[FIX] {sale_,}purchase_stock: handle extra qty on MTO SM
To reproduce the issue:
1. In Settings, enable "Multi Routes"
2. In Routes, unarchive MTO
3. Create a product P:
- Storable
- With a vendor
- Routes: MTO + Buy
4. Create and confirm a SO for 10 x P
5. On the delivery, set the done quantity to 12
6. Validate the delivery
Error: The state of the delivery is set to _done_ but the SM has
changed, its demand is 2 and the done quantity is 0. Moreover, a
backorder has been created
Since [1], extra moves are supposed to be merged with the initial
one. However, there is an issue with the above case. The initial SM
has generated a POL (the product is an MTO-buy one). However, since
the extra move is an MTS one, it does not generate any POL. As a
result, the SMs don't have the same value for the field
`created_purchase_line_ids`. This is an issue because for two SM to
be merged, this field must be the same on both SM:
https://github.com/odoo/odoo/blob/ef3c21255c6f2b1172be3b1f8f0dc83bc276d806/addons/purchase_stock/models/stock_move.py#L20-L24
As a result, the SMs are not merged, the method `_create_extra_move`
only returns the extra move, the initial one is lost and it leads to
an unexpected behaviour.
We should also copy the values of `created_purchase_line_ids` to
ensure the merged of both initial and extra moves.
[1] f9867a5fa5
OPW-3504138
closes odoo/odoo#137869
Related: odoo/enterprise#48547
Signed-off-by: Djamel Touati (otd) <otd@odoo.com>
Signed-off-by: Adrien Widart (awt) <awt@odoo.com>
This commit is contained in:
@@ -150,6 +150,7 @@ class StockMove(models.Model):
|
||||
def _prepare_extra_move_vals(self, qty):
|
||||
vals = super(StockMove, self)._prepare_extra_move_vals(qty)
|
||||
vals['purchase_line_id'] = self.purchase_line_id.id
|
||||
vals['created_purchase_line_ids'] = [Command.set(self.created_purchase_line_ids.ids)]
|
||||
return vals
|
||||
|
||||
def _prepare_move_split_vals(self, uom_qty):
|
||||
|
||||
@@ -6,38 +6,81 @@ from odoo.tests.common import TransactionCase, Form
|
||||
|
||||
class TestSalePurchaseStockFlow(TransactionCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestSalePurchaseStockFlow, cls).setUpClass()
|
||||
cls.mto_route = cls.env.ref('stock.route_warehouse0_mto')
|
||||
cls.buy_route = cls.env.ref('purchase_stock.route_warehouse0_buy')
|
||||
cls.mto_route.active = True
|
||||
|
||||
cls.customer_location = cls.env.ref('stock.stock_location_customers')
|
||||
|
||||
cls.vendor = cls.env['res.partner'].create({'name': 'Super Vendor'})
|
||||
cls.customer = cls.env['res.partner'].create({'name': 'Super Customer'})
|
||||
|
||||
cls.mto_product = cls.env['product.product'].create({
|
||||
'name': 'SuperProduct',
|
||||
'type': 'product',
|
||||
'route_ids': [(6, 0, (cls.mto_route + cls.buy_route).ids)],
|
||||
'seller_ids': [(0, 0, {
|
||||
'partner_id': cls.vendor.id,
|
||||
})],
|
||||
})
|
||||
|
||||
def test_cancel_so_with_draft_po(self):
|
||||
"""
|
||||
Sell a MTO+Buy product -> a PO is generated
|
||||
Cancel the SO -> an activity should be added to the PO
|
||||
"""
|
||||
mto_route = self.env.ref('stock.route_warehouse0_mto')
|
||||
buy_route = self.env.ref('purchase_stock.route_warehouse0_buy')
|
||||
mto_route.active = True
|
||||
|
||||
vendor = self.env['res.partner'].create({
|
||||
'name': 'Super Vendor'
|
||||
})
|
||||
|
||||
product = self.env['product.product'].create({
|
||||
'name': 'SuperProduct',
|
||||
'type': 'product',
|
||||
'route_ids': [(6, 0, (mto_route + buy_route).ids)],
|
||||
'seller_ids': [(0, 0, {
|
||||
'partner_id': vendor.id,
|
||||
})],
|
||||
})
|
||||
|
||||
so_form = Form(self.env['sale.order'])
|
||||
so_form.partner_id = self.env.user.partner_id
|
||||
with so_form.order_line.new() as line:
|
||||
line.product_id = product
|
||||
line.product_id = self.mto_product
|
||||
so = so_form.save()
|
||||
so.action_confirm()
|
||||
|
||||
po = self.env['purchase.order'].search([('partner_id', '=', vendor.id)])
|
||||
po = self.env['purchase.order'].search([('partner_id', '=', self.vendor.id)])
|
||||
|
||||
so._action_cancel()
|
||||
|
||||
self.assertTrue(po.activity_ids)
|
||||
self.assertIn(so.name, po.activity_ids.note)
|
||||
|
||||
def test_qty_delivered_with_mto_and_done_quantity_change(self):
|
||||
"""
|
||||
MTO product P
|
||||
Sell 10 x P. On the delivery, set the done quantity to 12, validate and
|
||||
then set the done quantity to 10: the delivered qty of the SOL should
|
||||
be 10
|
||||
"""
|
||||
so = self.env['sale.order'].create({
|
||||
'partner_id': self.customer.id,
|
||||
'order_line': [(0, 0, {
|
||||
'name': self.mto_product.name,
|
||||
'product_id': self.mto_product.id,
|
||||
'product_uom_qty': 10,
|
||||
'product_uom': self.mto_product.uom_id.id,
|
||||
'price_unit': 1,
|
||||
})],
|
||||
})
|
||||
so.action_confirm()
|
||||
|
||||
delivery = so.picking_ids.filtered(lambda p: p.location_dest_id == self.customer_location)
|
||||
sm = delivery.move_ids
|
||||
sm.move_line_ids = [(5, 0, 0), (0, 0, {
|
||||
'location_id': sm.location_id.id,
|
||||
'location_dest_id': sm.location_dest_id.id,
|
||||
'product_id': sm.product_id.id,
|
||||
'qty_done': 12,
|
||||
'company_id': sm.company_id.id,
|
||||
'product_uom_id': sm.product_uom.id,
|
||||
'picking_id': delivery.id,
|
||||
})]
|
||||
delivery.button_validate()
|
||||
|
||||
self.assertEqual(delivery.state, 'done')
|
||||
self.assertEqual(delivery.move_ids.move_line_ids.qty_done, 12)
|
||||
self.assertEqual(so.order_line.qty_delivered, 12)
|
||||
|
||||
sm.move_line_ids.qty_done = 10
|
||||
self.assertEqual(so.order_line.qty_delivered, 10)
|
||||
|
||||
Reference in New Issue
Block a user