# -*- coding: utf-8 -*- from odoo import api, fields, models class MrpBom(models.Model): _inherit = 'mrp.bom' # Tracks the parent BOM record that this branch BOM is synchronized from parent_bom_id = fields.Many2one( 'mrp.bom', string='Parent BOM', ondelete='cascade', index=True, copy=False ) def _is_compatible_with_company(self, company): """ Check if the BOM and all of its components are compatible with the target company. A product is compatible with a target company if: 1. It is global (no company_id set). 2. It belongs to the target company. 3. The target company is a descendant (child) of the product's company (i.e. parent company's product). """ def is_product_compatible(product): if not product.company_id: return True # Check if the target company is the same or a descendant of the product's company child_companies = self.env['res.company'].search([('id', 'child_of', product.company_id.id)]) return company.id in child_companies.ids # The main product template must be compatible if not is_product_compatible(self.product_tmpl_id): return False # The product variant must be compatible if self.product_id and not is_product_compatible(self.product_id): return False # All component products must be compatible for line in self.bom_line_ids: if not is_product_compatible(line.product_id): return False return True def _sync_to_branch_companies(self): """Replicate Bill of Materials (BOM) to all active branch companies recursively.""" if self.env.context.get('skip_company_dependent_sync'): return for bom in self: # Prevent synchronization loop if this is already a child BOM if bom.parent_bom_id: continue # Use the BOM's company as the parent company parent_company = bom.company_id if not parent_company: continue branch_companies = self.env['res.company'].search([('id', 'child_of', parent_company.id)]) - parent_company if not branch_companies: continue for branch in branch_companies: # Check compatibility before creating/updating branch BOM if not bom._is_compatible_with_company(branch): continue # Search if a branch BOM already exists for this parent BOM child_bom = self.env['mrp.bom'].with_company(branch).search([ ('parent_bom_id', '=', bom.id), ('company_id', '=', branch.id) ], limit=1) bom_vals = { 'product_tmpl_id': bom.product_tmpl_id.id, 'product_id': bom.product_id.id if bom.product_id else False, 'product_qty': bom.product_qty, 'product_uom_id': bom.product_uom_id.id, 'type': bom.type, 'code': bom.code, 'consumption': bom.consumption, 'active': bom.active, 'company_id': branch.id, 'parent_bom_id': bom.id, } # Prep line values line_vals_list = [] for line in bom.bom_line_ids: line_vals_list.append((0, 0, { 'product_id': line.product_id.id, 'product_qty': line.product_qty, 'product_uom_id': line.product_uom_id.id, 'sequence': line.sequence, 'company_id': branch.id, })) if child_bom: # Update existing child BOM # 1. Clear existing lines child_bom.with_context(skip_company_dependent_sync=True).write({ 'bom_line_ids': [(5, 0, 0)] }) # 2. Re-create settings and lines bom_vals['bom_line_ids'] = line_vals_list child_bom.with_context(skip_company_dependent_sync=True).write(bom_vals) else: # Create new child BOM bom_vals['bom_line_ids'] = line_vals_list self.env['mrp.bom'].with_company(branch).with_context(skip_company_dependent_sync=True).create(bom_vals) @api.model_create_multi def create(self, vals_list): boms = super().create(vals_list) boms._sync_to_branch_companies() return boms def write(self, vals): res = super().write(vals) # Re-sync if relevant BOM or line fields are changed sync_fields = [ 'product_tmpl_id', 'product_id', 'product_qty', 'product_uom_id', 'type', 'code', 'consumption', 'active', 'bom_line_ids' ] if any(f in vals for f in sync_fields): self._sync_to_branch_companies() return res