diff --git a/docs/implementation/task-d7-initial-system-setup-audit.md b/docs/implementation/task-d7-initial-system-setup-audit.md new file mode 100644 index 0000000..db5c507 --- /dev/null +++ b/docs/implementation/task-d7-initial-system-setup-audit.md @@ -0,0 +1,402 @@ +# Task D.7 Initial System Setup Wizard and Seed Framework - Phase 1/2 Audit + +Date: 2026-07-02 +Status: Phase 1 and Phase 2 audit complete. Implementation intentionally not started. + +## Scope + +Task D.7 asks for a production initialization framework for a brand-new ALLA OS installation, including seed dependency analysis, CSV templates, a reusable CSV import engine, Setup Wizard, verification, reset/reseed support, and documentation. + +The task contract explicitly says no implementation should begin until the current system audit and seed dependency report are complete. This document records that required first pass. + +## Documents Reviewed + +- `AGENTS.md` +- `plans/task-d.7.md` +- `docs/standards/task-contract-template.md` +- `docs/standards/task-catalog.md` +- `docs/standards/project-foundations.md` +- `docs/standards/architecture-rules.md` +- `docs/standards/ui-ux-rules.md` +- `docs/standards/task-review-checklist.md` +- `docs/adr/*.md` +- `docs/business/*.md` +- `docs/security/*.md` +- existing implementation notes under `docs/implementation/**` + +## Code Areas Reviewed + +- `src/db/schema.ts` +- `drizzle/*.sql` +- `src/db/seeds/**` +- `scripts/seed-system.ts` +- `scripts/seed-uat.ts` +- `scripts/seed-reset.ts` +- `scripts/db/reset-database.ts` +- `scripts/lib/seed-utils.ts` +- `src/features/foundation/**` +- `src/features/crm/**` +- `src/features/setup/components/setup-wizard.tsx` +- existing API routes under `src/app/api/**` +- existing dashboard routes under `src/app/dashboard/**` +- `src/config/nav-config.ts` + +## Existing Seed and Reset Inventory + +| Area | Existing file | Current behavior | Reuse decision | +| --- | --- | --- | --- | +| Super admin | `scripts/seed-super-admin.js` | Creates or updates the bootstrap admin from environment variables. | Reuse. Wizard should call or wrap this behavior server-side. | +| System seed runner | `scripts/seed-system.ts` | Runs super admin, system organization, foundation seed, and PDF template reseed. | Reuse as foundation install orchestration baseline. | +| System organization | `src/db/seeds/system-organization.seed.ts` | Creates deterministic `ALLA Demo` organization and super-admin membership. | Reuse logic, but D.7 needs user-provided organization inputs instead of hardcoded demo values. | +| Foundation data | `src/db/seeds/foundation.seed.ts` | Idempotently seeds master options, document sequences, approval workflows/matrices, document templates, document libraries, notification templates, and report definitions per organization. | Reuse and split into callable services before wizard execution. | +| UAT users | `src/db/seeds/uat-users.seed.ts` | Seeds marketing, sales, manager, CEO, and admin UAT users with CRM role assignments. | Reuse for Demo/UAT mode only. Do not mix with production foundation seed. | +| CRM UAT data | `src/db/seeds/crm-uat.seed.ts` | Seeds customers, contacts, leads, opportunities, quotations, approval requests/actions, followups, project parties, and sequence bumps. | Reuse for Demo/UAT mode only. Needs transaction wrapper and import/report output. | +| UAT runner | `scripts/seed-uat.ts` | Runs UAT users then CRM UAT data. | Reuse as demo seed orchestration baseline. | +| Seed reset | `scripts/seed-reset.ts` | Guarded full database reset, migrate, system seed, UAT seed. | Extend rather than duplicate. D.7 needs narrower reset scopes. | +| DB reset | `scripts/db/reset-database.ts` | Guarded schema drop/recreate, blocked in production and unsafe targets. | Keep guard contract. D.7 reset engine must preserve explicit confirmation and production blocking. | + +## Existing Setup UI + +`src/features/setup/components/setup-wizard.tsx` already exists, but it is a client-only template onboarding helper. It generates `.env.local` values and points users to example dashboards. + +It does not currently provide: + +- dashboard administration route +- server readiness checks +- seed execution API +- CSV template generation +- CSV upload, preview, validation, or rollback +- organization/branch/product type initialization +- document sequence setup +- administrator creation through app-owned services +- verification report +- setup resume state + +Reuse decision: treat it as historical UI prototype only. D.7 should build a dashboard-native Administration Setup Wizard using `PageContainer`, shadcn/ui primitives, server route handlers, and setup feature services. + +## Seed Classification + +### Required Foundation Seed + +These are required for a production install to operate: + +- users and initial super admin +- organizations +- memberships +- CRM role profiles +- CRM user role assignments for the first administrator +- master options in `ms_options` +- branch options in `ms_options` category `crm_branch` +- product type options in `ms_options` category `crm_product_type` +- currencies, units, statuses, priorities, lead channels, lost reasons, job titles, project party roles +- document sequences for customer, lead, opportunity, quotation, and approval-related numbering +- approval workflows +- approval steps +- approval matrices +- document templates and template versions +- document template mappings and table columns +- document libraries and library versions +- notification templates +- report definitions + +### Optional Configuration Seed + +These may be required by deployment policy but are not always required before first login: + +- Sentry configuration +- email provider configuration +- storage provider configuration beyond local defaults +- PDF visual assets or organization-specific logos +- additional document libraries +- additional branch/team/warehouse abstractions once modeled as first-class domains +- price books, tax rates, and product/service catalogs, because current schema has only the generic `products` table and CRM quotation items use free-form item data plus master options + +### Demo / UAT Seed + +These are already represented by `uat-users.seed.ts` and `crm-uat.seed.ts`: + +- Marketing user +- Sales users by product type +- Sales manager +- CEO/top manager +- CRM admin +- customers +- contacts +- leads +- opportunities +- quotations across approved, rejected, pending, returned, sent, cancelled, won/lost/no-quotation scenarios +- followups +- approval requests and actions +- quotation items, project parties, topics + +### Generated Runtime Data + +These should not be manually seeded except through controlled fixture generation: + +- audit logs, except explicit UAT followup/audit fixture rows +- notification events, dispatches, deliveries +- approval requests and approval actions in production +- document artifacts for generated approved PDFs +- quotation approved snapshots +- sequence current numbers, except after deterministic demo data creation + +### Runtime Only + +These are produced by application behavior and should not be foundation seed inputs: + +- active user sessions +- notification deliveries +- audit security-denial events +- generated PDFs and immutable approved artifacts +- uploaded binary attachment storage objects +- report export output files + +## Dependency Graph + +```text +Environment + -> Database migration status + -> Storage/PDF/email configuration checks + -> Super admin user + -> Organization + -> Membership + -> CRM role profiles + -> CRM user role assignments + -> Master options + -> Branch scope options + -> Product type options + -> Currency/unit/status/priority/job title/project party options + -> Document sequences + -> Customer codes + -> Lead codes + -> Opportunity codes + -> Quotation codes + -> Approval workflows + -> Approval steps + -> Approval matrices + -> Document templates + -> Template versions + -> Template mappings + -> Table-column mappings + -> Document libraries + -> Library versions + -> Storage provider objects + -> Notification templates + -> Report definitions + +Demo/UAT: +Organization foundation + -> UAT users + -> UAT memberships + -> UAT CRM role assignments + -> Customers + -> Customer owner history + -> Contacts + -> Contact sharing, optional + -> Leads + -> Lead followup audit rows + -> Opportunities + -> Opportunity project parties + -> Opportunity followups + -> Quotations + -> Quotation items + -> Quotation project parties + -> Quotation topics + -> Quotation topic items + -> Quotation followups + -> Approval requests + -> Approval actions + -> Document sequence bumps +``` + +## Recommended Seed Order + +1. Validate environment: `AUTH_SECRET`, `DATABASE_URL`, storage, upload directory, PDF template assets, email settings. +2. Validate migrations are applied. +3. Create first system administrator. +4. Create organization from wizard input. +5. Create administrator membership in the organization. +6. Seed CRM role profiles for the organization. +7. Assign CRM administrator role assignment to first administrator. +8. Seed master options, including branch and product type options. +9. Seed document sequences using organization, branch, product type, document type, and period scope. +10. Seed approval workflows and steps. +11. Seed approval matrices. +12. Seed document templates, versions, mappings, and table columns. +13. Seed document libraries through the storage provider. +14. Seed notification templates. +15. Seed report definitions. +16. Optionally import CSV master/business data. +17. Optionally run Demo/UAT seed. +18. Run verification checks and persist setup summary/report. + +## Circular Dependency Check + +No hard circular dependency was found in the existing seed flow. + +Important dependency edges: + +- Foundation seed requires at least one organization and `created_by` user. +- Document sequence seed requires branch/product type options from master options. +- UAT user role assignments require role profiles and branch/product type options. +- CRM UAT data requires UAT users, master options, branches, approval workflow, customers, contacts, leads, opportunities, and quotations in order. +- Approval actions require approval requests. +- Document library seed writes storage objects and therefore depends on storage provider readiness. + +Potential soft cycle to avoid: + +- First administrator creation needs permissions/role assignment, but role profiles are organization-scoped and need an organization. Resolve by creating user, organization, membership, then role profiles, then role assignment. + +## Existing Coverage Matrix + +| Requirement from Task D.7 | Current coverage | Gap | +| --- | --- | --- | +| Foundation seed | Strong script coverage in `foundation.seed.ts`. | Not exposed as service/API/wizard step. | +| Roles and permissions | `src/lib/auth/rbac.ts`, role profiles, assignments. | Wizard needs first-admin role assignment flow. | +| Master options | Strong coverage through `ms_options`. | CSV template/import for governed options missing. | +| Branch scope | Implemented as `ms_options` category `crm_branch`. | Wizard asks branch/department/team/warehouse, but only branch is modeled. Department/team/warehouse need scope decision. | +| Product type scope | Implemented as `ms_options` category `crm_product_type`. | Wizard needs product-type-aware sequence setup UI. | +| Document sequence | Strong foundation and APIs exist. | Wizard orchestration and verification missing. | +| Approval workflow | Strong foundation and settings APIs exist. | Wizard initialization/verification missing. | +| Storage | Foundation exists. | Readiness check and seed report integration missing. | +| PDF configuration | Foundation and audit docs exist. | Setup verification should call template inventory/mapping checks. | +| Report foundation | Existing definitions and services. | Setup should verify report definitions seeded. | +| Demo/UAT seed | Strong script coverage. | Needs deterministic report, transaction boundary, and wizard separation from production seed. | +| CSV templates | Not present. | Need `/setup/templates/*.csv` generator or checked-in templates. | +| Generic CSV import engine | Not present. | Need reusable setup/import foundation. | +| Setup Wizard route | Prototype component only. | Need dashboard admin page, route handlers, state persistence. | +| Verification engine | Not present as setup feature. | Need checks for login, permissions, org, branch, sequences, approval, storage, PDF, email. | +| Reset/reseed engine | Full reset exists. | Need scoped reset: foundation, organization, demo, business data. | + +## Missing or Ambiguous Seed Data + +- `branches.csv` can map to `ms_options` today, not a first-class branch table. +- `departments.csv`, `teams.csv`, and `warehouses.csv` have no obvious first-class schema in the reviewed model. +- `roles.csv` and `permissions.csv` should map to CRM role profiles and static permission definitions; permissions are code-defined in `src/lib/auth/rbac.ts`, not a database table. +- `products.csv`, `services.csv`, `price-books.csv`, `tax-rates.csv`, and `sites.csv` are not fully represented as CRM-owned first-class schemas. Current quotation item/product behavior does not equal a production price book. +- Attachments have metadata tables, but upload/storage transport is not a generic CSV-importable business seed. +- Email configuration appears environment/provider-driven; no dedicated setup table was identified. +- There is no setup state table for resume setup or setup summary persistence. + +## Recommended Architecture for Next Phase + +Create a setup feature that wraps existing foundations instead of duplicating them: + +```text +src/features/setup/ + api/ + types.ts + service.ts + queries.ts + mutations.ts + server/ + readiness.service.ts + seed.service.ts + csv-template.service.ts + csv-import.service.ts + verification.service.ts + reset.service.ts + components/ + setup-wizard.tsx + import-preview-table.tsx + verification-summary.tsx +``` + +Route handlers should live under: + +```text +src/app/api/setup/readiness/route.ts +src/app/api/setup/foundation/route.ts +src/app/api/setup/templates/route.ts +src/app/api/setup/import/preview/route.ts +src/app/api/setup/import/commit/route.ts +src/app/api/setup/verify/route.ts +src/app/api/setup/reset/route.ts +``` + +Dashboard page: + +```text +src/app/dashboard/administration/setup/page.tsx +``` + +Key rules for implementation: + +- service layer owns business logic +- route handlers remain thin +- seed execution must be transaction-aware where database writes happen +- storage writes need compensation/reporting because they cannot participate in the DB transaction +- CSV import must preview and validate before commit +- reset operations must preserve the existing explicit confirmation and production-safety guard +- CRM authorization must use existing resolved access helpers, not raw role branching +- audit setup mutations using the audit foundation once setup APIs exist + +## CSV Template Scope Recommendation + +Start with templates that map to existing schema and foundations: + +- `organizations.csv` +- `users.csv` +- `memberships.csv` +- `crm-role-assignments.csv` +- `master-options.csv` +- `branches.csv` as a typed `master-options` template for `crm_branch` +- `product-types.csv` as a typed `master-options` template for `crm_product_type` +- `document-sequences.csv` +- `approval-workflows.csv` +- `approval-steps.csv` +- `approval-matrix.csv` +- `customers.csv` +- `contacts.csv` +- `contact-shares.csv` +- `leads.csv` +- `opportunities.csv` +- `opportunity-parties.csv` +- `quotations.csv` +- `quotation-items.csv` +- `quotation-parties.csv` +- `quotation-topics.csv` +- `quotation-topic-items.csv` + +Defer or mark as unsupported until schema is clarified: + +- `departments.csv` +- `teams.csv` +- `warehouses.csv` +- `sites.csv` +- `services.csv` +- `price-books.csv` +- `tax-rates.csv` + +## Verification Checks Needed + +- environment variable presence and secrecy checks +- database connectivity +- migration table/status check +- storage provider write/read/delete smoke test +- upload directory readiness for local provider +- PDF template inventory and mapping coverage +- email provider configuration and optional dry-run +- first admin login readiness +- active organization on first admin +- membership and CRM role assignment resolution +- branch and product type scope resolution +- document sequence preview for customer, lead, opportunity, quotation +- approval workflow and matrix resolution for quotation +- report definition availability +- notification template availability + +## Implementation Boundary + +No code implementation was started in this pass because Task D.7 requires the audit and dependency report first. The safest next implementation slice is: + +1. Extract current seed script logic into setup-callable server services without changing behavior. +2. Add setup readiness and verification read-only APIs. +3. Add checked-in CSV templates for entities already represented in schema. +4. Build CSV preview validation before commit support. +5. Replace the existing prototype setup wizard with a dashboard-native wizard that calls the setup APIs. + +## Notes + +- `rtk` was requested by repository instructions, but it was not available in this shell session. Raw PowerShell commands were used for the audit. +- Existing seed scripts include Thai strings that appeared mojibake in terminal output. This audit did not modify those files. diff --git a/docs/implementation/task-d72-setup-readiness-verification-apis.md b/docs/implementation/task-d72-setup-readiness-verification-apis.md new file mode 100644 index 0000000..99c09db --- /dev/null +++ b/docs/implementation/task-d72-setup-readiness-verification-apis.md @@ -0,0 +1,51 @@ +# Task D.7.2 Setup Readiness and Verification APIs + +Date: 2026-07-02 +Status: implemented + +## Scope Delivered + +- Added setup API contracts: + - `src/features/setup/api/types.ts` + - `src/features/setup/api/service.ts` + - `src/features/setup/api/queries.ts` +- Added read-only setup server services: + - `src/features/setup/server/readiness.service.ts` + - `src/features/setup/server/verification.service.ts` +- Added thin protected route handlers: + - `src/app/api/setup/readiness/route.ts` + - `src/app/api/setup/verify/route.ts` + +## Behavior + +- `GET /api/setup/readiness` + - protected by `requireSystemRole('super_admin')` + - returns structured setup readiness checks + - checks environment, database, migrations/schema queryability, storage smoke test, local upload directory, PDF template/mapping readiness, and optional email configuration + +- `POST /api/setup/verify` + - protected by `requireSystemRole('super_admin')` + - accepts optional `organizationId` and `administratorEmail` + - falls back to the current session active organization when `organizationId` is not provided + - returns structured verification checks for administrator, membership, CRM assignment, organization, branch/product type options, document sequences, approval workflow/matrix, PDF, notification templates, report definitions, email config, and planned CSV/manifest checks + +## Safety Notes + +- No seed execution was added. +- No CSV import was added. +- No reset/reseed behavior was added. +- No setup state tables or migrations were added. +- The only intentional write-like behavior is the readiness storage smoke test, which writes a temporary setup object and immediately deletes it. Cleanup failures are reported as warnings. +- Document sequence checks are read-only and do not call `generateNextDocumentCode()`. +- Report verification checks query report definition rows directly and do not call report services that audit report views. + +## Verification + +- `npm run typecheck`: passed. +- `npx oxlint src/features/setup src/app/api/setup`: passed. +- `npm run lint`: failed due to pre-existing unrelated lint errors/warnings outside this task, including `src/features/foundation/document-template/server/management-service.ts` and skill asset scripts. + +## Follow-up + +- D.7.3 can add static CSV template pack. +- D.7.8 should replace planned CSV preview/seed manifest warnings with persisted setup state checks. diff --git a/docs/setup/csv-template-spec.md b/docs/setup/csv-template-spec.md new file mode 100644 index 0000000..0de2a4a --- /dev/null +++ b/docs/setup/csv-template-spec.md @@ -0,0 +1,557 @@ +# 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 +``` diff --git a/docs/setup/import-mapping.md b/docs/setup/import-mapping.md new file mode 100644 index 0000000..5b72ac7 --- /dev/null +++ b/docs/setup/import-mapping.md @@ -0,0 +1,215 @@ +# 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. | diff --git a/docs/setup/reset-reseed-matrix.md b/docs/setup/reset-reseed-matrix.md new file mode 100644 index 0000000..f595939 --- /dev/null +++ b/docs/setup/reset-reseed-matrix.md @@ -0,0 +1,32 @@ +# Reset and Reseed Matrix + +All reset operations must be blocked in production unless an explicit product decision introduces a safe production-specific reset path. Existing reset guards in `scripts/db/reset-database.ts` and `scripts/seed-reset.ts` must be preserved. + +| Scope | Tables Affected | Data Retained | Data Deleted | Safety Guard | Requires Confirmation | Allowed Environment | Transaction Behavior | Storage Cleanup Behavior | Audit Behavior | Recommended Command/API | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| `foundation_reset` | `ms_options`, `document_sequences`, `crm_approval_workflows`, `crm_approval_steps`, `crm_approval_matrices`, document template/library/report/notification foundation rows | Users, organizations, memberships, business data where compatible | Foundation rows for target organization only | Block production destructive mode; require target org | yes | local, test, staging with flag | Single DB transaction where possible | Document library storage cleanup must be planned and reported; do not delete approved artifacts | Audit setup reset action | `POST /api/setup/reset` with scope | +| `organization_reset` | `organizations`, `memberships`, CRM role assignments/profiles for target org, organization-scoped foundation rows | Global users unless explicitly selected | Target organization and tenant-scoped rows | Require exact organization code/name confirmation | yes | local/test/staging with flag | Transaction for DB rows | Cleanup only setup-owned storage prefix; report leftovers | Audit setup reset action | future setup reset service | +| `business_reset` | customers, contacts, contact shares, leads, opportunities, quotations, related parties/items/topics/followups, business attachments metadata | Organization, users, memberships, foundation rows | Business records for target organization | Require target org and business reset phrase | yes | local/test/staging with flag | Transaction for DB rows | Delete only non-approved setup/business import files when explicitly selected | Audit reset summary | future setup reset service | +| `demo_uat_reset` | UAT users, UAT memberships, UAT CRM role assignments, UAT-seeded CRM business/runtime fixture rows | Production foundation rows and non-UAT business data | Rows tagged/deterministically known from `uat-users.seed.ts` and `crm-uat.seed.ts` | Require demo/UAT mode and seed tag | yes | local/test/staging | Transaction for DB rows | Remove demo-generated storage objects under demo prefix only | Audit reset summary | extend `scripts/seed-uat.ts`/future API | +| `full_reset` | Entire public/drizzle schemas through existing reset script | Nothing in target database | All DB data | Existing `--allow-db-reset`; production blocked; unsafe host/db blocked | yes | local/test only by default | Drop/recreate schema | Storage cleanup is out-of-band and must be explicit | Console/report only before setup audit exists | `npm run db:reset -- --allow-db-reset` | +| `csv_import_rollback` | Rows from one import manifest | Data outside import manifest | Imported/updated rows when reversible metadata exists | Requires seed manifest item checksums | yes | local/test/staging; production only if designed later | Transaction if rollback immediately after failed commit; compensating actions otherwise | Cleanup files imported by same manifest only | Audit rollback action | future setup import rollback service | +| `setup_run_reset` | proposed `setup_runs`, `setup_run_steps`, `seed_manifests`, `seed_manifest_items` | Actual business/foundation data unless separate scope selected | Setup run metadata | Require setup run id | yes | all non-production; production only metadata reset by super admin later | Transaction | No storage cleanup | Audit metadata reset | future setup state service | + +## General Reset Rules + +- Never rely on client-side confirmation alone. +- Require exact typed confirmation for destructive scopes. +- Refuse reset when `NODE_ENV=production` unless a future ADR defines an allowed production maintenance workflow. +- Use organization-scoped deletes whenever possible. +- Runtime generated approved artifacts must not be deleted by `business_reset` unless they are demo/UAT artifacts and the scope explicitly includes them. +- Storage cleanup must be best-effort with a compensation report because storage providers do not participate in DB transactions. +- Reset reports must list retained data, deleted data, skipped data, warnings, and errors. + +## Reseed Behavior + +- Foundation reseed must be idempotent. +- Demo/UAT reseed must be deterministic and isolated from production seed. +- Same seed version and checksum should skip unless `force` is explicitly set. +- Same seed version with changed checksum should warn or block. +- Newer compatible seed versions may apply. +- Older versions should block unless rollback mode exists. diff --git a/docs/setup/seed-matrix.md b/docs/setup/seed-matrix.md new file mode 100644 index 0000000..4ce029b --- /dev/null +++ b/docs/setup/seed-matrix.md @@ -0,0 +1,82 @@ +# Setup Seed Matrix + +Source task: `plans/task-d.7.1.md` +Source audit: `docs/implementation/task-d7-initial-system-setup-audit.md` + +Seed type values: + +- `foundation`: shared system or CRM foundation data required before normal app use. +- `organization`: tenant-specific setup data. +- `business`: customer, sales, quotation, and related master/business data. +- `demo_uat`: deterministic demo or UAT fixture data. +- `runtime_generated`: data generated by approved workflows or system actions. +- `runtime_only`: operational data that should not be seeded directly. +- `unsupported_pending_schema`: requested concept that does not currently have a first-class schema. + +| Module | Entity | Table / Storage | Seed Type | Required For First Login | Required For CRM | Required For Quotation | CSV Importable | Wizard Editable | Auto Generated | Source Truth | Dependencies | Import Order | Reset Scope | Notes | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| Core | User | `users` | organization | yes | yes | yes | yes | yes | no | CSV/Wizard/Auth seed | none | 01 | organization_reset | First administrator must exist before organization-owned foundation setup can complete. | +| Core | Organization | `organizations` | organization | yes | yes | yes | yes | yes | no | CSV/Wizard | created_by_user | 02 | organization_reset | Current system seed creates `ALLA Demo`; wizard must accept real organization input. | +| Core | Membership | `memberships` | organization | yes | yes | yes | yes | yes | no | CSV/Wizard | user, organization | 03 | organization_reset | Grants app-level organization access. | +| Authorization | CRM role profile | `crm_role_profiles` | foundation | no | yes | yes | no | limited | no | `src/lib/auth/rbac.ts` + role foundation | organization, created_by_user | 04 | foundation_reset | Seed from code-defined defaults; import custom profiles later only after governance. | +| Authorization | CRM user role assignment | `crm_user_role_assignments` | organization | no | yes | yes | yes | yes | no | CSV/Wizard | user, organization, role profile, branch/product options | 05 | organization_reset | Primary CRM authorization source per ADR-0014. | +| Foundation | Master option | `ms_options` | foundation | no | yes | yes | yes | yes | no | Foundation seed/CSV | organization, created_by_user | 06 | foundation_reset | Shared option table for many governed CRM dropdowns. | +| Foundation | Branch | `ms_options(category=crm_branch)` | organization | no | yes | yes | yes | yes | no | CSV/Wizard | organization, created_by_user | 07 | organization_reset | Branch is currently modeled as a master option, not a branch table. | +| Foundation | Product type | `ms_options(category=crm_product_type)` | organization | no | yes | yes | yes | yes | no | CSV/Wizard | organization, created_by_user | 08 | organization_reset | Used by CRM scope and document sequence resolution. | +| Foundation | Currency | `ms_options(category=crm_currency)` | foundation | no | yes | yes | yes | yes | no | Foundation seed/CSV | organization | 09 | foundation_reset | Default should include THB. | +| Foundation | Unit | `ms_options(category=crm_unit)` | foundation | no | no | yes | yes | yes | no | Foundation seed/CSV | organization | 10 | foundation_reset | Used by quotation items. | +| Foundation | Status and reason options | `ms_options` status/reason categories | foundation | no | yes | yes | yes | limited | no | Foundation seed/CSV | organization | 11 | foundation_reset | Includes customer, lead, opportunity, quotation, lost/cancel/no-quotation statuses. | +| Foundation | Job title | `ms_options(category=crm_job_title)` | foundation | no | yes | yes | yes | limited | no | Foundation seed/CSV | organization | 12 | foundation_reset | Used by PDF signature position fallback rules. | +| Foundation | Project party role | `ms_options(category=crm_project_party_role)` | foundation | no | yes | yes | yes | limited | no | Foundation seed/CSV | organization | 13 | foundation_reset | Required by revenue attribution and quotation parties. | +| Document sequence | Document sequence | `document_sequences` | foundation | no | yes | yes | yes | yes | no | Foundation seed/Wizard | organization, branch, product type | 14 | foundation_reset | Scoped by organization, branch, product type, document type, period. | +| Approval | Approval workflow | `crm_approval_workflows` | foundation | no | no | yes | yes | yes | no | Foundation seed/Wizard | organization, created_by_user | 15 | foundation_reset | Quotation approval depends on this. | +| Approval | Approval step | `crm_approval_steps` | foundation | no | no | yes | yes | yes | no | Foundation seed/Wizard | approval workflow | 16 | foundation_reset | Defines sequential actors and SLA metadata. | +| Approval | Approval matrix | `crm_approval_matrices` | foundation | no | no | yes | yes | yes | no | Foundation seed/Wizard | approval workflow, branch, product type | 17 | foundation_reset | Resolves workflow by amount/scope. | +| Document template | Template | `crm_document_templates` | foundation | no | no | yes | no | limited | no | Foundation seed/document-template foundation | organization, created_by_user | 18 | foundation_reset | Approved path for template metadata. | +| Document template | Template version | `crm_document_template_versions` | foundation | no | no | yes | no | limited | no | Foundation seed/document-template foundation | template | 19 | foundation_reset | Must have one active PDFME version for quotation. | +| Document template | Template mapping | `crm_document_template_mappings` | foundation | no | no | yes | no | limited | no | Foundation seed/PDF registry | template version | 20 | foundation_reset | DB mapping for PDF placeholders. | +| Document template | Template table column | `crm_document_template_table_columns` | foundation | no | no | yes | no | limited | no | Foundation seed/PDF registry | template mapping | 21 | foundation_reset | Required by table placeholders. | +| Document library | Document library | `crm_document_libraries` | foundation | no | no | yes | no | limited | no | Foundation seed/document-library foundation | organization, storage provider | 22 | foundation_reset | Metadata for reusable document library files. | +| Document library | Document library version | `crm_document_library_versions` + storage object | foundation | no | no | yes | no | limited | partly | Foundation seed/storage provider | document library, storage provider | 23 | foundation_reset | Storage write cannot be DB-transactional; setup report must capture compensation needs. | +| Notifications | Notification template | `app_notification_templates` | foundation | no | yes | yes | no | limited | no | Foundation seed/notification foundation | organization, created_by_user | 24 | foundation_reset | Required for approval in-app notifications. | +| Reports | Report definition | `crm_report_definitions` | foundation | no | yes | no | no | limited | no | Foundation seed/report foundation | organization | 25 | foundation_reset | Required by report catalog. | +| CRM | Customer | `crm_customers` | business | no | yes | yes | yes | yes | no | CSV/CRM service | organization, branch, owner user, master options | 30 | business_reset | Must use customer service/audit rules in implementation. | +| CRM | Customer owner history | `crm_customer_owner_history` | runtime_generated | no | yes | yes | no | no | yes | Customer service | customer, owner user | 31 | business_reset | Generated when owner changes; UAT seed currently writes initial history. | +| CRM | Contact | `crm_customer_contacts` | business | no | yes | yes | yes | yes | no | CSV/CRM service | organization, customer | 32 | business_reset | Contact visibility is governed by customer/contact sharing rules. | +| CRM | Contact share | `crm_contact_shares` | business | no | yes | optional | yes | yes | no | CSV/CRM service | organization, contact, shared user | 33 | business_reset | Must preserve ADR-0015 visibility model. | +| CRM | Lead | `crm_leads` | business | no | yes | optional | yes | yes | no | CSV/CRM service | organization, branch, customer/contact optional, owner/assignee users, options | 34 | business_reset | Dedicated marketing-owned lead table per ADR-0018 direction. | +| CRM | Lead followup | audit payload in `tr_audit_logs` today | runtime_generated | no | yes | no | no | no | yes | Lead service | lead, user | 35 | business_reset | No first-class lead followup table currently identified. | +| CRM | Opportunity | `crm_opportunities` | business | no | yes | yes | yes | yes | no | CSV/CRM service | organization, customer, contact, lead optional, options, assignee | 36 | business_reset | Active sales opportunity domain. | +| CRM | Opportunity party | `crm_opportunity_customers` | business | no | yes | yes | yes | yes | no | CSV/CRM service | opportunity, customer, project party role | 37 | business_reset | Used by revenue attribution. | +| CRM | Opportunity followup | `crm_opportunity_followups` | business | no | yes | optional | no | yes | no | Opportunity service | opportunity, contact, followup option | 38 | business_reset | Could be CSV-supported later; D.7.1 does not require a template. | +| CRM | Opportunity attachment | `crm_opportunity_attachments` + storage/file path | business | no | yes | optional | no | yes | partly | Opportunity service/storage foundation | opportunity, storage | 39 | business_reset | File import deferred until upload transport rules are specified. | +| Quotation | Quotation | `crm_quotations` | business | no | yes | yes | yes | yes | no | CSV/Quotation service | organization, opportunity optional, customer, contact, branch, options, salesman | 40 | business_reset | Must preserve revision/approval/document behavior. | +| Quotation | Quotation item | `crm_quotation_items` | business | no | no | yes | yes | yes | no | CSV/Quotation service | quotation, product type, unit, discount type | 41 | business_reset | Not a price-book row; item text/pricing belong to quotation. | +| Quotation | Quotation party | `crm_quotation_customers` | business | no | yes | yes | yes | yes | no | CSV/Quotation service | quotation, customer, project party role | 42 | business_reset | Billing/end customer rules affect reports. | +| Quotation | Quotation topic | `crm_quotation_topics` | business | no | no | yes | yes | yes | no | CSV/Quotation service | quotation, topic type | 43 | business_reset | Parent for topic items. | +| Quotation | Quotation topic item | `crm_quotation_topic_items` | business | no | no | yes | yes | yes | no | CSV/Quotation service | quotation topic | 44 | business_reset | Ordered terms/scope rows. | +| Quotation | Quotation followup | `crm_quotation_followups` | business | no | yes | yes | no | yes | no | Quotation service | quotation, contact, followup option | 45 | business_reset | Could be CSV-supported later; D.7.1 does not require a template. | +| Quotation | Quotation attachment | `crm_quotation_attachments` + storage/file path | business | no | optional | optional | no | yes | partly | Quotation service/storage foundation | quotation, storage | 46 | business_reset | `attachments.csv` deferred because binary transport is not generic CSV. | +| Approval runtime | Approval request | `crm_approval_requests` | runtime_generated | no | no | yes | no | no | yes | Approval service | quotation, approval workflow | 50 | demo_uat_reset | Production requests are runtime data; demo seed may generate fixtures. | +| Approval runtime | Approval action | `crm_approval_actions` | runtime_generated | no | no | yes | no | no | yes | Approval service | approval request, actor | 51 | demo_uat_reset | Production actions are runtime audit/workflow records. | +| Audit | Audit log | `tr_audit_logs` | runtime_only | no | no | no | no | no | yes | Audit foundation | actor, entity | 60 | full_reset | Should be created by services, not CSV import. | +| Notifications | Notification event | `app_notification_events` | runtime_only | no | no | no | no | no | yes | Notification foundation | entity/action | 61 | full_reset | Runtime queue/event state. | +| Notifications | User notification | `app_notifications` | runtime_only | no | no | no | no | no | yes | Notification foundation | notification event, recipient | 62 | full_reset | User-facing runtime inbox state. | +| Notifications | Dispatch | `app_notification_dispatches` | runtime_only | no | no | no | no | no | yes | Notification foundation | notification event/template | 63 | full_reset | Provider dispatch runtime state. | +| Notifications | Delivery | `app_notification_deliveries` | runtime_only | no | no | no | no | no | yes | Notification foundation | notification | 64 | full_reset | Delivery runtime state. | +| PDF/storage | Document artifact | `crm_document_artifacts` + storage object | runtime_generated | no | no | yes | no | no | yes | Document artifact/PDF/storage foundations | entity, template, storage | 65 | explicit_artifact_reset | Approved documents are immutable-first; do not delete casually. | +| Storage | Uploaded file | storage provider + attachment metadata | runtime_generated | no | optional | optional | no | no | partly | Storage foundation | entity, uploader | 66 | explicit_storage_reset | Storage cleanup needs compensation/reporting. | +| Setup | Setup run | proposed `setup_runs` | foundation | no | no | no | no | no | yes | Setup foundation | actor | 70 | setup_run_reset | Proposed for resume/reporting; not implemented yet. | +| Setup | Setup run step | proposed `setup_run_steps` | foundation | no | no | no | no | no | yes | Setup foundation | setup run | 71 | setup_run_reset | Proposed for step state and error history. | +| Setup | Seed manifest | proposed `seed_manifests` | foundation | no | yes | yes | no | no | yes | Setup foundation | organization optional | 72 | setup_run_reset | Proposed for seed lifecycle tracking. | +| Setup | Seed manifest item | proposed `seed_manifest_items` | foundation | no | yes | yes | no | no | yes | Setup foundation | seed manifest | 73 | setup_run_reset | Proposed for checksum and item-level report. | +| Demo | UAT users | `users`, `memberships`, `crm_user_role_assignments` | demo_uat | no | no | no | no | no | no | `src/db/seeds/uat-users.seed.ts` | foundation seed | 80 | demo_uat_reset | Separate from production foundation install. | +| Demo | UAT CRM data | CRM business/runtime tables | demo_uat | no | no | no | no | no | no | `src/db/seeds/crm-uat.seed.ts` | UAT users, foundation seed | 81 | demo_uat_reset | Deterministic demo stories. | +| Unsupported | Department | none identified | unsupported_pending_schema | no | no | no | no | no | no | pending ADR/schema | organization | 90 | none | Do not implement as first-class setup data without schema decision. | +| Unsupported | Team | none identified | unsupported_pending_schema | no | no | no | no | no | no | pending ADR/schema | organization | 91 | none | Team-scope limitations are documented in security docs. | +| Unsupported | Warehouse | none identified | unsupported_pending_schema | no | no | no | no | no | no | pending ADR/schema | organization | 92 | none | No warehouse table reviewed. | +| Unsupported | Site | none identified | unsupported_pending_schema | no | optional | optional | no | no | no | pending ADR/schema | customer/opportunity | 93 | none | Project location is currently stored on lead/opportunity/quotation text fields. | +| Unsupported | Service catalog | none identified | unsupported_pending_schema | no | no | optional | no | no | no | pending ADR/schema | organization | 94 | none | Current quotation item descriptions are free-form. | +| Unsupported | Price book | none identified | unsupported_pending_schema | no | no | optional | no | no | no | pending ADR/schema | product/service catalog | 95 | none | Do not conflate with generic starter `products` table. | +| Unsupported | Tax rate table | none identified | unsupported_pending_schema | no | no | optional | no | no | no | pending ADR/schema | organization/currency | 96 | none | Quotation tax rate is currently stored on quotation/item rows. | +| Configuration | Email provider config | environment/provider settings | unsupported_pending_schema | no | optional | optional | no | limited | no | environment | deployment config | 97 | none | Verification can check env readiness; no first-class table identified. | diff --git a/docs/setup/seed-version-manifest.md b/docs/setup/seed-version-manifest.md new file mode 100644 index 0000000..8e24333 --- /dev/null +++ b/docs/setup/seed-version-manifest.md @@ -0,0 +1,136 @@ +# Seed Version and Manifest Strategy + +Seed manifests make setup repeatable, auditable, and safe to resume. + +## Seed Package Model + +| Field | Meaning | +| --- | --- | +| `name` | Stable package name, such as `system-core`, `crm-foundation`, `crm-demo-uat`. | +| `version` | Semantic version for the seed package. | +| `type` | `foundation`, `organization`, `business`, `demo_uat`, or `setup_state`. | +| `dependencies` | Package names and compatible versions required before this package. | +| `checksum` | Checksum over manifest content and referenced item checksums. | +| `appliedAt` | Timestamp when applied. | +| `appliedBy` | User id or setup actor id. | +| `targetOrganization` | Organization id/code when organization-scoped. | +| `source` | `script`, `wizard`, `csv`, or `plugin`. | +| `status` | `pending`, `applied`, `skipped`, `warning`, `failed`, `rolled_back`. | +| `report` | Import/seed report with counts, warnings, errors, and storage compensation notes. | + +## Manifest Format + +```json +{ + "name": "crm-foundation", + "version": "1.0.0", + "type": "foundation", + "dependencies": ["system-core@1.0.0"], + "targetOrganization": "ALLA", + "source": "wizard", + "items": [ + { + "file": "master-options.csv", + "checksum": "sha256:...", + "required": true, + "importOrder": "06" + }, + { + "file": "document-sequences.csv", + "checksum": "sha256:...", + "required": true, + "importOrder": "14" + } + ] +} +``` + +## Package Naming + +| Package | Type | Owner | +| --- | --- | --- | +| `system-core` | organization | setup foundation | +| `crm-foundation` | foundation | foundation seed services | +| `crm-organization` | organization | setup wizard/import | +| `crm-business-import` | business | CSV import engine | +| `crm-demo-uat` | demo_uat | UAT seed services | +| `document-template-foundation` | foundation | document-template/PDF foundations | +| `notification-foundation` | foundation | notification foundation | +| `report-foundation` | foundation | CRM report foundation | + +## Version Rules + +| Existing Manifest | Incoming Manifest | Behavior | +| --- | --- | --- | +| none | any valid version | Apply when dependencies pass. | +| same version + same checksum | same content | Skip and report already applied. | +| same version + changed checksum | changed content | Warn/block unless `force` is true and package allows mutable same-version seed. | +| older version | newer compatible version | Apply upgrade path after compatibility check. | +| newer version | older version | Block unless explicit rollback mode exists. | +| failed | same checksum | Allow retry from failed item when service is idempotent. | +| rolled back | newer version | Apply only after dependency and cleanup verification. | + +## Checksum Rules + +- Item checksum uses canonical CSV bytes after line-ending normalization. +- Manifest checksum includes sorted item checksums and manifest metadata except `appliedAt`, `appliedBy`, and runtime report. +- Generated wizard inputs should be serialized into canonical JSON and hashed as an item, for example `wizard-organization.json`. +- Storage object checksums should be recorded separately because storage writes cannot be DB-transactional. + +## Dependency Rules + +- Dependencies use package names and semver ranges, such as `crm-foundation@^1.0.0`. +- A dependency is satisfied when a successful manifest with compatible version/checksum exists for the same target organization or global scope. +- Missing required dependencies block apply. +- Optional dependencies produce warnings. + +## Report Contract + +Every seed package apply should produce: + +```json +{ + "imported": 0, + "updated": 0, + "skipped": 0, + "errors": [], + "warnings": [], + "storage": { + "createdObjects": [], + "cleanupWarnings": [] + } +} +``` + +## Persistence Strategy + +Use proposed tables: + +- `seed_manifests` +- `seed_manifest_items` + +D.7.1 does not add migrations. D.7.8 should implement persistence after setup APIs and CSV import behavior are stable. + +## Upgrade Behavior + +1. Resolve target organization. +2. Load existing manifests for target scope. +3. Validate package dependencies. +4. Compute incoming checksums. +5. Compare version/checksum behavior table. +6. Dry-run item operations. +7. Apply in import order. +8. Store manifest and item reports in the same DB transaction where possible. +9. Store storage compensation report after non-transactional storage operations. + +## Rollback Behavior + +Rollback is not a default seed feature. It requires: + +- previous manifest item reports +- reversible upsert strategy +- explicit reset scope +- storage cleanup strategy +- audit trail + +Until implemented, use scoped reset/reseed rather than pretending arbitrary rollback is safe. diff --git a/docs/setup/setup-plugin-architecture.md b/docs/setup/setup-plugin-architecture.md new file mode 100644 index 0000000..89f11fb --- /dev/null +++ b/docs/setup/setup-plugin-architecture.md @@ -0,0 +1,161 @@ +# Setup Plugin Architecture + +Goal: future modules can register setup behavior without editing the core wizard for every feature. + +This is a design proposal only. No plugin runtime is implemented in D.7.1. + +## Plugin Contract + +```ts +interface SetupPlugin { + moduleId: string; + displayName: string; + dependencies: string[]; + enabledCondition: SetupEnabledCondition; + setupSteps: SetupStepDefinition[]; + csvTemplates: CsvTemplateDefinition[]; + seedTasks: SeedTaskDefinition[]; + verificationChecks: VerificationCheckDefinition[]; + resetScopes: ResetScopeDefinition[]; +} +``` + +## Fields + +| Field | Purpose | +| --- | --- | +| `moduleId` | Stable identifier, for example `crm.quotation`. | +| `displayName` | Human-readable module name in setup UI. | +| `dependencies` | Other module ids or seed package versions required first. | +| `enabledCondition` | Function/config deciding if module applies to this installation. | +| `setupSteps` | Optional wizard steps or sections contributed by the module. | +| `csvTemplates` | CSV templates owned by the module. | +| `seedTasks` | Idempotent seed operations. | +| `verificationChecks` | Checks added to the verification matrix. | +| `resetScopes` | Reset/reseed behavior owned by the module. | + +## Example Plugin Manifest + +```json +{ + "moduleId": "crm.quotation", + "displayName": "CRM Quotation", + "dependencies": ["core.foundation", "crm.foundation", "crm.approval"], + "setupSteps": ["document-sequences", "approval-workflow", "csv-import"], + "csvTemplates": ["quotations.csv", "quotation-items.csv", "quotation-parties.csv"], + "seedTasks": ["quotation-document-sequences", "quotation-template-mappings"], + "verificationChecks": ["SEQUENCE_QUOTATION", "APPROVAL_WORKFLOW", "PDF_TEMPLATE_ASSET"], + "resetScopes": ["business_reset", "demo_uat_reset"] +} +``` + +## Core Modules + +| Module | Owns | +| --- | --- | +| `core.foundation` | Readiness, users, organizations, memberships, setup state, seed manifests. | +| `crm.foundation` | Master options, branch/product type scopes, CRM role profiles, CRM assignments. | +| `crm.customer` | Customers, contacts, contact sharing, customer ownership verification. | +| `crm.lead` | Lead import, lead sequence verification, lead master options. | +| `crm.opportunity` | Opportunity import, parties, followups, outcome lifecycle checks. | +| `crm.quotation` | Quotations, items, parties, topics, quotation sequences. | +| `crm.approval` | Workflows, steps, matrices, approval verification. | +| `document` | Templates, template versions, mappings, libraries. | +| `notification` | Notification templates and provider readiness checks. | +| `reporting` | Report definitions and report catalog verification. | +| `future_asset` | Placeholder for asset/warehouse/catalog setup after schema decisions. | +| `future_hr` | Placeholder for teams/departments after schema decisions. | + +## Execution Order + +1. Load core plugins. +2. Resolve enabled modules. +3. Build dependency graph. +4. Detect cycles. +5. Sort setup steps by dependency and declared order. +6. Merge CSV templates and reject duplicate file ownership. +7. Merge verification checks and reset scopes. +8. Render wizard steps by module and step group. + +## Cycle Rules + +- A plugin cannot depend on itself. +- Cycles block setup startup with a developer-facing error. +- Optional dependencies must not introduce ordering ambiguity. + +## Extension Points + +### Setup Step + +```ts +interface SetupStepDefinition { + key: string; + title: string; + required: boolean; + inputsSchema: unknown; + previewAction?: string; + commitAction?: string; + produces: string[]; + invalidates: string[]; +} +``` + +### CSV Template + +```ts +interface CsvTemplateDefinition { + fileName: string; + requiredColumns: string[]; + optionalColumns: string[]; + importOrder: string; + targetService: string; + unsupportedReason?: string; +} +``` + +### Seed Task + +```ts +interface SeedTaskDefinition { + key: string; + packageName: string; + version: string; + idempotent: boolean; + dependencies: string[]; + dryRunAction: string; + commitAction: string; +} +``` + +### Verification Check + +```ts +interface VerificationCheckDefinition { + checkId: string; + area: string; + requiredFor: string[]; + runAction: string; + suggestedFix: string; +} +``` + +### Reset Scope + +```ts +interface ResetScopeDefinition { + key: string; + destructive: boolean; + allowedEnvironments: string[]; + requiresTypedConfirmation: boolean; + affectedTables: string[]; + storageCleanup: "none" | "report_only" | "best_effort"; +} +``` + +## Safety Rules + +- Plugins can register behavior but cannot bypass setup core authorization. +- Plugin seed tasks must be idempotent or marked unsafe. +- Plugin reset scopes must declare affected tables and storage behavior. +- Plugin CSV templates must declare ownership to avoid two modules writing the same file contract. +- Setup core owns persistence of run state, manifests, audit events, and final reports. diff --git a/docs/setup/setup-state-machine.md b/docs/setup/setup-state-machine.md new file mode 100644 index 0000000..601fb22 --- /dev/null +++ b/docs/setup/setup-state-machine.md @@ -0,0 +1,155 @@ +# Setup State Machine + +D.7.1 does not implement persistence. This document proposes the model and transitions for D.7.8. + +## States + +```text +not_started +readiness_checked +foundation_installed +organization_created +administrator_created +scope_configured +sequences_configured +approval_configured +csv_validated +csv_imported +verified +completed +failed +cancelled +``` + +## Events + +```text +CHECK_READINESS +INSTALL_FOUNDATION +CREATE_ORGANIZATION +CREATE_ADMIN +CONFIGURE_SCOPE +CONFIGURE_SEQUENCES +CONFIGURE_APPROVAL +VALIDATE_CSV +IMPORT_CSV +RUN_VERIFICATION +COMPLETE_SETUP +FAIL_STEP +CANCEL_SETUP +RESUME_SETUP +RESET_STEP +``` + +## Transition Rules + +| From | Event | To | Guard | +| --- | --- | --- | --- | +| `not_started` | `CHECK_READINESS` | `readiness_checked` | Required readiness checks pass or warnings are acknowledged. | +| `readiness_checked` | `INSTALL_FOUNDATION` | `foundation_installed` | Migrations valid and seed transaction succeeds. | +| `foundation_installed` | `CREATE_ORGANIZATION` | `organization_created` | Organization is created or resolved. | +| `organization_created` | `CREATE_ADMIN` | `administrator_created` | User, membership, and CRM role assignment resolve. | +| `administrator_created` | `CONFIGURE_SCOPE` | `scope_configured` | At least one branch and product type exists. | +| `scope_configured` | `CONFIGURE_SEQUENCES` | `sequences_configured` | Required sequence previews succeed. | +| `sequences_configured` | `CONFIGURE_APPROVAL` | `approval_configured` | Quotation workflow, steps, and matrix resolve. | +| `approval_configured` | `VALIDATE_CSV` | `csv_validated` | Preview has no blocking errors, or no CSV files were selected. | +| `csv_validated` | `IMPORT_CSV` | `csv_imported` | Commit succeeds or CSV import skipped. | +| `csv_imported` | `RUN_VERIFICATION` | `verified` | Required verification checks pass. | +| `verified` | `COMPLETE_SETUP` | `completed` | No blocking failures remain. | +| any non-completed state | `FAIL_STEP` | `failed` | Step returns blocking error. | +| any non-completed state | `CANCEL_SETUP` | `cancelled` | User confirms cancellation. | +| `failed` | `RESUME_SETUP` | prior recoverable state | Failure source still recoverable. | +| completed step state | `RESET_STEP` | earliest affected state | Explicit confirmation and reset scope guard pass. | + +## Proposed Persistence + +### `setup_runs` + +Purpose: one row per setup attempt. + +Fields: + +- `id` +- `status` +- `mode`: `production | demo_uat | dry_run` +- `target_organization_id` +- `started_by` +- `completed_by` +- `started_at` +- `completed_at` +- `last_error` +- `metadata` +- timestamps + +### `setup_run_steps` + +Purpose: step-level status, resume, and report storage. + +Fields: + +- `id` +- `setup_run_id` +- `step_key` +- `status` +- `input_hash` +- `output_json` +- `errors_json` +- `warnings_json` +- `started_at` +- `completed_at` +- timestamps + +### `seed_manifests` + +Purpose: seed package lifecycle tracking. + +Fields: + +- `id` +- `setup_run_id` +- `organization_id` +- `name` +- `version` +- `type` +- `checksum` +- `status` +- `applied_by` +- `applied_at` +- `report_json` +- timestamps + +### `seed_manifest_items` + +Purpose: item-level seed/import report and checksum tracking. + +Fields: + +- `id` +- `seed_manifest_id` +- `item_key` +- `source_file` +- `checksum` +- `status` +- `imported_count` +- `updated_count` +- `skipped_count` +- `error_count` +- `warning_count` +- `report_json` +- timestamps + +## Staleness Rules + +- Changing organization setup makes administrator, scope, sequence, approval, CSV, and verification stale. +- Changing administrator makes verification stale. +- Changing scope makes sequences, approval, CSV, and verification stale. +- Changing sequences makes verification stale. +- Changing approval makes CSV quotations and verification stale. +- Changing CSV preview invalidates committed import unless the source file checksum is unchanged. +- Any committed import makes verification stale. + +## Cancellation Rules + +- `cancelled` keeps report history. +- Cancellation does not rollback already committed successful steps. +- A cancelled setup can resume only through `RESUME_SETUP` with explicit acknowledgement of committed state. diff --git a/docs/setup/setup-wizard-blueprint.md b/docs/setup/setup-wizard-blueprint.md new file mode 100644 index 0000000..ba41781 --- /dev/null +++ b/docs/setup/setup-wizard-blueprint.md @@ -0,0 +1,57 @@ +# Setup Wizard Blueprint + +Target route proposal: `src/app/dashboard/administration/setup/page.tsx` + +Target feature proposal: `src/features/setup/**` + +UI rules: + +- Use dashboard shell and `PageContainer`. +- Use shadcn/ui primitives and app icons through `@/components/icons`. +- Use server-side enforcement for all setup actions. +- Treat client-side step visibility as UX only. +- Do not reuse the existing `/setup` template onboarding component as-is; it is a historical prototype. + +| Step Number | Step Name | Purpose | Inputs | Validation | Actions | APIs | Can Skip | Can Go Back | Resume Behavior | Error Handling | Output | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| 1 | System Readiness | Prove the environment can safely initialize. | none, optional setup mode | Env variables, DB connectivity, migration status, storage read/write/delete, upload directory, PDF template asset availability, optional email config. | Run read-only checks and record result. | `GET /api/setup/readiness` | no | no | Resume from latest readiness result but allow re-run. | PASS/WARNING/FAIL with suggested fix per check. | Readiness report. | +| 2 | Foundation Install | Install or verify foundation data. | seed mode, target organization context if already created | Foundation seed dependencies present; no pending migration failures. | Run extracted foundation seed services or dry-run. | `POST /api/setup/foundation` | no | yes, before commit only | If already installed, show manifest and allow verify/reapply compatible seed. | Roll back DB transaction on failure; report storage compensation if library write fails. | Foundation seed report. | +| 3 | Organization Setup | Create or update tenant organization. | `organization_name`, `organization_code`, `slug`, `tax_id`, `phone`, `email`, `address`, `default_currency`, `timezone`, `logo` | Name/code/slug required; code uppercase; slug lowercase unique; email format; timezone defaults `Asia/Bangkok`; currency resolves. | Create organization and store setup alias. | `POST /api/setup/organization` | no | yes | Existing run loads created organization and locks destructive fields unless reset. | Field errors; duplicate slug/code error; retry safe. | Organization summary. | +| 4 | Branch & Product Type Setup | Configure current scope foundations. | Branch rows, product type rows, default branch, default product type | At least one active branch and product type; codes unique; prefixes valid. | Upsert `crm_branch` and `crm_product_type` options. | `POST /api/setup/scope` | no | yes | Resume loads imported/created options. | Row-level validation table. | Scope summary and sequence suggestions. | +| 5 | Administrator Setup | Create first administrator and CRM authorization. | Admin name, email, password or existing user, membership role, CRM role, branch/product scope | Email valid; password policy for new user; user membership exists or can be created; role profile resolves. | Create/update user, membership, CRM role assignment. | `POST /api/setup/administrator` | no | yes | Resume masks password and shows resolved user/membership/assignment. | Avoid logging password; show user/role validation errors. | Administrator access summary. | +| 6 | Document Sequence Setup | Configure numbering by organization/branch/product/document type. | Document type, branch, product type, prefix, period, current number, padding, format, reset policy | Required sequence coverage for customer, lead, opportunity, quotation; format contains `{running}`; scope resolves. | Preview and upsert sequences. | `POST /api/setup/document-sequences/preview`, `POST /api/setup/document-sequences` | no | yes | Resume loads existing sequence rows and preview samples. | Per-row validation and conflict warnings. | Sequence preview and seed report. | +| 7 | Approval Workflow Setup | Configure quotation approval. | Workflow header, steps, matrix rows, SLA fields | Workflow active; steps sequential; matrix resolves to workflow; default quotation matrix exists. | Upsert workflow, steps, matrix. | `POST /api/setup/approval/preview`, `POST /api/setup/approval` | no for quotation install | yes | Resume shows active workflow and matrix status. | Matrix conflict/error report. | Approval configuration report. | +| 8 | CSV Import | Import optional master/business data. | CSV files, import mode, upsert mode, partial import toggle | Parseable CSV; required columns; references resolve; duplicates classified; unsupported templates blocked. | Preview, validate, then commit selected files in order. | `POST /api/setup/import/preview`, `POST /api/setup/import/commit` | yes | yes | Resume stores last preview hash and committed manifest. | No commit if preview has blocking errors; partial import only for selected valid files. | Import report. | +| 9 | Verification | Confirm install can operate. | check selection, optional email dry-run recipient | Required checks pass; warnings acknowledged. | Run verification matrix. | `POST /api/setup/verify` | no | yes | Resume shows last verification but requires re-run after mutations. | PASS/WARNING/FAIL with suggested fixes. | Verification report. | +| 10 | Finish & Report | Close setup and generate summary. | acknowledgement, optional export report | Required setup states completed; blocking failures absent. | Mark setup completed, write manifest/report, optionally lock setup. | `POST /api/setup/complete`, `GET /api/setup/report` | no | yes until complete | Completed run becomes read-only except explicit reset. | Completion blocked when required checks fail. | Setup summary, seed summary, import summary, verification report. | + +## API Behavior + +All setup write APIs should: + +- require authenticated administrator or bootstrap setup token depending on system state +- validate request body with Zod or equivalent schema +- call `src/features/setup/server/**` services +- use existing auth, organization, role, seed, storage, approval, sequence, PDF, and audit foundations +- return structured step reports, not raw SQL/provider errors + +## Resume Rules + +- Setup run state is stored in proposed `setup_runs` and `setup_run_steps`. +- A completed step can be reviewed. +- A completed step can be re-run only when its service supports idempotent upsert and the user confirms conflict behavior. +- Later steps become stale if an earlier dependency step changes. +- Verification must be re-run after foundation, organization, scope, sequence, approval, or CSV commit changes. + +## Step Outputs + +Every step output should include: + +- `status`: `not_started | running | passed | warning | failed | skipped` +- `startedAt` +- `completedAt` +- `summary` +- `items` +- `errors` +- `warnings` +- `nextRecommendedAction` diff --git a/docs/setup/verification-matrix.md b/docs/setup/verification-matrix.md new file mode 100644 index 0000000..04bf247 --- /dev/null +++ b/docs/setup/verification-matrix.md @@ -0,0 +1,39 @@ +# Setup Verification Matrix + +Verification checks return `PASS`, `WARNING`, or `FAIL`. + +| Check ID | Area | Check Name | Purpose | Required For | Method | Pass Condition | Warning Condition | Fail Condition | Suggested Fix | API / Service | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| `ENV_AUTH_SECRET` | Environment | Auth secret present | Ensure Auth.js can sign sessions. | first login | Read server env. | `AUTH_SECRET` exists and length is acceptable. | Weak-looking local/dev secret. | Missing secret. | Set `AUTH_SECRET` in `.env.local` or deployment secrets. | `setup/readiness.service.ts` | +| `ENV_DATABASE_URL` | Environment | Database URL present | Ensure app can connect to PostgreSQL. | all setup | Read server env. | `DATABASE_URL` exists. | Host appears local in non-local environment. | Missing value. | Set `DATABASE_URL`. | `setup/readiness.service.ts` | +| `DB_CONNECTIVITY` | Database | Database connectivity | Prove DB is reachable. | all setup | Open DB connection and `select 1`. | Query succeeds. | High latency. | Connection/query fails. | Verify database host, network, credentials. | `src/lib/db.ts` | +| `DB_MIGRATIONS` | Database | Migration status | Prevent setup against stale schema. | all setup | Check Drizzle migration metadata/schema tables where available. | Schema matches expected tables. | Migration metadata unavailable but required tables exist. | Required tables missing. | Run `npm run db:migrate`. | setup readiness + Drizzle | +| `STORAGE_PROVIDER` | Storage | Storage provider smoke test | Prove seed/document storage can write. | document library/PDF | Write/read/delete test object under setup prefix. | All operations succeed. | Delete cleanup fails but object tracked. | Write/read fails. | Fix local directory/S3 credentials. | `src/features/foundation/storage/**` | +| `UPLOAD_DIRECTORY` | Storage | Upload directory readiness | Prove local provider path is writable. | local storage | Check directory exists/create/write/delete. | Directory writable. | Directory created during check. | Not writable. | Create/fix permissions. | storage provider | +| `PDF_TEMPLATE_ASSET` | PDF | PDF template assets exist | Ensure quotation PDF can render. | quotation | Check active template version/file/schema. | Active version and schema exist. | Template exists but preview asset missing. | No active template version. | Run foundation/PDF template seed. | document-template/pdf foundations | +| `PDF_MAPPING_COVERAGE` | PDF | PDF mappings exist | Ensure placeholders can resolve. | quotation | Count required mappings for active version. | Required mappings present. | Optional mappings missing. | Required mapping missing. | Run PDF mapping seed/audit. | PDF audit services/scripts | +| `EMAIL_CONFIG` | Email | Email configuration | Confirm optional email readiness. | optional notifications | Read env/provider config; optional dry-run. | Provider configured and dry-run succeeds. | Email not configured but in-app notifications are available. | Provider configured but dry-run fails. | Fix SMTP/provider settings. | future notification/email service | +| `ADMIN_USER` | Auth | First administrator exists | Ensure first login actor exists. | first login | Query `users` by setup admin email. | User exists. | User exists but inactive semantics added later. | Missing user. | Create administrator step. | user/auth service | +| `ADMIN_MEMBERSHIP` | Auth | Administrator membership exists | Ensure admin can enter organization. | first login | Query `memberships`. | Membership exists with admin role. | Membership role is user but CRM assignment admin. | Missing membership. | Create membership. | organization/session helpers | +| `ADMIN_CRM_ASSIGNMENT` | Authorization | Administrator CRM assignment resolves | Ensure CRM admin permissions resolve. | CRM | Resolve CRM access. | `crm_admin` or equivalent permissions resolve. | Compatibility business role only, no assignment. | No CRM access. | Seed role profiles and assignment. | `resolveCrmAccess()` | +| `ORG_ACTIVE` | Organization | Organization active/resolvable | Ensure active org context works. | all setup | Resolve organization and active org for admin. | Organization exists and admin active org set. | Organization exists but active org missing. | Organization missing. | Update `users.active_organization_id`. | `requireOrganizationAccess()` | +| `BRANCH_OPTION` | Scope | Branch option exists | Ensure branch scope can resolve. | CRM/quotation | Query `ms_options` category `crm_branch`. | At least one active branch. | Branch exists but no default convention. | No active branch. | Configure branch step. | master-options foundation | +| `PRODUCT_TYPE_OPTION` | Scope | Product type option exists | Ensure product scope can resolve. | CRM/quotation | Query `ms_options` category `crm_product_type`. | At least one active product type. | Product type exists but no document prefix metadata. | No active product type. | Configure product types. | master-options foundation | +| `SEQUENCE_CUSTOMER` | Document sequence | Customer sequence preview | Ensure customer codes can generate. | CRM | Call sequence preview for customer. | Preview returns code. | Generic branch/product scope only. | Preview fails. | Configure document sequence. | document-sequence foundation | +| `SEQUENCE_LEAD` | Document sequence | Lead sequence preview | Ensure lead codes can generate. | CRM | Call sequence preview for `crm_lead`. | Preview returns code. | Generic scope only. | Preview fails. | Configure lead sequence. | document-sequence foundation | +| `SEQUENCE_OPPORTUNITY` | Document sequence | Opportunity sequence preview | Ensure opportunity codes can generate. | CRM | Call sequence preview for `crm_opportunity`. | Preview returns code. | Generic scope only. | Preview fails. | Configure opportunity sequence. | document-sequence foundation | +| `SEQUENCE_QUOTATION` | Document sequence | Quotation sequence preview | Ensure quotation codes can generate. | quotation | Preview by branch/product type. | Preview returns code for each configured product type. | Missing some branch/product combinations. | No quotation sequence works. | Configure quotation sequences. | document-sequence foundation | +| `APPROVAL_WORKFLOW` | Approval | Quotation workflow resolves | Ensure quotation approval can start. | quotation | Query active workflow for quotation. | Active workflow exists. | Multiple active candidates. | Missing workflow. | Configure approval workflow. | approval foundation | +| `APPROVAL_MATRIX` | Approval | Approval matrix resolves | Ensure quotation can select workflow by scope/amount. | quotation | Resolve matrix for sample quotation amount. | Matrix resolves. | Default-only fallback. | No matrix resolves. | Configure approval matrix. | approval foundation | +| `NOTIFICATION_TEMPLATES` | Notifications | Approval notification templates exist | Ensure in-app approval notifications can render. | CRM/quotation | Query `app_notification_templates`. | Required approval templates active. | Some optional templates missing. | Required templates missing. | Run foundation seed. | notification foundation | +| `REPORT_DEFINITIONS` | Reports | Report definitions exist | Ensure report catalog works. | CRM reports | Query `crm_report_definitions`. | Required definitions active. | Definitions exist but categories missing. | No definitions. | Run report foundation seed. | report foundation | +| `CSV_PREVIEW` | Import | CSV preview integrity | Ensure uploaded files were validated before commit. | CSV import | Check latest preview hash/status. | Preview exists and matches commit input hash. | Preview stale after dependency change. | Missing preview for commit. | Re-run CSV preview. | setup import service | +| `SETUP_MANIFEST` | Setup | Seed manifest recorded | Ensure lifecycle tracking exists. | setup completion | Query proposed manifest tables once implemented. | Manifest recorded with checksums. | Manifest not implemented in early slice. | Manifest required but missing. | Implement D.7.8. | setup manifest service | + +## Required Completion Rule + +Setup can complete only when all required checks for selected setup mode pass: + +- Production foundation mode: all first-login, CRM, quotation, storage/PDF required checks must pass except optional email may warn. +- Demo/UAT mode: production required checks plus UAT user/data checks must pass. +- Dry-run mode: failures do not write setup completion, but report can be exported. diff --git a/plans/task-d.7.1.md b/plans/task-d.7.1.md new file mode 100644 index 0000000..aa6639a --- /dev/null +++ b/plans/task-d.7.1.md @@ -0,0 +1,775 @@ +# Task D.7.1 – Setup Blueprint & Seed Specification + +## Objective + +Create a complete implementation blueprint for the ALLA OS Initial Setup, Seed Framework, CSV Import, Reset/Reseed, and Verification workflow before starting code implementation. + +This task must convert the Phase 1/2 audit from Task D.7 into a precise technical specification that can be implemented safely without guessing, duplicating seed logic, or breaking existing CRM, RBAC, approval, document sequence, storage, or PDF behavior. + +No production code implementation should begin in this task except documentation/specification files and optional CSV template drafts. + +--- + +# Scope + +## In Scope + +- Complete Seed Matrix +- CSV Template Specification +- CSV Import Mapping +- Setup Wizard Flow Specification +- Setup State Machine +- Verification Matrix +- Reset/Reseed Matrix +- Seed Version Strategy +- Seed Manifest Strategy +- Setup Plugin Architecture +- Implementation Slice Plan for D.7.2+ + +## Out of Scope + +- Building the final Setup Wizard UI +- Building the CSV import engine +- Running destructive reset logic +- Changing existing seed behavior +- Changing production CRM business logic +- Adding unsupported first-class domains without schema decision + +--- + +# Phase 1 — Review Existing Audit + +Start from the existing Task D.7 Phase 1/2 audit report. + +Review: + +- `plans/task-d.7.md` +- Task D.7 audit output +- `AGENTS.md` +- `docs/standards/*` +- `docs/adr/*` +- `src/db/schema.ts` +- `src/db/seeds/**/*` +- `scripts/seed-system.ts` +- `scripts/seed-uat.ts` +- `scripts/seed-reset.ts` +- `scripts/db/reset-database.ts` +- `src/features/foundation/**/*` +- `src/features/crm/**/*` +- `src/features/setup/**/*` + +Do not repeat the same audit. Extend it into a blueprint. + +--- + +# Phase 2 — Complete Seed Matrix + +Create a complete seed matrix covering all known setup-related tables and concepts. + +Output file: + +```text +docs/setup/seed-matrix.md +``` + +Required columns: + +```text +Module +Entity +Table / Storage +Seed Type +Required For First Login +Required For CRM +Required For Quotation +CSV Importable +Wizard Editable +Auto Generated +Source of Truth +Dependencies +Import Order +Reset Scope +Notes +``` + +Seed Type values: + +```text +foundation +organization +business +demo_uat +runtime_generated +runtime_only +unsupported_pending_schema +``` + +Example row: + +```text +Foundation | Branch | ms_options(category=crm_branch) | organization | yes | yes | yes | yes | yes | no | CSV/Wizard | organization, created_by_user | 08 | organization_reset | Branch is currently modeled as master option, not a branch table. +``` + +The matrix must explicitly include: + +- users +- organizations +- memberships +- CRM role profiles +- CRM user role assignments +- ms_options +- crm_branch options +- crm_product_type options +- document sequences +- approval workflows +- approval steps +- approval matrices +- document templates +- document template versions +- document template mappings +- document libraries +- notification templates +- report definitions +- customers +- contacts +- contact shares +- leads +- opportunities +- quotations +- quotation items +- quotation topics +- quotation topic items +- quotation project parties +- followups +- audit logs +- notification events +- generated PDFs / document artifacts +- uploaded files / attachments +- setup state +- seed manifests + +Also include unsupported or ambiguous items: + +- departments +- teams +- warehouses +- sites +- price books +- services +- tax rate table if not first-class +- email provider config if not first-class + +--- + +# Phase 3 — CSV Template Specification + +Create CSV template specification. + +Output file: + +```text +docs/setup/csv-template-spec.md +``` + +For each supported CSV, define: + +```text +File Name +Purpose +Target Table / Service +Seed Type +Required Columns +Optional Columns +Generated Columns +Validation Rules +Relationship Resolution +Duplicate Detection +Upsert Key +Import Order +Example Rows +Error Examples +``` + +Required CSV specs: + +```text +organizations.csv +users.csv +memberships.csv +crm-role-assignments.csv +master-options.csv +branches.csv +product-types.csv +document-sequences.csv +approval-workflows.csv +approval-steps.csv +approval-matrix.csv +customers.csv +contacts.csv +contact-shares.csv +leads.csv +opportunities.csv +opportunity-parties.csv +quotations.csv +quotation-items.csv +quotation-parties.csv +quotation-topics.csv +quotation-topic-items.csv +``` + +Unsupported/deferred specs must be marked clearly: + +```text +departments.csv +teams.csv +warehouses.csv +sites.csv +services.csv +price-books.csv +tax-rates.csv +attachments.csv +email-settings.csv +``` + +Each CSV section must include at least one example row. + +Example: + +```csv +organization_code,organization_name,slug,tax_id,default_currency,status +ALLA,ALLA Public Company Limited,alla,0107558000393,THB,active +``` + +--- + +# Phase 4 — Import Mapping Specification + +Create import mapping specification. + +Output file: + +```text +docs/setup/import-mapping.md +``` + +For every supported CSV, define: + +```text +CSV Column +Target Field +Transform +Validation +Required +Reference Resolver +Fallback +Notes +``` + +Important mappings: + +## branches.csv + +Must map to: + +```text +ms_options +category = crm_branch +organization_id = selected organization +value/code = branch_code +label = branch_name +metadata = prefix, address, tax info if applicable +``` + +## product-types.csv + +Must map to: + +```text +ms_options +category = crm_product_type +value/code = product_type_code +label = product_type_name +metadata = document_prefix +``` + +## document-sequences.csv + +Must resolve: + +```text +organization_code +branch_code +product_type_code +document_type +period_format +prefix +current_number +``` + +## users.csv + memberships.csv + +Must clarify whether user creation and organization membership are separate or combined. + +## crm-role-assignments.csv + +Must resolve: + +```text +user_email +organization_code +crm_role_code +branch_code +product_type_code +``` + +## customers.csv + +Must resolve: + +```text +organization_code +owner_user_email +customer_type +customer_group +``` + +## leads/opportunities/quotations + +Must resolve: + +```text +organization_code +customer_code +contact_email +branch_code +product_type_code +owner_user_email +created_by_email +``` + +--- + +# Phase 5 — Setup Wizard Blueprint + +Create wizard blueprint. + +Output file: + +```text +docs/setup/setup-wizard-blueprint.md +``` + +Define: + +```text +Step Number +Step Name +Purpose +Inputs +Validation +Actions +APIs +Can Skip +Can Go Back +Resume Behavior +Error Handling +Output +``` + +Required wizard steps: + +1. System Readiness +2. Foundation Install +3. Organization Setup +4. Branch & Product Type Setup +5. Administrator Setup +6. Document Sequence Setup +7. Approval Workflow Setup +8. CSV Import +9. Verification +10. Finish & Report + +Each step must define required fields. + +Example: + +```text +Organization Setup Inputs + +organization_name: required +organization_code: required, uppercase, unique +slug: required, lowercase, unique +tax_id: optional +phone: optional +email: optional email format +address: optional +default_currency: required, default THB +timezone: required, default Asia/Bangkok +logo: optional +``` + +--- + +# Phase 6 — Setup State Machine + +Create setup state specification. + +Output file: + +```text +docs/setup/setup-state-machine.md +``` + +Define state model: + +```text +not_started +readiness_checked +foundation_installed +organization_created +administrator_created +scope_configured +sequences_configured +approval_configured +csv_validated +csv_imported +verified +completed +failed +cancelled +``` + +Define events: + +```text +CHECK_READINESS +INSTALL_FOUNDATION +CREATE_ORGANIZATION +CREATE_ADMIN +CONFIGURE_SCOPE +CONFIGURE_SEQUENCES +CONFIGURE_APPROVAL +VALIDATE_CSV +IMPORT_CSV +RUN_VERIFICATION +COMPLETE_SETUP +FAIL_STEP +CANCEL_SETUP +RESUME_SETUP +RESET_STEP +``` + +Define persistence strategy. + +If no setup table exists, propose new table: + +```text +setup_runs +setup_run_steps +seed_manifests +seed_manifest_items +``` + +Do not implement migrations in this task unless explicitly approved. + +--- + +# Phase 7 — Verification Matrix + +Create verification matrix. + +Output file: + +```text +docs/setup/verification-matrix.md +``` + +Required columns: + +```text +Check ID +Area +Check Name +Purpose +Required For +Method +Pass Condition +Warning Condition +Fail Condition +Suggested Fix +API / Service +``` + +Checks must include: + +- environment variable presence +- database connectivity +- migration status +- storage read/write/delete smoke test +- upload directory readiness +- first administrator exists +- administrator membership exists +- administrator CRM role assignment resolves +- organization active +- branch option exists +- product type option exists +- document sequence preview works +- quotation approval workflow resolves +- approval matrix resolves +- PDF template exists +- PDF template version active +- PDF mapping exists +- notification templates exist +- report definitions exist +- email configuration optional dry-run + +--- + +# Phase 8 — Reset / Reseed Matrix + +Create reset/reseed specification. + +Output file: + +```text +docs/setup/reset-reseed-matrix.md +``` + +Define reset scopes: + +```text +foundation_reset +organization_reset +business_reset +demo_uat_reset +full_reset +csv_import_rollback +setup_run_reset +``` + +For each scope define: + +```text +Tables affected +Data retained +Data deleted +Safety guard +Requires confirmation +Allowed environment +Transaction behavior +Storage cleanup behavior +Audit behavior +Recommended command/API +``` + +Rules: + +- Production destructive reset must be blocked. +- Reset must require explicit confirmation. +- Storage cleanup must be compensated and reported. +- Runtime generated approved artifacts must not be casually deleted outside explicit full reset/demo reset. + +--- + +# Phase 9 — Seed Version and Manifest Strategy + +Create seed lifecycle specification. + +Output file: + +```text +docs/setup/seed-version-manifest.md +``` + +Define: + +```text +Seed Package +Version +Dependencies +Checksum +Applied At +Applied By +Target Organization +Source +Status +Report +``` + +Propose manifest format: + +```json +{ + "name": "crm-foundation", + "version": "1.0.0", + "type": "foundation", + "dependencies": ["system-core@1.0.0"], + "items": [ + { + "file": "master-options.csv", + "checksum": "...", + "required": true + } + ] +} +``` + +Define upgrade behavior: + +```text +same version + same checksum = skip +same version + changed checksum = warn/block unless force +newer version = apply if compatible +older version = block unless rollback mode +``` + +--- + +# Phase 10 — Setup Plugin Architecture + +Create plugin architecture proposal. + +Output file: + +```text +docs/setup/setup-plugin-architecture.md +``` + +Goal: + +Future modules can register setup behavior without editing the core wizard. + +Plugin contract should cover: + +```text +module_id +display_name +setup_steps +csv_templates +seed_tasks +verification_checks +reset_scopes +dependencies +enabled_condition +``` + +Example modules: + +```text +core +foundation +crm +quotation +approval +document +notification +reporting +future_asset +future_hr +``` + +No implementation required, but the design must be concrete enough for future implementation. + +--- + +# Phase 11 — Implementation Plan for D.7.2+ + +Create implementation slice plan. + +Output file: + +```text +plans/task-d.7.2.md +``` + +Recommended slices: + +## D.7.2 + +Setup Readiness and Verification APIs + +## D.7.3 + +CSV Template Generator and Static Template Pack + +## D.7.4 + +CSV Preview Validation Engine + +## D.7.5 + +CSV Commit Import Engine + +## D.7.6 + +Dashboard Setup Wizard UI + +## D.7.7 + +Scoped Reset and Reseed Engine + +## D.7.8 + +Seed Manifest and Setup State Persistence + +Each slice must include: + +```text +Objective +Files to review +Implementation scope +Out of scope +Acceptance criteria +Risk notes +``` + +--- + +# Deliverables + +The task is complete only when the following files exist: + +```text +docs/setup/seed-matrix.md +docs/setup/csv-template-spec.md +docs/setup/import-mapping.md +docs/setup/setup-wizard-blueprint.md +docs/setup/setup-state-machine.md +docs/setup/verification-matrix.md +docs/setup/reset-reseed-matrix.md +docs/setup/seed-version-manifest.md +docs/setup/setup-plugin-architecture.md +plans/task-d.7.2.md +``` + +Optional drafts: + +```text +setup/templates/*.csv +``` + +--- + +# Acceptance Criteria + +- The setup implementation can be started without guessing. +- Every setup-related entity is classified. +- Every supported CSV has required fields, validation, mapping, upsert key, and examples. +- Unsupported CSVs are clearly marked with the reason. +- Wizard steps have inputs, validation, actions, APIs, outputs, and resume behavior. +- Setup state machine is defined. +- Verification checks are actionable. +- Reset/reseed scopes are safe and explicit. +- Seed version and manifest behavior is defined. +- Future module setup plugin strategy is documented. +- No existing production behavior is changed. + +--- + +# Implementation Log + +## 2026-07-02 - Blueprint documentation completed + +- Created complete setup blueprint/specification deliverables under `docs/setup/`. +- Created next implementation slice plan at `plans/task-d.7.2.md`. +- No production code behavior was changed. diff --git a/plans/task-d.7.2.md b/plans/task-d.7.2.md new file mode 100644 index 0000000..cdd6a36 --- /dev/null +++ b/plans/task-d.7.2.md @@ -0,0 +1,222 @@ +# Task D.7.2 - Setup Readiness and Verification APIs + +## Objective + +Implement the first non-destructive setup API slice for ALLA OS Initial Setup: readiness checks, verification checks, and report contracts. This slice must not run destructive reset logic or commit seed/import data. + +## Files To Review + +- `AGENTS.md` +- `plans/task-d.7.md` +- `plans/task-d.7.1.md` +- `docs/implementation/task-d7-initial-system-setup-audit.md` +- `docs/setup/seed-matrix.md` +- `docs/setup/setup-wizard-blueprint.md` +- `docs/setup/setup-state-machine.md` +- `docs/setup/verification-matrix.md` +- `docs/setup/reset-reseed-matrix.md` +- `src/db/schema.ts` +- `src/features/foundation/storage/**` +- `src/features/foundation/document-sequence/**` +- `src/features/foundation/document-template/**` +- `src/features/foundation/pdf-generator/**` +- `src/features/foundation/approval/**` +- `src/features/foundation/master-options/**` +- `src/features/crm/reports/**` +- `src/lib/auth/session.ts` +- `src/lib/auth/crm-access.ts` + +## Implementation Scope + +- Create setup feature API contracts: + - `src/features/setup/api/types.ts` + - `src/features/setup/api/service.ts` + - `src/features/setup/api/queries.ts` +- Create read-only setup server services: + - `src/features/setup/server/readiness.service.ts` + - `src/features/setup/server/verification.service.ts` +- Create thin route handlers: + - `src/app/api/setup/readiness/route.ts` + - `src/app/api/setup/verify/route.ts` +- Implement structured check result types: + - `PASS` + - `WARNING` + - `FAIL` +- Implement checks from `docs/setup/verification-matrix.md` that can safely run without mutating production data. +- For storage smoke test, either use an explicitly temporary setup key and cleanup immediately or mark as warning when provider does not support safe smoke test yet. +- Return actionable `suggestedFix` values without leaking secrets or raw SQL errors. + +## Out Of Scope + +- Final Setup Wizard UI. +- CSV template generator. +- CSV preview/import commit engine. +- Seed execution. +- Destructive reset/reseed APIs. +- Setup state persistence tables. +- Migrations for `setup_runs` or `seed_manifests`. +- Changing existing production CRM behavior. + +## Acceptance Criteria + +- `GET /api/setup/readiness` returns readiness report with environment, database, migration, storage, PDF, and optional email checks. +- `POST /api/setup/verify` returns verification report for a selected organization or current active organization. +- Route handlers are thin and call setup server services. +- All checks return consistent structured result objects. +- Checks use existing foundation services where available. +- No production data is created, updated, or deleted except safe temporary storage smoke-test object if explicitly implemented. +- Unauthorized users cannot access setup reports. +- Relevant lint/typecheck passes or failures are documented. + +## Risk Notes + +- Migration status detection may need a conservative fallback if Drizzle metadata table differs by environment. +- Storage smoke testing is partly non-transactional; if cleanup fails, the API must report a warning with the storage key. +- PDF/template checks should inspect existing template rows and mappings, not render or persist PDFs in this slice. +- Email checks should remain optional unless a first-class provider configuration is added. + +--- + +# Future Slices + +--- + +# Implementation Log + +## 2026-07-02 - Readiness and verification APIs implemented + +- Added setup API contracts under `src/features/setup/api/`. +- Added read-only setup services under `src/features/setup/server/`. +- Added protected route handlers: + - `GET /api/setup/readiness` + - `POST /api/setup/verify` +- Verification: + - `npm run typecheck` passed. + - `npx oxlint src/features/setup src/app/api/setup` passed. + - `npm run lint` still fails on pre-existing unrelated repo lint issues outside this task. +- Implementation note: `docs/implementation/task-d72-setup-readiness-verification-apis.md` + +## Task D.7.3 - CSV Template Generator Static Template Pack + +Objective: create checked-in `setup/templates/*.csv` files and optional route to download them. + +Scope: + +- Generate templates from `docs/setup/csv-template-spec.md`. +- Include headers and sample rows. +- Mark unsupported templates in docs only, not importable packs. + +Out scope: + +- CSV parser. +- CSV commit import. + +Acceptance criteria: + +- Supported templates exist under `setup/templates/`. +- Each template has required headers and one safe sample row. +- Template docs match checked-in files. + +## Task D.7.4 - CSV Preview Validation Engine + +Objective: implement reusable CSV parser, validation, relationship resolution, duplicate detection, and preview report. + +Scope: + +- `src/features/setup/server/csv-import.service.ts` +- preview-only API route +- validation from `docs/setup/import-mapping.md` + +Out scope: + +- DB commit writes. +- rollback. + +Acceptance criteria: + +- Preview classifies imported/updated/skipped/error candidates. +- Unsupported templates are blocked with clear reason. + +## Task D.7.5 - CSV Commit Import Engine + +Objective: commit validated CSV imports transactionally where safe. + +Scope: + +- commit API +- transaction support +- upsert support +- import report + +Out scope: + +- UI. +- arbitrary rollback beyond failed transaction. + +Acceptance criteria: + +- Commit requires matching preview hash. +- DB-only imports rollback on error. +- Related feature/foundation services are reused where practical. + +## Task D.7.6 - Dashboard Setup Wizard UI + +Objective: build dashboard-native setup wizard using existing app shell. + +Scope: + +- `src/app/dashboard/administration/setup/page.tsx` +- setup wizard components under `src/features/setup/components/` +- readiness, verification, CSV preview integration + +Out scope: + +- destructive reset UI. +- migrations for setup state persistence unless D.7.8 completed first. + +Acceptance criteria: + +- Uses `PageContainer`. +- Responsive, accessible, dark-mode compatible. +- Shows progress, preview tables, validation badges, and summary report. + +## Task D.7.7 - Scoped Reset and Reseed Engine + +Objective: implement non-production scoped reset/reseed service. + +Scope: + +- reset scopes from `docs/setup/reset-reseed-matrix.md` +- safety guards +- confirmation tokens +- reports + +Out scope: + +- production reset workflow. + +Acceptance criteria: + +- Production destructive reset blocked. +- Scope reset is organization-aware. +- Storage cleanup is reported. + +## Task D.7.8 - Seed Manifest and Setup State Persistence + +Objective: add persistent setup run and seed manifest tracking. + +Scope: + +- schema/migrations for `setup_runs`, `setup_run_steps`, `seed_manifests`, `seed_manifest_items` +- state transitions from `docs/setup/setup-state-machine.md` +- manifest behavior from `docs/setup/seed-version-manifest.md` + +Out scope: + +- new business seed behavior. + +Acceptance criteria: + +- Setup can resume. +- Seed packages are versioned and checksummed. +- Completion report is persisted. diff --git a/plans/task-d.7.md b/plans/task-d.7.md new file mode 100644 index 0000000..ba6d723 --- /dev/null +++ b/plans/task-d.7.md @@ -0,0 +1,531 @@ +# Task D.7 – Initial System Setup Wizard & Seed Framework + +## Objective + +Design and implement a production-ready initialization framework for ALLA OS that allows a brand-new installation to be configured without writing code. + +The implementation must audit the current project, discover all required seed dependencies, generate reusable CSV templates, provide a generic CSV import engine, and build a Setup Wizard for initializing the system from an empty database. + +The solution must preserve existing architecture, Organization isolation, Branch scope, Product Type scope, RBAC, Approval Workflow, Document Sequence, Storage, and PDF configuration. + +--- + +# Phase 1 — Existing System Audit (Mandatory) + +Before implementing, perform a complete audit of the current project. + +Review at minimum: + +- AGENTS.md +- docs/standards/* +- docs/adr/* +- drizzle/* +- src/db/schema.ts +- src/db/seeds/**/* +- src/features/foundation/**/* +- src/features/auth/**/* +- src/features/crm/**/* +- src/features/approval/**/* +- src/features/storage/**/* +- src/features/document-sequence/**/* +- src/features/master-options/**/* +- src/features/pdf/**/* +- src/features/organizations/**/* + +Do not create duplicate seed logic. + +Reuse existing services whenever possible. + +--- + +# Phase 2 — Seed Dependency Analysis + +Audit every table and identify: + +- Required +- Optional +- Generated +- Runtime only + +Generate a dependency graph. + +Example + +Organization +→ Branch +→ Membership +→ User +→ Customer +→ Contact +→ Lead +→ Opportunity +→ Quotation +→ Approval + +Produce a report containing: + +- Seed order +- Dependency tree +- Circular dependency detection +- Missing seed data +- Existing seed coverage +- Recommended improvements + +No implementation should begin until this report is complete. + +--- + +# Phase 3 — Seed Classification + +Separate all seed data into four groups. + +## Foundation Seed + +Required for every installation. + +Examples + +- System Configuration +- Roles +- Permissions +- Master Option Categories +- Master Options +- Status Definitions +- Document Types +- Product Types +- Currency +- Tax Rates +- Storage Providers +- Notification Types +- Approval Types +- Default PDF Templates + +--- + +## Organization Seed + +Organization-specific data. + +Examples + +- Organization +- Company Profile +- Branch +- Department +- Team +- Warehouse +- Position +- Company Logo +- Signature +- Email Settings +- Approval Matrix +- Document Sequence + +--- + +## Business Seed + +Operational data. + +Examples + +- Customer +- Contact +- Site +- Product +- Service +- Price Book +- Lead +- Opportunity +- Quotation +- Quotation Items + +--- + +## Demo / UAT Seed + +Sample business data. + +Create realistic demo data for: + +Organization + +- ALLA +- ONVALLA + +Users + +- IT +- Marketing +- Sales +- Sales Manager +- CEO + +Customers + +Leads + +Opportunities + +Approved Quotations + +Rejected Quotations + +Follow-up Activities + +Attachments + +--- + +# Phase 4 — CSV Template Generator + +Generate reusable CSV templates for every importable entity. + +Directory + +/setup/templates/ + +Example + +organizations.csv + +branches.csv + +users.csv + +memberships.csv + +roles.csv + +permissions.csv + +customers.csv + +contacts.csv + +sites.csv + +products.csv + +leads.csv + +opportunities.csv + +quotations.csv + +quotation-items.csv + +approval-matrix.csv + +document-sequences.csv + +master-options.csv + +currencies.csv + +tax-rates.csv + +Each template must contain + +- Header +- Sample row +- Required fields +- Optional fields +- Enum examples +- Validation rules +- Relationship examples + +--- + +# Phase 5 — Generic CSV Import Engine + +Create a reusable import engine. + +Requirements + +- CSV Parser +- Validation +- Required field checking +- Enum validation +- Foreign key resolution +- Duplicate detection +- Upsert support +- Transaction support +- Rollback on failure +- Import report + +Return + +- Imported +- Updated +- Skipped +- Errors +- Warnings + +The engine must be reusable by every future module. + +--- + +# Phase 6 — Setup Wizard + +Create a new Administration page. + +Administration + +└── Setup Wizard + +Wizard flow + +## Step 1 + +System Readiness + +Check + +- Environment Variables +- Database +- Storage +- Migration Status +- Upload Directory +- PDF Template +- Email Configuration + +--- + +## Step 2 + +Initialize Foundation + +Install + +- Foundation Seed +- Roles +- Permissions +- Master Data + +--- + +## Step 3 + +Organization Setup + +Configure + +- Company Name +- Organization Code +- Tax ID +- Address +- Logo +- Default Currency +- Fiscal Year + +--- + +## Step 4 + +Branch Setup + +Configure + +- Branch +- Department +- Team +- Warehouse + +--- + +## Step 5 + +CSV Import + +Upload CSV files. + +Preview + +- Data +- Validation +- Error Summary + +Support partial import. + +--- + +## Step 6 + +Document Sequence + +Configure + +Organization + +↓ + +Branch + +↓ + +Product Type + +↓ + +Prefix + +↓ + +Running Number + +Preview + +CRA2607-001 + +DKA2607-001 + +SCO2607-001 + +--- + +## Step 7 + +Approval Workflow + +Configure + +- Sequential +- Any One +- Amount-based Approval + +Preview approval flow. + +--- + +## Step 8 + +Administrator + +Create first administrator account. + +Assign + +- System Admin +- Organization Admin + +--- + +## Step 9 + +Verification + +Automatically verify + +- Login +- Permission +- Organization +- Branch +- Document Sequence +- Approval +- Storage +- PDF +- Email + +Display + +PASS + +WARNING + +FAIL + +--- + +## Step 10 + +Finish + +Generate + +- Setup Summary +- Seed Summary +- Import Summary +- Verification Report + +--- + +# Phase 7 — Reset & Reseed + +Create reusable reset commands. + +Support + +- Reset Foundation +- Reset Organization +- Reset Demo Data +- Reset Business Data +- Full Reset + +All operations must be transactional. + +--- + +# UI Requirements + +Use shadcn/ui. + +Implement + +- Stepper Wizard +- Progress Bar +- Import Preview Table +- Validation Badge +- Summary Cards +- Success Screen +- Error Screen +- Resume Setup +- Dark Mode +- Responsive Layout + +--- + +# Technical Requirements + +- Reuse existing services. +- Do not duplicate business logic. +- Idempotent seed operations. +- Organization-aware. +- Branch-aware. +- Product Type-aware. +- Transaction-safe. +- Extensible for future modules. +- Follow ALLA OS coding standards. + +--- + +# Deliverables + +- Seed dependency audit report +- Seed inventory matrix +- CSV templates +- Generic CSV import engine +- Setup Wizard +- Validation engine +- Verification engine +- Reset engine +- Seed execution report +- Technical documentation + +--- + +# Acceptance Criteria + +> Implementation log 2026-07-02: Phase 1/2 audit completed and saved to +> `docs/implementation/task-d7-initial-system-setup-audit.md`. Implementation +> is intentionally gated until this seed dependency audit report is reviewed. + +- Empty database can be initialized entirely through the Setup Wizard. +- All Foundation data can be installed without manual SQL. +- Organizations can be created from the wizard. +- CSV templates cover every importable master and business entity. +- CSV imports support validation, preview, rollback, and upsert. +- Demo/UAT data can be generated independently from Foundation data. +- System verification reports all required services before first login. +- Reset and reseed operations are repeatable and deterministic. +- Existing functionality remains backward compatible. diff --git a/plans/task-seed-uat.md b/plans/task-seed-uat.md new file mode 100644 index 0000000..b647473 --- /dev/null +++ b/plans/task-seed-uat.md @@ -0,0 +1,28 @@ +Create UAT seed pipeline for ALLA OS. + +Requirements: +1. Add CSV import seed support from /src/db/seeds/uat-csv/*.csv +2. Import order: + organizations + branches + product_types + users + memberships + document_sequences + customers + contacts + leads_opportunities + quotations + quotation_items + approval_steps +3. Add npm scripts: + - seed:system + - seed:org + - seed:uat + - db:reset:uat +4. Seed must be idempotent using upsert. +5. Reset must clear transactional CRM data first, then seed foundation/master/UAT data again. +6. Preserve organization boundary: ALLA and ONVALLA data must not mix. +7. Verify login personas: + IT → MK → Sale → Manager → CEO +8. Verify document sequence by organization + branch + product type. diff --git a/src/app/api/setup/readiness/route.ts b/src/app/api/setup/readiness/route.ts new file mode 100644 index 0000000..4544394 --- /dev/null +++ b/src/app/api/setup/readiness/route.ts @@ -0,0 +1,20 @@ +import { NextResponse } from 'next/server'; +import { getSetupReadinessReport } from '@/features/setup/server/readiness.service'; +import { AuthError, requireSystemRole } from '@/lib/auth/session'; + +export async function GET() { + try { + const session = await requireSystemRole('super_admin'); + const report = await getSetupReadinessReport({ + organizationId: session.user.activeOrganizationId ?? null + }); + + return NextResponse.json(report); + } catch (error) { + if (error instanceof AuthError) { + return NextResponse.json({ message: error.message }, { status: error.status }); + } + + return NextResponse.json({ message: 'Unable to run setup readiness checks' }, { status: 500 }); + } +} diff --git a/src/app/api/setup/verify/route.ts b/src/app/api/setup/verify/route.ts new file mode 100644 index 0000000..9774478 --- /dev/null +++ b/src/app/api/setup/verify/route.ts @@ -0,0 +1,37 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { z } from 'zod'; +import { getSetupVerificationReport } from '@/features/setup/server/verification.service'; +import { AuthError, requireSystemRole } from '@/lib/auth/session'; + +const verificationSchema = z + .object({ + organizationId: z.string().min(1).optional(), + administratorEmail: z.string().email().optional() + }) + .optional(); + +export async function POST(request: NextRequest) { + try { + const session = await requireSystemRole('super_admin'); + const body = verificationSchema.parse(await request.json().catch(() => undefined)) ?? {}; + const report = await getSetupVerificationReport({ + organizationId: body.organizationId ?? session.user.activeOrganizationId ?? null, + administratorEmail: body.administratorEmail + }); + + return NextResponse.json(report); + } catch (error) { + if (error instanceof AuthError) { + return NextResponse.json({ message: error.message }, { status: error.status }); + } + + if (error instanceof z.ZodError) { + return NextResponse.json( + { message: 'Invalid setup verification payload', errors: error.flatten() }, + { status: 400 } + ); + } + + return NextResponse.json({ message: 'Unable to run setup verification checks' }, { status: 500 }); + } +} diff --git a/src/features/setup/api/queries.ts b/src/features/setup/api/queries.ts new file mode 100644 index 0000000..2757816 --- /dev/null +++ b/src/features/setup/api/queries.ts @@ -0,0 +1,22 @@ +import { queryOptions } from '@tanstack/react-query'; +import { getSetupReadiness, verifySetup } from './service'; +import type { SetupVerificationPayload } from './types'; + +export const setupKeys = { + all: ['setup'] as const, + readiness: () => [...setupKeys.all, 'readiness'] as const, + verification: (payload: SetupVerificationPayload = {}) => + [...setupKeys.all, 'verification', payload] as const +}; + +export const setupReadinessQueryOptions = () => + queryOptions({ + queryKey: setupKeys.readiness(), + queryFn: () => getSetupReadiness() + }); + +export const setupVerificationQueryOptions = (payload: SetupVerificationPayload = {}) => + queryOptions({ + queryKey: setupKeys.verification(payload), + queryFn: () => verifySetup(payload) + }); diff --git a/src/features/setup/api/service.ts b/src/features/setup/api/service.ts new file mode 100644 index 0000000..6083833 --- /dev/null +++ b/src/features/setup/api/service.ts @@ -0,0 +1,19 @@ +import { apiClient } from '@/lib/api-client'; +import type { + SetupReadinessReport, + SetupVerificationPayload, + SetupVerificationReport +} from './types'; + +export async function getSetupReadiness(): Promise { + return apiClient('/setup/readiness'); +} + +export async function verifySetup( + payload: SetupVerificationPayload = {} +): Promise { + return apiClient('/setup/verify', { + method: 'POST', + body: JSON.stringify(payload) + }); +} diff --git a/src/features/setup/api/types.ts b/src/features/setup/api/types.ts new file mode 100644 index 0000000..9c2b3f8 --- /dev/null +++ b/src/features/setup/api/types.ts @@ -0,0 +1,39 @@ +export type SetupCheckStatus = 'PASS' | 'WARNING' | 'FAIL'; + +export interface SetupCheckResult { + id: string; + area: string; + name: string; + status: SetupCheckStatus; + requiredFor: string[]; + purpose: string; + message: string; + suggestedFix?: string; + details?: Record; + checkedAt: string; +} + +export interface SetupReportSummary { + pass: number; + warning: number; + fail: number; + total: number; +} + +export interface SetupReadinessReport { + success: boolean; + time: string; + status: SetupCheckStatus; + message: string; + summary: SetupReportSummary; + checks: SetupCheckResult[]; +} + +export interface SetupVerificationPayload { + organizationId?: string; + administratorEmail?: string; +} + +export interface SetupVerificationReport extends SetupReadinessReport { + organizationId: string | null; +} diff --git a/src/features/setup/server/readiness.service.ts b/src/features/setup/server/readiness.service.ts new file mode 100644 index 0000000..f14c584 --- /dev/null +++ b/src/features/setup/server/readiness.service.ts @@ -0,0 +1,408 @@ +import 'server-only'; + +import { mkdir, rm, writeFile } from 'node:fs/promises'; +import path from 'node:path'; +import { and, count, eq, inArray, isNull, sql } from 'drizzle-orm'; +import { + crmApprovalWorkflows, + crmDocumentTemplateMappings, + crmDocumentTemplateVersions, + crmDocumentTemplates, + crmReportDefinitions, + documentSequences, + memberships, + msOptions, + organizations, + users +} from '@/db/schema'; +import { EmailNotificationProvider } from '@/features/foundation/notifications/server/email-provider'; +import { getStorageProvider } from '@/features/foundation/storage/service'; +import { db } from '@/lib/db'; +import type { + SetupCheckResult, + SetupCheckStatus, + SetupReadinessReport, + SetupReportSummary +} from '../api/types'; + +type CheckMeta = Omit; + +const REQUIRED_TABLES = [ + { name: 'users', table: users }, + { name: 'organizations', table: organizations }, + { name: 'memberships', table: memberships }, + { name: 'ms_options', table: msOptions }, + { name: 'document_sequences', table: documentSequences }, + { name: 'crm_approval_workflows', table: crmApprovalWorkflows }, + { name: 'crm_document_templates', table: crmDocumentTemplates }, + { name: 'crm_report_definitions', table: crmReportDefinitions } +] as const; + +function now() { + return new Date().toISOString(); +} + +export function buildSetupCheck( + meta: CheckMeta, + status: SetupCheckStatus, + message: string, + details?: Record +): SetupCheckResult { + return { + ...meta, + status, + message, + details, + checkedAt: now() + }; +} + +export function summarizeSetupChecks(checks: SetupCheckResult[]): SetupReportSummary { + return { + pass: checks.filter((check) => check.status === 'PASS').length, + warning: checks.filter((check) => check.status === 'WARNING').length, + fail: checks.filter((check) => check.status === 'FAIL').length, + total: checks.length + }; +} + +export function resolveSetupStatus(summary: SetupReportSummary): SetupCheckStatus { + if (summary.fail > 0) return 'FAIL'; + if (summary.warning > 0) return 'WARNING'; + return 'PASS'; +} + +export function buildSetupReport( + checks: SetupCheckResult[], + message: string +): SetupReadinessReport { + const summary = summarizeSetupChecks(checks); + const status = resolveSetupStatus(summary); + return { + success: status !== 'FAIL', + time: now(), + status, + message, + summary, + checks + }; +} + +async function runSafeCheck( + meta: CheckMeta, + run: () => Promise +): Promise { + try { + return await run(); + } catch { + return buildSetupCheck( + meta, + 'FAIL', + 'The setup check failed unexpectedly.', + { error: 'Unexpected setup check failure' } + ); + } +} + +export function checkAuthSecret(): SetupCheckResult { + const meta: CheckMeta = { + id: 'ENV_AUTH_SECRET', + area: 'Environment', + name: 'Auth secret present', + requiredFor: ['first login'], + purpose: 'Ensure Auth.js can sign sessions.', + suggestedFix: 'Set AUTH_SECRET in .env.local or deployment secrets.' + }; + const value = process.env.AUTH_SECRET?.trim() ?? ''; + if (!value) return buildSetupCheck(meta, 'FAIL', 'AUTH_SECRET is not configured.'); + if (value.length < 32) { + return buildSetupCheck(meta, 'WARNING', 'AUTH_SECRET is present but looks short.'); + } + return buildSetupCheck(meta, 'PASS', 'AUTH_SECRET is configured.'); +} + +export function checkDatabaseUrl(): SetupCheckResult { + const meta: CheckMeta = { + id: 'ENV_DATABASE_URL', + area: 'Environment', + name: 'Database URL present', + requiredFor: ['all setup'], + purpose: 'Ensure app can connect to PostgreSQL.', + suggestedFix: 'Set DATABASE_URL.' + }; + const value = process.env.DATABASE_URL?.trim() ?? ''; + if (!value) return buildSetupCheck(meta, 'FAIL', 'DATABASE_URL is not configured.'); + if (/localhost|127\.0\.0\.1/.test(value) && process.env.NODE_ENV === 'production') { + return buildSetupCheck(meta, 'WARNING', 'DATABASE_URL points to a local host in production.'); + } + return buildSetupCheck(meta, 'PASS', 'DATABASE_URL is configured.'); +} + +export async function checkDatabaseConnectivity(): Promise { + const meta: CheckMeta = { + id: 'DB_CONNECTIVITY', + area: 'Database', + name: 'Database connectivity', + requiredFor: ['all setup'], + purpose: 'Prove DB is reachable.', + suggestedFix: 'Verify database host, network, and credentials.' + }; + return runSafeCheck(meta, async () => { + const startedAt = Date.now(); + await db.execute(sql`select 1`); + const latencyMs = Date.now() - startedAt; + if (latencyMs > 1000) { + return buildSetupCheck(meta, 'WARNING', 'Database is reachable but responding slowly.', { + latencyMs + }); + } + return buildSetupCheck(meta, 'PASS', 'Database connectivity check passed.', { latencyMs }); + }); +} + +export async function checkMigrationStatus(): Promise { + const meta: CheckMeta = { + id: 'DB_MIGRATIONS', + area: 'Database', + name: 'Migration status', + requiredFor: ['all setup'], + purpose: 'Prevent setup against stale schema.', + suggestedFix: 'Run npm run db:migrate.' + }; + return runSafeCheck(meta, async () => { + await Promise.all( + REQUIRED_TABLES.map(({ table }) => db.select({ value: count() }).from(table).limit(1)) + ); + return buildSetupCheck(meta, 'PASS', 'Required setup tables are queryable.', { + checkedTables: REQUIRED_TABLES.map((table) => table.name) + }); + }); +} + +export async function checkStorageProvider(): Promise { + const meta: CheckMeta = { + id: 'STORAGE_PROVIDER', + area: 'Storage', + name: 'Storage provider smoke test', + requiredFor: ['document library/PDF'], + purpose: 'Prove seed/document storage can write.', + suggestedFix: 'Fix local directory or S3 credentials.' + }; + return runSafeCheck(meta, async () => { + const provider = getStorageProvider(); + const key = `setup/readiness/${crypto.randomUUID()}.txt`; + await provider.putObject({ + key, + body: Buffer.from('setup-readiness-smoke-test'), + contentType: 'text/plain', + fileName: 'readiness.txt' + }); + const exists = await provider.objectExists({ key }); + try { + await provider.deleteObject({ key }); + } catch { + return buildSetupCheck( + meta, + 'WARNING', + 'Storage write/read succeeded, but cleanup failed.', + { key, exists } + ); + } + if (!exists) { + return buildSetupCheck(meta, 'FAIL', 'Storage object was not readable after write.', { key }); + } + return buildSetupCheck(meta, 'PASS', 'Storage write/read/delete smoke test passed.', { key }); + }); +} + +export async function checkUploadDirectory(): Promise { + const meta: CheckMeta = { + id: 'UPLOAD_DIRECTORY', + area: 'Storage', + name: 'Upload directory readiness', + requiredFor: ['local storage'], + purpose: 'Prove local provider path is writable.', + suggestedFix: 'Create the upload directory or fix permissions.' + }; + return runSafeCheck(meta, async () => { + const provider = process.env.STORAGE_PROVIDER?.trim() || 'local'; + if (provider !== 'local') { + return buildSetupCheck( + meta, + 'WARNING', + 'Upload directory check is only applicable to the local storage provider.', + { provider } + ); + } + const root = path.resolve(process.cwd(), process.env.LOCAL_STORAGE_ROOT?.trim() || 'storage'); + const directory = path.join(root, 'setup-readiness'); + const filePath = path.join(directory, `${crypto.randomUUID()}.txt`); + await mkdir(directory, { recursive: true }); + await writeFile(filePath, 'setup-upload-directory-check'); + await rm(filePath, { force: true }); + return buildSetupCheck(meta, 'PASS', 'Local upload directory is writable.', { directory }); + }); +} + +export async function checkPdfTemplateAsset( + organizationId?: string | null +): Promise { + const meta: CheckMeta = { + id: 'PDF_TEMPLATE_ASSET', + area: 'PDF', + name: 'PDF template assets exist', + requiredFor: ['quotation'], + purpose: 'Ensure quotation PDF can render.', + suggestedFix: 'Run foundation/PDF template seed.' + }; + return runSafeCheck(meta, async () => { + if (!organizationId) { + return buildSetupCheck( + meta, + 'WARNING', + 'No organization context was available for PDF template verification.' + ); + } + const templates = await db + .select({ id: crmDocumentTemplates.id }) + .from(crmDocumentTemplates) + .where( + and( + eq(crmDocumentTemplates.organizationId, organizationId), + eq(crmDocumentTemplates.documentType, 'quotation'), + eq(crmDocumentTemplates.fileType, 'pdfme'), + eq(crmDocumentTemplates.isActive, true), + isNull(crmDocumentTemplates.deletedAt) + ) + ); + if (!templates.length) { + return buildSetupCheck(meta, 'FAIL', 'No active quotation PDF template exists.'); + } + const templateIds = templates.map((template) => template.id); + const versions = await db + .select({ id: crmDocumentTemplateVersions.id }) + .from(crmDocumentTemplateVersions) + .where( + and( + eq(crmDocumentTemplateVersions.organizationId, organizationId), + inArray(crmDocumentTemplateVersions.templateId, templateIds), + eq(crmDocumentTemplateVersions.isActive, true), + isNull(crmDocumentTemplateVersions.deletedAt) + ) + ); + if (!versions.length) { + return buildSetupCheck(meta, 'FAIL', 'No active quotation PDF template version exists.', { + templateCount: templates.length + }); + } + return buildSetupCheck(meta, 'PASS', 'Active quotation PDF template and version exist.', { + templateCount: templates.length, + activeVersionCount: versions.length + }); + }); +} + +export async function checkPdfMappingCoverage( + organizationId?: string | null +): Promise { + const meta: CheckMeta = { + id: 'PDF_MAPPING_COVERAGE', + area: 'PDF', + name: 'PDF mappings exist', + requiredFor: ['quotation'], + purpose: 'Ensure placeholders can resolve.', + suggestedFix: 'Run PDF mapping seed/audit.' + }; + return runSafeCheck(meta, async () => { + if (!organizationId) { + return buildSetupCheck( + meta, + 'WARNING', + 'No organization context was available for PDF mapping verification.' + ); + } + const versions = await db + .select({ id: crmDocumentTemplateVersions.id }) + .from(crmDocumentTemplateVersions) + .innerJoin( + crmDocumentTemplates, + eq(crmDocumentTemplates.id, crmDocumentTemplateVersions.templateId) + ) + .where( + and( + eq(crmDocumentTemplateVersions.organizationId, organizationId), + eq(crmDocumentTemplateVersions.isActive, true), + isNull(crmDocumentTemplateVersions.deletedAt), + eq(crmDocumentTemplates.documentType, 'quotation'), + eq(crmDocumentTemplates.fileType, 'pdfme'), + isNull(crmDocumentTemplates.deletedAt) + ) + ); + const versionIds = versions.map((row) => row.id); + if (!versionIds.length) { + return buildSetupCheck(meta, 'FAIL', 'No active quotation PDF template version exists.'); + } + const [mappingCount] = await db + .select({ value: count() }) + .from(crmDocumentTemplateMappings) + .where( + and( + eq(crmDocumentTemplateMappings.organizationId, organizationId), + inArray(crmDocumentTemplateMappings.templateVersionId, versionIds), + isNull(crmDocumentTemplateMappings.deletedAt) + ) + ); + const total = Number(mappingCount?.value ?? 0); + if (total === 0) return buildSetupCheck(meta, 'FAIL', 'No PDF template mappings exist.'); + if (total < 10) { + return buildSetupCheck(meta, 'WARNING', 'PDF mappings exist but coverage looks low.', { + mappingCount: total + }); + } + return buildSetupCheck(meta, 'PASS', 'PDF mappings exist for the active quotation template.', { + mappingCount: total + }); + }); +} + +export async function checkEmailConfig(): Promise { + const meta: CheckMeta = { + id: 'EMAIL_CONFIG', + area: 'Email', + name: 'Email configuration', + requiredFor: ['optional notifications'], + purpose: 'Confirm optional email readiness.', + suggestedFix: 'Fix SMTP provider settings.' + }; + return runSafeCheck(meta, async () => { + const hasSmtpConfig = Boolean(process.env.SMTP_HOST?.trim() || process.env.SMTP_FROM?.trim()); + if (!hasSmtpConfig) { + return buildSetupCheck( + meta, + 'WARNING', + 'SMTP email is not configured; in-app notifications can still work.' + ); + } + const provider = new EmailNotificationProvider(); + await provider.validate(); + return buildSetupCheck(meta, 'PASS', 'SMTP email configuration is valid.'); + }); +} + +export async function getSetupReadinessReport(input: { + organizationId?: string | null; +} = {}): Promise { + const checks = await Promise.all([ + Promise.resolve(checkAuthSecret()), + Promise.resolve(checkDatabaseUrl()), + checkDatabaseConnectivity(), + checkMigrationStatus(), + checkStorageProvider(), + checkUploadDirectory(), + checkPdfTemplateAsset(input.organizationId), + checkPdfMappingCoverage(input.organizationId), + checkEmailConfig() + ]); + + return buildSetupReport(checks, 'Setup readiness checks completed.'); +} diff --git a/src/features/setup/server/verification.service.ts b/src/features/setup/server/verification.service.ts new file mode 100644 index 0000000..ad7955a --- /dev/null +++ b/src/features/setup/server/verification.service.ts @@ -0,0 +1,435 @@ +import 'server-only'; + +import { and, count, eq, isNull } from 'drizzle-orm'; +import { + appNotificationTemplates, + crmApprovalMatrices, + crmApprovalWorkflows, + crmReportDefinitions, + crmRoleProfiles, + crmUserRoleAssignments, + documentSequences, + memberships, + msOptions, + organizations, + users +} from '@/db/schema'; +import { db } from '@/lib/db'; +import type { SetupCheckResult, SetupVerificationReport } from '../api/types'; +import { + buildSetupCheck, + buildSetupReport, + checkEmailConfig, + checkPdfMappingCoverage, + checkPdfTemplateAsset +} from './readiness.service'; + +type VerificationInput = { + organizationId?: string | null; + administratorEmail?: string | null; +}; + +async function resolveOrganizationId(input: VerificationInput): Promise { + if (input.organizationId) { + const [organization] = await db + .select({ id: organizations.id }) + .from(organizations) + .where(eq(organizations.id, input.organizationId)) + .limit(1); + return organization?.id ?? null; + } + return null; +} + +async function resolveAdminUser(input: VerificationInput, organizationId: string | null) { + if (input.administratorEmail) { + const [user] = await db + .select() + .from(users) + .where(eq(users.email, input.administratorEmail.trim().toLowerCase())) + .limit(1); + return user ?? null; + } + if (organizationId) { + const [adminMembership] = await db + .select({ user: users }) + .from(memberships) + .innerJoin(users, eq(users.id, memberships.userId)) + .where(and(eq(memberships.organizationId, organizationId), eq(memberships.role, 'admin'))) + .limit(1); + return adminMembership?.user ?? null; + } + const email = process.env.SUPER_ADMIN_EMAIL?.trim().toLowerCase(); + if (!email) return null; + const [user] = await db.select().from(users).where(eq(users.email, email)).limit(1); + return user ?? null; +} + +async function countActiveOption(organizationId: string, category: string) { + const [row] = await db + .select({ value: count() }) + .from(msOptions) + .where( + and( + eq(msOptions.organizationId, organizationId), + eq(msOptions.category, category), + eq(msOptions.isActive, true), + isNull(msOptions.deletedAt) + ) + ); + return Number(row?.value ?? 0); +} + +async function countActiveSequences(organizationId: string, documentType: string) { + const [row] = await db + .select({ value: count() }) + .from(documentSequences) + .where( + and( + eq(documentSequences.organizationId, organizationId), + eq(documentSequences.documentType, documentType), + eq(documentSequences.isActive, true) + ) + ); + return Number(row?.value ?? 0); +} + +export async function getSetupVerificationReport( + input: VerificationInput +): Promise { + const organizationId = await resolveOrganizationId(input); + const adminUser = await resolveAdminUser(input, organizationId); + const checks: SetupCheckResult[] = []; + + checks.push( + buildSetupCheck( + { + id: 'ADMIN_USER', + area: 'Auth', + name: 'First administrator exists', + requiredFor: ['first login'], + purpose: 'Ensure first login actor exists.', + suggestedFix: 'Create administrator step.' + }, + adminUser ? 'PASS' : 'FAIL', + adminUser ? 'Administrator user exists.' : 'Administrator user was not found.', + adminUser ? { email: adminUser.email } : undefined + ) + ); + + let adminMembershipRole: string | null = null; + if (organizationId && adminUser) { + const [membership] = await db + .select() + .from(memberships) + .where(and(eq(memberships.organizationId, organizationId), eq(memberships.userId, adminUser.id))) + .limit(1); + adminMembershipRole = membership?.role ?? null; + } + checks.push( + buildSetupCheck( + { + id: 'ADMIN_MEMBERSHIP', + area: 'Auth', + name: 'Administrator membership exists', + requiredFor: ['first login'], + purpose: 'Ensure admin can enter organization.', + suggestedFix: 'Create membership.' + }, + adminMembershipRole === 'admin' ? 'PASS' : adminMembershipRole ? 'WARNING' : 'FAIL', + adminMembershipRole === 'admin' + ? 'Administrator membership exists with admin role.' + : adminMembershipRole + ? 'Administrator membership exists but is not an admin role.' + : 'Administrator membership was not found.', + adminMembershipRole ? { role: adminMembershipRole } : undefined + ) + ); + + let crmAssignmentCount = 0; + let legacyCrmAdmin = false; + if (organizationId && adminUser) { + const [assignmentRow] = await db + .select({ value: count() }) + .from(crmUserRoleAssignments) + .innerJoin(crmRoleProfiles, eq(crmRoleProfiles.id, crmUserRoleAssignments.roleProfileId)) + .where( + and( + eq(crmUserRoleAssignments.organizationId, organizationId), + eq(crmUserRoleAssignments.userId, adminUser.id), + eq(crmUserRoleAssignments.isActive, true), + isNull(crmUserRoleAssignments.deletedAt), + isNull(crmRoleProfiles.deletedAt), + eq(crmRoleProfiles.code, 'crm_admin') + ) + ); + crmAssignmentCount = Number(assignmentRow?.value ?? 0); + const [membership] = await db + .select({ businessRole: memberships.businessRole }) + .from(memberships) + .where(and(eq(memberships.organizationId, organizationId), eq(memberships.userId, adminUser.id))) + .limit(1); + legacyCrmAdmin = membership?.businessRole === 'crm_admin'; + } + checks.push( + buildSetupCheck( + { + id: 'ADMIN_CRM_ASSIGNMENT', + area: 'Authorization', + name: 'Administrator CRM assignment resolves', + requiredFor: ['CRM'], + purpose: 'Ensure CRM admin permissions resolve.', + suggestedFix: 'Seed role profiles and assignment.' + }, + crmAssignmentCount > 0 ? 'PASS' : legacyCrmAdmin ? 'WARNING' : 'FAIL', + crmAssignmentCount > 0 + ? 'CRM administrator role assignment exists.' + : legacyCrmAdmin + ? 'Only compatibility business role crm_admin was found.' + : 'CRM administrator role assignment was not found.', + { assignmentCount: crmAssignmentCount } + ) + ); + + const orgExists = Boolean(organizationId); + const adminActiveOrgMatches = Boolean(adminUser && organizationId && adminUser.activeOrganizationId === organizationId); + checks.push( + buildSetupCheck( + { + id: 'ORG_ACTIVE', + area: 'Organization', + name: 'Organization active/resolvable', + requiredFor: ['all setup'], + purpose: 'Ensure active org context works.', + suggestedFix: 'Update users.active_organization_id.' + }, + orgExists && adminActiveOrgMatches ? 'PASS' : orgExists ? 'WARNING' : 'FAIL', + orgExists && adminActiveOrgMatches + ? 'Organization exists and is active for the administrator.' + : orgExists + ? 'Organization exists, but it is not the administrator active organization.' + : 'Organization was not found.', + { organizationId, adminActiveOrganizationId: adminUser?.activeOrganizationId ?? null } + ) + ); + + if (!organizationId) { + const report = buildSetupReport(checks, 'Setup verification checks completed.'); + return { ...report, organizationId }; + } + + const branchCount = await countActiveOption(organizationId, 'crm_branch'); + checks.push( + buildSetupCheck( + { + id: 'BRANCH_OPTION', + area: 'Scope', + name: 'Branch option exists', + requiredFor: ['CRM/quotation'], + purpose: 'Ensure branch scope can resolve.', + suggestedFix: 'Configure branch step.' + }, + branchCount > 0 ? 'PASS' : 'FAIL', + branchCount > 0 ? 'Active branch options exist.' : 'No active branch option exists.', + { count: branchCount } + ) + ); + + const productTypeCount = await countActiveOption(organizationId, 'crm_product_type'); + checks.push( + buildSetupCheck( + { + id: 'PRODUCT_TYPE_OPTION', + area: 'Scope', + name: 'Product type option exists', + requiredFor: ['CRM/quotation'], + purpose: 'Ensure product scope can resolve.', + suggestedFix: 'Configure product types.' + }, + productTypeCount > 0 ? 'PASS' : 'FAIL', + productTypeCount > 0 ? 'Active product type options exist.' : 'No active product type option exists.', + { count: productTypeCount } + ) + ); + + const sequenceChecks = [ + ['SEQUENCE_CUSTOMER', 'Customer sequence preview', 'customer'], + ['SEQUENCE_LEAD', 'Lead sequence preview', 'crm_lead'], + ['SEQUENCE_OPPORTUNITY', 'Opportunity sequence preview', 'crm_opportunity'], + ['SEQUENCE_QUOTATION', 'Quotation sequence preview', 'quotation'] + ] as const; + for (const [id, name, documentType] of sequenceChecks) { + const sequenceCount = await countActiveSequences(organizationId, documentType); + checks.push( + buildSetupCheck( + { + id, + area: 'Document sequence', + name, + requiredFor: documentType === 'quotation' ? ['quotation'] : ['CRM'], + purpose: `Ensure ${documentType} codes can generate.`, + suggestedFix: 'Configure document sequence.' + }, + sequenceCount > 0 ? 'PASS' : 'FAIL', + sequenceCount > 0 + ? `Active ${documentType} sequence exists.` + : `No active ${documentType} sequence exists.`, + { documentType, count: sequenceCount } + ) + ); + } + + const [workflowRow] = await db + .select({ value: count() }) + .from(crmApprovalWorkflows) + .where( + and( + eq(crmApprovalWorkflows.organizationId, organizationId), + eq(crmApprovalWorkflows.entityType, 'quotation'), + eq(crmApprovalWorkflows.isActive, true), + isNull(crmApprovalWorkflows.deletedAt) + ) + ); + const workflowCount = Number(workflowRow?.value ?? 0); + checks.push( + buildSetupCheck( + { + id: 'APPROVAL_WORKFLOW', + area: 'Approval', + name: 'Quotation workflow resolves', + requiredFor: ['quotation'], + purpose: 'Ensure quotation approval can start.', + suggestedFix: 'Configure approval workflow.' + }, + workflowCount === 1 ? 'PASS' : workflowCount > 1 ? 'WARNING' : 'FAIL', + workflowCount === 1 + ? 'One active quotation approval workflow exists.' + : workflowCount > 1 + ? 'Multiple active quotation approval workflows exist.' + : 'No active quotation approval workflow exists.', + { count: workflowCount } + ) + ); + + const [matrixRow] = await db + .select({ value: count() }) + .from(crmApprovalMatrices) + .where( + and( + eq(crmApprovalMatrices.organizationId, organizationId), + eq(crmApprovalMatrices.entityType, 'quotation'), + eq(crmApprovalMatrices.isActive, true), + isNull(crmApprovalMatrices.deletedAt) + ) + ); + const matrixCount = Number(matrixRow?.value ?? 0); + checks.push( + buildSetupCheck( + { + id: 'APPROVAL_MATRIX', + area: 'Approval', + name: 'Approval matrix resolves', + requiredFor: ['quotation'], + purpose: 'Ensure quotation can select workflow by scope/amount.', + suggestedFix: 'Configure approval matrix.' + }, + matrixCount > 0 ? 'PASS' : 'FAIL', + matrixCount > 0 ? 'Active quotation approval matrix rows exist.' : 'No active quotation approval matrix exists.', + { count: matrixCount } + ) + ); + + checks.push(await checkPdfTemplateAsset(organizationId)); + checks.push(await checkPdfMappingCoverage(organizationId)); + + const [notificationRow] = await db + .select({ value: count() }) + .from(appNotificationTemplates) + .where( + and( + eq(appNotificationTemplates.organizationId, organizationId), + eq(appNotificationTemplates.channel, 'in_app'), + eq(appNotificationTemplates.isActive, true), + isNull(appNotificationTemplates.deletedAt) + ) + ); + const notificationCount = Number(notificationRow?.value ?? 0); + checks.push( + buildSetupCheck( + { + id: 'NOTIFICATION_TEMPLATES', + area: 'Notifications', + name: 'Approval notification templates exist', + requiredFor: ['CRM/quotation'], + purpose: 'Ensure in-app approval notifications can render.', + suggestedFix: 'Run foundation seed.' + }, + notificationCount > 0 ? 'PASS' : 'FAIL', + notificationCount > 0 + ? 'Active in-app notification templates exist.' + : 'No active in-app notification template exists.', + { count: notificationCount } + ) + ); + + const [reportRow] = await db + .select({ value: count() }) + .from(crmReportDefinitions) + .where( + and( + eq(crmReportDefinitions.organizationId, organizationId), + eq(crmReportDefinitions.isActive, true) + ) + ); + const reportCount = Number(reportRow?.value ?? 0); + checks.push( + buildSetupCheck( + { + id: 'REPORT_DEFINITIONS', + area: 'Reports', + name: 'Report definitions exist', + requiredFor: ['CRM reports'], + purpose: 'Ensure report catalog works.', + suggestedFix: 'Run report foundation seed.' + }, + reportCount > 0 ? 'PASS' : 'FAIL', + reportCount > 0 ? 'Active report definitions exist.' : 'No active report definitions exist.', + { count: reportCount } + ) + ); + + checks.push(await checkEmailConfig()); + + checks.push( + buildSetupCheck( + { + id: 'CSV_PREVIEW', + area: 'Import', + name: 'CSV preview integrity', + requiredFor: ['CSV import'], + purpose: 'Ensure uploaded files were validated before commit.', + suggestedFix: 'Re-run CSV preview.' + }, + 'WARNING', + 'CSV preview tracking is planned for a later setup import slice.' + ) + ); + + checks.push( + buildSetupCheck( + { + id: 'SETUP_MANIFEST', + area: 'Setup', + name: 'Seed manifest recorded', + requiredFor: ['setup completion'], + purpose: 'Ensure lifecycle tracking exists.', + suggestedFix: 'Implement D.7.8.' + }, + 'WARNING', + 'Seed manifest persistence is planned for D.7.8.' + ) + ); + + const report = buildSetupReport(checks, 'Setup verification checks completed.'); + return { ...report, organizationId }; +}