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,1074 @@
|
|
1
|
+
# Copyright 2020 Camptocamp SA
|
2
|
+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
3
|
+
|
4
|
+
from .test_location_content_transfer_base import LocationContentTransferCommonCase
|
5
|
+
|
6
|
+
# pylint: disable=missing-return
|
7
|
+
|
8
|
+
|
9
|
+
class LocationContentTransferSetDestinationXCase(LocationContentTransferCommonCase):
|
10
|
+
"""Tests for endpoint used from scan_destination
|
11
|
+
|
12
|
+
* /set_destination_package
|
13
|
+
* /set_destination_line
|
14
|
+
|
15
|
+
"""
|
16
|
+
|
17
|
+
# TODO see what can be common
|
18
|
+
@classmethod
|
19
|
+
def setUpClassBaseData(cls):
|
20
|
+
super().setUpClassBaseData()
|
21
|
+
products = cls.product_a + cls.product_b + cls.product_c + cls.product_d
|
22
|
+
for product in products:
|
23
|
+
cls.env["stock.putaway.rule"].sudo().create(
|
24
|
+
{
|
25
|
+
"product_id": product.id,
|
26
|
+
"location_in_id": cls.stock_location.id,
|
27
|
+
"location_out_id": cls.shelf1.id,
|
28
|
+
}
|
29
|
+
)
|
30
|
+
|
31
|
+
cls.picking1 = picking1 = cls._create_picking(
|
32
|
+
lines=[(cls.product_a, 10), (cls.product_b, 10)]
|
33
|
+
)
|
34
|
+
cls.picking2 = picking2 = cls._create_picking(
|
35
|
+
lines=[(cls.product_c, 10), (cls.product_d, 10)]
|
36
|
+
)
|
37
|
+
cls.pickings = picking1 | picking2
|
38
|
+
cls._fill_stock_for_moves(
|
39
|
+
picking1.move_ids, in_package=True, location=cls.content_loc
|
40
|
+
)
|
41
|
+
cls._fill_stock_for_moves(picking2.move_ids, location=cls.content_loc)
|
42
|
+
cls.pickings.action_assign()
|
43
|
+
cls._simulate_pickings_selected(cls.pickings)
|
44
|
+
cls.dest_location = (
|
45
|
+
cls.env["stock.location"]
|
46
|
+
.sudo()
|
47
|
+
.create(
|
48
|
+
{
|
49
|
+
"name": "Sub Shelf 1",
|
50
|
+
"barcode": "subshelf1",
|
51
|
+
"location_id": cls.shelf1.id,
|
52
|
+
}
|
53
|
+
)
|
54
|
+
)
|
55
|
+
cls.warehouse = cls.env.ref("stock.warehouse0")
|
56
|
+
|
57
|
+
def test_set_destination_package_wrong_parameters(self):
|
58
|
+
"""Wrong 'location' and 'package_level_id' parameters, redirect the
|
59
|
+
user to the 'start' screen.
|
60
|
+
"""
|
61
|
+
package_level = self.picking1.package_level_ids[0]
|
62
|
+
self._simulate_selected_move_line(package_level.move_line_ids)
|
63
|
+
response = self.service.dispatch(
|
64
|
+
"set_destination_package",
|
65
|
+
params={
|
66
|
+
"location_id": 1234567890, # Doesn't exist
|
67
|
+
"package_level_id": package_level.id,
|
68
|
+
"barcode": "TEST",
|
69
|
+
},
|
70
|
+
)
|
71
|
+
self.assert_response_start(
|
72
|
+
response, message=self.service.msg_store.record_not_found()
|
73
|
+
)
|
74
|
+
response = self.service.dispatch(
|
75
|
+
"set_destination_package",
|
76
|
+
params={
|
77
|
+
"location_id": self.content_loc.id,
|
78
|
+
"package_level_id": 1234567890, # Doesn't exist
|
79
|
+
"barcode": "TEST",
|
80
|
+
},
|
81
|
+
)
|
82
|
+
move_lines = self.service._find_transfer_move_lines(self.content_loc)
|
83
|
+
self.assert_response_start_single(
|
84
|
+
response,
|
85
|
+
move_lines.mapped("picking_id"),
|
86
|
+
)
|
87
|
+
|
88
|
+
def test_set_destination_package_dest_location_nok(self):
|
89
|
+
"""Scanned destination location not valid, redirect to 'scan_destination'."""
|
90
|
+
package_level = self.picking1.package_level_ids[0]
|
91
|
+
self._simulate_selected_move_line(package_level.move_line_ids)
|
92
|
+
# Unknown destination location
|
93
|
+
response = self.service.dispatch(
|
94
|
+
"set_destination_package",
|
95
|
+
params={
|
96
|
+
"location_id": self.content_loc.id,
|
97
|
+
"package_level_id": package_level.id,
|
98
|
+
"barcode": "UNKNOWN_LOCATION",
|
99
|
+
},
|
100
|
+
)
|
101
|
+
self.assert_response_scan_destination(
|
102
|
+
response,
|
103
|
+
package_level,
|
104
|
+
message=self.service.msg_store.no_location_found(),
|
105
|
+
)
|
106
|
+
# Destination location not allowed
|
107
|
+
customer_location = self.env.ref("stock.stock_location_customers")
|
108
|
+
customer_location.sudo().barcode = "CUSTOMER"
|
109
|
+
response = self.service.dispatch(
|
110
|
+
"set_destination_package",
|
111
|
+
params={
|
112
|
+
"location_id": self.content_loc.id,
|
113
|
+
"package_level_id": package_level.id,
|
114
|
+
"barcode": customer_location.barcode,
|
115
|
+
},
|
116
|
+
)
|
117
|
+
self.assert_response_scan_destination(
|
118
|
+
response,
|
119
|
+
package_level,
|
120
|
+
message=self.service.msg_store.dest_location_not_allowed(),
|
121
|
+
)
|
122
|
+
|
123
|
+
def test_set_destination_package_dest_location_move_nok(self):
|
124
|
+
"""Scanned destination location not valid (different as move and picking)"""
|
125
|
+
package_level = self.picking1.package_level_ids[0]
|
126
|
+
# if the move related to the package level has a destination
|
127
|
+
# location not a parent or equal to the scanned location,
|
128
|
+
# refuse the action
|
129
|
+
move = package_level.move_line_ids.move_id
|
130
|
+
move.location_dest_id = self.shelf1
|
131
|
+
move.picking_id.location_dest_id = self.shelf1
|
132
|
+
self._simulate_selected_move_line(package_level.move_line_ids)
|
133
|
+
response = self.service.dispatch(
|
134
|
+
"set_destination_package",
|
135
|
+
params={
|
136
|
+
"location_id": self.content_loc.id,
|
137
|
+
"package_level_id": package_level.id,
|
138
|
+
"barcode": self.shelf2.barcode,
|
139
|
+
},
|
140
|
+
)
|
141
|
+
self.assert_response_scan_destination(
|
142
|
+
response,
|
143
|
+
package_level,
|
144
|
+
message=self.service.msg_store.dest_location_not_allowed(),
|
145
|
+
)
|
146
|
+
|
147
|
+
def test_set_destination_package_dest_location_to_confirm(self):
|
148
|
+
"""Scanned destination location valid, but need a confirmation."""
|
149
|
+
package_level = self.picking1.package_level_ids[0]
|
150
|
+
self._simulate_selected_move_line(package_level.move_line_ids)
|
151
|
+
response = self.service.dispatch(
|
152
|
+
"set_destination_package",
|
153
|
+
params={
|
154
|
+
"location_id": self.content_loc.id,
|
155
|
+
"package_level_id": package_level.id,
|
156
|
+
"barcode": self.env.ref("stock.stock_location_14").barcode,
|
157
|
+
},
|
158
|
+
)
|
159
|
+
self.assert_response_scan_destination(
|
160
|
+
response,
|
161
|
+
package_level,
|
162
|
+
message=self.service.msg_store.need_confirmation(),
|
163
|
+
confirmation_required=True,
|
164
|
+
)
|
165
|
+
|
166
|
+
def test_set_destination_package_dest_location_ok(self):
|
167
|
+
"""Scanned destination location valid, moves set to done."""
|
168
|
+
original_picking = self.picking1
|
169
|
+
package_level = original_picking.package_level_ids[0]
|
170
|
+
self._simulate_selected_move_line(package_level.move_line_ids)
|
171
|
+
response = self.service.dispatch(
|
172
|
+
"set_destination_package",
|
173
|
+
params={
|
174
|
+
"location_id": self.content_loc.id,
|
175
|
+
"package_level_id": package_level.id,
|
176
|
+
"barcode": self.dest_location.barcode,
|
177
|
+
},
|
178
|
+
)
|
179
|
+
# Check the data (the whole transfer has been validated here w/o backorder)
|
180
|
+
self.assertFalse(original_picking.backorder_ids)
|
181
|
+
self.assertEqual(original_picking.state, "done")
|
182
|
+
self.assertEqual(package_level.state, "done")
|
183
|
+
# Check the response
|
184
|
+
move_lines = self.service._find_transfer_move_lines(self.content_loc)
|
185
|
+
self.assert_response_start_single(
|
186
|
+
response,
|
187
|
+
move_lines.mapped("picking_id"),
|
188
|
+
message=self.service.msg_store.location_content_transfer_item_complete(
|
189
|
+
self.dest_location
|
190
|
+
),
|
191
|
+
)
|
192
|
+
for move in package_level.move_line_ids.mapped("move_id"):
|
193
|
+
self.assertEqual(move.state, "done")
|
194
|
+
|
195
|
+
def test_set_destination_package_dest_location_ok_with_completion_info(self):
|
196
|
+
"""Scanned destination location valid, moves set to done
|
197
|
+
and completion info is returned as the next transfer is ready.
|
198
|
+
"""
|
199
|
+
original_picking = self.picking1
|
200
|
+
package_level = original_picking.package_level_ids[0]
|
201
|
+
move = package_level.move_line_ids.move_id[0]
|
202
|
+
next_move = move.copy(
|
203
|
+
{
|
204
|
+
"picking_id": False,
|
205
|
+
"picking_type_id": self.warehouse.out_type_id.id,
|
206
|
+
"location_id": move.location_dest_id.id,
|
207
|
+
"location_dest_id": self.customer_location.id,
|
208
|
+
"move_orig_ids": [(6, 0, move.ids)],
|
209
|
+
}
|
210
|
+
)
|
211
|
+
next_move._action_confirm(merge=False)
|
212
|
+
next_move._assign_picking()
|
213
|
+
self.assertEqual(next_move.state, "waiting")
|
214
|
+
self.assertTrue(next_move.picking_id)
|
215
|
+
self._simulate_selected_move_line(package_level.move_line_ids)
|
216
|
+
response = self.service.dispatch(
|
217
|
+
"set_destination_package",
|
218
|
+
params={
|
219
|
+
"location_id": self.content_loc.id,
|
220
|
+
"package_level_id": package_level.id,
|
221
|
+
"barcode": self.dest_location.barcode,
|
222
|
+
},
|
223
|
+
)
|
224
|
+
# Check the data (the whole transfer has been validated here w/o backorder)
|
225
|
+
self.assertFalse(original_picking.backorder_ids)
|
226
|
+
self.assertEqual(original_picking.state, "done")
|
227
|
+
self.assertEqual(package_level.state, "done")
|
228
|
+
self.assertEqual(next_move.state, "assigned")
|
229
|
+
# Check the response
|
230
|
+
move_lines = self.service._find_transfer_move_lines(self.content_loc)
|
231
|
+
completion_info = self.service._actions_for("completion.info")
|
232
|
+
completion_info_popup = completion_info.popup(package_level.move_line_ids)
|
233
|
+
self.assert_response_start_single(
|
234
|
+
response,
|
235
|
+
move_lines.mapped("picking_id"),
|
236
|
+
message=self.service.msg_store.location_content_transfer_item_complete(
|
237
|
+
self.dest_location
|
238
|
+
),
|
239
|
+
popup=completion_info_popup,
|
240
|
+
)
|
241
|
+
for move in package_level.move_line_ids.mapped("move_id"):
|
242
|
+
self.assertEqual(move.state, "done")
|
243
|
+
|
244
|
+
def test_set_destination_line_wrong_parameters(self):
|
245
|
+
"""Wrong 'location' and 'move_line_id' parameters, redirect the
|
246
|
+
user to the 'start' screen.
|
247
|
+
"""
|
248
|
+
move_line = self.picking2.move_line_ids[0]
|
249
|
+
self._simulate_selected_move_line(move_line)
|
250
|
+
response = self.service.dispatch(
|
251
|
+
"set_destination_line",
|
252
|
+
params={
|
253
|
+
"location_id": 1234567890, # Doesn't exist
|
254
|
+
"move_line_id": move_line.id,
|
255
|
+
"quantity": move_line.reserved_uom_qty,
|
256
|
+
"barcode": "TEST",
|
257
|
+
},
|
258
|
+
)
|
259
|
+
self.assert_response_start(
|
260
|
+
response, message=self.service.msg_store.record_not_found()
|
261
|
+
)
|
262
|
+
response = self.service.dispatch(
|
263
|
+
"set_destination_line",
|
264
|
+
params={
|
265
|
+
"location_id": self.content_loc.id,
|
266
|
+
"move_line_id": 1234567890, # Doesn't exist
|
267
|
+
"quantity": move_line.reserved_uom_qty,
|
268
|
+
"barcode": "TEST",
|
269
|
+
},
|
270
|
+
)
|
271
|
+
move_lines = self.service._find_transfer_move_lines(self.content_loc)
|
272
|
+
self.assert_response_start_single(
|
273
|
+
response,
|
274
|
+
move_lines.mapped("picking_id"),
|
275
|
+
)
|
276
|
+
|
277
|
+
def test_set_destination_line_dest_location_nok(self):
|
278
|
+
"""Scanned destination location not valid, redirect to 'scan_destination'."""
|
279
|
+
move_line = self.picking2.move_line_ids[0]
|
280
|
+
self._simulate_selected_move_line(move_line)
|
281
|
+
# Unknown destination location
|
282
|
+
response = self.service.dispatch(
|
283
|
+
"set_destination_line",
|
284
|
+
params={
|
285
|
+
"location_id": self.content_loc.id,
|
286
|
+
"move_line_id": move_line.id,
|
287
|
+
"quantity": move_line.reserved_uom_qty,
|
288
|
+
"barcode": "UNKNOWN_LOCATION",
|
289
|
+
},
|
290
|
+
)
|
291
|
+
self.assert_response_scan_destination(
|
292
|
+
response,
|
293
|
+
move_line,
|
294
|
+
message=self.service.msg_store.no_location_found(),
|
295
|
+
)
|
296
|
+
# Destination location not allowed
|
297
|
+
customer_location = self.env.ref("stock.stock_location_customers")
|
298
|
+
customer_location.sudo().barcode = "CUSTOMER"
|
299
|
+
response = self.service.dispatch(
|
300
|
+
"set_destination_line",
|
301
|
+
params={
|
302
|
+
"location_id": self.content_loc.id,
|
303
|
+
"move_line_id": move_line.id,
|
304
|
+
"quantity": move_line.reserved_uom_qty,
|
305
|
+
"barcode": customer_location.barcode,
|
306
|
+
},
|
307
|
+
)
|
308
|
+
self.assert_response_scan_destination(
|
309
|
+
response,
|
310
|
+
move_line,
|
311
|
+
message=self.service.msg_store.dest_location_not_allowed(),
|
312
|
+
)
|
313
|
+
|
314
|
+
def test_set_destination_line_dest_location_move_nok(self):
|
315
|
+
"""Scanned destination location not valid (different as picking and move)"""
|
316
|
+
move_line = self.picking2.move_line_ids[0]
|
317
|
+
# if the move related to the move line has a destination
|
318
|
+
# location not a parent or equal to the scanned location,
|
319
|
+
# refuse the action
|
320
|
+
move_line.move_id.location_dest_id = self.shelf1
|
321
|
+
move_line.picking_id.location_dest_id = self.shelf1
|
322
|
+
self._simulate_selected_move_line(move_line)
|
323
|
+
response = self.service.dispatch(
|
324
|
+
"set_destination_line",
|
325
|
+
params={
|
326
|
+
"location_id": self.content_loc.id,
|
327
|
+
"move_line_id": move_line.id,
|
328
|
+
"quantity": move_line.reserved_uom_qty,
|
329
|
+
"barcode": self.shelf2.barcode,
|
330
|
+
},
|
331
|
+
)
|
332
|
+
self.assert_response_scan_destination(
|
333
|
+
response,
|
334
|
+
move_line,
|
335
|
+
message=self.service.msg_store.dest_location_not_allowed(),
|
336
|
+
)
|
337
|
+
|
338
|
+
def test_set_destination_line_dest_location_to_confirm(self):
|
339
|
+
"""Scanned destination location valid, but need a confirmation."""
|
340
|
+
move_line = self.picking2.move_line_ids[0]
|
341
|
+
self._simulate_selected_move_line(move_line)
|
342
|
+
response = self.service.dispatch(
|
343
|
+
"set_destination_line",
|
344
|
+
params={
|
345
|
+
"location_id": self.content_loc.id,
|
346
|
+
"move_line_id": move_line.id,
|
347
|
+
"quantity": move_line.reserved_uom_qty,
|
348
|
+
"barcode": self.env.ref("stock.stock_location_14").barcode,
|
349
|
+
},
|
350
|
+
)
|
351
|
+
self.assert_response_scan_destination(
|
352
|
+
response,
|
353
|
+
move_line,
|
354
|
+
message=self.service.msg_store.need_confirmation(),
|
355
|
+
confirmation_required=True,
|
356
|
+
)
|
357
|
+
|
358
|
+
def test_set_destination_line_dest_location_ok(self):
|
359
|
+
"""Scanned destination location valid, moves set to done."""
|
360
|
+
original_picking = self.picking2
|
361
|
+
move_line = original_picking.move_line_ids[0]
|
362
|
+
self._simulate_selected_move_line(move_line)
|
363
|
+
response = self.service.dispatch(
|
364
|
+
"set_destination_line",
|
365
|
+
params={
|
366
|
+
"location_id": self.content_loc.id,
|
367
|
+
"move_line_id": move_line.id,
|
368
|
+
"quantity": move_line.reserved_uom_qty,
|
369
|
+
"barcode": self.dest_location.barcode,
|
370
|
+
},
|
371
|
+
)
|
372
|
+
# Check the resulting data
|
373
|
+
# We got a new picking as the original one had two moves (and we
|
374
|
+
# validated only one)
|
375
|
+
new_picking = move_line.picking_id
|
376
|
+
self.assertTrue(new_picking != original_picking)
|
377
|
+
self.assertEqual(move_line.move_id.state, "done")
|
378
|
+
self.assertEqual(move_line.picking_id.state, "done")
|
379
|
+
self.assertEqual(original_picking.state, "assigned")
|
380
|
+
# Check the response
|
381
|
+
move_lines = self.service._find_transfer_move_lines(self.content_loc)
|
382
|
+
self.assert_response_start_single(
|
383
|
+
response,
|
384
|
+
move_lines.mapped("picking_id"),
|
385
|
+
message=self.service.msg_store.location_content_transfer_item_complete(
|
386
|
+
self.dest_location
|
387
|
+
),
|
388
|
+
)
|
389
|
+
|
390
|
+
def test_set_destination_line_dest_location_ok_with_completion_info(self):
|
391
|
+
"""Scanned destination location valid, moves set to done
|
392
|
+
and completion info is returned as the next transfer is ready.
|
393
|
+
"""
|
394
|
+
original_picking = self.picking2
|
395
|
+
move_line = original_picking.move_line_ids[0]
|
396
|
+
move = move_line.move_id
|
397
|
+
next_move = move.copy(
|
398
|
+
{
|
399
|
+
"picking_id": False,
|
400
|
+
"picking_type_id": self.warehouse.out_type_id.id,
|
401
|
+
"location_id": move.location_dest_id.id,
|
402
|
+
"location_dest_id": self.customer_location.id,
|
403
|
+
"move_orig_ids": [(6, 0, move.ids)],
|
404
|
+
}
|
405
|
+
)
|
406
|
+
next_move._action_confirm(merge=False)
|
407
|
+
next_move._assign_picking()
|
408
|
+
self.assertEqual(next_move.state, "waiting")
|
409
|
+
self.assertTrue(next_move.picking_id)
|
410
|
+
self._simulate_selected_move_line(move_line)
|
411
|
+
response = self.service.dispatch(
|
412
|
+
"set_destination_line",
|
413
|
+
params={
|
414
|
+
"location_id": self.content_loc.id,
|
415
|
+
"move_line_id": move_line.id,
|
416
|
+
"quantity": move_line.reserved_uom_qty,
|
417
|
+
"barcode": self.dest_location.barcode,
|
418
|
+
},
|
419
|
+
)
|
420
|
+
# Check the resulting data
|
421
|
+
# We got a new picking as the original one had two moves (and we
|
422
|
+
# validated only one)
|
423
|
+
new_picking = move_line.picking_id
|
424
|
+
self.assertTrue(new_picking != original_picking)
|
425
|
+
self.assertEqual(move_line.move_id.state, "done")
|
426
|
+
self.assertEqual(move_line.picking_id.state, "done")
|
427
|
+
self.assertEqual(original_picking.state, "assigned")
|
428
|
+
self.assertEqual(next_move.state, "assigned")
|
429
|
+
# Check the response
|
430
|
+
move_lines = self.service._find_transfer_move_lines(self.content_loc)
|
431
|
+
completion_info = self.service._actions_for("completion.info")
|
432
|
+
completion_info_popup = completion_info.popup(move_line)
|
433
|
+
self.assert_response_start_single(
|
434
|
+
response,
|
435
|
+
move_lines.mapped("picking_id"),
|
436
|
+
message=self.service.msg_store.location_content_transfer_item_complete(
|
437
|
+
self.dest_location
|
438
|
+
),
|
439
|
+
popup=completion_info_popup,
|
440
|
+
)
|
441
|
+
|
442
|
+
def test_set_destination_line_partial_qty(self):
|
443
|
+
"""Scanned destination location with partial qty, but related moves
|
444
|
+
has to be splitted.
|
445
|
+
"""
|
446
|
+
original_picking = self.picking2
|
447
|
+
move_line_c = original_picking.move_line_ids.filtered(
|
448
|
+
lambda m: m.product_id == self.product_c
|
449
|
+
)
|
450
|
+
self.assertEqual(move_line_c.reserved_uom_qty, 10)
|
451
|
+
self.assertEqual(move_line_c.qty_done, 10)
|
452
|
+
self._simulate_selected_move_line(move_line_c)
|
453
|
+
# Scan partial qty (6/10)
|
454
|
+
response = self.service.dispatch(
|
455
|
+
"set_destination_line",
|
456
|
+
params={
|
457
|
+
"location_id": self.content_loc.id,
|
458
|
+
"move_line_id": move_line_c.id,
|
459
|
+
"quantity": move_line_c.reserved_uom_qty - 4, # Scan 6 qty
|
460
|
+
"barcode": self.dest_location.barcode,
|
461
|
+
},
|
462
|
+
)
|
463
|
+
done_picking = original_picking.backorder_ids
|
464
|
+
# Check move line data
|
465
|
+
self.assertEqual(move_line_c.move_id.product_uom_qty, 6)
|
466
|
+
self.assertEqual(move_line_c.reserved_uom_qty, 0)
|
467
|
+
self.assertEqual(move_line_c.qty_done, 6)
|
468
|
+
self.assertEqual(move_line_c.state, "done")
|
469
|
+
self.assertEqual(original_picking.backorder_ids, done_picking)
|
470
|
+
self.assertEqual(done_picking.state, "done")
|
471
|
+
|
472
|
+
# the remaining move is put in a backorder
|
473
|
+
move = done_picking.backorder_ids.move_ids
|
474
|
+
self.assertEqual(move.picking_id.state, "assigned")
|
475
|
+
|
476
|
+
self.assertEqual(move.state, "assigned")
|
477
|
+
self.assertEqual(move.product_id, self.product_c)
|
478
|
+
self.assertEqual(move.product_uom_qty, 4)
|
479
|
+
self.assertEqual(move.move_line_ids.reserved_uom_qty, 4)
|
480
|
+
self.assertEqual(move.move_line_ids.qty_done, 4)
|
481
|
+
# Check the response -> we must first process the backorder
|
482
|
+
self.assert_response_start_single(
|
483
|
+
response,
|
484
|
+
done_picking.backorder_ids,
|
485
|
+
message=self.service.msg_store.location_content_transfer_item_complete(
|
486
|
+
self.dest_location
|
487
|
+
),
|
488
|
+
)
|
489
|
+
self.assertEqual(move_line_c.move_id.state, "done")
|
490
|
+
# Scan remaining qty (4/10)
|
491
|
+
remaining_move_line_c = move.move_line_ids
|
492
|
+
self._simulate_selected_move_line(remaining_move_line_c)
|
493
|
+
self.service.dispatch(
|
494
|
+
"set_destination_line",
|
495
|
+
params={
|
496
|
+
"location_id": self.content_loc.id,
|
497
|
+
"move_line_id": remaining_move_line_c.id,
|
498
|
+
"quantity": remaining_move_line_c.reserved_uom_qty,
|
499
|
+
"barcode": self.dest_location.barcode,
|
500
|
+
},
|
501
|
+
)
|
502
|
+
done_picking2 = remaining_move_line_c.picking_id
|
503
|
+
# Check move line data
|
504
|
+
self.assertEqual(remaining_move_line_c.move_id.product_uom_qty, 4)
|
505
|
+
self.assertEqual(remaining_move_line_c.reserved_uom_qty, 0)
|
506
|
+
self.assertEqual(remaining_move_line_c.qty_done, 4)
|
507
|
+
self.assertEqual(remaining_move_line_c.state, "done")
|
508
|
+
self.assertTrue(done_picking2 != original_picking)
|
509
|
+
self.assertEqual(done_picking2.state, "done")
|
510
|
+
# All move lines related to product_c are now done and extracted from
|
511
|
+
# the initial transfer
|
512
|
+
all_pickings = original_picking | done_picking | done_picking2
|
513
|
+
moves_product_c = all_pickings.move_ids.filtered(
|
514
|
+
lambda m: m.product_id == self.product_c
|
515
|
+
)
|
516
|
+
moves_product_c_done = all(move.state == "done" for move in moves_product_c)
|
517
|
+
self.assertTrue(moves_product_c_done)
|
518
|
+
moves_product_c_qty_done = sum([move.quantity_done for move in moves_product_c])
|
519
|
+
self.assertEqual(moves_product_c_qty_done, 10)
|
520
|
+
# The picking is still not done as product_d hasn't been processed
|
521
|
+
self.assertEqual(original_picking.state, "assigned")
|
522
|
+
# Let scan product_d quantity and check picking state
|
523
|
+
move_line_d = original_picking.move_line_ids.filtered(
|
524
|
+
lambda m: m.product_id == self.product_d
|
525
|
+
)
|
526
|
+
self._simulate_selected_move_line(move_line_d)
|
527
|
+
self.service.dispatch(
|
528
|
+
"set_destination_line",
|
529
|
+
params={
|
530
|
+
"location_id": self.content_loc.id,
|
531
|
+
"move_line_id": move_line_d.id,
|
532
|
+
"quantity": move_line_d.reserved_uom_qty,
|
533
|
+
"barcode": self.dest_location.barcode,
|
534
|
+
},
|
535
|
+
)
|
536
|
+
self.assertEqual(move_line_d.move_id.product_uom_qty, 10)
|
537
|
+
self.assertEqual(move_line_d.reserved_uom_qty, 0)
|
538
|
+
self.assertEqual(move_line_d.qty_done, 10)
|
539
|
+
self.assertEqual(move_line_d.state, "done")
|
540
|
+
self.assertEqual(original_picking.state, "done")
|
541
|
+
|
542
|
+
def test_set_destination_line_partial_qty_with_backorder_policy(self):
|
543
|
+
"""Scanned destination location with partial qty, but related moves
|
544
|
+
has to be splitted. Since the backorder policy is 'never', the
|
545
|
+
remaining move line should be removed.
|
546
|
+
"""
|
547
|
+
# set the backorder policy to 'never'
|
548
|
+
|
549
|
+
picking = self._create_picking(lines=[(self.product_a, 10)])
|
550
|
+
picking.picking_type_id.sudo().create_backorder = "never"
|
551
|
+
self._update_qty_in_location(picking.location_id, self.product_a, 20)
|
552
|
+
# Reserve quantities
|
553
|
+
picking.action_assign()
|
554
|
+
self._simulate_pickings_selected(picking)
|
555
|
+
move_line = picking.move_line_ids[0]
|
556
|
+
self._simulate_selected_move_line(move_line)
|
557
|
+
# Scan partial qty (6/10)
|
558
|
+
self.service.dispatch(
|
559
|
+
"set_destination_line",
|
560
|
+
params={
|
561
|
+
"location_id": self.content_loc.id,
|
562
|
+
"move_line_id": move_line.id,
|
563
|
+
"quantity": move_line.reserved_uom_qty - 4, # Scan 6 qty
|
564
|
+
"barcode": self.dest_location.barcode,
|
565
|
+
},
|
566
|
+
)
|
567
|
+
done_picking = picking
|
568
|
+
# Check move line data
|
569
|
+
self.assertEqual(move_line.move_id.product_uom_qty, 6)
|
570
|
+
self.assertEqual(move_line.reserved_uom_qty, 0)
|
571
|
+
self.assertEqual(move_line.qty_done, 6)
|
572
|
+
self.assertEqual(move_line.state, "done")
|
573
|
+
self.assertEqual(done_picking.state, "done")
|
574
|
+
|
575
|
+
# no remaining move should exist
|
576
|
+
self.assertFalse(done_picking.backorder_ids.move_ids)
|
577
|
+
|
578
|
+
def test_set_destination_lines_partial_qty_with_backorder_policy(self):
|
579
|
+
"""Scanned destination location with partial qty, but related moves
|
580
|
+
has to be splitted. Since the backorder policy is 'never', the
|
581
|
+
remaining move line should be removed.
|
582
|
+
|
583
|
+
# multi lines mode
|
584
|
+
"""
|
585
|
+
# set the backorder policy to 'never'
|
586
|
+
|
587
|
+
picking = self._create_picking(
|
588
|
+
lines=[(self.product_a, 10), (self.product_b, 10)]
|
589
|
+
)
|
590
|
+
picking.picking_type_id.sudo().create_backorder = "never"
|
591
|
+
self._update_qty_in_location(picking.location_id, self.product_a, 20)
|
592
|
+
self._update_qty_in_location(picking.location_id, self.product_b, 20)
|
593
|
+
# Reserve quantities
|
594
|
+
picking.action_assign()
|
595
|
+
self._simulate_pickings_selected(picking)
|
596
|
+
move_line = picking.move_line_ids.filtered(
|
597
|
+
lambda ml: ml.product_id == self.product_a
|
598
|
+
)
|
599
|
+
self._simulate_selected_move_line(move_line)
|
600
|
+
# Scan partial qty (6/10)
|
601
|
+
self.service.dispatch(
|
602
|
+
"set_destination_line",
|
603
|
+
params={
|
604
|
+
"location_id": self.content_loc.id,
|
605
|
+
"move_line_id": move_line.id,
|
606
|
+
"quantity": move_line.reserved_uom_qty - 4, # Scan 6 qty
|
607
|
+
"barcode": self.dest_location.barcode,
|
608
|
+
},
|
609
|
+
)
|
610
|
+
# 2 operations then the done operation is set into a specific picking
|
611
|
+
first_done_picking = picking.backorder_ids
|
612
|
+
# Check move line data
|
613
|
+
self.assertEqual(move_line.move_id.product_uom_qty, 6)
|
614
|
+
self.assertEqual(move_line.reserved_uom_qty, 0)
|
615
|
+
self.assertEqual(move_line.qty_done, 6)
|
616
|
+
self.assertEqual(move_line.state, "done")
|
617
|
+
self.assertEqual(first_done_picking.state, "done")
|
618
|
+
|
619
|
+
# no remaining move should exist
|
620
|
+
self.assertFalse(first_done_picking.backorder_ids.move_ids)
|
621
|
+
|
622
|
+
# process the second line
|
623
|
+
move_line = picking.move_line_ids.filtered(
|
624
|
+
lambda ml: ml.product_id == self.product_b
|
625
|
+
)
|
626
|
+
self._simulate_selected_move_line(move_line)
|
627
|
+
# Scan partial qty (6/10)
|
628
|
+
self.service.dispatch(
|
629
|
+
"set_destination_line",
|
630
|
+
params={
|
631
|
+
"location_id": self.content_loc.id,
|
632
|
+
"move_line_id": move_line.id,
|
633
|
+
"quantity": move_line.reserved_uom_qty - 4, # Scan 6 qty
|
634
|
+
"barcode": self.dest_location.barcode,
|
635
|
+
},
|
636
|
+
)
|
637
|
+
|
638
|
+
# the initial picking should be done
|
639
|
+
# Check move line data
|
640
|
+
self.assertEqual(move_line.move_id.product_uom_qty, 6)
|
641
|
+
self.assertEqual(move_line.reserved_uom_qty, 0)
|
642
|
+
self.assertEqual(move_line.qty_done, 6)
|
643
|
+
self.assertEqual(move_line.state, "done")
|
644
|
+
self.assertEqual(picking.state, "done")
|
645
|
+
|
646
|
+
# no remaining move should exist
|
647
|
+
self.assertEqual(picking.backorder_ids, first_done_picking)
|
648
|
+
|
649
|
+
|
650
|
+
class LocationContentTransferSetDestinationXSpecialCase(
|
651
|
+
LocationContentTransferCommonCase
|
652
|
+
):
|
653
|
+
"""Tests for endpoint used from scan_destination (special cases)
|
654
|
+
|
655
|
+
* /set_destination_package
|
656
|
+
* /set_destination_line
|
657
|
+
|
658
|
+
"""
|
659
|
+
|
660
|
+
@classmethod
|
661
|
+
def setUpClassBaseData(cls):
|
662
|
+
super().setUpClassBaseData()
|
663
|
+
products = cls.product_a
|
664
|
+
for product in products:
|
665
|
+
cls.env["stock.putaway.rule"].sudo().create(
|
666
|
+
{
|
667
|
+
"product_id": product.id,
|
668
|
+
"location_in_id": cls.stock_location.id,
|
669
|
+
"location_out_id": cls.shelf1.id,
|
670
|
+
}
|
671
|
+
)
|
672
|
+
|
673
|
+
cls.picking = cls._create_picking(
|
674
|
+
lines=[(cls.product_a, 10), (cls.product_b, 10)]
|
675
|
+
)
|
676
|
+
cls.move_product_a = cls.picking.move_ids.filtered(
|
677
|
+
lambda m: m.product_id == cls.product_a
|
678
|
+
)
|
679
|
+
cls.move_product_b = cls.picking.move_ids.filtered(
|
680
|
+
lambda m: m.product_id == cls.product_b
|
681
|
+
)
|
682
|
+
# Change the initial demand of product_a to get two move lines for
|
683
|
+
# reserved qties:
|
684
|
+
# - 10 from the package
|
685
|
+
# - 5 from the qty without package
|
686
|
+
cls._fill_stock_for_moves(
|
687
|
+
cls.move_product_a, in_package=True, location=cls.content_loc
|
688
|
+
)
|
689
|
+
cls.move_product_a.product_uom_qty = 15
|
690
|
+
cls._update_qty_in_location(
|
691
|
+
cls.picking.location_id,
|
692
|
+
cls.product_a,
|
693
|
+
5,
|
694
|
+
)
|
695
|
+
# Put product_b quantities in two different source locations to get
|
696
|
+
# two stock move lines (6 and 4 to satisfy 10 qties)
|
697
|
+
cls._update_qty_in_location(cls.picking.location_id, cls.product_b, 6)
|
698
|
+
cls._update_qty_in_location(cls.content_loc, cls.product_b, 4)
|
699
|
+
# Reserve quantities
|
700
|
+
cls.picking.action_assign()
|
701
|
+
cls._simulate_pickings_selected(cls.picking)
|
702
|
+
cls.dest_location = (
|
703
|
+
cls.env["stock.location"]
|
704
|
+
.sudo()
|
705
|
+
.create(
|
706
|
+
{
|
707
|
+
"name": "Sub Shelf 1",
|
708
|
+
"barcode": "subshelf1",
|
709
|
+
"location_id": cls.shelf1.id,
|
710
|
+
}
|
711
|
+
)
|
712
|
+
)
|
713
|
+
|
714
|
+
def test_set_destination_package_split_move(self):
|
715
|
+
"""Scanned destination location valid for a package, but related moves
|
716
|
+
has to be splitted because it is linked to additional move lines.
|
717
|
+
"""
|
718
|
+
original_picking = self.picking
|
719
|
+
self.assertEqual(len(original_picking.move_ids), 2)
|
720
|
+
self.assertEqual(len(self.move_product_a.move_line_ids), 2)
|
721
|
+
package_level = original_picking.package_level_ids[0]
|
722
|
+
self._simulate_selected_move_line(package_level.move_line_ids)
|
723
|
+
response = self.service.dispatch(
|
724
|
+
"set_destination_package",
|
725
|
+
params={
|
726
|
+
"location_id": self.content_loc.id,
|
727
|
+
"package_level_id": package_level.id,
|
728
|
+
"barcode": self.dest_location.barcode,
|
729
|
+
},
|
730
|
+
)
|
731
|
+
done_picking = package_level.picking_id
|
732
|
+
# Check the picking data
|
733
|
+
self.assertEqual(original_picking.backorder_ids, done_picking)
|
734
|
+
self.assertEqual(package_level.location_dest_id, self.dest_location)
|
735
|
+
for move_line in package_level.move_line_ids:
|
736
|
+
self.assertEqual(move_line.location_dest_id, self.dest_location)
|
737
|
+
moves_product_a = original_picking.move_ids.filtered(
|
738
|
+
lambda m: m.product_id == self.product_a
|
739
|
+
)
|
740
|
+
self.assertEqual(len(original_picking.move_ids), 2)
|
741
|
+
self.assertEqual(len(moves_product_a), 1)
|
742
|
+
for move in moves_product_a:
|
743
|
+
self.assertEqual(len(move.move_line_ids), 1)
|
744
|
+
move_lines_wo_pkg = original_picking.move_line_ids_without_package
|
745
|
+
move_lines_wo_pkg_states = set(move_lines_wo_pkg.mapped("state"))
|
746
|
+
self.assertEqual(len(move_lines_wo_pkg_states), 1)
|
747
|
+
self.assertEqual(move_lines_wo_pkg_states.pop(), "assigned")
|
748
|
+
self.assertEqual(done_picking.package_level_ids.state, "done")
|
749
|
+
# Check the response
|
750
|
+
move_lines = self.service._find_transfer_move_lines(self.content_loc)
|
751
|
+
self.assert_response_start_single(
|
752
|
+
response,
|
753
|
+
move_lines.mapped("picking_id"),
|
754
|
+
message=self.service.msg_store.location_content_transfer_item_complete(
|
755
|
+
self.dest_location
|
756
|
+
),
|
757
|
+
)
|
758
|
+
|
759
|
+
def test_set_destination_line_split_move(self):
|
760
|
+
"""Scanned destination location valid for a move line, but related moves
|
761
|
+
has to be splitted because it is linked to additional move lines.
|
762
|
+
"""
|
763
|
+
original_picking = self.picking
|
764
|
+
self.assertEqual(len(original_picking.move_ids), 2)
|
765
|
+
self.assertEqual(len(self.move_product_b.move_line_ids), 2)
|
766
|
+
move_line = self.move_product_b.move_line_ids.filtered(
|
767
|
+
lambda ml: ml.reserved_uom_qty == 6
|
768
|
+
)
|
769
|
+
self._simulate_selected_move_line(move_line)
|
770
|
+
response = self.service.dispatch(
|
771
|
+
"set_destination_line",
|
772
|
+
params={
|
773
|
+
"location_id": self.content_loc.id,
|
774
|
+
"move_line_id": move_line.id,
|
775
|
+
"quantity": move_line.reserved_uom_qty,
|
776
|
+
"barcode": self.dest_location.barcode,
|
777
|
+
},
|
778
|
+
)
|
779
|
+
done_picking = move_line.picking_id
|
780
|
+
# Check the picking data
|
781
|
+
self.assertEqual(original_picking.backorder_ids, done_picking)
|
782
|
+
self.assertEqual(done_picking.state, "done")
|
783
|
+
self.assertEqual(original_picking.state, "assigned")
|
784
|
+
self.assertEqual(move_line.move_id.product_uom_qty, 6)
|
785
|
+
self.assertEqual(move_line.reserved_uom_qty, 0)
|
786
|
+
self.assertEqual(move_line.qty_done, 6)
|
787
|
+
self.assertEqual(move_line.location_dest_id, self.dest_location)
|
788
|
+
self.assertEqual(len(original_picking.move_ids), 2)
|
789
|
+
moves_product_b = original_picking.move_ids.filtered(
|
790
|
+
lambda m: m.product_id == self.product_b
|
791
|
+
)
|
792
|
+
self.assertEqual(len(moves_product_b), 1)
|
793
|
+
for move in moves_product_b:
|
794
|
+
self.assertEqual(len(move.move_line_ids), 1)
|
795
|
+
move_lines_wo_pkg = original_picking.move_line_ids_without_package
|
796
|
+
move_lines_wo_pkg_states = set(move_lines_wo_pkg.mapped("state"))
|
797
|
+
self.assertEqual(len(move_lines_wo_pkg_states), 1)
|
798
|
+
self.assertTrue(all(state == "assigned" for state in move_lines_wo_pkg_states))
|
799
|
+
self.assertEqual(move_line.state, "done")
|
800
|
+
remaining_move = original_picking.move_ids.filtered(
|
801
|
+
lambda m: move_line.move_id != m and m.product_id == self.product_b
|
802
|
+
)
|
803
|
+
self.assertEqual(remaining_move.state, "assigned")
|
804
|
+
self.assertEqual(remaining_move.product_uom_qty, 4)
|
805
|
+
self.assertEqual(remaining_move.move_line_ids.reserved_uom_qty, 4)
|
806
|
+
self.assertEqual(remaining_move.move_line_ids.qty_done, 4)
|
807
|
+
# Check the response
|
808
|
+
move_lines = self.service._find_transfer_move_lines(self.content_loc)
|
809
|
+
self.assert_response_start_single(
|
810
|
+
response,
|
811
|
+
move_lines.mapped("picking_id"),
|
812
|
+
message=self.service.msg_store.location_content_transfer_item_complete(
|
813
|
+
self.dest_location
|
814
|
+
),
|
815
|
+
)
|
816
|
+
# Process the other move lines (lines w/o package + package levels)
|
817
|
+
# to check the picking state
|
818
|
+
remaining_move_lines = original_picking.move_line_ids_without_package.filtered(
|
819
|
+
lambda ml: ml.state == "assigned"
|
820
|
+
)
|
821
|
+
for ml in remaining_move_lines:
|
822
|
+
self._simulate_selected_move_line(ml)
|
823
|
+
self.service.dispatch(
|
824
|
+
"set_destination_line",
|
825
|
+
params={
|
826
|
+
"location_id": self.content_loc.id,
|
827
|
+
"move_line_id": ml.id,
|
828
|
+
"quantity": ml.reserved_uom_qty,
|
829
|
+
"barcode": self.dest_location.barcode,
|
830
|
+
},
|
831
|
+
)
|
832
|
+
self.assertEqual(original_picking.state, "assigned")
|
833
|
+
package_level = original_picking.package_level_ids[0]
|
834
|
+
self._simulate_selected_move_line(package_level.move_line_ids)
|
835
|
+
self.service.dispatch(
|
836
|
+
"set_destination_package",
|
837
|
+
params={
|
838
|
+
"location_id": self.content_loc.id,
|
839
|
+
"package_level_id": package_level.id,
|
840
|
+
"barcode": self.dest_location.barcode,
|
841
|
+
},
|
842
|
+
)
|
843
|
+
self.assertEqual(original_picking.state, "done")
|
844
|
+
|
845
|
+
|
846
|
+
class LocationContentTransferSetDestinationChainSpecialCase(
|
847
|
+
LocationContentTransferCommonCase
|
848
|
+
):
|
849
|
+
"""Tests for endpoint used from scan_destination (special cases with
|
850
|
+
chained pickings)
|
851
|
+
|
852
|
+
* /set_destination_package
|
853
|
+
* /set_destination_line
|
854
|
+
|
855
|
+
"""
|
856
|
+
|
857
|
+
@classmethod
|
858
|
+
def setUpClassBaseData(cls):
|
859
|
+
super().setUpClassBaseData()
|
860
|
+
# Test split of partial qty when the moves have "move_orig_ids".
|
861
|
+
# We create a chain of pickings to ensure the proper state is computed
|
862
|
+
# for the split move.
|
863
|
+
cls.picking_a = picking_a = cls._create_picking(lines=[(cls.product_c, 10)])
|
864
|
+
cls.picking_b = picking_b = cls._create_picking(lines=[(cls.product_c, 10)])
|
865
|
+
# connect a and b in a chain of moves
|
866
|
+
for move_a in picking_a.move_ids:
|
867
|
+
for move_b in picking_b.move_ids:
|
868
|
+
if move_a.product_id == move_b.product_id:
|
869
|
+
move_a.move_dest_ids = move_b
|
870
|
+
move_b.procure_method = "make_to_order"
|
871
|
+
|
872
|
+
cls.pickings = picking_a | picking_b
|
873
|
+
cls._fill_stock_for_moves(picking_a.move_ids, location=cls.content_loc)
|
874
|
+
cls.pickings.action_assign()
|
875
|
+
|
876
|
+
cls.dest_location = (
|
877
|
+
cls.env["stock.location"]
|
878
|
+
.sudo()
|
879
|
+
.create(
|
880
|
+
{
|
881
|
+
"name": "Sub Shelf 1",
|
882
|
+
"barcode": "subshelf1",
|
883
|
+
"location_id": cls.shelf1.id,
|
884
|
+
}
|
885
|
+
)
|
886
|
+
)
|
887
|
+
|
888
|
+
def test_set_destination_line_partial_qty_with_move_orig_ids(self):
|
889
|
+
"""Scanned destination location with partial qty, but related moves
|
890
|
+
has to be split and the move has origin moves (with origin moves)
|
891
|
+
"""
|
892
|
+
picking_a = self.picking_a
|
893
|
+
picking_b = self.picking_b
|
894
|
+
picking_a.move_line_ids.qty_done = 10
|
895
|
+
picking_a._action_done()
|
896
|
+
self.assertEqual(picking_a.state, "done")
|
897
|
+
self.assertEqual(picking_b.state, "assigned")
|
898
|
+
self._simulate_pickings_selected(picking_b)
|
899
|
+
|
900
|
+
move_line_c = picking_b.move_line_ids.filtered(
|
901
|
+
lambda m: m.product_id == self.product_c
|
902
|
+
)
|
903
|
+
|
904
|
+
self.assertEqual(move_line_c.reserved_uom_qty, 10)
|
905
|
+
self.assertEqual(move_line_c.qty_done, 10)
|
906
|
+
# Scan partial qty (6/10)
|
907
|
+
self.service.dispatch(
|
908
|
+
"set_destination_line",
|
909
|
+
params={
|
910
|
+
"location_id": self.content_loc.id,
|
911
|
+
"move_line_id": move_line_c.id,
|
912
|
+
"quantity": move_line_c.reserved_uom_qty - 4, # Scan 6 qty
|
913
|
+
"barcode": self.dest_location.barcode,
|
914
|
+
},
|
915
|
+
)
|
916
|
+
# Check move line data
|
917
|
+
self.assertEqual(move_line_c.move_id.product_uom_qty, 6)
|
918
|
+
self.assertEqual(move_line_c.reserved_uom_qty, 0)
|
919
|
+
self.assertEqual(move_line_c.qty_done, 6)
|
920
|
+
self.assertEqual(move_line_c.state, "done")
|
921
|
+
# the move has been split
|
922
|
+
move = move_line_c.picking_id.backorder_ids.move_ids
|
923
|
+
self.assertNotEqual(move_line_c.move_id, move)
|
924
|
+
|
925
|
+
# Check the move handling the remaining qty
|
926
|
+
self.assertEqual(move.state, "assigned")
|
927
|
+
move_line = move.move_line_ids
|
928
|
+
self.assertEqual(move_line.move_id.product_uom_qty, 4)
|
929
|
+
self.assertEqual(move_line.reserved_uom_qty, 4)
|
930
|
+
self.assertEqual(move_line.qty_done, 4)
|
931
|
+
|
932
|
+
def test_set_destination_package_partial_qty_with_move_orig_ids(self):
|
933
|
+
"""Scanned destination location with partial qty, but related moves
|
934
|
+
has to be split and the move has origin moves
|
935
|
+
(with package and origin moves)
|
936
|
+
"""
|
937
|
+
picking_a = self.picking_a
|
938
|
+
picking_b = self.picking_b
|
939
|
+
|
940
|
+
# we put 6 in a new package and 4 in another new package
|
941
|
+
package1 = self.env["stock.quant.package"].create({})
|
942
|
+
package2 = self.env["stock.quant.package"].create({})
|
943
|
+
line1 = picking_a.move_line_ids
|
944
|
+
line2 = line1.copy({"reserved_uom_qty": 4, "qty_done": 4})
|
945
|
+
line1.with_context(bypass_reservation_update=True).reserved_uom_qty = 6
|
946
|
+
line1.qty_done = 6
|
947
|
+
line1.result_package_id = package1
|
948
|
+
line2.result_package_id = package2
|
949
|
+
picking_a._action_done()
|
950
|
+
self.assertEqual(picking_a.state, "done")
|
951
|
+
self.assertEqual(picking_b.state, "assigned")
|
952
|
+
# we have 1 move line per package
|
953
|
+
self.assertEqual(len(picking_b.move_line_ids), 2)
|
954
|
+
self._simulate_pickings_selected(picking_b)
|
955
|
+
|
956
|
+
move_line = picking_b.move_line_ids.filtered(lambda m: m.package_id == package1)
|
957
|
+
move = move_line.move_id
|
958
|
+
|
959
|
+
self.assertEqual(move_line.reserved_uom_qty, 6.0)
|
960
|
+
self.assertEqual(move_line.qty_done, 6.0)
|
961
|
+
self._simulate_selected_move_line(move_line)
|
962
|
+
# Scan partial qty (6/10)
|
963
|
+
self.service.dispatch(
|
964
|
+
"set_destination_line",
|
965
|
+
params={
|
966
|
+
"location_id": self.content_loc.id,
|
967
|
+
"move_line_id": move_line.id,
|
968
|
+
"quantity": 6.0, # Scan 6 qty
|
969
|
+
"barcode": self.dest_location.barcode,
|
970
|
+
},
|
971
|
+
)
|
972
|
+
# Check move line data
|
973
|
+
self.assertEqual(move_line.move_id.product_uom_qty, 6)
|
974
|
+
self.assertEqual(move_line.reserved_uom_qty, 0)
|
975
|
+
self.assertEqual(move_line.qty_done, 6)
|
976
|
+
self.assertEqual(move_line.state, "done")
|
977
|
+
# the move has been split
|
978
|
+
self.assertNotEqual(move_line.move_id, move)
|
979
|
+
|
980
|
+
# Check the move handling the remaining qty
|
981
|
+
self.assertEqual(move.state, "assigned")
|
982
|
+
move_line = move.move_line_ids
|
983
|
+
self.assertEqual(move_line.move_id.product_uom_qty, 4)
|
984
|
+
self.assertEqual(move_line.reserved_uom_qty, 4)
|
985
|
+
self.assertEqual(move_line.qty_done, 4)
|
986
|
+
|
987
|
+
|
988
|
+
class LocationContentTransferSetDestinationNextOperationSpecialCase(
|
989
|
+
LocationContentTransferCommonCase
|
990
|
+
):
|
991
|
+
"""Tests for endpoint used from scan_destination to ensure that in
|
992
|
+
case of partial qty, the next operation is the one for the remaining
|
993
|
+
qty.
|
994
|
+
|
995
|
+
* /set_destination_line
|
996
|
+
|
997
|
+
"""
|
998
|
+
|
999
|
+
@classmethod
|
1000
|
+
def setUpClassBaseData(cls):
|
1001
|
+
super().setUpClassBaseData()
|
1002
|
+
cls.picking = cls._create_picking(
|
1003
|
+
lines=[(cls.product_a, 10), (cls.product_b, 10)]
|
1004
|
+
)
|
1005
|
+
cls._update_qty_in_location(cls.picking.location_id, cls.product_a, 20)
|
1006
|
+
cls._update_qty_in_location(cls.picking.location_id, cls.product_b, 20)
|
1007
|
+
# Reserve quantities
|
1008
|
+
cls.picking.action_assign()
|
1009
|
+
cls._simulate_pickings_selected(cls.picking)
|
1010
|
+
cls.dest_location = (
|
1011
|
+
cls.env["stock.location"]
|
1012
|
+
.sudo()
|
1013
|
+
.create(
|
1014
|
+
{
|
1015
|
+
"name": "Sub Shelf 1",
|
1016
|
+
"barcode": "subshelf1",
|
1017
|
+
"location_id": cls.shelf1.id,
|
1018
|
+
}
|
1019
|
+
)
|
1020
|
+
)
|
1021
|
+
|
1022
|
+
def test_set_destination_lines_partial_qty_next_line(self):
|
1023
|
+
"""Scanned destination location with partial qty, the next line to process
|
1024
|
+
should be the one for the remaining qty.
|
1025
|
+
"""
|
1026
|
+
|
1027
|
+
move_line = self.picking.move_line_ids[0]
|
1028
|
+
self._simulate_selected_move_line(move_line)
|
1029
|
+
# Scan partial qty (6/10)
|
1030
|
+
response = self.service.dispatch(
|
1031
|
+
"set_destination_line",
|
1032
|
+
params={
|
1033
|
+
"location_id": self.content_loc.id,
|
1034
|
+
"move_line_id": move_line.id,
|
1035
|
+
"quantity": move_line.reserved_uom_qty - 4, # Scan 6 qty
|
1036
|
+
"barcode": self.dest_location.barcode,
|
1037
|
+
},
|
1038
|
+
)
|
1039
|
+
# the new qty is in a backorder of the backorder where the done qty has bee
|
1040
|
+
# processed
|
1041
|
+
backorder = self.picking.backorder_ids.backorder_ids
|
1042
|
+
self.assertTrue(backorder)
|
1043
|
+
|
1044
|
+
self.assert_response_start_single(
|
1045
|
+
response,
|
1046
|
+
backorder,
|
1047
|
+
message=self.service.msg_store.location_content_transfer_item_complete(
|
1048
|
+
self.dest_location
|
1049
|
+
),
|
1050
|
+
)
|
1051
|
+
# check that the next operation has the appropriate attributes
|
1052
|
+
move_line = backorder.move_line_ids
|
1053
|
+
self.assertEqual(move_line.reserved_uom_qty, 4)
|
1054
|
+
self.assertEqual(move_line.qty_done, 4)
|
1055
|
+
self.assertEqual(move_line.picking_id.user_id, self.env.user)
|
1056
|
+
# if we process the quantity of the backorder, the next operation should
|
1057
|
+
# be the remaining one of the initial picking
|
1058
|
+
self._simulate_selected_move_line(move_line)
|
1059
|
+
response = self.service.dispatch(
|
1060
|
+
"set_destination_line",
|
1061
|
+
params={
|
1062
|
+
"location_id": self.content_loc.id,
|
1063
|
+
"move_line_id": move_line.id,
|
1064
|
+
"quantity": move_line.reserved_uom_qty, # Scan 6 qty
|
1065
|
+
"barcode": self.dest_location.barcode,
|
1066
|
+
},
|
1067
|
+
)
|
1068
|
+
self.assert_response_start_single(
|
1069
|
+
response,
|
1070
|
+
self.picking,
|
1071
|
+
message=self.service.msg_store.location_content_transfer_item_complete(
|
1072
|
+
self.dest_location
|
1073
|
+
),
|
1074
|
+
)
|