From 2bf369a17211a4dbef63b47ea4edcc27c3732df8 Mon Sep 17 00:00:00 2001 From: jem-odoo Date: Mon, 20 Nov 2017 10:06:54 +0100 Subject: [PATCH] [IMP] sale: better amount to invoice and invoiced computation Since quantity to invoice and quantity invoiced already take into account refund invoices, the calculation of those amounts can be simplify. Also, we simply need those on the sale.report, so removing them from sale.order.line model will ease performance. --- addons/sale/models/sale.py | 33 --- addons/sale/report/sale_report.py | 8 +- addons/sale/tests/__init__.py | 1 - ...test_sale_to_invoice_and_to_be_invoiced.py | 271 ------------------ 4 files changed, 4 insertions(+), 309 deletions(-) delete mode 100644 addons/sale/tests/test_sale_to_invoice_and_to_be_invoiced.py diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py index aa802a43a1d..8851d7e0fdd 100644 --- a/addons/sale/models/sale.py +++ b/addons/sale/models/sale.py @@ -718,36 +718,6 @@ class SaleOrderLine(models.Model): for line in self: line.qty_delivered_updateable = (line.order_id.state == 'sale') and (line.product_id.service_type == 'manual') and (line.product_id.expense_policy == 'no') - @api.depends('state', - 'price_reduce_taxinc', - 'qty_delivered', - 'invoice_lines', - 'invoice_lines.price_total', - 'invoice_lines.invoice_id', - 'invoice_lines.invoice_id.state', - 'invoice_lines.invoice_id.refund_invoice_ids', - 'invoice_lines.invoice_id.refund_invoice_ids.state', - 'invoice_lines.invoice_id.refund_invoice_ids.amount_total') - def _compute_invoice_amount(self): - for line in self: - # Invoice lines referenced by this line - invoice_lines = line.invoice_lines.filtered(lambda l: l.invoice_id.state in ('open', 'paid')) - # Refund invoices linked to invoice_lines - refund_invoices = invoice_lines.mapped('invoice_id.refund_invoice_ids').filtered(lambda inv: inv.state in ('open', 'paid')) - # Total invoiced amount - invoiced_amount_total = sum(invoice_lines.mapped('price_total')) - # Total refunded amount - refund_amount_total = sum(refund_invoices.mapped('amount_total')) - # Total of remaining amount to invoice on the sale ordered (and draft invoice included) to support upsell (when - # delivered quantity is higher than ordered one). Draft invoice are ignored on purpose, the 'to invoice' should - # come only from the SO lines. - total_sale_line = line.price_total - if line.product_id.invoice_policy == 'delivery': - total_sale_line = line.price_reduce_taxinc * line.qty_delivered - - line.amt_invoiced = invoiced_amount_total - refund_amount_total - line.amt_to_invoice = (total_sale_line - invoiced_amount_total) if line.state in ['sale', 'done'] else 0.0 - @api.depends('qty_invoiced', 'qty_delivered', 'product_uom_qty', 'order_id.state') def _get_to_invoice_qty(self): """ @@ -917,9 +887,6 @@ class SaleOrderLine(models.Model): customer_lead = fields.Float( 'Delivery Lead Time', required=True, default=0.0, help="Number of days between the order confirmation and the shipping of the products to the customer", oldname="delay") - amt_to_invoice = fields.Monetary(string='Amount To Invoice', compute='_compute_invoice_amount', compute_sudo=True, store=True) - amt_invoiced = fields.Monetary(string='Amount Invoiced', compute='_compute_invoice_amount', compute_sudo=True, store=True) - layout_category_id = fields.Many2one('sale.layout_category', string='Section') layout_category_sequence = fields.Integer(string='Layout Sequence') # TODO: remove layout_category_sequence in master or make it work properly diff --git a/addons/sale/report/sale_report.py b/addons/sale/report/sale_report.py index cb7677c5b58..e3be9566cde 100644 --- a/addons/sale/report/sale_report.py +++ b/addons/sale/report/sale_report.py @@ -26,8 +26,8 @@ class SaleReport(models.Model): user_id = fields.Many2one('res.users', 'Salesperson', readonly=True) price_total = fields.Float('Total', readonly=True) price_subtotal = fields.Float('Untaxed Total', readonly=True) - amt_to_invoice = fields.Float('Amount To Invoice', readonly=True) - amt_invoiced = fields.Float('Amount Invoiced', readonly=True) + amount_to_invoice = fields.Float('Amount To Invoice', readonly=True) + amount_invoiced = fields.Float('Amount Invoiced', readonly=True) product_tmpl_id = fields.Many2one('product.template', 'Product Template', readonly=True) categ_id = fields.Many2one('product.category', 'Product Category', readonly=True) nbr = fields.Integer('# of Lines', readonly=True) @@ -58,8 +58,8 @@ class SaleReport(models.Model): sum(l.qty_to_invoice / u.factor * u2.factor) as qty_to_invoice, sum(l.price_total / COALESCE(cr.rate, 1.0)) as price_total, sum(l.price_subtotal / COALESCE(cr.rate, 1.0)) as price_subtotal, - sum(l.amt_to_invoice / COALESCE(cr.rate, 1.0)) as amt_to_invoice, - sum(l.amt_invoiced / COALESCE(cr.rate, 1.0)) as amt_invoiced, + sum(l.price_reduce * l.qty_to_invoice / COALESCE(cr.rate, 1.0)) as amount_to_invoice, + sum(l.price_reduce * l.qty_invoiced / COALESCE(cr.rate, 1.0)) as amount_invoiced, count(*) as nbr, s.name as name, s.date_order as date, diff --git a/addons/sale/tests/__init__.py b/addons/sale/tests/__init__.py index 7f9d41d4921..3aac8a509ba 100644 --- a/addons/sale/tests/__init__.py +++ b/addons/sale/tests/__init__.py @@ -3,4 +3,3 @@ from . import test_sale_to_invoice from . import test_sale_order from . import test_product_id_change -from . import test_sale_to_invoice_and_to_be_invoiced diff --git a/addons/sale/tests/test_sale_to_invoice_and_to_be_invoiced.py b/addons/sale/tests/test_sale_to_invoice_and_to_be_invoiced.py deleted file mode 100644 index cc4f9cfa438..00000000000 --- a/addons/sale/tests/test_sale_to_invoice_and_to_be_invoiced.py +++ /dev/null @@ -1,271 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -from odoo.addons.account.tests.account_test_classes import AccountingTestCase - - -class TestSaleOrderInvoicing(AccountingTestCase): - - def test_sale_to_invoice_and_to_be_invoiced(self): - """ Testing amount to invoice and amount to be invoiced, with advances. """ - - partner = self.env.ref('base.res_partner_1') - partner.property_account_receivable_id = self.env['account.account'].search( - [('user_type_id', '=', self.env.ref('account.data_account_type_revenue').id)], limit=1) - product_1 = self.env.ref('product.product_product_8') - product_2 = self.env.ref('product.product_product_11') - - # In order to test I create sales order and confirmed it. - order = self.env['sale.order'].create({ - 'partner_id': partner.id, - 'partner_invoice_id': partner.id, - 'partner_shipping_id': partner.id, - 'order_line': [ - (0, 0, { - 'name': product_1.name, - 'product_id': product_1.id, - 'product_uom_qty': 2, - 'qty_delivered': 2, - 'product_uom': product_1.uom_id.id, - 'price_unit': 100 - }), - (0, 0, { - 'name': product_2.name, - 'product_id': product_2.id, - 'product_uom_qty': 2, - 'qty_delivered': 2, - 'product_uom': product_2.uom_id.id, - 'price_unit': 300 - }) - ], - 'pricelist_id': self.env.ref('product.list0').id, - }) - - context = {"active_model": 'sale.order', "active_ids": [order.id], "active_id": order.id} - order.with_context(context).action_confirm() - - # Now I create invoice. - advance_product = self.env.ref('sale.advance_product_0') - advance_product.property_account_income_id = self.env['account.account'].search( - [('user_type_id', '=', self.env.ref('account.data_account_type_revenue').id)], limit=1) - - payment = self.env['sale.advance.payment.inv'].create({ - 'advance_payment_method': 'fixed', - 'amount': 500, - 'product_id': advance_product.id, - }) - payment.with_context(context).create_invoices() - invoice_1 = order.invoice_ids[0] - - # Lets create one more invoice. - - payment = self.env['sale.advance.payment.inv'].create({ - 'advance_payment_method': 'fixed', - 'amount': 300, - 'product_id': advance_product.id, - }) - payment.with_context(context).create_invoices() - invoice_2 = order.invoice_ids[1] - - self.assertEqual(sum(order.order_line.mapped('amt_to_invoice')), 800.0, 'Sale: the Amount To Invoice for the sale order should be 800.0.') - self.assertEqual(sum(order.order_line.mapped('amt_invoiced')), 0.0, 'Sale: the Amount Invoiced for the sale order should be 0.0.') - - # Now I validate invoice_1. - invoice_1.invoice_validate() - - self.assertEqual(sum(order.order_line.mapped('amt_to_invoice')), 300.0, 'Sale: the Amount To Invoice for the sale order should be 300.0.') - self.assertEqual(sum(order.order_line.mapped('amt_invoiced')), 500.0, 'Sale: the Amount Invoiced for the sale order should be 500.0.') - - # Now I validate invoice_2. - invoice_2.invoice_validate() - - self.assertEqual(sum(order.order_line.mapped('amt_to_invoice')), 0.0, 'Sale: the Amount To Invoice for the sale order should be 0.0.') - self.assertEqual(sum(order.order_line.mapped('amt_invoiced')), 800.0, 'Sale: the Amount Invoiced for the sale order should be 800.0.') - - # Lets create a refund invoice for invoice_1. - # I refund the invoice Using Refund Button. - context = {"active_model": 'account.invoice', "active_ids": [invoice_1.id], "active_id": invoice_1.id} - account_invoice_refund_1 = self.env['account.invoice.refund'].with_context(context).create(dict( - description='Refund for Invoice 1', - filter_refund='refund' - )) - - # I clicked on refund button. - account_invoice_refund_1.with_context(context).invoice_refund() - invoice_1.refund_invoice_ids and invoice_1.refund_invoice_ids[0].invoice_validate() - - self.assertEqual(sum(order.order_line.mapped('amt_to_invoice')), 0.0, 'Sale: the Amount To Invoice for the sale order should be 0.0.') - self.assertEqual(sum(order.order_line.mapped('amt_invoiced')), 300.0, 'Sale: the Amount Invoiced for the sale order should be 300.0.') - - # Lets create a refund invoice for invoice_2. - # I refund the invoice Using Refund Button. - context = {"active_model": 'account.invoice', "active_ids": [invoice_2.id], "active_id": invoice_2.id} - account_invoice_refund_2 = self.env['account.invoice.refund'].with_context(context).create(dict( - description='Refund for Invoice 2', - filter_refund='refund' - )) - - # I clicked on refund button. - account_invoice_refund_2.with_context(context).invoice_refund() - invoice_2.refund_invoice_ids and invoice_2.refund_invoice_ids[0].invoice_validate() - - self.assertEqual(sum(order.order_line.mapped('amt_to_invoice')), 0.0, 'Sale: the Amount To Invoice for the sale order should be 0.0.') - self.assertEqual(sum(order.order_line.mapped('amt_invoiced')), 0.0, 'Sale: the Amount Invoiced for the sale order should be 0.0.') - - def test_amount_delivered_and_ordered_qty(self): - """ Testing amount to invoice and amount to be invoiced, with different invoice policy and cancelling SO """ - # disable mail feature to speed test - context_no_mail = { - 'tracking_disable': True, - 'mail_notrack': True, - 'mail_create_nolog': True, - } - - # create products and partner - Product = self.env['product.product'].with_context(context_no_mail) - product_order = Product.create({ - 'name': "Service Ordered", - 'standard_price': 10, - 'list_price': 20, - 'type': 'service', - 'invoice_policy': 'order', - 'default_code': 'SERV-ORDERED1', - 'taxes_id': False, # force no tax - }) - product_deli = Product.create({ - 'name': 'iMac', - 'standard_price': 400, - 'list_price': 500, - 'type': 'consu', - 'invoice_policy': 'delivery', - 'default_code': 'E-COM09', - 'taxes_id': False, # force no tax - }) - - partner = self.env.ref('base.res_partner_1') - partner.property_account_receivable_id = self.env['account.account'].search([('user_type_id', '=', self.env.ref('account.data_account_type_revenue').id)], limit=1) - - # create Sales order, with 2 lines: one delivered, one ordered - order = self.env['sale.order'].with_context(context_no_mail).create({ - 'partner_id': partner.id, - 'partner_invoice_id': partner.id, - 'partner_shipping_id': partner.id, - 'pricelist_id': self.env.ref('product.list0').id, - }) - sale_line_ord = self.env['sale.order.line'].with_context(context_no_mail).create({ - 'order_id': order.id, - 'name': product_order.name, - 'product_id': product_order.id, - 'product_uom_qty': 2, - 'qty_delivered': 0, - 'product_uom': product_order.uom_id.id, - 'price_unit': 20 - }) - sale_line_deli = self.env['sale.order.line'].with_context(context_no_mail).create({ - 'order_id': order.id, - 'name': product_deli.name, - 'product_id': product_deli.id, - 'product_uom_qty': 2, - 'qty_delivered': 0, - 'product_uom': product_deli.uom_id.id, - 'price_unit': 500 - }) - - self.assertEqual(sale_line_deli.amt_to_invoice, 0.0, 'Amount to invoice for delivered qty SO line should zero, since its state is draft') - self.assertEqual(sale_line_deli.amt_invoiced, 0.0, 'Amount invoiced for delivered qty SO line should zero, since its state is draft, and there is no invoice at this moment') - self.assertEqual(sale_line_ord.amt_to_invoice, 0.0, 'Amount to invoice for ordered qty SO line should zero, since its state is draft') - self.assertEqual(sale_line_ord.amt_invoiced, 0.0, 'Amount invoiced for ordered qty SO line should zero, since its state is draft, and there is no invoice at this moment') - - # confirm SO - order.action_confirm() - - self.assertEqual(sale_line_deli.amt_to_invoice, 0.0, 'Amount to invoice for delivered SO line is still zero, since its delivered quantity is zero') - self.assertEqual(sale_line_deli.amt_invoiced, 0.0, 'Amount invoiced for delivered SO line is still zero, since its delivered quantity is zero, and there is no invoice at this moment') - self.assertEqual(sale_line_ord.amt_to_invoice, 40.0, 'Amount to invoice for ordered SO line should be 40, even if there is no invoice') - self.assertEqual(sale_line_ord.amt_invoiced, 0.0, 'Amount invoiced for ordered SO line should zero, there is no invoice at this moment') - - # create invoice for delivered product - invoice_context = {"active_model": 'sale.order', "active_ids": [order.id], "active_id": order.id} - payment = self.env['sale.advance.payment.inv'].with_context(context_no_mail).create({ - 'advance_payment_method': 'delivered', - }) - payment.with_context(invoice_context).create_invoices() - invoice_1 = order.invoice_ids[0] - - self.assertEqual(sale_line_deli.amt_to_invoice, 0.0, 'Amount to invoice for delivered SO line is still zero, since its delivered quantity (on SO line) is zero') - self.assertEqual(sale_line_deli.amt_invoiced, 0.0, 'Amount invoiced for delivered SO line is still zero, since its delivered quantity (on SO line) is zero, and there is no invoice at this moment') - self.assertEqual(sale_line_ord.amt_to_invoice, 40.0, 'Amount to invoice for ordered SO line should be 40, since there is a draft invoice') - self.assertEqual(sale_line_ord.amt_invoiced, 0.0, 'Amount invoiced for ordered SO line should zero, there is no validated invoice at this moment') - - # validate invoice - invoice_1.invoice_validate() - - self.assertEqual(sale_line_deli.amt_to_invoice, 0.0, 'Amount to invoice for delivered SO line is still zero, since its delivered quantity (on SO line) is zero') - self.assertEqual(sale_line_deli.amt_invoiced, 0.0, 'Amount invoiced for delivered SO line is still zero, since its delivered quantity (on SO line) is zero, and there is no invoice at this moment') - self.assertEqual(sale_line_ord.amt_to_invoice, 0.0, 'Amount to invoice for ordered SO line is zero, since the invoice is validated') - self.assertEqual(sale_line_ord.amt_invoiced, 40.0, 'Amount invoiced for ordered SO line should 40, there is a validated invoice at this moment') - - # deliver 2 unit of product_deli - sale_line_deli.write({'qty_delivered': 2}) - - self.assertEqual(sale_line_deli.amt_to_invoice, 1000.0, 'Amount to invoice for delivered SO line is now 1000, since its delivered quantity (on SO line) is 2 (unit price = 500)') - self.assertEqual(sale_line_deli.amt_invoiced, 0.0, 'Amount invoiced for delivered SO line is still zero, since there is no invoice at this moment for this product') - self.assertEqual(sale_line_ord.amt_to_invoice, 0.0, 'Amount to invoice for ordered SO line is zero, since the invoice is validated') - self.assertEqual(sale_line_ord.amt_invoiced, 40.0, 'Amount invoiced for ordered SO line should 40, there is a validated invoice at this moment') - - # create second invoice and validate it - payment = self.env['sale.advance.payment.inv'].with_context(context_no_mail).create({ - 'advance_payment_method': 'delivered', - }) - payment.with_context(invoice_context).create_invoices() - invoice_2 = order.invoice_ids.sorted(key='id')[1] - invoice_2.invoice_validate() - - self.assertEqual(sale_line_deli.amt_to_invoice, 0.0, 'Amount to invoice for delivered SO line is 0, since this is all invoiced') - self.assertEqual(sale_line_deli.amt_invoiced, 1000.0, 'Amount invoiced for delivered SO line is now 1000, since the invoice for this product is validated') - self.assertEqual(sale_line_ord.amt_to_invoice, 0.0, 'Amount to invoice for ordered SO line is zero, since the invoice is validated') - self.assertEqual(sale_line_ord.amt_invoiced, 40.0, 'Amount invoiced for ordered SO line should 40, there is a validated invoice at this moment') - - # deliver 1 more unit of product_deli - sale_line_deli.write({'qty_delivered': sale_line_deli.qty_delivered+1}) - - self.assertEqual(sale_line_deli.amt_to_invoice, 500.0, 'Amount to invoice for delivered SO line is now 500, since we got a uninvoiced unit') - self.assertEqual(sale_line_deli.amt_invoiced, 1000.0, 'Amount invoiced for delivered SO line is still 1000, even the delivered qty change') - self.assertEqual(sale_line_ord.amt_to_invoice, 0.0, 'Amount to invoice for ordered SO line is zero, since the invoice is validated') - self.assertEqual(sale_line_ord.amt_invoiced, 40.0, 'Amount invoiced for ordered SO line should 40, there is a validated invoice at this moment') - - # create third invoice - payment = self.env['sale.advance.payment.inv'].with_context(context_no_mail).create({ - 'advance_payment_method': 'delivered', - }) - payment.with_context(invoice_context, open_invoices=True).create_invoices() - invoice_3 = order.invoice_ids.sorted(key='id')[2] - - self.assertEqual(sale_line_deli.amt_to_invoice, 500.0, 'Amount to invoice for delivered SO line is now 500, since we got a uninvoiced unit') - self.assertEqual(sale_line_deli.amt_invoiced, 1000.0, 'Amount invoiced for delivered SO line is still 1000') - self.assertEqual(sale_line_ord.amt_to_invoice, 0.0, 'Amount to invoice for ordered SO line is zero, since the invoice is validated') - self.assertEqual(sale_line_ord.amt_invoiced, 40.0, 'Amount invoiced for ordered SO line should 40, there is a validated invoice at this moment') - - # cancel order - order.action_cancel() - - self.assertEqual(sale_line_deli.amt_to_invoice, 0.0, 'Amount to invoice for delivered SO line is now 0, since SO is cancel') - self.assertEqual(sale_line_deli.amt_invoiced, 1000.0, 'Amount invoiced for delivered SO line is still 1000, even if the SO is cancelled') - self.assertEqual(sale_line_ord.amt_to_invoice, 0.0, 'Amount to invoice for ordered SO line is zero, since the invoice is validated') - self.assertEqual(sale_line_ord.amt_invoiced, 40.0, 'Amount invoiced for ordered SO line should 40, there is a validated invoice at this moment') - - # change the unit price on 3rd invoice - invoice_3.invoice_line_ids.write({'price_unit': 300}) - - self.assertEqual(sale_line_deli.amt_to_invoice, 0.0, 'Amount to invoice for delivered SO line is now 0: nothing should change as the invoice is in draft state') - self.assertEqual(sale_line_deli.amt_invoiced, 1000.0, 'Amount invoiced for delivered SO line is still 1000, even if we change price of unvalidated invoice') - self.assertEqual(sale_line_ord.amt_to_invoice, 0.0, 'Amount to invoice for ordered SO line is zero, since the invoice is validated') - self.assertEqual(sale_line_ord.amt_invoiced, 40.0, 'Amount invoiced for ordered SO line should 40, there is a validated invoice at this moment') - - # validate third invoice - invoice_3.invoice_validate() - - self.assertEqual(sale_line_deli.amt_to_invoice, 0.0, 'Amount to invoice for delivered SO line is now 0, since SO is cancel') - self.assertEqual(sale_line_deli.amt_invoiced, 1300.0, 'Amount invoiced for delivered SO line is incremented, since the 3rd invoice for this product is validated') - self.assertEqual(sale_line_ord.amt_to_invoice, 0.0, 'Amount to invoice for ordered SO line is zero, since the invoice is validated') - self.assertEqual(sale_line_ord.amt_invoiced, 40.0, 'Amount invoiced for ordered SO line should 40, there is a validated invoice at this moment')