Skip to main content

Template Resolver - Location Configuration Integration

Overview​

The TemplateResolverService now implements the complete template resolution hierarchy with location-specific configurations.

Implementation Date​

October 21, 2025

Resolution Order​

The template resolver follows a waterfall pattern with three levels:

1. Location-Specific Template (if locationId provided)
↓ (if not found or inactive)
2. Business Default Template
↓ (if not found or inactive)
3. System Default Template
↓ (if not found)
4. Throw TemplateNotFoundError

How It Works​

1. Location-Specific Resolution​

When: locationId is provided and location config exists

Steps:

  1. Query location_template_config table by locationId + documentType
  2. Verify config is active (isActive = true)
  3. Fetch the configured template by documentTemplateId
  4. Verify template is active (isActive = true)
  5. Merge printer configuration from location settings
  6. Return with source: "location"

Printer Config Merging:

const mergedPrinterConfig = {
...printerConfig, // Base config (if any)
autoPrint: locationConfig.autoPrint, // From location settings
copies: locationPrinterConfig.copies || 1, // From JSONB
...locationPrinterConfig // Any other printer settings
};

2. Business Default Resolution​

When: No location config found or location config inactive

Steps:

  1. Query document_template table for business default
  2. Filter: documentType + businessId + isDefault = true + isActive = true
  3. Return with source: "business"

3. System Default Resolution​

When: No business default found

Steps:

  1. Query document_template table for system default
  2. Filter: documentType + businessId IS NULL + isDefault = true + isActive = true
  3. Return with source: "system"

4. No Template Found​

When: All resolution attempts fail

Result: Throws TemplateNotFoundError

Code Changes​

Updated Files​

File: apps/backend/src/pdf/domain/services/template-resolver.service.ts

Changes:

  1. βœ… Imported LocationTemplateConfigRepository
  2. βœ… Injected repository in constructor
  3. βœ… Added location resolution logic (step 1)
  4. βœ… Merged printer config from location settings
  5. βœ… Updated numbering in comments
  6. βœ… Used optional chaining for cleaner code

Integration Points​

PDF Generation Use Cases​

All PDF generation use cases use the template resolver:

// In generate-sale-pdf.use-case.ts
const resolution = await this.templateResolver.resolveTemplate(
DocumentType.SALE,
sale.businessId,
sale.locationId, // πŸ‘ˆ Location ID enables location-specific templates
);

// Returns:
// - template: The resolved template
// - pdfOptions: PDF generation options
// - printerConfig: Merged printer settings
// - source: "location" | "business" | "system"

Location Configuration Effect​

When a location has a configured template:

// Location config exists:
{
locationId: "abc-123",
documentType: "sale",
documentTemplateId: "template-xyz",
autoPrint: true,
printerConfig: { copies: 2, printerProfileId: "printer-1" },
isActive: true
}

// Resolved result:
{
template: { id: "template-xyz", ... },
pdfOptions: { ... },
printerConfig: {
autoPrint: true,
copies: 2,
printerProfileId: "printer-1"
},
source: "location"
}

Printer Configuration​

The printer config from location settings includes:

  • autoPrint - Whether to auto-print after generation
  • copies - Number of copies to print (stored in JSONB)
  • printerProfileId - Specific printer to use (optional)
  • Custom settings - Any additional printer-specific settings in JSONB

Example Scenarios​

Scenario 1: Location Has Custom Template​

Request: Generate sale PDF for location "Store-A"
Document Type: sale
Business ID: business-123
Location ID: Store-A

Resolution Flow:
1. βœ… Check location config β†’ Found config for Store-A + sale
2. βœ… Load template from config β†’ Template "Store-A Receipt"
3. βœ… Merge printer config β†’ { autoPrint: true, copies: 2 }
4. βœ“ Return: source="location"

Result: Uses "Store-A Receipt" template with auto-print enabled

Scenario 2: No Location Config​

Request: Generate sale PDF for location "Store-B"
Document Type: sale
Business ID: business-123
Location ID: Store-B

Resolution Flow:
1. ❌ Check location config β†’ Not found
2. βœ… Check business default β†’ Found "Business Sale Template"
3. βœ“ Return: source="business"

Result: Uses business-wide default template

Scenario 3: New Business, No Custom Templates​

Request: Generate sale PDF
Document Type: sale
Business ID: new-business-456
Location ID: location-1

Resolution Flow:
1. ❌ Check location config β†’ Not found
2. ❌ Check business default β†’ Not found
3. βœ… Check system default β†’ Found "Standard Sale Template"
4. βœ“ Return: source="system"

Result: Uses built-in system template

Scenario 4: Location Config Inactive​

Request: Generate sale PDF
Document Type: sale
Business ID: business-123
Location ID: Store-C

Resolution Flow:
1. ⚠️ Check location config β†’ Found but isActive=false
2. βœ… Check business default β†’ Found
3. βœ“ Return: source="business"

Result: Skips inactive location config, uses business default

