mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
459 lines
17 KiB
TypeScript
459 lines
17 KiB
TypeScript
import type { AuthUser } from "@/auth/types";
|
|
import type { RuleEngineResourceSlug } from "@/types/rule-engine";
|
|
|
|
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",
|
|
},
|
|
contracts: {
|
|
view: "edr_freight_app:contracts:view",
|
|
staffAccept: "edr_freight_app:contracts:staff_accept",
|
|
requestChanges: "edr_freight_app:contracts:request_changes",
|
|
reject: "edr_freight_app:contracts:reject",
|
|
approveLineStaff: "edr_freight_app:contracts:approve_line_staff",
|
|
approveDirector: "edr_freight_app:contracts:approve_director",
|
|
approveCeo: "edr_freight_app:contracts:approve_ceo",
|
|
generateContract: "edr_freight_app:contracts:generate_contract",
|
|
signStaff: "edr_freight_app:contracts:sign_staff",
|
|
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",
|
|
clearanceDutyAdvise: "edr_freight_app:contracts:clearance_duty_advise",
|
|
clearanceEtActions: "edr_freight_app:contracts:clearance_et_actions",
|
|
clearanceDjActions: "edr_freight_app:contracts:clearance_dj_actions",
|
|
},
|
|
trainScheduling: {
|
|
view: "edr_freight_app:train_scheduling:view",
|
|
manage: "edr_freight_app:train_scheduling:manage",
|
|
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",
|
|
},
|
|
fleet: {
|
|
view: "edr_freight_app:fleet:view",
|
|
manage: "edr_freight_app:fleet:manage",
|
|
},
|
|
admin: "edr_freight_app:admin",
|
|
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",
|
|
},
|
|
payments: {
|
|
view: "edr_freight_app:payments:view",
|
|
verify: "edr_freight_app:payments:verify",
|
|
refund: "edr_freight_app:payments:refund",
|
|
},
|
|
invoices: {
|
|
view: "edr_freight_app:invoices:view",
|
|
create: "edr_freight_app:invoices:create",
|
|
cancel: "edr_freight_app:invoices:cancel",
|
|
export: "edr_freight_app:invoices:export",
|
|
},
|
|
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",
|
|
},
|
|
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",
|
|
},
|
|
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",
|
|
},
|
|
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",
|
|
},
|
|
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",
|
|
},
|
|
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",
|
|
},
|
|
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",
|
|
},
|
|
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",
|
|
approve: "edr_freight_app:fuel:approve",
|
|
},
|
|
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",
|
|
complete: "edr_freight_app:maintenance:complete",
|
|
},
|
|
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",
|
|
},
|
|
config: {
|
|
contractValidity: {
|
|
view: "edr_freight_app:config:contract_validity:view",
|
|
manage: "edr_freight_app:config:contract_validity:manage",
|
|
},
|
|
},
|
|
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",
|
|
},
|
|
},
|
|
staff: {
|
|
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",
|
|
},
|
|
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;
|
|
|
|
const slugToResourceKey = (slug: RuleEngineResourceSlug): string =>
|
|
slug.replace(/-/g, "_");
|
|
|
|
export function getPermissionKeys(user: AuthUser | null | undefined): string[] {
|
|
if (!user) return [];
|
|
if (user.permissionKeys?.length) return user.permissionKeys;
|
|
|
|
const keys = new Set<string>();
|
|
for (const p of user.permissions ?? []) {
|
|
if (p.key) keys.add(p.key);
|
|
}
|
|
for (const emp of user.employee ?? []) {
|
|
for (const pos of emp.positions ?? []) {
|
|
for (const p of pos.permissions ?? []) {
|
|
if (p.key) keys.add(p.key);
|
|
}
|
|
}
|
|
}
|
|
return [...keys];
|
|
}
|
|
|
|
/**
|
|
* Position keys held by the user (e.g. "ethiopian_gl", "djibouti_gl").
|
|
* Tolerates IAM payload shape variants: the key flat on the employee position,
|
|
* nested under `position.key`, or the GL modeled as a role instead.
|
|
*/
|
|
export function getPositionKeys(user: AuthUser | null | undefined): string[] {
|
|
if (!user) return [];
|
|
const keys = new Set<string>();
|
|
for (const emp of user.employee ?? []) {
|
|
for (const pos of emp.positions ?? []) {
|
|
if (pos.key) keys.add(pos.key);
|
|
if (pos.position?.key) keys.add(pos.position.key);
|
|
}
|
|
}
|
|
for (const role of user.roles ?? []) {
|
|
if (role.key) keys.add(role.key);
|
|
}
|
|
return [...keys];
|
|
}
|
|
|
|
export function hasPosition(
|
|
user: AuthUser | null | undefined,
|
|
positionKey: string,
|
|
): boolean {
|
|
return getPositionKeys(user).includes(positionKey);
|
|
}
|
|
|
|
export const POSITION_KEYS = {
|
|
ethiopianGl: "ethiopian_gl",
|
|
djiboutiGl: "djibouti_gl",
|
|
} as const;
|
|
|
|
export function isEthiopianGl(user: AuthUser | null | undefined): boolean {
|
|
return hasPosition(user, POSITION_KEYS.ethiopianGl);
|
|
}
|
|
|
|
export function isDjiboutiGl(user: AuthUser | null | undefined): boolean {
|
|
return hasPosition(user, POSITION_KEYS.djiboutiGl);
|
|
}
|
|
|
|
export function isSuperAdmin(user: AuthUser | null | undefined): boolean {
|
|
if (user?.isSuperAdmin) return true;
|
|
return Boolean(user?.roles?.some((r) => r.key === "super_admin"));
|
|
}
|
|
|
|
/** Org-level admins may act on any approval step in the chain. */
|
|
export function isOrganizationAdmin(
|
|
user: AuthUser | null | undefined,
|
|
): boolean {
|
|
return Boolean(user?.roles?.some((r) => r.key === "organization_admin"));
|
|
}
|
|
|
|
export function isFreightApprovalAdmin(
|
|
user: AuthUser | null | undefined,
|
|
): boolean {
|
|
return isSuperAdmin(user) || isOrganizationAdmin(user);
|
|
}
|
|
|
|
export function hasPermission(
|
|
user: AuthUser | null | undefined,
|
|
key: string,
|
|
): boolean {
|
|
if (!user) return false;
|
|
if (isSuperAdmin(user)) return true;
|
|
return getPermissionKeys(user).includes(key);
|
|
}
|
|
|
|
export function canAccessBookings(user: AuthUser | null | undefined): boolean {
|
|
return hasPermission(user, FREIGHT_PERMS.bookings.view);
|
|
}
|
|
|
|
export function canAccessContracts(user: AuthUser | null | undefined): boolean {
|
|
return hasPermission(user, FREIGHT_PERMS.contracts.view);
|
|
}
|
|
|
|
/** Can review the GL Ethiopia pre-booking contract clearance queue (Path B). */
|
|
export function canReviewContractClearance(
|
|
user: AuthUser | null | undefined,
|
|
): boolean {
|
|
return hasPermission(user, FREIGHT_PERMS.contracts.clearanceReview);
|
|
}
|
|
|
|
/** GL Ethiopia: can create a booking under a cleared contract (Path B). */
|
|
export function canCreateContractBooking(
|
|
user: AuthUser | null | undefined,
|
|
): boolean {
|
|
return hasPermission(user, FREIGHT_PERMS.contracts.createBooking);
|
|
}
|
|
|
|
/** Operations: can review the Path A self-clearance queue (non-customs). */
|
|
export function canReviewSelfClearance(
|
|
user: AuthUser | null | undefined,
|
|
): boolean {
|
|
return hasPermission(user, FREIGHT_PERMS.contracts.opsClearanceReview);
|
|
}
|
|
|
|
/** Can see/manage the customs document-clearance queue (Global Logistics). */
|
|
export function canViewClearance(user: AuthUser | null | undefined): boolean {
|
|
return hasPermission(user, FREIGHT_PERMS.bookings.reviewDocuments);
|
|
}
|
|
|
|
export function canViewScheduling(user: AuthUser | null | undefined): boolean {
|
|
return hasPermission(user, FREIGHT_PERMS.trainScheduling.view);
|
|
}
|
|
|
|
export function canManageScheduling(user: AuthUser | null | undefined): boolean {
|
|
return hasPermission(user, FREIGHT_PERMS.trainScheduling.manage);
|
|
}
|
|
|
|
export function canViewFleet(user: AuthUser | null | undefined): boolean {
|
|
return hasPermission(user, FREIGHT_PERMS.fleet.view);
|
|
}
|
|
|
|
export function isFreightAdmin(user: AuthUser | null | undefined): boolean {
|
|
return hasPermission(user, FREIGHT_PERMS.admin);
|
|
}
|
|
|
|
export function ruleEngineViewKey(slug: RuleEngineResourceSlug): string {
|
|
return `edr_freight_app:rule_engine:${slugToResourceKey(slug)}:view`;
|
|
}
|
|
|
|
export function ruleEngineManageKey(slug: RuleEngineResourceSlug): string {
|
|
return `edr_freight_app:rule_engine:${slugToResourceKey(slug)}:manage`;
|
|
}
|
|
|
|
export function canAccessRuleEngineResource(
|
|
user: AuthUser | null | undefined,
|
|
slug: RuleEngineResourceSlug,
|
|
mode: "view" | "manage",
|
|
): boolean {
|
|
const key = mode === "manage" ? ruleEngineManageKey(slug) : ruleEngineViewKey(slug);
|
|
return hasPermission(user, key);
|
|
}
|
|
|
|
export function canAccessAnyRuleEngineView(
|
|
user: AuthUser | null | undefined,
|
|
slugs: RuleEngineResourceSlug[],
|
|
): boolean {
|
|
return slugs.some((slug) => canAccessRuleEngineResource(user, slug, "view"));
|
|
}
|