[FIX] purchase_stock, mrp_subcontracting_dropshipping: dropship to subcontract
A user can not buy a subcontracted product and directly deliver it
(dropship) to another subcontractor. Moreover, in such situation, the
received quantity is not correctly computed.
To reproduce the issue:
(Enable debug mode)
1. In Settings, enable "Storage Locations"
2. Create three products:
- P1:
- Storable
- With a vendor V1
- P2:
- Storable
- With a vendor V2
- P3:
- Consumable
3. Edit V1:
- Customer Location: Physical Locations/Subcontracting Location
4. Create two BoMs:
- Product: P1
- Type: Subcontracting
- Subcontractors: V1
- Components: 1 x P2
- Product: P2
- Type: Subcontracting
- Subcontractors: V2
- Components: 1 x P3
5. Create a PO:
- Vendor: V2
- Deliver To: Dropship
- Drop Ship Address: V1
- Products: 1 x P2
6. Confirm the PO
Error: a Validation Error is raised at `mrp.production` creation because
of a missing field (`picking_type_id`).
In some cases, when getting the values to create the MO, the basic
`_prepare_subcontract_mo_vals` does not return any `picking_type_id`.
That's the reason why an override has been added in
`/mrp_subcontracting_dropshipping` (see [1] for more details). Thanks to
this override, if the usage of the destination location is `customer`,
we know that we are in a "dropship" situation and we manually define the
`picking_type_id`:
https://github.com/odoo/odoo/blob/d73e70f22e47e81e59aff0c9f578aff260447256/addons/mrp_subcontracting_dropshipping/models/stock_picking.py#L15-L17
However, in the above case, the subcontracted stock move starts from a
subcontracted location and also goes to a subcontracted location
(because of step 3). As a result, the if-condition is not respected and
the `picking_type_id` is not defined.
Once this issue is solved, there is a second one: suppose the PO
confirmed. The user validates the transfer. New error: the received
quantity on the PO is not updated. This is because of an incorrect
condition in `/purchase_stock._compute_qty_received`:
https://github.com/odoo/odoo/blob/d0537e32e5aa4b0fe2ad674ab3ec7c42ae1a12f9/addons/purchase_stock/models/purchase.py#L306-L315
Added by [2] and modified by [3], this condition checks that:
- the destination location usage is `internal` (correct, this is a
subcontracting location)
- the source location usage is not `supplier` (correct, this is a
subcontracting location, so it is `internal`)
- the destination location is not part of the warehouse children (here
is the issue: the SM does not have any warehouse, so it gives a false
positive)
So, because of the incorrect third condition, the condition is
respected. And because `to_refund` is `False` (which is correct), we
don't do anything. That's the reason why the received quantity is not
updated.
[1] d73e70f22e47e81e59aff0c9f578aff260447256
[2] e6a1e240f1
[3] fd22fe221026e353aac7414348b029ae7e290b2f
OPW-2922546
closes odoo/odoo#102224
X-original-commit: a51b4158b964a11fa371a9eb8c877c9ec395f412
Signed-off-by: Arnold Moyaux (arm) <arm@odoo.com>
Signed-off-by: Adrien Widart <awt@odoo.com>
This commit is contained in:
@@ -24,7 +24,10 @@ class StockPicking(models.Model):
|
||||
|
||||
def _prepare_subcontract_mo_vals(self, subcontract_move, bom):
|
||||
res = super()._prepare_subcontract_mo_vals(subcontract_move, bom)
|
||||
if not res.get('picking_type_id') and subcontract_move.location_dest_id.usage == 'customer':
|
||||
if not res.get('picking_type_id') and (
|
||||
subcontract_move.location_dest_id.usage == 'customer'
|
||||
or subcontract_move.location_dest_id.is_subcontracting_location
|
||||
):
|
||||
# If the if-condition is respected, it means that `subcontract_move` is not
|
||||
# related to a specific warehouse. This can happen if, for instance, the user
|
||||
# confirms a PO with a subcontracted product that should be delivered to a
|
||||
|
||||
@@ -248,3 +248,75 @@ class TestSubcontractingDropshippingFlows(TestMrpSubcontractingCommon):
|
||||
self.assertEqual(delivery.state, 'done')
|
||||
self.assertEqual(mo.state, 'done')
|
||||
self.assertEqual(po.order_line.qty_received, 1)
|
||||
|
||||
def test_po_to_subcontractor(self):
|
||||
"""
|
||||
Create and confirm a PO with a subcontracted move. The bought product is
|
||||
also a component of another subcontracted product. The picking type of
|
||||
the PO is 'Dropship' and the delivery address is the other subcontractor
|
||||
"""
|
||||
subcontractor, super_subcontractor = self.env['res.partner'].create([
|
||||
{'name': 'Subcontractor'},
|
||||
{'name': 'SuperSubcontractor'},
|
||||
])
|
||||
|
||||
super_product, product, component = self.env['product.product'].create([{
|
||||
'name': 'Super Product',
|
||||
'type': 'product',
|
||||
'seller_ids': [(0, 0, {'partner_id': super_subcontractor.id})],
|
||||
}, {
|
||||
'name': 'Product',
|
||||
'type': 'product',
|
||||
'seller_ids': [(0, 0, {'partner_id': subcontractor.id})],
|
||||
}, {
|
||||
'name': 'Component',
|
||||
'type': 'consu',
|
||||
}])
|
||||
|
||||
_, bom_product = self.env['mrp.bom'].create([{
|
||||
'product_tmpl_id': super_product.product_tmpl_id.id,
|
||||
'product_qty': 1,
|
||||
'type': 'subcontract',
|
||||
'subcontractor_ids': [(6, 0, super_subcontractor.ids)],
|
||||
'bom_line_ids': [
|
||||
(0, 0, {'product_id': product.id, 'product_qty': 1}),
|
||||
],
|
||||
}, {
|
||||
'product_tmpl_id': product.product_tmpl_id.id,
|
||||
'product_qty': 1,
|
||||
'type': 'subcontract',
|
||||
'subcontractor_ids': [(6, 0, subcontractor.ids)],
|
||||
'bom_line_ids': [
|
||||
(0, 0, {'product_id': component.id, 'product_qty': 1}),
|
||||
],
|
||||
}])
|
||||
|
||||
dropship_picking_type = self.env['stock.picking.type'].search([
|
||||
('company_id', '=', self.env.company.id),
|
||||
('default_location_src_id.usage', '=', 'supplier'),
|
||||
('default_location_dest_id.usage', '=', 'customer'),
|
||||
], limit=1, order='sequence')
|
||||
|
||||
po = self.env['purchase.order'].create({
|
||||
"partner_id": subcontractor.id,
|
||||
"picking_type_id": dropship_picking_type.id,
|
||||
"dest_address_id": super_subcontractor.id,
|
||||
"order_line": [(0, 0, {
|
||||
'product_id': product.id,
|
||||
'name': product.name,
|
||||
'product_qty': 1.0,
|
||||
})],
|
||||
})
|
||||
po.button_confirm()
|
||||
|
||||
mo = self.env['mrp.production'].search([('bom_id', '=', bom_product.id)])
|
||||
self.assertEqual(mo.picking_type_id, self.warehouse.subcontracting_type_id)
|
||||
|
||||
delivery = po.picking_ids
|
||||
self.assertEqual(delivery.location_dest_id, super_subcontractor.property_stock_subcontractor)
|
||||
self.assertTrue(delivery.is_dropship)
|
||||
|
||||
delivery.move_line_ids.qty_done = 1.0
|
||||
delivery.button_validate()
|
||||
|
||||
self.assertEqual(po.order_line.qty_received, 1.0)
|
||||
|
||||
@@ -331,6 +331,7 @@ class PurchaseOrderLine(models.Model):
|
||||
elif (
|
||||
move.location_dest_id.usage == "internal"
|
||||
and move.location_id.usage != "supplier"
|
||||
and move.warehouse_id
|
||||
and move.location_dest_id
|
||||
not in self.env["stock.location"].search(
|
||||
[("id", "child_of", move.warehouse_id.view_location_id.id)]
|
||||
|
||||
Reference in New Issue
Block a user