Thursday, August 27, 2026
55 changes · saas-19.3
Enhancements to existing features
This update makes internal mail test reporting more robust when tests run in parallel. It prevents unusual test data from crashing the test suite, helping maintain smoother quality checks without changing user-facing behavior.
Original PR description
If the value needs to be serialized for IPC (cough cough pytest-xdist) and a weirdo sets recordsets as message values, the serialization fails and the test suite crashes. Since this is just subtest identification it shouldn't be too much of an issue. Forward-Port-Of: odoo/odoo#284279 Forward-Port-Of: odoo/odoo#284178
The live chat settings now clarify that automatic chat popups only open on larger screens. This helps teams avoid confusion when testing on phones or small screens, where visitors will only see the chat button and must tap it manually.
Original PR description
The 'Open automatically' action only triggers the auto popup on larger screens (`ui.isSmall` is checked in `AutopopupService. allowAutoPopup`). On mobile/small viewports, only the chat button is shown and the visitor must tap it manually. The existing help text does not mention this, which could lead to confusion when the auto popup does not trigger during testing on mobile. Update the field's help text to explicitly state that automatic opening is limited to larger screens. opw-6459279 Forward-Port-Of: odoo/odoo#284785
Belgian accounting data now applies the non-deductible fiscal position to 0% taxes that were previously missing it. This improves consistency in tax configuration and helps businesses apply Belgian tax rules more accurately.
Original PR description
Adding non-deductible fiscal position to taxes that were missing it in the data. task-6389423 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#284504 Forward-Port-Of: odoo/odoo#278020
Belgian payroll calculations are updated to reflect 2026 rules for copyright royalty payments. These payments now include social security contributions and use a flat 15% withholding tax, helping employers stay compliant with the new legal treatment.
Original PR description
As of 2026, copyright (IP) royalties paid through the payroll are subject to ONSS and their withholding tax becomes a flat rate: - new rule "Intellectual Property - ONSS Part" (IP.PART.ONSS) computes the 13.07% ONSS on the IP part of the remuneration - the IP withholding tax is now a flat 15% (new rule parameter ip_tax_rate) applied on the IP part net of its ONSS part, replacing the 50%/25% cost-deduction brackets and the withholding cap (ip_deduction_bracket_1/2 are no longer referenced by the code)
Resolved issues and error corrections
This fix prevents current-year and period profit balances from being counted twice in Uzbekistan balance sheet equity reporting. It updates the affected account setup so the balances flow through the intended report line, improving accuracy of financial statements.
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/129354 task-6361059
Documentation and clarification updates
ERPVibe Limited has signed Odoo's Corporate Contributor License Agreement. This confirms the legal terms for accepting the company's contributions to Odoo, with no product or user-facing changes.
Original PR description
ERPVibe Limited signs the Odoo Corporate Contributor License Agreement v1.0. Forward-Port-Of: odoo/odoo#283477
This fix prevents delivery transfers from losing their configured destination location when a contact is selected. Businesses using custom customer stock locations can keep the intended operation setup, reducing manual corrections and shipping errors.
Original PR description
### Steps to reproduce: - In the settings: Enable Storage Locations - Create a customer location "Customer stock" with "Customers" as its parent location - Create a delivery operation type "Deliver…
### Steps to reproduce: - In the settings: Enable Storage Locations - Create a customer location "Customer stock" with "Customers" as its parent location - Create a delivery operation type "Deliver Super Customer" and set its default destination location to "Customer stock" - Go to Inventory > Overview > Deliver Super Customer > New - Set a contact on the transfer #### > The destination location switches from "Customer stock" to "Customers" ### Cause of the issue: The `location_dest_id` of `stock.picking` depends on its `partner_id`. So that changing the partner recomputes the locations of the transfer. However, as soon as the destination of the operation type has a `customer` usage, the `property_stock_customer` of the contact replaces it unconditionally: https://github.com/odoo/odoo/blob/04f3a7bca99d0144a4ea871be9625db368b196ca/addons/stock/models/stock_picking.py#L949-L963 However, the `property_stock_customer` falls back to an `ir.default` pointing at the default `Customers` location when nothing is set on the contact: https://github.com/odoo/odoo/blob/1c40fab04b71def8f3645c4c4bb0c1441057f307/addons/stock/data/stock_data.xml#L71-L72vs The override comes from 8a0775aa1dd9, which replaced an `elif` fallback on the contact by an "unconditional" substitution as this fallback had become unreachable once `default_location_src_id` and `default_location_dest_id` were made required: https://github.com/odoo/odoo/blob/04f3a7bca99d0144a4ea871be9625db368b196ca/addons/stock/models/stock_picking.py#L34-L41 opw-6421090 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#283886 Forward-Port-Of: odoo/odoo#280016
The Point of Sale refund screen now ignores barcode scanner input when users are entering refund quantities. This prevents scanned product codes from being mistaken for refund amounts, avoiding incorrect refund quantities and confusing error messages.
Original PR description
Steps to reproduce: - Have a paid order with a product ordered once - Open the ticket screen, select that order and its line - Scan a product barcode with a keyboard-wedge scanner Issue: "Maximum…
Steps to reproduce: - Have a paid order with a product ordered once - Open the ticket screen, select that order and its line - Scan a product barcode with a keyboard-wedge scanner Issue: "Maximum Exceeded - The requested quantity to be refunded is higher than the ordered quantity. 6 is requested while only 1 can be refunded." When the line holds enough quantity no dialog is shown at all and a refund quantity taken from the barcode is silently set. Cause: A keyboard-wedge scanner types the barcode as a burst of keystrokes. The number buffer discards such bursts by waiting barcodeService.maxTimeBetweenKeysInMs before handling the keys it collected and dropping any batch of more than two, but only when its holder asks for it with `useWithBarcode`. TicketScreen never set the flag, so its buffer handled every keystroke on its own and the digits of the barcode reached _setToRefundDetail as the refund quantity. ProductScreen, OrderSummary and PaymentScreen all set it. Fix: Set `useWithBarcode: true` on the ticket screen number buffer. Since the keys are now handled with a delay, capture the buffer before the selected order or orderline changes, so that a keystroke is applied to the line that was selected when it was typed and not to the next one. opw-6465148 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#282610 Forward-Port-Of: odoo/odoo#281962
The Uzbek balance sheet now includes unclosed profit or loss from the current year in the Equity section. This helps ensure the report remains balanced and accurate both before and after year-end closing, without changing the official report numbering.
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/284697 task-6361059
Belgian payroll now correctly applies an employee's fuel card private-use setting to eligible payslips. This ensures the benefit is handled as taxable income for social security and withholding purposes, improving payroll accuracy and compliance.
Original PR description
FUEL_CARD_PRIV never fired: nothing copied the employee's fuel_card_personal_use default into the payslip's property input. Fixed by seeding it in _compute_input_line_ids(), gated on fuel_card set, no company car, no mobility budget. FUEL_CARD_PRIV is also a Benefit in Kind (ONSS + withholding), like ATN.INT, but was missing the BIK category. Added it. Task 6469003
When reviewing accounting report figures, users can now switch from the journal item list to other available views such as pivot, graph, and kanban. This makes it easier to analyze report details in the format best suited to the task.
Original PR description
Problem: When auditing reports, the audit cell action was only showing the journal items in the list view, and not enabling other view modes (pivot, graph, kanban). Steps to reproduce: 1. Go to Accounting > Reporting > Balance Sheet 2. Click on any cell with a number in the report 3. Notice how the journal items are only shown in the list view, and you cannot switch to other view modes. Cause: The action was hardcoded to only show the list view. opw-6403704 Forward-Port-Of: odoo/enterprise#129214 Forward-Port-Of: odoo/enterprise#128563
Users editing product translations no longer lose unsaved text when they drag or reposition the translation window. This prevents accidental rework and makes multilingual data entry more reliable.
Original PR description
Step to reproduce: - have atleast two language and install sale - open any product, hover over product, and click on Translation button - Enter a value for one of language - drag the dialog Observation: - we lose the data, we just entered and fallback to original data Cause: - Inputs used `t-att-value="term.value"`, bound to original data. Since this content is passed to Dialog via slot, it is rendered/patched as part of Dialog's render cycle, - Dragging updates Dialog's state, triggering a patch that re-evaluated the slotted template and reset input values (which comes from `term.value`) Fix: - bind value to `updatedTerms[term.id] ?? term.value` so edits survive patches triggered by the parent Dialog opw-6431521 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#283514
Fixes an issue in the website editor where changing a button's text color could accidentally remove its gradient background. This helps users customize website buttons reliably without losing previously selected styling.
Original PR description
Steps to Reproduce : 1. Go to Website → Edit Mode 2. Add a snippet with button 3. Click on button and change its type to : " Custom" 4. Apply the gradient type color in fill color option 5. Apply any…
Steps to Reproduce : 1. Go to Website → Edit Mode 2. Add a snippet with button 3. Click on button and change its type to : " Custom" 4. Apply the gradient type color in fill color option 5. Apply any color in text color option 6. You will notice that the gradient type color in fill color option is removed. Problem: Since [this commit][1] new button style options have been added to the sidebar. If one changes the style of a button to custom, changes the background to gradient, and tries to change the text color, the background gradient is removed. Cause: Whenever a gradient is added either to text or as a background, it is applied as a background image. In the case of a text gradient, an additional class, `text-gradient`, is applied for correct styling. Once a change to either color or background/fill is applied that is not a gradient color change, the background image of the element would be reset to nothing. This is the result of [this line][2] from a [previous commit][3]. In the case of a button, this meant changing the font color would reset the background. That is not the desired outcome. The flaw was only discovered once new options were added to change non-text/font elements' backgrounds to gradient. Solution: An additional check has been added to see if the element being edited is text. If so, and it's a gradient style being changed, we remove the background image. Otherwise it is kept so that the button case from above is resolved. [1]: https://github.com/odoo/odoo/commit/2bf1db001b195480b963b338583c170aa1c009a3 [2]: https://github.com/odoo/odoo/blob/8e0845712f462ecfafd2176406dcbafc869a5564/addons/html_editor/static/src/main/font/color_plugin.js#L575 [3]: https://github.com/odoo/odoo/commit/8e0845712f462ecfafd2176406dcbafc869a5564 task-6247134 Forward-Port-Of: odoo/odoo#279070
This fix prevents reporting and list views from attempting unsupported grouping or sorting on records that require complex document permission checks. It avoids errors for users working with attachments and product documents, making those views behave more reliably.
Original PR description
(see commits) https://github.com/odoo/enterprise/pull/129120 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
This fix ensures project costs are matched to the right sales order even when projects share the same analytic account or when a cost is split across multiple analytic accounts. Businesses can now rely on reinvoiceable vendor bills and expenses being added to the appropriate customer order instead of being missed.
Original PR description
### Before this fix --- The `_get_so_mapping_from_project()` method returns a mapping where the key is the move line ID and the value is a `sale.order` record (or `None`). Because of the issues…
### Before this fix
---
The `_get_so_mapping_from_project()` method returns a mapping where the key is
the move line ID and the value is a `sale.order` record (or `None`).
Because of the issues described below, a valid `sale.order` could be available
for reinvoicing, but the corresponding move line might still not be mapped to
that sale order. As a result, the move line is not added to the reinvoiceable
sale order.
However, the implementation has two issues:
#### 1. Projects are overwritten when they share the same analytic account
`project_per_accounts` is built as a dictionary mapping an analytic account ID
to a single project. If multiple projects reference the same analytic account,
each new assignment replaces the previous one. As a result, only the last
project associated with a given analytic account is retained.
**Example:**
* Analytic Account **AA1** is linked to **Project A** and **Project B**.
* The dictionary becomes `{AA1: Project B}`.
* **Project A** is lost, even though it also references **AA1**.
**Steps to reproduce:**
1. Create an analytic account **AA1**.
2. Create **Project A** and **Project B**, both linked to **AA1**.
3. Create **Sale Order SO1** linked only to **Project A**.
4. Create a vendor bill (or expense) that generates an AML using **AA1** for a
product configured with **Reinvoice Costs = At Sales Price**.
5. Validate the document.
**Expected behavior:**
The product should be added to **SO1** for reinvoicing.
**Actual behavior:**
The move line is not mapped to **SO1**, so no sale order line is created.
#### 2. Previously found projects are overwritten during iteration
The `project` variable is reassigned on every iteration of the loop. After the
loop completes, it only contains the project (or lack of one) corresponding to
the last processed analytic account. This can cause valid projects found earlier
in the loop to be discarded.
**Example:**
* Move line has analytic accounts **AA1** and **AA2**.
* **AA1** maps to **Project A**.
* **AA2** has no linked project.
* After the loop, `project` is `None`, even though **Project A** was found.
**Steps to reproduce:**
1. Create analytic accounts **AA1** and **AA2**.
2. Create **Project A** linked to **AA1** only.
3. Create **Sale Order SO1** linked to **Project A**.
4. Create a vendor bill (or expense) whose AML is distributed between **AA1**
and **AA2**, where **AA2** is processed after **AA1**.
5. Validate the document.
**Expected behavior:**
The move line should still be mapped to **SO1** because **AA1** references
**Project A**.
**Actual behavior:**
The last processed analytic account (**AA2**) overwrites the previously found
project, causing the move line not to be linked to **SO1**.
### After this fix
---
* `project_per_accounts` stores **all** projects associated with each analytic
account instead of keeping only the last one.
* The project lookup preserves all valid project candidates instead of
overwriting previously found results during iteration.
* As a result, the method can resolve the related `sale.order` in more cases,
improving the overall accuracy of the mapping.
> **Note:** This change prevents valid project associations from being lost
> when multiple projects share an analytic account or when multiple analytic
> accounts are processed for the same move line.
**OPW:** 6294615
Forward-Port-Of: odoo/odoo#277110A test for restaurant appointment scheduling now waits until a cancellation dialog fully closes before finishing. This prevents occasional false failures in automated checks, helping keep releases stable without changing user-facing behavior.
Original PR description
Following commit b6a7991a6aaef1be15a27852c2c76737322cdcab, the tour clicks the cancel button (.o_form_button_cancel) to discard unsaved changes. However, as it was the last step of the tour, the test could terminate before the asynchronous discard/dialog closure completed. This caused intermittent test failures with: `AssertionError: Tour finished with a dirty form view being open.` We now add a final step that waits for the dialog to close and the form to no longer be marked as dirty (`body:not(:has(.o_dialog)):not(:has(.o_form_dirty))`) before completing the tour.
The AI attachment cleanup process now checks permissions only for the specific attachments being processed, rather than scanning all attachments first. This avoids unnecessary work and helps cleanup run more reliably in databases with access rules.
Original PR description
When marking attachments, check access only to given ids explicitly instead of searching for all records. Given access rules, the search without a sudo would try to find all attachments and filter them. https://github.com/odoo/odoo/pull/284394 task-6488506
The Turkish reports journal form now places the return from sales account field in the correct position. This prevents labels and values from appearing under the wrong captions, reducing confusion when configuring journals.
Original PR description
The journal form renders `default_account_id` as six standalone labels followed by two `nolabel="1"` fields, one for bank, cash and credit journals and one for sale, purchase and general ones. The xpath matched the first of those two fields, so the return from sales account was inserted between them. Its own label then landed in the middle of the label run, shifting the group grid: both labels rendered side by side with their values underneath, each next to the wrong caption. Anchor on the second field instead, so the new field follows the whole label and field run. Task-6438412 Forward-Port-Of: odoo/enterprise#128083
Kenyan POS refunds now send the original sale's KRA invoice number to eTIMS instead of the refund's own order number. This prevents valid refunds from being rejected by eTIMS because they were matched against the wrong sale.
Original PR description
Steps to reproduce: - Set up a company in Kenya with eTIMS. - Sell an order from the POS and send it to eTIMS. - Refund that order and send the refund to eTIMS. Cause of the issue: When we build the…
Steps to reproduce: - Set up a company in Kenya with eTIMS. - Sell an order from the POS and send it to eTIMS. - Refund that order and send the refund to eTIMS. Cause of the issue: When we build the JSON for eTIMS, the "orgInvcNo" field (the KRA invoice number of the order we are refunding) was always set to `self.sequence_number`, which is just the order's own number in its session. This is wrong for a refund: eTIMS then can't find the item on "the original invoice" (since it's looking at the wrong invoice), and also can't check the amounts, since it's comparing them to the wrong order. eTIMS then rejects the refund with a 910 error, like "item sequence ... does not exist on the original invoice" or "amount is incorrect for item ...". The invoice-based flow (account_move.py) already does this the right way, using `reversed_entry_id.l10n_ke_oscu_invoice_number`, but the POS order flow was not doing the same thing. Solution: For a refund order, use the KRA invoice number of the refunded order (`refunded_order_id.l10n_ke_oscu_order_number`) instead of the refund's own sequence number. Normal sales keep working like before. opw-6445174 Forward-Port-Of: odoo/enterprise#128387
This fixes an issue where reports or data views using repeated grouping criteria could return incorrectly shaped or duplicated results. The change prevents related crashes and makes grouped data output consistent for callers that rely on these results.
Original PR description
`_read_grouping_sets` dispatches each SQL result row to the grouping set(s) that requested it, using the `GROUPING()` bitmask as the key. When a grouping set repeats a groupby spec, e.g. `['foo',…
`_read_grouping_sets` dispatches each SQL result row to the grouping set(s) that requested it, using the `GROUPING()` bitmask as the key.
When a grouping set repeats a groupby spec, e.g. `['foo', 'foo']`, it computes the exact same bitmask as the grouping set for the deduplicated column alone (`['foo']`).
Two grouping sets sharing a bitmask were treated as interchangeable duplicates: only the first one seen got its extractor registered, and its already-extracted result list was later blindly copied onto every other grouping set sharing that bitmask.
This is wrong whenever those sets don't actually share the same shape:
`_read_grouping_sets(grouping_sets=[['foo'], ['foo', 'foo']])`
returned
`[('my_foo_val', <aggregate>), ('my_foo_val', <aggregate>)]`
instead of
`[('my_foo_val', <aggregate>), ('my_foo_val', 'my_foo_val', <aggregate>)]` (repeating the value, as `_read_group` does).
Callers unpacking crashed with `ValueError: not enough values to unpack`.
Fix:
- Deduplicate the SQL terms of each grouping set before building its `GROUPING SETS (...)`, so that grouping sets which are physically the same always produce a single row from PostgreSQL.
- Register every grouping set's own extractor under its bitmask in a list, instead of keeping only the first one seen, and dispatch each result row to all of them.
task-6511626
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#284683Swedish ISO20022 payment batches now generate files that correctly match the selected pain.001.001.09 format. This prevents banks from rejecting vendor payment files due to mismatched or non-compliant XML content.
Original PR description
**Steps to reproduce:** - Install Accounting and l10n_se - Switch to a Swedish company (e.g. SE Company) - In Accounting settings, set "Identification" with anything - In Bank journal: * Set an…
**Steps to reproduce:** - Install Accounting and l10n_se - Switch to a Swedish company (e.g. SE Company) - In Accounting settings, set "Identification" with anything - In Bank journal: * Set an account number * Make sure "Swedish ISO20022" is available in "Outgoing Payments" * Set "pain.001.001.09" as "XML Format" in "Outgoing Payments" - Create a vendor payment: * Vendor: [a vendor with a trusted bank account] * Payment Method: Swedish ISO20022 * Amount: [any] - Confirm the payment - From the payments list, select the payment and create a batch - Validate the batch payment **Issue:** When the batch is validated, a `pain.001.001.09` file should be generated. However, its content is that of a `pain.001.001.03` file, even if the version reported in the file is `pain.001.001.09`. For example, `<ReqdExctnDt>` should contains a subnode `<Dt>` in `001.001.09`, which is not the case. It leads to the file being rejected as non-compliant to `pain.001.001.09`. opw-6472050 Forward-Port-Of: odoo/enterprise#129234 Forward-Port-Of: odoo/enterprise#128598
This fixes a barcode manufacturing test flow where a scrap quantity could be lost while the product selection was still updating. The change helps ensure scrap operations are validated with the intended quantity instead of incorrectly failing as zero.
Original PR description
Selecting the product in the scrap form triggers a `stock.move` onchange. The quantity step only waited for the input to exist, not for that onchange to be applied, so the value could be written while it was still in flight and be reset to 0 by its response. It was also assigned directly on the input, without any event, so the field was never flagged as dirty. The scrap was then recorded with a quantity of 0 and `action_scrap` rejected it with "You can only enter positive quantities.". Wait for the quantity input to hold its post-onchange value before typing, and dispatch an input event, like the other scrap tours already do. error-238911 Forward-Port-Of: odoo/enterprise#128931
New onsite learning events created from the Onsite view or an employee resume are now automatically linked to the current employee and remain visible after creation. This prevents users from losing track of newly created training events because of overly strict filtering rules.
Original PR description
Onsite events created from the "Onsite" view or the employee resume selector do not appear immediatlely after creation This occurs because currently the domain for onsite events requires that the…
Onsite events created from the "Onsite" view or the employee resume selector do not appear immediatlely after creation This occurs because currently the domain for onsite events requires that the event to have multiple slots as well as to have at least one employee registered to it. Therefore, newly created records often fail these criteria and remain hidden. In further versions, this pr: https://github.com/odoo/odoo/pull/246285/ changes the domain of the event selector in the employee resume by removing the dependency on the multiple slots and filtering by the specific employee for registration. This change is not stable to backport as it indroduces the `employee_id` field as an invisible field in the xml to be able to compare in the domain. This commit partly changes both domains to not require the multiple slots anymore, while still showing all events for which an employee is registered. This commit also ensures that when an event is created from the Onsite view or selector, the current user's employee will be registered to it. Steps to reproduce - Go to employees->Learning->Onsite - Select New and create an event - Go back to Onsite Courses - You will not see the created event (unless it is multi_slot and an employee was registered) opw-5915686 Forward-Port-Of: odoo/odoo#283719 Forward-Port-Of: odoo/odoo#258952
The mail meeting flow now closes the camera permission dialog before entering a guest name. This prevents automated checks from failing when the dialog appears unpredictably, helping keep the public meeting experience stable.
Original PR description
Before this commit, discuss.meeting_view_public_tour failed on runbot at the step typing the guest name: "It is not allowed to do action on an element that's below a modal." This happens because the welcome page opens the camera permission dialog as soon as navigator.permissions.query answers "prompt", and that answer comes at no fixed moment. The tour types the name first and closes the dialog only on the next step, so a dialog already open covers the input. This commit closes the dialog before typing the name. https://runbot.odoo.com/odoo/error/946301 Forward-Port-Of: odoo/odoo#284491
Repeat website orders now use the customer's saved delivery and billing addresses when selecting the default fiscal position. This prevents orders from applying tax rules from the customer's main profile country when the delivery country is different, improving billing accuracy.
Original PR description
Issue: --- Fiscal position is wrongly set to `self.env.user.partner_id.country_id` instead of `partner_shipping` country, if `partner_shipping_id` is not changed in the checkout process. Steps: 1-…
Issue: --- Fiscal position is wrongly set to `self.env.user.partner_id.country_id` instead of `partner_shipping` country, if `partner_shipping_id` is not changed in the checkout process. Steps: 1- Create two auto detect fiscal positions: France, Germany 2- Set portal user's partner address country to France. 3- Using portal user, shop from website, and create a delivery address. 4- Pay and confirm the order. 5- Using the admin user, you check the SO's FP which is correctly set to Germany. 6- Using portal user, again shop from website, and don't change address. Keep previous shipping address which is Germany. 7- Confirm and pay the order. 8- Using admin user, check the new SO's FP. It's set to France. Cause: --- `_compute_fiscal_position_id` in SO depends on `partner_shipping_id`. When the `partner_shipping_id` is not changed, the fiscal position value set in create will remain. This value is set in `Website._prepare_sale_order_values()`. The `fiscal_position_id` is set to self.fiscal_position_id, which is `_get_fiscal_position(self.env.user.partner_id)`. Fix: --- If the user has already a SO, we can use last SO's shipping address and invoice address to calculate FP in `_prepare_sale_order_values`. opw-6357638 Forward-Port-Of: odoo/odoo#279716 Forward-Port-Of: odoo/odoo#276485
Attendance officer roles now automatically include standard internal user access. This keeps attendance permissions consistent across supported versions and helps prevent access issues for staff managing attendance.
Original PR description
In [this forward port in 19.4](https://github.com/odoo/odoo/pull/281713/changes#diff-4b6f4473332f5e30b7d83acb51946ffa4731af0093c75954f619906010c3f8a8R28), I have changed the `implied_ids` of `group_hr_attendance_officer` as well. This PR reflects the change on other stable versions. The change should not break permissions, as an attendance officer should be a user, and `base.group_user` implies the group `hr_attendance.group_hr_attendance_own_reader` task-6499161 Forward-Port-Of: odoo/odoo#284191
UAE companies can now create a salaries bank account directly from Payroll Settings. This fixes a setup blocker that could prevent completing payroll configuration and the UAE WPS salary payment process.
Original PR description
Issue: UAE companies cannot create their salaries bank account directly from Payroll Settings. The bank account cannot be saved, leaving payroll configuration incomplete and preventing the UAE WPS…
Issue: UAE companies cannot create their salaries bank account directly from Payroll Settings. The bank account cannot be saved, leaving payroll configuration incomplete and preventing the UAE WPS process from being completed. Steps to reproduce: * Configure an Emirati company with the UAE Payroll localization. * Open Payroll > Configuration > Settings. * Create a new Salaries Bank Account from the settings field. * Fill in the bank details and try to save the account. Cause: Since saas-19.2, the bank account form hides the required account holder and expects the opening field to provide it through `default_partner_id`. The UAE salaries bank account setting only restricts selectable accounts through its domain and does not provide that creation default. Newly created accounts therefore have no owner and cannot be saved. Domains only filter selectable records and do not initialize fields on new records. Since the shared bank account form hides the required partner, accounts created from Payroll Settings have no owner and cannot be saved. Solution: We need to provide the current company partner as the account creation default while retaining the existing selection domain. This preserves the company and country restrictions and guarantees that newly created salaries accounts satisfy the required ownership invariant. opw-6441848 Forward-Port-Of: odoo/enterprise#127777
This update corrects several SAF-T/FAIA reporting issues for Luxembourg and related SAF-T outputs, including tax amount signs, software version length, foreign-currency tax amounts, and mutually exclusive customer/supplier invoice details. These fixes help businesses generate audit files that better match official validation rules and reduce the risk of rejected or questioned statutory reports.
Original PR description
This is one of many errors fixing the FAIA report, which is the SAF-T report for Luxembourg. See PR #113316 for a list of similar PRs. ### Error 1: Negative tax amounts Auditors from Luxemborg…
This is one of many errors fixing the FAIA report, which is the SAF-T report for Luxembourg. See PR #113316 for a list of similar PRs. ### Error 1: Negative tax amounts Auditors from Luxemborg provided one Odoo user with analysis files of their FAIA xml report. The following discrepancy was present in more than 300 lines: `[TaxInformation/TaxAmount/Amount] # is negative. Only postive values are admitted. The sign is automatically determined by the corresponding CreditAmount (-) Or DebitAmount (+) on the same Line.` This discrepancy was caused by two different scenarios. The first was a negative `unit_price` line, such as a Discount product. The second was a tax with negative and positive repartition lines, such as a tax with xml ID `lu_2015_tax_AP-EC-17`. Luxembourg officials confirmed the following behavior: 1. The TaxInformation/TaxAmount/Amount element must be positive. 2. The TaxInformationTotals/TaxAmount/Amount element may be negative. 3. There may only be one TaxInformationTotals element per TaxCode in an Invoice element. This commit ensures that these conditions are met for the FAIA report. I'm not sure if the TaxInformation changes should also be applied to the base `account_saft saft_report.xml` file. ### Error 2: SoftwareVersion The SoftwareVersion element is limited to 18 characters. The relevant error from a customer's analysis file is below. Error: Value exceeds maxLength of "18". ### Error 3: CurrencyAmount The `account_saft` method `GeneralLedgerCustomHandler._saft_fill_report_tax_details_values()` does not report the amount of tax in foreign currency, instead replacing this value with the amount in company currency. No errors prompted this change; it just seems wrong on its face. ### Error 4: PR #113720 ensured that the TaxType element is always TVA. This means that the TaxType should no longer should be ignored in our example documents. ### Error 5: Schema validation failure The elements Inovice/CustomerInfo and Invoice/SupplierInfo are defined with the element `<xs:choice>` in the XSD file linked below. Only one can be present at any time, not both. https://pfi.public.lu/dam-assets/backup/FAIA/FAIA/XSD_Files.zip. note: currently the link is broken. PR #100749 allowed many parts of SAF-T code to display both customer and supplier data, including these elements. This commit ensures that the elements are mutually exclusive. opw-6344914 [Link](https://www.odoo.com/odoo/project.task/6344914) Forward-Port-Of: odoo/enterprise#129043 Forward-Port-Of: odoo/enterprise#126121
French VAT electronic filings now automatically split long account holder names into two parts when required. This prevents submissions from being rejected because the holder name exceeds the official XML format limit.
Original PR description
The XSD for XML-EDI does not allow strings longer than 35 for TitulaireDesignation This commit splits the holder name in 2 parts when it is more than 35 characters task-6476440 Forward-Port-Of: odoo/enterprise#129225 Forward-Port-Of: odoo/enterprise#128239
French VAT filings now only include a direct debit payment instruction when VAT is actually owed. This prevents refund claims from being rejected by the French tax authority because an inappropriate payment block was sent.
Original PR description
`_prepare_edi_vals` always called `_get_formatted_payment_values()`, adding an EDI-Paiement (telereglement) block to the T-IDENTIF of the 3310CA3, regardless of whether the company owes VAT or is in…
`_prepare_edi_vals` always called `_get_formatted_payment_values()`, adding an EDI-Paiement (telereglement) block to the T-IDENTIF of the 3310CA3, regardless of whether the company owes VAT or is in a credit position. Steps to reproduce: - French company in a VAT credit position, requesting a refund. - Fill a bank account line, the account to receive the refund and send the VAT report to the DGFiP. Current behaviour: The DGFiP returns a negative acknowledgement on the CA3 interchange: "Telereglement 1 rejete: Montant telereglement absent ou invalide. Code erreur : 018", even though the declaration itself is accepted. The wizard's bank account lines are reused for two opposite purposes: the account to debit when VAT is due, and the account to credit when a refund is asked. `_get_formatted_payment_values()` builds a payment order from them unconditionally, so a telereglement for the credit amount is emitted in the refund case. A telereglement is invalid when no VAT is due, hence error 018. A return nets to either a payment or a credit, never both, so the two cases are mutually exclusive. This commit guards the call with `self.is_vat_due`, so the telereglement is only generated when the company actually owes VAT. The VAT-due flow is unchanged. opw-6275695 Forward-Port-Of: odoo/enterprise#124694 Forward-Port-Of: odoo/enterprise#120840
Belgian Acerta payroll exports now include weekend days when an employee's qualifying leave overlaps a weekend. This prevents missing leave information in reports and helps ensure exports match Acerta's expected reporting rules.
Original PR description
## Steps to reproduce: - Install l10n_be_hr_payroll_acerta - Create an employee in a belgian company - Create a sick time off for the created employee that overlaps with a weekend - Export acerta report for the employee - Notice the weekend that overlaps with the time off is not present in the report ## Cause: While exporting the report file we only loop over the created work entries' dates and since weekends doesn't have work entries we don't consider them in the report. ## Fix: When generating the line of a leave's start date we check if the leave overlaps with a WE, we fetch the WE's date and we generate a line for each day of the WE. According to Acerta this is the correct behavior for their reports for specific types of leaves. **opw-6313534** Forward-Port-Of: odoo/enterprise#128939 Forward-Port-Of: odoo/enterprise#124500
Point of Sale receipts once again show customer notes added to the overall order, not just notes attached to individual products. This helps staff and customers see important order instructions on the printed ticket as expected.
Original PR description
## Steps to reproduce: - Go to the pos, click a product - Click the product again to unselect the line - Go to the 3 dots -> customer note - Enter a customer note, pay for the order - Try and print the receipt -> The customer note is not displayed ## Why the fix: Since the receipt REF, the general customer note was not displayed on the ticket anymore, but in was in earlier versions. When making a customer note without a selected line, we make a general one, which is not attached to a product line, so it was never displayed. We now display the general customer note if it exists, after having displayed all lines, as we did before 19.2. opw-6483621 Forward-Port-Of: odoo/odoo#284456
The mail interface styling was simplified by removing an overly complex visual selector that added processing cost without meaningful user benefit. This should help keep the interface lighter and more efficient while preserving the expected look and feel.
Original PR description
This PR cleans up a complex selector that is quite costly without providing any striking visual value. task-6481656 Forward-Port-Of: odoo/enterprise#128906
This fixes an issue where emailing multiple job applicants could incorrectly link them all to the same newly created contact if one applicant was missing contact details. Each applicant now keeps the correct contact and email information, helping recruiters avoid accidental data mix-ups.
Original PR description
**Problem:** When sending an Email to multiple Applicants when one Applicant does not have a `partner_id` set, all Applicants set to receive the email will copy the `email_from` of the Applicant…
**Problem:** When sending an Email to multiple Applicants when one Applicant does not have a `partner_id` set, all Applicants set to receive the email will copy the `email_from` of the Applicant without a `partner_id`. **Cause:** When sending an Email to an Applicant record that does not have a `partner_id` set on it, a new Contact is created to be set to the Applicant. When this Contact is created, its `applicant_ids` value defaults to the Applicants in the context. This causes all Applicants in the current context (i.e. the recipients of the Email) to be set on the Contact, also causing that Contact to be set as the `partner_id` of all the Applicants, rather than just the one missing a `partner_id`. This then causes the `email_from` of the Contact to be set on all the Applicants. https://github.com/odoo/odoo/blob/ccd167e23ea80545e4f09e2302c1b2489e8c42d8/addons/hr_recruitment/wizard/applicant_send_mail.py#L40-L42 https://github.com/odoo/odoo/blob/ccd167e23ea80545e4f09e2302c1b2489e8c42d8/odoo/orm/models.py#L1292-L1293 **Purpose:** Modify the `create` call used to create the new Contact to define `applicant_ids` as only the Applicant that the Contact is being created for. **Steps to Reproduce in Runbot:** 1. Delete the Contact corresponding to the `partner_id` of any given Applicant. 2. Select multiple Applicants, including the one whose `partner_id` was deleted, then send an email to them using the Actions menu. opw-6383263 Forward-Port-Of: odoo/odoo#284323 Forward-Port-Of: odoo/odoo#283271
Deleting an active project no longer incorrectly moves document folders from archived projects to the trash. This prevents teams from losing access to documents that still belong to existing archived projects.
Original PR description
Deleting a project also sends the folders of every archived project to the trash. ### Steps to reproduce - Install `documents_project`, where each project has its own Documents folder linked through…
Deleting a project also sends the folders of every archived project to the trash.
### Steps to reproduce
- Install `documents_project`, where each project has its own Documents folder linked through `project.project.documents_folder_id`.
- Create `Project 1`, `Project 2`, and `Project 3`, then archive the first two.
- Delete `Project 3`.
- The folders of `Project 1` and `Project 2` are moved to the trash with their contents, although both projects still exist and still reference them.
### Cause
`_archive_folder_on_projects_unlinked` only archives folders that are no longer used by any project. This was checked through a `documents.document` domain on `project_ids`.
The domain mixed two conditions on the same relation:
- `('project_ids', '!=', False)` checks that a folder has users,
- `('project_ids', 'not any', [('id', 'not in', self.ids)])` checks that it has no users outside the projects being deleted.
Those conditions are not evaluated the same way by the ORM. The first one keeps archived projects visible by disabling `active_test` internally, while the second one searches `project.project` normally and hides archived projects.
An archived project can therefore be counted as a folder user by one condition and ignored by the other, causing its folder to be archived.
### Fix
Check remaining users directly on `project.project` with `active_test=False`, so archived projects are included. Since only folders of deleted projects can become unused, the search starts from those folders instead of scanning all Documents.
opw-6442976
Forward-Port-Of: odoo/enterprise#127217The Colombian DIAN integration now handles missing or invalid document attachments when building the commercial event history. This prevents vendor bill acknowledgement actions from crashing when earlier DIAN responses contain corrupted or plain XML attachments.
Original PR description
When building the commercial event history for DIAN documents, the system crashes if an intermediate document's attachment is missing or is not a valid ZIP file. Steps to reproduce: - Create a vendor bill and send it to DIAN to generate a commercial event document. - Manually alter or corrupt the attachment of the first response document (e.g., save plain XML instead of a ZIP). - Click "Acusar Recibo" (Acknowledge Reception) on the bill. Issue: The system crashes when attempting to unzip a malformed, plaintext, or missing attachment while iterating through past documents to build the event history. Analysis: The system aggregates the XML of previous events to maintain the history trail. Doing so, the system assumes that all past attachments are ZIP archives. However, interacting with the DIAN API can occasionally result in plaintext XML, that raises `zipfile.BadZipFile` when unzipping. opw-5467690 Forward-Port-Of: odoo/enterprise#121494
Invoice scanning now matches bank account numbers using a cleaned IBAN format, avoiding mismatches caused by spaces, dots, or dashes in saved partner bank details. This helps invoices link to the correct partner bank account more reliably when using OCR extraction.
Original PR description
When looking for a matching IBAN, we were searching on the `acc_number` field, which can contain spaces or special characters (dots, dashes, etc). But the OCR always returns the IBAN in a sanitized format, without any space or special characters, so it should be compared against the sanitized IBAN of the partners. task-none (issue found by chance) Forward-Port-Of: odoo/enterprise#128264 Forward-Port-Of: odoo/enterprise#127775
This fixes an issue in Odoo Studio where non-Latin characters, such as Chinese or Arabic text, could appear as encoded codes when editing combined views in the XML editor. Users working in multiple languages will now see the intended characters, reducing confusion and editing errors.
Original PR description
Currently, when we combine the arch for the base views in the xml editor, we encode the text using the etree default of us-ascii. This causes special characters (chinese, arabic, etc.) to be converted to html codes. To rectify this issue, we set the encoding of the string to unicode. ### Before <img width="739" height="334" alt="before" src="https://github.com/user-attachments/assets/ad77fc47-08a0-42ed-b34b-d033779e9fc2" /> ### After <img width="673" height="334" alt="after" src="https://github.com/user-attachments/assets/1dae36c6-46d1-4189-b448-790aee18f319" /> opw-6325841 Forward-Port-Of: odoo/enterprise#128336
The Nilvera PDF button now appears only where relevant for Turkish companies, reducing confusion for other users. Invoicing users without administrator rights can now send, fetch, and retrieve Nilvera e-invoice PDFs without access errors, and manual PDF fetching refreshes pending invoice statuses sooner.
Original PR description
## Note on this PR My previous PR in this chain (odoo/odoo#284334, base `saas-19.2`) was mistakenly capped with `@robodoo up to saas-19.2`, so the forward port stopped there instead of continuing.…
## Note on this PR
My previous PR in this chain (odoo/odoo#284334, base `saas-19.2`) was mistakenly capped with
`@robodoo up to saas-19.2`, so the forward port stopped there instead of continuing. This PR is
technically a forward port: it cherry-picks the merged `saas-19.2` commit of #284334 (itself a
forward port of #284003 and of the original #274312 on 19.0) onto `saas-19.3`.
## Description of the issue/feature this PR addresses:
Two related Nilvera e-invoice issues affecting Turkish companies:
- The "Fetch Nilvera PDF" button appeared for every company, not just Turkish ones.
- Sending/fetching e-invoices (or fetching the PDF) as a non-admin Invoicing user raised an
AccessError, because the company's Nilvera API key field is restricted to System/Settings users
and several call sites read it without `.sudo()`.
## Current behavior before PR:
- The PDF-fetch button shows on both the list view and form view of `account.move` regardless of
the company's country.
- An Invoicing-only user (no System/Settings access) gets an AccessError when sending/fetching
e-invoices or fetching the PDF, because `_get_nilvera_client` reads
`company.l10n_tr_nilvera_api_key` without `.sudo()`.
- Clicking the button on an invoice still on `unknown` does nothing at all, since those invoices
are filtered out before the status refresh.
## Desired behavior after PR is merged:
- Both buttons are only visible for Turkish companies (the list-view button has no bound record, so
its gate is driven by a new `l10n_tr` session_info/JS context injection instead of a direct field
reference).
- Invoicing users can send/fetch e-invoices and fetch the PDF without hitting an AccessError.
- Invoices on `unknown` get their status refreshed on click, so one that Nilvera has already
resolved is picked up without waiting for the next cron run.
## Things to add on forward-port
The `.sudo()` fix lives inside `_get_nilvera_client` itself (`l10n_tr_nilvera/lib/nilvera_client.py`),
so every caller that goes through it is already fixed automatically once this diff forward-ports.
Only call sites that read `company.l10n_tr_nilvera_api_key` **directly**, bypassing
`_get_nilvera_client`, still need their own `.sudo()`:
### 19.4
- [ ] Fix e-Dispatch/e-Receipt fetch gate-check access error (direct read of the API-key field,
sudo it the same way as `_l10n_tr_nilvera_company_get_documents` in this PR)
task-6328589
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-prThis fix keeps the Point of Sale ticket screen working when a paid order has a related return that was cancelled but not deleted. Staff can reopen the original sale normally, avoiding interruptions during customer service and order review.
Original PR description
When a return order is created from the front end and later cancelled without being deleted from the backend, opening the original order from the POS ticket screen causes the UI to crash. The issue…
When a return order is created from the front end and later cancelled without being deleted from the backend, opening the original order from the POS ticket screen causes the UI to crash. The issue occurs because the refundedQty getter in `addons/point_of_sale/static/src/app/models/pos_order_line.js` assumes that every refunded order line has a valid order_id. For cancelled return orders, line.order_id is no longer available, resulting in the following runtime error: `TypeError: can't access property 'state', line.order_id is undefined` As a result, selecting the original paid order from the Ticket Screen breaks the POS interface. **Steps to Reproduce** 1. Create a normal sale in the POS. 2. Keep the POS session open. 3. In the backend, open the POS order and click Return Products. 4. Cancel the generated refund order (do not delete it). 5. In the POS, navigate to Ticket Screen → Orders → Paid Orders. 6. Select the original order. Desired behavior after PR is merged: The original order should open normally without any errors, even if a related refund order has been cancelled. Issue Ticket: https://github.com/odoo/odoo/issues/260079 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#279942
Odoo now waits until the IoT system is ready before starting connected display screens. This prevents customers from seeing an initial error page and helps ensure the browser opens in fullscreen as expected.
Original PR description
Before this commit, the display driver (and therefore browser) were being started too early, causing the following issues: - The browser initially displays an error page, as it tries to load the status page before Odoo has finished starting. - The browser window is not fullscreen. This may be because it is started before labwc is fully initialised, as the correct fullscreen command line arguments are used. The second issue can be fixed by restarting the Odoo service, the first happens every time. After this commit, we start the IoT interfaces in the main run method, instead of when the module is imported, meaning that everything else has time to finish initialising. This solves both problems. task-6469793 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#284386
Sales users without accounting permissions can now open invoices created from sales orders that use cash rounding. This prevents unnecessary access errors while keeping the invoice available to users who are already allowed to view it.
Original PR description
**Behavior:** When a sales user without accounting access rights tries to view an invoice created from a sale order where a cash rounding is applied, an AccessError is raised. This occurs because…
**Behavior:** When a sales user without accounting access rights tries to view an invoice created from a sale order where a cash rounding is applied, an AccessError is raised. This occurs because loading the invoice view triggers `_compute_tax_totals()`, which then passes the invoice's `invoice_cash_rounding_id` to `_get_tax_totals_summary()`. Which then ends up failing when trying to access fields on the `cash_rounding` record due to missing accounting rights, even though the user is allowed to view the parent invoice. This is fixed by ensuring reading fields on `cash_rounding` during tax total computation bypasses the access check using `sudo()`, as the user already has legitimate access to the invoice itself. **Steps to reproduce:** - As an admin, enable cash rounding then create one. - Create an invoice and set the Cash Rounding Method - In debug mode, go to 'Set Default Values' in the debug dropdown and set Cash Rounding Method = your rounding for all users - Go to users, and ensures that Demo has no accounting rights but has sales user rights - As Demo, create a sale order, confirm it, then create the related invoice. - When trying to acces said invoice, you should get an Access Error opw-6379781 Forward-Port-Of: odoo/odoo#284212 Forward-Port-Of: odoo/odoo#278815
This fix makes the website page management test wait until a page is fully selected before trying to delete it. It helps prevent random automated test failures, improving confidence in future website updates without changing day-to-day user behavior.
Original PR description
Fix the random tour failure by making sure the record is selected before trying to delete it. runbot-944542 Forward-Port-Of: odoo/odoo#280644
Users can now update rental start or end dates on sale orders even when they do not have direct access to planning records. This prevents unnecessary errors and keeps the related planning slots updated automatically.
Original PR description
This commit prevents a potential access error, if a user changes the rental start date and/or end date of a sale order without the access rights to the 'planning.slot' model. In this case, we want the write to be executed and changes repercuted to the associated slots. Forward-Port-Of: odoo/enterprise#128778 Forward-Port-Of: odoo/enterprise#128365
Refunds for orders with fixed global discounts now mirror the original discount correctly, so customers are refunded the right amount. This also prevents point-of-sale sessions from closing without balanced accounting entries.
Original PR description
BACKPORT OF https://github.com/odoo/odoo/pull/278180 When refunding an order that had a fixed-amount global discount, the refunded total was inflated: e.g. a 65.00 order (80.00 - 15.00 fixed…
BACKPORT OF https://github.com/odoo/odoo/pull/278180 When refunding an order that had a fixed-amount global discount, the refunded total was inflated: e.g. a 65.00 order (80.00 - 15.00 fixed discount) was refunded as 95.00 instead of 65.00. On the refund order amount_paid then differed from amount_total, so the POS session's closing entry could not balance; _validate_session rolled back and no account.move was created, leaving the session closed with no accounting. When a refund is created, the product lines are negated and the discount line is excluded, then pos_discount re-applies the global discount to the refund order through applyDiscount. For a fixed amount, reduce_base_lines_to_target_amount targets an absolute value and does not follow the sign of the (now negative) base, so the discount keeps the sale sign. Percentages scale with the base and are not affected. Negate the fixed discount amount when the destination order is a refund so it mirrors the negative base. The stored discount_value is left unchanged so the auto-resync of the discount stays idempotent. opw-6458124 Forward-Port-Of: odoo/odoo#281958
This update makes an error message clearer when sending French e-invoicing credit notes in demo mode. Users should better understand what went wrong during document generation, reducing confusion and support effort.
Original PR description
Steps to reproduce: - Install `l10n_fr_pdp` module > Switch to `FR Company` - Activate `French e-invoicing` (Demo mode) - Create a New `Credit Note` with `FR Customer` > Send Issue: The system currently displays a confusing error message during EDI document generation. We are making the error message clearer and more user-friendly. opw-6412521 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#284189
Corrects an issue in Turkish Nilvera e-invoicing where invoices with zero VAT could trigger an error instead of showing the expected warning. This helps businesses continue processing affected electronic invoices without interruption.
Original PR description
The `l10n_tr_zero_vat_warning` field is a boolean field but was incorrectly defined as [binary], causing the compute method to fail when assigning a boolean value. ```py File…
The `l10n_tr_zero_vat_warning` field is a boolean field but was incorrectly defined as [binary], causing the compute method to fail when assigning a boolean value.
```py
File "/home/odoo/src/odoo/saas-19.3/addons/l10n_tr_nilvera_einvoice/models/account_move.py", line 156, in _compute_l10n_tr_l10n_tr_zero_vat_warning
invoice.l10n_tr_zero_vat_warning = exempt_zero_tax and invoice.l10n_tr_gib_invoice_type == 'SATIS' and exempt_zero_tax in invoice.line_ids.tax_ids
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/saas-19.3/odoo/orm/fields.py", line 1892, in __set__
self.write(protected_records, value)
File "/home/odoo/src/odoo/saas-19.3/odoo/orm/fields_binary.py", line 151, in write
super().write(records, value)
File "/home/odoo/src/odoo/saas-19.3/odoo/orm/fields.py", line 1583, in write
cache_value = self.convert_to_cache(value, records)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/saas-19.3/odoo/orm/fields_binary.py", line 83, in convert_to_cache
raise TypeError(f'{self}: use BinaryValue instead of {value.__class__.__name__}')
TypeError: account.move.l10n_tr_zero_vat_warning: use BinaryValue instead of bool
```
upg-4608394
[binary]: https://github.com/odoo/odoo/pull/242043/changes#diff-d3cbb345d0a5855b7d7aa91e64a0ff480e3e5acfa3b2c71503a23ca7f3c0c511R132Very short assistant events are now fully ignored, including any project or task changes they might have carried. This prevents brief accidental activity from changing where later timesheet entries are assigned, improving accuracy for users.
Original PR description
Before this Commit, small events (<60s) would be ignored but could still set or change the project and task of future events. After this Commit, if an event is small enough to be ignored by the assistant, it is also unable to change the current project or task of the user. For this commit to work correctly, it is expected that each event objects from the assistant has a duration value. task-[6486183](https://www.odoo.com/odoo/project/4105/tasks/6486183)
A small configuration error meant two accounting report records were not individually protected from deletion as intended. This fix corrects the list so those reports are preserved, reducing the risk of accidentally removing important accounting documents.
Original PR description
On `ir.actions.report` we want to block the unlinking of specific reports in odoo. However, when the list was created a comma was missed between `action_account_original_vendor_bill` and `account_invoice_without_payment` which means we were actually protecting against people unlinking `action_account_original_vendor_billaccount_invoice_without_payment`. Adding in that comma will allow these two records to be properly protected. task-none Forward-Port-Of: odoo/odoo#283323
The Documents app now avoids a crash when multiple shortcuts point to the same document. This keeps the search panel usable and helps users browse documents reliably without interruption.
Original PR description
This commit prevents the Documents search panel from crashing with an "Expected singleton" error. `grouped` does not de-duplicate: it extends each group with the record ids as given, so as soon as two shortcuts point to the same document, the browsed target ids contain that id twice and reading `user_permission` on the group fails. Browsing through an `OrderedSet` keeps the batched read while guaranteeing one record per group. Forward-Port-Of: odoo/enterprise#129248 Forward-Port-Of: odoo/enterprise#129134
This fix prevents intermittent point-of-sale issues where AvaTax processing could accidentally refresh an order from the wrong timing and remove the selected line. It also makes the related automated test wait for tax calculation to finish, improving confidence in checkout stability.
Original PR description
The POS Avatax tour sporadically failed after returning from the payment screen. The race was reproduced locally by delaying the mocked Avatax response in mocked_request(). There's three somewhat…
The POS Avatax tour sporadically failed after returning from the payment screen. The race was reproduced locally by delaying the mocked Avatax response in mocked_request(). There's three somewhat related fixes. Firstly, get_order_tax_details() calls sync_from_ui(), which emits a SYNCHRONISATION notification. Unlike the normal POS sync path, the Avatax RPC did not pass the device context. The browser therefore treated its own notification as coming from another device and started an independent reload of open orders. That reload could replace the current order state after the tour returned to the product screen, causing the selected order line to disappear. We now pass the normal sync context so the browser can properly ignore its own notification. Secondly, we'll keep the complete sync_from_ui() response and replace its order, line, tax, and tax group data after the AvaTax calculation. We then simplify the processing client-side by moving towards the established pattern in the POS: missingRecursive() to load any other referenced records, and then pass that through loadConnectedData(). Lastly, clickPayButton() only waits for the payment screen element to be displayed. The AvaTax request starts from the screen's onMounted() callback, leaving a short window where the screen and its buttons exist but the request and UI blocker have not started yet. The next tour step can probably run during that window. To make sure this can't happen we explicitly waitRequest(). This first waits for requests to appear and then waits for them to complete. runbot-error-944281 Forward-Port-Of: odoo/enterprise#125944
This fix corrects mislabeled Japanese fiscal position names, including a wrong domestic translation and an English spelling error. It helps Japanese localization users see clearer, more accurate labels when configuring accounting tax positions.
Original PR description
Japanese translation "海外取引先" for domestic was clearly wrong.
Also fixed the misspelling ("Oversea" -> "Overseas") and removed the unnecessary "Customer" context from the name.
@qrtl
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#284608This fix prevents people who appear multiple times in a signing workflow from being prompted to sign before it is their turn. It helps ensure documents follow the intended approval sequence, reducing mistakes and preserving process control.
Original PR description
### Steps to Reproduce: 1. Create a sign request and have 3 total signers (User, Customer, Employee) 2. Enable Signing Order and make the order as follows: (1) User, (2) Customer, (3) Employee But…
### Steps to Reproduce: 1. Create a sign request and have 3 total signers (User, Customer, Employee) 2. Enable Signing Order and make the order as follows: (1) User, (2) Customer, (3) Employee But make the User and Employee the same contact 3. Send and sign the request > Notice that (1) is able to sign for (3) immediately after, (2) has not signed yet. ### Description of the issue/feature this PR addresses: **Issue:** The signing order is ignored when the same user has to sign multiple times on a document, even if it is configured for a different person to sign in between. This happens because all signature request items are initialized in the 'sent' state upon creation, rather than strictly advancing based on the order. As a result, the system prematurely allows users to sign out of order and prompts them with their next turn too early. **Solution:** To resolve this, the controller was updated to include an `is_mail_sent = True` domain filter. This ensures that the UI's post-sign popup only displays documents where it is explicitly the user's active turn, rather than prompting a premature sign. ### Current behavior before PR: Users are able to sign prematurely, and the system will disregard the configured signing order. ### Desired behavior after PR: Users will only be prompted and able to sign a document when it is explicitly their turn, per the `mail_sent_order`. This way, documents are signed in order. opw-6417327 Forward-Port-Of: odoo/enterprise#128487 Forward-Port-Of: odoo/enterprise#125573
This fixes an issue where adding or removing people from a field service planning slot could show the wrong allocated hours. Working hours are now split across the assigned resources, keeping time allocation accurate for scheduling and workload planning.
Original PR description
## Steps to reproduce: - Install planning_field_service - Create a planning slot with one resource - Add another resource for the slot we created - Notice the allocated_hours now equals 16h - Remove one of the resources - Notice now the allocated hours are 5h instead of 8h ## Cause: When adding a second resource in a slot and while computing the break_time we use the working hours of all of the resources combined instead of dividing by the number of resources and this messes up the break_time calculation which affects the allocated_percentage and at the end when trying to compute the allocated hours it will be wrongly calculated ## Fix: We divide the working hours by the number of resources to be able to compute the break_time correctly. opw-6307906 Forward-Port-Of: odoo/enterprise#125641
The AI chat launch button now correctly recognizes the selected website record, such as a job page, even when its record number has multiple digits. This prevents misleading “missing record” errors and lets users open the AI chat from frontend content as expected.
Original PR description
### Steps to Reproduce: 1. Go to the Website 2. Click on the Jobs section 3. Select any job 4. Click the Edit button 5. Select any text and click the AI button 6. Observe the error ### Description of…
### Steps to Reproduce:
1. Go to the Website
2. Click on the Jobs section
3. Select any job
4. Click the Edit button
5. Select any text and click the AI button
6. Observe the error
### Description of the issue/feature this PR addresses:
**Issue:** Opening the AI chat from a text selection on frontend records (ex. website Jobs pages) raises a "Missing Record" error referencing an unrelated hr.job record. The `res_id` sent from the frontend arrives as a string (e.g. "133"), and `action_launch_ai_chat` passes it directly into `browse()`. Since Python strings are iterable, `browse("133")` was interpreted as three separate ids ('1', '3', '3') instead of one id, causing an access check on a nonexistent record.
**Solution:** We can cast `res_id` to `int` before calling `browse()` in `action_launch_ai_chat`. This ensures that the id is treated as a single record reference instead of being unpacked one character at a time.
### Current behavior before PR:
Selecting text and launching the AI chat on a record with a multi-digit id (e.g. 133) fails with `MissingError: Record does not exist or has been deleted. (Record: hr.job('1',), User: 2)`, which blocks the feature entirely for any id longer than one digit.
### Desired behavior after PR:
The AI chat should correctly resolve and check access on the actual selected record (e.g. hr.job(133)). Ultimately, it should launche successfully, regardless of the length of the id.
opw-6443065