mirror of
https://gitlab.com/hibou-io/hibou-odoo/suite.git
synced 2025-01-20 12:37:31 +02:00
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:
2
hr_payroll_attendance/__init__.py
Executable file
2
hr_payroll_attendance/__init__.py
Executable file
@@ -0,0 +1,2 @@
|
||||
from . import hr_payslip
|
||||
from . import hr_contract
|
||||
16
hr_payroll_attendance/__manifest__.py
Executable file
16
hr_payroll_attendance/__manifest__.py
Executable 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',
|
||||
],
|
||||
}
|
||||
7
hr_payroll_attendance/hr_contract.py
Executable file
7
hr_payroll_attendance/hr_contract.py
Executable 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)
|
||||
22
hr_payroll_attendance/hr_contract_view.xml
Executable file
22
hr_payroll_attendance/hr_contract_view.xml
Executable 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>
|
||||
93
hr_payroll_attendance/hr_payslip.py
Executable file
93
hr_payroll_attendance/hr_payslip.py
Executable 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
|
||||
1
hr_payroll_attendance_holidays/__init__.py
Executable file
1
hr_payroll_attendance_holidays/__init__.py
Executable file
@@ -0,0 +1 @@
|
||||
from . import hr_payslip
|
||||
15
hr_payroll_attendance_holidays/__manifest__.py
Executable file
15
hr_payroll_attendance_holidays/__manifest__.py
Executable 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,
|
||||
}
|
||||
52
hr_payroll_attendance_holidays/hr_payslip.py
Executable file
52
hr_payroll_attendance_holidays/hr_payslip.py
Executable 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(' ', '_')
|
||||
@@ -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.
|
||||
""",
|
||||
|
||||
Reference in New Issue
Block a user