Cart icon:
- CartIcon component in header: listens to cart-updated events, shows
item count badge, navigates to /inventory/cart on click
- Visible for TECHNICAL, MANNING, SUPERUSER, MANAGER roles only
PO pre-population:
- /po/new page reads ?cart= searchParam, parses CartItem[] into
LineItemInput[], passes as initialLineItems prop to NewPoForm
- NewPoForm accepts initialLineItems and seeds useState from them
instead of always starting with one blank row
- Cart is cleared immediately when "Create Purchase Order" is clicked
so re-visiting cart doesn't re-submit the same items
Cart fixes:
- Empty state and "Add more items" links corrected from /admin/products
to /inventory/items and /inventory/vendors
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New /inventory/items/[id]: vendor price table with distance sorting via
SiteSelect client component (?siteId= URL param), price chart, stock
by site, Add to Cart per vendor
- New /inventory/vendors/[id]: contacts panel + searchable items table
with Add to Cart, links back to /inventory/items/[id]
- SiteSelect: reusable client component (useRouter.push, configurable
param key) used by both inventory and admin detail pages
- items-table: item names link to /inventory/items/[id]; vendor names
in expanded rows link to /inventory/vendors/[id]
- vendors-table: vendor names link to /inventory/vendors/[id]
- Fix admin product detail page: replace illegal Server Component
onChange handler with SiteSelect client component
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Vendors: remove deprecated contactName/Email/Mobile fields; use
VendorContact nested creates with primary flag, role, mobile, email
- Add pincode + lat/lng to all vendors for distance calculations
- Sites: 12 Indian port locations with lat/lng
- Vessels: 11, linked to sites
- Accounts: 12 covering all spend categories
- Users: 12 across all roles
- ProductVendorPrice: 51 entries linking vendors to products
- ItemInventory: 15 stock records across sites
- ItemConsumption: 14 daily consumption records
- PurchaseOrders: 12 spanning full status lifecycle
- Seed is idempotent (all upserts, safe to re-run)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds /inventory/vendors with distance-sorted vendor list and URL-param
site selector (?siteId=). Wires Items + Vendors + Cart into sidebar for
TECH/MANNING/SUPERUSER roles; MANAGER keeps admin management views.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Items page now fetches all active sites and passes them alongside
preferredSiteId to ItemsTable. A "Working Site" row appears at the
top of the table — selecting a site calls setPreferredSite, revalidates
the page, and shows distances in the vendor sub-rows. A status hint
("Distances shown from selected site") appears when a site is active;
"No site selected — distances hidden" is the empty-state label.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
VendorContact model
- New VendorContact table (name, role, mobile, email, isPrimary) with
cascade delete from Vendor
- Old single contactName/Mobile/Email fields removed from Vendor model
- Migration: 20260514191638_vendor_contacts
Vendor form
- ContactsEditor: dynamic list of contact rows — add, remove, mark primary
- Each contact row: name, role, mobile, email, primary checkbox
- Serialised as contacts[i].field form fields; existing single-contact
section removed
Vendor actions
- parseContacts() reads indexed contacts from FormData
- createVendor creates VendorContact rows in a nested write
- updateVendor deletes all contacts then re-creates them in a transaction
Vendor list page
- Contact column shows primary contact name + email; "+N more" badge
Vendor detail page
- Two-column layout: Vendor Details card + Contacts card
- Contacts displayed with avatar initials, role badge, primary badge
- VendorItemsTable client component: inline search (name, code,
description), tabular layout with links to item detail
Inventory items page (/inventory/items)
- Rebuilt as a searchable table (ItemsTable client component)
- Columns: Item name/description, Code, Vendor count, Lowest price
- Click any row to inline-expand vendor sub-rows for that item
- Vendor sub-rows: vendor name, verified badge, price, distance (if site
selected), + Cart button with "Added ✓" feedback
- Sort toggle (Distance / Price) shown in toolbar when a row is open
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Schema
- POLineItem.accountId String? — optional per-row account override
- Account.lineItems back-relation added
- Migration: 20260514185519
Line Items Editor
- New props: multiAccount, accounts, defaultAccountId
- When multiAccount=true: Account column appears in the editable table,
each row pre-selected with defaultAccountId, independently changeable
- Read-only view: Account column shown when any line item carries an
accountId (displays account code)
New & Edit PO forms
- "Multiple accounts" checkbox (default off) sits inline with the
Account label
- When checked: label changes to "Default Account / Cost Centre";
per-row dropdowns appear in the line items table pre-filled with
the chosen default account
- Edit form: checkbox initialises to checked when the PO already has
per-line accounts; existing accountId values are pre-populated
- accountId per line item is included in form data and persisted
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
TC_FIXED_LINE_2 added for the packaging/asbestos clause, rendered as
read-only item 8 (same style as item 1) in both the New PO and Edit PO
forms. Others (item 7) now initialises empty instead of carrying the
fixed text.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds deleteX server actions and ConfirmDeleteButton to the Items,
Vessels, Sites, Users, and Accounts tables.
Guard rails per entity:
- Items: blocked by non-draft PO line items; nulls draft references,
cascades inventory, consumption, and vendor prices
- Sites: blocked by non-draft POs; nulls draft PO siteId and vessel
siteId, cascades inventory and consumption
- Vessels, Accounts: blocked by any PO (FK is non-nullable)
- Users: blocked by any submitted PO; cascades notifications
UI: inline two-step confirm ("Delete X? Confirm / Cancel") using the
shared ConfirmDeleteButton component; errors surface inline.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace manual lat/lng fields in the vendor form with a single
Pincode field; location is auto-filled from the GST GSTIN lookup
- On save the Server Action geocodes the pincode via Nominatim and
caches lat/lng in the DB so distance queries stay fast
- Add deleteVendor action: blocked by non-draft POs; nulls out draft
PO vendorId and Product.lastVendorId; cascades ProductVendorPrice
- Add ConfirmDeleteButton shared component (inline two-step confirm)
Migration: 20260514091124_vendor_pincode
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Default port changed 3002 → 3003 in the GstService and both proxy
routes. The vendor-form was reading `captchaB64` from the API
response but the GstService returns `captchaBase64`, so the CAPTCHA
image was never displayed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Permissions:
- Add manage_products to MANAGER (alongside existing manage_vendors)
Sidebar:
- Add Items link for MANAGER under main nav (alongside Vendors)
Vendor list (/admin/vendors):
- Name is now a link to /admin/vendors/[id]
- Show item count column
Vendor detail (/admin/vendors/[id]):
- Vendor info card (GSTIN, address, contact)
- Items Supplied table: name (links to item detail), code, last price, updated
- Recent Purchase Orders table
Item list (/admin/products):
- Name is now a link to /admin/products/[id]
- Show vendor count column; reorder columns (name first)
- Add/Toggle buttons shown only for ADMIN
Item detail (/admin/products/[id]):
- Price summary cards (vendor count, lowest price, highest price)
- Available From table: vendor (links to vendor detail), vendor ID,
verified badge, price (lowest highlighted in green), last updated
Both detail pages cross-link to each other.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Schema:
- Add ProductVendorPrice table (productId, vendorId, price, updatedAt)
with unique constraint on (productId, vendorId)
Payment action (markPaid):
- Auto-create Product for any unlinked line item (matched by name
case-insensitively, or created fresh with auto-generated code)
- Link POLineItem.productId for newly matched/created products
- Upsert ProductVendorPrice for the PO vendor + unit price
- Always update Product.lastPrice / lastVendorId as denormalized cache
Search API:
- Include vendorPrices[] in results (vendorId, vendorName, price)
Editor dropdown:
- Show per-vendor prices below item name when available
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add POLineItem.name column; migrate existing description→name; description is now optional
- NameCell component: name input with fuzzy product search, description input stacked below
- Read-only view shows name prominently, description in subdued text below
- All server actions (create, edit, manager edit, import) updated to read/write name
- ParsedImportLine.description renamed to .name throughout import parser and form
- Seed data updated; CLAUDE.md added
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Parser extraction:
- Move parseSheet/parseWorkbook/cellStr/cellNum to lib/po-import-parser.ts
so they can be unit-tested without HTTP overhead
- Route now re-exports types and delegates to the lib
Unit tests (165 total, all passing):
- permissions.test.ts: +15 cases covering MANAGER create_po/submit_po/
manage_vendors, ACCOUNTS manage_vendors, AUDITOR all-denied, ADMIN
operational denial, SUPERUSER no manage_vendors
- po-state-machine.test.ts: +12 cases covering MANAGER submit from DRAFT
and EDITS_REQUESTED, ACCOUNTS provide_vendor_id, AUDITOR/ADMIN denied
on all transitions
- po-import-parser.test.ts (new, 32 cases): cellStr/cellNum edge cases;
parseSheet against real Sample_PO.xlsx (1 line item, correct values,
T&C not included, vendor/quotation/T&C extraction); synthetic sheet
edge cases (GST normalisation, INSTRUCTIONS stop, zero-price skip,
empty rows); parseWorkbook happy path and empty-workbook
Integration tests (new files):
- discard-po.test.ts: owner/MANAGER/SUPERUSER can discard; ACCOUNTS and
non-owners denied; status guard blocks non-DRAFT; cascade cleanup of
POActions and POLineItems verified in DB
- vendor-approval.test.ts: approval blocked without vendor; approval
succeeds with vendor; ACCOUNTS can provideVendorId; unverified vendor
rejected; AUDITOR denied; wrong-status denied
- manager-po-creation.test.ts: MANAGER creates DRAFT and submits; stores
correct submitterId; can discard own draft; ACCOUNTS denied; unauth
returns Unauthorized
- products-search.test.ts: 401 unauth; min-length validation; search by
name/code/description; case-insensitive; max 10 results; lastPrice as
number; inactive products excluded
- import-api.test.ts: 401 unauth; 403 for TECHNICAL and ACCOUNTS; 400
no file; 400 invalid binary; 200 for MANAGER with Sample_PO.xlsx;
correct line item values; T&C absent from results; vendor/PI extracted
Spec/TEST_PLAN.md (new):
- Testing strategy, stack, and environment setup
- Coverage matrix across unit/integration/E2E layers
- Permission test matrix for all 7 roles × 15 operations
- Feature-level scenario index (F-01 through F-06) with IDs mapping to test files
- Known gaps and out-of-scope items
- Authoring conventions (PREFIX isolation, negative-first, no any)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Row 26 ("INSTRUCTIONS TO VENDORS") had an empty col-1 so the loop
continued into the numbered T&C rows (27-33), which all have text in
col-1 and were mistakenly added as line items.
Two guards added:
1. Break immediately when col-0 contains "INSTRUCTION" (catches the
section header even though col-1 is empty).
2. Skip any row where both qty and unitPrice are 0 (belt-and-suspenders
for T&C rows that might slip through in other PO layouts).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a "Discard" button to the PO detail header for any DRAFT PO.
Submitters, managers, and superusers can discard. The action deletes
the PO and its related actions/notifications, then redirects to
/my-orders. Non-cascade child records (POAction, Notification) are
explicitly deleted in a transaction before the PO row is removed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Permissions & access:
- MANAGER gains create_po, submit_po, edit_own_draft_po, view_own_pos, manage_vendors
- ACCOUNTS gains manage_vendors
- ACCOUNTS added to provide_vendor_id state transition
- MANAGER added to DRAFT/EDITS_REQUESTED submit allowed roles
- canProvideVendorId now includes ACCOUNTS and any MANAGER/SUPERUSER
Vendor required for approval:
- approvepo() now returns error if po.vendorId is null
- Approval page shows danger banner when vendor is missing
Navigation:
- MANAGER gets "New PO", "My Purchase Orders", "Import PO", "Vendors" nav items
- ACCOUNTS gets "Vendors" nav item
Seed data:
- Vendors: 12 total (up from 3), with GST, address, contact details
- Products: 25 total (up from 4), with lastPrice pre-populated
Product fuzzy search in line items editor:
- Typing ≥2 chars in description fetches /api/products/search?q=
- Dropdown shows code, name, description, last price
- Selecting a product auto-fills description and unit price
- Linked items show a "✓ linked" indicator
- productId passed through FormData to createPo action and stored on POLineItem
Excel PO import (/po/import):
- MANAGER, SUPERUSER, ADMIN can access
- Uploads .xlsx file to /api/po/import which parses the Pelagia PO format
- Extracts vendor, line items, quotation ref, T&C, delivery address
- Preview step: user selects vessel + account, auto-matches vendor by name
- Confirmed import creates PO in DRAFT status
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Convert quantity, unitPrice, totalPrice, gstRate, and totalAmount to
plain numbers in server pages before passing to client components,
preventing Next.js serialization errors on Decimal objects.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
font-mono (monospace) renders optically larger than the surrounding
sans-serif at the same em size. Removing it from quantity, unit price,
taxable, GST% and total cells in both read-only and edit mode makes
all numbers visually consistent with the rest of the page.
formatCurrency reverted to Intl.NumberFormat with style:currency (the
original implementation); the spacing issue the user saw was font-related,
not symbol-related.
formatCurrency: manually prepend Rs symbol instead of using Intl currency
style, which could inject a non-breaking space between the symbol and number.
po-detail vendor section: replace font-mono on Vendor ID with font-medium
text-neutral-900 so it matches all other detail fields. Not assigned
indicator changed from italic warning text to a small pill badge.
GSTIN keeps mono (it is a standardised code) but now has correct text colour
and tracking for legibility.
Replaces the line-items-only editor with a full ManagerEditPoForm that
covers every field: title, vessel, account, vendor, project code, delivery
date, PI/Quotation No+Date, Requisition No+Date, place of delivery, all
structured T&C fields, and line items (with GST rate).
Edit toggle is amber-styled to distinguish manager changes from submitter
input. On save, a complete snapshot of original values is written to the
audit trail (MANAGER_LINE_EDIT action with metadata.original).
managerEditPo server action: validates manager permission, checks
status == MGR_REVIEW, recalculates totalAmount as grand total including
GST, and persists all updated fields.
Approval detail page now fetches vessels/accounts/vendors to populate the
edit form dropdowns.
link-document server action attaches an uploaded file to a PO after creation.
requirements.txt lists Python packages used for standalone PO generation scripts.
GET /api/po/[id]/export?format=pdf — HTML print page; company header, PO meta grid,
vendor block with GSTIN, line items table with taxable/GST%/total columns,
totals (taxable subtotal, GST, grand total), numbered T&C list, dual signature block.
GET /api/po/[id]/export?format=xlsx — SheetJS workbook matching Sample_PO.xlsx column layout.
Export PDF / Export XLSX buttons added to PO detail header.
Products: code, name, description; lastPrice and lastVendor are read-only.
On markPaid: for each line item linked to a product, Product.lastPrice and
lastVendorId are updated automatically and logged as PRODUCT_PRICE_UPDATED.
Users: CRUD with role assignment, bcrypt password, cannot deactivate own account.
Vendors: CRUD with address, GSTIN, contact mobile; isVerified set when vendorId provided.
Vessels: CRUD with IMO number uniqueness check.
Accounts: CRUD with unique account code.
Full audit list of all POs (latest 200). Filters: date range, vessel, status.
CSV export respects active filters. PDF export renders print-optimised HTML table.
My Orders: all submitter POs grouped as open/past with live status, manager note inline.
Receipt: upload receipt file, optional notes; confirms delivery and closes PO to CLOSED.
Step 1 (process): MGR_APPROVED → SENT_FOR_PAYMENT, notifies submitter and managers.
Step 2 (mark paid): SENT_FOR_PAYMENT → PAID_DELIVERED, stores paymentRef.
On mark paid: auto-updates Product.lastPrice and lastVendorId for any line items
linked to a product code; logs PRODUCT_PRICE_UPDATED action.
Approval queue: paginated list with search (PO number, vessel, submitter, date range).
Decision actions: approve, approve with note, reject (with reason), request edits,
request vendor ID.
Manager line edit: amend line items during review; original snapshot saved to audit
trail; diff shown with amber strikethrough on PO detail.
Detail: order info, vendor (address/GSTIN/contact), line items with GST breakdown,
structured T&C, attachments, activity trail, Export PDF/XLSX buttons.
Vendor ID form: inline on PO detail when status is VENDOR_ID_PENDING.
Edit: pre-populated form for DRAFT and EDITS_REQUESTED; resubmit transitions to MGR_REVIEW.
Form sections: order info, quotation reference (PI No/Date), requisition (No/Date),
place of delivery, line items (UoM dropdown, size, GST% per item), vendor, T&C, attachments.
Line items editor: add/remove rows, GST dropdown (0/5/12/18/28%, default 18%),
live taxable/GST/grand-total breakdown.
T&C: fixed line 1, individual inputs for Delivery, Dispatch, Inspection,
Transit Insurance, Payment Terms, Others.
Save as draft or submit directly for approval (→ MGR_REVIEW).
Submitter: open PO count, recent orders table, New PO CTA.
Manager: approvals count, approved PO listing, spend by vessel and month bar charts (Recharts).
Accounts: payment queue total value, ready-for-payment count.
Admin/Auditor: total PO count card.
Sign API returns presigned upload URL + storage key.
Dev: files served through auth-gated /api/files/dev route with path-traversal protection.
Prod: R2 presigned URLs for upload and time-limited download.
7 event templates: po-submitted, po-approved, po-rejected, edits-requested,
vendor-id-needed, payment-processed, receipt-confirmed.
Notifier uses Resend in production and console.log in development.
10 statuses, 11 transitions. Each transition declares allowedRoles,
requiresNote flag and sideEffects (which email groups to notify).
Helpers: getTransition, canPerformAction, getAvailableActions, requiresNote.