Accounting Code search (new/edit/import/manager-edit PO forms):
- New SearchableSelect component (components/ui/searchable-select.tsx):
type-to-filter by code or name, results grouped by sub-category with
sticky headers, highlighted selected item, clear button, Escape/outside-click
to dismiss
- Replaces the plain <select> for the main Accounting Code field on all PO forms
- LineItemsEditor per-row account column also uses SearchableSelect (compact size)
when multi-account mode is active
Cost Centre dropdown reorganised by site:
- New type CostCentreGroup replaces flat CostCentreOption
- Each site becomes an <optgroup> label (unselectable); the site itself is the
first selectable option inside ("Haldia (Site)"), followed by its vessels
- Vessels with no site assigned appear under an "Unassigned Vessels" group
- Shared helpers buildCostCentreGroups() and buildAccountGroups() in
lib/cost-centre-groups.ts — used by all four PO form pages
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
- 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>
- 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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>