# CSV Template Specification Source task: `plans/task-d.7.1.md` All supported CSV imports must run in two phases: 1. Preview: parse, validate, resolve references, detect duplicates, and produce an import report. 2. Commit: upsert in a transaction where database-only writes are involved. CSV imports must not bypass feature/foundation services once those services exist. Where current seed logic only exists in scripts, D.7.2+ should extract reusable server services first. ## Supported Templates ### `organizations.csv` Purpose: create tenant organizations. Target: `organizations`. Seed type: `organization`. Required columns: `organization_code`, `organization_name`, `slug`, `created_by_email`. Optional columns: `tax_id`, `default_currency`, `phone`, `email`, `address`, `logo_url`, `plan`, `status`. Generated columns: `id`, `created_at`, `updated_at`. Validation rules: `slug` lowercase and unique; `organization_code` uppercase; `created_by_email` must resolve to a user. Relationship resolution: `created_by_email -> users.id`. Duplicate detection: existing organization by `slug`. Upsert key: `slug`. Import order: `02`. ```csv organization_code,organization_name,slug,created_by_email,tax_id,default_currency,status ALLA,ALLA Public Company Limited,alla,admin@example.com,0107558000393,THB,active ``` Error examples: duplicate slug with conflicting name; missing creator user; invalid lowercase/uppercase format. ### `users.csv` Purpose: create setup-managed users. Target: `users` through auth/user service. Seed type: `organization`. Required columns: `email`, `name`, `system_role`. Optional columns: `password`, `active_organization_code`, `image_url`, `reset_password`. Generated columns: `id`, `password_hash`, `created_at`, `updated_at`. Validation rules: email format; `system_role` in `super_admin,user`; password required for new credential users unless invitation flow exists. Relationship resolution: `active_organization_code -> organizations.slug or setup organization_code`. Duplicate detection: existing user by email. Upsert key: `email`. Import order: `01`. ```csv email,name,system_role,password,active_organization_code,reset_password admin@example.com,System Administrator,super_admin,ChangeMe123!,ALLA,true ``` Error examples: invalid email; unsupported system role; existing user without reset permission. ### `memberships.csv` Purpose: grant app-level organization access. Target: `memberships`. Seed type: `organization`. Required columns: `organization_code`, `user_email`, `membership_role`, `business_role`. Optional columns: `permissions`, `branch_scope_codes`, `product_type_scope_codes`. Generated columns: `id`, timestamps. Validation rules: `membership_role` in `admin,user`; `business_role` should be a known CRM role fallback code; scope codes must resolve when provided. Relationship resolution: organization, user, branch options, product type options. Duplicate detection: existing membership by `organization_id + user_id`. Upsert key: `organization_code + user_email`. Import order: `03`. ```csv organization_code,user_email,membership_role,business_role,permissions,branch_scope_codes,product_type_scope_codes ALLA,admin@example.com,admin,crm_admin,crm.dashboard.read|crm.role.read,head_office,crane|dockdoor ``` Error examples: unknown user; unknown organization; invalid branch/product type code. ### `crm-role-assignments.csv` Purpose: assign CRM role profiles and CRM scopes. Target: `crm_user_role_assignments`. Seed type: `organization`. Required columns: `organization_code`, `user_email`, `crm_role_code`, `is_primary`, `branch_scope_mode`, `product_type_scope_mode`. Optional columns: `branch_codes`, `product_type_codes`, `is_active`. Generated columns: `id`, `assigned_at`, timestamps. Validation rules: role profile must exist; scope mode in `all,selected,none,inherit`; selected modes require matching codes. Relationship resolution: user, organization, role profile, branch options, product type options. Duplicate detection: existing assignment by `organization_id + user_id + role_profile_id`. Upsert key: `organization_code + user_email + crm_role_code`. Import order: `05`. ```csv organization_code,user_email,crm_role_code,is_primary,branch_scope_mode,branch_codes,product_type_scope_mode,product_type_codes,is_active ALLA,admin@example.com,crm_admin,true,all,,all,,true ``` Error examples: selected scope without codes; role profile missing; multiple primary rows for one user unless explicit replacement mode. ### `master-options.csv` Purpose: import governed option rows. Target: `ms_options`. Seed type: `foundation`. Required columns: `organization_code`, `category`, `code`, `label`. Optional columns: `value`, `parent_category`, `parent_code`, `sort_order`, `is_active`, `metadata_json`. Generated columns: `id`, timestamps. Validation rules: category/code non-empty; parent resolves in same organization when provided; metadata must be valid JSON. Relationship resolution: organization, optional parent option. Duplicate detection: existing option by `organization_id + category + code`. Upsert key: `organization_code + category + code`. Import order: `06`. ```csv organization_code,category,code,label,value,parent_category,parent_code,sort_order,is_active,metadata_json ALLA,crm_customer_group,industrial,Industrial,industrial,,,1,true,{} ``` Error examples: invalid JSON; missing parent option; duplicate category/code in same CSV with conflicting labels. ### `branches.csv` Purpose: typed convenience template for branch options. Target: `ms_options` with `category=crm_branch`. Seed type: `organization`. Required columns: `organization_code`, `branch_code`, `branch_name`. Optional columns: `prefix`, `address`, `tax_id`, `phone`, `email`, `sort_order`, `is_active`. Generated columns: `id`, `category`, timestamps. Validation rules: branch code unique per organization; code lowercase snake_case recommended; metadata fields serialized into option metadata. Relationship resolution: organization. Duplicate detection: existing `crm_branch` option by code. Upsert key: `organization_code + branch_code`. Import order: `07`. ```csv organization_code,branch_code,branch_name,prefix,address,tax_id,sort_order,is_active ALLA,head_office,Head Office,HO,99/9 Rama IX Road,0107558000393,1,true ``` Error examples: duplicate branch code; invalid organization; malformed metadata. ### `product-types.csv` Purpose: typed convenience template for product type options. Target: `ms_options` with `category=crm_product_type`. Seed type: `organization`. Required columns: `organization_code`, `product_type_code`, `product_type_name`. Optional columns: `document_prefix`, `sort_order`, `is_active`, `metadata_json`. Generated columns: `id`, `category`, timestamps. Validation rules: product type code unique per organization; `document_prefix` uppercase recommended. Relationship resolution: organization. Duplicate detection: existing `crm_product_type` option by code. Upsert key: `organization_code + product_type_code`. Import order: `08`. ```csv organization_code,product_type_code,product_type_name,document_prefix,sort_order,is_active ALLA,crane,Crane,CR,1,true ``` Error examples: duplicate product type code; invalid JSON metadata; unsupported inactive default product type for document sequence. ### `document-sequences.csv` Purpose: configure document numbering. Target: `document_sequences`. Seed type: `foundation`. Required columns: `organization_code`, `document_type`, `prefix`, `period`. Optional columns: `branch_code`, `product_type_code`, `current_number`, `padding_length`, `format`, `reset_policy`, `is_active`. Generated columns: `id`, timestamps. Validation rules: document type supported by sequence foundation; branch/product type resolve or default to generic scope; current number non-negative integer. Relationship resolution: organization, branch option, product type option. Duplicate detection: `organization_id + branch_id + product_type + document_type + period`. Upsert key: same as duplicate detection. Import order: `14`. ```csv organization_code,branch_code,product_type_code,document_type,prefix,period,current_number,padding_length,format,reset_policy,is_active ALLA,head_office,crane,quotation,QT-HO-CR-,2607,0,3,{prefix}{period}-{running},period,true ``` Error examples: unknown branch; unknown product type; malformed format missing `{running}`. ### `approval-workflows.csv` Purpose: configure approval workflow headers. Target: `crm_approval_workflows`. Seed type: `foundation`. Required columns: `organization_code`, `workflow_code`, `workflow_name`, `entity_type`. Optional columns: `description`, `is_system`, `is_active`. Generated columns: `id`, timestamps. Validation rules: workflow code unique per organization; entity type supported, currently `quotation`. Relationship resolution: organization. Duplicate detection: `organization_id + workflow_code`. Upsert key: `organization_code + workflow_code`. Import order: `15`. ```csv organization_code,workflow_code,workflow_name,entity_type,description,is_system,is_active ALLA,quotation_standard,Quotation Standard Approval,quotation,Default quotation approval,false,true ``` Error examples: duplicate workflow code; unsupported entity type. ### `approval-steps.csv` Purpose: configure workflow steps. Target: `crm_approval_steps`. Seed type: `foundation`. Required columns: `organization_code`, `workflow_code`, `step_number`, `role_code`, `role_name`. Optional columns: `approval_mode`, `is_required`, `sla_hours`, `calendar_mode`, `timeout_action`. Generated columns: `id`, timestamps. Validation rules: workflow resolves; step number positive integer; role code known to CRM role profiles or approved workflow roles. Relationship resolution: organization, approval workflow. Duplicate detection: `workflow_id + step_number`. Upsert key: `organization_code + workflow_code + step_number`. Import order: `16`. ```csv organization_code,workflow_code,step_number,role_code,role_name,approval_mode,is_required,sla_hours,calendar_mode,timeout_action ALLA,quotation_standard,1,sales_manager,Sales Manager,sequential,true,24,calendar_days,none ``` Error examples: missing workflow; duplicate step number; invalid SLA value. ### `approval-matrix.csv` Purpose: map approval workflows to entity/scope/amount. Target: `crm_approval_matrices`. Seed type: `foundation`. Required columns: `organization_code`, `entity_type`, `workflow_code`, `priority`, `is_default`. Optional columns: `branch_code`, `product_type_code`, `min_amount`, `max_amount`, `currency`, `is_active`, `description`. Generated columns: `id`, timestamps. Validation rules: workflow resolves; branch/product optional; amount range valid; at most one default per target policy unless replacement mode. Relationship resolution: organization, workflow, branch option, product type option. Duplicate detection: default matrix by `organization + entity_type + is_default` or scoped matrix by full scope/amount tuple. Upsert key: `organization_code + entity_type + workflow_code + priority + scope`. Import order: `17`. ```csv organization_code,entity_type,workflow_code,branch_code,product_type_code,min_amount,max_amount,currency,priority,is_default,is_active,description ALLA,quotation,quotation_standard,head_office,crane,0,5000000,THB,100,true,true,Default crane approval ``` Error examples: max less than min; missing workflow; unresolved currency option. ### `customers.csv` Purpose: import CRM customers. Target: `crm_customers`. Seed type: `business`. Required columns: `organization_code`, `customer_code`, `customer_name`, `customer_type`, `customer_status`, `created_by_email`. Optional columns: `branch_code`, `abbr`, `tax_id`, `address`, `province`, `district`, `sub_district`, `postal_code`, `country`, `phone`, `fax`, `email`, `website`, `lead_channel`, `customer_group`, `customer_sub_group`, `owner_user_email`, `notes`, `is_active`. Generated columns: `id`, timestamps, owner assignment timestamps if owner provided. Validation rules: code unique per organization; option references resolve; email format when provided. Relationship resolution: organization, branch, owner user, creator user, master options. Duplicate detection: `organization_id + customer_code`. Upsert key: `organization_code + customer_code`. Import order: `30`. ```csv organization_code,customer_code,customer_name,customer_type,customer_status,owner_user_email,created_by_email,branch_code,customer_group,email,phone ALLA,CUS-001,Siam Manufacturing,company,active,sales.crane.uat@alla.local,admin@example.com,head_office,industrial,contact@siam.example,02-000-1000 ``` Error examples: unresolved owner; invalid customer group/sub-group relationship; duplicate customer code. ### `contacts.csv` Purpose: import customer contacts. Target: `crm_customer_contacts`. Seed type: `business`. Required columns: `organization_code`, `customer_code`, `contact_name`, `created_by_email`. Optional columns: `position`, `department`, `phone`, `mobile`, `email`, `is_primary`, `notes`, `is_active`. Generated columns: `id`, timestamps. Validation rules: customer resolves; email format; one primary contact warning when multiple rows set primary. Relationship resolution: organization, customer, creator user. Duplicate detection: `organization + customer + email` when email exists, otherwise customer + contact name. Upsert key: `organization_code + customer_code + email/contact_name`. Import order: `32`. ```csv organization_code,customer_code,contact_name,email,mobile,position,is_primary,created_by_email ALLA,CUS-001,Anan Sales,anan@siam.example,081-000-0001,Procurement Manager,true,admin@example.com ``` Error examples: missing customer; duplicate primary contact warning; invalid email. ### `contact-shares.csv` Purpose: import contact sharing grants. Target: `crm_contact_shares`. Seed type: `business`. Required columns: `organization_code`, `customer_code`, `contact_email`, `shared_to_user_email`, `shared_by_user_email`. Optional columns: `remark`, `is_active`. Generated columns: `id`, `shared_at`, timestamps. Validation rules: contact resolves; users resolve; cannot share to user outside organization membership. Relationship resolution: organization, customer, contact, users. Duplicate detection: `organization_id + contact_id + shared_to_user_id`. Upsert key: same as duplicate detection. Import order: `33`. ```csv organization_code,customer_code,contact_email,shared_to_user_email,shared_by_user_email,remark,is_active ALLA,CUS-001,anan@siam.example,sales.dockdoor.uat@alla.local,admin@example.com,Shared for joint account coverage,true ``` Error examples: contact not found; target user has no membership; self-share warning. ### `leads.csv` Purpose: import marketing-owned leads. Target: `crm_leads`. Seed type: `business`. Required columns: `organization_code`, `lead_code`, `status`, `created_by_email`. Optional columns: `branch_code`, `customer_code`, `contact_email`, `description`, `project_name`, `project_location`, `lead_channel`, `product_type_code`, `priority`, `estimated_value`, `awareness_code`, `followup_status`, `lost_reason`, `outcome`, `owner_marketing_user_email`, `assigned_sales_owner_email`, `assignment_remark`. Generated columns: `id`, assignment timestamps when assigned, timestamps. Validation rules: status/priority/product type resolve; customer/contact relationship valid; estimated value numeric. Relationship resolution: organization, branch, customer, contact, users, master options. Duplicate detection: `organization_id + lead_code`. Upsert key: `organization_code + lead_code`. Import order: `34`. ```csv organization_code,lead_code,status,created_by_email,customer_code,contact_email,branch_code,product_type_code,priority,project_name,owner_marketing_user_email,assigned_sales_owner_email ALLA,LD-001,assigned,mk.uat@alla.local,CUS-001,anan@siam.example,head_office,crane,high,Warehouse Crane,mk.uat@alla.local,sales.crane.uat@alla.local ``` Error examples: contact does not belong to customer; unresolved status option; assignee outside organization. ### `opportunities.csv` Purpose: import sales opportunities. Target: `crm_opportunities`. Seed type: `business`. Required columns: `organization_code`, `opportunity_code`, `customer_code`, `title`, `product_type_code`, `status`, `priority`, `created_by_email`, `updated_by_email`. Optional columns: `lead_code`, `contact_email`, `branch_code`, `description`, `requirement`, `project_name`, `project_location`, `outcome_status`, `lead_channel`, `estimated_value`, `chance_percent`, `expected_close_date`, `competitor`, `source`, `notes`, `is_hot_project`, `pipeline_stage`, `assigned_to_user_email`. Generated columns: `id`, assignment timestamps when assigned, lifecycle timestamps where appropriate. Validation rules: product/status/priority resolve; percent 0-100; dates ISO `YYYY-MM-DD`; lead optional. Relationship resolution: organization, lead, customer, contact, users, options. Duplicate detection: `organization_id + opportunity_code`. Upsert key: `organization_code + opportunity_code`. Import order: `36`. ```csv organization_code,opportunity_code,customer_code,title,product_type_code,status,priority,created_by_email,updated_by_email,branch_code,contact_email,lead_code,chance_percent,assigned_to_user_email ALLA,OP-001,CUS-001,Warehouse Crane Expansion,crane,negotiation,high,sales.crane.uat@alla.local,sales.crane.uat@alla.local,head_office,anan@siam.example,LD-001,70,sales.crane.uat@alla.local ``` Error examples: chance percent outside range; unresolved lead; contact/customer mismatch. ### `opportunity-parties.csv` Purpose: import opportunity project parties. Target: `crm_opportunity_customers`. Seed type: `business`. Required columns: `organization_code`, `opportunity_code`, `customer_code`, `role_code`. Optional columns: `remark`. Generated columns: `id`, timestamps. Validation rules: opportunity/customer resolve; project party role resolves. Relationship resolution: opportunity, customer, role option. Duplicate detection: `opportunity_id + customer_id + role`. Upsert key: same as duplicate detection. Import order: `37`. ```csv organization_code,opportunity_code,customer_code,role_code,remark ALLA,OP-001,CUS-001,end_customer,Primary project owner ``` Error examples: unknown role; opportunity not found; duplicate role/customer row. ### `quotations.csv` Purpose: import quotation headers. Target: `crm_quotations`. Seed type: `business`. Required columns: `organization_code`, `quotation_code`, `customer_code`, `quotation_date`, `quotation_type`, `status`, `currency`, `subtotal`, `total_amount`, `created_by_email`, `updated_by_email`. Optional columns: `opportunity_code`, `contact_email`, `branch_code`, `valid_until`, `project_name`, `project_location`, `attention`, `reference`, `notes`, `revision`, `parent_quotation_code`, `revision_remark`, `exchange_rate`, `discount`, `discount_type`, `tax_rate`, `tax_amount`, `chance_percent`, `is_hot_project`, `competitor`, `salesman_email`, `is_sent`, `sent_at`, `sent_via`. Generated columns: `id`, timestamps, approval/snapshot/artifact fields remain runtime generated. Validation rules: status/currency/type resolve; amounts numeric; parent quotation resolves for revision imports. Relationship resolution: organization, opportunity, customer, contact, branch, options, users. Duplicate detection: `organization_id + quotation_code`. Upsert key: `organization_code + quotation_code`. Import order: `40`. ```csv organization_code,quotation_code,customer_code,quotation_date,quotation_type,status,currency,subtotal,total_amount,created_by_email,updated_by_email,opportunity_code,branch_code,salesman_email ALLA,QT-HO-CR-2607-001,CUS-001,2026-07-02,crane,draft,THB,1000000,1070000,sales.crane.uat@alla.local,sales.crane.uat@alla.local,OP-001,head_office,sales.crane.uat@alla.local ``` Error examples: parent revision missing; total mismatch warning; approved/runtime fields supplied. ### `quotation-items.csv` Purpose: import quotation line items. Target: `crm_quotation_items`. Seed type: `business`. Required columns: `organization_code`, `quotation_code`, `item_number`, `product_type_code`, `description`, `quantity`, `unit_price`, `total_price`. Optional columns: `unit`, `discount`, `discount_type`, `tax_rate`, `notes`, `sort_order`. Generated columns: `id`, timestamps. Validation rules: numeric amount fields; product type resolves; quotation resolves. Relationship resolution: quotation, product type, unit, discount type. Duplicate detection: `quotation_id + item_number`. Upsert key: `organization_code + quotation_code + item_number`. Import order: `41`. ```csv organization_code,quotation_code,item_number,product_type_code,description,quantity,unit,unit_price,discount,discount_type,tax_rate,total_price,sort_order ALLA,QT-HO-CR-2607-001,1,crane,Crane installation package,1,set,1000000,0,,7,1000000,1 ``` Error examples: missing quotation; invalid item number; total calculation warning. ### `quotation-parties.csv` Purpose: import quotation project parties. Target: `crm_quotation_customers`. Seed type: `business`. Required columns: `organization_code`, `quotation_code`, `customer_code`, `role_code`. Optional columns: `is_primary`, `remark`. Generated columns: `id`, timestamps. Validation rules: quotation/customer resolve; role resolves; one primary warning per role as applicable. Relationship resolution: quotation, customer, project party role option. Duplicate detection: `quotation_id + customer_id + role`. Upsert key: same as duplicate detection. Import order: `42`. ```csv organization_code,quotation_code,customer_code,role_code,is_primary,remark ALLA,QT-HO-CR-2607-001,CUS-001,end_customer,true,Primary quotation customer ``` Error examples: missing quotation; unknown project party role; duplicate primary warning. ### `quotation-topics.csv` Purpose: import quotation topic headers. Target: `crm_quotation_topics`. Seed type: `business`. Required columns: `organization_code`, `quotation_code`, `topic_key`, `topic_type`, `title`. Optional columns: `sort_order`. Generated columns: `id`, timestamps. Validation rules: quotation resolves; topic type resolves; topic key unique per quotation CSV batch. Relationship resolution: quotation, topic type option. Duplicate detection: `quotation_id + topic_key`. Upsert key: `organization_code + quotation_code + topic_key`. Import order: `43`. ```csv organization_code,quotation_code,topic_key,topic_type,title,sort_order ALLA,QT-HO-CR-2607-001,scope-main,scope,Scope of Work,1 ``` Error examples: topic type missing; duplicate topic key. ### `quotation-topic-items.csv` Purpose: import quotation topic rows. Target: `crm_quotation_topic_items`. Seed type: `business`. Required columns: `organization_code`, `quotation_code`, `topic_key`, `content`. Optional columns: `sort_order`. Generated columns: `id`, timestamps. Validation rules: topic key resolves from imported or existing topic; content required. Relationship resolution: quotation topic. Duplicate detection: `topic_id + sort_order + content`. Upsert key: `organization_code + quotation_code + topic_key + sort_order`. Import order: `44`. ```csv organization_code,quotation_code,topic_key,content,sort_order ALLA,QT-HO-CR-2607-001,scope-main,Supply and install one overhead crane system,1 ``` Error examples: missing topic; empty content; duplicate sort order warning. ## Unsupported or Deferred Templates ### `departments.csv` Status: unsupported pending schema. No first-class department table was identified. Do not map this to ad hoc options without an ADR. ```csv department_code,department_name SALES,Sales ``` ### `teams.csv` Status: unsupported pending schema. Team scope limitations are documented; no first-class team graph exists. ```csv team_code,team_name SALES-CRANE,Crane Sales Team ``` ### `warehouses.csv` Status: unsupported pending schema. No warehouse table was identified. ```csv warehouse_code,warehouse_name MAIN,Main Warehouse ``` ### `sites.csv` Status: unsupported pending schema. Project/customer site is currently represented by text fields such as `project_location`, not a reusable site entity. ```csv site_code,site_name,address BKK-HQ,Bangkok HQ,99/9 Rama IX Road ``` ### `services.csv` Status: unsupported pending schema. Service catalog does not currently exist as first-class CRM data. ```csv service_code,service_name INSTALL,Installation Service ``` ### `price-books.csv` Status: unsupported pending schema. Current quotation items are not backed by a price-book foundation. ```csv price_book_code,product_or_service_code,currency,unit_price STD-THB,INSTALL,THB,10000 ``` ### `tax-rates.csv` Status: unsupported pending schema. Tax rate currently lives as numeric fields on quotation/item rows. ```csv tax_code,tax_name,tax_rate VAT7,VAT 7%,7 ``` ### `attachments.csv` Status: deferred. Attachment metadata exists, but binary upload/storage import is not safe as a generic CSV-only flow. ```csv entity_type,entity_code,file_path,description quotation,QT-HO-CR-2607-001,./files/spec.pdf,Customer specification ``` ### `email-settings.csv` Status: unsupported pending schema. Email configuration appears environment/provider driven; verification can check readiness but CSV import should not persist secrets without a secure configuration design. ```csv provider,host,port,from_address smtp,smtp.example.com,587,no-reply@example.com ```