Tuesday, August 11, 2026
20 changes · 18.0
Resolved issues and error corrections
This fix prevents a system error when an Australian Single Touch Payroll submission is attempted without any payslips or employees. Instead, users receive a clear validation message so they can correct the missing payroll data before submitting to the ATO.
Original PR description
When submitting payroll data to the ATO from an STP record without any payslips or employees, a traceback is raised. Steps to reproduce the error: - Install ``l10n_au_hr_payroll_account`` module -…
When submitting payroll data to the ATO from an STP record without any payslips or employees, a traceback is raised. Steps to reproduce the error: - Install ``l10n_au_hr_payroll_account`` module - Switch to ``My australian Company`` - Go to Payroll > Configuration > Settings > In Australian Localization, Set BMS ID > Set STP Responsible and his date of birth - Go to Payroll > Reporting > Single Touch Payroll > Create a new record > Set Payment Date > Submit to ATO > Sign & Submit to ATO Traceback: ```py IndexError: tuple index out of range ``` https://github.com/odoo/enterprise/blob/b0e48baaf99bdc4faefd2ffdd3bd5637fb548593/l10n_au_hr_payroll_account/models/l10n_au_stp.py#L228-L233 The traceback occurs because ``_get_fiscal_year_start()`` assumes that the STP record always contains at least one payslip or one employee. When these recordsets are empty, indexing the first element raises an IndexError. Solution: This commit validates that the required payslips or employees are present before the submission and raises a validation error instead of a traceback.
The French VAT reimbursement declaration now includes the bank account holder's name, which is required for form 3519. This helps businesses submit complete declarations and reduces the risk of rejection or follow-up requests.
Original PR description
For reimbursement declarations, the name of the holder of the account is required This commits adds holder's name to the account data zone no-task-id
Since commit ff57c836438b1fec32527db39270b02c68504955 `website` fails to install. The cache reset should not be done when installing modules. Reproduce: 1. Create a new empty db 2. Set language to German 3. Install Website 4. The install gets stuck and you can't access the db anymore. task-None
Original PR description
Since commit ff57c836438b1fec32527db39270b02c68504955 `website` fails to install. The cache reset should not be done when installing modules. Reproduce: 1. Create a new empty db 2. Set language to German 3. Install Website 4. The install gets stuck and you can't access the db anymore. task-None
l10n_fr_pdp's settings view inherits account_peppol and uses peppol_use_parent_company in `invisible`s. The node for that field is provided by account_peppol, but it was only added to account_peppol settings view in 0ab1e0323894. On a database whose account_peppol was not updated after this commit, installing l10n_fr_pdp does not upgrade account_peppol, so this invisible references a field that doesn't exist in the view, so tb at install ``` Error while validating view near: <form string=
Original PR description
l10n_fr_pdp's settings view inherits account_peppol and uses peppol_use_parent_company in `invisible`s. The node for that field is provided by account_peppol, but it was only added to account_peppol…
l10n_fr_pdp's settings view inherits account_peppol and uses peppol_use_parent_company in `invisible`s. The node for that field is provided by account_peppol, but it was only added to account_peppol settings view in 0ab1e0323894. On a database whose account_peppol was not updated after this commit, installing l10n_fr_pdp does not upgrade account_peppol, so this invisible references a field that doesn't exist in the view, so tb at install
```
Error while validating view near:
<form string="Settings" class="oe_form_configuration" js_class="base_settings">
<field name="is_root_company" invisible="1"/>
<app data-string="General Settings" string="General Settings" name="general_settings" logo="/base/static/description/settings.png">
Field 'peppol_use_parent_company' used in modifier 'invisible' (peppol_use_parent_company or account_peppol_proxy_state not in ('sender', 'pending', 'active')) must be present in view but is missing.
View error context:
{'file': '/home/odoo/src/odoo/17.0/addons/l10n_fr_pdp/views/res_config_settings_views.xml',
'line': 1,
'name': 'res.config.settings.view.form.inherit.proxy.user',
'view': ir.ui.view(2640,),
'view.model': 'res.config.settings',
'view.parent': ir.ui.view(1604,),
'xmlid': 'res_config_settings_view_form'}
```
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#281520Problem: When sending an invoice, deleting an attachment can lead to the deletion of another attachment with the same name. Steps to reproduce: 1. Duplicate the invoice PDF report 2. Edit the email template and add the duplicated report as a dynamic report attachment 3. Create an invoice, confirm it and send it by email 4. Delete one of the attachments in the email composer and send the email 5. Check the sent email and notice how the other attachment with the same name has been deleted
Original PR description
Problem: When sending an invoice, deleting an attachment can lead to the deletion of another attachment with the same name. Steps to reproduce: 1. Duplicate the invoice PDF report 2. Edit the email template and add the duplicated report as a dynamic report attachment 3. Create an invoice, confirm it and send it by email 4. Delete one of the attachments in the email composer and send the email 5. Check the sent email and notice how the other attachment with the same name has been deleted as well. Cause: The code was using the name of the attachment to identify it, but the name is not unique and this leads to attachments with the same name being deleted. opw-6413314
Issue: ------- After the fix: https://github.com/odoo/odoo/commit/f1557211d9e7f83761bb36e4800e4c2f62b234c5 we can't create a child menu for a mega menu or a menu can't be a mega menu when there's an existing child menu except the case of top level menu i.e; (url: /default-main-menu) and that menu will have no parent_id obviously... Now, as per the above pr conditions the top level can be set as mega menu since it has no parent id. And in version 17.3 in the pr https://github.com/odoo/odoo/
Original PR description
Issue: ------- After the fix: https://github.com/odoo/odoo/commit/f1557211d9e7f83761bb36e4800e4c2f62b234c5 we can't create a child menu for a mega menu or a menu can't be a mega menu when there's an…
Issue:
-------
After the fix:
https://github.com/odoo/odoo/commit/f1557211d9e7f83761bb36e4800e4c2f62b234c5 we can't create a child menu for a mega menu or a menu can't be a mega menu when there's an existing child menu except the case of top level menu i.e; (url: /default-main-menu) and that menu will have no parent_id obviously...
Now, as per the above pr conditions the top level can be set as mega menu since it has no parent id. And in version 17.3 in the pr https://github.com/odoo/odoo/commit/47af533e9f5f721b63570d3b301951f3855384a1 a 'Jobs' menu is being created and its parent_id refers to that top level menu which we have set as mega menu. And when the records gets validated during migration the database will get blocked.
Solution:
-----------
Restrict the user by throwing the same user error, when checking/selecting the top level menu as mega menu since it has existing child menus.
Step to reproduce:
-----------------------
1. Create a database in version 17.0 with 'website_hr_recruitment' installed.
2. Go to website menus, set a top level menu(/default-main-menu) as mega menu.
3. Migrate the database to version 18.0 or more.
Traceback:
```
File "/home/odoo/src/odoo/18.0/odoo/models.py", line 5297, in _create
records._validate_fields(name for data in data_list for name in data['stored'])
File "/home/odoo/src/odoo/18.0/odoo/models.py", line 1636, in _validate_fields
check(self)
File "/home/odoo/src/odoo/18.0/addons/website/models/website_menu.py", line 95, in _validate_parent_menu
raise UserError(_("A mega menu cannot have a parent or child menu."))
odoo.exceptions.UserError: A mega menu cannot have a parent or child menu.
File "/home/odoo/src/odoo/18.0/odoo/tools/convert.py", line 603, in _tag_root
raise ParseError('while parsing %s:%s, somewhere inside\n%s' % (
odoo.tools.convert.ParseError: while parsing /home/odoo/src/odoo/18.0/addons/website_hr_recruitment/data/config_data.xml:13, somewhere inside
<record id="website_menu_jobs" model="website.menu">
<field name="name">Jobs</field>
<field name="url">/jobs</field>
<field name="parent_id" ref="website.main_menu"/>
<field name="sequence">59</field>
</record>
```
Ref Images:
Before Fix:
<img width="1598" height="599" alt="image" src="https://github.com/user-attachments/assets/ef719945-a11b-4134-97f8-4b583c4ea6bc" />
After Fix:
<img width="1582" height="633" alt="image" src="https://github.com/user-attachments/assets/da326e45-0de8-4d42-ad47-845bfaedc84e" />
OPW - 6094298
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#263025Clearing the cache in the `write` of `ir.module` caused issues when installing `website`. Having it in `create` was enough for the original use case that prompted the PR. So we remove the `write` in this commit. Reproduce: 1. Create a new empty db 2. Set language to German 3. Install Website 4. The install gets stuck and you can't access the db anymore. task-None
Original PR description
Clearing the cache in the `write` of `ir.module` caused issues when installing `website`. Having it in `create` was enough for the original use case that prompted the PR. So we remove the `write` in this commit. Reproduce: 1. Create a new empty db 2. Set language to German 3. Install Website 4. The install gets stuck and you can't access the db anymore. task-None
Description of the issue/feature this PR addresses: Odoo supports WebP image fields, but `base_import` validates remote images with `PIL.Image.open()`. Odoo intentionally leaves Pillow's WebP decoder unloaded, so a valid WebP URL is rejected as an unidentified image. Current behavior before PR: Importing a valid WebP image URL fails with `cannot identify image file`. Oversized and unsupported WebP files are also rejected by Pillow before the import-specific size policy can be applied. Desire
Original PR description
Description of the issue/feature this PR addresses: Odoo supports WebP image fields, but `base_import` validates remote images with `PIL.Image.open()`. Odoo intentionally leaves Pillow's WebP decoder unloaded, so a valid WebP URL is rejected as an unidentified image. Current behavior before PR: Importing a valid WebP image URL fails with `cannot identify image file`. Oversized and unsupported WebP files are also rejected by Pillow before the import-specific size policy can be applied. Desired behavior after PR is merged: Use Odoo's existing WebP header parser for dimension validation. Valid WebP URLs import unchanged, unsupported WebP remains rejected, and the existing 42-million-pixel import limit remains enforced. Tests cover valid, unsupported, and oversized WebP URL payloads. The complete `test_base_import` suite passes (56 tests, 0 failures/errors). --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#276648
During the backport we discovered a programming error in `_get_peppol_document_params`. The super function can return `None` values for both `edi_user` and `document`. This can lead to a traceback when the variables are used. We do not have any known issues in 18.0 or higher versions. (Probably because the move send was heavily refactored.) But better safe than sorry. It could maybe happen with the batch sending or in case the "surrounding" logic is refactored in the future. task-None
Original PR description
During the backport we discovered a programming error in `_get_peppol_document_params`. The super function can return `None` values for both `edi_user` and `document`. This can lead to a traceback when the variables are used. We do not have any known issues in 18.0 or higher versions. (Probably because the move send was heavily refactored.) But better safe than sorry. It could maybe happen with the batch sending or in case the "surrounding" logic is refactored in the future. task-None
The test employee uses Europe/Brussels, but only the 35h calendar had an explicit timezone. The 40h calendar still inherited its timezone from the environment, which could lead to different work entry durations depending on the test setup. This commit sets the 40h calendar timezone explicitly to Europe/Brussels as well, keeping both contract calendars consistent with the employee. [error-938862 ](https://runbot.odoo.com/odoo/error/938862) --- I confirm I have signed the CLA and read t
Original PR description
The test employee uses Europe/Brussels, but only the 35h calendar had an explicit timezone. The 40h calendar still inherited its timezone from the environment, which could lead to different work entry durations depending on the test setup. This commit sets the 40h calendar timezone explicitly to Europe/Brussels as well, keeping both contract calendars consistent with the employee. [error-938862 ](https://runbot.odoo.com/odoo/error/938862) --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Steps to reproduce: - Open the first default To-do note. - Select the word Welcome. - Open the text color picker. - Click the trash icon to reset the text color. Issue: - When resetting the text color, the selected text also loses its font-size style. Cause: - The `color_plugin` removes the wrapper element after resetting the color style. If the same element also contains other inline styles (such as font-size), those styles are lost when the element is removed. Solution: - After
Original PR description
Steps to reproduce: - Open the first default To-do note. - Select the word Welcome. - Open the text color picker. - Click the trash icon to reset the text color. Issue: - When resetting the text color, the selected text also loses its font-size style. Cause: - The `color_plugin` removes the wrapper element after resetting the color style. If the same element also contains other inline styles (such as font-size), those styles are lost when the element is removed. Solution: - After resetting the color style, only remove the element if it no longer has any other inline styles. Otherwise, keep the element so that the remaining styles are preserved. task-6279198
Problem: When a `table` with a `color`/`backgroundColor` contains a nested `table`, `distributeTableColorsToAllCells` propagates the outer table's color to every `td` in the subtree, including cells belonging to the inner table. The inner table's own color is then discarded since its `td`s already have a value. Cause: `table.querySelectorAll("td")` returns every `td` in the entire subtree, not just the table's own direct cells. Solution: Scope the selected `td`s to `td.closest("table"
Original PR description
Problem:
When a `table` with a `color`/`backgroundColor` contains a nested `table`, `distributeTableColorsToAllCells` propagates the outer table's color to every `td` in the subtree, including cells belonging to the inner table. The inner table's own color is then discarded since its `td`s already have a value.
Cause:
`table.querySelectorAll("td")` returns every `td` in the entire subtree, not just the table's own direct cells.
Solution:
Scope the selected `td`s to `td.closest("table") === table`, so a table's color is only distributed to its own cells.
Steps to reproduce:
1. Add a `background-color` to an outer `table`.
2. Nest a `table` with a different `background-color` inside one of its cells.
3. Load/normalize the content in the editor.
4. Observe both tables' cells carry the outer table's color.
opw-6438972
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-prThe SIRET value on the contact form should not be overwritten when registering with the PDP. This fix from 18.0 to 18.2 task-6442186
Original PR description
The SIRET value on the contact form should not be overwritten when registering with the PDP. This fix from 18.0 to 18.2 task-6442186
Currently there is a traceback on PDP / Peppol. `render_peppol_errors` expects `error` as a dictionary from which it extracts the message. But we extract the message already before passing it to `render_peppol_errors`. The issue was introduced in this commit 8b7c36e45552943a266d2973eda89b9cec8ae1a4 . It also breaks the PDP logging logic from commit 08eea0a0788ee2588726a35d693c338b4974ca1e . ``` Traceback (most recent call last): File "/home/odoo/src/odoo/odoo/http.py", line 25
Original PR description
Currently there is a traceback on PDP / Peppol. `render_peppol_errors` expects `error` as a dictionary from which it extracts the message. But we extract the message already before passing it to…
Currently there is a traceback on PDP / Peppol.
`render_peppol_errors` expects `error` as a dictionary from which it extracts the message.
But we extract the message already before passing it to `render_peppol_errors`.
The issue was introduced in this commit
8b7c36e45552943a266d2973eda89b9cec8ae1a4 .
It also breaks the PDP logging logic from commit
08eea0a0788ee2588726a35d693c338b4974ca1e .
```
Traceback (most recent call last):
File "/home/odoo/src/odoo/odoo/http.py", line 2577, in __call__
response = request._serve_db()
^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/odoo/http.py", line 2104, in _serve_db
return self._transactioning(
^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/odoo/http.py", line 2167, in _transactioning
return service_model.retrying(func, env=self.env)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/odoo/service/model.py", line 157, in retrying
result = func()
^^^^^^
File "/home/odoo/src/odoo/odoo/http.py", line 2134, in _serve_ir_http
response = self.dispatcher.dispatch(rule.endpoint, args)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/odoo/http.py", line 2382, in dispatch
result = self.request.registry['ir.http']._dispatch(endpoint)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/odoo/addons/base/models/ir_http.py", line 333, in _dispatch
result = endpoint(**request.params)
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/odoo/http.py", line 754, in route_wrapper
result = endpoint(self, *args, **params_ok)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/addons/web/controllers/dataset.py", line 42, in call_button
action = call_kw(request.env[model], method, args, kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/odoo/api.py", line 535, in call_kw
result = getattr(recs, name)(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/addons/account_peppol/models/account_journal.py", line 34, in peppol_get_message_status
edi_users._peppol_get_message_status()
File "/home/odoo/src/odoo/addons/account_peppol/models/account_edi_proxy_user.py", line 352, in _peppol_get_message_status
processed_message_uuids = edi_user._peppol_process_messages_status(messages_to_process, uuid_to_record)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/addons/l10n_fr_pdp/models/account_edi_proxy_user.py", line 308, in _peppol_process_messages_status
processed_message_uuids += super()._peppol_process_messages_status(other_messages, uuid_to_record)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/addons/account_peppol_response/models/account_edi_proxy_user.py", line 212, in _peppol_process_messages_status
return processed_message_uuids + super()._peppol_process_messages_status(other_messages, uuid_to_record)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/addons/account_peppol/models/account_edi_proxy_user.py", line 385, in _peppol_process_messages_status
move._message_log(body=render_peppol_errors(move, error.get('data', {}).get('message') or error['message']))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/addons/account_peppol/tools/peppol_errors.py", line 81, in render_peppol_errors
raw_message = error.get('data', {}).get('message') or error['message']
^^^^^^^^^
AttributeError: 'str' object has no attribute 'get'
```
task-NoneVersion: -------- - 18.0 Steps to reproduce: ------------------- - Install `purchase` and `stock` - Create a storable product with Supplier info - Create a Replenishment Rule for the product - Click the information icon on the replenishment rule to open the replenishment information popup - Click **Set as Supplier** under **vendor tab** Issue: ------ The orderpoint is assigned the `Cross-Dock` route instead of the warehouse's `Buy` route. As a result, replenishments are conf
Original PR description
Version: -------- - 18.0 Steps to reproduce: ------------------- - Install `purchase` and `stock` - Create a storable product with Supplier info - Create a Replenishment Rule for the product - Click…
Version: -------- - 18.0 Steps to reproduce: ------------------- - Install `purchase` and `stock` - Create a storable product with Supplier info - Create a Replenishment Rule for the product - Click the information icon on the replenishment rule to open the replenishment information popup - Click **Set as Supplier** under **vendor tab** Issue: ------ The orderpoint is assigned the `Cross-Dock` route instead of the warehouse's `Buy` route. As a result, replenishments are configured to use the wrong route for vendor procurement. Cause: ------ Clicking **Set as Supplier** calls `SupplierInfo.action_set_supplier()`. When the orderpoint's current route does not contain a rule with `action='buy'`, the method falls back to assigning a route using: https://github.com/odoo/odoo/blob/788c2f3f19a3a2c70e2efcc683e21965c634b6de/addons/purchase_stock/models/product.py#L141-L142 This search is not restricted to the orderpoint's warehouse or to the warehouse's Buy route. It simply returns the first stock.rule whose action is buy, according to the default ordering of stock.rule. Prior to 18.0, this implementation was effectively safe because each warehouse only had a single stock.rule with action='buy': the warehouse's buy_pull_id. Therefore, the unscoped search always returned the correct Buy route. This changed in 18.0 with this [commit](https://github.com/odoo/odoo/commit/af5479dfdbed) As part of the new push-rule architecture, `StockWarehouse._get_receive_rules_dict()` now inserts an additional action='buy' rule into the Cross-Dock route: https://github.com/odoo/odoo/blob/788c2f3f19a3a2c70e2efcc683e21965c634b6de/addons/purchase_stock/models/stock.py#L84-L89 This rule is intentionally used to trigger the Cross-Dock push chain when `Buy` is the warehouse reception method. As a consequence, a warehouse now contains two different stock.rule records with action='buy': the warehouse's `Buy` rule (buy_pull_id); the Cross-Dock trigger rule. The unscoped search performed by `action_set_supplier()` therefore has two equally valid candidates and may return the Cross-Dock rule, depending on the default ordering of stock.rule. This results in the Cross-Dock route being assigned to the orderpoint instead of the warehouse's `Buy route`. The issue no longer reproduces from saas-18.2 onwards because the Cross-Dock route was removed in this [commit](https://github.com/odoo/odoo/commit/c2bf8b10d765) Fix: ---- Resolve the Buy route directly from the orderpoint's warehouse using `warehouse_id.buy_pull_id.route_id`, which explicitly represents the warehouse's Buy route generated by StockWarehouse._generate_global_route_rules_values(). Keep the previous unscoped search only as a fallback when the warehouse does not have a configured Buy rule. This ensures the supplier assignment always selects the warehouse's intended procurement route instead of another route that happens to contain a buy rule. --- opw-6365633 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
#### Issue When manually adding component lines in the subcontracting Record Production flow, extra stock move lines can remain in the database without a linked stock move. Those lines have no move_id, so their related state is empty. They can later be selected by stock reservation code and cause validation errors when processing the transfer. #### Steps to reproduce 1. Create a subcontracted product. 2. Confirm a subcontracting receipt/purchase flow for that product. 3. Open the s
Original PR description
#### Issue When manually adding component lines in the subcontracting Record Production flow, extra stock move lines can remain in the database without a linked stock move. Those lines have no…
#### Issue When manually adding component lines in the subcontracting Record Production flow, extra stock move lines can remain in the database without a linked stock move. Those lines have no move_id, so their related state is empty. They can later be selected by stock reservation code and cause validation errors when processing the transfer. #### Steps to reproduce 1. Create a subcontracted product. 2. Confirm a subcontracting receipt/purchase flow for that product. 3. Open the subcontracting Record Production popup. 4. Remove an existing component line. 5. Add a new component line for another storable product available at the subcontractor location. 6. Save/record the production. 7. Check stock.move.line records , Inventory > History > Group By Status. An extra stock move line is left with no move_id. #### Root cause The inverse of `mrp.production.move_line_raw_ids` already collects and deletes move lines detached from existing raw moves. However, when the user adds a new component product, the inverse creates a new additional raw move. Creating that raw move can also create reserved move lines. The inverse then replaces `move.move_line_ids` with the user-entered lines, but it did not collect the newly created reserved lines before replacing them. Those reserved lines were detached from the move and left in the database as orphan stock move lines. #### Fix Apply the same cleanup logic to newly created additional raw moves: collect their auto-created move lines before replacing `move_line_ids`, then unlink those detached lines at the end of the inverse. opw-6304649 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
An old refactor left some data around that are in conflict with other records for the same model. 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#281276
Original PR description
An old refactor left some data around that are in conflict with other records for the same model. 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#281276
Miscellaneous changes
This reverts commit ff57c836438b1fec32527db39270b02c68504955. It causes issues when installing `website`. Reproduce: 1. Create a new empty db 2. Set language to German 3. Install Website 4. The install gets stuck and you can't access the db anymore. task-None
Original PR description
This reverts commit ff57c836438b1fec32527db39270b02c68504955. It causes issues when installing `website`. Reproduce: 1. Create a new empty db 2. Set language to German 3. Install Website 4. The install gets stuck and you can't access the db anymore. task-None
- Compute the standard durations for all French leaves in a single `_get_durations()` call instead of recomputing them for each leave. - Reuse the precomputed duration mapping while calculating the French legal duration for each leave. - Keep the existing French-specific duration calculation unchanged, including public holidays, company calendar, date extension, and half-day handling. This avoids repeated ORM computations when processing multiple French leaves in a batch an
Original PR description
- Compute the standard durations for all French leaves in a single `_get_durations()` call instead of recomputing them for each leave. - Reuse the precomputed duration mapping while calculating the…
- Compute the standard durations for all French leaves in a single `_get_durations()` call instead of recomputing them for each leave. - Reuse the precomputed duration mapping while calculating the French legal duration for each leave. - Keep the existing French-specific duration calculation unchanged, including public holidays, company calendar, date extension, and half-day handling. This avoids repeated ORM computations when processing multiple French leaves in a batch and significantly improves the performance of leave duration computation. Performance testing was performed using the `hr.leave.employee.report` introduced in 19.1, which calls `_get_durations()` on a large batch of virtual leaves. Performance testing on a database containing 677 leaves, including 298 French leaves: | Metric | Before | After | |------------------------------------|-----------------|-------------| | Standard duration computations | 298 | 1 | | Total `_get_durations()` | >169s (timeout) | ~24s | not specific to the employee report and benefits any caller that invokes _get_durations() on a larger batch. **opw-6421323** --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Behavior before: When uploading an animated GIF to fields utilizing image responsive sizing or cropping (such as employee avatars or product images), no downscaling or cropping occurs for sub-variants like 'image_128' or 'image_1024'. The responsive fields replicate the exact file size and data footprint of the original large image, leading to heavy storage overhead and unnecessary frontend asset loading. Behavior after: Animated GIF images scale down and crop correctly to match requested r
Original PR description
Behavior before: When uploading an animated GIF to fields utilizing image responsive sizing or cropping (such as employee avatars or product images), no downscaling or cropping occurs for…
Behavior before: When uploading an animated GIF to fields utilizing image responsive sizing or cropping (such as employee avatars or product images), no downscaling or cropping occurs for sub-variants like 'image_128' or 'image_1024'. The responsive fields replicate the exact file size and data footprint of the original large image, leading to heavy storage overhead and unnecessary frontend asset loading. Behavior after: Animated GIF images scale down and crop correctly to match requested responsive dimensions and aspect ratios. Sub-variants take up significantly less space in the filestore, matching proportional dimensions without dropping or stripping the underlying animation loop. Large images that are smaller than requested boxes are safely left un-upscaled to maximize database deduplication. Root Cause: Historically, a legacy safeguard bypassed GIF resizing and cropping because older versions of the Pillow library did not gracefully handle multi-frame sequential image buffers. As a result, standard 'image.crop()', 'image.thumbnail()', or 'image.resize()' implementations would flatten multi-frame animated sequences down into a single, static first frame or throw dimension/mode mismatches during save operations. Fix: Intercept the image processing pipeline when encountering an asset identified as a GIF where 'is_animated' evaluates to True. Implemented a unified, in-place multi-frame helper routine (`_apply_gif_operation`) using PIL's 'ImageSequence.Iterator' to cleanly step through, normalize to a uniform color mode (RGBA), duplicate, and modify each animation frame individually. This single helper handles sequential workflows for both 'crop' and 'thumbnail' operations while preserving individual frame duration arrays and native loop metadata. Both 'resize' and 'crop_resize' leverage this logic to achieve precise dimensions cleanly. Crucially, upscaling (expanding) is intentionally unsupported for animated GIFs. Forcing a low-resolution, 256-color indexed animation to stretch beyond its native dimensions forces heavy color dithering across every single frame. This breaks the sequential LZW pattern compression, causing the resulting file sizes to skyrocket catastrophically. The logic utilizes thumbnail boundaries to completely block this expansion, protecting the filestore from accidental bloat. Benchmark: -------------------------------------------------------------------------------------------- | GIF size | Variant | Size Before (KB) | Size After (KB) | |---------------|--------------------|--------------------------|-----------------------| | (2.5MB) | image_1024 | 2475.87 | 2475.87 | | | image_128 | 2475.87 | 257.93 | |---------------|--------------------|--------------------------|-----------------------| | (3.8MB) | image_1024 | 3724.93 | 3724.93 | | | image_128 | 3724.93 | 463.62 | |----------------|-------------------|--------------------------|-----------------------| | (442KB) | image_1024 | 432.49 | 432.49 | | | image_128 | 432.49 | 36.14 | |----------------|-------------------|--------------------------|-----------------------| | (3.6MB) | image_1024 | 3491.98 | 3491.98 | | | image_128 | 3491.98 | 1728.25 | |----------------|-------------------|--------------------------|-----------------------| opw-6232841 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