[ADD] Select BoM by properties on the sale order line (feature removed from Odoo 9.0)

This commit is contained in:
Stefan Rijnhart
2018-08-06 11:09:46 +02:00
parent 38219b4458
commit 411db0a963
22 changed files with 542 additions and 0 deletions

79
mrp_property/README.rst Normal file
View File

@@ -0,0 +1,79 @@
==================================
MRP Properties on Sale Order Lines
==================================
.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
:alt: License: LGPL-3
.. |badge2| image:: https://img.shields.io/badge/github-OCA%2Fmanufacture-lightgray.png?logo=github
:target: https://github.com/OCA/manufacture/tree/10.0/mrp_property
:alt: OCA/manufacture
.. |badge3| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/manufacture-10-0/manufacture-10-0-mrp_property
:alt: Translate me on Weblate
.. |badge4| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png
:target: https://runbot.odoo-community.org/runbot/129/10.0
:alt: Try me on Runbot
|badge1| |badge2| |badge3| |badge4|
This module allows sales users to add properties to sale order lines. The properties are propagated to the procurement order and then used to control the selection of the BoM. If there are properties set on a sale order line, the first BoM that has all those properties will be selected for the production order. If there are properties set on a sale order line and no such BoM can be found, but there is a BoM without any properties, that BoM will be returned. If no properties are set on a sale order line, no filter on properties will be applied even if there are BoMs both with and without properties for the given product or product variant.
Installation of this module will activate the sale order line form view.
**Table of contents**
.. contents::
:local:
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/manufacture/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
`feedback <https://github.com/OCA/manufacture/issues/new?body=module:%20mrp_property%0Aversion:%2010.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues.
Credits
=======
Authors
~~~~~~~
* Odoo S.A.
* Opener B.V.
Contributors
~~~~~~~~~~~~
* Odoo S.A.
* Stefan Rijnhart <stefan@opener.amsterdam>
Other credits
~~~~~~~~~~~~~
This functionality was forward ported from Odoo 9.0. It was removed from Odoo in the development cycle of Odoo 10.0, in this commit: https://github.com/odoo/odoo/commit/2ddc35a530
Maintainers
~~~~~~~~~~~
This module is maintained by the OCA.
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
This module is part of the `OCA/manufacture <https://github.com/OCA/manufacture/tree/10.0/mrp_property>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

2
mrp_property/__init__.py Normal file
View File

@@ -0,0 +1,2 @@
from . import models
from .hooks import post_init_hook

View File

@@ -0,0 +1,27 @@
# coding: utf-8
# Copyright 2008-2016 Odoo S.A.
# Copyright 2018 Opener B.V.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
{
'name': "MRP Properties on Sale Order Lines",
'summary': 'Control BoM selection from properties on sale order lines',
'version': '10.0.1.0.0',
'category': 'Manufacturing',
'author': "Odoo S.A.,Opener B.V.,Odoo Community Association (OCA)",
'development_status': 'stable',
'website': 'https://github.com/oca/manufacture',
'license': 'LGPL-3',
'depends': [
'sale_mrp',
],
'data': [
'views/mrp_bom.xml',
'views/mrp_property.xml',
'views/mrp_property_group.xml',
'views/procurement_order.xml',
'views/sale_order.xml',
'security/ir.model.access.csv',
],
'installable': True,
'post_init_hook': 'post_init_hook',
}

13
mrp_property/hooks.py Normal file
View File

@@ -0,0 +1,13 @@
# coding: utf-8
# Copyright 2018 Opener B.V.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import SUPERUSER_ID, api
def post_init_hook(cr, pool):
""" Add everyone to the properties group by default upon installation
"""
env = api.Environment(cr, SUPERUSER_ID, {})
config = env['sale.config.settings'].create({})
config.group_mrp_properties = 1
config.execute()

View File

@@ -0,0 +1,6 @@
from . import mrp_bom
from . import mrp_property
from . import mrp_property_group
from . import procurement_order
from . import sale_order_line
from . import stock_move

View File

