feat: enhance train scheduling and contract management features

- Added StationWorkControls to manage loading/unloading phases in TrainScheduleV2DetailPage.
- Implemented API endpoints for recording station work and managing wagon detach requests.
- Updated contract templates to include Ethiopian customs handling options.
- Enhanced shipment forms to collect customs clearing agent details for without-customs bookings.
- Introduced NUMBER_OF_WAGONS as a unit of measure for bulk cargo, allowing customers to specify wagon counts.
- Improved validation for customs clearing agent information in shipment forms.
- Updated various components and services to accommodate new features and ensure data integrity.
This commit is contained in:
Marshal
2026-08-25 21:44:21 +00:00
parent d5a5085d6d
commit b926a3116e
67 changed files with 2998 additions and 255 deletions

View File

@@ -6,6 +6,7 @@ import {
bookingCargoTons,
bulkItemsFitFor,
bulkTonsPerWagon,
bulkTonsPerWagonFor,
bulkWagonsForAllowedTypes,
} from './train-capacity.util';
import {
@@ -179,7 +180,7 @@ const shortageFor = (
let seatable = 0;
let usedWagons = 0;
for (const { wt, free } of freeByType) {
const perWagon = bulkTonsPerWagon(booking.cargoType, wt.id, Number(wt.capacityTons));
const perWagon = bulkTonsPerWagonFor(booking, booking.cargoType, wt.id, Number(wt.capacityTons));
if (!(perWagon > 0) || free <= 0) continue;
seatable += free * perWagon;
usedWagons += free;
@@ -188,7 +189,7 @@ const shortageFor = (
const bestPerWagon = Math.max(
1,
...candidates.map((wt) =>
bulkTonsPerWagon(booking.cargoType, wt.id, Number(wt.capacityTons)),
bulkTonsPerWagonFor(booking, booking.cargoType, wt.id, Number(wt.capacityTons)),
),
);
return {
@@ -583,7 +584,8 @@ export function planWagonsWithStock(params: {
if (perItem ? remainingItems <= 0 : remainingWeight <= 0) break;
const wagonType = candidates.find((wt) => wt.id === open.slot.wagonTypeId);
if (!wagonType) continue;
const room = bulkTonsPerWagon(
const room = bulkTonsPerWagonFor(
booking,
booking.cargoType,
open.slot.wagonTypeId,
Number(open.slot.capacityTons),
@@ -640,7 +642,20 @@ export function planWagonsWithStock(params: {
openedSlot.freeItems = itemBudgetOf(openedSlot) - takeItems;
remainingItems -= takeItems;
} else {
take = roundTons(Math.min(openedSlot.freeCapacityTons, remainingWeight));
// NUMBER_OF_WAGONS: each wagon takes the even share (tons / requested),
// not the full per-wagon cap — the loop then opens exactly that count.
take = roundTons(
Math.min(
openedSlot.freeCapacityTons,
bulkTonsPerWagonFor(
booking,
booking.cargoType,
openedSlot.slot.wagonTypeId,
openedSlot.slot.capacityTons,
),
remainingWeight,
),
);
}
addAllocation(
openedSlot.slot,