Merge branch 'dev'

This commit is contained in:
Marshal
2026-08-13 14:14:31 +00:00
219 changed files with 13585 additions and 5559 deletions

View File

@@ -47,6 +47,55 @@ const perm = (id: string, key: string, en: string): FreightPermissionSeed => ({
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",
@@ -506,6 +555,20 @@ export const FINANCE_PERMISSIONS: FreightPermissionSeed[] = [
"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",
),
// 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(
@@ -1240,6 +1303,16 @@ export const CONFIG_SETTINGS_PERMISSIONS: FreightPermissionSeed[] = [
"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
@@ -1535,6 +1608,7 @@ export const NOTIFICATION_PERMISSIONS: FreightPermissionSeed[] = [
];
export const ADVANCED_BACKOFFICE_PERMISSIONS: FreightPermissionSeed[] = [
...REPORT_PERMISSIONS,
...CUSTOMER_PERMISSIONS,
...SHIPPING_LINE_PERMISSIONS,
...FINANCE_PERMISSIONS,
@@ -1764,6 +1838,8 @@ export const FREIGHT_PERMS = {
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",
confirmOffline: "edr_freight_app:invoices:confirm_offline",
},
firstMile: {
@@ -1976,6 +2052,12 @@ export const FREIGHT_PERMS = {
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.
@@ -2028,6 +2110,7 @@ export const FREIGHT_PERMS = {
},
reports: {
view: "edr_freight_app:reports:view",
report: (key: ReportKey): string => reportPermissionKey(key),
},
staff: {
users: {
@@ -2172,11 +2255,17 @@ const FLEET_GRANULAR_KEYS: string[] = [
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
@@ -2282,10 +2371,10 @@ export const ROLE_PERMISSION_PRESETS = {
FREIGHT_PERMS.bookings.view,
FREIGHT_PERMS.invoices.view,
FREIGHT_PERMS.invoices.export,
// Deliberately NOT granted here: invoices:eims_register and invoices:eims_resolve.
// Invoices are filed with MoR by the workflow, not by a person, so filing is not a
// Finance job function — the endpoints exist for controlled testing and exceptional
// operations, and are assigned to named admins rather than a role preset.
// Deliberately NOT granted here: invoices:eims_register, eims_resolve, eims_cancel,
// eims_receipt_register. Invoices are filed with MoR by the workflow, not by a person, so
// filing is not a Finance job function — the endpoints exist for controlled testing and
// exceptional operations, and are assigned to named admins rather than a role preset.
FREIGHT_PERMS.payments.view,
FREIGHT_PERMS.bookings.wagonCancellationView,
],