# -*- coding: utf-8 -*- from odoo.tests.common import TransactionCase from odoo.exceptions import UserError, ValidationError from odoo import fields from datetime import date, datetime, timedelta class TestPayrollRules(TransactionCase): def setUp(self): super(TestPayrollRules, self).setUp() self.env.user.tz = 'UTC' self.company = self.env.ref('base.main_company') self.category_basic = self.env['hr.salary.rule.category'].create({ 'name': 'Basic', 'code': 'BASIC', }) self.structure = self.env['hr.payroll.structure'].create({ 'name': 'Test Structure', 'code': 'TEST_STRUCT', 'company_id': self.company.id, }) attendances = [] for day in range(5): # 0=Monday to 4=Friday attendances.append((0, 0, { 'name': 'Morning', 'dayofweek': str(day), 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning', })) attendances.append((0, 0, { 'name': 'Afternoon', 'dayofweek': str(day), 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon', })) self.calendar = self.env['resource.calendar'].create({ 'name': 'Standard 40 Hours UTC', 'tz': 'UTC', 'hours_per_day': 8.0, 'attendance_ids': attendances, 'company_id': self.company.id, }) self.employee = self.env['hr.employee'].create({ 'name': 'Test Employee Payroll', 'company_id': self.company.id, 'resource_calendar_id': self.calendar.id, }) if self.employee.resource_id: self.employee.resource_id.write({ 'calendar_id': self.calendar.id, 'tz': 'UTC', }) self.contract = self.env['hr.contract'].create({ 'name': 'Contract for Test', 'employee_id': self.employee.id, 'struct_id': self.structure.id, 'wage': 5000.0, 'state': 'open', 'date_start': date.today() - timedelta(days=100), 'resource_calendar_id': self.calendar.id, 'schedule_pay': 'monthly', 'company_id': self.company.id, }) self.payslip = self.env['hr.payslip'].create({ 'employee_id': self.employee.id, 'contract_id': self.contract.id, 'struct_id': self.structure.id, 'date_from': date.today().replace(day=1), 'date_to': (date.today().replace(day=1) + timedelta(days=32)).replace(day=1) - timedelta(days=1), 'company_id': self.company.id, }) def test_satisfy_condition_python(self): rule = self.env['hr.salary.rule'].create({ 'name': 'Python Condition Rule', 'sequence': 10, 'code': 'PY_COND', 'category_id': self.category_basic.id, 'condition_select': 'python', 'condition_python': 'result = contract.wage > 3000', 'amount_select': 'fix', 'amount_fix': 100.0, }) localdict = {'contract': self.contract, 'employee': self.employee} self.assertTrue(rule._satisfy_condition(localdict)) rule.condition_python = 'result = contract.wage > 6000' self.assertFalse(rule._satisfy_condition(localdict)) def test_satisfy_condition_range(self): rule = self.env['hr.salary.rule'].create({ 'name': 'Range Condition Rule', 'sequence': 10, 'code': 'RANGE_COND', 'category_id': self.category_basic.id, 'condition_select': 'range', 'condition_range': 'contract.wage', 'condition_range_min': 1000, 'condition_range_max': 6000, 'amount_select': 'fix', 'amount_fix': 100.0, }) localdict = {'contract': self.contract} self.assertTrue(rule._satisfy_condition(localdict)) self.contract.wage = 8000 self.assertFalse(rule._satisfy_condition(localdict)) def test_compute_rule_percentage(self): rule = self.env['hr.salary.rule'].create({ 'name': 'Percentage Rule', 'sequence': 10, 'code': 'PERCENT', 'category_id': self.category_basic.id, 'amount_select': 'percentage', 'amount_percentage_base': 'contract.wage', 'amount_percentage': 10.0, 'quantity': '1.0', }) localdict = {'contract': self.contract} amount, qty, rate = rule._compute_rule(localdict) self.assertEqual(amount, 5000.0) self.assertEqual(amount * qty * rate / 100.0, 500.0) def test_compute_rule_python_code(self): rule = self.env['hr.salary.rule'].create({ 'name': 'Python Code Rule', 'sequence': 10, 'code': 'PY_CODE', 'category_id': self.category_basic.id, 'amount_select': 'code', 'amount_python_compute': 'result = contract.wage + 500', }) localdict = {'contract': self.contract} amount, qty, rate = rule._compute_rule(localdict) self.assertEqual(amount, 5500.0) def test_get_contract(self): old_contract = self.env['hr.contract'].create({ 'name': 'Old Contract', 'employee_id': self.employee.id, 'wage': 4000, 'state': 'close', 'date_start': date.today() - timedelta(days=400), 'date_end': date.today() - timedelta(days=200), 'resource_calendar_id': self.calendar.id, 'struct_id': self.structure.id, }) date_from = date.today().replace(day=1) date_to = (date.today().replace(day=1) + timedelta(days=32)).replace(day=1) - timedelta(days=1) contract_ids = self.env['hr.payslip'].get_contract(self.employee, date_from, date_to) self.assertIn(self.contract.id, contract_ids) self.assertNotIn(old_contract.id, contract_ids) def test_get_worked_day_lines(self): today = date.today() days_ahead = 0 - today.weekday() if days_ahead <= 0: days_ahead += 7 next_monday = today + timedelta(days=days_ahead) date_from = next_monday date_to = next_monday + timedelta(days=4) try: worked_days = self.env['hr.payslip'].get_worked_day_lines(self.contract, date_from, date_to) work_entry = next((item for item in worked_days if item['code'] == 'WORK100'), None) self.assertIsNotNone(work_entry, "WORK100 entry not found in result") except AttributeError as e: print(f"Skipping test_get_worked_day_lines due to missing dependency: {e}") def test_payslip_line_compute_total(self): line = self.env['hr.payslip.line'].create({ 'slip_id': self.payslip.id, 'name': 'Test Line', 'code': 'TEST', 'contract_id': self.contract.id, 'salary_rule_id': self.env['hr.salary.rule'].search([], limit=1).id, 'employee_id': self.employee.id, 'quantity': 2.0, 'amount': 500.0, 'rate': 50.0, 'category_id': self.category_basic.id, }) self.assertEqual(line.total, 500.0) def test_get_inputs(self): rule_with_input = self.env['hr.salary.rule'].create({ 'name': 'Rule with Input', 'code': 'INPUT_RULE', 'category_id': self.category_basic.id, 'amount_select': 'fix', 'amount_fix': 0.0, 'sequence': 50, }) self.env['hr.rule.input'].create({ 'name': 'Commission Input', 'code': 'COMMISSION', 'input_id': rule_with_input.id, }) self.structure.write({'rule_ids': [(4, rule_with_input.id)]}) date_from = date.today() date_to = date.today() inputs = self.env['hr.payslip'].get_inputs(self.contract, date_from, date_to) found_input = next((i for i in inputs if i['code'] == 'COMMISSION'), None) self.assertIsNotNone(found_input) self.assertEqual(found_input['contract_id'], self.contract.id)