Monday, August 25, 2025
21 changes · saas-18.4
Enhancements to existing features
This update makes the shared HTML building tools more flexible so they can be reused by the mass mailing app in a future refactoring. It moves useful design options into the common editor, improves configuration hooks, and fixes small reliability issues around editing behavior and placeholders.
Original PR description
This pull request makes small adjustments related to the `html_builder` in order to prepare for the `mass_mailing` refactoring. See each specific commit for a contextualized explanation. task-4247642 Co-authored-by: Damien Abeloos <abd@odoo.com> Co-authored-by: Thomas Josse <thjo@odoo.com>
UAE payroll calculations now include days when an employee is outside their contract period. This helps payroll teams produce more accurate salary computations and reduces manual adjustments for contract start or end gaps.
Original PR description
days to UAE payroll task-4700453 Forward-Port-Of: odoo/enterprise#84758
The Guatemalan EDI module now avoids heavy invoice-related recalculations during installation, reducing the risk of memory errors on large databases. It also no longer installs automatically with the base Guatemalan localization, giving businesses more control over when EDI is enabled.
Original PR description
This commit adds `_auto_init` to the `account.move` object in the Guatemalan EDI that prefills all computed stored fields in the move object with null values. This prevents computation on those fields when installing the module, and also prevents MemoryError on large databases. task-5031330 Forward-Port-Of: odoo/enterprise#92881
Resolved issues and error corrections
This fix prevents translated field values from being accidentally overwritten when working with temporary records in different languages. It helps businesses using multiple languages avoid unexpected data loss or blank product names during record creation and editing.
Original PR description
Steps to reproduce the issue:
1. Install a second language (eg, ar_001) and `product`
2. Start odoo shell
3. Run the following:
`(env := env(context=dict(env.context,lang='ar_001')))['product.template'].new({'name':'test'}).with_context(lang='en_US').name`
Current behavior before PR:
The `get` method of the cache will not find a value for the `en_US` language and set the value stored in the env language to a default (`None` in this case) after raising `CacheMiss`
Desired behavior after PR is merged:
We can read and write translatable fields in different languages on transient records without data loss.
opw-4943458
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#223997
Forward-Port-Of: odoo/odoo#222474This fix ensures invoices created from sales orders with global discounts show accurate unit prices and subtotals on down payment lines. It prevents duplicate price adjustments when invoice and company currencies match, improving invoice accuracy for customers and accounting teams.
Original PR description
Steps to reproduce 1. Create a sale order with a product line for 14,990.00 and a 0% tax. Make sure the product's product category has a Downpayment Account set on it. 2. Create a global discount for 990.00. 3. Create an invoice. Notice how the invoice lines' price_unit are respectively 2909.29 and -70.71, and differ from the lines' subtotals. Analysis Propagating the manual tax amounts in `_reduce_base_lines_with_grouping_function` causes a delta amount to be present in `_apply_base_lines_manual_amounts_to_reach` whereas there shouldn't be. Solution Drop manual tax amounts in `_reduce_base_lines_with_grouping_function`. We also take the opportunity to fix the `delta_currency == currency` condition which caused the price_unit to be adjusted twice if the invoice currency is the same as the company currency. opw-5001935 Forward-Port-Of: odoo/odoo#223876
The online store product feed for Google Merchant Center now checks for product images in a lighter way. This prevents memory failures when exporting very large product catalogs, helping merchants keep their product listings available and up to date.
Original PR description
**Traceback:** ```python File "/home/odoo/src/odoo/saas-18.3/addons/website_sale/controllers/gmc.py", line 63, in gmc_data_source 'items': products._prepare_gmc_items(), ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^…
**Traceback:**
```python
File "/home/odoo/src/odoo/saas-18.3/addons/website_sale/controllers/gmc.py", line 63, in gmc_data_source
'items': products._prepare_gmc_items(),
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/saas-18.3/addons/website_sale/models/product_product.py", line 251, in _prepare_gmc_items
**product._prepare_gmc_image_links(base_url),
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/saas-18.3/addons/website_sale/models/product_product.py", line 281, in _prepare_gmc_image_links
'image_link': urljoin(base_url, self._get_image_1920_url()) if self.image_1920 else '',
^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/saas-18.3/odoo/orm/fields.py", line 1495, in __get__
self.compute_value(recs)
File "/home/odoo/src/odoo/saas-18.3/odoo/orm/fields_binary.py", line 137, in compute_value
super().compute_value(records)
File "/home/odoo/src/odoo/saas-18.3/odoo/orm/fields.py", line 1661, in compute_value
records._compute_field_value(self)
File "/home/odoo/src/odoo/saas-18.3/addons/mail/models/mail_thread.py", line 468, in _compute_field_value
return super()._compute_field_value(field)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/saas-18.3/odoo/orm/models.py", line 4627, in _compute_field_value
determine(field.compute, self)
File "/home/odoo/src/odoo/saas-18.3/odoo/orm/fields.py", line 73, in determine
return needle(*args)
^^^^^^^^^^^^^
File "/home/odoo/src/odoo/saas-18.3/addons/product/models/product_product.py", line 164, in _compute_image_1920
record.image_1920 = record.image_variant_1920 or record.product_tmpl_id.image_1920
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/saas-18.3/odoo/orm/fields.py", line 1458, in __get__
recs._fetch_field(self)
File "/home/odoo/src/odoo/saas-18.3/odoo/orm/models.py", line 3452, in _fetch_field
self.fetch(fnames)
File "/home/odoo/src/odoo/saas-18.3/odoo/orm/models.py", line 3490, in fetch
fetched = self._fetch_query(query, fields_to_fetch)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/saas-18.3/odoo/orm/models.py", line 3607, in _fetch_query
field.read(fetched)
File "/home/odoo/src/odoo/saas-18.3/odoo/orm/fields_binary.py", line 149, in read
att.res_id: att.datas
^^^^^^^^^
File "/home/odoo/src/odoo/saas-18.3/odoo/orm/fields.py", line 1495, in __get__
self.compute_value(recs)
File "/home/odoo/src/odoo/saas-18.3/odoo/orm/fields_binary.py", line 137, in compute_value
super().compute_value(records)
File "/home/odoo/src/odoo/saas-18.3/odoo/orm/fields.py", line 1661, in compute_value
records._compute_field_value(self)
File "/home/odoo/src/odoo/saas-18.3/odoo/orm/models.py", line 4627, in _compute_field_value
determine(field.compute, self)
File "/home/odoo/src/odoo/saas-18.3/odoo/orm/fields.py", line 73, in determine
return needle(*args)
^^^^^^^^^^^^^
File "/home/odoo/src/odoo/saas-18.3/odoo/addons/base/models/ir_attachment.py", line 229, in _compute_datas
attach.datas = base64.b64encode(attach.raw or b'')
^^^^^^^^^^
File "/home/odoo/src/odoo/saas-18.3/odoo/orm/fields.py", line 1495, in __get__
self.compute_value(recs)
File "/home/odoo/src/odoo/saas-18.3/odoo/orm/fields_binary.py", line 137, in compute_value
super().compute_value(records)
File "/home/odoo/src/odoo/saas-18.3/odoo/orm/fields.py", line 1661, in compute_value
records._compute_field_value(self)
File "/home/odoo/src/odoo/saas-18.3/odoo/orm/models.py", line 4627, in _compute_field_value
determine(field.compute, self)
File "/home/odoo/src/odoo/saas-18.3/odoo/orm/fields.py", line 73, in determine
return needle(*args)
^^^^^^^^^^^^^
File "/home/odoo/src/odoo/saas-18.3/odoo/addons/base/models/ir_attachment.py", line 235, in _compute_raw
attach.raw = attach._file_read(attach.store_fname)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/saas-18.3/odoo/addons/base/models/ir_attachment.py", line 118, in _file_read
return f.read()
^^^^^^^^
MemoryError
```
Issue:
-------
Loading product data in Google Merchant Center was causing memory error when handling a large number of products (14k+ in this case). The problem occurred because `self.image_1920` was used to check if a product had an image. This field is computed and non-stored, so accessing it for many records loads the full high-resolution image into memory, leading to excessive memory usage.
Solution:
------------
Use `image_128` instead of `image_1920` for image existence checks, significantly reducing memory usage and preventing the error.
upg-3063427
opw-4981983
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#222584The online shop now loads smaller product images when processing large product lists. This prevents out-of-memory errors and helps keep shopping pages stable for customers and staff.
Original PR description
This commit fixes an "out of memory" error caused by loading the `image_1920` field into memory for a large `product.product` recordset. To resolve this, the code has been changed to use `image_128` to limit the memory impact. See also: - https://github.com/odoo/odoo/pull/222584 opw-4981983 Forward-Port-Of: odoo/odoo#223949
This fix prevents product image imports from failing when images are provided as URLs and a file type detection library is installed. Businesses can reliably test and import records with image fields without encountering an unexpected upload error.
Original PR description
Currently on SaaS it is not possible to import field image_1920 from file as the operation fails Steps to reproduce: - Have python-magic library installed - Products > Import records - Upload a file containing an URL as image_1920 - Test upload Upload fill fail with error `Could not retrieve URL: <img-url> [image_1920: L1]: argument 2: TypeError: wrong type` Investigation: It occurs because the python-magic library does not work with bytearray opw-5004768
Portal users sharing Microsoft Office documents will now have files saved with the correct file type information. This prevents spreadsheets or similar documents from being treated as zip files, so users can open and interact with them as expected in Odoo.
Original PR description
Issue: Inconsistent mimetype vs filename for documents shared by portal users. 1. Share a ms office document logged in as a portal user. 2. filename is correct, but mimetype is `application/zip`. 3.…
Issue: Inconsistent mimetype vs filename for documents shared by portal users. 1. Share a ms office document logged in as a portal user. 2. filename is correct, but mimetype is `application/zip`. 3. User can't interact with the file to open the file in the Odoo spreadsheet because the mimetype is zip. <img width="387" height="192" alt="image" src="https://github.com/user-attachments/assets/be31f05f-e4e3-44d9-b12a-66f96feba950" /> <img width="274" height="67" alt="image" src="https://github.com/user-attachments/assets/93064e5c-c028-41ce-8e79-1b9c9447c018" /> With `fix_filename_extension()`, the following two commits fixed the issue of Odoo's `guess_mimetype` function incorrectly guessing extensions such as `.xlsx` as `.zip` extension. https://github.com/odoo/odoo/commit/8842b6b43e7c3d1cc67d7d399bb210e4009e23b2 https://github.com/odoo/odoo/commit/34d9375e2efb604f8e7036cb710f44b01f9ccaee However, the `_from_request_file()` still uses the incorrectly guessed mimetype to create an attachment, leading to inconsistent extension from the filename (.xlsx) vs attachment record's mimetype (.zip). This commit fixes the issue by using the mimetype from the filename's extension. This is safe if the following assumption is correct: At the point where the `from_request_file()` attempts to create the attachment record, the `filename` contains correct extension name, presumably fixed by the `fix_filename_extension()`. Commit message to be changed after with Julien's confirmation; opw-4753670 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#222643 Forward-Port-Of: odoo/odoo#222368
This fixes an issue where event time slots could be created on the next day when the user's timezone differed significantly from the event timezone. It ensures calendar slots are displayed on the intended date, reducing scheduling confusion for event organizers.
Original PR description
Purpose ======= Fix the creation of slots at the wrong date. Specification ============= User timezone: Europe/Brussels Event timezone: America/Los Angeles When creating a slot on the calendar on the 10th of April from 8PM to 11PM, the slot is created from 8PM to 11PM but on the 11th of April. In scenario where the conversion from the local tz to the event tz changes the date, the slot is created at the wrong date. => After conversion, the DateTime 'zone' param needs to be set back to the local tz (without changing the converted times) to make sure the calendar renders records correctly as it always expects datetimes expressed in the 'local' tz. related PR: https://github.com/odoo/odoo/pull/205945 Task-4743999 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#217476
This fix prevents errors in workflows that handle very large selections, such as paying or reconciling more than 1,000 accounting records at once. It improves reliability for bulk operations by avoiding a crash caused by temporary record references during data loading.
Original PR description
Currently, an error occurs in any wizard or flow that fetches a many2many field with more than 1000 records. For example, attempting to reconcile more than 1000 journal items at once or making…
Currently, an error occurs in any wizard or flow that fetches a many2many field with more than 1000 records. For example, attempting to reconcile more than 1000 journal items at once or making payment of more than thousands invoices. **Steps to reproduce:** 1. Install Accounting(without demo data). 2. Create a new product (e.g., "p1"). 3. Import a [file](https://docs.google.com/spreadsheets/d/1QslJXsDB70jzJwW64AQ4Br6pVcqGCDLP/edit?usp=sharing&ouid=113889705772170635141&rtpof=true&sd=true) to a customer invoice and confirm it to post the entries. 4. Select all (1010 records) invoices and click on the "Pay" button. Tip: Posting the invoice may take some time. Alternatively, the issue can be reproduced by adjusting the PREFETCH_MAX limit. **Error:** `KeyError - <NewId origin=4250>` **Cause:** Previously, the method used `get_until_miss`, which was handled by stopping on missing keys. However, after [this](odoo/odoo@670897c#diff-720a85988e5f3afc3b2596b9521964ef4a99e03e5b1ea8bea2e8ee476187526aL66) commit, the error handling was removed, leading to the error. - [1] Here, the `move_line_ids` field is a many2many field. When fetching is involved at [2]: - If the number of records is less than PREFETCH_MAX(1000), both the real id and its corresponding origin ids are available in the `field_cache`. - When the number of records exceeds, only the real id will be fetched; new/origin ids are not. This leads to an error. **Fix:** This commit only performs fetching for real IDs and skips for new/origin IDs to avoid KeyError. [1] - https://github.com/odoo/odoo/blob/b424877f9d9f9dd03b766ba57ef7c1dd822fc1d9/odoo/orm/environments.py#L781-L791 [2] - https://github.com/odoo/odoo/blob/b424877f9d9f9dd03b766ba57ef7c1dd822fc1d9/odoo/orm/fields_relational.py#L63-L71 sentry-6789677009
Fully flexible employees can now request time off in hours as well as full days. This helps related apps like Timesheets and Planning accurately record paid holidays and unavailable time slots.
Original PR description
In this PR, we allow fully flexible employees to take time off in hours or days, to allow other apps such as timesheet to encode their paid holidays or planning to use unavailable intervals Forward-Port-Of: odoo/odoo#217938
This fix reduces memory consumption when generating the French POS inalterability check report, especially for companies with large volumes of orders. By avoiding unnecessary data loading, the report is less likely to fail and should run more reliably on large datasets.
Original PR description
### Problem: Following this pr https://github.com/odoo/odoo/pull/217348, field prefetching is unnecessary when fetching orders, as all required fields are already fetched explicitly. Keeping prefetching enabled causes excessive memory usage. ### Benchmark | Orders | Before | After | |--------|---------|--------| | 1k | 6MB | 5.8MB | | 10k | 42MB | 27MB | | 100k | 534MB | 320MB | | 200K | 1.1GB | 646MB | opw-4901994 Forward-Port-Of: odoo/odoo#223535 Forward-Port-Of: odoo/odoo#222416
Fixed an issue in the HTML editor where removing text color formatting could freeze the editor in certain selections, especially when color was applied to larger content blocks. The toolbar also avoids opening for non-editable content, making editing more reliable and preventing disruptive hangs for users.
Original PR description
**Current behavior before PR:** - When color was applied to a block-level element, attempting to remove it caused the `removeFormat` method to enter an infinite loop. - Removing formatting on a contenteditable false element infinite loop when removing format. **Desired behavior after PR is merged:** - Now, the color is correctly removed from the selected text, even when it is applied to a block-level element. - Now, the toolbar no longer opens when the selected element is contenteditable false task: 4575459 Forward-Port-Of: odoo/odoo#223939 Forward-Port-Of: odoo/odoo#198319
Refunds made through Viva payment terminals now keep their connection to the original payment even if the Point of Sale page is refreshed mid-process. This prevents staff from having to ask customers to present a card again for refunds that should be completed automatically.
Original PR description
Steps to reproduce: 1. Configure a PoS with a Viva payment terminal 2. Complete an order using the Viva payment method 3. Start to refund the order, and get to the Payment screen 4. At this point, refresh the page 5. Now continue with the refund, using the Viva payment method **Expected behaviour**: The refund is referenced to the original payment, meaning it can be automatically completed without a card. **Actual behaviour**: The refund reference is lost after refreshing, so an unreferenced refund is performed, requiring a card to be presented. The fix is to store the refund reference in the `uiState`, not just on the plain JS object. This way it is saved to the indexedDB. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#223908
Opening a Project could fail if a saved embedded action still pointed to Timesheets after that app was uninstalled. The change safely hides unavailable embedded actions instead of crashing, helping users keep working after module changes.
Original PR description
The system failed to evaluate the embedded action, which refers to the Uninstalled module.
**Steps to produce:-**
- Install `Project` and `Timesheets`.
- Go to Project and open any project.
- Click on the `embedded action` icon and select Timesheets.
- `Save` the view from the `embedded action's icon`.
- Now, `Uninstall Timesheets`.
- Now, go to that Project and try to open it.
**Error:-**
`KeyError: 'allow_timesheets'
ValueError: Invalid field in filter of project.project:
[('allow_timesheets', '=', True)]`
**Solution:-**
- Added a safe check using `hasattr` to verify if the `python_method` defined on the embedded action actually exists on the specified model.
- Ensured that if the method is missing or invalid, the embedded action is marked as not visible instead of causing a crash.
**sentry-6738596751**
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#218564This fixes an issue where automatic subscription invoicing could stop looking too early if some subscriptions in the first batch were not ready to invoice. The scheduler now applies its limit after checking invoice eligibility, helping ensure ready subscriptions are found and future invoice runs are triggered correctly.
Original PR description
Change was introduced here https://github.com/odoo/enterprise/pull/82340 that caused the cron trigger to not be activated if any of the 31 in the batch of subscriptions found was not to invoice. This is problematic because there could be other subscriptions that meet the domain requirements and the to invoice requirements that are not being seen because the limit is on the search before we check invoiceability. Added a limit to _get_subscriptions_to_invoice instead of on the search in order to take into account both filters for next cron triggers. opw-4850884 Forward-Port-Of: odoo/enterprise#92690 Forward-Port-Of: odoo/enterprise#89787
Knowledge now saves an article's pending edits before reloading it after another article is moved nearby. This prevents users from accidentally losing work while organizing content in the sidebar.
Original PR description
Currently, when a user moves another article beneath the one they are editing, the record is reloaded, causing any unsaved changes to be lost. This can be especially frustrating if the user has made significant edits. To address this issue, we will ensure that any changes made to the article are saved before the record is reloaded. This will prevent accidental loss of work and improve the editing experience. Steps to reproduce the issue: 1. Open an article 2. Make changes to the article (e.g, type or edit text) 3. Drag and drop another article beneath the current one. => The article reloads, and your unsaved changes are lost. In the `moveArticle` function, we will check if the record has unsaved changes (i.e., if it's "dirty"). If so, we will save the current content before triggering a reload via `this.props.record.model.load()`. Task-4636494 Forward-Port-Of: odoo/enterprise#87937
Appointment users can now create resource-based appointments directly from calendar events without hitting an access error. This fixes a permissions issue that blocked bookings when an appointment type had a single resource, improving reliability for day-to-day scheduling.
Original PR description
Before this commit, trying to create an appointment through a calendar event as an user will raise an AccessError. This is because in this fix #76653 we needed to make sure the appointment_resource_id is being set on the calendar event and for this we needed to make it readonly. This causes that an user, is not able to get the proper access rights to read on to the 'appointment.booking.line' which is being triggered since inside each booking line, we have an appointment_resource_id which is a many2one to the appointment resource. To fix this, we are adding a sudo on the booking lines when we only have 1 booking line and the appointment resource is set on the calendar event. This way, the user will be able to read the booking lines and create the appointment. opw-4614976 Forward-Port-Of: odoo/enterprise#90149 Forward-Port-Of: odoo/enterprise#88373
The Indian Payroll EPF report export has been corrected so users can download the XLSX file without encountering an error. This ensures payroll teams can generate required provident fund reports reliably after payslips are paid.
Original PR description
Steps to Reproduce: - Install Indian Payroll Localization - enable the Provident Fund of the employee in Payroll tab of employee form view - Create a payslip of the employee and mark as paid - Try to export XLSX file of EPF report from the reporting menu Issue: - A traceback error occurs when click on 'Export XLSX File' report. Reason: - The domain used to search payslips uses an incorrect date format. Solution: - Use the correct date format for the search. task-5002518
This change prevents Accounting from crashing when users reconcile more than 1,000 journal items at once. It improves reliability for high-volume accounting workflows by ensuring large reconciliations can complete without a key error.
Original PR description
Currently, an error occurs when attempting to reconcile more than 1000 journal items at once. **Steps to reproduce:** 1. Install the **Accounting** app. 2. Create a new product (e.g., "p1"). 3.…
Currently, an error occurs when attempting to reconcile more than 1000 journal items at once. **Steps to reproduce:** 1. Install the **Accounting** app. 2. Create a new product (e.g., "p1"). 3. Import a [file](https://docs.google.com/spreadsheets/d/1QslJXsDB70jzJwW64AQ4Br6pVcqGCDLP/edit?usp=sharing&ouid=113889705772170635141&rtpof=true&sd=true) to a customer invoice and confirm it to post the entries. 4. Open Journal Items and filter by `Account = 12100 Account Receivable`. 5. Click on the _Reconcile_ button. **Tip:** Posting the invoice may take some time. Alternatively, the issue can be reproduced by adjusting the PREFETCH_MAX limit. **Error:** `KeyError - <NewId origin=4250>` **Cause:** Previously, the method used `get_until_miss`, which was handled by stopping on missing keys. However, after [this](https://github.com/odoo/odoo/commit/670897c6dc8e744f0622cb96c78fdab0a6249fb6#diff-720a85988e5f3afc3b2596b9521964ef4a99e03e5b1ea8bea2e8ee476187526aL66) commit, the error handling was removed, leading to the error. - [1] Here, the `move_line_ids` field is a **many2many** field. When fetching is involved at [2]: - If the number of records is **less** than `PREFETCH_MAX`, both the **real ids** and its corresponding **origin ids** are available in the `field_cache`. - When the number of records **exceeds**, **only the real ids** are prefetched and cached; origin ids are not. This leads to an error. **Fix:** This commit ensures that real ids are used when accessing the field cache, preventing a key error when the number of records exceeds the prefetch limit. [1] - https://github.com/odoo/odoo/blob/b424877f9d9f9dd03b766ba57ef7c1dd822fc1d9/odoo/orm/environments.py#L781-L791 [2] - https://github.com/odoo/odoo/blob/b424877f9d9f9dd03b766ba57ef7c1dd822fc1d9/odoo/orm/fields_relational.py#L63-L71 sentry-6789677009