Skip to content
PharmaFlow Knowledgebase
Esc
↑↓navigate↵open⌘Jpreview
On this page

Purchasing and Purchase Orders

Receive batches directly, run the draft to received purchase order flow, and read the stock movement ledger.

You bring stock into a branch in two ways, both defined in apps/server/src/features/purchases/route.ts and apps/server/src/features/purchase-orders/route.ts: a direct batch receipt, or a formal purchase order that ends in batch receipts.

Receive a batch directly

Post to POST /purchases when stock arrives without a purchase order. You must send supplier_id, medicine_id, branch_id, batch_number, expiry_date, quantity, and unit_cost. The handler inserts one active batch, then writes an IN row into the stock movements ledger with reference_type: purchase, and the medicine stock is kept accurate as a ledger sum, not a stored column.

curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"supplier_id":"'"$SUPPLIER_ID"'","medicine_id":"'"$MEDICINE_ID"'","branch_id":"'"$BRANCH_ID"'","batch_number":"B-2026-041","expiry_date":"2027-06-30","quantity":100,"unit_cost":"8.250"}' \
  "http://localhost:8000/api/v1/purchases"

You need admin, pharmacist, inventory_manager, or branch_manager for this call. List receipts with GET /purchases using status, search, supplier_id, branch_id, medicine_id, from_date, and to_date. Written-off batches are hidden unless you pass status=written_off.

Run the purchase order flow

Create a DRAFT order with POST /purchase-orders using the schema in apps/server/src/features/purchase-orders/schemas.ts: supplier_id, branch_id, optional expected_date and notes, plus an items array of medicine_id, ordered_qty, and agreed_unit_cost.

curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"supplier_id":"'"$SUPPLIER_ID"'","branch_id":"'"$BRANCH_ID"'","items":[{"medicine_id":"'"$MEDICINE_ID"'","ordered_qty":200,"agreed_unit_cost":8.1}]}' \
  "http://localhost:8000/api/v1/purchase-orders"

Move the order with PUT /purchase-orders/:id/status to SENT or CANCELLED. A RECEIVED order is terminal and a cancelled one cannot change. Receive it with POST /purchase-orders/:id/receive, sending per-line batch_number, expiry_date, qty_received, and unit_cost; each line becomes a batch plus an IN movement with reference_type: purchase_order, and the order flips to RECEIVED.

Plan ordering from low-stock suggestions

Call GET /purchase-orders/suggested?branch_id=$BRANCH_ID before you draft an order. It returns medicines at or below threshold with suggested_quantity = max((threshold - stock) * 2, 10) plus the most reliable active supplier. Paste those lines straight into your POST /purchase-orders body.

curl -H "Authorization: Bearer $TOKEN" "http://localhost:8000/api/v1/purchase-orders/suggested?branch_id=$BRANCH_ID"

Read the movement ledger and write off stock

Query GET /purchases/movements/all with medicine_id, branch_id, movement_type, and search to audit every IN, OUT, ADJUST, RETURN, and WRITE_OFF entry. When a batch is unsellable, call PUT /purchases/:batchId/write-off: it zeroes qty_remaining, marks the batch written_off, and writes a negative WRITE_OFF movement, and the medicine stock total falls out of the ledger automatically.

curl -X PUT -H "Authorization: Bearer $TOKEN" "http://localhost:8000/api/v1/purchases/$BATCH_ID/write-off"

Inspect one receipt

Fetch GET /purchases/:batchId when a pharmacist questions a delivery. The response joins the medicine name and supplier name to the batch row so you can confirm batch_number, expiry_date, quantities, and unit_cost in one call.

curl -H "Authorization: Bearer $TOKEN" "http://localhost:8000/api/v1/purchases/$BATCH_ID"

Compare qty_received against qty_remaining to see how much of that batch has already sold. Use the batch id as the reference_id when you search the movement ledger for its full history.

Next steps

Go to /inventory/transfers-and-stockcount to move received stock between branches, and to /inventory/alerts-and-expiry to catch batches approaching expiry before they need a write-off.

Last updated on October 20, 2026