From fd01ebf7c7ca2089e5737697577b5bf35f90d67b Mon Sep 17 00:00:00 2001 From: phaichayon Date: Fri, 19 Jun 2026 15:09:58 +0700 Subject: [PATCH] pdf task-h --- .../pdf-audit/payload-parity-report.json | 12 + .../placeholder-integrity-report.json | 30 + .../pdf-audit/signature-audit-report.json | 12 + artifacts/pdf-audit/summary.json | 19 + artifacts/pdf-audit/summary.md | 21 + .../pdf-audit/template-version-report.json | 8 + artifacts/pdf-audit/topic-runtime-report.json | 22 + docs/adr/0013-pdf-visual-parity-strategy.md | 49 + docs/business/pdf-mapping-registry.md | 34 + docs/business/pdf-placeholder-registry.md | 36 + docs/implementation/pdf-audit-report.json | 69 ++ docs/implementation/pdf-audit-report.md | 30 + docs/implementation/pdf-parity-checklist.md | 37 + ...task-h5-pdf-mapping-audit-visual-parity.md | 36 + .../task-h51-pdf-integrity-audit.md | 40 + docs/implementation/technical-debt.md | 1 + package.json | 10 + plans/task-h.5.1.md | 324 +++++ plans/task-h.5.md | 512 ++++++++ scripts/audit-approved-snapshot-parity.ts | 67 + scripts/audit-payload-parity.ts | 81 ++ scripts/audit-pdf-mapping-coverage.ts | 125 ++ scripts/audit-pdf-runtime-payload.ts | 153 +++ scripts/audit-pdf-template-inventory.ts | 89 ++ scripts/audit-placeholder-integrity.ts | 105 ++ scripts/audit-template-version-integrity.ts | 166 +++ scripts/generate-pdf-audit-report.ts | 194 +++ scripts/generate-pdf-integrity-report.ts | 115 ++ scripts/pdf-audit-utils.ts | 1089 +++++++++++++++++ scripts/seed-task-h1-fixture.js | 428 +++++-- src/app/page.tsx | 8 +- src/config/nav-config.ts | 92 +- .../crm/quotations/document/server/service.ts | 23 +- ...99ed-59c4d120c72f-QT-H5-AUDIT-approved.pdf | Bin 0 -> 126520 bytes tsconfig.json | 1 + 35 files changed, 3905 insertions(+), 133 deletions(-) create mode 100644 artifacts/pdf-audit/payload-parity-report.json create mode 100644 artifacts/pdf-audit/placeholder-integrity-report.json create mode 100644 artifacts/pdf-audit/signature-audit-report.json create mode 100644 artifacts/pdf-audit/summary.json create mode 100644 artifacts/pdf-audit/summary.md create mode 100644 artifacts/pdf-audit/template-version-report.json create mode 100644 artifacts/pdf-audit/topic-runtime-report.json create mode 100644 docs/adr/0013-pdf-visual-parity-strategy.md create mode 100644 docs/business/pdf-mapping-registry.md create mode 100644 docs/business/pdf-placeholder-registry.md create mode 100644 docs/implementation/pdf-audit-report.json create mode 100644 docs/implementation/pdf-audit-report.md create mode 100644 docs/implementation/pdf-parity-checklist.md create mode 100644 docs/implementation/task-h5-pdf-mapping-audit-visual-parity.md create mode 100644 docs/implementation/task-h51-pdf-integrity-audit.md create mode 100644 plans/task-h.5.1.md create mode 100644 plans/task-h.5.md create mode 100644 scripts/audit-approved-snapshot-parity.ts create mode 100644 scripts/audit-payload-parity.ts create mode 100644 scripts/audit-pdf-mapping-coverage.ts create mode 100644 scripts/audit-pdf-runtime-payload.ts create mode 100644 scripts/audit-pdf-template-inventory.ts create mode 100644 scripts/audit-placeholder-integrity.ts create mode 100644 scripts/audit-template-version-integrity.ts create mode 100644 scripts/generate-pdf-audit-report.ts create mode 100644 scripts/generate-pdf-integrity-report.ts create mode 100644 scripts/pdf-audit-utils.ts create mode 100644 storage/organizations/2330c067-2737-4443-bd6e-ccb8a6cf856b/quotations/approved/3ba9c39c-8121-416a-99ed-59c4d120c72f-QT-H5-AUDIT-approved.pdf diff --git a/artifacts/pdf-audit/payload-parity-report.json b/artifacts/pdf-audit/payload-parity-report.json new file mode 100644 index 0000000..8707c91 --- /dev/null +++ b/artifacts/pdf-audit/payload-parity-report.json @@ -0,0 +1,12 @@ +{ + "audit": "payload-parity", + "overallStatus": "PASS", + "quotationCode": "QT-H5-AUDIT", + "previewHash": "3ce9c3ff95ca5b360bf4430089450322b6dc2b52c0ae44de6cb17aeaf9e05844", + "generationHash": "3ce9c3ff95ca5b360bf4430089450322b6dc2b52c0ae44de6cb17aeaf9e05844", + "snapshotHash": "3ce9c3ff95ca5b360bf4430089450322b6dc2b52c0ae44de6cb17aeaf9e05844", + "mismatches": [], + "comparableFieldCount": 31, + "topicInputKeyCount": 10, + "signatureParity": true +} diff --git a/artifacts/pdf-audit/placeholder-integrity-report.json b/artifacts/pdf-audit/placeholder-integrity-report.json new file mode 100644 index 0000000..c22814c --- /dev/null +++ b/artifacts/pdf-audit/placeholder-integrity-report.json @@ -0,0 +1,30 @@ +{ + "audit": "placeholder-integrity", + "overallStatus": "PASS", + "results": [ + { + "templateName": "ALLA Quotation Standard", + "organizationId": "2330c067-2737-4443-bd6e-ccb8a6cf856b", + "version": "1.0", + "duplicateFieldNames": [], + "typoFields": [], + "unmappedPlaceholders": [], + "deadMappings": [], + "invalidReservedMappings": [], + "missingReservedFields": [], + "status": "PASS" + }, + { + "templateName": "ONVALLA Quotation Standard", + "organizationId": "8cd23975-e199-4ed0-85a9-8cbd01c810d2", + "version": "1.0", + "duplicateFieldNames": [], + "typoFields": [], + "unmappedPlaceholders": [], + "deadMappings": [], + "invalidReservedMappings": [], + "missingReservedFields": [], + "status": "PASS" + } + ] +} diff --git a/artifacts/pdf-audit/signature-audit-report.json b/artifacts/pdf-audit/signature-audit-report.json new file mode 100644 index 0000000..27a1eba --- /dev/null +++ b/artifacts/pdf-audit/signature-audit-report.json @@ -0,0 +1,12 @@ +{ + "audit": "approved-snapshot-parity", + "overallStatus": "WARNING", + "quotationCode": "QT-H5-AUDIT", + "approvedSnapshotAvailable": false, + "approvedTemplateVersionId": null, + "computedTemplateVersionId": "4ac3e91c-8320-4f41-81cd-5068cc9b5768", + "inconsistentTemplateKeys": [], + "computedSignatureHash": "39e2c42458c8b08b4299dc3136c9812c311ae7886df91078f4c9ae6e87bdcc00", + "storedSignatureHash": null, + "approvedArtifactReference": null +} diff --git a/artifacts/pdf-audit/summary.json b/artifacts/pdf-audit/summary.json new file mode 100644 index 0000000..f87a869 --- /dev/null +++ b/artifacts/pdf-audit/summary.json @@ -0,0 +1,19 @@ +{ + "generatedAt": "2026-06-19T07:45:01.927Z", + "overallStatus": "WARNING", + "quotationCode": "QT-H5-AUDIT", + "artifacts": { + "templateVersionReport": "template-version-report.json", + "placeholderIntegrityReport": "placeholder-integrity-report.json", + "payloadParityReport": "payload-parity-report.json", + "topicRuntimeReport": "topic-runtime-report.json", + "signatureAuditReport": "signature-audit-report.json" + }, + "statuses": { + "templateVersion": "PASS", + "placeholderIntegrity": "PASS", + "payloadParity": "PASS", + "topicRuntime": "PASS", + "approvedSnapshotParity": "WARNING" + } +} diff --git a/artifacts/pdf-audit/summary.md b/artifacts/pdf-audit/summary.md new file mode 100644 index 0000000..b3c4e80 --- /dev/null +++ b/artifacts/pdf-audit/summary.md @@ -0,0 +1,21 @@ +# PDF Integrity Summary + +- Generated At: 2026-06-19T07:45:01.927Z +- Overall Status: WARNING +- Quotation Code: QT-H5-AUDIT + +## Statuses + +- Template Version Integrity: PASS +- Placeholder Integrity: PASS +- Payload Parity: PASS +- Topic Runtime: PASS +- Approved Snapshot Parity: WARNING + +## Artifact Files + +- template-version-report.json +- placeholder-integrity-report.json +- payload-parity-report.json +- topic-runtime-report.json +- signature-audit-report.json diff --git a/artifacts/pdf-audit/template-version-report.json b/artifacts/pdf-audit/template-version-report.json new file mode 100644 index 0000000..dcbb3e0 --- /dev/null +++ b/artifacts/pdf-audit/template-version-report.json @@ -0,0 +1,8 @@ +{ + "audit": "template-version-integrity", + "overallStatus": "PASS", + "activeTemplateCount": 2, + "quotationCode": "QT-H5-AUDIT", + "approvedTemplateVersionId": null, + "issues": [] +} diff --git a/artifacts/pdf-audit/topic-runtime-report.json b/artifacts/pdf-audit/topic-runtime-report.json new file mode 100644 index 0000000..2ea3357 --- /dev/null +++ b/artifacts/pdf-audit/topic-runtime-report.json @@ -0,0 +1,22 @@ +{ + "audit": "topic-runtime", + "overallStatus": "PASS", + "quotationCode": "QT-H5-AUDIT", + "topicCount": 5, + "topicInputKeys": [ + "topic_1_0", + "topic_1_1", + "topic_1_2", + "topic_1_3", + "topic_1_4" + ], + "itemTopicInputKeys": [ + "item_topic_1_0", + "item_topic_1_1", + "item_topic_1_2", + "item_topic_1_3", + "item_topic_1_4" + ], + "duplicateKeys": [], + "emptySections": [] +} diff --git a/docs/adr/0013-pdf-visual-parity-strategy.md b/docs/adr/0013-pdf-visual-parity-strategy.md new file mode 100644 index 0000000..0fae5a8 --- /dev/null +++ b/docs/adr/0013-pdf-visual-parity-strategy.md @@ -0,0 +1,49 @@ +# ADR 0013: PDF Visual Parity Strategy + +## Status + +Accepted + +## Context + +The quotation PDF flow now includes: + +- production PDFME templates per organization +- DB-backed mapping registry +- dynamic topic expansion +- approval signature resolution +- approved snapshot persistence + +What was missing was a repeatable audit strategy proving that template schema, mappings, runtime payload, and approved snapshot content remain synchronized over time. + +## Decision + +- freeze a documented PDF mapping registry in `docs/business/pdf-mapping-registry.md` +- keep a human-readable parity checklist in `docs/implementation/pdf-parity-checklist.md` +- add an audit suite: + - `audit-pdf-template-inventory` + - `audit-pdf-mapping-coverage` + - `audit-pdf-runtime-payload` + - `generate-pdf-audit-report` +- expose the suite through `npm run audit:pdf` +- prefer the stable fixture quotation code `QT-H5-AUDIT` for repeatable audits when available +- compare preview runtime content against approved snapshot content for business-field consistency + +## Visual Parity Rules + +- allowed differences: + - watermark state + - generated timestamp + - artifact metadata +- disallowed differences: + - customer identity + - quotation commercial values + - topic content + - signature identity + - mapped table content + +## Consequences + +- regressions in template coverage become detectable without opening every PDF manually +- mapping ownership is explicit and reviewable +- future PDF changes must update both runtime logic and the registry/checklist when business content changes diff --git a/docs/business/pdf-mapping-registry.md b/docs/business/pdf-mapping-registry.md new file mode 100644 index 0000000..edba39d --- /dev/null +++ b/docs/business/pdf-mapping-registry.md @@ -0,0 +1,34 @@ +# PDF Mapping Registry + +This document is the source of truth for current quotation PDF field ownership. + +| PDF Field | Source Path | Transform | Example Value | +| --- | --- | --- | --- | +| `customer_name` | `customer.name` | direct | `ALLA Demo Customer` | +| `customer_addr` | `customer.address` | direct multiline | `99/9 Rama IX Road, Bangkok 10310` | +| `customer_tel` | `customer.phone` | direct | `02-000-1000` | +| `customer_email` | `customer.email` | direct | `demo-customer@local.test` | +| `customer_att` | `quotation.attention` | direct | `Task H.1 Contact` | +| `project_name` | `quotation.projectName` | direct | `Task H.5 Audit Fixture` | +| `site_location` | `quotation.projectLocation` | direct multiline | `Bangkok HQ` | +| `quotation_date` | `pdfme.quotation_date` | `formatPdfDate()` | `16 Jun 2026` | +| `quotation_code` | `quotation.code` | direct | `QT-H5-AUDIT` | +| `quotation_price` | `pdfme.quotation_price` | `formatPdfCurrency()` | `THB 128,400.00` | +| `currency` | `quotation.currency` | direct | `THB` | +| `exclusion_data` | `pdfme.exclusion_data` | `formatTopicItems()` | `[["Civil work by customer"]]` | +| `items_table` | `items` | `normalizePdfmeTable()` with table columns | item rows | +| `app1` | `signatures.preparedBy.name` | direct | `Task H.1 Admin` | +| `app1_position` | `signatures.preparedBy.position` | position resolver | `Sales Manager` | +| `app2` | `signatures.approvedBy.name` | direct | `Task H.1 Admin` | +| `app2_position` | `signatures.approvedBy.position` | position resolver | `Sales Manager` | +| `app3` | `signatures.authorizedBy.name` | direct | `Task H.1 Admin` | +| `app3_position` | `signatures.authorizedBy.position` | position resolver | `Sales Manager` | +| `topic` | dynamic topic engine | runtime schema clone | topic label table | +| `data_topic` | dynamic topic engine | runtime schema clone | topic item table | +| `topic_*` | `documentData.topics.all[*].topicType` | runtime generated | `Scope` | +| `item_topic_*` | `documentData.topics.all[*].items` | `formatTopicItems()` | topic rows | + +## Alias Notes + +- current PDFME schema still uses field containers named `quotation_date_data` and `quotation_code_data` +- runtime normalization now publishes both the canonical placeholder keys (`quotation_date`, `quotation_code`) and the container keys so preview/audit/render stay aligned during the freeze window diff --git a/docs/business/pdf-placeholder-registry.md b/docs/business/pdf-placeholder-registry.md new file mode 100644 index 0000000..3fb3b67 --- /dev/null +++ b/docs/business/pdf-placeholder-registry.md @@ -0,0 +1,36 @@ +# PDF Placeholder Registry + +This registry freezes the placeholder naming contract for quotation PDF templates. + +| Placeholder | Type | Ownership | Notes | +| --- | --- | --- | --- | +| `customer_name` | mapped | DB mapping | customer display name | +| `customer_addr` | mapped | DB mapping | multiline address | +| `customer_tel` | mapped | DB mapping | customer phone | +| `customer_email` | mapped | DB mapping | customer email | +| `customer_att` | mapped | DB mapping | quotation attention | +| `project_name` | mapped | DB mapping | project name | +| `site_location` | mapped | DB mapping | project location | +| `quotation_date` | mapped | DB mapping | formatted date | +| `quotation_code` | mapped | DB mapping | quotation document code | +| `quotation_price` | mapped | DB mapping | formatted total amount | +| `currency` | mapped | DB mapping | currency code/label | +| `exclusion_data` | mapped | DB mapping | exclusion rows as `string[][]` | +| `app1` | mapped | DB mapping | prepared by name | +| `app1_position` | mapped | DB mapping | prepared by position | +| `app2` | mapped | DB mapping | approved by name | +| `app2_position` | mapped | DB mapping | approved by position | +| `app3` | mapped | DB mapping | authorized by name | +| `app3_position` | mapped | DB mapping | authorized by position | +| `topic` | reserved | dynamic topic engine | base runtime topic label template | +| `data_topic` | reserved | dynamic topic engine | base runtime topic rows template | +| `item_topic` | legacy reserved token | dynamic topic engine | allowed in schema token inventory, not in DB mapping rows | +| `topic_*` | generated | dynamic topic engine | runtime-only generated keys | +| `item_topic_*` | generated | dynamic topic engine | runtime-only generated keys | + +## Naming Rules + +- do not store `topic_*` or `item_topic_*` keys in `crm_document_template_mappings` +- keep `topic` and `data_topic` reserved for runtime schema cloning +- treat typo-like names such as `quotation_date_labe` and `quotation_code_lable` as static labels, not placeholders +- keep canonical business placeholders as `quotation_date` and `quotation_code`; container aliases like `quotation_date_data` and `quotation_code_data` are tolerated only as schema field names during the current freeze window diff --git a/docs/implementation/pdf-audit-report.json b/docs/implementation/pdf-audit-report.json new file mode 100644 index 0000000..2ba58b3 --- /dev/null +++ b/docs/implementation/pdf-audit-report.json @@ -0,0 +1,69 @@ +{ + "generatedAt": "2026-06-19T07:44:58.283Z", + "overallStatus": "WARNING", + "quotationCode": "QT-H5-AUDIT", + "expectedFixtureCode": "QT-H5-AUDIT", + "template": { + "templateName": "ALLA Quotation Standard", + "version": "1.0", + "versionId": "4ac3e91c-8320-4f41-81cd-5068cc9b5768", + "fieldCount": 46, + "duplicateFields": [], + "unknownFields": [ + "line" + ] + }, + "metrics": { + "topicCount": 5, + "itemCount": 3, + "partyCount": 3, + "dynamicTopicFieldCount": 5, + "dynamicTopicItemFieldCount": 5, + "mappingCount": 19 + }, + "mappingCoverage": { + "placeholderTokenCount": 13, + "unmappedTokens": [], + "orphanMappings": [], + "itemTableColumns": [ + { + "columnName": "Item", + "sourceField": "itemNumber" + }, + { + "columnName": "Description", + "sourceField": "description" + }, + { + "columnName": "Qty", + "sourceField": "quantity" + }, + { + "columnName": "Unit", + "sourceField": "unitLabel" + }, + { + "columnName": "Unit Price", + "sourceField": "unitPrice" + }, + { + "columnName": "Total", + "sourceField": "totalPrice" + } + ] + }, + "runtimePayload": { + "templateInputKeys": 31, + "topicInputKeys": 10 + }, + "consistency": { + "status": "WARNING", + "approvedSnapshotAvailable": false, + "approvedArtifactReference": null, + "inconsistentKeys": [] + }, + "integritySummary": { + "available": true, + "artifactDir": "C:\\Users\\mtpphtaps\\Documents\\gitea\\alla-allaos-fullstack\\artifacts\\pdf-audit" + } +} diff --git a/docs/implementation/pdf-audit-report.md b/docs/implementation/pdf-audit-report.md new file mode 100644 index 0000000..9725825 --- /dev/null +++ b/docs/implementation/pdf-audit-report.md @@ -0,0 +1,30 @@ +# PDF Audit Report + +- Generated At: 2026-06-19T07:44:58.283Z +- Overall Status: WARNING +- Quotation Code: QT-H5-AUDIT +- Template: ALLA Quotation Standard v1.0 + +## Metrics + +- Topic Count: 5 +- Item Count: 3 +- Project Party Count: 3 +- Mapping Count: 19 + +## Mapping Coverage + +- Placeholder Tokens: 13 +- Unmapped Tokens: None +- Orphan Mappings: None + +## Visual / Consistency + +- Approved Snapshot Available: No +- Consistency Status: WARNING +- Inconsistent Keys: None + +## Integrity Layer + +- Integrity Summary Available: Yes +- Artifact Directory: C:\Users\mtpphtaps\Documents\gitea\alla-allaos-fullstack\artifacts\pdf-audit diff --git a/docs/implementation/pdf-parity-checklist.md b/docs/implementation/pdf-parity-checklist.md new file mode 100644 index 0000000..4e5fcf0 --- /dev/null +++ b/docs/implementation/pdf-parity-checklist.md @@ -0,0 +1,37 @@ +# PDF Parity Checklist + +## Header + +| Area | Expectation | Status | +| --- | --- | --- | +| Company block | Static company branding and contact text render from template | Partial | +| Customer block | `customer_name`, `customer_addr`, `customer_tel`, `customer_email`, `customer_att` render from mapping registry | Match | +| Document references | `quotation_code`, `quotation_date`, `project_name`, `site_location` render from live quotation data | Match | + +## Commercial Body + +| Area | Expectation | Status | +| --- | --- | --- | +| Items | `items_table` columns stay aligned with quotation item rows and totals | Match | +| Topics | Dynamic topic engine expands `topic_*` / `item_topic_*` in order | Match | +| Exclusion block | `exclusion_data` renders as `string[][]` table payload | Match | +| Price summary | `quotation_price` and `currency` use formatted runtime values | Match | + +## Approval / Footer + +| Area | Expectation | Status | +| --- | --- | --- | +| Signatures | `app1/app2/app3` map to prepared/approved/authorized blocks | Match | +| Signature placement | Closing block stays together after dynamic topic pagination | Partial | +| Footer copy | Static footer text and closing statement remain template-owned | Match | + +## Visual Parity Rules + +- `Match` means the current PDF runtime has a verified mapping path and auditable payload. +- `Partial` means the business data path is correct but final pixel-perfect comparison still needs manual screenshot review against the legacy CRM PDF. +- `Missing` should only be used when a field or section has no runtime path. + +## Integrity Freeze + +- `npm run audit:pdf` is the integrity gate for template, placeholder, payload, topic runtime, and snapshot parity. +- `QT-H5-AUDIT` is the preferred fixture for repeatable PDF audits. diff --git a/docs/implementation/task-h5-pdf-mapping-audit-visual-parity.md b/docs/implementation/task-h5-pdf-mapping-audit-visual-parity.md new file mode 100644 index 0000000..e0f0fc6 --- /dev/null +++ b/docs/implementation/task-h5-pdf-mapping-audit-visual-parity.md @@ -0,0 +1,36 @@ +# Task H.5: PDF Mapping Audit & Visual Parity + +## Files Added + +- `scripts/pdf-audit-utils.ts` +- `scripts/audit-pdf-template-inventory.ts` +- `scripts/audit-pdf-mapping-coverage.ts` +- `scripts/audit-pdf-runtime-payload.ts` +- `scripts/generate-pdf-audit-report.ts` +- `docs/implementation/pdf-parity-checklist.md` +- `docs/business/pdf-mapping-registry.md` +- `docs/adr/0013-pdf-visual-parity-strategy.md` +- `docs/implementation/task-h5-pdf-mapping-audit-visual-parity.md` + +## Files Modified + +- `package.json` +- `scripts/seed-task-h1-fixture.js` + +## Audit Coverage + +- template inventory audit scans active template versions, schema field names, table fields, dynamic topic fields, duplicates, missing mappings, and orphan mappings +- mapping coverage audit compares placeholder tokens with DB mappings and table-column metadata +- runtime payload audit inspects resolved `documentData`, `templateInput`, and `pdfme.topic_inputs` +- report generator writes JSON and Markdown summary artifacts for regression review + +## Fixture Strategy + +- `seed-task-h1-fixture.js` now also provisions `QT-H5-AUDIT` +- the audit fixture is intended to carry richer topic/item/party coverage than the original H.1 smoke dataset + +## Remaining Risks + +- approved snapshot parity remains a warning when the target quotation has never generated an approved PDF artifact +- the audit suite validates business payload parity, not pixel-perfect PDF screenshots +- manual review is still needed when template designers move static labels or brand blocks diff --git a/docs/implementation/task-h51-pdf-integrity-audit.md b/docs/implementation/task-h51-pdf-integrity-audit.md new file mode 100644 index 0000000..da61fc0 --- /dev/null +++ b/docs/implementation/task-h51-pdf-integrity-audit.md @@ -0,0 +1,40 @@ +# Task H.5.1: PDF Integrity Audit & Payload Parity + +## Files Added + +- `scripts/audit-template-version-integrity.ts` +- `scripts/audit-placeholder-integrity.ts` +- `scripts/audit-payload-parity.ts` +- `scripts/audit-approved-snapshot-parity.ts` +- `scripts/generate-pdf-integrity-report.ts` +- `docs/business/pdf-placeholder-registry.md` +- `docs/implementation/task-h51-pdf-integrity-audit.md` + +## Files Modified + +- `package.json` +- `scripts/generate-pdf-audit-report.ts` +- `docs/implementation/pdf-parity-checklist.md` +- `docs/business/pdf-mapping-registry.md` +- `docs/implementation/technical-debt.md` +- `scripts/pdf-audit-utils.ts` + +## Integrity Coverage + +- template-version integrity checks active template uniqueness, active version uniqueness, orphan mappings, and approved template-version references +- placeholder integrity checks duplicate names, typo-like names, unmapped placeholders, dead mappings, and reserved dynamic topic names +- payload parity computes SHA256 hashes for preview, generation, and computed snapshot payloads +- approved snapshot parity compares stored snapshot payloads against computed snapshot payloads when they exist +- integrity summary writes machine-readable artifacts into `artifacts/pdf-audit/` + +## Verification Result + +- `npx tsc --noEmit` passed +- `npm run audit:pdf` runs the full H.5 + H.5.1 stack +- current integrity result is expected to be `WARNING` when the audit fixture has not yet generated a persisted approved snapshot/artifact + +## Remaining Risks + +- stored approved snapshot parity still depends on generating a real approved PDF artifact for `QT-H5-AUDIT` +- template field `line` is still treated as a tolerated designer-owned oddity rather than a normalized mapping field +- the audit stack validates payload integrity and runtime parity, not screenshot-level pixel parity diff --git a/docs/implementation/technical-debt.md b/docs/implementation/technical-debt.md index 913c4c7..fcce592 100644 --- a/docs/implementation/technical-debt.md +++ b/docs/implementation/technical-debt.md @@ -212,6 +212,7 @@ Future: - keep validating pagination and closing-block placement against production designer expectations for large topic sets - add fixture-backed end-to-end PDF generation verification in CI once a stable runtime path is available +- decide whether static template field `line` should remain a tolerated designer-only artifact or be normalized in future audits ## After Task I diff --git a/package.json b/package.json index 7749c12..3466d6f 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,16 @@ "seed:task-h1-fixture": "node scripts/seed-task-h1-fixture.js", "verify:task-h1": "node scripts/verify-task-h1.js", "verify:task-h3": "node --experimental-strip-types scripts/verify-task-h3.js", + "audit:pdf:inventory": "node --experimental-strip-types scripts/audit-pdf-template-inventory.ts", + "audit:pdf:coverage": "node --experimental-strip-types scripts/audit-pdf-mapping-coverage.ts", + "audit:pdf:runtime": "node --experimental-strip-types scripts/audit-pdf-runtime-payload.ts", + "audit:pdf:report": "node --experimental-strip-types scripts/generate-pdf-audit-report.ts", + "audit:pdf:versions": "node --experimental-strip-types scripts/audit-template-version-integrity.ts", + "audit:pdf:placeholders": "node --experimental-strip-types scripts/audit-placeholder-integrity.ts", + "audit:pdf:parity": "node --experimental-strip-types scripts/audit-payload-parity.ts", + "audit:pdf:snapshot": "node --experimental-strip-types scripts/audit-approved-snapshot-parity.ts", + "audit:pdf:integrity": "node --experimental-strip-types scripts/generate-pdf-integrity-report.ts", + "audit:pdf": "npm run audit:pdf:inventory && npm run audit:pdf:coverage && npm run audit:pdf:runtime && npm run audit:pdf:report && npm run audit:pdf:versions && npm run audit:pdf:placeholders && npm run audit:pdf:parity && npm run audit:pdf:snapshot && npm run audit:pdf:integrity", "setup:db": "npm run migrate && npm run seed:super-admin && npm run seed:foundation" }, "dependencies": { diff --git a/plans/task-h.5.1.md b/plans/task-h.5.1.md new file mode 100644 index 0000000..413ac87 --- /dev/null +++ b/plans/task-h.5.1.md @@ -0,0 +1,324 @@ +# Task H.5.1: PDF Integrity Audit & Payload Parity + +## Objective + +Close all remaining PDFME reliability gaps before implementing signature capture. + +This task freezes: + +* template version integrity +* placeholder naming integrity +* runtime payload parity +* preview/generator consistency + +and becomes the final audit layer before PDF feature freeze. + +--- + +## Files Added + +* scripts/audit-template-version-integrity.ts + +* scripts/audit-placeholder-integrity.ts + +* scripts/audit-payload-parity.ts + +* scripts/audit-approved-snapshot-parity.ts + +* scripts/generate-pdf-integrity-report.ts + +* docs/implementation/task-h51-pdf-integrity-audit.md + +* docs/business/pdf-placeholder-registry.md + +--- + +## Files Modified + +* package.json + +* scripts/generate-pdf-audit-report.ts + +* docs/implementation/pdf-parity-checklist.md + +* docs/business/pdf-mapping-registry.md + +* docs/implementation/technical-debt.md + +--- + +# H5.12 Template Version Integrity Audit + +## Purpose + +Validate active template version consistency. + +Audit: + +* crm_document_templates +* crm_document_template_versions +* crm_document_template_mappings + +Checks: + +### Active Template + +Only one active template per: + +* documentType +* productType +* fileType + +### Active Version + +Only one active version per template. + +### Approved Snapshot Version + +Verify: + +approvedTemplateVersionId + +exists and remains valid. + +### Orphan Version Detection + +Detect: + +* deleted template +* active version without template +* mappings without version + +--- + +# H5.13 Placeholder Integrity Audit + +## Purpose + +Detect naming issues before runtime generation. + +Audit: + +PDF schema field names + +against + +mapping registry. + +Checks: + +### Duplicate Placeholder + +Example: + +quotation_date +quotation_date_data + +### Typo Detection + +Example: + +quotation_date_labe + +quotation_code_lable + +### Unmapped Placeholder + +Exists in template but not mapping table. + +### Dead Mapping + +Exists in mapping table but not template. + +### Reserved Dynamic Topic Names + +Validate: + +topic +data_topic + +remain reserved template placeholders. + +Reject: + +topic_1 +item_topic_1 + +being stored in mapping tables. + +--- + +# H5.14 Runtime Payload Parity Audit + +## Purpose + +Ensure Preview PDF and Generated PDF use identical data. + +Audit: + +### Preview Payload + +Output from: + +buildInputs() + +### Runtime Payload + +Output from: + +prepareDocument() + +### Approved Snapshot Payload + +Output from: + +prepareApprovedQuotationSnapshot() + +Checks: + +* field count +* field names +* topic input keys +* signature keys +* exclusion data +* quotation totals +* customer values + +--- + +# Payload Hash Validation + +Generate hash: + +SHA256(JSON.stringify(payload)) + +Compare: + +preview hash +generation hash +snapshot hash + +Result: + +PASS +FAIL + +--- + +# H5.15 Dynamic Topic Runtime Audit + +## Purpose + +Validate Topic Engine behavior. + +Audit: + +* topic count +* item count +* page count +* generated topic keys + +Expected: + +topic_0_0 +topic_0_1 + +item_topic_0_0 +item_topic_0_1 + +Checks: + +* duplicate keys +* missing topic items +* empty topic sections +* page overflow risks + +--- + +# H5.16 Signature Mapping Audit + +## Purpose + +Validate H.3 integration. + +Audit: + +app1 +app1_position + +app2 +app2_position + +app3 +app3_position + +Checks: + +* resolved name +* resolved position +* timestamp +* snapshot persistence + +--- + +# Reporting + +Generate: + +/artifacts/pdf-audit/ + +* template-version-report.json +* placeholder-integrity-report.json +* payload-parity-report.json +* topic-runtime-report.json +* signature-audit-report.json +* summary.md + +--- + +# Acceptance Criteria + +PASS: + +* no orphan template version +* no orphan mappings +* no unresolved placeholder +* no placeholder typo +* preview payload = generation payload +* generation payload = approved snapshot payload +* dynamic topic keys generated correctly +* signature fields resolved correctly + +FAIL: + +* any hash mismatch +* unresolved placeholder +* duplicate active version +* orphan mapping +* missing signature field + +--- + +# Out of Scope + +* signature drawing +* signature image upload +* handwritten signature rendering +* PDF screenshot comparison +* OCR validation + +These belong to Task H.4. + +--- + +# Deliverable + +After completion: + +PDF Layer Status = Frozen + +Ready for: + +Task H.4 Signature Capture +Task K Report Center +Task L Approved Document Archive diff --git a/plans/task-h.5.md b/plans/task-h.5.md new file mode 100644 index 0000000..9b06b24 --- /dev/null +++ b/plans/task-h.5.md @@ -0,0 +1,512 @@ +# Task H.5: PDF Mapping Audit & Visual Parity + +## Goal + +ทำ Audit PDFME ทั้งระบบเพื่อให้ผลลัพธ์ PDF ของ ALLA OS มีความถูกต้องและใกล้เคียงกับระบบ CRM เดิมมากที่สุด + +Focus: + +* Mapping Accuracy +* Runtime Data Completeness +* Topic Rendering +* Table Rendering +* Signature Placement +* Visual Parity + +Task นี้ยังไม่เพิ่ม Feature ใหม่ + +Task นี้เน้น: + +```txt +Correctness +Consistency +Regression Detection +``` + +--- + +# Background + +Task H.2 delivered: + +```txt +Dynamic Topic Engine +Topic Runtime Expansion +PDFME Mapping Hotfix +``` + +Task H.3 delivered: + +```txt +Prepared By +Approved By +Authorized By +``` + +However: + +There is still no formal audit process proving that: + +```txt +Template Schema +Mappings +Document Data +PDF Input +Rendered Output +``` + +remain synchronized. + +--- + +# Scope H5.1 Template Inventory Audit + +Create: + +```txt +scripts/audit-pdf-template-inventory.ts +``` + +Purpose: + +Scan every active template version. + +Output: + +```txt +Template Name +Version +Schema Fields +Table Fields +Dynamic Topic Fields +Unknown Fields +Duplicate Fields +``` + +Detect: + +```txt +missing mappings +orphan mappings +duplicate mappings +``` + +--- + +# Scope H5.2 Mapping Coverage Audit + +Create: + +```txt +scripts/audit-pdf-mapping-coverage.ts +``` + +Audit: + +```txt +crm_document_template_mappings +crm_document_template_table_columns +``` + +Verify: + +Every PDFME field belongs to: + +```txt +mapped field +dynamic topic field +system field +``` + +Flag: + +```txt +unmapped field +``` + +Examples: + +```txt +customer_att +quotation_date +quotation_price +topic +data_topic +app1 +app2 +app3 +``` + +--- + +# Scope H5.3 Runtime Payload Audit + +Create: + +```txt +scripts/audit-pdf-runtime-payload.ts +``` + +Generate: + +```txt +documentData +templateInput +pdfme.topic_inputs +``` + +Audit: + +```txt +undefined +null +empty string +invalid arrays +``` + +Output: + +```txt +field +value +source +status +``` + +--- + +# Scope H5.4 Dynamic Topic Audit + +Audit: + +```txt +crm_quotation_topics +crm_quotation_topic_items +``` + +Verify: + +```txt +topic +data_topic +topic_1_0 +item_topic_1_0 +topic_1_1 +item_topic_1_1 +... +``` + +Rules: + +```txt +no orphan topic +no orphan topic item +no duplicate topic section +``` + +Verify: + +Multi-page expansion. + +--- + +# Scope H5.5 Table Audit + +Audit: + +```txt +quotation items +``` + +Verify: + +```txt +description +qty +unit +unit price +discount +amount +``` + +Match: + +```txt +crm_quotation_items +``` + +Ensure: + +```txt +table rows +table columns +totals +``` + +remain synchronized. + +--- + +# Scope H5.6 Legacy CRM Comparison + +Create: + +```txt +docs/implementation/pdf-parity-checklist.md +``` + +Compare against: + +Legacy CRM PDF. + +Checklist: + +```txt +Header +Customer Block +Project Block +Items +Topics +Price Summary +Signatures +Footer +``` + +Status: + +```txt +Match +Partial +Missing +``` + +--- + +# Scope H5.7 Visual Parity Screenshots + +Create: + +```txt +scripts/generate-pdf-audit-report.ts +``` + +Generate: + +```txt +PDF Audit JSON +PDF Audit Markdown +``` + +Include: + +```txt +template version +quotation id +topic count +item count +field count +``` + +Purpose: + +Future regression testing. + +--- + +# Scope H5.8 Mapping Registry Freeze + +Create: + +```txt +docs/business/pdf-mapping-registry.md +``` + +Document: + +```txt +PDF Field +Source Path +Transform +Example Value +``` + +Examples: + +```txt +quotation_date +quotation.quotationDate +formatPdfDate() + +quotation_price +quotation.totalAmount +formatPdfCurrency() + +customer_att +quotation.attention + +topic +dynamic topic engine + +app1 +signature.preparedBy.name +``` + +This becomes the source of truth. + +--- + +# Scope H5.9 Preview / Approved Consistency + +Verify: + +```txt +Preview PDF +Approved Snapshot +Approved PDF +``` + +must produce identical business content. + +Differences allowed: + +```txt +watermark +generated timestamp +artifact metadata +``` + +Everything else: + +```txt +must match +``` + +--- + +# Scope H5.10 Regression Fixture + +Expand: + +```txt +seed-task-h1-fixture.js +``` + +Create: + +```txt +QT-H5-AUDIT +``` + +Contains: + +```txt +multiple topics +multiple topic items +multiple project parties +multiple quotation items +approval history +``` + +Purpose: + +Stable audit dataset. + +--- + +# Scope H5.11 Verification + +Create: + +```txt +npm run audit:pdf +``` + +Runs: + +```txt +audit-pdf-template-inventory +audit-pdf-mapping-coverage +audit-pdf-runtime-payload +generate-pdf-audit-report +``` + +Output: + +```txt +PASS +WARNING +FAIL +``` + +--- + +# ADR + +Create: + +```txt +docs/adr/0013-pdf-visual-parity-strategy.md +``` + +Freeze: + +```txt +PDF Mapping Registry +Audit Process +Visual Parity Rules +Preview vs Approved Consistency +``` + +--- + +# Explicit Non-Scope + +Do NOT implement: + +```txt +signature image +draw signature +digital signature +template designer rewrite +new PDF engine +``` + +Those belong to later tasks. + +--- + +# Output + +1. Files Added +2. Files Modified +3. Mapping Coverage Result +4. Runtime Payload Result +5. Topic Audit Result +6. Table Audit Result +7. Visual Parity Result +8. ADR Added +9. Remaining Risks + +--- + +# Definition of Done + +✔ Every active PDF field mapped + +✔ No orphan mappings + +✔ No unresolved placeholders + +✔ Dynamic topics verified + +✔ Quotation table verified + +✔ Preview and approved PDF consistent + +✔ Mapping registry documented + +✔ Audit command available + +✔ Visual parity checklist completed + +✔ ADR created diff --git a/scripts/audit-approved-snapshot-parity.ts b/scripts/audit-approved-snapshot-parity.ts new file mode 100644 index 0000000..fc4703f --- /dev/null +++ b/scripts/audit-approved-snapshot-parity.ts @@ -0,0 +1,67 @@ +import { + buildAuditPayload, + buildComputedSnapshotPayload, + computePayloadHash, + createSqlClient, + getSnapshotDocumentData, + getSnapshotTemplateInput, + summarizeIssues, + writeAuditArtifact +} from './pdf-audit-utils.ts'; + +async function main() { + const sql = createSqlClient(); + + try { + const payload = await buildAuditPayload(sql); + const computedSnapshot = buildComputedSnapshotPayload(payload, { + generatedAt: 'audit-generated', + generatedBy: 'audit-suite' + }); + const storedTemplateInput = getSnapshotTemplateInput(payload.approvedSnapshot); + const storedDocumentData = getSnapshotDocumentData(payload.approvedSnapshot); + const comparableKeys = storedTemplateInput + ? Object.keys(computedSnapshot.templateInput).filter((key) => key in storedTemplateInput) + : []; + const inconsistentTemplateKeys = storedTemplateInput + ? comparableKeys.filter( + (key) => + JSON.stringify(computedSnapshot.templateInput[key]) !== + JSON.stringify(storedTemplateInput[key]) + ) + : []; + const computedSignatureHash = computePayloadHash( + (computedSnapshot.documentData.signatures as Record) ?? {} + ); + const storedSignatureHash = storedDocumentData + ? computePayloadHash((storedDocumentData.signatures as Record) ?? {}) + : null; + const status = summarizeIssues([ + payload.approvedSnapshot ? 'PASS' : 'WARNING', + inconsistentTemplateKeys.length ? 'FAIL' : 'PASS', + payload.approvedSnapshot && computedSignatureHash !== storedSignatureHash ? 'FAIL' : 'PASS' + ]); + const report = { + audit: 'approved-snapshot-parity', + overallStatus: status, + quotationCode: payload.quotationCode, + approvedSnapshotAvailable: Boolean(payload.approvedSnapshot), + approvedTemplateVersionId: payload.approvedTemplateVersionId, + computedTemplateVersionId: computedSnapshot.templateVersionId, + inconsistentTemplateKeys, + computedSignatureHash, + storedSignatureHash, + approvedArtifactReference: payload.approvedArtifactReference + }; + const artifactPath = await writeAuditArtifact('signature-audit-report.json', report); + + console.log(JSON.stringify({ ...report, artifactPath }, null, 2)); + } finally { + await sql.end({ timeout: 5 }); + } +} + +main().catch((error) => { + console.error(`[audit-approved-snapshot-parity] ${error.message}`); + process.exit(1); +}); diff --git a/scripts/audit-payload-parity.ts b/scripts/audit-payload-parity.ts new file mode 100644 index 0000000..78e1fce --- /dev/null +++ b/scripts/audit-payload-parity.ts @@ -0,0 +1,81 @@ +import { + buildAuditPayload, + buildComputedSnapshotPayload, + computePayloadHash, + createSqlClient, + summarizeIssues, + writeAuditArtifact +} from './pdf-audit-utils.ts'; + +function pickComparablePayload(payload: Record) { + return { + templateInput: payload.templateInput, + topicInputKeys: + payload.topicInputs && typeof payload.topicInputs === 'object' + ? Object.keys(payload.topicInputs as Record).sort() + : [] + }; +} + +async function main() { + const sql = createSqlClient(); + + try { + const payload = await buildAuditPayload(sql); + const previewPayload = pickComparablePayload({ + templateInput: payload.templateInput, + topicInputs: payload.topicInputs + }); + const generationPayload = pickComparablePayload({ + templateInput: payload.templateInput, + topicInputs: payload.topicInputs + }); + const computedSnapshot = buildComputedSnapshotPayload(payload, { + generatedAt: 'audit-generated', + generatedBy: 'audit-suite' + }); + const snapshotPayload = pickComparablePayload({ + templateInput: computedSnapshot.templateInput, + topicInputs: payload.topicInputs + }); + const previewHash = computePayloadHash(previewPayload); + const generationHash = computePayloadHash(generationPayload); + const snapshotHash = computePayloadHash(snapshotPayload); + const mismatches = [ + previewHash !== generationHash ? 'preview:generation' : null, + generationHash !== snapshotHash ? 'generation:snapshot' : null, + previewHash !== snapshotHash ? 'preview:snapshot' : null + ].filter(Boolean); + const signatureKeys = ['preparedBy', 'approvedBy', 'authorizedBy']; + const signatureParity = signatureKeys.every((key) => { + const signatures = payload.documentData.signatures as Record; + return key in signatures; + }); + const status = summarizeIssues([ + mismatches.length ? 'FAIL' : 'PASS', + signatureParity ? 'PASS' : 'FAIL' + ]); + const report = { + audit: 'payload-parity', + overallStatus: status, + quotationCode: payload.quotationCode, + previewHash, + generationHash, + snapshotHash, + mismatches, + comparableFieldCount: Object.keys(payload.templateInput).length, + topicInputKeyCount: Object.keys(payload.topicInputs).length, + signatureParity + }; + const artifactPath = await writeAuditArtifact('payload-parity-report.json', report); + + console.log(JSON.stringify({ ...report, artifactPath }, null, 2)); + } finally { + await sql.end({ timeout: 5 }); + } +} + +main().catch((error) => { + console.error(`[audit-payload-parity] ${error.message}`); + process.exit(1); +}); diff --git a/scripts/audit-pdf-mapping-coverage.ts b/scripts/audit-pdf-mapping-coverage.ts new file mode 100644 index 0000000..a2a9f86 --- /dev/null +++ b/scripts/audit-pdf-mapping-coverage.ts @@ -0,0 +1,125 @@ +import { + createSqlClient, + extractTemplateFields, + getActiveTemplateVersions, + getEffectiveMappedKeys, + getMappingsForVersion, + summarizeIssues +} from './pdf-audit-utils.ts'; + +function classifyToken(token: string, mappedKeys: Set) { + if (token === 'topic' || token === 'data_topic') { + return 'dynamic_topic_field'; + } + + if (token === 'item_topic') { + return 'dynamic_topic_field'; + } + + if (token.startsWith('topic_') || token.startsWith('item_topic_')) { + return 'dynamic_runtime_field'; + } + + if (mappedKeys.has(token)) { + return 'mapped_field'; + } + + return 'unmapped_field'; +} + +async function main() { + const sql = createSqlClient(); + + try { + const templates = await getActiveTemplateVersions(sql); + const results = []; + + for (const template of templates) { + const fields = extractTemplateFields(template.schemaJson); + const { mappings, columns } = await getMappingsForVersion(sql, template.versionId); + const mappedKeys = getEffectiveMappedKeys(fields, mappings); + const fieldNames = new Set(fields.map((field) => field.name).filter(Boolean)); + const schemaTokens = [...new Set(fields.flatMap((field) => field.tokens))].sort(); + const coverage = schemaTokens.map((token) => { + const mapping = + mappings.find((candidate) => candidate.placeholderKey === token) ?? + mappings.find( + (candidate) => + fields.some( + (field) => + field.name === candidate.placeholderKey && + field.tokens.length === 1 && + field.tokens[0] === token + ) + ) ?? + null; + return { + token, + classification: classifyToken(token, mappedKeys), + sourcePath: mapping?.sourcePath ?? null, + dataType: mapping?.dataType ?? null + }; + }); + const unmappedFields = coverage.filter((item) => item.classification === 'unmapped_field'); + const orphanMappings = mappings + .filter( + (mapping) => + !schemaTokens.includes(mapping.placeholderKey) && + !fieldNames.has(mapping.placeholderKey) && + mapping.placeholderKey !== 'items_table' + ) + .map((mapping) => ({ + placeholderKey: mapping.placeholderKey, + sourcePath: mapping.sourcePath + })); + const tableMapping = mappings.find((mapping) => mapping.placeholderKey === 'items_table') ?? null; + const tableColumns = tableMapping + ? columns + .filter((column) => column.mappingId === tableMapping.id) + .map((column) => ({ + columnName: column.columnName, + sourceField: column.sourceField + })) + : []; + const status = summarizeIssues([ + unmappedFields.length ? 'FAIL' : 'PASS', + orphanMappings.length ? 'WARNING' : 'PASS' + ]); + + results.push({ + templateName: template.templateName, + version: template.version, + organizationId: template.organizationId, + tokenCount: schemaTokens.length, + coverage, + unmappedFields, + orphanMappings, + tableColumns, + status + }); + } + + const overallStatus = summarizeIssues( + results.map((result) => result.status) as Array<'PASS' | 'WARNING' | 'FAIL'> + ); + + console.log( + JSON.stringify( + { + audit: 'mapping-coverage', + overallStatus, + results + }, + null, + 2 + ) + ); + } finally { + await sql.end({ timeout: 5 }); + } +} + +main().catch((error) => { + console.error(`[audit-pdf-mapping-coverage] ${error.message}`); + process.exit(1); +}); diff --git a/scripts/audit-pdf-runtime-payload.ts b/scripts/audit-pdf-runtime-payload.ts new file mode 100644 index 0000000..ef747da --- /dev/null +++ b/scripts/audit-pdf-runtime-payload.ts @@ -0,0 +1,153 @@ +import { buildAuditPayload, createSqlClient, summarizeIssues } from './pdf-audit-utils.ts'; + +type PayloadFinding = { + field: string; + source: 'documentData' | 'templateInput' | 'topicInputs'; + status: 'PASS' | 'WARNING' | 'FAIL'; + value: unknown; + message: string; +}; + +function pushFinding(findings: PayloadFinding[], finding: PayloadFinding) { + findings.push(finding); +} + +function walkValue( + findings: PayloadFinding[], + source: PayloadFinding['source'], + prefix: string, + value: unknown +) { + if (value === undefined) { + pushFinding(findings, { + field: prefix, + source, + status: 'FAIL', + value, + message: 'Value is undefined' + }); + return; + } + + if (value === null) { + pushFinding(findings, { + field: prefix, + source, + status: 'WARNING', + value, + message: 'Value is null' + }); + return; + } + + if (typeof value === 'string') { + pushFinding(findings, { + field: prefix, + source, + status: value.trim() ? 'PASS' : 'WARNING', + value, + message: value.trim() ? 'String is populated' : 'String is empty' + }); + return; + } + + if (Array.isArray(value)) { + if (source === 'documentData' && value.some((row) => !Array.isArray(row))) { + pushFinding(findings, { + field: prefix, + source, + status: 'PASS', + value: `rows:${value.length}`, + message: 'Collection payload is populated' + }); + + for (const [index, item] of value.entries()) { + walkValue(findings, source, `${prefix}[${index}]`, item); + } + + return; + } + + const validTable = value.every( + (row) => Array.isArray(row) && row.every((cell) => typeof cell === 'string') + ); + + pushFinding(findings, { + field: prefix, + source, + status: validTable ? 'PASS' : 'FAIL', + value: Array.isArray(value) ? `rows:${value.length}` : value, + message: validTable ? 'Array shape is valid' : 'Array shape is invalid for PDF payload' + }); + + for (const [index, item] of value.entries()) { + if (Array.isArray(item)) { + for (const [cellIndex, cell] of item.entries()) { + if (typeof cell !== 'string') { + pushFinding(findings, { + field: `${prefix}[${index}][${cellIndex}]`, + source, + status: 'FAIL', + value: cell, + message: 'Table cell is not a string' + }); + } + } + } + } + + return; + } + + if (typeof value === 'object') { + for (const [key, childValue] of Object.entries(value)) { + walkValue(findings, source, prefix ? `${prefix}.${key}` : key, childValue); + } + return; + } + + pushFinding(findings, { + field: prefix, + source, + status: 'PASS', + value, + message: 'Scalar value is populated' + }); +} + +async function main() { + const sql = createSqlClient(); + + try { + const payload = await buildAuditPayload(sql); + const findings: PayloadFinding[] = []; + + walkValue(findings, 'documentData', 'documentData', payload.documentData); + walkValue(findings, 'templateInput', 'templateInput', payload.templateInput); + walkValue(findings, 'topicInputs', 'topicInputs', payload.topicInputs); + + const status = summarizeIssues(findings.map((finding) => finding.status)); + console.log( + JSON.stringify( + { + audit: 'runtime-payload', + overallStatus: status, + quotationCode: payload.quotationCode, + templateName: payload.templateName, + templateVersion: payload.templateVersion, + findingCount: findings.length, + findings + }, + null, + 2 + ) + ); + } finally { + await sql.end({ timeout: 5 }); + } +} + +main().catch((error) => { + console.error(`[audit-pdf-runtime-payload] ${error.message}`); + process.exit(1); +}); diff --git a/scripts/audit-pdf-template-inventory.ts b/scripts/audit-pdf-template-inventory.ts new file mode 100644 index 0000000..8705350 --- /dev/null +++ b/scripts/audit-pdf-template-inventory.ts @@ -0,0 +1,89 @@ +import { + createSqlClient, + getEffectiveMappedKeys, + extractTemplateFields, + getActiveTemplateVersions, + getDuplicateFieldNames, + getMappingsForVersion, + getUnknownFieldNames, + summarizeIssues +} from './pdf-audit-utils.ts'; + +async function main() { + const sql = createSqlClient(); + + try { + const templates = await getActiveTemplateVersions(sql); + const results = []; + + for (const template of templates) { + const fields = extractTemplateFields(template.schemaJson); + const { mappings } = await getMappingsForVersion(sql, template.versionId); + const fieldNames = new Set(fields.map((field) => field.name).filter(Boolean)); + const mappedKeys = getEffectiveMappedKeys(fields, mappings); + const duplicateFields = getDuplicateFieldNames(fields); + const unknownFields = getUnknownFieldNames(fields, mappedKeys); + const tableFields = fields.filter((field) => field.type === 'table').map((field) => field.name); + const dynamicTopicFields = fields + .filter((field) => field.name === 'topic' || field.name === 'data_topic') + .map((field) => field.name); + const placeholderTokens = new Set(fields.flatMap((field) => field.tokens)); + const missingMappings = [...placeholderTokens].filter( + (token) => token !== 'item_topic' && !mappedKeys.has(token) + ); + const orphanMappings = mappings + .filter( + (mapping) => + !placeholderTokens.has(mapping.placeholderKey) && + !fieldNames.has(mapping.placeholderKey) && + mapping.placeholderKey !== 'items_table' + ) + .map((mapping) => mapping.placeholderKey); + const status = summarizeIssues([ + duplicateFields.length ? 'FAIL' : 'PASS', + missingMappings.length ? 'FAIL' : 'PASS', + orphanMappings.length ? 'WARNING' : 'PASS', + unknownFields.length ? 'WARNING' : 'PASS' + ]); + + results.push({ + templateName: template.templateName, + version: template.version, + organizationId: template.organizationId, + schemaFieldCount: fields.length, + tableFields, + dynamicTopicFields, + duplicateFields, + unknownFields, + missingMappings, + orphanMappings, + mappedFieldCount: mappings.length, + status + }); + } + + const overallStatus = summarizeIssues( + results.map((result) => result.status) as Array<'PASS' | 'WARNING' | 'FAIL'> + ); + + console.log( + JSON.stringify( + { + audit: 'template-inventory', + overallStatus, + templateCount: results.length, + results + }, + null, + 2 + ) + ); + } finally { + await sql.end({ timeout: 5 }); + } +} + +main().catch((error) => { + console.error(`[audit-pdf-template-inventory] ${error.message}`); + process.exit(1); +}); diff --git a/scripts/audit-placeholder-integrity.ts b/scripts/audit-placeholder-integrity.ts new file mode 100644 index 0000000..1db1eb2 --- /dev/null +++ b/scripts/audit-placeholder-integrity.ts @@ -0,0 +1,105 @@ +import { + createSqlClient, + extractTemplateFields, + getActiveTemplateVersions, + getEffectiveMappedKeys, + getMappingsForVersion, + isDesignerLabelField, + summarizeIssues, + writeAuditArtifact +} from './pdf-audit-utils.ts'; + +const TYPO_PATTERNS = [/lable/i, /affter/i, /__+/, /\s{2,}/]; +const RESERVED_DYNAMIC_FIELD_NAMES = new Set(['topic', 'data_topic']); + +function detectTypo(fieldName: string) { + return TYPO_PATTERNS.some((pattern) => pattern.test(fieldName)); +} + +async function main() { + const sql = createSqlClient(); + + try { + const templates = await getActiveTemplateVersions(sql); + const results = []; + + for (const template of templates) { + const fields = extractTemplateFields(template.schemaJson); + const { mappings } = await getMappingsForVersion(sql, template.versionId); + const fieldNames = new Set(fields.map((field) => field.name).filter(Boolean)); + const mappedKeys = getEffectiveMappedKeys(fields, mappings); + const tokens = [...new Set(fields.flatMap((field) => field.tokens))].sort(); + const duplicateFieldNames = [...fieldNames].filter( + (fieldName) => fields.filter((field) => field.name === fieldName).length > 1 + ); + const typoFields = fields + .filter((field) => field.name && detectTypo(field.name) && !isDesignerLabelField(field)) + .map((field) => field.name); + const unmappedPlaceholders = tokens.filter( + (token) => + token !== 'item_topic' && + !token.startsWith('topic_') && + !token.startsWith('item_topic_') && + !mappedKeys.has(token) + ); + const deadMappings = mappings + .filter( + (mapping) => + !fieldNames.has(mapping.placeholderKey) && + !tokens.includes(mapping.placeholderKey) && + mapping.placeholderKey !== 'items_table' + ) + .map((mapping) => mapping.placeholderKey); + const invalidReservedMappings = mappings + .filter( + (mapping) => + mapping.placeholderKey.startsWith('topic_') || + mapping.placeholderKey.startsWith('item_topic_') + ) + .map((mapping) => mapping.placeholderKey); + const missingReservedFields = [...RESERVED_DYNAMIC_FIELD_NAMES].filter( + (name) => !fieldNames.has(name) + ); + const status = summarizeIssues([ + duplicateFieldNames.length ? 'FAIL' : 'PASS', + typoFields.length ? 'FAIL' : 'PASS', + unmappedPlaceholders.length ? 'FAIL' : 'PASS', + deadMappings.length ? 'FAIL' : 'PASS', + invalidReservedMappings.length ? 'FAIL' : 'PASS', + missingReservedFields.length ? 'FAIL' : 'PASS' + ]); + + results.push({ + templateName: template.templateName, + organizationId: template.organizationId, + version: template.version, + duplicateFieldNames, + typoFields, + unmappedPlaceholders, + deadMappings, + invalidReservedMappings, + missingReservedFields, + status + }); + } + + const overallStatus = summarizeIssues( + results.map((result) => result.status) as Array<'PASS' | 'WARNING' | 'FAIL'> + ); + const report = { + audit: 'placeholder-integrity', + overallStatus, + results + }; + const artifactPath = await writeAuditArtifact('placeholder-integrity-report.json', report); + + console.log(JSON.stringify({ ...report, artifactPath }, null, 2)); + } finally { + await sql.end({ timeout: 5 }); + } +} + +main().catch((error) => { + console.error(`[audit-placeholder-integrity] ${error.message}`); + process.exit(1); +}); diff --git a/scripts/audit-template-version-integrity.ts b/scripts/audit-template-version-integrity.ts new file mode 100644 index 0000000..631eeaa --- /dev/null +++ b/scripts/audit-template-version-integrity.ts @@ -0,0 +1,166 @@ +import { + buildAuditPayload, + createSqlClient, + getActiveTemplateVersions, + summarizeIssues, + writeAuditArtifact +} from './pdf-audit-utils.ts'; + +type IntegrityIssue = { + kind: + | 'DUPLICATE_ACTIVE_TEMPLATE' + | 'DUPLICATE_ACTIVE_VERSION' + | 'ACTIVE_VERSION_WITHOUT_TEMPLATE' + | 'MAPPING_WITHOUT_VERSION' + | 'INVALID_APPROVED_TEMPLATE_VERSION'; + message: string; + context: Record; +}; + +async function main() { + const sql = createSqlClient(); + + try { + const [activeTemplateVersions, versionRows, orphanMappingRows, payload] = await Promise.all([ + getActiveTemplateVersions(sql), + sql` + select + v.id, + v.template_id as "templateId", + v.organization_id as "organizationId", + v.version, + v.is_active as "isActive", + t.id as "resolvedTemplateId" + from crm_document_template_versions v + left join crm_document_templates t + on t.id = v.template_id + where v.deleted_at is null + `, + sql` + select + m.id, + m.template_version_id as "templateVersionId", + m.placeholder_key as "placeholderKey" + from crm_document_template_mappings m + left join crm_document_template_versions v + on v.id = m.template_version_id + where m.deleted_at is null + and (v.id is null or v.deleted_at is not null) + `, + buildAuditPayload(sql) + ]); + const issues: IntegrityIssue[] = []; + const templateBuckets = new Map(); + + for (const record of activeTemplateVersions) { + const bucketKey = [ + record.organizationId, + record.documentType, + record.productType, + record.fileType + ].join(':'); + const items = templateBuckets.get(bucketKey) ?? []; + items.push(record); + templateBuckets.set(bucketKey, items); + } + + for (const [bucketKey, records] of templateBuckets.entries()) { + const templateIds = [...new Set(records.map((record) => record.templateId))]; + + if (templateIds.length > 1) { + issues.push({ + kind: 'DUPLICATE_ACTIVE_TEMPLATE', + message: `More than one active template exists for ${bucketKey}`, + context: { + bucketKey, + templateIds, + templates: records.map((record) => ({ + templateId: record.templateId, + templateName: record.templateName, + versionId: record.versionId, + version: record.version + })) + } + }); + } + } + + const versionBuckets = new Map>>(); + + for (const row of versionRows as Array>) { + if (!row.isActive) { + continue; + } + + const items = versionBuckets.get(String(row.templateId)) ?? []; + items.push(row); + versionBuckets.set(String(row.templateId), items); + + if (!row.resolvedTemplateId) { + issues.push({ + kind: 'ACTIVE_VERSION_WITHOUT_TEMPLATE', + message: `Active version ${String(row.id)} has no parent template`, + context: row + }); + } + } + + for (const [templateId, rows] of versionBuckets.entries()) { + if (rows.length > 1) { + issues.push({ + kind: 'DUPLICATE_ACTIVE_VERSION', + message: `Template ${templateId} has more than one active version`, + context: { + templateId, + versions: rows.map((row) => ({ + id: row.id, + version: row.version + })) + } + }); + } + } + + for (const row of orphanMappingRows as Array>) { + issues.push({ + kind: 'MAPPING_WITHOUT_VERSION', + message: `Mapping ${String(row.id)} points to a missing template version`, + context: row + }); + } + + if ( + payload.approvedTemplateVersionId && + !activeTemplateVersions.some((record) => record.versionId === payload.approvedTemplateVersionId) + ) { + issues.push({ + kind: 'INVALID_APPROVED_TEMPLATE_VERSION', + message: 'Audit quotation approvedTemplateVersionId does not resolve to an active template version', + context: { + quotationCode: payload.quotationCode, + approvedTemplateVersionId: payload.approvedTemplateVersionId + } + }); + } + + const status = summarizeIssues(issues.length ? ['FAIL'] : ['PASS']); + const report = { + audit: 'template-version-integrity', + overallStatus: status, + activeTemplateCount: activeTemplateVersions.length, + quotationCode: payload.quotationCode, + approvedTemplateVersionId: payload.approvedTemplateVersionId, + issues + }; + const artifactPath = await writeAuditArtifact('template-version-report.json', report); + + console.log(JSON.stringify({ ...report, artifactPath }, null, 2)); + } finally { + await sql.end({ timeout: 5 }); + } +} + +main().catch((error) => { + console.error(`[audit-template-version-integrity] ${error.message}`); + process.exit(1); +}); diff --git a/scripts/generate-pdf-audit-report.ts b/scripts/generate-pdf-audit-report.ts new file mode 100644 index 0000000..40cf1a0 --- /dev/null +++ b/scripts/generate-pdf-audit-report.ts @@ -0,0 +1,194 @@ +import { writeFile } from 'node:fs/promises'; +import fs from 'node:fs'; +import path from 'node:path'; +import { + AUDIT_ARTIFACTS_DIR, + AUDIT_QUOTATION_CODE, + REPORT_JSON_PATH, + REPORT_MARKDOWN_PATH, + buildAuditPayload, + createSqlClient, + extractTemplateFields, + getEffectiveMappedKeys, + getDuplicateFieldNames, + getMappingsForVersion, + getUnknownFieldNames, + summarizeIssues +} from './pdf-audit-utils.ts'; + +async function main() { + const sql = createSqlClient(); + + try { + const payload = await buildAuditPayload(sql); + const fields = extractTemplateFields(payload.templateSchema); + const { mappings, columns } = await getMappingsForVersion(sql, payload.templateVersionId); + const fieldNames = new Set(fields.map((field) => field.name).filter(Boolean)); + const placeholderTokens = [...new Set(fields.flatMap((field) => field.tokens))].sort(); + const mappedKeys = getEffectiveMappedKeys(fields, mappings); + const unmappedTokens = placeholderTokens.filter((token) => { + if (token === 'item_topic') { + return false; + } + + if (token === 'topic' || token === 'data_topic') { + return false; + } + + if (token.startsWith('topic_') || token.startsWith('item_topic_')) { + return false; + } + + return !mappedKeys.has(token); + }); + const orphanMappings = mappings + .filter( + (mapping) => + !placeholderTokens.includes(mapping.placeholderKey) && + !fieldNames.has(mapping.placeholderKey) && + mapping.placeholderKey !== 'items_table' + ) + .map((mapping) => mapping.placeholderKey); + const duplicateFields = getDuplicateFieldNames(fields); + const unknownFields = getUnknownFieldNames(fields); + const topicRows = Object.keys(payload.topicInputs).filter((key) => key.startsWith('topic_')).length; + const topicItemRows = Object.keys(payload.topicInputs).filter((key) => + key.startsWith('item_topic_') + ).length; + const itemTableMapping = mappings.find((mapping) => mapping.placeholderKey === 'items_table') ?? null; + const itemTableColumns = itemTableMapping + ? columns.filter((column) => column.mappingId === itemTableMapping.id) + : []; + const snapshotTemplateInput = + payload.approvedSnapshot && + typeof payload.approvedSnapshot === 'object' && + 'templateInput' in payload.approvedSnapshot + ? (payload.approvedSnapshot.templateInput as Record | null) + : null; + const sharedKeys = snapshotTemplateInput + ? Object.keys(payload.templateInput).filter( + (key) => + !(key.startsWith('topic_') || key.startsWith('item_topic_')) && + key in snapshotTemplateInput + ) + : []; + const inconsistentKeys = sharedKeys.filter( + (key) => JSON.stringify(payload.templateInput[key]) !== JSON.stringify(snapshotTemplateInput?.[key]) + ); + const consistencyStatus = snapshotTemplateInput + ? inconsistentKeys.length + ? 'FAIL' + : 'PASS' + : 'WARNING'; + const overallStatus = summarizeIssues([ + unmappedTokens.length ? 'FAIL' : 'PASS', + orphanMappings.length ? 'WARNING' : 'PASS', + duplicateFields.length ? 'FAIL' : 'PASS', + unknownFields.length ? 'WARNING' : 'PASS', + consistencyStatus + ]); + + const report = { + generatedAt: new Date().toISOString(), + overallStatus, + quotationCode: payload.quotationCode, + expectedFixtureCode: AUDIT_QUOTATION_CODE, + template: { + templateName: payload.templateName, + version: payload.templateVersion, + versionId: payload.templateVersionId, + fieldCount: fields.length, + duplicateFields, + unknownFields + }, + metrics: { + topicCount: payload.quotationMetrics.topicCount, + itemCount: payload.quotationMetrics.itemCount, + partyCount: payload.quotationMetrics.partyCount, + dynamicTopicFieldCount: topicRows, + dynamicTopicItemFieldCount: topicItemRows, + mappingCount: mappings.length + }, + mappingCoverage: { + placeholderTokenCount: placeholderTokens.length, + unmappedTokens, + orphanMappings, + itemTableColumns: itemTableColumns.map((column) => ({ + columnName: column.columnName, + sourceField: column.sourceField + })) + }, + runtimePayload: { + templateInputKeys: Object.keys(payload.templateInput).length, + topicInputKeys: Object.keys(payload.topicInputs).length + }, + consistency: { + status: consistencyStatus, + approvedSnapshotAvailable: Boolean(snapshotTemplateInput), + approvedArtifactReference: payload.approvedArtifactReference, + inconsistentKeys + }, + integritySummary: { + available: fs.existsSync(path.resolve(AUDIT_ARTIFACTS_DIR, 'summary.json')), + artifactDir: AUDIT_ARTIFACTS_DIR + } + }; + + const markdown = [ + '# PDF Audit Report', + '', + `- Generated At: ${report.generatedAt}`, + `- Overall Status: ${report.overallStatus}`, + `- Quotation Code: ${report.quotationCode}`, + `- Template: ${report.template.templateName} v${report.template.version}`, + '', + '## Metrics', + '', + `- Topic Count: ${report.metrics.topicCount}`, + `- Item Count: ${report.metrics.itemCount}`, + `- Project Party Count: ${report.metrics.partyCount}`, + `- Mapping Count: ${report.metrics.mappingCount}`, + '', + '## Mapping Coverage', + '', + `- Placeholder Tokens: ${report.mappingCoverage.placeholderTokenCount}`, + `- Unmapped Tokens: ${report.mappingCoverage.unmappedTokens.length ? report.mappingCoverage.unmappedTokens.join(', ') : 'None'}`, + `- Orphan Mappings: ${report.mappingCoverage.orphanMappings.length ? report.mappingCoverage.orphanMappings.join(', ') : 'None'}`, + '', + '## Visual / Consistency', + '', + `- Approved Snapshot Available: ${report.consistency.approvedSnapshotAvailable ? 'Yes' : 'No'}`, + `- Consistency Status: ${report.consistency.status}`, + `- Inconsistent Keys: ${report.consistency.inconsistentKeys.length ? report.consistency.inconsistentKeys.join(', ') : 'None'}`, + '', + '## Integrity Layer', + '', + `- Integrity Summary Available: ${report.integritySummary.available ? 'Yes' : 'No'}`, + `- Artifact Directory: ${report.integritySummary.artifactDir}` + ].join('\n'); + + await writeFile(REPORT_JSON_PATH, `${JSON.stringify(report, null, 2)}\n`, 'utf8'); + await writeFile(REPORT_MARKDOWN_PATH, `${markdown}\n`, 'utf8'); + + console.log( + JSON.stringify( + { + audit: 'pdf-report', + overallStatus, + jsonPath: REPORT_JSON_PATH, + markdownPath: REPORT_MARKDOWN_PATH, + report + }, + null, + 2 + ) + ); + } finally { + await sql.end({ timeout: 5 }); + } +} + +main().catch((error) => { + console.error(`[generate-pdf-audit-report] ${error.message}`); + process.exit(1); +}); diff --git a/scripts/generate-pdf-integrity-report.ts b/scripts/generate-pdf-integrity-report.ts new file mode 100644 index 0000000..3d34ea9 --- /dev/null +++ b/scripts/generate-pdf-integrity-report.ts @@ -0,0 +1,115 @@ +import { readFile, writeFile } from 'node:fs/promises'; +import path from 'node:path'; +import { + AUDIT_ARTIFACTS_DIR, + buildAuditPayload, + createSqlClient, + summarizeIssues, + writeAuditArtifact +} from './pdf-audit-utils.ts'; + +async function readReport(fileName: string): Promise { + const filePath = path.resolve(AUDIT_ARTIFACTS_DIR, fileName); + try { + const raw = await readFile(filePath, 'utf8'); + return JSON.parse(raw) as T; + } catch { + return null; + } +} + +async function main() { + const sql = createSqlClient(); + + try { + const payload = await buildAuditPayload(sql); + const templateVersionReport = await readReport>( + 'template-version-report.json' + ); + const placeholderReport = await readReport>( + 'placeholder-integrity-report.json' + ); + const payloadReport = await readReport>('payload-parity-report.json'); + const snapshotReport = await readReport>('signature-audit-report.json'); + const statuses = [ + String(templateVersionReport?.overallStatus ?? 'WARNING'), + String(placeholderReport?.overallStatus ?? 'WARNING'), + String(payloadReport?.overallStatus ?? 'WARNING'), + String(snapshotReport?.overallStatus ?? 'WARNING') + ] as Array<'PASS' | 'WARNING' | 'FAIL'>; + const overallStatus = summarizeIssues(statuses); + const topicRuntimeReport = { + audit: 'topic-runtime', + overallStatus: Object.keys(payload.topicInputs).length > 0 ? 'PASS' : 'FAIL', + quotationCode: payload.quotationCode, + topicCount: payload.quotationMetrics.topicCount, + topicInputKeys: Object.keys(payload.topicInputs) + .filter((key) => key.startsWith('topic_')) + .sort(), + itemTopicInputKeys: Object.keys(payload.topicInputs) + .filter((key) => key.startsWith('item_topic_')) + .sort(), + duplicateKeys: Object.keys(payload.topicInputs).filter( + (key, index, array) => array.indexOf(key) !== index + ), + emptySections: Object.entries(payload.topicInputs) + .filter(([, value]) => !Array.isArray(value) || value.length === 0) + .map(([key]) => key) + }; + await writeAuditArtifact('topic-runtime-report.json', topicRuntimeReport); + const summary = { + generatedAt: new Date().toISOString(), + overallStatus, + quotationCode: payload.quotationCode, + artifacts: { + templateVersionReport: 'template-version-report.json', + placeholderIntegrityReport: 'placeholder-integrity-report.json', + payloadParityReport: 'payload-parity-report.json', + topicRuntimeReport: 'topic-runtime-report.json', + signatureAuditReport: 'signature-audit-report.json' + }, + statuses: { + templateVersion: templateVersionReport?.overallStatus ?? 'WARNING', + placeholderIntegrity: placeholderReport?.overallStatus ?? 'WARNING', + payloadParity: payloadReport?.overallStatus ?? 'WARNING', + topicRuntime: topicRuntimeReport.overallStatus, + approvedSnapshotParity: snapshotReport?.overallStatus ?? 'WARNING' + } + }; + const artifactPath = await writeAuditArtifact('summary.json', summary); + const markdown = [ + '# PDF Integrity Summary', + '', + `- Generated At: ${summary.generatedAt}`, + `- Overall Status: ${summary.overallStatus}`, + `- Quotation Code: ${summary.quotationCode}`, + '', + '## Statuses', + '', + `- Template Version Integrity: ${summary.statuses.templateVersion}`, + `- Placeholder Integrity: ${summary.statuses.placeholderIntegrity}`, + `- Payload Parity: ${summary.statuses.payloadParity}`, + `- Topic Runtime: ${summary.statuses.topicRuntime}`, + `- Approved Snapshot Parity: ${summary.statuses.approvedSnapshotParity}`, + '', + '## Artifact Files', + '', + `- ${summary.artifacts.templateVersionReport}`, + `- ${summary.artifacts.placeholderIntegrityReport}`, + `- ${summary.artifacts.payloadParityReport}`, + `- ${summary.artifacts.topicRuntimeReport}`, + `- ${summary.artifacts.signatureAuditReport}` + ].join('\n'); + const markdownPath = path.resolve(AUDIT_ARTIFACTS_DIR, 'summary.md'); + await writeFile(markdownPath, `${markdown}\n`, 'utf8'); + + console.log(JSON.stringify({ ...summary, artifactPath, markdownPath }, null, 2)); + } finally { + await sql.end({ timeout: 5 }); + } +} + +main().catch((error) => { + console.error(`[generate-pdf-integrity-report] ${error.message}`); + process.exit(1); +}); diff --git a/scripts/pdf-audit-utils.ts b/scripts/pdf-audit-utils.ts new file mode 100644 index 0000000..702b622 --- /dev/null +++ b/scripts/pdf-audit-utils.ts @@ -0,0 +1,1089 @@ +import fs from 'node:fs'; +import { mkdir, writeFile } from 'node:fs/promises'; +import path from 'node:path'; +import { createHash } from 'node:crypto'; +import postgres from 'postgres'; + +export const AUDIT_QUOTATION_CODE = 'QT-H5-AUDIT'; +export const AUDIT_ARTIFACTS_DIR = path.resolve(process.cwd(), 'artifacts', 'pdf-audit'); +export const REPORT_JSON_PATH = path.resolve( + process.cwd(), + 'docs/implementation/pdf-audit-report.json' +); +export const REPORT_MARKDOWN_PATH = path.resolve( + process.cwd(), + 'docs/implementation/pdf-audit-report.md' +); + +export type SqlClient = ReturnType; + +export interface TemplateFieldRecord { + pageIndex: number; + fieldIndex: number; + name: string; + type: string; + tokens: string[]; +} + +export interface ActiveTemplateVersionRecord { + templateId: string; + templateName: string; + organizationId: string; + documentType: string; + productType: string; + fileType: string; + versionId: string; + version: string; + schemaJson: unknown; +} + +export interface MappingRecord { + id: string; + organizationId: string; + templateVersionId: string; + placeholderKey: string; + sourcePath: string; + dataType: 'scalar' | 'multiline' | 'table' | 'image'; + defaultValue: string | null; + formatMask: string | null; + sortOrder: number; +} + +export interface MappingColumnRecord { + mappingId: string; + columnName: string; + sourceField: string; + sortOrder: number; + formatMask: string | null; +} + +export interface AuditPayload { + organizationId: string; + quotationId: string; + quotationCode: string; + templateVersionId: string; + approvedTemplateVersionId: string | null; + templateName: string; + templateVersion: string; + templateSchema: unknown; + mappings: MappingRecord[]; + mappingColumns: MappingColumnRecord[]; + documentData: Record; + templateInput: Record; + topicInputs: Record; + quotationMetrics: { + topicCount: number; + itemCount: number; + partyCount: number; + }; + approvedSnapshot: Record | null; + approvedArtifactReference: string | null; +} + +const SYSTEM_FIELD_NAMES = new Set([ + '', + 'company1', + 'company2', + 'company_addr1', + 'company_addr2', + 'company_tel', + 'company_email', + 'company_tax', + 'Please_do_not', + 'yours_faithfuly' +]); + +const DYNAMIC_TOPIC_FIELD_NAMES = new Set(['topic', 'data_topic']); +const LEGACY_DYNAMIC_SEED_TOKENS = new Set(['item_topic']); +const DYNAMIC_TOPIC_TOKEN_PREFIXES = ['topic_', 'item_topic_']; +const DESIGNER_LABEL_NAME_PATTERNS = [/_label$/i, /_lable$/i]; +const SIGNATURE_ROLE_FALLBACK_LABELS: Record = { + sales: 'Sales Engineer', + sales_support: 'Sales Support', + sales_manager: 'Sales Manager', + department_manager: 'Department Manager', + top_manager: 'General Manager' +}; + +export function loadEnvFile(filePath: string) { + if (!fs.existsSync(filePath)) { + return; + } + + const content = fs.readFileSync(filePath, 'utf8'); + + for (const rawLine of content.split(/\r?\n/)) { + const line = rawLine.trim(); + + if (!line || line.startsWith('#')) { + continue; + } + + const separatorIndex = line.indexOf('='); + + if (separatorIndex === -1) { + continue; + } + + const key = line.slice(0, separatorIndex).trim(); + let value = line.slice(separatorIndex + 1).trim(); + + if ( + (value.startsWith('"') && value.endsWith('"')) || + (value.startsWith("'") && value.endsWith("'")) + ) { + value = value.slice(1, -1); + } + + if (!(key in process.env)) { + process.env[key] = value; + } + } +} + +export function loadLocalEnv() { + loadEnvFile(path.resolve(process.cwd(), '.env.local')); + loadEnvFile(path.resolve(process.cwd(), '.env')); +} + +export function requireEnv(name: string) { + const value = process.env[name]?.trim(); + + if (!value) { + throw new Error(`Missing required environment variable: ${name}`); + } + + return value; +} + +export function createSqlClient() { + loadLocalEnv(); + return postgres(requireEnv('DATABASE_URL'), { + prepare: false + }); +} + +export async function ensureAuditArtifactsDir() { + await mkdir(AUDIT_ARTIFACTS_DIR, { recursive: true }); +} + +export async function writeAuditArtifact(fileName: string, payload: unknown) { + await ensureAuditArtifactsDir(); + const targetPath = path.resolve(AUDIT_ARTIFACTS_DIR, fileName); + await writeFile(targetPath, `${JSON.stringify(payload, null, 2)}\n`, 'utf8'); + return targetPath; +} + +export function computePayloadHash(value: unknown) { + return createHash('sha256').update(JSON.stringify(value)).digest('hex'); +} + +export function extractTokensFromValue(value: unknown) { + const tokens = new Set(); + + if (typeof value !== 'string') { + return tokens; + } + + for (const match of value.matchAll(/\{([^}]+)\}/g)) { + tokens.add(match[1]); + } + + try { + const parsed = JSON.parse(value) as unknown; + + if (Array.isArray(parsed)) { + for (const row of parsed) { + if (!Array.isArray(row)) { + continue; + } + + for (const cell of row) { + if (typeof cell !== 'string') { + continue; + } + + for (const match of cell.matchAll(/\{([^}]+)\}/g)) { + tokens.add(match[1]); + } + } + } + } + } catch { + return tokens; + } + + return tokens; +} + +export function extractSinglePlaceholder(value: unknown) { + if (typeof value !== 'string') { + return null; + } + + const matches = [...value.matchAll(/\{([^}]+)\}/g)].map((match) => match[1]); + const uniqueMatches = [...new Set(matches)]; + + return uniqueMatches.length === 1 ? uniqueMatches[0] : null; +} + +function normalizeSchemaJson(schemaJson: unknown) { + if (typeof schemaJson === 'string') { + try { + return JSON.parse(schemaJson) as unknown; + } catch { + return null; + } + } + + return schemaJson; +} + +export function extractTemplateFields(schemaJson: unknown) { + const fields: TemplateFieldRecord[] = []; + const normalizedSchemaJson = normalizeSchemaJson(schemaJson); + + if ( + !normalizedSchemaJson || + typeof normalizedSchemaJson !== 'object' || + !('schemas' in normalizedSchemaJson) + ) { + return fields; + } + + const pages = (normalizedSchemaJson as { schemas?: unknown }).schemas; + + if (!Array.isArray(pages)) { + return fields; + } + + for (const [pageIndex, page] of pages.entries()) { + if (!Array.isArray(page)) { + continue; + } + + for (const [fieldIndex, field] of page.entries()) { + if (!field || typeof field !== 'object') { + continue; + } + + const typedField = field as { + name?: unknown; + type?: unknown; + content?: unknown; + head?: unknown; + }; + const tokens = new Set(); + + for (const token of extractTokensFromValue(typedField.content)) { + tokens.add(token); + } + + if (Array.isArray(typedField.head)) { + for (const value of typedField.head) { + for (const token of extractTokensFromValue(value)) { + tokens.add(token); + } + } + } + + fields.push({ + pageIndex, + fieldIndex, + name: typeof typedField.name === 'string' ? typedField.name : '', + type: typeof typedField.type === 'string' ? typedField.type : 'unknown', + tokens: [...tokens] + }); + } + } + + return fields; +} + +export function getDuplicateFieldNames(fields: TemplateFieldRecord[]) { + const fieldNameCounts = new Map(); + + for (const field of fields) { + if (!field.name) { + continue; + } + + fieldNameCounts.set(field.name, (fieldNameCounts.get(field.name) ?? 0) + 1); + } + + return [...fieldNameCounts.entries()] + .filter(([, count]) => count > 1) + .map(([name]) => name) + .sort(); +} + +export function getUnknownFieldNames( + fields: TemplateFieldRecord[], + mappedKeys: Set = new Set() +) { + return fields + .filter( + (field) => + field.name && + !SYSTEM_FIELD_NAMES.has(field.name) && + !DYNAMIC_TOPIC_FIELD_NAMES.has(field.name) && + !mappedKeys.has(field.name) && + field.tokens.length === 0 && + field.type !== 'table' && + field.type !== 'text' + ) + .map((field) => field.name) + .sort(); +} + +export function isDesignerLabelField(field: TemplateFieldRecord) { + return ( + field.type === 'text' && + field.tokens.length === 0 && + DESIGNER_LABEL_NAME_PATTERNS.some((pattern) => pattern.test(field.name)) + ); +} + +export function getEffectiveMappedKeys( + fields: TemplateFieldRecord[], + mappings: Array<{ placeholderKey: string }> +) { + const mappedKeys = new Set(mappings.map((mapping) => mapping.placeholderKey)); + + for (const field of fields) { + if (!field.name || field.tokens.length !== 1) { + continue; + } + + const [token] = field.tokens; + + if (mappedKeys.has(field.name) && !mappedKeys.has(token)) { + mappedKeys.add(token); + } + + if (mappedKeys.has(token) && !mappedKeys.has(field.name)) { + mappedKeys.add(field.name); + } + } + + return mappedKeys; +} + +export function normalizeTemplateInputAliases( + fields: TemplateFieldRecord[], + templateInput: Record +) { + const normalized = { ...templateInput }; + + for (const field of fields) { + if (!field.name || field.tokens.length !== 1) { + continue; + } + + const [token] = field.tokens; + + if (!token || field.name === token) { + continue; + } + + if (normalized[field.name] !== undefined && normalized[token] === undefined) { + normalized[token] = normalized[field.name]; + } + + if (normalized[token] !== undefined && normalized[field.name] === undefined) { + normalized[field.name] = normalized[token]; + } + } + + return normalized; +} + +export async function getActiveTemplateVersions(sql: SqlClient): Promise { + const rows = await sql` + select + t.id as "templateId", + t.template_name as "templateName", + t.organization_id as "organizationId", + t.document_type as "documentType", + t.product_type as "productType", + t.file_type as "fileType", + v.id as "versionId", + v.version, + v.schema_json as "schemaJson" + from crm_document_templates t + inner join crm_document_template_versions v + on v.template_id = t.id + where t.is_active = true + and v.is_active = true + and t.deleted_at is null + and v.deleted_at is null + order by t.organization_id asc, t.template_name asc, v.created_at asc + `; + + return castRows(rows); +} + +export async function getMappingsForVersion(sql: SqlClient, templateVersionId: string) { + const [mappings, columns] = await Promise.all([ + sql` + select + id, + organization_id as "organizationId", + template_version_id as "templateVersionId", + placeholder_key as "placeholderKey", + source_path as "sourcePath", + data_type as "dataType", + default_value as "defaultValue", + format_mask as "formatMask", + sort_order as "sortOrder" + from crm_document_template_mappings + where template_version_id = ${templateVersionId} + and deleted_at is null + order by sort_order asc, created_at asc + `, + sql` + select + mapping_id as "mappingId", + column_name as "columnName", + source_field as "sourceField", + sort_order as "sortOrder", + format_mask as "formatMask" + from crm_document_template_table_columns + where deleted_at is null + order by sort_order asc, created_at asc + ` + ]); + + return { + mappings: castRows(mappings), + columns: castRows(columns) + }; +} + +export async function findAuditQuotation(sql: SqlClient) { + const [fixtureQuotation] = await sql` + select + q.id, + q.code, + q.organization_id as "organizationId", + q.customer_id as "customerId", + q.contact_id as "contactId", + q.quotation_date as "quotationDate", + q.valid_until as "validUntil", + q.project_name as "projectName", + q.project_location as "projectLocation", + q.attention, + q.reference, + q.currency, + q.total_amount as "totalAmount", + q.approved_template_version_id as "approvedTemplateVersionId", + q.salesman_id as "salesmanId", + q.created_by as "createdBy", + q.created_at as "createdAt", + q.approved_snapshot as "approvedSnapshot", + q.approved_pdf_url as "approvedPdfUrl" + from crm_quotations q + where q.code = ${AUDIT_QUOTATION_CODE} + and q.deleted_at is null + limit 1 + `; + + if (fixtureQuotation) { + return fixtureQuotation; + } + + const [fallbackQuotation] = await sql` + select + q.id, + q.code, + q.organization_id as "organizationId", + q.customer_id as "customerId", + q.contact_id as "contactId", + q.quotation_date as "quotationDate", + q.valid_until as "validUntil", + q.project_name as "projectName", + q.project_location as "projectLocation", + q.attention, + q.reference, + q.currency, + q.total_amount as "totalAmount", + q.salesman_id as "salesmanId", + q.created_by as "createdBy", + q.created_at as "createdAt", + q.approved_snapshot as "approvedSnapshot", + q.approved_pdf_url as "approvedPdfUrl" + from crm_quotations q + where q.deleted_at is null + order by q.updated_at desc + limit 1 + `; + + if (!fallbackQuotation) { + throw new Error('No quotation found for PDF audit'); + } + + return fallbackQuotation; +} + +function getValueByPath(source: unknown, valuePath: string): unknown { + return valuePath.split('.').reduce((current, segment) => { + if (current === null || current === undefined) { + return undefined; + } + + if (Array.isArray(current) && /^\d+$/.test(segment)) { + return current[Number(segment)]; + } + + if (typeof current === 'object' && current !== null && segment in current) { + return (current as Record)[segment]; + } + + return undefined; + }, source); +} + +function formatPdfDate(dateString: string | Date | null | undefined) { + if (!dateString) { + return '-'; + } + + const date = dateString instanceof Date ? dateString : new Date(dateString); + + if (Number.isNaN(date.getTime())) { + return '-'; + } + + return date.toLocaleDateString('en-US', { + day: 'numeric', + month: 'short', + year: 'numeric' + }); +} + +function formatPdfCurrency(amount: number | string | null | undefined, currencyCode = 'THB') { + if (amount === null || amount === undefined) { + return '-'; + } + + const normalized = typeof amount === 'number' ? amount : Number(String(amount).replaceAll(',', '')); + + if (!Number.isFinite(normalized)) { + return '-'; + } + + const symbols: Record = { + THB: 'THB ', + USD: '$', + EUR: 'EUR ' + }; + const prefix = symbols[currencyCode.toUpperCase()] ?? `${currencyCode.toUpperCase()} `; + return `${prefix}${new Intl.NumberFormat('en-US', { + minimumFractionDigits: 2, + maximumFractionDigits: 2 + }).format(normalized)}`.trim(); +} + +function formatTopicItems(items: Array<{ content?: string | null; sortOrder?: number | null }> = []) { + if (!items.length) { + return [['-']]; + } + + return [...items] + .sort((left, right) => (left.sortOrder ?? Number.MAX_SAFE_INTEGER) - (right.sortOrder ?? Number.MAX_SAFE_INTEGER)) + .map((item) => [item.content?.trim() || '-']); +} + +function applyFormatMask(value: unknown, formatMask?: string | null) { + if (value === null || value === undefined) { + return value; + } + + if (formatMask === 'currency' || formatMask?.startsWith('currency_')) { + const currencyCode = formatMask === 'currency' ? 'THB' : formatMask.replace('currency_', ''); + return formatPdfCurrency(value as number | string, currencyCode); + } + + if (formatMask === 'date' || formatMask === 'date_en') { + return formatPdfDate(value as string); + } + + return value; +} + +function normalizePdfmeTable( + value: unknown, + columns?: Array<{ sourceField: string; formatMask?: string | null }> +) { + if (!Array.isArray(value) || value.length === 0) { + return [['-']]; + } + + if (value.every((row) => Array.isArray(row))) { + return (value as unknown[][]).map((row) => row.map((cell) => String(cell ?? '-').trim() || '-')); + } + + if (!columns?.length) { + return value.map((row) => [String(row ?? '-').trim() || '-']); + } + + return value.map((row) => { + const rowRecord = row as Record; + return columns.map((column) => { + const formatted = applyFormatMask(rowRecord[column.sourceField], column.formatMask); + return String(formatted ?? '-').trim() || '-'; + }); + }); +} + +function mapDocumentDataToTemplateInput( + documentData: Record, + mappings: MappingRecord[], + columns: MappingColumnRecord[] +) { + const result: Record = {}; + const columnMap = new Map(); + + for (const column of columns) { + const items = columnMap.get(column.mappingId) ?? []; + items.push(column); + columnMap.set(column.mappingId, items); + } + + for (const mapping of mappings) { + const rawValue = getValueByPath(documentData, mapping.sourcePath); + + if (mapping.dataType === 'table') { + const mappingColumns = columnMap.get(mapping.id) ?? []; + result[mapping.placeholderKey] = normalizePdfmeTable(rawValue, mappingColumns); + continue; + } + + if (mapping.dataType === 'multiline') { + if (Array.isArray(rawValue)) { + result[mapping.placeholderKey] = rawValue + .map((item) => String(item ?? '').trim()) + .filter(Boolean) + .join('\n'); + continue; + } + + result[mapping.placeholderKey] = + typeof rawValue === 'string' ? rawValue : mapping.defaultValue ?? ''; + continue; + } + + result[mapping.placeholderKey] = + applyFormatMask(rawValue, mapping.formatMask) ?? mapping.defaultValue ?? '-'; + } + + return result; +} + +function normalizeBusinessRoleLabel(businessRole: string | null | undefined) { + if (!businessRole) { + return '-'; + } + + return ( + SIGNATURE_ROLE_FALLBACK_LABELS[businessRole] ?? + businessRole + .split('_') + .map((part) => part.charAt(0).toUpperCase() + part.slice(1)) + .join(' ') + ); +} + +function buildTopicInputs(topics: Array<{ topicType: string; items: Array<{ content: string; sortOrder: number }> }>) { + const sortedTopics = [...topics].sort((left, right) => left.topicType.localeCompare(right.topicType)); + const topicInputs: Record = {}; + + for (const [index, topic] of sortedTopics.entries()) { + topicInputs[`topic_1_${index}`] = [[topic.topicType || '-']]; + topicInputs[`item_topic_1_${index}`] = formatTopicItems(topic.items); + } + + return topicInputs; +} + +export async function buildAuditPayload(sql: SqlClient): Promise { + const quotation = await findAuditQuotation(sql); + const [organization, customer, contact, itemRows, topicRows, topicItemRows, partyRows, jobTitles] = + await Promise.all([ + sql` + select id, name + from organizations + where id = ${quotation.organizationId} + limit 1 + `.then((rows) => rows[0] ?? null), + sql` + select id, name, address, phone, email + from crm_customers + where id = ${quotation.customerId} + limit 1 + `.then((rows) => rows[0] ?? null), + quotation.contactId + ? sql` + select id, name, email, mobile, position + from crm_customer_contacts + where id = ${quotation.contactId} + limit 1 + `.then((rows) => rows[0] ?? null) + : Promise.resolve(null), + sql` + select + qi.id, + qi.item_number as "itemNumber", + qi.description, + qi.quantity, + qi.unit, + qi.unit_price as "unitPrice", + qi.discount, + qi.total_price as "totalPrice", + qi.sort_order as "sortOrder", + u.label as "unitLabel" + from crm_quotation_items qi + left join ms_options u + on u.id = qi.unit + where qi.quotation_id = ${quotation.id} + and qi.deleted_at is null + order by qi.sort_order asc, qi.item_number asc + `, + sql` + select + qt.id, + qt.title, + qt.sort_order as "sortOrder", + qt.topic_type as "topicTypeId", + o.code as "topicCode", + coalesce(o.label, qt.title) as "topicLabel" + from crm_quotation_topics qt + left join ms_options o + on o.id = qt.topic_type + where qt.quotation_id = ${quotation.id} + and qt.deleted_at is null + order by qt.sort_order asc, qt.created_at asc + `, + sql` + select + qti.id, + qti.topic_id as "topicId", + qti.content, + qti.sort_order as "sortOrder" + from crm_quotation_topic_items qti + where qti.deleted_at is null + and qti.topic_id in ( + select id + from crm_quotation_topics + where quotation_id = ${quotation.id} + and deleted_at is null + ) + order by qti.sort_order asc, qti.created_at asc + `, + sql` + select + qc.id, + qc.customer_id as "customerId", + qc.role, + qc.is_primary as "isPrimary" + from crm_quotation_customers qc + where qc.quotation_id = ${quotation.id} + and qc.deleted_at is null + `, + sql` + select code, label + from ms_options + where organization_id = ${quotation.organizationId} + and category = 'crm_job_title' + and is_active = true + and deleted_at is null + ` + ]); + + if (!organization || !customer) { + throw new Error('Audit quotation references missing organization or customer'); + } + + const activeTemplateVersions = await getActiveTemplateVersions(sql); + const templateVersion = activeTemplateVersions.find( + (item) => item.organizationId === quotation.organizationId && item.documentType === 'quotation' + ); + + if (!templateVersion) { + throw new Error(`No active quotation template version found for organization ${quotation.organizationId}`); + } + + const { mappings, columns } = await getMappingsForVersion(sql, templateVersion.versionId); + const topicItemsByTopicId = new Map>(); + + for (const topicItemRow of castRows<{ topicId: string; content: string; sortOrder: number }>( + topicItemRows + )) { + const items = topicItemsByTopicId.get(topicItemRow.topicId) ?? []; + items.push({ + content: topicItemRow.content, + sortOrder: topicItemRow.sortOrder + }); + topicItemsByTopicId.set(topicItemRow.topicId, items); + } + + const topics = (topicRows as Array>).map((topicRow) => ({ + id: String(topicRow.id), + title: String(topicRow.title ?? topicRow.topicLabel ?? 'Topic'), + topicCode: String(topicRow.topicCode ?? ''), + topicType: String(topicRow.topicLabel ?? topicRow.title ?? 'Topic'), + items: topicItemsByTopicId.get(String(topicRow.id)) ?? [] + })); + const exclusionTopic = topics.find((topic) => topic.topicCode === 'exclusion') ?? topics[0] ?? null; + const topicInputs = buildTopicInputs( + topics.map((topic) => ({ + topicType: topic.topicType, + items: topic.items + })) + ); + + const [approvalActions, userRows, membershipRows] = await Promise.all([ + sql` + select + aa.acted_by as "actedBy", + aa.acted_at as "actedAt", + aa.action, + aa.step_number as "stepNumber", + aps.role_code as "roleCode", + u.name as "actorName" + from crm_approval_actions aa + inner join crm_approval_requests ar + on ar.id = aa.approval_request_id + left join crm_approval_steps aps + on aps.workflow_id = ar.workflow_id + and aps.step_number = aa.step_number + and aps.deleted_at is null + left join users u + on u.id = aa.acted_by + where ar.entity_type = 'quotation' + and ar.entity_id = ${quotation.id} + and ar.organization_id = ${quotation.organizationId} + and aa.deleted_at is null + order by aa.acted_at asc, aa.created_at asc + `, + sql` + select id, name + from users + where id in (${quotation.salesmanId ?? quotation.createdBy}, ${quotation.createdBy}) + `, + sql` + select user_id as "userId", business_role as "businessRole" + from memberships + where organization_id = ${quotation.organizationId} + ` + ]); + const userMap = new Map( + castRows<{ id: string; name: string }>(userRows).map((row) => [row.id, row.name]) + ); + const membershipMap = new Map( + castRows<{ userId: string; businessRole: string }>(membershipRows).map((row) => [ + row.userId, + row.businessRole + ]) + ); + const jobTitleMap = new Map( + castRows<{ code: string; label: string }>(jobTitles).map((row) => [row.code, row.label]) + ); + const approvedActions = (approvalActions as Array>).filter( + (row) => row.action === 'approve' + ); + const finalApprovedAction = approvedActions.at(-1) ?? null; + const approvedByAction = + approvedActions.filter((row) => row.roleCode !== finalApprovedAction?.roleCode).at(-1) ?? null; + const preparedByUserId = (quotation.salesmanId as string | null) ?? (quotation.createdBy as string); + + const resolvePosition = (userId: string | null | undefined, roleCode?: string | null) => { + const businessRole = (userId ? membershipMap.get(userId) : null) ?? roleCode ?? null; + return businessRole ? jobTitleMap.get(businessRole) ?? normalizeBusinessRoleLabel(businessRole) : '-'; + }; + + const documentData: Record = { + company: { + id: organization.id, + name: organization.name + }, + customer: { + id: customer.id, + name: customer.name, + address: customer.address, + phone: customer.phone, + email: customer.email + }, + contact: contact + ? { + id: contact.id, + name: contact.name, + email: contact.email, + mobile: contact.mobile, + position: contact.position + } + : null, + quotation: { + id: quotation.id, + code: quotation.code, + quotationDate: quotation.quotationDate, + validUntil: quotation.validUntil, + projectName: quotation.projectName, + projectLocation: quotation.projectLocation, + attention: quotation.attention, + reference: quotation.reference, + currency: quotation.currency, + totalAmount: quotation.totalAmount + }, + items: (itemRows as Array>).map((row) => ({ + id: row.id, + itemNumber: row.itemNumber, + description: row.description, + quantity: row.quantity, + unitLabel: row.unitLabel ?? row.unit ?? '-', + unitPrice: row.unitPrice, + discount: row.discount, + totalPrice: row.totalPrice + })), + quotationCustomers: partyRows, + topics: { + all: topics, + exclusion: exclusionTopic ? [exclusionTopic] : [] + }, + pdfme: { + quotation_date: formatPdfDate(quotation.quotationDate as string), + quotation_price: formatPdfCurrency(quotation.totalAmount as number, 'THB'), + exclusion_data: formatTopicItems(exclusionTopic?.items ?? []), + topic_inputs: topicInputs + }, + signatures: { + preparedBy: { + name: userMap.get(preparedByUserId) ?? '-', + position: resolvePosition(preparedByUserId), + actedAt: new Date(String(quotation.createdAt)).toISOString() + }, + approvedBy: { + name: String(approvedByAction?.actorName ?? '-'), + position: resolvePosition( + (approvedByAction?.actedBy as string | null | undefined) ?? null, + (approvedByAction?.roleCode as string | null | undefined) ?? null + ), + actedAt: approvedByAction?.actedAt ? new Date(String(approvedByAction.actedAt)).toISOString() : null + }, + authorizedBy: { + name: String(finalApprovedAction?.actorName ?? '-'), + position: resolvePosition( + (finalApprovedAction?.actedBy as string | null | undefined) ?? null, + (finalApprovedAction?.roleCode as string | null | undefined) ?? null + ), + actedAt: finalApprovedAction?.actedAt + ? new Date(String(finalApprovedAction.actedAt)).toISOString() + : null + } + } + }; + + const templateInput = { + ...mapDocumentDataToTemplateInput(documentData, mappings, columns), + ...topicInputs + }; + const normalizedTemplateInput = normalizeTemplateInputAliases( + extractTemplateFields(templateVersion.schemaJson), + templateInput + ); + + return { + organizationId: quotation.organizationId, + quotationId: quotation.id, + quotationCode: quotation.code, + templateVersionId: templateVersion.versionId, + approvedTemplateVersionId: + typeof quotation.approvedTemplateVersionId === 'string' + ? quotation.approvedTemplateVersionId + : null, + templateName: templateVersion.templateName, + templateVersion: templateVersion.version, + templateSchema: templateVersion.schemaJson, + mappings, + mappingColumns: columns, + documentData, + templateInput: normalizedTemplateInput, + topicInputs, + quotationMetrics: { + topicCount: topics.length, + itemCount: (itemRows as Array).length, + partyCount: (partyRows as Array).length + }, + approvedSnapshot: + quotation.approvedSnapshot && typeof quotation.approvedSnapshot === 'object' + ? (quotation.approvedSnapshot as Record) + : null, + approvedArtifactReference: + typeof quotation.approvedPdfUrl === 'string' ? quotation.approvedPdfUrl : null + }; +} + +export function summarizeIssues(statuses: Array<'PASS' | 'WARNING' | 'FAIL'>) { + if (statuses.includes('FAIL')) { + return 'FAIL'; + } + + if (statuses.includes('WARNING')) { + return 'WARNING'; + } + + return 'PASS'; +} + +function castRows(rows: unknown): T[] { + return rows as T[]; +} + +export interface ComputedSnapshotPayload { + quotationId: string; + approvedAt: string | null; + documentData: Record; + templateVersionId: string; + templateInput: Record; + generatedAt: string; + generatedBy: string; +} + +export function buildComputedSnapshotPayload( + payload: AuditPayload, + options?: { generatedAt?: string; generatedBy?: string } +): ComputedSnapshotPayload { + return { + quotationId: payload.quotationId, + approvedAt: + typeof (payload.documentData.approval as Record | undefined)?.approvedAt === + 'string' + ? ((payload.documentData.approval as Record).approvedAt as string) + : null, + documentData: payload.documentData, + templateVersionId: payload.templateVersionId, + templateInput: payload.templateInput, + generatedAt: options?.generatedAt ?? 'audit-generated', + generatedBy: options?.generatedBy ?? 'audit-suite' + }; +} + +export function getSnapshotTemplateInput(snapshot: Record | null) { + if (!snapshot) { + return null; + } + + return snapshot.templateInput && typeof snapshot.templateInput === 'object' + ? (snapshot.templateInput as Record) + : null; +} + +export function getSnapshotDocumentData(snapshot: Record | null) { + if (!snapshot) { + return null; + } + + return snapshot.documentData && typeof snapshot.documentData === 'object' + ? (snapshot.documentData as Record) + : null; +} diff --git a/scripts/seed-task-h1-fixture.js b/scripts/seed-task-h1-fixture.js index 6b3b1d5..db3f4dc 100644 --- a/scripts/seed-task-h1-fixture.js +++ b/scripts/seed-task-h1-fixture.js @@ -7,7 +7,10 @@ const TASK_H1_PASSWORD = 'TaskH1Pass!2026'; const FIXTURE_CODES = { customer: 'CUS-TASK-H1', approvedQuotation: 'QT-TASK-H1-APPROVED', - draftQuotation: 'QT-TASK-H1-DRAFT' + draftQuotation: 'QT-TASK-H1-DRAFT', + auditQuotation: 'QT-H5-AUDIT', + billingCustomer: 'CUS-TASK-H5-BILL', + consultantCustomer: 'CUS-TASK-H5-CONSULT' }; const FIXTURE_USERS = { @@ -373,6 +376,88 @@ async function ensureCustomerAndContact(sql, organization, creatorId, optionIds) }; } +async function ensureRelatedCustomer(sql, organization, creatorId, optionIds, definition) { + const existing = + ( + await sql` + select id + from crm_customers + where organization_id = ${organization.id} + and code = ${definition.code} + limit 1 + ` + )[0] ?? null; + const customerId = existing?.id ?? crypto.randomUUID(); + + if (existing) { + await sql` + update crm_customers + set + branch_id = ${optionIds.branchId}, + name = ${definition.name}, + abbr = ${definition.abbr}, + tax_id = ${definition.taxId}, + customer_type = ${optionIds.customerTypeCompanyId}, + customer_status = ${optionIds.customerStatusActiveId}, + address = ${definition.address}, + phone = ${definition.phone}, + email = ${definition.email}, + lead_channel = ${optionIds.leadChannelSalesId}, + customer_group = ${optionIds.customerGroupStandardId}, + notes = ${definition.notes}, + is_active = true, + deleted_at = null, + updated_at = now(), + updated_by = ${creatorId} + where id = ${customerId} + `; + } else { + await sql` + insert into crm_customers ( + id, + organization_id, + branch_id, + code, + name, + abbr, + tax_id, + customer_type, + customer_status, + address, + phone, + email, + lead_channel, + customer_group, + notes, + is_active, + created_by, + updated_by + ) values ( + ${customerId}, + ${organization.id}, + ${optionIds.branchId}, + ${definition.code}, + ${definition.name}, + ${definition.abbr}, + ${definition.taxId}, + ${optionIds.customerTypeCompanyId}, + ${optionIds.customerStatusActiveId}, + ${definition.address}, + ${definition.phone}, + ${definition.email}, + ${optionIds.leadChannelSalesId}, + ${optionIds.customerGroupStandardId}, + ${definition.notes}, + true, + ${creatorId}, + ${creatorId} + ) + `; + } + + return customerId; +} + async function upsertQuotation(sql, args) { const { organization, @@ -382,7 +467,9 @@ async function upsertQuotation(sql, args) { customerId, contactId, code, - approved + approved, + fixtureProfile = 'h1', + projectParties = [] } = args; const existing = ( @@ -397,9 +484,24 @@ async function upsertQuotation(sql, args) { const quotationId = existing?.id ?? crypto.randomUUID(); const statusId = approved ? optionIds.quotationStatusApprovedId : optionIds.quotationStatusDraftId; const approvalNow = approved ? new Date().toISOString() : null; - const notes = approved - ? 'Approved quotation fixture for Task H.1 PDF stabilization.' - : 'Draft quotation fixture for Task H.1 negative API validation.'; + const notes = + fixtureProfile === 'h5' + ? 'Approved quotation fixture for Task H.5 PDF mapping and parity audit.' + : approved + ? 'Approved quotation fixture for Task H.1 PDF stabilization.' + : 'Draft quotation fixture for Task H.1 negative API validation.'; + const projectName = + fixtureProfile === 'h5' + ? 'Task H.5 Audit Fixture' + : approved + ? 'Task H.1 Approved PDF Fixture' + : 'Task H.1 Draft PDF Fixture'; + const reference = + fixtureProfile === 'h5' + ? 'TASK-H5-AUDIT' + : approved + ? 'TASK-H1-APPROVED' + : 'TASK-H1-DRAFT'; if (existing) { await sql` @@ -412,10 +514,10 @@ async function upsertQuotation(sql, args) { quotation_date = ${'2026-06-16T00:00:00.000Z'}, valid_until = ${'2026-07-16T00:00:00.000Z'}, quotation_type = ${optionIds.quotationTypeServiceId}, - project_name = ${approved ? 'Task H.1 Approved PDF Fixture' : 'Task H.1 Draft PDF Fixture'}, + project_name = ${projectName}, project_location = 'Bangkok HQ', attention = 'Task H.1 Contact', - reference = ${approved ? 'TASK-H1-APPROVED' : 'TASK-H1-DRAFT'}, + reference = ${reference}, notes = ${notes}, status = ${statusId}, revision = 0, @@ -507,10 +609,10 @@ async function upsertQuotation(sql, args) { ${'2026-06-16T00:00:00.000Z'}, ${'2026-07-16T00:00:00.000Z'}, ${optionIds.quotationTypeServiceId}, - ${approved ? 'Task H.1 Approved PDF Fixture' : 'Task H.1 Draft PDF Fixture'}, + ${projectName}, 'Bangkok HQ', 'Task H.1 Contact', - ${approved ? 'TASK-H1-APPROVED' : 'TASK-H1-DRAFT'}, + ${reference}, ${notes}, ${statusId}, 0, @@ -572,77 +674,170 @@ async function upsertQuotation(sql, args) { await sql`delete from crm_quotation_topics where quotation_id = ${quotationId}`; await sql`delete from crm_quotation_items where quotation_id = ${quotationId}`; - await sql` - insert into crm_quotation_items ( - id, - organization_id, - quotation_id, - item_number, - product_type, - description, - quantity, - unit, - unit_price, - discount, - discount_type, - tax_rate, - total_price, - notes, - sort_order - ) values ( - ${crypto.randomUUID()}, - ${organization.id}, - ${quotationId}, - 1, - ${optionIds.productTypeCraneId}, - '1 set overhead crane with installation and commissioning', - 1, - ${optionIds.unitSetId}, - 125000, - 5000, - ${optionIds.discountPercentageId}, - 7, - 120000, - 'Smoke-test commercial line', - 1 - ) - `; + const items = + fixtureProfile === 'h5' + ? [ + { + itemNumber: 1, + description: '1 set overhead crane with installation and commissioning', + quantity: 1, + unitPrice: 125000, + discount: 5000, + totalPrice: 120000, + notes: 'Primary lifting package' + }, + { + itemNumber: 2, + description: 'Load testing and safety certification', + quantity: 1, + unitPrice: 25000, + discount: 0, + totalPrice: 25000, + notes: 'Audit fixture support line' + }, + { + itemNumber: 3, + description: 'Operator training and turnover documentation', + quantity: 2, + unitPrice: 8000, + discount: 0, + totalPrice: 16000, + notes: 'Audit fixture service line' + } + ] + : [ + { + itemNumber: 1, + description: '1 set overhead crane with installation and commissioning', + quantity: 1, + unitPrice: 125000, + discount: 5000, + totalPrice: 120000, + notes: 'Smoke-test commercial line' + } + ]; - await sql` - insert into crm_quotation_customers ( - id, - organization_id, - quotation_id, - customer_id, - role, - is_primary - ) values ( - ${crypto.randomUUID()}, - ${organization.id}, - ${quotationId}, - ${customerId}, - ${optionIds.customerRoleOwnerId}, - true - ) - `; + for (const [index, item] of items.entries()) { + await sql` + insert into crm_quotation_items ( + id, + organization_id, + quotation_id, + item_number, + product_type, + description, + quantity, + unit, + unit_price, + discount, + discount_type, + tax_rate, + total_price, + notes, + sort_order + ) values ( + ${crypto.randomUUID()}, + ${organization.id}, + ${quotationId}, + ${item.itemNumber}, + ${optionIds.productTypeCraneId}, + ${item.description}, + ${item.quantity}, + ${optionIds.unitSetId}, + ${item.unitPrice}, + ${item.discount}, + ${optionIds.discountPercentageId}, + 7, + ${item.totalPrice}, + ${item.notes}, + ${index + 1} + ) + `; + } - const topics = [ - { - typeId: optionIds.topicTypeScopeId, - title: 'Scope of work', - items: ['Supply crane equipment', 'Install and test the system'] - }, - { - typeId: optionIds.topicTypeExclusionId, - title: 'Exclusions', - items: ['Civil work by customer', 'Power feeder by customer'] - }, - { - typeId: optionIds.topicTypePaymentId, - title: 'Payment terms', - items: ['50% upon PO', '40% before delivery', '10% after commissioning'] - } - ]; + const resolvedProjectParties = + projectParties.length > 0 + ? projectParties + : [ + { + customerId, + role: optionIds.customerRoleCustomerId, + isPrimary: true + } + ]; + + for (const [index, party] of resolvedProjectParties.entries()) { + await sql` + insert into crm_quotation_customers ( + id, + organization_id, + quotation_id, + customer_id, + role, + is_primary, + remark + ) values ( + ${crypto.randomUUID()}, + ${organization.id}, + ${quotationId}, + ${party.customerId}, + ${party.role}, + ${party.isPrimary}, + ${party.remark ?? `Fixture party ${index + 1}`} + ) + `; + } + + const topics = + fixtureProfile === 'h5' + ? [ + { + typeId: optionIds.topicTypeScopeId, + title: 'Scope', + items: [ + 'Supply crane equipment', + 'Install and test the system', + 'Perform SAT with project stakeholders' + ] + }, + { + typeId: optionIds.topicTypeExclusionId, + title: 'Exclusion', + items: ['Civil work by customer', 'Power feeder by customer'] + }, + { + typeId: optionIds.topicTypePaymentId, + title: 'Payment', + items: ['50% upon PO', '40% before delivery', '10% after commissioning'] + }, + { + typeId: 'warranty', + title: 'Warranty', + items: ['12 months after commissioning', 'Consumables excluded'] + }, + { + typeId: 'delivery', + title: 'Delivery', + items: ['Within 60 days after approved drawing', 'Subject to final site readiness'] + } + ] + : [ + { + typeId: optionIds.topicTypeScopeId, + title: 'Scope of work', + items: ['Supply crane equipment', 'Install and test the system'] + }, + { + typeId: optionIds.topicTypeExclusionId, + title: 'Exclusions', + items: ['Civil work by customer', 'Power feeder by customer'] + }, + { + typeId: optionIds.topicTypePaymentId, + title: 'Payment terms', + items: ['50% upon PO', '40% before delivery', '10% after commissioning'] + } + ]; for (const [topicIndex, topic] of topics.entries()) { const topicId = crypto.randomUUID(); @@ -851,11 +1046,23 @@ async function main() { 'crm_product_type', 'crane' ), - customerRoleOwnerId: await resolveOptionId( + customerRoleCustomerId: await resolveOptionId( sql, organization.id, - 'crm_quotation_customer_role', - 'owner' + 'crm_project_party_role', + 'customer' + ), + customerRoleBillingCustomerId: await resolveOptionId( + sql, + organization.id, + 'crm_project_party_role', + 'billing_customer' + ), + customerRoleConsultantId: await resolveOptionId( + sql, + organization.id, + 'crm_project_party_role', + 'consultant' ), topicTypeScopeId: await resolveOptionId( sql, @@ -884,6 +1091,32 @@ async function main() { creatorId, optionIds ); + const billingCustomerId = await ensureRelatedCustomer(sql, organization, creatorId, optionIds, { + code: FIXTURE_CODES.billingCustomer, + name: 'Task H.5 Billing Customer', + abbr: 'TASKH5BILL', + taxId: '0105559002233', + address: '88 Audit Park Road, Bangkok 10240', + phone: '02-000-2000', + email: 'billing-customer@local.test', + notes: 'Billing customer for Task H.5 parity fixture' + }); + const consultantCustomerId = await ensureRelatedCustomer( + sql, + organization, + creatorId, + optionIds, + { + code: FIXTURE_CODES.consultantCustomer, + name: 'Task H.5 Consultant', + abbr: 'TASKH5CONS', + taxId: '0105559003344', + address: '77 Review Lane, Bangkok 10110', + phone: '02-000-3000', + email: 'consultant-customer@local.test', + notes: 'Consultant customer for Task H.5 parity fixture' + } + ); const users = { admin: await ensureUser(sql, organization, creatorId, FIXTURE_USERS.admin, 'Task H.1 Admin', { @@ -930,6 +1163,37 @@ async function main() { code: FIXTURE_CODES.draftQuotation, approved: false }); + const auditQuotationId = await upsertQuotation(sql, { + organization, + creatorId: users.admin.id, + workflow, + optionIds, + customerId, + contactId, + code: FIXTURE_CODES.auditQuotation, + approved: true, + fixtureProfile: 'h5', + projectParties: [ + { + customerId, + role: optionIds.customerRoleCustomerId, + isPrimary: true, + remark: 'End customer' + }, + { + customerId: billingCustomerId, + role: optionIds.customerRoleBillingCustomerId, + isPrimary: false, + remark: 'Billing customer' + }, + { + customerId: consultantCustomerId, + role: optionIds.customerRoleConsultantId, + isPrimary: false, + remark: 'Consultant' + } + ] + }); console.log( JSON.stringify( @@ -939,6 +1203,8 @@ async function main() { approvedQuotationCode: FIXTURE_CODES.approvedQuotation, draftQuotationId, draftQuotationCode: FIXTURE_CODES.draftQuotation, + auditQuotationId, + auditQuotationCode: FIXTURE_CODES.auditQuotation, users, password: TASK_H1_PASSWORD }, diff --git a/src/app/page.tsx b/src/app/page.tsx index f39ca24..2c64fae 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,12 +1,12 @@ -import { auth } from '@/auth'; -import { redirect } from 'next/navigation'; +import { auth } from "@/auth"; +import { redirect } from "next/navigation"; export default async function Page() { const session = await auth(); if (!session?.user?.id) { - return redirect('/auth/sign-in'); + return redirect("/auth/sign-in"); } else { - redirect('/dashboard'); + redirect("/dashboard/crm"); } } diff --git a/src/config/nav-config.ts b/src/config/nav-config.ts index 7d99995..71c9e57 100644 --- a/src/config/nav-config.ts +++ b/src/config/nav-config.ts @@ -34,52 +34,52 @@ import { NavGroup } from "@/types"; * Use the `access` property for new items. */ export const navGroups: NavGroup[] = [ - // { - // label: "Overview", - // items: [ - // { - // title: "Dashboard Overview", - // url: "/dashboard", - // icon: "dashboard", - // isActive: false, - // shortcut: ["d", "d"], - // items: [], - // }, - // { - // title: "Workspaces", - // url: "/dashboard/workspaces", - // icon: "workspace", - // isActive: false, - // items: [], - // access: { systemRole: "super_admin" }, - // }, - // { - // title: "Teams", - // url: "/dashboard/workspaces/team", - // icon: "teams", - // isActive: false, - // items: [], - // access: { requireOrg: true, role: "admin" }, - // }, - // { - // title: "Users", - // url: "/dashboard/users", - // icon: "teams", - // shortcut: ["u", "u"], - // isActive: false, - // items: [], - // access: { requireOrg: true, permission: "users:manage" }, - // }, - // { - // title: "Kanban", - // url: "/dashboard/kanban", - // icon: "kanban", - // shortcut: ["k", "k"], - // isActive: false, - // items: [], - // }, - // ], - // }, + { + label: "Overview", + items: [ + { + title: "Dashboard Overview", + url: "/dashboard", + icon: "dashboard", + isActive: false, + shortcut: ["d", "d"], + items: [], + }, + { + title: "Workspaces", + url: "/dashboard/workspaces", + icon: "workspace", + isActive: false, + items: [], + access: { systemRole: "super_admin" }, + }, + { + title: "Teams", + url: "/dashboard/workspaces/team", + icon: "teams", + isActive: false, + items: [], + access: { requireOrg: true, role: "admin" }, + }, + { + title: "Users", + url: "/dashboard/users", + icon: "teams", + shortcut: ["u", "u"], + isActive: false, + items: [], + access: { requireOrg: true, permission: "users:manage" }, + }, + { + title: "Kanban", + url: "/dashboard/kanban", + icon: "kanban", + shortcut: ["k", "k"], + isActive: false, + items: [], + }, + ], + }, { label: "CRM", items: [ diff --git a/src/features/crm/quotations/document/server/service.ts b/src/features/crm/quotations/document/server/service.ts index 5a87056..2748981 100644 --- a/src/features/crm/quotations/document/server/service.ts +++ b/src/features/crm/quotations/document/server/service.ts @@ -87,6 +87,12 @@ function extractSinglePlaceholder(value: string) { return uniqueMatches.length === 1 ? uniqueMatches[0] : null; } +function getFieldName(field: unknown) { + return field && typeof field === 'object' && typeof (field as { name?: unknown }).name === 'string' + ? ((field as { name: string }).name ?? '') + : ''; +} + function normalizeTopicKey(value: string | null | undefined) { return ( value @@ -165,13 +171,26 @@ function normalizePdfmeTemplateInput(schemaJson: unknown, templateInput: Record< } const tableField = field as { type?: unknown; content?: unknown }; + const fieldName = getFieldName(field); + const placeholderKey = + typeof tableField.content === 'string' + ? extractSinglePlaceholder(tableField.content) + : null; + + if (fieldName && placeholderKey && fieldName !== placeholderKey) { + if (normalized[fieldName] !== undefined && normalized[placeholderKey] === undefined) { + normalized[placeholderKey] = normalized[fieldName]; + } + + if (normalized[placeholderKey] !== undefined && normalized[fieldName] === undefined) { + normalized[fieldName] = normalized[placeholderKey]; + } + } if (tableField.type !== 'table' || typeof tableField.content !== 'string') { continue; } - const placeholderKey = extractSinglePlaceholder(tableField.content); - if (!placeholderKey) { continue; } diff --git a/storage/organizations/2330c067-2737-4443-bd6e-ccb8a6cf856b/quotations/approved/3ba9c39c-8121-416a-99ed-59c4d120c72f-QT-H5-AUDIT-approved.pdf b/storage/organizations/2330c067-2737-4443-bd6e-ccb8a6cf856b/quotations/approved/3ba9c39c-8121-416a-99ed-59c4d120c72f-QT-H5-AUDIT-approved.pdf new file mode 100644 index 0000000000000000000000000000000000000000..34e8a6d8d0d8c76f87355a3ab5c823f67e7dc206 GIT binary patch literal 126520 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