# CSV Import Mapping

This document maps supported setup CSV columns to target fields and reference resolvers.

Common resolver rules:

- `organization_code` resolves to the setup organization by wizard code, organization slug, or import context alias.
- `*_email` resolves to `users.email`.
- `branch_code` resolves to `ms_options` where `category = crm_branch`.
- `product_type_code` resolves to `ms_options` where `category = crm_product_type`.
- Master option values resolve by category and code unless a target field explicitly stores plain text.
- All imported rows are organization-scoped unless noted otherwise.

## `organizations.csv`

| CSV Column | Target Field | Transform | Validation | Required | Reference Resolver | Fallback | Notes |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `organization_code` | setup alias / metadata | trim uppercase | unique in import batch | yes | none | none | Not currently a DB column; used by setup import context. |
| `organization_name` | `organizations.name` | trim | non-empty | yes | none | none | |
| `slug` | `organizations.slug` | trim lowercase | unique | yes | `organizations.slug` duplicate check | generated from name only in wizard mode | |
| `created_by_email` | `organizations.created_by` | lowercase email | user exists | yes | `users.email -> id` | current setup actor | |
| `plan` | `organizations.plan` | trim lowercase | known plan string | no | none | `enterprise` for setup-created org | |
| `logo_url` | `organizations.image_url` | trim | URL or relative path | no | none | null | |

## `users.csv`

| CSV Column | Target Field | Transform | Validation | Required | Reference Resolver | Fallback | Notes |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `email` | `users.email` | trim lowercase | email format, unique | yes | duplicate by email | none | Upsert key. |
| `name` | `users.name` | trim | non-empty | yes | none | email local part | |
| `system_role` | `users.system_role` | trim lowercase | `super_admin,user` | yes | none | `user` | |
| `password` | `users.password_hash` | bcrypt hash | required for new credentials user | no | none | generated invite flow later | Do not store raw password. |
| `active_organization_code` | `users.active_organization_id` | resolve | org exists | no | organization resolver | null | |
| `image_url` | `users.image` | trim | URL optional | no | none | null | |
| `reset_password` | import behavior | boolean | true/false | no | none | false | Allows password update for existing users. |

## `memberships.csv`

| CSV Column | Target Field | Transform | Validation | Required | Reference Resolver | Fallback | Notes |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `organization_code` | `memberships.organization_id` | resolve | org exists | yes | organization resolver | setup org | |
| `user_email` | `memberships.user_id` | lowercase | user exists | yes | `users.email` | none | |
| `membership_role` | `memberships.role` | lowercase | `admin,user` | yes | none | `user` | |
| `business_role` | `memberships.business_role` | lowercase | known CRM fallback role | yes | role definition code | `sales_support` | Compatibility fallback only. |
| `permissions` | `memberships.permissions` | pipe split | known permission codes | no | `src/lib/auth/rbac.ts` | default permissions | |
| `branch_scope_codes` | `memberships.branch_scope_ids` | pipe split resolve | branch options exist | no | `crm_branch` options | empty array | Compatibility scope only. |
| `product_type_scope_codes` | `memberships.product_type_scope_ids` | pipe split resolve | product type options exist | no | `crm_product_type` options | empty array | Compatibility scope only. |

## `crm-role-assignments.csv`

| CSV Column | Target Field | Transform | Validation | Required | Reference Resolver | Fallback | Notes |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `organization_code` | `organization_id` | resolve | org exists | yes | organization resolver | setup org | |
| `user_email` | `user_id` | lowercase | user + membership exist | yes | user resolver | none | |
| `crm_role_code` | `role_profile_id` | lowercase | role profile exists | yes | `crm_role_profiles.code` | none | |
| `is_primary` | `is_primary` | boolean | true/false | yes | none | false | One primary per user warning/enforcement. |
| `branch_scope_mode` | `branch_scope_mode` | lowercase | `all,selected,none,inherit` | yes | none | `inherit` | |
| `branch_codes` | `branch_scope_ids` | pipe split resolve | required when mode selected | no | `crm_branch` options | empty array | |
| `product_type_scope_mode` | `product_type_scope_mode` | lowercase | `all,selected,none,inherit` | yes | none | `inherit` | |
| `product_type_codes` | `product_type_scope_ids` | pipe split resolve | required when mode selected | no | `crm_product_type` options | empty array | |
| `is_active` | `is_active` | boolean | true/false | no | none | true | |

