Direct sales
Route: /org/inventory/sales · Module: org_inv_sales — 3 permissions
Permission to view: View Inventory sales
1. What direct sales are
Sales that did not come through the storefront: walk-in counter sales and offline B2B orders. The screen describes itself as "Walk-in POS & offline B2B orders — every sale decrements finished-good stock."
| Direct sale | Storefront order | |
|---|---|---|
| Origin | Staff ringing it up | A customer checking out |
| Screen | /org/inventory/sales | /org/ecom/orders |
| Has a lifecycle | ❌ posted and done | ✅ placed → confirmed → shipped → delivered |
| Fulfilment, delivery, refunds | ❌ | ✅ |
| Writes a ledger row | ✅ SALE | ❌ |
Writes current_stock | ❌ | ✅ |
A storefront order decrements current_stock and posts no ledger row. A direct sale posts a
SALE ledger row and does not touch current_stock — despite the screen's own subtitle saying
it decrements stock.
Neither channel can see the other's numbers. See How stock is counted.
2. Reading the list
| Column | Notes |
|---|---|
| Date | When it was posted |
| Invoice | Your reference — INV-20260728-00412 |
| Customer | Name and phone, decoded from the row's remarks |
| Items | Line count, with total units beneath |
| Payment | A pill — see below |
| Total | After discount and tax |
| 👁 | Opens the detail drawer |
TODAY in the header sums today's sales only. It is not a running total.
Payment methods
| Value | Pill |
|---|---|
cash | Cash |
card | Card |
upi | UPI |
bank | Bank Transfer |
credit | Credit / On Account |
Recording upi does not create a payment record, reconcile anything, or mark money as received.
Nothing in Orbit chases a credit sale for settlement. It is metadata on the ledger row.
3. Ringing up a sale
| Field | Notes |
|---|---|
| Reference | Pre-filled as INV-<yyyymmdd>-<seconds since midnight>. Editable |
| Customer name / phone | Optional. Free text — not linked to any customer record |
| Payment method | One of the five above |
| Discount % | Applied to the whole cart, not per line |
| Tax % | Applied to the discounted subtotal |
| Notes | Free text |
The arithmetic, in order:
subtotal = Σ (qty × unit price)
discount = subtotal × discount% / 100
tax base = subtotal − discount
tax = tax base × tax% / 100
total = tax base + tax
It is not taken from the product's tax category. A product configured as 5% India GST does not pre-fill this field, and nothing checks that the number you type matches the catalogue.
Two people ringing up the same product can post different tax. If tax correctness matters, the storefront path — where the tax engine applies — is the one to use.
Validation — exact strings
| Condition | Message |
|---|---|
| Empty cart | Cart is empty |
| A line with qty ≤ 0 or negative price | Each line needs a positive quantity and non-negative price |
| Success | Sale posted • ₹<total> |
After a successful post the cart clears and the view stays open with a fresh reference — POS behaviour, for ringing up the next customer.
4. What a sale writes
One SALE ledger row per cart line:
| Field | Value |
|---|---|
quantity | Negative — stock leaving |
unit_cost | The unit selling price, not cost |
batch_id | Shared across the invoice's lines |
reference_no | Your invoice reference |
remarks | The encoded customer, phone, payment method, discount and tax |
There are no customer columns on the ledger. The app packs them into remarks as
[CUST:…] [PH:…] [PAY:…] [DISC:…] [TAX:…] and decodes them on this screen.
The ledger does not decode them, so it displays the raw tags — including the customer's name and phone in plain text. That is a privacy consideration on any tenant with real customers.
5. Permissions
| Action | Permission |
|---|---|
| View | sales.view |
| Menu | sales.menu |
| Post a sale | sales.create |
There is no edit, delete, refund or void permission — and no such action exists. A posted sale is permanent.
NEW SALE renders for anyone with sales.view. The API rejects the post. See
Permissions.
6. Troubleshooting
| Symptom | Cause |
|---|---|
| List is empty | No project selected |
| Stock did not fall | Expected — sales write the ledger, not current_stock |
| Tax looks wrong | It was typed manually; it does not come from the tax category |
| Customer name not searchable as a customer | It is free text on the sale, not a customer record |
| A sale needs reversing | No void exists. Post a compensating adjustment |
| Discount applied unexpectedly | It applies to the whole cart, not one line |