Files
odoo_source/addons/mrp_subcontracting/tests/test_subcontracting.py
T
Simon Lejeune d91db5bde6 [ADD] mrp_subcontracting
In a few words, it allows to send components to a subcontractor partner
and receive a finished product. The production is managed through classical
manufacturing orders where the raw and finished location is the
subcontracting location. When creating a receipt picking for a
subcontractor partner, the location_id of the moves will be set to the
subcontracting locations by using the property_stock_customer and
property_stock_supplier of the partner. An override of _action_confirm
will create the manufacturing order.

When a subcontracted bom contains tracking, the produce wizard is
available from the picking to record which lots were produced/consumed.

New type on the BoM: subcontract
New type on res.partner: subcontactor
New location per company (we still own the components stored at the
subcontractor so they're still valued)
New routes created to resupply the subcontracting location on order or
not (through a reordering rule).
Override of res.partner to set the property_stock_customer/supplier on
subcontactor partners.

Proper tests were added too.

Joint work with: Arnaud Baes <arb@odoo.com>

task 1831382
2019-04-03 13:52:22 +00:00

526 lines
25 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.tests import tagged
@tagged('post_install', '-at_install')
class TestSubcontractingBasic(TransactionCase):
def test_partner_1(self):
""" Checks the `display_name` of subcontracted partners. """
main_partner = self.env['res.partner'].create({'name': 'main_partner'})
# When a name is set to the subcontracted partner.
subcontractor_partner1 = self.env['res.partner'].create({
'name': 'subcontractor_partner',
'type': 'subcontractor',
'parent_id': main_partner.id,
})
self.assertEqual(subcontractor_partner1.display_name, "main_partner, subcontractor_partner, Subcontractor")
# When a name is not set to the subcontracted partner.
subcontractor_partner1 = self.env['res.partner'].create({
'name': '',
'type': 'subcontractor',
'parent_id': main_partner.id,
})
self.assertEqual(subcontractor_partner1.display_name, "main_partner, Subcontractor")
def test_partner_2(self):
""" Open an existing partner and add a contact of type subcontractor (simple form view).
Checks the customer and supplier locations are set to the subcontracting one onces saved.
"""
that_company = self.env['res.partner'].create({
'is_company': True,
'name': "That Company",
})
partner_form = Form(that_company)
with partner_form.child_ids.new() as partner:
partner.name = 'That Guy'
partner.type = 'subcontractor'
partner_form.save()
subcontractor = that_company.child_ids
self.assertEquals(subcontractor.property_stock_supplier, self.env.user.company_id.subcontracting_location_id)
self.assertEquals(subcontractor.property_stock_customer, self.env.user.company_id.subcontracting_location_id)
def test_partner_3(self):
""" Create a new partner, select a parent company, and choose type subcontractor (debug
active). Checks the onchange correctly set the customer and supplier locations to the
subcontracting ones. Change them to another one, save, checks they are rightly applied.
"""
# FIXME sle: this field should not be writeable without the debug mode but it is. ask xmo
group_no_one = self.env.ref('base.group_no_one')
self.env.user.write({'groups_id': [(3, group_no_one.id)]})
partner_form = Form(self.env['res.partner'])
partner_form.name = 'subcontractor partner'
self.assertNotEqual(partner_form.property_stock_supplier, self.env.user.company_id.subcontracting_location_id)
self.assertNotEqual(partner_form.property_stock_customer, self.env.user.company_id.subcontracting_location_id)
partner_form.type = 'subcontractor'
self.assertEqual(partner_form.property_stock_supplier, self.env.user.company_id.subcontracting_location_id)
self.assertEqual(partner_form.property_stock_customer, self.env.user.company_id.subcontracting_location_id)
subcontracting_location2 = self.env.user.company_id.subcontracting_location_id.copy()
partner_form.property_stock_supplier = subcontracting_location2
partner_form.property_stock_customer = subcontracting_location2
partner = partner_form.save()
self.assertEqual(partner.property_stock_supplier, subcontracting_location2)
self.assertEqual(partner.property_stock_customer, subcontracting_location2)
def test_partner_4(self):
""" Create a new partner, select a parent company, and choose type subcontractor (debug
not active). Checks the customer and supplier locations are set to the subcontracting one
onces saved.
"""
that_company = self.env['res.partner'].create({
'is_company': True,
'name': "That Company",
})
partner_form = Form(self.env['res.partner'])
partner_form.name = 'That Guy'
partner_form.parent_id = that_company
partner_form.type = 'subcontractor'
subcontractor = partner_form.save()
self.assertEquals(subcontractor.property_stock_supplier, self.env.user.company_id.subcontracting_location_id)
self.assertEquals(subcontractor.property_stock_customer, self.env.user.company_id.subcontracting_location_id)
def test_partner_5(self):
""" Create a new partner with a child partner of type contact,
then write the type 'subcontractor' on it
"""
that_company = self.env['res.partner'].create({
'is_company': True,
'name': "That Company",
})
partner_form = Form(that_company)
with partner_form.child_ids.new() as partner:
partner.name = 'That Guy'
partner.type = 'contact'
partner_form.save()
# We write a new type on the created child partner
subcontractor = that_company.child_ids
self.assertEquals(len(that_company.child_ids), 1)
subcontractor.type = 'subcontractor'
self.assertEquals(subcontractor.property_stock_supplier, self.env.user.company_id.subcontracting_location_id)
self.assertEquals(subcontractor.property_stock_customer, self.env.user.company_id.subcontracting_location_id)
def test_subcontracting_location_1(self):
""" Checks the creation and presence of the subcontracting location. """
self.assertTrue(self.env.user.company_id.subcontracting_location_id)
self.assertTrue(self.env.user.company_id.subcontracting_location_id.active)
company2 = self.env['res.company'].create({'name': 'Test Company'})
self.assertTrue(company2.subcontracting_location_id)
class TestSubcontractingFlows(TransactionCase):
def setUp(self):
super(TestSubcontractingFlows, self).setUp()
# 1: Create a subcontracting partner
main_partner = self.env['res.partner'].create({'name': 'main_partner'})
self.subcontractor_partner1 = self.env['res.partner'].create({
'name': 'subcontractor_partner',
'type': 'subcontractor',
'parent_id': main_partner.id,
})
# 2. Create a BOM of subcontracting type
self.comp1 = self.env['product.product'].create({
'name': 'Component1',
'type': 'product',
'categ_id': self.env.ref('product.product_category_all').id,
})
self.comp2 = self.env['product.product'].create({
'name': 'Component2',
'type': 'product',
'categ_id': self.env.ref('product.product_category_all').id,
})
self.finished = self.env['product.product'].create({
'name': 'finished',
'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 = self.comp2
bom_line.product_qty = 1
self.bom = bom_form.save()
# Create a BoM for self.comp2
self.comp2comp = self.env['product.product'].create({
'name': 'component for Component2',
'type': 'product',
'categ_id': self.env.ref('product.product_category_all').id,
})
bom_form = Form(self.env['mrp.bom'])
bom_form.product_tmpl_id = self.comp2.product_tmpl_id
with bom_form.bom_line_ids.new() as bom_line:
bom_line.product_id = self.comp2comp
bom_line.product_qty = 1
self.comp2_bom = bom_form.save()
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
"""
# 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)
self.assertFalse(picking_receipt.display_view_subcontracted_move_lines)
# 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.assertEquals(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.assertEquals(picking.picking_type_id, wh.out_type_id)
picking_receipt.move_lines.quantity_done = 1
picking_receipt.button_validate()
self.assertEquals(mo.state, 'done')
# Now that the picking is done, the details stat button should be visible
self.assertTrue(picking_receipt.display_view_subcontracted_move_lines)
# 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_supplier, allow_negative=True)
avail_qty_comp2 = self.env['stock.quant']._get_available_quantity(self.comp2, self.subcontractor_partner1.property_stock_supplier, allow_negative=True)
avail_qty_finished = self.env['stock.quant']._get_available_quantity(self.finished, wh.lot_stock_id)
self.assertEquals(avail_qty_comp1, -1)
self.assertEquals(avail_qty_comp2, -1)
self.assertEquals(avail_qty_finished, 1)
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.
"""
# 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 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)
self.assertFalse(picking_receipt.display_view_subcontracted_move_lines)
# Pickings should directly be created
mo = self.env['mrp.production'].search([('bom_id', '=', self.bom.id)])
self.assertEqual(len(mo.picking_ids), 1)
self.assertEquals(mo.state, 'to_close')
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.assertEquals(picking.picking_type_id, wh.out_type_id)
self.assertEquals(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.assertEquals(mo.state, 'done')
# Now that the picking is done, the details stat button should be visible
self.assertTrue(picking_receipt.display_view_subcontracted_move_lines)
# 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_supplier, allow_negative=True)
avail_qty_comp2 = self.env['stock.quant']._get_available_quantity(self.comp2, self.subcontractor_partner1.property_stock_supplier, allow_negative=True)
avail_qty_finished = self.env['stock.quant']._get_available_quantity(self.finished, wh.lot_stock_id)
self.assertEquals(avail_qty_comp1, -1)
self.assertEquals(avail_qty_comp2, -1)
self.assertEquals(avail_qty_finished, 1)
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['stock.location.route'].search([('name', '=', 'Replenish on Order (MTO)')])
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)
self.assertFalse(picking_receipt.display_view_subcontracted_move_lines)
# Pickings should directly be created
mo = self.env['mrp.production'].search([('bom_id', '=', self.bom.id)])
self.assertEqual(len(mo.picking_ids), 1)
self.assertEquals(mo.state, 'to_close')
self.assertEqual(len(mo.picking_ids.move_lines), 2)
# The picking should be a delivery order
wh = picking_receipt.picking_type_id.warehouse_id
self.assertEquals(mo.picking_ids.picking_type_id, wh.out_type_id)
self.assertEquals(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.assertEquals(mo.state, 'done')
# Now that the picking is done, the details stat button should be visible
self.assertTrue(picking_receipt.display_view_subcontracted_move_lines)
# 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_supplier, allow_negative=True)
avail_qty_comp2 = self.env['stock.quant']._get_available_quantity(self.comp2, self.subcontractor_partner1.property_stock_supplier, allow_negative=True)
avail_qty_finished = self.env['stock.quant']._get_available_quantity(self.finished, wh.lot_stock_id)
self.assertEquals(avail_qty_comp1, -1)
self.assertEquals(avail_qty_comp2, -1)
self.assertEquals(avail_qty_finished, 1)
def test_flow_4(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',
'type': 'subcontractor',
'parent_id': main_partner_2.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.assertEquals(len(mo_pick1), 1)
self.assertEquals(len(mo_pick2), 1)
self.assertEquals(mo_pick1.bom_id, self.bom)
self.assertEquals(mo_pick2.bom_id, bom2)
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',
'type': 'subcontractor',
'parent_id': main_company_1.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_lot = 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.product_tmpl_id = self.finished_lot.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_lot
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)
self.assertFalse(picking_receipt.display_view_subcontracted_move_lines)
# Check the created manufacturing order
mo = self.env['mrp.production'].search([('bom_id', '=', self.bom_tracked.id)])
self.assertEqual(len(mo), 1)
self.assertEquals(mo.state, 'confirmed')
self.assertEqual(len(mo.picking_ids), 0)
wh = picking_receipt.picking_type_id.warehouse_id
self.assertEquals(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.assertEquals(picking.picking_type_id, wh.out_type_id)
lot_id = self.env['stock.production.lot'].create({
'name': 'lot1',
'product_id': self.finished_lot.id
})
serial_id = self.env['stock.production.lot'].create({
'name': 'lot1',
'product_id': self.comp1_sn.id
})
produce_form = Form(self.env['mrp.product.produce'].with_context({
'active_id': mo.id,
'active_ids': [mo.id],
}))
produce_form.final_lot_id = lot_id
produce_form.workorder_line_ids._records[0]['lot_id'] = serial_id.id
wiz_produce = produce_form.save()
wiz_produce.do_produce()
# We should not be able to call the 'record_components' button
self.assertFalse(picking_receipt.display_action_record_components)
# We should see the move lines stat button
self.assertTrue(picking_receipt.display_view_subcontracted_move_lines)
picking_receipt.move_lines.quantity_done = 1
picking_receipt.move_lines.move_line_ids.lot_name = 'lot00001test'
picking_receipt.button_validate()
self.assertEquals(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_supplier, allow_negative=True)
avail_qty_comp2 = self.env['stock.quant']._get_available_quantity(self.comp2, self.subcontractor_partner1.property_stock_supplier, allow_negative=True)
avail_qty_finished = self.env['stock.quant']._get_available_quantity(self.finished_lot, wh.lot_stock_id)
self.assertEquals(avail_qty_comp1, -1)
self.assertEquals(avail_qty_comp2, -1)
self.assertEquals(avail_qty_finished, 1)