Friday, September 4, 2026
44 changes · saas-19.3
Enhancements to existing features
Forum pages are now included in the website sitemap using much less memory. This reduces repeated sitemap failures on high-traffic sites like odoo.com, improving reliability for search engines and site indexing.
Original PR description
On odoo.com, /sitemap.xml fails dozens of times a day with a 500 `MemoryError` before finally succeeding and being cached. The primary cause is the loading of all forum posts. With this commit, we…
On odoo.com, /sitemap.xml fails dozens of times a day with a 500 `MemoryError` before finally succeeding and being cached. The primary cause is the loading of all forum posts. With this commit, we avoid fetching all fields and specifically the `content` field which is html which can contain embeded images in base64. | | Peak memory | |--------|--------| | Before | 1.7GB | | After | 610MB | ### Before <img width="2505" height="400" alt="sitemap-forum-posts-before" src="https://github.com/user-attachments/assets/0ce3aca9-52ed-46a6-ac9b-245c754a08a7" /> ### After <img width="1864" height="291" alt="image" src="https://github.com/user-attachments/assets/6686b0fc-d0ce-4dc2-b2ff-d34e37695104" /> Another approach could be to load posts in batch and free the memory between each batch. It has been measured and yield very similar results in practice. I chose this approach because it matches the future behavior in version 20.0 (see https://github.com/odoo-dev/odoo/commit/fe186b07475cf15a50558d9bdacd0599c17c39be) --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
French accounting localization now treats Monaco as part of the European Union VAT grouping and adds a separate EU group excluding Monaco where needed. The change also introduces localization packages for French overseas departments and regions, helping businesses apply the right accounting and tax setup in those locations.
Original PR description
[[IMP] l10n_fr,l10n_fr_account: European union vat](https://github.com/odoo/odoo/commit/9870a204bb64bdf8a11e433eef3a9e9f8f3fad86)
This commit will add Monaco to the European union vat country group and
create a new country group that is the same than the european union but
without monaco
task-6321652
[[ADD] l10n_{gf,gp,mq,re,yt}: new localization](https://github.com/odoo/odoo/commit/58518e1d7df773a70c595bd3842a33ac6be611bb)
Like Monaco, we need to add new localization package for
the drom countries
task-6321652
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#284404The rental stock module now reuses an existing shared rule for identifying rented products instead of keeping its own separate version. This makes future customizations easier and more consistent across rental-related features, with no expected change for everyday users.
Original PR description
There is already an existing hook on product.product ([sale_renting.models.product_product.ProductProduct._get_qty_in_rent_domain](https://github.com/acsone/enterprise/blob/2cd105c93bb7199a5ecea67a919a7ab5d9251cbf/sale_renting/models/product_product.py#L24)), use it to get the domain, so it can be inherited by other modules.
While the upstream method uses `('product_id', 'in', self.ids)` instead of this one's `('product_id', '=', self.id)`, this isn't an issue since there's a `self.ensure_one()` just above.
Forward-Port-Of: odoo/enterprise#129191The Bancontact payment integration now uses the latest approved logo and receipt visuals required by Bancontact. It also adds German and English frame assets, improving brand compliance and support for more customer languages.
Original PR description
Bancontact sent us new mark specifications regarding the use of their logo on receipts. At the same time, new assets became available for the frames, adding support for new languages (German and English). --- Task: https://www.odoo.com/odoo/project/1737/tasks/6531896
Resolved issues and error corrections
This fixes Italian electronic invoices so credit notes and final invoices reference only the relevant down payment document, not unrelated past invoices or the document itself. It helps prevent incorrect FatturaPA XML content and reduces confusion or rejection risk in Italian invoicing workflows.
Original PR description
Steps to reproduce: - Create a sale order with a down payment term. - Invoice the down payment (down payment invoice A). - Credit that down payment invoice directly (credit note A), without…
Steps to reproduce: - Create a sale order with a down payment term. - Invoice the down payment (down payment invoice A). - Credit that down payment invoice directly (credit note A), without reconciling it with anything else. - Generate the FatturaPA XML for credit note A. - Observe that DatiFattureCollegate lists both down payment invoice A and credit note A itself, instead of only down payment invoice A. On an order with several down payment invoices/credit notes over time, every one of them is listed instead of just the document the current credit note actually reverses. Cause of the issue: _l10n_it_edi_export_data built downpayment_moves from self.invoice_line_ids._get_downpayment_lines().move_id with no filtering. _get_downpayment_lines() (sale override) returns every invoice line ever created against the same down payment sale order line, across the whole life of the order, not just the invoice the current document actually relates to. The template renders DatiFattureCollegate for every one of those moves unconditionally, on top of the already-correct reversed_entry_id/reconciled_moves fallback, including the current document itself. Solution: Restrict downpayment_moves to lines with a negative price_subtotal, mirroring the existing down payment deduction-line detection a few lines above, and explicitly exclude the current document. This limits DatiFattureCollegate's down payment entries to the case they are meant for: a final invoice actually deducting a previously invoiced down payment. opw-6429716 Forward-Port-Of: odoo/odoo#285555 Forward-Port-Of: odoo/odoo#279938
This fixes an issue where incoming VoIP call ringtones ignored the audio output selected in softphone settings and instead played through the system default device. Users with multiple speakers or headsets will now hear incoming call rings on the device they chose, making call handling more predictable.
Original PR description
Commit [1] introduced the possibility to select the output device used for all softphone call voices and ringtones. For the ringtone, it only worked for the outgoing calls though. Indeed, the…
Commit [1] introduced the possibility to select the output device used for all softphone call voices and ringtones. For the ringtone, it only worked for the outgoing calls though. Indeed, the ringtone object specific to each session* was only known by the AudioManager instance once an incoming call was actually >answered<. While the softphone was ringing, the output device used was left to default browser selection which often means default OS selection. Steps to reproduce: - Have a working Odoo voip provider - Have two audio outputs on your computer and the OS allowing output to both at the same time (if connecting a headset, it often means having to select the computer speaker as the main source os side). - Call your softphone with your smartphone => The softphone rings, through the main OS selection - Change output device in the softphone setting => Bug 1: Nothing changes - Hangup - Change output device to something other that the default OS output in the softphone setting - Call your softphone with your smartphone => Bug 2: The softphone rings, still through the main OS selection *: this bug is another symptom of the fact that we really should have only one ringtone service instead of one object per session. This will be probably be done in master later, as it requires more checks. [1]: https://github.com/odoo/enterprise/commit/e964e4b28604f32550566ad1b9f17aed89653597 Related to task-6533808
DHL shipments in Odoo will no longer automatically request a courier pickup. This aligns DHL with other shipping carriers and prevents avoidable errors when pickup dates or time windows are not configured.
Original PR description
Before this commit, the DHL REST module had the pick-up request set, which led to an unnatural use flow, needing to set up an scheduled date for pick-up, and several errors from DHL when these were not properly configured. This feature was unique to this module among the shipping carriers as we don't normally request pick-up for packages and leave it to user to decide how to do it. This commit removes the request for pick up and the logic that was needed to make it work. This will make the behavior consistent with other carriers in Odoo, and avoids the need to set scheduled pick-up windows and having to deal with unnecessary errors. opw-6148927 Forward-Port-Of: odoo/enterprise#123773
Point of Sale preparation tickets now print pickup and order times using the shop's configured timezone instead of the account that generated the receipt. This prevents customers and staff from seeing incorrect UTC-based times on self-order receipts.
Original PR description
Preparation tickets are rendered server-side for self orders. format_datetime and format_time only fall back on env.user.tz, and the render runs under whichever user triggered it: the public user for an online payment confirmed on /payment/status/poll, OdooBot for the payment cron, the self ordering default user for an OBOX print. None of them is guaranteed to have a timezone, so the ticket could be printed in UTC: an 18:15 pickup showed as 16:15 Take the timezone from res.company.tz instead. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
This fix makes employee records with the same name appear in a consistent order when absence information is prepared. It prevents inconsistent test/build results and helps ensure the correct return-date information is selected reliably.
Original PR description
Before this commit, TestPartner.test_res_partner_to_store fails on the all-modules and per-country builds: AssertionError: '2024-06-06' != '2024-06-07' : Return date is the return date of the main…
Before this commit, TestPartner.test_res_partner_to_store fails on the all-modules and per-country builds: AssertionError: '2024-06-06' != '2024-06-07' : Return date is the return date of the main user of the partner This happens because the test reads the first entry of the hr.employee list, which holds one employee per user of the partner: back on the 7th for the main user, on the 6th for the other. This comes from "[FIX] hr*: load out-of-office dates from all user employees", which added the employees of the partner to the payload, where it held those of the main user only. The problem is that the list keeps the order of employee_ids, which is 'name' with no tiebreaker, and both employees are named test1, as an employee takes the name of its user and creating the second user renames the partner. Postgres is then free to return either one first, and the failing builds get the second one. This commit fixes the issue by ordering the employees on 'name, id', so that employees sharing a name keep a stable order instead of the one the database picks. The test asserts the whole hr.employee list, one entry per user of the partner, rather than its first entry alone, and that assertion pins the order. https://runbot.odoo.com/odoo/error/945994 Forward-Port-Of: odoo/odoo#286603 Forward-Port-Of: odoo/odoo#286236
This fixes outdated internal documentation about how temporary records are accessed. The documentation now reflects that standard access rights and record rules apply, reducing confusion for developers and administrators without changing product behavior.
Original PR description
Since 6d8688bb124d, TransientModel records use the regular access rights mechanisms instead of being implicitly restricted to their creator. The docstring was not updated with that change and has therefore incorrectly documented creator-only access since 14.0. Forward-Port-Of: odoo/odoo#286374 Forward-Port-Of: odoo/odoo#286251
The record selector in reference fields now shows a placeholder, making it clearer that users must choose a specific record after selecting a model. This helps prevent incomplete links from being saved and later disappearing after a page refresh.
Original PR description
Steps to reproduce: 1. Install Sign. 2. Open the form view of a document. 3. Select a model in the "Link to" (Reference) field, but leave the record empty. 4. Save and refresh the page. Issue: -…
Steps to reproduce:
1. Install Sign.
2. Open the form view of a document.
3. Select a model in the "Link to" (Reference) field, but leave the record empty.
4. Save and refresh the page.
Issue:
- After refreshing, the selected model is cleared because the reference value is invalid.
Cause:
- A reference field is only valid when it contains both a model and a record ID. However, the record selector has no placeholder, making it easy to overlook and save an incomplete value.
Solution:
- Add a default placeholder to the record selector of reference fields to make it more visible.
<table>
<tr>
<th>Before</th>
</tr>
<tr>
<td>
<img src="https://github.com/user-attachments/assets/3ba34b77-f901-4d3e-9768-5acaf2e673b8" alt="Before" width="100%">
</td>
</tr>
<tr>
<th>After</th>
</tr>
<tr>
<td>
<img src="https://github.com/user-attachments/assets/1fe54b89-221f-4d28-bd2b-0a3c706d5746" alt="After" width="100%">
</td>
</tr>
</table>
opw-6426339
Forward-Port-Of: odoo/odoo#280266Editing the spacing between products in the website builder no longer makes the product grid blink or reload with every slider change. This makes designing shop pages feel smoother and avoids distracting visual interruptions while preserving the saved layout behavior.
Original PR description
Steps to reproduce: 1. Drop a "Products" dynamic snippet (or open /shop) in edit mode. 2. Click the snippet, open the Design sliding panel. 3. Drag the "Gap" slider or change its number input. Before…
Steps to reproduce: 1. Drop a "Products" dynamic snippet (or open /shop) in edit mode. 2. Click the snippet, open the Design sliding panel. 3. Drag the "Gap" slider or change its number input. Before this commit: The dynamic snippet content re-renders on every slider/input change, causing the products grid to blink. Issue: The dataset write in `SetGapAction.apply` mutates the element's dataset, which changes `getConfigurationSnapshot()` of the DynamicSnippet interaction, causing the interactions to stop and restart on every change. That write is guarded by the `needsDbPersistence` flag of the panel, which is set from the `recordName` prop. This https://github.com/odoo/odoo/commit/f427f795c24e instantiated `ProductsDesignPanel` with `recordName` on the dynamic snippet option, so from this version on the write lands on `.s_dynamic_snippet_products`, the root element of the interaction. The dataset write was originally read by `onSave()` to persist the gap. This https://github.com/odoo/odoo/commit/728049b4597b refactored persistence to read the value directly from style property value of `--o-wsale-products-grid-gap` in `product_design_list_to_save.getData()`, leaving the dataset write without any reader. Solution: Remove the unused `dataset.gapToSave` write from `SetGapAction.apply`. Inline style alone covers rendering, persistence, and dirty tracking. Also remove the now unused `needsDbPersistence` property from the panel, as it was only consumed by the removed code. task-[6535301](https://www.odoo.com/odoo/project/974/tasks/6535301)
When a cashier or manager turns off preparation printers in Point of Sale, the linked printers are now removed automatically. This prevents outdated printer settings from staying active and helps keep order preparation workflows aligned with the chosen configuration.
Original PR description
Before, when the user was unticking the preparation printer checkbox, the preparation printers were not cleared. This fix ensure that when you set the boolean to false, the preparation printers are cleaned. task-id: 6484254 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
The event booth registration page now shows booth options for the visitor's currently selected category, even when earlier page requests finish later. This prevents outdated booth lists from appearing and avoids registration flow delays or failures that previously required a page reload.
Original PR description
Before this commit, the "Get A Booth" page of an event could keep listing the booths of the category checked on load after the visitor picks another one. Only a page reload brought the right ones…
Before this commit, the "Get A Booth" page of an event could keep listing the booths of the category checked on load after the visitor picks another one. Only a page reload brought the right ones back. The tour webooth_exhibitor_register timed out on that list: FAILED: [5/13] Tour webooth_exhibitor_register -> Step Choose Booth (trigger: .o_wbooth_booths div:contains(OpenWood Demonstrator 2) input:not(:visible)). TIMEOUT step failed to complete within 10000 ms. This happens because the page fetches the booths of the category checked on load, and clicking another category fetches its booths while that first answer is still on its way. The answer coming back last fills the list, and the widget caches it under the category active on arrival, so the booths of the first category land in the cache of the clicked one. This commit fixes the issue by caching an answer under the category it was asked for, and by filling the list only while that category is still the chosen one. https://runbot.odoo.com/odoo/error/110527 https://runbot.odoo.com/odoo/error/161834 Forward-Port-Of: odoo/odoo#286552 Forward-Port-Of: odoo/odoo#286266
This update removes an older cleanup step in Point of Sale that is no longer needed because a newer fix handles the issue more precisely. It reduces the risk of removing more data than necessary while keeping stale loyalty reward lines cleaned up correctly.
Original PR description
This fix is no longer required(https://github.com/odoo/odoo/pull/202752) as this one (https://github.com/odoo/odoo/pull/257043) is cleaner and less aggressive (it will only delete stale reward lines). This is the relevant part of the new fix to delete the previous one: https://github.com/odoo/odoo/pull/257043/changes#diff-18cef42f8c39513a82387e9cb81bc732b252304cd15b5b2f7b0b4623ac20cb41R100-R104 opw-6447092 Forward-Port-Of: odoo/odoo#286072 Forward-Port-Of: odoo/odoo#285376
This fix ensures Belgian payroll correctly includes canteen-related cost codes when reading payslip data. It helps prevent inaccurate payroll accounting or validation results for companies using Belgian payroll features.
Original PR description
Previous fix 9c4acee2d324a454d12dd21388ae083f99c40416 was missing a change in the list of code to read.
This fix ensures file-related controls in the HTML editor are fully cleaned up when an editor is closed or replaced. It prevents hidden leftover event handlers from accumulating over time, reducing the risk of memory leaks and gradual performance issues for users who open editors repeatedly.
Original PR description
FilePlugin registered its click, keydown and pointerdown handlers with raw `addEventListener`, so they were never removed when the plugin was destroyed. The pointerdown one is bound to the document, which outlives the editable, leaking a handler per editor instance. Use `addDomListener` so Plugin.destroy() removes them. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#286144
Customers paying with Razorpay FPX can now return from checkout without triggering an error when payment details omit amount information. This prevents failed payment follow-up handling and helps keep the checkout experience reliable.
Original PR description
Issue: --- Paying with Razorpay using FPX raises `KeyError: 'amount'` when the customer returns from checkout. Steps to reproduce: 1- Enable Razorpay with FPX as a payment method. 2- Pay an order using FPX. Cause: --- In FPX, Razorpay returns without `amount`/`currency`. `_apply_updates` fetches the full payment from the API to determine the state, but that fetched entity is local to the method and never reused for amount validation, so `_process` still calls `_validate_amount` / `_extract_amount_data` with the original, amount-less redirect data. This issue is introduced after c34992ffd94ba4594731400839332b677fdc2b32, which removed the check in `_extract_amount_data` that returned `None` (skip validation) when `amount`/`currency` were absent. opw-6467815
This update brings the embedded spreadsheet engine up to its latest maintenance version. It fixes several user-facing issues around charts, pivot ranges, formula recalculation, printing, fonts, and crashes when interacting with formula tokens, making spreadsheet reports more stable and accurate.
Original PR description
### Contains the following commits: https://github.com/odoo/o-spreadsheet/commit/c8c526d863 [REL] 19.3.19 [Task: 0](https://www.odoo.com/odoo/2328/tasks/0)…
### Contains the following commits: https://github.com/odoo/o-spreadsheet/commit/c8c526d863 [REL] 19.3.19 [Task: 0](https://www.odoo.com/odoo/2328/tasks/0) https://github.com/odoo/o-spreadsheet/commit/269e2c6342 [FIX] carousel: cannot add non-existing chart to carousel [Task: 6455790](https://www.odoo.com/odoo/2328/tasks/6455790) https://github.com/odoo/o-spreadsheet/commit/dea2498fd1 [FIX] Fonts: fix linux font on css variable [Task: 6452045](https://www.odoo.com/odoo/2328/tasks/6452045) https://github.com/odoo/o-spreadsheet/commit/32c6cc5f3f [FIX] svg: remove unused commented SVGs [Task: 6317134](https://www.odoo.com/odoo/2328/tasks/6317134) https://github.com/odoo/o-spreadsheet/commit/3593d9f264 [FIX] chart: fix funnel chart show value [Task: 6475094](https://www.odoo.com/odoo/2328/tasks/6475094) https://github.com/odoo/o-spreadsheet/commit/98da68e226 [FIX] pivot: unbounded ranges in the pivot side panel [Task: 6478220](https://www.odoo.com/odoo/2328/tasks/6478220) https://github.com/odoo/o-spreadsheet/commit/cd38becce3 [FIX] composer: crash when hovering a composer token [Task: 5153319](https://www.odoo.com/odoo/2328/tasks/5153319) https://github.com/odoo/o-spreadsheet/commit/d5fff28fab [FIX] evaluation: wrong dependencies invalidation with spread formulas [Task: 5103328](https://www.odoo.com/odoo/2328/tasks/5103328) https://github.com/odoo/o-spreadsheet/commit/bd7d230bfe [FIX] print: figure border wrong render [Task: 6458624](https://www.odoo.com/odoo/2328/tasks/6458624) 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>
Corrects how Odoo calculates tax base amounts in Mexican CFDI payment complements so they match the values validated by the tax authority/PAC. This prevents valid USD invoices with MXN partial payments from being rejected with CRP20268, reducing billing delays and manual corrections.
Original PR description
**Steps to reproduce:** * Install the **l10n_mx** localization. * Enable **USD** and fetch the latest currency exchange rate from the settings. * Configure **Quadrum** as the **PAC** in the settings.…
**Steps to reproduce:**
* Install the **l10n_mx** localization.
* Enable **USD** and fetch the latest currency exchange rate from the settings.
* Configure **Quadrum** as the **PAC** in the settings.
* Create and sign a USD invoice with **16% IVA** (e.g. 4,500 USD + 720 USD tax = 5,220 USD total).
* Send the invoice to **CFDI**.
* Register a **partial MXN payment** that does not convert to a round USD amount (e.g. 30,000 MXN = 1,762.45 USD).
* Send the payment complement to the PAC by clicking **Update Payments** on the invoice.
**Observed behavior:**
The PAC rejects the CFDI with error **CRP20268**:
> El campo BaseP que corresponde a Traslado, no es igual a la suma de
> los importes de las bases registrados en los documentos relacionados
> donde el impuesto del documento relacionado sea igual al campo
> ImpuestoP de este elemento y la TasaOCuotaDR del documento
> relacionado sea igual al campo TasaOCuotaP de este elemento.
**Cause:**
* The SAT validator enforces a strict arithmetic relationship between three fields that are printed in the payment complement XML:
```
BaseP == round(BaseDR / EquivalenciaDR, 6)
```
* When `percentage_paid` is a non-terminating decimal (which happens for any partial MXN payment against a USD invoice), the internal `raw_base` float carries more precision than the 6-decimal-place `BaseDR` that is actually written to the XML:
```
percentage_paid = 1762.45 / 5220 = 0.33763409961685825...
raw_base = 4500 × 0.33763... = 1519.353448275862...
BaseDR (XML) = float_round(raw_base, 6) = 1519.353448
```
* The old code then used `raw_base` (the unrounded internal value) as the dividend when computing BaseP:
```
BaseP (old) = float_round(1519.353448275862... / 0.0587483333, 6)
= 25862.068980 ← written to <TrasladoP BaseP="...">
```
* The PAC performs the same division using only what it can read from the XML. The already-rounded BaseDR:
```
BaseP (PAC) = round(1519.353448 / 0.0587483333, 6)
= 25862.068975
```
* The 6th-decimal mismatch (25862.068980 ≠ 25862.068975) triggers CRP20268 and the document is rejected.
* The same issue occurs with a full MXN payment on a different date than the invoice when multiple invoice lines cause the rounded aggregate to diverge from the sum of per-line raw values.
**Fix:**
In `_l10n_mx_edi_add_payment_cfdi_values` (`account_move.py`), the block that builds the BaseP aggregation list was changed from iterating over raw per-`base_line` amounts to building a single synthetic entry per invoice using the already-rounded document-level `BaseDR` values (`tax_details['base']`) as the dividend:
- Before : wrong: raw_base ≠ BaseDR printed in the XML
'raw_base': tax_details['raw_base'] / inv_rate
- After : correct: 'base' == BaseDR, the exact value in the XML
'raw_base': tax_details['base'] / inv_rate
This guarantees that Odoo and the PAC divide the identical value, producing the identical 6-decimal result.
The `base_line_cfdi_values_mx_curr_list` path (used only for the `Totales` summary fields rounded to 2 dp) is left unchanged because the CRP20268 rule does not apply to that block.
opw-6468077
Forward-Port-Of: odoo/enterprise#128357This fix ensures Odoo correctly flags a numbering gap when a previously posted journal entry is reset to draft and a newer entry is posted afterward. It helps maintain accurate accounting sequence controls without changing existing invoice numbers or reset-to-draft behavior.
Original PR description
To reproduce: * create and post 2 journal entries in the same journal * reset to draft the entry with the highest number * create and post a third entry in the same journal The draft entry is not flagged as having made a gap, because at the time of resetting it to draft, it was the last entry and therefore didn't really make a gap. 3 options to solve were considered: * flag all moves when reseting to draft even if they were the last of the chain * if we reset the last move of the sequence to draft, also remove its sequence and put it back to `/` * when posting, check if the previous number was draft. If it was the case, flag it. The last options was taken in this fix to avoid changing the previous behavior while fixing the current issue. Forward-Port-Of: odoo/odoo#286204
Fixes an issue where automated accounting-related processes could fail when generating reports with elevated permissions. This helps background tasks and localization workflows complete reliably even when the initiating user does not have accounting access rights.
Original PR description
This was spotted in a dev branch for master, where we set a default account_opening_date on every company, hence trying to directly create the returns for it. Some modules need to call reports for that (like intrastat l10n), and do it in sudo(). However, even in sudo, a user without the accounting rights still doesn't have the proper group, and it raises an exception. We hence adapt the condition.
The Appointments calendar now opens on the next upcoming booking instead of jumping to the most distant future booking. This helps users quickly review and manage imminent appointments without manually navigating back through the calendar.
Original PR description
Problem: The bookings calendar of an appointment type opens on the week of the most distant booking instead of the next one, even when a booking exists later the same day. Cause:…
Problem: The bookings calendar of an appointment type opens on the week of the most distant booking instead of the next one, even when a booking exists later the same day. Cause: `action_calendar_meetings` sets the landing date from `appointments[0].start`, where `appointments` is `self.meeting_ids.filtered_domain(domain)`. `calendar.event` is ordered on `start desc` and a one2many is read in the order of its comodel, so the first record is the furthest booking rather than the next one. The original `search([...], order='start')` was replaced by the one2many in 14b5caccc325 (odoo/enterprise#23191). Solution: Sort the filtered bookings on `start` in `action_calendar_meetings`. That method is the only place the initial date is built, and `action_calendar_event_view_request` reuses it for the gantt start date, so both entry points are covered. `calendar.event` keeps its `start desc` order, which the booking list views rely on. Steps to reproduce: - Go to Appointments. - Open the appointment type "Schedule a Demo" and click Appointments. - Click New, set the date to later today, save and go back. - Click New, set the date to one year from now, save and go back. - Go back to Appointments, reopen "Schedule a Demo" and click Appointments. - Switch to the calendar view. - Observe that the calendar opens on the week of the booking one year from now. Ticket [link](https://www.odoo.com/odoo/project.task/6480029) opw-6480029 Forward-Port-Of: odoo/enterprise#130264 Forward-Port-Of: odoo/enterprise#129000
Fixed an issue that prevented spreadsheets containing images from being downloaded as Excel files. The export process now uses the proper access link for shared images, so users can download spreadsheets without errors.
Original PR description
Since https://github.com/odoo/enterprise/pull/97488, the images are shared across spreadsheets and the attachments are no longer related to a specific record. Due to the management of the…
Since https://github.com/odoo/enterprise/pull/97488, the images are shared across spreadsheets and the attachments are no longer related to a specific record. Due to the management of the IrAttachment access rights, it means that those attachments are not accessible by anyone by default and we rely on the access_token to display them in the webclient. However, the method that builds the final xlsx file fetches the images from the server and did not use the access token, meaning that it could never access the attachment. Such situation raised a UserError that was caught by the webclient. How to reproduce: - As admin, create a spreadsheet and insert an image inside of it - try to download the spreadsheet as an xlsx file counterpart of https://github.com/odoo/enterprise/pull/126384 Task-6432724 Description of the issue/feature this PR addresses: Current behavior before PR: Desired behavior after PR is merged: --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#285981 Forward-Port-Of: odoo/odoo#279788
Stripe SEPA payments will once again use the company name for the bank statement description instead of the order reference. This prevents payment failures when an order reference contains only numbers, helping customers complete SEPA payments reliably.
Original PR description
**Steps to reproduce:** 1. Install Sales, l10n_be, payment_stripe and switch to belgian company 2. Switch the admin user's company to the belgian one, and update their contact address to be in Belgium 3. Enable stripe payment provider 4. Add SEPA payment method in stripe configuration 5. Create a sale order with a name that doesn't include any characters (numbers only), confirm, click preview and attempt to make a payment using SEPA **Issue:** `The statement descriptor must contain at least one Latin character.` **Cause:** The previous fix (4fde0232b821c9a1d46d589ff14495f4e19029f4) passed the order reference directly, assuming it will contain characters. The intended behavior is to actually have the company name used in the statement descriptor field: https://support.stripe.com/questions/what-is-a-statement-descriptor-and-how-do-i-update-it This was the existing behavior before the fix, so will revert back to it. opw-6497127 Forward-Port-Of: odoo/odoo#286287
This update adds test coverage to ensure spreadsheets containing images can still be exported to Excel correctly. It helps reduce the risk of broken downloads or missing visual content when users share or export spreadsheet documents.
Original PR description
Counterpart of https://github.com/odoo/odoo/pull/279788 Task-6432724 Forward-Port-Of: odoo/enterprise#130018 Forward-Port-Of: odoo/enterprise#126384
This fix prevents electronic invoice exports from crashing when the Enterprise accounting add-on is not installed. It checks whether optional deferred billing date fields are available before using them, keeping Community Edition invoice processing reliable.
Original PR description
The fields `deferred_start_date` and `deferred_end_date` are created by the enterprise addon `account_accountant`, so not having it installed, this crashes with:
```
File "/opt/odoo/auto/addons/account_edi_ubl_cii/models/account_edi_cii.py", line 684, in <listcomp>
billing_start_dates += [move_line.deferred_start_date for move_line in invoice.invoice_line_ids if move_line.deferred_start_date]
AttributeError: 'account.move.line' object has no attribute 'deferred_start_date'
```
This PR protects the access to these fields checking first if they exist.
Bug introduced in the refactoring in #261572.
@Tecnativa TT64359
Forward-Port-Of: odoo/odoo#286245This fixes an issue where tables copied from Google Docs could keep multiple header rows after being pasted into Odoo's HTML editor. The editor now treats only the first row as the table header, making pasted tables display and behave more consistently.
Original PR description
Steps to Reproduce - Copy a table with multiple header rows from Google Docs. - Paste the table into the editor. Description of the issue: - The pasted table contains multiple header rows, but the editor supports only the first row as the table header row. Cause: - During paste, `cleanForPaste` does not handle tables with multiple header rows - As a result, header cells (`<th>`) in rows other than the first row remain as header cells instead of being converted to normal table cells (`<td>`). Solution: - Update `cleanForPaste` to handle tables with multiple header rows. - If a table contains `<th>` elements in any row other than the first row, replace those `<th>` elements with `<td>` elements. - This ensures that only the first row is treated as the table header row. task-6455248 Forward-Port-Of: odoo/odoo#283862 Forward-Port-Of: odoo/odoo#281234
The inventory report table layout has been corrected after a previous change left extra body columns that no longer matched the header. This prevents confusing or misaligned report output for users reviewing stock information.
Original PR description
This reverts commit 43add3f72f29c35280869010b897c3c5656f5120. It was adding too many columns in table body after columns were removed from the header in 19.0. opw-6307728 Forward-Port-Of: odoo/odoo#286294
Employees in Belgium can now combine a company car with public transport or bicycle commuting without losing reimbursement details. This fixes payslip calculations for people who use more than one transport mode.
Original PR description
When configuring transport modes on an employee contract version profile, selecting a company car automatically reset public transport (bus, tram, metro) kilometers to zero and disabled the bicycle option. This prevented employees who use multiple transport modes (e.g., combining a company car with public transport or a company bicycle) from having multiple transportation reimbursements calculated on their payslip. Remove the automatic field resets for public transport and bicycle options in `_onchange_transport_mode` and remove `_onchange_has_bicycle`, allowing these transport modes to co-exist with a company car. task-6514557
This fix prevents installation errors for restaurant point-of-sale features when related point-of-sale components have not yet been upgraded. It makes the receipt template update rely on a stable existing section, reducing manual upgrade steps and avoiding disruptions for customized deployments.
Original PR description
The template for the preparation tickets was using an xpath targeting a newly added div element. This raised an error when installing the `pos_restaurant` module while an old version of `point_of_sale` was still in place, since the customer would need to manually upgrade `point_of_sale` first to have that new div element available. We now use receipt-header as the xpath target, which was always present in the template. We also refill `pos_self_order.pos_order_change_receipt` to prevent breaks with custo. --- Report: https://github.com/odoo/odoo/pull/267161#discussion_r3758115362 Forward-Port-Of: odoo/odoo#281768
This fixes an issue in the demo payment provider where refunds after manually capturing a payment could remain only authorized, leaving a negative authorized amount and blocking further refund actions. Businesses using the demo provider for testing can now complete the capture and refund flow reliably.
Original PR description
Issue: --- When using manual capture, you can refund the transaction but it will be only authorized, and you'll then be blocked with a negative authorized amount and the impossibility to refund it. This seems to be specific to demo payment provider. Steps: 1- Enable manual capture on demo provider. 2- Buy a product from shop and checkout and pay. 3- Capture the payment in backend. Then click on post process. 4- Try refund. opw-6413515 Forward-Port-Of: odoo/odoo#286084 Forward-Port-Of: odoo/odoo#282315
Portal users who follow a project can now reliably open tasks they are allowed to see, instead of sometimes receiving a “not found” page. This improves the customer portal experience by aligning direct task access with the existing task list permissions.
Original PR description
Before this commit, the portal user could have a request not found when he wants to access to a task from a project he follows even if he can access to the task in /my/tasks route. This commit makes sure the read access are checked instead of checking if the user can access to the task thanks to the token since the token if the one of the project. Forward-Port-Of: odoo/odoo#285878
This fix ensures French VAT credits from a previous period are included in the next tax closing journal entry. It helps companies keep tax return entries compliant with French accounting rules and prevents missing carryover amounts in VAT reporting.
Original PR description
### Issue before this commit: When generating a tax closing entry for France, the VAT credit carryover from the previous period is missing from the journal entry lines. ### Steps to reproduce the…
### Issue before this commit: When generating a tax closing entry for France, the VAT credit carryover from the previous period is missing from the journal entry lines. ### Steps to reproduce the issue: 1. Download Accounting and l10n_fr 2. Go to Vendor > Bills 3. Create a vendor bill with date in June and price > 0 (ex. 1000$) and the 20% G tax that produce a 200$ VAT tax 4. Go to Customers > Invoices 5. Create an invoice with price > 0 (ex. 9000$), the date in July and the 20% G tax that will produce a 1800$ VAT tax 6. Go to Tax Return and validate all opened months up to July and see that for June the balance is -200$ 9. Then go to View Entries of July using the 3 dots next to the "submit" button and see that there is no mentioning of the 200$ carry over of credit from the month before ### Cause of the issue: The issue stems from a structural change in the core code where the logic to evaluate and balance the tax receivable account was removed from the _add_tax_group_closing_items method. Consequently, the VAT closing entry now only processes the current period's taxes and there is no reporting of any carried-forward VAT credits. ### Reason to introduce the fix: French accounting rules strictly require the carried-forward VAT credit to be explicitly integrated into the current period's tax closing entry. opw-6438648 Forward-Port-Of: odoo/enterprise#129181
This update fixes how the HTML builder identifies the currently selected design option when some options have lower internal priority values. It helps ensure the editor shows the right applied option, reducing confusion when configuring composite design actions.
Original PR description
The function `useSelectableComponent` looked for the selected option by searching for the option with the highest priority among the options that are applied. The search for highest priority implicitely excluded options with negative priority. This case happens with `composite` action when the inner actions have no definition of `getPriority`. This commit initialize the "highest priority found so far" as `-Infinity` instead of 0. task-5245362 Forward-Port-Of: odoo/odoo#286503
Sales teams can now correct line descriptions on confirmed orders while the order remains open, even after delivery or invoicing. This removes confusing behavior tied to column layout while still preventing protected product changes on processed lines.
Original PR description
Descriptions on order lines become impossible to change after the line is delivered or invoiced, even though the order is still open. Interestingly, hiding the product column makes the description editable again. This shows that editing descriptions is already technically allowed, but currently depends on the column layout, which is confusing for users. Keep descriptions editable until the order is locked or cancelled. This lets users correct text without allowing changes to products on processed lines. Desired behavior after PR is merged: After a sale order is confirmed, users can modify descriptions while the order remains unlocked, regardless of the column layout. Products on processed lines remain protected. @moduon MT-15454 opw-6432243 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#285946
French electronic invoice exports now automatically include the due date when an invoice is marked as paid. This helps ensure paid invoices comply with the latest French validation rules and reduces rejection risk during electronic invoicing.
Original PR description
According the schematron v1.4, the cbc:DueDate is required when the move is PAID. "[BR-FR-CO-09/BT-23] : Si le cadre de facturation (BT-23) est B2, S2 ou M2, alors la date d’échéance (BT-9) doit être renseignée et correspondre à la date de paiement." no-task --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#286263
Service order lines now update remaining hours when relevant unit or availability details change. This helps sales and project teams see more reliable remaining time on timesheet-based services.
Original PR description
The dependencies of _compute_remaining_hours do not match the fields actually used for the computation: it lists analytic_line_ids, which it never uses, and omits both remaining_hours_available, and product_uom. _compute_remaining_hours_available has the same issue: it uses product_uom but only depends on product_id.service_policy. analytic_line_ids, on the other hand, can be dropped: qty_delivered already depends on it, along with its so_line, unit_amount, product_uom_id and project_id, so the timesheet flow keeps triggering the recomputation. This PR fixes the dependencies for both aforementioned compute methods. Task-4748521 Forward-Port-Of: odoo/odoo#285685 Forward-Port-Of: odoo/odoo#284750
Fixed a display issue on product pages where thumbnails could become uneven or misaligned when auto-crop was disabled. This keeps the product image gallery cleaner and makes thumbnail navigation more consistent for shoppers.
Original PR description
Steps to reproduce: =================== 1. Add several images to a product, one of them much wider than tall. 2. On the product page, set the image ratio to "Disabled". 3. Move the thumbnails below…
Steps to reproduce: =================== 1. Add several images to a product, one of them much wider than tall. 2. On the product page, set the image ratio to "Disabled". 3. Move the thumbnails below the image. => The wide thumbnail sits at the top of its slot, with blank space under it. Root cause: =========== Thumbnails are given a fixed width of 64px and take their height from `aspect-ratio: var(--o-wsale-product-image-ratio)` [1]. With cropping disabled that variable is `auto`, so each thumbnail keeps the shape of its own image and they no longer share a height. They sit in a flex row, which stretches every slot to the height of the tallest one. The image inside keeps its own height and stays at the top of the slot, so a wide image leaves the rest of its slot empty. The slots are only stretched when the images differ in height, which is why the problem needs cropping to be disabled, and why it does not appear when every image is a landscape one. - [1] sized the thumbnails from the image ratio. Fix: ==== Center the thumbnails in the row so each one keeps the height of its own image instead of being stretched to the tallest. Their width stays at 64px, so a row of wide images still takes the same space as before and none of them is pushed out of the container. [1]: 670b1daa2254 opw-6472484 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#283246
Financial reports no longer show the unallocated earnings or losses line when it has a zero balance across all reporting columns. This keeps reports cleaner and helps users focus on meaningful figures.
Original PR description
… zero The unallocated earnings/losses line was displayed even when its balance was zero in every column group, cluttering the report with uninformative rows. We therefore filter out lines whose balance is zero across all column groups. Forward-Port-Of: odoo/enterprise#129666 Forward-Port-Of: odoo/enterprise#129129
This fixes internal tests so they use the same timezone settings as the application logic. It prevents false test failures during a narrow overnight window, improving reliability of validation without changing customer-facing behavior.
Original PR description
## Issue By default in Odoo, `datetime.now()` returns the UTC time, which is two hours behind the local time in Belgium. This leads to two tests failing when executed between 00:00 and 02:00 Belgian…
## Issue By default in Odoo, `datetime.now()` returns the UTC time, which is two hours behind the local time in Belgium. This leads to two tests failing when executed between 00:00 and 02:00 Belgian time: `test_ir_sequence_interpolation_dict` and `test_ir_sequence_iso_directives`. The `_interpolate_dict` method (responsible for interpolating the prefix/suffix from the sequences) specifies the tzinfo when calling `datetime.now()`: https://github.com/odoo/odoo/blob/9c67949be529eb86886b3d5bde08e81e048ecfe7/odoo/addons/base/models/ir_sequence.py#L211-L212 Since this is not the case in the tests, the tests evaluate the date using UTC. This creates a two hours difference between the time expected and the time actually used by `next_by_code`. The tests thus fail between 00:00 and 02:00 because the evaluate dates from both `datetime.now` calls differ, leading to a mismatch in the expected prefixes. ## Fix We force the `env.tz` on the `datetime.now()` call, to mimic the behavior from the `_interpolate_dict`. runbot-242662 Forward-Port-Of: odoo/odoo#284955
The system now checks for duplicate French VAT XML declarations before sending them to AspOne. This helps avoid rejected submissions and unnecessary external processing costs.
Original PR description
When sending an xml to aspone, they will check if a duplicate declaration exist, and if it's the case, then they will refuse it. Since contacting aspone cost us money we will block the sending before that. task-6420220 Forward-Port-Of: odoo/enterprise#127953
This fixes an issue where selling additional units after a product's average cost changed could create incorrect or even negative cost entries on later invoices. Costs are now based on the actual value of delivered stock moves, keeping cost of goods sold accurate across multiple deliveries and invoices.
Original PR description
## HOW TO REPRODUCE: - Create a product AVCO Perpetual - Receive 10 units with a unit price of $10 => Product average price is now $10 - Create a Sale Order for 10 units, confirm, deliver and invoice…
## HOW TO REPRODUCE:
- Create a product AVCO Perpetual
- Receive 10 units with a unit price of $10 => Product average price is now $10
- Create a Sale Order for 10 units, confirm, deliver and invoice => COGS are at $100
- Receive 1 unit with a price of $5 => Product average price is now $5
- Update SO and add 1 unit, deliver and invoice this new unit => New invoice COGS is $-45
This is because Odoo computes the COGS for the whole SO, and decided that the value of the deliveries is the `quantity * standard_price`, which would means `11 units * $5 = $55`. Because the 1st invoice has a COGS balance of $ 100, the 2nd invoice is set to $ -45.
This behavior is inconsistent with how it was done in previous versions. Furthermore, if we added the +1 unit in a new invoice, the COGS would have been $ 5, for a total products COGS of $ 105.
- - -
With this fix, the COGS are computed using the average of the move value. So if the first move value is $100, and the second is $5, then the global COGS for the sale order should be $105. Because the first invoice COGS are already at $100, the second invoice COGS must be $5.
OPW-6442049
---
## TEST RESULT WITHOUT FIX:
```
2026-08-21 08:00:11,722 28203 INFO oes_test_19.0 odoo.addons.sale_stock.tests.test_anglo_saxon_valuation: Starting TestAngloSaxonValuation.test_cogs_average_multiple_invoices_and_deliveries ...
2026-08-21 08:00:13,055 28203 INFO oes_test_19.0 odoo.addons.sale_stock.tests.test_anglo_saxon_valuation: ======================================================================
2026-08-21 08:00:13,055 28203 ERROR oes_test_19.0 odoo.addons.sale_stock.tests.test_anglo_saxon_valuation: FAIL: TestAngloSaxonValuation.test_cogs_average_multiple_invoices_and_deliveries
Traceback (most recent call last):
File "/home/odoo/Odoo/src/19.0/odoo/addons/sale_stock/tests/test_anglo_saxon_valuation.py", line 1935, in test_cogs_average_multiple_invoices_and_deliveries
self.assertRecordValues((cogs_line_1 | cogs_line_2), [
File "/home/odoo/Odoo/src/19.0/odoo/odoo/tests/common.py", line 727, in assertRecordValues
self.assertSequenceEqual(expected_reformatted, record_reformatted, seq_type=list)
AssertionError: Lists differ: [{'ac[142 chars]it': 5.0}, {'account_id': 3566, 'debit': 5.0, 'credit': 0.0}] != [{'ac[142 chars]it': 45.0}, {'account_id': 3566, 'debit': 45.0, 'credit': 0.0}]
First differing element 2:
{'account_id': 3536, 'debit': 0.0, 'credit': 5.0}
{'account_id': 3536, 'debit': 0.0, 'credit': 45.0}
[{'account_id': 3536, 'credit': 100.0, 'debit': 0.0},
{'account_id': 3566, 'credit': 0.0, 'debit': 100.0},
- {'account_id': 3536, 'credit': 5.0, 'debit': 0.0},
+ {'account_id': 3536, 'credit': 45.0, 'debit': 0.0},
? +
- {'account_id': 3566, 'credit': 0.0, 'debit': 5.0}]
+ {'account_id': 3566, 'credit': 0.0, 'debit': 45.0}]
? +
```
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#283778Selecting a suggested partner now correctly returns the form to its saved state after the record is updated. This prevents users from seeing misleading save or discard buttons that do not perform any action.
Original PR description
Steps to reproduce: 1) open partner form view 2) type in the name field 3) select a suggestion from the autocomplete dropdown Issue: The record is enriched and saved, but the form still displays the save/discard buttons, and clicking them does nothing. The record dirty indicator is driven by 2 signals 1) `model.root.dirty`, which is cleared by save() call earlier in the onSelect() 2) the state `fieldIsDirty` of the `FormStatusIndicator` The second indicator is set to true when the user types in an input field, and selecting an option never clears it. The `setDirty` prop no longer exists, and therefore was deadcode. This commit emits `FIELD_IS_DIRTY` bus event, which is the only way to update the second indicator. With that, the indicator goes back to saved state once the record is updated. task-6475332 Forward-Port-Of: odoo/odoo#286268 Forward-Port-Of: odoo/odoo#285897