add yard distances management to rule engine

- Introduced new yard distances resource with CRUD operations.
- Created migration for yard distances table with necessary constraints.
- Implemented service and repository for yard distances handling.
- Added controller for API endpoints to manage yard distances.
- Updated rule engine configuration to include yard distances.
- Enhanced rule engine resource page to support yard distance selection.
- Updated contracts and train builder pages to handle new yard distance logic.
- Added error handling utility for better error message extraction.
This commit is contained in:
Marshal
2026-07-21 08:50:50 +00:00
parent 1647681840
commit 603537a20b
45 changed files with 1275 additions and 227 deletions

View File

@@ -229,15 +229,26 @@ export const contractsService = {
approveStep: ({ id, stepId }: { id: string; stepId: string }) =>
postContract<Freight.IContract>(C.APPROVE_STEP(id, stepId)),
/**
* Reject the current step. Without `returnToStepId` the contract is rejected
* to the customer (terminal). With it, the contract is sent back to that
* earlier approved step and the chain re-runs from there.
*/
rejectStep: ({
id,
stepId,
reason,
returnToStepId,
}: {
id: string;
stepId: string;
reason: string;
}) => postContract<Freight.IContract>(C.REJECT_STEP(id, stepId), { reason }),
returnToStepId?: string;
}) =>
postContract<Freight.IContract>(C.REJECT_STEP(id, stepId), {
reason,
...(returnToStepId ? { returnToStepId } : {}),
}),
// ── Contract document ──
generateContract: (id: string) =>

View File

@@ -15,6 +15,10 @@ export type LocomotiveStatus =
export interface LocomotiveListFilters {
status?: LocomotiveStatus;
currentYardId?: string;
/** Drop locos already coupled to a built train (train-builder picker). */
excludeCoupled?: boolean;
/** With excludeCoupled: keep THIS train's own coupled locos in the list. */
excludeTrainId?: string;
}
export interface Locomotive {
@@ -48,6 +52,8 @@ export const locomotivesService = {
const params = new URLSearchParams();
if (filters.status) params.set('status', filters.status);
if (filters.currentYardId) params.set('currentYardId', filters.currentYardId);
if (filters.excludeCoupled) params.set('excludeCoupled', 'true');
if (filters.excludeTrainId) params.set('excludeTrainId', filters.excludeTrainId);
const qs = params.toString();
return apiClient.get<Locomotive[]>(
`${URL_CONSTANTS.LOCOMOTIVES.BASE}${qs ? `?${qs}` : ''}`,

View File

@@ -35,8 +35,9 @@ export interface RouteRecord {
milestones?: RouteMilestone[];
}
/** Segment km are resolved server-side from configured yard distances. */
export interface SaveRoutePayload {
milestones: Array<{ yardId: string; distanceKm?: number }>;
milestones: Array<{ yardId: string }>;
status?: RouteStatus;
}

View File

@@ -86,6 +86,7 @@ const RESOURCE_BASE: Record<RuleEngineResourceSlug, string> = {
"service-types": URL_CONSTANTS.RULE_ENGINE.SERVICE_TYPES,
"weight-limit-rules": URL_CONSTANTS.RULE_ENGINE.WEIGHT_LIMIT_RULES,
yards: URL_CONSTANTS.RULE_ENGINE.YARDS,
"yard-distances": URL_CONSTANTS.RULE_ENGINE.YARD_DISTANCES,
"shipping-lines": URL_CONSTANTS.RULE_ENGINE.SHIPPING_LINES,
rates: URL_CONSTANTS.RULE_ENGINE.RATES,
"approval-rules": URL_CONSTANTS.RULE_ENGINE.APPROVAL_RULES,
@@ -107,6 +108,8 @@ const byIdPath = (resource: RuleEngineResourceSlug, id: string): string => {
return URL_CONSTANTS.RULE_ENGINE.WEIGHT_LIMIT_RULE_BY_ID(id);
case "yards":
return URL_CONSTANTS.RULE_ENGINE.YARD_BY_ID(id);
case "yard-distances":
return URL_CONSTANTS.RULE_ENGINE.YARD_DISTANCE_BY_ID(id);
case "shipping-lines":
return URL_CONSTANTS.RULE_ENGINE.SHIPPING_LINE_BY_ID(id);
case "rates":