Initial commit: OCA Payroll packages (5 packages)

This commit is contained in:
Ernad Husremovic 2025-08-29 15:43:05 +02:00
commit d19274f581
407 changed files with 214057 additions and 0 deletions

View file

@ -0,0 +1,4 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import test_hr_fiscalyear
from . import test_payslip

View file

@ -0,0 +1,393 @@
# Copyright 2015 Savoir-faire Linux. All Rights Reserved.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from datetime import date, datetime
from dateutil.relativedelta import relativedelta
from odoo import fields
from odoo.exceptions import UserError, ValidationError
from odoo.tests import common
class TestHrFiscalyear(common.TransactionCase):
def setUp(self):
super(TestHrFiscalyear, self).setUp()
self.user_model = self.env["res.users"]
self.company_model = self.env["res.company"]
self.payslip_model = self.env["hr.payslip"]
self.run_model = self.env["hr.payslip.run"]
self.fy_model = self.env["hr.fiscalyear"]
self.period_model = self.env["hr.period"]
self.data_range_type_model = self.env["date.range.type"]
self.company = self.env.ref("base.main_company")
self.today = fields.Datetime.now().date()
self.type_fy = self.create_data_range_type("test_hr_fy", "fy")
self.type = self.create_data_range_type("test_hr_period", "per")
self.vals = {
"company_id": self.company.id,
"date_start": "2015-01-01",
"date_end": "2015-12-31",
"schedule_pay": "monthly",
"type_id": self.type.id,
"payment_day": "2",
"payment_weekday": "0",
"payment_week": "1",
"name": "Test",
}
def create_data_range_type(self, name, hr_type, company=False):
if not company:
company = self.company
vals = {
"name": name,
"active": True,
"company_id": company.id,
}
if hr_type == "per":
vals.update(hr_period=True)
else:
vals.update(hr_fiscal_year=True)
return self.data_range_type_model.create(vals)
def create_fiscal_year(self, vals=None):
if vals is None:
vals = {}
if not vals.get("type_id"):
vals["type_id"] = self.fy_model._default_type().id
if not vals.get("date_start"):
vals["date_start"] = self.fy_model._default_date_start()
if not vals.get("date_end"):
vals["date_end"] = self.fy_model._default_date_end()
if vals.get("company_id"):
vals["company_id"] = vals["company_id"]
if vals.get("schedule_pay"):
vals["schedule_pay"] = vals["schedule_pay"]
self.vals.update(vals)
return self.fy_model.create(self.vals)
def get_periods(self, fiscal_year):
return fiscal_year.period_ids.sorted(key=lambda p: p.date_start)
def check_period(self, period, date_start, date_end, date_payment):
if date_start:
self.assertEqual(str(period.date_start), date_start)
if date_end:
self.assertEqual(str(period.date_end), date_end)
if date_payment:
self.assertEqual(str(period.date_payment), date_payment)
def test_fy_change_scheduled_pay(self):
fy = self.create_fiscal_year(
{
"date_start": "2015-01-01",
"date_end": "2015-12-31",
"type_id": self.type_fy.id,
}
)
fy.schedule_pay = "weekly"
fy.onchange_schedule()
self.assertEqual(fy.name, "2015 - Weekly (52)")
def test_confirm_periods(self):
fy = self.create_fiscal_year(
{"type_id": self.type_fy.id, "company_id": self.company.id}
)
with self.assertRaises(UserError):
fy.button_confirm()
fy.create_periods()
periods = self.get_periods(fy)
fy.button_confirm()
self.assertEqual(periods[0].state, "open")
self.assertEqual(periods[1].state, "draft")
def test_create_periods_monthly(self):
fy = self.create_fiscal_year(
{
"date_start": "2015-01-01",
"date_end": "2015-12-31",
"type_id": self.type_fy.id,
}
)
fy.create_periods()
periods = self.get_periods(fy)
self.assertEqual(len(periods), 12)
self.check_period(periods[0], "2015-01-01", "2015-01-31", "2015-02-02")
self.check_period(periods[1], "2015-02-01", "2015-02-28", "2015-03-02")
self.check_period(periods[2], "2015-03-01", "2015-03-31", "2015-04-02")
self.check_period(periods[11], "2015-12-01", "2015-12-31", "2016-01-02")
def test_create_periods_monthly_custom_year(self):
fy = self.create_fiscal_year(
{
"date_start": "2015-03-16",
"date_end": "2016-03-15",
"type_id": self.type_fy.id,
}
)
fy.create_periods()
periods = self.get_periods(fy)
self.assertEqual(len(periods), 12)
self.check_period(periods[0], "2015-03-16", "2015-04-15", "2015-04-17")
self.check_period(periods[1], "2015-04-16", "2015-05-15", "2015-05-17")
self.check_period(periods[2], "2015-05-16", "2015-06-15", "2015-06-17")
self.check_period(periods[11], "2016-02-16", "2016-03-15", "2016-03-17")
def test_create_periods_semi_monthly(self):
fy = self.create_fiscal_year(
{
"date_start": "2015-01-01",
"date_end": "2015-12-31",
"schedule_pay": "semi-monthly",
"type_id": self.type_fy.id,
}
)
fy.create_periods()
periods = self.get_periods(fy)
self.assertEqual(len(periods), 24)
self.check_period(periods[0], "2015-01-01", "2015-01-15", "2015-01-17")
self.check_period(periods[1], "2015-01-16", "2015-01-31", "2015-02-02")
self.check_period(periods[2], "2015-02-01", "2015-02-15", "2015-02-17")
self.check_period(periods[3], "2015-02-16", "2015-02-28", "2015-03-02")
self.check_period(periods[23], "2015-12-16", "2015-12-31", "2016-01-02")
def test_create_periods_semi_monthly_custom_year(self):
fy = self.create_fiscal_year(
{
"date_start": "2015-03-20",
"date_end": "2016-03-19",
"schedule_pay": "semi-monthly",
"type_id": self.type_fy.id,
}
)
fy.create_periods()
periods = self.get_periods(fy)
self.assertEqual(len(periods), 24)
self.check_period(periods[0], "2015-03-20", "2015-04-03", "2015-04-05")
self.check_period(periods[1], "2015-04-04", "2015-04-19", "2015-04-21")
self.check_period(periods[2], "2015-04-20", "2015-05-04", "2015-05-06")
self.check_period(periods[22], "2016-02-20", "2016-03-05", "2016-03-07")
self.check_period(periods[23], "2016-03-06", "2016-03-19", "2016-03-21")
def test_create_fy_wrong_dates(self):
with self.assertRaises(ValidationError):
self.create_fiscal_year(
{
"schedule_pay": "annually",
"date_start": "2015-12-31",
"date_end": "2015-01-01",
"type_id": self.type_fy.id,
}
)
def test_create_periods_annually(self):
fy = self.create_fiscal_year(
{
"date_start": "2015-01-01",
"date_end": "2015-12-31",
"schedule_pay": "annually",
"type_id": self.type_fy.id,
}
)
fy.create_periods()
periods = self.get_periods(fy)
self.assertEqual(len(periods), 1)
self.check_period(periods[0], "2015-01-01", "2015-12-31", "2016-01-02")
def test_create_periods_semi_annually(self):
fy = self.create_fiscal_year(
{
"date_start": "2015-01-01",
"date_end": "2015-12-31",
"schedule_pay": "semi-annually",
"type_id": self.type_fy.id,
}
)
fy.create_periods()
periods = self.get_periods(fy)
self.assertEqual(len(periods), 2)
self.check_period(periods[0], "2015-01-01", "2015-06-30", "2015-07-02")
def test_create_periods_annually_custom_year(self):
fy = self.create_fiscal_year(
{
"date_start": "2015-03-16",
"date_end": "2016-03-15",
"schedule_pay": "annually",
"type_id": self.type_fy.id,
}
)
fy.create_periods()
periods = self.get_periods(fy)
self.assertEqual(len(periods), 1)
self.check_period(periods[0], "2015-03-16", "2016-03-15", "2016-03-17")
def test_create_periods_weekly(self):
fy = self.create_fiscal_year(
{
"date_start": "2015-01-01",
"date_end": "2015-12-31",
"schedule_pay": "weekly",
"type_id": self.type_fy.id,
}
)
fy.create_periods()
periods = self.get_periods(fy)
self.assertEqual(len(periods), 52)
self.check_period(periods[0], "2015-01-01", "2015-01-07", "2015-01-11")
self.check_period(periods[1], "2015-01-08", "2015-01-14", "2015-01-18")
self.check_period(periods[2], "2015-01-15", "2015-01-21", "2015-01-25")
self.check_period(periods[51], "2015-12-24", "2015-12-30", "2016-01-03")
def test_create_periods_weekly_payment_same_week(self):
fy = self.create_fiscal_year(
{
"schedule_pay": "weekly",
"payment_week": "0",
"date_start": "2015-01-01",
"date_end": "2015-12-31",
"type_id": self.type_fy.id,
}
)
fy.create_periods()
periods = self.get_periods(fy)
self.assertEqual(len(periods), 52)
self.assertEqual(periods[0].date_payment, date(2015, 1, 4))
self.assertEqual(periods[1].date_payment, date(2015, 1, 11))
self.assertEqual(periods[2].date_payment, date(2015, 1, 18))
self.assertEqual(periods[51].date_payment, date(2015, 12, 27))
def test_create_periods_weekly_payment_2_weeks(self):
fy = self.create_fiscal_year(
{
"schedule_pay": "weekly",
"payment_week": "2",
"date_start": "2015-01-01",
"date_end": "2015-12-31",
"type_id": self.type_fy.id,
}
)
fy.create_periods()
periods = self.get_periods(fy)
self.assertEqual(len(periods), 52)
self.assertEqual(periods[0].date_payment, date(2015, 1, 18))
self.assertEqual(periods[1].date_payment, date(2015, 1, 25))
self.assertEqual(periods[2].date_payment, date(2015, 2, 1))
self.assertEqual(periods[51].date_payment, date(2016, 1, 10))
def test_create_periods_monthly_payment_fifth_day(self):
fy = self.create_fiscal_year(
{
"payment_day": "5",
"date_start": "2015-01-01",
"date_end": "2015-12-31",
"type_id": self.type_fy.id,
}
)
fy.create_periods()
periods = self.get_periods(fy)
self.assertEqual(len(periods), 12)
self.assertEqual(periods[0].date_payment, date(2015, 2, 5))
self.assertEqual(periods[1].date_payment, date(2015, 3, 5))
self.assertEqual(periods[2].date_payment, date(2015, 4, 5))
self.assertEqual(periods[11].date_payment, date(2016, 1, 5))
def test_create_periods_monthly_payment_last_day(self):
fy = self.create_fiscal_year(
{
"payment_day": "0",
"date_start": "2015-01-01",
"date_end": "2015-12-31",
"type_id": self.type_fy.id,
}
)
fy.create_periods()
periods = self.get_periods(fy)
self.assertEqual(len(periods), 12)
self.assertEqual(periods[0].date_payment, date(2015, 1, 31))
self.assertEqual(periods[1].date_payment, date(2015, 2, 28))
self.assertEqual(periods[2].date_payment, date(2015, 3, 31))
self.assertEqual(periods[11].date_payment, date(2015, 12, 31))
def test_create_periods_semi_monthly_payment_fifth_day(self):
fy = self.create_fiscal_year(
{
"payment_day": "5",
"date_start": "2015-01-01",
"date_end": "2015-12-31",
"type_id": self.type_fy.id,
}
)
fy.create_periods()
periods = self.get_periods(fy)
self.assertEqual(len(periods), 12)
self.assertEqual(periods[0].date_payment, date(2015, 2, 5))
self.assertEqual(periods[1].date_payment, date(2015, 3, 5))
self.assertEqual(periods[2].date_payment, date(2015, 4, 5))
self.assertEqual(periods[11].date_payment, date(2016, 1, 5))
def test_create_periods_semi_monthly_payment_last_day(self):
fy = self.create_fiscal_year(
{
"schedule_pay": "semi-monthly",
"payment_day": "0",
"date_start": "2015-03-20",
"date_end": "2016-03-19",
"type_id": self.type_fy.id,
}
)
fy.create_periods()
periods = self.get_periods(fy)
self.assertEqual(len(periods), 24)
self.assertEqual(periods[0].date_payment, date(2015, 4, 3))
self.assertEqual(periods[1].date_payment, date(2015, 4, 19))
self.assertEqual(periods[2].date_payment, date(2015, 5, 4))
self.assertEqual(periods[22].date_payment, date(2016, 3, 5))
self.assertEqual(periods[23].date_payment, date(2016, 3, 19))
def test_cron_create_next_fiscal_year(self):
# if we are in 2024, it should create the periods for 2025
current_year = datetime.now().year
current_fiscal_year = self.fy_model.search(
[
("date_start", "=", f"{current_year}-01-01"),
("date_end", "=", f"{current_year}-12-31"),
],
limit=1,
)
# If 2024 doesn't exist, create it
if not current_fiscal_year:
current_fiscal_year = self.create_fiscal_year(
{
"date_start": f"{current_year}-01-01",
"date_end": f"{current_year}-12-31",
}
)
current_fiscal_year.create_periods()
next_fiscal_year = self.env["hr.fiscalyear"].cron_create_next_fiscal_year()
periods = self.get_periods(next_fiscal_year)
# Ensure the periods are continuous and within the fiscal year dates
for i, period in enumerate(periods):
if i == 0:
self.assertEqual(period.date_start, next_fiscal_year.date_start)
else:
self.assertEqual(
period.date_start, periods[i - 1].date_end + relativedelta(days=1)
)
if i == len(periods) - 1:
self.assertEqual(period.date_end, next_fiscal_year.date_end)
# Check that the first period of next fiscal year starts 1 day after
current_year_periods = self.get_periods(current_fiscal_year)
last_period_current_year = current_year_periods[-1]
first_period_next_year = periods[0]
self.assertEqual(
first_period_next_year.date_start,
last_period_current_year.date_end + relativedelta(days=1),
)
# Check that the first period end is on last of January
self.assertEqual(
first_period_next_year.date_end, datetime(current_year + 1, 1, 31).date()
)

