Skip to content

Sales

Location: dg_smart_pos_api/modules/Sales. Owns sale transactions themselves: creating them, holding them as drafts at the POS, syncing ones made offline, confirming payment, and printing receipts. Stock deduction happens through App\Services\StockService; serialized-item tracking through App\Services\SerialNumberService. This module coordinates with both rather than owning stock or serials itself.

Sales is one of the modules under the core architecture change freeze: new features touching it are paused while the platform restructure is in progress; bug fixes and correctness work continue as normal.

  • Sale, the transaction record: receipt number, totals (cash, mpesa, bank, sale_total, broker_total), customer name/phone, tax flag, links to an original sale for returns.
  • DraftSale / DraftSaleStock, a held sale at the POS terminal (a “hold order”) and its line items, kept separate from Sale until committed.

A cashier can hold a sale mid-transaction and resume it later. posDraftIndex/posDraftStore/posDraftShow/posDraftUpdate/posDraftDestroy on SalesController manage these against the DraftSale model, entirely separate from the real sales table until committed via commitDraft.

SalesController::store (SalesStoreRequest) takes:

  • sale_by (required), sale_total (required), stocks (required array)
  • cash, mpesa, bank, broker_total, customer_name, customer_phone, ref_number (all optional)

Each entry in stocks needs id (stock ID) and sale_quantity/selling_price; optionally broker, upgraded/upgraded_to, serial (array), accompany_stocks (for restaurant-mode accompanying items).

offlineSync (OfflineSaleSyncRequest) exists specifically for the Electron desktop app: a cashier can keep selling with no network connection, and this endpoint reconciles a batch of locally-created sales once connectivity returns.

confirmPayment (ConfirmPaymentRequest) handles payment methods that resolve asynchronously (e.g. M-Pesa STK push). A sale can exist before its payment is confirmed.

All under auth:sanctum, prefix sales/stores/{store}, registered via modules/Sales/Routes/sales.php (required from routes/tenant.php):

Method Path Controller method
GET pos/draft-sales posDraftIndex
POST pos/draft-sales posDraftStore
GET pos/draft-sales/{draft} posDraftShow
PUT pos/draft-sales/{draft} posDraftUpdate
DELETE pos/draft-sales/{draft} posDraftDestroy
POST sales/offline-sync offlineSync
GET/POST/PUT/DELETE sales (apiResource) index/store/show/update/destroy
POST sales/{sale}/commit-draft commitDraft
POST sales/{sale}/confirm-payment confirmPayment
POST sales/{sale}/mark-receipt-printed markReceiptPrinted

The legacy v1_new_sales prefix (NewSaleController) is still live for returns/replacement (above). It is not used for creating, holding, or syncing sales.

The read-only performance/reporting endpoints referenced by salesPerformance.js on the frontend (get_user_performance, get_user_sales, get_user_invoices) are not on NewSaleController either, despite sharing the v1_new_sales URL prefix, they’re registered from modules/Analytics/Routes/analytics_routes.php against UserSalesController. See Analytics.

A sale list item:

{
"id": 1,
"sale_type": "normal",
"receipt": "A25A25-000001",
"broker_total": 0,
"mpesa": 1000,
"cash": 0,
"sale_total": 1000,
"customer_name": "John Doe",
"date": "2024-01-01",
"has_tax": false,
"original_sale": null,
"amount_from_previous_sale": 0,
"created_at": "2024-01-01T00:00:00.000000Z",
"new_sale_stocks_count": 2
}

A single sale additionally includes time, refund_cash, refund_mpesa, refund_total, new_sale_stocks, original_return_stocks, return_stocks, get_original_sale, return_sales.