This commit is contained in:
phaichayon
2026-07-01 00:09:05 +07:00
parent 8feaee61cb
commit 53dfd20913
44 changed files with 2829 additions and 25931 deletions

2
.gitignore vendored
View File

@@ -137,4 +137,4 @@ dist
.yarn/install-state.gz .yarn/install-state.gz
.pnp.* .pnp.*
.storage storage/organizations/

View File

@@ -1,13 +1,13 @@
{ {
"audit": "payload-parity", "audit": "payload-parity",
"overallStatus": "PASS", "overallStatus": "PASS",
"quotationCode": "CRA2606-1121", "quotationCode": "CRA2606-1108",
"previewHash": "f50ad995d51a33c5ae169f788591f9efe6e70b7a4f96d6861b549b0c6f71cd2e", "previewHash": "8f93479501b66dfd397c742447b010eaf427697429bfcfd523cd127d06106f26",
"generationHash": "f50ad995d51a33c5ae169f788591f9efe6e70b7a4f96d6861b549b0c6f71cd2e", "generationHash": "8f93479501b66dfd397c742447b010eaf427697429bfcfd523cd127d06106f26",
"snapshotHash": "f50ad995d51a33c5ae169f788591f9efe6e70b7a4f96d6861b549b0c6f71cd2e", "snapshotHash": "8f93479501b66dfd397c742447b010eaf427697429bfcfd523cd127d06106f26",
"mismatches": [], "mismatches": [],
"comparableFieldCount": 25, "comparableFieldCount": 31,
"topicInputKeyCount": 0, "topicInputKeyCount": 6,
"signatureParity": true, "signatureParity": true,
"priceTableParity": true, "priceTableParity": true,
"priceTableRowCount": 1, "priceTableRowCount": 1,

View File

@@ -3,7 +3,7 @@
"overallStatus": "PASS", "overallStatus": "PASS",
"results": [ "results": [
{ {
"templateName": "ALLA Demo Organization Quotation Standard", "templateName": "ALLA Demo Quotation Standard",
"organizationId": "daf796fc-4588-5240-a27e-9bfef0739658", "organizationId": "daf796fc-4588-5240-a27e-9bfef0739658",
"version": "1.0", "version": "1.0",
"duplicateFieldNames": [], "duplicateFieldNames": [],

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +1,13 @@
{ {
"audit": "approved-snapshot-parity", "audit": "approved-snapshot-parity",
"overallStatus": "PASS", "overallStatus": "PASS",
"quotationCode": "CRA2606-1121", "quotationCode": "CRA2606-1108",
"approvedSnapshotAvailable": true, "approvedSnapshotAvailable": true,
"approvedTemplateVersionId": "adf930b4-3d63-4ee1-bec9-3e82854ca406", "approvedTemplateVersionId": "e7e89bf2-f107-4f90-b03d-4a7533883d84",
"computedTemplateVersionId": "adf930b4-3d63-4ee1-bec9-3e82854ca406", "computedTemplateVersionId": "e7e89bf2-f107-4f90-b03d-4a7533883d84",
"inconsistentTemplateKeys": [], "inconsistentTemplateKeys": [],
"computedSignatureHash": "ef7a8fc3429f1c5b5a74ac0452d3de809558842f5431d0d5b02122980a286117", "computedSignatureHash": "922ab2a823f98b032b17da281c86c3a2780d27cca73a60a7b983d14ad039b229",
"storedSignatureHash": "ef7a8fc3429f1c5b5a74ac0452d3de809558842f5431d0d5b02122980a286117", "storedSignatureHash": "922ab2a823f98b032b17da281c86c3a2780d27cca73a60a7b983d14ad039b229",
"approvedArtifactReference": null, "approvedArtifactReference": null,
"refreshedFixtureSnapshot": true, "refreshedFixtureSnapshot": true,
"usedAuditFixture": false "usedAuditFixture": false

View File

@@ -1,7 +1,7 @@
{ {
"generatedAt": "2026-06-30T13:14:19.935Z", "generatedAt": "2026-06-30T16:18:58.393Z",
"overallStatus": "FAIL", "overallStatus": "PASS",
"quotationCode": "CRA2606-1121", "quotationCode": "CRA2606-1108",
"artifacts": { "artifacts": {
"templateVersionReport": "template-version-report.json", "templateVersionReport": "template-version-report.json",
"placeholderIntegrityReport": "placeholder-integrity-report.json", "placeholderIntegrityReport": "placeholder-integrity-report.json",
@@ -11,11 +11,11 @@
"signatureAuditReport": "signature-audit-report.json" "signatureAuditReport": "signature-audit-report.json"
}, },
"statuses": { "statuses": {
"templateVersion": "FAIL", "templateVersion": "PASS",
"placeholderIntegrity": "PASS", "placeholderIntegrity": "PASS",
"runtimePayload": "PASS", "runtimePayload": "PASS",
"payloadParity": "PASS", "payloadParity": "PASS",
"topicRuntime": "FAIL", "topicRuntime": "PASS",
"approvedSnapshotParity": "PASS", "approvedSnapshotParity": "PASS",
"staticLabels": "PASS" "staticLabels": "PASS"
}, },

View File

@@ -1,16 +1,16 @@
# PDF Integrity Summary # PDF Integrity Summary
- Generated At: 2026-06-30T13:14:19.935Z - Generated At: 2026-06-30T16:18:58.393Z
- Overall Status: FAIL - Overall Status: PASS
- Quotation Code: CRA2606-1121 - Quotation Code: CRA2606-1108
## Statuses ## Statuses
- Template Version Integrity: FAIL - Template Version Integrity: PASS
- Placeholder Integrity: PASS - Placeholder Integrity: PASS
- Runtime Payload: PASS - Runtime Payload: PASS
- Payload Parity: PASS - Payload Parity: PASS
- Topic Runtime: FAIL - Topic Runtime: PASS
- Approved Snapshot Parity: PASS - Approved Snapshot Parity: PASS
- Static Labels: PASS - Static Labels: PASS
- Price Table Present: Yes - Price Table Present: Yes

View File

@@ -1,21 +1,9 @@
{ {
"audit": "template-version-integrity", "audit": "template-version-integrity",
"overallStatus": "FAIL", "overallStatus": "PASS",
"activeTemplateCount": 1, "activeTemplateCount": 1,
"quotationCode": "CRA2606-1121", "quotationCode": "CRA2606-1108",
"approvedTemplateVersionId": null, "approvedTemplateVersionId": null,
"approvedTemplateVersionIsActive": false, "approvedTemplateVersionIsActive": false,
"issues": [ "issues": []
{
"kind": "ACTIVE_VERSION_SCHEMA_DRIFT",
"message": "Active template version does not match source JSON for ALLA Demo Organization Quotation Standard.",
"context": {
"templateId": "83928eee-0b3b-4dcb-9756-726dc2337e6c",
"organizationName": "ALLA Demo Organization",
"activeVersionId": "adf930b4-3d63-4ee1-bec9-3e82854ca406",
"activeVersion": "1.0",
"sourceFile": "src/pdfme_template/ALLA_template_pdfme_fainal3.json"
}
}
]
} }

View File

@@ -1,10 +1,18 @@
{ {
"audit": "topic-runtime", "audit": "topic-runtime",
"overallStatus": "FAIL", "overallStatus": "PASS",
"quotationCode": "CRA2606-1121", "quotationCode": "CRA2606-1108",
"topicCount": 0, "topicCount": 3,
"topicInputKeys": [], "topicInputKeys": [
"itemTopicInputKeys": [], "topic_1_0",
"topic_1_1",
"topic_1_2"
],
"itemTopicInputKeys": [
"item_topic_1_0",
"item_topic_1_1",
"item_topic_1_2"
],
"duplicateKeys": [], "duplicateKeys": [],
"emptySections": [] "emptySections": []
} }

View File

@@ -1,12 +1,12 @@
{ {
"generatedAt": "2026-06-30T13:14:09.584Z", "generatedAt": "2026-06-30T16:18:57.428Z",
"overallStatus": "PASS", "overallStatus": "PASS",
"quotationCode": "CRA2606-1121", "quotationCode": "CRA2606-1108",
"expectedFixtureCode": "QT-H5-AUDIT", "expectedFixtureCode": "QT-H5-AUDIT",
"template": { "template": {
"templateName": "ALLA Demo Organization Quotation Standard", "templateName": "ALLA Demo Quotation Standard",
"version": "1.0", "version": "1.0",
"versionId": "adf930b4-3d63-4ee1-bec9-3e82854ca406", "versionId": "e7e89bf2-f107-4f90-b03d-4a7533883d84",
"fieldCount": 44, "fieldCount": 44,
"previousVersion": null, "previousVersion": null,
"deletedFields": [], "deletedFields": [],
@@ -238,11 +238,11 @@
] ]
}, },
"metrics": { "metrics": {
"topicCount": 0, "topicCount": 3,
"itemCount": 0, "itemCount": 4,
"partyCount": 2, "partyCount": 1,
"dynamicTopicFieldCount": 0, "dynamicTopicFieldCount": 3,
"dynamicTopicItemFieldCount": 0, "dynamicTopicItemFieldCount": 3,
"mappingCount": 23 "mappingCount": 23
}, },
"mappingCoverage": { "mappingCoverage": {
@@ -256,8 +256,8 @@
"itemTableColumns": [] "itemTableColumns": []
}, },
"runtimePayload": { "runtimePayload": {
"templateInputKeys": 25, "templateInputKeys": 31,
"topicInputKeys": 0, "topicInputKeys": 6,
"priceTableRows": 1 "priceTableRows": 1
}, },
"consistency": { "consistency": {

View File

@@ -1,16 +1,16 @@
# PDF Audit Report # PDF Audit Report
- Generated At: 2026-06-30T13:14:09.584Z - Generated At: 2026-06-30T16:18:57.428Z
- Overall Status: PASS - Overall Status: PASS
- Quotation Code: CRA2606-1121 - Quotation Code: CRA2606-1108
- Template: ALLA Demo Organization Quotation Standard v1.0 - Template: ALLA Demo Quotation Standard v1.0
- Previous Version: None - Previous Version: None
## Metrics ## Metrics
- Topic Count: 0 - Topic Count: 3
- Item Count: 0 - Item Count: 4
- Project Party Count: 2 - Project Party Count: 1
- Mapping Count: 23 - Mapping Count: 23
## Mapping Coverage ## Mapping Coverage

View File

@@ -1,3 +1,106 @@
CREATE TABLE "app_notification_deliveries" (
"id" text PRIMARY KEY NOT NULL,
"organization_id" text NOT NULL,
"notification_id" text NOT NULL,
"channel" text NOT NULL,
"recipient_address" text,
"status" text DEFAULT 'pending' NOT NULL,
"attempt_count" integer DEFAULT 0 NOT NULL,
"last_error" text,
"sent_at" timestamp with time zone,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "app_notification_dispatches" (
"id" text PRIMARY KEY NOT NULL,
"organization_id" text NOT NULL,
"event_id" text NOT NULL,
"template_id" text,
"entity_type" text NOT NULL,
"entity_id" text NOT NULL,
"channel" text NOT NULL,
"provider_key" text NOT NULL,
"status" text DEFAULT 'pending' NOT NULL,
"recipient_to" jsonb NOT NULL,
"recipient_cc" jsonb,
"recipient_bcc" jsonb,
"reply_to" text,
"subject" text NOT NULL,
"body_text" text NOT NULL,
"body_html" text,
"attachments" jsonb,
"recipient_user_id" text,
"recipient_address" text,
"requested_by" text NOT NULL,
"metadata" jsonb,
"last_error" text,
"retry_count" integer DEFAULT 0 NOT NULL,
"max_retry_count" integer DEFAULT 3 NOT NULL,
"last_attempt_at" timestamp with time zone,
"sent_at" timestamp with time zone,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "app_notification_events" (
"id" text PRIMARY KEY NOT NULL,
"organization_id" text NOT NULL,
"event_type" text NOT NULL,
"entity_type" text NOT NULL,
"entity_id" text NOT NULL,
"actor_user_id" text,
"payload" jsonb,
"dedupe_key" text,
"status" text DEFAULT 'pending' NOT NULL,
"last_error" text,
"published_at" timestamp with time zone DEFAULT now() NOT NULL,
"processed_at" timestamp with time zone,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "app_notification_templates" (
"id" text PRIMARY KEY NOT NULL,
"organization_id" text NOT NULL,
"event_type" text NOT NULL,
"code" text DEFAULT '' NOT NULL,
"name" text DEFAULT '' NOT NULL,
"channel" text NOT NULL,
"subject_template" text,
"title_template" text NOT NULL,
"body_template" text NOT NULL,
"body_format" text DEFAULT 'plain' NOT NULL,
"link_template" text,
"language" text DEFAULT 'th' NOT NULL,
"variables" jsonb,
"version" integer DEFAULT 1 NOT NULL,
"is_active" boolean DEFAULT true NOT NULL,
"metadata" jsonb,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
"deleted_at" timestamp with time zone,
"created_by" text NOT NULL,
"updated_by" text NOT NULL
);
--> statement-breakpoint
CREATE TABLE "app_notifications" (
"id" text PRIMARY KEY NOT NULL,
"organization_id" text NOT NULL,
"recipient_user_id" text NOT NULL,
"event_id" text NOT NULL,
"title" text NOT NULL,
"body" text NOT NULL,
"link_url" text,
"severity" text DEFAULT 'info' NOT NULL,
"status" text DEFAULT 'unread' NOT NULL,
"read_at" timestamp with time zone,
"archived_at" timestamp with time zone,
"metadata" jsonb,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "crm_approval_actions" ( CREATE TABLE "crm_approval_actions" (
"id" text PRIMARY KEY NOT NULL, "id" text PRIMARY KEY NOT NULL,
"organization_id" text NOT NULL, "organization_id" text NOT NULL,
@@ -12,6 +115,60 @@ CREATE TABLE "crm_approval_actions" (
"deleted_at" timestamp with time zone "deleted_at" timestamp with time zone
); );
--> statement-breakpoint --> statement-breakpoint
CREATE TABLE "crm_approval_escalation_policies" (
"id" text PRIMARY KEY NOT NULL,
"organization_id" text NOT NULL,
"workflow_id" text NOT NULL,
"step_number" integer NOT NULL,
"trigger_after_hours" integer NOT NULL,
"target_type" text NOT NULL,
"target_value" text,
"is_active" boolean DEFAULT true NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
"deleted_at" timestamp with time zone,
"created_by" text NOT NULL,
"updated_by" text NOT NULL
);
--> statement-breakpoint
CREATE TABLE "crm_approval_matrices" (
"id" text PRIMARY KEY NOT NULL,
"organization_id" text NOT NULL,
"entity_type" text NOT NULL,
"workflow_id" text NOT NULL,
"branch_id" text,
"product_type" text,
"min_amount" double precision,
"max_amount" double precision,
"currency" text,
"priority" integer DEFAULT 100 NOT NULL,
"is_default" boolean DEFAULT false NOT NULL,
"is_active" boolean DEFAULT true NOT NULL,
"description" text,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
"deleted_at" timestamp with time zone,
"created_by" text NOT NULL,
"updated_by" text NOT NULL
);
--> statement-breakpoint
CREATE TABLE "crm_approval_reminder_policies" (
"id" text PRIMARY KEY NOT NULL,
"organization_id" text NOT NULL,
"workflow_id" text NOT NULL,
"step_number" integer NOT NULL,
"sla_hours" integer DEFAULT 24 NOT NULL,
"reminder_offsets_hours" integer[] DEFAULT '{}' NOT NULL,
"calendar_mode" text DEFAULT 'calendar_days' NOT NULL,
"timeout_action" text DEFAULT 'none' NOT NULL,
"is_active" boolean DEFAULT true NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
"deleted_at" timestamp with time zone,
"created_by" text NOT NULL,
"updated_by" text NOT NULL
);
--> statement-breakpoint
CREATE TABLE "crm_approval_requests" ( CREATE TABLE "crm_approval_requests" (
"id" text PRIMARY KEY NOT NULL, "id" text PRIMARY KEY NOT NULL,
"organization_id" text NOT NULL, "organization_id" text NOT NULL,
@@ -19,6 +176,7 @@ CREATE TABLE "crm_approval_requests" (
"entity_type" text NOT NULL, "entity_type" text NOT NULL,
"entity_id" text NOT NULL, "entity_id" text NOT NULL,
"current_step" integer DEFAULT 1 NOT NULL, "current_step" integer DEFAULT 1 NOT NULL,
"current_step_started_at" timestamp with time zone DEFAULT now() NOT NULL,
"status" text NOT NULL, "status" text NOT NULL,
"requested_by" text NOT NULL, "requested_by" text NOT NULL,
"requested_at" timestamp with time zone DEFAULT now() NOT NULL, "requested_at" timestamp with time zone DEFAULT now() NOT NULL,
@@ -35,7 +193,11 @@ CREATE TABLE "crm_approval_steps" (
"step_number" integer NOT NULL, "step_number" integer NOT NULL,
"role_code" text NOT NULL, "role_code" text NOT NULL,
"role_name" text NOT NULL, "role_name" text NOT NULL,
"approval_mode" text DEFAULT 'sequential' NOT NULL,
"is_required" boolean DEFAULT true NOT NULL, "is_required" boolean DEFAULT true NOT NULL,
"sla_hours" integer DEFAULT 24 NOT NULL,
"calendar_mode" text DEFAULT 'calendar_days' NOT NULL,
"timeout_action" text DEFAULT 'none' NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL, "created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL, "updated_at" timestamp with time zone DEFAULT now() NOT NULL,
"deleted_at" timestamp with time zone "deleted_at" timestamp with time zone
@@ -47,7 +209,11 @@ CREATE TABLE "crm_approval_workflows" (
"code" text NOT NULL, "code" text NOT NULL,
"name" text NOT NULL, "name" text NOT NULL,
"entity_type" text NOT NULL, "entity_type" text NOT NULL,
"description" text,
"is_system" boolean DEFAULT false NOT NULL,
"is_active" boolean DEFAULT true NOT NULL, "is_active" boolean DEFAULT true NOT NULL,
"created_by" text NOT NULL,
"updated_by" text NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL, "created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL, "updated_at" timestamp with time zone DEFAULT now() NOT NULL,
"deleted_at" timestamp with time zone "deleted_at" timestamp with time zone
@@ -164,6 +330,49 @@ CREATE TABLE "crm_document_artifacts" (
"deleted_at" timestamp with time zone "deleted_at" timestamp with time zone
); );
--> statement-breakpoint --> statement-breakpoint
CREATE TABLE "crm_document_libraries" (
"id" text PRIMARY KEY NOT NULL,
"organization_id" text NOT NULL,
"code" text NOT NULL,
"name" text NOT NULL,
"document_type" text NOT NULL,
"description" text,
"brand" text NOT NULL,
"language" text NOT NULL,
"product_type" text NOT NULL,
"status" text DEFAULT 'active' NOT NULL,
"is_active" boolean DEFAULT true NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
"deleted_at" timestamp with time zone,
"created_by" text NOT NULL,
"updated_by" text NOT NULL
);
--> statement-breakpoint
CREATE TABLE "crm_document_library_versions" (
"id" text PRIMARY KEY NOT NULL,
"organization_id" text NOT NULL,
"library_id" text NOT NULL,
"version" text NOT NULL,
"file_name" text NOT NULL,
"storage_provider" text NOT NULL,
"storage_key" text NOT NULL,
"mime_type" text NOT NULL,
"file_size" integer NOT NULL,
"checksum" text NOT NULL,
"page_count" integer NOT NULL,
"status" text DEFAULT 'draft' NOT NULL,
"is_active" boolean DEFAULT false NOT NULL,
"published_at" timestamp with time zone,
"published_by" text,
"archived_at" timestamp with time zone,
"archived_by" text,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
"deleted_at" timestamp with time zone,
"created_by" text NOT NULL
);
--> statement-breakpoint
CREATE TABLE "crm_document_template_mappings" ( CREATE TABLE "crm_document_template_mappings" (
"id" text PRIMARY KEY NOT NULL, "id" text PRIMARY KEY NOT NULL,
"organization_id" text NOT NULL, "organization_id" text NOT NULL,
@@ -201,6 +410,7 @@ CREATE TABLE "crm_document_template_versions" (
"version" text NOT NULL, "version" text NOT NULL,
"file_path" text, "file_path" text,
"schema_json" jsonb NOT NULL, "schema_json" jsonb NOT NULL,
"metadata_json" jsonb,
"preview_image_url" text, "preview_image_url" text,
"is_active" boolean DEFAULT true NOT NULL, "is_active" boolean DEFAULT true NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL, "created_at" timestamp with time zone DEFAULT now() NOT NULL,
@@ -270,6 +480,7 @@ CREATE TABLE "crm_opportunities" (
"project_location" text, "project_location" text,
"product_type" text NOT NULL, "product_type" text NOT NULL,
"status" text NOT NULL, "status" text NOT NULL,
"outcome_status" text DEFAULT 'open' NOT NULL,
"priority" text NOT NULL, "priority" text NOT NULL,
"lead_channel" text, "lead_channel" text,
"estimated_value" double precision, "estimated_value" double precision,
@@ -281,6 +492,7 @@ CREATE TABLE "crm_opportunities" (
"is_hot_project" boolean DEFAULT false NOT NULL, "is_hot_project" boolean DEFAULT false NOT NULL,
"is_active" boolean DEFAULT true NOT NULL, "is_active" boolean DEFAULT true NOT NULL,
"pipeline_stage" text DEFAULT 'lead' NOT NULL, "pipeline_stage" text DEFAULT 'lead' NOT NULL,
"closed_at" timestamp with time zone,
"closed_won_at" timestamp with time zone, "closed_won_at" timestamp with time zone,
"closed_lost_at" timestamp with time zone, "closed_lost_at" timestamp with time zone,
"closed_by_user_id" text, "closed_by_user_id" text,
@@ -289,8 +501,11 @@ CREATE TABLE "crm_opportunities" (
"po_amount" double precision, "po_amount" double precision,
"po_currency" text, "po_currency" text,
"lost_reason" text, "lost_reason" text,
"lost_detail" text,
"lost_competitor" text, "lost_competitor" text,
"lost_remark" text, "lost_remark" text,
"cancel_reason" text,
"no_quotation_reason" text,
"assigned_to_user_id" text, "assigned_to_user_id" text,
"assigned_at" timestamp with time zone, "assigned_at" timestamp with time zone,
"assigned_by" text, "assigned_by" text,
@@ -549,11 +764,14 @@ CREATE TABLE "document_sequences" (
"id" text PRIMARY KEY NOT NULL, "id" text PRIMARY KEY NOT NULL,
"organization_id" text NOT NULL, "organization_id" text NOT NULL,
"branch_id" text DEFAULT '' NOT NULL, "branch_id" text DEFAULT '' NOT NULL,
"product_type" text DEFAULT 'all' NOT NULL,
"document_type" text NOT NULL, "document_type" text NOT NULL,
"prefix" text NOT NULL, "prefix" text NOT NULL,
"period" text NOT NULL, "period" text NOT NULL,
"current_number" integer DEFAULT 0 NOT NULL, "current_number" integer DEFAULT 0 NOT NULL,
"padding_length" integer DEFAULT 3 NOT NULL, "padding_length" integer DEFAULT 3 NOT NULL,
"format" text DEFAULT '{prefix}{period}-{running}' NOT NULL,
"reset_policy" text DEFAULT 'period' NOT NULL,
"is_active" boolean DEFAULT true NOT NULL, "is_active" boolean DEFAULT true NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL, "created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL "updated_at" timestamp with time zone DEFAULT now() NOT NULL
@@ -637,10 +855,17 @@ CREATE TABLE "users" (
"updated_at" timestamp with time zone DEFAULT now() NOT NULL "updated_at" timestamp with time zone DEFAULT now() NOT NULL
); );
--> statement-breakpoint --> statement-breakpoint
CREATE UNIQUE INDEX "app_notification_dispatches_org_event_channel_recipient_idx" ON "app_notification_dispatches" USING btree ("organization_id","event_id","channel","recipient_address");--> statement-breakpoint
CREATE UNIQUE INDEX "app_notification_events_org_dedupe_idx" ON "app_notification_events" USING btree ("organization_id","dedupe_key");--> statement-breakpoint
CREATE UNIQUE INDEX "app_notification_templates_org_event_channel_idx" ON "app_notification_templates" USING btree ("organization_id","event_type","channel");--> statement-breakpoint
CREATE UNIQUE INDEX "app_notifications_recipient_event_idx" ON "app_notifications" USING btree ("recipient_user_id","event_id");--> statement-breakpoint
CREATE UNIQUE INDEX "crm_approval_reminder_policies_workflow_step_idx" ON "crm_approval_reminder_policies" USING btree ("workflow_id","step_number");--> statement-breakpoint
CREATE UNIQUE INDEX "crm_approval_steps_workflow_step_idx" ON "crm_approval_steps" USING btree ("workflow_id","step_number");--> statement-breakpoint CREATE UNIQUE INDEX "crm_approval_steps_workflow_step_idx" ON "crm_approval_steps" USING btree ("workflow_id","step_number");--> statement-breakpoint
CREATE UNIQUE INDEX "crm_approval_workflows_org_code_idx" ON "crm_approval_workflows" USING btree ("organization_id","code");--> statement-breakpoint CREATE UNIQUE INDEX "crm_approval_workflows_org_code_idx" ON "crm_approval_workflows" USING btree ("organization_id","code");--> statement-breakpoint
CREATE UNIQUE INDEX "crm_contact_shares_org_contact_shared_user_idx" ON "crm_contact_shares" USING btree ("organization_id","contact_id","shared_to_user_id");--> statement-breakpoint CREATE UNIQUE INDEX "crm_contact_shares_org_contact_shared_user_idx" ON "crm_contact_shares" USING btree ("organization_id","contact_id","shared_to_user_id");--> statement-breakpoint
CREATE UNIQUE INDEX "crm_customers_org_code_idx" ON "crm_customers" USING btree ("organization_id","code");--> statement-breakpoint CREATE UNIQUE INDEX "crm_customers_org_code_idx" ON "crm_customers" USING btree ("organization_id","code");--> statement-breakpoint
CREATE UNIQUE INDEX "crm_document_libraries_org_code_idx" ON "crm_document_libraries" USING btree ("organization_id","code");--> statement-breakpoint
CREATE UNIQUE INDEX "crm_document_library_versions_library_version_idx" ON "crm_document_library_versions" USING btree ("library_id","version");--> statement-breakpoint
CREATE UNIQUE INDEX "crm_document_template_mappings_version_placeholder_idx" ON "crm_document_template_mappings" USING btree ("template_version_id","placeholder_key");--> statement-breakpoint CREATE UNIQUE INDEX "crm_document_template_mappings_version_placeholder_idx" ON "crm_document_template_mappings" USING btree ("template_version_id","placeholder_key");--> statement-breakpoint
CREATE UNIQUE INDEX "crm_document_template_table_columns_mapping_name_idx" ON "crm_document_template_table_columns" USING btree ("mapping_id","column_name");--> statement-breakpoint CREATE UNIQUE INDEX "crm_document_template_table_columns_mapping_name_idx" ON "crm_document_template_table_columns" USING btree ("mapping_id","column_name");--> statement-breakpoint
CREATE UNIQUE INDEX "crm_document_template_versions_template_version_idx" ON "crm_document_template_versions" USING btree ("template_id","version");--> statement-breakpoint CREATE UNIQUE INDEX "crm_document_template_versions_template_version_idx" ON "crm_document_template_versions" USING btree ("template_id","version");--> statement-breakpoint
@@ -651,7 +876,7 @@ CREATE UNIQUE INDEX "crm_quotations_org_code_idx" ON "crm_quotations" USING btre
CREATE UNIQUE INDEX "crm_report_definitions_org_code_idx" ON "crm_report_definitions" USING btree ("organization_id","code");--> statement-breakpoint CREATE UNIQUE INDEX "crm_report_definitions_org_code_idx" ON "crm_report_definitions" USING btree ("organization_id","code");--> statement-breakpoint
CREATE UNIQUE INDEX "crm_role_profiles_org_code_idx" ON "crm_role_profiles" USING btree ("organization_id","code");--> statement-breakpoint CREATE UNIQUE INDEX "crm_role_profiles_org_code_idx" ON "crm_role_profiles" USING btree ("organization_id","code");--> statement-breakpoint
CREATE UNIQUE INDEX "crm_user_role_assignments_org_user_role_idx" ON "crm_user_role_assignments" USING btree ("organization_id","user_id","role_profile_id");--> statement-breakpoint CREATE UNIQUE INDEX "crm_user_role_assignments_org_user_role_idx" ON "crm_user_role_assignments" USING btree ("organization_id","user_id","role_profile_id");--> statement-breakpoint
CREATE UNIQUE INDEX "document_sequences_org_doc_period_branch_idx" ON "document_sequences" USING btree ("organization_id","document_type","period","branch_id");--> statement-breakpoint CREATE UNIQUE INDEX "document_sequences_org_branch_product_doc_period_idx" ON "document_sequences" USING btree ("organization_id","branch_id","product_type","document_type","period");--> statement-breakpoint
CREATE UNIQUE INDEX "ms_options_org_category_code_idx" ON "ms_options" USING btree ("organization_id","category","code");--> statement-breakpoint CREATE UNIQUE INDEX "ms_options_org_category_code_idx" ON "ms_options" USING btree ("organization_id","category","code");--> statement-breakpoint
CREATE UNIQUE INDEX "organizations_slug_idx" ON "organizations" USING btree ("slug");--> statement-breakpoint CREATE UNIQUE INDEX "organizations_slug_idx" ON "organizations" USING btree ("slug");--> statement-breakpoint
CREATE UNIQUE INDEX "users_email_idx" ON "users" USING btree ("email"); CREATE UNIQUE INDEX "users_email_idx" ON "users" USING btree ("email");

View File

@@ -1,5 +0,0 @@
ALTER TABLE "crm_opportunities" ADD COLUMN "outcome_status" text DEFAULT 'open' NOT NULL;--> statement-breakpoint
ALTER TABLE "crm_opportunities" ADD COLUMN "closed_at" timestamp with time zone;--> statement-breakpoint
ALTER TABLE "crm_opportunities" ADD COLUMN "lost_detail" text;--> statement-breakpoint
ALTER TABLE "crm_opportunities" ADD COLUMN "cancel_reason" text;--> statement-breakpoint
ALTER TABLE "crm_opportunities" ADD COLUMN "no_quotation_reason" text;

View File

@@ -1,20 +0,0 @@
CREATE TABLE "crm_approval_matrices" (
"id" text PRIMARY KEY NOT NULL,
"organization_id" text NOT NULL,
"entity_type" text NOT NULL,
"workflow_id" text NOT NULL,
"branch_id" text,
"product_type" text,
"min_amount" double precision,
"max_amount" double precision,
"currency" text,
"priority" integer DEFAULT 100 NOT NULL,
"is_default" boolean DEFAULT false NOT NULL,
"is_active" boolean DEFAULT true NOT NULL,
"description" text,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
"deleted_at" timestamp with time zone,
"created_by" text NOT NULL,
"updated_by" text NOT NULL
);

View File

@@ -1,14 +0,0 @@
ALTER TABLE "crm_approval_workflows"
ADD COLUMN "description" text;
--> statement-breakpoint
ALTER TABLE "crm_approval_workflows"
ADD COLUMN "is_system" boolean DEFAULT false NOT NULL;
--> statement-breakpoint
ALTER TABLE "crm_approval_workflows"
ADD COLUMN "created_by" text NOT NULL DEFAULT '';
--> statement-breakpoint
ALTER TABLE "crm_approval_workflows"
ADD COLUMN "updated_by" text NOT NULL DEFAULT '';
--> statement-breakpoint
ALTER TABLE "crm_approval_steps"
ADD COLUMN "approval_mode" text DEFAULT 'sequential' NOT NULL;

View File

@@ -1,73 +0,0 @@
CREATE TABLE "app_notification_deliveries" (
"id" text PRIMARY KEY NOT NULL,
"organization_id" text NOT NULL,
"notification_id" text NOT NULL,
"channel" text NOT NULL,
"recipient_address" text,
"status" text DEFAULT 'pending' NOT NULL,
"attempt_count" integer DEFAULT 0 NOT NULL,
"last_error" text,
"sent_at" timestamp with time zone,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "app_notification_events" (
"id" text PRIMARY KEY NOT NULL,
"organization_id" text NOT NULL,
"event_type" text NOT NULL,
"entity_type" text NOT NULL,
"entity_id" text NOT NULL,
"actor_user_id" text,
"payload" jsonb,
"dedupe_key" text,
"status" text DEFAULT 'pending' NOT NULL,
"last_error" text,
"published_at" timestamp with time zone DEFAULT now() NOT NULL,
"processed_at" timestamp with time zone,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "app_notification_templates" (
"id" text PRIMARY KEY NOT NULL,
"organization_id" text NOT NULL,
"event_type" text NOT NULL,
"channel" text NOT NULL,
"title_template" text NOT NULL,
"body_template" text NOT NULL,
"link_template" text,
"is_active" boolean DEFAULT true NOT NULL,
"metadata" jsonb,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
"deleted_at" timestamp with time zone,
"created_by" text NOT NULL,
"updated_by" text NOT NULL
);
--> statement-breakpoint
CREATE TABLE "app_notifications" (
"id" text PRIMARY KEY NOT NULL,
"organization_id" text NOT NULL,
"recipient_user_id" text NOT NULL,
"event_id" text NOT NULL,
"title" text NOT NULL,
"body" text NOT NULL,
"link_url" text,
"severity" text DEFAULT 'info' NOT NULL,
"status" text DEFAULT 'unread' NOT NULL,
"read_at" timestamp with time zone,
"archived_at" timestamp with time zone,
"metadata" jsonb,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE UNIQUE INDEX "app_notification_events_org_dedupe_idx"
ON "app_notification_events" USING btree ("organization_id", "dedupe_key");
--> statement-breakpoint
CREATE UNIQUE INDEX "app_notification_templates_org_event_channel_idx"
ON "app_notification_templates" USING btree ("organization_id", "event_type", "channel");
--> statement-breakpoint
CREATE UNIQUE INDEX "app_notifications_recipient_event_idx"
ON "app_notifications" USING btree ("recipient_user_id", "event_id");

View File

@@ -1,38 +0,0 @@
CREATE TABLE "crm_approval_escalation_policies" (
"id" text PRIMARY KEY NOT NULL,
"organization_id" text NOT NULL,
"workflow_id" text NOT NULL,
"step_number" integer NOT NULL,
"trigger_after_hours" integer NOT NULL,
"target_type" text NOT NULL,
"target_value" text,
"is_active" boolean DEFAULT true NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
"deleted_at" timestamp with time zone,
"created_by" text NOT NULL,
"updated_by" text NOT NULL
);
--> statement-breakpoint
CREATE TABLE "crm_approval_reminder_policies" (
"id" text PRIMARY KEY NOT NULL,
"organization_id" text NOT NULL,
"workflow_id" text NOT NULL,
"step_number" integer NOT NULL,
"sla_hours" integer DEFAULT 24 NOT NULL,
"reminder_offsets_hours" integer[] DEFAULT '{}' NOT NULL,
"calendar_mode" text DEFAULT 'calendar_days' NOT NULL,
"timeout_action" text DEFAULT 'none' NOT NULL,
"is_active" boolean DEFAULT true NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
"deleted_at" timestamp with time zone,
"created_by" text NOT NULL,
"updated_by" text NOT NULL
);
--> statement-breakpoint
ALTER TABLE "crm_approval_requests" ADD COLUMN "current_step_started_at" timestamp with time zone DEFAULT now() NOT NULL;--> statement-breakpoint
ALTER TABLE "crm_approval_steps" ADD COLUMN "sla_hours" integer DEFAULT 24 NOT NULL;--> statement-breakpoint
ALTER TABLE "crm_approval_steps" ADD COLUMN "calendar_mode" text DEFAULT 'calendar_days' NOT NULL;--> statement-breakpoint
ALTER TABLE "crm_approval_steps" ADD COLUMN "timeout_action" text DEFAULT 'none' NOT NULL;--> statement-breakpoint
CREATE UNIQUE INDEX "crm_approval_reminder_policies_workflow_step_idx" ON "crm_approval_reminder_policies" USING btree ("workflow_id","step_number");

View File

@@ -1,11 +0,0 @@
ALTER TABLE "crm_document_template_versions" ADD COLUMN "metadata_json" jsonb;
--> statement-breakpoint
UPDATE "crm_document_template_versions"
SET
"metadata_json" = COALESCE("metadata_json", "schema_json" -> '__templateManagement'),
"schema_json" = CASE
WHEN "schema_json" ? '__templateManagement'
THEN "schema_json" - '__templateManagement'
ELSE "schema_json"
END
WHERE "schema_json" ? '__templateManagement';

View File

@@ -1,50 +0,0 @@
CREATE TABLE "crm_document_libraries" (
"id" text PRIMARY KEY NOT NULL,
"organization_id" text NOT NULL,
"code" text NOT NULL,
"name" text NOT NULL,
"document_type" text NOT NULL,
"description" text,
"brand" text NOT NULL,
"language" text NOT NULL,
"product_type" text NOT NULL,
"status" text DEFAULT 'active' NOT NULL,
"is_active" boolean DEFAULT true NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
"deleted_at" timestamp with time zone,
"created_by" text NOT NULL,
"updated_by" text NOT NULL
);
--> statement-breakpoint
CREATE UNIQUE INDEX "crm_document_libraries_org_code_idx" ON "crm_document_libraries" USING btree ("organization_id","code");
--> statement-breakpoint
CREATE TABLE "crm_document_library_versions" (
"id" text PRIMARY KEY NOT NULL,
"organization_id" text NOT NULL,
"library_id" text NOT NULL,
"version" text NOT NULL,
"file_name" text NOT NULL,
"storage_provider" text NOT NULL,
"storage_key" text NOT NULL,
"mime_type" text NOT NULL,
"file_size" integer NOT NULL,
"checksum" text NOT NULL,
"page_count" integer NOT NULL,
"status" text DEFAULT 'draft' NOT NULL,
"is_active" boolean DEFAULT false NOT NULL,
"published_at" timestamp with time zone,
"published_by" text,
"archived_at" timestamp with time zone,
"archived_by" text,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
"deleted_at" timestamp with time zone,
"created_by" text NOT NULL
);
--> statement-breakpoint
CREATE UNIQUE INDEX "crm_document_library_versions_library_version_idx" ON "crm_document_library_versions" USING btree ("library_id","version");
--> statement-breakpoint
CREATE UNIQUE INDEX "crm_document_library_versions_active_library_idx"
ON "crm_document_library_versions" USING btree ("library_id")
WHERE "is_active" = true AND "deleted_at" IS NULL;

View File

@@ -1,28 +0,0 @@
ALTER TABLE "document_sequences"
ADD COLUMN "product_type" text DEFAULT 'all' NOT NULL;
--> statement-breakpoint
ALTER TABLE "document_sequences"
ADD COLUMN "format" text DEFAULT '{prefix}{period}-{running}' NOT NULL;
--> statement-breakpoint
ALTER TABLE "document_sequences"
ADD COLUMN "reset_policy" text DEFAULT 'period' NOT NULL;
--> statement-breakpoint
UPDATE "document_sequences"
SET
"product_type" = CASE
WHEN "document_type" = 'quotation' THEN 'legacy'
ELSE 'all'
END,
"format" = '{prefix}{period}-{running}',
"reset_policy" = 'period';
--> statement-breakpoint
DROP INDEX "document_sequences_org_doc_period_branch_idx";
--> statement-breakpoint
CREATE UNIQUE INDEX "document_sequences_org_branch_product_doc_period_idx"
ON "document_sequences" USING btree (
"organization_id",
"branch_id",
"product_type",
"document_type",
"period"
);

View File

@@ -1,60 +0,0 @@
ALTER TABLE "app_notification_templates"
ADD COLUMN "code" text DEFAULT '' NOT NULL,
ADD COLUMN "name" text DEFAULT '' NOT NULL,
ADD COLUMN "subject_template" text,
ADD COLUMN "body_format" text DEFAULT 'plain' NOT NULL,
ADD COLUMN "language" text DEFAULT 'th' NOT NULL,
ADD COLUMN "variables" jsonb,
ADD COLUMN "version" integer DEFAULT 1 NOT NULL;
--> statement-breakpoint
UPDATE "app_notification_templates"
SET
"code" = CASE
WHEN "code" = '' THEN replace("event_type", '.', '_') || '_' || "channel"
ELSE "code"
END,
"name" = CASE
WHEN "name" = '' THEN "event_type" || ' (' || "channel" || ')'
ELSE "name"
END,
"subject_template" = COALESCE("subject_template", "title_template"),
"variables" = COALESCE("variables", '[]'::jsonb);
--> statement-breakpoint
CREATE TABLE "app_notification_dispatches" (
"id" text PRIMARY KEY NOT NULL,
"organization_id" text NOT NULL,
"event_id" text NOT NULL,
"template_id" text,
"entity_type" text NOT NULL,
"entity_id" text NOT NULL,
"channel" text NOT NULL,
"provider_key" text NOT NULL,
"status" text DEFAULT 'pending' NOT NULL,
"recipient_to" jsonb NOT NULL,
"recipient_cc" jsonb,
"recipient_bcc" jsonb,
"reply_to" text,
"subject" text NOT NULL,
"body_text" text NOT NULL,
"body_html" text,
"attachments" jsonb,
"recipient_user_id" text,
"recipient_address" text,
"requested_by" text NOT NULL,
"metadata" jsonb,
"last_error" text,
"retry_count" integer DEFAULT 0 NOT NULL,
"max_retry_count" integer DEFAULT 3 NOT NULL,
"last_attempt_at" timestamp with time zone,
"sent_at" timestamp with time zone,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE UNIQUE INDEX "app_notification_dispatches_org_event_channel_recipient_idx"
ON "app_notification_dispatches" USING btree (
"organization_id",
"event_id",
"channel",
"recipient_address"
);

View File

@@ -1,89 +0,0 @@
CREATE TABLE "app_notification_dispatches" (
"id" text PRIMARY KEY NOT NULL,
"organization_id" text NOT NULL,
"event_id" text NOT NULL,
"template_id" text,
"entity_type" text NOT NULL,
"entity_id" text NOT NULL,
"channel" text NOT NULL,
"provider_key" text NOT NULL,
"status" text DEFAULT 'pending' NOT NULL,
"recipient_to" jsonb NOT NULL,
"recipient_cc" jsonb,
"recipient_bcc" jsonb,
"reply_to" text,
"subject" text NOT NULL,
"body_text" text NOT NULL,
"body_html" text,
"attachments" jsonb,
"recipient_user_id" text,
"recipient_address" text,
"requested_by" text NOT NULL,
"metadata" jsonb,
"last_error" text,
"retry_count" integer DEFAULT 0 NOT NULL,
"max_retry_count" integer DEFAULT 3 NOT NULL,
"last_attempt_at" timestamp with time zone,
"sent_at" timestamp with time zone,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "crm_document_libraries" (
"id" text PRIMARY KEY NOT NULL,
"organization_id" text NOT NULL,
"code" text NOT NULL,
"name" text NOT NULL,
"document_type" text NOT NULL,
"description" text,
"brand" text NOT NULL,
"language" text NOT NULL,
"product_type" text NOT NULL,
"status" text DEFAULT 'active' NOT NULL,
"is_active" boolean DEFAULT true NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
"deleted_at" timestamp with time zone,
"created_by" text NOT NULL,
"updated_by" text NOT NULL
);
--> statement-breakpoint
CREATE TABLE "crm_document_library_versions" (
"id" text PRIMARY KEY NOT NULL,
"organization_id" text NOT NULL,
"library_id" text NOT NULL,
"version" text NOT NULL,
"file_name" text NOT NULL,
"storage_provider" text NOT NULL,
"storage_key" text NOT NULL,
"mime_type" text NOT NULL,
"file_size" integer NOT NULL,
"checksum" text NOT NULL,
"page_count" integer NOT NULL,
"status" text DEFAULT 'draft' NOT NULL,
"is_active" boolean DEFAULT false NOT NULL,
"published_at" timestamp with time zone,
"published_by" text,
"archived_at" timestamp with time zone,
"archived_by" text,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
"deleted_at" timestamp with time zone,
"created_by" text NOT NULL
);
--> statement-breakpoint
DROP INDEX "document_sequences_org_doc_period_branch_idx";--> statement-breakpoint
ALTER TABLE "app_notification_templates" ADD COLUMN "code" text DEFAULT '' NOT NULL;--> statement-breakpoint
ALTER TABLE "app_notification_templates" ADD COLUMN "name" text DEFAULT '' NOT NULL;--> statement-breakpoint
ALTER TABLE "app_notification_templates" ADD COLUMN "subject_template" text;--> statement-breakpoint
ALTER TABLE "app_notification_templates" ADD COLUMN "body_format" text DEFAULT 'plain' NOT NULL;--> statement-breakpoint
ALTER TABLE "app_notification_templates" ADD COLUMN "language" text DEFAULT 'th' NOT NULL;--> statement-breakpoint
ALTER TABLE "app_notification_templates" ADD COLUMN "variables" jsonb;--> statement-breakpoint
ALTER TABLE "app_notification_templates" ADD COLUMN "version" integer DEFAULT 1 NOT NULL;--> statement-breakpoint
ALTER TABLE "document_sequences" ADD COLUMN "product_type" text DEFAULT 'all' NOT NULL;--> statement-breakpoint
ALTER TABLE "document_sequences" ADD COLUMN "format" text DEFAULT '{prefix}{period}-{running}' NOT NULL;--> statement-breakpoint
ALTER TABLE "document_sequences" ADD COLUMN "reset_policy" text DEFAULT 'period' NOT NULL;--> statement-breakpoint
CREATE UNIQUE INDEX "app_notification_dispatches_org_event_channel_recipient_idx" ON "app_notification_dispatches" USING btree ("organization_id","event_id","channel","recipient_address");--> statement-breakpoint
CREATE UNIQUE INDEX "crm_document_libraries_org_code_idx" ON "crm_document_libraries" USING btree ("organization_id","code");--> statement-breakpoint
CREATE UNIQUE INDEX "crm_document_library_versions_library_version_idx" ON "crm_document_library_versions" USING btree ("library_id","version");--> statement-breakpoint
CREATE UNIQUE INDEX "document_sequences_org_branch_product_doc_period_idx" ON "document_sequences" USING btree ("organization_id","branch_id","product_type","document_type","period");

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -5,71 +5,8 @@
{ {
"idx": 0, "idx": 0,
"version": "7", "version": "7",
"when": 1782359607850, "when": 1782833951256,
"tag": "0000_hard_butterfly", "tag": "0000_short_the_executioner",
"breakpoints": true
},
{
"idx": 1,
"version": "7",
"when": 1782381033930,
"tag": "0001_lazy_otto_octavius",
"breakpoints": true
},
{
"idx": 2,
"version": "7",
"when": 1782604800000,
"tag": "0002_calm_approval_matrix",
"breakpoints": true
},
{
"idx": 3,
"version": "7",
"when": 1782691200000,
"tag": "0003_workflow_builder_foundation",
"breakpoints": true
},
{
"idx": 4,
"version": "7",
"when": 1782455991734,
"tag": "0004_unusual_hairball",
"breakpoints": true
},
{
"idx": 5,
"version": "7",
"when": 1782464526434,
"tag": "0005_furry_maelstrom",
"breakpoints": true
},
{
"idx": 6,
"version": "7",
"when": 1782749787685,
"tag": "0006_lazy_shiver_man",
"breakpoints": true
},
{
"idx": 7,
"version": "7",
"when": 1782910000000,
"tag": "0007_document_library_foundation",
"breakpoints": true
},
{
"idx": 8,
"version": "7",
"when": 1782913600000,
"tag": "0008_document_sequence_scope_refactor",
"breakpoints": true
},
{
"idx": 9,
"version": "7",
"when": 1782815675836,
"tag": "0009_stale_next_avengers",
"breakpoints": true "breakpoints": true
} }
] ]

2
next-env.d.ts vendored
View File

@@ -1,6 +1,6 @@
/// <reference types="next" /> /// <reference types="next" />
/// <reference types="next/image-types/global" /> /// <reference types="next/image-types/global" />
import "./.next/types/routes.d.ts"; import "./.next/dev/types/routes.d.ts";
// NOTE: This file should not be edited // NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

327
plans/task-p.7.2.1.md Normal file
View File

@@ -0,0 +1,327 @@
# Amendment to Task P.7.1.2 Working Documents & Official Documents UX
## Design Revision
Following the UX review, the document experience will be refined to better reflect the actual CRM business process.
The terminology **Draft PDF** is too technical for end users and should not be the primary wording exposed in the UI.
The new UX separates documents into three business levels.
---
# Level 1 — Working Documents
Purpose:
Internal working documents used during quotation preparation.
Source:
Live quotation data.
Characteristics:
* Generated from current database values.
* Changes whenever quotation data changes.
* Used internally only.
* Not legally or commercially binding.
* Not intended for customer delivery.
* Not part of audit history.
UI terminology:
```text
Working Documents
```
Primary document:
```text
Preview PDF
```
The UI should avoid exposing the technical term **Draft PDF**.
Instead:
```text
Preview PDF
```
or
```text
Working Copy
```
may be displayed.
Available actions:
```text
Generate
Preview
```
No Download button is required unless business rules explicitly allow downloading internal working copies.
---
# Level 2 — Official Documents
Purpose:
Business-approved immutable documents.
Source:
Approved Snapshot.
Characteristics:
* Generated only after approval.
* Immutable.
* Stored as document artifact.
* Used for audit.
* Used for revision history.
* Used as official company record.
UI terminology:
```text
Official Documents
```
Primary document:
```text
Approved PDF
```
Available actions:
```text
Preview
Download
```
No Generate button should appear here because approval already created the official document.
---
# Level 3 — Customer Package
Purpose:
Deliverable package sent to customers.
Source:
Approved PDF
plus
Document Library appendices.
Example:
```text
Approved PDF
+
SLA
+
Warranty
+
Datasheet
+
Drawing
```
Characteristics:
* Generated after document assembly.
* Built from official documents.
* Represents exactly what customers receive.
* May be regenerated if append documents change.
Available actions:
```text
Generate Package
Preview Package
Download Package
Send Package (future)
```
---
# Relationship Between Document Types
Business flow becomes:
```text
Quotation
Working Documents
Approval
Official Documents
Customer Package
Email Customer
```
This hierarchy should be reflected visually.
---
# Documents Tab Layout
Replace the previous layout with:
```text
Documents
────────────────────────
Working Documents
Preview PDF
[Generate]
[Preview]
────────────────────────
Official Documents
Approved PDF
[Preview]
[Download]
────────────────────────
Customer Package
Status
Included Documents
Warnings
[Generate Package]
[Preview Package]
[Download Package]
[Send Package]
```
The **Send Package** action remains disabled until P.7.2.
---
# Customer Package Tab
The dedicated Customer Package tab remains.
It focuses on operational details:
* Package status
* Included documents
* Assembly warnings
* Generated time
* Generated by
* Page count
* Checksum
* Generate / Regenerate
* Preview
* Download
This tab is not responsible for previewing individual PDFs.
---
# UX Rules
Working Documents
* Can always regenerate.
* Uses live quotation data.
* Intended for internal review.
Official Documents
* Never regenerate from this screen.
* Always read-only.
* Represents approved business record.
Customer Package
* Always assembled from Official Documents.
* Must never assemble from Working Documents.
* If no Approved PDF exists, package generation must fail with a business-readable message.
---
# Business Rules
Customer Package must always use:
```text
Approved PDF
+
Active Document Library files
```
Customer Package must never use:
```text
Working Documents
Preview PDF
Draft runtime output
```
This guarantees that every package delivered to a customer is assembled from approved, auditable content.
---
# Acceptance Criteria (Additional)
* "Draft PDF" is no longer the primary UI terminology.
* Documents are clearly separated into Working Documents, Official Documents, and Customer Package.
* Customer Package assembly is based exclusively on Approved PDF.
* UI hierarchy reflects the CRM document lifecycle.
* The new terminology is reusable across future modules such as Purchase Order, Service Report, Inspection Report, and Work Order.

View File

@@ -1,7 +1,7 @@
import { runNodeScript } from './lib/seed-utils.ts'; import { runNodeScript } from "./lib/seed-utils.ts";
function main() { function main() {
runNodeScript('seed:uat:core', 'src/db/seeds/uat.seed.ts'); runNodeScript("seed:uat:core", "src/db/seeds/uat.seed.ts");
} }
main(); main();

View File

@@ -1576,7 +1576,7 @@ async function upsertDocumentSequencesForOrganizationV2(
async function upsertApprovalWorkflowsForOrganization( async function upsertApprovalWorkflowsForOrganization(
sql: SqlClient, sql: SqlClient,
organizationId: string, organization: { id: string; createdBy: string },
) { ) {
for (const workflow of APPROVAL_WORKFLOWS) { for (const workflow of APPROVAL_WORKFLOWS) {
const [workflowRow] = await sql` const [workflowRow] = await sql`
@@ -1587,14 +1587,18 @@ async function upsertApprovalWorkflowsForOrganization(
name, name,
entity_type, entity_type,
is_active, is_active,
created_by,
updated_by,
deleted_at deleted_at
) values ( ) values (
${crypto.randomUUID()}, ${crypto.randomUUID()},
${organizationId}, ${organization.id},
${workflow.code}, ${workflow.code},
${workflow.name}, ${workflow.name},
${workflow.entityType}, ${workflow.entityType},
${true}, ${true},
${organization.createdBy},
${organization.createdBy},
${null} ${null}
) )
on conflict (organization_id, code) do update on conflict (organization_id, code) do update
@@ -1602,6 +1606,7 @@ async function upsertApprovalWorkflowsForOrganization(
name = excluded.name, name = excluded.name,
entity_type = excluded.entity_type, entity_type = excluded.entity_type,
is_active = excluded.is_active, is_active = excluded.is_active,
updated_by = excluded.updated_by,
deleted_at = excluded.deleted_at, deleted_at = excluded.deleted_at,
updated_at = now() updated_at = now()
returning id returning id
@@ -1620,7 +1625,7 @@ async function upsertApprovalWorkflowsForOrganization(
deleted_at deleted_at
) values ( ) values (
${crypto.randomUUID()}, ${crypto.randomUUID()},
${organizationId}, ${organization.id},
${workflowRow.id}, ${workflowRow.id},
${step.stepNumber}, ${step.stepNumber},
${step.roleCode}, ${step.roleCode},
@@ -2338,7 +2343,7 @@ async function main() {
organization, organization,
branchRows, branchRows,
); );
await upsertApprovalWorkflowsForOrganization(sql, organization.id); await upsertApprovalWorkflowsForOrganization(sql, organization);
await upsertApprovalMatricesForOrganization(sql, organization); await upsertApprovalMatricesForOrganization(sql, organization);
await upsertDocumentTemplatesForOrganization(sql, organization); await upsertDocumentTemplatesForOrganization(sql, organization);
await upsertDocumentLibraryForOrganization(sql, organization); await upsertDocumentLibraryForOrganization(sql, organization);

View File

@@ -1,5 +1,9 @@
import { getDefaultPermissions } from '../../lib/auth/rbac.ts'; import { getDefaultPermissions } from "../../lib/auth/rbac.ts";
import { createSqlClient, deterministicId, type SqlClient } from '../../../scripts/lib/seed-utils.ts'; import {
createSqlClient,
deterministicId,
type SqlClient,
} from "../../../scripts/lib/seed-utils.ts";
type UserRow = { type UserRow = {
id: string; id: string;
@@ -7,17 +11,19 @@ type UserRow = {
}; };
const DEFAULT_ORGANIZATION = { const DEFAULT_ORGANIZATION = {
id: deterministicId('organization:alla-demo'), id: deterministicId("organization:alla-demo"),
name: 'ALLA Demo Organization', name: "ALLA Demo",
slug: 'alla-demo', slug: "alla-demo",
membershipId: deterministicId('membership:alla-demo:super-admin') membershipId: deterministicId("membership:alla-demo:super-admin"),
}; };
async function resolveSuperAdmin(sql: SqlClient): Promise<UserRow> { async function resolveSuperAdmin(sql: SqlClient): Promise<UserRow> {
const email = process.env.SUPER_ADMIN_EMAIL?.trim().toLowerCase(); const email = process.env.SUPER_ADMIN_EMAIL?.trim().toLowerCase();
if (!email) { if (!email) {
throw new Error('SUPER_ADMIN_EMAIL is required before running system organization seed.'); throw new Error(
"SUPER_ADMIN_EMAIL is required before running system organization seed.",
);
} }
const [user] = await sql<UserRow[]>` const [user] = await sql<UserRow[]>`
@@ -28,7 +34,7 @@ async function resolveSuperAdmin(sql: SqlClient): Promise<UserRow> {
`; `;
if (!user) { if (!user) {
throw new Error('Super admin user not found. Run seed-super-admin first.'); throw new Error("Super admin user not found. Run seed-super-admin first.");
} }
return user; return user;
@@ -46,7 +52,7 @@ async function ensureOrganization(sql: SqlClient, superAdmin: UserRow) {
${DEFAULT_ORGANIZATION.id}, ${DEFAULT_ORGANIZATION.id},
${DEFAULT_ORGANIZATION.name}, ${DEFAULT_ORGANIZATION.name},
${DEFAULT_ORGANIZATION.slug}, ${DEFAULT_ORGANIZATION.slug},
${'enterprise'}, ${"enterprise"},
${superAdmin.id} ${superAdmin.id}
) )
on conflict (slug) do update on conflict (slug) do update
@@ -56,7 +62,7 @@ async function ensureOrganization(sql: SqlClient, superAdmin: UserRow) {
updated_at = now() updated_at = now()
`; `;
const membershipPermissions = getDefaultPermissions('admin', 'crm_admin'); const membershipPermissions = getDefaultPermissions("admin", "crm_admin");
const [existingMembership] = await sql<{ id: string }[]>` const [existingMembership] = await sql<{ id: string }[]>`
select id select id
from memberships from memberships
@@ -69,8 +75,8 @@ async function ensureOrganization(sql: SqlClient, superAdmin: UserRow) {
await sql` await sql`
update memberships update memberships
set set
role = ${'admin'}, role = ${"admin"},
business_role = ${'crm_admin'}, business_role = ${"crm_admin"},
permissions = ${sql.array(membershipPermissions)}, permissions = ${sql.array(membershipPermissions)},
updated_at = now() updated_at = now()
where id = ${existingMembership.id} where id = ${existingMembership.id}
@@ -90,8 +96,8 @@ async function ensureOrganization(sql: SqlClient, superAdmin: UserRow) {
${DEFAULT_ORGANIZATION.membershipId}, ${DEFAULT_ORGANIZATION.membershipId},
${superAdmin.id}, ${superAdmin.id},
${DEFAULT_ORGANIZATION.id}, ${DEFAULT_ORGANIZATION.id},
${'admin'}, ${"admin"},
${'crm_admin'}, ${"crm_admin"},
${sql.array(membershipPermissions)}, ${sql.array(membershipPermissions)},
${sql.array([])}, ${sql.array([])},
${sql.array([])} ${sql.array([])}
@@ -122,8 +128,8 @@ async function main() {
main().catch((error) => { main().catch((error) => {
console.error( console.error(
'[seed:system-org] Failed:', "[seed:system-org] Failed:",
error instanceof Error ? error.message : String(error) error instanceof Error ? error.message : String(error),
); );
process.exitCode = 1; process.exitCode = 1;
}); });

View File

@@ -1,36 +1,38 @@
export const DOCUMENT_LIBRARY_TYPES = [ export const DOCUMENT_LIBRARY_TYPES = [
'sla', "sla",
'warranty', "warranty",
'terms_conditions', "terms_conditions",
'company_profile', "company_profile",
'datasheet', "datasheet",
'drawing', "drawing",
'certificate', "certificate",
'appendix', "appendix",
'safety_document', "safety_document",
'installation_manual' "installation_manual",
] as const; ] as const;
export const DOCUMENT_LIBRARY_BRANDS = ['alla', 'onvalla', 'generic'] as const; export const DOCUMENT_LIBRARY_BRANDS = ["alla", "onvalla", "generic"] as const;
export const DOCUMENT_LIBRARY_LANGUAGES = ['th', 'en'] as const; export const DOCUMENT_LIBRARY_LANGUAGES = ["th", "en"] as const;
export const DOCUMENT_LIBRARY_PRODUCT_TYPES = [ export const DOCUMENT_LIBRARY_PRODUCT_TYPES = [
'all', "all",
'crane', "crane",
'dock_door', "dock_door",
'solar', "solar",
'service' "service",
] as const; ] as const;
export const DOCUMENT_LIBRARY_VERSION_STATUSES = [ export const DOCUMENT_LIBRARY_VERSION_STATUSES = [
'draft', "draft",
'published', "published",
'active', "active",
'archived' "archived",
] as const; ] as const;
export type DocumentLibraryType = (typeof DOCUMENT_LIBRARY_TYPES)[number]; export type DocumentLibraryType = (typeof DOCUMENT_LIBRARY_TYPES)[number];
export type DocumentLibraryBrand = (typeof DOCUMENT_LIBRARY_BRANDS)[number]; export type DocumentLibraryBrand = (typeof DOCUMENT_LIBRARY_BRANDS)[number];
export type DocumentLibraryLanguage = (typeof DOCUMENT_LIBRARY_LANGUAGES)[number]; export type DocumentLibraryLanguage =
export type DocumentLibraryProductType = (typeof DOCUMENT_LIBRARY_PRODUCT_TYPES)[number]; (typeof DOCUMENT_LIBRARY_LANGUAGES)[number];
export type DocumentLibraryProductType =
(typeof DOCUMENT_LIBRARY_PRODUCT_TYPES)[number];
export type DocumentLibraryVersionStatus = export type DocumentLibraryVersionStatus =
(typeof DOCUMENT_LIBRARY_VERSION_STATUSES)[number]; (typeof DOCUMENT_LIBRARY_VERSION_STATUSES)[number];
@@ -44,7 +46,7 @@ export interface DocumentLibraryRecord {
brand: DocumentLibraryBrand; brand: DocumentLibraryBrand;
language: DocumentLibraryLanguage; language: DocumentLibraryLanguage;
productType: DocumentLibraryProductType; productType: DocumentLibraryProductType;
status: 'active' | 'inactive'; status: "active" | "inactive";
isActive: boolean; isActive: boolean;
createdBy: string; createdBy: string;
updatedBy: string; updatedBy: string;
@@ -91,7 +93,7 @@ export interface DocumentLibraryFilters {
brand?: DocumentLibraryBrand; brand?: DocumentLibraryBrand;
language?: DocumentLibraryLanguage; language?: DocumentLibraryLanguage;
productType?: DocumentLibraryProductType; productType?: DocumentLibraryProductType;
isActive?: 'true' | 'false'; isActive?: "true" | "false";
} }
export interface DocumentLibraryMutationPayload { export interface DocumentLibraryMutationPayload {