diff --git a/hr_payroll_slip_ytd/__init__.py b/hr_payroll_slip_ytd/__init__.py new file mode 100755 index 00000000..0650744f --- /dev/null +++ b/hr_payroll_slip_ytd/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/hr_payroll_slip_ytd/__manifest__.py b/hr_payroll_slip_ytd/__manifest__.py new file mode 100755 index 00000000..5eacc7bf --- /dev/null +++ b/hr_payroll_slip_ytd/__manifest__.py @@ -0,0 +1,18 @@ +{ + 'name': 'Payroll Report Year to Date', + 'author': 'Hibou Corp. ', + 'version': '15.0.1.0.0', + 'category': 'Human Resources', + 'sequence': 95, + 'summary': 'Show YTD computations on Payslip Report', + 'description': """ +Show Year to Date (YTD) computations on Payslip Report. + """, + 'website': 'https://hibou.io/', + 'depends': ['hr_payroll'], + 'data': [ + 'views/payslip_views.xml', + ], + 'installable': True, + 'application': False, +} diff --git a/hr_payroll_slip_ytd/models/__init__.py b/hr_payroll_slip_ytd/models/__init__.py new file mode 100644 index 00000000..305e17f9 --- /dev/null +++ b/hr_payroll_slip_ytd/models/__init__.py @@ -0,0 +1 @@ +from . import payslip diff --git a/hr_payroll_slip_ytd/models/payslip.py b/hr_payroll_slip_ytd/models/payslip.py new file mode 100644 index 00000000..984f0d16 --- /dev/null +++ b/hr_payroll_slip_ytd/models/payslip.py @@ -0,0 +1,49 @@ +from odoo import models + + +class Payslip(models.Model): + _inherit = 'hr.payslip' + + def ytd(self, code, allow_draft=False): + to_date = self.date_to + from_date = str(self.date_to.year) + '-01-01' + state_allowed = ('done',) if not allow_draft else ('done', 'verify', 'draft') + self.env.cr.execute(""" + SELECT sum(pi.total) as total, + sum(pi.quantity) as quantity, + sum(pi.amount) as amount + FROM hr_payslip as hp + JOIN hr_payslip_line as pi ON hp.id = pi.slip_id + WHERE hp.employee_id = %s + AND hp.state in %s + AND hp.date_to >= %s AND hp.date_to <= %s AND pi.code = %s""", + (self.employee_id.id, state_allowed, from_date, to_date, code)) + res = self.env.cr.dictfetchone() + if res: + # Can return dictionary with NULL aka Nones + for key in res: + res[key] = res[key] or 0.0 + else: + res = {'total': 0.0, 'quantity': 0.0, 'amount': 0.0} + return res + + def worked_ytd(self, code, allow_draft=False): + to_date = self.date_to + from_date = str(self.date_to.year) + '-01-01' + state_allowed = ('done',) if not allow_draft else ('done', 'verify', 'draft') + self.env.cr.execute(""" + SELECT sum(wd.amount) as amount + FROM hr_payslip as hp + JOIN hr_payslip_worked_days as wd ON hp.id = wd.payslip_id + JOIN hr_work_entry_type as wt ON wd.work_entry_type_id = wt.id + WHERE hp.employee_id = %s + AND hp.state in %s + AND hp.date_to >= %s AND hp.date_to <= %s AND wt.code = %s""", + (self.employee_id.id, state_allowed, from_date, to_date, code)) + res = self.env.cr.dictfetchone() + if res: + # Can return dictionary with NULL aka Nones + res['amount'] = res.get('amount', 0.0) + else: + res = {'amount': 0.0} + return res diff --git a/hr_payroll_slip_ytd/tests/__init__.py b/hr_payroll_slip_ytd/tests/__init__.py new file mode 100644 index 00000000..63a156de --- /dev/null +++ b/hr_payroll_slip_ytd/tests/__init__.py @@ -0,0 +1 @@ +from . import test_hr_payslip_ytd diff --git a/hr_payroll_slip_ytd/tests/test_hr_payslip_ytd.py b/hr_payroll_slip_ytd/tests/test_hr_payslip_ytd.py new file mode 100644 index 00000000..17de71d8 --- /dev/null +++ b/hr_payroll_slip_ytd/tests/test_hr_payslip_ytd.py @@ -0,0 +1,54 @@ +from odoo.addons.hr_payroll.tests.common import TestPayslipBase +from odoo.tests.common import Form +from datetime import date + + +class TestPayslipYtd(TestPayslipBase): + def test_00_payslip_ytd(self): + # The common test leaves the contract in draft mode + richard_contract = self.richard_emp.contract_ids[0] + richard_contract.state = 'open' + # pay structure in TestPayslipBase is missing default Salary Rules + # I don't need all of them, but I would like to use Basic Salary + self.developer_pay_structure.write({ + 'rule_ids': [(4, self.env['hr.salary.rule'].search([('code', '=', 'BASIC')], limit=1).id)] + }) + + payslip_form = Form(self.env['hr.payslip']) + payslip_form.employee_id = self.richard_emp + payslip_form.date_from = date(2020, 1, 1) + payslip_form.date_to = date(2020, 1, 31) + richard_payslip = payslip_form.save() + + richard_payslip.compute_sheet() + richard_payslip.action_payslip_done() + richard_payslip.flush() # Didn't have to do this in 12, but now the test fails if I don't + + basic_line = richard_payslip.line_ids.filtered(lambda l: l.code == 'BASIC') + self.assertEqual(basic_line.amount, 5000.0) + self.assertEqual(basic_line.total, 5000.0) + ytd = richard_payslip.ytd(basic_line.code, allow_draft=False) + self.assertEqual(ytd, {'total': 5000.0, 'quantity': 1.0, 'amount': 5000.0}) + + # get worked days + self.assertTrue(richard_payslip.worked_days_line_ids) + worked_day_line = richard_payslip.worked_days_line_ids.filtered(lambda l: l.code == 'WORK100') + self.assertEqual(worked_day_line.amount, 5000.0) + worked_day_ytd = richard_payslip.worked_ytd(worked_day_line.code, allow_draft=False) + self.assertEqual(worked_day_ytd, {'amount': 5000.0}) + + payslip_form = Form(self.env['hr.payslip']) + payslip_form.employee_id = self.richard_emp + payslip_form.date_from = date(2020, 2, 1) + payslip_form.date_to = date(2020, 2, 29) + richard_payslip_next = payslip_form.save() + richard_payslip_next.compute_sheet() + richard_payslip_next.flush() + + basic_line = richard_payslip_next.line_ids.filtered(lambda l: l.code == 'BASIC') + self.assertEqual(basic_line.amount, 5000.0) + self.assertEqual(basic_line.total, 5000.0) + ytd = richard_payslip_next.ytd(basic_line.code, allow_draft=True) + self.assertEqual(ytd, {'total': 10000.0, 'quantity': 2.0, 'amount': 10000.0}) + ytd = richard_payslip_next.ytd(basic_line.code, allow_draft=False) + self.assertEqual(ytd, {'total': 5000.0, 'quantity': 1.0, 'amount': 5000.0}) diff --git a/hr_payroll_slip_ytd/views/payslip_views.xml b/hr_payroll_slip_ytd/views/payslip_views.xml new file mode 100644 index 00000000..34241ecd --- /dev/null +++ b/hr_payroll_slip_ytd/views/payslip_views.xml @@ -0,0 +1,51 @@ + + + + \ No newline at end of file