Tuesday, September 22, 2026
25 changes · saas-19.4
Resolved issues and error corrections
This fixes Peruvian PLE sales and purchase ledgers so selective consumption tax is not incorrectly included in taxable base amounts. It prevents overstated tax bases and keeps ledger reporting 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#132106 Forward-Port-Of: odoo/enterprise#131475
Currency rates fetched from SUNAT for Peru are now dated to match SUNAT’s official daily rate timing. This prevents mismatches when reporting exchange rates to SUNAT, helping Peruvian localization users stay aligned with official requirements.
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
Vendor bill lines now only allow users to select purchase order lines from confirmed purchase orders. This prevents draft or unconfirmed orders from being accidentally linked to bills, improving purchasing and billing accuracy.
Original PR description
Issue Before This Commit: ========================= Currently, the Purchase Order Line field on the vendor bill line allows users to select purchase order lines regardless of the purchase order…
Issue Before This Commit: ========================= Currently, the Purchase Order Line field on the vendor bill line allows users to select purchase order lines regardless of the purchase order state. As a result, lines from purchase orders that are not confirmed can also be selected and linked to vendor bill lines. Steps to Reproduce: =================== - Install Purchase and create an RFQ. - Create a vendor bill for the same vendor. - Add a bill line and enable the Purchase Order column. - Open the dropdown of the Purchase Order Line field. - Observe that lines from unconfirmed purchase orders are available for selection. Cause of the Issue: ==================== This [PR](https://github.com/odoo/odoo/pull/266003) made Purchase Order lines selectable directly on bill lines, but the selection domain did not restrict the lines to confirmed purchase orders. Therefore, lines from unconfirmed purchase orders remained available for selection. After This Commit: ================== Only Purchase Order lines from confirmed purchase orders are selectable on vendor bill lines.
Opening an email message that contains a code block in the chatter no longer triggers an error. The mail app now skips code syntax highlighting for incoming email bodies, avoiding a display crash while keeping the message readable.
Original PR description
**Steps to reproduce:** - Receive a mail with an embedded code block - Open a chatter containing this message - Error: "Cannot mount a component on a detached dom node" **Issue:** Mail message are…
**Steps to reproduce:**
- Receive a mail with an embedded code block
- Open a chatter containing this message
- Error: "Cannot mount a component on a detached dom node"
**Issue:**
Mail message are rendered with a shadow dom body and isolated from surrounding styling.
```xml
<div class="o-mail-Message-shadowBody overflow-x-auto" t-if="this.message.message_type and this.message.message_type.includes('email')" t-custom-ref="shadowBody"/>
```
Then in `useLayoutEffect` parent element is created for the shadow root and the message body is created:
```js
const bodyEl = createElementWithContent(
"span",
this.message.showTranslation
? this.message.richTranslationValue
: this.props.messageSearch?.highlight(this.message.richBody) ??
this.message.richBody
);
const roots = this.prepareMessageBody(bodyEl) ?? [];
this.shadowRoot.appendChild(bodyEl);
```
But after [1] we have `return this.renderEmbeddedCodeBlocks(bodyEl);` which calls:
```js
const { root, mountPromise } = mountComponent(...);
```
And root creation fails as the element is not on the shadow root yet (due to `validateTarget` and `isAttachedToDocument`).
```js
const root = app.createRoot(Component, { props, env });
```
But even if we have the element directly added on the `shadowRoot` the highlighting styling won't be applied properly without all the needed assets.
**Fix:**
Prevent `ReadonlySyntaxHighlightingComponent` for incoming email body.
Doesn't seem worth to try to load the needed css/style elements in the shadow root.
[1] https://github.com/odoo/odoo/commit/8fa3cc31e8b81645d40b5d09c925b57d2c0558ab
opw-6463735
Forward-Port-Of: odoo/odoo#287977Fixes an error that could stop point-of-sale payment validation when settling sales orders for made-to-order manufactured products. This helps stores complete these orders reliably without manual troubleshooting or interrupted checkout flows.
Original PR description
Step to reproduce: ------------------ - Install `pos_sale_stock` and `mrp` module. - Go to the setting enable "Replenish on Order (MTO)" - Create a storable product with the MTO route - Create a Bill…
Step to reproduce:
------------------
- Install `pos_sale_stock` and `mrp` module.
- Go to the setting enable "Replenish on Order (MTO)"
- Create a storable product with the MTO route
- Create a Bill of Materials for the product with a storable component
- Now Create Sale order for the Product and confirm it.
- Open POS Store and settel Created sale order.
- Process toward payment and try to validate it.
Issue:
------
`AssertionError: Invalid falsy real id`
Cause:
------
When an MTO product with a manufacturing Bill of Materials is added to a Sale Order and the SO is confirmed, Odoo creates two distinct sets of stock.move records that share the same stock.reference group:
1. **Delivery moves** (picking_id → stock.picking) Created by the outgoing stock rule triggered by the MTO route. These moves are assigned to a delivery picking and have a valid picking_id.
2. **Manufacturing component moves** (picking_id = False) Created by the Manufacture route's procurement rule, which spawns an mrp.production. The raw-material moves inside an MO belong to the production order, not to any stock.picking. Their picking_id field is intentionally False by design.
Both sets of moves are linked to the same stock.reference record through the stock_reference_move_rel many2many table. This means that traversing:
so_line.move_ids → delivery move(s)
.reference_ids → shared stock.reference
.move_ids → ALL moves in the group (delivery + MRP)
every move in the reference group, including MRP component moves whose picking_id is False.
- Then In PosOrder.sync_from_ui(), the code collected the IDs of all related pickings into a set for later cancellation:
https://github.com/odoo/odoo/blob/a0cdf9c33ed464b4ec8bd520e4d0523afb8dbd28/addons/pos_sale/models/pos_order.py#L111
https://github.com/odoo/odoo/blob/a0cdf9c33ed464b4ec8bd520e4d0523afb8dbd28/addons/pos_sale/models/pos_order.py#L130-L133
waiting_picking_ids.add(move.picking_id.id) # ← adds False for MRP moves
Because MRP moves pass the state filter ('confirmed') but have picking_id = False, `move.picking_id.id` evaluates to False (the empty recordset's falsy id), which was silently added to the waiting_picking_ids set.
Issue occur from this [commit](https://github.com/odoo/odoo/commit/515eb5ba0892a759dde32a9b2923d8ecc1e4bf74?debug=1), the ORM assertion in browse() rejects falsy IDs:
https://github.com/odoo/odoo/blob/a0cdf9c33ed464b4ec8bd520e4d0523afb8dbd28/addons/pos_sale/models/pos_order.py#L139
https://github.com/odoo/odoo/blob/9e02922f821a3b4e891e90bf9bd18448a08e66fa/odoo/orm/models.py#L5297
Fix:
---
Add guards in `sync_from_ui()`
*Inner filtered() guard* — add `m.picking_id` as the first condition so
that MRP component moves (picking_id = False) are never iterated, preventing
`False` from ever entering the set
---
opw-6486681
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#289680
Forward-Port-Of: odoo/odoo#284103Customer credit checks now count confirmed sales orders even when products have not yet been delivered. This prevents customers from placing additional orders that exceed their credit limit simply because earlier delivery-based orders have not been invoiced yet.
Original PR description
### Steps to reproduce Set a credit limit of 1.000 on a customer, then: 1. Create a Sale Order for a product with Invoice Policy on **Delivered Quantities**, over that amount → the warning shows.…
### Steps to reproduce
Set a credit limit of 1.000 on a customer, then:
1. Create a Sale Order for a product with Invoice Policy on **Delivered Quantities**, over that amount → the warning shows. Good.
2. Confirm it, and deliver nothing.
3. Create a second order for the same customer → the warning never shows, even though the customer is already over the limit.
### Solution
The ordered quantity is what the customer committed to, delivered or not. Using `qty_to_invoice` instead of `uom_qty_to_consider` or `qty_delivered`
Introducing invoice_status in sales domain for `_compute_credit_to_invoice`: `untaxed_amount_to_invoice` still follows the invoicing policy, while `amount_to_invoice` no longer does. On a confirmed order for a delivery-based product with nothing delivered:
```
line.untaxed_amount_to_invoice = 0 # still gated by qty_delivered
line.invoice_status = 'no'
order.amount_to_invoice = 2000 # fixed by this PR
```
The domain filters on the first one, so the order is dropped from the search before its amount is ever read and `credit_to_invoice` stays at 0.`'no'` is the stored marker for a confirmed line that is not invoiceable yet, which is exactly what the first clause misses.
ticket: [6480211](https://www.odoo.com/odoo/project/967/tasks/6480211)
Forward-Port-Of: odoo/odoo#289305
Forward-Port-Of: odoo/odoo#285578Fixes an issue where manufacturing users could be blocked from completing an order after generating a lot number and then increasing the quantity to produce. This ensures valid production updates can be processed without an incorrect lot-number error, reducing disruption on the shop floor.
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#289725 Forward-Port-Of: odoo/odoo#289171
This fix prevents restaurant orders from failing when staff try to fire a course that has no preparation printer or display configured. The system now only fires courses that are ready and actually routed for preparation, reducing errors and avoiding courses being marked as sent without producing a ticket.
Original PR description
Issue: Firing a course with no pdis linked could make a traceback. Cause: - the fire_course backend method could be called without the order being synched. - The Fire button only checked that a course was ready to fire, never that its products were routed anywhere. With no preparation printer or display covering them, firing marked the course as fired and produced no ticket. Fix: - Add `canBeFired` on the course: ready to fire, and at least one of its products in a preparation category. - Select and fire the first course that can be fired, searching forward from the selected one. - Only call `fire_course` once the order and the course both have a database id.
Website editors can now correctly see options to update or remove the theme currently in use. This prevents confusion when managing a site's design and ensures the current theme is handled correctly.
Original PR description
Steps to reproduce: - Apply a theme on a website, for example Bistro - Enter website edit mode and open the Theme tab - Click Switch Theme - Hover the card of Bistro (the theme currently in use) -…
Steps to reproduce: - Apply a theme on a website, for example Bistro - Enter website edit mode and open the Theme tab - Click Switch Theme - Hover the card of Bistro (the theme currently in use) - "Use this theme" button appears like every other card - It should show "Update theme" and "Remove theme" instead Since [1], `self.env.website` only reads `website_id` from the context, and that key is stripped on a non-website route, where the resolved website is moved to `host_id` instead. The theme kanban is read through a plain ORM call, so `_compute_is_installed_on_current_website` resolved no website and no card was ever flagged as the one in use. This commit falls back on `host_id` to resolve the current website. `button_refresh_theme` and `button_remove_theme` get the same fallback: they resolved the website the same way and acted on an empty one, which went unnoticed only because the buttons triggering them were never displayed. task-6575727 [1]: https://github.com/odoo/odoo/commit/9d97e0e919a953e4f86e42e32ca24d9790840f68
Multi-day time off requests for fully flexible employees now show the correct number of days instead of always showing one day. The fix also accounts for public holidays and half-day boundaries, improving payroll and absence tracking accuracy.
Original PR description
Steps to reproduce: ------------------------------------ 1. Install Time off module 2. Create New Fully Flexible Employee 3. Click on the Time off smart button 4. Create time off for multiple days…
Steps to reproduce: ------------------------------------ 1. Install Time off module 2. Create New Fully Flexible Employee 3. Click on the Time off smart button 4. Create time off for multiple days (eg. Mon - Friday) Observation: ------------------------------------ Number of days still shows 1 Days. Issue: ------------------------------------ Issue occurs because `work_time_per_day_mapped` returns one interval per day for standard and flexible schedules in multi-day time off requests, so the interval count correctly matches the number of leave days. https://github.com/odoo/odoo/blob/d73e5662a0af7c549008661f743ba5d51f765339/addons/hr_holidays/models/hr_leave.py#L460-L461 However, for fully flexible schedules, it returns a single interval containing the total hours across all days, causing the leave duration to always be computed as 1 day regardless of the actual number of days requested. Solution: ------------------------------------ For fully flexible employees, count the actual calendar days and subtract public holidays when applicable. opw-6060552 Forward-Port-Of: odoo/odoo#255826
When a project's company is changed, its related Documents workspace is now updated when all linked projects belong to that same company. This prevents workspaces and documents from accidentally remaining company-neutral after projects are created through quick create, improving consistency in multi-company setups.
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
Point of Sale product search now includes products whose variant has a single attribute value, such as Size M. This helps cashiers find the right product more reliably and avoids missed sales or manual lookup when searching by variant details.
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
Spanish OSS sales are now assigned to the correct VAT return box, ensuring amounts are reported in box 123 instead of box 124. This helps businesses file more accurate Spanish VAT declarations and aligns the related OSS tax templates and tests with the required reporting treatment.
Original PR description
OSS sales were being mapped to mod_303_casilla_124_balance where it should be mapped to mod_303_casilla_123_balance. These amounts must be reported values in casilla 123. This commit updates es_assec, es_common, es_full and es_pymes tax templates from 124 to 123 and updates test_country_tag_from_spain. task-6360387 I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#284984
Attendance officers without full Employees app access can now open the Employees menu from Attendance without hitting an access error. The menu now shows an attendance-focused employee view with only the information needed for attendance management, reducing support interruptions and avoiding unnecessary HR access.
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
Appointment scheduling now avoids creating invalid reservations when shareable resources are booked from the Gantt view. This prevents website booking errors and helps keep resource capacity calculations consistent for customers and staff.
Original PR description
**Steps to reproduce:** - Install Appointment app - Create an appointment type based on resources, auto-assigned, with two shareable resources linked together - Set the first resource's capacity to 3…
**Steps to reproduce:**
- Install Appointment app
- Create an appointment type based on resources, auto-assigned, with two shareable resources linked together
- Set the first resource's capacity to 3 and the second resource's capacity to 4
- From the backend Gantt view, create a booking for 2 people on the first resource
- Create a second booking for 2 people on the same resource, at the same date and time
- First resource is now overbooked with reserved capacity of 4 out of 3
- Try to create an appointment from the website
- Error: "The capacity reserved should be positive."
**Issue:**
When bookings are created from the backend gantt view, the selected resource can be overbooked even if another linked resource still has available capacity.
Then when trying to book an appointment the new booking lines will trigger this constraint:
```py
_check_capacity_reserved = models.Constraint(
'CHECK(capacity_reserved >= 0)',
"The capacity reserved should be positive.",
)
```
This is caused by the negative values in:
```py
booking_line_values = []
if appointment_type.schedule_based_on == 'resources':
capacity_to_assign = asked_capacity
for resource in resources:
resource_remaining_capacity = resources_remaining_capacity.get(resource)
new_capacity_reserved = min(resource_remaining_capacity, capacity_to_assign, resource.capacity)
capacity_to_assign -= new_capacity_reserved
booking_line_values.append({
'appointment_resource_id': resource.id,
'capacity_reserved': new_capacity_reserved,
'capacity_used': new_capacity_reserved if resource.shareable and appointment_type.resource_manage_capacity else resource.capacity,
})
```
**Fix:**
Avoid negative remaining value in resource booking when computing available slots.
Note: Tried to take the capacity already used by overlapping bookings into account when assigning resource booking lines from the backend gantt view. And also force linked_resources booking when trying to book more
than the total capacity to properly dispatch as many slots as possible. But it was breaking `appointment_google_reserve` tests.
opw-6503147
Forward-Port-Of: odoo/enterprise#132265
Forward-Port-Of: odoo/enterprise#130520Malaysian payroll calculations now use the correct SOCSO Act 800/EIS rules and avoid counting employee SOCSO deductions twice. This helps ensure employee net pay and employer contribution reporting match expected statutory amounts.
Original PR description
There was a difference between the actual and expected NET. **Causes** - rules existed for both SOCSO Act 800 and EIS, but Act 800 seems to be the official name of the Employment Insurance System…
There was a difference between the actual and expected NET. **Causes** - rules existed for both SOCSO Act 800 and EIS, but Act 800 seems to be the official name of the Employment Insurance System (source: https://www.perkeso.gov.my/images/dokumen/Rate_of_Contribution_ACT_800.pdf). Keep the ACT 800 one as its matches the expected amount. - there was a double counting of the SOCSO employee contributions (4 and 800) , as `l10n_my_rule_socso_employee` (the sum of both) was added to the total deductions. **Change** Before: 3500/month wage results in a 3108 NET. <img width="1181" height="533" alt="before" src="https://github.com/user-attachments/assets/cdf4420f-e2cf-4dc7-9488-6bde3f1f9961" /> After: 3500/month wage results in a 3090.85 NET: - 6.90 SOCSO Act 800 Employee - 6.90 SOCSO Act 800 Employer - 17.25 SOCSO Act 4 Employee - 60.35 SOCSO Act 4 Employer Which seems consistent with online sources (https://payroll.my/) <img width="1181" height="425" alt="after" src="https://github.com/user-attachments/assets/7fb41186-dbb4-4d7a-ac9a-14fb56efc4bc" /> Other fix: while not affecting the calculation, 'SOCSO Employer Share' appeared as incorrect, the two rules SOCSO_800_EMPLR and SOCSO_4_EMPLR should have the same sign. opw-5976362 Forward-Port-Of: odoo/enterprise#131153 Forward-Port-Of: odoo/enterprise#118138
Belgian payroll calculations now exclude time credit and extra hours when computing worked day amounts. This helps ensure payslips reflect the correct worked day values and reduces payroll accounting errors.
Original PR description
We forgot to filter out time credit and extra hours when computing worked day lines amount. Forward-Port-Of: odoo/enterprise#132400
The calendar view now switches properly between desktop and mobile layouts when the browser window is resized. This prevents the desktop sidebar from staying open on mobile, giving users a cleaner and more consistent experience on smaller screens.
Original PR description
See commits Forward-Port-Of: odoo/odoo#289301 Forward-Port-Of: odoo/odoo#288329
Credit notes using Spain's TicketBAI/Batuz integration now keep the equivalence surcharge rate positive, as required by the tax agency. This prevents validation rejections and allows affected Spanish businesses to submit these credit notes successfully.
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
This fixes an issue where accordion sections added to default Terms & Conditions pages could break after saving. Businesses can now use richer page blocks in invoice terms previews without losing functionality or triggering errors when editing.
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
This fixes Irish balance sheet reporting so current-year invoice amounts appear only in the correct profit-or-loss line. It prevents the same amount from being counted in both brought-forward profit and current-year profit, improving accuracy for Irish financial statements.
Original PR description
Scenario: - install l10n_ie and switch to a company with irish accounting - create and validate a 2025 customer invoice with one line and value 50 - go to the balance sheet report and check values of year 2025 Result: the 50 amount is present in both "H.V. Profit or loss brought forward" and "H.VI. Profit or loss for the financial year" while it should only be present in "H.VI." Cause: Start of december 2025 e699a14a1922d6b8a5bc6b88ad9c02a411e4b073 was merged that requires adding force_date_scope in some case. End of december 2025 597f25a4faff914504d1dfa021e9839e39ece937 was merged that added a new balance sheet report but didn't take into account the recent change for the force_date_scope parameter. Fix: add the missing force_date_scope parameters. opw-6425317 Forward-Port-Of: odoo/enterprise#129500
Invoice forms now keep the amount due aligned with the invoice total while users are still editing. Applying outstanding credit also saves the invoice first, preventing newly added unsaved invoice lines from being 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
The Timesheet Grid now only marks public holidays that belong to the active company. This prevents employees from seeing days incorrectly greyed out because of holidays configured for another company.
Original PR description
Issue: - Public holidays from other companies were incorrectly included in the Timesheet Grid view of the current company. As a result, days corresponding to those holidays appeared greyed out.…
Issue: - Public holidays from other companies were incorrectly included in the Timesheet Grid view of the current company. As a result, days corresponding to those holidays appeared greyed out. Cause: - The `grid_unavailability` method relies on the `_get_valid_work_intervals` function to fetch all unavailability data at once. When fetching data for multiple employees, this function also retrieves public holidays from all companies. Fix: - The method no longer uses the data returned by `_get_valid_work_intervals` for company unavailable days. Instead, it now always makes a separate, direct call via the `get_company_unavailable_dates()` function. This ensures that only holidays relevant to the current company are considered in the grid view. The company is also passed in the domain of `_work_intervals_batch`, which otherwise returns the leaves of every company when no resource is given. Steps to reproduce: - 1. Create Company A and Company B. 2. In Company B, create a public holiday on Tuesday. 3. Switch back to Company A. 4. Open the All Timesheets Grid view from Company A. Expected behavior: - - The grid column for Tuesday should not be grey for Company A users. Current behavior: - - The grid column for Tuesday is grey, incorrectly showing it as a time-off day. task:4492966 Forward-Port-Of: odoo/enterprise#132332 Forward-Port-Of: odoo/enterprise#88495
Users can now click amounts in the aged receivable/payable reports when an "Open On" date filter is set without triggering an error. This restores access to the underlying journal item details needed for receivables and payables review.
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
This fix ensures manufacturing costs use stock movement data only from the relevant company. It prevents FIFO component costs from being incorrectly taken from another company, improving accuracy in multi-company inventory valuation.
Original PR description
**Problem**: In a multi-company environment, while manufacturing a product, if the component of the product is visible for both company and there is no last_in stock move for the component in the current company, then the last_in stock move of the other company is used to compute the cost of the component. This only happens when the costing method of the product is FIFO **Steps to reproduce:** 1. Create company A and company B. 1. Create a product A and set FIFO costing method to it. 2. Make a purchase order of product A in company A and receive it. 3. Create a product B with product A as its BOM material. 4. Create a MO of product B in company B and produce it. 5. The unit cost of product A in company B is using the unit cost from the purchase order of product A in company A. **Fix**: Add a company domain to the last_in stock move search to prevent cross-company last_in stock move search. opw-6411216 Forward-Port-Of: odoo/odoo#289365 Forward-Port-Of: odoo/odoo#280074