make a contrat template

This commit is contained in:
Marshal
2026-07-09 21:13:07 +00:00
parent e4429592d3
commit 3443b79644
26 changed files with 3243 additions and 41 deletions

View File

@@ -0,0 +1,151 @@
import { parseArticleBody, interpolateTemplateText } from './contract-article.util';
import { ContractRendererService } from './contract-renderer.service';
import { getTemplateMeta } from './contract-template.registry';
import type { ContractViewModel } from './contract-view-model.builder';
describe('parseArticleBody', () => {
it('numbers each non-empty line as a clause', () => {
const parsed = parseArticleBody('First clause.\nSecond clause.\n\nThird clause.');
expect(parsed.paragraph).toBeUndefined();
expect(parsed.clauses.map((c) => c.text)).toEqual([
'First clause.',
'Second clause.',
'Third clause.',
]);
});
it('nests "- " lines as bullets under the previous clause', () => {
const parsed = parseArticleBody('Rates are:\n- USD 10 per ton\n- USD 20 per wagon\nPayment in advance.');
expect(parsed.clauses).toHaveLength(2);
expect(parsed.clauses[0].bullets).toEqual(['USD 10 per ton', 'USD 20 per wagon']);
expect(parsed.clauses[1].text).toBe('Payment in advance.');
});
it('renders a single bare line as a paragraph', () => {
const parsed = parseArticleBody('This Agreement becomes effective when signed.');
expect(parsed.paragraph).toBe('This Agreement becomes effective when signed.');
expect(parsed.clauses).toEqual([]);
});
});
describe('interpolateTemplateText', () => {
it('fills placeholders from the view model', () => {
expect(
interpolateTemplateText('Valid until August 31, {{contractYear}}.', {
contractYear: 2026,
}),
).toBe('Valid until August 31, 2026.');
});
it('falls back to raw text on malformed placeholders', () => {
expect(interpolateTemplateText('Broken {{#if}} tag', {})).toBe('Broken {{#if}} tag');
});
});
describe('dynamic template rendering (edr-dynamic.hbs)', () => {
const renderer = new ContractRendererService();
renderer.onModuleInit();
function dynamicView(): ContractViewModel {
const meta = getTemplateMeta('IMP_BULK_USD_FORWARDING');
return {
bookingId: 'test-id',
reference: 'EDR/CT/2026/0042',
status: 'CONTRACT_READY',
templateKey: 'IMP_BULK_USD_FORWARDING',
template: { ...meta, title: 'Bulk Import Contract', templateFile: 'edr-dynamic.hbs' },
contractDate: '1 January 2026',
contractYear: 2026,
client: {
companyName: 'Abyssinia Trading PLC',
companyAddress: 'Bole Sub-city, Addis Ababa',
companyLocation: 'Ethiopia',
phone: '+251900000000',
email: 'test@example.com',
tinNumber: '1234567890',
vatNumber: 'VAT-001',
fanNumber: 'FAN-001',
businessLicense: 'BL-001',
},
provider: {
name: 'Ethio-Djibouti Standard Gauge Railway Share Company',
address: 'Nifas Silk Lafto Sub City, Addis Ababa, Ethiopia',
phone: '+251 11 872 0000',
email: 'info@edr.gov.et',
tinNumber: '—',
},
schedule: {
originLabel: 'Nagad',
destinationLabel: 'Galaan Multipurpose Port',
tradeDirection: 'IMPORT',
freightType: 'BULK',
serviceType: 'Rail + clearance',
scheduledDate: '—',
contractType: 'GENERAL',
cargoDescription: 'Steel billets',
totalWeightVgm: '—',
equipmentReturn: '—',
hazardousLabel: 'No',
firstMilePickupAddress: '—',
lastMileDeliveryAddress: '—',
},
pricing: {
displayMode: 'UNIT_RATES',
unitRates: [
{ label: 'Rail transport', unitPrice: 59.4, unit: 'ton', currency: 'USD' },
],
currency: 'USD',
equipmentReturn: '—',
originLabel: 'Nagad',
destinationLabel: 'Galaan Multipurpose Port',
} as unknown as ContractViewModel['pricing'],
signatures: [],
canSignCustomer: false,
canSignStaff: false,
hasContractDocument: false,
hasCustomerSignature: false,
hasStaffSignature: false,
dynamicTemplate: {
code: 'IMPORT_BULK',
name: 'Bulk Import Contract',
documentTitle: 'Bulk Cargo Transportation and Customs Clearance Services',
whereasClauses: ['The Client has agreed to engage the Service Provider.'],
articles: [
{
id: 'objective',
title: 'Objective of the Services',
body: 'Integrated logistics services including:\n- Rail transport to GMP\n- Customs clearance',
order: 1,
},
{
id: 'duration',
title: 'Duration',
body: 'Valid until August 31, {{contractYear}}.',
order: 2,
},
],
},
};
}
it('renders numbered dynamic articles with bullets and interpolation', () => {
const html = renderer.render(dynamicView());
expect(html).toContain('Bulk Cargo Transportation and Customs Clearance Services');
expect(html).toContain('Article 1');
expect(html).toContain('Objective of the Services');
expect(html).toContain('Rail transport to GMP');
expect(html).toContain('Valid until August 31, 2026.');
expect(html).toContain('Abyssinia Trading PLC');
expect(html).toContain('Annex A — Commercial Schedule');
// Greenish theme marker from styles.hbs
expect(html).toContain('#1b9e7a');
});
it('keeps the generic layout when no dynamic template is attached', () => {
const view = dynamicView();
delete view.dynamicTemplate;
view.template = getTemplateMeta('IMP_BULK_USD_FORWARDING');
const html = renderer.render(view);
expect(html).toContain('Article 5: Contract Price');
});
});