Friday, August 28, 2026
36 changes · saas-19.2
Resolved issues and error corrections
This fix makes an automated Knowledge calendar walkthrough more reliable by preventing timing-related failures when creating or editing calendar item properties. It helps ensure the feature continues to be tested consistently without changing the user-facing behavior.
Original PR description
In `knowledge_calendar_command_tour`, we were experiencing two issues 1. First, when we change the properties of a new calendar item, we were running into an issues where the tour would fail due to…
In `knowledge_calendar_command_tour`, we were experiencing two issues 1. First, when we change the properties of a new calendar item, we were running into an issues where the tour would fail due to not being able to locate the dropdown option to create a new property. This only occurs if you don't set a step delay on the tour. This happens because in the `editSelectMenuInput` helper, we first check if the dropdown is open bfore we proceed. Since the tour runs so fast, we detect that the dropdown for the first property is open, so we pass the check. However, this then closes since we've moved on to the next dropdown, and since nothing has been input into the next dropdown, the create option doesn't appear. Now, we ensure that the create option will be present before attempting to click it 2. Later in the tour, we attempt to edit the properties on a new calendar item. We previously used `edit` to edit the property name, however this resulted in the "Select a template" modal being opened, which broke the tour, since we needed to click elements behind it. Using `fill` instead to populate the text field doesn't produce this behavior, allowing the tour to proceed without error. [runbot-939647](https://runbot.odoo.com/odoo/error/939647?debug=assets) Forward-Port-Of: odoo/enterprise#128830
The Uzbek balance sheet now correctly includes unclosed profit or loss from the current year in the Equity section. This prevents the report from appearing out of balance before year-end closing while preserving the official report structure.
Original PR description
The Uzbek balance sheet was unbalanced because the current year's unclosed profit/loss was not reflected in the Equity section. This commit restructures '[0540] - Retained Earnings' into an aggregate of three lines: realized retained earnings (existing tag-based formula), current year unallocated earnings, and previous years' unallocated earnings, the latter two computed from income, expense and equity_unaffected accounts, scoped to the current and prior fiscal years respectively. This keeps the balance sheet correct both before and after year-end closing, without changing the report's official line numbering. see https://github.com/odoo/odoo/pull/284707 task-6361059
The Uzbekistan accounting setup now classifies current-year and period profit/loss accounts correctly so they are counted only once in the balance sheet. This prevents overstating the Equity section and improves the accuracy of local financial reports.
Original PR description
Accounts 8710 (Current Year Profit/Loss) and 9910 (Net Profit for the Period) were equity_unaffected, causing their balances to be picked up both by the retained earnings tag-based formula and by the current-year-earnings domain formula in l10n_uz_reports, double- counting them in the balance sheet's Equity section. This commit changes both accounts' type to Equity and adds the BS Line 0540 tag to 9910 (8710 already carried it), so their balances are captured through the tag alone. see https://github.com/odoo/enterprise/pull/129361 task-6361059
Sending or replying to normal messages on a tax return will no longer accidentally change its status to Paid. Tax return payments are now only finalized from the intended tax payment instructions flow, reducing the risk of incorrect return statuses.
Original PR description
Before this fix: Replying to or sending a message from the chatter of a tax return could incorrectly change its state to Paid. This happened because action_send_mail() automatically called _action_finalize_payment() for account.return records. After this fix: Payment finalization only happens when the composer is opened from the tax payment instructions flow. Normal chatter messages and replies will no longer change the tax return state to Paid. task-6469536 Forward-Port-Of: odoo/enterprise#129161
This fix ensures user group checks work correctly when the same group has more than one internal identifier. It prevents incorrect access or membership results caused by looking up only one of those identifiers.
Original PR description
A group can be identified by multiple xmlids. We add support to provide a list of "refs" to the `SetDefintions` object.
Reproductible issue:
```
demo = self.env["res.users"].browse(5)
demo.has_group("accountant.group_account_user") # False
demo.has_group("account.group_account_user") # True
assert self.env.ref("accountant.group_account_user") == self.env.ref("account.group_account_user")
```
task-6471260
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#285153
Forward-Port-Of: odoo/odoo#284866Fixes an issue where confirming a sales order again after cancellation could fail to create a new event registration. This helps ensure customers are properly registered for events when orders are reset and reconfirmed, including through portal flows or skipped registration dialogs.
Original PR description
**Steps to reproduce:** - Create a SO and add the Event Registration - Standard product and specify an event - Confirm the SO then select "Create/Update registrations" - Cancel the SO, then "Set to…
**Steps to reproduce:** - Create a SO and add the Event Registration - Standard product and specify an event - Confirm the SO then select "Create/Update registrations" - Cancel the SO, then "Set to Quotation" - Select "Preview" and confirm the Sale Order again - There will not be any new registration created when there should be one. **Behavior:** Usually when a sale order is confirmed the `action_sale_order_event_registration` form will be opened which when filled correctly creates registrations. However in certain cases: confirming from the customer portal, or simply closing the form when it is opened, will not trigger `action_make_registration` which creates registrations if it is not already the case `action_confirm()` should be creating the registrations correctly on its own anyway by calling ´init_registrations()´ : https://github.com/odoo/odoo/blob/beed378cde592bc96c1e79a976ac775264b843ed/addons/event_sale/models/sale_order_line.py#L49-L66 This function tries to create each missing registrations by looking at the amount in the so_line and deducting the already created registrations, however since some of them can be cancelled, this computation is wrong. And leads to registration not being created when they should. opw-6444127 Forward-Port-Of: odoo/odoo#280426
PDF Quotes generated with Quote Builder could fail when selected documents contained dynamic fields, especially with newer PDF library versions. This fix preserves the form settings and adds the expected empty resource entry so quotes can be generated reliably without errors.
Original PR description
Issue: --- Due to this issue, generating PDF Quote using Quote Builder with dynamic fields leads to a traceback. This was partially fixed by: e16edc7b0d9f56cb7769068ecf7d64ff8b0f6359 Steps: --- 1-…
Issue: --- Due to this issue, generating PDF Quote using Quote Builder with dynamic fields leads to a traceback. This was partially fixed by: e16edc7b0d9f56cb7769068ecf7d64ff8b0f6359 Steps: --- 1- Using a python 3.13 env, install requirements.txt. (You could instead uninstall pypdf2 and install pypdf==5.4.0) 2- Enable Quote Builder. 3- Create a SO and in quote builder tab, select a document. This document should have dynamic fields. e.g. you could use`Office Furnitures Header` document. 4- Print -> PDF Quote. Cause: --- In previous fix, we fixed the traceback when no dynamic field is set. However, if you have a dynamic field, then inside `PdfWriter._update_field_annotation()`, the font is get from `DR` dict inside acroform: https://github.com/py-pdf/pypdf/blob/f20954f2241640feb484800e191373f8fbdfa44b/pypdf/_writer.py#L917-L929 Even if we are not setting a font, we need to have an empty `DR` dict inside acroform, in order to avoid calling `get` on a none object, which is leading to the traceback. Also in previous fix we were losing `NeedAppearances` inside `AcroForm` by creating new dict which wasn't right. opw-6392001 Forward-Port-Of: odoo/odoo#278881
Users can now open the link popover when clicking non-editable links, making it possible to view or inspect link details even in read-only content. This fixes a case where clicking an inserted article link appeared to do nothing, improving clarity for website and editor users.
Original PR description
Problem: Clicking a non-editable link does nothing, making it impossible to open or inspect the link. Solution: Allow the link popover to open in read-only mode for non-editable links. Steps to reproduce: - Run `/article`. - Click on the inserted article link. - Observe that nothing happens. opw-6442026 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#284560 Forward-Port-Of: odoo/odoo#280224
Restaurant Point of Sale now keeps the entered guest count when the same table is opened from another device. This prevents staff from being prompted twice and helps keep table service smoother across shared devices.
Original PR description
Steps to reproduce: - Enable presets on a restaurant PoS and tick "Amount of Guests" on the preset used for tables - On device A, open a table and enter the number of guests - On device B, open the…
Steps to reproduce: - Enable presets on a restaurant PoS and tick "Amount of Guests" on the preset used for tables - On device A, open a table and enter the number of guests - On device B, open the same table Issue: Device B pops the guest count numpad again, even though the guest count was already entered on device A. Cause: ensureGuestCustomerCount guarded the popup on order.uiState.guestSetted. uiState is only serialized to IndexedDB (SERIALIZED_UI_STATE_PROP, used by serializeForIndexedDB); it is never sent to the server, so the flag is local to one browser and a second device always considers the guest count as not yet asked. customer_count is synced and could carry that information, but PosStore createNewOrder pre-filled it with the table seats, so it was never 0 for a table order and could not tell "not asked" from "answered". Fix: Stop storing the seats default on the record and expose it from getCustomerCount() instead, so customer_count == 0 means "no guest count entered yet". ensureGuestCustomerCount now guards on that synced value, so an order whose guest count was entered on another device is not asked for it again. Every display goes through getCustomerCount(), so the values shown on the Guests button, the receipt and the preparation ticket are unchanged. opw-6470180 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#284793 Forward-Port-Of: odoo/odoo#283002
This fixes an issue where Odoo could use the wrong path separator when handling static files on Windows. It helps ensure assets and other static resources are found reliably across operating systems.
Original PR description
In commit 31aad6c, path normalization was added which also resulted in `/` being converted into `\` on Windows. There the `path.split('/')` did not work.
This commit changes the `'/'` to `os.sep` to fix the issue.
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#285216Planning now correctly totals allocated hours when a shift involves multiple employees with the same working schedule. This prevents daily Gantt view totals from being undercounted or displayed incorrectly, helping managers rely on accurate staffing information.
Original PR description
## Steps to reproduce: - Install Planning and Employee - Create a working schedule for example 38h/week (8h,8h,8h,8h,6h) - Create two employees and assign the created working schedule to them -…
## Steps to reproduce: - Install Planning and Employee - Create a working schedule for example 38h/week (8h,8h,8h,8h,6h) - Create two employees and assign the created working schedule to them - Create a shift in planning for the two employees for a week - Notice in gantt view the total allocated hours for each day are not calculated correctly ## Cause: When calculating each cell's duration we fetch the resources' intervals while doing this here https://github.com/odoo/enterprise/blob/1851d3046682020686f517dec3c744d88a38b049/planning/static/src/views/planning_gantt/planning_gantt_renderer.js#L354-L359 we loop over the first interval and instead of pushing to the intervals we overwrite on the whole list with the interval we fetched so when it comes to the next interval it won't find any intersection so it will set the resourceIntervals to an empty array. So here https://github.com/odoo/enterprise/blob/1851d3046682020686f517dec3c744d88a38b049/planning/static/src/views/planning_gantt/planning_gantt_renderer.js#L231-L234 it will mess up the percentage calculation which will lead to wrong allocated hours numbers. ## Fix: Instead of overwriting on resourceIntervals we push to it the intervals returned to keep the intervals for each resource. opw-6316368
This fixes an issue in Documents where the "Search More" popup could close unexpectedly while editing document details such as Owner or Customer. Users can now interact with the popup to sort, resize, and select records without losing their current document selection.
Original PR description
Steps to reproduce: 1. Install Documents 2. In the Documents list view, select a document to display the inspector. 3. Edit a field such as Owner or Customer which uses a Many2one widget. 4. In the…
Steps to reproduce: 1. Install Documents 2. In the Documents list view, select a document to display the inspector. 3. Edit a field such as Owner or Customer which uses a Many2one widget. 4. In the field dropdown, click "Search More..." to open a modal dialog. 5. Click inside the "Search More..." modal (e.g., to sort columns or resize headers). Issue: - The modal dialog immediately closes, and the contact cannot be selected. Root cause: - When an inspector field is edited, the record row is put into edit mode. While in edit mode, the documents list renderer listens for global clicks. Clicking inside the "Search More..." modal dialog targets elements that have `.o_list_renderer` (since the modal dialog renders a list view). Because the click target is within a list renderer but is not a document row, `DocumentsListRenderer.onGlobalClick` executes and clears the selection of the main list view. Clearing the selection unmounts the edited field in the inspector, thereby destroying the modal dialog stack. Solution: - Modify DocumentsListRenderer.onGlobalClick to scope click handling to the current Documents list renderer. Ignore clicks outside this.root.el, so interactions in nested UI such as Search More... do not clear the main selection and destroy the inspector field. opw-6253360 Forward-Port-Of: odoo/enterprise#128812 Forward-Port-Of: odoo/enterprise#119262
Archiving or deleting one user will no longer remove a shared contact from restricted discussion channels when another active user for that contact still qualifies. This prevents accidental loss of channel access for businesses that use multiple users linked to the same contact.
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. Forward-Port-Of: odoo/odoo#283933 Forward-Port-Of: odoo/odoo#283807
The inventory report now prints location grouping rows with the correct column alignment. This prevents missing gridlines and broken borders in PDF reports, making printed inventory counts clearer and more professional.
Original PR description
When new columns were added to the stock inventory report, the location grouping row was not updated. This results in mismatched column counts, causing missing gridlines and broken borders in the PDF output Fixed by ensuring the location row's column count matches the header <img width="603" height="200" alt="image" src="https://github.com/user-attachments/assets/86872bee-f315-4bdd-b3f2-a525e3bb5fe0" /> ### Steps to reproduce: - Ensure warehouses are activated in the settings - Go to Barcode -> Count Inventory - Add a Product - Select the gear Icon then "Print Inventory" - You will notice that the location row has missing gridlines opw-6307728 Forward-Port-Of: odoo/odoo#275918
This fixes Uruguay electronic invoicing so credit notes and related documents use the exact exchange rate saved on the original invoice. It prevents mismatches when currency rates are updated after an invoice has already been posted, improving consistency for accounting documents.
Original PR description
18.0 introduced an `invoice_currency_rate` field, storing the currency rate used for each invoice. Currently `_l10n_uy_edi_get_used_rate` uses the `currency.convert()` method using the date and company of the invoice. This is vulnerable to inaccuracy if the currency rates are changed after the invoice posting. For example, if an invoiceis posted and the currency rates are updated, the invoice will use the old rate, while `_l10n_uy_edi_get_used_rate` will return the new one. In the case of the customer in the related ticket, this caused their credit note reference currency rate to mismatch with the one on the invoice. This PR changes the `currency.convert()` call to fetching and inverting the `invoice_currency_rate` field. This ensures the returned value will match the one used for the invoice. opw-6456867 Forward-Port-Of: odoo/enterprise#128190
Appraisals now keep the generic template chosen by the user when they are confirmed or reset, instead of switching back to the first available template. This ensures appraisal records remain easy to find with the expected template filters and avoids confusion for HR teams.
Original PR description
Issue: When a user selects a generic appraisal template other than the first one, confirming or resetting the appraisal silently replaces that selection with the first generic template. Filtering…
Issue: When a user selects a generic appraisal template other than the first one, confirming or resetting the appraisal silently replaces that selection with the first generic template. Filtering appraisals by the originally selected template then fails to return the appraisal. Steps to reproduce: * Configure multiple appraisal templates without department restrictions. * Create an appraisal and select a template other than the first one. * Confirm the appraisal. * Filter appraisals by the selected template. Cause: `_compute_appraisal_template()` only preserved templates directly linked to the appraisal's department. Generic templates have no department relation, so a valid selected template was discarded whenever the computation was triggered by a state dependent department recomputation. The generic fallback then stored the first template instead. https://github.com/odoo/enterprise/blob/5c57ccbb13269af28de0a6c7f35454be52424f43/hr_appraisal/models/hr_appraisal.py#L195-L209 Solution: We need to distinguish the generic default loaded on an unsaved form from a compatible generic template already stored on an appraisal. Preserve the latter across recomputations while retaining department template priority during creation and rejecting templates that no longer match the appraisal's department or company. opw-6449041 Forward-Port-Of: odoo/enterprise#127566
This fix makes a Belgian payroll attendance test less sensitive to demo employee data that can vary between environments. It helps keep automated checks reliable without changing payroll behavior for users.
Original PR description
The FFE employer contribution rate depends on the company's current worker count. Additional demo employees installed on runbot can move the company across the applicable threshold and change the expected payslip amounts. This commit update the test expectations according to the computed worker count. [error-939674](https://runbot.odoo.com/odoo/error/939674)
Manual prices on optional quotation lines are now preserved when customers change quantities in the portal preview. This prevents custom sales pricing from being accidentally replaced by automated pricelist rules, helping quotations remain accurate.
Original PR description
When a user manually sets a price on an optional line (overriding the pricelist), and then changes the quantity in the portal preview, the manual price is lost and gets reset to the pricelist price.…
When a user manually sets a price on an optional line (overriding the pricelist), and then changes the quantity in the portal preview, the manual price is lost and gets reset to the pricelist price. Steps to reproduce: --- - Install Sales module and enable Pricelists. - Create a product with qty-based pricelist rules: - min qty: 1 → price: 100 - min qty: 10 → price: 80 - Create a quotation with an optional section containing this product. - Manually change the product price to 150 (overriding pricelist). - Mark the section as optional and preview the quotation. - Change the quantity to 10 in portal preview. Issue: --- - The manually set price (150) is incorrectly reset to the pricelist price (80). Root cause: --- - After [commit], if there is no config parameter set and if there is an active pricelist, we simply call `_reset_price_unit()` without checking whether the price was manually set or not. - Additionally, `_reset_price_unit()` calls `update()` which writes `price_unit` and `technical_price_unit` one by one as separate `write()` calls. The `write()` method has a guard([1]) that strips a lone `technical_price_unit` write unless `sale_write_from_compute` is set in context. Without this flag, `technical_price_unit` is silently discarded, causing it to drift from `price_unit`. On the next qty change, this mismatch is detected as a manual price, permanently blocking further pricelist updates. Solution: --- - Check whether the price was manually set before calling `_reset_price_unit()`, and pass `sale_write_from_compute=True` in context so both `price_unit` and `technical_price_unit` are written correctly. [commit]: https://github.com/odoo/odoo/commit/93b6bdd6a4909bc0b45b90ab6a2d0734a218292d [1]https://github.com/odoo/odoo/blob/fffd987cc98d1ea0cd04e24dda2ed8b64a219cdc/addons/sale/models/sale_order_line.py#L1391-L1399 opw-6426634 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#281107
Fixed an accounting issue where reversing a cash basis tax entry after unreconciling a payment could be dated in the current period instead of the original tax period. This keeps tax reports balanced in the correct month, preventing amounts from appearing in different reporting periods.
Original PR description
When unreconciling a payment from an invoice with a cash basis tax, the tax cash basis (CABA) entry is reversed. The reversal is supposed to land in the same period as the origin entry so the tax…
When unreconciling a payment from an invoice with a cash basis tax, the tax cash basis (CABA) entry is reversed. The reversal is supposed to land in the same period as the origin entry so the tax report nets to zero for that period. Steps to reproduce: - Enable cash basis and create a cash basis tax (exigibility on payment) - Post an invoice dated in the past with that tax - Reconcile a bank statement line to the invoice - Resequence the cash basis entry so the month is dropped from the name (CABA/08/2026/0001 -> CABA/2026/0001) - Unreconcile the statement line Issue: The reversal CABA entry created on the last unreconcile is dated today instead of the origin entry's month. In the tax report the original tax amount stays in the statement's month while the reversal amount appears in the current month, so the two no longer cancel out. Analysis: While under a monthly journal sequence a past date returns the last day of that month, under a yearly sequence a past date within the current year returns the latter between the move date and today, moving the reversal out of the origin period. opw-6301553 Forward-Port-Of: odoo/odoo#284840 Forward-Port-Of: odoo/odoo#281250
Customers can no longer set optional products on sales quotes to negative quantities from the portal. This keeps customer-entered optional items valid and reserves exceptional negative adjustments for sales staff when appropriate.
Original PR description
Since the fusion of `sale.order.option` model into `sale.order.line` model, the optional products (editable from portal) lines are not deleted when reaching a quantity of 0 or below. This could allow some customers to set negative quantities, which makes no sense as it's only something that should be set by the salesman if necessary. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#284493
This fix prevents recurring subscription billing from crashing when a saved payment method fails. It helps ensure failed payments are handled cleanly without interrupting the automated invoicing process or incorrectly removing invoices after successful payments.
Original PR description
Step to reproduce: - create a faulty token that won't work and link it to a subscription - launch the recurring invoice cron - the following traceback occurs ``` last_tx_sudo = (self.transaction_ids…
Step to reproduce:
- create a faulty token that won't work and link it to a subscription
- launch the recurring invoice cron
- the following traceback occurs
```
last_tx_sudo = (self.transaction_ids - existing_transactions).sudo()
```
When the payment fails, the system rollback and we store the last_tx_sudo value in a dedicated variable. After rollback, the record does not exists anymore. Therefore, accessing the value fails.
```
File "/home/odoo/src/enterprise/saas-18.2/sale_subscription/models/sale_order.py", line 1703, in _handle_automatic_invoices
if not last_tx_sudo or last_tx_sudo.renewal_state in ['pending', 'authorized']:
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/saas-18.2/odoo/orm/fields.py", line 1439, in __get__
self.compute_value(record)
File "/home/odoo/src/odoo/saas-18.2/odoo/orm/fields.py", line 1603, in compute_value
records._compute_field_value(self)
File "/home/odoo/src/odoo/saas-18.2/odoo/orm/models.py", line 4575, in _compute_field_value
determine(field.compute, self)
File "/home/odoo/src/odoo/saas-18.2/odoo/orm/fields.py", line 69, in determine
return needle(*args)
^^^^^^^^^^^^^
File "/home/odoo/src/enterprise/saas-18.2/sale_subscription/models/payment_transaction.py", line 25, in _compute_renewal_state
if tx.state in ['draft', 'pending']:
^^^^^^^^
File "/home/odoo/src/odoo/saas-18.2/odoo/orm/fields.py", line 1406, in __get__
raise MissingError("\n".join([
odoo.exceptions.MissingError: Record does not exist or has been deleted.
```
Moreover, since https://github.com/odoo/enterprise/pull/45236/files#diff-c36fd7952cc2bef40716419a668de41963d49e1aa4177d9319d503fc260da588R1678-R1682
```
if not last_tx_sudo or not last_tx_sudo.renewal_state not in ['pending', 'authorized']:
```
has become
```
if not last_tx_sudo or last_tx_sudo.renewal_state in ['pending', 'authorized']:
```
But it feels strange to unlink the invoice when the payment succeed.
This PR fixes it.
Forward-Port-Of: odoo/enterprise#83913Delivery fee lines are no longer shown in the “Invoiced not Delivered” accounting report because they are not physical items to deliver. This makes the report more accurate and helps accounting teams focus only on products that still require delivery follow-up.
Original PR description
Issue: --- Delivery lines are included in `invoiced not delivered` report, which is wrong as delivery lines are not deliverable. Steps: 1- Create a SO with a good product and add a delivery line. Set the product line as delivered and create an invoice. 2- Open accounting, and from review tab, open `Invoiced not Delivered`. As you see, delivery lines are included in the report. Fix: --- On stable we could fix it inside `_get_accrual_domain` by checking if `delivery` is installed. On master we need to implement a solution to be able to differentiate the lines that won't be delivered. opw-6360894 Forward-Port-Of: odoo/enterprise#123517
Closed or renewed subscriptions will no longer incorrectly appear in orders to invoice because prepaid recurring lines are cleared when no further billing should occur. Postpaid lines remain billable where products have already been delivered, helping teams avoid misleading invoice queues while preserving valid charges.
Original PR description
When a closed subscription could still show up in the Orders to Invoice because its recurring lines kept their "to invoice" status. This was misleading since no further period should be billed. When a subscription is churned (or renewed), prepaid lines are now flagged as nothing to invoice. Postpaid lines are left as is so that already delivered products can still be billed after closing. task-6227787 Forward-Port-Of: odoo/enterprise#117797
Changing a project's visibility no longer fails when the project folder contains document shortcuts. This ensures access settings can be updated as expected while still preventing direct access changes on shortcut-only selections.
Original PR description
Changing a project's visibility fails when its documents folder contains a shortcut. The visibility change is never applied and the following error is raised: "You can not update the access of a…
Changing a project's visibility fails when its documents folder contains a shortcut. The visibility change is never applied and the following error is raised: "You can not update the access of a shortcut, update its target instead." ### Reproduction steps - Create a project and add a document to its folder. - Create another document outside the project's folder. - Create a shortcut to that document in the project's folder. - Change the project's visibility. ### Cause Changing a project's visibility updates the access rights of its folder and documents together. The shortcut access check is meant to reject operations performed only on shortcuts. However, reading `shortcut_document_id` on a recordset returns the shortcut targets found across that recordset. Therefore, the presence of a single shortcut makes the check reject the whole operation. This prevents regular documents and the project folder from having their access updated. ### Fix Only reject access updates when all records involved are shortcuts. This preserves the protection against changing shortcut access directly while allowing project access updates to include shortcuts alongside regular documents and folders. opw-6472637 Forward-Port-Of: odoo/enterprise#129448 Forward-Port-Of: odoo/enterprise#128756
Fixes an accounting issue where removing the reconciliation for one bank statement line could accidentally unreconcile every payment linked to the same invoice or bill. This keeps the rest of the invoice or bill payment matching intact, reducing manual rework and risk of accounting errors.
Original PR description
**STEP TO REPRODUCE** 1. Create a bill or an invoice. 2. Create multiples bank statement. 3. Reconciles those bank statements to the invoice/bill. 4. Unreconciles one of those bank statement on the invoice/bill. 5. Notice the invoice/bill is completely unreconciled. Expected behavior: only the unreconciled line should be unreconciled. **CAUSE** When unreconciling a partial linked to a bank statement, we call `delete_reconciled_line()` on both `partial.credit_move_id` and `partial.debit_move_id`. One on those is the the payment_term line of the invoice/bill the bank statement line is reconciled with. This payment_term line is also linked to all partial reconcilliation line on the invoice/bill, so calling `delete_renconciled_line()` delete all the reconciled line of the invoice/bill. **FIX** We should call `delete_renconciled_line()` only on the bank statement move line, not on the payment term line. opw-6465096 Forward-Port-Of: odoo/enterprise#128126
Odoo now creates default initials avatars in a format browsers consistently recognize as images. This prevents contacts, users, chatter authors, and Discuss participants without uploaded photos from showing as file downloads instead of inline avatars.
Original PR description
**Description of the issue/feature this PR addresses:** `avatar.mixin._avatar_generate_svg()` generates an auto-initial SVG avatar for any record without a real uploaded image (used by `res.partner`,…
**Description of the issue/feature this PR addresses:** `avatar.mixin._avatar_generate_svg()` generates an auto-initial SVG avatar for any record without a real uploaded image (used by `res.partner`, `res.users`, and anything else inheriting `avatar.mixin`). The generated SVG opens with a single-quoted XML declaration: `<?xml version='1.0' encoding='UTF-8' ?>`. When this content is served via `/web/image/...`, `guess_mimetype()` needs to determine its Content-Type since it's a computed value with no stored attachment metadata. On systems where the installed `libmagic` library classifies that specific single-quoted byte pattern as `text/xml` rather than `image/svg+xml`, the wrong Content-Type reaches the browser. **Current behavior before PR:** On affected `libmagic` versions/databases, an auto-generated avatar (a contact or user with no uploaded photo) gets served with `Content-Type: application/octet-stream` (or `text/xml`) instead of `image/svg+xml`. Browsers can't render that inline as an image, so instead of showing the colored-initial avatar, the browser downloads it as an unrecognized file. This affects any place these avatars are displayed: contact/user form and kanban views, chatter message authors, Discuss, etc. Confirmed reproducible with `libmagic` 538 (`python-magic`), where the single-quoted declaration is classified as `text/xml`, while the exact same content with double-quoted attributes is correctly classified as `image/svg+xml`. **Desired behavior after PR is merged:** `_avatar_generate_svg()` now generates its markup with double-quoted attributes throughout, which is correctly sniffed as `image/svg+xml` regardless of the installed `libmagic` version. Auto-generated avatars render inline in the browser as intended. No other code depends on the exact quoting of this generated SVG - `res_users.py` and `hr_employee.py` both only assign the returned bytes to an image field without inspecting their content. Avatar fields are computed and non-stored, so nothing needs to be migrated - every record gets the corrected markup on its very next read, with no backfill required. Updated the two existing `test_avatar_mixin.py` tests that asserted the exact (single-quoted) SVG string, and added `test_generated_partner_avatar_mimetype` to assert the generated avatar is actually sniffed as `image/svg+xml`, so this can't silently regress. Ran locally: all 6 tests in `TestAvatarMixin` pass. Forward-Port-Of: odoo/odoo#283741 Forward-Port-Of: odoo/odoo#283149
Invalid VAT warnings now display the full VAT number exactly as entered by the user, instead of accidentally dropping the country prefix. This reduces confusion when contacts are saved or imported with incorrectly formatted VAT numbers and helps users identify what needs correcting.
Original PR description
Before this change: When entering or importing a VAT number (e.g., CHE-115.391.649), an invalid VAT warning displays a string missing its country_id (e.g., E-115.391.649). This confuses users and masks the actual input string that triggered the validation failure. To reproduce: 1. Open any contact record and set the Country to Switzerland. 2. Enter an invalid or manually formatted Swiss VAT number like `CHE-115.391.649`. 3. Save or trigger the VAT validation check. 4. Observe the warning banner showing `E-115.391.649` instead of `CHE-115.391.649`. After this change: The validation warning logic preserves the original user input when constructing the alert message, ensuring error notifications accurately display VAT number. Issue introduced by: * https://github.com/odoo/odoo/commit/ac95d2d6d80a368dfb190d0ac21da2af479a8488 * https://github.com/odoo/odoo/commit/a2afe3292e1cd0a4f339dc47707e469653d13ea0 opw-6474217 Forward-Port-Of: odoo/odoo#284305
Manufacturing orders using a three-step warehouse flow are now included correctly in stock forecasts for the final stock location. This prevents replenishment screens from understating expected inventory and helps planners avoid unnecessary purchases or production.
Original PR description
### Steps to reproduce: - In the settings enable Multi-Steps Routes - Put your warehouse in manufacture in 3 steps - Create a storable product P - Create and confirm an MO for 1 unit of P - Go to…
### Steps to reproduce: - In the settings enable Multi-Steps Routes - Put your warehouse in manufacture in 3 steps - Create a storable product P - Create and confirm an MO for 1 unit of P - Go to Inventory > Operations > Procurement > Replenishment - Create a new one for P in WH/stock #### > The forecasted quantity in stock is still 0 but should be at 1 ### Cause of the issue: This is the exact use case already fixed in 85dd3369ed17b98b2ce485be04f140cf4cfa8aa3, which stamped the finished move with a `location_final_id` pointing at WH/Stock so that the move contributes to the forecast there even though its `location_dest_id` is the intermediate WH/Post-Production. That fix was reverted in practice by 42275f83dc5350822a625e19d65148e8b41ab1d4, which replaced the value with `mo.location_dest_id`: https://github.com/odoo/odoo/blob/6a56908e5febfdb4e6e0eacfb8655b092319819f/addons/mrp/models/stock_move.py#L466-L467 Its reasoning was that in a single-warehouse setup `location_dest_id` equals the warehouse stock location, so the behaviour would be unchanged. That holds in 1 and 2 steps, where the extra step is on the component side and only moves `default_location_src_id` to the pre-production location. It breaks in 3 steps, the only mode that also moves `default_location_dest_id`, to the post-production location: https://github.com/odoo/odoo/blob/6a56908e5febfdb4e6e0eacfb8655b092319819f/addons/mrp/models/stock_warehouse.py#L246-L247 and `_compute_locations` propagates it to the MO: https://github.com/odoo/odoo/blob/6a56908e5febfdb4e6e0eacfb8655b092319819f/addons/mrp/models/mrp_production.py#L334-L341 WH/Post-Production is a sibling of WH/Stock under the warehouse view location, not a child of it. Since `location_final_id` takes precedence over `location_dest_id` for the non-done part of the move chain: https://github.com/odoo/odoo/blob/6a56908e5febfdb4e6e0eacfb8655b092319819f/addons/stock/models/product.py#L331-L334 the finished move stopped being counted in the WH/Stock forecast. Why the test did not catch it: `test_3_steps_manufacturing_forecast` stayed green through the whole regression, because it scoped `virtual_available` with a `location_id` context key. `_get_domain_locations` only reads `location` and `warehouse_id`; `location_id` is silently ignored: https://github.com/odoo/odoo/blob/6a56908e5febfdb4e6e0eacfb8655b092319819f/addons/stock/models/product.py#L284-L287 The call therefore fell through to the branch scoping the forecast to every warehouse view location: https://github.com/odoo/odoo/blob/6a56908e5febfdb4e6e0eacfb8655b092319819f/addons/stock/models/product.py#L303-L309 and the warehouse view location is the common parent of both WH/Stock and WH/Post-Production. The assertion held regardless of where `location_final_id` pointed, so the test was a false positive from the start: it also passes with 85dd3369ed17b98b2ce485be04f140cf4cfa8aa3 fully reverted. Using the `location` key makes it fail without the fix and pass with it. ### Fix: Neither fix proposition was right on its own; each one was correct only in its own scenario. The`mo.warehouse_id.lot_stock_id` resolves the warehouse from the components, so it points at the wrong warehouse as soon as the finished product is produced for another one. `mo.location_dest_id` is the post-production location as soon as the warehouse manufactures in 3 steps, so it drops the quantity from the forecast of the manufacturing warehouse itself. What separates the two is not the warehouse but whether the destination is a transit step. In 3 steps the finished product only reaches the stock through the post-production push rule: https://github.com/odoo/odoo/blob/6a56908e5febfdb4e6e0eacfb8655b092319819f/addons/mrp/models/stock_warehouse.py#L57 so the final location is the stock of the warehouse owning that destination. Any other destination is already final and is kept as is, which leaves cross-warehouse MOs and destinations set to a sub-location of the stock untouched. The rule's destination is read rather than `warehouse.lot_stock_id` because a push move takes its destination from the rule and not from the operation type: https://github.com/odoo/odoo/blob/6a56908e5febfdb4e6e0eacfb8655b092319819f/addons/stock/models/stock_rule.py#L256-L260 so the forecast stays correct when the store step is reconfigured to land somewhere else than the warehouse stock. The rule is looked up on `pbm_route_id` by its `picking_type_id` instead of through `warehouse.sam_rule_id`, because that field is no longer set. It used to be an entry of `_generate_global_route_rules_values`, and it is that entry which made the generic warehouse machinery create the rule and store it back on the warehouse: https://github.com/odoo/odoo/blob/6a56908e5febfdb4e6e0eacfb8655b092319819f/addons/stock/models/stock_warehouse.py#L403-L410 11e69870db1c49d9a6af79ffd263e4e162b34b6b removed it when the post-production step stopped being a pull rule on the Manufacture route and became a push rule generated from `get_rules_dict`. Only the field declaration was left behind, and nothing writes it any more: https://github.com/odoo/odoo/blob/6a56908e5febfdb4e6e0eacfb8655b092319819f/addons/mrp/models/stock_warehouse.py#L21-L22 so reading it would silently give an empty recordset. The lookup is not delegated to `_get_push_rule` to avoid a search per finished move. opw-4882390 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#283952 Forward-Port-Of: odoo/odoo#283234
This fix prevents inventory barcode records from failing when tracked and untracked product lines appear together. It keeps tracking information matched to the correct lines and shows a clear message for lines without tracking numbers, improving reliability when viewing or processing stock movements.
Original PR description
Problem: `_compute_electronic_product_code` built `tracking_number_list` by filtering out move lines without a lot_id/lot_name, but kept iterating over the full, unfiltered `move_line_ids`. As soon…
Problem: `_compute_electronic_product_code` built `tracking_number_list` by filtering out move lines without a lot_id/lot_name, but kept iterating over the full, unfiltered `move_line_ids`. As soon as a tracked product had an untracked move line mixed in with tracked ones (e.g. a manufacturing byproduct move line with no lot), the two lists fell out of sync: at best tracking numbers got assigned to the wrong move line, at worst `tracking_number_list[i]` went out of range and raised an IndexError. Solution: Exclude untracked move lines from `move_line_ids` before building `tracking_number_list`, so both stay the same length and index- aligned. Untracked lines get their own explicit "no tracking number" error instead of breaking the alignment for the rest. Steps to reproduce: Open runbot V19 -> go to moves history (Inventory) -> add `electronic_product_code` to list view using studio -> remove filter/select all records -> https://anotepad.com/notes/jwxyskc2 Forward-Port-Of: odoo/enterprise#123859
Takeout and delivery confirmation emails from self-ordering now include access to the customer receipt as promised in the message. This prevents customers from receiving incomplete emails and reduces follow-up requests for proof of purchase.
Original PR description
Currently, when takeout and delivery mails are sent out to clients the mention "Attached you will find you receipt" can be seen but no receipt is sent. Steps to reproduce: ------------------- * Modify restaurent config * Enable QR + self ordering * Add Online payment method * Open the self order * Make an order for delivery or takout * Pay the order * Check the emails sent > No attachment provided Why the fix: ------------ Since this commit https://github.com/odoo/odoo/commit/a0b567508ffeb572a3c36bf28ae085d766d95f18 we now send the email only from the backend but the receipt couldn't be rendered from the backend at that time. In this version it is now possible so we cans attach the receipts with the mail. opw-6197985
Invoice sequence gap warnings now check each suffix separately, so invoices like INV/2026/00010 and INV/2026/00010A no longer interfere with each other. This prevents false red warnings and ensures real numbering gaps remain visible in the correct sequence.
Original PR description
### Issue: When moves share the same `sequence_number` in a journal but have different suffixes (e.g. `INV/2026/00010` and `INV/2026/00010A`), the `made_sequence_gap` flag was incorrectly set ###…
### Issue: When moves share the same `sequence_number` in a journal but have different suffixes (e.g. `INV/2026/00010` and `INV/2026/00010A`), the `made_sequence_gap` flag was incorrectly set ### Cause: `_update_sequence_made_gap`, introduced in commit https://github.com/odoo/odoo/commit/17893089e8b21c0ecab5e61ed8e2c33f3731b3ac selects the previous and next moves ordered by `sequence_number` without filtering by suffix This causes two issues: - Moves from different suffix sequences are used as neighbors, leading to incorrect gap detection - Duplicate `sequence_number` values across suffixes are not accounted for, so only one move is considered per number ### Steps to reproduce: - Install `account` - Post 12 invoices to get a sequence up to `INV/2026/00012` - Reset `INV/2026/00012` to draft, rename it to `INV/2026/00010A` - Reset `INV/2026/00010A` to draft, rename it to `INV/2026/00009A` and confirm Before the fix: `INV/2026/00011` is red Expected: `INV/2026/00011` should not be red because `INV/2026/00010` exists - Delete `INV/2026/00009` Before the fix: `INV/2026/00010` is red Expected: `INV/2026/00010` should be red (gap in no-suffix sequence) - Reset `INV/2026/00009A` to draft and confirm it again Before the fix: `INV/2026/00010` is not red Expected: `INV/2026/00010` should still be red (different suffix) ### Notes: Suffix changes are treated as distinct sequences following the same gap rules as any other sequence This was agreed with R&D — the gap flag is meant to signal inconsistencies within a sequence, not across suffixes opw-6454823 Forward-Port-Of: odoo/odoo#282503
The registration wizard for France's PDP process no longer shows an unnecessary “Production” label when users are already working in production mode. This avoids confusing wording and makes the registration step clearer for users.
Original PR description
It makes no sense to mention (Production) on pdp registration wizard when you are in prod mode Forward-Port-Of: odoo/odoo#280501 Forward-Port-Of: odoo/odoo#280360
This fixes an intermittent issue in CRM's automated forecast checks by waiting until an opportunity is fully marked as won before moving on. It helps keep internal test results stable and reduces false alarms during release validation.
Original PR description
The crm_forecast tour is red randomly on runbot on the Won banner step. We click the won button and go back directly, so the kanban can be loaded before the lead is won. Now we wait for the ribbon first. runbot-242139 Forward-Port-Of: odoo/odoo#284721
This update prevents an unexpected error from interrupting report-related actions in the base system. It improves reliability for users by ensuring the correct record context is used when processing these actions.
Original PR description
opw-6360013 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#274977
Appointment invitation emails now generate public calendar links without triggering access errors. This helps recipients open their appointment details smoothly from email invitations.
Original PR description
Since calendar attendee access tokens are restricted to system users, appointment mail templates must sudo token reads when generating public calendar links. This follows the same pattern as the calendar mail templates and avoids an AccessError when rendering attendee invitation emails. ref: https://github.com/odoo/enterprise/commit/88a3cca752a5f726cd0260b485fc93f65a268cf8 Task-4711415 Forward-Port-Of: odoo/enterprise#129511
The automated walkthrough for field service sales now handles the signing flow in the correct order and waits properly when the page changes. This reduces false test failures and helps ensure the sales signing process remains dependable.
Original PR description
Solution: Reorder the signing steps and add `expectUnloadPage` when needed runbot-939241 Forward-Port-Of: odoo/enterprise#127442