feat: implement shipping line booking completion functionality

- Added ShippingLineBookingCompletionController and associated service to handle the completion of shipping line bookings.
- Introduced a new module for booking completion to maintain module separation and avoid cyclic dependencies.
- Updated the train scheduling global rules to set default desk hours to 24 hours.
- Modified existing services and entities to accommodate the new booking completion logic.
- Enhanced the front-end components to support the new booking completion flow, including updates to the booking detail and bookings pages.
- Implemented validation and error handling for booking completion, ensuring that only approved bookings can be completed.
- Added migration to set default desk hours in the database.
This commit is contained in:
Marshal
2026-08-13 15:10:27 +00:00
parent 5837ac7b6e
commit 0e00a98ef3
18 changed files with 681 additions and 389 deletions

View File

@@ -220,7 +220,7 @@ export default function ShippingLineBookingDetailPage() {
>
{status === "OPERATION_CHANGES_REQUESTED"
? "Resubmit booking"
: "Complete booking"}
: "Book"}
</Button>
)}
</Group>
@@ -317,7 +317,7 @@ export default function ShippingLineBookingDetailPage() {
>
{status === "OPERATION_CHANGES_REQUESTED"
? "Resubmit booking"
: "Complete booking"}
: "Book"}
</Button>
)}
</Group>

View File

@@ -41,9 +41,16 @@ import {
DOC_STATE_COLOR,
DOC_STATE_LABEL,
} from "./booking-doc-state";
import ShippingLineCompleteModal from "./ShippingLineCompleteModal";
import ShippingLineDocumentsModal from "./ShippingLineDocumentsModal";
import ShippingLineInitiateModal from "./ShippingLineInitiateModal";
/** Statuses where the booking is approved and waiting to be booked. */
const BOOKABLE_STATUSES = new Set([
"CLEARANCE_READY",
"OPERATION_CHANGES_REQUESTED",
]);
function ColHeader({ label }: { label: string }) {
return (
<Text fz={12} fw={700} c="edr-muted" tt="uppercase" lts="0.04em">
@@ -67,6 +74,9 @@ export default function ShippingLineBookingsPage() {
const [docsBooking, setDocsBooking] = useState<ShippingLineBooking | null>(
null,
);
const [bookBooking, setBookBooking] = useState<ShippingLineBooking | null>(
null,
);
const bookingsQuery = useQuery({
queryKey: ["shipping-line-bookings"],
@@ -154,6 +164,7 @@ export default function ShippingLineBookingsPage() {
const state = bookingDocState(booking);
const showDocs = hasDocuments(state);
const wantsUpload = needsUpload(state);
const bookable = BOOKABLE_STATUSES.has(booking.status as string);
return (
<Group
@@ -162,7 +173,24 @@ export default function ShippingLineBookingsPage() {
wrap="nowrap"
onClick={(e) => e.stopPropagation()}
>
{showDocs && (
{/* Approved documents make booking the primary move — the docs
button steps back into the menu so one action owns the row. */}
{bookable && (
<Button
size="xs"
radius="md"
color="edr-green"
fw={700}
fz={13}
leftSection={<PackageCheck size={14} />}
onClick={() => setBookBooking(booking)}
>
{booking.status === "OPERATION_CHANGES_REQUESTED"
? "Resubmit"
: "Book"}
</Button>
)}
{showDocs && !bookable && (
<Button
size="xs"
radius="md"
@@ -214,19 +242,14 @@ export default function ShippingLineBookingsPage() {
{DOC_STATE_ACTION_LABEL[state]}
</Menu.Item>
)}
{/* Approved documents (or an operations return) unlock the
completion step — it lives on the detail page. */}
{(booking.status === "CLEARANCE_READY" ||
booking.status === "OPERATION_CHANGES_REQUESTED") && (
{bookable && (
<Menu.Item
leftSection={<PackageCheck size={15} />}
onClick={() =>
navigate(`/shipping-line/bookings/${booking.id}`)
}
onClick={() => setBookBooking(booking)}
>
{booking.status === "OPERATION_CHANGES_REQUESTED"
? "Resubmit booking"
: "Complete booking"}
: "Book"}
</Menu.Item>
)}
</Menu.Dropdown>
@@ -325,6 +348,16 @@ export default function ShippingLineBookingsPage() {
booking={docsBooking}
onClose={() => setDocsBooking(null)}
/>
<ShippingLineCompleteModal
booking={bookBooking}
onClose={() => setBookBooking(null)}
onCompleted={(booking) => {
setBookBooking(null);
// Straight into the booking — its status and price just changed.
navigate(`/shipping-line/bookings/${booking.id}`);
}}
/>
</Box>
);
}