182 lines
8.0 KiB
Python
182 lines
8.0 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from collections import defaultdict
|
|
|
|
from odoo import api, fields, models
|
|
from odoo.osv import expression
|
|
|
|
|
|
class MrpBom(models.Model):
|
|
_inherit = 'mrp.bom'
|
|
|
|
version = fields.Integer('Version', default=1, readonly=True)
|
|
previous_bom_id = fields.Many2one('mrp.bom', 'Previous BoM')
|
|
active = fields.Boolean('Production Ready')
|
|
image_128 = fields.Image(related='product_tmpl_id.image_128', readonly=False)
|
|
eco_ids = fields.One2many(
|
|
'mrp.eco', 'new_bom_id', 'ECO to be applied')
|
|
eco_count = fields.Integer('# ECOs', compute='_compute_eco_data')
|
|
|
|
def _compute_eco_data(self):
|
|
previous_boms_mapping = self._get_previous_boms()
|
|
previous_boms_list = list(previous_boms_mapping.keys())
|
|
eco_data = self.env['mrp.eco']._read_group([
|
|
('bom_id', 'in', previous_boms_list),
|
|
('stage_id.folded', '=', False)],
|
|
['bom_id'], ['__count'])
|
|
eco_count = defaultdict(lambda: 0)
|
|
for previous_bom, count in eco_data:
|
|
for bom_id in previous_boms_mapping[previous_bom.id]:
|
|
eco_count[bom_id] += count
|
|
for bom in self:
|
|
bom.eco_count = eco_count[bom.id]
|
|
|
|
def apply_new_version(self):
|
|
""" Put old BoM as deprecated - TODO: Set to stage that is production_ready """
|
|
MrpEco = self.env['mrp.eco']
|
|
for new_bom in self:
|
|
new_bom.write({'active': True})
|
|
# Move eco's into rebase state which is in progress state.
|
|
ecos = MrpEco.search(['|',
|
|
('bom_id', '=', new_bom.previous_bom_id.id),
|
|
('current_bom_id', '=', new_bom.previous_bom_id.id),
|
|
('new_bom_id', '!=', False),
|
|
('new_bom_id', '!=', new_bom.id),
|
|
('state', 'not in', ('done', 'new'))])
|
|
ecos.write({'state': 'rebase', 'current_bom_id': new_bom.id})
|
|
# Change old bom of eco which is in draft state.
|
|
draft_ecos = MrpEco.search(['|',
|
|
('bom_id', '=', new_bom.previous_bom_id.id),
|
|
('current_bom_id', '=', new_bom.previous_bom_id.id),
|
|
('new_bom_id', '=', False)])
|
|
draft_ecos.write({'bom_id': new_bom.id})
|
|
# Deactivate previous revision of BoM
|
|
new_bom.previous_bom_id.write({'active': False})
|
|
return True
|
|
|
|
def button_mrp_eco(self):
|
|
self.ensure_one()
|
|
action = self.env["ir.actions.actions"]._for_xml_id("mrp_plm.mrp_eco_action_main")
|
|
previous_boms = self._get_previous_boms()
|
|
action['domain'] = [('bom_id', 'in', list(previous_boms.keys()))]
|
|
action['context'] = {
|
|
'default_bom_id': self.id,
|
|
'default_product_tmpl_id': self.product_tmpl_id.id,
|
|
'default_type': 'bom'
|
|
}
|
|
return action
|
|
|
|
def _get_previous_boms(self):
|
|
""" Return a dictionary with the keys to be all the previous boms' id and
|
|
the value to be a set of ids in self of which the key is their previous boms.
|
|
"""
|
|
boms_data = self.with_context(active_test=False).search_read(
|
|
[('product_tmpl_id', 'in', self.product_tmpl_id.ids)],
|
|
fields=['id', 'previous_bom_id'], load=False,
|
|
order='id desc, version desc')
|
|
previous_boms = defaultdict(set, {bom.id: {bom.id} for bom in self})
|
|
for bom_data in boms_data:
|
|
if not bom_data['previous_bom_id']:
|
|
continue
|
|
bom_id = bom_data['id']
|
|
previous_bom_id = bom_data['previous_bom_id']
|
|
previous_boms[previous_bom_id] |= previous_boms[bom_id]
|
|
return dict(previous_boms)
|
|
|
|
def _get_active_version(self):
|
|
self.ensure_one()
|
|
domain = [('version', '>', self.version)]
|
|
if self.product_id:
|
|
domain = expression.AND([domain, [('product_id', '=', self.product_id.id)]])
|
|
else:
|
|
domain = expression.AND([domain, [('product_tmpl_id', '=', self.product_tmpl_id.id)]])
|
|
boms = self.with_context(active_test=False).search(domain, order='version')
|
|
previous_boms = self
|
|
for bom in boms:
|
|
if bom.previous_bom_id not in previous_boms:
|
|
continue
|
|
previous_boms += bom
|
|
if bom.active:
|
|
return bom
|
|
return False
|
|
|
|
|
|
class MrpBomLine(models.Model):
|
|
_inherit = 'mrp.bom.line'
|
|
|
|
def _prepare_rebase_line(self, eco, change_type, product_id, uom_id, operation_id=None, new_qty=0):
|
|
self.ensure_one()
|
|
return {
|
|
'change_type': change_type,
|
|
'product_id': product_id,
|
|
'rebase_id': eco.id,
|
|
'old_uom_id': self.product_uom_id.id,
|
|
'new_uom_id': uom_id,
|
|
'old_operation_id': self.operation_id.id,
|
|
'new_operation_id': operation_id,
|
|
'old_product_qty': 0.0 if change_type == 'add' else self.product_qty,
|
|
'new_product_qty': new_qty,
|
|
}
|
|
|
|
def _create_or_update_rebase_line(self, ecos, operation, product_id, uom_id, operation_id=None, new_qty=0):
|
|
self.ensure_one()
|
|
BomChange = self.env['mrp.eco.bom.change']
|
|
for eco in ecos:
|
|
# When product exist in new bill of material update line otherwise add line in rebase changes.
|
|
rebase_line = BomChange.search([
|
|
('product_id', '=', product_id),
|
|
('rebase_id', '=', eco.id)], limit=1)
|
|
if rebase_line:
|
|
# Update existing rebase line or unlink it.
|
|
if (rebase_line.old_product_qty, rebase_line.old_uom_id.id, rebase_line.old_operation_id.id) != (new_qty, uom_id, operation_id):
|
|
if rebase_line.change_type == 'update':
|
|
rebase_line.write({'new_product_qty': new_qty, 'new_operation_id': operation_id, 'new_uom_id': uom_id})
|
|
else:
|
|
rebase_line_vals = self._prepare_rebase_line(eco, 'add', product_id, uom_id, operation_id, new_qty)
|
|
rebase_line.write(rebase_line_vals)
|
|
else:
|
|
rebase_line.unlink()
|
|
else:
|
|
rebase_line_vals = self._prepare_rebase_line(eco, operation, product_id, uom_id, operation_id, new_qty)
|
|
BomChange.create(rebase_line_vals)
|
|
eco.state = 'rebase' if eco.bom_rebase_ids or eco.previous_change_ids else 'progress'
|
|
return True
|
|
|
|
def bom_line_change(self, vals, operation='update'):
|
|
MrpEco = self.env['mrp.eco']
|
|
for line in self:
|
|
ecos = MrpEco.search([
|
|
('bom_id', '=', line.bom_id.id), ('state', 'in', ('progress', 'rebase')),
|
|
('type', 'in', ('bom', 'both'))
|
|
])
|
|
if ecos:
|
|
# Latest bom line (product, uom, operation_id, product_qty)
|
|
product_id = vals.get('product_id', line.product_id.id)
|
|
uom_id = vals.get('product_uom_id', line.product_uom_id.id)
|
|
operation_id = vals.get('operation_id', line.operation_id.id)
|
|
product_qty = vals.get('product_qty', line.product_qty)
|
|
line._create_or_update_rebase_line(ecos, operation, product_id, uom_id, operation_id, product_qty)
|
|
return True
|
|
|
|
@api.model_create_multi
|
|
def create(self, vals_list):
|
|
lines = super().create(vals_list)
|
|
for line, vals in zip(lines, vals_list):
|
|
line.bom_line_change(vals, operation='add')
|
|
return lines
|
|
|
|
def write(self, vals):
|
|
operation = 'update'
|
|
if vals.get('product_id'):
|
|
# It will create update rebase line with negative quantity.
|
|
self.bom_line_change({'product_qty': 0.0}, operation)
|
|
operation = 'add'
|
|
self.bom_line_change(vals, operation)
|
|
return super(MrpBomLine, self).write(vals)
|
|
|
|
def unlink(self):
|
|
# It will create update rebase line.
|
|
self.bom_line_change({'product_qty': 0.0})
|
|
return super(MrpBomLine, self).unlink()
|