enhance contract document rendering with detailed cargo information

- Updated ContractDocumentViewModelBuilder to include cargoTypeName, containerType, and cargoSummary in the schedule.
- Modified contract dynamic template tests to validate the new cargo fields.
- Enhanced contract renderer service tests to reflect changes in cargo data structure.
- Updated contract view model interface to include new cargo-related fields.
- Improved dynamic template rendering to display cargo type and container type.
- Refactored exchange settings controller and service to streamline error handling and feed status management.
- Introduced article HTML conversion functions to support Quill editor integration for structured article editing.
- Added tests for article HTML conversion to ensure correct round-trip processing of clauses and bullets.
This commit is contained in:
Marshal
2026-08-04 13:03:39 +00:00
parent 45216c5624
commit d0040a6851
14 changed files with 472 additions and 224 deletions

View File

@@ -166,6 +166,8 @@ export class ContractDocumentViewModelBuilder {
year: 'numeric',
}),
contractYear: new Date().getFullYear(),
contractStartDate: this.formatDate(contract.contractValidFrom),
contractEndDate: this.formatDate(contract.contractValidUntil),
client: {
companyName: contract.company?.name ?? 'Client',
companyAddress: this.valueOrDash(contract.company?.address),
@@ -281,7 +283,8 @@ export class ContractDocumentViewModelBuilder {
private buildSchedule(contract: Contract): ContractViewModel['schedule'] {
const firstRoute = this.firstRoute(contract);
const cargoScope = (contract.cargoScope ?? [])[0];
const scope = contract.cargoScope ?? [];
const cargoScope = scope[0];
const cargoName =
cargoScope?.cargoType?.cargoTypeName ||
cargoScope?.cargoFreeText ||
@@ -289,6 +292,28 @@ export class ContractDocumentViewModelBuilder {
? `${cargoScope.containerSize} container`
: 'Container cargo');
// A contract's scope can list several cargo lines (e.g. coffee in 20ft and
// 40ft); name each distinctly rather than collapsing to the first.
const containerType = [
...new Set(scope.map((s) => s.containerSize ?? '').filter(Boolean)),
].join(', ');
const cargoTypeName = [
...new Set(
scope
.map((s) => s.cargoType?.cargoTypeName ?? s.cargoFreeText ?? '')
.filter(Boolean),
),
].join(', ');
const cargoSummary = scope
.map((s) => {
const name = s.cargoType?.cargoTypeName ?? s.cargoFreeText ?? null;
const size = s.containerSize ? `(${s.containerSize})` : null;
const cap = s.quantityCap ? `× ${Number(s.quantityCap)}` : null;
return [name, size, cap].filter(Boolean).join(' ');
})
.filter(Boolean)
.join('; ');
return {
originLabel: this.yardLabel(firstRoute?.originYard),
destinationLabel: this.yardLabel(firstRoute?.destinationYard),
@@ -302,6 +327,9 @@ export class ContractDocumentViewModelBuilder {
scheduledDate: this.formatDate(null),
contractType: this.valueOrDash(contract.contractType),
cargoDescription: this.valueOrDash(cargoName),
cargoTypeName: this.valueOrDash(cargoTypeName),
containerType: this.valueOrDash(containerType),
cargoSummary: this.valueOrDash(cargoSummary),
totalWeightVgm: '—',
equipmentReturn: this.valueOrDash(contract.equipmentReturn),
// A hazardous contract names the declared class + UN number on the