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 @@ - - - + + + + + +