From e12ec1290a3f967a295ebae0fc95989fa1fb3de3 Mon Sep 17 00:00:00 2001 From: Jared Kipe Date: Wed, 9 May 2018 16:01:49 -0700 Subject: [PATCH] Proper migration to 11.0, and implementation of 'overtime rules' breakdown of hours/weeks. --- hr_payroll_timesheet/hr_contract_view.xml | 2 +- hr_payroll_timesheet/hr_payslip.py | 91 ++++++++++++++++------- 2 files changed, 66 insertions(+), 27 deletions(-) diff --git a/hr_payroll_timesheet/hr_contract_view.xml b/hr_payroll_timesheet/hr_contract_view.xml index abd831f5..6b8edde4 100755 --- a/hr_payroll_timesheet/hr_contract_view.xml +++ b/hr_payroll_timesheet/hr_contract_view.xml @@ -8,7 +8,7 @@ - + diff --git a/hr_payroll_timesheet/hr_payslip.py b/hr_payroll_timesheet/hr_payslip.py index 5d09c267..b320495b 100755 --- a/hr_payroll_timesheet/hr_payslip.py +++ b/hr_payroll_timesheet/hr_payslip.py @@ -1,5 +1,8 @@ -# -*- coding: utf-8 -*- +from datetime import datetime +from collections import defaultdict from odoo import api, models +from odoo.exceptions import ValidationError +from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT class HrPayslip(models.Model): @@ -7,44 +10,80 @@ class HrPayslip(models.Model): @api.model def get_worked_day_lines(self, contracts, date_from, date_to): - def create_empty_worked_lines(employee_id, contract_id, date_from, date_to): - attendance = { - 'name': 'Timesheet Attendance', + def create_empty_worked_lines(employee, contract, date_from, date_to): + attn = { + 'name': 'Attendance', 'sequence': 10, 'code': 'ATTN', 'number_of_days': 0.0, 'number_of_hours': 0.0, - 'contract_id': contract_id, + 'contract_id': contract.id, } - valid_days = [ - ('sheet_id.employee_id', '=', employee_id), - ('sheet_id.state', '=', 'done'), - ('sheet_id.date_from', '>=', date_from), - ('sheet_id.date_to', '<=', date_to), + valid_attn = [ + ('employee_id', '=', employee.id), + ('check_in', '>=', date_from), + ('check_in', '<=', date_to), ] - return attendance, valid_days - - attendances = [] + return attn, valid_attn + work = [] for contract in contracts: - attendance, valid_days = create_empty_worked_lines( - contract.employee_id.id, - contract.id, + worked_attn, valid_attn = create_empty_worked_lines( + contract.employee_id, + contract, date_from, date_to ) - - for day in self.env['hr_timesheet_sheet.sheet.day'].search(valid_days): - if day.total_attendance >= 0.0: - attendance['number_of_days'] += 1 - attendance['number_of_hours'] += day.total_attendance - - # needed so that the shown hours matches any calculations you use them for - attendance['number_of_hours'] = round(attendance['number_of_hours'], 2) - attendances.append(attendance) + days = set() + for attn in self.env['hr.attendance'].search(valid_attn): + if not attn.check_out: + raise ValidationError('This pay period must not have any open attendances.') + if attn.worked_hours: + # Avoid in/outs + attn_start_time = datetime.strptime(attn.check_in, DEFAULT_SERVER_DATETIME_FORMAT) + attn_iso = attn_start_time.isocalendar() + if not attn_iso in days: + worked_attn['number_of_days'] += 1 + days.add(attn_iso) + worked_attn['number_of_hours'] += attn.worked_hours + worked_attn['number_of_hours'] = round(worked_attn['number_of_hours'], 2) + work.append(worked_attn) res = super(HrPayslip, self).get_worked_day_lines(contracts, date_from, date_to) - res.extend(attendances) + res.extend(work) return res + @api.multi + def hour_break_down(self, code): + """ + :param code: what kind of worked days you need aggregated + :return: dict: keys are isocalendar tuples, values are hours. + """ + self.ensure_one() + if code == 'ATTN': + attns = self.env['hr.attendance'].search([ + ('employee_id', '=', self.employee_id.id), + ('check_in', '>=', self.date_from), + ('check_in', '<=', self.date_to), + ]) + day_values = defaultdict(float) + for attn in attns: + if not attn.check_out: + raise ValidationError('This pay period must not have any open attendances.') + if attn.worked_hours: + # Avoid in/outs + attn_start_time = datetime.strptime(attn.check_in, DEFAULT_SERVER_DATETIME_FORMAT) + attn_iso = attn_start_time.isocalendar() + day_values[attn_iso] += attn.worked_hours + return day_values + elif hasattr(super(HrPayslip, self), 'hours'): + return super(HrPayslip, self).hours(code) + + @api.multi + def hours_break_down_week(self, code): + days = self.hour_break_down(code) + weeks = defaultdict(float) + for isoday, hours in days.items(): + weeks[isoday[1]] += hours + return weeks