From c69dd3c2387d3cbc2fec71f8185d67e582586cfe Mon Sep 17 00:00:00 2001 From: hagiye Date: Wed, 24 Jun 2026 15:23:50 +0300 Subject: [PATCH] Release Order plus Storage fee --- .../warehouses/warehouse-fee.service.ts | 24 ++- .../warehouses/warehouse-invoice.service.ts | 6 +- .../bookings/OperationsBookingQueue.tsx | 2 +- .../warehouses/CreateWarehouseModal.tsx | 10 -- .../components/warehouses/FeePreviewModal.tsx | 13 +- .../warehouses/InspectionReportModal.tsx | 3 - .../warehouses/InventoryWorkbench.tsx | 10 +- .../warehouses/ReleaseOrderModal.tsx | 4 +- .../warehouses/WarehouseCardView.tsx | 2 +- .../components/warehouses/WarehouseTable.tsx | 140 ++++++++++++++---- .../src/components/warehouses/badges.tsx | 2 + .../warehouses/WarehouseDashboardPage.tsx | 2 +- .../pages/warehouses/WarehouseRulesPage.tsx | 31 +++- .../src/services/warehouse.service.ts | 3 + .../backoffice/src/types/warehouse.ts | 11 +- 15 files changed, 178 insertions(+), 85 deletions(-) diff --git a/apps/edr-freight-api/src/modules/warehouses/warehouse-fee.service.ts b/apps/edr-freight-api/src/modules/warehouses/warehouse-fee.service.ts index ccf66deb4..61f991d23 100644 --- a/apps/edr-freight-api/src/modules/warehouses/warehouse-fee.service.ts +++ b/apps/edr-freight-api/src/modules/warehouses/warehouse-fee.service.ts @@ -13,6 +13,8 @@ interface ItemAttributes { tradeDirection: string | null; cargoTypeCode: string | null; containerTypeCode: string | null; + inventoryQuantity: number; + bookingContainerCount: number; facilityId: string | null; warehouseId: string | null; yardId: string | null; @@ -31,6 +33,8 @@ export interface FeePreview { endIsOpen: boolean; // true when still accruing (no release/gate-clear yet) elapsedDays: number; chargeableDays: number; + containerCount: number; + billableUnits: number; amount: number; } @@ -67,6 +71,7 @@ export class WarehouseFeeService { `SELECT inv.arrived_at AS "arrivedAt", inv.gate_cleared_at AS "gateClearedAt", inv.release_date AS "releaseDate", + inv.quantity AS "inventoryQuantity", inv.warehouse_id AS "warehouseId", inv.yard_id AS "yardId", inv.zone_id AS "zoneId", @@ -74,7 +79,8 @@ export class WarehouseFeeService { b.freight_type AS "freightType", b.trade_direction AS "tradeDirection", cgt.code AS "cargoTypeCode", - ctt.code AS "containerTypeCode" + ctt.code AS "containerTypeCode", + COALESCE(container_lines.container_count, 0) AS "bookingContainerCount" FROM freight.warehouse_inventory inv LEFT JOIN freight.warehouses w ON w.id = inv.warehouse_id LEFT JOIN freight.bookings b ON b.id = inv.booking_id @@ -82,6 +88,12 @@ export class WarehouseFeeService { LEFT JOIN freight.cargo_types cgt ON cgt.id = cg.cargo_type_id LEFT JOIN freight.containers ct ON ct.id = inv.container_id LEFT JOIN freight.container_types ctt ON ctt.id = ct.container_type_id + LEFT JOIN LATERAL ( + SELECT COALESCE(SUM(bc.quantity), 0)::int AS container_count + FROM freight.booking_container bc + WHERE bc.booking_id = inv.booking_id + AND bc.deleted_at IS NULL + ) container_lines ON true WHERE inv.id = $1 AND inv.deleted_at IS NULL`, [inventoryId], ); @@ -131,12 +143,18 @@ export class WarehouseFeeService { const endIsOpen = !item.gateClearedAt && !item.releaseDate; const freeDays = rule?.freeDays ?? 0; const ratePerDay = Number(rule?.ratePerDay ?? 0); + const isContainer = (item.freightType ?? '').toUpperCase() === 'CONTAINER'; + const inventoryQuantity = Math.max(1, Math.round(Number(item.inventoryQuantity) || 1)); + const containerCount = isContainer + ? Math.max(1, Math.round(Number(item.bookingContainerCount) || inventoryQuantity)) + : 1; const elapsedDays = start ? Math.max(0, Math.ceil((new Date(endDate).getTime() - start.getTime()) / MS_PER_DAY)) : 0; const chargeableDays = Math.max(0, elapsedDays - freeDays); - const amount = Math.round(chargeableDays * ratePerDay * 100) / 100; + const billableUnits = chargeableDays * containerCount; + const amount = Math.round(billableUnits * ratePerDay * 100) / 100; return { ruleType, @@ -150,6 +168,8 @@ export class WarehouseFeeService { endIsOpen, elapsedDays, chargeableDays, + containerCount, + billableUnits, amount, }; } diff --git a/apps/edr-freight-api/src/modules/warehouses/warehouse-invoice.service.ts b/apps/edr-freight-api/src/modules/warehouses/warehouse-invoice.service.ts index d58a74b7d..abe514d80 100644 --- a/apps/edr-freight-api/src/modules/warehouses/warehouse-invoice.service.ts +++ b/apps/edr-freight-api/src/modules/warehouses/warehouse-invoice.service.ts @@ -75,9 +75,9 @@ export class WarehouseInvoiceService { feeType, description: p.ruleType === 'STORAGE_FEE' - ? `Storage fee — ${p.chargeableDays} chargeable day(s) after ${p.freeDays} free` - : `${isContainer ? 'Container' : 'Bulk'} demurrage — ${p.chargeableDays} chargeable day(s) after ${p.freeDays} free`, - quantity: p.chargeableDays, + ? `Storage fee - ${p.chargeableDays} chargeable day(s) x ${p.containerCount} container(s) after ${p.freeDays} free` + : `${isContainer ? 'Container' : 'Bulk'} demurrage - ${p.chargeableDays} chargeable day(s) x ${p.containerCount} container(s) after ${p.freeDays} free`, + quantity: p.billableUnits, unitRate: p.ratePerDay, amount: p.amount, currency: p.currency, diff --git a/apps/edr-freight-web/backoffice/src/components/bookings/OperationsBookingQueue.tsx b/apps/edr-freight-web/backoffice/src/components/bookings/OperationsBookingQueue.tsx index 3ed723be1..bb40823d8 100644 --- a/apps/edr-freight-web/backoffice/src/components/bookings/OperationsBookingQueue.tsx +++ b/apps/edr-freight-web/backoffice/src/components/bookings/OperationsBookingQueue.tsx @@ -1,7 +1,7 @@ import { useCallback, useRef } from "react"; import { useNavigate } from "react-router-dom"; import { ArrowRight, Calendar, Package, User } from "lucide-react"; -import { Group, Text } from "@mantine/core"; +import { Group } from "@mantine/core"; import { BookingActionsMenu } from "@/components/bookings/BookingActionsMenu"; import { BookingPriorityBadge } from "@/components/bookings/BookingPriorityBadge"; diff --git a/apps/edr-freight-web/backoffice/src/components/warehouses/CreateWarehouseModal.tsx b/apps/edr-freight-web/backoffice/src/components/warehouses/CreateWarehouseModal.tsx index 1ea58a1ea..ad8b4109d 100644 --- a/apps/edr-freight-web/backoffice/src/components/warehouses/CreateWarehouseModal.tsx +++ b/apps/edr-freight-web/backoffice/src/components/warehouses/CreateWarehouseModal.tsx @@ -160,16 +160,6 @@ export function CreateWarehouseModal({ opened, onClose, warehouse }: CreateWareh )} - setForm((f) => ({ ...f, cargoTypeCode: e.currentTarget.value }))} + onChange={(e) => { + const value = e.currentTarget.value; + setForm((f) => ({ ...f, cargoTypeCode: value })); + }} /> diff --git a/apps/edr-freight-web/backoffice/src/services/warehouse.service.ts b/apps/edr-freight-web/backoffice/src/services/warehouse.service.ts index dec5409e6..f3ffb134d 100644 --- a/apps/edr-freight-web/backoffice/src/services/warehouse.service.ts +++ b/apps/edr-freight-web/backoffice/src/services/warehouse.service.ts @@ -48,6 +48,7 @@ import type { Warehouse, WarehouseActivityLog, WarehouseDashboard, + WarehouseFacility, WarehouseFilter, WarehouseInventoryItem, WarehouseLoading, @@ -67,6 +68,8 @@ export const warehouseService = { params: cleanParams(filter ?? {}), }), dashboard: () => apiClient.get(URL_CONSTANTS.WAREHOUSES.DASHBOARD), + getDashboardSummary: (_filter?: InventoryFilter) => + apiClient.get(URL_CONSTANTS.WAREHOUSES.DASHBOARD), getById: (id: string) => apiClient.get(URL_CONSTANTS.WAREHOUSES.BY_ID(id)), create: (payload: SaveWarehousePayload) => apiClient.post(URL_CONSTANTS.WAREHOUSES.BASE, payload), diff --git a/apps/edr-freight-web/backoffice/src/types/warehouse.ts b/apps/edr-freight-web/backoffice/src/types/warehouse.ts index 947ce8764..274b2b3de 100644 --- a/apps/edr-freight-web/backoffice/src/types/warehouse.ts +++ b/apps/edr-freight-web/backoffice/src/types/warehouse.ts @@ -151,13 +151,14 @@ export interface Facility { isActive?: boolean; } +export type WarehouseFacility = Facility; + export interface Warehouse { id: string; name: string; code: string; type: WarehouseType; stationId: string | null; - facility?: WarehouseFacility | null; facilityId: string | null; facility?: Facility | null; locationName: string | null; @@ -202,12 +203,6 @@ export interface WarehouseInventoryItem { releaseOrderReference: string | null; deliveredAt: string | null; notes: string | null; - booking?: { - id: string; - reference: string; - status: string; - paymentStatus: string; - } | null; warehouse?: Warehouse | null; yard?: WarehouseYard | null; zone?: WarehouseZone | null; @@ -625,6 +620,8 @@ export interface FeePreview { endIsOpen: boolean; elapsedDays: number; chargeableDays: number; + containerCount: number; + billableUnits: number; amount: number; }