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,109 @@
|
|
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 .common import CommonCase, PickingBatchMixin
|
5
|
+
|
6
|
+
# pylint: disable=missing-return
|
7
|
+
|
8
|
+
|
9
|
+
class ClusterPickingBatchCase(CommonCase, PickingBatchMixin):
|
10
|
+
@classmethod
|
11
|
+
def setUpClassVars(cls, *args, **kwargs):
|
12
|
+
super().setUpClassVars(*args, **kwargs)
|
13
|
+
cls.menu = cls.env.ref("shopfloor.shopfloor_menu_demo_cluster_picking")
|
14
|
+
cls.profile = cls.env.ref("shopfloor_base.profile_demo_1")
|
15
|
+
cls.picking_type = cls.menu.picking_type_ids
|
16
|
+
cls.wh = cls.picking_type.warehouse_id
|
17
|
+
|
18
|
+
@classmethod
|
19
|
+
def setUpClassBaseData(cls, *args, **kwargs):
|
20
|
+
super().setUpClassBaseData(*args, **kwargs)
|
21
|
+
cls.product_a = (
|
22
|
+
cls.env["product.product"]
|
23
|
+
.sudo()
|
24
|
+
.create({"name": "Product A", "type": "product"})
|
25
|
+
)
|
26
|
+
cls.product_b = (
|
27
|
+
cls.env["product.product"]
|
28
|
+
.sudo()
|
29
|
+
.create({"name": "Product B", "type": "product"})
|
30
|
+
)
|
31
|
+
cls.batch1 = cls._create_picking_batch(
|
32
|
+
[[cls.BatchProduct(product=cls.product_a, quantity=1)]]
|
33
|
+
)
|
34
|
+
cls.batch2 = cls._create_picking_batch(
|
35
|
+
[[cls.BatchProduct(product=cls.product_a, quantity=1)]]
|
36
|
+
)
|
37
|
+
cls.batch3 = cls._create_picking_batch(
|
38
|
+
[[cls.BatchProduct(product=cls.product_a, quantity=1)]]
|
39
|
+
)
|
40
|
+
cls.batch4 = cls._create_picking_batch(
|
41
|
+
[[cls.BatchProduct(product=cls.product_b, quantity=1)]]
|
42
|
+
)
|
43
|
+
cls.batch5 = cls._create_picking_batch(
|
44
|
+
[[cls.BatchProduct(product=cls.product_b, quantity=1)]]
|
45
|
+
)
|
46
|
+
cls.batch6 = cls._create_picking_batch(
|
47
|
+
[[cls.BatchProduct(product=cls.product_b, quantity=1)]]
|
48
|
+
)
|
49
|
+
cls.all_batches = (
|
50
|
+
cls.batch1 + cls.batch2 + cls.batch3 + cls.batch4 + cls.batch5 + cls.batch6
|
51
|
+
)
|
52
|
+
|
53
|
+
def setUp(self):
|
54
|
+
super().setUp()
|
55
|
+
self.service = self.get_service(
|
56
|
+
"cluster_picking", menu=self.menu, profile=self.profile
|
57
|
+
)
|
58
|
+
|
59
|
+
def test_search_empty(self):
|
60
|
+
"""No batch is available"""
|
61
|
+
# Simulate the client asking the list of picking batch
|
62
|
+
# none of the pickings are assigned, so we can't work on them
|
63
|
+
self.assertFalse(self.service._batch_picking_search())
|
64
|
+
|
65
|
+
def test_search(self):
|
66
|
+
"""Return only draft batches with assigned pickings"""
|
67
|
+
pickings = self.all_batches.mapped("picking_ids")
|
68
|
+
self._fill_stock_for_moves(pickings.mapped("move_ids"))
|
69
|
+
pickings.action_assign()
|
70
|
+
self.assertTrue(all(p.state == "assigned" for p in pickings))
|
71
|
+
# we should not have done batches in list
|
72
|
+
self.batch5.state = "done"
|
73
|
+
# nor canceled batches
|
74
|
+
self.batch6.state = "cancel"
|
75
|
+
# we should not have batches in progress
|
76
|
+
self.batch4.user_id = self.env.ref("base.user_demo")
|
77
|
+
self.batch4.action_confirm()
|
78
|
+
# unless it's assigned to our user
|
79
|
+
self.batch3.user_id = self.env.user
|
80
|
+
self.batch3.action_confirm()
|
81
|
+
|
82
|
+
# Simulate the client asking the list of picking batch
|
83
|
+
res = self.service._batch_picking_search()
|
84
|
+
self.assertRecordValues(
|
85
|
+
res,
|
86
|
+
[
|
87
|
+
{
|
88
|
+
"id": self.batch1.id,
|
89
|
+
"name": self.batch1.name,
|
90
|
+
"picking_count": 1,
|
91
|
+
"move_line_count": 1,
|
92
|
+
"total_weight": 0.0,
|
93
|
+
},
|
94
|
+
{
|
95
|
+
"id": self.batch2.id,
|
96
|
+
"name": self.batch2.name,
|
97
|
+
"picking_count": 1,
|
98
|
+
"move_line_count": 1,
|
99
|
+
"total_weight": 0.0,
|
100
|
+
},
|
101
|
+
{
|
102
|
+
"id": self.batch3.id,
|
103
|
+
"name": self.batch3.name,
|
104
|
+
"picking_count": 1,
|
105
|
+
"move_line_count": 1,
|
106
|
+
"total_weight": 0.0,
|
107
|
+
},
|
108
|
+
],
|
109
|
+
)
|
@@ -0,0 +1,111 @@
|
|
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 ClusterPickingChangePackLotCase(ClusterPickingCommonCase):
|
10
|
+
"""Tests covering the /change_pack_lot endpoint
|
11
|
+
|
12
|
+
Only simple cases are tested to check the flow of responses on success and
|
13
|
+
error, the "change.package.lot" component is tested in its own tests.
|
14
|
+
"""
|
15
|
+
|
16
|
+
@classmethod
|
17
|
+
def setUpClassBaseData(cls, *args, **kwargs):
|
18
|
+
super().setUpClassBaseData(*args, **kwargs)
|
19
|
+
cls.batch = cls._create_picking_batch(
|
20
|
+
[[cls.BatchProduct(product=cls.product_a, quantity=10)]]
|
21
|
+
)
|
22
|
+
|
23
|
+
def _test_change_pack_lot(self, line, barcode, success=True, message=None):
|
24
|
+
batch = line.picking_id.batch_id
|
25
|
+
response = self.service.dispatch(
|
26
|
+
"change_pack_lot",
|
27
|
+
params={
|
28
|
+
"picking_batch_id": batch.id,
|
29
|
+
"move_line_id": line.id,
|
30
|
+
"barcode": barcode,
|
31
|
+
"quantity": line.qty_done,
|
32
|
+
},
|
33
|
+
)
|
34
|
+
if success:
|
35
|
+
self.assert_response(
|
36
|
+
response,
|
37
|
+
message=message,
|
38
|
+
next_state="scan_destination",
|
39
|
+
data=self._line_data(line),
|
40
|
+
)
|
41
|
+
else:
|
42
|
+
self.assert_response(
|
43
|
+
response,
|
44
|
+
message=message,
|
45
|
+
next_state="change_pack_lot",
|
46
|
+
data=self._line_data(line),
|
47
|
+
)
|
48
|
+
return response
|
49
|
+
|
50
|
+
def test_change_pack_lot_change_pack_ok(self):
|
51
|
+
initial_package = self._create_package_in_location(
|
52
|
+
self.shelf1, [self.PackageContent(self.product_a, 10, lot=None)]
|
53
|
+
)
|
54
|
+
self._simulate_batch_selected(self.batch, fill_stock=False)
|
55
|
+
|
56
|
+
# ensure we have our new package in the same location
|
57
|
+
new_package = self._create_package_in_location(
|
58
|
+
self.shelf1, [self.PackageContent(self.product_a, 10, lot=None)]
|
59
|
+
)
|
60
|
+
|
61
|
+
line = self.batch.picking_ids.move_line_ids
|
62
|
+
self._test_change_pack_lot(
|
63
|
+
line,
|
64
|
+
new_package.name,
|
65
|
+
success=True,
|
66
|
+
message=self.service.msg_store.package_replaced_by_package(
|
67
|
+
initial_package, new_package
|
68
|
+
),
|
69
|
+
)
|
70
|
+
|
71
|
+
self.assertRecordValues(
|
72
|
+
line,
|
73
|
+
[
|
74
|
+
{
|
75
|
+
"package_id": new_package.id,
|
76
|
+
"result_package_id": new_package.id,
|
77
|
+
"reserved_qty": 10.0,
|
78
|
+
}
|
79
|
+
],
|
80
|
+
)
|
81
|
+
self.assertRecordValues(line.package_level_id, [{"package_id": new_package.id}])
|
82
|
+
|
83
|
+
def test_change_pack_lot_change_lot_ok(self):
|
84
|
+
initial_lot = self._create_lot(self.product_a)
|
85
|
+
self._update_qty_in_location(self.shelf1, self.product_a, 10, lot=initial_lot)
|
86
|
+
self._simulate_batch_selected(self.batch, fill_stock=False)
|
87
|
+
line = self.batch.picking_ids.move_line_ids
|
88
|
+
source_location = line.location_id
|
89
|
+
new_lot = self._create_lot(self.product_a)
|
90
|
+
# ensure we have our new package in the same location
|
91
|
+
self._update_qty_in_location(source_location, line.product_id, 10, lot=new_lot)
|
92
|
+
self._test_change_pack_lot(
|
93
|
+
line,
|
94
|
+
new_lot.name,
|
95
|
+
success=True,
|
96
|
+
message=self.service.msg_store.lot_replaced_by_lot(initial_lot, new_lot),
|
97
|
+
)
|
98
|
+
self.assertRecordValues(line, [{"lot_id": new_lot.id}])
|
99
|
+
|
100
|
+
def test_change_pack_lot_change_error(self):
|
101
|
+
initial_lot = self._create_lot(self.product_a)
|
102
|
+
self._update_qty_in_location(self.shelf1, self.product_a, 10, lot=initial_lot)
|
103
|
+
self._simulate_batch_selected(self.batch, fill_stock=False)
|
104
|
+
line = self.batch.picking_ids.move_line_ids
|
105
|
+
# ensure we have our new package in the same location
|
106
|
+
self._test_change_pack_lot(
|
107
|
+
line,
|
108
|
+
"NOT_FOUND",
|
109
|
+
success=False,
|
110
|
+
message=self.service.msg_store.no_package_or_lot_for_barcode("NOT_FOUND"),
|
111
|
+
)
|
@@ -0,0 +1,98 @@
|
|
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 ClusterPickingIsZeroCase(ClusterPickingCommonCase):
|
10
|
+
"""Tests covering the /is_zero endpoint
|
11
|
+
|
12
|
+
After a line has been scanned, if the location is empty, the
|
13
|
+
client application is redirected to the "zero_check" state,
|
14
|
+
where the user has to confirm or not that the location is empty.
|
15
|
+
When the location is empty, there is nothing to do, but when it
|
16
|
+
in fact not empty, a draft inventory must be created for the
|
17
|
+
product so someone can verify.
|
18
|
+
"""
|
19
|
+
|
20
|
+
@classmethod
|
21
|
+
def setUpClassBaseData(cls, *args, **kwargs):
|
22
|
+
super().setUpClassBaseData(*args, **kwargs)
|
23
|
+
cls.batch = cls._create_picking_batch(
|
24
|
+
[
|
25
|
+
[
|
26
|
+
cls.BatchProduct(product=cls.product_a, quantity=10),
|
27
|
+
cls.BatchProduct(product=cls.product_b, quantity=10),
|
28
|
+
]
|
29
|
+
]
|
30
|
+
)
|
31
|
+
cls.picking = cls.batch.picking_ids
|
32
|
+
cls._simulate_batch_selected(cls.batch)
|
33
|
+
|
34
|
+
cls.line = cls.picking.move_line_ids[0]
|
35
|
+
cls.next_line = cls.picking.move_line_ids[1]
|
36
|
+
cls.bin1 = cls.env["stock.quant.package"].create({})
|
37
|
+
cls._update_qty_in_location(
|
38
|
+
cls.line.location_id, cls.line.product_id, cls.line.reserved_uom_qty
|
39
|
+
)
|
40
|
+
# we already scan and put the first line in bin1, at this point the
|
41
|
+
# system see the location is empty and reach "zero_check"
|
42
|
+
cls._set_dest_package_and_done(cls.line, cls.bin1)
|
43
|
+
|
44
|
+
def test_is_zero_is_empty(self):
|
45
|
+
"""call /is_zero confirming it's empty"""
|
46
|
+
response = self.service.dispatch(
|
47
|
+
"is_zero",
|
48
|
+
params={
|
49
|
+
"picking_batch_id": self.batch.id,
|
50
|
+
"move_line_id": self.line.id,
|
51
|
+
"zero": True,
|
52
|
+
},
|
53
|
+
)
|
54
|
+
self.assert_response(
|
55
|
+
response,
|
56
|
+
next_state="start_line",
|
57
|
+
data=self._line_data(self.next_line),
|
58
|
+
message={
|
59
|
+
"message_type": "success",
|
60
|
+
"body": "{} {} put in {}".format(
|
61
|
+
self.line.qty_done,
|
62
|
+
self.line.product_id.display_name,
|
63
|
+
self.bin1.name,
|
64
|
+
),
|
65
|
+
},
|
66
|
+
)
|
67
|
+
|
68
|
+
def test_is_zero_is_not_empty(self):
|
69
|
+
"""call /is_zero not confirming it's empty"""
|
70
|
+
response = self.service.dispatch(
|
71
|
+
"is_zero",
|
72
|
+
params={
|
73
|
+
"picking_batch_id": self.batch.id,
|
74
|
+
"move_line_id": self.line.id,
|
75
|
+
"zero": False,
|
76
|
+
},
|
77
|
+
)
|
78
|
+
quant = self.env["stock.quant"].search(
|
79
|
+
[
|
80
|
+
("location_id", "=", self.line.location_id.id),
|
81
|
+
("product_id", "=", self.line.product_id.id),
|
82
|
+
("inventory_quantity_set", "=", True),
|
83
|
+
]
|
84
|
+
)
|
85
|
+
self.assertTrue(quant)
|
86
|
+
self.assert_response(
|
87
|
+
response,
|
88
|
+
next_state="start_line",
|
89
|
+
data=self._line_data(self.next_line),
|
90
|
+
message={
|
91
|
+
"message_type": "success",
|
92
|
+
"body": "{} {} put in {}".format(
|
93
|
+
self.line.qty_done,
|
94
|
+
self.line.product_id.display_name,
|
95
|
+
self.bin1.name,
|
96
|
+
),
|
97
|
+
},
|
98
|
+
)
|
@@ -0,0 +1,376 @@
|
|
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 ClusterPickingScanDestinationPackCase(ClusterPickingCommonCase):
|
10
|
+
"""Tests covering the /scan_destination_pack endpoint
|
11
|
+
|
12
|
+
After a batch has been selected and the user confirmed they are
|
13
|
+
working on it, user picked the good, now they scan the location
|
14
|
+
destination.
|
15
|
+
"""
|
16
|
+
|
17
|
+
@classmethod
|
18
|
+
def setUpClassBaseData(cls, *args, **kwargs):
|
19
|
+
super().setUpClassBaseData(*args, **kwargs)
|
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_ok(self):
|
42
|
+
"""Happy path for scan destination package
|
43
|
+
|
44
|
+
It sets the line in the pack for the full qty
|
45
|
+
"""
|
46
|
+
line = self.batch.picking_ids.move_line_ids[0]
|
47
|
+
next_line = self.batch.picking_ids.move_line_ids[1]
|
48
|
+
qty_done = line.reserved_uom_qty
|
49
|
+
response = self.service.dispatch(
|
50
|
+
"scan_destination_pack",
|
51
|
+
params={
|
52
|
+
"picking_batch_id": self.batch.id,
|
53
|
+
"move_line_id": line.id,
|
54
|
+
"barcode": self.bin1.name,
|
55
|
+
"quantity": qty_done,
|
56
|
+
},
|
57
|
+
)
|
58
|
+
self.assertRecordValues(
|
59
|
+
line, [{"qty_done": qty_done, "result_package_id": self.bin1.id}]
|
60
|
+
)
|
61
|
+
self.assert_response(
|
62
|
+
response,
|
63
|
+
next_state="start_line",
|
64
|
+
data=self._line_data(next_line),
|
65
|
+
message={
|
66
|
+
"message_type": "success",
|
67
|
+
"body": "{} {} put in {}".format(
|
68
|
+
line.qty_done, line.product_id.display_name, self.bin1.name
|
69
|
+
),
|
70
|
+
},
|
71
|
+
)
|
72
|
+
|
73
|
+
def test_scan_destination_pack_ok_last_line(self):
|
74
|
+
"""Happy path for scan destination package
|
75
|
+
|
76
|
+
It sets the line in the pack for the full qty
|
77
|
+
"""
|
78
|
+
self._set_dest_package_and_done(self.one_line_picking.move_line_ids, self.bin1)
|
79
|
+
self._set_dest_package_and_done(
|
80
|
+
self.two_lines_picking.move_line_ids[0], self.bin2
|
81
|
+
)
|
82
|
+
# this is the only remaining line to pick
|
83
|
+
line = self.two_lines_picking.move_line_ids[1]
|
84
|
+
qty_done = line.reserved_uom_qty
|
85
|
+
response = self.service.dispatch(
|
86
|
+
"scan_destination_pack",
|
87
|
+
params={
|
88
|
+
"picking_batch_id": self.batch.id,
|
89
|
+
"move_line_id": line.id,
|
90
|
+
"barcode": self.bin2.name,
|
91
|
+
"quantity": qty_done,
|
92
|
+
},
|
93
|
+
)
|
94
|
+
self.assertRecordValues(
|
95
|
+
line, [{"qty_done": qty_done, "result_package_id": self.bin2.id}]
|
96
|
+
)
|
97
|
+
data = self._data_for_batch(self.batch, self.packing_location)
|
98
|
+
self.assert_response(
|
99
|
+
response,
|
100
|
+
# they reach the same destination so next state unload_all
|
101
|
+
next_state="unload_all",
|
102
|
+
data=data,
|
103
|
+
)
|
104
|
+
|
105
|
+
def test_scan_destination_pack_not_empty_same_picking(self):
|
106
|
+
"""Scan a destination package with move lines of same picking"""
|
107
|
+
line1 = self.two_lines_picking.move_line_ids[0]
|
108
|
+
line2 = self.two_lines_picking.move_line_ids[1]
|
109
|
+
# we already scan and put the first line in bin1
|
110
|
+
self._set_dest_package_and_done(line1, self.bin1)
|
111
|
+
response = self.service.dispatch(
|
112
|
+
"scan_destination_pack",
|
113
|
+
params={
|
114
|
+
"picking_batch_id": self.batch.id,
|
115
|
+
"move_line_id": line2.id,
|
116
|
+
# this bin is used for the same picking, should be allowed
|
117
|
+
"barcode": self.bin1.name,
|
118
|
+
"quantity": line2.reserved_uom_qty,
|
119
|
+
},
|
120
|
+
)
|
121
|
+
self.assert_response(
|
122
|
+
response,
|
123
|
+
next_state="start_line",
|
124
|
+
# we did not pick this line, so it should go there
|
125
|
+
data=self._line_data(self.one_line_picking.move_line_ids),
|
126
|
+
message=self.ANY,
|
127
|
+
)
|
128
|
+
|
129
|
+
def test_scan_destination_pack_not_empty_different_picking(self):
|
130
|
+
"""Scan a destination package with move lines of other picking"""
|
131
|
+
# do as if the user already picked the first good (for another picking)
|
132
|
+
# and put it in bin1
|
133
|
+
self._set_dest_package_and_done(self.one_line_picking.move_line_ids, self.bin1)
|
134
|
+
line = self.two_lines_picking.move_line_ids[0]
|
135
|
+
response = self.service.dispatch(
|
136
|
+
"scan_destination_pack",
|
137
|
+
params={
|
138
|
+
"picking_batch_id": self.batch.id,
|
139
|
+
"move_line_id": line.id,
|
140
|
+
# this bin is used for the other picking
|
141
|
+
"barcode": self.bin1.name,
|
142
|
+
"quantity": line.reserved_uom_qty,
|
143
|
+
},
|
144
|
+
)
|
145
|
+
self.assertRecordValues(line, [{"qty_done": 0, "result_package_id": False}])
|
146
|
+
self.assert_response(
|
147
|
+
response,
|
148
|
+
next_state="scan_destination",
|
149
|
+
data=self._line_data(line, qty_done=10.0),
|
150
|
+
message={
|
151
|
+
"message_type": "error",
|
152
|
+
"body": "The destination bin {} is not empty,"
|
153
|
+
" please take another.".format(self.bin1.name),
|
154
|
+
},
|
155
|
+
)
|
156
|
+
|
157
|
+
def test_scan_destination_pack_not_empty_multi_pick_allowed(self):
|
158
|
+
"""Scan a destination package with move lines of other picking"""
|
159
|
+
# do as if the user already picked the first good (for another picking)
|
160
|
+
# and put it in bin1
|
161
|
+
self.menu.sudo().write(
|
162
|
+
{"unload_package_at_destination": True, "multiple_move_single_pack": True}
|
163
|
+
)
|
164
|
+
self._set_dest_package_and_done(self.one_line_picking.move_line_ids, self.bin1)
|
165
|
+
line = self.two_lines_picking.move_line_ids[0]
|
166
|
+
self.service.dispatch(
|
167
|
+
"scan_destination_pack",
|
168
|
+
params={
|
169
|
+
"picking_batch_id": self.batch.id,
|
170
|
+
"move_line_id": line.id,
|
171
|
+
# this bin is used for the other picking
|
172
|
+
"barcode": self.bin1.name,
|
173
|
+
"quantity": line.reserved_uom_qty,
|
174
|
+
},
|
175
|
+
)
|
176
|
+
# Since `multiple_move_single_pack` is enabled, assigning `bin` should be ok
|
177
|
+
new_line = self.two_lines_picking.move_line_ids - line
|
178
|
+
self.assertRecordValues(
|
179
|
+
line,
|
180
|
+
[
|
181
|
+
{
|
182
|
+
"qty_done": 10,
|
183
|
+
"result_package_id": self.bin1.id,
|
184
|
+
"reserved_uom_qty": 10,
|
185
|
+
}
|
186
|
+
],
|
187
|
+
)
|
188
|
+
self.assertRecordValues(
|
189
|
+
new_line,
|
190
|
+
[{"qty_done": 0, "result_package_id": False, "reserved_uom_qty": 10}],
|
191
|
+
)
|
192
|
+
|
193
|
+
def test_scan_destination_pack_bin_not_found(self):
|
194
|
+
"""Scan a destination package that do not exist"""
|
195
|
+
line = self.one_line_picking.move_line_ids
|
196
|
+
response = self.service.dispatch(
|
197
|
+
"scan_destination_pack",
|
198
|
+
params={
|
199
|
+
"picking_batch_id": self.batch.id,
|
200
|
+
"move_line_id": line.id,
|
201
|
+
# this bin is used for the other picking
|
202
|
+
"barcode": "⌿",
|
203
|
+
"quantity": line.reserved_uom_qty,
|
204
|
+
},
|
205
|
+
)
|
206
|
+
line_data = self._line_data(line)
|
207
|
+
line_data["qty_done"] = 10
|
208
|
+
self.assert_response(
|
209
|
+
response,
|
210
|
+
next_state="scan_destination",
|
211
|
+
data=line_data,
|
212
|
+
message={
|
213
|
+
"message_type": "error",
|
214
|
+
"body": "Bin {} doesn't exist".format("⌿"),
|
215
|
+
},
|
216
|
+
)
|
217
|
+
|
218
|
+
def test_scan_destination_pack_quantity_more(self):
|
219
|
+
"""Pick more units than expected"""
|
220
|
+
line = self.one_line_picking.move_line_ids
|
221
|
+
response = self.service.dispatch(
|
222
|
+
"scan_destination_pack",
|
223
|
+
params={
|
224
|
+
"picking_batch_id": self.batch.id,
|
225
|
+
"move_line_id": line.id,
|
226
|
+
"barcode": self.bin1.name,
|
227
|
+
"quantity": line.reserved_uom_qty + 1,
|
228
|
+
},
|
229
|
+
)
|
230
|
+
self.assert_response(
|
231
|
+
response,
|
232
|
+
next_state="scan_destination",
|
233
|
+
data=self._line_data(line, qty_done=11.0),
|
234
|
+
message={
|
235
|
+
"message_type": "error",
|
236
|
+
"body": "You must not pick more than {} units.".format(
|
237
|
+
line.reserved_uom_qty
|
238
|
+
),
|
239
|
+
},
|
240
|
+
)
|
241
|
+
|
242
|
+
def test_scan_destination_pack_quantity_less(self):
|
243
|
+
"""Pick less units than expected"""
|
244
|
+
line = self.one_line_picking.move_line_ids
|
245
|
+
quant = self.env["stock.quant"].search(
|
246
|
+
[
|
247
|
+
("location_id", "=", line.location_id.id),
|
248
|
+
("product_id", "=", line.product_id.id),
|
249
|
+
]
|
250
|
+
)
|
251
|
+
quant.ensure_one()
|
252
|
+
self.assertRecordValues(quant, [{"quantity": 40.0, "reserved_quantity": 20.0}])
|
253
|
+
|
254
|
+
# when we pick less quantity than expected, the line is split
|
255
|
+
# and the user is proposed to pick the next line for the remaining
|
256
|
+
# quantity
|
257
|
+
response = self.service.dispatch(
|
258
|
+
"scan_destination_pack",
|
259
|
+
params={
|
260
|
+
"picking_batch_id": self.batch.id,
|
261
|
+
"move_line_id": line.id,
|
262
|
+
"barcode": self.bin1.name,
|
263
|
+
"quantity": line.reserved_uom_qty - 3,
|
264
|
+
},
|
265
|
+
)
|
266
|
+
new_line = self.one_line_picking.move_line_ids - line
|
267
|
+
|
268
|
+
self.assert_response(
|
269
|
+
response,
|
270
|
+
next_state="start_line",
|
271
|
+
data=self._line_data(new_line),
|
272
|
+
message={
|
273
|
+
"message_type": "success",
|
274
|
+
"body": "{} {} put in {}".format(
|
275
|
+
line.qty_done, line.product_id.display_name, self.bin1.name
|
276
|
+
),
|
277
|
+
},
|
278
|
+
)
|
279
|
+
|
280
|
+
self.assertRecordValues(
|
281
|
+
line,
|
282
|
+
[{"qty_done": 7, "result_package_id": self.bin1.id, "reserved_uom_qty": 7}],
|
283
|
+
)
|
284
|
+
self.assertRecordValues(
|
285
|
+
new_line,
|
286
|
+
[{"qty_done": 0, "result_package_id": False, "reserved_uom_qty": 3}],
|
287
|
+
)
|
288
|
+
# the reserved quantity on the quant must stay the same
|
289
|
+
self.assertRecordValues(quant, [{"quantity": 40.0, "reserved_quantity": 20.0}])
|
290
|
+
|
291
|
+
def test_scan_destination_pack_zero_check_activated(self):
|
292
|
+
"""Location will be emptied, have to go to zero check"""
|
293
|
+
# ensure that the location used for the test will contain only what we want
|
294
|
+
self.zero_check_location = (
|
295
|
+
self.env["stock.location"]
|
296
|
+
.sudo()
|
297
|
+
.create(
|
298
|
+
{
|
299
|
+
"name": "ZeroCheck",
|
300
|
+
"location_id": self.stock_location.id,
|
301
|
+
"barcode": "ZEROCHECK",
|
302
|
+
}
|
303
|
+
)
|
304
|
+
)
|
305
|
+
line = self.one_line_picking.move_line_ids
|
306
|
+
location, product, qty = (
|
307
|
+
self.zero_check_location,
|
308
|
+
line.product_id,
|
309
|
+
line.reserved_uom_qty,
|
310
|
+
)
|
311
|
+
self.one_line_picking.do_unreserve()
|
312
|
+
|
313
|
+
# ensure we have activated the zero check
|
314
|
+
self.one_line_picking.picking_type_id.sudo().shopfloor_zero_check = True
|
315
|
+
# Update the quantity in the location to be equal to the line's
|
316
|
+
# so when scan_destination_pack sets the qty_done, the planned
|
317
|
+
# qty should be zero and trigger a zero check
|
318
|
+
self._update_qty_in_location(location, product, qty)
|
319
|
+
# Reserve goods (now the move line has the expected source location)
|
320
|
+
self.one_line_picking.move_ids.location_id = location
|
321
|
+
self.one_line_picking.action_assign()
|
322
|
+
line = self.one_line_picking.move_line_ids
|
323
|
+
response = self.service.dispatch(
|
324
|
+
"scan_destination_pack",
|
325
|
+
params={
|
326
|
+
"picking_batch_id": self.batch.id,
|
327
|
+
"move_line_id": line.id,
|
328
|
+
"barcode": self.bin1.name,
|
329
|
+
"quantity": line.reserved_uom_qty,
|
330
|
+
},
|
331
|
+
)
|
332
|
+
|
333
|
+
self.assert_response(
|
334
|
+
response,
|
335
|
+
next_state="zero_check",
|
336
|
+
data={
|
337
|
+
"id": line.id,
|
338
|
+
"location_src": self.data.location(line.location_id),
|
339
|
+
"batch": self.data.picking_batch(self.batch),
|
340
|
+
},
|
341
|
+
)
|
342
|
+
|
343
|
+
def test_scan_destination_pack_zero_check_disabled(self):
|
344
|
+
"""Location will be emptied, no zero check, continue"""
|
345
|
+
line = self.one_line_picking.move_line_ids
|
346
|
+
# ensure we have deactivated the zero check
|
347
|
+
self.one_line_picking.picking_type_id.sudo().shopfloor_zero_check = False
|
348
|
+
# Update the quantity in the location to be equal to the line's
|
349
|
+
# so when scan_destination_pack sets the qty_done, the planned
|
350
|
+
# qty should be zero and trigger a zero check
|
351
|
+
self._update_qty_in_location(
|
352
|
+
line.location_id, line.product_id, line.reserved_uom_qty
|
353
|
+
)
|
354
|
+
response = self.service.dispatch(
|
355
|
+
"scan_destination_pack",
|
356
|
+
params={
|
357
|
+
"picking_batch_id": self.batch.id,
|
358
|
+
"move_line_id": line.id,
|
359
|
+
"barcode": self.bin1.name,
|
360
|
+
"quantity": line.reserved_uom_qty,
|
361
|
+
},
|
362
|
+
)
|
363
|
+
|
364
|
+
next_line = self.two_lines_picking.move_line_ids[0]
|
365
|
+
# continue to the next one, no zero check
|
366
|
+
self.assert_response(
|
367
|
+
response,
|
368
|
+
next_state="start_line",
|
369
|
+
data=self._line_data(next_line),
|
370
|
+
message={
|
371
|
+
"message_type": "success",
|
372
|
+
"body": "{} {} put in {}".format(
|
373
|
+
line.qty_done, line.product_id.display_name, self.bin1.name
|
374
|
+
),
|
375
|
+
},
|
376
|
+
)
|