[MIG] hr_payroll_payment: for Odoo 14.0

Added actual payment to tests.
This commit is contained in:
Jared Kipe
2021-03-09 17:01:45 -08:00
parent 726c12d0d8
commit b1825e6808
5 changed files with 76 additions and 28 deletions

View File

@@ -19,6 +19,8 @@ When paying on a batch, a "Batch Payment" will be generated and linked to the wh
Adds Accounting Date field on Batch to use when creating slips with the batch's date. Adds Accounting Date field on Batch to use when creating slips with the batch's date.
Adds fiscal position mappings to set a fiscal position on the contract and have payslips map their accounts.
Tested Tested
------ ------

View File

@@ -3,7 +3,7 @@
{ {
'name': 'Payroll Payments', 'name': 'Payroll Payments',
'author': 'Hibou Corp. <hello@hibou.io>', 'author': 'Hibou Corp. <hello@hibou.io>',
'version': '13.0.1.1.0', 'version': '14.0.1.0.0',
'category': 'Human Resources', 'category': 'Human Resources',
'sequence': 95, 'sequence': 95,
'summary': 'Register payments for Payroll Payslips', 'summary': 'Register payments for Payroll Payslips',
@@ -13,7 +13,7 @@ Pay your Payroll
Hibou's Payroll Payments modifies, and abstracts, the way that the accounting for payslips is generated. Hibou's Payroll Payments modifies, and abstracts, the way that the accounting for payslips is generated.
In stock Odoo 13, journal entries are grouped by account and name, but has no linking to partners. In stock Odoo 14, journal entries are grouped by account and name, but has no linking to partners.
On the Payroll Journal, you can now select optional journal entry creation with the options: On the Payroll Journal, you can now select optional journal entry creation with the options:
@@ -29,6 +29,8 @@ When paying on a batch, a "Batch Payment" will be generated and linked to the wh
Adds Accounting Date field on Batch to use when creating slips with the batch's date. Adds Accounting Date field on Batch to use when creating slips with the batch's date.
Adds fiscal position mappings to set a fiscal position on the contract and have payslips map their accounts.
Tested Tested
------ ------
@@ -41,7 +43,6 @@ Passes original Payroll Accounting tests and additional ones for gouping behavio
'hibou_professional', 'hibou_professional',
], ],
'data': [ 'data': [
#'wizard/hr_payroll_register_payment_views.xml',
'views/account_views.xml', 'views/account_views.xml',
'views/hr_payslip_views.xml', 'views/hr_payslip_views.xml',
], ],

View File

