Commit graph

260 commits

Author SHA1 Message Date
9f681ace89 Merge remote-tracking branch 'origin/master' into pr141-fix
All checks were successful
PR checks / checks (pull_request) Successful in 54s
PR checks / integration (pull_request) Successful in 32s
# Conflicts:
#	App/components/po/po-detail.tsx
2026-06-28 02:34:26 +05:30
e481eb0a15 feat(po): allow attachments in any state except rejected/cancelled
All checks were successful
PR checks / checks (pull_request) Successful in 50s
PR checks / integration (pull_request) Successful in 31s
Broadens the feature-flagged attachment affordance (same flag,
NEXT_PUBLIC_CLOSED_PO_ATTACHMENTS_ENABLED) from CLOSED-only to **any PO state
except REJECTED / CANCELLED**, for the same roles: the PO's own submitter plus
Accounts / Manager / SuperUser.

- lib/permissions.ts: canAddClosedPoAttachment → canAddPoAttachment(role,
  status, { isSubmitter }); allows the submitter + ACCOUNTS/MANAGER/SUPERUSER
  in any non-voided state. REJECTED/CANCELLED are always refused.
- uploadPoDocuments: voided POs are refused regardless of the flag; with the
  flag on, uploads are restricted to those roles in any live state (the normal
  create/receipt actors qualify, so those flows keep working); with the flag
  off, the legacy behaviour stands (closed POs immutable).
- po-detail.tsx: the Attachments card now shows the uploader for any non-voided
  state when permitted (not just CLOSED).
- Renamed ClosedPoAttachmentUploader → PoAttachmentUploader and the test file
  to po-attachment-permissions.test.ts (flag-on matrix now covers live states +
  rejected/cancelled refusal). Docs updated (feature-flags, .env.example,
  CLAUDE.md).

