Friday, August 21, 2026
23 changes · 18.0
Enhancements to existing features
The website cleanup job now processes old inactive visitors in smaller, controlled batches instead of loading every record at once. This reduces memory usage and helps scheduled maintenance continue smoothly on large databases.
Original PR description
**Description of the issue/feature this PR addresses:** `_cron_unlink_old_visitors()` retrieves all inactive visitor IDs before splitting them into batches. Consequently, `batch_size` only limits the unlink operations, while the initial query and Python list remain unbounded. **Current behavior before PR:** All inactive visitor IDs are loaded into memory during a single cron execution. **Desired behavior after PR is merged:** The initial search is limited to `batch_size` by default. After deleting the batch, the cron reports the actual number of remaining visitors so the Odoo 18 progress mechanism can continue processing them. This is a backport of #197781 to Odoo 18. @Tecnativa @pedrobaeza please review --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Polish companies can now prepare a dedicated VAT-UE report instead of relying on the generic EC Sales List. The new report includes key EU transaction types and supports export in the official Polish XML format, helping businesses meet local filing requirements more accurately.
Original PR description
Description of the issue this commit addresses: Polish companies only have the generic EC Sales List without purchase transactions or an XML export matching the official VAT-UE structure. --- Desired behavior after this commit is merged: This commit adds a Polish VAT-UE report covering intra-Community supplies, acquisitions, services, triangular transactions, and the official XML export. --- task-6368808
Resolved issues and error corrections
This fixes certificate loading so Odoo works cleanly with newer operating system versions of its encryption library. It prevents unnecessary warning messages in test and server logs while preserving compatibility with older library versions.
Original PR description
pyOpenSSL 24.3.0 deprecated passing its own X509/PKey objects to Context.use_certificate()/use_privatekey(), and started accepting cryptography objects instead. Odoo pins pyopenssl 24.1.0, but the distro builds run the version shipped by the OS: since the test added by f0fb287c6502 covers that path, they now add a warning in the logs. Load the certificate and the key as cryptography objects when the installed pyOpenSSL supports them, keep the previous loaders otherwise. Reference: https://github.com/pyca/pyopenssl/commit/b0cb4b4 This fix is based on https://github.com/odoo/odoo/blob/a2b4f618328f3ce3f654fd2c1ee4410365706a7e/odoo/addons/base/models/ir_mail_server.py#L34-L46 runbot-944176
Odoo now shows the specific error code and message returned by Serbia’s eFaktura service when an invoice submission fails. This helps users understand why an invoice was rejected instead of seeing only a generic connection or HTTP error.
Original PR description
**Steps to reproduce:** - Install the Serbian EDI module `l10n_rs_edi`. - Configure eFaktura credentials on the company. - Create and confirm a Serbian customer invoice. - Send the invoice to…
**Steps to reproduce:**
- Install the Serbian EDI module `l10n_rs_edi`.
- Configure eFaktura credentials on the company.
- Create and confirm a Serbian customer invoice.
- Send the invoice to eFaktura.
**Observed Behavior:**
When the eFaktura API returns an HTTP error, Odoo only displays the generic exception generated by `requests`, for example an HTTP 400/500 error.
The actual error information returned by eFaktura in the response body is not shown to the user, making it difficult to understand why the invoice was rejected.
**Cause:**
`_l10n_rs_edi_send` catches `HTTPError`, `Timeout`, and `ConnectionError`, but the error message is built only from the Python exception.
For HTTP errors, the eFaktura API may return a response containing more precise information such as:
```json
{
ErrorCode: ...,
Message: ...
}
```
This response was not being used when displaying the error in Odoo.
**Fix:**
When an HTTP response is available and contains an eFaktura error payload, use the returned `ErrorCode` and `Message` as the error displayed on the invoice. Fallback to the existing connection/HTTP exception message when no usable API response is available.
opw - 6453653
Forward-Port-Of: odoo/odoo#281490This change prevents a final invoice from incorrectly becoming a tiny credit note due to rounding after a 100% down payment. It ensures the final invoice correctly balances to zero, avoiding confusing refund documents for customers and accounting teams.
Original PR description
With "Round Globally" tax rounding, invoicing a 100% down payment and then creating the final regular invoice yields a credit note of 0.01 instead of an invoice of 0.00. The sales order is still…
With "Round Globally" tax rounding, invoicing a 100% down payment and then creating the final regular invoice yields a credit note of 0.01 instead of an invoice of 0.00. The sales order is still flagged as fully invoiced, so the customer is left with an unexpected refund document. A down payment line can only store a price_unit rounded to the 'Product Price' decimal precision, while the product lines it must offset are aggregated from their raw amounts. When a product subtotal falls on a half cent (e.g. quantity 0.5 at 1.01 => 0.505), the final invoice carries a raw residual of -0.005. _round_tax_details_base_lines rounds that aggregate to -0.01 and _distribute_delta_amount_smoothly assigns the cent to the largest base line, leaving its balance one cent away from its own price_subtotal. Since amount_untaxed derives from the balances, the invoice totals -0.01 and _create_invoices switches it to a refund. The product amounts have already been invoiced and rounded on the down payment invoice, so the aggregation must target those rounded amounts. Declare them through manual_total_excluded_currency, which is read from the base line dict and feeds target_total_excluded. total_excluded is unchanged, so no posted or displayed amount moves. TICKET #6426626 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
Mentioning a partner linked to multiple active users in a sub-channel no longer causes an error. The system now checks each user’s notification settings and invites the partner when at least one user allows channel notifications, keeping discussions working smoothly.
Original PR description
Before this commit, mentioning a partner that has two active users in a sub-channel ended on: ValueError: Expected singleton: res.users.settings(77, 80) This happens because the channel notification setting is read through res_users_settings_id, a Many2one on res.users, so two users give two settings records and reading a value on them asks for a singleton. Only an administrator reaches it, as the res.users.settings rule limits everyone else to their own settings. This commit fixes the issue by inviting the partner as soon as one of its users did not turn channel notifications off.
This fixes a mobile editing issue where choosing a Gboard word suggestion could place the suggested word in the wrong position and leave part of the old word behind. The editor now distinguishes this Gboard behavior from a separate SwiftKey workaround, making text correction more reliable for mobile users.
Original PR description
Before this commit: on mobile, when typing using Gboard and select a word suggestion will only delete the last character and put the new word at the beginning of the word to be replaced. This is because Gboard extends the selection to the text to be corrected, then deletes it, and inserts the corrected text. This flow falls in our previous fix for MS Swiftkey's delete backward, and wrongly uses cached old selection instead of using extended new selection from Gboard. After this commit: We strict the Swiftkey fix further, and only execute it when the cursor is at the beginning of the p element. Related commit: https://github.com/odoo/odoo/commit/822fd4e8fec7e114e6748dd8c9b4969f423fb290 task-6233756 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
When someone is mentioned in Discuss, Odoo now chooses an active user account for that contact instead of possibly sending the notification to an archived account. This helps ensure mentioned people actually see their inbox notifications and reduces missed internal communication.
Original PR description
Before this commit, mentioning a partner that has an archived user sent the inbox notification to that archived user, so the mentioned person never saw the mention. This happens because the query picking the user of a recipient joins res_users without filtering on active, and keeps one row per partner with DISTINCT ON and no ORDER BY, so which row survives is arbitrary. One solution could have been to keep every active user of the partner, which is what we want as each of them has its own notification type, but a notification is stored per partner, so the type of a single user applies to all of them. Picking one user is a current limitation. This commit fixes the issue by taking the first active user of each partner in a lateral join, ordered as mail.followers._get_recipient_data already does: internal users first, then the lowest id.
This fixes an issue where temporary setup or wizard screens could incorrectly appear as available targets for mass mailing. Business users should see cleaner, more relevant mailing options and avoid selecting records that are not meant for campaigns.
Original PR description
The search function ` _search_is_mailing_enabled` mistakenly used `model.is_transient()` (where the model is the `ir.model` record itself) to filter the transient models, which always returns `False` since `ir.model` is a regular persistent model. As a result, transient models (wizards) were never filtered out. This commit fixes it by using`self.env[model.model].is_transient()` to call `is_transient` on the actual model. Task-6458883 Forward-Port-Of: odoo/odoo#282783
Sales users limited to their own documents can now cancel sales orders that include loyalty rewards without hitting an access error. The fix ensures temporary loyalty point records are cleaned up correctly during cancellation, reducing order processing blockers.
Original PR description
Steps to produce: --- - Install `sale_management` and `sale_loyalty` module without demo. - From sales > products > discounts & loyalty, create new loyalty card program and save. - Now create new…
Steps to produce: --- - Install `sale_management` and `sale_loyalty` module without demo. - From sales > products > discounts & loyalty, create new loyalty card program and save. - Now create new product of 100$. - Create a user which have sales rights as `user: own documents only`. - With that user, create new sale order with product and confirm. - Try to cancel the order. Issue: --- - It shows the access error: ```py You are not allowed to delete 'Sale Order Coupon Points - Keeps track of how a sale order impacts a coupon' (sale.order.coupon.points) records. This operation is allowed for the following groups: - Sales/Administrator Contact your administrator to request access if necessary. ``` Root cause: --- - Users with the `Sales: Own Documents Only` access right only have read permissions ([1]). When they cancel a Sales Order, the `_action_cancel` method attempts to clean up the temporary pending points allocated to the order by calling `self.coupon_point_ids.unlink()`. Because this call is executed without elevated privileges, the system blocks the deletion and raises an Access Error Solution: --- - Added `.sudo()` to the `unlink()` call for `coupon_point_ids` in the `_action_cancel` method. This ensures the pending point records are cleaned up with the necessary elevated privileges. [1]https://github.com/odoo/odoo/blob/23af2b443735c6d3a2f64e44f9ea5da45638b052/addons/sale_loyalty/security/ir.model.access.csv#L16 opw-6453016 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#281477
This fix makes the Turkish Nilvera localization explicitly include a required invoicing component instead of relying on automatic installation behavior. It prevents setup errors in certain testing or deployment environments, improving reliability without changing day-to-day functionality.
Original PR description
The view 'res.partner.property.form.inherit.ubl.tr' references external ID 'account_edi_ubl_cii.view_partner_property_form' but module 'account_edi_ubl_cii' is only available because of auto_install-links in the module graph. This causes an error when auto_install is skipped on runbot. The error is fixed since saas-19.2(https://github.com/odoo/odoo/pull/248034) where the view is moved but no backport for saas-19.1 and earlier has been made. Adding the explicit dependency is valid because 'account_edi_ubl_cii' is indirectly installed at the same time. This is the effect chain: 1. 'l10n_tr_nilvera_einvoice' is auto_install'ed after 'l10n_tr_nilvera' installs 2. 'l10n_tr_nilvera_einvoice' has explicit dependency on 'account_edi_ubl_cii'. Forward-Port-Of: odoo/odoo#282475
This fixes an edge case where a company sending an electronic invoice to itself could have the incoming document incorrectly treated as a duplicate. The system now allows the matching vendor bill to be created, helping businesses handle legitimate internal invoicing without manual workarounds.
Original PR description
Edge case: self-addressed messages (sender == receiver). i.e. a company genuinely invoicing itself. The outgoing invoice already carries the message UUID, so the duplicate check would wrongly discard the incoming document. Exclude those messages from the check so the vendor bill can still be created. task-no --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#283574
Calendar invitation files now include the meeting’s video call link in a standard URL field. This helps recipients open the online meeting directly from calendar apps that use that field.
Original PR description
Add the meeting's videocall_location as a URL property in generated iCalendar (.ics) invitation files. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Fixed an issue where archiving or deleting one user could remove a shared contact from restricted mail channels even when another active user for that contact still qualified. Contacts are now only unsubscribed when no remaining linked user has access, preventing accidental loss of channel membership.
Original PR description
Before this commit, archiving or deleting a user removed its partner from every group restricted channel, even when another user of that partner was still active and in the group the channel requires. This happens because the members to unsubscribe are searched on partner_id alone, so the search cannot tell whether the partner keeps another user. This commit fixes the issue by unsubscribing a partner only when none of its remaining users has the group the channel requires.
This fix restores the ability for Safari mobile users to start a new Discuss conversation after selecting a contact. It ensures the Enter key is recognized correctly, preventing users from getting stuck at the final step.
Original PR description
**Steps to reproduce:** - Go to Discuss app on Safari mobile - Click on "Start a conversation" button - Try to search for a partner and select it - "Press Enter to start" is shown - Enter key does not work **Issue:** On selection the browser keyboard `Shift` modifier is enabled again, preventing the `enter` key press from being detected by the `ChannelSelector` when using `getActiveHotkey`. **Fix:** Rely on `ev.key` instead to ignore the modifier. Seems to be limited to 18.0 as in further versions adding the partner directly opens the conversation. opw-6375062
The spreadsheet component was updated to a newer version with performance improvements for formula calculations. Users should see faster spreadsheet behavior, especially in sheets with many formulas, without any expected workflow changes.
Original PR description
### Contains the following commits: https://github.com/odoo/o-spreadsheet/commit/3fdaa53d2a [REL] 18.0.78 [Task: 0](https://www.odoo.com/odoo/2328/tasks/0)…
### Contains the following commits: https://github.com/odoo/o-spreadsheet/commit/3fdaa53d2a [REL] 18.0.78 [Task: 0](https://www.odoo.com/odoo/2328/tasks/0) https://github.com/odoo/o-spreadsheet/commit/1411393173 [PERF] vectorization: specialize formula call for common arities [Task: 6222157](https://www.odoo.com/odoo/2328/tasks/6222157) https://github.com/odoo/o-spreadsheet/commit/5c119b4f86 [PERF] vectorization: inline generateMatrix [Task: 6222157](https://www.odoo.com/odoo/2328/tasks/6222157) https://github.com/odoo/o-spreadsheet/commit/e0c0d394b3 [PERF] vectorization: skip non-vectorized args in inner loop [Task: 6222157](https://www.odoo.com/odoo/2328/tasks/6222157) https://github.com/odoo/o-spreadsheet/commit/30364b97ee [PERF] vectorization: hoist argDefinitions out of vectorized inner loop [Task: 6222157](https://www.odoo.com/odoo/2328/tasks/6222157) https://github.com/odoo/o-spreadsheet/commit/0ee645fd96 [PERF] vectorization: hoist per-arg getter resolution out of inner loop [Task: 6222157](https://www.odoo.com/odoo/2328/tasks/6222157) https://github.com/odoo/o-spreadsheet/commit/3f4dc3deb0 [PERF] vectorization: reuse args buffer across cells [Task: 6222157](https://www.odoo.com/odoo/2328/tasks/6222157) 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>
Safari users can now use Shift+Enter in the Knowledge article editor to add a soft line break instead of unintentionally splitting the paragraph. This makes text editing behave consistently across browsers and avoids unwanted formatting changes.
Original PR description
**Steps to reproduce:** - Use a Mac with Safari - Install Knowledge app - Go to any article - Press Shift+Enter to try to enter a soft line break - Hard split is done instead **Issue:** Shift+Enter causes a `insertParagraph` event instead of `insertLineBreak` in Safari, which triggers the `SplitPlugin` instead of the `LineBreakPlugin`. **Fix:** Check if the browser is Safari and call `insertLineBreak` from the `SplitPlugin` (when needed) by listening to the "keydown" events. (note: I was not able to find any other key combination to properly trigger the `insertLineBreak` event in Safari) opw-6413507
This fix ensures inventory transfers recorded with zero planned demand are still considered when calculating past forecasted stock levels. It prevents historical forecasts from incorrectly showing negative quantities after unplanned physical movements, improving inventory reporting accuracy.
Original PR description
**Problem:** When creating a transfer that moves out a product with zero demand quantity, it will change the forecasted quantity of that product in the past. **Cause:** The query filtered out the stock move with zero demand quantity, which preventing the system from accounting for unplanned physical transfers when retroactively calculating past inventory balances **Steps to reproduce the issue:** 1. Create a stock picking with 0 demand quantity that moves a product from an internal location to a virtual location or production location. 2. The forecasted quantity of the product becomes negative in the past. **Fix:** Add another check in the query to include stock moves with zero demand quantity. **Notes:** Since the forecast report is made from a SQL view, this will require a -u to update the report. opw-6462883
This pull request groups several business-facing fixes and improvements across accounting, payroll, localization, rentals, social media, and AI assistant tools. It improves bank reconciliation guidance and performance, updates compliance rules and reports, and fixes edge cases that could block valid invoices or cause confusing user interfaces.
Incoming Chilean electronic invoices in foreign currency now import correctly even when the optional foreign-currency total is absent. The system falls back to the standard total amount, preventing fetchmail import failures and reducing manual intervention.
Original PR description
When importing an incoming DTE through the fetchmail server, the total amount is read from the MntTotOtrMnda as soon as a Moneda node is present in the document. Steps to reproduce: - Set up a CL company with a DTE mail server - Fetch a DTE that includes the line-level Moneda node but does not include the header OtraMoneda block, so no MntTotOtrMnda - Run the fetchmail cron and check the logs Issue: The DTE fails to import Analysis: Occurs since https://github.com/odoo-dev/enterprise/commit/5805a92f91411846fdffa245cb047397cfc9b1f3 Moneda is defined at line level while MntTotOtrMnda in the optional header block Encabezado/OtraMoneda. Instead of assuming MntTotOtrMnda is always present whenever the document carries a foreign currency, fall back to the base-currency total MntTotal when it is missing. opw-6432612 Forward-Port-Of: odoo/enterprise#126869
Aged Receivables and Aged Payables now calculate aging periods correctly when horizontal groups are applied. This prevents incorrect amounts from appearing in older period columns, improving reliability of customer and vendor aging analysis.
Original PR description
Problem: When using horizontal groups in Aged Receivables / Aged Payables reports, the amounts shown in the Older periods are incorrect. Steps to reproduce: 1. Activate debug mode 2. Go to Accounting…
Problem: When using horizontal groups in Aged Receivables / Aged Payables reports, the amounts shown in the Older periods are incorrect. Steps to reproduce: 1. Activate debug mode 2. Go to Accounting > Configuration > Horizontal Groups 3. Add a new horizontal group that results in at least 2 groups 4. Go to Accounting > Reporting > Aged Receivables / Aged Payables 5. Apply the horizontal group created 6. Notice how the amount in the Older period is incorrect, different from before applying the horizontal group. (It may be coincidentally correct, you can check by applying different aging intervals until you find one that shows the issue) Cause: The periods were not correctly calculated. The number of periods was calculated based on the number of period columns, without taking into account the number of column groups. When using horizontal groups, period columns are duplicated for each group that exists after applying the horziontal group. This is not considered when calculating the number of periods, which results in calculating too many periods and therefore having incorrect durations for each period. opw-6374639
When a spreadsheet is frozen in Documents, the system now records the related data loss prevention activity. This improves auditability and helps businesses track sensitive document actions more reliably.
Original PR description
Task: 6389096
This fix allows users to enter and compare budgets on the Moroccan Profit and Loss report, including reports with multiple columns. Budget amounts now remain visible and use the correct balance figures for comparison, improving financial planning accuracy for Moroccan accounting reports.
Original PR description
Before this commit, it was impossible to use budget on the Moroccan P&L, for the following reasons: - The feature was designed for one-column reports. MA's P&L uses 3, one of which is the total of…
Before this commit, it was impossible to use budget on the Moroccan P&L, for the following reasons:
- The feature was designed for one-column reports. MA's P&L uses 3, one of which is the total of the two others.
=> We remove that requirement, and make sure to always select the 'balance' column as the reference for the budget comparison.
- When trying to input a budget amount in the report, the amount disappeared entirely.
=> This was because the total column of report was not using 'balance' as its expression label. We fix that by rewriting the expression labels of that report.
The fact we hardcode the use of 'balance' is arguable. It is however not possible here to rely on some custom handler to change a specific option key that would be used to generate the budget comparison data, since some of those data need to be generated in the get_options, before _custom_options_initializer even gets called. This is the simplest approach, and this case is rare enough for us to deem it acceptable.
opw-6385229