diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 12f3217f6e2..36c665c6cbb 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -3412,11 +3412,11 @@ class stock_pack_operation(osv.osv): res = {} for ops in self.browse(cr, uid, ids, context=context): res[ops.id] = 0 - if ops.package_id: + if ops.package_id and not ops.product_id: #dont try to compute the remaining quantity for packages because it's not relevant (a package could include different products). #should use _get_remaining_prod_quantities instead continue - elif ops.product_id: + else: qty = ops.product_qty if ops.product_uom_id: qty = uom_obj._compute_qty(cr, uid, ops.product_uom_id.id, ops.product_qty, ops.product_id.uom_id.id) @@ -3501,24 +3501,74 @@ class stock_pack_operation(osv.osv): if qty_to_assign <= 0: break + def _check_quants_reserved(ops): + if ops.package_id and not ops.product_id: + for quant in quant_obj.browse(cr, uid, package_obj.get_content(cr, uid, [ops.package_id.id]), context=context): + if quant.reservation_id and quant.reservation_id.id in [x.id for x in ops.picking_id.move_lines] and (not quants_done.get(quant.id)): + #Entire packages means entire quants from those packages + if not quants_done.get(quant.id): + quants_done[quant.id] = 0 + link_obj.create(cr, uid, {'move_id': quant.reservation_id.id, 'operation_id': ops.id, 'qty': quant.qty}, context=context) + else: + qty = uom_obj._compute_qty(cr, uid, ops.product_uom_id.id, ops.product_qty, ops.product_id.uom_id.id) + #Check moves with same product + for move in [x for x in ops.picking_id.move_lines if ops.product_id.id == x.product_id.id]: + for quant in move.reserved_quant_ids: + if not qty > 0: + break + if ops.package_id: + flag = quant.package_id and bool(package_obj.search(cr, uid, [('id', 'child_of', [ops.package_id.id]), ('id', '=', quant.package_id.id)], context=context)) or False + else: + flag = not quant.package_id.id + flag = flag and ((ops.lot_id and ops.lot_id.id == quant.lot_id.id) or not ops.lot_id) + flag = flag and (ops.owner_id.id == quant.owner_id.id) + if flag: + quant_qty = quant.qty + if quants_done.get(quant.id): + if quants_done[quant.id] == 0: + continue + quant_qty = quants_done[quant.id] + if quant_qty > qty: + qty_todo = qty + quants_done[quant.id] = quant_qty - qty + else: + qty_todo = quant_qty + quants_done[quant.id] = 0 + qty -= qty_todo + link_obj.create(cr, uid, {'move_id': quant.reservation_id.id, 'operation_id': ops.id, 'qty': qty_todo}, context=context) + link_obj = self.pool.get('stock.move.operation.link') uom_obj = self.pool.get('product.uom') package_obj = self.pool.get('stock.quant.package') - if op_ids: - #sort moves in order to process first the ones that have already reserved quants - ops = self.browse(cr, uid, op_ids[0], context=context) - sorted_moves = ops.picking_id.move_lines - sorted_moves.sort(key = lambda x: x.product_qty - x.reserved_availability) - for op in self.browse(cr, uid, op_ids, context=context): + quant_obj = self.pool.get('stock.quant') + quants_done = {} + + operations = self.browse(cr, uid, op_ids, context=context) + operations.sort(key=lambda x: ((x.package_id and not x.product_id) and -4 or 0) + (x.package_id and -2 or 0) + (x.lot_id and -1 or 0)) + sorted_moves = [] + for op in operations: + if not sorted_moves: + #sort moves in order to process first the ones that have already reserved quants + sorted_moves = op.picking_id.move_lines + sorted_moves.sort(key=lambda x: x.product_qty - x.reserved_availability) + to_unlink_ids = [x.id for x in op.linked_move_operation_ids] if to_unlink_ids: link_obj.unlink(cr, uid, to_unlink_ids, context=context) + _check_quants_reserved(op) + + for op in operations: + op.refresh() if op.product_id: - normalized_qty = uom_obj._compute_qty(cr, uid, op.product_uom_id.id, op.product_qty, op.product_id.uom_id.id) - _create_link_for_product(op.product_id.id, normalized_qty) + #TODO: Remaining qty: UoM conversions are done twice + normalized_qty = uom_obj._compute_qty(cr, uid, op.product_uom_id.id, op.remaining_qty, op.product_id.uom_id.id) + if normalized_qty > 0: + _create_link_for_product(op.product_id.id, normalized_qty) elif op.package_id: - for product_id, qty in package_obj._get_all_products_quantities(cr, uid, op.package_id.id, context=context).items(): - _create_link_for_product(product_id, qty) + prod_quants = self._get_remaining_prod_quantities(cr, uid, op, context=context) + for product_id, qty in prod_quants.items(): + if qty > 0: + _create_link_for_product(product_id, qty) def process_packaging(self, cr, uid, operation, quants, context=None): ''' Process the packaging of a given operation, after the quants have been moved. If there was not enough quants found diff --git a/addons/stock/test/packing.yml b/addons/stock/test/packing.yml index 4978d44750e..c85e2b0d769 100644 --- a/addons/stock/test/packing.yml +++ b/addons/stock/test/packing.yml @@ -139,7 +139,11 @@ backorder_ids = self.search(cr, uid, [('backorder_id', '=', ref('delivery_order1'))], context=context) assert backorder_ids, "Backorder should have been created" self.do_prepare_partial(cr, uid, backorder_ids, context=context) - stock_pack = self.pool.get('stock.pack.operation').create(cr, uid, {'picking_id': backorder_ids[0], 'product_qty': 20}) + picking = self.browse(cr, uid, backorder_ids[0]) + assert len(picking.pack_operation_ids) == 2, "Wrong number of pack operation" + for pack_op in picking.pack_operation_ids: + assert pack_op.product_qty == 1, "Wrong quantity in pack operation (%s found instead of 1)" % (pack_op.product_qty) + assert pack_op.package_id.name in ('Pallet 2', 'Pallet 3'), "Wrong pallet info in pack operation (%s found)" % (pack_op.package_id.name) self.action_assign(cr, uid, backorder_ids, context=context) self.do_transfer(cr, uid, backorder_ids, context=context) - diff --git a/addons/stock/test/packingneg.yml b/addons/stock/test/packingneg.yml index 5f26c2f59ac..d42cbfa52e2 100644 --- a/addons/stock/test/packingneg.yml +++ b/addons/stock/test/packingneg.yml @@ -93,10 +93,11 @@ !python {model: stock.picking}: | self.do_transfer(cr, uid, [ref('delivery_order_neg')], context=context) - - Check the quants that you have 120 pieces pallet 1 in customers, -20 pieces pallet 2 in stock and 120 and 20 with customers and 20/30 in stock, 10 in customers from pallet 3 + Check the quants that you have 120 pieces pallet 1 in customers, -20 pieces pallet 2 in stock, 120 + 20 pieces 2 in customer with lot, and a total quantity of 50 in stock from pallet 3 (should be 20+30, as it has been split by reservation), finally 10 in customers from pallet 3 - !python {model: stock.quant}: | reco_id = self.search(cr ,uid , [('product_id','=',ref('product_neg'))], context=context) + pallet_3_stock_qty = 0 for rec in self.browse(cr, uid, reco_id, context=context): if rec.package_id.name == 'Palneg 1' and rec.location_id.id == ref('stock_location_customers'): assert rec.qty == 120, "Should have 120 pieces on pallet 1" @@ -106,11 +107,12 @@ elif rec.lot_id.name == 'Lot neg' and rec.location_id.id == ref('stock_location_customers'): assert ((rec.qty == 20 or rec.qty == 120) and not rec.package_id), "Should have 140 pieces (120+20) in customer location from pallet 2 and lot A" elif rec.package_id.name == 'Palneg 3' and rec.location_id.id == ref('stock_location_stock'): - assert rec.qty == 30 or rec.qty == 20, "Should have 30 and 20 pieces in stock on pallet 3" + pallet_3_stock_qty += rec.qty elif not rec.package_id and not rec.lot_id and rec.location_id.id == ref('stock_location_customers'): assert rec.qty == 10, "Should have 10 pieces in customer location from pallet 3" else: assert False, "Unrecognized quant" + assert pallet_3_stock_qty == 50, "Should have 50 pieces in stock on pallet 3" - Create a picking for reconciling the negative quant -