mirror of
https://gitlab.com/hibou-io/hibou-odoo/suite.git
synced 2025-01-20 12:37:31 +02:00
[IMP] hr_payroll_payment: add fiscal position on hr.contract and utilize when making journal entries for payslips
This commit is contained in:
@@ -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.0.0',
|
'version': '13.0.1.1.0',
|
||||||
'category': 'Human Resources',
|
'category': 'Human Resources',
|
||||||
'sequence': 95,
|
'sequence': 95,
|
||||||
'summary': 'Register payments for Payroll Payslips',
|
'summary': 'Register payments for Payroll Payslips',
|
||||||
|
|||||||
@@ -5,6 +5,13 @@ from odoo.exceptions import UserError, ValidationError
|
|||||||
from odoo.tools import float_compare, float_is_zero
|
from odoo.tools import float_compare, float_is_zero
|
||||||
|
|
||||||
|
|
||||||
|
class HrContract(models.Model):
|
||||||
|
_inherit = 'hr.contract'
|
||||||
|
|
||||||
|
payroll_fiscal_position_id = fields.Many2one('account.fiscal.position', 'Payroll Fiscal Position', domain="[('company_id', '=', company_id)]",
|
||||||
|
help="Used for mapping accounts when processing payslip journal entries.")
|
||||||
|
|
||||||
|
|
||||||
class HrPayslipRun(models.Model):
|
class HrPayslipRun(models.Model):
|
||||||
_inherit = 'hr.payslip.run'
|
_inherit = 'hr.payslip.run'
|
||||||
|
|
||||||
@@ -140,6 +147,7 @@ class HrPayslip(models.Model):
|
|||||||
# payslips_to_post}
|
# payslips_to_post}
|
||||||
# Hibou Customization: group with journal itself so that journal behavior can be derived.
|
# Hibou Customization: group with journal itself so that journal behavior can be derived.
|
||||||
# Hibou Customization: prefer slip's `date` over end of month
|
# Hibou Customization: prefer slip's `date` over end of month
|
||||||
|
# Hibou Customization: add an account mapping based on fiscal position
|
||||||
slip_mapped_data = {
|
slip_mapped_data = {
|
||||||
slip.struct_id.journal_id: {slip.date or fields.Date().end_of(slip.date_to, 'month'): self.env['hr.payslip']} for slip
|
slip.struct_id.journal_id: {slip.date or fields.Date().end_of(slip.date_to, 'month'): self.env['hr.payslip']} for slip
|
||||||
in
|
in
|
||||||
@@ -147,6 +155,8 @@ class HrPayslip(models.Model):
|
|||||||
for slip in payslips_to_post:
|
for slip in payslips_to_post:
|
||||||
slip_mapped_data[slip.struct_id.journal_id][slip.date or fields.Date().end_of(slip.date_to, 'month')] |= slip
|
slip_mapped_data[slip.struct_id.journal_id][slip.date or fields.Date().end_of(slip.date_to, 'month')] |= slip
|
||||||
|
|
||||||
|
account_map = payslips_to_post._generate_account_map()
|
||||||
|
|
||||||
for journal in slip_mapped_data: # For each journal_id.
|
for journal in slip_mapped_data: # For each journal_id.
|
||||||
"""
|
"""
|
||||||
All methods to generate journal entry should generate one or more
|
All methods to generate journal entry should generate one or more
|
||||||
@@ -154,9 +164,9 @@ class HrPayslip(models.Model):
|
|||||||
"""
|
"""
|
||||||
for slip_date in slip_mapped_data[journal]: # For each month.
|
for slip_date in slip_mapped_data[journal]: # For each month.
|
||||||
if hasattr(self, '_generate_move_' + str(journal.payroll_entry_type)):
|
if hasattr(self, '_generate_move_' + str(journal.payroll_entry_type)):
|
||||||
getattr(self, '_generate_move_' + str(journal.payroll_entry_type))(slip_mapped_data, journal, slip_date)
|
getattr(self, '_generate_move_' + str(journal.payroll_entry_type))(slip_mapped_data, journal, slip_date, account_map)
|
||||||
else:
|
else:
|
||||||
self._generate_move_original(slip_mapped_data, journal, slip_date)
|
self._generate_move_original(slip_mapped_data, journal, slip_date, account_map)
|
||||||
|
|
||||||
def _check_slips_employee_home_address(self):
|
def _check_slips_employee_home_address(self):
|
||||||
employees_missing_partner = self.mapped('employee_id').filtered(lambda e: not e.address_home_id)
|
employees_missing_partner = self.mapped('employee_id').filtered(lambda e: not e.address_home_id)
|
||||||
@@ -167,7 +177,17 @@ class HrPayslip(models.Model):
|
|||||||
if len(address_ap) > 1:
|
if len(address_ap) > 1:
|
||||||
raise UserError(_('Employee\'s private address account payable not the same for all addresses.'))
|
raise UserError(_('Employee\'s private address account payable not the same for all addresses.'))
|
||||||
|
|
||||||
def _process_journal_lines_grouped(self, line_ids, date, precision):
|
def _generate_account_map(self):
|
||||||
|
account_map = {}
|
||||||
|
rules = self.mapped('line_ids.salary_rule_id')
|
||||||
|
base_account_map = {a: a for a in (rules.mapped('account_debit') | rules.mapped('account_credit'))}
|
||||||
|
account_map[self.env['account.fiscal.position']] = base_account_map
|
||||||
|
fiscal_positions = self.mapped('contract_id.payroll_fiscal_position_id')
|
||||||
|
for fp in fiscal_positions:
|
||||||
|
account_map[fp] = fp.map_accounts(base_account_map.copy())
|
||||||
|
return account_map
|
||||||
|
|
||||||
|
def _process_journal_lines_grouped(self, line_ids, date, precision, account_map):
|
||||||
slip = self
|
slip = self
|
||||||
employee_partner_id = slip.employee_id.address_home_id.id
|
employee_partner_id = slip.employee_id.address_home_id.id
|
||||||
for line in slip.line_ids.filtered(lambda l: l.category_id):
|
for line in slip.line_ids.filtered(lambda l: l.category_id):
|
||||||
@@ -181,8 +201,10 @@ class HrPayslip(models.Model):
|
|||||||
amount += abs(tmp_line.total)
|
amount += abs(tmp_line.total)
|
||||||
if float_is_zero(amount, precision_digits=precision):
|
if float_is_zero(amount, precision_digits=precision):
|
||||||
continue
|
continue
|
||||||
debit_account_id = line.salary_rule_id.account_debit.id
|
debit_account = line.salary_rule_id.account_debit
|
||||||
credit_account_id = line.salary_rule_id.account_credit.id
|
debit_account_id = account_map[debit_account].id if debit_account else False
|
||||||
|
credit_account = line.salary_rule_id.account_credit
|
||||||
|
credit_account_id = account_map[credit_account].id if credit_account else False
|
||||||
partner_id = line.salary_rule_id.partner_id.id or employee_partner_id
|
partner_id = line.salary_rule_id.partner_id.id or employee_partner_id
|
||||||
|
|
||||||
if debit_account_id: # If the rule has a debit account.
|
if debit_account_id: # If the rule has a debit account.
|
||||||
@@ -247,7 +269,7 @@ class HrPayslip(models.Model):
|
|||||||
credit_line['debit'] += debit
|
credit_line['debit'] += debit
|
||||||
credit_line['credit'] += credit
|
credit_line['credit'] += credit
|
||||||
|
|
||||||
def _generate_move_grouped(self, slip_mapped_data, journal, slip_date):
|
def _generate_move_grouped(self, slip_mapped_data, journal, slip_date, account_map):
|
||||||
slip_mapped_data[journal][slip_date]._check_slips_employee_home_address()
|
slip_mapped_data[journal][slip_date]._check_slips_employee_home_address()
|
||||||
|
|
||||||
precision = self.env['decimal.precision'].precision_get('Payroll')
|
precision = self.env['decimal.precision'].precision_get('Payroll')
|
||||||
@@ -263,9 +285,10 @@ class HrPayslip(models.Model):
|
|||||||
}
|
}
|
||||||
|
|
||||||
for slip in slip_mapped_data[journal][slip_date]:
|
for slip in slip_mapped_data[journal][slip_date]:
|
||||||
|
slip_accounts = account_map[slip.contract_id.payroll_fiscal_position_id]
|
||||||
move_dict['narration'] += slip.number or '' + ' - ' + slip.employee_id.name or ''
|
move_dict['narration'] += slip.number or '' + ' - ' + slip.employee_id.name or ''
|
||||||
move_dict['narration'] += '\n'
|
move_dict['narration'] += '\n'
|
||||||
slip._process_journal_lines_grouped(line_ids, date, precision)
|
slip._process_journal_lines_grouped(line_ids, date, precision, slip_accounts)
|
||||||
|
|
||||||
for line_id in line_ids: # Get the debit and credit sum.
|
for line_id in line_ids: # Get the debit and credit sum.
|
||||||
debit_sum += line_id['debit']
|
debit_sum += line_id['debit']
|
||||||
@@ -327,12 +350,13 @@ class HrPayslip(models.Model):
|
|||||||
for slip in slip_mapped_data[journal][slip_date]:
|
for slip in slip_mapped_data[journal][slip_date]:
|
||||||
slip.write({'move_id': move.id, 'date': date})
|
slip.write({'move_id': move.id, 'date': date})
|
||||||
|
|
||||||
def _generate_move_slip(self, slip_mapped_data, journal, slip_date):
|
def _generate_move_slip(self, slip_mapped_data, journal, slip_date, account_map):
|
||||||
slip_mapped_data[journal][slip_date]._check_slips_employee_home_address()
|
slip_mapped_data[journal][slip_date]._check_slips_employee_home_address()
|
||||||
|
|
||||||
precision = self.env['decimal.precision'].precision_get('Payroll')
|
precision = self.env['decimal.precision'].precision_get('Payroll')
|
||||||
|
|
||||||
for slip in slip_mapped_data[journal][slip_date]:
|
for slip in slip_mapped_data[journal][slip_date]:
|
||||||
|
slip_accounts = account_map[slip.contract_id.payroll_fiscal_position_id]
|
||||||
line_ids = []
|
line_ids = []
|
||||||
debit_sum = 0.0
|
debit_sum = 0.0
|
||||||
credit_sum = 0.0
|
credit_sum = 0.0
|
||||||
@@ -346,7 +370,7 @@ class HrPayslip(models.Model):
|
|||||||
|
|
||||||
move_dict['narration'] += slip.number or '' + ' - ' + slip.employee_id.name or ''
|
move_dict['narration'] += slip.number or '' + ' - ' + slip.employee_id.name or ''
|
||||||
move_dict['narration'] += '\n'
|
move_dict['narration'] += '\n'
|
||||||
slip._process_journal_lines_grouped(line_ids, date, precision)
|
slip._process_journal_lines_grouped(line_ids, date, precision, slip_accounts)
|
||||||
|
|
||||||
for line_id in line_ids: # Get the debit and credit sum.
|
for line_id in line_ids: # Get the debit and credit sum.
|
||||||
debit_sum += line_id['debit']
|
debit_sum += line_id['debit']
|
||||||
@@ -407,7 +431,7 @@ class HrPayslip(models.Model):
|
|||||||
move = self.env['account.move'].create(move_dict)
|
move = self.env['account.move'].create(move_dict)
|
||||||
slip.write({'move_id': move.id, 'date': date})
|
slip.write({'move_id': move.id, 'date': date})
|
||||||
|
|
||||||
def _generate_move_original(self, slip_mapped_data, journal, slip_date):
|
def _generate_move_original(self, slip_mapped_data, journal, slip_date, account_map):
|
||||||
"""
|
"""
|
||||||
Odoo's original version.
|
Odoo's original version.
|
||||||
Fixed bug with 'matching' credit line
|
Fixed bug with 'matching' credit line
|
||||||
@@ -425,6 +449,7 @@ class HrPayslip(models.Model):
|
|||||||
}
|
}
|
||||||
|
|
||||||
for slip in slip_mapped_data[journal][slip_date]:
|
for slip in slip_mapped_data[journal][slip_date]:
|
||||||
|
slip_accounts = account_map[slip.contract_id.payroll_fiscal_position_id]
|
||||||
move_dict['narration'] += slip.number or '' + ' - ' + slip.employee_id.name or ''
|
move_dict['narration'] += slip.number or '' + ' - ' + slip.employee_id.name or ''
|
||||||
move_dict['narration'] += '\n'
|
move_dict['narration'] += '\n'
|
||||||
for line in slip.line_ids.filtered(lambda l: l.category_id):
|
for line in slip.line_ids.filtered(lambda l: l.category_id):
|
||||||
@@ -439,8 +464,10 @@ class HrPayslip(models.Model):
|
|||||||
if float_is_zero(amount, precision_digits=precision):
|
if float_is_zero(amount, precision_digits=precision):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
debit_account_id = line.salary_rule_id.account_debit.id
|
debit_account = line.salary_rule_id.account_debit
|
||||||
credit_account_id = line.salary_rule_id.account_credit.id
|
debit_account_id = slip_accounts[debit_account].id if debit_account else False
|
||||||
|
credit_account = line.salary_rule_id.account_credit
|
||||||
|
credit_account_id = slip_accounts[credit_account].id if credit_account else False
|
||||||
|
|
||||||
if debit_account_id: # If the rule has a debit account.
|
if debit_account_id: # If the rule has a debit account.
|
||||||
debit = amount if amount > 0.0 else 0.0
|
debit = amount if amount > 0.0 else 0.0
|
||||||
|
|||||||
@@ -15,8 +15,26 @@ class TestHrPayrollAccount(TestBase):
|
|||||||
})
|
})
|
||||||
# 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...
|
||||||
rule = self.env.ref('hr_payroll.hr_salary_rule_houserentallowance1')
|
self.rule = self.env.ref('hr_payroll.hr_salary_rule_houserentallowance1')
|
||||||
rule.partner_id = False
|
self.rule.partner_id = False
|
||||||
|
|
||||||
|
def _setup_fiscal_position(self):
|
||||||
|
account_rule_debit = self.rule.account_debit
|
||||||
|
self.assertTrue(account_rule_debit)
|
||||||
|
account_other = self.env['account.account'].search([('id', '!=', account_rule_debit.id)], limit=1)
|
||||||
|
self.assertTrue(account_other)
|
||||||
|
fp = self.env['account.fiscal.position'].create({
|
||||||
|
'name': 'Salary Remap 1',
|
||||||
|
'account_ids': [(0, 0, {
|
||||||
|
'account_src_id': account_rule_debit.id,
|
||||||
|
'account_dest_id': account_other.id,
|
||||||
|
})]
|
||||||
|
})
|
||||||
|
self.hr_contract_john.payroll_fiscal_position_id = fp
|
||||||
|
|
||||||
|
def _setup_fiscal_position_empty(self):
|
||||||
|
self._setup_fiscal_position()
|
||||||
|
self.hr_contract_john.payroll_fiscal_position_id.write({'account_ids': [(5, 0, 0)]})
|
||||||
|
|
||||||
def test_00_hr_payslip_run(self):
|
def test_00_hr_payslip_run(self):
|
||||||
# Original method groups but has no partners.
|
# Original method groups but has no partners.
|
||||||
@@ -26,6 +44,16 @@ class TestHrPayrollAccount(TestBase):
|
|||||||
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')), 0)
|
self.assertEqual(len(self.payslip_run.slip_ids.mapped('move_id.line_ids.partner_id')), 0)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
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.
|
||||||
self.account_journal.payroll_entry_type = 'grouped'
|
self.account_journal.payroll_entry_type = 'grouped'
|
||||||
@@ -34,6 +62,16 @@ class TestHrPayrollAccount(TestBase):
|
|||||||
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)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
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
|
||||||
self.account_journal.payroll_entry_type = 'slip'
|
self.account_journal.payroll_entry_type = 'slip'
|
||||||
@@ -42,3 +80,13 @@ 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)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|||||||
@@ -60,4 +60,16 @@
|
|||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
<!-- contract -->
|
||||||
|
<record id="hr_contract_view_form_inherit" model="ir.ui.view">
|
||||||
|
<field name="name">hr.contract.view.form.inherit</field>
|
||||||
|
<field name="model">hr.contract</field>
|
||||||
|
<field name="inherit_id" ref="hr_payroll_account.hr_contract_view_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//field[@name='analytic_account_id']" position="after">
|
||||||
|
<field name="payroll_fiscal_position_id"/>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
</odoo>
|
</odoo>
|
||||||
Reference in New Issue
Block a user