Benefits​

1. Flexibility​

  • Each location can have unique templates
  • Different receipts for different stores
  • Location-specific branding

2. Fallback Safety​

  • Always has a template to use
  • Graceful degradation
  • No broken PDFs

3. Printer Control​

  • Location-specific printer settings
  • Auto-print configuration per location
  • Copy count per location

4. Performance​

  • Single query to check location config
  • Efficient template caching
  • Usage tracking for analytics

5. Maintainability​

  • Clear resolution hierarchy
  • Easy to debug (check source field)
  • Comprehensive logging

Usage in Controllers​

Sale Controller Example​

// In sales.controller.ts
@Post(':id/generate-pdf')
async generatePdf(
@Param('id') saleId: string,
@Body() options: { locationId?: string }
) {
return this.pdfService.generateSalePdf(
saleId,
options.locationId // Pass location for custom template
);
}

PDF Service Integration​

The generate use cases automatically use the resolver:

// In generate-sale-pdf.use-case.ts
const resolution = await this.templateResolver.resolveTemplate(
DocumentType.SALE,
sale.businessId,
sale.locationId // Automatically checks location config
);

// Use the resolved template
const pdfBuffer = await this.generatePdf(
resolution.template,
saleData,
resolution.pdfOptions
);

// Handle auto-print if configured
if (resolution.printerConfig?.autoPrint) {
await this.printService.print(
pdfBuffer,
resolution.printerConfig
);
}

Monitoring & Debugging​

Log Output​

[TemplateResolverService] Resolving template for documentType=sale, businessId=abc, locationId=store-1
[TemplateResolverService] Found location-specific template: template-xyz for location store-1

Resolution Source​

Check the source field in the response to know which template was used:

const resolution = await resolver.resolveTemplate(...);

switch (resolution.source) {
case "location":
console.log("Using location-specific template");
break;
case "business":
console.log("Using business default template");
break;
case "system":
console.log("Using system default template");
break;
}

Usage Analytics​

Templates track usage count:

SELECT 
id,
name,
document_type,
usage_count,
last_used_at
FROM document_template
WHERE business_id = 'abc-123'
ORDER BY usage_count DESC;

Configuration Best Practices​

1. Set Business Defaults First​

-- Set business-wide default for sales
UPDATE document_template
SET is_default = true
WHERE business_id = 'abc'
AND document_type = 'sale'
AND name = 'Standard Receipt';

2. Configure Locations as Needed​

-- Store A uses thermal receipt
INSERT INTO location_template_config (
location_id,
document_type,
document_template_id,
auto_print,
printer_config
) VALUES (
'store-a',
'sale',
'thermal-receipt-template-id',
true,
'{"copies": 1, "printerProfileId": "thermal-80mm"}'
);

3. Keep System Templates Active​

-- Ensure system fallbacks exist
SELECT document_type, COUNT(*)
FROM document_template
WHERE business_id IS NULL
AND is_default = true
AND is_active = true
GROUP BY document_type;

Testing​

Test Location Resolution​

# Create location config
curl -X POST http://localhost:4000/pdf/location-template-configs \
-H "Authorization: Bearer <token>" \
-d '{
"locationId": "test-location",
"documentType": "sale",
"documentTemplateId": "custom-template-id",
"autoPrint": true,
"isActive": true,
"createdBy": "user-id"
}'

# Generate PDF - should use location template
curl -X POST http://localhost:4000/sales/:saleId/pdf \
-H "Authorization: Bearer <token>"

Verify Resolution​

Check logs for:

Found location-specific template: <template-id> for location <location-id>

Troubleshooting​

Location Template Not Used​

Check:

  1. Is locationId being passed to PDF generation?
  2. Is location config isActive = true?
  3. Is the configured template isActive = true?
  4. Does the template exist in the database?

Always Using System Template​

Check:

  1. Does business have any custom templates?
  2. Is business default set (isDefault = true)?
  3. Are business templates active?
  4. Is businessId being passed correctly?

Printer Config Not Applied​

Check:

  1. Is location config loaded?
  2. Is printerConfig JSONB valid?
  3. Are printer settings being read in PDF service?
  4. Check resolution result logs

Future Enhancements​

  1. Template Inheritance - Location inherits from business
  2. Time-Based Templates - Different templates by time of day
  3. Conditional Templates - Based on order amount, customer type, etc.
  4. Template Versioning - Lock locations to specific versions
  5. A/B Testing - Test different templates at different locations
  6. Template Analytics - Track which templates perform best

Conclusion​

The template resolver now fully supports location-specific configurations with:

  • βœ… Complete fallback hierarchy
  • βœ… Printer configuration merging
  • βœ… Auto-print support
  • βœ… Copy count configuration
  • βœ… Active/inactive toggle
  • βœ… Comprehensive logging
  • βœ… Usage tracking
  • βœ… Type safety

Location managers can now configure custom templates for their stores while maintaining safe fallbacks to business and system defaults! πŸŽ‰