[FIX] sale_stock: delivered product invoice
Before this commit, if extra products are added in a delivery generated by a sale order, the added products will not be take in account to the invoice. Now, the corresponding SO line will be created. Also, the price for the SO lines added this way will be either 0 or the product's sale price, depending of the product's invoicing policy. task-2372938 closes odoo/odoo#62874 X-original-commit: 1f0835a9d01b46b5cc617756f854c25541b644a7 Signed-off-by: Arnold Moyaux <amoyaux@users.noreply.github.com> Signed-off-by: Steve Van Essche <svs-odoo@users.noreply.github.com>
This commit is contained in:
@@ -72,6 +72,38 @@ class StockPicking(models.Model):
|
||||
|
||||
sale_id = fields.Many2one(related="group_id.sale_id", string="Sales Order", store=True, readonly=False)
|
||||
|
||||
def _action_done(self):
|
||||
res = super()._action_done()
|
||||
sale_order_lines_vals = []
|
||||
for move in self.move_lines:
|
||||
sale_order = move.picking_id.sale_id
|
||||
# Creates new SO line only when pickings linked to a sale order and
|
||||
# for moves with qty. done and not already linked to a SO line.
|
||||
if not sale_order or move.sale_line_id or not move.quantity_done:
|
||||
continue
|
||||
product = move.product_id
|
||||
so_line_vals = {
|
||||
'move_ids': [(4, move.id, 0)],
|
||||
'name': product.display_name,
|
||||
'order_id': sale_order.id,
|
||||
'product_id': product.id,
|
||||
'product_uom_qty': 0,
|
||||
'qty_delivered': move.quantity_done,
|
||||
}
|
||||
if product.invoice_policy == 'delivery':
|
||||
# Check if there is already a SO line for this product to get
|
||||
# back its unit price (in case it was manually updated).
|
||||
so_line = sale_order.order_line.filtered(lambda sol: sol.product_id == product)
|
||||
if so_line:
|
||||
so_line_vals['price_unit'] = so_line[0].price_unit
|
||||
elif product.invoice_policy == 'order':
|
||||
# No unit price if the product is invoiced on the ordered qty.
|
||||
so_line_vals['price_unit'] = 0
|
||||
sale_order_lines_vals.append(so_line_vals)
|
||||
|
||||
if sale_order_lines_vals:
|
||||
self.env['sale.order.line'].create(sale_order_lines_vals)
|
||||
return res
|
||||
|
||||
def _log_less_quantities_than_expected(self, moves):
|
||||
""" Log an activity on sale order that are linked to moves. The
|
||||
|
||||
@@ -11,21 +11,22 @@ from odoo.tests import Form, tagged
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestSaleStock(TestSaleCommon, ValuationReconciliationTestCommon):
|
||||
|
||||
def _get_new_sale_order(self, amount=10.0):
|
||||
def _get_new_sale_order(self, amount=10.0, product=False):
|
||||
""" Creates and returns a sale order with one default order line.
|
||||
|
||||
:param float amount: quantity of product for the order line (10 by default)
|
||||
"""
|
||||
product = product or self.company_data['product_delivery_no']
|
||||
sale_order_vals = {
|
||||
'partner_id': self.partner_a.id,
|
||||
'partner_invoice_id': self.partner_a.id,
|
||||
'partner_shipping_id': self.partner_a.id,
|
||||
'order_line': [(0, 0, {
|
||||
'name': self.company_data['product_delivery_no'].name,
|
||||
'product_id': self.company_data['product_delivery_no'].id,
|
||||
'name': product.name,
|
||||
'product_id': product.id,
|
||||
'product_uom_qty': amount,
|
||||
'product_uom': self.company_data['product_delivery_no'].uom_id.id,
|
||||
'price_unit': self.company_data['product_delivery_no'].list_price})],
|
||||
'product_uom': product.uom_id.id,
|
||||
'price_unit': product.list_price})],
|
||||
'pricelist_id': self.company_data['default_pricelist'].id,
|
||||
}
|
||||
sale_order = self.env['sale.order'].create(sale_order_vals)
|
||||
@@ -770,6 +771,88 @@ class TestSaleStock(TestSaleCommon, ValuationReconciliationTestCommon):
|
||||
# Checks the delivery amount (must still be 10).
|
||||
self.assertEqual(sale_order.order_line.qty_delivered, 10)
|
||||
|
||||
def test_13_delivered_qty(self):
|
||||
""" Creates a sale order, valids it and adds a new move line in the delivery for a
|
||||
product with an invoicing policy on 'order', then checks a new SO line was created.
|
||||
After that, creates a second sale order and does the same thing but with a product
|
||||
with and invoicing policy on 'ordered'.
|
||||
"""
|
||||
product_inv_on_delivered = self.company_data['product_delivery_no']
|
||||
# Configure a product with invoicing policy on order.
|
||||
product_inv_on_order = self.env['product.product'].create({
|
||||
'name': 'Shenaniffluffy',
|
||||
'type': 'consu',
|
||||
'invoice_policy': 'order',
|
||||
'list_price': 55.0,
|
||||
})
|
||||
# Creates a sale order for 3 products invoiced on qty. delivered.
|
||||
sale_order = self._get_new_sale_order(amount=3)
|
||||
# Confirms the sale order, then increases the delivered qty., adds a new
|
||||
# line and valids the delivery.
|
||||
sale_order.action_confirm()
|
||||
self.assertTrue(sale_order.picking_ids)
|
||||
self.assertEqual(len(sale_order.order_line), 1)
|
||||
self.assertEqual(sale_order.order_line.qty_delivered, 0)
|
||||
picking = sale_order.picking_ids
|
||||
|
||||
picking_form = Form(picking)
|
||||
with picking_form.move_line_ids_without_package.edit(0) as move:
|
||||
move.qty_done = 5
|
||||
with picking_form.move_line_ids_without_package.new() as new_move:
|
||||
new_move.product_id = product_inv_on_order
|
||||
new_move.qty_done = 5
|
||||
picking = picking_form.save()
|
||||
picking.button_validate()
|
||||
|
||||
# Check a new sale order line was correctly created.
|
||||
self.assertEqual(len(sale_order.order_line), 2)
|
||||
so_line_1 = sale_order.order_line[0]
|
||||
so_line_2 = sale_order.order_line[1]
|
||||
self.assertEqual(so_line_1.product_id.id, product_inv_on_delivered.id)
|
||||
self.assertEqual(so_line_1.product_uom_qty, 3)
|
||||
self.assertEqual(so_line_1.qty_delivered, 5)
|
||||
self.assertEqual(so_line_1.price_unit, 70.0)
|
||||
self.assertEqual(so_line_2.product_id.id, product_inv_on_order.id)
|
||||
self.assertEqual(so_line_2.product_uom_qty, 0)
|
||||
self.assertEqual(so_line_2.qty_delivered, 5)
|
||||
self.assertEqual(
|
||||
so_line_2.price_unit, 0,
|
||||
"Shouldn't get the product price as the invoice policy is on qty. ordered")
|
||||
|
||||
# Creates a second sale order for 3 product invoiced on qty. ordered.
|
||||
sale_order = self._get_new_sale_order(product=product_inv_on_order, amount=3)
|
||||
# Confirms the sale order, then increases the delivered qty., adds a new
|
||||
# line and valids the delivery.
|
||||
sale_order.action_confirm()
|
||||
self.assertTrue(sale_order.picking_ids)
|
||||
self.assertEqual(len(sale_order.order_line), 1)
|
||||
self.assertEqual(sale_order.order_line.qty_delivered, 0)
|
||||
picking = sale_order.picking_ids
|
||||
|
||||
picking_form = Form(picking)
|
||||
with picking_form.move_line_ids_without_package.edit(0) as move:
|
||||
move.qty_done = 5
|
||||
with picking_form.move_line_ids_without_package.new() as new_move:
|
||||
new_move.product_id = product_inv_on_delivered
|
||||
new_move.qty_done = 5
|
||||
picking = picking_form.save()
|
||||
picking.button_validate()
|
||||
|
||||
# Check a new sale order line was correctly created.
|
||||
self.assertEqual(len(sale_order.order_line), 2)
|
||||
so_line_1 = sale_order.order_line[0]
|
||||
so_line_2 = sale_order.order_line[1]
|
||||
self.assertEqual(so_line_1.product_id.id, product_inv_on_order.id)
|
||||
self.assertEqual(so_line_1.product_uom_qty, 3)
|
||||
self.assertEqual(so_line_1.qty_delivered, 5)
|
||||
self.assertEqual(so_line_1.price_unit, 55.0)
|
||||
self.assertEqual(so_line_2.product_id.id, product_inv_on_delivered.id)
|
||||
self.assertEqual(so_line_2.product_uom_qty, 0)
|
||||
self.assertEqual(so_line_2.qty_delivered, 5)
|
||||
self.assertEqual(
|
||||
so_line_2.price_unit, 70.0,
|
||||
"Should get the product price as the invoice policy is on qty. delivered")
|
||||
|
||||
def test_08_sale_return_qty_and_cancel(self):
|
||||
"""
|
||||
Test a SO with a product on delivery with a 5 quantity.
|
||||
|
||||
Reference in New Issue
Block a user