docs(crewing): fix leave-planner inconsistencies

- Leave is applied by the Site in-charge on behalf of crew (crew are not
  portal users), not self-service.
- Attendance is visible to site staff + Manager only; the MPO has no
  attendance access. Drop MPO from record_attendance and (consequently)
  generate_wage_report; add a view_attendance permission.
- A leave clash (approved leaves overlapping below a rank's required
  strength) auto-raises a LEAVE requisition; documented across module,
  design decisions, data model, workflows (new sequence), use cases,
  user stories, roles matrix and the architecture diagram.
Hardik 2026-06-22 05:13:22 +05:30
parent 8eaf2ed0a0
commit 2fcd41596e
8 changed files with 130 additions and 34 deletions

@ -113,6 +113,7 @@ flowchart TB
PPE --> A5 --> DB
PLAN --> A6
ATT --> A6 --> DB
A6 -.leave clash.-> SM1
APPR --> A7 --> SM4 --> DB
PAY --> A8 --> WR --> DB
PAY --> H3

@ -210,7 +210,7 @@ erDiagram
string assignmentId FK
date date
enum status "AttendanceStatus"
string recordedById FK
string recordedById FK "site staff; visible to site + Manager, not MPO"
}
LEAVE_REQUEST {
string id PK
@ -219,7 +219,7 @@ erDiagram
date fromDate
date toDate
enum status "LeaveStatus"
string appliedById FK
string appliedById FK "Site in-charge (applies for crew)"
}
PPE_ISSUE {
string id PK
@ -327,8 +327,15 @@ erDiagram
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).
**MPO**; bank + EPF → **Accounts**. **Attendance** is operational: no verify
gate, and it is **visible only to site staff and the Manager — the MPO has no
attendance access** (the Manager reviews it for the wage report).
- **Leave** (`LEAVE_REQUEST`) is **applied by the Site in-charge for a crew
member** (`appliedById` = site staff, `assignmentId` = the crew member) and
**decided by the MPO**. An approved leave that **clashes** — overlaps others for
the same rank below its required strength — auto-raises a `Requisition`
(`reason = LEAVE`, `autoRaised`, system-raised), mirroring the sign-off backfill
on `CrewAssignment`.
- **`SalaryStructure`** holds `basic`, `victualingPerDay`, and a JSON
`allowances`; approved by a Manager. Salary is the restricted field on the
contract letter for site staff.

@ -51,7 +51,9 @@ 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.
otherwise site staff or the MPO raise it manually. A **leave clash** — when
approved leaves overlap so a rank drops below its required strength on a
vessel/site — likewise **auto-raises a Requisition** (reason `LEAVE`).
### 3.2 Sourcing candidates
The MPO shortlists candidates for the rank. **Ex-hands are preferred** (people
@ -89,14 +91,19 @@ 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**.
for **leave on behalf of crew members** through the site **leave planner** (which
shows every crew member's contract span and leaves on one timeline; crew are data
subjects, not portal users, so the **Site in-charge applies leave for them**). If
an approved leave **clashes** with another for the same rank, the planner
auto-raises a backfill **Requisition**. 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).
everything site staff enter, except attendance** — which the **MPO cannot see at
all**; attendance is an operational record reviewed by the **Manager** (it feeds
the wage report). **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
@ -122,6 +129,8 @@ Manager**, and **sent to Accounts** for disbursement.
| 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. |
| D11 | **A leave clash auto-raises a Requisition.** When approved leaves overlap so a rank falls below its required strength, the system raises a `LEAVE`-reason requisition automatically (same path as the sign-off backfill). | Overlapping leave was the planner's main blind spot; auto-raising keeps cover proactive instead of reactive. |
| D12 | **Attendance is Manager-only at the office.** Site staff record it; the **Manager** reviews it; the **MPO has no attendance access**. | The MPO's remit is recruitment + record verification (docs/PPE/leave/NoK); attendance is an operational/payroll input the Manager owns. |
## 5. Non-functional requirements
@ -148,6 +157,9 @@ Manager**, and **sent to Accounts** for disbursement.
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.
6. Each rank has a **required strength** per vessel/site (default 1). A *leave
clash* = approved leaves overlapping such that active crew for that rank would
fall below that strength; this is what triggers the auto-requisition (D11).
## 7. Open questions

