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

@@ -41,6 +41,13 @@ export interface ContractViewModel {
template: ContractTemplateMeta;
contractDate: string;
contractYear: number;
/**
* The contract's validity window (`contract_valid_from` / `_until`). Distinct
* from `contractDate`, which is the day the document is generated — these are
* the dates the contract is actually in force between. "—" when unset.
*/
contractStartDate: string;
contractEndDate: string;
client: {
companyName: string;
companyAddress: string;
@@ -68,6 +75,16 @@ export interface ContractViewModel {
scheduledDate: string;
contractType: string;
cargoDescription: string;
/**
* The named cargo type on its own (e.g. "Coffee"), separate from
* `cargoDescription` which folds in free text and a container fallback.
* Lets a clause name the commodity without the surrounding prose.
*/
cargoTypeName: string;
/** Container size alone, e.g. "20ft" / "40ft"; "—" for bulk. */
containerType: string;
/** Every cargo line on the contract, e.g. "Coffee (40ft) × 12". */
cargoSummary: string;
totalWeightVgm: string;
equipmentReturn: string;
hazardousLabel: string;
@@ -195,6 +212,21 @@ export class ContractViewModelBuilder {
'Bulk commodity'
: booking.cargoType?.cargoTypeName || 'Container cargo';
const totalWeight = Number(booking.cargoTotalWeightVgm || 0);
// A booking may carry both sizes; name each one once, in the order booked.
const containerType = [
...new Set(
(booking.bookingContainers ?? [])
.map(
(line) =>
line.containerType?.label ??
(line.containerType?.sizeFt
? `${line.containerType.sizeFt}ft`
: line.containerSize) ??
'',
)
.filter(Boolean),
),
].join(', ');
return {
originLabel: this.yardLabel(booking.originYard),
@@ -207,6 +239,13 @@ export class ContractViewModelBuilder {
scheduledDate: this.formatDate(booking.scheduledDate),
contractType: this.valueOrDash(booking.contractType),
cargoDescription: this.valueOrDash(cargoName),
cargoTypeName: this.valueOrDash(booking.cargoType?.cargoTypeName),
containerType: this.valueOrDash(containerType),
cargoSummary: this.valueOrDash(
[cargoName, containerType ? `(${containerType})` : null]
.filter(Boolean)
.join(' '),
),
totalWeightVgm:
totalWeight > 0 ? `${totalWeight.toLocaleString()} tons` : '—',
equipmentReturn: this.valueOrDash(booking.equipmentReturn),