Audience
Procurement, Owner, Admin
Purchasing
Purchasing is suppliers, supplier prices, and purchase orders. A supplier price says what a supplier charges for a SKU and unit. A purchase order (PO) is the document that orders stock from a supplier. This chapter explains all three, plus the exact order in which a PO line price is resolved.
1. The short version
- Suppliers live under Procurement > Suppliers. There is no active/inactive switch. To stop ordering, delete the supplier.
- Supplier prices live inside the product form’s Cost section. They feed the PO line price. They never change standard cost.
- Purchase orders are created DRAFT, issued, then received. PO lines cannot be edited after creation. A typo on a line means cancel and recreate the PO.
- Issuing a PO adds ON ORDER stock. It does not change cost.
- PO line price order: supplier same-unit, supplier other-unit converted, last price paid, standard cost, unpriced.
2. Suppliers
Open Procurement > Suppliers. The context label reads “Vendor Master”, the heading “Suppliers”, and the intro “Manage vendor profiles and contact details.”
The list columns are Code, Company Name, Contact, Email, Phone, Address, Actions.
- Buttons: Template, Export CSV, Import CSV, Add Supplier.
- Search placeholder: “Search by supplier code, name, email, or phone…”
- Loading: “Loading suppliers…”. Empty: “No suppliers found” plus “Try adjusting your search or add a new supplier profile.”
- Row action: Delete (destructive). Clicking a code or name opens the detail.
Add and edit
The add drawer is titled “Add New Supplier”. The edit drawer is titled “Edit Supplier”. Buttons: Cancel plus Create Supplier / Save Changes.
Fields: Supplier Code (required, max 50, read-only when editing), Company Name (required, max 200), Contact Person, Email Address (must be valid), Phone Number, Payment terms (select, “No default”), Custom days (only when terms are CUSTOM), Tax ID, Currency (placeholder “USD”), Street line 1, Street line 2, City, State, ZIP, Country, Legacy address (textarea, placeholder “Full postal address…”).
Validation: “Code is required (max 50 chars)”, “Company name is required (max 200 chars)”, “Please enter a valid email address”.
Save: “Supplier created successfully” / “Supplier updated successfully”. Save error: the server message, or “Error saving supplier”.
- Duplicate code answers 409 “Supplier with code {code} already exists”.
- Payment terms codes: NET_15, NET_30, NET_45, NET_60, COD, PREPAID, CUSTOM. Labels: “Net 15”, “Net 30”, “Net 45”, “Net 60”, “COD”, “Prepaid”, “Custom days”.
- Incoterm codes: NONE, FOB, CIF, EXW, DDP, FCA.
Supplier detail
The detail shows the supplier name as heading, with the code below, and buttons Edit Supplier / Delete. Loading: “Loading supplier…”. Not found: “Supplier not found.”
- Supplier Details fields: Supplier Code, Company Name, Contact Person, Email Address, Phone Number, Payment terms (with “(N days)” when CUSTOM), Tax ID, Currency, Address.
- Legacy address shows a “(legacy)” badge plus “Graduate this to structured fields when you edit. The original text is kept until you change it.”
- Items supplied: “Vendor item codes for this supplier. Edit codes in SKU Management.” Columns: SKU, Description, Vendor item code. Loading: “Loading items supplied…”. Empty: “No vendor item codes for this supplier.”
- Tabs: Purchase Orders (“All purchase orders placed with this supplier.”) and Ordered Products.
- Purchase Orders tab columns: PO Number, Status, Order Date, Expected Date, Lines, Ordered Qty. Loading: “Loading purchase orders…”. Empty: “No purchase orders yet”.
- Ordered Products tab columns: Product, Unit Price, Qty Ordered, Last Order, Last PO, Orders. Search: “Search by SKU, item code, or description…”. Loading: “Loading ordered products…”. Empty: “No ordered products found”.
Delete
Delete asks: Are you sure you want to delete supplier “{name}”? This cannot be undone. Confirm Delete, cancel Cancel.
- Success: “Supplier deleted successfully”. Error: “Error deleting supplier”.
- Delete is a soft delete. It is blocked by any non-CANCELLED PO (400 “Cannot delete supplier with active purchase orders”). It is blocked by vendor item codes (400 with a message listing the SKUs).
- Delete needs Owner or Admin. The Delete button is shown to every role with Procurement view, but the server refuses other roles with 403.
Import and export
- Import result: “Suppliers imported: {inserted} created, {updated} updated”. Import failure reports the number of row errors.
- Template download: “Import template downloaded” / “Failed to download import template”.
- Export: “Suppliers exported” / “Failed to export suppliers”.
3. Supplier prices
Supplier prices live inside the product form’s Cost section (only when the user holds Catalog cost_view; editing needs Catalog cost_manage). Section intro: “Price this supplier charges for this SKU and unit.”
Form: Supplier select, UOM select, Price, Minimum quantity (minimum 1, default 1), “Add row” / “Replace” button.
- Replace confirm: “Replace the open supplier price with this row. The previous row ends the day before.”
- “Show ended prices” / “Hide ended prices” toggle. Table: Supplier, UOM, Price, Minimum quantity, Valid from, Valid to. Open rows show “Open” for Valid to. Row actions “Replace” / “End” (only on open rows, only when the user can manage).
- Success: “Supplier price added.” / “Supplier price replaced.” Errors: “Could not save the supplier price.” / “Could not end the supplier price.”
Rules:
- The UOM must be the base UOM, PCS/EACH, or an active conversion, else 400 “No active {uomCode} conversion for {sku}”.
- Overlapping range (same supplier + UOM + minimum quantity + overlapping dates) answers 409 “Supplier price overlaps {code} {uomCode}, minimum {minQty}, {from} through {to or open}”.
- Duplicate key answers 409 “A supplier price already exists for {sku} {uomCode}, minimum {minQty}, from {date}”.
- Amount must be a number, rounded to 4 decimals, greater than 0, else “Amount must be a number” / “Supplier price must be greater than 0” / “Amount is too large”.
- End sets the end date to the end of the current company day. Replace ends the old row the day before and creates the new row in one transaction.
- Supplier prices do NOT change standard cost. They only feed the PO line price resolution.
4. Purchase orders
Open Procurement > Purchase Orders. The context label reads “Procurement”, the heading “Purchase Orders”, and the intro “Track and manage inventory procurement orders.” Button: Create Purchase Order.
The list columns are PO Number, Supplier, Status, Order Date, Expected Date, Items / Ordered Qty, Actions. Each row shows “{n} unique products” plus “Total: {cases} Cs / {pieces} Pcs”. Row action: View.
- Search: “Search by PO number, supplier code, name, or status…”
- Loading: “Loading purchase orders…”. Empty: “No purchase orders found” plus “Procure inventory by creating your first purchase order.”
- An autosaved draft row shows a draft PO form with Resume (primary) and Discard (menu). Discard asks: “Discard this unsaved purchase order? This cannot be undone.” / “Discard” / “Cancel”.
Create
The form: context “Procurement”, heading “New Purchase Order”, subtitle “Initialize a new inventory procurement draft.”
Header fields: Purchase Order Number (placeholder “Assigned when you save”, hint “Leave blank and the server assigns the next number.”), Supplier Company (picker, “Search suppliers”), Payment terms (“Inherit / none”), Expected Delivery Date, Custom days (only when CUSTOM), Warehouse (“Select warehouse…”), Ship to (“Select ship-to address”), Bill to (“None”), Incoterm, Ship method, Supplier reference (placeholder “Quote or vendor order #”).
- Warehouse without an address: “{code} has no address. Add it in Administration > Warehouses.”
- Lines editor columns: #, SKU / Item, Description, Qty, UOM, Unit Price, Amount, Line date, Action. Waiting row: “Search SKU / item / barcode”, “Description”, “Remove”. UOM error offers “Configure package conversion”.
- Line price caption (purchase only): the resolved price source, for example “Supplier price $X / CS” or “Last price paid $X / CS (PO-123)”. The caption shows only to cost_view holders. Everyone who can edit a PO gets amount and UOM.
- Notes: Internal memo (“Add shipment requirements, vendor references, etc.”) and Vendor notes (“Visible to the supplier”).
- Summary: “Total Lines:”, “Total Cases:”, “Total Pieces:”, “Est. Total Cost:”. Buttons Cancel plus Create Draft PO / “Creating Order…”.
- Create success: “Purchase Order created in DRAFT”, then the app shows the PO detail. Create error fallback: “Error creating purchase order”.
- Create validation: “At least one product line is required”, “Each line must order at least one piece”.
Create rules:
- Duplicate PO number answers 409 “PO with number {n} already exists”. Supplier must exist.
- Payment terms inherit from the supplier when the PO does not set them. CUSTOM days inherit likewise.
- Warehouse defaults to the default warehouse. It must be active (“Warehouse {code} is inactive”) and must have an address unless explicitly allowed. Ship-to defaults to the warehouse address. Bill-to defaults to the default billing address.
- Expected dates (header and lines) must not be before today.
- PO number is server-assigned when blank. Line prices are persisted as entered or resolved. Quantities normalize: PCS/EACH to pieces; CASE/MASTER_CASE with factor greater than 1 to cases. Other units keep the entered pieces and cases with no normalization.
Detail
The detail: context “Procurement”, heading = PO number plus status chip, “Created on {date}”. Loading: “Loading purchase order details…”. Not found: “Purchase Order not found.” plus “Go back to list”.
Actions: Print menu (“Print Documents”, “Print PO”); DRAFT: Issue Order plus Cancel Order; ISSUED or PARTIALLY_RECEIVED: Start Receiving; ISSUED: Cancel Order; RECEIVED or PARTIALLY_RECEIVED with a receipt: Open Receipt; while updating: “Processing status update…”.
- Order Information: Supplier Vendor (name, “Code: {code}”, Contact, email, phone), Expected Delivery (“Not Scheduled” when empty), Order Date, Payment terms (with “(N days)” when CUSTOM), Supplier reference, Incoterm / method, Buyer, Warehouse, Ship to, Bill to, Vendor notes, Internal memo.
- DRAFT only: Edit header opens a form with Expected date, Payment terms, Warehouse, Ship to, Bill to, “Save header” / “Saving…” / “Cancel”. Save success: “Purchase order header updated”. Save error: the server message, or “Error updating purchase order”.
- Inbound Shipment panel: linked shipment number plus status plus carrier, or “Not linked to an inbound shipment.” / “No carrier”.
- Financial Summary: “Estimated Total Cost”, “Unique items:”, “Ordered Volume” (Ordered Cases, Base UOM Qty). When RECEIVED or PARTIALLY_RECEIVED: “Received Volume” (Received Cases, Base UOM Qty).
- Linked Receipts (when RECEIVED or PARTIALLY_RECEIVED, or receipts exist): Receipt #, Received At, Status, Items, Actions; “Open Latest Receipt”; loading “Loading linked receipts…”; empty “No linked receipts yet.”
- Itemized Line Details: Product Details (SKU plus description), Unit Price, Ordered Cases, Base Qty, Received Cases plus Received Base Qty (only when RECEIVED or PARTIALLY_RECEIVED), Est. Line Total, Line date (with “own” badge when the line owns its date; line date falls back to the PO expected date).
Statuses
PO statuses: DRAFT, ISSUED, PARTIALLY_RECEIVED, RECEIVED, CANCELLED.
- Issue (DRAFT to ISSUED): dialog “Issue Purchase Order”, message “Are you sure you want to issue {poNum}? This will set status to ISSUED and increment ON ORDER stock balances in the warehouse.” Issue adds ON ORDER pieces and cases per line and writes an ORDER / ON_ORDER inventory movement per line. Issuing does NOT change cost.
- Cancel (ISSUED to CANCELLED): dialog “Cancel Purchase Order”, message “Are you sure you want to cancel {poNum}? If ISSUED, this will reverse the ON ORDER stock balances. This action is permanent.”, type danger. Cancel reverses ON_ORDER (floored at 0) and writes a negative ORDER / ON_ORDER movement per line.
- Cancel (DRAFT to CANCELLED): header flip only. No ON_ORDER to reverse.
- Status success: “Purchase order status updated to {status}”.
- CANCELLED POs cannot be edited: “Cancelled purchase orders cannot be edited”.
- PO lines cannot be edited after creation.
- Receiving comes from receipts, not from the status API. “Start Receiving” opens the receipt form with the PO preselected.
5. PO line price order
When a PO line opens, the price resolves in this exact order. “Supplier same-unit” means: a supplier price row whose unit equals the PO line’s unit, date-effective today, with minimum quantity at or below the line quantity.
- Supplier price, same unit. Among qualifying rows in the line unit, the greatest minimum quantity wins.
- Supplier price, other unit, converted. Among qualifying rows in other units, the greatest minimum quantity wins, then the larger pack (larger conversion factor). Converted to the line unit.
- Last price paid, converted. Last commercial unit price on an ISSUED, PARTIALLY_RECEIVED, or RECEIVED line for the same product and supplier. DRAFT and CANCELLED do not count. Ranked by PO order date desc, then PO created date desc, then line created date desc. Zero or negative last price is ignored.
- Standard cost, converted. Product unit cost in its cost unit (or base unit), converted to the line unit. Zero or empty cost is skipped.
- Unpriced. Amount 0.0000, caption empty. The operator types the price.
Rules:
- If the requested unit has no conversion, the line is UNPRICED (no fallback).
- When the line opens with no unit chosen, it opens in the winning row’s own unit: supplier row (greatest minimum, then larger pack), else last-paid unit, else standard-cost unit, else unpriced in the base unit.
- Captions: same unit, “Supplier price $X / CS”; converted, “Supplier price $X / MC = $Y / CS”; last paid, “Last price paid $X / CS (PO-123)”; standard cost, “Standard cost $X / CS”. Unit shorts: MC, DZ, CS, PLT, PCS.
- The caption and source are returned only to cost_view holders. Everyone who can edit a PO gets amount and UOM.
- This matches the pricing chapter’s PO line price order (“1. That supplier’s price for the same unit. 2. That supplier’s price for another unit of the same product, converted. 3. The last price paid to that supplier on an issued or received purchase order. 4. Standard cost. 5. Unpriced. Type the price.”), with the exact statuses ISSUED, PARTIALLY_RECEIVED, RECEIVED.
6. Who can do what on the purchasing screens
The permission registry is the authority for this table.
| Role | Can open | Can do |
|---|---|---|
| OWNER | Suppliers, Purchase Orders, PO detail, supplier prices on the product form | Procurement view, create, edit, approve, import, export, admin; issue and cancel POs (approve); CSV import/export; supplier delete (via admin); supplier prices (via Catalog cost_view/cost_manage); price captions (via cost_view) |
| ADMIN | Same as OWNER | Same as OWNER |
| MANAGER | Suppliers, Purchase Orders, PO detail | Procurement view, create, edit, approve, import, export: create/edit POs, issue and cancel POs, CSV import/export. NO supplier delete (no delete token). NO supplier prices (no Catalog cost_view). NO price captions (no cost_view; gets amount and UOM only). |
| PROCUREMENT_MANAGER | Suppliers, Purchase Orders, PO detail, supplier prices on the product form | Same as MANAGER, PLUS supplier prices (Catalog cost_view/cost_manage) and price captions (cost_view). NO supplier delete (no delete token). |
| READ_ONLY | Suppliers, Purchase Orders, PO detail | Procurement view only: read-only. Cannot create, edit, issue, cancel, import, export, or delete. No supplier prices, no captions. |
| ANALYST | Suppliers, Purchase Orders, PO detail | Procurement view, export: read plus CSV export. Cannot create, edit, issue, cancel, import, or delete. No supplier prices, no captions. |
| ACCOUNTING_MANAGER | No Procurement section, no access to Suppliers or POs | No Procurement tokens. CAN see supplier prices on the product form via direct link (Catalog cost_view/cost_manage). Cannot open a PO at all (PO routes require Procurement). |
| SALES_MANAGER | No Procurement section, no access | No Procurement tokens. No supplier prices (no cost tokens). |
| WAREHOUSE_SUPERVISOR | No Procurement section, no access | No Procurement tokens. |
| WAREHOUSE_OPERATOR | No Procurement section, no access | No Procurement tokens. |
| INVENTORY_CLERK | No Procurement section, no access | No Procurement tokens. |
| MARKETPLACE_MANAGER | No Procurement section, no access | No Procurement tokens. |
7. Quick answers
I cannot open Suppliers. Suppliers needs Procurement view. Warehouse, inventory, accounting, sales, and marketplace roles have no Procurement access. Ask an admin.
I cannot deactivate a supplier. There is no active/inactive switch. To stop ordering, delete the supplier. Delete is blocked by any non-CANCELLED PO or by vendor item codes.
I cannot delete a supplier. Delete needs Owner or Admin. The button is shown to every role with Procurement view, but the server refuses other roles.
I cannot edit a PO line. PO lines cannot be edited after creation. Cancel and recreate the PO.
The PO line shows no price. The line is unpriced: no supplier price, no last paid price, no standard cost, or the unit has no conversion. Type the price.
The price caption is missing. Captions show only to cost_view holders (Owner, Admin, Accounting Manager, Procurement Manager). Everyone else gets amount and UOM.
Issuing a PO changed my stock. Correct. Issuing adds ON ORDER stock. It does not change cost.
Receiving changed my cost. Receiving does not change standard cost. Standard cost changes only through “Use as standard cost” on the product form. “Use as standard cost” copies the current landed cost basis into standard cost. It needs the profit calculator turned on.