POS Sales -- Auto-Post (مبيعات نقاط البيع)
POS Sales use a single-request flow that creates a sales invoice, auto-posts it (creating journal entries and deducting stock), and records payments -- all in one atomic transaction. This eliminates the multi-step draft-approve-post workflow used in the regular Sales module.
How It Works
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
pos_session_id | FK | yes | Active POS session ID |
customer_id | FK | yes | Customer (business partner) |
warehouse_id | FK | no | Override warehouse (defaults to session's warehouse) |
items | array | yes | Line items (min 1) |
items[].product_id | FK | yes | Product ID |
items[].product_variant_id | FK | no | Product variant ID |
items[].unit_id | FK | yes | Unit of measure |
items[].warehouse_id | FK | no | Per-item warehouse override |
items[].description | string | no | Custom line description |
items[].quantity | decimal | yes | Quantity (must be > 0) |
items[].unit_price | decimal | yes | Unit price |
items[].discount_percent | decimal | no | Line discount percentage (0-100) |
items[].tax_rate_id | FK | no | Tax rate to apply |
payments | array | no | Payment records |
payments[].method | string | yes | cash, card, credit, check |
payments[].amount | decimal | yes | Payment amount |
payments[].reference | string | no | Payment reference (e.g., card approval number) |
API Endpoint
| Method | Endpoint | Description | Permission |
|---|---|---|---|
POST | /api/pos/sales | Create a POS sale (auto-post) | sales.invoices.create |
Request / Response Examples
Create POS Sale
bash
curl -X POST /api/pos/sales \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"pos_session_id": 1,
"customer_id": 5,
"items": [
{
"product_id": 10,
"unit_id": 1,
"quantity": 2,
"unit_price": 15.500,
"discount_percent": 0,
"tax_rate_id": 1
},
{
"product_id": 22,
"unit_id": 1,
"quantity": 1,
"unit_price": 8.000
}
],
"payments": [
{ "method": "cash", "amount": 30.000 },
{ "method": "card", "amount": 9.000, "reference": "AUTH-12345" }
]
}'dart
final response = await dio.post('/api/pos/sales', data: {
'pos_session_id': 1,
'customer_id': 5,
'items': [
{
'product_id': 10,
'unit_id': 1,
'quantity': 2,
'unit_price': 15.500,
'discount_percent': 0,
'tax_rate_id': 1,
},
{
'product_id': 22,
'unit_id': 1,
'quantity': 1,
'unit_price': 8.000,
},
],
'payments': [
{'method': 'cash', 'amount': 30.000},
{'method': 'card', 'amount': 9.000, 'reference': 'AUTH-12345'},
],
});Response 201 Created
The response returns a full SalesInvoiceResource with loaded relationships including customer, items, payments, and journal entries.
json
{
"data": {
"id": 42,
"invoice_number": "INV-000042",
"receipt_number": "POS-2026-02-00015",
"receipt_barcode": "POSPOS20260200015",
"date": "2026-02-28",
"status": "posted",
"is_pos": true,
"pos_session_id": 1,
"pos_terminal_id": 1,
"subtotal": "39.000",
"discount_amount": "0.000",
"tax_amount": "1.550",
"total": "40.550",
"amount_paid": "39.000",
"balance_due": "1.550",
"customer": { "id": 5, "name": "Walk-in Customer" },
"items": [
{
"product_id": 10,
"quantity": "2.000",
"unit_price": "15.500",
"line_total": "31.000",
"tax_amount": "1.550"
},
{
"product_id": 22,
"quantity": "1.000",
"unit_price": "8.000",
"line_total": "8.000",
"tax_amount": "0.000"
}
],
"payments": [
{ "payment_method": "cash", "amount": "30.000", "status": "posted" },
{ "payment_method": "card", "amount": "9.000", "status": "posted", "reference": "AUTH-12345" }
]
}
}Auto-Post Flow (Detail)
The POS sale endpoint executes the following steps atomically within a database transaction:
Step 1: Create Invoice
- Status is set directly to
Approved(skipping draft/pending approval). is_pos = true,source = 'pos', andpos_session_id/pos_terminal_idare set.invoice_numberandreceipt_numberare auto-generated via SequenceService.
Step 2: Create Line Items
- Each item's discount, tax, and line total are calculated via
SalesCalculationService. - Warehouse defaults to the session's warehouse unless overridden per-item.
Step 3: Generate Receipt Barcode
- Format:
POS+ receipt number with dashes removed (e.g.,POSPOS20260200015).
Step 4: Post Invoice (PostSalesInvoice Action)
- Creates the revenue journal entry (debit AR, credit revenue/tax).
- Creates the COGS journal entry (debit COGS, credit inventory) for tracked products.
- Deducts stock via
StockService::decreaseStock().
Step 5: Create and Post Payments
- For each payment (except
creditmethod), aSalesPaymentis created and posted viaPostSalesPayment. - Receiving accounts are resolved from sales settings:
sales.cash_account_id,sales.card_account_id,sales.checks_received_account_id. creditpayments are skipped (the balance remains on the customer's account).
Business Rules
- Session validation -- The
pos_session_idmust belong to the authenticated user and must be open. Returns422if closed or403if it belongs to another user. - Atomic transaction -- The entire flow (invoice creation, posting, payments) runs in a single database transaction. If any step fails, everything is rolled back.
- No draft state -- POS invoices skip the draft-approve flow and are created directly as
Approved, then immediately posted. - Credit payments -- Payments with
method: "credit"are skipped during payment creation. The balance remains on the customer as accounts receivable. - Receipt numbers -- Distinct from invoice numbers, receipt numbers use the sequence
pos.receiptand are branch-specific. - Shared permission -- POS sales reuse the
sales.invoices.createpermission from the Sales module.