19.0 vanilla

This commit is contained in:
Ernad Husremovic 2026-03-25 12:00:11 +01:00
parent e1d89e11e3
commit a1f02d8cc7
225 changed files with 2335 additions and 775 deletions

View file

@ -182,8 +182,8 @@ class HrAttendance(models.Model):
calendar = self._get_employee_calendar()
resource = self.employee_id.resource_id
tz = timezone(resource.tz) if not calendar else timezone(calendar.tz)
start_dt_tz = max(self.check_in, start_dt).astimezone(tz)
end_dt_tz = min(self.check_out, end_dt).astimezone(tz)
start_dt_tz = utc.localize(max(self.check_in, start_dt)).astimezone(tz)
end_dt_tz = utc.localize(min(self.check_out, end_dt)).astimezone(tz)
if end_dt_tz < start_dt_tz:
return 0.0
@ -268,11 +268,19 @@ class HrAttendance(models.Model):
def _get_overtimes_to_update_domain(self):
if not self:
return Domain.FALSE
domain_list = [Domain.AND([
Domain('employee_id', '=', employee.id),
Domain('date', '<=', max(attendances.mapped('check_out')).date() + relativedelta(SU)),
Domain('date', '>=', min(attendances.mapped('check_in')).date() + relativedelta(MO(-1))),
]) for employee, attendances in self.filtered(lambda att: att.check_out).grouped('employee_id').items()]
domain_list = []
for employee, attendances in self.filtered(lambda att: att.check_out).grouped('employee_id').items():
tz = timezone(employee._get_tz())
local_check_in = utc.localize(min(attendances.mapped('check_in'))).astimezone(tz)
local_check_out = utc.localize(max(attendances.mapped('check_out'))).astimezone(tz)
date_from = local_check_in.date() + relativedelta(weekday=MO(-1))
date_to = local_check_out.date() + relativedelta(weekday=SU)
domain_list.append(Domain.AND([
Domain('employee_id', '=', employee.id),
Domain('date', '<=', date_to),
Domain('date', '>=', date_from),
]))
if not domain_list:
return Domain.FALSE
return Domain.OR(domain_list) if len(domain_list) > 1 else domain_list[0]
@ -280,7 +288,11 @@ class HrAttendance(models.Model):
def _update_overtime(self, attendance_domain=None):
if not attendance_domain:
attendance_domain = self._get_overtimes_to_update_domain()
self.env['hr.attendance.overtime.line'].search(attendance_domain).unlink()
all_overtime_lines = self.env['hr.attendance.overtime.line'].search(attendance_domain)
manual_overtimes = set(all_overtime_lines.filtered(
lambda l: l.manual_duration != l.duration or l.status == 'to_approve'
).mapped(lambda l: (l.employee_id.id, l.date)))
all_overtime_lines.unlink()
all_attendances = (self | self.env['hr.attendance'].search(attendance_domain)).filtered_domain([('check_out', '!=', False)])
if not all_attendances:
return
@ -305,9 +317,13 @@ class HrAttendance(models.Model):
overtime_vals_list = []
for ruleset_sudo, ruleset_attendances in attendances_by_ruleset.items():
attendances_dates = list(chain(*ruleset_attendances._get_dates().values()))
overtime_vals_list.extend(
ruleset_sudo.rule_ids._generate_overtime_vals_v2(min(attendances_dates), max(attendances_dates), ruleset_attendances, schedules_intervals_by_employee)
)
overtime_vals_list.extend([
{
**val,
'status': 'to_approve'
} if (val['employee_id'], val['date']) in manual_overtimes else val
for val in ruleset_sudo.rule_ids._generate_overtime_vals_v2(min(attendances_dates), max(attendances_dates), ruleset_attendances, schedules_intervals_by_employee)
])
self.env['hr.attendance.overtime.line'].create(overtime_vals_list)
self.env.add_to_compute(self._fields['overtime_hours'], all_attendances)
self.env.add_to_compute(self._fields['validated_overtime_hours'], all_attendances)

View file

