[REF] stock,mrp: make picking_type_id compute store in stock.move

In order to have always a picking_type_id on a stock move (and
consistent with his parent model), make it compute store.
Then clean the call of picking_type_id without change the behavior
(/!\ `move.picking_id.picking_type_id != move.picking_type_id` because
of mrp). Fix some test where the location(_dest) was unset in moves.

Also:
- Clean the mess in the create of `stock.picking` about that
and add a onchange to keep location(_dest) consistent with moves.
- The move `description_picking` is set when the `picking_type_id`
change, not only at the create method.

task-2373638
This commit is contained in:
Rémy Voet (ryv)
2021-01-05 08:32:05 +00:00
parent 56463c2a29
commit 515b9a78e6
9 changed files with 52 additions and 54 deletions
-2
View File
@@ -651,8 +651,6 @@ class MrpProduction(models.Model):
location.check_access_rule('read')
except (AttributeError, AccessError):
location = self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)], limit=1).lot_stock_id
self.move_raw_ids.update({'picking_type_id': self.picking_type_id})
self.move_finished_ids.update({'picking_type_id': self.picking_type_id})
self.location_src_id = self.picking_type_id.default_location_src_id.id or location.id
self.location_dest_id = self.picking_type_id.default_location_dest_id.id or location.id
+7
View File
@@ -115,6 +115,13 @@ class StockMove(models.Model):
for move in self:
move.priority = move.raw_material_production_id.priority or move.priority or '0'
@api.depends('raw_material_production_id.picking_type_id', 'production_id.picking_type_id')
def _compute_picking_type_id(self):
super()._compute_picking_type_id()
for move in self:
if move.raw_material_production_id or move.production_id:
move.picking_type_id = (move.raw_material_production_id or move.production_id).picking_type_id
@api.depends('raw_material_production_id.lot_producing_id')
def _compute_order_finished_lot_ids(self):
for move in self:
+2
View File
@@ -453,6 +453,8 @@ class TestProcurement(TestMrpCommon):
'product_uom': product_1.uom_id.id,
'product_uom_qty': 10.00,
'procure_method': 'make_to_stock',
'location_id': self.warehouse.lot_stock_id.id,
'location_dest_id': self.ref('stock.stock_location_customers'),
})],
})
pick_output.action_confirm() # should trigger orderpoint to create and confirm 1st MO
+27 -30
View File
@@ -132,7 +132,7 @@ class StockMove(models.Model):
'Propagate cancel and split', default=True,
help='If checked, when this move is cancelled, cancel the linked move too')
delay_alert_date = fields.Datetime('Delay Alert Date', help='Process at this date to be on time', compute="_compute_delay_alert_date", store=True)
picking_type_id = fields.Many2one('stock.picking.type', 'Operation Type', check_company=True)
picking_type_id = fields.Many2one('stock.picking.type', 'Operation Type', compute='_compute_picking_type_id', store=True, check_company=True)
inventory_id = fields.Many2one('stock.inventory', 'Inventory', check_company=True)
move_line_ids = fields.One2many('stock.move.line', 'move_id')
move_line_nosuggest_ids = fields.One2many('stock.move.line', 'move_id', domain=['|', ('product_qty', '=', 0.0), ('qty_done', '!=', 0.0)])
@@ -156,10 +156,10 @@ class StockMove(models.Model):
warehouse_id = fields.Many2one('stock.warehouse', 'Warehouse', help="Technical field depicting the warehouse to consider for the route selection on the next procurement (if any).")
has_tracking = fields.Selection(related='product_id.tracking', string='Product with Tracking')
quantity_done = fields.Float('Quantity Done', compute='_quantity_done_compute', digits='Product Unit of Measure', inverse='_quantity_done_set')
show_operations = fields.Boolean(related='picking_id.picking_type_id.show_operations', readonly=False)
show_operations = fields.Boolean(related='picking_id.picking_type_id.show_operations')
picking_code = fields.Selection(related='picking_id.picking_type_id.code', readonly=True)
show_details_visible = fields.Boolean('Details Visible', compute='_compute_show_details_visible')
show_reserved_availability = fields.Boolean('From Supplier', compute='_compute_show_reserved_availability')
picking_code = fields.Selection(related='picking_id.picking_type_id.code', readonly=True)
product_type = fields.Selection(related='product_id.type', readonly=True)
additional = fields.Boolean("Whether the move was added after the picking's confirmation", default=False)
is_locked = fields.Boolean(compute='_compute_is_locked', readonly=True)
@@ -200,6 +200,12 @@ class StockMove(models.Model):
for move in self:
move.priority = move.picking_id.priority or '0'
@api.depends('picking_id.picking_type_id')
def _compute_picking_type_id(self):
for move in self:
if move.picking_id:
move.picking_type_id = move.picking_id.picking_type_id
@api.depends('picking_id.is_locked')
def _compute_is_locked(self):
for move in self:
@@ -225,10 +231,10 @@ class StockMove(models.Model):
elif len(move.move_line_ids) > 1:
move.show_details_visible = True
else:
move.show_details_visible = (((consignment_enabled and move.picking_id.picking_type_id.code != 'incoming') or
move.show_details_visible = (((consignment_enabled and move.picking_code != 'incoming') or
show_details_visible or move.has_tracking != 'none') and
move._show_details_in_draft() and
move.picking_id.picking_type_id.show_operations is False)
move.show_operations is False)
def _compute_show_reserved_availability(self):
""" This field is only of use in an attrs in the picking view, in order to hide the
@@ -300,7 +306,7 @@ class StockMove(models.Model):
else:
move.delay_alert_date = False
@api.depends('move_line_ids.qty_done', 'move_line_ids.product_uom_id', 'move_line_nosuggest_ids.qty_done', 'picking_type_id')
@api.depends('move_line_ids.qty_done', 'move_line_ids.product_uom_id', 'move_line_nosuggest_ids.qty_done', 'picking_type_id.show_reserved')
def _quantity_done_compute(self):
""" This field represents the sum of the move lines `qty_done`. It allows the user to know
if there is still work to do.
@@ -400,7 +406,7 @@ class StockMove(models.Model):
total_availability = self.env['stock.quant']._get_available_quantity(move.product_id, move.location_id) if move.product_id else 0.0
move.availability = min(move.product_qty, total_availability)
@api.depends('product_id', 'picking_type_id', 'picking_id', 'reserved_availability', 'priority', 'state', 'product_uom_qty', 'location_id')
@api.depends('product_id', 'picking_type_id', 'reserved_availability', 'priority', 'state', 'product_uom_qty', 'location_id')
def _compute_forecast_information(self):
""" Compute forecasted information of the related product by warehouse."""
self.forecast_availability = False
@@ -415,11 +421,10 @@ class StockMove(models.Model):
outgoing_unreserved_moves_per_warehouse = defaultdict(lambda: self.env['stock.move'])
for move in product_moves:
picking_type = move.picking_type_id or move.picking_id.picking_type_id
is_unreserved = move.state in ('waiting', 'confirmed', 'partially_available')
if picking_type.code in self._consuming_picking_types() and is_unreserved:
if move.picking_type_id.code in self._consuming_picking_types() and is_unreserved:
outgoing_unreserved_moves_per_warehouse[warehouse_by_location[move.location_id]] |= move
elif picking_type.code in self._consuming_picking_types():
elif move.picking_type_id.code in self._consuming_picking_types():
move.forecast_availability = move.reserved_availability
for warehouse, moves in outgoing_unreserved_moves_per_warehouse.items():
@@ -497,17 +502,13 @@ class StockMove(models.Model):
move_lines_commands.append((0, 0, move_line_vals))
move.write({'move_line_ids': move_lines_commands})
@api.depends('picking_id', 'picking_type_id', 'date')
@api.depends('picking_type_id', 'date')
def _compute_reservation_date(self):
for move in self:
if move.state in ['draft', 'confirmed', 'waiting', 'partially_available']:
picking_type_id = False
if move.picking_type_id:
picking_type_id = move.picking_type_id
elif move.picking_id:
picking_type_id = move.picking_id.picking_type_id
if picking_type_id and picking_type_id.reservation_method == 'by_date':
move.reservation_date = fields.Date.to_date(move.date) - timedelta(days=move.picking_type_id.reservation_days_before)
if move.state not in ['draft', 'confirmed', 'waiting', 'partially_available']:
continue
if move.picking_type_id and move.picking_type_id.reservation_method == 'by_date':
move.reservation_date = fields.Date.to_date(move.date) - timedelta(days=move.picking_type_id.reservation_days_before)
@api.constrains('product_uom')
def _check_uom(self):
@@ -614,13 +615,11 @@ class StockMove(models.Model):
"""
self.ensure_one()
picking_type_id = self.picking_type_id or self.picking_id.picking_type_id
# If "show suggestions" is not checked on the picking type, we have to filter out the
# reserved move lines. We do this by displaying `move_line_nosuggest_ids`. We use
# different views to display one field or another so that the webclient doesn't have to
# fetch both.
if picking_type_id.show_reserved:
if self.picking_type_id.show_reserved:
view = self.env.ref('stock.view_stock_move_operations')
else:
view = self.env.ref('stock.view_stock_move_nosuggest_operations')
@@ -637,8 +636,8 @@ class StockMove(models.Model):
'context': dict(
self.env.context,
show_owner=self.picking_type_id.code != 'incoming',
show_lots_m2o=self.has_tracking != 'none' and (picking_type_id.use_existing_lots or self.state == 'done' or self.origin_returned_move_id.id), # able to create lots, whatever the value of ` use_create_lots`.
show_lots_text=self.has_tracking != 'none' and picking_type_id.use_create_lots and not picking_type_id.use_existing_lots and self.state != 'done' and not self.origin_returned_move_id.id,
show_lots_m2o=self.has_tracking != 'none' and (self.picking_type_id.use_existing_lots or self.state == 'done' or self.origin_returned_move_id.id), # able to create lots, whatever the value of ` use_create_lots`.
show_lots_text=self.has_tracking != 'none' and self.picking_type_id.use_create_lots and not self.picking_type_id.use_existing_lots and self.state != 'done' and not self.origin_returned_move_id.id,
show_source_location=self.picking_type_id.code != 'incoming',
show_destination_location=self.picking_type_id.code != 'outgoing',
show_package=not self.location_id.usage == 'supplier',
@@ -897,7 +896,7 @@ class StockMove(models.Model):
'warning': {'title': _('Warning'), 'message': _('Existing Serial numbers (%s). Please correct the serial numbers encoded.') % ','.join(used_lots.lot_id.mapped('display_name'))}
}
@api.onchange('move_line_ids', 'move_line_nosuggest_ids')
@api.onchange('move_line_ids', 'move_line_nosuggest_ids', 'picking_type_id')
def onchange_move_line_ids(self):
if not self.picking_type_id.use_create_lots:
# This onchange manages the creation of multiple lot name. We don't
@@ -1141,7 +1140,6 @@ class StockMove(models.Model):
and move.state == 'confirmed'
and (move._should_bypass_reservation()
or move.picking_type_id.reservation_method == 'at_confirm'
or move.picking_id.picking_type_id.reservation_method == 'at_confirm'
or (move.reservation_date and move.reservation_date <= fields.Date.today())))\
._action_assign()
if new_push_moves:
@@ -1169,7 +1167,7 @@ class StockMove(models.Model):
'move_dest_ids': self,
'group_id': group_id,
'route_ids': self.route_ids,
'warehouse_id': self.warehouse_id or self.picking_id.picking_type_id.warehouse_id or self.picking_type_id.warehouse_id,
'warehouse_id': self.warehouse_id or self.picking_type_id.warehouse_id,
'priority': self.priority,
'orderpoint_id': self.orderpoint_id,
}
@@ -1800,8 +1798,7 @@ class StockMove(models.Model):
for move in self:
domains.append([('product_id', '=', move.product_id.id), ('location_id', '=', move.location_dest_id.id)])
static_domain = [('state', 'in', ['confirmed', 'partially_available']), ('procure_method', '=', 'make_to_stock')]
reservation_domain = ['|', '|', ('picking_type_id.reservation_method', '=', 'at_confirm'),
('picking_id.picking_type_id.reservation_method', '=', 'at_confirm'),
('reservation_date', '<=', fields.Date.today())]
reservation_domain = ['|', ('picking_type_id.reservation_method', '=', 'at_confirm'),
('reservation_date', '<=', fields.Date.today())]
moves_to_reserve = self.env['stock.move'].search(expression.AND([static_domain, expression.OR(domains), reservation_domain]))
moves_to_reserve._action_assign()
+1 -1
View File
@@ -59,6 +59,7 @@ class StockMoveLine(models.Model):
picking_code = fields.Selection(related='picking_id.picking_type_id.code', readonly=True)
picking_type_use_create_lots = fields.Boolean(related='picking_id.picking_type_id.use_create_lots', readonly=True)
picking_type_use_existing_lots = fields.Boolean(related='picking_id.picking_type_id.use_existing_lots', readonly=True)
picking_type_entire_packs = fields.Boolean(related='picking_id.picking_type_id.show_entire_packs', readonly=True)
state = fields.Selection(related='move_id.state', store=True, related_sudo=False)
is_initial_demand_editable = fields.Boolean(related='move_id.is_initial_demand_editable', readonly=False)
is_locked = fields.Boolean(related='move_id.is_locked', default=True, readonly=True)
@@ -67,7 +68,6 @@ class StockMoveLine(models.Model):
reference = fields.Char(related='move_id.reference', store=True, related_sudo=False, readonly=False)
tracking = fields.Selection(related='product_id.tracking', readonly=True)
origin = fields.Char(related='move_id.origin', string='Source')
picking_type_entire_packs = fields.Boolean(related='picking_id.picking_type_id.show_entire_packs', readonly=True)
description_picking = fields.Text(string="Description picking")
@api.depends('picking_id.picking_type_id', 'product_id.tracking')
+10 -17
View File
@@ -595,9 +595,11 @@ class Picking(models.Model):
self.location_id = location_id
self.location_dest_id = location_dest_id
(self.move_lines | self.move_ids_without_package).update({
"picking_type_id": self.picking_type_id,
"picking_type_id": self.picking_type_id, # The compute store doesn't work in case of One2many inverse (move_ids_without_package)
"company_id": self.company_id,
})
for move in (self.move_lines | self.move_ids_without_package):
move.description_picking = move.product_id._get_description(move.picking_type_id)
if self.partner_id and self.partner_id.picking_warn:
if self.partner_id.picking_warn == 'no-message' and self.partner_id.parent_id:
@@ -614,6 +616,13 @@ class Picking(models.Model):
'message': partner.picking_warn_msg
}}
@api.onchange('location_id', 'location_dest_id')
def _onchange_locations(self):
(self.move_lines | self.move_ids_without_package).update({
"location_id": self.location_id,
"location_dest_id": self.location_dest_id
})
@api.model
def create(self, vals):
defaults = self.default_get(['name', 'picking_type_id'])
@@ -622,22 +631,6 @@ class Picking(models.Model):
if picking_type.sequence_id:
vals['name'] = picking_type.sequence_id.next_by_id()
# As the on_change in one2many list is WIP, we will overwrite the locations on the stock moves here
# As it is a create the format will be a list of (0, 0, dict)
moves = vals.get('move_lines', []) + vals.get('move_ids_without_package', [])
if moves and vals.get('location_id') and vals.get('location_dest_id'):
for move in moves:
if len(move) == 3 and move[0] == 0:
move[2]['location_id'] = vals['location_id']
move[2]['location_dest_id'] = vals['location_dest_id']
# When creating a new picking, a move can have no `company_id` (create before
# picking type was defined) or a different `company_id` (the picking type was
# changed for an another company picking type after the move was created).
# So, we define the `company_id` in one of these cases.
picking_type = self.env['stock.picking.type'].browse(vals['picking_type_id'])
if 'picking_type_id' not in move[2] or move[2]['picking_type_id'] != picking_type.id:
move[2]['picking_type_id'] = picking_type.id
move[2]['company_id'] = picking_type.company_id.id
# make sure to write `schedule_date` *after* the `stock.move` creation in
# order to get a determinist execution of `_set_scheduled_date`
scheduled_date = vals.pop('scheduled_date', False)
+2 -4
View File
@@ -489,10 +489,8 @@ class ProcurementGroup(models.Model):
('state', 'in', ['confirmed', 'partially_available']),
('product_uom_qty', '!=', 0.0)
]
# TODO: make picking_type_id/reservation method one value on stock.move
moves_domain = expression.AND([moves_domain, ['|', '|', ('picking_type_id.reservation_method', '=', 'at_confirm'),
('picking_id.picking_type_id.reservation_method', '=', 'at_confirm'),
('reservation_date', '<=', fields.Date.today())]])
moves_domain = expression.AND([moves_domain, ['|', ('picking_type_id.reservation_method', '=', 'at_confirm'),
('reservation_date', '<=', fields.Date.today())]])
if company_id:
moves_domain = expression.AND([[('company_id', '=', company_id)], moves_domain])
return moves_domain
+2
View File
@@ -52,6 +52,8 @@ class TestProcRule(TransactionCase):
'product_uom': product.uom_id.id,
'product_uom_qty': 10.00,
'procure_method': 'make_to_order',
'location_id': self.ref('stock.stock_location_output'),
'location_dest_id': self.ref('stock.stock_location_customers'),
})],
}
pick_output = self.env['stock.picking'].create(vals)
+1
View File
@@ -135,6 +135,7 @@
<field name="location_id" invisible="1"/>
<field name="location_dest_id" invisible="1"/>
<field name="picking_id" invisible="1"/>
<field name="picking_type_id" invisible="1"/>
<field name="is_locked" invisible="1"/>
<field name="picking_type_entire_packs" invisible="1"/>
<field name="display_assign_serial" invisible="1"/>