[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:
Adrien Widart
2022-10-05 14:26:14 +02:00
parent 0ba6a5714f
commit b62e6d45d4
3 changed files with 77 additions and 1 deletions
@@ -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)
+1
View File
@@ -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)]