Merge branch 'mig/15.0/hr_payroll_slip_ytd' into '15.0'

WIP: mig/15.0/hr_payroll_slip_ytd into 15.0

See merge request hibou-io/hibou-odoo/suite!1373
This commit is contained in:
Jared Kipe
2022-03-22 16:05:23 +00:00
7 changed files with 175 additions and 0 deletions

View File

@@ -0,0 +1 @@
from . import models

View File

@@ -0,0 +1,18 @@
{
'name': 'Payroll Report Year to Date',
'author': 'Hibou Corp. <hello@hibou.io>',
'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,
}

View File

@@ -0,0 +1 @@
from . import payslip

View File

@@ -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

View File

@@ -0,0 +1 @@
from . import test_hr_payslip_ytd

View File

@@ -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})

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<template id="report_payslip_inherit" name="Payslip YTD" inherit_id="hr_payroll.report_payslip">
<!-- Add YTD to basic salary summary -->
<xpath expr="//table[2]/tr" position="inside">
<t t-set="ytd" t-value="o.ytd('BASIC', allow_draft=True)"/>
<td><strong>YTD</strong></td>
<td><span t-esc="ytd.get('total', 0.0)" t-options="{&quot;widget&quot;: &quot;monetary&quot;, &quot;display_currency&quot;: o.company_id.currency_id}"/></td>
</xpath>
<!-- Revert the way quantity is represented on the payslip -->
<xpath expr="//div[@id='total']/table/thead/tr//th[3]" position="replace">
<th>Quantity/Number of Days</th>
</xpath>
<xpath expr="//div[@id='total']/table/tbody/span[2]//td[3]/span" position="attributes">
<attribute name="t-if"/>
</xpath>
<!-- Add YTD to the table head-->
<xpath expr="//div[@id='total']/table/thead/tr//th[last()]" position="after">
<th>Total</th>
<th>YTD Quantity</th>
<th>YTD Amount</th>
<th>YTD Total</th>
</xpath>
<!-- Add YTD worked days data -->
<xpath expr="//div[@id='total']/table/tbody/span[1]//td[last()]" position="after">
<t t-set="worked_ytd" t-value="o.worked_ytd(worked_days.code, allow_draft=True)"/>
<td/>
<td/>
<td><span t-esc="worked_ytd.get('amount', 0.0)" t-options="{&quot;widget&quot;: &quot;monetary&quot;, &quot;display_currency&quot;: o.company_id.currency_id}"/></td>
<td/>
</xpath>
<!-- Re-add column for line amount -->
<xpath expr="//div[@id='total']/table/tbody/span[2]//td[last()]" position="before">
<td><span t-esc="line.amount"
t-options='{"widget": "monetary", "display_currency": o.company_id.currency_id}'/></td>
</xpath>
<!-- Add YTD table data-->
<xpath expr="//div[@id='total']/table/tbody/span[2]//td[last()]" position="after">
<t t-set="ytd" t-value="o.ytd(line.code, allow_draft=True)"/>
<td><span t-esc="ytd.get('quantity', 0.0)"/></td>
<td><span t-esc="ytd.get('amount', 0.0)" t-options="{&quot;widget&quot;: &quot;monetary&quot;, &quot;display_currency&quot;: o.company_id.currency_id}"/></td>
<td><span t-esc="ytd.get('total', 0.0)" t-options="{&quot;widget&quot;: &quot;monetary&quot;, &quot;display_currency&quot;: o.company_id.currency_id}"/></td>
</xpath>
</template>
</odoo>