@ -16,9 +16,9 @@ for email/in-app events.
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.
- **Vacancies are reactive and untracked.** A sign-off or a leave clash creates a
gap that nobody formally owns until a vessel is short-handed. → Auto-raised
**Requisitions** on sign-off / end-of-contract and on a leave clash.
- **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.
@ -32,17 +32,17 @@ for this design were literally two notebook pages). The pain points it removes:
| Area | Capability |
|---|---|
| **Requisitions** | Auto- or manually-raised vacancy for a rank on a vessel/site; reason (leave / end-of-contract / termination / medical / other). |
| **Requisitions** | Auto-raised (on sign-off / end-of-contract, or when a **leave clash** leaves a rank short) 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. |
| **Leave & attendance** | Site-level leave planner — the **Site in-charge applies leave on behalf of crew**, and an overlapping **leave clash auto-raises a requisition**. Daily attendance is captured on site and reviewed by the **Manager** (the MPO has no attendance access). |
| **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. |
| **Verification** | Office sign-off: MPO verifies everything except attendance (which the MPO cannot see — it is the Manager's to review); bank + EPF verified by Accounts. |
## How it fits PPMS

@ -9,10 +9,10 @@ relevant state machine adds the status+role gate on top.
| 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 |
| PM / APM / Site In-charge | **`SITE_STAFF`** (new, proposed) | apply-only leave **(on behalf of crew)**, 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; **no attendance access** |
| Accounts | **`ACCOUNTS`** | verifies bank + EPF; consumes wage report |
| Manager | **`MANAGER`** | approves salary structures, candidate list, appraisals, wage reports |
| Manager | **`MANAGER`** | approves salary structures, candidate list, appraisals, wage reports; **reviews attendance** |
| Superuser | **`SUPERUSER`** | combined authority across crewing actions |
| Auditor | **`AUDITOR`** | read-only across crewing |
| Admin | **`ADMIN`** | manages ranks & doc requirements |
@ -44,13 +44,14 @@ relevant state machine adds the status+role gate on top.
| `issue_ppe` | ✓ | ✓ | | ✓ | ✓ | | |
| `apply_leave` | ✓ | ✓ | | ✓ | ✓ | | |
| `decide_leave` | | ✓ | | ✓ | ✓ | | |
| `record_attendance` | ✓ | ✓ | | ✓ | ✓ | | |
| `record_attendance` | ✓ | | | | ✓ | | |
| `view_attendance` ³ | ✓ | | | ✓ | ✓ | ✓ | |
| `verify_site_records` (docs/PPE/leave/NoK) | | ✓ | | ✓ | ✓ | | |
| `verify_bank_epf` | | | ✓ | ✓ | ✓ | | |
| `raise_appraisal` | ✓ | | | ✓ | ✓ | | |
| `verify_appraisal` | | ✓ | | ✓ | ✓ | | |
| `approve_appraisal` | | | | ✓ | ✓ | | |
| `generate_wage_report` | | | | ✓ | ✓ | | |
| `generate_wage_report` | | | | ✓ | ✓ | | |
| `approve_wage_report` | | | | ✓ | ✓ | | |
| `view_wage_report` | | | ✓ | ✓ | ✓ | ✓ | ✓ |
| `manage_ranks` | | | | ✓ | | | ✓ |
@ -58,6 +59,10 @@ relevant state machine adds the status+role gate on top.
¹ 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.
³ **Attendance is deliberately not visible to the MPO** (or Accounts). Site staff
record it; the **Manager** reviews it (it feeds the wage report).
⁴ The wage report reads attendance, so it is generated by the **Manager** (or the
month-end job) — **not** the MPO, who has no attendance access.
## 3. Field-level restrictions (PII)
@ -76,7 +81,14 @@ Enforced in Server Components/Actions on top of the permission map:
**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).
- **Attendance** has no verification gate (operational record). It is **recorded by
site staff and visible only to site staff and the Manager** — the **MPO cannot
see attendance**; the Manager reviews it (it feeds the wage report).
- **Leave is applied by the Site in-charge on behalf of crew** (crew are not portal
users); the **MPO decides** (approves/rejects) leave. An **approved leave that
clashes** — overlaps another for the same rank below its required strength —
**auto-raises a `LEAVE`-reason requisition** (system-raised, same backfill path
as sign-off).
- 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

