From cd4f85918c6518711a146a9a1dca7ee4d85b3d71 Mon Sep 17 00:00:00 2001 From: Hardik Date: Sun, 21 Jun 2026 20:51:02 +0530 Subject: [PATCH] docs: Crewing management System Co-Authored-By: Claude Opus 4.8 --- Crewing-Architecture.md | 188 +++++++++++++++++ Crewing-Data-Model.md | 349 +++++++++++++++++++++++++++++++ Crewing-Design-Document.md | 164 +++++++++++++++ Crewing-Module.md | 72 +++++++ Crewing-Roles-and-Permissions.md | 102 +++++++++ Crewing-Use-Cases.md | 107 ++++++++++ Crewing-User-Stories.md | 329 +++++++++++++++++++++++++++++ Crewing-Workflows.md | 291 ++++++++++++++++++++++++++ README.md | 36 ++++ 9 files changed, 1638 insertions(+) create mode 100644 Crewing-Architecture.md create mode 100644 Crewing-Data-Model.md create mode 100644 Crewing-Design-Document.md create mode 100644 Crewing-Module.md create mode 100644 Crewing-Roles-and-Permissions.md create mode 100644 Crewing-Use-Cases.md create mode 100644 Crewing-User-Stories.md create mode 100644 Crewing-Workflows.md create mode 100644 README.md diff --git a/Crewing-Architecture.md b/Crewing-Architecture.md new file mode 100644 index 0000000..8780522 --- /dev/null +++ b/Crewing-Architecture.md @@ -0,0 +1,188 @@ +# Crewing Architecture + +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](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 + +```mermaid +flowchart TB + subgraph UI["UI — app/(portal)/crewing/*"] + REQ["Requisitions
/crewing/requisitions"] + CAND["Candidates & Pipeline
/crewing/candidates"] + CREW["Crew Directory
/crewing/crew/[id]"] + PLAN["Leave Planner
/crewing/leave"] + ATT["Attendance
/crewing/attendance"] + PPE["PPE Register
/crewing/ppe"] + APPR["Appraisals
/crewing/appraisals"] + PAY["Wage Reports
/crewing/payroll"] + VERI["Office Verification
/crewing/verify"] + APPLY["Public Apply
/careers/apply"] + end + + subgraph SA["Server Actions — actions.ts (mutations)"] + A1["requisition actions"] + A2["application/pipeline actions"] + A3["onboarding action"] + A4["crew-record actions
(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
Prisma crew models")] + FS[("File storage
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 + 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](Crewing-Roles-and-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`/`Site` used by + POs, so crew cost and PO spend share a cost-centre axis for reporting. +- **PPE as inventory (optional)** — if the [inventory flag](Inventory-and-Catalogue) + is on, PPE issues can draw down `ItemInventory` at a site, reusing the + consumption model. +- **Notifications & dashboard** — crew events surface in the same notification + bell; the dashboard gains crewing stat cards. diff --git a/Crewing-Data-Model.md b/Crewing-Data-Model.md new file mode 100644 index 0000000..37d487a --- /dev/null +++ b/Crewing-Data-Model.md @@ -0,0 +1,349 @@ +# Crewing Data Model + +**Proposed source of truth:** `App/prisma/schema.prisma` (new models). Mirrors +the conventions on [Data Model](Data-Model): monetary values are `Decimal`, +IDs are `cuid()`, every lifecycle change writes an audit row. + +## 1. Enums + +```prisma +// who/what kind of vacancy +enum RequisitionReason { LEAVE END_OF_CONTRACT TERMINATION MEDICAL OTHER } +enum RequisitionStatus { OPEN SHORTLISTING PROPOSING INTERVIEWING SELECTED FILLED CANCELLED } + +// candidate progress against a requisition (the gated pipeline) +enum CandidateType { EX_HAND NEW } +enum ApplicationStage { + SHORTLISTED COMPETENCY_CHECK DOC_VERIFICATION REFERENCE_CHECK + SALARY_AGREEMENT PROPOSED INTERVIEW SELECTED REJECTED JOINING ONBOARDED +} + +// the person's overall status +enum CrewStatus { PROSPECT CANDIDATE EMPLOYEE EX_HAND BLACKLISTED } + +// an onboarded tour of duty +enum AssignmentStatus { ACTIVE ON_LEAVE SIGNED_OFF } + +// site HR +enum AttendanceStatus { PRESENT ABSENT HALF_DAY ON_LEAVE SIGN_OFF } +enum LeaveType { ANNUAL MEDICAL EMERGENCY UNPAID OTHER } +enum LeaveStatus { APPLIED APPROVED REJECTED CANCELLED } + +// PPE kit +enum PpeItem { + BOILER_SUIT SAFETY_SHOES HELMET VEST GLOVES MASK GOGGLES + TIFFIN TORCH WALKIE_TALKIE +} + +// documents +enum SeafarerDocType { + STCW AADHAAR PAN PASSPORT CDC COC PHOTOGRAPH + DRIVING_LICENSE MEDICAL_FITNESS CONTRACT_LETTER +} +enum VerificationStatus { PENDING VERIFIED REJECTED EXPIRED } + +// appraisal + payroll +enum AppraisalStatus { DRAFT SUBMITTED MPO_VERIFIED MANAGER_APPROVED REJECTED } +enum WageReportStatus { DRAFT GENERATED MANAGER_APPROVED SENT_TO_ACCOUNTS } + +// audit +enum CrewActionType { + REQUISITION_RAISED REQUISITION_CANCELLED REQUISITION_FILLED + CANDIDATE_SHORTLISTED GATE_PASSED GATE_FAILED PROPOSED INTERVIEWED + SELECTED REJECTED ONBOARDED SIGNED_OFF + RECORD_UPDATED RECORD_VERIFIED RECORD_REJECTED + PPE_ISSUED PPE_RETURNED LEAVE_APPLIED LEAVE_DECIDED + APPRAISAL_SUBMITTED APPRAISAL_VERIFIED APPRAISAL_APPROVED + WAGE_REPORT_GENERATED WAGE_REPORT_APPROVED WAGE_REPORT_SENT +} +``` + +## 2. Rank — the org hierarchy + +`Rank` is reference data with a self-relation (parent/children), exactly like the +3-level `Account` accounting-code hierarchy. It drives requisition criteria, +required documents per rank, and the org chart. Captured from +`Crewing.excalidraw`: + +```mermaid +flowchart TB + PM["PM"] --> APM["Ass. PM"] + APM --> ACC["Accountant"] + APM --> DRV["Driver"] + APM --> COOK["Cook"] + COOK --> CH["Cook Helper"] + APM --> SIC["Site in-charge"] + SIC --> DIC["Dredger in-charge"] + DIC --> SDO["Sr. Dredge Op."] + SDO --> PLS["Pipeline Supervisor"] + PLS --> PLA["Pipeline Ass."] + SDO --> JDO["Jr. Dredge Op."] + JDO --> ERO["Engine Room Op."] + ERO --> DH["Deck Hand"] + DH --> TR["Trainee"] + DH --> MB["Mess Boy"] + SDO --> ELE["Electrician"] + SDO --> FAB["Sr. Fab"] + FAB --> FW["Fab / Welder"] + + classDef sup fill:#eef,stroke:#88a; + class ACC,DRV,COOK,CH sup; +``` + +Ranks carry a `category` (e.g. `OPERATIONAL` vs `SUPPORT`) and an +`isSeafarer` flag (drives which document set applies). `Driver` additionally +requires a driving licence (`RankDocRequirement`). + +## 3. Entity-relationship diagram + +```mermaid +erDiagram + USER ||--o{ CREW_ACTION : actor + USER ||--o{ REQUISITION : raised_by + USER ||--o{ APPRAISAL : "added/verified/approved" + + RANK ||--o{ RANK : parent + RANK ||--o{ REQUISITION : for_rank + RANK ||--o{ CREW_ASSIGNMENT : in_rank + RANK ||--o{ RANK_DOC_REQUIREMENT : requires + + VESSEL ||--o{ REQUISITION : cost_centre + VESSEL ||--o{ CREW_ASSIGNMENT : on + SITE ||--o{ WAGE_REPORT : for_site + + REQUISITION ||--o{ APPLICATION : receives + REQUISITION ||--o| CREW_ASSIGNMENT : filled_by + REQUISITION }o--|| CREW_MEMBER : vacated_by + + CREW_MEMBER ||--o{ APPLICATION : applies + CREW_MEMBER ||--o{ CREW_ASSIGNMENT : has + CREW_MEMBER ||--o{ SEAFARER_DOCUMENT : holds + CREW_MEMBER ||--o| BANK_DETAIL : has + CREW_MEMBER ||--o| EPF_DETAIL : has + CREW_MEMBER ||--o{ NEXT_OF_KIN : has + CREW_MEMBER ||--o{ EMERGENCY_CONTACT : has + CREW_MEMBER ||--o{ EXPERIENCE_RECORD : has + + APPLICATION ||--o{ APPLICATION_GATE : evaluated_by + APPLICATION ||--o{ REFERENCE_CHECK : has + + CREW_ASSIGNMENT ||--o| SALARY_STRUCTURE : paid_by + CREW_ASSIGNMENT ||--o| CONTRACT_LETTER : documented_by + CREW_ASSIGNMENT ||--o{ ATTENDANCE : records + CREW_ASSIGNMENT ||--o{ LEAVE_REQUEST : has + CREW_ASSIGNMENT ||--o{ PPE_ISSUE : issued + CREW_ASSIGNMENT ||--o{ APPRAISAL : appraised + CREW_ASSIGNMENT ||--o{ WAGE_LINE : billed + + WAGE_REPORT ||--o{ WAGE_LINE : contains + + CREW_MEMBER { + string id PK + string employeeId "unique, set at onboard" + string name + date dob + string phone + string email + enum status "CrewStatus" + enum type "EX_HAND|NEW" + string cvKey "storage key" + string currentRankId FK + } + REQUISITION { + string id PK + string vesselId FK + string rankId FK + enum reason "RequisitionReason" + enum status "RequisitionStatus" + string vacatedByCrewId FK + int minExperienceMonths + string vesselTypeCriteria + string raisedById FK + bool autoRaised + date neededBy + } + APPLICATION { + string id PK + string requisitionId FK + string candidateId FK + enum type "CandidateType" + enum stage "ApplicationStage" + decimal proposedSalary + string remarks + bool interviewWaived + } + APPLICATION_GATE { + string id PK + string applicationId FK + string gate "competency|document|experience|reference|salary|interview" + enum result "PENDING|VERIFIED|REJECTED" + string note + string decidedById FK + } + CREW_ASSIGNMENT { + string id PK + string crewMemberId FK + string vesselId FK + string rankId FK + string requisitionId FK + date signOnDate + date signOffDate + enum status "AssignmentStatus" + } + SALARY_STRUCTURE { + string id PK + string assignmentId FK + decimal basic + decimal victualingPerDay + json allowances + string currency + string approvedById FK "Manager" + } + CONTRACT_LETTER { + string id PK + string assignmentId FK + string fileKey + bool salaryRestricted "hide salary from site staff" + } + ATTENDANCE { + string id PK + string assignmentId FK + date date + enum status "AttendanceStatus" + string recordedById FK + } + LEAVE_REQUEST { + string id PK + string assignmentId FK + enum type "LeaveType" + date fromDate + date toDate + enum status "LeaveStatus" + string appliedById FK + } + PPE_ISSUE { + string id PK + string assignmentId FK + enum item "PpeItem" + string size "for boiler suit / shoes" + int quantity + date issuedDate + date returnedDate + string issuedById FK + } + SEAFARER_DOCUMENT { + string id PK + string crewMemberId FK + enum docType "SeafarerDocType" + string number "masked" + string fileKey + date issueDate + date expiryDate + enum verificationStatus "VerificationStatus" + string verifiedById FK "MPO" + } + BANK_DETAIL { + string id PK + string crewMemberId FK + string accountName + string accountNumberEnc "encrypted" + string ifsc + string bankName + enum verificationStatus "by Accounts" + string verifiedById FK + } + EPF_DETAIL { + string id PK + string crewMemberId FK + string uan + string aadhaarLast4 + string pfNumber + enum verificationStatus "EPFO check by Accounts" + string verifiedById FK + } + EXPERIENCE_RECORD { + string id PK + string crewMemberId FK + string vesselType + string rankId FK + date fromDate + date toDate + int durationMonths + string source "internal|declared" + } + APPRAISAL { + string id PK + string assignmentId FK + string period + json ratings + string comments + enum status "AppraisalStatus" + string addedById FK "PM" + string verifiedById FK "MPO" + string approvedById FK "Manager" + } + WAGE_REPORT { + string id PK + string siteId FK + string period "YYYY-MM" + enum status "WageReportStatus" + decimal totalAmount + string generatedById FK + string approvedById FK + } + WAGE_LINE { + string id PK + string wageReportId FK + string assignmentId FK + int daysAttended + decimal dailyRate + decimal victualing + decimal lineTotal + } + CREW_ACTION { + string id PK + string entityType "requisition|application|assignment|..." + string entityId + enum actionType "CrewActionType" + string actorId FK + string note + json metadata + datetime createdAt + } +``` + +## 4. Model notes + +- **`CrewMember`** is the spine: a row exists from the first application + (`PROSPECT`/`CANDIDATE`) and persists as `EMPLOYEE` then `EX_HAND`. This is + what makes "ex-hand preferred" cheap — their experience and documents are + already on file. `employeeId` is assigned at onboarding. +- **`Application` vs `Requisition`** — one requisition, many applications; + `APPLICATION_GATE` rows are the audit of each vetting gate (the columns in + notebook page 2: competency, document, experience, reference, salary, + interview). `interviewWaived` is set true for ex-hands. +- **`CrewAssignment`** is a single tour of duty. Onboarding creates it and + flips the requisition to `FILLED`. Sign-off sets `signOffDate`/`SIGNED_OFF`, + appends an `EXPERIENCE_RECORD`, and triggers a backfill requisition. +- **Verification is a field** — `verificationStatus + verifiedById` on + documents, bank and EPF. Routing rule (from the notebook): site-entered data → + **MPO**; bank + EPF → **Accounts**; attendance is operational (no separate + verify gate). +- **`SalaryStructure`** holds `basic`, `victualingPerDay`, and a JSON + `allowances`; approved by a Manager. Salary is the restricted field on the + contract letter for site staff. +- **`WageReport` / `WageLine`** are the generated monthly artefact: + `lineTotal = daysAttended × dailyRate + victualing`; the report + `totalAmount` is the sum. Exportable to XLSX/PDF like a PO. +- **PPE as inventory** — when the inventory flag is on, a `PPE_ISSUE` can also + decrement `ItemInventory` at the site (optional integration). + +## 5. Relationship to existing models + +| Existing model | Crewing relationship | +|---|---| +| `Vessel` (cost centre) | `Requisition.vesselId`, `CrewAssignment.vesselId` | +| `Site` | `WageReport.siteId`; site grouping for attendance & leave planner; optional PPE inventory | +| `User` | actors on every `CrewAction`; raisers/verifiers/approvers | +| `Notification` | crew events reuse the in-app bell + email log | +| `ItemInventory` (flagged) | optional PPE draw-down | diff --git a/Crewing-Design-Document.md b/Crewing-Design-Document.md new file mode 100644 index 0000000..8600ba6 --- /dev/null +++ b/Crewing-Design-Document.md @@ -0,0 +1,164 @@ +# Crewing Design Document + +> Companion pages: [Architecture](Crewing-Architecture) · +> [Data Model](Crewing-Data-Model) · [Workflows](Crewing-Workflows) · +> [Use Cases](Crewing-Use-Cases) · [Roles & Permissions](Crewing-Roles-and-Permissions) · +> [User Stories](Crewing-User-Stories) + +## 1. Purpose & scope + +The Crewing Module digitises Pelagia's **manning process**: keeping every +dredger and site crewed with competent, documented, paid people, and recording +the full lifecycle of each crew member. + +**In scope** + +1. Vacancy detection & **requisitions**. +2. **Recruitment pipeline** (shortlist → vetting → proposal → interview → + selection) for ex-hands and new candidates. +3. **Onboarding** and the automatic processes it kicks off. +4. **Crew records**: documents, bank, EPF, next-of-kin, emergency contact, + contract, PPE. +5. **Site HR**: leave planner, attendance. +6. **Appraisal** (PM → MPO → Manager). +7. **Sign-off** and experience accrual. +8. **Wage report** generation for Accounts. + +**Out of scope (v1)** — actual salary disbursement / banking integration (we +produce the report; Accounts pays), payslip generation, statutory return filing, +training-course delivery, and seafarer-document issuance (we store and verify, +we don't issue). + +## 2. Actors + +| Actor | Real-world role | Maps to PPMS role | +|---|---|---| +| **Site staff** | PM / Assistant PM / Site In-charge on the dredger | new `SITE_STAFF` role (proposed) | +| **MPO** | Manning / Personnel Officer in the office | existing `MANNING` role | +| **Accounts** | Finance team | existing `ACCOUNTS` role | +| **Manager** | Department / crewing manager | existing `MANAGER` role | +| **Candidate** | External applicant (ex-hand or new) | unauthenticated public form / lightweight `CANDIDATE` identity | +| **Crew member** | Onboarded employee | data subject; not necessarily a portal login in v1 | +| Admin / Superuser / Auditor | as in PO module | existing roles | + +See the on-vessel **rank hierarchy** (PM → … → Mess Boy) in +[Data Model § Rank](Crewing-Data-Model#rank-the-org-hierarchy). + +## 3. Domain narrative + +### 3.1 A vacancy opens +A crew member goes on leave, finishes a contract (EOC), is terminated, is +signed off on medical grounds, or leaves for another reason. Any of these +creates a **gap** for a specific **rank** on a specific **vessel/site**. On a +*scheduled sign-off* the gap (and its **Requisition**) is raised automatically; +otherwise site staff or the MPO raise it manually. + +### 3.2 Sourcing candidates +The MPO shortlists candidates for the rank. **Ex-hands are preferred** (people +who have sailed with Pelagia before — their record is already in the system). +New candidates either **apply on the Pelagia site** (uploading a CV, which is +**parsed** for age, vessel type, current/past rank and experience — with manual +entry as fallback) or are **uploaded manually** by the MPO. + +### 3.3 Vetting (gated) +Each shortlisted candidate is evaluated against the rank's **minimum criteria**: + +- **Competency** — qualifications for the rank. +- **Document verification** — seafarer documents present and valid. +- **Experience** — *type of vessel*, *time in rank*, *total duration*. +- **Reference check** — previous-employer confirmation. +- **Salary agreement** — proposed structure accepted in principle. + +The candidate is then **proposed** and (except for ex-hands, where it's +**optional**) **interviewed**. The outcome is **Selection** or **Rejection with +remarks**. Every gate is recorded. + +### 3.4 Onboarding +On selection: **joining formalities** run — salary is confirmed and a +**contract letter** issued. Creating the **CrewAssignment** automatically starts: + +- **salary** (the agreed structure becomes active), +- **victualing** (food/messing allowance accrual), +- **attendance** capture, +- **experience** accrual on this vessel/rank, +- **PF** tracking (UAN/EPF), and +- the **PPE issue** checklist. + +The crew member receives an **Employee ID**. + +### 3.5 Life on site +Site staff record **attendance** daily, **issue PPE** (with boiler-suit and shoe +sizes), maintain **documents/bank/EPF/next-of-kin/emergency contact**, and apply +for **leave** through the site **leave planner** (which shows every crew +member's contract span and leaves on one timeline). The PM raises **appraisals**; +the **MPO verifies**, the **Manager approves**. + +### 3.6 Office verification +The office is the source of truth for verified data. **The MPO verifies +everything site staff enter, except attendance**; **bank details and EPF are +verified by Accounts** (UAN/Aadhaar checked against EPFO). + +### 3.7 Sign-off & backfill +When a crew member signs off, the assignment closes, their **experience record +is updated** with the completed tour, and a **backfill Requisition** is raised +automatically — closing the loop back to §3.1. + +### 3.8 Payroll feed +At month end a **Wage Report** is generated per site: +`crew × salary × days-attended (+ victualing)`. It is reviewed, **approved by the +Manager**, and **sent to Accounts** for disbursement. + +## 4. Salient features & design decisions + +| # | Decision | Rationale | +|---|---|---| +| D1 | **Two coupled state machines** — `Requisition` (the vacancy) and `Application` (a candidate's progress against a requisition). | The requisition is "is this seat filled?"; the application is the per-candidate gated pipeline. Many applications per requisition; exactly one ends in onboarding. | +| D2 | **Unified `CrewMember`** record with a `status` (PROSPECT → CANDIDATE → EMPLOYEE → EX_HAND). | "Ex-hand preferred" only works if a candidate and an ex-employee are the same record; experience carries forward automatically. | +| D3 | **Onboarding is a side-effecting transition**, exactly like PO approval increments inventory. | Single source of truth: salary/victualing/attendance/experience/PF/PPE all start from one event, recorded as one `CrewAction`. | +| D4 | **Verification is a field-level state**, not a separate workflow. Each verifiable record carries `verificationStatus + verifiedBy`. Routing: site-entered → MPO; bank/EPF → Accounts. | Mirrors "view-only / upload / verify" split from the notebook; keeps the office as the trust boundary. | +| D5 | **Sensitive fields are masked & gated.** Salary on the contract letter, full bank account number, and Aadhaar/PAN are restricted; site staff get *view-only-except-salary* on contracts and *view-only* bank. | Matches the explicit notebook rules and basic PII hygiene. | +| D6 | **Rank is reference data with a self-hierarchy** (3+ levels), mirroring the `Account` accounting-code hierarchy. | Drives requisition criteria, document requirements (e.g. licence for drivers), and the org chart. | +| D7 | **Document requirements are rank-driven.** A `RankDocRequirement` defines which seafarer docs a rank must hold. | "Seafarer docs (based on role)"; driving licence only for drivers. | +| D8 | **Wage report is a generated artefact**, not live-computed on the Accounts screen. | Auditable monthly snapshot; mirrors PO export/report pattern. | +| D9 | **Reuse `Vessel`/`Site` as the cost centre** so crew cost is attributable on the same axis as PO spend. | Single cost-centre vocabulary across the portal. | +| D10 | **CV parsing is best-effort with manual fallback.** Extraction populates a draft; a human confirms. | Never block intake on a parser; keep the office in control. | + +## 5. Non-functional requirements + +- **Auditability** — every lifecycle transition and verification writes a + `CrewAction` row (actor, type, note, metadata), like `POAction`. +- **Privacy** — bank/EPF/identity documents are PII: stored via the same + signed-URL [file storage](File-Storage); account numbers and identity numbers + stored masked/encrypted; access gated by permission. +- **Consistency** — all mutations are Server Actions calling `requirePermission` + then the relevant state-machine guard; **no mutation REST endpoints**. +- **Notifications** — declarative per-transition email/in-app side-effects via + `lib/notifier.ts` (e.g. requisition raised → MPO; proposal → Manager; + appraisal verified → Manager; wage report ready → Accounts). +- **Document expiry** — documents with `expiryDate` surface upcoming-expiry + warnings (medical fitness, passport, CDC, STCW). + +## 6. Assumptions + +1. PM/APM/Site In-charge get a dedicated `SITE_STAFF` role rather than reusing + `TECHNICAL`. (Confirm in review — see Open Questions.) +2. Crew members are **data subjects**, not portal logins, in v1. A future phase + may give crew a self-service login. +3. **EPFO** UAN/Aadhaar verification is a manual/assisted step in v1 (record the + result); a programmatic check can follow the GstService precedent. +4. "Victualing" is a per-day messing allowance, configurable per rank/vessel. +5. One **active** assignment per crew member at a time. + +## 7. Open questions + +| # | Question | +|---|---| +| Q1 | New role `SITE_STAFF`, or extend `TECHNICAL`/`MANNING`? | +| Q2 | Does the candidate self-apply form live inside the portal (public route) or on the marketing site posting to an API? | +| Q3 | EPFO verification — assisted-manual now, or build an EPFO proxy microservice like GstService? | +| Q4 | Should salary structures be versioned per assignment (raises mid-contract) in v1, or single-structure-per-assignment? | +| Q5 | Is victualing part of the wage report total, or a separate accrual line? | +| Q6 | Do we need crew self-service (view own docs/payslip) in v1 or v2? | +| Q7 | Attendance granularity — daily present/absent only, or shift/hours? | + +See the requirement backlog in [Crewing User Stories](Crewing-User-Stories). diff --git a/Crewing-Module.md b/Crewing-Module.md new file mode 100644 index 0000000..65fc775 --- /dev/null +++ b/Crewing-Module.md @@ -0,0 +1,72 @@ +# Crewing Module + +The **Crewing Module** (a.k.a. *Manning*) extends Pelagia Portal (PPMS) from a +purchasing system into a crew lifecycle system. It manages the people who run +Pelagia's dredgers and sites — from the moment a vacancy opens, through +recruitment and onboarding, to day-to-day site HR (attendance, leave, PPE, +documents) and finally to the monthly wage report that feeds Accounts. + +It reuses the platform primitives already proven by the [PO module](Purchase-Orders): +a single **state machine** per lifecycle, a centralised **permission map**, an +**audit trail** of actions, **file storage** for documents, and the **notifier** +for email/in-app events. + +## Why software + +Today crewing lives in notebooks, WhatsApp, and spreadsheets (the source notes +for this design were literally two notebook pages). The pain points it removes: + +- **Vacancies are reactive and untracked.** A sign-off or leave creates a gap + that nobody formally owns until a vessel is short-handed. → Auto-raised + **Requisitions** on sign-off / end-of-contract. +- **Candidate vetting is ad-hoc.** Competency, document, reference and salary + checks happen over chat with no record of who cleared what. → A gated + **Candidate Pipeline** with an event log. +- **Site data and office verification are disconnected.** PMs hold PPE/leave/doc + data on paper; the office can't see or verify it. → Site-entered records with + an explicit **MPO / Accounts verification** step. +- **Wage calculation is manual.** Days-attended × salary is re-derived by hand + every month. → A generated **Wage Report** per site. + +## What it does (salient features) + +| Area | Capability | +|---|---| +| **Requisitions** | Auto- or manually-raised vacancy for a rank on a vessel/site; reason (leave / end-of-contract / termination / medical / other). | +| **Candidate pipeline** | Shortlist (ex-hand preferred) → competency → document verification → reference check → salary agreement → proposal → interview (optional for ex-hands) → selection / rejection with remarks. | +| **CV intake** | Self-apply on the Pelagia site or MPO upload; CV parsed for age, vessel type, rank and experience, with manual fallback. | +| **Onboarding** | One selection event starts salary, victualing, attendance, experience accrual, PF tracking and the PPE issue checklist. | +| **Crew records** | Bank details, EPF/UAN, next of kin, emergency contact, contract letter, and role-based seafarer documents (STCW, Aadhaar, PAN, Passport, CDC, COC, photo, licence, medical fitness). | +| **PPE issue register** | Boiler-suit & shoe sizes plus issue tracking for the full PPE kit. | +| **Leave & attendance** | Site-level leave planner (contracts + leaves on one timeline) and daily attendance capture. | +| **Appraisal** | PM raises → MPO verifies → Manager approves. | +| **Sign-off** | Updates experience, closes the assignment, and auto-raises the backfill requisition. | +| **Wage report** | Monthly, per site: crew × salary × days attended (+ victualing), generated and sent to Accounts. | +| **Verification** | Office sign-off: MPO verifies everything except attendance; bank + EPF verified by Accounts. | + +## How it fits PPMS + +- **Same stack** — Next.js 15 App Router, Prisma + PostgreSQL, NextAuth v5, + Tailwind v4, shadcn/ui. See [Crewing Architecture](Crewing-Architecture). +- **Same patterns** — `lib/requisition-state-machine.ts` and + `lib/application-pipeline.ts` mirror `lib/po-state-machine.ts`; a `CrewAction` + audit row mirrors `POAction`; verification/permission gating reuses + `requirePermission`. +- **Shared entities** — `Vessel` (cost centre), `Site`, `User`, `Notification` + are reused; crew salary cost is attributable to the same cost-centre concept + used by POs. + +## Page map + +| Page | Purpose | Detail | +|---|---|---| +| [Crewing Design Document](Crewing-Design-Document) | Goals, scope, domain narrative, decisions | — | +| [Crewing Architecture](Crewing-Architecture) | System & component diagrams, module layout | Component diagram | +| [Crewing Data Model](Crewing-Data-Model) | Entities, enums, relationships | **ER diagram** | +| [Crewing Workflows](Crewing-Workflows) | Lifecycles & flows | **Sequence + state diagrams** | +| [Crewing Use Cases](Crewing-Use-Cases) | Actors × use cases | **Use-case diagram** | +| [Crewing Roles and Permissions](Crewing-Roles-and-Permissions) | Permission matrix extension | — | +| [Crewing User Stories](Crewing-User-Stories) | Backlog by epic with acceptance criteria | — | + +> **Status:** design proposal. Entity, route and permission names are +> proposals to be confirmed in review (see [Open Questions](Crewing-Design-Document#open-questions)). diff --git a/Crewing-Roles-and-Permissions.md b/Crewing-Roles-and-Permissions.md new file mode 100644 index 0000000..7f67576 --- /dev/null +++ b/Crewing-Roles-and-Permissions.md @@ -0,0 +1,102 @@ +# Crewing Roles and Permissions + +Crewing extends the existing `lib/permissions.ts` map (see +[Roles and Permissions](Roles-and-Permissions)) with crewing permissions. Every +crewing Server Action calls `requirePermission(role, permission)` first; the +relevant state machine adds the status+role gate on top. + +## 1. Role mapping + +| Crewing actor | PPMS role | Notes | +|---|---|---| +| PM / APM / Site In-charge | **`SITE_STAFF`** (new, proposed) | apply-only leave, attendance, PPE issue, doc upload, view-only contract (except salary) & bank | +| MPO | **`MANNING`** (existing — "crew-management staff") | recruitment + verifies all site data except bank/EPF | +| Accounts | **`ACCOUNTS`** | verifies bank + EPF; consumes wage report | +| Manager | **`MANAGER`** | approves salary structures, candidate list, appraisals, wage reports | +| Superuser | **`SUPERUSER`** | combined authority across crewing actions | +| Auditor | **`AUDITOR`** | read-only across crewing | +| Admin | **`ADMIN`** | manages ranks & doc requirements | +| Candidate | none | public careers form only | + +> **Proposed enum change:** add `SITE_STAFF` to `Role`. If review prefers reuse, +> `TECHNICAL` could host site staff, but its PO semantics ("deck/engine crew") +> differ from PM/APM managerial duties — hence the new role. + +## 2. Permission → role matrix + +✓ = granted. (`SITE` = SITE_STAFF, `MAN` = MANNING/MPO, `ACC` = ACCOUNTS, +`MGR` = MANAGER, `SU` = SUPERUSER, `AUD` = AUDITOR, `ADM` = ADMIN.) + +| Permission | SITE | MAN | ACC | MGR | SU | AUD | ADM | +|---|:--:|:--:|:--:|:--:|:--:|:--:|:--:| +| `raise_requisition` | ✓ | ✓ | | ✓ | ✓ | | | +| `cancel_requisition` | | ✓ | | ✓ | ✓ | | | +| `view_requisitions` | ✓ | ✓ | | ✓ | ✓ | ✓ | ✓ | +| `manage_candidates` (shortlist/vetting) | | ✓ | | ✓ | ✓ | | | +| `record_reference_check` | | ✓ | | ✓ | ✓ | | | +| `propose_interview` | | ✓ | | ✓ | ✓ | | | +| `approve_salary_structure` | | | | ✓ | ✓ | | | +| `select_candidate` | | ✓ | | ✓ | ✓ | | | +| `onboard_crew` | | ✓ | | ✓ | ✓ | | | +| `sign_off_crew` | ✓ | ✓ | | ✓ | ✓ | | | +| `view_crew_records` | ✓¹ | ✓ | ✓² | ✓ | ✓ | ✓ | ✓ | +| `upload_crew_records` (docs/NoK/emergency/EPF) | ✓ | ✓ | | ✓ | ✓ | | | +| `issue_ppe` | ✓ | ✓ | | ✓ | ✓ | | | +| `apply_leave` | ✓ | ✓ | | ✓ | ✓ | | | +| `decide_leave` | | ✓ | | ✓ | ✓ | | | +| `record_attendance` | ✓ | ✓ | | ✓ | ✓ | | | +| `verify_site_records` (docs/PPE/leave/NoK) | | ✓ | | ✓ | ✓ | | | +| `verify_bank_epf` | | | ✓ | ✓ | ✓ | | | +| `raise_appraisal` | ✓ | | | ✓ | ✓ | | | +| `verify_appraisal` | | ✓ | | ✓ | ✓ | | | +| `approve_appraisal` | | | | ✓ | ✓ | | | +| `generate_wage_report` | | ✓ | | ✓ | ✓ | | | +| `approve_wage_report` | | | | ✓ | ✓ | | | +| `view_wage_report` | | | ✓ | ✓ | ✓ | ✓ | ✓ | +| `manage_ranks` | | | | ✓ | | | ✓ | + +¹ Site staff get crew records for **their own site only**, with contract +**view-only-except-salary** and bank **view-only**. +² Accounts see bank/EPF and the wage report, not the full HR record. + +## 3. Field-level restrictions (PII) + +Enforced in Server Components/Actions on top of the permission map: + +| Field | Visible to | Hidden from | +|---|---|---| +| Salary (contract letter, salary structure) | MPO, Manager, Accounts, Superuser, Auditor | **Site staff** | +| Full bank account number | Accounts (verify), Manager, Superuser | Site staff (view-only masked), MPO | +| Aadhaar / PAN number | MPO (verify), Accounts (EPF), Superuser | Site staff (masked) | +| Candidate rejection remarks | MPO, Manager, Superuser, Auditor | Candidate | + +## 4. Business rules layered on top + +- A candidate cannot be **onboarded** until the salary structure is + **Manager-approved** and a contract letter is attached. +- **Bank and EPF** records can only be set `VERIFIED` by **Accounts** (UAN/Aadhaar + EPFO check); everything else site staff enter is verified by **MPO**. +- **Attendance** has no verification gate (operational record). +- An **appraisal** must be `MPO_VERIFIED` before a Manager can approve it. +- A **wage report** must be `MANAGER_APPROVED` before it is `SENT_TO_ACCOUNTS`. +- Only **one ACTIVE assignment** per crew member; sign-off is required before a + new one. +- Site staff actions are scoped to the **site/vessel they are assigned to**. + +## 5. Implementation pattern + +```ts +// app/(portal)/crewing/candidates/actions.ts +'use server' +export async function onboardCandidate(input: OnboardInput) { + const session = await auth() + requirePermission(session.user.role, 'onboard_crew') // role gate + const app = await db.application.findUniqueOrThrow(...) + assertCanTransition(app.stage, 'onboard') // pipeline gate + // ...create assignment + side-effects in a transaction, + // write a CrewAction, dispatch notifications +} +``` + +Same shape as every PO Server Action: **permission → state-machine guard → +transactional write → audit row → notify.** diff --git a/Crewing-Use-Cases.md b/Crewing-Use-Cases.md new file mode 100644 index 0000000..7363d4e --- /dev/null +++ b/Crewing-Use-Cases.md @@ -0,0 +1,107 @@ +# Crewing Use Cases + +Actors and the use cases each can perform. The diagram uses a flowchart to +approximate UML use-case notation (Mermaid has no native use-case diagram); +actors are on the edges, use cases are rounded nodes, grouped by area. + +## 1. Use-case diagram + +```mermaid +flowchart LR + SS([Site staff
PM / APM / Site I-C]) + MPO([MPO]) + ACC([Accounts]) + MGR([Manager]) + CAND([Candidate]) + ADM([Admin]) + + subgraph Requisition + U1(["Raise requisition"]) + U2(["Sign off crew → auto-requisition"]) + U3(["Cancel requisition"]) + end + subgraph Recruitment + U4(["Apply with CV"]) + U5(["Shortlist candidates"]) + U6(["Run vetting gates"]) + U7(["Reference check"]) + U8(["Propose & interview"]) + U9(["Select / reject with remarks"]) + U10(["Approve salary structure & list"]) + U11(["Onboard candidate"]) + end + subgraph SiteHR["Site HR"] + U12(["Apply for leave"]) + U13(["View leave planner"]) + U14(["Record attendance"]) + U15(["Issue PPE (sizes)"]) + U16(["Upload documents / EPF / NoK"]) + end + subgraph Verify["Office verification"] + U17(["Verify docs / PPE / leave"]) + U18(["Verify bank & EPF"]) + end + subgraph Appraisal + U19(["Raise appraisal"]) + U20(["Verify appraisal"]) + U21(["Approve appraisal"]) + end + subgraph Payroll + U22(["Generate wage report"]) + U23(["Approve wage report"]) + U24(["View / export wage report"]) + end + subgraph Admin + U25(["Manage ranks & doc requirements"]) + end + + SS --- U1 & U2 & U12 & U13 & U14 & U15 & U16 & U19 + CAND --- U4 + MPO --- U5 & U6 & U7 & U8 & U9 & U11 & U17 & U20 + MGR --- U3 & U9 & U10 & U11 & U21 & U22 & U23 & U24 + ACC --- U18 & U24 + ADM --- U25 +``` + +## 2. Use-case catalogue + +| ID | Use case | Primary actor | Pre-condition | Main success outcome | +|---|---|---|---|---| +| UC-01 | Raise requisition | Site staff / MPO | a rank is or will be vacant | `Requisition(OPEN)` created, MPO notified | +| UC-02 | Auto-requisition on sign-off | System | crew member signed off | requisition raised automatically; experience updated | +| UC-03 | Cancel requisition | Manager | requisition not yet filled | `CANCELLED` with reason | +| UC-04 | Apply with CV | Candidate | careers form reachable | `CrewMember(PROSPECT)` + application; CV parsed or manual | +| UC-05 | Shortlist candidates | MPO | requisition `OPEN` | applications `SHORTLISTED`; ex-hands preferred | +| UC-06 | Run vetting gates | MPO | candidate shortlisted | competency/document/experience gates recorded | +| UC-07 | Reference check | MPO | candidate in vetting | reference result recorded | +| UC-08 | Propose & interview | MPO | gates cleared | proposal sent; interview recorded (waived for ex-hands) | +| UC-09 | Select / reject with remarks | MPO / Manager | interview done / waived | `SELECTED` or `REJECTED` + remarks | +| UC-10 | Approve salary structure & candidate list | Manager | candidate proposed | salary structure approved | +| UC-11 | Onboard candidate | MPO / Manager | candidate selected | `CrewAssignment(ACTIVE)`; salary/victualing/attendance/experience/PF/PPE started; employeeId issued; requisition `FILLED` | +| UC-12 | Apply for leave | Site staff | active assignment | `LeaveRequest(APPLIED)` | +| UC-13 | View leave planner | Site staff / MPO / Manager | — | timeline of contracts + leaves per site | +| UC-14 | Record attendance | Site staff | active assignment | daily `Attendance` rows | +| UC-15 | Issue PPE | Site staff | active assignment | `PpeIssue` with size (boiler suit/shoes) | +| UC-16 | Upload documents / EPF / next-of-kin / emergency | Site staff | crew exists | records created `PENDING` verification | +| UC-17 | Verify docs / PPE / leave | MPO | pending records exist | `VERIFIED` (everything except attendance) | +| UC-18 | Verify bank & EPF | Accounts | pending bank/EPF | `VERIFIED`; UAN/Aadhaar checked vs EPFO | +| UC-19 | Raise appraisal | Site staff (PM) | active assignment | `Appraisal(SUBMITTED)` | +| UC-20 | Verify appraisal | MPO | appraisal submitted | `MPO_VERIFIED` | +| UC-21 | Approve appraisal | Manager | appraisal verified | `MANAGER_APPROVED` | +| UC-22 | Generate wage report | Manager / system | month end | `WageReport(GENERATED)` per site | +| UC-23 | Approve wage report | Manager | report generated | `MANAGER_APPROVED` → `SENT_TO_ACCOUNTS` | +| UC-24 | View / export wage report | Accounts / Manager | report sent | XLSX/PDF export for disbursement | +| UC-25 | Manage ranks & doc requirements | Admin | — | rank hierarchy + per-rank required documents | + +## 3. Notable extensions / alternates + +- **UC-04 alt** — CV not parseable → manual field entry (never blocks intake). +- **UC-06/07/09 alt** — any gate fails → `REJECTED` with remarks; application + ends, requisition stays open for other candidates. +- **UC-08 alt** — ex-hand → interview waived (`interviewWaived = true`). +- **UC-11 alt** — joining formalities incomplete (no signed contract / unapproved + salary) → onboarding blocked. +- **UC-16/17 alt** — verifier rejects a record → back to `PENDING` with remarks + for the site to correct. +- **UC-22 alt** — attendance gaps flagged before generation; report can be + regenerated until approved. diff --git a/Crewing-User-Stories.md b/Crewing-User-Stories.md new file mode 100644 index 0000000..e78f057 --- /dev/null +++ b/Crewing-User-Stories.md @@ -0,0 +1,329 @@ +# Crewing User Stories + +Backlog for the Crewing Module, grouped into epics. Format: +*As a ``, I want ``, so that ``* + acceptance criteria +(AC). IDs are stable references for PRs/issues. + +--- + +## Epic A — Requisitions (vacancy management) + +**A1 — Auto-requisition on sign-off** +As an **MPO**, I want a requisition raised automatically when a crew member +signs off, so that no vacancy goes untracked. +- AC1: signing off an assignment creates a `Requisition(OPEN, autoRaised)` for + the same rank + vessel. +- AC2: the requisition records the reason (EOC / medical / termination / other). +- AC3: the MPO receives an email + in-app notification. +- AC4: a `CrewAction(REQUISITION_RAISED)` is written. + +**A2 — Manual requisition** +As **site staff / MPO**, I want to raise a requisition manually (e.g. planned +leave), so that I can pre-empt a gap. +- AC1: I select vessel, rank, reason, needed-by date, and min-experience criteria. +- AC2: the requisition appears in the MPO shortlist queue. + +**A3 — Requisition list & detail** +As an **MPO/Manager**, I want a filterable list of requisitions by +vessel/rank/status, so that I can prioritise sourcing. +- AC1: filters for status, vessel, rank, reason, needed-by. +- AC2: each row shows candidate count and days open. + +**A4 — Cancel requisition** +As a **Manager**, I want to cancel a requisition with a reason, so that +withdrawn vacancies don't clutter the pipeline. +- AC1: only non-`FILLED` requisitions can be cancelled. +- AC2: reason is required and audited. + +--- + +## Epic B — Candidate intake + +**B1 — Public application with CV** +As a **candidate**, I want to apply on the Pelagia site and upload my CV, so +that I'm considered without re-typing everything. +- AC1: the form accepts personal details + CV upload (signed upload to storage). +- AC2: the CV is parsed for age, vessel type, current/past rank, experience. +- AC3: parsed fields pre-fill a draft I can correct before submitting. +- AC4: if parsing fails, I can enter all fields manually (intake never blocks). +- AC5: submission creates a `CrewMember(PROSPECT)` + application(s). + +**B2 — MPO manual candidate upload** +As an **MPO**, I want to add a candidate and their CV directly, so that +walk-ins/referrals enter the same pipeline. +- AC1: same parse-or-manual flow as B1. +- AC2: the candidate is attachable to one or more open requisitions. + +**B3 — Ex-hand recognition** +As an **MPO**, I want ex-hands flagged and preferred in shortlisting, so that +proven crew are surfaced first. +- AC1: candidates matching an existing `CrewMember(EX_HAND)` are tagged. +- AC2: ex-hands sort above new candidates by default. +- AC3: ex-hand experience/documents are pre-populated from history. + +--- + +## Epic C — Recruitment pipeline (vetting) + +**C1 — Shortlist to a requisition** +As an **MPO**, I want to shortlist candidates against a requisition, so that +vetting can begin. +- AC1: shortlisting moves the requisition to `SHORTLISTING` and the application + to `SHORTLISTED`. + +**C2 — Competency check** +As an **MPO**, I want to record a competency result against the rank's minimum +criteria, so that unqualified candidates are filtered early. +- AC1: pass advances to document verification; fail → `REJECTED` + remarks. + +**C3 — Document verification (vetting)** +As an **MPO**, I want to verify a candidate's seafarer documents during vetting, +so that only documented candidates proceed. +- AC1: missing/expired required docs (per rank) block advancement. +- AC2: result recorded as an `APPLICATION_GATE`. + +**C4 — Experience check** +As an **MPO**, I want to verify experience (vessel type, time-in-rank, total +duration) against the requisition's criteria, so that the candidate fits the seat. +- AC1: criteria comparison shown; shortfall flagged. + +**C5 — Reference check** +As an **MPO**, I want to record reference-check outcomes, so that prior conduct +is confirmed. +- AC1: at least one reference result recorded before proposal. + +**C6 — Salary agreement** +As an **MPO**, I want to record an agreed salary structure (basic, victualing, +allowances), so that the proposal has terms. +- AC1: structure captured; visible only to office roles (not site staff). + +**C7 — Proposal & interview** +As an **MPO**, I want to propose to the candidate and record an interview, so +that selection is informed. +- AC1: new candidates require an interview record. +- AC2: ex-hands can have the interview **waived** (`interviewWaived`). + +**C8 — Selection / rejection with remarks** +As an **MPO/Manager**, I want to select or reject with remarks, so that the +decision and its reason are on record. +- AC1: rejection requires remarks. +- AC2: selection requires all prior gates cleared. + +**C9 — Pipeline board** +As an **MPO**, I want a board showing each candidate's stage per requisition, so +that I can see the funnel at a glance. +- AC1: columns map to `ApplicationStage`; cards show candidate + last gate. + +--- + +## Epic D — Onboarding + +**D1 — Approve salary structure & candidate list** +As a **Manager**, I want to approve the proposed salary structure and selected +candidate, so that onboarding can proceed. +- AC1: onboarding is blocked until the structure is `MANAGER_APPROVED`. + +**D2 — Joining formalities & onboard** +As an **MPO/Manager**, I want to complete joining formalities and onboard the +candidate, so that they become an employee. +- AC1: a contract letter is attached; salary confirmed. +- AC2: onboarding creates a `CrewAssignment(ACTIVE)`, assigns an `employeeId`, + and sets `CrewMember.status = EMPLOYEE`. +- AC3: the filled requisition flips to `FILLED`. + +**D3 — Onboarding side-effects** +As the **system**, I want onboarding to start salary, victualing, attendance, +experience, PF and PPE in one transaction, so that nothing is missed. +- AC1: all six are initialised atomically. +- AC2: a single `CrewAction(ONBOARDED)` records the created IDs in `metadata`. + +--- + +## Epic E — Crew records & documents + +**E1 — Crew directory & profile** +As an **MPO**, I want a crew directory with a per-member profile, so that I can +find anyone's records. +- AC1: profile groups documents, bank, EPF, next-of-kin, emergency, PPE, + experience, appraisals. + +**E2 — Upload seafarer documents** +As **site staff**, I want to upload role-based documents (STCW, Aadhaar, PAN, +passport, CDC, COC, photo, licence, medical fitness), so that the office can +verify them. +- AC1: required set is driven by rank (`RankDocRequirement`); driver requires a + driving licence. +- AC2: documents store issue/expiry dates; uploads start `PENDING`. + +**E3 — Document expiry warnings** +As an **MPO**, I want upcoming-expiry warnings, so that crew don't sail on +expired documents. +- AC1: docs expiring within N days are flagged on the profile and a queue. + +**E4 — Bank / EPF / next-of-kin / emergency** +As **site staff**, I want to record bank details, EPF (UAN/Aadhaar), next-of-kin +and emergency contacts, so that payroll and welfare are covered. +- AC1: bank account number stored masked/encrypted; site staff see it + **view-only**. +- AC2: contract letter is **view-only-except-salary** for site staff. + +--- + +## Epic F — PPE issue register + +**F1 — Capture sizes** +As **site staff**, I want to record boiler-suit and safety-shoe sizes, so that +the right kit is issued. +- AC1: size is captured on the relevant PPE issue. + +**F2 — Issue PPE kit** +As **site staff**, I want to issue PPE items (boiler suit, shoes, helmet, vest, +gloves, mask, goggles, tiffin, torch, walkie-talkie), so that issuance is logged. +- AC1: each issue records item, quantity, size?, date, issuer. +- AC2: (flag on) optionally decrements site `ItemInventory`. + +**F3 — Returns** +As **site staff**, I want to record PPE returns, so that the register is current. +- AC1: a `returnedDate` closes the issue. + +--- + +## Epic G — Leave & attendance + +**G1 — Site leave planner** +As **site staff / MPO / Manager**, I want a planner showing every crew member's +contract span and leaves on one timeline, so that I can plan cover. +- AC1: timeline per site with contract bars + leave bars. +- AC2: overlapping absences for the same rank are highlighted. + +**G2 — Apply for leave** +As **site staff**, I want to apply for leave (apply-only), so that the office can +approve it. +- AC1: I cannot self-approve; status starts `APPLIED`. + +**G3 — Decide leave** +As an **MPO**, I want to approve/reject leave with a note, so that the planner +stays accurate. +- AC1: approval moves the assignment to `ON_LEAVE` for the period. + +**G4 — Record attendance** +As **site staff**, I want to record daily attendance, so that wages can be +computed. +- AC1: one row per assignment per day; statuses present/absent/half/leave/sign-off. +- AC2: no verification gate (operational). + +--- + +## Epic H — Appraisal + +**H1 — Raise appraisal** +As a **PM (site staff)**, I want to raise an appraisal for a crew member, so that +performance is recorded. +- AC1: appraisal starts `DRAFT`, submits to `SUBMITTED`. + +**H2 — Verify appraisal** +As an **MPO**, I want to verify a submitted appraisal, so that the Manager +reviews vetted input. +- AC1: verify → `MPO_VERIFIED`; return → `REJECTED` + remarks. + +**H3 — Approve appraisal** +As a **Manager**, I want to approve a verified appraisal, so that it's final. +- AC1: only `MPO_VERIFIED` appraisals can be approved. + +--- + +## Epic I — Office verification + +**I1 — MPO verification queue** +As an **MPO**, I want a queue of pending site records (documents, PPE, leave, +next-of-kin), so that I can verify everything except attendance. +- AC1: verify sets `VERIFIED` + me as `verifiedBy`; reject returns with remarks. + +**I2 — Accounts bank/EPF verification** +As **Accounts**, I want to verify bank details and EPF (UAN/Aadhaar vs EPFO), so +that payments go to the right verified account. +- AC1: only Accounts can verify bank/EPF. +- AC2: EPFO check result is recorded. + +--- + +## Epic J — Payroll / wage report + +**J1 — Generate wage report** +As a **Manager (or month-end job)**, I want a per-site wage report +(`crew × salary × days-attended + victualing`), so that Accounts can pay. +- AC1: one `WageLine` per active assignment; `lineTotal` computed. +- AC2: attendance gaps flagged before generation. + +**J2 — Approve & send** +As a **Manager**, I want to approve the report and send it to Accounts, so that +disbursement is authorised. +- AC1: `MANAGER_APPROVED` → `SENT_TO_ACCOUNTS`. + +**J3 — Accounts view & export** +As **Accounts**, I want to view and export the wage report (XLSX/PDF), so that I +can disburse. +- AC1: export reuses the PO export mechanism. + +--- + +## Epic K — Sign-off & experience + +**K1 — Sign off crew** +As **site staff / MPO**, I want to sign off a crew member, so that the tour +closes cleanly. +- AC1: assignment → `SIGNED_OFF` with `signOffDate`. +- AC2: an `EXPERIENCE_RECORD` is appended (vessel type, rank, duration). +- AC3: a backfill requisition is auto-raised (links to A1). + +**K2 — Experience history** +As an **MPO**, I want a crew member's accumulated experience on file, so that +ex-hand vetting is instant. +- AC1: internal tours appear automatically; declared experience can be added. + +--- + +## Epic L — Reference data & admin + +**L1 — Manage ranks** +As an **Admin/Manager**, I want to manage the rank hierarchy, so that +requisitions and the org chart are correct. +- AC1: ranks form a self-hierarchy with category + `isSeafarer`. + +**L2 — Manage rank document requirements** +As an **Admin/Manager**, I want to set which documents each rank requires, so +that vetting and uploads enforce the right set. +- AC1: e.g. driver → driving licence; deck/engine → STCW/CDC/COC/medical. + +--- + +## Epic M — Cross-cutting + +**M1 — Crewing dashboard** +As a **Manager**, I want crewing stat cards (open requisitions, candidates in +pipeline, expiring documents, crew on leave), so that I see status at a glance. + +**M2 — Notifications** +As any **actor**, I want email + in-app notifications on the events relevant to +me (requisition raised, proposal, appraisal verified, wage report ready), so +that I act promptly. + +**M3 — Audit trail** +As an **Auditor**, I want every crewing transition recorded with actor/time/note, +so that the process is fully traceable. +- AC1: `CrewAction` rows for all lifecycle and verification events. + +**M4 — Feature flag** +As an **operator**, I want the crewing surface behind `NEXT_PUBLIC_CREWING_ENABLED`, +so that it can be rolled out gradually. + +--- + +### Suggested delivery order + +1. Reference data (L1, L2) + data model + permissions. +2. Requisitions (A) + sign-off/experience (K). +3. Candidate intake (B) + pipeline (C) + onboarding (D). +4. Crew records (E), PPE (F), leave/attendance (G). +5. Verification (I), appraisal (H). +6. Payroll (J), dashboard/notifications (M). diff --git a/Crewing-Workflows.md b/Crewing-Workflows.md new file mode 100644 index 0000000..b8c8932 --- /dev/null +++ b/Crewing-Workflows.md @@ -0,0 +1,291 @@ +# Crewing Workflows + +Every crewing lifecycle is enforced by a single state-machine module (the +`po-state-machine.ts` pattern) and recorded as `CrewAction` audit rows. This +page is the authoritative behaviour spec: **state diagrams**, **transition +tables**, and **sequence diagrams** for the end-to-end flows. + +--- + +## 1. Requisition lifecycle + +The vacancy. Raised automatically on sign-off / end-of-contract, or manually. + +```mermaid +stateDiagram-v2 + [*] --> OPEN : raise (auto on sign-off / manual) + OPEN --> SHORTLISTING : add candidates + SHORTLISTING --> PROPOSING : a candidate clears vetting + PROPOSING --> INTERVIEWING : interview scheduled + PROPOSING --> SELECTED : ex-hand, interview waived + INTERVIEWING --> SELECTED : candidate selected + INTERVIEWING --> SHORTLISTING : all rejected, re-source + SELECTED --> FILLED : onboarded (assignment created) + OPEN --> CANCELLED : vacancy withdrawn + SHORTLISTING --> CANCELLED + FILLED --> [*] + CANCELLED --> [*] +``` + +| From | Action | To | Roles | Side-effect | +|---|---|---|---|---| +| — | `raise` | OPEN | system (sign-off) / SITE_STAFF / MANNING | email MPO | +| OPEN | `start_shortlist` | SHORTLISTING | MANNING | — | +| SHORTLISTING | `propose` | PROPOSING | MANNING | email Manager | +| PROPOSING | `schedule_interview` | INTERVIEWING | MANNING | email candidate | +| PROPOSING | `select` (ex-hand) | SELECTED | MANNING, MANAGER | email candidate | +| INTERVIEWING | `select` | SELECTED | MANNING, MANAGER | email candidate | +| INTERVIEWING | `reject_all` | SHORTLISTING | MANNING | — | +| SELECTED | `onboard` | FILLED | MANNING, MANAGER | **onboarding side-effects** | +| OPEN/SHORTLISTING | `cancel` | CANCELLED | MANAGER | — | + +--- + +## 2. Candidate pipeline (Application) + +The per-candidate gated vetting from notebook page 2. Many applications run per +requisition; one ends in `ONBOARDED`. **Ex-hands skip the interview** (optional). +Any gate can end in **rejection with remarks**. + +```mermaid +stateDiagram-v2 + [*] --> SHORTLISTED + SHORTLISTED --> COMPETENCY_CHECK + COMPETENCY_CHECK --> DOC_VERIFICATION : pass + DOC_VERIFICATION --> REFERENCE_CHECK : pass + REFERENCE_CHECK --> SALARY_AGREEMENT : pass + SALARY_AGREEMENT --> PROPOSED : agreed + PROPOSED --> INTERVIEW : new candidate + PROPOSED --> SELECTED : ex-hand (interview waived) + INTERVIEW --> SELECTED : pass + SELECTED --> JOINING : joining formalities + JOINING --> ONBOARDED : contract + salary confirmed + COMPETENCY_CHECK --> REJECTED : fail (remarks) + DOC_VERIFICATION --> REJECTED : fail (remarks) + REFERENCE_CHECK --> REJECTED : fail (remarks) + SALARY_AGREEMENT --> REJECTED : not agreed + INTERVIEW --> REJECTED : fail (remarks) + ONBOARDED --> [*] + REJECTED --> [*] +``` + +Each transition writes an `APPLICATION_GATE` row (`gate`, `result`, `note`, +`decidedById`) so the office can see exactly who cleared what and why a +candidate was rejected. + +--- + +## 3. Assignment lifecycle + +An onboarded tour of duty. + +```mermaid +stateDiagram-v2 + [*] --> ACTIVE : onboard + ACTIVE --> ON_LEAVE : leave approved + ON_LEAVE --> ACTIVE : return + ACTIVE --> SIGNED_OFF : sign-off + ON_LEAVE --> SIGNED_OFF : sign-off (EOC / medical / etc.) + SIGNED_OFF --> [*] +``` + +`onboard` starts salary + victualing + attendance + experience + PF + PPE. +`sign-off` closes the tour, appends an `EXPERIENCE_RECORD`, and **auto-raises a +backfill Requisition** for the same rank/vessel. + +--- + +## 4. Appraisal lifecycle + +```mermaid +stateDiagram-v2 + [*] --> DRAFT : PM starts + DRAFT --> SUBMITTED : PM submits + SUBMITTED --> MPO_VERIFIED : MPO verifies + SUBMITTED --> REJECTED : MPO returns (remarks) + MPO_VERIFIED --> MANAGER_APPROVED : Manager approves + MPO_VERIFIED --> REJECTED : Manager returns + MANAGER_APPROVED --> [*] + REJECTED --> DRAFT : revise +``` + +--- + +## 5. Wage report status + +```mermaid +stateDiagram-v2 + [*] --> DRAFT : month-end trigger + DRAFT --> GENERATED : lines computed + GENERATED --> MANAGER_APPROVED : Manager approves + MANAGER_APPROVED --> SENT_TO_ACCOUNTS : dispatched + SENT_TO_ACCOUNTS --> [*] +``` + +--- + +## 6. Sequence — sign-off → auto-requisition → fill + +```mermaid +sequenceDiagram + actor SS as Site staff (PM) + participant SYS as App / state machines + actor MPO + actor MGR as Manager + participant DB as PostgreSQL + participant N as Notifier + + SS->>SYS: sign off crew member (EOC) + SYS->>DB: assignment → SIGNED_OFF + SYS->>DB: append EXPERIENCE_RECORD + SYS->>DB: create Requisition (autoRaised, OPEN) + SYS->>N: notify MPO "vacancy: on " + N-->>MPO: email + bell + MPO->>SYS: start shortlist, add candidates + SYS->>DB: applications (SHORTLISTED) + Note over MPO,SYS: candidate pipeline (see §7) + MPO->>SYS: select candidate → onboard + SYS->>SYS: onboarding side-effects + SYS->>DB: requisition → FILLED, assignment ACTIVE +``` + +--- + +## 7. Sequence — candidate self-apply + CV parse + +```mermaid +sequenceDiagram + actor C as Candidate + participant WEB as /careers/apply (public) + participant API as /api/crewing/cv-parse + participant CVP as CvParseService + participant FS as File storage + participant DB as PostgreSQL + actor MPO + + C->>WEB: fill form + upload CV + WEB->>FS: store CV (signed upload) + WEB->>API: parse CV + API->>CVP: extract age, vessel type, rank, experience + CVP-->>API: fields (best-effort) + alt extracted + API-->>WEB: pre-filled draft + else not extractable + API-->>WEB: empty draft (manual entry) + end + C->>WEB: confirm / correct details + WEB->>DB: CrewMember (PROSPECT) + Application(s) + WEB-->>MPO: appears in shortlist queue +``` + +Manual MPO upload follows the same path minus the public form: MPO uploads the +CV and confirms the parsed fields. + +--- + +## 8. Sequence — recruitment pipeline (vetting → onboard) + +```mermaid +sequenceDiagram + actor MPO + participant SYS as application-pipeline + actor MGR as Manager + actor C as Candidate + participant DB as PostgreSQL + + MPO->>SYS: competency check + SYS->>DB: gate(competency)=VERIFIED + MPO->>SYS: document verification + SYS->>DB: gate(document)=VERIFIED + MPO->>SYS: reference check + SYS->>DB: gate(reference)=VERIFIED + MPO->>SYS: salary agreement (propose structure) + SYS->>DB: gate(salary)=VERIFIED, proposedSalary + MPO->>C: proposal + alt new candidate + MPO->>SYS: schedule + record interview + SYS->>DB: gate(interview)=VERIFIED + else ex-hand + Note over MPO,SYS: interview waived + end + MGR->>SYS: approve salary structure + select + SYS->>DB: application SELECTED + MGR->>SYS: onboard (joining formalities) + SYS->>DB: CrewAssignment ACTIVE, contract letter, employeeId + Note over SYS,DB: salary+victualing+attendance+experience+PF+PPE started +``` + +Any failed gate → `gate=REJECTED` + remarks → application `REJECTED` +(visible to the office with the reason). + +--- + +## 9. Sequence — site data entry & office verification + +```mermaid +sequenceDiagram + actor SS as Site staff + participant SYS as App + participant DB as PostgreSQL + actor MPO + actor ACC as Accounts + + SS->>SYS: upload documents / PPE issue / leave / next-of-kin / EPF + SYS->>DB: records (verificationStatus=PENDING) + par MPO verifies (all except attendance) + MPO->>SYS: verify document / PPE / leave / NoK + SYS->>DB: VERIFIED (verifiedById=MPO) + and Accounts verifies bank + EPF + ACC->>SYS: verify bank detail + ACC->>SYS: verify EPF (UAN/Aadhaar vs EPFO) + SYS->>DB: VERIFIED (verifiedById=Accounts) + end + SS->>SYS: record attendance (no verify gate) + SYS->>DB: attendance rows +``` + +Site staff see contract letters **view-only except salary** and bank details +**view-only**; salary and full account numbers are gated. + +--- + +## 10. Sequence — monthly wage report + +```mermaid +sequenceDiagram + participant CRON as Month-end trigger + participant WR as wage-report.ts + participant DB as PostgreSQL + actor MGR as Manager + actor ACC as Accounts + participant EXP as /payroll/[id]/export + + CRON->>WR: generate for site, period YYYY-MM + WR->>DB: read assignments + attendance + salary + WR->>DB: WageLine = days × dailyRate + victualing + WR->>DB: WageReport (GENERATED, totalAmount) + WR-->>MGR: notify "wage report ready" + MGR->>DB: approve → MANAGER_APPROVED + MGR->>DB: send → SENT_TO_ACCOUNTS + DB-->>ACC: appears in Accounts queue + ACC->>EXP: export XLSX/PDF for disbursement +``` + +--- + +## 11. Side-effects of onboarding (the one event that starts everything) + +| Side-effect | What it creates / starts | +|---|---| +| Salary | activates the approved `SalaryStructure` | +| Victualing | begins per-day messing accrual | +| Attendance | opens daily attendance for the assignment | +| Experience | begins accrual on this vessel/rank (closed into `EXPERIENCE_RECORD` at sign-off) | +| PF | enables EPF tracking against the crew member's UAN | +| PPE | seeds the PPE issue checklist for the kit | +| Identity | assigns `employeeId`, sets `CrewMember.status = EMPLOYEE` | +| Requisition | flips the filled requisition to `FILLED` | + +All recorded as a single `ONBOARDED` `CrewAction` with the created IDs in +`metadata` — the same "one transition, one audit row, declared side-effects" +discipline as PO approval. diff --git a/README.md b/README.md new file mode 100644 index 0000000..baa5e61 --- /dev/null +++ b/README.md @@ -0,0 +1,36 @@ +# Crewing wiki bundle + +Design documentation for the **Crewing / Manning Module** of Pelagia Portal +(PPMS). Drop these pages into the Forgejo wiki repo root and push. + +## Pages + +| File | Wiki page | Contains | +|---|---|---| +| `Crewing-Module.md` | Crewing Module | Landing page / feature overview | +| `Crewing-Design-Document.md` | Crewing Design Document | Goals, scope, domain narrative, decisions, open questions | +| `Crewing-Architecture.md` | Crewing Architecture | System + **component diagram**, module layout | +| `Crewing-Data-Model.md` | Crewing Data Model | Enums, rank hierarchy, **ER diagram** | +| `Crewing-Workflows.md` | Crewing Workflows | **State machines** + **sequence diagrams** | +| `Crewing-Use-Cases.md` | Crewing Use Cases | **Use-case diagram** + catalogue | +| `Crewing-Roles-and-Permissions.md` | Crewing Roles and Permissions | Permission matrix extension | +| `Crewing-User-Stories.md` | Crewing User Stories | Backlog by epic with acceptance criteria | + +## Diagrams + +All diagrams are **Mermaid** fenced code blocks (` ```mermaid `), which Forgejo +renders natively — no screenshot pipeline needed (unlike the script-based +mockups). If a target renderer doesn't support Mermaid, the blocks degrade to +readable text. The `images/crewing/` folder is reserved for any future static +captures and currently empty. + +## Cross-links + +Pages link to each other and to existing wiki pages (`Architecture`, +`Data-Model`, `Roles-and-Permissions`, `PO-Lifecycle`, `File-Storage`, +`Inventory-and-Catalogue`) using Forgejo's `[Text](Page-Name)` wiki-link form. + +## Source + +Derived from the director meeting notes, two notebook pages (site-staff record +permissions; recruitment flow), and `Crewing.excalidraw` (rank hierarchy).