forked from Mapan/odoo17e
3591 lines
171 KiB
Python
3591 lines
171 KiB
Python
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from unittest.mock import patch
|
|
|
|
import odoo
|
|
from odoo import Command
|
|
from odoo.tests import Form, tagged
|
|
from odoo.addons.stock_barcode.tests.test_barcode_client_action import TestBarcodeClientAction
|
|
|
|
|
|
@tagged('post_install', '-at_install')
|
|
class TestPickingBarcodeClientAction(TestBarcodeClientAction):
|
|
def test_internal_picking_from_scratch(self):
|
|
""" Opens an empty internal picking and creates following move through the form view:
|
|
- move 2 `self.product1` from shelf1 to shelf2
|
|
- move 1 `self.product2` from shelf1 to shelf3
|
|
- move 1 `self.product2` from shelf1 to shelf2
|
|
Then creates a fourth move by scanning product1 (from shelf1 to shelf3).
|
|
Counts the number of picking's write.
|
|
"""
|
|
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)]})
|
|
self.picking_type_internal.restrict_scan_dest_location = 'mandatory'
|
|
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,
|
|
})
|
|
url = self._get_client_action_url(internal_picking.id)
|
|
|
|
# Mock the calls to write and run the phantomjs script.
|
|
picking_write_orig = odoo.addons.stock.models.stock_picking.Picking.write
|
|
picking_button_validate_orig = odoo.addons.stock.models.stock_picking.Picking.button_validate
|
|
product1 = self.product1
|
|
stock_location = self.stock_location
|
|
shelf1 = self.shelf1
|
|
shelf3 = self.shelf3
|
|
assertEqual = self.assertEqual
|
|
self1 = self
|
|
self1.stop_count_write = False
|
|
|
|
def picking_write_mock(self, vals):
|
|
if self1.stop_count_write:
|
|
# Stops to count before `stock.picking` `button_validate` was called because
|
|
# that method and its overrides can call an unpredictable amount of write.
|
|
return picking_write_orig(self, vals)
|
|
self1.call_count += 1
|
|
if self1.call_count == 1: # Open the edit form view for a line added by scanning its product.
|
|
cmd = vals['move_line_ids'][0]
|
|
write_vals = cmd[2]
|
|
assertEqual(cmd[0], 0)
|
|
assertEqual(cmd[1], 0)
|
|
assertEqual(write_vals['product_id'], product1.id)
|
|
assertEqual(write_vals['picking_id'], internal_picking.id)
|
|
assertEqual(write_vals['location_id'], shelf1.id)
|
|
assertEqual(write_vals['location_dest_id'], stock_location.id)
|
|
assertEqual(write_vals['quantity'], 1)
|
|
elif self1.call_count == 2: # Write before the validate.
|
|
cmd = vals['move_line_ids'][0]
|
|
write_vals = cmd[2]
|
|
assertEqual(cmd[0], 1)
|
|
assertEqual(write_vals['location_dest_id'], shelf3.id)
|
|
return picking_write_orig(self, vals)
|
|
|
|
def picking_button_validate_mock(self):
|
|
self1.stop_count_write = True # Stops to count write once validate is called.
|
|
return picking_button_validate_orig(self)
|
|
|
|
with patch('odoo.addons.stock.models.stock_picking.Picking.write', new=picking_write_mock),\
|
|
patch('odoo.addons.stock.models.stock_picking.Picking.button_validate', new=picking_button_validate_mock):
|
|
self.start_tour(url, 'test_internal_picking_from_scratch', login='admin', timeout=180)
|
|
|
|
self.assertEqual(self.call_count, 2)
|
|
self.assertEqual(len(internal_picking.move_line_ids), 4)
|
|
prod1_ml = internal_picking.move_line_ids.filtered(lambda ml: ml.product_id.id == self.product1.id).sorted(
|
|
lambda ml: (ml.location_id.complete_name, ml.location_dest_id.complete_name, ml.id))
|
|
prod2_ml = internal_picking.move_line_ids.filtered(lambda ml: ml.product_id.id == self.product2.id).sorted(
|
|
lambda ml: (ml.location_id.complete_name, ml.location_dest_id.complete_name, ml.id))
|
|
self.assertEqual(prod1_ml[0].quantity, 2)
|
|
self.assertTrue(prod1_ml[0].picked)
|
|
self.assertEqual(prod1_ml[0].location_id, self.shelf1)
|
|
self.assertEqual(prod1_ml[0].location_dest_id, self.shelf2)
|
|
self.assertEqual(prod1_ml[1].quantity, 1)
|
|
self.assertTrue(prod1_ml[1].picked)
|
|
self.assertEqual(prod1_ml[1].location_id, self.shelf1)
|
|
self.assertEqual(prod1_ml[1].location_dest_id, self.shelf3)
|
|
self.assertEqual(prod2_ml[0].quantity, 1)
|
|
self.assertTrue(prod2_ml[0].picked)
|
|
self.assertEqual(prod2_ml[0].location_id, self.shelf1)
|
|
self.assertEqual(prod2_ml[0].location_dest_id, self.shelf2)
|
|
self.assertEqual(prod2_ml[1].quantity, 1)
|
|
self.assertTrue(prod2_ml[1].picked)
|
|
self.assertEqual(prod2_ml[1].location_id, self.shelf1)
|
|
self.assertEqual(prod2_ml[1].location_dest_id, self.shelf3)
|
|
|
|
def test_picking_scan_package_confirmation(self):
|
|
"""
|
|
This test ensures that whenever a product is already scanned in a package,
|
|
if we scan the package, a confirmation is asked before adding the content of the package.
|
|
"""
|
|
self.clean_access_rights()
|
|
grp_pack = self.env.ref('stock.group_tracking_lot')
|
|
self.env.user.write({'groups_id': [(4, grp_pack.id, 0)]})
|
|
package1 = self.env['stock.quant.package'].create({'name': 'package001'})
|
|
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 1, package_id=package1)
|
|
self.env['stock.quant']._update_available_quantity(self.product2, self.stock_location, 1, package_id=package1)
|
|
|
|
delivery_with_move = self.env['stock.picking'].create({
|
|
'name': "Delivery with Stock Move",
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.customer_location.id,
|
|
'picking_type_id': self.picking_type_out.id,
|
|
'move_ids': [(0, 0, {
|
|
'name': 'scan_package_confirmation',
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.customer_location.id,
|
|
'product_id': self.product1.id,
|
|
'product_uom': self.uom_unit.id,
|
|
'product_uom_qty': 2
|
|
})],
|
|
})
|
|
delivery_with_move.action_confirm()
|
|
delivery_with_move.action_assign()
|
|
|
|
action = self.env["ir.actions.actions"]._for_xml_id("stock_barcode.stock_barcode_picking_client_action")
|
|
url = '/web#action=%s&active_id=%s' % (action['id'], delivery_with_move.id)
|
|
self.start_tour(url, 'test_picking_scan_package_confirmation', login='admin', timeout=180)
|
|
|
|
def test_internal_picking_from_scratch_with_package(self):
|
|
""" Opens an empty internal picking, scans the source (shelf1), then scans
|
|
the products (product1 and product2), scans a existing empty package to
|
|
assign it as the result package, and finally scans the destination (shelf2).
|
|
Checks the dest location is correctly set on the lines.
|
|
"""
|
|
self.clean_access_rights()
|
|
grp_pack = self.env.ref('stock.group_tracking_lot')
|
|
grp_multi_loc = self.env.ref('stock.group_stock_multi_locations')
|
|
self.env.user.write({'groups_id': [(4, grp_multi_loc.id, 0), (4, grp_pack.id, 0)]})
|
|
self.picking_type_internal.active = True
|
|
# Creates a new package and add some quants.
|
|
package2 = self.env['stock.quant.package'].create({'name': 'P00002'})
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 1, package_id=package2)
|
|
self.env['stock.quant']._update_available_quantity(self.product2, self.stock_location, 2, package_id=package2)
|
|
self.assertEqual(package2.location_id.id, self.stock_location.id)
|
|
|
|
action_id = self.env.ref('stock_barcode.stock_barcode_action_main_menu')
|
|
url = "/web#action=" + str(action_id.id)
|
|
self.start_tour(url, 'test_internal_picking_from_scratch_with_package', login='admin', timeout=1800000)
|
|
|
|
self.assertEqual(len(self.package.quant_ids), 2)
|
|
self.assertEqual(self.package.location_id.id, self.shelf2.id)
|
|
self.assertRecordValues(self.package.quant_ids, [
|
|
{'product_id': self.product1.id, 'quantity': 1, 'location_id': self.shelf2.id},
|
|
{'product_id': self.product2.id, 'quantity': 1, 'location_id': self.shelf2.id},
|
|
])
|
|
|
|
self.assertEqual(package2.location_id.id, self.shelf2.id)
|
|
self.assertRecordValues(package2.quant_ids, [
|
|
{'product_id': self.product1.id, 'quantity': 1, 'location_id': self.shelf2.id},
|
|
{'product_id': self.product2.id, 'quantity': 2, 'location_id': self.shelf2.id},
|
|
])
|
|
|
|
def test_internal_picking_reserved_1(self):
|
|
""" Open a reserved internal picking
|
|
- move 1 `self.product1` and 1 `self.product2` from shelf1 to shelf2
|
|
- move 1 `self.product1` from shelf3 to shelf4.
|
|
Before doing the reservation, move 1 `self.product1` from shelf3 to shelf2
|
|
"""
|
|
self.clean_access_rights()
|
|
self.picking_type_internal.restrict_scan_dest_location = 'mandatory'
|
|
self.picking_type_internal.restrict_scan_source_location = 'mandatory'
|
|
grp_multi_loc = self.env.ref('stock.group_stock_multi_locations')
|
|
self.env.user.write({'groups_id': [(4, grp_multi_loc.id, 0)]})
|
|
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,
|
|
})
|
|
odoo.addons.stock.models.stock_picking.Picking.write
|
|
url = self._get_client_action_url(internal_picking.id)
|
|
|
|
# prepare the picking
|
|
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.product2, self.shelf3, 1)
|
|
move1 = self.env['stock.move'].create({
|
|
'name': 'test_internal_picking_reserved_1_1',
|
|
'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': 1,
|
|
'picking_id': internal_picking.id,
|
|
})
|
|
move2 = self.env['stock.move'].create({
|
|
'name': 'test_internal_picking_reserved_1_2',
|
|
'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_picking.id,
|
|
})
|
|
internal_picking.action_confirm()
|
|
internal_picking.action_assign()
|
|
move1.move_line_ids.location_dest_id = self.shelf2.id
|
|
for ml in move2.move_line_ids:
|
|
if ml.location_id.id == self.shelf1.id:
|
|
ml.location_dest_id = self.shelf2.id
|
|
else:
|
|
ml.location_dest_id = self.shelf4.id
|
|
|
|
self.start_tour(url, 'test_internal_picking_reserved_1', login='admin', timeout=180)
|
|
|
|
def test_receipt_from_scratch_with_lots_1(self):
|
|
self.clean_access_rights()
|
|
grp_multi_loc = self.env.ref('stock.group_stock_multi_locations')
|
|
grp_lot = self.env.ref('stock.group_production_lot')
|
|
self.env.user.write({'groups_id': [(4, grp_multi_loc.id, 0)]})
|
|
self.env.user.write({'groups_id': [(4, grp_lot.id, 0)]})
|
|
# Create a sibling stock location to check we can scan not only picking's
|
|
# destination and its sublocations for immediate transfers.
|
|
stock_2 = self.env['stock.location'].create({
|
|
'name': "Stock 2",
|
|
'location_id': self.stock_location.location_id.id,
|
|
'barcode': 'WH-STOCK-2',
|
|
})
|
|
receipt_picking = self.env['stock.picking'].create({
|
|
'location_id': self.supplier_location.id,
|
|
'location_dest_id': self.stock_location.id,
|
|
'picking_type_id': self.picking_type_in.id,
|
|
})
|
|
url = self._get_client_action_url(receipt_picking.id)
|
|
self.start_tour(url, 'test_receipt_from_scratch_with_lots_1', login='admin', timeout=180)
|
|
self.assertRecordValues(receipt_picking.move_line_ids, [
|
|
{'lot_name': 'lot1', 'location_dest_id': self.stock_location.id},
|
|
{'lot_name': 'lot2', 'location_dest_id': self.shelf1.id},
|
|
{'lot_name': 'lot3', 'location_dest_id': stock_2.id},
|
|
])
|
|
|
|
def test_receipt_from_scratch_with_lots_2(self):
|
|
self.clean_access_rights()
|
|
grp_multi_loc = self.env.ref('stock.group_stock_multi_locations')
|
|
grp_lot = self.env.ref('stock.group_production_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.picking_type_in.write({
|
|
"use_existing_lots": True,
|
|
"use_create_lots": True,
|
|
})
|
|
|
|
receipt_picking = self.env['stock.picking'].create({
|
|
'location_id': self.supplier_location.id,
|
|
'location_dest_id': self.stock_location.id,
|
|
'picking_type_id': self.picking_type_in.id,
|
|
})
|
|
url = self._get_client_action_url(receipt_picking.id)
|
|
self.start_tour(url, 'test_receipt_from_scratch_with_lots_2', login='admin', timeout=180)
|
|
self.assertEqual(receipt_picking.move_line_ids.mapped('lot_name'), ['lot1', 'lot2'])
|
|
self.assertEqual(receipt_picking.move_line_ids.mapped('qty_done'), [2, 2])
|
|
|
|
def test_receipt_from_scratch_with_lots_3(self):
|
|
""" Scans a non tracked product, then scans a tracked by lots product, then scans a
|
|
production lot twice and checks the tracked product quantity was rightly increased.
|
|
"""
|
|
self.clean_access_rights()
|
|
grp_multi_loc = self.env.ref('stock.group_stock_multi_locations')
|
|
grp_lot = self.env.ref('stock.group_production_lot')
|
|
self.env.user.write({'groups_id': [(4, grp_multi_loc.id, 0)]})
|
|
self.env.user.write({'groups_id': [(4, grp_lot.id, 0)]})
|
|
|
|
receipt_picking = self.env['stock.picking'].create({
|
|
'location_id': self.supplier_location.id,
|
|
'location_dest_id': self.stock_location.id,
|
|
'picking_type_id': self.picking_type_in.id,
|
|
})
|
|
url = self._get_client_action_url(receipt_picking.id)
|
|
self.start_tour(url, 'test_receipt_from_scratch_with_lots_3', login='admin', timeout=180)
|
|
move_lines = receipt_picking.move_line_ids
|
|
self.assertEqual(move_lines[0].product_id.id, self.product1.id)
|
|
self.assertEqual(move_lines[0].qty_done, 2.0)
|
|
self.assertEqual(move_lines[1].product_id.id, self.productlot1.id)
|
|
self.assertEqual(move_lines[1].qty_done, 2.0)
|
|
self.assertEqual(move_lines[1].lot_name, 'lot1')
|
|
|
|
def test_receipt_from_scratch_with_lots_4(self):
|
|
""" With picking type options "use_create_lots" and "use existing lots" disabled,
|
|
scan a tracked product 3 times and checks the tracked product quantity was rightly
|
|
increased without the need to enter serial/lot number.
|
|
"""
|
|
self.clean_access_rights()
|
|
grp_multi_loc = self.env.ref('stock.group_stock_multi_locations')
|
|
grp_lot = self.env.ref('stock.group_production_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.picking_type_in.use_create_lots = False
|
|
self.picking_type_in.use_existing_lots = False
|
|
|
|
receipt_picking = self.env['stock.picking'].create({
|
|
'location_id': self.supplier_location.id,
|
|
'location_dest_id': self.stock_location.id,
|
|
'picking_type_id': self.picking_type_in.id,
|
|
})
|
|
url = self._get_client_action_url(receipt_picking.id)
|
|
self.start_tour(url, 'test_receipt_from_scratch_with_lots_4', login='admin', timeout=180)
|
|
move_lines = receipt_picking.move_line_ids
|
|
self.assertEqual(move_lines[0].product_id.id, self.productserial1.id)
|
|
self.assertEqual(move_lines[0].qty_done, 3.0)
|
|
|
|
def test_receipt_with_sn_1(self):
|
|
""" With picking type options "use_create_lots" and "use_existing_lots" enabled, scan a
|
|
tracked product and enter a serial number already registered (but not used) in the system.
|
|
"""
|
|
self.clean_access_rights()
|
|
grp_multi_loc = self.env.ref('stock.group_stock_multi_locations')
|
|
grp_lot = self.env.ref('stock.group_production_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.picking_type_in.use_create_lots = True
|
|
self.picking_type_in.use_existing_lots = True
|
|
snObj = self.env['stock.lot']
|
|
snObj.create({'name': 'sn1', 'product_id': self.productserial1.id, 'company_id': self.env.company.id})
|
|
|
|
receipt_picking = self.env['stock.picking'].create({
|
|
'location_id': self.supplier_location.id,
|
|
'location_dest_id': self.stock_location.id,
|
|
'picking_type_id': self.picking_type_in.id,
|
|
'state': 'draft',
|
|
})
|
|
|
|
self.env['stock.move'].create({
|
|
'name': 'test_receipt_1',
|
|
'location_id': self.supplier_location.id,
|
|
'location_dest_id': self.stock_location.id,
|
|
'product_id': self.productserial1.id,
|
|
'product_uom': self.productserial1.uom_id.id,
|
|
'product_uom_qty': 1,
|
|
'picking_id': receipt_picking.id,
|
|
'picking_type_id': self.picking_type_in.id,
|
|
})
|
|
|
|
url = self._get_client_action_url(receipt_picking.id)
|
|
self.start_tour(url, 'test_receipt_with_sn_1', login='admin', timeout=180)
|
|
move_lines = receipt_picking.move_line_ids
|
|
self.assertEqual(move_lines[0].product_id.id, self.productserial1.id)
|
|
self.assertEqual(move_lines[0].lot_id.name, 'sn1')
|
|
self.assertEqual(move_lines[0].quantity, 1.0)
|
|
|
|
def test_receipt_reserved_1(self):
|
|
""" Open a receipt. Move four units of `self.product1` and four units of
|
|
unit of `self.product2` into shelf1.
|
|
"""
|
|
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)]})
|
|
receipt_picking = self.env['stock.picking'].create({
|
|
'location_id': self.supplier_location.id,
|
|
'location_dest_id': self.stock_location.id,
|
|
'picking_type_id': self.picking_type_in.id,
|
|
})
|
|
picking_write_orig = odoo.addons.stock.models.stock_picking.Picking.write
|
|
url = self._get_client_action_url(receipt_picking.id)
|
|
|
|
# Create a sibling stock location to check we can only scan picking's
|
|
# destination and its sublocations as move line destination.
|
|
self.env['stock.location'].create({
|
|
'name': "Stock 2",
|
|
'location_id': self.stock_location.location_id.id,
|
|
'barcode': 'WH-STOCK-2',
|
|
})
|
|
|
|
move1 = self.env['stock.move'].create({
|
|
'name': 'test_receipt_reserved_1_1',
|
|
'location_id': self.supplier_location.id,
|
|
'location_dest_id': self.stock_location.id,
|
|
'product_id': self.product1.id,
|
|
'product_uom': self.uom_unit.id,
|
|
'product_uom_qty': 4,
|
|
'picking_id': receipt_picking.id,
|
|
})
|
|
move2 = self.env['stock.move'].create({
|
|
'name': 'test_receipt_reserved_1_2',
|
|
'location_id': self.supplier_location.id,
|
|
'location_dest_id': self.stock_location.id,
|
|
'product_id': self.product2.id,
|
|
'product_uom': self.uom_unit.id,
|
|
'product_uom_qty': 4,
|
|
'picking_id': receipt_picking.id,
|
|
})
|
|
receipt_picking.action_confirm()
|
|
receipt_picking.action_assign()
|
|
|
|
# Mock the calls to write and run the phantomjs script.
|
|
assertEqual = self.assertEqual
|
|
ml1 = move1.move_line_ids
|
|
ml2 = move2.move_line_ids
|
|
shelf1 = self.shelf1
|
|
self1 = self
|
|
|
|
def picking_write_mock(self, vals):
|
|
self1.call_count += 1
|
|
if self1.call_count == 1:
|
|
assertEqual(len(vals['move_line_ids']), 2)
|
|
assertEqual(vals['move_line_ids'][0][:2], [1, ml2.id])
|
|
assertEqual(vals['move_line_ids'][1][:2], [1, ml1.id])
|
|
return picking_write_orig(self, vals)
|
|
|
|
with patch('odoo.addons.stock.models.stock_picking.Picking.write', new=picking_write_mock):
|
|
self.start_tour(url, 'test_receipt_reserved_1', login='admin', timeout=180)
|
|
self.assertEqual(self.call_count, 1)
|
|
self.assertEqual(receipt_picking.move_line_ids[0].location_dest_id.id, shelf1.id)
|
|
self.assertEqual(receipt_picking.move_line_ids[1].location_dest_id.id, shelf1.id)
|
|
|
|
def test_receipt_reserved_2_partial_put_in_pack(self):
|
|
""" For a planned receipt, check put in pack a uncompleted move line will split it. """
|
|
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 a receipt and confirm it.
|
|
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.product1
|
|
move.product_uom_qty = 3
|
|
with receipt_form.move_ids_without_package.new() as move:
|
|
move.product_id = self.product2
|
|
move.product_uom_qty = 3
|
|
receipt_picking = receipt_form.save()
|
|
receipt_picking.action_confirm()
|
|
receipt_picking.action_assign()
|
|
receipt_picking.name = "receipt_test"
|
|
|
|
# Set packages' sequence to 1000 to find it easily during the tour.
|
|
package_sequence = self.env['ir.sequence'].search([('code', '=', 'stock.quant.package')], limit=1)
|
|
package_sequence.write({'number_next_actual': 1000})
|
|
|
|
# Opens the barcode main menu to be able to open the pickings by scanning their name.
|
|
action_id = self.env.ref('stock_barcode.stock_barcode_action_main_menu')
|
|
url = "/web#action=" + str(action_id.id)
|
|
self.start_tour(url, "test_receipt_reserved_2_partial_put_in_pack", login="admin", timeout=180)
|
|
|
|
package1 = self.env['stock.quant.package'].search([('name', '=', 'PACK0001000')])
|
|
package2 = self.env['stock.quant.package'].search([('name', '=', 'PACK0001001')])
|
|
self.assertRecordValues(receipt_picking.move_ids, [
|
|
{'product_id': self.product1.id, 'product_uom_qty': 3, 'quantity': 3, 'picked': True},
|
|
{'product_id': self.product2.id, 'product_uom_qty': 1, 'quantity': 1, 'picked': True},
|
|
])
|
|
self.assertRecordValues(receipt_picking.move_line_ids.sorted(), [
|
|
{'product_id': self.product2.id, 'quantity': 1, 'picked': True, 'result_package_id': package2.id},
|
|
{'product_id': self.product1.id, 'quantity': 1, 'picked': True, 'result_package_id': package2.id},
|
|
{'product_id': self.product1.id, 'quantity': 2, 'picked': True, 'result_package_id': package1.id},
|
|
])
|
|
# Since the receipt wasn't complete, a backorder should be created.
|
|
receipt_backorder = receipt_picking.backorder_ids
|
|
self.assertRecordValues(receipt_backorder.move_ids, [
|
|
{'product_id': self.product2.id, 'product_uom_qty': 2, 'quantity': 2, 'picked': False},
|
|
])
|
|
|
|
def test_receipt_product_not_consecutively(self):
|
|
""" Check that there is no new line created when scanning the same product several times but not consecutively."""
|
|
self.clean_access_rights()
|
|
|
|
receipt_picking = self.env['stock.picking'].create({
|
|
'location_id': self.supplier_location.id,
|
|
'location_dest_id': self.stock_location.id,
|
|
'picking_type_id': self.picking_type_in.id,
|
|
'state': 'draft',
|
|
})
|
|
|
|
url = self._get_client_action_url(receipt_picking.id)
|
|
self.start_tour(url, 'test_receipt_product_not_consecutively', login='admin', timeout=180)
|
|
|
|
self.assertEqual(len(receipt_picking.move_line_ids), 2)
|
|
self.assertEqual(receipt_picking.move_line_ids.product_id.mapped('id'), [self.product1.id, self.product2.id])
|
|
self.assertEqual(receipt_picking.move_line_ids.mapped('quantity'), [2, 1])
|
|
|
|
def test_delivery_source_location(self):
|
|
""" Ensures a location who isn't the picking's source location or one of its sublocations
|
|
can't be scanned as the source while processing a delivery.
|
|
Ensures also this constraint is not applyable for immediate transfers."""
|
|
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)]})
|
|
# Creates a new location at the same level than WH/Stock.
|
|
sibling_loc = self.env['stock.location'].create({
|
|
'name': "Second Stock",
|
|
'location_id': self.stock_location.location_id.id,
|
|
'barcode': 'WH-SECOND-STOCK',
|
|
})
|
|
# Adds some quantities in stock and creates a delivery using them.
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 4)
|
|
delivery_from_stock = self.env['stock.picking'].create({
|
|
'name': 'delivery_from_stock',
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.customer_location.id,
|
|
'picking_type_id': self.picking_type_out.id,
|
|
})
|
|
self.env['stock.move'].create({
|
|
'name': 'product1 x4 (WH/Stock)',
|
|
'location_id': delivery_from_stock.location_id.id,
|
|
'location_dest_id': delivery_from_stock.location_dest_id.id,
|
|
'product_id': self.product1.id,
|
|
'product_uom': self.uom_unit.id,
|
|
'product_uom_qty': 4,
|
|
'picking_id': delivery_from_stock.id,
|
|
})
|
|
delivery_from_stock.action_confirm()
|
|
delivery_from_stock.action_assign()
|
|
# Adds some quantities in 2nd stock and creates a delivery using them.
|
|
self.env['stock.quant']._update_available_quantity(self.product1, sibling_loc, 4)
|
|
delivery_from_stock_2 = self.env['stock.picking'].create({
|
|
'name': 'delivery_from_second_stock',
|
|
'location_id': sibling_loc.id,
|
|
'location_dest_id': self.customer_location.id,
|
|
'picking_type_id': self.picking_type_out.id,
|
|
})
|
|
self.env['stock.move'].create({
|
|
'name': 'product1 x4 (WH/Second Stock)',
|
|
'location_id': delivery_from_stock_2.location_id.id,
|
|
'location_dest_id': delivery_from_stock_2.location_dest_id.id,
|
|
'product_id': self.product1.id,
|
|
'product_uom': self.uom_unit.id,
|
|
'product_uom_qty': 4,
|
|
'picking_id': delivery_from_stock_2.id,
|
|
})
|
|
delivery_from_stock_2.action_confirm()
|
|
delivery_from_stock_2.action_assign()
|
|
|
|
action = self.env["ir.actions.actions"]._for_xml_id("stock_barcode.stock_barcode_action_main_menu")
|
|
url = f'/web#action={action["id"]}'
|
|
self.start_tour(url, 'test_delivery_source_location', login='admin', timeout=180)
|
|
|
|
def test_delivery_lot_with_multi_companies(self):
|
|
""" This test ensures that scanning a lot or serial number who exists for
|
|
multiple companies will fetch only the one who belongs to the active company.
|
|
"""
|
|
self.clean_access_rights()
|
|
# Creates two companies and assign them to the user.
|
|
company_a = self.env['res.company'].create({'name': 'Company "Ah !" (le meme TMTC)'})
|
|
company_b = self.env['res.company'].create({'name': 'Company Bae 😏😘'})
|
|
self.env.user.write({
|
|
'groups_id': [(4, self.env.ref('stock.group_production_lot').id)],
|
|
'company_ids': [(4, company_a.id), (4, company_b.id)],
|
|
'company_id': company_b.id,
|
|
})
|
|
warehouse_b = self.env['stock.warehouse'].search([('company_id', '=', company_b.id)])
|
|
location_a = self.env['stock.warehouse'].search([('company_id', '=', company_a.id)]).lot_stock_id
|
|
location_b = warehouse_b.lot_stock_id
|
|
picking_type_out_b = warehouse_b.out_type_id
|
|
location_id_by_company = {
|
|
company_a: location_a.id,
|
|
company_b: location_b.id,
|
|
}
|
|
# Creates some serial numbers (some of them being in the two companies.)
|
|
sn_a_1 = self.env['stock.lot'].create({'name': 'tsn-001', 'product_id': self.productserial1.id, 'company_id': company_a.id})
|
|
sn_a_2 = self.env['stock.lot'].create({'name': 'tsn-002', 'product_id': self.productserial1.id, 'company_id': company_a.id})
|
|
sn_b_1 = self.env['stock.lot'].create({'name': 'tsn-001', 'product_id': self.productserial1.id, 'company_id': company_b.id})
|
|
sn_b_2 = self.env['stock.lot'].create({'name': 'tsn-003', 'product_id': self.productserial1.id, 'company_id': company_b.id})
|
|
for sn in [sn_a_1, sn_a_2, sn_b_1, sn_b_2]:
|
|
self.env['stock.quant'].with_context(inventory_mode=True).create({
|
|
'product_id': self.productserial1.id,
|
|
'inventory_quantity': 1,
|
|
'lot_id': sn.id,
|
|
'location_id': location_id_by_company[sn.company_id],
|
|
})
|
|
# Creates a delivery for Company B.
|
|
picking_form = Form(self.env['stock.picking'])
|
|
picking_form.picking_type_id = picking_type_out_b
|
|
delivery = picking_form.save()
|
|
url = self._get_client_action_url(delivery.id)
|
|
self.start_tour(url, 'test_delivery_lot_with_multi_companies', login='admin', timeout=180)
|
|
self.assertRecordValues(delivery.move_line_ids.lot_id, [
|
|
{'name': 'tsn-001', 'company_id': company_b.id},
|
|
{'name': 'tsn-003', 'company_id': company_b.id},
|
|
])
|
|
|
|
def test_delivery_lot_with_package(self):
|
|
""" Have a delivery for a product tracked by SN, scan a non-reserved SN
|
|
and checks the new created line has the right SN's package & owner.
|
|
"""
|
|
self.clean_access_rights()
|
|
grp_lot = self.env.ref('stock.group_production_lot')
|
|
grp_owner = self.env.ref('stock.group_tracking_owner')
|
|
grp_pack = self.env.ref('stock.group_tracking_lot')
|
|
self.env.user.write({'groups_id': [(4, grp_lot.id, 0)]})
|
|
self.env.user.write({'groups_id': [(4, grp_owner.id, 0)]})
|
|
self.env.user.write({'groups_id': [(4, grp_pack.id, 0)]})
|
|
|
|
# Creates 4 serial numbers and adds 2 qty. for the reservation.
|
|
snObj = self.env['stock.lot']
|
|
sn1 = snObj.create({'name': 'sn1', 'product_id': self.productserial1.id, 'company_id': self.env.company.id})
|
|
sn2 = snObj.create({'name': 'sn2', 'product_id': self.productserial1.id, 'company_id': self.env.company.id})
|
|
sn3 = snObj.create({'name': 'sn3', 'product_id': self.productserial1.id, 'company_id': self.env.company.id})
|
|
sn4 = snObj.create({'name': 'sn4', 'product_id': self.productserial1.id, 'company_id': self.env.company.id})
|
|
package1 = self.env['stock.quant.package'].create({'name': 'pack_sn_1'})
|
|
package2 = self.env['stock.quant.package'].create({'name': 'pack_sn_2'})
|
|
partner = self.env['res.partner'].create({'name': 'Particulier'})
|
|
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.stock_location.id,
|
|
'package_id': package1.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.stock_location.id,
|
|
'package_id': package1.id,
|
|
}).action_apply_inventory()
|
|
|
|
# Creates and confirms the delivery.
|
|
delivery_picking = self.env['stock.picking'].create({
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.customer_location.id,
|
|
'picking_type_id': self.picking_type_out.id,
|
|
})
|
|
self.env['stock.move'].create({
|
|
'name': self.productserial1.name,
|
|
'product_id': self.productserial1.id,
|
|
'product_uom_qty': 2,
|
|
'product_uom': self.productserial1.uom_id.id,
|
|
'picking_id': delivery_picking.id,
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.customer_location.id,
|
|
})
|
|
delivery_picking.action_confirm()
|
|
delivery_picking.action_assign()
|
|
# Add 2 more qty. after the reservation.
|
|
self.env['stock.quant'].with_context(inventory_mode=True).create({
|
|
'product_id': self.productserial1.id,
|
|
'inventory_quantity': 1,
|
|
'lot_id': sn3.id,
|
|
'location_id': self.stock_location.id,
|
|
'package_id': package2.id,
|
|
}).action_apply_inventory()
|
|
self.env['stock.quant'].with_context(inventory_mode=True).create({
|
|
'product_id': self.productserial1.id,
|
|
'inventory_quantity': 1,
|
|
'lot_id': sn4.id,
|
|
'location_id': self.stock_location.id,
|
|
'package_id': package2.id,
|
|
'owner_id': partner.id,
|
|
}).action_apply_inventory()
|
|
|
|
# Runs the tour.
|
|
url = self._get_client_action_url(delivery_picking.id)
|
|
self.start_tour(url, 'test_delivery_lot_with_package', login='admin', timeout=180)
|
|
|
|
# Checks move lines values after delivery was completed.
|
|
self.assertEqual(delivery_picking.state, "done")
|
|
# ensure that SNs not scanned by validation time are removed
|
|
self.assertEqual(len(delivery_picking.move_line_ids), 2)
|
|
move_line_1 = delivery_picking.move_line_ids[0]
|
|
move_line_2 = delivery_picking.move_line_ids[1]
|
|
self.assertEqual(move_line_1.lot_id, sn3)
|
|
self.assertEqual(move_line_1.package_id, package2)
|
|
self.assertEqual(move_line_1.owner_id.id, False)
|
|
self.assertEqual(move_line_2.lot_id, sn4)
|
|
self.assertEqual(move_line_2.package_id, package2)
|
|
self.assertEqual(move_line_2.owner_id, partner)
|
|
|
|
def test_delivery_lot_with_package_delivery_step(self):
|
|
"""
|
|
Test that we unpack from the right package in case of having
|
|
multiple packages (or package and no package) for the same lot
|
|
in multi-locations configuration.
|
|
"""
|
|
self.clean_access_rights()
|
|
grp_lot = self.env.ref('stock.group_production_lot')
|
|
grp_pack = self.env.ref('stock.group_tracking_lot')
|
|
grp_multi_loc = self.env.ref('stock.group_stock_multi_locations')
|
|
|
|
self.env.user.write({'groups_id': [(4, grp_lot.id, 0)]})
|
|
self.env.user.write({'groups_id': [(4, grp_pack.id, 0)]})
|
|
self.env.user.write({'groups_id': [(4, grp_multi_loc.id, 0)]})
|
|
|
|
sn = self.env['stock.lot'].create({'name': 'sn', 'product_id': self.productlot1.id, 'company_id': self.env.company.id})
|
|
package1 = self.env['stock.quant.package'].create({'name': 'pack_sn'})
|
|
package2 = self.env['stock.quant.package'].create({'name': 'pack_sn_2'})
|
|
self.env['stock.quant'].create([
|
|
{
|
|
'product_id': self.productlot1.id,
|
|
'inventory_quantity': 1,
|
|
'lot_id': sn.id,
|
|
'location_id': self.shelf1.id,
|
|
'package_id': package1.id,
|
|
}, {
|
|
'product_id': self.productlot1.id,
|
|
'inventory_quantity': 1,
|
|
'lot_id': sn.id,
|
|
'location_id': self.shelf2.id,
|
|
},
|
|
{
|
|
'product_id': self.productlot1.id,
|
|
'inventory_quantity': 1,
|
|
'lot_id': sn.id,
|
|
'location_id': self.shelf2.id,
|
|
'package_id': package2.id,
|
|
}
|
|
]).action_apply_inventory()
|
|
|
|
# Creates and confirms the delivery.
|
|
delivery_picking = self.env['stock.picking'].create({
|
|
'location_id': self.shelf2.id,
|
|
'location_dest_id': self.customer_location.id,
|
|
'picking_type_id': self.picking_type_out.id,
|
|
})
|
|
move = self.env['stock.move'].create({
|
|
'name': self.productlot1.name,
|
|
'product_id': self.productlot1.id,
|
|
'product_uom_qty': 1,
|
|
'product_uom': self.productlot1.uom_id.id,
|
|
'picking_id': delivery_picking.id,
|
|
'location_id': self.shelf2.id,
|
|
'location_dest_id': self.customer_location.id,
|
|
})
|
|
delivery_picking.action_confirm()
|
|
delivery_picking.action_assign()
|
|
|
|
# Runs the tour.
|
|
url = self._get_client_action_url(delivery_picking.id)
|
|
self.start_tour(url, 'test_delivery_lot_with_package_delivery_step', login='admin', timeout=180)
|
|
self.assertEqual(delivery_picking.state, "done")
|
|
self.assertRecordValues(move.move_line_ids, [
|
|
{'lot_id': sn.id, 'product_id': self.productlot1.id, 'quantity': 1, 'package_id': package2.id, 'result_package_id': False},
|
|
])
|
|
|
|
def test_delivery_reserved_1(self):
|
|
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)]})
|
|
delivery_picking = self.env['stock.picking'].create({
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.customer_location.id,
|
|
'picking_type_id': self.picking_type_out.id,
|
|
'note': "A Test Note",
|
|
})
|
|
picking_write_orig = odoo.addons.stock.models.stock_picking.Picking.write
|
|
url = self._get_client_action_url(delivery_picking.id)
|
|
|
|
self.env['stock.move'].create({
|
|
'name': 'test_delivery_reserved_1_1',
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.customer_location.id,
|
|
'product_id': self.product1.id,
|
|
'product_uom': self.uom_unit.id,
|
|
'product_uom_qty': 4,
|
|
'picking_id': delivery_picking.id,
|
|
})
|
|
self.env['stock.move'].create({
|
|
'name': 'test_delivery_reserved_1_2',
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.customer_location.id,
|
|
'product_id': self.product2.id,
|
|
'product_uom': self.uom_unit.id,
|
|
'product_uom_qty': 4,
|
|
'picking_id': delivery_picking.id,
|
|
})
|
|
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 4)
|
|
self.env['stock.quant']._update_available_quantity(self.product2, self.stock_location, 4)
|
|
|
|
delivery_picking.action_confirm()
|
|
delivery_picking.action_assign()
|
|
|
|
self1 = self
|
|
|
|
# Mock the calls to write and run the phantomjs script.
|
|
def picking_write_mock(self, vals):
|
|
self1.call_count += 1
|
|
return picking_write_orig(self, vals)
|
|
with patch('odoo.addons.stock.models.stock_picking.Picking.write', new=picking_write_mock):
|
|
self.start_tour(url, 'test_delivery_reserved_1', login='admin', timeout=180)
|
|
self.assertEqual(self.call_count, 1)
|
|
|
|
def test_delivery_reserved_2(self):
|
|
self.clean_access_rights()
|
|
self.picking_type_out.restrict_scan_source_location = 'no'
|
|
delivery_picking = self.env['stock.picking'].create({
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.customer_location.id,
|
|
'picking_type_id': self.picking_type_out.id,
|
|
})
|
|
picking_write_orig = odoo.addons.stock.models.stock_picking.Picking.write
|
|
url = self._get_client_action_url(delivery_picking.id)
|
|
|
|
partner_1 = self.env['res.partner'].create({'name': 'Parter1'})
|
|
partner_2 = self.env['res.partner'].create({'name': 'Partner2'})
|
|
self.env['stock.move'].create({
|
|
'name': 'test_delivery_reserved_2_1',
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.customer_location.id,
|
|
'product_id': self.product1.id,
|
|
'product_uom': self.uom_unit.id,
|
|
'product_uom_qty': 2,
|
|
'picking_id': delivery_picking.id,
|
|
'restrict_partner_id': partner_1.id
|
|
})
|
|
self.env['stock.move'].create({
|
|
'name': 'test_delivery_reserved_2_2',
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.customer_location.id,
|
|
'product_id': self.product1.id,
|
|
'product_uom': self.uom_unit.id,
|
|
'product_uom_qty': 2,
|
|
'picking_id': delivery_picking.id,
|
|
'restrict_partner_id': partner_2.id
|
|
})
|
|
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 4)
|
|
self.env['stock.quant']._update_available_quantity(self.product2, self.stock_location, 4)
|
|
|
|
delivery_picking.action_confirm()
|
|
delivery_picking.action_assign()
|
|
self.assertEqual(len(delivery_picking.move_ids), 2)
|
|
|
|
self1 = self
|
|
|
|
def picking_write_mock(self, vals):
|
|
self1.call_count += 1
|
|
return picking_write_orig(self, vals)
|
|
|
|
with patch('odoo.addons.stock.models.stock_picking.Picking.write', new=picking_write_mock):
|
|
self.start_tour(url, 'test_delivery_reserved_2', login='admin', timeout=180)
|
|
self.assertEqual(self.call_count, 0)
|
|
|
|
def test_delivery_reserved_3(self):
|
|
self.clean_access_rights()
|
|
delivery_picking = self.env['stock.picking'].create({
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.customer_location.id,
|
|
'picking_type_id': self.picking_type_out.id,
|
|
})
|
|
picking_write_orig = odoo.addons.stock.models.stock_picking.Picking.write
|
|
url = self._get_client_action_url(delivery_picking.id)
|
|
|
|
self.env['stock.move'].create({
|
|
'name': 'test_delivery_reserved_2_1',
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.customer_location.id,
|
|
'product_id': self.product1.id,
|
|
'product_uom': self.uom_unit.id,
|
|
'product_uom_qty': 1,
|
|
'picking_id': delivery_picking.id,
|
|
})
|
|
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 2)
|
|
|
|
delivery_picking.action_confirm()
|
|
delivery_picking.action_assign()
|
|
|
|
self1 = self
|
|
|
|
def picking_write_mock(self, vals):
|
|
self1.call_count += 1
|
|
return picking_write_orig(self, vals)
|
|
|
|
with patch('odoo.addons.stock.models.stock_picking.Picking.write', new=picking_write_mock):
|
|
self.start_tour(url, 'test_delivery_reserved_3', login='admin', timeout=180)
|
|
self.assertEqual(self.call_count, 0)
|
|
|
|
def test_delivery_reserved_4_backorder(self):
|
|
""" Checks the backorders are correctly created when all quantity isn't processed and the
|
|
confirmation's dialog is shown at the right time with the right informations.
|
|
"""
|
|
self.clean_access_rights()
|
|
product3 = self.env['product.product'].create({
|
|
'name': 'product3',
|
|
'type': 'product',
|
|
'categ_id': self.env.ref('product.product_category_all').id,
|
|
'barcode': 'product3',
|
|
})
|
|
# Creates a delivery with three different products.
|
|
delivery = self.env['stock.picking'].create({
|
|
'picking_type_id': self.picking_type_out.id,
|
|
})
|
|
common_vals = {
|
|
'location_dest_id': self.stock_location.id,
|
|
'location_id': self.stock_location.id,
|
|
'name': 'test_delivery_reserved_4_backorder',
|
|
'picking_id': delivery.id,
|
|
'product_uom_qty': 4,
|
|
}
|
|
self.env['stock.move'].create(dict(common_vals, product_id=self.product1.id))
|
|
self.env['stock.move'].create(dict(common_vals, product_id=self.product2.id))
|
|
self.env['stock.move'].create(dict(common_vals, product_id=product3.id))
|
|
# Adds quantity on hand for the delivery, but not to fulfill it.
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 4)
|
|
self.env['stock.quant']._update_available_quantity(self.product2, self.stock_location, 2)
|
|
# Confirms and reserves the delivery, then process it.
|
|
delivery.action_confirm()
|
|
delivery.action_assign()
|
|
url = self._get_client_action_url(delivery.id)
|
|
self.start_tour(url, 'test_delivery_reserved_4_backorder', login='admin', timeout=180)
|
|
|
|
# The delivery should have a backorder with two moves.
|
|
self.assertEqual(len(delivery.backorder_ids), 1)
|
|
backorder = delivery.backorder_ids
|
|
self.assertEqual(len(backorder.move_ids), 2)
|
|
self.assertRecordValues(backorder.move_ids, [
|
|
{'product_uom_qty': 4, 'quantity': 2, 'picked': False, 'product_id': self.product2.id},
|
|
{'product_uom_qty': 4, 'quantity': 0, 'picked': False, 'product_id': product3.id},
|
|
])
|
|
|
|
def test_delivery_from_scratch_1(self):
|
|
""" Scan unreserved lots on a delivery order.
|
|
"""
|
|
self.clean_access_rights()
|
|
grp_lot = self.env.ref('stock.group_production_lot')
|
|
self.env.user.write({'groups_id': [(4, grp_lot.id, 0)]})
|
|
|
|
# Adds lot1 and lot2 for productlot1
|
|
lotObj = self.env['stock.lot']
|
|
lotObj.create({'name': 'lot1', 'product_id': self.productlot1.id, 'company_id': self.env.company.id})
|
|
lotObj.create({'name': 'lot2', 'product_id': self.productlot1.id, 'company_id': self.env.company.id})
|
|
|
|
# Creates an empty picking.
|
|
delivery_picking = self.env['stock.picking'].create({
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.customer_location.id,
|
|
'picking_type_id': self.picking_type_out.id,
|
|
})
|
|
url = self._get_client_action_url(delivery_picking.id)
|
|
|
|
self.start_tour(url, 'test_delivery_from_scratch_with_lots_1', login='admin', timeout=180)
|
|
|
|
lines = delivery_picking.move_line_ids
|
|
self.assertEqual(lines[0].lot_id.name, 'lot1')
|
|
self.assertEqual(lines[1].lot_id.name, 'lot2')
|
|
self.assertEqual(lines[0].qty_done, 2)
|
|
self.assertEqual(lines[1].qty_done, 2)
|
|
|
|
def test_delivery_from_scratch_with_incompatible_lot(self):
|
|
"""
|
|
If a product and a lot have the same barcode, when this barcode is
|
|
scanned, both are found, but to avoid issue, the lot is ignored because
|
|
a lot shouldn't be applied to a line if its product is not the same.
|
|
"""
|
|
self.clean_access_rights()
|
|
grp_lot = self.env.ref('stock.group_production_lot')
|
|
self.env.user.write({'groups_id': [(4, grp_lot.id)]})
|
|
|
|
self.picking_type_out.use_create_lots = False
|
|
self.picking_type_out.use_existing_lots = True
|
|
|
|
lot = self.env['stock.lot'].create({
|
|
'name': '0000000001',
|
|
'product_id': self.productlot1.id,
|
|
'company_id': self.env.company.id,
|
|
})
|
|
|
|
for product in [self.product1, self.productserial1]:
|
|
product.barcode = lot.name
|
|
|
|
delivery_picking = self.env['stock.picking'].create({
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.customer_location.id,
|
|
'picking_type_id': self.picking_type_out.id,
|
|
'state': 'draft',
|
|
})
|
|
url = self._get_client_action_url(delivery_picking.id)
|
|
self.start_tour(url, 'test_delivery_from_scratch_with_incompatible_lot', login='admin', timeout=180)
|
|
|
|
self.assertRecordValues(delivery_picking.move_line_ids, [
|
|
{'product_id': product.id, 'lot_name': False, 'lot_id': False},
|
|
])
|
|
|
|
product.barcode = False
|
|
|
|
def test_delivery_from_scratch_with_common_lots_name(self):
|
|
"""
|
|
Suppose:
|
|
- two tracked-by-lot products
|
|
- these products share one lot name
|
|
- an extra product tracked by serial number
|
|
This test ensures that a user can scan the tracked products in a picking
|
|
that does not expect them and updates/creates the right line depending
|
|
of the scanned lot
|
|
"""
|
|
self.clean_access_rights()
|
|
group_lot = self.env.ref('stock.group_production_lot')
|
|
self.env.user.write({'groups_id': [(4, group_lot.id, 0)]})
|
|
|
|
(self.product1 + self.product2).tracking = 'lot'
|
|
|
|
lot01, lot02, sn = self.env['stock.lot'].create([{
|
|
'name': lot_name,
|
|
'product_id': product.id,
|
|
'company_id': self.env.company.id,
|
|
} for (lot_name, product) in [("LOT01", self.product1), ("LOT01", self.product2), ("SUPERSN", self.productserial1)]])
|
|
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 2, lot_id=lot01)
|
|
self.env['stock.quant']._update_available_quantity(self.product2, self.stock_location, 3, lot_id=lot02)
|
|
self.env['stock.quant']._update_available_quantity(self.productserial1, self.stock_location, 1, lot_id=sn)
|
|
|
|
picking_form = Form(self.env['stock.picking'])
|
|
picking_form.picking_type_id = self.picking_type_out
|
|
delivery = picking_form.save()
|
|
|
|
url = self._get_client_action_url(delivery.id)
|
|
self.start_tour(url, 'test_delivery_from_scratch_with_common_lots_name', login='admin', timeout=180)
|
|
|
|
self.assertRecordValues(delivery.move_line_ids, [
|
|
# pylint: disable=C0326
|
|
{'product_id': self.product1.id, 'lot_id': lot01.id, 'qty_done': 2},
|
|
{'product_id': self.product2.id, 'lot_id': lot02.id, 'qty_done': 3},
|
|
{'product_id': self.productserial1.id, 'lot_id': sn.id, 'qty_done': 1},
|
|
])
|
|
|
|
def test_delivery_reserved_lots_1(self):
|
|
""" Creates a delivery for product tracked by lots and having some
|
|
quantities in stock. Checks reserved lots are correctly visible in the
|
|
Barcode app and that they can be processed alongside not reserved lots.
|
|
"""
|
|
self.clean_access_rights()
|
|
grp_lot = self.env.ref('stock.group_production_lot')
|
|
self.env.user.write({'groups_id': [(4, grp_lot.id, 0)]})
|
|
|
|
delivery_picking = self.env['stock.picking'].create({
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.customer_location.id,
|
|
'picking_type_id': self.picking_type_out.id,
|
|
})
|
|
url = self._get_client_action_url(delivery_picking.id)
|
|
|
|
self.env['stock.move'].create({
|
|
'name': 'test_delivery_reserved_lots_1',
|
|
'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': 5,
|
|
'picking_id': delivery_picking.id,
|
|
})
|
|
|
|
# Creates lot1, lot2 and lot3 for productlot1.
|
|
lotObj = self.env['stock.lot']
|
|
lot1 = lotObj.create({'name': 'lot1', 'product_id': self.productlot1.id, 'company_id': self.env.company.id})
|
|
lot2 = lotObj.create({'name': 'lot2', 'product_id': self.productlot1.id, 'company_id': self.env.company.id})
|
|
lot3 = lotObj.create({'name': 'lot3', 'product_id': self.productlot1.id, 'company_id': self.env.company.id})
|
|
|
|
self.env['stock.quant']._update_available_quantity(self.productlot1, self.stock_location, 2, lot_id=lot1)
|
|
self.env['stock.quant']._update_available_quantity(self.productlot1, self.stock_location, 3, lot_id=lot2)
|
|
|
|
delivery_picking.action_confirm()
|
|
delivery_picking.action_assign()
|
|
self.assertEqual(delivery_picking.move_ids.state, 'assigned')
|
|
self.assertEqual(len(delivery_picking.move_ids.move_line_ids), 2)
|
|
|
|
self.start_tour(url, 'test_delivery_reserved_lots_1', login='admin', timeout=180)
|
|
|
|
self.assertRecordValues(delivery_picking.move_line_ids, [
|
|
{'lot_id': lot1.id, 'quantity': 2, 'picked': True},
|
|
{'lot_id': lot2.id, 'quantity': 2, 'picked': True},
|
|
{'lot_id': lot3.id, 'quantity': 1, 'picked': True},
|
|
])
|
|
|
|
def test_delivery_different_products_with_same_lot_name(self):
|
|
self.clean_access_rights()
|
|
grp_lot = self.env.ref('stock.group_production_lot')
|
|
self.env.user.write({'groups_id': [(4, grp_lot.id, 0)]})
|
|
|
|
self.productlot2 = self.env['product.product'].create({
|
|
'name': 'productlot2',
|
|
'type': 'product',
|
|
'categ_id': self.env.ref('product.product_category_all').id,
|
|
'barcode': 'productlot2',
|
|
'tracking': 'lot',
|
|
})
|
|
|
|
delivery_picking = self.env['stock.picking'].create({
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.customer_location.id,
|
|
'picking_type_id': self.picking_type_out.id,
|
|
})
|
|
url = self._get_client_action_url(delivery_picking.id)
|
|
|
|
self.env['stock.move'].create({
|
|
'name': 'test_delivery_different_products_with_same_lot_name_1',
|
|
'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': 2,
|
|
'picking_id': delivery_picking.id,
|
|
})
|
|
self.env['stock.move'].create({
|
|
'name': 'test_delivery_different_products_with_same_lot_name_2',
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.customer_location.id,
|
|
'product_id': self.productlot2.id,
|
|
'product_uom': self.uom_unit.id,
|
|
'product_uom_qty': 2,
|
|
'picking_id': delivery_picking.id,
|
|
})
|
|
|
|
# Create 2 lots with the same name for productlot1 and productlot2
|
|
lot1 = self.env['stock.lot'].create({'name': 'lot1', 'product_id': self.productlot1.id, 'company_id': self.env.company.id})
|
|
lot2 = self.env['stock.lot'].create({'name': 'lot1', 'product_id': self.productlot2.id, 'company_id': self.env.company.id})
|
|
|
|
self.env['stock.quant']._update_available_quantity(self.productlot1, self.stock_location, 2, lot_id=lot1)
|
|
self.env['stock.quant']._update_available_quantity(self.productlot2, self.stock_location, 2, lot_id=lot2)
|
|
|
|
delivery_picking.action_confirm()
|
|
delivery_picking.action_assign()
|
|
|
|
self.assertEqual(len(delivery_picking.move_ids), 2)
|
|
|
|
self.start_tour(url, 'test_delivery_different_products_with_same_lot_name', login='admin', timeout=180)
|
|
|
|
self.env.invalidate_all()
|
|
lines = delivery_picking.move_line_ids
|
|
self.assertEqual(lines[0].lot_id.name, 'lot1')
|
|
self.assertEqual(lines[0].product_id.name, 'productlot1')
|
|
self.assertEqual(lines[0].qty_done, 2)
|
|
self.assertEqual(lines[1].lot_id.name, 'lot1')
|
|
self.assertEqual(lines[1].product_id.name, 'productlot2')
|
|
self.assertEqual(lines[1].qty_done, 2)
|
|
|
|
def test_scan_same_lot_different_products(self):
|
|
"""
|
|
Checks that the same lot can be scanned for different products and there
|
|
is no conflict when trying to retrieve the lot from the cache.
|
|
"""
|
|
products = self.env['product.product'].create([{
|
|
'name': name,
|
|
'barcode': name,
|
|
'type': 'product',
|
|
'tracking': 'lot',
|
|
} for name in ['aaa', 'bbb']])
|
|
|
|
self.env["stock.lot"].create([{
|
|
'name': "123",
|
|
'product_id': product.id,
|
|
'company_id': self.env.company.id,
|
|
} for product in products])
|
|
|
|
self.picking_type_internal.restrict_scan_product = True
|
|
internal_picking = self.env['stock.picking'].create({
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.shelf1.id,
|
|
'picking_type_id': self.picking_type_internal.id,
|
|
})
|
|
url = self._get_client_action_url(internal_picking.id)
|
|
|
|
self.start_tour(url, 'test_scan_same_lot_different_products', login="admin")
|
|
|
|
def test_delivery_from_scratch_sn_1(self):
|
|
""" Scan unreserved serial number on a delivery order.
|
|
"""
|
|
|
|
self.clean_access_rights()
|
|
grp_lot = self.env.ref('stock.group_production_lot')
|
|
self.env.user.write({'groups_id': [(4, grp_lot.id, 0)]})
|
|
|
|
# Add 4 serial numbers productserial1
|
|
snObj = self.env['stock.lot']
|
|
sn1 = snObj.create({'name': 'sn1', 'product_id': self.productserial1.id, 'company_id': self.env.company.id})
|
|
sn2 = snObj.create({'name': 'sn2', 'product_id': self.productserial1.id, 'company_id': self.env.company.id})
|
|
sn3 = snObj.create({'name': 'sn3', 'product_id': self.productserial1.id, 'company_id': self.env.company.id})
|
|
sn4 = snObj.create({'name': 'sn4', 'product_id': self.productserial1.id, 'company_id': self.env.company.id})
|
|
|
|
self.env['stock.quant']._update_available_quantity(self.productserial1, self.stock_location, 1, lot_id=sn1)
|
|
self.env['stock.quant']._update_available_quantity(self.productserial1, self.stock_location, 1, lot_id=sn2)
|
|
self.env['stock.quant']._update_available_quantity(self.productserial1, self.stock_location, 1, lot_id=sn3)
|
|
self.env['stock.quant']._update_available_quantity(self.productserial1, self.stock_location, 1, lot_id=sn4)
|
|
|
|
# empty picking
|
|
delivery_picking = self.env['stock.picking'].create({
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.customer_location.id,
|
|
'picking_type_id': self.picking_type_out.id,
|
|
})
|
|
|
|
self.env['stock.move'].create({
|
|
'name': 'test_delivery_reserved_lots_1',
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.customer_location.id,
|
|
'product_id': self.productserial1.id,
|
|
'product_uom': self.uom_unit.id,
|
|
'product_uom_qty': 4,
|
|
'picking_id': delivery_picking.id,
|
|
})
|
|
|
|
delivery_picking.action_confirm()
|
|
delivery_picking.action_assign()
|
|
|
|
url = self._get_client_action_url(delivery_picking.id)
|
|
|
|
self.start_tour(url, 'test_delivery_reserved_with_sn_1', login='admin', timeout=180)
|
|
|
|
# TODO: the framework should call invalidate_cache every time a test cursor is asked or
|
|
# given back
|
|
self.env.invalidate_all()
|
|
lines = delivery_picking.move_line_ids
|
|
self.assertEqual(lines.mapped('lot_id.name'), ['sn1', 'sn2', 'sn3', 'sn4'])
|
|
self.assertEqual(lines.mapped('qty_done'), [1, 1, 1, 1])
|
|
|
|
def test_delivery_using_buttons(self):
|
|
""" Creates a delivery with 3 lines, then:
|
|
- Completes first line with "+1" button;
|
|
- Completes second line with "Add reserved quantities" button;
|
|
- Completes last line with "+1" button and scanning barcode.
|
|
Checks also written quantity on buttons is correctly updated and only
|
|
"+1" button is displayed on new line created by user.
|
|
"""
|
|
self.clean_access_rights()
|
|
|
|
# Creates a new product.
|
|
product3 = self.env['product.product'].create({
|
|
'name': 'product3',
|
|
'type': 'product',
|
|
'categ_id': self.env.ref('product.product_category_all').id,
|
|
'barcode': 'product3',
|
|
})
|
|
|
|
# Creates some quants.
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 2)
|
|
self.env['stock.quant']._update_available_quantity(self.product2, self.stock_location, 3)
|
|
self.env['stock.quant']._update_available_quantity(product3, self.stock_location, 4)
|
|
|
|
# Create the delivery transfer.
|
|
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 = 2
|
|
with delivery_form.move_ids_without_package.new() as move:
|
|
move.product_id = self.product2
|
|
move.product_uom_qty = 3
|
|
with delivery_form.move_ids_without_package.new() as move:
|
|
move.product_id = product3
|
|
move.product_uom_qty = 4
|
|
|
|
delivery_picking = delivery_form.save()
|
|
delivery_picking.action_confirm()
|
|
delivery_picking.action_assign()
|
|
|
|
url = self._get_client_action_url(delivery_picking.id)
|
|
self.start_tour(url, 'test_delivery_using_buttons', login='admin', timeout=180)
|
|
|
|
self.assertEqual(len(delivery_picking.move_line_ids), 4)
|
|
self.assertEqual(delivery_picking.move_line_ids.mapped('qty_done'), [2, 3, 4, 2])
|
|
|
|
def test_remaining_decimal_accuracy(self):
|
|
""" Checks if the remaining value of a move is correct
|
|
"""
|
|
self.clean_access_rights()
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 4)
|
|
|
|
# Create the delivery transfer.
|
|
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 = 4
|
|
|
|
delivery_picking = delivery_form.save()
|
|
delivery_picking.action_confirm()
|
|
delivery_picking.action_assign()
|
|
|
|
url = self._get_client_action_url(delivery_picking.id)
|
|
self.start_tour(url, 'test_remaining_decimal_accuracy', login='admin', timeout=90)
|
|
|
|
def test_receipt_reserved_lots_multiloc_1(self):
|
|
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_lot = self.env.ref('stock.group_production_lot')
|
|
self.env.user.write({'groups_id': [(4, grp_lot.id, 0)]})
|
|
|
|
receipts_picking = self.env['stock.picking'].create({
|
|
'location_id': self.supplier_location.id,
|
|
'location_dest_id': self.stock_location.id,
|
|
'picking_type_id': self.picking_type_in.id,
|
|
'user_id': False,
|
|
})
|
|
|
|
url = self._get_client_action_url(receipts_picking.id)
|
|
|
|
self.env['stock.move'].create({
|
|
'name': 'test_delivery_reserved_lots_1',
|
|
'location_id': self.supplier_location.id,
|
|
'location_dest_id': self.stock_location.id,
|
|
'product_id': self.productlot1.id,
|
|
'product_uom': self.uom_unit.id,
|
|
'product_uom_qty': 4,
|
|
'picking_id': receipts_picking.id,
|
|
})
|
|
|
|
# Creates lot1 and lot2 for productlot1.
|
|
lotObj = self.env['stock.lot']
|
|
lotObj.create({'name': 'lot1', 'product_id': self.productlot1.id, 'company_id': self.env.company.id})
|
|
lotObj.create({'name': 'lot2', 'product_id': self.productlot1.id, 'company_id': self.env.company.id})
|
|
|
|
receipts_picking.action_confirm()
|
|
receipts_picking.action_assign()
|
|
|
|
self.assertEqual(receipts_picking.user_id.id, False)
|
|
self.start_tour(url, 'test_receipt_reserved_lots_multiloc_1', login='admin', timeout=180)
|
|
self.assertEqual(receipts_picking.user_id.id, self.env.user.id)
|
|
self.env.invalidate_all()
|
|
lines = receipts_picking.move_line_ids.sorted(lambda ml: (ml.location_id.complete_name, ml.location_dest_id.complete_name, ml.id))
|
|
self.assertEqual(len(lines), 2)
|
|
self.assertEqual(lines.mapped('qty_done'), [2, 2])
|
|
self.assertEqual(lines.mapped('location_id.name'), ['Vendors'])
|
|
self.assertEqual(lines[0].location_dest_id.name, 'Section 1')
|
|
self.assertEqual(lines[0].lot_name, 'lot2')
|
|
self.assertEqual(lines[1].location_dest_id.name, 'Section 2')
|
|
self.assertEqual(lines[1].lot_name, 'lot1')
|
|
|
|
def test_pack_multiple_scan(self):
|
|
""" Make a reception of two products, put them in pack and validate.
|
|
Then make a delivery, scan the package two times (check the warning) and validate.
|
|
Finally, check that the package is in the customer location.
|
|
"""
|
|
self.clean_access_rights()
|
|
grp_pack = self.env.ref('stock.group_tracking_lot')
|
|
self.env.user.write({'groups_id': [(4, grp_pack.id, 0)]})
|
|
|
|
action_id = self.env.ref('stock_barcode.stock_barcode_action_main_menu')
|
|
url = "/web#action=" + str(action_id.id)
|
|
|
|
# set sequence packages to 1000 to find it easily in the tour
|
|
sequence = self.env['ir.sequence'].search([(
|
|
'code', '=', 'stock.quant.package',
|
|
)], limit=1)
|
|
sequence.write({'number_next_actual': 1000})
|
|
|
|
self.start_tour(url, 'test_pack_multiple_scan', login='admin', timeout=180)
|
|
|
|
# Check the new package is well delivered
|
|
package = self.env['stock.quant.package'].search([
|
|
('name', '=', 'PACK0001000')
|
|
])
|
|
self.assertEqual(package.location_id, self.customer_location)
|
|
|
|
def test_pack_common_content_scan(self):
|
|
""" Simulate a picking where 2 packages have the same products
|
|
inside. It should display one barcode line for each package and
|
|
not a common barcode line for both packages.
|
|
"""
|
|
self.clean_access_rights()
|
|
grp_pack = self.env.ref('stock.group_tracking_lot')
|
|
self.env.user.write({'groups_id': [(4, grp_pack.id, 0)]})
|
|
|
|
action_id = self.env.ref('stock_barcode.stock_barcode_action_main_menu')
|
|
url = "/web#action=" + str(action_id.id)
|
|
|
|
# Create a pack and 2 quants in this pack
|
|
pack1 = self.env['stock.quant.package'].create({
|
|
'name': 'PACK1',
|
|
})
|
|
pack2 = self.env['stock.quant.package'].create({
|
|
'name': 'PACK2',
|
|
})
|
|
|
|
self.env['stock.quant']._update_available_quantity(
|
|
product_id=self.product1,
|
|
location_id=self.stock_location,
|
|
quantity=5,
|
|
package_id=pack1,
|
|
)
|
|
self.env['stock.quant']._update_available_quantity(
|
|
product_id=self.product2,
|
|
location_id=self.stock_location,
|
|
quantity=1,
|
|
package_id=pack1,
|
|
)
|
|
|
|
self.env['stock.quant']._update_available_quantity(
|
|
product_id=self.product1,
|
|
location_id=self.stock_location,
|
|
quantity=5,
|
|
package_id=pack2,
|
|
)
|
|
self.env['stock.quant']._update_available_quantity(
|
|
product_id=self.product2,
|
|
location_id=self.stock_location,
|
|
quantity=1,
|
|
package_id=pack2,
|
|
)
|
|
|
|
self.start_tour(url, 'test_pack_common_content_scan', login='admin', timeout=180)
|
|
|
|
def test_pack_multiple_location(self):
|
|
""" Create a package in Shelf 1 and makes an internal transfer to move it to Shelf 2.
|
|
"""
|
|
self.clean_access_rights()
|
|
grp_pack = self.env.ref('stock.group_tracking_lot')
|
|
grp_multi_loc = self.env.ref('stock.group_stock_multi_locations')
|
|
self.env.user.write({'groups_id': [(4, grp_pack.id, 0)]})
|
|
self.env.user.write({'groups_id': [(4, grp_multi_loc.id, 0)]})
|
|
self.picking_type_internal.active = True
|
|
self.picking_type_internal.show_entire_packs = True
|
|
self.picking_type_internal.restrict_scan_dest_location = 'mandatory'
|
|
self.picking_type_internal.restrict_scan_source_location = 'mandatory'
|
|
|
|
action_id = self.env.ref('stock_barcode.stock_barcode_action_main_menu')
|
|
url = "/web#action=" + str(action_id.id)
|
|
|
|
# Create a pack and 2 quants in this pack
|
|
pack1 = self.env['stock.quant.package'].create({
|
|
'name': 'PACK0000666',
|
|
})
|
|
|
|
self.env['stock.quant']._update_available_quantity(
|
|
product_id=self.product1,
|
|
location_id=self.shelf1,
|
|
quantity=5,
|
|
package_id=pack1,
|
|
)
|
|
self.env['stock.quant']._update_available_quantity(
|
|
product_id=self.product2,
|
|
location_id=self.shelf1,
|
|
quantity=5,
|
|
package_id=pack1,
|
|
)
|
|
|
|
self.start_tour(url, 'test_pack_multiple_location', login='admin', timeout=180)
|
|
|
|
# Check the new package is well transfered
|
|
self.assertEqual(pack1.location_id, self.shelf2)
|
|
|
|
def test_pack_multiple_location_02(self):
|
|
""" Creates an internal transfer and reserves a package. Then this test will scan the
|
|
location source, the package (already in the barcode view) and the location destination.
|
|
"""
|
|
self.clean_access_rights()
|
|
grp_pack = self.env.ref('stock.group_tracking_lot')
|
|
grp_multi_loc = self.env.ref('stock.group_stock_multi_locations')
|
|
self.env.user.write({'groups_id': [(4, grp_pack.id, 0)]})
|
|
self.env.user.write({'groups_id': [(4, grp_multi_loc.id, 0)]})
|
|
|
|
# Creates a package with 1 quant in it.
|
|
pack1 = self.env['stock.quant.package'].create({
|
|
'name': 'PACK0002020',
|
|
})
|
|
self.env['stock.quant']._update_available_quantity(
|
|
product_id=self.product1,
|
|
location_id=self.shelf1,
|
|
quantity=5,
|
|
package_id=pack1,
|
|
)
|
|
|
|
# Creates an internal transfer for this package.
|
|
internal_picking = self.env['stock.picking'].create({
|
|
'location_id': self.shelf1.id,
|
|
'location_dest_id': self.shelf2.id,
|
|
'picking_type_id': self.picking_type_internal.id,
|
|
})
|
|
url = self._get_client_action_url(internal_picking.id)
|
|
|
|
self.env['stock.move'].create({
|
|
'name': 'test_delivery_reserved_2_1',
|
|
'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': 5,
|
|
'picking_id': internal_picking.id,
|
|
})
|
|
internal_picking.action_confirm()
|
|
internal_picking.action_assign()
|
|
|
|
self.start_tour(url, 'test_pack_multiple_location_02', login='admin', timeout=180)
|
|
|
|
# Checks the new package is well transfered.
|
|
self.assertEqual(pack1.location_id, self.shelf2)
|
|
|
|
def test_pack_multiple_location_03(self):
|
|
""" Creates a delivery and reserves a package. Then this test will scan
|
|
a different location source, the package should be removed.
|
|
"""
|
|
self.clean_access_rights()
|
|
grp_pack = self.env.ref('stock.group_tracking_lot')
|
|
grp_multi_loc = self.env.ref('stock.group_stock_multi_locations')
|
|
self.env.user.write({'groups_id': [(4, grp_pack.id, 0), (4, grp_multi_loc.id, 0)]})
|
|
|
|
# Creates a package with a quant in it.
|
|
pack1 = self.env['stock.quant.package'].create({
|
|
'name': 'PACK000666',
|
|
})
|
|
self.env['stock.quant']._update_available_quantity(
|
|
product_id=self.product1,
|
|
location_id=self.shelf1,
|
|
quantity=5,
|
|
package_id=pack1,
|
|
)
|
|
|
|
# Creates a delivery transfer for this package.
|
|
picking_form = Form(self.env['stock.picking'])
|
|
picking_form.picking_type_id = self.picking_type_out
|
|
picking_form.location_id = self.stock_location
|
|
with picking_form.move_ids_without_package.new() as move:
|
|
move.product_id = self.product1
|
|
move.product_uom_qty = 1
|
|
delivery_picking = picking_form.save()
|
|
delivery_picking.action_confirm()
|
|
delivery_picking.action_assign()
|
|
url = self._get_client_action_url(delivery_picking.id)
|
|
|
|
self.start_tour(url, 'test_pack_multiple_location_03', login='admin', timeout=180)
|
|
self.assertFalse(delivery_picking.move_line_ids.package_id)
|
|
|
|
def test_pack_source_location(self):
|
|
""" Put a package in shelf4. Then this test will scan
|
|
this package, the source location of line should be the same location of the package.
|
|
"""
|
|
self.clean_access_rights()
|
|
grp_pack = self.env.ref('stock.group_tracking_lot')
|
|
grp_multi_loc = self.env.ref('stock.group_stock_multi_locations')
|
|
self.env.user.write({'groups_id': [Command.link(grp_pack.id), Command.link(grp_multi_loc.id)]})
|
|
self.picking_type_internal.active = True
|
|
self.picking_type_internal.show_entire_packs = False
|
|
action_id = self.env.ref('stock_barcode.stock_barcode_action_main_menu')
|
|
url = "/web#action=" + str(action_id.id)
|
|
# Creates a package with a quant in it.
|
|
pack1 = self.env['stock.quant.package'].create({
|
|
'name': 'PACK123666',
|
|
})
|
|
self.env['stock.quant']._update_available_quantity(
|
|
product_id=self.product1,
|
|
location_id=self.shelf4,
|
|
quantity=5,
|
|
package_id=pack1,
|
|
)
|
|
|
|
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.shelf4, package_id=pack1), 5)
|
|
self.start_tour(url, 'test_pack_source_location', login='admin', timeout=180)
|
|
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.shelf4, package_id=pack1), 0)
|
|
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location, package_id=pack1), 5)
|
|
|
|
def test_put_in_pack_from_multiple_pages(self):
|
|
""" In an internal picking 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.
|
|
"""
|
|
self.clean_access_rights()
|
|
# 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'
|
|
grp_pack = self.env.ref('stock.group_tracking_lot')
|
|
grp_multi_loc = self.env.ref('stock.group_stock_multi_locations')
|
|
self.env.user.write({'groups_id': [(4, grp_pack.id, 0)]})
|
|
self.env.user.write({'groups_id': [(4, grp_multi_loc.id, 0)]})
|
|
|
|
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)
|
|
|
|
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,
|
|
})
|
|
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_picking.id,
|
|
})
|
|
|
|
url = self._get_client_action_url(internal_picking.id)
|
|
internal_picking.action_confirm()
|
|
internal_picking.action_assign()
|
|
|
|
self.start_tour(url, 'test_put_in_pack_from_multiple_pages', login='admin', timeout=180)
|
|
|
|
pack = self.env['stock.quant.package'].search([])[-1]
|
|
self.assertEqual(len(pack.quant_ids), 2)
|
|
self.assertEqual(sum(pack.quant_ids.mapped('quantity')), 4)
|
|
|
|
def test_put_in_pack_float_rounding(self):
|
|
""" Test that qtyDemand and qtyDone with the value 10.1000001 is rounded to 10.10 """
|
|
self.env.ref('base.group_user').implied_ids += self.env.ref('stock.group_production_lot')
|
|
self.clean_access_rights()
|
|
self.env['res.config.settings'].create({'group_stock_tracking_lot': True}).execute()
|
|
|
|
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 = 13.5
|
|
|
|
receipt = receipt_form.save()
|
|
receipt.action_confirm()
|
|
receipt.action_assign()
|
|
|
|
action_id = self.env.ref('stock_barcode.stock_barcode_action_main_menu')
|
|
url = "/web#action=" + str(action_id.id)
|
|
self.start_tour(url, 'test_put_in_pack_float_rounding', login='admin', timeout=180)
|
|
|
|
def test_reload_flow(self):
|
|
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)]})
|
|
|
|
action_id = self.env.ref('stock_barcode.stock_barcode_action_main_menu')
|
|
url = "/web#action=" + str(action_id.id)
|
|
|
|
self.start_tour(url, 'test_reload_flow', login='admin', timeout=180)
|
|
|
|
move_line1 = self.env['stock.move.line'].search_count([
|
|
('product_id', '=', self.product1.id),
|
|
('location_dest_id', '=', self.shelf1.id),
|
|
('location_id', '=', self.supplier_location.id),
|
|
('quantity', '=', 2),
|
|
('picked', '=', True),
|
|
])
|
|
move_line2 = self.env['stock.move.line'].search_count([
|
|
('product_id', '=', self.product2.id),
|
|
('location_dest_id', '=', self.shelf1.id),
|
|
('location_id', '=', self.supplier_location.id),
|
|
('quantity', '=', 1),
|
|
('picked', '=', True),
|
|
])
|
|
self.assertEqual(move_line1, 1)
|
|
self.assertEqual(move_line2, 1)
|
|
|
|
def test_duplicate_serial_number(self):
|
|
""" Simulate a receipt and a delivery with a product tracked by serial
|
|
number. It will try to break the ClientAction by using twice the same
|
|
serial number.
|
|
"""
|
|
self.clean_access_rights()
|
|
grp_lot = self.env.ref('stock.group_production_lot')
|
|
grp_multi_loc = self.env.ref('stock.group_stock_multi_locations')
|
|
self.env.user.write({'groups_id': [(4, grp_multi_loc.id, 0)]})
|
|
self.env.user.write({'groups_id': [(4, grp_lot.id, 0)]})
|
|
|
|
action_id = self.env.ref('stock_barcode.stock_barcode_action_main_menu')
|
|
url = "/web#action=" + str(action_id.id)
|
|
|
|
self.start_tour(url, 'test_receipt_duplicate_serial_number', login='admin', timeout=180)
|
|
|
|
self.start_tour(url, 'test_delivery_duplicate_serial_number', login='admin', timeout=180)
|
|
|
|
def test_bypass_source_scan(self):
|
|
""" Scan a lot, package, product without source location scan. """
|
|
self.clean_access_rights()
|
|
grp_multi_loc = self.env.ref('stock.group_stock_multi_locations')
|
|
grp_pack = self.env.ref('stock.group_tracking_lot')
|
|
grp_lot = self.env.ref('stock.group_production_lot')
|
|
self.env.user.write({'groups_id': [(4, grp_lot.id, 0)]})
|
|
self.env.user.write({'groups_id': [(4, grp_pack.id, 0)]})
|
|
self.env.user.write({'groups_id': [(4, grp_multi_loc.id, 0)]})
|
|
# For the purpose of this test, disable the source scan (mandatory for a deliery otherwise).
|
|
self.picking_type_out.restrict_scan_source_location = 'no'
|
|
|
|
lot1 = self.env['stock.lot'].create({'name': 'lot1', 'product_id': self.productlot1.id, 'company_id': self.env.company.id})
|
|
lot2 = self.env['stock.lot'].create({'name': 'serial1', 'product_id': self.productserial1.id, 'company_id': self.env.company.id})
|
|
|
|
pack1 = self.env['stock.quant.package'].create({
|
|
'name': 'THEPACK',
|
|
})
|
|
|
|
self.env['stock.quant']._update_available_quantity(self.productlot1, self.shelf1, 2, lot_id=lot1)
|
|
self.env['stock.quant']._update_available_quantity(self.productserial1, self.shelf2, 1, lot_id=lot2)
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.shelf2, 4, package_id=pack1)
|
|
|
|
# Creates a second pack with some qty in a location the delivery shouldn't have access.
|
|
pack2 = self.env['stock.quant.package'].create({'name': 'SUSPACK'})
|
|
other_loc = self.env['stock.location'].create({
|
|
'name': "Second Stock",
|
|
'location_id': self.stock_location.location_id.id,
|
|
'barcode': 'WH-SECOND-STOCK',
|
|
})
|
|
self.env['stock.quant']._update_available_quantity(self.product1, other_loc, 4, package_id=pack2)
|
|
|
|
delivery_picking = self.env['stock.picking'].create({
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.customer_location.id,
|
|
'picking_type_id': self.picking_type_out.id,
|
|
})
|
|
url = self._get_client_action_url(delivery_picking.id)
|
|
|
|
self.env['stock.move'].create({
|
|
'name': 'test_bypass_source_scan_1_1',
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.customer_location.id,
|
|
'product_id': self.productserial1.id,
|
|
'product_uom': self.uom_unit.id,
|
|
'product_uom_qty': 1,
|
|
'picking_id': delivery_picking.id,
|
|
})
|
|
self.env['stock.move'].create({
|
|
'name': 'test_bypass_source_scan_1_2',
|
|
'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': 2,
|
|
'picking_id': delivery_picking.id,
|
|
})
|
|
self.env['stock.move'].create({
|
|
'name': 'test_bypass_source_scan_1_3',
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.customer_location.id,
|
|
'product_id': self.product1.id,
|
|
'product_uom': self.uom_unit.id,
|
|
'product_uom_qty': 4,
|
|
'picking_id': delivery_picking.id,
|
|
})
|
|
delivery_picking.action_confirm()
|
|
delivery_picking.action_assign()
|
|
|
|
self.start_tour(url, 'test_bypass_source_scan', login='admin', timeout=180)
|
|
|
|
def test_put_in_pack_from_different_location(self):
|
|
""" Scans two different products from two different locations, then put them in pack and
|
|
scans a destination location. Checks the package is in the right location.
|
|
"""
|
|
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_internal.active = True
|
|
self.picking_type_internal.restrict_scan_source_location = 'no'
|
|
self.picking_type_internal.restrict_scan_dest_location = 'optional'
|
|
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_from_different_location',
|
|
'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,
|
|
})
|
|
self.env['stock.move'].create({
|
|
'name': 'test_put_in_pack_from_different_location2',
|
|
'location_id': self.shelf3.id,
|
|
'location_dest_id': self.shelf2.id,
|
|
'product_id': self.product2.id,
|
|
'product_uom': self.uom_unit.id,
|
|
'product_uom_qty': 1,
|
|
'picking_id': internal_picking.id,
|
|
})
|
|
# 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_client_action_url(internal_picking.id)
|
|
internal_picking.action_confirm()
|
|
internal_picking.action_assign()
|
|
|
|
self.start_tour(url, 'test_put_in_pack_from_different_location', login='admin', timeout=180)
|
|
pack = internal_picking.move_line_ids.result_package_id
|
|
self.assertEqual(len(pack.quant_ids), 2)
|
|
self.assertEqual(pack.location_id, self.shelf2)
|
|
|
|
def test_put_in_pack_before_dest(self):
|
|
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_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,
|
|
})
|
|
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_picking.id,
|
|
})
|
|
|
|
url = self._get_client_action_url(internal_picking.id)
|
|
internal_picking.action_confirm()
|
|
internal_picking.action_assign()
|
|
|
|
self.start_tour(url, 'test_put_in_pack_before_dest', login='admin', timeout=180)
|
|
pack = self.env['stock.quant.package'].search([])[-1]
|
|
self.assertEqual(len(pack.quant_ids), 2)
|
|
self.assertEqual(pack.location_id, self.shelf2)
|
|
|
|
def test_put_in_pack_scan_package(self):
|
|
""" Put in pack a product line, then scan the newly created package to
|
|
assign it to another 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.env['stock.quant']._update_available_quantity(self.product1, 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.shelf1, 1)
|
|
|
|
# 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
|
|
|
|
# Creates a delivery with three move lines: two 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 = 2
|
|
with delivery_form.move_ids_without_package.new() as move:
|
|
move.product_id = self.product2
|
|
move.product_uom_qty = 1
|
|
|
|
delivery = delivery_form.save()
|
|
delivery.action_confirm()
|
|
delivery.action_assign()
|
|
|
|
url = self._get_client_action_url(delivery.id)
|
|
self.start_tour(url, 'test_put_in_pack_scan_package', login='admin', timeout=180)
|
|
|
|
self.assertEqual(delivery.state, 'done')
|
|
self.assertEqual(len(delivery.move_line_ids), 3)
|
|
for move_line in delivery.move_line_ids:
|
|
self.assertEqual(move_line.result_package_id.name, 'PACK0000001')
|
|
|
|
def test_put_in_pack_new_lines(self):
|
|
"""
|
|
Receive a product P, put it in a pack PK and validates the receipt.
|
|
Then, do the same a second time with the same package PK
|
|
"""
|
|
self.clean_access_rights()
|
|
grp_pack = self.env.ref('stock.group_tracking_lot')
|
|
self.env.user.write({'groups_id': [(4, grp_pack.id, 0)]})
|
|
|
|
receipt01 = self.env['stock.picking'].create({
|
|
'location_id': self.supplier_location.id,
|
|
'location_dest_id': self.stock_location.id,
|
|
'picking_type_id': self.picking_type_in.id,
|
|
})
|
|
url = self._get_client_action_url(receipt01.id)
|
|
self.start_tour(url, 'test_put_in_pack_new_lines', login='admin', timeout=180)
|
|
|
|
self.assertRecordValues(receipt01.move_line_ids, [
|
|
{'product_id': self.product1.id, 'qty_done': 1, 'result_package_id': self.package.id},
|
|
])
|
|
self.assertEqual(self.package.quant_ids.available_quantity, 1)
|
|
|
|
receipt02 = self.env['stock.picking'].create({
|
|
'location_id': self.supplier_location.id,
|
|
'location_dest_id': self.stock_location.id,
|
|
'picking_type_id': self.picking_type_in.id,
|
|
})
|
|
url = self._get_client_action_url(receipt02.id)
|
|
self.start_tour(url, 'test_put_in_pack_new_lines', login='admin', timeout=180)
|
|
|
|
self.assertRecordValues(receipt02.move_line_ids, [
|
|
{'product_id': self.product1.id, 'qty_done': 1, 'result_package_id': self.package.id},
|
|
])
|
|
self.assertEqual(self.package.quant_ids.available_quantity, 2)
|
|
|
|
def test_highlight_packs(self):
|
|
self.clean_access_rights()
|
|
grp_pack = self.env.ref('stock.group_tracking_lot')
|
|
self.env.user.write({'groups_id': [(4, grp_pack.id, 0)]})
|
|
|
|
pack1 = self.env['stock.quant.package'].create({
|
|
'name': 'PACK001',
|
|
})
|
|
pack2 = self.env['stock.quant.package'].create({
|
|
'name': 'PACK002',
|
|
})
|
|
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 4, package_id=pack1)
|
|
self.env['stock.quant']._update_available_quantity(self.product2, self.stock_location, 4, package_id=pack1)
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 2, package_id=pack2)
|
|
self.env['stock.quant']._update_available_quantity(self.product2, self.stock_location, 2, package_id=pack2)
|
|
|
|
out_picking = self.env['stock.picking'].create({
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.customer_location.id,
|
|
'picking_type_id': self.picking_type_out.id,
|
|
'state': 'draft',
|
|
})
|
|
|
|
self.picking_type_out.show_entire_packs = True
|
|
|
|
self.env['stock.package_level'].create({
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.customer_location.id,
|
|
'package_id': pack1.id,
|
|
'is_done': False,
|
|
'picking_id': out_picking.id,
|
|
'company_id': self.env.company.id,
|
|
})
|
|
|
|
url = self._get_client_action_url(out_picking.id)
|
|
out_picking.action_confirm()
|
|
out_picking.action_assign()
|
|
|
|
self.start_tour(url, 'test_highlight_packs', login='admin', timeout=180)
|
|
|
|
def test_picking_owner_scan_package(self):
|
|
grp_owner = self.env.ref('stock.group_tracking_owner')
|
|
grp_pack = self.env.ref('stock.group_tracking_lot')
|
|
self.env.user.write({'groups_id': [(4, grp_pack.id, 0)]})
|
|
self.env.user.write({'groups_id': [(4, grp_owner.id, 0)]})
|
|
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 7, package_id=self.package, owner_id=self.owner)
|
|
action_id = self.env.ref('stock_barcode.stock_barcode_action_main_menu')
|
|
url = "/web#action=" + str(action_id.id)
|
|
|
|
self.start_tour(url, 'test_picking_owner_scan_package', login='admin', timeout=180)
|
|
|
|
move_line = self.env['stock.move.line'].search([('product_id', '=', self.product1.id)], limit=1)
|
|
self.assertTrue(move_line)
|
|
|
|
line_owner = move_line.owner_id
|
|
self.assertEqual(line_owner.id, self.owner.id)
|
|
|
|
def test_picking_type_mandatory_scan_settings(self):
|
|
''' Makes some operations with different scan's settings.'''
|
|
self.clean_access_rights()
|
|
|
|
# Enables packages and multi-locations.
|
|
grp_multi_loc = self.env.ref('stock.group_stock_multi_locations')
|
|
grp_lot = self.env.ref('stock.group_production_lot')
|
|
self.env.user.write({'groups_id': [(4, grp_multi_loc.id, 0), (4, grp_lot.id, 0)]})
|
|
# Creates a product without barcode to check it can always be processed regardless the config.
|
|
product_without_barcode = self.env['product.product'].create({
|
|
'name': 'Barcodeless Product',
|
|
'type': 'product',
|
|
'categ_id': self.env.ref('product.product_category_all').id,
|
|
})
|
|
# Adds products' quantities.
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.shelf1, 8)
|
|
self.env['stock.quant']._update_available_quantity(product_without_barcode, self.shelf1, 8)
|
|
|
|
# First config: products must be scanned, empty picking can't be immediatly validated,
|
|
# locations can't be scanned, no put in pack.
|
|
self.picking_type_internal.barcode_validation_after_dest_location = False
|
|
self.picking_type_internal.barcode_validation_all_product_packed = False
|
|
self.picking_type_internal.barcode_validation_full = False
|
|
self.picking_type_internal.restrict_scan_product = True
|
|
self.picking_type_internal.restrict_put_in_pack = 'optional'
|
|
self.picking_type_internal.restrict_scan_source_location = 'no'
|
|
self.picking_type_internal.restrict_scan_dest_location = 'no'
|
|
|
|
# Creates an internal transfer, from WH/Stock/Shelf 1 to WH/Stock.
|
|
picking_form = Form(self.env['stock.picking'])
|
|
picking_form.picking_type_id = self.picking_type_internal
|
|
with picking_form.move_ids_without_package.new() as move:
|
|
move.product_id = self.product1
|
|
move.product_uom_qty = 4
|
|
with picking_form.move_ids_without_package.new() as move:
|
|
move.product_id = product_without_barcode
|
|
move.product_uom_qty = 4
|
|
|
|
picking_internal_1 = picking_form.save()
|
|
picking_internal_1.action_confirm()
|
|
picking_internal_1.action_assign()
|
|
|
|
url = self._get_client_action_url(picking_internal_1.id)
|
|
self.start_tour(url, 'test_picking_type_mandatory_scan_settings_pick_int_1', login='admin', timeout=180)
|
|
self.assertEqual(picking_internal_1.state, 'done')
|
|
|
|
# Second picking: change the config (same than before but locations MUST be scanned).
|
|
self.picking_type_internal.restrict_scan_source_location = 'mandatory'
|
|
self.picking_type_internal.restrict_scan_dest_location = 'mandatory'
|
|
# Creates an internal transfer, from WH/Stock/Shelf 1 to WH/Stock.
|
|
picking_form = Form(self.env['stock.picking'])
|
|
picking_form.picking_type_id = self.picking_type_internal
|
|
with picking_form.move_ids_without_package.new() as move:
|
|
move.product_id = self.product1
|
|
move.product_uom_qty = 4
|
|
with picking_form.move_ids_without_package.new() as move:
|
|
move.product_id = product_without_barcode
|
|
move.product_uom_qty = 4
|
|
|
|
picking_internal_2 = picking_form.save()
|
|
picking_internal_2.action_confirm()
|
|
picking_internal_2.action_assign()
|
|
|
|
url = self._get_client_action_url(picking_internal_2.id)
|
|
self.start_tour(url, 'test_picking_type_mandatory_scan_settings_pick_int_2', login='admin', timeout=180)
|
|
self.assertEqual(picking_internal_2.state, 'done')
|
|
|
|
def test_receipt_scan_package_and_location_after_group_of_product(self):
|
|
""" This test ensures when a package or a destination is scanned after a group of product,
|
|
if picking type's destination/package destination is on "After group of product" (optional)
|
|
then all and only previously scanned line will be packed/go to this location.
|
|
"""
|
|
# Enables packages and multi-locations.
|
|
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_lot = self.env.ref('stock.group_production_lot')
|
|
self.env.user.write({'groups_id': [(4, grp_lot.id, 0)]})
|
|
grp_pack = self.env.ref('stock.group_tracking_lot')
|
|
self.env.user.write({'groups_id': [(4, grp_pack.id, 0)]})
|
|
# Creates a product without barcode to check it will count even if not
|
|
# scanned but processed through the button.
|
|
product_without_barcode = self.env['product.product'].create({
|
|
'name': 'Barcodeless Product',
|
|
'type': 'product',
|
|
'categ_id': self.env.ref('product.product_category_all').id,
|
|
})
|
|
# Create an empty package.
|
|
package = self.env['stock.quant.package'].create({'name': 'pack-128'})
|
|
|
|
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 = 4
|
|
with picking_form.move_ids_without_package.new() as move:
|
|
move.product_id = product_without_barcode
|
|
move.product_uom_qty = 4
|
|
with picking_form.move_ids_without_package.new() as move:
|
|
move.product_id = self.productlot1
|
|
move.product_uom_qty = 6
|
|
|
|
picking_receipt = picking_form.save()
|
|
picking_receipt.action_confirm()
|
|
picking_receipt.action_assign()
|
|
|
|
url = self._get_client_action_url(picking_receipt.id)
|
|
self.start_tour(url, 'test_receipt_scan_package_and_location_after_group_of_product', login='admin', timeout=180)
|
|
self.assertEqual(picking_receipt.state, 'done')
|
|
(lot1, lot2, lot3) = self.env['stock.lot'].search([
|
|
('product_id', '=', self.productlot1.id), ('name', 'in', ['lot-01', 'lot-02', 'lot-03'])
|
|
])
|
|
move_lines = picking_receipt.move_line_ids.sorted(lambda ml: (ml.product_id.id, ml.location_dest_id.id))
|
|
self.assertRecordValues(move_lines, [
|
|
{'product_id': self.product1.id, 'location_dest_id': self.shelf3.id, 'qty_done': 2, 'lot_id': False, 'result_package_id': package.id},
|
|
{'product_id': self.product1.id, 'location_dest_id': self.shelf1.id, 'qty_done': 2, 'lot_id': False, 'result_package_id': False},
|
|
{'product_id': self.productlot1.id, 'location_dest_id': self.shelf3.id, 'qty_done': 2, 'lot_id': lot3.id, 'result_package_id': package.id},
|
|
{'product_id': self.productlot1.id, 'location_dest_id': self.shelf1.id, 'qty_done': 2, 'lot_id': lot1.id, 'result_package_id': False},
|
|
{'product_id': self.productlot1.id, 'location_dest_id': self.shelf1.id, 'qty_done': 1, 'lot_id': lot2.id, 'result_package_id': False},
|
|
{'product_id': self.productlot1.id, 'location_dest_id': self.shelf2.id, 'qty_done': 1, 'lot_id': lot2.id, 'result_package_id': False},
|
|
{'product_id': product_without_barcode.id, 'location_dest_id': self.shelf1.id, 'qty_done': 4, 'lot_id': False, 'result_package_id': False},
|
|
])
|
|
|
|
def test_picking_type_mandatory_scan_product_packaging(self):
|
|
""" Check a product's packaging can also be scanned when the scan of a product is mandatory.
|
|
"""
|
|
self.clean_access_rights()
|
|
group_packaging = self.env.ref('product.group_stock_packaging')
|
|
self.env.user.write({'groups_id': [(4, group_packaging.id)]})
|
|
self.picking_type_in.restrict_scan_product = True
|
|
self.env['product.packaging'].create({
|
|
'barcode': 'product1x10',
|
|
'name': "product1 x10",
|
|
'product_id': self.product1.id,
|
|
'qty': 10,
|
|
})
|
|
receipt = self.env['stock.picking'].create({
|
|
'location_id': self.supplier_location.id,
|
|
'location_dest_id': self.stock_location.id,
|
|
'picking_type_id': self.picking_type_in.id,
|
|
'move_ids': [Command.create({
|
|
'name': 'product1 x 10',
|
|
'location_id': self.supplier_location.id,
|
|
'location_dest_id': self.stock_location.id,
|
|
'product_id': self.product1.id,
|
|
'product_uom': self.product1.uom_id.id,
|
|
'product_uom_qty': 10,
|
|
})],
|
|
})
|
|
receipt.action_confirm()
|
|
url = self._get_client_action_url(receipt.id)
|
|
self.start_tour(url, 'test_picking_type_mandatory_scan_product_packaging', login='admin')
|
|
|
|
def test_picking_type_mandatory_scan_complete_flux(self):
|
|
""" From the receipt to the delivery, make a complete flux with each
|
|
picking types having their own barcode's settings:
|
|
- Starts by receive multiple products (some of them are tracked);
|
|
- Stores each product in a different location;
|
|
- Makes a picking operation;
|
|
- Then makes a packing operation and put all products in pack;
|
|
- And finally, does the delivery.
|
|
"""
|
|
def create_picking(picking_type):
|
|
picking_form = Form(self.env['stock.picking'])
|
|
picking_form.picking_type_id = picking_type
|
|
with picking_form.move_ids_without_package.new() as move:
|
|
move.product_id = self.product1
|
|
move.product_uom_qty = 2
|
|
with picking_form.move_ids_without_package.new() as move:
|
|
move.product_id = self.product2
|
|
move.product_uom_qty = 1
|
|
with picking_form.move_ids_without_package.new() as move:
|
|
move.product_id = product_without_barcode
|
|
move.product_uom_qty = 1
|
|
with picking_form.move_ids_without_package.new() as move:
|
|
move.product_id = self.productserial1
|
|
move.product_uom_qty = 3
|
|
with picking_form.move_ids_without_package.new() as move:
|
|
move.product_id = self.productlot1
|
|
move.product_uom_qty = 6
|
|
return picking_form.save()
|
|
|
|
self.clean_access_rights()
|
|
# Creates a product without barcode to check it can always be processed regardless the config.
|
|
product_without_barcode = self.env['product.product'].create({
|
|
'name': 'Barcodeless Product',
|
|
'type': 'product',
|
|
'categ_id': self.env.ref('product.product_category_all').id,
|
|
})
|
|
|
|
# Enables packages, multi-locations and multiple steps routes.
|
|
self.env.user.write({'groups_id': [(4, self.env.ref('stock.group_production_lot').id, 0)]})
|
|
self.env.user.write({'groups_id': [(4, self.env.ref('stock.group_tracking_lot').id, 0)]})
|
|
self.env.user.write({'groups_id': [(4, self.env.ref('stock.group_stock_multi_locations').id, 0)]})
|
|
self.env.user.write({'groups_id': [(4, self.env.ref('stock.group_adv_location').id, 0)]})
|
|
warehouse = self.env.ref('stock.warehouse0')
|
|
warehouse.reception_steps = 'two_steps'
|
|
warehouse.delivery_steps = 'pick_pack_ship'
|
|
|
|
# Creates two cluster packs.
|
|
self.env['stock.quant.package'].create({
|
|
'name': 'cluster-pack-01',
|
|
'package_use': 'reusable',
|
|
})
|
|
self.env['stock.quant.package'].create({
|
|
'name': 'cluster-pack-02',
|
|
'package_use': 'reusable',
|
|
})
|
|
# 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
|
|
|
|
# Configures the picking type's scan settings.
|
|
# Receipt: no put in pack, can not be directly validate.
|
|
self.picking_type_in.barcode_validation_full = False
|
|
self.picking_type_in.restrict_put_in_pack = 'no'
|
|
# Storage (internal transfer): no put in pack, scan dest. after each product.
|
|
self.picking_type_internal.barcode_validation_full = False
|
|
self.picking_type_internal.restrict_put_in_pack = 'no'
|
|
self.picking_type_internal.restrict_scan_dest_location = 'mandatory'
|
|
# Pick: source mandatory, lots reserved only.
|
|
warehouse.pick_type_id.barcode_validation_full = False
|
|
warehouse.pick_type_id.restrict_scan_source_location = 'mandatory'
|
|
warehouse.pick_type_id.restrict_put_in_pack = 'mandatory' # Will use cluster packs.
|
|
warehouse.pick_type_id.restrict_scan_tracking_number = 'mandatory'
|
|
warehouse.pick_type_id.restrict_scan_dest_location = 'no'
|
|
# Pack: pack after group, all products have to be packed to be validate.
|
|
warehouse.pack_type_id.restrict_put_in_pack = 'optional'
|
|
warehouse.pack_type_id.restrict_scan_tracking_number = 'mandatory'
|
|
warehouse.pack_type_id.barcode_validation_all_product_packed = True
|
|
warehouse.pick_type_id.restrict_scan_dest_location = 'no'
|
|
# Delivery: pack after group, all products have to be packed to be validate.
|
|
self.picking_type_out.restrict_put_in_pack = 'optional'
|
|
self.picking_type_out.restrict_scan_tracking_number = 'mandatory'
|
|
self.picking_type_out.barcode_validation_all_product_packed = True
|
|
self.picking_type_out.show_entire_packs = True
|
|
|
|
# Creates and assigns the receipt.
|
|
picking_receipt = create_picking(self.picking_type_in)
|
|
picking_receipt.action_confirm()
|
|
picking_receipt.action_assign()
|
|
# Get the storage operation (automatically created by the receipt).
|
|
picking_internal = picking_receipt.move_ids.move_dest_ids.picking_id
|
|
|
|
# Creates the pick, pack, ship.
|
|
picking_pick = create_picking(warehouse.pick_type_id)
|
|
picking_pack = create_picking(warehouse.pack_type_id)
|
|
picking_delivery = create_picking(self.picking_type_out)
|
|
picking_pack.location_dest_id = picking_delivery.location_id
|
|
|
|
# Process each picking one by one.
|
|
url = self._get_client_action_url(picking_receipt.id)
|
|
self.start_tour(url, 'test_picking_type_mandatory_scan_complete_flux_receipt', login='admin', timeout=180)
|
|
self.assertEqual(picking_receipt.state, 'done')
|
|
|
|
url = self._get_client_action_url(picking_internal.id)
|
|
self.start_tour(url, 'test_picking_type_mandatory_scan_complete_flux_internal', login='admin', timeout=180)
|
|
self.assertEqual(picking_internal.state, 'done')
|
|
|
|
picking_pick.action_confirm()
|
|
picking_pick.action_assign()
|
|
url = self._get_client_action_url(picking_pick.id)
|
|
self.start_tour(url, 'test_picking_type_mandatory_scan_complete_flux_pick', login='admin', timeout=180)
|
|
self.assertEqual(picking_pick.state, 'done')
|
|
|
|
picking_pack.action_confirm()
|
|
picking_pack.action_assign()
|
|
for move_line in picking_pack.move_line_ids: # TODO: shouldn't have to do that, reusable packages shouldn't be set in `result_package_id` for the next move.
|
|
move_line.result_package_id = False
|
|
url = self._get_client_action_url(picking_pack.id)
|
|
self.start_tour(url, 'test_picking_type_mandatory_scan_complete_flux_pack', login='admin', timeout=180)
|
|
self.assertEqual(picking_pack.state, 'done')
|
|
|
|
picking_delivery.action_confirm()
|
|
picking_delivery.action_assign()
|
|
url = self._get_client_action_url(picking_delivery.id)
|
|
self.start_tour(url, 'test_picking_type_mandatory_scan_complete_flux_delivery', login='admin', timeout=180)
|
|
self.assertEqual(picking_delivery.state, 'done')
|
|
|
|
def test_procurement_backorder(self):
|
|
self.clean_access_rights()
|
|
product_a, _product_b = self.env['product.product'].create([{
|
|
'name': p_name,
|
|
'type': 'product',
|
|
'barcode': p_name,
|
|
} for p_name in ['PA', 'PB']])
|
|
|
|
customer = self.env["res.partner"].create({"name": "Customer"})
|
|
proc_group = self.env["procurement.group"].create({"partner_id": customer.id})
|
|
|
|
procurement = self.env["procurement.group"].Procurement(
|
|
product_a, 1, product_a.uom_id,
|
|
self.env.ref('stock.stock_location_customers'),
|
|
product_a.name,
|
|
"/",
|
|
self.env.company,
|
|
{
|
|
"warehouse_id": self.env['stock.warehouse'].search([], limit=1),
|
|
"group_id": proc_group,
|
|
}
|
|
)
|
|
self.env["procurement.group"].run([procurement])
|
|
|
|
move = self.env['stock.move'].search([('product_id', '=', product_a.id)], limit=1)
|
|
url = self._get_client_action_url(move.picking_id.id)
|
|
self.start_tour(url, 'test_procurement_backorder', login='admin', timeout=99)
|
|
self.assertEqual(len(proc_group.stock_move_ids), 2)
|
|
|
|
def test_receipt_delete_button(self):
|
|
""" Scan products that not part of a receipt. Check that products not part of original receipt
|
|
can be deleted, but the products that are part of the original receipt cannot be deleted.
|
|
"""
|
|
self.clean_access_rights()
|
|
receipt_picking = self.env['stock.picking'].create({
|
|
'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({
|
|
'name': 'test_receipt_1',
|
|
'location_id': self.supplier_location.id,
|
|
'location_dest_id': self.stock_location.id,
|
|
'product_id': self.product1.id,
|
|
'product_uom': self.product1.uom_id.id,
|
|
'product_uom_qty': 1,
|
|
'picking_id': receipt_picking.id,
|
|
'picking_type_id': self.picking_type_in.id,
|
|
})
|
|
# extra product to test that deleting works
|
|
self.env['product.product'].create({
|
|
'name': 'product3',
|
|
'type': 'product',
|
|
'categ_id': self.env.ref('product.product_category_all').id,
|
|
'barcode': 'product3',
|
|
})
|
|
|
|
url = self._get_client_action_url(receipt_picking.id)
|
|
receipt_picking.action_confirm()
|
|
self.start_tour(url, 'test_receipt_delete_button', login='admin', timeout=180)
|
|
self.assertEqual(len(receipt_picking.move_line_ids), 2, "2 lines expected: product1 + product2")
|
|
|
|
def test_scrap(self):
|
|
""" Checks the scrap button is displayed for when it's possible to scrap
|
|
and the corresponding barcode command follows the same rules."""
|
|
# Creates a receipt and a delivery.
|
|
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.product1
|
|
move.product_uom_qty = 1
|
|
receipt_picking = receipt_form.save()
|
|
receipt_picking.action_confirm()
|
|
receipt_picking.action_assign()
|
|
receipt_picking.name = "receipt_scrap_test"
|
|
|
|
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
|
|
delivery_picking = delivery_form.save()
|
|
delivery_picking.action_confirm()
|
|
delivery_picking.action_assign()
|
|
delivery_picking.name = "delivery_scrap_test"
|
|
# Opens the barcode main menu to be able to open the pickings by scanning their name.
|
|
action_id = self.env.ref('stock_barcode.stock_barcode_action_main_menu')
|
|
url = "/web#action=" + str(action_id.id)
|
|
self.start_tour(url, "test_scrap", login="admin", timeout=180)
|
|
|
|
def test_show_entire_package(self):
|
|
""" Enables 'Move Entire Packages' for delivery and then creates two deliveries:
|
|
- One where we use package level;
|
|
- One where we use move without package.
|
|
Then, checks it's the right type of line who is shown in the Barcode App."""
|
|
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
|
|
package1 = self.env['stock.quant.package'].create({'name': 'package001'})
|
|
package2 = self.env['stock.quant.package'].create({'name': 'package002'})
|
|
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 4, package_id=package1)
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 4, package_id=package2)
|
|
|
|
delivery_with_package_level = self.env['stock.picking'].create({
|
|
'name': "Delivery with Package Level",
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.customer_location.id,
|
|
'picking_type_id': self.picking_type_out.id,
|
|
'state': 'draft',
|
|
})
|
|
self.env['stock.package_level'].create({
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.customer_location.id,
|
|
'package_id': package1.id,
|
|
'is_done': False,
|
|
'picking_id': delivery_with_package_level.id,
|
|
'company_id': self.env.company.id,
|
|
})
|
|
delivery_with_package_level.action_confirm()
|
|
delivery_with_package_level.action_assign()
|
|
|
|
delivery_with_move = self.env['stock.picking'].create({
|
|
'name': "Delivery with Stock Move",
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.customer_location.id,
|
|
'picking_type_id': self.picking_type_out.id,
|
|
'state': 'draft',
|
|
})
|
|
self.env['stock.move'].create({
|
|
'name': 'test_show_entire_package',
|
|
'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': delivery_with_move.id,
|
|
})
|
|
delivery_with_move.action_confirm()
|
|
delivery_with_move.action_assign()
|
|
|
|
action = self.env["ir.actions.actions"]._for_xml_id("stock_barcode.stock_barcode_action_main_menu")
|
|
url = '/web#action=%s' % action['id']
|
|
delivery_with_package_level.action_confirm()
|
|
delivery_with_package_level.action_assign()
|
|
|
|
self.assertFalse(delivery_with_package_level.package_level_ids.is_done)
|
|
self.start_tour(url, 'test_show_entire_package', login='admin', timeout=180)
|
|
self.assertTrue(delivery_with_package_level.package_level_ids.is_done)
|
|
|
|
def test_define_the_destination_package(self):
|
|
"""
|
|
Suppose a picking that moves a product from a package to another one
|
|
This test ensures that the user can scans the destination package
|
|
"""
|
|
self.clean_access_rights()
|
|
group_pack = self.env.ref('stock.group_tracking_lot')
|
|
self.env.user.write({'groups_id': [(4, group_pack.id, 0)]})
|
|
|
|
pack01, pack02 = self.env['stock.quant.package'].create([{
|
|
'name': name,
|
|
} for name in ('PACK01', 'PACK02')])
|
|
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 2, package_id=pack01)
|
|
|
|
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
|
|
delivery = picking_form.save()
|
|
delivery.action_confirm()
|
|
|
|
url = self._get_client_action_url(delivery.id)
|
|
self.start_tour(url, 'test_define_the_destination_package', login='admin', timeout=180)
|
|
|
|
self.assertRecordValues(delivery.move_line_ids, [
|
|
{'product_id': self.product1.id, 'qty_done': 1, 'result_package_id': pack02.id, 'state': 'done'},
|
|
])
|
|
|
|
def test_avoid_useless_line_creation(self):
|
|
"""
|
|
Suppose
|
|
- the option "Create New Lots/Serial Numbers" disabled
|
|
- a tracked product P with an available lot L
|
|
On a delivery, a user scans L (it should add a line)
|
|
Then, the user scans a non-existing lot LX (it should not create any line)
|
|
"""
|
|
self.clean_access_rights()
|
|
group_lot = self.env.ref('stock.group_production_lot')
|
|
self.env.user.write({'groups_id': [(4, group_lot.id, 0)]})
|
|
|
|
lot01 = self.env['stock.lot'].create({
|
|
'name': "LOT01",
|
|
'product_id': self.productlot1.id,
|
|
'company_id': self.env.company.id,
|
|
})
|
|
|
|
self.env['stock.quant']._update_available_quantity(self.productlot1, self.stock_location, 1, lot_id=lot01)
|
|
|
|
picking_form = Form(self.env['stock.picking'])
|
|
picking_form.picking_type_id = self.picking_type_out
|
|
delivery = picking_form.save()
|
|
|
|
url = self._get_client_action_url(delivery.id)
|
|
self.start_tour(url, 'test_avoid_useless_line_creation', login='admin', timeout=180)
|
|
|
|
self.assertRecordValues(delivery.move_ids, [
|
|
{'product_id': self.productlot1.id, 'lot_ids': lot01.ids, 'quantity': 1},
|
|
])
|
|
|
|
def test_split_line_reservation(self):
|
|
""" Tests new lines created when a line is split to take
|
|
from qty in a different location than the reserved
|
|
The following cases:
|
|
- productlot1 available at given location
|
|
- product1 partially available at given location
|
|
- product2 not available
|
|
"""
|
|
self.clean_access_rights()
|
|
self.env.user.write({'groups_id': [(4, self.env.ref('stock.group_production_lot').id, 0)]})
|
|
self.env.user.write({'groups_id': [(4, self.env.ref('stock.group_tracking_lot').id, 0)]})
|
|
self.env.user.write({'groups_id': [(4, self.env.ref('stock.group_stock_multi_locations').id, 0)]})
|
|
self.env.user.write({'groups_id': [(4, self.env.ref('stock.group_adv_location').id, 0)]})
|
|
lot01 = self.env['stock.lot'].create({
|
|
'name': "LOT01",
|
|
'product_id': self.productlot1.id,
|
|
'company_id': self.env.company.id,
|
|
})
|
|
|
|
lot02 = self.env['stock.lot'].create({
|
|
'name': "LOT02",
|
|
'product_id': self.productlot1.id,
|
|
'company_id': self.env.company.id,
|
|
})
|
|
|
|
lot03 = self.env['stock.lot'].create({
|
|
'name': "LOT03",
|
|
'product_id': self.productlot1.id,
|
|
'company_id': self.env.company.id,
|
|
})
|
|
# all 3 products are available in WH-STOCK
|
|
self.env['stock.quant']._update_available_quantity(self.productlot1, self.stock_location, 5, lot_id=lot01)
|
|
self.env['stock.quant']._update_available_quantity(self.productlot1, self.stock_location, 5, lot_id=lot02)
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 5)
|
|
self.env['stock.quant']._update_available_quantity(self.product2, self.stock_location, 5)
|
|
# lots are available in shelfs
|
|
self.env['stock.quant']._update_available_quantity(self.productlot1, self.shelf1, 2, lot_id=lot02)
|
|
self.env['stock.quant']._update_available_quantity(self.productlot1, self.shelf2, 2, lot_id=lot03)
|
|
# product 1 has some qty in shelf1
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.shelf1, 1)
|
|
# create delivery
|
|
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.productlot1
|
|
move.product_uom_qty = 5
|
|
with picking_form.move_ids_without_package.new() as move:
|
|
move.product_id = self.product1
|
|
move.product_uom_qty = 4
|
|
with picking_form.move_ids_without_package.new() as move:
|
|
move.product_id = self.product2
|
|
move.product_uom_qty = 3
|
|
|
|
delivery = picking_form.save()
|
|
delivery.action_confirm()
|
|
delivery.action_assign()
|
|
|
|
url = self._get_client_action_url(delivery.id)
|
|
self.start_tour(url, 'test_split_line_reservation', login='admin', timeout=180)
|
|
productlot_mls = delivery.move_ids.filtered(lambda m: m.product_id.id == self.productlot1.id).move_line_ids
|
|
product1_mls = delivery.move_ids.filtered(lambda m: m.product_id.id == self.product1.id).move_line_ids
|
|
product2_mls = delivery.move_ids.filtered(lambda m: m.product_id.id == self.product2.id).move_line_ids
|
|
|
|
# Checks the move lines' quantities.
|
|
self.assertRecordValues(productlot_mls, [
|
|
{'quantity': 1, 'picked': True, 'lot_id': lot01.id, 'location_id': self.stock_location.id},
|
|
{'quantity': 1, 'picked': True, 'lot_id': lot02.id, 'location_id': self.stock_location.id},
|
|
{'quantity': 2, 'picked': True, 'lot_id': lot02.id, 'location_id': self.shelf1.id},
|
|
{'quantity': 1, 'picked': True, 'lot_id': lot03.id, 'location_id': self.shelf2.id},
|
|
])
|
|
self.assertRecordValues(product1_mls, [
|
|
{'quantity': 2, 'picked': True, 'location_id': self.stock_location.id},
|
|
# Only 1 quantity was available for reservation in shelf1
|
|
{'quantity': 2, 'picked': True, 'location_id': self.shelf1.id},
|
|
])
|
|
self.assertRecordValues(product2_mls, [
|
|
{'quantity': 2, 'picked': True, 'location_id': self.stock_location.id},
|
|
# No qty to reserve in shelf1.
|
|
{'quantity': 1, 'picked': True, 'location_id': self.shelf1.id},
|
|
])
|
|
|
|
def test_split_line_on_destination_scan(self):
|
|
""" Ensures a non-complete line is split when a destination is scanned. """
|
|
self.clean_access_rights()
|
|
self.env.user.write({'groups_id': [(4, self.env.ref('stock.group_stock_multi_locations').id, 0)]})
|
|
self.picking_type_internal.restrict_scan_dest_location = 'mandatory'
|
|
self.picking_type_internal.restrict_scan_source_location = 'mandatory'
|
|
# Creates a receipt for 4x product1 and confirm it.
|
|
receipt = self.env['stock.picking'].create({
|
|
'location_dest_id': self.picking_type_in.default_location_dest_id.id,
|
|
'location_id': self.supplier_location.id,
|
|
'name': "receipt_split_line_on_destination_scan",
|
|
'picking_type_id': self.picking_type_in.id,
|
|
})
|
|
self.env['stock.move'].create({
|
|
'location_dest_id': receipt.location_dest_id.id,
|
|
'location_id': receipt.location_id.id,
|
|
'name': "product1 x4",
|
|
'picking_id': receipt.id,
|
|
'product_id': self.product1.id,
|
|
'product_uom_qty': 4,
|
|
})
|
|
receipt.action_confirm()
|
|
# Create an internal transfer for 7x product2 (reserved from two different locations.)
|
|
self.env['stock.quant']._update_available_quantity(self.product2, self.shelf1, 3)
|
|
self.env['stock.quant']._update_available_quantity(self.product2, self.shelf2, 4)
|
|
internal = self.env['stock.picking'].create({
|
|
'location_dest_id': self.stock_location.id,
|
|
'location_id': self.stock_location.id,
|
|
'name': "internal_split_line_on_destination_scan",
|
|
'picking_type_id': self.picking_type_internal.id,
|
|
})
|
|
self.env['stock.move'].create({
|
|
'location_dest_id': internal.location_dest_id.id,
|
|
'location_id': internal.location_id.id,
|
|
'name': "product2 x7",
|
|
'picking_id': internal.id,
|
|
'product_id': self.product2.id,
|
|
'product_uom_qty': 7,
|
|
})
|
|
internal.action_confirm()
|
|
# Process the receipt and the internal transfer in a tour, then checks its move lines values.
|
|
action_id = self.env.ref('stock_barcode.stock_barcode_action_main_menu')
|
|
url = "/web#action=" + str(action_id.id)
|
|
self.start_tour(url, 'test_split_line_on_destination_scan', login='admin')
|
|
self.assertRecordValues(receipt.move_line_ids, [
|
|
{'quantity': 2, 'picked': True, 'location_dest_id': receipt.location_dest_id.id},
|
|
{'quantity': 2, 'picked': True, 'location_dest_id': self.shelf1.id},
|
|
])
|
|
self.assertRecordValues(internal.move_line_ids, [
|
|
{'quantity': 2, 'picked': True, 'location_id': self.shelf1.id, 'location_dest_id': self.shelf3.id},
|
|
{'quantity': 2, 'picked': True, 'location_id': self.shelf2.id, 'location_dest_id': self.shelf2.id},
|
|
{'quantity': 1, 'picked': True, 'location_id': self.shelf1.id, 'location_dest_id': self.stock_location.id},
|
|
{'quantity': 2, 'picked': True, 'location_id': self.shelf2.id, 'location_dest_id': self.shelf1.id},
|
|
])
|
|
|
|
def test_split_line_on_exit_for_delivery(self):
|
|
""" Ensures that exit an unfinished operation 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()
|
|
product3 = self.env['product.product'].create({
|
|
'name': 'product3',
|
|
'type': 'product',
|
|
'categ_id': self.env.ref('product.product_category_all').id,
|
|
'barcode': 'product3',
|
|
})
|
|
# Adds some quantity in stock but not enough to fully complete the delivery.
|
|
self.env['stock.quant'].with_context(inventory_mode=True).create([{
|
|
'product_id': product.id,
|
|
'inventory_quantity': qty,
|
|
'location_id': self.stock_location.id,
|
|
} for product, qty in [
|
|
(self.product1, 4), (self.product2, 4), (product3, 2)]
|
|
]).action_apply_inventory()
|
|
|
|
# Creates a delivery for 4x product1, 4x product2 and 4x product3.
|
|
delivery = self.env['stock.picking'].create({
|
|
'name': "delivery_split_line_on_exit",
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.customer_location.id,
|
|
'picking_type_id': self.picking_type_out.id,
|
|
})
|
|
self.env['stock.move'].create([{
|
|
'location_dest_id': delivery.location_dest_id.id,
|
|
'location_id': delivery.location_id.id,
|
|
'name': f"{product.name} x4",
|
|
'picking_id': delivery.id,
|
|
'product_id': product.id,
|
|
'product_uom_qty': 4,
|
|
} for product in [self.product1, self.product2, product3]])
|
|
delivery.action_confirm()
|
|
|
|
action = self.env.ref('stock_barcode.stock_barcode_action_main_menu')
|
|
url = f"/web#action={action.id}"
|
|
self.start_tour(url, 'test_split_line_on_exit_for_delivery', login='admin')
|
|
# Checks delivery moves values:
|
|
# - product1 line should not be split (completed line)
|
|
# - product2 line should be split in two (2 qty picked, 2 qty left)
|
|
# - product3 line should not be split (not picked at all)
|
|
self.assertEqual(len(delivery.move_ids), 4)
|
|
self.assertRecordValues(delivery.move_ids, [
|
|
{'product_id': self.product1.id, 'quantity': 4, 'picked': True},
|
|
{'product_id': self.product2.id, 'quantity': 2, 'picked': True},
|
|
{'product_id': product3.id, 'quantity': 2, 'picked': False},
|
|
{'product_id': self.product2.id, 'quantity': 2, 'picked': False},
|
|
])
|
|
|
|
def test_split_line_on_exit_for_receipt(self):
|
|
""" Ensures that exit an unfinished operation 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()
|
|
# Enables package to check the split after a put in pack.
|
|
group_pack = self.env.ref('stock.group_tracking_lot')
|
|
self.env.user.write({'groups_id': [(4, group_pack.id, 0)]})
|
|
# Set packages' sequence to 1000 to find it easily during the tour.
|
|
package_sequence = self.env['ir.sequence'].search([('code', '=', 'stock.quant.package')], limit=1)
|
|
package_sequence.write({'number_next_actual': 1000})
|
|
|
|
# Creates a receipt for 4x product1 and 4x product2.
|
|
receipt = self.env['stock.picking'].create({
|
|
'name': "receipt_split_line_on_exit",
|
|
'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': receipt.location_dest_id.id,
|
|
'location_id': receipt.location_id.id,
|
|
'name': "product1 x4",
|
|
'picking_id': receipt.id,
|
|
'product_id': self.product1.id,
|
|
'product_uom_qty': 4,
|
|
})
|
|
self.env['stock.move'].create({
|
|
'location_dest_id': receipt.location_dest_id.id,
|
|
'location_id': receipt.location_id.id,
|
|
'name': "product2 x4",
|
|
'picking_id': receipt.id,
|
|
'product_id': self.product2.id,
|
|
'product_uom_qty': 4,
|
|
})
|
|
receipt.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_receipt', login='admin')
|
|
# Checks receipt moves values.
|
|
self.assertRecordValues(receipt.move_ids, [
|
|
{'product_id': self.product1.id, 'quantity': 3, 'picked': True},
|
|
{'product_id': self.product2.id, 'quantity': 1, 'picked': True},
|
|
{'product_id': self.product1.id, 'quantity': 1, 'picked': False},
|
|
{'product_id': self.product2.id, 'quantity': 3, 'picked': False},
|
|
])
|
|
|
|
def test_editing_done_picking(self):
|
|
""" Create and validate a picking then try editing it."""
|
|
self.clean_access_rights()
|
|
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 = 69
|
|
|
|
receipt = picking_form.save()
|
|
receipt.action_confirm()
|
|
receipt.move_ids.quantity = 69
|
|
receipt.button_validate()
|
|
|
|
url = self._get_client_action_url(receipt.id)
|
|
self.start_tour(url, 'test_editing_done_picking', login='admin', timeout=180)
|
|
|
|
def test_sml_sort_order_by_product_category(self):
|
|
"""Test the lines are correctly sorted in the Barcode App regarding
|
|
their product's category.
|
|
"""
|
|
self.clean_access_rights()
|
|
# Creates two categories and some products using them.
|
|
product_categoryB = self.env["product.category"].create({"name": "TestB"})
|
|
product_categoryA = self.env["product.category"].create({"name": "TestA"})
|
|
productA = self.env["product.product"].create(
|
|
{"name": "Product A", "categ_id": product_categoryB.id, "type": "product"}
|
|
)
|
|
productB = self.env["product.product"].create(
|
|
{"name": "Product B", "categ_id": product_categoryA.id, "type": "product"}
|
|
)
|
|
productC = self.env["product.product"].create(
|
|
{"name": "Product C", "categ_id": product_categoryB.id, "type": "product"}
|
|
)
|
|
# Creates a receipt with three move lines (one for each product).
|
|
receipt = self.env["stock.picking"].create(
|
|
{
|
|
"location_id": self.stock_location.id,
|
|
"location_dest_id": self.stock_location.id,
|
|
"picking_type_id": self.picking_type_in.id,
|
|
}
|
|
)
|
|
self.env["stock.move.line"].create(
|
|
[
|
|
{
|
|
"product_id": productA.id,
|
|
"product_uom_id": productA.uom_id.id,
|
|
"location_id": self.stock_location.id,
|
|
"location_dest_id": self.stock_location.id,
|
|
"qty_done": 1,
|
|
"picking_id": receipt.id,
|
|
},
|
|
{
|
|
"product_id": productB.id,
|
|
"product_uom_id": productB.uom_id.id,
|
|
"location_id": self.stock_location.id,
|
|
"location_dest_id": self.stock_location.id,
|
|
"qty_done": 1,
|
|
"picking_id": receipt.id,
|
|
},
|
|
{
|
|
"product_id": productC.id,
|
|
"product_uom_id": productC.uom_id.id,
|
|
"location_id": self.stock_location.id,
|
|
"location_dest_id": self.stock_location.id,
|
|
"qty_done": 1,
|
|
"picking_id": receipt.id,
|
|
},
|
|
]
|
|
)
|
|
url = self._get_client_action_url(receipt.id)
|
|
self.start_tour(
|
|
url, "test_sml_sort_order_by_product_category", login="admin", timeout=180
|
|
)
|
|
|
|
def test_create_backorder_after_qty_modified(self):
|
|
""" Adding qty to an SML via the edit buttons updates the barcode cache;
|
|
we should still be shown the confirmation dialog when validating a partially
|
|
complete order, informing the user that a backorder will be created.
|
|
"""
|
|
self.clean_access_rights()
|
|
|
|
receipt = self.env['stock.picking'].create({
|
|
'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({
|
|
'name': 'test_create_backorder_after_qty_modified move',
|
|
'picking_id': receipt.id,
|
|
'product_id': self.product1.id,
|
|
'product_uom_qty': 2.0,
|
|
'product_uom': self.product1.uom_id.id,
|
|
'location_id': self.supplier_location.id,
|
|
'location_dest_id': self.stock_location.id,
|
|
})
|
|
receipt.action_confirm()
|
|
|
|
url = self._get_client_action_url(receipt.id)
|
|
self.start_tour(url, 'test_create_backorder_after_qty_modified', login='admin', timeout=180)
|
|
|
|
# Original receipt move demand should be modified and the resulting
|
|
# backorder move demand should be for the rest of the original demand.
|
|
backorder = self.env['stock.picking'].search([('backorder_id', '=', receipt.id)])
|
|
self.assertEqual(backorder.move_ids[0].product_uom_qty, 1.0)
|
|
self.assertEqual(receipt.move_ids[0].product_uom_qty, 1.0)
|
|
|
|
def test_open_picking_dont_override_assigned_user(self):
|
|
"""
|
|
Test that clicking on a picking from the barcode view does not replace
|
|
the current responsible with the current user.
|
|
"""
|
|
bob = self.env['res.users'].create({
|
|
'partner_id': self.env['res.partner'].create({'name': 'Bob'}).id,
|
|
'login': 'bob',
|
|
})
|
|
receipt_picking = self.env['stock.picking'].create({
|
|
'name': "test_responsible_receipt",
|
|
'location_id': self.supplier_location.id,
|
|
'location_dest_id': self.stock_location.id,
|
|
'picking_type_id': self.picking_type_in.id,
|
|
'user_id': bob.id,
|
|
'move_ids': [(0, 0, {
|
|
'name': 'some product',
|
|
'location_id': self.supplier_location.id,
|
|
'location_dest_id': self.stock_location.id,
|
|
'product_id': self.product1.id,
|
|
'product_uom': self.uom_unit.id,
|
|
'product_uom_qty': 2
|
|
})],
|
|
})
|
|
receipt_picking.action_confirm()
|
|
action = self.env.ref('stock_barcode.stock_barcode_action_main_menu')
|
|
url = f"/web#action={action.id}"
|
|
self.start_tour(url, 'test_open_picking_dont_override_assigned_user', login='admin', timeout=180)
|
|
self.assertEqual(receipt_picking.user_id.id, bob.id, "Picking responsible should be unchanged after click when previously set")
|
|
|
|
def test_multi_company_record_access_in_barcode(self):
|
|
""" Test that creating a picking operation wholly in the barcode app will not permit a user
|
|
to find records that don't belong to the operation type's company.
|
|
"""
|
|
self.clean_access_rights()
|
|
company2 = self.env['res.company'].create({'name': 'second company'})
|
|
self.env['stock.picking.type'].search([
|
|
('code', '=', 'incoming'),
|
|
('company_id', '=', company2.id),
|
|
], limit=1).barcode = 'company2_receipt'
|
|
self.env.user.company_ids = [(4, company2.id)]
|
|
|
|
self.product1.write({
|
|
'company_id': self.env.company.id,
|
|
'barcode': 'company1_product',
|
|
})
|
|
self.product2.write({
|
|
'company_id': company2.id,
|
|
'barcode': 'company2_product',
|
|
})
|
|
|
|
action_id = self.env.ref('stock_barcode.stock_barcode_action_main_menu')
|
|
cids = '-'.join(str(cid) for cid in self.env.user.company_ids.ids)
|
|
url = f'/web#action={action_id.id}&cids={cids}'
|
|
self.start_tour(url, 'test_multi_company_record_access_in_barcode', login='admin', timeout=180)
|
|
|
|
self.assertTrue(
|
|
self.env['stock.picking'].search([
|
|
('company_id', '=', company2.id),
|
|
('product_id', 'in', self.product2.ids),
|
|
], limit=1)
|
|
)
|
|
|
|
def test_no_zero_demand_new_line_from_split(self):
|
|
""" If a split of incomplete barcode lines is triggered when the line quantity == 0, don't
|
|
go through with the split.
|
|
"""
|
|
self.clean_access_rights()
|
|
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 1)
|
|
picking = self.env['stock.picking'].create({
|
|
'name': 'TNZDNLFS picking',
|
|
'picking_type_id': self.picking_type_internal.id,
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.stock_location.id,
|
|
'move_ids': [Command.create({
|
|
'name': 'TNZDNLFS move',
|
|
'product_id': self.product1.id,
|
|
'product_uom_qty': 1,
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.stock_location.id,
|
|
})],
|
|
})
|
|
picking.action_confirm()
|
|
|
|
action_id = self.env.ref('stock_barcode.stock_barcode_action_main_menu')
|
|
url = "/web#action=" + str(action_id.id)
|
|
self.start_tour(url, 'test_no_zero_demand_new_line_from_split', login='admin', timeout=180)
|
|
|
|
self.assertRecordValues(
|
|
picking.move_ids,
|
|
[{'quantity': 1, 'product_uom_qty': 1, 'picked': False}]
|
|
)
|
|
self.assertRecordValues(
|
|
picking.move_line_ids,
|
|
[{'picked': False, 'quantity': 1}]
|
|
)
|
|
|
|
def test_barcode_pack_lot(self):
|
|
"""
|
|
This test ensures that products of the same lot can be
|
|
packed in different packages.
|
|
"""
|
|
self.env.ref('base.group_user').implied_ids += self.env.ref('stock.group_production_lot')
|
|
self.clean_access_rights()
|
|
group_tracking = self.env.ref('stock.group_tracking_lot')
|
|
self.env.user.write({'groups_id': [Command.link(group_tracking.id)]})
|
|
warehouse = self.picking_type_out.warehouse_id
|
|
|
|
# Create a product and its packaging.
|
|
product = self.env['product.product'].create({
|
|
'name': 'Lovely product',
|
|
'type': 'product',
|
|
'tracking': 'lot',
|
|
'uom_id': self.uom_unit.id,
|
|
})
|
|
|
|
lot_1, lot_2 = self.env['stock.lot'].create([
|
|
{'name': 'LOT001', 'product_id': product.id},
|
|
{'name': 'LOT002', 'product_id': product.id},
|
|
])
|
|
# create 2 lots to test the flow for both reserved and not reserved lots
|
|
self.env['stock.quant']._update_available_quantity(product, warehouse.lot_stock_id, 4, lot_id=lot_1)
|
|
self.env['stock.quant']._update_available_quantity(product, warehouse.lot_stock_id, 2, lot_id=lot_2)
|
|
|
|
delivery = self.env['stock.picking'].create({
|
|
'name': "Lovely delivery",
|
|
'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': 'Lovely move',
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.customer_location.id,
|
|
'product_id': product.id,
|
|
'product_uom': product.uom_id.id,
|
|
'product_uom_qty': 4
|
|
})],
|
|
})
|
|
delivery.action_confirm()
|
|
self.assertEqual(delivery.move_line_ids.lot_id, lot_1)
|
|
url = self._get_client_action_url(delivery.id)
|
|
self.start_tour(url, 'test_barcode_pack_lot_tour', login='admin')
|
|
self.assertEqual(delivery.state, 'done')
|
|
self.assertRecordValues(delivery.move_line_ids, [
|
|
{'quantity': 1.0, 'lot_id': lot_1.id},
|
|
{'quantity': 1.0, 'lot_id': lot_1.id},
|
|
{'quantity': 1.0, 'lot_id': lot_2.id},
|
|
{'quantity': 1.0, 'lot_id': lot_2.id},
|
|
])
|
|
self.assertEqual(len(delivery.move_line_ids.result_package_id), 4)
|
|
|
|
def test_scan_location_destination_for_internal_transfers(self):
|
|
"""
|
|
This test ensures that destination location scan is taken into
|
|
account when proposed by the UI.
|
|
"""
|
|
self.clean_access_rights()
|
|
grp_multi_loc = self.env.ref('stock.group_stock_multi_locations')
|
|
self.env.user.write({'groups_id': [Command.link(grp_multi_loc.id)]})
|
|
self.picking_type_internal.active = True
|
|
|
|
# Create a sibling stock location to use a destination
|
|
location_dest = self.env['stock.location'].create({
|
|
'name': "Lovely Location",
|
|
'location_id': self.stock_location.location_id.id,
|
|
'barcode': 'WH-LOVE',
|
|
})
|
|
self.product1.name = "Lovely Product"
|
|
action_id = self.env.ref('stock_barcode.stock_barcode_action_main_menu')
|
|
url = "/web#action=" + str(action_id.id)
|
|
self.start_tour(url, 'test_scan_location_destination_for_internal_transfers', login='admin', timeout=180)
|
|
internal_transfer = self.env['stock.picking'].search([('picking_type_id', '=', self.picking_type_internal.id)], limit=1)
|
|
self.assertRecordValues(internal_transfer.move_ids.move_line_ids, [{
|
|
"product_id": self.product1.id,
|
|
"location_dest_id": location_dest.id,
|
|
}])
|
|
|
|
def test_split_uncomplete_moves_on_exit(self):
|
|
"""
|
|
Check that the uncompleted moves are splitted in the backend when you exit
|
|
the barcode, so that the demand of the picking is correctly displayed the
|
|
next time you open the record.
|
|
|
|
The flow slightly change with mto moves: both mts and mto procure methods are tested here
|
|
"""
|
|
self.clean_access_rights()
|
|
grp_multi_loc = self.env.ref('stock.group_stock_multi_locations')
|
|
self.env.user.write({'groups_id': [Command.link(grp_multi_loc.id)]})
|
|
procurement_group = self.env['procurement.group'].create({
|
|
'name': 'custom procurement',
|
|
})
|
|
warehouse = self.picking_type_out.warehouse_id
|
|
mto_product = self.product1
|
|
self.env['stock.quant']._update_available_quantity(mto_product, warehouse.lot_stock_id, 4)
|
|
warehouse.delivery_steps = 'pick_ship'
|
|
final_destination = self.env.ref('stock.stock_location_customers')
|
|
origin = 'custom origin'
|
|
self.env['procurement.group'].run([
|
|
self.env['procurement.group'].Procurement(mto_product, 4.0, mto_product.uom_id, final_destination, mto_product.name, origin,
|
|
self.picking_type_out.company_id, {'warehouse_id': warehouse, 'group_id': procurement_group})
|
|
])
|
|
delivery_steps = self.env['stock.picking'].search([('group_id', '=', procurement_group.id)], limit=2)
|
|
pick = delivery_steps.filtered(lambda p: p.picking_type_id == warehouse.pick_type_id)
|
|
ship = delivery_steps - pick
|
|
pick.button_validate()
|
|
mts_product = self.product2
|
|
self.env['stock.quant']._update_available_quantity(mts_product, ship.location_id, 5)
|
|
self.env['stock.move'].create({
|
|
'name': mts_product.name,
|
|
'product_id': mts_product.id,
|
|
'product_uom_qty': 5,
|
|
'product_uom': mts_product.uom_id.id,
|
|
'picking_id': ship.id,
|
|
'location_id': ship.location_id.id,
|
|
'location_dest_id': ship.location_dest_id.id,
|
|
})
|
|
ship.action_assign()
|
|
self.assertRecordValues(ship.move_ids.sorted('quantity'), [
|
|
{'quantity': 4.0, 'picked': False, 'procure_method': 'make_to_order'},
|
|
{'quantity': 5.0, 'picked': False, 'procure_method': 'make_to_stock'},
|
|
])
|
|
url = self._get_client_action_url(ship.id)
|
|
self.start_tour(url, 'test_split_uncomplete_moves_on_exit', login='admin', timeout=180)
|
|
self.assertRecordValues(ship.move_ids.filtered(lambda m: m.product_id == mto_product).sorted('quantity'), [
|
|
{"quantity": 1.0, "picked": True},
|
|
{"quantity": 3.0, "picked": False},
|
|
])
|
|
self.assertRecordValues(ship.move_ids.filtered(lambda m: m.product_id == mts_product).sorted('quantity'), [
|
|
{"quantity": 1.0, "picked": True},
|
|
{"quantity": 4.0, "picked": False},
|
|
])
|
|
|
|
# === GS1 TESTS ===#
|
|
def test_gs1_delivery_ambiguous_lot_number(self):
|
|
"""
|
|
Have a delivery for a product tracked by lots then scan a lot who exists for
|
|
two different products and check the move line has the right lot.
|
|
Do the same test by scanning a packaging instead of the product.
|
|
"""
|
|
self.clean_access_rights()
|
|
group_packaging = self.env.ref('product.group_stock_packaging')
|
|
self.env.user.write({'groups_id': [(4, group_packaging.id)]})
|
|
self.env.company.nomenclature_id = self.env.ref('barcodes_gs1_nomenclature.default_gs1_nomenclature')
|
|
product_a, product_b = self.env['product.product'].create([{
|
|
'name': name,
|
|
'type': 'product',
|
|
'categ_id': self.env.ref('product.product_category_all').id,
|
|
'barcode': barcode,
|
|
'tracking': 'lot',
|
|
} for (name, barcode) in [('Product A', '22222220'), ('Product B', '44444440')]])
|
|
# Creates 2 lot numbers (same name but different product.)
|
|
lot_b, lot_a = self.env['stock.lot'].create([
|
|
{'name': '12345', 'product_id': product.id} for product in [product_b, product_a]
|
|
])
|
|
# Create a product packaging
|
|
self.env['product.packaging'].create({
|
|
'barcode': '10000000240489',
|
|
'name': "Packaging - Product A x1",
|
|
'product_id': product_a.id,
|
|
'qty': 1,
|
|
})
|
|
# For the purpose of the test, lot for product_b has to be created first.
|
|
for [product, lot] in [[product_b, lot_b], [product_a, lot_a]]:
|
|
self.env['stock.quant'].with_context(inventory_mode=True).create({
|
|
'product_id': product.id,
|
|
'inventory_quantity': 1,
|
|
'lot_id': lot.id,
|
|
'location_id': self.stock_location.id,
|
|
}).action_apply_inventory()
|
|
# Run the tour.
|
|
action = self.env.ref('stock_barcode.stock_barcode_action_main_menu')
|
|
url = "/web#action=" + str(action.id)
|
|
self.start_tour(url, 'test_gs1_delivery_ambiguous_lot_number', login='admin', timeout=180)
|
|
|
|
def test_gs1_delivery_ambiguous_serial_number(self):
|
|
"""
|
|
Have a delivery for a product tracked by SN then scan a SN who exists for
|
|
two different products and check the move line has the right SN.
|
|
"""
|
|
self.clean_access_rights()
|
|
self.env.company.nomenclature_id = self.env.ref('barcodes_gs1_nomenclature.default_gs1_nomenclature')
|
|
product_a, product_b = self.env['product.product'].create([{
|
|
'name': f'product{i}',
|
|
'type': 'product',
|
|
'categ_id': self.env.ref('product.product_category_all').id,
|
|
'barcode': barcode,
|
|
'tracking': 'serial',
|
|
} for i, barcode in enumerate(['05711544001952', '05711544001969'])])
|
|
# Creates 2 serial numbers (same name different product).
|
|
lot_b, lot_a = self.env['stock.lot'].create([
|
|
{'name': '304', 'product_id': product.id} for product in [product_b, product_a]
|
|
])
|
|
# For the purpose of the test, lot for product_b has to be created first.
|
|
for [product, lot] in [[product_b, lot_b], [product_a, lot_a]]:
|
|
self.env['stock.quant'].with_context(inventory_mode=True).create({
|
|
'product_id': product.id,
|
|
'inventory_quantity': 1,
|
|
'lot_id': lot.id,
|
|
'location_id': self.stock_location.id,
|
|
}).action_apply_inventory()
|
|
# Creates and confirms the delivery.
|
|
delivery_picking = self.env['stock.picking'].create({
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.customer_location.id,
|
|
'picking_type_id': self.picking_type_out.id,
|
|
})
|
|
self.env['stock.move'].create({
|
|
'name': product_a.name,
|
|
'product_id': product_a.id,
|
|
'product_uom_qty': 1,
|
|
'product_uom': product_a.uom_id.id,
|
|
'picking_id': delivery_picking.id,
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.customer_location.id,
|
|
})
|
|
delivery_picking.action_confirm()
|
|
delivery_picking.action_assign()
|
|
# Run the tour.
|
|
url = self._get_client_action_url(delivery_picking.id)
|
|
self.start_tour(url, 'test_gs1_delivery_ambiguous_serial_number', login='admin', timeout=180)
|
|
self.assertEqual(delivery_picking.move_line_ids.lot_id, lot_a)
|
|
self.assertEqual(delivery_picking.move_line_ids.product_id, product_a)
|
|
|
|
def test_gs1_reserved_delivery(self):
|
|
""" Process a delivery by scanning multiple quantity multiple times.
|
|
"""
|
|
self.clean_access_rights()
|
|
self.env.company.nomenclature_id = self.env.ref('barcodes_gs1_nomenclature.default_gs1_nomenclature')
|
|
|
|
# Creates a product and adds some quantity.
|
|
product_gtin_8 = self.env['product.product'].create({
|
|
'name': 'PRO_GTIN_8',
|
|
'type': 'product',
|
|
'categ_id': self.env.ref('product.product_category_all').id,
|
|
'barcode': '11011019', # GTIN-8 format.
|
|
'uom_id': self.env.ref('uom.product_uom_unit').id,
|
|
})
|
|
self.env['stock.quant']._update_available_quantity(product_gtin_8, self.stock_location, 99)
|
|
|
|
# Creates and process the delivery.
|
|
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 = product_gtin_8
|
|
move.product_uom_qty = 10
|
|
|
|
delivery = picking_form.save()
|
|
delivery.action_confirm()
|
|
delivery.action_assign()
|
|
|
|
url = self._get_client_action_url(delivery.id)
|
|
self.start_tour(url, 'test_gs1_reserved_delivery', login='admin', timeout=180)
|
|
|
|
self.assertEqual(delivery.state, 'done')
|
|
self.assertEqual(len(delivery.move_ids), 1)
|
|
self.assertEqual(delivery.move_ids.product_uom_qty, 10, "10 units was reserved")
|
|
self.assertEqual(delivery.move_ids.quantity, 14, "14 units was processed")
|
|
self.assertEqual(len(delivery.move_line_ids), 2)
|
|
self.assertEqual(delivery.move_line_ids[0].quantity, 10)
|
|
self.assertEqual(delivery.move_line_ids[1].quantity, 4)
|
|
|
|
def test_gs1_receipt_conflicting_barcodes(self):
|
|
""" Creates some receipts for two products but their barcodes mingle
|
|
together once they are adapted for GS1.
|
|
"""
|
|
self.clean_access_rights()
|
|
self.env.company.nomenclature_id = self.env.ref('barcodes_gs1_nomenclature.default_gs1_nomenclature')
|
|
|
|
product_gtin_8 = self.env['product.product'].create({
|
|
'name': 'PRO_GTIN_8',
|
|
'type': 'product',
|
|
'categ_id': self.env.ref('product.product_category_all').id,
|
|
'barcode': '11011019', # GTIN-8 format -> Will become 00000011011019.
|
|
'uom_id': self.env.ref('uom.product_uom_unit').id,
|
|
})
|
|
|
|
product_gtin_12 = self.env['product.product'].create({
|
|
'name': 'PRO_GTIN_12',
|
|
'type': 'product',
|
|
'categ_id': self.env.ref('product.product_category_all').id,
|
|
'barcode': '000011011019', # GTIN-12 format -> Will also become 00000011011019.
|
|
'uom_id': self.env.ref('uom.product_uom_unit').id,
|
|
})
|
|
|
|
# Test for product_gtin_8 only.
|
|
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 = product_gtin_8
|
|
move.product_uom_qty = 1
|
|
|
|
receipt_1 = picking_form.save()
|
|
receipt_1.action_confirm()
|
|
receipt_1.action_assign()
|
|
|
|
url = self._get_client_action_url(receipt_1.id)
|
|
self.start_tour(url, 'test_gs1_receipt_conflicting_barcodes_1', login='admin', timeout=180)
|
|
|
|
self.assertEqual(receipt_1.state, 'done')
|
|
self.assertEqual(len(receipt_1.move_line_ids), 1)
|
|
self.assertEqual(receipt_1.move_line_ids.product_id.id, product_gtin_8.id)
|
|
|
|
# Test for product_gtin_12 only.
|
|
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 = product_gtin_12
|
|
move.product_uom_qty = 1
|
|
|
|
receipt_2 = picking_form.save()
|
|
receipt_2.action_confirm()
|
|
receipt_2.action_assign()
|
|
|
|
url = self._get_client_action_url(receipt_2.id)
|
|
self.start_tour(url, 'test_gs1_receipt_conflicting_barcodes_2', login='admin', timeout=180)
|
|
|
|
self.assertEqual(receipt_2.state, 'done')
|
|
self.assertEqual(len(receipt_2.move_line_ids), 1)
|
|
self.assertEqual(receipt_2.move_line_ids.product_id.id, product_gtin_12.id)
|
|
|
|
# Test for both product_gtin_8 and product_gtin_12.
|
|
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 = product_gtin_8
|
|
move.product_uom_qty = 1
|
|
with picking_form.move_ids_without_package.new() as move:
|
|
move.product_id = product_gtin_12
|
|
move.product_uom_qty = 1
|
|
|
|
receipt_3 = picking_form.save()
|
|
receipt_3.action_confirm()
|
|
receipt_3.action_assign()
|
|
|
|
self.assertEqual(len(receipt_3.move_line_ids), 2)
|
|
url = self._get_client_action_url(receipt_3.id)
|
|
self.start_tour(url, 'test_gs1_receipt_conflicting_barcodes_3', login='admin', timeout=180)
|
|
|
|
self.assertEqual(receipt_3.state, 'done')
|
|
self.assertEqual(len(receipt_3.move_line_ids), 3)
|
|
self.assertEqual(receipt_3.move_line_ids[0].product_id.id, product_gtin_8.id)
|
|
self.assertEqual(receipt_3.move_line_ids[0].qty_done, 1)
|
|
self.assertEqual(receipt_3.move_line_ids[1].product_id.id, product_gtin_12.id)
|
|
self.assertEqual(receipt_3.move_line_ids[1].qty_done, 1)
|
|
self.assertEqual(receipt_3.move_line_ids[2].product_id.id, product_gtin_8.id)
|
|
self.assertEqual(receipt_3.move_line_ids[2].qty_done, 1)
|
|
|
|
def test_gs1_receipt_conflicting_barcodes_mistaken_as_gs1(self):
|
|
""" Checks if a record has a barcode who can be mistaken for a GS1 barcode,
|
|
this record can still be found anyway while using the GS1 nomenclature."""
|
|
self.clean_access_rights()
|
|
group_package = self.env.ref('stock.group_tracking_lot')
|
|
group_lot = self.env.ref('stock.group_production_lot')
|
|
self.env.user.write({'groups_id': [
|
|
(4, group_package.id, 0),
|
|
(4, group_lot.id, 0),
|
|
]})
|
|
self.env.company.nomenclature_id = self.env.ref('barcodes_gs1_nomenclature.default_gs1_nomenclature')
|
|
# Creates two products and a package with misleading barcode.
|
|
self.env['product.product'].create({
|
|
'name': "Product AI 21",
|
|
'type': 'product',
|
|
'categ_id': self.env.ref('product.product_category_all').id,
|
|
'barcode': '21000000000003', # Can be read as a serial number (AI 21)
|
|
'uom_id': self.env.ref('uom.product_uom_unit').id,
|
|
})
|
|
self.env['product.product'].create({
|
|
'name': "Product AI 30",
|
|
'type': 'product',
|
|
'categ_id': self.env.ref('product.product_category_all').id,
|
|
'barcode': '3000000015', # Can be read as a quantity (15 units, AI 30)
|
|
'uom_id': self.env.ref('uom.product_uom_unit').id,
|
|
})
|
|
self.env['stock.quant.package'].create({'name': '21-Chouette-MegaPack'})
|
|
action_id = self.env.ref('stock_barcode.stock_barcode_action_main_menu')
|
|
url = "/web#action=" + str(action_id.id)
|
|
self.start_tour(url, 'test_gs1_receipt_conflicting_barcodes_mistaken_as_gs1', login='admin', timeout=180)
|
|
|
|
def test_gs1_receipt_lot_serial(self):
|
|
""" Creates a receipt for a product tracked by lot, then process it in the Barcode App.
|
|
"""
|
|
self.clean_access_rights()
|
|
grp_lot = self.env.ref('stock.group_production_lot')
|
|
self.env.user.write({'groups_id': [(4, grp_lot.id, 0)]})
|
|
self.env.company.nomenclature_id = self.env.ref('barcodes_gs1_nomenclature.default_gs1_nomenclature')
|
|
|
|
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.product_tln_gtn8
|
|
move.product_uom_qty = 40
|
|
|
|
receipt = picking_form.save()
|
|
receipt.action_confirm()
|
|
receipt.action_assign()
|
|
|
|
url = self._get_client_action_url(receipt.id)
|
|
self.start_tour(url, 'test_gs1_receipt_lot_serial', login='admin', timeout=180)
|
|
|
|
self.assertEqual(receipt.state, 'done')
|
|
self.assertEqual(len(receipt.move_line_ids), 5)
|
|
self.assertEqual(
|
|
receipt.move_line_ids.lot_id.mapped('name'),
|
|
['b1-b001', 'b1-b002', 'b1-b003', 'b1-b004', 'b1-b005']
|
|
)
|
|
for move_line in receipt.move_line_ids:
|
|
self.assertEqual(move_line.quantity, 8)
|
|
self.assertTrue(move_line.picked)
|
|
|
|
def test_gs1_receipt_quantity_with_uom(self):
|
|
""" Creates a new receipt and scans barcodes with different combinaisons
|
|
of product and quantity expressed with different UoM and checks the
|
|
quantity is taken only if the UoM is compatible with the product's one.
|
|
"""
|
|
self.clean_access_rights()
|
|
# Enables the UoM and the GS1 nomenclature.
|
|
grp_uom = self.env.ref('uom.group_uom')
|
|
group_user = self.env.ref('base.group_user')
|
|
group_user.write({'implied_ids': [(4, grp_uom.id)]})
|
|
self.env.user.write({'groups_id': [(4, grp_uom.id)]})
|
|
self.env.company.nomenclature_id = self.env.ref('barcodes_gs1_nomenclature.default_gs1_nomenclature')
|
|
# Configures three products using units, kg and g.
|
|
uom_unit = self.env.ref('product.product_category_all')
|
|
uom_g = self.env.ref('uom.product_uom_gram')
|
|
uom_kg = self.env.ref('uom.product_uom_kgm')
|
|
product_by_units = self.env['product.product'].create({
|
|
'name': 'Product by Units',
|
|
'type': 'product',
|
|
'categ_id': self.env.ref('product.product_category_all').id,
|
|
'barcode': '15264329',
|
|
'uom_id': uom_unit.id,
|
|
})
|
|
product_by_g = self.env['product.product'].create({
|
|
'name': 'Product by g',
|
|
'type': 'product',
|
|
'categ_id': self.env.ref('product.product_category_all').id,
|
|
'barcode': '15264893',
|
|
'uom_id': uom_g.id,
|
|
'uom_po_id': uom_g.id,
|
|
})
|
|
product_by_kg = self.env['product.product'].create({
|
|
'name': 'Product by kg',
|
|
'type': 'product',
|
|
'categ_id': self.env.ref('product.product_category_all').id,
|
|
'barcode': '15264879',
|
|
'uom_id': uom_kg.id,
|
|
'uom_po_id': uom_kg.id,
|
|
})
|
|
# Creates a new receipt.
|
|
picking_form = Form(self.env['stock.picking'])
|
|
picking_form.picking_type_id = self.picking_type_in
|
|
receipt = picking_form.save()
|
|
# Runs the tour.
|
|
url = self._get_client_action_url(receipt.id)
|
|
self.start_tour(url, 'test_gs1_receipt_quantity_with_uom', login='admin', timeout=180)
|
|
# Checks the moves' quantities and UoM.
|
|
self.assertEqual(len(receipt.move_ids), 3)
|
|
move1, move2, move3 = receipt.move_ids
|
|
self.assertEqual(move1.product_id.id, product_by_units.id)
|
|
self.assertEqual(move1.quantity, 4)
|
|
self.assertTrue(move1.picked)
|
|
self.assertEqual(move1.product_uom.id, uom_unit.id)
|
|
self.assertEqual(move2.product_id.id, product_by_kg.id)
|
|
self.assertEqual(move2.quantity, 5)
|
|
self.assertTrue(move2.picked)
|
|
self.assertEqual(move2.product_uom.id, uom_kg.id)
|
|
self.assertEqual(move3.product_id.id, product_by_g.id)
|
|
self.assertEqual(move3.quantity, 1250)
|
|
self.assertTrue(move3.picked)
|
|
self.assertEqual(move3.product_uom.id, uom_g.id)
|
|
|
|
def test_gs1_package_receipt_and_delivery(self):
|
|
""" Receives some products and scans a GS1 barcode for a package, then
|
|
creates a delivery and scans the same package.
|
|
"""
|
|
self.clean_access_rights()
|
|
self.env.company.nomenclature_id = self.env.ref('barcodes_gs1_nomenclature.default_gs1_nomenclature')
|
|
grp_pack = self.env.ref('stock.group_tracking_lot')
|
|
self.env.user.write({'groups_id': [(4, grp_pack.id, 0)]})
|
|
|
|
# Set package's sequence to 123 to generate always the same package's name in the tour.
|
|
sequence = self.env['ir.sequence'].search([('code', '=', 'stock.quant.package')], limit=1)
|
|
sequence.write({'number_next_actual': 123})
|
|
|
|
# Creates two products and two package's types.
|
|
product1 = self.env['product.product'].create({
|
|
'name': 'PRO_GTIN_8',
|
|
'type': 'product',
|
|
'categ_id': self.env.ref('product.product_category_all').id,
|
|
'barcode': '82655853', # GTIN-8
|
|
'uom_id': self.env.ref('uom.product_uom_unit').id
|
|
})
|
|
product2 = self.env['product.product'].create({
|
|
'name': 'PRO_GTIN_12',
|
|
'type': 'product',
|
|
'categ_id': self.env.ref('product.product_category_all').id,
|
|
'barcode': '584687955629', # GTIN-12
|
|
'uom_id': self.env.ref('uom.product_uom_unit').id,
|
|
})
|
|
wooden_chest_package_type = self.env['stock.package.type'].create({
|
|
'name': 'Wooden Chest',
|
|
'barcode': 'WOODC',
|
|
})
|
|
iron_chest_package_type = self.env['stock.package.type'].create({
|
|
'name': 'Iron Chest',
|
|
'barcode': 'IRONC',
|
|
})
|
|
|
|
action_id = self.env.ref('stock_barcode.stock_barcode_action_main_menu')
|
|
url = "/web#action=" + str(action_id.id)
|
|
|
|
self.start_tour(url, 'test_gs1_package_receipt', login='admin', timeout=180)
|
|
# Checks the package is in the stock location with the products.
|
|
package = self.env['stock.quant.package'].search([('name', '=', '546879213579461324')])
|
|
package2 = self.env['stock.quant.package'].search([('name', '=', '130406658041178543')])
|
|
package3 = self.env['stock.quant.package'].search([('name', '=', 'PACK0000123')])
|
|
self.assertEqual(len(package), 1)
|
|
self.assertEqual(len(package.quant_ids), 2)
|
|
self.assertEqual(package.package_type_id.id, wooden_chest_package_type.id)
|
|
self.assertEqual(package.quant_ids[0].product_id.id, product1.id)
|
|
self.assertEqual(package.quant_ids[1].product_id.id, product2.id)
|
|
self.assertEqual(package.location_id.id, self.stock_location.id)
|
|
self.assertEqual(package2.package_type_id.id, iron_chest_package_type.id)
|
|
self.assertEqual(package2.quant_ids.product_id.id, product1.id)
|
|
self.assertEqual(package3.package_type_id.id, iron_chest_package_type.id)
|
|
self.assertEqual(package3.quant_ids.product_id.id, product2.id)
|
|
|
|
self.start_tour(url, 'test_gs1_package_delivery', login='admin', timeout=180)
|
|
# Checks the package is in the customer's location.
|
|
self.assertEqual(package.location_id.id, self.customer_location.id)
|
|
|
|
def test_gs1_receipt_packaging(self):
|
|
"""
|
|
This test ensures that a user can scan a packaging when processing a receipt
|
|
"""
|
|
self.clean_access_rights()
|
|
|
|
group_packaging = self.env.ref('product.group_stock_packaging')
|
|
self.env.user.write({'groups_id': [(4, group_packaging.id)]})
|
|
self.env.company.nomenclature_id = self.env.ref('barcodes_gs1_nomenclature.default_gs1_nomenclature')
|
|
|
|
product = self.env['product.product'].create({
|
|
'name': 'Bottle',
|
|
'type': 'product',
|
|
'barcode': '1113',
|
|
'packaging_ids': [(0, 0, {
|
|
'name': '6-bottle pack',
|
|
'qty': 6,
|
|
'barcode': '2226',
|
|
})],
|
|
})
|
|
|
|
picking_form = Form(self.env['stock.picking'])
|
|
picking_form.picking_type_id = self.picking_type_in
|
|
receipt = picking_form.save()
|
|
|
|
url = self._get_client_action_url(receipt.id)
|
|
self.start_tour(url, 'test_gs1_receipt_packaging', login='admin', timeout=180)
|
|
|
|
move = receipt.move_ids
|
|
self.assertEqual(move.product_id, product)
|
|
self.assertEqual(move.quantity, 30)
|
|
self.assertEqual(move.picked, True)
|
|
|
|
def test_gs1_receipt_packaging_with_uom(self):
|
|
""" This test ensures that packaging quantity is used when a weight is
|
|
scanned but the product uses Units as UoM.
|
|
"""
|
|
self.clean_access_rights()
|
|
group_lot = self.env.ref('stock.group_production_lot')
|
|
group_packaging = self.env.ref('product.group_stock_packaging')
|
|
group_uom = self.env.ref('uom.group_uom')
|
|
self.env.user.write({'groups_id': [(4, group_lot.id), (4, group_packaging.id), (4, group_uom.id)]})
|
|
self.env.company.nomenclature_id = self.env.ref('barcodes_gs1_nomenclature.default_gs1_nomenclature')
|
|
# Create a product and its packaging.
|
|
self.env['product.product'].create({
|
|
'name': 'Product by Units',
|
|
'type': 'product',
|
|
'tracking': 'lot',
|
|
'uom_id': self.uom_unit.id,
|
|
'uom_po_id': self.uom_unit.id,
|
|
'barcode': '03287890001332',
|
|
'packaging_ids': [(0, 0, {
|
|
'name': 'PBUx6',
|
|
'qty': 6,
|
|
'barcode': '10347543011337',
|
|
})],
|
|
})
|
|
action = self.env.ref('stock_barcode.stock_barcode_action_main_menu')
|
|
url = f'/web#action={action.id}'
|
|
self.start_tour(url, 'test_gs1_receipt_packaging_with_uom', login='admin', timeout=180)
|
|
|
|
def test_gs1_tracked_packaging(self):
|
|
""" Ensures we can scan a GS1 barcode containing a packaging for a
|
|
tracked product and the lot in one scan.
|
|
"""
|
|
self.clean_access_rights()
|
|
self.env.company.nomenclature_id = self.env.ref('barcodes_gs1_nomenclature.default_gs1_nomenclature')
|
|
group_tracking = self.env.ref('stock.group_production_lot')
|
|
group_packaging = self.env.ref('product.group_stock_packaging')
|
|
self.env.user.write({'groups_id': [(4, group_tracking.id, 0), (4, group_packaging.id, 0)]})
|
|
|
|
self.env['product.packaging'].create({
|
|
'name': 'productlot1 6 pack',
|
|
'qty': 6,
|
|
'barcode': '12653256',
|
|
'product_id': self.productlot1.id
|
|
})
|
|
|
|
action = self.env.ref('stock_barcode.stock_barcode_action_main_menu')
|
|
url = "/web#action=" + str(action.id)
|
|
self.start_tour(url, 'test_gs1_tracked_packaging', login='admin', timeout=180)
|
|
lot = self.env['stock.lot'].search([('name', '=', 'lot-001')])
|
|
move_lines = self.env['stock.move.line'].search([('product_id', '=', self.productlot1.id)])
|
|
self.assertRecordValues(move_lines, [
|
|
{'quantity': 6, 'lot_id': lot.id, 'location_id': self.supplier_location.id, 'location_dest_id': self.stock_location.id},
|
|
{'quantity': 6, 'lot_id': lot.id, 'location_id': self.stock_location.id, 'location_dest_id': self.customer_location.id},
|
|
])
|
|
|
|
def test_split_line_on_scan(self):
|
|
"""
|
|
This test ensures that move lines are split correctly
|
|
when a user scans a package on an incomplete move line
|
|
"""
|
|
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.env['stock.quant']._update_available_quantity(self.product2, self.stock_location, 5)
|
|
# Create two empty packs
|
|
pack1 = self.env['stock.quant.package'].create({
|
|
'name': 'THEPACK1',
|
|
})
|
|
pack2 = self.env['stock.quant.package'].create({
|
|
'name': 'THEPACK2',
|
|
})
|
|
|
|
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.product2
|
|
move.product_uom_qty = 5
|
|
delivery_with_move = delivery_form.save()
|
|
delivery_with_move.action_confirm()
|
|
delivery_with_move.action_assign()
|
|
|
|
url = self._get_client_action_url(delivery_with_move.id)
|
|
self.start_tour(url, 'test_split_line_on_scan', login='admin', timeout=180)
|
|
|
|
self.assertEqual(len(pack1.quant_ids), 1)
|
|
self.assertEqual(len(pack2.quant_ids), 1)
|
|
self.assertEqual(len(delivery_with_move.move_line_ids), 2)
|
|
|
|
def test_scan_line_splitting_preserve_destination(self):
|
|
"""
|
|
This test ensures that move lines, when assigned a new destination
|
|
while scanning, properly preserve destination info after scanning
|
|
a package
|
|
"""
|
|
self.clean_access_rights()
|
|
grp_pack = self.env.ref('stock.group_tracking_lot')
|
|
grp_multi_loc = self.env.ref('stock.group_stock_multi_locations')
|
|
self.env.user.write({'groups_id': [(4, grp_pack.id, 0)]})
|
|
self.env.user.write({'groups_id': [(4, grp_multi_loc.id, 0)]})
|
|
|
|
# Create two empty packs
|
|
pack1 = self.env['stock.quant.package'].create({
|
|
'name': 'THEPACK1',
|
|
})
|
|
pack2 = self.env['stock.quant.package'].create({
|
|
'name': 'THEPACK2',
|
|
})
|
|
|
|
# Create a receipt and confirm it.
|
|
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.product2
|
|
move.product_uom_qty = 5
|
|
receipt_picking = receipt_form.save()
|
|
receipt_picking.action_confirm()
|
|
receipt_picking.action_assign()
|
|
|
|
url = self._get_client_action_url(receipt_picking.id)
|
|
self.start_tour(url, 'test_scan_line_splitting_preserve_destination', login='admin', timeout=180)
|
|
|
|
self.assertEqual(len(pack1.quant_ids), 1)
|
|
self.assertEqual(pack1.location_id.id, self.shelf3.id)
|
|
self.assertRecordValues(pack1.quant_ids, [
|
|
{'product_id': self.product2.id, 'quantity': 2, 'location_id': self.shelf3.id},
|
|
])
|
|
|
|
self.assertEqual(len(pack2.quant_ids), 1)
|
|
self.assertEqual(pack2.location_id.id, self.shelf4.id)
|
|
self.assertRecordValues(pack2.quant_ids, [
|
|
{'product_id': self.product2.id, 'quantity': 3, 'location_id': self.shelf4.id},
|
|
])
|