feat(train-scheduling): dedicated load/unload permissions

Carves cargo load/unload confirmation out of the coarse
trainScheduling.update permission into its own guard
(TrainSchedulingLoad/TrainSchedulingUnload), covering import, export,
and intercity — the generic per-booking route already serves all
directions, and the intercity-specific route gets the same two keys.
Adds the catalog entries and grants them to operationsOfficer/director
alongside the existing .update grant so current access is unchanged.
This commit is contained in:
Hagernesh
2026-08-22 09:06:11 +00:00
parent 50f442a414
commit e426e0c16e
3 changed files with 46 additions and 5 deletions

View File

@@ -75,7 +75,7 @@ export const TrainSchedulingView = () =>
BookingStaff(FREIGHT_PERMS.trainScheduling.view); BookingStaff(FREIGHT_PERMS.trainScheduling.view);
// Granular train-scheduling actions replace the retired coarse manage: // Granular train-scheduling actions replace the retired coarse manage:
// create a schedule, update (assign/consist/loading/finalize/dispatch/arrive…), // create a schedule, update (assign/consist/finalize/dispatch/arrive…),
// cancel a schedule, reschedule (+ maintenance), and manage global rules. // cancel a schedule, reschedule (+ maintenance), and manage global rules.
export const TrainSchedulingCreate = () => export const TrainSchedulingCreate = () =>
BookingStaff(FREIGHT_PERMS.trainScheduling.create); BookingStaff(FREIGHT_PERMS.trainScheduling.create);
@@ -83,6 +83,18 @@ export const TrainSchedulingCreate = () =>
export const TrainSchedulingUpdate = () => export const TrainSchedulingUpdate = () =>
BookingStaff(FREIGHT_PERMS.trainScheduling.update); BookingStaff(FREIGHT_PERMS.trainScheduling.update);
/**
* Confirm a booking's cargo loaded/unloaded at a yard — carved out of the
* coarse `update` so it can be granted independently of general schedule
* editing. Same two keys gate import, export, and intercity movements alike:
* the generic per-booking route and the intercity-specific one both use them.
*/
export const TrainSchedulingLoad = () =>
BookingStaff(FREIGHT_PERMS.trainScheduling.load);
export const TrainSchedulingUnload = () =>
BookingStaff(FREIGHT_PERMS.trainScheduling.unload);
export const TrainSchedulingCancel = () => export const TrainSchedulingCancel = () =>
BookingStaff(FREIGHT_PERMS.trainScheduling.cancel); BookingStaff(FREIGHT_PERMS.trainScheduling.cancel);

View File

