Commit graph

91 commits

Author SHA1 Message Date
Claude (auto-fix)
5cefe8f7ed feat(history): paginate PO history with items-per-page control
All checks were successful
PR checks / checks (pull_request) Successful in 44s
PR checks / integration (pull_request) Successful in 32s
The /history page fetched a fixed first 200 POs in one flat table with no
way to page further and no control over page size. Replace that with real
pagination:

- page.tsx: read page/perPage from searchParams; clamp perPage to 25/50/100
  (default 25) and page to [1, totalPages] via a new shared resolvePagination
  helper. Swap the fixed take:200 for skip/take + a count() for totals.
  Replace the "first 200 results" notice with a footer ("Showing X-Y of N",
  Prev/Next, page indicator) that preserves all filters. Export PDF/CSV links
  stay on the full filtered set.
- history-filters.tsx: add a Per-page dropdown; changing it or any filter
  resets to page 1 while preserving perPage in the URL.
- lib/pagination.ts: dependency-free clamp/skip/take helper, unit-tested.

Verified: type-check clean, 272 unit tests pass (9 new), skip/take windows
and clamping checked against the test DB.

Fixes #104
2026-06-24 03:26:47 +05:30
dc9ab327b8 Merge branch 'master' into feat/delivery-locations
All checks were successful
PR checks / checks (pull_request) Successful in 44s
PR checks / integration (pull_request) Successful in 31s
2026-06-23 20:43:24 +00:00
5aae45299b feat(po): admin-managed delivery locations + Place of Delivery dropdown (#19)
All checks were successful
PR checks / checks (pull_request) Successful in 42s
PR checks / integration (pull_request) Successful in 30s
Replaces the free-text "Place of Delivery" with a dropdown sourced from a new
admin-managed Delivery Locations list (each = a Company FK + free-text address).

- schema + migration: new DeliveryLocation model (companyId, address, isActive).
- permission: manage_delivery_locations granted to Manager + SuperUser + Admin
  (Manager-accessible, not admin-only, per the issue).
- admin screen /admin/delivery-locations: table + Add/Edit dialogs +
  activate/deactivate + delete (mirrors /admin/sites); sidebar link under
  Administration for Manager/SuperUser/Admin.
- PO forms (new / edit / manager-edit): shared <DeliveryLocationField> native
  select populated from active locations, formatted "Company — address".
- PurchaseOrder.placeOfDelivery stays a free-text SNAPSHOT (no FK) — the dropdown
  only changes how the value is picked, so export/import/historical POs are
  unchanged, and an edit preserves a current value not in the list as a
  "(current)" option. Deleting a location is therefore always safe.
- tests: delivery-location CRUD + permission guard (6).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 02:08:59 +05:30
0e0e377718 Merge branch 'master' into feat/manager-advance-payment
All checks were successful
PR checks / checks (pull_request) Successful in 44s
PR checks / integration (pull_request) Successful in 30s
2026-06-23 20:26:09 +00:00
99c928213b feat(po): manager sets advance payment on approval (issue #92)
All checks were successful
PR checks / checks (pull_request) Successful in 43s
PR checks / integration (pull_request) Successful in 30s
The approving Manager decides how much of the PO is paid first, via a
0–100% slider on the approval card (default 100% = full). The slider is
convenience only — the resolved ABSOLUTE amount is stored on
PurchaseOrder.suggestedAdvancePayment (Decimal(12,2), nullable).

- schema + migration: add suggestedAdvancePayment (null = no explicit
  advance ⇒ full payment, preserves legacy behaviour).
- approvePo(): accepts the amount, clamps to [0, totalAmount], persists
  it, records it on the APPROVED audit row. Set once at approval; never
  edited afterwards.
- approval-actions.tsx: whole-percent slider showing the resolved ₹
  amount + remaining balance; value sent with Approve / Approve-with-Remarks.
- Accounts surface: payment queue + PO detail show the advance; it
  prefills the FIRST payment amount (only when nothing is paid yet and
  it is a true partial). Balance runs the normal PARTIALLY_PAID loop.
- Not shown on the exported PO/invoice (po-export-layout untouched).
- Tests: persist + audit metadata + clamp-to-total.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 01:40:20 +05:30
e7888a0886 Merge branch 'master' into fix/crewing-exhand-admin-only
All checks were successful
PR checks / checks (pull_request) Successful in 44s
PR checks / integration (pull_request) Successful in 30s
2026-06-23 16:22:47 +00:00
6da6c277ad Merge remote-tracking branch 'origin/master' into feat/submitter-view-all
All checks were successful
PR checks / checks (pull_request) Successful in 43s
PR checks / integration (pull_request) Successful in 30s
# Conflicts:
#	App/CLAUDE.md
#	App/components/layout/sidebar.tsx
#	App/lib/feature-flags.ts
2026-06-23 21:50:08 +05:30
e951a44a67 fix(crewing): make rank-held universal, ex-hand an admin-only flag
All checks were successful
PR checks / checks (pull_request) Successful in 41s
PR checks / integration (pull_request) Successful in 30s
Rank held applies to every candidate, not just ex-hands; it auto-updates
for returning crew on sign-off. Ex-hand designation is decoupled from the
Source dropdown and owned by the office:

- Candidate form: drop the EX_HAND source option, relabel "Rank held
  (ex-hands)" to "Rank held". addCandidate always intakes NEW/CANDIDATE
  (ex-hand recognition still reuses an existing EX_HAND row); updateCandidate
  no longer rewrites type/status, so an admin-set EX_HAND or onboarded
  EMPLOYEE is never clobbered by a candidate edit.
- Admin crew form: the type NEW/EX_HAND select becomes an "Ex-hand
  (returning crew)" checkbox -- the only place ex-hand is tagged.
- List/detail ex-hand indicators key on type === EX_HAND (not source).
- Sign-off preserves the original recruitment source when flipping to EX_HAND.
- Tests seed EX_HAND rows directly; assert candidate intake stays NEW.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 21:33:50 +05:30
93d13a415c feat(crewing): complete requisition list A3 (candidate count + filters)
- A3 AC2: each requisition row shows its candidate count (sourced via
  _count.applications in the list query) alongside the existing days-open age.
- A3 AC1: add rank and reason filters (derived from the visible data, like the
  existing vessel/site filter) on top of search + status + location.

requisitions.test.ts asserts the per-row candidateCount (2 vs 0) the page exposes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 23:52:05 +05:30
df950c7253 feat(crewing): ex-hand recognition (B3)
- AC1: addCandidate recognizes a returning hand re-entered as a fresh candidate
  — matched to their existing EX_HAND pool record by email (preferred) or exact
  name — and reuses that row instead of creating a duplicate, preserving tour
  history/documents/bank. Audited CANDIDATE_UPDATED { exHandRecognized: true }.
- AC2: the Candidates list sorts ex-hands above new candidates by default
  (stable, preserving createdAt order within each group).
- AC3: the candidate detail "Returning crew" callout now renders the matched
  member's actual tour history (ExperienceRecord) and documents on file.

candidates.test.ts covers email/name recognition, the no-match path, and the
ex-hand-first page ordering.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 23:49:43 +05:30
0679883273 refactor(crewing): correct audit action types + atomic auto-raise backfills
Audit-trail & transaction consistency (spec §11 "one transition, one row"):

- Action types: returnSalary/returnSelection/declineInterviewWaiver no longer
  mislabel a backward decision as its forward action. New CrewActionType members
  SALARY_RETURNED / SELECTION_RETURNED / WAIVER_DECLINED; added RECORD_DELETED;
  dropped the unused GATE_FAILED (migration recreates the enum).
- Deletions are audited: deleteDocument / deleteNextOfKin now write a
  RECORD_DELETED CrewAction (PII removals are traceable).
- Atomicity: autoRaiseRequisition takes an optional tx so the leave-clash and
  sign-off backfills are created INSIDE the approval/sign-off transaction; the
  office notification (notifyAutoRaised) fires after commit. An approved leave or
  a sign-off can no longer commit without its backfill requisition.

Tests assert the corrected action types (crewing-gates, crew-records) and the
existing clash/sign-off suites still pass with the in-transaction backfill.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 23:46:23 +05:30
184250f903 feat(crewing): enforce recruitment vetting gates C5 + partial C3
- C5 (Epic C5 AC1): advanceStage("verify_competency") now requires ≥1
  ReferenceCheck before leaving COMPETENCY_AND_REFERENCES.
- C3 (Epic C3 AC1): verifyDocuments blocks advancement when a mandatory document
  for the seat's rank that the candidate holds is expired. Missing-document
  presence stays enforced post-onboarding in the verification queue (seafarer
  docs aren't collected pre-onboarding) — documented inline + in wiki Tech-Debt.
- C4 (experience): deferred with an inline note (Requisition has no
  min-experience field yet — Epic A2 AC1).

applications.test.ts: reference-gate block/pass and expired-required-doc
block/renew-pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 23:40:08 +05:30
d796e81efc fix(crewing): harden onboardCandidate (D1 guard, D3 metadata, atomic contract)
- D1: require a Manager-approved SalaryStructure before onboarding; a SELECTED
  application with none is now blocked instead of silently binding zero salary
  rows.
- D3 AC2: the CREW_ONBOARDED CrewAction records the created IDs
  (assignmentId, employeeId, salaryStructureId) in metadata.
- Atomicity: the contract letter is uploaded before the transaction and its row
  is created INSIDE it, so onboarding is one atomic write (no half-onboarded
  crew member without a contract on failure).

onboarding.test.ts asserts the metadata and the new D1 block (no assignment, the
candidate stays a CANDIDATE).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 23:32:30 +05:30
06ff587024 fix(crewing): mask Aadhaar/PAN document numbers server-side
The crew profile page passed SeafarerDocument.number to the client unmasked for
all roles and all doc types, exposing full Aadhaar/PAN identity numbers to MPO /
Manager / Site staff — contradicting the field's PII annotation and §6 /
Roles-and-Permissions §3 (Aadhaar/PAN are gated to Accounts/SuperUser, same as
the bank account number).

- crew-pii.ts: add documentNumberValue(number, docType, role) — masks AADHAAR /
  PAN for non-privileged roles via the existing canViewFullBankEpf gate +
  maskTail; non-identity docs (passport, CDC, STCW…) pass through; preserves the
  string|null contract.
- crew/[id]/page.tsx: mask the number server-side before it crosses to the client.
- Tests: unit cases for the helper; an integration test that invokes the server
  component and asserts the documents prop is masked for MANAGER/SITE_STAFF/MPO
  and full for ACCOUNTS/SUPERUSER.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 23:29:11 +05:30
e193e26368 feat(crewing): EPFO/UAN assisted verification (GstService pattern, flagged)
All checks were successful
PR checks / checks (pull_request) Successful in 40s
PR checks / integration (pull_request) Successful in 30s
Scaffolds EPFO/UAN verification the same way GST works — a standalone Playwright
proxy microservice + an /api proxy + an assisted affordance that records the
result. Aadhaar stays manual (UIDAI-restricted). Stacks on the follow-ups branch.
Behind NEXT_PUBLIC_CREWING_ENABLED.

What's in
- EpfoService/ (new microservice, GstService pattern): Express + Playwright.
  POST /otp {uan} → session + OTP request; POST /verify {sessionId,uan,otp} →
  member record; GET /health. EPFO is OTP-gated (no anonymous captcha lookup like
  GST), so the handshake is two steps. Live portal navigation is gated behind
  EPFO_LIVE (default STUB: OTP 000000 → matched) until real selectors/OTP are
  validated. README documents the differences + that Aadhaar is out of scope.
- App: /api/epfo/otp + /api/epfo proxies (gated by verify_bank_epf) to
  EPFO_SERVICE_URL. EpfDetail += epfoMemberName + epfoCheckedAt (migration
  crewing_epfo_check). recordEpfoCheck action persists the EPFO result + audit.
- UI: an "EPFO check" affordance on the verification EPF rows — request OTP →
  enter OTP → matched member → record. Aadhaar noted as manual-only.

Tests & docs
- Integration: verification.test.ts gains recordEpfoCheck (records name+timestamp,
  Accounts-only gating). type-check clean; full unit (245) + integration (213)
  green (RESEND_API_KEY unset).
- .env.example (EPFO_SERVICE_URL/EPFO_LIVE), CLAUDE.md, EpfoService/README.

Note: the EpfoService live portal selectors/OTP are stubbed and must be validated
against a real EPFO session before enabling EPFO_LIVE.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 22:43:24 +05:30
df3b4bdc97 feat(crewing): resolve self-contained deferred follow-ups (flagged)
All checks were successful
PR checks / checks (pull_request) Successful in 42s
PR checks / integration (pull_request) Successful in 30s
Clears the self-contained deferrals tracked across phases. Stacks on 5b appraisal.
Behind NEXT_PUBLIC_CREWING_ENABLED.

- SITE_STAFF login on onboard/placement (Epic D follow-up): lib/crew-login.ts
  maybeCreateSiteStaffLogin creates a passwordless SITE_STAFF User (sharing the
  CRW- employee no., siteId = the assignment's site) when a grantsLogin rank is
  onboarded (onboardCandidate) or placed (placeCrew) and the crew member has an
  email. No-op otherwise.
- Own-site scoping (Epic E follow-up, §8.7): User.siteId added (migration
  crewing_followups); the Crew directory filters a SITE_STAFF user with a home site
  to crew whose active assignment is at that site (graceful when unset). The link is
  set at login creation.
- PPE / next-of-kin verify gates (Epic F/I follow-up): PpeIssue/NextOfKin gained
  verificationStatus + verifiedById; verifyPpe / verifyNextOfKin (verify_site_records,
  MPO) + queue sections in /crewing/verification.

Tests & docs
- Integration: crewing-followups.test.ts (6) — login created/skipped by rank+email
  (+ siteId set), PPE/NoK verify + reject-reason + already-decided guard + gating.
  type-check clean; full unit (245) + integration (211) green (RESEND_API_KEY unset).
- CLAUDE.md updated.

Part of Epic D (#78), Epic E (#79), Epic F (#80), Epic I (#83).
Still deferred (not self-contained): public careers API (A2); Pay-status pay rows (Phase 6).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 22:28:23 +05:30
c14a22588e feat(crewing): Phase 5b — appraisal (flagged)
All checks were successful
PR checks / checks (pull_request) Successful in 40s
PR checks / integration (pull_request) Successful in 30s
Final slice of Phase 5. The appraisal lifecycle raise → verify → approve across
three role-gated surfaces, per Crewing-Implementation-Spec §5.4/§8.14. Stacks on
5a verification. Behind NEXT_PUBLIC_CREWING_ENABLED. Completes Phase 5.

What's in
- Schema: Appraisal (on CrewAssignment) + AppraisalStatus
  (DRAFT/SUBMITTED/MPO_VERIFIED/MANAGER_APPROVED/REJECTED); CrewActionType +=
  APPRAISAL_SUBMITTED/VERIFIED/APPROVED/REJECTED. Migration crewing_appraisal.
- State machine lib/appraisal-state-machine.ts: verify (SUBMITTED→MPO_VERIFIED,
  MPO/Manager), approve (MPO_VERIFIED→MANAGER_APPROVED, Manager); orthogonal reject.
- Actions (crewing/appraisals/actions.ts): raiseAppraisal (raise_appraisal — PM/
  site staff), verifyAppraisal (verify_appraisal — MPO), approveAppraisal
  (approve_appraisal — Manager); reject paths require remarks; notifications
  APPRAISAL_FOR_VERIFICATION / APPRAISAL_FOR_APPROVAL.
- Three surfaces (§8.14): PM raises + tracks status on the crew-profile Appraisals
  tab; MPO verifies in the Verification queue (Appraisals section); Manager approves
  in the central /approvals queue (Appraisal kind).

Tests & docs
- Unit: appraisal-state-machine.test.ts (4). Integration: appraisal.test.ts (4) —
  raise→verify→approve happy path, MPO reject, permission gating (MPO can't raise,
  site staff can't verify, MPO can't approve). type-check clean; full unit (245) +
  integration (205) green (verified with RESEND_API_KEY unset).
- CLAUDE.md updated — completes Phase 5 (I + H).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 22:09:32 +05:30
8982118eee feat(crewing): Phase 5a — verification queue (flagged)
All checks were successful
PR checks / checks (pull_request) Successful in 41s
PR checks / integration (pull_request) Successful in 29s
First slice of Phase 5 (verification + appraisal). The office queue for verifying
site-entered records, per Crewing-Implementation-Spec §8.11/R11. Stacks on 4c.
Behind NEXT_PUBLIC_CREWING_ENABLED.

What's in
- Schema: CrewActionType += RECORD_VERIFIED/RECORD_REJECTED (migration
  crewing_verification_actions). No model changes — SeafarerDocument/BankDetail/
  EpfDetail already carry verificationStatus + verifiedById (3b/4a).
- Actions (crewing/verification/actions.ts): verifyDocument (verify_site_records —
  MPO/Manager) and verifyBankEpf (verify_bank_epf — Accounts) set
  verificationStatus VERIFIED/REJECTED + verifiedById; rejection requires remarks;
  each writes a CrewAction. Already-decided records are guarded.
- Screen: /crewing/verification — role-aware (MPO: pending documents with expiry
  flags; Accounts: pending bank/EPF), Verify / Reject-with-remarks. Leave is not
  here (Manager approval, R11). Verification added to nav (MPO + Accounts + SU, §7).

Tests & docs
- Integration: verification.test.ts (6) — doc verify/reject + already-decided
  guard, bank/EPF verify, permission gating (Accounts can't verify docs, MPO can't
  verify bank/EPF). type-check clean; full unit (241) + integration (201) green
  (verified with RESEND_API_KEY unset, mimicking CI).
- CLAUDE.md updated.

Deferred (per decision): PPE / next-of-kin verification gates.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 21:59:31 +05:30
4e71863c57 feat(crewing): Phase 4c — sign-off & experience (flagged)
Some checks failed
PR checks / checks (pull_request) Successful in 40s
PR checks / integration (pull_request) Failing after 29s
Final slice of Phase 4 (the Epic K piece deferred from Phase 2). Ends a tour of
duty and returns the crew member to the candidate pool as an ex-hand. Per
Crewing-Implementation-Spec §5.3. Behind NEXT_PUBLIC_CREWING_ENABLED.

What's in
- Schema: CrewActionType += CREW_SIGNED_OFF (migration crewing_signoff).
- signOffCrew(assignmentId, date, remarks) (crewing/crew/actions.ts, sign_off_crew):
  one transaction — assignment → SIGNED_OFF (+ signOffDate); append an internal
  ExperienceRecord (rank, on/off dates, computed durationMonths); flip the SAME
  CrewMember EMPLOYEE → EX_HAND (type/source EX_HAND), so they reappear in
  Candidates as a returning hand; CrewAction CREW_SIGNED_OFF; then auto-raise a
  SIGN_OFF backfill requisition via autoRaiseRequisition.
- Screen: a "Sign off" button on the crew-profile header (sign_off_crew holders —
  site staff / MPO / Manager); on success redirects to the Crew directory.

Tests & docs
- Integration: signoff.test.ts (3) — SIGNED_OFF + experience + EX_HAND + SIGN_OFF
  backfill, already-signed-off guard, permission gating. type-check clean; full
  unit (241) + integration (195) green.
- CLAUDE.md updated — completes Phase 4 (E/F/G + K).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 21:34:29 +05:30
bb5f4126b0 feat(crewing): admin crew management — direct placement, CRUD, strength config
All checks were successful
PR checks / checks (pull_request) Successful in 39s
PR checks / integration (pull_request) Successful in 28s
Office/admin crewing-management surface behind a new manage_crew permission
(Manager + SuperUser + Admin). Stacks on 4b. Behind NEXT_PUBLIC_CREWING_ENABLED.

What's in
- Permission: manage_crew added to the §6 matrix (MGR/SU/ADMIN).
- Direct placement (placeCrew): a Manager assigns a crew member to a vessel/site
  WITHOUT a requisition — creates an ACTIVE CrewAssignment, promotes a candidate to
  EMPLOYEE with a CRW- number (generateEmployeeId), blocked if already actively
  assigned.
- Admin crew CRUD: createCrewMember / updateCrewMember / deleteCrewMember (delete
  blocked when assignments/applications exist).
- Crew strength config: upsert/delete VesselRankRequirement (the minStrength that
  drives R6 leave-clash detection).
- Screens under Administration (flag-gated, MGR/SU/ADMIN): /admin/crew (list + add/
  edit/delete + Place modal) and /admin/crew-strength (requirement table + form).

Tests & docs
- Unit: permissions-crewing.test.ts gains a manage_crew check. Integration:
  crewing-admin.test.ts (9) — CRUD, delete guard, direct placement (+promotion,
  +active-assignment guard), strength upsert/delete, manage_crew gating.
  type-check clean; full unit (241) + integration (192) green.
- CLAUDE.md updated with the crewing-admin surface.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 21:23:31 +05:30
040a66488d feat(crewing): clash detection by required strength (Option A)
All checks were successful
PR checks / checks (pull_request) Successful in 38s
PR checks / integration (pull_request) Successful in 28s
Replace the implicit "strength = 1" clash rule with a configurable per-vessel,
per-rank requirement (director decision). Adds VesselRankRequirement
{vesselId, rankId, minStrength} (migration crewing_vessel_rank_requirement) and
reworks lib/leave-clash.ts → leaveCausesClash: a leave approval clashes when the
remaining active same-rank cover over the window would fall below minStrength
(default 1 when unconfigured), auto-raising a LEAVE requisition. The requirement
is managed by the office (manage_crew, admin UI in the follow-up).

- Integration: leave-attendance.test.ts gains a configured-strength case
  (minStrength 2, one remaining → clash). Full unit (240) + integration (183) green.
- CLAUDE.md updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 21:14:21 +05:30
aac31c6755 feat(crewing): Phase 4b — leave & attendance (flagged)
Second slice of Phase 4 (stacked on 4a crew records). Leave (site-applied,
Manager-decided) with clash auto-backfill, and the daily attendance calendar,
per Crewing-Implementation-Spec §5.3/§8.9–8.10. Behind NEXT_PUBLIC_CREWING_ENABLED.

What's in
- Schema (crewing_leave_attendance migration): LeaveRequest (LeaveType,
  LeaveStatus) + Attendance (AttendanceStatus, unique per assignment+date) on
  CrewAssignment; CrewActionType += LEAVE_APPLIED/LEAVE_DECIDED/ATTENDANCE_RECORDED.
- Leave (R1): site staff apply on behalf (apply_leave); Manager decides
  (decide_leave) → assignment ON_LEAVE; MPO has no leave role. Leave approvals also
  surface in the central /approvals queue (§8.13 Leave kind). Notification
  LEAVE_FOR_APPROVAL.
- Clash auto-backfill (R6): lib/leave-clash.ts, required strength = 1 — approving a
  leave that leaves the vessel with zero active same-rank cover auto-raises a LEAVE
  requisition via the Phase-2 autoRaiseRequisition.
- Attendance (R5): daily month calendar; site staff record (record_attendance),
  Manager views (view_attendance) but cannot edit, MPO neither. saveAttendance
  bulk-upserts dirty cells.
- Screens: /crewing/leave (apply-on-behalf + Manager Approve/Decline) and
  /crewing/attendance (tap-to-cycle calendar + Save). Leave + Attendance added to
  the flag-gated nav (Manager + Site staff).

Tests & docs
- Integration: leave-attendance.test.ts (7) — apply/decide, clash auto-raise (and
  no-raise when cover remains), MPO/Manager attendance lockout, permission gating.
  type-check clean; full unit (240) + integration (182) green.
- CLAUDE.md updated with the Phase 4b surface.

Deferred: the 6-month leave-planner timeline (lightweight list for now); hours/
overtime attendance (A7).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 21:07:15 +05:30
37b1debc9d feat(crewing): Phase 4a — crew records & profile + PPE (flagged)
All checks were successful
PR checks / checks (pull_request) Successful in 40s
PR checks / integration (pull_request) Successful in 28s
First slice of Phase 4 (stacked on 3c onboarding). The Crew directory and tabbed
crew profile with documents, bank/EPF (role-masked), next of kin, PPE and
experience, per Crewing-Implementation-Spec §8.7–8.8. Behind
NEXT_PUBLIC_CREWING_ENABLED; production unchanged.

What's in
- Schema (crewing_crew_records migration): SeafarerDocument, NextOfKin
  (isEmergency), ExperienceRecord, PpeIssue (PpeItem enum) — all on CrewMember;
  CrewActionType += DOCUMENT_UPLOADED/RECORD_UPDATED/PPE_ISSUED/PPE_RETURNED/
  EXPERIENCE_ADDED.
- PII masking (lib/crew-pii.ts, §6/§8.8): bank account + Aadhaar full only for
  Accounts/SuperUser, masked otherwise; salary hidden from site staff. Applied
  server-side before crossing to the client.
- Actions (crewing/crew/actions.ts): uploadDocument/deleteDocument, saveBankEpf,
  addNextOfKin/deleteNextOfKin, issuePpe/returnPpe, addExperience — guarded by
  upload_crew_records / issue_ppe, each writes a CrewAction.
- Screens: /crewing/crew (directory, search + vessel filter, ex-hands excluded)
  and /crewing/crew/[id] (tabbed profile: Documents · Bank & EPF · Next of kin ·
  PPE · Experience · Pay status). Crew added to the flag-gated nav (MGR/MPO/Site/
  Accounts).

Tests & docs
- Unit: crew-pii.test.ts (6). Integration: crew-records.test.ts (7) — documents,
  bank/EPF upsert, NoK, PPE issue/return, experience + permission gating.
  type-check clean; full unit (240) + integration (175) green.
- CLAUDE.md updated with the Phase 4a surface.

Deferred: site-staff own-site scoping (needs a User↔Site link); the records verify
queue (§8.11, Phase 5); Pay-status shows the salary structure only until payroll
(Phase 6).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 19:27:21 +05:30
c82efa71af feat(crewing): Phase 3c — onboarding (flagged)
All checks were successful
PR checks / checks (pull_request) Successful in 40s
PR checks / integration (pull_request) Successful in 28s
Final slice of Phase 3 (stacked on 3b pipeline). The onboarding transaction that
turns a SELECTED candidate into active crew, per Crewing-Implementation-Spec
§8.5/§9/§11. Behind NEXT_PUBLIC_CREWING_ENABLED; production unchanged.

What's in
- Schema (crewing_onboarding migration): CrewAssignment + AssignmentStatus
  (ACTIVE/ON_LEAVE/SIGNED_OFF — leave/sign-off are Phase 4); ContractLetter
  (salaryRestricted); SalaryStructure += assignmentId; CrewActionType +=
  CREW_ONBOARDED. Employee numbers CRW-xxxx via lib/employee-number.ts.
- Action (onboardCandidate, onboard_crew): one transaction off a SELECTED
  application — assign employeeId, create CrewAssignment(ACTIVE, signOnDate),
  bind the approved SalaryStructure (assignmentId + effectiveFrom), Application →
  ONBOARDED, Requisition → FILLED, CrewMember → EMPLOYEE (+ currentRank); contract
  letter stored after. Guards flag + permission + SELECTED state.
- Screen: the SELECTED action card's "Onboard to crew" modal (joining date,
  contract upload, starts-automatically chips); the CRW- number shows on the
  ONBOARDED card.

Tests & docs
- Integration: onboarding.test.ts (5) — full transaction, requisition FILLED +
  salary binding, joining-date + SELECTED-only guards, permission gating, sequential
  CRW- ids. type-check clean; full unit (234) + integration (168) green.
- CLAUDE.md updated with the Phase 3c surface.

Deferred: SITE_STAFF login creation for management ranks (grantsLogin) — a
follow-up; attendance/experience/PPE records begin in Phase 4.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 19:12:53 +05:30
3ec3a2b4ef feat(crewing): Phase 3b — recruitment pipeline (flagged)
All checks were successful
PR checks / checks (pull_request) Successful in 40s
PR checks / integration (pull_request) Successful in 30s
Second slice of Phase 3 (stacked on 3a candidates). The gated 7-stage
recruitment pipeline per Crewing-Implementation-Spec §5.1/§8.4–8.5/§8.13.
Behind NEXT_PUBLIC_CREWING_ENABLED; production unchanged.

What's in
- Schema (crewing_pipeline migration): Application (one per requisition+candidate)
  + 7-stage ApplicationStage; ApplicationGate (SALARY/SELECTION/WAIVER pending =
  Manager queue items); ReferenceCheck; effective-dated SalaryStructure (attached
  to the Application now, bound to the assignment in 3c); minimal BankDetail/EpfDetail
  captured at DOC_VERIFICATION (PII encryption deferred to Phase 4). CrewAction +=
  applicationId; pipeline CrewActionTypes.
- State machine: lib/application-pipeline.ts — sourcing advances MPO/Manager;
  approve_salary + select are Manager-only; orthogonal canReject; BOARD_STAGES.
- Actions: addApplication (first candidate → requisition SHORTLISTING), advanceStage,
  recordReferenceCheck, verifyDocuments (bank/EPF), agreeSalary→approveSalary/returnSalary,
  recordInterviewResult, requestInterviewWaiver→approve/decline, selectCandidate
  (→ requisition SELECTED)/returnSelection, rejectApplication. Waiver never automatic (R2).
  Notifications SALARY/SELECTION/WAIVER + CANDIDATE_PROPOSED.
- Screens: pipeline board per requisition (7 columns + Add candidate); application
  workhorse (7-step stepper + adaptive per-stage action card); "Open pipeline" on the
  requisition detail. Central /approvals gains a crewing section (inline Approve/Return)
  for one unified Manager queue (§8.13 R8).

Tests & docs
- Unit: application-pipeline.test.ts (9). Integration: applications.test.ts (10) —
  full happy path, salary/selection/waiver approvals + Manager-only gating, failed
  interview, reject, site-staff lockout. type-check clean; full unit (234) + integration
  (163) green.
- CLAUDE.md "Crewing" updated with the Phase 3b surface.

Deferred: onboarding (Epic D, Phase 3c) — SELECTED → ONBOARDED, CrewAssignment,
employeeId, requisition → FILLED, salary bound to the assignment.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 18:49:12 +05:30
be6db075dc feat(crewing): Phase 3a — candidates / talent pool (flagged)
All checks were successful
PR checks / checks (pull_request) Successful in 36s
PR checks / integration (pull_request) Successful in 27s
First slice of Phase 3 (Epics B/C/D shipped as stacked sub-PRs). Adds the
CrewMember talent-pool spine and the Candidates screens. Behind
NEXT_PUBLIC_CREWING_ENABLED; production unchanged. Stacks on the requisitions
branch (Phase 2).

What's in
- Schema (crewing_candidates migration): CrewMember (spine) + CrewStatus,
  CandidateType, CandidateSource enums; CrewAction gains a nullable crewMemberId;
  CrewActionType += CANDIDATE_ADDED/UPDATED. employeeId is assigned at onboarding
  (3c), so it's nullable here.
- Actions (crewing/candidates/actions.ts): addCandidate / updateCandidate —
  guard flag + manage_candidates, write a CrewAction, optional CV upload via
  buildStorageKey("cv", …) + uploadBuffer (no parsing — A2 deferred). EX_HAND
  source ⇒ type/status EX_HAND; edits never downgrade an EMPLOYEE.
- Screens: /crewing/candidates (master list with search/source/rank-applied/
  min-experience filters as removable chips + match count + Clear all; Add-candidate
  modal) and /crewing/candidates/[id] (profile; pipeline stepper is 3b). Candidates
  added to the flag-gated Crewing nav (Manager + MPO).

Tests & docs
- Integration: candidates.test.ts (7) — add/update, ex-hand derivation, employee
  no-downgrade, permission gating. type-check clean; full unit (225) + integration
  (153) suites green.
- CLAUDE.md "Crewing" section updated with the Phase 3a surface.

Deferred: public careers intake API (A2, §13 open question); CV parsing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 18:23:01 +05:30
0b2ed9ac07 feat(crewing): Phase 2 — requisitions + relief requests (flagged)
All checks were successful
PR checks / checks (pull_request) Successful in 37s
PR checks / integration (pull_request) Successful in 28s
Second slice of the Crewing module per wiki Crewing-Implementation-Spec §12
(build order item 2). Everything stays behind NEXT_PUBLIC_CREWING_ENABLED;
production is unchanged. Schema is added incrementally — this lands the
requisition lifecycle layer.

What's in
- Schema: Requisition (OPEN→SHORTLISTING→PROPOSING→INTERVIEWING→SELECTED→FILLED,
  →CANCELLED), ReliefRequest, CrewAction (the POAction mirror) + their enums.
  Migration crewing_requisitions.
- State machine: lib/requisition-state-machine.ts mirrors po-state-machine
  (selection Manager-only; orthogonal cancel from OPEN/SHORTLISTING by
  cancel_requisition holders, §6). Codes REQ-9000… via lib/requisition-number.ts.
- Actions: raise/cancel/transition + requestReliefCover/convertReliefToRequisition,
  each guarding flag+permission+state, writing a CrewAction and notifying. Shared
  autoRaiseRequisition() (lib/requisition-service.ts) is the backfill entry point
  for sign-off / leave-clash (later phases).
- Notifier: notifyCrew() PO-independent path + CrewNotificationEvent.
- Screens: /crewing/requisitions (list + Raise modal + relief convert) and
  /crewing/requisitions/[id] (detail). Requisitions added to the flag-gated
  Crewing sidebar (Manager + MPO, §7).

Tests & docs
- Unit: requisition-state-machine.test.ts (11).
- Integration: requisitions.test.ts (15) — raise/cancel/transition, relief
  request + convert, auto-raise, permission gating.
- CLAUDE.md "Crewing" section updated with the Phase 2 surface.

Deferred: sign-off/experience (Epic K, §12 item 2) depends on the crew/assignment
models from Phase 3/4; autoRaiseRequisition() is ready for it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 18:22:59 +05:30
d0006a8fc7 feat(crewing): foundations — SITE_STAFF role, ranks reference data + admin (flagged)
All checks were successful
PR checks / checks (pull_request) Successful in 36s
PR checks / integration (pull_request) Successful in 28s
Phase 1 of the Crewing module per wiki Crewing-Implementation-Spec §12, all dark
behind NEXT_PUBLIC_CREWING_ENABLED (off by default — production unchanged).

- schema: add SITE_STAFF to Role; add Rank (self-referential org hierarchy, like
  Account) + RankDocRequirement, RankCategory & SeafarerDocType enums.
- permissions: full §6 crewing grant matrix (PO_ROLE_PERMISSIONS +
  CREWING_ROLE_PERMISSIONS merged); SITE_STAFF row; MPO has no attendance/leave,
  approvals are Manager-only, manage_ranks is Manager+Admin.
- feature flag: CREWING_ENABLED (opt-in "true").
- nav: flag-gated Crewing section scaffold + "Ranks & documents" under Admin.
- reference data: rank-data.ts + rank-doc-data.ts seeded via shared seed-ranks.ts
  in both dev and prod seeds (19 ranks, 118 doc requirements).
- screen: /admin/ranks — rank hierarchy card + per-rank required-documents card.
- role-label/prefix maps updated for the new role.

Tests: unit (permission matrix + flag), integration (ranks admin CRUD, parent
linking, cycle/children guards, doc-requirement upsert/remove, permission gating).
Docs: CLAUDE.md "Crewing (feature-flagged)" section + env var.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 13:26:04 +05:30
da2d856b73 feat(po): submitter view-all of POs + History + export (feature-flagged)
All checks were successful
PR checks / checks (pull_request) Successful in 34s
PR checks / integration (pull_request) Successful in 26s
Gated behind NEXT_PUBLIC_SUBMITTER_VIEW_ALL_ENABLED (opt-in, "true").
When on, submitter roles (TECHNICAL/MANNING) get read-only access to every
PO: the History page + report export, any other user's PO detail page, and
the per-PO Export PDF/XLSX buttons. No approval/payment/edit rights are added.

- lib/feature-flags.ts: SUBMITTER_VIEW_ALL_ENABLED flag
- lib/permissions.ts: isSubmitterRole / submitterCanViewAll / canViewAllPos
- po/[id] page + export route: gate via canViewAllPos
- history page + reports/export route: OR submitterCanViewAll into export_reports
- sidebar: show History to submitters when flag on
- tests: permission helpers, both flag states
- docs: .env.example, CLAUDE.md (wiki updated separately)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 04:57:11 +05:30
65a9335de1 fix(po): keep the export stamp clear of the signature (no overlap)
All checks were successful
PR checks / checks (pull_request) Successful in 35s
PR checks / integration (pull_request) Successful in 26s
Uploaded signatures/stamps aren't always transparent PNGs, so an opaque stamp
overlapping the signature/name would cover them. Extract the signatory-block
geometry into a tested helper (signatoryLayout): the signature is centred over
the name and the stamp sits to its RIGHT with a 10px gap — never overlapping.

- lib/po-export-layout.ts (signatoryLayout) + unit test
- export route uses it instead of inline overlap math

Verified in a real export: signature 175-328px (centred), stamp 338-405px
(10px gap, no overlap), stamp drawn behind the signature.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 15:35:09 +05:30
610c9aa56d fix(po): centre signature over name; stamp to its right and behind it
All checks were successful
PR checks / checks (pull_request) Successful in 34s
PR checks / integration (pull_request) Successful in 25s
In the XLSX signatory block, place the approver signature centred over the
name and tuck the stamp to its right with a slight overlap. The stamp is now
drawn before the signature so it layers behind it (Excel z-order = add order).

Images are positioned by absolute pixels via native EMU offsets — ExcelJS's
fractional-column anchors don't map cleanly to pixels (the stamp was landing
on top of the signature centre instead of to its right). Verified in a real
export: signature centre 252px in the 503px A-D block (centred), stamp to the
right (305-372px), stamp drawn behind the signature.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 13:45:56 +05:30
6677ef4fcf fix(po): size XLSX export images by pixels (aspect preserved)
All checks were successful
PR checks / checks (pull_request) Successful in 34s
PR checks / integration (pull_request) Successful in 26s
The logo, signature, stamp and cancelled watermark were placed with ExcelJS
two-cell (tl/br) anchors, which stretch each image to fill a cell range —
distorting them and making the watermark text small/squished. The PDF looked
fine because CSS sizes by aspect.

- New lib/image-size.ts: getImageSize (PNG/JPEG/WebP header parse) + scaleToBox.
- Export route now places each image with a oneCell `tl` + pixel `ext`,
  aspect preserved and matched to the PDF sizes (logo ≤96×52, signature ≤165×44,
  stamp ≤80×66, watermark ≤880×720).
- Watermark regenerated as a landscape canvas with the text filling it, so it
  spans the page like the PDF instead of sitting small in the centre.
- Unit test for getImageSize + scaleToBox.

Verified structurally: generated XLSX uses oneCellAnchors with fixed pixel
ext sizes (49×52 / 45×44 / 67×66 / 880×629), not stretched cell ranges.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 13:27:15 +05:30
Claude (auto-fix)
cb25d2e5fd feat(vendors): search by vendor ID and show it next to the name
All checks were successful
PR checks / checks (pull_request) Successful in 34s
PR checks / integration (pull_request) Successful in 26s
On /inventory/vendors, include vendorId in the search filter and render
it as a muted mono badge beside the vendor name. The vendorId data was
already passed to the client component, so this is a presentation/filter
change only. Unverified vendors (no vendorId) render unchanged.

Fixes #57

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 12:43:21 +05:30
0b10ba5e54 feat(po): cancel POs (manager/superuser) + optional supersede link (#53)
All checks were successful
PR checks / checks (pull_request) Successful in 32s
Managers and superusers can cancel a PO from any state via a confirmation modal
that requires typing "cancel" and a mandatory reason. A cancelled PO becomes a
terminal CANCELLED state and drops out of every spend tracker/graph (those filter
on POST_APPROVAL_STATUSES / explicit whitelists, none of which include CANCELLED).

A cancelled PO may optionally be linked to the existing PO that supersedes it
(by PO number); the replacement shows the reciprocal "supersedes" link. No
vessel/account/vendor match is enforced and the link can be added any time.

Cancelled POs remain visible (greyed in history) and exportable, with a diagonal
"CANCELLED" watermark on both the PDF and XLSX exports.

- schema: POStatus CANCELLED; cancelledAt/cancellationReason; self-referential
  supersededById relation; ActionType CANCELLED/SUPERSEDED (+ migration)
- state machine canCancel(); cancel_po permission (MANAGER + SUPERUSER)
- cancelPo / supersedePo server actions + PO_CANCELLED notification
- cancel modal + supersede form; cancelled banner with reciprocal links
- exhaustive CANCELLED entries in all status label/variant maps
- diagonal CANCELLED watermark embedded for PDF (CSS) and XLSX (image)
- integration tests (cancel from any state, reason/role guards, supersede)

Inventory reversal on cancel is deferred to #55 (inventory is feature-flagged off).

Closes #53

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 12:20:54 +05:30
e388ec917e Merge branch 'master' into feat/company-form-pages
All checks were successful
PR checks / checks (pull_request) Successful in 31s
2026-06-20 20:37:37 +00:00
Claude (auto-fix)
defd6e7a18 feat(dashboard): compact INR formatting for Total Approved Spend card
All checks were successful
PR checks / checks (pull_request) Successful in 31s
Overhaul the manager dashboard "Total Approved Spend" stat card per the
reporter's request:

- Swap the DollarSign lucide icon for IndianRupee (rupee symbol).
- Render the amount in the Indian short scale (lakh/crore) via a new
  `formatCompactINR` helper, e.g. ₹2 Cr, ₹49 L, ₹75 K, instead of the full
  ₹49,00,000.00.

`formatCompactINR` rounds to at most 2 decimals, trims trailing zeros, keeps
the ₹ prefix and sign. The DollarSign icon is retained for the Accounts
"Payment Queue Value" card; the precise `formatCurrency` is kept for tables.
Adds unit tests covering crore/lakh/thousand/sub-thousand, boundaries, zero,
string input, negatives and non-finite input.

Fixes #50

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 02:02:41 +05:30
bad67f66c4 feat(companies): move add/edit from dialog to dedicated pages
Some checks failed
PR checks / checks (pull_request) Failing after 3s
The company form outgrew the modal once the branding (logo/stamp) section
was added. Add/edit now live on their own routes:
- /admin/companies/new
- /admin/companies/[id]/edit

- createCompany returns the new id and the create flow lands on the edit
  page so logo/stamp can be uploaded immediately
- list "+ Add Company" is a link; row "Edit" navigates to the edit page
- branding is its own card on the edit page (independent uploads)
- list page no longer mints a presigned URL per company (moved to edit)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 01:45:59 +05:30
1071cb226f feat(po): per-company logo, stamp & brand bar on exported POs
All checks were successful
PR checks / checks (pull_request) Successful in 31s
Companies can upload a logo and a stamp/seal (Admin → Companies → Edit →
Branding); both render on exported PDF and XLSX purchase orders. A fixed
brand-colour bar (#92D050, matching the sample PO) runs along the bottom of
every export.

- Company.logoKey / stampKey + migration
- buildCompanyAssetKey() deterministic storage keys (overwrite-in-place)
- uploadCompanyAsset / removeCompanyAsset server actions (≤4MB PNG/JPG/WebP,
  manage_vessels_accounts gated)
- CompanyBrandingUploader in the company edit dialog with live previews
- Export route embeds logo (top-left), stamp (signatory block) and brand bar
  in both ExcelJS and print-HTML paths
- Unit test (storage keys) + integration test (branding actions)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 01:17:23 +05:30
791e99f3fd Merge commit '859be8c8d0' into fix-pr34
# Conflicts:
#	App/app/(portal)/history/history-filters.tsx
2026-06-19 12:35:35 +05:30
b3e6f6181a Merge branch 'master' into claude/issue-31 2026-06-19 06:59:39 +00:00
Claude (auto-fix)
fdc3ebdac9 fix(dashboard): count all POs approved this month, not just current MGR_APPROVED
The manager dashboard "Approved This Month" card only counted POs whose
current status is MGR_APPROVED, so approvals that had already moved on to
payment, delivery, or closure dropped out of the count. Managers could not
see what happened to the POs they approved this month.

- Count every PO whose `approvedAt` falls in the current month across all
  post-approval statuses (MGR_APPROVED → ... → CLOSED). `approvedAt` is set
  once at approval and persists, so it is the correct anchor.
- Introduce a shared `POST_APPROVAL_STATUSES` constant (includes the
  previously-omitted PARTIALLY_CLOSED). This also fixes Total Approved Spend
  and the vessel/monthly breakdowns, which were silently dropping
  partially-received POs.
- Make the card a link into /history with an approval-date filter applied
  (?approvedFrom=<startOfMonth>) so a click shows the full set with each PO's
  current status, as requested.
- Add `approvedFrom`/`approvedTo` filtering to the history page, its filter
  UI, and the reports export route so the deep-link and exports stay in sync.

Scope note: the count remains org-wide, consistent with every other card on
the manager dashboard.

Adds an integration test covering the moved-on case and the date window.

Fixes #32
2026-06-19 12:07:53 +05:30
b592358db0 feat(app): env-gated banner (EnvBanner) for non-prod environments
Renders a thin fixed banner only when NEXT_PUBLIC_ENV_LABEL is set; production
leaves it unset so nothing shows. Used to mark the staging instance.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 11:56:34 +05:30
Claude (auto-fix)
e94c7f99a3 feat(history): allow filtering PO history by multiple statuses
The PO history page previously allowed only a single status filter. This
enhances it to accept multiple statuses that are OR-ed together (e.g.
Closed + Approved shows all POs in either state), as requested.

- Status filter is now a multi-select checkbox dropdown that serialises
  selections as repeated `status` query params.
- History page and the reports export endpoint read all `status` values
  and query with `status: { in: [...] }` (OR semantics).
- Single-status and no-status cases remain unchanged.

Verified OR-query semantics against the test DB and confirmed both routes
compile and respond. type-check passes for the changed files.

Fixes #31

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 11:53:42 +05:30
64634ccb5e Merge branch 'master' into claude/issue-9 2026-06-18 22:34:18 +00:00
Claude (auto-fix)
9adc93e54a fix(receipt): upsert Receipt record on repeat confirmations with notes
Partial-receipt flows call confirmReceipt multiple times. The nested
`create` on the Receipt relation threw a unique-constraint error on the
second call when both confirmations supplied notes, preventing any
delivery from completing and blocking attachment uploads.

Changed to `upsert` so subsequent confirmations update the existing
Receipt row's notes instead of failing.

Adds integration tests covering full receipt, partial receipt, the
upsert scenario (two confirmations each with notes), and permission guards.

Fixes #9
2026-06-19 04:01:26 +05:30
Claude (auto-fix)
d7be141589 fix(export): include optional line item description in PDF and XLSX exports
The POLineItem model has both a required `name` and an optional `description`
field. The export was only rendering `name` (with description as a fallback),
dropping the optional description entirely when a name was present.

- PDF: renders description in smaller italic text below the item name
- XLSX: appends description on a new line within the cell (wrapText enabled)
- Row height in XLSX now accounts for the extra line when description exists

Fixes #8

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 03:43:49 +05:30
Claude (auto-fix)
a37ca068c2 fix(my-orders): correct closed PO filters for manager and submitter
Managers and superusers were silently filtered to only their own
submitted POs because submitterId: userId was applied unconditionally.
Submitters were also shown MGR_APPROVED, SENT_FOR_PAYMENT,
PAID_DELIVERED and REJECTED orders alongside CLOSED ones.

Fix: managers/superusers see all CLOSED POs (no submitterId filter);
submitters see only their own CLOSED POs.

Fixes #6
2026-06-19 03:34:21 +05:30
Claude (auto-fix)
66f2e133b1 fix(inventory): add items to inventory on PO approval, not on closure
Moves the ItemInventory upsert from confirmReceipt (CLOSED) to approvePo
(MGR_APPROVED) so site inventory is visible as soon as a purchase order
is manager-approved, without waiting for full closure.

- approvePo: fetch lineItems, upsert ItemInventory per site PO line item
  that has a productId; revalidate the site admin path.
- confirmReceipt: remove the now-redundant inventory update block.
- Rename approvepo → approvePo for consistency (fixes import mismatch
  in the existing integration test file).
- Add three integration test cases covering: site PO inventory increment,
  line items without productId are skipped, vessel-only POs are untouched.

Fixes #7

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 03:15:56 +05:30
Claude (auto-fix)
25d1164d34 feat(po): allow submitter to set an optional PO date
Add an optional PO Date field to the create and edit PO forms.
Submitters can pick any date (back-dated or forward-dated). If left
blank, the exported PO document falls back to the approved date, then
to the creation date.

Changes:
- Prisma schema: add `poDate DateTime?` to PurchaseOrder
- Migration 20260616000000_add_po_date: ALTER TABLE to add the column
- createPoSchema: add optional `poDate` string field
- new-po-form, edit-po-form: add PO Date picker in Order Information
- create/edit actions: persist poDate to DB
- edit action resubmit snapshot: track poDate changes for manager diff
- po-detail: show PO Date in Order Details; include in resubmit diff banner
- export route: use poDate ?? approvedAt ?? createdAt as the date on
  the exported PDF/XLSX document
- validations.test: fix pre-existing costCentreRef→vesselId mismatch
  and add poDate test cases

Fixes #4

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 13:06:12 +05:30
add0f3c19c feat(payments): compulsory payment date when Accounts records payment
- New PurchaseOrder.paymentDate field (migration 20260531000002)
- Backfill: existing POs use paidAt, else the earliest payment action date
- Accounts must enter a payment date with the payment reference
- Date input pre-selected to today, max=today (no future dates)
- Validated server-side (required + not in future) in processPaymentSchema
- paymentDate stored on both full and partial payments; paidAt set from it
- Shown on PO detail (Payment Date) and payment history (prefers paymentDate)
- Integration tests updated; added future-date rejection test

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 08:59:25 +05:30