mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
129 lines
4.8 KiB
TypeScript
129 lines
4.8 KiB
TypeScript
import { BaseEntity } from "@edr/api-common";
|
|
import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm";
|
|
|
|
import { CargoType } from "../../rule-engine/entities/cargo-type.entity";
|
|
|
|
/**
|
|
* The five seeded container templates (import/export split by customs
|
|
* clearing; intercity is domestic, crosses no border, so it has a single
|
|
* template). These are system rows: always present, never deletable.
|
|
*
|
|
* Bulk templates are NOT seeded — staff create them per bulk cargo type
|
|
* (`cargoTypeId`) and customs option (`withCustoms`), one template per
|
|
* combination. Their codes are generated as BULK_<cargo code>_(NO_)CUSTOMS.
|
|
* The retired direction-keyed bulk codes remain listed so old frozen document
|
|
* snapshots still label correctly.
|
|
*
|
|
* Contracts store DOMESTIC for intercity movements; the template layer labels
|
|
* those INTERCITY to match the commercial vocabulary used on the printed
|
|
* documents.
|
|
*
|
|
* The `_CUSTOMS` variant is issued when the contract has customs clearing
|
|
* enabled (the Service Provider clears in Djibouti/Ethiopia on the Client's
|
|
* behalf); `_NO_CUSTOMS` is the transport-only paper, where the Client handles
|
|
* its own declarations.
|
|
*/
|
|
export const CONTRACT_TEMPLATE_CODES = [
|
|
"IMPORT_BULK_CUSTOMS",
|
|
"IMPORT_BULK_NO_CUSTOMS",
|
|
"EXPORT_BULK_CUSTOMS",
|
|
"EXPORT_BULK_NO_CUSTOMS",
|
|
"INTERCITY_BULK",
|
|
"IMPORT_CONTAINER_CUSTOMS",
|
|
"IMPORT_CONTAINER_NO_CUSTOMS",
|
|
"EXPORT_CONTAINER_CUSTOMS",
|
|
"EXPORT_CONTAINER_NO_CUSTOMS",
|
|
"INTERCITY_CONTAINER",
|
|
] as const;
|
|
|
|
export type ContractTemplateCode = (typeof CONTRACT_TEMPLATE_CODES)[number];
|
|
|
|
/**
|
|
* One dynamic article on a contract template. `body` is plain multiline text:
|
|
* each non-empty line renders as a numbered clause; lines prefixed with "- "
|
|
* render as bullet points nested under the preceding clause. A single-line
|
|
* body renders as an unnumbered paragraph. Handlebars placeholders (e.g.
|
|
* {{client.companyName}}, {{contractDate}}, {{contractYear}}, {{reference}})
|
|
* are interpolated against the contract view model at render time.
|
|
*/
|
|
export interface ContractTemplateArticle {
|
|
id: string;
|
|
title: string;
|
|
body: string;
|
|
order: number;
|
|
}
|
|
|
|
/**
|
|
* Map a contract's stored direction/freight/customs triple onto a template
|
|
* code. `customsClearingEnabled` is treated as false when absent so an older
|
|
* contract row with a null flag still resolves to a real template rather than
|
|
* falling through to the generic layout.
|
|
*
|
|
* Intercity is domestic and has no customs leg, so it resolves to a single
|
|
* unsuffixed code regardless of the flag.
|
|
*/
|
|
export function contractTemplateCodeFor(
|
|
tradeDirection?: string | null,
|
|
freightType?: string | null,
|
|
customsClearingEnabled?: boolean | null,
|
|
): ContractTemplateCode {
|
|
const direction =
|
|
tradeDirection === "IMPORT"
|
|
? "IMPORT"
|
|
: tradeDirection === "EXPORT"
|
|
? "EXPORT"
|
|
: "INTERCITY";
|
|
const freight =
|
|
(freightType ?? "").toUpperCase().includes("BULK") ? "BULK" : "CONTAINER";
|
|
if (direction === "INTERCITY") {
|
|
return `INTERCITY_${freight}` as ContractTemplateCode;
|
|
}
|
|
const customs = customsClearingEnabled ? "CUSTOMS" : "NO_CUSTOMS";
|
|
return `${direction}_${freight}_${customs}` as ContractTemplateCode;
|
|
}
|
|
|
|
@Entity({ schema: "freight", name: "contract_templates" })
|
|
// Uniqueness lives in partial DB indexes (live rows only): code, and
|
|
// (cargo_type_id, with_customs) for staff-created bulk templates.
|
|
@Index(["code"])
|
|
export class ContractTemplate extends BaseEntity {
|
|
@Column({ name: "code", type: "varchar", length: 80 })
|
|
code!: string;
|
|
|
|
@Column({ name: "name", type: "varchar", length: 200 })
|
|
name!: string;
|
|
|
|
@Column({ name: "description", type: "text", nullable: true })
|
|
description?: string | null;
|
|
|
|
/** Cover-page service line, e.g. "Steel Billet Transportation and Customs Clearance Services". */
|
|
@Column({ name: "document_title", type: "varchar", length: 300 })
|
|
documentTitle!: string;
|
|
|
|
/** WHEREAS recitals rendered between the parties block and the articles. */
|
|
@Column({ name: "whereas_clauses", type: "jsonb", default: () => "'[]'" })
|
|
whereasClauses!: string[];
|
|
|
|
@Column({ name: "articles", type: "jsonb", default: () => "'[]'" })
|
|
articles!: ContractTemplateArticle[];
|
|
|
|
@Column({ name: "is_active", type: "boolean", default: true })
|
|
isActive!: boolean;
|
|
|
|
/** Bulk templates only: the cargo type this template is written for. */
|
|
@Column({ name: "cargo_type_id", type: "uuid", nullable: true })
|
|
cargoTypeId?: string | null;
|
|
|
|
@ManyToOne(() => CargoType, { nullable: true })
|
|
@JoinColumn({ name: "cargo_type_id" })
|
|
cargoType?: CargoType | null;
|
|
|
|
/** Bulk templates only: whether this is the with-customs-clearing variant. */
|
|
@Column({ name: "with_customs", type: "boolean", nullable: true })
|
|
withCustoms?: boolean | null;
|
|
|
|
/** The five seeded container templates — cannot be deleted. */
|
|
@Column({ name: "is_system", type: "boolean", default: false })
|
|
isSystem!: boolean;
|
|
}
|