Odoo Hr_attendance: Overtime Bug & Fix
Hey guys! Let's dive into a pesky bug I found in Odoo's hr_attendance module, specifically in version 18.0. It's about how overtime records get created when employees check out. This can lead to a database error, so let's break down the problem, how to reproduce it, and most importantly, how to fix it! I'll also explain the root cause and share a code patch to solve the issue. So, if you are looking for Odoo, overtime, bug fixing or hr_attendance modules, then you are in the right place.
The Overtime Conundrum: Duplicate Records
Odoo's hr_attendance module is essential for managing employee attendance and calculating overtime. However, a bug can occur when employees check out. The system sometimes tries to create duplicate overtime records, causing a psycopg2.errors.UniqueViolation error. This error essentially means that the database is rejecting the creation of a record because a similar record already exists, violating a unique constraint. This can be annoying, as it prevents the employee from checking out, and also leads to incorrect overtime calculations.
This issue mainly happens in Odoo 18.0 (Community Edition), although, it might be possible that it also happens in other versions or editions. The core of the problem lies within the _update_overtime method of the hr.attendance model. This method is the brains behind the overtime calculation and creation process. The bug is often triggered when several attendance records are processed close together, as in an API call or during a high-volume checkout scenario. For those of you who're more technically inclined, it's a race condition. Let's see how this occurs!
Steps to Reproduce the Issue
Reproducing this bug might seem tricky because it depends on the timing and the particular data in your system. But, I've outlined the steps to make it easier for you:
- Install the Module: First, make sure you have the
hr_attendancemodule installed in your Odoo 18.0 instance. - Create an Employee: Set up an employee record in Odoo.
- Create Attendance Records: Make sure the employee has one or more existing attendance records on a particular day.
- Trigger Checkout: Programmatically trigger a checkout for the employee on the same day. This step is where the bug is most likely to show its head, especially if you have multiple attendance records for the same day.
Following these steps, you should be able to reproduce the error. If you're a developer, you can also use an API call to trigger the checkout. If you are experiencing issues with the Odoo hr_attendance module, then this process might help you find the problem.
Unveiling the Error: Observed Behavior
When the bug surfaces, the system throws a psycopg2.errors.UniqueViolation error. This is a PostgreSQL error that stops the database transaction, preventing the checkout from being completed. You'll see this error in the Odoo server log, and it will look something like this:
PostgreSQL Error:
ERROR: duplicate key value violates unique constraint "hr_attendance_overtime_unique_employee_per_day"
DETAIL: Key (employee_id, date)=(825, 2025-12-04) already exists.
Odoo Server Error:
psycopg2.errors.InFailedSqlTransaction: current transaction is aborted, commands ignored until end of transaction block
This error indicates that the database is trying to add an overtime record that already exists. This will also result in a failed checkout. As a result, overtime won't be calculated accurately, and the employee might not be able to complete their checkout process. If you notice any of these logs in your server log, then it means that you might be facing this issue.
The Expected Outcome: What Should Happen
When everything is working correctly, the checkout process should finish without any errors. The overtime should be calculated accurately, and the system should store the information properly. The system should not throw any database constraint violations. Basically, the system should smoothly process the checkout, calculate the overtime correctly, and store the appropriate overtime records in the database.
The Root of the Problem: Root Cause Analysis
The heart of the problem lies in the _update_overtime method of the hr.attendance model. The method calculates overtime records. The logic fetches existing overtime records, but when it processes several attendance records for the same day, inside a single transaction, the method can add multiple overtime records for the same employee and date. This leads to attempts to insert the same record, which violates the database's unique constraint.
The critical error here is that the method does not account for the records that are pending within the same transaction. This gap in the logic allows duplicate records to sneak into the creation list, triggering the error. This is a classic race condition, which is a common type of bug that occurs when multiple processes try to access and modify the same data. This is what you should focus on to fix the issue.
Fixing the Bug: Recommended Patch
To resolve this issue, you need to modify the _update_overtime method. The fix involves tracking overtime records scheduled for creation within the current transaction. This prevents duplicate entries from being added to the creation list. Here's the code patch you can apply to addons/hr_attendance/models/hr_attendance.py:
def _update_overtime(self, employee_attendance_dates=None):
if employee_attendance_dates is None:
employee_attendance_dates = self._get_attendances_dates()
overtime_to_unlink = self.env['hr.attendance.overtime']
overtime_vals_list = []
overtime_to_create = set()
affected_employees = self.env['hr.employee']
for emp, attendance_dates in employee_attendance_dates.items():
# ... (rest of the method is unchanged until the loop over day_data)
for day_data in attendance_dates:
# ... (rest of the loop is unchanged until the creation logic)
overtime = overtimes.filtered(lambda o: o.date == attendance_date)
if not float_is_zero(overtime_duration, 2) or unfinished_shifts:
# Do not create if any attendance doesn't have a check_out, update if exists
if unfinished_shifts:
overtime_duration = 0
if not overtime and overtime_duration:
key = (emp.id, attendance_date)
if key not in overtime_to_create:
overtime_vals_list.append({
'employee_id': emp.id,
'date': attendance_date,
'duration': overtime_duration,
'duration_real': overtime_duration_real,
})
overtime_to_create.add(key)
elif overtime:
overtime.sudo().write({
'duration': overtime_duration,
'duration_real': overtime_duration
})
affected_employees |= overtime.employee_id
elif overtime:
overtime_to_unlink |= overtime
# ... (rest of the method is unchanged)
By introducing the overtime_to_create set, the method ensures that only one overtime record per employee per day is added. This simple change eliminates the race condition and resolves the bug. If you are having issues with Odoo's hr_attendance module, then this is the perfect solution for you.
Conclusion
I hope this guide helps you. This bug can be frustrating, but with the fix, your overtime calculations should work seamlessly. Remember to back up your system before applying any patches and always test them in a non-production environment first. If you have any questions or need further assistance, don't hesitate to ask! Thanks for reading, and happy coding!