Friday, August 21, 2026
15 changes · 18.0
Enhancements to existing features
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
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
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.
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
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
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