mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 16:28:12 +00:00
feat(bookings): enhance booking process with customs clearing and document handling
- Update bookings service to prioritize uploaded documents over profile snapshots. - Refactor pricing data seeder to remove unused service types and streamline cargo type seeding. - Add customs clearing information to BookingRouteServiceCard, displaying agent details if applicable. - Extend BookingDetail type to include customs clearing options. - Modify NewBookingPage to remove the scheduling step, integrating estimated shipment date into the route step. - Update StepIndicator to reflect the new step structure. - Revise document handling in StepDocuments to allow for user uploads while displaying onboarding documents. - Adjust Step2ServiceType to manage customs clearing agent input based on service type. - Implement shipment date input in Step4Route for one-time bookings. - Revise Step8Review to reflect changes in document handling and scheduling.
This commit is contained in:
@@ -480,7 +480,11 @@ export class BookingsService {
|
||||
// Reuse the booking profile's onboarding documents instead of asking the
|
||||
// customer to re-upload. Snapshot them onto the booking now (by reference),
|
||||
// so a later active-profile switch never changes this booking's documents.
|
||||
if (companyProfileId) {
|
||||
//
|
||||
// Skip this when the customer uploaded documents for this booking — those
|
||||
// per-booking files take precedence, so auto-attaching the profile snapshots
|
||||
// would create duplicates.
|
||||
if (companyProfileId && files.length === 0) {
|
||||
try {
|
||||
const onboardingFiles =
|
||||
await this.companiesService.getProfileOnboardingFiles(companyProfileId);
|
||||
|
||||
@@ -32,7 +32,7 @@ export class PricingDataSeeder {
|
||||
const prRepo = manager.getRepository(PriorityConfig);
|
||||
const rRepo = manager.getRepository(Rate);
|
||||
|
||||
await this.upsertReferenceData(manager, ctRepo, stRepo, yRepo, slRepo);
|
||||
await this.upsertReferenceData(manager, ctRepo, yRepo, slRepo);
|
||||
await this.seedDomesticRoute(manager, yRepo);
|
||||
await this.seedWeightLimits(wlRepo, ctRepo);
|
||||
await this.seedPriorityConfigs(prRepo);
|
||||
@@ -71,7 +71,6 @@ export class PricingDataSeeder {
|
||||
private async upsertReferenceData(
|
||||
manager: any,
|
||||
ctRepo: any,
|
||||
stRepo: any,
|
||||
yRepo: any,
|
||||
slRepo: any,
|
||||
): Promise<void> {
|
||||
@@ -155,47 +154,7 @@ export class PricingDataSeeder {
|
||||
{ conflictPaths: { code: true } },
|
||||
);
|
||||
|
||||
await stRepo.upsert(
|
||||
[
|
||||
{
|
||||
code: "RAIL_CONTAINER",
|
||||
serviceName: "Rail Container Service",
|
||||
description: "Standard rail container transport",
|
||||
canBeBookedAlone: true,
|
||||
includesFirstMile: false,
|
||||
includesLastMile: false,
|
||||
includesCustoms: false,
|
||||
priorityBonusPoints: 0,
|
||||
isActive: true,
|
||||
displayOrder: 1,
|
||||
},
|
||||
{
|
||||
code: "RAIL_FORWARDING",
|
||||
serviceName: "Rail Forwarding Service",
|
||||
description: "Rail transport with first/last mile and customs",
|
||||
canBeBookedAlone: true,
|
||||
includesFirstMile: true,
|
||||
includesLastMile: true,
|
||||
includesCustoms: true,
|
||||
priorityBonusPoints: 15,
|
||||
isActive: true,
|
||||
displayOrder: 2,
|
||||
},
|
||||
{
|
||||
code: "RAIL_BULK",
|
||||
serviceName: "Rail Bulk Transport",
|
||||
description: "Bulk commodity rail transport",
|
||||
canBeBookedAlone: true,
|
||||
includesFirstMile: false,
|
||||
includesLastMile: false,
|
||||
includesCustoms: false,
|
||||
priorityBonusPoints: 10,
|
||||
isActive: true,
|
||||
displayOrder: 3,
|
||||
},
|
||||
],
|
||||
{ conflictPaths: { code: true } },
|
||||
);
|
||||
|
||||
|
||||
await slRepo.upsert(
|
||||
[
|
||||
@@ -238,53 +197,78 @@ export class PricingDataSeeder {
|
||||
{ conflictPaths: { code: true } },
|
||||
);
|
||||
|
||||
await manager.getRepository(CargoType).upsert(
|
||||
await this.seedCargoTypes(manager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cargo types are a fixed two-level tree: two top-level groups — Bulk and
|
||||
* Break Bulk — each with a set of commodity children. The groups are the
|
||||
* stable parents the booking wizard renders; children carry the
|
||||
* unit_of_measure used when reserving quantity (PER_TON for bulk commodities,
|
||||
* PER_ITEM for break-bulk items like vehicles/machinery).
|
||||
*
|
||||
* Parents are upserted first, then re-read by code to resolve their ids so the
|
||||
* children can be linked via parent_group_id (upsert doesn't return ids).
|
||||
*/
|
||||
private async seedCargoTypes(manager: any): Promise<void> {
|
||||
const repo = manager.getRepository(CargoType);
|
||||
|
||||
const groups = [
|
||||
{ code: "BULK", cargoTypeName: "Bulk", displayOrder: 1 },
|
||||
{ code: "BREAK_BULK", cargoTypeName: "Break Bulk", displayOrder: 2 },
|
||||
];
|
||||
await repo.upsert(
|
||||
groups.map((g) => ({ ...g, isActive: true })),
|
||||
{ conflictPaths: { code: true } },
|
||||
);
|
||||
|
||||
const bulk = await repo.findOneBy({ code: "BULK" });
|
||||
const breakBulk = await repo.findOneBy({ code: "BREAK_BULK" });
|
||||
if (!bulk || !breakBulk) return;
|
||||
|
||||
// Bulk commodities — measured by tonnage (PER_TON).
|
||||
const bulkChildren = [
|
||||
{ code: "SUGAR", cargoTypeName: "Sugar" },
|
||||
{ code: "GRAIN", cargoTypeName: "Grain / Cereals" },
|
||||
{ code: "WHEAT", cargoTypeName: "Wheat" },
|
||||
{ code: "FERTILIZER", cargoTypeName: "Fertilizer" },
|
||||
{ code: "CEMENT", cargoTypeName: "Cement / Clinker" },
|
||||
{ code: "COAL", cargoTypeName: "Coal" },
|
||||
];
|
||||
|
||||
// Break-bulk items — counted as whole units (PER_ITEM).
|
||||
const breakBulkChildren = [
|
||||
{ code: "CARS", cargoTypeName: "Cars / Vehicles" },
|
||||
{ code: "MACHINERY", cargoTypeName: "Heavy Machinery" },
|
||||
{ code: "STEEL", cargoTypeName: "Steel / Rebar" },
|
||||
{ code: "PIPES", cargoTypeName: "Pipes" },
|
||||
{ code: "TIMBER", cargoTypeName: "Timber" },
|
||||
];
|
||||
|
||||
await repo.upsert(
|
||||
[
|
||||
{
|
||||
code: "GRAIN",
|
||||
cargoTypeName: "Grain / Cereals",
|
||||
requiresDirectorApproval: false,
|
||||
...bulkChildren.map((c, i) => ({
|
||||
...c,
|
||||
parentGroupId: bulk.id,
|
||||
unitOfMeasure: "PER_TON",
|
||||
isActive: true,
|
||||
displayOrder: 1,
|
||||
},
|
||||
{
|
||||
code: "FERTILIZER",
|
||||
cargoTypeName: "Fertilizer",
|
||||
requiresDirectorApproval: false,
|
||||
displayOrder: i + 1,
|
||||
})),
|
||||
...breakBulkChildren.map((c, i) => ({
|
||||
...c,
|
||||
parentGroupId: breakBulk.id,
|
||||
unitOfMeasure: "PER_ITEM",
|
||||
isActive: true,
|
||||
displayOrder: 2,
|
||||
},
|
||||
{
|
||||
code: "CEMENT",
|
||||
cargoTypeName: "Cement / Clinker",
|
||||
requiresDirectorApproval: false,
|
||||
isActive: true,
|
||||
displayOrder: 3,
|
||||
},
|
||||
{
|
||||
code: "STEEL",
|
||||
cargoTypeName: "Steel / Rebar",
|
||||
requiresDirectorApproval: true,
|
||||
isActive: true,
|
||||
displayOrder: 4,
|
||||
},
|
||||
{
|
||||
code: "MACHINERY",
|
||||
cargoTypeName: "Heavy Machinery",
|
||||
requiresDirectorApproval: true,
|
||||
isActive: true,
|
||||
displayOrder: 5,
|
||||
},
|
||||
{
|
||||
code: "OTHER_BULK",
|
||||
cargoTypeName: "Other Bulk Cargo",
|
||||
requiresDirectorApproval: false,
|
||||
isActive: true,
|
||||
displayOrder: 6,
|
||||
},
|
||||
displayOrder: i + 1,
|
||||
})),
|
||||
],
|
||||
{ conflictPaths: { code: true } },
|
||||
);
|
||||
|
||||
// Retire the old flat "Other Bulk Cargo" top-level type from earlier seeds so
|
||||
// it no longer shows alongside the Bulk / Break Bulk groups. No-op on a fresh
|
||||
// DB where it was never seeded.
|
||||
await repo.update({ code: "OTHER_BULK" }, { isActive: false });
|
||||
}
|
||||
|
||||
private async seedDomesticRoute(manager: any, yRepo: any): Promise<void> {
|
||||
|
||||
Reference in New Issue
Block a user