---
title: Returns and Credit Notes
description: 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.

```bash
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.

```bash
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`.

```bash
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.

```bash
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.
