Initial commit of hr_payroll_attendance and hr_payroll_attendance_holidays modules for 11.0.

The purpose of this new functionality is to better distinguish between timesheets and attendance, as well as preventing the stock "salaried" time computations from working.
This commit is contained in:
Jared Kipe
2018-05-14 16:08:30 -07:00
parent 8137950756
commit 1dea2b7711
9 changed files with 209 additions and 1 deletions

View File

@@ -0,0 +1,2 @@
from . import hr_payslip
from . import hr_contract

View File

@@ -0,0 +1,16 @@
{
'name': 'Attendance on Payslips',
'description': 'Get Attendence numbers onto Employee Payslips.',
'version': '11.0.1.0.0',
'website': 'https://hibou.io/',
'author': 'Hibou Corp. <hello@hibou.io>',
'license': 'AGPL-3',
'category': 'Human Resources',
'data': [
'hr_contract_view.xml',
],
'depends': [
'hr_payroll',
'hr_attendance',
],
}

View File

@@ -0,0 +1,7 @@
from odoo import models, fields
class HrContract(models.Model):
_inherit = 'hr.contract'
paid_hourly_attendance = fields.Boolean(string="Paid Hourly Attendance", default=False)

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="hr_contract_form_inherit" model="ir.ui.view">
<field name="name">hr.contract.form.inherit</field>
<field name="model">hr.contract</field>
<field name="priority">20</field>
<field name="inherit_id" ref="hr_contract.hr_contract_view_form"/>
<field name="arch" type="xml">
<data>
<xpath expr="//field[@name='advantages']" position="after">
<field name="paid_hourly_attendance"/>
</xpath>
<xpath expr="//div[@name='wage']/span" position="replace">
<span attrs="{'invisible': [('paid_hourly_attendance', '=', True)]}">/ pay period</span>
<span attrs="{'invisible': [('paid_hourly_attendance', '=', False)]}">/ hour</span>
</xpath>
</data>
</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,93 @@
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):
_inherit = 'hr.payslip'
@api.model
def get_worked_day_lines(self, contracts, date_from, date_to):
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,
}
valid_attn = [
('employee_id', '=', employee.id),
('check_in', '>=', date_from),
('check_in', '<=', date_to),
]
return attn, valid_attn
work = []
for contract in contracts.filtered(lambda c: c.paid_hourly_attendance):
worked_attn, valid_attn = create_empty_worked_lines(
contract.employee_id,
contract,
date_from,
date_to
)
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.filtered(lambda c: not c.paid_hourly_attendance), date_from, date_to)
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), 'hour_break_down'):
return super(HrPayslip, self).hour_break_down(code)
@api.multi
def hours_break_down_week(self, code):
"""
:param code: hat kind of worked days you need aggregated
:return: dict: keys are isocalendar weeks, values are hours.
"""
days = self.hour_break_down(code)
weeks = defaultdict(float)
for isoday, hours in days.items():
weeks[isoday[1]] += hours
return weeks

View File

@@ -0,0 +1 @@
from . import hr_payslip

View File

@@ -0,0 +1,15 @@
{
'name': 'Payroll Attendance Holidays',
'author': 'Hibou Corp. <hello@hibou.io>',
'version': '11.0.1.0.0',
'category': 'Human Resources',
'sequence': 95,
'summary': 'Holiday Pay',
'description': """
Simplifies getting approved Holiday Leaves onto an employee Payslip.
""",
'website': 'https://hibou.io/',
'depends': ['hr_payroll_attendance', 'hr_holidays'],
'installable': True,
'application': False,
}

View File

@@ -0,0 +1,52 @@
from odoo import models, api
from odoo.addons.hr_holidays.models.hr_holidays import HOURS_PER_DAY
class HrPayslip(models.Model):
_inherit = 'hr.payslip'
@api.model
def get_worked_day_lines(self, contracts, date_from, date_to):
leaves = {}
for contract in contracts.filtered(lambda c: c.paid_hourly_attendance):
for leave in self._fetch_valid_leaves(contract.employee_id.id, date_from, date_to):
leave_code = self._create_leave_code(leave.holiday_status_id.name)
if leave_code in leaves:
leaves[leave_code]['number_of_days'] += leave.number_of_days_temp
leaves[leave_code]['number_of_hours'] += leave.number_of_days_temp * HOURS_PER_DAY
else:
leaves[leave_code] = {
'name': leave.holiday_status_id.name,
'sequence': 15,
'code': leave_code,
'number_of_days': leave.number_of_days_temp,
'number_of_hours': leave.number_of_days_temp * HOURS_PER_DAY,
'contract_id': contract.id,
}
res = super(HrPayslip, self).get_worked_day_lines(contracts.filtered(lambda c: not c.paid_hourly_attendance), date_from, date_to)
res.extend(leaves.values())
return res
@api.multi
def action_payslip_done(self):
for slip in self.filtered(lambda s: s.contract_id.paid_hourly_attendance):
leaves = self._fetch_valid_leaves(slip.employee_id.id, slip.date_from, slip.date_to)
leaves.write({'payslip_status': True})
return super(HrPayslip, self).action_payslip_done()
def _fetch_valid_leaves(self, employee_id, date_from, date_to):
valid_leaves = [
('employee_id', '=', employee_id),
('state', '=', 'validate'),
('date_from', '>=', date_from),
('date_from', '<=', date_to),
('payslip_status', '=', False),
('type', '=', 'remove'),
]
return self.env['hr.holidays'].search(valid_leaves)
def _create_leave_code(self, name):
return 'L_' + name.replace(' ', '_')

View File

@@ -6,7 +6,7 @@
'version': '11.0.0.0.0',
'category': 'Human Resources',
'sequence': 95,
'summary': 'Register payments for Payroll Payslips',
'summary': 'Holiday Pay',
'description': """
Simplifies getting approved Holiday Leaves onto an employee Payslip.
""",