task-4.3.1

This commit is contained in:
phaichayon
2026-06-29 11:57:37 +07:00
parent 0c0450b152
commit 94fe19c658
17 changed files with 836 additions and 174 deletions

View File

@@ -1,13 +1,13 @@
{
"audit": "payload-parity",
"overallStatus": "PASS",
"quotationCode": "QT-H5-AUDIT",
"previewHash": "1914aaab07b562d967c63d9f3bdea6d44b583e4aac39ea590d08e49975391675",
"generationHash": "1914aaab07b562d967c63d9f3bdea6d44b583e4aac39ea590d08e49975391675",
"snapshotHash": "1914aaab07b562d967c63d9f3bdea6d44b583e4aac39ea590d08e49975391675",
"quotationCode": "QT2606-1122",
"previewHash": "8ab32cb3cf38b76f6bbc4c5704f89fca6a3d1bbaeed0b1d0488cf33217f0f39a",
"generationHash": "8ab32cb3cf38b76f6bbc4c5704f89fca6a3d1bbaeed0b1d0488cf33217f0f39a",
"snapshotHash": "8ab32cb3cf38b76f6bbc4c5704f89fca6a3d1bbaeed0b1d0488cf33217f0f39a",
"mismatches": [],
"comparableFieldCount": 35,
"topicInputKeyCount": 10,
"comparableFieldCount": 33,
"topicInputKeyCount": 6,
"signatureParity": true,
"priceTableParity": true,
"priceTableRowCount": 1,

View File

@@ -1,30 +1,34 @@
{
"audit": "placeholder-integrity",
"overallStatus": "PASS",
"overallStatus": "FAIL",
"results": [
{
"templateName": "ALLA Quotation Standard",
"organizationId": "2330c067-2737-4443-bd6e-ccb8a6cf856b",
"version": "1.1",
"templateName": "ONVALLA Quotation Standard",
"organizationId": "6bf90327-2ce9-4831-93bb-e8d026699ff2",
"version": "1.0",
"duplicateFieldNames": [],
"typoFields": [],
"unmappedPlaceholders": [],
"deadMappings": [],
"deadMappings": [
"exclusion_data"
],
"invalidReservedMappings": [],
"missingReservedFields": [],
"status": "PASS"
"status": "FAIL"
},
{
"templateName": "ONVALLA Quotation Standard",
"organizationId": "8cd23975-e199-4ed0-85a9-8cbd01c810d2",
"version": "1.1",
"templateName": "ALLA Quotation Standard",
"organizationId": "d5c46139-9af4-421b-9395-d83620c62f9b",
"version": "1.0",
"duplicateFieldNames": [],
"typoFields": [],
"unmappedPlaceholders": [],
"deadMappings": [],
"deadMappings": [
"exclusion_data"
],
"invalidReservedMappings": [],
"missingReservedFields": [],
"status": "PASS"
"status": "FAIL"
}
]
}

View File

@@ -1,11 +1,11 @@
{
"audit": "runtime-payload",
"overallStatus": "FAIL",
"staticLabelsStatus": "FAIL",
"quotationCode": "QT2606-1126",
"overallStatus": "PASS",
"staticLabelsStatus": "PASS",
"quotationCode": "QT2606-1122",
"templateName": "ALLA Quotation Standard",
"templateVersion": "1.0",
"findingCount": 165,
"findingCount": 170,
"findings": [
{
"field": "documentData.company.id",
@@ -25,28 +25,28 @@
"field": "documentData.customer.id",
"source": "documentData",
"status": "PASS",
"value": "ba10e844-997e-45ed-8696-7ca96730a859",
"value": "56f1f9a3-6204-4ccc-af0f-5dbf866d5a56",
"message": "String is populated"
},
{
"field": "documentData.customer.name",
"source": "documentData",
"status": "PASS",
"value": "มหาวิทยาลัยเทคโนโลยีพระจอมเกล้าพระนครเหนือ",
"value": "บริษัท นอร์ธเทิร์น แมชชีนเนอรี่ ดีลเลอร์ จำกัด",
"message": "String is populated"
},
{
"field": "documentData.customer.address",
"source": "documentData",
"status": "PASS",
"value": "Bang Sue, Bangkok",
"value": "Mueang Chiang Mai, Chiang Mai",
"message": "String is populated"
},
{
"field": "documentData.customer.phone",
"source": "documentData",
"status": "PASS",
"value": "02-400827",
"value": "02-400824",
"message": "String is populated"
},
{
@@ -60,14 +60,14 @@
"field": "documentData.contact.id",
"source": "documentData",
"status": "PASS",
"value": "2591dec2-b7c9-486a-9810-8ab9bf0f4926",
"value": "93843e06-65db-492f-9229-066e6dfdec5d",
"message": "String is populated"
},
{
"field": "documentData.contact.name",
"source": "documentData",
"status": "PASS",
"value": "คุณวราภรณ์ เกษมศรี",
"value": "คุณกิตติพงศ์ สุวรรณ",
"message": "String is populated"
},
{
@@ -81,56 +81,56 @@
"field": "documentData.contact.mobile",
"source": "documentData",
"status": "PASS",
"value": "0810000250",
"value": "0810000220",
"message": "String is populated"
},
{
"field": "documentData.contact.position",
"source": "documentData",
"status": "PASS",
"value": "Procurement Manager",
"value": "Plant Manager",
"message": "String is populated"
},
{
"field": "documentData.quotation.id",
"source": "documentData",
"status": "PASS",
"value": "c964ec57-c036-404b-93c5-6826d6539d1e",
"value": "7dc0630b-e27d-4696-803a-d93f51bb9804",
"message": "String is populated"
},
{
"field": "documentData.quotation.code",
"source": "documentData",
"status": "PASS",
"value": "QT2606-1126",
"value": "QT2606-1122",
"message": "String is populated"
},
{
"field": "documentData.quotation.projectName",
"source": "documentData",
"status": "PASS",
"value": "Material Handling Lab Crane",
"value": "Dealer Stock Crane Package",
"message": "String is populated"
},
{
"field": "documentData.quotation.projectLocation",
"source": "documentData",
"status": "PASS",
"value": "Bang Sue Engineering Faculty",
"value": "Chiang Mai Service Warehouse",
"message": "String is populated"
},
{
"field": "documentData.quotation.attention",
"source": "documentData",
"status": "PASS",
"value": "คุณวราภรณ์ เกษมศรี",
"value": "คุณกิตติพงศ์ สุวรรณ",
"message": "String is populated"
},
{
"field": "documentData.quotation.reference",
"source": "documentData",
"status": "PASS",
"value": "crm-uat-seed-v1:university-lab-crane-approved",
"value": "crm-uat-seed-v1:dealer-channel-approved",
"message": "String is populated"
},
{
@@ -151,14 +151,14 @@
"field": "documentData.quotation.subtotal",
"source": "documentData",
"status": "PASS",
"value": 3008000,
"value": 4418000,
"message": "Scalar value is populated"
},
{
"field": "documentData.quotation.totalAmount",
"source": "documentData",
"status": "PASS",
"value": 3122003,
"value": 4585442,
"message": "Scalar value is populated"
},
{
@@ -172,7 +172,7 @@
"field": "documentData.items[0].id",
"source": "documentData",
"status": "PASS",
"value": "4b151f4b-ff48-4dcc-b092-d891ff6f5a29",
"value": "7b2a99ab-5106-4ab1-b611-cd63957aa38f",
"message": "String is populated"
},
{
@@ -186,7 +186,7 @@
"field": "documentData.items[0].description",
"source": "documentData",
"status": "PASS",
"value": "Main equipment supply for Material Handling Lab Crane",
"value": "Main equipment supply for Dealer Stock Crane Package",
"message": "String is populated"
},
{
@@ -207,7 +207,7 @@
"field": "documentData.items[0].unitPrice",
"source": "documentData",
"status": "PASS",
"value": 1717102,
"value": 2521993,
"message": "Scalar value is populated"
},
{
@@ -221,14 +221,14 @@
"field": "documentData.items[0].totalPrice",
"source": "documentData",
"status": "PASS",
"value": 1717102,
"value": 2521993,
"message": "Scalar value is populated"
},
{
"field": "documentData.items[1].id",
"source": "documentData",
"status": "PASS",
"value": "43c6d8c7-4af3-4e35-80af-5bb1beeffdc7",
"value": "cc058b8e-2ce1-4e66-a72c-6c40474cf962",
"message": "String is populated"
},
{
@@ -242,7 +242,7 @@
"field": "documentData.items[1].description",
"source": "documentData",
"status": "PASS",
"value": "Installation and commissioning for Material Handling Lab Crane",
"value": "Installation and commissioning for Dealer Stock Crane Package",
"message": "String is populated"
},
{
@@ -263,7 +263,7 @@
"field": "documentData.items[1].unitPrice",
"source": "documentData",
"status": "PASS",
"value": 874161,
"value": 1283924,
"message": "Scalar value is populated"
},
{
@@ -277,14 +277,14 @@
"field": "documentData.items[1].totalPrice",
"source": "documentData",
"status": "PASS",
"value": 874161,
"value": 1283924,
"message": "Scalar value is populated"
},
{
"field": "documentData.items[2].id",
"source": "documentData",
"status": "PASS",
"value": "b2aca42a-0592-4725-9ecd-89c8287b4b44",
"value": "9436e5e6-6dc7-4fa6-bf8a-da2727992680",
"message": "String is populated"
},
{
@@ -298,7 +298,7 @@
"field": "documentData.items[2].description",
"source": "documentData",
"status": "PASS",
"value": "Operator training and documentation for Material Handling Lab Crane",
"value": "Operator training and documentation for Dealer Stock Crane Package",
"message": "String is populated"
},
{
@@ -319,7 +319,7 @@
"field": "documentData.items[2].unitPrice",
"source": "documentData",
"status": "PASS",
"value": 312200,
"value": 458544,
"message": "Scalar value is populated"
},
{
@@ -333,14 +333,14 @@
"field": "documentData.items[2].totalPrice",
"source": "documentData",
"status": "PASS",
"value": 312200,
"value": 458544,
"message": "Scalar value is populated"
},
{
"field": "documentData.items[3].id",
"source": "documentData",
"status": "PASS",
"value": "bb721763-f87f-4a92-8899-36e690eede66",
"value": "63d82897-1660-4f49-b0d1-fddb9712922f",
"message": "String is populated"
},
{
@@ -354,7 +354,7 @@
"field": "documentData.items[3].description",
"source": "documentData",
"status": "PASS",
"value": "Project management and testing for Material Handling Lab Crane",
"value": "Project management and testing for Dealer Stock Crane Package",
"message": "String is populated"
},
{
@@ -375,7 +375,7 @@
"field": "documentData.items[3].unitPrice",
"source": "documentData",
"status": "PASS",
"value": 218540,
"value": 320981,
"message": "Scalar value is populated"
},
{
@@ -389,7 +389,7 @@
"field": "documentData.items[3].totalPrice",
"source": "documentData",
"status": "PASS",
"value": 218540,
"value": 320981,
"message": "Scalar value is populated"
},
{
@@ -403,14 +403,14 @@
"field": "documentData.quotationCustomers[0].id",
"source": "documentData",
"status": "PASS",
"value": "8de4be97-45d0-48f7-b6a6-962077b63389",
"value": "7a20818a-48a1-4eed-a779-ac5ec9d6159b",
"message": "String is populated"
},
{
"field": "documentData.quotationCustomers[0].customerId",
"source": "documentData",
"status": "PASS",
"value": "ba10e844-997e-45ed-8696-7ca96730a859",
"value": "56f1f9a3-6204-4ccc-af0f-5dbf866d5a56",
"message": "String is populated"
},
{
@@ -438,7 +438,7 @@
"field": "documentData.topics.all[0].id",
"source": "documentData",
"status": "PASS",
"value": "1c97eb84-131e-49ad-97ca-a59547223c8d",
"value": "ef076be2-fbef-444a-b7cf-cc2523e8b7dc",
"message": "String is populated"
},
{
@@ -473,7 +473,7 @@
"field": "documentData.topics.all[0].items[0].content",
"source": "documentData",
"status": "PASS",
"value": "Supply and execute material handling lab crane according to approved drawing.",
"value": "Supply and execute dealer stock crane package according to approved drawing.",
"message": "String is populated"
},
{
@@ -501,7 +501,7 @@
"field": "documentData.topics.all[1].id",
"source": "documentData",
"status": "PASS",
"value": "49935121-792a-405c-8a6f-fbdbc8bdda65",
"value": "14f41b98-f9d8-463b-9880-8e96d258a6ba",
"message": "String is populated"
},
{
@@ -564,7 +564,7 @@
"field": "documentData.topics.all[2].id",
"source": "documentData",
"status": "PASS",
"value": "5685dff7-add1-407e-9e6a-e16d6ea8d1be",
"value": "54500c11-5f51-49d6-947e-db2f4d689959",
"message": "String is populated"
},
{
@@ -634,7 +634,7 @@
"field": "documentData.topics.exclusion[0].id",
"source": "documentData",
"status": "PASS",
"value": "49935121-792a-405c-8a6f-fbdbc8bdda65",
"value": "14f41b98-f9d8-463b-9880-8e96d258a6ba",
"message": "String is populated"
},
{
@@ -732,14 +732,14 @@
"field": "documentData.pdfme.quotation_date",
"source": "documentData",
"status": "PASS",
"value": "Jun 15, 2026",
"value": "Jun 3, 2026",
"message": "String is populated"
},
{
"field": "documentData.pdfme.quotation_price",
"source": "documentData",
"status": "PASS",
"value": "THB 3,008,000.00",
"value": "THB 4,418,000.00",
"message": "String is populated"
},
{
@@ -816,29 +816,29 @@
"field": "documentData.signatures.preparedBy.actedAt",
"source": "documentData",
"status": "PASS",
"value": "2026-06-15T09:00:00.000Z",
"value": "2026-06-03T09:00:00.000Z",
"message": "String is populated"
},
{
"field": "documentData.signatures.approvedBy.name",
"source": "documentData",
"status": "PASS",
"value": "-",
"value": "Super Admin",
"message": "String is populated"
},
{
"field": "documentData.signatures.approvedBy.position",
"source": "documentData",
"status": "PASS",
"value": "-",
"value": "Sales Manager",
"message": "String is populated"
},
{
"field": "documentData.signatures.approvedBy.actedAt",
"source": "documentData",
"status": "WARNING",
"value": null,
"message": "Value is null"
"status": "PASS",
"value": "2026-06-06T10:00:00.000Z",
"message": "String is populated"
},
{
"field": "documentData.signatures.authorizedBy.name",
@@ -858,28 +858,28 @@
"field": "documentData.signatures.authorizedBy.actedAt",
"source": "documentData",
"status": "PASS",
"value": "2026-06-17T11:00:00.000Z",
"value": "2026-06-07T09:00:00.000Z",
"message": "String is populated"
},
{
"field": "templateInput.customer_name",
"source": "templateInput",
"status": "PASS",
"value": "มหาวิทยาลัยเทคโนโลยีพระจอมเกล้าพระนครเหนือ",
"value": "บริษัท นอร์ธเทิร์น แมชชีนเนอรี่ ดีลเลอร์ จำกัด",
"message": "String is populated"
},
{
"field": "templateInput.customer_addr",
"source": "templateInput",
"status": "PASS",
"value": "Bang Sue, Bangkok",
"value": "Mueang Chiang Mai, Chiang Mai",
"message": "String is populated"
},
{
"field": "templateInput.customer_tel",
"source": "templateInput",
"status": "PASS",
"value": "02-400827",
"value": "02-400824",
"message": "String is populated"
},
{
@@ -893,35 +893,35 @@
"field": "templateInput.customer_att",
"source": "templateInput",
"status": "PASS",
"value": "คุณวราภรณ์ เกษมศรี",
"value": "คุณกิตติพงศ์ สุวรรณ",
"message": "String is populated"
},
{
"field": "templateInput.project_name",
"source": "templateInput",
"status": "PASS",
"value": "Material Handling Lab Crane",
"value": "Dealer Stock Crane Package",
"message": "String is populated"
},
{
"field": "templateInput.site_location",
"source": "templateInput",
"status": "PASS",
"value": "Bang Sue Engineering Faculty",
"value": "Chiang Mai Service Warehouse",
"message": "String is populated"
},
{
"field": "templateInput.quotation_date",
"source": "templateInput",
"status": "PASS",
"value": "Jun 15, 2026",
"value": "Jun 3, 2026",
"message": "String is populated"
},
{
"field": "templateInput.quotation_code",
"source": "templateInput",
"status": "PASS",
"value": "QT2606-1126",
"value": "QT2606-1122",
"message": "String is populated"
},
{
@@ -935,7 +935,7 @@
"field": "templateInput.quotation_price",
"source": "templateInput",
"status": "PASS",
"value": "THB 3,008,000.00",
"value": "THB 4,418,000.00",
"message": "String is populated"
},
{
@@ -970,14 +970,14 @@
"field": "templateInput.app2",
"source": "templateInput",
"status": "PASS",
"value": "-",
"value": "Super Admin",
"message": "String is populated"
},
{
"field": "templateInput.app2_position",
"source": "templateInput",
"status": "PASS",
"value": "-",
"value": "Sales Manager",
"message": "String is populated"
},
{
@@ -1001,6 +1001,41 @@
"value": "rows:4",
"message": "Array shape is valid"
},
{
"field": "templateInput.tel_label",
"source": "templateInput",
"status": "PASS",
"value": "Tel",
"message": "String is populated"
},
{
"field": "templateInput.email_label",
"source": "templateInput",
"status": "PASS",
"value": "Email",
"message": "String is populated"
},
{
"field": "templateInput.att_label",
"source": "templateInput",
"status": "PASS",
"value": "Att",
"message": "String is populated"
},
{
"field": "templateInput.project_label",
"source": "templateInput",
"status": "PASS",
"value": "Project",
"message": "String is populated"
},
{
"field": "templateInput.site_label",
"source": "templateInput",
"status": "PASS",
"value": "Location",
"message": "String is populated"
},
{
"field": "templateInput.topic_1_0",
"source": "templateInput",
@@ -1047,14 +1082,14 @@
"field": "templateInput.quotation_date_data",
"source": "templateInput",
"status": "PASS",
"value": "Jun 15, 2026",
"value": "Jun 3, 2026",
"message": "String is populated"
},
{
"field": "templateInput.quotation_code_data",
"source": "templateInput",
"status": "PASS",
"value": "QT2606-1126",
"value": "QT2606-1122",
"message": "String is populated"
},
{
@@ -1117,7 +1152,7 @@
"field": "templateInput.quotation_price_data[0][1]",
"source": "templateInput",
"status": "PASS",
"value": "฿3,008,000.00",
"value": "฿4,418,000.00",
"message": "Price table amount cell is formatted"
},
{
@@ -1130,37 +1165,37 @@
{
"field": "templateInput.tel_label",
"source": "templateInput",
"status": "FAIL",
"value": null,
"message": "Static label is empty"
"status": "PASS",
"value": "Tel",
"message": "Static label is populated"
},
{
"field": "templateInput.email_label",
"source": "templateInput",
"status": "FAIL",
"value": null,
"message": "Static label is empty"
"status": "PASS",
"value": "Email",
"message": "Static label is populated"
},
{
"field": "templateInput.att_label",
"source": "templateInput",
"status": "FAIL",
"value": null,
"message": "Static label is empty"
"status": "PASS",
"value": "Att",
"message": "Static label is populated"
},
{
"field": "templateInput.project_label",
"source": "templateInput",
"status": "FAIL",
"value": null,
"message": "Static label is empty"
"status": "PASS",
"value": "Project",
"message": "Static label is populated"
},
{
"field": "templateInput.site_label",
"source": "templateInput",
"status": "FAIL",
"value": null,
"message": "Static label is empty"
"status": "PASS",
"value": "Location",
"message": "Static label is populated"
}
]
}

View File

@@ -1,13 +1,22 @@
{
"audit": "approved-snapshot-parity",
"overallStatus": "PASS",
"quotationCode": "QT-H5-AUDIT",
"overallStatus": "FAIL",
"quotationCode": "QT2606-1122",
"approvedSnapshotAvailable": true,
"approvedTemplateVersionId": "bd6713d5-a731-482f-a5bb-0dea9d766301",
"computedTemplateVersionId": "bd6713d5-a731-482f-a5bb-0dea9d766301",
"inconsistentTemplateKeys": [],
"computedSignatureHash": "39e2c42458c8b08b4299dc3136c9812c311ae7886df91078f4c9ae6e87bdcc00",
"storedSignatureHash": "39e2c42458c8b08b4299dc3136c9812c311ae7886df91078f4c9ae6e87bdcc00",
"approvedArtifactReference": "artifact:a1fd951d-f0b4-4e5b-be88-073596b3829d",
"approvedTemplateVersionId": null,
"computedTemplateVersionId": "6c375f16-3f89-4987-88b0-c8ebe48aeb89",
"inconsistentTemplateKeys": [
"quotation_date",
"topic_1_0",
"item_topic_1_0",
"topic_1_1",
"item_topic_1_1",
"topic_1_2",
"item_topic_1_2",
"quotation_date_data"
],
"computedSignatureHash": "72c05979b9a0b0666a06cce5b80dc67fe9c5493eced4366a4cf63785dc415269",
"storedSignatureHash": "933007a4f0e782b88e3d3049527a25b5bcd744d4a483282050fbfc1b7f06a362",
"approvedArtifactReference": "artifact:ec300b87-3710-43a4-a63c-a72168fe3933",
"refreshedFixtureSnapshot": false
}

View File

@@ -1,7 +1,7 @@
{
"generatedAt": "2026-06-19T10:12:54.977Z",
"overallStatus": "PASS",
"quotationCode": "QT-H5-AUDIT",
"generatedAt": "2026-06-29T04:38:30.133Z",
"overallStatus": "FAIL",
"quotationCode": "QT2606-1122",
"artifacts": {
"templateVersionReport": "template-version-report.json",
"placeholderIntegrityReport": "placeholder-integrity-report.json",
@@ -12,11 +12,11 @@
},
"statuses": {
"templateVersion": "PASS",
"placeholderIntegrity": "PASS",
"placeholderIntegrity": "FAIL",
"runtimePayload": "PASS",
"payloadParity": "PASS",
"topicRuntime": "PASS",
"approvedSnapshotParity": "PASS",
"approvedSnapshotParity": "FAIL",
"staticLabels": "PASS"
},
"priceTable": {

View File

@@ -1,17 +1,17 @@
# PDF Integrity Summary
- Generated At: 2026-06-19T10:12:54.977Z
- Overall Status: PASS
- Quotation Code: QT-H5-AUDIT
- Generated At: 2026-06-29T04:38:30.133Z
- Overall Status: FAIL
- Quotation Code: QT2606-1122
## Statuses
- Template Version Integrity: PASS
- Placeholder Integrity: PASS
- Placeholder Integrity: FAIL
- Runtime Payload: PASS
- Payload Parity: PASS
- Topic Runtime: PASS
- Approved Snapshot Parity: PASS
- Approved Snapshot Parity: FAIL
- Static Labels: PASS
- Price Table Present: Yes
- Price Table Status: PASS

View File

@@ -2,8 +2,8 @@
"audit": "template-version-integrity",
"overallStatus": "PASS",
"activeTemplateCount": 2,
"quotationCode": "QT-H5-AUDIT",
"approvedTemplateVersionId": "bd6713d5-a731-482f-a5bb-0dea9d766301",
"approvedTemplateVersionIsActive": true,
"quotationCode": "QT2606-1122",
"approvedTemplateVersionId": null,
"approvedTemplateVersionIsActive": false,
"issues": []
}

View File

@@ -1,21 +1,17 @@
{
"audit": "topic-runtime",
"overallStatus": "PASS",
"quotationCode": "QT-H5-AUDIT",
"topicCount": 5,
"quotationCode": "QT2606-1122",
"topicCount": 3,
"topicInputKeys": [
"topic_1_0",
"topic_1_1",
"topic_1_2",
"topic_1_3",
"topic_1_4"
"topic_1_2"
],
"itemTopicInputKeys": [
"item_topic_1_0",
"item_topic_1_1",
"item_topic_1_2",
"item_topic_1_3",
"item_topic_1_4"
"item_topic_1_2"
],
"duplicateKeys": [],
"emptySections": []

View File

@@ -1,19 +1,61 @@
{
"generatedAt": "2026-06-19T10:12:54.188Z",
"overallStatus": "PASS",
"quotationCode": "QT-H5-AUDIT",
"generatedAt": "2026-06-29T04:38:25.895Z",
"overallStatus": "FAIL",
"quotationCode": "QT2606-1122",
"expectedFixtureCode": "QT-H5-AUDIT",
"template": {
"templateName": "ALLA Quotation Standard",
"version": "1.1",
"versionId": "bd6713d5-a731-482f-a5bb-0dea9d766301",
"version": "1.0",
"versionId": "6c375f16-3f89-4987-88b0-c8ebe48aeb89",
"fieldCount": 44,
"previousVersion": "1.0",
"deletedFields": [
"exclusion_data",
"exclusion_label"
"previousVersion": null,
"deletedFields": [],
"newFields": [
"Please_do_not",
"app1",
"app1_position",
"app2",
"app2_position",
"app3",
"app3_position",
"att_label",
"colon",
"colon copy",
"colon_att",
"colon_email",
"colon_fax",
"colon_project",
"colon_site",
"colon_tel",
"company1",
"company2",
"company_addr1",
"company_addr2",
"company_email",
"company_tax",
"company_tel",
"customer_addr",
"customer_att",
"customer_email",
"customer_name",
"customer_tel",
"data_topic",
"dear_sirs",
"email_label",
"line",
"project_label",
"project_name",
"quotation_code_data",
"quotation_code_lable",
"quotation_date_data",
"quotation_date_labe",
"quotation_price_data",
"site_label",
"site_location",
"tel_label",
"topic",
"yours_faithfuly"
],
"newFields": [],
"duplicateFields": [],
"unknownFields": [],
"classifications": [
@@ -135,23 +177,23 @@
},
{
"name": "tel_label",
"classification": "static input field"
"classification": "readonly/static visual field"
},
{
"name": "email_label",
"classification": "static input field"
"classification": "readonly/static visual field"
},
{
"name": "att_label",
"classification": "static input field"
"classification": "readonly/static visual field"
},
{
"name": "project_label",
"classification": "static input field"
"classification": "readonly/static visual field"
},
{
"name": "site_label",
"classification": "static input field"
"classification": "readonly/static visual field"
},
{
"name": "topic",
@@ -196,33 +238,63 @@
]
},
"metrics": {
"topicCount": 5,
"itemCount": 3,
"partyCount": 3,
"dynamicTopicFieldCount": 5,
"dynamicTopicItemFieldCount": 5,
"mappingCount": 23
"topicCount": 3,
"itemCount": 4,
"partyCount": 1,
"dynamicTopicFieldCount": 3,
"dynamicTopicItemFieldCount": 3,
"mappingCount": 20
},
"mappingCoverage": {
"placeholderTokenCount": 12,
"unmappedTokens": [],
"orphanMappings": [],
"orphanMappings": [
"exclusion_data"
],
"priceTableMapping": {
"sourcePath": "pdfme.quotation_price_data",
"dataType": "table"
},
"itemTableColumns": []
"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": 35,
"topicInputKeys": 10,
"templateInputKeys": 33,
"topicInputKeys": 6,
"priceTableRows": 1
},
"consistency": {
"status": "PASS",
"status": "FAIL",
"approvedSnapshotAvailable": true,
"approvedArtifactReference": "artifact:a1fd951d-f0b4-4e5b-be88-073596b3829d",
"inconsistentKeys": []
"approvedArtifactReference": "artifact:ec300b87-3710-43a4-a63c-a72168fe3933",
"inconsistentKeys": [
"quotation_date",
"quotation_date_data"
]
},
"integritySummary": {
"available": true,

View File

@@ -1,29 +1,29 @@
# PDF Audit Report
- Generated At: 2026-06-19T10:12:54.188Z
- Overall Status: PASS
- Quotation Code: QT-H5-AUDIT
- Template: ALLA Quotation Standard v1.1
- Previous Version: 1.0
- Generated At: 2026-06-29T04:38:25.895Z
- Overall Status: FAIL
- Quotation Code: QT2606-1122
- Template: ALLA Quotation Standard v1.0
- Previous Version: None
## Metrics
- Topic Count: 5
- Item Count: 3
- Project Party Count: 3
- Mapping Count: 23
- Topic Count: 3
- Item Count: 4
- Project Party Count: 1
- Mapping Count: 20
## Mapping Coverage
- Placeholder Tokens: 12
- Unmapped Tokens: None
- Orphan Mappings: None
- Orphan Mappings: exclusion_data
## Visual / Consistency
- Approved Snapshot Available: Yes
- Consistency Status: PASS
- Inconsistent Keys: None
- Consistency Status: FAIL
- Inconsistent Keys: quotation_date, quotation_date_data
## Integrity Layer

413
plans/task-p.4.3.1.md Normal file
View File

@@ -0,0 +1,413 @@
# Task P.4.3.1 - PDF Runtime Regression Stabilization
## Objective
Stabilize the Task P.4.3 section-based PDF runtime refactor before moving to Product Item implementation.
This task fixes regression only.
Do NOT add new Product Item pages.
Do NOT create new template JSON files.
Do NOT change database schema.
Do NOT change CRM Template UI.
Do NOT introduce new user-facing behavior.
---
# Background
After Task P.4.3, runtime audit still fails.
Observed audit result:
```text
overallStatus = FAIL
staticLabelsStatus = FAIL
quotationCode = QT2606-1126
templateName = ALLA Quotation Standard
templateVersion = 1.0
```
Most runtime payloads pass, including:
* documentData
* quotation data
* customer data
* item rows
* topic inputs
* signatures
* quotation price data
* items_table
However static label inputs are missing from `templateInput`.
Failing fields:
```text
templateInput.tel_label
templateInput.email_label
templateInput.att_label
templateInput.project_label
templateInput.site_label
```
Expected values:
```text
tel_label = Tel
email_label = Email
att_label = Att
project_label = Project
site_label = Location
```
---
# Root Cause Hypothesis
The refactored section-based runtime likely preserved business data and dynamic section inputs, but dropped legacy/default static inputs during template input assembly.
The missing values still exist in source document data:
```text
documentData.pdfme.labels.tel
documentData.pdfme.labels.email
documentData.pdfme.labels.att
documentData.pdfme.labels.project
documentData.pdfme.labels.location
```
but they are not merged into final `templateInput`.
This indicates a regression in one of:
* mapping resolver
* static input resolver
* render context construction
* section composer input merge
* template assembler input merge
---
# Scope
Included:
* Restore static label input compatibility
* Verify final `templateInput` contains all legacy static inputs
* Ensure new runtime preserves all old runtime input keys
* Strengthen audit/regression coverage
* Confirm ALLA and ONVALLA pass runtime audit
Excluded:
* Product Item page rendering
* Product Item template creation
* Template version publishing
* Render Policy UI
* New section visibility configuration
* Database changes
* API changes
---
# Required Investigation
Before fixing, inspect the old and new runtime input construction path.
Compare:
## Old Runtime
* how `tel_label`
* `email_label`
* `att_label`
* `project_label`
* `site_label`
were added to `templateInput`
## New Runtime
* where `templateInput` is initialized
* where mapping output is merged
* where section inputs are merged
* where static/default inputs are merged or lost
Produce a short root-cause note before applying fixes.
---
# Implementation Requirements
## 1. Introduce or Restore Static Input Resolution
Ensure final `templateInput` always includes legacy static inputs.
At minimum:
```ts
{
tel_label: 'Tel',
email_label: 'Email',
att_label: 'Att',
project_label: 'Project',
site_label: 'Location'
}
```
Preferred source:
```text
documentData.pdfme.labels
```
Fallback source:
```text
hardcoded legacy defaults
```
The resolver must not duplicate dynamic mapping logic.
---
## 2. Recommended Component
Add or restore a dedicated component:
```text
StaticInputResolver
```
Responsibilities:
* resolve static labels
* resolve legacy fixed inputs
* normalize alias fields
* provide fallback defaults
* return template input patch
It should run before final template assembly.
Suggested pipeline:
```text
Document Data
-> Template Resolver
-> Mapping Resolver
-> Compatibility Adapter
-> Page Resolver
-> Render Policy Resolver
-> Static Input Resolver
-> Section Composer
-> Template Assembler
-> PDF Generator
```
---
## 3. Preserve Legacy Input Contract
The final `templateInput` must preserve all keys expected by legacy templates.
Minimum required keys:
```text
customer_name
customer_addr
customer_tel
customer_email
customer_att
project_name
site_location
quotation_date
quotation_date_data
quotation_code
quotation_code_data
quotation_price
quotation_price_data
currency
exclusion_data
app1
app1_position
app2
app2_position
app3
app3_position
tel_label
email_label
att_label
project_label
site_label
topic_*
item_topic_*
items_table
```
---
## 4. Template Assembler Merge Rule
The assembler must merge template inputs in deterministic order.
Recommended order:
```text
base mapped input
static input patch
section input patches
diagnostic/debug input patch if any
```
Later patches may override earlier patches only when explicitly intended.
No accidental null overwrite is allowed.
---
## 5. Null-safe Merge
If a patch contains `null` or `undefined`, it must not overwrite a populated existing value unless explicitly allowed.
Example rule:
```text
Do not overwrite "Tel" with null.
Do not overwrite populated static labels with empty values.
```
---
# Regression Requirements
Run audit for at least:
## ALLA
* approved quotation fixture
* quotation with topics
* quotation with items
## ONVALLA
* active quotation fixture
* quotation with topics
* quotation with items
Verify:
```text
overallStatus = PASS
staticLabelsStatus = PASS
findingCount = 0 FAIL
```
---
# Verification Matrix
## Runtime Payload
Confirm final `templateInput` contains:
```text
tel_label = Tel
email_label = Email
att_label = Att
project_label = Project
site_label = Location
```
## Existing Dynamic Inputs
Confirm no regression for:
```text
quotation_price_data
exclusion_data
topic_*
item_topic_*
items_table
app1
app1_position
app2
app2_position
app3
app3_position
```
## Routes
Verify unchanged behavior for:
```text
GET /api/crm/quotations/[id]/document-preview
GET /api/crm/quotations/[id]/pdf-preview
GET /api/crm/quotations/[id]/pdf-download
GET /api/crm/quotations/[id]/approved-pdf
```
---
# Testing Requirements
Run:
```bash
npm exec tsc --noEmit
npm run build
npm run audit:pdf
```
If project has targeted PDF audit commands, run those as well.
---
# Acceptance Criteria
* Static label regression is fixed.
* `overallStatus = PASS`.
* `staticLabelsStatus = PASS`.
* No failing runtime audit findings remain.
* Legacy templates render as before.
* ALLA and ONVALLA remain compatible.
* Section-based runtime architecture from P.4.3 remains intact.
* No Product Item feature implementation is introduced.
* Runtime is safe to proceed to Task P.4.4.
---
# Out of Scope
The following belongs to later tasks:
## Task P.4.4
* Product Item Engine
* Product Item pagination
* Product Item page rendering
## Task P.4.5
* New Product Item template versions
* New template JSON files
* Template seed/reseed support
## Task P.4.6
* CRM Template integration and activation flow
## Task P.4.7
* User-selectable render options
* Optional section UI
* Organization/customer render preferences
---
# Final Success Condition
Task P.4.3.1 is complete only when the refactored section-based runtime produces a passing runtime audit with zero legacy input regressions.
After this task passes, continue to Task P.4.4.

View File

@@ -3,6 +3,7 @@ import { mkdir, writeFile } from 'node:fs/promises';
import path from 'node:path';
import { createHash } from 'node:crypto';
import postgres from 'postgres';
import { resolveQuotationStaticTemplateInputs } from '../src/features/crm/quotations/document/server/static-input-resolver.ts';
export const AUDIT_QUOTATION_CODE = 'QT-H5-AUDIT';
export const AUDIT_ARTIFACTS_DIR = path.resolve(process.cwd(), 'artifacts', 'pdf-audit');
@@ -1428,6 +1429,11 @@ export async function buildAuditPayload(sql: SqlClient): Promise<AuditPayload> {
const templateInput = {
...mapDocumentDataToTemplateInput(documentData, mappings, columns),
...resolveQuotationStaticTemplateInputs(
documentData as unknown as Parameters<
typeof resolveQuotationStaticTemplateInputs
>[0],
),
...topicInputs
};
const normalizedTemplateInput = normalizeTemplateInputAliases(

View File

@@ -5,6 +5,7 @@ import type { ResolvedDocumentTemplate } from '@/features/foundation/document-te
import type { QuotationDocumentData } from '../types';
import { buildQuotationPdfRuntime } from './quotation-pdf-runtime';
import { resolveTemplatePages } from './page-resolver';
import { assembleTemplate } from './template-assembler';
import type { ResolvedTemplate } from './runtime-contracts';
import { adaptTemplateForSectionRuntime } from './template-compatibility-adapter';
@@ -238,3 +239,54 @@ test('runtime keeps signature block on the last generated topic page', async ()
assert.equal(fieldNames.has('app1'), false);
}
});
test('runtime restores legacy static labels into final template input', async () => {
const schema = createLegacyTemplate();
const template = createResolvedDocumentTemplate(schema);
const runtime = await buildQuotationPdfRuntime({
documentData: createDocumentData(1),
template,
mappings: [],
templateInput: {
customer_name: 'Customer',
},
});
assert.equal(runtime.assembled.templateInput.tel_label, 'Tel');
assert.equal(runtime.assembled.templateInput.email_label, 'Email');
assert.equal(runtime.assembled.templateInput.att_label, 'Att');
assert.equal(runtime.assembled.templateInput.project_label, 'Project');
assert.equal(runtime.assembled.templateInput.site_label, 'Location');
});
test('template assembler does not overwrite populated values with nullish or blank patches', () => {
const assembled = assembleTemplate({
template: createLegacyTemplate(),
templateInput: {
tel_label: 'Tel',
customer_name: 'Customer',
},
staticInputPatch: {
tel_label: '',
email_label: 'Email',
},
sections: [
{
role: 'topics',
enabled: true,
rendered: false,
anchorPageIndex: 1,
pages: [],
templateInputPatch: {
tel_label: undefined,
project_label: 'Project',
},
issues: [],
},
],
});
assert.equal(assembled.templateInput.tel_label, 'Tel');
assert.equal(assembled.templateInput.email_label, 'Email');
assert.equal(assembled.templateInput.project_label, 'Project');
});

View File

@@ -7,6 +7,7 @@ import type { QuotationDocumentData } from '../types';
import { buildPdfTopicPages } from './pdf-topic-engine';
import { resolveTemplatePages } from './page-resolver';
import { createRenderContext, resolveRenderPolicies } from './render-context';
import { resolveQuotationStaticTemplateInputs } from './static-input-resolver';
import type {
BuiltSection,
QuotationPdfRuntimeResult,
@@ -129,6 +130,7 @@ export async function buildQuotationPdfRuntime(args: {
const compatibleTemplate = adaptTemplateForSectionRuntime(resolvedTemplate);
const pages = resolveTemplatePages(compatibleTemplate);
const policies = resolveRenderPolicies();
const staticInputPatch = resolveQuotationStaticTemplateInputs(args.documentData);
const context = createRenderContext({
documentData: args.documentData,
template: resolvedTemplate,
@@ -141,6 +143,7 @@ export async function buildQuotationPdfRuntime(args: {
const assembled = assembleTemplate({
template: resolvedTemplate.schema,
templateInput: args.templateInput,
staticInputPatch,
sections: composed.sections,
});
const issues = [...pages.issues, ...composed.issues, ...assembled.issues];

View File

@@ -0,0 +1,50 @@
import type { QuotationDocumentData } from '../types';
const LEGACY_STATIC_LABEL_DEFAULTS = {
tel_label: 'Tel',
email_label: 'Email',
att_label: 'Att',
project_label: 'Project',
site_label: 'Location',
} as const;
function normalizeStringValue(
value: string | null | undefined,
fallback: string,
): string {
if (typeof value !== 'string') {
return fallback;
}
const trimmed = value.trim();
return trimmed.length > 0 ? trimmed : fallback;
}
export function resolveQuotationStaticTemplateInputs(
documentData: QuotationDocumentData,
): Record<string, unknown> {
const labels = documentData.pdfme.labels;
return {
tel_label: normalizeStringValue(
labels.tel,
LEGACY_STATIC_LABEL_DEFAULTS.tel_label,
),
email_label: normalizeStringValue(
labels.email,
LEGACY_STATIC_LABEL_DEFAULTS.email_label,
),
att_label: normalizeStringValue(
labels.att,
LEGACY_STATIC_LABEL_DEFAULTS.att_label,
),
project_label: normalizeStringValue(
labels.project,
LEGACY_STATIC_LABEL_DEFAULTS.project_label,
),
site_label: normalizeStringValue(
labels.location,
LEGACY_STATIC_LABEL_DEFAULTS.site_label,
),
};
}

View File

@@ -9,9 +9,29 @@ function cloneTemplate<T>(value: T): T {
return JSON.parse(JSON.stringify(value)) as T;
}
function mergeTemplateInputPatch(
target: Record<string, unknown>,
patch: Record<string, unknown>,
): Record<string, unknown> {
for (const [key, value] of Object.entries(patch)) {
if (value === null || value === undefined) {
continue;
}
if (typeof value === 'string' && value.trim().length === 0) {
continue;
}
target[key] = value;
}
return target;
}
export function assembleTemplate(args: {
template: Template;
templateInput: Record<string, unknown>;
staticInputPatch?: Record<string, unknown>;
sections: BuiltSection[];
}): AssembledTemplate {
const baseTemplate = cloneTemplate(args.template);
@@ -62,13 +82,15 @@ export function assembleTemplate(args: {
baseTemplate.schemas = nextPages;
const templateInput = args.sections.reduce<Record<string, unknown>>(
(accumulator, section) => ({
...accumulator,
...section.templateInputPatch,
}),
{ ...args.templateInput },
);
const templateInput = mergeTemplateInputPatch({}, args.templateInput);
if (args.staticInputPatch) {
mergeTemplateInputPatch(templateInput, args.staticInputPatch);
}
for (const section of args.sections) {
mergeTemplateInputPatch(templateInput, section.templateInputPatch);
}
return {
template: baseTemplate,