mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 21:08:12 +00:00
frist mile receive to warehouse and last mile truck arrival integration
This commit is contained in:
@@ -76,6 +76,8 @@ interface ReceiveInventoryModalProps {
|
||||
/** When supplied the modal locks to a single booking (legacy single-receive). */
|
||||
bookingId?: string;
|
||||
bookingLabel?: string;
|
||||
mode?: 'single' | 'bulk';
|
||||
direction?: WarehouseFlowDirection;
|
||||
onReceived?: () => void;
|
||||
}
|
||||
|
||||
@@ -716,11 +718,15 @@ function EligibleTab({
|
||||
location,
|
||||
enabled,
|
||||
onChanged,
|
||||
focusedBookingId,
|
||||
focusedBookingLabel,
|
||||
}: {
|
||||
direction: 'IMPORT' | 'EXPORT';
|
||||
location: Location;
|
||||
enabled: boolean;
|
||||
onChanged?: () => void;
|
||||
focusedBookingId?: string;
|
||||
focusedBookingLabel?: string;
|
||||
}) {
|
||||
const { toast } = useToast();
|
||||
const qc = useQueryClient();
|
||||
@@ -730,7 +736,15 @@ function EligibleTab({
|
||||
enabled,
|
||||
}),
|
||||
);
|
||||
const rows = useMemo(() => allRows.filter((r) => r.direction === direction), [allRows, direction]);
|
||||
const rows = useMemo(
|
||||
() =>
|
||||
allRows.filter(
|
||||
(r) =>
|
||||
r.direction === direction &&
|
||||
(!focusedBookingId || r.id === focusedBookingId),
|
||||
),
|
||||
[allRows, direction, focusedBookingId],
|
||||
);
|
||||
const bulkReceive = useMutation(api.warehouses.bulkReceive.mutationOptions());
|
||||
const requestFirstMile = useMutation({
|
||||
mutationFn: (reference: string) => firstMileService.accept(reference),
|
||||
@@ -856,6 +870,18 @@ function EligibleTab({
|
||||
title: `${r.receivedCount} received at ${direction === 'EXPORT' ? 'facility' : 'warehouse'}`,
|
||||
description: r.results.find((item) => item.grnNumber)?.grnNumber ?? (r.skippedCount ? `${r.skippedCount} skipped` : undefined),
|
||||
});
|
||||
const firstGrn = r.results.find((item) => item.inventoryId && item.grnNumber);
|
||||
if (focusedBookingId && firstGrn?.inventoryId && firstGrn.grnNumber) {
|
||||
const pdfWindow = window.open('', '_blank');
|
||||
try {
|
||||
const response = await warehouseService.downloadGrnDocument(firstGrn.inventoryId);
|
||||
const opened = openPdfBlob(response.data, `grn-${firstGrn.grnNumber}.pdf`, pdfWindow);
|
||||
toast({ title: opened ? 'GRN document opened' : 'GRN document downloaded' });
|
||||
} catch (error) {
|
||||
pdfWindow?.close();
|
||||
toast({ variant: 'destructive', title: 'GRN document failed', description: extractErrorMessage(error) });
|
||||
}
|
||||
}
|
||||
setSelected(new Set());
|
||||
setTruckOpen(false);
|
||||
setPendingReceiveIds([]);
|
||||
@@ -1007,7 +1033,9 @@ function EligibleTab({
|
||||
</Group>
|
||||
) : statusFilteredRows.length === 0 ? (
|
||||
<Text c="dimmed" ta="center" py="lg" size="sm">
|
||||
No eligible PAID {direction.toLowerCase()} bookings to receive.
|
||||
{focusedBookingLabel
|
||||
? `${focusedBookingLabel} is not eligible for warehouse receiving yet.`
|
||||
: `No eligible PAID ${direction.toLowerCase()} bookings to receive.`}
|
||||
</Text>
|
||||
) : (
|
||||
<Table.ScrollContainer minWidth={1700}>
|
||||
@@ -2369,6 +2397,8 @@ interface WarehouseFlowWorkbenchProps {
|
||||
direction?: WarehouseFlowDirection;
|
||||
enabled?: boolean;
|
||||
onChanged?: () => void;
|
||||
focusedBookingId?: string;
|
||||
focusedBookingLabel?: string;
|
||||
}
|
||||
|
||||
function WarehouseQueueTabs<TValue extends string>({
|
||||
@@ -2588,10 +2618,14 @@ function ExportWarehouseTabs({
|
||||
enabled,
|
||||
location,
|
||||
onChanged,
|
||||
focusedBookingId,
|
||||
focusedBookingLabel,
|
||||
}: {
|
||||
enabled: boolean;
|
||||
location: Location;
|
||||
onChanged?: () => void;
|
||||
focusedBookingId?: string;
|
||||
focusedBookingLabel?: string;
|
||||
}) {
|
||||
const [activeTab, setActiveTab] = useState<ExportWarehouseTab>('receive-queue');
|
||||
const { data: eligibleRows = [] } = useQuery(
|
||||
@@ -2650,7 +2684,14 @@ function ExportWarehouseTabs({
|
||||
<WarehouseQueueTabs value={activeTab} onChange={setActiveTab} tabs={tabs} />
|
||||
|
||||
{activeTab === 'receive-queue' && (
|
||||
<EligibleTab direction="EXPORT" location={location} enabled={enabled} onChanged={onChanged} />
|
||||
<EligibleTab
|
||||
direction="EXPORT"
|
||||
location={location}
|
||||
enabled={enabled}
|
||||
onChanged={onChanged}
|
||||
focusedBookingId={focusedBookingId}
|
||||
focusedBookingLabel={focusedBookingLabel}
|
||||
/>
|
||||
)}
|
||||
{activeTab === 'received' && (
|
||||
<ExportReceivedTab enabled={enabled} onChanged={onChanged} />
|
||||
@@ -2675,6 +2716,8 @@ export function WarehouseFlowWorkbench({
|
||||
direction = 'BOTH',
|
||||
enabled = true,
|
||||
onChanged,
|
||||
focusedBookingId,
|
||||
focusedBookingLabel,
|
||||
}: WarehouseFlowWorkbenchProps) {
|
||||
const [location, setLocation] = useState<Location>({ warehouseId: '', yardId: '', zoneId: '' });
|
||||
const [tab, setTab] = useState<Exclude<WarehouseFlowDirection, 'BOTH'>>(
|
||||
@@ -2707,24 +2750,42 @@ export function WarehouseFlowWorkbench({
|
||||
<ImportWarehouseTabs enabled={enabled} onChanged={onChanged} />
|
||||
</Tabs.Panel>
|
||||
<Tabs.Panel value="EXPORT">
|
||||
<ExportWarehouseTabs enabled={enabled} location={location} onChanged={onChanged} />
|
||||
<ExportWarehouseTabs
|
||||
enabled={enabled}
|
||||
location={location}
|
||||
onChanged={onChanged}
|
||||
focusedBookingId={focusedBookingId}
|
||||
focusedBookingLabel={focusedBookingLabel}
|
||||
/>
|
||||
</Tabs.Panel>
|
||||
</Tabs>
|
||||
) : activeDirection === 'IMPORT' ? (
|
||||
<ImportWarehouseTabs enabled={enabled} onChanged={onChanged} />
|
||||
) : (
|
||||
<ExportWarehouseTabs enabled={enabled} location={location} onChanged={onChanged} />
|
||||
<ExportWarehouseTabs
|
||||
enabled={enabled}
|
||||
location={location}
|
||||
onChanged={onChanged}
|
||||
focusedBookingId={focusedBookingId}
|
||||
focusedBookingLabel={focusedBookingLabel}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
/** New bulk Receive: Import / Export tabs with eligible PAID bookings. */
|
||||
function BulkReceiveModal({ opened, onClose, onReceived }: ReceiveInventoryModalProps) {
|
||||
function BulkReceiveModal({ opened, onClose, onReceived, bookingId, bookingLabel, direction = 'BOTH' }: ReceiveInventoryModalProps) {
|
||||
return (
|
||||
<Modal opened={opened} onClose={onClose} title="Receive at warehouse" centered size="80rem">
|
||||
<Stack gap="md">
|
||||
<WarehouseFlowWorkbench enabled={opened} direction="BOTH" onChanged={onReceived} />
|
||||
<WarehouseFlowWorkbench
|
||||
enabled={opened}
|
||||
direction={direction}
|
||||
onChanged={onReceived}
|
||||
focusedBookingId={bookingId}
|
||||
focusedBookingLabel={bookingLabel}
|
||||
/>
|
||||
|
||||
<Group justify="flex-end" mt="sm">
|
||||
<Button variant="default" onClick={onClose}>
|
||||
@@ -2870,5 +2931,5 @@ function SingleBookingReceiveModal({
|
||||
|
||||
export function ReceiveInventoryModal(props: ReceiveInventoryModalProps) {
|
||||
// Locked to a booking → legacy single receive; otherwise the Import/Export bulk flow.
|
||||
return props.bookingId ? <SingleBookingReceiveModal {...props} /> : <BulkReceiveModal {...props} />;
|
||||
return props.bookingId && props.mode !== 'bulk' ? <SingleBookingReceiveModal {...props} /> : <BulkReceiveModal {...props} />;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,17 @@ interface ReleaseOrderModalProps {
|
||||
opened: boolean;
|
||||
onClose: () => void;
|
||||
item: WarehouseInventoryItem | null;
|
||||
truckPrefill?: ReleaseOrderTruckPrefill | null;
|
||||
}
|
||||
|
||||
export interface ReleaseOrderTruckPrefill {
|
||||
truckPlateNumber?: string | null;
|
||||
trailerPlateNumber?: string | null;
|
||||
driverName?: string | null;
|
||||
driverLicense?: string | null;
|
||||
driverPhone?: string | null;
|
||||
truckType?: string | null;
|
||||
containerNumber?: string | null;
|
||||
}
|
||||
|
||||
const REGISTERED_FIRST_LAST_MILE_TRUCKS = [
|
||||
@@ -120,7 +131,7 @@ const parseInspectionNote = (notes: string | null | undefined) => {
|
||||
};
|
||||
};
|
||||
|
||||
export function ReleaseOrderModal({ opened, onClose, item }: ReleaseOrderModalProps) {
|
||||
export function ReleaseOrderModal({ opened, onClose, item, truckPrefill }: ReleaseOrderModalProps) {
|
||||
const { toast } = useToast();
|
||||
const releaseMutation = useMutation(api.warehouses.release.mutationOptions());
|
||||
const [reference, setReference] = useState('');
|
||||
@@ -145,27 +156,30 @@ export function ReleaseOrderModal({ opened, onClose, item }: ReleaseOrderModalPr
|
||||
const assignedDriverName = assignedTruckValue(item, 'customerTruckDriverName');
|
||||
const assignedTruckType = assignedTruckValue(item, 'customerTruckType');
|
||||
const assignedContainerNumber = assignedTruckValue(item, 'customerTruckContainerNumber');
|
||||
const prefillContainerNumber = truckPrefill?.containerNumber ?? '';
|
||||
setReference(item?.releaseOrderReference ?? generateReleaseReference(item));
|
||||
setTruckPlateNumber(inspection.truckPlateNumber || assignedTruckPlate);
|
||||
setTrailerPlateNumber(inspection.trailerPlateNumber);
|
||||
setDriverName(inspection.driverName || assignedDriverName);
|
||||
setDriverLicense(inspection.driverLicense);
|
||||
setDriverPhone(inspection.driverPhone);
|
||||
setTruckType(inspection.truckType || assignedTruckType);
|
||||
setContainerNumbers(initialContainerNumbers(item, inspection.containerNumber || assignedContainerNumber));
|
||||
setTruckPlateNumber(inspection.truckPlateNumber || assignedTruckPlate || truckPrefill?.truckPlateNumber || '');
|
||||
setTrailerPlateNumber(inspection.trailerPlateNumber || truckPrefill?.trailerPlateNumber || '');
|
||||
setDriverName(inspection.driverName || assignedDriverName || truckPrefill?.driverName || '');
|
||||
setDriverLicense(inspection.driverLicense || truckPrefill?.driverLicense || '');
|
||||
setDriverPhone(inspection.driverPhone || truckPrefill?.driverPhone || '');
|
||||
setTruckType(inspection.truckType || assignedTruckType || truckPrefill?.truckType || '');
|
||||
setContainerNumbers(initialContainerNumbers(item, inspection.containerNumber || assignedContainerNumber || prefillContainerNumber));
|
||||
setGateInTime(inspection.gateInTime);
|
||||
setTareWeight(inspection.tareWeight);
|
||||
setGrossWeight(inspection.grossWeight);
|
||||
setNetWeight(item?.weight == null ? inspection.netWeight : Number(item.weight));
|
||||
setGateOutTime(inspection.gateOutTime);
|
||||
}
|
||||
}, [opened, item]);
|
||||
}, [opened, item, truckPrefill]);
|
||||
|
||||
const savedInspection = parseInspectionNote(item?.notes);
|
||||
const isExitStep = savedInspection.tareWeight !== '';
|
||||
const isEntranceLocked = isExitStep;
|
||||
const isCustomerAssignedTruck = Boolean(item?.booking?.customerTruckAssignedAt);
|
||||
const isTruckIdentityLocked = isEntranceLocked || isCustomerAssignedTruck;
|
||||
const hasLastMileTruckPrefill = Boolean(truckPrefill?.truckPlateNumber || truckPrefill?.trailerPlateNumber);
|
||||
const isTruckIdentityLocked = isEntranceLocked || isCustomerAssignedTruck || hasLastMileTruckPrefill;
|
||||
const isDriverNameLocked = isEntranceLocked || isCustomerAssignedTruck || Boolean(truckPrefill?.driverName);
|
||||
const systemNetWeight = item?.weight == null ? netWeight : Number(item.weight);
|
||||
const computedNetWeight =
|
||||
tareWeight !== '' && grossWeight !== '' ? Number((Number(grossWeight) - Number(tareWeight)).toFixed(3)) : null;
|
||||
@@ -302,7 +316,7 @@ export function ReleaseOrderModal({ opened, onClose, item }: ReleaseOrderModalPr
|
||||
/>
|
||||
</Group>
|
||||
<Group grow>
|
||||
<TextInput label="Driver name" required value={driverName} onChange={(e) => setDriverName(e.currentTarget.value)} readOnly={isTruckIdentityLocked} />
|
||||
<TextInput label="Driver name" required value={driverName} onChange={(e) => setDriverName(e.currentTarget.value)} readOnly={isDriverNameLocked} />
|
||||
<TextInput label="Driver license" value={driverLicense} onChange={(e) => setDriverLicense(e.currentTarget.value)} readOnly={isEntranceLocked} />
|
||||
</Group>
|
||||
<Group grow>
|
||||
|
||||
Reference in New Issue
Block a user