Saltar al contenido principal

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! 🎉