Skip to main content

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​

AppEntry point
MobileCreditNoteList → CreditNoteDetail, or the return action from an invoice
POS TerminalcreditNote 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:

StatusMeaningBackground
DRAFTDraftLight cyan #CBFEFF
NEWNewLight cyan #CBFEFF
WAITINGCNWaiting for B1 approvalLight lime #CFFD67
INVUPDATEWarehouse updatedLight pink-purple #E7BCE7
RECEIVEDSent to B1 successfullyLight green #76FFA3
APPROVED / APPROVEApprovedPurple #BB8AE3
CANCELEDCanceledLight orange #FFC385
ERRORRECEIVEDB1 submission failedLight orange #FFC385
QC_FAILEDQC failedLight orange #FFC385
Other / missingFallbackLight 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​

  1. 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.
  2. Select Reason 1. This is required before an item can be added and before the document can be saved.
  3. Select an item from the invoice. The source quantity, price, amount, UOM, warehouse, tax, and weight settings seed the credit-note item.
  4. 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.
  5. Save the item. The first item save creates the document as DRAFT when necessary; subsequent saves refresh the document and totals.
  6. For a physical return (isReturn = 1), an authorized warehouse user selects a Lot/Batch under Location. The choices come directly from GET /api/invoices/{invoiceId}?includeLot=true, specifically the matching invoice item's lots data. No /api/products/lot request is made and no current warehouse-stock availability is checked.
  7. 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.
  8. Add evidence images after the document exists, where permitted. Save or forward the document according to the role and status rules below.
  9. 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​

RuleClient behavior
Invoice sourceInvoice detail is requested with includeLot=true.
Lot pickerUses invoice.items[].lots; each lotNo is displayed as a Batch, with its invoice quantity.
Stock checkNot performed for this return flow. A return is not blocked because current stock is lower than qtyCN.
Quantity sent for a selected lotExactly the credit-note item qtyCN.
Physical returnisReturn = 1; a Lot is mandatory before B1 submission.
Financial-only creditisReturn = 0; no Location/Lot requirement before B1 submission.
Source-item validationqtyCN must not exceed source qty; priceCN must not exceed source amount.
Multiple lotsThe 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 / actionResult or guard
Initial save of a new documentCreates DRAFT.
Non-warehouse save with a physical-return itemUpdates to WAITINGCN.
Non-warehouse save without a physical-return itemUpdates to NEW.
Warehouse save from WAITINGCNUpdates to INVUPDATE.
Warehouse save from INVUPDATERemains INVUPDATE.
Warehouse save from another statusBlocked 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 submitCalls the B1 endpoint, then updates the document to RECEIVED on success.
Already sent to B1Submission is blocked when datetimeToB1 is present.
Received documentEditing 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 still WAITINGCN, B1 remains hidden until the normal warehouse save changes it to INVUPDATE. B1 only appears for NEW or INVUPDATE. Existing Lot, Q1/Q2/Q3, already-submitted checks and status transitions still apply.
  • qcStatus = false, or the API's QC_FAILED status: 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.

CapabilityPermission 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 document7, 8, 9, 12, 22, 28, 29, and status must be DRAFT, NEW, WAITINGCN, or INVUPDATE.
Add itemDocument is not RECEIVED and current user is the document owner.
Edit item quantity/amountNot RECEIVED, document owner, and 7, 12, 22, 28, 29.
Delete itemNot RECEIVED and document owner.
Select Location/LotNot 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 fields8, 9, 12, 22, 28, 29; Q1/Q2/Q3 and temperature become read-only after RECEIVED.
Add/delete image evidenceDocument already exists and is not RECEIVED.
Send to B1Document 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 listNever 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​

OperationAPI/client service
ListGET /api/credit-notes via getCreditNotes, with branch/warehouse scope and optional status, search, and isReturn
Credit-note detailGET /api/credit-notes/{creditNoteId}
Source invoice and LotsGET /api/invoices/{invoiceId}?includeLot=true
Create/update headerPOST /api/credit-notes; PATCH /api/credit-notes/{creditNoteId}
Create/update/delete item/api/credit-notes/{creditNoteId}/items
Save selected LotPATCH /api/credit-notes/{creditNoteId}/items/{itemId}/lots with lots: [{ lotNo, qty }]
Image evidenceCredit-note file upload/delete endpoints
QC decisionPATCH /api/credit-notes/{id}/qc with qcPassed and qcRemark, then GET detail
B1POST /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​

ResponsibilitySource
B1 status gate (NEW / INVUPDATE)packages/utils/src/creditNoteB1.ts
Deposit-warehouse QC state and payload validationpackages/utils/src/creditNoteQc.ts
Shared QC modal, duplicate-submit guard and reload retrypackages/rn-ui/src/CreditNoteQcModal.tsx
Mobile form / APIapps/mobile/src/screens/creditNoteStack/creditNoteDetailScreen/index.tsx, apps/mobile/src/services/creditNotes.ts
Terminal form / APIapps/pos-terminal/src/screens/HomeScreen/CreditNoteDetailForm.tsx, apps/pos-terminal/src/services/creditNotes.ts
Mobile list colorsapps/mobile/src/screens/creditNoteStack/helpers.ts, apps/mobile/src/ui/colors.ts
Terminal list colorsapps/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__ = true and false; neither mode may bypass the business gates.
  • Verify B1 visibility for both warehouse types across all statuses, including unknown/missing statuses; only NEW and INVUPDATE qualify. Existing permissions still apply, and the submit handler uses the same gate as the button.
  • For deposit-warehouse WAITINGCN with no QC decision, show QC and hide Save/B1. After a pass, reload detail; if still WAITINGCN, allow the normal warehouse save to reach INVUPDATE before 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 datetimeToB1 checks 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.