[FIX] hr_timesheet_invoice: description of invoice

Partial backport of 4d912af without the group by partner part. Do not forward
port above saas-6.

The generation of invoices from analytic lines was messy and mixed the
description of lines (e.g. redundant message when same product is invoiced twice
with different user, see opw 633047). Grouping was not consistent.
In 4d912af, grouping by partner was added with a refactoring of the grouping
method. Backport the second part only to get cleaner grouping and avoid mixing
messages.
This commit is contained in:
Martin Trigaux 2015-04-16 13:46:09 +02:00
parent 58a481329e
commit 55f9cbf9c7
1 changed files with 144 additions and 122 deletions

View File

@ -150,14 +150,106 @@ class account_analytic_line(osv.osv):
price = 0.0
return price
def _prepare_cost_invoice(self, cr, uid, partner, company_id, currency_id, analytic_lines, context=None):
""" returns values used to create main invoice from analytic lines"""
account_payment_term_obj = self.pool['account.payment.term']
invoice_name = analytic_lines[0].account_id.name
date_due = False
if partner.property_payment_term:
pterm_list = account_payment_term_obj.compute(cr, uid,
partner.property_payment_term.id, value=1,
date_ref=time.strftime('%Y-%m-%d'))
if pterm_list:
pterm_list = [line[0] for line in pterm_list]
pterm_list.sort()
date_due = pterm_list[-1]
return {
'name': "%s - %s" % (time.strftime('%d/%m/%Y'), invoice_name),
'partner_id': partner.id,
'company_id': company_id,
'payment_term': partner.property_payment_term.id or False,
'account_id': partner.property_account_receivable.id,
'currency_id': currency_id,
'date_due': date_due,
'fiscal_position': partner.property_account_position.id
}
def _prepare_cost_invoice_line(self, cr, uid, invoice_id, product_id, uom, user_id,
factor_id, account, analytic_lines, journal_type, data, context=None):
product_obj = self.pool['product.product']
uom_context = dict(context or {}, uom=uom)
total_price = sum(l.amount for l in analytic_lines)
total_qty = sum(l.unit_amount for l in analytic_lines)
if data.get('product'):
# force product, use its public price
if isinstance(data['product'], (tuple, list)):
product_id = data['product'][0]
else:
product_id = data['product']
unit_price = self._get_invoice_price(cr, uid, account, product_id, user_id, total_qty, uom_context)
elif journal_type == 'general' and product_id:
# timesheets, use sale price
unit_price = self._get_invoice_price(cr, uid, account, product_id, user_id, total_qty, uom_context)
else:
# expenses, using price from amount field
unit_price = total_price*-1.0 / total_qty
factor = self.pool['hr_timesheet_invoice.factor'].browse(cr, uid, factor_id, context=uom_context)
factor_name = factor.customer_name
curr_invoice_line = {
'price_unit': unit_price,
'quantity': total_qty,
'product_id': product_id,
'discount': factor.factor,
'invoice_id': invoice_id,
'name': factor_name,
'uos_id': uom,
'account_analytic_id': account.id,
}
if product_id:
product = product_obj.browse(cr, uid, product_id, context=uom_context)
factor_name = product_obj.name_get(cr, uid, [product_id], context=uom_context)[0][1]
if factor.customer_name:
factor_name += ' - ' + factor.customer_name
general_account = product.property_account_income or product.categ_id.property_account_income_categ
if not general_account:
raise osv.except_osv(_('Error!'), _("Configuration Error!") + '\n' + _("Please define income account for product '%s'.") % product.name)
taxes = product.taxes_id or general_account.tax_ids
tax = self.pool['account.fiscal.position'].map_tax(cr, uid, account.partner_id.property_account_position, taxes)
curr_invoice_line.update({
'invoice_line_tax_id': [(6, 0, tax)],
'name': factor_name,
'invoice_line_tax_id': [(6, 0, tax)],
'account_id': general_account.id,
})
note = []
for line in analytic_lines:
# set invoice_line_note
details = []
if data.get('date', False):
details.append(line['date'])
if data.get('time', False):
if line['product_uom_id']:
details.append("%s %s" % (line.unit_amount, line.product_uom_id.name))
else:
details.append("%s" % (line['unit_amount'], ))
if data.get('name', False):
details.append(line['name'])
if details:
note.append(u' - '.join(map(lambda x: unicode(x) or '', details)))
if note:
curr_invoice_line['name'] += "\n" + ("\n".join(map(lambda x: unicode(x) or '', note)))
return curr_invoice_line
def invoice_cost_create(self, cr, uid, ids, data=None, context=None):
analytic_account_obj = self.pool.get('account.analytic.account')
account_payment_term_obj = self.pool.get('account.payment.term')
invoice_obj = self.pool.get('account.invoice')
product_obj = self.pool.get('product.product')
invoice_factor_obj = self.pool.get('hr_timesheet_invoice.factor')
fiscal_pos_obj = self.pool.get('account.fiscal.position')
product_uom_obj = self.pool.get('product.uom')
invoice_line_obj = self.pool.get('account.invoice.line')
invoices = []
if context is None:
@ -165,132 +257,62 @@ class account_analytic_line(osv.osv):
if data is None:
data = {}
journal_types = {}
# use key (partner/account, company, currency)
# creates one invoice per key
invoice_grouping = {}
currency_id = False
# prepare for iteration on journal and accounts
for line in self.pool.get('account.analytic.line').browse(cr, uid, ids, context=context):
if line.journal_id.type not in journal_types:
journal_types[line.journal_id.type] = set()
journal_types[line.journal_id.type].add(line.account_id.id)
for journal_type, account_ids in journal_types.items():
for account in analytic_account_obj.browse(cr, uid, list(account_ids), context=context):
partner = account.partner_id
for line in self.browse(cr, uid, ids, context=context):
key = (line.account_id.id,
line.account_id.company_id.id,
line.account_id.pricelist_id.currency_id.id)
invoice_grouping.setdefault(key, []).append(line)
for (key_id, company_id, currency_id), analytic_lines in invoice_grouping.items():
# key_id is an account.analytic.account
partner = analytic_lines[0].account_id.partner_id # will be the same for every line
curr_invoice = self._prepare_cost_invoice(cr, uid, partner, company_id, currency_id, analytic_lines, context=context)
invoice_context = dict(context,
lang=partner.lang,
force_company=company_id, # set force_company in context so the correct product properties are selected (eg. income account)
company_id=company_id) # set company_id in context, so the correct default journal will be selected
last_invoice = invoice_obj.create(cr, uid, curr_invoice, context=invoice_context)
invoices.append(last_invoice)
# use key (product, uom, user, invoiceable, analytic account, journal type)
# creates one invoice line per key
invoice_lines_grouping = {}
for analytic_line in analytic_lines:
account = analytic_line.account_id
if (not partner) or not (account.pricelist_id):
raise osv.except_osv(_('Analytic Account Incomplete!'),
_('Contract incomplete. Please fill in the Customer and Pricelist fields.'))
raise osv.except_osv(_('Error!'), _('Contract incomplete. Please fill in the Customer and Pricelist fields for %s.') % (account.name))
date_due = False
if partner.property_payment_term:
pterm_list= account_payment_term_obj.compute(cr, uid,
partner.property_payment_term.id, value=1,
date_ref=time.strftime('%Y-%m-%d'))
if pterm_list:
pterm_list = [line[0] for line in pterm_list]
pterm_list.sort()
date_due = pterm_list[-1]
if not analytic_line.to_invoice:
raise osv.except_osv(_('Error!'), _('Trying to invoice non invoiceable line for %s.') % (analytic_line.product_id.name))
curr_invoice = {
'name': time.strftime('%d/%m/%Y') + ' - '+account.name,
'partner_id': account.partner_id.id,
'company_id': account.company_id.id,
'payment_term': partner.property_payment_term.id or False,
'account_id': partner.property_account_receivable.id,
'currency_id': account.pricelist_id.currency_id.id,
'date_due': date_due,
'fiscal_position': account.partner_id.property_account_position.id
}
context2 = context.copy()
context2['lang'] = partner.lang
# set company_id in context, so the correct default journal will be selected
context2['force_company'] = curr_invoice['company_id']
# set force_company in context so the correct product properties are selected (eg. income account)
context2['company_id'] = curr_invoice['company_id']
key = (analytic_line.product_id.id,
analytic_line.product_uom_id.id,
analytic_line.user_id.id,
analytic_line.to_invoice.id,
analytic_line.account_id,
analytic_line.journal_id.type)
invoice_lines_grouping.setdefault(key, []).append(analytic_line)
last_invoice = invoice_obj.create(cr, uid, curr_invoice, context=context2)
invoices.append(last_invoice)
# finally creates the invoice line
for (product_id, uom, user_id, factor_id, account, journal_type), lines_to_invoice in invoice_lines_grouping.items():
curr_invoice_line = self._prepare_cost_invoice_line(cr, uid, last_invoice,
product_id, uom, user_id, factor_id, account, lines_to_invoice,
journal_type, data, context=context)
cr.execute("""SELECT product_id, user_id, to_invoice, sum(amount), sum(unit_amount), product_uom_id
FROM account_analytic_line as line LEFT JOIN account_analytic_journal journal ON (line.journal_id = journal.id)
WHERE account_id = %s
AND line.id IN %s AND journal.type = %s AND to_invoice IS NOT NULL
GROUP BY product_id, user_id, to_invoice, product_uom_id""", (account.id, tuple(ids), journal_type))
for product_id, user_id, factor_id, total_price, qty, uom in cr.fetchall():
context2.update({'uom': uom})
if data.get('product'):
# force product, use its public price
product_id = data['product'][0]
unit_price = self._get_invoice_price(cr, uid, account, product_id, user_id, qty, context2)
elif journal_type == 'general' and product_id:
# timesheets, use sale price
unit_price = self._get_invoice_price(cr, uid, account, product_id, user_id, qty, context2)
else:
# expenses, using price from amount field
unit_price = total_price*-1.0 / qty
factor = invoice_factor_obj.browse(cr, uid, factor_id, context=context2)
# factor_name = factor.customer_name and line_name + ' - ' + factor.customer_name or line_name
factor_name = factor.customer_name
curr_line = {
'price_unit': unit_price,
'quantity': qty,
'product_id': product_id or False,
'discount': factor.factor,
'invoice_id': last_invoice,
'name': factor_name,
'uos_id': uom,
'account_analytic_id': account.id,
}
product = product_obj.browse(cr, uid, product_id, context=context2)
if product:
factor_name = product_obj.name_get(cr, uid, [product_id], context=context2)[0][1]
if factor.customer_name:
factor_name += ' - ' + factor.customer_name
general_account = product.property_account_income or product.categ_id.property_account_income_categ
if not general_account:
raise osv.except_osv(_("Configuration Error!"), _("Please define income account for product '%s'.") % product.name)
taxes = product.taxes_id or general_account.tax_ids
tax = fiscal_pos_obj.map_tax(cr, uid, account.partner_id.property_account_position, taxes)
curr_line.update({
'invoice_line_tax_id': [(6,0,tax )],
'name': factor_name,
'invoice_line_tax_id': [(6,0,tax)],
'account_id': general_account.id,
})
#
# Compute for lines
#
cr.execute("SELECT * FROM account_analytic_line WHERE account_id = %s and id IN %s AND product_id=%s and to_invoice=%s ORDER BY account_analytic_line.date", (account.id, tuple(ids), product_id, factor_id))
line_ids = cr.dictfetchall()
note = []
for line in line_ids:
# set invoice_line_note
details = []
if data.get('date', False):
details.append(line['date'])
if data.get('time', False):
if line['product_uom_id']:
details.append("%s %s" % (line['unit_amount'], product_uom_obj.browse(cr, uid, [line['product_uom_id']],context2)[0].name))
else:
details.append("%s" % (line['unit_amount'], ))
if data.get('name', False):
details.append(line['name'])
if details:
note.append(u' - '.join(map(lambda x: unicode(x) or '',details)))
if note:
curr_line['name'] += "\n" + ("\n".join(map(lambda x: unicode(x) or '',note)))
invoice_line_obj.create(cr, uid, curr_line, context=context)
cr.execute("update account_analytic_line set invoice_id=%s WHERE account_id = %s and id IN %s", (last_invoice, account.id, tuple(ids)))
self.invalidate_cache(cr, uid, ['invoice_id'], ids, context=context)
invoice_obj.button_reset_taxes(cr, uid, [last_invoice], context)
invoice_line_obj.create(cr, uid, curr_invoice_line, context=context)
self.write(cr, uid, [l.id for l in analytic_lines], {'invoice_id': last_invoice}, context=context)
invoice_obj.button_reset_taxes(cr, uid, [last_invoice], context)
return invoices
class hr_analytic_timesheet(osv.osv):
_inherit = "hr.analytic.timesheet"
def on_change_account_id(self, cr, uid, ids, account_id, user_id=False):