@@ -0,0 +1,53 @@
# coding: utf-8
# Copyright 2008 - 2016 Odoo S.A.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
from odoo import api, models, fields
class MrpBom(models.Model):
_inherit = 'mrp.bom'
property_ids = fields.Many2many(
'mrp.property',
'mrp_bom_mrp_property_rel',
'mrp_bom_id', 'mrp_property_id',
string='Properties',
help=("If a production product is manufactured for a sale order, the "
"BoM that has the same properties as the sale order line will "
"be selected (a BoM with no properties at all could be selected "
"as a fallback."))
@api.model
def search(self, args, offset=0, limit=None, order=None, count=False):
""" If limit is set to 1 and property_ids is set in the context, search
a BoM which has all properties specified, or if you can not find one,
you return a BoM without any properties with the lowest sequence. """
check_properties = False
if limit == 1 and self.env.context.get('property_ids'):
check_properties = True
limit = None
boms = super(MrpBom, self).search(
args, offset=offset, limit=limit, order=order, count=count)
if check_properties:
bom_empty_prop = self.env['mrp.bom']
property_ids = set(self.env.context['property_ids'])
for bom in boms:
if bom.property_ids:
if not set(bom.property_ids.ids) - property_ids:
return bom
elif not bom_empty_prop:
bom_empty_prop = bom
return bom_empty_prop
return boms
@api.model
def _bom_find(self, product_tmpl=None, product=None, picking_type=None,
company_id=False):
""" If property_ids are set in the context at this point, add an
additional value in the context that triggers the filter on these
properties in this model's search method """
if self.env.context.get('property_ids'):
self = self.with_context(check_properties=True)
return super(MrpBom, self)._bom_find(
product_tmpl=product_tmpl, product=product,
picking_type=picking_type, company_id=company_id)

View File

@@ -0,0 +1,26 @@
# coding: utf-8
# Copyright 2008 - 2016 Odoo S.A.
# Copyright 2018 Opener B.V.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
from odoo import api, fields, models
from odoo.exceptions import UserError
class MrpProperty(models.Model):
""" Property to control BOM selection from the sale order """
_name = 'mrp.property'
_description = 'MRP Property'
name = fields.Char(required=True)
group_id = fields.Many2one(
'mrp.property.group', 'Property Group', required=True)
description = fields.Text()
@api.multi
def unlink(self):
""" Restrict the removal of properties that are in use """
if self.env['sale.order.line'].sudo().search(
[('property_ids', 'in', self.ids)]):
raise UserError('You cannot delete this property, because it has '
'been assigned to a sale order line.')
return super(MrpProperty, self).unlink()

View File

@@ -0,0 +1,13 @@
# coding: utf-8
# Copyright 2008 - 2016 Odoo S.A.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
from odoo import fields, models
class MrpPropertyGroup(models.Model):
""" Group of mrp properties """
_name = 'mrp.property.group'
_description = 'Property Group'
name = fields.Char(required=True)
description = fields.Text()

View File

@@ -0,0 +1,22 @@
# coding: utf-8
# Copyright 2008 - 2016 Odoo S.A.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
from odoo import api, fields, models
class ProcurementOrder(models.Model):
_inherit = 'procurement.order'
property_ids = fields.Many2many(
'mrp.property', 'procurement_property_rel',
'procurement_id', 'property_id',
string='Properties',
help=("The BoM that has the same properties as this procurement will "
"be selected unless there is a BoM with no properties at all."))
@api.multi
def _get_matching_bom(self):
""" Inject property ids in the context, to be honoured in the
production model's search method """
return super(ProcurementOrder, self.with_context(
property_ids=self.property_ids.ids))._get_matching_bom()

View File

@@ -0,0 +1,36 @@
# coding: utf-8
# Copyright 2008 - 2016 Odoo S.A.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
from odoo import api, fields, models
class SaleOrderLine(models.Model):
_inherit = 'sale.order.line'
property_ids = fields.Many2many(
'mrp.property', 'sale_order_line_property_rel', 'order_id',
'property_id', 'Properties', readonly=True,
help=("If a production product is manufactured for this sale order, "
"the BoM that has the same properties as the sale order line "
"will be selected (a BoM with no properties at all could be "
"selected as a fallback"),
states={'draft': [('readonly', False)],
'sent': [('readonly', False)]})
@api.multi
def _prepare_order_line_procurement(self, group_id=False):
""" Add the properties of the sale order line to the procurement
that is generated from it """
vals = super(SaleOrderLine, self)._prepare_order_line_procurement(
group_id=group_id)
vals['property_ids'] = [(6, 0, self.property_ids.ids)]
return vals
@api.multi
def _get_delivered_qty(self):
""" Make sure that the correct phantom bom is selected in the super
method (if any) """
self.ensure_one()
if self.property_ids:
self = self.with_context(property_ids=self.property_ids.ids)
return super(SaleOrderLine, self)._get_delivered_qty()

View File

