odoo-addon-shopfloor 16.0.1.0.0.24__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- odoo/addons/shopfloor/README.rst +160 -0
- odoo/addons/shopfloor/__init__.py +4 -0
- odoo/addons/shopfloor/__manifest__.py +65 -0
- odoo/addons/shopfloor/actions/__init__.py +15 -0
- odoo/addons/shopfloor/actions/change_package_lot.py +164 -0
- odoo/addons/shopfloor/actions/completion_info.py +42 -0
- odoo/addons/shopfloor/actions/data.py +329 -0
- odoo/addons/shopfloor/actions/data_detail.py +154 -0
- odoo/addons/shopfloor/actions/inventory.py +150 -0
- odoo/addons/shopfloor/actions/location_content_transfer_sorter.py +89 -0
- odoo/addons/shopfloor/actions/message.py +846 -0
- odoo/addons/shopfloor/actions/move_line_search.py +119 -0
- odoo/addons/shopfloor/actions/packaging.py +59 -0
- odoo/addons/shopfloor/actions/savepoint.py +44 -0
- odoo/addons/shopfloor/actions/schema.py +182 -0
- odoo/addons/shopfloor/actions/schema_detail.py +98 -0
- odoo/addons/shopfloor/actions/search.py +187 -0
- odoo/addons/shopfloor/actions/stock.py +239 -0
- odoo/addons/shopfloor/actions/stock_unreserve.py +66 -0
- odoo/addons/shopfloor/components/__init__.py +5 -0
- odoo/addons/shopfloor/components/scan_handler_location.py +26 -0
- odoo/addons/shopfloor/components/scan_handler_lot.py +26 -0
- odoo/addons/shopfloor/components/scan_handler_package.py +26 -0
- odoo/addons/shopfloor/components/scan_handler_product.py +26 -0
- odoo/addons/shopfloor/components/scan_handler_transfer.py +26 -0
- odoo/addons/shopfloor/data/shopfloor_scenario_data.xml +73 -0
- odoo/addons/shopfloor/demo/shopfloor_app_demo.xml +12 -0
- odoo/addons/shopfloor/demo/shopfloor_menu_demo.xml +64 -0
- odoo/addons/shopfloor/demo/shopfloor_profile_demo.xml +8 -0
- odoo/addons/shopfloor/demo/stock_picking_type_demo.xml +93 -0
- odoo/addons/shopfloor/docs/checkout_diag_seq.plantuml +61 -0
- odoo/addons/shopfloor/docs/checkout_diag_seq.png +0 -0
- odoo/addons/shopfloor/docs/cluster_picking_diag_seq.plantuml +112 -0
- odoo/addons/shopfloor/docs/cluster_picking_diag_seq.png +0 -0
- odoo/addons/shopfloor/docs/delivery_diag_seq.plantuml +56 -0
- odoo/addons/shopfloor/docs/delivery_diag_seq.png +0 -0
- odoo/addons/shopfloor/docs/location_content_transfer_diag_seq.plantuml +66 -0
- odoo/addons/shopfloor/docs/location_content_transfer_diag_seq.png +0 -0
- odoo/addons/shopfloor/docs/oca_logo.png +0 -0
- odoo/addons/shopfloor/docs/single_pack_transfer_diag_seq.plantuml +36 -0
- odoo/addons/shopfloor/docs/single_pack_transfer_diag_seq.png +0 -0
- odoo/addons/shopfloor/docs/zone_picking_diag_seq.plantuml +85 -0
- odoo/addons/shopfloor/docs/zone_picking_diag_seq.png +0 -0
- odoo/addons/shopfloor/exceptions.py +6 -0
- odoo/addons/shopfloor/i18n/ca.po +1802 -0
- odoo/addons/shopfloor/i18n/de.po +1791 -0
- odoo/addons/shopfloor/i18n/es_AR.po +2147 -0
- odoo/addons/shopfloor/i18n/pt_BR.po +1791 -0
- odoo/addons/shopfloor/i18n/shopfloor.pot +1877 -0
- odoo/addons/shopfloor/models/__init__.py +12 -0
- odoo/addons/shopfloor/models/priority_postpone_mixin.py +41 -0
- odoo/addons/shopfloor/models/shopfloor_app.py +9 -0
- odoo/addons/shopfloor/models/shopfloor_menu.py +436 -0
- odoo/addons/shopfloor/models/stock_location.py +76 -0
- odoo/addons/shopfloor/models/stock_move.py +119 -0
- odoo/addons/shopfloor/models/stock_move_line.py +307 -0
- odoo/addons/shopfloor/models/stock_package_level.py +50 -0
- odoo/addons/shopfloor/models/stock_picking.py +118 -0
- odoo/addons/shopfloor/models/stock_picking_batch.py +41 -0
- odoo/addons/shopfloor/models/stock_picking_type.py +26 -0
- odoo/addons/shopfloor/models/stock_quant.py +31 -0
- odoo/addons/shopfloor/models/stock_quant_package.py +101 -0
- odoo/addons/shopfloor/readme/CONTRIBUTORS.rst +18 -0
- odoo/addons/shopfloor/readme/CREDITS.rst +5 -0
- odoo/addons/shopfloor/readme/DESCRIPTION.rst +17 -0
- odoo/addons/shopfloor/readme/HISTORY.rst +4 -0
- odoo/addons/shopfloor/readme/ROADMAP.rst +4 -0
- odoo/addons/shopfloor/readme/USAGE.rst +6 -0
- odoo/addons/shopfloor/security/groups.xml +17 -0
- odoo/addons/shopfloor/services/__init__.py +16 -0
- odoo/addons/shopfloor/services/checkout.py +1763 -0
- odoo/addons/shopfloor/services/cluster_picking.py +1628 -0
- odoo/addons/shopfloor/services/delivery.py +828 -0
- odoo/addons/shopfloor/services/forms/__init__.py +1 -0
- odoo/addons/shopfloor/services/forms/picking_form.py +78 -0
- odoo/addons/shopfloor/services/location_content_transfer.py +1194 -0
- odoo/addons/shopfloor/services/menu.py +60 -0
- odoo/addons/shopfloor/services/picking_batch.py +126 -0
- odoo/addons/shopfloor/services/service.py +101 -0
- odoo/addons/shopfloor/services/single_pack_transfer.py +366 -0
- odoo/addons/shopfloor/services/zone_picking.py +1938 -0
- odoo/addons/shopfloor/static/description/icon.png +0 -0
- odoo/addons/shopfloor/static/description/index.html +500 -0
- odoo/addons/shopfloor/tests/__init__.py +83 -0
- odoo/addons/shopfloor/tests/common.py +324 -0
- odoo/addons/shopfloor/tests/models.py +29 -0
- odoo/addons/shopfloor/tests/test_actions_change_package_lot.py +1175 -0
- odoo/addons/shopfloor/tests/test_actions_data.py +376 -0
- odoo/addons/shopfloor/tests/test_actions_data_base.py +244 -0
- odoo/addons/shopfloor/tests/test_actions_data_detail.py +322 -0
- odoo/addons/shopfloor/tests/test_actions_search.py +248 -0
- odoo/addons/shopfloor/tests/test_actions_stock.py +48 -0
- odoo/addons/shopfloor/tests/test_checkout_auto_post.py +67 -0
- odoo/addons/shopfloor/tests/test_checkout_base.py +81 -0
- odoo/addons/shopfloor/tests/test_checkout_cancel_line.py +154 -0
- odoo/addons/shopfloor/tests/test_checkout_change_packaging.py +184 -0
- odoo/addons/shopfloor/tests/test_checkout_done.py +133 -0
- odoo/addons/shopfloor/tests/test_checkout_list_delivery_packaging.py +131 -0
- odoo/addons/shopfloor/tests/test_checkout_list_package.py +327 -0
- odoo/addons/shopfloor/tests/test_checkout_new_package.py +88 -0
- odoo/addons/shopfloor/tests/test_checkout_no_package.py +95 -0
- odoo/addons/shopfloor/tests/test_checkout_scan.py +174 -0
- odoo/addons/shopfloor/tests/test_checkout_scan_line.py +377 -0
- odoo/addons/shopfloor/tests/test_checkout_scan_line_base.py +25 -0
- odoo/addons/shopfloor/tests/test_checkout_scan_line_no_prefill_qty.py +91 -0
- odoo/addons/shopfloor/tests/test_checkout_scan_package_action.py +451 -0
- odoo/addons/shopfloor/tests/test_checkout_scan_package_action_no_prefill_qty.py +107 -0
- odoo/addons/shopfloor/tests/test_checkout_select.py +74 -0
- odoo/addons/shopfloor/tests/test_checkout_select_line.py +130 -0
- odoo/addons/shopfloor/tests/test_checkout_select_package_base.py +64 -0
- odoo/addons/shopfloor/tests/test_checkout_set_qty.py +257 -0
- odoo/addons/shopfloor/tests/test_checkout_summary.py +69 -0
- odoo/addons/shopfloor/tests/test_cluster_picking_base.py +83 -0
- odoo/addons/shopfloor/tests/test_cluster_picking_batch.py +109 -0
- odoo/addons/shopfloor/tests/test_cluster_picking_change_pack_lot.py +111 -0
- odoo/addons/shopfloor/tests/test_cluster_picking_is_zero.py +98 -0
- odoo/addons/shopfloor/tests/test_cluster_picking_scan_destination.py +376 -0
- odoo/addons/shopfloor/tests/test_cluster_picking_scan_destination_no_prefill_qty.py +115 -0
- odoo/addons/shopfloor/tests/test_cluster_picking_scan_line.py +402 -0
- odoo/addons/shopfloor/tests/test_cluster_picking_scan_line_location_or_pack_first.py +114 -0
- odoo/addons/shopfloor/tests/test_cluster_picking_scan_line_no_prefill_qty.py +70 -0
- odoo/addons/shopfloor/tests/test_cluster_picking_select.py +387 -0
- odoo/addons/shopfloor/tests/test_cluster_picking_skip.py +90 -0
- odoo/addons/shopfloor/tests/test_cluster_picking_stock_issue.py +364 -0
- odoo/addons/shopfloor/tests/test_cluster_picking_unload.py +911 -0
- odoo/addons/shopfloor/tests/test_delivery_base.py +155 -0
- odoo/addons/shopfloor/tests/test_delivery_done.py +108 -0
- odoo/addons/shopfloor/tests/test_delivery_list_stock_picking.py +49 -0
- odoo/addons/shopfloor/tests/test_delivery_reset_qty_done_line.py +119 -0
- odoo/addons/shopfloor/tests/test_delivery_reset_qty_done_pack.py +107 -0
- odoo/addons/shopfloor/tests/test_delivery_scan_deliver.py +557 -0
- odoo/addons/shopfloor/tests/test_delivery_select.py +38 -0
- odoo/addons/shopfloor/tests/test_delivery_set_qty_done_line.py +91 -0
- odoo/addons/shopfloor/tests/test_delivery_set_qty_done_pack.py +135 -0
- odoo/addons/shopfloor/tests/test_delivery_sublocation.py +180 -0
- odoo/addons/shopfloor/tests/test_location_content_transfer_base.py +136 -0
- odoo/addons/shopfloor/tests/test_location_content_transfer_get_work.py +125 -0
- odoo/addons/shopfloor/tests/test_location_content_transfer_mix.py +509 -0
- odoo/addons/shopfloor/tests/test_location_content_transfer_putaway.py +143 -0
- odoo/addons/shopfloor/tests/test_location_content_transfer_scan_location.py +34 -0
- odoo/addons/shopfloor/tests/test_location_content_transfer_set_destination_all.py +343 -0
- odoo/addons/shopfloor/tests/test_location_content_transfer_set_destination_package_or_line.py +1074 -0
- odoo/addons/shopfloor/tests/test_location_content_transfer_single.py +748 -0
- odoo/addons/shopfloor/tests/test_location_content_transfer_start.py +359 -0
- odoo/addons/shopfloor/tests/test_menu_base.py +261 -0
- odoo/addons/shopfloor/tests/test_menu_counters.py +61 -0
- odoo/addons/shopfloor/tests/test_misc.py +25 -0
- odoo/addons/shopfloor/tests/test_move_action_assign.py +87 -0
- odoo/addons/shopfloor/tests/test_openapi.py +21 -0
- odoo/addons/shopfloor/tests/test_picking_form.py +62 -0
- odoo/addons/shopfloor/tests/test_scan_anything.py +49 -0
- odoo/addons/shopfloor/tests/test_single_pack_transfer.py +1121 -0
- odoo/addons/shopfloor/tests/test_single_pack_transfer_base.py +32 -0
- odoo/addons/shopfloor/tests/test_single_pack_transfer_putaway.py +104 -0
- odoo/addons/shopfloor/tests/test_stock_split.py +204 -0
- odoo/addons/shopfloor/tests/test_user.py +42 -0
- odoo/addons/shopfloor/tests/test_zone_picking_base.py +608 -0
- odoo/addons/shopfloor/tests/test_zone_picking_change_pack_lot.py +140 -0
- odoo/addons/shopfloor/tests/test_zone_picking_select_line.py +723 -0
- odoo/addons/shopfloor/tests/test_zone_picking_select_line_first_scan_location.py +207 -0
- odoo/addons/shopfloor/tests/test_zone_picking_select_line_first_scan_location.py.bak +202 -0
- odoo/addons/shopfloor/tests/test_zone_picking_select_line_no_prefill_qty.py +107 -0
- odoo/addons/shopfloor/tests/test_zone_picking_select_picking_type.py +26 -0
- odoo/addons/shopfloor/tests/test_zone_picking_set_line_destination.py +643 -0
- odoo/addons/shopfloor/tests/test_zone_picking_set_line_destination_no_prefill_qty.py +146 -0
- odoo/addons/shopfloor/tests/test_zone_picking_set_line_destination_pick_pack.py +241 -0
- odoo/addons/shopfloor/tests/test_zone_picking_start.py +206 -0
- odoo/addons/shopfloor/tests/test_zone_picking_stock_issue.py +121 -0
- odoo/addons/shopfloor/tests/test_zone_picking_unload_all.py +353 -0
- odoo/addons/shopfloor/tests/test_zone_picking_unload_buffer_lines.py +113 -0
- odoo/addons/shopfloor/tests/test_zone_picking_unload_set_destination.py +374 -0
- odoo/addons/shopfloor/tests/test_zone_picking_unload_single.py +123 -0
- odoo/addons/shopfloor/tests/test_zone_picking_zero_check.py +43 -0
- odoo/addons/shopfloor/utils.py +13 -0
- odoo/addons/shopfloor/views/shopfloor_menu.xml +167 -0
- odoo/addons/shopfloor/views/stock_location.xml +20 -0
- odoo/addons/shopfloor/views/stock_move_line.xml +52 -0
- odoo/addons/shopfloor/views/stock_picking_type.xml +19 -0
- odoo_addon_shopfloor-16.0.1.0.0.24.dist-info/METADATA +192 -0
- odoo_addon_shopfloor-16.0.1.0.0.24.dist-info/RECORD +182 -0
- odoo_addon_shopfloor-16.0.1.0.0.24.dist-info/WHEEL +5 -0
- odoo_addon_shopfloor-16.0.1.0.0.24.dist-info/top_level.txt +1 -0
@@ -0,0 +1,115 @@
|
|
1
|
+
# Copyright 2020 Camptocamp SA (http://www.camptocamp.com)
|
2
|
+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
3
|
+
|
4
|
+
from .test_cluster_picking_base import ClusterPickingCommonCase
|
5
|
+
|
6
|
+
# pylint: disable=missing-return
|
7
|
+
|
8
|
+
|
9
|
+
class ClusterPickingScanDestinationPackPrefillQtyCase(ClusterPickingCommonCase):
|
10
|
+
"""Tests covering the /scan_destination_pack endpoint
|
11
|
+
|
12
|
+
With the no prefill quantity option set
|
13
|
+
|
14
|
+
"""
|
15
|
+
|
16
|
+
@classmethod
|
17
|
+
def setUpClassBaseData(cls, *args, **kwargs):
|
18
|
+
super().setUpClassBaseData(*args, **kwargs)
|
19
|
+
cls.menu.sudo().no_prefill_qty = True
|
20
|
+
cls.batch = cls._create_picking_batch(
|
21
|
+
[
|
22
|
+
[
|
23
|
+
cls.BatchProduct(product=cls.product_a, quantity=10),
|
24
|
+
cls.BatchProduct(product=cls.product_b, quantity=10),
|
25
|
+
],
|
26
|
+
[cls.BatchProduct(product=cls.product_a, quantity=10)],
|
27
|
+
]
|
28
|
+
)
|
29
|
+
cls.one_line_picking = cls.batch.picking_ids.filtered(
|
30
|
+
lambda picking: len(picking.move_ids) == 1
|
31
|
+
)
|
32
|
+
cls.two_lines_picking = cls.batch.picking_ids.filtered(
|
33
|
+
lambda picking: len(picking.move_ids) == 2
|
34
|
+
)
|
35
|
+
|
36
|
+
cls.bin1 = cls.env["stock.quant.package"].create({})
|
37
|
+
cls.bin2 = cls.env["stock.quant.package"].create({})
|
38
|
+
|
39
|
+
cls._simulate_batch_selected(cls.batch)
|
40
|
+
|
41
|
+
def test_scan_destination_pack_increment_with_product(self):
|
42
|
+
"""Check quantity increment by scanning the product."""
|
43
|
+
line = self.batch.picking_ids.move_line_ids[0]
|
44
|
+
previous_qty_done = line.qty_done
|
45
|
+
for qty_done in range(1, 2):
|
46
|
+
response = self.service.dispatch(
|
47
|
+
"scan_destination_pack",
|
48
|
+
params={
|
49
|
+
"picking_batch_id": self.batch.id,
|
50
|
+
"move_line_id": line.id,
|
51
|
+
"barcode": line.product_id.barcode,
|
52
|
+
"quantity": qty_done,
|
53
|
+
},
|
54
|
+
)
|
55
|
+
# Ensure the qty has not changed.
|
56
|
+
self.assertEqual(line.qty_done, previous_qty_done)
|
57
|
+
|
58
|
+
expected_qty_done = qty_done + 1
|
59
|
+
self.assert_response(
|
60
|
+
response,
|
61
|
+
next_state="scan_destination",
|
62
|
+
data=self._line_data(line, qty_done=expected_qty_done),
|
63
|
+
)
|
64
|
+
|
65
|
+
def test_scan_destination_pack_increment_with_wrong_product(self):
|
66
|
+
"""Check quantity is not incremented by scanning the wrong product."""
|
67
|
+
line = self.batch.picking_ids.move_line_ids[0]
|
68
|
+
previous_qty_done = line.qty_done
|
69
|
+
qty_done = 2
|
70
|
+
response = self.service.dispatch(
|
71
|
+
"scan_destination_pack",
|
72
|
+
params={
|
73
|
+
"picking_batch_id": self.batch.id,
|
74
|
+
"move_line_id": line.id,
|
75
|
+
"barcode": self.product_b.barcode,
|
76
|
+
"quantity": qty_done,
|
77
|
+
},
|
78
|
+
)
|
79
|
+
# Ensure the qty has not changed.
|
80
|
+
self.assertEqual(line.qty_done, previous_qty_done)
|
81
|
+
|
82
|
+
expected_qty_done = qty_done
|
83
|
+
self.assert_response(
|
84
|
+
response,
|
85
|
+
next_state="scan_destination",
|
86
|
+
data=self._line_data(line, qty_done=expected_qty_done),
|
87
|
+
message=self.service.msg_store.bin_not_found_for_barcode(
|
88
|
+
self.product_b.barcode
|
89
|
+
),
|
90
|
+
)
|
91
|
+
|
92
|
+
def test_scan_destination_pack_increment_with_packaging(self):
|
93
|
+
"""Check quantity incremented by scanning the packaging."""
|
94
|
+
line = self.batch.picking_ids.move_line_ids[0]
|
95
|
+
previous_qty_done = line.qty_done
|
96
|
+
packaging = self.product_a_packaging
|
97
|
+
qty_done = 2
|
98
|
+
response = self.service.dispatch(
|
99
|
+
"scan_destination_pack",
|
100
|
+
params={
|
101
|
+
"picking_batch_id": self.batch.id,
|
102
|
+
"move_line_id": line.id,
|
103
|
+
"barcode": packaging.barcode,
|
104
|
+
"quantity": qty_done,
|
105
|
+
},
|
106
|
+
)
|
107
|
+
# Ensure the qty has not changed in the record.
|
108
|
+
self.assertEqual(line.qty_done, previous_qty_done)
|
109
|
+
|
110
|
+
expected_qty_done = qty_done + packaging.qty
|
111
|
+
self.assert_response(
|
112
|
+
response,
|
113
|
+
next_state="scan_destination",
|
114
|
+
data=self._line_data(line, qty_done=expected_qty_done),
|
115
|
+
)
|
@@ -0,0 +1,402 @@
|
|
1
|
+
# Copyright 2020 Camptocamp SA (http://www.camptocamp.com)
|
2
|
+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
3
|
+
|
4
|
+
from .test_cluster_picking_base import ClusterPickingLineCommonCase
|
5
|
+
|
6
|
+
|
7
|
+
class ClusterPickingScanLineCase(ClusterPickingLineCommonCase):
|
8
|
+
"""Tests covering the /scan_line endpoint
|
9
|
+
|
10
|
+
After a batch has been selected and the user confirmed they are
|
11
|
+
working on it.
|
12
|
+
|
13
|
+
User scans something and the scan_line endpoints validates they
|
14
|
+
scanned the proper thing to pick.
|
15
|
+
"""
|
16
|
+
|
17
|
+
def _scan_line_ok(self, line, scanned, expected_qty_done=1):
|
18
|
+
batch = line.picking_id.batch_id
|
19
|
+
previous_qty_done = line.qty_done
|
20
|
+
response = self.service.dispatch(
|
21
|
+
"scan_line",
|
22
|
+
params={
|
23
|
+
"picking_batch_id": batch.id,
|
24
|
+
"move_line_id": line.id,
|
25
|
+
"barcode": scanned,
|
26
|
+
},
|
27
|
+
)
|
28
|
+
# For any barcode scanned, the quantity done is set in
|
29
|
+
# the response data to fully done but the record is not updated.
|
30
|
+
# We ensure the qty has not changed in the record.
|
31
|
+
self.assertEqual(line.qty_done, previous_qty_done)
|
32
|
+
|
33
|
+
self.assert_response(
|
34
|
+
response,
|
35
|
+
next_state="scan_destination",
|
36
|
+
data=self._line_data(line, qty_done=expected_qty_done),
|
37
|
+
)
|
38
|
+
|
39
|
+
def _scan_line_error(self, line, scanned, message, sublocation=None):
|
40
|
+
batch = line.picking_id.batch_id
|
41
|
+
response = self.service.dispatch(
|
42
|
+
"scan_line",
|
43
|
+
params={
|
44
|
+
"picking_batch_id": batch.id,
|
45
|
+
"move_line_id": line.id,
|
46
|
+
"barcode": scanned,
|
47
|
+
},
|
48
|
+
)
|
49
|
+
kw = {"sublocation": self.data.location(sublocation)} if sublocation else {}
|
50
|
+
self.assert_response(
|
51
|
+
response,
|
52
|
+
next_state="start_line",
|
53
|
+
data=self._line_data(line, **kw),
|
54
|
+
message=message,
|
55
|
+
)
|
56
|
+
|
57
|
+
def test_scan_line_pack_ok(self):
|
58
|
+
"""Scan to check if user picks the correct pack for current line"""
|
59
|
+
self._simulate_batch_selected(self.batch, in_package=True)
|
60
|
+
line = self.batch.picking_ids.move_line_ids
|
61
|
+
self._scan_line_ok(line, line.package_id.name)
|
62
|
+
|
63
|
+
def test_scan_line_product_ok(self):
|
64
|
+
"""Scan to check if user picks the correct product for current line"""
|
65
|
+
self._simulate_batch_selected(self.batch)
|
66
|
+
line = self.batch.picking_ids.move_line_ids
|
67
|
+
self._scan_line_ok(line, line.product_id.barcode)
|
68
|
+
|
69
|
+
def test_scan_line_lot_ok(self):
|
70
|
+
"""Scan to check if user picks the correct lot for current line"""
|
71
|
+
self.product_a.tracking = "lot"
|
72
|
+
self._simulate_batch_selected(self.batch, in_lot=True)
|
73
|
+
line = self.batch.picking_ids.move_line_ids
|
74
|
+
self._scan_line_ok(line, line.lot_id.name)
|
75
|
+
|
76
|
+
def test_scan_line_serial_ok(self):
|
77
|
+
"""Scan to check if user picks the correct serial for current line"""
|
78
|
+
self.product_a.tracking = "serial"
|
79
|
+
self._simulate_batch_selected(self.batch, in_lot=True)
|
80
|
+
line = self.batch.picking_ids.move_line_ids
|
81
|
+
self._scan_line_ok(line, line.lot_id.name)
|
82
|
+
|
83
|
+
def test_scan_line_error_product_tracked(self):
|
84
|
+
"""Scan a product tracked by lot, must scan the lot.
|
85
|
+
|
86
|
+
If for the same product there is multiple lots in the location,
|
87
|
+
the user must scan the lot.
|
88
|
+
"""
|
89
|
+
self.product_a.tracking = "lot"
|
90
|
+
self._simulate_batch_selected(self.batch, in_lot=True)
|
91
|
+
line = self.batch.picking_ids.move_line_ids
|
92
|
+
# Add another lot for the same product in the location
|
93
|
+
location = self.batch.picking_ids.location_id
|
94
|
+
new_lot = self.env["stock.lot"].create(
|
95
|
+
{"product_id": self.product_a.id, "company_id": self.env.company.id}
|
96
|
+
)
|
97
|
+
self._update_qty_in_location(location, line.product_id, 2, lot=new_lot)
|
98
|
+
self._scan_line_error(
|
99
|
+
line,
|
100
|
+
line.product_id.barcode,
|
101
|
+
{
|
102
|
+
"message_type": "warning",
|
103
|
+
"body": "Product tracked by lot, please scan one.",
|
104
|
+
},
|
105
|
+
)
|
106
|
+
|
107
|
+
def test_scan_line_lot_ok_only_one_in_location(self):
|
108
|
+
"""Scan a product tracked by lot but no error.
|
109
|
+
|
110
|
+
If only one lot for that product is in the location, it can
|
111
|
+
be safely picked up.
|
112
|
+
"""
|
113
|
+
self.product_a.tracking = "lot"
|
114
|
+
self._simulate_batch_selected(self.batch, in_lot=True)
|
115
|
+
line = self.batch.picking_ids.move_line_ids
|
116
|
+
self._scan_line_ok(line, line.lot_id.name)
|
117
|
+
|
118
|
+
def test_scan_line_product_error_several_packages(self):
|
119
|
+
"""When we scan a product which is in more than one package, error"""
|
120
|
+
self._simulate_batch_selected(self.batch, in_package=True)
|
121
|
+
line = self.batch.picking_ids.move_line_ids
|
122
|
+
# create a second move line for the same product in a different
|
123
|
+
# package
|
124
|
+
move = line.move_id.copy()
|
125
|
+
self._fill_stock_for_moves(move, in_package=True)
|
126
|
+
move._action_confirm(merge=False)
|
127
|
+
move._action_assign()
|
128
|
+
|
129
|
+
self._scan_line_error(
|
130
|
+
line,
|
131
|
+
move.product_id.barcode,
|
132
|
+
{
|
133
|
+
"message_type": "warning",
|
134
|
+
"body": "This product is part of multiple"
|
135
|
+
" packages, please scan a package.",
|
136
|
+
},
|
137
|
+
)
|
138
|
+
|
139
|
+
def test_scan_line_product_error_in_one_package_and_raw_same_location(self):
|
140
|
+
"""Scan product which is both in a package and as raw in same location"""
|
141
|
+
self._simulate_batch_selected(self.batch, in_package=True)
|
142
|
+
line = self.batch.picking_ids.move_line_ids
|
143
|
+
# create a second move line for the same product in a different
|
144
|
+
# package
|
145
|
+
move = line.move_id.copy()
|
146
|
+
self._fill_stock_for_moves(move)
|
147
|
+
move._action_confirm(merge=False)
|
148
|
+
move._action_assign()
|
149
|
+
move.move_line_ids[0].package_id = None
|
150
|
+
|
151
|
+
self._scan_line_error(
|
152
|
+
line,
|
153
|
+
move.product_id.barcode,
|
154
|
+
{
|
155
|
+
"message_type": "warning",
|
156
|
+
"body": "This product is part of multiple"
|
157
|
+
" packages, please scan a package.",
|
158
|
+
},
|
159
|
+
)
|
160
|
+
|
161
|
+
def test_scan_line_product_error_in_one_package_and_raw_different_location(self):
|
162
|
+
"""Scan product which is both in a package and as raw in another location"""
|
163
|
+
self._simulate_batch_selected(self.batch, in_package=True)
|
164
|
+
line = self.batch.picking_ids.move_line_ids
|
165
|
+
# create a second move line for the same product in a different
|
166
|
+
# package
|
167
|
+
move = line.move_id.copy()
|
168
|
+
move.location_id = line.location_id.copy()
|
169
|
+
self._fill_stock_for_moves(move)
|
170
|
+
move._action_confirm(merge=False)
|
171
|
+
move._action_assign()
|
172
|
+
move.move_line_ids[0].package_id = None
|
173
|
+
move.move_line_ids[0].location_id = line.location_id.copy()
|
174
|
+
self._scan_line_ok(line, move.product_id.barcode)
|
175
|
+
|
176
|
+
def test_scan_line_lot_error_several_packages(self):
|
177
|
+
"""When we scan a lot which is in more than one package, error"""
|
178
|
+
self._simulate_batch_selected(self.batch, in_package=True, in_lot=True)
|
179
|
+
line = self.batch.picking_ids.move_line_ids
|
180
|
+
# create a second move line for the same product in a different
|
181
|
+
# package
|
182
|
+
move = line.move_id.copy()
|
183
|
+
self._fill_stock_for_moves(move, in_lot=line.lot_id)
|
184
|
+
move._action_confirm(merge=False)
|
185
|
+
move._action_assign()
|
186
|
+
|
187
|
+
self._scan_line_error(
|
188
|
+
line,
|
189
|
+
line.lot_id.name,
|
190
|
+
{
|
191
|
+
"message_type": "warning",
|
192
|
+
"body": "This lot is part of multiple"
|
193
|
+
" packages, please scan a package.",
|
194
|
+
},
|
195
|
+
)
|
196
|
+
|
197
|
+
def test_scan_line_lot_error_in_one_package_and_unit(self):
|
198
|
+
"""When we scan a lot which is in a package and as raw, error"""
|
199
|
+
self._simulate_batch_selected(self.batch, in_package=True, in_lot=True)
|
200
|
+
line = self.batch.picking_ids.move_line_ids
|
201
|
+
# create a second move line for the same product in a different
|
202
|
+
# package
|
203
|
+
move = line.move_id.copy()
|
204
|
+
self._fill_stock_for_moves(move, in_lot=line.lot_id)
|
205
|
+
move._action_confirm(merge=False)
|
206
|
+
move._action_assign()
|
207
|
+
self._scan_line_error(
|
208
|
+
line,
|
209
|
+
line.lot_id.name,
|
210
|
+
{
|
211
|
+
"message_type": "warning",
|
212
|
+
"body": "This lot is part of multiple"
|
213
|
+
" packages, please scan a package.",
|
214
|
+
},
|
215
|
+
)
|
216
|
+
|
217
|
+
def test_scan_line_location_ok_single_package(self):
|
218
|
+
"""Scan to check if user scans a correct location for current line
|
219
|
+
|
220
|
+
If there is only one single package in the location, there is no
|
221
|
+
ambiguity so we can use it.
|
222
|
+
"""
|
223
|
+
self._simulate_batch_selected(self.batch, in_package=True)
|
224
|
+
line = self.batch.picking_ids.move_line_ids
|
225
|
+
self._scan_line_ok(line, line.location_id.barcode)
|
226
|
+
|
227
|
+
def test_scan_line_location_ok_single_product(self):
|
228
|
+
"""Scan to check if user scans a correct location for current line
|
229
|
+
|
230
|
+
If there is only one single product in the location, there is no
|
231
|
+
ambiguity so we can use it.
|
232
|
+
"""
|
233
|
+
self._simulate_batch_selected(self.batch)
|
234
|
+
line = self.batch.picking_ids.move_line_ids
|
235
|
+
self._scan_line_ok(line, line.location_id.barcode)
|
236
|
+
|
237
|
+
def test_scan_line_location_ok_single_lot(self):
|
238
|
+
"""Scan to check if user scans a correct location for current line
|
239
|
+
|
240
|
+
If there is only one single lot in the location, there is no
|
241
|
+
ambiguity so we can use it.
|
242
|
+
"""
|
243
|
+
self._simulate_batch_selected(self.batch, in_lot=True)
|
244
|
+
line = self.batch.picking_ids.move_line_ids
|
245
|
+
self._scan_line_ok(line, line.location_id.barcode)
|
246
|
+
|
247
|
+
def test_scan_line_location_error_several_package(self):
|
248
|
+
"""Scan to check if user scans a correct location for current line
|
249
|
+
|
250
|
+
If there are several packages in the location, user has to scan one.
|
251
|
+
"""
|
252
|
+
self._simulate_batch_selected(self.batch, in_package=True)
|
253
|
+
line = self.batch.picking_ids.move_line_ids
|
254
|
+
location = line.location_id
|
255
|
+
pack_1 = line.package_id
|
256
|
+
# add a second package for another product in the location
|
257
|
+
self._create_package_in_location(
|
258
|
+
location, [self.PackageContent(self.product_b, 10, lot=None)]
|
259
|
+
)
|
260
|
+
# it leads to an error, but now the location is kept as working location
|
261
|
+
self._scan_line_error(
|
262
|
+
line,
|
263
|
+
location.barcode,
|
264
|
+
{
|
265
|
+
"message_type": "warning",
|
266
|
+
"body": "Several packages found in Stock, please scan a package.",
|
267
|
+
},
|
268
|
+
sublocation=location,
|
269
|
+
)
|
270
|
+
# scanning the package works
|
271
|
+
self._scan_line_ok(line, pack_1.name)
|
272
|
+
|
273
|
+
def test_scan_line_location_error_several_products(self):
|
274
|
+
"""Scan to check if user scans a correct location for current line
|
275
|
+
|
276
|
+
If there are several products in the location, user has to scan one.
|
277
|
+
"""
|
278
|
+
self._simulate_batch_selected(self.batch)
|
279
|
+
line = self.batch.picking_ids.move_line_ids
|
280
|
+
location = line.location_id
|
281
|
+
# add a second product in the location, leads to an error
|
282
|
+
self._update_qty_in_location(location, self.product_b, 10)
|
283
|
+
self._scan_line_error(
|
284
|
+
line,
|
285
|
+
location.barcode,
|
286
|
+
{
|
287
|
+
"message_type": "warning",
|
288
|
+
"body": "Several products found in Stock, please scan a product.",
|
289
|
+
},
|
290
|
+
sublocation=location,
|
291
|
+
)
|
292
|
+
self._scan_line_ok(line, self.product_a.barcode)
|
293
|
+
|
294
|
+
def test_scan_line_location_error_several_lots(self):
|
295
|
+
"""Scan to check if user scans a correct location for current line
|
296
|
+
|
297
|
+
If there are several lots in the location, user has to scan one.
|
298
|
+
"""
|
299
|
+
self._simulate_batch_selected(self.batch, in_lot=True)
|
300
|
+
line = self.batch.picking_ids.move_line_ids
|
301
|
+
location = line.location_id
|
302
|
+
# add a 2nd lot in the same location
|
303
|
+
lot_2 = (
|
304
|
+
self.env["stock.lot"]
|
305
|
+
.sudo()
|
306
|
+
.create(
|
307
|
+
{
|
308
|
+
"name": "LOT_2nd",
|
309
|
+
"product_id": line.product_id.id,
|
310
|
+
"company_id": self.env.company.id,
|
311
|
+
}
|
312
|
+
)
|
313
|
+
)
|
314
|
+
self._update_qty_in_location(location, line.product_id, 10, lot=lot_2)
|
315
|
+
# leads to an error
|
316
|
+
self._scan_line_error(
|
317
|
+
line,
|
318
|
+
location.barcode,
|
319
|
+
{
|
320
|
+
"message_type": "warning",
|
321
|
+
"body": "Several lots found in Stock, please scan a lot.",
|
322
|
+
},
|
323
|
+
sublocation=location,
|
324
|
+
)
|
325
|
+
self._scan_line_ok(line, line.lot_id.name)
|
326
|
+
|
327
|
+
def test_scan_line_error_wrong_package(self):
|
328
|
+
"""Wrong package scanned"""
|
329
|
+
self._simulate_batch_selected(self.batch, in_package=True)
|
330
|
+
pack = self.env["stock.quant.package"].sudo().create({})
|
331
|
+
self._scan_line_error(
|
332
|
+
self.batch.picking_ids.move_line_ids,
|
333
|
+
pack.name,
|
334
|
+
{"message_type": "error", "body": "Wrong pack."},
|
335
|
+
)
|
336
|
+
|
337
|
+
def test_scan_line_error_wrong_product(self):
|
338
|
+
"""Wrong product scanned"""
|
339
|
+
self._simulate_batch_selected(self.batch, in_package=True)
|
340
|
+
product = (
|
341
|
+
self.env["product.product"]
|
342
|
+
.sudo()
|
343
|
+
.create(
|
344
|
+
{
|
345
|
+
"name": "Wrong",
|
346
|
+
"barcode": "WRONGPRODUCT",
|
347
|
+
}
|
348
|
+
)
|
349
|
+
)
|
350
|
+
self._scan_line_error(
|
351
|
+
self.batch.picking_ids.move_line_ids,
|
352
|
+
product.barcode,
|
353
|
+
{"message_type": "error", "body": "Wrong product."},
|
354
|
+
)
|
355
|
+
|
356
|
+
def test_scan_line_error_wrong_lot(self):
|
357
|
+
"""Wrong product scanned"""
|
358
|
+
self._simulate_batch_selected(self.batch, in_package=True)
|
359
|
+
lot = (
|
360
|
+
self.env["stock.lot"]
|
361
|
+
.sudo()
|
362
|
+
.create(
|
363
|
+
{
|
364
|
+
"name": "WRONGLOT",
|
365
|
+
"product_id": self.batch.picking_ids.move_line_ids[0].product_id.id,
|
366
|
+
"company_id": self.env.company.id,
|
367
|
+
}
|
368
|
+
)
|
369
|
+
)
|
370
|
+
self._scan_line_error(
|
371
|
+
self.batch.picking_ids.move_line_ids,
|
372
|
+
lot.name,
|
373
|
+
{"message_type": "error", "body": "Wrong lot."},
|
374
|
+
)
|
375
|
+
|
376
|
+
def test_scan_line_error_wrong_location(self):
|
377
|
+
"""Wrong product scanned"""
|
378
|
+
self._simulate_batch_selected(self.batch, in_package=True)
|
379
|
+
location = (
|
380
|
+
self.env["stock.location"]
|
381
|
+
.sudo()
|
382
|
+
.create(
|
383
|
+
{
|
384
|
+
"name": "Wrong",
|
385
|
+
"barcode": "WRONGLOCATION",
|
386
|
+
}
|
387
|
+
)
|
388
|
+
)
|
389
|
+
self._scan_line_error(
|
390
|
+
self.batch.picking_ids.move_line_ids,
|
391
|
+
location.barcode,
|
392
|
+
{"message_type": "error", "body": "Wrong location."},
|
393
|
+
)
|
394
|
+
|
395
|
+
def test_scan_line_error_not_found(self):
|
396
|
+
"""Nothing found for the barcode"""
|
397
|
+
self._simulate_batch_selected(self.batch, in_package=True)
|
398
|
+
self._scan_line_error(
|
399
|
+
self.batch.picking_ids.move_line_ids,
|
400
|
+
"NO_EXISTING_BARCODE",
|
401
|
+
{"message_type": "error", "body": "Barcode not found"},
|
402
|
+
)
|
@@ -0,0 +1,114 @@
|
|
1
|
+
# Copyright 2023 Camptocamp SA (http://www.camptocamp.com)
|
2
|
+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
3
|
+
|
4
|
+
from .test_cluster_picking_base import ClusterPickingLineCommonCase
|
5
|
+
|
6
|
+
# pylint: disable=missing-return
|
7
|
+
|
8
|
+
|
9
|
+
class ClusterPickingScanLineLocationOrPackFirstCase(ClusterPickingLineCommonCase):
|
10
|
+
"""Tests covering the /scan_line endpoint
|
11
|
+
|
12
|
+
When the scan location or pack frist option enabled.
|
13
|
+
|
14
|
+
"""
|
15
|
+
|
16
|
+
def setUp(self):
|
17
|
+
super().setUp()
|
18
|
+
self.menu.sudo().scan_location_or_pack_first = True
|
19
|
+
|
20
|
+
def _scan_line_error(self, line, scanned, message, sublocation=None):
|
21
|
+
batch = line.picking_id.batch_id
|
22
|
+
response = self.service.dispatch(
|
23
|
+
"scan_line",
|
24
|
+
params={
|
25
|
+
"picking_batch_id": batch.id,
|
26
|
+
"move_line_id": line.id,
|
27
|
+
"barcode": scanned,
|
28
|
+
"sublocation_id": sublocation.id if sublocation else None,
|
29
|
+
},
|
30
|
+
)
|
31
|
+
kw = {"sublocation": self.data.location(sublocation)} if sublocation else {}
|
32
|
+
self.assert_response(
|
33
|
+
response,
|
34
|
+
next_state="start_line",
|
35
|
+
data=self._line_data(line, scan_location_or_pack_first=True, **kw),
|
36
|
+
message=message,
|
37
|
+
)
|
38
|
+
return response
|
39
|
+
|
40
|
+
def _scan_line_ok(self, line, scanned, expected_qty_done=1, sublocation_id=None):
|
41
|
+
batch = line.picking_id.batch_id
|
42
|
+
response = self.service.dispatch(
|
43
|
+
"scan_line",
|
44
|
+
params={
|
45
|
+
"picking_batch_id": batch.id,
|
46
|
+
"move_line_id": line.id,
|
47
|
+
"barcode": scanned,
|
48
|
+
"sublocation_id": sublocation_id,
|
49
|
+
},
|
50
|
+
)
|
51
|
+
self.assert_response(
|
52
|
+
response,
|
53
|
+
next_state="scan_destination",
|
54
|
+
data=self._line_data(
|
55
|
+
line, qty_done=expected_qty_done, scan_location_or_pack_first=True
|
56
|
+
),
|
57
|
+
)
|
58
|
+
|
59
|
+
def test_scan_line_product_ask_for_package(self):
|
60
|
+
"""Check scanning the product first will request to scan the package.
|
61
|
+
|
62
|
+
This is if the line being worked on as a package.
|
63
|
+
|
64
|
+
"""
|
65
|
+
self._simulate_batch_selected(self.batch, in_package=True, in_lot=False)
|
66
|
+
line = self.batch.picking_ids.move_line_ids
|
67
|
+
location = self.batch.picking_ids.location_id
|
68
|
+
self._update_qty_in_location(location, self.product_a, 2)
|
69
|
+
self._scan_line_error(
|
70
|
+
line,
|
71
|
+
line.product_id.barcode,
|
72
|
+
self.msg_store.line_has_package_scan_package(),
|
73
|
+
)
|
74
|
+
|
75
|
+
def test_scan_line_product_ask_for_location(self):
|
76
|
+
"""Check scanning the product first will request to scan the location.
|
77
|
+
|
78
|
+
That is if the product on the move line is tracked by lot.
|
79
|
+
|
80
|
+
"""
|
81
|
+
self._simulate_batch_selected(self.batch, in_package=False, in_lot=True)
|
82
|
+
line = self.batch.picking_ids.move_line_ids
|
83
|
+
location = self.batch.picking_ids.location_id
|
84
|
+
self._update_qty_in_location(location, self.product_a, 2)
|
85
|
+
self._scan_line_error(
|
86
|
+
line, line.product_id.barcode, self.msg_store.scan_the_location_first()
|
87
|
+
)
|
88
|
+
|
89
|
+
def test_scan_line_location_ask_for_package(self):
|
90
|
+
"""Check scanning location for a line with pack will ask to scan the pack."""
|
91
|
+
self._simulate_batch_selected(self.batch, in_package=True, in_lot=False)
|
92
|
+
line = self.batch.picking_ids.move_line_ids
|
93
|
+
location = self.batch.picking_ids.location_id
|
94
|
+
self._update_qty_in_location(location, self.product_a, 2)
|
95
|
+
self._scan_line_error(
|
96
|
+
line,
|
97
|
+
line.location_id.barcode,
|
98
|
+
self.msg_store.line_has_package_scan_package(),
|
99
|
+
sublocation=location,
|
100
|
+
)
|
101
|
+
|
102
|
+
def test_scan_line_location_with_multiple_product(self):
|
103
|
+
"""Check scanning a location then a product without package.
|
104
|
+
|
105
|
+
When there is multiple product in the location and the location is scanned,
|
106
|
+
The user needs to scan the product but the system does not remember the location ?
|
107
|
+
|
108
|
+
"""
|
109
|
+
self._simulate_batch_selected(self.batch, in_package=False, in_lot=False)
|
110
|
+
line = self.batch.picking_ids.move_line_ids
|
111
|
+
location = self.batch.picking_ids.location_id
|
112
|
+
self._update_qty_in_location(location, self.product_a, 2)
|
113
|
+
self._update_qty_in_location(location, self.product_b, 2)
|
114
|
+
self._scan_line_ok(line, line.product_id.barcode, 1.0, location.id)
|