View file

@ -0,0 +1,204 @@
# Copyright 2015 Savoir-faire Linux. All Rights Reserved.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo.exceptions import UserError
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT as DF
from . import test_hr_fiscalyear
class PayslipCase(test_hr_fiscalyear.TestHrFiscalyear):
def setUp(self):
result = super(PayslipCase, self).setUp()
self.payslip_obj = self.env["hr.payslip"]
self.run_obj = self.env["hr.payslip.run"]
self.wzd_obj = self.env["hr.payslip.employees"]
user_id = (
self.env["res.users"]
.create(
{
"name": "Test User",
"login": "user",
"email": "test.user@example.com",
}
)
.id
)
resource_id = (
self.env["resource.resource"]
.create(
{
"name": "Test resource",
"resource_type": "user",
"user_id": user_id,
"company_id": self.company.id,
}
)
.id
)
self.employee = self.env["hr.employee"].create(
{
"resource_id": resource_id,
"name": "Employee 1",
}
)
self.company2 = self.env["res.company"].create({"name": "Acme"})
self.type_fy2 = self.create_data_range_type("test_hr_fy", "fy", self.company2)
self.type2 = self.create_data_range_type("test_hr_period", "per", self.company2)
# create Contract
self.contract = self.create_contract("Contract 1", "monthly")
self.contract2 = self.create_contract("Contract 2", "quarterly")
return result
def create_contract(self, name, schedule_pay):
contract_dict = {
"name": name,
"employee_id": self.employee.id,
"wage": 10.0,
"schedule_pay": schedule_pay,
}
return self.env["hr.contract"].create(contract_dict)
def _prepare_payslip_data(
self, date_from, date_to, date_payment, company, run=None
):
data = {
"employee_id": self.employee.id,
"contract_id": self.contract.id,
"date_from": date_from,
"date_to": date_to,
"date_payment": date_payment,
"company_id": company.id,
}
if run:
data.update(payslip_run_id=run.id)
return data
def _prepare_payslip_run_data(self, period):
schedule = self.env["hr.payslip.run"].get_default_schedule(period.company_id.id)
data = {
"name": period.name,
"date_start": period.date_start,
"date_end": period.date_end,
"date_payment": period.date_payment,
"hr_period_id": period.id,
"schedule_pay": schedule,
"company_id": period.company_id.id,
}
return data
def test_payslip(self):
fy = self.create_fiscal_year({"type_id": self.type_fy.id})
fy.create_periods()
periods = self.get_periods(fy)
fy.button_confirm()
self.assertEqual(periods[0].state, "open")
fy.button_set_to_draft()
self.assertEqual(periods[0].state, "draft")
date_from = periods[1].date_start
date_to = periods[1].date_end
move_date = periods[1].date_payment
company = periods[1].company_id
data = self._prepare_payslip_data(date_from, date_to, move_date, company)
payslip = self.payslip_obj.create(data)
payslip.hr_period_id = periods[0]
payslip.onchange_hr_period_id()
self.assertEqual(payslip.date_from, periods[0].date_start)
self.assertEqual(payslip.date_to, periods[0].date_end)
self.assertEqual(payslip.date_payment, periods[0].date_payment)
data = self._prepare_payslip_run_data(periods[0])
run = self.run_obj.create(data)
run.get_payslip_employees_wizard()
run.onchange_period_id()
for pay in run.slip_ids:
self.assertEqual(pay.date_from, periods[0].date_start)
self.assertEqual(pay.date_to, periods[0].date_end)
self.assertEqual(pay.date_payment, periods[0].date_payment)
with self.assertRaises(UserError):
periods[1].button_draft()
for pay in run.slip_ids:
pay.write({"state": "draft"})
with self.assertRaises(UserError):
run.close_payslip_run()
pay.write({"state": "draft"})
pay.action_payslip_done()
run.close_payslip_run()
next_period = fy.search_period(number=periods[0].number + 1)
self.assertEqual(next_period.state, "open")
run.draft_payslip_run()
for pay in run.slip_ids:
self.assertEqual(pay.state, "open")
self.assertEqual(fy.state, "open")
def test_payslip_batch_company(self):
fy = self.create_fiscal_year({"type_id": self.type_fy.id})
fy2 = self.create_fiscal_year(
{
"company_id": self.company2.id,
"date_start": "2016-01-01",
"date_end": "2016-12-31",
"type_id": self.type_fy2.id,
}
)
fy.create_periods()
fy2.create_periods()
periods = self.get_periods(fy)
periods2 = self.get_periods(fy2)
fy.button_confirm()
fy2.button_confirm()
self.assertEqual(periods2[0].state, "open")
date_from = periods[1].date_start
date_to = periods[1].date_end
move_date = periods[1].date_payment
data = self._prepare_payslip_data(date_from, date_to, move_date, self.company)
self.payslip_obj.create(data)
data = self._prepare_payslip_run_data(periods[0])
run = self.run_obj.create(data)
with self.assertRaises(UserError):
run.write({"company_id": self.company2.id})
run.write({"hr_period_id": periods2[0].id, "company_id": self.company2.id})
run.onchange_company_id()
self.assertEqual(run.hr_period_id.date_start.strftime(DF), "2016-01-01")
def test_contract(self):
fy = self.create_fiscal_year(
{
"name": "fy1",
"company_id": self.company.id,
"date_start": "2016-01-01",
"date_end": "2016-12-31",
"schedule_pay": "monthly",
"type_id": self.type_fy.id,
}
)
fy.create_periods()
fy2 = self.create_fiscal_year(
{
"name": "fy2",
"company_id": self.company.id,
"date_start": "2017-01-01",
"date_end": "2017-12-31",
"schedule_pay": "quarterly",
"type_id": self.type_fy.id,
}
)
fy2.create_periods()
periods = self.get_periods(fy)
fy.button_confirm()
fy2.button_confirm()
date_from = periods[1].date_start
date_to = periods[1].date_end
move_date = periods[1].date_payment
run_data = self._prepare_payslip_run_data(periods[1])
run = self.run_obj.create(run_data)
data = self._prepare_payslip_data(
date_from, date_to, move_date, self.company, run
)
payslip = self.payslip_obj.create(data)
self.assertEqual(payslip.hr_period_id, periods[1], "Wrong pay period")
payslip.contract_id = self.contract2
payslip.onchange_contract_period()
period = self.env["hr.period"].get_next_period(self.company.id, "quarterly")
self.assertEqual(payslip.hr_period_id, period)