## `master-options.csv`

| CSV Column | Target Field | Transform | Validation | Required | Reference Resolver | Fallback | Notes |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `organization_code` | `organization_id` | resolve | org exists | yes | organization resolver | setup org | |
| `category` | `category` | trim lowercase | non-empty | yes | none | none | |
| `code` | `code` | trim | non-empty unique per category | yes | duplicate check | none | |
| `label` | `label` | trim | non-empty | yes | none | none | |
| `value` | `value` | trim | optional | no | none | same as code | |
| `parent_category` | parent lookup | trim | parent requires parent_code | no | `ms_options.category` | null | |
| `parent_code` | `parent_id` | resolve | parent exists | no | `ms_options.category+code` | null | |
| `sort_order` | `sort_order` | integer | >= 0 | no | none | 0 | |
| `is_active` | `is_active` | boolean | true/false | no | none | true | |
| `metadata_json` | `metadata` | JSON parse | valid object | no | none | null | |

## `branches.csv`

Maps to `ms_options` with fixed `category = crm_branch`.

| CSV Column | Target Field | Transform | Validation | Required | Reference Resolver | Fallback | Notes |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `organization_code` | `organization_id` | resolve | org exists | yes | organization resolver | setup org | |
| `branch_code` | `code`, `value` | trim lowercase snake_case | unique per org | yes | duplicate check | none | |
| `branch_name` | `label` | trim | non-empty | yes | none | none | |
| `prefix` | `metadata.prefix` | trim uppercase | optional | no | none | first letters of code | Used by sequence suggestions only. |
| `address` | `metadata.address` | trim | optional | no | none | null | |
| `tax_id` | `metadata.taxId` | trim | optional | no | none | null | |
| `phone` | `metadata.phone` | trim | optional | no | none | null | |
| `email` | `metadata.email` | lowercase | email optional | no | none | null | |
| `sort_order` | `sort_order` | integer | >= 0 | no | none | 0 | |
| `is_active` | `is_active` | boolean | true/false | no | none | true | |

## `product-types.csv`

Maps to `ms_options` with fixed `category = crm_product_type`.

| CSV Column | Target Field | Transform | Validation | Required | Reference Resolver | Fallback | Notes |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `organization_code` | `organization_id` | resolve | org exists | yes | organization resolver | setup org | |
| `product_type_code` | `code`, `value` | lowercase snake_case | unique per org | yes | duplicate check | none | |
| `product_type_name` | `label` | trim | non-empty | yes | none | none | |
| `document_prefix` | `metadata.documentPrefix` | uppercase | optional | no | none | derived from code | Used for sequence suggestion. |
| `sort_order` | `sort_order` | integer | >= 0 | no | none | 0 | |
| `is_active` | `is_active` | boolean | true/false | no | none | true | |
| `metadata_json` | `metadata` merge | JSON parse | object | no | none | null | Merged after `documentPrefix`. |

## `document-sequences.csv`

| CSV Column | Target Field | Transform | Validation | Required | Reference Resolver | Fallback | Notes |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `organization_code` | `organization_id` | resolve | org exists | yes | organization resolver | setup org | |
| `branch_code` | `branch_id` | resolve | option exists when provided | no | `crm_branch` option | empty string | Empty string means generic branch scope. |
| `product_type_code` | `product_type` | normalize | option exists or `all` | no | `crm_product_type` option/code | `all` | Store normalized code, not option id. |
| `document_type` | `document_type` | lowercase | supported by sequence foundation | yes | none | none | |
| `prefix` | `prefix` | trim | non-empty | yes | none | none | |
| `period` | `period` | trim | matches active period format | yes | none | generated current YYMM later | |
| `current_number` | `current_number` | integer | >= 0 | no | none | 0 | |
| `padding_length` | `padding_length` | integer | 1-12 | no | none | 3 | |
| `format` | `format` | trim | contains `{running}` | no | none | `{prefix}{period}-{running}` | |
| `reset_policy` | `reset_policy` | lowercase | known reset policy | no | none | `period` | |
| `is_active` | `is_active` | boolean | true/false | no | none | true | |

