Commit graph

107 commits

Author SHA1 Message Date
0e3a79ecd4 feat: production seed script for Pelagia Marine (pnpm db:seed:prod)
Idempotent seed with real company data:
- 14 users (SSO/no-password): Accounts, Managers, Technical, Manning
- 7 sites: Head Office (HOFC), PMS Kochi, Laccadives, Haldia,
  Thilakkam, Kavaratti, Thinnakara
- 10 vessels assigned to their respective sites:
  HNR1-4, Champion, Hanunam, Sejal, Sejal 2, GD 3000, Thilakkam
- Full accounting code hierarchy (Rev. 01/251227) — 300+ codes across
  7 top categories via two-pass upsert to wire parent links

Run with: pnpm db:seed:prod

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 04:26:38 +05:30
a1b77d8b00 feat(vessels): editable custom code when creating a vessel
- Add Vessel dialog now shows an editable Code field pre-filled with the
  next auto-generated code (e.g. SITE-004) — user can change it freely
- Edit Vessel dialog keeps the code read-only (changing codes on existing
  data would break PO references)
- createVessel action: uses submitted code if provided, auto-generates if
  left blank, and validates uniqueness before saving

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 04:14:03 +05:30
0d17672ea9 feat(accounts): hierarchical accounting codes with 6-digit format and category tree
- Account model gains parentId (self-referential, 3 levels: TopCategory → SubCategory → Item)
- DB migration: adds parentId FK column to Account table
- Code format changed from PREFIX-NNN to 6-digit numeric (e.g. 100101)
- Seeded all 300+ accounting codes from the official chart (Rev. 01/251227) across
  7 top categories: Capital Expenses, Business Development, Office Admin, Project
  Expenses, Manning, Technical, Bunker/Lubes
- Admin Accounting Code page: collapsible tree view (top category > sub-category > items),
  inline search, Add/Edit dialogs with parent selector and 6-digit code field
- All PO forms (new, edit, import, manager-edit): accounting code dropdown now shows
  only leaf items grouped in <optgroup> by sub-category, labelled "TopCat › SubCat"
- Seed data updated: old flat account codes replaced by mapped leaf codes from new hierarchy

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 03:27:31 +05:30
cc7251e6b7 feat: Cost Centre covers vessels and sites, vessel codes, Accounting Code rename, vessel-site assignment
- Undo Vessel→Cost Centre rename in admin (admin shows "Vessel Management" again)
- Sidebar: "Cost Centres"→"Vessels", "Accounts"→"Accounting Codes"
- PO forms (new/edit/import/manager-edit) now show both Vessels (with code) and Sites in the
  Cost Centre dropdown, encoded as v:<id> / s:<id> via a costCentreRef field
