From c31ca41c6005316a12ff355983d61e52bc41beab Mon Sep 17 00:00:00 2001 From: Adrien Widart Date: Fri, 1 Oct 2021 13:50:52 +0000 Subject: [PATCH] [FIX] sale_stock,stock_account: ignore SVL without any qty In some conditions, confirming an invoice will raise an Odoo Server Error due to a division by zero. To reproduce the issue: (Need sale_management) 1. Create a product category PC: - Costing Method: Average Cost (AVCO) - Inventory Valuation: Automated 2. Create a product P: - Product Type: Storable - Category: PC - Cost: 50 3. Create a sale order SO: - 5 x P 4. Confirm and process the delivery D 5. Edit P: - Cost: 40 6. Open D and create a return R: - 1 x P without "Update quantities on SO/PO" 7. Process R 8. In Inventory, create a transfer T: - Operation Type: Receipt - 1 x P 9. Process T 10. Back to SO, create an invoice and post it Error: an Odoo Server Error is raised: "ZeroDivisionError: float division by zero" When validating the transfer T, a stock valuation layer is created with a zero quantity and is linked to the first SO.order_line.move_ids (i.e., the SM from Stock to Customer with 5 x P) https://github.com/odoo/odoo/blob/dfab388fdf8c7597774ff1738a1b09295c12fb49/addons/stock_account/models/product.py#L439-L450 Then, when posting the invoice, this SVL is used to make a division: https://github.com/odoo/odoo/blob/dfab388fdf8c7597774ff1738a1b09295c12fb49/addons/stock_account/models/product.py#L690-L691 In such cases, we should ignore this SVL and use the standard price: https://github.com/odoo/odoo/blob/dfab388fdf8c7597774ff1738a1b09295c12fb49/addons/stock_account/models/product.py#L695-L699 OPW-2647921 OPW-2646419 closes odoo/odoo#77909 X-original-commit: d5eccda583e28a15e8a74a4e82f9e76ec32d0806 Signed-off-by: William Henrotin --- .../tests/test_anglo_saxon_valuation.py | 61 +++++++++++++++++++ addons/stock_account/models/product.py | 2 + 2 files changed, 63 insertions(+) diff --git a/addons/sale_stock/tests/test_anglo_saxon_valuation.py b/addons/sale_stock/tests/test_anglo_saxon_valuation.py index ffb94563494..4251b731d8e 100644 --- a/addons/sale_stock/tests/test_anglo_saxon_valuation.py +++ b/addons/sale_stock/tests/test_anglo_saxon_valuation.py @@ -478,6 +478,67 @@ class TestAngloSaxonValuation(ValuationReconciliationTestCommon): self.assertEqual(income_aml.debit, 0) self.assertEqual(income_aml.credit, 24) + def test_avco_ordered_return_and_receipt(self): + """ Sell and deliver some products before the user encodes the products receipt """ + product = self.product + product.invoice_policy = 'order' + product.type = 'product' + product.categ_id.property_cost_method = 'average' + product.categ_id.property_valuation = 'real_time' + product.list_price = 100 + product.standard_price = 50 + + so = self.env['sale.order'].create({ + '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': product.name, + 'product_id': product.id, + 'product_uom_qty': 5.0, + 'product_uom': product.uom_id.id, + 'price_unit': product.list_price})], + }) + so.action_confirm() + + pick = so.picking_ids + pick.move_lines.write({'quantity_done': 5}) + pick.button_validate() + + product.standard_price = 40 + + stock_return_picking_form = Form(self.env['stock.return.picking'] + .with_context(active_ids=pick.ids, active_id=pick.sorted().ids[0], active_model='stock.picking')) + return_wiz = stock_return_picking_form.save() + return_wiz.product_return_moves.quantity = 1 + return_wiz.product_return_moves.to_refund = False + res = return_wiz.create_returns() + + return_pick = self.env['stock.picking'].browse(res['res_id']) + return_pick.move_lines.write({'quantity_done': 1}) + return_pick.button_validate() + + picking = self.env['stock.picking'].create({ + 'location_id': self.env.ref('stock.stock_location_suppliers').id, + 'location_dest_id': self.company_data['default_warehouse'].lot_stock_id.id, + 'picking_type_id': self.company_data['default_warehouse'].in_type_id.id, + }) + # We don't set the price_unit so that the `standard_price` will be used (see _get_price_unit()): + self.env['stock.move'].create({ + 'name': 'test_immediate_validate_1', + 'location_id': self.env.ref('stock.stock_location_suppliers').id, + 'location_dest_id': self.company_data['default_warehouse'].lot_stock_id.id, + 'picking_id': picking.id, + 'product_id': product.id, + 'product_uom': product.uom_id.id, + 'quantity_done': 1, + }) + picking.button_validate() + + invoice = so._create_invoices() + invoice.action_post() + self.assertEqual(invoice.state, 'posted') + # ------------------------------------------------------------------------- # AVCO Delivered # ------------------------------------------------------------------------- diff --git a/addons/stock_account/models/product.py b/addons/stock_account/models/product.py index 7a70b20cb63..04ff048e733 100644 --- a/addons/stock_account/models/product.py +++ b/addons/stock_account/models/product.py @@ -673,6 +673,8 @@ class ProductProduct(models.Model): qty_to_take_on_candidates = qty_to_invoice tmp_value = 0 # to accumulate the value taken on the candidates for candidate in candidates: + if not candidate.quantity: + continue candidate_quantity = abs(candidate.quantity) if candidate.stock_move_id.id in returned_quantities: candidate_quantity -= returned_quantities[candidate.stock_move_id.id]