## `approval-workflows.csv`

| CSV Column | Target Field | Transform | Validation | Required | Reference Resolver | Fallback | Notes |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `organization_code` | `organization_id` | resolve | org exists | yes | organization resolver | setup org | |
| `workflow_code` | `code` | lowercase snake_case | unique per org | yes | duplicate check | none | |
| `workflow_name` | `name` | trim | non-empty | yes | none | none | |
| `entity_type` | `entity_type` | lowercase | `quotation` initially | yes | none | `quotation` | |
| `description` | `description` | trim | optional | no | none | null | |
| `is_system` | `is_system` | boolean | true/false | no | none | false | |
| `is_active` | `is_active` | boolean | true/false | no | none | true | |

## `approval-steps.csv`

| CSV Column | Target Field | Transform | Validation | Required | Reference Resolver | Fallback | Notes |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `organization_code` | `organization_id` | resolve | org exists | yes | organization resolver | setup org | |
| `workflow_code` | `workflow_id` | resolve | workflow exists | yes | approval workflow | none | |
| `step_number` | `step_number` | integer | > 0 | yes | none | none | |
| `role_code` | `role_code` | lowercase | role code known | yes | role profile optional check | none | |
| `role_name` | `role_name` | trim | non-empty | yes | none | label from role profile | |
| `approval_mode` | `approval_mode` | lowercase | known mode | no | none | `sequential` | |
| `is_required` | `is_required` | boolean | true/false | no | none | true | |
| `sla_hours` | `sla_hours` | integer | >= 0 | no | none | 24 | |
| `calendar_mode` | `calendar_mode` | lowercase | known mode | no | none | `calendar_days` | |
| `timeout_action` | `timeout_action` | lowercase | known action | no | none | `none` | |

## `approval-matrix.csv`

| CSV Column | Target Field | Transform | Validation | Required | Reference Resolver | Fallback | Notes |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `organization_code` | `organization_id` | resolve | org exists | yes | organization resolver | setup org | |
| `entity_type` | `entity_type` | lowercase | `quotation` initially | yes | none | quotation | |
| `workflow_code` | `workflow_id` | resolve | workflow exists | yes | approval workflow | none | |
| `branch_code` | `branch_id` | resolve | optional branch exists | no | branch option | null | Null means any branch. |
| `product_type_code` | `product_type` | normalize | optional product type exists | no | product type option/code | null | |
| `min_amount` | `min_amount` | decimal | <= max when both exist | no | none | null | |
| `max_amount` | `max_amount` | decimal | >= min when both exist | no | none | null | |
| `currency` | `currency` | uppercase | currency option exists | no | `crm_currency` | null | |
| `priority` | `priority` | integer | >= 0 | yes | none | 100 | |
| `is_default` | `is_default` | boolean | true/false | yes | none | false | |
| `is_active` | `is_active` | boolean | true/false | no | none | true | |
| `description` | `description` | trim | optional | no | none | null | |

## CRM Business CSVs

The following mappings use the same resolver pattern:

