mrp/populate/mrp.py

409 lines
19 KiB
Python
Raw Permalink Normal View History

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import logging
from datetime import datetime, timedelta
from collections import defaultdict
from odoo import models
from odoo.tools import populate, OrderedSet
from odoo.addons.stock.populate.stock import COMPANY_NB_WITH_STOCK
_logger = logging.getLogger(__name__)
class ResCompany(models.Model):
_inherit = 'res.company'
def _populate_factories(self):
return super()._populate_factories() + [
('manufacturing_lead', populate.randint(0, 2)),
]
class Warehouse(models.Model):
_inherit = 'stock.warehouse'
def _populate_factories(self):
return super()._populate_factories() + [
('manufacture_steps', populate.iterate(['mrp_one_step', 'pbm', 'pbm_sam'], [0.6, 0.2, 0.2]))
]
# TODO : stock picking type manufacturing
class MrpBom(models.Model):
_inherit = 'mrp.bom'
_populate_sizes = {'small': 100, 'medium': 2_000, 'large': 20_000}
_populate_dependencies = ['product.product', 'stock.location']
def _populate_factories(self):
company_ids = self.env.registry.populated_models['res.company'][:COMPANY_NB_WITH_STOCK]
product_tmpl_ids = self.env['product.product'].search([
('id', 'in', self.env.registry.populated_models['product.product']),
('type', 'in', ('product', 'consu'))
]).product_tmpl_id.ids
# Use only a 80 % subset of the products - the 20 % remaining will leaves of the bom tree
random = populate.Random('subset_product_bom')
product_tmpl_ids = random.sample(product_tmpl_ids, int(len(product_tmpl_ids) * 0.8))
def get_product_id(values=None, random=None, **kwargs):
if random.random() > 0.5: # 50 % change to target specific product.product
return False
return random.choice(self.env['product.template'].browse(values['product_tmpl_id']).product_variant_ids.ids)
return [
('company_id', populate.randomize(
[False] + company_ids,
[0.9] + [0.1 / (len(company_ids) or 1.0)] * (len(company_ids)) # TODO: Inverse the weight, but need to make the bom tree by company (in bom line populate)
)),
('product_tmpl_id', populate.randomize(product_tmpl_ids)),
('product_id', populate.compute(get_product_id)),
('product_qty', populate.randint(1, 5)),
('sequence', populate.randint(1, 1000)),
('code', populate.constant("R{counter}")),
('ready_to_produce', populate.randomize(['all_available', 'asap'])),
('produce_delay', populate.randint(1, 4)),
]
class MrpBomLine(models.Model):
_inherit = 'mrp.bom.line'
_populate_sizes = {'small': 500, 'medium': 10_000, 'large': 100_000}
_populate_dependencies = ['mrp.bom']
def _populate_factories(self):
# TODO: tree of product by company to be more closer to the reality
boms = self.env['mrp.bom'].search([('id', 'in', self.env.registry.populated_models['mrp.bom'])], order='sequence, product_id, id')
product_manu_ids = OrderedSet()
for bom in boms:
if bom.product_id:
product_manu_ids.add(bom.product_id.id)
else:
for product_id in bom.product_tmpl_id.product_variant_ids:
product_manu_ids.add(product_id.id)
product_manu_ids = list(product_manu_ids)
product_manu = self.env['product.product'].browse(product_manu_ids)
# product_no_manu is products which don't have any bom (leaves in the BoM trees)
product_no_manu = self.env['product.product'].browse(self.env.registry.populated_models['product.product']) - product_manu
product_no_manu_ids = product_no_manu.ids
def get_product_id(values, counter, random):
bom = self.env['mrp.bom'].browse(values['bom_id'])
last_product_bom = bom.product_id if bom.product_id else bom.product_tmpl_id.product_variant_ids[-1]
# TODO: index in list is in O(n) can be avoid by a cache dict (if performance issue)
index_prod = product_manu_ids.index(last_product_bom.id)
# Always choose a product futher in the recordset `product_manu` to avoid any loops
# Or used a product in the `product_no_manu`
sparsity = 0.4 # Increase the sparsity will decrease the density of the BoM trees => smaller Tree
len_remaining_manu = len(product_manu_ids) - index_prod - 1
len_no_manu = len(product_no_manu_ids)
threshold = len_remaining_manu / (len_remaining_manu + sparsity * len_no_manu)
if random.random() <= threshold:
# TODO: avoid copy the list (if performance issue)
return random.choice(product_manu_ids[index_prod+1:])
else:
return random.choice(product_no_manu_ids)
def get_product_uom_id(values, counter, random):
return self.env['product.product'].browse(values['product_id']).uom_id.id
return [
('bom_id', populate.iterate(boms.ids)),
('sequence', populate.randint(1, 1000)),
('product_id', populate.compute(get_product_id)),
('product_uom_id', populate.compute(get_product_uom_id)),
('product_qty', populate.randint(1, 10)),
]
class MrpWorkcenter(models.Model):
_inherit = 'mrp.workcenter'
_populate_sizes = {'small': 20, 'medium': 100, 'large': 1_000}
def _populate(self, size):
res = super()._populate(size)
# Set alternative workcenters
_logger.info("Set alternative workcenters")
# Valid workcenters by company_id (the workcenter without company can be the alternative of all workcenter)
workcenters_by_company = defaultdict(OrderedSet)
for workcenter in res:
workcenters_by_company[workcenter.company_id.id].add(workcenter.id)
workcenters_by_company = {company_id: self.env['mrp.workcenter'].browse(workcenters) for company_id, workcenters in workcenters_by_company.items()}
workcenters_by_company = {
company_id: workcenters | workcenters_by_company.get(False, self.env['mrp.workcenter'])
for company_id, workcenters in workcenters_by_company.items()}
random = populate.Random('set_alternative_workcenter')
for workcenter in res:
nb_alternative = max(random.randint(0, 3), len(workcenters_by_company[workcenter.company_id.id]) - 1)
if nb_alternative > 0:
alternatives_workcenter_ids = random.sample((workcenters_by_company[workcenter.company_id.id] - workcenter).ids, nb_alternative)
workcenter.alternative_workcenter_ids = [(6, 0, alternatives_workcenter_ids)]
return res
def _populate_factories(self):
company_ids = self.env.registry.populated_models['res.company'][:COMPANY_NB_WITH_STOCK]
resource_calendar_no_company = self.env.ref('resource.resource_calendar_std').copy({'company_id': False})
def get_resource_calendar_id(values, counter, random):
if not values['company_id']:
return resource_calendar_no_company.id
return self.env['res.company'].browse(values['company_id']).resource_calendar_id.id
return [
('name', populate.constant("Workcenter - {counter}")),
('company_id', populate.iterate(company_ids + [False])),
('resource_calendar_id', populate.compute(get_resource_calendar_id)),
('active', populate.iterate([True, False], [0.9, 0.1])),
('code', populate.constant("W/{counter}")),
('default_capacity', populate.iterate([0.5, 1.0, 2.0, 5.0], [0.2, 0.4, 0.2, 0.2])),
('sequence', populate.randint(1, 1000)),
('color', populate.randint(1, 12)),
('costs_hour', populate.randint(5, 25)),
('time_start', populate.iterate([0.0, 2.0, 10.0], [0.6, 0.2, 0.2])),
('time_stop', populate.iterate([0.0, 2.0, 10.0], [0.6, 0.2, 0.2])),
('oee_target', populate.randint(80, 99)),
]
class MrpRoutingWorkcenter(models.Model):
_inherit = 'mrp.routing.workcenter'
_populate_sizes = {'small': 500, 'medium': 5_000, 'large': 50_000}
_populate_dependencies = ['mrp.workcenter', 'mrp.bom']
def _populate_factories(self):
# Take a subset (70%) of bom to have some of then without any operation
random = populate.Random('operation_subset_bom')
boms_ids = self.env.registry.populated_models['mrp.bom']
boms_ids = random.sample(boms_ids, int(len(boms_ids) * 0.7))
# Valid workcenters by company_id (the workcenter without company can be used by any operation)
workcenters_by_company = defaultdict(OrderedSet)
for workcenter in self.env['mrp.workcenter'].browse(self.env.registry.populated_models['mrp.workcenter']):
workcenters_by_company[workcenter.company_id.id].add(workcenter.id)
workcenters_by_company = {company_id: self.env['mrp.workcenter'].browse(workcenters) for company_id, workcenters in workcenters_by_company.items()}
workcenters_by_company = {
company_id: workcenters | workcenters_by_company.get(False, self.env['mrp.workcenter'])
for company_id, workcenters in workcenters_by_company.items()}
def get_company_id(values, counter, random):
bom = self.env['mrp.bom'].browse(values['bom_id'])
return bom.company_id.id
def get_workcenter_id(values, counter, random):
return random.choice(workcenters_by_company[values['company_id']]).id
return [
('bom_id', populate.iterate(boms_ids)),
('company_id', populate.compute(get_company_id)),
('workcenter_id', populate.compute(get_workcenter_id)),
('name', populate.constant("OP-{counter}")),
('sequence', populate.randint(1, 1000)),
('time_mode', populate.iterate(['auto', 'manual'])),
('time_mode_batch', populate.randint(1, 100)),
('time_cycle_manual', populate.randomize([1.0, 15.0, 60.0, 1440.0])),
]
class MrpBomByproduct(models.Model):
_inherit = 'mrp.bom.byproduct'
_populate_sizes = {'small': 50, 'medium': 1_000, 'large': 5_000}
_populate_dependencies = ['mrp.bom.line', 'mrp.routing.workcenter']
def _populate_factories(self):
# Take a subset (50%) of bom to have some of then without any operation
random = populate.Random('byproduct_subset_bom')
boms_ids = self.env.registry.populated_models['mrp.bom']
boms_ids = random.sample(boms_ids, int(len(boms_ids) * 0.5))
boms = self.env['mrp.bom'].search([('id', 'in', self.env.registry.populated_models['mrp.bom'])], order='sequence, product_id, id')
product_manu_ids = OrderedSet()
for bom in boms:
if bom.product_id:
product_manu_ids.add(bom.product_id.id)
else:
for product_id in bom.product_tmpl_id.product_variant_ids:
product_manu_ids.add(product_id.id)
product_manu = self.env['product.product'].browse(product_manu_ids)
# product_no_manu is products which don't have any bom (leaves in the BoM trees)
product_no_manu = self.env['product.product'].browse(self.env.registry.populated_models['product.product']) - product_manu
product_no_manu_ids = product_no_manu.ids
def get_product_uom_id(values, counter, random):
return self.env['product.product'].browse(values['product_id']).uom_id.id
return [
('bom_id', populate.iterate(boms_ids)),
('product_id', populate.randomize(product_no_manu_ids)),
('product_uom_id', populate.compute(get_product_uom_id)),
('product_qty', populate.randint(1, 10)),
]
class MrpProduction(models.Model):
_inherit = 'mrp.production'
_populate_sizes = {'small': 100, 'medium': 1_000, 'large': 10_000}
_populate_dependencies = ['mrp.routing.workcenter', 'mrp.bom.line']
def _populate(self, size):
productions = super()._populate(size)
def confirm_bom_mo(sample_ratio):
# Confirm X % of prototype MO
random = populate.Random('confirm_bom_mo')
mo_ids = productions.filtered('bom_id').ids
mo_to_confirm = self.env['mrp.production'].browse(random.sample(mo_ids, int(len(mo_ids) * 0.8)))
_logger.info("Confirm %d MO with BoM" % len(mo_to_confirm))
mo_to_confirm.action_confirm()
# Uncomment this line to confirm a part of MO, can be useful to check performance
# confirm_bom_mo(0.8)
return productions
def _populate_factories(self):
now = datetime.now()
company_ids = self.env.registry.populated_models['res.company'][:COMPANY_NB_WITH_STOCK]
products = self.env['product.product'].browse(self.env.registry.populated_models['product.product'])
product_ids = products.filtered(lambda product: product.type in ('product', 'consu')).ids
boms = self.env['mrp.bom'].browse(self.env.registry.populated_models['mrp.bom'])
boms_by_company = defaultdict(OrderedSet)
for bom in boms:
boms_by_company[bom.company_id.id].add(bom.id)
boms_by_company = {company_id: self.env['mrp.bom'].browse(boms) for company_id, boms in boms_by_company.items()}
boms_by_company = {
company_id: boms | boms_by_company.get(False, self.env['mrp.bom'])
for company_id, boms in boms_by_company.items()}
def get_bom_id(values, counter, random):
if random.random() > 0.7: # 30 % of prototyping
return False
return random.choice(boms_by_company[values['company_id']]).id
def get_consumption(values, counter, random):
if not values['bom_id']:
return 'flexible'
return self.env['mrp.bom'].browse(values['bom_id']).consumption
def get_product_id(values, counter, random):
if not values['bom_id']:
return random.choice(product_ids)
bom = self.env['mrp.bom'].browse(values['bom_id'])
return bom.product_id.id or random.choice(bom.product_tmpl_id.product_variant_ids.ids)
def get_product_uom_id(values, counter, random):
product = self.env['product.product'].browse(values['product_id'])
return product.uom_id.id
# Fetch all stock picking type and group then by company_id
manu_picking_types = self.env['stock.picking.type'].search([('code', '=', 'mrp_operation')])
manu_picking_types_by_company_id = defaultdict(OrderedSet)
for picking_type in manu_picking_types:
manu_picking_types_by_company_id[picking_type.company_id.id].add(picking_type.id)
manu_picking_types_by_company_id = {company_id: list(picking_ids) for company_id, picking_ids in manu_picking_types_by_company_id.items()}
def get_picking_type_id(values, counter, random):
return random.choice(manu_picking_types_by_company_id[values['company_id']])
def get_location_src_id(values, counter, random):
# TODO : add some randomness
picking_type = self.env['stock.picking.type'].browse(values['picking_type_id'])
return picking_type.default_location_src_id.id
def get_location_dest_id(values, counter, random):
# TODO : add some randomness
picking_type = self.env['stock.picking.type'].browse(values['picking_type_id'])
return picking_type.default_location_dest_id.id
def get_date_start(values, counter, random):
# 95.45 % of picking scheduled between (-10, 30) days and follow a gauss distribution (only +-15% picking is late)
delta = random.gauss(10, 10)
return now + timedelta(days=delta)
return [
('company_id', populate.iterate(company_ids)),
('bom_id', populate.compute(get_bom_id)),
('consumption', populate.compute(get_consumption)),
('product_id', populate.compute(get_product_id)),
('product_uom_id', populate.compute(get_product_uom_id)),
('product_qty', populate.randint(1, 10)),
('picking_type_id', populate.compute(get_picking_type_id)),
('date_start', populate.compute(get_date_start)),
('location_src_id', populate.compute(get_location_src_id)),
('location_dest_id', populate.compute(get_location_dest_id)),
('priority', populate.iterate(['0', '1'], [0.95, 0.05])),
]
class StockMove(models.Model):
_inherit = 'stock.move'
_populate_dependencies = ['stock.picking', 'mrp.production']
def _populate(self, size):
moves = super()._populate(size)
def confirm_prototype_mo(sample_ratio):
# Confirm X % of prototype MO
random = populate.Random('confirm_prototype_mo')
mo_ids = moves.raw_material_production_id.ids
mo_to_confirm = self.env['mrp.production'].browse(random.sample(mo_ids, int(len(mo_ids) * 0.8)))
_logger.info("Confirm %d of prototype MO" % len(mo_to_confirm))
mo_to_confirm.action_confirm()
# (Un)comment this line to confirm a part of prototype MO, can be useful to check performance
# confirm_prototype_mo(0.8)
return moves.exists() # Confirm Mo can unlink moves
def _populate_attach_record_weight(self):
fields, weight = super()._populate_attach_record_weight()
return fields + ['raw_material_production_id'], weight + [1]
def _populate_attach_record_generator(self):
productions = self.env['mrp.production'].browse(self.env.registry.populated_models['mrp.production'])
productions = productions.filtered(lambda prod: not prod.bom_id)
def next_production_id():
while productions:
yield from productions.ids
return {**super()._populate_attach_record_generator(), 'raw_material_production_id': next_production_id()}
def _populate_factories(self):
def _compute_production_values(iterator, field_name, model_name):
for values in iterator:
if values.get('raw_material_production_id'):
production = self.env['mrp.production'].browse(values['raw_material_production_id'])
values['location_id'] = production.location_src_id.id
values['location_dest_id'] = production.production_location_id.id
values['picking_type_id'] = production.picking_type_id.id
values['name'] = production.name
values['date'] = production.date_start
values['company_id'] = production.company_id.id
yield values
return super()._populate_factories() + [
('_compute_production_values', _compute_production_values),
]