Tuesday, September 22, 2026
24 changes · saas-19.2
New functionality added to Odoo
Adds Swiss payroll options that keep an employee's net pay unchanged when daily allowances are paid by an insurer. Employers can mark these allowances as net-compensated, and the payslip automatically calculates the adjustment needed while preserving manual overrides.
Original PR description
Daily allowances paid by an insurance (sickness, accident, maternity, military service, disability, loss of earnings) are entered as third party payments: the automatic 2050 line deducts them from…
Daily allowances paid by an insurance (sickness, accident, maternity, military service, disability, loss of earnings) are entered as third party payments: the automatic 2050 line deducts them from the payslip so the employee is not paid twice, and takes them out of the insurance bases. Because that insurance money is free of social contributions, the deductions no longer match a normal month and the final net drifts away from what the employee normally receives. Some employers promise the employee the exact same net as if nothing had happened. This commit adds a "(Net compensation)" variant of each daily allowance wage type to express that promise: when one of them is used, the payslip computes wage type 4900 (Net/Gross compensation) on its own, with the amount that brings the final net back to exactly what it would be without the allowance lines. The employer takes over, or gets back, the whole difference in contributions and source tax. There is no direct formula for that amount, since the 4900 line changes its own deductions (source tax brackets, insurance ceilings, 5 cents rounding). The payslip is therefore recomputed a few times with different amounts until the net matches, each attempt running inside a database savepoint that is rolled back afterwards, so nothing of those attempts is kept. A manual 4900 input keeps priority over the automatic computation, the same way a manual 2050 input does. The new wage types get the same accounting mapping as their normal counterparts. Forward-Port-Of: odoo/enterprise#128243
Enhancements to existing features
Repeated barcodes and QR codes in large reports are now reused during report generation instead of being recreated each time. This can dramatically reduce processing time for long documents with repeated codes, making report generation much faster without changing the report output.
Original PR description
### Description of the issue/feature this PR addresses: Reports with hundreds of pages and repeated barcodes or QR codes (same document number, same lot code on every page) re-run reportlab's…
### Description of the issue/feature this PR addresses: Reports with hundreds of pages and repeated barcodes or QR codes (same document number, same lot code on every page) re-run reportlab's createBarcodeDrawing and PNG encoding on every occurrence, even when type, value and options are identical. ### Current behavior before PR: Every call to ir.actions.report.barcode() re-renders the barcode/QR from scratch, regardless of whether an identical (type, value, options) combination was already rendered earlier in the same report. On a 900-page report with 4 barcodes/page, this dominates generation time. Benchmark, 900 pages x 4 barcodes/page, 4 distinct combos, 3600 calls: 19.131s. ### Desired behavior after PR is merged: The pure rendering step (createBarcodeDrawing + mask + PNG encoding) is extracted to a module-level function keyed on (barcode_type, value, options, mask) and wrapped with functools.lru_cache. Repeated barcodes reuse the cached PNG instead of re-rendering. Same benchmark after the fix: 0.020s (946.7x speedup, cache_info hits=3596 misses=4). --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#289261 Forward-Port-Of: odoo/odoo#289135
Resolved issues and error corrections
Manufacturing order overviews now calculate costs correctly even when related work orders do not have an employee assigned. This prevents misleading totals in production summaries and gives managers a more reliable view of manufacturing costs.
Original PR description
Description of the issue/feature this PR addresses: Resolves an issue where Manufacturing Order (MO) summary calculations produced incorrect totals when associated Work Orders had no assigned employee. Current behavior before PR: <img width="2419" height="802" alt="mo-wo-employee-fix-before" src="https://github.com/user-attachments/assets/9a0d64b4-2ca3-4732-9ac7-39673e0ae319" /> Short-circuiting the logic when a work order has no associated employee causes an error in the overview's cost calculation. Behavior after PR: <img width="2389" height="641" alt="mo-wo-employee-fix-after" src="https://github.com/user-attachments/assets/f2b67e84-c149-4d05-be30-f834e62e0882" /> By updating it to no longer short-circuit it now correctly calculates the cost of the manufacturing order. opw-6464637 Forward-Port-Of: odoo/enterprise#131181 Forward-Port-Of: odoo/enterprise#128465
Users will now see a persistent notification when the IAP service is down. This helps them understand that related actions may not work temporarily due to a service outage rather than a problem with their setup.
Original PR description
We wanted to let the user know when the IAP server is down with a sticky notification. Task-6570098 Forward-Port-Of: odoo/odoo#289599 Forward-Port-Of: odoo/odoo#288098
This fixes how Peruvian SUNAT exchange rates are saved so they match the official rate expected for each day. It prevents newly fetched rates from being dated in a way that could cause mismatches in tax reporting, though existing incorrect rates are not automatically corrected.
Original PR description
In this commit https://github.com/odoo/odoo/pull/231948 the base functionality of exchange rate fetching was changed in order to have a consistent exchange rate value per day. The problem is that in l10n_pe, SUNAT already does this, setting each day's official exchange rate to be the closing rate of the previous day. Because SUNAT expects the exchange rate being sent to it to be the same as the one generated in the morning, the new logic is defaulting to a mismatched rate instead. This commit changes how new currency rates are stored through SUNAT by dating them one day in the past to align with the new architecture. This will not realign existing currency rates that do not work with the change. This is a mirror of a similar adjustment that was made for Banxico in Mexico here: https://github.com/odoo/enterprise/pull/118999 opw-6468065 Forward-Port-Of: odoo/enterprise#131483
Employees without an active contract or version who are on leave counted as working time are no longer automatically checked out by the attendance scheduler. This prevents incorrect attendance records and preserves accurate working time tracking for cases such as homeworking or attendance-type leave.
Original PR description
## Short functional explanation of the error When an employee doesn't have an active version, and they're on a leave considered working time. When we run the scheduled action `Attendance:…
## Short functional explanation of the error When an employee doesn't have an active version, and they're on a leave considered working time. When we run the scheduled action `Attendance: Automatically check-out employees`, the employee is checked out after the defined tolerance time. The employee shouldn't be checked out, as they're considered working at the time. ## Reproduction steps 1. Create an employee without contract. 2. Go to Attendances > Configuration > Settings. Check Automatic Check-out. Select based on Tolerance and set the Tolerance at 2 hours. 3. Go to Time off > Management > Allocations and click on New. As Time Type, select a type considered as Working (like Homeworking or Attendance), set your employee and an Allocation duration. Click Validate. 4. Click on Management tab > Time off. Click on New and use for Time Type and Employee fields the same values you used on the allocation. Set the date as today. 5. Go to Attendances and click on New. Select the employee you're working with. Set the Check in at today, 3 hours before your local time. Remove the check out. 6. Enable the debugger and go to Scheduled Actions. Search for `Attendance: Automatically check-out employees` and click on Run manually. 7. Go back to Attendances. ### Expected Behavior The attendance of the employee is still ongoing as they're on a leave considered as working time. ### Unexpected Behavior The attendance of the employee has ended, and its duration is equal to the tolerance time defined in the settings. ## Origin of the issue This bug doesn't occur when an employee has an active version, as we retrieve the work intervals taking into account the leaves of the employee: https://github.com/odoo/odoo/blob/50298287733ce4ed8c5372495778e69cac5e114d/addons/hr/models/hr_employee.py#L1804 However, we don't take such leaves in consideration when an employee doesn't have a version: https://github.com/odoo/odoo/blob/50298287733ce4ed8c5372495778e69cac5e114d/addons/hr/models/hr_employee.py#L1780-L1788 __ task-6453044 Forward-Port-Of: odoo/odoo#289607 Forward-Port-Of: odoo/odoo#282477
Closing an AI live chat conversation now ends the session without unexpectedly opening a separate chat window. This improves the website visitor experience by avoiding confusing follow-up popups after the conversation is closed.
Original PR description
Closing the chat with the ai agent on the ai_livechat snippet results in a chatwindow popping up which gives a bad user experience. Steps to reproduce: - Log in as Mitchell Admin. - Go to website. -…
Closing the chat with the ai agent on the ai_livechat snippet results in a chatwindow popping up which gives a bad user experience. Steps to reproduce: - Log in as Mitchell Admin. - Go to website. - Click on edit and choose `Contact & Forms`. - Add the AI Livechat website snippet. - From the snippet options, add an AI Agent and choose a livechat team. Make sure that Mitchell Admin is configured as an operator for that livechat team (livechat channel). - Click on save. - Open an incognito tab. Log in as Marc Demo. - Go to Website. - Type a message inside the `ASK AI` text area and press enter. - Wait until you receive a response and then click close. - A chat window will popup with the messages of the conversation with the AI along with a message saying `Visitor has left the channel`. This happens because `close` button will call `closeConversation` => `livechatService.leave()` => `visitor_leave_session` => `_close_livechat_session` that posts a message that the visitor has left the channel. This commit solves the issue by setting the user who left the channel as the author of the `visitor left chat` message instead of OdooBot. Forward-Port-Of: odoo/odoo#258836
This fix ensures Point of Sale refunds keep the manually calculated tax amounts from discounted orders instead of recalculating them incorrectly. It prevents session closing failures caused by small rounding differences in refund accounting entries.
Original PR description
Closing a session fails with an unbalanced journal entry when it holds the refund of an order whose global discount falls on a specific rounding. Steps to reproduce: - configure a 20% tax and sell 2…
Closing a session fails with an unbalanced journal entry when it holds the refund of an order whose global discount falls on a specific rounding. Steps to reproduce: - configure a 20% tax and sell 2 x 2.12 in the PoS - apply a 10% global discount, pay the order, then refund it - close the session The tax of the discount line cannot be recomputed from its price: the UI splits the rounded tax included amount, 0.51 here, into 0.42 of base and 0.09 of tax, where 0.42 taxed at 20% would give 0.08. It pins both amounts in 'extra_tax_data' with the quantity they were computed for, and they are used again only if the line still matches that snapshot. Refund orders reverse that quantity, so the discount line the UI builds for them no longer matches and its taxes are computed again. Reverse those amounts only when the quantity they were computed for has the opposite sign to the one now used. Refunds made with "Return Products" copy the line with its quantity already negated, so reversing them as well would break those instead. opw-6533657 Forward-Port-Of: odoo/odoo#288573
The point of sale product search now includes products whose variants have a single attribute value, such as Size M. This helps cashiers find the right products more reliably and avoids missing items during checkout.
Original PR description
In the POS search bar, searching for an attribute value (e.g., "M") returned products with multi-value attributes (e.g., Size: M - L), but omitted products with single-value attributes (e.g., Size: M). This occurred because backend `_compute_display_name` filters out single-value attribute lines from variant display names. As POS `searchString` relied on `display_name`, `name`, `default_code`, and `barcode`, the attribute value name was missing from single-value variant search strings. task-id: 6431718 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#279428
This fixes a website setup failure that could block users when creating a new website with eCommerce and design themes installed. Odoo now checks all existing website themes when adding required snippets, so the configurator can complete without missing-template errors or misleading operation popups.
Original PR description
# How to reproduce - Start a new db with the design-themes addons - Install the eCommerce module - Skip the first website configurator - Go to Website > Configuration > Settings > New website - Go…
# How to reproduce - Start a new db with the design-themes addons - Install the eCommerce module - Skip the first website configurator - Go to Website > Configuration > Settings > New website - Go through all the steps as usual # The issue At the last step, you will get, depending on the version either : - A traceback, pointing out the fact that a template is missing - A popup signaling that another module operation is being processed, even though there are none The configurator won't go through in both cases # Cause When calling `configurator_apply`, we will try to get the content of every snippet specific to theme installed with the website : https://github.com/odoo/odoo/blob/ef6765208d400aa1252cd4dff5bc074ea551bbc2/addons/website/models/website.py#L910 But, when trying to find the 'website_sale.configurator_homepage_s_dynamic_snippet_category_list' view, we find nothing and an error is thrown. This view should be created by the `_generate_primary_snippet_templates` function, which is called when loading any theme module or `website` or `website_sale`: https://github.com/odoo/odoo/blob/ef6765208d400aa1252cd4dff5bc074ea551bbc2/addons/website/models/ir_module_module.py#L594 The issue is that, to know which snippets to install, that function relies on `get_current_website()`: https://github.com/odoo/odoo/blob/ef6765208d400aa1252cd4dff5bc074ea551bbc2/addons/website/models/ir_module_module.py#L694 Which relies on multiple things to find the current website, notably the current request's session : https://github.com/odoo/odoo/blob/ef6765208d400aa1252cd4dff5bc074ea551bbc2/addons/website/models/website.py#L1374-L1381 Since [this commit], the request is not kept when creating a new registry, which is done when loading a module. `get_current_website()` fallbacks on the first website's theme, which is the default theme (since we skipped the first configurator). That theme does not have the `website_sale` snippet in its manifest, contrary to others. # Proposed solution Since a new registry is created, we cannot use the context, nor the current request. We also cannot add a parameter to the `_generate_primary_snippet_templates` function since we are in stable (It would not help much anyway because this function is called when loading a module, so giving the current website is not possible). This means that we have to stop relying on `get_current_website()` to differentiate between the two use case : - Installing a theme module - Installing a module with specific snippets We can determine if the module being installed is a theme by looking at its category. If it is not, we assume its the other use case as there should not be any other. In that case, instead of installing the snippets of the theme of the current website, we check the theme of all websites. Later in the code, we take only distincts snippets and check that a corresponding view do not already exist, so there should be no risk of duplicate : https://github.com/odoo/odoo/blob/1f2ea7be8377c906ded75da893d739109db93c4d/addons/website/models/ir_module_module.py#L692 https://github.com/odoo/odoo/blob/1f2ea7be8377c906ded75da893d739109db93c4d/addons/website/models/ir_module_module.py#L712 https://github.com/odoo/odoo/blob/1f2ea7be8377c906ded75da893d739109db93c4d/addons/website/models/ir_module_module.py#L608 Furthermore, I believe it makes more sense to check every website anyway. If the `website_sale` module is installed, and there already exists multiple website with different themes, these themes might require different `website_sale` specific snippets. opw-6517556 [this commit]: https://github.com/odoo/odoo/commit/be8c85b4ccb10d6f16ddea3ab493e4c9aac749e1 Forward-Port-Of: odoo/odoo#285935
Employees with schedules that define only a daily number of hours can now request hourly time off later in the day without it being incorrectly counted as zero hours. This prevents valid time off requests from being rejected when no specific working start or end time is imposed.
Original PR description
Issue: A time off in hours requested after 16:00 cannot be taken by an employee whose working schedule only defines a number of hours per day, without any start and end time. The request is computed…
Issue: A time off in hours requested after 16:00 cannot be taken by an employee whose working schedule only defines a number of hours per day, without any start and end time. The request is computed with a 0:00 hours duration and its approval is rejected with "The following employees are not supposed to work during that period", even though the employee has no imposed working hours that day. Steps to reproduce: * Give an employee a working schedule with no start/end time (not flexible) * Allocate them a time off type expressed in hours * Request that time off from 16:00 to 20:00 and approve it Cause: A duration based attendance states how many hours are worked on a day, never when they are worked, so the intervals built in `_attendance_intervals_batch` place them in an arbitrary block centered on midday, 08:00 -> 16:00 for a full day. A period requested outside of that block intersects no attendance at all, hence a duration of zero and a period considered as non working. https://github.com/odoo/odoo/blob/6b037d0ef4e10120ce76cd21f43e5378a26dd55a/addons/resource/models/resource_calendar.py#L296-L327 Solution: Since the placement of those hours is arbitrary, it should only be kept when it is compatible with the period being computed. When the attendances of a day do not overlap the requested period, slide them, without letting them leave their day, towards that period. Only the first and the last day of a period can be concerned, as the days in between always overlap their centered block, so the working time reported for full days, as well as schedules with explicit start and end times, keep their current behaviour. opw-6446477 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Spanish Mod 349 reporting now keeps refund amounts when those refunds have simply been paid or matched with bank transactions. Amounts are only removed when an invoice or bill is directly matched against a refund, improving report accuracy for affected businesses.
Original PR description
This completes the commit https://github.com/odoo/enterprise/commit/c0d581965bc6dfe626746f9089ef6190acac1375 but for refund. Currently, the refunds were removed from mod349 report if they were reconciled with a payment, bank transaction or any "entry" type move. Now, we only remove the amounts if an invoice/bill was directly reconciled with a refund. opw-6297666
Time Off now displays selected start and end times correctly for languages that use different abbreviations for hours or minutes, such as Dutch. This prevents confusing 12:00 AM displays while keeping the underlying leave request values accurate and visible to users.
Original PR description
## Issue When using a language that does not use `h` for "hours" and/or `m` for "minutes", times are not properly displayed in the Time Off app. For example, changing the `request_hour_to` of a time…
## Issue
When using a language that does not use `h` for "hours" and/or `m` for "minutes", times are not properly displayed in the Time Off app. For example, changing the `request_hour_to` of a time off request in Dutch will always show 12:00 AM, regardless of the time we set.
## Steps to reproduce
1. Install *Time Off* (`hr_holidays`)
2. Change the user language to Dutch
3. In Time Off, create a new time off request
- Time Off Type: Any type with a "Custom Hours" duration type
4. **The times displayed are "12:00 a.m" for both the start and the end of the time off. We can change the times but they will always display 12:00 a.m, even if the proper values are used in the backend.**
## Cause
The displayed time is formatted in the FloatTimeSelectionField component:
https://github.com/odoo/odoo/blob/dcaba2dd99d0553bb3df7b9b5f0b77fec2d53159/addons/hr_holidays/static/src/components/float_time_selection/float_time_selection.js#L45-L62
The getter attempts to parse the provided `unitAmount` (e.g. "13h 14m") by looking for `h` and `m` to determine the hours and minutes. This works fine in multiple languages such as French or English, but it does not work for languages that use other letters for hours and/or minutes (such as Dutch, which uses "u" for hours).
This issue was introduced by https://github.com/odoo/odoo/commit/e80750d20b7ddbd015451a885016151347f3975b, which was correcting an issue where *"Invalid DateTime"* being displayed instead of actual time values.
## Fix
The solution is to use the correct hour and minute identifiers depending on the user language. This is done by the `durationUnitsRegex` function:
https://github.com/odoo/odoo/blob/dcaba2dd99d0553bb3df7b9b5f0b77fec2d53159/addons/web/static/src/core/l10n/time.js#L287-L307
which is then used by the [`parseDuration`](https://github.com/odoo/odoo/blob/dcaba2dd99d0553bb3df7b9b5f0b77fec2d53159/addons/web/static/src/views/fields/parsers.js#L144) to parse a string such as "13u 14m" or "13h 14m".
<img width="394" height="164" alt="6373786" src="https://github.com/user-attachments/assets/d64647f0-4bd7-4f06-bd32-fd3f258fdb63" />
opw-6373786When a project's company is updated after creation, its related document workspace now updates to the same company when all linked projects share it. This keeps project documents aligned with the correct company and avoids workspaces unintentionally remaining company-neutral.
Original PR description
**Steps to Reproduce** - Install the `Documents` and `Project` modules. - Create two companies. - Create a project using the `Kanban quick create` option, without setting a company. - A related…
**Steps to Reproduce** - Install the `Documents` and `Project` modules. - Create two companies. - Create a project using the `Kanban quick create` option, without setting a company. - A related workspace is automatically created without a company. - Set a company on the project from the form view. - The company of the related workspace remains unset. **Observation** - Projects created through the Kanban quick-create flow do not have a company assigned at creation time, so their related workspace is also created without a company. - When the project's company is subsequently changed from the form view, the workspace company was not updated. As a result, the workspace and its documents remained without a company. **Solution** When a project's company is changed, all projects linked to the same workspace are checked: * If all projects belong to the same company, the workspace company is updated accordingly. * If the linked projects belong to different companies and the workspace has no company, the workspace remain unchanged and accessible to all companies. * If the linked projects belong to different companies and the workspace already has a company, an error is raised, preserving the existing behavior. Backport of https://github.com/odoo/enterprise/pull/111760 OPW-6487090 Forward-Port-Of: odoo/enterprise#129315
Opening an order from the Sales report is now faster on databases with many sales lines. The system now identifies the related order directly, avoiding a slow report lookup that could delay users.
Original PR description
Versions -------- - 18.0+ Steps ----- 1. On a database with many sale order lines, open Sales > Reporting > Sales and switch to the list view. 2. Click a line to open its order. Issue ----- Opening the order takes seconds. Cause ----- `action_open_order` reads `order_reference` off the `sale.report` view. The view's `id` is `MIN(sale_order_line.id)`, so reading one record filters on an aggregate. PostgreSQL therefore has to aggregate every sale order line and POS order line before keeping one row. Solution -------- The report id is itself a line primary key, so the order can be resolved without reading the view. Add a `_get_order_reference` hook returning the order from the `sale.order.line` record directly. Forward-Port-Of: odoo/odoo#289284 Forward-Port-Of: odoo/odoo#289143
This fix ensures Factur-X electronic invoices include the required customer name even when an invoicing address has no name of its own. It helps prevent compliance validation failures and reduces the risk of rejected electronic invoices.
Original PR description
Description of the issue/feature this PR addresses: The Factur-X Refactoring (https://github.com/odoo/odoo/pull/284123) introduced an error that can result in the generated XMLs not being compliant…
Description of the issue/feature this PR addresses: The Factur-X Refactoring (https://github.com/odoo/odoo/pull/284123) introduced an error that can result in the generated XMLs not being compliant due to the partner name being missing (BT-44) Current behavior before PR: When a contact has an invoicing address without a name set, then the new Factur-X generation does not fall back to the parent name, leaving the corresponding XML entry empty, which is therefore pruned, leaving the Factur-X without a BT-44 and thus failing BR-07 Desired behavior after PR is merged: The new generation method uses the same data source and fallback method as the old Factur-X generator, pulling the `display_name` of the `commercial_partner_id` if the address doesn't have a `name`. This greatly reduces the chance of a generated Factur-X failing BR-07. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#289661 Forward-Port-Of: odoo/odoo#289498
This fixes an issue where accordion sections added to default invoice terms and conditions could break after saving. Businesses can now edit richer terms pages without losing interactive content or encountering errors.
Original PR description
# How to reproduce - Go to Settings > Default Terms & Conditions > Preview - Open the editor - Add a Content Block - Add an Accordion in that Block - Save # The issue The accordion gets messed up and…
# How to reproduce - Go to Settings > Default Terms & Conditions > Preview - Open the editor - Add a Content Block - Add an Accordion in that Block - Save # The issue The accordion gets messed up and trying to add a new item to that accordion raises a traceback # Cause `invoice_terms_html` is an html field with some sanitization enabled : https://github.com/odoo/odoo/blob/c11ca6d10cac99b367078823dafef46808346267/addons/account/models/company.py#L172 This sanitization will remove the accordion snippet's buttons that controls the functionality of the snippet : https://github.com/odoo/odoo/blob/c11ca6d10cac99b367078823dafef46808346267/addons/website/views/snippets/s_accordion.xml#L9 This issue was already addressed by : https://github.com/odoo/odoo/commit/b6b4db5fb5690436a4284f6a22abf9f3b346a324 But it is not enough in the case of the accordion. The buttons will be removed by the lxml clean.Cleaner : https://github.com/odoo/odoo/blob/c11ca6d10cac99b367078823dafef46808346267/odoo/tools/mail.py#L364 # Proposed Solution Disable sanitization entirely, like for blog's content, which can also be edited in the website editor : https://github.com/odoo/odoo/blob/c11ca6d10cac99b367078823dafef46808346267/addons/website_blog/models/website_blog.py#L29 opw-6500378 Forward-Port-Of: odoo/odoo#285255
Credit notes using Spain’s equivalence surcharge are now sent with the surcharge rate as a positive percentage, as required by the tax agency. This prevents TicketBAI/Batuz submissions from being rejected when businesses issue these credit notes.
Original PR description
In credit notes, `TipoRecargoEquivalencia` was multiplied by the reversal sign, producing negative values (e.g. -1.40) that violate the Batuz `Tipo3.2Type` pattern and get rejected by the tax agency (`B4_2000001: cvc-pattern-valid`). Unlike `BaseImponible`/`CuotaImpuesto` (amounts that must be negative), `TipoRecargoEquivalencia` is a percentage and must stay positive, as `TipoImpositivo` does. Steps to reproduce: 1. Configure a Spanish company with TicketBAI (Bizkaia). 2. Create a credit note (out_refund) with an equivalence surcharge tax. 3. Send it to TicketBAI; the send fails with a schema validation error. Task: MT-15939 OPW: https://www.odoo.com/es_ES/my/tasks/6573801 @jco-odoo could you review? It's essential to be able to send to Tbai/Batuz with equivalence surcharge tax. Forward-Port-Of: odoo/odoo#289618
Attendance officers without full Employees app access can now open the Employees menu from the Attendance overview without hitting an access error. The menu now uses an attendance-specific employee view, so managers can review attendance-related employee information without needing broader HR permissions.
Original PR description
A user configured with "Officer: Manage all attendances" but no access to the Employees app can see the "Employees" entry under Attendances > Overview, since that menu only requires Attendance…
A user configured with "Officer: Manage all attendances" but no access to the Employees app can see the "Employees" entry under Attendances > Overview, since that menu only requires Attendance rights. Opening it, however, raises an access error naming the "Current Time Off Type" field. Steps to reproduce: ------------------- * Go to Settings > Users, open a user and set their access rights to: Employees app: no access, Attendances app: "Officer: Manage all attendances" * Log in as that user * Go to Attendances > Overview > Employees > Observation: Error: "You do not have enough rights to access the field "current_leave_id" on Employee (hr.employee). Please contact your system administrator." Why the fix: ------------ The "Employees" menu pointed to `hr.open_view_employee_list_my`, the full HR Employees action (model `hr.employee`, no view_id override), which resolves to the same default kanban view used by the Employees app. That view is extended by hr_holidays to embed `current_leave_id` (and other Time Off fields), declared with `groups="hr.group_hr_user"` on the model. That group restriction is enforced by the ORM on read, independently of whether the field is actually shown in the view, so any attendance-only officer opening the menu had that read rejected outright. The menu now points to `hr_employee_attendance_action_kanban`, an attendance-scoped action already shipped in the module for this exact purpose: it uses the `hr.employee.public` model with a minimal kanban view (name, avatar, job, work location, check-in state), none of which require HR app access, restoring the ability to browse employees from the Attendance app without needing `hr.group_hr_user`. opw-6488582 Forward-Port-Of: odoo/odoo#287140
Invoices now show the correct amount due immediately as lines are added, rather than only after saving. When applying outstanding credit, the system also saves the invoice first so newly added lines are not lost.
Original PR description
Before this commit, the account.move amount due field would only update on record saving (as it depended on line_ids). This caused a temporary out of sync situation between the total and the amount due on the move form view. This commit ensures that the amount due is computed even before saving the record so that out-of-sync situation is avoided. It also solves the following bug: Repro steps: 1) Create a new invoice 2) Add a customer and save the record 3) Add some invoice lines 4) without saving, add a payment from the outstanding credit Issue: The added (unsaved) invoice lines are removed Solution: This commit forces a record save before assigning outstanding credit task-6534794 Forward-Port-Of: odoo/odoo#288894
Fixed an issue that could block users from completing a manufacturing order after generating a lot number and then increasing the production quantity. This helps manufacturers complete valid production adjustments without unnecessary lot/serial number errors.
Original PR description
Currently, when the user tries to produce a Manufacturing Order after generating a lot and increasing the quantity, they get a user error stating that a lot must be assigned. ## Steps to produce: -…
Currently, when the user tries to produce a Manufacturing Order after generating a lot and increasing the quantity, they get a user error stating that a lot must be assigned. ## Steps to produce: - Install Manufacturing - Go to the setting enable Lots & Serial Numbers and Storage Locations - Create a product 'Soda can' which is tracked by lots - Create a bill of material for Soda can with component as 'Metal sheet' - Create a storage location called 'Fridge' with parent location WH/Stock - Create a Putaway rule for the Soda can to be stored in the fridge when it arrives in WH/Stock. - Create and confirm a Manufacturing Order for soda can - Click 'Generate Lot' - Increase the total quantity to be produced from 1 -> 2 - Click 'Produce All' ## Observed Behaviour: ``` Invalid Operation: You need to supply a Lot/Serial Number for product: - Soda can ``` ## Root cause: When the user confirms the Manufacturing Order, `_apply_putaway_strategy` is called through: ``action_confirm -> _action_assign -> _apply_putaway_strategy`` This happens for both finished-product move lines and component-product move lines. It updates the finished product move lines' destination location to `WH/Stock/Fridge` at: https://github.com/odoo/odoo/blob/fd06c4df5889e23cfb701c12d743b5652141a111/addons/stock/models/stock_move_line.py#L288-L292 However, the `move_finished_ids` destination location remains` WH/Stock`. After the user generates a lot and increases the production quantity using the wizard, `change_prod_qty()` updates the finished move's demand and re-reserves it through `_update_finished_moves()`: https://github.com/odoo/odoo/blob/f586c1dad0b9a7653c45d1f82a9c3989a287c641/addons/mrp/wizard/change_production_qty.py#L77 This calls `_action_assign` on the finished move: https://github.com/odoo/odoo/blob/f586c1dad0b9a7653c45d1f82a9c3989a287c641/addons/mrp/wizard/change_production_qty.py#L49 Within `_action_assign` because finished moves originate from the production location, they bypass the normal reservation logic at: https://github.com/odoo/odoo/blob/708e5ebcd8b3338d3ce2c092aafadfe0f941aa56/addons/stock/models/stock_move.py#L2086 So `_action_assign() `then tries to reuse an existing move line. However, it only searches for move lines whose `location_dest_id` matches the move's destination location (WH/Stock): https://github.com/odoo/odoo/blob/708e5ebcd8b3338d3ce2c092aafadfe0f941aa56/addons/stock/models/stock_move.py#L2108-L2117 The existing move line has `WH/Stock/Fridge` as its destination, so it does not match. As a result, a new move line is created for the finished move: https://github.com/odoo/odoo/blob/708e5ebcd8b3338d3ce2c092aafadfe0f941aa56/addons/stock/models/stock_move.py#L2182 The new move line gets the lot value from the Manufacturing Order: https://github.com/odoo/odoo/blob/f586c1dad0b9a7653c45d1f82a9c3989a287c641/addons/mrp/models/stock_move.py#L557 Later, when Produce All is triggered,` button_mark_done()` calls` _post_inventory()`: https://github.com/odoo/odoo/blob/708e5ebcd8b3338d3ce2c092aafadfe0f941aa56/addons/mrp/models/mrp_production.py#L2236 Because the finished move already has a `lot_id`, the condition below is not satisfied: https://github.com/odoo/odoo/blob/708e5ebcd8b3338d3ce2c092aafadfe0f941aa56/addons/mrp/models/mrp_production.py#L1929-L1933 Therefore, the `lot_id` is not assigned to the move line that was created earlier. When `button_mark_done()` validates the finished move lines, it finds that one move line has no lot assigned and raises an 'Invalid Operation' error. ## Solution: Allow the user to produce the Manufacturing Order after changing the quantity. Increasing the quantity is a valid operation, so the user should not be blocked from completing the production afterward. opw-6563498 Forward-Port-Of: odoo/odoo#289171
Users can now click amounts in the aged receivable or payable reports when using an “Open On” date filter without triggering an error. This restores access to the underlying journal item details needed for reviewing outstanding balances.
Original PR description
Currently, an error occurs when a user clicks on a cell in the aged partner balance report. **Steps to Reproduce:** - Install the `account_reports` module. - Go to `Invoicing` > `Reporting` >…
Currently, an error occurs when a user clicks on a cell in the aged partner balance report. **Steps to Reproduce:** - Install the `account_reports` module. - Go to `Invoicing` > `Reporting` > `Partner Reports` > `Aged Receivable`. - Click on the `date filter`, select `Open On`, and set `any date`. - Click on any `amount` in the `Total Aged Receivable line`. `TypeError: the JSON object must be str, bytes or bytearray, not dict` After the [recent commit] that adds the context to the action, when preparing the action to open the journal items corresponding to the selected cell in the aged partner balance report, the context is added to the action [1]. When an "Open On" date is set, the code attempts to convert the context using json.loads() before adding the search_default_open_on key to it [2]. but, the context is already a dictionary, which raises the error [3]. This commit ensures that, since the action context is already a dictionary, the search_default_open_on key and its value are added directly to the context. [recent commit]: https://github.com/odoo/enterprise/commit/9678a1b987a5aa87922b72d972dd7f73a689afee [1]- https://github.com/odoo/enterprise/blob/8fceecea541564a9877babfc31b7e0bf896573a6/account_reports/models/account_aged_partner_balance.py#L357-L359 [2]- https://github.com/odoo/enterprise/blob/8fceecea541564a9877babfc31b7e0bf896573a6/account_reports/models/account_aged_partner_balance.py#L362 [3]- https://github.com/odoo/enterprise/blob/8fceecea541564a9877babfc31b7e0bf896573a6/account_reports/models/account_aged_partner_balance.py#L361 sentry-7685491449 Forward-Port-Of: odoo/enterprise#129142
Opening image attachments linked to products could trigger a client error in the Attachments screen. This fix ensures the attachment selector receives the expected data format, preventing the crash and keeping attachment management reliable.
Original PR description
### Steps to Reproduce: 1. Go to Sales > Products and select a product 2. Upload an image for the product 3. Go to Settings > Technical > Attachments 4. Click on one of the image attachments 5.…
### Steps to Reproduce: 1. Go to Sales > Products and select a product 2. Upload an image for the product 3. Go to Settings > Technical > Attachments 4. Click on one of the image attachments 5. Observe the Client Error: "Uncaught Promise > Invalid props for component 'Many2One': 'domain' is not a function" ### Issue: In `attachment_patch.js`, the domain for the `Many2OneReferenceField` is overridden when an attachment is linked to another attachment. The patch calculates the domain and incorrectly assigns a static Array (using `.toList()`) to `props.domain`. In Odoo 19, the `Many2One` component enforces strict prop validation (caught when Owl's debug mode is enabled during testing), causing a crash because it expects the domain prop to be a function. ### Solution: We can just wrap the resulting domain Array in an arrow function so that it evaluates dynamically when the `Many2One` component mounts. Also added a unit test to explicitly cover this patched scenario. The test manually applies a local patch to `Many2OneReferenceField.prototype` to accurately replicate the environment and verify the fix. opw-6563954 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#287951
Peruvian PLE sales and purchase ledgers now exclude ISC tax amounts from taxable base columns when ISC affects later taxes. This prevents overstated ledger values and keeps tax ledgers aligned with electronic invoices sent to SUNAT.
Original PR description
When a tax affects the base of the subsequent ones — `include_base_amount`, which is how the ISC is set up in Peru — Odoo links the tax line it generates to those subsequent taxes, so the ISC tax…
When a tax affects the base of the subsequent ones — `include_base_amount`, which is how the ISC is set up in Peru — Odoo links the tax line it generates to those subsequent taxes, so the ISC tax line ends up carrying the IGV in its `tax_ids`. The PLE ledgers build their base columns by summing every move line related to a given tax group, so that ISC tax line was added to `base_igv` as well and the ISC ended up reported twice: once inside the taxable base and once in its own column. A line of 1000.00 with 100.00 of ISC and 198.00 of IGV was declared with a base of 1100.00 instead of 1000.00. `l10n_pe_edi` already subtracts the ISC from the `cbc:TaxableAmount` of the CPE sent to SUNAT, so the electronic invoice and the ledger disagreed on the very same document. ### What changed The base taxes are now joined only for base lines, instead of repeating the condition on each of the twelve `base_*` columns. Besides keeping tax lines out of the base columns, this also stops a tax line from being counted once per subsequent tax: an ISC followed by both the IGV and the ICBPER produced two rows in the join, and its own column was reported twice. ### Scope `_get_ple_report_data` is shared, so this covers the sales ledger 14.1 and the purchase ledgers 8.1 and 8.2. A configuration where no tax affects the base of a later one is unaffected: the standard Peruvian chart template has no purchase ISC, so ordinary IGV-only purchases report exactly the same values as before. ### Tests - `test_sale_report_isc_base`: an ISC preceding the IGV, on an invoice and on a credit note; the base column excludes the ISC. - `test_sale_report_isc_reported_once`: an ISC followed by the IGV and the ICBPER is reported once. Both fail without the fix. Supersedes odoo/enterprise#128300, which targeted 19.0. Forward-Port-Of: odoo/enterprise#131475