Tuesday, September 15, 2026
20 changes · saas-19.4
Resolved issues and error corrections
This fixes Greek e-invoicing so successful provider operations are preserved even if a later Odoo step fails, reducing the risk of mismatched invoice status between Odoo and the external service. It also updates QR code handling so generated invoice QR codes work correctly in this version.
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
Portal users can now submit product reviews with file attachments when product discussions and ratings are enabled. This prevents a confusing “Not Found” error and lets customers complete reviews without needing support or retrying without files.
Original PR description
A portal user cannot add an attachment when posting a review on a product page: submitting the review with a file attached fails with a generic "Not Found" error, while posting the exact same review…
A portal user cannot add an attachment when posting a review on a product page: submitting the review with a file attached fails with a generic "Not Found" error, while posting the exact same review without an attachment works fine.
Steps to reproduce:
-------------------
* Enable "Discussion and Rating" on a product page (Website > Customize)
* Log in as a portal user and open that product page
* Write a review, attach a file, then submit
> Observation:
"Not Found
The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again."
Why the fix:
------------
`product.template._get_mail_message_access()` demotes a portal user's create access to 'write' whenever
`env['website'].is_view_active('website_sale.product_comment')` is False, since 'write' access is a deliberate way to block posting when the feature is toggled off for the website. `is_view_active` only picks the correct, website-specific view when `website_id` is present in `self.env.context`; that key is injected by the website frontend only for routes declared `website=True`.
`/mail/attachment/upload` is not `website=True` (attachments used to go through the dedicated, `website=True` `/portal/attachment/add` route, removed when portal's attachment uploader was unified with mail's), so `website_id` is missing from context on that route, `is_view_active` falls back to the generic, shipped-`active="False"` view, and always reports the feature as disabled - even when it is actually enabled for the current website. Create access is then wrongly demoted to 'write', which a portal user never has on `product.template`, so the thread lookup fails and the controller raises `NotFound()`.
Resolve the current website from the request itself (`env['website'].get_current_website()`) and inject its id into context before checking `is_view_active`, instead of relying on `website_id` already being in context. This makes the check accurate regardless of which route triggered it.
opw-6539184
Forward-Port-Of: odoo/odoo#288116
Forward-Port-Of: odoo/odoo#287345Leads created from WhatsApp conversations in Discuss now correctly include the customer contact. This helps sales teams keep WhatsApp-originated CRM leads complete and avoids manual contact updates.
Original PR description
### Steps to reproduce: - Install 'whatsapp', and 'crm_livechat' - Configure a WhatsApp channel and receive a message to create a conversation in Discuss - Open the WhatsApp conversation in Discuss -…
### Steps to reproduce: - Install 'whatsapp', and 'crm_livechat' - Configure a WhatsApp channel and receive a message to create a conversation in Discuss - Open the WhatsApp conversation in Discuss - Click on the 'Create Lead' smart button - Check the created lead > The Customer/Contact field is empty ### Cause of Issue: When a lead is created from a Discuss conversation, `_convert_visitor_to_lead` in `crm_livechat` attempts to set the lead's customer. It does this by checking if the channel has `livechat_customer_partner_ids`. However, in whatsapp discuss conversations, the client is saved in `whatsapp_partner_id` and `livechat_customer_partner_ids` is empty. https://github.com/odoo/odoo/blob/29556fda44b9f1e6cf08129443ca47fa6cda34f9/addons/crm_livechat/models/discuss_channel.py#L54-L64 ### Fix: Kept the fix local to `crm_livechat` and checked whether `whatsapp` is installed before reading `whatsapp_partner_id`, which would otherwise raise an `AttributeError` since `crm_livechat` does not depend on `whatsapp` and vice versa. opw-6371274 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#283008
Users who follow a task in a private project can now open that task without errors when older or mismatched attachments are present. The fix prevents attachment metadata loading from being blocked by project access rules while keeping normal document and attachment permissions intact.
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
Sales users can once again drag and drop request for quotation files onto the quotations list or kanban view. This fixes a broken upload path that showed an error instead of importing the file, reducing friction in quotation processing.
Original PR description
Versions -------- - saas-19.4+ Steps ----- 1. Install Sales (with `sale_management`); 2. open Sales > Orders > Quotations; 3. drag a request for quotation file onto the list. Issue ----- The drop…
Versions -------- - saas-19.4+ Steps ----- 1. Install Sales (with `sale_management`); 2. open Sales > Orders > Quotations; 3. drag a request for quotation file onto the list. Issue ----- The drop zone appears, but dropping the file only raises a "Could not upload files" notification. Same in the kanban view. Cause ----- `UploadDropZone.onDrop` hands the dropped files to the closest input matching `.document_file_uploader.o_input_file`. Commit d1742c42 moved the RFQ upload into the cog menu with a template overriding `account.DocumentFileUploader`'s, but without forwarding `fileUploadClass` to the `FileUploader`, so that input never carried the class. This went unnoticed because `saleFileUploadListView` inherited `account.FileuploadListView.Buttons`, which renders `account.DocumentViewUploadButton` and therefore a matching (hidden) input the drop zone could fall back on. Commit 00545bdd patched `saleFileUploadListView` and `saleFileUploadKanbanView` to use the `sale_management` button templates, which inherit `web.ListView.Buttons` instead, removing that input and leaving the page with no drop target at all. Solution -------- Set `fileUploadClass` on the cog menu's `FileUploader`. The drop zone now targets the RFQ uploader itself rather than the account one, so dropped files go through `QuotationRequestUploader.getResModel` as intended. opw-testing-day-v20
This change fixes Belgian payroll salary calculations when an employee has multiple contract versions in the same month. It ensures the 50% rule is applied based on paid versus unpaid hours for each period, helping prevent incorrect basic salary amounts on payslips.
Original PR description
there is a problem that we If we have two versions in the same month, we always follow the second option of the 50% rule. This is because the theoretical hours are calculated for the whole month, but the paid amount is calculated separately for each version. We fixed this by checking If unpaid hours > paid hours, salary is calculated by multiplying the hourly rate by the total hours. Otherwise, salary is calculated as the base wage minus (unpaid hours × hourly rate). And return back the test to what exist before the task with id : 6260378 task Id: 6515980 Forward-Port-Of: odoo/enterprise#129985
Rental orders can now be created correctly when an optional rental product is added, even if the website rental feature is not installed. The system now handles rental dates consistently before calculating prices, preventing a crash and improving order processing reliability.
Original PR description
Problem: When the website_sale_renting module is uninstalled and you try to create a rental order with a product that has an optional rental product, the system crashes with a traceback. This happens…
Problem: When the website_sale_renting module is uninstalled and you try to create a rental order with a product that has an optional rental product, the system crashes with a traceback. This happens because the rental start and end dates are passed to the product pricing calculations as plain text strings instead of proper date formats, which breaks the timezone math. Solution: This commit ensures that the rental start and end dates are converted into proper datetime formats before any duration or pricing calculations occur, preventing the error and allowing the products to be added to the rental order smoothly. Steps to reproduce(runbot v18): 1. Install the Rental (sale_renting) and Website modules. 2. Uninstall the website_sale_renting module. 3. Create a rental product and configure another rental product as its optional product. 4. Open the Rental application and try to add the created product to a rental order. 5. A traceback is raised while calculating the rental price. opw-6485776 Forward-Port-Of: odoo/enterprise#130966 Forward-Port-Of: odoo/enterprise#128668
This fixes an error that prevented users from customizing certain Kanban cards in Odoo Studio, such as Project task cards. Users can now add or remove fields in these views without the save operation failing.
Original PR description
Issue: Editing the Project task Kanban with Studio fails with an "element cannot be located in parent view" error. This affects Kanban views whose card architecture is provided through `card_id`.…
Issue: Editing the Project task Kanban with Studio fails with an "element cannot be located in parent view" error. This affects Kanban views whose card architecture is provided through `card_id`. Adding or removing a field produces an XPath targeting the inlined `<card>`, but that node cannot be found when the Studio customization is saved. Steps to reproduce: * Open Project > Tasks > My Tasks in Kanban view. * Open Studio. * Add or remove a field from the card. Cause: The client receives a postprocessed Kanban architecture in which the view referenced by `card_id` has already been appended as a `<card>` node: https://github.com/odoo/odoo/blob/1aa1f1967c7b9c8fd2c941fbc0c7b4c563698e46/odoo/addons/base/models/ir_ui_view.py#L3138-L3140 Studio therefore generates paths containing `/kanban/card`. However, Studio normalization applies those paths to the pre-postprocessed architecture, which still contains only the `card_id` attribute: https://github.com/odoo/enterprise/blob/42aa8fafdef159476d708f91467cba6f7fb2c6d3/web_studio/models/ir_ui_view.py#L667-L671 As `<card>` does not exist in that source tree, the inheritance engine cannot locate the target. Solution: We need to inline the referenced card before applying or normalizing Studio customization specifications. This makes Studio operate on the same architecture shape that was presented to the client. The `card_id` attribute is then removed from that temporary source to prevent regular post processing from appending the card a second time. The behavior is limited to Studio customization views and is a no op for Kanban views without `card_id`, preserving the existing inheritance flow for other views. opw-6445398
Validated time off records now keep their calendar blocking entry when dates are shortened, instead of deleting it. This prevents final payslips from incorrectly counting approved time off as worked attendance after an employee departure date changes the leave period.
Original PR description
Problem ------- Fixes bug caused by PR odoo#249527. When a validated time off's dates are shortened while it stays validated (e.g. when the employee's departure date falls int he middle of the…
Problem ------- Fixes bug caused by PR odoo#249527. When a validated time off's dates are shortened while it stays validated (e.g. when the employee's departure date falls int he middle of the leave), the linked resource.calendar.leaves record was unconditionally unlinked. To reproduce: 1. Create and validate a time off request covering a whole month. 2. Register the employee's departure with a departure date in the middle of that time off. 3. Generate the employee's last payslip. The leave is correctly cut at the departure date, but since it never leaves the `validate` state, it never goes through `_validate_leave_request()` again, so its resource.calendar.leaves record is never recreated. The days that were covered by the deleted entry are no longer blocked in the employee's resource calendar, so the payslip's worked day lines (computed from resource.calendar.leaves) count them as attendance instead of time off. Cause ----- `hr.leave.write()` removed the resource.calendar.leaves record any time either the state changed away from `validate` or the leave's dates changed, regardless of whether the leave remained validated. Date-only changes on an already-validated leave never re-trigger validation, so the entry was not recreated. Solution -------- Only remove the resource.calendar.leaves record when the leave actually loses its validated state. When a validated leave's dates change but it stays validated, amend the existing resource.calendar.leaves record in place instead, falling back to creating one if none exists. Related PR: odoo#249527 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#287862 Forward-Port-Of: odoo/odoo#286465
Managers can now open employee records and expense documents without being blocked by unrelated company access restrictions in the organization chart or expense approval flow. Expense team approvers also get more flexibility to create expenses for their subordinates across company boundaries, reducing duplicate employee setup and day-to-day admin friction.
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
The Navarra SII tax reporting service address has been updated because the previous web service link stopped working. This helps Spanish localization users continue submitting required tax information to the Navarra tax agency without connection failures.
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
Dropshipped purchases are now excluded from average cost calculations, preventing them from changing product costs when goods never enter company inventory. This avoids incorrect negative stock valuation balances and keeps inventory accounting aligned after related bills and invoices are confirmed.
Original PR description
stock_*: stock_account, stock_dropshipping, stock_landed_costs **Problem:** dropship moves impact the average cost of products which can lead to negative balance in stock valuation account **Steps to…
stock_*: stock_account, stock_dropshipping, stock_landed_costs **Problem:** dropship moves impact the average cost of products which can lead to negative balance in stock valuation account **Steps to reproduce:** On a new db with no demo data and stock_dropshipping, sale_management and accountant module installed (bug also reproducible in runbot with same steps, but it's easier to see the negative impact on accounting on a new db) : 1) enable dropshipping 2) create a storable product with average perpetual category 3) in the purchase tab set a vendor with a price of 10 4) in the inventory tab select the dropship route 5) create PO for 1 unit @ 5, validate receipt and confirm bill 6) confirm a SO for 1 unit of the product 7) confirm linked PO and validate dropship move 8) confirm invoice and vendor bill -> see how the standard price is now 7.5 9) remove dropship route from the inventory tab of the product 10) confirm a SO for 1 unit of the product 11) validate delivery and confirm invoice 12) open 'inventory valuation' view **Current behavior:** the initial balance of stock valuation is -2.5 **Expected behavior:** it should be 0 (there shouldn't be a negative initial balance if all invoices and bills are confirmed) **Cause of the issue:** The issue happens after step 8) The problem is that the dropship has an impact on the average price of the product but not on the accounting. Before the dropship we have 1 unit in stock @ 5 and the stock valuation account has a balance of 5 (from the bill), so all is good. The dropship then changes the standard price to 7.5. That's because currently, in _run_average_batch() the dropship move first impacts average cost like an incoming move with a value of 10 (at this point we have 1 move @ 10 and 1 @ 5 so average cost is 7.5) https://github.com/odoo/odoo/blob/bcaa8b6b00fa1f16513d680c5187fd424bc7cf8c/addons/stock_account/models/product.py#L493-L500 https://github.com/odoo/odoo/blob/bcaa8b6b00fa1f16513d680c5187fd424bc7cf8c/addons/stock_account/models/product.py#L505-L510 and then it impacts the value as a regular outgoing move (meaning it leaves the inventory at the average cost of 7.5) and does not impact the average cost (which is the basic behaviour of outgoing moves) https://github.com/odoo/odoo/blob/bcaa8b6b00fa1f16513d680c5187fd424bc7cf8c/addons/stock_account/models/product.py#L515-L517 https://github.com/odoo/odoo/blob/60b5b99d569dbe183ef9c980217ccfe9bf243756/addons/stock_account/models/product.py#L522 Therefore after step 8), the standard price is 7.5 and we have a unit in stock so, in the inventory valuation view the ending stock is 7.5$. But the dropship did not impact the stock valuation account so the initial balance is still 5$ and we have lines with credits and debits of 2.5$ in the the stock variation section. After steps 9 to 12, both the initial balance and ending stock decrease by 7.5 (which is expected), leading to a negative initial balance in stock valuation. **fix:** We don't take into account the stock move from dropships in the avco computation **tests:** The fix requires modifications in a few tests: - test_dropship_bill_standard_price_update checks that the bill of a dropship move impacts the standard price, so we delete this test - test_lot_normal_3, the asserts on the total_value still make sense but not those on standard_price - test_dropship_kit_bom_updates_component_standard_price test_average_cost_dropship_in_negative_quantity, test_out_move_validate_as_stock_user: standard price should not be impacted by dropship Task 6515358 Forward-Port-Of: odoo/odoo#285576
Fixes an error that could stop the Forum app from being installed when website cookie and third-party tracking blocking options were enabled. The website now reuses the existing cookie permission setting during page rendering, avoiding a server error and improving installation reliability.
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-6477558Mexican payroll calculations now correctly protect minimum wage employees from IMSS deductions even when they receive extra pay such as commissions. The fix also prevents rounding discrepancies in payroll schedule days and ensures eligible employees are not wrongly excluded from employment subsidies due to unpaid absences.
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
Upgrading the Website Sale Collect module will no longer turn the Pay on Site payment option back on or overwrite its related settings. This prevents customers from being offered an unpaid checkout option that the merchant had intentionally disabled.
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 X/Z reports now show category totals that match the displayed order lines when taxes are configured as included or excluded. This avoids confusing discrepancies for cashiers and managers while preserving the existing daily sales report behavior.
Original PR description
The per category totals on X/Z reports would use the same total computation that we use for the daily sales report, but the order lines are shown tax included or excluded based on the config for the database. So we could have a category showing a total which is different from the sum of the order lines on that category. This only shows on X/Z reports, but the daily sale report uses the same data, and always displays the tax excluded prices So here I added an extra 'total_paid' field on the report data, which is used on X/Z reports generation, so the category total matches the order lines on X/Z reports, while keeping the original total which is used for daily reports Task-[6538575](https://www.odoo.com/odoo/project/1737/tasks/6538575) Enterprise PR-[#131110](https://github.com/odoo/enterprise/pull/131110) --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#286954
Restaurant POS orders shared between trusted points of sale now keep the correct session information during synchronization. This prevents valid payments from being blocked when each POS location uses its own 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
This fix prevents preparation tickets from being printed twice when a kiosk uses an Obox-connected printer. It ensures printers handled server-side are not also selected for local device printing, 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 Community PR: https://github.com/odoo/odoo/pull/287382
Kiosk orders now correctly print preparation tickets when a preparation printer is configured. This ensures kitchen or preparation staff receive the order information at the right time, while avoiding duplicate printouts.
Original PR description
Steps to reproduce: - Configure a kiosk with a preparation printer. - Order and confirm a product from the kiosk. Issue: The preparation ticket is not printed. `_send_order` creates the preparation records before the confirmation page computes the preparation changes, leaving nothing to print. Compute and store the changes before sending the order, then consume them before printing to prevent duplicate tickets. Add `localPreparationPrinters` to exclude printers that should not print locally. Task-6537765 Enterprise PR: https://github.com/odoo/enterprise/pull/130938
The Point of Sale now safely ignores coupon or loyalty card records that were removed during an order. This prevents the customer list from crashing after a coupon reward is removed, helping cashiers continue sales without interruption.
Original PR description
Steps to reproduce: - A coupon program with a coupon assigned to a customer - In the PoS, set that customer on the order - Enter the coupon code, its reward line is added - Remove the reward line -…
Steps to reproduce: - A coupon program with a coupon assigned to a customer - In the PoS, set that customer on the order - Enter the coupon code, its reward line is added - Remove the reward line - Click the customer button Issue: The customer list does not open. The PoS crashes with "TypeError: Cannot read properties of undefined (reading 'id')" raised while rendering PartnerLine. Cause: `partnerId2CouponIds` maps a partner to the ids of its `loyalty.card` records. It is filled at boot and on every `loyalty.card` create, but nothing ever removes an id from it: the models only trigger a create event. Removing the reward line of a code activated coupon deletes that card from the local models (`_setValue` in the order summary), so its id stays in the map while the record is gone. `getLoyaltyCards` pushed `models["loyalty.card"].get(id)` unconditionally, hence an `undefined` entry in the list the PartnerLine template iterates over with `t-key="_loyaltyCard.id"`. Fix: Skip the ids whose record no longer exists. The partner keeps its remaining cards, and the path where no card was deleted is unchanged. The stale id is not pruned from the map: it is reached through the reactive store proxy, and mutating it there would notify subscribers in the middle of a render. opw-6517564 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#287503 Forward-Port-Of: odoo/odoo#286971