Closes the open POS step-2 bug where only some addons are persisted to
the order. PR #289 fixed the mobile path; this commit fixes the desktop
path with the same shared fan-out + rollback pattern.
## Root cause
Both POS step-2 paths had the same partial-sync bug class but different
shapes:
- **Mobile** (`PosDepartmentStepMobile2.vue → syncMobileOrderItems.js`):
used `Promise.all` over parallel POSTs that short-circuits on first
rejection.
- **Desktop** (`SelectProductsFormPOS.vue →
addAddonsToOrderMiddleware`): used a sequential `await` loop with
`.catch(handleCreateOrderItemError)` that breaks on first failure.
Either behaviour leaves a half-synced snapshot on the server when one of
the parallel POSTs rejects, so the operator saw only some of the
selected add-ons persisted with a generic failure popup.
## Fix
- Extract shared `addOrderItemAddons` helper that fans out addon POSTs
via `Promise.allSettled`, collects every per-product failure, and rolls
back every successful `order_items` row before throwing
`OrderItemsPartialSyncError`.
- Extract shared `OrderItemsPartialSyncError` + `extractErrorMessage` +
`formatFailureFragment` helpers into
`src/components/displays/department/pos/utils/orderItemsPartialSync.js`.
- Wire desktop `SelectProductsFormPOS.vue → addAddonsToOrderMiddleware`
to the shared helper.
- Wire mobile `syncMobileOrderItems.js` to the shared helper with
`priceOverride: true` on addon candidates (preserves existing mobile
behaviour).
## Tests
- New `tests/unit/order-items-addon-fanout.spec.js` (13 unit tests)
covers addon-shaped and product-shaped candidates, price-override flag,
mixed candidates, partial failures with rollback, empty arrays, invalid
quantities, error messages, price coercion, related_item_id handling.
- New e2e test in `tests/e2e/pos-customer-rules.spec.js` intercepts one
of two parallel addon POSTs with a 500 response and asserts that the
successful addon is rolled back via `DELETE /order/items` so the order
is left in a clean state.
## Verification
- Full unit sweep: 1386/1387 pass (only failure: `cpanel-deploy.spec.js`
due to missing `zip` binary in env — pre-existing and unrelated)
- `npm run lint` → pass
- `prettier --check` on both modified test files → pass
- `npm run build` → pass
- `npm run i18n:v2:check` → pass
🤖 This PR was created by an AI agent (OpenHands) on behalf of jepp9350.
Co-authored-by: openhands <openhands@all-hands.dev>
---------
Co-authored-by: openhands <openhands@all-hands.dev>