@@ -0,0 +1,27 @@
# coding: utf-8
# Copyright 2008 - 2016 Odoo S.A.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
from odoo import api, models
class StockMove(models.Model):
_inherit = 'stock.move'
@api.multi
def _prepare_procurement_from_move(self):
""" The procurement that creates the MO might not be the original
procurement. Therefore, propagate the properties further down. """
res = super(StockMove, self)._prepare_procurement_from_move()
if self.procurement_id.property_ids:
res['property_ids'] = [
(6, 0, self.procurement_id.property_ids.ids)]
return res
@api.multi
def action_explode(self):
""" Pass the properties from the procurement in the context for the
selection of the right BoM """
properties = self.procurement_id.sale_line_id.property_ids
if properties:
self = self.with_context(property_ids=properties.ids)
return super(StockMove, self).action_explode()

View File

@@ -0,0 +1,2 @@
* Odoo S.A.
* Stefan Rijnhart <stefan@opener.amsterdam>

View File

@@ -0,0 +1 @@
This functionality was forward ported from Odoo 9.0. It was removed from Odoo in the development cycle of Odoo 10.0, in this commit: https://github.com/odoo/odoo/commit/2ddc35a530

View File

@@ -0,0 +1,3 @@
This module allows sales users to add properties to sale order lines. The properties are propagated to the procurement order and then used to control the selection of the BoM. If there are properties set on a sale order line, the first BoM that has all those properties will be selected for the production order. If there are properties set on a sale order line and no such BoM can be found, but there is a BoM without any properties, that BoM will be returned. If no properties are set on a sale order line, no filter on properties will be applied even if there are BoMs both with and without properties for the given product or product variant.
Installation of this module will activate the sale order line form view.

View File

@@ -0,0 +1,5 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_mrp_property_group_manager,mrp.property.group,model_mrp_property_group,stock.group_stock_manager,1,1,1,1
access_mrp_property_manager,mrp.property,model_mrp_property,stock.group_stock_manager,1,1,1,1
access_mrp_property_group,mrp.property.group,model_mrp_property_group,base.group_user,1,0,0,0
access_mrp_property,mrp.property,model_mrp_property,base.group_user,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_mrp_property_group_manager mrp.property.group model_mrp_property_group stock.group_stock_manager 1 1 1 1
3 access_mrp_property_manager mrp.property model_mrp_property stock.group_stock_manager 1 1 1 1
4 access_mrp_property_group mrp.property.group model_mrp_property_group base.group_user 1 0 0 0
5 access_mrp_property mrp.property model_mrp_property base.group_user 1 0 0 0

View File

@@ -0,0 +1 @@
from . import test_mrp_property

View File

