Thursday, September 3, 2026
20 changes · saas-19.3
Resolved issues and error corrections
This fix ensures that selected website themes are fully applied during setup, including visible changes such as headers and footers. It prevents theme installation steps from being skipped, giving users the expected website appearance after choosing a theme.
Original PR description
--- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#286221 Forward-Port-Of: odoo/odoo#283174
Analytic plan applicability now keeps the default setting on screens that are not tied to a specific business area, such as Work Centers or Employees. This prevents invoice-specific company rules from incorrectly making analytic fields mandatory in unrelated workflows, reducing user confusion and data-entry friction.
Original PR description
### Issue: When an analytic plan has applicability lines with a company filter, the applicability is incorrectly applied on views that do not define a `business_domain`, such as Work Centers or…
### Issue: When an analytic plan has applicability lines with a company filter, the applicability is incorrectly applied on views that do not define a `business_domain`, such as Work Centers or Employee views For example, if a plan has: - Default Applicability: Unavailable - A line with Domain: Invoice, Company: My Company, Applicability: Mandatory Opening the analytic distribution on a Work Center shows `Mandatory` instead of the default `Unavailable` ### Cause: In commit https://github.com/odoo/odoo/commit/ffcf2ee1a3185ef73db93bfd95625844506692c5 `_get_score` was updated to return `0.5` when the applicability line's company matches the caller's company, even when no `business_domain` is provided In `_get_applicability`, the loop selects the first rule whose score exceeds the current minimum, which starts at `0`: https://github.com/odoo/odoo/blob/710e056e5171af2ab72d7d7793da3518f12faf5e/addons/analytic/models/analytic_plan.py#L255-L264 A score of `0.5` is enough to win over the default applicability, so a company-only match on a domain-specific rule incorrectly overrides the default when no `business_domain` is passed ### Steps to reproduce: - Install `mrp` and `accountant` with demo data - Enable Analytic Accounting in Settings - Open the Internal analytic plan and edit its applicability line: -- Remove the account prefix -- Default Applicability: Unavailable -- Domain: Invoice, Company: My Company (SF), Applicability: Mandatory - Go to Manufacturing > Configuration > Work Centers - Open any work center and click on Analytic Distribution Before the fix, Internal is shown as Mandatory instead of Unavailable Removing the company from the applicability line confirms the issue opw-6404884 Forward-Port-Of: odoo/odoo#280312
This fix ensures Adyen payment notifications use the correct values from the data they receive. It helps keep payment processing and transaction status updates accurate for businesses using Adyen.
Original PR description
opw-6512723 Forward-Port-Of: odoo/odoo#284773
This fixes a billing issue where manually increasing a timesheet invoice could prevent later timesheets from being invoiced. Businesses can now invoice future time periods correctly while preserving expected behavior for refunds.
Original PR description
## Issue When a use rmanually increases the invoiced quantity on a timesheet invoice, subsequent timesheets logged in future periods can no longer be invoiced. The system assumes that the most recent…
## Issue
When a use rmanually increases the invoiced quantity on a timesheet invoice, subsequent timesheets logged in future periods can no longer be invoiced. The system assumes that the most recent logged hours are already covered by the previously over-invoiced amount, blocking the billing of the most recent timesheet entries.
## Steps to reproduce
1. Install *Sales Timesheet* (`sale_timesheet`)
2. Create a Product P
- Product Type: Service
- Invoicing Policy: Based on Timesheets
- Create on Order: (Project &) Task
3. Create an SO:
- Customer: Any
- Product: P (any quantity)
- Confirm
4. Record hours on the task created:
- 06/01/2026 (June 1st): 1 hour
- 07/01/2026 (July 1st): 1 hour
5. Create the invoice for the June timesheet entry (by setting a timesheets period when creating the invoice), then **change the quantity to any value strictly greater than 2** and confirm.
6. Create the invoice for July
7. **An "Invalid Operation" error appears, stating that there's nothing to invoice, even though the timesheet entry from July was never invoiced.**
## Cause
Since https://github.com/odoo/odoo/commit/3b86ac3d180993239b63fd305ee0ba1f17f7eb20, the computation of the quantity to invoice changed to include the difference between the quantity delivered and the quantity already invoiced. In the flow described by the steps to reproduce above, the quantity to invoice is larger than the quantity delivered, making the `qty_to_invoice` equal to `0.0`.
https://github.com/odoo/odoo/blob/626d31fa0191bfe7ca8e1fcf177357eeeb60f2c7/addons/sale_timesheet/models/sale_order.py#L331-L334
The reason for the fix above being the partial refunding of timesheet-related invoices, we can keep that solution when working with refunded invoices, and keep the previous behavior for other cases.
This solution solves the issue in the steps to reproduce above, but was also manually tested on the issues from https://github.com/odoo/odoo/commit/3b86ac3d180993239b63fd305ee0ba1f17f7eb20 and https://github.com/odoo/odoo/commit/64cf4afabd0c5ef040cf87fc6f3125fe0bb81bbb.
opw-6485674
Forward-Port-Of: odoo/odoo#286301
Forward-Port-Of: odoo/odoo#284470Planning managers who do not have HR permissions can now publish scheduled shifts successfully. The update ensures the system can still find the relevant employee information needed to send publication notifications, so staff receive their planning updates as expected.
Original PR description
Steps: - As a planning manager without HR right - Schedule planning slots - Publish them Actual result: - employee_ids is empty as user don't have access HR - nothing is sent Expected result: - Planning publication is well sent Similar to: https://github.com/odoo/enterprise/pull/127503
The Helpdesk SLA Status Analysis report now measures Hours Open from ticket creation to closing, matching the Ticket Analysis report. This fixes misleading SLA reporting where tickets assigned immediately could show no open time, giving managers a more accurate view of how long issues remain unresolved.
Original PR description
1. Open Helpdesk > Tickets and create a ticket on the team "Customer Care", assigned to yourself 2. More than an hour later, move it to the "Solved" stage to close it 3. Open Helpdesk > Reporting > Ticket Analysis, switch to the pivot view and pick the "Hours Open" measure -> the ticket holds the hour it stayed open 4. Open Helpdesk > Reporting > SLA Status Analysis and pick the "Hours Open" measure as well -> the ticket holds nothing, as it was assigned as soon as it was created odoo/enterprise#47454 added the "Hours Open" measure of the ticket analysis to the SLA status analysis, but computes it up to the assignment date instead of the closing date. The measure therefore holds the hours until the ticket was assigned, which the report already offers as "Working Hours to Assign". With this commit, both reports count the hours from the creation of the ticket to its closing. Forward-Port-Of: odoo/enterprise#130170
This fix ensures that when a refund is created in Italian POS with a fiscal printer, users are sent to the payment page for the refund order instead of the product screen or original order. This prevents confusion at checkout and helps staff complete refund transactions correctly.
Original PR description
**Issue**: 1. From versions 18.4 to 19.1 inclusive, the system redirects to the product screen; 2. From version 19.2 onward, the redirection targets the payment page of the original order instead of the refund order. **Expected behavior**: The system navigates to the payment page for the refund order. **Steps to reproduce**: - Set up an Italian fiscal printer; - Open a POS session and process an order; - Create a refund for the order. [Ticket link](https://www.odoo.com/odoo/project/49/tasks/6499079) opw-6499079 Forward-Port-Of: odoo/enterprise#130044 Forward-Port-Of: odoo/enterprise#129758
Belgian payroll now correctly treats public holidays as unpaid when they fall after the guaranteed sick pay period, even if a new medical certificate starts that day. This prevents employees from being overpaid due to timing or timezone differences in leave records.
Original PR description
Steps to reproduce: - Employee on full-time sickness certified month by month (one hr.leave per medical certificate), guaranteed salary period already exhausted. - Compute payroll for the month whose public holiday falls on the first day of a new certificate. - Public holiday work entry stays paid, every other day that month is correctly unpaid. Cause: the 30-day lookback compared exact datetimes instead of calendar dates, so a certificate starting the same day as the holiday but later in clock-time got excluded. Also ignored timezone: date_from is stored in UTC and needs localizing before taking .date(). Fix: compare local calendar dates via hr.leave's own request_date_from/request_date_to instead of raw UTC datetimes. Added a regression test for the split-certificate case. Task 6512860
Refund payslips are now recalculated correctly whenever payroll worked days or pay lines are recomputed, not only during a reset. This helps prevent payroll refund amounts from becoming inaccurate after updates or recalculations.
Original PR description
Instead of only resetting correctly the refund payslip in the reset, ensure that anytime we recompute worked days or lines, the refund is correctly computed.
This fixes a manufacturing issue where Odoo could show a false consumption warning for serial-tracked components bought on demand. Users can now complete affected manufacturing orders without unnecessary warning prompts when the component quantity matches what was expected.
Original PR description
Steps to reproduce the bug: - Warehouse configured for 2-Step Manufacturing - Component "C1": - Routes: MTO + Buy - Tracking: By Unique Serial Number - Create a Finished product: - Route: Manufacture…
Steps to reproduce the bug:
- Warehouse configured for 2-Step Manufacturing
- Component "C1":
- Routes: MTO + Buy
- Tracking: By Unique Serial Number
- Create a Finished product:
- Route: Manufacture
- Tracking: By Unique Serial Number
- BoM:
- component "C1": 1 unit
- Create and confirm the Manufacturing Order
- Generate/assign the serial number for the finished product immediately, before the related purchase order is even confirmed
- Confirm the Purchase Order
- Receive the component
- Transfer the component to WH/Pre-Production
- Click Produce All
Problem:
A Consumption Warning was displayed stating that the consumed quantity differs from the expected quantity, even though the consumed quantity was exactly equal to the BoM quantity and no manual quantity change was performed. Clicking "Set Quantities & Validate" completed the MO successfully, hiding the inconsistency.
`_get_consumption_issues()` only counts a raw move's quantity as "consumed" when `move.picked` is `True`
[(odoo/addons/mrp/models/mrp_production.py#L1791)](https://github.com/odoo/odoo/blob/19.0/addons/mrp/models/mrp_production.py#L1791).
Generating the finished product's serial number calls `_set_qty_producing()`
[(odoo/addons/mrp/models/mrp_production.py#L1402)](https://github.com/odoo/odoo/blob/19.0/addons/mrp/models/mrp_production.py#L1402).
, which itself only sets `move.picked = True` when `move.quantity` is truthy
[(odoo/addons/mrp/models/mrp_production.py#L1443)](https://github.com/odoo/odoo/blob/19.0/addons/mrp/models/mrp_production.py#L1443).
At that point the MTO component had not been received yet, so `move.quantity` was still 0 and `picked` was never set. Once the component was later received and transferred to Pre-Production, `_action_assign()` correctly reserved the raw move (`quantity` became correct), but nothing ever went back to flip `picked` to `True`, since `_set_quantities()`
[(odoo/addons/mrp/models/mrp_production.py#L2932)](https://github.com/odoo/odoo/blob/19.0/addons/mrp/models/mrp_production.py#L2932).
only calls `_set_qty_producing()` again when `qty_producing` is still falsy, which was no longer the case. `_get_consumption_issues()` therefore still counted the consumed quantity as 0 against the expected BoM quantity.
Solution:
In `pre_button_mark_done()`, for auto productions (single unit being produced), after `_set_quantities()` runs, also mark as `picked` any non-manual-consumption raw move that is not yet `picked` but whose reserved `quantity` already exactly matches its own `product_uom_qty`. This is scoped to auto productions only, since on a multi-unit production a raw move's aggregate `quantity` can equal its `product_uom_qty` by coincidence (reservation for future backorder steps) even though only part of it is meant to be consumed for the current step.
opw-6421531
Forward-Port-Of: odoo/odoo#285405
Forward-Port-Of: odoo/odoo#281258Mexican electronic tax documents attached to expense-generated vendor receipts are now correctly visible to users. This ensures employees and accounting teams can access the CFDI XML files already stored in the system and allows the related SAT status checks to run for these receipts.
Original PR description
**Current behavior:** Currently, account moves created from expenses (type receipt) don't include the XML when it is a CFDI. Causing users cannot see their attachment even though it is created on DB. https://docs.google.com/videos/d/1eFSAA-wvUzPDwIJDeiS2dkne94lGM7QBxRfjN3HxaLw/play **Versions:** 19+ **Fix:** Implementing a new helper to identify those moves that can actually hold a CFDI document (for now, all `is_invoice()` documents + vendor bill receipt, `in_receipt`), so now, we include 'in_receipts' in _compute_l10n_mx_edi_cfdi_state_and_attachment, _compute_l10n_mx_edi_document_ids, _compute_l10n_mx_edi_update_sat_needed and l10n_mx_edi_cfdi_try_sat. As these documents might need to fetch SAT services as well. Task-id: [6397080](https://www.odoo.com/odoo/project/49/tasks/6397080) Forward-Port-Of: odoo/enterprise#129074 Forward-Port-Of: odoo/enterprise#126493
Italian withholding tax returns submitted in quarterly months no longer trigger the periodic VAT return export by mistake. This prevents crashes and avoids generating or attaching the wrong LIPE file to non-VAT returns.
Original PR description
In Italy the periodic VAT return (LIPE) is filed quarterly, so `is_quarter_month` gates the XML export wizard on March, June, September and December. That field only looks at the date, never at the…
In Italy the periodic VAT return (LIPE) is filed quarterly, so `is_quarter_month` gates the XML export wizard on March, June, September and December. That field only looks at the date, never at the kind of return, so every Italian return locked on a quarter month was treated as a LIPE. Submitting the withholding tax return therefore opened the LIPE export wizard. Depending on the record the wizard receives, it either crashes while reading the VP lines missing from the withholding report, or silently generates a LIPE file and attaches it to another return. Add an `is_lipe_return` field telling whether the return actually produces the LIPE. Steps to reproduce: - Install `l10n_it_xml_export` and `l10n_it_edi_withholding_reports` on an Italian company with monthly returns - Open Accounting > Reporting > Tax Return - Review and submit the withholding returns up to February - Review then submit the March withholding return --> The LIPE export wizard opens, and the export fails on the withholding report. opw-6255016 Forward-Port-Of: odoo/enterprise#126676 Forward-Port-Of: odoo/enterprise#125263
The Shopee sales connector now handles shipment label errors within the affected batch instead of stopping the full scheduled process. This helps ensure remaining shipment batches continue to be processed even when Shopee returns incomplete label data for some orders.
Original PR description
Currently, an error occurs when the `sync_shopee_pickings` cron tries to fetch shipment labels for multiple batches of shipments. If some shipments do not contain the required data in the Shopee API…
Currently, an error occurs when the `sync_shopee_pickings` cron tries to fetch
shipment labels for multiple batches of shipments. If some shipments do not
contain the required data in the Shopee API response, the error causes the
cron to stop, preventing the remaining batches from being processed.
Error:
`ValueError: KeyError('status') while evaluating 'model._sync_shopee_pickings()'`
This issue was introduced by a recent refactoring commit [1] focused on linting
and formatting the Python file. The commit replaced the generic `Exception`
with `UserError` when handling errors during batch label printing.
As a result, errors other than `UserError` are no longer handled at the batch
level. When such an error occurs, the entire cron process stops instead of
failing only the affected batch and continuing with the remaining batches.
This commit fixes the above issue by handling generic `Exception` during
shipment label generation, ensuring that the error is handled for the
affected batch while the remaining batches continue to be processed.
[1]: https://github.com/odoo/enterprise/commit/cfe5d947579739ad770ac3f1525ce157af530846#diff-e072c3ecf980add685bb4a4f25cf2b0ab5d4625988e4afc2c4d4faeae687ca88R199
sentry-7685943988The UK CIS report now correctly includes payment information for receipts that use CIS taxes, matching the behavior already available for vendor bills. This helps businesses keep CIS reporting complete and accurate when recording supplier receipts.
Original PR description
With the l10n_uk_reports_cis module installed: - Create a vendor bills and add a CIS tax --> This vendor's bills appear correctly in the report. - Create a receipt and add a CIS tax --> This type of bill appears in the report, but the payment is not showing up. opw-6282548 Forward-Port-Of: odoo/enterprise#127807 Forward-Port-Of: odoo/enterprise#124043
This fixes an issue where pages using Odoo live chat could block common browser keyboard shortcuts such as focusing the address bar with Alt+D. The change keeps Odoo shortcut overlays working when available, while leaving browser shortcuts untouched when there are no Odoo shortcuts to show.
Original PR description
Description of the issue/feature this PR addresses: When `im_livechat` (or any page) boots the OWL web stack with no `[data-hotkey]` / `withOverlay` targets, pressing Alt still called…
Description of the issue/feature this PR addresses: When `im_livechat` (or any page) boots the OWL web stack with no `[data-hotkey]` / `withOverlay` targets, pressing Alt still called `preventDefault()` in `hotkey_service`. That blocked browser shortcuts such as Alt+D (focus address bar) on websites that embed the livechat scripts. See https://github.com/odoo/odoo/issues/267928 Current behavior before PR: - Pressing the overlay modifier (Alt, or Ctrl on macOS mapped to `"alt"`) always set `overlaysVisible = true` and called `preventDefault()`, even when there were no hotkeys to overlay. - A follow-up key (e.g. D while Alt is held) then also hit `preventDefault()` because overlays were considered visible. - Loading `/im_livechat/loader/...` + `assets_embed.js` on an external site therefore broke browser Alt shortcuts. Desired behavior after PR is merged: - `addHotkeyOverlays` returns whether overlays were actually displayed. - `preventDefault` on the overlay modifier runs only when there is at least one overlay target. - Pages with no hotkeys (typical livechat embed) leave browser shortcuts alone. - When hotkeys exist, Alt still shows overlays and cancels the default, same as before. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#284145
This fix prevents the Polish bank verification module from trying to process every payment record during installation. It helps avoid installation crashes for companies with large payment histories, making module setup more reliable.
Original PR description
account.payment model computes every record l10n_pl_verification_id at module installation (l10n_pl_bank_verification), causing crash in case of db with a large number of records wrong method name correction: _auto_init instead of init and call super after creating the db column see odoo/odoo#282504 Forward-Port-Of: odoo/odoo#285968
UPS shipments could be rejected when a customer's invoicing address did not have its own name. The update uses a fallback name for the billing contact, helping ensure shipments can be confirmed successfully in UPS delivery flows.
Original PR description
Issue ----- By default, invoicing addresses of existing partners are created without a name. This leads to the deliveries being rejected by UPS. Steps to reproduce ----- - Set Up UPS - Create a Customer - Create an invoicing address with no name - Create a SO for the partner & confirm - UPS delivery - Open the picking and confirm it > UPS rejects the shipment /!\ I could not reproduce in testing environment, so this is based off user steps in their production DB. /!\ Cause ----- The partner being used in `_set_invoice` was changed in #119747 but this use case was missed due to the error not occuring in test mode. ----- Ticket: opw-6485164 Forward-Port-Of: odoo/enterprise#128933
This fixes accrued expense entries for purchase products that are billed based on ordered quantities. Confirmed purchase orders can now generate the expected accruals even before goods are received, improving accounting accuracy for month-end reporting.
Original PR description
Version: -------- - 19.0+ Steps to reproduce: ------------------- - Install `stock`, `purchase`, and `accountant` - Create a storable product with: - Tracking Inventory enabled - Control Policy set…
Version: -------- - 19.0+ Steps to reproduce: ------------------- - Install `stock`, `purchase`, and `accountant` - Create a storable product with: - Tracking Inventory enabled - Control Policy set to **Ordered Quantities** - Create and confirm a Purchase Order with some unit price - Do not receive or invoice the order - From the Purchase Order gear menu, click **Accrued Expense Entry** Issue: ------ The Accrued Expense Entry wizard opens, but no accounting lines are generated. For products invoiced on **Ordered Quantities**, the ordered quantity should already be accrued even though nothing has been received. Cause: ------ This issue was introduced after this changes [commit](https://github.com/odoo-dev/odoo/commit/81f25bc57b8433a65bf33950c64dc7582240a229) Previously, the accrual wizard relied on the stored `qty_to_invoice` field, whose computation already respected the product's Control Policy. For products invoiced on **Ordered Quantities**, `_compute_qty_invoiced()` computes the quantity to invoice from the ordered quantity: https://github.com/odoo/odoo/blob/810a02a577c2811dc5c12f0abf45eebb9cf96d00/addons/purchase/models/purchase_order_line.py#L147-L152 The refactoring replaced this logic with the new `amount_to_invoice_at_date` field, which always computes the invoicable quantity as: `qty_received_at_date - qty_invoiced_at_date` https://github.com/odoo/odoo/blob/65dbcabcd243abf24d6d3c3788d2caff66485790/addons/purchase/models/purchase_order_line.py#L282-L285 This formula ignores the product's Control Policy. For products invoiced on Ordered Quantities, before any receipt: `qty_received_at_date` = 0 `qty_invoiced_at_date` = 0 therefore: `amount_to_invoice_at_date` = 0 The Accrued Expense wizard filters out lines whose `amount_to_invoice_at_date` is zero: https://github.com/odoo/odoo/blob/65dbcabcd243abf24d6d3c3788d2caff66485790/addons/account/wizard/accrued_orders.py#L168-L178 As a result, the purchase order line is excluded entirely and the wizard produces no accounting entries. The same assumption is also used later in `account.accrued.orders.wizard._compute_move_vals()` when computing tax-included amounts, causing incorrect accrual values for Ordered Quantities products whenever receipts and invoices differ. Fix: ---- Introduce `_get_qty_to_invoice_at_date()`, mirroring the existing purchase_method logic used by _compute_qty_invoiced(). The helper returns: product_qty - qty_invoiced_at_date for Ordered Quantities products; `qty_received_at_date` - `qty_invoiced_at_date` for Received Quantities products. Now products invoiced on `Ordered Quantities` become accruable as soon as the Purchase Order is confirmed; --- opw-6290782 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#284507 Forward-Port-Of: odoo/odoo#276435
The Helpdesk unread ticket filter now includes tickets where the latest message was an automatic system message, such as one from OdooBot. This helps support teams avoid missing customer tickets that still need attention after being created through the website.
Original PR description
**Steps to reproduce:** - Install website_helpdesk. - Create a team and enable website form. - Create a ticket through the website. - Apply the Unread filter. **Issue:** system generated message, such as message authored by OdooBot, were not considered when determining whether a ticket was unanswered. **Cause:** the search method only considered the last message when its author matched the ticket's partner. **Fix:** Consider a ticket unanswered when the last message's author matches the ticket's partner, or when the last message is an automatic system generated message. task-5138678 Forward-Port-Of: odoo/enterprise#130100
This fix prevents users from seeing repeated error popups when the browser clears or blocks the storage used for unread message badge counts. The unread badge may fail to update in those rare storage situations, but the main Odoo interface will continue working normally.
Original PR description
Description of the issue/feature this PR addresses: The unread counter feeding the PWA app badge is written to the `odoo-mail-unread-db` IndexedDB database, through a connection opened once, at…
Description of the issue/feature this PR addresses: The unread counter feeding the PWA app badge is written to the `odoo-mail-unread-db` IndexedDB database, through a connection opened once, at module load, and cached for the life of the page by the bundled idb-keyval (3.2.0), which cannot reopen it. When the browser drops the origin's storage (eviction under disk pressure, site data cleared, a privacy extension purging it), that connection is closed for good, and as `updateAppBadge()` discards the promise returned by `idbKeyval.set()`, the rejection reaches the user as a client error dialog. Reported in production on a backend tab left open overnight (Firefox, 19.0): ``` UncaughtPromiseError > InvalidStateError Uncaught Promise > IDBDatabase.transaction: Can't start a transaction on a closed database ``` Reproduced on a demo database below, with `?debug=assets` so that the stack points at the source: lines 23 and 24 of the vendored idb-keyval are the `db.transaction()` call of `_withIDBStore`, on the connection cached at module load. <img width="1600" height="590" alt="error-dialog-debug" src="https://github.com/user-attachments/assets/e2b1aa46-501a-43d4-b4c0-20def1f529ef" /> Current behavior before PR: 1. log into the backend and leave the tab open 2. in the devtools, Application > Storage, tick *only* "IndexedDB" and click "Clear site data", as the browser itself does when it evicts the origin (the session cookie is left untouched, so the tab keeps working) 3. receive a message, or do anything else that changes the unread counter The dialog opens, and opens again on every counter update for the whole life of the tab, since the dead connection is never replaced; the counter is not saved any more either. Two variants of the same code: the write also rejects, without anything being cleared, when the origin runs out of storage quota (`QuotaExceededError`), and when the browser forbids storage for the origin, `indexedDB.open()` throws during module evaluation, so `store_service_patch.js` fails to load entirely. Desired behavior after PR is merged: The store is opened lazily, dropped whenever saving the counter fails, and the write is retried once on a new connection: a single counter update after the connection was closed saves it again. Opening the database is guarded too, for the synchronous throw. When the retry fails as well the error is ignored, the app badge being cosmetic. `mail/static/tests/web/app_badge.test.js` covers the retry, the recovery of a permanently closed connection, and the database that cannot be opened at all. The three tests fail on the current code with the uncaught `IDBDatabase.transaction` error. Introduced in 19.0 by c04c4cb715902fe95618a15e18233cd001bdb304, and identical from saas-19.1 to master, hence this PR against 19.0. The corporate CLA for ERPVibe Limited is submitted in #283477. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#283480