Files
edr-platform/apps/edr-freight-api/src/contracts/contract-renderer.service.ts
Marshal 3a1a08b1e1 add lashing surcharge for cargo types with hasLashing flag
add lashing surcharge for cargo types with hasLashing flag
2026-07-17 23:25:53 +00:00

89 lines
2.9 KiB
TypeScript

import { Injectable, OnModuleInit } from '@nestjs/common';
import * as fs from 'fs';
import * as path from 'path';
import Handlebars from 'handlebars';
import {
interpolateTemplateText,
parseArticleBody,
RenderedArticle,
} from './contract-article.util';
import { ContractViewModel } from './contract-view-model.builder';
@Injectable()
export class ContractRendererService implements OnModuleInit {
private readonly templatesDir = path.join(__dirname, 'templates');
private readonly compiled = new Map<string, Handlebars.TemplateDelegate>();
onModuleInit(): void {
Handlebars.registerHelper('eq', (a: unknown, b: unknown) => a === b);
const partialsDir = path.join(this.templatesDir, '_partials');
if (fs.existsSync(partialsDir)) {
for (const file of fs.readdirSync(partialsDir)) {
if (!file.endsWith('.hbs')) continue;
const name = file.replace(/\.hbs$/, '');
const content = fs.readFileSync(path.join(partialsDir, file), 'utf-8');
Handlebars.registerPartial(name, content);
}
}
}
render(view: ContractViewModel): string {
const fileName =
view.template.templateFile ?? 'generic.hbs';
const template = this.getCompiled(fileName);
return template({
...view,
paymentArticle: view.pricing.currency === 'ETB' ? 'ETB' : 'USD',
...this.buildDynamicSections(view),
});
}
/**
* Turn the DB-backed dynamic template (when present) into render-ready data:
* interpolate placeholders against the view model, then parse each article
* body into numbered clauses with nested bullets.
*/
private buildDynamicSections(view: ContractViewModel): {
dynamicDocumentTitle?: string;
dynamicWhereas?: string[];
dynamicArticles?: RenderedArticle[];
} {
const dyn = view.dynamicTemplate;
if (!dyn || dyn.articles.length === 0) return {};
const articles = [...dyn.articles]
.sort((a, b) => (a.order ?? 0) - (b.order ?? 0))
.map((article, index) => ({
number: index + 1,
id: article.id,
title: interpolateTemplateText(article.title, view),
...parseArticleBody(interpolateTemplateText(article.body, view)),
}));
return {
dynamicDocumentTitle: interpolateTemplateText(dyn.documentTitle, view),
dynamicWhereas: dyn.whereasClauses.map((clause) =>
interpolateTemplateText(clause, view),
),
dynamicArticles: articles,
};
}
private getCompiled(fileName: string): Handlebars.TemplateDelegate {
const cached = this.compiled.get(fileName);
if (cached) return cached;
const filePath = path.join(this.templatesDir, fileName);
const fallbackPath = path.join(this.templatesDir, 'generic.hbs');
const source = fs.existsSync(filePath)
? fs.readFileSync(filePath, 'utf-8')
: fs.readFileSync(fallbackPath, 'utf-8');
const compiled = Handlebars.compile(source);
this.compiled.set(fileName, compiled);
return compiled;
}
}