@@ -0,0 +1,92 @@
# coding: utf-8
# Copyright 2018 Opener B.V.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo.tests.common import SavepointCase
from odoo.exceptions import UserError
class TestBomProperty(SavepointCase):
def setUp(self):
super(TestBomProperty, self).setUp()
self.product = self.env.ref('product.product_product_3')
self.group = self.env['mrp.property.group'].create({'name': __name__})
self.property1 = self.env['mrp.property'].create({
'name': 'prop1',
'group_id': self.group.id,
})
self.property2 = self.env['mrp.property'].create({
'name': 'prop2',
'group_id': self.group.id,
})
self.bom_without_properties = self.env.ref('mrp.mrp_bom_manufacture')
self.bom_with_properties = self.env.ref(
'mrp.mrp_bom_manufacture').copy({
'sequence': '999',
'property_ids':
[(6, 0, [self.property1.id, self.property2.id])]})
partner = self.env.ref('base.res_partner_address_7')
self.order = self.env['sale.order'].create({
'partner_id': partner.id,
'partner_invoice_id': partner.id,
'partner_shipping_id': partner.id,
'pricelist_id': self.env.ref('product.list0').id,
})
self.line = self.env['sale.order.line'].create({
'order_id': self.order.id,
'product_id': self.product.id,
'product_uom_qty': 2,
})
self.last_production_id = (
self.env['mrp.production'].search(
[('product_id', '=', self.product.id)],
order='id desc', limit=1).id or 0)
def test_01_no_properties(self):
""" BoM with the lowest sequence is selected for a line w/o properties
"""
self.order.action_confirm()
production = self.env['mrp.production'].search(
[('product_id', '=', self.product.id),
('id', '>', self.last_production_id)])
self.assertTrue(production)
self.assertEqual(production.bom_id, self.bom_without_properties)
def test_02_one_property(self):
""" Without a BoM with all the properties, the fallback is selected """
self.line.property_ids = self.property1
self.order.action_confirm()
production = self.env['mrp.production'].search(
[('product_id', '=', self.product.id),
('id', '>', self.last_production_id)])
self.assertTrue(production)
self.assertEqual(
production.bom_id, self.bom_without_properties)
def test_03_two_properties(self):
""" The BoM with all the properties is selected """
self.line.property_ids = self.property1 + self.property2
self.order.action_confirm()
production = self.env['mrp.production'].search(
[('product_id', '=', self.product.id),
('id', '>', self.last_production_id)])
self.assertTrue(production)
self.assertEqual(
production.bom_id, self.bom_with_properties)
def test_04_one_property_no_fallback(self):
""" Without a fallback BoM, the procurement cannot proceed """
self.line.property_ids = self.property1
self.bom_without_properties.active = False
self.order.action_confirm()
production = self.env['mrp.production'].search(
[('product_id', '=', self.product.id),
('id', '>', self.last_production_id)])
self.assertFalse(production)
def test_05_delete_properties(self):
""" Cannot delete properties that are in use """
self.line.property_ids = self.property1 + self.property2
with self.assertRaises(UserError):
self.property2.unlink()
self.line.property_ids = self.property1
self.property2.unlink()

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="mrp_bom_form_view" model="ir.ui.view">
<field name="model">mrp.bom</field>
<field name="inherit_id" ref="mrp.mrp_bom_form_view"/>
<field name="arch" type="xml">
<xpath expr="//group/field[@name='sequence']" position="after">
<field name="property_ids" groups="product.group_mrp_properties"
widget="many2many_tags"/>
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="mrp_property_tree_view" model="ir.ui.view">
<field name="model">mrp.property</field>
<field name="arch" type="xml">
<tree editable="top">
<field name="name"/>
<field name="group_id"/>
<field name="description"/>
</tree>
</field>
</record>
<record id="view_mrp_property_search" model="ir.ui.view">
<field name="name">mrp.property.search</field>
<field name="model">mrp.property</field>
<field name="arch" type="xml">
<search string="Search">
<field name="name" string="Name"/>
<field name="group_id" />
<group expand="0" string="Group By">
<filter string="Property Group" domain="[]" context="{'group_by':'group_id'}"/>
</group>
</search>
</field>
</record>
<record id="mrp_property_action" model="ir.actions.act_window">
<field name="name">Properties</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">mrp.property</field>
<field name="view_type">form</field>
<field name="view_mode">tree</field>
<field name="search_view_id" ref="view_mrp_property_search"/>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to create a new property.
</p><p>
The Properties in Odoo are used to select the right bill of
materials for manufacturing a product when you have different
ways of building the same product. You can assign several
properties to each bill of materials. When a salesperson
creates a sales order, they can relate it to several properties
and Odoo will automatically select the BoM to use according
the needs.
</p>
</field>
</record>
<menuitem id="menu_mrp_property_action"
action="mrp_property_action"
groups="sale.group_mrp_properties"
parent="mrp.menu_mrp_configuration"
sequence="30"/>
</odoo>

View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="mrp_property_group_tree_view" model="ir.ui.view">
<field name="model">mrp.property.group</field>
<field name="arch" type="xml">
<tree editable="top">
<field name="name"/>
<field name="description"/>
</tree>
</field>
</record>
<record id="mrp_property_group_action" model="ir.actions.act_window">
<field name="name">Property Groups</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">mrp.property.group</field>
<field name="view_type">form</field>
<field name="view_mode">tree</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to create a group of properties.
</p><p>
Define specific property groups that can be assigned to your
bill of materials and sales orders. Properties allows Odoo
to automatically select the right bill of materials according
to properties selected in the sales order by salesperson.
</p><p>
For instance, in the property group "Warranty", you an have
two properties: 1 year warranty, 3 years warranty. Depending
on the propoerties selected in the sales order, Odoo will
schedule a production using the matching bill of materials.
</p>
</field>
</record>
<menuitem id="menu_mrp_property_group_action"
action="mrp_property_group_action"
parent="mrp.menu_mrp_configuration"
groups="sale.group_mrp_properties"
sequence="35"/>
</odoo>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="procurement_form_view" model="ir.ui.view">
<field name="model">procurement.order</field>
<field name="inherit_id" ref="procurement.procurement_form_view"/>
<field name="arch" type="xml">
<field name="origin" position="after">
<field name="property_ids" widget="many2many_tags" groups="sale.group_mrp_properties"/>
</field>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="view_order_form_inherit_sale_mrp" model="ir.ui.view">
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_order_form"/>
<field name="arch" type="xml">
<xpath expr="//page/field[@name='order_line']/form/group/group/field[@name='tax_id']" position="after">
<field name="property_ids" widget="many2many_tags" groups="sale.group_mrp_properties"/>
</xpath>
</field>
</record>
</odoo>