← Back to Posable Office

Plan B — Back-Office Does Not Return POS (POPC) Sales

This is the small plan. Back-Office (BO) returns only sales that were made in BO. A sale made at POS (POPC) is returned at POS (POPC). It can replace the big plan (v3) for now. Built on 14 Oct 2026 in the working tree (not committed, not deployed). Tests: backend sales-return 156 passed, frontend sales pages 213 passed, typecheck 372 errors (was 375). B2 note added. The POS-return code (SOURCES_RETURNABLE, _till_line_refund, canReturnFromSource and the till-only tests) was removed, so it is no longer "dormant" as section 1 says.

14 Oct 2026 · Branch Redy-20261005-Sales-Return · Big plan for comparison: Plan v3 · Facts below were read from the code on 14 Oct.

What this plan does, in short
  1. The rule. BO returns only sales made in BO. A POS (POPC) sale is returned at POS (POPC). Online orders stay out, as today.
  2. The work. Turn the switch back to BO only. Two lines of code, plus the tests that expect the old switch.
  3. Dropped. The cash block (R1 to R5), the store picker, the link-id fix and the POS (POPC) app change. None is needed, because BO never writes a return of a POS (POPC) sale.
  4. Kept. The server fix for an empty register. BO-made documents have no register either (sales_commit_repository.py:262), so it stays useful.
  5. No new POS (POPC) build. The release gate of Plan v3 goes away: there is no cash problem when BO cannot refund a POS (POPC) sale.
Sections 1  What changes in the code 2  What the user sees 3  Old data and open returns 4  Plan B against Plan v3 5  Tests, deploy, risks 6  Decisions 7  What other brands do

1What changes in the code

WhereTodayPlan B
apps/v2-sellable-bo/backend/app/repositories/sales_commit_repository.py:40SOURCES_RETURNABLE = (SOURCE_TYPE_BO, "POS")SOURCES_RETURNABLE = (SOURCE_TYPE_BO,). This one value feeds the invoice search (:892) and the server check (sales_commit_service.py:1717), so both follow it.
apps/v2-sellable-bo/frontend/src/lib/constants/sales-lifecycle.ts:143canReturnFromSource accepts BO and "POS"Accepts BO only. It hides the Return button in the invoice list (salesListColumns.tsx:434) and on the invoice page (SalesDocumentViewPage.tsx:537). Fix the comment above it too.
apps/v2-sellable-bo/backend/tests/unit/services/test_sales_return_qa_sr.py:104-133Tests say the POS invoice is returnableChange them: the switch is ("BO",); a POS invoice is refused with ORIGINAL_NOT_RETURNABLE; search gets ["BO"]. The "if switched off" test becomes the normal test.
The till-line refund code (_till_line_refund, sales_return_rules.py:153) and its testsUsed for POS salesLeave it in. It is dormant. Turning POS returns on later is the same two lines back. (Deleting it is cleaner but costs a rewrite later.)

The server already has the right words for BO-only: "Only a completed sales invoice made in the Back-Office can be returned here." (sales_commit_service.py:1719-1722). It shows when the switch is BO only.

2What the user sees

  1. A POS (POPC) invoice has no Return button in the BO invoice list and on its page.
  2. Searching for an invoice to return finds BO-made invoices only.
  3. If someone calls the API for a POS (POPC) invoice anyway, the server answers 409 ORIGINAL_NOT_RETURNABLE with the message above.
  4. The user does not see why the button is missing. Option B2, not in the base plan: a small grey note on the invoice page: "Sales made at POS (POPC) are returned at POS (POPC)." It is a few lines in SalesDocumentViewPage.tsx. I would add it: without it, people ask where the button went.

3Old data and open returns

CaseWhat happensWhat to do
Returns of POS sales already made (dev company OHY33941 has 4 BO returns; one is of a POS sale: CI-8/BO/261013052505369. I did not check the other 3)They stay. They are completed documents. With the server fix they sync. The POS (POPC) app can still crash when opening one (the app change is dropped).Leave them (my pick), or delete the test rows. I will not touch data without your OK.
A Draft or For Approval return of a POS saleIt is refused at the next submit or approve (ORIGINAL_NOT_RETURNABLE). It keeps holding quantity until voided.Void it. Dev has 0 such (checked 13 Oct). Production: the switch was never committed, so this code made none there. Not checked on the production data.
Returns of BO-made salesNo change.None.

