forked from Mapan/odoo17e
844 lines
40 KiB
Python
844 lines
40 KiB
Python
# -*- encoding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from odoo import Command
|
|
from odoo.tests import tagged
|
|
from odoo.tests.common import Form
|
|
from odoo.addons.stock_barcode.tests.test_barcode_client_action import TestBarcodeClientAction
|
|
|
|
|
|
@tagged('post_install', '-at_install')
|
|
class TestBarcodeBatchClientAction(TestBarcodeClientAction):
|
|
def setUp(self):
|
|
super().setUp()
|
|
|
|
self.clean_access_rights()
|
|
grp_lot = self.env.ref('stock.group_production_lot')
|
|
grp_multi_loc = self.env.ref('stock.group_stock_multi_locations')
|
|
grp_pack = self.env.ref('stock.group_tracking_lot')
|
|
self.env.user.write({'groups_id': [(4, grp_multi_loc.id, 0)]})
|
|
self.env.user.write({'groups_id': [(4, grp_lot.id, 0)]})
|
|
self.env.user.write({'groups_id': [(4, grp_pack.id, 0)]})
|
|
|
|
# Create some products
|
|
self.product3 = self.env['product.product'].create({
|
|
'name': 'product3',
|
|
'type': 'product',
|
|
'categ_id': self.env.ref('product.product_category_all').id,
|
|
'barcode': 'product3',
|
|
})
|
|
self.product4 = self.env['product.product'].create({
|
|
'name': 'product4',
|
|
'type': 'product',
|
|
'categ_id': self.env.ref('product.product_category_all').id,
|
|
'barcode': 'product4',
|
|
})
|
|
self.product5 = self.env['product.product'].create({
|
|
'name': 'product5',
|
|
'type': 'product',
|
|
'categ_id': self.env.ref('product.product_category_all').id,
|
|
'barcode': 'product5',
|
|
})
|
|
|
|
# Create locations dedicated to package
|
|
self.shelf5 = self.env['stock.location'].create({
|
|
'name': 'Section 5',
|
|
'location_id': self.stock_location.id,
|
|
'barcode': 'shelf5',
|
|
})
|
|
|
|
# Create some packages
|
|
self.package1 = self.env['stock.quant.package'].create({'name': 'p5pack01'})
|
|
self.package2 = self.env['stock.quant.package'].create({'name': 'p5pack02'})
|
|
|
|
# Create some quants (for deliveries)
|
|
Quant = self.env['stock.quant']
|
|
quants = Quant.with_context(inventory_mode=True).create({
|
|
'product_id': self.product1.id,
|
|
'location_id': self.shelf1.id,
|
|
'inventory_quantity': 2
|
|
})
|
|
quants |= Quant.with_context(inventory_mode=True).create({
|
|
'product_id': self.product2.id,
|
|
'location_id': self.shelf2.id,
|
|
'inventory_quantity': 1
|
|
})
|
|
quants |= Quant.with_context(inventory_mode=True).create({
|
|
'product_id': self.product2.id,
|
|
'location_id': self.shelf3.id,
|
|
'inventory_quantity': 1
|
|
})
|
|
quants |= Quant.with_context(inventory_mode=True).create({
|
|
'product_id': self.product3.id,
|
|
'location_id': self.shelf3.id,
|
|
'inventory_quantity': 2
|
|
})
|
|
quants |= Quant.with_context(inventory_mode=True).create({
|
|
'product_id': self.product4.id,
|
|
'location_id': self.shelf1.id,
|
|
'inventory_quantity': 1
|
|
})
|
|
quants |= Quant.with_context(inventory_mode=True).create({
|
|
'product_id': self.product4.id,
|
|
'location_id': self.shelf4.id,
|
|
'inventory_quantity': 1
|
|
})
|
|
quants |= Quant.with_context(inventory_mode=True).create({
|
|
'product_id': self.product5.id,
|
|
'location_id': self.shelf5.id,
|
|
'package_id': self.package1.id,
|
|
'inventory_quantity': 4,
|
|
})
|
|
quants |= Quant.with_context(inventory_mode=True).create({
|
|
'product_id': self.product5.id,
|
|
'location_id': self.shelf5.id,
|
|
'inventory_quantity': 4,
|
|
})
|
|
quants.action_apply_inventory()
|
|
|
|
# Create a first receipt for 2 products.
|
|
picking_form = Form(self.env['stock.picking'])
|
|
picking_form.picking_type_id = self.picking_type_in
|
|
with picking_form.move_ids_without_package.new() as move:
|
|
move.product_id = self.product1
|
|
move.product_uom_qty = 1
|
|
with picking_form.move_ids_without_package.new() as move:
|
|
move.product_id = self.productserial1
|
|
move.product_uom_qty = 2
|
|
self.picking_receipt_1 = picking_form.save()
|
|
self.picking_receipt_1.action_confirm()
|
|
|
|
# Create a second receipt for 2 products.
|
|
picking_form = Form(self.env['stock.picking'])
|
|
picking_form.picking_type_id = self.picking_type_in
|
|
with picking_form.move_ids_without_package.new() as move:
|
|
move.product_id = self.product1
|
|
move.product_uom_qty = 3
|
|
with picking_form.move_ids_without_package.new() as move:
|
|
move.product_id = self.productlot1
|
|
move.product_uom_qty = 8
|
|
self.picking_receipt_2 = picking_form.save()
|
|
self.picking_receipt_2.action_confirm()
|
|
|
|
# Changes name of pickings to be able to track them on the tour
|
|
self.picking_receipt_1.name = 'picking_receipt_1'
|
|
self.picking_receipt_2.name = 'picking_receipt_2'
|
|
|
|
# Create a first delivery for 2 products.
|
|
picking_form = Form(self.env['stock.picking'])
|
|
picking_form.picking_type_id = self.picking_type_out
|
|
with picking_form.move_ids_without_package.new() as move:
|
|
move.product_id = self.product1
|
|
move.product_uom_qty = 1
|
|
with picking_form.move_ids_without_package.new() as move:
|
|
move.product_id = self.product2
|
|
move.product_uom_qty = 2
|
|
self.picking_delivery_1 = picking_form.save()
|
|
self.picking_delivery_1.action_confirm()
|
|
self.picking_delivery_1.action_assign()
|
|
|
|
# Create a second delivery for 3 products.
|
|
picking_form = Form(self.env['stock.picking'])
|
|
picking_form.picking_type_id = self.picking_type_out
|
|
with picking_form.move_ids_without_package.new() as move:
|
|
move.product_id = self.product1
|
|
move.product_uom_qty = 1
|
|
with picking_form.move_ids_without_package.new() as move:
|
|
move.product_id = self.product3
|
|
move.product_uom_qty = 2
|
|
with picking_form.move_ids_without_package.new() as move:
|
|
move.product_id = self.product4
|
|
move.product_uom_qty = 2
|
|
self.picking_delivery_2 = picking_form.save()
|
|
self.picking_delivery_2.action_confirm()
|
|
self.picking_delivery_2.action_assign()
|
|
|
|
# Create a delivery dedicated to package testing.
|
|
picking_form = Form(self.env['stock.picking'])
|
|
picking_form.picking_type_id = self.picking_type_out
|
|
with picking_form.move_ids_without_package.new() as move:
|
|
move.product_id = self.product5
|
|
move.product_uom_qty = 8
|
|
self.picking_delivery_package = picking_form.save()
|
|
self.picking_delivery_package.action_confirm()
|
|
self.picking_delivery_package.action_assign()
|
|
|
|
# Create another quant with package after reservation to test scan
|
|
# unexpected package in the Barcode App.
|
|
Quant.with_context(inventory_mode=True).create({
|
|
'product_id': self.product5.id,
|
|
'location_id': self.shelf5.id,
|
|
'package_id': self.package2.id,
|
|
'inventory_quantity': 4,
|
|
}).action_apply_inventory()
|
|
|
|
# Changes name of pickings to be able to track them on the tour
|
|
self.picking_delivery_1.name = 'picking_delivery_1'
|
|
self.picking_delivery_2.name = 'picking_delivery_2'
|
|
self.picking_delivery_package.name = 'picking_delivery_package'
|
|
|
|
def _get_batch_client_action_url(self, batch_id):
|
|
action = self.env["ir.actions.actions"]._for_xml_id("stock_barcode_picking_batch.stock_barcode_picking_batch_client_action")
|
|
return '/web#action=%s&active_id=%s' % (action['id'], batch_id)
|
|
|
|
def test_barcode_batch_receipt_1(self):
|
|
""" Create a batch picking with 3 receipts, then open the batch in
|
|
barcode app and scan each product, SN or LN one by one.
|
|
"""
|
|
# Creates an additional receipt for the product tracked by lots.
|
|
picking_form = Form(self.env['stock.picking'])
|
|
picking_form.picking_type_id = self.picking_type_in
|
|
with picking_form.move_ids_without_package.new() as move:
|
|
move.product_id = self.productlot1
|
|
move.product_uom_qty = 4
|
|
picking_receipt_3 = picking_form.save()
|
|
picking_receipt_3.action_confirm()
|
|
picking_receipt_3.name = 'picking_receipt_3'
|
|
picking_receipt_3.user_id = False
|
|
|
|
batch_form = Form(self.env['stock.picking.batch'])
|
|
batch_form.picking_ids.add(self.picking_receipt_1)
|
|
batch_form.picking_ids.add(self.picking_receipt_2)
|
|
batch_form.picking_ids.add(picking_receipt_3)
|
|
batch_receipt = batch_form.save()
|
|
self.assertEqual(
|
|
batch_receipt.picking_type_id.id,
|
|
self.picking_receipt_1.picking_type_id.id,
|
|
"Batch picking must take the picking type of its sub-pickings"
|
|
)
|
|
batch_receipt.action_confirm()
|
|
self.assertEqual(len(batch_receipt.move_ids), 5)
|
|
self.assertEqual(len(batch_receipt.move_line_ids), 6)
|
|
self.assertEqual(batch_receipt.user_id.id, False)
|
|
self.assertEqual(picking_receipt_3.user_id.id, False)
|
|
|
|
url = self._get_batch_client_action_url(batch_receipt.id)
|
|
self.start_tour(url, 'test_barcode_batch_receipt_1', login='admin', timeout=180)
|
|
# Checks user was assign on the batch and its pickings.
|
|
self.assertEqual(batch_receipt.user_id.id, self.env.user.id)
|
|
self.assertEqual(picking_receipt_3.user_id.id, self.env.user.id)
|
|
|
|
def test_barcode_batch_delivery_1(self):
|
|
""" Create a batch picking with 2 deliveries (split into 3 locations),
|
|
then open the batch in barcode app and scan each product.
|
|
Change the location when all products of the page has been scanned.
|
|
"""
|
|
batch_form = Form(self.env['stock.picking.batch'])
|
|
# Adds two quantities for product tracked by SN.
|
|
sn1 = self.env['stock.lot'].create({'name': 'sn1', 'product_id': self.productserial1.id, 'company_id': self.env.company.id})
|
|
sn2 = self.env['stock.lot'].create({'name': 'sn2', 'product_id': self.productserial1.id, 'company_id': self.env.company.id})
|
|
self.env['stock.quant'].with_context(inventory_mode=True).create({
|
|
'product_id': self.productserial1.id,
|
|
'inventory_quantity': 1,
|
|
'lot_id': sn1.id,
|
|
'location_id': self.shelf1.id,
|
|
}).action_apply_inventory()
|
|
self.env['stock.quant'].with_context(inventory_mode=True).create({
|
|
'product_id': self.productserial1.id,
|
|
'inventory_quantity': 1,
|
|
'lot_id': sn2.id,
|
|
'location_id': self.shelf1.id,
|
|
}).action_apply_inventory()
|
|
|
|
# Creates a delivery for a product tracked by SN, the purpose is to
|
|
# reserve sn1 and scan sn2 instead.
|
|
picking_form = Form(self.env['stock.picking'])
|
|
picking_form.picking_type_id = self.picking_type_out
|
|
with picking_form.move_ids_without_package.new() as move:
|
|
move.product_id = self.productserial1
|
|
move.product_uom_qty = 1
|
|
picking_delivery_sn = picking_form.save()
|
|
picking_delivery_sn.name = 'picking_delivery_sn'
|
|
picking_delivery_sn.action_confirm()
|
|
picking_delivery_sn.action_assign()
|
|
|
|
batch_form.picking_ids.add(self.picking_delivery_1)
|
|
batch_form.picking_ids.add(self.picking_delivery_2)
|
|
batch_form.picking_ids.add(self.picking_delivery_package)
|
|
batch_form.picking_ids.add(picking_delivery_sn)
|
|
batch_delivery = batch_form.save()
|
|
self.assertEqual(
|
|
batch_delivery.picking_type_id.id,
|
|
self.picking_delivery_1.picking_type_id.id,
|
|
"Batch picking must take the picking type of its sub-pickings"
|
|
)
|
|
batch_delivery.action_confirm()
|
|
self.assertEqual(len(batch_delivery.move_ids), 7)
|
|
self.assertEqual(len(batch_delivery.move_line_ids), 10)
|
|
packaged_move_lines = batch_delivery.move_line_ids.filtered(lambda ml: ml.product_id.id == self.product5.id)
|
|
self.assertEqual(len(packaged_move_lines), 2)
|
|
self.assertEqual(packaged_move_lines.package_id.id, self.package1.id)
|
|
|
|
url = self._get_batch_client_action_url(batch_delivery.id)
|
|
self.start_tour(url, 'test_barcode_batch_delivery_1', login='admin', timeout=180)
|
|
|
|
def test_barcode_batch_delivery_2_move_entire_package(self):
|
|
""" Creates a batch picking with 2 deliveries while the delivery picking type use the "move
|
|
entire package" setting and check lines are correctly displayed as package line when moving
|
|
the entire package, or as usual barcode line in other cases.
|
|
"""
|
|
self.clean_access_rights()
|
|
grp_pack = self.env.ref('stock.group_tracking_lot')
|
|
self.env.user.write({'groups_id': [(4, grp_pack.id, 0)]})
|
|
self.picking_type_out.show_entire_packs = True
|
|
|
|
# Creates two packages and adds some quantities on hand.
|
|
pack1 = self.env['stock.quant.package'].create({'name': 'pack1'})
|
|
pack2 = self.env['stock.quant.package'].create({'name': 'pack2'})
|
|
self.env['stock.quant'].with_context(inventory_mode=True).create({
|
|
'product_id': self.product1.id,
|
|
'inventory_quantity': 10,
|
|
'package_id': pack1.id,
|
|
'location_id': self.stock_location.id,
|
|
}).action_apply_inventory()
|
|
self.env['stock.quant'].with_context(inventory_mode=True).create({
|
|
'product_id': self.product2.id,
|
|
'inventory_quantity': 10,
|
|
'package_id': pack2.id,
|
|
'location_id': self.stock_location.id,
|
|
}).action_apply_inventory()
|
|
self.env['stock.quant'].with_context(inventory_mode=True).create({
|
|
'product_id': self.product3.id,
|
|
'inventory_quantity': 10,
|
|
'package_id': pack1.id,
|
|
'location_id': self.stock_location.id,
|
|
}).action_apply_inventory()
|
|
|
|
# Creates two deliveries.
|
|
picking_form = Form(self.env['stock.picking'])
|
|
picking_form.picking_type_id = self.picking_type_out
|
|
with picking_form.move_ids_without_package.new() as move:
|
|
move.product_id = self.product1
|
|
move.product_uom_qty = 10
|
|
with picking_form.move_ids_without_package.new() as move:
|
|
move.product_id = self.product2
|
|
move.product_uom_qty = 5
|
|
with picking_form.move_ids_without_package.new() as move:
|
|
move.product_id = self.product3
|
|
move.product_uom_qty = 10
|
|
delivery_1 = picking_form.save()
|
|
delivery_1.action_confirm()
|
|
delivery_1.action_assign()
|
|
|
|
picking_form = Form(self.env['stock.picking'])
|
|
picking_form.picking_type_id = self.picking_type_out
|
|
with picking_form.move_ids_without_package.new() as move:
|
|
move.product_id = self.product2
|
|
move.product_uom_qty = 5
|
|
delivery_2 = picking_form.save()
|
|
delivery_2.action_confirm()
|
|
delivery_2.action_assign()
|
|
|
|
# Changes name of pickings to be able to track them on the tour.
|
|
delivery_1.name = 'delivery_1'
|
|
delivery_2.name = 'delivery_2'
|
|
|
|
# Creates and confirms the batch.
|
|
batch_form = Form(self.env['stock.picking.batch'])
|
|
batch_form.picking_ids.add(delivery_1)
|
|
batch_form.picking_ids.add(delivery_2)
|
|
batch_delivery = batch_form.save()
|
|
batch_delivery.action_confirm()
|
|
|
|
url = self._get_batch_client_action_url(batch_delivery.id)
|
|
self.start_tour(url, 'test_barcode_batch_delivery_2_move_entire_package', login='admin', timeout=180)
|
|
|
|
def test_barcode_batch_scan_lots(self):
|
|
""" Checks while scanning lots for a tracked product, the currently selected line must be
|
|
completed before changing the line, even if the scanned lot is planned for another picking
|
|
or another picking has an empty line for this product.
|
|
"""
|
|
self.env.ref('base.group_user').implied_ids += self.env.ref('stock.group_production_lot')
|
|
self.clean_access_rights()
|
|
grp_pack = self.env.ref('stock.group_tracking_lot')
|
|
self.env.user.write({'groups_id': [(4, grp_pack.id, 0)]})
|
|
common_vals = {'product_id': self.productlot1.id, 'company_id': self.env.company.id}
|
|
lot1 = self.env['stock.lot'].create({**common_vals, 'name': 'lot1'})
|
|
lot2 = self.env['stock.lot'].create({**common_vals, 'name': 'lot2'})
|
|
self.env['stock.quant']._update_available_quantity(self.productlot1, self.stock_location, 3, lot_id=lot1)
|
|
self.env['stock.quant']._update_available_quantity(self.productlot1, self.stock_location, 3, lot_id=lot2)
|
|
|
|
# Create two receipts and batch them (test for unreserved lots.)
|
|
receipt_form = Form(self.env['stock.picking'])
|
|
receipt_form.picking_type_id = self.picking_type_in
|
|
with receipt_form.move_ids_without_package.new() as move:
|
|
move.product_id = self.productlot1
|
|
move.product_uom_qty = 4
|
|
receipt_1 = receipt_form.save()
|
|
receipt_1.action_confirm()
|
|
receipt_1.name = "receipt_1"
|
|
|
|
receipt_2 = receipt_1.copy()
|
|
receipt_2.action_confirm
|
|
receipt_2.name = "receipt_2"
|
|
|
|
batch_form = Form(self.env['stock.picking.batch'])
|
|
batch_form.picking_ids.add(receipt_1)
|
|
batch_form.picking_ids.add(receipt_2)
|
|
batch_receipt = batch_form.save()
|
|
batch_receipt.action_confirm()
|
|
batch_receipt.name = "test_barcode_batch_scan_lots - receipt"
|
|
|
|
# Create two deliveries and batch them (test for reserved lots.)
|
|
delivery_form = Form(self.env['stock.picking'])
|
|
delivery_form.picking_type_id = self.picking_type_out
|
|
with delivery_form.move_ids_without_package.new() as move:
|
|
move.product_id = self.productlot1
|
|
move.product_uom_qty = 3
|
|
delivery_1 = delivery_form.save()
|
|
delivery_1.action_confirm()
|
|
delivery_1.name = "delivery_1"
|
|
|
|
delivery_2 = delivery_1.copy()
|
|
delivery_2.action_confirm()
|
|
delivery_2.name = "delivery_2"
|
|
|
|
batch_form = Form(self.env['stock.picking.batch'])
|
|
batch_form.picking_ids.add(delivery_1)
|
|
batch_form.picking_ids.add(delivery_2)
|
|
batch_delivery = batch_form.save()
|
|
batch_delivery.action_confirm()
|
|
batch_delivery.name = "test_barcode_batch_scan_lots - delivery"
|
|
|
|
# Process both of them (first the receipt then the delivery.)
|
|
action = self.env.ref('stock_barcode.stock_barcode_action_main_menu')
|
|
self.start_tour(f'/web#action={action.id}', 'test_barcode_batch_scan_lots', login='admin', timeout=180)
|
|
|
|
# Checks pickings move lines values.
|
|
lot1, lot2, lot3 = (batch_receipt | batch_delivery).move_line_ids.lot_id.sorted('name')
|
|
self.assertRecordValues(receipt_1.move_line_ids, [
|
|
{'lot_id': lot1.id, 'lot_name': 'lot1', 'quantity': 3, 'picked': True},
|
|
{'lot_id': lot2.id, 'lot_name': 'lot2', 'quantity': 1, 'picked': True},
|
|
])
|
|
self.assertRecordValues(receipt_2.move_line_ids, [
|
|
{'lot_id': lot2.id, 'lot_name': 'lot2', 'quantity': 2, 'picked': True},
|
|
{'lot_id': lot3.id, 'lot_name': 'lot3', 'quantity': 2, 'picked': True},
|
|
])
|
|
self.assertRecordValues(delivery_1.move_line_ids, [
|
|
{'lot_id': lot2.id, 'quantity': 2, 'picked': True},
|
|
{'lot_id': lot3.id, 'quantity': 1, 'picked': True},
|
|
])
|
|
self.assertRecordValues(delivery_2.move_line_ids, [
|
|
{'lot_id': lot2.id, 'quantity': 1, 'picked': True},
|
|
{'lot_id': lot1.id, 'quantity': 1, 'picked': True},
|
|
{'lot_id': lot3.id, 'quantity': 1, 'picked': True},
|
|
])
|
|
|
|
def test_barcode_batch_scan_other_reserved_lost(self):
|
|
""" Checks that scanning a lot won't erase the reserved one if the
|
|
scanned one is reserved too."""
|
|
self.env.ref('base.group_user').implied_ids += self.env.ref('stock.group_production_lot')
|
|
self.clean_access_rights()
|
|
grp_pack = self.env.ref('stock.group_tracking_lot')
|
|
self.env.user.write({'groups_id': [(4, grp_pack.id, 0)]})
|
|
# Create some lots and add quantity in stock for them.
|
|
common_vals = {'product_id': self.productlot1.id, 'company_id': self.env.company.id}
|
|
lots = self.env['stock.lot'].create([{**common_vals, 'name': f'lot{i}'} for i in range(1, 4)])
|
|
for lot in lots:
|
|
self.env['stock.quant']._update_available_quantity(self.productlot1, self.stock_location, 3, lot_id=lot)
|
|
# Create a delivery and batch it alone (a batch with a single picking is enough for this test's purpose.)
|
|
delivery = self.env['stock.picking'].create({
|
|
'name': "delivery with lots",
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.customer_location.id,
|
|
'picking_type_id': self.picking_type_out.id,
|
|
'move_ids': [Command.create({
|
|
'name': 'productlot1 delivery move',
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.customer_location.id,
|
|
'product_id': self.productlot1.id,
|
|
'product_uom': self.uom_unit.id,
|
|
'product_uom_qty': 9,
|
|
})],
|
|
})
|
|
batch = self.env['stock.picking.batch'].create({
|
|
'name': "delivery batch",
|
|
'picking_ids': [Command.link(delivery.id)],
|
|
})
|
|
batch.action_confirm()
|
|
action = self.env.ref('stock_barcode.stock_barcode_action_main_menu')
|
|
self.start_tour(f'/web#action={action.id}', 'test_barcode_batch_scan_other_reserved_lost', login='admin', timeout=180)
|
|
|
|
def test_put_in_pack_from_multiple_pages(self):
|
|
""" A batch picking of 2 internal pickings where prod1 and prod2 are reserved in shelf1 and shelf2,
|
|
processing all these products and then hitting put in pack should move them all in the new pack.
|
|
|
|
This is a copy of the stock_barcode `test_put_in_pack_from_multiple_pages` test with exception that
|
|
there are 2 internal pickings containing the 2 products. We expect the same UI and behavior with the
|
|
batch's `put_in_pack` button as we do with a single internal transfer so we re-use the same exact tour.
|
|
Note that batch `put_in_pack` logic is not the same as it is for pickings.
|
|
"""
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.shelf1, 1)
|
|
self.env['stock.quant']._update_available_quantity(self.product2, self.shelf1, 1)
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.shelf2, 1)
|
|
self.env['stock.quant']._update_available_quantity(self.product2, self.shelf2, 1)
|
|
|
|
# Adapts the setting to scan only the source location.
|
|
self.picking_type_internal.restrict_scan_dest_location = 'no'
|
|
self.picking_type_internal.restrict_scan_source_location = 'mandatory'
|
|
|
|
internal_picking = self.env['stock.picking'].create({
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.stock_location.id,
|
|
'picking_type_id': self.picking_type_internal.id,
|
|
})
|
|
self.env['stock.move'].create({
|
|
'name': 'test_put_in_pack_from_multiple_pages',
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.stock_location.id,
|
|
'product_id': self.product1.id,
|
|
'product_uom': self.uom_unit.id,
|
|
'product_uom_qty': 2,
|
|
'picking_id': internal_picking.id,
|
|
})
|
|
internal_picking2 = self.env['stock.picking'].create({
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.stock_location.id,
|
|
'picking_type_id': self.picking_type_internal.id,
|
|
})
|
|
self.env['stock.move'].create({
|
|
'name': 'test_put_in_pack_from_multiple_pages',
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.stock_location.id,
|
|
'product_id': self.product2.id,
|
|
'product_uom': self.uom_unit.id,
|
|
'product_uom_qty': 2,
|
|
'picking_id': internal_picking2.id,
|
|
})
|
|
|
|
internal_picking.action_confirm()
|
|
internal_picking.action_assign()
|
|
internal_picking2.action_confirm()
|
|
internal_picking2.action_assign()
|
|
|
|
batch_form = Form(self.env['stock.picking.batch'])
|
|
batch_form.picking_ids.add(internal_picking)
|
|
batch_form.picking_ids.add(internal_picking2)
|
|
batch_internal = batch_form.save()
|
|
|
|
batch_internal.action_confirm()
|
|
self.assertEqual(len(batch_internal.move_ids), 2)
|
|
|
|
url = self._get_batch_client_action_url(batch_internal.id)
|
|
|
|
self.start_tour(url, 'test_put_in_pack_from_multiple_pages', login='admin', timeout=180)
|
|
|
|
pack = self.env['stock.quant.package'].search([('location_id', '=', self.stock_location.id)], limit=1)
|
|
self.assertEqual(len(pack.quant_ids), 2)
|
|
self.assertEqual(sum(pack.quant_ids.mapped('quantity')), 4)
|
|
|
|
def test_put_in_pack_before_dest(self):
|
|
""" A batch picking of 2 internal pickings where prod1 and prod2 are reserved in shelf1 and shelf3,
|
|
and have different move destinations. Processing the products and then put in pack should open a choose
|
|
destination wizard which will help make sure the package ends up where its expected.
|
|
|
|
This is a copy of the stock_barcode `test_put_in_pack_before_dest` test with exception that
|
|
there are 2 internal pickings containing the 2 products. We expect the same UI and behavior with the
|
|
batch's `put_in_pack` button as we do with a single internal transfer so we re-use the same exact tour.
|
|
For some reason the order of the move lines in the destination wizard is different, so we swap the expected
|
|
destination in this test (since it doesn't matter).
|
|
"""
|
|
self.picking_type_internal.active = True
|
|
self.picking_type_internal.restrict_scan_dest_location = 'mandatory'
|
|
self.picking_type_internal.restrict_scan_source_location = 'mandatory'
|
|
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.shelf1, 1)
|
|
self.env['stock.quant']._update_available_quantity(self.product2, self.shelf3, 1)
|
|
|
|
internal_picking = self.env['stock.picking'].create({
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.stock_location.id,
|
|
'picking_type_id': self.picking_type_internal.id,
|
|
})
|
|
self.env['stock.move'].create({
|
|
'name': 'test_put_in_pack_before_dest',
|
|
'location_id': self.shelf1.id,
|
|
'location_dest_id': self.shelf2.id,
|
|
'product_id': self.product1.id,
|
|
'product_uom': self.uom_unit.id,
|
|
'product_uom_qty': 1,
|
|
'picking_id': internal_picking.id,
|
|
})
|
|
internal_picking2 = self.env['stock.picking'].create({
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.stock_location.id,
|
|
'picking_type_id': self.picking_type_internal.id,
|
|
})
|
|
self.env['stock.move'].create({
|
|
'name': 'test_put_in_pack_before_dest',
|
|
'location_id': self.shelf3.id,
|
|
'location_dest_id': self.shelf4.id,
|
|
'product_id': self.product2.id,
|
|
'product_uom': self.uom_unit.id,
|
|
'product_uom_qty': 1,
|
|
'picking_id': internal_picking2.id,
|
|
})
|
|
|
|
internal_picking.action_confirm()
|
|
internal_picking.action_assign()
|
|
internal_picking2.action_confirm()
|
|
internal_picking2.action_assign()
|
|
|
|
batch_form = Form(self.env['stock.picking.batch'])
|
|
batch_form.picking_ids.add(internal_picking)
|
|
batch_form.picking_ids.add(internal_picking2)
|
|
batch_internal = batch_form.save()
|
|
|
|
batch_internal.action_confirm()
|
|
self.assertEqual(len(batch_internal.move_ids), 2)
|
|
|
|
url = self._get_batch_client_action_url(batch_internal.id)
|
|
|
|
self.start_tour(url, 'test_put_in_pack_before_dest', login='admin', timeout=180)
|
|
pack = self.env['stock.quant.package'].search([('location_id', '=', self.shelf2.id)], limit=1)
|
|
self.assertEqual(len(pack.quant_ids), 2)
|
|
self.assertEqual(pack.location_id, self.shelf2)
|
|
|
|
def test_put_in_pack_scan_suggested_package(self):
|
|
""" Create two deliveries with a line from two different locations each.
|
|
Then, group them in a batch and process the batch in barcode.
|
|
Put first picking line in a package and the second one in another package,
|
|
then change the location page and scan the suggested packaged for each picking lines.
|
|
"""
|
|
self.clean_access_rights()
|
|
grp_multi_loc = self.env.ref('stock.group_stock_multi_locations')
|
|
self.env.user.write({'groups_id': [(4, grp_multi_loc.id, 0)]})
|
|
grp_pack = self.env.ref('stock.group_tracking_lot')
|
|
self.env.user.write({'groups_id': [(4, grp_pack.id, 0)]})
|
|
self.picking_type_out.barcode_validation_all_product_packed = True
|
|
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.shelf1, 2)
|
|
self.env['stock.quant']._update_available_quantity(self.product2, self.shelf2, 2)
|
|
self.env['stock.quant']._update_available_quantity(self.product3, self.shelf1, 1)
|
|
|
|
# Creates a first delivery with 2 move lines: one from Section 1 and one from Section 2.
|
|
delivery_form = Form(self.env['stock.picking'])
|
|
delivery_form.picking_type_id = self.picking_type_out
|
|
with delivery_form.move_ids_without_package.new() as move:
|
|
move.product_id = self.product1
|
|
move.product_uom_qty = 1
|
|
with delivery_form.move_ids_without_package.new() as move:
|
|
move.product_id = self.product2
|
|
move.product_uom_qty = 1
|
|
with delivery_form.move_ids_without_package.new() as move:
|
|
move.product_id = self.product3
|
|
move.product_uom_qty = 1
|
|
delivery_1 = delivery_form.save()
|
|
delivery_1.action_confirm()
|
|
delivery_1.action_assign()
|
|
|
|
# Creates a second delivery (same idea than the first one).
|
|
|
|
delivery_form = Form(self.env['stock.picking'])
|
|
delivery_form.picking_type_id = self.picking_type_out
|
|
with delivery_form.move_ids_without_package.new() as move:
|
|
move.product_id = self.product1
|
|
move.product_uom_qty = 1
|
|
with delivery_form.move_ids_without_package.new() as move:
|
|
move.product_id = self.product2
|
|
move.product_uom_qty = 1
|
|
delivery_2 = delivery_form.save()
|
|
delivery_2.action_confirm()
|
|
delivery_2.action_assign()
|
|
|
|
# Changes name of pickings to be able to track them on the tour
|
|
delivery_1.name = 'test_delivery_1'
|
|
delivery_2.name = 'test_delivery_2'
|
|
|
|
batch_form = Form(self.env['stock.picking.batch'])
|
|
batch_form.picking_ids.add(delivery_1)
|
|
batch_form.picking_ids.add(delivery_2)
|
|
batch_delivery = batch_form.save()
|
|
batch_delivery.action_confirm()
|
|
self.assertEqual(len(batch_delivery.move_ids), 5)
|
|
self.assertEqual(len(batch_delivery.move_line_ids), 5)
|
|
|
|
# Resets package sequence to be sure we'll have the attended packages name.
|
|
seq = self.env['ir.sequence'].search([('code', '=', 'stock.quant.package')])
|
|
seq.number_next_actual = 1
|
|
|
|
url = self._get_batch_client_action_url(batch_delivery.id)
|
|
self.start_tour(url, 'test_put_in_pack_scan_suggested_package', login='admin', timeout=180)
|
|
|
|
self.assertEqual(batch_delivery.state, 'done')
|
|
self.assertEqual(len(batch_delivery.move_line_ids), 5)
|
|
for move_line in delivery_1.move_line_ids:
|
|
self.assertEqual(move_line.result_package_id.name, 'PACK0000001')
|
|
for move_line in delivery_2.move_line_ids:
|
|
self.assertEqual(move_line.result_package_id.name, 'PACK0000002')
|
|
|
|
def test_batch_create(self):
|
|
""" Create a batch picking via barcode app from scratch """
|
|
|
|
action_id = self.env.ref('stock_barcode.stock_barcode_action_main_menu')
|
|
url = "/web#action=" + str(action_id.id)
|
|
|
|
self.start_tour(url, 'test_batch_create', login='admin', timeout=180)
|
|
self.assertEqual(self.picking_delivery_1.batch_id, self.picking_delivery_2.batch_id)
|
|
batch_delivery = self.picking_delivery_1.batch_id
|
|
self.assertEqual(len(batch_delivery.move_ids), 5)
|
|
self.assertEqual(len(batch_delivery.move_line_ids), 7)
|
|
|
|
def test_pack_and_same_product_several_sml(self):
|
|
"""
|
|
A batch with two transfers, source and destination are the same. The
|
|
first picking contains 3 x P1 and 25 x P2, the second one 7 x P1 and
|
|
30 x P2. The 10 P1 are in a package PK1. The situation is more
|
|
complicated for the second product: there are 100 P2 in a package PK2.
|
|
When processing the batch, if the user scans the package PK1, it should
|
|
update all move lines related to P1. Then, when scanning PK2, it should
|
|
also update the move lines related to P2 and a new line should be
|
|
created for the surplus (45 x P2).
|
|
"""
|
|
self.clean_access_rights()
|
|
grp_pack = self.env.ref('stock.group_tracking_lot')
|
|
self.env.user.write({'groups_id': [(4, grp_pack.id)]})
|
|
|
|
package02 = self.package.copy({'name': 'P00002'})
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.shelf1, 10, package_id=self.package)
|
|
self.env['stock.quant']._update_available_quantity(self.product2, self.shelf1, 100, package_id=package02)
|
|
|
|
# Two pickings,
|
|
# one with 3 x P1 and 25 x P2
|
|
# one with 7 x P1 and 30 x P2
|
|
pickings = self.env['stock.picking'].create([{
|
|
'location_id': self.shelf1.id,
|
|
'location_dest_id': self.shelf2.id,
|
|
'picking_type_id': self.picking_type_internal.id,
|
|
'move_ids': [(0, 0, {
|
|
'name': 'test_put_in_pack_from_multiple_pages',
|
|
'location_id': self.shelf1.id,
|
|
'location_dest_id': self.shelf2.id,
|
|
'product_id': product.id,
|
|
'product_uom': self.uom_unit.id,
|
|
'product_uom_qty': qty,
|
|
}) for product, qty in picking_lines]
|
|
} for picking_lines in [
|
|
[(self.product1, 3), (self.product2, 25)],
|
|
[(self.product1, 7), (self.product2, 30)],
|
|
]])
|
|
pickings.action_confirm()
|
|
pickings.action_assign()
|
|
|
|
batch_form = Form(self.env['stock.picking.batch'])
|
|
batch_form.picking_ids.add(pickings[0])
|
|
batch_form.picking_ids.add(pickings[1])
|
|
batch = batch_form.save()
|
|
batch.action_confirm()
|
|
|
|
url = self._get_batch_client_action_url(batch.id)
|
|
self.start_tour(url, 'test_pack_and_same_product_several_sml', login='admin', timeout=180)
|
|
|
|
self.assertRecordValues(pickings.move_ids, [
|
|
{'picking_id': pickings[0].id, 'product_id': self.product1.id, 'state': 'done', 'quantity': 3, 'picked': True},
|
|
{'picking_id': pickings[0].id, 'product_id': self.product2.id, 'state': 'done', 'quantity': 70, 'picked': True},
|
|
{'picking_id': pickings[1].id, 'product_id': self.product1.id, 'state': 'done', 'quantity': 7, 'picked': True},
|
|
{'picking_id': pickings[1].id, 'product_id': self.product2.id, 'state': 'done', 'quantity': 30, 'picked': True},
|
|
])
|
|
|
|
def test_delete_from_batch(self):
|
|
action_id = self.env.ref('stock_barcode.stock_barcode_action_main_menu')
|
|
url = "/web#action=" + str(action_id.id)
|
|
self.start_tour(url, 'test_delete_from_batch', login='admin', timeout=180)
|
|
|
|
def test_split_line_on_exit_for_batch(self):
|
|
""" Ensures that exit an unfinished batch will split the uncompleted move lines to have one
|
|
move line with all picked quantity and one move line with the remaining quantity."""
|
|
self.clean_access_rights()
|
|
|
|
# Creates a new batch.
|
|
batch_receipts = self.env['stock.picking.batch'].create({
|
|
'name': 'batch_split_line_on_exit',
|
|
'picking_type_id': self.picking_type_in.id,
|
|
})
|
|
|
|
# Creates two receipts (one for 4x product1, one for 4x product2) and add them to the batch.
|
|
# Creates a receipt for 4x product1 and a second receipt for 4x product2.
|
|
receipt1 = self.env['stock.picking'].create({
|
|
'batch_id': batch_receipts.id,
|
|
'name': "receipt1",
|
|
'location_id': self.supplier_location.id,
|
|
'location_dest_id': self.stock_location.id,
|
|
'picking_type_id': self.picking_type_in.id,
|
|
})
|
|
self.env['stock.move'].create({
|
|
'location_dest_id': receipt1.location_dest_id.id,
|
|
'location_id': receipt1.location_id.id,
|
|
'name': "product1 x4",
|
|
'picking_id': receipt1.id,
|
|
'product_id': self.product1.id,
|
|
'product_uom_qty': 4,
|
|
})
|
|
receipt2 = self.env['stock.picking'].create({
|
|
'batch_id': batch_receipts.id,
|
|
'name': "receipt2",
|
|
'location_id': self.supplier_location.id,
|
|
'location_dest_id': self.stock_location.id,
|
|
'picking_type_id': self.picking_type_in.id,
|
|
})
|
|
self.env['stock.move'].create({
|
|
'location_dest_id': receipt2.location_dest_id.id,
|
|
'location_id': receipt2.location_id.id,
|
|
'name': "product2 x4",
|
|
'picking_id': receipt2.id,
|
|
'product_id': self.product2.id,
|
|
'product_uom_qty': 4,
|
|
})
|
|
batch_receipts.action_confirm()
|
|
|
|
action_id = self.env.ref('stock_barcode.stock_barcode_action_main_menu')
|
|
url = f"/web#action={action_id.id}"
|
|
self.start_tour(url, 'test_split_line_on_exit_for_batch', login='admin')
|
|
# Checks the receipts moves values.
|
|
self.assertRecordValues(receipt1.move_ids, [
|
|
{'product_id': self.product1.id, 'quantity': 2, 'picked': True},
|
|
{'product_id': self.product1.id, 'quantity': 2, 'picked': False},
|
|
])
|
|
self.assertRecordValues(receipt2.move_ids, [
|
|
{'product_id': self.product2.id, 'quantity': 1, 'picked': True},
|
|
{'product_id': self.product2.id, 'quantity': 3, 'picked': False},
|
|
])
|
|
|
|
def test_scan_can_change_destination_location(self):
|
|
""" When we have multiple pickings in a batch for the same product, we should be able to change
|
|
the destination location of each of them by scanning only once the destination.
|
|
"""
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, quantity=2)
|
|
self.picking_type_internal.write({
|
|
'restrict_scan_source_location': 'mandatory',
|
|
'restrict_scan_product': True,
|
|
'restrict_scan_dest_location': 'mandatory',
|
|
})
|
|
|
|
internal_picking_1 = self.env['stock.picking'].create({
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.stock_location.id,
|
|
'picking_type_id': self.picking_type_internal.id,
|
|
})
|
|
self.env['stock.move'].create({
|
|
'location_dest_id': internal_picking_1.location_dest_id.id,
|
|
'location_id': internal_picking_1.location_id.id,
|
|
'name': 'product1 x1',
|
|
'picking_id': internal_picking_1.id,
|
|
'product_id': self.product1.id,
|
|
'product_uom_qty': 1,
|
|
})
|
|
internal_picking_2 = internal_picking_1.copy()
|
|
|
|
internal_picking_1.name = 'test_int_picking_1'
|
|
internal_picking_2.name = 'test_int_picking_2'
|
|
|
|
batch_form = Form(self.env['stock.picking.batch'])
|
|
batch_form.picking_type_id = self.picking_type_internal
|
|
batch_form.picking_ids.add(internal_picking_1)
|
|
batch_form.picking_ids.add(internal_picking_2)
|
|
batch = batch_form.save()
|
|
batch.action_confirm()
|
|
|
|
url = self._get_batch_client_action_url(batch.id)
|
|
self.start_tour(url, 'test_scan_can_change_destination_location', login='admin')
|
|
|
|
# validate that SML destination locations changed
|
|
move_lines = internal_picking_1.move_line_ids + internal_picking_2.move_line_ids
|
|
self.assertEqual(move_lines.mapped('location_dest_id'), self.shelf3)
|