@ -82,7 +82,10 @@ class HrEmployee(models.Model):
def _compute_total_overtime(self):
mapped_validated_overtimes = dict(
self.env['hr.attendance.overtime.line']._read_group(
domain=[('status', '=', 'approved')],
domain=[
('status', '=', 'approved'),
('employee_id', 'in', self.ids),
],
groupby=['employee_id'],
aggregates=['manual_duration:sum']
))
@ -96,59 +99,66 @@ class HrEmployee(models.Model):
"""
now = fields.Datetime.now()
now_utc = pytz.utc.localize(now)
for employee in self:
tz = pytz.timezone(employee.tz or 'UTC')
for timezone, employees in self.grouped('tz').items():
tz = pytz.timezone(timezone or 'UTC')
now_tz = now_utc.astimezone(tz)
start_tz = now_tz.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
start_naive = start_tz.astimezone(pytz.utc).replace(tzinfo=None)
end_tz = now_tz
end_naive = end_tz.astimezone(pytz.utc).replace(tzinfo=None)
current_month_attendances = employee.attendance_ids.filtered(
lambda att: att.check_in >= start_naive and att.check_out and att.check_out <= end_naive
)
hours = 0
overtime_hours = 0
for att in current_month_attendances:
hours += att.worked_hours or 0
overtime_hours += att.validated_overtime_hours or 0
employee.hours_last_month = round(hours, 2)
employee.hours_last_month_display = "%g" % employee.hours_last_month
# overtime_adjustments = sum(
# ot.duration or 0
# for ot in employee.overtime_ids.filtered(
# lambda ot: ot.date >= start_tz.date() and ot.date <= end_tz.date() and ot.adjustment
# )
# )
employee.hours_last_month_overtime = round(overtime_hours, 2)
for employee in employees:
current_month_attendances = employee.attendance_ids.filtered(
lambda att: att.check_in >= start_naive and att.check_out and att.check_out <= end_naive
)
hours = 0
overtime_hours = 0
for att in current_month_attendances:
hours += att.worked_hours or 0
overtime_hours += att.validated_overtime_hours or 0
employee.hours_last_month = round(hours, 2)
employee.hours_last_month_display = "%g" % employee.hours_last_month
# overtime_adjustments = sum(
# ot.duration or 0
# for ot in employee.overtime_ids.filtered(
# lambda ot: ot.date >= start_tz.date() and ot.date <= end_tz.date() and ot.adjustment
# )
# )
employee.hours_last_month_overtime = round(overtime_hours, 2)
def _compute_hours_today(self):
now = fields.Datetime.now()
now_utc = pytz.utc.localize(now)
for employee in self:
for timezone, employees in self.grouped('tz').items():
# start of day in the employee's timezone might be the previous day in utc
tz = pytz.timezone(employee.tz)
now_tz = now_utc.astimezone(tz)
start_tz = now_tz + relativedelta(hour=0, minute=0) # day start in the employee's timezone
tz = pytz.timezone(timezone or 'UTC')
start_tz = now_utc.astimezone(tz) + relativedelta(hour=0, minute=0) # day start in the employee's timezone
start_naive = start_tz.astimezone(pytz.utc).replace(tzinfo=None)
attendances = self.env['hr.attendance'].search([
('employee_id', 'in', employee.ids),
('check_in', '<=', now),
'|', ('check_out', '>=', start_naive), ('check_out', '=', False),
], order='check_in asc')
hours_previously_today = 0
worked_hours = 0
attendance_worked_hours = 0
for attendance in attendances:
delta = (attendance.check_out or now) - max(attendance.check_in, start_naive)
attendance_worked_hours = delta.total_seconds() / 3600.0
worked_hours += attendance_worked_hours
hours_previously_today += attendance_worked_hours
employee.last_attendance_worked_hours = attendance_worked_hours
hours_previously_today -= attendance_worked_hours
employee.hours_previously_today = hours_previously_today
employee.hours_today = worked_hours
attendances_by_employee = dict(self.env['hr.attendance']._read_group(
[
('employee_id', 'in', employees.ids),
('check_in', '<=', now),
'|', ('check_out', '>=', start_naive), ('check_out', '=', False),
],
['employee_id'],
['id:recordset'],
))
for employee in employees:
attendances = attendances_by_employee.get(employee, self.env['hr.attendance'])
hours_previously_today = 0
worked_hours = 0
attendance_worked_hours = 0
for attendance in attendances:
delta = (attendance.check_out or now) - max(attendance.check_in, start_naive)
attendance_worked_hours = delta.total_seconds() / 3600.0
worked_hours += attendance_worked_hours
hours_previously_today += attendance_worked_hours
employee.last_attendance_worked_hours = attendance_worked_hours
hours_previously_today -= attendance_worked_hours
employee.hours_previously_today = hours_previously_today
employee.hours_today = worked_hours
@api.depends('attendance_ids')
def _compute_last_attendance_id(self):