Tuesday, September 15, 2026
41 changes · master
Resolved issues and error corrections
Some Odoo interface icons were not appearing for users on Safari and iOS browsers because of how those browsers handled icon names with hyphens. This update ensures those icons display correctly, improving navigation and visual clarity across Apple devices.
Original PR description
__Problem__ Since `odoo_ui_icons` moved to ligatures, 42 of its 203 icons render as nothing on iOS (Safari and Chrome) and macOS Safari, `oi_view-kanban` among them. Chromium is fine. __Reason__ WebKit treats `-` as a line-break opportunity and splits the text run there, so the ligature never forms. Only names whose prefix is not itself an icon break: `oi_x-square` survives because `oi_x` exists, `oi_view-kanban` does not. Nothing is painted at all, rather than the raw string, because the font's letter glyphs are empty and zero-advance: they exist only so ligature components resolve. Hence the silent breakage. __Fix__ `word-break: keep-all` keeps the run intact.
Managers in multi-company setups can now open employee records and expense items without being blocked by access errors caused by related employees in other companies. Expense team approvers also gain more flexibility to create expenses for their subordinates across company boundaries where allowed.
Original PR description
_(updates on July-17th and July-28th for points 2. and 3. in `hr_expense`)_ # Introduction **1\.** A fix to `hr_org_chart` was initially proposed. I think it is mergeable because it fixes the…
_(updates on July-17th and July-28th for points 2. and 3. in `hr_expense`)_ # Introduction **1\.** A fix to `hr_org_chart` was initially proposed. I think it is mergeable because it fixes the user-experience properly like expected in Odoo standard. Other commits are suggested to `hr_expense`. They were designed to allow more flexibility in the management of `hr_employee.company_id` (in multi-company context) and allow not to duplicate the `hr.employee` of each companies of the `res.users`. **2\.** The [FIX] silences a multi-company access error when searching for Expense validators => it seems safe **3\.** The [IMP] largely allow more flexibility for a "Expense: Team Approver" when creating expenses on behalf of its subordinates # 1. in hr_org_chart [FIX] Prevents multi-company error when recursively searching for ancestors. <img width="1035" height="789" alt="image" src="https://github.com/user-attachments/assets/d5517f79-7efc-4223-ae77-0af8b25a3d1e" /> ### Issue description In a multi-company environment, when one of the `hr_employee.company_id` of a hierarchy is not in the allowed companies of a Manager's `res_users.company_ids`, this Manager can view `hr.employee` in the list view but cannot open their forms. This happens when: - `hr_org_chart` module is installed - the Org Chart is displayed on the 1st page of the `hr.employee` form, like when the HR settings "Skills Management" is disabled (in `res.settings`) => thus the whole form becomes inaccessible from the manager When a `hr.employee` form is opened, a multi-company access error is thrown to him, even if the `company_id` of the opened `hr.employee` is in the user's `res_users.company_ids`, because of the hierarchy's `company_id`. It should be expected that the part of the Org Chart which is not allowed to be seen would just be hidden. ### Steps to reproduce Data setup: - Employee "A" in company A - Manager "M" in company A, manager of "Employee A" - Manager of manager "MM" in company A, manager of "Manager M" - And now, in company B (let's say a Holding), the "Director" is manager of "Manager MM" - "Manager M" is only given access access to Company A - the module "hr_org_chart" is installed Actions: - Login with Manager M - Browse to Employees list and try and open the form of "Employee A" (up to tab _"Professional information"_, if it is not the 1st of the notebook) ### Proposed fix This PR re-uses the already existing method `_check_employee` which contains all the logic to solve the issue. Maybe the call to this method was forgotten? This PR simply call this method when finding an ancestor, in the controller of `hr_org_chart`. This fix is thus very limited to the call to the public method `hr_org_chart.get_org_chart()` made by the Org Chart widget. ### Desired behavior after PR is merged The part of the Org Chart not allowed to be seen by "Manager M" is hidden. # 2. hr_expense [FIX] <img width="541" height="415" alt="image" src="https://github.com/user-attachments/assets/d594815b-2b77-4088-878b-9b192b64d6a3" /> ### Issue description As an employee, I click on the button "View Report" on my expense. I get a multi-company access error, preventing me to view and edit my expense report. This is because the manager of the department I belong is in a company I'm not allowed to see. This can also happen just when opening my Expense (instead of Expense Report). ### Current behavior before this PR The employee is blocked to continue editing its Expense or to submit it to a Report. ### Current behavior after this PR The employee can edit and submit its Expense no matter the `company_id` of its hierarchy. Technically: the `can_approve` field on the expense sheet uses a localized `.sudo()` method to bypass multi-company limits when searching if the current user is a validator. # 3. hr_expense [IMP] <img width="1028" height="549" alt="image" src="https://github.com/user-attachments/assets/1096c0d4-d025-46a9-8559-6bab5de71577" /> ### Improvement summary In multi-company environment, allow a "Expense: Team Approver" to create Expenses for its subordinates (`hr_expense.employee_id`) **no matter the `hr_employee.company_id` of its subordinates**. The domain of `hr_expense.employee_id` keeps the security of `check_company=True` => thus the Manager only sees `hr.employee` having their `company_id` in the manager's allowed companies (`res_users.company_ids`). ### Current behavior before this PR Context: a 8-companies environment where the `hr.employee` of each hierarchy chains are splitted in many different companies, like: - top-level (admin board): 1 company - middle management: approx. 2 companies - down level: the other companies The "down level" have `hr.employee` but no `res.users`. The "middle management" have `res.users` and must create the Expenses of their "down level" subordinates on their behalf. Issue: as a manager, as per Odoo proposal, I need to have a `hr.employee` in the same company of my subordinates to be able to create Expense of their behalf. However, this is very inconvenient because as a Manager, I can have employees in various companies. And my own manager it not in the same company than me, so the same issue applies recursively. ### Behavior after this PR is merged The domain of the field `expense_id.employee_id` is more permissive. As a Manager, it allows me to select the Employee I manage in my active company, no matter if I have or not myself a `hr.employee` in this company. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#286293 Forward-Port-Of: odoo/odoo#266261
Users who follow a private project task can now open it even when older or mismatched attachments are missing their expected document link. The fix prevents an internal document eligibility check from causing an access error while preserving normal permissions for attachments and documents.
Original PR description
Issue: A user following a task in a private project can be allowed to read the task without having access to its parent project. If the task contains a legacy or desynchronized attachment without a…
Issue: A user following a task in a private project can be allowed to read the task without having access to its parent project. If the task contains a legacy or desynchronized attachment without a related document, opening the task can nevertheless raise an AccessError while loading the chatter. Steps to reproduce: - Create a follower only private project and a task in that project - Add an internal user as a follower of the task but not of the project - Leave a task attachment without its expected documents.document link - Open the task as that follower Cause: When computing the attachment's linked document, `_exclude_documents_mixin()` calls `_check_create_documents()` on the linked business record with the requesting user's permissions. That eligibility hook may consult protected records such as the task's private project, even though it is only used to classify the attachment. https://github.com/odoo/enterprise/blob/656469d08453a823979153200cddd3688f815468/documents/models/ir_attachment.py#L41-L53 Solution: Evaluate only the document creation eligibility hook with elevated rights. This keeps chatter metadata independent from access to the hook's related configuration while leaving attachment access, document lookup, and explicit document creation checks under the requesting user's permissions. opw-6479654 Forward-Port-Of: odoo/enterprise#128929
This fix keeps Odoo's Greek e-invoicing records aligned with actions already completed by the external provider, even if a later Odoo step fails. It also updates QR code generation so invoice QR images continue to work correctly after recent platform changes.
Original PR description
e-invoo operations occur outside the Odoo transaction, so a later failure could roll back local state while the corresponding external operation had already occurred. Commit the provider issuance result at the respective transaction boundaries. Also adapt the QR code generation as here in 19.3 upwards it doesn't use base64.b64encode anymore, but rather pass the image bytes directly. Note: regarding the commit thing, this is partially what we already have in 18.0 till 19.2, we just removed it from 19.3 at the time because it was failing the ci/style test and it needed an exception from the framework team but we didn't have the time back then we needed it to be merged asap, that's why we're adding them again right now. related: https://github.com/odoo/odoo/pull/281739 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#285581
Users with the Invoicing and Banks role can now use bank reconciliation actions that were previously blocked by access rules. This restores the intended workflow without changing access for higher-level accounting users.
Original PR description
The bank reconciliation "set account" button and the auto-reconcile wizard create account.select.account.line and bank.rec.auto.reconcile.wizard records, but their ACLs only granted access to group_account_user. Grant both ACLs to group_account_basic (Invoicing and Banks) instead, so these users can perform bank reconciliation as intended; group_account_user still inherits the access through group implication. no-task-id
Odoo now handles empty or incomplete image attachments safely when users open the media dialog or use the chatter. This prevents an error screen caused by failed uploads or malformed email attachments and lets the interface continue loading normally.
Original PR description
Problem: Interrupted uploads or malformed email payloads can create 0-byte binary `ir.attachment` records where the `checksum` is `False`. Accessing the computed `image_src` field on these records…
Problem:
Interrupted uploads or malformed email payloads can create 0-byte binary `ir.attachment` records where the `checksum` is `False`. Accessing the computed `image_src` field on these records triggers a `TypeError: 'bool' object is not subscriptable` when attempting to slice `attachment.checksum[:8]`. This crashes the Media Dialog and Chatter.
Purpose:
Add a fallback boolean guard to `attachment.checksum` inside `_compute_image_src` so that empty attachments evaluate safely to a string ('0') instead of raising a traceback, allowing the UI to render gracefully.
Steps to Reproduce on Runbot:
1. Go to Settings > Technical > Database Structure > Attachments.
2. Create a new record: Name: `test.png`, Type: `File` (Binary), File Content: [leave empty], Is public document: Checked.
3. Open any record with a Chatter (e.g., Contact or CRM Lead) and click "Insert Image" to open the Media Dialog.
4. The system attempts to evaluate `image_src` and throws the `TypeError`.
Notes:
A test (`test_compute_image_src_empty_checksum`) was added to `test_ir_attachment.py`
opw-6482674
Forward-Port-Of: odoo/odoo#287953
Forward-Port-Of: odoo/odoo#287444Users can now create and sign requests from shared Sign templates even when the template contains custom fields restricted to another group. This prevents an access error in valid signing workflows while keeping field selection restrictions in place for template setup.
Original PR description
Version-19.5 Steps to reproduce: - As Administrator, go to Sign > Templates and create a template. - Add a custom field (e.g. 'B-day'), uncheck 'Shared' and set 'Used by' to a group the sender/signer…
Version-19.5 Steps to reproduce: - As Administrator, go to Sign > Templates and create a template. - Add a custom field (e.g. 'B-day'), uncheck 'Shared' and set 'Used by' to a group the sender/signer does not belong to (e.g. an HR group). - Place the field on the document and save. - Share the template via its 'Authorized Groups' field (not 'Authorized Users') with a group the sending user belongs to. - Log in as that user, open the template, click "Sign now", assign a signer and sign now. Issue: `_check_send_ready()` and `_populate_constant_items()` read `item.type_id.item_type` without `sudo()`. A custom field type restricted to a specific group is unreadable by users who only have template access via "Authorized Groups", so creating a SR through 'Sign Now' raised an AccessError despite full access to the template. Fix: Read the field type through `sudo()` in both methods, since checking its technical type/name is an internal check and shouldn't be gated by the field 'Used by' restriction, which only controls who can pick that field type while building templates. Taskid-6574543
Fixed an issue where receiving a very long message could leave a chat channel scrolled to the end of that message instead of showing the start of the new message. This improves readability and keeps users oriented when new messages arrive, including cases with hidden notifications or delayed message rendering.
Original PR description
Before this commit, receiving a very long message in a channel scrolled at the bottom could move the message list to the end of that message instead of to its beginning. Every message received…
Before this commit, receiving a very long message in a channel scrolled at the bottom could move the message list to the end of that message instead of to its beginning. Every message received afterwards then kept the list at the end. The test "should scroll to bottom on receiving new message if the list is initially scrolled to bottom (asc order)" fails on runbot with:
10. [toBeGreaterThan] expected value to be strictly greater
> Minimum: 20762
> Received: 5190.500
This happens because `applyScroll` also runs outside of a patch, on a resize of the message list or on a loaded image, i.e. while a received message is in the store and not rendered yet. Such a run finds no element for the message, scrolls to the bottom of the list, and records the message as the newest one the scroll was applied on. The patch that renders the message then finds no newer message, and keeps the list at the bottom.
This commit fixes the issue by keeping the newest message `applyScroll` recorded when the first newer message has no element yet, so that the patch that renders the message applies the scroll.
Note that the added test delays the registration of the message element, as a resize cannot be timed between the store update and the patch.
https://runbot.odoo.com/odoo/error/946929
Forward-Port-Of: odoo/odoo#287985
Forward-Port-Of: odoo/odoo#286759Product route diagrams now open reliably instead of showing a JavaScript error. Users can view the diagram and follow its links to related records, avoiding interruption when reviewing product logistics setup.
Original PR description
Issue before this commit: ========================= Opening a product's route diagram raises an uncaught JavaScript error, preventing the report from being opened correctly. Steps to Reproduce: =================== - Create a product. - Open the product form and go to the Inventory tab. - Click on View Diagram. - An uncaught JavaScript error is raised over the diagram. Cause of the issue: =================== The report viewer moves each clickable element into a link wrapper, making the wrapper its new parentNode. The subsequent operation then tries to insert the wrapper into itself, causing a JavaScript error. With this commit: ================= Users can open the route diagram without an error and navigate to the related records through its links.
Live chat information panels now show chatbot answers even before the related conversation messages are loaded. Free-text chatbot responses are also displayed as clean plain text, improving readability and preventing layout issues.
Original PR description
Chatbot answers were read from the messages loaded in the thread, so the info panel listed them only once the message holding them was fetched. They are now stored on the channel itself, independently of its messages. Free input answers are html fields: render them as plain text, otherwise the markup adds a block element that pushes the answer off the vertical center of its icon and prevents it from being truncated. task-6449288
Manufacturing order overviews now include the cost of subcontracted product components, making estimated production costs more accurate when subcontracting is involved. A related display issue in debug mode was also fixed so the overview works reliably for these manufacturing scenarios.
Original PR description
In the MO overview, when one of the components is subcontracted, and the linked RFQ/PO is displayed, the cost of the PO does not include the components of the BoM. The total estimated cost of the MO is therefore not accurate. Other than that, a props issue was fixed for an OWL component. task-6516013 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Signed quotation PDFs created through the customer portal are now saved as automated entries rather than editable customer comments. This prevents customers from accidentally deleting the signed document before the order is confirmed, preserving an important business record while leaving normal comments unchanged.
Original PR description
Issue: A customer who signs a quotation can delete the chatter entry containing the generated signed PDF. For quotations awaiting a wire transfer, this can remove the only retrievable snapshot of the…
Issue: A customer who signs a quotation can delete the chatter entry containing the generated signed PDF. For quotations awaiting a wire transfer, this can remove the only retrievable snapshot of the signed quotation before the order is confirmed. Steps to reproduce: - Create a quotation requiring an online signature and payment. - Sign it from the customer portal and select wire transfer. - Delete the "Order signed by ..." entry from the portal communication history. Cause: The signing endpoint posts the generated PDF as a regular customer authored comment. Portal chatter allows customers to update their own comments, and deleting one clears its attachments, so the signed PDF is physically removed. https://github.com/odoo/odoo/blob/e479111294b11058038defc31505cc81ac1f1821/addons/sale/controllers/portal.py#L348-L358 Solution: Classify the signed document entry as an automated comment. This keeps it visible and attributed to the customer while ensuring that both the portal interface and backend message rules treat its content and attachment as immutable. Regular customer comments keep their existing behavior. opw-6466029 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#287478 Forward-Port-Of: odoo/odoo#283595
Fixes an error that could stop the Forum app from being installed when website cookie and third-party tracking blocking settings were enabled. The website now reuses already available cookie preference information during page/template processing, improving reliability for setup and module installation flows.
Original PR description
**Steps to reproduce:** - Install Website app - Go to Settings - Enable `Cookies Bar` and `Block tracking 3rd-party services` - Try to install Forum app (`website_forum`) - `RPC_ERROR: Odoo Server…
**Steps to reproduce:**
- Install Website app
- Go to Settings
- Enable `Cookies Bar` and `Block tracking 3rd-party services`
- Try to install Forum app (`website_forum`)
- `RPC_ERROR: Odoo Server Error`
**Issue:**
During `_render_template`, `RuntimeError: request not bound` is raised due to `self.env['ir.http']._is_allowed_cookie('optional')` in `_should_remove_third_party_trackers` depending on `request`, which is not available (unbound `<LocalProxy>`).
This was introduced by [1] where the value is recomputed instead of relying on the context.
In previous versions, the flow was not triggered as `website_id` was not in the context of `_post_processing_att`,
but it was added by [2].
Before [2] `_prepare_frontend_environment` was being restricted to calls made with `request` set (in < 19.4
versions), but now the context is always set so it triggers `_should_remove_third_party_trackers` check.
Also post 19.4 we have [3] which now sets the editable variable to `true` when `translatable` is `True` and
`edit_translations` is not present in the context. This changes the branding mode from `inherit_branding_auto`
to `inherit_branding`, which short-circuits `_post_processing_att` and prevents the problematic behavior.
**Fix:**
Use the context `cookies_allowed` value that is computed in `website.py` by `_render_template`.
[1] https://github.com/odoo/odoo/commit/0c1799f81bc70a35c8dc429c7425d3c8109ea75a
[2] https://github.com/odoo/odoo/commit/b4d852a250801921ab899eff805ab078c98c374f
[3] https://github.com/odoo/odoo/commit/05c2fb1f364ca61adc65c2b87d020885f8184a9a
opw-6477558
Forward-Port-Of: odoo/odoo#283133This fix prevents Turkish payroll payments from failing when a payslip configuration does not include stamp tax. The system now treats missing stamp tax as zero, allowing MUHSGK V2 payments to complete reliably.
Original PR description
## Steps to Reproduce: - Install the `l10n_tr_hr_payroll` and `hr_attendance` modules with demo data. - Switch to the company "My Turkish Company". - Settings > Set `Tax Responsible` and `SGK Workspace Registration Number`. - Pay Structures > `Türkiye: Monthly Pay` > remove `Stamp Tax Deduction (STAX)`. - Create and validate any employee's payslip. - Pay the payslip using the `MUHSGK V2` mode. ## Error: `TypeError: bad operand type for abs(): 'NoneType'` ## Cause: When the STAX code is missing from `totals_per_code`, it returns None. And calling `abs()` on this value raises a TypeError. ## Fix: Use `0` as the default value when STAX is missing. Align with the other values retrieved from `totals_per_code`, such as BTNET, CURTAXABLE. sentry-7719230995 Forward-Port-Of: odoo/enterprise#131360
This fixes an issue where suggested combo items in Point of Sale could be priced as zero and where combo prices were shown incorrectly in product details. Cashiers and customers now see accurate combo pricing during sales, reducing checkout errors.
Original PR description
Applying a suggested combo set the converted lines' price to 0, and the product info popup showed wrong prices incl for combos In this PR (https://github.com/odoo/odoo/pull/286576) `computeComboItems()` became `getComboPrice()` and its `parentProduct` argument became `this`, which is passed to `getPrice()`` as the variant. `getPrice()` reads `lst_price`, a field that only exists on product.product But `this` is a template when called from createComboFromLines() and getComboTaxDetails(), so the price was NaN and got rounded to 0 FIX: Find the variant first task-id: 6566915 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
This fixes an issue where Point of Sale could fail to open after a related module had been uninstalled. The system now skips outdated stored data for missing modules, helping shops get back into their Point of Sale without errors.
Original PR description
Models from uninstalled modules that were previously loaded in the indexDB of a PoS config were raising a traceback when opening the config again, as the PoS was still trying to load them. We now avoid trying to load them if they are not present anymore. task-6567097
Fixed an issue where the payment information popup on invoices could appear empty after reconciliation. This restores access to payment details and actions like viewing or unreconciling payments, preventing errors for accounting users.
Original PR description
odoo/odoo#287426 (commit `22de645`) replaces static `props` with `useProps`, not allowing the content popover to receive its dynamic props. The commit mentions that empty `props` definition should be removed, which was the case in AccountPaymentPopOver. Instead, it was replaced with `useProps(popoverProps)` which is the outer `web.Popover` component. By removing the props definition, we restore the preceding behaviour. Steps to Reproduce 1. Create an invoice. 2. Post it. 3. Go to the Accounting Dashboard and click `Transactions` to open the reconciliation widget. 4. Click `Create` to add a bank statement line with an amount sufficient to cover the invoice. 5. Ensure the statement line and the invoice are fully reconciled. 6. Open the invoice and click the info icon `(i)` next to `Paid on X`. pad-accountingv20
This fix makes required, invalid, and read-only fields display correctly when multiple fields are grouped in one list cell. Users now get clearer visual cues and more reliable keyboard navigation, reducing confusion when editing records and saving forms.
Original PR description
*: account, project, sale, sale_project Fields stacked in a single cell with the <column> tag were never styled when required or invalid: o_required_modifier and o_invalid_cell were only computed for…
*: account, project, sale, sale_project Fields stacked in a single cell with the <column> tag were never styled when required or invalid: o_required_modifier and o_invalid_cell were only computed for columns of type "field", so a required sub-field showed no underline and, left empty, stayed unhighlighted while still blocking the save. Mark the wrapper of the sub-field rather than the whole cell, as a column group cell may display several fields of which only one is required or invalid. Readonly modifiers (o_readonly_modifier and text-muted) were not computed for the fields stacked in a single cell with the <column> tag, so a readonly sub-field was rendered as an editable one. Compute them on each sub-field wrapper, as a column group cell may display several fields of which only one is readonly. The keyboard navigation looked for that class on the first child of the cell, which never matched a stacked cell. As a result, Tab could land on a readonly sub-field holding a tabable element, e.g. the link of a readonly many2one. Skip a stacked cell when all of its sub-fields are readonly, and otherwise ignore the readonly ones when picking the element to focus. For consistency sake, isCellReadonly is renamed into isFieldReadonly in consequence to these changes. task-6564093
Fixed an error that could occur when managers opened a new time off request using calendar-day counting before an employee was selected. This keeps the leave creation flow usable and avoids interruptions for HR teams configuring or managing sick leave.
Original PR description
BUG :
- choose a US company
- in sick leave timeoff type , make to count days as "calendar days"
- open management and try to create a leave -> traceback
REASON :
- when you open the timoff form view for the first time , the employee_id in empty this causes work_time_per_day_mapped to be empty, => work_time_per_day_mapped[leave.date_from, leave.date_to, include_public, calendar] this fails because there is no entry in the dict
FIX:
- add a safeguard at line 653 , to prevent the computations when the leave has no employees , in this case we should fall back to the else in line 739, this acts as way to prevent tracebacks, during calculation , because in the database itself you could never have a leave with no employee assigned to it.
task-6411996
Forward-Port-Of: odoo/odoo#279028Maintenance requests must now have both a start and end schedule, or neither, preventing planning screens from failing when schedule information is only partly filled in. This helps manufacturing teams open work order planning reliably even when maintenance data is being entered or edited.
Original PR description
#### Issue: Opening the MRP planning view could raise a traceback when a maintenance request had a `schedule_end` but no `schedule_date`. `TypeError: '<' not supported between instances of 'NoneType'…
#### Issue: Opening the MRP planning view could raise a traceback when a maintenance request had a `schedule_end` but no `schedule_date`. `TypeError: '<' not supported between instances of 'NoneType' and 'datetime.datetime'` #### Steps to reproduce: 1. Install maintenance, mrp, and mrp_maintenance. 2. Create a work center. 3. Create a maintenance request linked to that work center. 4. set a `Scheduled end` and Leave `Scheduled Date` empty. 5. Go to MRP > Planning > Work Orders. #### Cause: `maintenance.request` stores `schedule_end` as a writable field, but no constraint enforces that `schedule_date` and `schedule_end` must be set together. Later, `mrp_maintenance` in `_get_maintenances_intervals` fetches maintenance intervals for the gantt view without filtering null bounds. If a request has `(schedule_date, schedule_end)` = `(False, datetime)`, that interval is passed to `Intervals(...)`, which crashes when comparing `None` with a `datetime`. #### Fix: Add a constraint on `maintenance.request` to require `schedule_date` and `schedule_end` to either both be set or both be empty. Also filter out incomplete intervals in the MRP maintenance gantt query in this enterprise PR: https://github.com/odoo/enterprise/pull/117710 opw-6225772 enterprise PR: https://github.com/odoo/enterprise/pull/117710 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#276633 Forward-Port-Of: odoo/odoo#265208
Installing the Belgian payroll localization no longer fails when a company already has sick leave records. This prevents users from ending up with Payroll partly installed while the Belgian localization is missing.
Original PR description
Since the sickness relapse origin became a stored computed field, the ORM recomputes it on every existing leave when the module is installed. That happens while the models are reflected, i.e. before the module data files are loaded, so the sickness_relapse_period_days rule parameter does not exist yet and the compute raised:
UserError: No rule parameter with code "sickness_relapse_period_days"
was found for 2026-09-21
The installation aborted there, after hr_payroll had been committed: the user saw an "Invalid Operation" dialog and ended up with Payroll installed but the Belgian localization missing.
Look the parameter up with raise_if_not_found=False, as _compute_is_meal_voucher_valid already does, and keep whatever origin is stored on the leave when the relapse period is not known yet.The MRP planning view now ignores maintenance entries with only one scheduled date filled in, preventing an error that could block users from opening the plan. This keeps production planning accessible while related validation ensures future maintenance requests use complete scheduling information.
Original PR description
#### Issue: Opening the MRP planning view could raise a traceback when a maintenance request had a ``Scheduled End`` but no ``Scheduled Date``. ```TypeError: '<' not supported between instances of 'NoneType' and 'datetime.datetime'``` #### Cause: In `_get_maintenances_intervals`, `mrp_maintenance` loaded maintenance intervals for gantt unavailability without filtering out incomplete rows. If an interval like False, datetime reached Intervals, it crashed when comparing None with a datetime. #### Fix: Filter out incomplete maintenance intervals in the gantt query. Also added a constraint on `maintenance.request` to require `schedule_date` and `schedule_end` to either both be set or both be empty in this community PR: https://github.com/odoo/odoo/pull/265208 opw-6225772 Forward-Port-Of: odoo/enterprise#124516 Forward-Port-Of: odoo/enterprise#117710
This update fixes errors that could appear when users opened the Miscellaneous tab or clicked the popover in the Bill of Materials form. The change ensures the popover is only shown when data is available and aligns the manufacturing customization with the updated button-based interface.
Original PR description
This commit fixes some issues, following changes done in [^1]: 1. The template override done in MRP targeted a `<a>` element which was replaced by a `<button>` element; 2. The props for the MRP override of this widget were added but most of them are optional (in fact, the only one which is required was the only one marked as optional, it is the opposite); 3. The widget were rendered no matter what, even when `json_popover` was empty. Issue 1 caused a traceback when trying to display the "Miscellaneous" tab in the BoM form view and issues 2 and 3 caused another traceback when clicking on the widget from this view. [^1]: https://github.com/odoo/odoo/pull/287605
This fix prevents the Pay on Site option from being automatically re-enabled when the Website Sale Collect app is upgraded. Merchants' payment settings are now preserved, helping avoid unpaid checkout orders being offered unintentionally.
Original PR description
Steps to reproduce: =================== 1. Go to the payment providers and set "Pay on Site" to disabled. 2. Go to Apps, clear the filter, look up "website_sale_collect" and upgrade it. This also…
Steps to reproduce:
===================
1. Go to the payment providers and set "Pay on Site" to disabled.
2. Go to Apps, clear the filter, look up "website_sale_collect" and upgrade it. This also happens on its own, as upgrading any custom module that depends on it upgrades it too.
3. Go back to the payment providers.
=> "Pay on Site" is enabled again, and published as well from 19.0 on. Customers are offered it at checkout and place orders that are never paid, without the merchant ever enabling anything.
Root cause:
===========
`data/payment_provider_data.xml` is loaded in update mode because it carries no `noupdate`, and it hardcodes the state of the provider:
<field name="state">enabled</field>
So every upgrade of the module writes that value back over whatever the merchant configured. Every other provider ships its data with `noupdate="1"` and leaves the state alone, this module is the exception.
The file has been loaded this way since the module was added:
- [1] created the module with an updatable provider record.
Fix:
====
Load the file with `noupdate="1"`. The record is still created, enabled, when the module is installed, it is simply not written again on later upgrades. The flag is read from the file rather than from the `ir.model.data` row, so databases where the provider already exists are covered on their next upgrade, no data migration needed.
[1]: 087c48c4ed2e
opw-6528110
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#286913Point of Sale now correctly includes product variant extra charges when a discount pricelist is based on another pricelist. This prevents undercharging at checkout and helps ensure displayed and charged prices match the intended product setup.
Original PR description
## Steps to reproduce: - Create a product, with never variant, the variant has an extra price of 100 - Make Pricelist 1, just leave it as default - Make Pricelist 2, make it a discount, based on Pricelist 1, for all products - Go to the PoS, click on the created product - Change the pricelist to Pricelist 2 -> the price does not take the extra price into account ## Why the fix: When we have a pricelist based on another pricelist, we recursively calculate the price on the base pricelist. Before this commit, in the recursive call, we gave 0 as the extra price. We now give the extra price in the recursive function call. opw-6500086 Forward-Port-Of: odoo/odoo#286608 Forward-Port-Of: odoo/odoo#285371
Fixed an issue where selling and invoicing a shared product in Point of Sale could fail when vendor or replenishment data from another company was cached. This helps multi-company users complete POS sales reliably without access errors caused by records from another company.
Original PR description
**Steps to reproduce:** - Install PoS and Purchase - Make 2 companies, A and B - On product "Office Lamp" that is accessed by both companies, put Partner A in the vendor tab for company B - Company A…
**Steps to reproduce:**
- Install PoS and Purchase
- Make 2 companies, A and B
- On product "Office Lamp" that is accessed by both companies, put Partner A in the vendor tab for company B
- Company A should have a Partner A and Partner B in this tab
- In the stock, set a replenishment for company A, with Partner A on Office Lamp
- Go to company B and open the PoS
- Try to buy Office Lamp while requesting an invoice
- An access error appears
**Why the fix:**
When requesting an invoice in the PoS, we try to create the stock picking. Doing so will trigger the replenishment rules linked to the product to be recomputed.
Those are executed when we **flush_all()**, processing Company A's replenishments as sudo(), meaning all of Company A's **seller_id** are fetched and cached. This means the product's **seller_ids** now contains Company A's **seller_id**, even though we are currently in Company B.
While trying to get the product's code, we iterate over **product.seller_ids**, but we do not have access to every record in that product.
https://github.com/odoo/odoo/blob/b8e5291d103d9f43bd8db6d2dfe708076a57ea37/addons/product/models/product_product.py#L337-L343
As we don't have access to those, we get an access error when we stumble upon it.
To avoid those errors, we now filter the sellers to only have the ones compatible with our current Company in the given product we are currently buying.
Another solution would be to do **product.invalidate_recordset(['seller_ids'])** before looping over it, but feels more like a band-aid than the current fix IMO.
We could also write **self.lines.product_id.mapped('code')** in **_create_order_picking(self)** to have the solution be in PoS directly, but the error might arise from somewhere else at some point, and this just hides the issue by adding the code to the cache so that we don't have to fetch it again later.
opw-6308182
Forward-Port-Of: odoo/odoo#286656
Forward-Port-Of: odoo/odoo#275354The Navarra SII tax agency service link has been updated to the current endpoint after the previous one stopped working. This helps Spanish electronic VAT reporting continue to connect correctly for companies using the Navarra tax service.
Original PR description
The WSDL URL used for the Navarra tax agency SII web service was no longer working. It has been replaced with the updated endpoint 'ssii_1_1'. task-6457647 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#285544 Forward-Port-Of: odoo/odoo#285049
When a chart of accounts is installed for an existing company, required tax returns are now created automatically instead of requiring a manual refresh. The update also ensures archived returns stay hidden from reporting summaries, reducing confusion for users reviewing return status.
Original PR description
1) Create a company without any country
2) Go to the settings, install a CoA on it that uses returns (like the Belgian one) 3) Go to the returns of that company
====> Nothing has been created, you need to click "Refresh" manually to get them.
This is wrong, returns should be created by default in this case.The Belgian payroll working schedule change wizard now correctly carries over calculated time-off allocation hours when a change takes effect immediately. This prevents employees from receiving allocations with zero hours, improving accuracy in payroll and leave records.
Original PR description
to reproduce: - Open the "Working Schedule Change" wizard on a BE employee's contract and confirm it for a change that applies immediately (not scheduled for a future date). - Open the resulting time off allocation: its hours are 0. Issue: `time_off_allocation` and `time_off_allocation_hours` are computed together by `_compute_time_off_allocation`, but only `time_off_allocation` is user-editable (`readonly=False`). On save, the ORM protects a whole compute group from recomputation as soon as one of its fields is given explicitly, so `time_off_allocation_hours` is never sent nor recomputed and stays at its 0 default. The wizard then writes `number_of_hours: 0` on the allocation. Fix: Make `time_off_allocation_hours` `readonly=False` too, and add it (invisible) to the view, so its last computed value is actually saved alongside its sibling field. Task-6571882
Mexican payroll now checks an employee's daily wage when applying minimum wage protections, preventing incorrect social security deductions when minimum wage workers receive extra pay. It also rounds schedule days and fixes subsidy eligibility so unpaid absences do not wrongly disqualify employees from benefits.
Original PR description
According to LSS Art. 36, minimum wage workers are exempt from IMSS deductions. Previously, the system evaluated the period's total gross wage instead of the daily wage, which caused incorrect…
According to LSS Art. 36, minimum wage workers are exempt from IMSS deductions. Previously, the system evaluated the period's total gross wage instead of the daily wage, which caused incorrect withholdings when a minimum wage employee received extra pay (like commissions). We now evaluate the `l10n_mx_daily_salary` directly to protect minimum wage workers while ensuring correct deductions for higher earners with unpaid leaves. Since calculations rely on `schedule_days` retrieved from the schedule table, users commonly adjust these values (e.g., from 15 to 15.2, or 30 to 30.4). To prevent discrepancies caused by this practice, we now round the `schedule_days`. Finally, this commit fixes the employment subsidy eligibility threshold. Previously, the limit was incorrectly reduced by unpaid absences. This caused a double-counting effect (since absences already lower the actual gross wage) and wrongly disqualified employees. The subsidy limit is now based strictly on the full payroll schedule length, while the ISR minimum wage exemption correctly continues to consider actual worked days. target: 19.0 task-6370959 Forward-Port-Of: odoo/enterprise#130706 Forward-Port-Of: odoo/enterprise#125061
The Indian salary configurator now shows only one Gross salary line and keeps the Monthly Equivalent total accurate. This prevents duplicate salary information and gives HR teams and employees a clearer, more reliable compensation view.
Original PR description
Issue: - The salary configurator displays two 'Gross' lines for Indian payroll structures. - The generic salary resume uses the `wage` code, while the Indian payroll defines a structure-specific…
Issue: - The salary configurator displays two 'Gross' lines for Indian payroll structures. - The generic salary resume uses the `wage` code, while the Indian payroll defines a structure-specific 'Gross' resume using the 'GROSS' payslip rule. Both resumes were included in the salary configurator, resulting in duplicate 'Gross' entries. - The 'Monthly Equivalent' total shown in the configurator was also affected, as it included the generic 'wage' amount on top of the actual payslip values. Cause: - The generic 'wage' resume was still included for the Indian payroll structure alongside the India-specific 'GROSS' payslip resume. - The generic 'wage' resume also counts towards the 'Monthly Equivalent' total shown in the configurator, so removing only the duplicate line left this total incorrect. Fix: - Exclude the generic 'wage' resume from the salary configurator results when the selected structure is the Indian employee payroll structure. - This keeps the India-specific 'Gross' resume based on the 'GROSS' payslip rule while preventing the generic contract wage from being displayed as a duplicate. - Subtract the removed 'wage' amount from the 'Monthly Equivalent' total so it stays correct after the duplicate line is removed. task-6511413 Forward-Port-Of: odoo/enterprise#129518
Fixed an issue that could prevent users from creating a new product from the purchase catalog when a vendor was prefilled. This keeps the purchasing workflow running smoothly and avoids an unexpected error screen in the product creation form.
Original PR description
Opening a product creation form from the purchase catalog raises a traceback. ### Steps to Reproduce 1. Go to **Purchase > Orders** (or Requests for Quotation). 2. Open any order with a **Vendor**…
Opening a product creation form from the purchase catalog raises a
traceback.
### Steps to Reproduce
1. Go to **Purchase > Orders** (or Requests for Quotation).
2. Open any order with a **Vendor** selected.
3. Click **Catalog** on the order lines table.
4. Search for any non-existent product to trigger the empty state.
5. Click **Create a product** from the no content helper.
### Traceback
```pytb
Traceback (most recent call last):
File "odoo/addons/web/models/models.py", line 2232, in onchange
defaults = self.default_get(missing_names)
File "odoo/orm/models.py", line 1401, in default_get
defaults[fname] = field.convert_to_write(value, self)
File "odoo/orm/fields_relational.py", line 759, in convert_to_write
if record != origin:
File "odoo/orm/models.py", line 6117, in __eq__
return self._name == other._name and set(self._ids) == set(other._ids)
TypeError: cannot use 'dict' as a set element (unhashable type: 'dict')
```
### Issue
The purchase catalog action helper (PR odoo/odoo#164131) sets the
current vendor as default on the new product using:
```python
context = {'default_seller_ids': [{'partner_id': vendor_id}]}
```
In commit 188c81575130 (PR odoo/odoo#272499), a check was added to
`convert_to_cache()` to optimize lists of record ids (`[1, 2, 3]`):
```python
elif isinstance(value, list) and value and not isinstance(value[0], (tuple, list)):
```
The PR assumed all relational commands are tuples or lists
(like `(0, 0, vals)` or `[0, 0, vals]`), and that anything else must
be an id. However, x2many command values can also be
dictionaries (`[{'partner_id': vendor_id}]`), which the method
explicitly supports further down (`elif isinstance(command, dict):`).
Because a dictionary is neither a tuple nor a list, the check mistakenly
treated `{'partner_id': vendor_id}` as a record id and placed it inside
`record._ids`. When the ORM subsequently compares records
(`record != origin`), it attempts to create a `set()` of the ids and
crashes because dictionaries cannot be hashed.
### Fix
Exclude `dict` from the check:
```python
elif isinstance(value, list) and value and not isinstance(value[0], (tuple, list, dict)):
```
This ensures lists of dictionaries fall through to `elif isinstance(command, dict):`
and are properly instantiated as new in-memory records.
Related: odoo/odoo#272499
Related: odoo/odoo#164131Fixes an issue where editing purchase or invoice line descriptions could accidentally add the internal product name before vendor-specific or translated product details. This keeps printed orders and invoices cleaner and avoids confusing duplicate product names for vendors and customers.
Original PR description
\* = point_of_sale, purchase_requisition **Steps to reproduce:** 1. Install purchase app 2. Create a product, and in the purchase tab set a vendor and specify product name/code (e.g., "VENDOR-CODE")…
\* = point_of_sale, purchase_requisition **Steps to reproduce:** 1. Install purchase app 2. Create a product, and in the purchase tab set a vendor and specify product name/code (e.g., "VENDOR-CODE") 3. Create a purchase order, select that product and vendor 4. The PO line shows: "VENDOR-CODE\n[Product Description]" 5. Edit the description in the line and print the invoice **Issue:** Two related issues with product name/description handling in invoice and purchase order lines: 1. **Vendor Product Name Issue:** When a purchase line has a vendor product name/code, editing the description causes the original internal product name to be prepended, resulting in: `[Product Name]\n[Vendor Code]\n[Description]` 2. **Translation Issue:** When a customer has a different language, editing the product description causes the original product name to be prepended to the invoice line: `[Original Name]\n[Translated Name]\n[Translated Description] [1] **Why this happens:** The widget (`ProductLabelSectionAndNoteField`) was using the `productName` (untranslated/base name) to detect what to strip from the label, but in the two scenarios this didn't match: - **Vendor flow:** `productName` = "Product A" but the label contains vendor code "VENDOR-CODE" - **Translation flow:** `productName` = "Product A" (English) but the label contains "Produit A" (French) When neither matched, the truncation logic wouldn't work, and `parseLabel()` would later prepend the original product name blindly. **Expected behaviour:** - Original product name should not be prepended in both scenarios **The fix:** 1. Added computed field to `pos.order.line` since it uses the same widget and needs to have the field dependency [2] 2. Added computed field to `purchase.requisition.line` since it uses the same widget and needs to have the field dependency 3. Modified `ProductLabelSectionAndNoteField.parseLabel()` to: - Only concatenate product name when `translatedProductName == productName` (indicating same language/no vendor override) since it would have been truncated by `get label()` - Otherwise, return the value as-is (no concatenation needed since `get label()` didn't truncate) This handles the flows: - **Vendor flow:** `translated_product_name` contains vendor name/code (≠ productName) → no concatenation - **Translation flow:** `translated_product_name` differs from `productName` → no concatenation - **Same language/no vendor:** `translated_product_name` == `productName` → concatenates → maintains existing behavior **Related:** - [1] - #248401 - [2] - #254158 opw-6391501
Point of Sale sessions now handle outdated cached data from older installations or removed modules without failing. This prevents users from being blocked when opening POS after model changes or module uninstallations, avoiding the need for manual cache reloads.
Original PR description
In PR-#[225341](https://github.com/odoo/odoo/pull/225341) we started passing all the models which are cached on the front end directly to the back end, but if the database existed before and the front end cached models which no longer exist, either because the model name changes (such as pos.product.template.snooze -> pos.snooze), or because a module was uninstalled (removing pos_restaurant_appointment), the front end would ask the back end for models which no longer exist, and that would error. When we do the reload data, all the local cache would be deleted and then we could launch the POS. To fix it, now the back end will check whether the model exists before trying to filter on it, and just ignore it if it doesn't exist Task-[6562705](https://www.odoo.com/odoo/project/1737/tasks/6562705) --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
The point of sale now ignores old cached references to features or models that no longer exist, instead of failing during startup. This helps businesses continue loading POS sessions after upgrades, renames, or module removals without needing a manual cache reset.
Original PR description
In PR-#[225341](https://github.com/odoo/odoo/pull/225341) we started passing all the models which are cached on the front end directly to the back end, but if the database existed before and the front end cached models which no longer exist, either because the model name changes (such as pos.product.template.snooze -> pos.snooze), or because a module was uninstalled (removing pos_restaurant_appointment), the front end would ask the back end for models which no longer exist, and that would error. When we do the reload data, all the local cache would be deleted and then we could launch the POS. To fix it, now the back end will check whether the model exists before trying to filter on it, and just ignore it if it doesn't exist Task-[6562705](https://www.odoo.com/odoo/project/1737/tasks/6562705) --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Fixes an error that occurred when users applied an inventory date in the Stock reporting view while using Arabic. The system now saves the selected date in a standard format, preventing crashes and allowing stock reports to load correctly across languages.
Original PR description
Currently, an error occurs when applying the inventory date in the Stock reporting view. Steps to Reproduce: - Install the `stock` module with demo data. - Go to `Settings` > `Languages`, add…
Currently, an error occurs when applying the inventory date in the Stock reporting view. Steps to Reproduce: - Install the `stock` module with demo data. - Go to `Settings` > `Languages`, add `Arabic`, and switch to it. - Go to `Inventory` > `Reporting` > `Stock`. - In the `left-side panel`, enter an `Inventory at Date` and click `Apply`. `ValueError: time data '٢٠٢٦-٠٩-٠٩ ٠٥:١٨:٠٠' does not match format '%Y-%m-%d %H:%M:%S'` After the [recent commit] that added a date picker to the Stock report search panel, when user enters a date using Arabic numerals, the text is displayed from right to left [1]. This date is then added to the context [2]. When computing the quantities, the date value from the context [3] is passed to it, where it is converted to a datetime [4]. However, the conversion expects the date to use Latin numerals. Since the date is in Arabic numerals, this raises the error. This commit ensures that the date is serialized using serializeDateTime [5], which also uses the UTC timezone and the Latin numbering system (latn) as expected by the system. [recent commit]: https://github.com/odoo/odoo/commit/52bfa5b9bebbcb4f8042daa183edc39865036605 [1]- https://github.com/odoo/odoo/blob/7ef98548726f36856d78c745679b14b0165803ba/addons/stock/static/src/views/search/stock_report_search_panel.js#L39-L40 [2]: https://github.com/odoo/odoo/blob/7ef98548726f36856d78c745679b14b0165803ba/addons/stock/static/src/views/search/stock_report_search_model.js#L52-L53 [3]: https://github.com/odoo/odoo/blob/7ef98548726f36856d78c745679b14b0165803ba/addons/stock/models/product.py#L146 [4]- https://github.com/odoo/odoo/blob/7ef98548726f36856d78c745679b14b0165803ba/addons/stock/models/product.py#L162 [5]: https://github.com/odoo/odoo/blob/7ef98548726f36856d78c745679b14b0165803ba/addons/web/static/src/core/l10n/dates.js#L552-L560 sentry-7629069474 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#287181
This fix prevents Hong Kong payroll payment reports from crashing when an employee has no ID or passport number. It also adds clearer validation for required AutoPay information so payroll and batch payment files are not generated with missing bank-required data.
Original PR description
When both identification_id and passport_id are empty (False), re.sub() receives a bool and raises: TypeError: expected string or bytes-like object, got 'bool'. Fall back to an empty string and normalize the passport fallback the same way as the HKID. The HSBC/Hang Seng (l10n_hk_mri) AutoPay file format requires a Payment Set Code, a First Party Reference and a per-payment identifier (HKID or passport number); without them the file was silently generated with missing data. Validate these in l10n_hk.bank.format._validate() so the check is shared by both consumers of the format: the payroll payment report and the generic batch payment export. Add the matching Party Reference check to l10n_hk_payment_autopay's journal validation so batch payments get a proper RedirectWarning to the journal instead of a bare error. Task-6536846 Forward-Port-Of: odoo/enterprise#130894
This fix ensures that changes made in forms with related line items keep all needed background data in sync. It prevents inconsistent values from appearing after an automatic form update, improving reliability for users working with complex records.
Original PR description
If we copy only the fields that are in the spec, we miss fields that are used during computes in the trigger tree. The example is `test_onchange_one2many_with_domain_on_related_field` that may miss the m2o field in test_orm.emailmessage._inherits. Following that change, we see that the value sent in the onchange did not invalidate values depending on it. The data was inconsistent. Actually, for new records, a call to `update` does all invalidations we need. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Fixed an inventory issue where products packed separately could appear under the same package on the final delivery when delivery steps were merged. This helps warehouse teams keep package tracking accurate and prevents confusion during shipping.
Original PR description
Steps to reproduce --- 1. Set the warehouse to deliver in 3 steps. 2. Create a delivery pick not tied to a procurement group (not from a sale order) with a consumable product, and validate it. 3. On…
Steps to reproduce --- 1. Set the warehouse to deliver in 3 steps. 2. Create a delivery pick not tied to a procurement group (not from a sale order) with a consumable product, and validate it. 3. On the pack transfer, put the goods in a package and validate. 4. Duplicate the pick, validate it, put its goods in a second package on the pack transfer, and validate. 5. Open the final delivery: both lines sit in the same package instead of one line per package. Issue --- The two picks share no procurement group, so their delivery moves merge onto a single move keyed by partner. Validating the second pack tops up that already partially reserved move: for a consumable, `_action_assign` builds its lines from `_get_available_move_lines`, which reports the availability of every upstream package without discounting what the move already reserved. https://github.com/odoo/odoo/blob/05af9e6877fd0f044bb46c983fe7300eb1db9307/addons/stock/models/stock_move.py#L1907-L1909 The package already reserved on the first line is therefore offered again and reused, collapsing both lines onto one package. The reserved-product branch below already subtracts the move's own reservation before allocating; doing the same here leaves each package on its own line. opw-6498532 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#287935 Forward-Port-Of: odoo/odoo#284956
Orders shared between trusted Point of Sale configurations could keep the wrong session information when the restaurant app was installed, causing valid payments to be rejected. This fix preserves the synchronization data so shared orders use the correct session and can be paid with the appropriate payment methods.
Original PR description
**TL;DR** - in certain cases, Session is not updated on order shared on trusted pos Steps to reproduce: - Install pos_restaurant with demo data. - Have two configs: Cloth Shop and Furniture Shop. -…
**TL;DR** - in certain cases, Session is not updated on order shared on trusted pos Steps to reproduce: - Install pos_restaurant with demo data. - Have two configs: Cloth Shop and Furniture Shop. - Ensure both configs have their own individual payment method (e.g., CASH). - In Cloth Shop, add Furniture Shop as a trusted PoS. - In Cloth Shop, enable "Log in with Employee". - Open Cloth Shop and Furniture Shop in separate browsers (different users). - Refresh Cloth Shop once. - In Cloth Shop, create and save an order. - In Furniture Shop, open that order and try to pay with "CASH". Observation: * A validation error is raised: "The payment method selected is not allowed in the config of the POS session." <img width="320" height="201" alt="image" src="https://github.com/user-attachments/assets/8ca80575-76bf-4ecf-a739-08b03d543ed6" /> - although we can pay the order by a shared payment method Cause: - Refreshing Cloth Shop triggers `notify_synchronisation` due to `setCashierUpdateSession` , which pushes Cloth Shop's `pos.session` record into Furniture Shop's IndexedDB (because Cloth Shop trusts Furniture Shop). https://github.com/odoo/odoo/blob/6a3655c8efd2de68754ace38bd1ab5eb6499faca/addons/pos_hr/static/src/app/services/pos_store.js#L61-L63 - When Cloth Shop saves an order, sync also pushes a copy of that order into Furniture Shop, and passed through `processDynamicRecords` - `processDynamicRecords` ensures that the Record created is in sync with server data, as Furniture Shop now has a local record of session of Cloth Shop, the record keeps `session_id` pointing to Cloth Shop's session, else it would have been left `undefined` - from the `res` we get, we set the session_id on `pos.order ` https://github.com/odoo/odoo/blob/6a3655c8efd2de68754ace38bd1ab5eb6499faca/addons/point_of_sale/static/src/app/utils/devices_synchronisation.js#L127-L139 - However with pos_restaurant installed we get its orveride which stores the result of super() and only returns result early when there are no` pos.order ` records in dynamicRecords. When pos.order records are present (our case), the method proceeds with its own logic but never returns result (or its own output) at the end https://github.com/odoo/odoo/blob/6a3655c8efd2de68754ace38bd1ab5eb6499faca/addons/pos_restaurant/static/src/app/utils/devices_synchronisation.js#L5-L9 - so the corrected data from the super call is silently dropped. - we do not get a change to update the session id - The order keeps Cloth Shop's session ID - Furniture Shop's payment validation checks the order against its session's allowed payment methods, but since the session is still Cloth Shop's, the check fails for methods not shared between Cloth Shop and Furniture Shop (e.g., CASH). Why this is hidden in other cases: - without pos_restaurant, or if Furniture Shop had no prior knowledge of Cloth Shop's session (we made is possible by `Log in with Employee` feature, session_id would stay `undefined` and later get correctly set toFurniture Shop's session in PosOrder.setup(). https://github.com/odoo/odoo/blob/6a3655c8efd2de68754ace38bd1ab5eb6499faca/addons/point_of_sale/static/src/app/models/pos_order.js#L27-L30 or from here https://github.com/odoo/odoo/blob/6a3655c8efd2de68754ace38bd1ab5eb6499faca/addons/point_of_sale/static/src/app/utils/devices_synchronisation.js#L127-L139 - Both safety nets happen to be bypassed here. Fix: * Tighten the early return condition so that data is only discarded when the current configuration is not a restaurant configuration. * Also, A trusted PoS configuration cannot be a restaurant, so not returning later makes sense. opw-6465228 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#287574 Forward-Port-Of: odoo/odoo#282962
Preparation tickets printed through an Obox-connected printer are now excluded from the kiosk’s own local printing list. This prevents the same kitchen or preparation ticket from being printed twice, reducing paper waste and staff confusion.
Original PR description
A preparation printer proxied through an Obox is not reachable by the customer device: `pos.order._send_order` queues its ticket server side. Now that the kiosk prints its preparation ticket again, such a printer must be left out of the ones the device prints on itself, otherwise the ticket is printed twice. `hasProxyPreparationPrinters` is superseded by `localPreparationPrinters` and is left deprecated here, to be removed in master. Task-6537765 Related : https://github.com/odoo/odoo/pull/269216