[IMP] stock: reserve on return to select delivered lot
Since return moves are linked to their original move, it's easy to manage reservation in those case. We just don't bypass the reservation when it comes from a reservation outside our stock in order to manage the computation on linked moves and reserve pieces that were not yet return. Also add a return picking type for 2 reasons: - Allow to select existing lot. - Display reserved move line for an incoming picking. Note that this picking type is just a default configuration and could be modify by the user without any issue. closes odoo/odoo#64384 Related: odoo/enterprise#20605 Signed-off-by: Arnold Moyaux <amoyaux@users.noreply.github.com>
This commit is contained in:
@@ -367,8 +367,9 @@ class StockMove(models.Model):
|
||||
return True
|
||||
return False
|
||||
|
||||
def _should_bypass_reservation(self):
|
||||
res = super(StockMove, self)._should_bypass_reservation()
|
||||
def _should_bypass_reservation(self, forced_location=False):
|
||||
res = super(StockMove, self)._should_bypass_reservation(
|
||||
forced_location=forced_location)
|
||||
return bool(res and not self.production_id)
|
||||
|
||||
def _key_assign_picking(self):
|
||||
|
||||
@@ -5,7 +5,6 @@ from . import product
|
||||
from . import res_company
|
||||
from . import res_partner
|
||||
from . import stock_move
|
||||
from . import stock_move_line
|
||||
from . import stock_picking
|
||||
from . import stock_quant
|
||||
from . import stock_rule
|
||||
|
||||
@@ -197,9 +197,9 @@ class StockMove(models.Model):
|
||||
return False
|
||||
return super()._should_bypass_set_qty_producing()
|
||||
|
||||
def _should_bypass_reservation(self):
|
||||
def _should_bypass_reservation(self, forced_location=False):
|
||||
""" If the move is subcontracted then ignore the reservation. """
|
||||
should_bypass_reservation = super(StockMove, self)._should_bypass_reservation()
|
||||
should_bypass_reservation = super()._should_bypass_reservation(forced_location=forced_location)
|
||||
if not should_bypass_reservation and self.is_subcontract:
|
||||
return True
|
||||
return should_bypass_reservation
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import models, fields
|
||||
|
||||
|
||||
class StockMoveLine(models.Model):
|
||||
_inherit = 'stock.move.line'
|
||||
|
||||
def _should_bypass_reservation(self, location):
|
||||
""" If the move line is subcontracted then ignore the reservation. """
|
||||
should_bypass_reservation = super(StockMoveLine, self)._should_bypass_reservation(location)
|
||||
if not should_bypass_reservation and self.move_id.is_subcontract:
|
||||
return True
|
||||
return should_bypass_reservation
|
||||
@@ -1320,13 +1320,16 @@ class StockMove(models.Model):
|
||||
self.env['stock.move.line'].create(self._prepare_move_line_vals(quantity=quantity, reserved_quant=reserved_quant))
|
||||
return taken_quantity
|
||||
|
||||
def _should_bypass_reservation(self):
|
||||
def _should_bypass_reservation(self, forced_location=False):
|
||||
self.ensure_one()
|
||||
return self.location_id.should_bypass_reservation() or self.product_id.type != 'product'
|
||||
location = forced_location or self.location_id
|
||||
return location.should_bypass_reservation() or self.product_id.type != 'product'
|
||||
|
||||
# necessary hook to be able to override move reservation to a restrict lot, owner, pack, location...
|
||||
def _get_available_quantity(self, location_id, lot_id=None, package_id=None, owner_id=None, strict=False, allow_negative=False):
|
||||
self.ensure_one()
|
||||
if location_id.should_bypass_reservation():
|
||||
return self.product_qty
|
||||
return self.env['stock.quant']._get_available_quantity(self.product_id, location_id, lot_id=lot_id, package_id=package_id, owner_id=owner_id, strict=strict, allow_negative=allow_negative)
|
||||
|
||||
def _action_assign(self):
|
||||
@@ -1335,6 +1338,46 @@ class StockMove(models.Model):
|
||||
equal to its `product_qty`. If it is less, the stock move is considered
|
||||
partially available.
|
||||
"""
|
||||
|
||||
def _get_available_move_lines(move):
|
||||
move_lines_in = move.move_orig_ids.filtered(lambda m: m.state == 'done').mapped('move_line_ids')
|
||||
keys_in_groupby = ['location_dest_id', 'lot_id', 'result_package_id', 'owner_id']
|
||||
|
||||
def _keys_in_sorted(ml):
|
||||
return (ml.location_dest_id.id, ml.lot_id.id, ml.result_package_id.id, ml.owner_id.id)
|
||||
|
||||
grouped_move_lines_in = {}
|
||||
for k, g in groupby(sorted(move_lines_in, key=_keys_in_sorted), key=itemgetter(*keys_in_groupby)):
|
||||
qty_done = 0
|
||||
for ml in g:
|
||||
qty_done += ml.product_uom_id._compute_quantity(ml.qty_done, ml.product_id.uom_id)
|
||||
grouped_move_lines_in[k] = qty_done
|
||||
move_lines_out_done = (move.move_orig_ids.mapped('move_dest_ids') - move)\
|
||||
.filtered(lambda m: m.state in ['done'])\
|
||||
.mapped('move_line_ids')
|
||||
# As we defer the write on the stock.move's state at the end of the loop, there
|
||||
# could be moves to consider in what our siblings already took.
|
||||
moves_out_siblings = move.move_orig_ids.mapped('move_dest_ids') - move
|
||||
moves_out_siblings_to_consider = moves_out_siblings & (StockMove.browse(assigned_moves_ids) + StockMove.browse(partially_available_moves_ids))
|
||||
reserved_moves_out_siblings = moves_out_siblings.filtered(lambda m: m.state in ['partially_available', 'assigned'])
|
||||
move_lines_out_reserved = (reserved_moves_out_siblings | moves_out_siblings_to_consider).mapped('move_line_ids')
|
||||
keys_out_groupby = ['location_id', 'lot_id', 'package_id', 'owner_id']
|
||||
|
||||
def _keys_out_sorted(ml):
|
||||
return (ml.location_id.id, ml.lot_id.id, ml.package_id.id, ml.owner_id.id)
|
||||
|
||||
grouped_move_lines_out = {}
|
||||
for k, g in groupby(sorted(move_lines_out_done, key=_keys_out_sorted), key=itemgetter(*keys_out_groupby)):
|
||||
qty_done = 0
|
||||
for ml in g:
|
||||
qty_done += ml.product_uom_id._compute_quantity(ml.qty_done, ml.product_id.uom_id)
|
||||
grouped_move_lines_out[k] = qty_done
|
||||
for k, g in groupby(sorted(move_lines_out_reserved, key=_keys_out_sorted), key=itemgetter(*keys_out_groupby)):
|
||||
grouped_move_lines_out[k] = sum(self.env['stock.move.line'].concat(*list(g)).mapped('product_qty'))
|
||||
available_move_lines = {key: grouped_move_lines_in[key] - grouped_move_lines_out.get(key, 0) for key in grouped_move_lines_in}
|
||||
# pop key if the quantity available amount to 0
|
||||
return dict((k, v) for k, v in available_move_lines.items() if v)
|
||||
|
||||
StockMove = self.env['stock.move']
|
||||
assigned_moves_ids = OrderedSet()
|
||||
partially_available_moves_ids = OrderedSet()
|
||||
@@ -1349,10 +1392,26 @@ class StockMove(models.Model):
|
||||
missing_reserved_quantity = move.product_uom._compute_quantity(missing_reserved_uom_quantity, move.product_id.uom_id, rounding_method='HALF-UP')
|
||||
if move._should_bypass_reservation():
|
||||
# create the move line(s) but do not impact quants
|
||||
if move.product_id.tracking == 'serial' and (move.picking_type_id.use_create_lots or move.picking_type_id.use_existing_lots):
|
||||
if move.move_orig_ids:
|
||||
available_move_lines = _get_available_move_lines(move)
|
||||
for (location_id, lot_id, package_id, owner_id), quantity in available_move_lines.items():
|
||||
qty_added = min(missing_reserved_quantity, quantity)
|
||||
move_line_vals = move._prepare_move_line_vals(qty_added)
|
||||
move_line_vals.update({
|
||||
'location_id': location_id.id,
|
||||
'lot_id': lot_id.id,
|
||||
'lot_name': lot_id.name,
|
||||
'owner_id': owner_id.id,
|
||||
})
|
||||
move_line_vals_list.append(move_line_vals)
|
||||
missing_reserved_quantity -= qty_added
|
||||
if float_is_zero(missing_reserved_quantity, precision_rounding=move.product_id.uom_id.rounding):
|
||||
break
|
||||
|
||||
if missing_reserved_quantity and move.product_id.tracking == 'serial' and (move.picking_type_id.use_create_lots or move.picking_type_id.use_existing_lots):
|
||||
for i in range(0, int(missing_reserved_quantity)):
|
||||
move_line_vals_list.append(move._prepare_move_line_vals(quantity=1))
|
||||
else:
|
||||
elif missing_reserved_quantity:
|
||||
to_update = move.move_line_ids.filtered(lambda ml: ml.product_uom_id == move.product_uom and
|
||||
ml.location_id == move.location_id and
|
||||
ml.location_dest_id == move.location_dest_id and
|
||||
@@ -1361,7 +1420,8 @@ class StockMove(models.Model):
|
||||
not ml.package_id and
|
||||
not ml.owner_id)
|
||||
if to_update:
|
||||
to_update[0].product_uom_qty += missing_reserved_uom_quantity
|
||||
to_update[0].product_uom_qty += move.product_id.uom_id._compute_quantity(
|
||||
missing_reserved_quantity, move.product_uom, rounding_method='HALF-UP')
|
||||
else:
|
||||
move_line_vals_list.append(move._prepare_move_line_vals(quantity=missing_reserved_quantity))
|
||||
assigned_moves_ids.add(move.id)
|
||||
@@ -1394,44 +1454,7 @@ class StockMove(models.Model):
|
||||
# `qty_done` is in `ml.product_uom_id` and, as we will later increase
|
||||
# the reserved quantity on the quants, convert it here in
|
||||
# `product_id.uom_id` (the UOM of the quants is the UOM of the product).
|
||||
move_lines_in = move.move_orig_ids.filtered(lambda m: m.state == 'done').mapped('move_line_ids')
|
||||
keys_in_groupby = ['location_dest_id', 'lot_id', 'result_package_id', 'owner_id']
|
||||
|
||||
def _keys_in_sorted(ml):
|
||||
return (ml.location_dest_id.id, ml.lot_id.id, ml.result_package_id.id, ml.owner_id.id)
|
||||
|
||||
grouped_move_lines_in = {}
|
||||
for k, g in groupby(sorted(move_lines_in, key=_keys_in_sorted), key=itemgetter(*keys_in_groupby)):
|
||||
qty_done = 0
|
||||
for ml in g:
|
||||
qty_done += ml.product_uom_id._compute_quantity(ml.qty_done, ml.product_id.uom_id)
|
||||
grouped_move_lines_in[k] = qty_done
|
||||
move_lines_out_done = (move.move_orig_ids.mapped('move_dest_ids') - move)\
|
||||
.filtered(lambda m: m.state in ['done'])\
|
||||
.mapped('move_line_ids')
|
||||
# As we defer the write on the stock.move's state at the end of the loop, there
|
||||
# could be moves to consider in what our siblings already took.
|
||||
moves_out_siblings = move.move_orig_ids.mapped('move_dest_ids') - move
|
||||
moves_out_siblings_to_consider = moves_out_siblings & (StockMove.browse(assigned_moves_ids) + StockMove.browse(partially_available_moves_ids))
|
||||
reserved_moves_out_siblings = moves_out_siblings.filtered(lambda m: m.state in ['partially_available', 'assigned'])
|
||||
move_lines_out_reserved = (reserved_moves_out_siblings | moves_out_siblings_to_consider).mapped('move_line_ids')
|
||||
keys_out_groupby = ['location_id', 'lot_id', 'package_id', 'owner_id']
|
||||
|
||||
def _keys_out_sorted(ml):
|
||||
return (ml.location_id.id, ml.lot_id.id, ml.package_id.id, ml.owner_id.id)
|
||||
|
||||
grouped_move_lines_out = {}
|
||||
for k, g in groupby(sorted(move_lines_out_done, key=_keys_out_sorted), key=itemgetter(*keys_out_groupby)):
|
||||
qty_done = 0
|
||||
for ml in g:
|
||||
qty_done += ml.product_uom_id._compute_quantity(ml.qty_done, ml.product_id.uom_id)
|
||||
grouped_move_lines_out[k] = qty_done
|
||||
for k, g in groupby(sorted(move_lines_out_reserved, key=_keys_out_sorted), key=itemgetter(*keys_out_groupby)):
|
||||
grouped_move_lines_out[k] = sum(self.env['stock.move.line'].concat(*list(g)).mapped('product_qty'))
|
||||
available_move_lines = {key: grouped_move_lines_in[key] - grouped_move_lines_out.get(key, 0) for key in grouped_move_lines_in.keys()}
|
||||
# pop key if the quantity available amount to 0
|
||||
available_move_lines = dict((k, v) for k, v in available_move_lines.items() if v)
|
||||
|
||||
available_move_lines = _get_available_move_lines(move)
|
||||
if not available_move_lines:
|
||||
continue
|
||||
for move_line in move.move_line_ids.filtered(lambda m: m.product_qty):
|
||||
|
||||
@@ -340,7 +340,7 @@ class StockMoveLine(models.Model):
|
||||
new_product_uom_qty = ml.product_qty
|
||||
|
||||
# Unreserve the old charateristics of the move line.
|
||||
if not ml._should_bypass_reservation(ml.location_id):
|
||||
if not ml.move_id._should_bypass_reservation(ml.location_id):
|
||||
try:
|
||||
Quant._update_reserved_quantity(ml.product_id, ml.location_id, -ml.product_qty, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id, strict=True)
|
||||
except UserError:
|
||||
@@ -351,7 +351,7 @@ class StockMoveLine(models.Model):
|
||||
raise
|
||||
|
||||
# Reserve the maximum available of the new charateristics of the move line.
|
||||
if not ml._should_bypass_reservation(updates.get('location_id', ml.location_id)):
|
||||
if not ml.move_id._should_bypass_reservation(updates.get('location_id', ml.location_id)):
|
||||
reserved_qty = 0
|
||||
try:
|
||||
q = Quant._update_reserved_quantity(ml.product_id, updates.get('location_id', ml.location_id), new_product_uom_qty, lot_id=updates.get('lot_id', ml.lot_id),
|
||||
@@ -395,7 +395,7 @@ class StockMoveLine(models.Model):
|
||||
result_package_id = updates.get('result_package_id', ml.result_package_id)
|
||||
owner_id = updates.get('owner_id', ml.owner_id)
|
||||
quantity = ml.move_id.product_uom._compute_quantity(qty_done, ml.move_id.product_id.uom_id, rounding_method='HALF-UP')
|
||||
if not ml._should_bypass_reservation(location_id):
|
||||
if not ml.move_id._should_bypass_reservation(location_id):
|
||||
ml._free_reservation(product_id, location_id, quantity, lot_id=lot_id, package_id=package_id, owner_id=owner_id)
|
||||
if not float_is_zero(quantity, precision_digits=precision):
|
||||
available_qty, in_date = Quant._update_available_quantity(product_id, location_id, -quantity, lot_id=lot_id, package_id=package_id, owner_id=owner_id)
|
||||
@@ -406,7 +406,7 @@ class StockMoveLine(models.Model):
|
||||
taken_from_untracked_qty = min(untracked_qty, abs(available_qty))
|
||||
Quant._update_available_quantity(product_id, location_id, -taken_from_untracked_qty, lot_id=False, package_id=package_id, owner_id=owner_id)
|
||||
Quant._update_available_quantity(product_id, location_id, taken_from_untracked_qty, lot_id=lot_id, package_id=package_id, owner_id=owner_id)
|
||||
if not ml._should_bypass_reservation(location_id):
|
||||
if not ml.move_id._should_bypass_reservation(location_id):
|
||||
ml._free_reservation(ml.product_id, location_id, untracked_qty, lot_id=False, package_id=package_id, owner_id=owner_id)
|
||||
Quant._update_available_quantity(product_id, location_dest_id, quantity, lot_id=lot_id, package_id=result_package_id, owner_id=owner_id, in_date=in_date)
|
||||
|
||||
@@ -451,7 +451,7 @@ class StockMoveLine(models.Model):
|
||||
precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
|
||||
for ml in self:
|
||||
# Unlinking a move line should unreserve.
|
||||
if ml.product_id.type == 'product' and not ml._should_bypass_reservation(ml.location_id) and not float_is_zero(ml.product_qty, precision_digits=precision):
|
||||
if ml.product_id.type == 'product' and not ml.move_id._should_bypass_reservation(ml.location_id) and not float_is_zero(ml.product_qty, precision_digits=precision):
|
||||
try:
|
||||
self.env['stock.quant']._update_reserved_quantity(ml.product_id, ml.location_id, -ml.product_qty, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id, strict=True)
|
||||
except UserError:
|
||||
@@ -553,12 +553,12 @@ class StockMoveLine(models.Model):
|
||||
rounding = ml.product_uom_id.rounding
|
||||
|
||||
# if this move line is force assigned, unreserve elsewhere if needed
|
||||
if not ml._should_bypass_reservation(ml.location_id) and float_compare(ml.qty_done, ml.product_uom_qty, precision_rounding=rounding) > 0:
|
||||
if not ml.move_id._should_bypass_reservation(ml.location_id) and float_compare(ml.qty_done, ml.product_uom_qty, precision_rounding=rounding) > 0:
|
||||
qty_done_product_uom = ml.product_uom_id._compute_quantity(ml.qty_done, ml.product_id.uom_id, rounding_method='HALF-UP')
|
||||
extra_qty = qty_done_product_uom - ml.product_qty
|
||||
ml._free_reservation(ml.product_id, ml.location_id, extra_qty, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id, ml_ids_to_ignore=ml_ids_to_ignore)
|
||||
# unreserve what's been reserved
|
||||
if not ml._should_bypass_reservation(ml.location_id) and ml.product_id.type == 'product' and ml.product_qty:
|
||||
if not ml.move_id._should_bypass_reservation(ml.location_id) and ml.product_id.type == 'product' and ml.product_qty:
|
||||
try:
|
||||
Quant._update_reserved_quantity(ml.product_id, ml.location_id, -ml.product_qty, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id, strict=True)
|
||||
except UserError:
|
||||
@@ -702,10 +702,6 @@ class StockMoveLine(models.Model):
|
||||
self.env['stock.move.line'].browse(to_unlink_candidate_ids).unlink()
|
||||
move_to_recompute_state._recompute_state()
|
||||
|
||||
def _should_bypass_reservation(self, location):
|
||||
self.ensure_one()
|
||||
return location.should_bypass_reservation() or self.product_id.type != 'product'
|
||||
|
||||
def _get_aggregated_product_quantities(self, **kwargs):
|
||||
""" Returns a dictionary of products (key = id+name+description+uom) and corresponding values of interest.
|
||||
|
||||
|
||||
@@ -76,6 +76,7 @@ class Warehouse(models.Model):
|
||||
out_type_id = fields.Many2one('stock.picking.type', 'Out Type', check_company=True)
|
||||
in_type_id = fields.Many2one('stock.picking.type', 'In Type', check_company=True)
|
||||
int_type_id = fields.Many2one('stock.picking.type', 'Internal Type', check_company=True)
|
||||
return_type_id = fields.Many2one('stock.picking.type', 'Return Type', check_company=True)
|
||||
crossdock_route_id = fields.Many2one('stock.location.route', 'Crossdock Route', ondelete='restrict')
|
||||
reception_route_id = fields.Many2one('stock.location.route', 'Receipt Route', ondelete='restrict')
|
||||
delivery_route_id = fields.Many2one('stock.location.route', 'Delivery Route', ondelete='restrict')
|
||||
@@ -332,7 +333,7 @@ class Warehouse(models.Model):
|
||||
warehouse_data[picking_type] = PickingType.create(values).id
|
||||
|
||||
if 'out_type_id' in warehouse_data:
|
||||
PickingType.browse(warehouse_data['out_type_id']).write({'return_picking_type_id': warehouse_data.get('in_type_id', False)})
|
||||
PickingType.browse(warehouse_data['out_type_id']).write({'return_picking_type_id': warehouse_data.get('return_type_id', False)})
|
||||
if 'in_type_id' in warehouse_data:
|
||||
PickingType.browse(warehouse_data['in_type_id']).write({'return_picking_type_id': warehouse_data.get('out_type_id', False)})
|
||||
return warehouse_data
|
||||
@@ -915,6 +916,10 @@ class Warehouse(models.Model):
|
||||
'int_type_id': {
|
||||
'barcode': self.code.replace(" ", "").upper() + "-INTERNAL",
|
||||
},
|
||||
'return_type_id': {
|
||||
'default_location_dest_id': output_loc.id,
|
||||
'barcode': self.code.replace(" ", "").upper() + "-RETURNS",
|
||||
},
|
||||
}
|
||||
|
||||
def _get_picking_type_create_values(self, max_sequence):
|
||||
@@ -976,6 +981,16 @@ class Warehouse(models.Model):
|
||||
'sequence': max_sequence + 2,
|
||||
'sequence_code': 'INT',
|
||||
'company_id': self.company_id.id,
|
||||
}, 'return_type_id': {
|
||||
'name': _('Returns'),
|
||||
'code': 'incoming',
|
||||
'use_create_lots': False,
|
||||
'use_existing_lots': True,
|
||||
'default_location_src_id': False,
|
||||
'sequence': max_sequence + 6,
|
||||
'show_reserved': True,
|
||||
'sequence_code': 'IN',
|
||||
'company_id': self.company_id.id,
|
||||
},
|
||||
}, max_sequence + 6
|
||||
|
||||
@@ -1009,6 +1024,11 @@ class Warehouse(models.Model):
|
||||
'prefix': self.code + '/INT/', 'padding': 5,
|
||||
'company_id': self.company_id.id,
|
||||
},
|
||||
'return_type_id': {
|
||||
'name': self.name + ' ' + _('Sequence return'),
|
||||
'prefix': self.code + '/RET/', 'padding': 5,
|
||||
'company_id': self.company_id.id,
|
||||
},
|
||||
}
|
||||
|
||||
def _format_rulename(self, from_loc, dest_loc, suffix):
|
||||
|
||||
@@ -740,6 +740,75 @@ class TestPickShip(TestStockCommon):
|
||||
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.productA, return_location), 5.0)
|
||||
self.assertEqual(len(self.env['stock.quant'].search([('product_id', '=', self.productA.id), ('quantity', '!=', 0)])), 2)
|
||||
|
||||
def test_return_lot(self):
|
||||
""" With two distinct deliveries for the same product tracked by lot, ensure that the
|
||||
return of the second picking suggest the lot from the picking returned.
|
||||
"""
|
||||
self.productA.tracking = 'lot'
|
||||
lot1 = self.env['stock.production.lot'].create({
|
||||
'name': 'lot1',
|
||||
'product_id': self.productA.id,
|
||||
'company_id': self.env.company.id,
|
||||
})
|
||||
lot2 = self.env['stock.production.lot'].create({
|
||||
'name': 'lot2',
|
||||
'product_id': self.productA.id,
|
||||
'company_id': self.env.company.id,
|
||||
})
|
||||
lot3 = self.env['stock.production.lot'].create({
|
||||
'name': 'lot3',
|
||||
'product_id': self.productA.id,
|
||||
'company_id': self.env.company.id,
|
||||
})
|
||||
stock_location = self.env['stock.location'].browse(self.stock_location)
|
||||
|
||||
self.env['stock.quant']._update_available_quantity(self.productA, stock_location, 7.0, lot_id=lot1)
|
||||
self.env['stock.quant']._update_available_quantity(self.productA, stock_location, 7.0, lot_id=lot2)
|
||||
self.env['stock.quant']._update_available_quantity(self.productA, stock_location, 7.0, lot_id=lot3)
|
||||
|
||||
picking_pick, picking_client = self.create_pick_ship()
|
||||
picking_pick.action_confirm()
|
||||
picking_pick.action_assign()
|
||||
for move_line in picking_pick.move_line_ids:
|
||||
move_line.qty_done = move_line.product_uom_qty
|
||||
picking_pick._action_done()
|
||||
picking_client.action_confirm()
|
||||
picking_client.action_assign()
|
||||
for move_line in picking_client.move_line_ids:
|
||||
move_line.qty_done = move_line.product_uom_qty
|
||||
picking_client._action_done()
|
||||
|
||||
picking_pick, picking_client = self.create_pick_ship()
|
||||
picking_pick.action_confirm()
|
||||
picking_pick.action_assign()
|
||||
for move_line in picking_pick.move_line_ids:
|
||||
move_line.qty_done = move_line.product_uom_qty
|
||||
picking_pick._action_done()
|
||||
picking_client.action_confirm()
|
||||
picking_client.action_assign()
|
||||
for move_line in picking_client.move_line_ids:
|
||||
move_line.qty_done = move_line.product_uom_qty
|
||||
picking_client._action_done()
|
||||
|
||||
# Following FIFO strategy, First picking should have empty lot1 and took 3 of lot2.
|
||||
# So the second picking contains 4 lot2 and 6 lot3
|
||||
self.assertEqual(picking_client.move_line_ids[0].lot_id, lot2)
|
||||
self.assertEqual(picking_client.move_line_ids[0].qty_done, 4)
|
||||
self.assertEqual(picking_client.move_line_ids[1].lot_id, lot3)
|
||||
self.assertEqual(picking_client.move_line_ids[1].qty_done, 6)
|
||||
|
||||
stock_return_picking_form = Form(self.env['stock.return.picking'].with_context(
|
||||
active_ids=picking_client.ids, active_id=picking_client.ids[0], active_model='stock.picking'))
|
||||
stock_return_picking = stock_return_picking_form.save()
|
||||
stock_return_picking.product_return_moves.quantity = 10.0
|
||||
return_pick = self.env['stock.picking'].browse(stock_return_picking.create_returns()['res_id'])
|
||||
|
||||
self.assertEqual(len(return_pick.move_line_ids), 2)
|
||||
self.assertEqual(return_pick.move_line_ids[0].lot_id, lot2)
|
||||
self.assertEqual(return_pick.move_line_ids[0].product_uom_qty, 4)
|
||||
self.assertEqual(return_pick.move_line_ids[1].lot_id, lot3)
|
||||
self.assertEqual(return_pick.move_line_ids[1].product_uom_qty, 6)
|
||||
self.assertEqual(return_pick.picking_type_id, picking_client.location_id.warehouse_id.return_type_id)
|
||||
|
||||
class TestSinglePicking(TestStockCommon):
|
||||
def test_backorder_1(self):
|
||||
|
||||
@@ -218,7 +218,7 @@ class TestPacking(TestPackingCommon):
|
||||
})
|
||||
picking = self.env['stock.picking'].create({
|
||||
'picking_type_id': self.warehouse.in_type_id.id,
|
||||
'location_id': self.stock_location.id,
|
||||
'location_id': self.customer_location.id,
|
||||
'location_dest_id': self.stock_location.id,
|
||||
'state': 'draft',
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user