mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 01:48:12 +00:00
Merge branch 'dev' into freight/feat/fixes-v1
This commit is contained in:
@@ -102,6 +102,7 @@ import type {
|
||||
LoadInventoryPayload,
|
||||
LoadPassedExportResult,
|
||||
MoveInventoryPayload,
|
||||
StoreInventoryPayload,
|
||||
PayInvoicePayload,
|
||||
ReadyToLoadRow,
|
||||
ReceiveInventoryPayload,
|
||||
@@ -1054,10 +1055,13 @@ export const api = {
|
||||
() => [["warehouse-inventory"], ["warehouses"]],
|
||||
),
|
||||
|
||||
store: endpoint<string, WarehouseInventoryItem>(
|
||||
store: endpoint<
|
||||
{ id: string; payload?: StoreInventoryPayload },
|
||||
WarehouseInventoryItem
|
||||
>(
|
||||
"warehouse-inventory",
|
||||
"store",
|
||||
(id) => warehouseService.store(id).then((r) => r.data),
|
||||
({ id, payload }) => warehouseService.store(id, payload).then((r) => r.data),
|
||||
undefined,
|
||||
() => INVENTORY_INVALIDATIONS,
|
||||
),
|
||||
|
||||
@@ -482,10 +482,25 @@ export const contractsService = {
|
||||
postContract<Freight.IContract>(C.OPS_CLEARANCE_FINALIZE(id)),
|
||||
|
||||
// ── Booking under contract (GL ET — Path B) ──
|
||||
createBookingUnderContract: (
|
||||
// The API returns { booking, warnings } (CreateBookingUnderContractResult) —
|
||||
// unwrap to the booking itself so callers can use its id directly.
|
||||
createBookingUnderContract: async (
|
||||
id: string,
|
||||
payload: Freight.CreateBookingUnderContractDto,
|
||||
) => postContract<{ id: string; reference: string }>(C.BOOKINGS(id), payload),
|
||||
): Promise<{ id: string; reference: string; warnings?: string[] }> => {
|
||||
const result = await postContract<{
|
||||
booking?: { id: string; reference: string };
|
||||
id?: string;
|
||||
reference?: string;
|
||||
warnings?: string[];
|
||||
}>(C.BOOKINGS(id), payload);
|
||||
const booking = result.booking ?? result;
|
||||
return {
|
||||
id: booking.id ?? "",
|
||||
reference: booking.reference ?? "",
|
||||
warnings: result.warnings,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Pre-create validation + authoritative price preview: the same
|
||||
|
||||
@@ -27,6 +27,10 @@ export interface Locomotive {
|
||||
currentYard?: { id: string; label?: string; code?: string } | null;
|
||||
maxPullWeightTons: number;
|
||||
maxTrainLengthMeters: number;
|
||||
/** Tons a train may exceed maxPullWeightTons by before scheduling blocks it. */
|
||||
overageToleranceTons?: number | null;
|
||||
/** Metres a train may exceed maxTrainLengthMeters by before scheduling blocks it. */
|
||||
overageToleranceMeters?: number | null;
|
||||
powerKw?: number | null;
|
||||
tractionForceKn?: number | null;
|
||||
maxSpeedKmh?: number | null;
|
||||
|
||||
@@ -39,11 +39,16 @@ export interface SaveRoutePayload {
|
||||
status?: RouteStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Human-readable route label: yard names, not yard codes. Staff read
|
||||
* "Addis Ababa → Dire Dawa", not "ADDIS_ABABA → DIRE_DAWA". Falls back to the
|
||||
* code only when a yard has no label.
|
||||
*/
|
||||
export function formatRouteLabel(route: RouteRecord): string {
|
||||
const origin =
|
||||
route.originYard?.code ?? route.originYard?.label ?? 'Origin';
|
||||
route.originYard?.label ?? route.originYard?.code ?? 'Origin';
|
||||
const dest =
|
||||
route.destinationYard?.code ?? route.destinationYard?.label ?? 'Destination';
|
||||
route.destinationYard?.label ?? route.destinationYard?.code ?? 'Destination';
|
||||
return `${origin} → ${dest}`;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,8 @@ export interface WagonType {
|
||||
name: string;
|
||||
capacityTons: number;
|
||||
lengthMeters: number;
|
||||
maxWagonsPerTrain?: number | null;
|
||||
/** Empty wagon weight; required, since pull limits apply to tare + cargo. */
|
||||
tareWeightTons: number;
|
||||
supportedLoadTypes: string[];
|
||||
isActive: boolean;
|
||||
}
|
||||
|
||||
@@ -15,14 +15,16 @@ export interface Wagon {
|
||||
label: string;
|
||||
country?: string;
|
||||
} | null;
|
||||
/** Owns this wagon's spec — tare, capacity, length are read from here, never off the wagon. */
|
||||
wagonType?: {
|
||||
id: string;
|
||||
code: string;
|
||||
name: string;
|
||||
supportedLoadTypes?: string[];
|
||||
tareWeightTons?: number;
|
||||
capacityTons?: number;
|
||||
lengthMeters?: number;
|
||||
} | null;
|
||||
tareWeight: number;
|
||||
maxPayloadWeight: number;
|
||||
status: Freight.WagonStatus;
|
||||
currentYardId: string | null;
|
||||
currentYard?: { id: string; label?: string; code?: string } | null;
|
||||
|
||||
@@ -30,6 +30,7 @@ import type {
|
||||
LoadableWagon,
|
||||
LoadInventoryPayload,
|
||||
MoveInventoryPayload,
|
||||
StoreInventoryPayload,
|
||||
ReceiveInventoryPayload,
|
||||
ReleaseOrderPayload,
|
||||
DeliverInventoryPayload,
|
||||
@@ -63,7 +64,7 @@ import type {
|
||||
WarehouseZone,
|
||||
} from '@/types/warehouse';
|
||||
|
||||
export type ContainerItemStage = 'PENDING' | 'RECEIVED' | 'GRN' | 'LOADED' | 'LEFT' | 'DELIVERED';
|
||||
export type ContainerItemStage = 'PENDING' | 'RECEIVED' | 'GRN' | 'ASSIGNED' | 'LOADED' | 'LEFT' | 'DELIVERED';
|
||||
|
||||
export interface ContainerItem {
|
||||
containerNumber: string;
|
||||
@@ -74,9 +75,12 @@ export interface ContainerItem {
|
||||
truckPlate: string | null;
|
||||
truckArrived: boolean;
|
||||
truckLeft: boolean;
|
||||
/** Operator has loaded this container onto the truck (customer assignment alone is not "loaded"). */
|
||||
loaded: boolean;
|
||||
bookingReference: string | null;
|
||||
contractId: string | null;
|
||||
hasLastMile: boolean;
|
||||
handoverSigned: boolean;
|
||||
}
|
||||
|
||||
/** A pre-dispatch EXPORT train that has inventory waiting to be loaded. */
|
||||
@@ -135,6 +139,26 @@ export const warehouseService = {
|
||||
return data?.data ?? data ?? [];
|
||||
},
|
||||
|
||||
/** Ask the customer to sign the booking's handover (creates one if none, then notifies). */
|
||||
requestHandoverSignature: async (
|
||||
bookingId: string,
|
||||
): Promise<{ notified: boolean; reference: string | null; alreadySigned: boolean }> => {
|
||||
const { data } = await apiClient.post(
|
||||
`/warehouse-inventory/bookings/${bookingId}/request-handover-signature`,
|
||||
);
|
||||
return data?.data ?? data;
|
||||
},
|
||||
|
||||
/** A booking's containers with VGM cargo weight (tonnes) for exit weighing. */
|
||||
getContainerWeights: async (
|
||||
bookingId: string,
|
||||
): Promise<Array<{ containerNumber: string; weightTons: number }>> => {
|
||||
const { data } = await apiClient.get(
|
||||
`/warehouse-inventory/bookings/${bookingId}/container-weights`,
|
||||
);
|
||||
return data?.data ?? data ?? [];
|
||||
},
|
||||
|
||||
/** Booking container numbers not yet loaded onto any truck. */
|
||||
getLoadableContainers: async (bookingId: string): Promise<string[]> => {
|
||||
const { data } = await apiClient.get(
|
||||
@@ -235,8 +259,8 @@ export const warehouseService = {
|
||||
}),
|
||||
|
||||
// ── Lifecycle (Batch 2) ──────────────────────────────────────────────────
|
||||
store: (id: string) =>
|
||||
apiClient.post<WarehouseInventoryItem>(URL_CONSTANTS.WAREHOUSE_INVENTORY.STORE(id)),
|
||||
store: (id: string, payload?: StoreInventoryPayload) =>
|
||||
apiClient.post<WarehouseInventoryItem>(URL_CONSTANTS.WAREHOUSE_INVENTORY.STORE(id), payload),
|
||||
reserve: (payload: ReserveInventoryPayload) =>
|
||||
apiClient.post<WarehouseInventoryItem>(URL_CONSTANTS.WAREHOUSE_INVENTORY.RESERVE, payload),
|
||||
markReadyForLoading: (id: string) =>
|
||||
|
||||
Reference in New Issue
Block a user