@ -18,6 +18,7 @@ flowchart LR
subgraph Requisition
U1(["Raise requisition"])
U2(["Sign off crew → auto-requisition"])
U2b(["Leave clash → auto-requisition (system)"])
U3(["Cancel requisition"])
end
subgraph Recruitment
@ -57,7 +58,7 @@ flowchart LR
SS --- U1 & U2 & U12 & U13 & U14 & U15 & U16 & U19
CAND --- U4
MPO --- U5 & U6 & U7 & U8 & U9 & U11 & U17 & U20
MPO --- U5 & U6 & U7 & U8 & U9 & U11 & U17 & U20 & U2b
MGR --- U3 & U9 & U10 & U11 & U21 & U22 & U23 & U24
ACC --- U18 & U24
ADM --- U25
@ -78,9 +79,9 @@ flowchart LR
| 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-12 | Apply for leave (for a crew member) | Site in-charge | crew has active assignment | `LeaveRequest(APPLIED)` on the crew member's assignment |
| 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-14 | Record attendance | Site staff | active assignment | daily `Attendance` rows (visible to site staff + Manager; **not** the MPO) |
| 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) |
@ -92,6 +93,7 @@ flowchart LR
| 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 |
| UC-26 | Auto-requisition on leave clash | System | approved leaves overlap below a rank's required strength | `Requisition(OPEN, reason=LEAVE, autoRaised)`; MPO notified |
## 3. Notable extensions / alternates
@ -105,3 +107,10 @@ flowchart LR
for the site to correct.
- **UC-22 alt** — attendance gaps flagged before generation; report can be
regenerated until approved.
- **UC-12 note** — crew are data subjects, not portal users, so the **Site
in-charge applies leave on their behalf** (apply-only; the MPO decides it).
- **UC-14 note** — attendance is recorded by site staff and **reviewed by the
Manager**; the **MPO has no attendance access** (so the wage report, UC-22, is
generated by the Manager / month-end job, not the MPO).
- **UC-26** — a leave clash (overlapping approved leaves below a rank's required
strength) auto-raises a `LEAVE` requisition; same backfill path as UC-02.

