Wednesday, July 1, 2026
6 changes · saas-18.3
Enhancements to existing features
This change ensures that the unique transaction ID provided by Italy’s SDI is saved not only for sent documents, but also for received vendor bills. It improves traceability and makes it easier to match and audit incoming electronic invoices.
Original PR description
A unique transaction id is provided by the SDI for every document. This transaction id was saved on document sending, but discarded for received one. backport of f2cc23b30dd4 opw-6111186 Forward-Port-Of: odoo/odoo#268508
Resolved issues and error corrections
This update ensures Romania-specific stock batch behavior is applied only when appropriate, instead of affecting all cases. It prevents test and build failures caused by those rules being triggered unconditionally.
Original PR description
The Romanian specifics were applied without condition which caused runbot errors. Note that this was revealed later on (saas-19.3) after a change in the generic stock test setup. runbot-241098 Forward-Port-Of: odoo/odoo#271985
This update corrects a test in the Mexican e-invoicing flow so it works whether the accounting app is installed or not. It prevents false failures caused by a payment status changing from “paid” to “in payment” in certain setups.
Original PR description
If accountant is installed, payment state of unreconciled payment switch from 'paid' to 'in_payment'. Not having accountant break the test. runbot-939445 Forward-Port-Of: odoo/enterprise#121113
We updated the Colombian POS tour check so it no longer expects one exact document number. This prevents the test from failing when document numbers increase during repeated runs, making the validation more stable and reducing false errors.
Original PR description
**Why the fix:** This step failed from time to time as we did some batch testing on the runbot with the same database, and because of this, the Número de Documento increased, making it SETF990000002 or more. This error existed before 68da209 but by fixing the refund flow in said commit, this error has been appearing way more frequently. As this has already happened a few times in 18.2, it is still the targeted version for this fix. We now use a regex to make sure that we have **Número de Documento: SETF** followed by some numbers, but we do not specify that it should be SETF990000001 anymore. runbot-241997 Forward-Port-Of: odoo/enterprise#121211
This update moves a bank account retrieval test into the dedicated invoice import test file and uses a partial XML sample instead of a generated one. It makes the test setup closer to real import data and helps keep the test more stable and maintainable.
Original PR description
Move the partner retrieval bank account number test to the `test_ubl_import_bis3_invoice_be_retrieve_partner.py` file and use a partial XML instead of a generated XML. Forward-Port-Of: odoo/odoo#269995
In a multi-company setup with a website per company, the unsubscribe link in mass mailing emails could send recipients to the login page instead of the unsubscribe confirmation page. ### Steps to reproduce 1. Enable multi-company and create a second company `Company B`. 2. Create two websites with different domains, one per company: - `Website A` on the main company, domain `http://website-a.test` - `Website B` on `Company B`, domain `http://website-b.test` 3. Set the system para
Original PR description
In a multi-company setup with a website per company, the unsubscribe link in mass mailing emails could send recipients to the login page instead of the unsubscribe confirmation page. ### Steps to…
In a multi-company setup with a website per company, the unsubscribe link in mass mailing emails could send recipients to the login page instead of the unsubscribe confirmation page.
### Steps to reproduce
1. Enable multi-company and create a second company `Company B`.
2. Create two websites with different domains, one per company:
- `Website A` on the main company, domain `http://website-a.test`
- `Website B` on `Company B`, domain `http://website-b.test`
3. Set the system parameter `web.base.url` to `http://website-a.test`. System parameters are global, so this value applies to the whole database regardless of the company you switch to.
4. Create a contact and set its `Company` field to `Company B`.
5. In Email Marketing, create a mailing with recipient model `Contact`, target the contact above, pick any template with an unsubscribe link, and send it.
6. Open the email in an incognito window and click the unsubscribe link: you land on the login page instead of the unsubscribe page.
### Cause
Mass mailing builds the unsubscribe link in two steps.
First, each email body is rendered for its recipient. While rendering, relative URLs like `/unsubscribe_from_list` are turned into absolute URLs by prepending a base URL. That base URL comes from the recipient record itself: `recipient.get_base_url()`. The `website` module overrides this so that, when the record has a company, it returns that company's website domain. For a contact in `Company B`, the body ends up with `http://website-b.test/unsubscribe_from_list`.
Second, right before sending, `mail_mail._prepare_outgoing_list` replaces that placeholder URL with a per-recipient signed URL pointing to `/confirm_unsubscribe`. It does this by plain string replacement: it looks for `{base_url}/unsubscribe_from_list` in the body and swaps it. The `base_url` used here came from `self.mailing_id.get_base_url()`. A mailing has no company, so its base URL falls back to the global `web.base.url`, which in our setup is `http://website-a.test`.
The two base URLs no longer match. The body contains the website B URL, but the replacement code searches for the website A URL. The search fails, the placeholder stays in the email, and the recipient clicks a link to `/unsubscribe_from_list`. That route only redirects to `/mailing/my`, which requires being logged in, so the user lands on the login page.
### Fix
Compute the base URL from the recipient record (the same record used when rendering the body) instead of the mailing. The two URLs then agree and the replacement works. Fall back to the mailing's base URL if there is no recipient model on the mail.
opw-4914203
Forward-Port-Of: odoo/odoo#273190
Forward-Port-Of: odoo/odoo#264055