mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-27 00:52:50 +00:00
Release Order plus Storage fee
This commit is contained in:
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -160,16 +160,6 @@ export function CreateWarehouseModal({ opened, onClose, warehouse }: CreateWareh
|
||||
)}
|
||||
</Group>
|
||||
|
||||
<Select
|
||||
label="Facility / Port"
|
||||
placeholder="Select facility"
|
||||
clearable
|
||||
searchable
|
||||
data={facilityOptions}
|
||||
value={form.stationId || null}
|
||||
onChange={(value) => setForm((f) => ({ ...f, stationId: value ?? '' }))}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
label="Location name"
|
||||
placeholder="Modjo, Oromia"
|
||||
|
||||
@@ -5,15 +5,9 @@ import { useMutation, useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '@/services/api';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import {
|
||||
useFeePreview,
|
||||
useGateClearance,
|
||||
useGenerateInvoice,
|
||||
useInvoicesForInventory,
|
||||
} from '@/hooks/useWarehouses';
|
||||
import { warehouseService } from '@/services/warehouse.service';
|
||||
import { extractErrorMessage } from './options';
|
||||
import type { FeePreview, WarehouseInvoiceStatus } from '@/types/warehouse';
|
||||
import type { FeePreview, WarehouseInventoryItem, WarehouseInvoiceStatus } from '@/types/warehouse';
|
||||
import { openPdfBlob } from './pdf';
|
||||
|
||||
const INVOICE_STATUS_COLOR: Record<WarehouseInvoiceStatus, string> = {
|
||||
@@ -75,6 +69,8 @@ function FeeCard({ fee }: { fee: FeePreview }) {
|
||||
<Row label="Period" value={`${fmtDate(fee.startDate)} → ${fmtDate(fee.endDate)}${fee.endIsOpen ? ' (today)' : ''}`} />
|
||||
<Row label="Elapsed days" value={String(fee.elapsedDays)} />
|
||||
<Row label="Chargeable days" value={`${fee.chargeableDays} (after ${fee.freeDays} free)`} />
|
||||
<Row label="Containers" value={String(fee.containerCount ?? 1)} />
|
||||
<Row label="Billable units" value={`${fee.billableUnits ?? fee.chargeableDays} container-day(s)`} />
|
||||
</Stack>
|
||||
)}
|
||||
</Card>
|
||||
@@ -134,8 +130,7 @@ export function FeePreviewModal({ opened, onClose, inventoryId }: FeePreviewModa
|
||||
if (!inventoryId) return;
|
||||
const pdfWindow = window.open('', '_blank');
|
||||
try {
|
||||
const response = await gateClear.mutateAsync(inventoryId) as { data?: { booking?: { reference?: string | null }; bookingId?: string | null } };
|
||||
const releasedItem = response.data;
|
||||
const releasedItem = await gateClear.mutateAsync(inventoryId) as WarehouseInventoryItem;
|
||||
const documentResponse = await warehouseService.downloadReleaseDocument(inventoryId);
|
||||
const filename = `release-${releasedItem?.booking?.reference ?? releasedItem?.bookingId ?? inventoryId}.pdf`;
|
||||
const opened = openPdfBlob(documentResponse.data, filename, pdfWindow);
|
||||
|
||||
@@ -12,9 +12,6 @@ import {
|
||||
} from '@mantine/core';
|
||||
import { Upload } from 'lucide-react';
|
||||
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '@/services/api';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { useCreateInspectionReport, useInspectionReports, useUploadInspectionAttachments } from '@/hooks/useWarehouses';
|
||||
import {
|
||||
|
||||
@@ -6,13 +6,6 @@ import { useMutation } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '@/services/api';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import {
|
||||
useBulkMarkInspected,
|
||||
useDispatchInventory,
|
||||
useMarkReadyForLoading,
|
||||
useMarkReadyForPickup,
|
||||
useStoreInventory,
|
||||
} from '@/hooks/useWarehouses';
|
||||
import { warehouseService } from '@/services/warehouse.service';
|
||||
import type { InventoryAction, WarehouseInventoryItem } from '@/types/warehouse';
|
||||
import { DeliverInventoryModal } from './DeliverInventoryModal';
|
||||
@@ -126,8 +119,7 @@ export function InventoryWorkbench({ items, isLoading, onLastMile }: InventoryWo
|
||||
const storeInventory = async (item: WarehouseInventoryItem) => {
|
||||
setBusyId(item.id);
|
||||
try {
|
||||
const response = (await storeMutation.mutateAsync(item.id)) as { data: WarehouseInventoryItem };
|
||||
const stored = response.data;
|
||||
const stored = await storeMutation.mutateAsync(item.id);
|
||||
toast({
|
||||
title: 'Inventory stored',
|
||||
description: [stored.warehouse?.code, stored.yard?.code, stored.zone?.code].filter(Boolean).join(' / '),
|
||||
|
||||
@@ -6,7 +6,6 @@ import { useMutation } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '@/services/api';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { useReleaseInventory } from '@/hooks/useWarehouses';
|
||||
import { warehouseService } from '@/services/warehouse.service';
|
||||
import type { WarehouseInventoryItem } from '@/types/warehouse';
|
||||
import { extractErrorMessage } from './options';
|
||||
@@ -36,11 +35,10 @@ export function ReleaseOrderModal({ opened, onClose, item }: ReleaseOrderModalPr
|
||||
id: item.id,
|
||||
payload: { reference: reference.trim() || undefined },
|
||||
});
|
||||
const releasedItem = released.data;
|
||||
setDownloading(true);
|
||||
const response = await warehouseService.downloadReleaseDocument(item.id);
|
||||
const blob = response.data;
|
||||
const filename = `release-${releasedItem.booking?.reference ?? releasedItem.bookingId ?? item.id}.pdf`;
|
||||
const filename = `release-${released.booking?.reference ?? released.bookingId ?? item.id}.pdf`;
|
||||
const opened = openPdfBlob(blob, filename, pdfWindow);
|
||||
toast({
|
||||
title: 'Release exit paper issued',
|
||||
|
||||
@@ -167,7 +167,7 @@ function CapacityRow({
|
||||
</Text>
|
||||
</Group>
|
||||
<Text size="sm" fw={700}>
|
||||
{formatCapacity(current, capacity)}
|
||||
{formatCapacity(Number(current) || 0, capacity)}
|
||||
</Text>
|
||||
</Group>
|
||||
<Progress value={percent} color={color} size="xs" radius="xl" bg="var(--mantine-color-gray-1)" />
|
||||
|
||||
@@ -1,10 +1,20 @@
|
||||
import { useMemo } from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
import { ActionIcon, Group, Text } from '@mantine/core';
|
||||
import { Eye, Pencil } from 'lucide-react';
|
||||
import {
|
||||
Building2,
|
||||
Eye,
|
||||
MapPin,
|
||||
Package,
|
||||
Pencil,
|
||||
Scale,
|
||||
Warehouse as WarehouseIcon,
|
||||
} from 'lucide-react';
|
||||
import { DataTable, type ColumnDef } from '@edr/ui-common';
|
||||
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { bookingTable } from '@/components/bookings/booking-ui.styles';
|
||||
import { api } from '@/services/api';
|
||||
import type { Warehouse } from '@/types/warehouse';
|
||||
import { WarehouseStatusBadge, WarehouseTypeBadge } from './badges';
|
||||
@@ -16,6 +26,43 @@ interface WarehouseTableProps {
|
||||
onEdit: (warehouse: Warehouse) => void;
|
||||
}
|
||||
|
||||
const HEADER = bookingTable.headerCell;
|
||||
|
||||
function CapacityCell({
|
||||
current,
|
||||
capacity,
|
||||
icon,
|
||||
}: {
|
||||
current?: number | null;
|
||||
capacity?: number | null;
|
||||
icon: ReactNode;
|
||||
}) {
|
||||
const numericCurrent = Number(current) || 0;
|
||||
const numericCapacity = Number(capacity) || 0;
|
||||
const hasCapacity = numericCapacity > 0;
|
||||
const ratio = hasCapacity ? Math.min(100, Math.max(0, (numericCurrent / numericCapacity) * 100)) : 0;
|
||||
const isOverCapacity = hasCapacity && numericCurrent > numericCapacity;
|
||||
|
||||
return (
|
||||
<div className="min-w-[8rem] space-y-2 py-1">
|
||||
<div className="flex items-center gap-2 text-sm font-medium text-foreground">
|
||||
<span className="flex size-7 shrink-0 items-center justify-center rounded-lg border border-border/50 bg-background/70 text-muted-foreground">
|
||||
{icon}
|
||||
</span>
|
||||
<span className="whitespace-nowrap">{formatCapacity(numericCurrent, capacity)}</span>
|
||||
</div>
|
||||
{hasCapacity ? (
|
||||
<div className="h-1.5 overflow-hidden rounded-full bg-muted/50">
|
||||
<div
|
||||
className={isOverCapacity ? 'h-full rounded-full bg-red-500' : 'h-full rounded-full bg-edr-green'}
|
||||
style={{ width: `${ratio}%` }}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function WarehouseTable({ warehouses, onView, onEdit }: WarehouseTableProps) {
|
||||
const { data: stations } = useQuery(
|
||||
api.stations.list.queryOptions({ staleTime: 5 * 60 * 1000 }),
|
||||
@@ -28,63 +75,98 @@ export function WarehouseTable({ warehouses, onView, onEdit }: WarehouseTablePro
|
||||
const columns: ColumnDef<Warehouse>[] = [
|
||||
{
|
||||
id: 'code',
|
||||
header: 'Code',
|
||||
header: () => <span className={HEADER}>Warehouse</span>,
|
||||
cell: ({ row }) => (
|
||||
<Text
|
||||
fw={600}
|
||||
size="sm"
|
||||
c="edr-green.7"
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => onView(row.original)}
|
||||
>
|
||||
{row.original.code}
|
||||
</Text>
|
||||
<div className="flex min-w-[11rem] items-center gap-3 py-1.5">
|
||||
<div className={bookingTable.rowIcon}>
|
||||
<WarehouseIcon className="size-4" strokeWidth={1.75} />
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<button
|
||||
type="button"
|
||||
className="block max-w-full truncate text-left text-sm font-semibold text-edr-green transition-colors hover:text-edr-green/80"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onView(row.original);
|
||||
}}
|
||||
>
|
||||
{row.original.code}
|
||||
</button>
|
||||
<p className="mt-0.5 truncate text-xs text-muted-foreground">
|
||||
{row.original.name}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{ id: 'name', header: 'Name', cell: ({ row }) => row.original.name },
|
||||
{
|
||||
id: 'facility',
|
||||
header: 'Facility',
|
||||
header: () => <span className={HEADER}>Facility</span>,
|
||||
cell: ({ row }) => {
|
||||
const name = row.original.stationId
|
||||
? stationNameById.get(row.original.stationId)
|
||||
: undefined;
|
||||
return name ? (
|
||||
<Text size="sm" fw={500}>
|
||||
{name}
|
||||
</Text>
|
||||
<div className="flex min-w-[9rem] items-center gap-2 py-1 text-sm font-medium text-foreground">
|
||||
<Building2 className="size-4 shrink-0 text-muted-foreground" />
|
||||
<span className="truncate">{name}</span>
|
||||
</div>
|
||||
) : (
|
||||
<Text size="sm" c="dimmed">
|
||||
—
|
||||
-
|
||||
</Text>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'type',
|
||||
header: 'Type',
|
||||
cell: ({ row }) => <WarehouseTypeBadge type={row.original.type} />,
|
||||
header: () => <span className={HEADER}>Type</span>,
|
||||
cell: ({ row }) => (
|
||||
<div className="py-1">
|
||||
<WarehouseTypeBadge type={row.original.type} />
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'location',
|
||||
header: 'Location',
|
||||
cell: ({ row }) => row.original.locationName ?? '—',
|
||||
header: () => <span className={HEADER}>Location</span>,
|
||||
cell: ({ row }) => (
|
||||
<div className="flex min-w-[10rem] items-center gap-2 py-1 text-sm text-foreground">
|
||||
<MapPin className="size-4 shrink-0 text-muted-foreground" />
|
||||
<span className="truncate">{row.original.locationName ?? '-'}</span>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'weight',
|
||||
header: 'Weight (cur / cap)',
|
||||
cell: ({ row }) => formatCapacity(row.original.currentWeight, row.original.capacityWeight),
|
||||
header: () => <span className={HEADER}>Weight</span>,
|
||||
cell: ({ row }) => (
|
||||
<CapacityCell
|
||||
current={row.original.currentWeight}
|
||||
capacity={row.original.capacityWeight}
|
||||
icon={<Scale className="size-3.5" />}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'containers',
|
||||
header: 'Containers (cur / cap)',
|
||||
cell: ({ row }) =>
|
||||
formatCapacity(row.original.currentContainers, row.original.capacityContainers),
|
||||
header: () => <span className={HEADER}>Containers</span>,
|
||||
cell: ({ row }) => (
|
||||
<CapacityCell
|
||||
current={row.original.currentContainers}
|
||||
capacity={row.original.capacityContainers}
|
||||
icon={<Package className="size-3.5" />}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'status',
|
||||
header: 'Status',
|
||||
cell: ({ row }) => <WarehouseStatusBadge status={row.original.status} />,
|
||||
header: () => <span className={HEADER}>Status</span>,
|
||||
cell: ({ row }) => (
|
||||
<div className="py-1">
|
||||
<WarehouseStatusBadge status={row.original.status} />
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
@@ -109,7 +191,7 @@ export function WarehouseTable({ warehouses, onView, onEdit }: WarehouseTablePro
|
||||
status="success"
|
||||
onRowClick={(warehouse) => onView(warehouse)}
|
||||
emptyMessage="No warehouses found."
|
||||
containerClassName="border-0 shadow-none"
|
||||
containerClassName="border-0 bg-transparent shadow-none"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -57,6 +57,8 @@ const inventoryStatusColor: Record<InventoryStatus, string> = {
|
||||
RECEIVED: "yellow",
|
||||
STORED: "blue",
|
||||
RESERVED: "grape",
|
||||
ARRIVED_AT_WAREHOUSE: "orange",
|
||||
UNDER_INSPECTION: "yellow",
|
||||
READY_FOR_LOADING: "cyan",
|
||||
LOADED: "teal",
|
||||
READY_FOR_PICKUP: "teal",
|
||||
|
||||
@@ -29,7 +29,7 @@ interface Metric {
|
||||
theme: string;
|
||||
}
|
||||
|
||||
const ORANGE = 'rgb(245, 227, 203)';
|
||||
const ORANGE = 'rgb(241, 147, 23)';
|
||||
const GREEN = '#084b21';
|
||||
|
||||
const METRICS: Metric[] = [
|
||||
|
||||
@@ -230,7 +230,10 @@ function AllocationRules() {
|
||||
placeholder="e.g. Import containers to open yard"
|
||||
required
|
||||
value={form.name}
|
||||
onChange={(e) => setForm((f) => ({ ...f, name: e.currentTarget.value }))}
|
||||
onChange={(e) => {
|
||||
const value = e.currentTarget.value;
|
||||
setForm((f) => ({ ...f, name: value }));
|
||||
}}
|
||||
/>
|
||||
<NumberInput
|
||||
label="Priority"
|
||||
@@ -261,13 +264,19 @@ function AllocationRules() {
|
||||
label="Cargo type code"
|
||||
placeholder="e.g. COFFEE"
|
||||
value={form.cargoTypeCode}
|
||||
onChange={(e) => setForm((f) => ({ ...f, cargoTypeCode: e.currentTarget.value }))}
|
||||
onChange={(e) => {
|
||||
const value = e.currentTarget.value;
|
||||
setForm((f) => ({ ...f, cargoTypeCode: value }));
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label="Container status"
|
||||
placeholder="e.g. MAINTENANCE"
|
||||
value={form.containerStatus}
|
||||
onChange={(e) => setForm((f) => ({ ...f, containerStatus: e.currentTarget.value }))}
|
||||
onChange={(e) => {
|
||||
const value = e.currentTarget.value;
|
||||
setForm((f) => ({ ...f, containerStatus: value }));
|
||||
}}
|
||||
/>
|
||||
</Group>
|
||||
<Group grow>
|
||||
@@ -287,7 +296,10 @@ function AllocationRules() {
|
||||
label="Storage type"
|
||||
placeholder="e.g. OPEN_STACK"
|
||||
value={form.storageType}
|
||||
onChange={(e) => setForm((f) => ({ ...f, storageType: e.currentTarget.value }))}
|
||||
onChange={(e) => {
|
||||
const value = e.currentTarget.value;
|
||||
setForm((f) => ({ ...f, storageType: value }));
|
||||
}}
|
||||
/>
|
||||
</Group>
|
||||
<Group justify="flex-end" mt="sm">
|
||||
@@ -337,7 +349,6 @@ function FeeRules() {
|
||||
freeDays: form.freeDays,
|
||||
ratePerDay: form.ratePerDay,
|
||||
currency: form.currency || 'USD',
|
||||
isActive: true,
|
||||
} as never);
|
||||
toast({ title: 'Fee rule created' });
|
||||
setOpen(false);
|
||||
@@ -417,7 +428,10 @@ function FeeRules() {
|
||||
label="Name"
|
||||
required
|
||||
value={form.name}
|
||||
onChange={(e) => setForm((f) => ({ ...f, name: e.currentTarget.value }))}
|
||||
onChange={(e) => {
|
||||
const value = e.currentTarget.value;
|
||||
setForm((f) => ({ ...f, name: value }));
|
||||
}}
|
||||
/>
|
||||
<Select
|
||||
label="Rule type"
|
||||
@@ -453,7 +467,10 @@ function FeeRules() {
|
||||
<TextInput
|
||||
label="Cargo type code"
|
||||
value={form.cargoTypeCode}
|
||||
onChange={(e) => setForm((f) => ({ ...f, cargoTypeCode: e.currentTarget.value }))}
|
||||
onChange={(e) => {
|
||||
const value = e.currentTarget.value;
|
||||
setForm((f) => ({ ...f, cargoTypeCode: value }));
|
||||
}}
|
||||
/>
|
||||
</Group>
|
||||
<Group grow>
|
||||
|
||||
@@ -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<WarehouseDashboard>(URL_CONSTANTS.WAREHOUSES.DASHBOARD),
|
||||
getDashboardSummary: (_filter?: InventoryFilter) =>
|
||||
apiClient.get<WarehouseDashboard>(URL_CONSTANTS.WAREHOUSES.DASHBOARD),
|
||||
getById: (id: string) => apiClient.get<Warehouse>(URL_CONSTANTS.WAREHOUSES.BY_ID(id)),
|
||||
create: (payload: SaveWarehousePayload) =>
|
||||
apiClient.post<Warehouse>(URL_CONSTANTS.WAREHOUSES.BASE, payload),
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user