From 756ad73e9cdb354b597a10d84235454b426fbcfa Mon Sep 17 00:00:00 2001 From: Hagernesh Date: Mon, 6 Jul 2026 13:16:34 +0000 Subject: [PATCH] pERTRUCK EXIT --- .../bookings/container-receipt.service.ts | 10 +++--- .../bookings/customer-truck.service.ts | 4 +-- .../warehouses/warehouse-inventory.service.ts | 34 +++++++++++++----- .../warehouse-release-document.service.ts | 36 +++++++++++++++---- .../warehouses/TruckDispatchModal.tsx | 23 ++++++++++-- .../src/services/warehouse.service.ts | 11 ++++-- 6 files changed, 90 insertions(+), 28 deletions(-) diff --git a/apps/edr-freight-api/src/modules/bookings/container-receipt.service.ts b/apps/edr-freight-api/src/modules/bookings/container-receipt.service.ts index fde3ab797..fea73603a 100644 --- a/apps/edr-freight-api/src/modules/bookings/container-receipt.service.ts +++ b/apps/edr-freight-api/src/modules/bookings/container-receipt.service.ts @@ -38,7 +38,7 @@ export class ContainerReceiptService { SET received_to_port = true, received_at = COALESCE(bcu.received_at, NOW()), updated_at = NOW() - FROM freight.booking_containers bc, + FROM freight.booking_container bc, freight.customer_truck_containers ctc WHERE bc.id = bcu.booking_container_id AND bc.booking_id = $1 @@ -60,7 +60,7 @@ export class ContainerReceiptService { bcu.received_at AS "receivedAt", bcu.grn_number AS "grnNumber" FROM freight.booking_container_units bcu - JOIN freight.booking_containers bc + JOIN freight.booking_container bc ON bc.id = bcu.booking_container_id AND bc.deleted_at IS NULL WHERE bc.booking_id = $1 AND bcu.deleted_at IS NULL @@ -92,7 +92,7 @@ export class ContainerReceiptService { const pending: ReceivedUnitRow[] = await manager.query( `SELECT bcu.id, bcu.container_number AS "containerNumber" FROM freight.booking_container_units bcu - JOIN freight.booking_containers bc + JOIN freight.booking_container bc ON bc.id = bcu.booking_container_id AND bc.deleted_at IS NULL WHERE bc.booking_id = $1 AND bcu.deleted_at IS NULL @@ -109,7 +109,7 @@ export class ContainerReceiptService { const [{ batches }]: Array<{ batches: string }> = await manager.query( `SELECT COUNT(DISTINCT bcu.grn_number) AS batches FROM freight.booking_container_units bcu - JOIN freight.booking_containers bc + JOIN freight.booking_container bc ON bc.id = bcu.booking_container_id AND bc.deleted_at IS NULL WHERE bc.booking_id = $1 AND bcu.grn_number IS NOT NULL AND bcu.deleted_at IS NULL`, [bookingId], @@ -129,7 +129,7 @@ export class ContainerReceiptService { const [{ remaining }]: Array<{ remaining: string }> = await manager.query( `SELECT COUNT(*) AS remaining FROM freight.booking_container_units bcu - JOIN freight.booking_containers bc + JOIN freight.booking_container bc ON bc.id = bcu.booking_container_id AND bc.deleted_at IS NULL WHERE bc.booking_id = $1 AND bcu.deleted_at IS NULL AND bcu.grn_number IS NULL`, [bookingId], diff --git a/apps/edr-freight-api/src/modules/bookings/customer-truck.service.ts b/apps/edr-freight-api/src/modules/bookings/customer-truck.service.ts index 4274352fb..6f0ec6f55 100644 --- a/apps/edr-freight-api/src/modules/bookings/customer-truck.service.ts +++ b/apps/edr-freight-api/src/modules/bookings/customer-truck.service.ts @@ -269,7 +269,7 @@ export class CustomerTruckService { const [row]: Array<{ kg: string }> = await this.dataSource.query( `SELECT COALESCE(SUM(bcu.vgm_tons), 0) * 1000 AS kg FROM freight.booking_container_units bcu - JOIN freight.booking_containers bc + JOIN freight.booking_container bc ON bc.id = bcu.booking_container_id AND bc.deleted_at IS NULL WHERE bc.booking_id = $1 AND bcu.container_number = ANY($2::varchar[]) @@ -369,7 +369,7 @@ export class CustomerTruckService { const rows: Array<{ containerNumber: string }> = await this.dataSource.query( `SELECT bcu.container_number AS "containerNumber" FROM freight.booking_container_units bcu - JOIN freight.booking_containers bc + JOIN freight.booking_container bc ON bc.id = bcu.booking_container_id AND bc.deleted_at IS NULL WHERE bc.booking_id = $1 AND bcu.deleted_at IS NULL`, [bookingId], diff --git a/apps/edr-freight-api/src/modules/warehouses/warehouse-inventory.service.ts b/apps/edr-freight-api/src/modules/warehouses/warehouse-inventory.service.ts index 7e41e82ec..8e9457831 100644 --- a/apps/edr-freight-api/src/modules/warehouses/warehouse-inventory.service.ts +++ b/apps/edr-freight-api/src/modules/warehouses/warehouse-inventory.service.ts @@ -930,7 +930,7 @@ export class WarehouseInventoryService { SET received_to_port = true, received_at = COALESCE(bcu.received_at, NOW()), updated_at = NOW() - FROM freight.booking_containers bc + FROM freight.booking_container bc WHERE bc.id = bcu.booking_container_id AND bc.booking_id = $1 AND bc.deleted_at IS NULL @@ -1801,7 +1801,7 @@ export class WarehouseInventoryService { SET received_to_port = true, received_at = COALESCE(bcu.received_at, NOW()), updated_at = NOW() - FROM freight.booking_containers bc, freight.containers cont + FROM freight.booking_container bc, freight.containers cont WHERE bc.id = bcu.booking_container_id AND bc.booking_id = $1 AND bc.deleted_at IS NULL @@ -2248,7 +2248,7 @@ export class WarehouseInventoryService { FROM freight.customer_truck_containers cc JOIN freight.booking_container_units bcu ON bcu.container_number = cc.container_number AND bcu.deleted_at IS NULL - JOIN freight.booking_containers bc + JOIN freight.booking_container bc ON bc.id = bcu.booking_container_id AND bc.deleted_at IS NULL AND bc.booking_id = c.booking_id WHERE cc.assignment_id = a.id AND cc.deleted_at IS NULL @@ -2514,9 +2514,17 @@ export class WarehouseInventoryService { return { filename: `grn-${String(row.grnNumber).replace(/[^a-zA-Z0-9_-]+/g, '-')}.pdf`, - // Generic render — NOT the release-order fallback (would mislabel the GRN - // as a "Gate Clearance / Release Order" when Chromium is unavailable). - buffer: await this.releaseDocuments.renderDocumentHtml(html, 'Goods Received Note'), + // Styled fallback titled as a GRN (not a release order) for Chromium-less render. + buffer: await this.releaseDocuments.renderStyledDocument( + html, + { + titleLines: ['GOODS RECEIVED', 'NOTE'], + subtitle: 'OFFICIAL WAREHOUSE GOODS RECEIVED NOTE', + sectionTitle: 'RECEIVED PARTICULARS', + refLabel: 'GRN No.', + }, + 'Goods Received Note', + ), }; } @@ -2721,9 +2729,17 @@ export class WarehouseInventoryService { return { filename: `handover-${String(reference).replace(/[^a-zA-Z0-9_-]+/g, '-')}.pdf`, - // Generic render — NOT the release-order fallback (would mislabel the - // handover as a "Gate Clearance / Release Order" when Chromium is down). - buffer: await this.releaseDocuments.renderDocumentHtml(html, 'Import Goods Handover'), + // Styled fallback titled as a handover (not a release order) for Chromium-less render. + buffer: await this.releaseDocuments.renderStyledDocument( + html, + { + titleLines: ['IMPORT GOODS', 'HANDOVER', 'DOCUMENT'], + subtitle: 'EDR TO CUSTOMER WAREHOUSE HANDOVER', + sectionTitle: 'HANDOVER PARTICULARS', + refLabel: 'Document / Handover No.', + }, + 'Import Goods Handover', + ), }; } diff --git a/apps/edr-freight-api/src/modules/warehouses/warehouse-release-document.service.ts b/apps/edr-freight-api/src/modules/warehouses/warehouse-release-document.service.ts index 68e630e0b..1d8e3d9ea 100644 --- a/apps/edr-freight-api/src/modules/warehouses/warehouse-release-document.service.ts +++ b/apps/edr-freight-api/src/modules/warehouses/warehouse-release-document.service.ts @@ -32,16 +32,38 @@ export class WarehouseReleaseDocumentService { return this.pdf.htmlToPdfBuffer(html, { label }); } - private htmlToBasicPdfBuffer(html: string): Buffer { + /** + * Render document HTML with a STYLED hand-built fallback (the release layout, + * but with a custom title + section heading) for when Chromium is unavailable. + * Handover / GRN use this so their fallback looks like a proper document — + * not a plain-text dump, and not mislabelled as a release order. + */ + renderStyledDocument( + html: string, + fallbackOpts: { titleLines?: string[]; subtitle?: string; sectionTitle?: string; refLabel?: string }, + label = 'Document', + ): Promise { + return this.pdf.htmlToPdfBuffer(html, { + label, + fallback: (preparedHtml) => this.htmlToBasicPdfBuffer(preparedHtml, fallbackOpts), + }); + } + + private htmlToBasicPdfBuffer( + html: string, + opts?: { titleLines?: string[]; subtitle?: string; sectionTitle?: string; refLabel?: string }, + ): Buffer { const doc = this.extractReleaseDocument(html); + const titleLines = (opts?.titleLines ?? ['WAREHOUSE GATE', 'CLEARANCE / RELEASE', 'ORDER']).slice(0, 3); + const subtitle = opts?.subtitle ?? 'OFFICIAL WAREHOUSE RELEASE AND EXIT AUTHORIZATION'; + const sectionTitle = opts?.sectionTitle ?? 'RELEASE PARTICULARS'; + const refLabel = opts?.refLabel ?? 'Document / Release No.'; const body: string[] = [ this.lineOp(36, 810, 559, 810, '0 0 0', 2.2), this.textOp('ETHIO-DJIBOUTI RAILWAY S.C.', 36, 787, 9, 'F2', '0.08 0.32 0.18'), - this.textOp('WAREHOUSE GATE', 36, 764, 24, 'F2', '0.02 0.08 0.16'), - this.textOp('CLEARANCE / RELEASE', 36, 742, 24, 'F2', '0.02 0.08 0.16'), - this.textOp('ORDER', 36, 720, 24, 'F2', '0.02 0.08 0.16'), - this.textOp('OFFICIAL WAREHOUSE RELEASE AND EXIT AUTHORIZATION', 36, 696, 8.5, 'F1', '0.25 0.34 0.45'), - this.textOp('Document / Release No.', 424, 781, 8.5, 'F1', '0.15 0.22 0.32'), + ...titleLines.map((line, i) => this.textOp(line, 36, 764 - i * 22, 24, 'F2', '0.02 0.08 0.16')), + this.textOp(subtitle, 36, 764 - titleLines.length * 22 + 2, 8.5, 'F1', '0.25 0.34 0.45'), + this.textOp(refLabel, 424, 781, 8.5, 'F1', '0.15 0.22 0.32'), this.textOp(doc.reference, 504 - doc.reference.length * 2.2, 761, 15, 'F2', '0.02 0.08 0.16'), this.textOp(`Issued: ${doc.issuedAt}`, 424, 742, 8.5, 'F1', '0.15 0.22 0.32'), this.lineOp(36, 682, 559, 682, '0.08 0.32 0.18', 2), @@ -50,7 +72,7 @@ export class WarehouseReleaseDocumentService { ...this.wrapLines(doc.notice, 68) .slice(0, 4) .map((line, index) => this.textOp(line, 52, 659 - index * 12, 9.2, 'F1')), - this.textOp('RELEASE PARTICULARS', 36, 604, 10, 'F2', '0.08 0.32 0.18'), + this.textOp(sectionTitle, 36, 604, 10, 'F2', '0.08 0.32 0.18'), ]; let y = 586; diff --git a/apps/edr-freight-web/backoffice/src/components/warehouses/TruckDispatchModal.tsx b/apps/edr-freight-web/backoffice/src/components/warehouses/TruckDispatchModal.tsx index a1517de78..73e37c1ec 100644 --- a/apps/edr-freight-web/backoffice/src/components/warehouses/TruckDispatchModal.tsx +++ b/apps/edr-freight-web/backoffice/src/components/warehouses/TruckDispatchModal.tsx @@ -1,11 +1,12 @@ import { Alert, Badge, Button, Group, Loader, Modal, MultiSelect, Stack, Text } from '@mantine/core'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; -import { Truck } from 'lucide-react'; +import { FileText, Truck } from 'lucide-react'; import { useState } from 'react'; import { useToast } from '@/hooks/use-toast'; import { warehouseService } from '@/services/warehouse.service'; import { extractErrorMessage } from './options'; +import { openPdfBlob } from './pdf'; interface TruckDispatchModalProps { opened: boolean; @@ -50,6 +51,15 @@ export function TruckDispatchModal({ opened, onClose, bookingId, bookingReferenc onError: (e) => toast({ variant: 'destructive', title: 'Load failed', description: extractErrorMessage(e) }), }); + const openTruckExitPaper = async (assignmentId: string, plate: string) => { + try { + const res = await warehouseService.downloadTruckExitPaper(assignmentId); + openPdfBlob(res.data, `exit-${plate}.pdf`); + } catch (e) { + toast({ variant: 'destructive', title: 'Exit paper not ready', description: extractErrorMessage(e) }); + } + }; + return ( - + +