multicompany_product_sync/models/mrp_bom.py

129 lines
5.3 KiB
Python

# -*- 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