[FIX] hr_expense now creates an account.move directly instead of a voucher. For legal issues, it was necessary to do this fix because it's not possible to put several taxes on a voucher line
bzr revid: qdp-launchpad@openerp.com-20130313155012-5p3lkod76rhsfnhi
This commit is contained in:
@@ -1150,6 +1150,29 @@ class account_move(osv.osv):
|
||||
_description = "Account Entry"
|
||||
_order = 'id desc'
|
||||
|
||||
def account_move_prepare(self, cr, uid, journal_id, date=False, ref='', company_id=False, context=None):
|
||||
'''
|
||||
Prepares and returns a dictionary of values, ready to be passed to create() based on the parameters received.
|
||||
'''
|
||||
if not date:
|
||||
date = fields.date.today()
|
||||
period_obj = self.pool.get('account.period')
|
||||
if not company_id:
|
||||
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
|
||||
company_id = user.company_id.id
|
||||
if context is None:
|
||||
context = {}
|
||||
#put the company in context to find the good period
|
||||
ctx = context.copy()
|
||||
ctx.update({'company_id': company_id})
|
||||
return {
|
||||
'journal_id': journal_id,
|
||||
'date': date,
|
||||
'period_id': period_obj.find(cr, uid, date, context=ctx)[0],
|
||||
'ref': ref,
|
||||
'company_id': company_id,
|
||||
}
|
||||
|
||||
def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
|
||||
"""
|
||||
Returns a list of tupples containing id, name, as internally it is called {def name_get}
|
||||
@@ -1858,7 +1881,6 @@ class account_tax(osv.osv):
|
||||
default.update({'name': name + _(' (Copy)')})
|
||||
return super(account_tax, self).copy_data(cr, uid, id, default=default, context=context)
|
||||
|
||||
|
||||
def get_precision_tax():
|
||||
def change_digit_tax(cr):
|
||||
res = pooler.get_pool(cr.dbname).get('decimal.precision').precision_get(cr, SUPERUSER_ID, 'Account')
|
||||
|
||||
@@ -46,7 +46,7 @@ This module also uses analytic accounting and is compatible with the invoice on
|
||||
'author': 'OpenERP SA',
|
||||
'website': 'http://www.openerp.com',
|
||||
'images': ['images/hr_expenses_analysis.jpeg', 'images/hr_expenses.jpeg'],
|
||||
'depends': ['hr', 'account_voucher'],
|
||||
'depends': ['hr', 'account_voucher', 'account_accountant'],
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
'hr_expense_data.xml',
|
||||
|
||||
+212
-71
@@ -145,93 +145,234 @@ class hr_expense_expense(osv.osv):
|
||||
def expense_canceled(self, cr, uid, ids, context=None):
|
||||
return self.write(cr, uid, ids, {'state': 'cancelled'}, context=context)
|
||||
|
||||
def action_receipt_create(self, cr, uid, ids, context=None):
|
||||
property_obj = self.pool.get('ir.property')
|
||||
sequence_obj = self.pool.get('ir.sequence')
|
||||
analytic_journal_obj = self.pool.get('account.analytic.journal')
|
||||
account_journal = self.pool.get('account.journal')
|
||||
voucher_obj = self.pool.get('account.voucher')
|
||||
currency_obj = self.pool.get('res.currency')
|
||||
wkf_service = netsvc.LocalService("workflow")
|
||||
def account_move_get(self, cr, uid, expense_id, context=None):
|
||||
'''
|
||||
This method prepare the creation of the account move related to the given expense.
|
||||
|
||||
:param expense_id: Id of voucher for which we are creating account_move.
|
||||
:return: mapping between fieldname and value of account move to create
|
||||
:rtype: dict
|
||||
'''
|
||||
journal_obj = self.pool.get('account.journal')
|
||||
expense = self.browse(cr, uid, expense_id, context=context)
|
||||
company_id = expense.company_id.id
|
||||
date = expense.date_confirm
|
||||
ref = expense.name
|
||||
journal_id = False
|
||||
if expense.journal_id:
|
||||
journal_id = expense.journal_id.id
|
||||
else:
|
||||
journal_id = journal_obj.search(cr, uid, [('type', '=', 'purchase'), ('company_id', '=', company_id)])
|
||||
if not journal_id:
|
||||
raise osv.except_osv(_('Error!'), _("No expense journal found. Please make sure you have a journal with type 'purchase' configured."))
|
||||
journal_id = journal_id[0]
|
||||
return self.pool.get('account.move').account_move_prepare(cr, uid, journal_id, date=date, ref=ref, company_id=company_id, context=context)
|
||||
|
||||
def line_get_convert(self, cr, uid, x, part, date, context=None):
|
||||
partner_id = self.pool.get('res.partner')._find_accounting_partner(part).id
|
||||
return {
|
||||
'date_maturity': x.get('date_maturity', False),
|
||||
'partner_id': partner_id,
|
||||
'name': x['name'][:64],
|
||||
'date': date,
|
||||
'debit': x['price']>0 and x['price'],
|
||||
'credit': x['price']<0 and -x['price'],
|
||||
'account_id': x['account_id'],
|
||||
'analytic_lines': x.get('analytic_lines', False),
|
||||
'amount_currency': x['price']>0 and abs(x.get('amount_currency', False)) or -abs(x.get('amount_currency', False)),
|
||||
'currency_id': x.get('currency_id', False),
|
||||
'tax_code_id': x.get('tax_code_id', False),
|
||||
'tax_amount': x.get('tax_amount', False),
|
||||
'ref': x.get('ref', False),
|
||||
'quantity': x.get('quantity',1.00),
|
||||
'product_id': x.get('product_id', False),
|
||||
'product_uom_id': x.get('uos_id', False),
|
||||
'analytic_account_id': x.get('account_analytic_id', False),
|
||||
}
|
||||
|
||||
def compute_expense_totals(self, cr, uid, exp, company_currency, ref, account_move_lines, context=None):
|
||||
'''
|
||||
internal method used for computation of total amount of an expense in the company currency and
|
||||
in the expense currency, given the account_move_lines that will be created. It also do some small
|
||||
transformations at these account_move_lines (for multi-currency purposes)
|
||||
|
||||
:param account_move_lines: list of dict
|
||||
:rtype: tuple of 3 elements (a, b ,c)
|
||||
a: total in company currency
|
||||
b: total in hr.expense currency
|
||||
c: account_move_lines potentially modified
|
||||
'''
|
||||
cur_obj = self.pool.get('res.currency')
|
||||
if context is None:
|
||||
context = {}
|
||||
for exp in self.browse(cr, uid, ids, context=context):
|
||||
company_id = exp.company_id.id
|
||||
lines = []
|
||||
total = 0.0
|
||||
ctx = context.copy()
|
||||
ctx.update({'date': exp.date})
|
||||
journal = False
|
||||
if exp.journal_id:
|
||||
journal = exp.journal_id
|
||||
context={}
|
||||
context.update({'date': exp.date_confirm or time.strftime('%Y-%m-%d')})
|
||||
total = 0.0
|
||||
total_currency = 0.0
|
||||
for i in account_move_lines:
|
||||
if exp.currency_id.id != company_currency:
|
||||
i['currency_id'] = exp.currency_id.id
|
||||
i['amount_currency'] = i['price']
|
||||
i['price'] = cur_obj.compute(cr, uid, exp.currency_id.id,
|
||||
company_currency, i['price'],
|
||||
context=context)
|
||||
else:
|
||||
journal_id = voucher_obj._get_journal(cr, uid, context={'type': 'purchase', 'company_id': company_id})
|
||||
if journal_id:
|
||||
journal = account_journal.browse(cr, uid, journal_id, context=context)
|
||||
if not journal:
|
||||
raise osv.except_osv(_('Error!'), _("No expense journal found. Please make sure you have a journal with type 'purchase' configured."))
|
||||
for line in exp.line_ids:
|
||||
if line.product_id:
|
||||
acc = line.product_id.property_account_expense
|
||||
if not acc:
|
||||
acc = line.product_id.categ_id.property_account_expense_categ
|
||||
else:
|
||||
acc = property_obj.get(cr, uid, 'property_account_expense_categ', 'product.category', context={'force_company': company_id})
|
||||
if not acc:
|
||||
raise osv.except_osv(_('Error!'), _('Please configure Default Expense account for Product purchase: `property_account_expense_categ`.'))
|
||||
total_amount = line.total_amount
|
||||
if journal.currency:
|
||||
if exp.currency_id != journal.currency:
|
||||
total_amount = currency_obj.compute(cr, uid, exp.currency_id.id, journal.currency.id, total_amount, context=ctx)
|
||||
elif exp.currency_id != exp.company_id.currency_id:
|
||||
total_amount = currency_obj.compute(cr, uid, exp.currency_id.id, exp.company_id.currency_id.id, total_amount, context=ctx)
|
||||
lines.append((0, False, {
|
||||
'name': line.name,
|
||||
'account_id': acc.id,
|
||||
'account_analytic_id': line.analytic_account.id,
|
||||
'amount': total_amount,
|
||||
'type': 'dr'
|
||||
}))
|
||||
total += total_amount
|
||||
i['amount_currency'] = False
|
||||
i['currency_id'] = False
|
||||
total -= i['price']
|
||||
total_currency -= i['amount_currency'] or i['price']
|
||||
return total, total_currency, account_move_lines
|
||||
|
||||
|
||||
def action_receipt_create(self, cr, uid, ids, context=None):
|
||||
'''
|
||||
main function that is called when trying to create the accounting entries related to an expense
|
||||
'''
|
||||
move_obj = self.pool.get('account.move')
|
||||
for exp in self.browse(cr, uid, ids, context=context):
|
||||
if not exp.employee_id.address_home_id:
|
||||
raise osv.except_osv(_('Error!'), _('The employee must have a home address.'))
|
||||
company_currency = exp.company_id.currency_id.id
|
||||
diff_currency_p = exp.currency_id.id <> company_currency
|
||||
|
||||
#create the move that will contain the accounting entries
|
||||
move_id = move_obj.create(cr, uid, self.account_move_get(cr, uid, exp.id, context=context), context=context)
|
||||
|
||||
#one account.move.line per expense line (+taxes..)
|
||||
eml = self.move_line_get(cr, uid, exp.id, context=context)
|
||||
|
||||
#create one more move line, a counterline for the total on payable account
|
||||
total, total_currency, eml = self.compute_expense_totals(cr, uid, exp, company_currency, exp.name, eml, context=context)
|
||||
acc = exp.employee_id.address_home_id.property_account_payable.id
|
||||
voucher = {
|
||||
'name': exp.name or '/',
|
||||
'reference': sequence_obj.get(cr, uid, 'hr.expense.invoice'),
|
||||
'account_id': acc,
|
||||
'type': 'purchase',
|
||||
'partner_id': exp.employee_id.address_home_id.id,
|
||||
'company_id': company_id,
|
||||
'line_ids': lines,
|
||||
'amount': total,
|
||||
'journal_id': journal.id,
|
||||
}
|
||||
if journal and not journal.analytic_journal_id:
|
||||
analytic_journal_ids = analytic_journal_obj.search(cr, uid, [('type','=','purchase')], context=context)
|
||||
if analytic_journal_ids:
|
||||
account_journal.write(cr, uid, [journal.id], {'analytic_journal_id': analytic_journal_ids[0]}, context=context)
|
||||
voucher_id = voucher_obj.create(cr, uid, voucher, context=context)
|
||||
self.write(cr, uid, [exp.id], {'voucher_id': voucher_id, 'state': 'done'}, context=context)
|
||||
eml.append({
|
||||
'type': 'dest',
|
||||
'name': '/',
|
||||
'price': total,
|
||||
'account_id': acc,
|
||||
'date_maturity': exp.date_confirm,
|
||||
'amount_currency': diff_currency_p and total_currency or False,
|
||||
'currency_id': diff_currency_p and exp.currency_id.id or False,
|
||||
'ref': exp.name
|
||||
})
|
||||
|
||||
#convert eml into an osv-valid format
|
||||
lines = map(lambda x:(0,0,self.line_get_convert(cr, uid, x, exp.employee_id.address_home_id, exp.date_confirm, context=context)), eml)
|
||||
move_obj.write(cr, uid, [move_id], {'line_id': lines}, context=context)
|
||||
self.write(cr, uid, ids, {'account_move_id': move_id, 'state': 'done'}, context=context)
|
||||
return True
|
||||
|
||||
|
||||
def move_line_get(self, cr, uid, expense_id, context=None):
|
||||
res = []
|
||||
tax_obj = self.pool.get('account.tax')
|
||||
cur_obj = self.pool.get('res.currency')
|
||||
if context is None:
|
||||
context = {}
|
||||
exp = self.browse(cr, uid, expense_id, context=context)
|
||||
company_currency = exp.company_id.currency_id.id
|
||||
|
||||
for line in exp.line_ids:
|
||||
mres = self.move_line_get_item(cr, uid, line, context)
|
||||
if not mres:
|
||||
continue
|
||||
res.append(mres)
|
||||
tax_code_found= False
|
||||
|
||||
#Calculate tax according to default tax on product
|
||||
|
||||
taxes = []
|
||||
#Taken from product_id_onchange in account.invoice
|
||||
if line.product_id:
|
||||
fposition_id = False
|
||||
fpos_obj = self.pool.get('account.fiscal.position')
|
||||
fpos = fposition_id and fpos_obj.browse(cr, uid, fposition_id, context=context) or False
|
||||
product = line.product_id
|
||||
taxes = product.supplier_taxes_id
|
||||
#If taxes are not related to the product, maybe they are in the account
|
||||
if not taxes:
|
||||
a = product.property_account_expense.id #Why is not there a check here?
|
||||
if not a:
|
||||
a = product.categ_id.property_account_expense_categ.id
|
||||
a = fpos_obj.map_account(cr, uid, fpos, a)
|
||||
taxes = a and self.pool.get('account.account').browse(cr, uid, a, context=context).tax_ids or False
|
||||
tax_id = fpos_obj.map_tax(cr, uid, fpos, taxes)
|
||||
if not taxes:
|
||||
continue
|
||||
#Calculating tax on the line and creating move?
|
||||
for tax in tax_obj.compute_all(cr, uid, taxes,
|
||||
line.unit_amount ,
|
||||
line.unit_quantity, line.product_id,
|
||||
exp.user_id.partner_id)['taxes']:
|
||||
tax_code_id = tax['base_code_id']
|
||||
tax_amount = line.total_amount * tax['base_sign']
|
||||
if tax_code_found:
|
||||
if not tax_code_id:
|
||||
continue
|
||||
res.append(self.move_line_get_item(cr, uid, line, context))
|
||||
res[-1]['price'] = 0.0
|
||||
res[-1]['account_analytic_id'] = False
|
||||
elif not tax_code_id:
|
||||
continue
|
||||
tax_code_found = True
|
||||
res[-1]['tax_code_id'] = tax_code_id
|
||||
res[-1]['tax_amount'] = cur_obj.compute(cr, uid, exp.currency_id.id, company_currency, tax_amount, context={'date': exp.date_confirm})
|
||||
|
||||
#Will create the tax here as we don't have the access
|
||||
assoc_tax = {
|
||||
'type':'tax',
|
||||
'name':tax['name'],
|
||||
'price_unit': tax['price_unit'],
|
||||
'quantity': 1,
|
||||
'price': tax['amount'] * tax['base_sign'] or 0.0,
|
||||
'account_id': tax['account_collected_id'],
|
||||
'tax_code_id': tax['tax_code_id'],
|
||||
'tax_amount': tax['amount'] * tax['base_sign'],
|
||||
}
|
||||
res.append(assoc_tax)
|
||||
return res
|
||||
|
||||
def move_line_get_item(self, cr, uid, line, context=None):
|
||||
company = line.expense_id.company_id
|
||||
property_obj = self.pool.get('ir.property')
|
||||
if line.product_id:
|
||||
acc = line.product_id.property_account_expense
|
||||
if not acc:
|
||||
acc = line.product_id.categ_id.property_account_expense_categ
|
||||
else:
|
||||
acc = property_obj.get(cr, uid, 'property_account_expense_categ', 'product.category', context={'force_company': company.id})
|
||||
if not acc:
|
||||
raise osv.except_osv(_('Error!'), _('Please configure Default Expense account for Product purchase: `property_account_expense_categ`.'))
|
||||
return {
|
||||
'type':'src',
|
||||
'name': line.name.split('\n')[0][:64],
|
||||
'price_unit':line.unit_amount,
|
||||
'quantity':line.unit_quantity,
|
||||
'price':line.total_amount,
|
||||
'account_id':acc.id,
|
||||
'product_id':line.product_id.id,
|
||||
'uos_id':line.uom_id.id,
|
||||
'account_analytic_id':line.analytic_account.id,
|
||||
}
|
||||
|
||||
def action_view_receipt(self, cr, uid, ids, context=None):
|
||||
'''
|
||||
This function returns an action that display existing receipt of given expense ids.
|
||||
This function returns an action that display existing account.move of given expense ids.
|
||||
'''
|
||||
assert len(ids) == 1, 'This option should only be used for a single id at a time'
|
||||
voucher_id = self.browse(cr, uid, ids[0], context=context).voucher_id.id
|
||||
res = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account_voucher', 'view_purchase_receipt_form')
|
||||
expense = self.browse(cr, uid, ids[0], context=context)
|
||||
assert expense.account_move_id
|
||||
try:
|
||||
dummy, view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account', 'view_move_form')
|
||||
except ValueError, e:
|
||||
view_id = False
|
||||
result = {
|
||||
'name': _('Expense Receipt'),
|
||||
'name': _('Expense Account Move'),
|
||||
'view_type': 'form',
|
||||
'view_mode': 'form',
|
||||
'view_id': res and res[1] or False,
|
||||
'res_model': 'account.voucher',
|
||||
'view_id': view_id,
|
||||
'res_model': 'account.move',
|
||||
'type': 'ir.actions.act_window',
|
||||
'nodestroy': True,
|
||||
'target': 'current',
|
||||
'res_id': voucher_id,
|
||||
'res_id': expense.account_move_id.id,
|
||||
}
|
||||
return result
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
<button name="refuse" states="confirm,accepted" string="Refuse" type="workflow" groups="base.group_hr_user" />
|
||||
<button name="draft" states="confirm,cancelled" string="Set to Draft" type="workflow" groups="base.group_hr_user" />
|
||||
<button name="done" states="accepted" string="Generate Accounting Entries" type="workflow" groups="account.group_account_invoice" class="oe_highlight"/>
|
||||
<button name="action_view_receipt" states="done" string="Open Receipt" type="object"/>
|
||||
<button name="action_view_receipt" states="done" string="Open Accounting Entries" type="object" groups="account.group_account_invoice"/>
|
||||
<field name="state" widget="statusbar" statusbar_visible="draft,confirm,accepted,done" statusbar_colors='{"confirm":"blue","cancelled":"red"}'/>
|
||||
</header>
|
||||
<sheet>
|
||||
@@ -133,7 +133,8 @@
|
||||
<group>
|
||||
<group string="Accounting Data">
|
||||
<field name="journal_id" widget="selection" domain="[('type', '=', 'purchase')]"/>
|
||||
<field name="voucher_id" context="{'form_view_ref': 'account_voucher.view_purchase_receipt_form'}"/>
|
||||
<field name="account_move_id"/>
|
||||
<field name="voucher_id" invisible="1" context="{'form_view_ref': 'account_voucher.view_purchase_receipt_form'}"/>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
|
||||
@@ -26,11 +26,6 @@
|
||||
!python {model: hr.expense.expense}: |
|
||||
sep_expenses = self.browse(cr, uid, ref("sep_expenses"), context=context)
|
||||
assert sep_expenses.state == 'done', "Expense should be in 'Done' state."
|
||||
assert sep_expenses.voucher_id, "Expense should have link of Purchase Receipt."
|
||||
assert sep_expenses.voucher_id.type == 'purchase', "Receipt type is not purchase receipt."
|
||||
assert sep_expenses.voucher_id.amount == sep_expenses.amount,"Receipt total amount is not correspond with expense total."
|
||||
assert len(sep_expenses.voucher_id.line_dr_ids) == len(sep_expenses.line_ids),"Lines of Receipt and expense line are not correspond."
|
||||
|
||||
-
|
||||
I duplicate the expenses and cancel duplicated.
|
||||
-
|
||||
|
||||
Reference in New Issue
Block a user