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

Returns and Credit Notes

Look up invoices by number, post partial returns with restocking, and issue credit notes.

You process every return through apps/server/src/features/returns/route.ts, and the credit note numbering lives in apps/server/src/features/returns/creation/queries.ts.

Log in first

All return endpoints require authentication and are scoped to your branch. Log in once first.

TOKEN=$(curl -s -X POST http://localhost:8000/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"admin@demo.pharmaflow","password":"Demo@1234"}' | python3 -c "import sys,json; print(json.load(sys.stdin)['token'])")

Look up the invoice

You start with GET /returns/lookup/:invoiceNumber, which finds the sale in your branch and annotates every line with already_returned and returnable_qty so you never refund the same unit twice.

curl http://localhost:8000/api/v1/returns/lookup/INV-BR001-000123 \
  -H "Authorization: Bearer $TOKEN"

An invoice from another branch returns 404 on purpose, because the lookup filters on your own branch_id.

Post a return

You post POST /returns with sale_id and one entry per line you accept back. Each entry carries sale_item_id, quantity, an optional restockable flag defaulting to true, and an optional per-line reason.

curl -X POST http://localhost:8000/api/v1/returns \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "sale_id": "SALE_ID",
    "reason": "Customer brought back unopened pack",
    "items": [{"sale_item_id":"SALE_ITEM_ID","quantity":1,"restockable":true}]
  }'

The server rejects zero or negative quantities and any quantity above the remaining returnable_qty for that line, so partial returns across several visits stay consistent.

How the refund is computed

You do not compute the refund on the client. The server refunds unit_price times quantity plus the line VAT for every accepted line, then sums those into total_refund. Restockable lines add the quantity back to the original batch and write a RETURN row into the stock_movements ledger, and the medicine stock snapshot is re-synced for the branch. Non-restockable lines skip the stock write but still refund the customer.

Credit notes and credit customers

Every return mints one credit note numbered CN-BRANCH-YEAR-NNNN via nextCreditNoteNumber, for example CN-BR001-2026-0007, linked to the original invoice UUID. If the original sale was a credit sale, the server also lowers the customer balance toward zero and writes a REFUND row into customer_ledger referencing the return.

List and inspect returns

You list returns for your branch with optional sale_id filtering and pagination, then fetch one return with its lines and credit note number.

curl "http://localhost:8000/api/v1/returns?page=1&page_size=20" \
  -H "Authorization: Bearer $TOKEN"
curl http://localhost:8000/api/v1/returns/RETURN_ID \
  -H "Authorization: Bearer $TOKEN"

Next steps

Re-read the original tender in /sales/selling-at-the-counter, apply a goodwill coupon from /sales/coupons-and-discounts, and reconcile the refunded cash in /sales/sessions-and-parked.

Last updated on October 20, 2026