Implement clearance-first booking flow and completion process for customs contracts

This commit is contained in:
Marshal
2026-07-10 18:56:39 +00:00
parent b50075dc83
commit 9ed473c309
18 changed files with 369 additions and 39 deletions

View File

@@ -145,13 +145,37 @@ function bulkUnitOfMeasure(
}
export default function GlCreateBookingForm() {
const { id } = useParams<{ id: string }>();
// With `bookingId` the form runs in COMPLETION mode: the bare instance
// (auto-initiated by the customer's shipment request) already finished its
// per-booking customs clearance, and this form supplies the deferred cargo
// (container numbers, VGM) + binding shipment day. Same window gate, same
// validation and price confirmation — the submit completes the existing
// booking instead of creating a new one.
const { id, bookingId: completeBookingId } = useParams<{
id: string;
bookingId?: string;
}>();
const [searchParams] = useSearchParams();
const requestId = searchParams.get("requestId");
const requestIdParam = searchParams.get("requestId");
const navigate = useNavigate();
const { data: contract, isLoading } = useContractDetail(id);
const mutations = useContractMutations(id ?? "");
// Completion mode without an explicit ?requestId=: find the shipment request
// that initiated this instance so the quantities still prefill.
const { data: contractRequests } = useQuery({
queryKey: ["shipment-requests-for-contract", id],
queryFn: () => contractsService.listBookingRequests(id!),
enabled: Boolean(id) && Boolean(completeBookingId) && !requestIdParam,
});
const requestId =
requestIdParam ??
(completeBookingId
? (contractRequests?.find(
(r) => r.createdBookingId === completeBookingId,
)?.id ?? null)
: null);
const { data: bookingRequest } = useQuery({
queryKey: ["shipment-request", requestId],
queryFn: () => contractsService.getBookingRequest(requestId!),
@@ -636,6 +660,18 @@ export default function GlCreateBookingForm() {
const payload = buildPayload();
if (!payload) return;
if (completeBookingId) {
// Completion mode: cargo + day land on the already-cleared instance —
// the request was linked and accepted at submission time.
mutations.completeBooking.mutate(
{ bookingId: completeBookingId, payload },
{
onSuccess: () => navigate(`/dashboard/clearance/${completeBookingId}`),
},
);
return;
}
mutations.createBooking.mutate(payload, {
onSuccess: async (booking) => {
if (requestId) {
@@ -685,10 +721,12 @@ export default function GlCreateBookingForm() {
<Group justify="space-between" align="flex-end" wrap="wrap" gap="md" mb="lg">
<Box>
<Text fw={800} fz={26} style={{ letterSpacing: "-0.01em" }}>
New Shipment Booking
{completeBookingId ? "Complete Shipment Booking" : "New Shipment Booking"}
</Text>
<Text size="sm" c="dimmed" mt={4}>
Book a shipment on behalf of the customer for contract {contract.reference}.
{completeBookingId
? `Clearance is finalized — enter the cargo details and shipment day to complete the booking under contract ${contract.reference}.`
: `Book a shipment on behalf of the customer for contract ${contract.reference}.`}
</Text>
</Box>
<Button
@@ -1261,7 +1299,11 @@ export default function GlCreateBookingForm() {
<Modal
opened={priceOpen}
onClose={() => {
if (!mutations.createBooking.isPending) setPriceOpen(false);
if (
!mutations.createBooking.isPending &&
!mutations.completeBooking.isPending
)
setPriceOpen(false);
}}
centered
radius="lg"
@@ -1413,7 +1455,10 @@ export default function GlCreateBookingForm() {
radius="md"
leftSection={<X size={16} />}
onClick={() => setPriceOpen(false)}
disabled={mutations.createBooking.isPending}
disabled={
mutations.createBooking.isPending ||
mutations.completeBooking.isPending
}
>
Reject &amp; edit
</Button>
@@ -1421,7 +1466,10 @@ export default function GlCreateBookingForm() {
color="edr-green"
radius="md"
leftSection={<CheckCircle2 size={16} />}
loading={mutations.createBooking.isPending}
loading={
mutations.createBooking.isPending ||
mutations.completeBooking.isPending
}
disabled={
validateShipmentMutation.isPending ||
pairingErrors.length > 0 ||
@@ -1429,7 +1477,7 @@ export default function GlCreateBookingForm() {
}
onClick={handleSubmit}
>
Confirm &amp; book
{completeBookingId ? "Confirm & complete" : "Confirm & book"}
</Button>
</Group>
</Stack>