diff --git a/models/mrp_mps.py b/models/mrp_mps.py index 8b0883c..fbcf63a 100644 --- a/models/mrp_mps.py +++ b/models/mrp_mps.py @@ -4,6 +4,17 @@ from odoo.tools.float_utils import float_round class MrpProductionSchedule(models.Model): _inherit = 'mrp.production.schedule' + @api.model_create_multi + def create(self, vals_list): + for vals in vals_list: + if 'product_id' in vals and not vals.get('bom_id'): + product = self.env['product.product'].browse(vals['product_id']) + company_id = vals.get('company_id', self.env.company.id) + bom_dict = self.env['mrp.bom']._bom_find(product, company_id=company_id) + if bom_dict and bom_dict.get(product): + vals['bom_id'] = bom_dict[product].id + return super().create(vals_list) + def set_replenish_qty(self, date_index, quantity, period_scale=False): """ Save the replenish quantity and mark the cells as manually updated. We override this to provide two-way (Trace up and Drill down) updating @@ -24,79 +35,81 @@ class MrpProductionSchedule(models.Model): quantity = float_round(float(quantity), precision_rounding=self.product_uom_id.rounding) diff_qty = quantity - old_qty - # If the quantity changed and we're not already propagating an update to avoid infinite loops - if diff_qty != 0 and not self.env.context.get('skip_two_way_mps'): - self._propagate_replenish_qty(diff_qty, date_index, period_scale) + # Determine propagation direction + direction = self.env.context.get('propagate_direction', 'both') + + # If the quantity changed and we're not skipping propagation + if diff_qty != 0 and direction != 'none': + self._propagate_replenish_qty(diff_qty, date_index, period_scale, direction) return res - def _propagate_replenish_qty(self, diff_qty, date_index, period_scale=False): + def _propagate_replenish_qty(self, diff_qty, date_index, period_scale=False, direction='both'): """Propagate replenishment difference to parents (bottom-up) and children (top-down)""" - # We need to trace up (bottom-up) - parent_schedules = self._get_impacted_parent_schedules() - # We need to drill down (top-down) - child_schedules = self._get_impacted_child_schedules() - - # Fetch the BOM hierarchy relationships - schedules_to_compute = parent_schedules | child_schedules | self - indirect_demand_trees = schedules_to_compute._get_indirect_demand_tree() - indirect_ratio_mps = schedules_to_compute._get_indirect_demand_ratio_mps(indirect_demand_trees) - - # TRACE UP: Update parents - for parent_schedule in parent_schedules: - # Find the ratio: how many of 'self.product_id' are needed to make 1 'parent_schedule.product_id' - ratios = indirect_ratio_mps.get((parent_schedule.warehouse_id, parent_schedule.product_id), {}) - ratio = ratios.get(self.product_id, 0.0) + if direction in ('both', 'up'): + parent_schedules = self._get_impacted_parent_schedules() + schedules_to_compute = parent_schedules | self + indirect_demand_trees = schedules_to_compute._get_indirect_demand_tree() + indirect_ratio_mps = schedules_to_compute._get_indirect_demand_ratio_mps(indirect_demand_trees) - if ratio > 0: - parent_diff = diff_qty / ratio + # TRACE UP: Update parents + for parent_schedule in parent_schedules: + # Odoo's indirect_ratio_mps gets non-zero values for direct MPS-connected components + ratios = indirect_ratio_mps.get((parent_schedule.warehouse_id, parent_schedule.product_id), {}) + ratio = ratios.get(self.product_id, 0.0) - # Retrieve the existing parent quantity for this period - p_date_start, p_date_stop = parent_schedule.company_id._get_date_range(force_period=period_scale)[date_index] - p_exist = parent_schedule.forecast_ids.filtered(lambda f: f.date >= p_date_start and f.date <= p_date_stop) - p_old_qty = sum(p_exist.mapped('replenish_qty')) if p_exist else 0.0 - - new_parent_qty = p_old_qty + parent_diff - if new_parent_qty < 0: - new_parent_qty = 0.0 - - # Update parent with skip context to prevent loops - parent_schedule.with_context(skip_two_way_mps=True).set_replenish_qty(date_index, new_parent_qty, period_scale) - - # DRILL DOWN: Update children - for child_schedule in child_schedules: - # Find the ratio: how many of 'child.product_id' base units are needed to make 1 'self.product_id' base unit - ratios = indirect_ratio_mps.get((self.warehouse_id, self.product_id), {}) - ratio = ratios.get(child_schedule.product_id, 0.0) + if ratio > 0: + parent_diff = diff_qty / ratio + + p_date_start, p_date_stop = parent_schedule.company_id._get_date_range(force_period=period_scale)[date_index] + p_exist = parent_schedule.forecast_ids.filtered(lambda f: f.date >= p_date_start and f.date <= p_date_stop) + p_old_qty_base = sum(p_exist.mapped('replenish_qty')) if p_exist else 0.0 + + if hasattr(parent_schedule, 'packaging_id') and parent_schedule.packaging_id and parent_schedule.packaging_id.qty: + p_old_qty_pack = p_old_qty_base / parent_schedule.packaging_id.qty + parent_diff = parent_diff / parent_schedule.packaging_id.qty + else: + p_old_qty_pack = p_old_qty_base + + new_parent_qty = p_old_qty_pack + parent_diff + if new_parent_qty < 0: + new_parent_qty = 0.0 + + # Update parent with 'up' context. This propagates it all the way to the top recursively! + parent_schedule.with_context(propagate_direction='up').set_replenish_qty(date_index, new_parent_qty, period_scale) + + if direction in ('both', 'down'): + child_schedules = self._get_impacted_child_schedules() + schedules_to_compute = child_schedules | self + indirect_demand_trees = schedules_to_compute._get_indirect_demand_tree() + indirect_ratio_mps = schedules_to_compute._get_indirect_demand_ratio_mps(indirect_demand_trees) - if ratio > 0: - # child_diff is in base units - child_diff = diff_qty * ratio + # DRILL DOWN: Update children + for child_schedule in child_schedules: + ratios = indirect_ratio_mps.get((self.warehouse_id, self.product_id), {}) + ratio = ratios.get(child_schedule.product_id, 0.0) - # If the packaging module is used, we must convert the base unit diff_qty - # into the packaging unit diff_qty. - # Because child_schedule.set_replenish_qty will do: quantity = float(quantity) * self.packaging_id.qty - # Therefore, we must pass the quantity *in packaging units*. - if hasattr(child_schedule, 'packaging_id') and child_schedule.packaging_id: - packaging_qty = child_schedule.packaging_id.qty - if packaging_qty: + if ratio > 0: + child_diff = diff_qty * ratio + + if hasattr(child_schedule, 'packaging_id') and child_schedule.packaging_id and child_schedule.packaging_id.qty: + packaging_qty = child_schedule.packaging_id.qty child_diff = child_diff / packaging_qty - - c_date_start, c_date_stop = child_schedule.company_id._get_date_range(force_period=period_scale)[date_index] - c_exist = child_schedule.forecast_ids.filtered(lambda f: f.date >= c_date_start and f.date <= c_date_stop) - - # The existing replenish_qty in the database is actually stored in base units. - # BUT we need to pass the new value to set_replenish_qty in the *packaging* units if packaging is used. - c_old_qty_base = sum(c_exist.mapped('replenish_qty')) if c_exist else 0.0 - - if hasattr(child_schedule, 'packaging_id') and child_schedule.packaging_id and child_schedule.packaging_id.qty: - c_old_qty_pack = c_old_qty_base / child_schedule.packaging_id.qty - else: - c_old_qty_pack = c_old_qty_base - - new_child_qty_pack = c_old_qty_pack + child_diff - if new_child_qty_pack < 0: - new_child_qty_pack = 0.0 - - child_schedule.with_context(skip_two_way_mps=True).set_replenish_qty(date_index, new_child_qty_pack, period_scale) + + c_date_start, c_date_stop = child_schedule.company_id._get_date_range(force_period=period_scale)[date_index] + c_exist = child_schedule.forecast_ids.filtered(lambda f: f.date >= c_date_start and f.date <= c_date_stop) + c_old_qty_base = sum(c_exist.mapped('replenish_qty')) if c_exist else 0.0 + + if hasattr(child_schedule, 'packaging_id') and child_schedule.packaging_id and child_schedule.packaging_id.qty: + c_old_qty_pack = c_old_qty_base / child_schedule.packaging_id.qty + else: + c_old_qty_pack = c_old_qty_base + + new_child_qty_pack = c_old_qty_pack + child_diff + if new_child_qty_pack < 0: + new_child_qty_pack = 0.0 + + # Update child with 'down' context. This propagates it all the way to the bottom! + child_schedule.with_context(propagate_direction='down').set_replenish_qty(date_index, new_child_qty_pack, period_scale) +