This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
Crewing Architecture
Authoritative build spec: Crewing Implementation Spec carries the full reconciled instructions (codebase changes, roles/nav matrices, screen spec). This page is the module/layer layout, aligned with that spec.
The Crewing Module is not a new application — it is a feature area inside the existing Pelagia Portal. It adopts the same stack and the same patterns documented in Architecture; this page only describes what crewing adds.
1. Where it lives in the stack
┌─────────────────────────────────────────────┐
│ Browser — React 19 + shadcn/ui + Tailwind │
│ Crewing pages: requisitions, candidates, │
│ crew, leave planner, attendance, payroll │
└───────────────────┬──────────────────────────┘
│ HTTPS
┌───────────────────▼──────────────────────────┐
│ Next.js 15 App Server │
│ RSC pages (read) │ Server Actions (write) │
│ ┌──────────────────────────────────────────┐│
│ │ requisition-state-machine ││
│ │ application-pipeline (candidate gates) ││
│ │ onboarding side-effects ││
│ │ wage-report generator ││
│ │ permissions (extended) · crew-notifier ││
│ └──────────────────────────────────────────┘│
└───────┬───────────────┬───────────────┬───────┘
│ │ │
┌───────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐
│ PostgreSQL │ │ R2 / local │ │ Resend │
│ (Prisma) │ │ docs, CVs, │ │ (email) │
│ crew tables │ │ PPE, signs │ │ │
└──────────────┘ └─────────────┘ └─────────────┘
▲
┌───────┴──────────────────────────────────────┐
│ (future) CvParseService · EpfoService │
│ best-effort CV extraction; UAN/Aadhaar check │
│ same out-of-process pattern as GstService │
└───────────────────────────────────────────────┘
Reused as-is: NextAuth v5 session/auth, lib/storage.ts (file storage),
lib/notifier.ts (email + Notification rows), Vessel/Site/User,
Recharts for dashboards.
2. Component diagram
flowchart TB
subgraph UI["UI — app/(portal)/crewing/*"]
REQ["Requisitions<br/>/crewing/requisitions"]
CAND["Candidates & Pipeline<br/>/crewing/candidates"]
CREW["Crew Directory<br/>/crewing/crew/[id]"]
PLAN["Leave Planner<br/>/crewing/leave"]
ATT["Attendance<br/>/crewing/attendance"]
PPE["PPE Register<br/>/crewing/ppe"]
APPR["Appraisals<br/>/crewing/appraisals"]
PAY["Wage Reports<br/>/crewing/payroll"]
VERI["Office Verification<br/>/crewing/verify"]
APPLY["Public Apply<br/>/careers/apply"]
end
subgraph SA["Server Actions — actions.ts (mutations)"]
A1["requisition actions"]
A2["application/pipeline actions"]
A3["onboarding action"]
A4["crew-record actions<br/>(docs/bank/epf/nok)"]
A5["ppe actions"]
A6["leave + attendance actions"]
A7["appraisal actions"]
A8["payroll actions"]
A9["verification actions"]
end
subgraph LIB["Domain logic — lib/"]
SM1["requisition-state-machine.ts"]
SM2["application-pipeline.ts"]
SM3["assignment-lifecycle.ts"]
SM4["appraisal-lifecycle.ts"]
ON["onboarding.ts (side-effects)"]
WR["wage-report.ts (generator)"]
PERM["permissions.ts (extended)"]
NOT["crew-notifier.ts → notifier.ts"]
REQDOC["rank-doc-requirements.ts"]
end
subgraph RH["Route Handlers — api/ (non-mutations)"]
H1["/api/careers/apply (public POST)"]
H2["/api/crewing/cv-parse"]
H3["/api/crewing/payroll/[id]/export"]
H4["/api/files/sign (reused)"]
end
subgraph DATA["Persistence"]
DB[("PostgreSQL<br/>Prisma crew models")]
FS[("File storage<br/>CVs · docs · PPE · signatures")]
end
subgraph EXT["External / future"]
CVP["CvParseService"]
EPFO["EpfoService"]
MAIL["Resend / console"]
end
REQ --> A1 --> SM1 --> DB
CAND --> A2 --> SM2 --> DB
CAND --> A3 --> ON --> DB
ON --> SM3
APPLY --> H1 --> H2 --> CVP
H1 --> DB
CREW --> A4 --> DB
A4 --> REQDOC
PPE --> A5 --> DB
PLAN --> A6
ATT --> A6 --> DB
A6 -.leave clash.-> SM1
APPR --> A7 --> SM4 --> DB
PAY --> A8 --> WR --> DB
PAY --> H3
VERI --> A9 --> DB
A9 --> EPFO
A1 & A2 & A3 & A7 & A8 --> NOT --> MAIL
A1 & A2 & A3 & A4 & A5 & A6 & A7 & A8 & A9 --> PERM
A4 & A5 & H1 --> FS
WR -.reads.-> DB
3. Module layout
app/(portal)/crewing/
├── requisitions/ # list, new, [id] (vacancies)
├── candidates/ # pipeline board, [id] (per-candidate gates)
│ └── [id]/onboard/ # joining formalities → CrewAssignment
├── crew/ # directory, crew/[id] (records, docs, bank, epf, ppe)
├── leave/ # site leave planner + apply
├── attendance/ # daily capture per site
├── ppe/ # PPE issue register
├── appraisals/ # raise / verify / approve
├── payroll/ # monthly wage reports
└── verify/ # office verification queue (MPO / Accounts)
app/careers/apply/ # public candidate application (CV upload)
app/api/
├── careers/apply/ # public POST (rate-limited)
├── crewing/cv-parse/ # CV → extracted fields
└── crewing/payroll/[id]/export/ # XLSX/PDF wage report
lib/
├── requisition-state-machine.ts # vacancy transitions + roles
├── application-pipeline.ts # candidate gate transitions
├── assignment-lifecycle.ts # active ↔ on-leave → signed-off
├── appraisal-lifecycle.ts # draft → verified → approved
├── onboarding.ts # the side-effecting onboard step
├── wage-report.ts # crew × salary × days generator
├── rank-doc-requirements.ts # which docs a rank needs
├── crew-notifier.ts # crew events → notifier.ts
└── permissions.ts # + crewing permissions
This mirrors the PO module's split: Server Actions for every mutation,
route handlers only for the public apply endpoint, CV parsing, file signing
and report export. State changes go through a single state-machine module and
are recorded as CrewAction rows.
4. Cross-cutting concerns
| Concern | Approach (reused) |
|---|---|
| Auth | NextAuth v5 session; public apply route is unauthenticated + rate-limited. |
| Authorisation | requirePermission(role, permission) at the top of every Server Action; RSC pages gate whole segments. See Crewing Roles & Permissions. |
| Audit | CrewAction rows (actor, type, note, metadata) — the POAction pattern. |
| Files | lib/storage.ts signed uploads (R2 prod / local dev) for CVs, documents, PPE proofs, signatures. |
| Email / in-app | lib/notifier.ts; events persisted as Notification. |
| Decimals | salary/victualing are Decimal; convert with Number() before crossing into Client Components (same gotcha as PO line items). |
| Feature flag | whole surface behind NEXT_PUBLIC_CREWING_ENABLED for staged rollout. |
| Migrations | new models = new Prisma migration, reviewed in a PR; never edit schema.prisma without one. |
5. Integration points with the PO module
- Cost centre — crew assignments reference the same
Vessel/Siteused by POs, so crew cost and PO spend share a cost-centre axis for reporting. - PPE as inventory (optional) — if the inventory flag
is on, PPE issues can draw down
ItemInventoryat a site, reusing the consumption model. - Notifications & dashboard — crew events surface in the same notification bell; the dashboard gains crewing stat cards.
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.