Daily updates from Odoo
Monday, July 20, 2026
45 changes
3 changes
Resolved issues and error corrections
Fixes an issue where Mexican payroll CFDI documents failed for employers registered as individuals under fiscal regime 621. The system now uses the RFC length to identify individual employers and includes the required CURP in the XML, allowing payslip CFDI generation to complete successfully.
Original PR description
Issue: ---------------------------------------- There is a fiscal regime where the company is actually an individual but is authorized to generate documents. Steps to reproduce:…
Issue: ---------------------------------------- There is a fiscal regime where the company is actually an individual but is authorized to generate documents. Steps to reproduce: ---------------------------------------- - Install "l10n_mx_hr_payroll_account_edi" - Change the current company fiscal regime to '621' - Add an VAT of length 13 to the current company - Add a CURP number on the current company - In Payroll generate a payslip, validate it - Post the Journal entry - On the payslip, click "Generate CFDI" - An error is returned, saying the Emisor:Curp applies to individuals Cause: ---------------------------------------- An RFC of length 13 means that the sender is an individual. It's intended with the fiscal regime '621'. We add the curp number in the CFDI XML only when `self.company_id.partner_id.is_company` is `False`. Since saas-19.1, `is_company` is computed to be truely if a VAT is present. So as soon as the VAT is entered, the CURP number is absent from the XML. Solution: ---------------------------------------- We change the condition to add the CURP number in the XML: A VAT number of length 13 means the contact is an individual (12 for companies). This is what is used to validate the XML: if the vat is of length 13, then the curp number should be present. opw-6351558 Forward-Port-Of: odoo/enterprise#124728 Forward-Port-Of: odoo/enterprise#124027
The Timesheets shortcut now appears when a user can create timesheets in any selected company, not just the current one. This prevents eligible employees from missing the quick access option when working across multiple companies.
Original PR description
Steps to reproduce: - install Timesheets - create an employee for a user in company B - switch to company A (where the user has no employee) - the systray icon is hidden even though the user can…
Steps to reproduce: - install Timesheets - create an employee for a user in company B - switch to company A (where the user has no employee) - the systray icon is hidden even though the user can create timesheets in company B via the company selector Current behavior: the systray only checks the current company for a valid employee, ignoring other selected companies. Expected behavior: the systray should be visible whenever the user can create timesheets i.e. when they have an active employee in any of the selected companies. Issue: the check used a stored boolean on `res.partner` that has no company scope and becomes stale when an employee is archived (the stored dependency does not re-fire). the timesheet creation logic checks all selected companies for active employees, but the systray did not mirror that. Fix: use `employee_ids` a `One2many` that checks all selected companies and excludes archived employees, matching the timesheet creation logic exactly. task-6330539 Forward-Port-Of: odoo/enterprise#124761 Forward-Port-Of: odoo/enterprise#121730
Belgian payroll now correctly treats employees marked as judicially separated when calculating withholding taxes and special social contributions. This prevents affected payslips from missing required deductions and producing incorrect net pay amounts.
Original PR description
**Steps to Reproduce:** 1. Create a new employee. 2. Set their marital status to "Judicially Separated". 3. Generate a payslip. **Reason:** - The "Judicially Separated" marital status was not explicitly included in the conditions for calculating withholding taxes or special social contributions. As a result, employees with this status bypassed the calculations entirely, receiving a rate of 0 for both and a significantly larger reduction on master which also appears to be incorrect. **Solution:** - Included the 'separated' status in isolated tax and CSSS logic. Task-6321245 Forward-Port-Of: odoo/enterprise#122749 Forward-Port-Of: odoo/enterprise#121457
2 changes
Resolved issues and error corrections
Fixes an issue where clicking the AI icon could fail if the default AI prompt had been duplicated. The system now selects one matching AI composer record, preventing an error and keeping the AI menu accessible for users.
Original PR description
Currently an error is generated when the user clicks on the AI icon as per the below steps: - Install `ai_app` - Go to `AI` > Configuration > Default Prompts - Duplicate `Ask AI` record…
Currently an error is generated when the user clicks on the AI icon as per the below steps: - Install `ai_app` - Go to `AI` > Configuration > Default Prompts - Duplicate `Ask AI` record (`ai.composer`) - An error occurs on clicking the `AI` icon on the navbar menu systray Error: `ValueError: Expected singleton: ai.composer(4, 14)` This issue was introduced during the refactoring in [1]. In the previous implementation, the AI Composer search was executed with `limit=1` (see [2]), ensuring that only a single composer record was returned. After the changes in [1], the limit=1 constraint was removed (see [3]), causing the search to return multiple composer records. As a result, the code now encounters an error when it expects a single record. This commit fixes the issue by adding `limit=1` to the AI Composer search, ensuring that only a single record is returned. [1]: https://github.com/odoo/enterprise/commit/800208fdc485b9e37f77648a01100b8eff118490 [2]: https://github.com/odoo/enterprise/blob/188dcc5078be7c7fee1a52f86505144d3b6309cf/ai/models/ai_composer.py#L71-L83 [3]: https://github.com/odoo/enterprise/blob/2972805fd1a4daacd7f1c47d5e3c7672e2aec61c/ai/models/ai_composer.py#L74-L77 Sentry-7549108967
Belgian payroll now correctly treats employees marked as judicially separated when calculating withholding tax and special social security contributions. This prevents affected payslips from incorrectly applying a zero rate or excessive reductions, improving payroll accuracy and compliance.
Original PR description
**Steps to Reproduce:** 1. Create a new employee. 2. Set their marital status to "Judicially Separated". 3. Generate a payslip. **Reason:** - The "Judicially Separated" marital status was not explicitly included in the conditions for calculating withholding taxes or special social contributions. As a result, employees with this status bypassed the calculations entirely, receiving a rate of 0 for both and a significantly larger reduction on master which also appears to be incorrect. **Solution:** - Included the 'separated' status in isolated tax and CSSS logic. Task-6321245 Forward-Port-Of: odoo/enterprise#122263 Forward-Port-Of: odoo/enterprise#121457
5 changes
Enhancements to existing features
The timesheet assistant now supports keyboard controls for moving through and selecting suggestions. This improves accessibility and helps users manage timesheet entries more efficiently without relying on a mouse.
Original PR description
Implement full keyboard controls for managing timesheet suggestions to improve accessibility and user efficiency. This adds support for the following interactions: - ArrowUp / ArrowDown to navigate focus through rows - Space to select/deselect the focused item (and set the selection anchor) - Shift + Arrows to select continuous ranges of suggestions task: 6267620
Resolved issues and error corrections
WhatsApp message lists no longer fail when a related calendar appointment or contact has been deleted. This keeps reminder history readable and prevents one broken reminder from stopping other queued WhatsApp messages from being sent.
Original PR description
Root cause: WhatsApp reminders are logged as mail.message records on calendar.attendee, which is not a mail.thread. Deleting a calendar event or a partner removes the attendees through the database…
Root cause: WhatsApp reminders are logged as mail.message records on calendar.attendee, which is not a mail.thread. Deleting a calendar event or a partner removes the attendees through the database cascades on calendar.attendee.event_id and calendar.attendee.partner_id, which do not call calendar.attendee.unlink, so each reminder keeps a model and res_id pointing to an attendee that no longer exists. mail.message access filters the related documents through exists() since https://github.com/odoo/odoo/commit/617d4a32409b730e0fb7d351553e4c6e10bf6c5f, so a reminder whose attendee is gone is no longer readable by anyone but its author or recipient. whatsapp.message.body reads mail.message.body without sudo, so opening WhatsApp > Messages raises an access error on the orphaned reminders, and the queue retry cron hits the same error and stops sending the rest of the queue. Fix: Read the related body with sudo. Orphaned messages are not specific to whatsapp, any model that is not a mail.thread keeps its messages after a record is deleted, so cleaning them up is a mail framework concern rather than a whatsapp one. One message the user cannot read should not block the whole message list. Who can read the body stays controlled by the access rights of whatsapp.message itself. Steps to reproduce: 1. Install WhatsApp and configure an account 2. Create an Appointment Type with a WhatsApp reminder 3. Book an appointment to create the calendar event 4. Settings > Technical > Automation > Scheduled Actions > Calendar: Event Reminder > Run Manually so the reminder is sent 5. Open the calendar event and delete it with the trash icon, not Cancel 6. Open WhatsApp > Messages => Access error, the user has no access to the mail.message Ticket [link](https://www.odoo.com/odoo/project.task/6297099) opw-6297099
QR-IBAN payment references are now sanitized before validation when generating ISO 20022 payment files. This helps prevent Swiss bank rejections caused by unsupported characters in payment references while keeping valid QR references intact.
Original PR description
**Steps to reproduce:** * Configure a Swiss company with the Swiss accounting localization. * Install `account_sepa`. * Configure a bank journal with a valid Swiss **QR-IBAN**. * Create a vendor with…
**Steps to reproduce:** * Configure a Swiss company with the Swiss accounting localization. * Install `account_sepa`. * Configure a bank journal with a valid Swiss **QR-IBAN**. * Create a vendor with a valid Swiss bank account. * Under **Accounting -> Configuration -> Settings** enable QR Payments. * Go to the vendor and enter the correct QR-IBAN number under the Accounting Section. * Navigate to **Accounting -> Configuration -> Journals** and in the **bank** journal, add the account number of QR-IBAN. * Create and post a vendor payment using the bank journal. * Set the payment reference (`ref`) to contain the `°` character. * Add the payment to a batch payment and generate the ISO 20022 payment file. * Inspect the generated XML. **Observed Behaviour:** The `°` character is preserved in the payment reference for QR-IBAN payments, producing an ISO 20022 file that may be rejected by Swiss banks because it does not comply with the EPC minimum character set. **Cause:** The QR-IBAN branch validates the structured reference before sanitizing it, allowing unsupported characters to remain in the generated XML. **Fix:** Sanitize the payment reference before validating it as a QR structured reference, ensuring invalid characters are removed while preserving valid QR references. opw - 6350931 Forward-Port-Of: odoo/enterprise#124299 Forward-Port-Of: odoo/enterprise#123267
This fix keeps existing tax returns aligned when their allowed workflow steps are changed, such as during upgrades. It prevents errors when viewing return lists and moves returns out of statuses that are no longer valid.
Original PR description
To reproduce the issue: 1) Create a company in Belgium 2) Instantiate its returns and review, submit and pay one of the VAT returns 3) Change the states_worklfow of the VAT return so that it only…
To reproduce the issue: 1) Create a company in Belgium 2) Instantiate its returns and review, submit and pay one of the VAT returns 3) Change the states_worklfow of the VAT return so that it only accepts "review" and "submit" stages, not "paid" anymore 4) Go to the list of returns, remove the TODO filter => traceback The problem is here that the existing returns don't recompute their state when the workflow of the type is modified. In some cases, this is fine, but it others, it's annoying. In our example, the terminal state changed, so all the returns in that terminal stage should change their state to the new terminal one. "paid" is not an accepted value anymore, it should become "submitted". Moreover, when the workflow is changed, the selection field actually containing the state must also change. As it is, it seems to work because "state" of account.return is stored, but the value it's based on (the workflow field) won't be consistent with it. It's not annoying now, but those inconsistencies could become a big source of trouble in the future (we know that from experience ... I'm looking at you, version 8 ! è-é). This issue typically happens at upgrade. We had cases in FR and AE already. We solve that by a generic override of the write to sort things out when such change needs to happen. An upgrade PR will also be done to adapt the script so that we eventually solve the inconsistencies on dbs that have already migrated to 19.0. Forward-Port-Of: odoo/enterprise#124144
Invoices marked as excluded from follow-up will no longer be attached to reminder emails or included in printed follow-up letters. This prevents customers from receiving payment reminders for invoices the business intentionally left out.
Original PR description
Steps to reproduce: 1. Install Accounting and create an invoice for a customer which has a due date in the past 2. Make sure the payment term for the invoice is "Immediate Payment" and Send the…
Steps to reproduce: 1. Install Accounting and create an invoice for a customer which has a due date in the past 2. Make sure the payment term for the invoice is "Immediate Payment" and Send the invoice. 3. Open the contact form and click on the Customer Statement smart button 4. Exclude the invoice using the 'No Follow-Up' toggle 5. In the Accounting tab in the contact form, click on send 6. Open the internal link of the Content Template, go to the options tab and select 'Print Follow-up Letter' in Dynamic Reports 7. Save the configuration and send the email Issue: Excluded invoices still appeared as PDF attachments in the follow-up email and were merged into the printed follow-up letter PDF. Why this happens: Both `default_get` in `account_followup.manual_reminder` and `_get_invoices_to_print` in `res.partner` traversed `unreconciled_aml_ids` without filtering out lines where `no_followup = True`, so excluded invoices were included regardless. opw-6310602 Forward-Port-Of: odoo/enterprise#124750 Forward-Port-Of: odoo/enterprise#122249
6 changes
Resolved issues and error corrections
Argentine electronic invoices for final consumers now validate the customer's identification type before processing. This prevents a technical error during invoice confirmation and instead shows a clear warning when the ID type is not accepted.
Original PR description
After applying the community fix, a new issue appears in enterprise. **Steps to reproduce:** - Install the `l10n_ar_edi` module. - Go to Customers and create a new customer with: - Country: Argentina…
After applying the community fix, a new issue appears in enterprise. **Steps to reproduce:** - Install the `l10n_ar_edi` module. - Go to Customers and create a new customer with: - Country: Argentina - Identification Type: `Passport` - Identification Number: `1234567890a` - AFIP Responsibility Type: `Consumidor Final` - Create a new invoice for this customer with: - Journal: `Electronic Invoice (FE)` - Document Type: `(6) INVOICES B` - Try to confirm the invoice. **Error:** ValueError: invalid literal for int() with base 10: '1234567890a' **Expected behaviour:** For Argentinean contacts with AFIP Responsibility Type `Consumidor Final`, the identification type must be `DNI` or `CUIL/CUIT`. If another identification type is used, the user should receive a proper warning instead of a traceback. (see [1]) **Root Cause:** After the community fix, `_get_id_number_sanitize()` can return an alphanumeric value, which is later converted with `int()` at [2] and [3], causing an error. **Fix:** This commit adds an explicit validation for Argentinean contacts with AFIP Responsibility Type Consumidor Final before converting the identification number to an integer, ensuring the user receives the proper warning message. [1]: https://www.odoo.com/mail/message/1118392033 [2]: https://github.com/odoo/enterprise/blob/064812dd9f1074f287fee677ba35ea4aeef3ab3f/l10n_ar_edi/models/account_move.py#L780-L798 [3]: https://github.com/odoo/enterprise/blob/064812dd9f1074f287fee677ba35ea4aeef3ab3f/l10n_ar_edi/models/account_move.py#L927-L931 Related community PR: https://github.com/odoo/odoo/pull/272651 opw-6333998 Forward-Port-Of: odoo/enterprise#123729
Envia shipping rate requests now include insurance in the format expected by the carrier API. This ensures quotes reflect the selected insurance amount, so customers and sales teams see more accurate delivery costs.
Original PR description
Issue ----- Insurance request is not reflected on the rating given by Envia. Steps to reproduce ----- - Create a MX company - Set up Envia - Fedex Nacional Economico (ground) - Create a MX client - Create a product (with some weight) - Create a SO - Add the product - Add envia delivery and get a rate - Update Envia delivery method - Set insurance to 50% - Go back to the SO - Add envia delivery and get a rate > The rate is the same both times Cause ----- We are passing the insurance value as a `insurance` field on the shipment, which is not what the API expects. We should instead pass it in `additionalServices` as shown in the example of https://docs.envia.com/docs/additional-services#how-to-add-services-to-a-shipment Follow up of 7258e4a ----- Ticket: opw-5254952 Forward-Port-Of: odoo/enterprise#124700
Barcode transfers now apply a default limit when loading reusable packages, preventing very large package lists from slowing down the screen. This reduces long wait times for warehouses with tens of thousands of packages and makes transfer opening more reliable.
Original PR description
# How to reproduce - Have a lot of reusable & locationless packages (e.g. > 10 000) - Go to any transfer via the barcode application # The issue There is a very long loading time, even in local…
# How to reproduce - Have a lot of reusable & locationless packages (e.g. > 10 000) - Go to any transfer via the barcode application # The issue There is a very long loading time, even in local testing. The client of the tickets experiences loadings up to 120 seconds with 50k packages # Cause When opening a transfer, we load barcode data by doing an API call to `_get_stock_barcode_data` : https://github.com/odoo/enterprise/blob/fe058ef501767b7ed9758fc9264f664b32c6bae8/stock_barcode/models/stock_picking.py#L85 During this we preload a lot of records, notably packages : https://github.com/odoo/enterprise/blob/fe058ef501767b7ed9758fc9264f664b32c6bae8/stock_barcode/models/stock_picking.py#L128 The issue is that in the fields we read for the packages, two of them (`location_dest_id` & `contained_quant_ids`) have a `_read_group` in their compute (or in the compute of one of the fields they depend on) : https://github.com/odoo/odoo/blob/625e6bcbd66c45ea2f699df14e2ea12e2e28a893/addons/stock/models/stock_package.py#L65 https://github.com/odoo/odoo/blob/625e6bcbd66c45ea2f699df14e2ea12e2e28a893/addons/stock/models/stock_package.py#L146 Fortunately, this does not mean that we make a query for every records. Instead, in Odoo, we fetch records in batch of 1000. So, for the case of the client, every time he loads the database, the backend does 50 000 / 1000 x 2 = 100 queries, which hinders performance a lot A [PERF] commit was done to limit the number of packages that are fetched base on a config parameter. The problem is that this parameter does not have a default value, so clients still end up with the problem. [PERF]: https://github.com/odoo/enterprise/commit/efe18bc1ea479270e42846986d7ed449b0865617 # Proposed Solution Add a default value for that config parameter. The exact value is up to discussion opw-6200730 Forward-Port-Of: odoo/enterprise#123696
This fixes an issue where tax returns could keep an outdated status after the allowed workflow steps were changed, which could cause errors when viewing return lists. Existing returns are now adjusted to valid statuses when workflows change, helping upgrades and country-specific tax processes run more reliably.
Original PR description
To reproduce the issue: 1) Create a company in Belgium 2) Instantiate its returns and review, submit and pay one of the VAT returns 3) Change the states_worklfow of the VAT return so that it only…
To reproduce the issue: 1) Create a company in Belgium 2) Instantiate its returns and review, submit and pay one of the VAT returns 3) Change the states_worklfow of the VAT return so that it only accepts "review" and "submit" stages, not "paid" anymore 4) Go to the list of returns, remove the TODO filter => traceback The problem is here that the existing returns don't recompute their state when the workflow of the type is modified. In some cases, this is fine, but it others, it's annoying. In our example, the terminal state changed, so all the returns in that terminal stage should change their state to the new terminal one. "paid" is not an accepted value anymore, it should become "submitted". Moreover, when the workflow is changed, the selection field actually containing the state must also change. As it is, it seems to work because "state" of account.return is stored, but the value it's based on (the workflow field) won't be consistent with it. It's not annoying now, but those inconsistencies could become a big source of trouble in the future (we know that from experience ... I'm looking at you, version 8 ! è-é). This issue typically happens at upgrade. We had cases in FR and AE already. We solve that by a generic override of the write to sort things out when such change needs to happen. An upgrade PR will also be done to adapt the script so that we eventually solve the inconsistencies on dbs that have already migrated to 19.0. Forward-Port-Of: odoo/enterprise#124144
Payment references for Swiss QR-IBAN vendor payments are now sanitized before generating ISO 20022 payment files. This helps prevent bank rejections caused by unsupported characters while keeping valid QR payment references intact.
Original PR description
**Steps to reproduce:** * Configure a Swiss company with the Swiss accounting localization. * Install `account_sepa`. * Configure a bank journal with a valid Swiss **QR-IBAN**. * Create a vendor with…
**Steps to reproduce:** * Configure a Swiss company with the Swiss accounting localization. * Install `account_sepa`. * Configure a bank journal with a valid Swiss **QR-IBAN**. * Create a vendor with a valid Swiss bank account. * Under **Accounting -> Configuration -> Settings** enable QR Payments. * Go to the vendor and enter the correct QR-IBAN number under the Accounting Section. * Navigate to **Accounting -> Configuration -> Journals** and in the **bank** journal, add the account number of QR-IBAN. * Create and post a vendor payment using the bank journal. * Set the payment reference (`ref`) to contain the `°` character. * Add the payment to a batch payment and generate the ISO 20022 payment file. * Inspect the generated XML. **Observed Behaviour:** The `°` character is preserved in the payment reference for QR-IBAN payments, producing an ISO 20022 file that may be rejected by Swiss banks because it does not comply with the EPC minimum character set. **Cause:** The QR-IBAN branch validates the structured reference before sanitizing it, allowing unsupported characters to remain in the generated XML. **Fix:** Sanitize the payment reference before validating it as a QR structured reference, ensuring invalid characters are removed while preserving valid QR references. opw - 6350931 Forward-Port-Of: odoo/enterprise#124299 Forward-Port-Of: odoo/enterprise#123267
Employee appraisals now use the template assigned to the employee's department instead of keeping a generic default. This helps HR teams apply the right appraisal questions and process for each department without manual correction.
Original PR description
**Problem:** When a template is restricted to a department, appraisals for employees of that department do not use it — a generic (global) template is used instead. **Steps to reproduce:** 1. Create…
**Problem:** When a template is restricted to a department, appraisals for employees of that department do not use it — a generic (global) template is used instead. **Steps to reproduce:** 1. Create an appraisal template and restrict it to a department. 2. Make sure at least one global (no department) template also exists. 3. Open a new appraisal and select an employee of that department. 4. Observe the appraisal template stays on the global one. **Current behavior:** The global template that was auto-selected before an employee was chosen is kept, ignoring the department-restricted template. **Expected behavior:** The template restricted to the employee's department is selected. **Cause of the issue:** On a new appraisal the template is computed before an employee (and therefore a department) is set, so a global template is pre-selected. When the employee is then chosen, `_compute_appraisal_template` reruns but the `appraisal.appraisal_template_id or ...` clause short-circuits on the already set global template and never falls through to the department's template. **Fix:** Evaluating the department's template before the existing value ensures a department-restricted template takes precedence over a template that was only auto-selected as a default. The existing value is still preserved when the department has no template of its own, so deliberate choices are untouched. opw-6293936 Forward-Port-Of: odoo/enterprise#121377
2 changes
Resolved issues and error corrections
Argentine electronic invoices now validate that final consumer customers use an accepted identification type before invoice confirmation. Instead of a technical crash when a passport or other unsupported ID contains letters, users receive a clear warning so they can correct the customer record.
Original PR description
After applying the community fix, a new issue appears in enterprise. **Steps to reproduce:** - Install the `l10n_ar_edi` module. - Go to Customers and create a new customer with: - Country: Argentina…
After applying the community fix, a new issue appears in enterprise. **Steps to reproduce:** - Install the `l10n_ar_edi` module. - Go to Customers and create a new customer with: - Country: Argentina - Identification Type: `Passport` - Identification Number: `1234567890a` - AFIP Responsibility Type: `Consumidor Final` - Create a new invoice for this customer with: - Journal: `Electronic Invoice (FE)` - Document Type: `(6) INVOICES B` - Try to confirm the invoice. **Error:** ValueError: invalid literal for int() with base 10: '1234567890a' **Expected behaviour:** For Argentinean contacts with AFIP Responsibility Type `Consumidor Final`, the identification type must be `DNI` or `CUIL/CUIT`. If another identification type is used, the user should receive a proper warning instead of a traceback. (see [1]) **Root Cause:** After the community fix, `_get_id_number_sanitize()` can return an alphanumeric value, which is later converted with `int()` at [2] and [3], causing an error. **Fix:** This commit adds an explicit validation for Argentinean contacts with AFIP Responsibility Type Consumidor Final before converting the identification number to an integer, ensuring the user receives the proper warning message. [1]: https://www.odoo.com/mail/message/1118392033 [2]: https://github.com/odoo/enterprise/blob/064812dd9f1074f287fee677ba35ea4aeef3ab3f/l10n_ar_edi/models/account_move.py#L780-L798 [3]: https://github.com/odoo/enterprise/blob/064812dd9f1074f287fee677ba35ea4aeef3ab3f/l10n_ar_edi/models/account_move.py#L927-L931 Related community PR: https://github.com/odoo/odoo/pull/272651 opw-6333998 Forward-Port-Of: odoo/enterprise#123729
Swiss QR-IBAN payment references are now cleaned before payment files are generated. This prevents unsupported characters from causing ISO 20022 payment files to be rejected by Swiss banks.
Original PR description
**Steps to reproduce:** * Configure a Swiss company with the Swiss accounting localization. * Install `account_sepa`. * Configure a bank journal with a valid Swiss **QR-IBAN**. * Create a vendor with…
**Steps to reproduce:** * Configure a Swiss company with the Swiss accounting localization. * Install `account_sepa`. * Configure a bank journal with a valid Swiss **QR-IBAN**. * Create a vendor with a valid Swiss bank account. * Under **Accounting -> Configuration -> Settings** enable QR Payments. * Go to the vendor and enter the correct QR-IBAN number under the Accounting Section. * Navigate to **Accounting -> Configuration -> Journals** and in the **bank** journal, add the account number of QR-IBAN. * Create and post a vendor payment using the bank journal. * Set the payment reference (`ref`) to contain the `°` character. * Add the payment to a batch payment and generate the ISO 20022 payment file. * Inspect the generated XML. **Observed Behaviour:** The `°` character is preserved in the payment reference for QR-IBAN payments, producing an ISO 20022 file that may be rejected by Swiss banks because it does not comply with the EPC minimum character set. **Cause:** The QR-IBAN branch validates the structured reference before sanitizing it, allowing unsupported characters to remain in the generated XML. **Fix:** Sanitize the payment reference before validating it as a QR structured reference, ensuring invalid characters are removed while preserving valid QR references. opw - 6350931 Forward-Port-Of: odoo/enterprise#124299 Forward-Port-Of: odoo/enterprise#123267
14 changes
New functionality added to Odoo
Businesses in the Philippines can now send BIR 2306/2307 withholding tax certificates directly to vendors from vendor bills. The feature supports both individual and batch emailing, helping teams save time and streamline tax compliance communication.
Original PR description
Add a mechanism to email the BIR 2306/2307 withholding tax certificate to the vendor from a bill, both individually and in batch. task-6298456
Adds an Argentina-specific Daily Book XLSX export so businesses can summarize journal entries by ledger instead of reporting every invoice individually. This helps reduce reporting volume and related page-based costs, while allowing users to configure the starting entry number and select relevant ledgers.
Original PR description
Purpose: Implement the daily book export for Argentina which allows users to summarize and report journal entries of the same nature, such as all sales, to reduce reporting every single invoice which…
Purpose: Implement the daily book export for Argentina which allows users to summarize and report journal entries of the same nature, such as all sales, to reduce reporting every single invoice which incurs a cost per page. Users can use the multi-ledger system to summarize the journal entries. Ledgers can be created and set on the journal. Journals of the same nature can share the same ledger. Then, the user can view and select which ledgers they want to export on the general ledger and then export. By default, all ledgers will be selected, until the user deselects ledgers. A "Daily Book XLSX (AR)" button was added and can be accessed in the dropdown of the cog menu. Upon click, a wizard will pop up and it will display the starting entry number for the report. By default, it will display 1 if there's no history or the last entry number (that we store) + 1. The user can input their own number. Once the starting number is set, the XLSX file for the daily book will export. task-5244083
Enhancements to existing features
Online payment initiation now includes the payer's bank account information and account holder name when sending payment requests. This helps meet requirements from providers like Powens and banks that need these details to process payments reliably.
Original PR description
With Powens, some banks requires the payer's bank information such as bank account number and bank holder name. This commit adds those informations to the odoofin route to be sent to the payment providers. task-6373634 Forward-Port-Of: odoo/enterprise#124930 Forward-Port-Of: odoo/enterprise#124088
Users can now delete attachments or add them to Documents directly while previewing a file. This saves time by avoiding the need to return to the chatter to perform common document actions.
Original PR description
Users can currently delete attachments or add them to Documents from the chatter. However, when opening an attachment in the file viewer, these actions are not available. This commit adds Delete and Add to Documents buttons in the file viewer, allowing users to perform the same actions without leaving the viewer. Related Community PR: https://github.com/odoo/odoo/pull/247924 <img width="972" height="362" alt="image" src="https://github.com/user-attachments/assets/51581ef5-5855-4cc4-b850-12b7a8e1af5e" /> Task-[5408185](https://www.odoo.com/odoo/project/1519/tasks/5408185)
Resolved issues and error corrections
This fixes balance receipt printing for SIX payment terminals after a prior change caused the receipt template to be loaded incorrectly. The receipt layout is now provided as its own template file, restoring the expected balance receipt behavior after the module is upgraded.
Original PR description
odoo/enterprise#104683 broke the balance receipt for six terminals trying to load an html template in a template name param. We fix this by extracting the template in its own file. Note: clients will have to upgrade the module in order to make the template available. task-6391500 Forward-Port-Of: odoo/enterprise#124521
Studio approval rules now handle restricted fields without causing an access error for users who do not have permission to see those fields. This prevents sales order confirmations and similar actions from being unexpectedly blocked while keeping the approval logic in place.
Original PR description
Issue: A studio.approval.rule.domain includes a related field that calls an access rights group that the user who used the action isn't apart of, Is blocked by the filtered_domain. To Replicate: 1) Install studio, sale, Accounting and make sure "account_followup" is installed 2) create a related field on the sales.order form related to "customer -> follow up status" 3) Save 4) Create a "Studio Approval Rule" (studio.approval.rule) with a domain using the new related studio field -> method : "action_confirm" -> approver:admin 5) create a test user with no accounting access rights 6) in an incognito browser try and create a sales order, and then confirm it. it will throw the access rights error Fix: add a sudo to the filtered_domain opw-6316069 Forward-Port-Of: odoo/enterprise#124722 Forward-Port-Of: odoo/enterprise#121856
The POS now loads only the Kenya e-invoicing code records that are actually used by products, instead of fetching entire code lists. This reduces unnecessary data loading and can improve POS startup performance for businesses using the Kenya OSCU localization.
Original PR description
Before `product.unspsc.code` and `l10n_ke_edi_oscu.code` records were loaded without domain, which could lead to loading all records of these models in POS, which is not necessary. This commit adds a domain to the loading of these records, so that only the records that are actually used in the products are loaded in POS. Forward-Port-Of: odoo/enterprise#124154 Forward-Port-Of: odoo/enterprise#123684
VOIP call smart buttons now open the same full set of related records that they count, covering the customer’s company and related contacts. When creating a new record from a call, the system still defaults to the specific contact from the call, reducing confusion and keeping follow-up work accurate.
Original PR description
Commit [1] change the smart buttons on voip.call to count records from all family of the partner (count all record from all partners from the partner's company). However, the actions on the buttons stay the same, they will only open records from the partner itself. This is confusing, we change it to also show records from the whole partner family. [1]: 16772c5c1e604d6beb514f9e5b166fc0c2f452a7 Task-6379569 Forward-Port-Of: odoo/enterprise#123944
Service sale order lines generated from planning now let Odoo calculate prices automatically instead of forcing a fixed unit price. This ensures customer or product pricelists are applied correctly, reducing incorrect billing for field service work.
Original PR description
Before this commit, in the method `_generate_service_sale_order_lines` the `price_unit` for sale order lines used an explicitly defined unit price. This prevented the pre-computation of the price. It may cause some issues, for example if there was a pricelist defined on the product, we didn't apply the prices from that pricelist. This commit removes explicit definition of the `price_unit` to let the sale order creation process handle it. [error-941068](https://runbot.odoo.com/odoo/error/941068) Forward-Port-Of: odoo/enterprise#124680
This fixes an error that could interrupt stock operations when using Kenya OSCU e-invoicing modules, especially in kit-related picking flows. The change helps ensure stock moves complete reliably without unexpected tracebacks.
Original PR description
**CAUSE** super()._action_done() delete a record from self, and return a new recordset of stock.moves. We filter the old recordset instead of filtering the new one, leading to an MissingError traceback. **STEP TO REPRODUCE** On a fresh db, install: `l10n_ke,l10n_ke_edi_oscu,l10n_ke_edi_oscu_mrp,l10n_ke_edi_oscu_pos,l10n_ke_edi_oscu_stock,l10n_ke_edi_tremol,l10n_ke_hr_payroll,l10n_ke_hr_payroll_account,l10n_ke_reports` and run `TestKitPicking.test_add_sml_with_kit_to_confirmed_picking`. runbot-241262 Forward-Port-Of: odoo/enterprise#124663
Mexican payroll CFDI generation now includes the employer CURP when the company represents an individual, identified by a 13-character RFC. This prevents validation errors for businesses under fiscal regime 621 and allows affected payslips to be generated correctly.
Original PR description
Issue: ---------------------------------------- There is a fiscal regime where the company is actually an individual but is authorized to generate documents. Steps to reproduce:…
Issue: ---------------------------------------- There is a fiscal regime where the company is actually an individual but is authorized to generate documents. Steps to reproduce: ---------------------------------------- - Install "l10n_mx_hr_payroll_account_edi" - Change the current company fiscal regime to '621' - Add an VAT of length 13 to the current company - Add a CURP number on the current company - In Payroll generate a payslip, validate it - Post the Journal entry - On the payslip, click "Generate CFDI" - An error is returned, saying the Emisor:Curp applies to individuals Cause: ---------------------------------------- An RFC of length 13 means that the sender is an individual. It's intended with the fiscal regime '621'. We add the curp number in the CFDI XML only when `self.company_id.partner_id.is_company` is `False`. Since saas-19.1, `is_company` is computed to be truely if a VAT is present. So as soon as the VAT is entered, the CURP number is absent from the XML. Solution: ---------------------------------------- We change the condition to add the CURP number in the XML: A VAT number of length 13 means the contact is an individual (12 for companies). This is what is used to validate the XML: if the vat is of length 13, then the curp number should be present. opw-6351558 Forward-Port-Of: odoo/enterprise#124868 Forward-Port-Of: odoo/enterprise#124027
This update improves how email, SMS, and WhatsApp delivery failures and bounces are reflected in marketing automation campaigns. Businesses get more accurate campaign follow-up behavior and clearer failure information, while related tests were expanded to protect these workflows.
Original PR description
RATIONALE In order to prepare upcoming improvements for marketing automation application, as well as performance improvements, status update from sms / mail / whatsapp is checked, tested and fixed. Fixes are done in master mainly because it would be difficult in stable, and because those are not critical. SPECIFICATIONS See sub-commits for more details. Task-6401319: [mail_*] Fixup mail/sms/WA -> Trace -> MarketAuto update Task-4224152: [marketing_automation] Performance / Scalability
The Timesheets shortcut now appears whenever a user has an active employee profile in any selected company, not just the current company. This prevents eligible users from losing quick access to timesheet entry when working across multiple companies.
Original PR description
Steps to reproduce: - install Timesheets - create an employee for a user in company B - switch to company A (where the user has no employee) - the systray icon is hidden even though the user can…
Steps to reproduce: - install Timesheets - create an employee for a user in company B - switch to company A (where the user has no employee) - the systray icon is hidden even though the user can create timesheets in company B via the company selector Current behavior: the systray only checks the current company for a valid employee, ignoring other selected companies. Expected behavior: the systray should be visible whenever the user can create timesheets i.e. when they have an active employee in any of the selected companies. Issue: the check used a stored boolean on `res.partner` that has no company scope and becomes stale when an employee is archived (the stored dependency does not re-fire). the timesheet creation logic checks all selected companies for active employees, but the systray did not mirror that. Fix: use `employee_ids` a `One2many` that checks all selected companies and excludes archived employees, matching the timesheet creation logic exactly. task-6330539 Forward-Port-Of: odoo/enterprise#124830 Forward-Port-Of: odoo/enterprise#121730
Features or functions removed from Odoo
The India GST reporting flow now keeps only the newer HSN Summary format, which has been available for over a year. This simplifies maintenance and reduces duplicate reporting paths, while related test data for the retired format has also been removed.
Original PR description
As it has been more than a year since the new HSN Summary format was launched, this commit removes the old HSN Summary format for simplification. Additionally, it removes the related test cases as well. ref - https://github.com/odoo/enterprise/commit/8f3400d5037ea4aa23112ea7d65ae6afc7f488e4 https://github.com/odoo/enterprise/commit/d0df0f845ac2853d1247cae0d9543e92050bf2a4
12 changes
Resolved issues and error corrections
Fixed an issue where Project tasks could be accidentally duplicated after switching browser tabs during a drag action. Tasks now correctly reschedule when dropped unless the user is actively choosing to copy them, reducing accidental planning errors.
Original PR description
## Current behavior: When rescheduling a task in Project's Gantt chart view, if user switches to a different browser tab using the Ctrl shortcuts (either Ctrl + Tab, Ctrl + Shift + Tab or Ctrl + PgUp…
## Current behavior: When rescheduling a task in Project's Gantt chart view, if user switches to a different browser tab using the Ctrl shortcuts (either Ctrl + Tab, Ctrl + Shift + Tab or Ctrl + PgUp / PgDn), and navigate back to Gantt chart view, the task will get duplicated unexpectedly when getting dragged around even when the Ctrl button is released ## Expected behavior: Dropping a task without Ctrl held should reschedule even after a tab switch using the Ctrl shortcuts ## Steps to reproduce: 1. Install `project` module 2. Create a project and switch to Gantt chart view 3. Hold Ctrl and start dragging a task while switching browser tab with Ctrl+Tab, Ctrl+Shift+Tab, or Ctrl+PgUp/PgDn 4. Return to the Gantt tab and drop the task without holding Ctrl 5. The task is duplicated instead of rescheduled ## Cause of the issue: Copy and reschedule actions are tracked internally via interaction.dragAction, updated only on window keydown/keyup for Control. Currently, we're not keeping track of the `visibilitychange` event when switching tabs, causing the state of `ctrlPressed` to stale after a tab change, causing the dragAction to stay on copy mode ## Fix: Reset `dragAction` and `ctrlPressed` on document's `visibilitychange` when the tab has been changed opw-6298440
Lazada and Shopee order imports now handle discounts, vouchers, shipping fees, and small rounding differences in a way that matches the marketplace totals. This reduces mismatches during reconciliation and gives teams clearer order lines for discounts and shipping.
Original PR description
Marketplace orders with discount lines could not match the platform total: Odoo accumulated a small rounding residue vs Shopee's total_amount / Lazada's order price. sale_shopee ----------- Changes:…
Marketplace orders with discount lines could not match the platform total: Odoo accumulated a small rounding residue vs Shopee's total_amount / Lazada's order price. sale_shopee ----------- Changes: - Fetch buyer-side escrow amounts via `_fetch_order_income` and pass them through `self.env.context` (`order_income`). - Build item lines from the buyer-paid item price with `discount=0` and a recomputed tax-exclusive `price_unit`. - Distribute order-level discounts (seller/platform vouchers and coins) as dedicated negative lines per product tax group via `_prepare_discount_lines_values`. - Append a shipping line from `buyer_paid_shipping_fee` with fiscal-position mapped taxes. - Reconcile any leftover residue with `_adjust_order_total` using a single tax-free amount-adjustment line. - Register `default_discount_product` and configure it on upgrade (v1.1). sale_lazada ----------- - Port the same reconciliation model as shopee: reconciled line specs, discount=0 with discounted unit from paid_price, shipping line from shipping_fee, order-level "Discount line" distributed at order-level. task-6112062
Reconciliation models now use the correct currency when transactions involve multiple currencies. This prevents amounts from staying tied to the original transaction currency and helps businesses reconcile entries more accurately.
Original PR description
In the reconciliation model, the currency is not automatically converted if multiple currencies are being used. Instead, it remains stuck on the original currency of the transaction. This issue is resolved by this commit. runbot-https://runbot.odoo.com/runbot/bundle/190-reconciliation-model-currency-fix-andha-461793 task-5974154
Fixes an issue where selecting a customer in Point of Sale could show an AvaTax tax-loading error when an AvaTax fiscal position was used. This restores the checkout flow for businesses using AvaTax in PoS and avoids interruptions during sales.
Original PR description
Steps to reproduce: ------------------- 1. Install pos_avatax. 2. Enable Avatax in accounting, and use the testing credentials of Odoo. 3. Enable "Use AvaTax API" and "Detect Automatically" on the…
Steps to reproduce: ------------------- 1. Install pos_avatax. 2. Enable Avatax in accounting, and use the testing credentials of Odoo. 3. Enable "Use AvaTax API" and "Detect Automatically" on the default fiscal position (or another fiscal position available in PoS). 4. In a PoS shop, enable "AvaTax PoS Integration" 5. Find a product sell in that PoS, and assign it an "Avatax Category" 6. Open that PoS, add the prodct of step 5, and choose a customer (make sure the fiscal position of step 3 is being used). Observation: Error modal "Error while loading Avatax taxes", and error in the terminal `KeyError: 'base_line'`. Why it's happening: ------------------- Commit 0404086db567 changed the return of `_get_line_data_for_external_taxes`. Before, pos was returning something with `id`, `qty`, `price_subtotal` etc. After the commit the return should have a `base_line` key (the accounting base line we get from `_prepare_base_line_for_taxes_computation`). But that commit forgot to update the override in pos. So when avatax tries to read `line_data['base_line']` we get the KeyError. The fix: -------- Update the override in pos to return the new format, same way `sale_external_tax` is already doing it for sale orders. opw-6244141
Argentine electronic invoices now show a clear warning when a final consumer contact uses an unsupported identification type. This prevents a system error during invoice confirmation and helps users correct customer details before submitting invoices.
Original PR description
After applying the community fix, a new issue appears in enterprise. **Steps to reproduce:** - Install the `l10n_ar_edi` module. - Go to Customers and create a new customer with: - Country: Argentina…
After applying the community fix, a new issue appears in enterprise. **Steps to reproduce:** - Install the `l10n_ar_edi` module. - Go to Customers and create a new customer with: - Country: Argentina - Identification Type: `Passport` - Identification Number: `1234567890a` - AFIP Responsibility Type: `Consumidor Final` - Create a new invoice for this customer with: - Journal: `Electronic Invoice (FE)` - Document Type: `(6) INVOICES B` - Try to confirm the invoice. **Error:** ValueError: invalid literal for int() with base 10: '1234567890a' **Expected behaviour:** For Argentinean contacts with AFIP Responsibility Type `Consumidor Final`, the identification type must be `DNI` or `CUIL/CUIT`. If another identification type is used, the user should receive a proper warning instead of a traceback. (see [1]) **Root Cause:** After the community fix, `_get_id_number_sanitize()` can return an alphanumeric value, which is later converted with `int()` at [2] and [3], causing an error. **Fix:** This commit adds an explicit validation for Argentinean contacts with AFIP Responsibility Type Consumidor Final before converting the identification number to an integer, ensuring the user receives the proper warning message. [1]: https://www.odoo.com/mail/message/1118392033 [2]: https://github.com/odoo/enterprise/blob/064812dd9f1074f287fee677ba35ea4aeef3ab3f/l10n_ar_edi/models/account_move.py#L780-L798 [3]: https://github.com/odoo/enterprise/blob/064812dd9f1074f287fee677ba35ea4aeef3ab3f/l10n_ar_edi/models/account_move.py#L927-L931 Related community PR: https://github.com/odoo/odoo/pull/272651 opw-6333998 Forward-Port-Of: odoo/enterprise#123729
Appraisals now choose the template assigned to an employee's department instead of keeping a generic default. This helps HR teams use the right appraisal forms automatically while preserving deliberate template choices when they match the department.
Original PR description
**Problem:** When a template is restricted to a department, appraisals for employees of that department do not use it — a generic (global) template is used instead. **Steps to reproduce:** 1. Create…
**Problem:** When a template is restricted to a department, appraisals for employees of that department do not use it — a generic (global) template is used instead. **Steps to reproduce:** 1. Create an appraisal template and restrict it to a department. 2. Make sure at least one global (no department) template also exists. 3. Open a new appraisal and select an employee of that department. 4. Observe the appraisal template stays on the global one. **Current behavior:** The global template that was auto-selected before an employee was chosen is kept, ignoring the department-restricted template. **Expected behavior:** The template restricted to the employee's department is selected. **Cause of the issue:** On a new appraisal the template is computed before an employee (and therefore a department) is set, so a global template is pre-selected. When the employee is then chosen, `_compute_appraisal_template` reruns but the `appraisal.appraisal_template_id or ...` clause short-circuits on the already set global template and never falls through to the department's template. **Fix:** Evaluating the department's template before the existing value ensures a department-restricted template takes precedence over a template that was only auto-selected as a default. The existing value is still preserved when the department has no template of its own, so deliberate choices are untouched. opw-6293936
Spanish VAT books now report tax amounts correctly for vendor bills using EU taxes with negative components. This prevents valid tax amounts from being shown as zero, improving the accuracy of VAT reporting.
Original PR description
Problem: In the Spanish VAT books, taxes with negative component (like 21% EU G) always show their amounts as zero. Steps to reproduce: 1. Install the l10n_es_reports module and select the Spanish company 2. Create a vendor bill with a vendor from another EU country and add a product 3. Make sure the tax applied to the product has a negative component (like 21% EU G) 4. Confirm the bill 5. Go to Accounting > Reporting > Tax Return 6. Generate the VAT books from the action menu and check the tax amounts 7. Notice how the tax amount is zero, even though the tax was applied to the bill Cause: When calculating the tax amounts, the negative component cancels out the positive component, leading to the amount always showing as zero in the VAT books. opw-6169766 Forward-Port-Of: odoo/enterprise#123738
Swiss QR-IBAN payment references are now sanitized before payment files are generated. This helps prevent ISO 20022 payment files from being rejected by Swiss banks due to unsupported characters in references.
Original PR description
**Steps to reproduce:** * Configure a Swiss company with the Swiss accounting localization. * Install `account_sepa`. * Configure a bank journal with a valid Swiss **QR-IBAN**. * Create a vendor with…
**Steps to reproduce:** * Configure a Swiss company with the Swiss accounting localization. * Install `account_sepa`. * Configure a bank journal with a valid Swiss **QR-IBAN**. * Create a vendor with a valid Swiss bank account. * Under **Accounting -> Configuration -> Settings** enable QR Payments. * Go to the vendor and enter the correct QR-IBAN number under the Accounting Section. * Navigate to **Accounting -> Configuration -> Journals** and in the **bank** journal, add the account number of QR-IBAN. * Create and post a vendor payment using the bank journal. * Set the payment reference (`ref`) to contain the `°` character. * Add the payment to a batch payment and generate the ISO 20022 payment file. * Inspect the generated XML. **Observed Behaviour:** The `°` character is preserved in the payment reference for QR-IBAN payments, producing an ISO 20022 file that may be rejected by Swiss banks because it does not comply with the EPC minimum character set. **Cause:** The QR-IBAN branch validates the structured reference before sanitizing it, allowing unsupported characters to remain in the generated XML. **Fix:** Sanitize the payment reference before validating it as a QR structured reference, ensuring invalid characters are removed while preserving valid QR references. opw - 6350931 Forward-Port-Of: odoo/enterprise#124299 Forward-Port-Of: odoo/enterprise#123267
Resetting a submitted tax return no longer changes the company-wide tax lock date, preventing closed periods from being reopened for everyone by mistake. The fix also ensures companies can set the tax lock date before submitting a return, supporting safer workflows in larger accounting teams.
Original PR description
To reproduce the issue: 1) Initialize a company in Belgium, create the tax returns 2) Submit the VAT return from January 3) Open the lock date wizard. The tax lock date is January 31st. 4) Add a lock…
To reproduce the issue:
1) Initialize a company in Belgium, create the tax returns 2) Submit the VAT return from January
3) Open the lock date wizard. The tax lock date is January 31st. 4) Add a lock date exception removing the tax lock date just for you, for 5 min. 5) Reset January's return
6) Reopen the lock date wizard.
====> Your exception is still there, but the tax lock date for everyone has been reset to December 31st.
This is plain wrong. Resetting a return should not automatically reopen the period for everyone. Lock dates exceptions/modifications are anyway required to reset the return ; they should pilot the whole flow. Nothing being magically hidden from the user means there can't be someone else mistakenly encoding something into the reopened period.
Another fix was required to make this one work: setting the tax lock date before submitting the return should work. In bigger environments, users might want to do that as a first step to reduce the number of people encoding data before actually doing the submission of the return. Therefore, the case where the tax lock date is already set at the date_to of the return when submitting it was supposed to be already supported, and allow the creation of the closing entry for that return, despite it being on the tax lock date. The test ensuring this was however badly written, and the feature didn't work: the closing was created at a later date than the lock date automatically, due to the Bills' Algorithm.
Forward-Port-Of: odoo/enterprise#124811The report screen layout was adjusted so the chatter panel remains visible on the right side of the screen even when reports are very wide. Users can now discuss and follow up on reports without needing to scroll horizontally to find the conversation panel.
Original PR description
Issue: - When reports are large/wide, the chatter component is pushed beyond the visible viewport, appearing only at the absolute right edge of the overflowing report rather than the right edge of the screen. Fix: - Updated the layout container to prevent the chatter from shrinking or overflowing with the report block, ensuring the main report scrolls independently while the chatter stays pinned to the screen viewport. Impact: - Keeps the chatter panel fully visible on the right side of the screen, allowing users to communicate without scrolling horizontally on wide reports. task-[6376792](https://www.odoo.com/odoo/project/967/tasks/6376792)
Peruvian electronic invoices now convert detraction withholding amounts to PEN correctly even when the company currency is set to USD. This prevents incorrect XML submissions to SUNAT and ensures PDF reports show the proper Peruvian sol amount and symbol.
Original PR description
**Steps to reproduce:** * Install the **l10n_pe_edi** module. * Change the company currency to **USD**. * Configure **Peruvian detraction** settings: * Set a **withholding percentage** on a product.…
**Steps to reproduce:**
* Install the **l10n_pe_edi** module.
* Change the company currency to **USD**.
* Configure **Peruvian detraction** settings:
* Set a **withholding percentage** on a product.
* Set a **withholding code** on the same product.
* Create a customer invoice in **USD**.
* Set the **Operation Type** to **[1001] Operation Subject to Detraction**.
* Add the configured product to the invoice.
* Confirm the invoice, **send it to SUNAT** by *process now**, and generate the ZIP which has XML.
**Observed behavior:**
* The generated XML contains: `<cbc:Amount currencyID="PEN">116.82</cbc:Amount>` The `currencyID` is `PEN` but the amount is the raw USD value instead of being converted to PEN using the exchange rate.
* The PDF report also shows the withholding amount with the wrong currency symbol.
**Cause:**
* `_l10n_pe_edi_get_spot()` computed `spot['amount']` as `amount_total_signed * percent`, where `amount_total_signed` is stored in the **company currency**. When the company currency is PEN this works correctly, but when it is changed to USD the value is in USD while the XML node hardcodes `currencyID="PEN"`.
* `spot['currency']` was also set to `company_id.currency_id`, so the PDF rendered the amount with the wrong currency symbol.
* In `_add_invoice_payment_terms_nodes`, `spot_amount` was selected via `spot['amount'] if invoice.currency_id == invoice.company_id.currency_id` which relied on company currency being PEN as a proxy for the invoice being in PEN, breaking when company currency is USD.
**Fix:**
* In `_l10n_pe_edi_get_spot()`, compute `spot['amount']` by explicitly converting `amount_total` from the invoice currency to PEN using `currency_id._convert(..., env.ref('base.PEN'), ...)`, ensuring the detraction amount is always in PEN regardless of the company currency.
* Set `spot['currency']` to `env.ref('base.PEN')` so the PDF report always displays the withholding amount with the correct PEN symbol.
* In `_add_invoice_payment_terms_nodes`, always use `spot['spot_amount']` (invoice currency amount) for the `FormaPago`/`Cuota` arithmetic, since `spot['amount']` is now exclusively the PEN amount for the detraction XML node.
opw-6327571Peruvian electronic invoices that were registered by SUNAT but temporarily lacked their confirmation receipt will no longer get stuck after a duplicate-document response. Odoo now treats this as a retryable warning, allowing automatic retries to continue until SUNAT provides the confirmation, reducing manual follow-up.
Original PR description
Steps to reproduce: - Post a Peruvian invoice so it is sent to SUNAT (directly or through Estela/Digiflow). - SUNAT's sendBill call hangs and Odoo's request times out (ReadTimeout / ConnectionError),…
Steps to reproduce: - Post a Peruvian invoice so it is sent to SUNAT (directly or through Estela/Digiflow). - SUNAT's sendBill call hangs and Odoo's request times out (ReadTimeout / ConnectionError), even though SUNAT actually finishes registering the document on its side a moment later. - Odoo retries sending the same invoice (either automatically through the EDI cron, or manually). SUNAT now replies with a "document already exists" SOAP fault (code 1033/4000), since it processed the previous attempt. - Odoo tries to recover from this by fetching the CDR through getStatusCdr, but SUNAT has not finished generating it yet, so the lookup also fails. Cause of the issue: _l10n_pe_edi_post_invoice_web_service() already has recovery logic for error codes 1033/4000: it calls _l10n_pe_edi_retrieve_cdr() to fetch the CDR and treat the invoice as sent. But when that lookup itself fails (CDR not generated yet), the resulting error keeps the 'blocking_level' set to 'error' from the original SOAP fault. Documents with blocking_level 'error' are excluded from the automatic EDI cron retries (see account.edi.document._cron_process_documents_web_services), so the invoice gets stuck needing a manual retry, which can lose the same race against SUNAT again and again. Solution: When the CDR can't be retrieved yet after a 1033/4000 duplicate error, mark the result as 'blocking_level': 'warning' instead of leaving it at 'error'. This keeps the invoice eligible for the automatic EDI cron retries, so Odoo keeps polling SUNAT until the CDR becomes available, instead of requiring manual intervention every time this race is lost. opw-6393231
1 change
Resolved issues and error corrections
Spanish VAT record books now include taxable accounting entries created outside standard invoices and bills, such as Point of Sale session closures. This helps businesses produce more complete VAT records and avoid missing tax-relevant POS activity in exports.
Original PR description
Currently, the Spanish VAT record books (Libros Registro de IVA) only include move types associated with invoices and bills. However, miscellaneous entries (type 'entry'), such as those generated by the Point of Sale session closures or manual liquidations, also carry tax obligations and must be reflected in these reports. Steps to reproduce: - Open a POS Session - Create an order, pay and close session - Go to Accouting > Reporting > Tax report - Select Generic Tax report - Print "VAT record Books" Issue: Only invoices and bills are visible in the excel file, and not the entry generated from point of sale. However, movements that are not related to invoices should be included in the VAT books. opw-5862529