@@ -16,7 +16,9 @@ import {
TrainSchedulingCancel, TrainSchedulingCancel,
TrainSchedulingCreate, TrainSchedulingCreate,
TrainSchedulingEditTrainNumber, TrainSchedulingEditTrainNumber,
TrainSchedulingLoad,
TrainSchedulingReschedule, TrainSchedulingReschedule,
TrainSchedulingUnload,
TrainSchedulingRulesManage, TrainSchedulingRulesManage,
TrainSchedulingUpdate, TrainSchedulingUpdate,
TrainSchedulingView, TrainSchedulingView,
@@ -598,7 +600,7 @@ export class TrainSchedulingController {
} }
@Post("schedules/:id/bookings/:bookingId/load") @Post("schedules/:id/bookings/:bookingId/load")
@TrainSchedulingUpdate() @TrainSchedulingLoad()
@ApiOperation({ @ApiOperation({
summary: summary:
"Confirm a booking's cargo loaded at its origin yard (any direction; train must be at that yard)", "Confirm a booking's cargo loaded at its origin yard (any direction; train must be at that yard)",
@@ -611,7 +613,7 @@ export class TrainSchedulingController {
} }
@Post("schedules/:id/bookings/:bookingId/unload") @Post("schedules/:id/bookings/:bookingId/unload")
@TrainSchedulingUpdate() @TrainSchedulingUnload()
@ApiOperation({ @ApiOperation({
summary: summary:
"Confirm a booking's cargo unloaded at its destination yard — per-booking arrival, may precede the train's final arrival", "Confirm a booking's cargo unloaded at its destination yard — per-booking arrival, may precede the train's final arrival",
@@ -624,7 +626,7 @@ export class TrainSchedulingController {
} }
@Post("schedules/:id/intercity/:bookingId/load") @Post("schedules/:id/intercity/:bookingId/load")
@TrainSchedulingUpdate() @TrainSchedulingLoad()
@ApiOperation({ @ApiOperation({
summary: "Confirm intercity cargo loaded (train must be at the booking's origin yard)", summary: "Confirm intercity cargo loaded (train must be at the booking's origin yard)",
}) })
@@ -636,7 +638,7 @@ export class TrainSchedulingController {
} }
@Post("schedules/:id/intercity/:bookingId/unload") @Post("schedules/:id/intercity/:bookingId/unload")
@TrainSchedulingUpdate() @TrainSchedulingUnload()
@ApiOperation({ @ApiOperation({
summary: summary:
"Confirm intercity cargo unloaded at the booking's destination yard (completes the booking)", "Confirm intercity cargo unloaded at the booking's destination yard (completes the booking)",

View File

@@ -1469,6 +1469,20 @@ export const SCHEDULING_EXTRA_PERMISSIONS: FreightPermissionSeed[] = [
"edr_freight_app:train_scheduling:rules_manage", "edr_freight_app:train_scheduling:rules_manage",
"Manage global scheduling rules", "Manage global scheduling rules",
), ),
// Carved out of the coarse `update` — confirming a booking's cargo loaded/
// unloaded at a yard, across import, export, and intercity movements alike
// (the same schedules/:id/bookings/:bookingId/{load,unload} + intercity
// routes serve all three directions).
perm(
"a2a00001-0001-4000-8000-000000000006",
"edr_freight_app:train_scheduling:load",
"Confirm cargo loaded (import, export, intercity)",
),
perm(
"a2a00001-0001-4000-8000-000000000007",
"edr_freight_app:train_scheduling:unload",
"Confirm cargo unloaded (import, export, intercity)",
),
]; ];
// L. Administration & settings (split from the coarse admin umbrella) // L. Administration & settings (split from the coarse admin umbrella)
@@ -1996,6 +2010,15 @@ export const FREIGHT_PERMS = {
cancel: "edr_freight_app:train_scheduling:cancel", cancel: "edr_freight_app:train_scheduling:cancel",
reschedule: "edr_freight_app:train_scheduling:reschedule", reschedule: "edr_freight_app:train_scheduling:reschedule",
rulesManage: "edr_freight_app:train_scheduling:rules_manage", rulesManage: "edr_freight_app:train_scheduling:rules_manage",
/**
* Confirm a booking's cargo loaded/unloaded at a yard — carved out of the
* coarse `update` so load/unload can be granted independently of general
* schedule editing. Covers import, export, and intercity alike: the
* generic per-booking route and the intercity-specific one both gate on
* these same two keys.
*/
load: "edr_freight_app:train_scheduling:load",
unload: "edr_freight_app:train_scheduling:unload",
dispatch: "edr_freight_app:train_scheduling:dispatch", dispatch: "edr_freight_app:train_scheduling:dispatch",
markPaid: "edr_freight_app:train_scheduling:mark_paid", markPaid: "edr_freight_app:train_scheduling:mark_paid",
expireBooking: "edr_freight_app:train_scheduling:expire_booking", expireBooking: "edr_freight_app:train_scheduling:expire_booking",
@@ -2602,6 +2625,8 @@ export const ROLE_PERMISSION_PRESETS = {
FREIGHT_PERMS.trainScheduling.view, FREIGHT_PERMS.trainScheduling.view,
FREIGHT_PERMS.trainScheduling.create, FREIGHT_PERMS.trainScheduling.create,
FREIGHT_PERMS.trainScheduling.update, FREIGHT_PERMS.trainScheduling.update,
FREIGHT_PERMS.trainScheduling.load,
FREIGHT_PERMS.trainScheduling.unload,
FREIGHT_PERMS.trainScheduling.cancel, FREIGHT_PERMS.trainScheduling.cancel,
FREIGHT_PERMS.trainScheduling.reschedule, FREIGHT_PERMS.trainScheduling.reschedule,
FREIGHT_PERMS.trainScheduling.rulesManage, FREIGHT_PERMS.trainScheduling.rulesManage,
@@ -2822,6 +2847,8 @@ export const POSITION_PERMISSION_PRESETS = {
FREIGHT_PERMS.trainScheduling.view, FREIGHT_PERMS.trainScheduling.view,
FREIGHT_PERMS.trainScheduling.create, FREIGHT_PERMS.trainScheduling.create,
FREIGHT_PERMS.trainScheduling.update, FREIGHT_PERMS.trainScheduling.update,
FREIGHT_PERMS.trainScheduling.load,
FREIGHT_PERMS.trainScheduling.unload,
FREIGHT_PERMS.trainScheduling.cancel, FREIGHT_PERMS.trainScheduling.cancel,
FREIGHT_PERMS.trainScheduling.reschedule, FREIGHT_PERMS.trainScheduling.reschedule,
FREIGHT_PERMS.trainScheduling.rulesManage, FREIGHT_PERMS.trainScheduling.rulesManage,