[IMP] hr_payroll: add multi contract support

Purpose
=======

We would like to be able to manage multi contracts payslips and contract transitions.

Specification
=============

General flow:
1/ Benefits are generated based on contracts's calendar.
2/ When validating additionnal benefits, calendar.attendances are created in the contract's calendar
3/ Create one payslip per contract in state [open|pending|close]
4/ For each payslip, generate worked day lines based on the contract's calendar

New Constraints:
1/ An employee cannot have more than one contract at the same time (excluding draft, closed and cancelled contracts).
2/ A leave cannot overlap multiple contracts.
3/ A contract cannot have starting and ending dates such that a leave become in the middle of
   two contacts.

New state for a contract: Incoming. It means it's ready and will be running soon.
An incoming contract automatically becomes 'Running' if the start date is passed (cron)

Number of hours and days are always correct (according to the contract's calendar) on payslips as the
flow starts from the contract's calendar.
However, it is not correct in the Time Off app because it's counted based on the employee's calendar.
The number of days/hours for hr.leaves should be computed according to
the contract at the time of the leave.
e.g. If I currently work full time and I request 2 weeks off next month where I'll be working only
3 days/week and 5hours/day (other contract), It should count 6 days and 30 hours.

- in hr.benefits form view show the contract (below employee field)

TaskID: 1903483

closes odoo/odoo#30181
This commit is contained in:
Lucas Lefèvre
2019-02-01 12:16:54 +00:00
committed by Yannick Tivisse
parent f5bca867ac
commit 20bc2bbab5
20 changed files with 612 additions and 86 deletions
+2 -2
View File
@@ -11,7 +11,7 @@
<field name="department_id" model="hr.department"
eval="obj().env.ref('hr.employee_admin').department_id.id"/>
<field eval="7540.0" name="wage"/>
<field name="state">open</field>
<field name="state">draft</field>
</record>
<record id="hr_contract_al" model="hr.contract">
@@ -64,7 +64,7 @@
<field name="department_id" model="hr.department"
eval="obj().env.ref('hr.employee_qdp').department_id.id"/>
<field eval="3750.0" name="wage"/>
<field name="state">open</field>
<field name="state">draft</field>
</record>
<record id="hr_contract_han" model="hr.contract">
+42 -13
View File
@@ -16,15 +16,9 @@ class Employee(models.Model):
medic_exam = fields.Date(string='Medical Examination Date', groups="hr.group_hr_user")
vehicle = fields.Char(string='Company Vehicle', groups="hr.group_hr_user")
contract_ids = fields.One2many('hr.contract', 'employee_id', string='Employee Contracts')
contract_id = fields.Many2one('hr.contract', compute='_compute_contract_id', string='Current Contract', help='Latest contract of the employee')
contract_id = fields.Many2one('hr.contract', string='Current Contract', help='Current contract of the employee')
contracts_count = fields.Integer(compute='_compute_contracts_count', string='Contract Count')
def _compute_contract_id(self):
""" get the lastest contract """
Contract = self.env['hr.contract']
for employee in self:
employee.contract_id = Contract.search([('employee_id', '=', employee.id)], order='date_start desc', limit=1)
def _compute_contracts_count(self):
# read_group as sudo, since contract count is displayed on form view
contract_data = self.env['hr.contract'].sudo().read_group([('employee_id', 'in', self.ids)], ['employee_id'], ['employee_id'])
@@ -32,25 +26,28 @@ class Employee(models.Model):
for employee in self:
employee.contracts_count = result.get(employee.id, 0)
def _get_contracts(self, date_from, date_to):
def _get_contracts(self, date_from, date_to, states=['open', 'pending']):
"""
Returns the contracts of the employee between date_from and date_to
"""
# a contract is valid if it ends between the given dates
if not self:
employees = self.env['hr.employee'].search([])
else:
employees = self
clause_1 = ['&', ('date_end', '<=', date_to), ('date_end', '>=', date_from)]
# OR if it starts between the given dates
clause_2 = ['&', ('date_start', '<=', date_to), ('date_start', '>=', date_from)]
# OR if it starts before the date_from and finish after the date_end (or never finish)
clause_3 = ['&', ('date_start', '<=', date_from), '|', ('date_end', '=', False), ('date_end', '>=', date_to)]
clause_final = expression.AND([
[('employee_id', 'in', employees.ids), ('state', '=', 'open')],
[('employee_id', 'in', self.ids), ('state', 'in', states)],
expression.OR([clause_1, clause_2, clause_3])])
return self.env['hr.contract'].search(clause_final)
@api.model
def _get_all_contracts(self, date_from, date_to, states=['open', 'pending']):
"""
Returns the contracts of all employees between date_from and date_to
"""
return self.search([])._get_contracts(date_from, date_to, states=states)
class ContractType(models.Model):
@@ -88,6 +85,7 @@ class Contract(models.Model):
notes = fields.Text('Notes')
state = fields.Selection([
('draft', 'New'),
('incoming', 'Incoming'),
('open', 'Running'),
('pending', 'To Renew'),
('close', 'Expired'),
@@ -112,6 +110,27 @@ class Contract(models.Model):
self.department_id = self.employee_id.department_id
self.resource_calendar_id = self.employee_id.resource_calendar_id
@api.constrains('employee_id', 'state', 'date_start', 'date_end')
def _check_current_contract(self):
""" Two contracts in state [incoming | pending | open | close] cannot overlap """
for contract in self.filtered(lambda c: c.state not in ['draft', 'cancel']):
domain = [
('id', '!=', contract.id),
('employee_id', '=', contract.employee_id.id),
('state', 'in', ['incoming', 'pending', 'open', 'close']),
]
if not contract.date_end:
start_domain = []
end_domain = ['|', ('date_end', '>=', contract.date_start), ('date_end', '=', False)]
else:
start_domain = [('date_start', '<=', contract.date_end)]
end_domain = ['|', ('date_end', '>', contract.date_start), ('date_end', '=', False)]
domain = expression.AND([domain, start_domain, end_domain])
if self.search_count(domain):
raise ValidationError(_('An employee can only have one contract at the same time. (Excluding Draft and Cancelled contracts)'))
@api.constrains('date_start', 'date_end')
def _check_dates(self):
if self.filtered(lambda c: c.date_end and c.date_start > c.date_end):
@@ -141,8 +160,18 @@ class Contract(models.Model):
'state': 'close'
})
self.search([('state', '=', 'incoming'), ('date_start', '<=', fields.Date.to_string(date.today())),]).write({
'state': 'open'
})
return True
@api.multi
def write(self, vals):
if vals.get('state') == 'open':
for contract in self:
contract.employee_id.contract_id = contract
return super(Contract, self).write(vals)
@api.multi
def _track_subtype(self, init_values):
self.ensure_one()
+1
View File
@@ -2,3 +2,4 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import test_auto_status
from . import test_contract
+12 -1
View File
@@ -10,7 +10,12 @@ class TestHrContracts(TransactionCase):
def setUp(self):
super(TestHrContracts, self).setUp()
self.contracts = self.env['hr.contract'].with_context(tracking_disable=True)
self.employee = self.env.ref('hr.employee_admin')
self.employee = self.env['hr.employee'].create({
'name': 'Richard',
'gender': 'male',
'birthday': '1984-05-01',
'country_id': self.ref('base.be'),
})
self.test_contract = dict(name='Test', wage=1, employee_id=self.employee.id, state='open')
def apply_cron(self):
@@ -51,3 +56,9 @@ class TestHrContracts(TransactionCase):
self.contract.write(self.test_contract)
self.apply_cron()
self.assertEquals(self.contract.state, 'close')
def test_contract_start_date(self):
self.test_contract.update(dict(date_start=datetime.now(), state='incoming'))
self.contract = self.contracts.create(self.test_contract)
self.apply_cron()
self.assertEquals(self.contract.state, 'open')
+86
View File
@@ -0,0 +1,86 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import datetime
from odoo.tests.common import TransactionCase
from odoo.exceptions import ValidationError
from dateutil.relativedelta import relativedelta
class TestHrContracts(TransactionCase):
def setUp(self):
super(TestHrContracts, self).setUp()
self.contracts = self.env['hr.contract'].with_context(tracking_disable=True)
self.employee = self.env['hr.employee'].create({
'name': 'Richard',
'gender': 'male',
'birthday': '1984-05-01',
'country_id': self.ref('base.be'),
})
def create_contract(self, state, start, end=None):
return self.env['hr.contract'].create({
'name': 'Contract',
'employee_id': self.employee.id,
'state': state,
'wage': 1,
'date_start': start,
'date_end': end,
})
def test_incoming_overlapping_contract(self):
start = datetime.strptime('2015-11-01', '%Y-%m-%d').date()
end = datetime.strptime('2015-11-30', '%Y-%m-%d').date()
self.create_contract('open', start, end)
# Incoming contract
with self.assertRaises(ValidationError, msg="It should not create two contract in state open or incoming"):
start = datetime.strptime('2015-11-15', '%Y-%m-%d').date()
end = datetime.strptime('2015-12-30', '%Y-%m-%d').date()
self.create_contract('incoming', start, end)
def test_pending_overlapping_contract(self):
start = datetime.strptime('2015-11-01', '%Y-%m-%d').date()
end = datetime.strptime('2015-11-30', '%Y-%m-%d').date()
self.create_contract('open', start, end)
# Pending contract
with self.assertRaises(ValidationError, msg="It should not create two contract in state open or pending"):
start = datetime.strptime('2015-11-15', '%Y-%m-%d').date()
end = datetime.strptime('2015-12-30', '%Y-%m-%d').date()
self.create_contract('pending', start, end)
# Draft contract -> should not raise
start = datetime.strptime('2015-11-15', '%Y-%m-%d').date()
end = datetime.strptime('2015-12-30', '%Y-%m-%d').date()
self.create_contract('draft', start, end)
def test_draft_overlapping_contract(self):
start = datetime.strptime('2015-11-01', '%Y-%m-%d').date()
end = datetime.strptime('2015-11-30', '%Y-%m-%d').date()
self.create_contract('open', start, end)
# Draft contract -> should not raise even if overlapping
start = datetime.strptime('2015-11-15', '%Y-%m-%d').date()
end = datetime.strptime('2015-12-30', '%Y-%m-%d').date()
self.create_contract('draft', start, end)
def test_overlapping_contract_no_end(self):
# No end date
self.create_contract('open', datetime.strptime('2015-11-01', '%Y-%m-%d').date())
with self.assertRaises(ValidationError):
start = datetime.strptime('2015-11-15', '%Y-%m-%d').date()
end = datetime.strptime('2015-12-30', '%Y-%m-%d').date()
self.create_contract('incoming', start, end)
def test_overlapping_contract_no_end2(self):
start = datetime.strptime('2015-11-01', '%Y-%m-%d').date()
end = datetime.strptime('2015-12-30', '%Y-%m-%d').date()
self.create_contract('open', start, end)
with self.assertRaises(ValidationError):
# No end
self.create_contract('incoming', datetime.strptime('2015-01-01', '%Y-%m-%d').date())
+3 -1
View File
@@ -21,6 +21,7 @@ class HrBenefit(models.Model):
date_start = fields.Datetime(required=True, string='From')
date_stop = fields.Datetime(string='To')
duration = fields.Float(compute='_compute_duration', inverse='_inverse_duration', store=True, string="Period")
contract_id = fields.Many2one('hr.contract', string="Contract", required=True)
benefit_type_id = fields.Many2one('hr.benefit.type')
color = fields.Integer(related='benefit_type_id.color', readonly=True)
state = fields.Selection([
@@ -190,6 +191,7 @@ class HrBenefit(models.Model):
'name': benefit.name,
'employee_id': benefit.employee_id.id,
'benefit_type_id': benefit.benefit_type_id.id,
'contract_id': benefit.contract_id.id,
}
benefit_state = benefit.state
benefit.unlink()
@@ -256,7 +258,7 @@ class HrBenefit(models.Model):
'date_to': end.date(),
'hour_from':start.hour + start.minute/60,
'hour_to': end.hour + end.minute/60,
'calendar_id': benefit.employee_id.resource_calendar_id.id,
'calendar_id': benefit.contract_id.resource_calendar_id.id,
'day_period': 'morning' if end.hour <= 12 else 'afternoon',
'resource_id': benefit.employee_id.resource_id.id,
'benefit_type_id': benefit.benefit_type_id.id,
+13 -1
View File
@@ -2,7 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
from datetime import date
class HrContract(models.Model):
_inherit = 'hr.contract'
@@ -25,6 +25,18 @@ class HrContract(models.Model):
full_time_required_hours = fields.Float(related='resource_calendar_id.full_time_required_hours')
is_fulltime = fields.Boolean(related='resource_calendar_id.is_fulltime')
@api.constrains('date_start', 'date_end', 'state')
def _check_contracts(self):
self._get_leaves()._check_contracts()
@api.multi
def _get_leaves(self):
return self.env['hr.leave'].search([
('employee_id', 'in', self.mapped('employee_id.id')),
('date_from', '<=', max([end or date.max for end in self.mapped('date_end')])),
('date_to', '>=', min(self.mapped('date_start'))),
])
@api.multi
def get_all_structures(self):
"""
+12 -6
View File
@@ -33,8 +33,14 @@ class HrEmployee(models.Model):
('state', 'in', ['draft', 'confirmed'])
]))
@api.multi
def write(self, vals):
res = super(HrEmployee, self).write(vals)
if vals.get('contract_id'):
for employee in self:
employee.resource_calendar_id.transfer_leaves_to(employee.contract_id.resource_calendar_id, employee.resource_id)
employee.resource_calendar_id = employee.contract_id.resource_calendar_id
return res
@api.model
def generate_benefit(self, date_start, date_stop):
@@ -47,7 +53,7 @@ class HrEmployee(models.Model):
date_start = _format_datetime(date_start)
date_stop = _format_datetime(date_stop)
current_contracts = self.env['hr.employee']._get_contracts(date_start, date_stop)
current_contracts = self.env['hr.employee']._get_all_contracts(date_start, date_stop, states=['open', 'pending', 'close'])
current_employees = current_contracts.mapped('employee_id')
mapped_data = dict.fromkeys(current_employees, self.env['hr.contract'])
@@ -55,6 +61,7 @@ class HrEmployee(models.Model):
mapped_data[contract.employee_id] |= contract
for employee, contracts in mapped_data.items():
# Approved leaves
emp_leaves = employee.resource_calendar_id.leave_ids.filtered(
lambda r:
@@ -63,9 +70,7 @@ class HrEmployee(models.Model):
r.date_to.replace(tzinfo=pytz.utc) >= date_start
)
global_leaves = employee.resource_calendar_id.global_leave_ids
for calendar_leave in emp_leaves | global_leaves:
hr_leave = calendar_leave.holiday_id
hr_leave.copy_to_benefits()
(emp_leaves | global_leaves).mapped('holiday_id').copy_to_benefits()
new_benefits = self.env['hr.benefit']
for contract in contracts:
@@ -85,6 +90,7 @@ class HrEmployee(models.Model):
'date_stop': interval[1].astimezone(pytz.utc),
'benefit_type_id': benefit_type_id.id,
'employee_id': employee.id,
'contract_id': contract.id,
'state': 'confirmed',
})
+105 -7
View File
@@ -1,8 +1,12 @@
# -*- coding:utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import math
from dateutil.relativedelta import relativedelta
from datetime import datetime, date
from odoo import api, fields, models, _
from odoo.exceptions import ValidationError
from odoo.addons.resource.models.resource import Intervals
from odoo.addons.resource.models.resource import HOURS_PER_DAY
class HrLeaveType(models.Model):
_inherit = 'hr.leave.type'
@@ -16,23 +20,87 @@ class HrLeave(models.Model):
@api.multi
def copy_to_benefits(self):
for leave in self:
contract = leave.employee_id._get_contracts(leave.date_from, leave.date_to, states=['open', 'pending', 'close'])
start = max(leave.date_from, datetime.combine(contract.date_start, datetime.min.time()))
end = min(leave.date_to, datetime.combine(contract.date_end or date.max, datetime.max.time()))
benefit_type = leave.holiday_status_id.benefit_type_id
self.env['hr.benefit'].safe_duplicate_create({
'name': "%s%s" % (benefit_type.name + ": " if benefit_type else "", leave.employee_id.name),
'date_start': leave.date_from,
'date_stop': leave.date_to,
'date_start': start,
'date_stop': end,
'benefit_type_id': benefit_type.id,
'employee_id': leave.employee_id.id,
'leave_id': self.id,
'leave_id': leave.id,
'state': 'confirmed',
'contract_id': contract.id,
})
@api.multi
def _create_resource_leave(self):
"""
Add a resource leave in calendars of contracts running at the same period.
This is needed in order to compute the correct number of hours/days of the leave
according to the contract's calender.
"""
super(HrLeave, self)._create_resource_leave()
resource_leave_values = []
for leave in self.filtered(lambda l: l.employee_id):
contract = leave.employee_id._get_contracts(leave.date_from, leave.date_to, states=['open', 'pending', 'close'])
if contract and contract.resource_calendar_id != leave.employee_id.resource_calendar_id:
resource_leave_values += [{
'name': leave.name,
'holiday_id': leave.id,
'resource_id': leave.employee_id.resource_id.id,
'benefit_type_id': leave.holiday_status_id.benefit_type_id.id,
'time_type': leave.holiday_status_id.time_type,
'date_from': max(leave.date_from, datetime.combine(contract.date_start, datetime.min.time())),
'date_to': min(leave.date_to, datetime.combine(contract.date_end or date.max, datetime.max.time())),
'calendar_id': contract.resource_calendar_id.id,
}]
self.env['resource.calendar.leaves'].create(resource_leave_values)
return True
@api.constrains('date_from', 'date_to')
def _check_contracts(self):
"""
A leave cannot be set across multiple contracts.
Note: a leave can be across multiple contracts despite this constraint.
It happens if a leave is correctly created (not accross multiple contracts) but
contracts are later modifed/created in the middle of the leave.
"""
for holiday in self:
domain = [
('employee_id', '=', holiday.employee_id.id),
('date_start', '<=', holiday.date_to),
('state', 'not in', ['draft', 'cancel']),
'|',
('date_end', '>=', holiday.date_from),
('date_end', '=', False),
]
nbr_contracts = self.env['hr.contract'].sudo().search_count(domain)
if nbr_contracts > 1:
raise ValidationError(_('A leave cannot be set across multiple contracts.'))
@api.multi
def _cancel_benefit_conflict(self):
"""
Unlink any benefit linked to a leave in self.
Re-create new benefits where the leaves do not cover the full range of the deleted benefits.
Create a leave benefit for each leave in self.
Return True if one or more benefits are unlinked.
e.g.:
|---------------- benefit ----------------|
|------ leave ------|
||
vv
|-benef-|---benefit leave---|----benefit---|
"""
benefits = self.env['hr.benefit'].search([('leave_id', 'in', self.ids)])
if benefits:
self.copy_to_benefits()
# create new benefits where the leave does not cover the full benefit
benefits_intervals = Intervals(intervals=[(b.date_start, b.date_stop, b) for b in benefits])
leave_intervals = Intervals(intervals=[(l.date_from, l.date_to, l) for l in self])
@@ -52,17 +120,18 @@ class HrLeave(models.Model):
'date_start': benefit_start,
'date_stop': benefit_stop,
'benefit_type_id': benefit_type.id,
'contract_id': benefit.contract_id.id,
'employee_id': employee.id,
'state': 'confirmed',
})
benefits.unlink()
return True
return False
@api.multi
def action_validate(self):
super(HrLeave, self).action_validate()
self._cancel_benefit_conflict()
calendar_leaves = self.env['resource.calendar.leaves'].search([('holiday_id', 'in', self.ids)])
calendar_leaves.write({'benefit_type_id': self.holiday_status_id.benefit_type_id.id})
self._cancel_benefit_conflict() # delete preexisting conflicting benefits
return True
@api.multi
@@ -71,3 +140,32 @@ class HrLeave(models.Model):
benefits = self.env['hr.benefit'].search([('leave_id', 'in', self.ids)])
benefits.write({'display_warning': False, 'active': False})
return True
def _get_number_of_days(self, date_from, date_to, employee_id):
""" If an employee is currently working full time but requests a leave next month
where he has a new contract working only 3 days/week. This should be taken into
account when computing the number of days for the leave (2 weeks leave = 6 days).
Override this method to get number of days according to the contract's calendar
at the time of the leave.
"""
days = super(HrLeave, self)._get_number_of_days(date_from, date_to, employee_id)
if employee_id:
employee = self.env['hr.employee'].browse(employee_id)
# Use sudo otherwise base users can't compute number of days
contracts = employee.sudo()._get_contracts(date_from, date_to, states=['incoming', 'open', 'pending'])
calendar = contracts[:1].resource_calendar_id if contracts else None # Note: if len(contracts)>1, the leave creation will crash because of unicity constaint
return employee.get_work_days_data(date_from, date_to, calendar=calendar)['days']
return days
@api.multi
@api.depends('number_of_days')
def _compute_number_of_hours_display(self):
""" Override for the same reason as _get_number_of_days()"""
super(HrLeave, self)._compute_number_of_hours_display()
for holiday in self:
if holiday.date_from and holiday.date_to:
contracts = holiday.employee_id.sudo()._get_contracts(holiday.date_from, holiday.date_to, states=['incoming', 'open', 'pending'])
contract_calendar = contracts[:1].resource_calendar_id if contracts else None
calendar = contract_calendar or holiday.employee_id.resource_calendar_id or self.env.user.company_id.resource_calendar_id
holiday.number_of_hours_display = holiday.number_of_days * (calendar.hours_per_day or HOURS_PER_DAY)
+14 -27
View File
@@ -23,7 +23,7 @@ class HrPayslip(models.Model):
'to the contract chosen. If you let empty the field contract, this field isn\'t '
'mandatory anymore and thus the rules applied will be all the rules set on the '
'structure of all contracts of the employee valid for the chosen period')
name = fields.Char(string='Payslip Name', readonly=True,
name = fields.Char(string='Payslip Name', readonly=True, required=True,
states={'draft': [('readonly', False)], 'verify': [('readonly', False)]})
number = fields.Char(string='Reference', readonly=True, copy=False,
states={'draft': [('readonly', False)], 'verify': [('readonly', False)]})
@@ -143,31 +143,27 @@ class HrPayslip(models.Model):
number = payslip.number or self.env['ir.sequence'].next_by_code('salary.slip')
# delete old payslip lines
payslip.line_ids.unlink()
# set the list of contract for which the rules have to be applied
# if we don't give the contract, then the rules to apply should be for all current contracts of the employee
contracts = payslip.contract_id or \
payslip.employee_id._get_contracts(payslip.date_from, payslip.date_to)
lines = [(0, 0, line) for line in payslip._get_payslip_lines(contracts)]
lines = [(0, 0, line) for line in payslip._get_payslip_lines(payslip.contract_id)]
payslip.write({'line_ids': lines, 'number': number, 'state': 'verify', 'compute_date': fields.Date.today()})
return True
@api.model
def get_worked_day_lines(self, contracts, date_from, date_to):
def get_worked_day_lines(self, contract, date_from, date_to):
"""
@param contract: Browse record of contracts
@return: returns a list of dict containing the worked days that should be applied for the given contract between date_from and date_to
"""
res = []
# fill only if the contract as a working schedule linked
for contract in contracts.filtered(lambda contract: contract.resource_calendar_id):
day_from = datetime.combine(fields.Date.from_string(date_from), time.min)
day_to = datetime.combine(fields.Date.from_string(date_to), time.max)
if contract.resource_calendar_id:
day_from = datetime.combine(max(fields.Date.from_string(date_from), contract.date_start), time.min)
day_to = datetime.combine(min(fields.Date.from_string(date_to), contract.date_end or date.max), time.max)
calendar = contract.resource_calendar_id
benefit_types = self.env['hr.benefit.type'].search([('code', '!=', False)])
for benefit_type in benefit_types:
hours = contract.employee_id.get_benefit_days_data(benefit_type, day_from, day_to)['hours']
hours = contract.employee_id.get_benefit_days_data(benefit_type, day_from, day_to, calendar=calendar)['hours']
if hours:
line = {
'name': benefit_type.name,
@@ -265,7 +261,7 @@ class HrPayslip(models.Model):
return list(result_dict.values())
@api.onchange('employee_id', 'date_from', 'date_to')
@api.onchange('employee_id', 'contract_id', 'date_from', 'date_to')
def onchange_employee(self):
if (not self.employee_id) or (not self.date_from) or (not self.date_to):
@@ -274,22 +270,20 @@ class HrPayslip(models.Model):
employee = self.employee_id
date_from = self.date_from
date_to = self.date_to
self.company_id = employee.company_id
contracts = self.env['hr.contract']
if not self.env.context.get('contract') or not self.contract_id:
if not self.contract_id: # Add a default contract if not already defined
contracts = employee._get_contracts(date_from, date_to)
if not contracts:
if not contracts or not contracts[0].struct_id:
return
self.contract_id = contracts[0]
self.struct_id = contracts[0].struct_id
if not self.contract_id.struct_id:
return
self.struct_id = self.contract_id.struct_id
#computation of the salary worked days
worked_days_line_ids = self.get_worked_day_lines(contracts, date_from, date_to)
worked_days_line_ids = self.get_worked_day_lines(self.contract_id, date_from, date_to)
worked_days_lines = self.worked_days_line_ids.browse([])
for r in worked_days_line_ids:
worked_days_lines += worked_days_lines.new(r)
@@ -302,13 +296,6 @@ class HrPayslip(models.Model):
payslip_name = self.struct_id.payslip_name or _('Salary Slip')
self.name = '%s - %s - %s' % (payslip_name, self.employee_id.name, tools.ustr(babel.dates.format_date(date=ttyme, format='MMMM-y', locale=locale)))
@api.onchange('contract_id')
def onchange_contract(self):
if not self.contract_id:
self.struct_id = False
self.with_context(contract=True).onchange_employee()
return
def get_salary_line_total(self, code):
self.ensure_one()
line = self.line_ids.filtered(lambda line: line.code == code)
+22 -1
View File
@@ -1,8 +1,10 @@
# -*- coding:utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import models, fields, api
from odoo.osv.expression import AND, OR
from odoo.osv.expression import AND
from odoo.tools import float_compare
from datetime import datetime
class ResourceCalendar(models.Model):
_inherit = 'resource.calendar'
@@ -39,6 +41,25 @@ class ResourceCalendar(models.Model):
def _onchange_hours_per_day(self):
return super(ResourceCalendar, self)._onchange_hours_per_day()
@api.multi
def transfer_leaves_to(self, other_calendar, resources=None, from_date=None):
"""
Transfer some resource.calendar.leaves from 'self' to another calendar 'other_calendar'.
Transfered leaves linked to `resources` (or all if `resources` is None) and starting
after 'from_date' (or today if None).
"""
from_date = from_date or datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
domain = [
('calendar_id', 'in', self.ids),
('date_from', '>=', from_date),
]
domain = AND([domain, [('resource_id', 'in', resources.ids)]]) if resources else domain
self.env['resource.calendar.leaves'].search(domain).write({
'calendar_id': other_calendar.id,
})
class ResourceCalendarAttendance(models.Model):
_inherit = 'resource.calendar.attendance'
+2
View File
@@ -3,3 +3,5 @@
from . import test_payslip_flow
from . import test_benefit
from . import test_multi_contract
from . import test_calendar_sync
+61
View File
@@ -47,3 +47,64 @@ class TestPayslipBase(TransactionCase):
'employee_id': self.richard_emp.id,
'struct_id': self.developer_pay_structure.id,
})
self.benefit_type_leave = self.env['hr.benefit.type'].create({
'name': 'Leave',
'is_leave': True,
'code': 'LEAVE100'
})
self.leave_type = self.env['hr.leave.type'].create({
'name': 'Legal Leaves',
'time_type': 'leave',
'allocation_type': 'no',
'benefit_type_id': self.benefit_type_leave.id
})
class TestPayslipContractBase(TestPayslipBase):
def setUp(self):
super(TestPayslipContractBase, self).setUp()
self.calendar_richard = self.env['resource.calendar'].create({'name': 'Calendar of Richard'})
self.calendar_40h = self.env['resource.calendar'].create({'name': 'Default calendar'})
self.calendar_35h = self.env['resource.calendar'].create({
'name': '35h calendar',
'attendance_ids': [
(0, 0, {'name': 'Monday Morning', 'dayofweek': '0', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}),
(0, 0, {'name': 'Monday Evening', 'dayofweek': '0', 'hour_from': 13, 'hour_to': 16, 'day_period': 'afternoon'}),
(0, 0, {'name': 'Tuesday Morning', 'dayofweek': '1', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}),
(0, 0, {'name': 'Tuesday Evening', 'dayofweek': '1', 'hour_from': 13, 'hour_to': 16, 'day_period': 'afternoon'}),
(0, 0, {'name': 'Wednesday Morning', 'dayofweek': '2', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}),
(0, 0, {'name': 'Wednesday Evening', 'dayofweek': '2', 'hour_from': 13, 'hour_to': 16, 'day_period': 'afternoon'}),
(0, 0, {'name': 'Thursday Morning', 'dayofweek': '3', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}),
(0, 0, {'name': 'Thursday Evening', 'dayofweek': '3', 'hour_from': 13, 'hour_to': 16, 'day_period': 'afternoon'}),
(0, 0, {'name': 'Friday Morning', 'dayofweek': '4', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}),
(0, 0, {'name': 'Friday Evening', 'dayofweek': '4', 'hour_from': 13, 'hour_to': 16, 'day_period': 'afternoon'})
]
})
self.calendar_35h._onchange_hours_per_day() # update hours/day
self.richard_emp.resource_calendar_id = self.calendar_richard
# This contract ends at the 15th of the month
self.contract_cdd = self.env['hr.contract'].create({ # Fixed term contract
'date_end': datetime.strptime('2015-11-15', '%Y-%m-%d'),
'date_start': datetime.strptime('2015-01-01', '%Y-%m-%d'),
'name': 'First CDD Contract for Richard',
'resource_calendar_id': self.calendar_40h.id,
'wage': 5000.0,
'type_id': self.ref('hr_contract.hr_contract_type_emp'),
'employee_id': self.richard_emp.id,
'struct_id': self.developer_pay_structure.id,
'state': 'close',
})
# This contract starts the next day
self.contract_cdi = self.env['hr.contract'].create({ # Fixed term contract
'date_start': datetime.strptime('2015-11-16', '%Y-%m-%d'),
'name': 'Contract for Richard',
'resource_calendar_id': self.calendar_35h.id,
'wage': 5000.0,
'type_id': self.ref('hr_contract.hr_contract_type_emp'),
'employee_id': self.richard_emp.id,
'struct_id': self.developer_pay_structure.id,
'state': 'open',
})
+20 -14
View File
@@ -18,7 +18,7 @@ class TestBenefit(TestPayslipBase):
self.start = datetime.strptime('2015-11-01 01:00:00', '%Y-%m-%d %H:%M:%S').replace(tzinfo=self.tz)
self.end = datetime.strptime('2015-11-30 23:59:59', '%Y-%m-%d %H:%M:%S').replace(tzinfo=self.tz)
self.resource_calendar_id = self.env['resource.calendar'].create({'name': 'Zboub'})
self.env['hr.contract'].create({
contract = self.env['hr.contract'].create({
'date_start': self.start - relativedelta(days=5),
'name': 'dodo',
'resource_calendar_id': self.resource_calendar_id.id,
@@ -28,18 +28,7 @@ class TestBenefit(TestPayslipBase):
'state': 'open',
})
self.richard_emp.resource_calendar_id = self.resource_calendar_id
self.benefit_type_leave = self.env['hr.benefit.type'].create({
'name': 'Leave',
'is_leave': True,
'code': 'LEAVE100'
})
self.leave_type = self.env['hr.leave.type'].create({
'name': 'Paid Time Off',
'time_type': 'leave',
'allocation_type': 'no',
'benefit_type_id': self.benefit_type_leave.id
})
self.richard_emp.contract_id = contract
self.benefit_type = self.env['hr.benefit.type'].create({
'name': 'Extra attendance',
'is_leave': False,
@@ -69,6 +58,7 @@ class TestBenefit(TestPayslipBase):
benefit = self.env['hr.benefit'].create({
'name': '1',
'employee_id': self.richard_emp.id,
'contract_id': self.richard_emp.contract_id.id,
'date_start': start,
'date_stop': end,
})
@@ -91,6 +81,7 @@ class TestBenefit(TestPayslipBase):
benefit = self.env['hr.benefit'].create({
'name': '1',
'employee_id': self.richard_emp.id,
'contract_id': self.richard_emp.contract_id.id,
'date_start': start,
'date_stop': end,
})
@@ -114,6 +105,7 @@ class TestBenefit(TestPayslipBase):
benefit = self.env['hr.benefit'].create({
'name': '1',
'employee_id': self.richard_emp.id,
'contract_id': self.richard_emp.contract_id.id,
'date_start': start,
'date_stop': end,
'benefit_type_id': self.benefit_type.id,
@@ -131,6 +123,7 @@ class TestBenefit(TestPayslipBase):
'name': '1',
'employee_id': self.richard_emp.id,
'benefit_type_id': self.env.ref('hr_payroll.benefit_type_attendance').id,
'contract_id': self.richard_emp.contract_id.id,
'date_start': start,
'date_stop': end,
})
@@ -150,6 +143,7 @@ class TestBenefit(TestPayslipBase):
'name': '1',
'employee_id': self.richard_emp.id,
'benefit_type_id': self.benefit_type.id,
'contract_id': self.richard_emp.contract_id.id,
'date_start': start,
'date_stop': end,
})
@@ -186,6 +180,7 @@ class TestBenefit(TestPayslipBase):
'name': 'Richard leave from benef',
'employee_id': self.richard_emp.id,
'benefit_type_id': self.benefit_type_leave.id,
'contract_id': self.richard_emp.contract_id.id,
'date_start': start,
'date_stop': end,
})
@@ -201,6 +196,7 @@ class TestBenefit(TestPayslipBase):
'name': '1',
'employee_id': self.richard_emp.id,
'benefit_type_id': self.env.ref('hr_payroll.benefit_type_attendance').id,
'contract_id': self.richard_emp.contract_id.id,
'date_start': start,
'date_stop': end + relativedelta(hours=5),
})
@@ -208,6 +204,7 @@ class TestBenefit(TestPayslipBase):
'name': '2',
'employee_id': self.richard_emp.id,
'benefit_type_id': self.env.ref('hr_payroll.benefit_type_attendance').id,
'contract_id': self.richard_emp.contract_id.id,
'date_start': start + relativedelta(hours=3),
'date_stop': end,
})
@@ -220,6 +217,7 @@ class TestBenefit(TestPayslipBase):
'name': '1',
'employee_id': self.richard_emp.id,
'benefit_type_id': self.benefit_type_leave.id,
'contract_id': self.richard_emp.contract_id.id,
'date_start': self.start,
'date_stop': self.end,
})
@@ -238,6 +236,7 @@ class TestBenefit(TestPayslipBase):
benef1 = self.env['hr.benefit'].create({
'name': '1',
'employee_id': self.richard_emp.id,
'contract_id': self.richard_emp.contract_id.id,
'date_start': self.start,
'date_stop': self.end,
})
@@ -257,10 +256,11 @@ class TestBenefit(TestPayslipBase):
benef = self.env['hr.benefit'].create({
'name': '1',
'employee_id': self.richard_emp.id,
'contract_id': self.richard_emp.contract_id.id,
'benefit_type_id': self.benefit_type.id,
'date_start': start,
'date_stop': end,
'leave_id': leave.id
'leave_id': leave.id, # benefit conflicts with this leave
})
leave.action_approve()
@@ -295,6 +295,7 @@ class TestBenefit(TestPayslipBase):
benef = self.env['hr.benefit'].create({
'name': '1',
'employee_id': self.richard_emp.id,
'contract_id': self.richard_emp.contract_id.id,
'benefit_type_id': self.benefit_type.id,
'date_start': start,
'date_stop': end,
@@ -316,6 +317,7 @@ class TestBenefit(TestPayslipBase):
benef = self.env['hr.benefit'].create({
'name': '1',
'employee_id': self.richard_emp.id,
'contract_id': self.richard_emp.contract_id.id,
'benefit_type_id': self.benefit_type.id,
'date_start': start,
'date_stop': end,
@@ -331,6 +333,7 @@ class TestBenefit(TestPayslipBase):
leave_benef = self.env['hr.benefit'].create({
'name': '1leave',
'employee_id': self.richard_emp.id,
'contract_id': self.richard_emp.contract_id.id,
'benefit_type_id': self.benefit_type_leave.id,
'date_start': start,
'date_stop': end,
@@ -346,6 +349,7 @@ class TestBenefit(TestPayslipBase):
leave_benef = self.env['hr.benefit'].create({
'name': '1leave',
'employee_id': self.richard_emp.id,
'contract_id': self.richard_emp.contract_id.id,
'benefit_type_id': self.benefit_type_leave.id,
'date_start': start,
'date_stop': end,
@@ -361,6 +365,7 @@ class TestBenefit(TestPayslipBase):
leave_benef = self.env['hr.benefit'].create({
'name': '1leave',
'employee_id': self.richard_emp.id,
'contract_id': self.richard_emp.contract_id.id,
'benefit_type_id': self.benefit_type_leave.id,
'date_start': start,
'date_stop': end,
@@ -384,6 +389,7 @@ class TestBenefit(TestPayslipBase):
benef = self.env['hr.benefit'].create({
'name': 'Extra',
'employee_id': self.richard_emp.id,
'contract_id': self.richard_emp.contract_id.id,
'benefit_type_id': self.benefit_type.id,
'date_start': start,
'date_stop': end,
@@ -0,0 +1,52 @@
import os
from datetime import datetime
from odoo.tools import config, test_reports
from odoo.tests.common import tagged
from odoo.addons.hr_payroll.tests.common import TestPayslipContractBase
class TestPayslipCalendars(TestPayslipContractBase):
def test_contract_state_incoming_to_open(self):
# Employee's calendar should change
self.assertEqual(self.richard_emp.resource_calendar_id, self.calendar_richard)
self.contract_cdd.state = 'open'
self.assertEqual(self.richard_emp.resource_calendar_id, self.contract_cdd.resource_calendar_id, "The employee should have the calendar of its contract.")
def test_contract_transfer_leaves(self):
def create_calendar_leave(start, end, resource=None):
return self.env['resource.calendar.leaves'].create({
'name': 'leave name',
'date_from': start,
'date_to': end,
'resource_id': resource.id if resource else None,
'calendar_id': self.richard_emp.resource_calendar_id.id,
'benefit_type_id': self.benefit_type_leave.id,
'time_type': 'leave',
})
start = datetime.strptime('2015-11-17 07:00:00', '%Y-%m-%d %H:%M:%S')
end = datetime.strptime('2015-11-20 18:00:00', '%Y-%m-%d %H:%M:%S')
leave1 = create_calendar_leave(start, end, resource=self.richard_emp.resource_id)
start = datetime.strptime('2015-11-25 07:00:00', '%Y-%m-%d %H:%M:%S')
end = datetime.strptime('2015-11-28 18:00:00', '%Y-%m-%d %H:%M:%S')
leave2 = create_calendar_leave(start, end, resource=self.richard_emp.resource_id)
# global leave
start = datetime.strptime('2015-11-25 07:00:00', '%Y-%m-%d %H:%M:%S')
end = datetime.strptime('2015-11-28 18:00:00', '%Y-%m-%d %H:%M:%S')
leave3 = create_calendar_leave(start, end)
self.calendar_richard.transfer_leaves_to(self.calendar_35h, resources=self.richard_emp.resource_id, from_date=datetime.strptime('2015-11-21', '%Y-%m-%d').date())
self.assertEqual(leave1.calendar_id, self.calendar_richard, "It should stay in Richard's calendar")
self.assertEqual(leave3.calendar_id, self.calendar_richard, "Global leave should stay in original calendar")
self.assertEqual(leave2.calendar_id, self.calendar_35h, "It should be transfered to the other calendar")
# Transfer global leaves
self.calendar_richard.transfer_leaves_to(self.calendar_35h, resources=None, from_date=datetime.strptime('2015-11-21', '%Y-%m-%d').date())
self.assertEqual(leave3.calendar_id, self.calendar_35h, "Global leave should be transfered")
@@ -0,0 +1,149 @@
import os
from datetime import datetime
from odoo.tools import config, test_reports
from odoo.tests.common import tagged
from odoo.exceptions import ValidationError
from odoo.addons.hr_payroll.tests.common import TestPayslipContractBase
@tagged('payslips_multi_contract')
class TestPayslipMultiContract(TestPayslipContractBase):
def create_leave(self, start, end):
return self.env['hr.leave'].create({
'name': 'Doctor Appointment',
'employee_id': self.richard_emp.id,
'holiday_status_id': self.leave_type.id,
'date_from': start,
'date_to': end,
})
def test_multi_contract(self):
# First contact: 40h, start of the month
payslip = self.env['hr.payslip'].create({
'name': 'November 2015',
'employee_id': self.richard_emp.id,
'date_from': datetime.strptime('2015-11-01', '%Y-%m-%d'),
'date_to': datetime.strptime('2015-11-30', '%Y-%m-%d'),
'contract_id': self.contract_cdd.id,
})
payslip.onchange_employee()
self.assertEqual(payslip.worked_days_line_ids.number_of_hours, 80, "It should be 80 hours of work this month for this contract")
self.assertEqual(payslip.worked_days_line_ids.number_of_days, 10, "It should be 10 days of work this month for this contract")
# Second contract: 35h, end of the month
payslip = self.env['hr.payslip'].create({
'name': 'November 2015',
'employee_id': self.richard_emp.id,
'date_from': datetime.strptime('2015-11-01', '%Y-%m-%d'),
'date_to': datetime.strptime('2015-11-30', '%Y-%m-%d'),
'contract_id': self.contract_cdi.id,
})
payslip.onchange_employee()
self.assertEqual(payslip.worked_days_line_ids.number_of_hours, 77, "It should be 77 hours of work this month for this contract")
self.assertEqual(payslip.worked_days_line_ids.number_of_days, 11, "It should be 11 days of work this month for this contract")
def test_multi_contract_holiday(self):
# Leave during second contract
leave = self.env['resource.calendar.leaves'].create({
'name': 'leave name',
'date_from': datetime.strptime('2015-11-17 07:00:00', '%Y-%m-%d %H:%M:%S'),
'date_to': datetime.strptime('2015-11-20 18:00:00', '%Y-%m-%d %H:%M:%S'),
'resource_id': self.richard_emp.resource_id.id,
'calendar_id': self.calendar_35h.id,
'benefit_type_id': self.benefit_type_leave.id,
'time_type': 'leave',
})
payslip = self.env['hr.payslip'].create({
'name': 'November 2015',
'employee_id': self.richard_emp.id,
'date_from': datetime.strptime('2015-11-01', '%Y-%m-%d'),
'date_to': datetime.strptime('2015-11-30', '%Y-%m-%d'),
'contract_id': self.contract_cdi.id,
})
payslip.onchange_employee()
work = payslip.worked_days_line_ids.filtered(lambda line: line.code == 'WORK100')
leave = payslip.worked_days_line_ids.filtered(lambda line: line.code == 'LEAVE100')
self.assertEqual(work.number_of_hours, 49, "It should be 49 hours of work this month for this contract")
self.assertEqual(leave.number_of_hours, 28, "It should be 28 hours of leave this month for this contract")
self.assertEqual(work.number_of_days, 7, "It should be 7 days of work this month for this contract")
self.assertEqual(leave.number_of_days, 4, "It should be 4 days of leave this month for this contract")
def test_move_contract_in_leave(self):
# test move contract dates such that a leave is accross two contracts
start = datetime.strptime('2015-11-05 07:00:00', '%Y-%m-%d %H:%M:%S')
end = datetime.strptime('2015-12-15 18:00:00', '%Y-%m-%d %H:%M:%S')
self.contract_cdi.write({'date_start': datetime.strptime('2015-12-30', '%Y-%m-%d').date()})
print(self.contract_cdi.date_start, 'test')
# begins during contract, ends after contract
leave = self.create_leave(start, end)
leave.action_approve()
# move contract in the middle of the leave
with self.assertRaises(ValidationError):
self.contract_cdi.date_start = datetime.strptime('2015-11-17', '%Y-%m-%d').date()
def test_create_contract_in_leave(self):
# test create contract such that a leave is accross two contracts
start = datetime.strptime('2015-11-05 07:00:00', '%Y-%m-%d %H:%M:%S')
end = datetime.strptime('2015-12-15 18:00:00', '%Y-%m-%d %H:%M:%S')
self.contract_cdi.date_start = datetime.strptime('2015-12-30', '%Y-%m-%d').date() # remove this contract to be able to create the leave
# begins during contract, ends after contract
leave = self.create_leave(start, end)
leave.action_approve()
# move contract in the middle of the leave
with self.assertRaises(ValidationError):
contract = self.env['hr.contract'].create({
'date_start': datetime.strptime('2015-11-30', '%Y-%m-%d').date(),
'name': 'Contract for Richard',
'resource_calendar_id': self.calendar_40h.id,
'wage': 5000.0,
'type_id': self.ref('hr_contract.hr_contract_type_emp'),
'employee_id': self.richard_emp.id,
'struct_id': self.developer_pay_structure.id,
'state': 'open',
})
def test_leave_outside_contract(self):
# Leave outside contract => should not raise
start = datetime.strptime('2014-10-18 07:00:00', '%Y-%m-%d %H:%M:%S')
end = datetime.strptime('2014-10-20 09:00:00', '%Y-%m-%d %H:%M:%S')
self.create_leave(start, end)
# begins before contract, ends during contract => should not raise
start = datetime.strptime('2014-10-25 07:00:00', '%Y-%m-%d %H:%M:%S')
end = datetime.strptime('2015-01-15 18:00:00', '%Y-%m-%d %H:%M:%S')
self.create_leave(start, end)
# begins during contract, ends after contract => should not raise
self.contract_cdi.date_end = datetime.strptime('2015-11-30', '%Y-%m-%d').date()
start = datetime.strptime('2015-11-25 07:00:00', '%Y-%m-%d %H:%M:%S')
end = datetime.strptime('2015-12-5 18:00:00', '%Y-%m-%d %H:%M:%S')
self.create_leave(start, end)
def test_no_leave_overlapping_contracts(self):
with self.assertRaises(ValidationError):
# Overlap two contracts
start = datetime.strptime('2015-11-12 07:00:00', '%Y-%m-%d %H:%M:%S')
end = datetime.strptime('2015-11-17 18:00:00', '%Y-%m-%d %H:%M:%S')
leave = self.create_leave(start, end)
# Leave inside fixed term contract => should not raise
start = datetime.strptime('2015-11-04 07:00:00', '%Y-%m-%d %H:%M:%S')
end = datetime.strptime('2015-11-07 09:00:00', '%Y-%m-%d %H:%M:%S')
leave = self.create_leave(start, end)
# Leave inside contract (no end) => should not raise
start = datetime.strptime('2015-11-18 07:00:00', '%Y-%m-%d %H:%M:%S')
end = datetime.strptime('2015-11-20 09:00:00', '%Y-%m-%d %H:%M:%S')
leave = self.create_leave(start, end)
def test_leave_request_next_contracts(self):
start = datetime.strptime('2015-11-23 07:00:00', '%Y-%m-%d %H:%M:%S')
end = datetime.strptime('2015-11-24 18:00:00', '%Y-%m-%d %H:%M:%S')
leave = self.create_leave(start, end)
leave._compute_number_of_hours_display()
self.assertEqual(leave.number_of_hours_display, 14, "It should count hours according to the future contract.")
@@ -82,6 +82,7 @@
<group>
<group>
<field name="employee_id" attrs="{'readonly': [('state', '!=', 'draft')]}" />
<field name="contract_id" readonly="1"/>
<field name="benefit_type_id" attrs="{'readonly': [('state', '=', 'validated')]}" options="{'no_create': True, 'no_open': True}"/>
<field name="leave_id" attrs="{'invisible': [('leave_id', '=', False)]}"/>
</group>
@@ -3,7 +3,7 @@
from odoo import api, fields, models, _
from odoo.exceptions import UserError
from datetime import datetime
from datetime import datetime, date
class HrPayslipEmployees(models.TransientModel):
@@ -43,17 +43,20 @@ class HrPayslipEmployees(models.TransientModel):
values = Payslip.default_get(Payslip.fields_get())
values.update({
'employee_id': employee.id,
'date_from': payslip_run.date_start,
'date_to': payslip_run.date_end,
'credit_note': payslip_run.credit_note,
'payslip_run_id': payslip_run.id,
'date_from': payslip_run.date_start,
'date_to': payslip_run.date_end,
})
payslip = self.env['hr.payslip'].new(values)
payslip.onchange_employee()
payslip.onchange_contract()
payslip._onchange_struct_id()
values = payslip._convert_to_write(payslip._cache)
payslips += Payslip.create(values)
for contract in employee._get_contracts(payslip_run.date_start, payslip_run.date_end, states=['open', 'pending', 'close']):
values.update({
'contract_id': contract.id,
})
payslip = self.env['hr.payslip'].new(values)
payslip.onchange_employee()
payslip._onchange_struct_id()
values = payslip._convert_to_write(payslip._cache)
payslips += Payslip.create(values)
payslips.compute_sheet()
payslip_run.state = 'verify'
@@ -39,9 +39,9 @@ class HrPayslip(models.Model):
vals['journal_id'] = self.env.context.get('journal_id')
return super(HrPayslip, self).create(vals)
@api.onchange('contract_id')
def onchange_contract(self):
super(HrPayslip, self).onchange_contract()
@api.onchange('employee_id', 'contract_id', 'date_from', 'date_to')
def onchange_employee(self):
super(HrPayslip, self).onchange_employee()
self.journal_id = self.contract_id.journal_id.id or self.default_get(['journal_id'])['journal_id']
@api.multi
@@ -80,7 +80,6 @@ class TestHrPayrollAccount(common.TransactionCase):
self.hr_payslip.date_from = time.strftime('%Y-%m-01')
self.hr_payslip.date_to = str(datetime.now() + relativedelta.relativedelta(months=+1, day=1, days=-1))[:10]
self.hr_payslip.onchange_employee()
self.hr_payslip.onchange_contract()
self.hr_payslip._onchange_struct_id()
# I assign the amount to Input data.