- vesselId on PurchaseOrder is now nullable; siteId is set when a site is the cost centre
- History, approvals, dashboard, my-orders, payments display vessel.name ?? site.name as Cost Centre
- History and approvals cost centre filters use costCentreRef URL param supporting both types
- Admin vessel form: adds Site assignment dropdown
- Admin accounts: renamed to "Accounting Code" throughout (pages, forms, sidebar)
- PO detail and exports: "Account" label renamed to "Accounting Code"
- Site detail: "Assigned Vessels (Cost Centres)" heading; vessel detail breadcrumb fixed
- Create PO links from vessel/site detail use ?costCentreRef= param
- Export routes handle costCentreRef filter param (with legacy vesselId fallback)
- DB migration: ALTER TABLE PurchaseOrder ALTER COLUMN vesselId DROP NOT NULL
- CLAUDE.md updated with Cost Centre Model documentation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 03:04:29 +05:30
3f3e1e6423 feat(admin): confirm activate/deactivate via modal popup across all tables
Replace immediate server action calls with ConfirmDialog modals for
activate/deactivate on all 6 admin tables (users, vendors, vessels,
sites, accounts, products). Delete already used DeleteConfirmDialog;
this adds the same pattern for reversible toggle actions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 03:07:04 +05:30
d27ec9152c feat(admin): collapse row actions into ⋯ dropdown menu
Replace per-row inline action buttons (Edit, Activate/Deactivate, Delete,
Grant SuperUser) across all six admin tables with a Radix DropdownMenu
triggered by a ⋯ button. Introduces RowActionsMenu/Item/DestructiveItem/
Separator primitives and a DeleteConfirmDialog modal. Each Edit*Button
gains controlled open/onOpenChange props so the dialog can be driven from
the table's per-row ActionsMenu sub-component. Toggle and delete actions
use useTransition + router.refresh() directly in the table.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 02:58:54 +05:30
9758dcd8ab feat(admin): add client-side search, sort, and filter chips to all admin tables
Adds a reusable useTableControls hook and TableControls/SortableTh
components, then wires them into all six admin table pages (users,
vendors, vessels, sites, accounts, products). Each page now supports
a global search bar, clickable sortable column headers with ↑/↓/⇅
indicators, and role/status filter chips — all purely client-side with
no URL params or server round-trips. Server pages continue to fetch the
full list and pass it as props to a new *-table.tsx Client Component.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 02:46:52 +05:30
bff9696b7b fix(profile): allow empty current password when setting password for first time
SSO users have no passwordHash and should be able to set a local password
without providing a current one. Users with an existing password still
must verify it. Removes the client-side required attribute and updates
the server-side logic accordingly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 00:14:46 +05:30
a16f418e71 fix(admin): guard user deletion against all FK constraints
The delete action was only checking for submitted POs, leaving POAction
(actorId) and ItemConsumption (recordedById) to throw FK constraint
errors at the DB level. Now returns a clear error for each case and
also cleans up SuperUserRequest rows (requester + resolver) inside the
transaction before deleting.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 23:20:18 +05:30
bef401aa76 fix(auth): display OAuth errors on the login page
Maps NextAuth error codes (OAuthCallbackError, AccessDenied, OAuthSignin)
from the ?error= query param to user-friendly messages shown above the
Microsoft sign-in button.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 23:15:27 +05:30
352b14cec7 fix(build): run prisma generate before next build
Ensures the Prisma client types are always regenerated from the current
schema before compilation. Without this, schema changes (e.g. making
passwordHash nullable) cause TypeScript errors on the build server if
prisma generate hasn't been run manually after pulling.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 22:55:44 +05:30
56817a7d86 feat(auth): add Microsoft 365 SSO via Azure Entra ID
Adds the Microsoft Entra ID provider to NextAuth alongside the existing
credentials provider. Sign-in is restricted to Pelagia Marine's M365
tenant via the issuer URL; access is further gated by requiring a
matching active user record in the DB (DB-managed roles remain unchanged).

- auth.ts: add MicrosoftEntra provider, signIn callback (DB lookup),
  async jwt callback to populate id/role on first SSO sign-in
- login-form.tsx: add primary "Sign in with Microsoft 365" button with
  Microsoft logo; credentials form kept as a fallback below a divider
- prisma: make passwordHash nullable (migration applied) to allow
  SSO-only users without a local password
- admin/users: password is now optional when creating a user — leave
  blank for SSO-only accounts
- profile/actions: return a clear error if an SSO user (no passwordHash)
  attempts to use the change-password form
