mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
2677 lines
93 KiB
TypeScript
2677 lines
93 KiB
TypeScript
const EDR_FREIGHT_APP_KEY = "edr_freight_app";
|
||
|
||
export type FreightPermissionSeed = {
|
||
id: string;
|
||
key: string;
|
||
name: { am: string; en: string };
|
||
applicationKey: string;
|
||
};
|
||
|
||
export const RULE_ENGINE_RESOURCE_SLUGS = [
|
||
"cargo-types",
|
||
"container-types",
|
||
"wagon-types",
|
||
"service-types",
|
||
"yards",
|
||
"shipping-lines",
|
||
"weight-limit-rules",
|
||
"priority-configs",
|
||
"rates",
|
||
"approval-rules",
|
||
"yard-distances",
|
||
// Keep new slugs at the END: ruleEngineCrudId derives ids from list index,
|
||
// so a mid-list insert would shift ids already seeded for later slugs.
|
||
"truck-types",
|
||
"transit-agents",
|
||
] as const;
|
||
|
||
export type RuleEngineResourceSlug =
|
||
(typeof RULE_ENGINE_RESOURCE_SLUGS)[number];
|
||
|
||
const slugToResourceKey = (slug: RuleEngineResourceSlug): string =>
|
||
slug.replace(/-/g, "_");
|
||
|
||
const VIEW_KEY_SUFFIX = ":view";
|
||
const READ_KEY_SUFFIX = ":read";
|
||
|
||
/** The `:read` twin of a `:view` key, or null if `key` is not a view key. */
|
||
export const readTwinOf = (key: string): string | null =>
|
||
key.endsWith(VIEW_KEY_SUFFIX)
|
||
? `${key.slice(0, -VIEW_KEY_SUFFIX.length)}${READ_KEY_SUFFIX}`
|
||
: null;
|
||
|
||
const perm = (id: string, key: string, en: string): FreightPermissionSeed => ({
|
||
id,
|
||
key,
|
||
name: { am: en, en },
|
||
applicationKey: EDR_FREIGHT_APP_KEY,
|
||
});
|
||
|
||
/**
|
||
* One entry per report definition (see modules/reports/definitions). Each
|
||
* gets its own permission, gated behind the `reports:view` master key that
|
||
* opens the Reports section itself.
|
||
* Keep new keys at the END: reportPermId derives ids from list index, so a
|
||
* mid-list insert would shift ids already seeded for later keys.
|
||
*/
|
||
export const REPORT_KEYS = [
|
||
"bookings-list",
|
||
"revenue-by-customer",
|
||
"aging-receivables",
|
||
"contract-utilization",
|
||
"wagon-fleet-status",
|
||
"wagon-status-duration",
|
||
"wagon-requests",
|
||
"locomotive-fleet-status",
|
||
"booking-status-breakdown",
|
||
"train-schedule-status",
|
||
"train-turnaround",
|
||
"wagon-teu-utilization",
|
||
"loaded-capacity",
|
||
"global-logistics-wagons",
|
||
"customer-status",
|
||
"contract-lifecycle",
|
||
"customs-documents",
|
||
"invoicing-pipeline",
|
||
"first-last-mile-bookings",
|
||
"invoices-by-status",
|
||
"payments-by-status",
|
||
"revenue-summary",
|
||
"cargo-summary",
|
||
] as const;
|
||
|
||
export type ReportKey = (typeof REPORT_KEYS)[number];
|
||
|
||
export const reportPermissionKey = (key: ReportKey): string =>
|
||
`edr_freight_app:reports:${key.replace(/-/g, "_")}:view`;
|
||
|
||
const reportPermId = (index: number): string =>
|
||
`a4f00002-0001-4000-8000-${(index + 1).toString(16).padStart(12, "0")}`;
|
||
|
||
const titleCase = (slug: string): string =>
|
||
slug.split("-").map((w) => w[0].toUpperCase() + w.slice(1)).join(" ");
|
||
|
||
export const REPORT_PERMISSIONS: FreightPermissionSeed[] = REPORT_KEYS.map(
|
||
(key, index) =>
|
||
perm(reportPermId(index), reportPermissionKey(key), `Report: ${titleCase(key)}`),
|
||
);
|
||
|
||
export const BOOKING_PERMISSIONS: FreightPermissionSeed[] = [
|
||
perm(
|
||
"a1000001-0001-4000-8000-000000000001",
|
||
"edr_freight_app:bookings:view",
|
||
"View bookings",
|
||
),
|
||
perm(
|
||
"a1000001-0001-4000-8000-000000000002",
|
||
"edr_freight_app:bookings:staff_accept",
|
||
"Accept booking intake",
|
||
),
|
||
perm(
|
||
"a1000001-0001-4000-8000-000000000003",
|
||
"edr_freight_app:bookings:request_changes",
|
||
"Request booking changes",
|
||
),
|
||
perm(
|
||
"a1000001-0001-4000-8000-000000000004",
|
||
"edr_freight_app:bookings:reject",
|
||
"Reject booking submission",
|
||
),
|
||
perm(
|
||
"a1000001-0001-4000-8000-000000000005",
|
||
"edr_freight_app:bookings:approve_line_staff",
|
||
"Approve as line staff",
|
||
),
|
||
perm(
|
||
"a1000001-0001-4000-8000-000000000006",
|
||
"edr_freight_app:bookings:approve_director",
|
||
"Approve as director",
|
||
),
|
||
perm(
|
||
"a1000001-0001-4000-8000-000000000007",
|
||
"edr_freight_app:bookings:approve_ceo",
|
||
"Approve as CEO",
|
||
),
|
||
perm(
|
||
"a1000001-0001-4000-8000-000000000008",
|
||
"edr_freight_app:bookings:reject_approval",
|
||
"Reject at approval step",
|
||
),
|
||
perm(
|
||
"a1000001-0001-4000-8000-000000000009",
|
||
"edr_freight_app:bookings:generate_contract",
|
||
"Generate contract",
|
||
),
|
||
perm(
|
||
"a1000001-0001-4000-8000-00000000000a",
|
||
"edr_freight_app:bookings:sign_staff",
|
||
"Staff contract signature",
|
||
),
|
||
perm(
|
||
"a1000001-0001-4000-8000-00000000000d",
|
||
"edr_freight_app:bookings:operations",
|
||
"Booking operations",
|
||
),
|
||
perm(
|
||
"a1000001-0001-4000-8000-00000000000e",
|
||
"edr_freight_app:bookings:cancel",
|
||
"Cancel booking",
|
||
),
|
||
perm(
|
||
"a1000001-0001-4000-8000-000000000023",
|
||
"edr_freight_app:bookings:clearance_view",
|
||
"View customs-clearance queue",
|
||
),
|
||
perm(
|
||
"a1000001-0001-4000-8000-000000000020",
|
||
"edr_freight_app:bookings:review_documents",
|
||
"Review clearance documents",
|
||
),
|
||
perm(
|
||
"a1000001-0001-4000-8000-000000000021",
|
||
"edr_freight_app:bookings:upload_clearance_output",
|
||
"Upload customs output documents",
|
||
),
|
||
perm(
|
||
"a1000001-0001-4000-8000-000000000022",
|
||
"edr_freight_app:bookings:finalize_clearance",
|
||
"Finalize document clearance",
|
||
),
|
||
perm(
|
||
"a1000001-0001-4000-8000-00000000000f",
|
||
"edr_freight_app:train_scheduling:view",
|
||
"View train scheduling",
|
||
),
|
||
perm(
|
||
"a1000001-0001-4000-8000-000000000011",
|
||
"edr_freight_app:fleet:view",
|
||
"View fleet",
|
||
),
|
||
perm(
|
||
"a1000001-0001-4000-8000-000000000012",
|
||
"edr_freight_app:fleet:manage",
|
||
"Manage fleet",
|
||
),
|
||
perm(
|
||
"a1000001-0001-4000-8000-000000000013",
|
||
"edr_freight_app:admin",
|
||
"Freight administration",
|
||
),
|
||
perm(
|
||
"a1000001-0001-4000-8000-000000000024",
|
||
"edr_freight_app:bookings:create",
|
||
"Create booking",
|
||
),
|
||
// Header alarm for the document-review deadline: its own key so only the
|
||
// position types that actually decide operation requests are alerted.
|
||
perm(
|
||
"a1000001-0001-4000-8000-000000000025",
|
||
"edr_freight_app:bookings:doc_review_alert",
|
||
"See document-review deadline alarm",
|
||
),
|
||
// Partial wagon cancellation (paid bookings): staff-side keys. The customer
|
||
// portal needs none — customer actions are ownership-scoped on the API.
|
||
perm(
|
||
"a1000001-0001-4000-8000-000000000026",
|
||
"edr_freight_app:bookings:wagon_cancellation_view",
|
||
"View wagon cancellation history",
|
||
),
|
||
perm(
|
||
"a1000001-0001-4000-8000-000000000027",
|
||
"edr_freight_app:bookings:wagon_cancellation_void",
|
||
"Void a pending wagon cancellation",
|
||
),
|
||
perm(
|
||
"a1000001-0001-4000-8000-000000000028",
|
||
"edr_freight_app:bookings:wagon_cancellation_rebook",
|
||
"Rebook cancelled wagons for a customer",
|
||
),
|
||
];
|
||
|
||
/**
|
||
* Contract-phase permissions (contract–booking separation). Mirror the booking
|
||
* approval/sign/clearance permissions but scoped to the new contracts module.
|
||
*/
|
||
export const CONTRACT_PERMISSIONS: FreightPermissionSeed[] = [
|
||
perm(
|
||
"a3000001-0001-4000-8000-000000000001",
|
||
"edr_freight_app:contracts:view",
|
||
"View contracts",
|
||
),
|
||
// Intake actions are split per freight type (bulk vs container) — fresh ids
|
||
// because the seeder upserts ON CONFLICT (key); reusing the old ids with new
|
||
// keys would PK-collide with the legacy staff_accept/request_changes/reject rows.
|
||
perm(
|
||
"a3000001-0001-4000-8000-000000000011",
|
||
"edr_freight_app:contracts:staff_accept:bulk",
|
||
"Accept bulk contract intake",
|
||
),
|
||
perm(
|
||
"a3000001-0001-4000-8000-000000000012",
|
||
"edr_freight_app:contracts:staff_accept:container",
|
||
"Accept container contract intake",
|
||
),
|
||
perm(
|
||
"a3000001-0001-4000-8000-000000000013",
|
||
"edr_freight_app:contracts:request_changes:bulk",
|
||
"Request bulk contract changes",
|
||
),
|
||
perm(
|
||
"a3000001-0001-4000-8000-000000000014",
|
||
"edr_freight_app:contracts:request_changes:container",
|
||
"Request container contract changes",
|
||
),
|
||
perm(
|
||
"a3000001-0001-4000-8000-000000000015",
|
||
"edr_freight_app:contracts:reject:bulk",
|
||
"Reject bulk contract",
|
||
),
|
||
perm(
|
||
"a3000001-0001-4000-8000-000000000016",
|
||
"edr_freight_app:contracts:reject:container",
|
||
"Reject container contract",
|
||
),
|
||
perm(
|
||
"a3000001-0001-4000-8000-000000000005",
|
||
"edr_freight_app:contracts:approve_line_staff",
|
||
"Approve contract as line staff",
|
||
),
|
||
perm(
|
||
"a3000001-0001-4000-8000-000000000006",
|
||
"edr_freight_app:contracts:approve_director",
|
||
"Approve contract as director",
|
||
),
|
||
perm(
|
||
"a3000001-0001-4000-8000-000000000007",
|
||
"edr_freight_app:contracts:approve_ceo",
|
||
"Approve contract as CEO",
|
||
),
|
||
perm(
|
||
"a3000001-0001-4000-8000-000000000008",
|
||
"edr_freight_app:contracts:generate_contract",
|
||
"Generate contract document",
|
||
),
|
||
// Staff counter-signature is split per freight type too — fresh ids for the
|
||
// same reason as the intake keys above.
|
||
perm(
|
||
"a3000001-0001-4000-8000-000000000017",
|
||
"edr_freight_app:contracts:sign_staff:bulk",
|
||
"Staff contract signature: bulk",
|
||
),
|
||
perm(
|
||
"a3000001-0001-4000-8000-000000000018",
|
||
"edr_freight_app:contracts:sign_staff:container",
|
||
"Staff contract signature: container",
|
||
),
|
||
perm(
|
||
"a3000001-0001-4000-8000-00000000000a",
|
||
"edr_freight_app:contracts:clearance_review",
|
||
"Review pre-booking clearance docs",
|
||
),
|
||
perm(
|
||
"a3000001-0001-4000-8000-00000000000b",
|
||
"edr_freight_app:contracts:finalize_clearance",
|
||
"Finalize pre-booking clearance",
|
||
),
|
||
perm(
|
||
"a3000001-0001-4000-8000-00000000000c",
|
||
"edr_freight_app:contracts:create_booking",
|
||
"GL ET create booking under contract",
|
||
),
|
||
perm(
|
||
"a3000001-0001-4000-8000-00000000000d",
|
||
"edr_freight_app:contracts:ops_clearance_review",
|
||
"Operations review of self-clearance docs (Path A)",
|
||
),
|
||
perm(
|
||
"a3000001-0001-4000-8000-00000000000e",
|
||
"edr_freight_app:contracts:clearance_et_actions",
|
||
"GL Ethiopia phased clearance actions",
|
||
),
|
||
perm(
|
||
"a3000001-0001-4000-8000-00000000000f",
|
||
"edr_freight_app:contracts:clearance_dj_actions",
|
||
"GL Djibouti phased clearance actions",
|
||
),
|
||
perm(
|
||
"a3000001-0001-4000-8000-000000000010",
|
||
"edr_freight_app:contracts:clearance_duty_advise",
|
||
"Advise contract duty/tax",
|
||
),
|
||
// Hazardous contracts get two extra approval steps ahead of the normal chain.
|
||
// Each has its own permission so the two desks are genuinely separate people.
|
||
perm(
|
||
"a3000001-0001-4000-8000-000000000019",
|
||
"edr_freight_app:contracts:hazardous_approval_one",
|
||
"Hazardous approval — first review",
|
||
),
|
||
perm(
|
||
"a3000001-0001-4000-8000-00000000001a",
|
||
"edr_freight_app:contracts:hazardous_approval_two",
|
||
"Hazardous approval — second review",
|
||
),
|
||
// Freeze/unfreeze a signed contract. One key covers both directions — whoever
|
||
// may suspend must be able to lift it again.
|
||
perm(
|
||
"a3000001-0001-4000-8000-00000000001b",
|
||
"edr_freight_app:contracts:suspend",
|
||
"Suspend / resume a signed contract",
|
||
),
|
||
];
|
||
|
||
// Historical ids. EdrOrgSeeder no longer sends them — it upserts on `key` and
|
||
// lets the column default mint the uuid — so these are kept only as a record of
|
||
// which ids each environment already holds. A hand-picked id must still never
|
||
// be recycled from a retired key: `edr_freight_app:rule_engine:truck_types:manage`
|
||
// owned …001b, and reusing it for transit-agents crashed boot with a PK 23505
|
||
// on every environment that still had the retired row.
|
||
const RULE_ENGINE_VIEW_IDS: Record<RuleEngineResourceSlug, string> = {
|
||
"cargo-types": "b2000001-0001-4000-8000-000000000001",
|
||
"container-types": "b2000001-0001-4000-8000-000000000003",
|
||
"wagon-types": "b2000001-0001-4000-8000-000000000015",
|
||
"truck-types": "b2000001-0001-4000-8000-00000000001a",
|
||
"service-types": "b2000001-0001-4000-8000-000000000005",
|
||
yards: "b2000001-0001-4000-8000-000000000007",
|
||
"shipping-lines": "b2000001-0001-4000-8000-000000000009",
|
||
"weight-limit-rules": "b2000001-0001-4000-8000-00000000000b",
|
||
"priority-configs": "b2000001-0001-4000-8000-00000000000f",
|
||
rates: "b2000001-0001-4000-8000-000000000011",
|
||
"approval-rules": "b2000001-0001-4000-8000-000000000013",
|
||
"yard-distances": "b2000001-0001-4000-8000-000000000018",
|
||
"transit-agents": "b2000003-0001-4000-8000-000000000001",
|
||
};
|
||
|
||
// CRUD replaces the retired coarse `:manage`. New ids live in a fresh block
|
||
// (b2000002-…) so a stale `:manage` grant can never silently confer a CRUD
|
||
// action — the migration re-grants create/update/delete explicitly.
|
||
const RULE_ENGINE_CRUD_ACTIONS = ["create", "update", "delete"] as const;
|
||
type RuleEngineCrudAction = (typeof RULE_ENGINE_CRUD_ACTIONS)[number];
|
||
const ruleEngineCrudId = (
|
||
slug: RuleEngineResourceSlug,
|
||
action: RuleEngineCrudAction,
|
||
): string => {
|
||
const n =
|
||
RULE_ENGINE_RESOURCE_SLUGS.indexOf(slug) * 3 +
|
||
RULE_ENGINE_CRUD_ACTIONS.indexOf(action) +
|
||
1; // 1..36
|
||
return `b2000002-0001-4000-8000-${n.toString(16).padStart(12, "0")}`;
|
||
};
|
||
|
||
/**
|
||
* Slugs whose changes go through a separate approver. CRUD lets a staff member
|
||
* propose a change; only `approve` lets someone put it into effect. Only listed
|
||
* slugs get the permission.
|
||
*/
|
||
const RULE_ENGINE_APPROVE_PERMISSION_IDS: Partial<
|
||
Record<RuleEngineResourceSlug, string>
|
||
> = {
|
||
rates: "b2000001-0001-4000-8000-000000000017",
|
||
};
|
||
|
||
export type RuleEngineApprovableSlug = "rates";
|
||
|
||
export const RULE_ENGINE_PERMISSIONS: FreightPermissionSeed[] =
|
||
RULE_ENGINE_RESOURCE_SLUGS.flatMap((slug) => {
|
||
const resource = slugToResourceKey(slug);
|
||
const approveId = RULE_ENGINE_APPROVE_PERMISSION_IDS[slug];
|
||
return [
|
||
perm(
|
||
RULE_ENGINE_VIEW_IDS[slug],
|
||
`edr_freight_app:rule_engine:${resource}:view`,
|
||
`View ${slug}`,
|
||
),
|
||
perm(
|
||
ruleEngineCrudId(slug, "create"),
|
||
`edr_freight_app:rule_engine:${resource}:create`,
|
||
`Create ${slug}`,
|
||
),
|
||
perm(
|
||
ruleEngineCrudId(slug, "update"),
|
||
`edr_freight_app:rule_engine:${resource}:update`,
|
||
`Update ${slug}`,
|
||
),
|
||
perm(
|
||
ruleEngineCrudId(slug, "delete"),
|
||
`edr_freight_app:rule_engine:${resource}:delete`,
|
||
`Delete ${slug}`,
|
||
),
|
||
...(approveId
|
||
? [
|
||
perm(
|
||
approveId,
|
||
`edr_freight_app:rule_engine:${resource}:approve`,
|
||
`Approve ${slug} changes`,
|
||
),
|
||
]
|
||
: []),
|
||
];
|
||
});
|
||
|
||
/**
|
||
* Container-allocation permission for the previously-unguarded
|
||
* booking allocate-containers endpoint.
|
||
*/
|
||
export const GAP_CONTROLLER_PERMISSIONS: FreightPermissionSeed[] = [
|
||
perm(
|
||
"c1000001-0001-4000-8000-000000000001",
|
||
"edr_freight_app:allocation:manage",
|
||
"Allocate containers to vehicles",
|
||
),
|
||
];
|
||
|
||
/**
|
||
* Advanced backoffice resources — full CRUD + workflow-action keys.
|
||
* See docs/rbac/freight-backoffice-permissions.md. Additive only: the existing
|
||
* bookings/contracts/rule-engine/allocation keys above are unchanged.
|
||
*/
|
||
|
||
// C. Customers
|
||
export const CUSTOMER_PERMISSIONS: FreightPermissionSeed[] = [
|
||
perm(
|
||
"d1a00001-0001-4000-8000-000000000001",
|
||
"edr_freight_app:customers:view",
|
||
"View customers",
|
||
),
|
||
perm(
|
||
"d1a00001-0001-4000-8000-000000000002",
|
||
"edr_freight_app:customers:create",
|
||
"Create customer",
|
||
),
|
||
perm(
|
||
"d1a00001-0001-4000-8000-000000000003",
|
||
"edr_freight_app:customers:update",
|
||
"Update customer",
|
||
),
|
||
perm(
|
||
"d1a00001-0001-4000-8000-000000000004",
|
||
"edr_freight_app:customers:deactivate",
|
||
"Deactivate customer",
|
||
),
|
||
perm(
|
||
"d1a00001-0001-4000-8000-000000000005",
|
||
"edr_freight_app:customers:verify",
|
||
"Verify customer (KYC/Fayda)",
|
||
),
|
||
perm(
|
||
"d1a00001-0001-4000-8000-000000000006",
|
||
"edr_freight_app:customers:reset-password",
|
||
"Trigger customer password reset",
|
||
),
|
||
];
|
||
|
||
// C2. Shipping lines — carriers registered by staff (no self-signup).
|
||
export const SHIPPING_LINE_PERMISSIONS: FreightPermissionSeed[] = [
|
||
perm(
|
||
"d1a00002-0001-4000-8000-000000000001",
|
||
"edr_freight_app:shipping_lines:view",
|
||
"View shipping lines",
|
||
),
|
||
perm(
|
||
"d1a00002-0001-4000-8000-000000000002",
|
||
"edr_freight_app:shipping_lines:create",
|
||
"Register shipping line",
|
||
),
|
||
perm(
|
||
"d1a00002-0001-4000-8000-000000000003",
|
||
"edr_freight_app:shipping_lines:update",
|
||
"Update shipping line",
|
||
),
|
||
perm(
|
||
"d1a00002-0001-4000-8000-000000000004",
|
||
"edr_freight_app:shipping_lines:reset-password",
|
||
"Resend shipping line activation link",
|
||
),
|
||
];
|
||
|
||
// D. Finance — payments + invoices
|
||
export const FINANCE_PERMISSIONS: FreightPermissionSeed[] = [
|
||
perm(
|
||
"d2a00001-0001-4000-8000-000000000001",
|
||
"edr_freight_app:payments:view",
|
||
"View payments",
|
||
),
|
||
perm(
|
||
"d2b00001-0001-4000-8000-000000000001",
|
||
"edr_freight_app:invoices:view",
|
||
"View invoices",
|
||
),
|
||
perm(
|
||
"d2b00001-0001-4000-8000-000000000004",
|
||
"edr_freight_app:invoices:export",
|
||
"Download invoice document",
|
||
),
|
||
// Filing with the tax authority is its own grant: registration is irreversible at MoR, so it
|
||
// must not ride along with the right to download an invoice PDF.
|
||
perm(
|
||
"d2b00001-0001-4000-8000-000000000005",
|
||
"edr_freight_app:invoices:eims_register",
|
||
"Register invoice with MoR EIMS",
|
||
),
|
||
// Separate from registering: resolving an unacknowledged submission clears the
|
||
// system-wide chain block and can record an IRN against an invoice, so it is a
|
||
// supervisor/admin action rather than an operational one.
|
||
perm(
|
||
"d2b00001-0001-4000-8000-000000000006",
|
||
"edr_freight_app:invoices:eims_resolve",
|
||
"Resolve a blocked MoR EIMS submission",
|
||
),
|
||
// Cancellation is a separate irreversible-at-MoR action from registration — its own grant,
|
||
// same reasoning as eims_register.
|
||
perm(
|
||
"d2b00001-0001-4000-8000-000000000008",
|
||
"edr_freight_app:invoices:eims_cancel",
|
||
"Cancel a registered invoice with MoR EIMS",
|
||
),
|
||
// Covers both sales and withholding receipts — same risk profile (filing a document with
|
||
// MoR), no reason to split further.
|
||
perm(
|
||
"d2b00001-0001-4000-8000-000000000009",
|
||
"edr_freight_app:invoices:eims_receipt_register",
|
||
"Register a sales or withholding receipt with MoR EIMS",
|
||
),
|
||
// Issuing a credit/debit memo is itself filing-equivalent — auto-submit picks it up like any
|
||
// other issued invoice — so it carries the same restricted grant as the eims_* actions above,
|
||
// not invoices:export.
|
||
perm(
|
||
"d2b00001-0001-4000-8000-00000000000a",
|
||
"edr_freight_app:invoices:memo_issue",
|
||
"Issue a credit or debit memo against a registered invoice",
|
||
),
|
||
// USD bookings are paid by bank transfer; Finance uploads the slip and settles
|
||
// the invoice. Moves money state, so it is its own grant, not part of view.
|
||
perm(
|
||
"d2b00001-0001-4000-8000-000000000007",
|
||
"edr_freight_app:invoices:confirm_offline",
|
||
"Confirm offline (bank transfer) invoice payment",
|
||
),
|
||
// Shipping lines consume services on credit and are invoiced after the fact,
|
||
// so what they owe is its own Finance surface, separate from invoices:view —
|
||
// an unbilled credit is not an invoice yet.
|
||
perm(
|
||
"d2c00001-0001-4000-8000-000000000001",
|
||
"edr_freight_app:shipping_line_credits:view",
|
||
"View shipping-line credits and outstanding balance",
|
||
),
|
||
perm(
|
||
"d2c00001-0001-4000-8000-000000000002",
|
||
"edr_freight_app:shipping_line_credits:invoice",
|
||
"Generate an invoice from shipping-line credits",
|
||
),
|
||
// Erases a debt outright, which is why it is not folded into :invoice.
|
||
perm(
|
||
"d2c00001-0001-4000-8000-000000000003",
|
||
"edr_freight_app:shipping_line_credits:cancel",
|
||
"Cancel (write off) an unbilled shipping-line credit",
|
||
),
|
||
// Two-step manual actions on credit invoices: request grants per action,
|
||
// decision grants that apply to any pending request.
|
||
perm(
|
||
"d2c00001-0001-4000-8000-000000000004",
|
||
"edr_freight_app:shipping_line_credits:invoice_mark_paid",
|
||
"Request marking a shipping-line credit invoice paid (offline payment)",
|
||
),
|
||
perm(
|
||
"d2c00001-0001-4000-8000-000000000005",
|
||
"edr_freight_app:shipping_line_credits:invoice_approve",
|
||
"Approve any pending shipping-line credit invoice request",
|
||
),
|
||
perm(
|
||
"d2c00001-0001-4000-8000-000000000006",
|
||
"edr_freight_app:shipping_line_credits:invoice_cancel",
|
||
"Request cancelling a shipping-line credit invoice",
|
||
),
|
||
perm(
|
||
"d2c00001-0001-4000-8000-000000000007",
|
||
"edr_freight_app:shipping_line_credits:invoice_reject",
|
||
"Reject any pending shipping-line credit invoice request",
|
||
),
|
||
];
|
||
|
||
// E. First / last mile operations
|
||
export const MILE_PERMISSIONS: FreightPermissionSeed[] = [
|
||
perm(
|
||
"d3a00001-0001-4000-8000-000000000001",
|
||
"edr_freight_app:first_mile:view",
|
||
"View first-mile",
|
||
),
|
||
perm(
|
||
"d3a00001-0001-4000-8000-000000000002",
|
||
"edr_freight_app:first_mile:accept",
|
||
"Accept first-mile request",
|
||
),
|
||
perm(
|
||
"d3a00001-0001-4000-8000-000000000003",
|
||
"edr_freight_app:first_mile:create",
|
||
"Create first-mile",
|
||
),
|
||
perm(
|
||
"d3a00001-0001-4000-8000-000000000004",
|
||
"edr_freight_app:first_mile:update",
|
||
"Update first-mile",
|
||
),
|
||
perm(
|
||
"d3a00001-0001-4000-8000-000000000005",
|
||
"edr_freight_app:first_mile:delete",
|
||
"Delete first-mile",
|
||
),
|
||
perm(
|
||
"d3a00001-0001-4000-8000-000000000006",
|
||
"edr_freight_app:first_mile:assign_vehicles",
|
||
"Assign first-mile vehicles",
|
||
),
|
||
perm(
|
||
"d3a00001-0001-4000-8000-000000000007",
|
||
"edr_freight_app:first_mile:set_distances",
|
||
"Set first-mile distances",
|
||
),
|
||
perm(
|
||
"d3a00001-0001-4000-8000-000000000008",
|
||
"edr_freight_app:first_mile:generate_invoice",
|
||
"Generate first-mile invoice",
|
||
),
|
||
perm(
|
||
"d3b00001-0001-4000-8000-000000000001",
|
||
"edr_freight_app:last_mile:view",
|
||
"View last-mile",
|
||
),
|
||
perm(
|
||
"d3b00001-0001-4000-8000-000000000002",
|
||
"edr_freight_app:last_mile:accept",
|
||
"Accept last-mile request",
|
||
),
|
||
perm(
|
||
"d3b00001-0001-4000-8000-000000000003",
|
||
"edr_freight_app:last_mile:create",
|
||
"Create last-mile",
|
||
),
|
||
perm(
|
||
"d3b00001-0001-4000-8000-000000000004",
|
||
"edr_freight_app:last_mile:update",
|
||
"Update last-mile",
|
||
),
|
||
perm(
|
||
"d3b00001-0001-4000-8000-000000000005",
|
||
"edr_freight_app:last_mile:delete",
|
||
"Delete last-mile",
|
||
),
|
||
perm(
|
||
"d3b00001-0001-4000-8000-000000000006",
|
||
"edr_freight_app:last_mile:assign_vehicles",
|
||
"Assign last-mile vehicles",
|
||
),
|
||
perm(
|
||
"d3b00001-0001-4000-8000-000000000007",
|
||
"edr_freight_app:last_mile:set_distances",
|
||
"Set last-mile distances",
|
||
),
|
||
perm(
|
||
"d3b00001-0001-4000-8000-000000000008",
|
||
"edr_freight_app:last_mile:generate_invoice",
|
||
"Generate last-mile invoice",
|
||
),
|
||
perm(
|
||
"d3b00001-0001-4000-8000-000000000009",
|
||
"edr_freight_app:last_mile:request_view",
|
||
"View last-mile confirmation requests",
|
||
),
|
||
perm(
|
||
"d3b00001-0001-4000-8000-00000000000a",
|
||
"edr_freight_app:last_mile:request_review",
|
||
"Review last-mile confirmation requests (T&M dept)",
|
||
),
|
||
perm(
|
||
"d3b00001-0001-4000-8000-00000000000b",
|
||
"edr_freight_app:last_mile:request_approve",
|
||
"Approve/reject last-mile confirmation requests",
|
||
),
|
||
];
|
||
|
||
// F. Fleet — rail assets (splits the flat fleet:view/manage)
|
||
export const FLEET_RAIL_PERMISSIONS: FreightPermissionSeed[] = [
|
||
perm(
|
||
"e1a00001-0001-4000-8000-000000000001",
|
||
"edr_freight_app:locomotives:view",
|
||
"View locomotives",
|
||
),
|
||
perm(
|
||
"e1a00001-0001-4000-8000-000000000002",
|
||
"edr_freight_app:locomotives:create",
|
||
"Create locomotive",
|
||
),
|
||
perm(
|
||
"e1a00001-0001-4000-8000-000000000003",
|
||
"edr_freight_app:locomotives:update",
|
||
"Update locomotive",
|
||
),
|
||
perm(
|
||
"e1a00001-0001-4000-8000-000000000004",
|
||
"edr_freight_app:locomotives:delete",
|
||
"Delete locomotive",
|
||
),
|
||
perm(
|
||
"e1a00001-0001-4000-8000-000000000005",
|
||
"edr_freight_app:locomotives:hard_delete",
|
||
"Permanently delete locomotive",
|
||
),
|
||
perm(
|
||
"e1b00001-0001-4000-8000-000000000001",
|
||
"edr_freight_app:wagons:view",
|
||
"View wagons",
|
||
),
|
||
perm(
|
||
"e1b00001-0001-4000-8000-000000000002",
|
||
"edr_freight_app:wagons:create",
|
||
"Create wagon",
|
||
),
|
||
perm(
|
||
"e1b00001-0001-4000-8000-000000000003",
|
||
"edr_freight_app:wagons:update",
|
||
"Update wagon",
|
||
),
|
||
perm(
|
||
"e1b00001-0001-4000-8000-000000000004",
|
||
"edr_freight_app:wagons:delete",
|
||
"Delete wagon",
|
||
),
|
||
perm(
|
||
"e1b00001-0001-4000-8000-000000000005",
|
||
"edr_freight_app:wagons:transfer_request",
|
||
"Request wagon transfer",
|
||
),
|
||
perm(
|
||
"e1b00001-0001-4000-8000-000000000006",
|
||
"edr_freight_app:wagons:transfer_fulfill",
|
||
"Fulfil wagon transfer (OCC)",
|
||
),
|
||
perm(
|
||
"e1b00001-0001-4000-8000-000000000007",
|
||
"edr_freight_app:wagons:transfer_history_all",
|
||
"View all staff's transfer history",
|
||
),
|
||
// The transfer desk is its own screen, so it carries its own per-action keys —
|
||
// seeing the queue, withdrawing a request and short-closing one are separate
|
||
// grants from filing or fulfilling.
|
||
perm(
|
||
"e1b00001-0001-4000-8000-000000000008",
|
||
"edr_freight_app:wagons:transfer_view",
|
||
"View wagon transfer requests",
|
||
),
|
||
perm(
|
||
"e1b00001-0001-4000-8000-000000000009",
|
||
"edr_freight_app:wagons:transfer_cancel",
|
||
"Withdraw a wagon transfer request",
|
||
),
|
||
perm(
|
||
"e1b00001-0001-4000-8000-00000000000a",
|
||
"edr_freight_app:wagons:transfer_close_short",
|
||
"Close a transfer request short of the requested count",
|
||
),
|
||
perm(
|
||
"e1b00001-0001-4000-8000-00000000000b",
|
||
"edr_freight_app:wagons:hard_delete",
|
||
"Permanently delete wagon",
|
||
),
|
||
// Maintenance ⇄ availability flip on the wagons desk — its own key so
|
||
// operations/OCC can flip readiness without holding full wagon edit.
|
||
perm(
|
||
"e1b00001-0001-4000-8000-00000000000c",
|
||
"edr_freight_app:wagons:status_toggle",
|
||
"Flip wagon between maintenance and available",
|
||
),
|
||
perm(
|
||
"e1c00001-0001-4000-8000-000000000001",
|
||
"edr_freight_app:trains:view",
|
||
"View trains",
|
||
),
|
||
perm(
|
||
"e1c00001-0001-4000-8000-000000000002",
|
||
"edr_freight_app:trains:create",
|
||
"Create train",
|
||
),
|
||
perm(
|
||
"e1c00001-0001-4000-8000-000000000003",
|
||
"edr_freight_app:trains:update",
|
||
"Update train",
|
||
),
|
||
perm(
|
||
"e1c00001-0001-4000-8000-000000000004",
|
||
"edr_freight_app:trains:delete",
|
||
"Delete train",
|
||
),
|
||
perm(
|
||
"e1c00001-0001-4000-8000-000000000005",
|
||
"edr_freight_app:trains:assign_wagons",
|
||
"Assign wagons to train",
|
||
),
|
||
// Granular splits of trains:update / trains:delete for the train-builder
|
||
// detail page's Actions menu — each item gets its own grant instead of
|
||
// sharing the coarse update/delete keys.
|
||
perm(
|
||
"e1c00001-0001-4000-8000-000000000006",
|
||
"edr_freight_app:trains:change_locomotives",
|
||
"Change train locomotives",
|
||
),
|
||
perm(
|
||
"e1c00001-0001-4000-8000-000000000007",
|
||
"edr_freight_app:trains:change_yard",
|
||
"Change train yard",
|
||
),
|
||
perm(
|
||
"e1c00001-0001-4000-8000-000000000008",
|
||
"edr_freight_app:trains:toggle_active",
|
||
"Activate or deactivate train",
|
||
),
|
||
perm(
|
||
"e1c00001-0001-4000-8000-000000000009",
|
||
"edr_freight_app:trains:disband",
|
||
"Disband train",
|
||
),
|
||
perm(
|
||
"e1d00001-0001-4000-8000-000000000001",
|
||
"edr_freight_app:routes:view",
|
||
"View routes",
|
||
),
|
||
perm(
|
||
"e1d00001-0001-4000-8000-000000000002",
|
||
"edr_freight_app:routes:create",
|
||
"Create route",
|
||
),
|
||
perm(
|
||
"e1d00001-0001-4000-8000-000000000003",
|
||
"edr_freight_app:routes:update",
|
||
"Update route",
|
||
),
|
||
perm(
|
||
"e1d00001-0001-4000-8000-000000000004",
|
||
"edr_freight_app:routes:delete",
|
||
"Delete route",
|
||
),
|
||
perm(
|
||
"e1d00001-0001-4000-8000-000000000005",
|
||
"edr_freight_app:routes:hard_delete",
|
||
"Permanently delete route",
|
||
),
|
||
perm(
|
||
"e1e00001-0001-4000-8000-000000000001",
|
||
"edr_freight_app:containers:view",
|
||
"View containers",
|
||
),
|
||
perm(
|
||
"e1e00001-0001-4000-8000-000000000002",
|
||
"edr_freight_app:containers:create",
|
||
"Create container",
|
||
),
|
||
perm(
|
||
"e1e00001-0001-4000-8000-000000000003",
|
||
"edr_freight_app:containers:update",
|
||
"Update container",
|
||
),
|
||
perm(
|
||
"e1e00001-0001-4000-8000-000000000004",
|
||
"edr_freight_app:containers:delete",
|
||
"Delete container",
|
||
),
|
||
perm(
|
||
"e1f00001-0001-4000-8000-000000000001",
|
||
"edr_freight_app:cargoes:view",
|
||
"View cargoes",
|
||
),
|
||
perm(
|
||
"e1f00001-0001-4000-8000-000000000002",
|
||
"edr_freight_app:cargoes:create",
|
||
"Create cargo",
|
||
),
|
||
perm(
|
||
"e1f00001-0001-4000-8000-000000000003",
|
||
"edr_freight_app:cargoes:update",
|
||
"Update cargo",
|
||
),
|
||
perm(
|
||
"e1f00001-0001-4000-8000-000000000004",
|
||
"edr_freight_app:cargoes:delete",
|
||
"Delete cargo",
|
||
),
|
||
// NB: id prefixes must stay hex — 'e1g…' once crashed the boot seeder
|
||
// (postgres: invalid input syntax for type uuid).
|
||
perm(
|
||
"e1900001-0001-4000-8000-000000000001",
|
||
"edr_freight_app:consignments:view",
|
||
"View consignments",
|
||
),
|
||
perm(
|
||
"e1900001-0001-4000-8000-000000000002",
|
||
"edr_freight_app:consignments:create",
|
||
"Create consignment",
|
||
),
|
||
];
|
||
|
||
// G. Fleet — road & telemetry
|
||
export const FLEET_ROAD_PERMISSIONS: FreightPermissionSeed[] = [
|
||
perm(
|
||
"e2a00001-0001-4000-8000-000000000001",
|
||
"edr_freight_app:vehicles:view",
|
||
"View vehicles",
|
||
),
|
||
perm(
|
||
"e2a00001-0001-4000-8000-000000000002",
|
||
"edr_freight_app:vehicles:create",
|
||
"Create vehicle",
|
||
),
|
||
perm(
|
||
"e2a00001-0001-4000-8000-000000000003",
|
||
"edr_freight_app:vehicles:update",
|
||
"Update vehicle",
|
||
),
|
||
perm(
|
||
"e2a00001-0001-4000-8000-000000000004",
|
||
"edr_freight_app:vehicles:delete",
|
||
"Delete vehicle",
|
||
),
|
||
perm(
|
||
"e2b00001-0001-4000-8000-000000000001",
|
||
"edr_freight_app:drivers:view",
|
||
"View drivers",
|
||
),
|
||
perm(
|
||
"e2b00001-0001-4000-8000-000000000002",
|
||
"edr_freight_app:drivers:create",
|
||
"Create driver",
|
||
),
|
||
perm(
|
||
"e2b00001-0001-4000-8000-000000000003",
|
||
"edr_freight_app:drivers:update",
|
||
"Update driver",
|
||
),
|
||
perm(
|
||
"e2b00001-0001-4000-8000-000000000004",
|
||
"edr_freight_app:drivers:delete",
|
||
"Delete driver",
|
||
),
|
||
perm(
|
||
"e2c00001-0001-4000-8000-000000000001",
|
||
"edr_freight_app:tracking:view",
|
||
"Track vehicles",
|
||
),
|
||
perm(
|
||
"e2c00001-0001-4000-8000-000000000002",
|
||
"edr_freight_app:tracking:manage",
|
||
"Manage GPS trackers",
|
||
),
|
||
perm(
|
||
"e2d00001-0001-4000-8000-000000000001",
|
||
"edr_freight_app:fuel:view",
|
||
"View fuel purchases",
|
||
),
|
||
perm(
|
||
"e2d00001-0001-4000-8000-000000000002",
|
||
"edr_freight_app:fuel:create",
|
||
"Create fuel purchase",
|
||
),
|
||
perm(
|
||
"e2d00001-0001-4000-8000-000000000003",
|
||
"edr_freight_app:fuel:update",
|
||
"Update fuel purchase",
|
||
),
|
||
perm(
|
||
"e2d00001-0001-4000-8000-000000000004",
|
||
"edr_freight_app:fuel:delete",
|
||
"Delete fuel purchase",
|
||
),
|
||
perm(
|
||
"e2e00001-0001-4000-8000-000000000001",
|
||
"edr_freight_app:maintenance:view",
|
||
"View maintenance",
|
||
),
|
||
perm(
|
||
"e2e00001-0001-4000-8000-000000000002",
|
||
"edr_freight_app:maintenance:create",
|
||
"Create maintenance",
|
||
),
|
||
perm(
|
||
"e2e00001-0001-4000-8000-000000000003",
|
||
"edr_freight_app:maintenance:update",
|
||
"Update maintenance",
|
||
),
|
||
perm(
|
||
"e2e00001-0001-4000-8000-000000000004",
|
||
"edr_freight_app:maintenance:delete",
|
||
"Delete maintenance",
|
||
),
|
||
perm(
|
||
"e2f00001-0001-4000-8000-000000000001",
|
||
"edr_freight_app:fleet_reports:view",
|
||
"View fleet financial reports",
|
||
),
|
||
perm(
|
||
"e2f00001-0001-4000-8000-000000000002",
|
||
"edr_freight_app:fleet_reports:export",
|
||
"Export fleet financial reports",
|
||
),
|
||
perm(
|
||
"e2000001-0001-4000-8000-000000000001",
|
||
"edr_freight_app:fleet_dashboard:view",
|
||
"View fleet dashboard",
|
||
),
|
||
];
|
||
|
||
// H. Warehouse management
|
||
export const WAREHOUSE_PERMISSIONS: FreightPermissionSeed[] = [
|
||
perm(
|
||
"f1000001-0001-4000-8000-000000000001",
|
||
"edr_freight_app:warehouse_dashboard:view",
|
||
"View warehouse dashboard",
|
||
),
|
||
perm(
|
||
"f1a00001-0001-4000-8000-000000000001",
|
||
"edr_freight_app:warehouses:view",
|
||
"View warehouses",
|
||
),
|
||
perm(
|
||
"f1a00001-0001-4000-8000-000000000002",
|
||
"edr_freight_app:warehouses:create",
|
||
"Create warehouse",
|
||
),
|
||
perm(
|
||
"f1a00001-0001-4000-8000-000000000003",
|
||
"edr_freight_app:warehouses:update",
|
||
"Update warehouse",
|
||
),
|
||
perm(
|
||
"f1a00001-0001-4000-8000-000000000004",
|
||
"edr_freight_app:warehouses:delete",
|
||
"Delete warehouse",
|
||
),
|
||
perm(
|
||
"f1b00001-0001-4000-8000-000000000001",
|
||
"edr_freight_app:warehouse_yards:view",
|
||
"View warehouse yards",
|
||
),
|
||
perm(
|
||
"f1b00001-0001-4000-8000-000000000002",
|
||
"edr_freight_app:warehouse_yards:create",
|
||
"Create warehouse yard",
|
||
),
|
||
perm(
|
||
"f1b00001-0001-4000-8000-000000000003",
|
||
"edr_freight_app:warehouse_yards:update",
|
||
"Update warehouse yard",
|
||
),
|
||
perm(
|
||
"f1b00001-0001-4000-8000-000000000004",
|
||
"edr_freight_app:warehouse_yards:delete",
|
||
"Delete warehouse yard",
|
||
),
|
||
perm(
|
||
"f1c00001-0001-4000-8000-000000000001",
|
||
"edr_freight_app:warehouse_zones:view",
|
||
"View warehouse zones",
|
||
),
|
||
perm(
|
||
"f1c00001-0001-4000-8000-000000000002",
|
||
"edr_freight_app:warehouse_zones:create",
|
||
"Create warehouse zone",
|
||
),
|
||
perm(
|
||
"f1c00001-0001-4000-8000-000000000003",
|
||
"edr_freight_app:warehouse_zones:update",
|
||
"Update warehouse zone",
|
||
),
|
||
perm(
|
||
"f1d00001-0001-4000-8000-000000000001",
|
||
"edr_freight_app:warehouse_allocation_rules:view",
|
||
"View allocation rules",
|
||
),
|
||
perm(
|
||
"f1d00001-0001-4000-8000-000000000002",
|
||
"edr_freight_app:warehouse_allocation_rules:create",
|
||
"Create allocation rule",
|
||
),
|
||
perm(
|
||
"f1d00001-0001-4000-8000-000000000003",
|
||
"edr_freight_app:warehouse_allocation_rules:update",
|
||
"Update allocation rule",
|
||
),
|
||
perm(
|
||
"f1d00001-0001-4000-8000-000000000004",
|
||
"edr_freight_app:warehouse_allocation_rules:delete",
|
||
"Delete allocation rule",
|
||
),
|
||
perm(
|
||
"f1e00001-0001-4000-8000-000000000001",
|
||
"edr_freight_app:warehouse_fee_rules:view",
|
||
"View fee rules",
|
||
),
|
||
perm(
|
||
"f1e00001-0001-4000-8000-000000000002",
|
||
"edr_freight_app:warehouse_fee_rules:create",
|
||
"Create fee rule",
|
||
),
|
||
perm(
|
||
"f1e00001-0001-4000-8000-000000000003",
|
||
"edr_freight_app:warehouse_fee_rules:update",
|
||
"Update fee rule",
|
||
),
|
||
perm(
|
||
"f1e00001-0001-4000-8000-000000000004",
|
||
"edr_freight_app:warehouse_fee_rules:delete",
|
||
"Delete fee rule",
|
||
),
|
||
perm(
|
||
"f1f00001-0001-4000-8000-000000000001",
|
||
"edr_freight_app:warehouse_inspection_reports:view",
|
||
"View inspection reports",
|
||
),
|
||
perm(
|
||
"f1f00001-0001-4000-8000-000000000002",
|
||
"edr_freight_app:warehouse_inspection_reports:create",
|
||
"Create inspection report",
|
||
),
|
||
perm(
|
||
"f1f00001-0001-4000-8000-000000000003",
|
||
"edr_freight_app:warehouse_inspection_reports:update",
|
||
"Update inspection report",
|
||
),
|
||
];
|
||
|
||
// I. Port & terminal — inventory movement + interchange + fee invoices
|
||
export const PORT_TERMINAL_PERMISSIONS: FreightPermissionSeed[] = [
|
||
perm(
|
||
"f2a00001-0001-4000-8000-000000000001",
|
||
"edr_freight_app:warehouse_inventory:view",
|
||
"View terminal inventory",
|
||
),
|
||
perm(
|
||
"f2a00001-0001-4000-8000-000000000002",
|
||
"edr_freight_app:warehouse_inventory:receive",
|
||
"Receive inventory",
|
||
),
|
||
perm(
|
||
"f2a00001-0001-4000-8000-000000000003",
|
||
"edr_freight_app:warehouse_inventory:move",
|
||
"Move/store/reserve inventory",
|
||
),
|
||
perm(
|
||
"f2a00001-0001-4000-8000-000000000004",
|
||
"edr_freight_app:warehouse_inventory:load",
|
||
"Load inventory",
|
||
),
|
||
perm(
|
||
"f2a00001-0001-4000-8000-000000000005",
|
||
"edr_freight_app:warehouse_inventory:unload",
|
||
"Unload inventory",
|
||
),
|
||
perm(
|
||
"f2a00001-0001-4000-8000-000000000006",
|
||
"edr_freight_app:warehouse_inventory:dispatch",
|
||
"Dispatch inventory",
|
||
),
|
||
perm(
|
||
"f2a00001-0001-4000-8000-000000000007",
|
||
"edr_freight_app:warehouse_inventory:gate_pass",
|
||
"Gate-clearance inventory",
|
||
),
|
||
perm(
|
||
"f2a00001-0001-4000-8000-000000000008",
|
||
"edr_freight_app:warehouse_inventory:release",
|
||
"Release inventory",
|
||
),
|
||
perm(
|
||
"f2a00001-0001-4000-8000-000000000009",
|
||
"edr_freight_app:warehouse_inventory:deliver",
|
||
"Deliver inventory",
|
||
),
|
||
perm(
|
||
"f2a00001-0001-4000-8000-00000000000a",
|
||
"edr_freight_app:warehouse_inventory:inspect",
|
||
"Inspect inventory",
|
||
),
|
||
perm(
|
||
"f2b00001-0001-4000-8000-000000000001",
|
||
"edr_freight_app:interchange_documents:view",
|
||
"View interchange documents",
|
||
),
|
||
perm(
|
||
"f2b00001-0001-4000-8000-000000000002",
|
||
"edr_freight_app:interchange_documents:generate",
|
||
"Generate interchange document",
|
||
),
|
||
perm(
|
||
"f2b00001-0001-4000-8000-000000000003",
|
||
"edr_freight_app:interchange_documents:acknowledge",
|
||
"Acknowledge interchange document",
|
||
),
|
||
perm(
|
||
"f2b00001-0001-4000-8000-000000000004",
|
||
"edr_freight_app:interchange_documents:dispute",
|
||
"Dispute interchange document",
|
||
),
|
||
perm(
|
||
"f2b00001-0001-4000-8000-000000000005",
|
||
"edr_freight_app:interchange_documents:cancel",
|
||
"Cancel interchange document",
|
||
),
|
||
perm(
|
||
"f2c00001-0001-4000-8000-000000000001",
|
||
"edr_freight_app:warehouse_fee_invoices:view",
|
||
"View warehouse fee invoices",
|
||
),
|
||
perm(
|
||
"f2c00001-0001-4000-8000-000000000002",
|
||
"edr_freight_app:warehouse_fee_invoices:generate",
|
||
"Generate warehouse fee invoice",
|
||
),
|
||
perm(
|
||
"f2c00001-0001-4000-8000-000000000003",
|
||
"edr_freight_app:warehouse_fee_invoices:cancel",
|
||
"Cancel warehouse fee invoice",
|
||
),
|
||
perm(
|
||
"f2c00001-0001-4000-8000-000000000004",
|
||
"edr_freight_app:warehouse_fee_invoices:pay",
|
||
"Pay warehouse fee invoice",
|
||
),
|
||
];
|
||
|
||
// E'. Train-scheduling finer actions (augment existing view/manage)
|
||
export const SCHEDULING_EXTRA_PERMISSIONS: FreightPermissionSeed[] = [
|
||
perm(
|
||
"a2a00001-0001-4000-8000-000000000001",
|
||
"edr_freight_app:train_scheduling:create",
|
||
"Create train schedule",
|
||
),
|
||
perm(
|
||
"a2a00001-0001-4000-8000-000000000002",
|
||
"edr_freight_app:train_scheduling:update",
|
||
"Update train schedule",
|
||
),
|
||
perm(
|
||
"a2a00001-0001-4000-8000-000000000003",
|
||
"edr_freight_app:train_scheduling:cancel",
|
||
"Cancel train schedule",
|
||
),
|
||
perm(
|
||
"a2a00001-0001-4000-8000-000000000004",
|
||
"edr_freight_app:train_scheduling:reschedule",
|
||
"Reschedule train",
|
||
),
|
||
perm(
|
||
"a2a00001-0001-4000-8000-000000000005",
|
||
"edr_freight_app:train_scheduling:rules_manage",
|
||
"Manage global scheduling rules",
|
||
),
|
||
];
|
||
|
||
// L. Administration & settings (split from the coarse admin umbrella)
|
||
export const CONFIG_SETTINGS_PERMISSIONS: FreightPermissionSeed[] = [
|
||
perm(
|
||
"b4a00001-0001-4000-8000-000000000001",
|
||
"edr_freight_app:settings:file_upload:view",
|
||
"View file-upload settings",
|
||
),
|
||
perm(
|
||
"b4a00001-0001-4000-8000-000000000002",
|
||
"edr_freight_app:settings:file_upload:manage",
|
||
"Manage file-upload settings",
|
||
),
|
||
perm(
|
||
"b4b00001-0001-4000-8000-000000000001",
|
||
"edr_freight_app:settings:dropdown:view",
|
||
"View dropdown settings",
|
||
),
|
||
perm(
|
||
"b4b00001-0001-4000-8000-000000000002",
|
||
"edr_freight_app:settings:dropdown:manage",
|
||
"Manage dropdown settings",
|
||
),
|
||
perm(
|
||
"b4b00002-0001-4000-8000-000000000001",
|
||
"edr_freight_app:settings:stamp:view",
|
||
"View the company stamp",
|
||
),
|
||
perm(
|
||
"b4b00002-0001-4000-8000-000000000002",
|
||
"edr_freight_app:settings:stamp:manage",
|
||
"Manage the company stamp",
|
||
),
|
||
perm(
|
||
"b4b00004-0001-4000-8000-000000000001",
|
||
"edr_freight_app:settings:logo:view",
|
||
"View the company logo",
|
||
),
|
||
perm(
|
||
"b4b00004-0001-4000-8000-000000000002",
|
||
"edr_freight_app:settings:logo:manage",
|
||
"Manage the company logo",
|
||
),
|
||
// The per-officer approval teeter (ማህተም) — an individual's own stamp +
|
||
// signature, not the company seal. It used to ride on settings:stamp:*, which
|
||
// now gates the ONE company stamp; this key was split out when the two were
|
||
// untangled. `settings:invoice_stamp:*` retired at the same time: it gated the
|
||
// company stamp before the fold and is deliberately left orphaned in any DB
|
||
// that already seeded it (the seeder upserts by key and never deletes).
|
||
perm(
|
||
"b4b00003-0001-4000-8000-000000000001",
|
||
"edr_freight_app:settings:teeter:view",
|
||
"View own approval teeter and signature",
|
||
),
|
||
perm(
|
||
"b4b00003-0001-4000-8000-000000000002",
|
||
"edr_freight_app:settings:teeter:manage",
|
||
"Manage own approval teeter and signature",
|
||
),
|
||
perm(
|
||
"b4c00001-0001-4000-8000-000000000001",
|
||
"edr_freight_app:audit:view",
|
||
"View audit logs",
|
||
),
|
||
];
|
||
|
||
// M. Staff / IAM admin — NEW keys only. The employee_registration / role_assignment
|
||
// / hierarchy_* / position_types:view keys are seeded separately in edr-freight.seed.ts.
|
||
export const STAFF_IAM_PERMISSIONS: FreightPermissionSeed[] = [
|
||
perm(
|
||
"c2a00001-0001-4000-8000-000000000001",
|
||
"edr_freight_app:staff:roles:view",
|
||
"View roles",
|
||
),
|
||
perm(
|
||
"c2a00001-0001-4000-8000-000000000002",
|
||
"edr_freight_app:staff:roles:create",
|
||
"Create role",
|
||
),
|
||
perm(
|
||
"c2a00001-0001-4000-8000-000000000003",
|
||
"edr_freight_app:staff:roles:update",
|
||
"Update role",
|
||
),
|
||
perm(
|
||
"c2a00001-0001-4000-8000-000000000004",
|
||
"edr_freight_app:staff:roles:delete",
|
||
"Delete role",
|
||
),
|
||
perm(
|
||
"c2b00001-0001-4000-8000-000000000001",
|
||
"edr_freight_app:staff:permissions:view",
|
||
"View permission assignments",
|
||
),
|
||
perm(
|
||
"c2b00001-0001-4000-8000-000000000002",
|
||
"edr_freight_app:staff:permissions:assign",
|
||
"Assign permissions",
|
||
),
|
||
perm(
|
||
"c2c00001-0001-4000-8000-000000000001",
|
||
"edr_freight_app:position_types:create",
|
||
"Create position type",
|
||
),
|
||
perm(
|
||
"c2c00001-0001-4000-8000-000000000002",
|
||
"edr_freight_app:position_types:update",
|
||
"Update position type",
|
||
),
|
||
perm(
|
||
"c2c00001-0001-4000-8000-000000000003",
|
||
"edr_freight_app:position_types:delete",
|
||
"Delete position type",
|
||
),
|
||
];
|
||
|
||
// O. Granular splits of previously-shared keys: money/irreversible actions that
|
||
// used to ride on a broader permission (staff_accept, train_scheduling:update,
|
||
// the admin umbrella) get their own grant so departments can hold them apart.
|
||
export const GRANULAR_SPLIT_PERMISSIONS: FreightPermissionSeed[] = [
|
||
perm(
|
||
"a5a00001-0001-4000-8000-000000000001",
|
||
"edr_freight_app:bookings:government_expedite",
|
||
"Expedite a government booking (bypass payment)",
|
||
),
|
||
perm(
|
||
"a5b00001-0001-4000-8000-000000000001",
|
||
"edr_freight_app:contracts:edit_document",
|
||
"Edit contract document articles",
|
||
),
|
||
perm(
|
||
"a5b00001-0001-4000-8000-000000000002",
|
||
"edr_freight_app:contracts:final_invoice_raise",
|
||
"Raise final invoice (GL DJ)",
|
||
),
|
||
perm(
|
||
"a5b00001-0001-4000-8000-000000000003",
|
||
"edr_freight_app:contracts:final_invoice_confirm",
|
||
"Confirm final-invoice payment slip",
|
||
),
|
||
perm(
|
||
"a5c00001-0001-4000-8000-000000000001",
|
||
"edr_freight_app:train_scheduling:dispatch",
|
||
"Finalize / dispatch a train schedule",
|
||
),
|
||
perm(
|
||
"a5c00001-0001-4000-8000-000000000002",
|
||
"edr_freight_app:train_scheduling:mark_paid",
|
||
"Mark a reserved booking paid (staff)",
|
||
),
|
||
perm(
|
||
"a5c00001-0001-4000-8000-000000000003",
|
||
"edr_freight_app:train_scheduling:expire_booking",
|
||
"Expire a reserved booking",
|
||
),
|
||
perm(
|
||
"b4d00001-0001-4000-8000-000000000001",
|
||
"edr_freight_app:settings:exchange_rate:view",
|
||
"View exchange-rate settings",
|
||
),
|
||
perm(
|
||
"b4d00001-0001-4000-8000-000000000002",
|
||
"edr_freight_app:settings:exchange_rate:manage",
|
||
"Set the USD-ETB fallback rate",
|
||
),
|
||
perm(
|
||
"b4d00001-0001-4000-8000-000000000003",
|
||
"edr_freight_app:settings:manual_payment:view",
|
||
"View the manual (offline) payment channel settings",
|
||
),
|
||
perm(
|
||
"b4d00001-0001-4000-8000-000000000004",
|
||
"edr_freight_app:settings:manual_payment:manage",
|
||
"Enable or disable manual invoice settlement per currency",
|
||
),
|
||
perm(
|
||
"b4e00001-0001-4000-8000-000000000001",
|
||
"edr_freight_app:settings:contract_templates:view",
|
||
"View contract templates",
|
||
),
|
||
perm(
|
||
"b4e00001-0001-4000-8000-000000000002",
|
||
"edr_freight_app:settings:contract_templates:manage",
|
||
"Edit contract templates & articles",
|
||
),
|
||
// Granular split of contract-template access. `view` opens the sidebar page;
|
||
// `read` is API-read-only for other pages that display template data — and is
|
||
// NOT written out here: deriveReadPermissions mints the `:read` twin of every
|
||
// `:view` key, so a hand-written one duplicates the key (Postgres 21000 on the
|
||
// seeder's ON CONFLICT (key) insert) and carries a v4 id where twins are v5.
|
||
perm(
|
||
"b4e00001-0001-4000-8000-000000000003",
|
||
"edr_freight_app:settings:contract_templates:create",
|
||
"Create bulk contract templates",
|
||
),
|
||
perm(
|
||
"b4e00001-0001-4000-8000-000000000004",
|
||
"edr_freight_app:settings:contract_templates:update",
|
||
"Update contract templates & articles",
|
||
),
|
||
perm(
|
||
"b4e00001-0001-4000-8000-000000000005",
|
||
"edr_freight_app:settings:contract_templates:delete",
|
||
"Delete bulk contract templates",
|
||
),
|
||
perm(
|
||
"b4f00001-0001-4000-8000-000000000001",
|
||
"edr_freight_app:settings:support_content:view",
|
||
"View portal help & legal content",
|
||
),
|
||
perm(
|
||
"b4f00001-0001-4000-8000-000000000002",
|
||
"edr_freight_app:settings:support_content:manage",
|
||
"Edit portal help, FAQ & legal content",
|
||
),
|
||
];
|
||
|
||
// N. Previously-ungated staff surfaces (support inbox, procurement, compliance,
|
||
// facilities) plus keys for routes that only had auth-no-permission gating
|
||
// (staff directory, trade access) and the dashboard/report reads the frontend
|
||
// already gated on but were never seeded (overview:view / reports:view).
|
||
export const AUDIENCE_GAP_PERMISSIONS: FreightPermissionSeed[] = [
|
||
perm(
|
||
"a4a00001-0001-4000-8000-000000000001",
|
||
"edr_freight_app:support:agent_view",
|
||
"View support inbox (agent)",
|
||
),
|
||
perm(
|
||
"a4a00001-0001-4000-8000-000000000002",
|
||
"edr_freight_app:support:agent_send",
|
||
"Reply / start support threads (agent)",
|
||
),
|
||
perm(
|
||
"a4b00001-0001-4000-8000-000000000001",
|
||
"edr_freight_app:procurement:view",
|
||
"View procurement & asset lifecycle",
|
||
),
|
||
perm(
|
||
"a4b00001-0001-4000-8000-000000000002",
|
||
"edr_freight_app:procurement:vendor_manage",
|
||
"Manage vendors",
|
||
),
|
||
perm(
|
||
"a4b00001-0001-4000-8000-000000000003",
|
||
"edr_freight_app:procurement:acquisition_manage",
|
||
"Manage asset acquisitions",
|
||
),
|
||
perm(
|
||
"a4b00001-0001-4000-8000-000000000004",
|
||
"edr_freight_app:procurement:disposal_manage",
|
||
"Manage asset disposals",
|
||
),
|
||
perm(
|
||
"a4c00001-0001-4000-8000-000000000001",
|
||
"edr_freight_app:compliance:view",
|
||
"View vehicle compliance",
|
||
),
|
||
perm(
|
||
"a4c00001-0001-4000-8000-000000000002",
|
||
"edr_freight_app:compliance:manage",
|
||
"Manage vehicle compliance",
|
||
),
|
||
perm(
|
||
"a4d00001-0001-4000-8000-000000000001",
|
||
"edr_freight_app:facilities:view",
|
||
"View facilities",
|
||
),
|
||
perm(
|
||
"a4d00001-0001-4000-8000-000000000002",
|
||
"edr_freight_app:facilities:manage",
|
||
"Manage facilities",
|
||
),
|
||
perm(
|
||
"a4e00001-0001-4000-8000-000000000001",
|
||
"edr_freight_app:staff:users:view",
|
||
"List staff users (pickers)",
|
||
),
|
||
perm(
|
||
"a4e00001-0001-4000-8000-000000000002",
|
||
"edr_freight_app:trade_access:view",
|
||
"View trade-direction access",
|
||
),
|
||
perm(
|
||
"a4e00001-0001-4000-8000-000000000003",
|
||
"edr_freight_app:trade_access:manage",
|
||
"Manage trade-direction access",
|
||
),
|
||
perm(
|
||
"a4f00001-0001-4000-8000-000000000001",
|
||
"edr_freight_app:overview:view",
|
||
"View backoffice overview dashboard",
|
||
),
|
||
perm(
|
||
"a4f00001-0001-4000-8000-000000000002",
|
||
"edr_freight_app:reports:view",
|
||
"Run backoffice reports",
|
||
),
|
||
];
|
||
|
||
// O. Notification recipient selectors. NOT route guards — these are never
|
||
// passed to FreightPermissionGuard/assertFreightPermission and never appear in
|
||
// the frontend's RequirePermission. They exist so ops can tune who gets pinged
|
||
// WITHOUT changing who can open the page. Before them every staff notification
|
||
// used the `allBackoffice` selector, i.e. every current employee in every org.
|
||
export const NOTIFICATION_PERMISSIONS: FreightPermissionSeed[] = [
|
||
perm(
|
||
"f3a00001-0001-4000-8000-000000000001",
|
||
"edr_freight_app:bookings:get_notification",
|
||
"Receive booking desk notifications",
|
||
),
|
||
perm(
|
||
"f3a00001-0001-4000-8000-000000000002",
|
||
"edr_freight_app:bookings:clearance_get_notification",
|
||
"Receive booking clearance notifications",
|
||
),
|
||
perm(
|
||
"f3a00001-0001-4000-8000-000000000003",
|
||
"edr_freight_app:contracts:get_notification",
|
||
"Receive contract desk notifications",
|
||
),
|
||
perm(
|
||
"f3a00001-0001-4000-8000-000000000004",
|
||
"edr_freight_app:contracts:clearance_get_notification",
|
||
"Receive contract clearance notifications",
|
||
),
|
||
perm(
|
||
"f3a00001-0001-4000-8000-000000000005",
|
||
"edr_freight_app:customers:get_notification",
|
||
"Receive customer desk notifications",
|
||
),
|
||
perm(
|
||
"f3a00001-0001-4000-8000-000000000006",
|
||
"edr_freight_app:maintenance:get_notification",
|
||
"Receive maintenance due notifications",
|
||
),
|
||
perm(
|
||
"f3a00001-0001-4000-8000-000000000007",
|
||
"edr_freight_app:rule_engine:get_notification",
|
||
"Receive rule-change approval notifications",
|
||
),
|
||
perm(
|
||
"f3a00001-0001-4000-8000-000000000008",
|
||
"edr_freight_app:warehouse_fee_invoices:get_notification",
|
||
"Receive warehouse fee accrual notifications",
|
||
),
|
||
];
|
||
|
||
export const ADVANCED_BACKOFFICE_PERMISSIONS: FreightPermissionSeed[] = [
|
||
...REPORT_PERMISSIONS,
|
||
...CUSTOMER_PERMISSIONS,
|
||
...SHIPPING_LINE_PERMISSIONS,
|
||
...FINANCE_PERMISSIONS,
|
||
...MILE_PERMISSIONS,
|
||
...FLEET_RAIL_PERMISSIONS,
|
||
...FLEET_ROAD_PERMISSIONS,
|
||
...WAREHOUSE_PERMISSIONS,
|
||
...PORT_TERMINAL_PERMISSIONS,
|
||
...SCHEDULING_EXTRA_PERMISSIONS,
|
||
...CONFIG_SETTINGS_PERMISSIONS,
|
||
...STAFF_IAM_PERMISSIONS,
|
||
...AUDIENCE_GAP_PERMISSIONS,
|
||
...GRANULAR_SPLIT_PERMISSIONS,
|
||
...NOTIFICATION_PERMISSIONS,
|
||
];
|
||
|
||
export const BOOKING_RULE_ENGINE_PERMISSIONS = [
|
||
...BOOKING_PERMISSIONS,
|
||
...CONTRACT_PERMISSIONS,
|
||
...RULE_ENGINE_PERMISSIONS,
|
||
...GAP_CONTROLLER_PERMISSIONS,
|
||
...ADVANCED_BACKOFFICE_PERMISSIONS,
|
||
];
|
||
|
||
export const BOOKING_RULE_ENGINE_PERMISSION_KEYS =
|
||
BOOKING_RULE_ENGINE_PERMISSIONS.map((p) => p.key);
|
||
|
||
/**
|
||
* Id for a derived `:read` key: the source `:view` id with its version nibble
|
||
* moved 4 → 5.
|
||
*
|
||
* Unique because the source ids are, and disjoint from every hand-written id
|
||
* because those are all v4-shaped. Nothing index-derived, so a new `:view`
|
||
* landing mid-list cannot shift ids already seeded — the trap the
|
||
* RULE_ENGINE_RESOURCE_SLUGS comment warns about.
|
||
*
|
||
* (`EdrOrgSeeder.ensurePermissions` deliberately never sends an id — the
|
||
* column default wins and `key` is the identity every consumer resolves by.
|
||
* These exist to satisfy the seed type and keep the array self-consistent.)
|
||
*/
|
||
const readPermissionId = (viewId: string): string =>
|
||
`${viewId.slice(0, 14)}5${viewId.slice(15)}`;
|
||
|
||
/**
|
||
* API-read twin of every `:view` key.
|
||
*
|
||
* `:view` does three jobs at once — backoffice sidebar entry (App.tsx), route
|
||
* admission (RequirePermission), and API read. That bundling means a user who
|
||
* only needs another module's list endpoint for a form dropdown has to be
|
||
* granted the whole module, page and all. `<module>:read` unbundles it: the
|
||
* guard accepts it wherever the matching `:view` is required on a GET, and the
|
||
* frontend never looks at it, so the module stays out of the menu.
|
||
*
|
||
* Derived rather than hand-written so a new `:view` key gets its twin for
|
||
* free. Grants stay hand-curated in `iam.position_type_permissions` — nothing
|
||
* here hands a `:read` to anyone.
|
||
*/
|
||
export const deriveReadPermissions = (
|
||
seeds: readonly FreightPermissionSeed[],
|
||
): FreightPermissionSeed[] =>
|
||
seeds
|
||
.filter((p) => p.key.endsWith(VIEW_KEY_SUFFIX))
|
||
.map((p) =>
|
||
perm(
|
||
readPermissionId(p.id),
|
||
readTwinOf(p.key) as string,
|
||
`Read ${p.name.en.replace(/^View /, "")} (API only)`,
|
||
),
|
||
);
|
||
|
||
/**
|
||
* Read twins of the freight-domain catalog, for `PERMISSIONS_CATALOG`. The
|
||
* IAM/hierarchy keys seeded alongside it live in `edr-freight.seed.ts` and get
|
||
* theirs there — each twin is derived from its own source row, so the two call
|
||
* sites agree on any key they share without coordination.
|
||
*/
|
||
export const FREIGHT_READ_PERMISSIONS = deriveReadPermissions(
|
||
BOOKING_RULE_ENGINE_PERMISSIONS,
|
||
);
|
||
|
||
export const FREIGHT_PERMS = {
|
||
bookings: {
|
||
view: "edr_freight_app:bookings:view",
|
||
create: "edr_freight_app:bookings:create",
|
||
clearanceView: "edr_freight_app:bookings:clearance_view",
|
||
staffAccept: "edr_freight_app:bookings:staff_accept",
|
||
requestChanges: "edr_freight_app:bookings:request_changes",
|
||
reject: "edr_freight_app:bookings:reject",
|
||
approveLineStaff: "edr_freight_app:bookings:approve_line_staff",
|
||
approveDirector: "edr_freight_app:bookings:approve_director",
|
||
approveCeo: "edr_freight_app:bookings:approve_ceo",
|
||
rejectApproval: "edr_freight_app:bookings:reject_approval",
|
||
generateContract: "edr_freight_app:bookings:generate_contract",
|
||
signStaff: "edr_freight_app:bookings:sign_staff",
|
||
operations: "edr_freight_app:bookings:operations",
|
||
cancel: "edr_freight_app:bookings:cancel",
|
||
reviewDocuments: "edr_freight_app:bookings:review_documents",
|
||
uploadClearanceOutput: "edr_freight_app:bookings:upload_clearance_output",
|
||
finalizeClearance: "edr_freight_app:bookings:finalize_clearance",
|
||
docReviewAlert: "edr_freight_app:bookings:doc_review_alert",
|
||
governmentExpedite: "edr_freight_app:bookings:government_expedite",
|
||
wagonCancellationView: "edr_freight_app:bookings:wagon_cancellation_view",
|
||
wagonCancellationVoid: "edr_freight_app:bookings:wagon_cancellation_void",
|
||
wagonCancellationRebook:
|
||
"edr_freight_app:bookings:wagon_cancellation_rebook",
|
||
// Notification selectors, not route guards — see NOTIFICATION_PERMISSIONS.
|
||
getNotification: "edr_freight_app:bookings:get_notification",
|
||
clearanceGetNotification:
|
||
"edr_freight_app:bookings:clearance_get_notification",
|
||
},
|
||
contracts: {
|
||
view: "edr_freight_app:contracts:view",
|
||
staffAccept: {
|
||
bulk: "edr_freight_app:contracts:staff_accept:bulk",
|
||
container: "edr_freight_app:contracts:staff_accept:container",
|
||
},
|
||
requestChanges: {
|
||
bulk: "edr_freight_app:contracts:request_changes:bulk",
|
||
container: "edr_freight_app:contracts:request_changes:container",
|
||
},
|
||
reject: {
|
||
bulk: "edr_freight_app:contracts:reject:bulk",
|
||
container: "edr_freight_app:contracts:reject:container",
|
||
},
|
||
approveLineStaff: "edr_freight_app:contracts:approve_line_staff",
|
||
approveDirector: "edr_freight_app:contracts:approve_director",
|
||
approveCeo: "edr_freight_app:contracts:approve_ceo",
|
||
hazardousApprovalOne: "edr_freight_app:contracts:hazardous_approval_one",
|
||
hazardousApprovalTwo: "edr_freight_app:contracts:hazardous_approval_two",
|
||
generateContract: "edr_freight_app:contracts:generate_contract",
|
||
signStaff: {
|
||
bulk: "edr_freight_app:contracts:sign_staff:bulk",
|
||
container: "edr_freight_app:contracts:sign_staff:container",
|
||
},
|
||
clearanceReview: "edr_freight_app:contracts:clearance_review",
|
||
finalizeClearance: "edr_freight_app:contracts:finalize_clearance",
|
||
createBooking: "edr_freight_app:contracts:create_booking",
|
||
opsClearanceReview: "edr_freight_app:contracts:ops_clearance_review",
|
||
clearanceEtActions: "edr_freight_app:contracts:clearance_et_actions",
|
||
clearanceDjActions: "edr_freight_app:contracts:clearance_dj_actions",
|
||
clearanceDutyAdvise: "edr_freight_app:contracts:clearance_duty_advise",
|
||
suspend: "edr_freight_app:contracts:suspend",
|
||
editDocument: "edr_freight_app:contracts:edit_document",
|
||
finalInvoiceRaise: "edr_freight_app:contracts:final_invoice_raise",
|
||
finalInvoiceConfirm: "edr_freight_app:contracts:final_invoice_confirm",
|
||
// Notification selectors, not route guards — see NOTIFICATION_PERMISSIONS.
|
||
getNotification: "edr_freight_app:contracts:get_notification",
|
||
clearanceGetNotification:
|
||
"edr_freight_app:contracts:clearance_get_notification",
|
||
},
|
||
trainScheduling: {
|
||
view: "edr_freight_app:train_scheduling:view",
|
||
create: "edr_freight_app:train_scheduling:create",
|
||
update: "edr_freight_app:train_scheduling:update",
|
||
cancel: "edr_freight_app:train_scheduling:cancel",
|
||
reschedule: "edr_freight_app:train_scheduling:reschedule",
|
||
rulesManage: "edr_freight_app:train_scheduling:rules_manage",
|
||
dispatch: "edr_freight_app:train_scheduling:dispatch",
|
||
markPaid: "edr_freight_app:train_scheduling:mark_paid",
|
||
expireBooking: "edr_freight_app:train_scheduling:expire_booking",
|
||
/**
|
||
* Edit a schedule's operational run numbers (train + voyage) before
|
||
* dispatch. Separate from `update`: these numbers are what yards and
|
||
* customs quote, so changing them is narrower than general scheduling edits.
|
||
*/
|
||
editTrainNumber: "edr_freight_app:train_scheduling:edit_train_number",
|
||
},
|
||
fleet: {
|
||
view: "edr_freight_app:fleet:view",
|
||
manage: "edr_freight_app:fleet:manage",
|
||
},
|
||
/**
|
||
* Audit trail. View-only: the module has no write routes, so this is the
|
||
* only key it needs — see AUDIT_LOG_PERMISSIONS in edr-freight.seed.ts.
|
||
*/
|
||
auditLog: {
|
||
view: "edr_freight_app:audit_log:view",
|
||
},
|
||
admin: "edr_freight_app:admin",
|
||
ruleEngine: {
|
||
view: (slug: RuleEngineResourceSlug) =>
|
||
`edr_freight_app:rule_engine:${slugToResourceKey(slug)}:view`,
|
||
create: (slug: RuleEngineResourceSlug) =>
|
||
`edr_freight_app:rule_engine:${slugToResourceKey(slug)}:create`,
|
||
update: (slug: RuleEngineResourceSlug) =>
|
||
`edr_freight_app:rule_engine:${slugToResourceKey(slug)}:update`,
|
||
delete: (slug: RuleEngineResourceSlug) =>
|
||
`edr_freight_app:rule_engine:${slugToResourceKey(slug)}:delete`,
|
||
approve: (slug: RuleEngineApprovableSlug) =>
|
||
`edr_freight_app:rule_engine:${slugToResourceKey(slug)}:approve`,
|
||
// Notification selector, not a route guard — see NOTIFICATION_PERMISSIONS.
|
||
// One key for the whole rules desk: every preset grants the rule-engine
|
||
// view keys as a block, and both producers link to /dashboard/rules/*.
|
||
getNotification: "edr_freight_app:rule_engine:get_notification",
|
||
},
|
||
allocation: {
|
||
manage: "edr_freight_app:allocation:manage",
|
||
},
|
||
customers: {
|
||
view: "edr_freight_app:customers:view",
|
||
create: "edr_freight_app:customers:create",
|
||
update: "edr_freight_app:customers:update",
|
||
deactivate: "edr_freight_app:customers:deactivate",
|
||
verify: "edr_freight_app:customers:verify",
|
||
resetPassword: "edr_freight_app:customers:reset-password",
|
||
// Notification selector, not a route guard — see NOTIFICATION_PERMISSIONS.
|
||
getNotification: "edr_freight_app:customers:get_notification",
|
||
},
|
||
shippingLines: {
|
||
view: "edr_freight_app:shipping_lines:view",
|
||
create: "edr_freight_app:shipping_lines:create",
|
||
update: "edr_freight_app:shipping_lines:update",
|
||
resetPassword: "edr_freight_app:shipping_lines:reset-password",
|
||
},
|
||
shippingLineCredits: {
|
||
view: "edr_freight_app:shipping_line_credits:view",
|
||
/** Turn a batch of unbilled credits into an invoice. */
|
||
invoice: "edr_freight_app:shipping_line_credits:invoice",
|
||
/** Write off an unbilled credit — separate grant: it erases a debt. */
|
||
cancel: "edr_freight_app:shipping_line_credits:cancel",
|
||
// Two-step manual actions on credit invoices, gated purely by permission:
|
||
// finance-level REQUEST grants (per action) and decision grants that apply
|
||
// to ANY pending request — including the holder's own.
|
||
/** Request recording an offline payment against a credit invoice. */
|
||
invoiceMarkPaid:
|
||
"edr_freight_app:shipping_line_credits:invoice_mark_paid",
|
||
/** Request voiding a credit invoice (credits return to unbilled). */
|
||
invoiceCancel: "edr_freight_app:shipping_line_credits:invoice_cancel",
|
||
/** Approve any pending invoice request (mark-paid or cancel). */
|
||
invoiceApprove: "edr_freight_app:shipping_line_credits:invoice_approve",
|
||
/** Reject any pending invoice request. */
|
||
invoiceReject: "edr_freight_app:shipping_line_credits:invoice_reject",
|
||
},
|
||
payments: {
|
||
view: "edr_freight_app:payments:view",
|
||
},
|
||
invoices: {
|
||
view: "edr_freight_app:invoices:view",
|
||
export: "edr_freight_app:invoices:export",
|
||
eimsRegister: "edr_freight_app:invoices:eims_register",
|
||
eimsResolve: "edr_freight_app:invoices:eims_resolve",
|
||
eimsCancel: "edr_freight_app:invoices:eims_cancel",
|
||
eimsReceiptRegister: "edr_freight_app:invoices:eims_receipt_register",
|
||
memoIssue: "edr_freight_app:invoices:memo_issue",
|
||
confirmOffline: "edr_freight_app:invoices:confirm_offline",
|
||
},
|
||
firstMile: {
|
||
view: "edr_freight_app:first_mile:view",
|
||
accept: "edr_freight_app:first_mile:accept",
|
||
create: "edr_freight_app:first_mile:create",
|
||
update: "edr_freight_app:first_mile:update",
|
||
delete: "edr_freight_app:first_mile:delete",
|
||
assignVehicles: "edr_freight_app:first_mile:assign_vehicles",
|
||
setDistances: "edr_freight_app:first_mile:set_distances",
|
||
generateInvoice: "edr_freight_app:first_mile:generate_invoice",
|
||
},
|
||
lastMile: {
|
||
view: "edr_freight_app:last_mile:view",
|
||
accept: "edr_freight_app:last_mile:accept",
|
||
create: "edr_freight_app:last_mile:create",
|
||
update: "edr_freight_app:last_mile:update",
|
||
delete: "edr_freight_app:last_mile:delete",
|
||
assignVehicles: "edr_freight_app:last_mile:assign_vehicles",
|
||
setDistances: "edr_freight_app:last_mile:set_distances",
|
||
generateInvoice: "edr_freight_app:last_mile:generate_invoice",
|
||
// Pre-approval confirmation stage (Truck & Machinery department): view/review
|
||
// a submitted request, approve/reject it.
|
||
requestView: "edr_freight_app:last_mile:request_view",
|
||
requestReview: "edr_freight_app:last_mile:request_review",
|
||
requestApprove: "edr_freight_app:last_mile:request_approve",
|
||
},
|
||
locomotives: {
|
||
view: "edr_freight_app:locomotives:view",
|
||
create: "edr_freight_app:locomotives:create",
|
||
update: "edr_freight_app:locomotives:update",
|
||
delete: "edr_freight_app:locomotives:delete",
|
||
/**
|
||
* Permanently purge the row — irreversible, and separate from `delete`
|
||
* (which only decommissions) so it can be granted to far fewer people.
|
||
*/
|
||
hardDelete: "edr_freight_app:locomotives:hard_delete",
|
||
},
|
||
wagons: {
|
||
view: "edr_freight_app:wagons:view",
|
||
create: "edr_freight_app:wagons:create",
|
||
update: "edr_freight_app:wagons:update",
|
||
delete: "edr_freight_app:wagons:delete",
|
||
/** Permanently purge the row — irreversible; see locomotives.hardDelete. */
|
||
hardDelete: "edr_freight_app:wagons:hard_delete",
|
||
// Requester creates a transfer request; OCC fulfils it (picks the wagons and
|
||
// executes the move). Distinct keys so OCC can hold fulfil without request.
|
||
transferRequest: "edr_freight_app:wagons:transfer_request",
|
||
transferFulfill: "edr_freight_app:wagons:transfer_fulfill",
|
||
/** Open the transfer-requests desk (list + detail). */
|
||
transferView: "edr_freight_app:wagons:transfer_view",
|
||
/** Withdraw a request that has not moved any wagon yet. */
|
||
transferCancel: "edr_freight_app:wagons:transfer_cancel",
|
||
/** Maintenance ⇄ availability flip on the wagons desk (audited). */
|
||
statusToggle: "edr_freight_app:wagons:status_toggle",
|
||
/** End a request short — anyone who can fulfil may also do this. */
|
||
transferCloseShort: "edr_freight_app:wagons:transfer_close_short",
|
||
// Admin: read every staffer's transfer history. Without it, a user only sees
|
||
// their own (the /history endpoint uses the caller id, backend-enforced).
|
||
transferHistoryAll: "edr_freight_app:wagons:transfer_history_all",
|
||
},
|
||
trains: {
|
||
view: "edr_freight_app:trains:view",
|
||
create: "edr_freight_app:trains:create",
|
||
update: "edr_freight_app:trains:update",
|
||
delete: "edr_freight_app:trains:delete",
|
||
assignWagons: "edr_freight_app:trains:assign_wagons",
|
||
changeLocomotives: "edr_freight_app:trains:change_locomotives",
|
||
changeYard: "edr_freight_app:trains:change_yard",
|
||
toggleActive: "edr_freight_app:trains:toggle_active",
|
||
disband: "edr_freight_app:trains:disband",
|
||
},
|
||
routes: {
|
||
view: "edr_freight_app:routes:view",
|
||
create: "edr_freight_app:routes:create",
|
||
update: "edr_freight_app:routes:update",
|
||
delete: "edr_freight_app:routes:delete",
|
||
/** Permanently purge the row — irreversible; see locomotives.hardDelete. */
|
||
hardDelete: "edr_freight_app:routes:hard_delete",
|
||
},
|
||
containers: {
|
||
view: "edr_freight_app:containers:view",
|
||
create: "edr_freight_app:containers:create",
|
||
update: "edr_freight_app:containers:update",
|
||
delete: "edr_freight_app:containers:delete",
|
||
},
|
||
cargoes: {
|
||
view: "edr_freight_app:cargoes:view",
|
||
create: "edr_freight_app:cargoes:create",
|
||
update: "edr_freight_app:cargoes:update",
|
||
delete: "edr_freight_app:cargoes:delete",
|
||
},
|
||
consignments: {
|
||
view: "edr_freight_app:consignments:view",
|
||
create: "edr_freight_app:consignments:create",
|
||
},
|
||
vehicles: {
|
||
view: "edr_freight_app:vehicles:view",
|
||
create: "edr_freight_app:vehicles:create",
|
||
update: "edr_freight_app:vehicles:update",
|
||
delete: "edr_freight_app:vehicles:delete",
|
||
},
|
||
drivers: {
|
||
view: "edr_freight_app:drivers:view",
|
||
create: "edr_freight_app:drivers:create",
|
||
update: "edr_freight_app:drivers:update",
|
||
delete: "edr_freight_app:drivers:delete",
|
||
},
|
||
tracking: {
|
||
view: "edr_freight_app:tracking:view",
|
||
manage: "edr_freight_app:tracking:manage",
|
||
},
|
||
fuel: {
|
||
view: "edr_freight_app:fuel:view",
|
||
create: "edr_freight_app:fuel:create",
|
||
update: "edr_freight_app:fuel:update",
|
||
delete: "edr_freight_app:fuel:delete",
|
||
},
|
||
maintenance: {
|
||
view: "edr_freight_app:maintenance:view",
|
||
create: "edr_freight_app:maintenance:create",
|
||
update: "edr_freight_app:maintenance:update",
|
||
delete: "edr_freight_app:maintenance:delete",
|
||
// Notification selector, not a route guard — see NOTIFICATION_PERMISSIONS.
|
||
getNotification: "edr_freight_app:maintenance:get_notification",
|
||
},
|
||
fleetReports: {
|
||
view: "edr_freight_app:fleet_reports:view",
|
||
export: "edr_freight_app:fleet_reports:export",
|
||
},
|
||
fleetDashboard: {
|
||
view: "edr_freight_app:fleet_dashboard:view",
|
||
},
|
||
warehouseDashboard: {
|
||
view: "edr_freight_app:warehouse_dashboard:view",
|
||
},
|
||
warehouses: {
|
||
view: "edr_freight_app:warehouses:view",
|
||
create: "edr_freight_app:warehouses:create",
|
||
update: "edr_freight_app:warehouses:update",
|
||
delete: "edr_freight_app:warehouses:delete",
|
||
},
|
||
warehouseYards: {
|
||
view: "edr_freight_app:warehouse_yards:view",
|
||
create: "edr_freight_app:warehouse_yards:create",
|
||
update: "edr_freight_app:warehouse_yards:update",
|
||
delete: "edr_freight_app:warehouse_yards:delete",
|
||
},
|
||
warehouseZones: {
|
||
view: "edr_freight_app:warehouse_zones:view",
|
||
create: "edr_freight_app:warehouse_zones:create",
|
||
update: "edr_freight_app:warehouse_zones:update",
|
||
},
|
||
warehouseAllocationRules: {
|
||
view: "edr_freight_app:warehouse_allocation_rules:view",
|
||
create: "edr_freight_app:warehouse_allocation_rules:create",
|
||
update: "edr_freight_app:warehouse_allocation_rules:update",
|
||
delete: "edr_freight_app:warehouse_allocation_rules:delete",
|
||
},
|
||
warehouseFeeRules: {
|
||
view: "edr_freight_app:warehouse_fee_rules:view",
|
||
create: "edr_freight_app:warehouse_fee_rules:create",
|
||
update: "edr_freight_app:warehouse_fee_rules:update",
|
||
delete: "edr_freight_app:warehouse_fee_rules:delete",
|
||
},
|
||
warehouseInspectionReports: {
|
||
view: "edr_freight_app:warehouse_inspection_reports:view",
|
||
create: "edr_freight_app:warehouse_inspection_reports:create",
|
||
update: "edr_freight_app:warehouse_inspection_reports:update",
|
||
},
|
||
warehouseInventory: {
|
||
view: "edr_freight_app:warehouse_inventory:view",
|
||
receive: "edr_freight_app:warehouse_inventory:receive",
|
||
move: "edr_freight_app:warehouse_inventory:move",
|
||
load: "edr_freight_app:warehouse_inventory:load",
|
||
unload: "edr_freight_app:warehouse_inventory:unload",
|
||
dispatch: "edr_freight_app:warehouse_inventory:dispatch",
|
||
gatePass: "edr_freight_app:warehouse_inventory:gate_pass",
|
||
release: "edr_freight_app:warehouse_inventory:release",
|
||
deliver: "edr_freight_app:warehouse_inventory:deliver",
|
||
inspect: "edr_freight_app:warehouse_inventory:inspect",
|
||
},
|
||
interchangeDocuments: {
|
||
view: "edr_freight_app:interchange_documents:view",
|
||
generate: "edr_freight_app:interchange_documents:generate",
|
||
acknowledge: "edr_freight_app:interchange_documents:acknowledge",
|
||
dispute: "edr_freight_app:interchange_documents:dispute",
|
||
cancel: "edr_freight_app:interchange_documents:cancel",
|
||
},
|
||
warehouseFeeInvoices: {
|
||
view: "edr_freight_app:warehouse_fee_invoices:view",
|
||
generate: "edr_freight_app:warehouse_fee_invoices:generate",
|
||
cancel: "edr_freight_app:warehouse_fee_invoices:cancel",
|
||
pay: "edr_freight_app:warehouse_fee_invoices:pay",
|
||
// Notification selector, not a route guard — see NOTIFICATION_PERMISSIONS.
|
||
getNotification: "edr_freight_app:warehouse_fee_invoices:get_notification",
|
||
},
|
||
settings: {
|
||
fileUpload: {
|
||
view: "edr_freight_app:settings:file_upload:view",
|
||
manage: "edr_freight_app:settings:file_upload:manage",
|
||
},
|
||
dropdown: {
|
||
view: "edr_freight_app:settings:dropdown:view",
|
||
manage: "edr_freight_app:settings:dropdown:manage",
|
||
},
|
||
// The ONE company stamp/seal, applied to every generated document
|
||
// (invoices, receipts, warehouse papers, the EDR side of contracts).
|
||
stamp: {
|
||
view: "edr_freight_app:settings:stamp:view",
|
||
manage: "edr_freight_app:settings:stamp:manage",
|
||
},
|
||
// The ONE company logo, applied to every generated document (invoices,
|
||
// receipts, contracts, warehouse papers, train-scheduling manifests).
|
||
logo: {
|
||
view: "edr_freight_app:settings:logo:view",
|
||
manage: "edr_freight_app:settings:logo:manage",
|
||
},
|
||
// The per-officer approval teeter (ማህተም) + signature — genuinely per-person,
|
||
// and NOT the company seal above. Retired: `invoiceStamp`, which used to
|
||
// gate the company stamp before the two were untangled.
|
||
teeter: {
|
||
view: "edr_freight_app:settings:teeter:view",
|
||
manage: "edr_freight_app:settings:teeter:manage",
|
||
},
|
||
exchangeRate: {
|
||
view: "edr_freight_app:settings:exchange_rate:view",
|
||
manage: "edr_freight_app:settings:exchange_rate:manage",
|
||
},
|
||
// Whether Finance may settle invoices by hand, per currency. Split
|
||
// view/manage on purpose: Finance reads it (the worklist offers only the
|
||
// enabled currencies) but must not switch its own channel on — same
|
||
// maker-checker split as the other sensitive finance settings.
|
||
manualPayment: {
|
||
view: "edr_freight_app:settings:manual_payment:view",
|
||
manage: "edr_freight_app:settings:manual_payment:manage",
|
||
},
|
||
contractTemplates: {
|
||
view: "edr_freight_app:settings:contract_templates:view",
|
||
manage: "edr_freight_app:settings:contract_templates:manage",
|
||
create: "edr_freight_app:settings:contract_templates:create",
|
||
update: "edr_freight_app:settings:contract_templates:update",
|
||
delete: "edr_freight_app:settings:contract_templates:delete",
|
||
read: "edr_freight_app:settings:contract_templates:read",
|
||
},
|
||
// Portal-facing help/FAQ/legal copy, edited from Portal content.
|
||
supportContent: {
|
||
view: "edr_freight_app:settings:support_content:view",
|
||
manage: "edr_freight_app:settings:support_content:manage",
|
||
},
|
||
},
|
||
support: {
|
||
agentView: "edr_freight_app:support:agent_view",
|
||
agentSend: "edr_freight_app:support:agent_send",
|
||
},
|
||
procurement: {
|
||
view: "edr_freight_app:procurement:view",
|
||
vendorManage: "edr_freight_app:procurement:vendor_manage",
|
||
acquisitionManage: "edr_freight_app:procurement:acquisition_manage",
|
||
disposalManage: "edr_freight_app:procurement:disposal_manage",
|
||
},
|
||
compliance: {
|
||
view: "edr_freight_app:compliance:view",
|
||
manage: "edr_freight_app:compliance:manage",
|
||
},
|
||
facilities: {
|
||
view: "edr_freight_app:facilities:view",
|
||
manage: "edr_freight_app:facilities:manage",
|
||
},
|
||
tradeAccess: {
|
||
view: "edr_freight_app:trade_access:view",
|
||
manage: "edr_freight_app:trade_access:manage",
|
||
},
|
||
overview: {
|
||
view: "edr_freight_app:overview:view",
|
||
},
|
||
reports: {
|
||
view: "edr_freight_app:reports:view",
|
||
report: (key: ReportKey): string => reportPermissionKey(key),
|
||
},
|
||
staff: {
|
||
users: {
|
||
view: "edr_freight_app:staff:users:view",
|
||
},
|
||
roles: {
|
||
view: "edr_freight_app:staff:roles:view",
|
||
create: "edr_freight_app:staff:roles:create",
|
||
update: "edr_freight_app:staff:roles:update",
|
||
delete: "edr_freight_app:staff:roles:delete",
|
||
},
|
||
permissions: {
|
||
view: "edr_freight_app:staff:permissions:view",
|
||
assign: "edr_freight_app:staff:permissions:assign",
|
||
},
|
||
// Seeded in edr-freight.seed.ts (EDR_FREIGHT_PERMISSIONS) — surfaced here for gating.
|
||
employeeRegistration: {
|
||
view: "edr_freight_app:employee_registration:view",
|
||
create: "edr_freight_app:employee_registration:create",
|
||
update: "edr_freight_app:employee_registration:update",
|
||
activate: "edr_freight_app:employee_registration:activate",
|
||
deactivate: "edr_freight_app:employee_registration:deactivate",
|
||
},
|
||
roleAssignment: {
|
||
view: "edr_freight_app:role_assignment:view",
|
||
assign: "edr_freight_app:role_assignment:assign",
|
||
replace: "edr_freight_app:role_assignment:replace",
|
||
},
|
||
hierarchyUnits: {
|
||
view: "edr_freight_app:hierarchy_units:view",
|
||
create: "edr_freight_app:hierarchy_units:create",
|
||
update: "edr_freight_app:hierarchy_units:update",
|
||
delete: "edr_freight_app:hierarchy_units:delete",
|
||
},
|
||
hierarchyPositions: {
|
||
view: "edr_freight_app:hierarchy_positions:view",
|
||
create: "edr_freight_app:hierarchy_positions:create",
|
||
update: "edr_freight_app:hierarchy_positions:update",
|
||
delete: "edr_freight_app:hierarchy_positions:delete",
|
||
changeParent: "edr_freight_app:hierarchy_positions:change_parent",
|
||
},
|
||
hierarchyEmployeeAssignment: {
|
||
view: "edr_freight_app:hierarchy_employee_assignment:view",
|
||
invite: "edr_freight_app:hierarchy_employee_assignment:invite",
|
||
assign: "edr_freight_app:hierarchy_employee_assignment:assign",
|
||
},
|
||
positionTypes: {
|
||
view: "edr_freight_app:position_types:view",
|
||
create: "edr_freight_app:position_types:create",
|
||
update: "edr_freight_app:position_types:update",
|
||
delete: "edr_freight_app:position_types:delete",
|
||
},
|
||
},
|
||
} as const;
|
||
|
||
/**
|
||
* Backfill sources for the `<module>:get_notification` keys: a new key is
|
||
* granted to whoever already holds ANY of its anchors. The anchor is the key
|
||
* that gates the page the notification deep-links to — if you cannot open the
|
||
* page, you were never the intended recipient. The rule-engine desk has no page
|
||
* of its own, so it anchors on the keys that let you ACT on a filed change.
|
||
*
|
||
* Consumed only by FreightNotificationPermissionsSeeder. Keeping it here means
|
||
* the registry spec can assert every anchor still exists in the catalog — a
|
||
* typo'd anchor backfills nobody, silently.
|
||
*/
|
||
export const NOTIFICATION_PERMISSION_ANCHORS: Record<string, string[]> = {
|
||
[FREIGHT_PERMS.bookings.getNotification]: [FREIGHT_PERMS.bookings.view],
|
||
[FREIGHT_PERMS.bookings.clearanceGetNotification]: [
|
||
FREIGHT_PERMS.bookings.clearanceView,
|
||
],
|
||
[FREIGHT_PERMS.contracts.getNotification]: [FREIGHT_PERMS.contracts.view],
|
||
[FREIGHT_PERMS.contracts.clearanceGetNotification]: [
|
||
FREIGHT_PERMS.contracts.clearanceReview,
|
||
FREIGHT_PERMS.contracts.clearanceEtActions,
|
||
FREIGHT_PERMS.contracts.clearanceDjActions,
|
||
FREIGHT_PERMS.contracts.opsClearanceReview,
|
||
],
|
||
[FREIGHT_PERMS.customers.getNotification]: [FREIGHT_PERMS.customers.view],
|
||
[FREIGHT_PERMS.maintenance.getNotification]: [FREIGHT_PERMS.maintenance.view],
|
||
[FREIGHT_PERMS.ruleEngine.getNotification]: [
|
||
FREIGHT_PERMS.ruleEngine.approve("rates"),
|
||
FREIGHT_PERMS.ruleEngine.update("priority-configs"),
|
||
],
|
||
[FREIGHT_PERMS.warehouseFeeInvoices.getNotification]: [
|
||
FREIGHT_PERMS.warehouseFeeInvoices.view,
|
||
],
|
||
};
|
||
|
||
/** Both arms of a freight-type-split permission (for one-of route guards). */
|
||
export const bothFreightTypes = (p: {
|
||
bulk: string;
|
||
container: string;
|
||
}): string[] => [p.bulk, p.container];
|
||
|
||
/** The arm of a freight-type-split permission matching a contract's freightType. */
|
||
export const forFreightType = (
|
||
p: { bulk: string; container: string },
|
||
freightType: string,
|
||
): string => (freightType === "BULK" ? p.bulk : p.container);
|
||
|
||
const allRuleEngineViewKeys = () =>
|
||
RULE_ENGINE_RESOURCE_SLUGS.map((s) => FREIGHT_PERMS.ruleEngine.view(s));
|
||
|
||
/**
|
||
* Granular equivalents of the legacy fleet:view + fleet:manage pair.
|
||
* Deliberately excludes the wagon-transfer keys — those were always separate
|
||
* grants (requester vs OCC vs admin history), not part of fleet:manage.
|
||
*/
|
||
const FLEET_GRANULAR_KEYS: string[] = [
|
||
FREIGHT_PERMS.locomotives.view,
|
||
FREIGHT_PERMS.locomotives.create,
|
||
FREIGHT_PERMS.locomotives.update,
|
||
FREIGHT_PERMS.locomotives.delete,
|
||
FREIGHT_PERMS.wagons.view,
|
||
FREIGHT_PERMS.wagons.create,
|
||
FREIGHT_PERMS.wagons.update,
|
||
FREIGHT_PERMS.wagons.delete,
|
||
FREIGHT_PERMS.wagons.statusToggle,
|
||
FREIGHT_PERMS.trains.view,
|
||
FREIGHT_PERMS.trains.create,
|
||
FREIGHT_PERMS.trains.update,
|
||
FREIGHT_PERMS.trains.delete,
|
||
FREIGHT_PERMS.trains.assignWagons,
|
||
FREIGHT_PERMS.trains.changeLocomotives,
|
||
FREIGHT_PERMS.trains.changeYard,
|
||
FREIGHT_PERMS.trains.toggleActive,
|
||
FREIGHT_PERMS.trains.disband,
|
||
FREIGHT_PERMS.routes.view,
|
||
FREIGHT_PERMS.routes.create,
|
||
FREIGHT_PERMS.routes.update,
|
||
FREIGHT_PERMS.routes.delete,
|
||
FREIGHT_PERMS.containers.view,
|
||
FREIGHT_PERMS.containers.create,
|
||
FREIGHT_PERMS.containers.update,
|
||
FREIGHT_PERMS.containers.delete,
|
||
FREIGHT_PERMS.cargoes.view,
|
||
FREIGHT_PERMS.cargoes.create,
|
||
FREIGHT_PERMS.cargoes.update,
|
||
FREIGHT_PERMS.cargoes.delete,
|
||
FREIGHT_PERMS.consignments.view,
|
||
FREIGHT_PERMS.consignments.create,
|
||
];
|
||
|
||
const allReportKeys = (): string[] => REPORT_KEYS.map((k) => reportPermissionKey(k));
|
||
|
||
// Everyone who works the booking desk also opens the overview dashboard and
|
||
// the canned reports — granted alongside bookings:view in every preset below.
|
||
// Each report also carries its own key (see REPORT_PERMISSIONS); spreading
|
||
// allReportKeys() here keeps every existing preset seeing every report, same
|
||
// as when reports:view alone gated the whole section.
|
||
const STAFF_DASHBOARD_KEYS: string[] = [
|
||
FREIGHT_PERMS.overview.view,
|
||
FREIGHT_PERMS.reports.view,
|
||
...allReportKeys(),
|
||
];
|
||
|
||
// Notification desks — recipient selectors, not access. A preset gets a desk
|
||
// key only where it actually works that queue, which is why the GL presets take
|
||
// the clearance pair and nothing else: they hold no bookings:view and no intake
|
||
// keys, so intake pings would only be noise they cannot act on.
|
||
const BOOKING_DESK_NOTIFICATION_KEYS: string[] = [
|
||
FREIGHT_PERMS.bookings.getNotification,
|
||
FREIGHT_PERMS.contracts.getNotification,
|
||
];
|
||
const CLEARANCE_DESK_NOTIFICATION_KEYS: string[] = [
|
||
FREIGHT_PERMS.bookings.clearanceGetNotification,
|
||
FREIGHT_PERMS.contracts.clearanceGetNotification,
|
||
];
|
||
|
||
export const ROLE_PERMISSION_PRESETS = {
|
||
// Marketing / line staff: drives a booking from intake through line-staff
|
||
// approval and contract generation/signing — i.e. until the contract is ready
|
||
// and signed. No director/CEO approval, no scheduling, no operations.
|
||
lineStaff: [
|
||
...STAFF_DASHBOARD_KEYS,
|
||
FREIGHT_PERMS.bookings.view,
|
||
FREIGHT_PERMS.bookings.staffAccept,
|
||
FREIGHT_PERMS.bookings.requestChanges,
|
||
FREIGHT_PERMS.bookings.reject,
|
||
FREIGHT_PERMS.bookings.approveLineStaff,
|
||
FREIGHT_PERMS.bookings.rejectApproval,
|
||
FREIGHT_PERMS.bookings.cancel,
|
||
FREIGHT_PERMS.bookings.wagonCancellationView,
|
||
FREIGHT_PERMS.bookings.wagonCancellationVoid,
|
||
FREIGHT_PERMS.contracts.view,
|
||
...bothFreightTypes(FREIGHT_PERMS.contracts.staffAccept),
|
||
...bothFreightTypes(FREIGHT_PERMS.contracts.requestChanges),
|
||
...bothFreightTypes(FREIGHT_PERMS.contracts.reject),
|
||
FREIGHT_PERMS.contracts.approveLineStaff,
|
||
FREIGHT_PERMS.contracts.editDocument,
|
||
...allRuleEngineViewKeys(),
|
||
...BOOKING_DESK_NOTIFICATION_KEYS,
|
||
FREIGHT_PERMS.ruleEngine.getNotification,
|
||
],
|
||
// Operations Officer: train scheduling + wagon allocation + transit/complete
|
||
// + fleet management (wagons, trains, locomotives, routes, containers, cargo).
|
||
operationsOfficer: [
|
||
...STAFF_DASHBOARD_KEYS,
|
||
FREIGHT_PERMS.bookings.view,
|
||
FREIGHT_PERMS.bookings.operations,
|
||
FREIGHT_PERMS.bookings.wagonCancellationView,
|
||
// They are the ones who accept/reject operation requests, so they are the
|
||
// ones the doc-review countdown is for.
|
||
FREIGHT_PERMS.bookings.docReviewAlert,
|
||
FREIGHT_PERMS.trainScheduling.view,
|
||
FREIGHT_PERMS.trainScheduling.create,
|
||
FREIGHT_PERMS.trainScheduling.update,
|
||
FREIGHT_PERMS.trainScheduling.cancel,
|
||
FREIGHT_PERMS.trainScheduling.reschedule,
|
||
FREIGHT_PERMS.trainScheduling.rulesManage,
|
||
FREIGHT_PERMS.trainScheduling.dispatch,
|
||
FREIGHT_PERMS.trainScheduling.markPaid,
|
||
FREIGHT_PERMS.trainScheduling.expireBooking,
|
||
FREIGHT_PERMS.trainScheduling.editTrainNumber,
|
||
FREIGHT_PERMS.fleet.view,
|
||
FREIGHT_PERMS.fleet.manage,
|
||
...FLEET_GRANULAR_KEYS,
|
||
// Path A (no customs): Operations reviews the customer's self-clearance docs
|
||
// — on the contract for ONE_TIME contracts, and PER BOOKING for GENERAL
|
||
// contracts (booking-level document review → finalize → CLEARANCE_READY).
|
||
FREIGHT_PERMS.contracts.view,
|
||
FREIGHT_PERMS.contracts.opsClearanceReview,
|
||
FREIGHT_PERMS.bookings.clearanceView,
|
||
FREIGHT_PERMS.bookings.reviewDocuments,
|
||
FREIGHT_PERMS.bookings.finalizeClearance,
|
||
...allRuleEngineViewKeys(),
|
||
...BOOKING_DESK_NOTIFICATION_KEYS,
|
||
// They run Path A clearance review from the booking detail, so the booking
|
||
// clearance desk is theirs too — but not the contract one, which is GL's.
|
||
FREIGHT_PERMS.bookings.clearanceGetNotification,
|
||
FREIGHT_PERMS.ruleEngine.getNotification,
|
||
],
|
||
director: [
|
||
...STAFF_DASHBOARD_KEYS,
|
||
FREIGHT_PERMS.bookings.view,
|
||
FREIGHT_PERMS.bookings.approveDirector,
|
||
FREIGHT_PERMS.bookings.rejectApproval,
|
||
FREIGHT_PERMS.bookings.generateContract,
|
||
FREIGHT_PERMS.contracts.view,
|
||
FREIGHT_PERMS.contracts.approveDirector,
|
||
FREIGHT_PERMS.contracts.generateContract,
|
||
...allRuleEngineViewKeys(),
|
||
...BOOKING_DESK_NOTIFICATION_KEYS,
|
||
],
|
||
ceo: [
|
||
...STAFF_DASHBOARD_KEYS,
|
||
FREIGHT_PERMS.bookings.view,
|
||
FREIGHT_PERMS.bookings.approveCeo,
|
||
FREIGHT_PERMS.bookings.rejectApproval,
|
||
FREIGHT_PERMS.contracts.view,
|
||
FREIGHT_PERMS.contracts.approveCeo,
|
||
...allRuleEngineViewKeys(),
|
||
...BOOKING_DESK_NOTIFICATION_KEYS,
|
||
],
|
||
finance: [
|
||
...STAFF_DASHBOARD_KEYS,
|
||
FREIGHT_PERMS.bookings.view,
|
||
FREIGHT_PERMS.invoices.view,
|
||
FREIGHT_PERMS.invoices.export,
|
||
// Manual settlement (bank transfer / counter) of USD and ETB invoices.
|
||
FREIGHT_PERMS.invoices.confirmOffline,
|
||
// Read-only: the worklist offers whichever currencies are switched on.
|
||
// Flipping the switch is deliberately NOT here — see `manualPayment`.
|
||
FREIGHT_PERMS.settings.manualPayment.view,
|
||
// Deliberately NOT granted here: invoices:eims_register, eims_resolve, eims_cancel,
|
||
// eims_receipt_register, eims:memo_issue. Automatic filing needs no human permission at all
|
||
// (the cron sweep runs as the system); these are the *manual* exceptional-operations
|
||
// endpoints, and stay off the general Finance role. They are granted to the `chief` position
|
||
// instead — see below — the same maker–checker split already used for shipping-line credit
|
||
// mark-paid/cancel (Finance raises, chief decides).
|
||
FREIGHT_PERMS.payments.view,
|
||
FREIGHT_PERMS.bookings.wagonCancellationView,
|
||
// Shipping-line credit ledger is a Finance surface: bill batches into
|
||
// invoices and RAISE manual invoice actions. Approval of those actions is
|
||
// deliberately absent — it sits with the chief (maker–checker).
|
||
FREIGHT_PERMS.shippingLineCredits.view,
|
||
FREIGHT_PERMS.shippingLineCredits.invoice,
|
||
FREIGHT_PERMS.shippingLineCredits.invoiceMarkPaid,
|
||
FREIGHT_PERMS.shippingLineCredits.invoiceCancel,
|
||
],
|
||
// Global Logistics: manages ONLY the customs-clearance queue. Scoped out of
|
||
// the general booking-request list (no bookings:view) — instead a dedicated
|
||
// clearance:view permission lists the clearance bookings. Reviews customer
|
||
// clearance documents, uploads customs output documents, and finalizes the
|
||
// clearance gate.
|
||
// GL Ethiopia (edr_gl_ethiopia): reviews pre-booking clearance docs on the
|
||
// contract, uploads ET output docs, finalizes clearance, and EXCLUSIVELY creates
|
||
// the booking under a customs contract (Path B). Also runs post-booking ET
|
||
// milestones + the legacy booking-clearance permissions during migration.
|
||
glEthiopia: [
|
||
FREIGHT_PERMS.contracts.view,
|
||
FREIGHT_PERMS.contracts.clearanceReview,
|
||
FREIGHT_PERMS.contracts.finalizeClearance,
|
||
FREIGHT_PERMS.contracts.createBooking,
|
||
FREIGHT_PERMS.contracts.clearanceEtActions,
|
||
FREIGHT_PERMS.contracts.clearanceDutyAdvise,
|
||
FREIGHT_PERMS.contracts.finalInvoiceConfirm,
|
||
FREIGHT_PERMS.bookings.clearanceView,
|
||
FREIGHT_PERMS.bookings.reviewDocuments,
|
||
FREIGHT_PERMS.bookings.uploadClearanceOutput,
|
||
FREIGHT_PERMS.bookings.finalizeClearance,
|
||
FREIGHT_PERMS.bookings.operations,
|
||
...CLEARANCE_DESK_NOTIFICATION_KEYS,
|
||
],
|
||
// GL Djibouti (edr_gl_djibouti): DO/RO collection, gatepass, loading milestones,
|
||
// damage reports. Read-only on the contract; no booking creation.
|
||
glDjibouti: [
|
||
FREIGHT_PERMS.contracts.view,
|
||
FREIGHT_PERMS.contracts.clearanceDjActions,
|
||
FREIGHT_PERMS.contracts.finalInvoiceRaise,
|
||
FREIGHT_PERMS.contracts.finalInvoiceConfirm,
|
||
FREIGHT_PERMS.bookings.clearanceView,
|
||
FREIGHT_PERMS.bookings.uploadClearanceOutput,
|
||
FREIGHT_PERMS.bookings.operations,
|
||
...CLEARANCE_DESK_NOTIFICATION_KEYS,
|
||
],
|
||
// Marketing handles intake through contract (same as line staff here) and,
|
||
// for non-customs bookings, reviews/finalizes the customer's clearance
|
||
// documents from the booking detail (customs bookings go to Global Logistics).
|
||
marketing: [
|
||
...STAFF_DASHBOARD_KEYS,
|
||
FREIGHT_PERMS.bookings.view,
|
||
FREIGHT_PERMS.bookings.staffAccept,
|
||
FREIGHT_PERMS.bookings.requestChanges,
|
||
FREIGHT_PERMS.bookings.reject,
|
||
FREIGHT_PERMS.bookings.approveLineStaff,
|
||
FREIGHT_PERMS.bookings.rejectApproval,
|
||
FREIGHT_PERMS.bookings.cancel,
|
||
FREIGHT_PERMS.bookings.wagonCancellationView,
|
||
FREIGHT_PERMS.bookings.wagonCancellationVoid,
|
||
FREIGHT_PERMS.bookings.wagonCancellationRebook,
|
||
FREIGHT_PERMS.bookings.generateContract,
|
||
FREIGHT_PERMS.bookings.signStaff,
|
||
FREIGHT_PERMS.bookings.reviewDocuments,
|
||
FREIGHT_PERMS.bookings.finalizeClearance,
|
||
FREIGHT_PERMS.contracts.view,
|
||
...bothFreightTypes(FREIGHT_PERMS.contracts.staffAccept),
|
||
...bothFreightTypes(FREIGHT_PERMS.contracts.requestChanges),
|
||
...bothFreightTypes(FREIGHT_PERMS.contracts.reject),
|
||
FREIGHT_PERMS.contracts.approveLineStaff,
|
||
FREIGHT_PERMS.contracts.generateContract,
|
||
...bothFreightTypes(FREIGHT_PERMS.contracts.signStaff),
|
||
FREIGHT_PERMS.contracts.suspend,
|
||
FREIGHT_PERMS.contracts.editDocument,
|
||
...BOOKING_DESK_NOTIFICATION_KEYS,
|
||
],
|
||
orgManager: [...BOOKING_RULE_ENGINE_PERMISSION_KEYS],
|
||
} as const;
|
||
|
||
/**
|
||
* Position permission presets (positions-as-roles). Grants flow to users via
|
||
* Position → PositionPermission (NOT Role/RolePermission). Each reuses the
|
||
* matching ROLE_PERMISSION_PRESETS key-array as a building block and adds the
|
||
* gap-controller keys the position needs. Deduped via Set.
|
||
*/
|
||
const dedupe = (keys: string[]): string[] => [...new Set(keys)];
|
||
|
||
export const POSITION_PERMISSION_PRESETS = {
|
||
// Chief: senior operational role — intake/line-staff approval + director
|
||
// approval + scheduling/ops, plus container allocation.
|
||
chief: dedupe([
|
||
...ROLE_PERMISSION_PRESETS.lineStaff,
|
||
...ROLE_PERMISSION_PRESETS.director,
|
||
...ROLE_PERMISSION_PRESETS.operationsOfficer,
|
||
FREIGHT_PERMS.allocation.manage,
|
||
// Customer desk: onboarding intake lands on the chief — open the customer
|
||
// list and approve/suspend a submitted profile. Deliberately NOT granted:
|
||
// create, update and password reset, which stay with the customer admins.
|
||
FREIGHT_PERMS.customers.view,
|
||
FREIGHT_PERMS.customers.verify,
|
||
FREIGHT_PERMS.customers.deactivate,
|
||
// …and therefore the profile-review pings company-notifier emits.
|
||
FREIGHT_PERMS.customers.getNotification,
|
||
// The chief also owns the customer-facing support inbox.
|
||
FREIGHT_PERMS.support.agentView,
|
||
FREIGHT_PERMS.support.agentSend,
|
||
// Senior commercial oversight: government expedite + the finance views.
|
||
FREIGHT_PERMS.bookings.governmentExpedite,
|
||
FREIGHT_PERMS.invoices.view,
|
||
FREIGHT_PERMS.invoices.export,
|
||
// Manual MoR EIMS actions and credit/debit memo issuance: kept off the general Finance role
|
||
// (see that preset's comment) and granted here instead — the chief is already the decision
|
||
// side of every other sensitive finance action (mark-paid/cancel approval below), and these
|
||
// are irreversible-at-MoR or receivable-creating in the same way.
|
||
FREIGHT_PERMS.invoices.eimsCancel,
|
||
FREIGHT_PERMS.invoices.eimsResolve,
|
||
FREIGHT_PERMS.invoices.eimsReceiptRegister,
|
||
FREIGHT_PERMS.invoices.memoIssue,
|
||
FREIGHT_PERMS.payments.view,
|
||
// Decision side of the credit-invoice two-step: finance raises
|
||
// mark-paid/cancel requests, the chief approves or rejects them.
|
||
FREIGHT_PERMS.shippingLineCredits.view,
|
||
FREIGHT_PERMS.shippingLineCredits.invoiceApprove,
|
||
FREIGHT_PERMS.shippingLineCredits.invoiceReject,
|
||
]),
|
||
// Director additionally manages train scheduling + rail fleet (same block the
|
||
// operation officer/chief hold), on top of the approval-chain role preset,
|
||
// and carries the same full warehouse authority the chief tier holds.
|
||
director: dedupe([
|
||
...ROLE_PERMISSION_PRESETS.director,
|
||
FREIGHT_PERMS.trainScheduling.view,
|
||
FREIGHT_PERMS.trainScheduling.create,
|
||
FREIGHT_PERMS.trainScheduling.update,
|
||
FREIGHT_PERMS.trainScheduling.cancel,
|
||
FREIGHT_PERMS.trainScheduling.reschedule,
|
||
FREIGHT_PERMS.trainScheduling.rulesManage,
|
||
FREIGHT_PERMS.trainScheduling.editTrainNumber,
|
||
FREIGHT_PERMS.fleet.view,
|
||
FREIGHT_PERMS.fleet.manage,
|
||
...FLEET_GRANULAR_KEYS,
|
||
// Warehouse — full CRUD, matching the chief tier. Unlike the dispatcher,
|
||
// the director also owns the allocation and fee rules themselves.
|
||
FREIGHT_PERMS.warehouseDashboard.view,
|
||
...Object.values(FREIGHT_PERMS.warehouses),
|
||
...Object.values(FREIGHT_PERMS.warehouseYards),
|
||
...Object.values(FREIGHT_PERMS.warehouseZones),
|
||
...Object.values(FREIGHT_PERMS.warehouseAllocationRules),
|
||
...Object.values(FREIGHT_PERMS.warehouseFeeRules),
|
||
...Object.values(FREIGHT_PERMS.warehouseInventory),
|
||
...Object.values(FREIGHT_PERMS.warehouseInspectionReports),
|
||
...Object.values(FREIGHT_PERMS.interchangeDocuments),
|
||
...Object.values(FREIGHT_PERMS.warehouseFeeInvoices),
|
||
]),
|
||
ceo: dedupe([...ROLE_PERMISSION_PRESETS.ceo]),
|
||
ethiopianGl: dedupe([...ROLE_PERMISSION_PRESETS.glEthiopia]),
|
||
djiboutiGl: dedupe([...ROLE_PERMISSION_PRESETS.glDjibouti]),
|
||
marketer: dedupe([...ROLE_PERMISSION_PRESETS.marketing]),
|
||
operation: dedupe([
|
||
...ROLE_PERMISSION_PRESETS.operationsOfficer,
|
||
FREIGHT_PERMS.allocation.manage,
|
||
]),
|
||
// Operations Chief: full operational authority — the entire freight
|
||
// permission catalog (all CRUD across bookings, contracts, scheduling,
|
||
// fleet, warehouse, mile, finance, settings, staff).
|
||
operationsChief: dedupe([...BOOKING_RULE_ENGINE_PERMISSION_KEYS]),
|
||
// Dispatcher: full CRUD on warehouse management (incl. import/export/intercity
|
||
// inventory flows) and fleet management, plus truck dispatch on the mile legs
|
||
// and operational context. The ONE carve-out: allocation & fee rules stay
|
||
// VIEW-ONLY — a dispatcher never creates/updates/deletes those rules.
|
||
dispatcher: dedupe([
|
||
// Warehouse management — full CRUD.
|
||
FREIGHT_PERMS.warehouseDashboard.view,
|
||
...Object.values(FREIGHT_PERMS.warehouses),
|
||
...Object.values(FREIGHT_PERMS.warehouseYards),
|
||
...Object.values(FREIGHT_PERMS.warehouseZones),
|
||
...Object.values(FREIGHT_PERMS.warehouseInventory),
|
||
...Object.values(FREIGHT_PERMS.warehouseInspectionReports),
|
||
...Object.values(FREIGHT_PERMS.interchangeDocuments),
|
||
...Object.values(FREIGHT_PERMS.warehouseFeeInvoices),
|
||
// View-only on the rules that govern allocation and fees.
|
||
FREIGHT_PERMS.warehouseAllocationRules.view,
|
||
FREIGHT_PERMS.warehouseFeeRules.view,
|
||
// Fleet management — full CRUD.
|
||
...Object.values(FREIGHT_PERMS.fleet),
|
||
FREIGHT_PERMS.fleetDashboard.view,
|
||
...Object.values(FREIGHT_PERMS.fleetReports),
|
||
...Object.values(FREIGHT_PERMS.vehicles),
|
||
...Object.values(FREIGHT_PERMS.drivers),
|
||
...Object.values(FREIGHT_PERMS.tracking),
|
||
...Object.values(FREIGHT_PERMS.fuel),
|
||
...Object.values(FREIGHT_PERMS.maintenance),
|
||
...Object.values(FREIGHT_PERMS.locomotives),
|
||
...Object.values(FREIGHT_PERMS.wagons),
|
||
...Object.values(FREIGHT_PERMS.trains),
|
||
...Object.values(FREIGHT_PERMS.routes),
|
||
...Object.values(FREIGHT_PERMS.containers),
|
||
...Object.values(FREIGHT_PERMS.cargoes),
|
||
// Truck dispatch on the EDR mile legs + operational context.
|
||
...Object.values(FREIGHT_PERMS.firstMile),
|
||
...Object.values(FREIGHT_PERMS.lastMile),
|
||
FREIGHT_PERMS.trainScheduling.view,
|
||
FREIGHT_PERMS.bookings.operations,
|
||
// Road-fleet back office: vendors, asset lifecycle, compliance, facilities.
|
||
...Object.values(FREIGHT_PERMS.procurement),
|
||
...Object.values(FREIGHT_PERMS.compliance),
|
||
...Object.values(FREIGHT_PERMS.facilities),
|
||
]),
|
||
// Truck & Machinery chief: reviews and approves/rejects last-mile
|
||
// confirmation requests (the pre-approval gate ahead of vehicle assignment),
|
||
// plus enough fleet visibility to judge truck availability.
|
||
truckMachineryChief: dedupe([
|
||
FREIGHT_PERMS.lastMile.view,
|
||
FREIGHT_PERMS.lastMile.requestView,
|
||
FREIGHT_PERMS.lastMile.requestReview,
|
||
FREIGHT_PERMS.lastMile.requestApprove,
|
||
FREIGHT_PERMS.fleetDashboard.view,
|
||
FREIGHT_PERMS.vehicles.view,
|
||
]),
|
||
} as const;
|
||
|
||
/** Derive the module bucket from the resource segment of a permission key. */
|
||
const moduleOf = (key: string): string => key.split(":")[1] ?? "other";
|
||
|
||
export const PERMISSIONS_CATALOG = [
|
||
...BOOKING_RULE_ENGINE_PERMISSIONS,
|
||
...FREIGHT_READ_PERMISSIONS,
|
||
].map((p) => ({
|
||
key: p.key,
|
||
label: p.name.en,
|
||
module: moduleOf(p.key),
|
||
}));
|