357 lines
15 KiB
Python
357 lines
15 KiB
Python
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from datetime import date
|
|
|
|
from odoo.tests.common import tagged, TransactionCase
|
|
|
|
|
|
@tagged('headcount')
|
|
class TestHeadcount(TransactionCase):
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
def lsplit(lst):
|
|
return lst[0], lst[1:]
|
|
|
|
super().setUpClass()
|
|
# create 2 companies with distinct resource names and ressource calendars.
|
|
cls.company_us, cls.company_be = cls.env['res.company'].create([{
|
|
'name': 'US',
|
|
}, {
|
|
'name': 'BE',
|
|
}])
|
|
cls.employee_be, cls.employees_us = lsplit(cls.env['hr.employee'].create([{
|
|
'name': 'Employee BE',
|
|
'company_id': cls.company_be.id,
|
|
}] + [{
|
|
'name': 'Employee %s' % i,
|
|
'company_id': cls.company_us.id,
|
|
} for i in range(10)]))
|
|
|
|
# create 4 resource calendars with distinct working hours.
|
|
cls.resource_40h, cls.resource_35h, cls.resource_65h, cls.resource_30h = \
|
|
cls.env['resource.calendar'].create([{
|
|
'name': '40h',
|
|
'company_id': cls.company_us.id,
|
|
'attendance_ids': [(0, 0, {
|
|
'name': str(i),
|
|
'dayofweek': str(i),
|
|
'hour_from': 8,
|
|
'hour_to': 12,
|
|
}) for i in range(5)] + [(0, 0, {
|
|
'name': str(i),
|
|
'dayofweek': str(i),
|
|
'hour_from': 13,
|
|
'hour_to': 17,
|
|
}) for i in range(5)],
|
|
}, {
|
|
'name': '35h',
|
|
'company_id': cls.company_us.id,
|
|
'attendance_ids': [(0, 0, {
|
|
'name': str(i),
|
|
'dayofweek': str(i),
|
|
'hour_from': 8,
|
|
'hour_to': 12,
|
|
}) for i in range(5)] + [(0, 0, {
|
|
'name': str(i),
|
|
'dayofweek': str(i),
|
|
'hour_from': 13,
|
|
'hour_to': 16,
|
|
}) for i in range(5)],
|
|
}, {
|
|
'name': '65h',
|
|
'company_id': cls.company_us.id,
|
|
'attendance_ids': [(0, 0, {
|
|
'name': str(i),
|
|
'dayofweek': str(i),
|
|
'hour_from': 8,
|
|
'hour_to': 12,
|
|
}) for i in range(5)] + [(0, 0, {
|
|
'name': str(i),
|
|
'dayofweek': str(i),
|
|
'hour_from': 13,
|
|
'hour_to': 17,
|
|
}) for i in range(5)] + [(0, 0, {
|
|
'name': str(i),
|
|
'dayofweek': str(i),
|
|
'hour_from': 18,
|
|
'hour_to': 23,
|
|
}) for i in range(5)],
|
|
}, {
|
|
'name': '30h',
|
|
'company_id': cls.company_us.id,
|
|
'attendance_ids': [(0, 0, {
|
|
'name': str(i),
|
|
'dayofweek': str(i),
|
|
'hour_from': 8,
|
|
'hour_to': 12,
|
|
}) for i in range(5)] + [(0, 0, {
|
|
'name': str(i),
|
|
'dayofweek': str(i),
|
|
'hour_from': 13,
|
|
'hour_to': 15,
|
|
}) for i in range(5)],
|
|
}])
|
|
|
|
# make one contract for the BE employee and make 12 contracts for US
|
|
# employees with some employees having two contracts.
|
|
# note that the contracts are never overlapping if an employee has two contracts.
|
|
cls.contract_be, cls.contracts_us = lsplit(cls.env['hr.contract'].create([{
|
|
'name': 'Contract BE',
|
|
'employee_id': cls.employee_be.id,
|
|
'company_id': cls.company_be.id,
|
|
'date_start': date(2000, 1, 1),
|
|
'wage': 1000,
|
|
'state': 'open',
|
|
'resource_calendar_id': cls.resource_40h.id,
|
|
}] + [{
|
|
'name': 'Contract 1:%s' % i,
|
|
'employee_id': cls.employees_us[i].id,
|
|
'company_id': cls.company_us.id,
|
|
'date_start': date(2000 + i // 3, 1, 1),
|
|
'date_end': date(2000 + i // 2, 1, 1) if i % 3 else False,
|
|
'wage': 1000 * (i + 1),
|
|
'state': 'open',
|
|
'resource_calendar_id': cls.resource_40h.id if i % 2 else cls.resource_35h.id,
|
|
} for i in range(10)] + [{
|
|
'name': 'Contract 2:%s' % i,
|
|
'employee_id': cls.employees_us[i].id,
|
|
'company_id': cls.company_us.id,
|
|
'date_start': date(2000 + i // 2, 1, 2),
|
|
'date_end': date(2000 + i, 1, 1),
|
|
'wage': 12000 * (i + 1),
|
|
'state': 'close',
|
|
'resource_calendar_id': cls.resource_65h.id if i % 2 else cls.resource_30h.id,
|
|
} for i in range(1, 3)]))
|
|
|
|
# the two following contracts shouldn't be taken into account in the headcount.
|
|
# no matter the date range.
|
|
cls.contracts_us_draft, cls.contracts_us_cancelled = cls.env['hr.contract'].create([{
|
|
'name': 'draft',
|
|
'employee_id': cls.employees_us[0].id,
|
|
'company_id': cls.company_us.id,
|
|
'date_start': date(2000, 1, 1),
|
|
'wage': 1000,
|
|
'state': 'draft',
|
|
'resource_calendar_id': cls.resource_40h.id,
|
|
}, {
|
|
'name': 'cancelled',
|
|
'employee_id': cls.employees_us[0].id,
|
|
'company_id': cls.company_us.id,
|
|
'date_start': date(2000, 1, 1),
|
|
'wage': 1000,
|
|
'state': 'cancel',
|
|
'resource_calendar_id': cls.resource_40h.id,
|
|
}])
|
|
|
|
def test_headcount_title_company(self):
|
|
"""Test that the headcount name is properly set with the company."""
|
|
|
|
headcount = self.env['hr.payroll.headcount'].create({
|
|
'company_id': self.company_us.id,
|
|
'date_from': date.today(),
|
|
})
|
|
self.assertEqual(headcount.name, 'Headcount for US on the %s' % date.today())
|
|
headcount = self.env['hr.payroll.headcount'].create({
|
|
'company_id': self.company_be.id,
|
|
'date_from': date.today(),
|
|
})
|
|
self.assertEqual(headcount.name, 'Headcount for BE on the %s' % date.today())
|
|
|
|
def test_headcount_title_date_range(self):
|
|
"""Test that the headcount name is properly when a date range is set
|
|
or not."""
|
|
|
|
headcount = self.env['hr.payroll.headcount'].create({
|
|
'company_id': self.company_us.id,
|
|
'date_from': date(2020, 1, 1),
|
|
})
|
|
self.assertEqual(headcount.name, 'Headcount for US on the 2020-01-01')
|
|
headcount = self.env['hr.payroll.headcount'].create({
|
|
'company_id': self.company_us.id,
|
|
'date_from': date(2020, 1, 1),
|
|
'date_to': date(2020, 1, 1),
|
|
})
|
|
self.assertEqual(headcount.name, 'Headcount for US on the 2020-01-01')
|
|
headcount = self.env['hr.payroll.headcount'].create({
|
|
'company_id': self.company_us.id,
|
|
'date_from': date(2020, 1, 1),
|
|
'date_to': date(2020, 1, 31),
|
|
})
|
|
self.assertEqual(headcount.name, 'Headcount for US from 2020-01-01 to 2020-01-31')
|
|
|
|
def test_company_consistency(self):
|
|
"""Test that the headcount is consistent with the company of the
|
|
contracts that are actually taken into account.
|
|
"""
|
|
|
|
headcount = self.env['hr.payroll.headcount'].create({
|
|
'company_id': self.company_be.id,
|
|
'date_from': date(1900, 1, 1),
|
|
'date_to': date(2100, 1, 1),
|
|
})
|
|
self.assertEqual(headcount.employee_count, 0)
|
|
headcount.action_populate()
|
|
self.assertEqual(headcount.line_ids.mapped('contract_id.company_id'), self.company_be)
|
|
self.assertEqual(headcount.employee_count, 1)
|
|
|
|
def test_headcount_population_large_range(self):
|
|
"""Test that the headcount can be populated with a large range of
|
|
dates. This test is also used to check the working rates of the
|
|
contracts. In addition to that it checks that the contracts that are
|
|
draft or cancelled and the contracts of other companies are not taken
|
|
into account.
|
|
"""
|
|
|
|
headcount = self.env['hr.payroll.headcount'].create({
|
|
'company_id': self.company_us.id,
|
|
'date_from': date(1900, 1, 1),
|
|
'date_to': date(2100, 1, 1),
|
|
})
|
|
self.assertEqual(headcount.employee_count, 0)
|
|
headcount.action_populate()
|
|
self.assertEqual(headcount.line_ids.mapped('contract_id.company_id'), self.company_us)
|
|
self.assertEqual(headcount.employee_count, 10)
|
|
all_headcount_working_rates = self.env['hr.payroll.headcount.working.rate'].search([], order='rate')
|
|
self.assertEqual(all_headcount_working_rates.mapped('rate'), [30.0, 35.0, 40.0, 65.0])
|
|
assert self.contract_be not in headcount.line_ids.mapped('contract_id')
|
|
first_contracts = self.env['hr.contract']
|
|
for contracts in self.contracts_us.grouped('employee_id').values():
|
|
first_contracts |= contracts[-1]
|
|
self.assertEqual(first_contracts, headcount.line_ids.mapped('contract_id'))
|
|
|
|
def test_headcount_population_no_range(self):
|
|
"""Test that the headcount can be populated without a range of dates
|
|
to ensure that the headcount is working properly with no end date.
|
|
"""
|
|
|
|
headcount = self.env['hr.payroll.headcount'].create({
|
|
'company_id': self.company_us.id,
|
|
'date_from': date(2000, 1, 1),
|
|
})
|
|
self.assertEqual(headcount.employee_count, 0)
|
|
headcount.action_populate()
|
|
self.assertEqual(headcount.line_ids.mapped('contract_id.company_id'), self.company_us)
|
|
self.assertEqual(headcount.employee_count, 3)
|
|
self.assertEqual(headcount.line_ids.contract_id, self.contracts_us[0:3])
|
|
|
|
def test_headcount_population_no_contracts_started(self):
|
|
"""Test that the headcount can be populated where no contracts have
|
|
started in the selected range to ensure that the headcount is still
|
|
consistent and don't take into account the contracts that haven't
|
|
started yet.
|
|
"""
|
|
|
|
headcount = self.env['hr.payroll.headcount'].create({
|
|
'company_id': self.company_us.id,
|
|
'date_from': date(1999, 1, 1),
|
|
})
|
|
self.assertEqual(headcount.employee_count, 0)
|
|
headcount.action_populate()
|
|
self.assertEqual(headcount.employee_count, 0)
|
|
|
|
def test_headcount_population_some_contracts_over(self):
|
|
"""Test that the headcount can be populated where some contracts are
|
|
over in the selected range to ensure that the headcount is still
|
|
consistent and don't take into account the contracts that are over.
|
|
"""
|
|
|
|
headcount = self.env['hr.payroll.headcount'].create({
|
|
'company_id': self.company_us.id,
|
|
'date_from': date(2001, 1, 1),
|
|
})
|
|
self.assertEqual(headcount.employee_count, 0)
|
|
headcount.action_populate()
|
|
self.assertEqual(headcount.line_ids.mapped('contract_id.company_id'), self.company_us)
|
|
self.assertEqual(headcount.employee_count, 6)
|
|
self.assertEqual(
|
|
headcount.line_ids.contract_id,
|
|
self.contracts_us[0] | self.contracts_us[2:6] | self.contracts_us[10]
|
|
)
|
|
|
|
def test_headcount_population_only_indefinite_contracts(self):
|
|
"""Test that the headcount can be populated properly where all the
|
|
contracts are indefinite in the selected range.
|
|
"""
|
|
|
|
headcount = self.env['hr.payroll.headcount'].create({
|
|
'company_id': self.company_us.id,
|
|
'date_from': date(2020, 2, 1),
|
|
})
|
|
self.assertEqual(headcount.employee_count, 0)
|
|
self.assertEqual(headcount.line_ids, self.env['hr.payroll.headcount.line'])
|
|
headcount.action_populate()
|
|
self.assertEqual(headcount.line_ids.mapped('contract_id.company_id'), self.company_us)
|
|
self.assertEqual(headcount.employee_count, 4)
|
|
self.assertEqual(
|
|
headcount.line_ids.contract_id,
|
|
self.contracts_us[0] | self.contracts_us[3] | self.contracts_us[6] | self.contracts_us[9]
|
|
)
|
|
|
|
def test_headcount_population_small_range(self):
|
|
"""Test that the headcount can be populated with a small range of
|
|
dates.
|
|
"""
|
|
|
|
headcount = self.env['hr.payroll.headcount'].create({
|
|
'company_id': self.company_us.id,
|
|
'date_from': date(2000, 1, 1),
|
|
'date_to': date(2000, 1, 2),
|
|
})
|
|
self.assertEqual(headcount.employee_count, 0)
|
|
headcount.action_populate()
|
|
self.assertEqual(headcount.line_ids.mapped('contract_id.company_id'), self.company_us)
|
|
self.assertEqual(headcount.employee_count, 3)
|
|
self.assertEqual(
|
|
headcount.line_ids.contract_id,
|
|
self.contracts_us[0] | self.contracts_us[2] | self.contracts_us[10]
|
|
)
|
|
|
|
def test_headcount_repopulate(self):
|
|
"""Test that the headcount can be repopulated.
|
|
And that the values are actually updated.
|
|
"""
|
|
|
|
headcount = self.env['hr.payroll.headcount'].create({
|
|
'company_id': self.company_us.id,
|
|
'date_from': date(2000, 1, 1),
|
|
'date_to': date(2000, 1, 1),
|
|
})
|
|
headcount.action_populate()
|
|
self.assertEqual(headcount.employee_count, 3)
|
|
headcount.write({'date_to': date(2020, 1, 1)})
|
|
headcount.action_populate()
|
|
self.assertEqual(headcount.employee_count, 10)
|
|
|
|
def test_special_fields_coherence(self):
|
|
"""These tests are here to ensure that the set by the action_populate
|
|
method are coherent with the contracts and their orders that are
|
|
actually taken into account.
|
|
"""
|
|
|
|
headcount = self.env['hr.payroll.headcount'].create({
|
|
'company_id': self.company_us.id,
|
|
'date_from': date(1900, 1, 1),
|
|
'date_to': date(2100, 1, 1),
|
|
})
|
|
headcount.action_populate()
|
|
contracts_by_employee = self.contracts_us.grouped('employee_id')
|
|
headcount_lines_by_employee = headcount.line_ids.grouped('employee_id')
|
|
for employee_id, contracts in contracts_by_employee.items():
|
|
# verify uniqueness of the lines.
|
|
self.assertEqual(len(headcount_lines_by_employee[employee_id]), 1)
|
|
# verify the names order of the contracts.
|
|
self.assertEqual(
|
|
headcount_lines_by_employee[employee_id].contract_names,
|
|
', '.join(contract.name for contract in reversed(contracts))
|
|
)
|
|
self.assertEqual(
|
|
headcount_lines_by_employee[employee_id].contract_names.split(', ')[0],
|
|
headcount_lines_by_employee[employee_id].contract_id.name
|
|
)
|
|
# verify the working rates of the contracts.
|
|
self.assertEqual(
|
|
set(headcount_lines_by_employee[employee_id].working_rate_ids.mapped('rate')),
|
|
{round(contract.hours_per_week, 2) for contract in contracts}
|
|
)
|