Commit graph

349 commits

Author SHA1 Message Date
8b56c79f5f docs: changelog + PdfService README for reports, email-to-vendor, microservices
All checks were successful
PR checks / checks (pull_request) Successful in 48s
PR checks / integration (pull_request) Successful in 31s
The repo CHANGELOG had fallen behind. Adds [Unreleased] entries for the recently
shipped work and a README for the previously undocumented PdfService.

Added (changelog):
- Reports — Purchasing spend analytics (cost centres + accounting codes).
- Email PO to vendor + the PdfService microservice (cached per PO).
- EpfoService + PdfService microservices and their release auto-deploy.
- Unsaved-changes prompt on PO create/edit (#18).
- Crew login-on-hire for management ranks.
- Delivery Locations (#19), T&C catalogue (#11), advance payment (#92).

Fixed (changelog):
- "Email to vendor" never rendered (auth middleware bounced the svc fetch) — #127.
- Reports charts all one colour (RSC client/server boundary) — #120.

New: PdfService/README.md (endpoints, token/origin security, env, app integration).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 15:53:05 +05:30
70152b0a5e Merge pull request 'feat(automation): watcher that addresses claude-review: comments on Claude PRs' (#129) from claude/busy-boyd-b16092 into master
All checks were successful
Refresh staging / refresh (push) Successful in 7s
Reviewed-on: #129
2026-06-24 10:08:01 +00:00
7acd86e3dd feat(automation): watcher that addresses claude-review: comments on Claude PRs
All checks were successful
PR checks / checks (pull_request) Successful in 47s
PR checks / integration (pull_request) Successful in 32s
Sibling to claude-issue-watcher.sh: polls open Claude-raised PRs (head
branch under claude/, or labelled claude-pr) for review comments carrying
the marker `claude-review:` — in the PR conversation, review summaries, or
inline on-file comments — and runs headless Claude Code on the PR's own
branch to address them, pushing the follow-up commit(s) to the same branch.

- Authorization gate: only repo collaborators (write access) + the owner can
  trigger it; the bot's own comments are ignored.
- Idempotent: handled comments are tracked by a hidden marker on the bot's
  acknowledgements, so the 10-min poll never redoes a comment.
- Own clone (~/pelagia-pr-review), config, and lock so it never races the
  issue watcher. Token needs write:repository + write:issue.

Adds the script, an example config, .gitignore entries for the live
config/lock, and an automation/README.md section with deploy + cron steps.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 15:21:04 +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
7a4c1c7f62 Merge pull request 'fix(pdf): let PdfService reach the export route past auth middleware' (#127) from fix/pdf-export-middleware into master
All checks were successful
Refresh staging / refresh (push) Successful in 7s
Deploy release to production / deploy (push) Successful in 1m30s
Reviewed-on: #127
2026-06-24 09:27:35 +00:00
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
2fcb207add Merge pull request 'fix(reports): charts rendered one colour — RSC client/server boundary bug' (#120) from fix/reports-series-colors into master
All checks were successful
Refresh staging / refresh (push) Successful in 8s
Deploy release to production / deploy (push) Successful in 1m35s
Reviewed-on: #120
2026-06-24 07:05:39 +00:00
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
ee8313e10c Merge pull request 'fix(reports): one colour per item in the comparison chart (yearly too)' (#118) from fix/reports-chart-colors into master
All checks were successful
Refresh staging / refresh (push) Successful in 8s
Reviewed-on: #118
2026-06-24 06:18:13 +00:00
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
56497a0d20 Merge pull request 'feat(po): prompt to save as draft when leaving with unsaved changes' (#116) from feat/po-draft-prompt into master
All checks were successful
Refresh staging / refresh (push) Successful in 7s
Reviewed-on: #116
2026-06-24 01:14:20 +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
502411afe6 Merge pull request 'chore(crewing): expand abbreviated rank names (+ seed ranks to prod)' (#111) from feat/expand-rank-names into master
All checks were successful
Refresh staging / refresh (push) Successful in 7s
Reviewed-on: #111
2026-06-24 00:45:10 +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
85805754b5 Merge pull request 'feat(po): user-defined T&C categories + dynamic PO terms editor (#11 follow-up)' (#107) from feat/terms-dynamic-editor into master
All checks were successful
Refresh staging / refresh (push) Successful in 8s
Reviewed-on: #107
2026-06-23 23:27:47 +00:00
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
7fe46c2448 Merge pull request 'feat(po): email PO to vendor � PDF link in an Outlook draft (#14)' (#101) from feat/email-po-to-vendor into master
All checks were successful
Refresh staging / refresh (push) Successful in 7s
Reviewed-on: #101
2026-06-23 21:55:10 +00:00
470523a7a6 Merge branch 'master' into feat/email-po-to-vendor
All checks were successful
PR checks / checks (pull_request) Successful in 53s
PR checks / integration (pull_request) Successful in 32s
2026-06-23 21:55:02 +00:00
cc7161d5ed Merge pull request 'fix(deploy): expand sparse checkout so microservices deploy (run #120 fix)' (#103) from fix/deploy-services-sparse-checkout into master
Some checks are pending
Deploy release to production / deploy (push) Successful in 2m4s
Refresh staging / refresh (push) Waiting to run
Reviewed-on: #103
2026-06-23 21:43:43 +00:00
cd2bcefdbd fix(deploy): expand sparse checkout so microservices/ecosystem are on disk
All checks were successful
PR checks / checks (pull_request) Successful in 43s
PR checks / integration (pull_request) Successful in 32s
Run #120 (v0.3.0 deploy) failed at the microservice step: every service folder
and ecosystem.config.js were "absent", and pm2 reported "File ecosystem.config.js
not found". Root cause: ~/pms on pms1 is a sparse checkout limited to App/, so
`git checkout -f $TAG` never materialised the service folders or the root
ecosystem.config.js. The app itself deployed fine (App/ is in the sparse set) and
prod stayed healthy.

- deploy.yml: before managing services, disable sparse-checkout (and clear the
  legacy core.sparseCheckout config + .git/info/sparse-checkout), then re-checkout
  the tag to materialise the full tree. Idempotent / no-op once expanded.
- Guard the pm2 call: if ecosystem.config.js is still absent, fail with a clear
  diagnostic (+ sparse-checkout list) instead of the cryptic PM2 error.
- README: note the sparse-checkout expansion.

Needs a fresh tag (e.g. v0.3.1) to re-run the deploy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 03:12:54 +05:30
2ac11d7528 Merge pull request 'chore(deploy): build & (re)start microservices on release tag' (#102) from chore/deploy-microservices into master
Some checks failed
Refresh staging / refresh (push) Successful in 7s
Deploy release to production / deploy (push) Failing after 1m14s
Reviewed-on: #102
2026-06-23 21:33:45 +00:00
6b0210078a chore(deploy): build & (re)start microservices on release tag
All checks were successful
PR checks / checks (pull_request) Successful in 42s
PR checks / integration (pull_request) Successful in 31s
The v* tag deploy previously only updated the Next app (ppms); GstService /
EpfoService / PdfService were never built or restarted by automation. Now the
same deploy manages them.

- ecosystem.config.js (root): pm2 definitions for gst-service (3003) /
  epfo-service (3004) / pdf-service (3005). Registers only services whose source
  is checked out (keyed on package.json), so a not-yet-merged service is skipped
  and adopted automatically once its PR lands. Secrets come from the env at pm2
  invocation; ports are fixed here.
- deploy.yml: after the app restart, export the few service secrets out of
  App/.env (never PORT or the ephemeral FORGEJO_TOKEN), npm install + playwright
  install chromium + build each present service, then
  `pm2 startOrReload ecosystem.config.js --update-env` (create on first release,
  reload after) + pm2 save, and health-check :3003/:3004/:3005.
- automation/README.md: documents the flow + the one-time alignment for any
  pre-existing differently-named pm2 process.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 02:59:36 +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
144d44ccca Merge pull request 'feat(po): admin-managed delivery locations + Place of Delivery dropdown (#19)' (#100) from feat/delivery-locations into master
All checks were successful
Refresh staging / refresh (push) Successful in 7s
Reviewed-on: #100
2026-06-23 20:43:57 +00:00
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
8a2c592f6f Merge pull request 'feat(po): manager sets advance payment on approval (#92)' (#99) from feat/manager-advance-payment into master
All checks were successful
Refresh staging / refresh (push) Successful in 7s
Reviewed-on: #99
2026-06-23 20:26:27 +00:00