Selling End to End
Open a session, park a cart, post an idempotent sale with DEMO10, tender cash, close out, and return part of it.
You run the full counter shift against the routes in apps/server/src/features/sessions/route.ts, apps/server/src/features/parked/route.ts, apps/server/src/features/sales/route.ts, and apps/server/src/features/returns/route.ts. You start by logging in and saving the token.
TOKEN=$(curl -s -X POST http://localhost:8000/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"pharm1@demo.pharmaflow","password":"Demo@1234"}' \
| python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")
Routes mount under the /api/v1 prefix that apps/server/src/app.ts applies with app.route("/api/v1", apiV1), so every path below sits at http://localhost:8000/api/v1/....
Open your cash session
You open one session per shift with POST /sessions/open. You pass your opening float and keep the returned session id for close-out.
curl -X POST http://localhost:8000/api/v1/sessions/open \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"opening_float":200}'
You check it any time with GET /sessions/current, and you review past shifts with GET /sessions/history.
Park an interrupted cart
You park a cart when the customer walks away with POST /parked. You recall it later with POST /parked/:parkedId/recall and void it with POST /parked/:parkedId/void.
curl -X POST http://localhost:8000/api/v1/parked \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"items":[{"medicine_id":"MEDICINE_ID","quantity":1,"unit_price":20}],"customer_name":"Walk-in"}'
Post the sale with DEMO10
You create the sale with POST /sales in apps/server/src/features/sales/route.ts. You send a fresh X-Idempotency-Key per new sale: replaying a key returns the stored response with 201 instead of charging twice, through claimIdempotencyKey and readIdempotencyResponse in apps/server/src/features/sales/idempotency/queries.ts. The DEMO10 coupon applies 10 percent off and the server uppercases the code before lookup in apps/server/src/features/sales/creation/service.ts.
curl -X POST http://localhost:8000/api/v1/sales \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: sale-0001" \
-d '{
"items": [{"medicine_id":"MEDICINE_ID","quantity":2,"unit_price":12.5}],
"payment_method": "cash",
"coupon_code": "DEMO10",
"customer_name": "Walk-in"
}'
You save the returned sale id and invoice_number. Inside one transaction the server prices VAT, deducts batches FEFO, writes OUT stock movements, and allocates the invoice number plus ICV, in apps/server/src/features/sales/creation/.
In the register UI the same sale is built from apps/register/components/sales/useSaleCart.ts state in apps/register/app/[locale]/pharmacist/sales/new/page.tsx. When the checkout request fails with a timeout or a network error, shouldQueueSaleLocally in apps/register/app/lib/offline-sale.ts passes and queueOfflineSale in apps/register/app/lib/tauri-sync.ts queues the sale on the device, ready for the offline-first flow.
Declare tender and close
You declare the drawer cash with POST /sessions/tender, sending declared_cash so the server computes the difference against expected takings. Then you close the shift with POST /sessions/close, which stamps closed_at and flips the status to CLOSED.
curl -X POST http://localhost:8000/api/v1/sessions/tender \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"declared_cash":1250.5,"notes":"Evening close"}'
curl -X POST http://localhost:8000/api/v1/sessions/close \
-H "Authorization: Bearer $TOKEN"
Pull the day numbers
You reconcile with the sales and VAT reports in apps/server/src/features/reports/route.ts. Both GET /reports/sales and GET /reports/vat allow only admin, branch_manager, or auditor roles, so you repeat the login as admin@demo.pharmaflow and use that token:
ADMIN=$(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)['access_token'])")
curl "http://localhost:8000/api/v1/reports/sales?from_date=2026-01-01&to_date=2026-12-31" \
-H "Authorization: Bearer $ADMIN"
curl "http://localhost:8000/api/v1/reports/vat?from_date=2026-01-01&to_date=2026-12-31" \
-H "Authorization: Bearer $ADMIN"
The window defaults to the last thirty days when you omit the dates.
Return part of the sale
You look up the invoice, then you post a partial return with POST /returns. You reference sale item ids with quantities, and the server restocks, writes the credit note, and records the refund.
curl http://localhost:8000/api/v1/returns/lookup/INV-001 \
-H "Authorization: Bearer $TOKEN"
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 changed mind","items":[{"sale_item_id":"ITEM_ID","quantity":1}]}'
Next steps
Read the pricing internals in /sales/selling-at-the-counter, handle prescriptions in /dispensing/prescriptions, and trace every step in /platform/audit-log.