diff options
| author | Christophe Simonis <chs@odoo.com> | 2019-09-13 18:07:59 +0200 |
|---|---|---|
| committer | Christophe Simonis <chs@odoo.com> | 2019-09-13 18:07:59 +0200 |
| commit | 6c948c36bbf72dd896e0bff03aa7fbddadcbb0cd (patch) | |
| tree | f8620905c011735d8a3c340c46d1417a54a75972 /addons/mrp/models | |
| parent | 48503fcf08eac6fed12e080d4f501f9bbc8d257d (diff) | |
| parent | 197474e5b9ad43ab2d587a98136a1a809ce9a2e7 (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.py | 25 | ||||
| -rw-r--r-- | addons/mrp/models/mrp_bom.py | 109 | ||||
| -rw-r--r-- | addons/mrp/models/mrp_production.py | 97 | ||||
| -rw-r--r-- | addons/mrp/models/mrp_routing.py | 6 | ||||
| -rw-r--r-- | addons/mrp/models/mrp_unbuild.py | 75 | ||||
| -rw-r--r-- | addons/mrp/models/mrp_workcenter.py | 39 | ||||
| -rw-r--r-- | addons/mrp/models/mrp_workorder.py | 59 | ||||
| -rw-r--r-- | addons/mrp/models/product.py | 18 | ||||
| -rw-r--r-- | addons/mrp/models/res_company.py | 28 | ||||
| -rw-r--r-- | addons/mrp/models/stock_move.py | 33 | ||||
| -rw-r--r-- | addons/mrp/models/stock_scrap.py | 6 | ||||
| -rw-r--r-- | addons/mrp/models/stock_warehouse.py | 19 |
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 |
