summaryrefslogtreecommitdiff
path: root/addons/mrp/models
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2019-09-13 18:07:59 +0200
committerChristophe Simonis <chs@odoo.com>2019-09-13 18:07:59 +0200
commit6c948c36bbf72dd896e0bff03aa7fbddadcbb0cd (patch)
treef8620905c011735d8a3c340c46d1417a54a75972 /addons/mrp/models
parent48503fcf08eac6fed12e080d4f501f9bbc8d257d (diff)
parent197474e5b9ad43ab2d587a98136a1a809ce9a2e7 (diff)
[MERGE] forward port branch saas-12.5 up to 197474e5b9a
Diffstat (limited to 'addons/mrp/models')
-rw-r--r--addons/mrp/models/mrp_abstract_workorder.py25
-rw-r--r--addons/mrp/models/mrp_bom.py109
-rw-r--r--addons/mrp/models/mrp_production.py97
-rw-r--r--addons/mrp/models/mrp_routing.py6
-rw-r--r--addons/mrp/models/mrp_unbuild.py75
-rw-r--r--addons/mrp/models/mrp_workcenter.py39
-rw-r--r--addons/mrp/models/mrp_workorder.py59
-rw-r--r--addons/mrp/models/product.py18
-rw-r--r--addons/mrp/models/res_company.py28
-rw-r--r--addons/mrp/models/stock_move.py33
-rw-r--r--addons/mrp/models/stock_scrap.py6
-rw-r--r--addons/mrp/models/stock_warehouse.py19
12 files changed, 347 insertions, 167 deletions
diff --git a/addons/mrp/models/mrp_abstract_workorder.py b/addons/mrp/models/mrp_abstract_workorder.py
index c3f20e0684b..177da2ef423 100644
--- a/addons/mrp/models/mrp_abstract_workorder.py
+++ b/addons/mrp/models/mrp_abstract_workorder.py
@@ -11,12 +11,15 @@ from odoo.tools import float_compare, float_round, float_is_zero
class MrpAbstractWorkorder(models.AbstractModel):
_name = "mrp.abstract.workorder"
_description = "Common code between produce wizards and workorders."
+ _check_company_auto = True
- production_id = fields.Many2one('mrp.production', 'Manufacturing Order', required=True)
- product_id = fields.Many2one(related='production_id.product_id', readonly=True, store=True)
+ production_id = fields.Many2one('mrp.production', 'Manufacturing Order', required=True, check_company=True)
+ product_id = fields.Many2one(related='production_id.product_id', readonly=True, store=True, check_company=True)
qty_producing = fields.Float(string='Currently Produced Quantity', digits='Product Unit of Measure')
product_uom_id = fields.Many2one('uom.uom', 'Unit of Measure', required=True, readonly=True)
- finished_lot_id = fields.Many2one('stock.production.lot', string='Lot/Serial Number', domain="[('product_id', '=', product_id)]")
+ finished_lot_id = fields.Many2one(
+ 'stock.production.lot', string='Lot/Serial Number',
+ domain="[('product_id', '=', product_id), ('company_id', '=', company_id)]", check_company=True)
product_tracking = fields.Selection(related="product_id.tracking")
consumption = fields.Selection([
('strict', 'Strict'),
@@ -24,6 +27,7 @@ class MrpAbstractWorkorder(models.AbstractModel):
required=True,
)
use_create_components_lots = fields.Boolean(related="production_id.picking_type_id.use_create_components_lots")
+ company_id = fields.Many2one(related='production_id.company_id')
@api.model
def _prepare_component_quantity(self, move, qty_producing):
@@ -300,15 +304,20 @@ class MrpAbstractWorkorderLine(models.AbstractModel):
_name = "mrp.abstract.workorder.line"
_description = "Abstract model to implement product_produce_line as well as\
workorder_line"
+ _check_company_auto = True
- move_id = fields.Many2one('stock.move')
- product_id = fields.Many2one('product.product', 'Product', required=True)
+ move_id = fields.Many2one('stock.move', check_company=True)
+ product_id = fields.Many2one('product.product', 'Product', required=True, check_company=True)
product_tracking = fields.Selection(related="product_id.tracking")
- lot_id = fields.Many2one('stock.production.lot', 'Lot/Serial Number')
+ lot_id = fields.Many2one(
+ 'stock.production.lot', 'Lot/Serial Number',
+ check_company=True,
+ domain="[('product_id', '=', product_id), '|', ('company_id', '=', False), ('company_id', '=', company_id)]")
qty_to_consume = fields.Float('To Consume', digits='Product Unit of Measure')
product_uom_id = fields.Many2one('uom.uom', string='Unit of Measure')
qty_done = fields.Float('Consumed', digits='Product Unit of Measure')
qty_reserved = fields.Float('Reserved', digits='Product Unit of Measure')
+ company_id = fields.Many2one('res.company', compute='_compute_company_id')
@api.onchange('lot_id')
def _onchange_lot_id(self):
@@ -335,6 +344,10 @@ class MrpAbstractWorkorderLine(models.AbstractModel):
res['warning'] = {'title': _('Warning'), 'message': message}
return res
+ def _compute_company_id(self):
+ for line in self:
+ line.company_id = line._get_production().company_id
+
def _update_move_lines(self):
""" update a move line to save the workorder line data"""
self.ensure_one()
diff --git a/addons/mrp/models/mrp_bom.py b/addons/mrp/models/mrp_bom.py
index 0a66b1f04e8..d24ad6d9504 100644
--- a/addons/mrp/models/mrp_bom.py
+++ b/addons/mrp/models/mrp_bom.py
@@ -15,6 +15,7 @@ class MrpBom(models.Model):
_inherit = ['mail.thread']
_rec_name = 'product_tmpl_id'
_order = "sequence"
+ _check_company_auto = True
def _get_default_product_uom_id(self):
return self.env['uom.uom'].search([], limit=1, order='id').id
@@ -29,10 +30,12 @@ class MrpBom(models.Model):
default='normal', required=True)
product_tmpl_id = fields.Many2one(
'product.template', 'Product',
- domain="[('type', 'in', ['product', 'consu'])]", required=True)
+ check_company=True,
+ domain="[('type', 'in', ['product', 'consu']), '|', ('company_id', '=', False), ('company_id', '=', company_id)]", required=True)
product_id = fields.Many2one(
'product.product', 'Product Variant',
- domain="['&', ('product_tmpl_id', '=', product_tmpl_id), ('type', 'in', ['product', 'consu'])]",
+ check_company=True,
+ domain="['&', ('product_tmpl_id', '=', product_tmpl_id), ('type', 'in', ['product', 'consu']), '|', ('company_id', '=', False), ('company_id', '=', company_id)]",
help="If a product variant is defined the BOM is available only for this product.")
bom_line_ids = fields.One2many('mrp.bom.line', 'bom_id', 'BoM Lines', copy=True)
byproduct_ids = fields.One2many('mrp.bom.byproduct', 'bom_id', 'By-products', copy=True)
@@ -46,7 +49,7 @@ class MrpBom(models.Model):
product_uom_category_id = fields.Many2one(related='product_id.uom_id.category_id')
sequence = fields.Integer('Sequence', help="Gives the sequence order when displaying a list of bills of material.")
routing_id = fields.Many2one(
- 'mrp.routing', 'Routing',
+ 'mrp.routing', 'Routing', check_company=True,
help="The operations for producing this BoM. When a routing is specified, the production orders will "
" be executed through work orders, otherwise everything is processed in the production order itself. ")
ready_to_produce = fields.Selection([
@@ -54,14 +57,14 @@ class MrpBom(models.Model):
('asap', 'When components for 1st operation are available')], string='Manufacturing Readiness',
default='asap', help="Defines when a Manufacturing Order is considered as ready to be started", required=True)
picking_type_id = fields.Many2one(
- 'stock.picking.type', 'Operation Type', domain=[('code', '=', 'mrp_operation')],
+ 'stock.picking.type', 'Operation Type', domain="[('code', '=', 'mrp_operation'), ('company_id', '=', company_id)]",
+ check_company=True,
help=u"When a procurement has a ‘produce’ route with a operation type set, it will try to create "
"a Manufacturing Order for that product using a BoM of the same operation type. That allows "
"to define stock rules which trigger different manufacturing orders with different BoMs.")
company_id = fields.Many2one(
- 'res.company', 'Company',
- default=lambda self: self.env.company,
- required=True)
+ 'res.company', 'Company', index=True,
+ default=lambda self: self.env.company)
consumption = fields.Selection([
('strict', 'Strict'),
('flexible', 'Flexible')],
@@ -74,17 +77,25 @@ class MrpBom(models.Model):
def onchange_product_id(self):
if self.product_id:
for line in self.bom_line_ids:
- line.attribute_value_ids = False
+ line.bom_product_template_attribute_value_ids = False
@api.constrains('product_id', 'product_tmpl_id', 'bom_line_ids')
- def _check_product_recursion(self):
+ def _check_bom_lines(self):
for bom in self:
- if bom.product_id:
- if bom.bom_line_ids.filtered(lambda x: x.product_id == bom.product_id):
- raise ValidationError(_('BoM line product %s should not be same as BoM product.') % bom.display_name)
- else:
- if bom.bom_line_ids.filtered(lambda x: x.product_id.product_tmpl_id == bom.product_tmpl_id):
- raise ValidationError(_('BoM line product %s should not be same as BoM product.') % bom.display_name)
+ for bom_line in bom.bom_line_ids:
+ if bom.product_id and bom_line.product_id == bom.product_id:
+ raise ValidationError(_("BoM line product %s should not be the same as BoM product.") % bom.display_name)
+ if bom_line.product_tmpl_id == bom.product_tmpl_id:
+ raise ValidationError(_("BoM line product %s should not be the same as BoM product.") % bom.display_name)
+ if bom.product_id and bom_line.bom_product_template_attribute_value_ids:
+ raise ValidationError(_("BoM cannot concern product %s and have a line with attributes (%s) at the same time.")
+ % (bom.product_id.display_name, ", ".join([ptav.display_name for ptav in bom_line.bom_product_template_attribute_value_ids])))
+ for ptav in bom_line.bom_product_template_attribute_value_ids:
+ if ptav.product_tmpl_id != bom.product_tmpl_id:
+ raise ValidationError(
+ _("The attribute value %s set on product %s does not match the BoM product %s.") %
+ (ptav.display_name, ptav.product_tmpl_id.display_name, bom_line.parent_product_tmpl_id.display_name)
+ )
@api.onchange('product_uom_id')
def onchange_product_uom_id(self):
@@ -103,7 +114,7 @@ class MrpBom(models.Model):
if self.product_id.product_tmpl_id != self.product_tmpl_id:
self.product_id = False
for line in self.bom_line_ids:
- line.attribute_value_ids = False
+ line.bom_product_template_attribute_value_ids = False
@api.onchange('routing_id')
def onchange_routing_id(self):
@@ -132,7 +143,7 @@ class MrpBom(models.Model):
if picking_type:
domain += ['|', ('picking_type_id', '=', picking_type.id), ('picking_type_id', '=', False)]
if company_id or self.env.context.get('company_id'):
- domain = domain + [('company_id', '=', company_id or self.env.context.get('company_id'))]
+ domain = domain + ['|', ('company_id', '=', False), ('company_id', '=', company_id or self.env.context.get('company_id'))]
if bom_type:
domain += [('type', '=', bom_type)]
# order to prioritize bom with product_id over the one without
@@ -219,13 +230,15 @@ class MrpBomLine(models.Model):
_order = "sequence, id"
_rec_name = "product_id"
_description = 'Bill of Material Line'
+ _check_company_auto = True
def _get_default_product_uom_id(self):
return self.env['uom.uom'].search([], limit=1, order='id').id
- product_id = fields.Many2one(
- 'product.product', 'Component', required=True)
+ product_id = fields.Many2one( 'product.product', 'Component', required=True, check_company=True)
product_tmpl_id = fields.Many2one('product.template', 'Product Template', related='product_id.product_tmpl_id', readonly=False)
+ company_id = fields.Many2one(
+ related='bom_id.company_id', store=True, index=True, readonly=True)
product_qty = fields.Float(
'Quantity', default=1.0,
digits='Product Unit of Measure', required=True)
@@ -248,12 +261,14 @@ class MrpBomLine(models.Model):
'mrp.bom', 'Parent BoM',
index=True, ondelete='cascade', required=True)
parent_product_tmpl_id = fields.Many2one('product.template', 'Parent Product Template', related='bom_id.product_tmpl_id')
- valid_product_attribute_value_ids = fields.Many2many('product.attribute.value', related='bom_id.product_tmpl_id.valid_product_attribute_value_ids')
- attribute_value_ids = fields.Many2many(
- 'product.attribute.value', string='Apply on Variants',
- help="BOM Product Variants needed form apply this line.")
+ possible_bom_product_template_attribute_value_ids = fields.Many2many('product.template.attribute.value', compute='_compute_possible_bom_product_template_attribute_value_ids')
+ bom_product_template_attribute_value_ids = fields.Many2many(
+ 'product.template.attribute.value', string="Apply on Variants", ondelete='restrict',
+ domain="[('id', 'in', possible_bom_product_template_attribute_value_ids)]",
+ help="BOM Product Variants needed to apply this line.")
operation_id = fields.Many2one(
- 'mrp.routing.workcenter', 'Consumed in Operation',
+ 'mrp.routing.workcenter', 'Consumed in Operation', check_company=True,
+ domain="[('routing_id', '=', routing_id), '|', ('company_id', '=', company_id), ('company_id', '=', False)]",
help="The operation where the components are consumed, or the finished products created.")
child_bom_id = fields.Many2one(
'mrp.bom', 'Sub BoM', compute='_compute_child_bom_id')
@@ -268,6 +283,15 @@ class MrpBomLine(models.Model):
'You should install the mrp_byproduct module if you want to manage extra products on BoMs !'),
]
+ @api.depends(
+ 'parent_product_tmpl_id.attribute_line_ids.value_ids',
+ 'parent_product_tmpl_id.attribute_line_ids.attribute_id.create_variant',
+ 'parent_product_tmpl_id.attribute_line_ids.product_template_value_ids.ptav_active',
+ )
+ def _compute_possible_bom_product_template_attribute_value_ids(self):
+ for line in self:
+ line.possible_bom_product_template_attribute_value_ids = line.parent_product_tmpl_id.valid_product_template_attribute_line_ids._without_no_variant_attributes().product_template_value_ids._only_active()
+
@api.depends('product_id', 'bom_id')
def _compute_child_bom_id(self):
for line in self:
@@ -309,15 +333,6 @@ class MrpBomLine(models.Model):
if self.product_id:
self.product_uom_id = self.product_id.uom_id.id
- @api.onchange('parent_product_tmpl_id')
- def onchange_parent_product(self):
- if not self.parent_product_tmpl_id:
- return {}
- return {'domain': {'attribute_value_ids': [
- ('id', 'in', self.parent_product_tmpl_id.valid_product_attribute_value_ids.ids),
- ('attribute_id.create_variant', '!=', 'no_variant')
- ]}}
-
@api.model_create_multi
def create(self, vals_list):
for values in vals_list:
@@ -326,13 +341,17 @@ class MrpBomLine(models.Model):
return super(MrpBomLine, self).create(vals_list)
def _skip_bom_line(self, product):
- """ Control if a BoM line should be produce, can be inherited for add
+ """ Control if a BoM line should be produced, can be inherited to add
custom control. It currently checks that all variant values are in the
- product. """
- if self.attribute_value_ids:
- for att, att_values in groupby(self.attribute_value_ids, lambda l: l.attribute_id):
- values = self.env['product.attribute.value'].concat(*list(att_values))
- if not (product.attribute_value_ids & values):
+ product.
+
+ If multiple values are encoded for the same attribute line, only one of
+ them has to be found on the variant.
+ """
+ self.ensure_one()
+ if self.bom_product_template_attribute_value_ids:
+ for ptal, iter_ptav in groupby(self.bom_product_template_attribute_value_ids.sorted('attribute_line_id'), lambda ptav: ptav.attribute_line_id):
+ if not any([ptav in product.product_template_attribute_value_ids for ptav in iter_ptav]):
return True
return False
@@ -356,21 +375,27 @@ class MrpBomLine(models.Model):
Use this feature to store any files, like drawings or specifications.
</p>'''),
'limit': 80,
- 'context': "{'default_res_model': '%s','default_res_id': %d}" % ('product.product', self.product_id.id)
+ 'context': "{'default_res_model': '%s','default_res_id': %d, 'default_company_id': %s}" % ('product.product', self.product_id.id, self.company_id.id)
}
class MrpByProduct(models.Model):
_name = 'mrp.bom.byproduct'
_description = 'Byproduct'
+ _check_company_auto = True
- product_id = fields.Many2one('product.product', 'By-product', required=True)
+ product_id = fields.Many2one('product.product', 'By-product', required=True, check_company=True)
+ company_id = fields.Many2one(related='bom_id.company_id', store=True, index=True, readonly=True)
product_qty = fields.Float(
'Quantity',
default=1.0, digits='Product Unit of Measure', required=True)
product_uom_id = fields.Many2one('uom.uom', 'Unit of Measure', required=True)
bom_id = fields.Many2one('mrp.bom', 'BoM', ondelete='cascade')
- operation_id = fields.Many2one('mrp.routing.workcenter', 'Produced in Operation')
+ routing_id = fields.Many2one(
+ 'mrp.routing', 'Routing', store=True, related='bom_id.routing_id')
+ operation_id = fields.Many2one(
+ 'mrp.routing.workcenter', 'Produced in Operation', check_company=True,
+ domain="[('routing_id', '=', routing_id), '|', ('company_id', '=', company_id), ('company_id', '=', False)]")
@api.onchange('product_id')
def onchange_product_id(self):
diff --git a/addons/mrp/models/mrp_production.py b/addons/mrp/models/mrp_production.py
index b3e2795871a..d0da5edac4b 100644
--- a/addons/mrp/models/mrp_production.py
+++ b/addons/mrp/models/mrp_production.py
@@ -20,35 +20,30 @@ class MrpProduction(models.Model):
@api.model
def _get_default_picking_type(self):
+ company_id = self.env.context.get('default_company_id', self.env.company.id)
return self.env['stock.picking.type'].search([
('code', '=', 'mrp_operation'),
- ('warehouse_id.company_id', 'in', [self.env.context.get('company_id', self.env.company.id), False])],
- limit=1).id
+ ('warehouse_id.company_id', '=', company_id),
+ ], limit=1).id
@api.model
def _get_default_location_src_id(self):
location = False
- if self._context.get('default_picking_type_id'):
+ company_id = self.env.context.get('default_company_id', self.env.company.id)
+ if self.env.context.get('default_picking_type_id'):
location = self.env['stock.picking.type'].browse(self.env.context['default_picking_type_id']).default_location_src_id
if not location:
- location = self.env.ref('stock.stock_location_stock', raise_if_not_found=False)
- try:
- location.check_access_rule('read')
- except (AttributeError, AccessError):
- location = self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)], limit=1).lot_stock_id
+ location = self.env['stock.warehouse'].search([('company_id', '=', company_id)], limit=1).lot_stock_id
return location and location.id or False
@api.model
def _get_default_location_dest_id(self):
location = False
+ company_id = self.env.context.get('default_company_id', self.env.company.id)
if self._context.get('default_picking_type_id'):
location = self.env['stock.picking.type'].browse(self.env.context['default_picking_type_id']).default_location_dest_id
if not location:
- location = self.env.ref('stock.stock_location_stock', raise_if_not_found=False)
- try:
- location.check_access_rule('read')
- except (AttributeError, AccessError):
- location = self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)], limit=1).lot_stock_id
+ location = self.env['stock.warehouse'].search([('company_id', '=', company_id)], limit=1).lot_stock_id
return location and location.id or False
@api.model
@@ -65,8 +60,8 @@ class MrpProduction(models.Model):
product_id = fields.Many2one(
'product.product', 'Product',
- domain=[('type', 'in', ['product', 'consu'])],
- readonly=True, required=True,
+ domain="[('bom_ids', '!=', False), ('bom_ids.active', '=', True), ('bom_ids.type', '=', 'normal'), ('type', 'in', ['product', 'consu']), '|', ('company_id', '=', False), ('company_id', '=', company_id)]",
+ readonly=True, required=True, check_company=True,
states={'draft': [('readonly', False)]})
product_tmpl_id = fields.Many2one('product.template', 'Product Template', related='product_id.product_tmpl_id')
product_qty = fields.Float(
@@ -81,18 +76,21 @@ class MrpProduction(models.Model):
product_uom_qty = fields.Float(string='Total Quantity', compute='_compute_product_uom_qty', store=True)
picking_type_id = fields.Many2one(
'stock.picking.type', 'Operation Type',
- default=_get_default_picking_type, required=True)
+ domain="[('code', '=', 'mrp_operation'), ('company_id', '=', company_id)]",
+ default=_get_default_picking_type, required=True, check_company=True)
location_src_id = fields.Many2one(
'stock.location', 'Components Location',
default=_get_default_location_src_id,
- readonly=True, required=True,
- states={'draft': [('readonly', False)]},
+ readonly=True, required=True,
+ domain="[('usage','=','internal'), '|', ('company_id', '=', False), ('company_id', '=', company_id)]",
+ states={'draft': [('readonly', False)]}, check_company=True,
help="Location where the system will look for components.")
location_dest_id = fields.Many2one(
'stock.location', 'Finished Products Location',
default=_get_default_location_dest_id,
- readonly=True, required=True,
- states={'draft': [('readonly', False)]},
+ readonly=True, required=True,
+ domain="[('usage','=','internal'), '|', ('company_id', '=', False), ('company_id', '=', company_id)]",
+ states={'draft': [('readonly', False)]}, check_company=True,
help="Location where the system will stock the finished products.")
date_planned_start = fields.Datetime(
'Planned Date', copy=False, default=fields.Datetime.now,
@@ -116,6 +114,19 @@ class MrpProduction(models.Model):
bom_id = fields.Many2one(
'mrp.bom', 'Bill of Material',
readonly=True, states={'draft': [('readonly', False)]},
+ domain="""[
+ '&',
+ '|',
+ ('company_id', '=', False),
+ ('company_id', '=', company_id),
+ '&',
+ '|',
+ ('product_id','=',product_id),
+ '&',
+ ('product_tmpl_id.product_variant_ids','=',product_id),
+ ('product_id','=',False),
+ ('type', '=', 'normal')]""",
+ check_company=True,
help="Bill of Materials allow you to define the list of required components to make a finished product.")
routing_id = fields.Many2one(
'mrp.routing', 'Routing',
@@ -178,11 +189,12 @@ class MrpProduction(models.Model):
post_visible = fields.Boolean(
'Allowed to Post Inventory', compute='_compute_post_visible',
help='Technical field to check when we can post')
- user_id = fields.Many2one('res.users', 'Responsible', default=lambda self: self._uid)
+ user_id = fields.Many2one(
+ 'res.users', 'Responsible', default=lambda self: self.env.user,
+ domain=lambda self: [('groups_id', 'in', self.env.ref('mrp.group_mrp_user').id)])
company_id = fields.Many2one(
- 'res.company', 'Company',
- default=lambda self: self.env.company,
- required=True)
+ 'res.company', 'Company', default=lambda self: self.env.company,
+ index=True, required=True)
qty_produced = fields.Float(compute="_get_produced_qty", string="Quantity Produced")
procurement_group_id = fields.Many2one(
@@ -202,7 +214,7 @@ class MrpProduction(models.Model):
readonly=True, states={'draft': [('readonly', False)]}, default='1')
is_locked = fields.Boolean('Is Locked', default=True, copy=False)
show_final_lots = fields.Boolean('Show Final Lots', compute='_compute_show_lots')
- production_location_id = fields.Many2one('stock.location', "Production Location", related='product_id.property_stock_production', readonly=False)
+ production_location_id = fields.Many2one('stock.location', "Production Location", related='product_id.property_stock_production', readonly=False) # FIXME sle: probably wrong if document in another company
picking_ids = fields.Many2many('stock.picking', compute='_compute_picking_ids', string='Picking associated to this manufacturing order')
delivery_count = fields.Integer(string='Delivery Orders', compute='_compute_picking_ids')
confirm_cancel = fields.Boolean(compute='_compute_confirm_cancel')
@@ -357,6 +369,7 @@ class MrpProduction(models.Model):
order.post_visible = order.is_locked and any((x.quantity_done > 0 and x.state not in ['done', 'cancel']) for x in order.move_raw_ids | order.move_finished_ids)
else:
order.post_visible = order.is_locked and any((x.quantity_done > 0 and x.state not in ['done', 'cancel']) for x in order.move_finished_ids)
+ order.post_visible &= all(wo.state in ['done', 'cancel'] for wo in order.workorder_ids) or all(m.product_id.tracking == 'none' for m in order.move_raw_ids)
@api.depends('workorder_ids.state', 'move_finished_ids', 'move_finished_ids.quantity_done', 'is_locked')
def _get_produced_qty(self):
@@ -377,6 +390,17 @@ class MrpProduction(models.Model):
('qty_positive', 'check (product_qty > 0)', 'The quantity to produce must be positive!'),
]
+ @api.onchange('company_id')
+ def onchange_company_id(self):
+ if self.company_id:
+ if self.move_raw_ids:
+ self.move_raw_ids.update({'company_id': self.company_id})
+ if self.picking_type_id and self.picking_type_id.company_id != self.company_id:
+ self.picking_type_id = self.env['stock.picking.type'].search([
+ ('code', '=', 'mrp_operation'),
+ ('warehouse_id.company_id', '=', self.company_id.id),
+ ], limit=1).id
+
@api.onchange('product_id', 'picking_type_id', 'company_id')
def onchange_product_id(self):
""" Finds UoM of changed product. """
@@ -397,6 +421,7 @@ class MrpProduction(models.Model):
def _onchange_bom_id(self):
self.product_qty = self.bom_id.product_qty
self.product_uom_id = self.bom_id.product_uom_id.id
+ self.move_raw_ids = [(2, move.id) for move in self.move_raw_ids.filtered(lambda m: m.bom_line_id)]
@api.onchange('date_planned_start')
def _onchange_date_planned_start(self):
@@ -409,11 +434,21 @@ class MrpProduction(models.Model):
@api.onchange('bom_id', 'product_id', 'product_qty', 'product_uom_id')
def _onchange_move_raw(self):
- self.move_raw_ids = [(2, move.id) for move in self.move_raw_ids.filtered(lambda m: m.bom_line_id)]
if self.bom_id and self.product_qty > 0:
+ # keep manual entries
+ list_move_raw = [(4, move.id) for move in self.move_raw_ids.filtered(lambda m: not m.bom_line_id)]
moves_raw_values = self._get_moves_raw_values()
+ move_raw_dict = {move.bom_line_id.id: move for move in self.move_raw_ids.filtered(lambda m: m.bom_line_id)}
for move_raw_values in moves_raw_values:
- self.move_raw_ids += self.env['stock.move'].new(move_raw_values)
+ if move_raw_values['bom_line_id'] in move_raw_dict:
+ # update existing entries
+ list_move_raw += [(1, move_raw_dict[move_raw_values['bom_line_id']].id, move_raw_values)]
+ else:
+ # add new entries
+ list_move_raw += [(0, 0, move_raw_values)]
+ self.move_raw_ids = list_move_raw
+ else:
+ self.move_raw_ids = [(2, move.id) for move in self.move_raw_ids.filtered(lambda m: m.bom_line_id)]
@api.onchange('location_src_id', 'move_raw_ids', 'routing_id')
def _onchange_location(self):
@@ -496,7 +531,7 @@ class MrpProduction(models.Model):
'date': self.date_planned_start,
'date_expected': self.date_planned_finished,
'picking_type_id': self.picking_type_id.id,
- 'location_id': self.product_id.property_stock_production.id,
+ 'location_id': self.product_id.with_context(force_company=self.company_id.id).property_stock_production.id,
'location_dest_id': self.location_dest_id.id,
'company_id': self.company_id.id,
'production_id': self.id,
@@ -552,7 +587,7 @@ class MrpProduction(models.Model):
'product_uom_qty': quantity,
'product_uom': bom_line.product_uom_id.id,
'location_id': source_location.id,
- 'location_dest_id': self.product_id.property_stock_production.id,
+ 'location_dest_id': self.product_id.with_context(force_company=self.company_id.id).property_stock_production.id,
'raw_material_production_id': self.id,
'company_id': self.company_id.id,
'operation_id': bom_line.operation_id.id or alt_op,
@@ -612,6 +647,7 @@ class MrpProduction(models.Model):
return 'confirmed'
def action_confirm(self):
+ self._check_company()
for production in self:
if not production.move_raw_ids:
raise UserError(_("Add some materials to consume before marking this MO as to do."))
@@ -634,6 +670,8 @@ class MrpProduction(models.Model):
def open_produce_product(self):
self.ensure_one()
+ if self.bom_id.type == 'phantom':
+ raise UserError(_('You cannot produce a MO with a bom kit product.'))
action = self.env.ref('mrp.act_mrp_product_produce').read()[0]
return action
@@ -873,6 +911,7 @@ class MrpProduction(models.Model):
def button_mark_done(self):
self.ensure_one()
+ self._check_company()
for wo in self.workorder_ids:
if wo.time_ids.filtered(lambda x: (not x.date_end) and (x.loss_type in ('productive', 'performance'))):
raise UserError(_('Work order %s is still running') % wo.name)
diff --git a/addons/mrp/models/mrp_routing.py b/addons/mrp/models/mrp_routing.py
index 026b075b7a3..ca3a4645f7a 100644
--- a/addons/mrp/models/mrp_routing.py
+++ b/addons/mrp/models/mrp_routing.py
@@ -21,8 +21,7 @@ class MrpRouting(models.Model):
'mrp.routing.workcenter', 'routing_id', 'Operations',
copy=True)
company_id = fields.Many2one(
- 'res.company', 'Company',
- default=lambda self: self.env.company)
+ 'res.company', 'Company', default=lambda self: self.env.company)
@api.model
def create(self, vals):
@@ -35,9 +34,10 @@ class MrpRoutingWorkcenter(models.Model):
_name = 'mrp.routing.workcenter'
_description = 'Work Center Usage'
_order = 'sequence, id'
+ _check_company_auto = True
name = fields.Char('Operation', required=True)
- workcenter_id = fields.Many2one('mrp.workcenter', 'Work Center', required=True)
+ workcenter_id = fields.Many2one('mrp.workcenter', 'Work Center', required=True, check_company=True)
sequence = fields.Integer(
'Sequence', default=100,
help="Gives the sequence order when displaying a list of routing Work Centers.")
diff --git a/addons/mrp/models/mrp_unbuild.py b/addons/mrp/models/mrp_unbuild.py
index 75589b1d590..b767e71cf1b 100644
--- a/addons/mrp/models/mrp_unbuild.py
+++ b/addons/mrp/models/mrp_unbuild.py
@@ -12,26 +12,15 @@ class MrpUnbuild(models.Model):
_inherit = ['mail.thread', 'mail.activity.mixin']
_order = 'id desc'
- def _get_default_location_id(self):
- stock_location = self.env.ref('stock.stock_location_stock', raise_if_not_found=False)
- try:
- stock_location.check_access_rule('read')
- return stock_location.id
- except (AttributeError, AccessError):
- return self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)], limit=1).lot_stock_id.id
-
- def _get_default_location_dest_id(self):
- stock_location = self.env.ref('stock.stock_location_stock', raise_if_not_found=False)
- try:
- stock_location.check_access_rule('read')
- return stock_location.id
- except (AttributeError, AccessError):
- return self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)], limit=1).lot_stock_id.id
-
name = fields.Char('Reference', copy=False, readonly=True, default=lambda x: _('New'))
product_id = fields.Many2one(
- 'product.product', 'Product',
+ 'product.product', 'Product', check_company=True,
+ domain="[('bom_ids', '!=', False), '|', ('company_id', '=', False), ('company_id', '=', company_id)]",
required=True, states={'done': [('readonly', True)]})
+ company_id = fields.Many2one(
+ 'res.company', 'Company',
+ default=lambda s: s.env.company,
+ required=True, index=True, states={'done': [('readonly', True)]})
product_qty = fields.Float(
'Quantity', default=1.0,
required=True, states={'done': [('readonly', True)]})
@@ -40,24 +29,37 @@ class MrpUnbuild(models.Model):
required=True, states={'done': [('readonly', True)]})
bom_id = fields.Many2one(
'mrp.bom', 'Bill of Material',
- domain=[('product_tmpl_id', '=', 'product_id.product_tmpl_id')], #should be more specific
- required=True, states={'done': [('readonly', True)]}) # Add domain
+ domain="""[
+ '|',
+ ('product_id', '=', product_id),
+ '&',
+ ('product_tmpl_id.product_variant_ids', '=', product_id),
+ ('product_id','=',False),
+ ('type', '=', 'normal'),
+ '|',
+ ('company_id', '=', company_id),
+ ('company_id', '=', False)
+ ]
+""",
+ required=True, states={'done': [('readonly', True)]}, check_company=True)
mo_id = fields.Many2one(
'mrp.production', 'Manufacturing Order',
- domain="[('product_id', '=', product_id), ('state', 'in', ['done', 'cancel'])]",
- states={'done': [('readonly', True)]})
+ domain="[('state', 'in', ['done', 'cancel']), ('company_id', '=', company_id)]",
+ states={'done': [('readonly', True)]}, check_company=True)
lot_id = fields.Many2one(
'stock.production.lot', 'Lot/Serial Number',
- domain="[('product_id', '=', product_id)]",
+ domain="[('product_id', '=', product_id), ('company_id', '=', company_id)]", check_company=True,
states={'done': [('readonly', True)]}, help="Lot/Serial Number of the product to unbuild.")
has_tracking=fields.Selection(related='product_id.tracking', readonly=True)
location_id = fields.Many2one(
'stock.location', 'Source Location',
- default=_get_default_location_id,
+ domain="[('usage','=','internal'), '|', ('company_id', '=', False), ('company_id', '=', company_id)]",
+ check_company=True,
required=True, states={'done': [('readonly', True)]}, help="Location where the product you want to unbuild is.")
location_dest_id = fields.Many2one(
'stock.location', 'Destination Location',
- default=_get_default_location_dest_id,
+ domain="[('usage','=','internal'), '|', ('company_id', '=', False), ('company_id', '=', company_id)]",
+ check_company=True,
required=True, states={'done': [('readonly', True)]}, help="Location where you want to send the components resulting from the unbuild order.")
consume_line_ids = fields.One2many(
'stock.move', 'consume_unbuild_id', readonly=True,
@@ -69,19 +71,29 @@ class MrpUnbuild(models.Model):
('draft', 'Draft'),
('done', 'Done')], string='Status', default='draft', index=True)
+ @api.onchange('company_id')
+ def _onchange_company_id(self):
+ if self.company_id:
+ warehouse = self.env['stock.warehouse'].search([('company_id', '=', self.company_id.id)], limit=1)
+ self.location_id = warehouse.lot_stock_id
+ self.location_dest_id = warehouse.lot_stock_id
+ else:
+ self.location_id = False
+ self.location_dest_id = False
+
@api.onchange('mo_id')
- def onchange_mo_id(self):
+ def _onchange_mo_id(self):
if self.mo_id:
self.product_id = self.mo_id.product_id.id
self.product_qty = self.mo_id.product_qty
@api.onchange('product_id')
- def onchange_product_id(self):
+ def _onchange_product_id(self):
if self.product_id:
- self.bom_id = self.env['mrp.bom']._bom_find(product=self.product_id)
+ self.bom_id = self.env['mrp.bom']._bom_find(product=self.product_id, company_id=self.company_id.id)
self.product_uom_id = self.product_id.uom_id.id
- return {'domain': {'mo_id': [('state', '=', 'done'), ('product_id', '=', self.product_id.id)]}}
- return {'domain': {'mo_id': [('state', '=', 'done')]}}
+ if self.company_id:
+ return {'domain': {'mo_id': [('state', '=', 'done'), ('product_id', '=', self.product_id.id), ('company_id', '=', self.company_id.id)]}}
@api.constrains('product_qty')
def _check_qty(self):
@@ -101,6 +113,7 @@ class MrpUnbuild(models.Model):
def action_unbuild(self):
self.ensure_one()
+ self._check_company()
if self.product_id.tracking != 'none' and not self.lot_id.id:
raise UserError(_('You should provide a lot number for the final product.'))
@@ -211,11 +224,12 @@ class MrpUnbuild(models.Model):
'location_id': location_id.id,
'warehouse_id': location_dest_id.get_warehouse().id,
'unbuild_id': self.id,
+ 'company_id': move.company_id.id,
})
def _generate_move_from_bom_line(self, product, product_uom, quantity, bom_line_id=False, byproduct_id=False):
location_id = bom_line_id and product.property_stock_production or self.location_id
- location_dest_id = bom_line_id and self.location_dest_id or product.property_stock_production
+ location_dest_id = bom_line_id and self.location_dest_id or product.with_context(force_company=self.company_id.id).property_stock_production
warehouse = location_dest_id.get_warehouse()
return self.env['stock.move'].create({
'name': self.name,
@@ -230,6 +244,7 @@ class MrpUnbuild(models.Model):
'location_id': location_id.id,
'warehouse_id': warehouse.id,
'unbuild_id': self.id,
+ 'company_id': self.company_id.id,
})
def action_validate(self):
diff --git a/addons/mrp/models/mrp_workcenter.py b/addons/mrp/models/mrp_workcenter.py
index 9748e6042ed..8743ff5f3aa 100644
--- a/addons/mrp/models/mrp_workcenter.py
+++ b/addons/mrp/models/mrp_workcenter.py
@@ -13,6 +13,7 @@ class MrpWorkcenter(models.Model):
_description = 'Work Center'
_order = "sequence, id"
_inherit = ['resource.mixin']
+ _check_company_auto = True
# resource
name = fields.Char('Work Center', related='resource_id.name', store=True, readonly=False)
@@ -61,7 +62,8 @@ class MrpWorkcenter(models.Model):
'mrp_workcenter_alternative_rel',
'workcenter_id',
'alternative_workcenter_id',
- string="Alternative Workcenters",
+ domain="[('id', '!=', id), '|', ('company_id', '=', company_id), ('company_id', '=', False)]",
+ string="Alternative Workcenters", check_company=True,
help="Alternative workcenters that can be substituted to this one in order to dispatch production"
)
@@ -173,12 +175,17 @@ class MrpWorkcenter(models.Model):
times.write({'date_end': fields.Datetime.now()})
return {'type': 'ir.actions.client', 'tag': 'reload'}
- @api.model
- def create(self, vals):
+ @api.model_create_multi
+ def create(self, vals_list):
# resource_type is 'human' by default. As we are not living in
# /r/latestagecapitalism, workcenters are 'material'
- return super(MrpWorkcenter, self.with_context({
- 'default_resource_type': 'material'})).create(vals)
+ records = super(MrpWorkcenter, self.with_context(default_resource_type='material')).create(vals_list)
+ return records
+
+ def write(self, vals):
+ if 'company_id' in vals:
+ self.resource_id.company_id = vals['company_id']
+ return super(MrpWorkcenter, self).write(vals)
def action_work_order(self):
action = self.env.ref('mrp.action_work_orders').read()[0]
@@ -225,10 +232,28 @@ class MrpWorkcenterProductivity(models.Model):
_description = "Workcenter Productivity Log"
_order = "id desc"
_rec_name = "loss_id"
+ _check_company_auto = True
+
+ def _get_default_company_id(self):
+ company_id = False
+ if self.env.context.get('default_company_id'):
+ company_id = self.env.context['default_company_id']
+ if not company_id and self.env.context.get('default_workorder_id'):
+ workorder = self.env['mrp.workorder'].browse(self.env.context['default_workorder_id'])
+ company_id = workorder.company_id
+ if not company_id and self.env.context.get('default_workcenter_id'):
+ workcenter = self.env['mrp.workcenter'].browse(self.env.context['default_workcenter_id'])
+ company_id = workcenter.company_id
+ if not company_id:
+ company_id = self.env.company
+ return company_id
production_id = fields.Many2one('mrp.production', string='Manufacturing Order', related='workorder_id.production_id', readonly='True')
- workcenter_id = fields.Many2one('mrp.workcenter', "Work Center", required=True)
- workorder_id = fields.Many2one('mrp.workorder', 'Work Order')
+ workcenter_id = fields.Many2one('mrp.workcenter', "Work Center", required=True, check_company=True)
+ company_id = fields.Many2one(
+ 'res.company', required=True, index=True,
+ default=lambda self: self._get_default_company_id())
+ workorder_id = fields.Many2one('mrp.workorder', 'Work Order', check_company=True)
user_id = fields.Many2one(
'res.users', "User",
default=lambda self: self.env.uid)
diff --git a/addons/mrp/models/mrp_workorder.py b/addons/mrp/models/mrp_workorder.py
index 75f18a7c4dc..656d93eb4ba 100644
--- a/addons/mrp/models/mrp_workorder.py
+++ b/addons/mrp/models/mrp_workorder.py
@@ -4,6 +4,7 @@
from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta
from collections import defaultdict
+from math import floor
from odoo import api, fields, models, _
from odoo.exceptions import UserError
@@ -18,9 +19,14 @@ class MrpWorkorder(models.Model):
name = fields.Char(
'Work Order', required=True,
states={'done': [('readonly', True)], 'cancel': [('readonly', True)]})
+ company_id = fields.Many2one(
+ 'res.company', 'Company',
+ default=lambda self: self.env.company,
+ required=True, index=True, readonly=True)
workcenter_id = fields.Many2one(
'mrp.workcenter', 'Work Center', required=True,
- states={'done': [('readonly', True)], 'cancel': [('readonly', True)]})
+ states={'done': [('readonly', True)], 'cancel': [('readonly', True)]},
+ check_company=True)
working_state = fields.Selection(
'Workcenter Status', related='workcenter_id.working_state', readonly=False,
help='Technical: used in views only')
@@ -50,7 +56,8 @@ class MrpWorkorder(models.Model):
default='pending')
leave_id = fields.Many2one(
'resource.calendar.leaves',
- help='Slot into workcenter calendar once planned')
+ help='Slot into workcenter calendar once planned',
+ check_company=True)
date_planned_start = fields.Datetime(
'Scheduled Date Start',
compute='_compute_dates_planned',
@@ -83,9 +90,12 @@ class MrpWorkorder(models.Model):
duration_percent = fields.Integer(
'Duration Deviation (%)', compute='_compute_duration',
group_operator="avg", readonly=True, store=True)
+ progress = fields.Float('Progress Done (%)', digits=(16, 2), compute='_compute_progress')
operation_id = fields.Many2one(
- 'mrp.routing.workcenter', 'Operation') # Should be used differently as BoM can change in the meantime
+ 'mrp.routing.workcenter', 'Operation',
+ check_company=True)
+ # Should be used differently as BoM can change in the meantime
worksheet = fields.Binary(
'Worksheet', related='operation_id.worksheet', readonly=True)
worksheet_type = fields.Selection(
@@ -102,8 +112,8 @@ class MrpWorkorder(models.Model):
'stock.move.line', 'workorder_id', 'Moves to Track',
help="Inventory moves for which you must scan a lot number at this work order")
finished_lot_id = fields.Many2one(
- 'stock.production.lot', 'Lot/Serial Number', domain="[('product_id', '=', product_id)]",
- states={'done': [('readonly', True)], 'cancel': [('readonly', True)]})
+ 'stock.production.lot', 'Lot/Serial Number', domain="[('id', 'in', allowed_lots_domain)]",
+ states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}, check_company=True)
time_ids = fields.One2many(
'mrp.workcenter.productivity', 'workorder_id')
is_user_working = fields.Boolean(
@@ -112,7 +122,7 @@ class MrpWorkorder(models.Model):
working_user_ids = fields.One2many('res.users', string='Working user on this work order.', compute='_compute_working_users')
last_working_user_id = fields.One2many('res.users', string='Last user that worked on this work order.', compute='_compute_working_users')
- next_work_order_id = fields.Many2one('mrp.workorder', "Next Work Order")
+ next_work_order_id = fields.Many2one('mrp.workorder', "Next Work Order", check_company=True)
scrap_ids = fields.One2many('stock.scrap', 'workorder_id')
scrap_count = fields.Integer(compute='_compute_scrap_move_count', string='Scrap Move')
production_date = fields.Datetime('Production Date', related='production_id.date_planned_start', store=True, readonly=False)
@@ -146,14 +156,6 @@ class MrpWorkorder(models.Model):
'date_to': date_to,
})
- @api.onchange('date_planned_start')
- def _onchange_date_planned_start(self):
- if self.duration_expected:
- time_delta = timedelta(minutes=self.duration_expected)
- else:
- time_delta = timedelta(hours=1)
- self.update({'date_planned_finished': self.date_planned_start + time_delta})
-
@api.onchange('finished_lot_id')
def _onchange_finished_lot_id(self):
"""When the user changes the lot being currently produced, suggest
@@ -166,6 +168,12 @@ class MrpWorkorder(models.Model):
if line:
self.qty_producing = line.qty_done
+ @api.onchange('date_planned_finished')
+ def _onchange_date_planned_finished(self):
+ if self.date_planned_start and self.date_planned_finished:
+ diff = self.date_planned_finished - self.date_planned_start
+ self.duration_expected = diff.total_seconds() / 60
+
@api.depends('production_id.workorder_ids.finished_workorder_line_ids',
'production_id.workorder_ids.finished_workorder_line_ids.qty_done',
'production_id.workorder_ids.finished_workorder_line_ids.lot_id')
@@ -194,7 +202,10 @@ class MrpWorkorder(models.Model):
qty_produced = sum([max(qty_dones) for qty_dones in qties_done_per_lot.values()])
if float_compare(qty_produced, qty_to_produce, precision_rounding=rounding) < 0:
# If we haven't produced enough, all lots are available
- allowed_lot_ids = self.env['stock.production.lot'].search([('product_id', '=', production.product_id.id)])
+ allowed_lot_ids = self.env['stock.production.lot'].search([
+ ('product_id', '=', production.product_id.id),
+ ('company_id', '=', production.company_id.id),
+ ])
else:
# If we produced enough, only the already produced lots are available
allowed_lot_ids = self.env['stock.production.lot'].browse(qties_done_per_lot.keys())
@@ -232,6 +243,16 @@ class MrpWorkorder(models.Model):
else:
order.duration_percent = 0
+ @api.depends('duration', 'duration_expected', 'state')
+ def _compute_progress(self):
+ for order in self:
+ if order.state == 'done':
+ order.progress = 100
+ elif order.duration_expected:
+ order.progress = order.duration * 100 / order.duration_expected
+ else:
+ order.progress = 0
+
def _compute_working_users(self):
""" Checks whether the current user is working, all the users currently working and the last user that worked. """
for order in self:
@@ -262,9 +283,9 @@ class MrpWorkorder(models.Model):
order.color = 2
@api.onchange('date_planned_start', 'duration_expected')
- def _onchange_date_planned_finished(self):
+ def _onchange_date_planned_start(self):
if self.date_planned_start and self.duration_expected:
- self.date_planned_finished = self.date_planned_start + relativedelta(minutes=self.duration_expected)
+ self.date_planned_finished = self.date_planned_start + relativedelta(minutes=self.duration_expected)
def write(self, values):
if list(values.keys()) != ['time_ids'] and any(workorder.state == 'done' for workorder in self):
@@ -363,6 +384,7 @@ class MrpWorkorder(models.Model):
return True
self.ensure_one()
+ self._check_company()
if float_compare(self.qty_producing, 0, precision_rounding=self.product_uom_id.rounding) <= 0:
raise UserError(_('Please set the quantity you are currently producing. It should be different from zero.'))
@@ -497,7 +519,8 @@ class MrpWorkorder(models.Model):
'description': _('Time Tracking: ')+self.env.user.name,
'loss_id': loss_id[0].id,
'date_start': datetime.now(),
- 'user_id': self.env.user.id
+ 'user_id': self.env.user.id, # FIXME sle: can be inconsistent with company_id
+ 'company_id': self.company_id.id,
})
return self.write({'state': 'progress',
'date_start': datetime.now(),
diff --git a/addons/mrp/models/product.py b/addons/mrp/models/product.py
index ea2f8084d5f..18b8981a522 100644
--- a/addons/mrp/models/product.py
+++ b/addons/mrp/models/product.py
@@ -11,9 +11,12 @@ class ProductTemplate(models.Model):
bom_line_ids = fields.One2many('mrp.bom.line', 'product_tmpl_id', 'BoM Components')
bom_ids = fields.One2many('mrp.bom', 'product_tmpl_id', 'Bill of Materials')
- bom_count = fields.Integer('# Bill of Material', compute='_compute_bom_count')
- used_in_bom_count = fields.Integer('# of BoM Where is Used', compute='_compute_used_in_bom_count')
- mrp_product_qty = fields.Float('Manufactured', compute='_compute_mrp_product_qty')
+ bom_count = fields.Integer('# Bill of Material',
+ compute='_compute_bom_count', compute_sudo=False)
+ used_in_bom_count = fields.Integer('# of BoM Where is Used',
+ compute='_compute_used_in_bom_count', compute_sudo=False)
+ mrp_product_qty = fields.Float('Manufactured',
+ compute='_compute_mrp_product_qty', compute_sudo=False)
produce_delay = fields.Float(
'Manufacturing Lead Time', default=0.0,
help="Average lead time in days to manufacture this product. In the case of multi-level BOM, the manufacturing lead times of the components will be added.")
@@ -52,9 +55,12 @@ class ProductProduct(models.Model):
variant_bom_ids = fields.One2many('mrp.bom', 'product_id', 'BOM Product Variants')
bom_line_ids = fields.One2many('mrp.bom.line', 'product_id', 'BoM Components')
- bom_count = fields.Integer('# Bill of Material', compute='_compute_bom_count')
- used_in_bom_count = fields.Integer('# BoM Where Used', compute='_compute_used_in_bom_count')
- mrp_product_qty = fields.Float('Manufactured', compute='_compute_mrp_product_qty')
+ bom_count = fields.Integer('# Bill of Material',
+ compute='_compute_bom_count', compute_sudo=False)
+ used_in_bom_count = fields.Integer('# BoM Where Used',
+ compute='_compute_used_in_bom_count', compute_sudo=False)
+ mrp_product_qty = fields.Float('Manufactured',
+ compute='_compute_mrp_product_qty', compute_sudo=False)
def _compute_bom_count(self):
for product in self:
diff --git a/addons/mrp/models/res_company.py b/addons/mrp/models/res_company.py
index 0b96b19f3b6..bbfdfd84d14 100644
--- a/addons/mrp/models/res_company.py
+++ b/addons/mrp/models/res_company.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from odoo import fields, models
+from odoo import api, fields, models
class Company(models.Model):
@@ -10,3 +10,29 @@ class Company(models.Model):
manufacturing_lead = fields.Float(
'Manufacturing Lead Time', default=0.0, required=True,
help="Security days for each manufacturing operation.")
+
+ def _create_unbuild_sequence(self):
+ unbuild_vals = []
+ for company in self:
+ unbuild_vals.append({
+ 'name': 'Unbuild',
+ 'code': 'mrp.unbuild',
+ 'company_id': company.id,
+ 'prefix': 'UB/',
+ 'padding': 5,
+ 'number_next': 1,
+ 'number_increment': 1
+ })
+ if unbuild_vals:
+ self.env['ir.sequence'].create(unbuild_vals)
+
+ @api.model
+ def create_missing_unbuild_sequences(self):
+ company_ids = self.env['res.company'].search([])
+ company_has_unbuild_seq = self.env['ir.sequence'].search([('code', '=', 'mrp.unbuild')]).mapped('company_id')
+ company_todo_sequence = company_ids - company_has_unbuild_seq
+ company_todo_sequence._create_unbuild_sequence()
+
+ def _create_per_company_sequences(self):
+ super(Company, self)._create_per_company_sequences()
+ self._create_unbuild_sequence()
diff --git a/addons/mrp/models/stock_move.py b/addons/mrp/models/stock_move.py
index cb46947fd9c..479fad8a5fc 100644
--- a/addons/mrp/models/stock_move.py
+++ b/addons/mrp/models/stock_move.py
@@ -9,11 +9,11 @@ from odoo.tools import float_compare, float_round
class StockMoveLine(models.Model):
_inherit = 'stock.move.line'
- workorder_id = fields.Many2one('mrp.workorder', 'Work Order')
- production_id = fields.Many2one('mrp.production', 'Production Order')
- lot_produced_ids = fields.Many2many('stock.production.lot', string='Finished Lot/Serial Number')
+ workorder_id = fields.Many2one('mrp.workorder', 'Work Order', check_company=True)
+ production_id = fields.Many2one('mrp.production', 'Production Order', check_company=True)
+ lot_produced_ids = fields.Many2many('stock.production.lot', string='Finished Lot/Serial Number', check_company=True)
lot_produced_qty = fields.Float(
- 'Quantity Finished Product', digits='Product Unit of Measure',
+ 'Quantity Finished Product', digits='Product Unit of Measure', check_company=True,
help="Informative, not used in matching")
done_move = fields.Boolean('Move Done', related='move_id.is_done', readonly=False, store=True) # TDE FIXME: naming
@@ -53,23 +53,23 @@ class StockMoveLine(models.Model):
class StockMove(models.Model):
_inherit = 'stock.move'
- created_production_id = fields.Many2one('mrp.production', 'Created Production Order')
+ created_production_id = fields.Many2one('mrp.production', 'Created Production Order', check_company=True)
production_id = fields.Many2one(
- 'mrp.production', 'Production Order for finished products')
+ 'mrp.production', 'Production Order for finished products', check_company=True)
raw_material_production_id = fields.Many2one(
- 'mrp.production', 'Production Order for components')
+ 'mrp.production', 'Production Order for components', check_company=True)
unbuild_id = fields.Many2one(
- 'mrp.unbuild', 'Disassembly Order')
+ 'mrp.unbuild', 'Disassembly Order', check_company=True)
consume_unbuild_id = fields.Many2one(
- 'mrp.unbuild', 'Consumed Disassembly Order')
+ 'mrp.unbuild', 'Consumed Disassembly Order', check_company=True)
operation_id = fields.Many2one(
- 'mrp.routing.workcenter', 'Operation To Consume') # TDE FIXME: naming
+ 'mrp.routing.workcenter', 'Operation To Consume', check_company=True) # TDE FIXME: naming
workorder_id = fields.Many2one(
- 'mrp.workorder', 'Work Order To Consume')
+ 'mrp.workorder', 'Work Order To Consume', check_company=True)
# Quantities to process, in normalized UoMs
- bom_line_id = fields.Many2one('mrp.bom.line', 'BoM Line')
+ bom_line_id = fields.Many2one('mrp.bom.line', 'BoM Line', check_company=True)
byproduct_id = fields.Many2one(
- 'mrp.bom.byproduct', 'By-products',
+ 'mrp.bom.byproduct', 'By-products', check_company=True,
help="By-product line that generated the move in a manufacturing order")
unit_factor = fields.Float('Unit Factor', default=1)
is_done = fields.Boolean(
@@ -234,6 +234,11 @@ class StockMove(models.Model):
res = super(StockMove, self)._should_be_assigned()
return bool(res and not (self.production_id or self.raw_material_production_id))
+ def _key_assign_picking(self):
+ keys = super(StockMove, self)._key_assign_picking()
+ return keys + (self.created_production_id,)
+
+
def _compute_kit_quantities(self, product_id, kit_qty, kit_bom, filters):
""" Computes the quantity delivered or received when a kit is sold or purchased.
A ratio 'qty_processed/qty_needed' is computed for each component, and the lowest one is kept
@@ -267,4 +272,4 @@ class StockMove(models.Model):
# doesn't make sense.
return min(qty_ratios) // 1
else:
- return 0.0
+ return 0.0 \ No newline at end of file
diff --git a/addons/mrp/models/stock_scrap.py b/addons/mrp/models/stock_scrap.py
index 4eb5d06cdac..a13c52ee589 100644
--- a/addons/mrp/models/stock_scrap.py
+++ b/addons/mrp/models/stock_scrap.py
@@ -9,11 +9,11 @@ class StockScrap(models.Model):
production_id = fields.Many2one(
'mrp.production', 'Manufacturing Order',
- states={'done': [('readonly', True)]})
+ states={'done': [('readonly', True)]}, check_company=True)
workorder_id = fields.Many2one(
'mrp.workorder', 'Work Order',
states={'done': [('readonly', True)]},
- help='Not to restrict or prefer quants, but informative.')
+ help='Not to restrict or prefer quants, but informative.', check_company=True)
@api.onchange('workorder_id')
def _onchange_workorder_id(self):
@@ -36,4 +36,4 @@ class StockScrap(models.Model):
return vals
def _get_origin_moves(self):
- return super(StockScrap, self)._get_origin_moves() or self.production_id and self.production_id.move_raw_ids.filtered(lambda x: x.product_id == self.product_id) \ No newline at end of file
+ return super(StockScrap, self)._get_origin_moves() or self.production_id and self.production_id.move_raw_ids.filtered(lambda x: x.product_id == self.product_id)
diff --git a/addons/mrp/models/stock_warehouse.py b/addons/mrp/models/stock_warehouse.py
index 98e8cada056..d89b5e036b5 100644
--- a/addons/mrp/models/stock_warehouse.py
+++ b/addons/mrp/models/stock_warehouse.py
@@ -21,10 +21,10 @@ class StockWarehouse(models.Model):
'stock.rule', 'Stock After Manufacturing Rule')
manu_type_id = fields.Many2one(
'stock.picking.type', 'Manufacturing Operation Type',
- domain=[('code', '=', 'mrp_operation')])
+ domain="[('code', '=', 'mrp_operation'), ('company_id', '=', company_id)]", check_company=True)
- pbm_type_id = fields.Many2one('stock.picking.type', 'Picking Before Manufacturing Operation Type')
- sam_type_id = fields.Many2one('stock.picking.type', 'Stock After Manufacturing Operation Type')
+ pbm_type_id = fields.Many2one('stock.picking.type', 'Picking Before Manufacturing Operation Type', check_company=True)
+ sam_type_id = fields.Many2one('stock.picking.type', 'Stock After Manufacturing Operation Type', check_company=True)
manufacture_steps = fields.Selection([
('mrp_one_step', 'Manufacture (1 step)'),
@@ -38,8 +38,8 @@ class StockWarehouse(models.Model):
pbm_route_id = fields.Many2one('stock.location.route', 'Picking Before Manufacturing Route', ondelete='restrict')
- pbm_loc_id = fields.Many2one('stock.location', 'Picking before Manufacturing Location')
- sam_loc_id = fields.Many2one('stock.location', 'Stock after Manufacturing Location')
+ pbm_loc_id = fields.Many2one('stock.location', 'Picking before Manufacturing Location', check_company=True)
+ sam_loc_id = fields.Many2one('stock.location', 'Stock after Manufacturing Location', check_company=True)
def get_rules_dict(self):
result = super(StockWarehouse, self).get_rules_dict()
@@ -210,9 +210,9 @@ class StockWarehouse(models.Model):
def _get_sequence_values(self):
values = super(StockWarehouse, self)._get_sequence_values()
values.update({
- 'pbm_type_id': {'name': self.name + ' ' + _('Sequence picking before manufacturing'), 'prefix': self.code + '/PC/', 'padding': 5},
- 'sam_type_id': {'name': self.name + ' ' + _('Sequence stock after manufacturing'), 'prefix': self.code + '/SFP/', 'padding': 5},
- 'manu_type_id': {'name': self.name + ' ' + _('Sequence production'), 'prefix': self.code + '/MO/', 'padding': 5},
+ 'pbm_type_id': {'name': self.name + ' ' + _('Sequence picking before manufacturing'), 'prefix': self.code + '/PC/', 'padding': 5, 'company_id': self.company_id.id},
+ 'sam_type_id': {'name': self.name + ' ' + _('Sequence stock after manufacturing'), 'prefix': self.code + '/SFP/', 'padding': 5, 'company_id': self.company_id.id},
+ 'manu_type_id': {'name': self.name + ' ' + _('Sequence production'), 'prefix': self.code + '/MO/', 'padding': 5, 'company_id': self.company_id.id},
})
return values
@@ -228,6 +228,7 @@ class StockWarehouse(models.Model):
'default_location_dest_id': self.pbm_loc_id.id,
'sequence': next_sequence + 1,
'sequence_code': 'PC',
+ 'company_id': self.company_id.id,
},
'sam_type_id': {
'name': _('Store Finished Product'),
@@ -238,6 +239,7 @@ class StockWarehouse(models.Model):
'default_location_dest_id': self.lot_stock_id.id,
'sequence': next_sequence + 3,
'sequence_code': 'SFP',
+ 'company_id': self.company_id.id,
},
'manu_type_id': {
'name': _('Manufacturing'),
@@ -246,6 +248,7 @@ class StockWarehouse(models.Model):
'use_existing_lots': True,
'sequence': next_sequence + 2,
'sequence_code': 'MO',
+ 'company_id': self.company_id.id,
},
})
return data, max_sequence + 4