- .env.example: document AZURE_AD_CLIENT_ID/SECRET/TENANT_ID

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 22:48:37 +05:30
b31fe7374c fix(ui): remove PMS taglines from login and mobile pages
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 15:23:23 +05:30
a2c35d0a93 feat(admin): auto-generate structured IDs for users, vendors, accounts and cost centres
Users: employeeId auto-generated from role prefix (TCH/MAN/ACC/MGR/SUP/AUD/ADM)
followed by next sequential number; shown read-only in edit form, removed
from create form. Cost Centres: new code field (SITE-001 ...) added to
Vessel model with migration + backfill; auto-generated on create, read-only
in edit. Vendors and Accounts: code/vendorId inputs pre-filled with the
next suggested ID (VND-001, ACC-001) from the server page; user can override
with any PREFIX-NUMBER format, validated by regex.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 15:02:50 +05:30
49ba6e8be5 fix(ui): use defined theme colors for danger/warning buttons
The theme only defines danger/warning/success as flat colors plus -50/-100/-700
shades. bg-danger-600 and bg-warning-600 don't exist so those buttons rendered
with a transparent background, making white text invisible until hover revealed
bg-danger-700. Replaced with bg-danger / bg-warning which are defined.
Also fixed border-danger-200/400 and text-danger-600 (undefined) on the
Delete and Confirm Delete buttons.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 13:11:10 +05:30
3e5d11c4ae feat(po): show note author name on manager note banners
Derives the author from the most recent EDITS_REQUESTED / REJECTED /
APPROVED action that carries a note. PO detail banner now shows 'Note from
[name]', edit-page banner shows 'Edits requested by [name]', and the
closed-orders list prefixes the truncated note with the author's name.
No schema changes required - uses the already-fetched actions with actor.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 04:41:53 +05:30
c92f136b09 fix(ui): reset pending state on success for all save/confirm buttons
Parent button components (EditVendorButton, EditAccountButton, etc.) stay
mounted even when their AdminDialog closes — so pending was never cleared
on success, causing buttons to show 'Saving...' on the next open. The
payment confirm button (no dialog) was stuck in 'Confirming...' until the
page re-render eventually unmounted it. Added setPending(false) before
setOpen/router.refresh in every success path.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 04:36:29 +05:30
5ad85417d9 fix(payments): only log PRODUCT_PRICE_UPDATED when price actually changed
Previously the action was logged unconditionally for every line item on
every payment confirmation, even when the price was identical to what was
already on the product. Now we compare unitPrice to the stored lastPrice
before deciding to record the change. New products being registered for
the first time are also excluded since that is not a price update.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 04:28:40 +05:30
d4007ee222 feat(my-orders): rename to Closed Purchase Orders, show only closed POs
Open POs are already visible on the dashboard. The /my-orders page now
fetches and displays only completed/closed statuses (MGR_APPROVED through
REJECTED) and is labelled 'Closed Purchase Orders' throughout.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 04:21:53 +05:30
cf9ff40262 feat(payments): partial/advance payment support
Allow accounts to record partial/advance payments against a PO before
full delivery. A new PARTIALLY_PAID status tracks in-progress payment;
paidAmount accumulates across multiple markPaid calls. PO only closes
when both paidAmount >= totalAmount AND all line items are delivered.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 04:17:19 +05:30
7169d52885 fix(notifications): rationalise who gets which email
PO_SUBMITTED: managers only, submitter no longer copied
PAYMENT_SENT: submitter only (it is a receipt prompt, not a manager action)
PARTIAL_RECEIPT_CONFIRMED: managers now notified via new event type
RECEIPT_CONFIRMED: unchanged (managers + accounts)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 03:45:40 +05:30
d82d18bac2 feat(email): rich PO details, action buttons, Purchase Order in subject
Subject line now reads 'Purchase Order XXXX' instead of 'PO XXXX'.
Each email includes a recipient-specific deep-link action button,
a PO summary card (number, title, submitter, vendor, vessel, cost centre, total),
and a line items table with qty/unit price/GST/line total.
notify() fetches enriched data internally so no call sites change.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 03:39:49 +05:30
a8e3b5d69b fix(diff): show GST rate strikethrough when submitter changes it on edit
qtyChanged/priceChanged/nameChanged were tracked for the diff view but
gstChanged was missing, so an edited GST % showed only the new value
with no strikethrough of the old one. Added gstChanged detection and
two-line rendering (old % struck through, new % in amber) to match the
other diff columns.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 03:34:54 +05:30
987b0aedfa fix(mobile): prevent notification dropdown overflow on small screens
The dropdown was w-96 (384 px) anchored right-0 to the bell button.
On phones the bell sits left of the logout icon so the panel bled off
the left viewport edge. Changed to w-80/sm:w-96 with a 100vw safety cap.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 03:32:37 +05:30
91f369da9e fix(my-orders): surface PARTIALLY_CLOSED POs in Open Orders
PARTIALLY_CLOSED was missing from the open-filter so affected POs
disappeared from the submitter''s My Orders view entirely, making it
impossible to confirm remaining deliveries.
Also hardens confirmReceipt() against negative delivery quantities
and extends partial-receipt.spec.ts with US-8c/8d/8e covering the
full PARTIALLY_CLOSED revisit flow.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 03:32:26 +05:30
1c5727850a fix(gst): 0% GST rate no longer falls back to 18%
parseFloat('0') is falsy in JS so `|| 0.18` silently discarded the user's
explicit 0% selection. Replaced with an explicit empty-string guard.
Adds e2e spec gst-rate.spec.ts covering all five GST rates (0/5/12/18/28%).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 00:00:51 +05:30
5f874cf2a2 pnpm workspace allow build scripts 2026-05-22 17:17:15 +05:30
934979750f test(e2e): harden PO form selectors 2026-05-22 17:15:17 +05:30
32ea27331c fix(po): tighten filters and export data 2026-05-22 17:15:03 +05:30
2c39f0225f feat(vessels): remove IMO number tracking 2026-05-22 17:14:40 +05:30
1ea22df2f7 fix(auth): stabilize login page rendering 2026-05-22 17:14:21 +05:30
d689ef8893 fix(vendors): fix transaction timeout and misleading error on vendor delete
Increase the Prisma interactive transaction timeout from the default 5s
to 30s so that the four sequential nullification + delete queries complete
reliably on a seeded database (P2028 timeout was the root cause).

