mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
- Add ShippingLineBookingsPage for listing and managing shipping line bookings. - Create ShippingLineDocumentsModal for document uploads related to bookings. - Introduce ShippingLineInitiateModal for initiating new shipping line bookings. - Implement booking document state management with booking-doc-state utility. - Add shipping line bookings service for API interactions. - Update index to export new components and services. - Enhance types for freight to include shipping line credits.
764 lines
28 KiB
TypeScript
764 lines
28 KiB
TypeScript
import { Injectable, Logger } from "@nestjs/common";
|
|
import { DataSource } from "typeorm";
|
|
|
|
import { FileUploadField } from "../modules/file-upload-settings/entities/file-upload-field.entity";
|
|
import { FileUploadSetting } from "../modules/file-upload-settings/entities/file-upload-setting.entity";
|
|
import { poaDelegationField } from "../modules/file-upload-settings/poa-delegation.constants";
|
|
|
|
interface OnboardingField {
|
|
fileKey: string;
|
|
fileLabel: string;
|
|
helpText: string;
|
|
isRequired: boolean;
|
|
isMultiple: boolean;
|
|
maxFiles: number;
|
|
allowedExtensions: string[];
|
|
maxSizeMb: number;
|
|
displayOrder: number;
|
|
}
|
|
|
|
const DOC_EXTENSIONS = ["pdf", "jpg", "jpeg", "png"];
|
|
|
|
/**
|
|
* Listed in the sets below only so the reference defaults stay a complete
|
|
* picture of a company onboarding form. Unlike every other field here, the DARS
|
|
* delegation paper is not admin-managed: `FileUploadSettingsService.getByCode`
|
|
* injects it from poa-delegation.constants.ts whether or not a row exists.
|
|
*/
|
|
const poaDelegationDefault = (displayOrder: number): OnboardingField =>
|
|
poaDelegationField(displayOrder) as unknown as OnboardingField;
|
|
|
|
/** Documents required from an Ethiopian company at onboarding. */
|
|
const ETHIOPIAN_ONBOARDING_FIELDS: OnboardingField[] = [
|
|
{
|
|
fileKey: "tin_certificate",
|
|
fileLabel: "TIN Certificate",
|
|
helpText: "Verified against the TIN registry during registration.",
|
|
isRequired: true,
|
|
isMultiple: false,
|
|
maxFiles: 1,
|
|
allowedExtensions: DOC_EXTENSIONS,
|
|
maxSizeMb: 50,
|
|
displayOrder: 1,
|
|
},
|
|
{
|
|
fileKey: "commercial_license",
|
|
fileLabel: "Commercial Registration",
|
|
helpText:
|
|
"Verified against the government trade system during registration.",
|
|
isRequired: true,
|
|
isMultiple: false,
|
|
maxFiles: 1,
|
|
allowedExtensions: DOC_EXTENSIONS,
|
|
maxSizeMb: 50,
|
|
displayOrder: 2,
|
|
},
|
|
{
|
|
fileKey: "national_id",
|
|
fileLabel: "National ID",
|
|
helpText: "Verified against the National ID API during registration.",
|
|
isRequired: true,
|
|
isMultiple: false,
|
|
maxFiles: 1,
|
|
allowedExtensions: DOC_EXTENSIONS,
|
|
maxSizeMb: 50,
|
|
displayOrder: 3,
|
|
},
|
|
poaDelegationDefault(4),
|
|
];
|
|
|
|
/** Documents required from a Foreign company at onboarding. */
|
|
const FOREIGN_ONBOARDING_FIELDS: OnboardingField[] = [
|
|
{
|
|
fileKey: "tin_certificate",
|
|
fileLabel: "TIN Certificate",
|
|
helpText: "Verified against the TIN registry during registration.",
|
|
isRequired: true,
|
|
isMultiple: false,
|
|
maxFiles: 1,
|
|
allowedExtensions: DOC_EXTENSIONS,
|
|
maxSizeMb: 50,
|
|
displayOrder: 1,
|
|
},
|
|
{
|
|
fileKey: "investment_license",
|
|
fileLabel: "Investment License",
|
|
helpText: "Investment license issued for operating in Ethiopia.",
|
|
isRequired: true,
|
|
isMultiple: false,
|
|
maxFiles: 1,
|
|
allowedExtensions: DOC_EXTENSIONS,
|
|
maxSizeMb: 50,
|
|
displayOrder: 2,
|
|
},
|
|
{
|
|
fileKey: "national_id",
|
|
fileLabel: "National ID",
|
|
helpText: "National ID of the company's authorized representative.",
|
|
isRequired: true,
|
|
isMultiple: false,
|
|
maxFiles: 1,
|
|
allowedExtensions: DOC_EXTENSIONS,
|
|
maxSizeMb: 50,
|
|
displayOrder: 3,
|
|
},
|
|
{
|
|
fileKey: "passport",
|
|
fileLabel: "Passport",
|
|
helpText: "Passport of the company's authorized representative.",
|
|
isRequired: true,
|
|
isMultiple: false,
|
|
maxFiles: 1,
|
|
allowedExtensions: DOC_EXTENSIONS,
|
|
maxSizeMb: 50,
|
|
displayOrder: 4,
|
|
},
|
|
poaDelegationDefault(5),
|
|
];
|
|
|
|
// Legacy combined set for the removed per-company-type codes
|
|
// (company_onboarding_documents_customer/forwarder/transporter/forwarder_dj).
|
|
// const LEGACY_ONBOARDING_FIELDS: OnboardingField[] = [
|
|
// {
|
|
// fileKey: "business_license",
|
|
// fileLabel: "Business License / Trade License",
|
|
// helpText:
|
|
// "Verified against the government trade system during registration.",
|
|
// isRequired: true,
|
|
// isMultiple: false,
|
|
// maxFiles: 1,
|
|
// allowedExtensions: DOC_EXTENSIONS,
|
|
// maxSizeMb: 50,
|
|
// displayOrder: 1,
|
|
// },
|
|
// {
|
|
// fileKey: "tin_certificate",
|
|
// fileLabel: "TIN Certificate",
|
|
// helpText: "Verified against the TIN registry during registration.",
|
|
// isRequired: true,
|
|
// isMultiple: false,
|
|
// maxFiles: 1,
|
|
// allowedExtensions: DOC_EXTENSIONS,
|
|
// maxSizeMb: 50,
|
|
// displayOrder: 2,
|
|
// },
|
|
// {
|
|
// fileKey: "national_id_passport",
|
|
// fileLabel: "National ID / Passport",
|
|
// helpText: "Verified against the National ID API during registration.",
|
|
// isRequired: true,
|
|
// isMultiple: false,
|
|
// maxFiles: 1,
|
|
// allowedExtensions: DOC_EXTENSIONS,
|
|
// maxSizeMb: 50,
|
|
// displayOrder: 3,
|
|
// },
|
|
// ];
|
|
|
|
/**
|
|
* Documents required from a co-operative union or farm — the third alternative
|
|
* to the two nationality sets, not an addition to them. A co-op is always
|
|
* registered in Ethiopia and has a TIN but no business licence, so its
|
|
* registration certificate stands in for the commercial registration every
|
|
* other Ethiopian company uploads.
|
|
*
|
|
* Admin-managed like every other onboarding set: what these members must
|
|
* actually produce is a backoffice decision, edited in the file-settings editor.
|
|
*/
|
|
const COOPERATIVE_ONBOARDING_FIELDS: OnboardingField[] = [
|
|
// First on purpose: only the first field of a new set is seeded, and this is
|
|
// the paper that distinguishes a co-operative from every other company.
|
|
{
|
|
fileKey: "cooperative_registration_certificate",
|
|
fileLabel: "Co-operative Union / Farm Registration Certificate",
|
|
helpText:
|
|
"Certificate issued by the co-operative promotion agency that registered the union or farm.",
|
|
isRequired: true,
|
|
isMultiple: false,
|
|
maxFiles: 1,
|
|
allowedExtensions: DOC_EXTENSIONS,
|
|
maxSizeMb: 50,
|
|
displayOrder: 1,
|
|
},
|
|
{
|
|
fileKey: "tin_certificate",
|
|
fileLabel: "TIN Certificate",
|
|
helpText: "Verified against the TIN registry during registration.",
|
|
isRequired: true,
|
|
isMultiple: false,
|
|
maxFiles: 1,
|
|
allowedExtensions: DOC_EXTENSIONS,
|
|
maxSizeMb: 50,
|
|
displayOrder: 2,
|
|
},
|
|
{
|
|
fileKey: "national_id",
|
|
fileLabel: "National ID",
|
|
helpText: "Verified against the National ID API during registration.",
|
|
isRequired: true,
|
|
isMultiple: false,
|
|
maxFiles: 1,
|
|
allowedExtensions: DOC_EXTENSIONS,
|
|
maxSizeMb: 50,
|
|
displayOrder: 3,
|
|
},
|
|
poaDelegationDefault(4),
|
|
];
|
|
|
|
interface OnboardingDocumentSetting {
|
|
code: string;
|
|
label: string;
|
|
entity: string;
|
|
fields: OnboardingField[];
|
|
}
|
|
|
|
const COMPANY_ONBOARDING_DOCUMENTS: OnboardingDocumentSetting[] = [
|
|
// Nationality-based sets — the document requirements depend only on whether
|
|
// the company is Ethiopian or Foreign (same for importer/exporter/forwarder).
|
|
{
|
|
code: "company_onboarding_documents_ethiopian",
|
|
label: "Ethiopian company onboarding documents",
|
|
entity: "customer",
|
|
fields: ETHIOPIAN_ONBOARDING_FIELDS,
|
|
},
|
|
{
|
|
code: "company_onboarding_documents_foreign",
|
|
label: "Foreign company onboarding documents",
|
|
entity: "customer",
|
|
fields: FOREIGN_ONBOARDING_FIELDS,
|
|
},
|
|
// The third set: a union or farm resolves here INSTEAD of a nationality set
|
|
// (it is always Ethiopian, and holds no business licence).
|
|
{
|
|
code: "company_onboarding_documents_cooperative",
|
|
label: "Co-operative union / farm onboarding documents",
|
|
entity: "customer",
|
|
fields: COOPERATIVE_ONBOARDING_FIELDS,
|
|
},
|
|
// Legacy per-company-type codes — removed, unused by any resolver or portal
|
|
// lookup (only `company_onboarding_documents_ethiopian`/`_foreign` are live).
|
|
// {
|
|
// code: "company_onboarding_documents_customer",
|
|
// label: "Customer onboarding documents",
|
|
// entity: "customer",
|
|
// fields: LEGACY_ONBOARDING_FIELDS,
|
|
// },
|
|
// {
|
|
// code: "company_onboarding_documents_forwarder",
|
|
// label: "Forwarder onboarding documents",
|
|
// entity: "other",
|
|
// fields: LEGACY_ONBOARDING_FIELDS,
|
|
// },
|
|
// {
|
|
// code: "company_onboarding_documents_transporter",
|
|
// label: "Transporter onboarding documents",
|
|
// entity: "other",
|
|
// fields: LEGACY_ONBOARDING_FIELDS,
|
|
// },
|
|
// {
|
|
// code: "company_onboarding_documents_forwarder_dj",
|
|
// label: "Djibouti forwarder onboarding documents",
|
|
// entity: "other",
|
|
// fields: LEGACY_ONBOARDING_FIELDS,
|
|
// },
|
|
];
|
|
|
|
const COMPANY_ONBOARDING_DESCRIPTION =
|
|
"Required documents for external company onboarding, by company nationality.";
|
|
|
|
// ── Clearance document settings ────────────────────────────────────────────
|
|
// Operation/clearance documents collected after contract counter-sign, resolved
|
|
// at runtime from (operationType, freightType, includesCustoms). The `entity`
|
|
// is "booking_clearance" so the backoffice file-settings editor can filter them.
|
|
// Two kinds of set per customs category: a CUSTOMER-INPUT set (the customer
|
|
// uploads) and a GL-OUTPUT set (Global Logistics uploads the customs outputs).
|
|
|
|
// const JPG_EXTENSIONS = ["jpg", "jpeg", "png", "pdf"]; // only used by the commented-out output field sets below
|
|
const CLEARANCE_ENTITY = "booking_clearance";
|
|
|
|
/** Build a clearance field with sensible defaults; `critical` marks isRequired. */
|
|
function clearanceField(
|
|
fileKey: string,
|
|
fileLabel: string,
|
|
displayOrder: number,
|
|
opts?: { required?: boolean; help?: string; extensions?: string[] },
|
|
): OnboardingField {
|
|
return {
|
|
fileKey,
|
|
fileLabel,
|
|
helpText: opts?.help ?? "",
|
|
isRequired: opts?.required ?? true,
|
|
isMultiple: false,
|
|
maxFiles: 1,
|
|
allowedExtensions: opts?.extensions ?? DOC_EXTENSIONS,
|
|
maxSizeMb: 50,
|
|
displayOrder,
|
|
};
|
|
}
|
|
|
|
/** Documents shared by every container import category (with/without customs). */
|
|
const IMPORT_CONTAINER_FIELDS: OnboardingField[] = [
|
|
clearanceField("commercial_invoice", "Commercial Invoice", 1),
|
|
clearanceField("packing_list", "Packing List", 2),
|
|
clearanceField("import_license", "Import License", 3),
|
|
clearanceField("certificate_of_origin", "Certificate of Origin", 4),
|
|
clearanceField(
|
|
"external_freight_cost",
|
|
"External Freight Cost / Checkup Documentation",
|
|
5,
|
|
),
|
|
clearanceField("bill_of_lading", "Bill of Lading / Railway Bill", 6),
|
|
clearanceField("vgm", "Verified Gross Mass (VGM)", 7, { required: true }),
|
|
clearanceField("release_order", "Release Order", 8, { required: true }),
|
|
];
|
|
|
|
/** Documents shared by every container export category (with/without customs). */
|
|
const EXPORT_CONTAINER_FIELDS: OnboardingField[] = [
|
|
clearanceField("booking_confirmation", "Booking Confirmation", 1),
|
|
clearanceField("commercial_invoice", "Commercial Invoice", 2),
|
|
clearanceField("packing_list", "Packing List", 3),
|
|
clearanceField("shipping_instruction", "Shipping Instruction", 4),
|
|
clearanceField("bank_permit", "Bank Permit", 5),
|
|
clearanceField("export_license", "Export License", 6),
|
|
clearanceField("vgm_letter", "VGM Letter", 7, { required: true }),
|
|
clearanceField("railway_bill", "Railway Bill", 8),
|
|
clearanceField("delegation_letter", "Delegation Letter / POA", 9, {
|
|
required: false,
|
|
help: "Required only if EDR manages all transit activity.",
|
|
}),
|
|
];
|
|
|
|
/** Bulk import documents (shorter, transit-focused set). */
|
|
const IMPORT_BULK_FIELDS: OnboardingField[] = [
|
|
clearanceField("packing_list", "Packing List", 1, { required: true }),
|
|
clearanceField("bill_of_loading", "Bill of Loading", 2, { required: true }),
|
|
clearanceField("port_invoice", "Port Invoice", 3),
|
|
];
|
|
|
|
/** Bulk export documents (transit/customs corridor docs). */
|
|
const EXPORT_BULK_FIELDS: OnboardingField[] = [
|
|
clearanceField("release_order_djibouti", "Release Order (Djibouti)", 1),
|
|
clearanceField("port_gate_pass", "Port Gate Pass", 2),
|
|
clearanceField("port_invoice", "Port Invoice", 3),
|
|
];
|
|
|
|
// GL-uploaded customs output documents — unused now that all
|
|
// clearance_output_*/contract_clearance_output_* settings are commented out.
|
|
// const IMPORT_CONTAINER_OUTPUT_FIELDS: OnboardingField[] = [
|
|
// clearanceField("im4", "IM4 — Permanent Import Document", 1),
|
|
// clearanceField("im5", "IM5 — Temporary Import Document", 2, {
|
|
// required: false,
|
|
// }),
|
|
// clearanceField("transit_permitted", "Transit Permitted Screenshot", 3, {
|
|
// extensions: JPG_EXTENSIONS,
|
|
// }),
|
|
// ];
|
|
// const EXPORT_CONTAINER_OUTPUT_FIELDS: OnboardingField[] = [
|
|
// clearanceField("ex3", "EX3 — Permanent Export Document", 1),
|
|
// clearanceField("ex8", "EX8 — Export Transit Document", 2),
|
|
// clearanceField("export_release", "Export Release", 3),
|
|
// clearanceField("t1", "T1 — Transport Document", 4),
|
|
// ];
|
|
|
|
const CLEARANCE_DOCUMENT_SETTINGS: OnboardingDocumentSetting[] = [
|
|
// ── Customer-input sets ──
|
|
{
|
|
code: "clearance_import_container_with_customs",
|
|
label: "Import container clearance documents (with customs)",
|
|
entity: CLEARANCE_ENTITY,
|
|
fields: IMPORT_CONTAINER_FIELDS,
|
|
},
|
|
{
|
|
code: "clearance_import_container_without_customs",
|
|
label: "Import container documents (without customs)",
|
|
entity: CLEARANCE_ENTITY,
|
|
fields: IMPORT_CONTAINER_FIELDS,
|
|
},
|
|
{
|
|
code: "clearance_export_container_with_customs",
|
|
label: "Export container clearance documents (with customs)",
|
|
entity: CLEARANCE_ENTITY,
|
|
fields: EXPORT_CONTAINER_FIELDS,
|
|
},
|
|
{
|
|
code: "clearance_export_container_without_customs",
|
|
label: "Export container documents (without customs)",
|
|
entity: CLEARANCE_ENTITY,
|
|
fields: EXPORT_CONTAINER_FIELDS,
|
|
},
|
|
{
|
|
code: "clearance_import_bulk_with_customs",
|
|
label: "Import bulk clearance documents (with customs)",
|
|
entity: CLEARANCE_ENTITY,
|
|
fields: IMPORT_BULK_FIELDS,
|
|
},
|
|
{
|
|
code: "clearance_import_bulk_without_customs",
|
|
label: "Import bulk documents (without customs)",
|
|
entity: CLEARANCE_ENTITY,
|
|
fields: IMPORT_BULK_FIELDS,
|
|
},
|
|
{
|
|
code: "clearance_export_bulk_with_customs",
|
|
label: "Export bulk clearance documents (with customs)",
|
|
entity: CLEARANCE_ENTITY,
|
|
fields: EXPORT_BULK_FIELDS,
|
|
},
|
|
{
|
|
code: "clearance_export_bulk_without_customs",
|
|
label: "Export bulk documents (without customs)",
|
|
entity: CLEARANCE_ENTITY,
|
|
fields: EXPORT_BULK_FIELDS,
|
|
},
|
|
// ── GL-output sets (customs only) ──
|
|
// Commented out per request. NOTE: these codes are still resolved and
|
|
// looked up at runtime by clearanceOutputSettingCode() in clearance.util.ts,
|
|
// called from booking-transition.service.ts (finalizeClearance,
|
|
// uploadClearanceOutputDocuments) and booking-clearance.service.ts —
|
|
// reachable from bookings.controller.ts's finalizeClearance/
|
|
// uploadClearanceOutputDocuments endpoints. finalizeClearance does not
|
|
// catch getByCode()'s NotFoundException, so finalizing a customs-enabled
|
|
// booking will 500 once these rows are gone from the DB too.
|
|
// {
|
|
// code: "clearance_output_import_container",
|
|
// label: "Customs output documents (import container)",
|
|
// entity: CLEARANCE_ENTITY,
|
|
// fields: IMPORT_CONTAINER_OUTPUT_FIELDS,
|
|
// },
|
|
// {
|
|
// code: "clearance_output_export_container",
|
|
// label: "Customs output documents (export container)",
|
|
// entity: CLEARANCE_ENTITY,
|
|
// fields: EXPORT_CONTAINER_OUTPUT_FIELDS,
|
|
// },
|
|
// Bulk output sets mirror the container output docs so customs+bulk bookings
|
|
// can finalize (previously bulk had no output set and got stuck at finalize).
|
|
// {
|
|
// code: "clearance_output_import_bulk",
|
|
// label: "Customs output documents (import bulk)",
|
|
// entity: CLEARANCE_ENTITY,
|
|
// fields: IMPORT_CONTAINER_OUTPUT_FIELDS,
|
|
// },
|
|
// {
|
|
// code: "clearance_output_export_bulk",
|
|
// label: "Customs output documents (export bulk)",
|
|
// entity: CLEARANCE_ENTITY,
|
|
// fields: EXPORT_CONTAINER_OUTPUT_FIELDS,
|
|
// },
|
|
];
|
|
|
|
// ── Contract pre-booking clearance settings ─────────────────────────────────
|
|
// Removed — clearance is now collected once, per booking, instead of also on
|
|
// the contract. See bookings/clearance.util.ts + CLEARANCE_DOCUMENT_SETTINGS.
|
|
// const CONTRACT_CLEARANCE_ENTITY = "contract_clearance";
|
|
|
|
// Contract-level IMPORT/EXPORT clearance input codes — removed. Clearance is
|
|
// now collected once, per booking, via bookings/clearance.util.ts's 4+4
|
|
// clearance_{op}_{freight}_{with|without}_customs codes. Kept here as
|
|
// reference in case a contract-level step is reinstated.
|
|
// const CONTRACT_CLEARANCE_SETTINGS: OnboardingDocumentSetting[] = [
|
|
// {
|
|
// code: "contract_clearance_import_container_with_customs",
|
|
// label: "Contract clearance documents (import container, with customs)",
|
|
// entity: CONTRACT_CLEARANCE_ENTITY,
|
|
// fields: IMPORT_CONTAINER_FIELDS,
|
|
// },
|
|
// {
|
|
// code: "contract_clearance_export_container_with_customs",
|
|
// label: "Contract clearance documents (export container, with customs)",
|
|
// entity: CONTRACT_CLEARANCE_ENTITY,
|
|
// fields: EXPORT_CONTAINER_FIELDS,
|
|
// },
|
|
// {
|
|
// code: "contract_clearance_import_bulk_with_customs",
|
|
// label: "Contract clearance documents (import bulk, with customs)",
|
|
// entity: CONTRACT_CLEARANCE_ENTITY,
|
|
// fields: IMPORT_BULK_FIELDS,
|
|
// },
|
|
// {
|
|
// code: "contract_clearance_export_bulk_with_customs",
|
|
// label: "Contract clearance documents (export bulk, with customs)",
|
|
// entity: CONTRACT_CLEARANCE_ENTITY,
|
|
// fields: EXPORT_BULK_FIELDS,
|
|
// },
|
|
// ];
|
|
|
|
// GL ET output sets uploaded during pre-booking clearance — commented out
|
|
// per request. NOTE: still resolved/looked up at runtime by
|
|
// contractClearanceOutputSettingCode() in contract-clearance.util.ts,
|
|
// called from contract-clearance.service.ts (finalize, uploadOutputDocuments)
|
|
// — reachable from contracts.controller.ts. `finalize` does not catch
|
|
// getByCode()'s NotFoundException, so finalizing a customs-enabled contract
|
|
// will 500 once these rows are gone from the DB too.
|
|
// const CONTRACT_CLEARANCE_OUTPUT_SETTINGS: OnboardingDocumentSetting[] = [
|
|
// {
|
|
// code: "contract_clearance_output_import_container",
|
|
// label: "Contract customs output documents (import container)",
|
|
// entity: CONTRACT_CLEARANCE_ENTITY,
|
|
// fields: IMPORT_CONTAINER_OUTPUT_FIELDS,
|
|
// },
|
|
// {
|
|
// code: "contract_clearance_output_export_container",
|
|
// label: "Contract customs output documents (export container)",
|
|
// entity: CONTRACT_CLEARANCE_ENTITY,
|
|
// fields: EXPORT_CONTAINER_OUTPUT_FIELDS,
|
|
// },
|
|
// {
|
|
// code: "contract_clearance_output_import_bulk",
|
|
// label: "Contract customs output documents (import bulk)",
|
|
// entity: CONTRACT_CLEARANCE_ENTITY,
|
|
// fields: IMPORT_CONTAINER_OUTPUT_FIELDS,
|
|
// },
|
|
// {
|
|
// code: "contract_clearance_output_export_bulk",
|
|
// label: "Contract customs output documents (export bulk)",
|
|
// entity: CONTRACT_CLEARANCE_ENTITY,
|
|
// fields: EXPORT_CONTAINER_OUTPUT_FIELDS,
|
|
// },
|
|
// ];
|
|
|
|
// ── Path A self-clearance settings — removed (contract-level clearance input
|
|
// codes no longer exist; see CONTRACT_CLEARANCE_SETTINGS above). Kept as
|
|
// reference in case a contract-level step is reinstated.
|
|
// const SELF_CLEARANCE_IMPORT_FIELDS: OnboardingField[] = [
|
|
// clearanceField("customs_declaration", "Customs Declaration (IM4/IM5)", 1),
|
|
// clearanceField("import_release", "Import Release Permit", 2),
|
|
// clearanceField("duty_tax_receipt", "Duty & Tax Payment Receipt", 3, {
|
|
// required: false,
|
|
// }),
|
|
// clearanceField("delivery_order", "Delivery Order", 4, { required: false }),
|
|
// clearanceField("supporting_document", "Other Clearance Document", 5, {
|
|
// required: false,
|
|
// }),
|
|
// ];
|
|
// const SELF_CLEARANCE_EXPORT_FIELDS: OnboardingField[] = [
|
|
// clearanceField("customs_declaration", "Customs Declaration (EX3/EX8)", 1),
|
|
// clearanceField("export_release", "Export Release", 2),
|
|
// clearanceField("transit_document", "Transit Document (T1)", 3, {
|
|
// required: false,
|
|
// }),
|
|
// clearanceField("supporting_document", "Other Clearance Document", 4, {
|
|
// required: false,
|
|
// }),
|
|
// ];
|
|
// const SELF_CLEARANCE_SETTINGS: OnboardingDocumentSetting[] = [
|
|
// {
|
|
// code: "contract_clearance_import_container_without_customs",
|
|
// label: "Contract clearance documents (import container, without customs)",
|
|
// entity: CONTRACT_CLEARANCE_ENTITY,
|
|
// fields: SELF_CLEARANCE_IMPORT_FIELDS,
|
|
// },
|
|
// {
|
|
// code: "contract_clearance_export_container_without_customs",
|
|
// label: "Contract clearance documents (export container, without customs)",
|
|
// entity: CONTRACT_CLEARANCE_ENTITY,
|
|
// fields: SELF_CLEARANCE_EXPORT_FIELDS,
|
|
// },
|
|
// {
|
|
// code: "contract_clearance_import_bulk_without_customs",
|
|
// label: "Contract clearance documents (import bulk, without customs)",
|
|
// entity: CONTRACT_CLEARANCE_ENTITY,
|
|
// fields: SELF_CLEARANCE_IMPORT_FIELDS,
|
|
// },
|
|
// {
|
|
// code: "contract_clearance_export_bulk_without_customs",
|
|
// label: "Contract clearance documents (export bulk, without customs)",
|
|
// entity: CONTRACT_CLEARANCE_ENTITY,
|
|
// fields: SELF_CLEARANCE_EXPORT_FIELDS,
|
|
// },
|
|
// ];
|
|
|
|
// ── Contract intake settings ────────────────────────────────────────────────
|
|
// Commercial/framework documents attached at contract submission (wizard step 5),
|
|
// distinct from the post-sign clearance docs above.
|
|
const CONTRACT_INTAKE_ENTITY = "contract_intake";
|
|
|
|
// Removed — unused. Nothing resolves/looks up `contract_intake_documents` by
|
|
// code anywhere in the API or web apps.
|
|
// const CONTRACT_INTAKE_FIELDS: OnboardingField[] = [
|
|
// clearanceField(
|
|
// "commercial_framework",
|
|
// "Commercial Framework / Agreement",
|
|
// 1,
|
|
// {
|
|
// required: false,
|
|
// },
|
|
// ),
|
|
// clearanceField("onboarding_attachment", "Onboarding Attachment", 2, {
|
|
// required: false,
|
|
// }),
|
|
// clearanceField("supporting_document", "Supporting Document", 3, {
|
|
// required: false,
|
|
// }),
|
|
// ];
|
|
// const CONTRACT_INTAKE_SETTINGS: OnboardingDocumentSetting[] = [
|
|
// {
|
|
// code: "contract_intake_documents",
|
|
// label: "Contract intake documents",
|
|
// entity: CONTRACT_INTAKE_ENTITY,
|
|
// fields: CONTRACT_INTAKE_FIELDS,
|
|
// },
|
|
// ];
|
|
|
|
const CLEARANCE_DESCRIPTION =
|
|
"Operation/clearance documents collected after contract execution, by operation, freight type and customs.";
|
|
|
|
// ── Driver documents ────────────────────────────────────────────────────────
|
|
// Configurable upload area (code "driver_docs") attached to a driver profile —
|
|
// license, national ID, contracts, training certificates, etc.
|
|
const DRIVER_DOCUMENT_FIELDS: OnboardingField[] = [
|
|
{
|
|
fileKey: "driver_docs",
|
|
fileLabel: "Driver documents",
|
|
helpText: "License, national ID, contracts, training certificates, etc.",
|
|
isRequired: false,
|
|
isMultiple: true,
|
|
maxFiles: 20,
|
|
allowedExtensions: ["pdf", "jpg", "jpeg", "png", "doc", "docx"],
|
|
maxSizeMb: 50,
|
|
displayOrder: 1,
|
|
},
|
|
];
|
|
|
|
const DRIVER_DOCUMENT_SETTINGS: OnboardingDocumentSetting[] = [
|
|
{
|
|
code: "driver_docs",
|
|
label: "Driver documents",
|
|
entity: "driver",
|
|
fields: DRIVER_DOCUMENT_FIELDS,
|
|
},
|
|
];
|
|
|
|
// ── Intercity documents ─────────────────────────────────────────────────────
|
|
// One shared set for DOMESTIC (intercity) shipments, reviewed by Operations.
|
|
// ONE_TIME contracts collect it at contract level after both signatures;
|
|
// GENERAL contracts collect it per booking right after the booking is created.
|
|
// Fields start empty and are configured in the backoffice file-settings editor.
|
|
const INTERCITY_DOCUMENT_SETTINGS: OnboardingDocumentSetting[] = [
|
|
{
|
|
code: "intercity_documents",
|
|
label: "Intercity documents",
|
|
entity: "booking",
|
|
fields: [],
|
|
},
|
|
];
|
|
|
|
// ── Shipping line booking documents ─────────────────────────────────────────
|
|
// Collected on a shipping line's booking right after it is initiated. Shipping
|
|
// lines book without a contract, so this set — not a contract — is what
|
|
// Operations reviews before the booking may be completed. Fields start empty
|
|
// and are configured in the backoffice file-settings editor, like the sets
|
|
// above. `entity: "booking"` puts it alongside the other per-booking sets.
|
|
const SHIPPING_LINE_DOCUMENT_SETTINGS: OnboardingDocumentSetting[] = [
|
|
{
|
|
code: "shipping_line_booking_documents",
|
|
label: "Shipping line booking documents",
|
|
entity: "booking",
|
|
fields: [],
|
|
},
|
|
];
|
|
|
|
// ── Hazardous cargo documents ───────────────────────────────────────────────
|
|
// Asked for in the contract wizard the moment the customer flags the cargo as
|
|
// hazardous (ONE_TIME contracts only). Fields start empty and are configured in
|
|
// the backoffice file-settings editor.
|
|
const HAZARDOUS_DOCUMENT_SETTINGS: OnboardingDocumentSetting[] = [
|
|
{
|
|
code: "hazardous_documents",
|
|
label: "Hazardous cargo documents",
|
|
entity: CONTRACT_INTAKE_ENTITY,
|
|
fields: [],
|
|
},
|
|
];
|
|
|
|
@Injectable()
|
|
export class FileUploadSettingsSeeder {
|
|
private readonly logger = new Logger(FileUploadSettingsSeeder.name);
|
|
|
|
constructor(private readonly dataSource: DataSource) { }
|
|
|
|
async run() {
|
|
const settingRepository = this.dataSource.getRepository(FileUploadSetting);
|
|
|
|
// Seed per CODE, not "only into an empty table". An existing row is
|
|
// admin-managed and never touched — including a soft-deleted one, which
|
|
// means the set was removed on purpose (and would still conflict on the
|
|
// unique `code`). What the table-wide check got wrong is the other half: a
|
|
// set added to this file after the first boot could never reach a database
|
|
// that already held the others, so it existed in code and nowhere else.
|
|
const existingCodes = new Set(
|
|
(await settingRepository.find({ withDeleted: true })).map((s) => s.code),
|
|
);
|
|
|
|
const allSettings: Array<
|
|
OnboardingDocumentSetting & { description: string }
|
|
> = [
|
|
...COMPANY_ONBOARDING_DOCUMENTS.map((s) => ({
|
|
...s,
|
|
description: COMPANY_ONBOARDING_DESCRIPTION,
|
|
})),
|
|
...CLEARANCE_DOCUMENT_SETTINGS.map((s) => ({
|
|
...s,
|
|
description: CLEARANCE_DESCRIPTION,
|
|
})),
|
|
...DRIVER_DOCUMENT_SETTINGS.map((s) => ({
|
|
...s,
|
|
description:
|
|
"Documents uploaded against a driver profile (license, ID, contracts, etc.).",
|
|
})),
|
|
...HAZARDOUS_DOCUMENT_SETTINGS.map((s) => ({
|
|
...s,
|
|
description:
|
|
"Documents required when a one-time contract's cargo is flagged hazardous.",
|
|
})),
|
|
...INTERCITY_DOCUMENT_SETTINGS.map((s) => ({
|
|
...s,
|
|
description:
|
|
"Intercity shipment documents — contract-level for ONE_TIME (after both signatures), per booking for GENERAL; reviewed by Operations.",
|
|
})),
|
|
...SHIPPING_LINE_DOCUMENT_SETTINGS.map((s) => ({
|
|
...s,
|
|
description:
|
|
"Documents a shipping line uploads on a booking it initiated. Reviewed by Operations; the booking can only be completed once they are approved.",
|
|
})),
|
|
];
|
|
|
|
const missing = allSettings.filter((s) => !existingCodes.has(s.code));
|
|
if (missing.length === 0) {
|
|
this.logger.log("file upload settings up to date — nothing to seed");
|
|
return;
|
|
}
|
|
|
|
const inserted = await settingRepository.save(
|
|
missing.map((documentSetting) =>
|
|
settingRepository.create({
|
|
code: documentSetting.code,
|
|
label: documentSetting.label,
|
|
description: documentSetting.description,
|
|
entity: documentSetting.entity,
|
|
}),
|
|
),
|
|
);
|
|
|
|
// A brand-new set gets exactly ONE field: its first reference default. The
|
|
// rest of the list above stays documentation — what a set actually asks for
|
|
// is a backoffice decision, edited in the file-settings editor. Seeding one
|
|
// means a set is never born empty (an empty set silently requires nothing),
|
|
// while leaving the admin a single row to extend rather than a list to prune.
|
|
const fieldRepository = this.dataSource.getRepository(FileUploadField);
|
|
const firstFields = inserted.flatMap((setting) => {
|
|
const reference = missing.find((s) => s.code === setting.code)?.fields[0];
|
|
return reference
|
|
? [fieldRepository.create({ ...reference, settingId: setting.id })]
|
|
: [];
|
|
});
|
|
if (firstFields.length > 0) await fieldRepository.save(firstFields);
|
|
|
|
this.logger.log(
|
|
`Seeded ${missing.length} file upload settings (${firstFields.length} with a default field): ${missing
|
|
.map((s) => s.code)
|
|
.join(", ")}`,
|
|
);
|
|
}
|
|
}
|