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

19 KiB

PDFME Legacy Audit

1. Executive Summary

This audit compares the legacy PDFME quotation generation implementation with the current ALLA OS CRM vNext implementation. The investigation reveals significant gaps in placeholder mapping, data transformation, and topic handling that explain why the current system displays incomplete or incorrectly mapped data.

Key Finding: The legacy implementation uses a direct mapping approach with specialized transform functions for each field, while the current implementation relies on a generic mapping system that may not handle all edge cases correctly.


2. Legacy Files Inspected

2.1 Core Files

  • src/features/quotations/lib/pdfme-export.ts - Main PDFME generation logic
  • src/features/quotations/lib/quotation-preview-utils.ts - Format utilities
  • src/features/quotations/pdfme/ALLA_template_pdfme_fainal3.json - Template definition
  • src/features/quotations/types/quotation-preview.types.ts - Type definitions

2.2 Current Project Files

  • src/features/foundation/pdf-generator/server/service.ts - PDF generation service
  • src/features/crm/quotations/document/server/service.ts - Document data builder
  • src/features/foundation/document-template/server/service.ts - Template mapping service
  • src/db/schema.ts - Database schema

3. Legacy PDFME Flow

3.1 Architecture

generateQuotationPDF()
  ↓
mapDataToTemplate()
  ↓
formatDate() / formatCurrency()
  ↓
formatTopicItems() / section() / section2()
  ↓
generateExtraTopicFields()
  ↓
generate() from @pdfme/generator

3.2 Key Characteristics

  • Direct mapping: Each placeholder key is explicitly mapped in mapDataToTemplate()
  • Inline transforms: Transform functions are applied inline during mapping
  • Dynamic schema generation: Extra topics modify template schema at runtime
  • Multi-branch support: Different templates for alla vs onvalla
  • Specialized formatting: Currency, date, and topic item formatting

3.3 Data Structure

{
  customer: QuotationPreviewCustomer
  quotation: QuotationPreviewData
  items: QuotationPreviewItem[]
  topics: Array<{ topicType: string; items: Array<{ content: string }> }>
}

4. Legacy Placeholder Inventory

Placeholder Legacy Source Transform Current Mapping Status
customer_name customer.name Fallback to '-' Mapped OK
customer_addr customer.address Fallback to '-' Mapped OK
customer_tel customer.contacts[primary].mobile/phone Prefix: Tel : Mapped OK
customer_email customer.contacts[primary].email Prefix: Email : Mapped OK
customer_att quotation.attention Direct Mapped OK
project_name quotation.projectName Direct Mapped OK
site_location quotation.projectLocation Direct Mapped OK
quotation_date quotation.quotationDate formatDate(date, 'en') Mapped ⚠️ Check format
quotation_code quotation.code Direct Mapped OK
quotation_price quotation.totalAmount formatCurrency(amount, currency) Mapped ⚠️ Check format
currency quotation.currency Direct Mapped OK
exclusion_label termsTopic.topicType Hardcoded: "Exclusion from scope of Supply:" Missing MISSING
exclusion_data termsTopic.items formatTopicItems()string[][] Missing MISSING
Terms_of_payment termsTopic.topicType Hardcoded: "Terms of payment upon progress of each item:" Missing MISSING
data_term_of_payment termsTopic.items formatTopicItems()string[][] Missing MISSING
warranty warrantyTopic.topicType Hardcoded: "Warranty:" Missing MISSING
data_warranty warrantyTopic.items formatTopicItems()string[][] Missing MISSING
Delivery deliveryTopic.topicType Hardcoded: "Delivery:" Missing MISSING
data_delivery deliveryTopic.items formatTopicItems()string[][] Missing MISSING
Affter service deliveryTopic.topicType Hardcoded: "Affter service:" Missing MISSING
data_affter_service deliveryTopic.items formatTopicItems()string[][] Missing MISSING
topic Static text Hardcoded: "Topic:" Static OK
data_topic topics Complex transform needed Missing MISSING
item_topic Not in legacy - Not defined TEMPLATE MISMATCH
app1 Not populated Static: "Type Something..." Missing MISSING
app1_position Not populated Static: "Type Something..." Missing MISSING
app2 Not populated Static: "Type Something..." Missing MISSING
app2_position Not populated Static: "Type Something..." Missing MISSING
app3 Not populated Static: "Type Something..." Missing MISSING
app3_position Not populated Static: "Type Something..." Missing MISSING

