From 4d912af4656f21d7b1da957b3f7bfc190775c298 Mon Sep 17 00:00:00 2001 From: Martin Trigaux Date: Wed, 1 Apr 2015 10:46:58 +0200 Subject: [PATCH] [ADD] hr_timesheet_invoice: grouping invoice by partner Add option to group analytic lines by partner to generate invoices. Refactoring of the method that was kind of unclear, splitting in separated methods and adding tests. Add graph view in analytic lines view. --- addons/account/account_analytic_view.xml | 13 +- .../account_analytic_analysis_view.xml | 2 +- addons/analytic/views/analytic_view.xml | 14 +- .../hr_timesheet_invoice.py | 286 ++++++++++-------- .../test/test_hr_timesheet_invoice.yml | 63 ++++ addons/hr_timesheet_invoice/tests/__init__.py | 1 + .../tests/test_invoice.py | 103 +++++++ .../hr_timesheet_final_invoice_create.py | 1 + .../wizard/hr_timesheet_invoice_create.py | 3 +- ...hr_timesheet_invoice_create_final_view.xml | 11 +- .../hr_timesheet_invoice_create_view.xml | 5 +- .../project_timesheet_view.xml | 2 +- 12 files changed, 374 insertions(+), 130 deletions(-) create mode 100644 addons/hr_timesheet_invoice/tests/__init__.py create mode 100644 addons/hr_timesheet_invoice/tests/test_invoice.py diff --git a/addons/account/account_analytic_view.xml b/addons/account/account_analytic_view.xml index 7077b73d66f..acbe331a952 100644 --- a/addons/account/account_analytic_view.xml +++ b/addons/account/account_analytic_view.xml @@ -104,7 +104,18 @@ - + + + account.analytic.line.pivot + account.analytic.line + + + + + + + + diff --git a/addons/account_analytic_analysis/account_analytic_analysis_view.xml b/addons/account_analytic_analysis/account_analytic_analysis_view.xml index 3ff41480c0c..4d28b838a67 100644 --- a/addons/account_analytic_analysis/account_analytic_analysis_view.xml +++ b/addons/account_analytic_analysis/account_analytic_analysis_view.xml @@ -278,7 +278,7 @@ Time & Materials to Invoice account.analytic.line form - tree,form + tree,form,pivot [('invoice_id','=',False)] {'search_default_to_invoice': 1} diff --git a/addons/analytic/views/analytic_view.xml b/addons/analytic/views/analytic_view.xml index d977caa25ac..d04faa422b6 100644 --- a/addons/analytic/views/analytic_view.xml +++ b/addons/analytic/views/analytic_view.xml @@ -242,6 +242,18 @@ + + account.analytic.line.pivot + account.analytic.line + + + + + + + + + Analytic Entries ir.actions.act_window @@ -365,7 +377,7 @@ Analytic Journal Items account.analytic.line form - tree,form + tree,form,pivot diff --git a/addons/hr_timesheet_invoice/hr_timesheet_invoice.py b/addons/hr_timesheet_invoice/hr_timesheet_invoice.py index f34e03b69a7..ca12f464eda 100644 --- a/addons/hr_timesheet_invoice/hr_timesheet_invoice.py +++ b/addons/hr_timesheet_invoice/hr_timesheet_invoice.py @@ -154,14 +154,109 @@ 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, group_by_partner=False, context=None): + """ returns values used to create main invoice from analytic lines""" + account_payment_term_obj = self.pool['account.payment.term'] + if group_by_partner: + invoice_name = partner.name + else: + 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 UserError(_("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: @@ -169,127 +264,78 @@ 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 = {} + # grouping on partner instead of analytic account + group_by_partner = data.get('group_by_partner', False) + 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): + # check if currency is the same in different accounts when grouping by partner + if not currency_id : + + currency_id = line.account_id.pricelist_id.currency_id.id + if line.account_id.pricelist_id and line.account_id.pricelist_id.currency_id: + if line.account_id.pricelist_id.currency_id.id != currency_id and group_by_partner: + raise UserError(_('Error!'), + _('You cannot group invoices having different currencies on different analytic accounts for the same partner.')) + + if group_by_partner: + key = (line.account_id.partner_id.id, + line.account_id.company_id.id, + line.account_id.pricelist_id.currency_id.id) + invoice_grouping.setdefault(key, []).append(line) + else: + 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 either an account.analytic.account, either a res.partner + # don't really care, what's important is the analytic lines that + # will be used to create the invoice lines + + 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, group_by_partner, 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 UserError(_('Contract incomplete. Please fill in the Customer and Pricelist fields.')) + raise UserError(_('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 UserError(_('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 UserError(_("Configuration Error!") + '\n' + _("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 diff --git a/addons/hr_timesheet_invoice/test/test_hr_timesheet_invoice.yml b/addons/hr_timesheet_invoice/test/test_hr_timesheet_invoice.yml index 2f9b4e2ea32..3d852a53540 100644 --- a/addons/hr_timesheet_invoice/test/test_hr_timesheet_invoice.yml +++ b/addons/hr_timesheet_invoice/test/test_hr_timesheet_invoice.yml @@ -114,3 +114,66 @@ assert aline.invoice_id == invoice_id, "Invoice doesn't match the one at analytic line" assert float_compare(invoice_id.amount_untaxed, 187.5, precision_digits=2) == 0, "Invoice amount mismatch: %s" % invoice_id.amount_untaxed assert float_compare(invoice_id.amount_tax, 50, precision_digits=2) == 0, "Invoice tax mismatch: %s" % invoice_id.amount_tax + +- + In order to test group invoicing by customer. +- + I create an analytic account 'Sales Integration' for customer Asustek. +- + !record {model: account.analytic.account, id: analytic_asustek01}: + name: Sales Integration + partner_id: base.res_partner_1 +- + I create an analytic account 'Asustek Website' for customer Asustek. +- + !record {model: account.analytic.account, id: analytic_asustek02}: + name: Asustek Website + partner_id: base.res_partner_1 +- + I create an account analytic line. +- + !record {model: account.analytic.line, id: account_analytic_line_0 }: + account_id: analytic_asustek01 + amount: -1.0 + general_account_id: account.a_expense + journal_id: hr_timesheet.analytic_journal + name: sales module extension + product_id: product.product_product_consultant + product_uom_id: product.product_uom_hour + to_invoice: hr_timesheet_invoice.timesheet_invoice_factor2 + unit_amount: 5.00 +- + I create an account analytic line. +- + !record {model: account.analytic.line, id: account_analytic_line_3 }: + account_id: analytic_asustek02 + amount: -1.0 + general_account_id: account.a_expense + journal_id: hr_timesheet.analytic_journal + name: template design + product_id: product.product_product_consultant + product_uom_id: product.product_uom_hour + to_invoice: hr_timesheet_invoice.timesheet_invoice_factor2 + unit_amount: 5.00 +- + Now I create invoice on analytic Line using "Invoice analytic Line" wizard and set group by partner true. +- + !record {model: hr.timesheet.invoice.create, id: hr_timesheet_invoice_create_1}: + date: 1 + name: 1 + product: product.product_product_consultant + time: 1 + group_by_partner: 1 +- + I click on "Create Invoice" button of "Invoice analytic Line" wizard to create invoice and check that only one invoice is created for customer Asustek. +- + !python {model: hr.timesheet.invoice.create}: | + action_result = self.do_create(cr, uid, [ref("hr_timesheet_invoice_create_1")], { + "active_ids": [ref("hr_timesheet_invoice.account_analytic_line_0"),ref("hr_timesheet_invoice.account_analytic_line_3")] + }) + invoice_pool = self.pool.get('account.invoice') + invoice_domain = action_result['domain'] + invoice_ids = invoice_pool.search(cr, uid, invoice_domain) + invoice_id = invoice_pool.browse(cr, uid, invoice_ids)[0] + assert len(invoice_ids) == 1,"only one invoice should be created for customer Asustek." + assert invoice_id.amount_untaxed == 375.0, "Invoice amount mismatch: %s" % invoice_id.amount_untaxed diff --git a/addons/hr_timesheet_invoice/tests/__init__.py b/addons/hr_timesheet_invoice/tests/__init__.py new file mode 100644 index 00000000000..26d048f8f98 --- /dev/null +++ b/addons/hr_timesheet_invoice/tests/__init__.py @@ -0,0 +1 @@ +from . import test_invoice diff --git a/addons/hr_timesheet_invoice/tests/test_invoice.py b/addons/hr_timesheet_invoice/tests/test_invoice.py new file mode 100644 index 00000000000..14782e49897 --- /dev/null +++ b/addons/hr_timesheet_invoice/tests/test_invoice.py @@ -0,0 +1,103 @@ +from openerp.tests.common import TransactionCase + +class TestInvoice(TransactionCase): + """Tests to generate invoices from analytic lines + """ + + def setUp(self): + super(TestInvoice, self).setUp() + cr, uid = self.cr, self.uid + + self.account_invoice = self.registry('account.invoice') + self.account_analytic_account = self.registry('account.analytic.account') + self.account_analytic_line = self.registry('account.analytic.line') + self.product_product = self.registry('product.product') + + self.think_big_id = self.registry("ir.model.data").get_object_reference(cr, uid, "base", "res_partner_18")[1] + self.timesheet_journal_id = self.registry("ir.model.data").get_object_reference(cr, uid, "hr_timesheet", "analytic_journal")[1] + self.expense_journal_id = self.registry("ir.model.data").get_object_reference(cr, uid, "account", "exp")[1] + self.expense_account_id = self.registry("ir.model.data").get_object_reference(cr, uid, "account", "a_expense")[1] + self.expense_account_id = self.registry("ir.model.data").get_object_reference(cr, uid, "account", "a_expense")[1] + self.factor_100_id = self.registry("ir.model.data").get_object_reference(cr, uid, "hr_timesheet_invoice", "timesheet_invoice_factor1")[1] + + self.potato_account_id = self.account_analytic_account.create(cr, uid, { + 'name': 'Potatoes Project', + 'partner_id': self.think_big_id, + 'type': 'contract', + 'state': 'open', + }) + self.carrot_account_id = self.account_analytic_account.create(cr, uid, { + 'name': 'Carrot & Development', + 'partner_id': self.think_big_id, + 'type': 'contract', + 'state': 'open', + }) + self.potato_id = self.product_product.create(cr, uid, { + 'name': 'Potato', + 'list_price': 2, + }) + self.carrot_id = self.product_product.create(cr, uid, { + 'name': 'Carrot', + 'list_price': 3, + }) + + def test_signle_invoice(self): + cr, uid = self.cr, self.uid + first_line = { + 'name': 'One potato', + 'amount': 2, + 'unit_amount': 1, + 'product_id': self.potato_id, + 'account_id': self.potato_account_id, + 'general_account_id': self.expense_account_id, + 'journal_id': self.expense_journal_id, + 'partner_id': self.think_big_id, + 'to_invoice': self.factor_100_id, + } + second_line = { + 'name': 'Two carrots', + 'amount': 6, + 'unit_amount': 2, + 'product_id': self.carrot_id, + 'account_id': self.carrot_account_id, + 'general_account_id': self.expense_account_id, + 'journal_id': self.expense_journal_id, + 'partner_id': self.think_big_id, + 'to_invoice': self.factor_100_id, + } + first_line_id = self.account_analytic_line.create(cr, uid, first_line) + second_line_id = self.account_analytic_line.create(cr, uid, second_line) + + data = {'group_by_partner': False, 'date': True, 'name': True} + invoice_ids = self.account_analytic_line.invoice_cost_create(cr, uid, [first_line_id, second_line_id], data) + self.assertEquals(len(invoice_ids), 2) + for invoice in self.account_invoice.browse(cr, uid, invoice_ids[0]): + self.assertEquals(len(invoice.invoice_line), 1) + line = invoice.invoice_line[0] + if line.product_id.id == self.potato_id: + self.assertEquals(line.account_analytic_id.id, self.potato_account_id) + self.assertEquals(line.price_unit, -2) + self.assertEquals(line.quantity, 1) + else: + self.assertEquals(line.product_id, self.carrot_id) + self.assertEquals(line.account_analytic_id.id, self.carrot_account_id) + self.assertEquals(line.price_unit, -3) + self.assertEquals(line.quantity, 2) + + data = {'group_by_partner': True, 'date': True, 'name': True} + first_line_id = self.account_analytic_line.create(cr, uid, first_line) + second_line_id = self.account_analytic_line.create(cr, uid, second_line) + invoice_ids = self.account_analytic_line.invoice_cost_create(cr, uid, [first_line_id, second_line_id], data) + self.assertEquals(len(invoice_ids), 1) + invoice = self.account_invoice.browse(cr, uid, invoice_ids[0]) + self.assertEquals(len(invoice.invoice_line), 2) + for line in invoice.invoice_line: + if line.product_id.id == self.potato_id: + self.assertEquals(line.account_analytic_id.id, self.potato_account_id) + self.assertEquals(line.price_unit, -2) + self.assertEquals(line.quantity, 1) + else: + self.assertEquals(line.product_id.id, self.carrot_id) + self.assertEquals(line.account_analytic_id.id, self.carrot_account_id) + self.assertEquals(line.price_unit, -3) + self.assertEquals(line.quantity, 2) diff --git a/addons/hr_timesheet_invoice/wizard/hr_timesheet_final_invoice_create.py b/addons/hr_timesheet_invoice/wizard/hr_timesheet_final_invoice_create.py index dc384bb48af..952680e204a 100644 --- a/addons/hr_timesheet_invoice/wizard/hr_timesheet_final_invoice_create.py +++ b/addons/hr_timesheet_invoice/wizard/hr_timesheet_final_invoice_create.py @@ -40,6 +40,7 @@ class final_invoice_create(osv.osv_memory): 'name': fields.boolean('Log of Activity', help='Display detail of work in the invoice line.'), 'price': fields.boolean('Cost', help='Display cost of the item you reinvoice'), 'product': fields.many2one('product.product', 'Product', help='The product that will be used to invoice the remaining amount'), + 'group_by_partner': fields.boolean('Group by Partner', help="If this box is checked, the system will group invoices by customer."), } def do_create(self, cr, uid, ids, context=None): diff --git a/addons/hr_timesheet_invoice/wizard/hr_timesheet_invoice_create.py b/addons/hr_timesheet_invoice/wizard/hr_timesheet_invoice_create.py index b71950b08aa..f3a57d683d2 100644 --- a/addons/hr_timesheet_invoice/wizard/hr_timesheet_invoice_create.py +++ b/addons/hr_timesheet_invoice/wizard/hr_timesheet_invoice_create.py @@ -34,6 +34,7 @@ class hr_timesheet_invoice_create(osv.osv_memory): 'name': fields.boolean('Description', help='The detail of each work done will be displayed on the invoice'), 'price': fields.boolean('Cost', help='The cost of each work done will be displayed on the invoice. You probably don\'t want to check this'), 'product': fields.many2one('product.product', 'Force Product', help='Fill this field only if you want to force to use a specific product. Keep empty to use the real product that comes from the cost.'), + 'group_by_partner': fields.boolean('Group by Partner', help='If this box is checked, the system will group invoices by customer.'), } _defaults = { @@ -51,7 +52,7 @@ class hr_timesheet_invoice_create(osv.osv_memory): @param context: A standard dictionary for contextual values """ analytic_obj = self.pool.get('account.analytic.line') - data = context and context.get('active_ids', []) + data = context and context.get('active_ids', []) or [] for analytic in analytic_obj.browse(cr, uid, data, context=context): if analytic.invoice_id: raise UserError(_("Invoice is already linked to some of the analytic line(s)!")) diff --git a/addons/hr_timesheet_invoice/wizard/hr_timesheet_invoice_create_final_view.xml b/addons/hr_timesheet_invoice/wizard/hr_timesheet_invoice_create_final_view.xml index 5c7fb8a8824..0bb7f914325 100644 --- a/addons/hr_timesheet_invoice/wizard/hr_timesheet_invoice_create_final_view.xml +++ b/addons/hr_timesheet_invoice/wizard/hr_timesheet_invoice_create_final_view.xml @@ -26,7 +26,7 @@ hr.timesheet.invoice.create.final
-

Do you want to show details of each activity to your customer?

+

Show details in invoice lines

@@ -35,9 +35,12 @@ - - - + + + + + +