Wednesday, October 29, 2025
18 changes · saas-18.3
Enhancements to existing features
Late GSTR2B bill matching now uses invoice reference details more carefully, depending on whether an IRN is available. This helps reduce duplicate or incorrect matches during bill reconciliation, improving reliability for Indian GST reporting.
Original PR description
- Adjusted domain construction in GSTR2B late bill matching logic. - Split condition to handle the presence or absence of IRN separately: - If IRN exists, match on both `ref` and `l10n_in_irn_number`. - If IRN is missing, match only on `ref`. - Improves the accuracy of bill reconciliation and avoids redundant matches.
The Italian localization now removes outdated tax exemption reason codes that the Italian Tax Agency no longer accepts. This helps businesses issue compliant electronic invoices using the newer, more detailed Natura codes already available in Odoo.
Original PR description
There are old Natura (l10n_it_tax_exemption_reason) that are deprecated in favour of more detailed ones. We already have the detailed ones, but we allow the old values, but the Tax Agency considers them invalid already. ref: https://fex-app.com/FatturaElettronica/FatturaElettronicaBody/DatiBeniServizi/DatiRiepilogo/Natura Forward-Port-Of: odoo/odoo#233440 Forward-Port-Of: odoo/odoo#226939
Inventory users can now reset the cached links between printers and reports directly from the Inventory app. This makes it easier to resolve printer-report pairing issues without navigating to technical IoT settings.
Original PR description
In order to simplify resetting the link between printers and reports in cache, we added the "Reset Linked Printers" button to the Inventory app. Forward-Port-Of: odoo/enterprise#98111 Forward-Port-Of: odoo/enterprise#98028
Resolved issues and error corrections
Third-party checks now use the actual most recent operation to determine whether they are still on hand. This prevents checks that have already been both received and used for payment from incorrectly appearing as available.
Original PR description
**Steps to reproduce:** 1. Install the modules: `accounting`, `l10n_ar`, and `l10n_latam_check`. 2. Create a third-party check journal: * Add payment methods: *New Third Party Checks* (inbound) and…
**Steps to reproduce:** 1. Install the modules: `accounting`, `l10n_ar`, and `l10n_latam_check`. 2. Create a third-party check journal: * Add payment methods: *New Third Party Checks* (inbound) and *Existing Third Party Checks* (outbound). * Configure outstanding accounts for both methods. 3. Create a vendor payment: * Use the third-party check journal. * Select the *Existing Third Party Checks* method. * Leave the check list empty. * Keep the payment in *Draft*. 4. Create a customer payment: * Use the same journal and select the *New Third Party Checks* method. * Add a new check under the *Checks* tab. * Post the payment. 5. Return to the draft vendor payment created in step 3: * Add the newly created check to it (via *Add a line*). * Post the payment. 6. Go to **Customers → Third Party Checks**. **Observed behavior:** - The check still shows a `current_journal_id` even though it has both inbound and outbound operations. - As a result, the check incorrectly appears in the 'On Hand' filter. **Root cause:** - `_get_last_operation` was sorting operations by `date`. - In some scenarios or flows where multiple operations share the same `date`, the wrong operation could be selected as the "last one", leading `_compute_current_journal` to assign a journal incorrectly. **Solution:** - Sort operations by `write_date` to always pick the true last operation. - This ensures `_compute_current_journal` correctly clears `current_journal_id` whenever both inbound and outbound operations exist. opw-5012903 Forward-Port-Of: odoo/odoo#225556
Cancelled restaurant POS orders are now removed from the Ticket Screen instead of appearing under paid orders. This keeps staff views accurate and reduces confusion when reviewing completed or cancelled sales.
Original PR description
pos*: point_of_sale, pos_restaurant Steps to reproduce: - Open the Restaurant POS and place an order. - Send it to the preparation screen by clicking Order. - Reopen the order and cancel it via the Action button. Issue: - The cancelled order still appears under the Paid section on the Ticket Screen. Fix: - Cancelled orders are now excluded from the Ticket Screen display. - Remove canceled orders from local records. Task: 4936636 Forward-Port-Of: odoo/odoo#231977 Forward-Port-Of: odoo/odoo#219402
This fixes an issue where testing a contact import with smaller batches could cause the final import to start from the wrong place and only import the last batch. After a successful test import, the import process now resets correctly so all records are included when the user proceeds.
Original PR description
Steps to reproduce ================== - Go to contacts - Click on the cog menu > Import records - Upload a csv file - Limit the batch limit to a value lower than the total number of records in the csv file - Click on the test button - Click on the Import button => Only the last batch is imported Cause of the issue ================== The start line is not reset after the test import, which can be confusing Solution ======== When the test import fully succeeds, we reset the start line opw-4916102 Forward-Port-Of: odoo/odoo#232938 Forward-Port-Of: odoo/odoo#230805
This fixes an issue where adding a column in the report editor could update only some rows when report headers used conditional display rules. Business users editing delivery slips and similar reports should now see columns added consistently across the table.
Original PR description
The TableSizeComputer.compute() method can return cells with incorrect cell indexes. This can result in the _addColumn function only adding a column to some rows in the report editor.…
The TableSizeComputer.compute() method can return cells with incorrect cell indexes. This can result in the _addColumn function only adding a column to some rows in the report editor. TableSizeComputer.compute() does not handle cases where a conditional is used inside of a th element but the corresponding td element does not have a conditional. Steps to reproduce ----- 1. Open the Delivery Slip in the report editor. 2. Click the header to show the dropdown and select elif: o.move_line_ids and o.state=='done'. 3. Hover over the last th element to select the column button. 4. Click Insert right and see that only the header row has an additional column. Cause ----- TableSizeComputer.compute() calculates how many possible cells can be in a row which can be more than the total number of columns you have if you have th or td elements with a conditional inside of them. In the Delivery Slip report, a th element is present when using serial numbers and a different th element is present when not using serial numbers. The corresponding t-body has two different tr elements for these conditions. The compute method will have one less cell in these rows because the corresponding conditional is handled outside of the row. Solution ----- Only count mutually exclusive th or td elements once. This prevents the cellIndex from being larger than the total possible columns. opw-ticket 5173345 Forward-Port-Of: odoo/enterprise#98207
This fixes an issue where certain point-of-sale loyalty rewards could add one more product than the cashier intended, such as when changing quantities with the numpad. It also restores the intended reward calculation so eligible free items are applied correctly for configured promotions.
Original PR description
After commit https://github.com/odoo/odoo/commit/b86dd8f0674f02a14f4aaa10db39bd4a1e8270f1, an extra order line could be added unintentionally. For example, in a "Buy X Get Y" loyalty program, if you add the product and press 3x on the numpad, the system would incorrectly add 3x + 1 products to the order. Moreover, the fix introduced in the above commit was not correct according to the program logic. For instance, if the configuration is set to "Grant: 1 credit per order" with a minimum quantity of 2, and the reward is for one point, then when ordering 2 products, one of them should indeed be free. The previous fix changed this intended behavior, resulting in incorrect reward application. opw-5151979 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#232192 Forward-Port-Of: odoo/odoo#231704
Fixes an issue where Safari users could not reliably apply, cancel, or choose link types when editing links in the HTML editor. This makes link editing in email templates and other rich text areas behave consistently across browsers.
Original PR description
## Issue: On Safari, the Apply and Dismiss buttons of the LinkPopover did not trigger their actions In some cases, it seemed to work only because a temporary link was not properly cleared The Link…
## Issue: On Safari, the Apply and Dismiss buttons of the LinkPopover did not trigger their actions In some cases, it seemed to work only because a temporary link was not properly cleared The Link Type Selection was also broken by the same bug ## Cause: Safari triggers a `pointerdown` event through the `LinkPopover`, which changes the `selection` and calls the `handleSelectionChange()` method https://github.com/odoo/odoo/blob/e43135e94bf22bc2f7a115c37e8a082f96871ed0/addons/html_editor/static/src/main/link/link_plugin.js#L672-L678 At that point, `documentSelection` is `null`, causing the overlay to close before the `onClickApply()` on the Apply button It's the same issue with the Dismiss button and the Link Type Selection ## Steps to reproduce: - Install mail to get access to an html_editor - Enable debug mode - Go in Email Templates and open one of them - Select a text and open link tools - Add Odoo.com and click on Apply - The link may seems to be created (if it's the case, there is no preview) - You can confirm that with the link type selector that closed the popover before the fix opw-5115887 Forward-Port-Of: odoo/odoo#233401 Forward-Port-Of: odoo/odoo#233241
The Swedish Point of Sale integration now correctly prevents receipts from being reprinted more than once, helping businesses stay compliant with local receipt-control requirements. It also improves blackbox error handling so disconnected devices and driver issues are reported more clearly instead of leaving the screen stuck loading.
Original PR description
Before this commit, the receipt reprint check logic (which only allows a receipt to be reprinted once) was not working. There were two issues preventing it from working: - The name of the function had changed but not been updated in this module, so the override was not applied. - The ID being used to check the order was incorrect. A few smaller issues were also fixed: - A logging issue in the IoT driver, which prevented the error severity from being printed. - An infinite loading UI if the blackbox was disconnected. - The blackbox error handler swallowing up some errors due to only expecting one specific format. Forward-Port-Of: odoo/enterprise#98271 Forward-Port-Of: odoo/enterprise#98076
Default pallet dimensions for DHL deliveries have been corrected from millimeter-sized values to the centimeter values expected by DHL. This prevents delivery validation errors and allows shipping labels to be generated when using DHL pallets with default settings.
Original PR description
## Short functional explanation of the error When setting the 'DHLxw Package Type' of a DHL delivery method to 'DHL pallets' without changing default values, trying to validate an order delivery…
## Short functional explanation of the error When setting the 'DHLxw Package Type' of a DHL delivery method to 'DHL pallets' without changing default values, trying to validate an order delivery causes an error. ## Reproduction Steps 1. Type 'Delivery method' in the dashboard. Select DHL BE, set the region to Europe and the 'DHLxw Package Type' to 'DHL pallets'. Click on the arrow at the right of 'DHL pallets'. You should see the default values set to 1200, 1600 and 1200. These correspond to millimeters, while DHL expects centimeters. 2. Make sure you're using a company located in Belgium. 3. In the inventory settings, make sure that either 'multi-step route' is unchecked or that for your warehouse, the outgoing shipments are set to '1 step'. 3. Create a new quotation. Select a Customer with a Belgian address. Add a product for which there's at least 1 copy in stock. 4. Click on add shipping and select DHL BE. Then click add and ok. 5. Click on confirm. A smart button at the top of the page called "delivery" should appear. Click on it and set the scheduled date to a future day. Finally, click validate. ### Expected behavior New labels should generate without issue. ### Unexpected behavior An error occurs: ```Bad request Multiple problems found, see Additional Details 1001: The requested product(s) (N) not available based on your search criteria.(687662d08999745d3df3783068fcca24) 410107: The piece height exceeds max, N/- 410304: No products available ``` ## Origin of the issue The default values for DHL are set to 1200, 1600 and 1200, which correspond to millimeters. However, DHL expects centimeters, for which the default value is thus too high. Note: because the issue stems from default values, after implementing the fix, the module should be reinstalled. __ opw-4804639 Forward-Port-Of: odoo/enterprise#90272
Fixes an issue where deliveries linked to split manufacturing orders could show only the first produced quantity instead of the full ordered amount. This helps sales and warehouse teams see accurate delivery quantities when manufacturing backorders are completed.
Original PR description
Issue ----- After splitting a MO, the linked delivery's quantity only gets updated when the original MO is validated. Validating the backorder MOs doesn't affect the shown quantity. Steps to…
Issue ----- After splitting a MO, the linked delivery's quantity only gets updated when the original MO is validated. Validating the backorder MOs doesn't affect the shown quantity. Steps to reproduce ----- - Unarchive MTO route - Create a stored product - Routes MTO & Manufacturing - Empty BoM - Create a Sale Order for 3 units of the product & confirm it - Go to the linked MO and split it in 2 (quants of 1 and 2) - Confirm both MOs (and produce) - Go to the sale's delivery > The delivery's move only shows 1 unit of the product Why the move quantity is only 1 ----- After confirming the first of the 2 backorder productions, when we manufacture the product, we go through https://github.com/odoo/odoo/blob/e6b87a2a37b4550faf60a77981c533d19aa054d4/addons/mrp/models/mrp_production.py#L2054 Since the first backorder kept the existing move, it has the delivery move in `move_dest_ids` so we do https://github.com/odoo/odoo/blob/e6b87a2a37b4550faf60a77981c533d19aa054d4/addons/stock/models/stock_move.py#L2088-L2091 Which creates a SML for the delivery move when reserving it https://github.com/odoo/odoo/blob/e6b87a2a37b4550faf60a77981c533d19aa054d4/addons/stock/models/stock_move.py#L1853-L1858 https://github.com/odoo/odoo/blob/e6b87a2a37b4550faf60a77981c533d19aa054d4/addons/stock/models/stock_move.py#L1950-L1971 This in turn triggers the computation of the move's quantity since it depends on the move's lines https://github.com/odoo/odoo/blob/e6b87a2a37b4550faf60a77981c533d19aa054d4/addons/stock/models/stock_move.py#L382-L383 Our move ends up with a quantity of 1. When we proceed with the second MO, things are a little different since there is nothing in `moves_todo.move_dest_ids` when we do https://github.com/odoo/odoo/blob/e6b87a2a37b4550faf60a77981c533d19aa054d4/addons/stock/models/stock_move.py#L2088-L2091 This means we don't create a new SML for the delivery move, so the quantity stays at 1. Why there is no move_dest_id ----- When splitting the production, we go through https://github.com/odoo/odoo/blob/bc22cf5a225a4e7d58548a8d6dedbcd84d771acb/addons/mrp/models/mrp_production.py#L1815-L1829 We create new a MO and SM for the backorder. The SM is created here https://github.com/odoo/odoo/blob/bc22cf5a225a4e7d58548a8d6dedbcd84d771acb/addons/mrp/models/mrp_production.py#L1894-L1915 When preparing the values, we correctly copy the `move_dest_ids` of the original MO's SM, see https://github.com/odoo/odoo/blob/bc22cf5a225a4e7d58548a8d6dedbcd84d771acb/addons/mrp/models/stock_move.py#L646-L656 So when the backorder is created, its' SM has a correct `move_dest_ids`. The problem actually comes from what happens after `_split_productions` in `action_split` when we set the `date_start` https://github.com/odoo/odoo/blob/bc22cf5a225a4e7d58548a8d6dedbcd84d771acb/addons/mrp/wizard/mrp_production_split.py#L68-L78 In the write, we get to a line where we access the production's state https://github.com/odoo/odoo/blob/bc22cf5a225a4e7d58548a8d6dedbcd84d771acb/addons/mrp/models/mrp_production.py#L931 This triggers a recompute of the field. https://github.com/odoo/odoo/blob/bc22cf5a225a4e7d58548a8d6dedbcd84d771acb/addons/mrp/models/mrp_production.py#L539-L552 In the compute, we access `move_finished_ids`, which again triggers a recompute. In this compute, we call `_create_update_move_finished` https://github.com/odoo/odoo/blob/bc22cf5a225a4e7d58548a8d6dedbcd84d771acb/addons/mrp/models/mrp_production.py#L800 The problem is that the move we create gets its' `move_dest_ids` from the MO instead of using the one populated using `group_orders` (098af2f). https://github.com/odoo/odoo/blob/bc22cf5a225a4e7d58548a8d6dedbcd84d771acb/addons/mrp/models/mrp_production.py#L1142-L1167 ----- Ticket: opw-4865082 Forward-Port-Of: odoo/odoo#232712 Forward-Port-Of: odoo/odoo#223792
Assets created from vendor bills now calculate their original value using only the deductible portion of bill lines. This prevents overstating asset values when a bill includes non-deductible amounts, improving accounting accuracy.
Original PR description
We allow the user to create an asset from a bill with lines which are not fully deductible but the created asset's original value is the based on the entire balance of the journal item. It should be only the deductible part. task-5156256 Forward-Port-Of: odoo/enterprise#97505
Lithuanian payroll now avoids charging the pension accumulation contribution twice when it is enabled on an employee contract. This helps payslips reflect the expected employee social security contribution and prevents over-deducting payroll amounts.
Original PR description
**Issue**
When generating a payslip for an employee with the `l10n_lt_pension` setting enabled on the contract ("Participate to pension accumulation system"):
- the SSC is raised from 19.5% to 22.5%
- a 3% pension contribution ("Pension Scheme") is added This effectively doubles the expected contribution.
Various sources (e.g. https://taxsummaries.pwc.com/lithuania/individual/other-taxes) seem to show this is not correct.
opw-5067664
Forward-Port-Of: odoo/enterprise#98177
Forward-Port-Of: odoo/enterprise#95880This fix prevents the Point of Sale from crashing for Italian companies using the Italian fiscal printer setup when a cashier deletes a product line. It improves checkout reliability by safely handling zero-value lines during tax calculations.
Original PR description
Currently POS crashes with the Italian localization if they delete a product line. Steps to reproduce: ------------------- * Install l10n_it_pos and switch to the IT company * Create a pos and…
Currently POS crashes with the Italian localization if they delete a product line. Steps to reproduce: ------------------- * Install l10n_it_pos and switch to the IT company * Create a pos and configure Italian printer * Open shop * Add a product to cart * On the numpad try deleting the line > Observation, pos crashes Traceback: TypeError: Cannot read properties of undefined (reading 'tax_details') Why the fix: ------------ When first hitting the delete button we will put the price unit to 0. With that we have ``` const reduced_base_lines = Object.values(base_line_map).filter( (base_line) => !floatIsZero(base_line.price_unit, base_line.currency_id.decimal_places)); ``` returning an empty list. Utlimately making `reduce_base_lines_to_target_amount` return an empty list. `[][0]` returns `undefined` and we were passing `[undefined]` in parameters of `fix_base_lines_tax_details_on_manual_tax_amounts`, we enter the loop with undefined and try to access some variables. We also set `l10n_it_epson = False` in order to avoid recursively looping. opw-5184113
This change prevents French point-of-sale certification checks from treating orders without secure sequence numbers as valid previous orders. It avoids incorrect errors during database upgrades or recomputation, helping affected businesses complete maintenance smoothly.
Original PR description
## Description of the issue/feature this PR addresses: When obtaining the previous order for pos.order records, all orders with l10n_fr_secure_sequence_number == NULL will be recognised as the…
## Description of the issue/feature this PR addresses:
When obtaining the previous order for pos.order records, all orders with l10n_fr_secure_sequence_number == NULL will be recognised as the previous order for those where l10n_fr_secure_sequence_number == 1, as if their sequence value was zero.
## Current behavior before PR:
Since there can be more than one orders without sequence number, this behaviour will trigger an UserError exception, as the ORM will mistakenly deduce that there are multiple previous orders for a single one, which is not necessarily correct.
### Examples:
upg-3170341
```sql
lare_3170341=> SELECT count(id) FROM pos_order WHERE l10n_fr_secure_sequence_number IS NULL;
count
-------
67281
(1 row)
```
```python
# Debugging standard codebase with a Python debugger
...
match = prev_map.get(order.l10n_fr_secure_sequence_number - 1, []) # len(match) == 67281
if len(match) > 1:
raise UserError(_('An error occurred when computing the inalterability...'))
...
```
upg-3170341
```sql
lare_3167621=> SELECT count(id) FROM pos_order WHERE l10n_fr_secure_sequence_number IS NULL;
count
-------
294
(1 row)
```
```python
# Debugging standard codebase with a Python debugger
...
match = prev_map.get(order.l10n_fr_secure_sequence_number - 1, []) # len(match) == 294
if len(match) > 1:
raise UserError(_('An error occurred when computing the inalterability...'))
...
```
---
Traceback group: https://upgrade.odoo.com/odoo/tbg/1869
```
2025-09-30 07:48:30,945 329 INFO db_3167621 odoo.modules.loading: Loading module l10n_fr_pos_cert (110/131)
2025-09-30 07:48:31,375 329 INFO db_3167621 odoo.modules.registry: module l10n_fr_pos_cert: creating or updating database tables
2025-09-30 07:48:31,432 329 INFO db_3167621 odoo.models: Prepare computation of pos.order.previous_order_id
2025-09-30 07:48:31,597 329 WARNING db_3167621 odoo.modules.loading: Transient module states were reset
2025-09-30 07:48:31,597 329 ERROR db_3167621 odoo.modules.registry: Failed to load registry
2025-09-30 07:48:31,597 329 CRITICAL db_3167621 odoo.service.server: Failed to initialize database `db_3167621`.
Traceback (most recent call last):
File "/home/odoo/src/odoo/18.0/odoo/service/server.py", line 1361, in preload_registries
registry = Registry.new(dbname, update_module=update_module)
File "<decorator-gen-13>", line 2, in new
File "/home/odoo/src/odoo/18.0/odoo/tools/func.py", line 97, in locked
return func(inst, *args, **kwargs)
File "/home/odoo/src/odoo/18.0/odoo/modules/registry.py", line 129, in new
odoo.modules.load_modules(registry, force_demo, status, update_module)
File "/home/odoo/src/odoo/18.0/odoo/modules/loading.py", line 485, in load_modules
processed_modules += load_marked_modules(env, graph,
File "/home/odoo/src/odoo/18.0/odoo/modules/loading.py", line 365, in load_marked_modules
loaded, processed = load_module_graph(
File "/home/odoo/src/odoo/18.0/odoo/modules/loading.py", line 206, in load_module_graph
registry.init_models(env.cr, model_names, {'module': package.name}, new_install)
File "/home/odoo/src/odoo/18.0/odoo/modules/registry.py", line 618, in init_models
func()
File "/home/odoo/src/odoo/18.0/odoo/addons/base/models/ir_model.py", line 2007, in _reflect_relation
self.env.invalidate_all()
File "/home/odoo/src/odoo/18.0/odoo/api.py", line 839, in invalidate_all
self.flush_all()
File "/home/odoo/src/odoo/18.0/odoo/api.py", line 857, in flush_all
self._recompute_all()
File "/home/odoo/src/odoo/18.0/odoo/api.py", line 850, in _recompute_all
self[field.model_name]._recompute_field(field)
File "/home/odoo/src/odoo/18.0/odoo/models.py", line 7359, in _recompute_field
field.recompute(records)
File "/home/odoo/src/odoo/18.0/odoo/fields.py", line 1463, in recompute
apply_except_missing(self.compute_value, recs)
File "/home/odoo/src/odoo/18.0/odoo/fields.py", line 1436, in apply_except_missing
func(records)
File "/home/odoo/src/odoo/18.0/odoo/fields.py", line 1485, in compute_value
records._compute_field_value(self)
File "/home/odoo/src/odoo/18.0/addons/mail/models/mail_thread.py", line 427, in _compute_field_value
return super()._compute_field_value(field)
File "/home/odoo/src/odoo/18.0/odoo/models.py", line 5296, in _compute_field_value
fields.determine(field.compute, self)
File "/home/odoo/src/odoo/18.0/odoo/fields.py", line 110, in determine
return needle(*args)
File "/home/odoo/src/odoo/18.0/addons/l10n_fr_pos_cert/models/pos.py", line 79, in _compute_previous_order
raise UserError(_('An error occurred when computing the inalterability. Impossible to get the unique previous posted point of sale order.'))
odoo.exceptions.UserError: Une erreur s'est produite lors de la vérification de l'inaltérabilité. Impossible de récupérer la dernière commande de caisse unique et comptabilisée.
```
## Desired behavior after PR is merged:
The method `pos.order._compute_previous_order` already only checks orders with a sequence number != NULL, so to address this issue, we will use the same condition to retrieve only orders with a valid sequence. This will result in no more exceptions created by incorrect data.
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#229585This fix prevents users from disabling required Peppol electronic invoicing services, reducing the risk of compliance problems. It also corrects small formatting inconsistencies in invoice e-invoice format labels so they display more consistently.
Original PR description
#### [FIX] account_peppol: disallow disabling services Currently it is possible to disallow any services in the configuration. This can lead to complicance issues; i.e. if someone disables "BIS Billing 3.0". 1. Ensure Peppol is activated (test mode or production; not demo) 2. Settings -> Accounting -> PEPPOL Electronic Invoicing -> Configure Peppol Services 3. Any service can be disabled. This commit hides the button to open the service wizard. (Also in the wizard it is now not possible to disable services anymore.) #### [FIX] account_edi_ubl_cii: missing parenthesis in invoice_edi_format Follow-up to commit 860c0974f9b541be63f4079ef52366f91c1994ce . There we improved the eInvoice format labels for clarity. But 2 label were formatted differently than the others. This commit fixes that. #### References task-4737164 Forward-Port-Of: odoo/odoo#233502 Forward-Port-Of: odoo/odoo#233382
Batch payment users now see a clear, non-blocking warning when SEPA Credit Transfer partners are missing required country or city details. This helps businesses correct partner records before generating payment files that could become invalid under upcoming SEPA rules.
Original PR description
Starting from November 15th, 2026, SEPA Credit Transfer (SCT) payments require both the partner's country and city to be defined when using structured addresses. If missing, the generated XML file will be invalid. This change adds a non-blocking red banner on the batch payment form to warn users and provide a link to review the affected partners. task-5156613 Forward-Port-Of: odoo/enterprise#97598