Monday, August 10, 2026
6 changes · saas-18.4
Resolved issues and error corrections
The attendance app now applies date limits correctly when retrieving employee attendance records. This avoids processing unnecessary historical records, improving performance for attendance-related calculations without changing user workflows.
Original PR description
Before this commit: --- Due to changes introduced in the Odoo 18…
Before this commit:
---
Due to changes introduced in the Odoo 18 [`expression.combine`](https://github.com/odoo/odoo/pull/160979/changes#diff-fa4d9268d6e65e19aebec81c46038f0e496b91142588ed4d1c1bce7ff2338f2c) function, an empty domain ([]) is evaluated as True. As a result, [`attendance_domain`](https://github.com/odoo/odoo/blob/1073447ba56e2cc69177ee0ac8eab36d63d907bc/addons/hr_attendance/models/hr_attendance.py#L378), which is initially [], is [OR-ed](https://github.com/odoo/odoo/blob/1073447ba56e2cc69177ee0ac8eab36d63d907bc/addons/hr_attendance/models/hr_attendance.py#L380) with the date conditions, causing the expression True OR X to always evaluate to True.
Consequently, the date filters are never added to the domain, and the final domain becomes:
[('employee_id', '=', 28)]
This causes all attendance records for the employee to be fetched and processed, regardless of the requested date range, unnecessarily increasing computation time.
### Before the fix :
```python
(Pdb) attendance_domain = []
(Pdb) attendance_date
(datetime.datetime(2026, 8, 3, 18, 30), datetime.date(2026, 8, 4))
(Pdb) attendance_domain = OR([attendance_domain, [('check_in', '>=', attendance_date[0]), ('check_in', '<', attendance_date[0] + timedelta(hours=24)),]])
(Pdb) attendance_domain
[(1, '=', 1)]
(Pdb) attendance_domain = AND([[('employee_id', '=', emp.id)], attendance_domain])
(Pdb) attendance_domain
[('employee_id', '=', 28)]
(Pdb)
```
After this commit:
---
attendance_domain is initialized with FALSE_DOMAIN instead of an empty domain. This ensures that the OR operation correctly incorporates the date conditions, producing the expected domain and limiting the query to the relevant attendance records.
### After the fix :
```python
(Pdb) attendance_domain = expression.FALSE_DOMAIN
(Pdb) attendance_domain
((0, '=', 1),)
(Pdb) attendance_date
(datetime.datetime(2026, 8, 3, 18, 30), datetime.date(2026, 8, 4))
(Pdb) attendance_domain = OR([attendance_domain, [('check_in', '>=', attendance_date[0]), ('check_in', '<', attendance_date[0] + timedelta(hours=24)),]])
(Pdb) attendance_domain
['&', ('check_in', '>=', datetime.datetime(2026, 8, 3, 18, 30)), ('check_in', '<', datetime.datetime(2026, 8, 4, 18, 30))]
(Pdb) attendance_domain = AND([[('employee_id', '=', emp.id)], attendance_domain])
(Pdb) attendance_domain
['&', ('employee_id', '=', 28), '&', ('check_in', '>=', datetime.datetime(2026, 8, 3, 18, 30)), ('check_in', '<', datetime.datetime(2026, 8, 4, 18, 30))]
```
OPW: 6385811
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#281115Discounted supplier bills imported from UBL files now calculate price-included taxes using the pre-discount tax amount. This prevents incorrect unit prices and helps keep imported bill totals accurate.
Original PR description
When importing a UBL bill, if a price-included tax is used, we need to adjust the unit price by adding the tax amount per unit. When a discount is added to the line, the unit price doesn't reflect it, but the raw_tax_amount_currency does. As a result, we add the reduced tax amount to the original unit price. We propose to compute the raw tax amount before the discount to adjust the unit price. opw-6242701 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#278659
This fix resolves an error that could prevent users from creating credit notes for GCC-localized invoices when Arabic terms and conditions were used. It removes an outdated workaround that is no longer needed, keeping invoice terms displayed correctly while restoring smooth credit note creation.
Original PR description
Steps to Reproduce the Error (Odoo SaaS 19.2): 1. Install l10n_gcc_invoice localization & Accounting 2. Activate Arabic language (ar_001) and add Default Terms and Conditions in Settings >…
Steps to Reproduce the Error (Odoo SaaS 19.2):
1. Install l10n_gcc_invoice localization & Accounting
2. Activate Arabic language (ar_001) and add Default Terms and Conditions in Settings > Configuration > Customer Invoices > Default Terms and Conditions
3. Create invoice with ar_001 partner
4. Confirm the invoice
5. Try to create credit note → Error: KeyError: 'en_US'
Root Cause:
The _load_narration_translation() workaround reads raw invoice_terms from DB and injects the entire JSONB dict directly into cache, bypassing ORM field conversion. When Odoo 19.2's improved ORM conversion runs, it creates nested JSON in narration instead of a flat structure.
Timeline:
- bedf1cb66fbb: Workaround added to prevent T&C duplication in preview
- 75f050b9650d: Root cause fixed in report template (conditional display) → Made _load_narration_translation() redundant
- 4e4156536bc9: Odoo 19.2 improved ORM conversion → Now conflicts with the redundant workaround, causing nested JSON
How It Breaks:
1. Invoice creation: _load_narration_translation() injects raw dict into cache
2. ORM writes: nested JSON stored: {ar_001: {en_US: ., ar_001: Arabic}}
3. Credit note creation: copy_translations() expects flat structure → Crashes: KeyError: 'en_US'
Why It's Safe to Remove:
Report template already prevents T&C duplication (commit 75f050b9650d). Removing the workaround restores proper credit note creation without breaking T&C display.
Changes:
- Remove moves._load_narration_translation() in create()
- Remove out self.filtered('id')._load_narration_translation() in _compute_narration()
opw : 6284943
Forward-Port-Of: odoo/odoo#271037This fixes an invoicing issue where already billed and partially refunded timesheet hours could be billed again when creating an invoice for a date period. Businesses using timesheet-based services now get more accurate invoices, reducing customer disputes and manual corrections.
Original PR description
### Steps to reproduce 1. Install *Sales* and *Timesheets* 2. Create a service product: Invoicing Policy = *Based on Timesheets*, Create on Order = *Task* 3. Create a sale order for it (quantity 1)…
### Steps to reproduce 1. Install *Sales* and *Timesheets* 2. Create a service product: Invoicing Policy = *Based on Timesheets*, Create on Order = *Task* 3. Create a sale order for it (quantity 1) and confirm it 4. On the generated task, log **4.5 h on 15/06** and **3.5 h on 23/07** 5. *Create Invoice* with no timesheet period → 8 h, and post it 6. On that invoice: *Reverse* → *Partial Refund*, set the quantity to **3.5 h** and post it → 4.5 h invoiced 7. Log **1 h on 31/07** → 9 h delivered 8. *Create Invoice* again, with a **Timesheets Period of 01/06 → 31/07** ### Current behavior The invoice bills **9 h**: the 4.5 h that were invoiced and not credited are billed a second time. ### Expected behavior The invoice bills **4.5 h** — the quantity delivered minus the quantity invoiced. ### Cause of the issue Posting a partial credit note clears `timesheet_invoice_id` on every timesheet the reversed invoice had linked (`sale_timesheet/models/account_move.py`, `action_post`), because a credit note carries a quantity and never a set of timesheets, so there is no way to tell which hours it credited. All of those hours therefore become candidates again in `_recompute_qty_to_invoice`, which assigns their sum to `qty_to_invoice` without comparing it to what is still due on the line. ### Fix Timesheet links cannot express a partially invoiced timesheet, so they are used only to select the hours a period concerns, while the quantity that may still be billed is `qty_delivered - qty_invoiced`. The period lookup is capped by that remainder, and kept at zero or above so that an over-invoiced line is corrected by a deliberate credit note rather than as a side effect of invoicing a period. ### Tests Five tests are added to `addons/sale_timesheet/tests/test_sale_timesheet.py`. Three of them fail without the fix: | test | without the fix | | --- | --- | | `test_period_invoice_does_not_rebill_refunded_invoice_hours` | `9.0 != 4.5` | | `test_period_invoice_after_refund_is_computed_per_line` | `4.0 != 1.5` | | `test_period_invoice_after_refund_of_an_over_invoiced_line` | `8.0 != 1.0` | The other two cover behaviour that is not exercised today and that the fix must not break: an over-invoiced line (which must be left out rather than credited, and must not prevent the other lines of the order from being invoiced) and the reversed invoice's own `invoice_date`, which must not influence the quantity billed for a period. The full `sale_timesheet` suite passes (86 tests). Forward-Port-Of: odoo/odoo#281065 Forward-Port-Of: odoo/odoo#280536
This update fixes how certain user-provided search filters are evaluated, ensuring searches use the intended context and return more consistent results. It also cleans up an outdated event-related search customization to better align with the standard framework behavior.
Original PR description
The value often comes from the user and may be a Domain, the search implementation may incorrectly handle it by using the wrong context. For most cases, transform 'any' Domain into a Query object before calling `Field.search` to freeze the context used the generate the query. task-6446206 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#280368
Helpdesk tickets now show an SLA as reached only after the ticket actually reaches the required stage. This prevents new tickets from appearing compliant too early and ensures late completions are still recognized correctly while lateness remains tracked separately.
Original PR description
### Steps to Reproduce: 1. Go to Helpdesk and create an SLA 2. Create a new Ticket 3. Use Studio to add the field `sla_reached` 4. Apply the SLA 5. Notice that the field is True ### Description of…
### Steps to Reproduce: 1. Go to Helpdesk and create an SLA 2. Create a new Ticket 3. Use Studio to add the field `sla_reached` 4. Apply the SLA 5. Notice that the field is True ### Description of the issue/feature this PR addresses: **Issue:** The `_compute_sla_reached` method in on `helpdesk_ticket.py` determines whether an SLA has been reached by checking if `exceeded_hours` is less than 0 on its `helpdesk.sla.status` records. Since every newly assigned SLA has a future deadline, `exceeded_hours` starts as negative (AKA <0), so the ticket is immediately flagged as `sla_reached = True` before any progress has been made. Conversely, a ticket that reaches its target stage after the deadline has `exceeded_hours >= 0`, so it's incorrectly flagged as `sla_reached = False` even though the SLA target was genuinely reached (it was just late). **Solution:** Change the domain in `_compute_sla_reached` to check `reached_datetime != False` instead of `exceeded_hours < 0`, matching the field already listed in the method's `@api.depends` and the same field `_sla_reach()` sets when a ticket enters its target stage. This makes `sla_reached` reflect actual stage progression rather than a time-remaining calculation. ### Current behavior before PR: A newly created ticket with a pending SLA shows `sla_reached = True` and `sla_success = True` immediately upon creation, before the ticket has moved to any target stage. SImilarly, a ticket that reaches its target stage after the SLA deadline is incorrectly marked `sla_reached = False`. ### Desired behavior after PR: `sla_reached` is False on ticket creation and only becomes True once the ticket actually enters the SLA's target stage. Lateness continues to be tracked separately and correctly via `sla_reached_late` opw-6361851 Forward-Port-Of: odoo/enterprise#126432