Wednesday, August 26, 2026
66 changes · saas-19.4
Resolved issues and error corrections
The Peru sales ledger now reports the gross value of sales when a 3% IGV withholding applies, instead of reducing the sale total by that withholding. This aligns the report with SUNAT expectations, since the withholding is handled as a payment-time mechanism rather than a reduction of the sale amount.
Original PR description
The 3% IGV withholding is a negative sale tax, so it reduced amount_total and the 14.4 ledger reported a net total. SUNAT expects the gross total of the operation, the withholding being a payment-time mechanism. task-5935227 Forward-Port-Of: odoo/enterprise#128875 Forward-Port-Of: odoo/enterprise#128849
Fixed a timing issue that could cause point-of-sale orders using AvaTax to reload incorrectly after payment, sometimes making the selected order line disappear. The change improves reliability during checkout and strengthens the related automated test so the issue is less likely to return.
Original PR description
The POS Avatax tour sporadically failed after returning from the payment screen. The race was reproduced locally by delaying the mocked Avatax response in mocked_request(). There's three somewhat…
The POS Avatax tour sporadically failed after returning from the payment screen. The race was reproduced locally by delaying the mocked Avatax response in mocked_request(). There's three somewhat related fixes. Firstly, get_order_tax_details() calls sync_from_ui(), which emits a SYNCHRONISATION notification. Unlike the normal POS sync path, the Avatax RPC did not pass the device context. The browser therefore treated its own notification as coming from another device and started an independent reload of open orders. That reload could replace the current order state after the tour returned to the product screen, causing the selected order line to disappear. We now pass the normal sync context so the browser can properly ignore its own notification. Secondly, we'll keep the complete sync_from_ui() response and replace its order, line, tax, and tax group data after the AvaTax calculation. We then simplify the processing client-side by moving towards the established pattern in the POS: missingRecursive() to load any other referenced records, and then pass that through loadConnectedData(). Lastly, clickPayButton() only waits for the payment screen element to be displayed. The AvaTax request starts from the screen's onMounted() callback, leaving a short window where the screen and its buttons exist but the request and UI blocker have not started yet. The next tour step can probably run during that window. To make sure this can't happen we explicitly waitRequest(). This first waits for requests to appear and then waits for them to complete. runbot-error-944281 Forward-Port-Of: odoo/enterprise#125944
Fixed a niche issue where quotations created from templates with section or note lines could cause problems when connected to an active planning session. The update ensures only real sale order lines are considered, helping field service planning and sales workflows remain reliable.
Original PR description
This commit patches a niche bug involving creating a quotation via a quotation template containing a line section, then connecting it to an active planning session. The current architecture did not filter out `line_section` or `line_note` typed lines. This updated search domain resolves this issue. opw-6351484 Forward-Port-Of: odoo/enterprise#125056
Email notifications for tracked changes now display the expected arrows and parentheses, making updates easier to understand directly from the email. This fixes a presentation issue without requiring broad data changes to existing messages.
Original PR description
Bug === When notifying by email a tracking change, the arrow and parentheses are not rendered in the email body. Technical and Constraints ========================= The class `o_track` is only set in…
Bug === When notifying by email a tracking change, the arrow and parentheses are not rendered in the email body. Technical and Constraints ========================= The class `o_track` is only set in the web client template (`mail.Message`). There's no class in the body of the email that is sent. It can be rendered with "notification templates" that we cannot change either (and they just do `t-out="message.body"`, so the body field of the mail message has to be properly rendered). We also need existing mail messages to be rendered correctly, and so we need a way to differentiate mail messages created before the fix from those created after it, to know when to disable the arrow and parentheses. Alternatives ============ We have tough about many solutions, this one is the best we found based on the constraints we have 1. Add a class in 19.3, use that class to not remove the arrow on previous mail message. That solution required a migration script that will change all tracking messages. Because the initial migration of the tracking was really slow, we wanted to avoid that. 2. Add a class, and keep it forever. But that solution makes the body of the mail messages bigger, which defeat one of the purpose of the initial refactoring 3. Change the outgoing email without changing the body of the mail message. That solution was really not reliable (regex change to add the arrow, and we have no clean way to target the tracking rows) 4. During the migration create a system parameter with the date, and compare with the create_date of the mail message to know if we should add the arrows or not (but we will need to keep that system parameter forever, and the code to support both to) Task-6424104 Forward-Port-Of: odoo/odoo#282210
This fix prevents an error that could occur when sales reporting tries to use tracking information that is not fully in sync with the system. It helps keep sales and marketing attribution reports stable instead of failing unexpectedly.
Original PR description
Following 6dedae804748, in case `ir.model` models are out-of-sync with the available models in the registry, trying to compute the target selection model will result in a crash (`KeyError`). This commit ensure the target model is available in the registry to avoid that crash. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#284541 Forward-Port-Of: odoo/odoo#284161
This update fixes an internal automated test related to mobile mail notifications after a template tracking change. It helps keep quality checks reliable so future mail-related updates can be validated with fewer false failures.
Original PR description
Task-6424104 Forward-Port-Of: odoo/enterprise#127778
A small configuration error in Accounting report protections was fixed. This ensures two important invoice and vendor bill report records cannot be accidentally deleted, helping preserve expected reporting behavior.
Original PR description
On `ir.actions.report` we want to block the unlinking of specific reports in odoo. However, when the list was created a comma was missed between `action_account_original_vendor_bill` and `account_invoice_without_payment` which means we were actually protecting against people unlinking `action_account_original_vendor_billaccount_invoice_without_payment`. Adding in that comma will allow these two records to be properly protected. task-none Forward-Port-Of: odoo/odoo#283323
Copied project tasks now preserve the correct dependency order between their sub-tasks. This prevents duplicated tasks, task templates, and recurring tasks from showing misleading or reversed dependency chains, helping teams rely on copied project plans without manual cleanup.
Original PR description
**Problem:** Duplicating a task, creating a task from a task template, or generating the next occurrence of a recurring task scrambles the dependencies between its sub-tasks: each copied sub-task…
**Problem:** Duplicating a task, creating a task from a task template, or generating the next occurrence of a recurring task scrambles the dependencies between its sub-tasks: each copied sub-task carries the dependencies of a different sub-task instead of its own. **Steps to reproduce:** 1. Enable Task Dependencies on a project. 2. Create a task with three sub-tasks and chain them: the second depends on the first, the third depends on the second. 3. Duplicate the task, or use "Create from template" if the task is a template. 4. Open the sub-tasks of the new task and look at their dependencies. **Current behavior:** The dependencies of the copied sub-tasks are shifted: the chain runs in the reverse order of the original one. **Expected behavior:** Each copied sub-task depends on the copy of the sub-task its original depended on, so the new task reproduces the original chain. **Cause of the issue:** `_create_task_mapping` builds the original to copy mapping by pairing `original_task.child_ids` with `copied_task.child_ids` positionally, on the assumption stated in its docstring that both recordsets share the same index order. They do not. `project.task._order` ends with `id desc`, so `child_ids` is read newest-first, while the copies are created by iterating the original `child_ids` in that same order. The copies' ids therefore ascend along the original list, and reading them back through `child_ids` returns them in the exact reverse order. `zip` then pairs each original with the copy of the sub-task at the mirrored position, and `_resolve_copied_dependencies` writes every `depend_on_ids` and `dependent_ids` onto the wrong copy. This affects every caller of that method: `copy`, the task template action, and the creation of the next occurrences of a recurring task. **Fix:** Sorting the copied children by id restores the correspondence because id order is the order in which the copies were created from the original list, an invariant that holds whatever `_order` does, whereas the previous code silently depended on `_order` producing the same sequence on both sides. `test_duplicate_project_with_subtask_dependencies` and `test_recurrence_copy_task_dependency` were reading the copies by `child_ids` index too, which the mirrored mapping happened to satisfy, so they passed on a wrong result; they now index them in creation order as well. opw-6386578 Forward-Port-Of: odoo/odoo#284211 Forward-Port-Of: odoo/odoo#280893
This update improves the error shown when sending a French e-invoicing credit note in demo mode. Users now receive a clearer, more helpful message when EDI document generation cannot proceed, reducing confusion and support needs.
Original PR description
Steps to reproduce: - Install `l10n_fr_pdp` module > Switch to `FR Company` - Activate `French e-invoicing` (Demo mode) - Create a New `Credit Note` with `FR Customer` > Send Issue: The system currently displays a confusing error message during EDI document generation. We are making the error message clearer and more user-friendly. opw-6412521 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#284189
Duplicating multiple projects now gives each copied project only the milestones from its original project. This prevents confusing or incorrect milestone data from being added when teams duplicate projects in bulk.
Original PR description
Before this commit, duplicating several projects at once from the list view gave every copy the milestones of all the duplicated projects, because the copy loop assigned the milestones of the whole recordset instead of the ones of the project being copied. Duplicating a single project behaves correctly, which hid the issue. Steps to reproduce: - create two projects with milestones enabled, add a milestone to the first one and two others to the second one - select both projects in the list view and duplicate them Each copy contains the three milestones instead of only the milestones of its original project. Solution: Copy the milestones of the project being duplicated. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#278520
This fix prevents a signer who appears more than once in the same document from being prompted to sign again before the intervening signer has completed their step. It helps ensure agreements follow the configured signing sequence and reduces the risk of documents being completed out of order.
Original PR description
### Steps to Reproduce: 1. Create a sign request and have 3 total signers (User, Customer, Employee) 2. Enable Signing Order and make the order as follows: (1) User, (2) Customer, (3) Employee But…
### Steps to Reproduce: 1. Create a sign request and have 3 total signers (User, Customer, Employee) 2. Enable Signing Order and make the order as follows: (1) User, (2) Customer, (3) Employee But make the User and Employee the same contact 3. Send and sign the request > Notice that (1) is able to sign for (3) immediately after, (2) has not signed yet. ### Description of the issue/feature this PR addresses: **Issue:** The signing order is ignored when the same user has to sign multiple times on a document, even if it is configured for a different person to sign in between. This happens because all signature request items are initialized in the 'sent' state upon creation, rather than strictly advancing based on the order. As a result, the system prematurely allows users to sign out of order and prompts them with their next turn too early. **Solution:** To resolve this, the controller was updated to include an `is_mail_sent = True` domain filter. This ensures that the UI's post-sign popup only displays documents where it is explicitly the user's active turn, rather than prompting a premature sign. ### Current behavior before PR: Users are able to sign prematurely, and the system will disregard the configured signing order. ### Desired behavior after PR: Users will only be prompted and able to sign a document when it is explicitly their turn, per the `mail_sent_order`. This way, documents are signed in order. opw-6417327 Forward-Port-Of: odoo/enterprise#128487 Forward-Port-Of: odoo/enterprise#125573
Employees can now check in from the attendance shortcut when their employee record belongs to a non-default company. This prevents the check-in button from disappearing or selecting the wrong company in multi-company setups.
Original PR description
Steps to reproduce: - Install employees and attendance app - Make sure there are 2 companies - Make user's employee record for Company B, but not A - Make company A the default company for user - Enable "attendances from backend" setting - Click on the attendance dot (systray) Current Behavior: The dot disappears and you can't check in Expected Behavior: You are able to check in Other bug scenario: If you have employee records in both Company A and Company B, you can check in. However, you can never check in for Company B as the default company is always selected in the server code opw-6392301 Forward-Port-Of: odoo/odoo#281082 Forward-Port-Of: odoo/odoo#278377
The HTML editor now removes formatting correctly from list items that contain headings. It also prevents formatting applied to a parent list item from unintentionally affecting nested list items, making document editing more predictable.
Original PR description
**Current behavior before PR:**
1. Create a list with h1, have some text inside.
2. Select whole heading and apply underline style.
3. Click on remove format button.
You will notice that underline style is not removed from list.
**Desired behavior after PR is merged:**
Now, format is removed correctly from the list.
task-6348753
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-prPayroll users can now remove or adjust a payslip period without triggering an error. The system skips employee contract period checks until both payslip start and end dates are available, keeping payslip creation and editing smoother.
Original PR description
Currently, an error occurs when the user removes the payslip period. **Steps to Reproduce:** - Install the `hr_payroll` module. - Go to `Employees` and create an `employee`, or use an existing one. -…
Currently, an error occurs when the user removes the payslip period. **Steps to Reproduce:** - Install the `hr_payroll` module. - Go to `Employees` and create an `employee`, or use an existing one. - Make sure the `employee's version` has a `contract start date`. - Go to `Payroll` > `Payslips` > `Payslips` and create a payslip. - Select that `employee` on the payslip, then remove the `payslip start date`. `TypeError: '<=' not supported between instances of 'datetime.date' and 'bool'` After the [recent commit], which computes the version from the payslip period without allowing it to be overridden, when the user selects an employee whose version has a contract start date, it checks whether the version overlaps with the payslip period [1]. However, since the payslip dates have not yet been set, it raises an error. This commit ensures that the check for the version overlapping with the payslip period is skipped if the payslip does not have both a start and end date. It also makes the method depend on date_to, because if the user changes the payslip end date, it should recheck whether the version overlaps with the payslip period. [recent commit]: https://github.com/odoo/enterprise/commit/569ce2af32477d410b79e98ca72729385042619b [1]- https://github.com/odoo/enterprise/blob/8a66d7beabe6f9b28000ef12725f3a9937d5d1ee/hr_payroll/models/hr_payslip.py#L1716-L1725 sentry-7632216317
Cash in and out receipts in Point of Sale now print even when a default printer has not been set yet. The system will choose an available fallback printer, reducing failed receipt printing during store operations.
Original PR description
## Description Fixes cash in/out receipt printing when no default printer is configured. ## Issue Previously, an early return in the printer selection logic prevented the fallback printer mechanism from being executed, causing receipt printing to fail when no default printer was configured. ## Fix Removed the early return so that the fallback printer selection logic can select an available printer before attempting to print the receipt. This ensures cash in/out receipts can be printed even when no default printer is configured. opw-6485495 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
The overtime rules screen now only shows the related employee versions button to HR managers. This prevents HR officers from seeing an access error caused by data that requires manager-level permissions, improving reliability during normal use and upgrades.
Original PR description
The button requires the group `hr.group_hr_user`, but the button uses `versions_count`, that in its computation uses fields like `contract_date_start` that require the group `hr.group_hr_manager`. To avoid the mismatch, the button is restricted to only managers. This error was found in upgrades failing. To reproduce: - Install `hr_attendance`. - Assign any employee the Default Ruleset to make the button not invisible. - Change the HR security of your user to Officer. - Go to Attendance->Configuration->Overtime Rulesets and try to see the record. - A message will display the following error: ``` You do not have enough rights to access the field "contract_date_start" on Employee Record (hr.version). Please contact your system administrator. Operation: read User: 2 Groups: allowed for groups 'Employees / Administrator' ``` Forward-Port-Of: odoo/odoo#284194
The website editor now preserves the intended color styling for links that have their own color theme. This keeps the Splash Intro scroll button icon readable and prevents section styling from accidentally reducing contrast.
Original PR description
Steps to reproduce: - Drag and drop a "Splash Intro" snippet onto the page. - Inspect the scroll button. => The icon uses the `o_cc5` link color from the section. => There is not enough contrast between the arrow and the button background, making the arrow hard to see. Before this commit, color combination link rules still targeted links that were color combination roots themselves. Since [1] added `o_cc5` on the `s_splash_intro` section, its `a:not(.btn)` rule overrode the `o_cc1` scroll button color. After this commit, link color rules skip elements with `o_cc`, so a link using its own color combination keeps its own colors. [1]: https://github.com/odoo/odoo/commit/b7a4edb9fa3d task-6303725
Customers using Authorize.Net can once again save a new payment method from the portal. The fix stores the payment details before the authorization is cancelled, preventing the saved method from being lost.
Original PR description
Issue: --- Authorize payment tokenization doesn't work. Steps: 1- Setup authorize payment provider. 2- Using portal page, add a new payment method for the user. The created payment method is not…
Issue: --- Authorize payment tokenization doesn't work. Steps: 1- Setup authorize payment provider. 2- Using portal page, add a new payment method for the user. The created payment method is not saved. Cause: --- The issue was introduced in efc2788dfccd13ee6feb309430ff57e49664ff97. Before that, we were calling `_tokenize` before voiding the tx. In that PR, the `_tokenize` call was moved to `_process()`, after `_apply_updates()`. So now what happens is that we void the tx, then call `_tokenize()`. Inside tokenize we try to create a customer profile, which fails because the tx is already voided. Fix: --- We can fix it by calling `_tokenize()` once before voiding the tx. The redundant tokenize call inside the general payment tx `_process` is rendered ineffective by two safeguards: 1- There is a check for `tx.tokenize`, which neutralizes double tokenization: https://github.com/odoo/odoo/blob/fffd987cc98d1ea0cd04e24dda2ed8b64a219cdc/addons/payment/models/payment_transaction.py#L754-L755 https://github.com/odoo/odoo/blob/fffd987cc98d1ea0cd04e24dda2ed8b64a219cdc/addons/payment/models/payment_transaction.py#L893-L896 2- If `token_id` is already set, no token value is returned: https://github.com/odoo/odoo/blob/fffd987cc98d1ea0cd04e24dda2ed8b64a219cdc/addons/payment_authorize/models/payment_transaction.py#L237-L243 opw-6426847 Forward-Port-Of: odoo/odoo#283134 Forward-Port-Of: odoo/odoo#281014
Peruvian accounting reports now use the exchange rate already stored on each accounting entry instead of recalculating it during report generation. This reduces rounding differences and helps produce more reliable financial reporting figures.
Original PR description
Previously, the `_get_ple_report_data` method computed the currency rate when called. Since the calculation was based on the entry totals, it was prone to rounding errors. This PR makes it use the rate stored in the entry itself. This should lead to more accurate results. opw-6411322 Forward-Port-Of: odoo/enterprise#128027 Forward-Port-Of: odoo/enterprise#126882
AI chat windows now appear in front of other conversations when opened on mobile screens. This prevents chats and related AI popups from being hidden behind fullscreen editors or existing windows, making the feature easier to use.
Original PR description
AI chats opened on mobile views could appear behind other chats. This was inconsistent with the expected stacking behavior, where newly opened chats should appear on top of existing ones. To reproduce: * Open the chatter of any module. * Open the message composer in fullscreen mode. * Click the AI button. This commit increases the z-index of AI chats on mobile views so they are displayed on top of other chats. task-6412411 Forward-Port-Of: odoo/enterprise#128649 Forward-Port-Of: odoo/enterprise#128346
Chat windows now keep their normal display priority by default, but other Odoo apps can adjust how they layer on mobile screens when needed. This helps prevent chat from unintentionally covering or being covered by other interface elements in customized setups.
Original PR description
The z-index of chat windows on mobile views was previously fixed at `1020`, preventing other modules from adjusting their stacking order. This commit introduces a configurable z-index for chat windows, defaulting to `1020` while allowing other modules to override it when needed. task-6412411 Forward-Port-Of: odoo/odoo#283671 Forward-Port-Of: odoo/odoo#283178
The lunch ordering test now waits for the correct product to appear after changing location before placing an order. This prevents occasional failures caused by outdated demo products still being shown, improving test reliability without changing user-facing behavior.
Original PR description
The lunch order tour selects `Farm 1` before ordering a product. However, it only waits for the location input to be updated before clicking the first kanban record. With demo data installed, a product from the previous location can still be displayed while the product model is being reloaded. The tour can therefore order a demo product instead of the product created by the test. This notably fails during weekends when the corresponding demo vendor is unavailable. To fix we need to wait for the product created by the test before clicking it. Besides selecting the intended product, this also ensures that the product reload following the location change has completed. [error-181572 ](https://runbot.odoo.com/odoo/error/181572) --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#281753
The recruitment job offer screen now shows the quick assign button at the same size as the avatar image when a company is linked. This removes a small visual mismatch and makes the interface look cleaner and more consistent.
Original PR description
Before this PR, the o_quick_assign button was not the same size as the o_avatar img which makes it look like it's misaligned when there is a company associated with the job offer. task-6092395 | Before | After | |--------|--------| | <img width="1058" height="705" alt="Screenshot 2026-04-20 at 15 19 03" src="https://github.com/user-attachments/assets/31b38b78-591d-4c55-b3e8-b3888484f9a1" /> | <img width="1058" height="705" alt="Screenshot 2026-04-20 at 15 29 40" src="https://github.com/user-attachments/assets/6f78190a-be92-4eeb-9a9f-55e8f247bf1c" /> | --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#283038 Forward-Port-Of: odoo/odoo#260133
Fixed a point-of-sale issue where eWallet or gift card payments could discount an order by one cent less than the amount taken from the card when certain tax settings were used. This keeps the customer charge aligned with the redeemed card balance and prevents small payment discrepancies.
Original PR description
When paying with an eWallet or gift card in POS, the reward line could end up 0.01 short of the actual card balance if the discount product's tax is configured as tax-excluded through a per-tax…
When paying with an eWallet or gift card in POS, the reward line could end up 0.01 short of the actual card balance if the discount product's tax is configured as tax-excluded through a per-tax override, regardless of the tax's own default configuration. The card is still debited for the full balance, but the order is only discounted by one cent less, so the amount charged to the customer no longer matches the amount consumed from the card. Steps to reproduce: ------------------- * Top up an eWallet (or gift card) with a balance of 10.00 * On the eWallet/gift card program's discount product, set an 18% tax whose Tax Computation is overridden to "Excluded" (price_include_override = tax_excluded), independently of the company's default tax configuration * In POS, add a product to an order and pay (partly) with that eWallet/gift card > Observation: Only 9.99 is deducted from the order total, while the backend correctly shows 10 consumed on the wallet/gift card. Why the fix: ------------ The reward line's price_unit was reconstructed from a one-time backward tax computation, then kept only the tax amount for taxes whose price_include field was true, dropping it for any tax forced excluded. That price_unit was later re-taxed forward using the tax's real (excluded) configuration, and the two roundings don't agree for rates like 18%, losing a cent. We now force special_mode "total_included" whenever an eWallet/gift card reward line's taxes are computed, not just at creation, so its tax-included total always equals the exact redeemed amount regardless of how the tax is configured, and store price_unit as that target amount directly. opw-5819389 Forward-Port-Of: odoo/odoo#284397 Forward-Port-Of: odoo/odoo#278568
Website pages are now refreshed correctly when a visitor changes their cookie preference from denied to accepted. This prevents visitors from seeing an outdated cached version of a page and helps ensure the website reflects the correct consent-based behavior.
Original PR description
Initially with [commit 958b41c4], when cookies were denied (the page is cached a 1st time), then accepted (the page cache must be invalidated), cached pages would be computed again. This behavior was lost with [6c8a90ec], since which website pages are cached more aggressively. [commit 958b41c4]: https://github.com/odoo/odoo/commit/958b41c4acec7e1700ca4d6e0b25ee0ad2aac9f1 [6c8a90ec]: https://www.github.com/odoo/odoo/commit/6c8a90ecba45fb99addf1b86fe237fd626fba650 task-6471290 Forward-Port-Of: odoo/odoo#283863 Forward-Port-Of: odoo/odoo#282737
A problem during failed subscription payments could make the recurring billing process crash instead of handling the failed transaction cleanly. This fix prevents that interruption, helping automated subscription invoicing continue more reliably when a saved payment method is invalid.
Original PR description
Step to reproduce: - create a faulty token that won't work and link it to a subscription - launch the recurring invoice cron - the following traceback occurs ``` last_tx_sudo = (self.transaction_ids…
Step to reproduce:
- create a faulty token that won't work and link it to a subscription
- launch the recurring invoice cron
- the following traceback occurs
```
last_tx_sudo = (self.transaction_ids - existing_transactions).sudo()
```
When the payment fails, the system rollback and we store the last_tx_sudo value in a dedicated variable. After rollback, the record does not exists anymore. Therefore, accessing the value fails.
```
File "/home/odoo/src/enterprise/saas-18.2/sale_subscription/models/sale_order.py", line 1703, in _handle_automatic_invoices
if not last_tx_sudo or last_tx_sudo.renewal_state in ['pending', 'authorized']:
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/saas-18.2/odoo/orm/fields.py", line 1439, in __get__
self.compute_value(record)
File "/home/odoo/src/odoo/saas-18.2/odoo/orm/fields.py", line 1603, in compute_value
records._compute_field_value(self)
File "/home/odoo/src/odoo/saas-18.2/odoo/orm/models.py", line 4575, in _compute_field_value
determine(field.compute, self)
File "/home/odoo/src/odoo/saas-18.2/odoo/orm/fields.py", line 69, in determine
return needle(*args)
^^^^^^^^^^^^^
File "/home/odoo/src/enterprise/saas-18.2/sale_subscription/models/payment_transaction.py", line 25, in _compute_renewal_state
if tx.state in ['draft', 'pending']:
^^^^^^^^
File "/home/odoo/src/odoo/saas-18.2/odoo/orm/fields.py", line 1406, in __get__
raise MissingError("\n".join([
odoo.exceptions.MissingError: Record does not exist or has been deleted.
```
Moreover, since https://github.com/odoo/enterprise/pull/45236/files#diff-c36fd7952cc2bef40716419a668de41963d49e1aa4177d9319d503fc260da588R1678-R1682
```
if not last_tx_sudo or not last_tx_sudo.renewal_state not in ['pending', 'authorized']:
```
has become
```
if not last_tx_sudo or last_tx_sudo.renewal_state in ['pending', 'authorized']:
```
But it feels strange to unlink the invoice when the payment succeed.
This PR fixes it.
Forward-Port-Of: odoo/enterprise#83913Refunds made in Odoo are now correctly recognized when Stripe sends a refund confirmation webhook. This prevents duplicate refund records for the same Stripe refund, improving payment accuracy and reducing reconciliation confusion.
Original PR description
Steps to reproduce: - Configure Stripe with manual capture. - Authorize and capture an online payment. - Refund the captured payment from Odoo. - Let the `charge.refunded` webhook be processed. The refund initiated from Odoo is created as a child of the capture transaction, while the webhook resolves the charge to the source transaction. The webhook only checked direct refund children of that source transaction, so it missed the existing refund and created a second refund transaction with the same Stripe refund reference. Look up existing Stripe refund transactions in the child and grandchild transactions of the source transaction before creating webhook refund transactions, so the webhook recognizes refunds already created under capture children. opw-6359020 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#283790 Forward-Port-Of: odoo/odoo#276154
This fix prevents a manufacturing test cleanup from accidentally targeting stock rules outside the intended route or company. It reduces the risk of test failures caused by trying to remove records that are still in use, improving reliability for ongoing releases.
Original PR description
`test_check_update_qty_mto_chain` was removing `stock.rule` records from other companies using `mto_route.rule_ids.search()`. Calling `search()` on a recordset does not restrict the search to the records already present in that recordset, so the domain was effectively applied to all `stock.rule` records. With demo data, this could attempt to unlink an unrelated stock rule that is still referenced by an existing stock move, causing a `stock_move_rule_id_fkey` foreign key violation. This commit restricts the search explicitly to rules belonging to `mto_route` before unlinking them. [error-940031 ](https://runbot.odoo.com/odoo/error/940031) --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#282930
Sales quotation and pro forma email templates now use separate full sentences for quotations and orders. This lets translators adapt grammar correctly in languages where the two terms require different wording, improving customer-facing email quality.
Original PR description
The quotation and pro forma email templates inserted either "quotation" or "order" into shared translatable text. In French, for example, "devis" is masculine while "commande" is feminine, so the surrounding articles and adjectives cannot agree with both terms. Define a complete sentence for each document state so translators can translate the surrounding grammar independently. opw-6445304 Forward-Port-Of: odoo/odoo#283901 Forward-Port-Of: odoo/odoo#283229
Fixes a report layout issue where customized sale order PDFs could show an empty column after users removed fields such as Taxes or Discount in Studio. This keeps printed sale documents cleaner and aligned with the customer’s configured report layout.
Original PR description
**Steps to reproduce:** 1. Open a Sale Order report in Studio 2. Delete the Taxes column 3. Save 4. Create a sale order with at least one section line and products that have taxes 5. Print the sale…
**Steps to reproduce:** 1. Open a Sale Order report in Studio 2. Delete the Taxes column 3. Save 4. Create a sale order with at least one section line and products that have taxes 5. Print the sale order PDF **Issue:** - A blank column is rendered in the PDF report on section (and combo) rows whenever a column such as Taxes or Discount is removed via Studio. **Why this happens:** - The section row's `colspan` and the combo row's `colspan` were computed using `3 + (1 if display_discount else 0) + (1 if display_taxes else 0)`. - `display_taxes` and `display_discount` are derived from order data (i.e. whether any line has taxes/discounts), not from which columns are actually rendered in the table. - When Studio removes a column it deletes the `<th>` and matching `<td>` elements via XPath, but these Python variables remain `True`. As a result, section/combo rows still accounted for the removed column in their `colspan`, producing one extra cell and a visible blank column. **Fix:** - Introduce a `colspan_count` variable which is incremented inside each `<th>` body - Use that counter for `td_section_name` and `td_combo_name` instead of the previous formula. - Because the increment occurs inside the `<th>` element, it is skipped whenever the element is not rendered, whether because `display_taxes`/`display_discount` is `False` or because Studio's XPath removed the element entirely. opw-6433679 Forward-Port-Of: odoo/odoo#283657 Forward-Port-Of: odoo/odoo#280719
Intrastat reports now only include rental orders when the rental duration meets the required two-year threshold. This prevents short-term rentals from being reported incorrectly, improving compliance and report accuracy.
Original PR description
Problem: Some rental orders are showing in Intrastat reports when they should not be showing. Only rental orders with duration of 2 years or more should be shown in Intrastat reports. However, all rental orders are being shown. <img width="783" height="768" alt="intrastat_leasing" src="https://github.com/user-attachments/assets/7419e3dc-7b3e-4234-809f-6973fef93fc1" /> Cause: When querying the lines to show in the Intrastat report, there is no condition that checks for the duration of rental orders. opw-6351456 Forward-Port-Of: odoo/enterprise#125042
Timesheets now automatically clear a selected task when the project is changed to one that does not include that task. This prevents incorrect project-task combinations when users update timesheets in bulk or through automated processes, improving data accuracy for reporting and billing.
Original PR description
When modifying project_id on a timesheet through mass edit/rpc or anything that is not triggering `onChange`. The task_id would not be reset if it doesnt' belong to the new project set on the timesheet. Steps to reproduce: ------------------- * Install studio for easier reproducing of the issue * Open the timesheet list view * Open studio and activate the mass edit on the view * Modify the project_id on multiple records > Observation: The task_id stays the same even if they do not belong to the new set project Why the fix: ------------ Instead of relying only on the onChange we add an inverse to the project_id that will reset the task when needed. opw-6259149 Forward-Port-Of: odoo/odoo#283882
Argentina accounting users can now create invoices for foreign customers even when export sales journals are unavailable or archived. The system falls back to a standard Invoice B document type, avoiding an unnecessary workflow block while keeping invoice creation possible.
Original PR description
### Issue before this commit: Before this commit, users were completely blocked from creating an invoice for a foreign partner (e.g., "Cliente del Exterior") if all exportation journals were archived…
### Issue before this commit: Before this commit, users were completely blocked from creating an invoice for a foreign partner (e.g., "Cliente del Exterior") if all exportation journals were archived or unavailable, as the system would immediately trigger a RedirectWarning error. ### Steps to reproduce the issue: 1. Download Accounting and l10n_ar 2. Go to contacts and create a new one with: 1. Country as United States 2. VAT number ex. 55000002126 3. AFIP Responsibility Type as Cliente del Exterior 3. Go to Journals, filter for sales journals and archive: 1. Electronic Exportation Invoice (FEX) 2. Expo Sales Journal 4. Go to invoices and create a new one for the client you just created 5. As soon as you insert the client you will receive the error: You are trying to create an invoice for foreign partner but you don't have an exportation journal ### Cause of the issue: https://github.com/odoo/odoo/blob/014d58e3204d17db6dcba3c8ab7d8ad35003300e/addons/l10n_ar/models/account_move.py#L186-L189 The _onchange_partner_journal method rigidly enforced the use of an exportation journal for foreign AFIP responsibility types (codes 8, 9, and 10). If the query failed to find an active export journal, the code intentionally threw a hard error instead of providing a fallback mechanism. ### Reason to introduce the fix: This fix is introduced to prevent unnecessary workflow blocks. By catching the missing journal and defaulting the document type to "Invoice B" (code 6), the user can now successfully generate the invoice using a standard domestic sales journal without being forced to configure an exportation journal. opw-6442501 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#282971
Odoo now prevents users from selecting Peppol identifier codes that are deprecated or removed from the official specification. This helps avoid invalid Peppol registrations and partner records when creating or duplicating contacts.
Original PR description
Peppol EAS codes 0037, 0213, 9955, and 0193 are deprecated or removed from the Peppol specification but are still present in the selection field on stable branches, allowing users to register invalid identifiers. See: [eas codes](https://docs.peppol.eu/edelivery/codelists/v9.7/Peppol%20Code%20Lists%20-%20Participant%20identifier%20schemes%20v9.7.html) Before: - deprecated EAS codes were listed alongside valid ones in the partner's available Peppol EAS options, allowing users to select an outdated identifier for new or duplicated partners, or during Peppol registration. After: - Excluded deprecated EAS codes from the available Peppol EAS selection list on partners, preventing users from selecting them for new or duplicated partners, or during Peppol registration. Removed Deprecated codes in Master: odoo/odoo#271288 Task [link](https://www.odoo.com/odoo/project.task/6299691) task-6299691 Forward-Port-Of: odoo/odoo#283698 Forward-Port-Of: odoo/odoo#271435
This fix improves how timesheet suggestions created from calendar events are linked to the right project or task. It also corrects duration calculations when overlapping time entries are adjusted, helping users see more reliable timesheet information.
Original PR description
task: 6435164 Forward-Port-Of: odoo/enterprise#126680
Chilean electronic invoices received by email now import correctly when they include foreign-currency line details but do not provide a foreign-currency total in the header. This prevents failed imports and helps businesses process supplier invoices without manual intervention.
Original PR description
When importing an incoming DTE through the fetchmail server, the total amount is read from the MntTotOtrMnda as soon as a Moneda node is present in the document. Steps to reproduce: - Set up a CL company with a DTE mail server - Fetch a DTE that includes the line-level Moneda node but does not include the header OtraMoneda block, so no MntTotOtrMnda - Run the fetchmail cron and check the logs Issue: The DTE fails to import Analysis: Occurs since https://github.com/odoo-dev/enterprise/commit/5805a92f91411846fdffa245cb047397cfc9b1f3 Moneda is defined at line level while MntTotOtrMnda in the optional header block Encabezado/OtraMoneda. Instead of assuming MntTotOtrMnda is always present whenever the document carries a foreign currency, fall back to the base-currency total MntTotal when it is missing. opw-6432612 Forward-Port-Of: odoo/enterprise#128766 Forward-Port-Of: odoo/enterprise#126869
Employees and managers can now mark multiple appraisals as done from the list view without an error. The system sends the completion notification separately for each appraisal, ensuring the process finishes reliably.
Original PR description
Steps to reproduce: - select multiple appraisals and try to mark as done from list view. Issue: - The completion notification uses an appraisal variable assigned by a previous loop, raising an UnboundLocalError. Furthermore, message_notify() requires a singleton. Fix: - notify and post the completion message for each appraisal explicitly. task-6479018 Forward-Port-Of: odoo/enterprise#128207
The accounting app now blocks fiscal year periods that fully contain an existing fiscal year. This prevents duplicate or conflicting fiscal calendar setup, helping maintain accurate reporting periods.
Original PR description
Before this commit: - The current constraint for overlap check allows if we define a new, larger fiscal year that completely swallows an existing smaller one (e.g., creating Aug 2025 - Nov 2026 when Sept 2025 - Oct 2026 already exists). After this commit: - The constrain domain was changed to consider the above missed case. no task Forward-Port-Of: odoo/enterprise#128942
This fixes an issue where a required operation-level quality check could disappear after partially receiving goods in the Barcode app and returning to the transfer. Businesses can now rely on quality controls remaining in place until the full receipt is properly handled, reducing the risk of missed inspections.
Original PR description
Steps to reproduce --- 1. Create a quality control point on the Receipts operation type with Control per set to Operation. 2. Confirm a receipt of 2 units of the product: one pending operation…
Steps to reproduce --- 1. Create a quality control point on the Receipts operation type with Control per set to Operation. 2. Confirm a receipt of 2 units of the product: one pending operation quality check is created. 3. In the Barcode app, receive 1 unit and go back to the transfer with the back button. 4. The pending operation quality check is gone. Issue --- Going back from the Barcode app calls `post_barcode_process`, which on a partial reception splits the picked move into a done move and a remaining move, then merges the transient duplicate back with `_merge_moves`. https://github.com/odoo/enterprise/blob/b89614661682ecbd131d940539aac8afdd9d7289/stock_barcode/models/stock_move.py#L57-L60 `_merge_moves` cancels that transient duplicate through `_action_cancel` before unlinking it. https://github.com/odoo/odoo/blob/8f3100ca597559945cc42d9ef9517edbb40a900b/addons/stock/models/stock_move.py#L1400-L1401 The `quality_control` override of `_action_cancel`, picks the pending checks to drop from `is_product_canceled`, a `defaultdict(lambda: True)` keyed by `(picking, product_id)`. An operation check has no `product_id`, so its key is never computed by the loop and reads back the `True` default, so it is deleted even though the transfer still has a live move. Since an operation check covers the whole transfer, it must be dropped only when every move of its picking is cancelled. https://github.com/odoo/enterprise/blob/b89614661682ecbd131d940539aac8afdd9d7289/quality_control/models/stock_move.py#L68-L76 opw-6439179 Forward-Port-Of: odoo/enterprise#129051 Forward-Port-Of: odoo/enterprise#127427
Changing the project on timesheet entries through mass editing or automated updates now clears any task that does not belong to the new project. This prevents incorrect project-task combinations and helps keep timesheet reporting accurate.
Original PR description
When modifying project_id on a timesheet through mass edit/rpc or anything that is not triggering `onChange`. The task_id would not be reset if it doesnt' belong to the new project set on the timesheet. Steps to reproduce: ------------------- * Install studio for easier reproducing of the issue * Open the timesheet list view * Open studio and activate the mass edit on the view * Modify the project_id on multiple records > Observation: The task_id stays the same even if they do not belong to the new set project Why the fix: ------------ Instead of relying only on the onChange we add an inverse to the project_id that will reset the task when needed. opw-6259149 Forward-Port-Of: odoo/enterprise#128799
Appointment invitation emails now generate public calendar links without triggering permission errors. This helps ensure attendees can receive and open their appointment invitations consistently.
Original PR description
Since calendar attendee access tokens are restricted to system users, appointment mail templates must sudo token reads when generating public calendar links. This follows the same pattern as the calendar mail templates and avoids an AccessError when rendering attendee invitation emails. ref: https://github.com/odoo/enterprise/commit/88a3cca752a5f726cd0260b485fc93f65a268cf8 Forward-Port-Of: odoo/enterprise#128959
This fix ensures French VAT submissions treat notes containing only spaces as empty. It prevents incomplete XML filings from being sent to ASPOne, reducing avoidable submission errors for businesses filing French tax returns.
Original PR description
While sending the tax return to ASPOne, before adding the BC zone we are checking that BA zone won't be empty as if BC is completed there must be the BA zone in the xml file. The problem is that when we have only whitespaces, the condition will be respected but later on due to cleanup_xml_node(), the BA zone will not be rendered in the xml but BC will and it leads to an error This commit checks that express_mention_reason fields is not empty or not only whitespaces task-6476440 Forward-Port-Of: odoo/enterprise#128242
Financial report snapshots are now blocked when an open-ended tax or fiscal lock exception keeps a period editable. This prevents users from seeing outdated report amounts and removes snapshots created during that exception period.
Original PR description
An open-ended fiscal or tax lock exception keeps the period editable, but snapshot generation did not consider it and could serve stale amounts. Prevent snapshots while a full exception is active and clear snapshots created during it. opw-6427776 Forward-Port-Of: odoo/enterprise#127525
The point of sale variant selection popup now shows the truly available quantity after reserved stock is deducted. This prevents staff from seeing misleading stock levels when items have already been committed to sales orders.
Original PR description
## Steps to reproduce: - Create another warehouse - Create a product with a variant, like Color, values black and white - Track the product, add a qty on hand of 50 on the black product - Go to the…
## Steps to reproduce: - Create another warehouse - Create a product with a variant, like Color, values black and white - Track the product, add a qty on hand of 50 on the black product - Go to the sales app, make a quotation of 50 for the black product - Confirm the quotation - Go to the PoS, click on the product, check the available qty in the popup - It is still 50, even though the forecasted is correct at 0 ## Why the fix: Having the actual free qty was added in this commit 682bc82 to be able to check the qty that was really free instead of the available qty. This means that we subtract the reserved_qty from the qty_available to get the free_qty. The variant popup was forgotten in this commit, so it was still displaying the qty_available. This is why there was a difference in the qty if we press the product normally or if we long press it, because the variant popup was forgotten in said commit. opw-6382845 Forward-Port-Of: odoo/odoo#284321 Forward-Port-Of: odoo/odoo#280330
The email editor now skips unsupported figure layouts, such as figures without an image or with multiple images, instead of failing. This prevents Helpdesk tickets created from incoming emails from showing an error when those emails contain valid but uncommon figure formatting.
Original PR description
**Steps to reproduce:** - Install Helpdesk - Create an email with a figure that has no image - Send it to Helpdesk email alias - Open up auto-created ticket from the email - `OwlError` is raised on `CaptionPlugin.addImageCaption` **Issue:** `CaptionPlugin` [1] was designed for `<figure>` elements with a single `<img>` and a single `<figcaption>` (mainly for editor direct interactions). But the HTML specifications also allow `<figure>` with 0 or more than 1 `<img>` element(s), in which case an error is raised (or some elements are removed). (see https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/figure) **Fix:** Ignore such `<figure>` for now as it would require a rework of the plugin. [1] https://github.com/odoo/odoo/commit/b9d112a5800cfe11dc434caa0d335fa3f3db7178 opw-6413422 Forward-Port-Of: odoo/odoo#284139 Forward-Port-Of: odoo/odoo#279981
UPS shipping rate requests now accept phone numbers that match UPS rules, including shorter numbers used in countries such as Luxembourg. This prevents valid customers from being blocked when adding UPS shipping to a quotation.
Original PR description
**Steps to reproduce:** - Create a contact with a phone number that has 9 characters - Setup an UPS carrier, a configuration that works is UPS Saver as Service Type and UPS Package/customer supplied…
**Steps to reproduce:** - Create a contact with a phone number that has 9 characters - Setup an UPS carrier, a configuration that works is UPS Saver as Service Type and UPS Package/customer supplied as a package type - Create a quotation, put the created contact as a client - Try adding a shipping and getting the rates - An User Error appears, the phone number is too short **Why the fix:** Before this commit, any phone number that was less than 10 characters would raise an User Error, but some countries, such as Luxembourg, use phone numbers that are nine characters long or even less. If we check the official UPS documentation (https://developer.ups.com/tag/Shipping?loc=en_EN#operation/Shipment), we can see in the Ship_to/Phone section, that the phone number should be a number between 1 and 15, not saying it should be 10 characters or more. <img width="495" height="473" alt="image" src="https://github.com/user-attachments/assets/fed82987-ffb8-4b84-b282-6c3d3b4f304e" /> After this commit, we adapt the way we prevent the user from inputing phone numbers to fit the official UPS documentation. opw-6307577 Forward-Port-Of: odoo/enterprise#128632 Forward-Port-Of: odoo/enterprise#122831
The German tax report now keeps decimal values for the Kz83 field instead of rounding them down to whole numbers. This helps ensure submitted XML reports show accurate amounts, such as 26.40 instead of 26.00.
Original PR description
Description of the issue this commit addresses: The German tax report XML casts Kz83 to an integer before formatting it. This truncates decimal values, causing amounts such as 26.40 to become 26.00. --- Desired behavior after this commit is merged: This commit preserves the Kz83 decimal value and formats it with two decimal places in the German tax report XML. --- task-6414439 Forward-Port-Of: odoo/enterprise#125607
Deleting a draft invoice for timesheet-based services no longer removes or changes the sales order item tied to the original timesheets. This prevents billed hours from disappearing from the original order or being reassigned incorrectly, keeping delivered quantities accurate when invoices are cancelled and recreated.
Original PR description
Deleting a draft customer invoice linked to timesheets resets their timesheet_invoice_id so the hours become invoiceable again. This write also marks the timesheets' so_line for recompute, and the…
Deleting a draft customer invoice linked to timesheets resets their timesheet_invoice_id so the hours become invoiceable again. This write also marks the timesheets' so_line for recompute, and the re-derivation runs while the lines are no longer protected by the invoice link. When the task or project no longer resolves to a sale order item (e.g. it was unlinked after invoicing), the timesheets lose their sale order item or get reassigned to another one, so the delivered hours silently disappear from the original order line. Protect so_line during the write and drop the pending recompute: deleting an invoice must only make the hours invoiceable again, not change their allocation. Steps to reproduce: - Install Sales and Timesheets - Create a service product with invoice policy "Based on Timesheets" and "Create a task in a new project" - Create and confirm a sale order with this product - Log a timesheet on the generated task - Create the invoice (keep it in draft) - Remove the Sales Order Item from the task and from the project settings (or point them to a sale order item of another order) - Delete the draft invoice - Open the timesheet: its Sales Order Item is emptied (or replaced by the other order's item, whose delivered quantity now includes the hours sold on the original order), and the original line's delivered quantity is reset --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#283831 Forward-Port-Of: odoo/odoo#279552
This fixes an issue where the HTML editor could remove inline items that looked empty because they had no text, even though they displayed visible content such as icons or images. Saved pages and content should now better retain those visual elements, reducing accidental formatting or content loss.
Original PR description
#### Description of the issue this PR addresses: - Empty inline elements were detected using only their text content, causing visible non-text content (e.g. icons, images) to be removed. #### Desired behavior after PR is merged: - Remove the empty inline attribute when an inline element contains visible content by using `isVisible`. - Use `isVisible` to detect visible content instead of relying on text content. task-6391496 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
The update ensures the system always includes the required signup type when creating signup or invitation links. This prevents token generation failures that could block invited users from accessing shared project or portal content.
Original PR description
A `signup_type` is required to generate a token. Task-6452339 Forward-Port-Of: odoo/odoo#283417 Forward-Port-Of: odoo/odoo#280891
This update stabilizes an automated website test related to hiding popups, reducing occasional false failures during quality checks. It does not change the website experience for users, but helps keep development and release validation more dependable.
Original PR description
The test `undoing something on a target outside s_popup closes it` had a few fails in CI: the `fa-eye-slash` was not set as expected. This commit adds a `waitSidebarUpdated` call just before to ensure owl has no pending rendering when checking the eye. The fix is similar to aaf0f54d1feda60becb0bfbad578b366715c0172 which is about a similar failure in another test. runbot-938967 Forward-Port-Of: odoo/odoo#284381
The mail thread data endpoint now requests only the information actually needed for each user and conversation. This reduces unnecessary data handling and helps make mail discussions more reliable across access scenarios.
Original PR description
This change cleans up the requested data from `/mail/thread/data` route, ensuring it aligns with what is actually needed depending on the user and thread. part of task-6452761 Forward-Port-Of: odoo/odoo#284350 Forward-Port-Of: odoo/odoo#280713
Fixes an accounting reconciliation error that occurred when users worked with a parent company and branch company selected at the same time. The change ensures the correct company is used for currency conversion, allowing journal items in multi-company setups to reconcile without crashing.
Original PR description
When having multiple companies selected at the same time, _get_conversion_rate returns: File "/data/build/odoo/odoo/orm/fields_misc.py", line 114, in get raise ValueError("Expected singleton: %s" %…
When having multiple companies selected at the same time, _get_conversion_rate returns:
File "/data/build/odoo/odoo/orm/fields_misc.py", line 114, in get
raise ValueError("Expected singleton: %s" % record)
1 - Create a new company with currency EUR.
2 - Create a branch company underneath the main company.
3 - In Accounting, install fiscal localization, e.g. Belgian Companies on the company configuration settings.
4 - Select an account like 600000 Raw Materials, and enable Allow Reconciliation on this account. The exact account isn't important, only that we can make credits / debits to it to be reconciled.
5 - With only the top level company selected, make a debit of 100 USD, e.g. Vendor Bill, set in currency USD to the account 600000.
6 - Now with only the branch level company selected, make a credit of 100EUR, e.g. Customers Invoices, set in currency EUR to the same account with an amount equal to the credit in step 5. (if 1USD == 1EUR, 1-1), so that there is no residual amount, i.e. credit == debit.
7 - Now select both the top level company and the sub branch company in the company context.
8 - In Journal Items, reconcile the unreconciled journal items for the Account 600000.
With this commit we select the first company of the aml instead of every companies on the amls.
opw-6290703
Forward-Port-Of: odoo/enterprise#123774Fixes seven mislabeled entries in the Mexican chart of accounts so exported electronic accounting files use the official SAT descriptions. This helps Spanish-language Mexican databases report account group names accurately while leaving trial balance and journal policy exports unchanged.
Original PR description
Seven entries of the Mexican chart of accounts template carry a name belonging to a **different** group, copied from a neighbouring entry. Each record's XML ID still states the intended name, which…
Seven entries of the Mexican chart of accounts template carry a name belonging
to a **different** group, copied from a neighbouring entry. Each record's XML ID
still states the intended name, which is what this restores.
| Code | Field | Before | After |
|---|---|---|---|
| `6` | `name@es` | Gastos generales | Gastos |
| `252.07` | `name@es` | `account_subgroup_hipotecas_por_pagar_a_largo_plazo_nacional` | Hipotecas por pagar a largo plazo nacional |
| `602` | `name`, `name@es` | Cost of sales / Costo de venta | Selling expenses / Gastos de venta |
| `613` | `name@es` | Amortización contable | Depreciación contable |
| `614` | `name` | Accounting depreciation | Accounting amortisation |
| `701.06` | `name`, `name@es` | Interest on foreign bank charges / Intereses a cargo bancario extranjero | Interest payable by national natural persons / Intereses a cargo de personas físicas nacional |
| `702` | `name@es` | Utilidad cambiaria | Productos financieros |
### Why it is not cosmetic
The electronic accounting Chart of Accounts XML takes the `Desc` attribute of
every `<Ctas>` element from the *account group name* — `cfdicoa.xml`
(`t-att-Desc="account.get('name')"`), fed by `trial_balance.py`
`_l10n_mx_get_coa_values()`. Any `es_*` database therefore declares:
```xml
<catalogocuentas:Ctas CodAgrup="702" NumCta="702" Desc="Utilidad cambiaria" Nivel="1" Natur="A"/>
```
whereas the SAT catalogue (Anexo 24) publishes `702` as *Productos financieros*,
with `702.01 Utilidad cambiaria` … `702.10 Otros productos financieros` beneath
it. `CodAgrup` comes from `code_prefix_start` and stays correct, so the file
still validates against the XSD, but the declared description does not match the
official nomenclature. Trial Balance and Pólizas are unaffected — neither
exports group names.
### Evidence
- `252.07` contains its own XML ID as the Spanish name.
- `602` duplicates `501.01`, yet its children are `Sueldos y Salarios`,
`Compensaciones`, `Tiempos extras`.
- `613` and `614` are swapped in one language each: `613`'s children are
depreciations, `614`'s are amortisations.
- `701.06` duplicates `701.05` in both languages; the correct name is symmetric
to `701.07` and to `702.06`.
- `6` is the only single-digit root group whose Spanish name does not match its
XML ID (`account_group_gastos`).
### Notes
Introduced in d782b8b92557; correct in 15.0, where the names lived in
`account.account.tag.csv`. Still present in 18.0, 19.0 and master, hence
targeting 17.0. Template data only — existing databases are unaffected until the
chart is (re)installed, and renaming a group moves no balance.
Forward-Port-Of: odoo/odoo#277426
Forward-Port-Of: odoo/odoo#278328
Forward-Port-Of: odoo/odoo#277891UPS return shipments now show the required commercial invoice in the order chatter, matching outbound international shipments. The fix also prevents UPS delivery rejections for US customers using ZIP+4 postal codes by formatting those postal codes correctly before sending them to UPS.
Original PR description
Issues ----- 1. Commercial invoice is not forwarded to the user for the return delivery. 2. US postal codes of format 12345-6789 cause the delivery to be rejected. ----- Steps to reproduce issue 1…
Issues ----- 1. Commercial invoice is not forwarded to the user for the return delivery. 2. US postal codes of format 12345-6789 cause the delivery to be rejected. ----- Steps to reproduce issue 1 ----- - Set up UPS with return labels - Create an INTL delivery & confirm > OUT delivery has a commercial invoice in chatter, but the return doesn't Cause ----- The OUT and return call are not made using the same function. The OUT call is made via `ups_rest_send_shipping` which explicitly extracts the commercial invoice from the UPS response https://github.com/odoo/enterprise/blob/1a7c8ac34348ebc1ebe2da4100bdaec57484056f/delivery_ups_rest/models/delivery_ups.py#L204-L205 We should adapt `ups_rest_get_return_label` to match. ----- Steps to reproduce issue 2 ----- - Set up UPS - Create an american customer with a 9 digit zip (eg 20500-0003) - Create an delivery to the customer & confirm > Error: Invalid sold to postal code. Valid length is 0 to 9 alphanumeric Cause ----- The zip code is transmitted as-is, so we should sanitise it beforehand. https://github.com/odoo/enterprise/blob/c8c2f13b7fd17e215044fc62774f2b4a378aaf8c/delivery_ups_rest/models/ups_request.py#L368 Doc: https://github.com/UPS-API/api-documentation/blob/69e8a3cee7f9d3bf80735ae329aed0d8be156f97/Shipping.yaml#L5410-L5420 ----- Ticket: opw-6422500 Forward-Port-Of: odoo/enterprise#127375
This update corrects tax configuration details for Hungary in Odoo's local accounting and electronic invoicing setup. It helps ensure Hungarian tax records and related electronic tax handling are aligned with the expected configuration, reducing the risk of incorrect tax setup for affected companies.
Original PR description
Adjusting incorrect tax configuration elements for Hungary. task-6397915 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#284432 Forward-Port-Of: odoo/odoo#282697
The salary configurator now handles cases where no company bike is configured. This prevents an error when users select the company bike option and lets the offer process continue smoothly.
Original PR description
Steps to Reproduce: - install l10n_be_hr_contract_salary module. - make sure that there is no model with vehicle type bike in fleet. - create an offer in recruitment. - open salary configurator. - click on company bike checkbox. Issue: - traceback occurs when enabling the company bike option without a configured bike. Reason: - the company bike depreciated cost value is empty when no bike is available, but the code tries to split it into bike options and vehicle ID resulting in a traceback. Solution: - Use the condition to check if the company bike depreciated cost is available before spliting the value. - Set the depreciated cost to 0 when no bike is selected. task-6468987 Forward-Port-Of: odoo/enterprise#127898
The ESG HR pay gap tests now use the correct contract wage value depending on the payroll setup. This prevents false test failures and helps keep employee reporting checks reliable across configurations.
Original PR description
Without `hr_payroll`, the contract wage is stored in `wage`. With `hr_payroll`, hourly employees use `hourly_wage` instead. This commit uses `_get_contract_wage_field()` so the test sets the correct field in both cases. [error-237750](https://runbot.odoo.com/odoo/error/237750) Forward-Port-Of: odoo/enterprise#127398
Receipt emails for paid self-service orders now include the generated receipt image as an attachment. This ensures customers receive a complete proof of purchase by email, while draft orders remain unaffected.
Original PR description
Before this commit: ======================== * Receipt emails were sent without attachments for both paid and draft orders. * `fullTicketImage` and `basicTicketImage` were hardcoded to `false`. * As a result, paid orders were also sent without a receipt attachment. After this commit: ====================== * Receipt emails for paid orders now include the generated receipt image. * `fullTicketImage` and `basicTicketImage` are correctly handled to generate and attach the requested receipt image. Task-5353350 Forward-Port-Of: odoo/odoo#283947 Forward-Port-Of: odoo/odoo#237688
The Point of Sale now automatically chooses a product option when it is the only available choice, except for multi-select options. This removes an unnecessary step for cashiers and helps products with simple variants be added to orders smoothly.
Original PR description
Before this commit: ----------- - When a product attribute had only one available value, it was not automatically selected for display types other than multi. After this commit: ------------ - Automatically select the attribute value when an attribute has a single available value and its display type is not multi, allowing the product to be added without any additional user interaction. Task-6327371 Forward-Port-Of: odoo/odoo#282350 Forward-Port-Of: odoo/odoo#272437
This fixes website redirects so newer search and AI crawlers can access default-language pages even when they send language preferences. It helps pages be inspected and indexed correctly while keeping normal visitor language behavior unchanged.
Original PR description
Modern crawlers now send an `Accept-Language` header (for example, `en-US,en;q=0.9`), whereas historically they did not. When that language differs from the website's default language,…
Modern crawlers now send an `Accept-Language` header (for example, `en-US,en;q=0.9`), whereas historically they did not. When that language differs from the website's default language, `ir.http._match()` issues a 303 redirect from `/page` to `/<lang>/page`. Since crawlers do not retain cookies, unrecognized agents are redirected on every request and never reach the default-language page. Customers reported that Google Search Console URL Inspection live tests only receive a redirect and that pages remain unindexed. Googlebot itself is not affected because it already matches the existing `bot` token. `_match()` already skips language redirects for recognized bots by serving the default-language page directly. Extend the `bots` user-agent list with modern crawler identifiers, each verified against vendor documentation: * `google-inspectiontool`: Search Console URL Inspection / Rich Results Test * `googleother`: Google generic crawler (`GoogleOther`, `GoogleOther-Image`, `GoogleOther-Video`) * `meta-external`: `meta-externalagent`, `meta-externalfetcher`, and `meta-externalads`, successors to the already-listed `facebookexternalhit` * `meta-webindexer`: Meta AI search indexer * `chatgpt-user`: OpenAI user-request fetcher (currently matched only through the `bot` substring in its info URL, which is fragile) * `claude-user`: Anthropic user-request fetcher * `perplexity-user`: Perplexity user-request fetcher The redirect behavior remains unchanged for human visitors. Localized pages continue to be crawlable through their own URLs (for example, `/fr/page`) via `hreflang` alternates. As a side effect, `link_tracker` and `mass_mailing_sms` no longer count clicks from these crawlers, and website visitor tracking skips them. task-6213245 Forward-Port-Of: odoo/odoo#275571
This fixes an error when filtering sales orders by information coming from linked project tasks, such as a task stage. Users can now apply these custom filters without the sales order search failing.
Original PR description
step to reproduce : 1. Create a related field on `sale.order`, for example: x_studio_production_stage = tasks_ids.stage_id.name 2. Use this field in a filter: [('x_studio_production_stage', 'ilike',…
step to reproduce :
1. Create a related field on `sale.order`, for example:
x_studio_production_stage = tasks_ids.stage_id.name
2. Use this field in a filter:
[('x_studio_production_stage', 'ilike', 'Dispatch')]
3. Applying the filter raises:
```python
Traceback (most recent call last):
File "/home/odoo/src/odoo/19.0/odoo/http.py", line 2329, in _serve_db
return service_model.retrying(serve_func, env=self.env)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/19.0/odoo/service/model.py", line 188, in retrying
result = func()
^^^^^^
File "/home/odoo/src/odoo/19.0/odoo/http.py", line 2384, in _serve_ir_http
response = self.dispatcher.dispatch(rule.endpoint, args)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/19.0/odoo/http.py", line 2599, in dispatch
result = self.request.registry['ir.http']._dispatch(endpoint)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/19.0/odoo/addons/base/models/ir_http.py", line 353, in _dispatch
result = endpoint(**request.params)
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/19.0/odoo/http.py", line 838, in route_wrapper
result = endpoint(self, *args, **params_ok)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/19.0/addons/web/controllers/dataset.py", line 32, in call_kw
return call_kw(request.env[model], method, args, kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/19.0/odoo/service/model.py", line 97, in call_kw
result = method(recs, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/19.0/addons/web/models/models.py", line 67, in web_search_read
records = self.search_fetch(domain, specification.keys(), offset=offset, limit=limit, order=order)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/19.0/odoo/orm/models.py", line 1408, in search_fetch
query = self._search(domain, offset=offset, limit=limit, order=order or self._order)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/19.0/odoo/orm/models.py", line 5366, in _search
domain = domain.optimize_full(self)
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/19.0/odoo/orm/domains.py", line 446, in optimize_full
return self._optimize(model, OptimizationLevel.FULL)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/19.0/odoo/orm/domains.py", line 460, in _optimize
previous, domain = domain, domain._optimize_step(model, next_level)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/19.0/odoo/orm/domains.py", line 654, in _optimize_step
children = self._flatten(child._optimize(model, level) for child in self.children)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/19.0/odoo/orm/domains.py", line 609, in _flatten
for child in children:
File "/home/odoo/src/odoo/19.0/odoo/orm/domains.py", line 654, in <genexpr>
children = self._flatten(child._optimize(model, level) for child in self.children)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/19.0/odoo/orm/domains.py", line 460, in _optimize
previous, domain = domain, domain._optimize_step(model, next_level)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/19.0/odoo/orm/domains.py", line 654, in _optimize_step
children = self._flatten(child._optimize(model, level) for child in self.children)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/19.0/odoo/orm/domains.py", line 609, in _flatten
for child in children:
File "/home/odoo/src/odoo/19.0/odoo/orm/domains.py", line 654, in <genexpr>
children = self._flatten(child._optimize(model, level) for child in self.children)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/19.0/odoo/orm/domains.py", line 460, in _optimize
previous, domain = domain, domain._optimize_step(model, next_level)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/19.0/odoo/orm/domains.py", line 962, in _optimize_step
domain = self._optimize_field_search_method(model)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/19.0/odoo/orm/domains.py", line 1008, in _optimize_field_search_method
computed_domain = field.determine_domain(model, operator, value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/19.0/odoo/orm/fields.py", line 1928, in determine_domain
return determine(self.search, records, operator, value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/19.0/odoo/orm/fields.py", line 81, in determine
return needle(*args)
^^^^^^^^^^^^^
File "/home/odoo/src/odoo/19.0/addons/sale_project/models/sale_order.py", line 76, in _search_tasks_ids
query = self.env['project.task']._search(task_domain)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/19.0/odoo/orm/models.py", line 5355, in _search
domain = Domain(domain)
^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/19.0/odoo/orm/domains.py", line 259, in __new__
raise ValueError(f"Domain() invalid item in domain: {item!r}")
ValueError: Domain() invalid item in domain: ('id', 'any!', [('id', 'any!', <odoo.tools.query.Query object at 0x7aca184f4170>)])
```
Cause:
When searching on the related field, [_search_related()](https://github.com/odoo/odoo/blob/19.0/odoo/orm/fields.py#L768) converts the related path into an `any!` domain:
('tasks_ids', 'any!',
[('stage_id', 'any!', [('name', 'ilike', 'Dispatch')])]
)
During [Domain.optimize_full()](https://github.com/odoo/odoo/blob/19.0/odoo/orm/domains.py?utm_source=chatgpt.com#L436), [_optimize_field_search_method()](https://github.com/odoo/odoo/blob/19.0/odoo/orm/domains.py?utm_source=chatgpt.com#L1008) calls the field's search method, which invokes `_search_tasks_ids()` with `operator='any!'` and the related domain as `value`.
The existing [_search_tasks_ids()](https://github.com/odoo/odoo/blob/57c7c9938725d392a6f2cd6c89a861d2a8385c44/addons/sale_project/models/sale_order.py#L76) expects a normal search value and therefore generates an invalid nested domain.
Fix :
`_search_tasks_ids()` to directly pass the domain to `project.task._search()` when the operator is `any` or `any!`.
upg - 4584778
opw - 6475804
[here]: https://github.com/odoo/odoo/blob/19.0/odoo/orm/fields.py#L768
[here]: https://github.com/odoo/odoo/blob/19.0/odoo/orm/models.py#L5366
[here]: https://github.com/odoo/odoo/blob/19.0/odoo/orm/domains.py?utm_source=chatgpt.com#L436
[here]: https://github.com/odoo/odoo/blob/57c7c9938725d392a6f2cd6c89a861d2a8385c44/addons/sale_project/models/sale_order.py#L76
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#284101This fix ensures users granted editor rights on a Documents folder can update that folder's sharing settings as intended. It removes a permissions blocker that prevented legitimate editors from changing internal user access, improving consistency in folder management.
Original PR description
1. Create a non-company root folder 2. Edit rights as follows: * add Marc Demo as editor member * access for internal users and link to None 3. As Marc Demo, try updating Internal users access to "editor" ⮕ You can't. Task-6410610 Forward-Port-Of: odoo/enterprise#129054 Forward-Port-Of: odoo/enterprise#125191
Archived projects no longer appear as selectable recent projects in the timesheet timer. This prevents users from accidentally logging new time on projects that have been closed or archived, keeping timesheet entries aligned with active work.
Original PR description
Steps to reproduce: ---------------------------------- 1. Install the `timesheet_grid` module. 2. Create a project and add any timesheet to it. 3. Archive the project. 5. From the systray timer,…
Steps to reproduce:
----------------------------------
1. Install the `timesheet_grid` module.
2. Create a project and add any timesheet to it.
3. Archive the project.
5. From the systray timer, click on the Project field.
Observation:
----------------------------------
The archived project is visible in the dropdown.
Issue:
----------------------------------
In Odoo, standard search views and `name_search` calls on `project.project` automatically respect `active_test=True`. When you open the timer, the frontend passes `{'timesheet_timer_search': True}` in the context to `name_search` with an empty query string. `name_search` overrides standard searching to retrieve recently used projects first by querying `account.analytic.line` via `_get_recently_used_records ('project_id', ...)`. `account.analytic.line` stores past timesheet logs. Even after a project is archived, historical timesheet records for that project still exist in `account.analytic.line`. Because `_get_recently_used_records` runs a `_read_group` query on `account.analytic.line` (which has no active field of its own), it fetched the `project_id` from historical timesheet entries without checking if the referenced project was active.
Solution:
----------------------------------
In `name_search`, explicitly append `[('active', '=', True)]` to the `project_domain` used when querying `_get_recently_used_records`. Standard form/list views using `_domain_project_id` already benefit from Odoo's default ORM `active_test=True` mechanism during standard `project.project` searches.
Note:
----------------------------------
Another solution was to add `active = true` in `getTimesheetTimerFieldInfo` https://github.com/odoo/enterprise/blob/22eb84cdc94ba334d42bad32fb491d35c8147c94/timesheet_grid/static/src/services/static_timesheet_timer_service.js#L322-L328
Fixing it in Python ensures that any call passing `timesheet_timer_search` in context (e.g. mobile widgets, custom RPCs, or python wizards) will benefit from the fix, rather than only patching a single OWL JS service.
opw-6445528
Forward-Port-Of: odoo/enterprise#127374Reauthorizing a Shopee shop now correctly links it to the selected Shopee account when different API credentials are used. This prevents shops from staying connected to the wrong account after reauthorization, reducing setup errors for sales integrations.
Original PR description
Context: when a user re-authenticate a shop, they might use different shopee.account (API key). Currently Odoo will not change the shopee.account when they re-auth with another shopee.account. Enable a shopee.shop switches to another shopee.account when we run `create_or_update_shop` function. Forward-Port-Of: odoo/enterprise#128837 Forward-Port-Of: odoo/enterprise#92446
Contacts now use the country set on the partner record to identify the issuing country for tax numbers, instead of guessing from the first two characters. This prevents valid tax IDs, such as Mexican RFC numbers that happen to start with another country code, from being rejected incorrectly.
Original PR description
**Issue**: If a partner has a tax number that begins with a different country’s code (for instance, a Mexican RFC number that begins with “RO”, matching Romania), when a user tries to add the tax…
**Issue**: If a partner has a tax number that begins with a different country’s code (for instance, a Mexican RFC number that begins with “RO”, matching Romania), when a user tries to add the tax number to the partner, there will be a validation error.
**Steps to reproduce** (on fresh database with Contacts app and l10n_mx module installed):
1. Make a new contact.
2. Give the contact a Mexican address.
3. Give the contact the RFC number (or `vat` field): ROS561231GR8.
4. Try to save this change. Observe the validation error.
**Explanation**:
The `get_all_identifiers` method uses the first two characters of `partner.vat` as a heuristic to detect the issuing country, since many VAT formats start with a country code (e.g. RO1234567897). This prefix was used unconditionally whenever it matched an item from `get_tin_metadata_of_country`. without checking whether the VAT actually belongs to that country. Some countries' identifier formats begin with letters which are not country codes. In Mexico, for instance, RFC numbers start with letters derived from the partner's name, so a partner named “Sofia Rodriguez” would get an RFC starting with “RO”. Therefore, this heuristic can produce false-positive matches against unrelated countries.
**Solution**:
We no longer use a partner's vat number to detect the issuing country. Instead, we use the partner's `country_code` field as the issuing country.
opw-6471006
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr