Before this commit, for a subcontracted product with reserved available
tracked components, if at least one component is recorded but not all,
the "Record components" button isn't available anymore.
How to reproduce:
- Create a product and create a subcontracting BOM for this product;
- Add a tracked component for this product;
- Add some qty. for the tracked component in the subcontracting loc.;
- Create a receipt for the subcontracted product (for demand qty. more
than 1) with the subcontractor as partner and confirm it:
=> The "Record components" button should be visible.
- Record a part of the demand qty. then click on "Continue", then on
"Discard":
=> The "Record components" button is now hidden.
It's because the button is hidden if all the MO are done or to close
(they are) and if all the tracked move lines have a SN/LN (they have as
they are reserved).
task-2604728
closes odoo/odoo#73991
X-original-commit: 392bc27c0ef0bc238a7eac98497b293622f0fe1d
Related: odoo/enterprise#19756
Signed-off-by: Arnold Moyaux <amoyaux@users.noreply.github.com>
732 lines
36 KiB
Python
732 lines
36 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from odoo.tests import Form
|
|
from odoo.tests.common import TransactionCase
|
|
from odoo.addons.mrp_subcontracting.tests.common import TestMrpSubcontractingCommon
|
|
|
|
from odoo.tests import tagged
|
|
|
|
|
|
@tagged('post_install', '-at_install')
|
|
class TestSubcontractingBasic(TransactionCase):
|
|
def test_subcontracting_location_1(self):
|
|
""" Checks the creation and presence of the subcontracting location. """
|
|
self.assertTrue(self.env.company.subcontracting_location_id)
|
|
self.assertTrue(self.env.company.subcontracting_location_id.active)
|
|
company2 = self.env['res.company'].create({'name': 'Test Company'})
|
|
self.assertTrue(company2.subcontracting_location_id)
|
|
self.assertTrue(self.env.company.subcontracting_location_id != company2.subcontracting_location_id)
|
|
|
|
|
|
class TestSubcontractingFlows(TestMrpSubcontractingCommon):
|
|
def test_flow_1(self):
|
|
""" Don't tick any route on the components and trigger the creation of the subcontracting
|
|
manufacturing order through a receipt picking. Create a reordering rule in the
|
|
subcontracting locations for a component and run the scheduler to resupply. Checks if the
|
|
resupplying actually works
|
|
"""
|
|
# Check subcontracting picking Type
|
|
self.assertTrue(all(self.env['stock.warehouse'].search([]).with_context(active_test=False).mapped('subcontracting_type_id.use_create_components_lots')))
|
|
# Create a receipt picking from the subcontractor
|
|
picking_form = Form(self.env['stock.picking'])
|
|
picking_form.picking_type_id = self.env.ref('stock.picking_type_in')
|
|
picking_form.partner_id = self.subcontractor_partner1
|
|
with picking_form.move_ids_without_package.new() as move:
|
|
move.product_id = self.finished
|
|
move.product_uom_qty = 1
|
|
picking_receipt = picking_form.save()
|
|
picking_receipt.action_confirm()
|
|
|
|
# Nothing should be tracked
|
|
self.assertTrue(all(m.product_uom_qty == m.reserved_availability for m in picking_receipt.move_lines))
|
|
self.assertEqual(picking_receipt.state, 'assigned')
|
|
self.assertFalse(picking_receipt.display_action_record_components)
|
|
|
|
# Check the created manufacturing order
|
|
mo = self.env['mrp.production'].search([('bom_id', '=', self.bom.id)])
|
|
self.assertEqual(len(mo), 1)
|
|
self.assertEqual(len(mo.picking_ids), 0)
|
|
wh = picking_receipt.picking_type_id.warehouse_id
|
|
self.assertEqual(mo.picking_type_id, wh.subcontracting_type_id)
|
|
self.assertFalse(mo.picking_type_id.active)
|
|
|
|
# Create a RR
|
|
pg1 = self.env['procurement.group'].create({})
|
|
self.env['stock.warehouse.orderpoint'].create({
|
|
'name': 'xxx',
|
|
'product_id': self.comp1.id,
|
|
'product_min_qty': 0,
|
|
'product_max_qty': 0,
|
|
'location_id': self.env.user.company_id.subcontracting_location_id.id,
|
|
'group_id': pg1.id,
|
|
})
|
|
|
|
# Run the scheduler and check the created picking
|
|
self.env['procurement.group'].run_scheduler()
|
|
picking = self.env['stock.picking'].search([('group_id', '=', pg1.id)])
|
|
self.assertEqual(len(picking), 1)
|
|
self.assertEqual(picking.picking_type_id, wh.out_type_id)
|
|
picking_receipt.move_lines.quantity_done = 1
|
|
picking_receipt.button_validate()
|
|
self.assertEqual(mo.state, 'done')
|
|
|
|
# Available quantities should be negative at the subcontracting location for each components
|
|
avail_qty_comp1 = self.env['stock.quant']._get_available_quantity(self.comp1, self.subcontractor_partner1.property_stock_subcontractor, allow_negative=True)
|
|
avail_qty_comp2 = self.env['stock.quant']._get_available_quantity(self.comp2, self.subcontractor_partner1.property_stock_subcontractor, allow_negative=True)
|
|
avail_qty_finished = self.env['stock.quant']._get_available_quantity(self.finished, wh.lot_stock_id)
|
|
self.assertEqual(avail_qty_comp1, -1)
|
|
self.assertEqual(avail_qty_comp2, -1)
|
|
self.assertEqual(avail_qty_finished, 1)
|
|
|
|
# Ensure returns to subcontractor location
|
|
return_form = Form(self.env['stock.return.picking'].with_context(active_id=picking_receipt.id, active_model='stock.picking'))
|
|
return_wizard = return_form.save()
|
|
return_picking_id, pick_type_id = return_wizard._create_returns()
|
|
return_picking = self.env['stock.picking'].browse(return_picking_id)
|
|
self.assertEqual(len(return_picking), 1)
|
|
self.assertEqual(return_picking.move_lines.location_dest_id, self.subcontractor_partner1.property_stock_subcontractor)
|
|
|
|
def test_flow_2(self):
|
|
""" Tick "Resupply Subcontractor on Order" on the components and trigger the creation of
|
|
the subcontracting manufacturing order through a receipt picking. Checks if the resupplying
|
|
actually works. Also set a different subcontracting location on the partner.
|
|
"""
|
|
# Tick "resupply subconractor on order"
|
|
resupply_sub_on_order_route = self.env['stock.location.route'].search([('name', '=', 'Resupply Subcontractor on Order')])
|
|
(self.comp1 + self.comp2).write({'route_ids': [(4, resupply_sub_on_order_route.id, None)]})
|
|
# Create a different subcontract location
|
|
partner_subcontract_location = self.env['stock.location'].create({
|
|
'name': 'Specific partner location',
|
|
'location_id': self.env.ref('stock.stock_location_locations_partner').id,
|
|
'usage': 'internal',
|
|
'company_id': self.env.company.id,
|
|
})
|
|
self.subcontractor_partner1.property_stock_subcontractor = partner_subcontract_location.id
|
|
resupply_rule = resupply_sub_on_order_route.rule_ids.filtered(lambda l:
|
|
l.location_id == self.comp1.property_stock_production and
|
|
l.location_src_id == self.env.company.subcontracting_location_id)
|
|
resupply_rule.copy({'location_src_id': partner_subcontract_location.id})
|
|
resupply_warehouse_rule = self.warehouse.route_ids.rule_ids.filtered(lambda l:
|
|
l.location_id == self.env.company.subcontracting_location_id and
|
|
l.location_src_id == self.warehouse.lot_stock_id)
|
|
resupply_warehouse_rule.copy({'location_id': partner_subcontract_location.id})
|
|
|
|
# Create a receipt picking from the subcontractor
|
|
picking_form = Form(self.env['stock.picking'])
|
|
picking_form.picking_type_id = self.env.ref('stock.picking_type_in')
|
|
picking_form.partner_id = self.subcontractor_partner1
|
|
with picking_form.move_ids_without_package.new() as move:
|
|
move.product_id = self.finished
|
|
move.product_uom_qty = 1
|
|
picking_receipt = picking_form.save()
|
|
picking_receipt.action_confirm()
|
|
|
|
# Nothing should be tracked
|
|
self.assertFalse(picking_receipt.display_action_record_components)
|
|
|
|
# Pickings should directly be created
|
|
mo = self.env['mrp.production'].search([('bom_id', '=', self.bom.id)])
|
|
self.assertEqual(len(mo.picking_ids), 1)
|
|
self.assertEqual(mo.state, 'confirmed')
|
|
self.assertEqual(len(mo.picking_ids.move_lines), 2)
|
|
|
|
picking = mo.picking_ids
|
|
wh = picking.picking_type_id.warehouse_id
|
|
|
|
# The picking should be a delivery order
|
|
self.assertEqual(picking.picking_type_id, wh.out_type_id)
|
|
|
|
self.assertEqual(mo.picking_type_id, wh.subcontracting_type_id)
|
|
self.assertFalse(mo.picking_type_id.active)
|
|
|
|
# No manufacturing order for `self.comp2`
|
|
comp2mo = self.env['mrp.production'].search([('bom_id', '=', self.comp2_bom.id)])
|
|
self.assertEqual(len(comp2mo), 0)
|
|
|
|
picking_receipt.move_lines.quantity_done = 1
|
|
picking_receipt.button_validate()
|
|
self.assertEqual(mo.state, 'done')
|
|
|
|
# Available quantities should be negative at the subcontracting location for each components
|
|
avail_qty_comp1 = self.env['stock.quant']._get_available_quantity(self.comp1, self.subcontractor_partner1.property_stock_subcontractor, allow_negative=True)
|
|
avail_qty_comp2 = self.env['stock.quant']._get_available_quantity(self.comp2, self.subcontractor_partner1.property_stock_subcontractor, allow_negative=True)
|
|
avail_qty_finished = self.env['stock.quant']._get_available_quantity(self.finished, wh.lot_stock_id)
|
|
self.assertEqual(avail_qty_comp1, -1)
|
|
self.assertEqual(avail_qty_comp2, -1)
|
|
self.assertEqual(avail_qty_finished, 1)
|
|
|
|
avail_qty_comp1_in_global_location = self.env['stock.quant']._get_available_quantity(self.comp1, self.env.company.subcontracting_location_id, allow_negative=True)
|
|
avail_qty_comp2_in_global_location = self.env['stock.quant']._get_available_quantity(self.comp2, self.env.company.subcontracting_location_id, allow_negative=True)
|
|
self.assertEqual(avail_qty_comp1_in_global_location, 0.0)
|
|
self.assertEqual(avail_qty_comp2_in_global_location, 0.0)
|
|
|
|
def test_flow_3(self):
|
|
""" Tick "Resupply Subcontractor on Order" and "MTO" on the components and trigger the
|
|
creation of the subcontracting manufacturing order through a receipt picking. Checks if the
|
|
resupplying actually works. One of the component has also "manufacture" set and a BOM
|
|
linked. Checks that an MO is created for this one.
|
|
"""
|
|
# Tick "resupply subconractor on order"
|
|
resupply_sub_on_order_route = self.env['stock.location.route'].search([('name', '=', 'Resupply Subcontractor on Order')])
|
|
(self.comp1 + self.comp2).write({'route_ids': [(4, resupply_sub_on_order_route.id, None)]})
|
|
|
|
# Tick "manufacture" and MTO on self.comp2
|
|
mto_route = self.env.ref('stock.route_warehouse0_mto')
|
|
mto_route.active = True
|
|
manufacture_route = self.env['stock.location.route'].search([('name', '=', 'Manufacture')])
|
|
self.comp2.write({'route_ids': [(4, manufacture_route.id, None)]})
|
|
self.comp2.write({'route_ids': [(4, mto_route.id, None)]})
|
|
|
|
# Create a receipt picking from the subcontractor
|
|
picking_form = Form(self.env['stock.picking'])
|
|
picking_form.picking_type_id = self.env.ref('stock.picking_type_in')
|
|
picking_form.partner_id = self.subcontractor_partner1
|
|
with picking_form.move_ids_without_package.new() as move:
|
|
move.product_id = self.finished
|
|
move.product_uom_qty = 1
|
|
picking_receipt = picking_form.save()
|
|
picking_receipt.action_confirm()
|
|
|
|
# Nothing should be tracked
|
|
self.assertFalse(picking_receipt.display_action_record_components)
|
|
|
|
# Pickings should directly be created
|
|
mo = self.env['mrp.production'].search([('bom_id', '=', self.bom.id)])
|
|
self.assertEqual(mo.state, 'confirmed')
|
|
|
|
picking_delivery = mo.picking_ids
|
|
self.assertEqual(len(picking_delivery), 1)
|
|
self.assertEqual(len(picking_delivery.move_lines), 2)
|
|
self.assertEqual(picking_delivery.origin, picking_receipt.name)
|
|
self.assertEqual(picking_delivery.partner_id, picking_receipt.partner_id)
|
|
|
|
# The picking should be a delivery order
|
|
wh = picking_receipt.picking_type_id.warehouse_id
|
|
self.assertEqual(mo.picking_ids.picking_type_id, wh.out_type_id)
|
|
|
|
self.assertEqual(mo.picking_type_id, wh.subcontracting_type_id)
|
|
self.assertFalse(mo.picking_type_id.active)
|
|
|
|
# As well as a manufacturing order for `self.comp2`
|
|
comp2mo = self.env['mrp.production'].search([('bom_id', '=', self.comp2_bom.id)])
|
|
self.assertEqual(len(comp2mo), 1)
|
|
picking_receipt.move_lines.quantity_done = 1
|
|
picking_receipt.button_validate()
|
|
self.assertEqual(mo.state, 'done')
|
|
|
|
# Available quantities should be negative at the subcontracting location for each components
|
|
avail_qty_comp1 = self.env['stock.quant']._get_available_quantity(self.comp1, self.subcontractor_partner1.property_stock_subcontractor, allow_negative=True)
|
|
avail_qty_comp2 = self.env['stock.quant']._get_available_quantity(self.comp2, self.subcontractor_partner1.property_stock_subcontractor, allow_negative=True)
|
|
avail_qty_finished = self.env['stock.quant']._get_available_quantity(self.finished, wh.lot_stock_id)
|
|
self.assertEqual(avail_qty_comp1, -1)
|
|
self.assertEqual(avail_qty_comp2, -1)
|
|
self.assertEqual(avail_qty_finished, 1)
|
|
|
|
def test_flow_4(self):
|
|
""" Tick "Manufacture" and "MTO" on the components and trigger the
|
|
creation of the subcontracting manufacturing order through a receipt
|
|
picking. Checks that the delivery and MO for its components are
|
|
automatically created.
|
|
"""
|
|
# Tick "manufacture" and MTO on self.comp2
|
|
mto_route = self.env.ref('stock.route_warehouse0_mto')
|
|
mto_route.active = True
|
|
manufacture_route = self.env['stock.location.route'].search([('name', '=', 'Manufacture')])
|
|
self.comp2.write({'route_ids': [(4, manufacture_route.id, None)]})
|
|
self.comp2.write({'route_ids': [(4, mto_route.id, None)]})
|
|
|
|
orderpoint_form = Form(self.env['stock.warehouse.orderpoint'])
|
|
orderpoint_form.product_id = self.comp2
|
|
orderpoint_form.product_min_qty = 0.0
|
|
orderpoint_form.product_max_qty = 10.0
|
|
orderpoint_form.location_id = self.env.company.subcontracting_location_id
|
|
orderpoint = orderpoint_form.save()
|
|
|
|
# Create a receipt picking from the subcontractor
|
|
picking_form = Form(self.env['stock.picking'])
|
|
picking_form.picking_type_id = self.env.ref('stock.picking_type_in')
|
|
picking_form.partner_id = self.subcontractor_partner1
|
|
with picking_form.move_ids_without_package.new() as move:
|
|
move.product_id = self.finished
|
|
move.product_uom_qty = 1
|
|
picking_receipt = picking_form.save()
|
|
picking_receipt.action_confirm()
|
|
|
|
warehouse = picking_receipt.picking_type_id.warehouse_id
|
|
|
|
# Pickings should directly be created
|
|
mo = self.env['mrp.production'].search([('bom_id', '=', self.bom.id)])
|
|
self.assertEqual(mo.state, 'confirmed')
|
|
|
|
picking_delivery = mo.picking_ids
|
|
self.assertFalse(picking_delivery)
|
|
|
|
picking_delivery = self.env['stock.picking'].search([('origin', 'ilike', '%' + picking_receipt.name + '%')])
|
|
self.assertFalse(picking_delivery)
|
|
|
|
move = self.env['stock.move'].search([
|
|
('product_id', '=', self.comp2.id),
|
|
('location_id', '=', warehouse.lot_stock_id.id),
|
|
('location_dest_id', '=', self.env.company.subcontracting_location_id.id)
|
|
])
|
|
self.assertTrue(move)
|
|
picking_delivery = move.picking_id
|
|
self.assertTrue(picking_delivery)
|
|
self.assertEqual(move.product_uom_qty, 11.0)
|
|
|
|
# As well as a manufacturing order for `self.comp2`
|
|
comp2mo = self.env['mrp.production'].search([('bom_id', '=', self.comp2_bom.id)])
|
|
self.assertEqual(len(comp2mo), 1)
|
|
|
|
def test_flow_5(self):
|
|
""" Check that the correct BoM is chosen accordingly to the partner
|
|
"""
|
|
# We create a second partner of type subcontractor
|
|
main_partner_2 = self.env['res.partner'].create({'name': 'main_partner'})
|
|
subcontractor_partner2 = self.env['res.partner'].create({
|
|
'name': 'subcontractor_partner',
|
|
'parent_id': main_partner_2.id,
|
|
'company_id': self.env.ref('base.main_company').id
|
|
})
|
|
|
|
# We create a different BoM for the same product
|
|
comp3 = self.env['product.product'].create({
|
|
'name': 'Component1',
|
|
'type': 'product',
|
|
'categ_id': self.env.ref('product.product_category_all').id,
|
|
})
|
|
|
|
bom_form = Form(self.env['mrp.bom'])
|
|
bom_form.type = 'subcontract'
|
|
bom_form.product_tmpl_id = self.finished.product_tmpl_id
|
|
with bom_form.bom_line_ids.new() as bom_line:
|
|
bom_line.product_id = self.comp1
|
|
bom_line.product_qty = 1
|
|
with bom_form.bom_line_ids.new() as bom_line:
|
|
bom_line.product_id = comp3
|
|
bom_line.product_qty = 1
|
|
bom2 = bom_form.save()
|
|
|
|
# We assign the second BoM to the new partner
|
|
self.bom.write({'subcontractor_ids': [(4, self.subcontractor_partner1.id, None)]})
|
|
bom2.write({'subcontractor_ids': [(4, subcontractor_partner2.id, None)]})
|
|
|
|
# Create a receipt picking from the subcontractor1
|
|
picking_form = Form(self.env['stock.picking'])
|
|
picking_form.picking_type_id = self.env.ref('stock.picking_type_in')
|
|
picking_form.partner_id = self.subcontractor_partner1
|
|
with picking_form.move_ids_without_package.new() as move:
|
|
move.product_id = self.finished
|
|
move.product_uom_qty = 1
|
|
picking_receipt1 = picking_form.save()
|
|
picking_receipt1.action_confirm()
|
|
|
|
# Create a receipt picking from the subcontractor2
|
|
picking_form = Form(self.env['stock.picking'])
|
|
picking_form.picking_type_id = self.env.ref('stock.picking_type_in')
|
|
picking_form.partner_id = subcontractor_partner2
|
|
with picking_form.move_ids_without_package.new() as move:
|
|
move.product_id = self.finished
|
|
move.product_uom_qty = 1
|
|
picking_receipt2 = picking_form.save()
|
|
picking_receipt2.action_confirm()
|
|
|
|
mo_pick1 = picking_receipt1.move_lines.mapped('move_orig_ids.production_id')
|
|
mo_pick2 = picking_receipt2.move_lines.mapped('move_orig_ids.production_id')
|
|
self.assertEqual(len(mo_pick1), 1)
|
|
self.assertEqual(len(mo_pick2), 1)
|
|
self.assertEqual(mo_pick1.bom_id, self.bom)
|
|
self.assertEqual(mo_pick2.bom_id, bom2)
|
|
|
|
def test_flow_6(self):
|
|
""" Extra quantity on the move.
|
|
"""
|
|
# We create a second partner of type subcontractor
|
|
main_partner_2 = self.env['res.partner'].create({'name': 'main_partner'})
|
|
subcontractor_partner2 = self.env['res.partner'].create({
|
|
'name': 'subcontractor_partner',
|
|
'parent_id': main_partner_2.id,
|
|
'company_id': self.env.ref('base.main_company').id,
|
|
})
|
|
self.env.cache.invalidate()
|
|
|
|
# We create a different BoM for the same product
|
|
comp3 = self.env['product.product'].create({
|
|
'name': 'Component3',
|
|
'type': 'product',
|
|
'categ_id': self.env.ref('product.product_category_all').id,
|
|
})
|
|
|
|
bom_form = Form(self.env['mrp.bom'])
|
|
bom_form.type = 'subcontract'
|
|
bom_form.product_tmpl_id = self.finished.product_tmpl_id
|
|
with bom_form.bom_line_ids.new() as bom_line:
|
|
bom_line.product_id = self.comp1
|
|
bom_line.product_qty = 1
|
|
with bom_form.bom_line_ids.new() as bom_line:
|
|
bom_line.product_id = comp3
|
|
bom_line.product_qty = 2
|
|
bom2 = bom_form.save()
|
|
|
|
# We assign the second BoM to the new partner
|
|
self.bom.write({'subcontractor_ids': [(4, self.subcontractor_partner1.id, None)]})
|
|
bom2.write({'subcontractor_ids': [(4, subcontractor_partner2.id, None)]})
|
|
|
|
# Create a receipt picking from the subcontractor1
|
|
picking_form = Form(self.env['stock.picking'])
|
|
picking_form.picking_type_id = self.env.ref('stock.picking_type_in')
|
|
picking_form.partner_id = subcontractor_partner2
|
|
with picking_form.move_ids_without_package.new() as move:
|
|
move.product_id = self.finished
|
|
move.product_uom_qty = 1
|
|
picking_receipt = picking_form.save()
|
|
picking_receipt.action_confirm()
|
|
|
|
picking_receipt.move_lines.quantity_done = 3.0
|
|
picking_receipt._action_done()
|
|
mo = picking_receipt._get_subcontracted_productions()
|
|
move_comp1 = mo.move_raw_ids.filtered(lambda m: m.product_id == self.comp1)
|
|
move_comp3 = mo.move_raw_ids.filtered(lambda m: m.product_id == comp3)
|
|
self.assertEqual(sum(move_comp1.mapped('product_uom_qty')), 3.0)
|
|
self.assertEqual(sum(move_comp3.mapped('product_uom_qty')), 6.0)
|
|
self.assertEqual(sum(move_comp1.mapped('quantity_done')), 3.0)
|
|
self.assertEqual(sum(move_comp3.mapped('quantity_done')), 6.0)
|
|
move_finished = mo.move_finished_ids
|
|
self.assertEqual(sum(move_finished.mapped('product_uom_qty')), 3.0)
|
|
self.assertEqual(sum(move_finished.mapped('quantity_done')), 3.0)
|
|
|
|
def test_flow_8(self):
|
|
resupply_sub_on_order_route = self.env['stock.location.route'].search([('name', '=', 'Resupply Subcontractor on Order')])
|
|
(self.comp1 + self.comp2).write({'route_ids': [(4, resupply_sub_on_order_route.id, None)]})
|
|
|
|
# Create a receipt picking from the subcontractor
|
|
picking_form = Form(self.env['stock.picking'])
|
|
picking_form.picking_type_id = self.env.ref('stock.picking_type_in')
|
|
picking_form.partner_id = self.subcontractor_partner1
|
|
with picking_form.move_ids_without_package.new() as move:
|
|
move.product_id = self.finished
|
|
move.product_uom_qty = 5
|
|
picking_receipt = picking_form.save()
|
|
picking_receipt.action_confirm()
|
|
|
|
picking_receipt.move_lines.quantity_done = 3
|
|
backorder_wiz = picking_receipt.button_validate()
|
|
backorder_wiz = Form(self.env[backorder_wiz['res_model']].with_context(backorder_wiz['context'])).save()
|
|
backorder_wiz.process()
|
|
|
|
backorder = self.env['stock.picking'].search([('backorder_id', '=', picking_receipt.id)])
|
|
self.assertTrue(backorder)
|
|
self.assertEqual(backorder.move_lines.product_uom_qty, 2)
|
|
mo_done = backorder.move_lines.move_orig_ids.production_id.filtered(lambda p: p.state == 'done')
|
|
backorder_mo = backorder.move_lines.move_orig_ids.production_id.filtered(lambda p: p.state != 'done')
|
|
self.assertTrue(mo_done)
|
|
self.assertEqual(mo_done.qty_produced, 3)
|
|
self.assertEqual(mo_done.product_uom_qty, 3)
|
|
self.assertTrue(backorder_mo)
|
|
self.assertEqual(backorder_mo.product_uom_qty, 2)
|
|
self.assertEqual(backorder_mo.qty_produced, 0)
|
|
backorder.move_lines.quantity_done = 2
|
|
backorder._action_done()
|
|
self.assertTrue(picking_receipt.move_lines.move_orig_ids.production_id.state == 'done')
|
|
|
|
def test_flow_9(self):
|
|
"""Ensure that cancel the subcontract moves will also delete the
|
|
components need for the subcontractor.
|
|
"""
|
|
resupply_sub_on_order_route = self.env['stock.location.route'].search([
|
|
('name', '=', 'Resupply Subcontractor on Order')
|
|
])
|
|
(self.comp1 + self.comp2).write({
|
|
'route_ids': [(4, resupply_sub_on_order_route.id)]
|
|
})
|
|
|
|
picking_form = Form(self.env['stock.picking'])
|
|
picking_form.picking_type_id = self.env.ref('stock.picking_type_in')
|
|
picking_form.partner_id = self.subcontractor_partner1
|
|
with picking_form.move_ids_without_package.new() as move:
|
|
move.product_id = self.finished
|
|
move.product_uom_qty = 5
|
|
picking_receipt = picking_form.save()
|
|
picking_receipt.action_confirm()
|
|
|
|
picking_delivery = self.env['stock.move'].search([
|
|
('product_id', 'in', (self.comp1 | self.comp2).ids)
|
|
]).picking_id
|
|
self.assertTrue(picking_delivery)
|
|
self.assertEqual(picking_delivery.state, 'confirmed')
|
|
self.assertEqual(self.comp1.virtual_available, -5)
|
|
self.assertEqual(self.comp2.virtual_available, -5)
|
|
# action_cancel is not call on the picking in order
|
|
# to test behavior from other source than picking (e.g. puchase).
|
|
picking_receipt.move_lines._action_cancel()
|
|
self.assertEqual(picking_delivery.state, 'cancel')
|
|
self.assertEqual(self.comp1.virtual_available, 0.0)
|
|
self.assertEqual(self.comp1.virtual_available, 0.0)
|
|
|
|
def test_flow_10(self):
|
|
"""Receipts from a children contact of a subcontractor are properly
|
|
handled.
|
|
"""
|
|
# Create a children contact
|
|
subcontractor_contact = self.env['res.partner'].create({
|
|
'name': 'Test children subcontractor contact',
|
|
'parent_id': self.subcontractor_partner1.id,
|
|
})
|
|
# Create a receipt picking from the subcontractor
|
|
picking_form = Form(self.env['stock.picking'])
|
|
picking_form.picking_type_id = self.env.ref('stock.picking_type_in')
|
|
picking_form.partner_id = subcontractor_contact
|
|
with picking_form.move_ids_without_package.new() as move:
|
|
move.product_id = self.finished
|
|
move.product_uom_qty = 1
|
|
picking_receipt = picking_form.save()
|
|
picking_receipt.action_confirm()
|
|
# Check that a manufacturing order is created
|
|
mo = self.env['mrp.production'].search([('bom_id', '=', self.bom.id)])
|
|
self.assertEqual(len(mo), 1)
|
|
|
|
|
|
class TestSubcontractingTracking(TransactionCase):
|
|
def setUp(self):
|
|
super(TestSubcontractingTracking, self).setUp()
|
|
# 1: Create a subcontracting partner
|
|
main_company_1 = self.env['res.partner'].create({'name': 'main_partner'})
|
|
self.subcontractor_partner1 = self.env['res.partner'].create({
|
|
'name': 'Subcontractor 1',
|
|
'parent_id': main_company_1.id,
|
|
'company_id': self.env.ref('base.main_company').id
|
|
})
|
|
|
|
# 2. Create a BOM of subcontracting type
|
|
# 2.1. Comp1 has tracking by lot
|
|
self.comp1_sn = self.env['product.product'].create({
|
|
'name': 'Component1',
|
|
'type': 'product',
|
|
'categ_id': self.env.ref('product.product_category_all').id,
|
|
'tracking': 'serial'
|
|
})
|
|
self.comp2 = self.env['product.product'].create({
|
|
'name': 'Component2',
|
|
'type': 'product',
|
|
'categ_id': self.env.ref('product.product_category_all').id,
|
|
})
|
|
|
|
# 2.2. Finished prodcut has tracking by serial number
|
|
self.finished_product = self.env['product.product'].create({
|
|
'name': 'finished',
|
|
'type': 'product',
|
|
'categ_id': self.env.ref('product.product_category_all').id,
|
|
'tracking': 'lot'
|
|
})
|
|
bom_form = Form(self.env['mrp.bom'])
|
|
bom_form.type = 'subcontract'
|
|
bom_form.subcontractor_ids.add(self.subcontractor_partner1)
|
|
bom_form.product_tmpl_id = self.finished_product.product_tmpl_id
|
|
with bom_form.bom_line_ids.new() as bom_line:
|
|
bom_line.product_id = self.comp1_sn
|
|
bom_line.product_qty = 1
|
|
with bom_form.bom_line_ids.new() as bom_line:
|
|
bom_line.product_id = self.comp2
|
|
bom_line.product_qty = 1
|
|
self.bom_tracked = bom_form.save()
|
|
|
|
def test_flow_tracked_1(self):
|
|
""" This test mimics test_flow_1 but with a BoM that has tracking included in it.
|
|
"""
|
|
# Create a receipt picking from the subcontractor
|
|
picking_form = Form(self.env['stock.picking'])
|
|
picking_form.picking_type_id = self.env.ref('stock.picking_type_in')
|
|
picking_form.partner_id = self.subcontractor_partner1
|
|
with picking_form.move_ids_without_package.new() as move:
|
|
move.product_id = self.finished_product
|
|
move.product_uom_qty = 1
|
|
picking_receipt = picking_form.save()
|
|
picking_receipt.action_confirm()
|
|
|
|
# We should be able to call the 'record_components' button
|
|
self.assertTrue(picking_receipt.display_action_record_components)
|
|
|
|
# Check the created manufacturing order
|
|
mo = self.env['mrp.production'].search([('bom_id', '=', self.bom_tracked.id)])
|
|
self.assertEqual(len(mo), 1)
|
|
self.assertEqual(len(mo.picking_ids), 0)
|
|
wh = picking_receipt.picking_type_id.warehouse_id
|
|
self.assertEqual(mo.picking_type_id, wh.subcontracting_type_id)
|
|
self.assertFalse(mo.picking_type_id.active)
|
|
|
|
# Create a RR
|
|
pg1 = self.env['procurement.group'].create({})
|
|
self.env['stock.warehouse.orderpoint'].create({
|
|
'name': 'xxx',
|
|
'product_id': self.comp1_sn.id,
|
|
'product_min_qty': 0,
|
|
'product_max_qty': 0,
|
|
'location_id': self.env.user.company_id.subcontracting_location_id.id,
|
|
'group_id': pg1.id,
|
|
})
|
|
|
|
# Run the scheduler and check the created picking
|
|
self.env['procurement.group'].run_scheduler()
|
|
picking = self.env['stock.picking'].search([('group_id', '=', pg1.id)])
|
|
self.assertEqual(len(picking), 1)
|
|
self.assertEqual(picking.picking_type_id, wh.out_type_id)
|
|
|
|
lot_id = self.env['stock.production.lot'].create({
|
|
'name': 'lot1',
|
|
'product_id': self.finished_product.id,
|
|
'company_id': self.env.company.id,
|
|
})
|
|
serial_id = self.env['stock.production.lot'].create({
|
|
'name': 'lot1',
|
|
'product_id': self.comp1_sn.id,
|
|
'company_id': self.env.company.id,
|
|
})
|
|
|
|
action = picking_receipt.action_record_components()
|
|
mo = self.env['mrp.production'].browse(action['res_id'])
|
|
mo_form = Form(mo.with_context(**action['context']), view=action['view_id'])
|
|
mo_form.qty_producing = 1
|
|
mo_form.lot_producing_id = lot_id
|
|
with mo_form.move_line_raw_ids.edit(0) as ml:
|
|
ml.lot_id = serial_id
|
|
mo = mo_form.save()
|
|
mo.subcontracting_record_component()
|
|
|
|
# We should not be able to call the 'record_components' button
|
|
self.assertFalse(picking_receipt.display_action_record_components)
|
|
|
|
picking_receipt.button_validate()
|
|
self.assertEqual(mo.state, 'done')
|
|
|
|
# Available quantities should be negative at the subcontracting location for each components
|
|
avail_qty_comp1 = self.env['stock.quant']._get_available_quantity(self.comp1_sn, self.subcontractor_partner1.property_stock_subcontractor, allow_negative=True)
|
|
avail_qty_comp2 = self.env['stock.quant']._get_available_quantity(self.comp2, self.subcontractor_partner1.property_stock_subcontractor, allow_negative=True)
|
|
avail_qty_finished = self.env['stock.quant']._get_available_quantity(self.finished_product, wh.lot_stock_id)
|
|
self.assertEqual(avail_qty_comp1, -1)
|
|
self.assertEqual(avail_qty_comp2, -1)
|
|
self.assertEqual(avail_qty_finished, 1)
|
|
|
|
def test_flow_tracked_only_finished(self):
|
|
""" Test when only the finished product is tracked """
|
|
self.finished_product.tracking = "serial"
|
|
self.comp1_sn.tracking = "none"
|
|
nb_finished_product = 3
|
|
# Create a receipt picking from the subcontractor
|
|
picking_form = Form(self.env['stock.picking'])
|
|
picking_form.picking_type_id = self.env.ref('stock.picking_type_in')
|
|
picking_form.partner_id = self.subcontractor_partner1
|
|
with picking_form.move_ids_without_package.new() as move:
|
|
move.product_id = self.finished_product
|
|
move.product_uom_qty = nb_finished_product
|
|
picking_receipt = picking_form.save()
|
|
picking_receipt.action_confirm()
|
|
|
|
# We shouldn't be able to call the 'record_components' button
|
|
self.assertFalse(picking_receipt.display_action_record_components)
|
|
|
|
wh = picking_receipt.picking_type_id.warehouse_id
|
|
lot_names_finished = [f"subtracked_{i}" for i in range(nb_finished_product)]
|
|
|
|
move_details = Form(picking_receipt.move_lines, view='stock.view_stock_move_nosuggest_operations')
|
|
for lot_name in lot_names_finished:
|
|
with move_details.move_line_nosuggest_ids.new() as ml:
|
|
ml.qty_done = 1
|
|
ml.lot_name = lot_name
|
|
move_details.save()
|
|
|
|
picking_receipt.button_validate()
|
|
# Check the created manufacturing order
|
|
# Should have one mo by serial number
|
|
mos = picking_receipt.move_lines.move_orig_ids.production_id
|
|
self.assertEqual(len(mos), nb_finished_product)
|
|
self.assertEqual(mos.mapped("state"), ["done"] * nb_finished_product)
|
|
self.assertEqual(mos.picking_type_id, wh.subcontracting_type_id)
|
|
self.assertFalse(mos.picking_type_id.active)
|
|
self.assertEqual(set(mos.lot_producing_id.mapped("name")), set(lot_names_finished))
|
|
|
|
# Available quantities should be negative at the subcontracting location for each components
|
|
avail_qty_comp1 = self.env['stock.quant']._get_available_quantity(self.comp1_sn, self.subcontractor_partner1.property_stock_subcontractor, allow_negative=True)
|
|
avail_qty_comp2 = self.env['stock.quant']._get_available_quantity(self.comp2, self.subcontractor_partner1.property_stock_subcontractor, allow_negative=True)
|
|
avail_qty_finished = self.env['stock.quant']._get_available_quantity(self.finished_product, wh.lot_stock_id)
|
|
self.assertEqual(avail_qty_comp1, -nb_finished_product)
|
|
self.assertEqual(avail_qty_comp2, -nb_finished_product)
|
|
self.assertEqual(avail_qty_finished, nb_finished_product)
|
|
|
|
def test_flow_tracked_backorder(self):
|
|
""" This test uses tracked (serial and lot) component and tracked (serial) finished product """
|
|
todo_nb = 4
|
|
self.comp2.tracking = 'lot'
|
|
self.finished_product.tracking = 'serial'
|
|
|
|
# Create a receipt picking from the subcontractor
|
|
picking_form = Form(self.env['stock.picking'])
|
|
picking_form.picking_type_id = self.env.ref('stock.picking_type_in')
|
|
picking_form.partner_id = self.subcontractor_partner1
|
|
with picking_form.move_ids_without_package.new() as move:
|
|
move.product_id = self.finished_product
|
|
move.product_uom_qty = todo_nb
|
|
picking_receipt = picking_form.save()
|
|
picking_receipt.action_confirm()
|
|
|
|
# We should be able to call the 'record_components' button
|
|
self.assertTrue(picking_receipt.display_action_record_components)
|
|
|
|
# Check the created manufacturing order
|
|
mo = self.env['mrp.production'].search([('bom_id', '=', self.bom_tracked.id)])
|
|
self.assertEqual(len(mo), 1)
|
|
self.assertEqual(len(mo.picking_ids), 0)
|
|
wh = picking_receipt.picking_type_id.warehouse_id
|
|
self.assertEqual(mo.picking_type_id, wh.subcontracting_type_id)
|
|
self.assertFalse(mo.picking_type_id.active)
|
|
|
|
lot_comp2 = self.env['stock.production.lot'].create({
|
|
'name': 'lot_comp2',
|
|
'product_id': self.comp2.id,
|
|
'company_id': self.env.company.id,
|
|
})
|
|
serials_finished = []
|
|
serials_comp1 = []
|
|
for i in range(todo_nb):
|
|
serials_finished.append(self.env['stock.production.lot'].create({
|
|
'name': 'serial_fin_%s' % i,
|
|
'product_id': self.finished_product.id,
|
|
'company_id': self.env.company.id,
|
|
}))
|
|
serials_comp1.append(self.env['stock.production.lot'].create({
|
|
'name': 'serials_comp1_%s' % i,
|
|
'product_id': self.comp1_sn.id,
|
|
'company_id': self.env.company.id,
|
|
}))
|
|
|
|
for i in range(todo_nb):
|
|
action = picking_receipt.action_record_components()
|
|
mo = self.env['mrp.production'].browse(action['res_id'])
|
|
mo_form = Form(mo.with_context(**action['context']), view=action['view_id'])
|
|
mo_form.lot_producing_id = serials_finished[i]
|
|
with mo_form.move_line_raw_ids.edit(0) as ml:
|
|
self.assertEqual(ml.product_id, self.comp1_sn)
|
|
ml.lot_id = serials_comp1[i]
|
|
with mo_form.move_line_raw_ids.edit(1) as ml:
|
|
self.assertEqual(ml.product_id, self.comp2)
|
|
ml.lot_id = lot_comp2
|
|
mo = mo_form.save()
|
|
mo.subcontracting_record_component()
|
|
|
|
# We should not be able to call the 'record_components' button
|
|
self.assertFalse(picking_receipt.display_action_record_components)
|
|
|
|
picking_receipt.button_validate()
|
|
self.assertEqual(mo.state, 'done')
|
|
self.assertEqual(mo.procurement_group_id.mrp_production_ids.mapped("state"), ['done'] * todo_nb)
|
|
self.assertEqual(len(mo.procurement_group_id.mrp_production_ids), todo_nb)
|
|
self.assertEqual(mo.procurement_group_id.mrp_production_ids.mapped("qty_produced"), [1] * todo_nb)
|
|
|
|
# Available quantities should be negative at the subcontracting location for each components
|
|
avail_qty_comp1 = self.env['stock.quant']._get_available_quantity(self.comp1_sn, self.subcontractor_partner1.property_stock_subcontractor, allow_negative=True)
|
|
avail_qty_comp2 = self.env['stock.quant']._get_available_quantity(self.comp2, self.subcontractor_partner1.property_stock_subcontractor, allow_negative=True)
|
|
avail_qty_finished = self.env['stock.quant']._get_available_quantity(self.finished_product, wh.lot_stock_id)
|
|
self.assertEqual(avail_qty_comp1, -todo_nb)
|
|
self.assertEqual(avail_qty_comp2, -todo_nb)
|
|
self.assertEqual(avail_qty_finished, todo_nb)
|