diff options
| -rw-r--r-- | addons/sale_stock/models/sale_order.py | 38 | ||||
| -rw-r--r-- | addons/sale_stock/tests/test_sale_stock.py | 66 |
2 files changed, 92 insertions, 12 deletions
diff --git a/addons/sale_stock/models/sale_order.py b/addons/sale_stock/models/sale_order.py index b7a9a1620d1..45c2bf5432c 100644 --- a/addons/sale_stock/models/sale_order.py +++ b/addons/sale_stock/models/sale_order.py @@ -210,12 +210,15 @@ class SaleOrderLine(models.Model): for line in self: # TODO: maybe one day, this should be done in SQL for performance sake if line.qty_delivered_method == 'stock_move': qty = 0.0 - for move in line.move_ids.filtered(lambda r: r.state == 'done' and not r.scrapped and line.product_id == r.product_id): - if move.location_dest_id.usage == "customer": - if not move.origin_returned_move_id or (move.origin_returned_move_id and move.to_refund): - qty += move.product_uom._compute_quantity(move.product_uom_qty, line.product_uom) - elif move.location_dest_id.usage != "customer" and move.to_refund: - qty -= move.product_uom._compute_quantity(move.product_uom_qty, line.product_uom) + outgoing_moves, incoming_moves = line._get_outgoing_incoming_moves() + for move in outgoing_moves: + if move.state != 'done': + continue + qty += move.product_uom._compute_quantity(move.product_uom_qty, line.product_uom, rounding_method='HALF-UP') + for move in incoming_moves: + if move.state != 'done': + continue + qty -= move.product_uom._compute_quantity(move.product_uom_qty, line.product_uom, rounding_method='HALF-UP') line.qty_delivered = qty @api.model_create_multi @@ -355,13 +358,26 @@ class SaleOrderLine(models.Model): def _get_qty_procurement(self, previous_product_uom_qty=False): self.ensure_one() qty = 0.0 - for move in self.move_ids.filtered(lambda r: r.state != 'cancel'): - if move.picking_code == 'outgoing': - qty += move.product_uom._compute_quantity(move.product_uom_qty, self.product_uom, rounding_method='HALF-UP') - elif move.picking_code == 'incoming': - qty -= move.product_uom._compute_quantity(move.product_uom_qty, self.product_uom, rounding_method='HALF-UP') + outgoing_moves, incoming_moves = self._get_outgoing_incoming_moves() + for move in outgoing_moves: + qty += move.product_uom._compute_quantity(move.product_uom_qty, self.product_uom, rounding_method='HALF-UP') + for move in incoming_moves: + qty -= move.product_uom._compute_quantity(move.product_uom_qty, self.product_uom, rounding_method='HALF-UP') return qty + def _get_outgoing_incoming_moves(self): + outgoing_moves = self.env['stock.move'] + incoming_moves = self.env['stock.move'] + + for move in self.move_ids.filtered(lambda r: r.state != 'cancel' and not r.scrapped and self.product_id == r.product_id): + if move.location_dest_id.usage == "customer": + if not move.origin_returned_move_id or (move.origin_returned_move_id and move.to_refund): + outgoing_moves |= move + elif move.location_dest_id.usage != "customer" and move.to_refund: + incoming_moves |= move + + return outgoing_moves, incoming_moves + def _get_procurement_group(self): return self.order_id.procurement_group_id diff --git a/addons/sale_stock/tests/test_sale_stock.py b/addons/sale_stock/tests/test_sale_stock.py index 47fd2408bd3..a6b6c26940d 100644 --- a/addons/sale_stock/tests/test_sale_stock.py +++ b/addons/sale_stock/tests/test_sale_stock.py @@ -494,4 +494,68 @@ class TestSaleStock(TestSale): }) so1.picking_ids.button_validate() self.assertEqual(so1.picking_ids.state, 'done') - self.assertEqual(so1.order_line.mapped('qty_delivered'), [1, 1, 1])
\ No newline at end of file + self.assertEqual(so1.order_line.mapped('qty_delivered'), [1, 1, 1]) + + def test_08_quantities(self): + """Change the picking code of the receipts to internal. Make a SO for 10 units, go to the + picking and return 5, edit the SO line to 15 units. + + The purpose of the test is to check the consistencies across the delivered quantities and the + procurement quantities. + """ + # Change the code of the picking type receipt + self.env['stock.picking.type'].search([('code', '=', 'incoming')]).write({'code': 'internal'}) + + # Sell and deliver 10 units + item1 = self.products['prod_order'] + uom_unit = self.env.ref('uom.product_uom_unit') + so1 = self.env['sale.order'].create({ + 'partner_id': self.partner.id, + 'order_line': [ + (0, 0, { + 'name': item1.name, + 'product_id': item1.id, + 'product_uom_qty': 10, + 'product_uom': uom_unit.id, + 'price_unit': item1.list_price, + }), + ], + }) + so1.action_confirm() + + picking = so1.picking_ids + wiz_act = picking.button_validate() + wiz = self.env[wiz_act['res_model']].browse(wiz_act['res_id']) + wiz.process() + + # Return 5 units + stock_return_picking_form = Form(self.env['stock.return.picking'].with_context( + active_ids=picking.ids, + active_id=picking.ids[0], + active_model='stock.picking' + )) + return_wiz = stock_return_picking_form.save() + for return_move in return_wiz.product_return_moves: + return_move.write({ + 'quantity': 5, + 'to_refund': True + }) + res = return_wiz.create_returns() + return_pick = self.env['stock.picking'].browse(res['res_id']) + wiz_act = return_pick.button_validate() + wiz = self.env[wiz_act['res_model']].browse(wiz_act['res_id']) + wiz.process() + + self.assertEqual(so1.order_line.qty_delivered, 5) + + # Deliver 15 instead of 10. + so1.write({ + 'order_line': [ + (1, so1.order_line[0].id, {'product_uom_qty': 15}), + ] + }) + + # A new move of 10 unit (15 - 5 units) + self.assertEqual(so1.order_line.qty_delivered, 5) + self.assertEqual(so1.picking_ids[-1].move_lines.product_qty, 10) + |
