Monday, July 20, 2026
23 changes · saas-19.2
Enhancements to existing features
The French balance sheet report now places establishment costs before fixed assets and includes additional impairment accounts in the relevant tangible fixed asset lines. This improves report consistency and helps ensure fixed asset values are presented more completely for French reporting.
Original PR description
Move establishment costs before fixed assets in the French balance sheet, and include the missing 2912, 2913, 2914, and 2915 impairment accounts in the relevant tangible fixed asset amortization/provision lines. task-6226138 Forward-Port-Of: odoo/enterprise#124562 Forward-Port-Of: odoo/enterprise#117689
No task ID Forward-Port-Of: odoo/odoo#276936
Original PR description
No task ID Forward-Port-Of: odoo/odoo#276936
Resolved issues and error corrections
The restaurant appointment point-of-sale test was adjusted to avoid repeating a data reload step that could cause false test failures. This improves test reliability without changing the normal user experience, since users are not expected to perform these immediate back-to-back reloads.
Original PR description
The `RestaurantAppointmentTour` performed two consecutive "Reload Data" actions. The first reload starts synchronizing data from the server to IndexedDB. If the second reload is triggered before the synchronization completes, it deletes the IndexedDB while it is still in use, causing the synchronization process to crash. This race condition can be reproduced locally by running the tour with `cpu_throttling` enabled. In practice, users never trigger two consecutive reloads, so the second reload step in the tour is unnecessary. Remove it to avoid the artificial race condition while preserving the intended test coverage. Task-[6364951](https://www.odoo.com/odoo/project/1737/tasks/6364951) Runbot Error-[941343](https://runbot.odoo.com/odoo/error/941343), [941344](https://runbot.odoo.com/odoo/error/941344), [941345](https://runbot.odoo.com/odoo/error/941345)
Miscellaneous changes
Steps to reproduce ------------------ 1. On a bank payment method, enable "Identify Customer". 2. Create a lot of orders paid with this method (the client reporting the issue had 1700), and put a customer on each order. 3. Close the session. With "Identify Customer" enabled, closing the session creates one `account.payment` for each payment and posts it one by one, then reconciles each payment separately. With many payments this is slow and the close takes several minutes and the worker i
Original PR description
Steps to reproduce ------------------ 1. On a bank payment method, enable "Identify Customer". 2. Create a lot of orders paid with this method (the client reporting the issue had 1700), and put a customer on each order. 3. Close the session. With "Identify Customer" enabled, closing the session creates one `account.payment` for each payment and posts it one by one, then reconciles each payment separately. With many payments this is slow and the close takes several minutes and the worker is stopped by its time limit, so we get the error "Cursor already closed". Now we create and post all these payments at once in a single batch, and reconcile the payments in one call too. Benchmark --------- These numbers come from closing a session that has 1700 orders paid with such a payment method, on a test database: - before: the close did not finish after more than 10 minutes. - after: the close takes around 75 seconds. opw-6242303 Forward-Port-Of: odoo/odoo#269306
Users can no longer trigger a server error by typing an unknown name in the Add to or Reduce From fields on commission adjustments. The change removes an unsupported quick-create option, keeping the adjustment workflow stable and avoiding confusing error messages.
Original PR description
Steps to reproduce: - Go to Sales > Commission > Adjustments - In Add to/Reduce From, type a name with no match - Click Create '<name>' -> server crashes (RPC_ERROR) Cause: `sale.commission.plan.user._rec_name` is a Many2one (`user_id`). Default `name_create` wrote the typed text into that integer column, which Postgres rejected. Fix: - Disable quick-create on `add_user_id`/`reduce_user_id` in the Adjustments list view Version: saas-18.3 to master opw-6384909 Forward-Port-Of: odoo/enterprise#124431
Polish currency rates from NBP are no longer shifted by an extra day because the system already uses the required previous day's rate. This prevents rates from being dated too far back and helps Polish accounting use the correct exchange rate dates.
Original PR description
In Poland, it is mandatory to use the previous day's currency rate. We used to achieve this by shifting the rate's date by one day. Since https://github.com/odoo/odoo/pull/231948, we already use the previous day's rate, so the shift is no longer needed. See: https://www.odoo.com/mail/message/1118975071 Forward-Port-Of: odoo/enterprise#123849
This PR hides the Upgrade button for modules imported via file, as these modules cannot be upgraded without re-importing a file. Displaying the upgrade button in such cases could lead to confusion or unexpected behavior. Task: 4643890 Forward-Port-Of: odoo/odoo#213122
Original PR description
This PR hides the Upgrade button for modules imported via file, as these modules cannot be upgraded without re-importing a file. Displaying the upgrade button in such cases could lead to confusion or unexpected behavior. Task: 4643890 Forward-Port-Of: odoo/odoo#213122
Steps to reproduce: --- Case 1: 1. Add a carousel at the top of the page. 2. Apply the parallax effect to all slides. 3. Reload the page and navigate to the second slide without scrolling. 4. Scroll slightly and observe the jump in background position. 5. Scroll back to the top and notice the changed background position. Case 2: 1. Add a carousel to the page. 2. Apply the parallax effect to each slide. 3. Add text over the carousel and enable animation on appearance and on eve
Original PR description
Steps to reproduce: --- Case 1: 1. Add a carousel at the top of the page. 2. Apply the parallax effect to all slides. 3. Reload the page and navigate to the second slide without scrolling. 4. Scroll…
Steps to reproduce: --- Case 1: 1. Add a carousel at the top of the page. 2. Apply the parallax effect to all slides. 3. Reload the page and navigate to the second slide without scrolling. 4. Scroll slightly and observe the jump in background position. 5. Scroll back to the top and notice the changed background position. Case 2: 1. Add a carousel to the page. 2. Apply the parallax effect to each slide. 3. Add text over the carousel and enable animation on appearance and on every time. 4. Slide between images and observe the bouncing effect. https://github.com/user-attachments/assets/d11ee430-8b95-4903-b001-1b9ba8ee58cd Why this change? --- When the parallax effect is applied to carousel slides, only the first slide is initialized with the correct [parallaxHeight](https://github.com/odoo/odoo/blob/616e82d7b3a53b1facf481e783baed3e99393d3c/addons/website/static/src/interactions/parallax/parallax.js#L49). The remaining slides are initialized while hidden, resulting in a computed height of 0. When scrolling, the background height is recalculated with the correct value, producing a visible jump effect. Additionally, when text or any animated content is added on top of the carousel (especially animations triggered on appearance or on every slide change), a [resize event](https://github.com/odoo/odoo/blob/616e82d7b3a53b1facf481e783baed3e99393d3c/addons/website/static/src/interactions/animation.js#L111) is triggered on each transition. This causes `updateBackgroundHeight()` to run again. Since hidden slides were initialized with height 0 and later receive their actual height, the recalculation produces a noticeable bouncing effect. After this commit: --- The parallax computation now relies on the parent element’s `getBoundingClientRect().height` when used inside a carousel. Since the carousel slide height is already defined and consistent, this ensures correct initialization even when the slide is not yet visible. OPW: 5909351 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#250290
Add a regression test ensuring that setting a relative date global filter on a dynamic Odoo pivot does not crash while the pivot table is reloading. Task: 6306263 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Original PR description
Add a regression test ensuring that setting a relative date global filter on a dynamic Odoo pivot does not crash while the pivot table is reloading. Task: 6306263 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Rather than assign `log_target` and blow up if the third branch is taken, use a `defaultdict` and just increment the count in each branch on hit. The continue in the third branch is not strictly necessary, but that way it protects us if anyone decides to add post-processing which also doesn't account for the third branch. Forward-Port-Of: odoo/odoo#276757 Forward-Port-Of: odoo/odoo#276481
Original PR description
Rather than assign `log_target` and blow up if the third branch is taken, use a `defaultdict` and just increment the count in each branch on hit. The continue in the third branch is not strictly necessary, but that way it protects us if anyone decides to add post-processing which also doesn't account for the third branch. Forward-Port-Of: odoo/odoo#276757 Forward-Port-Of: odoo/odoo#276481
Steps to reproduce --- 1. Install a carrier that requires a positive package shipping weight (e.g. UPS) and enable Packages. 2. From the Barcode app, process a delivery and Put in Pack the products. 3. Validate the transfer. Issue --- Validation is refused because the package has no positive shipping weight, even though its computed weight is correct. The package shipping weight is only set in `_post_put_in_pack_hook` from `context['weight']`, which is filled exclusively by the put i
Original PR description
Steps to reproduce --- 1. Install a carrier that requires a positive package shipping weight (e.g. UPS) and enable Packages. 2. From the Barcode app, process a delivery and Put in Pack the products.…
Steps to reproduce --- 1. Install a carrier that requires a positive package shipping weight (e.g. UPS) and enable Packages. 2. From the Barcode app, process a delivery and Put in Pack the products. 3. Validate the transfer. Issue --- Validation is refused because the package has no positive shipping weight, even though its computed weight is correct. The package shipping weight is only set in `_post_put_in_pack_hook` from `context['weight']`, which is filled exclusively by the put in pack wizard. Flows that bypass the wizard, like the Barcode app, never provide that key, so the stored `shipping_weight` stays 0 while carriers reading it directly reject the delivery. The Barcode app suppresses the wizard through the `barcode_view` context, so its put in pack goes straight to the hook without any weight. https://github.com/odoo/enterprise/blob/6b18215bfa98ef636b63f4fafbba1d25264f4883/stock_barcode/models/stock_move_line.py#L193-L196 This handling was centralized in this hook by 9a2ff6f4033e, so every wizard-bypassed pack leaves the field empty. Defaulting it to the package computed weight via `_get_weight` when the context did not provide one makes the stored value correct for any carrier, independently of the flow that created the package. https://github.com/odoo/odoo/blob/4235b48c86077bd5bceb9e817cc45b2eec8697e8/addons/stock_delivery/models/stock_move.py#L111-L115 opw-6297689 Forward-Port-Of: odoo/odoo#273876 Forward-Port-Of: odoo/odoo#272510
Steps to reproduce 1. Set the user timezone to one with a positive UTC offset, e.g. Europe/Brussels. 2. Open Payroll > Work Entries in pivot view and apply the built-in "Current Month" filter. 3. Expand an employee row and group by Date > Day. Expected: only days of the current month are visible (April 1..30 at the time of writing). Actual: the last day of the previous month (March 31) is included and the last day of the current month (April 30) is missing — the whole window
Original PR description
Steps to reproduce 1. Set the user timezone to one with a positive UTC offset, e.g. Europe/Brussels. 2. Open Payroll > Work Entries in pivot view and apply the built-in "Current Month" filter. 3.…
Steps to reproduce 1. Set the user timezone to one with a positive UTC offset, e.g. Europe/Brussels. 2. Open Payroll > Work Entries in pivot view and apply the built-in "Current Month" filter. 3. Expand an employee row and group by Date > Day. Expected: only days of the current month are visible (April 1..30 at the time of writing). Actual: the last day of the previous month (March 31) is included and the last day of the current month (April 30) is missing — the whole window is shifted one day earlier. Issue The relative-date DSL added in https://github.com/odoo/odoo/commit/d1ea43f6721116914762ea323d8a5987f043e87f operates on a tz-aware datetime starting from `now` when no explicit anchor is given. With the filter at https://github.com/odoo/odoo/blob/d58f4ed332af35f6de26a93f07adf05368731e20/addons/hr_work_entry/views/hr_work_entry_views.xml#L190-L192, `=1d` resets the clock to 00:00 and the day to the 1st in the user's timezone, evaluating to `2026-04-01 00:00:00+02:00` for Brussels. `parse_date` then normalizes that to UTC-naive `2026-03-31 22:00:00` (see https://github.com/odoo/odoo/blob/d58f4ed332af35f6de26a93f07adf05368731e20/odoo/tools/date_utils.py#L196-L199). When the ORM compares this to the `fields.Date` column `date` it truncates via `datetime.date()` (see https://github.com/odoo/odoo/blob/d58f4ed332af35f6de26a93f07adf05368731e20/odoo/orm/fields_temporal.py#L138-L158), yielding `2026-03-31`. The effective domain becomes `[2026-03-31, 2026-04-30)` instead of `[2026-04-01, 2026-05-01)`, which is why March 31 leaks in and April 30 disappears. Solution Anchor the filter to `today` instead of the implicit `now`. The DSL documents `today` as a starting point that yields a `date` value (see https://github.com/odoo/odoo/blob/d58f4ed332af35f6de26a93f07adf05368731e20/odoo/tools/date_utils.py#L108-L144), so `today =1d` resolves to the first of the month as a calendar date end-to-end and never goes through the tz-aware datetime → UTC-naive roundtrip that was silently shifting the window. opw-6123753 Forward-Port-Of: odoo/odoo#274105 Forward-Port-Of: odoo/odoo#260727
In the Outlook calendar sync, it's possible to have the same attendee twice. That's because the normalized email wasn't used to check for preexisting attendee. To reproduce, sync event with the organizer, also an attendee, using high case in the email such as: ORGANIZER: Mike@organizer.com ATTENDEE: Mike@organizer.com ATTENDEE: John@attendee.com opw-6186606 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#272258 For
Original PR description
In the Outlook calendar sync, it's possible to have the same attendee twice. That's because the normalized email wasn't used to check for preexisting attendee. To reproduce, sync event with the organizer, also an attendee, using high case in the email such as: ORGANIZER: Mike@organizer.com ATTENDEE: Mike@organizer.com ATTENDEE: John@attendee.com opw-6186606 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#272258 Forward-Port-Of: odoo/odoo#268201
From versions 18.3 to 19.3, company_id and siret refer to the same field. This caused an issue when connecting to the PDP using siret or siren could overwrite company_id. task-6327304 Forward-Port-Of: odoo/odoo#276805 Forward-Port-Of: odoo/odoo#275276
Original PR description
From versions 18.3 to 19.3, company_id and siret refer to the same field. This caused an issue when connecting to the PDP using siret or siren could overwrite company_id. task-6327304 Forward-Port-Of: odoo/odoo#276805 Forward-Port-Of: odoo/odoo#275276
Steps to reproduce: ------------------------------------ 1. Install barcode_gs1_nomenclature Module. 2. Go to Inventory > Configuration > Barcode Nomenclature. 3. Open any barcode nomenclature. Observation: ------------------------------------ The "Is GS1 Nomenclature" field is visible in the list header. Issue: ------------------------------------ The field uses the invisible attribute, which hides the field values but does not hide the corresponding list header, resulting in an e
Original PR description
Steps to reproduce: ------------------------------------ 1. Install barcode_gs1_nomenclature Module. 2. Go to Inventory > Configuration > Barcode Nomenclature. 3. Open any barcode nomenclature. Observation: ------------------------------------ The "Is GS1 Nomenclature" field is visible in the list header. Issue: ------------------------------------ The field uses the invisible attribute, which hides the field values but does not hide the corresponding list header, resulting in an empty header column. Solution: ------------------------------------ Replace the 'invisible' attribute with 'column_invisible' so that both the field values and the corresponding list header are hidden. opw-6390731 Forward-Port-Of: odoo/odoo#276532
**Steps to reproduce:** - Install the `accountant` and `sale_management` modules. - Create and confirm a SO with a product whose `Invoicing Policy` is set to `Ordered quantities`. - Click `Create Invoices` > select `Regular invoice` > `Create Draft`. (leave the invoice in the draft state). - Navigate to Accounting > Review > Invoiced Not Delivered. - Select the newly created invoice and click `Create Accrual Entries`. **Error:** `ZeroDivisionError: division by zero` **Root Ca
Original PR description
**Steps to reproduce:** - Install the `accountant` and `sale_management` modules. - Create and confirm a SO with a product whose `Invoicing Policy` is set to `Ordered quantities`. - Click `Create…
**Steps to reproduce:** - Install the `accountant` and `sale_management` modules. - Create and confirm a SO with a product whose `Invoicing Policy` is set to `Ordered quantities`. - Click `Create Invoices` > select `Regular invoice` > `Create Draft`. (leave the invoice in the draft state). - Navigate to Accounting > Review > Invoiced Not Delivered. - Select the newly created invoice and click `Create Accrual Entries`. **Error:** `ZeroDivisionError: division by zero` **Root Cause:** At [1], `processed_qty` is initialized with `0`. If all the selected invoices are in the draft state, the code never enters the for loop, so `processed_qty` remains 0. Later, it attempts to divide the `amount` by `processed_qty`, causing an error. **Fix:** This commit prevents errors when creating accrual entries for draft invoices. [1]: https://github.com/odoo/odoo/blob/a7d285a833f5b1d47a11a1b8ec31e3730c1c854c/addons/account/wizard/accrued_orders.py#L258-L265 opw-6332652 Forward-Port-Of: odoo/odoo#273330
Steps to reproduce: ================== 1. Add a Donation snippet on a page 2. Click "Donate Now" 3. Switch the website language on the payment page => Configured amounts/descriptions disappear Cause: ====== The donation snippet posts its configuration (prefilled amounts, display options, descriptions) in the request body. If the user switches the website language on the payment page, it triggers a page reload via a GET request which drops the original form body (this also applies to
Original PR description
Steps to reproduce: ================== 1. Add a Donation snippet on a page 2. Click "Donate Now" 3. Switch the website language on the payment page => Configured amounts/descriptions disappear Cause:…
Steps to reproduce: ================== 1. Add a Donation snippet on a page 2. Click "Donate Now" 3. Switch the website language on the payment page => Configured amounts/descriptions disappear Cause: ====== The donation snippet posts its configuration (prefilled amounts, display options, descriptions) in the request body. If the user switches the website language on the payment page, it triggers a page reload via a GET request which drops the original form body (this also applies to a simple page refresh). As a result, the kwargs were empty and the page fell back to the default free-amount input, losing the configured options. Solution: ========= Implement Post/Redirect/Get: on POST, redirect to /donation/pay with the options in the query string so any later GET (language switch, refresh, iframe src reload) re-renders a fully configured page without losing data. Alternative Solution: ===================== We could also store the options in the session, but the current solution is much simpler and avoids session pollution. opw-6282391 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#271460
Issue: - After confirming a sale order with a reward applied (e.g. an ewallet reward), unlocking it, and editing it so the reward line's cost changes (e.g. adding a product and re-claiming the reward), the coupon's point balance updates correctly. - However, the corresponding loyalty.history record's used value is never refreshed, so it keeps showing the old cost instead of the new one. Steps to reproduce: - Enable Loyalty and Lock Confirmed Sales in Sales settings. - Create an ewallet ty
Original PR description
Issue: - After confirming a sale order with a reward applied (e.g. an ewallet reward), unlocking it, and editing it so the reward line's cost changes (e.g. adding a product and re-claiming the…
Issue: - After confirming a sale order with a reward applied (e.g. an ewallet reward), unlocking it, and editing it so the reward line's cost changes (e.g. adding a product and re-claiming the reward), the coupon's point balance updates correctly. - However, the corresponding loyalty.history record's used value is never refreshed, so it keeps showing the old cost instead of the new one. Steps to reproduce: - Enable Loyalty and Lock Confirmed Sales in Sales settings. - Create an ewallet type loyalty.program and generate an ewallet for a partner with e.g. 1000 points. - Create a sale order for that partner, add a product worth 100, and claim the ewallet reward (reward line created with points_cost = 100). - Confirm the order. loyalty.history shows used = 100 (correct), card balance shows 900 (correct). - Unlock the order, add a second product worth 100, and claim the reward again (same reward line updates to points_cost = 200). - Lock the order again. - Check the loyalty.card: balance is correctly 800. - Check loyalty.history for that order: used still shows 100 instead of 200. Fix: - Updated _update_loyalty_history() in sale_order.py to create a new history line if none exists for the given card and order combination. - Updated write() in sale_order_line.py to correctly sync history lines when a reward line is modified on a confirmed order, handling both same-coupon updates via delta and coupon changes by subtracting the old coupon cost and adding the new one separately. - Added test_loyalty_history_created_on_post_confirm_reward to verify that a history line is created when a reward is claimed on a confirmed order where no history line existed before. - Added test_loyalty_history_updated_on_points_cost_write to verify that history.used is updated by the correct delta when points_cost changes on a reward line of a confirmed order. Impact: - Ensures a coupon's usage history stays accurate after a confirmed order is unlocked and edited. - Prevents the loyalty.history used field from silently going stale while the actual point balance is correct. - Not scoped to ewallet specifically — since points_cost semantics are the same across program types, this also corrects the same class of staleness for other reward types (discount, gift_card, etc.) when a reward line's cost changes post-confirmation. Forward-Port-Of: odoo/odoo#276961 Forward-Port-Of: odoo/odoo#273688
On touch devices (smartphones, tablets), the in-browser notification sound plays through the media audio channel, which is not affected by the device's silent mode. This causes unexpected sound playback when the device is set to silent. Suppress _playSound() on devices with maxTouchPoints > 1 (touch/mobile). Push notifications handle alerts on mobile and properly respect the device's silent mode. The side effect is that also laptops with touch screen are affected and need to use web push n
Original PR description
On touch devices (smartphones, tablets), the in-browser notification sound plays through the media audio channel, which is not affected by the device's silent mode. This causes unexpected sound playback when the device is set to silent. Suppress _playSound() on devices with maxTouchPoints > 1 (touch/mobile). Push notifications handle alerts on mobile and properly respect the device's silent mode. The side effect is that also laptops with touch screen are affected and need to use web push notifications in order to get notification sounds played. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#276675 Forward-Port-Of: odoo/odoo#275797
Translatable rows that are identified by a stable key rather than by their text (e.g. selection option labels in ir.model.fields.selection, and other module-reflected metadata written through upsert_en()) can have their English source text changed by a module upgrade while keeping the same key. For example product.template's selection value 'consu' went from meaning "Consumable" to meaning "Goods" between 17.0 and 18.0, without the stored value itself changing. In that situation, upsert
Original PR description
Translatable rows that are identified by a stable key rather than by their text (e.g. selection option labels in ir.model.fields.selection, and other module-reflected metadata written through…
Translatable rows that are identified by a stable key rather than by their text (e.g. selection option labels in ir.model.fields.selection, and other module-reflected metadata written through upsert_en()) can have their English source text changed by a module upgrade while keeping the same key. For example product.template's selection value 'consu' went from meaning "Consumable" to meaning "Goods" between 17.0 and 18.0, without the stored value itself changing. In that situation, upsert_en() always refreshes the 'en_US' entry from the current code, so English is never wrong. But every other language keeps whatever value it had, since upsert_en() blindly merges the new 'en_US' entry into the existing jsonb without checking whether the previous 'en_US' value actually changed. When the module's .po file is later reloaded, TranslationImporter.save() finds a non-empty value already there for each language and, by design, does not overwrite it unless explicitly asked to (`overwrite`) on a record that isn't `noupdate`. That protection is correct when the English text hasn't changed (it keeps manual translation customizations across upgrades), but here it ends up protecting a translation of a source string that no longer exists. As a result, a value whose meaning changes across versions keeps showing its old label in every language except English, forever, unless someone forces a translation overwrite. Fix this in upsert_en() itself: when the incoming 'en_US' value differs from the one currently stored, drop every other language already stored for that row instead of merging into it. Those translations were made for a source string that no longer exists, so there is nothing worth protecting. Once dropped, they are simply missing from the jsonb, so TranslationImporter.save() naturally lets the freshly (re)loaded translations in on its next run, without needing to know anything about source drift, and `overwrite`/`noupdate` keep their exact original meaning for every other case. Task-6333080 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#276725 Forward-Port-Of: odoo/odoo#275080
### Contains the following commits: https://github.com/odoo/o-spreadsheet/commit/fba73fe75b [REL] 19.2.21 [Task: 0](https://www.odoo.com/odoo/2328/tasks/0) https://github.com/odoo/o-spreadsheet/commit/0266f73f3b [FIX] scorecardPanel: mismatch baseline/keyValue [Task: 6380393](https://www.odoo.com/odoo/2328/tasks/6380393) https://github.com/odoo/o-spreadsheet/commit/5bb486c814 [FIX] TableComputedStylePlugin: skip table style for invalid pivot [Task: 6306263](https://www.odoo.com/odoo/2328/tasks/
Original PR description
### Contains the following commits: https://github.com/odoo/o-spreadsheet/commit/fba73fe75b [REL] 19.2.21 [Task: 0](https://www.odoo.com/odoo/2328/tasks/0)…
### Contains the following commits: https://github.com/odoo/o-spreadsheet/commit/fba73fe75b [REL] 19.2.21 [Task: 0](https://www.odoo.com/odoo/2328/tasks/0) https://github.com/odoo/o-spreadsheet/commit/0266f73f3b [FIX] scorecardPanel: mismatch baseline/keyValue [Task: 6380393](https://www.odoo.com/odoo/2328/tasks/6380393) https://github.com/odoo/o-spreadsheet/commit/5bb486c814 [FIX] TableComputedStylePlugin: skip table style for invalid pivot [Task: 6306263](https://www.odoo.com/odoo/2328/tasks/6306263) https://github.com/odoo/o-spreadsheet/commit/ecef954c0e [FIX] squisher: adapted ranges that become cells [Task: 6390727](https://www.odoo.com/odoo/2328/tasks/6390727) https://github.com/odoo/o-spreadsheet/commit/43bc2d9e1d [FIX] index: export `positionMap` [Task: 6159213](https://www.odoo.com/odoo/2328/tasks/6159213) https://github.com/odoo/o-spreadsheet/commit/1584d3c4b1 [FIX] Pivots: cache `getPivotIdFromPosition` for performance [Task: 6159213](https://www.odoo.com/odoo/2328/tasks/6159213) Co-authored-by: Florian Damhaut (flda) <flda@odoo.com> Co-authored-by: Anthony Hendrickx (anhe) <anhe@odoo.com> Co-authored-by: Alexis Lacroix (laa) <laa@odoo.com> Co-authored-by: Lucas Lefèvre (lul) <lul@odoo.com> Co-authored-by: Adrien Minne (adrm) <adrm@odoo.com> Co-authored-by: Ronak Mukeshbhai Bharadiya (rmbh) <rmbh@odoo.com> Co-authored-by: Dhrutik Patel (dhrp) <dhrp@odoo.com> Co-authored-by: Rémi Rahir (rar) <rar@odoo.com> Co-authored-by: Pierre Rousseau (pro) <pro@odoo.com> Co-authored-by: Vincent Schippefilt (vsc) <vsc@odoo.com> Co-authored-by: Marceline Thomas (matho) <matho@odoo.com>
When sending an email to an invoice mail alias without valid attachment, a custom bounce mail is sent to inform the customer that it was bounced because of a missing attachment. Currently, the bounce email from will default to the bounce alias of the main company of the DB. In a multi company setup, where each company might have their own mail alias domain, this can be confusing for the receiving party that didn't send the original email to the main company's alias domain. ## Propo
Original PR description
When sending an email to an invoice mail alias without valid attachment, a custom bounce mail is sent to inform the customer that it was bounced because of a missing attachment. Currently, the bounce…
When sending an email to an invoice mail alias without valid attachment, a custom bounce mail is sent to inform the customer that it was bounced because of a missing attachment. Currently, the bounce email from will default to the bounce alias of the main company of the DB. In a multi company setup, where each company might have their own mail alias domain, this can be confusing for the receiving party that didn't send the original email to the main company's alias domain. ## Proposed solution: We call `_routing_create_bounce_email` with the right company in the context, since the journal company is already detected. Additionally, in the context of the accounting code calling the bounce private method, we pass an explicit value for `reply_to` ## How to reproduce it: 1) Set up DB with two companies (companyA and companyB). Install account. 2) Set up two mail alias domains (companya.com and companyb.com) 3) Send an email to the invoice alias of companyB (e.g. invoices@companyb.com). -> bounce email will have FROM as "notifications@companya.com" (the notifications alias of companyA) Example headers before: ``` Reply-To: "YourCompany" <catchall@companya.com> To: customer@example.com Date: Thu, 02 Jul 2026 11:18:49 -0000 Return-Path: bounce@companya.com From: "MAILER-DAEMON" <bounce@companya.com> ``` After fix: ``` Reply-To: info@companyb.com To: customer@example.com Date: Thu, 02 Jul 2026 11:16:19 -0000 Return-Path: bounce@companyb.com From: "MAILER-DAEMON" <notifications@companyb.com> ``` opw-6342778 Forward-Port-Of: odoo/odoo#273671
The reversal wizard always injected a shared 'R4' onto every credit note it created, so a credit note of a simplified invoice never got the required 'R5', and reversing several invoices of mixed simplified status at once forced the same reason onto all of them. The wizard now derives the reason per original move ('R5' if it is simplified, 'R4' otherwise) instead of using one shared value, while still respecting an explicit manual choice. `l10n_es_is_simplified` already freezes correctly
Original PR description
The reversal wizard always injected a shared 'R4' onto every credit
note it created, so a credit note of a simplified invoice never got
the required 'R5', and reversing several invoices of mixed simplified
status at once forced the same reason onto all of them.
The wizard now derives the reason per original move ('R5' if it is
simplified, 'R4' otherwise) instead of using one shared value, while
still respecting an explicit manual choice. `l10n_es_is_simplified`
already freezes correctly on the credit note via its existing compute,
so no new compute is needed on `l10n_es_edi_verifactu_refund_reason`.
task-6358684
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#276187