4 Inventory and Catalogue
Hardik edited this page 2026-06-21 03:00:58 +05:30
This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

Inventory and Catalogue

This covers the product catalogue, per-vendor pricing, the cart, and the feature-flagged site-inventory surfaces.

Products

Two views over the same Product records:

  • /admin/products — the editable catalogue (MANAGER, ADMIN). Add a product (code, name, description), toggle Active/Inactive, delete.
  • /inventory/itemsread-only catalogue, available to all roles for PO creation. Expand a row to see per-vendor prices.

Both link to a shared item detail page. A product carries lastPrice and lastVendor (read-only — auto-populated on payment) and per-vendor history via ProductVendorPrice (one row per productvendor pair).

Auto-sync on payment confirmation

When a PO is marked paid, for each line item:

  • if it has a productId, set Product.lastPrice = line.unitPrice and Product.lastVendorId = po.vendorId;
  • upsert the (product, vendor) price into ProductVendorPrice;
  • log a PRODUCT_PRICE_UPDATED action.

Future POs using that product show the vendor's latest price as a hint in the line-item editor. (The original spec also described fuzzy-matching unlinked items into new products; price/vendor tracking is the shipped behaviour.)

/api/products/search powers the line-item name autocomplete: min 2 chars, case-insensitive match on name / code / description, max 10 results, inactive products excluded, lastPrice serialised as a plain number (not a Prisma Decimal).

Cheapest / ★ Closest tags

On the item detail and items pages, when a Site is selected each item's vendor list is annotated:

  • Cheapest — vendor with the minimum ProductVendorPrice.
  • ★ Closest — vendor nearest the selected site by geocoded distance (distanceKm).

These are computed independently, so both tags can appear simultaneously regardless of whether the list is sorted by Price or Distance. Selecting a site also auto-switches the active sort to Distance (a useEffect keyed on the site id resets it on every site change — important because Next.js soft navigation preserves React state). With no site selected, neither distance tag shows. See Testing for the specs pinning this down.

Cart

A persistent cart (lib/cart.ts) stored in browser localStorage under a fixed key, surviving navigation but local to the device/user. A cart-updated custom event lets components (e.g. the header cart icon with its count badge) react in real time.

Flow: add items from product/item detail → open /inventory/cart → adjust quantities, remove items, pick a delivery site → Create PO opens /po/new pre-filled with the cart line items and vendor/site.

Site inventory (feature-flagged)

Gated by NEXT_PUBLIC_INVENTORY_ENABLED (lib/feature-flags.ts): INVENTORY_ENABLED = process.env.NEXT_PUBLIC_INVENTORY_ENABLED !== "false" (i.e. on unless explicitly "false"). When off, site stock/consumption surfaces are hidden; the vendor/product catalogue and cart remain available.

  • Sites (/admin/sites) — ports/depots/offices that hold stock; geocoded from pincode; vessels can be associated.
  • ItemInventory — quantity per (product, site). Designed to be incremented at PO approval (not on close) for the ordered quantities, when the PO has a siteId. ⚠️ In production this rarely fires — POs are raised against a Vessel and carry no siteId, and the write isn't gated by the flag. See Inventory on Approval and Tech Debt.
  • ItemConsumption — daily draw-down per (product, site, date), recorded via the "Log Consumption" form on the site detail page (with recordedBy).

Site detail (/admin/sites/[id]) shows a stock bar chart, a 30-day consumption line chart, the inventory table, assigned vessels, and recent POs for the site.