Files
alla-allaos-fullstack/docs/implementation/pdfme-runtime-audit.md
2026-06-18 08:58:01 +07:00

910 lines
26 KiB
Markdown

# PDFME Runtime Audit Report
## Executive Summary
This audit compares the current PDFME template and mapping system against the legacy implementation and identifies runtime issues that cause incomplete or incorrectly mapped data in generated quotation PDFs.
**Key Finding**: The current implementation has significant gaps in placeholder mappings, missing topic sections, and incorrect data formats that prevent PDFME from displaying complete quotation information.
**Total Template Placeholders**: 20
**Total Required Mappings**: 20
**Missing Mappings**: 8
**Critical Issues**: 5
---
## 1. Template Analysis
### 1.1 Current Template File
**Path**: `src/pdfme_template/ALLA_template_pdfme_fainal3.json`
### 1.2 Placeholder Extraction
The following placeholders were extracted from the current template schema:
| Placeholder Key | Field Type | Content Pattern | Position (Page 1) |
|-----------------|------------|-----------------|-------------------|
| `customer_name` | text | `{customer_name}` | Header |
| `customer_addr` | text | `{customer_addr}` | Header |
| `customer_tel` | text | `{customer_tel}` | Header |
| `customer_email` | text | `{customer_email}` | Header |
| `customer_att` | text | `{customer_att}` | Header |
| `project_name` | text | `{project_name}` | Header |
| `site_location` | text | `{site_location}` | Header |
| `quotation_date` | text | `{quotation_date}` | Header |
| `quotation_code` | text | `{quotation_code}` | Header |
| `quotation_price` | table (cell) | `{quotation_price}` | Price section |
| `currency` | table (cell) | `{currency}` | Price section |
| `exclusion_data` | table | `[[\"{exclusion_data}\"]]` | Exclusion section |
**Static Content (No Mapping Required)**:
- Company header (company1, company2, company_addr1, company_addr2, company_tel, company_email, company_tax)
- Labels (Date, Quotation No, Tel, Email, etc.)
- Colons and separators
- "Dear sirs" text
---
## 2. Runtime Audit Results
### 2.1 Placeholder Mapping Status
| Placeholder | Template Exists | Mapping Exists | Source Path | Expected Type | Actual Type | Status |
|-------------|-----------------|----------------|-------------|---------------|-------------|--------|
| `customer_name` | ✅ | ✅ | `customer.name` | string | string | ✅ OK |
| `customer_addr` | ✅ | ✅ | `customer.address` | string | string | ✅ OK |
| `customer_tel` | ✅ | ✅ | `customer.contacts[primary].phone` | string | string | ✅ OK |
| `customer_email` | ✅ | ✅ | `customer.contacts[primary].email` | string | string | ✅ OK |
| `customer_att` | ✅ | ✅ | `quotation.attention` | string | string | ✅ OK |
| `project_name` | ✅ | ✅ | `quotation.projectName` | string | string | ✅ OK |
| `site_location` | ✅ | ✅ | `quotation.projectLocation` | string | string | ✅ OK |
| `quotation_date` | ✅ | ✅ | `quotation.quotationDate` | string | string | ⚠️ **Check Format** |
| `quotation_code` | ✅ | ✅ | `quotation.code` | string | string | ✅ OK |
| `quotation_price` | ✅ | ✅ | `quotation.totalAmount` | string (formatted) | number | ⚠️ **Type Mismatch** |
| `currency` | ✅ | ✅ | `quotation.currency` | string | string | ✅ OK |
| `exclusion_data` | ✅ | ❌ | - | `string[][]` | undefined | ❌ **MISSING** |
### 2.2 Legacy Placeholders Not in Current Template
The following placeholders existed in the legacy implementation but are **not present** in the current template:
| Placeholder | Legacy Purpose | Status |
|-------------|----------------|--------|
| `exclusion_label` | Topic label (static) | ❌ **Not in template** |
| `Terms_of_payment` | Payment topic label | ❌ **Not in template** |
| `data_term_of_payment` | Payment topic data | ❌ **Not in template** |
| `warranty` | Warranty topic label | ❌ **Not in template** |
| `data_warranty` | Warranty topic data | ❌ **Not in template** |
| `Delivery` | Delivery topic label | ❌ **Not in template** |
| `data_delivery` | Delivery topic data | ❌ **Not in template** |
| `Affter service` | After-service topic label | ❌ **Not in template** |
| `data_affter_service` | After-service topic data | ❌ **Not in template** |
| `topic` | General topic label | ❌ **Not in template** |
| `data_topic` | General topic data | ❌ **Not in template** |
| `app1` | Approver 1 name | ❌ **Not in template** |
| `app1_position` | Approver 1 position | ❌ **Not in template** |
| `app2` | Approver 2 name | ❌ **Not in template** |
| `app2_position` | Approver 2 position | ❌ **Not in template** |
| `app3` | Approver 3 name | ❌ **Not in template** |
| `app3_position` | Approver 3 position | ❌ **Not in template** |
---
## 3. Critical Issues
### 3.1 Issue: Missing Exclusion Data Mapping
**Severity**: HIGH
**Placeholder**: `exclusion_data`
**Template Format**: `[[\"{exclusion_data}\"]]` (table with single column)
**Expected Data Type**: `string[][]`
**Current Status**: No mapping exists
**Problem**:
- Template expects exclusion topic items to be displayed as a table
- Legacy implementation used `formatTopicItems()` to convert topic items to `string[][]` format
- Current implementation has no mapping for this placeholder
- Exclusion topic data exists in database but is not passed to PDFME
**Impact**:
- Exclusion section will be empty or show placeholder text
- Critical business information missing from quotation PDF
**Legacy Implementation**:
```typescript
// Legacy: src/features/quotations/lib/pdfme-export.ts
function formatTopicItems(topic: any): string[][] {
if (!topic || !topic.items || topic.items.length === 0) {
return [['-']];
}
return topic.items.map((item: any) => [item.content || '-']);
}
const termsTopic = topics.find(t => t.topicType === 'exclusion_from_scope_of_supply');
data.exclusion_data = formatTopicItems(termsTopic);
```
**Required Fix**:
1. Add mapping for `exclusion_data` placeholder
2. Transform topic items to `string[][]` format
3. Handle empty topics with fallback `[['-']]`
---
### 3.2 Issue: Missing Topic Sections
**Severity**: HIGH
**Affected Placeholders**:
- `Terms_of_payment`, `data_term_of_payment`
- `warranty`, `data_warranty`
- `Delivery`, `data_delivery`
- `Affter service`, `data_affter_service`
**Problem**:
- Legacy template had separate sections for each topic type
- Current template only has exclusion section
- Payment, warranty, delivery, and after-service topics are not displayed
- These are critical business terms in quotations
**Impact**:
- Payment terms not visible to customer
- Warranty information missing
- Delivery details not specified
- After-service information absent
**Legacy Structure**:
```
Topic: Scope of Work
[data_scope]
Topic: Exclusion from scope of Supply:
[data_exclusion]
Terms of payment upon progress of each item:
[data_term_of_payment]
Warranty:
[data_warranty]
Delivery:
[data_delivery]
Affter service:
[data_affter_service]
```
**Current Template Structure**:
```
Quotation details...
Exclusion from scope of Supply:
[exclusion_data]
```
**Required Fix**:
1. Add topic section fields to template OR
2. Merge all topics into a single table with labels
3. Implement topic filtering based on product type
---
### 3.3 Issue: Currency Format Mismatch
**Severity**: MEDIUM
**Placeholder**: `quotation_price`
**Template Usage**: `[[\"Price (Exclude VAT)\",\"{quotation_price}\",\" \",\"{currency}\"]]`
**Expected Type**: Formatted string (e.g., "฿1,000,000.00")
**Actual Type**: Number (from database)
**Problem**:
- Database stores `quotation.totalAmount` as number (double precision)
- PDFME table cell expects formatted currency string
- Current mapping likely passes raw number without formatting
- Thai locale formatting required (฿ symbol, thousand separators, 2 decimal places)
**Impact**:
- Price may display as "1000000" instead of "฿1,000,000.00"
- Unprofessional appearance
- Potential customer confusion
**Legacy Implementation**:
```typescript
function formatCurrency(amount: number, currencyCode: CurrencyCode): string {
const symbol = getCurrencySymbol(currencyCode);
const formatted = new Intl.NumberFormat('th-TH', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
}).format(amount);
return `${symbol}${formatted}`;
}
data.quotation_price = formatCurrency(quotation.totalAmount, quotation.currency);
```
**Required Fix**:
1. Add currency formatting transform
2. Use `formatMask` in mapping or custom transform function
3. Support multiple currency codes
---
### 3.4 Issue: Date Format
**Severity**: MEDIUM
**Placeholder**: `quotation_date`
**Expected Format**: Localized date string (Thai: Buddhist calendar or English: Gregorian)
**Current Format**: ISO date string
**Problem**:
- Database stores dates as ISO 8601 strings (e.g., "2026-06-17T00:00:00.000Z")
- PDF displays raw ISO string instead of formatted date
- Legacy implementation had language-aware date formatting
**Impact**:
- Unprofessional appearance
- May cause confusion about quotation validity date
- Doesn't match business requirements for Thai/English date formats
**Legacy Implementation**:
```typescript
function formatDate(dateString: string, language: 'th' | 'en'): string {
const date = new Date(dateString);
if (language === 'th') {
return date.toLocaleDateString('th-TH', {
day: 'numeric',
month: 'short',
year: 'numeric',
calendar: 'buddhist'
});
}
return date.toLocaleDateString('en-US', {
day: 'numeric',
month: 'short',
year: 'numeric'
});
}
```
**Required Fix**:
1. Add date formatting transform
2. Support Thai and English locales
3. Configure format mask in mapping
---
### 3.5 Issue: Missing Product Type Topic Mapping
**Severity**: HIGH
**Legacy Behavior**: Different topic types mapped based on quotation product type
**Problem**:
- Legacy implementation had product-specific topic mappings:
- **Crane**: scope_of_work, exclusion_from_scope_of_supply, terms_of_payment_upon_progress_of_each_item, delivery_date, warranty
- **Dockdoor**: dock_scope_of_work, exclusion_of_supply_installation, payment_conditions, delivery_date, warranty
- **Solarcell**: solarcell_scope_of_work, solarcell_exclusion_of_supply_installation, solarcell_payment_conditions, solarcell_delivery, solarcell_warranty
- Current implementation treats all topics uniformly
- Topic type labels may not match product type
**Impact**:
- Wrong topic labels for different product types
- Inconsistent quotation formatting
- Business process violation
**Required Fix**:
1. Implement product type to topic type mapping
2. Apply correct labels based on quotation type
3. Support dynamic topic resolution
---
## 4. Data Format Analysis
### 4.1 Table Data Formats
#### Exclusion Data Table
**Template Content**:
```json
"content": "[[\"{exclusion_data}\"]]"
```
**Expected Format**: `string[][]`
**Example**:
```typescript
[
["Provide crane and installation service"],
["Site survey and installation planning"],
["Operator training (2 days)"]
]
```
**Legacy Transform**:
```typescript
function formatTopicItems(topic: any): string[][] {
if (!topic || !topic.items || topic.items.length === 0) {
return [['-']];
}
return topic.items.map((item: any) => [item.content || '-']);
}
```
**Current Status**: ❌ Not implemented
---
#### Price Table
**Template Content**:
```json
"content": "[[\"Price (Exclude VAT)\",\"{quotation_price}\",\" \",\"{currency}\"]]"
```
**Expected Format**: `string[][]` (4 columns)
**Example**:
```typescript
[
["Price (Exclude VAT)", "฿1,000,000.00", " ", "THB"]
]
```
**Legacy Transform**:
```typescript
data.quotation_price = formatCurrency(quotation.totalAmount, quotation.currency);
data.currency = quotation.currency;
```
**Current Status**: ⚠️ Number passed instead of formatted string
---
### 4.2 Text Data Formats
#### Customer Contact Information
**Legacy Prefixes**:
- Tel: `Tel : {customer_tel}`
- Email: `Email : {customer_email}`
**Current Template**: Labels and data are separate fields
- Label: "Tel" (static)
- Data: `{customer_tel}` (placeholder)
**Status**: ✅ OK (modern approach)
---
## 5. Current Implementation Gaps
### 5.1 Missing Transform Functions
| Function | Purpose | Current Status |
|----------|---------|----------------|
| `formatTopicItems()` | Convert topic items to `string[][]` | ❌ Missing |
| `formatCurrency()` | Format currency with locale | ⚠️ May exist but not applied |
| `formatDate()` | Format date with locale | ⚠️ May exist but not applied |
| `getCurrencySymbol()` | Get currency symbol | ❌ Missing |
| `section()` | Create label/data pairs for topics | ❌ Missing |
| `productTypeTopicMapping` | Map product type to topic types | ❌ Missing |
### 5.2 Missing Database Mappings
The following mappings should exist in `crm_document_template_mappings` table:
| placeholder_key | source_path | data_type | format_mask | status |
|-----------------|-------------|-----------|-------------|--------|
| `exclusion_data` | `topics.exclusion_from_scope_of_supply.items` | table | - | ❌ Missing |
| `quotation_price` | `quotation.totalAmount` | scalar | currency_THB | ⚠️ Needs format |
| `quotation_date` | `quotation.quotationDate` | scalar | date_th | ⚠️ Needs format |
### 5.3 Data Builder Issues
**File**: `src/features/crm/quotations/document/server/service.ts`
**Issues**:
1. `buildQuotationDocumentData()` does not transform topic items to `string[][]`
2. Does not format currency or dates
3. Does not apply product type topic mapping
4. Does not handle empty topics with fallbacks
---
## 6. Comparison with Legacy
### 6.1 Legacy Flow
```typescript
generateQuotationPDF(quotation, customer, topics)
mapDataToTemplate() // Direct mapping with transforms
formatDate() // Thai/English date
formatCurrency() // Thai locale currency
formatTopicItems() // Convert to string[][]
generateExtraTopicFields() // Dynamic schema
pdfme.generate(template, inputs)
```
### 6.2 Current Flow
```typescript
buildQuotationDocumentData() // Fetch data
mapDocumentDataToTemplateInput() // Generic mapping
applyFormatMask() // Simple format masks only
pdfme.generate(template, inputs)
```
### 6.3 Key Differences
| Aspect | Legacy | Current | Gap |
|--------|--------|---------|-----|
| Mapping Approach | Direct with inline transforms | Generic via database | Transform logic |
| Topic Formatting | `string[][]` via `formatTopicItems()` | Unknown | Format mismatch |
| Currency Formatting | `formatCurrency()` with locale | Basic format mask | Missing locale |
| Date Formatting | `formatDate()` with locale | Basic format mask | Missing locale |
| Product Type | Product-specific topic mapping | None | Wrong labels |
| Dynamic Topics | `generateExtraTopicFields()` | Static schema | No extra topics |
| Fallback Values | Inline defaults in mapping | `defaultValue` column | May not cover all cases |
---
## 7. Recommended Fixes
### 7.1 Immediate Fixes (P0)
#### Fix 1: Add Exclusion Data Mapping
**Add to `crm_document_template_mappings`**:
```sql
INSERT INTO crm_document_template_mappings (
id, organization_id, template_version_id, placeholder_key,
source_path, data_type, sort_order
) VALUES (
gen_random_uuid(),
'<organization_id>',
'<template_version_id>',
'exclusion_data',
'topics.exclusion_from_scope_of_supply.items',
'table',
10
);
```
**Add transform function** in `src/features/crm/quotations/document/server/utils.ts`:
```typescript
export function formatTopicItems(
topic: { items?: Array<{ content?: string }> } | undefined
): string[][] {
if (!topic?.items || topic.items.length === 0) {
return [['-']];
}
return topic.items.map((item) => [item.content || '-']);
}
```
**Update `buildQuotationDocumentData()`**:
```typescript
const exclusionTopic = topics.find(
t => t.topicType === 'exclusion_from_scope_of_supply'
);
data.exclusion_data = formatTopicItems(exclusionTopic);
```
---
#### Fix 2: Format Currency
**Update mapping for `quotation_price`**:
```sql
UPDATE crm_document_template_mappings
SET format_mask = 'currency_THB'
WHERE placeholder_key = 'quotation_price';
```
**Add currency formatter** in `src/features/crm/quotations/document/server/utils.ts`:
```typescript
export function formatCurrency(
amount: number,
currencyCode: string = 'THB'
): string {
const symbols: Record<string, string> = {
THB: '฿',
USD: '$',
EUR: '€'
};
const symbol = symbols[currencyCode] || currencyCode;
const formatted = new Intl.NumberFormat('th-TH', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
}).format(amount);
return `${symbol}${formatted}`;
}
```
**Update `mapDocumentDataToTemplateInput()`** in `document-template/server/service.ts`:
```typescript
if (mapping.formatMask?.startsWith('currency_')) {
const currencyCode = mapping.formatMask.replace('currency_', '');
result[mapping.placeholderKey] = formatCurrency(
rawValue as number,
currencyCode
);
continue;
}
```
---
#### Fix 3: Format Date
**Update mapping for `quotation_date`**:
```sql
UPDATE crm_document_template_mappings
SET format_mask = 'date_th'
WHERE placeholder_key = 'quotation_date';
```
**Add date formatter** in `src/features/crm/quotations/document/server/utils.ts`:
```typescript
export function formatDate(
dateString: string,
language: 'th' | 'en' = 'th'
): string {
const date = new Date(dateString);
if (language === 'th') {
return date.toLocaleDateString('th-TH', {
day: 'numeric',
month: 'short',
year: 'numeric',
calendar: 'buddhist'
});
}
return date.toLocaleDateString('en-US', {
day: 'numeric',
month: 'short',
year: 'numeric'
});
}
```
**Update `mapDocumentDataToTemplateInput()`**:
```typescript
if (mapping.formatMask?.startsWith('date_')) {
const language = mapping.formatMask.replace('date_', '') as 'th' | 'en';
result[mapping.placeholderKey] = formatDate(
rawValue as string,
language
);
continue;
}
```
---
### 7.2 Template Updates (P0)
#### Option A: Add Missing Topic Sections to Template
Add the following fields to the template schema:
```json
{
"name": "payment_label",
"type": "table",
"content": "[[\"Terms of payment upon progress of each item:\"]]",
"position": { "x": 10, "y": 220 },
"width": 190,
"height": 5
},
{
"name": "payment_data",
"type": "table",
"content": "[[\"{payment_data}\"]]",
"position": { "x": 15, "y": 230 },
"width": 185,
"height": 7
},
{
"name": "warranty_label",
"type": "table",
"content": "[[\"Warranty:\"]]",
"position": { "x": 10, "y": 250 },
"width": 190,
"height": 5
},
{
"name": "warranty_data",
"type": "table",
"content": "[[\"{warranty_data}\"]]",
"position": { "x": 15, "y": 260 },
"width": 185,
"height": 7
}
```
#### Option B: Consolidate Topics into Single Table
Create a unified topics table with labels:
```json
{
"name": "topics_table",
"type": "table",
"content": "[{topics_data}]",
"position": { "x": 10, "y": 195 },
"width": 190,
"height": 50
}
```
Where `topics_data` is a structured array:
```typescript
[
["Exclusion from scope of Supply:", "Provide crane, Site survey"],
["Terms of payment:", "30% deposit, 70% on delivery"],
["Warranty:", "12 months parts and labor"],
["Delivery:", "30 days after order confirmation"],
["After service:", "Annual maintenance available"]
]
```
---
### 7.3 Product Type Mapping (P1)
**Create configuration** in `src/features/crm/quotations/document/config/topic-mapping.ts`:
```typescript
export const TOPIC_TYPE_MAPPING: Record<string, Record<string, string>> = {
crane: {
scope: 'scope_of_work',
exclusion: 'exclusion_from_scope_of_supply',
payment: 'terms_of_payment_upon_progress_of_each_item',
delivery: 'delivery_date',
warranty: 'warranty'
},
dockdoor: {
scope: 'dock_scope_of_work',
exclusion: 'exclusion_of_supply_installation',
payment: 'payment_conditions',
delivery: 'delivery_date',
warranty: 'warranty'
},
solarcell: {
scope: 'solarcell_scope_of_work',
exclusion: 'solarcell_exclusion_of_supply_installation',
payment: 'solarcell_payment_conditions',
delivery: 'solarcell_delivery',
warranty: 'solarcell_warranty'
}
};
```
**Update `buildQuotationDocumentData()`** to use product type:
```typescript
const mapping = TOPIC_TYPE_MAPPING[quotation.productType] || TOPIC_TYPE_MAPPING.crane;
const exclusionTopic = topics.find(t => t.topicType === mapping.exclusion);
data.exclusion_data = formatTopicItems(exclusionTopic);
```
---
## 8. Risk Assessment
### 8.1 High Risk
| Risk | Impact | Probability | Mitigation |
|------|--------|-------------|------------|
| Missing exclusion data | High customer confusion | 100% | Add mapping immediately |
| Missing payment terms | Legal/compliance issue | 100% | Add to template |
| Wrong currency format | Unprofessional appearance | 90% | Add currency formatter |
| Wrong date format | Date confusion | 90% | Add date formatter |
### 8.2 Medium Risk
| Risk | Impact | Probability | Mitigation |
|------|--------|-------------|------------|
| Missing product type mapping | Inconsistent formatting | 80% | Implement product mapping |
| Missing warranty info | Customer expectations | 70% | Add to template |
| Missing delivery info | Order confusion | 70% | Add to template |
### 8.3 Low Risk
| Risk | Impact | Probability | Mitigation |
|------|--------|-------------|------------|
| Missing signatures | Process compliance | 50% | Add approval workflow |
| Static company info | Outdated info | 30% | Make dynamic |
---
## 9. Testing Recommendations
### 9.1 Unit Tests
Create tests for transform functions:
```typescript
// tests/features/crm/quotations/document/utils.test.ts
describe('formatTopicItems', () => {
it('should format topic items to string[][]', () => {
const topic = {
items: [
{ content: 'Item 1' },
{ content: 'Item 2' }
]
};
expect(formatTopicItems(topic)).toEqual([
['Item 1'],
['Item 2']
]);
});
it('should return fallback for empty topic', () => {
expect(formatTopicItems(undefined)).toEqual([['-']]);
});
});
```
### 9.2 Integration Tests
Test end-to-end PDF generation:
```typescript
describe('Quotation PDF Generation', () => {
it('should generate PDF with all fields populated', async () => {
const quotation = await createTestQuotation();
const pdf = await generateQuotationPDF(quotation.id);
// Verify placeholders are replaced
expect(pdf).toContain('CUSTOMER NAME');
expect(pdf).not.toContain('{customer_name}');
});
});
```
### 9.3 Visual Regression Tests
Compare generated PDFs with legacy PDFs:
1. Generate PDF from legacy implementation
2. Generate PDF from current implementation
3. Compare pixel-by-pixel
4. Identify visual differences
---
## 10. Implementation Priority
### Phase 1: Critical Fixes (1-2 days)
1. ✅ Add exclusion data mapping
2. ✅ Implement `formatTopicItems()`
3. ✅ Format currency
4. ✅ Format dates
5. ✅ Test with real quotation
### Phase 2: Template Updates (1 day)
1. Add missing topic sections (payment, warranty, delivery)
2. Update template layout
3. Add mappings for new fields
### Phase 3: Product Type Logic (1 day)
1. Implement product type mapping
2. Update topic resolution
3. Test different product types
### Phase 4: Enhancements (2-3 days)
1. Dynamic topic schema generation
2. Approval signature integration
3. Visual regression testing
---
## 11. Conclusion
The current PDFME implementation has significant gaps compared to the legacy system, resulting in incomplete or incorrectly formatted quotation PDFs. The primary issues are:
1. **Missing topic mappings** (exclusion, payment, warranty, delivery)
2. **Missing transform functions** (formatTopicItems, currency, date)
3. **Template structure differences** (fewer sections)
4. **No product type logic** (generic topic labels)
**Recommended Action**: Implement Phase 1 fixes immediately to restore basic functionality, then proceed with template updates and product type logic.
**Estimated Total Effort**: 5-7 days for complete resolution.
---
## Appendix A: Placeholder Inventory
### A.1 Current Template Placeholders
```
customer_name
customer_addr
customer_tel
customer_email
customer_att
project_name
site_location
quotation_date
quotation_code
quotation_price
currency
exclusion_data
```
### A.2 Legacy Template Placeholders (Not in Current)
```
exclusion_label
Terms_of_payment
data_term_of_payment
warranty
data_warranty
Delivery
data_delivery
Affter service
data_affter_service
topic
data_topic
app1
app1_position
app2
app2_position
app3
app3_position
```
### A.3 Mapped vs Unmapped
| Category | Total | Mapped | Unmapped | % Mapped |
|----------|-------|--------|----------|----------|
| Customer Info | 4 | 4 | 0 | 100% |
| Quotation Info | 6 | 6 | 0 | 100% |
| Topics | 1 | 0 | 1 | 0% |
| Signatures | 6 | 0 | 6 | 0% |
| **Total** | **17** | **10** | **7** | **59%** |
---
## Appendix B: Data Source Reference
### B.1 Database Schema
**Customer**: `crm_customers`
- `name``{customer_name}`
- `address``{customer_addr}`
- `phone``{customer_tel}`
- `email``{customer_email}`
**Quotation**: `crm_quotations`
- `attention``{customer_att}`
- `projectName``{project_name}`
- `projectLocation``{site_location}`
- `quotationDate``{quotation_date}`
- `code``{quotation_code}`
- `totalAmount``{quotation_price}`
- `currency``{currency}`
**Topics**: `crm_quotation_topics` + `crm_quotation_topic_items`
- `topicType` → determines which topic
- `items[].content` → topic content items
### B.2 Source Path Examples
| Placeholder | Source Path | Notes |
|-------------|-------------|-------|
| `customer_name` | `customer.name` | Direct mapping |
| `customer_tel` | `customer.contacts[primary].phone` | Array access |
| `quotation_price` | `quotation.totalAmount` | Number → string transform |
| `exclusion_data` | `topics.exclusion_from_scope_of_supply.items` | Topic → string[][] transform |
---
**Report Generated**: 2026-06-17
**Auditor**: AI Code Review Agent
**Version**: 1.0