[MIG] hr_payroll_account: Migrate to new API (code + tests)

This commit is contained in:
Yannick Tivisse
2016-07-29 09:58:22 +02:00
parent 4d8894c8d6
commit eb11f3ba25
8 changed files with 210 additions and 280 deletions
+2 -9
View File
@@ -2,7 +2,6 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': 'Payroll Accounting',
'version': '1.0',
'category': 'Human Resources',
'description': """
Generic Payroll system Integrated with Accounting.
@@ -12,14 +11,8 @@ Generic Payroll system Integrated with Accounting.
* Payment Encoding
* Company Contribution Management
""",
'depends': [
'hr_payroll',
'account',
'hr_expense'
],
'depends': ['hr_payroll', 'account', 'hr_expense'],
'data': ['views/hr_payroll_account_views.xml'],
'demo': ['data/hr_payroll_account_demo.xml'],
'test': ['../account/test/account_minimal_test.xml', 'test/hr_payroll_account.yml'],
'installable': True,
'auto_install': False,
'test': ['../account/test/account_minimal_test.xml'],
}
@@ -1,11 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<odoo>
<record id="hr_payroll.hr_employee_payroll" model="hr.employee">
<field name="address_home_id" ref="base.res_partner_main2"/>
</record>
</data>
</openerp>
</odoo>
@@ -1,110 +1,76 @@
#-*- coding:utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import time
from datetime import date, datetime, timedelta
from openerp import api
from openerp.osv import fields, osv
from openerp.tools import float_compare, float_is_zero
from openerp.tools.translate import _
from openerp.exceptions import UserError
from odoo import api, fields, models, _
from odoo.exceptions import UserError
from odoo.tools import float_compare, float_is_zero
class hr_payslip_line(osv.osv):
'''
Payslip Line
'''
class HrPayslipLine(models.Model):
_inherit = 'hr.payslip.line'
def _get_partner_id(self, cr, uid, payslip_line, credit_account, context=None):
def _get_partner_id(self, credit_account):
"""
Get partner_id of slip line to use in account_move_line
"""
# use partner of salary rule or fallback on employee's address
partner_id = payslip_line.salary_rule_id.register_id.partner_id.id or \
payslip_line.slip_id.employee_id.address_home_id.id
register_partner_id = self.salary_rule_id.register_id.partner_id
partner_id = register_partner_id.id or self.slip_id.employee_id.address_home_id.id
if credit_account:
if payslip_line.salary_rule_id.register_id.partner_id or \
payslip_line.salary_rule_id.account_credit.internal_type in ('receivable', 'payable'):
if register_partner_id or self.salary_rule_id.account_credit.internal_type in ('receivable', 'payable'):
return partner_id
else:
if payslip_line.salary_rule_id.register_id.partner_id or \
payslip_line.salary_rule_id.account_debit.internal_type in ('receivable', 'payable'):
if register_partner_id or self.salary_rule_id.account_debit.internal_type in ('receivable', 'payable'):
return partner_id
return False
class hr_payslip(osv.osv):
'''
Pay Slip
'''
class HrPayslip(models.Model):
_inherit = 'hr.payslip'
_description = 'Pay Slip'
_columns = {
'date': fields.date('Date Account', states={'draft': [('readonly', False)]}, readonly=True, help="Keep empty to use the period of the validation(Payslip) date."),
'journal_id': fields.many2one('account.journal', 'Salary Journal',states={'draft': [('readonly', False)]}, readonly=True, required=True),
'move_id': fields.many2one('account.move', 'Accounting Entry', readonly=True, copy=False),
}
date = fields.Date('Date Account', states={'draft': [('readonly', False)]}, readonly=True,
help="Keep empty to use the period of the validation(Payslip) date.")
journal_id = fields.Many2one('account.journal', 'Salary Journal', readonly=True, required=True,
states={'draft': [('readonly', False)]}, default=lambda self: self.env['account.journal'].search([('type', '=', 'general')], limit=1))
move_id = fields.Many2one('account.move', 'Accounting Entry', readonly=True, copy=False)
def _get_default_journal(self, cr, uid, context=None):
journal_obj = self.pool.get('account.journal')
res = journal_obj.search(cr, uid, [('type', '=', 'general')])
if res:
return res[0]
return False
_defaults = {
'journal_id': _get_default_journal,
}
def create(self, cr, uid, vals, context=None):
if context is None:
context = {}
if 'journal_id' in context:
vals.update({'journal_id': context.get('journal_id')})
return super(hr_payslip, self).create(cr, uid, vals, context=context)
@api.model
def create(self, vals):
if 'journal_id' in self.env.context:
vals['journal_id'] = self.env.context.get('journal_id')
return super(HrPayslip, self).create(vals)
@api.onchange('contract_id')
def onchange_contract(self):
super(hr_payslip, self).onchange_contract()
self.journal_id = self.contract_id and self.contract_id.journal_id.id or (not self.contract_id and self._get_default_journal())
return
super(HrPayslip, self).onchange_contract()
self.journal_id = self.contract_id.journal_id.id or (not self.contract_id and self.default_get(['journal_id'])['journal_id'])
def cancel_sheet(self, cr, uid, ids, context=None):
move_pool = self.pool.get('account.move')
move_ids = []
move_to_cancel = []
for slip in self.browse(cr, uid, ids, context=context):
if slip.move_id:
move_ids.append(slip.move_id.id)
if slip.move_id.state == 'posted':
move_to_cancel.append(slip.move_id.id)
move_pool.button_cancel(cr, uid, move_to_cancel, context=context)
move_pool.unlink(cr, uid, move_ids, context=context)
return super(hr_payslip, self).cancel_sheet(cr, uid, ids, context=context)
@api.multi
def cancel_sheet(self):
moves = self.mapped('move_id')
moves.filtered(lambda x: x.state == 'posted').button_cancel()
moves.unlink()
return super(HrPayslip, self).cancel_sheet()
def process_sheet(self, cr, uid, ids, context=None):
move_pool = self.pool.get('account.move')
hr_payslip_line_pool = self.pool['hr.payslip.line']
precision = self.pool.get('decimal.precision').precision_get(cr, uid, 'Payroll')
timenow = time.strftime('%Y-%m-%d')
@api.multi
def process_sheet(self):
precision = self.env['decimal.precision'].precision_get('Payroll')
today = fields.Date.today()
for slip in self.browse(cr, uid, ids, context=context):
for slip in self:
line_ids = []
debit_sum = 0.0
credit_sum = 0.0
date = timenow
name = _('Payslip of %s') % (slip.employee_id.name)
move = {
move_dict = {
'narration': name,
'ref': slip.number,
'journal_id': slip.journal_id.id,
'date': date,
'date': today,
}
for line in slip.details_by_salary_rule_category:
amt = slip.credit_note and -line.total or line.total
if float_is_zero(amt, precision_digits=precision):
amount = slip.credit_note and -line.total or line.total
if float_is_zero(amount, precision_digits=precision):
continue
debit_account_id = line.salary_rule_id.account_debit.id
credit_account_id = line.salary_rule_id.account_credit.id
@@ -112,14 +78,14 @@ class hr_payslip(osv.osv):
if debit_account_id:
debit_line = (0, 0, {
'name': line.name,
'partner_id': hr_payslip_line_pool._get_partner_id(cr, uid, line, credit_account=False, context=context),
'partner_id': line._get_partner_id(credit_account=False),
'account_id': debit_account_id,
'journal_id': slip.journal_id.id,
'date': date,
'debit': amt > 0.0 and amt or 0.0,
'credit': amt < 0.0 and -amt or 0.0,
'analytic_account_id': line.salary_rule_id.analytic_account_id and line.salary_rule_id.analytic_account_id.id or False,
'tax_line_id': line.salary_rule_id.account_tax_id and line.salary_rule_id.account_tax_id.id or False,
'date': today,
'debit': amount > 0.0 and amount or 0.0,
'credit': amount < 0.0 and -amount or 0.0,
'analytic_account_id': line.salary_rule_id.analytic_account_id.id,
'tax_line_id': line.salary_rule_id.account_tax_id.id,
})
line_ids.append(debit_line)
debit_sum += debit_line[2]['debit'] - debit_line[2]['credit']
@@ -127,14 +93,14 @@ class hr_payslip(osv.osv):
if credit_account_id:
credit_line = (0, 0, {
'name': line.name,
'partner_id': hr_payslip_line_pool._get_partner_id(cr, uid, line, credit_account=True, context=context),
'partner_id': line._get_partner_id(credit_account=True),
'account_id': credit_account_id,
'journal_id': slip.journal_id.id,
'date': date,
'debit': amt < 0.0 and -amt or 0.0,
'credit': amt > 0.0 and amt or 0.0,
'analytic_account_id': line.salary_rule_id.analytic_account_id and line.salary_rule_id.analytic_account_id.id or False,
'tax_line_id': line.salary_rule_id.account_tax_id and line.salary_rule_id.account_tax_id.id or False,
'date': today,
'debit': amount < 0.0 and -amount or 0.0,
'credit': amount > 0.0 and amount or 0.0,
'analytic_account_id': line.salary_rule_id.analytic_account_id.id,
'tax_line_id': line.salary_rule_id.account_tax_id.id,
})
line_ids.append(credit_line)
credit_sum += credit_line[2]['credit'] - credit_line[2]['debit']
@@ -145,11 +111,11 @@ class hr_payslip(osv.osv):
raise UserError(_('The Expense Journal "%s" has not properly configured the Credit Account!') % (slip.journal_id.name))
adjust_credit = (0, 0, {
'name': _('Adjustment Entry'),
'date': timenow,
'date': today,
'partner_id': False,
'account_id': acc_id,
'journal_id': slip.journal_id.id,
'date': date,
'date': today,
'debit': 0.0,
'credit': debit_sum - credit_sum,
})
@@ -164,52 +130,36 @@ class hr_payslip(osv.osv):
'partner_id': False,
'account_id': acc_id,
'journal_id': slip.journal_id.id,
'date': date,
'date': today,
'debit': credit_sum - debit_sum,
'credit': 0.0,
})
line_ids.append(adjust_debit)
move.update({'line_ids': line_ids})
move_id = move_pool.create(cr, uid, move, context=context)
self.write(cr, uid, [slip.id], {'move_id': move_id, 'date' : date}, context=context)
move_pool.post(cr, uid, [move_id], context=context)
return super(hr_payslip, self).process_sheet(cr, uid, [slip.id], context=context)
move_dict['line_ids'] = line_ids
move = self.env['account.move'].create(move_dict)
slip.write({'move_id': move.id, 'date': today})
move.post()
return super(HrPayslip, self).process_sheet()
class hr_salary_rule(osv.osv):
class HrSalaryRule(models.Model):
_inherit = 'hr.salary.rule'
_columns = {
'analytic_account_id':fields.many2one('account.analytic.account', 'Analytic Account'),
'account_tax_id':fields.many2one('account.tax', 'Tax'),
'account_debit': fields.many2one('account.account', 'Debit Account', domain=[('deprecated', '=', False)]),
'account_credit': fields.many2one('account.account', 'Credit Account', domain=[('deprecated', '=', False)]),
}
class hr_contract(osv.osv):
analytic_account_id = fields.Many2one('account.analytic.account', 'Analytic Account')
account_tax_id = fields.Many2one('account.tax', 'Tax')
account_debit = fields.Many2one('account.account', 'Debit Account', domain=[('deprecated', '=', False)])
account_credit = fields.Many2one('account.account', 'Credit Account', domain=[('deprecated', '=', False)])
class HrContract(models.Model):
_inherit = 'hr.contract'
_description = 'Employee Contract'
_columns = {
'analytic_account_id':fields.many2one('account.analytic.account', 'Analytic Account'),
'journal_id': fields.many2one('account.journal', 'Salary Journal'),
}
class hr_payslip_run(osv.osv):
analytic_account_id = fields.Many2one('account.analytic.account', 'Analytic Account')
journal_id = fields.Many2one('account.journal', 'Salary Journal')
class HrPayslipRun(models.Model):
_inherit = 'hr.payslip.run'
_description = 'Payslip Run'
_columns = {
'journal_id': fields.many2one('account.journal', 'Salary Journal', states={'draft': [('readonly', False)]}, readonly=True, required=True),
}
def _get_default_journal(self, cr, uid, context=None):
journal_obj = self.pool.get('account.journal')
res = journal_obj.search(cr, uid, [('type', '=', 'general')])
if res:
return res[0]
return False
_defaults = {
'journal_id': _get_default_journal,
}
journal_id = fields.Many2one('account.journal', 'Salary Journal', states={'draft': [('readonly', False)]}, readonly=True,
required=True, default=lambda self: self.env['account.journal'].search([('type', '=', 'general')], limit=1))
@@ -1,123 +0,0 @@
-
I test the "Payslip" in order to check the hr_payroll_account module in OpenERP.
-
I create a bank record.
-
!record {model: res.partner.bank, id: res_partner_bank_0}:
acc_number: '001-9876543-21'
partner_id: base.res_partner_12
acc_type: bank
bank_id: base.res_bank_1
-
I create a new employee "John".
-
!record {model: hr.employee, id: hr_employee_john}:
address_home_id: base.res_partner_address_2
address_id: base.res_partner_address_12
birthday: '1984-05-01'
children: 0.0
country_id: base.in
department_id: hr.dep_rd
gender: male
marital: single
name: John
bank_account_id: res_partner_bank_0
vehicle_distance: 0.0
-
I create a salary structure for 'Software Developer'.
-
!record {model: hr.payroll.structure, id: hr_payroll_structure_softwaredeveloper}:
name: Salary Structure for Software Developer
code: SD
company_id: base.main_company
parent_id: hr_payroll.structure_base
rule_ids:
- hr_payroll.hr_salary_rule_houserentallowance1
- hr_payroll.hr_salary_rule_convanceallowance1
- hr_payroll.hr_salary_rule_professionaltax1
- hr_payroll.hr_salary_rule_providentfund1
- hr_payroll.hr_salary_rule_meal_voucher
- hr_payroll.hr_salary_rule_sales_commission
-
I create a contract for "John".
-
!record {model: hr.contract, id: hr_contract_john}:
date_end: !eval "(datetime.now() + timedelta(days=365)).strftime('%Y-%m-%d')"
date_start: !eval time.strftime('%Y-%m-%d')
name: Contract for John
wage: 5000.0
type_id: hr_contract.hr_contract_type_emp
employee_id: hr_employee_john
struct_id: hr_payroll_structure_softwaredeveloper
working_hours: resource.timesheet_group1
journal_id: expenses_journal
-
I create a Payslip.
-
!record {model: hr.payslip, id: hr_payslip_0}:
employee_id: hr_employee_john
journal_id: expenses_journal
-
The selection of an employee fills the contract, structure, worked days and input data fields.
-
!python {model: hr.payslip}: |
import time
from datetime import datetime
from dateutil import relativedelta
date_from = time.strftime('%Y-%m-01')
worked_days_obj = self.pool.get('hr.payslip.worked_days')
date_to = str(datetime.now() + relativedelta.relativedelta(months=+1, day=1, days=-1))[:10]
res = self.onchange_employee_id(cr, uid, [], date_from, date_to, ref("hr_employee_john"), False, None)
vals = {
'struct_id': res['value']['struct_id'],
'contract_id': res['value']['contract_id'],
'name': res['value']['name'],
}
vals['worked_days_line_ids'] = [(0,0,i) for i in res['value']['worked_days_line_ids']]
vals['input_line_ids'] = [(0,0,i) for i in res['value']['input_line_ids']]
vals.update({'contract_id': ref('hr_contract_john')})
self.write(cr, uid, ref("hr_payslip_0"), vals)
-
I assign the amount to Input data.
-
!python {model: hr.payslip.input}: |
ids = self.search(cr, uid, [('payslip_id', '=', ref("hr_payslip_0"))])
self.write(cr, uid, ids, {'amount': 5.0})
-
I verify the payslip is in draft state.
-
!python {model: hr.payslip}: |
payslip_brw=self.browse(cr, uid, ref("hr_payslip_0"))
assert(payslip_brw.state == 'draft'), 'State not changed!'
-
I click on "Compute Sheet" button.
-
!python {model: hr.payslip}: |
self.compute_sheet(cr, uid, [ref("hr_payslip_0")], {"lang": "en_US", "tz": False,
"active_model": "ir.ui.menu", "department_id": False, "active_ids": [ref("hr_payroll.menu_department_tree")],
"section_id": False, "active_id": ref("hr_payroll.menu_department_tree"),
})
-
I want to check cancel button. So I first cancel the sheet then make it set to draft.
-
!python {model: hr.payslip}: |
self.cancel_sheet(cr, uid, [ref("hr_payslip_0")], None)
self.signal_workflow(cr, uid, [ref("hr_payslip_0")], 'draft')
-
Then I click on the "Confirm" button.
-
!workflow {model: hr.payslip, action: hr_verify_sheet, ref: hr_payslip_0}
-
I verify that the Accounting Entries are created.
-
!python {model: hr.payslip}: |
payslip = self.browse(cr, uid, ref("hr_payslip_0"))
assert payslip.move_id, "Accounting Entries has not been created"
-
I verify that the payslip is in done state.
-
!python {model: hr.payslip}: |
payslip_brw=self.browse(cr, uid, ref("hr_payslip_0"))
assert(payslip_brw.state == 'done'), 'State not changed!'
@@ -0,0 +1,4 @@
#-*- coding:utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import test_hr_payroll_account
@@ -0,0 +1,121 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import time
from datetime import datetime, timedelta
from dateutil import relativedelta
from odoo import fields, tools
from openerp.addons.account.tests.account_test_classes import AccountingTestCase
from odoo.modules.module import get_module_resource
from odoo.tests import common
class TestHrPayrollAccount(common.TransactionCase):
def _load(self, module, *args):
tools.convert_file(self.cr, 'hr_payroll_account',
get_module_resource(module, *args), {}, 'init', False, 'test', self.registry._assertion_report)
def setUp(self):
super(TestHrPayrollAccount, self).setUp()
self._load('account', 'test', 'account_minimal_test.xml')
self.payslip_action_id = self.ref('hr_payroll.menu_department_tree')
self.res_partner_bank = self.env['res.partner.bank'].create({
'acc_number': '001-9876543-21',
'partner_id': self.ref('base.res_partner_12'),
'acc_type': 'bank',
'bank_id': self.ref('base.res_bank_1'),
})
self.hr_employee_john = self.env['hr.employee'].create({
'address_home_id': self.ref('base.res_partner_address_2'),
'address_id': self.ref('base.res_partner_address_12'),
'birthday': '1984-05-01',
'children': 0.0,
'country_id': self.ref('base.in'),
'department_id': self.ref('hr.dep_rd'),
'gender': 'male',
'marital': 'single',
'name': 'John',
'bank_account_id': self.res_partner_bank.bank_id.id,
'vehicle_distance': 0.0,
})
self.hr_structure_softwaredeveloper = self.env['hr.payroll.structure'].create({
'name': 'Salary Structure for Software Developer',
'code': 'SD',
'company_id': self.ref('base.main_company'),
'parent_id': self.ref('hr_payroll.structure_base'),
'rule_ids': [(6, 0, [
self.ref('hr_payroll.hr_salary_rule_houserentallowance1'),
self.ref('hr_payroll.hr_salary_rule_convanceallowance1'),
self.ref('hr_payroll.hr_salary_rule_professionaltax1'),
self.ref('hr_payroll.hr_salary_rule_providentfund1'),
self.ref('hr_payroll.hr_salary_rule_meal_voucher'),
self.ref('hr_payroll.hr_salary_rule_sales_commission')
])],
})
# Create account journal.
self.hr_contract_john = self.env['hr.contract'].create({
'date_end': fields.Date.to_string(datetime.now() + timedelta(days=365)),
'date_start': fields.Date.today(),
'name': 'Contract for John',
'wage': 5000.0,
'type_id': self.ref('hr_contract.hr_contract_type_emp'),
'employee_id': self.hr_employee_john.id,
'struct_id': self.hr_structure_softwaredeveloper.id,
'working_hours': self.ref('resource.timesheet_group1'),
'journal_id': self.ref('hr_payroll_account.expenses_journal'),
})
self.hr_payslip = self.env['hr.payslip'].create({
'employee_id': self.hr_employee_john.id,
'journal_id': self.ref('hr_payroll_account.expenses_journal'),
})
def test_00_hr_payslip(self):
""" checking the process of payslip. """
date_from = time.strftime('%Y-%m-01')
date_to = str(datetime.now() + relativedelta.relativedelta(months=+1, day=1, days=-1))[:10]
res = self.hr_payslip.onchange_employee_id(date_from, date_to, self.hr_employee_john.id)
vals = {
'struct_id': res['value']['struct_id'],
'contract_id': res['value']['contract_id'],
'name': res['value']['name'],
}
vals['worked_days_line_ids'] = [(0, 0, i) for i in res['value']['worked_days_line_ids']]
vals['input_line_ids'] = [(0, 0, i) for i in res['value']['input_line_ids']]
vals.update({'contract_id': self.hr_contract_john.id})
self.hr_payslip.write(vals)
# I assign the amount to Input data.
payslip_input = self.env['hr.payslip.input'].search([('payslip_id', '=', self.hr_payslip.id)])
payslip_input.write({'amount': 5.0})
# I verify the payslip is in draft state.
self.assertEqual(self.hr_payslip.state, 'draft', 'State not changed!')
# I click on "Compute Sheet" button.
context = {"lang": "en_US", "tz": False, "active_model": 'hr.payslip', "department_id": False, "active_ids": [self.payslip_action_id], "section_id": False, "active_id": self.payslip_action_id}
self.hr_payslip.with_context(context).compute_sheet()
# I want to check cancel button. So I first cancel the sheet then make it set to draft.
self.hr_payslip.cancel_sheet()
self.assertEqual(self.hr_payslip.state, 'cancel', "Payslip is rejected.")
self.hr_payslip.signal_workflow('draft')
# Confirm Payslip
self.hr_payslip.signal_workflow('hr_verify_sheet')
# I verify that the Accounting Entries are created.
self.assertTrue(self.hr_payslip.move_id, 'Accounting Entries has not been created')
# I verify that the payslip is in done state.
self.assertEqual(self.hr_payslip.state, 'done', 'State not changed!')
@@ -1,7 +1,5 @@
<?xml version="1.0"?>
<openerp>
<data>
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record model="ir.ui.view" id="view_hr_payslip_inherit_form">
<field name="name">hr.payslip.inherit.form</field>
<field name="model">hr.payslip</field>
@@ -85,6 +83,4 @@
</field>
</field>
</record>
</data>
</openerp>
</odoo>
@@ -1,20 +1,14 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from openerp.osv import osv
from odoo import api, models
class hr_payslip_employees(osv.osv_memory):
class HrPayslipEmployees(models.TransientModel):
_inherit = 'hr.payslip.employees'
_inherit ='hr.payslip.employees'
def compute_sheet(self, cr, uid, ids, context=None):
run_pool = self.pool.get('hr.payslip.run')
if context is None:
context = {}
if context.get('active_id'):
run_data = run_pool.read(cr, uid, context['active_id'], ['journal_id'])
journal_id = run_data.get('journal_id')
journal_id = journal_id and journal_id[0] or False
if journal_id:
context = dict(context, journal_id=journal_id)
return super(hr_payslip_employees, self).compute_sheet(cr, uid, ids, context=context)
@api.multi
def compute_sheet(self):
journal_id = False
if self.env.context.get('active_id'):
journal_id = self.env['hr.payslip.run'].browse(self.env.context.get['active_id']).journal_id.id
return super(HrPayslipEmployees, self).with_context(journal_id=journal_id).compute_sheet()