# 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. |