Wrap the transaction in a try/catch so that if a timeout does still occur
the user sees "Delete timed out — please try again." instead of an
unhandled 500 that previously manifested as the misleading "referenced in
submitted or active purchase orders" error message.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 23:34:35 +05:30
19029a5a77 chore: restructure repo — flatten App/pelagia-portal to App, rename Prototype→Wireframe and Spec→Design
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 23:18:58 +05:30
26211e898d test(e2e): add comprehensive Playwright test suite for all recent features
Covers 21 user story groups extracted from the last 15+ feature commits:

- rebrand.spec.ts — PPMS branding on login page, sidebar, and tab title
- dashboard/po-status-badges.js — color-coded status badges for submitter & manager
- po-submit-button.spec.ts — Submit for Approval button visibility on DRAFT POs
- notification-bell.spec.ts — in-app bell icon, unread badge, and panel open
- export-gate.spec.ts — export buttons gated on MGR_APPROVED+ status; 403 on pre-approval
- payment-history.spec.ts — /payments/history accessible to ACCOUNTS; redirects others
- partial-receipt.spec.ts — per-item delivery tracking UI on paid POs
- vendor-auto-verify.spec.ts — vendor verification status visible in admin
- admin-bordered-buttons.spec.ts — Edit/Deactivate/Delete have border classes on admin pages
- profile.spec.ts — profile page loads for all roles; signature section for MANAGER/SUPERUSER
- inventory/items-tags.spec.ts — Cheapest/Closest tags and auto-sort by distance
- inventory/cart-icon.spec.ts — cart header icon with badge; item/vendor detail pages
- mobile/desktop-required.spec.ts — Desktop Required overlay for non-mobile roles + sign-out
- mobile/manager-approvals.spec.ts — mobile card layout; edit form hidden; action buttons visible
- mobile/accounts-payments.spec.ts — ACCOUNTS payments queue and buttons on mobile
- mobile/bottom-nav.spec.ts — Home/Approvals/Profile tabs for MANAGER; Home/Payments/Profile for ACCOUNTS
- approvals-edit-highlight.spec.ts — diff indicators on resubmitted POs

