[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.
This commit is contained in:
jem-odoo
2017-12-11 11:33:31 +01:00
parent f2a5ebe32f
commit 2bf369a172
4 changed files with 4 additions and 309 deletions
-33
View File
@@ -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
+4 -4
View File
@@ -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,
-1
View File
@@ -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
@@ -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')