Credit Note / Product Return
Use this feature to create a credit note from an invoice, either with a physical product return or as a financial-only credit. The same rules apply in Mobile and POS Terminal; only the screen layout differs.
Where to open it
| App | Entry point |
|---|---|
| Mobile | CreditNoteList → CreditNoteDetail, or the return action from an invoice |
| POS Terminal | creditNote split-pane menu, or Open product return from invoice detail |
The list is scoped to the selected branchId and whGrpCode. It supports search, status filtering, refresh, and pagination. Has product return is enabled by default and sends isReturn=1; turn it off to include financial-only credit notes.
List card background colors
Mobile and Terminal use the same fixed background colors for credit-note list cards in both warehouse types:
| Status | Meaning | Background |
|---|---|---|
DRAFT | Draft | Light cyan #CBFEFF |
NEW | New | Light cyan #CBFEFF |
WAITINGCN | Waiting for B1 approval | Light lime #CFFD67 |
INVUPDATE | Warehouse updated | Light pink-purple #E7BCE7 |
RECEIVED | Sent to B1 successfully | Light green #76FFA3 |
APPROVED / APPROVE | Approved | Purple #BB8AE3 |
CANCELED | Canceled | Light orange #FFC385 |
ERRORRECEIVED | B1 submission failed | Light orange #FFC385 |
QC_FAILED | QC failed | Light orange #FFC385 |
| Other / missing | Fallback | Light orange #FFC385 |
QC_FAILED currently uses the list's fallback orange; it does not have a separate list color. The full-width ตรวจ QC ไม่ผ่าน action inside the deposit-warehouse detail form is red (#C53030) and disabled. These are separate UI elements; this documentation records the existing palette and does not change it.
End-to-end return flow
- Open an invoice and start a product-return draft. The system copies invoice/customer/company/warehouse context, the responsible salesperson, and the invoice items. Existing credit notes also reload their linked invoice so that the source-item picker remains available.
- Select Reason 1. This is required before an item can be added and before the document can be saved.
- Select an item from the invoice. The source quantity, price, amount, UOM, warehouse, tax, and weight settings seed the credit-note item.
- Set the return type, returned quantity (
qtyCN), and credit amount (priceCN). The client blocks a return quantity higher than the source item quantity and a credit amount higher than the source item amount. - Save the item. The first item save creates the document as
DRAFTwhen necessary; subsequent saves refresh the document and totals. - For a physical return (
isReturn = 1), an authorized warehouse user selects a Lot/Batch under Location. The choices come directly fromGET /api/invoices/{invoiceId}?includeLot=true, specifically the matching invoice item'slotsdata. No/api/products/lotrequest is made and no current warehouse-stock availability is checked. - Selecting a Lot sends one lot record to the credit-note item:
{ lotNo, qty: qtyCN }. Therefore the recorded return quantity always follows the credit-note item, not the invoice-lot display quantity. If several invoice lines share a product code, the displayed lot quantities are combined by Lot number only for selection. - Add evidence images after the document exists, where permitted. Save or forward the document according to the role and status rules below.
- Before B1 submission, every physical-return item must have a selected Lot and at least one of warehouse checks Q1/Q2/Q3 must be checked. Temperature is recorded as document data but has no additional client-side range validation.
Lot and return rules
| Rule | Client behavior |
|---|---|
| Invoice source | Invoice detail is requested with includeLot=true. |
| Lot picker | Uses invoice.items[].lots; each lotNo is displayed as a Batch, with its invoice quantity. |
| Stock check | Not performed for this return flow. A return is not blocked because current stock is lower than qtyCN. |
| Quantity sent for a selected lot | Exactly the credit-note item qtyCN. |
| Physical return | isReturn = 1; a Lot is mandatory before B1 submission. |
| Financial-only credit | isReturn = 0; no Location/Lot requirement before B1 submission. |
| Source-item validation | qtyCN must not exceed source qty; priceCN must not exceed source amount. |
| Multiple lots | The current UI selects one Lot per credit-note item and sends one-element lots payload. |
Status and save behavior
The client displays these statuses: DRAFT, NEW, WAITINGCN, INVUPDATE, RECEIVED, APPROVED/APPROVE, CANCELED, ERRORRECEIVED, and QC_FAILED.
| Actor / action | Result or guard |
|---|---|
| Initial save of a new document | Creates DRAFT. |
| Non-warehouse save with a physical-return item | Updates to WAITINGCN. |
| Non-warehouse save without a physical-return item | Updates to NEW. |
Warehouse save from WAITINGCN | Updates to INVUPDATE. |
Warehouse save from INVUPDATE | Remains INVUPDATE. |
| Warehouse save from another status | Blocked with a prompt to wait for warehouse processing. |
| B1 button (both warehouse types) | Visible only for NEW or INVUPDATE, subject to the existing permissions and deposit-warehouse QC gates. All other statuses hide it. |
| B1 submit | Calls the B1 endpoint, then updates the document to RECEIVED on success. |
| Already sent to B1 | Submission is blocked when datetimeToB1 is present. |
| Received document | Editing actions are read-only/disabled. |
These rules now apply equally in development and release. The temporary exception that hid QC and exposed B1 for error testing has been removed, including its Lot/Q bypass.
Deposit warehouse QC before B1
For isMain = false, a WAITINGCN credit note with qcStatus = null shows ตรวจสอบ QC (Check QC) in place of the B1 action for users who already have B1 permission. Save is also hidden while QC is pending so that saving cannot advance the status to INVUPDATE and bypass QC. Main-warehouse behavior is unchanged.
All QC actions and QC-based locks apply only when isMain === false. Main warehouses (isMain = true) and unknown warehouse types do not enter the QC flow, including when QC fields are present in the response.
The QC modal has a multiline reason field (up to 500 characters) and ผ่าน / ไม่ผ่าน actions. Passing does not require a reason; failing requires a nonblank reason. It sends PATCH /api/credit-notes/{id}/qc with { qcPassed: boolean, qcRemark: string }, then reloads the credit-note detail. It does not itself save the header or send to B1.
QC_FAILED is defined by the backend QC API, not a status invented or assigned by the client. The API documentation specifies that a failed QC result changes the document to QC_FAILED. The app sends only the decision and remark, then reads qcStatus and creditNoteStatus from the refreshed detail; it does not PATCH creditNoteStatus: "QC_FAILED" itself.
Example failure payload:
{
"qcPassed": false,
"qcRemark": "สินค้าละลาย บรรจุภัณฑ์ฉีกขาด"
}
For a pass with no reason, the client sends { "qcPassed": true, "qcRemark": "" }.
qcStatus = true: restore Save according to existing permissions. If the reloaded status is stillWAITINGCN, B1 remains hidden until the normal warehouse save changes it toINVUPDATE. B1 only appears forNEWorINVUPDATE. Existing Lot, Q1/Q2/Q3, already-submitted checks and status transitions still apply.qcStatus = false, or the API'sQC_FAILEDstatus: hide Save and Send B1 and show one full-width red, disabled ตรวจ QC ไม่ผ่าน action. Item, warehouse-check, reason, and evidence editing are read-only.- Prevent duplicate decisions while submitting. Preserve the reason and API error on failure. If PATCH succeeds but reloading fails, retry loading without resubmitting QC; do not unlock B1 using an assumed result.
Mobile and Terminal share the QC state/payload helper in packages/utils/src/creditNoteQc.ts and the modal in packages/rn-ui/src/CreditNoteQcModal.tsx. The cashier owns the form; there is no second-screen QC editor.
Permissions
The table below retains the base role rules. In a deposit warehouse, pending QC additionally hides Save; failed QC additionally locks detail editing and hides Save/B1. The list-delete rules are separate.
All department values below are deptCode. The B1 user-code overrides are 012142, 012570, 012544, 012051, and 012566.
| Capability | Permission and conditions |
|---|---|
| Open the Credit Note menu (Mobile/POS Terminal) | -2, 1, 2, 3, 4, 7, 8, 9, 12, 13, 16, 20, 21, 22, 23, 27, 28, 29, or the configured menu user-code override. |
| Create from the Mobile list | -2, 1, 2, 4, 7, 8, 9, 12, 16, 22, 28, 29. |
| Save document | 7, 8, 9, 12, 22, 28, 29, and status must be DRAFT, NEW, WAITINGCN, or INVUPDATE. |
| Add item | Document is not RECEIVED and current user is the document owner. |
| Edit item quantity/amount | Not RECEIVED, document owner, and 7, 12, 22, 28, 29. |
| Delete item | Not RECEIVED and document owner. |
| Select Location/Lot | Not RECEIVED, physical-return item, and 12, 22, 28 at any branch. Departments 20 and 23 also have this permission when the selected branch has isMain = false; B1 user-code overrides remain allowed. Ownership is not required. |
| Warehouse inspection fields | 8, 9, 12, 22, 28, 29; Q1/Q2/Q3 and temperature become read-only after RECEIVED. |
| Add/delete image evidence | Document already exists and is not RECEIVED. |
| Send to B1 | Document exists, status is NEW or INVUPDATE, deposit-warehouse QC does not block it, caller is 8, 9, 12, 20, 22, 23, 28, 29 or B1 user-code override, datetimeToB1 is empty, required Lots are set, and at least one of Q1/Q2/Q3 is selected when there is a physical return. |
| Delete document from the list | Never when RECEIVED, APPROVED, or APPROVE. Otherwise it is allowed to cancel departments -2, 1, 2, 4, 12, 22, 28; for DRAFT/NEW the owner may delete; for WAITINGCN/INVUPDATE deletion is allowed. Confirmation is always required. |
For API failures shown in Global Alert, use คัดลอกปัญหาส่งทีม IT to copy the backend response together with request URL/query/payload and the initiating usercode/deptCode for troubleshooting; see API error copying.
API map
| Operation | API/client service |
|---|---|
| List | GET /api/credit-notes via getCreditNotes, with branch/warehouse scope and optional status, search, and isReturn |
| Credit-note detail | GET /api/credit-notes/{creditNoteId} |
| Source invoice and Lots | GET /api/invoices/{invoiceId}?includeLot=true |
| Create/update header | POST /api/credit-notes; PATCH /api/credit-notes/{creditNoteId} |
| Create/update/delete item | /api/credit-notes/{creditNoteId}/items |
| Save selected Lot | PATCH /api/credit-notes/{creditNoteId}/items/{itemId}/lots with lots: [{ lotNo, qty }] |
| Image evidence | Credit-note file upload/delete endpoints |
| QC decision | PATCH /api/credit-notes/{id}/qc with qcPassed and qcRemark, then GET detail |
| B1 | POST /api/credit-notes/{creditNoteId}/to-b1, then header PATCH to RECEIVED |
Mobile and POS Terminal parity
Both clients use the same source-invoice Lots, item validation, status transitions, B1 gates, and department/user overrides. Mobile presents a stack and modals; POS Terminal presents the CreditNoteDetailForm inside the split pane. POS Terminal keeps sourceInvoiceItems when a persisted credit note is opened so that its Lot picker uses the same invoice payload.
Developer handoff and regression checks
| Responsibility | Source |
|---|---|
B1 status gate (NEW / INVUPDATE) | packages/utils/src/creditNoteB1.ts |
| Deposit-warehouse QC state and payload validation | packages/utils/src/creditNoteQc.ts |
| Shared QC modal, duplicate-submit guard and reload retry | packages/rn-ui/src/CreditNoteQcModal.tsx |
| Mobile form / API | apps/mobile/src/screens/creditNoteStack/creditNoteDetailScreen/index.tsx, apps/mobile/src/services/creditNotes.ts |
| Terminal form / API | apps/pos-terminal/src/screens/HomeScreen/CreditNoteDetailForm.tsx, apps/pos-terminal/src/services/creditNotes.ts |
| Mobile list colors | apps/mobile/src/screens/creditNoteStack/helpers.ts, apps/mobile/src/ui/colors.ts |
| Terminal list colors | apps/pos-terminal/src/screens/HomeScreen/ListPane.tsx, apps/pos-terminal/src/screens/HomeScreen/config.ts |
Run from the repository root:
yarn typecheck
yarn workspace @bsr/mobile jest --runInBand --watchman=false __tests__/creditNoteQc.test.ts __tests__/creditNoteQcModal.test.tsx __tests__/creditNoteQcTerminal.test.tsx
- Run the QC/Save/B1 visibility checks with both
__DEV__ = trueandfalse; neither mode may bypass the business gates. - Verify B1 visibility for both warehouse types across all statuses, including unknown/missing statuses; only
NEWandINVUPDATEqualify. Existing permissions still apply, and the submit handler uses the same gate as the button. - For deposit-warehouse
WAITINGCNwith no QC decision, show QC and hide Save/B1. After a pass, reload detail; if stillWAITINGCN, allow the normal warehouse save to reachINVUPDATEbefore showing B1. A failed result keeps the form locked with its disabled red action. - Main warehouses never enter QC or inherit QC-based locks. Existing Lot, Q1/Q2/Q3 and
datetimeToB1checks still run when B1 is pressed. - Verify required failure reason, optional pass reason, API error retention, rapid opposite decisions, reload-only retry after successful PATCH, and ignoring a late response after leaving the selected document.
- Compare the list palette above separately from the detail's failed-QC action.
Verification after restoring the gates and renaming the button (6442b189): alertResponseCopy.test.tsx, creditNoteQcTerminal.test.tsx, and creditNoteQc.test.ts passed 114 tests in total. Mobile/Terminal TypeScript checks and the bilingual Wiki build passed. API submissions in tests were mocked.