Roles and Permissions
Authorisation is centralised in lib/permissions.ts. Server Actions call
requirePermission(role, permission) at the top before any DB write;
hasPermission(role, permission) gates UI and page segments. The PO state
machine adds a second gate (status + role) on top of permissions — see
PO Lifecycle.
The seven roles
| Role | Who they are | Core capability |
|---|---|---|
| TECHNICAL | Deck / engine crew | Create, submit, track own POs; confirm receipt; add (unverified) vendors |
| MANNING | Crew-management staff | Same as Technical |
| ACCOUNTS | Finance / accounts | Process payments (records ref + date); manage/verify vendors; view all POs |
| MANAGER | Department manager | Review/approve/reject/request-edits; edit line items pre-approval; manage cost centres, items, vendors, sites; analytics; import |
| SUPERUSER | Power user / ops lead | Combined Technical + Manning + Manager authority across PO actions |
| AUDITOR | Internal auditor | Read-only access to all POs and reports |
| ADMIN | System administrator | Manage users, companies, accounting codes, cost centres, sites, items, vendors |
Accounts are provisioned by an Admin or via Microsoft Entra SSO. There is no self-registration. SSO-only users have no password and may set one from their profile (any role can reach the profile page; only approvers upload a signature).
Permission → role matrix
The exact ROLE_PERMISSIONS map in lib/permissions.ts. ✓ = granted.
| Permission | TECH | MANNING | ACCOUNTS | MANAGER | SUPERUSER | AUDITOR | ADMIN |
|---|---|---|---|---|---|---|---|
create_po |
✓ | ✓ | ✓ | ✓ | |||
submit_po |
✓ | ✓ | ✓ | ✓ | |||
edit_own_draft_po |
✓ | ✓ | ✓ | ✓ | |||
view_own_pos |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | |
view_all_pos |
✓ | ✓ | ✓ | ✓ | ✓ | ||
approve_po |
✓ | ✓ | |||||
reject_po |
✓ | ✓ | |||||
cancel_po |
✓ | ✓ | |||||
request_edits |
✓ | ✓ | |||||
request_vendor_id |
✓ | ✓ | |||||
process_payment |
✓ | ✓ | ✓ | ||||
confirm_receipt |
✓ | ✓ | ✓ | ✓ | |||
view_analytics |
✓ | ✓ | ✓ | ✓ | |||
export_reports |
✓ | ✓ | ✓ | ✓ | |||
create_vendor |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | |
manage_vendors |
✓ | ✓ | ✓ | ||||
manage_products |
✓ | ✓ | |||||
manage_sites |
✓ | ✓ | |||||
manage_vessels_accounts |
✓ | ✓ | |||||
manage_users |
✓ |
Notes on the shipped behaviour
create_vendoris held by submitters too — vendors they add are created unverified and become verified when a PO closes/pays with them, on import, or via a Manager/Accounts/Admin (manage_vendors). Onlymanage_vendorsholders may assign the formal verifiedvendorId.- MANAGER is broad: it holds
process_payment,confirm_receipt, and themanage_*permissions for vendors, products, sites, and vessels/accounts — in addition to the approval permissions. - ADMIN does not create/approve POs — it is an administration role
(
manage_usersplus the catalogue/manage_*permissions) and can view/export. - AUDITOR is strictly read-only (
view_*,export_reports).
Feature-flagged access
Submitter view-all (NEXT_PUBLIC_SUBMITTER_VIEW_ALL_ENABLED)
By default submitters (TECHNICAL, MANNING) only see the POs they raised.
When NEXT_PUBLIC_SUBMITTER_VIEW_ALL_ENABLED="true" they gain read-only
access to every PO:
- the History page (
/history) and its CSV/PDF report export; - any other user's PO detail page (
/po/[id]); - the Export PDF / XLSX buttons on those POs (approved & cancelled POs only, same status gate as everyone else).
It is a pure read widening — it grants no approval, payment, edit, cancel, or
admin rights, and does not change what non-submitter roles can do. The flag is
opt-in (off unless the value is exactly "true").
Implementation lives in lib/permissions.ts:
| Helper | Meaning |
|---|---|
isSubmitterRole(role) |
role ∈ {TECHNICAL, MANNING} |
submitterCanViewAll(role) |
flag is on and isSubmitterRole(role) |
canViewAllPos(role) |
hasPermission(role, "view_all_pos") || submitterCanViewAll(role) |
canViewAllPos gates the PO detail page and the per-PO export route;
submitterCanViewAll is OR'd into the export_reports gate on the History page,
the report-export route, and the sidebar's History link. When the flag is off,
all of these collapse to the prior behaviour (view_all_pos holders only). The
my-orders "Closed Purchase Orders" list stays personal for submitters
regardless — History is the all-POs surface.
Business rules layered on top
Beyond the permission map, server actions and the state machine enforce:
- A vendor must be assigned before a manager can approve a PO.
- Only verified vendors (with a
vendorId) may be assigned viaprovideVendorId. - Discarding is only possible on
DRAFTPOs; the owner, MANAGER, or SUPERUSER may discard. - Receipt confirmation is the submitter's own PO (or SUPERUSER/MANAGER).
- Payment date is compulsory and cannot be in the future.
See the Testing page for the permission test matrix that pins these rules down with integration tests.
Pelagia Portal (PPMS)
Overview
Build & Run
System
Product
- Feature Catalogue
- Pages and Navigation
- Workflows
- Purchase Orders
- Vendors and GST Lookup
- Inventory and Catalogue
- Inventory on Approval
- Notifications
- File Storage
- Design System
Planned
Quality
Ops
Engineering
Pelagia Portal (PPMS) — internal purchase-order management. Self-hosted on pms1, live at pms.pelagiamarine.com. This wiki tracks the shipped product; authoritative sources are the repo code, App/CLAUDE.md, Docs/, and CHANGELOG.md.