Also adds shared helpers/login.ts with USERS constants, login(), createDraftPo(),
and submitPo() — all using name-attribute selectors since PO form labels have no
htmlFor binding. Playwright config updated: workers capped at 2 locally (was
unlimited) to prevent auth concurrency failures, and retries set to 1 locally.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 18:25:42 +05:30
13b8bcd38a feat(mobile): add Home/Dashboard tab to mobile bottom nav
Prepend a Home tab (linking to /dashboard) to the bottom navigation bar
for both Manager/SuperUser and Accounts roles, giving one-tap access to
the dashboard from any mobile screen.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 01:58:32 +05:30
7ae1189042 fix(mobile): add sign-out button to DesktopRequired screen
Users on mobile who see the "Desktop Required" wall had no way to log out.
Added a Sign out button using next-auth signOut (requires "use client").

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 21:32:50 +05:30
f60f249c96 feat(mobile): extend mobile experience to Accounts role for payment actions
- Add ACCOUNTS to MOBILE_ROLES so the desktop-required wall no longer
  blocks them on small screens.
- MobileBottomNav is now role-aware: ACCOUNTS gets Payments + Profile tabs;
  MANAGER/SUPERUSER keep Approvals + Profile.  Role prop threaded from
  layout → MobileBottomNav.
- PaymentActions (both MGR_APPROVED and SENT_FOR_PAYMENT states) stacks
  vertically on small screens — input takes full width, button below it —
  then reverts to the horizontal inline layout at sm+ breakpoint.
- Payments page card bottom row (status badge + action) stacks on mobile
  (flex-col sm:flex-row) so the reference input isn't squashed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 21:31:57 +05:30
cfb16600d7 feat(mobile): manager approval queue and PO review on small screens
Adds a responsive mobile experience scoped to MANAGER and SUPERUSER roles:

Layout:
- Sidebar hidden on small screens (md: breakpoint)
- New MobileHeader (logo + notification bell + sign out) visible on mobile
- New MobileBottomNav (Approvals + Profile tabs) pinned to bottom on mobile
- New DesktopRequired overlay shown to all other roles on small screens —
  a fixed full-screen message directing them to use a desktop browser

Approvals queue:
- Desktop: existing table layout (hidden md:block)
- Mobile: tap-to-review card stack (md:hidden) — shows PO number, title,
  submitter, cost centre, amount, and a full-width Review button

PO approval detail:
- ManagerEditPoForm (direct field editing) hidden on mobile; still available
  on desktop (direct edits not required per spec)
- ApprovalActions buttons stack full-width on mobile, row on sm+
- Paddings reduced on small screens throughout

PO detail component:
- Order Details and Vendor grids switch from grid-cols-2 → grid-cols-1
  on small screens (sm:grid-cols-2 restores two columns at 640px+)
- Section padding reduced on mobile (p-3 md:p-6, p-4 md:p-6)
- Line items table already had overflow-x-auto — no change needed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 21:27:43 +05:30
3646af5c64 style(admin): replace text-link button styles with proper bordered buttons
Edit, Deactivate/Activate, and Delete actions in all admin table rows
were styled as plain text links (coloured text, no border or background).
Replaced with small pill-shaped bordered buttons that have a clear visual
affordance as interactive controls:

- Edit       → blue tinted border/bg  (primary-50 / primary-200)
- Deactivate → red tinted border/bg   (danger-50  / danger-200)
- Activate   → green tinted border/bg (success-50 / success-200)
- Delete     → white bg, red border; confirm state = solid red
- Cancel     → white bg, neutral border

Applies to: accounts, cost centres (vessels), users, vendors, products,
and the shared ConfirmDeleteButton component.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 21:18:49 +05:30
891e854c7c feat(vendors): auto-verify vendor on first successful payment
When accounts confirms a payment (SENT_FOR_PAYMENT → PAID_DELIVERED),
set Vendor.isVerified = true for the PO's vendor. The field already
exists in the schema (default false); this closes the loop so vendors
who have transacted at least once are marked verified automatically
without manual admin intervention.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 21:11:25 +05:30
a5fb7d088c rebrand: Pelagia Portal → PPMS (Pelagia Payment Management System)
Rename the application to PPMS across all user-facing surfaces:
- Browser title / metadata
- Login page (with tagline: "PMS — it runs the ship.")
- Sidebar logo text
- Email header, footer, and body copy in all transactional emails
- PDF/XLSX workbook creator field
- Reports export page title and heading
- Notifier FROM display name and default email domain

