[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:
Tiffany Chang (tic)
2022-01-21 07:57:49 +00:00
parent b935db1e70
commit fb781dcf57
4 changed files with 71 additions and 39 deletions
+26 -35
View File
@@ -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:]
+40
View File
@@ -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
+3 -3
View File
@@ -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'), '&amp;', ('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', '&gt;', 0), ('state', '=', 'cancel'), '&amp;', ('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'), '&amp;', ('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'), '&amp;', ('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'), '&amp;', ('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"/>
+2 -1
View File
@@ -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']