Skip to content

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

FieldTypeRequiredDescription
pos_session_idFKyesActive POS session ID
customer_idFKyesCustomer (business partner)
warehouse_idFKnoOverride warehouse (defaults to session's warehouse)
itemsarrayyesLine items (min 1)
items[].product_idFKyesProduct ID
items[].product_variant_idFKnoProduct variant ID
items[].unit_idFKyesUnit of measure
items[].warehouse_idFKnoPer-item warehouse override
items[].descriptionstringnoCustom line description
items[].quantitydecimalyesQuantity (must be > 0)
items[].unit_pricedecimalyesUnit price
items[].discount_percentdecimalnoLine discount percentage (0-100)
items[].tax_rate_idFKnoTax rate to apply
paymentsarraynoPayment records
payments[].methodstringyescash, card, credit, check
payments[].amountdecimalyesPayment amount
payments[].referencestringnoPayment reference (e.g., card approval number)

API Endpoint

MethodEndpointDescriptionPermission
POST/api/pos/salesCreate 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', and pos_session_id / pos_terminal_id are set.
  • invoice_number and receipt_number are 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 credit method), a SalesPayment is created and posted via PostSalesPayment.
  • Receiving accounts are resolved from sales settings: sales.cash_account_id, sales.card_account_id, sales.checks_received_account_id.
  • credit payments are skipped (the balance remains on the customer's account).

Business Rules

  1. Session validation -- The pos_session_id must belong to the authenticated user and must be open. Returns 422 if closed or 403 if it belongs to another user.
  2. Atomic transaction -- The entire flow (invoice creation, posting, payments) runs in a single database transaction. If any step fails, everything is rolled back.
  3. No draft state -- POS invoices skip the draft-approve flow and are created directly as Approved, then immediately posted.
  4. Credit payments -- Payments with method: "credit" are skipped during payment creation. The balance remains on the customer as accounts receivable.
  5. Receipt numbers -- Distinct from invoice numbers, receipt numbers use the sequence pos.receipt and are branch-specific.
  6. Shared permission -- POS sales reuse the sales.invoices.create permission from the Sales module.

Moon ERP API Documentation