Legal company name ("Pelagia Marine Services Pvt. Ltd.") on PO documents
is unchanged — that is the issuing entity, not the application brand.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 21:00:15 +05:30
8322f33880 fix(export): gate PDF/XLSX on manager-approved status; drop submitter-name fallback
- Export API returns 403 for any PO not yet approved (DRAFT, SUBMITTED,
  MGR_REVIEW, EDITS_REQUESTED, VENDOR_ID_PENDING, REJECTED) — only
  MGR_APPROVED, SENT_FOR_PAYMENT, PAID_DELIVERED, PARTIALLY_CLOSED and
  CLOSED are exportable.
- The submitter's name is no longer used as a signatory fallback; since
  export is blocked until after manager approval an approver always exists.
- Export PDF / Export XLSX buttons are hidden in po-detail for pre-approval
  statuses, so users never encounter the 403 through normal UI flows.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 20:50:26 +05:30
340a3dcce0 feat(gst-service): structured logging, request tracing, and per-session captcha refresh
Logging (GstService):
- JSON-structured log lines: { ts, level, msg, ...ctx } — one per line,
  machine-parseable by any log aggregator (datadog, loki, etc.)
- LOG_LEVEL env var (DEBUG|INFO|WARN|ERROR, default INFO) — set DEBUG to
  see every captcha fetch, raw GST response body, and page console event
- WARN and ERROR lines go to stderr; INFO/DEBUG go to stdout so process
  supervisors can separate them
- Every log line carries relevant context: reqId, sessionId, gstin, ms, etc.
- errCtx() helper extracts errName, errMsg, and first 6 stack frames from
  any thrown value — no more bare String(e)
- elapsed() helper records wall-clock ms for every expensive step:
  browser launch, page navigation, captcha fetch, GST API call
- Request/response middleware: every HTTP request logs method, path,
  reqId, status, and duration; status >= 500 logs at ERROR, >= 400 at WARN
- Playwright page listeners: console errors/warnings, pageerror,
  requestfailed, and HTTP 4xx/5xx on GST portal endpoints
- process.on(uncaughtException) and process.on(unhandledRejection) so
  unexpected crashes surface in logs instead of silently dying
- Browser "disconnected" event logged; _browser reset so next request
  auto-relaunches without manual restart
- SESSION_TTL_MS configurable via env (default 3 min)
- closeSession() logs the reason (success / errorCode / exception / etc.)
- GET /health now returns browserConnected, per-session captchaCount,
  expiresInMs, and lastUsedMsAgo for operational visibility

Multiple captchas per session:
- Session now holds captchas: CaptchaEntry[] (ordered oldest→newest)
  so every image fetched in a session is kept for traceability
- GET /captcha/:sessionId — new endpoint that calls /services/captcha
  again within the SAME browser context (no page reload, ~200ms vs ~5s)
  and appends a new CaptchaEntry; resets TTL; returns totalCaptchas
- POST /search on SWEB_9034 (wrong captcha) no longer closes the session —
  returns { canRefresh: true, sessionId } so the caller can hit
  GET /captcha/:sessionId for a fresh image and retry immediately
- All other error paths (SWEB_9000, network error, no data) still close
  the session as before

Next.js proxy (app/api/gst/captcha/route.ts):
- GET /api/gst/captcha?refresh=<sessionId> proxies to the new
  GET /captcha/:sessionId endpoint on GstService
- Plain GET /api/gst/captcha still creates a new session as before

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 16:44:22 +05:30
e8041a8230 fix(notifications): role-aware deep-links instead of always /po/[id]
Notification links now route each recipient to the page where they
need to take action, not just the PO detail:

  Manager / SuperUser:
    PO_SUBMITTED, VENDOR_ID_PROVIDED → /approvals/[id]  (approval queue)

  Accounts:
    PO_APPROVED / PO_APPROVED_WITH_NOTE → /payments  (payment queue)

  Submitter:
    EDITS_REQUESTED → /po/[id]/edit  (open the edit form directly)
    PAYMENT_SENT    → /po/[id]/receipt  (open the receipt confirmation)

  All other events / recipients → /po/[id]

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 16:35:14 +05:30
d297fd044f fix(po): add Submit for Approval button on draft PO detail and edit pages
A draft PO had no way to be submitted without creating a brand new PO.
Two surfaces are now fixed:

