[IMP] mrp,stock: enforce consistency for MO's move values
Due to many move values being set only during an onchange, there were inconsistencies with the values of the products to manufacture and the MO's components in the following cases: - when a MO has manually added components/byproducts (i.e. not from a BoM or a MO with no BoM) = missing values - a MO is copied = incorrect values copied - a backorder is made = backorders' moves contained new "-00#" name, but original "-001" MO did not update all corresponding values to new name There were also a couple of default values for manually added moves that were either incorrect or not correctly saving. Most of these inconsistencies are not visible to the user unless they do a data export or are playing close attention (e.g. copying an MTO triggered MO will copy the date_deadline, and this value cannot be edited by the user.) Part 6 of Task: 2667151 Part-of: odoo/odoo#80319
This commit is contained in:
@@ -228,7 +228,7 @@ class MrpProduction(models.Model):
|
||||
'procurement.group', 'Procurement Group',
|
||||
copy=False)
|
||||
product_description_variants = fields.Char('Custom Description')
|
||||
orderpoint_id = fields.Many2one('stock.warehouse.orderpoint', 'Orderpoint')
|
||||
orderpoint_id = fields.Many2one('stock.warehouse.orderpoint', 'Orderpoint', copy=False)
|
||||
propagate_cancel = fields.Boolean(
|
||||
'Propagate cancel and split',
|
||||
help='If checked, when the previous move of the move (which was generated by a next procurement) is cancelled or split, the move generated by this move will too')
|
||||
@@ -767,20 +767,6 @@ class MrpProduction(models.Model):
|
||||
if vals.get('date_planned_finished'):
|
||||
production.move_finished_ids.write({'date': production.date_planned_finished})
|
||||
if any(field in ['move_raw_ids', 'move_finished_ids', 'workorder_ids'] for field in vals) and production.state != 'draft':
|
||||
if production.state == 'done':
|
||||
# for some reason moves added after state = 'done' won't save group_id, reference if added in
|
||||
# "stock_move.default_get()"
|
||||
production.move_raw_ids.filtered(lambda move: move.additional and move.date > production.date_planned_start).write({
|
||||
'group_id': production.procurement_group_id.id,
|
||||
'reference': production.name,
|
||||
'date': production.date_planned_start,
|
||||
'date_deadline': production.date_planned_start
|
||||
})
|
||||
production.move_finished_ids.filtered(lambda move: move.additional and move.date > production.date_planned_finished).write({
|
||||
'reference': production.name,
|
||||
'date': production.date_planned_finished,
|
||||
'date_deadline': production.date_deadline
|
||||
})
|
||||
production._autoconfirm_production()
|
||||
if production in production_to_replan:
|
||||
production._plan_workorders(replan=True)
|
||||
@@ -820,12 +806,6 @@ class MrpProduction(models.Model):
|
||||
vals['procurement_group_id'] = self.env["procurement.group"].create(procurement_group_vals).id
|
||||
productions = super().create(vals_list)
|
||||
for production in productions:
|
||||
(production.move_raw_ids | production.move_finished_ids).write({
|
||||
'group_id': production.procurement_group_id.id,
|
||||
'origin': production.name
|
||||
})
|
||||
production.move_raw_ids.write({'date': production.date_planned_start})
|
||||
production.move_finished_ids.write({'date': production.date_planned_finished})
|
||||
# Trigger move_raw creation when importing a file
|
||||
if 'import_file' in self.env.context:
|
||||
production._onchange_move_raw()
|
||||
@@ -912,10 +892,6 @@ class MrpProduction(models.Model):
|
||||
move_dest_ids = self.move_dest_ids
|
||||
if len(group_orders) > 1:
|
||||
move_dest_ids |= group_orders[0].move_finished_ids.filtered(lambda m: m.product_id == self.product_id).move_dest_ids
|
||||
date_planned_finished = self.date_planned_start + relativedelta(days=self.product_id.produce_delay)
|
||||
date_planned_finished = date_planned_finished + relativedelta(days=self.company_id.manufacturing_lead)
|
||||
if date_planned_finished == self.date_planned_start:
|
||||
date_planned_finished = date_planned_finished + relativedelta(hours=1)
|
||||
return {
|
||||
'product_id': product_id,
|
||||
'product_uom_qty': product_uom_qty,
|
||||
@@ -923,7 +899,7 @@ class MrpProduction(models.Model):
|
||||
'operation_id': operation_id,
|
||||
'byproduct_id': byproduct_id,
|
||||
'name': self.name,
|
||||
'date': date_planned_finished,
|
||||
'date': self._get_date_planned_finished(),
|
||||
'date_deadline': self.date_deadline,
|
||||
'picking_type_id': self.picking_type_id.id,
|
||||
'location_id': self.product_id.with_company(self.company_id).property_stock_production.id,
|
||||
@@ -938,6 +914,13 @@ class MrpProduction(models.Model):
|
||||
'cost_share': cost_share,
|
||||
}
|
||||
|
||||
def _get_date_planned_finished(self):
|
||||
date_planned_finished = self.date_planned_start + relativedelta(days=self.product_id.produce_delay)
|
||||
date_planned_finished = date_planned_finished + relativedelta(days=self.company_id.manufacturing_lead)
|
||||
if date_planned_finished == self.date_planned_start:
|
||||
date_planned_finished = date_planned_finished + relativedelta(hours=1)
|
||||
return date_planned_finished
|
||||
|
||||
def _get_moves_finished_values(self):
|
||||
moves = []
|
||||
for production in self:
|
||||
@@ -999,12 +982,11 @@ class MrpProduction(models.Model):
|
||||
return moves
|
||||
|
||||
def _get_move_raw_values(self, product_id, product_uom_qty, product_uom, operation_id=False, bom_line=False):
|
||||
""" Warning, any changes done to this method will need to be repeated for consistency in:
|
||||
- Manually added components, i.e. "default_" values in view
|
||||
- Moves from a copied MO, i.e. move.create
|
||||
- Existing moves during backorder creation """
|
||||
source_location = self.location_src_id
|
||||
origin = self.name
|
||||
if self.orderpoint_id:
|
||||
origin = self.origin.replace(
|
||||
'%s - ' % (self.orderpoint_id.display_name), '')
|
||||
origin = '%s,%s' % (origin, self.name)
|
||||
data = {
|
||||
'sequence': bom_line.sequence if bom_line else 10,
|
||||
'name': self.name,
|
||||
@@ -1022,7 +1004,7 @@ class MrpProduction(models.Model):
|
||||
'operation_id': operation_id,
|
||||
'price_unit': product_id.standard_price,
|
||||
'procure_method': 'make_to_stock',
|
||||
'origin': origin,
|
||||
'origin': self._get_origin(),
|
||||
'state': 'draft',
|
||||
'warehouse_id': source_location.warehouse_id.id,
|
||||
'group_id': self.procurement_group_id.id,
|
||||
@@ -1030,6 +1012,14 @@ class MrpProduction(models.Model):
|
||||
}
|
||||
return data
|
||||
|
||||
def _get_origin(self):
|
||||
origin = self.name
|
||||
if self.orderpoint_id and self.origin:
|
||||
origin = self.origin.replace(
|
||||
'%s - ' % (self.orderpoint_id.display_name), '')
|
||||
origin = '%s,%s' % (origin, self.name)
|
||||
return origin
|
||||
|
||||
def _set_qty_producing(self):
|
||||
if self.product_id.tracking == 'serial':
|
||||
qty_producing_uom = self.product_uom_id._compute_quantity(self.qty_producing, self.product_id.uom_id, rounding_method='HALF-UP')
|
||||
@@ -1095,9 +1085,6 @@ class MrpProduction(models.Model):
|
||||
additional_moves = production.move_raw_ids.filtered(
|
||||
lambda move: move.state == 'draft'
|
||||
)
|
||||
additional_moves.write({
|
||||
'group_id': production.procurement_group_id.id,
|
||||
})
|
||||
additional_moves._adjust_procure_method()
|
||||
moves_to_confirm |= additional_moves
|
||||
additional_byproducts = production.move_finished_ids.filtered(
|
||||
@@ -1496,6 +1483,8 @@ class MrpProduction(models.Model):
|
||||
'lot_producing_id': False,
|
||||
'origin': self.origin,
|
||||
'state': 'confirmed',
|
||||
'date_deadline': self.date_deadline,
|
||||
'orderpoint_id': self.orderpoint_id.id,
|
||||
}
|
||||
|
||||
def _split_productions(self, amounts=False, cancel_remaning_qty=False):
|
||||
@@ -1534,6 +1523,8 @@ class MrpProduction(models.Model):
|
||||
if production.backorder_sequence == 0: # Activate backorder naming
|
||||
production.backorder_sequence = 1
|
||||
production.name = self._get_name_backorder(production.name, production.backorder_sequence)
|
||||
(production.move_raw_ids | production.move_finished_ids).name = production.name
|
||||
(production.move_raw_ids | production.move_finished_ids).origin = production._get_origin()
|
||||
production.product_qty = amounts[production][0]
|
||||
backorder_vals = production.copy_data(default=production._get_backorder_mo_vals())[0]
|
||||
backorder_qtys = amounts[production][1:]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from collections import defaultdict
|
||||
from odoo import api, Command, fields, models
|
||||
from odoo.osv import expression
|
||||
from odoo.tools import float_compare, float_round, float_is_zero, OrderedSet
|
||||
@@ -242,6 +243,40 @@ class StockMove(models.Model):
|
||||
defaults['reference'] = production_id.name
|
||||
return defaults
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
""" Enforce consistent values (i.e. match _get_move_raw_values/_get_move_finished_values) for:
|
||||
- Manually added components/byproducts specifically values we can't set via view with "default_"
|
||||
- Moves from a copied MO
|
||||
- Backorders
|
||||
"""
|
||||
mo_id_to_mo = defaultdict(lambda: self.env['mrp.production'])
|
||||
product_id_to_product = defaultdict(lambda: self.env['product.product'])
|
||||
for values in vals_list:
|
||||
mo_id = values.get('raw_material_production_id', False) or values.get('production_id', False)
|
||||
if mo_id:
|
||||
mo = mo_id_to_mo[mo_id]
|
||||
if not mo:
|
||||
mo = mo.browse(mo_id)
|
||||
mo_id_to_mo[mo_id] = mo
|
||||
values['name'] = mo.name
|
||||
values['origin'] = mo._get_origin()
|
||||
values['group_id'] = mo.procurement_group_id.id
|
||||
values['propagate_cancel'] = mo.propagate_cancel
|
||||
if values.get('raw_material_production_id', False):
|
||||
product = product_id_to_product[values['product_id']]
|
||||
if not product:
|
||||
product = product.browse(values['product_id'])
|
||||
product_id_to_product[values['product_id']] = product
|
||||
values['location_dest_id'] = mo.production_location_id.id
|
||||
values['price_unit'] = product.standard_price
|
||||
continue
|
||||
# produced products + byproducts
|
||||
values['location_id'] = mo.production_location_id.id
|
||||
values['date'] = mo._get_date_planned_finished()
|
||||
values['date_deadline'] = mo.date_deadline
|
||||
return super().create(vals_list)
|
||||
|
||||
def write(self, vals):
|
||||
if 'product_uom_qty' in vals and 'move_line_ids' in vals:
|
||||
# first update lines then product_uom_qty as the later will unreserve
|
||||
@@ -490,3 +525,8 @@ class StockMove(models.Model):
|
||||
self.move_line_ids = self._set_quantity_done_prepare_vals(quantity_done)
|
||||
else:
|
||||
super()._multi_line_quantity_done_set(quantity_done)
|
||||
|
||||
def _prepare_extra_move_vals(self, qty):
|
||||
vals = super()._prepare_extra_move_vals(qty)
|
||||
vals['date_deadline'] = self.date_deadline
|
||||
return vals
|
||||
|
||||
@@ -263,7 +263,7 @@
|
||||
<notebook>
|
||||
<page string="Components" name="components">
|
||||
<field name="move_raw_ids"
|
||||
context="{'default_date': date_planned_start, 'default_date_deadline': date_deadline, 'default_location_id': location_src_id, 'default_location_dest_id': production_location_id, 'default_state': 'draft', 'default_raw_material_production_id': id, 'default_picking_type_id': picking_type_id, 'default_company_id': company_id}"
|
||||
context="{'default_date': date_planned_start, 'default_date_deadline': date_planned_start, 'default_location_id': location_src_id, 'default_location_dest_id': production_location_id, 'default_state': 'draft', 'default_raw_material_production_id': id, 'default_picking_type_id': picking_type_id, 'default_company_id': company_id}"
|
||||
attrs="{'readonly': ['|', ('state', '=', 'cancel'), '&', ('state', '=', 'done'), ('is_locked', '=', True)]}" options="{'delete': [('state', '=', 'draft')]}">
|
||||
<tree default_order="is_done,sequence" editable="bottom">
|
||||
<field name="product_id" force_save="1" required="1" context="{'default_detailed_type': 'product'}" attrs="{'readonly': ['|', '|', ('move_lines_count', '>', 0), ('state', '=', 'cancel'), '&', ('state', '!=', 'draft'), ('additional', '=', False) ]}"/>
|
||||
@@ -285,7 +285,6 @@
|
||||
<field name="id" invisible="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
|
||||
<field name="propagate_cancel" invisible="1"/>
|
||||
<field name="price_unit" invisible="1"/>
|
||||
<field name="company_id" invisible="1"/>
|
||||
@@ -344,7 +343,7 @@
|
||||
<field name="workorder_ids" attrs="{'readonly': ['|', ('state', '=', 'cancel'), '&', ('state', '=', 'done'), ('is_locked', '=', True)]}" context="{'tree_view_ref': 'mrp.mrp_production_workorder_tree_editable_view', 'default_product_uom_id': product_uom_id}"/>
|
||||
</page>
|
||||
<page string="By-Products" name="finished_products" groups="mrp.group_mrp_byproducts">
|
||||
<field name="move_byproduct_ids" context="{'default_date': date_planned_finished, 'default_date_deadline': date_deadline, 'default_location_id': production_location_id, 'default_location_dest_id': location_src_id, 'default_state': 'draft', 'default_production_id': id, 'default_picking_type_id': picking_type_id, 'default_company_id': company_id}" attrs="{'readonly': ['|', ('state', '=', 'cancel'), '&', ('state', '=', 'done'), ('is_locked', '=', True)]}" options="{'delete': [('state', '=', 'draft')]}">
|
||||
<field name="move_byproduct_ids" context="{'default_date': date_planned_finished, 'default_date_deadline': date_deadline, 'default_location_id': production_location_id, 'default_location_dest_id': location_dest_id, 'default_state': 'draft', 'default_production_id': id, 'default_picking_type_id': picking_type_id, 'default_company_id': company_id}" attrs="{'readonly': ['|', ('state', '=', 'cancel'), '&', ('state', '=', 'done'), ('is_locked', '=', True)]}" options="{'delete': [('state', '=', 'draft')]}">
|
||||
<tree default_order="is_done,sequence" decoration-muted="is_done" editable="bottom">
|
||||
<field name="byproduct_id" invisible="1"/>
|
||||
<field name="product_id" context="{'default_detailed_type': 'product'}" domain="[('id', '!=', parent.product_id)]" required="1"/>
|
||||
@@ -374,6 +373,7 @@
|
||||
<field name="allowed_operation_ids" invisible="1"/>
|
||||
<field name="unit_factor" invisible="1"/>
|
||||
<field name="date" invisible="1"/>
|
||||
<field name="date_deadline" invisible="1" force_save="1"/>
|
||||
<field name="additional" invisible="1"/>
|
||||
<field name="picking_type_id" invisible="1"/>
|
||||
<field name="has_tracking" invisible="1"/>
|
||||
|
||||
@@ -34,7 +34,7 @@ class StockMove(models.Model):
|
||||
'Date Scheduled', default=fields.Datetime.now, index=True, required=True,
|
||||
help="Scheduled date until move is done, then date of actual move processing")
|
||||
date_deadline = fields.Datetime(
|
||||
"Deadline", readonly=True,
|
||||
"Deadline", readonly=True, copy=False,
|
||||
help="Date Promise to the customer on the top level document (SO/PO)")
|
||||
company_id = fields.Many2one(
|
||||
'res.company', 'Company',
|
||||
@@ -1691,6 +1691,7 @@ class StockMove(models.Model):
|
||||
'move_orig_ids': [(4, x.id) for x in self.move_orig_ids],
|
||||
'origin_returned_move_id': self.origin_returned_move_id.id,
|
||||
'price_unit': self.price_unit,
|
||||
'date_deadline': self.date_deadline,
|
||||
}
|
||||
if self.env.context.get('force_split_uom_id'):
|
||||
vals['product_uom'] = self.env.context['force_split_uom_id']
|
||||
|
||||
Reference in New Issue
Block a user