diff --git a/hr_payroll_payment/README.md b/hr_payroll_payment/README.md index bf2c362e..b35dfe73 100644 --- a/hr_payroll_payment/README.md +++ b/hr_payroll_payment/README.md @@ -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 fiscal position mappings to set a fiscal position on the contract and have payslips map their accounts. + Tested ------ diff --git a/hr_payroll_payment/__manifest__.py b/hr_payroll_payment/__manifest__.py index 31514164..1ed17fdd 100755 --- a/hr_payroll_payment/__manifest__.py +++ b/hr_payroll_payment/__manifest__.py @@ -3,7 +3,7 @@ { 'name': 'Payroll Payments', 'author': 'Hibou Corp. ', - 'version': '13.0.1.1.0', + 'version': '14.0.1.0.0', 'category': 'Human Resources', 'sequence': 95, '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. -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: @@ -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 fiscal position mappings to set a fiscal position on the contract and have payslips map their accounts. + Tested ------ @@ -41,7 +43,6 @@ Passes original Payroll Accounting tests and additional ones for gouping behavio 'hibou_professional', ], 'data': [ - #'wizard/hr_payroll_register_payment_views.xml', 'views/account_views.xml', 'views/hr_payslip_views.xml', ], diff --git a/hr_payroll_payment/models/hr_payslip.py b/hr_payroll_payment/models/hr_payslip.py index b68514a1..91d9a2d5 100644 --- a/hr_payroll_payment/models/hr_payslip.py +++ b/hr_payroll_payment/models/hr_payslip.py @@ -65,7 +65,7 @@ class HrPayslip(models.Model): def _payment_values(self, amount): values = { '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, 'payment_method_id': self.move_id.journal_id.payroll_payment_method_id.id, 'partner_type': 'supplier', @@ -82,20 +82,29 @@ class HrPayslip(models.Model): return values 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.')) - 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.')) + # 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'] - 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 and l.account_id == slip.employee_id.address_home_id.property_account_payable_id) amount = sum(lines_to_pay.mapped('amount_residual')) + if not amount: + continue payment_values = slip._payment_values(amount) payment = payments.create(payment_values) - payment.post() - lines_paid = payment.move_line_ids.filtered(lambda l: l.account_id == slip.employee_id.address_home_id.property_account_payable_id) + payment.action_post() + 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.reconcile() 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. 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: 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)) existing_adjustment_line = ( 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 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: - 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)) existing_adjustment_line = ( 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. 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: 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)) existing_adjustment_line = ( 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 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: - 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)) existing_adjustment_line = ( 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. 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: 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)) existing_adjustment_line = ( 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 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: - 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)) existing_adjustment_line = ( line_id for line_id in line_ids if line_id['name'] == _('Adjustment Entry') diff --git a/hr_payroll_payment/tests/test_hr_payroll_account.py b/hr_payroll_payment/tests/test_hr_payroll_account.py index d57cdddf..2d1e0013 100644 --- a/hr_payroll_payment/tests/test_hr_payroll_account.py +++ b/hr_payroll_payment/tests/test_hr_payroll_account.py @@ -4,20 +4,41 @@ import odoo.tests from odoo.addons.hr_payroll_account.tests.test_hr_payroll_account import TestHrPayrollAccount as TestBase + @odoo.tests.tagged('post_install', '-at_install') class TestHrPayrollAccount(TestBase): def setUp(self): 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... self.hr_employee_mark.address_home_id = self.env['res.partner'].create({ 'name': 'employee_mark', }) + # This rule has a partner, and is the only one with any accounting side effects. # 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 + # 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): account_rule_debit = self.rule.account_debit self.assertTrue(account_rule_debit) @@ -47,12 +68,12 @@ class TestHrPayrollAccount(TestBase): def test_00_fiscal_position(self): self._setup_fiscal_position() 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): self._setup_fiscal_position_empty() 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): # 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.mapped('move_id')), 1) 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): self._setup_fiscal_position() 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): self._setup_fiscal_position_empty() 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): # 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.mapped('move_id')), 3) 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): self._setup_fiscal_position() 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): self._setup_fiscal_position_empty() 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) diff --git a/hr_payroll_payment/views/account_views.xml b/hr_payroll_payment/views/account_views.xml index 26f5e875..e81d5dee 100644 --- a/hr_payroll_payment/views/account_views.xml +++ b/hr_payroll_payment/views/account_views.xml @@ -13,6 +13,9 @@ + + + \ No newline at end of file