Edit page (/po/[id]/edit):
- updatePo action now accepts intent "submit" (alongside "save" and
  "resubmit"), which transitions a DRAFT PO to MGR_REVIEW, creates a
  SUBMITTED action, and fires notifications — identical behaviour to
  the new-PO submit flow
- EditPoForm shows a primary "Submit for Approval" button when the PO
  is in DRAFT status; "Resubmit for Approval" remains for EDITS_REQUESTED

PO detail page (/po/[id]):
- New submitDraftPo server action in po/[id]/actions.ts handles the
  same DRAFT → MGR_REVIEW transition without requiring the full edit form
- New SubmitDraftButton client component renders next to the Edit link
  in the detail header for DRAFT POs owned by the current user
- Button order: Edit · Submit for Approval · Discard

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 16:32:44 +05:30
48de2d08a2 fix(notifications): drop redundant "PO" prefix, link all notifications to /po/[id]
In-app notification bodies now lead directly with the PO number
(e.g. "PO-2024-12345 approved" → "PO-2024-12345 approved") without the
word "PO" repeated before the identifier, since the ID already makes
the context obvious.

All notification links now route to /po/[id] so clicking any
notification takes the user straight to the relevant PO detail page,
regardless of their role or the event type.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 16:23:21 +05:30
f0b49c4b96 feat(notifications): in-app bell with real-time badge and per-recipient messages
Schema (migration: 20260516104351_notification_isread_link):
- Notification.isRead Boolean @default(false) — tracks unread state
- Notification.link String? — deep-link URL for each notification

lib/notifier.ts:
- buildInAppBody(): per-recipient message text, context-aware
  e.g. managers see "Maria Santos submitted PO-2024-12345 for your review"
       submitters see "Your PO-2024-12345 has been approved"
       accounts see "PO-2024-12345 approved — ready for payment"
- buildInAppLink(): routes to the correct page per recipient role
  (submitter → /po/[id] or /po/[id]/receipt; manager → /approvals/[id];
   accounts → /payments; etc.)
- Notifications written with both body and link on every event

API:
- GET /api/notifications — returns { unreadCount, notifications[] } for
  the session user; last 20 ordered by sentAt desc
- PATCH /api/notifications/read — marks all (or specific ids) as read

NotificationBell (components/layout/notification-bell.tsx):
- Bell icon in header with red unread count badge
- Polls /api/notifications every 30 seconds
- When unread count increases vs previous tick: bounces the bell icon
  and pulses the badge for 3 seconds to signal a new arrival
- Click opens dropdown panel:
  - Unread items highlighted with blue-dot indicator and bolder text
  - Each item is a Link to the notification's deep-link URL
  - "Mark all read" button in panel header
  - Auto-marks all as read when panel opens (optimistic update + API call)
  - Closes on outside click
  - "Showing 20 most recent" footer when list is at limit

Header: receives initialUnreadCount and initialNotifications as props
Portal layout: fetches initial notification data server-side to avoid
a loading flash on first render; dates serialised to ISO strings

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 16:16:06 +05:30
3556b1425f feat(profile): user profile page, manager signature, and SuperUser access requests
Schema (migration: 20260516103515_user_profile_signature):
- User.signatureKey String? — storage key for the manager's approval signature
- New RequestStatus enum (PENDING / APPROVED / DENIED)
- New SuperUserRequest model — tracks access-escalation requests from users

Profile page (/profile) — all roles:
- Account info panel (name, email, employee ID, role — read-only)
- Change password form (validates current password, bcrypt hash on save)
- Signature uploader (MANAGER / SUPERUSER only) — PNG/JPG/WebP up to 2 MB;
  previews before save; can remove existing signature