| CSV File | Business Key | Required Reference Resolvers | Important Option Resolvers | Generated Fields | Notes |
| --- | --- | --- | --- | --- | --- |
| `customers.csv` | `organization_code + customer_code` | organization, branch, owner user, created_by user | `crm_customer_type`, `crm_customer_status`, `crm_customer_group`, `crm_customer_sub_group`, `crm_lead_channel` | `id`, timestamps, owner assignment timestamps | Owner history should be generated by service behavior. |
| `contacts.csv` | `organization_code + customer_code + email/contact_name` | organization, customer, created_by user | none | `id`, timestamps | Contact must belong to resolved customer. |
| `contact-shares.csv` | `organization + contact + shared_to_user` | organization, customer, contact, shared users | none | `id`, `shared_at`, timestamps | Target user must have organization membership. |
| `leads.csv` | `organization_code + lead_code` | organization, branch, customer, contact, creator, marketing owner, sales assignee | `crm_lead_status`, `crm_lead_followup_status`, `crm_lead_lost_reason`, `crm_product_type`, `crm_priority`, `crm_lead_awareness`, `crm_lead_channel` | `id`, timestamps, assignment timestamps | Lead outcome defaults to `open`. |
| `opportunities.csv` | `organization_code + opportunity_code` | organization, lead, customer, contact, creator, updater, assignee | `crm_product_type`, `crm_opportunity_status`, `crm_priority`, `crm_lead_channel`, outcome/cancel/no-quotation reasons | `id`, timestamps, assignment/lifecycle timestamps | Validate contact/customer and optional lead lineage. |
| `opportunity-parties.csv` | `opportunity + customer + role_code` | organization, opportunity, customer | `crm_project_party_role` | `id`, timestamps | Used by revenue attribution. |
| `quotations.csv` | `organization_code + quotation_code` | organization, opportunity, customer, contact, branch, users, parent quotation | `crm_quotation_type`, `crm_quotation_status`, `crm_currency`, `crm_discount_type`, `crm_sent_via` | `id`, timestamps | Runtime approval/PDF fields must not be accepted from CSV. |
| `quotation-items.csv` | `quotation + item_number` | organization, quotation | `crm_product_type`, `crm_unit`, `crm_discount_type` | `id`, timestamps | Validate numeric price fields. |
| `quotation-parties.csv` | `quotation + customer + role_code` | organization, quotation, customer | `crm_project_party_role` | `id`, timestamps | Used by quotation reporting/PDF context. |
| `quotation-topics.csv` | `quotation + topic_key` | organization, quotation | `crm_quotation_topic_type` | `id`, timestamps | `topic_key` is import-only alias. |
| `quotation-topic-items.csv` | `quotation + topic_key + sort_order` | organization, quotation topic by import alias or existing lookup | none | `id`, timestamps | Topic must be preview-resolved before commit. |

Business CSV field transforms:

- Dates: accept `YYYY-MM-DD`; API responses remain ISO UTC.
- Booleans: accept `true,false,1,0,yes,no`; normalize to boolean.
- Amounts: parse decimal with `.`; reject thousands separators in strict mode.
- Pipe lists: split on `|`, trim, reject empty tokens in strict mode.
- Empty string: normalize to `null` for nullable DB fields.

Business CSV fallback rules:

- `created_by_email` may fall back to setup actor only when the CSV file explicitly omits it and the import run is wizard-owned.
- `updated_by_email` may fall back to `created_by_email`.
- `branch_code` may fall back to the wizard default branch.
- `currency` may fall back to organization default currency once that is persisted in setup state or organization metadata.
- Option fields should not silently create new options unless import mode is `allowOptionCreate`.

## Unsupported Mapping Decisions

| CSV | Mapping Decision |
| --- | --- |
| `departments.csv` | Block. No first-class schema. |
| `teams.csv` | Block. No first-class team graph; security docs note team-scope limitations. |
| `warehouses.csv` | Block. No first-class schema. |
| `sites.csv` | Block. Current project location is text on lead/opportunity/quotation. |
| `services.csv` | Block. No first-class service catalog. |
| `price-books.csv` | Block. No price book foundation. |
| `tax-rates.csv` | Block. No tax rate table; quotation/item rows carry tax numbers. |
| `attachments.csv` | Defer. Needs binary upload/storage import design, not pure CSV. |
| `email-settings.csv` | Block. No secure first-class email settings table. |
