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