Tuesday, September 22, 2026
49 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
Fixes issues in the Documents app spreadsheet template picker where the search bar could disappear when no templates matched and the custom filter option could crash. Users can now search and refine spreadsheet templates without losing the search controls or encountering an error.
Original PR description
- templates searchbar disappear when the search has no match - Clicking "Add a custom filter' in the searchbar crashes Task-6526322 Forward-Port-Of: odoo/enterprise#130376 Forward-Port-Of: odoo/enterprise#130096
Updates the Dominican Republic localization to apply new ISR withholding rates required by Law 30-26 from July 2026. This keeps tax setup and reporting aligned with DGII guidance, including separate handling for services, rentals, foreign payments, transfers, and other withholding categories.
Original PR description
## Description Law no. 30-26 of June 18, 2026 updates Dominican ISR withholdings effective **July 1, 2026**: - **Professional services, fees, commissions, and rentals paid to individuals:** 10% to…
## Description Law no. 30-26 of June 18, 2026 updates Dominican ISR withholdings effective **July 1, 2026**: - **Professional services, fees, commissions, and rentals paid to individuals:** 10% to 15%. - **Specific foreign-payment categories:** 15% for royalties or rights, software licenses, online advertising, and the use or storage of data. - **General remittances abroad:** remain at 27% when they are outside those specific categories. The DGII's current **IR-17-2026 (July 2026 onward)** also confirms that the concepts discussed in review are three distinct reporting rows: - **Row 4 — Transfers of titles and properties:** 2%. - **Row 17 — Other income under Decree 139-98, Article 70(a)/(b):** 3%. - **Row 18 — Other withholdings under General Rule 07-2007, as amended by Law 30-26:** 3%. The two 3% rows must not be conflated with each other or with the separate 2% transfer withholding. ## Implementation The changed-rate template entries use new, rate-explicit XML IDs, as recommended in review: - `ret_15_income_person`: new -15% fee withholding, replacing `ret_10_income_person` in the template; posts to `21030301`. - `ret_15_income_rent`: new -15% rental withholding, replacing `ret_10_income_rent` in the template; posts to `21030302`. - `ret_3_income_person`: new -3% General Rule 07-2007 withholding, replacing `ret_2_income_person` in the template; posts to `21030308`. - `tax_group_person_services_15`: new grouped tax using `ret_15_income_person`. - `tax_group_person_construction_3`: new grouped tax using `ret_3_income_person`. - `position_person_services_15`: new physical-services fiscal position mapped only to the current 15% grouped tax. The remaining legal concepts stay separate: - `ret_3_income_article_70`: new -3% tax for Decree 139-98, Article 70(a)/(b), posted to `21030309`. - `ret_2_income_transfer`: remains at -2% on `21030306`; its misleading “Materials” source metadata is corrected to transfers of titles and properties. - `ret_27_income_remittance`: remains active at -27% on `21030307`, with the general foreign-services fiscal position unchanged. - `ret_15_income_foreign_royalties_technology`: new -15% tax only for the foreign categories covered by Law 30-26. It posts to the new `21030310` Law 30-26 payable account rather than the L253-12 remittance account. - `position_exterior_royalties_technology`: new fiscal position mapping purchases only to that specific 15% tax. The specific foreign tax keeps the short invoice label `-15% ISR (L30-26)` to avoid wrapping in vendor-bill PDFs. The original manifest author entry is unchanged, as requested. The Git history and corporate CLA record this contribution by Grupo de Consultoria Henca. ### Existing-company reload behavior The superseded 10%/2% tax, grouped-tax, and physical-services fiscal-position rows are removed from the template rather than shipped as obsolete entries to new companies. On an existing company, Odoo's chart reload keeps those historical records and generated XML IDs unchanged, then creates the new current-rate records under the new XML IDs. On a newly loaded chart, only the current template entries are created. The physical-services fiscal position also has a new XML ID intentionally. Reload preserves existing fiscal-position mappings as user-configurable data and only appends mappings involving new taxes; reusing `position_person` could therefore leave both the historical and current destinations on one position. `position_person_services_15` keeps the current mapping isolated while the old position remains available for historical operations. `ret_2_income_transfer` keeps its existing XML ID because its legal rate, concept, and account remain 2% / transfers / `21030306`. The corrected label is present for new charts; backfilling label-only metadata on already-loaded charts would require an explicit upgrade migration because standard chart reload does not rewrite that user-visible metadata. ## Official references - DGII, current IR-17-2026 download page (July 2026 onward): https://dgii.gov.do/herramientas/formularios/formularioDeclaraciones/Paginas/impuestosRetencionesyRetribuciones.aspx - Law 30-26: https://www.consultoria.gov.do/Consulta/Home/FileManagement?documentId=3405887&managementType=1 - DGII implementation calendar, notice 10-26: https://dgii.gov.do/publicacionesOficiales/avisosInformativos/Documents/2026/10-26.pdf - DGII CA59, calculation for professional and technical services: https://ayuda.dgii.gov.do/conversations/retenciones-y-retribuciones-complementarias/ca59-qu-porcentaje-del-isr-deben-retener-las-personas-jurdicas-a-las-personas-fsicas-en-la-prestacin-de-servicios/5f3c175f8cd858ce879a130f - DGII clarification for General Rule 07-2007 and the construction sector: https://ayuda.dgii.gov.do/conversations/discusiones/aplicacin-de-la-ley-nm-3026-respecto-a-la-retencin-prevista-en-el-artculo-3-de-la-norma-general-072007-sector-construccin/6a45792cde3c6003da189ff1 - DGII legal basis for the 2% transfer withholding: https://ayuda.dgii.gov.do/conversations/discusiones/base-legal-retencion-2-transferencia-de-titulos-y-propiedades/5f6355928cd858ce872bb35a - Ministry clarification on the specific foreign technology and royalty categories: https://www.hacienda.gob.do/ley-30-26-no-dispone-impuestos-por-suscripciones-de-ciudadanos-a-plataformas-digitales-reduce-de-27-a-15-la-retencion-a-empresas-que-contratan-servicios-tecnologicos-en-el-exterior/ Legal basis cited by DGII: Law 11-92, article 309, as amended by Law 30-26, article 17; Regulation of Title II of the Tax Code, article 70. ## Validation - The branch is rebased on the current 17.0 head and contains one squashed commit. - The account, tax-template, and fiscal-position CSV files have consistent column counts, unique IDs, and valid child/account references. - A fresh Dominican chart contains only the new current-rate IDs; the superseded template IDs are absent. - The fresh chart was verified with fees/rentals at 15%; General Rule 07-2007 at 3% on `21030308`; Article 70(a)/(b) at 3% on `21030309`; transfers at 2% on `21030306`; general remittances at 27% on `21030307`; and the covered foreign categories at 15% on `21030310`. - The 15% services and 3% construction groups contain exactly the current tax children, and the new services fiscal position maps each purchase tax to only the 15% group. - An existing company loaded from the pre-law template was reloaded twice. Its historical 10%/10%/2% taxes, historical groups, and historical fiscal position retained their original record IDs and configuration; all current records were created once; the old and new positions each retained exactly two isolated mappings; and the second reload was idempotent. - `/account:TestChartTemplate`: 22 tests passed, 0 failures, 0 errors. This is the first contribution by Grupo de Consultoria Henca (https://www.consultoriahenca.com); the corporate CLA signature is included in `doc/cla/corporate/consultoriahenca.md` as instructed by `doc/cla/sign-cla.md`. Forward-Port-Of: odoo/odoo#287568 Forward-Port-Of: odoo/odoo#275986
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
This fixes how dates and date-times are shown and saved when using debug mode to set default values. Business users and administrators will see the correct date format, reducing confusion and preventing incorrect default date values from being stored.
Original PR description
Problem: When setting default values in debug mode, the date and datetime values are not formatted correctly. This leads to confusion and incorrect values being set for date fields. Steps to reproduce: 1. Enable debug mode 2. Create a journal entry, set the date to some date (August 31st, for example), and save. 3. Click the bug icon, click Set default values. 4. Click on the dropdown, notice how the date is displayed as datetime instead of date. Cause: The displayed values were not being formatted for date and datetime fields when setting default values. They were just output as strings. Also, dates were not serialized correctly before being sent to the server, they were serialized as datetime instead of date, because the code was checking the constructor name of the value instead of the field type. opw-6533491 Forward-Port-Of: odoo/odoo#287813
The Maintenance app now shows request status with the intended circular indicator in the card-style request view. This fixes a visual inconsistency so users can recognize request states more clearly and consistently.
Original PR description
Issue Before this Commit: ======================== The maintenance request state is displayed as a rounded pill in the kanban view instead of a circular indicator. <img width="500" height="150"…
Issue Before this Commit: ======================== The maintenance request state is displayed as a rounded pill in the kanban view instead of a circular indicator. <img width="500" height="150" alt="image" src="https://github.com/user-attachments/assets/84fe84ef-ff58-4873-bb73-1f375774f149" /> Steps to reproduce ======================== 1. Open the Maintenance app. 2. Open Maintenance Requests. 3. Observe the maintenance request state. The state is displayed as a rounded pill instead of a circular indicator. Cause of the issue: ======================== In the [PR](https://github.com/odoo/odoo/pull/260098), the view type of maintenance requests was changed from `kanban` to `cards`, but the `MaintenanceRequestStateSelection` component still checks for `kanban` in its `isKanbanOrMobileView` method. As a result, it uses the `rounded-pill` styling instead of the circular styling. After this Commit: ======================== Update the condition to correctly check `card` as the view type, so the maintenance request state is displayed as a circular indicator. <img width="500" height="150" alt="image" src="https://github.com/user-attachments/assets/e01b5898-2ec3-45b3-aa79-f7ed7c8e9e9f" />
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.
This fixes an issue where some grid-based views could appear empty after part of the page was rebuilt, until the user scrolled or resized the window. The grid now recalculates immediately when its container changes, keeping content visible and reducing user confusion.
Original PR description
See commit messages for details. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#289085 Forward-Port-Of: odoo/odoo#281721
This fixes an issue in the website editor where drop zones around the Animated Cover block could overlap, making it harder to add new content in the right place. Editors should now have a clearer and more reliable drag-and-drop experience when building pages.
Original PR description
Steps to reproduce: - Go to a website page in edit mode. - Drag and drop an "Animated Cover" snippet onto the page. - Try to drag and drop another snippet below it. => The two dropzones overlap. Before this commit, the last dropzone inside the `Animated Cover` snippet overlapped the dropzone used to add a new section below it. After this commit, the first and last dropzones inside the `Animated Cover` snippet are shifted away from adjacent dropzones. task-6588702
This fixes a website display issue where social media icons using the circle style could become hard to see on dark page sections. The icons now keep the appropriate contrasting color, improving readability and visual consistency for website visitors.
Original PR description
Steps to reproduce: - Select a dark website color palette. - Drag and drop a snippet with a dark background onto the page. - Drag and drop a "Social Media" snippet inside it. - Disable the "Color" option. - Select the "Circle" layout. => The icons are not visible against the dark background. Before this commit, the shaped icon fallback introduced by [1] had higher specificity than the `rounded-empty-circle` color reset. It forced a dark color onto icons placed on a dark background. After this commit, `:where()` keeps the fallback selector specificity low, so empty circle icons inherit a contrasting color. [1]: https://github.com/odoo/odoo/commit/cc02feec060a9469f3e4af6b40fb291266beca99
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
Employees in Belgium can now select a company bike even when using the mobility budget. This aligns the salary package options with legally allowed benefits and avoids blocking a valid employee choice.
Original PR description
When you take the mobility budget, it doesn't allow you to take a company bike. This should be possible as it is legally possible Forward-Port-Of: odoo/enterprise#132405
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
Email template editors now correctly prevent users from adding embedded videos. This avoids broken or empty email content caused when unsupported video elements are removed during saving.
Original PR description
Steps to reproduce: 1. Go to Settings > Technical > Email Templates. 2. Open any email template. 3. In the Body editor, either paste a YouTube URL and select Embed YouTube Video from the popup, or…
Steps to reproduce: 1. Go to Settings > Technical > Email Templates. 2. Open any email template. 3. In the Body editor, either paste a YouTube URL and select Embed YouTube Video from the popup, or type `/media` and open the 'Videos' tab in the Media dialog. 4. Select a video or embed a YouTube video. 5. Save the email template. Issue: - The YT URL is converted into an `<iframe>` video element in the email template. However, the `<iframe>` element is removed when the template is saved because it is not supported by the email HTML sanitization. - This leaves the surrounding content empty and can result in broken HTML in the email template. The `body_html` field was configured with `allowCommandVideo: false` to prevent video embedding, but this option is not recognized by the HTML field and is therefore ignored. Solution - Use the supported `allowVideo: false` option on the `body_html` field of email templates to disable video embedding in the editor as [this PR](https://github.com/odoo/odoo/pull/219288) has changed the html_field components's attribute. Expected behavior - Video embedding should be disabled in email templates, preventing users from inserting YouTube videos and avoiding `<iframe>` elements that are later removed by HTML sanitization. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#286133
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
This fixes an error that could occur when a user customized the Inventory Overview and removed a card containing a dashboard graph. The page now continues to load normally, making Studio customizations safer for inventory teams.
Original PR description
Steps to reproduce:
- Go to Inventory > Overview
- Enable Studio, delete a kanban card that contains the "picking_type_dashboard_graph" widget (field kanban_dashboard_graph)
> UncaughtPromiseError > OwlError
> TypeError: Cannot read properties of undefined (reading 'includes')
at StockKanbanRenderer.getGroupsOrRecords
Cause of the issue:
`StockKanbanRenderer.getGroupsOrRecords` assumes the field `kanban_dashboard_graph` is always present on every record to detect if all Inventory Overview graphs are sample data and, if so, replace them with randomized values.
By removing the card with studio, we remove the field from the fields fetched for the record, and so `r.data.kanban_dashboard_graph` is `undefined`.
Fix by ignoring records for which the field isn't fetched instead of assuming it's always there.
opw-6512743
Forward-Port-Of: odoo/odoo#285593Custom recorded tours can now be tested directly from the tours list as expected. This prevents a silent failure where pressing “Testing” appeared to do nothing, improving reliability for teams validating guided workflows.
Original PR description
Clicking "Testing" on a custom/recorded tour called startTour with mode "auto" and fromDB true, but getTour() only loaded from the database when mode was "manual". For a tour that only exists in the web_tour.tour record (not in the client-side registry), this left `tour` undefined and startTour() silently did nothing. Load from the database whenever fromDB is set, regardless of mode. Task-id: 6575435 Forward-Port-Of: odoo/odoo#288618 Forward-Port-Of: odoo/odoo#288474
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
This fix ensures Factur-X invoice files include the required customer name even when an invoicing address has no name of its own. It falls back to the parent commercial partner name, reducing failed e-invoice compliance checks.
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
Employees without HR access can now see one-off work locations for colleagues in the calendar, matching the behavior for recurring locations. This helps teams rely on the calendar for accurate daily workplace information without needing elevated HR permissions.
Original PR description
**Steps to reproduce** - With a user having HR rights, create an exceptional work location for a user (click on the top bar of one of the days in the calendar, where work locations are displayed, and do not check "repeat every". - Open the calendar app with a user having no HR rights, in the sidebar, add the user with a non-recurrent work location. Notice that the work location is not visible, unlike recurring ones. **Cause** Recurring work locations are defined on the public employee (`*_location_id` type fields) and are readable by all users. Non-recurring work locations are `hr.employee.location` records and the `homeworking_own_rule` rule restricts read operations for non-HR users. opw-6190464 Forward-Port-Of: odoo/odoo#288438 Forward-Port-Of: odoo/odoo#266380
This fix updates an accounting test to use a proper PDF file instead of invalid sample content. It helps keep automated checks reliable, reducing false failures during development and quality assurance.
Original PR description
Use PDF_RAW from base.tests.files instead of fake (and invalid) PDF content, like the other tests needing a PDF attachment. runbot-946577 Forward-Port-Of: odoo/enterprise#131746
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
This update corrects how employee work contact information is calculated so access rights are handled consistently. It helps prevent unexpected behavior when viewing or managing HR employee records.
Original PR description
Ensure correct access to avoid unexpected behavior opw-6560194 Forward-Port-Of: odoo/odoo#288661 Forward-Port-Of: odoo/odoo#288063
Generic demo leave types no longer carry a specific country, so they will not interfere when setting a company's country in demo or development environments. Real country-specific leave data remains protected by the existing checks.
Original PR description
_check_country_change_holidays constraint blocks writes to res.company.country_id whenever hr.leave/hr.leave.allocation records exist whose leave type country differs from the new company country. Unset country_id on the generic holiday_status_* demo leave types they are not meant to represent a specific country's holiday policy, so they should not carry a country at all. With country_id set to False, they no longer participate in the country-change constraint. The constraint keeps protecting real country changes on business data, while no longer blocking legitimate demo installs where we configure the main company with our country but rely on demo data for dev instances. Related to https://github.com/odoo/odoo/pull/277346 Forward-Port-Of: odoo/odoo#288983 Forward-Port-Of: odoo/odoo#278749
Odoo now applies a stable ordering when multiple fiscal positions have the same priority. This prevents unpredictable tax or accounting rule selection after data imports, improving consistency for businesses using fiscal positions.
Original PR description
The algorithm to find the right fiscal position works on top of deterministically ordered fiscal position records. This invariant is not enforced by the functional code. Importing data without explicit sequence number results in multiple records ending up with the same value in this column. Sorting by column 'sequence' is not good enough to have repetitive results in this case. The fiscal position algorithm can return a different record due to database implementation details. Adding field "id" to the default ordering ensures the order of fiscal position records remains deterministic. This is because insertion order is preserved and locked into order-preserving id-values when importing data. runbot-945738 Forward-Port-Of: odoo/odoo#289546 Forward-Port-Of: odoo/odoo#289242
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 update corrects minor English wording issues in the Peppol activation flow. The clearer language helps present a more polished and trustworthy experience for English-speaking users.
Original PR description
There were some minor English mistakes on the Peppol activation wizard that might make Odoo look cheap and untrustworthy to English-speaking audiences. This commit fixes these english mistakes. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#285462
This change rolls back duplicate receipt detection in Accounting because it caused vendor bill pages to load much more slowly. The duplicate check for bills returns to the previous faster behavior while a better long-term solution is prepared separately.
Original PR description
This reverts commit 391278237dc4fdbf48039bb269f1377d83bbc54d.
It introduced a performance regression.
Go to Accounting > Vendors > Bill
On odoo.com:
| | Time | Query plan |
|--------|--------|--------|
| Before | ~600ms | https://explain.dalibo.com/plan/3ge1afa2aa632be9|
| Now | 44s | https://explain.dalibo.com/plan/99e847h8d1c38dfd |
The reason is the condition `.move_type in ('in_invoice', 'in_refund')` which was changed to include 'in_receipt':
`.move_type in ('in_invoice', 'in_refund', 'in_receipt')`
Because of that, the query can no longer use the partial index `_duplicate_bills_idx` because it doesn't include 'in_receipt'.
We are reverting the commit in stable. We keep it and adapt the index in master.
task-6577249
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#289003
Forward-Port-Of: odoo/odoo#288748This 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 fix ensures Guatemala fiscal positions are applied in a consistent order, so invoices use the expected tax rules every time. It prevents random test and invoice calculation failures caused by unpredictable ordering.
Original PR description
Test TestGtFlow.test_gt_edi_basic_invoice nondeterministically fails, but the reported error has always the same values. Turns out the code is picking the wrong fiscal position to apply taxes on the prices of the invoice. `res.partner._get_fiscal_position()` searches auto_apply fiscal positions without explicit order, so it relies on the model's default order-by sequence. `account.fiscal.position-gt.csv` carries two records into the database without sequence number, so the order they are returned in is nondeterministic. The resultset is afterwards stable sorted (no tie-breaker between equal sequence numbers) and filtered, so the wrong/unexpected tax can be applied randomly. Explicit sequence numbers are added to account.fiscal.position-gt.csv so the fiscal positions are always returned in-order (domestic first). This approach matches the convention of the other fiscal-position CSVs. runbot-945738 Forward-Port-Of: odoo/enterprise#132377 Forward-Port-Of: odoo/enterprise#131587
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
This fixes an issue in the HTML editor where Safari users could lose selected text without the replacement character being inserted. Editing notes and other rich text fields is now more reliable when replacing text at the start of content.
Original PR description
When using Safari, if the first character of the editable is selected and a character is pressed, the selection content is removed, but the character is not inserted. It seems that Safari does not trigger the actual `input` event, nor its native behavior, if the initial anchor node is detached from the DOM after `beforeinput`. This commit avoids this issue by preventing Safari from proceeding with the insertion right after the deletion by instead re-triggering the `insertText` command. Steps to reproduce: - Use Safari - Go to a To do note - Select the first word - Press a letter => The first word was deleted but the letter was not inserted. task-6445669 Forward-Port-Of: odoo/odoo#289225 Forward-Port-Of: odoo/odoo#281223
This fix prevents French e-invoicing test checks from failing when the optional PDP component is not installed. It makes the test expectations match the installed configuration, improving reliability for validation and development workflows without changing customer-facing features.
Original PR description
### Issue: Tests in `TestCIIFR` fail when run without `l10n_fr_pdp` installed The expected XML files contain `PMT`, `PMD` and `AAB` notes that are only generated when `l10n_fr_pdp` is installed ### Cause: The note generation for FR e-invoicing lives in `l10n_fr_pdp` When it is not installed, the notes are absent from the generated XML but still present in the expected test files When `l10n_fr_pdp` is not installed, the expected tree is stripped of `PMT`, `PMD` and `AAB` notes before comparison ### Steps to reproduce: - Install `l10n_fr_account` without `l10n_fr_pdp` - Run `TestCIIFR` from `l10n_account_edi_ubl_cii_tests/tests/test_xml_cii_fr.py` Before the fix, the affected tests fail on the `cbc:Note` comparison runbot-945999 Forward-Port-Of: odoo/odoo#284661
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
The website editor now handles the card image position control more predictably. Clicking the same position button a second time closes the overlay instead of closing and immediately reopening it, reducing confusion while editing website cards.
Original PR description
Steps to reproduce: 1.Open the HTML editor. 2.Drop a card snippet, e.g. s_product_list. 3.Open the position option and click the button. 4.Click the same button again. 5.Before this fix, the overlay closes and immediately reopens. Before this commit: When selecting the position option on a card, clicking the button would open the overlay. Clicking the same button again would close the overlay and immediately reopen it. After this commit: The first click on the position option opens the overlay. The second click on that option closes the overlay without reopening it. task- 6333122 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Users who join a Discuss channel themselves will no longer receive a misleading push notification saying they were invited. This avoids confusion and keeps channel notifications accurate for actual invitations only.
Original PR description
Steps to reproduce: -------------------------------------- 1. Install the mail module 2. Allow push notification permissions from the site settings 3. Open Discuss > Go to Channels 4. Click Join on a…
Steps to reproduce: -------------------------------------- 1. Install the mail module 2. Allow push notification permissions from the site settings 3. Open Discuss > Go to Channels 4. Click Join on a channel where the current user is not a member Observation: -------------------------------------- The current user receives a push notification saying: 'User has invited you to this channel.' This is incorrect; the user joined voluntarily. Nobody invited them Issue: -------------------------------------- The commit https://github.com/odoo/odoo/pull/255052/changes/cddd9491c28e9c949efb24d2330c58e35144af12 Adds a push notification when a user is invited to a channel `_add_members` creates the new member and sends a push notification to all newly added members' partner IDs without checking whether a member joined by themselves or was invited by someone else. https://github.com/odoo/odoo/blob/e9cfa0fd65d234bcff8130a2c790cd80ee443595/addons/mail/models/discuss/discuss_channel.py#L830-L853 Interestingly, the bus notification code just a few lines above already makes this distinction using `member.is_self` but the web push block doesn't apply the same filter. https://github.com/odoo/odoo/blob/e9cfa0fd65d234bcff8130a2c790cd80ee443595/addons/mail/models/discuss/discuss_channel.py#L810-L811 Solution: -------------------------------------- Filter out self-joined members from the web push notification recipients. The push notification should only target members who were invited. OPW-6527691
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