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,1763 @@
|
|
1
|
+
# Copyright 2020-2021 Camptocamp SA (http://www.camptocamp.com)
|
2
|
+
# Copyright 2020-2021 Jacques-Etienne Baudoux (BCIM) <je@bcim.be>
|
3
|
+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
4
|
+
|
5
|
+
from werkzeug.exceptions import BadRequest
|
6
|
+
|
7
|
+
from odoo import _, fields
|
8
|
+
|
9
|
+
from odoo.addons.base_rest.components.service import to_int
|
10
|
+
from odoo.addons.component.core import Component
|
11
|
+
|
12
|
+
from ..utils import to_float
|
13
|
+
|
14
|
+
|
15
|
+
class Checkout(Component):
|
16
|
+
"""
|
17
|
+
Methods for the Checkout Process
|
18
|
+
|
19
|
+
This scenario runs on existing moves.
|
20
|
+
It happens on the "Packing" step of a pick/pack/ship.
|
21
|
+
|
22
|
+
Use cases:
|
23
|
+
|
24
|
+
1) Products are packed (e.g. full pallet shipping) and we keep the packages
|
25
|
+
2) Products are packed (e.g. rollercage bins) and we create a new package
|
26
|
+
with same content for shipping
|
27
|
+
3) Products are packed (e.g. half-pallet ) and we merge several into one
|
28
|
+
4) Products are packed (e.g. too high pallet) and we split it on several
|
29
|
+
5) Products are not packed (e.g. raw products) and we create new packages
|
30
|
+
6) Products are not packed (e.g. raw products) and we do not create packages
|
31
|
+
|
32
|
+
A new flag ``shopfloor_checkout_done`` on move lines allows to track which
|
33
|
+
lines have been checked out (can be with or without package).
|
34
|
+
|
35
|
+
Flow Diagram: https://www.draw.io/#G1qRenBcezk50ggIazDuu2qOfkTsoIAxXP
|
36
|
+
"""
|
37
|
+
|
38
|
+
_inherit = "base.shopfloor.process"
|
39
|
+
_name = "shopfloor.checkout"
|
40
|
+
_usage = "checkout"
|
41
|
+
_description = __doc__
|
42
|
+
|
43
|
+
def _response_for_select_line(
|
44
|
+
self, picking, message=None, need_confirm_pack_all=False
|
45
|
+
):
|
46
|
+
if all(line.shopfloor_checkout_done for line in picking.move_line_ids):
|
47
|
+
return self._response_for_summary(picking, message=message)
|
48
|
+
return self._response(
|
49
|
+
next_state="select_line",
|
50
|
+
data=self._data_for_select_line(
|
51
|
+
picking, need_confirm_pack_all=need_confirm_pack_all
|
52
|
+
),
|
53
|
+
message=message,
|
54
|
+
)
|
55
|
+
|
56
|
+
def _data_for_select_line(self, picking, need_confirm_pack_all=False):
|
57
|
+
return {
|
58
|
+
"picking": self._data_for_stock_picking(picking),
|
59
|
+
"group_lines_by_location": True,
|
60
|
+
"show_oneline_package_content": self.work.menu.show_oneline_package_content,
|
61
|
+
"need_confirm_pack_all": need_confirm_pack_all,
|
62
|
+
}
|
63
|
+
|
64
|
+
def _response_for_summary(self, picking, need_confirm=False, message=None):
|
65
|
+
return self._response(
|
66
|
+
next_state="summary" if not need_confirm else "confirm_done",
|
67
|
+
data={
|
68
|
+
"picking": self._data_for_stock_picking(picking, done=True),
|
69
|
+
"all_processed": not bool(self._lines_to_pack(picking)),
|
70
|
+
},
|
71
|
+
message=message,
|
72
|
+
)
|
73
|
+
|
74
|
+
def _response_for_select_document(self, message=None):
|
75
|
+
return self._response(next_state="select_document", message=message)
|
76
|
+
|
77
|
+
def _response_for_manual_selection(self, message=None):
|
78
|
+
pickings = self.env["stock.picking"].search(
|
79
|
+
self._domain_for_list_stock_picking(),
|
80
|
+
order=self._order_for_list_stock_picking(),
|
81
|
+
)
|
82
|
+
data = {"pickings": self.data.pickings(pickings)}
|
83
|
+
return self._response(next_state="manual_selection", data=data, message=message)
|
84
|
+
|
85
|
+
def _response_for_select_package(self, picking, lines, message=None):
|
86
|
+
return self._response(
|
87
|
+
next_state="select_package",
|
88
|
+
data={
|
89
|
+
"selected_move_lines": self._data_for_move_lines(lines.sorted()),
|
90
|
+
"picking": self.data.picking(picking),
|
91
|
+
"packing_info": self._data_for_packing_info(picking),
|
92
|
+
"no_package_enabled": not self.options.get(
|
93
|
+
"checkout__disable_no_package"
|
94
|
+
),
|
95
|
+
},
|
96
|
+
message=message,
|
97
|
+
)
|
98
|
+
|
99
|
+
def _data_for_packing_info(self, picking):
|
100
|
+
"""Return the packing information
|
101
|
+
|
102
|
+
Intended to be extended.
|
103
|
+
"""
|
104
|
+
# TODO: This could be avoided if included in the picking parser.
|
105
|
+
return ""
|
106
|
+
|
107
|
+
def _response_for_select_dest_package(self, picking, move_lines, message=None):
|
108
|
+
packages = picking.mapped("move_line_ids.result_package_id").filtered(
|
109
|
+
"package_type_id"
|
110
|
+
)
|
111
|
+
if not packages:
|
112
|
+
# FIXME: do we want to move from 'select_dest_package' to
|
113
|
+
# 'select_package' state? Until now (before enforcing the use of
|
114
|
+
# delivery package) this part of code was never reached as we
|
115
|
+
# always had a package on the picking (source or result)
|
116
|
+
# Also the response validator did not support this state...
|
117
|
+
return self._response_for_select_package(
|
118
|
+
picking,
|
119
|
+
move_lines,
|
120
|
+
message=self.msg_store.no_valid_package_to_select(),
|
121
|
+
)
|
122
|
+
picking_data = self.data.picking(picking)
|
123
|
+
packages_data = self.data.packages(
|
124
|
+
packages.with_context(picking_id=picking.id).sorted(),
|
125
|
+
picking=picking,
|
126
|
+
with_packaging=True,
|
127
|
+
)
|
128
|
+
return self._response(
|
129
|
+
next_state="select_dest_package",
|
130
|
+
data={
|
131
|
+
"picking": picking_data,
|
132
|
+
"packages": packages_data,
|
133
|
+
"selected_move_lines": self._data_for_move_lines(move_lines.sorted()),
|
134
|
+
},
|
135
|
+
message=message,
|
136
|
+
)
|
137
|
+
|
138
|
+
def _response_for_select_delivery_packaging(self, picking, packaging, message=None):
|
139
|
+
return self._response(
|
140
|
+
next_state="select_delivery_packaging",
|
141
|
+
data={
|
142
|
+
# We don't need to send the 'picking' as the mobile frontend
|
143
|
+
# already has this info after `select_document` state
|
144
|
+
# TODO adapt other endpoints to see if we can get rid of the
|
145
|
+
# 'picking' data
|
146
|
+
"packaging": self._data_for_delivery_packaging(packaging),
|
147
|
+
},
|
148
|
+
message=message,
|
149
|
+
)
|
150
|
+
|
151
|
+
def _response_for_change_packaging(self, picking, package, packaging_list):
|
152
|
+
if not package:
|
153
|
+
return self._response_for_summary(
|
154
|
+
picking, message=self.msg_store.record_not_found()
|
155
|
+
)
|
156
|
+
|
157
|
+
return self._response(
|
158
|
+
next_state="change_packaging",
|
159
|
+
data={
|
160
|
+
"picking": self.data.picking(picking),
|
161
|
+
"package": self.data.package(
|
162
|
+
package, picking=picking, with_packaging=True
|
163
|
+
),
|
164
|
+
"packaging": self.data.delivery_packaging_list(
|
165
|
+
packaging_list.sorted("sequence")
|
166
|
+
),
|
167
|
+
},
|
168
|
+
)
|
169
|
+
|
170
|
+
def scan_document(self, barcode):
|
171
|
+
"""Scan a package, a product, a transfer or a location
|
172
|
+
|
173
|
+
When a location is scanned, if all the move lines from this destination
|
174
|
+
are for the same stock.picking, the stock.picking is used for the
|
175
|
+
next steps.
|
176
|
+
|
177
|
+
When a package is scanned, if the package has a move line to move it
|
178
|
+
from a location/sublocation of the current stock.picking.type, the
|
179
|
+
stock.picking for the package is used for the next steps.
|
180
|
+
|
181
|
+
When a product is scanned, use the first picking (ordered by priority desc,
|
182
|
+
scheduled_date asc, id desc) which has an ongoing move line with no source
|
183
|
+
package for the given product.
|
184
|
+
|
185
|
+
When a stock.picking is scanned, it is used for the next steps.
|
186
|
+
|
187
|
+
In every case above, the stock.picking must be entirely available and
|
188
|
+
must match the current picking type.
|
189
|
+
|
190
|
+
Transitions:
|
191
|
+
* select_document: when no stock.picking could be found
|
192
|
+
* select_line: a stock.picking is selected
|
193
|
+
* summary: stock.picking is selected and all its lines have a
|
194
|
+
destination pack set
|
195
|
+
"""
|
196
|
+
search_result = self._scan_document_find(barcode)
|
197
|
+
result_handler = getattr(self, "_select_document_from_" + search_result.type)
|
198
|
+
return result_handler(search_result.record)
|
199
|
+
|
200
|
+
def _scan_document_find(self, barcode, search_types=None):
|
201
|
+
search = self._actions_for("search")
|
202
|
+
search_types = (
|
203
|
+
"picking",
|
204
|
+
"location",
|
205
|
+
"package",
|
206
|
+
"product",
|
207
|
+
"packaging",
|
208
|
+
)
|
209
|
+
return search.find(
|
210
|
+
barcode,
|
211
|
+
types=search_types,
|
212
|
+
)
|
213
|
+
|
214
|
+
def _select_document_from_picking(self, picking, **kw):
|
215
|
+
return self._select_picking(picking, "select_document")
|
216
|
+
|
217
|
+
def _select_document_from_location(self, location, **kw):
|
218
|
+
if not self.is_src_location_valid(location):
|
219
|
+
return self._response_for_select_document(
|
220
|
+
message=self.msg_store.location_not_allowed()
|
221
|
+
)
|
222
|
+
lines = location.source_move_line_ids
|
223
|
+
pickings = lines.mapped("picking_id")
|
224
|
+
if len(pickings) > 1:
|
225
|
+
return self._response_for_select_document(
|
226
|
+
message={
|
227
|
+
"message_type": "error",
|
228
|
+
"body": _(
|
229
|
+
"Several transfers found, please scan a package"
|
230
|
+
" or select a transfer manually."
|
231
|
+
),
|
232
|
+
}
|
233
|
+
)
|
234
|
+
return self._select_picking(pickings, "select_document")
|
235
|
+
|
236
|
+
def _select_document_from_package(self, package, **kw):
|
237
|
+
pickings = package.move_line_ids.filtered(
|
238
|
+
lambda ml: ml.state not in ("cancel", "done")
|
239
|
+
).mapped("picking_id")
|
240
|
+
if len(pickings) > 1:
|
241
|
+
# Filter only if we find several pickings to narrow the
|
242
|
+
# selection to one of the good type. If we have one picking
|
243
|
+
# of the wrong type, it will be caught in _select_picking
|
244
|
+
# with the proper error message.
|
245
|
+
# Side note: rather unlikely to have several transfers ready
|
246
|
+
# and moving the same things
|
247
|
+
pickings = pickings.filtered(
|
248
|
+
lambda p: p.picking_type_id in self.picking_types
|
249
|
+
)
|
250
|
+
if len(pickings) == 1:
|
251
|
+
picking = pickings
|
252
|
+
return self._select_picking(picking, "select_document")
|
253
|
+
|
254
|
+
def _select_document_from_product(self, product, line_domain=None, **kw):
|
255
|
+
line_domain = line_domain or []
|
256
|
+
line_domain.extend(
|
257
|
+
[
|
258
|
+
("product_id", "=", product.id),
|
259
|
+
("state", "not in", ("cancel", "done")),
|
260
|
+
("package_id", "=", False),
|
261
|
+
]
|
262
|
+
)
|
263
|
+
lines = self.env["stock.move.line"].search(line_domain)
|
264
|
+
picking = self.env["stock.picking"].search(
|
265
|
+
[
|
266
|
+
("id", "in", lines.move_id.picking_id.ids),
|
267
|
+
("picking_type_id", "in", self.picking_types.ids),
|
268
|
+
],
|
269
|
+
order="priority desc, scheduled_date asc, id desc",
|
270
|
+
limit=1,
|
271
|
+
)
|
272
|
+
return self._select_picking(picking, "select_document")
|
273
|
+
|
274
|
+
def _select_document_from_packaging(self, packaging, **kw):
|
275
|
+
# And retrieve its product
|
276
|
+
product = packaging.product_id
|
277
|
+
# The picking should have a move line for the product
|
278
|
+
# where qty >= packaging.qty, since it doesn't makes sense
|
279
|
+
# to select a move line which have less qty than the packaging
|
280
|
+
line_domain = [("reserved_uom_qty", ">=", packaging.qty)]
|
281
|
+
return self._select_document_from_product(product, line_domain=line_domain)
|
282
|
+
|
283
|
+
def _select_document_from_none(self, picking, **kw):
|
284
|
+
"""Handle result when no record is found."""
|
285
|
+
return self._select_picking(picking, "select_document")
|
286
|
+
|
287
|
+
def _select_picking(self, picking, state_for_error):
|
288
|
+
if not picking:
|
289
|
+
if state_for_error == "manual_selection":
|
290
|
+
return self._response_for_manual_selection(
|
291
|
+
message=self.msg_store.stock_picking_not_found()
|
292
|
+
)
|
293
|
+
return self._response_for_select_document(
|
294
|
+
message=self.msg_store.barcode_not_found()
|
295
|
+
)
|
296
|
+
if picking.picking_type_id not in self.picking_types:
|
297
|
+
if state_for_error == "manual_selection":
|
298
|
+
return self._response_for_manual_selection(
|
299
|
+
message=self.msg_store.cannot_move_something_in_picking_type()
|
300
|
+
)
|
301
|
+
return self._response_for_select_document(
|
302
|
+
message=self.msg_store.cannot_move_something_in_picking_type()
|
303
|
+
)
|
304
|
+
if picking.state != "assigned":
|
305
|
+
if state_for_error == "manual_selection":
|
306
|
+
return self._response_for_manual_selection(
|
307
|
+
message=self.msg_store.stock_picking_not_available(picking)
|
308
|
+
)
|
309
|
+
return self._response_for_select_document(
|
310
|
+
message=self.msg_store.stock_picking_not_available(picking)
|
311
|
+
)
|
312
|
+
return self._response_for_select_line(picking)
|
313
|
+
|
314
|
+
def _data_for_move_lines(self, lines, **kw):
|
315
|
+
return self.data.move_lines(lines, **kw)
|
316
|
+
|
317
|
+
def _data_for_delivery_packaging(self, packaging, **kw):
|
318
|
+
return self.data.delivery_packaging_list(packaging, **kw)
|
319
|
+
|
320
|
+
def _data_for_stock_picking(self, picking, done=False):
|
321
|
+
data = self.data.picking(picking)
|
322
|
+
line_picker = self._lines_checkout_done if done else self._lines_to_pack
|
323
|
+
data.update(
|
324
|
+
{
|
325
|
+
"move_lines": self._data_for_move_lines(
|
326
|
+
self._lines_prepare(picking, line_picker(picking)),
|
327
|
+
with_packaging=done,
|
328
|
+
)
|
329
|
+
}
|
330
|
+
)
|
331
|
+
return data
|
332
|
+
|
333
|
+
def _lines_checkout_done(self, picking):
|
334
|
+
return picking.move_line_ids.filtered(self._filter_lines_checkout_done)
|
335
|
+
|
336
|
+
def _lines_to_pack(self, picking):
|
337
|
+
return picking.move_line_ids.filtered(self._filter_lines_unpacked)
|
338
|
+
|
339
|
+
def _lines_prepare(self, picking, selected_lines):
|
340
|
+
"""Hook to manipulate lines' ordering or anything else before sending them back."""
|
341
|
+
return selected_lines
|
342
|
+
|
343
|
+
def _domain_for_list_stock_picking(self):
|
344
|
+
return [
|
345
|
+
("state", "=", "assigned"),
|
346
|
+
("picking_type_id", "in", self.picking_types.ids),
|
347
|
+
]
|
348
|
+
|
349
|
+
def _order_for_list_stock_picking(self):
|
350
|
+
return "scheduled_date asc, id asc"
|
351
|
+
|
352
|
+
def list_stock_picking(self):
|
353
|
+
"""List stock.picking records available
|
354
|
+
|
355
|
+
Returns a list of all the available records for the current picking
|
356
|
+
type.
|
357
|
+
|
358
|
+
Transitions:
|
359
|
+
* manual_selection: to the selection screen
|
360
|
+
"""
|
361
|
+
return self._response_for_manual_selection()
|
362
|
+
|
363
|
+
def select(self, picking_id):
|
364
|
+
"""Select a stock picking for the scenario
|
365
|
+
|
366
|
+
Used from the list of stock pickings (manual_selection), from there,
|
367
|
+
the user can click on a stock.picking record which calls this method.
|
368
|
+
|
369
|
+
The ``list_stock_picking`` returns only the valid records (same picking
|
370
|
+
type, fully available, ...), but this method has to check again in case
|
371
|
+
something changed since the list was sent to the client.
|
372
|
+
|
373
|
+
Transitions:
|
374
|
+
* manual_selection: stock.picking could finally not be selected (not
|
375
|
+
available, ...)
|
376
|
+
* summary: goes straight to this state used to set the moves as done when
|
377
|
+
all the move lines with a reserved quantity have a 'quantity done'
|
378
|
+
* select_line: the "normal" case, when the user has to put in pack/move
|
379
|
+
lines
|
380
|
+
"""
|
381
|
+
picking = self.env["stock.picking"].browse(picking_id)
|
382
|
+
message = self._check_picking_status(picking)
|
383
|
+
if message:
|
384
|
+
return self._response_for_manual_selection(message=message)
|
385
|
+
return self._select_picking(picking, "manual_selection")
|
386
|
+
|
387
|
+
def _select_lines(self, lines, prefill_qty=0, related_lines=None):
|
388
|
+
for i, line in enumerate(lines):
|
389
|
+
if line.shopfloor_checkout_done:
|
390
|
+
continue
|
391
|
+
if self.work.menu.no_prefill_qty and i == 0:
|
392
|
+
# For prefill quantity we only want to increment one line
|
393
|
+
line.qty_done += prefill_qty
|
394
|
+
elif not self.work.menu.no_prefill_qty:
|
395
|
+
line.qty_done = line.reserved_uom_qty
|
396
|
+
line.shopfloor_user_id = self.env.user
|
397
|
+
|
398
|
+
picking = lines.mapped("picking_id")
|
399
|
+
other_lines = picking.move_line_ids - lines
|
400
|
+
self._deselect_lines(other_lines)
|
401
|
+
if related_lines:
|
402
|
+
lines += related_lines
|
403
|
+
return lines
|
404
|
+
|
405
|
+
def _deselect_lines(self, lines):
|
406
|
+
lines.filtered(lambda l: not l.shopfloor_checkout_done).write(
|
407
|
+
{"qty_done": 0, "shopfloor_user_id": False}
|
408
|
+
)
|
409
|
+
|
410
|
+
def scan_line(self, picking_id, barcode, confirm_pack_all=False):
|
411
|
+
"""Scan move lines of the stock picking
|
412
|
+
|
413
|
+
It allows to select move lines of the stock picking for the next
|
414
|
+
screen. Lines can be found either by scanning a package, a product or a
|
415
|
+
lot.
|
416
|
+
|
417
|
+
There should be no ambiguity, so for instance if a product is scanned but
|
418
|
+
several packs contain it, the endpoint will ask to scan a pack; if the
|
419
|
+
product is tracked by lot, to scan a lot.
|
420
|
+
|
421
|
+
Once move lines are found, their ``qty_done`` is set to their reserved
|
422
|
+
quantity.
|
423
|
+
|
424
|
+
Transitions:
|
425
|
+
* select_line: nothing could be found for the barcode
|
426
|
+
* select_package: lines are selected, user is redirected to this
|
427
|
+
* summary: delivery package is scanned and all lines are done
|
428
|
+
screen to change the qty done and destination pack if needed
|
429
|
+
"""
|
430
|
+
picking = self.env["stock.picking"].browse(picking_id)
|
431
|
+
message = self._check_picking_status(picking)
|
432
|
+
if message:
|
433
|
+
return self._response_for_select_document(message=message)
|
434
|
+
|
435
|
+
selection_lines = self._lines_to_pack(picking)
|
436
|
+
if not selection_lines:
|
437
|
+
return self._response_for_summary(picking)
|
438
|
+
|
439
|
+
search_result = self._scan_line_find(picking, barcode)
|
440
|
+
result_handler = getattr(self, "_select_lines_from_" + search_result.type)
|
441
|
+
kw = {"confirm_pack_all": confirm_pack_all}
|
442
|
+
return result_handler(picking, selection_lines, search_result.record, **kw)
|
443
|
+
|
444
|
+
def _scan_line_find(self, picking, barcode, search_types=None):
|
445
|
+
search = self._actions_for("search")
|
446
|
+
search_types = (
|
447
|
+
"package",
|
448
|
+
"product",
|
449
|
+
"packaging",
|
450
|
+
"lot",
|
451
|
+
"serial",
|
452
|
+
"delivery_packaging",
|
453
|
+
)
|
454
|
+
return search.find(
|
455
|
+
barcode,
|
456
|
+
types=search_types,
|
457
|
+
handler_kw=dict(
|
458
|
+
lot=dict(products=picking.move_ids.product_id),
|
459
|
+
serial=dict(products=picking.move_ids.product_id),
|
460
|
+
),
|
461
|
+
)
|
462
|
+
|
463
|
+
def _select_lines_from_none(self, picking, selection_lines, record, **kw):
|
464
|
+
"""Handle result when no record is found."""
|
465
|
+
return self._response_for_select_line(
|
466
|
+
picking, message=self.msg_store.barcode_not_found()
|
467
|
+
)
|
468
|
+
|
469
|
+
def _select_lines_from_package(self, picking, selection_lines, package, **kw):
|
470
|
+
lines = selection_lines.filtered(
|
471
|
+
lambda l: l.package_id == package and not l.shopfloor_checkout_done
|
472
|
+
)
|
473
|
+
if not lines:
|
474
|
+
return self._response_for_select_line(
|
475
|
+
picking,
|
476
|
+
message={
|
477
|
+
"message_type": "error",
|
478
|
+
"body": _("Package {} is not in the current transfer.").format(
|
479
|
+
package.name
|
480
|
+
),
|
481
|
+
},
|
482
|
+
)
|
483
|
+
self._select_lines(lines)
|
484
|
+
if self.work.menu.no_prefill_qty:
|
485
|
+
lines = picking.move_line_ids
|
486
|
+
return self._response_for_select_package(picking, lines)
|
487
|
+
|
488
|
+
def _select_lines_from_product(
|
489
|
+
self, picking, selection_lines, product, prefill_qty=1, **kw
|
490
|
+
):
|
491
|
+
if product.tracking in ("lot", "serial"):
|
492
|
+
return self._response_for_select_line(
|
493
|
+
picking, message=self.msg_store.scan_lot_on_product_tracked_by_lot()
|
494
|
+
)
|
495
|
+
|
496
|
+
lines = selection_lines.filtered(lambda l: l.product_id == product)
|
497
|
+
if not lines:
|
498
|
+
return self._response_for_select_line(
|
499
|
+
picking, message=self.msg_store.product_not_found_in_current_picking()
|
500
|
+
)
|
501
|
+
|
502
|
+
# When products are as units outside of packages, we can select them for
|
503
|
+
# packing, but if they are in a package, we want the user to scan the packages.
|
504
|
+
# If the product is only in one package though, scanning the product selects
|
505
|
+
# the package.
|
506
|
+
packages = lines.mapped("package_id")
|
507
|
+
related_lines = self.env["stock.move.line"].browse()
|
508
|
+
# Do not use mapped here: we want to see if we have more than one package,
|
509
|
+
# but also if we have one product as a package and the same product as
|
510
|
+
# a unit in another line. In both cases, we want the user to scan the
|
511
|
+
# package.
|
512
|
+
if packages and len({line.package_id for line in lines}) > 1:
|
513
|
+
return self._response_for_select_line(
|
514
|
+
picking, message=self.msg_store.product_multiple_packages_scan_package()
|
515
|
+
)
|
516
|
+
elif packages:
|
517
|
+
# Select all the lines of the package when we scan a product in a
|
518
|
+
# package and we have only one.
|
519
|
+
return self._select_lines_from_package(picking, selection_lines, packages)
|
520
|
+
else:
|
521
|
+
# There is no package on selected lines, so also select all other lines
|
522
|
+
# not in a package. But only the quantity on first selected lines
|
523
|
+
# are updated.
|
524
|
+
related_lines = selection_lines.filtered(
|
525
|
+
lambda l: not l.package_id and l.product_id != product
|
526
|
+
)
|
527
|
+
|
528
|
+
lines = self._select_lines(
|
529
|
+
lines, prefill_qty=prefill_qty, related_lines=related_lines
|
530
|
+
)
|
531
|
+
return self._response_for_select_package(picking, lines)
|
532
|
+
|
533
|
+
def _select_lines_from_packaging(self, picking, selection_lines, packaging, **kw):
|
534
|
+
return self._select_lines_from_product(
|
535
|
+
picking, selection_lines, packaging.product_id, prefill_qty=packaging.qty
|
536
|
+
)
|
537
|
+
|
538
|
+
def _select_lines_from_lot(self, picking, selection_lines, lot, **kw):
|
539
|
+
lines = selection_lines.filtered(lambda l: l.lot_id == lot)
|
540
|
+
if not lines:
|
541
|
+
return self._response_for_select_line(
|
542
|
+
picking,
|
543
|
+
message={
|
544
|
+
"message_type": "error",
|
545
|
+
"body": _("Lot is not in the current transfer."),
|
546
|
+
},
|
547
|
+
)
|
548
|
+
|
549
|
+
# When lots are as units outside of packages, we can select them for
|
550
|
+
# packing, but if they are in a package, we want the user to scan the packages.
|
551
|
+
# If the product is only in one package though, scanning the lot selects
|
552
|
+
# the package.
|
553
|
+
packages = lines.mapped("package_id")
|
554
|
+
# Do not use mapped here: we want to see if we have more than one
|
555
|
+
# package, but also if we have one lot as a package and the same lot as
|
556
|
+
# a unit in another line. In both cases, we want the user to scan the
|
557
|
+
# package.
|
558
|
+
if packages and len({line.package_id for line in lines}) > 1:
|
559
|
+
return self._response_for_select_line(
|
560
|
+
picking, message=self.msg_store.lot_multiple_packages_scan_package()
|
561
|
+
)
|
562
|
+
elif packages:
|
563
|
+
# Select all the lines of the package when we scan a lot in a
|
564
|
+
# package and we have only one.
|
565
|
+
return self._select_lines_from_package(
|
566
|
+
picking, selection_lines, packages, **kw
|
567
|
+
)
|
568
|
+
|
569
|
+
self._select_lines(lines, prefill_qty=1)
|
570
|
+
return self._response_for_select_package(picking, lines)
|
571
|
+
|
572
|
+
def _select_lines_from_serial(self, picking, selection_lines, lot, **kw):
|
573
|
+
# Search for serial number is actually the same as searching for lot (as of v14...)
|
574
|
+
return self._select_lines_from_lot(picking, selection_lines, lot, **kw)
|
575
|
+
|
576
|
+
def _select_lines_from_delivery_packaging(
|
577
|
+
self, picking, selection_lines, packaging, confirm_pack_all=False, **kw
|
578
|
+
):
|
579
|
+
"""Handle delivery packaging.
|
580
|
+
|
581
|
+
|
582
|
+
If a delivery pkg has been scanned:
|
583
|
+
|
584
|
+
1. validate it
|
585
|
+
2. ask for confirmation to place all lines left into the same package
|
586
|
+
3. if scanned twice for confirmation,
|
587
|
+
assign new package and skip `select_package` state
|
588
|
+
|
589
|
+
"""
|
590
|
+
carrier = self._get_carrier(picking)
|
591
|
+
if carrier:
|
592
|
+
# Validate against carrier
|
593
|
+
is_valid = self._packaging_good_for_carrier(packaging, carrier)
|
594
|
+
else:
|
595
|
+
is_valid = True
|
596
|
+
if carrier and not is_valid:
|
597
|
+
return self._response_for_select_line(
|
598
|
+
picking,
|
599
|
+
message=self.msg_store.packaging_invalid_for_carrier(
|
600
|
+
packaging, carrier
|
601
|
+
),
|
602
|
+
)
|
603
|
+
if confirm_pack_all:
|
604
|
+
# Select all lines and pack them all w/o passing for select_package state
|
605
|
+
self._select_lines(selection_lines)
|
606
|
+
return self._create_and_assign_new_packaging(
|
607
|
+
picking, selection_lines, packaging=packaging
|
608
|
+
)
|
609
|
+
return self._response_for_select_line(
|
610
|
+
picking,
|
611
|
+
message=self.msg_store.confirm_put_all_goods_in_delivery_package(packaging),
|
612
|
+
need_confirm_pack_all=True,
|
613
|
+
)
|
614
|
+
|
615
|
+
def _select_line_package(self, picking, selection_lines, package):
|
616
|
+
if not package:
|
617
|
+
return self._response_for_select_line(
|
618
|
+
picking, message=self.msg_store.record_not_found()
|
619
|
+
)
|
620
|
+
return self._select_lines_from_package(picking, selection_lines, package)
|
621
|
+
|
622
|
+
def _select_line_move_line(self, picking, selection_lines, move_line):
|
623
|
+
if not move_line:
|
624
|
+
return self._response_for_select_line(
|
625
|
+
picking, message=self.msg_store.record_not_found()
|
626
|
+
)
|
627
|
+
# normally, the client should sent only move lines out of packages, but
|
628
|
+
# in case there is a package, handle it as a package
|
629
|
+
if move_line.package_id:
|
630
|
+
return self._select_lines_from_package(
|
631
|
+
picking, selection_lines, move_line.package_id
|
632
|
+
)
|
633
|
+
self._select_lines(move_line)
|
634
|
+
return self._response_for_select_package(picking, move_line)
|
635
|
+
|
636
|
+
def select_line(self, picking_id, package_id=None, move_line_id=None):
|
637
|
+
"""Select move lines of the stock picking
|
638
|
+
|
639
|
+
This is the same as ``scan_line``, except that a package id or a
|
640
|
+
move_line_id is given by the client (user clicked on a list).
|
641
|
+
|
642
|
+
It returns a list of move line ids that will be displayed by the
|
643
|
+
screen ``select_package``. This screen will have to send this list to
|
644
|
+
the endpoints it calls, so we can select/deselect lines but still
|
645
|
+
show them in the list of the client application.
|
646
|
+
|
647
|
+
Transitions:
|
648
|
+
* select_line: nothing could be found for the barcode
|
649
|
+
* select_package: lines are selected, user is redirected to this
|
650
|
+
screen to change the qty done and destination package if needed
|
651
|
+
"""
|
652
|
+
assert package_id or move_line_id
|
653
|
+
|
654
|
+
picking = self.env["stock.picking"].browse(picking_id)
|
655
|
+
message = self._check_picking_status(picking)
|
656
|
+
if message:
|
657
|
+
return self._response_for_select_document(message=message)
|
658
|
+
|
659
|
+
selection_lines = self._lines_to_pack(picking)
|
660
|
+
if not selection_lines:
|
661
|
+
return self._response_for_summary(picking)
|
662
|
+
|
663
|
+
if package_id:
|
664
|
+
package = self.env["stock.quant.package"].browse(package_id).exists()
|
665
|
+
return self._select_line_package(picking, selection_lines, package)
|
666
|
+
if move_line_id:
|
667
|
+
move_line = self.env["stock.move.line"].browse(move_line_id).exists()
|
668
|
+
return self._select_line_move_line(picking, selection_lines, move_line)
|
669
|
+
|
670
|
+
def _change_line_qty(
|
671
|
+
self, picking_id, selected_line_ids, move_line_ids, quantity_func
|
672
|
+
):
|
673
|
+
picking = self.env["stock.picking"].browse(picking_id)
|
674
|
+
message = self._check_picking_status(picking)
|
675
|
+
if message:
|
676
|
+
return self._response_for_select_document(message=message)
|
677
|
+
|
678
|
+
move_lines = self.env["stock.move.line"].browse(move_line_ids).exists()
|
679
|
+
|
680
|
+
message = None
|
681
|
+
if not move_lines:
|
682
|
+
message = self.msg_store.record_not_found()
|
683
|
+
for move_line in move_lines:
|
684
|
+
qty_done = quantity_func(move_line)
|
685
|
+
if qty_done < 0:
|
686
|
+
message = {
|
687
|
+
"body": _("Negative quantity not allowed."),
|
688
|
+
"message_type": "error",
|
689
|
+
}
|
690
|
+
else:
|
691
|
+
new_line = self.env["stock.move.line"]
|
692
|
+
if qty_done > 0:
|
693
|
+
new_line, qty_check = move_line._split_qty_to_be_done(
|
694
|
+
qty_done,
|
695
|
+
split_partial=False,
|
696
|
+
result_package_id=False,
|
697
|
+
)
|
698
|
+
move_line.qty_done = qty_done
|
699
|
+
if new_line:
|
700
|
+
selected_line_ids.append(new_line.id)
|
701
|
+
if qty_done > move_line.reserved_uom_qty:
|
702
|
+
return self._response_for_select_package(
|
703
|
+
picking,
|
704
|
+
self.env["stock.move.line"].browse(selected_line_ids).exists(),
|
705
|
+
message=self.msg_store.line_scanned_qty_done_higher_than_allowed(),
|
706
|
+
)
|
707
|
+
return self._response_for_select_package(
|
708
|
+
picking,
|
709
|
+
self.env["stock.move.line"].browse(selected_line_ids).exists(),
|
710
|
+
message=message,
|
711
|
+
)
|
712
|
+
|
713
|
+
def reset_line_qty(self, picking_id, selected_line_ids, move_line_id):
|
714
|
+
"""Reset qty_done of a move line to zero
|
715
|
+
|
716
|
+
Used to deselect a line in the "select_package" screen.
|
717
|
+
The selected_line_ids parameter is used to keep the selection of lines
|
718
|
+
stateless.
|
719
|
+
|
720
|
+
Transitions:
|
721
|
+
* select_package: goes back to the same state, the line will appear
|
722
|
+
as deselected
|
723
|
+
"""
|
724
|
+
return self._change_line_qty(
|
725
|
+
picking_id, selected_line_ids, [move_line_id], lambda __: 0
|
726
|
+
)
|
727
|
+
|
728
|
+
def set_line_qty(self, picking_id, selected_line_ids, move_line_id):
|
729
|
+
"""Set qty_done of a move line to its reserved quantity
|
730
|
+
|
731
|
+
Used to select a line in the "select_package" screen.
|
732
|
+
The selected_line_ids parameter is used to keep the selection of lines
|
733
|
+
stateless.
|
734
|
+
|
735
|
+
Transitions:
|
736
|
+
* select_package: goes back to the same state, the line will appear
|
737
|
+
as selected
|
738
|
+
"""
|
739
|
+
return self._change_line_qty(
|
740
|
+
picking_id, selected_line_ids, [move_line_id], lambda l: l.reserved_uom_qty
|
741
|
+
)
|
742
|
+
|
743
|
+
def set_custom_qty(self, picking_id, selected_line_ids, move_line_id, qty_done):
|
744
|
+
"""Change qty_done of a move line with a custom value
|
745
|
+
|
746
|
+
The selected_line_ids parameter is used to keep the selection of lines
|
747
|
+
stateless.
|
748
|
+
|
749
|
+
Transitions:
|
750
|
+
* select_package: goes back to this screen showing all the lines after
|
751
|
+
we changed the qty
|
752
|
+
"""
|
753
|
+
return self._change_line_qty(
|
754
|
+
picking_id, selected_line_ids, [move_line_id], lambda __: qty_done
|
755
|
+
)
|
756
|
+
|
757
|
+
def _switch_line_qty_done(self, picking, selected_lines, switch_lines):
|
758
|
+
"""Switch qty_done on lines and return to the 'select_package' state
|
759
|
+
|
760
|
+
If at least one of the lines to switch has a qty_done, set them all
|
761
|
+
to zero. If all the lines to switch have a zero qty_done, switch them
|
762
|
+
to their quantity to deliver.
|
763
|
+
"""
|
764
|
+
if any(line.qty_done for line in switch_lines):
|
765
|
+
return self._change_line_qty(
|
766
|
+
picking.id, selected_lines.ids, switch_lines.ids, lambda __: 0
|
767
|
+
)
|
768
|
+
else:
|
769
|
+
return self._change_line_qty(
|
770
|
+
picking.id,
|
771
|
+
selected_lines.ids,
|
772
|
+
switch_lines.ids,
|
773
|
+
lambda l: l.reserved_uom_qty,
|
774
|
+
)
|
775
|
+
|
776
|
+
def _increment_custom_qty(
|
777
|
+
self, picking, selected_lines, increment_lines, qty_increment
|
778
|
+
):
|
779
|
+
"""Increment the qty_done of a move line with a custom value
|
780
|
+
|
781
|
+
The selected_line parameter is used to keep the selection of lines
|
782
|
+
stateless.
|
783
|
+
|
784
|
+
Transitions:
|
785
|
+
* select_package: goes back to this screen showing all the lines after
|
786
|
+
we changed the qty
|
787
|
+
"""
|
788
|
+
return self._change_line_qty(
|
789
|
+
picking.id,
|
790
|
+
selected_lines.ids,
|
791
|
+
increment_lines.ids,
|
792
|
+
lambda line: line.qty_done + qty_increment,
|
793
|
+
)
|
794
|
+
|
795
|
+
@staticmethod
|
796
|
+
def _filter_lines_unpacked(move_line):
|
797
|
+
return (
|
798
|
+
move_line.qty_done == 0 or move_line.shopfloor_user_id
|
799
|
+
) and not move_line.shopfloor_checkout_done
|
800
|
+
|
801
|
+
@staticmethod
|
802
|
+
def _filter_lines_to_pack(move_line):
|
803
|
+
return move_line.qty_done > 0 and not move_line.shopfloor_checkout_done
|
804
|
+
|
805
|
+
@staticmethod
|
806
|
+
def _filter_lines_checkout_done(move_line):
|
807
|
+
return move_line.qty_done > 0 and move_line.shopfloor_checkout_done
|
808
|
+
|
809
|
+
def _is_package_allowed(self, picking, package):
|
810
|
+
"""Check if a package is allowed as a destination/delivery package.
|
811
|
+
|
812
|
+
A package is allowed as a destination one if it is present among
|
813
|
+
`picking` lines and qualified as a "delivery package" (having a
|
814
|
+
delivery packaging set on it).
|
815
|
+
"""
|
816
|
+
existing_packages = picking.mapped("move_line_ids.result_package_id").filtered(
|
817
|
+
"package_type_id"
|
818
|
+
)
|
819
|
+
return package in existing_packages
|
820
|
+
|
821
|
+
def _put_lines_in_package(self, picking, selected_lines, package):
|
822
|
+
"""Put the current selected lines with a qty_done in a package
|
823
|
+
|
824
|
+
Note: only packages which are already a delivery package for another
|
825
|
+
line of the stock picking can be selected. Packages which are the
|
826
|
+
source packages are allowed too only if it is a delivery package (we
|
827
|
+
keep the current package).
|
828
|
+
"""
|
829
|
+
if not self._is_package_allowed(picking, package):
|
830
|
+
return self._response_for_select_package(
|
831
|
+
picking,
|
832
|
+
selected_lines,
|
833
|
+
message=self.msg_store.dest_package_not_valid(package),
|
834
|
+
)
|
835
|
+
return self._pack_lines(picking, selected_lines, package)
|
836
|
+
|
837
|
+
def _put_lines_in_allowed_package(self, picking, lines_to_pack, package):
|
838
|
+
for line in lines_to_pack:
|
839
|
+
if line.qty_done < line.reserved_uom_qty:
|
840
|
+
line._split_partial_quantity_to_be_done(line.qty_done, {})
|
841
|
+
lines_to_pack.write(
|
842
|
+
{"result_package_id": package.id, "shopfloor_checkout_done": True}
|
843
|
+
)
|
844
|
+
self._post_put_lines_in_package(lines_to_pack)
|
845
|
+
# Hook to this method to override the response
|
846
|
+
# if anything else has to be handled
|
847
|
+
# before auto posting the lines.
|
848
|
+
return {}
|
849
|
+
|
850
|
+
def _post_put_lines_in_package(self, lines_packaged):
|
851
|
+
"""Hook to override."""
|
852
|
+
|
853
|
+
def _create_and_assign_new_packaging(self, picking, selected_lines, packaging=None):
|
854
|
+
actions = self._actions_for("packaging")
|
855
|
+
package = actions.create_package_from_packaging(packaging=packaging)
|
856
|
+
return self._pack_lines(picking, selected_lines, package)
|
857
|
+
|
858
|
+
def _pack_lines(self, picking, selected_lines, package):
|
859
|
+
lines_to_pack = selected_lines.filtered(self._filter_lines_to_pack)
|
860
|
+
if not lines_to_pack:
|
861
|
+
message = self.msg_store.no_line_to_pack()
|
862
|
+
return self._response_for_select_line(
|
863
|
+
picking,
|
864
|
+
message=message,
|
865
|
+
)
|
866
|
+
response = self._put_lines_in_allowed_package(picking, lines_to_pack, package)
|
867
|
+
if response:
|
868
|
+
return response
|
869
|
+
if self.work.menu.auto_post_line:
|
870
|
+
# If option auto_post_line is active in the shopfloor menu,
|
871
|
+
# create a split order with these packed lines.
|
872
|
+
self._auto_post_lines(lines_to_pack)
|
873
|
+
message = self.msg_store.goods_packed_in(package)
|
874
|
+
# go back to the screen to select the next lines to pack
|
875
|
+
return self._response_for_select_line(
|
876
|
+
picking,
|
877
|
+
message=message,
|
878
|
+
)
|
879
|
+
|
880
|
+
def scan_package_action(self, picking_id, selected_line_ids, barcode):
|
881
|
+
"""Scan a package, a lot, a product or a package to handle a line
|
882
|
+
|
883
|
+
When a package is scanned (only delivery ones), if the package is known
|
884
|
+
as the destination package of one of the lines or is the source package
|
885
|
+
of a selected line, the package is set to be the destination package of
|
886
|
+
all the lines to pack.
|
887
|
+
|
888
|
+
When a product is scanned, it selects (set qty_done = reserved qty) or
|
889
|
+
deselects (set qty_done = 0) the move lines for this product. Only
|
890
|
+
products not tracked by lot can use this.
|
891
|
+
|
892
|
+
When a lot is scanned, it does the same as for the products but based
|
893
|
+
on the lot.
|
894
|
+
|
895
|
+
When a packaging type (one without related product) is scanned, a new
|
896
|
+
package is created and set as destination of the lines to pack.
|
897
|
+
|
898
|
+
Lines to pack are move lines in the list of ``selected_line_ids``
|
899
|
+
where ``qty_done`` > 0 and have not been packed yet
|
900
|
+
(``shopfloor_checkout_done is False``).
|
901
|
+
|
902
|
+
Transitions:
|
903
|
+
* select_package: when a product or lot is scanned to select/deselect,
|
904
|
+
the client app has to show the same screen with the updated selection
|
905
|
+
* select_line: when a package or packaging type is scanned, move lines
|
906
|
+
have been put in package and we can return back to this state to handle
|
907
|
+
the other lines
|
908
|
+
* summary: if there is no other lines, go to the summary screen to be able
|
909
|
+
to close the stock picking
|
910
|
+
"""
|
911
|
+
picking = self.env["stock.picking"].browse(picking_id)
|
912
|
+
message = self._check_picking_status(picking)
|
913
|
+
if message:
|
914
|
+
return self._response_for_select_document(message=message)
|
915
|
+
|
916
|
+
selected_lines = self.env["stock.move.line"].browse(selected_line_ids).exists()
|
917
|
+
search_result = self._scan_package_find(picking, barcode)
|
918
|
+
result_handler = getattr(
|
919
|
+
self, "_scan_package_action_from_" + search_result.type
|
920
|
+
)
|
921
|
+
return result_handler(picking, selected_lines, search_result.record)
|
922
|
+
|
923
|
+
def _scan_package_find(self, picking, barcode, search_types=None):
|
924
|
+
search = self._actions_for("search")
|
925
|
+
search_types = (
|
926
|
+
"package",
|
927
|
+
"product",
|
928
|
+
"packaging",
|
929
|
+
"lot",
|
930
|
+
"serial",
|
931
|
+
"delivery_packaging",
|
932
|
+
)
|
933
|
+
return search.find(
|
934
|
+
barcode,
|
935
|
+
types=search_types,
|
936
|
+
handler_kw=dict(
|
937
|
+
lot=dict(products=picking.move_ids.product_id),
|
938
|
+
serial=dict(products=picking.move_ids.product_id),
|
939
|
+
),
|
940
|
+
)
|
941
|
+
|
942
|
+
def _scan_package_action_from_product(
|
943
|
+
self, picking, selected_lines, product, packaging=None, **kw
|
944
|
+
):
|
945
|
+
if product.tracking in ("lot", "serial"):
|
946
|
+
return self._response_for_select_package(
|
947
|
+
picking,
|
948
|
+
selected_lines,
|
949
|
+
message=self.msg_store.scan_lot_on_product_tracked_by_lot(),
|
950
|
+
)
|
951
|
+
product_lines = selected_lines.filtered(lambda l: l.product_id == product)
|
952
|
+
if self.work.menu.no_prefill_qty:
|
953
|
+
quantity_increment = packaging.qty if packaging else 1
|
954
|
+
return self._increment_custom_qty(
|
955
|
+
picking,
|
956
|
+
selected_lines,
|
957
|
+
fields.first(product_lines),
|
958
|
+
quantity_increment,
|
959
|
+
)
|
960
|
+
return self._switch_line_qty_done(picking, selected_lines, product_lines)
|
961
|
+
|
962
|
+
def _scan_package_action_from_packaging(
|
963
|
+
self, picking, selected_lines, packaging, **kw
|
964
|
+
):
|
965
|
+
return self._scan_package_action_from_product(
|
966
|
+
picking, selected_lines, packaging.product_id, packaging=packaging
|
967
|
+
)
|
968
|
+
|
969
|
+
def _scan_package_action_from_lot(self, picking, selected_lines, lot, **kw):
|
970
|
+
lot_lines = selected_lines.filtered(lambda l: l.lot_id == lot)
|
971
|
+
if self.work.menu.no_prefill_qty:
|
972
|
+
return self._increment_custom_qty(
|
973
|
+
picking, selected_lines, fields.first(lot_lines), 1
|
974
|
+
)
|
975
|
+
return self._switch_line_qty_done(picking, selected_lines, lot_lines)
|
976
|
+
|
977
|
+
def _scan_package_action_from_serial(self, picking, selection_lines, lot, **kw):
|
978
|
+
# Search for serial number is actually the same as searching for lot (as of v14...)
|
979
|
+
return self._scan_package_action_from_lot(picking, selection_lines, lot, **kw)
|
980
|
+
|
981
|
+
def _scan_package_action_from_package(self, picking, selected_lines, package, **kw):
|
982
|
+
if not package.package_type_id:
|
983
|
+
return self._response_for_select_package(
|
984
|
+
picking,
|
985
|
+
selected_lines,
|
986
|
+
message=self.msg_store.dest_package_not_valid(package),
|
987
|
+
)
|
988
|
+
return self._put_lines_in_package(picking, selected_lines, package)
|
989
|
+
|
990
|
+
def _scan_package_action_from_delivery_packaging(
|
991
|
+
self, picking, selected_lines, packaging, **kw
|
992
|
+
):
|
993
|
+
carrier = self._get_carrier(picking)
|
994
|
+
if carrier:
|
995
|
+
# Validate against carrier
|
996
|
+
is_valid = self._packaging_type_good_for_carrier(packaging, carrier)
|
997
|
+
else:
|
998
|
+
is_valid = True
|
999
|
+
if carrier and not is_valid:
|
1000
|
+
return self._response_for_select_package(
|
1001
|
+
picking,
|
1002
|
+
selected_lines,
|
1003
|
+
message=self.msg_store.packaging_invalid_for_carrier(
|
1004
|
+
packaging, carrier
|
1005
|
+
),
|
1006
|
+
)
|
1007
|
+
return self._create_and_assign_new_packaging(picking, selected_lines, packaging)
|
1008
|
+
|
1009
|
+
def _scan_package_action_from_none(self, picking, selected_lines, record, **kw):
|
1010
|
+
return self._response_for_select_package(
|
1011
|
+
picking, selected_lines, message=self.msg_store.barcode_not_found()
|
1012
|
+
)
|
1013
|
+
|
1014
|
+
def _get_carrier(self, picking):
|
1015
|
+
return picking.ship_carrier_id or picking.carrier_id
|
1016
|
+
|
1017
|
+
def _packaging_type_good_for_carrier(self, packaging, carrier):
|
1018
|
+
actions = self._actions_for("packaging")
|
1019
|
+
return actions.packaging_type_valid_for_carrier(packaging, carrier)
|
1020
|
+
|
1021
|
+
def _packaging_good_for_carrier(self, packaging, carrier):
|
1022
|
+
actions = self._actions_for("packaging")
|
1023
|
+
return actions.packaging_valid_for_carrier(packaging, carrier)
|
1024
|
+
|
1025
|
+
def _get_available_delivery_packaging(self, picking):
|
1026
|
+
model = self.env["stock.package.type"]
|
1027
|
+
carrier = picking.ship_carrier_id or picking.carrier_id
|
1028
|
+
if not carrier:
|
1029
|
+
return model.browse()
|
1030
|
+
return model.search(
|
1031
|
+
[("package_carrier_type", "=", carrier.delivery_type or "none")],
|
1032
|
+
order="name",
|
1033
|
+
)
|
1034
|
+
|
1035
|
+
def list_delivery_packaging(self, picking_id, selected_line_ids):
|
1036
|
+
"""List available delivery packaging for given picking.
|
1037
|
+
|
1038
|
+
Transitions:
|
1039
|
+
* select_delivery_packaging: list available delivery packaging, the
|
1040
|
+
user has to choose one to create the new package
|
1041
|
+
* select_package: when no delivery packaging is available
|
1042
|
+
"""
|
1043
|
+
picking = self.env["stock.picking"].browse(picking_id)
|
1044
|
+
message = self._check_picking_status(picking)
|
1045
|
+
if message:
|
1046
|
+
return self._response_for_select_document(message=message)
|
1047
|
+
selected_lines = self.env["stock.move.line"].browse(selected_line_ids).exists()
|
1048
|
+
delivery_packaging = self._get_available_delivery_packaging(picking)
|
1049
|
+
if not delivery_packaging:
|
1050
|
+
return self._response_for_select_package(
|
1051
|
+
picking,
|
1052
|
+
selected_lines,
|
1053
|
+
message=self.msg_store.no_delivery_packaging_available(),
|
1054
|
+
)
|
1055
|
+
response = self._check_allowed_qty_done(picking, selected_lines)
|
1056
|
+
if response:
|
1057
|
+
return response
|
1058
|
+
return self._response_for_select_delivery_packaging(picking, delivery_packaging)
|
1059
|
+
|
1060
|
+
def new_package(self, picking_id, selected_line_ids, package_type_id=None):
|
1061
|
+
"""Add all selected lines in a new package
|
1062
|
+
|
1063
|
+
It creates a new package and set it as the destination package of all
|
1064
|
+
the selected lines.
|
1065
|
+
|
1066
|
+
Selected lines are move lines in the list of ``move_line_ids`` where
|
1067
|
+
``qty_done`` > 0 and have no destination package
|
1068
|
+
(shopfloor_checkout_done is False).
|
1069
|
+
|
1070
|
+
Transitions:
|
1071
|
+
* select_line: goes back to selection of lines to work on next lines
|
1072
|
+
"""
|
1073
|
+
picking = self.env["stock.picking"].browse(picking_id)
|
1074
|
+
message = self._check_picking_status(picking)
|
1075
|
+
if message:
|
1076
|
+
return self._response_for_select_document(message=message)
|
1077
|
+
packaging = None
|
1078
|
+
if package_type_id:
|
1079
|
+
packaging = self.env["stock.package.type"].browse(package_type_id).exists()
|
1080
|
+
selected_lines = self.env["stock.move.line"].browse(selected_line_ids).exists()
|
1081
|
+
return self._create_and_assign_new_packaging(picking, selected_lines, packaging)
|
1082
|
+
|
1083
|
+
def no_package(self, picking_id, selected_line_ids):
|
1084
|
+
"""Process all selected lines without any package.
|
1085
|
+
|
1086
|
+
Selected lines are move lines in the list of ``move_line_ids`` where
|
1087
|
+
``qty_done`` > 0 and have no destination package
|
1088
|
+
(shopfloor_checkout_done is False).
|
1089
|
+
|
1090
|
+
Transitions:
|
1091
|
+
* select_line: goes back to selection of lines to work on next lines
|
1092
|
+
"""
|
1093
|
+
if self.options.get("checkout__disable_no_package"):
|
1094
|
+
raise BadRequest("`checkout.no_package` endpoint is not enabled")
|
1095
|
+
picking = self.env["stock.picking"].browse(picking_id)
|
1096
|
+
message = self._check_picking_status(picking)
|
1097
|
+
if message:
|
1098
|
+
return self._response_for_select_document(message=message)
|
1099
|
+
selected_lines = self.env["stock.move.line"].browse(selected_line_ids).exists()
|
1100
|
+
selected_lines.write(
|
1101
|
+
{"shopfloor_checkout_done": True, "result_package_id": False}
|
1102
|
+
)
|
1103
|
+
response = self._check_allowed_qty_done(picking, selected_lines)
|
1104
|
+
if response:
|
1105
|
+
return response
|
1106
|
+
return self._response_for_select_line(
|
1107
|
+
picking,
|
1108
|
+
message={
|
1109
|
+
"message_type": "success",
|
1110
|
+
"body": _("Product(s) processed as raw product(s)"),
|
1111
|
+
},
|
1112
|
+
)
|
1113
|
+
|
1114
|
+
def list_dest_package(self, picking_id, selected_line_ids):
|
1115
|
+
"""Return a list of packages the user can select for the lines
|
1116
|
+
|
1117
|
+
Only valid packages must be proposed. Look at ``scan_dest_package``
|
1118
|
+
for the conditions to be valid.
|
1119
|
+
|
1120
|
+
Transitions:
|
1121
|
+
* select_dest_package: selection screen
|
1122
|
+
* select_package: when no package is available
|
1123
|
+
"""
|
1124
|
+
picking = self.env["stock.picking"].browse(picking_id)
|
1125
|
+
message = self._check_picking_status(picking)
|
1126
|
+
if message:
|
1127
|
+
return self._response_for_select_document(message=message)
|
1128
|
+
lines = self.env["stock.move.line"].browse(selected_line_ids).exists()
|
1129
|
+
response = self._check_allowed_qty_done(picking, lines)
|
1130
|
+
if response:
|
1131
|
+
return response
|
1132
|
+
return self._response_for_select_dest_package(picking, lines)
|
1133
|
+
|
1134
|
+
def _check_allowed_qty_done(self, picking, lines):
|
1135
|
+
for line in lines:
|
1136
|
+
# Do not allow to proceed if the qty_done of
|
1137
|
+
# any of the selected lines
|
1138
|
+
# is higher than the quantity to do.
|
1139
|
+
if line.qty_done > line.reserved_uom_qty:
|
1140
|
+
return self._response_for_select_package(
|
1141
|
+
picking,
|
1142
|
+
lines,
|
1143
|
+
message=self.msg_store.selected_lines_qty_done_higher_than_allowed(),
|
1144
|
+
)
|
1145
|
+
|
1146
|
+
def _set_dest_package_from_selection(self, picking, selected_lines, package):
|
1147
|
+
if not self._is_package_allowed(picking, package):
|
1148
|
+
return self._response_for_select_dest_package(
|
1149
|
+
picking,
|
1150
|
+
selected_lines,
|
1151
|
+
message=self.msg_store.dest_package_not_valid(package),
|
1152
|
+
)
|
1153
|
+
return self._pack_lines(picking, selected_lines, package)
|
1154
|
+
|
1155
|
+
def scan_dest_package(self, picking_id, selected_line_ids, barcode):
|
1156
|
+
"""Scan destination package for lines
|
1157
|
+
|
1158
|
+
Set the destination package on the selected lines with a `qty_done` if
|
1159
|
+
the package is valid. It is valid when one of:
|
1160
|
+
|
1161
|
+
* it is already the destination package of another line of the stock.picking
|
1162
|
+
* it is the source package of the selected lines
|
1163
|
+
|
1164
|
+
Note: by default, Odoo puts the same destination package as the source
|
1165
|
+
package on lines.
|
1166
|
+
|
1167
|
+
Transitions:
|
1168
|
+
* select_dest_package: error when scanning package
|
1169
|
+
* select_line: lines to package remain
|
1170
|
+
* summary: all lines are put in packages
|
1171
|
+
"""
|
1172
|
+
picking = self.env["stock.picking"].browse(picking_id)
|
1173
|
+
message = self._check_picking_status(picking)
|
1174
|
+
if message:
|
1175
|
+
return self._response_for_select_document(message=message)
|
1176
|
+
lines = self.env["stock.move.line"].browse(selected_line_ids).exists()
|
1177
|
+
search = self._actions_for("search")
|
1178
|
+
package = search.package_from_scan(barcode)
|
1179
|
+
if not package:
|
1180
|
+
return self._response_for_select_dest_package(
|
1181
|
+
picking,
|
1182
|
+
lines,
|
1183
|
+
message=self.msg_store.package_not_found_for_barcode(barcode),
|
1184
|
+
)
|
1185
|
+
return self._set_dest_package_from_selection(picking, lines, package)
|
1186
|
+
|
1187
|
+
def set_dest_package(self, picking_id, selected_line_ids, package_id):
|
1188
|
+
"""Set destination package for lines from a package id
|
1189
|
+
|
1190
|
+
Used by the list obtained from ``list_dest_package``.
|
1191
|
+
|
1192
|
+
The validity is the same as ``scan_dest_package``.
|
1193
|
+
|
1194
|
+
Transitions:
|
1195
|
+
* select_dest_package: error when selecting package
|
1196
|
+
* select_line: lines to package remain
|
1197
|
+
* summary: all lines are put in packages
|
1198
|
+
"""
|
1199
|
+
picking = self.env["stock.picking"].browse(picking_id)
|
1200
|
+
message = self._check_picking_status(picking)
|
1201
|
+
if message:
|
1202
|
+
return self._response_for_select_document(message=message)
|
1203
|
+
lines = self.env["stock.move.line"].browse(selected_line_ids).exists()
|
1204
|
+
package = self.env["stock.quant.package"].browse(package_id).exists()
|
1205
|
+
if not package:
|
1206
|
+
return self._response_for_select_dest_package(
|
1207
|
+
picking,
|
1208
|
+
lines,
|
1209
|
+
message=self.msg_store.record_not_found(),
|
1210
|
+
)
|
1211
|
+
return self._set_dest_package_from_selection(picking, lines, package)
|
1212
|
+
|
1213
|
+
def _auto_post_lines(self, selected_lines):
|
1214
|
+
moves = self.env["stock.move"]
|
1215
|
+
for line in selected_lines:
|
1216
|
+
move = line.move_id.split_other_move_lines(line, intersection=True)
|
1217
|
+
moves = moves | move
|
1218
|
+
moves.extract_and_action_done()
|
1219
|
+
|
1220
|
+
def summary(self, picking_id):
|
1221
|
+
"""Return information for the summary screen
|
1222
|
+
|
1223
|
+
Transitions:
|
1224
|
+
* summary
|
1225
|
+
"""
|
1226
|
+
picking = self.env["stock.picking"].browse(picking_id)
|
1227
|
+
message = self._check_picking_status(picking)
|
1228
|
+
if message:
|
1229
|
+
return self._response_for_select_document(message=message)
|
1230
|
+
return self._response_for_summary(picking)
|
1231
|
+
|
1232
|
+
def _get_allowed_packaging(self):
|
1233
|
+
return self.env["stock.package.type"].search([])
|
1234
|
+
|
1235
|
+
def list_packaging(self, picking_id, package_id):
|
1236
|
+
"""List the available package types for a package
|
1237
|
+
|
1238
|
+
For a package, we can change the packaging. The available
|
1239
|
+
packaging are the ones with no product.
|
1240
|
+
|
1241
|
+
Transitions:
|
1242
|
+
* change_packaging
|
1243
|
+
* summary: if the package_id no longer exists
|
1244
|
+
"""
|
1245
|
+
picking = self.env["stock.picking"].browse(picking_id)
|
1246
|
+
message = self._check_picking_status(picking)
|
1247
|
+
if message:
|
1248
|
+
return self._response_for_select_document(message=message)
|
1249
|
+
package = self.env["stock.quant.package"].browse(package_id).exists()
|
1250
|
+
packaging_list = self._get_allowed_packaging()
|
1251
|
+
return self._response_for_change_packaging(picking, package, packaging_list)
|
1252
|
+
|
1253
|
+
def set_packaging(self, picking_id, package_id, package_type_id):
|
1254
|
+
"""Set a package type on a package
|
1255
|
+
|
1256
|
+
Transitions:
|
1257
|
+
* change_packaging: in case of error
|
1258
|
+
* summary
|
1259
|
+
"""
|
1260
|
+
picking = self.env["stock.picking"].browse(picking_id)
|
1261
|
+
message = self._check_picking_status(picking)
|
1262
|
+
if message:
|
1263
|
+
return self._response_for_select_document(message=message)
|
1264
|
+
|
1265
|
+
package = self.env["stock.quant.package"].browse(package_id).exists()
|
1266
|
+
packaging = self.env["stock.package.type"].browse(package_type_id).exists()
|
1267
|
+
if not (package and packaging):
|
1268
|
+
return self._response_for_summary(
|
1269
|
+
picking, message=self.msg_store.record_not_found()
|
1270
|
+
)
|
1271
|
+
package.package_type_id = packaging
|
1272
|
+
return self._response_for_summary(
|
1273
|
+
picking,
|
1274
|
+
message={
|
1275
|
+
"message_type": "success",
|
1276
|
+
"body": _("Packaging changed on package {}").format(package.name),
|
1277
|
+
},
|
1278
|
+
)
|
1279
|
+
|
1280
|
+
def cancel_line(self, picking_id, package_id=None, line_id=None):
|
1281
|
+
"""Cancel work done on given line or package.
|
1282
|
+
|
1283
|
+
If package, remove destination package from lines and set qty done to 0.
|
1284
|
+
If line is a raw product, set qty done to 0.
|
1285
|
+
|
1286
|
+
All the move lines with the package as ``result_package_id`` have their
|
1287
|
+
``result_package_id`` reset to the source package (default odoo behavior)
|
1288
|
+
and their ``qty_done`` set to 0.
|
1289
|
+
|
1290
|
+
It flags ``shopfloor_checkout_done`` to False
|
1291
|
+
so they have to be processed again.
|
1292
|
+
|
1293
|
+
Transitions:
|
1294
|
+
* summary: if package or line are not found
|
1295
|
+
* select_line: when package or line has been canceled
|
1296
|
+
"""
|
1297
|
+
picking = self.env["stock.picking"].browse(picking_id)
|
1298
|
+
message = self._check_picking_status(picking)
|
1299
|
+
if message:
|
1300
|
+
return self._response_for_select_document(message=message)
|
1301
|
+
|
1302
|
+
package = self.env["stock.quant.package"].browse(package_id).exists()
|
1303
|
+
line = self.env["stock.move.line"].browse(line_id).exists()
|
1304
|
+
if not package and not line:
|
1305
|
+
return self._response_for_summary(
|
1306
|
+
picking, message=self.msg_store.record_not_found()
|
1307
|
+
)
|
1308
|
+
|
1309
|
+
if package:
|
1310
|
+
move_lines = picking.move_line_ids.filtered(
|
1311
|
+
lambda l: self._filter_lines_checkout_done(l)
|
1312
|
+
and l.result_package_id == package
|
1313
|
+
)
|
1314
|
+
for move_line in move_lines:
|
1315
|
+
move_line.write(
|
1316
|
+
{
|
1317
|
+
"qty_done": 0,
|
1318
|
+
"result_package_id": move_line.package_id,
|
1319
|
+
"shopfloor_checkout_done": False,
|
1320
|
+
}
|
1321
|
+
)
|
1322
|
+
msg = _("Package cancelled")
|
1323
|
+
if line:
|
1324
|
+
line.write({"qty_done": 0, "shopfloor_checkout_done": False})
|
1325
|
+
msg = _("Line cancelled")
|
1326
|
+
return self._response_for_select_line(
|
1327
|
+
picking, message={"message_type": "success", "body": msg}
|
1328
|
+
)
|
1329
|
+
|
1330
|
+
def done(self, picking_id, confirmation=False):
|
1331
|
+
"""Set the moves as done
|
1332
|
+
|
1333
|
+
If some lines have not the full ``qty_done`` or no destination package set,
|
1334
|
+
a confirmation is asked to the user.
|
1335
|
+
|
1336
|
+
Transitions:
|
1337
|
+
* summary: in case of error
|
1338
|
+
* select_document: after done, goes back to start
|
1339
|
+
* confirm_done: confirm a partial
|
1340
|
+
"""
|
1341
|
+
picking = self.env["stock.picking"].browse(picking_id)
|
1342
|
+
message = self._check_picking_status(picking)
|
1343
|
+
if message:
|
1344
|
+
return self._response_for_select_document(message=message)
|
1345
|
+
lines = picking.move_line_ids
|
1346
|
+
if not confirmation:
|
1347
|
+
if not all(line.qty_done == line.reserved_uom_qty for line in lines):
|
1348
|
+
return self._response_for_summary(
|
1349
|
+
picking,
|
1350
|
+
need_confirm=True,
|
1351
|
+
message=self.msg_store.transfer_confirm_done(),
|
1352
|
+
)
|
1353
|
+
elif not all(line.shopfloor_checkout_done for line in lines):
|
1354
|
+
return self._response_for_summary(
|
1355
|
+
picking,
|
1356
|
+
need_confirm=True,
|
1357
|
+
message={
|
1358
|
+
"message_type": "warning",
|
1359
|
+
"body": _("Remaining raw product not packed, proceed anyway?"),
|
1360
|
+
},
|
1361
|
+
)
|
1362
|
+
stock = self._actions_for("stock")
|
1363
|
+
lines_done = self._lines_checkout_done(picking)
|
1364
|
+
stock.validate_moves(lines_done.move_id)
|
1365
|
+
return self._response_for_select_document(
|
1366
|
+
message=self.msg_store.transfer_done_success(lines_done.picking_id)
|
1367
|
+
)
|
1368
|
+
|
1369
|
+
|
1370
|
+
class ShopfloorCheckoutValidator(Component):
|
1371
|
+
"""Validators for the Checkout endpoints"""
|
1372
|
+
|
1373
|
+
_inherit = "base.shopfloor.validator"
|
1374
|
+
_name = "shopfloor.checkout.validator"
|
1375
|
+
_usage = "checkout.validator"
|
1376
|
+
|
1377
|
+
def scan_document(self):
|
1378
|
+
return {"barcode": {"required": True, "type": "string"}}
|
1379
|
+
|
1380
|
+
def list_stock_picking(self):
|
1381
|
+
return {}
|
1382
|
+
|
1383
|
+
def select(self):
|
1384
|
+
return {"picking_id": {"coerce": to_int, "required": True, "type": "integer"}}
|
1385
|
+
|
1386
|
+
def scan_line(self):
|
1387
|
+
return {
|
1388
|
+
"picking_id": {"coerce": to_int, "required": True, "type": "integer"},
|
1389
|
+
"barcode": {"required": True, "type": "string"},
|
1390
|
+
"confirm_pack_all": {
|
1391
|
+
"type": "boolean",
|
1392
|
+
"nullable": True,
|
1393
|
+
"required": False,
|
1394
|
+
},
|
1395
|
+
}
|
1396
|
+
|
1397
|
+
def select_line(self):
|
1398
|
+
return {
|
1399
|
+
"picking_id": {"coerce": to_int, "required": True, "type": "integer"},
|
1400
|
+
"package_id": {"coerce": to_int, "required": False, "type": "integer"},
|
1401
|
+
"move_line_id": {"coerce": to_int, "required": False, "type": "integer"},
|
1402
|
+
}
|
1403
|
+
|
1404
|
+
def reset_line_qty(self):
|
1405
|
+
return {
|
1406
|
+
"picking_id": {"coerce": to_int, "required": True, "type": "integer"},
|
1407
|
+
"selected_line_ids": {
|
1408
|
+
"type": "list",
|
1409
|
+
"required": True,
|
1410
|
+
"schema": {"coerce": to_int, "required": True, "type": "integer"},
|
1411
|
+
},
|
1412
|
+
"move_line_id": {"coerce": to_int, "required": True, "type": "integer"},
|
1413
|
+
}
|
1414
|
+
|
1415
|
+
def set_line_qty(self):
|
1416
|
+
return {
|
1417
|
+
"picking_id": {"coerce": to_int, "required": True, "type": "integer"},
|
1418
|
+
"selected_line_ids": {
|
1419
|
+
"type": "list",
|
1420
|
+
"required": True,
|
1421
|
+
"schema": {"coerce": to_int, "required": True, "type": "integer"},
|
1422
|
+
},
|
1423
|
+
"move_line_id": {"coerce": to_int, "required": True, "type": "integer"},
|
1424
|
+
}
|
1425
|
+
|
1426
|
+
def set_custom_qty(self):
|
1427
|
+
return {
|
1428
|
+
"picking_id": {"coerce": to_int, "required": True, "type": "integer"},
|
1429
|
+
"selected_line_ids": {
|
1430
|
+
"type": "list",
|
1431
|
+
"required": True,
|
1432
|
+
"schema": {"coerce": to_int, "required": True, "type": "integer"},
|
1433
|
+
},
|
1434
|
+
"move_line_id": {"coerce": to_int, "required": True, "type": "integer"},
|
1435
|
+
"qty_done": {"coerce": to_float, "required": True, "type": "float"},
|
1436
|
+
}
|
1437
|
+
|
1438
|
+
def scan_package_action(self):
|
1439
|
+
return {
|
1440
|
+
"picking_id": {"coerce": to_int, "required": True, "type": "integer"},
|
1441
|
+
"selected_line_ids": {
|
1442
|
+
"type": "list",
|
1443
|
+
"required": True,
|
1444
|
+
"schema": {"coerce": to_int, "required": True, "type": "integer"},
|
1445
|
+
},
|
1446
|
+
"barcode": {"required": True, "type": "string"},
|
1447
|
+
}
|
1448
|
+
|
1449
|
+
def list_delivery_packaging(self):
|
1450
|
+
return {
|
1451
|
+
"picking_id": {"coerce": to_int, "required": True, "type": "integer"},
|
1452
|
+
"selected_line_ids": {
|
1453
|
+
"type": "list",
|
1454
|
+
"required": True,
|
1455
|
+
"schema": {"coerce": to_int, "required": True, "type": "integer"},
|
1456
|
+
},
|
1457
|
+
}
|
1458
|
+
|
1459
|
+
def new_package(self):
|
1460
|
+
return {
|
1461
|
+
"picking_id": {"coerce": to_int, "required": True, "type": "integer"},
|
1462
|
+
"selected_line_ids": {
|
1463
|
+
"type": "list",
|
1464
|
+
"required": True,
|
1465
|
+
"schema": {"coerce": to_int, "required": True, "type": "integer"},
|
1466
|
+
},
|
1467
|
+
"package_type_id": {
|
1468
|
+
"coerce": to_int,
|
1469
|
+
"required": False,
|
1470
|
+
"type": "integer",
|
1471
|
+
},
|
1472
|
+
}
|
1473
|
+
|
1474
|
+
def no_package(self):
|
1475
|
+
return self.new_package()
|
1476
|
+
|
1477
|
+
def list_dest_package(self):
|
1478
|
+
return {
|
1479
|
+
"picking_id": {"coerce": to_int, "required": True, "type": "integer"},
|
1480
|
+
"selected_line_ids": {
|
1481
|
+
"type": "list",
|
1482
|
+
"required": True,
|
1483
|
+
"schema": {"coerce": to_int, "required": True, "type": "integer"},
|
1484
|
+
},
|
1485
|
+
}
|
1486
|
+
|
1487
|
+
def scan_dest_package(self):
|
1488
|
+
return {
|
1489
|
+
"picking_id": {"coerce": to_int, "required": True, "type": "integer"},
|
1490
|
+
"selected_line_ids": {
|
1491
|
+
"type": "list",
|
1492
|
+
"required": True,
|
1493
|
+
"schema": {"coerce": to_int, "required": True, "type": "integer"},
|
1494
|
+
},
|
1495
|
+
"barcode": {"required": True, "type": "string"},
|
1496
|
+
}
|
1497
|
+
|
1498
|
+
def set_dest_package(self):
|
1499
|
+
return {
|
1500
|
+
"picking_id": {"coerce": to_int, "required": True, "type": "integer"},
|
1501
|
+
"selected_line_ids": {
|
1502
|
+
"type": "list",
|
1503
|
+
"required": True,
|
1504
|
+
"schema": {"coerce": to_int, "required": True, "type": "integer"},
|
1505
|
+
},
|
1506
|
+
"package_id": {"coerce": to_int, "required": True, "type": "integer"},
|
1507
|
+
}
|
1508
|
+
|
1509
|
+
def summary(self):
|
1510
|
+
return {"picking_id": {"coerce": to_int, "required": True, "type": "integer"}}
|
1511
|
+
|
1512
|
+
def list_packaging(self):
|
1513
|
+
return {
|
1514
|
+
"picking_id": {"coerce": to_int, "required": True, "type": "integer"},
|
1515
|
+
"package_id": {"coerce": to_int, "required": True, "type": "integer"},
|
1516
|
+
}
|
1517
|
+
|
1518
|
+
def set_packaging(self):
|
1519
|
+
return {
|
1520
|
+
"picking_id": {"coerce": to_int, "required": True, "type": "integer"},
|
1521
|
+
"package_id": {"coerce": to_int, "required": True, "type": "integer"},
|
1522
|
+
"package_type_id": {
|
1523
|
+
"coerce": to_int,
|
1524
|
+
"required": True,
|
1525
|
+
"type": "integer",
|
1526
|
+
},
|
1527
|
+
}
|
1528
|
+
|
1529
|
+
def cancel_line(self):
|
1530
|
+
return {
|
1531
|
+
"picking_id": {"coerce": to_int, "required": True, "type": "integer"},
|
1532
|
+
"package_id": {
|
1533
|
+
"coerce": to_int,
|
1534
|
+
"required": False,
|
1535
|
+
"type": "integer",
|
1536
|
+
# excludes does not set the other as not required??? :/
|
1537
|
+
"excludes": "line_id",
|
1538
|
+
},
|
1539
|
+
"line_id": {
|
1540
|
+
"coerce": to_int,
|
1541
|
+
"required": False,
|
1542
|
+
"type": "integer",
|
1543
|
+
"excludes": "package_id",
|
1544
|
+
},
|
1545
|
+
}
|
1546
|
+
|
1547
|
+
def done(self):
|
1548
|
+
return {
|
1549
|
+
"picking_id": {"coerce": to_int, "required": True, "type": "integer"},
|
1550
|
+
"confirmation": {"type": "boolean", "nullable": True, "required": False},
|
1551
|
+
}
|
1552
|
+
|
1553
|
+
|
1554
|
+
class ShopfloorCheckoutValidatorResponse(Component):
|
1555
|
+
"""Validators for the Checkout endpoints responses"""
|
1556
|
+
|
1557
|
+
_inherit = "base.shopfloor.validator.response"
|
1558
|
+
_name = "shopfloor.checkout.validator.response"
|
1559
|
+
_usage = "checkout.validator.response"
|
1560
|
+
|
1561
|
+
_start_state = "select_document"
|
1562
|
+
|
1563
|
+
def _states(self):
|
1564
|
+
"""List of possible next states
|
1565
|
+
|
1566
|
+
With the schema of the data send to the client to transition
|
1567
|
+
to the next state.
|
1568
|
+
"""
|
1569
|
+
return {
|
1570
|
+
"select_document": {},
|
1571
|
+
"manual_selection": self._schema_selection_list,
|
1572
|
+
"select_line": self._schema_stock_picking_details,
|
1573
|
+
"select_package": dict(
|
1574
|
+
self._schema_selected_lines,
|
1575
|
+
packing_info={"type": "string", "nullable": True},
|
1576
|
+
no_package_enabled={
|
1577
|
+
"type": "boolean",
|
1578
|
+
"nullable": True,
|
1579
|
+
"required": False,
|
1580
|
+
},
|
1581
|
+
),
|
1582
|
+
"change_quantity": self._schema_selected_lines,
|
1583
|
+
"select_dest_package": self._schema_select_package,
|
1584
|
+
"select_delivery_packaging": self._schema_select_delivery_packaging,
|
1585
|
+
"summary": self._schema_summary,
|
1586
|
+
"change_packaging": self._schema_select_packaging,
|
1587
|
+
"confirm_done": self._schema_confirm_done,
|
1588
|
+
}
|
1589
|
+
|
1590
|
+
def _schema_stock_picking(self, lines_with_packaging=False):
|
1591
|
+
schema = self.schemas.picking()
|
1592
|
+
schema.update(
|
1593
|
+
{
|
1594
|
+
"move_lines": self.schemas._schema_list_of(
|
1595
|
+
self.schemas.move_line(with_packaging=lines_with_packaging)
|
1596
|
+
)
|
1597
|
+
}
|
1598
|
+
)
|
1599
|
+
return {"picking": self.schemas._schema_dict_of(schema, required=True)}
|
1600
|
+
|
1601
|
+
@property
|
1602
|
+
def _schema_stock_picking_details(self):
|
1603
|
+
return dict(
|
1604
|
+
self._schema_stock_picking(),
|
1605
|
+
group_lines_by_location={"type": "boolean"},
|
1606
|
+
show_oneline_package_content={"type": "boolean"},
|
1607
|
+
need_confirm_pack_all={"type": "boolean"},
|
1608
|
+
)
|
1609
|
+
|
1610
|
+
@property
|
1611
|
+
def _schema_summary(self):
|
1612
|
+
return dict(
|
1613
|
+
self._schema_stock_picking(lines_with_packaging=True),
|
1614
|
+
all_processed={"type": "boolean"},
|
1615
|
+
)
|
1616
|
+
|
1617
|
+
@property
|
1618
|
+
def _schema_confirm_done(self):
|
1619
|
+
return self._schema_stock_picking(lines_with_packaging=True)
|
1620
|
+
|
1621
|
+
@property
|
1622
|
+
def _schema_selection_list(self):
|
1623
|
+
return {
|
1624
|
+
"pickings": {
|
1625
|
+
"type": "list",
|
1626
|
+
"schema": {"type": "dict", "schema": self.schemas.picking()},
|
1627
|
+
}
|
1628
|
+
}
|
1629
|
+
|
1630
|
+
@property
|
1631
|
+
def _schema_select_package(self):
|
1632
|
+
return {
|
1633
|
+
"selected_move_lines": {
|
1634
|
+
"type": "list",
|
1635
|
+
"schema": {"type": "dict", "schema": self.schemas.move_line()},
|
1636
|
+
},
|
1637
|
+
"packages": {
|
1638
|
+
"type": "list",
|
1639
|
+
"schema": {
|
1640
|
+
"type": "dict",
|
1641
|
+
"schema": self.schemas.package(with_packaging=True),
|
1642
|
+
},
|
1643
|
+
},
|
1644
|
+
"picking": {"type": "dict", "schema": self.schemas.picking()},
|
1645
|
+
}
|
1646
|
+
|
1647
|
+
@property
|
1648
|
+
def _schema_select_delivery_packaging(self):
|
1649
|
+
return {
|
1650
|
+
"packaging": self.schemas._schema_list_of(
|
1651
|
+
self.schemas.delivery_packaging()
|
1652
|
+
),
|
1653
|
+
}
|
1654
|
+
|
1655
|
+
@property
|
1656
|
+
def _schema_select_packaging(self):
|
1657
|
+
return {
|
1658
|
+
"picking": {"type": "dict", "schema": self.schemas.picking()},
|
1659
|
+
"package": {
|
1660
|
+
"type": "dict",
|
1661
|
+
"schema": self.schemas.package(with_packaging=True),
|
1662
|
+
},
|
1663
|
+
"packaging": {
|
1664
|
+
"type": "list",
|
1665
|
+
"schema": {"type": "dict", "schema": self.schemas.delivery_packaging()},
|
1666
|
+
},
|
1667
|
+
}
|
1668
|
+
|
1669
|
+
@property
|
1670
|
+
def _schema_selected_lines(self):
|
1671
|
+
return {
|
1672
|
+
"selected_move_lines": {
|
1673
|
+
"type": "list",
|
1674
|
+
"schema": {"type": "dict", "schema": self.schemas.move_line()},
|
1675
|
+
},
|
1676
|
+
"picking": {"type": "dict", "schema": self.schemas.picking()},
|
1677
|
+
}
|
1678
|
+
|
1679
|
+
def scan_document(self):
|
1680
|
+
return self._response_schema(
|
1681
|
+
next_states={"select_document", "select_line", "summary"}
|
1682
|
+
)
|
1683
|
+
|
1684
|
+
def list_stock_picking(self):
|
1685
|
+
return self._response_schema(next_states={"manual_selection"})
|
1686
|
+
|
1687
|
+
def select(self):
|
1688
|
+
return self._response_schema(
|
1689
|
+
next_states={"manual_selection", "summary", "select_line"}
|
1690
|
+
)
|
1691
|
+
|
1692
|
+
def scan_line(self):
|
1693
|
+
return self._response_schema(
|
1694
|
+
next_states={"select_line", "select_package", "summary"}
|
1695
|
+
)
|
1696
|
+
|
1697
|
+
def select_line(self):
|
1698
|
+
return self.scan_line()
|
1699
|
+
|
1700
|
+
def reset_line_qty(self):
|
1701
|
+
return self._response_schema(next_states={"select_package"})
|
1702
|
+
|
1703
|
+
def set_line_qty(self):
|
1704
|
+
return self._response_schema(next_states={"select_package"})
|
1705
|
+
|
1706
|
+
def set_custom_qty(self):
|
1707
|
+
return self._response_schema(next_states={"select_package"})
|
1708
|
+
|
1709
|
+
def scan_package_action(self):
|
1710
|
+
return self._response_schema(
|
1711
|
+
next_states={"select_package", "select_line", "summary"}
|
1712
|
+
)
|
1713
|
+
|
1714
|
+
def list_delivery_packaging(self):
|
1715
|
+
return self._response_schema(
|
1716
|
+
next_states={"select_delivery_packaging", "select_package"}
|
1717
|
+
)
|
1718
|
+
|
1719
|
+
def new_package(self):
|
1720
|
+
return self._response_schema(next_states={"select_line", "summary"})
|
1721
|
+
|
1722
|
+
def no_package(self):
|
1723
|
+
return self.new_package()
|
1724
|
+
|
1725
|
+
def list_dest_package(self):
|
1726
|
+
return self._response_schema(
|
1727
|
+
next_states={"select_dest_package", "select_package"}
|
1728
|
+
)
|
1729
|
+
|
1730
|
+
def scan_dest_package(self):
|
1731
|
+
return self._response_schema(
|
1732
|
+
next_states={
|
1733
|
+
"select_dest_package",
|
1734
|
+
"select_package",
|
1735
|
+
"select_line",
|
1736
|
+
"summary",
|
1737
|
+
}
|
1738
|
+
)
|
1739
|
+
|
1740
|
+
def set_dest_package(self):
|
1741
|
+
return self._response_schema(
|
1742
|
+
next_states={
|
1743
|
+
"select_dest_package",
|
1744
|
+
"select_package",
|
1745
|
+
"select_line",
|
1746
|
+
"summary",
|
1747
|
+
}
|
1748
|
+
)
|
1749
|
+
|
1750
|
+
def summary(self):
|
1751
|
+
return self._response_schema(next_states={"summary"})
|
1752
|
+
|
1753
|
+
def list_packaging(self):
|
1754
|
+
return self._response_schema(next_states={"change_packaging", "summary"})
|
1755
|
+
|
1756
|
+
def set_packaging(self):
|
1757
|
+
return self._response_schema(next_states={"change_packaging", "summary"})
|
1758
|
+
|
1759
|
+
def cancel_line(self):
|
1760
|
+
return self._response_schema(next_states={"summary", "select_line"})
|
1761
|
+
|
1762
|
+
def done(self):
|
1763
|
+
return self._response_schema(next_states={"summary", "confirm_done"})
|