feat: enhance offline batch and serial number management - #359
feat: enhance offline batch and serial number management#359MohamedAliSmk wants to merge 10 commits into
Conversation
- Added functions to persist and consume batch and serial data for items in offline mode. - Implemented caching for batch and serial numbers during item searches. - Improved handling of cached data to ensure accurate updates and retrievals when offline. - Enhanced error handling for background caching operations.
engahmed1190
left a comment
There was a problem hiding this comment.
🔍 Full review — code logic, security, performance, coverage
Verdict: request changes. The offline read fix is correct and should ship. The consumption-accounting layer bolted on top has three high-severity defects and wasn't part of PN-83.
Structural read
What actually fixes "Batch/Serial unable to select at offline mode" is ~30 lines:
| Change | Why it fixes PN-83 |
|---|---|
persistItemBatchSerialData in batchesResource.onSuccess |
the dialog's own batch fetch was never written to IndexedDB |
persistItemBatchSerialData in fetchSerials |
serial store results were never written to IndexedDB |
the two new return guards in loadBatchesOrSerials |
offline previously fell through to batchesResource.reload(), which fails |
Everything else — the three new cacheBatchSerialForItems call sites, consumeCachedBatchQty, consumeCachedSerials, returnCachedSerials, and the create-on-missing db.items.put — is offline inventory accounting. Every defect below lives in that second layer. Cutting it removes ~90 of the 161 added lines and every high-severity finding.
Security
No new attack surface. No new endpoints, no new server-side code, no HTML/URL/query sink. get_batch_serial_data_for_items is @frappe.whitelist() and unchanged by this diff. All new code writes client-side IndexedDB, already trusted-per-device here.
The security-relevant findings are the inventory-integrity ones: T1/T2 understate stock, T3 lets the same serial reach two invoices. For a POS that's the risk surface that matters.
Coverage
Zero. find . -name "*.test.*" returns nothing, and there's no test block in POS/vite.config.js despite test / test:run / test:coverage in package.json — yarn test cannot run today. This diff adds four pure-ish mutators with real arithmetic (set diffing, Math.max(0, qty - n), sort-merge); they're the most testable and highest-consequence code in the changeset.
Verified
- Patch applies cleanly to current
develop(git apply --3way) — no conflicts, no duplicated call sites. npx biome linton the changed files: no new violations (one pre-existinguseOptionalChainatBatchSerialDialog.vue:365).- T1 reproduced with a standalone script — see the inline comment.
✅ Task list — please do not merge or close this PR until every box is checked
🔴 Blocking
- T1 — Batch qty subtracted twice offline. Delete
consumeCachedBatchQty;availableBatchesalready handles cart subtraction.BatchSerialDialog.vue:516 - T2 — Batch consumption has no return path (4 divergence paths: line remove, qty edit,
addItemuom-merge droppingbatch_no,addItemthrow). Resolved by T1.items.js:254 - T3 — Sold serials returned to durable cache on offline submit. Fix in
clearCart(useInvoice.js:1182) with areturnSerialsflag,falsefrom the two post-submit call sites.serialNumber.js:182
🟡 Should fix
- T4 — Wrap the surviving cache mutators in
db.transaction("rw", db.items, ...); they're non-atomic read-modify-write with.catch(() => {})at every call site.items.js:210 - T5 — Offline serial fallback bypasses
isCacheValid(warehouse + 5-min TTL).BatchSerialDialog.vue:460 - T6 — Drop the two pagination
cacheBatchSerialForItemssites; keep the search one with a recently-fetchedSet+ TTL. Up to 25 sequential POSTs per search today.itemSearch.js:1224,1339,1725
🟢 Nice to have
- T7 — Don't
putphantom item rows (noitem_name/barcodes); guard theundefinedwarehouse inreturnCachedSerials.items.js:173,236 - T8 — Add a
testblock toPOS/vite.config.js+ oneitems.test.jswithdbmocked, covering the consume/return round-trip,Math.max(0, …)clamping, andparseSerialNumberson both string and array input. Not a suite — one file. - T9 — Wrap the 118-char import at
BatchSerialDialog.vue:304.
Latent / no action needed this PR
- Persisted batch/serial data has no warehouse key. Harmless today (dialog is bound to
shiftStore.profileWarehouse,POSSale.vue:613) but a shift/profile change leaves stale data with nothing to invalidate it. Worth an issue.
🤖 Generated with Claude Code
availableBatches already subtracts cart qty from warehouseBatches; consumeCachedBatchQty was decrementing IndexedDB as well, under-reporting stock offline. Co-authored-by: Cursor <cursoragent@cursor.com>
Batch consume had no symmetric return, so line-remove, qty-edit, uom-merge, and addItem throw permanently drifted offline stock. Deleting the mutator closes all four paths; availableBatches remains the source of truth. Co-authored-by: Cursor <cursoragent@cursor.com>
clearCart could not tell abandoned cart from sold cart, so offline submit wrote sold serials back into IndexedDB. Pass returnSerials:false from both payment-success paths; abandoned clears still restore by default. Co-authored-by: Cursor <cursoragent@cursor.com>
Wrap persist/update/consume/return in db.transaction('rw', db.items) so
concurrent consumes or races with background cache writes cannot silently
drop updates.
Co-authored-by: Cursor <cursoragent@cursor.com>
Raw serialStore.getSerials() ignored warehouse match and 5-minute TTL. IndexedDB via getCachedSerialData already covers offline after fetchSerials persists. Co-authored-by: Cursor <cursoragent@cursor.com>
Pagination sites duplicated shift-start sync and could fire 20-per-request sequential POSTs per page. Keep search-path caching with a 5-minute recently-fetched Set so the same items are not re-fetched every search. Co-authored-by: Cursor <cursoragent@cursor.com>
Do not put incomplete {item_code, batch/serial} rows that blank the product
grid. returnCachedSerials now takes an explicit warehouse (or refuses when
the cache is empty so warehouse would be undefined).
Co-authored-by: Cursor <cursoragent@cursor.com>
Enable yarn test via a vite test block and cover parseSerialNumbers plus consume/return round-trip, phantom-row skip, and warehouse guard. Batch qty Math.max clamping left with consumeCachedBatchQty (T1/T2). Co-authored-by: Cursor <cursoragent@cursor.com>
Match the multi-line import style used elsewhere in the file. Co-authored-by: Cursor <cursoragent@cursor.com>
Review follow-up (local, not pushed yet)Addressed T1–T9 as 9 separate commits on
T8 note: Holding push until author review of the local commits. |
T8 — testsAgreed the mutators were the most testable / highest-consequence part of the changeset, and Done in
Coverage included:
Note:
|
🛑 Review action list — do not merge/close until all checked
Full review · 11 inline comments · details in each
Blocking
consumeCachedBatchQtyclearCartShould fix
db.transaction("rw", …)isCacheValid(warehouse + TTL)Nice to have
db.itemsrows +undefinedwarehouse guarditems.test.js(repo has zero tests)