mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 12:18:11 +00:00
Invoice and clearance seal approval
This commit is contained in:
@@ -58,6 +58,7 @@ interface TruckEntranceFormState {
|
||||
consigneeDetails: string;
|
||||
edrDigitalBookingId: string;
|
||||
tin: string;
|
||||
customerPhone: string;
|
||||
truckPlateNumber: string;
|
||||
trailerPlateNumber: string;
|
||||
assignedEquipmentNumber: string;
|
||||
@@ -85,11 +86,21 @@ interface TruckEntranceFormState {
|
||||
warehouseManagerName: string;
|
||||
}
|
||||
|
||||
interface LockedTruckEntranceFields {
|
||||
ownerName?: boolean;
|
||||
tin?: boolean;
|
||||
edrDigitalBookingId?: boolean;
|
||||
customerPhone?: boolean;
|
||||
}
|
||||
|
||||
type PackagingFreightType = 'CONTAINER' | 'BULK' | 'MIXED';
|
||||
|
||||
const emptyTruckEntrance = (): TruckEntranceFormState => ({
|
||||
ownerName: '',
|
||||
consigneeDetails: '',
|
||||
edrDigitalBookingId: '',
|
||||
tin: '',
|
||||
customerPhone: '',
|
||||
truckPlateNumber: '',
|
||||
trailerPlateNumber: '',
|
||||
assignedEquipmentNumber: '',
|
||||
@@ -122,6 +133,7 @@ const toTruckEntrancePayload = (form: TruckEntranceFormState): TruckEntrancePayl
|
||||
consigneeDetails: form.consigneeDetails.trim() || undefined,
|
||||
edrDigitalBookingId: form.edrDigitalBookingId.trim() || undefined,
|
||||
tin: form.tin.trim() || undefined,
|
||||
customerPhone: form.customerPhone.trim() || undefined,
|
||||
truckPlateNumber: form.truckPlateNumber.trim(),
|
||||
trailerPlateNumber: form.trailerPlateNumber.trim() || undefined,
|
||||
assignedEquipmentNumber: form.assignedEquipmentNumber.trim() || undefined,
|
||||
@@ -149,13 +161,106 @@ const toTruckEntrancePayload = (form: TruckEntranceFormState): TruckEntrancePayl
|
||||
warehouseManagerName: form.warehouseManagerName.trim() || undefined,
|
||||
});
|
||||
|
||||
const commonNonEmptyValue = (values: Array<string | null | undefined>) => {
|
||||
const unique = [...new Set(values.map((value) => value?.trim()).filter(Boolean))] as string[];
|
||||
return unique.length === 1 ? unique[0] : '';
|
||||
};
|
||||
|
||||
const truckEntranceFromBookings = (bookings: EligibleBooking[]): {
|
||||
form: TruckEntranceFormState;
|
||||
lockedFields: LockedTruckEntranceFields;
|
||||
packagingFreightType: PackagingFreightType;
|
||||
} => {
|
||||
const ownerName = commonNonEmptyValue(bookings.map((booking) => booking.customer));
|
||||
const tin = commonNonEmptyValue(bookings.map((booking) => booking.customerTin));
|
||||
const customerPhone = commonNonEmptyValue(bookings.map((booking) => booking.customerPhone));
|
||||
const edrDigitalBookingId =
|
||||
bookings.length === 1
|
||||
? bookings[0]?.reference ?? bookings[0]?.id ?? ''
|
||||
: commonNonEmptyValue(bookings.map((booking) => booking.reference));
|
||||
const firstMileBooking = bookings.length === 1 ? bookings[0] : null;
|
||||
const freightTypes = [...new Set(bookings.map((booking) => booking.freightType).filter(Boolean))];
|
||||
const packagingFreightType =
|
||||
freightTypes.length === 1 && freightTypes[0] === 'CONTAINER'
|
||||
? 'CONTAINER'
|
||||
: freightTypes.length === 1 && freightTypes[0] === 'BULK'
|
||||
? 'BULK'
|
||||
: 'MIXED';
|
||||
|
||||
return {
|
||||
form: {
|
||||
...emptyTruckEntrance(),
|
||||
ownerName,
|
||||
tin,
|
||||
customerPhone,
|
||||
edrDigitalBookingId,
|
||||
truckPlateNumber: firstMileBooking?.firstMileTruckPlateNumber ?? '',
|
||||
trailerPlateNumber: firstMileBooking?.firstMileTrailerPlateNumber ?? '',
|
||||
driverName: firstMileBooking?.firstMileDriverName ?? '',
|
||||
driverPhone: firstMileBooking?.firstMileDriverPhone ?? '',
|
||||
driverLicenseNumber: firstMileBooking?.firstMileDriverLicenseNumber ?? '',
|
||||
truckType: firstMileBooking?.firstMileTruckType ?? '',
|
||||
},
|
||||
lockedFields: {
|
||||
ownerName: Boolean(ownerName),
|
||||
tin: Boolean(tin),
|
||||
edrDigitalBookingId: Boolean(edrDigitalBookingId),
|
||||
customerPhone: Boolean(customerPhone),
|
||||
},
|
||||
packagingFreightType,
|
||||
};
|
||||
};
|
||||
|
||||
const BULK_PACKAGING_TYPE_OPTIONS = [
|
||||
{ value: 'BAG', label: 'Bag' },
|
||||
{ value: 'SACK', label: 'Sack' },
|
||||
{ value: 'BALE', label: 'Bale' },
|
||||
{ value: 'CARTON', label: 'Carton' },
|
||||
{ value: 'CRATE', label: 'Crate' },
|
||||
{ value: 'DRUM', label: 'Drum' },
|
||||
{ value: 'BARREL', label: 'Barrel' },
|
||||
{ value: 'PALLET', label: 'Pallet' },
|
||||
{ value: 'LOOSE_BULK', label: 'Loose bulk' },
|
||||
{ value: 'OTHER', label: 'Other' },
|
||||
];
|
||||
|
||||
const CONTAINER_PACKAGING_TYPE_OPTIONS = [
|
||||
{ value: 'CONTAINER_20FT', label: '20 ft container' },
|
||||
{ value: 'CONTAINER_40FT', label: '40 ft container' },
|
||||
{ value: 'CONTAINER_45FT', label: '45 ft container' },
|
||||
{ value: 'REEFER_CONTAINER', label: 'Reefer container' },
|
||||
{ value: 'TANK_CONTAINER', label: 'Tank container' },
|
||||
{ value: 'FLAT_RACK_CONTAINER', label: 'Flat rack container' },
|
||||
{ value: 'OPEN_TOP_CONTAINER', label: 'Open top container' },
|
||||
{ value: 'OTHER_CONTAINER', label: 'Other container' },
|
||||
];
|
||||
|
||||
const packagingOptionsFor = (freightType: PackagingFreightType) =>
|
||||
freightType === 'CONTAINER'
|
||||
? CONTAINER_PACKAGING_TYPE_OPTIONS
|
||||
: freightType === 'BULK'
|
||||
? BULK_PACKAGING_TYPE_OPTIONS
|
||||
: [...CONTAINER_PACKAGING_TYPE_OPTIONS, ...BULK_PACKAGING_TYPE_OPTIONS];
|
||||
|
||||
function TruckEntranceFields({
|
||||
value,
|
||||
onChange,
|
||||
lockedFields,
|
||||
packagingFreightType = 'MIXED',
|
||||
}: {
|
||||
value: TruckEntranceFormState;
|
||||
onChange: (next: TruckEntranceFormState) => void;
|
||||
lockedFields?: LockedTruckEntranceFields;
|
||||
packagingFreightType?: PackagingFreightType;
|
||||
}) {
|
||||
const packagingOptions = packagingOptionsFor(packagingFreightType);
|
||||
const quantityLabel =
|
||||
packagingFreightType === 'CONTAINER'
|
||||
? 'Container quantity'
|
||||
: packagingFreightType === 'BULK'
|
||||
? 'Unit count'
|
||||
: 'Quantity';
|
||||
|
||||
return (
|
||||
<Stack gap="sm">
|
||||
<Text size="sm" fw={600}>Customer and cargo ownership</Text>
|
||||
@@ -163,6 +268,7 @@ function TruckEntranceFields({
|
||||
<TextInput
|
||||
label="Owner's name"
|
||||
value={value.ownerName}
|
||||
readOnly={lockedFields?.ownerName}
|
||||
onChange={(e) => onChange({ ...value, ownerName: e.currentTarget.value })}
|
||||
/>
|
||||
<TextInput
|
||||
@@ -175,14 +281,22 @@ function TruckEntranceFields({
|
||||
<TextInput
|
||||
label="EDR digital booking ID"
|
||||
value={value.edrDigitalBookingId}
|
||||
readOnly={lockedFields?.edrDigitalBookingId}
|
||||
onChange={(e) => onChange({ ...value, edrDigitalBookingId: e.currentTarget.value })}
|
||||
/>
|
||||
<TextInput
|
||||
label="TIN"
|
||||
value={value.tin}
|
||||
readOnly={lockedFields?.tin}
|
||||
onChange={(e) => onChange({ ...value, tin: e.currentTarget.value })}
|
||||
/>
|
||||
</Group>
|
||||
<TextInput
|
||||
label="Customer phone"
|
||||
value={value.customerPhone}
|
||||
readOnly={lockedFields?.customerPhone}
|
||||
onChange={(e) => onChange({ ...value, customerPhone: e.currentTarget.value })}
|
||||
/>
|
||||
|
||||
<Text size="sm" fw={600} mt="xs">Transport and equipment tracking</Text>
|
||||
<Group grow>
|
||||
@@ -285,13 +399,16 @@ function TruckEntranceFields({
|
||||
/>
|
||||
</Group>
|
||||
<Group grow>
|
||||
<TextInput
|
||||
<Select
|
||||
label="Packaging type"
|
||||
data={packagingOptions}
|
||||
clearable
|
||||
searchable
|
||||
value={value.packagingType}
|
||||
onChange={(e) => onChange({ ...value, packagingType: e.currentTarget.value })}
|
||||
onChange={(v) => onChange({ ...value, packagingType: v ?? '' })}
|
||||
/>
|
||||
<NumberInput
|
||||
label="Unit count"
|
||||
label={quantityLabel}
|
||||
min={0}
|
||||
value={value.unitCount}
|
||||
onChange={(v) => onChange({ ...value, unitCount: v === '' ? '' : Number(v) })}
|
||||
@@ -466,6 +583,8 @@ function EligibleTab({
|
||||
const [truckOpen, setTruckOpen] = useState(false);
|
||||
const [pendingReceiveIds, setPendingReceiveIds] = useState<string[]>([]);
|
||||
const [truckForm, setTruckForm] = useState<TruckEntranceFormState>(emptyTruckEntrance());
|
||||
const [lockedTruckFields, setLockedTruckFields] = useState<LockedTruckEntranceFields>({});
|
||||
const [packagingFreightType, setPackagingFreightType] = useState<PackagingFreightType>('MIXED');
|
||||
|
||||
const locationReady = Boolean(location.warehouseId && location.yardId && location.zoneId);
|
||||
const canReceiveBooking = (row: EligibleBooking) =>
|
||||
@@ -564,13 +683,14 @@ function EligibleTab({
|
||||
toast({ variant: 'destructive', title: 'No selected booking is ready to receive' });
|
||||
return;
|
||||
}
|
||||
const row = filteredIds.length === 1 ? rows.find((item) => item.id === filteredIds[0]) : null;
|
||||
const selectedRows = filteredIds
|
||||
.map((id) => rows.find((item) => item.id === id))
|
||||
.filter(Boolean) as EligibleBooking[];
|
||||
const { form, lockedFields, packagingFreightType: nextPackagingFreightType } = truckEntranceFromBookings(selectedRows);
|
||||
setPendingReceiveIds(filteredIds);
|
||||
setTruckForm({
|
||||
...emptyTruckEntrance(),
|
||||
truckPlateNumber: row?.firstMileTruckPlateNumber ?? '',
|
||||
trailerPlateNumber: row?.firstMileTrailerPlateNumber ?? '',
|
||||
});
|
||||
setTruckForm(form);
|
||||
setLockedTruckFields(lockedFields);
|
||||
setPackagingFreightType(nextPackagingFreightType);
|
||||
setTruckOpen(true);
|
||||
};
|
||||
|
||||
@@ -593,6 +713,8 @@ function EligibleTab({
|
||||
setSelected(new Set());
|
||||
setTruckOpen(false);
|
||||
setPendingReceiveIds([]);
|
||||
setLockedTruckFields({});
|
||||
setPackagingFreightType('MIXED');
|
||||
onChanged?.();
|
||||
} catch (error) {
|
||||
toast({ variant: 'destructive', title: 'Receive failed', description: extractErrorMessage(error) });
|
||||
@@ -805,7 +927,12 @@ function EligibleTab({
|
||||
<Text size="sm" c="dimmed">
|
||||
Register the arriving truck and driver before receiving {pendingReceiveIds.length} booking{pendingReceiveIds.length === 1 ? '' : 's'}.
|
||||
</Text>
|
||||
<TruckEntranceFields value={truckForm} onChange={setTruckForm} />
|
||||
<TruckEntranceFields
|
||||
value={truckForm}
|
||||
onChange={setTruckForm}
|
||||
lockedFields={lockedTruckFields}
|
||||
packagingFreightType={packagingFreightType}
|
||||
/>
|
||||
<Group justify="flex-end">
|
||||
<Button variant="default" onClick={() => setTruckOpen(false)} disabled={bulkReceive.isPending}>
|
||||
Cancel
|
||||
|
||||
Reference in New Issue
Block a user