Full unit + integration suites green; tsc clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 01:42:13 +05:30
65be4ef330 Merge remote-tracking branch 'origin/master' into pr141-fix
All checks were successful
PR checks / checks (pull_request) Successful in 52s
PR checks / integration (pull_request) Successful in 32s
# Conflicts:
#	App/components/po/po-detail.tsx
2026-06-28 01:24:00 +05:30
ebb6230755 Merge pull request 'fix(po): upload attachments server-side so they persist & show' (#144) from claude/flamboyant-gagarin-370922 into master
All checks were successful
Refresh staging / refresh (push) Successful in 8s
Reviewed-on: #144
2026-06-27 19:47:26 +00:00
158b446117 feat(po): feature-flagged attachments on closed POs (bug remediation)
All checks were successful
PR checks / checks (pull_request) Successful in 51s
PR checks / integration (pull_request) Successful in 31s
Adds NEXT_PUBLIC_CLOSED_PO_ATTACHMENTS_ENABLED. When on, a CLOSED PO's own
submitter -- plus Accounts / Manager / SuperUser -- can attach documents to
it, so POs whose uploads were lost to the document-upload bug can be fixed
without reopening them. Off by default, so production stays unchanged until
enabled.

- lib/permissions.ts: canAddClosedPoAttachment(role, { isSubmitter }) gated
  by the flag; allowed roles are ACCOUNTS/MANAGER/SUPERUSER (plus the PO's
  own submitter regardless of role).
- uploadPoDocuments: a CLOSED PO is otherwise immutable, so it now enforces
  the permission server-side; the normal create/receipt flows upload while
  the PO is pre-CLOSED and are unaffected.
- po-detail.tsx: when allowed, the Attachments card renders an uploader
  (ClosedPoAttachmentUploader) and shows even when the PO has no docs yet.
- Enabled on staging (staging-up.sh) so the remediation can be exercised;
  documented in .env.example and CLAUDE.md.

Tests: closed-po-attachments.test.ts covers the flag-on role matrix (own
submitter / Accounts / Manager / SuperUser allowed; other submitter-role and
auditor refused; non-closed PO unaffected); po-document-upload.test.ts adds
the flag-off case (closed PO stays immutable). Full unit + integration suites
green; tsc clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 01:11:29 +05:30
Claude (auto-fix)
4dc10b834c feat(po): add Duplicate PO button to prefill a new PO
All checks were successful
PR checks / checks (pull_request) Successful in 52s
PR checks / integration (pull_request) Successful in 30s
Anyone with create_po browsing a PO now sees a Duplicate action that
opens the New Purchase Order form prefilled from the source PO. Like the
existing cart→new-PO prefill, nothing is written until the user saves or
submits — a duplicate is just a clean draft of the editable order fields.

- po-detail.tsx: Duplicate link in the header, gated by
  hasPermission(currentRole, "create_po") + !readOnly, linking to
  /po/new?duplicate=<id>.
- po/new/page.tsx: when ?duplicate=<id> is present, fetch the source PO
  and map it onto the form's initial props via the new pure helper.
- new-po-form.tsx: accept initial-value props for title, accounting code
  (+ per-item toggle), project code, place of delivery, date required,
  quotation/requisition refs, terms — following the existing prop pattern.
- lib/duplicate-po.ts: pure, unit-tested mapping (Decimals→numbers, dates
  →yyyy-MM-dd, saved-terms snapshot with legacy tc* fallback). Attachments,
  status/dates, payment data and audit history are intentionally not copied.

Fixes #142

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 00:40:36 +05:30
2afdeec6ad fix(po): upload attachments server-side so they persist & show
Some checks failed
PR checks / checks (pull_request) Failing after 2m14s
PR checks / integration (pull_request) Failing after 2m13s
PO/receipt attachments were uploaded via a browser presigned PUT straight
to R2 (POST /api/files/sign -> PUT uploadUrl -> linkDocument). That direct
browser->R2 PUT only succeeds when the bucket carries a CORS policy
allowing PUT from the portal origin; in production that policy was missing,
so the browser silently blocked the PUT, linkDocument never ran, and no
PODocument row was created -- "documents uploaded but not visible anywhere"
(0 PODocument rows in prod/staging).

Route uploads through a server action (uploadPoDocuments) that writes the
file with uploadBuffer and creates the PODocument row atomically -- the
same pattern the crewing module already uses for CV/crew-document uploads,
and within the 10mb serverActions.bodySizeLimit. This removes the R2-CORS
dependency and guarantees a created row always has its stored file.

Removes the now-dead presigned path (lib/upload-files.ts,
app/actions/link-document.ts, api/files/sign/route.ts).

Adds an integration test that drives the action against a real DB and
asserts the PODocument row is created and surfaced by the exact include
the PO detail page renders from (i.e. the attachment is visible).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 00:28:16 +05:30
Claude (auto-fix)
3335977773 feat(activity): show partial payment amount in PO timeline
All checks were successful
PR checks / checks (pull_request) Successful in 53s
PR checks / integration (pull_request) Successful in 31s
The PO Activity timeline rendered every partial payment as the generic
"Partial payment confirmed". markPaid() already persists the instalment
amount on the PARTIAL_PAYMENT_CONFIRMED action's metadata
(metadata.paymentAmount), so surface it: the row now reads
"Partial payment of <amount> confirmed" using the PO's own currency.

Falls back to the plain label when paymentAmount is missing or
non-numeric (older audit rows) so historical POs never render NaN.

Extracted ACTION_LABELS + the new actionLabel() helper into
lib/po-activity.ts so the label logic is unit-testable without pulling
the server-only PoDetail component (and its storage/auth imports) into
jsdom.

Fixes #140
2026-06-28 00:24:28 +05:30
efa9d90ddb test(history): cover removal of Approved From/To filters (#136)
All checks were successful
PR checks / checks (pull_request) Successful in 51s
PR checks / integration (pull_request) Successful in 32s
Adds a unit test for HistoryFilters asserting the Approved From / Approved
To date filters no longer render (the issue #136 change) while the remaining
filters (created-date range, cost centre, accounting code, status) stay.
Satisfies the test-presence policy that PR #137 was missing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 03:46:03 +05:30
Claude (auto-fix)
5218eb3717 feat(history): remove Approved From/To search fields
Some checks failed
PR checks / checks (pull_request) Failing after 3s
PR checks / integration (pull_request) Successful in 33s
Drop the two approval-date pickers from the PO History filter bar per
the manager's request — they were deemed unnecessary. Removes the
inputs, their useState hooks, and their entries in buildParams/clear/
hasFilters.

The approvedFrom/approvedTo URL params are left intact server-side
(page.tsx, lib/history-filter.ts, export route) so existing deep-links
from the dashboard "Approved This Month" card and the report drill-downs
keep pre-filtering History by approval date.

Fixes #136

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 03:23:08 +05:30
87fbeecf52 Merge branch 'master' into claude/issue-124
All checks were successful
PR checks / checks (pull_request) Successful in 49s
PR checks / integration (pull_request) Successful in 30s
2026-06-25 21:23:01 +00:00
02c0806d35 refactor(po): admin-managed Project Codes instead of a static list
All checks were successful
PR checks / checks (pull_request) Successful in 51s
PR checks / integration (pull_request) Successful in 32s
Replaces the hardcoded PROJECT_CODES array with an admin-managed
`ProjectCode` model, mirroring the Delivery Locations pattern (PR #100):

- ProjectCode model (unique `code` + isActive) + migration seeding the
  five previously-hardcoded codes; PO.projectCode stays a free-text
  snapshot (no FK) so history/exports/imports are unchanged.
- manage_project_codes permission (Manager + SuperUser + Admin).
- /admin/project-codes CRUD screen (table + Add/Edit + activate/delete)
  and an Administration sidebar link.
- ProjectCodeField now takes `options` from the active codes; the three
  PO forms + pages fetch them from the DB. Static list removed.
- Unit test reworked to the options API; CRUD integration test added;
  documented in App/CLAUDE.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 02:52:03 +05:30
6cfcd20c45 Merge remote-tracking branch 'origin/master' into work-issue-121
All checks were successful
PR checks / checks (pull_request) Successful in 50s
PR checks / integration (pull_request) Successful in 30s
# Conflicts:
#	CHANGELOG.md
2026-06-26 01:07:16 +05:30
Claude (review-bot)
d0e43135f8 feat(reports): drill from cost centre / accounting code into PO History (#126)
All checks were successful
PR checks / checks (pull_request) Successful in 49s
PR checks / integration (pull_request) Successful in 31s
Report detail pages now link to the underlying POs, addressing the PR #126
review comment: drilling into a cost centre or accounting code opens PO
History pre-filtered to that dimension and the period in view.

- Cost Centre / Accounting Code detail pages gain a "View POs" link.
- periodRange() maps the on-screen period onto History's approved-date
  window (weekly→month, monthly→FY, yearly→full span); spend is dated by
  approvedAt.
- PO History gains an accountId filter (any tree node, expanded to leaves
  via accountLeafIds()) matching PO-level OR line-item accounts — the same
  basis the reports use.
- History page + CSV/PDF export share one buildPoHistoryWhere() builder so
  they never diverge.
- Tests: unit (periodRange, accountLeafIds) + integration (History account
  filter across PO-level/line-item, with the approved window).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 01:03:17 +05:30
262ae5830b Merge pull request 'feat(pdf): cache the PO PDF per vendor email (reuse copy, refresh 7-day link)' (#128) from fix/po-pdf-cache into master
All checks were successful
Refresh staging / refresh (push) Successful in 7s
Reviewed-on: #128
2026-06-24 09:47:58 +00:00
a9fd927c1f feat(pdf): cache the PO PDF per vendor email, refresh only the link timer
All checks were successful
PR checks / checks (pull_request) Successful in 47s
PR checks / integration (pull_request) Successful in 31s
Previously every "Email to vendor" click re-rendered the PO via PdfService and
re-uploaded to R2 under a timestamped key — wasteful, and it orphaned a new
object each time.

Now the PDF is stored at a deterministic per-PO key (buildPoPdfKey →
po-pdf/<poId>/<slug>.pdf). On each send, statObject() checks for an existing
copy: if it exists and is at least as new as the PO's updatedAt, it's reused
(no re-render, no re-upload) and only a fresh presigned URL is minted —
refreshing the 7-day download timer. It re-renders only when there's no copy
yet or the PO changed since the cached one (so an edited PO never emails a
stale PDF).

- lib/storage.ts: buildPoPdfKey (deterministic) + statObject (HEAD/stat, no
  body transfer; null when absent).
- email-actions.ts: reuse-or-render decision keyed on updatedAt; always
  re-presign.
- Tests: +2 (reuse-on-second-send-only-refreshes-link, re-render-when-changed).
  email-vendor suite 8 green; tsc clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 15:01:25 +05:30
d1af1e6b12 fix(pdf): let PdfService reach the PO export route past auth middleware
All checks were successful
PR checks / checks (pull_request) Successful in 49s
PR checks / integration (pull_request) Successful in 31s
"Email PO to vendor" (issue #14) relies on PdfService fetching
/api/po/<id>/export?...&svc=<token> WITHOUT a user session, authenticating
with a `svc` token that matches PDF_SERVICE_TOKEN. The route handler validates
that token, but the auth middleware runs first and its matcher doesn't exempt
the export route — so every unauthenticated fetch was redirected to /login
(307) and the svc bypass never executed. Net effect: the feature could never
render a real PDF on any deployed env, even with the service configured.

Fix: middleware now lets exactly `/api/po/<id>/export` through when its `svc`
query param matches `process.env.PDF_SERVICE_TOKEN` (the route handler still
re-validates it — defense in depth). Everything else stays auth-gated. The
match lives in a dependency-free, edge-safe, unit-tested helper
(lib/pdf-export-auth.ts); middleware already reads server env at runtime via
auth()/NEXTAUTH_SECRET, so reading PDF_SERVICE_TOKEN there is consistent.

Verified on a running build: correct svc + real PO -> 200, correct svc + bogus
PO -> 404 (handler ran), wrong/no svc -> 307 (still gated). 324 unit tests
green; tsc clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 14:55:40 +05:30
Claude (auto-fix)
4ed27d668b feat(po): Project Code dropdown on PO forms
All checks were successful
PR checks / checks (pull_request) Successful in 51s
PR checks / integration (pull_request) Successful in 29s
Replace the free-text Project Code input with a native <select> carrying a
fixed list of project codes (Petronet LNG Cochin, COMACOE Trombay, Haldia
Reach, Haldia MMT, COMACOE Mandvi) plus an empty "— none —" option, across
all three PO forms (new / edit / manager-edit).

- Add a shared PROJECT_CODES constant in lib/validations/po.ts as the single
  source of truth.
- Add a reusable <ProjectCodeField> (mirrors <DeliveryLocationField>): plain
  HTML select keeping name="projectCode" so the server actions are unchanged.
- The field stays optional; projectCode remains a nullable free-text snapshot
  (no schema/migration, no validation tightening) so legacy/imported values
  are not rejected. On edit, a current value not in the list is preserved as a
  leading "(current)" option so it is never silently dropped.
- Add unit tests for the new field.

Fixes #124

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 13:35:59 +05:30
Claude (auto-fix)
3e8f5fb0c7 feat(history): add Accounting Code search filter to PO History
All checks were successful
PR checks / checks (pull_request) Successful in 49s
PR checks / integration (pull_request) Successful in 30s
PO History had no way to narrow by accounting code. Add an "Accounting
Code" filter (the shared type-to-search combobox) alongside Cost Centre,
backed by the PO-level account already included in the query.

- history/page.tsx: read `accountId` searchParam, fetch selectable leaf
  accounting codes (active, no children) via buildAccountGroups, apply
  `where.accountId`, thread the param into pagination + export links, and
  surface an Accounting Code column for context.
- history-filters.tsx: new SearchableSelect control wired into
  buildParams/apply/clear/hasFilters like the Cost Centre select.
- api/reports/export: apply the same `accountId` filter so CSV/PDF export
  respects the on-screen filter.
- tests/staging: verify picking a code drives an `accountId` query param.

Fixes #121

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 13:18:00 +05:30
34143b5e75 fix(reports): chart series all rendered one colour (RSC boundary bug)
Some checks failed
PR checks / checks (pull_request) Failing after 15s
PR checks / integration (pull_request) Successful in 41s
The comparison charts (and detail-page breakdown swatches) rendered every
series in recharts' default colour instead of the per-item palette.

Root cause: `SERIES_COLORS` was defined in `components/reports/charts.tsx`,
which is a "use client" module. The report **pages are server components** and
imported the palette from it. A plain value imported from a client module into
a server component is a client-reference proxy, not the real array — so
`SERIES_COLORS[i % SERIES_COLORS.length]` was `SERIES_COLORS[NaN]` → undefined,
every line got `stroke={undefined}`, and recharts fell back to #3182bd. (The
literal `strokeWidth={2}` still applied, which is why only the colour was wrong.
It passed jsdom tests because those import the array directly, not across the
RSC boundary.)

Fix: move the palette to a dependency-free shared module `lib/report-colors.ts`
(no "use client", no server-only imports) that resolves to the real array in
both server and client graphs. `charts.tsx` and all four report pages import it
from there. It can't live in `lib/reports.ts` (that imports Prisma `db`, which
must not enter the client bundle).

Verified in a real browser: line strokes now cycle the 10-colour palette
(#2563eb, #16a34a, …) instead of a uniform #3182bd.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 12:31:40 +05:30
cf69292be3 Merge pull request 'test(staging): feature-level verification of closed issues + seeded test users' (#119) from feat/staging-issue-verification into master
All checks were successful
Refresh staging / refresh (push) Successful in 7s
Reviewed-on: #119
2026-06-24 06:46:17 +00:00
a72e980558 test(staging): feature-level verification of closed issues + seeded test users
All checks were successful
PR checks / checks (pull_request) Successful in 46s
PR checks / integration (pull_request) Successful in 32s
Adds a Playwright suite (App/tests/staging/) that logs into the running staging
instance (ppms-staging, :3200) and verifies each closed portal issue is actually
fixed — feature level, driving the real UI, one spec per issue.

To make credential login possible against the prod-mirror pelagia_test (which only
holds real, mostly SSO-only users), prisma/seed-test-users.ts idempotently seeds one
known-password @pelagia.local user per role, and automation/refresh-test-db.sh runs
it after every daily refresh so the logins persist on staging.

Result against staging: 41 passed, 1 skipped (#10 — no attachment data on staging).
Two closed issues were found NOT fixed and are recorded as documented test.fail():
  - #13 Accounts "payments completed this month" card is absent.
  - #24/#40 logout tooltip still reads "Sign out" (pipeline test issues).

Docs/TESTING.md documents the suite, the seeded users, how to run it against
staging, and the full issue -> script mapping.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 11:49:48 +05:30
0e9d06fe71 fix(reports): colour the comparison chart per item in yearly mode too
Some checks failed
PR checks / checks (pull_request) Failing after 4s
PR checks / integration (pull_request) Successful in 30s
The monthly/weekly comparison already drew one colour per item (series =
items). Yearly mode instead coloured by financial year (series = FYs, items on
the x-axis), so multiple cost centres / accounting codes in the same yearly
graph shared colours. Unify all three granularities to series = items: the
x-axis is months / weeks / FYs and each item keeps its own distinct colour
(yearly becomes grouped bars per item rather than per year).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 11:47:12 +05:30
91349f7564 Merge pull request 'feat(reports): Purchasing spend analytics (Cost Centres + Accounting Codes)' (#117) from feat/reports into master
All checks were successful
Refresh staging / refresh (push) Successful in 8s
Reviewed-on: #117
2026-06-24 06:03:28 +00:00
47ac2c7813 feat(reports): weekly granularity, custom compare, line-item allocation
All checks were successful
PR checks / checks (pull_request) Successful in 48s
PR checks / integration (pull_request) Successful in 31s
Picks up the three pieces deferred from the initial reports PR:

#3 Line-item account allocation — allocatePoSpend() splits each PO across the
accounting codes its line items carry (line accountId, falling back to the
PO-level account), proportionally so per-PO rows sum back to totalAmount. The
accounting-code report now attributes multi-account POs correctly. SpendRow
gains poId; poCount is now distinct POs, not row count.

#2 Custom "Add to graph" — tick rows on either index (SelectCheckbox links
write ?sel=id1,id2), then "Compare selected" (?cmp=1) shows a custom comparison
of just those entities. Fully server-rendered + shareable; export honours sel.

#1 Weekly granularity — a third Granularity that focuses one FY month and
buckets spend by week-of-month (W1–W5) from approvedAt, with a Month picker in
the toolbar. Real buckets (not the mockup's synthetic split).

All three are URL-driven like the rest, so no client fetching. Charts/KPIs/
detail trends all branch on the new mode.

Tests: +8 unit cases (allocation proportional/fallback/empty, weekly buckets,
sel parse/toggle, month + granularity parsing); fixture updated for poId/week.
Full unit suite 311 green; tsc clean. Smoke-tested weekly + custom-compare +
exports end-to-end (all 200). Docs + wiki updated to mark them implemented.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 11:25:05 +05:30
8c6bbd8304 feat(reports): Purchasing spend analytics (Cost Centres + Accounting Codes)
All checks were successful
PR checks / checks (pull_request) Successful in 46s
PR checks / integration (pull_request) Successful in 31s
Implements the wiki "Reports Mockup" as a Reports → Purchasing sidebar section,
wired to real approved-PO spend.

Two report families, each index → drill/detail:
- Cost Centres (/reports/cost-centres) — spend compared across vessels; row
  opens a cost-centre report with a Top-accounting-codes breakdown re-pivotable
  by tier (Heading/Sub/Leaf) + Top-N.
- Accounting Codes (/reports/accounting-codes) — drills the Account tree
  (headings → sub → leaves) via ?parent=; a leaf opens its report broken down by
  cost centre (or, for a non-leaf, by sub-account).

Shared: a pinned filter toolbar (Granularity Monthly/Yearly, Financial Year,
Show Top5/Top10/Bottom5/All) whose values live in the URL query so the server
component re-renders — no client fetching. KPI tiles, recharts comparison/trend/
breakdown charts, per-row trend sparklines, and CSV export (/api/reports/spend).

- lib/reports.ts: the pure, unit-tested aggregation core. Spend = a PO once it
  reaches POST_APPROVAL_STATUSES, dated by approvedAt, valued at totalAmount
  (the dashboard's basis); Indian Apr–Mar FY; each PO's leaf accountId rolled up
  to parents. One query in getReportDataset(), everything else pure.
- Sidebar: new collapsible "Reports" section with a "Purchasing" subheading
  (subgroup support added to the Section model). Gated by view_analytics
  (Manager/SuperUser/Auditor/Admin); export by the same.

Deferred (documented): synthetic Weekly granularity, the "Add to graph" custom
multi-select, and line-item-level account allocation (v1 uses the PO-level
account). Sites are not cost centres — only vessels.

Tests: 11 unit cases for the aggregation core + 3 sidebar cases for the Reports
section. Full unit suite 303 green; tsc clean. Smoke-tested all routes end to
end against seed data (index/drill/detail/export 200; non-analytics role 307/403).

Wiki: "Reports Mockup" marked implemented; "Pages and Navigation" lists the new
routes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 07:52:23 +05:30
a08ed68569 Merge pull request 'fix: Make GST captcha a popup' (#115) from claude/issue-114 into master
All checks were successful
Refresh staging / refresh (push) Successful in 7s
Reviewed-on: #115
2026-06-24 01:19:51 +00:00
7d4ad6a9b8 feat(po): prompt to save as draft when leaving with unsaved changes
All checks were successful
PR checks / checks (pull_request) Successful in 44s
PR checks / integration (pull_request) Successful in 32s
Closes #18. Navigating away from a PO create/edit screen with unsaved
changes could silently lose in-progress work. The forms now track a dirty
flag and guard both navigation paths:

- Hard navigations (refresh / tab close / external link) → the browser's
  native "Leave site?" prompt via beforeunload.
- In-app navigations (sidebar / header / any internal link) → a capture-phase
  click interceptor opens a modal offering Save as draft / Discard changes /
  Stay on page. Save as draft runs the form's existing draft save (which
  redirects to the PO); Discard continues to the intended destination.

The guard (components/po/unsaved-changes-guard.tsx) is reusable and wired into
both new-po-form and edit-po-form. dirty is cleared before a successful submit
so saving never trips the prompt. SPA back-button (popstate) is left to
beforeunload only; the manager inline-edit panel is out of scope (saves in
place, no draft concept).

Tests: 7 new unit cases for the guard (intercept-when-dirty, no-op-when-clean,
external links pass through, Stay/Discard/Save actions, beforeunload arming).
Unit suite 296 green; tsc clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 06:37:33 +05:30
Claude (auto-fix)
55ae1d46d0 fix(vendors): move GSTIN CAPTCHA into a popup
All checks were successful
PR checks / checks (pull_request) Successful in 46s
PR checks / integration (pull_request) Successful in 31s
The GSTIN lookup rendered its CAPTCHA (image + 6-digit input + Verify /
New image) inline inside the Add/Edit Vendor dialog. AdminDialog has no
internal scroll and is vertically centred, so the taller form pushed its
footer (Cancel / Create Vendor / Save) off-screen and out of reach.

Extract the CAPTCHA into a dedicated popup (CaptchaPopup) overlaid on the
vendor form at z-[60] with an explicit Cancel button and a ✕ close
control. It handles Escape on the capture phase so dismissing the CAPTCHA
does not also close the underlying form. In-flight CAPTCHA errors now
show inside the popup (it stays open so the user can retry / get a new
image); the success line still lands on the main form. The form footer is
never displaced.

Adds a unit test covering popup open on Look up, Cancel closing only the
popup, and a successful verify populating the fields.

Fixes #114
2026-06-24 06:37:29 +05:30
21df005ab6 Merge pull request 'feat(sidebar): group Purchase Order links under Purchasing' (#113) from feat/sidebar-purchasing-section into master
All checks were successful
Refresh staging / refresh (push) Successful in 7s
Reviewed-on: #113
2026-06-24 00:45:23 +00:00
2e8fd67805 test(sidebar): cover PO links moved under Purchasing + renames
All checks were successful
PR checks / checks (pull_request) Successful in 44s
PR checks / integration (pull_request) Successful in 31s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 06:10:55 +05:30
29118aa88e feat(sidebar): group Purchase Order links under Purchasing
Some checks failed
PR checks / checks (pull_request) Failing after 3s
PR checks / integration (pull_request) Successful in 31s
Move New PO, Closed POs, Import PO and History into the Purchasing
section and rename them: "New PO" to "New Purchase Order", "Import PO"
to "Import Purchase Order", "History" to "Purchase Order History".
Per-role visibility is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 06:08:49 +05:30
d25a600566 chore(crewing): expand abbreviated rank names in rank-data
All checks were successful
PR checks / checks (pull_request) Successful in 44s
PR checks / integration (pull_request) Successful in 31s
Fully expand the five abbreviated rank names so the canonical seed (which
upserts ranks by code and overwrites name) matches the names loaded into prod:
- PM → Project Manager
- Assistant PM → Assistant Project Manager
- Sr. Dredge Operator → Senior Dredge Operator
- Jr. Dredge Operator → Junior Dredge Operator
- Sr. Fabricator → Senior Fabricator

Hierarchy, codes, category, isSeafarer and grantsLogin are unchanged. (The
prod Rank table was seeded with these 19 ranks out-of-band; this keeps the
source of truth in sync so a future seed won't revert the names.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 05:39:14 +05:30
7245bb1962 Merge pull request 'fix: On new PO screen Vendors should have search' (#110) from claude/issue-109 into master
All checks were successful
Refresh staging / refresh (push) Successful in 7s
Deploy release to production / deploy (push) Successful in 1m30s
Reviewed-on: #110
2026-06-23 23:52:47 +00:00
c710fe5d73 Merge pull request 'feat(po): catalogue line items on approval + move /inventory/{items,vendors} ? /catalogue' (#108) from feat/catalog-on-approval into master
All checks were successful
Refresh staging / refresh (push) Successful in 8s
Reviewed-on: #108
2026-06-23 23:47:49 +00:00
Claude (auto-fix)
c503f839e8 feat(po): make vendor field a searchable combobox
All checks were successful
PR checks / checks (pull_request) Successful in 46s
PR checks / integration (pull_request) Successful in 31s
The vendor field on the PO forms was a plain native <select>, forcing
users to scroll the full vendor list. Mirror the item-search UX with a
searchable combobox that filters by vendor name and formal code
(vendorId), case-insensitively. The vendor list is already client-side,
so this is a pure in-memory filter — no API or DB change.

New VendorSelect component (components/ui/vendor-select.tsx) is a
self-contained portal-rendered combobox posting a hidden vendorId input,
so it drops into all three PO forms unchanged on the server:
- po/new/new-po-form
- po/[id]/edit/edit-po-form
- approvals/[id]/manager-edit-po-form

Preserves the optional field, "No vendor selected" empty option, and the
"{name} (CODE)" / "(unverified)" label. Unverified vendors (null code)
remain findable by name. Adds unit tests for the filter logic and
component behaviour.

Fixes #109
2026-06-24 05:16:57 +05:30
2bdf3a6536 chore: rename e2e folder to catalogue + drop /inventory redirects
All checks were successful
PR checks / checks (pull_request) Successful in 43s
PR checks / integration (pull_request) Successful in 31s
- tests/e2e/inventory → tests/e2e/catalogue (folder name only; playwright globs
  ./tests/e2e so nothing else changes).
- remove the next.config redirects from /inventory/{items,vendors}: the old
  routes are intentionally left free for a future feature.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 05:06:44 +05:30
d7b455ab7d refactor(routes): move /inventory/{items,vendors} → /catalogue/{items,vendors}
All checks were successful
PR checks / checks (pull_request) Successful in 44s
PR checks / integration (pull_request) Successful in 31s
Renames the product-catalogue pages (items + vendors, incl. their [id] detail
pages) out of /inventory into /catalogue. /inventory/cart is unchanged. All
internal links, redirects, revalidatePath calls, sidebar nav, and tests are
updated; next.config redirects keep old /inventory/{items,vendors}[/...] URLs
working (permanent) so existing bookmarks don't 404.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 05:04:29 +05:30
70f3230c36 feat(po): register line items in the product catalogue on approval
All checks were successful
PR checks / checks (pull_request) Successful in 43s
PR checks / integration (pull_request) Successful in 31s
Previously a PO's free-text line items only became reusable catalogue products
(/inventory/items) on full payment (markPaid → syncProductCatalog). An approved-
but-unpaid PO's items weren't selectable for further POs yet.

- extract syncProductCatalog into lib/product-catalog.ts (shared).
- call it from approvePo so approved items are immediately catalogued (create
  product by name if unknown, link the line item, upsert last/per-vendor price);
  payment still re-syncs to refresh prices. Idempotent.
- test: approving a PO with a free-text line creates + links the product and
  records the per-vendor price.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 04:59:47 +05:30
3babfe26ef feat(po): user-defined T&C categories + dynamic PO terms editor (#11)
All checks were successful
PR checks / checks (pull_request) Successful in 45s
PR checks / integration (pull_request) Successful in 32s
Follow-up to the merged #11 PR (which shipped the enum-based catalogue): make
categories user-defined data and the PO T&C a dynamic editor.

- categories are a TermsCategory TABLE (not an enum) — admins add new ones;
- every PO T&C line is catalogued, incl. the previously-fixed boilerplate
  (seeded under a "General" category) and an "Others" bucket;
- the PO form is a dynamic editor: "+ Add term", pick a category, type/pick a
  clause (components/po/po-terms-editor.tsx), used by new/edit/manager-edit.

Migration: the already-released 20260624140000 migration is untouched; a new
20260624150000 FORWARD migration renames the enum, creates the table, migrates
existing enum clauses onto category rows, adds isDefault/sortOrder + the two
fixed lines under General, and adds PurchaseOrder.terms (JSON snapshot that
supersedes the legacy tc* columns for export/detail; old POs fall back to tc*).

Tests rewritten for category creation + catalogue/default helpers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 04:43:24 +05:30
fced7cc307 Merge pull request 'feat(po): admin-managed Terms & Conditions catalogue + PO dropdowns (#11)' (#106) from feat/terms-conditions-admin into master
All checks were successful
Refresh staging / refresh (push) Successful in 7s
Deploy release to production / deploy (push) Successful in 1m34s
Reviewed-on: #106
2026-06-23 22:14:05 +00:00
f4c8ec7585 feat(po): make TermsField a combobox (type-or-pick)
All checks were successful
PR checks / checks (pull_request) Successful in 43s
PR checks / integration (pull_request) Successful in 30s
Per review: the five named PO T&C slots now allow a one-off custom clause as
well as picking a catalogued one. TermsField becomes a native <input list> +
<datalist> combobox (still plain FormData, no form/page changes). Any current/
custom value is preserved as the input value.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 03:42:38 +05:30
6e8d05e34e Merge pull request 'fix: Paginate PO history' (#105) from claude/issue-104 into master
All checks were successful
Refresh staging / refresh (push) Successful in 7s
Reviewed-on: #105
2026-06-23 22:10:54 +00:00
a99b2ed5df feat(po): admin-managed Terms & Conditions catalogue + PO dropdowns (#11)
All checks were successful
PR checks / checks (pull_request) Successful in 43s
PR checks / integration (pull_request) Successful in 31s
Mirrors the Place-of-Delivery (#19) pattern: an admin clause library that feeds
the PO T&C fields as dropdowns. (No "work order" type — POs only, per steer.)

- schema + migration: TermsCondition (category enum + text + isActive); the
  migration seeds the prior TC_DEFAULTS as the starting clauses.
- permission manage_terms (Manager + SuperUser + Admin).
- admin screen /admin/terms: table + Add/Edit dialogs + activate/deactivate +
  delete (mirrors /admin/delivery-locations); sidebar link under Administration.
- PO forms (new / edit / manager-edit): the five named T&C slots (Delivery /
  Dispatch / Inspection / Transit Insurance / Payment Terms) become a shared
  <TermsField> select sourced from active clauses of that category; "Others"
  stays free text; the fixed boilerplate lines are untouched.
- tc* columns stay free-text SNAPSHOTS (export/import unchanged); a current value
  not among active clauses is preserved as a "(current)" option.
- tests: terms CRUD + permission guard + grouping helper (6).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 03:38:32 +05:30
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
8206483f88 Merge remote-tracking branch 'origin/master' into feat/email-po-to-vendor
All checks were successful
PR checks / checks (pull_request) Successful in 43s
PR checks / integration (pull_request) Successful in 30s
# Conflicts:
#	App/app/api/po/[id]/export/route.ts
2026-06-24 02:50:32 +05:30
3edd1ffcc5 feat(po): email PO to vendor — PDF link in an Outlook draft (#14)
All checks were successful
PR checks / checks (pull_request) Successful in 43s
PR checks / integration (pull_request) Successful in 31s
Adds an "Email to vendor" button on the PO detail (available once approved,
through CLOSED, and again after payment) that opens an Outlook draft addressed
to the vendor's primary contact with a time-limited PDF download link.

Since mailto: can't attach files, the PDF is rendered and stored, and the draft
carries a link (the approach chosen for this issue):

- PdfService/: new standalone Express + Playwright microservice (GstService/
  EpfoService pattern) — POST /pdf { url } renders a page to a real PDF via
  headless Chromium. SSRF-guarded (shared token + optional origin allowlist).
- export route: accepts a server-only `svc` token (PDF_SERVICE_TOKEN) so
  PdfService can fetch /api/po/[id]/export?format=pdf without a user session;
  `pdf=1` drops the print button + window.print() auto-trigger.
- lib/pdf-service.ts renderPoPdf(); prepareVendorEmail() server action renders →
  uploads to R2 (po-pdf/…) → presigns a 7-day link → returns a mailto draft.
- po-detail: EmailVendorButton, shown when approved + vendor has a contact email.
- Gated by PDF_SERVICE_URL/PDF_SERVICE_TOKEN; friendly error if unconfigured.
- No DB model/migration. Tests: prepareVendorEmail (6, PdfService/storage mocked).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 02:45:48 +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