Wednesday, August 26, 2026
37 changes · saas-19.1
Resolved issues and error corrections
Refunds for POS orders with a fixed global discount now correctly mirror the original discounted total. This prevents inflated refund amounts and avoids accounting issues that could leave a POS session closed without the expected accounting entry.
Original PR description
BACKPORT OF https://github.com/odoo/odoo/pull/278180 When refunding an order that had a fixed-amount global discount, the refunded total was inflated: e.g. a 65.00 order (80.00 - 15.00 fixed…
BACKPORT OF https://github.com/odoo/odoo/pull/278180 When refunding an order that had a fixed-amount global discount, the refunded total was inflated: e.g. a 65.00 order (80.00 - 15.00 fixed discount) was refunded as 95.00 instead of 65.00. On the refund order amount_paid then differed from amount_total, so the POS session's closing entry could not balance; _validate_session rolled back and no account.move was created, leaving the session closed with no accounting. When a refund is created, the product lines are negated and the discount line is excluded, then pos_discount re-applies the global discount to the refund order through applyDiscount. For a fixed amount, reduce_base_lines_to_target_amount targets an absolute value and does not follow the sign of the (now negative) base, so the discount keeps the sale sign. Percentages scale with the base and are not affected. Negate the fixed discount amount when the destination order is a refund so it mirrors the negative base. The stored discount_value is left unchanged so the auto-resync of the discount stays idempotent. opw-6458124
The IoT display browser now starts only after the main Odoo startup process is ready. This prevents the screen from showing an initial error page and helps ensure the browser opens in fullscreen mode, improving reliability for IoT device displays.
Original PR description
Before this commit, the display driver (and therefore browser) were being started too early, causing the following issues: - The browser initially displays an error page, as it tries to load the status page before Odoo has finished starting. - The browser window is not fullscreen. This may be because it is started before labwc is fully initialised, as the correct fullscreen command line arguments are used. The second issue can be fixed by restarting the Odoo service, the first happens every time. After this commit, we start the IoT interfaces in the main run method, instead of when the module is imported, meaning that everything else has time to finish initialising. This solves both problems. task-6469793 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
This fixes an issue in Recruitment where emailing several applicants at once could incorrectly link them all to the same newly created contact. Applicant contact details now stay assigned only to the correct person, preventing email addresses from being overwritten across candidates.
Original PR description
**Problem:** When sending an Email to multiple Applicants when one Applicant does not have a `partner_id` set, all Applicants set to receive the email will copy the `email_from` of the Applicant…
**Problem:** When sending an Email to multiple Applicants when one Applicant does not have a `partner_id` set, all Applicants set to receive the email will copy the `email_from` of the Applicant without a `partner_id`. **Cause:** When sending an Email to an Applicant record that does not have a `partner_id` set on it, a new Contact is created to be set to the Applicant. When this Contact is created, its `applicant_ids` value defaults to the Applicants in the context. This causes all Applicants in the current context (i.e. the recipients of the Email) to be set on the Contact, also causing that Contact to be set as the `partner_id` of all the Applicants, rather than just the one missing a `partner_id`. This then causes the `email_from` of the Contact to be set on all the Applicants. https://github.com/odoo/odoo/blob/ccd167e23ea80545e4f09e2302c1b2489e8c42d8/addons/hr_recruitment/wizard/applicant_send_mail.py#L40-L42 https://github.com/odoo/odoo/blob/ccd167e23ea80545e4f09e2302c1b2489e8c42d8/odoo/orm/models.py#L1292-L1293 **Purpose:** Modify the `create` call used to create the new Contact to define `applicant_ids` as only the Applicant that the Contact is being created for. **Steps to Reproduce in Runbot:** 1. Delete the Contact corresponding to the `partner_id` of any given Applicant. 2. Select multiple Applicants, including the one whose `partner_id` was deleted, then send an email to them using the Actions menu. opw-6383263 Forward-Port-Of: odoo/odoo#283271
French payroll users can now update an employee's working hours even when the employee has approved time off on a non-working day such as a Saturday. The fix keeps those time-off dates valid instead of incorrectly shifting them into an invalid range, preventing unnecessary save errors.
Original PR description
**Problem:** For a French company, an employee whose working schedule differs from the company's cannot have their Working Hours changed when they have a validated time off that falls on a…
**Problem:** For a French company, an employee whose working schedule differs from the company's cannot have their Working Hours changed when they have a validated time off that falls on a non-working day (e.g. a Saturday). Saving fails with "The operation cannot be completed: The start date must be before or equal to the end date." **Steps to reproduce:** 1. Install l10n_fr_hr_holidays and work in a French company. 2. Set the company Working Hours and a reference (Paid) Time Off type. 3. Give an employee a Monday-to-Friday schedule that differs from the company's. 4. Create a one day Paid Time Off for the employee on a Saturday. 5. Change the employee's Working Hours. **Current behavior:** Saving is rejected by the date_from <= date_to constraint; the Working Hours cannot be changed as long as the weekend time off exists. **Expected behavior:** The Working Hours can be changed and the time off keeps a valid date range. **Cause of the issue:** When the French computation applies, `_get_fr_date_from_to` moves `date_start` forward to the first working day and, in a separate loop, moves `date_target` forward while the next day is a non-working day. The two loops are asymmetric: for a leave lying entirely on non-working days (a single Saturday for a Monday-to-Friday employee) `date_start` is pushed to the following Monday while `date_target` only reaches the Sunday. The pair is then written to `date_from`/`date_to` as Monday > Sunday, violating the date_from <= date_to constraint. **Fix:** A leave that contains no working day has nothing to anchor the "lost days" extension on, so the adjustment must not apply. Detecting the crossed pointers and keeping the leave's original dates preserves a valid range while leaving every leave that contains at least one working day untouched. opw-6348425 Forward-Port-Of: odoo/odoo#278833
Invoices created from point-of-sale orders linked to sales orders now keep the customer reference from the original sales order. This helps businesses maintain clearer traceability between customer orders and invoices, while still preserving POS reference information for consolidated invoice batches.
Original PR description
The pos_sale override of _prepare_invoice_vals left ref and invoice_origin untouched, so the SO's client_order_ref was lost on the invoice. The fix is to mirror sale.order._prepare_invoice and set both fields from the linked SO, while preserving pos_reference traceability for mixed consolidated batches. task-id: 6295629 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#278035
This update simplifies a costly styling rule in the enterprise messaging interface. It helps keep the interface efficient without changing the visible user experience in any meaningful way.
Original PR description
This PR cleans up a complex selector that is quite costly without providing any striking visual value. task-6481656
This fixes a problem that could prevent Chilean supplier e-invoices from being imported when they included foreign currency information on lines but not in the document header. The import now uses the standard total when the foreign-currency total is absent, reducing failed automated email imports.
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
This fix adjusts point-of-sale self-ordering behavior so an IoT connection warning does not appear when it is not relevant. It helps avoid confusing kiosk users and keeps the related automated test aligned with the intended experience.
Original PR description
This PR fixes the test where iot request triggers a "failed to contact your iot box on local network popup" Forward-Port-Of: odoo/enterprise#128388
Marking multiple appraisals as done from the list view now completes correctly instead of triggering an error. Completion notifications and messages are sent for each appraisal, helping HR teams process appraisal batches without interruption.
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
UPS shipping rate checks now allow phone numbers that match UPS documentation, including shorter numbers used in some countries. This prevents valid customers, such as those with Luxembourg phone numbers, from being blocked when adding shipping rates.
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 fiscal year validation now catches cases where a newly created fiscal year fully contains an existing one. This helps prevent duplicate or conflicting accounting periods that could cause reporting or closing issues.
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
Intrastat reports now include rental orders only when the rental period is at least two years. This prevents shorter rentals from appearing incorrectly, improving compliance reporting 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
This fixes an automated Knowledge calendar walkthrough that could fail when creating or editing calendar item properties too quickly. The change makes the test steps more reliable, helping prevent false failures during quality checks without changing user-facing behavior.
Original PR description
In `knowledge_calendar_command_tour`, we were experiencing two issues 1. First, when we change the properties of a new calendar item, we were running into an issues where the tour would fail due to…
In `knowledge_calendar_command_tour`, we were experiencing two issues 1. First, when we change the properties of a new calendar item, we were running into an issues where the tour would fail due to not being able to locate the dropdown option to create a new property. This only occurs if you don't set a step delay on the tour. This happens because in the `editSelectMenuInput` helper, we first check if the dropdown is open bfore we proceed. Since the tour runs so fast, we detect that the dropdown for the first property is open, so we pass the check. However, this then closes since we've moved on to the next dropdown, and since nothing has been input into the next dropdown, the create option doesn't appear. Now, we ensure that the create option will be present before attempting to click it 2. Later in the tour, we attempt to edit the properties on a new calendar item. We previously used `edit` to edit the property name, however this resulted in the "Select a template" modal being opened, which broke the tour, since we needed to click elements behind it. Using `fill` instead to populate the text field doesn't produce this behavior, allowing the tour to proceed without error. [runbot-939647](https://runbot.odoo.com/odoo/error/939647?debug=assets) Forward-Port-Of: odoo/enterprise#128830
This fix ensures French VAT returns sent to ASPOne do not treat whitespace-only express mention text as valid content. It prevents incomplete XML submissions that could otherwise be rejected, improving reliability for businesses filing French tax reports.
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
Gantt charts using a weekly view now place tasks in the correct week based on the user's local start-of-week setting. This prevents extra empty columns and misplaced tasks for regions where the week starts on Sunday, while keeping existing behavior unchanged for standard views.
Original PR description
In Gantt views, for a given focus date, the appropriate time interval is displayed by finding its start and end date, based on the scale. For example, if I focus on 18 may 2026 with a month scale,…
In Gantt views, for a given focus date, the appropriate time interval is displayed by finding its start and end date, based on the scale. For example, if I focus on 18 may 2026 with a month scale, then the whole month of may is displayed. The behaviour was as expected in standard code, because all localisations agree on the beginning of the available scales (day, month, year). In custom code, however, some customer requires to see the gantt charts with a weekly scale. The differences in start of the week based on the localisations and the inconsistencies of use of localStartOf breaks the view. For example, if the localization has the start of the week on a sunday, and a task on the first column starts on a sunday as well, it will get assigned to column before (because it considers sunday as the last day of the previous week). The column before the first column does not exist, so one empty column is created to put the task in it. This commit fixes these inconsistencies so that GanttRenderer behaves as expected with weekly scales, without changing the standard behaviour. Tests are written to check both that the task is assigned to the proper localized week (starting on Sunday) and column (1, not 0). Forward-Port-Of: odoo/enterprise#118625
French VAT XML-EDI reports now handle long account holder names by splitting them into acceptable parts. This helps prevent rejected submissions when holder names exceed the official 35-character limit.
Original PR description
The XSD for XML-EDI does not allow strings longer than 35 for TitulaireDesignation This commit splits the holder name in 2 parts when it is more than 35 characters task-6476440 Forward-Port-Of: odoo/enterprise#129091 Forward-Port-Of: odoo/enterprise#128239
Copying an image that is already linked elsewhere now reuses the existing attachment instead of creating an unnecessary duplicate. This helps keep stored files cleaner and avoids redundant attachments without changing the user workflow.
Original PR description
Copying an image attachment already linked to another record could leave a redundant duplicate behind instead of reusing the existing one. opw-6463012 Forward-Port-Of: odoo/odoo#283221 Forward-Port-Of: odoo/odoo#282287
German tax report exports now keep cents for the Kz83 amount instead of rounding them down. This prevents incorrect tax XML values, such as 26.40 being reported as 26.00, improving accuracy for German tax filings.
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
This fixes an error that could occur when filtering sales orders using custom fields linked to project task information, such as a task stage. Users can now apply these filters reliably 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#284101Modern search and AI crawlers are now recognized correctly, so they can reach the default website page instead of getting stuck on language redirects. This helps prevent indexing issues in tools such as Google Search Console while leaving normal visitor 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
The lunch app’s automated order check now waits for the correct test product to appear after changing location. This prevents intermittent failures caused by accidentally selecting leftover demo products, especially when some vendors are unavailable.
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
Updating a contact's email no longer crashes when that contact has more than one active signature request. The fix ensures each request is handled through the correct delivery channel, so customers can continue managing contacts and signature workflows without interruption.
Original PR description
Steps to reproduce: ---------------------------------------- 1. Install `whatsapp_sign` and Contact modules 2. Go to Sign > templates 3. Make two sign requests for the same partner 4. Go to partner…
Steps to reproduce:
----------------------------------------
1. Install `whatsapp_sign` and Contact modules
2. Go to Sign > templates
3. Make two sign requests for the same partner
4. Go to partner record and try to change the email (remove or add a character)
Observation:
----------------------------------------
Traceback occurs:
```
File '/home/odoo/odoo/enterprise/whatsapp_sign/models/sign_request_item.py', line 101, in _send_signature_access_message
is_whatsapp = self.sign_request_id.send_channel == 'whatsapp'
File '/home/odoo/odoo/community/odoo/orm/fields.py', line 1657, in __get__
record.ensure_one()
File '/home/odoo/odoo/community/odoo/orm/models.py', line 5942, in ensure_one
raise ValueError('Expected singleton: %s' % self)
ValueError: Expected singleton: sign.request(3, 4)
```
Issue:
----------------------------------------
In `_send_signature_access_message()` which accessed `self.sign_request_id.send_channel` directly on the full multi-record recordset. When `res_partner.write()` detects an email change, it searches for all sent sign request items of that partner and calls `send_signature_accesses()` on the combined recordset. Accessing `sign_request_id` on this multi-record set resolved to multiple sign requests, and the subsequent field access triggered `ensure_one()`.
Solution:
----------------------------------------
* Refactored `_send_signature_access_message()` to iterate over each item individually, checking `send_channel` per item's own `sign_request_id`. Items are partitioned into `email_items` and `whatsapp_items` batches. Email items are delegated to `super()` and WhatsApp items are processed individually.
* Refactored to cleaner and more readable code and correctly handles mixed-channel recordsets where items may belong to different sign requests with different send channels.
opw-6387586
Forward-Port-Of: odoo/enterprise#125075Sales users without accounting permissions can now view invoices created from sales orders that use cash rounding. This prevents an access error and keeps the invoice review workflow available to authorized sales staff.
Original PR description
**Behavior:** When a sales user without accounting access rights tries to view an invoice created from a sale order where a cash rounding is applied, an AccessError is raised. This occurs because…
**Behavior:** When a sales user without accounting access rights tries to view an invoice created from a sale order where a cash rounding is applied, an AccessError is raised. This occurs because loading the invoice view triggers `_compute_tax_totals()`, which then passes the invoice's `invoice_cash_rounding_id` to `_get_tax_totals_summary()`. Which then ends up failing when trying to access fields on the `cash_rounding` record due to missing accounting rights, even though the user is allowed to view the parent invoice. This is fixed by ensuring reading fields on `cash_rounding` during tax total computation bypasses the access check using `sudo()`, as the user already has legitimate access to the invoice itself. **Steps to reproduce:** - As an admin, enable cash rounding then create one. - Create an invoice and set the Cash Rounding Method - In debug mode, go to 'Set Default Values' in the debug dropdown and set Cash Rounding Method = your rounding for all users - Go to users, and ensures that Demo has no accounting rights but has sales user rights - As Demo, create a sale order, confirm it, then create the related invoice. - When trying to acces said invoice, you should get an Access Error opw-6379781 Forward-Port-Of: odoo/odoo#278815
Argentinian accounting users can now create invoices for foreign customers even when export sales journals are unavailable or archived. Instead of stopping the workflow with an error, the system falls back to a standard Invoice B document type so sales can continue.
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
Project profitability reports now include the cost of goods sold for delivered products when Anglo-Saxon accounting and analytic accounting are enabled. This gives users a more accurate view of project profitability instead of hiding costs that cancel each other out internally.
Original PR description
**Problem:** Since this PR https://github.com/odoo/odoo/pull/261798, both cogs lines have an analytic account, which causes the cogs to not appear on the project profitability report because cogs…
**Problem:** Since this PR https://github.com/odoo/odoo/pull/261798, both cogs lines have an analytic account, which causes the cogs to not appear on the project profitability report because cogs lines balance each other **Steps to reproduce:** - enable 'anglo saxon accounting' and 'analytic accounting' settings - create a storable product with automated std category, a cost of 10 and on hand quantity - create a service product and set the 'create on order' field to 'project' - confirm a SO for 1 unit of the product and 1 unit of the service - validate the delivery and create and confirm invoice - from the sale order, click on the project smart button - from the project click on the dashboard smart button **Current behavior:** the cogs section don't appear in the profitability report **Expected behavior:** it should appear with a line with a value of -10 **Cause of the issue:** since this PR https://github.com/odoo/odoo/pull/261798, both cogs line are linked to the analytic account. That's the expected behaviour but in the case of the project profitability reports, it prevents the user the see the cost of the product in the cogs section. That's because, inside the _get_revenues_items_from_invoices() method, bot cogs_line are added to the cogs_line list. https://github.com/odoo/odoo/blob/141cb292dc5e456161119e19f7a91665feaa0198/addons/sale_project/models/project_project.py#L699-L700 So when computing the amount_to_invoice for the costs ml_type, the balance of the lines will zero out each other and amount_to_invoice will be 0. https://github.com/odoo/odoo/blob/141cb292dc5e456161119e19f7a91665feaa0198/addons/sale_project/models/project_project.py#L703-L716 As a consequence, the cost of goods sold section won't be created https://github.com/odoo/odoo/blob/141cb292dc5e456161119e19f7a91665feaa0198/addons/sale_project/models/project_project.py#L718-L719 **fix:** only the line with an account of internal type 'expense' reflects the actual cost of the product sold in the context of the project. So when computing the profitability report that's the only line we should consider **test:** test_report_invoice_items_anglo_saxon_automatic_valuation checks that the cogs section is well displayed in the project profitability report. In the PR (mentionned above) which sets the analytic account on the stock cogs line, lines were added in the test to manually remove the analytic account on the stock cogs line to make the test pass. With the fix of this PR we can remove those additional lines in the test and it will check our use case well again. opw-6412409 Forward-Port-Of: odoo/odoo#284270 Forward-Port-Of: odoo/odoo#283144
This update stabilizes an automated website test related to closing popups. It helps reduce false failures in the testing pipeline, supporting smoother releases without changing customer-facing behavior.
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
This fix ensures UPS return shipments include the required commercial invoice in the delivery chatter, matching outbound shipments. It also cleans up US ZIP+4 postal codes before sending them to UPS, reducing rejected deliveries for US customers.
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
Fixed the Field Service report walkthrough so it works whether demo worksheet data is installed or not. This prevents the automated tour from timing out and helps ensure task report previews include the worksheet section correctly.
Original PR description
The FSM tour needs to wait until the worksheet view is fully loaded before filling it. Otherwise, the tour may continue too early and the worksheet is not saved properly, causing the worksheet…
The FSM tour needs to wait until the worksheet view is fully loaded before filling it. Otherwise, the tour may continue too early and the worksheet is not saved properly, causing the worksheet section to be missing when previewing the task report. A synchronization step was added for this purpose by waiting for `x_comments`, the default HTML field of a worksheet. (Related: https://github.com/odoo/enterprise/pull/116918) However, the demo Device Installation and Maintenance worksheet replaces the generated form view and does not render `x_comments`. It renders the `x_description` text field instead, causing the tour to time out when demo data is installed. To fix the discrepancy, we need to wait for the editable field of either worksheet variant directly. When the default worksheet is used, fill the `x_comments` HTML editor. When the demo worksheet is used, fill the `x_description` textarea instead. This keeps the synchronization needed to avoid the worksheet loading race while allowing the tour to run with and without demo data. [error-939242](https://runbot.odoo.com/odoo/error/939242) Forward-Port-Of: odoo/enterprise#128245
This update prevents an error when reconciling accounting entries while multiple companies are selected. It ensures the correct company is used for currency conversion, so users can reconcile cross-company journal items without interruption.
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#123774This fixes how Odoo Mail identifies attachments that are not embedded in a message body. It helps ensure separate attachments are recognized reliably and displayed as expected, reducing the chance of missing or inconsistent attachment lists.
Original PR description
Before this commit, `extra_body_attachment_ids` is declared with `fields.Attr("ir.attachment", { compute() })`, while its compute returns the records of `attachment_ids` that the body does not inline. The model name is therefore the default of an attr field, and only a read inside an update cycle answers that string, as the compute runs on the first read outside one. No reader of the field does that today.
This commit declares the field as the `fields.Many("ir.attachment")` its compute returns, so that the declaration matches the value before the first compute as well as after.
Note that the added test asserts that a message inlining one of its two images lists only the other one, which nothing covered so far. It passes without this change.This fixes an inventory forecasting issue where transfers recorded with zero demand could incorrectly reduce past forecasted quantities. Businesses will see more reliable historical stock forecasts when products are physically moved without planned demand.
Original PR description
**Problem:** When creating a transfer that moves out a product with zero demand quantity, it will change the forecasted quantity of that product in the past. **Cause:** The query filtered out the stock move with zero demand quantity, which preventing the system from accounting for unplanned physical transfers when retroactively calculating past inventory balances **Steps to reproduce the issue:** 1. Create a stock picking with 0 demand quantity that moves a product from an internal location to a virtual location or production location. 2. The forecasted quantity of the product becomes negative in the past. **Fix:** Add another check in the query to include stock moves with zero demand quantity. **Notes:** Since the forecast report is made from a SQL view, this will require a -u to update the report. opw-6462883 Forward-Port-Of: odoo/odoo#284000 Forward-Port-Of: odoo/odoo#283577
This fix ensures Odoo correctly refreshes whether taxes are marked as used when related accounting, expenses, point of sale, or purchasing records change. This helps users see accurate tax status information and avoids stale data in financial setup screens.
Original PR description
Currently, `is_used` is computed using queries on `account.move.line`, `account.reconcile.model.line`, etc. As a result, it has no depends and is not automatically updated when records in either model are created, modified, or deleted. This commit reverse M2M fields for respective models and use it as dependency to `_compute_is_used`. It also adds a missing dependency of `is_used` to `_compute_repartition_lines_str`. Forward-Port-Of: odoo/odoo#283406
Inventory users can now adjust a product's quantity on hand directly from the product form, matching what they could already do from the inventory adjustments menu. This removes an unnecessary detour and makes stock updates more consistent for day-to-day inventory work.
Original PR description
Steps to reproduce the bug: - Log in as a user with only the "Inventory / User" access right and Products/Create (product.group_product_manager) granted (write access to…
Steps to reproduce the bug:
- Log in as a user with only the "Inventory / User" access right and
Products/Create (product.group_product_manager) granted (write access
to product.product/product.template)
- Open a storable product's form view.
- Observe the "Quantity On Hand" field is readonly, and the "On Hand"
quants popup opened from it is read-only too.
- Go to the Inventory > Physical Inventory / Inventory Adjustments menu instead.
- Observe the same user can freely edit the quantity and apply the inventory adjustment.
Problem:
A stock user could apply inventory adjustments from the Inventory
Adjustments menu, but could not perform the exact same action from
the product form, forcing an unnecessary detour.
Three places in `stock` still gated editing to `stock.group_stock_manager`,
even though `inventory_mode` is already granted to any `stock.group_stock_user`
by `stock.quant._set_view_context()`, and the underlying write is already
guarded correctly by `_is_inventory_mode()`:
- `stock.quant._get_quants_action()` only picks the editable tree view
(used by the "On Hand" quants popup) for managers:
https://github.com/odoo/odoo/blob/19.0/addons/stock/models/stock_quant.py#L1328
- The product form's own "Quantity On Hand" field/link
(`product_views.xml`) is only made editable for managers, and forced
readonly for everyone else:
https://github.com/odoo/odoo/blob/19.0/addons/stock/views/product_views.xml#L192-L196
- The `inventory_quantity_auto_apply` field itself (the one actually
rendered in the editable quants list, whether opened from the product
form or the Forecasted Report) is restricted to managers at the Python
field-definition level:
https://github.com/odoo/odoo/blob/19.0/addons/stock/models/stock_quant.py#L100-L104
All three checks were left over from before commit
https://github.com/odoo/odoo/commit/37d96f49ccc85fa651f092b6c32bab1af2c34f2d,
which gave `stock.group_stock_user` write access on `stock.quant`
(see `ir.model.access.csv`) and dropped the manager-only restriction on
`action_apply_inventory`. The ACL and the Inventory Adjustments flow
were updated at the time, but these three entry points were not, leaving
them stricter than the rest of the permission model.
opw-6439844
Forward-Port-Of: odoo/odoo#282010Appointment invitation emails now handle calendar access tokens correctly when creating public calendar links. This prevents invitation emails from failing to generate for attendees, improving reliability for appointment communications.
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
The Inventory Valuation report now shows account balances tied to products that currently have no stock on hand. This helps businesses spot and correct unbalanced inventory accounting entries without losing the performance benefit of skipping unnecessary value calculations.
Original PR description
## Problem If multiple valuation/variation accounts are used, the Inventory Valuation report will not show the balance of accounts attached to products that have 0 quantity available if a different…
## Problem If multiple valuation/variation accounts are used, the Inventory Valuation report will not show the balance of accounts attached to products that have 0 quantity available if a different account has quantity. ## Solution In order to maintain the performance improvements intended by the commit that introduced the `qty_available != 0` filter, we will avoid calculating `total_value` for products with 0 quantity. We will still run `stock_accounting_value` on these products in order to capture interim accounting value on the Inventory Valuation report. ## Steps to Reproduce (Runbot v19) (defer to the test for more info) 1. Create an extra set of valuation/variation accounts 2. Create a product, avco perpetual accounting the default valuation/variation accounts 3. Create a second product, avco perpetual accounting the new valuation/variation accounts 4. Purchase 1 unit of each of the products and receive, bill both 5. Sell 1 unit of the product attached to the new valuation/variation 6. Go to Accounting > Review > Inventory Valuation, and note that the new valuation/variation accounts are not present. If you click Generate Entry, you will see that these accounts need to be balanced opw-6473319 Forward-Port-Of: odoo/odoo#282819
Point of Sale cash in/out receipts now print correctly when the reason text is very long. This prevents company details at the bottom of the receipt from being squeezed or mixed with the reason, improving receipt readability for stores using supported printers.
Original PR description
Steps to reproduce ------------------ 1. link an epson printer to the PoS 2. open a session and make a cash out with a long line reason 3. print the receipt -> the company info at the bottom is…
Steps to reproduce ------------------ 1. link an epson printer to the PoS 2. open a session and make a cash out with a long line reason 3. print the receipt -> the company info at the bottom is printed on 2 or 3 characters per line and the reason is printed next to it Why it's happening ------------------ The reason value is a `float: right`, and with a long reason the float takes almost all the width of the receipt. The company info, which is below the reason since `fd8845b393fc`, is a `d-flex` block, and a flex container is not allowed to overlap a float, so it only gets the width that is left next to it. Its two columns become very narrow and, as they use `text-break`, the text is cut in the middle of the words. The fix ------- From 19.2 the receipt is rendered on the server and the label and the value are in a table, 2 cells side by side, so no overlaping there. We do the same here but with flex instead of tables, since it's simpler. We also add `text-break` on it for the reasons with a very long word. ### Before <img width="618" height="876" alt="image" src="https://github.com/user-attachments/assets/1580d040-a6f0-468e-9da4-830b9a71cdac" /> ### After <img width="682" height="651" alt="image" src="https://github.com/user-attachments/assets/d2ddf293-57b0-467e-aa87-7f761121915d" /> opw-6473483 Forward-Port-Of: odoo/odoo#284203
The Documents app now handles multiple shortcuts that point to the same document without triggering a crash in the search panel. This improves reliability for users navigating document shortcuts and prevents interruptions caused by duplicate shortcut targets.
Original PR description
This commit prevents the Documents search panel from crashing with an "Expected singleton" error. `grouped` does not de-duplicate: it extends each group with the record ids as given, so as soon as two shortcuts point to the same document, the browsed target ids contain that id twice and reading `user_permission` on the group fails. Browsing through an `OrderedSet` keeps the batched read while guaranteeing one record per group. Forward-Port-Of: odoo/enterprise#129134