- SuperUser access request form — textarea for reason, shows current request
  status (pending / approved / denied) after submission

Signature gate on approval page (/approvals/[id]):
- Server checks if the current manager has uploaded a signatureKey
- If missing: shows an amber warning banner with a deep-link to /profile
  instead of rendering the approval action buttons; managers cannot
  approve, reject, or request edits without a signature on file

PDF and XLSX exports:
- Fetches the approver's signature image from storage after identifying
  the approval action
- PDF: embeds as base64 data URI <img> above the approver name in the
  left signature block
- XLSX: inserts the image into the sig-row cells via ExcelJS addImage;
  adds a name row below the image for legibility

SuperUser requests admin page (/admin/superuser-requests):
- Pending requests listed with user info, role, reason, and Approve/Deny buttons
- Approve: sets user.role = SUPERUSER and closes the request
- Deny: marks request DENIED, user role unchanged
- Resolved history table below

Admin user management updates (/admin/users):
- "SuperUser" button (ShieldCheck icon) on every non-superuser, non-admin row
- Directly grants SUPERUSER role and auto-closes any open request for that user

lib/storage.ts:
- buildSignatureKey(userId, ext) helper
- uploadBuffer(key, buffer, contentType) — server-side write to dev-uploads or R2
- downloadBuffer(key) — server-side read from dev-uploads or R2 presigned URL

Sidebar:
- "My Profile" link (UserCircle) visible to all roles
- "SuperUser Requests" link (ShieldCheck) in admin section

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 16:09:30 +05:30
3b3a26eafe feat(receipt): allow partial receipt confirmation with per-item delivery tracking
Submitters can now mark individual item quantities as received when
confirming delivery, rather than treating a PO as all-or-nothing.

Schema (migration: 20260516103013_partial_receipt):
- POStatus: new PARTIALLY_CLOSED value between PAID_DELIVERED and CLOSED
- ActionType: new PARTIAL_RECEIPT_CONFIRMED value
- POLineItem: new deliveredQuantity Decimal? field — accumulates delivered qty
  across multiple receipt events

State machine:
- PAID_DELIVERED → confirm_partial_receipt → PARTIALLY_CLOSED (new)
- PARTIALLY_CLOSED → confirm_receipt → CLOSED (all delivered)
- PARTIALLY_CLOSED → confirm_partial_receipt → PARTIALLY_CLOSED (more partial)

Receipt page / form:
- Loads line items with ordered qty, previously delivered qty, and remaining qty
- Per-row numeric input for "receiving now" defaulting to all remaining
- "Mark all remaining" shortcut
- Dynamic button: "Confirm Partial Receipt" vs "Confirm Receipt & Close PO"
- Info banner telling user if the PO will stay open or close

Receipt action:
- Accumulates deliveredQuantity per line item
- If all lines fully delivered → CLOSED + fires notifications + updates inventory
- If any line still outstanding → PARTIALLY_CLOSED (no notifications yet)
- Inventory auto-update runs per-event for the delivered quantities only

Dashboard & PO detail:
- Open Orders count now includes PARTIALLY_CLOSED
- "Confirm Receipt" CTA in po-detail handles PARTIALLY_CLOSED with
  distinct amber styling and "Confirm Remaining" label
- Activity log shows PARTIAL_RECEIPT_CONFIRMED with appropriate label
- PARTIALLY_CLOSED gets warning (amber) badge variant

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 16:02:44 +05:30
b2bfa63f61 fix(export): show approver as signatory on PDF and XLSX reports
The left signature block on generated PO documents (PDF and XLSX) was
showing the submitter's name as the authorized signatory. The signatory
should be the manager who approved the PO, since they are the one
authorizing the expenditure on behalf of the company.

The approvedBy name was already extracted from the APPROVED / APPROVED_WITH_NOTE
action at the top of the export route — it just wasn't being used in the
signature block. Now both the PDF and XLSX templates use approvedBy for
the left sig block, falling back to the submitter only if the PO has not
yet been approved (e.g. when exporting a draft).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 15:57:39 +05:30