diff --git a/addons/hr_timesheet_invoice/hr_timesheet_invoice.py b/addons/hr_timesheet_invoice/hr_timesheet_invoice.py index 6e6393bced2..ac7c237d73c 100644 --- a/addons/hr_timesheet_invoice/hr_timesheet_invoice.py +++ b/addons/hr_timesheet_invoice/hr_timesheet_invoice.py @@ -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):