Invoice
The Invoice menu is used to track invoices created from the sales order flow, inspect customer/product/status details, send invoice data to B1, manage invoice cancellation requests, reopen a sales order from an existing invoice, and view today's sales summary for the current user and the whole branch.
In this system, invoices are not the main entry point for creating sales. The primary sales entry point is Sales Order. The invoice menu is the review, B1 handoff, cancellation, and summary surface after the order/invoice/payment flow has started.
Open workflowOpen sales summary notesOpen permissions
App Entry Points
| App | Entry point | Main files |
|---|---|---|
| Mobile | Home -> InvoiceList -> InvoiceDetail | invoiceListScreen, invoiceDetailScreen |
| POS Terminal | invoice menu in split home | HomeScreen/index.tsx, ListPane.tsx, InvoicePanels.tsx |
Both apps use the same service boundary:
| Job | Endpoint / service |
|---|---|
| Load invoice list | GET /invoices via getInvoices(...) |
| Load pending B1 list | GET /invoices?toB1=0 via getInvoicesPendingB1(...) |
| Load invoice detail | GET /invoices/{invoiceId}?includeLot=true |
| Send to B1 | POST /payment/to-b1 with an array of invoiceId values |
| Retry payment/incoming send | POST /payment/{invoiceId}/retry |
| Sales summary | GET /invoices/summary |
| Approve cancel request | POST /invoices/{invoiceId}/approve |
| Reject cancel request | POST /invoices/{invoiceId}/reject |
| Request invoice cancellation | POST /invoices/{invoiceId}/cancel |
| Direct invoice cancellation | POST /invoices/{invoiceId}/cancel-direct, payload { notes: string } |
Main Workflow
Invoice List
The main list loads through getInvoices(...) with the selected branch context:
branchIdfrom selected branchwhGrpCodefrom selected warehouse grouppage,pageLimit,search- base filters:
shippingType,invoiceStatus,toB1,inStock,withOutCN,dateFrom,dateTo - sorted
DESCbyinvoiceId
List behavior:
- search covers invoice number, customer, or sales order number
- tabs are
AllandPending B1 - rows are deduped by
invoiceIdorinvoiceStorageId - tapping a row in normal mode opens invoice detail
- tapping a row in manual payment mode selects or deselects the invoice for sending
Invoice Detail
Detail loads GET /invoices/{invoiceId}?includeLot=true and renders:
- invoice number, customer, sales order number, invoice owner, and invoice date
- invoice summary through
InvoiceSummarySection - product item lines and item count
- shipping invoices attached to the order, with retry buttons for shipping/incoming when retry is available
Open sales order from invoicewhen the user can create sales orders
Opening a sales order from an invoice passes these values into order detail:
{
duplicateInvoiceId,
duplicateInvoiceNo,
duplicateDocumentMove,
duplicateVehicleRelease,
}
Main branches can open the duplicated order draft directly. Non-main branches must first choose vehicleRelease and documentMove in the duplicate configuration modal.
Send To B1
B1 sending can run from a per-row action or from multi-select manual payment mode. The API call is POST /payment/to-b1.
Before sending, the app checks:
| Guard | Detail |
|---|---|
Has invoiceId | Uses invoiceId or fallback invoiceStorageId |
Has refKey | Missing refKey blocks sending |
toB1 = 0 | Already-sent invoices show a disabled action |
orderStatus = CONFIRMED | Unpaid/unconfirmed sales orders show an error |
| No pending cancellation request | canceledNote without rejectCanceledAt blocks sending |
| Invoice date | Normal users can send only today's/yesterday's invoices; dept 3 bypasses the date guard |
Sales Summary Button
The Sales Summary button is available in the invoice header:
| App | Button location |
|---|---|
| Mobile | header menu item named สรุปยอดขาย |
| POS Terminal | bar-chart-2 icon beside the Invoice menu header in ListPane |
When pressed, the app calls /invoices/summary twice for the current day:
The modal groups BFP and NFF and then renders a total block:
| Value | Calculation |
|---|---|
| Invoice count | invCount |
| Sales amount | amountTotal |
| point/coupon discount | discount |
| credit note discount | useCreditNote |
| net remaining | amountTotal - discount - useCreditNote |
| total weight | weightTotal |
| COD transaction count | codCount |
| COD fee total | codTotal |
For each company and for the grand total, the modal renders the COD metrics as ค่าธรรมเนียม COD : codCount @ codTotal. These are additional summary values; the net remaining calculation remains amountTotal - discount - useCreditNote and does not subtract codTotal.
Manual Payment Status Mode
This mode opens from the same header menu area as the sales summary:
- Mobile label:
ปรับสถานะการชำระเงินด้วยตนเอง - Terminal icon:
check-square; once active, it changes toxfor cancelling the mode - the list switches to the
Pending B1tab - normal users are limited to today/yesterday and their own
userCode - dept
3sees every user and bypasses the manual-mode date guard - users select multiple invoices and press
ส่งข้อมูล - selected invoices must not be canceled, must have
refKey, and their order must beCONFIRMED
Cancellation Requests
Mobile and Terminal use the same cancellation rules in the normal invoice list. Swipe the invoice card to reveal the available action. The list supports both pending-request approval and direct cancellation without a request.
Pending requests
Requests originate from Storefront Picking or Shipping Route, where staff enter a reason and send POST /invoices/{invoiceId}/cancel with { notes }.
A request is pending when canceledNote has a value and rejectCanceledAt is empty. The invoice owner (matching userCode) or override users 012265 and 999999 can open the approval modal. Approve calls /approve; Reject calls /reject. Both refresh the invoice list. CANCELED invoices cannot be canceled again.
Pending requests take priority over direct cancellation. Login isAdmin = 1 alone does not grant permission to manage an existing request.
Direct cancellation without a request
All of these conditions must hold:
- There is no pending cancellation request.
- The API explicitly returns
hasDelivery: null. Missing values,0, and1do not qualify. - The invoice is not
CANCELED. - The current user owns the invoice or has
isAdmin = 1in the successful login data. The flag also accepts the string"1"; override user codes alone do not grant this permission.
The action opens ยกเลิกใบแจ้งหนี้โดยไม่มีคำขอ, showing the invoice number, a multiline หมายเหตุ field, ปิด (Close), and ตกลง (OK).
- Enter a note and press OK. Leading and trailing whitespace is removed.
- Empty or whitespace-only notes show กรุณาระบุหมายเหตุการยกเลิก in the global Sweet Alert and do not call the API.
- A valid note is sent as follows (the field name is
notes):
POST /api/invoices/{invoiceId}/cancel-direct
Content-Type: application/json
{
"notes": "Customer requested cancellation"
}
While sending, duplicate submissions, editing, and dismissal are blocked. Success closes the modal and refreshes the current invoice list. API errors show the returned message in the global Sweet Alert; the fallback is ไม่สามารถยกเลิกใบแจ้งหนี้ได้. The modal retains the note for correction or retry. Close dismisses the form without submitting; reopening starts with an empty note.
Swipe action visibility
The following table applies in normal list mode, not manual payment mode:
| Condition | Swipe action |
|---|---|
| Eligible pending request and user | Active cancellation button opens approval/rejection |
| Eligible direct cancellation and user | Active cancellation button opens the note modal |
Cancellation unavailable and hasDelivery !== null | Inactive ยังไม่มีคำขอยกเลิก action keeps the card swipeable for debugging |
Cancellation unavailable and hasDelivery === null | Cancellation action hidden |
The inactive label is a fallback for any failed cancellation condition, including permissions or a canceled invoice. It does not prove that a request has never existed. An omitted hasDelivery also follows this inactive-action branch, because it is not explicit null.
Permissions And Guards
| Area | Condition |
|---|---|
| See Invoice menu | MENU_INVOICE.has(deptCode) or admin/full non-main warehouse access |
| menu dept codes | -2, 1, 2, 3, 4, 7, 8, 9, 13, 16, 20, 21, 22, 23, 27, 29 |
| Send one invoice to B1 | invoice owner, dept 3, or override user 012265, 999999 |
| Send older than today/yesterday | dept 3 only |
| Manage cancel request | invoice owner or override user 012265, 999999 |
| Cancel without a request | invoice owner or login isAdmin = 1; no pending request, explicit hasDelivery = null, not CANCELED |
| Open sales order from invoice | must pass canCreateSalesOrder |
main canCreateSalesOrder dept codes | admin, full non-main warehouse access, or dept -2, 1, 2, 4, 7, 8, 9, 13, 16, 21, 29 |
Important: “can see the menu” and “can perform an action” are separate gates. For example, dept 20 can see the Invoice menu and some Home shortcuts, but Open sales order from invoice still uses the detail-screen canCreateSalesOrder gate.
Mobile vs POS Terminal
| Topic | Mobile | POS Terminal |
|---|---|---|
| UI shape | stack screen: list -> detail | split pane: menu, list, detail |
| sales summary button | header menu item | icon beside invoice menu title |
| invoice detail | InvoiceDetailScreen | InvoicePanels detail panel |
| manual payment mode | bottom rail with ยกเลิก / ส่งข้อมูล | header action plus list-pane actions |
| duplicate order | navigate to OrderDetail | create/open order detail in split panel with progress |
| summary modal | InvoiceSummaryModal component | InvoiceSummaryModal in InvoicePanels.tsx |
Developer Handoff Map
| Job | Mobile | POS Terminal |
|---|---|---|
| invoice list | apps/mobile/src/screens/invoiceStack/invoiceListScreen/index.tsx | apps/pos-terminal/src/screens/HomeScreen/index.tsx, ListPane.tsx |
| invoice detail | apps/mobile/src/screens/invoiceStack/invoiceDetailScreen/index.tsx | apps/pos-terminal/src/screens/HomeScreen/InvoicePanels.tsx |
| invoice service | apps/mobile/src/services/invoices.ts | apps/pos-terminal/src/services/invoices.ts, splitMenuData.ts |
| summary modal | apps/mobile/src/components/InvoiceSummaryModal/index.tsx | InvoicePanels.tsx |
| Home menu permission | apps/mobile/src/screens/homeScreen/index.tsx | apps/pos-terminal/src/screens/HomeScreen/config.ts |
| open order from invoice permission | apps/mobile/src/utils/salesOrders.ts | canCreateSalesOrder(...) in Terminal config |
Shared cancellation rules: packages/utils/src/invoiceCancellation.ts. Shared direct-cancellation modal and global alert integration: packages/rn-ui/src/InvoiceDirectCancelModal.tsx. Contract and modal tests: apps/mobile/__tests__/invoiceCancelDirectService.test.ts, invoiceCancellation.test.ts, and invoiceDirectCancelModal.test.tsx.