diff --git a/addons/point_of_sale/models/pos_order.py b/addons/point_of_sale/models/pos_order.py index b5915e31742..306b8f08c93 100644 --- a/addons/point_of_sale/models/pos_order.py +++ b/addons/point_of_sale/models/pos_order.py @@ -1091,13 +1091,11 @@ class PosOrderLine(models.Model): pickings_to_confirm.action_confirm() tracked_lines = order.lines.filtered(lambda l: l.product_id.tracking != 'none') lines_by_tracked_product = groupby(sorted(tracked_lines, key=lambda l: l.product_id.id), key=lambda l: l.product_id.id) - mls_to_unlink = self.env['stock.move.line'] - for dummy, lines in lines_by_tracked_product: + for product_id, lines in lines_by_tracked_product: lines = self.env['pos.order.line'].concat(*lines) - moves = pickings_to_confirm.move_ids.filtered(lambda m: m.product_id in tracked_lines.product_id) - mls_to_unlink |= moves.move_line_ids + moves = pickings_to_confirm.move_ids.filtered(lambda m: m.product_id.id == product_id) + moves.move_line_ids.unlink() moves._add_mls_related_to_order(lines, are_qties_done=False) - mls_to_unlink.unlink() return True def _is_product_storable_fifo_avco(self): diff --git a/addons/point_of_sale/models/stock_picking.py b/addons/point_of_sale/models/stock_picking.py index 8bdc0735564..559b4329575 100644 --- a/addons/point_of_sale/models/stock_picking.py +++ b/addons/point_of_sale/models/stock_picking.py @@ -199,42 +199,58 @@ class StockMove(models.Model): existing_lots = moves_remaining._create_production_lots_for_pos_order(related_order_lines) move_lines_to_create = [] mls_qties = [] - for move in moves_remaining: - for line in lines_data[move.product_id.id]['order_lines']: - sum_of_lots = 0 - for lot in line.pack_lot_ids.filtered(lambda l: l.lot_name): - if line.product_id.tracking == 'serial': - qty = 1 - else: - qty = abs(line.qty) - ml_vals = dict(move._prepare_move_line_vals()) - if existing_lots: - existing_lot = existing_lots.filtered_domain([('product_id', '=', line.product_id.id), ('name', '=', lot.lot_name)]) - quant = self.env['stock.quant'] - if existing_lot: - quant = self.env['stock.quant'].search( - [('lot_id', '=', existing_lot.id), ('quantity', '>', '0.0'), ('location_id', 'child_of', move.location_id.id)], - order='id desc', - limit=1 - ) - ml_vals.update({ - 'lot_id': existing_lot.id, - 'location_id': quant.location_id.id or move.location_id.id - }) - else: - ml_vals.update({'lot_name': lot.lot_name}) - move_lines_to_create.append(ml_vals) - mls_qties.append(qty) - sum_of_lots += qty - if abs(line.qty) != sum_of_lots: - difference_qty = abs(line.qty) - sum_of_lots - ml_vals = move._prepare_move_line_vals() - if line.product_id.tracking == 'serial': - move_lines_to_create.extend([ml_vals for i in range(int(difference_qty))]) - mls_qties.extend([1]*int(difference_qty)) - else: + if are_qties_done: + for move in moves_remaining: + for line in lines_data[move.product_id.id]['order_lines']: + sum_of_lots = 0 + for lot in line.pack_lot_ids.filtered(lambda l: l.lot_name): + if line.product_id.tracking == 'serial': + qty = 1 + else: + qty = abs(line.qty) + ml_vals = dict(move._prepare_move_line_vals()) + if existing_lots: + existing_lot = existing_lots.filtered_domain([('product_id', '=', line.product_id.id), ('name', '=', lot.lot_name)]) + quant = self.env['stock.quant'] + if existing_lot: + quant = self.env['stock.quant'].search( + [('lot_id', '=', existing_lot.id), ('quantity', '>', '0.0'), ('location_id', 'child_of', move.location_id.id)], + order='id desc', + limit=1 + ) + ml_vals.update({ + 'lot_id': existing_lot.id, + 'location_id': quant.location_id.id or move.location_id.id + }) + else: + ml_vals.update({'lot_name': lot.lot_name}) move_lines_to_create.append(ml_vals) - mls_qties.append(difference_qty) - move_lines = self.env['stock.move.line'].create(move_lines_to_create) - for move_line, qty in zip(move_lines, mls_qties): - move_line.write({qty_fname: qty}) + mls_qties.append(qty) + sum_of_lots += qty + if abs(line.qty) != sum_of_lots: + difference_qty = abs(line.qty) - sum_of_lots + ml_vals = move._prepare_move_line_vals() + if line.product_id.tracking == 'serial': + move_lines_to_create.extend([ml_vals for i in range(int(difference_qty))]) + mls_qties.extend([1]*int(difference_qty)) + else: + move_lines_to_create.append(ml_vals) + mls_qties.append(difference_qty) + move_lines = self.env['stock.move.line'].create(move_lines_to_create) + for move_line, qty in zip(move_lines, mls_qties): + move_line.write({qty_fname: qty}) + else: + for move in moves_remaining: + for line in lines_data[move.product_id.id]['order_lines']: + for lot in line.pack_lot_ids.filtered(lambda l: l.lot_name): + if line.product_id.tracking == 'serial': + qty = 1 + else: + qty = abs(line.qty) + if existing_lots: + existing_lot = existing_lots.filtered_domain([('product_id', '=', line.product_id.id), ('name', '=', lot.lot_name)]) + if existing_lot: + available_quantity = move._get_available_quantity(move.location_id, lot_id=existing_lot, strict=True) + if not float_is_zero(available_quantity, precision_rounding=line.product_id.uom_id.rounding): + move._update_reserved_quantity(qty, min(qty, available_quantity), move.location_id, existing_lot) + continue diff --git a/addons/point_of_sale/tests/test_point_of_sale_flow.py b/addons/point_of_sale/tests/test_point_of_sale_flow.py index 2a8ae618084..0a2a49df634 100644 --- a/addons/point_of_sale/tests/test_point_of_sale_flow.py +++ b/addons/point_of_sale/tests/test_point_of_sale_flow.py @@ -1014,80 +1014,6 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon): diff_line = pos_session.move_id.line_ids.filtered(lambda line: line.name == 'Difference at closing PoS session') self.assertAlmostEqual(diff_line.credit, 5.0, msg="Missing amount of 5.0") - def test_order_and_ship_later(self): - """ In case the product is tracked and the "Ship Later" feature is enabled, this test ensures - that the lots defined on the POS order are the same that the ones on the SMLs of the associated - picking""" - self.env['stock.picking.type'].search([('code', '=', 'outgoing')]).write({ - 'use_create_lots': True, - 'use_existing_lots': True, - }) - - tracked_product = self.env['product.product'].create({ - 'name': 'SuperProduct Tracked', - 'type': 'product', - 'tracking': 'lot', - 'available_in_pos': True, - }) - - lot01, lot02 = self.env['stock.lot'].create([{ - 'name': name, - 'product_id': tracked_product.id, - 'company_id': self.env.company.id, - } for name in ['Lot01', 'Lot02']]) - stock_location = self.company_data['default_warehouse'].lot_stock_id - self.env['stock.quant']._update_available_quantity(tracked_product, stock_location, 10, lot_id=lot01) - self.env['stock.quant']._update_available_quantity(tracked_product, stock_location, 10, lot_id=lot02) - - self.pos_config.ship_later = True - self.pos_config.open_session_cb() - - # Create a POS order with: - # 1 x Lot01 - # 1 x Lot02 - # 1 x Lot03 (-> nonexistent lot!) - pos_order = self.PosOrder.create({ - 'company_id': self.env.company.id, - 'session_id': self.pos_config.current_session_id.id, - 'pricelist_id': self.partner1.property_product_pricelist.id, - 'partner_id': self.partner1.id, - 'lines': [(0, 0, { - 'name': "OL/0001", - 'product_id': tracked_product.id, - 'price_unit': 5, - 'discount': 0.0, - 'qty': 1.0, - 'tax_ids': [(6, 0, [])], - 'price_subtotal': 5, - 'price_subtotal_incl': 5, - 'pack_lot_ids': [[0, 0, {'lot_name': lot_name}]], - }) for lot_name in [lot01.name, lot02.name, 'Lot03']], - 'amount_tax': 10, - 'amount_total': 10, - 'amount_paid': 0, - 'amount_return': 0, - 'to_ship': True, - }) - - context_make_payment = { - "active_ids": [pos_order.id], - "active_id": pos_order.id, - } - pos_make_payment = self.PosMakePayment.with_context(context_make_payment).create({ - 'amount': 10, - }) - pos_make_payment.with_context(active_id=pos_order.id).check() - - picking = pos_order.picking_ids - picking_mls = picking.move_line_ids - lot01_ml = picking_mls.filtered(lambda ml: ml.lot_id == lot01) - lot02_ml = picking_mls.filtered(lambda ml: ml.lot_id == lot02) - lot03_ml = picking_mls - lot01_ml - lot02_ml - self.assertEqual(lot01_ml.reserved_qty, 1) - self.assertEqual(lot02_ml.reserved_qty, 1) - self.assertEqual(lot03_ml.reserved_qty, 1) - self.assertEqual(lot03_ml.lot_id.name, "Lot03") - def test_order_multi_step_route(self): """ Test that orders in sessions with "Ship Later" enabled and "Specific Route" set to a multi-step (2/3) route can be validated. This config implies multiple picking types @@ -1099,6 +1025,24 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon): 'tracking': 'lot', 'available_in_pos': True }) + tracked_product_2 = self.env['product.product'].create({ + 'name': 'SuperProduct Tracked 2', + 'type': 'product', + 'tracking': 'lot', + 'available_in_pos': True + }) + tracked_product_2_lot = self.env['stock.lot'].create({ + 'name': '80085', + 'product_id': tracked_product_2.id, + 'company_id': self.env.company.id, + }) + stock_location = self.company_data['default_warehouse'].lot_stock_id + self.env['stock.quant'].with_context(inventory_mode=True).create({ + 'product_id': tracked_product_2.id, + 'inventory_quantity': 1, + 'location_id': stock_location.id, + 'lot_id': tracked_product_2_lot.id + }).action_apply_inventory() warehouse_id = self.company_data['default_warehouse'] warehouse_id.delivery_steps = 'pick_ship' @@ -1122,6 +1066,20 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon): 'qty': 1.0, 'price_subtotal': untax, 'price_subtotal_incl': untax + tax, + 'pack_lot_ids': [ + [0, 0, {'lot_name': '80085'}], + ] + }), + (0, 0, { + 'name': "OL/0002", + 'product_id': tracked_product_2.id, + 'price_unit': 1.15, + 'qty': 1.0, + 'price_subtotal': untax, + 'price_subtotal_incl': untax + tax, + 'pack_lot_ids': [ + [0, 0, {'lot_name': '80085'}], + ] })], 'amount_tax': tax, 'amount_total': untax+tax, @@ -1139,9 +1097,10 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon): }) context_payment = {'active_id': pos_order.id} pos_make_payment.with_context(context_payment).check() - pickings = pos_order.picking_ids - picking_mls = pickings.move_line_ids + picking_mls_no_stock = pickings.move_line_ids.filtered(lambda l: l.product_id.id == tracked_product.id) + picking_mls_stock = pickings.move_line_ids.filtered(lambda l: l.product_id.id == tracked_product_2.id) self.assertEqual(pos_order.state, 'paid') - self.assertEqual(len(picking_mls), 2) + self.assertEqual(len(picking_mls_no_stock), 0) + self.assertEqual(len(picking_mls_stock), 1) self.assertEqual(len(pickings.picking_type_id), 2)