Tuesday, September 22, 2026
44 changes · saas-19.3
Resolved issues and error corrections
This fix prevents Ecuadorian Point of Sale orders from being invoiced and sent to the tax authority when the user has unchecked the invoice option. It ensures the customer’s invoicing choice is preserved after payment validation, avoiding unintended invoice creation and compliance follow-up.
Original PR description
Steps to reproduce: - Ecuadorian company, PoS with a preparation printer (or use the "Back" button on the feedback screen) - Open a session, add a product, go to the payment screen - Select a customer, uncheck "Invoice", pay in cash and validate Issue: An invoice is created and sent to the SRI although "Invoice" was unchecked. Cause: l10n_ec_edi_pos patches PosOrder.setup() to force to_invoice = true. setup() runs not only on creation but every time the record is reloaded from the server, so the sync done at validation flips the flag back to true in the browser. Since the paid order can now be re-synced after validation (kitchen printer, "Back" on the feedback screen), the server processes it in process_saved_payments, writes to_invoice = True and generates the invoice. Fix: Only apply the Ecuadorian default when the loaded values carry no to_invoice, i.e. for a newly created order. Reloaded records keep the value the user chose. opw-6572987
This fix ensures new currency rates fetched through Peru's SUNAT service are dated to match SUNAT's official daily rate. It helps prevent mismatched exchange rates in reporting or submissions that rely on SUNAT values, though already stored rates are not changed.
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
Fixes Peruvian PLE sales and purchase ledgers so ISC tax is not incorrectly included in taxable base amounts or counted multiple times. This keeps ledger reporting aligned with electronic invoices sent to SUNAT and prevents overstated tax bases for affected documents.
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
This update fixes several issues in how financial report snapshots are created and cleaned up across companies and branches. It helps ensure reports use the right company data, avoid unnecessary snapshot deletion, and better align snapshots with fiscal year ends for more meaningful comparisons.
Original PR description
[FIX] account_reports: don't rely on self.env.company in _is_available_for This caused issues in snapshot generations, where the function is called without forcing the active company in the context.…
[FIX] account_reports: don't rely on self.env.company in _is_available_for This caused issues in snapshot generations, where the function is called without forcing the active company in the context. Relying on companies[0] should do the same in other contexts it is called in, and is more resilient. =============================================== [FIX] account_reports: snapshots: properly sandbox move lines per company when deducing the first date for snapshots =============================================== [FIX] account_reports: snapshots properly access subranches when checking snapshots to delete _accessible_branches only returns the companies that are in self.env.companies. If we want all the sub-branches (and the company itself), we should search with a child_of. This was found while debugging a test, because the accessible branches returned for a company where an empty recordset instead of the company itself, just because it wasn't in self.env.companies. =============================================== [FIX] account_reports: snapshots: don't delete snapshots uselessly when there is a temporary lock date exception Before this commit, when checking which snapshot needed to be deleted, we always removed the ones created after a temporary lock date exception, without considering the end time of this exception. Also, we were only deleting the snapshots with a higher or equal create_date, which was wrong: snapshot created before the exception should also be deleted.
Receipts for self-order delivery orders now show the customer's delivery partner details, including the delivery address. This helps staff and customers verify delivery information directly from the receipt and avoids confusion for delivery orders.
Original PR description
Before this commit: --- The delivery address was not displayed on the order receipt when the delivery preset was selected. After this commit: --- The delivery partner details, including the delivery address, are correctly displayed on the order receipt. Task-6588342 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Updates the Dominican Republic accounting localization to reflect Law 30-26 withholding rules effective July 1, 2026. This helps businesses apply the correct ISR rates for individual services, rentals, selected foreign payments, and related reporting 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#287364 Forward-Port-Of: odoo/odoo#275986
Bank reconciliation partner searches now include contacts owned by parent companies, not just global contacts or those directly linked to the selected company. This helps branch-company users find the right partners when matching bank transactions.
Original PR description
When setting a partner from the bank reconciliation control panel, the partner lookup domain only considered global contacts and contacts directly linked to the selected company IDs. This caused a multi-company issue for branch companies, where users could not find contacts owned by their parent company. The domain is now updated to include: Global contacts (company_id = false) Contacts whose company is a parent of the selected companies (company_id parent_of companyIds) Forward-Port-Of: odoo/enterprise#118719
Products with multiple variants now show the add-to-cart option when at least one variant is still in stock. This prevents shoppers from seeing an item as unavailable just because the first variant is out of stock, helping avoid missed sales.
Original PR description
Steps to reproduce: =================== 1. Create a storable product with two variants and untick "Continue selling when out-of-stock". 2. Leave the first variant at 0 in stock, put some stock on the…
Steps to reproduce: =================== 1. Create a storable product with two variants and untick "Continue selling when out-of-stock". 2. Leave the first variant at 0 in stock, put some stock on the second one. 3. try the "Add to Cart" option of the shop page. => The product has no add to cart button, as if it were sold out, while its second variant can be bought. Reordering the variants so that the one in stock comes first brings the button back. Root cause: =========== `product.template._is_sold_out()` delegates to `product_variant_id`, which is `product_variant_ids[:1]`, so a whole template is declared sold out on the sole basis of its first variant. `_website_show_quick_add()` then hides the button for the template, whatever the stock of the other variants. The helper has looked at that single variant since it was written: - [1] added it to hide the button of sold out products. Fix: ==== Consider the template sold out only when all of its variants are. The check stops at the first variant in stock, so a product that can be bought still costs a single stock lookup. [1]: https://github.com/odoo/odoo/commit/4ae197cc770cec2058e1f113209c4f93a4a0f4b2 opw-6520052 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#287882 Forward-Port-Of: odoo/odoo#286781
Fixes an issue in debug mode where date fields could appear and be saved as date-time values when setting default values. This reduces confusion for administrators and helps prevent incorrect default dates from being applied.
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
This fix prevents temporary rental planning shifts from being left behind when adding a shift to the last rental order fails due to a scheduling conflict. The shift creation and order assignment now happen together, so failed actions are fully rolled back and planning data stays clean.
Original PR description
Steps to reproduce: - On the Planning Gantt view setup resources with `role_sync_shift_rental=True`. - Drag to create a new shift. - Click "Add to Last Order" while the target Rental order requires the slot to be rescheduled to different dates, on a resource that has a slot overlapping these dates. - The reschedule creates a conflict with another slot on the same resource ,and the slot created from the drag remains. Before this commmit, The called `planning.slot` would get orphanated due to being created through a different save request by the gantt quick create before 'action_add_last_order` is called. After this commit, A single orm call is used for the `planning.slot` creation and `add_to_last_order`. If an error was to be thrown both will be unrolled with all respective computation, and no residual data remains. task-6389195 Forward-Port-Of: odoo/enterprise#131399
Non-admin users who can create or edit products can now upload product images through the media dialog without being blocked by unrelated view permissions. Uploaded images are correctly linked to the product record, keeping media organized and access checks aligned with the user’s product permissions.
Original PR description
### Description of the issue/feature this PR addresses: Non-admin users with product create rights cannot upload a product image: the media dialog upload fails with "You are not allowed to access…
### Description of the issue/feature this PR addresses: Non-admin users with product create rights cannot upload a product image: the media dialog upload fails with "You are not allowed to access 'View' (ir.ui.view) records". The same defect affects the extra product media widgets used by website_sale. ### Current behavior before PR: ImageFieldWithMediaDialog, X2ManyImageField and X2ManyMediaViewer build their mediaDialogProps without resModel/resId. Both props are optional on MediaDialog, so the upload service posts res_model: undefined to /html_editor/attachment/add_data. The route falls back to its website-editor default of ir.ui.view, and attachment_create marks the attachment public with res_id=False while the access check runs against ir.ui.view, which only administrators pass. ### Desired behavior after PR is merged: The three widgets pass the root form record from record.model.config, so the attachment is created against the edited record, is not public, and the access check applies to the model the user has rights on. opw-6493564 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Selecting a city in an employee's private address now fills in the related address details directly on the form, before saving. This removes confusion for HR users by making the visible form match the information that will be saved.
Original PR description
Issue: When you select a city in the employee's private address field, the other address fields should be populated accordingly. Even though once you save, the changes are recorded, they don't appear in real-time in the form. This is because the onchange related to the address city field was only in `hr.version`, but not `hr.employee`, so the automatic population upon the change of the city wasn't visible in the employee form view. Fix: Add _onchange_private_city_id() in hr.employee under hr_address_extended module which calls the same-name onchange in the linked version. task-6584206
Duplicating a tax now keeps its domestic tax status correctly instead of accidentally marking it as non-domestic. This helps preserve accurate tax configuration and reduces the risk of accounting setup errors after copying existing taxes.
Original PR description
_compute_is_domestic checks whether the company's domestic_fiscal_position_id is part of the tax's fiscal_position_ids. Because this field is both stored and precompute=True, duplicating a tax recomputes it on a virtual new() record before insert. On that virtual record, fiscal_position_ids is resolved through NewId pseudo-ids while company_id (a plain Many2one) keeps its real id, so the containment check always failed, silently turning is_domestic into False on any duplicate. Use fiscal_position_ids._origin to compare against the real underlying records instead. opw-6575113 Forward-Port-Of: odoo/odoo#289638 Forward-Port-Of: odoo/odoo#289246
This fix prevents an error when users open the rental order lines schedule from a rental product that no longer has variants. The schedule now opens normally instead of trying to use a missing product variant, improving reliability for rental product management.
Original PR description
Currently, an error occurs when opening the Rental Order Lines Schedule view. **Steps to Reproduce:** - Install the `sale_stock_renting` module. - Go to `Settings` and enable `Variants`. - Go to…
Currently, an error occurs when opening the Rental Order Lines Schedule view. **Steps to Reproduce:** - Install the `sale_stock_renting` module. - Go to `Settings` and enable `Variants`. - Go to `Rental` > `Products` and create a product. - In the `Attributes & Variants` tab, add an attribute with two values and save. - Delete all variants using the `Variants` smart button or from `Inventory` > `Products` > `Product Variants`. - Return to the `Product` and click the `In Renting smart button`. `IndexError: list index out of range` When a product template is created, a product variant is automatically generated if the product has no attributes. Deleting this variant also deletes the product template [1]. By explicitly creating attribute values, a dynamic product variant is generated. Deleting this variant does not delete the product template because of the dynamic attribute [2]. When the In Renting button is clicked, it it going to open the rental order lines Schedule view and adds default_product_id to the action context. Since no product variants exist anymore, accessing the default product variant raises an error [3]. This commit ensures that default_product_id is not added to the context when the product template has no product variants [1]: https://github.com/odoo/odoo/blob/1f70b81eeebcc62b64c18772915e2f4696e189e7/addons/product/models/product_product.py#L486 [2]: https://github.com/odoo/odoo/blob/1f70b81eeebcc62b64c18772915e2f4696e189e7/addons/product/models/product_product.py#L478-L480 [3]- https://github.com/odoo/enterprise/blob/98ec35f3f17a2b706ad4fcc4d6161d12d05eadfd/sale_renting/models/product_product.py#L69 [4]: https://github.com/odoo/odoo/blob/942d3892c33e7ccdcfec260c3da37095069b699e/addons/stock/models/product.py#L635-L643 [5]: https://github.com/odoo/odoo/blob/942d3892c33e7ccdcfec260c3da37095069b699e/addons/stock/models/product.py#L601-L612 sentry-7637715724 Forward-Port-Of: odoo/enterprise#132028 Forward-Port-Of: odoo/enterprise#126356
The point of sale search now finds product variants when staff search by a single attribute value, such as a size or color. This helps cashiers locate the right product more reliably and reduces missed search results during checkout.
Original PR description
In the POS search bar, searching for an attribute value (e.g., "M") returned products with multi-value attributes (e.g., Size: M - L), but omitted products with single-value attributes (e.g., Size: M). This occurred because backend `_compute_display_name` filters out single-value attribute lines from variant display names. As POS `searchString` relied on `display_name`, `name`, `default_code`, and `barcode`, the attribute value name was missing from single-value variant search strings. task-id: 6431718 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#279428
Users can now customize the Inventory Overview by removing graph cards without causing the page to crash. This keeps Studio customizations stable and prevents errors when dashboard graph data is intentionally no longer shown.
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#285593Email template editing now correctly blocks video embeds such as YouTube videos. This prevents unsupported video content from being stripped out during saving, reducing the risk of broken or empty template content.
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
Closing a form or dialog during a file upload no longer triggers Odoo client error popups when the upload finishes. This prevents confusing interruptions for users and avoids uploaded attachments being left unlinked when the original screen is gone.
Original PR description
Description of the issue/feature this PR addresses: A file upload through `FileInput` (and therefore every `many2many_binary` field) still calls `onUpload` after the component has been destroyed.…
Description of the issue/feature this PR addresses:
A file upload through `FileInput` (and therefore every `many2many_binary` field) still calls `onUpload` after the component has been destroyed. When the form or dialog holding the field goes away while the upload request is in flight, the response crashes the web client with two "Odoo Client Error" popups and the uploaded attachment is orphaned.
Steps to reproduce on a stock database (19.0, and 17.0 carries the same code):
1. Open an Email Template form (Settings > Technical > Email Templates), page *Options*, field *Attachments*. Any `target="new"` wizard with a `many2many_binary` field shows the same thing, e.g. the Recruitment *Refuse* wizard.
2. Pick a file that takes a moment to upload (a few MB, or a slow connection).
3. Before the file tile appears, leave the form through the breadcrumb, or close the wizard with Escape, its close button, or its action button. Nothing in the field shows that an upload is still running, so users do this routinely.
Current behavior before PR:
When the upload response arrives, two errors are raised:
```
UncaughtPromiseError > Component is destroyed
at webRead <- _loadRecords <- _applyCommands <- addAndRemove
TypeError: Cannot set properties of null (setting 'value')
at FileInput.onFileInputChange
```
Cause: `FileInput` posts the file through the `http` service, which is not protected against a destroyed component (unlike `orm`), so the response still reaches `onFileInputChange` after the component, the field and the form controller are gone. It then calls `onUpload`, which for `Many2ManyBinaryField` links the attachment through the form controller's protected ORM (hence the rejection), and it clears a file input ref that no longer exists (hence the TypeError).
We hit this in production on three different wizards over a few months. The client error logs users saved all carry the same trace, and the audit log shows the dialog's action completing a fraction of a second before the upload landed.
Desired behavior after PR is merged:
After the upload resolves, `onFileInputChange` stops when the component is destroyed: nobody is left to receive the uploaded files, and this matches how the protected services already treat a destroyed component. No error is raised, and `onUpload` is not called.
A Hoot test in `file_input.test.js` mounts a `FileInput` behind a `t-if`, starts an upload, unmounts the input while the upload is pending, resolves the upload, and asserts that `onUpload` was not called. It fails before the fix with the two errors above and passes after.
`master` already guards the ref write through its signal-style refs, but still calls `onUpload` on the destroyed component, so the first error remains there.
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#286831Fixes a problem that could block creating a new website when eCommerce and design themes were installed. The setup process now correctly prepares the theme content needed for all relevant websites, preventing misleading errors or failed configuration.
Original PR description
# How to reproduce - Start a new db with the design-themes addons - Install the eCommerce module - Skip the first website configurator - Go to Website > Configuration > Settings > New website - Go…
# How to reproduce - Start a new db with the design-themes addons - Install the eCommerce module - Skip the first website configurator - Go to Website > Configuration > Settings > New website - Go through all the steps as usual # The issue At the last step, you will get, depending on the version either : - A traceback, pointing out the fact that a template is missing - A popup signaling that another module operation is being processed, even though there are none The configurator won't go through in both cases # Cause When calling `configurator_apply`, we will try to get the content of every snippet specific to theme installed with the website : https://github.com/odoo/odoo/blob/ef6765208d400aa1252cd4dff5bc074ea551bbc2/addons/website/models/website.py#L910 But, when trying to find the 'website_sale.configurator_homepage_s_dynamic_snippet_category_list' view, we find nothing and an error is thrown. This view should be created by the `_generate_primary_snippet_templates` function, which is called when loading any theme module or `website` or `website_sale`: https://github.com/odoo/odoo/blob/ef6765208d400aa1252cd4dff5bc074ea551bbc2/addons/website/models/ir_module_module.py#L594 The issue is that, to know which snippets to install, that function relies on `get_current_website()`: https://github.com/odoo/odoo/blob/ef6765208d400aa1252cd4dff5bc074ea551bbc2/addons/website/models/ir_module_module.py#L694 Which relies on multiple things to find the current website, notably the current request's session : https://github.com/odoo/odoo/blob/ef6765208d400aa1252cd4dff5bc074ea551bbc2/addons/website/models/website.py#L1374-L1381 Since [this commit], the request is not kept when creating a new registry, which is done when loading a module. `get_current_website()` fallbacks on the first website's theme, which is the default theme (since we skipped the first configurator). That theme does not have the `website_sale` snippet in its manifest, contrary to others. # Proposed solution Since a new registry is created, we cannot use the context, nor the current request. We also cannot add a parameter to the `_generate_primary_snippet_templates` function since we are in stable (It would not help much anyway because this function is called when loading a module, so giving the current website is not possible). This means that we have to stop relying on `get_current_website()` to differentiate between the two use case : - Installing a theme module - Installing a module with specific snippets We can determine if the module being installed is a theme by looking at its category. If it is not, we assume its the other use case as there should not be any other. In that case, instead of installing the snippets of the theme of the current website, we check the theme of all websites. Later in the code, we take only distincts snippets and check that a corresponding view do not already exist, so there should be no risk of duplicate : https://github.com/odoo/odoo/blob/1f2ea7be8377c906ded75da893d739109db93c4d/addons/website/models/ir_module_module.py#L692 https://github.com/odoo/odoo/blob/1f2ea7be8377c906ded75da893d739109db93c4d/addons/website/models/ir_module_module.py#L712 https://github.com/odoo/odoo/blob/1f2ea7be8377c906ded75da893d739109db93c4d/addons/website/models/ir_module_module.py#L608 Furthermore, I believe it makes more sense to check every website anyway. If the `website_sale` module is installed, and there already exists multiple website with different themes, these themes might require different `website_sale` specific snippets. opw-6517556 [this commit]: https://github.com/odoo/odoo/commit/be8c85b4ccb10d6f16ddea3ab493e4c9aac749e1 Forward-Port-Of: odoo/odoo#285935
When a project’s company is updated, its linked Documents workspace now updates consistently when all related projects share the same company. This prevents workspaces and documents created through quick project setup from being left without the correct company assignment.
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
The checkout now rejects combo products when required choices are missing or duplicated. This prevents customers from completing purchases with incomplete product configurations, reducing order errors and support follow-up.
Original PR description
Steps to reproduce: =================== 1. Publish a combo product whose choices each offer more than one item, so the combo configurator is shown. 2. On the shop, open the product and click "Add to…
Steps to reproduce: =================== 1. Publish a combo product whose choices each offer more than one item, so the combo configurator is shown. 2. On the shop, open the product and click "Add to Cart". 3. Select an item for one combo choice only. The dialog's "Add to cart" button stays disabled. 4. Remove the `disabled` attribute from that button with the browser developer tools and click it. => The combo is added to the cart with one of its choices unanswered, and the order can be paid in that state. Root cause: =========== The incomplete selection is only prevented on the client side, by disabling the button until every choice has been answered. Server side, `/website_sale/combo_configurator/update_cart` only rejects a completely empty selection, never that the selected items cover every choice. Fix: ==== Reject the request unless the selected combo items cover exactly the combo choices of the product. Comparing the set of choices rather than counting the items also rejects a selection answering the same choice twice while leaving another one unanswered. opw-6478837 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#286989 Forward-Port-Of: odoo/odoo#283465
Fixes an appointment scheduling issue where staff could overbook a shared resource from the planning view, which later blocked customers from booking online. The change keeps resource capacity calculations from going negative, helping appointments remain bookable and capacity limits accurate.
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#130520This change reverses a previous update that caused vendor bill pages to load much more slowly when checking for duplicate receipts. It restores the prior behavior in this stable version to avoid delays for accounting users, while a fuller adjustment is planned elsewhere.
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#288748The Time Off Ledger now shows expected working hours based on each employee’s actual schedule for that weekday, rather than using a weekly average. This makes reports more accurate for teams with shorter or longer workdays on specific days, such as reduced Friday hours.
Original PR description
The Time Off Ledger report shows the same "Expected Hours" value for every working day of an employee, even when their working schedule assigns different hours to different weekdays. ### Steps to…
The Time Off Ledger report shows the same "Expected Hours" value for every working day of an employee, even when their working schedule assigns different hours to different weekdays. ### Steps to reproduce: 1) Install hr_holidays_attendance. 2) Create/assign a working schedule where daily hours vary across the week (e.g. Monday-Thursday 8.5h, Friday 5h). 3) Go to Attendance > Reporting > Time Off Ledger, filter on that employee. ### Observed behavior: "Expected Hours" is identical on every day (the schedule's average hours/day), regardless of which weekday the row is for. ### Expected behavior: "Expected Hours" should match the hours actually scheduled for that specific weekday, so a shorter Friday shows less than a full Monday. ### Root cause: `Time off Ledger` is a SQL view. Its `_select()` builds `expected_hours` from `rc.hours_per_day` at [1], i.e. `resource.calendar.hours_per_day`, a field explicitly labelled **"Average Hour per Day"** and is computed via [_get_hours_per_day] as `hours_per_week / days_per_week`, a single flat value for the whole calendar. The per-weekday hours are available in `resource.calendar.attendance` it stores `dayofweek`, `hour_from`/`hour_to` and a computed `duration_hours` per line, and can legitimately differ per weekday (e.g. Monday 8h vs Friday 4h) at [2]. The report's own [_cte_cal_workday()] CTE already reads this table to decide whether a weekday is a working day (`cal_workday`, joined as `cw` in `_from()`), but discarded the actual hours, keeping only `(calendar_id, dayofweek)`. `_select()` then fell back to the calendar-wide average via `rc.hours_per_day` instead of the per-day value. Example: calendar with Monday 8h and Tuesday 4h -> `hours_per_day` averages to 6h; the report showed 6h on both Monday and Tuesday instead of 8h and 4h respectively. [1]- https://github.com/odoo/odoo/blob/5568f6e472e2e53bc2931e744421015b0f0f3550/addons/hr_holidays_attendance/report/hr_leave_attendance_report.py#L256 [_get_hours_per_day]: https://github.com/odoo/odoo/blob/5568f6e472e2e53bc2931e744421015b0f0f3550/addons/resource/models/resource_calendar.py#L703-L707 [2]- https://github.com/odoo/odoo/blob/5568f6e472e2e53bc2931e744421015b0f0f3550/addons/resource/models/resource_calendar_attendance.py#L15-L31 [_cte_cal_workday()]: https://github.com/odoo/odoo/blob/5568f6e472e2e53bc2931e744421015b0f0f3550/addons/hr_holidays_attendance/report/hr_leave_attendance_report.py#L82-L89 ### Fix: `_cte_cal_workday()` now aggregates `SUM(duration_hours)` per `(calendar_id, dayofweek)` from `resource_calendar_attendance`, instead of just selecting the distinct keys. `_select()` reads this per-weekday value (`cw.hours_per_day`) instead of the calendar-wide `rc.hours_per_day` for both `expected_hours` and `difference_hours`. **opw-6425167** Forward-Port-Of: odoo/odoo#289034 Forward-Port-Of: odoo/odoo#280664
Fixed an issue where settling a made-to-order sale with manufacturing in Point of Sale could fail at payment validation. The change ensures only valid delivery records are handled, helping cashiers complete these orders without interruption.
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#284103Employees using the Belgian mobility budget can now also choose a company bike. This fixes a restriction so salary package choices better match what is legally allowed in Belgium.
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
Belgian payroll now calculates worked day amounts more accurately by excluding time credit and extra hours where they should not be counted. This helps prevent incorrect payslip amounts and improves payroll reliability for affected employees.
Original PR description
We forgot to filter out time credit and extra hours when computing worked day lines amount.
The Irish balance sheet report now places current-year profit or loss only in the correct financial year line. This prevents the same invoice amount from appearing twice, improving accuracy for Irish accounting reports.
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
Fixes an issue where Spanish credit notes using the equivalence surcharge could be rejected by TicketBAI/Batuz because the surcharge percentage was sent as a negative value. The percentage now stays positive, allowing affected refunds to pass tax agency validation.
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
OSS sales for Spanish VAT reporting are now assigned to the correct Modelo 303 box, casilla 123, instead of casilla 124. This helps ensure reported amounts appear in the right place for compliance and reduces the risk of incorrect tax filings.
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
Factur-X invoice files now include the correct customer name when an invoicing address has no name of its own. This helps prevent compliance failures and reduces the risk of rejected electronic invoices.
Original PR description
Description of the issue/feature this PR addresses: The Factur-X Refactoring (https://github.com/odoo/odoo/pull/284123) introduced an error that can result in the generated XMLs not being compliant…
Description of the issue/feature this PR addresses: The Factur-X Refactoring (https://github.com/odoo/odoo/pull/284123) introduced an error that can result in the generated XMLs not being compliant due to the partner name being missing (BT-44) Current behavior before PR: When a contact has an invoicing address without a name set, then the new Factur-X generation does not fall back to the parent name, leaving the corresponding XML entry empty, which is therefore pruned, leaving the Factur-X without a BT-44 and thus failing BR-07 Desired behavior after PR is merged: The new generation method uses the same data source and fallback method as the old Factur-X generator, pulling the `display_name` of the `commercial_partner_id` if the address doesn't have a `name`. This greatly reduces the chance of a generated Factur-X failing BR-07. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#289661 Forward-Port-Of: odoo/odoo#289498
This fixes an issue where adding accordion content to default invoice terms and conditions could break after saving. Business users can now use richer page blocks in the terms editor without losing functionality or hitting errors.
Original PR description
# How to reproduce - Go to Settings > Default Terms & Conditions > Preview - Open the editor - Add a Content Block - Add an Accordion in that Block - Save # The issue The accordion gets messed up and…
# How to reproduce - Go to Settings > Default Terms & Conditions > Preview - Open the editor - Add a Content Block - Add an Accordion in that Block - Save # The issue The accordion gets messed up and trying to add a new item to that accordion raises a traceback # Cause `invoice_terms_html` is an html field with some sanitization enabled : https://github.com/odoo/odoo/blob/c11ca6d10cac99b367078823dafef46808346267/addons/account/models/company.py#L172 This sanitization will remove the accordion snippet's buttons that controls the functionality of the snippet : https://github.com/odoo/odoo/blob/c11ca6d10cac99b367078823dafef46808346267/addons/website/views/snippets/s_accordion.xml#L9 This issue was already addressed by : https://github.com/odoo/odoo/commit/b6b4db5fb5690436a4284f6a22abf9f3b346a324 But it is not enough in the case of the accordion. The buttons will be removed by the lxml clean.Cleaner : https://github.com/odoo/odoo/blob/c11ca6d10cac99b367078823dafef46808346267/odoo/tools/mail.py#L364 # Proposed Solution Disable sanitization entirely, like for blog's content, which can also be edited in the website editor : https://github.com/odoo/odoo/blob/c11ca6d10cac99b367078823dafef46808346267/addons/website_blog/models/website_blog.py#L29 opw-6500378 Forward-Port-Of: odoo/odoo#285255
Generic demo leave types in Time Off no longer carry a specific country, so they will not prevent setting or changing the main company country in demo environments. This keeps safeguards for real country-specific leave data while avoiding setup issues during demo or development installs.
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
This fix ensures fiscal positions are selected in a consistent order when imported records share the same priority. It helps prevent unexpected differences in tax or accounting rule selection caused by database ordering details.
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
This fix updates how employee work contact details are calculated so access rights are handled correctly. It helps prevent unexpected behavior when HR information is viewed or updated by different users.
Original PR description
Ensure correct access to avoid unexpected behavior opw-6560194 Forward-Port-Of: odoo/odoo#288661 Forward-Port-Of: odoo/odoo#288063
Attendance officers without full Employees app access can now open the Employees menu from Attendances without seeing an access error. The menu now shows an attendance-focused employee view with only the information they are allowed to see, helping managers continue their attendance work smoothly.
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 Guatemala fiscal positions are applied in a consistent order when creating invoices. It prevents occasional incorrect tax selection, improving reliability for Guatemalan electronic invoicing.
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 update corrects minor English wording issues in the Peppol activation flow. It helps the activation experience feel more polished and trustworthy 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
The attendance kiosk now shows weekday names in the company's selected language instead of always using English. This creates a more consistent translated experience for employees using kiosk mode in non-English environments.
Original PR description
Problem:
The weekday displayed in the attendance kiosk header is always shown in English, even when the comapany contact's language is changed.
This happens because `DateTime.toFormat("cccc")` uses Luxon's locale, but the `DateTime` instance is created without setting the current UI locale.
Steps to reproduce:
1. Install `hr_attendance`.
2. Change the company contact language to a language other than English (e.g. French).
3. Open the Kiosk Mode.
4. Observe that the date and UI are translated, but the weekday remains in English (e.g. "Monday" instead of "Lundi").
Fix:
Set the Luxon locale from the document language before formatting the weekday. This ensures `toFormat("cccc")` returns the weekday in the active Company contact language.
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#289173
Forward-Port-Of: odoo/odoo#274655Employee-paid expenses now remain visible in the Waiting Reimbursement dashboard tile and filter after their journal entry is posted. This keeps reimbursement tracking accurate until the employee is actually paid, preventing approved expenses from temporarily disappearing.
Original PR description
#### Description of the issue/feature this PR addresses: The "Waiting Reimbursement" tile stops counting an employee-paid expense once its journal entry is posted, instead of once the employee is reimbursed. #### Current behavior before PR: _compute_state sets the state to 'posted' as soon as a move is posted and unpaid, so an own_account expense never stays 'approved', yet get_expense_dashboard still totals the tile on 'approved' only. Regression from the removal of hr.expense.sheet in 704a5a1, which carried the 'posted' state until then. The "Waiting Reimbursement" search filter has the same condition. #### Desired behavior after PR is merged: The tile and the filter also match 'posted', so the expense stays counted until the payment is registered. opw-6410953 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#281882
The test suite for French electronic invoicing has been corrected so it no longer expects PDP-specific notes when the PDP module is not installed. This helps keep automated validation reliable across valid installation setups and reduces false test failures.
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
The timesheet grid now only greys out public holidays that belong to the company currently being viewed. This prevents employees in one company from seeing another company’s holidays as unavailable work days, improving clarity for multi-company timesheet planning.
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 or payable reports when using an Open On date without triggering an error. This restores access to the underlying journal item details needed for auditing balances.
Original PR description
Currently, an error occurs when a user clicks on a cell in the aged partner balance report. **Steps to Reproduce:** - Install the `account_reports` module. - Go to `Invoicing` > `Reporting` >…
Currently, an error occurs when a user clicks on a cell in the aged partner balance report. **Steps to Reproduce:** - Install the `account_reports` module. - Go to `Invoicing` > `Reporting` > `Partner Reports` > `Aged Receivable`. - Click on the `date filter`, select `Open On`, and set `any date`. - Click on any `amount` in the `Total Aged Receivable line`. `TypeError: the JSON object must be str, bytes or bytearray, not dict` After the [recent commit] that adds the context to the action, when preparing the action to open the journal items corresponding to the selected cell in the aged partner balance report, the context is added to the action [1]. When an "Open On" date is set, the code attempts to convert the context using json.loads() before adding the search_default_open_on key to it [2]. but, the context is already a dictionary, which raises the error [3]. This commit ensures that, since the action context is already a dictionary, the search_default_open_on key and its value are added directly to the context. [recent commit]: https://github.com/odoo/enterprise/commit/9678a1b987a5aa87922b72d972dd7f73a689afee [1]- https://github.com/odoo/enterprise/blob/8fceecea541564a9877babfc31b7e0bf896573a6/account_reports/models/account_aged_partner_balance.py#L357-L359 [2]- https://github.com/odoo/enterprise/blob/8fceecea541564a9877babfc31b7e0bf896573a6/account_reports/models/account_aged_partner_balance.py#L362 [3]- https://github.com/odoo/enterprise/blob/8fceecea541564a9877babfc31b7e0bf896573a6/account_reports/models/account_aged_partner_balance.py#L361 sentry-7685491449 Forward-Port-Of: odoo/enterprise#129142
Opening certain image attachments could trigger a client error in Odoo when the attachment was linked to another attachment. This fix corrects how the attachment selection filter is provided, preventing the crash and adding test coverage to reduce the risk of recurrence.
Original PR description
### Steps to Reproduce: 1. Go to Sales > Products and select a product 2. Upload an image for the product 3. Go to Settings > Technical > Attachments 4. Click on one of the image attachments 5.…
### Steps to Reproduce: 1. Go to Sales > Products and select a product 2. Upload an image for the product 3. Go to Settings > Technical > Attachments 4. Click on one of the image attachments 5. Observe the Client Error: "Uncaught Promise > Invalid props for component 'Many2One': 'domain' is not a function" ### Issue: In `attachment_patch.js`, the domain for the `Many2OneReferenceField` is overridden when an attachment is linked to another attachment. The patch calculates the domain and incorrectly assigns a static Array (using `.toList()`) to `props.domain`. In Odoo 19, the `Many2One` component enforces strict prop validation (caught when Owl's debug mode is enabled during testing), causing a crash because it expects the domain prop to be a function. ### Solution: We can just wrap the resulting domain Array in an arrow function so that it evaluates dynamically when the `Many2One` component mounts. Also added a unit test to explicitly cover this patched scenario. The test manually applies a local patch to `Many2OneReferenceField.prototype` to accurately replicate the environment and verify the fix. opw-6563954 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#287951