451 lines
22 KiB
Python
451 lines
22 KiB
Python
# # -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from dateutil.rrule import rrule, DAILY
|
|
from datetime import datetime, date, timedelta
|
|
from dateutil.relativedelta import relativedelta
|
|
from odoo.fields import Date
|
|
from odoo.tests import Form, tagged
|
|
from odoo.addons.hr_payroll.tests.common import TestPayslipContractBase
|
|
|
|
|
|
@tagged('payslip_computation')
|
|
class TestPayslipComputation(TestPayslipContractBase):
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super(TestPayslipComputation, cls).setUpClass()
|
|
|
|
cls.richard_payslip = cls.env['hr.payslip'].create({
|
|
'name': 'Payslip of Richard',
|
|
'employee_id': cls.richard_emp.id,
|
|
'contract_id': cls.contract_cdi.id, # wage = 5000 => average/day (over 3months/13weeks): 230.77
|
|
'struct_id': cls.developer_pay_structure.id,
|
|
'date_from': date(2016, 1, 1),
|
|
'date_to': date(2016, 1, 31)
|
|
})
|
|
cls.richard_emp.resource_calendar_id = cls.contract_cdi.resource_calendar_id
|
|
|
|
cls.richard_payslip_quarter = cls.env['hr.payslip'].create({
|
|
'name': 'Payslip of Richard Quarter',
|
|
'employee_id': cls.richard_emp.id,
|
|
'contract_id': cls.contract_cdi.id,
|
|
'struct_id': cls.developer_pay_structure.id,
|
|
'date_from': date(2016, 1, 1),
|
|
'date_to': date(2016, 3, 31)
|
|
})
|
|
# To avoid having is_paid = False in some tests, as all the records are created on the
|
|
# same transaction which is quite unlikely supposed to happen in real conditions
|
|
worked_days = (cls.richard_payslip + cls.richard_payslip_quarter).worked_days_line_ids
|
|
worked_days._compute_is_paid()
|
|
worked_days.flush_model(['is_paid'])
|
|
|
|
def _reset_work_entries(self, contract):
|
|
# Use hr.leave to automatically regenerate work entries for absences
|
|
self.env['hr.work.entry'].search([('employee_id', '=', contract.employee_id.id)]).unlink()
|
|
now = datetime(2016, 1, 1, 0, 0, 0)
|
|
contract.write({
|
|
'date_generated_from': now,
|
|
'date_generated_to': now,
|
|
})
|
|
|
|
def test_unpaid_amount(self):
|
|
self.assertAlmostEqual(self.richard_payslip._get_unpaid_amount(), 0, places=2, msg="It should be paid the full wage")
|
|
|
|
self._reset_work_entries(self.richard_payslip.contract_id)
|
|
self.env['resource.calendar.leaves'].create({
|
|
'name': 'Doctor Appointment',
|
|
'date_from': datetime.strptime('2016-1-11 07:00:00', '%Y-%m-%d %H:%M:%S'),
|
|
'date_to': datetime.strptime('2016-1-11 18:00:00', '%Y-%m-%d %H:%M:%S'),
|
|
'resource_id': self.richard_emp.resource_id.id,
|
|
'calendar_id': self.richard_emp.resource_calendar_id.id,
|
|
'work_entry_type_id': self.work_entry_type_unpaid.id,
|
|
'time_type': 'leave',
|
|
})
|
|
|
|
self.richard_emp.contract_ids.generate_work_entries(date(2016, 1, 1), date(2016, 2, 1))
|
|
self.richard_payslip._compute_worked_days_line_ids()
|
|
# TBE: In master the Monetary field were not rounded because the currency_id wasn't computed yet.
|
|
# The test was incorrect using the value 238.09, with 238.11 it is ok
|
|
self.assertAlmostEqual(self.richard_payslip._get_unpaid_amount(), 238.11, delta=0.01, msg="It should be paid 238.11 less")
|
|
|
|
def test_worked_days_amount_with_unpaid(self):
|
|
self.env['resource.calendar.leaves'].create({
|
|
'name': 'Doctor Appointment',
|
|
'date_from': datetime.strptime('2016-1-11 07:00:00', '%Y-%m-%d %H:%M:%S'),
|
|
'date_to': datetime.strptime('2016-1-11 18:00:00', '%Y-%m-%d %H:%M:%S'),
|
|
'resource_id': self.richard_emp.resource_id.id,
|
|
'calendar_id': self.richard_emp.resource_calendar_id.id,
|
|
'work_entry_type_id': self.work_entry_type_leave.id,
|
|
'time_type': 'leave',
|
|
})
|
|
|
|
self.env['resource.calendar.leaves'].create({
|
|
'name': 'Unpaid Doctor Appointment',
|
|
'date_from': datetime.strptime('2016-1-21 07:00:00', '%Y-%m-%d %H:%M:%S'),
|
|
'date_to': datetime.strptime('2016-1-21 18:00:00', '%Y-%m-%d %H:%M:%S'),
|
|
'resource_id': self.richard_emp.resource_id.id,
|
|
'calendar_id': self.richard_emp.resource_calendar_id.id,
|
|
'work_entry_type_id': self.work_entry_type_unpaid.id,
|
|
'time_type': 'leave',
|
|
})
|
|
|
|
self._reset_work_entries(self.richard_payslip.contract_id)
|
|
work_entries = self.richard_emp.contract_ids.generate_work_entries(date(2016, 1, 1), date(2016, 2, 1))
|
|
work_entries.action_validate()
|
|
|
|
self.richard_payslip._compute_worked_days_line_ids()
|
|
work_days = self.richard_payslip.worked_days_line_ids
|
|
|
|
self.assertAlmostEqual(sum(work_days.mapped('amount')), self.contract_cdi.wage - self.richard_payslip._get_unpaid_amount())
|
|
|
|
leave_line = work_days.filtered(lambda l: l.code == self.work_entry_type_leave.code)
|
|
self.assertAlmostEqual(leave_line.amount, 238.11, delta=0.01, msg="His paid time off must be paid 238.11")
|
|
|
|
extra_attendance_line = work_days.filtered(lambda l: l.code == self.work_entry_type_unpaid.code)
|
|
self.assertAlmostEqual(extra_attendance_line.amount, 0.0, places=2, msg="His unpaid time off must be paid 0.")
|
|
|
|
attendance_line = work_days.filtered(lambda l: l.code == self.env.ref('hr_work_entry.work_entry_type_attendance').code)
|
|
self.assertAlmostEqual(attendance_line.amount, 4524.11, delta=0.01, msg="His attendance must be paid 4524.11")
|
|
|
|
def test_worked_days_with_unpaid(self):
|
|
self.contract_cdi.resource_calendar_id = self.calendar_38h
|
|
self.richard_emp.resource_calendar_id = self.calendar_38h
|
|
|
|
# Create 2 hours upaid leave every day during 2 weeks
|
|
for day in rrule(freq=DAILY, byweekday=[0, 1, 2, 3, 4], count=10, dtstart=datetime(2016, 2, 8)):
|
|
start = day + timedelta(hours=13.6)
|
|
end = day + timedelta(hours=15.6)
|
|
self.env['resource.calendar.leaves'].create({
|
|
'name': 'Unpaid Leave',
|
|
'date_from': start,
|
|
'date_to': end,
|
|
'resource_id': self.richard_emp.resource_id.id,
|
|
'calendar_id': self.richard_emp.resource_calendar_id.id,
|
|
'work_entry_type_id': self.work_entry_type_unpaid.id,
|
|
'time_type': 'leave',
|
|
})
|
|
|
|
self._reset_work_entries(self.richard_payslip_quarter.contract_id)
|
|
work_entries = self.richard_emp.contract_ids.generate_work_entries(date(2016, 1, 1), date(2016, 3, 31))
|
|
work_entries.action_validate()
|
|
|
|
self.richard_payslip_quarter._compute_worked_days_line_ids()
|
|
work_days = self.richard_payslip_quarter.worked_days_line_ids
|
|
|
|
leave_line = work_days.filtered(lambda l: l.code == self.env.ref('hr_work_entry.work_entry_type_attendance').code)
|
|
self.assertAlmostEqual(leave_line.number_of_days, 62.5, places=2)
|
|
|
|
extra_attendance_line = work_days.filtered(lambda l: l.code == self.work_entry_type_unpaid.code)
|
|
self.assertAlmostEqual(extra_attendance_line.number_of_days, 2.5, places=2)
|
|
|
|
def test_worked_days_16h_with_unpaid(self):
|
|
self.contract_cdi.resource_calendar_id = self.calendar_16h
|
|
self.richard_emp.resource_calendar_id = self.calendar_16h
|
|
|
|
# Create 2 hours upaid leave every Thursday Evening during 5 weeks
|
|
for day in rrule(freq=DAILY, byweekday=3, count=5, dtstart=datetime(2016, 2, 4)):
|
|
start = day + timedelta(hours=12.5)
|
|
end = day + timedelta(hours=14.5)
|
|
self.env['resource.calendar.leaves'].create({
|
|
'name': 'Unpaid Leave',
|
|
'date_from': start,
|
|
'date_to': end,
|
|
'resource_id': self.richard_emp.resource_id.id,
|
|
'calendar_id': self.richard_emp.resource_calendar_id.id,
|
|
'work_entry_type_id': self.work_entry_type_unpaid.id,
|
|
'time_type': 'leave',
|
|
})
|
|
self._reset_work_entries(self.richard_payslip_quarter.contract_id)
|
|
|
|
work_entries = self.richard_emp.contract_ids.generate_work_entries(date(2016, 1, 1), date(2016, 3, 31))
|
|
work_entries.action_validate()
|
|
|
|
self.richard_payslip_quarter._compute_worked_days_line_ids()
|
|
work_days = self.richard_payslip_quarter.worked_days_line_ids
|
|
|
|
leave_line = work_days.filtered(lambda l: l.code == self.env.ref('hr_work_entry.work_entry_type_attendance').code)
|
|
self.assertAlmostEqual(leave_line.number_of_days, 49.5, places=2)
|
|
|
|
extra_attendance_line = work_days.filtered(lambda l: l.code == self.work_entry_type_unpaid.code)
|
|
self.assertAlmostEqual(extra_attendance_line.number_of_days, 2.5, places=2)
|
|
|
|
def test_worked_days_38h_friday_with_unpaid(self):
|
|
self.contract_cdi.resource_calendar_id = self.calendar_38h_friday_light
|
|
self.richard_emp.resource_calendar_id = self.calendar_38h_friday_light
|
|
|
|
# Create 4 hours (all work day) upaid leave every Friday during 5 weeks
|
|
for day in rrule(freq=DAILY, byweekday=4, count=5, dtstart=datetime(2016, 2, 4)):
|
|
start = day + timedelta(hours=7)
|
|
end = day + timedelta(hours=11)
|
|
self.env['resource.calendar.leaves'].create({
|
|
'name': 'Unpaid Leave',
|
|
'date_from': start,
|
|
'date_to': end,
|
|
'resource_id': self.richard_emp.resource_id.id,
|
|
'calendar_id': self.richard_emp.resource_calendar_id.id,
|
|
'work_entry_type_id': self.work_entry_type_unpaid.id,
|
|
'time_type': 'leave',
|
|
})
|
|
|
|
self._reset_work_entries(self.richard_payslip_quarter.contract_id)
|
|
work_entries = self.richard_emp.contract_ids.generate_work_entries(date(2016, 1, 1), date(2016, 3, 31))
|
|
work_entries.action_validate()
|
|
|
|
self.richard_payslip_quarter._compute_worked_days_line_ids()
|
|
work_days = self.richard_payslip_quarter.worked_days_line_ids
|
|
|
|
leave_line = work_days.filtered(lambda l: l.code == self.env.ref('hr_work_entry.work_entry_type_attendance').code)
|
|
self.assertAlmostEqual(leave_line.number_of_days, 62.5, places=2)
|
|
|
|
extra_attendance_line = work_days.filtered(lambda l: l.code == self.work_entry_type_unpaid.code)
|
|
self.assertAlmostEqual(extra_attendance_line.number_of_days, 2.5, places=2)
|
|
|
|
def test_sum_category(self):
|
|
self.richard_payslip.compute_sheet()
|
|
self.richard_payslip.action_payslip_done()
|
|
|
|
self.richard_payslip2 = self.env['hr.payslip'].create({
|
|
'name': 'Payslip of Richard',
|
|
'employee_id': self.richard_emp.id,
|
|
'contract_id': self.contract_cdi.id,
|
|
'struct_id': self.developer_pay_structure.id,
|
|
'date_from': date(2016, 1, 1),
|
|
'date_to': date(2016, 1, 31)
|
|
})
|
|
self.richard_payslip2.compute_sheet()
|
|
self.assertEqual(3010.13, self.richard_payslip2.line_ids.filtered(lambda x: x.code == 'SUMALW').total)
|
|
|
|
def test_payslip_generation_with_extra_work(self):
|
|
# /!\ this is in the weekend (Sunday) => no calendar attendance at this time
|
|
start = datetime(2015, 11, 1, 10, 0, 0)
|
|
end = datetime(2015, 11, 1, 17, 0, 0)
|
|
work_entries = self.contract_cdd.generate_work_entries(start.date(), (end + relativedelta(days=2)).date())
|
|
work_entries.action_validate()
|
|
|
|
work_entry = self.env['hr.work.entry'].create({
|
|
'name': 'Extra',
|
|
'employee_id': self.richard_emp.id,
|
|
'contract_id': self.contract_cdd.id,
|
|
'work_entry_type_id': self.work_entry_type.id,
|
|
'date_start': start,
|
|
'date_stop': end,
|
|
})
|
|
work_entry.action_validate()
|
|
payslip_wizard = self.env['hr.payslip.employees'].create({'employee_ids': [(4, self.richard_emp.id)]})
|
|
batch_id = payslip_wizard.with_context({
|
|
'default_date_start': Date.to_string(start),
|
|
'default_date_end': Date.to_string(end + relativedelta(days=1))
|
|
}).compute_sheet()['res_id']
|
|
payslip = self.env['hr.payslip'].search([
|
|
('employee_id', '=', self.richard_emp.id),
|
|
('payslip_run_id', '=', batch_id),
|
|
])
|
|
work_line = payslip.worked_days_line_ids.filtered(lambda l: l.work_entry_type_id == self.env.ref('hr_work_entry.work_entry_type_attendance')) # From default calendar.attendance
|
|
extra_work_line = payslip.worked_days_line_ids.filtered(lambda l: l.work_entry_type_id == self.work_entry_type)
|
|
|
|
self.assertTrue(work_line, "It should have a work line in the payslip")
|
|
self.assertTrue(extra_work_line, "It should have an extra work line in the payslip")
|
|
self.assertEqual(work_line.number_of_hours, 8.0, "It should have 8 hours of work") # Monday
|
|
self.assertEqual(extra_work_line.number_of_hours, 7.0, "It should have 7 hours of extra work") # Sunday
|
|
|
|
def test_work_data_with_exceeding_interval(self):
|
|
self.env['hr.work.entry'].create({
|
|
'name': 'Attendance',
|
|
'employee_id': self.richard_emp.id,
|
|
'contract_id': self.contract_cdd.id,
|
|
'work_entry_type_id': self.env.ref('hr_work_entry.work_entry_type_attendance').id,
|
|
'date_start': datetime(2015, 11, 9, 20, 0),
|
|
'date_stop': datetime(2015, 11, 10, 7, 0)
|
|
}).action_validate()
|
|
self.env['hr.work.entry'].create({
|
|
'name': 'Attendance',
|
|
'employee_id': self.richard_emp.id,
|
|
'contract_id': self.contract_cdd.id,
|
|
'work_entry_type_id': self.env.ref('hr_work_entry.work_entry_type_attendance').id,
|
|
'date_start': datetime(2015, 11, 10, 21, 0),
|
|
'date_stop': datetime(2015, 11, 11, 5, 0),
|
|
}).action_validate()
|
|
self.contract_cdd.generate_work_entries(date(2015, 11, 10), date(2015, 11, 10))
|
|
hours = self.contract_cdd.get_work_hours(date(2015, 11, 10), date(2015, 11, 10))
|
|
sum_hours = sum(v for k, v in hours.items() if k in self.env.ref('hr_work_entry.work_entry_type_attendance').ids)
|
|
self.assertAlmostEqual(sum_hours, 18, delta=0.01, msg='It should count 18 attendance hours') # 8h normal day + 7h morning + 3h night
|
|
|
|
def test_payslip_without_contract(self):
|
|
payslip = self.env['hr.payslip'].create({
|
|
'name': 'Payslip of Richard',
|
|
'employee_id': self.richard_emp.id,
|
|
'date_from': date(2016, 1, 1),
|
|
'date_to': date(2016, 1, 31)
|
|
})
|
|
self.assertTrue(payslip.contract_id)
|
|
payslip.contract_id = False
|
|
self.assertEqual(payslip._get_contract_wage(), 0, "It should have a default wage of 0")
|
|
self.assertEqual(payslip.basic_wage, 0, "It should have a default wage of 0")
|
|
self.assertEqual(payslip.gross_wage, 0, "It should have a default wage of 0")
|
|
self.assertEqual(payslip.net_wage, 0, "It should have a default wage of 0")
|
|
|
|
def test_payslip_with_salary_attachment(self):
|
|
#Create multiple salary attachments, some running, some closed
|
|
self.env['hr.salary.attachment'].create([
|
|
{
|
|
'employee_ids': [(4, self.richard_emp.id)],
|
|
'monthly_amount': 150,
|
|
'other_input_type_id': self.env.ref('hr_payroll.input_child_support').id,
|
|
'date_start': date(2016, 1, 1),
|
|
'description': 'Child Support',
|
|
},
|
|
{
|
|
'employee_ids': [(4, self.richard_emp.id)],
|
|
'monthly_amount': 400,
|
|
'total_amount': 1000,
|
|
'paid_amount': 1000,
|
|
'other_input_type_id': self.env.ref('hr_payroll.input_assignment_salary').id,
|
|
'date_start': date(2015, 1, 1),
|
|
'date_end': date(2015, 4, 1),
|
|
'description': 'Unpaid fine',
|
|
'state': 'close',
|
|
},
|
|
])
|
|
|
|
car_accident = self.env['hr.salary.attachment'].create({
|
|
'employee_ids': [(4, self.richard_emp.id)],
|
|
'monthly_amount': 250,
|
|
'paid_amount': 1450,
|
|
'total_amount': 1500,
|
|
'other_input_type_id': self.env.ref('hr_payroll.input_attachment_salary').id,
|
|
'date_start': date(2016, 1, 1),
|
|
'description': 'Car accident',
|
|
})
|
|
|
|
payslip = self.env['hr.payslip'].create({
|
|
'name': 'Payslip of Richard',
|
|
'employee_id': self.richard_emp.id,
|
|
'contract_id': self.contract_cdi.id,
|
|
'date_from': date(2016, 1, 1),
|
|
'date_to': date(2016, 1, 31)
|
|
})
|
|
input_lines = payslip.input_line_ids
|
|
self.assertTrue(input_lines.filtered(lambda r: r.code == 'CHILD_SUPPORT'), 'There should be an input line for child support.')
|
|
self.assertTrue(input_lines.filtered(lambda r: r.code == 'ATTACH_SALARY'), 'There should be an input line for the car accident.')
|
|
self.assertTrue(input_lines.filtered(lambda r: r.code == 'ATTACH_SALARY').amount <= 50, 'The amount for the car accident input line should be 50 or less.')
|
|
self.assertFalse(input_lines.filtered(lambda r: r.code == 'ASSIG_SALARY'), 'There should not be an input line for the unpaid fine.')
|
|
payslip.compute_sheet()
|
|
lines = payslip.line_ids
|
|
self.assertTrue(lines.filtered(lambda r: r.code == 'CHILD_SUPPORT'), 'There should be a salary line for child support.')
|
|
self.assertTrue(lines.filtered(lambda r: r.code == 'ATTACH_SALARY'), 'There should be a salary line for car accident.')
|
|
payslip.action_payslip_done()
|
|
payslip.action_payslip_paid()
|
|
self.assertEqual(car_accident.state, 'close', 'The salary attachment should be completed.')
|
|
|
|
def test_payslip_with_multiple_input_same_type(self):
|
|
payslip = self.env['hr.payslip'].create({
|
|
'name': 'Payslip of Richard',
|
|
'employee_id': self.richard_emp.id,
|
|
'contract_id': self.contract_cdi.id,
|
|
'date_from': date(2016, 1, 1),
|
|
'date_to': date(2016, 1, 31)
|
|
})
|
|
self.env['hr.payslip.input'].create([
|
|
{
|
|
'payslip_id': payslip.id,
|
|
'sequence': 1,
|
|
'input_type_id': self.env.ref("hr_payroll.BASIC").id,
|
|
'amount': 100,
|
|
'contract_id': self.contract_cdi.id
|
|
},
|
|
{
|
|
'payslip_id': payslip.id,
|
|
'sequence': 2,
|
|
'input_type_id': self.env.ref("hr_payroll.BASIC").id,
|
|
'amount': 200,
|
|
'contract_id': self.contract_cdi.id
|
|
},
|
|
])
|
|
payslip.compute_sheet()
|
|
lines = payslip.line_ids
|
|
self.assertEqual(len(lines.filtered(lambda r: r.code == 'BASIC')), 1)
|
|
|
|
def test_payslip_multiple_inputs_and_attachments_same_type(self):
|
|
self.env['hr.salary.attachment'].create([
|
|
{
|
|
'employee_ids': [self.richard_emp.id],
|
|
'monthly_amount': 100,
|
|
'paid_amount': 0,
|
|
'total_amount': 100,
|
|
'other_input_type_id': self.env.ref('hr_payroll.default_attachment_of_salary_rule').id,
|
|
'date_start': date(2016, 1, 1),
|
|
'description': 'Some attachment',
|
|
},
|
|
{
|
|
'employee_ids': [self.richard_emp.id],
|
|
'monthly_amount': 200,
|
|
'paid_amount': 0,
|
|
'total_amount': 200,
|
|
'other_input_type_id': self.env.ref('hr_payroll.default_attachment_of_salary_rule').id,
|
|
'date_start': date(2016, 1, 1),
|
|
'description': 'Another attachment',
|
|
},
|
|
])
|
|
|
|
payslip = self.env['hr.payslip'].create({
|
|
'name': 'Payslip of Richard',
|
|
'employee_id': self.richard_emp.id,
|
|
'contract_id': self.contract_cdi.id,
|
|
'date_from': date(2016, 1, 1),
|
|
'date_to': date(2016, 1, 31),
|
|
})
|
|
|
|
with Form(payslip) as payslip_form:
|
|
payslip_form.input_line_ids.remove(0) # remove merged input of attachments and use 2 inputs instead
|
|
with payslip_form.input_line_ids.new() as line:
|
|
line.input_type_id = self.env.ref("hr_payroll.input_assignment_salary")
|
|
line.amount = 100
|
|
with payslip_form.input_line_ids.new() as line:
|
|
line.input_type_id = self.env.ref("hr_payroll.input_assignment_salary")
|
|
line.amount = 200
|
|
|
|
payslip.compute_sheet()
|
|
payslip.action_payslip_done()
|
|
payslip.action_payslip_paid()
|
|
|
|
self.assertEqual(sum(s.paid_amount for s in payslip.salary_attachment_ids), 100 + 200)
|
|
|
|
def test_defaultdict_get(self):
|
|
# defaultdict.get(key) returns None if the key doesn't exist instead of default factory value
|
|
# which could lead to a traceback
|
|
self.developer_pay_structure.rule_ids.filtered(lambda r: r.code == "NET").amount_python_compute = "result = categories['BASIC'] + categories['ALW'] + categories['DED'] + categories.get('TEST')"
|
|
payslip = self.env['hr.payslip'].create({
|
|
'name': 'Payslip of Richard',
|
|
'employee_id': self.richard_emp.id,
|
|
'date_from': date(2016, 1, 1),
|
|
'date_to': date(2016, 1, 31)
|
|
})
|
|
payslip.compute_sheet()
|
|
|
|
def test_compute_payslip_no_worked_hours(self):
|
|
employee = self.env['hr.employee'].create({'name': 'John'})
|
|
contract = self.env['hr.contract'].create({
|
|
'name': 'Contract for John',
|
|
'wage': 5000,
|
|
'employee_id': employee.id,
|
|
'date_start': Date.to_date('2024-10-01'),
|
|
'date_end': Date.to_date('2024-10-31'),
|
|
'work_entry_source': 'attendance',
|
|
'structure_type_id': self.structure_type.id,
|
|
'state': 'open',
|
|
})
|
|
|
|
payslip = self.env['hr.payslip'].create({
|
|
'name': 'Payslip of John',
|
|
'employee_id': employee.id,
|
|
'contract_id': contract.id,
|
|
'struct_id': self.developer_pay_structure.id,
|
|
'date_from': date(2024, 10, 1),
|
|
'date_to': date(2024, 10, 31)
|
|
})
|
|
|
|
payslip.compute_sheet()
|
|
basic_salary_line = payslip.line_ids.filtered_domain([('code', '=', 'BASIC')])
|
|
self.assertAlmostEqual(basic_salary_line.amount, 0.0, 2, 'Basic salary = 0 worked hours * hourly wage = 0')
|