[18.0] pos_pwa, pos_offline: PWA infrastructure and offline mode for POS#1504
Draft
[18.0] pos_pwa, pos_offline: PWA infrastructure and offline mode for POS#1504
Conversation
Add Service Worker with scope /pos (separate from Odoo's /odoo SW), web manifest, and offline fallback page. Extends the POS index template to register the SW and inject PWA meta tags. Cache strategies: - Static assets: stale-while-revalidate - Fonts/images: cache-first - POS HTML shell: network-first with 4s timeout - RPC calls: network-only (managed by PosData)
Depends on pos_pwa. Adds: - Startup offline: patches PosData.loadInitialData() to cache load_data response in IndexedDB and fall back to cache on ConnectionLostError - IndexedDB stores: overrides initIndexedDB() to add _pos_load_data_cache and _pending_orders stores (DB version bump to 2) - Robust sync: patches PosStore to persist pending orders to IndexedDB, restore on startup, and retry with exponential backoff (1s→60s max) - Offline payment: patches PaymentScreen to hide terminal-based methods when offline and skip server sync during finalization - Backend: idempotent sync_from_ui (UUID dedup), auto rescue session creation for orders arriving after session close - Config: offline_enabled field on pos.config
pos_pwa: - Fix manifest.webmanifest returning 404 by using make_response with json.dumps instead of make_json_response - Replace deprecated apple-mobile-web-app-capable with mobile-web-app-capable meta tag pos_offline: - Fix TypeError "Cannot read properties of undefined (reading 'id')" on POS load. The custom IndexedDB stores (_pos_load_data_cache, _pending_orders) were being included in readAll() results and passed to missingRecursive/loadData which tried to process them as ORM models. Now loadIndexedDBData is overridden to strip custom stores before processing.
The /pos/manifest.webmanifest route returns 404 despite the controller being loaded (service-worker.js works). This may be caused by Odoo's routing treating .webmanifest as a static file extension. Add alternative route /pos/pwa_manifest without file extension and use it in the manifest link tag. Keep the original route as fallback.
pos_pwa: - Serve offline page as static HTML file instead of QWeb template (server may be unreachable when SW needs to serve it) - Add /pos/pwa_manifest route alongside /pos/manifest.webmanifest - Remove QWeb pos_offline_page template (replaced by static HTML) pos_offline: - Rewrite loadInitialData to call orm.call directly instead of super, intercepting network errors BEFORE the core's blocking window.alert. On ConnectionLostError or network failure, falls back to IndexedDB cache immediately. - Add _isNetworkError helper for non-ConnectionLostError failures (ERR_INTERNET_DISCONNECTED, Failed to fetch, etc.) - Patch BarcodeParser.fetchNomenclature to catch network errors and return empty nomenclature instead of blocking POS startup
…ocessServerData) - data_service_patch: call orm.call directly instead of super to intercept network errors before the core's blocking window.alert. Add _waitForIndexedDB to handle race condition where IndexedDB is not yet ready when loadInitialData runs offline. Add _isNetworkError for ERR_INTERNET_DISCONNECTED etc. - pos_store_patch: override afterProcessServerData to skip readDataFromServer when offline, go directly to markReady/showScreen. - Add barcode_reader_patch to manifest assets list.
pos_pwa: - Add /web/image to SW cache-first strategy so category images, user avatars, and product images are cached on first online visit pos_offline: - Override allowProductCreation to return false when offline instead of making an RPC call that throws ConnectionLostError
- Fix "Response body is already used" error in cacheFirst strategy by cloning response before passing to cache.put - Add /web/image URL pattern to cache-first strategy so POS category images and user avatars are cached for offline use
The /pos/pwa_manifest route returns 404 despite being registered in the controller (werkzeug routing issue with underscore URLs). The /pos/manifest.webmanifest route works correctly, so use that. Using t-att-href to bypass OCA XML link validation.
data_service_patch (#2): - Replace super.loadInitialData() fallback with inline alert + return. The old approach re-called the RPC (which would fail again) and could show duplicate error alerts. pos_store_patch (#3): - afterProcessServerData now runs local-only logic when offline: adds paid unsynced orders to pending set, marks residual orders as cancelled. Previously it skipped all of this. pos_store_patch (#4): - Fix exponential backoff retry being effectively dead. The core's syncAllOrders catches ConnectionLostError internally without re-throwing, so our catch block never triggered. Now we check if pendingOrders still exist after sync returns and schedule retry if so.
#1 data_service_patch: await _cacheLoadData to ensure persistence #5 payment_screen_patch: re-check offline status on addNewPaymentLine for reactive filtering (not just onMounted) #6 Remove dead offline_banner.xml template (no component uses it) #7 data_service_patch: check config.offline_enabled before caching #8 pos_order: simplify _get_valid_session, remove redundant super call #9 data_service_options_patch: use base version + offset instead of hardcoded DB version 2, call super.initIndexedDB() first #10 pos_sw: add MAX_CACHE_ENTRIES (300) with FIFO eviction in cacheFirst
Replace dynamic import() of @web/core/l10n/dates with static import. Dynamic imports fail offline because the module resolver tries to fetch from the server. Static imports are bundled in the asset.
- Add online event listener in PosStore to reset offline flag and trigger syncAllOrders when network comes back - Periodic sync check (reduced from 30s to 10s) now also resets the offline flag when navigator.onLine is true, fixing the case where the core's online listener doesn't fire (e.g. DevTools network throttle) - This ensures orders created offline are synced automatically when connectivity is restored, without requiring a page reload
Add readme/ directories with DESCRIPTION.md, CONTRIBUTORS.md, CONFIGURE.md, and ROADMAP.md for both modules following OCA maintainer-tools template.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two new modules that enable the Odoo 18.0 Point of Sale to operate fully offline:
pos_pwa— PWA Infrastructure/pos(separate from Odoo's/odooSW)pos_offline— Full Offline Capability (depends onpos_pwa)load_dataresponse in IndexedDB, falls back to cache onConnectionLostErrorwith automatic IndexedDB readiness waitonlineevent listener + 10s periodic check reset the offline flag and triggersyncAllOrdersfetchNomenclatureerrors caught gracefully (scanning disabled, POS still loads)sync_from_uiwith UUID dedupoffline_enabledfield onpos.config(enabled by default)How to test
pos_pwa+pos_offlineDB_FILTERis set (e.g.DB_FILTER=^mydb$) — required forauth='public'routes[POS Offline] Cached load_data for config Xin consoleTechnical notes
patch()from@web/core/utils/patchfor compatibility_pos_load_data_cache,_pending_orders) are stripped fromreadAll()beforeloadDatato prevent ORM model processing crashesloadInitialDatacallsorm.calldirectly instead ofsuperto intercept network errors before the core's blockingwindow.alert