[REF] mrp_subcontracting: remove partner type

Replace the type subcontractor on the partner.
Instead replace it by a property for a subcontracting location.
The purpose is to have a simplier configuration. The user would
just need to add the partner on the BoM in order to start a
subcontracting process.
This commit is contained in:
Arnold Moyaux
2019-08-19 15:03:59 +00:00
parent 319ae31860
commit 33cf89a6c6
11 changed files with 77 additions and 211 deletions
+2 -1
View File
@@ -8,12 +8,13 @@
'description': "",
'website': 'https://www.odoo.com/page/manufacturing',
'category': 'Manufacturing/Manufacturing',
'depends': ['mrp', 'contacts'],
'depends': ['mrp'],
'data': [
'data/mrp_subcontracting_data.xml',
'views/stock_warehouse_views.xml',
'views/stock_move_views.xml',
'views/stock_picking_views.xml',
'views/res_partner_views.xml',
'views/mrp_bom_views.xml',
'views/res_partner_views.xml',
],
+1 -2
View File
@@ -8,7 +8,7 @@ class MrpBom(models.Model):
_inherit = 'mrp.bom'
type = fields.Selection(selection_add=[('subcontract', 'Subcontracting')])
subcontractor_ids = fields.One2many('res.partner', 'bom_id', domain=[('type', '=', 'subcontractor')], string='Subcontractors')
subcontractor_ids = fields.Many2many('res.partner', 'mrp_bom_subcontractor', string='Subcontractors')
def _bom_subcontract_find(self, product_tmpl=None, product=None, picking_type=None, company_id=False, bom_type='subcontract', subcontractor=False):
domain = self._bom_find_domain(product_tmpl=product_tmpl, product=product, picking_type=picking_type, company_id=company_id, bom_type=bom_type)
@@ -17,4 +17,3 @@ class MrpBom(models.Model):
else:
domain += [('subcontractor_ids', '=', False)]
return self.search(domain, order='subcontractor_ids, sequence, product_id', limit=1)
@@ -9,24 +9,33 @@ class ResCompany(models.Model):
subcontracting_location_id = fields.Many2one('stock.location')
@api.model
def create_missing_subcontracting_location(self):
company_without_subcontracting_loc = self.env['res.company'].search(
[('subcontracting_location_id', '=', False)])
company_without_subcontracting_loc._create_subcontracting_location()
def _create_per_company_locations(self):
super(ResCompany, self)._create_per_company_locations()
self._create_subcontracting_location()
def _create_subcontracting_location(self):
parent_location = self.env.ref('stock.stock_location_locations_partner', raise_if_not_found=False)
property_stock_subcontractor_res_partner_field = self.env['ir.model.fields'].search([
('model', '=', 'res.partner'),
('name', '=', 'property_stock_subcontractor')
])
for company in self:
parent_location = self.env.ref('stock.stock_location_locations_partner', raise_if_not_found=False)
subcontracting_location = self.env['stock.location'].create({
'name': _('%s: Subcontracting Location') % company.name,
'usage': 'internal',
'location_id': parent_location.id,
'company_id': company.id,
})
company.write({'subcontracting_location_id': subcontracting_location.id})
@api.model
def create_missing_subcontracting_location(self):
company_without_subcontracting_loc = self.env['res.company'].search(
[('subcontracting_location_id', '=', False)])
for company in company_without_subcontracting_loc:
company._create_subcontracting_location()
self.env['ir.property'].create({
'name': 'property_stock_subcontractor_%s' % company.name,
'fields_id': property_stock_subcontractor_res_partner_field.id,
'company_id': company.id,
'value': 'stock.location,%d' % subcontracting_location.id,
})
company.subcontracting_location_id = subcontracting_location
@@ -1,56 +1,13 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, _
from odoo import fields, models
class ResPartner(models.Model):
_inherit = 'res.partner'
type = fields.Selection(selection_add=[('subcontractor', 'Subcontractor')])
bom_id = fields.Many2one('mrp.bom', domain=[('type', '=', 'subcontract')])
def _get_name(self):
name = super(ResPartner, self)._get_name()
if self.type == 'subcontractor':
subcontractor = _('Subcontractor')
if name.endswith(', '):
name = "%s%s" % (name, subcontractor)
else:
name = '%s, %s' % (name, subcontractor)
return name
@api.onchange('type')
def _onchange_partner_type(self):
if self.type == 'subcontractor':
company = self.company_id or self.parent_id.company_id
subcontracting_location = company.subcontracting_location_id
self.update({
'property_stock_customer': subcontracting_location,
'property_stock_supplier': subcontracting_location
})
else:
self.update({
'property_stock_customer': self.env.ref('stock.stock_location_customers'),
'property_stock_supplier': self.env.ref('stock.stock_location_suppliers')
})
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
if vals.get('type') == 'subcontractor':
if not vals.get('property_stock_supplier') and not vals.get('property_stock_customer'):
company = vals.get('company_id') and self.env['res.company'].browse(vals['company_id']) or \
self.browse(vals.get('parent_id')).company_id
subcontracting_location = company.subcontracting_location_id
vals['property_stock_supplier'] = subcontracting_location
vals['property_stock_customer'] = subcontracting_location
return super(ResPartner, self).create(vals_list)
def write(self, vals):
if vals.get('type') == 'subcontractor':
subcontracting_location = self.env.company.subcontracting_location_id
if not vals.get('property_stock_supplier') and not vals.get('property_stock_customer'):
vals['property_stock_supplier'] = subcontracting_location.id
vals['property_stock_customer'] = subcontracting_location.id
return super(ResPartner, self).write(vals)
property_stock_subcontractor = fields.Many2one(
'stock.location', string="Subcontractor Location", company_dependent=True,
help="The stock location used as source and destination when sending\
goods to this contact during a subcontracting process.")
@@ -47,19 +47,17 @@ class StockMove(models.Model):
def _action_confirm(self, merge=True, merge_into=False):
subcontract_details_per_picking = defaultdict(list)
for move in self:
if not move.picking_id:
if move.location_id.usage != 'supplier' or move.location_dest_id.usage == 'supplier':
continue
if not move.picking_id._is_subcontract():
if not move.picking_id:
continue
bom = move._get_subcontract_bom()
if not bom:
error_message = _('Please define a BoM of type subcontracting for the product "%s". If you don\'t want to subcontract the product "%s", do not select a partner of type subcontractor.')
error_message += '\n\n'
error_message += _('If there is well a BoM of type subcontracting defined, check if you have set the correct subcontractors on it.')
raise UserError(error_message % (move.product_id.name, move.product_id.name))
continue
subcontract_details_per_picking[move.picking_id].append((move, bom))
move.write({
'is_subcontract': True,
'location_id': self.picking_id.partner_id.property_stock_subcontractor.id
})
for picking, subcontract_details in subcontract_details_per_picking.items():
picking._subcontracted_produce(subcontract_details)
@@ -58,10 +58,7 @@ class StockPicking(models.Model):
# -------------------------------------------------------------------------
def _is_subcontract(self):
self.ensure_one()
if self.partner_id.type == 'subcontractor' and \
self.picking_type_id.code == 'incoming':
return True
return False
return self.picking_type_id.code == 'incoming' and any(m.is_subcontract for m in self.move_lines)
def _get_subcontracted_productions(self):
self.ensure_one()
@@ -75,8 +72,8 @@ class StockPicking(models.Model):
'product_id': product.id,
'product_uom_id': subcontract_move.product_uom.id,
'bom_id': bom.id,
'location_src_id': subcontract_move.picking_id.partner_id.property_stock_supplier.id,
'location_dest_id': subcontract_move.location_id.id,
'location_src_id': subcontract_move.picking_id.partner_id.property_stock_subcontractor.id,
'location_dest_id': subcontract_move.picking_id.partner_id.property_stock_subcontractor.id,
'product_qty': subcontract_move.product_uom_qty,
'picking_type_id': warehouse.subcontracting_type_id.id
}
@@ -8,114 +8,13 @@ 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",
'company_id': self.env.ref('base.main_company').id
})
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'
partner_form.company_id = self.env.ref('base.main_company')
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",
'company_id': self.env.ref('base.main_company').id
})
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)
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(SavepointCase):
@classmethod
@@ -125,7 +24,6 @@ class TestSubcontractingFlows(SavepointCase):
main_partner = cls.env['res.partner'].create({'name': 'main_partner'})
cls.subcontractor_partner1 = cls.env['res.partner'].create({
'name': 'subcontractor_partner',
'type': 'subcontractor',
'parent_id': main_partner.id,
'company_id': cls.env.ref('base.main_company').id
})
@@ -218,8 +116,8 @@ class TestSubcontractingFlows(SavepointCase):
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, 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_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.assertEquals(avail_qty_comp1, -1)
self.assertEquals(avail_qty_comp2, -1)
@@ -228,11 +126,27 @@ class TestSubcontractingFlows(SavepointCase):
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.
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'])
@@ -271,13 +185,18 @@ class TestSubcontractingFlows(SavepointCase):
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, 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_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.assertEquals(avail_qty_comp1, -1)
self.assertEquals(avail_qty_comp2, -1)
self.assertEquals(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
@@ -328,8 +247,8 @@ class TestSubcontractingFlows(SavepointCase):
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, 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_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.assertEquals(avail_qty_comp1, -1)
self.assertEquals(avail_qty_comp2, -1)
@@ -342,7 +261,6 @@ class TestSubcontractingFlows(SavepointCase):
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,
'company_id': self.env.ref('base.main_company').id
})
@@ -403,9 +321,9 @@ class TestSubcontractingFlows(SavepointCase):
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,
})
self.env.cache.invalidate()
# We create a different BoM for the same product
comp3 = self.env['product.product'].create({
@@ -460,7 +378,6 @@ class TestSubcontractingTracking(TransactionCase):
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,
'company_id': self.env.ref('base.main_company').id
})
@@ -565,8 +482,8 @@ class TestSubcontractingTracking(TransactionCase):
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_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_lot, wh.lot_stock_id)
self.assertEquals(avail_qty_comp1, -1)
self.assertEquals(avail_qty_comp2, -1)
@@ -1,14 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_partner_form_inherit_mrp" model="ir.ui.view">
<field name="name">res.partner.form.inherit.mrp</field>
<record id="view_partner_mrp_subcontracting_form" model="ir.ui.view">
<field name="name">res.partner.mrp_subcontracting.property.form.inherit</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form" />
<field name="inherit_id" ref="stock.view_partner_stock_form"/>
<field name="arch" type="xml">
<xpath expr="//span[@name='address_name']" position="inside">
<b attrs="{'invisible': ['|', ('parent_id', '=', False), ('type', '!=', 'subcontractor')]}">Subcontractor Address</b>
<xpath expr="//field[@name='property_stock_supplier']" position="after">
<field name="property_stock_subcontractor"/>
</xpath>
</field>
</record>
</odoo>
@@ -9,8 +9,9 @@ class SupplierInfo(models.Model):
is_subcontractor = fields.Boolean('Subcontracted', compute='_compute_is_subcontractor', help="Choose a vendor of type subcontractor if you want to subcontract the product")
@api.depends('name')
@api.depends('name', 'product_id', 'product_tmpl_id')
def _compute_is_subcontractor(self):
for supplierinfo in self:
supplierinfo.is_subcontractor = True if supplierinfo.name.type == 'subcontractor' else False
for supplier in self:
boms = supplier.product_id.variant_bom_ids
boms |= supplier.product_tmpl_id.bom_ids.filtered(lambda b: not b.product_id)
supplier.is_subcontractor = supplier.name in boms.subcontractor_ids
@@ -43,7 +43,6 @@ class TestPurchaseSubcontractingFlows(TransactionCase):
company_partner = self.env['res.partner'].create({'name': 'main_partner', 'is_company': True})
self.subcontractor_partner1 = self.env['res.partner'].create({
'name': 'subcontractor_partner',
'type': 'subcontractor',
'parent_id': company_partner.id,
'company_id': self.env.ref('base.main_company').id
})
@@ -10,16 +10,5 @@
</xpath>
</field>
</record>
<record id="product_supplierinfo_subcontracting_form_view" model="ir.ui.view">
<field name="name">product.supplierinfo.subcontracting.form.view</field>
<field name="model">product.supplierinfo</field>
<field name="inherit_id" ref="product.product_supplierinfo_form_view"/>
<field name="arch" type="xml">
<xpath expr="//group[@name='vendor']" position="inside">
<field name="is_subcontractor" readonly="1"/>
</xpath>
</field>
</record>
</odoo>