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", ); } }