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.