@ -35,6 +35,16 @@ withdrawn vacancies don't clutter the pipeline.
- AC1: only non-`FILLED` requisitions can be cancelled.
- AC2: reason is required and audited.
**A5 — Auto-requisition on leave clash**
As the **system**, I want a requisition raised automatically when an approved
leave clashes (a rank drops below its required strength on a vessel/site), so that
the cover gap is owned immediately.
- AC1: approving a leave that overlaps others for the same rank below required
strength creates a `Requisition(OPEN, reason=LEAVE, autoRaised)`.
- AC2: the MPO is notified (email + in-app) and the clash is highlighted on the
planner.
- AC3: a `CrewAction(REQUISITION_RAISED)` is written.
---
## Epic B — Candidate intake
@ -194,23 +204,29 @@ As **site staff**, I want to record PPE returns, so that the register is current
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.
- AC2: overlapping absences for the same rank are highlighted (a clash below
required strength auto-raises a requisition — see **A5**).
**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`.
**G2 — Apply for leave (for a crew member)**
As the **Site in-charge**, I want to apply for leave **on behalf of a crew member**
(apply-only; crew are not portal users), so that the office can approve it.
- AC1: I select the crew member's assignment, dates and type; status starts `APPLIED`.
- AC2: site staff cannot self-approve (no `decide_leave`).
**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.
- AC2: if the approval creates a clash (the rank falls below required strength for
the overlap), a backfill requisition is auto-raised — see **A5**.
**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).
- AC3: attendance is visible to **site staff and the Manager only** — the **MPO
has no attendance access**.
---
@ -238,6 +254,8 @@ As a **Manager**, I want to approve a verified appraisal, so that it's final.
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.
- AC2: **attendance never appears in this queue** — the MPO has no attendance
access (it is the Manager's to review).
**I2 — Accounts bank/EPF verification**
As **Accounts**, I want to verify bank details and EPF (UAN/Aadhaar vs EPFO), so

@ -9,11 +9,12 @@ 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.
The vacancy. Raised automatically on sign-off / end-of-contract or on a **leave
clash**, or manually.
```mermaid
stateDiagram-v2
[*] --> OPEN : raise (auto on sign-off / manual)
[*] --> OPEN : raise (auto on sign-off / leave clash / manual)
OPEN --> SHORTLISTING : add candidates
SHORTLISTING --> PROPOSING : a candidate clears vetting
PROPOSING --> INTERVIEWING : interview scheduled
@ -29,7 +30,7 @@ stateDiagram-v2
| From | Action | To | Roles | Side-effect |
|---|---|---|---|---|
| — | `raise` | OPEN | system (sign-off) / SITE_STAFF / MANNING | email MPO |
| — | `raise` | OPEN | system (sign-off **or leave clash**) / SITE_STAFF / MANNING | email MPO |
| OPEN | `start_shortlist` | SHORTLISTING | MANNING | — |
| SHORTLISTING | `propose` | PROPOSING | MANNING | email Manager |
| PROPOSING | `schedule_interview` | INTERVIEWING | MANNING | email candidate |
@ -93,6 +94,40 @@ stateDiagram-v2
`sign-off` closes the tour, appends an `EXPERIENCE_RECORD`, and **auto-raises a
backfill Requisition** for the same rank/vessel.
### 3.1 Leave & the clash backfill
Leave is **applied by the Site in-charge on behalf of a crew member**
(`LeaveRequest(APPLIED)`) and **decided by the MPO** (`APPROVED` / `REJECTED`).
An approval moves the assignment to `ON_LEAVE` for the period. On approval the
state machine checks the rank's cover on that vessel/site over the leave window:
if the approved leaves would drop the rank **below its required strength**, it
**auto-raises a backfill `Requisition`** (reason `LEAVE`, `autoRaised`) — the same
mechanism as the sign-off backfill, surfaced on the planner where the clash is
highlighted. (Attendance, by contrast, is recorded on site and reviewed by the
**Manager**; the **MPO has no attendance access**.)
```mermaid
sequenceDiagram
actor SIC as Site in-charge
participant SYS as App / state machines
actor MPO
participant DB as PostgreSQL
participant N as Notifier
SIC->>SYS: apply leave for crew member (dates)
SYS->>DB: LeaveRequest (APPLIED)
MPO->>SYS: approve leave
SYS->>DB: LeaveRequest APPROVED; assignment ON_LEAVE
SYS->>SYS: check rank cover over the leave window
alt clash — rank below required strength
SYS->>DB: create Requisition (reason=LEAVE, autoRaised, OPEN)
SYS->>N: notify MPO "vacancy (leave clash): <rank> on <vessel>"
N-->>MPO: email + bell
else covered
Note over SYS: no requisition needed
end
```
---
## 4. Appraisal lifecycle
@ -245,7 +280,9 @@ sequenceDiagram
```
Site staff see contract letters **view-only except salary** and bank details
**view-only**; salary and full account numbers are gated.
**view-only**; salary and full account numbers are gated. **Attendance** is
entered on site and **reviewed by the Manager only — the MPO has no attendance
access**; it has no verification gate.
---