Tuesday, September 15, 2026
21 changes · saas-19.2
Resolved issues and error corrections
Fixed an issue where creating a rental order could fail when an optional rental product was added and the website rental module was not installed. The system now handles rental dates correctly, so pricing can be calculated and orders can be completed without interruption.
Original PR description
Problem: When the website_sale_renting module is uninstalled and you try to create a rental order with a product that has an optional rental product, the system crashes with a traceback. This happens…
Problem: When the website_sale_renting module is uninstalled and you try to create a rental order with a product that has an optional rental product, the system crashes with a traceback. This happens because the rental start and end dates are passed to the product pricing calculations as plain text strings instead of proper date formats, which breaks the timezone math. Solution: This commit ensures that the rental start and end dates are converted into proper datetime formats before any duration or pricing calculations occur, preventing the error and allowing the products to be added to the rental order smoothly. Steps to reproduce(runbot v18): 1. Install the Rental (sale_renting) and Website modules. 2. Uninstall the website_sale_renting module. 3. Create a rental product and configure another rental product as its optional product. 4. Open the Rental application and try to add the created product to a rental order. 5. A traceback is raised while calculating the rental price. opw-6485776 Forward-Port-Of: odoo/enterprise#130966 Forward-Port-Of: odoo/enterprise#128668
Fully booked time slots are no longer hidden in the point of sale self-order flow. Customers and staff can still see unavailable slots clearly marked as unavailable, reducing confusion when choosing pickup or service times.
Original PR description
Before this commit: = * Slots that reached their maximum capacity for a given time frame were hidden from the `pos_self_order` slot selection dialog. After this commit: = * Slots remain visible but are disabled when they reach their maximum capacity. task-6340956 Forward-Port-Of: odoo/odoo#287912 Forward-Port-Of: odoo/odoo#286467
This fixes Belgian payroll salary calculations when an employee has multiple contract versions in the same month. The update applies the 50% rule correctly so payslips better reflect paid and unpaid hours, reducing payroll inaccuracies.
Original PR description
there is a problem that we If we have two versions in the same month, we always follow the second option of the 50% rule. This is because the theoretical hours are calculated for the whole month, but the paid amount is calculated separately for each version. We fixed this by checking If unpaid hours > paid hours, salary is calculated by multiplying the hourly rate by the total hours. Otherwise, salary is calculated as the base wage minus (unpaid hours × hourly rate). And return back the test to what exist before the task with id : 6260378 task Id: 6515980
The QFPay point-of-sale payment test has been updated to include the required Send action before waiting for card payment. This prevents the automated test from timing out and helps ensure QFPay terminal payments remain reliably validated.
Original PR description
Commit 7a208335fa84 ("[IMP] point_of_sale: avoid fast payments") removed the automatic sending of the transaction to the terminal, so the user now has to click on "Send" to trigger the payment request.
The tours of the other payment terminals (adyen, razorpay, safaricom, viva_com) were adapted accordingly, but pos_qfpay was missed. As a result, the payment line never reaches the 'waitingCard' status and the tour times out waiting for the "Waiting for card" step.
Add the missing PaymentScreen.clickSendButton() step to fix the tour.
runbot-940270This change prevents managers in multi-company setups from being blocked by access errors when viewing employee organization charts or handling expenses for their teams. It also makes expense delegation more flexible so team approvers can create expenses for subordinates across company boundaries where appropriate.
Original PR description
_(updates on July-17th and July-28th for points 2. and 3. in `hr_expense`)_ # Introduction **1\.** A fix to `hr_org_chart` was initially proposed. I think it is mergeable because it fixes the…
_(updates on July-17th and July-28th for points 2. and 3. in `hr_expense`)_ # Introduction **1\.** A fix to `hr_org_chart` was initially proposed. I think it is mergeable because it fixes the user-experience properly like expected in Odoo standard. Other commits are suggested to `hr_expense`. They were designed to allow more flexibility in the management of `hr_employee.company_id` (in multi-company context) and allow not to duplicate the `hr.employee` of each companies of the `res.users`. **2\.** The [FIX] silences a multi-company access error when searching for Expense validators => it seems safe **3\.** The [IMP] largely allow more flexibility for a "Expense: Team Approver" when creating expenses on behalf of its subordinates # 1. in hr_org_chart [FIX] Prevents multi-company error when recursively searching for ancestors. <img width="1035" height="789" alt="image" src="https://github.com/user-attachments/assets/d5517f79-7efc-4223-ae77-0af8b25a3d1e" /> ### Issue description In a multi-company environment, when one of the `hr_employee.company_id` of a hierarchy is not in the allowed companies of a Manager's `res_users.company_ids`, this Manager can view `hr.employee` in the list view but cannot open their forms. This happens when: - `hr_org_chart` module is installed - the Org Chart is displayed on the 1st page of the `hr.employee` form, like when the HR settings "Skills Management" is disabled (in `res.settings`) => thus the whole form becomes inaccessible from the manager When a `hr.employee` form is opened, a multi-company access error is thrown to him, even if the `company_id` of the opened `hr.employee` is in the user's `res_users.company_ids`, because of the hierarchy's `company_id`. It should be expected that the part of the Org Chart which is not allowed to be seen would just be hidden. ### Steps to reproduce Data setup: - Employee "A" in company A - Manager "M" in company A, manager of "Employee A" - Manager of manager "MM" in company A, manager of "Manager M" - And now, in company B (let's say a Holding), the "Director" is manager of "Manager MM" - "Manager M" is only given access access to Company A - the module "hr_org_chart" is installed Actions: - Login with Manager M - Browse to Employees list and try and open the form of "Employee A" (up to tab _"Professional information"_, if it is not the 1st of the notebook) ### Proposed fix This PR re-uses the already existing method `_check_employee` which contains all the logic to solve the issue. Maybe the call to this method was forgotten? This PR simply call this method when finding an ancestor, in the controller of `hr_org_chart`. This fix is thus very limited to the call to the public method `hr_org_chart.get_org_chart()` made by the Org Chart widget. ### Desired behavior after PR is merged The part of the Org Chart not allowed to be seen by "Manager M" is hidden. # 2. hr_expense [FIX] <img width="541" height="415" alt="image" src="https://github.com/user-attachments/assets/d594815b-2b77-4088-878b-9b192b64d6a3" /> ### Issue description As an employee, I click on the button "View Report" on my expense. I get a multi-company access error, preventing me to view and edit my expense report. This is because the manager of the department I belong is in a company I'm not allowed to see. This can also happen just when opening my Expense (instead of Expense Report). ### Current behavior before this PR The employee is blocked to continue editing its Expense or to submit it to a Report. ### Current behavior after this PR The employee can edit and submit its Expense no matter the `company_id` of its hierarchy. Technically: the `can_approve` field on the expense sheet uses a localized `.sudo()` method to bypass multi-company limits when searching if the current user is a validator. # 3. hr_expense [IMP] <img width="1028" height="549" alt="image" src="https://github.com/user-attachments/assets/1096c0d4-d025-46a9-8559-6bab5de71577" /> ### Improvement summary In multi-company environment, allow a "Expense: Team Approver" to create Expenses for its subordinates (`hr_expense.employee_id`) **no matter the `hr_employee.company_id` of its subordinates**. The domain of `hr_expense.employee_id` keeps the security of `check_company=True` => thus the Manager only sees `hr.employee` having their `company_id` in the manager's allowed companies (`res_users.company_ids`). ### Current behavior before this PR Context: a 8-companies environment where the `hr.employee` of each hierarchy chains are splitted in many different companies, like: - top-level (admin board): 1 company - middle management: approx. 2 companies - down level: the other companies The "down level" have `hr.employee` but no `res.users`. The "middle management" have `res.users` and must create the Expenses of their "down level" subordinates on their behalf. Issue: as a manager, as per Odoo proposal, I need to have a `hr.employee` in the same company of my subordinates to be able to create Expense of their behalf. However, this is very inconvenient because as a Manager, I can have employees in various companies. And my own manager it not in the same company than me, so the same issue applies recursively. ### Behavior after this PR is merged The domain of the field `expense_id.employee_id` is more permissive. As a Manager, it allows me to select the Employee I manage in my active company, no matter if I have or not myself a `hr.employee` in this company. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#286293 Forward-Port-Of: odoo/odoo#266261
This fix limits the payment methods shown in expense settings to those that match the company context. It helps prevent users from selecting inconsistent payment data, reducing configuration mistakes in expense management.
Original PR description
Add domain to company_expense_allowed_payment_method_line_ids field to avoid selecting inconsistent data @Tecnativa TT64416 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#287131
This fixes an issue where spreadsheet formulas using a single linked record amount could fail in locales that use commas as decimal separators, such as French. Users can now calculate with those single values normally, reducing spreadsheet errors in localized environments.
Original PR description
Steps to reproduce:
- insert a sale.order list into a spreadsheet
- change the locale to FR_fr
- add the formula in A1: =ODOO.LIST(1, 1, "invoice_ids.amount_total"
- in A2: =A1+1 => error
The value for the "amount_total" is stringified
to something like "4.5" because of the `[].join(",")` but "4.5" is not interpreted as a number in the FR locale where the decimal separator is "," and it should be "4,5"
With this commit, when there's a single record in the x2many, we fallback on the "normal" case and return the real value.
When there are multiple values, we volontarily keep the current behavior. We can't "just format it" because we join with "," and it wouldn't work if "," is also the decimal separator. This is a known and accepted limitation, especially in stable
Task: 6307092
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#270279Spanish OSS sales are now reported in the correct VAT declaration box, moving the amounts from box 124 to box 123. This helps ensure Spanish tax reports reflect the required casilla for these transactions and reduces reporting errors.
Original PR description
OSS sales were being mapped to mod_303_casilla_124_balance where it should be mapped to mod_303_casilla_123_balance. These amounts must be reported values in casilla 123. This commit updates es_assec, es_common, es_full and es_pymes tax templates from 124 to 123 and updates test_country_tag_from_spain. task-6360387 I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#284984
This fix prevents spreadsheet values for multiple linked records from being converted into plain text. It helps keep document spreadsheets accurate and avoids display or calculation issues when working with related business data.
Original PR description
See community PR task-6307092 Forward-Port-Of: odoo/enterprise#120701
Accounting users in Chilean companies can now upload customer invoice XML files without needing administrator access. The invoice XML is processed correctly, avoiding failed imports caused by overly restrictive permissions.
Original PR description
Scenario: - be not an admin but have access to accounting - switch to chilean company - go to customer invoices - click on Upload and upload an XML file Result: an invoice is created, but the XML is not treated because of this permission error: 19.0/l10n_cl_edi/…/account_move.py", line 1078, in _l10n_cl_import_dte invoice.l10n_cl_dte_file = file_data['attachment'] odoo.exceptions.AccessError: You do not have enough rights to access the field "l10n_cl_dte_file" on Journal Entry (account.move). Please contact your system administrator Cause: l10n_cl_dte_file field is only accessible to administrator. opw-6509550 Forward-Port-Of: odoo/enterprise#130416
Company-paid expenses created from a project now assign the project cost only to the actual expense line, not to balancing accounting lines. This prevents project cost reports from cancelling themselves out and gives businesses a more accurate view of project profitability.
Original PR description
### Current behavior: Creating a company-paid expense from the Project overview posts a journal entry with the project analytic on both the expense and outstanding lines, so the analytic balance nets to zero ### Expected behavior: Analytic distribution should only be on the P&L (expense) line ### Steps to reproduce: 1. Open a project overview and create a company-paid expense 2. Submit, approve, and post it 3. Open the journal entry: analytic is on debit and credit lines ### Cause of the issue: `project_id` stays in the context after `clean_context` during `_create_company_paid_moves`. With `sale_project`, AML analytic compute then applies the project distribution to outstanding/tax lines as well ### Fix: Removed `project_id` from the context when creating company-paid moves opw-6368848 Forward-Port-Of: odoo/odoo#280256
Invoices for customers in the Canary Islands, Ceuta, or Melilla are now reported with the correct special regime code instead of a generic one. This helps businesses comply with Spanish electronic invoicing requirements and reduces the risk of incorrect tax reporting.
Original PR description
… key 8 When the customer is located in Canary Islands, Ceuta or Melilla, the invoice falls under a different tax territory and the ClaveRegimenEspecialOTrascendencia should be 08. Add the regime key 08 to the clave_regimen_selection in verifactu Previously this case was not checked and invoices were reported with the generic refime code. The regime key 08 is not available in verifactu. task-6372837 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#279435
Fixed an issue where dragging an image onto its current position in the HTML editor could trigger an error, especially in Chrome. This improves editing reliability and prevents users from losing momentum while working with image content.
Original PR description
Steps to reproduce: - Insert an image as the last child of a paragraph. - Drag and drop it below or after itself. Description of the issue: - A traceback occurs. Cause: - When dropping an image below or after itself `document.caretPositionFromPoint()` computes a drop offset equal to the current node size. - The image is then removed from the DOM before being reinserted. Since it is the last child of its parent, removing it shrinks the parent, making the previously computed offset out of bounds. - Restoring the selection at that stale offset results in a traceback. Solution: - Treat dropping the image at its current position as a no-op and skip the remove/reinsert process, since it would not change the DOM. - Clamp the drop offset to the current node size before restoring the selection preventing out-of-bounds offsets. task-6435091 Forward-Port-Of: odoo/odoo#286613 Forward-Port-Of: odoo/odoo#280612
List columns can now be resized properly on tablets and other touch devices. This prevents the resize action from being interrupted by normal touch scrolling behavior, making list views easier to use on mobile hardware.
Original PR description
Steps to reproduce ================== - Use a tablet (touch screen) - Open any list view - Try to resize a column by dragging the column header's right edge => The resize doesn't work properly: it gets interrupted an we can only move a few pixels at a time Cause of the issue ================== The resize handle relies on pointerdown/pointermove/pointerup to run and stop the drag, but nothing tells the browser to opt out of its native touch gestures. On a tablet, the drag can get hijacked as a page scroll, which fires pointercancel instead of pointerup. That event was not listened to, leaving the pointermove handler attached and the resize state stuck. Solution ======== - Set touch-action: none on the resize handle so a touch drag isn't interpreted as scrolling - Listen to pointercancel to properly stop the resize when the browser takes over the gesture anyway Forward-Port-Of: odoo/odoo#288046
Mexican payroll calculations now use the employee's daily wage to determine minimum wage exemptions, preventing incorrect IMSS deductions when eligible workers receive extra pay such as commissions. The update also avoids subsidy eligibility errors caused by unpaid absences or adjusted schedule days, helping payroll results better match legal requirements.
Original PR description
According to LSS Art. 36, minimum wage workers are exempt from IMSS deductions. Previously, the system evaluated the period's total gross wage instead of the daily wage, which caused incorrect…
According to LSS Art. 36, minimum wage workers are exempt from IMSS deductions. Previously, the system evaluated the period's total gross wage instead of the daily wage, which caused incorrect withholdings when a minimum wage employee received extra pay (like commissions). We now evaluate the `l10n_mx_daily_salary` directly to protect minimum wage workers while ensuring correct deductions for higher earners with unpaid leaves. Since calculations rely on `schedule_days` retrieved from the schedule table, users commonly adjust these values (e.g., from 15 to 15.2, or 30 to 30.4). To prevent discrepancies caused by this practice, we now round the `schedule_days`. Finally, this commit fixes the employment subsidy eligibility threshold. Previously, the limit was incorrectly reduced by unpaid absences. This caused a double-counting effect (since absences already lower the actual gross wage) and wrongly disqualified employees. The subsidy limit is now based strictly on the full payroll schedule length, while the ISR minimum wage exemption correctly continues to consider actual worked days. target: 19.0 task-6370959 Forward-Port-Of: odoo/enterprise#130209 Forward-Port-Of: odoo/enterprise#125061
The shop page wishlist heart button now keeps a consistent size in Safari. This prevents product cards from looking misaligned, improving the shopping experience for Safari users.
Original PR description
The wishlist button on the shop page is misaligned in Safari Steps to reproduce: (in Safari) 1. Install eCommerce 2. Go to the shop 3. The wishlist button (heart icon in the top right corner of each product) is misaligned Issue: The wishlist button (`.o_add_wishlist`) is positioned with `position: absolute` with only top and right offsets set (through the `o-position-absolute` mixin), leaving its width and height on `auto`. https://github.com/odoo/odoo/blob/c4de5361fb207916175332dcf6815c0814ecf09c/addons/website_sale_wishlist/static/src/scss/website_sale_wishlist.options.scss#L62-L65 In other browsers, the button resolves to a 38 x 38px square box but in Safari, it is not a square which misaligns it in the product grid. Solution: Force width and height on `.o_add_wishlist`, so the button resolves to the same box size in every browser. opw-6456552 Forward-Port-Of: odoo/odoo#287940 Forward-Port-Of: odoo/odoo#281935
This fix prevents the Pay on Site option from being automatically turned back on when the related website sales module is upgraded. Merchants who disabled this payment option will no longer unexpectedly offer it at checkout, reducing the risk of unpaid orders.
Original PR description
Steps to reproduce: =================== 1. Go to the payment providers and set "Pay on Site" to disabled. 2. Go to Apps, clear the filter, look up "website_sale_collect" and upgrade it. This also…
Steps to reproduce:
===================
1. Go to the payment providers and set "Pay on Site" to disabled.
2. Go to Apps, clear the filter, look up "website_sale_collect" and upgrade it. This also happens on its own, as upgrading any custom module that depends on it upgrades it too.
3. Go back to the payment providers.
=> "Pay on Site" is enabled again, and published as well from 19.0 on. Customers are offered it at checkout and place orders that are never paid, without the merchant ever enabling anything.
Root cause:
===========
`data/payment_provider_data.xml` is loaded in update mode because it carries no `noupdate`, and it hardcodes the state of the provider:
<field name="state">enabled</field>
So every upgrade of the module writes that value back over whatever the merchant configured. Every other provider ships its data with `noupdate="1"` and leaves the state alone, this module is the exception.
The file has been loaded this way since the module was added:
- [1] created the module with an updatable provider record.
Fix:
====
Load the file with `noupdate="1"`. The record is still created, enabled, when the module is installed, it is simply not written again on later upgrades. The flag is read from the file rather than from the `ir.model.data` row, so databases where the provider already exists are covered on their next upgrade, no data migration needed.
[1]: 087c48c4ed2e
opw-6528110
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#286913Corrects how fixed-rate Mexican taxes are calculated on partial payment complements, preventing mismatches between taxable base and tax amount. This helps ensure payment CFDIs are accepted by certification providers and SAT, reducing failed electronic invoicing for affected customers.
Original PR description
When generating a payment complement, tax base and importe coming from the related invoice are prorated by the percentage actually paid, each rounded independently to the currency precision. The…
When generating a payment complement, tax base and importe coming from the related invoice are prorated by the percentage actually paid, each rounded independently to the currency precision. The post-fix step that restores the SAT invariant uses a Tasa-only formula (`base = total / (1 + rate)`), so Cuota (fixed amount per unit) taxes keep mismatched values, ending up with `ImporteDR != round(BaseDR * TasaOCuotaDR)`. This leads to CFDIs rejected by the PAC/SAT. Steps to reproduce: - Create a customer invoice with a Cuota IEPS tax (e.g. 26.2569). - Register a partial payment whose amount is not an exact divisor of the invoice total (e.g. one third). - Send the payment CFDI: the resulting Cuota TrasladoDR has an ImporteDR that does not match BaseDR * TasaOCuotaDR, leading to a rejected CFDI. This commit recomputes `importe` from the prorated `base` for Cuota taxes (bypassing the Tasa post-fix) opw-6087564 Forward-Port-Of: odoo/enterprise#130568 Forward-Port-Of: odoo/enterprise#113395
Odoo now correctly carries payment reference information from imported CII XML invoices into the resulting vendor bill. This helps accounting teams keep supplier payment details accurate and reduces manual correction after invoice import.
Original PR description
### Issue before this commit: When importing a CII XML invoice containing a PaymentReference, the value is not transferred to the generated vendor bill in Odoo. ### Steps to reproduce the issue: 1. Download Accounting 2. Try to import the invoice in the ticket 3. See that in the tab other info the payment reference is not imported ### Cause of the issue: During a previous refactoring (ffbdf29a816d0ff4136488d26b55b9707fc37fc6), the helper function responsible for extracting the payment reference during the import process was omitted. ### Reason to introduce the fix: Add the missing extraction logic to ensure the payment reference is correctly retrieved from the XML and assigned to the Odoo invoice. opw-6530627 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#287216
Validated future time off is now counted against an employee's available leave balance. This prevents allocations from showing too many remaining days and helps employees and managers see accurate balances when planning absences.
Original PR description
**Steps to reproduce:** - Create and validate an allocation of 10 days. - Take and validate a leave of 5 days in the future. - Issue: the allocation's `virtual_remaining_leaves` still shows 10 instead of 5. **Issue:** `hr.leave.allocation._compute_leaves()` calls `_get_consumed_leaves()` with `ignore_future=True`, which filters the leaves domain to `date_from <= today`. A validated future leave is excluded from the query before it can be deducted, even though the allocation grants its days upfront and isn't gated by any accrual plan. This flag was intentionally dropped from this call by (odoo/odoo#193685), then came back by accident via a forward-port of (odoo/odoo#249441). **Solution:** Drop `ignore_future=True` from `_compute_leaves()` Task-6534125 Forward-Port-Of: odoo/odoo#287626
FedEx shipping labels generated in ZPLII format now download with a standard .zpl file extension instead of being saved as a text file. This prevents confusion and helps users send downloaded labels directly to compatible label printers.
Original PR description
TL;DR When downloading a `.zplii` shipping label from the chatter on a Delivery Order (using FedEx), the browser automatically adds .txt to the end of the filename, saving it as `.zplii.txt` Step to…
TL;DR
When downloading a `.zplii` shipping label from the chatter on a Delivery Order
(using FedEx), the browser automatically adds .txt to the end of the filename,
saving it as `.zplii.txt`
Step to reproduce:
- install `delivery_fedex_rest` with demo
- open shipping method menu -> Fedex Us -> label format = `zplii` -> save
- create a SO, click on 'Add Shipping",
- select fedex as shipping method -> get rate -> add -> confirm SO
- go to delivery and validate
- notice, in thread, a attachment with ZPLII extension appears
- download (.txt is appended to file)
Issue:
- `fedex_rest_send_shipping` post message with documents with extension as
`fedex_rest_label_file_type` i.e. `ZPLII`
https://github.com/odoo/enterprise/blob/735490d7ba9bdc6d0df7a0bd07c0d4e36d1ed2d4/delivery_fedex_rest/models/delivery_fedex.py#L193-L195
- when the attachment is created for this document , it's mimetype is computed
to be `text/plain` from [guess_mimetype](https://github.com/odoo/odoo/blob/5de4a5f6caee09881e7e147062f9eab9f8916a61/odoo/tools/mimetypes.py#L192) method, as data is plain ASCII code
- moreover, when downloading, [_get_stream_from](https://github.com/odoo/odoo/blob/5de4a5f6caee09881e7e147062f9eab9f8916a61/odoo/addons/base/models/ir_binary.py#L89) tries to guess extension
using [get_extension](https://github.com/odoo/odoo/blob/5de4a5f6caee09881e7e147062f9eab9f8916a61/odoo/tools/mimetypes.py#L210) which return `None` as len('zplii') > 4, [see](https://github.com/odoo/odoo/blob/5de4a5f6caee09881e7e147062f9eab9f8916a61/odoo/tools/mimetypes.py#L221)
- finally, as we got `None`, and mimetype is `text/plain`, `.txt` is appended [here](https://github.com/odoo/odoo/blob/5de4a5f6caee09881e7e147062f9eab9f8916a61/odoo/addons/base/models/ir_binary.py#L149)
Fix:
- we use `zpl` as extension for file instead of `zplii` as there is not
difference between them from printing perspective
- as length of 'zpl' is <=4 , `get_extension` will considered it as valid
opw-6410968
Forward-Port-Of: odoo/enterprise#126620