4Plan B against Plan v3

Plan B (this page)Plan v3
BO returns a POS (POPC) saleNoYes, with limits (no Cash)
Code to build2 lines + tests + optional noteServer rules, return-form changes, store picker, link ids, POS (POPC) app
New POS (POPC) buildNoYes
End Shift / cash riskNone from BO returnsHandled by rule R1; card totals still off
Double-refund riskNoneNeeds the link-id fix
Customer returns a POS saleAt POS (POPC) onlyAt POS (POPC) or in BO
Can go liveRight after testsAfter build, device test, new POS build

5Tests, deploy, risks

Tests

  1. Backend: pytest tests/unit/services -k sales_return in apps/v2-sellable-bo/backend (last known: 162 passed). The edited qa_sr tests must pass.
  2. Frontend: the sales page tests, then npm run typecheck:ratchet (last known 372 errors; it must not go up).
  3. By hand in BO: a BO invoice shows Return; a POS (POPC) invoice does not; search finds only BO invoices.

Deploy

  1. Commit with the fixed subject shape, for example fix[sales]: allow back-office returns of back-office sales only. No Claude footer.
  2. Open the PR with the fixed title and body, then merge. The pipeline deploys BO backend and frontend. Also deploy v2-sellable-api for the register fix.
  3. The rest of this branch (rounding, decimal quantity, phone on search, credit-invoice PDF) ships in the same PR. It is not related to this switch.

Risks

6Decisions

#QuestionOption 1Option 2My pick
B1Which plan?Plan B now: BO only, smallPlan v3: BO also returns POS salesPlan B. It is small and safe. Plan v3 can come later.
B2Tell the user why the button is missing?Yes, one grey note on the invoice pageNo, button just hiddenYes
B3The dev return(s) of POS salesLeave themDelete the test rows (needs your OK)Leave them
What other brands do (section 7)

Official docs answer this for only 2 products. Cash refunds are tied to a register or drawer. No brand I found allows a back-office cash refund with a clean shift count. This does not clearly choose Plan B or Plan v3.

What I do next

Nothing in code until you say "go". On "go" I make the two changes, edit the tests, run them, and report. I do not commit or deploy the code without your word.

7What other brands do

Searched 14 Oct 2026 by a research agent. UNVERIFIED = no official page found. Only Toast and Dynamics 365 have official answers. The evidence is thin.

ProductCan back-office refund a POS sale?Cash and shift ruleOther storeSource
ToastCard: yes. Cash: no. "You cannot refund cash payments from Toast Web".A cash refund is done on a POS device tied to a cash drawer, so it shows in that drawer's report. With no drawer it shows in no report.Not statedToast doc
Dynamics 365 CommerceA return is a POS action. Return from head office or call centre: UNVERIFIED.The return policy limits refund methods by the original payment. A cash sale can go back to cash, gift card or customer account, not credit card. A manager can override (and from version 10.0.44 a permission "Allow using all payment methods for refund").Not statedrefund policy · POS returns
Dynamics AX 2012 (old)Not statedA cash return in a different shift needs a disbursement slip, made at POS or registered in advance.Not statedTechNet
Lightspeed X-SeriesYou open the original sale in Retail POS (Sales history, Return items). A refund made only in the Financial services tab is not in other reports. Card refunds go to the original card.Register closing records the cash counted. How a cash refund affects closing: UNVERIFIED.UNVERIFIEDrefund · closures
SquareDashboard handles card refunds. Interac Debit in person only and cash from the Dashboard are UNVERIFIED (community posts only).UNVERIFIEDUNVERIFIEDSquare help
Shopify POSUNVERIFIED. The official page did not say whether the admin can refund.A cash tracking report shows the refund total per cash session. Effect of an admin refund: UNVERIFIED.UNVERIFIEDchangelog
Odoo POSUNVERIFIED. A forum thread says refunds made in the Orders tab, outside an open session, stayed "New" and blocked closing the session. No confirmed fix.UNVERIFIEDUNVERIFIEDforum only
Clover, Loyverse, SAP B1, Oracle Xstore, NCR, ZohoNo official source found. UNVERIFIED.
What this means for the two plans