5191 lines
249 KiB
Python
5191 lines
249 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, http
|
|
from odoo.tests import Form, tagged
|
|
from odoo.addons.stock_barcode.controllers.stock_barcode import StockBarcodeController
|
|
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.env.user.write({'group_ids': [
|
|
Command.link(self.env.ref('stock.group_stock_multi_locations').id),
|
|
Command.link(self.env.ref('stock.group_tracking_lot').id),
|
|
]})
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.shelf1, 2.0)
|
|
self.env['stock.quant']._update_available_quantity(self.product2, self.shelf1, 2.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.StockPicking.write
|
|
picking_button_validate_orig = odoo.addons.stock.models.stock_picking.StockPicking.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.StockPicking.write', new=picking_write_mock),\
|
|
patch('odoo.addons.stock.models.stock_picking.StockPicking.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.assertRecordValues(internal_picking.move_line_ids, [
|
|
{"product_id": self.product1.id, "quantity": 2, "location_id": self.shelf1.id, "location_dest_id": self.shelf2.id, "picked": True, "result_package_id": False},
|
|
{"product_id": self.product2.id, "quantity": 1, "location_id": self.shelf1.id, "location_dest_id": self.shelf3.id, "picked": True, "result_package_id": False},
|
|
{"product_id": self.product2.id, "quantity": 1, "location_id": self.shelf1.id, "location_dest_id": self.shelf2.id, "picked": True, "result_package_id": False},
|
|
{"product_id": self.product1.id, "quantity": 1, "location_id": self.shelf1.id, "location_dest_id": self.shelf3.id, "picked": True, "result_package_id": False},
|
|
])
|
|
|
|
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.env.user.write({'group_ids': [Command.link(self.env.ref('stock.group_tracking_lot').id)]})
|
|
product3 = self.env['product.product'].create({
|
|
'name': 'product3',
|
|
'is_storable': True,
|
|
})
|
|
self.env['stock.package.type'].create([
|
|
{'name': 'Palet', 'barcode': 'PT_PALET', 'sequence_code': 'PAL'},
|
|
])
|
|
package1, package2 = self.env['stock.package'].create([{'name': 'package001'}, {'name': 'package002'}])
|
|
|
|
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)
|
|
self.env['stock.quant']._update_available_quantity(product3, self.stock_location, 2, package_id=package2)
|
|
|
|
delivery_with_move, delivery_for_package = 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, {
|
|
'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
|
|
})],
|
|
},
|
|
{
|
|
'name': "Delivery for package",
|
|
'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({
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.customer_location.id,
|
|
'product_id': product3.id,
|
|
'product_uom': self.uom_unit.id,
|
|
'product_uom_qty': 2
|
|
})],
|
|
}])
|
|
delivery_with_move.action_confirm()
|
|
delivery_with_move.action_assign()
|
|
delivery_for_package.action_confirm()
|
|
delivery_for_package.action_assign()
|
|
|
|
url = f'/odoo/{delivery_with_move.id}/action-stock_barcode.stock_barcode_picking_client_action'
|
|
self.start_tour(url, 'test_picking_scan_package_confirmation', login='admin', timeout=180)
|
|
|
|
def test_internal_picking_from_scratch_with_package(self):
|
|
""" This test ensures different flows regarding immediate transfers and packages:
|
|
|
|
1. Scan different products, put them in an existing empty package and scan a destination;
|
|
|
|
2. Scan a package and move it into another location;
|
|
|
|
3. Scan two packages, pack them into a palet and move the palet into a sublocation;
|
|
|
|
4. Scan a palet containing two packages and check we can't scan it multiple times.
|
|
|
|
For each case, check that the lines' destination is correcly set.
|
|
"""
|
|
self.env.user.write({
|
|
'group_ids': [
|
|
Command.link(self.ref('stock.group_tracking_lot')),
|
|
Command.link(self.ref('stock.group_stock_multi_locations')),
|
|
Command.link(self.ref('stock.group_tracking_owner')),
|
|
],
|
|
})
|
|
self.picking_type_internal.active = True
|
|
self.env['stock.package.type'].create([
|
|
{'name': 'Palet', 'sequence_code': 'PAL-', 'barcode': 'PT_PALET'},
|
|
])
|
|
# Creates a new package and add some quants.
|
|
package2, package3, package4 = self.env['stock.package'].create([{'name': f'P0000{i}'} for i in range(2, 5)])
|
|
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.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 1, package_id=package3)
|
|
self.env['stock.quant']._update_available_quantity(self.product2, self.stock_location, 1, package_id=package4)
|
|
self.assertEqual(package2.location_id.id, self.stock_location.id)
|
|
(self.product_tln_gtn8 | self.productlot1).tracking = 'none'
|
|
self.env['stock.quant']._update_available_quantity(self.product_tln_gtn8, self.stock_location, 2, owner_id=self.owner)
|
|
self.env['stock.quant']._update_available_quantity(self.productlot1, self.stock_location, 1, owner_id=self.owner)
|
|
self.env['stock.quant']._update_available_quantity(self.productlot1, self.stock_location, 2, owner_id=self.owner.copy())
|
|
|
|
self.start_tour("/odoo/barcode", 'test_internal_picking_from_scratch_with_package', login='admin')
|
|
|
|
# Check first package's content.
|
|
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},
|
|
])
|
|
|
|
# Check second package's content.
|
|
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},
|
|
])
|
|
|
|
# Check palet's content.
|
|
self.assertEqual(package3.parent_package_id.id, package4.parent_package_id.id)
|
|
self.assertTrue(package3.location_id.id == package4.location_id.id == self.shelf1.id)
|
|
palet = package3.parent_package_id
|
|
self.assertRecordValues(palet.contained_quant_ids.sorted(lambda q: q.product_id.id), [
|
|
{'product_id': self.product1.id, 'quantity': 1, 'location_id': self.shelf1.id, 'package_id': package3.id},
|
|
{'product_id': self.product2.id, 'quantity': 1, 'location_id': self.shelf1.id, 'package_id': package4.id},
|
|
])
|
|
self.assertRecordValues(self.product_tln_gtn8.stock_quant_ids, [
|
|
{'quantity': 1.0, 'location_id': self.stock_location.id, 'owner_id': self.owner.id},
|
|
{'quantity': 1.0, 'location_id': self.shelf2.id, 'owner_id': self.owner.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.env.user.write({'group_ids': [Command.link(self.env.ref('stock.group_stock_multi_locations').id)]})
|
|
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,
|
|
})
|
|
odoo.addons.stock.models.stock_picking.StockPicking.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({
|
|
'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({
|
|
'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_internal_picking_reserved_move_packages_into_new_palet(self):
|
|
""" Check we can unpack two palets then pack all the content (which is
|
|
packed in boxes) into a new one.
|
|
Also check the source and destination packages' labels use the right value.
|
|
"""
|
|
self.env.user.group_ids = [
|
|
Command.link(self.env.ref('stock.group_tracking_lot').id),
|
|
Command.link(self.env.ref('stock.group_stock_multi_locations').id),
|
|
]
|
|
self.picking_type_internal.active = True
|
|
self.picking_type_internal.show_entire_packs = True
|
|
box_type, palet_type = self.env['stock.package.type'].create([
|
|
{'name': 'Box', 'sequence_code': 'BOX-'},
|
|
{'name': 'Palet', 'sequence_code': 'PAL-', 'barcode': 'PT_PALET'},
|
|
])
|
|
# Create 6 boxes : 4 boxes to pack into 2 palets, and 2 stand-alone boxes.
|
|
packages = self.env['stock.package'].create([{
|
|
'name': f'BOX-0{i}',
|
|
'package_type_id': box_type.id,
|
|
} for i in range(1, 7)])
|
|
for i, package in enumerate(packages):
|
|
product = self.product1 if (i % 2 == 0) else self.product2
|
|
self.env['stock.quant']._update_available_quantity(product, self.stock_location, 5, package_id=package)
|
|
|
|
palets = self.env['stock.package'].create([{
|
|
'name': f'PAL-0{i}',
|
|
'package_type_id': palet_type.id,
|
|
} for i in range(1, 4)])
|
|
packages[:2].parent_package_id = palets[0]
|
|
packages[2:4].parent_package_id = palets[1]
|
|
|
|
# Create and confirm an internal transfer where we move both palets.
|
|
internal_pickings = self.env['stock.picking'].create([{
|
|
'name': f'TEST/INT/000{i}',
|
|
'location_id': self.picking_type_internal.default_location_src_id.id,
|
|
'location_dest_id': self.picking_type_internal.default_location_dest_id.id,
|
|
'picking_type_id': self.picking_type_internal.id,
|
|
} for i in (1, 2)])
|
|
internal_pickings[0].action_add_entire_packs(palets.ids)
|
|
internal_pickings[1].action_add_entire_packs(packages[4:].ids)
|
|
internal_pickings.action_confirm()
|
|
|
|
self.start_tour('/odoo/barcode', 'test_internal_picking_reserved_move_packages_into_new_palet', login='admin')
|
|
|
|
def test_receipt_from_scratch_with_lots_1(self):
|
|
self.env.user.write({
|
|
'group_ids': [
|
|
Command.link(self.env.ref('stock.group_stock_multi_locations').id),
|
|
Command.link(self.env.ref('stock.group_production_lot').id),
|
|
],
|
|
})
|
|
# 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': 'WHSTOCK-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.env.user.write({
|
|
'group_ids': [
|
|
Command.link(self.env.ref('stock.group_stock_multi_locations').id),
|
|
Command.link(self.env.ref('stock.group_production_lot').id),
|
|
],
|
|
})
|
|
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.
|
|
"""
|
|
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.env.user.write({'group_ids': [Command.link(self.env.ref('stock.group_production_lot').id)]})
|
|
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.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})
|
|
|
|
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({
|
|
'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.env.user.write({'group_ids': [Command.link(self.env.ref('stock.group_stock_multi_locations').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,
|
|
})
|
|
picking_write_orig = odoo.addons.stock.models.stock_picking.StockPicking.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': 'WHSTOCK-2',
|
|
})
|
|
|
|
move1 = self.env['stock.move'].create({
|
|
'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,
|
|
'description_picking': 'First Item\nMove carefully\nABC [123]',
|
|
})
|
|
move2 = self.env['stock.move'].create({
|
|
'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,
|
|
'description_picking': 'Second Item\nMove carefully\nDEF [456]',
|
|
})
|
|
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.StockPicking.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.env.user.write({'group_ids': [Command.link(self.env.ref('stock.group_tracking_lot').id)]})
|
|
# 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.new() as move:
|
|
move.product_id = self.product1
|
|
move.product_uom_qty = 3
|
|
with receipt_form.move_ids.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.
|
|
self._reset_package_sequence(1000)
|
|
|
|
# Opens the barcode main menu to be able to open the pickings by scanning their name.
|
|
self.start_tour("/odoo/barcode", "test_receipt_reserved_2_partial_put_in_pack", login="admin", timeout=180)
|
|
|
|
package1 = self.env['stock.package'].search([('name', '=', 'PACK0001000')])
|
|
package2 = self.env['stock.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_reserved_lot_putaway(self):
|
|
""" Test that a new line added for an extra lot on a reserved move takes that
|
|
move's destination (e.g. computed by a putaway rule) rather than the picking's
|
|
default destination.
|
|
"""
|
|
self.env.user.write({
|
|
'group_ids': [
|
|
Command.link(self.env.ref('stock.group_stock_multi_locations').id),
|
|
Command.link(self.env.ref('stock.group_production_lot').id),
|
|
],
|
|
})
|
|
self.env['stock.putaway.rule'].create({
|
|
'product_id': self.productlot1.id,
|
|
'location_in_id': self.stock_location.id,
|
|
'location_out_id': self.shelf1.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,
|
|
'move_ids': [Command.create({
|
|
'product_id': self.productlot1.id,
|
|
'product_uom': self.uom_unit.id,
|
|
'product_uom_qty': 2,
|
|
'location_id': self.supplier_location.id,
|
|
'location_dest_id': self.stock_location.id,
|
|
})],
|
|
})
|
|
receipt_picking.action_confirm()
|
|
receipt_picking.action_assign()
|
|
|
|
url = self._get_client_action_url(receipt_picking.id)
|
|
self.start_tour(url, 'test_receipt_reserved_lot_putaway', login='admin')
|
|
|
|
self.assertRecordValues(receipt_picking.move_line_ids.sorted('id'), [
|
|
{'lot_name': 'lot1', 'quantity': 1, 'location_dest_id': self.shelf1.id},
|
|
{'lot_name': 'lot2', 'quantity': 1, 'location_dest_id': self.shelf1.id},
|
|
])
|
|
|
|
def test_receipt_reserved_put_in_pack_after_interruption(self):
|
|
"""This test creates two receipts to ensure that:
|
|
1. We can pack move lines together even if the user leaves the operation between
|
|
scanning the products and scanning the package type.
|
|
2. When a package type is scanned, it's assigned to the last scanned package,
|
|
even if the selected line's product is packed into a package which
|
|
uses the last scanned package as its container.
|
|
"""
|
|
group_package = self.env.ref('stock.group_tracking_lot')
|
|
self.env.user.write({'group_ids': [Command.link(group_package.id)]})
|
|
self.picking_type_in.restrict_scan_product = True
|
|
self._reset_package_sequence()
|
|
# Create box and palet package types.
|
|
self.env['stock.package.type'].create([
|
|
{'name': 'Box', 'sequence_code': 'BOX-', 'barcode': 'PT_BOX'},
|
|
{'name': 'Palet', 'sequence_code': 'PAL-', 'barcode': 'PT_PALET'},
|
|
])
|
|
# Create two additional products.
|
|
product3, product4 = self.env['product.product'].create([{
|
|
'name': f'product{i}',
|
|
'is_storable': True,
|
|
'barcode': f'product{i}',
|
|
} for i in [3, 4]])
|
|
# Create and confirm a picking, then open it in the Barcode app.
|
|
receipts = self.env['stock.picking'].create([
|
|
{
|
|
'name': f'TEST/IN/000{i}',
|
|
'location_id': self.picking_type_in.default_location_src_id.id,
|
|
'location_dest_id': self.picking_type_in.default_location_dest_id.id,
|
|
'picking_type_id': self.picking_type_in.id,
|
|
'move_ids': [
|
|
Command.create({
|
|
'location_id': self.picking_type_in.default_location_src_id.id,
|
|
'location_dest_id': self.picking_type_in.default_location_dest_id.id,
|
|
'product_id': product.id,
|
|
'quantity': 1,
|
|
}) for product in products
|
|
],
|
|
} for (i, products) in [
|
|
(1, [self.product1, self.product2, product3, product4]),
|
|
(2, [self.product1, self.product2]),
|
|
]
|
|
])
|
|
receipts.action_confirm()
|
|
self.start_tour('/odoo/barcode', 'test_receipt_reserved_put_in_pack_after_interruption', login='admin')
|
|
|
|
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."""
|
|
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.ids, [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.env.user.write({'group_ids': [Command.link(self.env.ref('stock.group_stock_multi_locations').id)]})
|
|
# 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({
|
|
'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({
|
|
'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()
|
|
|
|
url = '/odoo/action-stock_barcode.stock_barcode_action_main_menu'
|
|
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.
|
|
"""
|
|
# 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({
|
|
'group_ids': [(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.env.user.write({
|
|
'group_ids': [
|
|
Command.link(self.env.ref('stock.group_production_lot').id),
|
|
Command.link(self.env.ref('stock.group_tracking_owner').id),
|
|
Command.link(self.env.ref('stock.group_tracking_lot').id),
|
|
],
|
|
})
|
|
self.picking_type_out.show_reserved_sns = True
|
|
# 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})
|
|
sn2 = snObj.create({'name': 'sn2', 'product_id': self.productserial1.id})
|
|
sn3 = snObj.create({'name': 'sn3', 'product_id': self.productserial1.id})
|
|
sn4 = snObj.create({'name': 'sn4', 'product_id': self.productserial1.id})
|
|
package1 = self.env['stock.package'].create({'name': 'pack_sn_1'})
|
|
package2 = self.env['stock.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({
|
|
'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.env.user.write({
|
|
'group_ids': [
|
|
Command.link(self.env.ref('stock.group_production_lot').id),
|
|
Command.link(self.env.ref('stock.group_tracking_lot').id),
|
|
Command.link(self.env.ref('stock.group_stock_multi_locations').id),
|
|
],
|
|
})
|
|
sn = self.env['stock.lot'].create({'name': 'sn', 'product_id': self.productlot1.id, 'company_id': self.env.company.id})
|
|
package1 = self.env['stock.package'].create({'name': 'pack_sn'})
|
|
package2 = self.env['stock.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({
|
|
'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_pack_from_different_location(self):
|
|
""" Ensures that when lines from different source location are packed,
|
|
their source location is unchanged."""
|
|
group_multi_loc = self.env.ref('stock.group_stock_multi_locations')
|
|
group_pack = self.env.ref('stock.group_tracking_lot')
|
|
self.env.user.write({'group_ids': [Command.link(group_multi_loc.id)]})
|
|
self.env.user.write({'group_ids': [Command.link(group_pack.id)]})
|
|
self.picking_type_out.restrict_scan_source_location = 'mandatory'
|
|
# Create an empty package (will be scanned during the tour.)
|
|
self.env['stock.package'].create({
|
|
'name': 'pack-test',
|
|
})
|
|
self.start_tour('/odoo/barcode', 'test_delivery_pack_from_different_location', login='admin')
|
|
|
|
def test_delivery_reserved_1(self):
|
|
self.env.user.write({'group_ids': [Command.link(self.env.ref('stock.group_stock_multi_locations').id)]})
|
|
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.StockPicking.write
|
|
url = self._get_client_action_url(delivery_picking.id)
|
|
|
|
self.env['stock.move'].create({
|
|
'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({
|
|
'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.StockPicking.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.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.StockPicking.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({
|
|
'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({
|
|
'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.StockPicking.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):
|
|
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.StockPicking.write
|
|
url = self._get_client_action_url(delivery_picking.id)
|
|
|
|
self.env['stock.move'].create({
|
|
'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.StockPicking.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.
|
|
"""
|
|
product3 = self.env['product.product'].create({
|
|
'name': 'product3',
|
|
'is_storable': True,
|
|
'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,
|
|
'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_reserved_5_dont_show_reserved_sn(self):
|
|
""" Checks reserved serial numbers aren't show until scanned when
|
|
`show_reserved_sns` is set on False."""
|
|
self.env.user.write({'group_ids': [Command.link(self.env.ref('stock.group_production_lot').id)]})
|
|
self.picking_type_out.show_reserved_sns = False
|
|
|
|
# Creates some SN and adds more than enough quantity on hand for the delivery.
|
|
serial_numbers = self.env['stock.lot'].create([{
|
|
'name': f'sn{n}',
|
|
'product_id': self.productserial1.id,
|
|
'company_id': self.env.company.id,
|
|
} for n in range(1, 6)])
|
|
for sn in serial_numbers:
|
|
self.env['stock.quant']._update_available_quantity(
|
|
self.productserial1, self.stock_location, 1, lot_id=sn)
|
|
|
|
# Creates and confirms a delivery.
|
|
delivery_picking = self.env['stock.picking'].create({
|
|
'name': 'test_delivery_reserved_5',
|
|
'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_id': delivery_picking.location_id.id,
|
|
'location_dest_id': delivery_picking.location_dest_id.id,
|
|
'picking_id': delivery_picking.id,
|
|
'product_id': self.productserial1.id,
|
|
'product_uom': self.uom_unit.id,
|
|
'product_uom_qty': 4,
|
|
})
|
|
delivery_picking.action_confirm()
|
|
reserved_move_lines = delivery_picking.move_line_ids
|
|
self.assertEqual(len(delivery_picking.move_line_ids), 4)
|
|
self.assertEqual(reserved_move_lines.mapped('quantity'), [1, 1, 1, 1])
|
|
self.assertEqual(reserved_move_lines.lot_id.mapped('name'), ['sn1', 'sn2', 'sn3', 'sn4'])
|
|
|
|
url = self._get_client_action_url(delivery_picking.id)
|
|
self.start_tour(url, 'test_delivery_reserved_5_dont_show_reserved_sn', login='admin', timeout=180)
|
|
self.assertEqual(delivery_picking.move_line_ids.lot_id.mapped('name'), ['sn1', 'sn2', 'sn3', 'sn5'])
|
|
|
|
def test_delivery_reserved_6_dont_show_reserved_lots(self):
|
|
""" Uncheck setting to display reserved lots and check they are not show
|
|
in the Barcode app until they are scanned."""
|
|
self.env.user.write({'group_ids': [Command.link(self.env.ref('stock.group_production_lot').id)]})
|
|
self.picking_type_out.show_reserved_sns = False
|
|
|
|
# Creates some lots and adds more than enough quantity on hand for the delivery.
|
|
lots = self.env['stock.lot'].create([{
|
|
'name': f'lot-00{n}',
|
|
'product_id': self.productlot1.id,
|
|
'company_id': self.env.company.id,
|
|
} for n in range(1, 6)])
|
|
for lot in lots:
|
|
self.env['stock.quant']._update_available_quantity(
|
|
self.productlot1, self.stock_location, 4, lot_id=lot)
|
|
|
|
# Creates and confirms a delivery.
|
|
delivery_picking = self.env['stock.picking'].create({
|
|
'name': 'test_delivery_reserved_6',
|
|
'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_id': delivery_picking.location_id.id,
|
|
'location_dest_id': delivery_picking.location_dest_id.id,
|
|
'picking_id': delivery_picking.id,
|
|
'product_id': self.productlot1.id,
|
|
'product_uom': self.uom_unit.id,
|
|
'product_uom_qty': 12,
|
|
})
|
|
delivery_picking.action_confirm()
|
|
reserved_move_lines = delivery_picking.move_line_ids
|
|
self.assertEqual(len(delivery_picking.move_line_ids), 3)
|
|
self.assertEqual(reserved_move_lines.mapped('quantity'), [4, 4, 4])
|
|
self.assertEqual(reserved_move_lines.lot_id.mapped('name'), ['lot-001', 'lot-002', 'lot-003'])
|
|
|
|
url = self._get_client_action_url(delivery_picking.id)
|
|
self.start_tour(url, 'test_delivery_reserved_6_dont_show_reserved_lots', login='admin', timeout=180)
|
|
|
|
def test_delivery_from_scratch_1(self):
|
|
""" Scan unreserved lots on a delivery order.
|
|
"""
|
|
self.env.user.write({'group_ids': [Command.link(self.env.ref('stock.group_production_lot').id)]})
|
|
|
|
# Adds lot1 and lot2 for productlot1
|
|
lotObj = self.env['stock.lot']
|
|
lotObj.create({'name': 'lot1', 'product_id': self.productlot1.id})
|
|
lotObj.create({'name': 'lot2', 'product_id': self.productlot1.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.env.user.write({'group_ids': [Command.link(self.env.ref('stock.group_production_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,
|
|
})
|
|
|
|
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.product1 + self.product2).tracking = 'lot'
|
|
|
|
lot01, lot02, sn = self.env['stock.lot'].create([{
|
|
'name': lot_name,
|
|
'product_id': product.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.env.user.write({'group_ids': [Command.link(self.env.ref('stock.group_production_lot').id)]})
|
|
self.picking_type_out.show_reserved_sns = True
|
|
|
|
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({
|
|
'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})
|
|
lot2 = lotObj.create({'name': 'lot2', 'product_id': self.productlot1.id})
|
|
lot3 = lotObj.create({'name': 'lot3', 'product_id': self.productlot1.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.productlot2 = self.env['product.product'].create({
|
|
'name': 'productlot2',
|
|
'is_storable': True,
|
|
'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({
|
|
'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({
|
|
'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})
|
|
lot2 = self.env['stock.lot'].create({'name': 'lot1', 'product_id': self.productlot2.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,
|
|
'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_reserved_with_sn_1(self):
|
|
""" Scan unreserved serial number on a delivery order.
|
|
"""
|
|
self.env.user.write({'group_ids': [Command.link(self.env.ref('stock.group_production_lot').id)]})
|
|
|
|
# Add 4 serial numbers productserial1
|
|
snObj = self.env['stock.lot']
|
|
sn1 = snObj.create({'name': 'sn1', 'product_id': self.productserial1.id})
|
|
sn2 = snObj.create({'name': 'sn2', 'product_id': self.productserial1.id})
|
|
sn3 = snObj.create({'name': 'sn3', 'product_id': self.productserial1.id})
|
|
sn4 = snObj.create({'name': 'sn4', 'product_id': self.productserial1.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({
|
|
'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()
|
|
|
|
url = self._get_client_action_url(delivery_picking.id)
|
|
self.start_tour(url, 'test_delivery_reserved_with_sn_1', login='admin', timeout=180)
|
|
|
|
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.
|
|
"""
|
|
# Creates a new product.
|
|
product3 = self.env['product.product'].create({
|
|
'name': 'product3',
|
|
'is_storable': True,
|
|
'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.new() as move:
|
|
move.product_id = self.product1
|
|
move.product_uom_qty = 2
|
|
with delivery_form.move_ids.new() as move:
|
|
move.product_id = self.product2
|
|
move.product_uom_qty = 3
|
|
with delivery_form.move_ids.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.env.user.write({'group_ids': [Command.link(self.env.ref('stock.group_production_lot').id)]})
|
|
self.picking_type_out.show_reserved_sns = True
|
|
lot01, lot02 = self.env['stock.lot'].create([{
|
|
'name': lot_name,
|
|
'product_id': self.productlot1.id,
|
|
} for lot_name in ["LOT01", "LOT02"]])
|
|
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, 1)
|
|
self.env['stock.quant']._update_available_quantity(self.productlot1, self.shelf1, 2, lot_id=lot01)
|
|
self.env['stock.quant']._update_available_quantity(self.productlot1, self.shelf1, 2, lot_id=lot02)
|
|
# 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.new() as move:
|
|
move.product_id = self.product1
|
|
move.product_uom_qty = 4
|
|
|
|
with delivery_form.move_ids.new() as move:
|
|
move.product_id = self.product2
|
|
move.product_uom_qty = 0.12
|
|
|
|
with delivery_form.move_ids.new() as move:
|
|
move.product_id = self.productlot1
|
|
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.env.user.write({
|
|
'group_ids': [
|
|
Command.link(self.env.ref('stock.group_stock_multi_locations').id),
|
|
Command.link(self.env.ref('stock.group_production_lot').id),
|
|
],
|
|
})
|
|
|
|
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({
|
|
'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})
|
|
lotObj.create({'name': 'lot2', 'product_id': self.productlot1.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.env.user.write({'group_ids': [Command.link(self.env.ref('stock.group_tracking_lot').id)]})
|
|
|
|
# set sequence packages to 1000 to find it easily in the tour
|
|
sequence = self.env['ir.sequence'].search([(
|
|
'code', '=', 'stock.package',
|
|
)], limit=1)
|
|
sequence.write({'number_next_actual': 1000})
|
|
|
|
self.start_tour("/odoo/barcode", 'test_pack_multiple_scan', login='admin', timeout=180)
|
|
|
|
# Check the new package is well delivered
|
|
package = self.env['stock.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.env.user.write({'group_ids': [Command.link(self.env.ref('stock.group_tracking_lot').id)]})
|
|
|
|
# Create a pack and 2 quants in this pack
|
|
pack1 = self.env['stock.package'].create({
|
|
'name': 'PACK1',
|
|
})
|
|
pack2 = self.env['stock.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("/odoo/barcode", '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.env.user.write({
|
|
'group_ids': [
|
|
Command.link(self.env.ref('stock.group_tracking_lot').id),
|
|
Command.link(self.env.ref('stock.group_stock_multi_locations').id),
|
|
],
|
|
})
|
|
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'
|
|
|
|
# Create a pack and 2 quants in this pack
|
|
pack1 = self.env['stock.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("/odoo/barcode", '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.env.user.write({
|
|
'group_ids': [
|
|
Command.link(self.env.ref('stock.group_tracking_lot').id),
|
|
Command.link(self.env.ref('stock.group_stock_multi_locations').id),
|
|
],
|
|
})
|
|
# Creates a package with 1 quant in it.
|
|
pack1 = self.env['stock.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({
|
|
'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.env.user.write({
|
|
'group_ids': [
|
|
Command.link(self.env.ref('stock.group_tracking_lot').id),
|
|
Command.link(self.env.ref('stock.group_stock_multi_locations').id),
|
|
],
|
|
})
|
|
|
|
# Creates a package with a quant in it.
|
|
pack1 = self.env['stock.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.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_multiple_scan_entire_packs(self):
|
|
""" With "Move Entire Packages" enabled and more than one package in the
|
|
transfer, re-scanning an already scanned package must be rejected instead
|
|
of adding its content a second time.
|
|
"""
|
|
self.env.user.write({
|
|
'group_ids': [
|
|
Command.link(self.env.ref('stock.group_tracking_lot').id),
|
|
Command.link(self.env.ref('stock.group_stock_multi_locations').id),
|
|
],
|
|
})
|
|
self.picking_type_internal.active = True
|
|
self.picking_type_internal.show_entire_packs = True
|
|
|
|
pack1, pack2 = self.env['stock.package'].create([
|
|
{'name': 'PACK0000111'},
|
|
{'name': 'PACK0000222'},
|
|
])
|
|
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=pack2)
|
|
|
|
self.start_tour("/odoo/barcode", 'test_pack_multiple_scan_entire_packs', login='admin')
|
|
|
|
self.assertRecordValues(pack1.quant_ids, [
|
|
{'product_id': self.product1.id, 'location_id': self.shelf2.id, 'quantity': 5},
|
|
])
|
|
|
|
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.env.user.write({
|
|
'group_ids': [
|
|
Command.link(self.env.ref('stock.group_tracking_lot').id),
|
|
Command.link(self.env.ref('stock.group_stock_multi_locations').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.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.env.user.write({
|
|
'group_ids': [
|
|
Command.link(self.env.ref('stock.group_tracking_lot').id),
|
|
Command.link(self.env.ref('stock.group_stock_multi_locations').id),
|
|
],
|
|
})
|
|
# 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'
|
|
|
|
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({
|
|
'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({
|
|
'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.package'].search([])[-1]
|
|
self.assertEqual(len(pack.quant_ids), 2)
|
|
self.assertEqual(sum(pack.quant_ids.mapped('quantity')), 4)
|
|
|
|
def test_put_in_pack_in_new_created_package(self):
|
|
""" Ensures when the user puts in pack a product in a new package, they can
|
|
scan the created package's barcode to put a second product in this package.
|
|
"""
|
|
group_package = self.env.ref('stock.group_tracking_lot')
|
|
self.env.user.write({'group_ids': [Command.link(group_package.id)]})
|
|
self._reset_package_sequence(42)
|
|
# Create a receipt for two products.
|
|
receipt = self.env['stock.picking'].create({
|
|
'location_id': self.picking_type_in.default_location_src_id.id,
|
|
'location_dest_id': self.picking_type_in.default_location_dest_id.id,
|
|
'name': "TEST/IN/0001",
|
|
'picking_type_id': self.picking_type_in.id,
|
|
'move_ids': [
|
|
Command.create({
|
|
'location_id': self.picking_type_in.default_location_src_id.id,
|
|
'location_dest_id': self.picking_type_in.default_location_dest_id.id,
|
|
'product_id': self.product1.id,
|
|
'product_uom_qty': 1,
|
|
}),
|
|
Command.create({
|
|
'location_id': self.picking_type_in.default_location_src_id.id,
|
|
'location_dest_id': self.picking_type_in.default_location_dest_id.id,
|
|
'product_id': self.product2.id,
|
|
'product_uom_qty': 1,
|
|
}),
|
|
],
|
|
})
|
|
receipt.action_confirm()
|
|
self.start_tour('/odoo/barcode', 'test_put_in_pack_in_new_created_package', login='admin')
|
|
self.assertEqual(receipt.move_ids.move_line_ids.result_package_id.name, "PACK0000042")
|
|
self.assertEqual(
|
|
receipt.move_ids[0].move_line_ids.result_package_id.id,
|
|
receipt.move_ids[1].move_line_ids.result_package_id.id,
|
|
"The two receipt's moves must go into the same package"
|
|
)
|
|
|
|
def test_put_in_pack_no_freeze(self):
|
|
""" Test that the page doesn't freeze when clicking on put in pack """
|
|
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.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_no_freeze', login='admin', timeout=180)
|
|
|
|
def test_delivery_put_in_pack_set_package_type(self):
|
|
"""
|
|
Check that the put-in-pack wizard opens from the Barcode app when the
|
|
operation type requires choosing a package type, letting the user pack the
|
|
delivery into a package of the chosen type before validating.
|
|
"""
|
|
self.env.user.group_ids += self.env.ref('stock.group_tracking_lot')
|
|
self.picking_type_out.set_package_type = True
|
|
package_type = self.env['stock.package.type'].create({'name': 'LovelyBox', 'barcode': 'LovelyPackageType'})
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 2)
|
|
deliveries = self.env['stock.picking'].create([
|
|
{
|
|
'name': f"TEST/OUT/PACK{i + 1}",
|
|
'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({
|
|
'product_id': self.product1.id,
|
|
'product_uom_qty': 1,
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.customer_location.id,
|
|
})],
|
|
} for i in range(2)
|
|
])
|
|
deliveries.action_confirm()
|
|
|
|
self.start_tour('/odoo/barcode', 'test_delivery_put_in_pack_set_package_type', login='admin')
|
|
result_package = deliveries.move_line_ids.result_package_id
|
|
self.assertRecordValues(result_package, [
|
|
{'package_type_id': package_type.id},
|
|
{'package_type_id': package_type.id},
|
|
])
|
|
|
|
def test_unpack_package_lines(self):
|
|
"""Ensure the unpack button works as expected, unpacking the lines beginning
|
|
with the outermost package and then only unpacking the result package."""
|
|
# Config.
|
|
group_package = self.env.ref('stock.group_tracking_lot')
|
|
self.env.user.write({'group_ids': [Command.link(group_package.id)]})
|
|
self.picking_type_out.show_entire_packs = True
|
|
# Create some packages and add packed quantities in stock.
|
|
box1, box2, palet1, palet2 = self.env['stock.package'].create([
|
|
{'name': "BOX01"}, {'name': "BOX02"},
|
|
{'name': "PAL01"}, {'name': "PAL02"},
|
|
])
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 4, package_id=box1)
|
|
self.env['stock.quant']._update_available_quantity(self.product2, self.stock_location, 4, package_id=box1)
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 4, package_id=box2)
|
|
self.env['stock.quant']._update_available_quantity(self.product2, self.stock_location, 4, package_id=box2)
|
|
# Pack boxes into a palet.
|
|
box1.parent_package_id = palet1
|
|
box2.parent_package_id = palet1
|
|
# Create a delivery and process it. Unpack all the products before to
|
|
# pack them in a single new package.
|
|
delivery = self.env['stock.picking'].create({
|
|
'location_id': self.picking_type_out.default_location_src_id.id,
|
|
'location_dest_id': self.picking_type_out.default_location_dest_id.id,
|
|
'picking_type_id': self.picking_type_out.id,
|
|
'move_ids': [Command.create({
|
|
'product_id': product.id,
|
|
'product_uom_qty': 8,
|
|
}) for product in (self.product1, self.product2)],
|
|
})
|
|
delivery.action_confirm()
|
|
url = self._get_client_action_url(delivery.id)
|
|
self.start_tour(url, 'test_unpack_package_lines', login='admin')
|
|
# palet2 must have all the products while other packages must be empty.
|
|
self.assertRecordValues(palet2.quant_ids, [
|
|
{'product_id': self.product1.id, 'quantity': 8, 'package_id': palet2.id},
|
|
{'product_id': self.product2.id, 'quantity': 8, 'package_id': palet2.id},
|
|
])
|
|
self.assertEqual((box1.quant_ids | box2.quant_ids | palet1.quant_ids).ids, [])
|
|
|
|
def test_unpack_palet_then_pack_another_palet(self):
|
|
""" Ensure we can unpack a palet than scan the palet barcode to pack
|
|
all lines in a new palet."""
|
|
group_package = self.env.ref('stock.group_tracking_lot')
|
|
group_location = self.env.ref('stock.group_stock_multi_locations')
|
|
self.env.user.write({'group_ids': [Command.link(group_location.id), Command.link(group_package.id)]})
|
|
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.picking_type_internal.show_entire_packs = True
|
|
self.picking_type_out.restrict_scan_source_location = 'no'
|
|
self.picking_type_out.show_entire_packs = True
|
|
# Create some packages and add packed quantities in stock.
|
|
box, palet = self.env['stock.package.type'].create([
|
|
{'name': 'Box', 'sequence_code': 'BOX-'},
|
|
{'name': 'Palet', 'sequence_code': 'PAL-', 'barcode': 'PT_PALET'},
|
|
])
|
|
box1, box2, palet1 = self.env['stock.package'].create([
|
|
{'name': "BOX01", 'package_type_id': box.id},
|
|
{'name': "BOX02", 'package_type_id': box.id},
|
|
{'name': "PAL01", 'package_type_id': palet.id},
|
|
])
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 4, package_id=box1)
|
|
self.env['stock.quant']._update_available_quantity(self.product2, self.stock_location, 4, package_id=box1)
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 4, package_id=box2)
|
|
self.env['stock.quant']._update_available_quantity(self.product2, self.stock_location, 4, package_id=box2)
|
|
# Pack boxes into a palet.
|
|
box1.parent_package_id = palet1
|
|
box2.parent_package_id = palet1
|
|
self.start_tour('/odoo/barcode', 'test_unpack_palet_then_pack_another_palet', login='admin')
|
|
|
|
def test_reload_flow(self):
|
|
self.env.user.write({'group_ids': [Command.link(self.env.ref('stock.group_stock_multi_locations').id)]})
|
|
|
|
self.start_tour("/odoo/barcode", '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.env.user.write({
|
|
'group_ids': [
|
|
Command.link(self.env.ref('stock.group_production_lot').id),
|
|
Command.link(self.env.ref('stock.group_stock_multi_locations').id),
|
|
],
|
|
})
|
|
self.start_tour("/odoo/barcode", 'test_receipt_duplicate_serial_number', login='admin', timeout=180)
|
|
self.start_tour("/odoo/barcode", '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.env.user.write({
|
|
'group_ids': [
|
|
Command.link(self.env.ref('stock.group_stock_multi_locations').id),
|
|
Command.link(self.env.ref('stock.group_tracking_lot').id),
|
|
Command.link(self.env.ref('stock.group_production_lot').id),
|
|
],
|
|
})
|
|
# 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})
|
|
lot2 = self.env['stock.lot'].create({'name': 'serial1', 'product_id': self.productserial1.id})
|
|
# Unreserved serial stored in a sublocation (Section 3), to be scanned on top of the reserved one.
|
|
sn_shelf = self.env['stock.lot'].create({'name': 'sn_shelf', 'product_id': self.productserial1.id})
|
|
|
|
pack1 = self.env['stock.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.productserial1, self.shelf3, 1, lot_id=sn_shelf)
|
|
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.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({
|
|
'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({
|
|
'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({
|
|
'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)
|
|
|
|
# The unreserved serial was sourced from the sublocation where it was stored.
|
|
self.assertEqual(
|
|
delivery_picking.move_line_ids.filtered(lambda ml: ml.lot_id == sn_shelf).location_id,
|
|
self.shelf3,
|
|
)
|
|
|
|
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.env.user.write({
|
|
'group_ids': [
|
|
Command.link(self.env.ref('stock.group_stock_multi_locations').id),
|
|
Command.link(self.env.ref('stock.group_tracking_lot').id),
|
|
],
|
|
})
|
|
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._reset_package_sequence()
|
|
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({
|
|
'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({
|
|
'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,
|
|
})
|
|
|
|
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.env.user.write({
|
|
'group_ids': [
|
|
Command.link(self.env.ref('stock.group_stock_multi_locations').id),
|
|
Command.link(self.env.ref('stock.group_tracking_lot').id),
|
|
],
|
|
})
|
|
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({
|
|
'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({
|
|
'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 = internal_picking.move_line_ids.result_package_id
|
|
self.assertEqual(len(pack), 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.env.user.write({
|
|
'group_ids': [
|
|
Command.link(self.env.ref('stock.group_stock_multi_locations').id),
|
|
Command.link(self.env.ref('stock.group_tracking_lot').id),
|
|
],
|
|
})
|
|
self._reset_package_sequence()
|
|
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)
|
|
|
|
# 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.new() as move:
|
|
move.product_id = self.product1
|
|
move.product_uom_qty = 2
|
|
with delivery_form.move_ids.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.env.user.write({'group_ids': [Command.link(self.env.ref('stock.group_tracking_lot').id)]})
|
|
|
|
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_put_packs_in_existing_pack(self):
|
|
""" Check we can put multiple packs into another pack by scanning an existing package
|
|
and that we can do that multiple times with no conflict."""
|
|
group_package = self.env.ref('stock.group_tracking_lot')
|
|
self.env.user.write({'group_ids': [Command.link(group_package.id)]})
|
|
self.picking_type_out.restrict_scan_source_location = 'no'
|
|
# Create two package types.
|
|
mini_box, maxi_box = self.env['stock.package.type'].create([
|
|
{'name': 'Mini-Box', 'sequence_code': 'MNB-'},
|
|
{'name': 'Maxi-Box', 'sequence_code': 'MXB-'},
|
|
])
|
|
# Create some packages: 4 mini-boxes and 2 maxi-boxes.
|
|
package_create_vals = []
|
|
for amount, package_type in [(4, mini_box), (2, maxi_box)]:
|
|
package_create_vals += ([{
|
|
'package_type_id': package_type.id,
|
|
} for __ in range(amount)])
|
|
packages = self.env['stock.package'].create(package_create_vals)
|
|
# Add quantities in stock, already packed in mini boxes.
|
|
for pack in packages:
|
|
if pack.package_type_id == maxi_box:
|
|
continue
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 4, package_id=pack)
|
|
# Create and confirm a delivery to process in the Barcode app.
|
|
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,
|
|
'move_ids': [Command.create({
|
|
'product_id': self.product1.id,
|
|
'product_uom_qty': 16,
|
|
})],
|
|
})
|
|
out_picking.action_confirm()
|
|
url = self._get_client_action_url(out_picking.id)
|
|
self.start_tour(url, 'test_put_packs_in_existing_pack', login='admin')
|
|
|
|
def test_put_packs_in_new_pack(self):
|
|
""" Check we can put multiple packs into another pack by scanning a package type barcode
|
|
and that we can do that multiple times with no conflict.
|
|
Check we can do that for both receipt (scan product, then put in pack, then put packs in pack)
|
|
and for delivery (scan package then put packs in pack.)
|
|
Also check that the packages are displayed on grouped lines if it contains a single subline.
|
|
"""
|
|
group_lot = self.env.ref('stock.group_production_lot')
|
|
group_package = self.env.ref('stock.group_tracking_lot')
|
|
self.env.user.write({'group_ids': [Command.link(group_package.id), Command.link(group_lot.id)]})
|
|
self.picking_type_out.restrict_scan_source_location = 'no'
|
|
# Create two package types.
|
|
package_types = self.env['stock.package.type'].create([
|
|
{'name': 'Mini-Box', 'barcode': 'minibox', 'sequence_code': 'MNB-'},
|
|
{'name': 'Maxi-Box', 'barcode': 'maxibox', 'sequence_code': 'MXB-'},
|
|
])
|
|
# Create 4 mini-boxes and add quantities in stock, already packed in mini boxes.
|
|
packages = self.env['stock.package'].create([
|
|
{'package_type_id': package_types[0].id} for __ in range(4)
|
|
] + [
|
|
{'name': 'PACK0007'}
|
|
])
|
|
for pack in packages[:4]:
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 4, package_id=pack)
|
|
lot = self.env['stock.lot'].create({
|
|
'name': 'LOT0007',
|
|
'product_id': self.productlot1.id,
|
|
})
|
|
self.env['stock.quant']._update_available_quantity(self.productlot1, self.stock_location, 10)
|
|
# Create and confirm a delivery to process in the Barcode app.
|
|
out_picking = self.env['stock.picking'].create({
|
|
'name': 'WH/OUT/TEST/001',
|
|
'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({'product_id': self.product1.id, 'product_uom_qty': 16}),
|
|
Command.create({'product_id': self.productlot1.id, 'product_uom_qty': 3}),
|
|
],
|
|
})
|
|
out_picking.action_confirm()
|
|
self.env['stock.quant']._update_available_quantity(self.productlot1, self.stock_location, 1, lot_id=lot, package_id=packages[4])
|
|
self.start_tour('/odoo/barcode', 'test_put_packs_in_new_pack', login='admin')
|
|
|
|
def test_highlight_packs(self):
|
|
""" Ensures the right line is selected regarding the last scanned package. Also check a
|
|
barcode line is correctly unselected when a package line is selected and vice versa."""
|
|
self.env.user.write({'group_ids': [Command.link(self.env.ref('stock.group_tracking_lot').id)]})
|
|
|
|
pack1 = self.env['stock.package'].create({
|
|
'name': 'PACK001',
|
|
})
|
|
pack2 = self.env['stock.package'].create({
|
|
'name': 'PACK002',
|
|
})
|
|
product3 = self.env['product.product'].create({
|
|
'name': 'product3',
|
|
'is_storable': True,
|
|
'barcode': 'product3',
|
|
})
|
|
|
|
self.env['stock.quant']._update_available_quantity(product3, self.stock_location, 1)
|
|
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',
|
|
'move_ids': [Command.create({
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.customer_location.id,
|
|
'product_id': product3.id,
|
|
'product_uom': product3.uom_id.id,
|
|
'product_uom_qty': 1,
|
|
})],
|
|
})
|
|
|
|
self.picking_type_out.show_entire_packs = True
|
|
out_picking.action_add_entire_packs(pack1.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')
|
|
|
|
def test_picking_owner_scan_package(self):
|
|
self.env.user.write({
|
|
'group_ids': [
|
|
Command.link(self.env.ref('stock.group_tracking_owner').id),
|
|
Command.link(self.env.ref('stock.group_tracking_lot').id),
|
|
],
|
|
})
|
|
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 7, package_id=self.package, owner_id=self.owner)
|
|
|
|
self.start_tour("/odoo/barcode", '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.'''
|
|
# Enables packages and multi-locations.
|
|
self.env.user.write({
|
|
'group_ids': [
|
|
Command.link(self.env.ref('stock.group_stock_multi_locations').id),
|
|
Command.link(self.env.ref('stock.group_production_lot').id),
|
|
],
|
|
})
|
|
# 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',
|
|
'is_storable': True,
|
|
})
|
|
# 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.new() as move:
|
|
move.product_id = self.product1
|
|
move.product_uom_qty = 4
|
|
with picking_form.move_ids.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.new() as move:
|
|
move.product_id = self.product1
|
|
move.product_uom_qty = 4
|
|
with picking_form.move_ids.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.env.user.write({
|
|
'group_ids': [
|
|
Command.link(self.env.ref('stock.group_stock_multi_locations').id),
|
|
Command.link(self.env.ref('stock.group_production_lot').id),
|
|
Command.link(self.env.ref('stock.group_tracking_lot').id),
|
|
],
|
|
})
|
|
self.picking_type_in.barcode_validation_after_dest_location = True
|
|
# 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',
|
|
'is_storable': True,
|
|
})
|
|
# Create an empty package.
|
|
package = self.env['stock.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.new() as move:
|
|
move.product_id = self.product1
|
|
move.product_uom_qty = 4
|
|
with picking_form.move_ids.new() as move:
|
|
move.product_id = product_without_barcode
|
|
move.product_uom_qty = 4
|
|
with picking_form.move_ids.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_receipt_assign_sibling_reservation_no_empty_line(self):
|
|
""" This check ensure that changing the dest location does not create an empty line.
|
|
"""
|
|
# Enables multi-locations and lots.
|
|
self.env.user.write({
|
|
'group_ids': [
|
|
Command.link(self.env.ref('stock.group_stock_multi_locations').id),
|
|
Command.link(self.env.ref('stock.group_production_lot').id),
|
|
],
|
|
})
|
|
|
|
picking_form = Form(self.env['stock.picking'])
|
|
picking_form.picking_type_id = self.picking_type_in
|
|
with picking_form.move_ids.new() as move:
|
|
move.product_id = self.productlot1
|
|
move.product_uom_qty = 2
|
|
|
|
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_assign_sibling_reservation_no_empty_line', login='admin', timeout=180)
|
|
|
|
self.assertEqual(picking_receipt.state, 'done')
|
|
(lot1, lot2) = self.env['stock.lot'].search([
|
|
('product_id', '=', self.productlot1.id), ('name', 'in', ['lot-01', 'lot-02'])
|
|
])
|
|
move_lines = picking_receipt.move_line_ids.sorted(lambda ml: (ml.product_id.id, ml.lot_id.id))
|
|
self.assertRecordValues(move_lines, [
|
|
{'product_id': self.productlot1.id, 'location_dest_id': self.shelf1.id, 'qty_done': 1, 'lot_id': lot1.id},
|
|
{'product_id': self.productlot1.id, 'location_dest_id': self.shelf1.id, 'qty_done': 1, 'lot_id': lot2.id},
|
|
])
|
|
|
|
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.new() as move:
|
|
move.product_id = self.product1
|
|
move.product_uom_qty = 2
|
|
with picking_form.move_ids.new() as move:
|
|
move.product_id = self.product2
|
|
move.product_uom_qty = 1
|
|
with picking_form.move_ids.new() as move:
|
|
move.product_id = product_without_barcode
|
|
move.product_uom_qty = 1
|
|
with picking_form.move_ids.new() as move:
|
|
move.product_id = self.productserial1
|
|
move.product_uom_qty = 3
|
|
with picking_form.move_ids.new() as move:
|
|
move.product_id = self.productlot1
|
|
move.product_uom_qty = 6
|
|
return picking_form.save()
|
|
|
|
# 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',
|
|
'is_storable': True,
|
|
})
|
|
|
|
# Enables packages, multi-locations and multiple steps routes.
|
|
self.env.user.write({'group_ids': [(4, self.env.ref('stock.group_production_lot').id, 0)]})
|
|
self.env.user.write({'group_ids': [(4, self.env.ref('stock.group_tracking_lot').id, 0)]})
|
|
self.env.user.write({'group_ids': [(4, self.env.ref('stock.group_stock_multi_locations').id, 0)]})
|
|
self.env.user.write({'group_ids': [(4, self.env.ref('stock.group_adv_location').id, 0)]})
|
|
self.warehouse.reception_steps = 'two_steps'
|
|
self.warehouse.delivery_steps = 'pick_pack_ship'
|
|
|
|
# Creates two cluster packs.
|
|
reusable_type = self.env['stock.package.type'].create({
|
|
'name': 'Reusable cluster',
|
|
'package_use': 'reusable',
|
|
})
|
|
self.env['stock.package'].create({
|
|
'name': 'cluster-pack-01',
|
|
'package_type_id': reusable_type.id,
|
|
})
|
|
self.env['stock.package'].create({
|
|
'name': 'cluster-pack-02',
|
|
'package_type_id': reusable_type.id,
|
|
})
|
|
self._reset_package_sequence()
|
|
|
|
# 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'
|
|
# Quality Control / Storage (internal transfer): no put in pack, scan dest. after each product.
|
|
internal_types = self.warehouse.qc_type_id | self.warehouse.store_type_id
|
|
internal_types.barcode_validation_full = False
|
|
internal_types.restrict_put_in_pack = 'no'
|
|
internal_types.restrict_scan_dest_location = 'mandatory'
|
|
internal_types.show_reserved_sns = True
|
|
# Pick: source mandatory, lots reserved only.
|
|
self.warehouse.pick_type_id.barcode_validation_full = False
|
|
self.warehouse.pick_type_id.restrict_scan_source_location = 'mandatory'
|
|
self.warehouse.pick_type_id.restrict_put_in_pack = 'mandatory' # Will use cluster packs.
|
|
self.warehouse.pick_type_id.restrict_scan_tracking_number = 'mandatory'
|
|
self.warehouse.pick_type_id.restrict_scan_dest_location = 'no'
|
|
self.warehouse.pick_type_id.show_reserved_sns = True
|
|
# Pack: pack after group, all products have to be packed to be validate.
|
|
self.warehouse.pack_type_id.restrict_put_in_pack = 'optional'
|
|
self.warehouse.pack_type_id.restrict_scan_tracking_number = 'mandatory'
|
|
self.warehouse.pack_type_id.barcode_validation_all_product_packed = True
|
|
self.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_source_location = 'no'
|
|
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()
|
|
|
|
# Creates the pick, pack, ship.
|
|
picking_pick = create_picking(self.warehouse.pick_type_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')
|
|
|
|
# Get the storage operation (created by the receipt).
|
|
picking_internal = picking_receipt.move_ids.move_dest_ids.picking_id
|
|
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 = self.env['stock.picking'].search([('location_id', '=', self.warehouse.pack_type_id.default_location_src_id.id)])
|
|
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 = self.env['stock.picking'].search([('location_id', '=', self.warehouse.out_type_id.default_location_src_id.id)])
|
|
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):
|
|
product_a, _product_b = self.env['product.product'].create([{
|
|
'name': p_name,
|
|
'is_storable': True,
|
|
'barcode': p_name,
|
|
} for p_name in ['PA', 'PB']])
|
|
|
|
reference = self.env["stock.reference"].create({'name': "test_procurement_backorder"})
|
|
|
|
procurement = self.env["stock.rule"].Procurement(
|
|
product_a, 1, product_a.uom_id,
|
|
self.env.ref('stock.stock_location_customers'),
|
|
product_a.name,
|
|
"/",
|
|
self.env.company,
|
|
{
|
|
"warehouse_id": self.warehouse,
|
|
"reference_ids": reference,
|
|
}
|
|
)
|
|
self.env["stock.rule"].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(reference.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.
|
|
"""
|
|
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({
|
|
'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',
|
|
'is_storable': True,
|
|
'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_scan_aggregate_barcode(self):
|
|
""" Checks the config parameter `stock_barcode.barcode_separator_regex`
|
|
works as expected and it's possible to scan aggregate barcodes if its
|
|
individual barcode encodings are separated by the separator."""
|
|
self.env.user.write({'group_ids': [Command.link(self.env.ref('stock.group_production_lot').id)]})
|
|
self.env['ir.config_parameter'].set_param('stock_barcode.barcode_separator_regex', '[,|]')
|
|
url = "/odoo/action-stock_barcode.stock_barcode_action_main_menu"
|
|
self.start_tour(url, "test_scan_aggregate_barcode", login="admin", timeout=180)
|
|
# Check the receipt values.
|
|
domain = [('picking_type_id', '=', self.picking_type_in.id), ('state', '=', 'done')]
|
|
receipt = self.env['stock.picking'].search(domain, limit=1, order="id DESC")
|
|
self.assertRecordValues(receipt.move_ids.sorted(lambda mv: mv.product_id), [
|
|
{'product_id': self.product1.id, 'quantity': 4},
|
|
{'product_id': self.product2.id, 'quantity': 2},
|
|
{'product_id': self.productserial1.id, 'quantity': 10},
|
|
])
|
|
tracked_move = receipt.move_ids.filtered(lambda mv: mv.product_id == self.productserial1)
|
|
self.assertEqual(
|
|
tracked_move.lot_ids.sorted(lambda sn: sn.name).mapped('name'),
|
|
['sn01', 'sn02', 'sn03', 'sn04', 'sn05', 'sn06', 'sn07', 'sn08', 'sn09', 'sn10']
|
|
)
|
|
|
|
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."""
|
|
self.env.user.write({'group_ids': [Command.link(self.env.ref('stock.group_production_lot').id)]})
|
|
# Creates SN product lots, for digipad testing
|
|
self.env['stock.lot'].create([{
|
|
'name': 'SN0001',
|
|
'product_id': self.productserial1.id,
|
|
}, {
|
|
'name': 'SN0002',
|
|
'product_id': self.productserial1.id,
|
|
}])
|
|
# 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.new() as move:
|
|
move.product_id = self.product1
|
|
move.product_uom_qty = 1
|
|
# Adds one line with SN product, for digipad testing
|
|
with receipt_form.move_ids.new() as move:
|
|
move.product_id = self.productserial1
|
|
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.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.
|
|
self.start_tour("/odoo/barcode", "test_scrap", login="admin", timeout=180)
|
|
|
|
def test_scrap_change_source_location(self):
|
|
self.env.user.group_ids = [
|
|
Command.link(self.env.ref('stock.group_stock_multi_locations').id),
|
|
Command.link(self.env.ref('stock.group_production_lot').id),
|
|
]
|
|
dest_location = self.picking_type_out.default_location_dest_id
|
|
self.product1.tracking = 'lot'
|
|
lot1 = self.env['stock.lot'].create({
|
|
'name': 'Lot1',
|
|
'product_id': self.product1.id,
|
|
})
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.shelf1, 25, lot_id=lot1)
|
|
picking = self.env['stock.picking'].create({
|
|
'name': self.product1.name,
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': dest_location.id,
|
|
'picking_type_id': self.picking_type_internal.id,
|
|
})
|
|
self.env['stock.move'].create({
|
|
'product_id': self.product1.id,
|
|
'lot_ids': lot1.id,
|
|
'product_uom_qty': 10.00,
|
|
'quantity': 10.00,
|
|
'picking_id': picking.id,
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': dest_location.id,
|
|
})
|
|
picking.action_confirm()
|
|
url = self._get_client_action_url(picking.id)
|
|
self.start_tour(url, 'test_scrap_change_source_location', login='admin')
|
|
scrap_location_id = self.env['stock.location'].search_read([('usage', '=', 'inventory'), ('company_id', '=', self.env.company.id)], fields=['id'], limit=1)[0].get('id')
|
|
self.assertRecordValues(
|
|
lot1.quant_ids,
|
|
[
|
|
{'location_id': self.shelf1.id, 'quantity': 0},
|
|
{'location_id': scrap_location_id, 'quantity': 15},
|
|
{'location_id': dest_location.id, 'quantity': 10},
|
|
]
|
|
)
|
|
|
|
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.env.user.write({'group_ids': [Command.link(self.env.ref('stock.group_tracking_lot').id)]})
|
|
self.picking_type_out.show_entire_packs = True
|
|
package1 = self.env['stock.package'].create({'name': 'package001'})
|
|
package2 = self.env['stock.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',
|
|
})
|
|
delivery_with_package_level.action_confirm()
|
|
delivery_with_package_level.action_add_entire_packs(package1.id)
|
|
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({
|
|
'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()
|
|
|
|
self.start_tour('/odoo/barcode', 'test_show_entire_package', login='admin', timeout=180)
|
|
|
|
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.env.user.write({'group_ids': [Command.link(self.env.ref('stock.group_tracking_lot').id)]})
|
|
|
|
pack01, pack02 = self.env['stock.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.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.picking_type_out.use_create_lots = False
|
|
self.env.user.write({'group_ids': [Command.link(self.env.ref('stock.group_production_lot').id)]})
|
|
lot01 = self.env['stock.lot'].create({
|
|
'name': "LOT01",
|
|
'product_id': self.productlot1.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_setting_barcode_allow_extra_product(self):
|
|
""" This test ensures we can't add non-reserved product when the picking type is set like
|
|
that, but we can still scan any product for a picking created on the fly.
|
|
"""
|
|
self.picking_type_out.barcode_allow_extra_product = False
|
|
self.env['stock.quant'].with_context(inventory_mode=True).create({
|
|
'product_id': self.product1.id,
|
|
'inventory_quantity': 1,
|
|
'location_id': self.stock_location.id,
|
|
}).action_apply_inventory()
|
|
# Create a delivery for product1 only.
|
|
delivery = self.env['stock.picking'].create({
|
|
'name': 'delivery_test',
|
|
'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_id': self.stock_location.id,
|
|
'location_dest_id': self.supplier_location.id,
|
|
'product_id': self.product1.id,
|
|
'product_uom_qty': 1,
|
|
'picking_id': delivery.id,
|
|
})
|
|
delivery.action_confirm()
|
|
delivery.action_assign()
|
|
self.start_tour("/odoo/barcode", 'test_setting_barcode_allow_extra_product', login='admin', timeout=180)
|
|
|
|
def test_setting_barcode_allow_extra_product_with_packages(self):
|
|
"""
|
|
Check that with allow extra products disabled, package scans containing extra
|
|
products are ignored, while scans of valid packages are still processed.
|
|
|
|
The test is performed with two deliveries: one in move entire package the other not.
|
|
"""
|
|
grp_pack = self.env.ref('stock.group_tracking_lot')
|
|
self.env.user.write({'group_ids': [Command.link(grp_pack.id)]})
|
|
# Disable "Allow Extra Products" setting
|
|
self.picking_type_out.barcode_allow_extra_product = False
|
|
picking_type_out_move_entire_package = self.picking_type_out.copy({'show_entire_packs': True})
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 10)
|
|
# PACK01-03: 10 x product1
|
|
# PACK04: 10 x product1 and 5 x product2
|
|
pack01, pack02, pack03, pack04 = self.env['stock.package'].create([
|
|
{'name': f"PACK0{i + 1}"} for i in range(4)
|
|
])
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 10, package_id=pack01)
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 10, package_id=pack02)
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 10, package_id=pack03)
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 10, package_id=pack04)
|
|
self.env['stock.quant']._update_available_quantity(self.product2, self.stock_location, 5, package_id=pack04)
|
|
|
|
deliveries = self.env['stock.picking'].create([
|
|
{
|
|
'name': 'SBAEPWP',
|
|
'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({
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.customer_location.id,
|
|
'product_id': self.product1.id,
|
|
'product_uom_qty': 5,
|
|
}),
|
|
],
|
|
},
|
|
{
|
|
'name': 'SBAEPWMEP',
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.customer_location.id,
|
|
'picking_type_id': picking_type_out_move_entire_package.id,
|
|
},
|
|
])
|
|
deliveries[1].action_add_entire_packs(pack02.id)
|
|
deliveries.action_confirm()
|
|
|
|
self.start_tour('/odoo/barcode', 'test_setting_barcode_allow_extra_product_with_packages', login='admin')
|
|
|
|
def test_scan_result_package_with_extra_product(self):
|
|
"""
|
|
Check that scanning a package at the location dest of a picking sets it
|
|
as result package without checks on its current product content.
|
|
"""
|
|
grp_pack = self.env.ref('stock.group_tracking_lot')
|
|
self.env.user.write({'group_ids': [Command.link(grp_pack.id)]})
|
|
self.warehouse.write({'delivery_steps': 'pick_ship'})
|
|
pick_type = self.warehouse.pick_type_id
|
|
pick_type.barcode_allow_extra_product = False
|
|
package = self.env['stock.package'].create({'name': 'POOK'})
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.warehouse.wh_output_stock_loc_id, 1, package_id=package)
|
|
self.env['stock.quant']._update_available_quantity(self.product2, self.stock_location, 1)
|
|
pick = self.env['stock.picking'].create({
|
|
'name': 'POOKPICK',
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.warehouse.wh_output_stock_loc_id.id,
|
|
'picking_type_id': pick_type.id,
|
|
'move_ids': [Command.create({
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.warehouse.wh_output_stock_loc_id.id,
|
|
'product_id': self.product2.id,
|
|
'product_uom_qty': 1,
|
|
})],
|
|
})
|
|
pick.action_confirm()
|
|
self.start_tour('/odoo/barcode', 'test_scan_result_package_with_extra_product', login='admin')
|
|
self.assertEqual(pick.move_line_ids.result_package_id, package)
|
|
|
|
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.env.user.write({
|
|
'group_ids': [
|
|
Command.link(self.env.ref('stock.group_production_lot').id),
|
|
Command.link(self.env.ref('stock.group_tracking_lot').id),
|
|
Command.link(self.env.ref('stock.group_stock_multi_locations').id),
|
|
Command.link(self.env.ref('stock.group_adv_location').id),
|
|
],
|
|
})
|
|
|
|
lot01 = self.env['stock.lot'].create({
|
|
'name': "LOT01",
|
|
'product_id': self.productlot1.id,
|
|
})
|
|
|
|
lot02 = self.env['stock.lot'].create({
|
|
'name': "LOT02",
|
|
'product_id': self.productlot1.id,
|
|
})
|
|
|
|
lot03 = self.env['stock.lot'].create({
|
|
'name': "LOT03",
|
|
'product_id': self.productlot1.id,
|
|
})
|
|
# all 3 products are available in WHSTOCK
|
|
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.new() as move:
|
|
move.product_id = self.productlot1
|
|
move.product_uom_qty = 5
|
|
with picking_form.move_ids.new() as move:
|
|
move.product_id = self.product1
|
|
move.product_uom_qty = 4
|
|
with picking_form.move_ids.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.env.user.write({'group_ids': [Command.link(self.env.ref('stock.group_stock_multi_locations').id)]})
|
|
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,
|
|
'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,
|
|
'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.
|
|
self.start_tour('/odoo/barcode', '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."""
|
|
product3 = self.env['product.product'].create({
|
|
'name': 'product3',
|
|
'is_storable': True,
|
|
'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,
|
|
'picking_id': delivery.id,
|
|
'product_id': product.id,
|
|
'product_uom_qty': 4,
|
|
} for product in [self.product1, self.product2, product3]])
|
|
delivery.action_confirm()
|
|
|
|
self.start_tour("/odoo/barcode", '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.assertRecordValues(delivery.move_ids, [
|
|
{'product_id': self.product1.id, 'quantity': 4, 'picked': True},
|
|
{'product_id': self.product2.id, 'quantity': 4, 'picked': True},
|
|
{'product_id': product3.id, 'quantity': 2, 'picked': False},
|
|
])
|
|
self.assertRecordValues(delivery.move_line_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_delivery_with_lot(self):
|
|
""" Ensures that the total quantity handled by the splitted moves does
|
|
not exceed the initial demand in case another lot than the initially
|
|
reserved one is scanned from the barcode."""
|
|
self.env.user.write({'group_ids': [Command.link(self.env.ref('stock.group_production_lot').id)]})
|
|
lots = self.env['stock.lot'].create([
|
|
{'name': 'LOT001', 'product_id': self.productlot1.id},
|
|
{'name': 'LOT002', 'product_id': self.productlot1.id},
|
|
])
|
|
for lot in lots:
|
|
self.env['stock.quant']._update_available_quantity(self.productlot1, self.stock_location, 5, lot_id=lot)
|
|
# Creates a delivery for 3 x productlot1 initially reserved with LOT001
|
|
delivery = self.env['stock.picking'].create({
|
|
'name': "delivery_split_move_on_exit",
|
|
'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({
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.customer_location.id,
|
|
'product_id': self.productlot1.id,
|
|
'product_uom_qty': 3,
|
|
})]
|
|
})
|
|
delivery.action_confirm()
|
|
self.assertRecordValues(delivery.move_ids, [
|
|
{'product_uom_qty': 3.0, 'quantity': 3.0, 'picked': False, 'lot_ids': lots[0].ids }
|
|
])
|
|
self.assertRecordValues(delivery.move_line_ids, [
|
|
{'quantity': 3.0, 'picked': False, 'lot_id': lots[0].id }
|
|
])
|
|
|
|
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_with_lot', login='admin')
|
|
# Checks receipt moves values.
|
|
self.assertRecordValues(delivery.move_line_ids.sorted('quantity'), [
|
|
{'quantity': 1, 'picked': True, 'lot_id': lots[0].id},
|
|
{'quantity': 2, 'picked': True, 'lot_id': lots[1].id},
|
|
])
|
|
|
|
def test_split_line_on_exit_for_receipt_with_grouped_lot(self):
|
|
""" Ensures that the total quantity handled by the splitted moves does
|
|
not exceed the initial demand in case the barcode lot lines are grouped.
|
|
"""
|
|
grouped_lot_group = self.env.ref('stock.group_production_lot')
|
|
self.env.user.write({'group_ids': [Command.link(grouped_lot_group.id)]})
|
|
# Creates a receipt for 3 x productlot1
|
|
receipt = self.env['stock.picking'].create({
|
|
'name': "SPLOEFRWGL",
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.supplier_location.id,
|
|
'picking_type_id': self.picking_type_in.id,
|
|
'move_ids': [Command.create({
|
|
'location_id': self.supplier_location.id,
|
|
'location_dest_id': self.stock_location.id,
|
|
'product_id': self.productlot1.id,
|
|
'product_uom_qty': 3,
|
|
})]
|
|
})
|
|
receipt.action_confirm()
|
|
self.assertRecordValues(receipt.move_ids, [
|
|
{'product_uom_qty': 3.0, 'quantity': 3.0, 'picked': False}
|
|
])
|
|
self.start_tour('/odoo/barcode', 'test_split_line_on_exit_for_receipt_with_grouped_lot', login='admin')
|
|
# Checks receipt moves values.
|
|
self.assertRecordValues(receipt.move_ids, [
|
|
{'product_uom_qty': 3.0, 'quantity': 3.0, 'picked': True},
|
|
])
|
|
|
|
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."""
|
|
# Enables package to check the split after a put in pack.
|
|
self.env.user.write({'group_ids': [Command.link(self.env.ref('stock.group_tracking_lot').id)]})
|
|
# Set packages' sequence to 1000 to find it easily during the tour.
|
|
self._reset_package_sequence(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,
|
|
'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,
|
|
'picking_id': receipt.id,
|
|
'product_id': self.product2.id,
|
|
'product_uom_qty': 4,
|
|
})
|
|
receipt.action_confirm()
|
|
|
|
self.start_tour("odoo/barcode/", 'test_split_line_on_exit_for_receipt', login='admin')
|
|
# Checks receipt moves values.
|
|
self.assertRecordValues(receipt.move_ids, [
|
|
{'product_id': self.product1.id, 'quantity': 4, 'picked': True},
|
|
{'product_id': self.product2.id, 'quantity': 4, 'picked': True},
|
|
])
|
|
self.assertRecordValues(receipt.move_ids.move_line_ids, [
|
|
{'product_id': self.product1.id, 'quantity': 2, 'picked': True},
|
|
{'product_id': self.product1.id, 'quantity': 1, 'picked': False},
|
|
{'product_id': self.product1.id, 'quantity': 1, 'picked': True},
|
|
{'product_id': self.product2.id, 'quantity': 1, 'picked': True},
|
|
{'product_id': self.product2.id, 'quantity': 3, 'picked': False},
|
|
])
|
|
|
|
def test_editing_done_picking(self):
|
|
""" Create and validate a picking then try editing it."""
|
|
picking_form = Form(self.env['stock.picking'])
|
|
picking_form.picking_type_id = self.picking_type_in
|
|
with picking_form.move_ids.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.
|
|
"""
|
|
# 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, 'is_storable': True}
|
|
)
|
|
productB = self.env["product.product"].create(
|
|
{"name": "Product B", "categ_id": product_categoryA.id, 'is_storable': True}
|
|
)
|
|
productC = self.env["product.product"].create(
|
|
{"name": "Product C", "categ_id": product_categoryB.id, 'is_storable': True}
|
|
)
|
|
# 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.
|
|
"""
|
|
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({
|
|
'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, {
|
|
'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 = "/odoo/action-stock_barcode.stock_barcode_action_main_menu"
|
|
self.start_tour(url, 'test_open_picking_dont_override_assigned_user', login='admin')
|
|
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.
|
|
"""
|
|
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',
|
|
})
|
|
|
|
cids = '-'.join(str(cid) for cid in self.env.user.company_ids.ids)
|
|
url = '/odoo/action-stock_barcode.stock_barcode_action_main_menu'
|
|
self.start_tour(url, 'test_multi_company_record_access_in_barcode', login='admin', timeout=180, cookies={"cids": cids})
|
|
|
|
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.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({
|
|
'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()
|
|
|
|
url = "/odoo/action-stock_barcode.stock_barcode_action_main_menu"
|
|
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.user.write({
|
|
'group_ids': [
|
|
Command.link(self.env.ref('stock.group_production_lot').id),
|
|
Command.link(self.env.ref('stock.group_tracking_lot').id),
|
|
],
|
|
})
|
|
warehouse = self.picking_type_out.warehouse_id
|
|
# Enable "Show reserved lots/SN"
|
|
warehouse.out_type_id.write({
|
|
'show_reserved_sns': True,
|
|
'restrict_scan_source_location': 'no',
|
|
})
|
|
# Create a product and its packaging.
|
|
product = self.env['product.product'].create({
|
|
'name': 'Lovely product',
|
|
'is_storable': True,
|
|
'tracking': 'lot',
|
|
'uom_id': self.uom_unit.id,
|
|
})
|
|
|
|
lot_1, lot_2 = self.env['stock.lot'].create([
|
|
{'name': 'LOT004', 'product_id': product.id},
|
|
{'name': 'LOT005', '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({
|
|
'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', timeout=180)
|
|
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_barcode_picking_return(self):
|
|
""" create a return from a done picking """
|
|
self.env['stock.quant']._update_available_quantity(self.product2, self.stock_location, 5)
|
|
|
|
delivery_form = Form(self.env['stock.picking'])
|
|
delivery_form.picking_type_id = self.picking_type_out
|
|
with delivery_form.move_ids.new() as move:
|
|
move.product_id = self.product2
|
|
move.product_uom_qty = 5
|
|
delivery = delivery_form.save()
|
|
delivery.action_confirm()
|
|
delivery.action_assign()
|
|
delivery.button_validate()
|
|
|
|
url = self._get_client_action_url(delivery.id)
|
|
self.start_tour(url, 'test_barcode_picking_return', login='admin', timeout=180)
|
|
|
|
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.env.user.write({'group_ids': [Command.link(self.env.ref('stock.group_stock_multi_locations').id)]})
|
|
self.picking_type_internal.active = True
|
|
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 10.0)
|
|
|
|
# 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_scan_product_when_in_form_view(self):
|
|
""" Ensure nothing happens when in `stock.move.line` for view."""
|
|
grp_multi_loc = self.env.ref('stock.group_stock_multi_locations')
|
|
self.env.user.write({'group_ids': [Command.link(grp_multi_loc.id)]})
|
|
self.picking_type_internal.active = True
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.shelf3, 5)
|
|
self.start_tour('/odoo/barcode', 'test_scan_product_when_in_form_view', login='admin')
|
|
|
|
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
|
|
"""
|
|
reference = self.env['stock.reference'].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['stock.rule'].run([
|
|
self.env['stock.rule'].Procurement(mto_product, 4.0, mto_product.uom_id, final_destination, mto_product.name, origin,
|
|
self.picking_type_out.company_id, {'warehouse_id': warehouse, 'reference_ids': reference})
|
|
])
|
|
pick = self.env['stock.picking'].search([('reference_ids', 'in', reference.ids)], limit=1)
|
|
pick.button_validate()
|
|
ship = self.env['stock.picking'].search([('reference_ids', 'in', reference.ids)], limit=2) - pick
|
|
mts_product = self.product2
|
|
self.env['stock.quant']._update_available_quantity(mts_product, ship.location_id, 5)
|
|
self.env['stock.move'].create({
|
|
'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.sorted('quantity'), [
|
|
{"product_id": mto_product.id, "quantity": 4.0, "picked": True},
|
|
{"product_id": mts_product.id, "quantity": 5.0, "picked": True},
|
|
])
|
|
self.assertRecordValues(ship.move_line_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_line_ids.filtered(lambda m: m.product_id == mts_product).sorted('quantity'), [
|
|
{"quantity": 1.0, "picked": True},
|
|
{"quantity": 4.0, "picked": False},
|
|
])
|
|
|
|
def test_split_uncomplete_manually_assigned_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.
|
|
"""
|
|
grp_multi_loc = self.env.ref('stock.group_stock_multi_locations')
|
|
self.env.user.write({'group_ids': [Command.link(grp_multi_loc.id)]})
|
|
# Avoid picking reservation at confirm
|
|
self.picking_type_out.write({
|
|
'reservation_method': 'manual',
|
|
'restrict_scan_source_location': 'no',
|
|
})
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 5)
|
|
delivery = self.env['stock.picking'].create({
|
|
'name': "SUMAMOE",
|
|
'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({
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.customer_location.id,
|
|
'product_id': self.product1.id,
|
|
'product_uom': self.product1.uom_id.id,
|
|
'product_uom_qty': 3,
|
|
})],
|
|
})
|
|
delivery.action_confirm()
|
|
delivery.action_assign()
|
|
url = self._get_client_action_url(delivery.id)
|
|
self.start_tour(url, 'test_split_uncomplete_manually_assigned_moves_on_exit', login='admin')
|
|
self.assertRecordValues(delivery.move_ids, [
|
|
{"quantity": 3.0, "picked": True},
|
|
])
|
|
self.assertRecordValues(delivery.move_line_ids.sorted('quantity'), [
|
|
{"quantity": 1.0, "picked": True},
|
|
{"quantity": 2.0, "picked": False},
|
|
])
|
|
|
|
def test_split_uncomplete_moves_on_exit_with_neutral_changes(self):
|
|
"""
|
|
Check that the post barcode process does change the state of the record if
|
|
neutral actions were performed such as adding and removing products to the
|
|
delivery.
|
|
"""
|
|
|
|
self.env.user.write({'group_ids': [Command.link(self.env.ref('stock.group_production_lot').id)]})
|
|
self.picking_type_out.show_reserved_sns = True
|
|
|
|
lots = self.env['stock.lot'].create([
|
|
{'name': 'LN001', 'product_id': self.productlot1.id},
|
|
{'name': 'LN002', 'product_id': self.productlot1.id},
|
|
])
|
|
for lot in lots:
|
|
self.env['stock.quant']._update_available_quantity(self.productlot1, self.stock_location, 2, lot_id=lot)
|
|
# Creates a delivery for 2 x productlot1 initially reserved with SN001 and SN002
|
|
delivery = self.env['stock.picking'].create({
|
|
'name': "SUMOEWNC",
|
|
'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({
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.customer_location.id,
|
|
'product_id': self.productlot1.id,
|
|
'product_uom_qty': 4,
|
|
})]
|
|
})
|
|
delivery.action_confirm()
|
|
self.assertRecordValues(delivery.move_ids, [
|
|
{'product_uom_qty': 4.0, 'quantity': 4.0, 'picked': False, 'lot_ids': lots.ids},
|
|
])
|
|
self.assertRecordValues(delivery.move_line_ids, [
|
|
{'quantity': 2.0, 'picked': False, 'lot_id': lots[0].id},
|
|
{'quantity': 2.0, 'picked': False, 'lot_id': lots[1].id},
|
|
])
|
|
|
|
action = self.env.ref('stock_barcode.stock_barcode_action_main_menu')
|
|
url = f"/web#action={action.id}"
|
|
self.start_tour(url, 'test_split_uncomplete_moves_on_exit_with_neutral_changes', login='admin')
|
|
# Checks the reservation state is equivalent
|
|
self.assertRecordValues(delivery.move_ids, [
|
|
{'product_uom_qty': 4.0, 'quantity': 4.0, 'picked': False, 'lot_ids': lots.ids},
|
|
])
|
|
self.assertRecordValues(delivery.move_line_ids, [
|
|
{'quantity': 2.0, 'picked': False, 'lot_id': lots[1].id},
|
|
{'quantity': 2.0, 'picked': False, 'lot_id': lots[0].id},
|
|
])
|
|
|
|
def test_barcode_create_serials_in_batch_with_single_scan(self):
|
|
""" Check that it is possible to generate 1000 serial numbers with a signle scan."""
|
|
self.env.user.write({
|
|
'group_ids': [
|
|
Command.link(self.env.ref('stock.group_production_lot').id),
|
|
Command.link(self.env.ref('stock.group_tracking_lot').id),
|
|
],
|
|
})
|
|
# Enable "Show reserved lots/SN"
|
|
self.picking_type_out.write({
|
|
'show_reserved_sns': True,
|
|
'restrict_scan_source_location': 'no',
|
|
'use_create_lots': True,
|
|
})
|
|
|
|
# Patch controller method to be able to count amount of calls.
|
|
self1 = self
|
|
get_specific_barcode_data_orig = StockBarcodeController.get_specific_barcode_data
|
|
@http.route('/stock_barcode/get_specific_barcode_data', type='jsonrpc', auth='user')
|
|
def mocked_data_batch_method(self, **kwargs):
|
|
if self1.call_count == 0:
|
|
self1.assertTrue('barcodes_by_model' in kwargs)
|
|
self1.assertFalse('barcodes' in kwargs)
|
|
elif self1.call_count == 1:
|
|
self1.assertFalse('barcodes_by_model' in kwargs)
|
|
self1.assertTrue('barcodes' in kwargs)
|
|
self1.call_count += 1
|
|
return get_specific_barcode_data_orig(self, **kwargs)
|
|
|
|
self.env['stock.quant']._update_available_quantity(self.productlot1, self.stock_location, 1500)
|
|
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({
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.customer_location.id,
|
|
'product_id': self.productlot1.id,
|
|
'product_uom': self.productlot1.uom_id.id,
|
|
'product_uom_qty': 1000,
|
|
})],
|
|
})
|
|
delivery.action_confirm()
|
|
self.assertFalse(delivery.move_line_ids.lot_id)
|
|
url = self._get_client_action_url(delivery.id)
|
|
with patch.object(
|
|
StockBarcodeController,
|
|
'get_specific_barcode_data',
|
|
mocked_data_batch_method
|
|
):
|
|
self.start_tour(url, 'test_barcode_create_serials_in_batch_with_single_scan', login='admin')
|
|
self.assertEqual(self.call_count, 2)
|
|
|
|
def test_quant_selection_delivery_picking(self):
|
|
""" Ensures the stock move's location, package and owner can be updated by clicking on a
|
|
quant card in the Barcode move line form view accordingly to the selected quant values.
|
|
"""
|
|
self.env.user.write({
|
|
'group_ids': [
|
|
Command.link(self.env.ref('stock.group_tracking_lot').id),
|
|
Command.link(self.env.ref('stock.group_stock_multi_locations').id),
|
|
],
|
|
})
|
|
package = self.env['stock.package'].create({'name': 'package001'})
|
|
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 10, package_id=package)
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 2)
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.shelf1, 10)
|
|
|
|
delivery_picking = 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': [Command.create({
|
|
'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_picking.action_confirm()
|
|
delivery_picking.action_assign()
|
|
|
|
url = self._get_client_action_url(delivery_picking.id)
|
|
self.start_tour(url, 'test_quant_selection_delivery_picking', login='admin', timeout=180)
|
|
self.assertRecordValues(delivery_picking.move_ids.move_line_ids, [{
|
|
'product_id': self.product1.id,
|
|
'qty_done': 2,
|
|
'location_id': self.stock_location.id,
|
|
'lot_id': False,
|
|
'package_id': package.id,
|
|
'state': 'done'
|
|
}])
|
|
|
|
def test_confirmation_location_delivery_picking(self):
|
|
""" When a location is selected in the move line form view of the Barcode app,
|
|
if the product is not available in that location, a confirmation dialog appears
|
|
to allow the user to verify when applying the change.
|
|
This test ensures this confirmation dialog is shown when needed.
|
|
"""
|
|
self.env.user.write({
|
|
'group_ids': [
|
|
Command.link(self.env.ref('stock.group_tracking_lot').id),
|
|
Command.link(self.env.ref('stock.group_stock_multi_locations').id),
|
|
],
|
|
})
|
|
package = self.env['stock.package'].create({'name': 'package001'})
|
|
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 10, package_id=package)
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.shelf1, 5)
|
|
self.product2.is_storable = False
|
|
|
|
delivery_picking = 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': [Command.create({
|
|
'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
|
|
})],
|
|
})
|
|
delivery_picking.action_confirm()
|
|
delivery_picking.action_assign()
|
|
|
|
url = self._get_client_action_url(delivery_picking.id)
|
|
self.start_tour(url, 'test_confirmation_location_delivery_picking', login='admin', timeout=180)
|
|
self.assertRecordValues(delivery_picking.move_ids.move_line_ids.sorted(lambda sml: sml.product_id.id), [
|
|
{
|
|
'product_id': self.product1.id,
|
|
'qty_done': 1,
|
|
'location_id': self.shelf2.id,
|
|
'lot_id': False,
|
|
'package_id': package.id,
|
|
'state': 'done'
|
|
},
|
|
{
|
|
'product_id': self.product2.id,
|
|
'qty_done': 1,
|
|
'location_id': self.stock_location.id,
|
|
'lot_id': False,
|
|
'package_id': False,
|
|
'state': 'done'
|
|
},
|
|
])
|
|
|
|
def test_barcode_lazy_cache_scan_two_lots(self):
|
|
""" Checks that you can scan 2 lots without the OWL error 'quantsByLocation is not iterable' """
|
|
self.env.user.write({
|
|
"group_ids": [
|
|
Command.link(self.env.ref('stock.group_production_lot').id),
|
|
Command.link(self.env.ref("stock.group_tracking_owner").id),
|
|
]
|
|
})
|
|
self.picking_type_in.write({'use_existing_lots': True})
|
|
|
|
sn1, _ = self.env['stock.lot'].create([
|
|
{'name': 'SN-001', 'product_id': self.productlot1.id},
|
|
{'name': 'SN-002', 'product_id': self.productlot1.id},
|
|
])
|
|
self.env['stock.quant']._update_available_quantity(self.productlot1, self.stock_location, 1, lot_id=sn1)
|
|
|
|
receipt = self.env['stock.picking'].create({
|
|
'name': "Lovely Receipt",
|
|
'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({
|
|
'location_id': self.supplier_location.id,
|
|
'location_dest_id': self.stock_location.id,
|
|
'product_id': self.productlot1.id,
|
|
'product_uom': self.productlot1.uom_id.id,
|
|
'product_uom_qty': 10,
|
|
})],
|
|
})
|
|
receipt.action_confirm()
|
|
url = self._get_client_action_url(receipt.id)
|
|
self.start_tour(url, 'test_barcode_lazy_cache_scan_two_lots', login='admin')
|
|
|
|
def test_fetch_archived_records_in_lazy_barcode_cache(self):
|
|
"""
|
|
Check that a picking related to archived records can be processed in barcode
|
|
in the same way as it can be in backend.
|
|
"""
|
|
warehouse = self.shelf2.warehouse_id
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.shelf1, 10)
|
|
floor_location = self.env['stock.location'].create({
|
|
'name': 'floor1',
|
|
'usage': 'internal',
|
|
'location_id': self.shelf2.id,
|
|
})
|
|
transfer = self.env['stock.picking'].create({
|
|
'name': 'Lovely Transfer',
|
|
'location_id': self.shelf1.id,
|
|
'location_dest_id': self.shelf2.id,
|
|
'picking_type_id': warehouse.int_type_id.id,
|
|
'move_ids': [Command.create({
|
|
'location_id': self.shelf1.id,
|
|
'location_dest_id': floor_location.id,
|
|
'product_id': self.product1.id,
|
|
'product_uom_qty': 3,
|
|
})],
|
|
})
|
|
transfer.action_confirm()
|
|
self.shelf2.action_archive()
|
|
self.assertFalse(self.shelf2.active)
|
|
url = self._get_client_action_url(transfer.id)
|
|
self.start_tour(url, 'test_fetch_archived_records_in_lazy_barcode_cache', login='admin')
|
|
self.assertEqual(transfer.state, 'done')
|
|
|
|
def test_validate_uncomplete_return(self):
|
|
""" Ensures we can validate a return created in the Barcode app without any issue.
|
|
"""
|
|
self.env['stock.picking'].create({
|
|
'name': 'TEST/IN/0001',
|
|
'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({
|
|
'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': 2,
|
|
})]
|
|
}).action_confirm()
|
|
self.start_tour('/odoo/barcode', 'test_validate_uncomplete_return', login='admin')
|
|
|
|
def test_scan_package_with_different_uom(self):
|
|
""" Ensure that when the user scans a package, if the line uses another
|
|
UoM, the quantity is correctly converted."""
|
|
self.env.user.write({'group_ids': [
|
|
Command.link(self.ref('stock.group_tracking_lot')),
|
|
Command.link(self.ref('uom.group_uom')),
|
|
]})
|
|
# Create a delivery for product1 (demand of 10,000 g, stock packaged as 10 kg)
|
|
# and product2 (demand of 8 kg, stock packaged as 10,000 g).
|
|
uom_kg = self.env.ref('uom.product_uom_kgm')
|
|
uom_g = self.env.ref('uom.product_uom_gram')
|
|
self.product1.uom_id = uom_kg
|
|
self.product2.uom_id = uom_g
|
|
package1, package2 = self.env['stock.package'].create([
|
|
{'name': 'package001'},
|
|
{'name': 'package002'},
|
|
])
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 10, package_id=package1)
|
|
self.env['stock.quant']._update_available_quantity(self.product2, self.stock_location, 10000, package_id=package2)
|
|
picking = self.env['stock.picking'].create({
|
|
'name': 'test picking',
|
|
'picking_type_id': self.picking_type_out.id,
|
|
'location_id': self.picking_type_out.default_location_src_id.id,
|
|
'location_dest_id': self.picking_type_out.default_location_dest_id.id,
|
|
'move_ids': [
|
|
Command.create({
|
|
'product_id': product.id,
|
|
'product_uom': uom.id,
|
|
'product_uom_qty': qty,
|
|
'location_id': self.picking_type_out.default_location_src_id.id,
|
|
'location_dest_id': self.picking_type_out.default_location_dest_id.id,
|
|
}) for (product, qty, uom) in [
|
|
(self.product1, 10000, uom_g),
|
|
(self.product2, 8, uom_kg),
|
|
]
|
|
],
|
|
})
|
|
picking.action_confirm()
|
|
picking.action_assign()
|
|
url = self._get_client_action_url(picking.id)
|
|
self.start_tour(url, 'test_scan_package_with_different_uom', login='admin')
|
|
|
|
def test_scan_packaging_on_picking_with_mixed_uom(self):
|
|
"""
|
|
Create receipts for a product with Unit uom and that can be packed in pack of 6.
|
|
|
|
Process these pickings in barcode by scanning both Units and pack of 6.
|
|
|
|
receipt 1: - 2 pack of 6
|
|
|
|
receipt 2: - 12 units expected to be packed in pack of 6
|
|
|
|
receipt 3: - 10 units
|
|
- 1 pack of 6
|
|
|
|
receipt 4: - 8 units
|
|
- 12 units expected to be packed in pack of 6
|
|
|
|
receipt 5: - 1 pack of 6 with lot12345
|
|
- 2 Dozens with lot54321
|
|
"""
|
|
self.env.user.write({'group_ids': [
|
|
Command.link(self.ref('uom.group_uom')),
|
|
Command.link(self.env.ref('stock.group_production_lot').id)
|
|
]})
|
|
self.uom_dozen.action_unarchive()
|
|
unit, pack_of_6 = self.ref('uom.product_uom_unit'), self.ref('uom.product_uom_pack_6')
|
|
lovely_product = self.env['product.product'].create({
|
|
'name': 'Lovely product',
|
|
'is_storable': True,
|
|
'barcode': 'love',
|
|
'uom_id': self.ref('uom.product_uom_unit'),
|
|
'uom_ids': [Command.link(pack_of_6), Command.link(self.uom_dozen.id)],
|
|
'product_uom_ids': [
|
|
Command.create({
|
|
'uom_id': pack_of_6,
|
|
'barcode': '6love',
|
|
}),
|
|
Command.create({
|
|
'uom_id': self.uom_dozen.id,
|
|
'barcode': '12love',
|
|
})]
|
|
})
|
|
self.productlot1.product_uom_ids = [
|
|
Command.create({
|
|
'uom_id': pack_of_6,
|
|
'barcode': '6lotprod',
|
|
}),
|
|
Command.create({
|
|
'uom_id': self.uom_dozen.id,
|
|
'barcode': '12lotprod',
|
|
}),
|
|
]
|
|
receipt_1, receipt_2, receipt_3, receipt_4 = self.env['stock.picking'].create([
|
|
{
|
|
'name': "SPOPWMU1",
|
|
'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({
|
|
'location_id': self.supplier_location.id,
|
|
'location_dest_id': self.stock_location.id,
|
|
'product_id': lovely_product.id,
|
|
'product_uom_qty': 2,
|
|
'product_uom': pack_of_6,
|
|
}),
|
|
],
|
|
},
|
|
{
|
|
'name': "SPOPWMU2",
|
|
'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({
|
|
'location_id': self.supplier_location.id,
|
|
'location_dest_id': self.stock_location.id,
|
|
'product_id': lovely_product.id,
|
|
'product_uom_qty': 12,
|
|
})],
|
|
},
|
|
{
|
|
'name': "SPOPWMU3",
|
|
'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({
|
|
'location_id': self.supplier_location.id,
|
|
'location_dest_id': self.stock_location.id,
|
|
'product_id': lovely_product.id,
|
|
'product_uom_qty': 10,
|
|
}),
|
|
Command.create({
|
|
'location_id': self.supplier_location.id,
|
|
'location_dest_id': self.stock_location.id,
|
|
'product_id': lovely_product.id,
|
|
'product_uom_qty': 1,
|
|
'product_uom': pack_of_6,
|
|
}),
|
|
],
|
|
},
|
|
{
|
|
'name': "SPOPWMU4",
|
|
'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({
|
|
'location_id': self.supplier_location.id,
|
|
'location_dest_id': self.stock_location.id,
|
|
'product_id': lovely_product.id,
|
|
'product_uom_qty': 8,
|
|
}),
|
|
Command.create({
|
|
'location_id': self.supplier_location.id,
|
|
'location_dest_id': self.stock_location.id,
|
|
'product_id': lovely_product.id,
|
|
'product_uom_qty': 12,
|
|
'price_unit': 100000000000, # Avoid merging
|
|
}),
|
|
],
|
|
},
|
|
])
|
|
receipt_2.move_ids.packaging_uom_id = pack_of_6
|
|
receipt_4.move_ids[-1].packaging_uom_id = pack_of_6
|
|
(receipt_1 | receipt_2 | receipt_3 | receipt_4).action_confirm()
|
|
self.assertRecordValues(receipt_1.move_ids, [
|
|
{'product_uom_qty': 2.0, 'quantity': 2.0, 'picked': False, 'product_uom': pack_of_6}
|
|
])
|
|
self.assertRecordValues(receipt_2.move_ids, [
|
|
{'product_uom_qty': 12.0, 'quantity': 12.0, 'picked': False, 'packaging_uom_qty': 2, 'packaging_uom_id': pack_of_6}
|
|
])
|
|
self.assertRecordValues(receipt_3.move_ids, [
|
|
{'product_uom_qty': 10.0, 'quantity': 10.0, 'product_uom': unit, 'picked': False},
|
|
{'product_uom_qty': 1.0, 'quantity': 1.0, 'product_uom': pack_of_6, 'picked': False},
|
|
])
|
|
self.assertRecordValues(receipt_4.move_ids, [
|
|
{'product_uom_qty': 8.0, 'quantity': 8.0, 'product_uom': unit, 'packaging_uom_id': unit},
|
|
{'product_uom_qty': 12.0, 'quantity': 12.0, 'product_uom': unit, 'packaging_uom_id': pack_of_6},
|
|
])
|
|
|
|
action = self.env.ref('stock_barcode.stock_barcode_action_main_menu')
|
|
url = f"/web#action={action.id}"
|
|
self.start_tour(url, 'test_scan_packaging_on_picking_with_mixed_uom', login='admin')
|
|
self.assertRecordValues(receipt_1.move_ids, [
|
|
{'product_uom_qty': 2.0, 'quantity': 2, 'product_uom': pack_of_6, 'state': 'done'},
|
|
])
|
|
self.assertRecordValues(receipt_2.move_ids, [
|
|
{'product_uom_qty': 12.0, 'quantity': 32.0, 'product_uom': unit, 'packaging_uom_id': pack_of_6, 'state': 'done'}
|
|
])
|
|
self.assertRecordValues(receipt_3.move_ids, [
|
|
{'product_uom_qty': 10.0, 'quantity': 10.0, 'product_uom': unit, 'state': 'done'},
|
|
{'product_uom_qty': 1.0, 'quantity': 3.5, 'product_uom': pack_of_6, 'state': 'done'},
|
|
])
|
|
self.assertRecordValues(receipt_4.move_ids, [
|
|
{'product_uom_qty': 8.0, 'quantity': 8.0, 'product_uom': unit, 'packaging_uom_id': unit, 'picked': True},
|
|
{'product_uom_qty': 12.0, 'quantity': 12.0, 'product_uom': unit, 'packaging_uom_id': pack_of_6, 'picked': True},
|
|
])
|
|
receipt_5 = self.env['stock.move.line'].search([
|
|
('product_id', '=', self.productlot1.id),
|
|
('product_uom_id', '=', self.productlot1.product_uom_ids[1].uom_id.id),
|
|
]).picking_id
|
|
self.assertRecordValues(receipt_5.move_ids, [
|
|
{'product_uom_qty': 0.0, 'quantity': 5.0, 'product_uom': pack_of_6, 'state': 'done'},
|
|
])
|
|
self.assertRecordValues(receipt_5.move_line_ids, [
|
|
{'quantity': 1.0, 'product_uom_id': pack_of_6, 'state': 'done'},
|
|
{'quantity': 2.0, 'product_uom_id': self.uom_dozen.id, 'state': 'done'},
|
|
])
|
|
|
|
def test_remove_sublines_and_scan_serial_again(self):
|
|
grp_lot = self.env.ref('stock.group_production_lot')
|
|
self.env.user.write({'group_ids': [Command.link(grp_lot.id)]})
|
|
picking = self.env['stock.picking'].create({
|
|
'picking_type_id': self.picking_type_in.id,
|
|
'move_ids': [
|
|
Command.create({
|
|
'product_id': self.productserial1.id,
|
|
'product_uom_qty': 3,
|
|
})
|
|
],
|
|
})
|
|
picking.action_assign()
|
|
url = self._get_client_action_url(picking.id)
|
|
self.start_tour(url, 'test_remove_sublines_and_scan_serial_again', login='admin')
|
|
|
|
def test_split_line_preserve_package(self):
|
|
"""
|
|
This test ensures that move lines, when assigned a new destination
|
|
while scanning, properly preserve package source info after scanning
|
|
a package destination when the move line moves partial quantity
|
|
"""
|
|
grp_pack = self.env.ref('stock.group_tracking_lot')
|
|
grp_multi_loc = self.env.ref('stock.group_stock_multi_locations')
|
|
self.env.user.write({'group_ids': [Command.link(grp_pack.id), Command.link(grp_multi_loc.id)]})
|
|
self.picking_type_out.restrict_scan_source_location = 'no'
|
|
|
|
# Create two empty packs
|
|
pack1, pack2 = self.env['stock.package'].create([{
|
|
'name': name,
|
|
} for name in ['THEPACK1', 'THEPACK2']])
|
|
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, quantity=100, package_id=pack1)
|
|
delivery_form = Form(self.env['stock.picking'])
|
|
delivery_form.picking_type_id = self.picking_type_out
|
|
with delivery_form.move_ids.new() as move:
|
|
move.product_id = self.product1
|
|
move.product_uom_qty = 50
|
|
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_preserve_package', login='admin')
|
|
self.assertEqual(delivery_with_move.move_line_ids[0].package_id, pack1)
|
|
self.assertEqual(delivery_with_move.move_line_ids[1].result_package_id, pack2)
|
|
self.assertEqual(delivery_with_move.move_line_ids[1].package_id, pack1)
|
|
|
|
# === 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.env.user.write({'group_ids': [
|
|
Command.link(self.env.ref('uom.group_uom').id),
|
|
Command.link(self.env.ref('stock.group_production_lot').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,
|
|
'is_storable': True,
|
|
'barcode': barcode,
|
|
'tracking': 'lot',
|
|
} for (name, barcode) in [('Product A', '22222220'), ('Product B', '44444440')]])
|
|
self.productlot1.barcode = '1234567895'
|
|
# Creates 2 lot numbers (same name but different product.)
|
|
# Create a lot number that starts with 10 and has a special character
|
|
lot_b, lot_a = self.env['stock.lot'].create([
|
|
{'name': '12345', 'product_id': product.id} for product in [product_b, product_a]
|
|
])
|
|
special_lot = self.env['stock.lot'].create([
|
|
{'name': '10LOT-AAA', 'product_id': self.productlot1.id}
|
|
])
|
|
# Create a product packaging
|
|
packaging = self.env['uom.uom'].create({
|
|
'name': "Packaging - Product A x1",
|
|
'relative_factor': 1,
|
|
})
|
|
product_a.uom_ids = packaging
|
|
self.env['product.uom'].create({
|
|
'product_id': product_a.id,
|
|
'uom_id': packaging.id,
|
|
'barcode': '10000000240489',
|
|
})
|
|
# 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.productlot1, special_lot]]:
|
|
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.
|
|
self.start_tour('/odoo/barcode', '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.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}',
|
|
'is_storable': True,
|
|
'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({
|
|
'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.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',
|
|
'is_storable': True,
|
|
'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.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.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',
|
|
'is_storable': True,
|
|
'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',
|
|
'is_storable': True,
|
|
'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.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.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.new() as move:
|
|
move.product_id = product_gtin_8
|
|
move.product_uom_qty = 1
|
|
with picking_form.move_ids.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.env.user.write({
|
|
'group_ids': [
|
|
Command.link(self.env.ref('stock.group_production_lot').id),
|
|
Command.link(self.env.ref('stock.group_tracking_lot').id),
|
|
],
|
|
})
|
|
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",
|
|
'is_storable': True,
|
|
'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",
|
|
'is_storable': True,
|
|
'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.package'].create({'name': '21-Chouette-MegaPack'})
|
|
self.start_tour("/odoo/barcode", '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.env.user.write({'group_ids': [Command.link(self.env.ref('stock.group_production_lot').id)]})
|
|
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.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_multiple_extra_items(self):
|
|
"""
|
|
This test ensures that when multiple extra items are added during the processing
|
|
of a picking, all the selected items are correctly added to the picking.
|
|
"""
|
|
self.product1.barcode = '12345678900005'
|
|
self.product2.barcode = '12345678900012'
|
|
self.productserial1.barcode = '12345678900029'
|
|
self.productlot1.barcode = '12345678900036'
|
|
|
|
group_lot = self.env.ref('stock.group_production_lot')
|
|
group_uom = self.env.ref('uom.group_uom')
|
|
self.env.user.write({'group_ids': [Command.link(group_lot.id), Command.link(group_uom.id)]})
|
|
self.env.company.nomenclature_id = self.env.ref('barcodes_gs1_nomenclature.default_gs1_nomenclature')
|
|
|
|
receipt_with_move = self.env['stock.picking'].create({
|
|
'name': "In Picking for multiple extra items",
|
|
'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({
|
|
'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_with_move.action_confirm()
|
|
|
|
url = self._get_client_action_url(receipt_with_move.id)
|
|
self.start_tour(url, 'test_gs1_receipt_multiple_extra_items', login='admin')
|
|
|
|
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.
|
|
"""
|
|
# Enables the UoM and the GS1 nomenclature.
|
|
self.env.user.write({'group_ids': [Command.link(self.env.ref('uom.group_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_g = self.env.ref('uom.product_uom_gram')
|
|
uom_kg = self.env.ref('uom.product_uom_kgm')
|
|
uom_unit = self.env['uom.uom'].create({
|
|
'name': 'Units',
|
|
'relative_factor': 1.0,
|
|
'relative_uom_id': uom_kg.id,
|
|
})
|
|
self.env['barcode.rule'].search([('sequence', '=', 305)]).associated_uom_id = uom_unit
|
|
product_by_units = self.env['product.product'].create({
|
|
'name': 'Product by Units',
|
|
'is_storable': True,
|
|
'barcode': '15264329',
|
|
'uom_id': uom_unit.id,
|
|
})
|
|
product_by_g = self.env['product.product'].create({
|
|
'name': 'Product by g',
|
|
'is_storable': True,
|
|
'barcode': '15264893',
|
|
'uom_id': uom_g.id,
|
|
})
|
|
product_by_kg = self.env['product.product'].create({
|
|
'name': 'Product by kg',
|
|
'is_storable': True,
|
|
'barcode': '15264879',
|
|
'uom_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.assertRecordValues(receipt.move_ids, [
|
|
{'picked': True, 'product_id': product_by_units.id, 'product_uom': uom_kg.id, 'quantity': 14},
|
|
{'picked': True, 'product_id': product_by_kg.id, 'product_uom': uom_kg.id, 'quantity': 11},
|
|
{'picked': True, 'product_id': product_by_g.id, 'product_uom': uom_kg.id, 'quantity': 1.25},
|
|
])
|
|
|
|
def test_gs1_receipt_scan_not_gs1_multi_barcode(self):
|
|
""" This test ensures the user can scan a barcode containing multiple
|
|
non-GS1 barcode when GS1 nomenclature is active."""
|
|
self.env.user.write({'group_ids': [Command.link(self.env.ref('stock.group_production_lot').id)]})
|
|
self.env.company.nomenclature_id = self.env.ref('barcodes_gs1_nomenclature.default_gs1_nomenclature')
|
|
self.start_tour('/odoo/barcode', 'test_gs1_receipt_scan_not_gs1_multi_barcode', login='admin')
|
|
|
|
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.env.user.write({'group_ids': [Command.link(self.env.ref('stock.group_tracking_lot').id)]})
|
|
self.env.company.nomenclature_id = self.env.ref('barcodes_gs1_nomenclature.default_gs1_nomenclature')
|
|
|
|
# Set package's sequence to 123 to generate always the same package's name in the tour.
|
|
self._reset_package_sequence(123)
|
|
|
|
# Creates two products and two package's types.
|
|
product1, product2 = self.env['product.product'].create([{
|
|
'name': 'PRO_GTIN_8',
|
|
'is_storable': True,
|
|
'barcode': '82655853', # GTIN-8
|
|
'uom_id': self.env.ref('uom.product_uom_unit').id
|
|
}, {
|
|
'name': 'PRO_GTIN_12',
|
|
'is_storable': True,
|
|
'barcode': '584687955629', # GTIN-12
|
|
'uom_id': self.env.ref('uom.product_uom_unit').id,
|
|
}])
|
|
wooden_chest_package_type, iron_chest_package_type = self.env['stock.package.type'].create([{
|
|
'name': 'Wooden Chest',
|
|
'barcode': 'WOODC',
|
|
}, {
|
|
'name': 'Iron Chest',
|
|
'barcode': 'IRONC',
|
|
}])
|
|
|
|
self.start_tour("/odoo/barcode", 'test_gs1_package_receipt', login='admin')
|
|
# Checks the package is in the stock location with the products.
|
|
package = self.env['stock.package'].search([('name', '=', '546879213579461324')])
|
|
package2 = self.env['stock.package'].search([('name', '=', '130406658041178543')])
|
|
package3 = self.env['stock.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("/odoo/barcode", 'test_gs1_package_delivery', login='admin')
|
|
# 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.env.user.write({'group_ids': [Command.link(self.env.ref('uom.group_uom').id)]})
|
|
self.env.company.nomenclature_id = self.env.ref('barcodes_gs1_nomenclature.default_gs1_nomenclature')
|
|
|
|
product = self.env['product.product'].create({
|
|
'name': 'Bottle',
|
|
'is_storable': True,
|
|
'barcode': '1113',
|
|
'uom_ids': [Command.link(self.env.ref('uom.product_uom_dozen').id)],
|
|
'product_uom_ids': [Command.create({
|
|
'uom_id': self.env.ref('uom.product_uom_dozen').id,
|
|
'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, 5)
|
|
self.assertEqual(move.picked, True)
|
|
|
|
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.env.user.write({
|
|
'group_ids': [
|
|
Command.link(self.env.ref('stock.group_production_lot').id),
|
|
Command.link(self.env.ref('uom.group_uom').id),
|
|
],
|
|
})
|
|
self.env.company.nomenclature_id = self.env.ref('barcodes_gs1_nomenclature.default_gs1_nomenclature')
|
|
|
|
self.productlot1.uom_ids = self.env.ref('uom.product_uom_dozen')
|
|
self.env['product.uom'].create({
|
|
'barcode': '12653256',
|
|
'product_id': self.productlot1.id,
|
|
'uom_id': self.env.ref('uom.product_uom_dozen').id,
|
|
})
|
|
|
|
self.start_tour("/odoo/barcode", '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': 1, 'lot_id': lot.id, 'location_id': self.supplier_location.id, 'location_dest_id': self.stock_location.id},
|
|
{'quantity': 1, 'lot_id': lot.id, 'location_id': self.stock_location.id, 'location_dest_id': self.customer_location.id},
|
|
])
|
|
|
|
def test_gs1_multi_company_setup(self):
|
|
"""
|
|
Check that the nomenclature used on the barcode main menu is the nomenclature
|
|
of the contextually selected company rather than the user company.
|
|
"""
|
|
self.env.user.group_ids += self.env.ref('stock.group_stock_multi_locations')
|
|
company = self.env['res.company'].create({
|
|
'name': 'Lovely Company',
|
|
'nomenclature_id': self.ref('barcodes_gs1_nomenclature.default_gs1_nomenclature'),
|
|
})
|
|
warehouse = self.env['stock.warehouse'].search([('company_id', '=', company.id)])
|
|
warehouse.reception_steps = 'two_steps'
|
|
warehouse.wh_input_stock_loc_id.barcode = '3033710074365'
|
|
self.product1.barcode = '36939282410106'
|
|
self.env['stock.quant'].with_company(company.id)._update_available_quantity(self.product1, warehouse.lot_stock_id, 3.0)
|
|
self.start_tour('/odoo', 'test_gs1_multi_company_setup', login='admin')
|
|
|
|
def test_serial_product_packaging(self):
|
|
""" This test ensures that correct packaging lines generated
|
|
for serial product in operations.
|
|
"""
|
|
self.env.user.write({
|
|
'group_ids': [
|
|
Command.link(self.env.ref('stock.group_production_lot').id),
|
|
Command.link(self.env.ref('uom.group_uom').id),
|
|
],
|
|
})
|
|
pack_4 = self.env['uom.uom'].create({
|
|
'name': 'Pack of 4',
|
|
'relative_factor': 4,
|
|
'relative_uom_id': self.env.ref('uom.product_uom_unit').id,
|
|
})
|
|
self.productserial1.uom_ids = pack_4
|
|
self.env['product.uom'].create({
|
|
'uom_id': pack_4.id,
|
|
'product_id': self.productserial1.id,
|
|
'barcode': 'PCK4',
|
|
})
|
|
|
|
self.start_tour('/odoo/barcode', 'test_serial_product_packaging', login='admin', timeout=180)
|
|
|
|
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.env.user.write({'group_ids': [Command.link(self.env.ref('stock.group_tracking_lot').id)]})
|
|
|
|
self.env['stock.quant']._update_available_quantity(self.product2, self.stock_location, 5)
|
|
# Create two empty packs
|
|
pack1 = self.env['stock.package'].create({
|
|
'name': 'THEPACK1',
|
|
})
|
|
pack2 = self.env['stock.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.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_package_with_decimal(self):
|
|
""" Ensure there is no rounding issue on javascript when scanning a
|
|
package that has more quantity than the required quantity on line."""
|
|
grp_pack_id = self.ref('stock.group_tracking_lot')
|
|
self.env.user.write({'group_ids': [Command.link(grp_pack_id)]})
|
|
self.product1.uom_id = self.env.ref('uom.product_uom_kgm')
|
|
self.picking_type_out.restrict_scan_source_location = 'no'
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 275.84, package_id=self.package)
|
|
delivery_form = Form(self.env['stock.picking'])
|
|
delivery_form.picking_type_id = self.picking_type_out
|
|
with delivery_form.move_ids.new() as move:
|
|
move.product_id = self.product1
|
|
move.product_uom_qty = 3.6
|
|
delivery_picking = delivery_form.save()
|
|
delivery_picking.action_confirm()
|
|
url = self._get_client_action_url(delivery_picking.id)
|
|
self.start_tour(url, 'test_scan_package_with_decimal', login='admin')
|
|
self.assertRecordValues(delivery_picking.move_line_ids, [
|
|
{"product_id": self.product1.id, "quantity": 3.6, "location_dest_id": delivery_picking.location_dest_id.id, "picked": True},
|
|
{"product_id": self.product1.id, "quantity": 272.24, "location_dest_id": delivery_picking.location_dest_id.id, "picked": True}
|
|
])
|
|
|
|
def test_barcode_signature_flow(self):
|
|
"""
|
|
1. Create two new delivery pickings to test two different signing flows
|
|
2. Assert that delivery does not have a signature assigned to it
|
|
3. After the tour is run, verify that the signature is set
|
|
4. Verify that the two delivery orders have been validated in the end
|
|
"""
|
|
self.env.user.write({'group_ids': [Command.link(self.env.ref('stock.group_stock_sign_delivery').id)]})
|
|
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 5)
|
|
|
|
partner = self.env['res.partner'].create({'name': 'My partner'})
|
|
|
|
def create_delivery_picking(picking_name):
|
|
delivery = self.env['stock.picking'].create({
|
|
'name': picking_name,
|
|
'picking_type_id': self.picking_type_out.id,
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.customer_location.id,
|
|
'partner_id': partner.id,
|
|
'user_id': False,
|
|
'move_ids': [(0, 0, {
|
|
'product_id': self.product1.id,
|
|
'product_uom': self.product1.uom_id.id,
|
|
'product_uom_qty': 1,
|
|
'procure_method': 'make_to_stock',
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.customer_location.id,
|
|
})],
|
|
})
|
|
delivery.action_confirm()
|
|
delivery.action_assign()
|
|
return delivery
|
|
|
|
delivery1 = create_delivery_picking('Delivery Order 1')
|
|
delivery2 = create_delivery_picking('Delivery Order 2')
|
|
|
|
self.assertFalse(delivery2.signature)
|
|
|
|
url = '/odoo/action-stock_barcode.stock_picking_type_action_kanban'
|
|
self.start_tour(url, 'test_barcode_signature_flow', login="admin")
|
|
|
|
self.assertTrue(delivery2.signature)
|
|
self.assertEqual(delivery1.state, 'done')
|
|
self.assertEqual(delivery2.state, 'done')
|
|
|
|
def test_select_with_same_product_and_lot(self):
|
|
self.env.user.write({'group_ids': [Command.link(self.env.ref('stock.group_production_lot').id)]})
|
|
ref = self.env['stock.reference'].create({'name': 'ProcurementGroup'})
|
|
lot_xyz = self.env['stock.lot'].create({'name': 'lot_xyz', 'product_id': self.productlot1.id, 'company_id': self.env.company.id})
|
|
self.env['stock.quant']._update_available_quantity(self.productlot1, self.stock_location, 4, lot_id=lot_xyz)
|
|
self.picking_type_out.show_reserved_sns = True
|
|
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({
|
|
'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,
|
|
'reference_ids': ref.ids,
|
|
})
|
|
delivery_picking.action_confirm()
|
|
second_move = self.env['stock.move'].create({
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.customer_location.id,
|
|
'product_id': self.productlot1.id,
|
|
'product_uom': self.uom_unit.id,
|
|
'quantity': 2,
|
|
'picking_id': delivery_picking.id,
|
|
'reference_ids': ref.ids,
|
|
})
|
|
second_move.move_line_ids.lot_id = lot_xyz
|
|
self.assertEqual(len(delivery_picking.move_ids), 2)
|
|
url = self._get_client_action_url(delivery_picking.id)
|
|
self.start_tour(url, 'test_select_with_same_product_and_lot', login='admin', timeout=180)
|
|
|
|
def test_description_picking_tour(self):
|
|
""" Test that when creating a receipt or internal transfer using the barcode app, the
|
|
description_picking field of the move_line is not empty
|
|
"""
|
|
product = self.env['product.product'].create({
|
|
'name': 'test_product',
|
|
'description_pickingin': 'receipt',
|
|
'barcode': 'test_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_description_picking_tour', login='admin', timeout=180)
|
|
picking = self.env['stock.picking'].search([('move_ids.product_id.id', '=', product.id), ('state', '=', 'done')])
|
|
|
|
self.assertEqual(picking.move_ids.description_picking, 'receipt')
|
|
|
|
def test_no_validate_no_dest_package(self):
|
|
"""Ensures that the user can't validate an operation if scanning a destination
|
|
is mandatory for each scanned product and no destination was scanned."""
|
|
grp_multi_loc = self.env.ref('stock.group_stock_multi_locations')
|
|
grp_pack = self.env.ref('stock.group_tracking_lot')
|
|
self.env.user.write({'group_ids': [(4, grp_pack.id, 0), (4, grp_multi_loc.id, 0)]})
|
|
self.picking_type_internal.write({
|
|
'restrict_scan_source_location': 'mandatory',
|
|
'restrict_scan_dest_location': 'mandatory',
|
|
'active': True,
|
|
})
|
|
pack1 = self.env['stock.package'].create({
|
|
'name': 'Pack1',
|
|
})
|
|
self.env['stock.quant']._update_available_quantity(self.product2, self.stock_location, 5, package_id=pack1)
|
|
self.start_tour('/odoo/barcode', 'test_no_validate_no_dest_package', login='admin')
|
|
|
|
def test_qty_after_uom_update_picking_tour(self):
|
|
""" Test that when creating a receipt and updating the uom using the barcode app, the
|
|
quantity demand of the move line is correctly updated according to the uom.
|
|
"""
|
|
self.env.user.group_ids += self.env.ref('uom.group_uom')
|
|
product = self.env['product.product'].create({
|
|
'name': 'test_product_uom_update',
|
|
'barcode': 'test_product_uom_update',
|
|
'uom_ids': [Command.link(self.env.ref('uom.product_uom_dozen').id)],
|
|
})
|
|
receipt = self.env['stock.picking'].create({
|
|
'name': 'receipt_test',
|
|
'location_id': self.customer_location.id,
|
|
'location_dest_id': self.stock_location.id,
|
|
'picking_type_id': self.picking_type_in.id,
|
|
'move_ids': [Command.create({
|
|
'product_id': product.id,
|
|
'product_uom_qty': 10,
|
|
'product_uom': self.env.ref('uom.product_uom_dozen').id,
|
|
'location_id': self.customer_location.id,
|
|
'location_dest_id': self.stock_location.id,
|
|
})],
|
|
})
|
|
receipt.action_confirm()
|
|
self.start_tour('/odoo/barcode', 'test_qty_after_uom_update_picking_tour', login='admin')
|
|
|
|
def test_stock_quant_ids_computed_by_product_update(self):
|
|
"""
|
|
Verify that the computed field `product_stock_quant_ids` on `move_line_ids`
|
|
is correctly updated when product_id is set
|
|
"""
|
|
self.env['stock.quant'].create({
|
|
'product_id': self.product1.id,
|
|
'location_id': self.stock_location.id,
|
|
'quantity': 10
|
|
})
|
|
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,
|
|
'move_ids': [Command.create({
|
|
'location_id': self.stock_location.id,
|
|
'product_id': self.product1.id,
|
|
'location_dest_id': self.stock_location.id,
|
|
'product_uom': self.uom_unit.id,
|
|
'product_uom_qty': 1,
|
|
})]
|
|
})
|
|
internal_picking.action_confirm()
|
|
self.assertEqual(internal_picking.move_ids.move_line_ids.product_stock_quant_ids.quantity, 10.0)
|
|
|
|
def test_quantity_distribution_sublines_same_lot(self):
|
|
"""Test that when two lines with the same lot are grouped in barcode,
|
|
the quantities are split correctly between the lines when scanning two
|
|
times the lot.
|
|
"""
|
|
grp_lot = self.env.ref('stock.group_production_lot')
|
|
self.env.user.write({'group_ids': [(4, grp_lot.id, 0)]})
|
|
lot_1 = self.env['stock.lot'].create({'name': 'lot 1', 'product_id': self.productlot1.id, 'company_id': self.env.company.id})
|
|
self.env['stock.quant'].create([
|
|
{
|
|
'product_id': self.productlot1.id,
|
|
'inventory_quantity': 2,
|
|
'lot_id': lot_1.id,
|
|
'location_id': self.stock_location.id,
|
|
},
|
|
]).action_apply_inventory()
|
|
|
|
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,
|
|
'move_ids': [Command.create({
|
|
'product_id': self.productlot1.id,
|
|
'product_uom_qty': 1,
|
|
'price_unit': i, # Different price unit so moves won't be merged.
|
|
'product_uom': self.productlot1.uom_id.id,
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.customer_location.id,
|
|
}) for i in (1, 3)],
|
|
})
|
|
delivery_picking.action_confirm()
|
|
delivery_picking.action_assign()
|
|
url = self._get_client_action_url(delivery_picking.id)
|
|
self.start_tour(url, 'test_quantity_distribution_sublines_same_lot', login='admin')
|
|
self.assertEqual(len(delivery_picking.backorder_ids), 0)
|
|
self.assertRecordValues(delivery_picking.move_ids, [
|
|
{'quantity': 1.0, 'product_uom_qty': 1.0, 'lot_ids': [lot_1.id], 'picked': True},
|
|
{'quantity': 1.0, 'product_uom_qty': 1.0, 'lot_ids': [lot_1.id], 'picked': True},
|
|
])
|
|
|
|
def test_rental_partial_reception(self):
|
|
""" Checks that processing a partial receipt for a rental order triggers the backorder dialog.
|
|
"""
|
|
if not self.env['ir.module.module'].search([('name', '=', 'sale_stock_renting'), ('state', '=', 'installed')]):
|
|
self.skipTest("sale_stock_renting is not installed, so there is no rental orders to test")
|
|
|
|
# Enable rental pickings
|
|
self.env['res.config.settings'].create({'group_rental_stock_picking': True}).execute()
|
|
|
|
product = self.env['product.product'].create({
|
|
'name': 'Rental',
|
|
'rent_ok': True,
|
|
'is_storable': True,
|
|
'barcode': 'RNT01'
|
|
})
|
|
self.env['stock.quant']._update_available_quantity(product, self.stock_location, 4)
|
|
rental = self.env['sale.order'].with_context(in_rental_app=True).create({
|
|
'partner_id': self.owner.id,
|
|
'order_line': [Command.create({
|
|
'product_id': product.id,
|
|
'product_uom_qty': 4,
|
|
})]
|
|
})
|
|
rental.action_confirm()
|
|
delivery = rental.picking_ids.filtered(lambda p: p.picking_type_id == rental.warehouse_id.out_type_id)
|
|
receipt = rental.picking_ids - delivery
|
|
delivery.button_validate()
|
|
|
|
url = self._get_client_action_url(receipt.id)
|
|
self.start_tour(url, 'test_rental_partial_reception', login='admin', timeout=180)
|
|
|
|
self.assertTrue(receipt.backorder_ids)
|
|
self.assertEqual(receipt.backorder_ids.move_ids.product_uom_qty, 3.0)
|
|
|
|
def test_no_validate_multiple_times(self):
|
|
grp_multi_loc = self.env.ref('stock.group_stock_multi_locations')
|
|
self.env.user.write({'group_ids': [Command.link(grp_multi_loc.id)]})
|
|
self.picking_type_internal.action_unarchive()
|
|
self.env['stock.quant']._update_available_quantity(self.product2, self.stock_location, 1)
|
|
self.start_tour('/odoo/barcode', 'test_no_validate_multiple_times', login='admin')
|
|
|
|
quant = self.env['stock.quant'].search([('product_id', '=', self.product2.id), ('location_id', '=', self.shelf1.id)], limit=1)
|
|
self.assertEqual(quant.quantity, 1)
|
|
|
|
def test_quantity_updates_on_exit_spam(self):
|
|
"""
|
|
Test that spamming the barcode back button multiple times
|
|
triggers the related updates only once.
|
|
"""
|
|
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,
|
|
})
|
|
self.start_tour('/odoo/barcode', 'test_quantity_updates_on_exit_spam', login='admin')
|
|
self.assertRecordValues(delivery.move_line_ids, [{
|
|
'product_id': self.product1.id, 'quantity': 1.0,
|
|
}])
|
|
|
|
def test_confirm_picking_with_archived_product(self):
|
|
"""
|
|
Test that a delivery picking can be confirmed even if a product involved
|
|
in the stock move has been archived (set as inactive).
|
|
|
|
This ensures that archived products do not block the confirmation process
|
|
in product selector.
|
|
"""
|
|
self.env.user.group_ids += self.env.ref('stock.group_stock_multi_locations')
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 10)
|
|
delivery_picking = 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': [
|
|
Command.create({
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.customer_location.id,
|
|
'product_id': self.product1.id,
|
|
'product_uom_qty': 1,
|
|
}),
|
|
],
|
|
})
|
|
delivery_picking.action_confirm()
|
|
self.product1.active = False
|
|
url = self._get_client_action_url(delivery_picking.id)
|
|
self.start_tour(url, 'test_confirm_picking_with_archived_product', login='admin')
|
|
|
|
def test_barcode_sync_on_exit(self):
|
|
"""
|
|
This tests that when exiting barcode after scanning a new serial number that it waits
|
|
for the backend network call to finish, preventing stale data from appearing on the
|
|
picking.
|
|
"""
|
|
grp_lot = self.env.ref('stock.group_production_lot')
|
|
grp_pack = self.env.ref('stock.group_tracking_lot')
|
|
self.env.user.write({'group_ids': [Command.link(grp_lot.id), Command.link(grp_pack.id)]})
|
|
serial_1 = self.env['stock.lot'].create({'name': 'serial 1', 'product_id': self.productserial1.id, 'company_id': self.env.company.id})
|
|
serial_2 = self.env['stock.lot'].create({'name': 'serial 2', 'product_id': self.productserial1.id, 'company_id': self.env.company.id})
|
|
package = self.env['stock.package'].create({'name': 'package001'})
|
|
self.env['stock.quant']._update_available_quantity(self.productserial1, self.stock_location, 1, lot_id=serial_1, package_id=package)
|
|
self.env['stock.quant']._update_available_quantity(self.productserial1, self.stock_location, 1, lot_id=serial_2)
|
|
|
|
delivery_picking = 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': [
|
|
Command.create({
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.customer_location.id,
|
|
'product_id': self.productserial1.id,
|
|
'product_uom_qty': 1,
|
|
'lot_ids': serial_1.ids,
|
|
}),
|
|
],
|
|
})
|
|
delivery_picking.action_confirm()
|
|
|
|
url = f'/odoo/action-stock.action_picking_tree_incoming/{delivery_picking.id}'
|
|
self.start_tour(url, 'test_barcode_sync_on_exit', login='admin')
|
|
|
|
def test_set_quantity_from_move_line_with_different_uom(self):
|
|
"""
|
|
Test that when exiting an unfinished receipt operation in barcode, the move quantity is correctly
|
|
updated from move lines even when the move line UoM differs from the move UoM.
|
|
"""
|
|
self.env.user.group_ids += self.env.ref('uom.group_uom')
|
|
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 10)
|
|
unit, pack_of_6 = self.ref('uom.product_uom_unit'), self.ref('uom.product_uom_pack_6')
|
|
receipt = self.env['stock.picking'].create({
|
|
'name': 'Test Receipt',
|
|
'picking_type_id': self.picking_type_in.id,
|
|
'location_id': self.supplier_location.id,
|
|
'location_dest_id': self.stock_location.id,
|
|
'move_ids': [Command.create({
|
|
'product_id': self.product1.id,
|
|
'product_uom_qty': 6,
|
|
'product_uom': unit,
|
|
'location_id': self.supplier_location.id,
|
|
'location_dest_id': self.stock_location.id,
|
|
})],
|
|
})
|
|
receipt.action_confirm()
|
|
receipt.move_line_ids.write({
|
|
'quantity': 1,
|
|
'product_uom_id': pack_of_6,
|
|
})
|
|
url = "/odoo/action-stock_barcode.stock_barcode_action_main_menu"
|
|
self.start_tour(url, 'test_set_quantity_from_move_line_with_different_uom', login='admin')
|
|
self.assertRecordValues(receipt.move_ids, [{'quantity': 6, 'product_uom_qty': 6, 'product_uom': unit}])
|
|
self.assertRecordValues(receipt.move_line_ids, [{'quantity': 1, 'product_uom_id': pack_of_6}])
|