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')