@@ -65,7 +65,7 @@ class HrPayslip(models.Model):
def _payment_values(self, amount): def _payment_values(self, amount):
values = { values = {
'payment_reference': self.number, 'payment_reference': self.number,
'communication': self.number + ' - ' + self.name, 'ref': self.number + ' - ' + self.name,
'journal_id': self.move_id.journal_id.payroll_payment_journal_id.id, 'journal_id': self.move_id.journal_id.payroll_payment_journal_id.id,
'payment_method_id': self.move_id.journal_id.payroll_payment_method_id.id, 'payment_method_id': self.move_id.journal_id.payroll_payment_method_id.id,
'partner_type': 'supplier', 'partner_type': 'supplier',
@@ -82,20 +82,29 @@ class HrPayslip(models.Model):
return values return values
def action_register_payment(self): def action_register_payment(self):
if not all(slip.move_id.journal_id.payroll_payment_journal_id for slip in self): slips = self.filtered(lambda s: s.move_id.state in ('draft', 'posted') and not s.is_paid)
if not all(slip.move_id.journal_id.payroll_payment_journal_id for slip in slips):
raise UserError(_('Payroll Payment journal not configured on the existing entry\'s journal.')) raise UserError(_('Payroll Payment journal not configured on the existing entry\'s journal.'))
if not all(slip.move_id.journal_id.payroll_payment_method_id for slip in self): if not all(slip.move_id.journal_id.payroll_payment_method_id for slip in slips):
raise UserError(_('Payroll Payment method not configured on the existing entry\'s journal.')) raise UserError(_('Payroll Payment method not configured on the existing entry\'s journal.'))
# as of 14, you cannot reconcile to un-posted moves
# so if you are paying it, we must assume you want to post any draft entries
slip_moves = slips.mapped('move_id')
unposted_moves = slip_moves.filtered(lambda m: m.state == 'draft')
unposted_moves._post(soft=False)
payments = self.env['account.payment'] payments = self.env['account.payment']
for slip in self.filtered(lambda s: s.move_id and not s.is_paid): for slip in slips:
lines_to_pay = slip.move_id.line_ids.filtered(lambda l: l.partner_id == slip.employee_id.address_home_id lines_to_pay = slip.move_id.line_ids.filtered(lambda l: l.partner_id == slip.employee_id.address_home_id
and l.account_id == slip.employee_id.address_home_id.property_account_payable_id) and l.account_id == slip.employee_id.address_home_id.property_account_payable_id)
amount = sum(lines_to_pay.mapped('amount_residual')) amount = sum(lines_to_pay.mapped('amount_residual'))
if not amount:
continue
payment_values = slip._payment_values(amount) payment_values = slip._payment_values(amount)
payment = payments.create(payment_values) payment = payments.create(payment_values)
payment.post() payment.action_post()
lines_paid = payment.move_line_ids.filtered(lambda l: l.account_id == slip.employee_id.address_home_id.property_account_payable_id) lines_paid = payment.line_ids.filtered(lambda l: l.account_id == slip.employee_id.address_home_id.property_account_payable_id)
lines_to_reconcile = lines_to_pay + lines_paid lines_to_reconcile = lines_to_pay + lines_paid
lines_to_reconcile.reconcile() lines_to_reconcile.reconcile()
payments += payment payments += payment
@@ -296,10 +305,10 @@ class HrPayslip(models.Model):
# The code below is called if there is an error in the balance between credit and debit sum. # The code below is called if there is an error in the balance between credit and debit sum.
if float_compare(credit_sum, debit_sum, precision_digits=precision) == -1: if float_compare(credit_sum, debit_sum, precision_digits=precision) == -1:
acc_id = slip.journal_id.default_credit_account_id.id acc_id = slip.journal_id.default_account_id.id
if not acc_id: if not acc_id:
raise UserError( raise UserError(
_('The Expense Journal "%s" has not properly configured the Credit Account!') % ( _('The Expense Journal "%s" has not properly configured the Default Account!') % (
slip.journal_id.name)) slip.journal_id.name))
existing_adjustment_line = ( existing_adjustment_line = (
line_id for line_id in line_ids if line_id['name'] == _('Adjustment Entry') line_id for line_id in line_ids if line_id['name'] == _('Adjustment Entry')
@@ -321,9 +330,9 @@ class HrPayslip(models.Model):
adjust_credit['credit'] = debit_sum - credit_sum adjust_credit['credit'] = debit_sum - credit_sum
elif float_compare(debit_sum, credit_sum, precision_digits=precision) == -1: elif float_compare(debit_sum, credit_sum, precision_digits=precision) == -1:
acc_id = slip.journal_id.default_debit_account_id.id acc_id = slip.journal_id.default_account_id.id
if not acc_id: if not acc_id:
raise UserError(_('The Expense Journal "%s" has not properly configured the Debit Account!') % ( raise UserError(_('The Expense Journal "%s" has not properly configured the Default Account!') % (
slip.journal_id.name)) slip.journal_id.name))
existing_adjustment_line = ( existing_adjustment_line = (
line_id for line_id in line_ids if line_id['name'] == _('Adjustment Entry') line_id for line_id in line_ids if line_id['name'] == _('Adjustment Entry')
@@ -378,10 +387,10 @@ class HrPayslip(models.Model):
# The code below is called if there is an error in the balance between credit and debit sum. # The code below is called if there is an error in the balance between credit and debit sum.
if float_compare(credit_sum, debit_sum, precision_digits=precision) == -1: if float_compare(credit_sum, debit_sum, precision_digits=precision) == -1:
acc_id = slip.journal_id.default_credit_account_id.id acc_id = slip.journal_id.default_account_id.id
if not acc_id: if not acc_id:
raise UserError( raise UserError(
_('The Expense Journal "%s" has not properly configured the Credit Account!') % ( _('The Expense Journal "%s" has not properly configured the Default Account!') % (
slip.journal_id.name)) slip.journal_id.name))
existing_adjustment_line = ( existing_adjustment_line = (
line_id for line_id in line_ids if line_id['name'] == _('Adjustment Entry') line_id for line_id in line_ids if line_id['name'] == _('Adjustment Entry')
@@ -403,9 +412,9 @@ class HrPayslip(models.Model):
adjust_credit['credit'] = debit_sum - credit_sum adjust_credit['credit'] = debit_sum - credit_sum
elif float_compare(debit_sum, credit_sum, precision_digits=precision) == -1: elif float_compare(debit_sum, credit_sum, precision_digits=precision) == -1:
acc_id = slip.journal_id.default_debit_account_id.id acc_id = slip.journal_id.default_account_id.id
if not acc_id: if not acc_id:
raise UserError(_('The Expense Journal "%s" has not properly configured the Debit Account!') % ( raise UserError(_('The Expense Journal "%s" has not properly configured the Default Account!') % (
slip.journal_id.name)) slip.journal_id.name))
existing_adjustment_line = ( existing_adjustment_line = (
line_id for line_id in line_ids if line_id['name'] == _('Adjustment Entry') line_id for line_id in line_ids if line_id['name'] == _('Adjustment Entry')
@@ -529,10 +538,10 @@ class HrPayslip(models.Model):
# The code below is called if there is an error in the balance between credit and debit sum. # The code below is called if there is an error in the balance between credit and debit sum.
if float_compare(credit_sum, debit_sum, precision_digits=precision) == -1: if float_compare(credit_sum, debit_sum, precision_digits=precision) == -1:
acc_id = slip.journal_id.default_credit_account_id.id acc_id = slip.journal_id.default_account_id.id
if not acc_id: if not acc_id:
raise UserError( raise UserError(
_('The Expense Journal "%s" has not properly configured the Credit Account!') % ( _('The Expense Journal "%s" has not properly configured the Default Account!') % (
slip.journal_id.name)) slip.journal_id.name))
existing_adjustment_line = ( existing_adjustment_line = (
line_id for line_id in line_ids if line_id['name'] == _('Adjustment Entry') line_id for line_id in line_ids if line_id['name'] == _('Adjustment Entry')
@@ -554,9 +563,9 @@ class HrPayslip(models.Model):
adjust_credit['credit'] = debit_sum - credit_sum adjust_credit['credit'] = debit_sum - credit_sum
elif float_compare(debit_sum, credit_sum, precision_digits=precision) == -1: elif float_compare(debit_sum, credit_sum, precision_digits=precision) == -1:
acc_id = slip.journal_id.default_debit_account_id.id acc_id = slip.journal_id.default_account_id.id
if not acc_id: if not acc_id:
raise UserError(_('The Expense Journal "%s" has not properly configured the Debit Account!') % ( raise UserError(_('The Expense Journal "%s" has not properly configured the Default Account!') % (
slip.journal_id.name)) slip.journal_id.name))
existing_adjustment_line = ( existing_adjustment_line = (
line_id for line_id in line_ids if line_id['name'] == _('Adjustment Entry') line_id for line_id in line_ids if line_id['name'] == _('Adjustment Entry')

View File

@@ -4,20 +4,41 @@
import odoo.tests import odoo.tests
from odoo.addons.hr_payroll_account.tests.test_hr_payroll_account import TestHrPayrollAccount as TestBase from odoo.addons.hr_payroll_account.tests.test_hr_payroll_account import TestHrPayrollAccount as TestBase
@odoo.tests.tagged('post_install', '-at_install') @odoo.tests.tagged('post_install', '-at_install')
class TestHrPayrollAccount(TestBase): class TestHrPayrollAccount(TestBase):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
# upstream code no-longer sets the journal, though it does create it....
self.hr_structure_softwaredeveloper.journal_id = self.account_journal
# upstream code no-longer has any accounts (just makes journal entries without any lines)
demo_account = self.env.ref('hr_payroll_account.demo_account')
self.hr_structure_softwaredeveloper.rule_ids.filtered(lambda r: r.code == 'HRA').account_debit = demo_account
# Need a default account as there will be adjustment lines equal and opposite to the above PT rule...
self.account_journal.default_account_id = demo_account
# Two employees, but in stock tests they share the same partner... # Two employees, but in stock tests they share the same partner...
self.hr_employee_mark.address_home_id = self.env['res.partner'].create({ self.hr_employee_mark.address_home_id = self.env['res.partner'].create({
'name': 'employee_mark', 'name': 'employee_mark',
}) })
# This rule has a partner, and is the only one with any accounting side effects. # This rule has a partner, and is the only one with any accounting side effects.
# Remove partner to use the home address... # Remove partner to use the home address...
self.rule = self.env.ref('hr_payroll.hr_salary_rule_houserentallowance1') self.rule = self.hr_structure_softwaredeveloper.rule_ids.filtered(lambda r: r.code == 'HRA')
self.rule.partner_id = False self.rule.partner_id = False
# configure journal to be able to make payments
ap = self.hr_employee_mark.address_home_id.property_account_payable_id
self.assertTrue(ap)
# note there is no NET rule, so I just use a random allowance with fixed 800.0 amount
net_rule = self.hr_structure_softwaredeveloper.rule_ids.filtered(lambda r: r.code == 'CA')
self.assertTrue(net_rule)
net_rule.account_credit = ap
bank_journal = self.env['account.journal'].search([('type', '=', 'bank')], limit=1)
self.account_journal.payroll_payment_journal_id = bank_journal
self.account_journal.payroll_payment_method_id = bank_journal.outbound_payment_method_ids[0]
def _setup_fiscal_position(self): def _setup_fiscal_position(self):
account_rule_debit = self.rule.account_debit account_rule_debit = self.rule.account_debit
self.assertTrue(account_rule_debit) self.assertTrue(account_rule_debit)
@@ -47,12 +68,12 @@ class TestHrPayrollAccount(TestBase):
def test_00_fiscal_position(self): def test_00_fiscal_position(self):
self._setup_fiscal_position() self._setup_fiscal_position()
self.test_00_hr_payslip_run() self.test_00_hr_payslip_run()
self.assertEqual(len(self.payslip_run.slip_ids.mapped('move_id.line_ids.account_id')), 2) self.assertEqual(len(self.payslip_run.slip_ids.mapped('move_id.line_ids.account_id')), 3)
def test_00_fiscal_position_empty(self): def test_00_fiscal_position_empty(self):
self._setup_fiscal_position_empty() self._setup_fiscal_position_empty()
self.test_00_hr_payslip_run() self.test_00_hr_payslip_run()
self.assertEqual(len(self.payslip_run.slip_ids.mapped('move_id.line_ids.account_id')), 1) self.assertEqual(len(self.payslip_run.slip_ids.mapped('move_id.line_ids.account_id')), 2)
def test_01_hr_payslip_run(self): def test_01_hr_payslip_run(self):
# Grouped method groups but has partners. # Grouped method groups but has partners.
@@ -61,16 +82,21 @@ class TestHrPayrollAccount(TestBase):
self.assertEqual(len(self.payslip_run.slip_ids), 3) self.assertEqual(len(self.payslip_run.slip_ids), 3)
self.assertEqual(len(self.payslip_run.slip_ids.mapped('move_id')), 1) self.assertEqual(len(self.payslip_run.slip_ids.mapped('move_id')), 1)
self.assertEqual(len(self.payslip_run.slip_ids.mapped('move_id.line_ids.partner_id')), 2) self.assertEqual(len(self.payslip_run.slip_ids.mapped('move_id.line_ids.partner_id')), 2)
# what is going on with the 3rd one?!
slips_to_pay = self.payslip_run.slip_ids
action = slips_to_pay.action_register_payment()
payment_ids = action['res_ids']
self.assertEqual(len(payment_ids), 2)
def test_01_fiscal_position(self): def test_01_fiscal_position(self):
self._setup_fiscal_position() self._setup_fiscal_position()
self.test_01_hr_payslip_run() self.test_01_hr_payslip_run()
self.assertEqual(len(self.payslip_run.slip_ids.mapped('move_id.line_ids.account_id')), 2) self.assertEqual(len(self.payslip_run.slip_ids.mapped('move_id.line_ids.account_id')), 3)
def test_01_fiscal_position_empty(self): def test_01_fiscal_position_empty(self):
self._setup_fiscal_position_empty() self._setup_fiscal_position_empty()
self.test_01_hr_payslip_run() self.test_01_hr_payslip_run()
self.assertEqual(len(self.payslip_run.slip_ids.mapped('move_id.line_ids.account_id')), 1) self.assertEqual(len(self.payslip_run.slip_ids.mapped('move_id.line_ids.account_id')), 2)
def test_01_2_hr_payslip_run(self): def test_01_2_hr_payslip_run(self):
# Payslip method makes an entry per payslip # Payslip method makes an entry per payslip
@@ -80,13 +106,20 @@ class TestHrPayrollAccount(TestBase):
self.assertEqual(len(self.payslip_run.slip_ids), 3) self.assertEqual(len(self.payslip_run.slip_ids), 3)
self.assertEqual(len(self.payslip_run.slip_ids.mapped('move_id')), 3) self.assertEqual(len(self.payslip_run.slip_ids.mapped('move_id')), 3)
self.assertEqual(len(self.payslip_run.slip_ids.mapped('move_id.line_ids.partner_id')), 2) self.assertEqual(len(self.payslip_run.slip_ids.mapped('move_id.line_ids.partner_id')), 2)
slips_to_pay = self.payslip_run.slip_ids
# what is going on with the 3rd one?!
# it is possible to filter it out, but it doesn't change it
self.assertEqual(len(slips_to_pay), 3)
action = slips_to_pay.action_register_payment()
payment_ids = action['res_ids']
self.assertEqual(len(payment_ids), 2)
def test_01_2_fiscal_position(self): def test_01_2_fiscal_position(self):
self._setup_fiscal_position() self._setup_fiscal_position()
self.test_01_2_hr_payslip_run() self.test_01_2_hr_payslip_run()
self.assertEqual(len(self.payslip_run.slip_ids.mapped('move_id.line_ids.account_id')), 2) self.assertEqual(len(self.payslip_run.slip_ids.mapped('move_id.line_ids.account_id')), 3)
def test_01_2_fiscal_position_empty(self): def test_01_2_fiscal_position_empty(self):
self._setup_fiscal_position_empty() self._setup_fiscal_position_empty()
self.test_01_2_hr_payslip_run() self.test_01_2_hr_payslip_run()
self.assertEqual(len(self.payslip_run.slip_ids.mapped('move_id.line_ids.account_id')), 1) self.assertEqual(len(self.payslip_run.slip_ids.mapped('move_id.line_ids.account_id')), 2)

View File

@@ -13,6 +13,9 @@
<field name="payroll_payment_method_refund_id" domain="[('payment_type', '=', 'inbound')]"/> <field name="payroll_payment_method_refund_id" domain="[('payment_type', '=', 'inbound')]"/>
</group> </group>
</xpath> </xpath>
<xpath expr="//page[@name='bank_account']//field[@name='code']" position="before">
<field name="default_account_id" string="Default Account" attrs="{'invisible': [('type', '!=', 'general')]}" />
</xpath>
</field> </field>
</record> </record>
</odoo> </odoo>