mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-27 23:40:56 +00:00
- Added ClearanceCard component to display and manage clearance documents in ReadonlyBookingView. - Introduced new API endpoints for clearance operations: getClearance, submitClearanceDocuments, and proceedToOperation. - Created BookingDocumentReview entity and migration for document review status tracking. - Developed GlClearancePage for Global Logistics to review and manage document submissions. - Implemented utility functions for determining clearance setting codes based on trade direction and freight type. - Added tests for booking transition clearance logic and clearance utility functions.
428 lines
14 KiB
TypeScript
428 lines
14 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";
|
|
|
|
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"];
|
|
|
|
/** 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: 10,
|
|
displayOrder: 1,
|
|
},
|
|
{
|
|
fileKey: "commercial_license",
|
|
fileLabel: "Commercial License",
|
|
helpText: "Verified against the government trade system during registration.",
|
|
isRequired: true,
|
|
isMultiple: false,
|
|
maxFiles: 1,
|
|
allowedExtensions: DOC_EXTENSIONS,
|
|
maxSizeMb: 10,
|
|
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: 10,
|
|
displayOrder: 3,
|
|
},
|
|
];
|
|
|
|
/** 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: 10,
|
|
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: 10,
|
|
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: 10,
|
|
displayOrder: 3,
|
|
},
|
|
{
|
|
fileKey: "passport",
|
|
fileLabel: "Passport",
|
|
helpText: "Passport of the company's authorized representative.",
|
|
isRequired: true,
|
|
isMultiple: false,
|
|
maxFiles: 1,
|
|
allowedExtensions: DOC_EXTENSIONS,
|
|
maxSizeMb: 10,
|
|
displayOrder: 4,
|
|
},
|
|
];
|
|
|
|
/** Legacy combined set, kept for the older per-company-type codes. */
|
|
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: 10,
|
|
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: 10,
|
|
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: 10,
|
|
displayOrder: 3,
|
|
},
|
|
];
|
|
|
|
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,
|
|
},
|
|
// Legacy per-company-type codes (kept for back-compat; no longer used by the portal).
|
|
{
|
|
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"];
|
|
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: 10,
|
|
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 (import container). */
|
|
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,
|
|
}),
|
|
];
|
|
|
|
/** GL-uploaded customs output documents (export container). */
|
|
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) ──
|
|
{
|
|
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,
|
|
},
|
|
];
|
|
|
|
const CLEARANCE_DESCRIPTION =
|
|
"Operation/clearance documents collected after contract execution, by operation, freight type and customs.";
|
|
|
|
@Injectable()
|
|
export class FileUploadSettingsSeeder {
|
|
private readonly logger = new Logger(FileUploadSettingsSeeder.name);
|
|
|
|
constructor(private readonly dataSource: DataSource) {}
|
|
|
|
async run() {
|
|
await this.dataSource.transaction(async (manager) => {
|
|
const settingRepository = manager.getRepository(FileUploadSetting);
|
|
const fieldRepository = manager.getRepository(FileUploadField);
|
|
|
|
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,
|
|
})),
|
|
];
|
|
|
|
for (const documentSetting of allSettings) {
|
|
await settingRepository.upsert(
|
|
{
|
|
code: documentSetting.code,
|
|
label: documentSetting.label,
|
|
description: documentSetting.description,
|
|
entity: documentSetting.entity,
|
|
},
|
|
{
|
|
conflictPaths: { code: true },
|
|
},
|
|
);
|
|
|
|
const setting = await settingRepository.findOne({
|
|
where: { code: documentSetting.code },
|
|
select: { id: true, code: true },
|
|
});
|
|
|
|
if (!setting) {
|
|
throw new Error(`file_upload_setting_seed_failed:${documentSetting.code}`);
|
|
}
|
|
|
|
await fieldRepository.delete({ settingId: setting.id });
|
|
|
|
await fieldRepository.insert(
|
|
documentSetting.fields.map((field, index) => ({
|
|
settingId: setting.id,
|
|
fileKey: field.fileKey,
|
|
fileLabel: field.fileLabel,
|
|
helpText: field.helpText,
|
|
isRequired: field.isRequired,
|
|
isMultiple: field.isMultiple,
|
|
maxFiles: field.maxFiles,
|
|
allowedExtensions: [...field.allowedExtensions],
|
|
maxSizeMb: field.maxSizeMb,
|
|
displayOrder: field.displayOrder ?? index + 1,
|
|
})),
|
|
);
|
|
}
|
|
});
|
|
|
|
this.logger.log(
|
|
"Ensured company onboarding + booking clearance file upload settings",
|
|
);
|
|
}
|
|
}
|