[IMP] hr_payroll: Improve benefits conflict performance

This commit changes the way benefits overlaps are detected.
Currently, all benefits are read from db and the
overlap detection is done in Python.

After this commit, use a single sql query to
only retreive overlapping benefits.
The query takes advantage of the postgresql
range type `tsrange` and the intersection operator

Performance improvements:
Tests with 500 employees and benefits generated for
15 months (~300000 records in the table)
Conflict detection over 1 month (~20000 records)
=> ~5X speedup (from more than 2s to less than 0.5s)
This commit is contained in:
Lucas Lefèvre
2019-03-19 09:35:31 +01:00
committed by Yannick Tivisse
parent 29633756b0
commit 74246d288f
+33 -14
View File
@@ -85,20 +85,39 @@ class HrWorkEnrty(models.Model):
@api.model
def _mark_conflicting_work_entries(self, start, stop):
conflict = False
domain = [
('date_start', '<', stop),
('date_stop', '>', start),
]
benefs = self.search(domain)
work_entries_by_employee = itertools.groupby(benefs, lambda b: b.employee_id)
for employee, benefs in work_entries_by_employee:
intervals = Intervals(intervals=((b.date_start, b.date_stop, b) for b in benefs))
for interval in intervals:
if len(interval[2]) > 1:
interval[2].write({'display_warning': True})
conflict = True
return conflict
"""
Set `display_warning` to True for overlapping benefits
between two dates.
Return True if overlapping benefits were detected.
"""
# Use the postgresql range type `tsrange` which is a range of timestamp
# It supports the intersection operator (&&) useful to detect overlap.
# use '()' to exlude the lower and upper bounds of the range.
# Filter on date_start and date_stop (both indexed) in the EXISTS clause to
# limit the resulting set size and fasten the query.
query = """
SELECT b1.id
FROM hr_work_entry b1
WHERE
b1.date_start <= %s
AND b1.date_stop >= %s
AND EXISTS (
SELECT 1
FROM hr_work_entry b2
WHERE
b2.date_start <= %s
AND b2.date_stop >= %s
AND tsrange(b1.date_start, b1.date_stop, '()') && tsrange(b2.date_start, b2.date_stop, '()')
AND b1.id <> b2.id
AND b1.employee_id = b2.employee_id
);
"""
self.env.cr.execute(query, (stop, start, stop, start))
conflicts = [res.get('id') for res in self.env.cr.dictfetchall()]
self.browse(conflicts).write({
'display_warning': True,
})
return bool(conflicts)
@api.multi
def _compute_conflicts_leaves_to_approve(self):