Thursday, September 10, 2026
37 changes · saas-19.2
Security fixes and vulnerability patches
Self-invoicing in Point of Sale now prevents customers from changing existing order customer details unless they are authorized to do so. The update also checks required invoicing information before creating invoices, reducing unauthorized changes and failed or incomplete invoicing.
Original PR description
Before this commit: ------------------- - During self-invoicing, a public user could create a new customer or update the current order's customer data by submitting the self-invoicing form, without any access rights validation. - For logged-in users (portal or internal), invoice generation could proceed even when the user or the selected customer lacked the required invoicing information. After this commit: ------------------- - During self-invoicing, a public user can create a new customer for the order, but cannot modify the existing customer linked to the order. - For logged-in users (portal or internal), required customer information is validated before generating an invoice. Customer data can only be updated when the customer is the logged-in user's partner or a child contact of that partner. Task-6272660 Forward-Port-Of: odoo/odoo#287007 Forward-Port-Of: odoo/odoo#270112
New functionality added to Odoo
Belgian companies can now generate SAF-T reports directly in Odoo. This supports local compliance needs by making it easier to prepare standardized audit and tax data exports.
Original PR description
SAF-T reports can now be generated for Belgium. task-5129628 Forward-Port-Of: odoo/enterprise#107270
Enhancements to existing features
The Belgian payroll module now includes an updated value for the training time off threshold used in salary rule calculations. This helps keep payroll computations aligned with the latest expected payroll parameter for Belgian HR processes.
Original PR description
add new value to the training_time_off_threshold salary rule param. task-6545545 Forward-Port-Of: odoo/enterprise#131080
Resolved issues and error corrections
The Chilean electronic invoicing process now reports progress as it sends invoices, so large backlogs no longer make the scheduled job look like it failed. This helps businesses keep invoice submissions moving automatically even when daily volumes are high.
Original PR description
Steps to reproduce: - Have a large number of invoices (thousands) with l10n_cl_dte_status 'not_sent' - Let the "Cron Job - Send document to SII" cron run and time out before it can send them all -…
Steps to reproduce: - Have a large number of invoices (thousands) with l10n_cl_dte_status 'not_sent' - Let the "Cron Job - Send document to SII" cron run and time out before it can send them all - After a few consecutive timeouts, the cron gets deactivated by Odoo, leaving the backlog stuck and growing Cause of the issue: cron_send_dte_to_sii() searches for every 'not_sent' move and sends them one by one in a single unbounded loop, committing after each send but never reporting progress to the cron framework Odoo cron worker treats a job that times out without ever calling _notify_progress as a full failed run, even though most of the batch was actually sent and committed. After enough consecutive failures within a short time span, the cron is auto-deactivated, which is exactly what happens once daily invoice volume outpaces what a single cron run can send before the worker times out Solution: Report progress via ir.cron._notify_progress() after each invoice is sent. A timeout mid-run is then treated as partially done instead of failed, the cron gets rescheduled immediately instead of waiting for its daily interval, and it no longer counts towards deactivation, This lets the cron drain an arbitrarily large backlog safely over several runs instead of dying after a handful of timeouts opw-6487236 opw-6511624 Forward-Port-Of: odoo/enterprise#129789
Code cleanup and technical improvements
The accounting test suite was tidied by removing duplicate helper code and outdated commented tests. This is an internal cleanup that helps maintain the quality of future accounting updates without changing how users work in the product.
Original PR description
This commit cleans up the test suite within the `account_accountant` module by removing redundant methods and old commented code. no-task Forward-Port-Of: odoo/enterprise#130857 Forward-Port-Of: odoo/enterprise#130644
Documentation and clarification updates
This pull request records that contributor victorhachard has signed Odoo's Contributor License Agreement. This supports legal compliance for accepting contributions and has no direct impact on product features or users.
Original PR description
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Signing against 17.0 so the signature is forward-ported to all newer versions, as advised in #139403. Forward-Port-Of: odoo/odoo#287231
Vertical notebook tabs are now visually separated when using dark mode. This makes pages easier to read and navigate by clearly distinguishing each tab.
Original PR description
## Behavior Before the Commit When a user opens a page with a vertical notebook in dark mode, the different tabs aren't separate from one another. task-[5933892](https://www.odoo.com/odoo/project/4105/tasks/5933892) Forward-Port-Of: odoo/enterprise#126013
Vertical notebook tabs now line up correctly, making the affected dialogs easier to read and use. Long tab titles wrap onto a new line instead of disrupting the layout.
Original PR description
When a user opened a page with a vertical notebook, the tabs were not aligned with each other. Vertical notebooks should now have properly aligned tabs. If a tab title is too long, it will wrap onto the next line instead of breaking the layout. task-[5933892](https://www.odoo.com/odoo/project/4105/tasks/5933892) --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#279098
Creating a new job position no longer shows the same confirmation message twice in the activity log. This keeps the recruitment chatter cleaner and avoids confusion for users reviewing job position history.
Original PR description
When creating a new job position, "Job Position created" was rendered twice in the chatter log. This occurred because the mail subtype definition specified a redundant `description` field with the exact same text as the subtype's name, causing the chatter logic to display both. Removing the explicit `description` field ensures the message is only displayed once upon job creation. Task: 6486002 Forward-Port-Of: odoo/odoo#285018
Malaysian payroll calculations now use the correct SOCSO and Employment Insurance contribution rules and avoid counting employee SOCSO deductions twice. This makes net salary amounts and employer contribution reporting align more closely with official rates and external payroll references.
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#129688 Forward-Port-Of: odoo/enterprise#118138
This update cleans up references to an old developer option that is no longer supported. It helps avoid confusion for administrators and developers by ensuring the system no longer suggests using a removed feature.
Original PR description
The feature was removed in odoo/odoo#115076 Description of the issue/feature this PR addresses: Current behavior before PR: Desired behavior after PR is merged: --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#283352
This fixes a website editor issue where applying text animation across multiple text blocks could make centered headings and paragraphs shift out of alignment. Animated text now keeps the intended layout, helping users safely style website snippets without breaking their page design.
Original PR description
### Problem: Text animation used a single span around the full selection range. When a selection crossed block elements, this placed headings and paragraphs inside a span, producing invalid HTML and changing their layout. ### Steps to reproduce: 1. Drag & drop a snippet (like the "Cover" block) that contains page centered text 2. Select a range of text spanning multiple block elements within the added snippet 3. Add an animation to the selected text. <img width="800" height="200" alt="image" src="https://github.com/user-attachments/assets/45074927-4d60-4517-b65b-7ba6d667e060" /> ### Solution: This PR splits the selection by block and creates one inline animation wrapper per block instead. The builder applies animation options to the resulting elements as one group, without changing the document's block structure. task-5155887 Forward-Port-Of: odoo/odoo#283176
This fixes an issue where users without certain permissions could hit an access error when saving records that include precomputed calculated fields. The change ensures those values can still be calculated safely during record creation, reducing failed saves in affected customizations and modules.
Original PR description
**Description of the issue/feature this PR addresses:** If a field is `computed`, `precomputed`, and has `groups` defined, an `AccessError` is raised when a user who does not have the required group…
**Description of the issue/feature this PR addresses:** If a field is `computed`, `precomputed`, and has `groups` defined, an `AccessError` is raised when a user who does not have the required group creates a record because the field is precomputed. After this commit https://github.com/odoo/odoo/pull/201565/changes/48521a311a6dc857c3808db70ed359c7866aa125, Odoo checks field access in `__get__`, so an `AccessError` is now raised. If the field is not precomputed, everything works fine. Therefore, this commit prevents the error by using `sudo()` to recompute the value. I have attached a module to demonstrate the issue. **Steps to reproduce the issue:** 1. Install the attached module. [sale_margin_security_test.zip](https://github.com/user-attachments/files/31967357/sale_margin_security_test.zip) 2. Create a sales order and add a product. 3. Try to save the sales order. The AccessError is raised. https://github.com/user-attachments/assets/54c7e4e2-005f-4ced-bb62-22d0e00049d9 For more context, this module is a simple example extracted from the OCA `sale_margin_security` module, which inherits from a mixin and adds groups to the fields: https://github.com/OCA/margin-analysis/pull/285 You can see the error in this PR: https://github.com/OCA/margin-analysis/actions/runs/34254749207/job/102157600233?pr=285#step:8:126 @Tecnativa @pedrobaeza @kmagusiak @rco-odoo @Feyensv, could you please review this? --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#287257
Point of Sale now avoids charging variant extra prices twice when products are selected through barcode search or related flows. This ensures customers are billed the intended price and reduces checkout pricing errors for products with dynamic variants.
Original PR description
Steps to reproduce: - Create an attribute "Size" with values M and L, variants creation mode "Dynamically", and an extra price of 10 on L - Create a product at 30 using that attribute, and give the L…
Steps to reproduce:
- Create an attribute "Size" with values M and L, variants creation mode "Dynamically", and an extra price of 10 on L
- Create a product at 30 using that attribute, and give the L variant a barcode
- In the PoS, type that barcode in the search bar and click the card
Issue:
The line is added at 50 instead of 40. Scanning the barcode with a barcode reader was fixed by c1ae3261e895, but resolving the variant from the search bar still charges the extra price twice.
Cause:
The lst_price of a variant already contains the extra price of every attribute value that creates a variant ('always' and 'dynamic'); only 'no_variant' extras are missing from it and have to be carried by the order line as price_extra. This is what ProductConfiguratorPopup does, hence the correct price when the configurator opens.
Both openConfigurator(), when the resolved variant leaves a single value per attribute line and no popup is needed, and handleConfigurableProduct(), when configure is false, filter those values with create_variant !== 'always', so a 'dynamic' extra is added on top of a lst_price that already includes it. The same fix was made in 18.0 by d4fabfa08d1c but was lost when pos_store.js was refactored in saas-18.1.
Fix:
Filter on create_variant === 'no_variant' in both places, like the configurator popup. This supersedes the !opts.code condition of c1ae3261e895: product_template_variant_value_ids never holds 'no_variant' values, so the extra is now ignored however the line was added - scan, barcode search, sale order import or optional product.
opw-6531114
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#286460Users with Invoicing & Banks access can now set partners and reconcile bank statement lines without being blocked by an incorrect access error. The fix prevents an internal review status reset from requiring extra Bookkeeper permissions during normal reconciliation work.
Original PR description
Problem: Users with "Invoicing & Banks" access encounter an "AccessError" when setting a partner on a bank statement line or reconciling it with an invoice. Cause: During `set_line_bank_statement_line()`, the linked journal entry's `review_state` is reset to `False`. This internal update triggers `_check_review_state_access()`, which requires the "Bookkeeper" role to modify the review state. Although the user is only performing a bank reconciliation action, the review state access check incorrectly blocks the operation. Solution: Use the `skip_account_review_check` context when resetting `review_state` as part of `set_line_bank_statement_line()`. This bypasses the review state access check for this internal update while allowing users with "Invoicing & Banks" access to set a partner and reconcile bank statement lines successfully. opw-6409220 Forward-Port-Of: odoo/enterprise#126020
Spreadsheet cells now display correctly when opened in Hoot test debug mode. The fix disables cell animations during these tests, avoiding misleading or incorrect spreadsheet data while keeping normal product behavior unchanged.
Original PR description
If you try to open a spreadsheet in debug mode in the Hoot tests, you will often end up with cells with wrong displayed data. That's because cell animations don't work in hoot (it patches `requestAnimationFrame`), so we end up with cell animations stuck in the first frame of the animation. We can simply disable the cell animations in the Hoot tests, as animations are not relevant to the tests. Task: [4909027](https://www.odoo.com/odoo/2328/tasks/4909027) Forward-Port-Of: odoo/odoo#216620
Updates the Dominican Republic localization to reflect Law 30-26 withholding rates effective July 1, 2026. This helps new company setups calculate and report ISR withholdings correctly, while keeping distinct reporting categories separate for compliance.
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
This update refreshes the spreadsheet component with several fixes that make chart exports, dashboard display, search and replace, and copy-paste behavior more reliable. Users should see fewer errors and more consistent spreadsheet visuals, especially when working with charts and dashboards.
Original PR description
### Contains the following commits: https://github.com/odoo/o-spreadsheet/commit/a3d2956fce [REL] 19.2.29 [Task: 0](https://www.odoo.com/odoo/2328/tasks/0)…
### Contains the following commits: https://github.com/odoo/o-spreadsheet/commit/a3d2956fce [REL] 19.2.29 [Task: 0](https://www.odoo.com/odoo/2328/tasks/0) https://github.com/odoo/o-spreadsheet/commit/e2d97c48a0 [FIX] xlsx: fix geo chart xlsx export [Task: 4632983](https://www.odoo.com/odoo/2328/tasks/4632983) https://github.com/odoo/o-spreadsheet/commit/a302feff54 [FIX] search and replace: manage invalid range [Task: 6483222](https://www.odoo.com/odoo/2328/tasks/6483222) https://github.com/odoo/o-spreadsheet/commit/429d029490 [FIX] calendar chart: wrong groupBy choice filtering [Task: 5358625](https://www.odoo.com/odoo/2328/tasks/5358625) https://github.com/odoo/o-spreadsheet/commit/a2688b20ab [FIX] charts: show value do not work for combo chart [Task: 6528059](https://www.odoo.com/odoo/2328/tasks/6528059) https://github.com/odoo/o-spreadsheet/commit/320c91c3a0 [FIX] dashboard: fix background color [Task: 6497278](https://www.odoo.com/odoo/2328/tasks/6497278) https://github.com/odoo/o-spreadsheet/commit/e8a1911f8a [FIX] grid: hide AddRowFooter when the mainViewport is too small [Task: 6103620](https://www.odoo.com/odoo/2328/tasks/6103620) https://github.com/odoo/o-spreadsheet/commit/df01b7dc91 [FIX] ComposerHighlight: Highlight the correct sheet with `#` [Task: 6527596](https://www.odoo.com/odoo/2328/tasks/6527596) https://github.com/odoo/o-spreadsheet/commit/57a27fc93c [FIX] clipboard: typo on copy/paste on a merge [Task: 6515850](https://www.odoo.com/odoo/2328/tasks/6515850) Co-authored-by: Florian Damhaut (flda) <flda@odoo.com> Co-authored-by: Anthony Hendrickx (anhe) <anhe@odoo.com> Co-authored-by: Alexis Lacroix (laa) <laa@odoo.com> Co-authored-by: Lucas Lefèvre (lul) <lul@odoo.com> Co-authored-by: Adrien Minne (adrm) <adrm@odoo.com> Co-authored-by: Ronak Mukeshbhai Bharadiya (rmbh) <rmbh@odoo.com> Co-authored-by: Dhrutik Patel (dhrp) <dhrp@odoo.com> Co-authored-by: Rémi Rahir (rar) <rar@odoo.com> Co-authored-by: Pierre Rousseau (pro) <pro@odoo.com> Co-authored-by: Vincent Schippefilt (vsc) <vsc@odoo.com> Co-authored-by: Marceline Thomas (matho) <matho@odoo.com>
This fix prevents non-product sales order lines used only for display or notes from being selected or copied during manufacturing order confirmation. It helps keep manufacturing and inventory records accurate by ensuring stock movements are linked only to valid sales lines.
Original PR description
Issue: ====== before this commit, user was able to select a display SOL and at MO confirmation the SOL was copied to the stock move, so we end up with a stock move linked to a display SOL, which is not correct. Solution: ========= - add a domain on the SOL field as first guard layer - add check on the SOL on MO confirmation to prevent copying a display SOL to the stock move opw-6473017 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#287309 Forward-Port-Of: odoo/odoo#286534
The Mexican POS self-invoicing test was updated to match the newer flow where public users can no longer edit customer details directly. This keeps automated checks accurate and helps ensure receipts and self-invoicing continue to work reliably under the updated customer data rules.
Original PR description
Before the related pr commit: - Public users could update customer data during the self-invoicing flow. - The test_qr_code_receipt_mx test relied on this behavior when updating customer data. After the ref commit: - Public users can no longer update customer data during self-invoicing. - Update test_qr_code_receipt_mx to create a new partner with the required customer data when the order is not linked to a customer. Related PR: odoo/odoo#283470 Task-6272660 Forward-Port-Of: odoo/enterprise#130700 Forward-Port-Of: odoo/enterprise#129948
The Indian e-invoicing check now opens a list only when missing e-invoices are actually found. When it does open, it is limited to the specific invoices that need attention, avoiding confusing unfiltered or overly broad results.
Original PR description
The `missing_einvoice` check passed custom `views` but no explicit `domain`, so its action ignored the `moves` recordset and instead opened all matching `account.move` records — an unfiltered list when no invoices were missing, and every eligible invoice (not just the flagged ones) otherwise.
Guard the action to only build when `moves` is non-empty, and pass `domain=[('id', 'in', moves.ids)]` so it's always scoped to the invoices actually found.
task-6544790
Forward-Port-Of: odoo/enterprise#130483Fixed a display issue in quotations where section totals could appear under the wrong column or be cut off after enabling the Related Intervention column. This keeps totals readable and correctly aligned for users preparing sales documents with field service planning details.
Original PR description
Steps to reproduce: --- - Install `planning_field_service_sale_timesheet` module. - Create a quotation. - Add a section line, then a product line below it. - Enable the optional `Related…
Steps to reproduce: --- - Install `planning_field_service_sale_timesheet` module. - Create a quotation. - Add a section line, then a product line below it. - Enable the optional `Related Intervention` column from the column selector. Issue: --- - When the Related Intervention optional column is enabled, the section line's aggregated Amount value appears in the wrong column or is clipped. Root cause: --- - `getSectionColumns()` computes the section title's colspan as `columns.length - sectionCols.length + 1` [1]. This formula assumes all non-section columns sit in the middle of the list, with the aggregated amount column (`price_subtotal`) anchored at the right end. - The `planning_slot_id` [2] was added via `position="inside"`, which appends it at the end of `<list>` after `price_subtotal` — breaking that assumption by placing a non-section column after the aggregated amount column. - This makes the section title colspan one unit too wide when `task_id` is enabled, shifting the section's aggregated Amount cell past the `price_subtotal` header column. Fix: --- - Changed the xpath to insert `planning_slot_id` after the `discount` field, placing it in the middle of the column list where the colspan formula correctly absorbs it into the title span and keeps the amount column aligned. [1]: https://github.com/odoo/odoo/blob/1ad38d396eb60858231ad667d9e71e8bee5b5ab2/addons/account/static/src/components/section_and_note_fields_backend/section_and_note_fields_backend.js#L453 [2]: https://github.com/odoo-dev/enterprise/blob/5b969ec8adba782f793ce3bae4ab175cc14d13d0/planning_field_service_sale_timesheet/views/sale_order_views.xml#L9-L11 Before: --- <img width="1248" height="210" alt="image" src="https://github.com/user-attachments/assets/5863331c-5fea-43cc-ab58-ae26546d97ee" /> After: --- <img width="1243" height="252" alt="image" src="https://github.com/user-attachments/assets/0c336f04-d3ab-4aad-b6d1-eb4e734118f7" /> opw-6472638 --- Forward-Port-Of: odoo/enterprise#129125
This fixes an issue where purchase stock reordering rule tests could fail in databases installed without demo data. It makes the test setup work reliably even when optional inventory tracking features are not pre-enabled, improving confidence in automated quality checks.
Original PR description
Steps to reproduce ------------------ 1. install purchase_stock in a database without demo data 2. run any test in TestReorderingRule 3. observe the error: can't write on invisible field 'tracking' Note: This fix is only relevant when you run the test on an empty database, the demo data enables the Lots & Serial Numbers.
Online store product searches now use better database indexing for product descriptions. This fixes very slow search results in affected stores, improving the shopping experience and reducing wait times.
Original PR description
### Description: Following commit e9d435d1c865e1f462ce2e4bb2c0715ec97c8b10, some fields were missing proper indexes, causing the e-commerce search to be slow. This commit adds an indexes on `description_ecommerce` to optimize the filters of the query. ### Benchmark: | Nb of product | Before | After | |-----------------|----------|---------| | 747 | 6m 15s | 91 ms | ### Reference: opw-6513905 ___ I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#287381
Timesheet email suggestions from designated sender addresses are no longer automatically linked to projects or tasks. This prevents incorrect timesheet categorization for special addresses such as online@odoo.com and keeps suggested work items more accurate.
Original PR description
Before this commit, incoming email suggestions were automatically matched to tasks or projects whenever the sender’s email address could be linked to a partner in the database. In certain niche cases, this resulted in unwanted matches for specific email addresses or partners. After this commit, Email suggestions originating from designated addresses (e.g., online@odoo.com) are now excluded from task/project matching. task-[6414046](https://www.odoo.com/odoo/project/4105/tasks/6414046) Forward-Port-Of: odoo/enterprise#125645
The Point of Sale customer list now loads more smoothly when many customers are present. Menu controls for each customer are only prepared when needed, reducing freezes and improving usability on slower devices.
Original PR description
Opening the customer list with a few hundred partners freezes the UI for several seconds on a slow device (~300ms on a desktop, 5.6s with a 6x CPU throttle in the Chrome profiler). Each `PartnerLine`…
Opening the customer list with a few hundred partners freezes the UI for several seconds on a slow device (~300ms on a desktop, 5.6s with a 6x CPU throttle in the Chrome profiler). Each `PartnerLine` instantiates a full `Dropdown` for its "≡" menu. Setting up a `Dropdown` registers about ten lifecycle hooks (`useDropdownNesting`, `useNavigation`, `useDropdownGroup`, `usePopover`, `useEffect`, ...), and each hook registration eagerly allocates two `OwlError` objects to keep a stack trace. Multiplied by hundreds of rows, this dominates the render: ~60% of the click task is spent constructing errors for menus that will never be opened. Render a plain button per row instead and only mount the `Dropdown`, driven by a `useDropdownState`, once that button is clicked. The `Dropdown` opens its popover on mount when its state is already open and is unmounted again when it closes. The placeholder button keeps the classes the `Dropdown` would add to its toggler so the DOM, the styling and the tour selectors (`button.dropdown`) are unchanged. opw-6453848 Description of the issue/feature this PR addresses: Current behavior before PR: Desired behavior after PR is merged: --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#285974
Generating sample timesheet data now skips partners that do not have an email address, preventing an error in the Timesheet Assistant. This helps users complete sample data setup reliably even when customer records are incomplete.
Original PR description
Forward-Port-Of: odoo/enterprise#131002
This fix prevents invoice forms using document extraction from crashing when users click into certain code-style fields. It makes the field detection more precise so the form only reacts to valid field elements, improving reliability without changing user workflows.
Original PR description
Follow-up of odoo/odoo#287085, as suggested by @aab-odoo: on a stable branch the fix belongs here rather than in the ace templates. Fixes odoo/odoo#286871. opw-6543997 ### The problem The `focusin`…
Follow-up of odoo/odoo#287085, as suggested by @aab-odoo: on a stable branch
the fix belongs here rather than in the ace templates.
Fixes odoo/odoo#286871.
opw-6543997
### The problem
The `focusin` listener of the extract mixin walks up from the focused node with
`closest(".o_field_widget,.o_field_cell")` and assumes whatever it finds
identifies a field. Not every `.o_field_widget` node does: a widget template
may render its own `.o_field_widget` inside the one the `Field` wrapper already
provides, and that inner div has no `name`. `web.AceField` (`widget="code"`)
does exactly that:
```html
<div name="my_field" class="o_field_widget o_field_code ...">
<div class="o_field_widget oe_form_field o_ace_view_editor oe_ace_open">
<div class="ace_editor">... <textarea class="ace_text-input">
```
The listener stops on the inner div, `getFullFieldName` finds no name and
builds `"<parent_field>.null"`. Since the name now contains a dot, `getBoxType`
takes the x2many branch:
```js
modelFieldType = this.props.record.data[parentField]?._config.fields[fieldName]?.type;
```
On a code field `record.data[parentField]` is a plain string. It is truthy, so
the optional chaining does not short-circuit, but it has no `_config` →
`undefined.fields` → `TypeError: Cannot read properties of undefined (reading
'fields')`, and the whole form breaks.
`account_invoice_extract` installs the renderer for `account_move_form` with
`force: true`, so this runs on every invoice form.
### Steps to reproduce
1. Install `account_invoice_extract`.
2. Put a `widget="code"` field on the `account.move` form view (with
`l10n_ar_edi` installed there are already two on the ARCA tab).
3. Give the field a value — with an empty value the error does not happen,
`record.data[parentField]` is `false` and the optional chaining cuts.
4. Enable developer mode and click inside the code editor.
Reproduced on a plain 19.0 runbot build.
### The fix
Restrict the selector to `.o_field_widget[name]`, so nodes that cannot identify
a field are skipped. The `focusout` listener a few lines below also matches
`.o_field_widget`, but it only calls `onBlurFieldWidget()` and never resolves a
name, so it is left as is.
The duplicated class in the ace templates looks like the actual root cause and
is handled separately in odoo/odoo#287085, retargeted to `master`.
Forward-Port-Of: odoo/enterprise#130952The customer list in Point of Sale now avoids repeated due-balance calculations for each customer row. This makes screens with many customers load faster and reduces the chance of showing the wrong due amount for customers with similar names.
Original PR description
Opening the customer list with a few hundred partners is slow; part of the time is spent in `getPartnerCredit`. The `PartnerLine` template reads `this.partnerInfos` eight times per row, and each call runs `getPartnerCredit` again. For a contact, that method resolves the partner carrying the due by scanning every loaded partner for one named like `parent_name`, so the cost grows with the square of the number of partners. Cache the getter in a template variable so it is evaluated once per row, and resolve the due partner through the loaded `commercial_partner_id` instead of the name scan. This is also what the server sums the dues by, and it no longer picks a wrong homonym. `refreshTotalDueOfPartner` used the same lookup and now shares it. opw-6453848 Forward-Port-Of: odoo/enterprise#130015
This fix prevents a company's designated project documents folder from being archived by mistake. It helps keep project document organization intact and avoids disruption for users relying on the shared projects folder.
Original PR description
The company's project folder is supposed to be [impossible](https://github.com/odoo/enterprise/blob/20cc61e69aa3f6a59de1e962b25ce11fa402bf22/documents_project/models/documents_document.py#L44) to archive, by using the `_unlink_except_company_folders` logic. However, the method that adds the company field to the list of fields to check was missing, so it was still possible to archive a folder set as projects folder. Forward-Port-Of: odoo/enterprise#120605
Sold-out event tickets or time slots now correctly show that no seats can be ordered, instead of falling back to the default per-order limit. This prevents future event sales flows from accidentally allowing customers to request unavailable tickets.
Original PR description
### Steps to reproduce: event = env['event.event'].create({ 'name': 'Repro Event', 'date_begin': '2026-09-01 08:00:00', 'date_end': '2026-09-01 18:00:00', }) ticket =…
### Steps to reproduce:
event = env['event.event'].create({
'name': 'Repro Event',
'date_begin': '2026-09-01 08:00:00',
'date_end': '2026-09-01 18:00:00',
})
ticket = env['event.event.ticket'].create({
'event_id': event.id,
'name': 'VIP',
'seats_limited': True,
'seats_max': 1,
})
env['event.registration'].create({
'event_id': event.id,
'event_ticket_id': ticket.id,
'name': 'Attendee 1',
'state': 'open',
})
ticket.seats_available -> 0
ticket.is_sold_out -> True
result = ticket._get_current_limit_per_order(event=event) print(result) # {ticket.id: 30} -- expected {ticket.id: 0}
### Issue and Expected
`_get_current_limit_per_order()` used `if not seats_available:` to detect the "no limit" case returned by `_get_seats_availability()`. That check is truthy for both `None` (genuinely no limit) and the integer `0` (fully booked), so a sold-out ticket/slot combination was incorrectly treated as unlimited and returned `limit_max_per_order or EVENT_MAX_TICKETS` (e.g. 30) instead of `0`.
### Fix
`_get_seats_availability()` explicitly documents `None` as the "no limit" sentinel, with `0` meaning "constrained, zero seats left". Use `seats_available is None` to preserve that distinction instead of a falsy check.
No functional regression was found in the current website_event flow: sold-out slots/tickets are filtered or re-derived independently before reaching this value in every existing UI path. This fixes the underlying contract of the method itself, so future or additional callers don't inherit the wrong value.
https://github.com/odoo/odoo/issues/284098
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#284100German Intrastat dispatch reports now use the required region code 99 when goods originate outside Germany, helping companies submit compliant statistical declarations. The update also makes German exports more reliable by handling local number formatting correctly and rounding reported weights according to official rules.
Original PR description
### Issue: In Germany, the intrastat report requires region code `99` for dispatch moves where the product origin country is not DE Per the official specification (section 5.8 — Region of origin):…
### Issue: In Germany, the intrastat report requires region code `99` for dispatch moves where the product origin country is not DE Per the official specification (section 5.8 — Region of origin): "As for goods with foreign origin, code '99' should be entered" https://erhebungsportal.estatistik.de/Erhebungsportal/api/assets/files?downloadId=0c5422f111104705b021b41616ad1ecc But `99` was not being used in those cases ### Cause: `99` was already the default fallback when no `region_code` is set but `_fill_missing_values` had no condition to override the region code for dispatch moves with a non-DE origin country ### Notes: While fixing this, two additional issues were found when exporting with the German locale (`de_DE`): - `weight` and `supplementary_units` are formatted with a comma as decimal separator by the German locale, causing `float()` to raise a `ValueError` — fixed by normalizing to dot before conversion, as done in other localizations - The dispatch/arrival check was comparing against the translated label (e.g. `'Versand'`) instead of a stable identifier A new `intrastat_type_code` field with static values `arrival` and `dispatch` is added based on the existing `id` values from `default_type`, avoiding locale-dependent comparisons This improvement could be extended to other localizations - Net mass is now rounded to full kilograms per item (see §5.14 of the specification linked above) A weight rounding down to 0 kg is reported as `0` instead of being silently dropped (QWeb `t-out` omits the element when the value is `None`) Totals are computed as the sum of already-rounded per-item values to stay internally consistent ### Steps to reproduce: - Install `sale_management`, `l10n_de_account` and `accountant` - Switch to the DE company - In Settings, configure the Intrastat values: -- Default invoice transaction code: 11 -- Default refund transaction code: 21 -- Intrastat region: 07 - Create 3 products with a commodity code set and country of origin: one DE, one empty, one other country - Create and confirm a Sale Order for all products with a European partner, deliver all, create and send the invoice - Open the Intrastat report (Accounting > Reporting > Taxes & Fiscal > Intrastat) - Set the month to the current month Before the fix, all regions show 07 Expected: non-DE origin products should show 99 opw-6455585 Forward-Port-Of: odoo/enterprise#128747
Fixes an issue where orders shared between trusted Point of Sale locations could keep the wrong session information when the restaurant app was installed. This prevents valid payment methods from being rejected, helping staff complete payments reliably across linked shops.
Original PR description
**TL;DR** - in certain cases, Session is not updated on order shared on trusted pos Steps to reproduce: - Install pos_restaurant with demo data. - Have two configs: Cloth Shop and Furniture Shop. -…
**TL;DR** - in certain cases, Session is not updated on order shared on trusted pos Steps to reproduce: - Install pos_restaurant with demo data. - Have two configs: Cloth Shop and Furniture Shop. - Ensure both configs have their own individual payment method (e.g., CASH). - In Cloth Shop, add Furniture Shop as a trusted PoS. - In Cloth Shop, enable "Log in with Employee". - Open Cloth Shop and Furniture Shop in separate browsers (different users). - Refresh Cloth Shop once. - In Cloth Shop, create and save an order. - In Furniture Shop, open that order and try to pay with "CASH". Observation: * A validation error is raised: "The payment method selected is not allowed in the config of the POS session." <img width="320" height="201" alt="image" src="https://github.com/user-attachments/assets/8ca80575-76bf-4ecf-a739-08b03d543ed6" /> - although we can pay the order by a shared payment method Cause: - Refreshing Cloth Shop triggers `notify_synchronisation` due to `setCashierUpdateSession` , which pushes Cloth Shop's `pos.session` record into Furniture Shop's IndexedDB (because Cloth Shop trusts Furniture Shop). https://github.com/odoo/odoo/blob/6a3655c8efd2de68754ace38bd1ab5eb6499faca/addons/pos_hr/static/src/app/services/pos_store.js#L61-L63 - When Cloth Shop saves an order, sync also pushes a copy of that order into Furniture Shop, and passed through `processDynamicRecords` - `processDynamicRecords` ensures that the Record created is in sync with server data, as Furniture Shop now has a local record of session of Cloth Shop, the record keeps `session_id` pointing to Cloth Shop's session, else it would have been left `undefined` - from the `res` we get, we set the session_id on `pos.order ` https://github.com/odoo/odoo/blob/6a3655c8efd2de68754ace38bd1ab5eb6499faca/addons/point_of_sale/static/src/app/utils/devices_synchronisation.js#L127-L139 - However with pos_restaurant installed we get its orveride which stores the result of super() and only returns result early when there are no` pos.order ` records in dynamicRecords. When pos.order records are present (our case), the method proceeds with its own logic but never returns result (or its own output) at the end https://github.com/odoo/odoo/blob/6a3655c8efd2de68754ace38bd1ab5eb6499faca/addons/pos_restaurant/static/src/app/utils/devices_synchronisation.js#L5-L9 - so the corrected data from the super call is silently dropped. - we do not get a change to update the session id - The order keeps Cloth Shop's session ID - Furniture Shop's payment validation checks the order against its session's allowed payment methods, but since the session is still Cloth Shop's, the check fails for methods not shared between Cloth Shop and Furniture Shop (e.g., CASH). Why this is hidden in other cases: - without pos_restaurant, or if Furniture Shop had no prior knowledge of Cloth Shop's session (we made is possible by `Log in with Employee` feature, session_id would stay `undefined` and later get correctly set toFurniture Shop's session in PosOrder.setup(). https://github.com/odoo/odoo/blob/6a3655c8efd2de68754ace38bd1ab5eb6499faca/addons/point_of_sale/static/src/app/models/pos_order.js#L27-L30 or from here https://github.com/odoo/odoo/blob/6a3655c8efd2de68754ace38bd1ab5eb6499faca/addons/point_of_sale/static/src/app/utils/devices_synchronisation.js#L127-L139 - Both safety nets happen to be bypassed here. Fix: * Tighten the early return condition so that data is only discarded when the current configuration is not a restaurant configuration. * Also, A trusted PoS configuration cannot be a restaurant, so not returning later makes sense. opw-6465228 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#282962
The Turkish e-Ledger export now fills in line numbers automatically and keeps the numbering continuous across monthly filings within the same fiscal period. This helps businesses meet GİB expectations and avoid manual post-export corrections or inconsistent filings.
Original PR description
Before: the `LineNumber` column of the e-Ledger CSV was always empty, left to be filled after the export. Since the ledger is filed monthly, whatever filled it restarted at 1 every month, while GİB expects the numbering to start once, at the beginning of the fiscal period, and to run unbroken until its end. Now: the column is written by the export. An export starting after the first day of the fiscal period is offset by the number of rows that period already counts, so February continues where January stopped, and a full-year export numbers the same rows identically. The lines are also read in ascending date order. They were sorted by the default order of `account.move.line`, the most recent first, so the first row of the file was the last entry of the period and could not be numbered 1. This reverses `EntryNumberCounter` as well, which now counts from the oldest entry. task-6424730 Forward-Port-Of: odoo/enterprise#130923 Forward-Port-Of: odoo/enterprise#127518
This fixes the automatic entry wizard so related destination and accrual entries are properly matched when changing periods. It helps prevent leftover accounting lines from cluttering records and reduces the risk of unbalanced or confusing financial data.
Original PR description
The previous code wasn't reconciling the destination and accrual moves, leaving potentially non-neutral journal items and their counterpart polluting the DB. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#279766
Argentine electronic invoices can now be printed when a foreign customer's identification type has no ARCA code. This prevents a crash on export invoices and helps businesses reliably issue and share posted invoices.
Original PR description
## Description Printing any posted electronic invoice whose commercial partner has an identification type **without** ARCA code (typically the generic `VAT` type from `l10n_latam_base`, common on…
## Description
Printing any posted electronic invoice whose commercial partner has an identification type **without** ARCA code (typically the generic `VAT` type from `l10n_latam_base`, common on foreign partners of export invoices) crashes:
```
File ".../l10n_ar_edi/models/account_move.py", line 161, in _compute_l10n_ar_afip_qr_code
data.update({'tipoDocRec': int(rec._get_partner_code_id(commercial_partner_id))})
TypeError: int() argument must be a string, a bytes-like object or a real number, not 'NoneType'
```
## Steps to reproduce
1. Install `l10n_ar_edi`.
2. Create a customer: Country **Spain**, Identification Type **VAT** (no ARCA code), Identification Number `ESA12345674`, ARCA Responsibility Type **Cliente / Proveedor del Exterior**.
3. Create and validate an invoice for this customer on an export electronic journal (document type 19).
4. Print the invoice → traceback above.
## Root cause
Since bb8e6fda72ae6364cf7806acb400b071f4108fc9 ([FIX] l10n_ar_edi: return the correct ARCA code when final consumer, odoo/enterprise#106881), `_get_partner_code_id()` lost its final `return partner_id_code` fallback: when the identification type has no ARCA code and the partner is not a Final Consumer, the method now returns an implicit `None`. The QR code compute casts the result with `int()`, which accepted the previous falsy return (`int(False) == 0`, rendering `tipoDocRec: 0` as in 17.0/18.0) but raises on `None` — making every such posted invoice impossible to print.
## Fix
Restore the fallback return so the method always returns the identification type code (possibly falsy) instead of an implicit `None`. The intent of bb8e6fda72ae is preserved: a Final Consumer with an identification number still gets its real code, and the other callers already handle falsy values (`partner_id_code or 0`).
Forward-Port-Of: odoo/enterprise#126714ERPly S.R.L. has signed Odoo's corporate contributor license agreement, confirming the legal terms for its contributions. This clears the remaining license check for a related contribution, allowing it to move forward once other review steps are complete.
Original PR description
ERPly S.R.L. (Santo Domingo, Dominican Republic) signs the Corporate Contributor License Agreement v1.0. This unblocks the `legal/cla` check on #286565, where every other CI check already passes. Signed by Rob Cruz (rob.cruz@erply.do, @rob-erply), acting on behalf of ERPly S.R.L. Forward-Port-Of: odoo/odoo#286665