5. Legacy Transform Functions

5.1 formatDate(dateString: string, language: 'th' | 'en'): string

Purpose: Format date for PDF display Input: ISO date string Output: Localized date string (Thai: Buddhist calendar, English: Gregorian) Why it matters: PDF requires specific date format Current project: Has equivalent but may differ in format

// Legacy
export 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'
  });
}

5.2 formatCurrency(amount: number, currencyCode: CurrencyCode): string

Purpose: Format currency with Thai locale Input: Amount and currency code Output: Formatted string (e.g., "฿1,000,000.00") Why it matters: PDF price display requires proper formatting Current project: May have equivalent but needs verification

// Legacy
export 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}`;
}

5.3 formatTopicItems(topic: any): string[][]

Purpose: Transform topic items to PDFME table format Input: Topic object with items array Output: 2D array where each item is wrapped in array Why it matters: PDFME tables require string[][] format Current project: MISSING

// Legacy
function formatTopicItems(topic: any): string[][] {
  if (!topic || !topic.items || topic.items.length === 0) {
    return [['-']];
  }
  return topic.items.map((item: any) => [item.content || '-']);
}

5.4 section(labelKey: string, valueKey: string, topic: any, label: string)

Purpose: Create label and data table pairs Input: Label key, value key, topic data, label text Output: Object with two keys mapping to string[][] Why it matters: Handles topic sections with consistent label/data pattern Current project: MISSING

// Legacy
function section(labelKey: string, valueKey: string, topic: any, label: string) {
  if (!topic) return {};
  return {
    [labelKey]: [[label]],
    [valueKey]: formatTopicItems(topic)
  };
}

5.5 section2(labelKey: string, valueKey: string, topic: any, label: string)

Purpose: Handle extra topics (unmapped/dynamic topics) Input: Same as section but supports arrays Output: Object with numbered keys for multiple topics Why it matters: Allows unlimited extra topics with dynamic numbering Current project: MISSING

5.6 generateExtraTopicFields(extraTopics: any[]): any[]

Purpose: Dynamically generate PDFME schema fields for extra topics Input: Array of extra topics Output: Array of PDFME field definitions Why it matters: Allows extra topics to appear in PDF without template modification Current project: MISSING


6. Legacy Table Data Format

6.1 Topic Tables (Scope, Exclusion, Payment, Warranty, Delivery)

Expected Format: string[][]

  • Each row is an array of strings
  • Each item becomes: [item.content]
  • Empty topic becomes: [["-"]]

Example:

// Input
{
  topicType: "scope",
  items: [
    { content: "Provide crane" },
    { content: "Install at site" }
  ]
}

// Output
[
  ["Provide crane"],
  ["Install at site"]
]

6.2 Price Table

Format: [[string, string, string, string]]

  • Hardcoded 4 columns
  • Example: [[\"Price (Exclude VAT)\",\"฿1,000,000.00\",\" \",\"THB\"]]

6.3 Current Project Table Handling

Issue: Current mapDocumentDataToTemplateInput() returns table data as:

  • Array of objects for dataType: 'table'
  • May not format as string[][] correctly
  • May not handle single-column tables properly

7. Legacy Topic Handling

7.1 Product Type Mapping

Legacy maps quotation type to topic type keys:

const TOPIC_MAPPING = {
  crane: {
    scope: 'scope_of_work',
    exclusion: 'exclusion_from_scope_of_supply',
    terms: 'terms_of_payment_upon_progress_of_each_item',
    delivery: 'delivery_date',
    warranty: 'warranty'
  },
  dockdoor: {
    scope: 'dock_scope_of_work',
    exclusion: 'exclusion_of_supply_installation',
    terms: 'payment_conditions',
    delivery: 'delivery_date',
    warranty: 'warranty'
  },
  solarcell: {
    scope: 'solarcell_scope_of_work',
    exclusion: 'solarcell_exclusion_of_supply_installation',
    terms: 'solarcell_payment_conditions',
    delivery: 'solarcell_delivery',
    warranty: 'solarcell_warranty'
  }
}

7.2 Topic Splitting Logic

Legacy splits topics into two categories:

  1. Mapped topics: scope, exclusion, terms, delivery, warranty (based on quotation type)
  2. Extra topics: All other topics (dynamic)

7.3 Topic Processing

  • Mapped topics use section() helper
  • Extra topics use section2() helper with numbering
  • Extra topics generate dynamic PDFME schema fields

7.4 Current Project Topic Handling

Issues:

  • No product type mapping
  • No topic splitting
  • No dynamic schema generation
  • Topics likely treated uniformly, losing type-specific labels

8. Legacy Approval / Signature Handling

8.1 Approval Fields in Template

Template has static signature fields:

  • app1, app1_position
  • app2, app2_position
  • app3, app3_position

8.2 Legacy Population

Status: Not populated in legacy code

  • Fields contain static placeholder: "Type Something..."
  • Appears to be manually filled or signature-only
  • No dynamic data source

8.3 Current Project

Status: NOT MAPPED

  • No mapping for approval fields
  • No source data for signatures
  • Approval data exists but not connected to PDF

9. Current Project Gaps

9.1 Critical Missing Placeholders

  1. Topic sections (scope, exclusion, terms, warranty, delivery)
  2. Extra topics (dynamic topics)
  3. Approval signatures

9.2 Missing Transform Functions

  1. formatTopicItems() - converts topic items to string[][]
  2. section() - creates label/data table pairs
  3. section2() - handles extra topics with numbering
  4. generateExtraTopicFields() - dynamic schema generation

9.3 Data Shape Mismatches

  1. Tables: Legacy expects string[][], current may return object arrays
  2. Price table: Legacy uses hardcoded 4-column format
  3. Topic items: Legacy uses .content field, current may differ

9.4 Missing Product Type Logic

  • No product type to topic type mapping
  • All topics treated the same way
  • Type-specific labels not applied

9.5 Missing Dynamic Schema Generation

  • Extra topics cannot appear in PDF
  • Template schema is static
  • Cannot handle variable number of topics

9.6 Template Mismatch

  • Current template has item_topic placeholder
  • Legacy uses data_topic with different logic
  • May need template versioning or migration

10.1 Phase 1: Add Missing Transform Functions

Priority: HIGH

Create utility functions in src/features/crm/quotations/document/server/utils.ts:

export function formatTopicItems(topic: { items?: Array<{ content?: string }> }): string[][] {
  if (!topic?.items?.length) {
    return [['-']];
  }
  return topic.items.map((item) => [item.content || '-']);
}

export function createTopicSection(
  label: string,
  topic: { items?: Array<{ content?: string }> }
): Record<string, string[][]> {
  return {
    [`${label}_label`]: [[label]],
    [`${label}_data`]: formatTopicItems(topic)
  };
}

10.2 Phase 2: Add Product Type Mapping

Priority: HIGH

Create mapping configuration:

// src/features/crm/quotations/document/config/topic-mapping.ts
const TOPIC_TYPE_MAPPING = {
  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'
  },
  default: {
    scope: 'scope_of_work',
    exclusion: 'exclusion_from_supply_installation',
    payment: 'payment_conditions',
    delivery: 'delivery',
    warranty: 'warranty'
  }
};

10.3 Phase 3: Update Document Data Builder

Priority: HIGH

Modify buildQuotationDocumentData() in document/server/service.ts:

  1. Add product type detection from quotation
  2. Split topics into mapped and extra categories
  3. Format topic items as string[][]
  4. Add approval/signature data

10.4 Phase 4: Fix Table Data Formatting

Priority: MEDIUM

Update mapDocumentDataToTemplateInput() in document-template/server/service.ts:

if (mapping.dataType === 'table') {
  const rows = Array.isArray(rawValue) ? rawValue : [];
  
  // Check if it's already string[][] format
  if (rows.length > 0 && Array.isArray(rows[0])) {
    result[mapping.placeholderKey] = rows;
  } else {
    // Convert objects to string[][]
    result[mapping.placeholderKey] = rows.map((row) => {
      const rowRecord = row as Record<string, unknown>;
      return mapping.columns.reduce<string[]>((acc, col) => {
        acc.push(String(applyFormatMask(rowRecord[col.sourceField], col.formatMask) || ''));
        return acc;
      }, []);
    });
  });
  }
  continue;
}

10.5 Phase 5: Update Template Mappings

Priority: HIGH

Add mappings for missing placeholders in database:

  1. exclusion_label - static text
  2. exclusion_data - topic data as table
  3. Terms_of_payment - static text
  4. data_term_of_payment - topic data as table
  5. warranty - static text
  6. data_warranty - topic data as table
  7. Delivery - static text
  8. data_delivery - topic data as table
  9. Affter service - static text
  10. data_affter_service - topic data as table
  11. data_topic - general topic data
  12. Approval fields

10.6 Phase 6: Add Approval Signature Data

Priority: MEDIUM

Extract approval workflow data and map to signature fields:

  • app1 = Sales Manager (from approval timeline)
  • app2 = Department Manager (from approval timeline)
  • app3 = Top Manager (from approval timeline)
  • Position fields from user profiles or roles

10.7 Phase 7: Template Verification

Priority: HIGH

Verify current template matches legacy:

  1. Check all placeholder names match
  2. Verify field positions match
  3. Ensure font names match (cordia, cordiaBold)
  4. Validate table structures

10.8 Phase 8: Dynamic Schema Generation

Priority: LOW (Future Enhancement)

Implement dynamic schema generation for extra topics:

  1. Generate PDFME field definitions
  2. Inject into template schema
  3. Calculate positioning dynamically

11. Risk / Notes

11.1 Risks

  1. Template Compatibility: Current template may have different placeholder names or structure
  2. Data Migration: Existing quotations may not have topic data in correct format
  3. Backward Compatibility: Changes may break existing PDF generation
  4. Font Issues: Cordia fonts may not be available in production

11.2 Notes

  1. Legacy Template Location:
    • ALLA: src/features/quotations/pdfme/ALLA_template_pdfme_fainal3.json
    • ONVALLA: src/features/quotations/pdfme/ONVALLA_template_pdfme_fainal3.json
  2. Current Template: src/pdfme_template/ALLA_template_pdfme_fainal3.json
  3. Template Version: Current template may be different version from legacy
  4. Topic Data Model: Current topics structure may need adjustment to match legacy expectations
  5. Approval Data: Current approval system is more complex than legacy, need to map correctly

11.3 Recommendations

  1. Create a migration script to convert existing topic data to new format
  2. Implement comprehensive PDF preview to verify all fields
  3. Add unit tests for each transform function
  4. Document all placeholder mappings in a central configuration
  5. Consider versioning PDFME templates to handle changes

12. Conclusion

The legacy PDFME implementation uses a specialized, direct mapping approach with inline transforms that ensure correct data formatting. The current implementation uses a more generic mapping system that, while more flexible, is missing critical transform logic and placeholder mappings.

Primary Issues:

  1. Missing topic section mappings (scope, exclusion, terms, warranty, delivery)
  2. Missing formatTopicItems() transform function
  3. No support for extra topics
  4. No product type to topic type mapping
  5. Template placeholder mismatch (item_topic vs data_topic)
  6. Missing approval signature mappings

Estimated Effort: 2-3 days to implement all fixes and test thoroughly.

Next Steps:

  1. Implement transform functions (Phase 1)
  2. Add topic mappings (Phase 2-3)
  3. Update template mappings in database (Phase 5)
  4. Test with real quotation data
  5. Compare output with legacy PDFs