mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-07 22:25:42 +00:00
Truck assiggnment and per truc
This commit is contained in:
@@ -58,6 +58,7 @@ import { QUERY_KEYS } from '@/constants/QUERY_KEYS';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { useAllWarehouseYards, useAllWarehouseZones, useInventoryInquiry } from '@/hooks/useWarehouses';
|
||||
import { firstMileService } from '@/services/first-mile.service';
|
||||
import { bookingsService } from '@/services/bookings.service';
|
||||
import { warehouseService } from '@/services/warehouse.service';
|
||||
import type {
|
||||
EligibleBooking,
|
||||
@@ -858,6 +859,8 @@ function EligibleTab({
|
||||
const [truckForm, setTruckForm] = useState<TruckEntranceFormState>(emptyTruckEntrance());
|
||||
const [lockedTruckFields, setLockedTruckFields] = useState<LockedTruckEntranceFields>({});
|
||||
const [packagingFreightType, setPackagingFreightType] = useState<PackagingFreightType>('MIXED');
|
||||
const [selectedCustomerTruckId, setSelectedCustomerTruckId] = useState<string | null>(null);
|
||||
const [selectedContainerNumbers, setSelectedContainerNumbers] = useState<string[]>([]);
|
||||
|
||||
const locationReady = Boolean(location.warehouseId && location.yardId && location.zoneId);
|
||||
const canReceiveBooking = (row: EligibleBooking) =>
|
||||
@@ -944,6 +947,105 @@ function EligibleTab({
|
||||
const pendingHasFirstMile = pendingReceiveRows.some((row) => row.hasFirstMile);
|
||||
const pendingUsesFirstMile =
|
||||
pendingReceiveRows.length > 0 && pendingReceiveRows.every((row) => row.hasFirstMile);
|
||||
const pendingContainerBooking =
|
||||
pendingReceiveRows.length === 1 && pendingReceiveRows[0]?.freightType === 'CONTAINER'
|
||||
? pendingReceiveRows[0]
|
||||
: null;
|
||||
const { data: assignedCustomerTrucks = [] } = useQuery({
|
||||
queryKey: ['receive-customer-trucks', pendingContainerBooking?.id],
|
||||
queryFn: () => warehouseService.getCustomerTrucks(pendingContainerBooking?.id as string),
|
||||
enabled: truckOpen && Boolean(pendingContainerBooking) && !pendingUsesFirstMile,
|
||||
});
|
||||
const pendingContainerUnits = (pendingContainerBooking?.containerUnits ?? []).filter(
|
||||
(unit) => !unit.received,
|
||||
);
|
||||
const selectedCustomerTruck = assignedCustomerTrucks.find(
|
||||
(truck) => truck.id === selectedCustomerTruckId,
|
||||
);
|
||||
const assignedNumbersForSelectedTruck = new Set(
|
||||
(selectedCustomerTruck?.containers ?? []).map((container) => container.containerNumber.toUpperCase()),
|
||||
);
|
||||
const selectableContainerUnits = pendingContainerUnits.filter(
|
||||
(unit) =>
|
||||
assignedNumbersForSelectedTruck.size === 0 ||
|
||||
assignedNumbersForSelectedTruck.has(unit.containerNumber.toUpperCase()),
|
||||
);
|
||||
const selectedContainerUnits = pendingContainerUnits.filter((unit) =>
|
||||
selectedContainerNumbers.includes(unit.containerNumber),
|
||||
);
|
||||
const selectedContainerWeight = selectedContainerUnits.reduce(
|
||||
(total, unit) => total + Number(unit.weightTons || 0),
|
||||
0,
|
||||
);
|
||||
const containerCapacityError =
|
||||
selectedContainerNumbers.length > 2
|
||||
? 'A truck carries no more than 2 containers.'
|
||||
: selectedContainerNumbers.length > 1 &&
|
||||
selectedContainerUnits.some((unit) => !String(unit.containerSize ?? '').includes('20'))
|
||||
? 'Truck capacity is either 1 x 40ft container or up to 2 x 20ft containers.'
|
||||
: null;
|
||||
|
||||
useEffect(() => {
|
||||
if (!truckOpen || pendingUsesFirstMile || !pendingContainerBooking) return;
|
||||
if (selectedCustomerTruckId || assignedCustomerTrucks.length === 0) return;
|
||||
const pendingNumbers = new Set(
|
||||
pendingContainerUnits.map((unit) => unit.containerNumber.toUpperCase()),
|
||||
);
|
||||
const truck =
|
||||
assignedCustomerTrucks.find(
|
||||
(candidate) =>
|
||||
!candidate.arrivedAt &&
|
||||
(candidate.containers ?? []).some((container) =>
|
||||
pendingNumbers.has(container.containerNumber.toUpperCase()),
|
||||
),
|
||||
) ?? assignedCustomerTrucks[0];
|
||||
const truckContainers = (truck.containers ?? [])
|
||||
.map((container) => container.containerNumber.toUpperCase())
|
||||
.filter((number) => pendingNumbers.has(number));
|
||||
setSelectedCustomerTruckId(truck.id);
|
||||
setSelectedContainerNumbers(truckContainers);
|
||||
setTruckForm((current) => ({
|
||||
...current,
|
||||
truckPlateNumber: truck.plateNumber,
|
||||
driverName: truck.driverName,
|
||||
truckType: truck.truckType,
|
||||
assignedEquipmentNumber: truckContainers.join(', '),
|
||||
unitCount: truckContainers.length,
|
||||
netWeightKg: pendingContainerUnits
|
||||
.filter((unit) => truckContainers.includes(unit.containerNumber.toUpperCase()))
|
||||
.reduce((total, unit) => total + Number(unit.weightTons || 0), 0),
|
||||
}));
|
||||
setLockedTruckFields((current) => ({
|
||||
...current,
|
||||
truckPlateNumber: true,
|
||||
driverName: true,
|
||||
truckType: true,
|
||||
assignedEquipmentNumber: true,
|
||||
unitCount: true,
|
||||
}));
|
||||
}, [
|
||||
assignedCustomerTrucks,
|
||||
pendingContainerBooking,
|
||||
pendingContainerUnits,
|
||||
pendingUsesFirstMile,
|
||||
selectedCustomerTruckId,
|
||||
truckOpen,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!truckOpen || !pendingContainerBooking) return;
|
||||
setTruckForm((current) => ({
|
||||
...current,
|
||||
assignedEquipmentNumber: selectedContainerNumbers.join(', '),
|
||||
unitCount: selectedContainerNumbers.length,
|
||||
netWeightKg: selectedContainerWeight,
|
||||
}));
|
||||
}, [
|
||||
pendingContainerBooking,
|
||||
selectedContainerNumbers,
|
||||
selectedContainerWeight,
|
||||
truckOpen,
|
||||
]);
|
||||
|
||||
const toggleAll = () =>
|
||||
setSelected(allSelected ? new Set() : new Set(selectableRows.map((r) => r.id)));
|
||||
@@ -954,29 +1056,60 @@ function EligibleTab({
|
||||
return next;
|
||||
});
|
||||
|
||||
const receiveBookings = async (bookingIds: string[], truckEntrance?: TruckEntrancePayload) => {
|
||||
const receiveBookings = async (
|
||||
bookingIds: string[],
|
||||
truckEntrance?: TruckEntrancePayload,
|
||||
containerNumbers?: string[],
|
||||
) => {
|
||||
const documentBookingId = direction === 'EXPORT' && bookingIds.length === 1 ? bookingIds[0] : null;
|
||||
const grnWindow = documentBookingId ? window.open('', '_blank') : null;
|
||||
const acceptanceWindow = documentBookingId ? window.open('', '_blank') : null;
|
||||
try {
|
||||
const r = await bulkReceive.mutateAsync({
|
||||
direction,
|
||||
...location,
|
||||
bookingIds,
|
||||
...(containerNumbers?.length ? { containerNumbers } : {}),
|
||||
...(truckEntrance ? { truckEntrance } : {}),
|
||||
});
|
||||
const receivedProgress = r.results.find(
|
||||
(item) => item.receivedContainers != null && item.remainingContainers != null,
|
||||
);
|
||||
toast({
|
||||
title: `${r.receivedCount} received at ${direction === 'EXPORT' ? 'facility' : 'warehouse'}`,
|
||||
description: r.results.find((item) => item.grnNumber)?.grnNumber ?? skippedSummary(r.skippedCount, r.results),
|
||||
description: receivedProgress
|
||||
? `${containerNumbers?.length ?? 0} container(s) arrived. ${receivedProgress.remainingContainers} container(s) left. GRN ${receivedProgress.grnNumber}.`
|
||||
: r.results.find((item) => item.grnNumber)?.grnNumber ?? skippedSummary(r.skippedCount, r.results),
|
||||
});
|
||||
const firstGrn = r.results.find((item) => item.inventoryId && item.grnNumber);
|
||||
if (focusedBookingId && firstGrn?.inventoryId && firstGrn.grnNumber) {
|
||||
const pdfWindow = window.open('', '_blank');
|
||||
if (documentBookingId && firstGrn?.inventoryId && firstGrn.grnNumber) {
|
||||
try {
|
||||
const response = await warehouseService.downloadGrnDocument(firstGrn.inventoryId);
|
||||
const opened = openPdfBlob(response.data, `grn-${firstGrn.grnNumber}.pdf`, pdfWindow);
|
||||
const opened = openPdfBlob(response.data, `grn-${firstGrn.grnNumber}.pdf`, grnWindow);
|
||||
toast({ title: opened ? 'GRN document opened' : 'GRN document downloaded' });
|
||||
} catch (error) {
|
||||
pdfWindow?.close();
|
||||
grnWindow?.close();
|
||||
toast({ variant: 'destructive', title: 'GRN document failed', description: await extractDownloadErrorMessage(error) });
|
||||
}
|
||||
try {
|
||||
const acceptance = await bookingsService.downloadCarriageAcceptanceSheet(documentBookingId);
|
||||
const opened = openPdfBlob(
|
||||
acceptance,
|
||||
`carriage-acceptance-${pendingReceiveRows[0]?.reference ?? documentBookingId}.pdf`,
|
||||
acceptanceWindow,
|
||||
);
|
||||
toast({ title: opened ? 'Carriage acceptance sheet opened' : 'Carriage acceptance sheet downloaded' });
|
||||
} catch (error) {
|
||||
acceptanceWindow?.close();
|
||||
toast({
|
||||
variant: 'destructive',
|
||||
title: 'Carriage acceptance sheet failed',
|
||||
description: await extractDownloadErrorMessage(error),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
grnWindow?.close();
|
||||
acceptanceWindow?.close();
|
||||
}
|
||||
setSelected(new Set());
|
||||
setTruckOpen(false);
|
||||
@@ -984,8 +1117,12 @@ function EligibleTab({
|
||||
setReceivedAt(null);
|
||||
setLockedTruckFields({});
|
||||
setPackagingFreightType('MIXED');
|
||||
setSelectedCustomerTruckId(null);
|
||||
setSelectedContainerNumbers([]);
|
||||
onChanged?.();
|
||||
} catch (error) {
|
||||
grnWindow?.close();
|
||||
acceptanceWindow?.close();
|
||||
toast({ variant: 'destructive', title: 'Receive failed', description: extractErrorMessage(error) });
|
||||
}
|
||||
};
|
||||
@@ -1012,6 +1149,18 @@ function EligibleTab({
|
||||
void receiveBookings(filteredIds);
|
||||
return;
|
||||
}
|
||||
if (
|
||||
direction === 'EXPORT' &&
|
||||
selectedRows.some((row) => row.freightType === 'CONTAINER') &&
|
||||
selectedRows.length !== 1
|
||||
) {
|
||||
toast({
|
||||
variant: 'destructive',
|
||||
title: 'Receive one container booking per truck',
|
||||
description: 'Select the arriving truck and its 1 x 40ft or up to 2 x 20ft containers.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
const hasFirstMileRows = selectedRows.some((row) => row.hasFirstMile);
|
||||
const hasCustomerTruckRows = selectedRows.some((row) => !row.hasFirstMile);
|
||||
if (hasFirstMileRows && hasCustomerTruckRows) {
|
||||
@@ -1041,6 +1190,8 @@ function EligibleTab({
|
||||
...form,
|
||||
};
|
||||
setPendingReceiveIds(filteredIds);
|
||||
setSelectedCustomerTruckId(null);
|
||||
setSelectedContainerNumbers([]);
|
||||
setReceivedAt(new Date().toISOString());
|
||||
setTruckForm(normalizedForm);
|
||||
setLockedTruckFields({
|
||||
@@ -1073,7 +1224,70 @@ function EligibleTab({
|
||||
toast({ variant: 'destructive', title: 'Gross weight and exit tare weight are required when weighing is Yes' });
|
||||
return;
|
||||
}
|
||||
await receiveBookings(pendingReceiveIds, toTruckEntrancePayload(truckForm));
|
||||
if (pendingContainerBooking && selectedContainerNumbers.length === 0) {
|
||||
toast({ variant: 'destructive', title: 'Select the containers arriving on this truck' });
|
||||
return;
|
||||
}
|
||||
if (containerCapacityError) {
|
||||
toast({ variant: 'destructive', title: 'Truck capacity exceeded', description: containerCapacityError });
|
||||
return;
|
||||
}
|
||||
await receiveBookings(
|
||||
pendingReceiveIds,
|
||||
toTruckEntrancePayload({
|
||||
...truckForm,
|
||||
...(pendingContainerBooking
|
||||
? {
|
||||
assignedEquipmentNumber: selectedContainerNumbers.join(', '),
|
||||
unitCount: selectedContainerNumbers.length,
|
||||
netWeightKg: selectedContainerWeight,
|
||||
}
|
||||
: {}),
|
||||
}),
|
||||
pendingContainerBooking ? selectedContainerNumbers : undefined,
|
||||
);
|
||||
};
|
||||
|
||||
const chooseCustomerTruck = (truckId: string | null) => {
|
||||
setSelectedCustomerTruckId(truckId);
|
||||
const truck = assignedCustomerTrucks.find((candidate) => candidate.id === truckId);
|
||||
if (!truck) {
|
||||
setSelectedContainerNumbers([]);
|
||||
setLockedTruckFields((current) => ({
|
||||
...current,
|
||||
truckPlateNumber: false,
|
||||
driverName: false,
|
||||
truckType: false,
|
||||
}));
|
||||
return;
|
||||
}
|
||||
const pendingNumbers = new Set(
|
||||
pendingContainerUnits.map((unit) => unit.containerNumber.toUpperCase()),
|
||||
);
|
||||
const containers = (truck.containers ?? [])
|
||||
.map((container) => container.containerNumber.toUpperCase())
|
||||
.filter((number) => pendingNumbers.has(number));
|
||||
const weight = pendingContainerUnits
|
||||
.filter((unit) => containers.includes(unit.containerNumber.toUpperCase()))
|
||||
.reduce((total, unit) => total + Number(unit.weightTons || 0), 0);
|
||||
setSelectedContainerNumbers(containers);
|
||||
setTruckForm((current) => ({
|
||||
...current,
|
||||
truckPlateNumber: truck.plateNumber,
|
||||
driverName: truck.driverName,
|
||||
truckType: truck.truckType,
|
||||
assignedEquipmentNumber: containers.join(', '),
|
||||
unitCount: containers.length,
|
||||
netWeightKg: weight,
|
||||
}));
|
||||
setLockedTruckFields((current) => ({
|
||||
...current,
|
||||
truckPlateNumber: true,
|
||||
driverName: true,
|
||||
truckType: true,
|
||||
assignedEquipmentNumber: true,
|
||||
unitCount: true,
|
||||
}));
|
||||
};
|
||||
|
||||
|
||||
@@ -1296,6 +1510,76 @@ function EligibleTab({
|
||||
: 'Register the customer or third-party truck and driver before export receiving and GRN.'}
|
||||
</Text>
|
||||
</Alert>
|
||||
{pendingContainerBooking && (
|
||||
<Stack gap="sm">
|
||||
<Alert icon={<PackageCheck size={16} />} color="teal" variant="light">
|
||||
<Group gap="xs">
|
||||
<Text size="sm" fw={600}>
|
||||
{pendingContainerBooking.receivedContainerCount + selectedContainerNumbers.length} containers arrived
|
||||
</Text>
|
||||
<Text size="sm">
|
||||
· {Math.max(
|
||||
0,
|
||||
pendingContainerBooking.remainingContainerCount - selectedContainerNumbers.length,
|
||||
)} left after this receipt
|
||||
</Text>
|
||||
<Badge variant="light" color="blue">
|
||||
This truck: {selectedContainerNumbers.length}
|
||||
</Badge>
|
||||
</Group>
|
||||
</Alert>
|
||||
{assignedCustomerTrucks.length > 0 && !pendingUsesFirstMile && (
|
||||
<Select
|
||||
label="Arriving assigned truck"
|
||||
description="Choose the physical truck at the gate; its assigned containers are selected below."
|
||||
placeholder="Select truck"
|
||||
data={assignedCustomerTrucks.map((truck) => ({
|
||||
value: truck.id,
|
||||
label: `${truck.plateNumber} · ${truck.driverName} · ${(truck.containers ?? [])
|
||||
.map((container) => container.containerNumber)
|
||||
.join(', ') || 'no containers'}`,
|
||||
}))}
|
||||
value={selectedCustomerTruckId}
|
||||
onChange={chooseCustomerTruck}
|
||||
searchable
|
||||
required
|
||||
/>
|
||||
)}
|
||||
<MultiSelect
|
||||
label="Containers arriving on this truck"
|
||||
description="Required: select either 1 x 40ft container or up to 2 x 20ft containers."
|
||||
placeholder="Select the containers physically arriving"
|
||||
data={selectableContainerUnits.map((unit) => {
|
||||
const selected = selectedContainerNumbers.includes(unit.containerNumber);
|
||||
const selectedHasNon20 = selectedContainerUnits.some(
|
||||
(selectedUnit) => !String(selectedUnit.containerSize ?? '').includes('20'),
|
||||
);
|
||||
const candidateIs20 = String(unit.containerSize ?? '').includes('20');
|
||||
return {
|
||||
value: unit.containerNumber,
|
||||
label: `${unit.containerNumber} · ${unit.containerSize ?? 'size unknown'} · ${Number(
|
||||
unit.weightTons || 0,
|
||||
).toLocaleString()} t`,
|
||||
disabled:
|
||||
!selected &&
|
||||
(selectedContainerNumbers.length >= 2 ||
|
||||
(selectedContainerNumbers.length === 1 &&
|
||||
(selectedHasNon20 || !candidateIs20))),
|
||||
};
|
||||
})}
|
||||
value={selectedContainerNumbers}
|
||||
onChange={setSelectedContainerNumbers}
|
||||
maxValues={2}
|
||||
searchable
|
||||
required
|
||||
/>
|
||||
{containerCapacityError && (
|
||||
<Alert color="red" variant="light">
|
||||
{containerCapacityError}
|
||||
</Alert>
|
||||
)}
|
||||
</Stack>
|
||||
)}
|
||||
<Table.ScrollContainer minWidth={900}>
|
||||
<Table withTableBorder highlightOnHover verticalSpacing="xs">
|
||||
<Table.Thead>
|
||||
@@ -1354,7 +1638,9 @@ function EligibleTab({
|
||||
Cancel
|
||||
</Button>
|
||||
<Button leftSection={<Truck size={16} />} loading={bulkReceive.isPending} onClick={receive}>
|
||||
Register Arrival & Generate GRN
|
||||
{pendingContainerBooking
|
||||
? 'Receive Selected Containers & Generate CAS + GRN'
|
||||
: 'Register Arrival & Generate GRN'}
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
|
||||
@@ -387,6 +387,9 @@ export function ReleaseOrderModal({ opened, onClose, item, truckPrefill }: Relea
|
||||
const containerWeightByNumber = new Map(
|
||||
containerWeights.map((c) => [c.containerNumber.toUpperCase(), Number(c.weightTons) || 0]),
|
||||
);
|
||||
const containerSizeByNumber = new Map(
|
||||
containerWeights.map((c) => [c.containerNumber.toUpperCase(), c.containerSize ?? '']),
|
||||
);
|
||||
// A truck may only carry out its OWN assigned containers — when the selected
|
||||
// truck has an assigned load, other trucks' containers are not offered.
|
||||
const assignedLoad = (selectedOption?.containerNumbers ?? []).map((n) => n.toUpperCase());
|
||||
@@ -398,7 +401,7 @@ export function ReleaseOrderModal({ opened, onClose, item, truckPrefill }: Relea
|
||||
c.containerNumber,
|
||||
{
|
||||
value: c.containerNumber,
|
||||
label: `${c.containerNumber} · ${(Number(c.weightTons) || 0).toLocaleString()} t`,
|
||||
label: `${c.containerNumber} · ${c.containerSize ?? 'size unknown'} · ${(Number(c.weightTons) || 0).toLocaleString()} t`,
|
||||
},
|
||||
]),
|
||||
).values(),
|
||||
@@ -409,6 +412,15 @@ export function ReleaseOrderModal({ opened, onClose, item, truckPrefill }: Relea
|
||||
containerNumbers.some((n) => n.trim().toUpperCase() === option.value.toUpperCase()),
|
||||
);
|
||||
const selectedContainerNumbers = containerNumbers.map((n) => n.trim()).filter(Boolean);
|
||||
const selectedContainerSizes = selectedContainerNumbers.map(
|
||||
(number) => containerSizeByNumber.get(number.toUpperCase()) ?? '',
|
||||
);
|
||||
const containerCapacityError =
|
||||
selectedContainerNumbers.length > 2
|
||||
? 'A truck carries no more than 2 containers.'
|
||||
: selectedContainerNumbers.length > 1 && selectedContainerSizes.some((size) => !size.includes('20'))
|
||||
? 'Truck capacity is either 1 x 40ft container or up to 2 x 20ft containers.'
|
||||
: null;
|
||||
const selectedCargoWeight = Number(
|
||||
selectedContainerNumbers
|
||||
.reduce((sum, n) => sum + (containerWeightByNumber.get(n.toUpperCase()) ?? 0), 0)
|
||||
@@ -470,6 +482,10 @@ export function ReleaseOrderModal({ opened, onClose, item, truckPrefill }: Relea
|
||||
toast({ variant: 'destructive', title: 'Select the containers loaded on this truck' });
|
||||
return;
|
||||
}
|
||||
if (isExitStep && containerCapacityError) {
|
||||
toast({ variant: 'destructive', title: 'Truck capacity exceeded', description: containerCapacityError });
|
||||
return;
|
||||
}
|
||||
if (isExitStep && !skipWeighing && systemNetWeight === '') {
|
||||
toast({ variant: 'destructive', title: 'System recorded net weight is missing' });
|
||||
return;
|
||||
@@ -636,14 +652,15 @@ export function ReleaseOrderModal({ opened, onClose, item, truckPrefill }: Relea
|
||||
label="Containers on this truck"
|
||||
description={
|
||||
isExitStep
|
||||
? 'Select the containers loaded on this truck — their cargo weight must match gross − tare.'
|
||||
: 'Containers this truck will carry.'
|
||||
? 'Select what is leaving: 1 x 40ft or up to 2 x 20ft. Their cargo weight must match gross - tare.'
|
||||
: 'Truck capacity: 1 x 40ft container or up to 2 x 20ft containers.'
|
||||
}
|
||||
placeholder="Select containers"
|
||||
searchable
|
||||
data={containerSelectData}
|
||||
value={selectedContainerNumbers}
|
||||
onChange={(values) => setContainerNumbers(values.length ? values : [''])}
|
||||
maxValues={2}
|
||||
disabled={hasTruckLeft}
|
||||
/>
|
||||
) : (
|
||||
@@ -708,6 +725,11 @@ export function ReleaseOrderModal({ opened, onClose, item, truckPrefill }: Relea
|
||||
</Text>
|
||||
</Alert>
|
||||
)}
|
||||
{containerCapacityError && (
|
||||
<Alert icon={<Info size={16} />} color="red" variant="light">
|
||||
<Text size="sm">{containerCapacityError}</Text>
|
||||
</Alert>
|
||||
)}
|
||||
<Group justify="flex-end" mt="sm">
|
||||
<Button variant="default" onClick={onClose} disabled={releaseMutation.isPending || downloading}>
|
||||
Cancel
|
||||
|
||||
@@ -206,7 +206,7 @@ export const warehouseService = {
|
||||
/** A booking's containers with VGM cargo weight (tonnes) for exit weighing. */
|
||||
getContainerWeights: async (
|
||||
bookingId: string,
|
||||
): Promise<Array<{ containerNumber: string; weightTons: number }>> => {
|
||||
): Promise<Array<{ containerNumber: string; weightTons: number; containerSize: string | null }>> => {
|
||||
const { data } = await apiClient.get(
|
||||
`/warehouse-inventory/bookings/${bookingId}/container-weights`,
|
||||
);
|
||||
|
||||
@@ -575,6 +575,15 @@ export interface EligibleBooking {
|
||||
customerTruckType: string | null;
|
||||
customerTruckContainerNumber: string | null;
|
||||
customerTruckAssignedAt: string | null;
|
||||
containerUnits: Array<{
|
||||
containerNumber: string;
|
||||
containerSize: string | null;
|
||||
weightTons: number;
|
||||
received: boolean;
|
||||
grnNumber: string | null;
|
||||
}>;
|
||||
receivedContainerCount: number;
|
||||
remainingContainerCount: number;
|
||||
}
|
||||
|
||||
export interface BulkReceivePayload {
|
||||
@@ -583,13 +592,23 @@ export interface BulkReceivePayload {
|
||||
yardId: string;
|
||||
zoneId: string;
|
||||
bookingIds: string[];
|
||||
containerNumbers?: string[];
|
||||
truckEntrance?: TruckEntrancePayload;
|
||||
}
|
||||
|
||||
export interface BulkReceiveResult {
|
||||
receivedCount: number;
|
||||
skippedCount: number;
|
||||
results: { bookingId: string; status: string; inventoryId?: string; grnNumber?: string; reason?: string }[];
|
||||
results: {
|
||||
bookingId: string;
|
||||
status: string;
|
||||
inventoryId?: string;
|
||||
inventoryIds?: string[];
|
||||
grnNumber?: string;
|
||||
receivedContainers?: number;
|
||||
remainingContainers?: number;
|
||||
reason?: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface TruckEntrancePayload {
|
||||
|
||||
Reference in New Issue
Block a user