Monday, June 8, 2026
2 changes · 19.0
Resolved issues and error corrections
The Time Off Ledger report has been optimized so it no longer times out when loading large datasets. Public holidays are now handled more accurately in the report, preventing employees from being incorrectly flagged while significantly reducing page load time.
Original PR description
The Time Off Ledger report was timing out on every page load, exceeding worker limit and never returning results (For the Db I worked on). **Root cause:** `_join_calendar_leaves` joined against all…
The Time Off Ledger report was timing out on every page load, exceeding worker limit and never returning results (For the Db I worked on).
**Root cause:** `_join_calendar_leaves` joined against all 95 rows of `resource_calendar_leaves` using a BETWEEN range condition. All 95 rows have `calendar_id = NULL`, making the join condition always TRUE, which forced every row in the query (148,880) to be evaluated against all 95 leave records — producing 316M intermediate comparisons just to filter out public holidays.
**Fix:** pre-expand holiday date ranges into individual dates using `generate_series` (filtered to the report window), then join on simple equality instead of BETWEEN. PSQL builds a hash table of ~20 rows once and looks up each day in O(1).
As a side effect, the old approach excluded public holiday rows from the report entirely. The new approach keeps them but sets `expected_hours = 0` on holidays so `difference_hours` stays neutral and employees aren't incorrectly flagged.
Before: 441s full scan, 14m+ per page load, worker timeout every time
After: ~84s full scan, ~1m 24s per page load (-90%)
**Note:** queries still take ~1-2 minutes because PSQL cannot push Odoo's date filters inside a regular view.
Queries output:
```sql
4281009_back_org_18.0_19.0=> SELECT calendar_id, count(*)
FROM resource_calendar_leaves
WHERE resource_id IS NULL
GROUP BY calendar_id;
calendar_id | count
-------------+-------
| 95
(1 row)
Time: 1.732 ms
4281009_back_org_18.0_19.0=> SELECT
count(*) AS total_rows,
count(*) FILTER (
WHERE (date_to AT TIME ZONE 'UTC')::date >= (date_trunc('month', CURRENT_DATE) - INTERVAL '1 year')::date
AND (date_from AT TIME ZONE 'UTC')::date <= CURRENT_DATE
) AS rows_in_window
FROM resource_calendar_leaves
WHERE resource_id IS NULL;
total_rows | rows_in_window
------------+----------------
95 | 20
(1 row)
Time: 1.128 ms
4281009_back_org_18.0_19.0=> SELECT count(*)
FROM hr_employee emp
CROSS JOIN generate_series(
(date_trunc('month', CURRENT_DATE) - INTERVAL '1 year')::date,
(CURRENT_DATE - 1)::date, INTERVAL '1 day'
) AS gs(day)
LEFT JOIN LATERAL (
SELECT resource_calendar_id FROM hr_version v
WHERE v.employee_id = emp.id
AND v.contract_date_start IS NOT NULL
AND v.contract_date_start <= gs.day
AND (v.contract_date_end >= gs.day OR v.contract_date_end IS NULL)
AND v.date_version <= gs.day
ORDER BY v.date_version DESC LIMIT 1
) AS ver ON TRUE
JOIN resource_calendar rc ON ver.resource_calendar_id = rc.id
LEFT JOIN resource_calendar_leaves rcl
ON (rc.id = rcl.calendar_id OR rcl.calendar_id IS NULL)
AND rcl.resource_id IS NULL
AND rcl.company_id = emp.company_id
AND gs.day
BETWEEN (rcl.date_from AT TIME ZONE 'UTC' AT TIME ZONE COALESCE(rc.tz, 'UTC'))::date
AND (rcl.date_to AT TIME ZONE 'UTC' AT TIME ZONE COALESCE(rc.tz, 'UTC'))::date;
count
--------
268998
(1 row)
Time: 4516.694 ms (00:04.517)
4281009_back_org_18.0_19.0=> SELECT count(*)
FROM hr_employee emp
CROSS JOIN generate_series(
(date_trunc('month', CURRENT_DATE) - INTERVAL '1 year')::date,
(CURRENT_DATE - 1)::date, INTERVAL '1 day'
) AS gs(day)
LEFT JOIN LATERAL (
SELECT resource_calendar_id FROM hr_version v
WHERE v.employee_id = emp.id
AND v.contract_date_start IS NOT NULL
AND v.contract_date_start <= gs.day
AND (v.contract_date_end >= gs.day OR v.contract_date_end IS NULL)
AND v.date_version <= gs.day
ORDER BY v.date_version DESC LIMIT 1
) AS ver ON TRUE
JOIN resource_calendar rc ON ver.resource_calendar_id = rc.id
LEFT JOIN (
SELECT DISTINCT company_id,
generate_series(
(date_from AT TIME ZONE 'UTC')::date,
(date_to AT TIME ZONE 'UTC')::date - INTERVAL '1 day',
INTERVAL '1 day'
)::date AS holiday_date
FROM resource_calendar_leaves
WHERE resource_id IS NULL
AND (date_to AT TIME ZONE 'UTC')::date >= (date_trunc('month', CURRENT_DATE) - INTERVAL '1 year')::date
AND (date_from AT TIME ZONE 'UTC')::date <= CURRENT_DATE
) AS rcl
ON rcl.company_id = emp.company_id
AND rcl.holiday_date = gs.day;
count
--------
268998
(1 row)
Time: 1300.824 ms (00:01.301)
```
opw-6224357
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-prFixed an issue where applying a discount to cart items with different tax rates could cause the checkout page to refresh continuously. Customers can now complete checkout normally when promotions apply across products with different taxes.
Original PR description
**Step to reproduce :** 1. Create a deliverable product with a sales tax. 2. Create another product with a different sales tax. 3. Publish both products on the eCommerce website. 4. Create a discount…
**Step to reproduce :**
1. Create a deliverable product with a sales tax.
2. Create another product with a different sales tax.
3. Publish both products on the eCommerce website.
4. Create a discount program.
5. Add both products to the shopping cart.
6. Apply the discount code.
7. Proceed to checkout.
**Issue :**
Applying a discount on multiple products with different taxes causes an infinite reload cycle during checkout.
**Reason :**
The reload is supposed to sync the discount lines in the back-end with the discount lines displayed during checkout. If the number of lines don't match, a reload is triggered.
https://github.com/odoo/odoo/blob/18.0/addons/website_sale_loyalty/static/src/js/checkout.js#L22-L24
After the fix introduced in:
https://github.com/odoo/odoo/pull/248215
However, when a discount is applied to products with different taxes, the corresponding reward lines are still categorized as `discounted_lines` instead of `groupable_lines`. As a result, they continue to be processed individually rather than being grouped by reward.
This leads to a mismatch between the backend, which generates one discount line per tax combination, and the frontend, which expects a single discount entry per reward. Consequently, the checkout page continuously reloads while attempting to synchronize both states.
**Solution:**
When a discount applies to products with different tax configurations, the corresponding reward lines should be included in `groupable_lines` rather than `discounted_lines`. This ensures that discount lines are grouped by
`reward_id` consistently on both the frontend and backend, preventing the checkout reload loop.
opw-6210411
Forward-Port-Of: odoo/odoo#265740