docs: Crewing management System
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
parent
2029ae7083
commit
cd4f85918c
9 changed files with 1638 additions and 0 deletions
188
Crewing-Architecture.md
Normal file
188
Crewing-Architecture.md
Normal file
|
|
@ -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<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
|
||||
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.
|
||||
349
Crewing-Data-Model.md
Normal file
349
Crewing-Data-Model.md
Normal file
|
|
@ -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 |
|
||||
164
Crewing-Design-Document.md
Normal file
164
Crewing-Design-Document.md
Normal file
|
|
@ -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).
|
||||
72
Crewing-Module.md
Normal file
72
Crewing-Module.md
Normal file
|
|
@ -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)).
|
||||
102
Crewing-Roles-and-Permissions.md
Normal file
102
Crewing-Roles-and-Permissions.md
Normal file
|
|
@ -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.**
|
||||
107
Crewing-Use-Cases.md
Normal file
107
Crewing-Use-Cases.md
Normal file
|
|
@ -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<br/>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.
|
||||
329
Crewing-User-Stories.md
Normal file
329
Crewing-User-Stories.md
Normal file
|
|
@ -0,0 +1,329 @@
|
|||
# Crewing User Stories
|
||||
|
||||
Backlog for the Crewing Module, grouped into epics. Format:
|
||||
*As a `<role>`, I want `<capability>`, so that `<value>`* + 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).
|
||||
291
Crewing-Workflows.md
Normal file
291
Crewing-Workflows.md
Normal file
|
|
@ -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: <rank> on <vessel>"
|
||||
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.
|
||||
36
README.md
Normal file
36
README.md
Normal file
|
|
@ -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).
|
||||
Loading…
Add table
Reference in a new issue