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,828 @@
|
|
1
|
+
# Copyright 2020 Camptocamp SA (http://www.camptocamp.com)
|
2
|
+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
3
|
+
from odoo import _, fields
|
4
|
+
from odoo.osv import expression
|
5
|
+
from odoo.tools.float_utils import float_is_zero
|
6
|
+
|
7
|
+
from odoo.addons.base_rest.components.service import to_bool, to_int
|
8
|
+
from odoo.addons.component.core import Component
|
9
|
+
|
10
|
+
|
11
|
+
class Delivery(Component):
|
12
|
+
"""
|
13
|
+
Methods for the Delivery Process
|
14
|
+
|
15
|
+
Deliver the goods by processing the PACK and raw products by delivery order.
|
16
|
+
Last step in the pick/pack/ship steps. (Cluster Picking → Checkout → Delivery)
|
17
|
+
|
18
|
+
Multiple operators could be processing a same delivery order.
|
19
|
+
|
20
|
+
You will find a sequence diagram describing states and endpoints
|
21
|
+
relationships [here](../docs/delivery_diag_seq.png).
|
22
|
+
Keep [the sequence diagram](../docs/delivery_diag_seq.plantuml)
|
23
|
+
up-to-date if you change endpoints.
|
24
|
+
|
25
|
+
Expected:
|
26
|
+
|
27
|
+
* Existing packages are moved to customer location
|
28
|
+
* Products are moved to customer location as raw products
|
29
|
+
* Bin packed products are placed in new shipping package and shipped to customer
|
30
|
+
|
31
|
+
Every time a package, product or lot is scanned, the package level and move line
|
32
|
+
are set to done. When the last line is scanned, the transfer is set to done.
|
33
|
+
Data for the last transfer for which we have been scanning a line if it is not done.
|
34
|
+
When a transfer is scanned, it returns its data to be shown on the screen.
|
35
|
+
|
36
|
+
Flow Diagram: https://www.draw.io/#G1qRenBcezk50ggIazDuu2qOfkTsoIAxXP
|
37
|
+
"""
|
38
|
+
|
39
|
+
_inherit = "base.shopfloor.process"
|
40
|
+
_name = "shopfloor.delivery"
|
41
|
+
_usage = "delivery"
|
42
|
+
_description = __doc__
|
43
|
+
|
44
|
+
def _response_for_deliver(self, picking=None, location=None, message=None):
|
45
|
+
"""Transition to the 'deliver' state
|
46
|
+
|
47
|
+
If no picking is passed, the screen shows an empty screen
|
48
|
+
"""
|
49
|
+
return self._response(
|
50
|
+
next_state="deliver",
|
51
|
+
data={
|
52
|
+
"picking": self.data_detail.picking_detail(picking)
|
53
|
+
if picking
|
54
|
+
else None,
|
55
|
+
"sublocation": self.data.location(
|
56
|
+
location, with_operation_progress=True
|
57
|
+
)
|
58
|
+
if location
|
59
|
+
else None,
|
60
|
+
},
|
61
|
+
message=message,
|
62
|
+
)
|
63
|
+
|
64
|
+
def _response_for_manual_selection(self, pickings, message=None):
|
65
|
+
"""Transition to the 'manual_selection' state
|
66
|
+
|
67
|
+
If no picking is passed, the screen shows an empty screen
|
68
|
+
"""
|
69
|
+
return self._response(
|
70
|
+
next_state="manual_selection",
|
71
|
+
data={
|
72
|
+
"pickings": [
|
73
|
+
self.data_detail.picking_detail(picking) for picking in pickings
|
74
|
+
],
|
75
|
+
},
|
76
|
+
message=message,
|
77
|
+
)
|
78
|
+
|
79
|
+
def _response_for_confirm_done(self, picking, message=None):
|
80
|
+
"""Transition to the 'confirm_done' state."""
|
81
|
+
return self._response(
|
82
|
+
next_state="confirm_done",
|
83
|
+
data={
|
84
|
+
"picking": self.data_detail.picking_detail(picking)
|
85
|
+
if picking
|
86
|
+
else None,
|
87
|
+
},
|
88
|
+
message=message,
|
89
|
+
)
|
90
|
+
|
91
|
+
def scan_deliver(self, barcode, picking_id=None, location_id=None):
|
92
|
+
"""Scan a stock picking, a package/product/lot or a stock location
|
93
|
+
|
94
|
+
When a stock picking is scanned and is partially or fully available, it
|
95
|
+
is returned to show its lines.
|
96
|
+
|
97
|
+
When a package is scanned, and has an available move line part of the
|
98
|
+
expected picking type, the package level is directly set to "done" and
|
99
|
+
the stock picking of the line is returned to work on its other lines.
|
100
|
+
|
101
|
+
When a stock location is scanned and it is a sub-location of an operation
|
102
|
+
type allowed on the current shopfloor menu, the next delivery
|
103
|
+
operations will be put into that location.
|
104
|
+
|
105
|
+
If the barcode is a product or a product's packaging, the move lines
|
106
|
+
for this product are set to done. However, if the product is in more
|
107
|
+
than one package, a package barcode is requested, and if the product is
|
108
|
+
tracked by lot/serial, a lot is asked.
|
109
|
+
|
110
|
+
If the option 'Process as pre-packaged' is enabled on the menu, then
|
111
|
+
when a product's packaging is scanned, the first move line without
|
112
|
+
a source package (bulk line) corresponding to the quantity of the
|
113
|
+
packaging will be set to done.
|
114
|
+
|
115
|
+
If the barcode is a lot, the lines for this lot are set to
|
116
|
+
done. However, if the lot is in more than one package, a package
|
117
|
+
barcode is requested.
|
118
|
+
|
119
|
+
NOTE: see scan_line in the Checkout service.
|
120
|
+
|
121
|
+
When all the available move lines of the stock picking are done, the
|
122
|
+
stock picking is set to done.
|
123
|
+
|
124
|
+
The ``picking_id`` parameter is used to be stateless: if the client
|
125
|
+
sends a wrong barcode, it allows to stay on the last picking with
|
126
|
+
updated data (and we really want to refresh data because several
|
127
|
+
users may work on the same transfer).
|
128
|
+
|
129
|
+
Transitions:
|
130
|
+
* deliver: always return here with the data for the last touched
|
131
|
+
picking or no picking if the picking has been set to done
|
132
|
+
"""
|
133
|
+
location = (
|
134
|
+
self.env["stock.location"].browse(location_id) if location_id else None
|
135
|
+
)
|
136
|
+
if not barcode:
|
137
|
+
return self._response_for_deliver(location=location)
|
138
|
+
search = self._actions_for("search")
|
139
|
+
picking = search.picking_from_scan(barcode)
|
140
|
+
barcode_valid = bool(picking)
|
141
|
+
|
142
|
+
if picking:
|
143
|
+
message = self._check_picking_status(picking)
|
144
|
+
if message:
|
145
|
+
return self._response_for_deliver(location=location, message=message)
|
146
|
+
|
147
|
+
if picking_id:
|
148
|
+
picking = self.env["stock.picking"].browse(picking_id)
|
149
|
+
|
150
|
+
# Validate picking anyway
|
151
|
+
if not barcode_valid:
|
152
|
+
package = search.package_from_scan(barcode)
|
153
|
+
if package:
|
154
|
+
return self._deliver_package(picking, package, location)
|
155
|
+
|
156
|
+
if not barcode_valid:
|
157
|
+
product = search.product_from_scan(barcode)
|
158
|
+
if product:
|
159
|
+
return self._deliver_product(
|
160
|
+
picking, product, product_qty=1, location=location
|
161
|
+
)
|
162
|
+
|
163
|
+
if not barcode_valid:
|
164
|
+
packaging = search.packaging_from_scan(barcode)
|
165
|
+
if packaging:
|
166
|
+
# By scanning a packaging, we want to process
|
167
|
+
# the full quantity of the packaging
|
168
|
+
packaging_qty = packaging.product_uom_id._compute_quantity(
|
169
|
+
packaging.qty, packaging.product_id.uom_id
|
170
|
+
)
|
171
|
+
return self._deliver_product(
|
172
|
+
picking,
|
173
|
+
packaging.product_id,
|
174
|
+
product_qty=packaging_qty,
|
175
|
+
location=location,
|
176
|
+
)
|
177
|
+
|
178
|
+
if not barcode_valid:
|
179
|
+
lot = search.lot_from_scan(barcode)
|
180
|
+
if lot:
|
181
|
+
return self._deliver_lot(picking, lot, product_qty=1, location=location)
|
182
|
+
|
183
|
+
if not barcode_valid:
|
184
|
+
sublocation = search.location_from_scan(barcode)
|
185
|
+
if sublocation and sublocation.is_sublocation_of(
|
186
|
+
self.picking_types.mapped("default_location_src_id")
|
187
|
+
):
|
188
|
+
message = self.msg_store.location_src_set_to_sublocation(sublocation)
|
189
|
+
return self._response_for_deliver(location=sublocation, message=message)
|
190
|
+
|
191
|
+
message = self.msg_store.barcode_not_found() if not barcode_valid else None
|
192
|
+
return self._response_for_deliver(
|
193
|
+
picking=picking, location=location, message=message
|
194
|
+
)
|
195
|
+
|
196
|
+
def _set_lines_done(self, lines, product_qty=None):
|
197
|
+
"""Set done quantities on `lines`.
|
198
|
+
|
199
|
+
Once all lines of a picking have been processed, the picking will be
|
200
|
+
validated automatically.
|
201
|
+
Return `True` if the related picking has been validated.
|
202
|
+
"""
|
203
|
+
allow_prepackaged_product = self.work.menu.allow_prepackaged_product
|
204
|
+
if product_qty: # defined with lot/product/packaging scan
|
205
|
+
# With a product_qty we process only one move line,
|
206
|
+
# so one move to deal with regarding the qty
|
207
|
+
qty_done = lines.move_id.product_id.uom_id._compute_quantity(
|
208
|
+
product_qty, lines.move_id.product_uom
|
209
|
+
)
|
210
|
+
lines.qty_done += qty_done
|
211
|
+
return self._action_picking_done(
|
212
|
+
lines.picking_id, force=allow_prepackaged_product
|
213
|
+
)
|
214
|
+
for line in lines:
|
215
|
+
# note: the package level is automatically set to "is_done" when
|
216
|
+
# the qty_done is full
|
217
|
+
line.qty_done = line.reserved_uom_qty
|
218
|
+
picking = fields.first(lines.mapped("picking_id"))
|
219
|
+
return self._action_picking_done(picking, force=allow_prepackaged_product)
|
220
|
+
|
221
|
+
def _reset_lines(self, lines):
|
222
|
+
for line in lines:
|
223
|
+
# note: the package level "is_done" field is automatically unset
|
224
|
+
# when the qty_done is not full
|
225
|
+
line.qty_done = 0
|
226
|
+
|
227
|
+
def _deliver_package(self, picking, package, location):
|
228
|
+
lines = package.move_line_ids.filtered(
|
229
|
+
lambda l: l.state in ("assigned", "partially_available")
|
230
|
+
)
|
231
|
+
# State of the picking might change while we reach this point: check again!
|
232
|
+
message = self._check_picking_status(lines.mapped("picking_id"))
|
233
|
+
if message:
|
234
|
+
message["body"] = "\n".join(
|
235
|
+
[
|
236
|
+
_("Package {} belongs to a picking without a valid state.").format(
|
237
|
+
package.name
|
238
|
+
),
|
239
|
+
message["body"],
|
240
|
+
]
|
241
|
+
)
|
242
|
+
return self._response_for_deliver(location=location, message=message)
|
243
|
+
if not lines:
|
244
|
+
return self._response_for_deliver(
|
245
|
+
picking=picking,
|
246
|
+
location=location,
|
247
|
+
message=self.msg_store.cannot_move_something_in_picking_type(),
|
248
|
+
)
|
249
|
+
# TODO add a message if any of the lines already had a qty_done > 0
|
250
|
+
new_picking = fields.first(lines.mapped("picking_id"))
|
251
|
+
if self._set_lines_done(lines):
|
252
|
+
return self._response_for_deliver(
|
253
|
+
location=location, message=self.msg_store.transfer_complete(new_picking)
|
254
|
+
)
|
255
|
+
return self._response_for_deliver(picking=new_picking, location=location)
|
256
|
+
|
257
|
+
def _lines_base_domain(self, no_qty_done=True):
|
258
|
+
domain = [
|
259
|
+
# we added auto_join for this, otherwise, the ORM would search all pickings
|
260
|
+
# in the picking type, and then use IN (ids)
|
261
|
+
("picking_id.picking_type_id", "in", self.picking_types.ids),
|
262
|
+
]
|
263
|
+
if no_qty_done:
|
264
|
+
domain.append(("qty_done", "=", 0))
|
265
|
+
return domain
|
266
|
+
|
267
|
+
def _lines_from_lot_domain(
|
268
|
+
self, lot, no_qty_done=True, product_qty=None, location=None
|
269
|
+
):
|
270
|
+
location_domain = (
|
271
|
+
[("picking_id.location_id", "=", location.id)] if location else []
|
272
|
+
)
|
273
|
+
domain = expression.AND(
|
274
|
+
[
|
275
|
+
self._lines_base_domain(no_qty_done),
|
276
|
+
[("lot_id", "=", lot.id)],
|
277
|
+
location_domain,
|
278
|
+
]
|
279
|
+
)
|
280
|
+
if product_qty:
|
281
|
+
domain.extend(
|
282
|
+
[
|
283
|
+
("reserved_qty", ">=", product_qty),
|
284
|
+
]
|
285
|
+
)
|
286
|
+
return domain
|
287
|
+
|
288
|
+
def _lines_from_product_domain(
|
289
|
+
self, product, no_qty_done=True, product_qty=None, location=None
|
290
|
+
):
|
291
|
+
# TODO: searching lines is common to other scenario, to refactor
|
292
|
+
domain = expression.AND(
|
293
|
+
[self._lines_base_domain(no_qty_done), [("product_id", "=", product.id)]]
|
294
|
+
)
|
295
|
+
if location:
|
296
|
+
domain.extend([("location_id", "=", location.id)])
|
297
|
+
if product_qty:
|
298
|
+
domain.extend(
|
299
|
+
[
|
300
|
+
("reserved_qty", ">=", product_qty),
|
301
|
+
]
|
302
|
+
)
|
303
|
+
return domain
|
304
|
+
|
305
|
+
def _lines_from_package_domain(self, package, no_qty_done=True):
|
306
|
+
return expression.AND(
|
307
|
+
[self._lines_base_domain(no_qty_done), [("package_id", "=", package.id)]]
|
308
|
+
)
|
309
|
+
|
310
|
+
def _deliver_product(self, picking, product, product_qty=None, location=None):
|
311
|
+
"""Handle the scan_deliver end point for a product."""
|
312
|
+
if product.tracking in ("lot", "serial"):
|
313
|
+
return self._response_for_deliver(
|
314
|
+
picking,
|
315
|
+
location=location,
|
316
|
+
message=self.msg_store.scan_lot_on_product_tracked_by_lot(),
|
317
|
+
)
|
318
|
+
|
319
|
+
lines = self.env["stock.move.line"].search(
|
320
|
+
self._lines_from_product_domain(
|
321
|
+
product, no_qty_done=False, product_qty=product_qty, location=location
|
322
|
+
),
|
323
|
+
order="date_planned",
|
324
|
+
)
|
325
|
+
if not lines:
|
326
|
+
return self._response_for_deliver(
|
327
|
+
picking,
|
328
|
+
location=location,
|
329
|
+
message=self.msg_store.product_not_found_in_pickings(),
|
330
|
+
)
|
331
|
+
|
332
|
+
multiple_location = (
|
333
|
+
not location and len(lines.mapped("picking_id.location_id")) > 1
|
334
|
+
)
|
335
|
+
if multiple_location:
|
336
|
+
return self._response_for_deliver(
|
337
|
+
picking,
|
338
|
+
location=location,
|
339
|
+
message=self.msg_store.product_in_multiple_sublocation(product),
|
340
|
+
)
|
341
|
+
|
342
|
+
# State of the picking might change while we reach this point: check again!
|
343
|
+
message = self._check_picking_status(lines.mapped("picking_id"))
|
344
|
+
if message:
|
345
|
+
message["body"] = "\n".join(
|
346
|
+
[
|
347
|
+
_("Product {} belongs to a picking without a valid state.").format(
|
348
|
+
product.name
|
349
|
+
),
|
350
|
+
message["body"],
|
351
|
+
]
|
352
|
+
)
|
353
|
+
return self._response_for_deliver(location=location, message=message)
|
354
|
+
|
355
|
+
new_picking = fields.first(lines.mapped("picking_id"))
|
356
|
+
# When products are as units outside of packages, we can select them for
|
357
|
+
# packing, but if they are in a package, we want the user to scan the packages.
|
358
|
+
# If the product is only in one package though, scanning the product selects
|
359
|
+
# the package.
|
360
|
+
packages = lines.mapped("package_id")
|
361
|
+
# Do not use mapped here: we want to see if we have more than one package,
|
362
|
+
# but also if we have one product as a package and the same product as
|
363
|
+
# a unit in another line. In both cases, we want the user to scan the
|
364
|
+
# package.
|
365
|
+
if packages and len({m.package_id for m in lines}) > 1:
|
366
|
+
return self._response_for_deliver(
|
367
|
+
new_picking,
|
368
|
+
location=location,
|
369
|
+
message=self.msg_store.product_multiple_packages_scan_package(),
|
370
|
+
)
|
371
|
+
elif packages:
|
372
|
+
# we have 1 package
|
373
|
+
# abort the operation if the package contain more than one product
|
374
|
+
if len(packages.mapped("quant_ids.product_id")) > 1:
|
375
|
+
return self._response_for_deliver(
|
376
|
+
new_picking,
|
377
|
+
location=location,
|
378
|
+
message=self.msg_store.product_mixed_package_scan_package(),
|
379
|
+
)
|
380
|
+
# abort if the quantity is bigger than one
|
381
|
+
if sum(packages.quant_ids.mapped("reserved_quantity")) > 1:
|
382
|
+
return self._response_for_deliver(
|
383
|
+
new_picking,
|
384
|
+
location=location,
|
385
|
+
message=self.msg_store.product_not_unitary_in_package_scan_package(),
|
386
|
+
)
|
387
|
+
# We focus only on lines on which we can increase the 'qty_done'
|
388
|
+
lines = lines.filtered(
|
389
|
+
lambda l: (l.qty_done + product_qty) <= l.reserved_uom_qty
|
390
|
+
)
|
391
|
+
# Filter lines to keep only ones from one delivery operation
|
392
|
+
# (we do not want to process lines of another delivery operation)
|
393
|
+
lines = lines._filter_on_picking(picking)
|
394
|
+
# We want to process 1 qty of one line
|
395
|
+
lines = fields.first(lines)
|
396
|
+
# Validate lines (this will validate the delivery if all lines are processed)
|
397
|
+
if self._set_lines_done(lines, product_qty):
|
398
|
+
return self._response_for_deliver(
|
399
|
+
location=location, message=self.msg_store.transfer_complete(new_picking)
|
400
|
+
)
|
401
|
+
return self._response_for_deliver(new_picking, location=location)
|
402
|
+
|
403
|
+
def _deliver_lot(self, picking, lot, product_qty=None, location=None):
|
404
|
+
lines = self.env["stock.move.line"].search(
|
405
|
+
self._lines_from_lot_domain(
|
406
|
+
lot, no_qty_done=False, product_qty=product_qty, location=location
|
407
|
+
)
|
408
|
+
)
|
409
|
+
if not lines:
|
410
|
+
return self._response_for_deliver(
|
411
|
+
picking,
|
412
|
+
location=location,
|
413
|
+
message=self.msg_store.lot_not_found_in_pickings(),
|
414
|
+
)
|
415
|
+
|
416
|
+
multiple_location = (
|
417
|
+
not location and len(lines.mapped("picking_id.location_id")) > 1
|
418
|
+
)
|
419
|
+
if multiple_location:
|
420
|
+
return self._response_for_deliver(
|
421
|
+
picking,
|
422
|
+
location=location,
|
423
|
+
message=self.msg_store.lot_in_multiple_sublocation(lot),
|
424
|
+
)
|
425
|
+
|
426
|
+
# State of the picking might change while we reach this point: check again!
|
427
|
+
message = self._check_picking_status(lines.mapped("picking_id"))
|
428
|
+
if message:
|
429
|
+
message["body"] = "\n".join(
|
430
|
+
[
|
431
|
+
_("Lot {} belongs to a picking without a valid state.").format(
|
432
|
+
lot.name
|
433
|
+
),
|
434
|
+
message["body"],
|
435
|
+
]
|
436
|
+
)
|
437
|
+
return self._response_for_deliver(location=location, message=message)
|
438
|
+
|
439
|
+
new_picking = fields.first(lines.mapped("picking_id"))
|
440
|
+
|
441
|
+
# When lots are as units outside of packages, we can select them for
|
442
|
+
# packing, but if they are in a package, we want the user to scan the packages.
|
443
|
+
# If the product is only in one package though, scanning the lot selects
|
444
|
+
# the package.
|
445
|
+
packages = lines.mapped("package_id")
|
446
|
+
# Do not use mapped here: we want to see if we have more than one
|
447
|
+
# package, but also if we have one lot as a package and the same lot as
|
448
|
+
# a unit in another line. In both cases, we want the user to scan the
|
449
|
+
# package.
|
450
|
+
if packages and len({m.package_id for m in lines}) > 1:
|
451
|
+
return self._response_for_deliver(
|
452
|
+
new_picking,
|
453
|
+
location=location,
|
454
|
+
message=self.msg_store.lot_multiple_packages_scan_package(),
|
455
|
+
)
|
456
|
+
elif packages:
|
457
|
+
# we have 1 package
|
458
|
+
# abort the operation if the package contain more than one product
|
459
|
+
if len(packages.quant_ids) > 1:
|
460
|
+
return self._response_for_deliver(
|
461
|
+
new_picking,
|
462
|
+
location=location,
|
463
|
+
message=self.msg_store.lot_mixed_package_scan_package(),
|
464
|
+
)
|
465
|
+
|
466
|
+
# Filter lines to keep only ones from one delivery operation
|
467
|
+
# (we do not want to process lines of another delivery operation)
|
468
|
+
lines = lines._filter_on_picking(picking)
|
469
|
+
# We want to process 1 qty of one line
|
470
|
+
lines = fields.first(lines)
|
471
|
+
if self._set_lines_done(lines, product_qty=product_qty):
|
472
|
+
return self._response_for_deliver(
|
473
|
+
location=location, message=self.msg_store.transfer_complete(new_picking)
|
474
|
+
)
|
475
|
+
return self._response_for_deliver(new_picking, location)
|
476
|
+
|
477
|
+
def _action_picking_done(self, picking, force=False):
|
478
|
+
"""Try to validate the stock picking if all quantities are satisfied.
|
479
|
+
|
480
|
+
Return `True` if the picking has been validated successfully.
|
481
|
+
|
482
|
+
:param picking: stock.picking recordset
|
483
|
+
:param force: bypass check and set picking as done no matter if satisfied.
|
484
|
+
You will likely get a backorder for not processed lines.
|
485
|
+
"""
|
486
|
+
if picking.state == "done":
|
487
|
+
return True
|
488
|
+
if force:
|
489
|
+
picking._action_done()
|
490
|
+
return True
|
491
|
+
all_done = False
|
492
|
+
for move in picking.move_ids:
|
493
|
+
if move.state in ("done", "cancel"):
|
494
|
+
continue
|
495
|
+
all_done = move._qty_is_satisfied()
|
496
|
+
if not all_done:
|
497
|
+
# At least one move not satisfied, cannot mark as done automatically
|
498
|
+
break
|
499
|
+
if all_done:
|
500
|
+
picking._action_done()
|
501
|
+
return True
|
502
|
+
return False
|
503
|
+
|
504
|
+
def list_stock_picking(self, message=None, location_id=None):
|
505
|
+
"""Return the list of stock pickings for the picking types
|
506
|
+
|
507
|
+
It returns only stock picking available or partially available.
|
508
|
+
|
509
|
+
Transitions:
|
510
|
+
* manual_selection: next state to show the list of stock pickings
|
511
|
+
"""
|
512
|
+
pickings = self.env["stock.picking"].search(
|
513
|
+
self._pickings_domain(location_id), order="id"
|
514
|
+
)
|
515
|
+
return self._response_for_manual_selection(pickings, message=message)
|
516
|
+
|
517
|
+
def _pickings_domain(self, location_id=None):
|
518
|
+
domain = [
|
519
|
+
("picking_type_id", "in", self.picking_types.ids),
|
520
|
+
("state", "=", "assigned"),
|
521
|
+
]
|
522
|
+
if location_id:
|
523
|
+
domain.append(("location_id", "=", location_id))
|
524
|
+
return domain
|
525
|
+
|
526
|
+
def select(self, picking_id):
|
527
|
+
"""Select a stock picking from its ID (found using /list_stock_picking)
|
528
|
+
|
529
|
+
It returns only stock picking available or partially available.
|
530
|
+
|
531
|
+
Transitions:
|
532
|
+
* manual_selection: the selected stock picking is no longer valid
|
533
|
+
* deliver: with information about the stock.picking
|
534
|
+
"""
|
535
|
+
picking = self.env["stock.picking"].browse(picking_id)
|
536
|
+
message = self._check_picking_status(picking)
|
537
|
+
if message:
|
538
|
+
return self.list_stock_picking(message=message)
|
539
|
+
if picking:
|
540
|
+
return self._response_for_deliver(picking)
|
541
|
+
return self.list_stock_picking(message=self.msg_store.stock_picking_not_found())
|
542
|
+
|
543
|
+
def set_qty_done_pack(self, picking_id, package_id, location_id=None):
|
544
|
+
"""Set a package to "Done"
|
545
|
+
|
546
|
+
When all the available move lines of the stock picking are done, the
|
547
|
+
stock picking is set to done.
|
548
|
+
|
549
|
+
Transitions:
|
550
|
+
* deliver: always return here with updated data
|
551
|
+
"""
|
552
|
+
picking = self.env["stock.picking"].browse(picking_id)
|
553
|
+
message = self._check_picking_status(picking)
|
554
|
+
if message:
|
555
|
+
return self._response_for_deliver(message=message)
|
556
|
+
package = self.env["stock.quant.package"].browse(package_id).exists()
|
557
|
+
if package:
|
558
|
+
response = self._deliver_package(picking, package, location_id)
|
559
|
+
self._action_picking_done(picking)
|
560
|
+
return response
|
561
|
+
return self._response_for_deliver(
|
562
|
+
picking=picking, message=self.msg_store.package_not_found()
|
563
|
+
)
|
564
|
+
|
565
|
+
def set_qty_done_line(self, picking_id, move_line_id):
|
566
|
+
"""Set a move line to "Done"
|
567
|
+
|
568
|
+
Should be called only for lines of raw products, /set_qty_done_pack
|
569
|
+
must be used for lines that move a package.
|
570
|
+
|
571
|
+
When all the available move lines of the stock picking are done, the
|
572
|
+
stock picking is set to done.
|
573
|
+
|
574
|
+
Transitions:
|
575
|
+
* deliver: always return here with updated data
|
576
|
+
"""
|
577
|
+
picking = self.env["stock.picking"].browse(picking_id)
|
578
|
+
message = self._check_picking_status(picking)
|
579
|
+
if message:
|
580
|
+
return self._response_for_deliver(message=message)
|
581
|
+
line = self.env["stock.move.line"].browse(move_line_id).exists()
|
582
|
+
if line:
|
583
|
+
if line.package_id:
|
584
|
+
return self._response_for_deliver(
|
585
|
+
picking=picking,
|
586
|
+
message=self.msg_store.line_has_package_scan_package(),
|
587
|
+
)
|
588
|
+
if self._set_lines_done(line):
|
589
|
+
return self._response_for_deliver(
|
590
|
+
message=self.msg_store.transfer_complete(picking)
|
591
|
+
)
|
592
|
+
return self._response_for_deliver(picking)
|
593
|
+
return self._response_for_deliver(
|
594
|
+
picking=picking,
|
595
|
+
message=self.msg_store.record_not_found(),
|
596
|
+
)
|
597
|
+
|
598
|
+
def reset_qty_done_pack(self, picking_id, package_id):
|
599
|
+
"""Remove "Done" on a package
|
600
|
+
|
601
|
+
Transitions:
|
602
|
+
* deliver: always return here with updated data
|
603
|
+
"""
|
604
|
+
picking = self.env["stock.picking"].browse(picking_id)
|
605
|
+
message = self._check_picking_status(picking)
|
606
|
+
if message:
|
607
|
+
return self._response_for_deliver(message=message)
|
608
|
+
package = self.env["stock.quant.package"].browse(package_id).exists()
|
609
|
+
if package:
|
610
|
+
lines = self.env["stock.move.line"].search(
|
611
|
+
self._lines_from_package_domain(package, no_qty_done=False)
|
612
|
+
)
|
613
|
+
if not lines:
|
614
|
+
return self._response_for_deliver(
|
615
|
+
picking,
|
616
|
+
message=self.msg_store.package_not_available_in_picking(
|
617
|
+
package, picking
|
618
|
+
),
|
619
|
+
)
|
620
|
+
self._reset_lines(lines)
|
621
|
+
return self._response_for_deliver(picking)
|
622
|
+
return self._response_for_deliver(
|
623
|
+
picking=picking, message=self.msg_store.package_not_found()
|
624
|
+
)
|
625
|
+
|
626
|
+
def reset_qty_done_line(self, picking_id, move_line_id):
|
627
|
+
"""Remove "Done" on a move line
|
628
|
+
|
629
|
+
Should be called only for lines of raw products, /set_qty_done_pack
|
630
|
+
must be used for lines that move a package.
|
631
|
+
|
632
|
+
Transitions:
|
633
|
+
* deliver: always return here with updated data
|
634
|
+
"""
|
635
|
+
picking = self.env["stock.picking"].browse(picking_id)
|
636
|
+
message = self._check_picking_status(picking)
|
637
|
+
if message:
|
638
|
+
return self._response_for_deliver(message=message)
|
639
|
+
line = self.env["stock.move.line"].browse(move_line_id).exists()
|
640
|
+
if line:
|
641
|
+
if line.picking_id != picking:
|
642
|
+
return self._response_for_deliver(
|
643
|
+
picking=picking,
|
644
|
+
message=self.msg_store.line_not_available_in_picking(picking),
|
645
|
+
)
|
646
|
+
if line.package_id:
|
647
|
+
return self._response_for_deliver(
|
648
|
+
picking=picking,
|
649
|
+
message=self.msg_store.line_has_package_scan_package(),
|
650
|
+
)
|
651
|
+
self._reset_lines(line)
|
652
|
+
return self._response_for_deliver(picking)
|
653
|
+
return self._response_for_deliver(
|
654
|
+
picking=picking,
|
655
|
+
message=self.msg_store.record_not_found(),
|
656
|
+
)
|
657
|
+
|
658
|
+
def done(self, picking_id, confirm=False):
|
659
|
+
"""Set the stock picking to done
|
660
|
+
|
661
|
+
Transitions:
|
662
|
+
* deliver: error during action
|
663
|
+
* confirm_done: when not all lines of the stock.picking are done
|
664
|
+
"""
|
665
|
+
picking = self.env["stock.picking"].browse(picking_id)
|
666
|
+
message = self._check_picking_status(picking)
|
667
|
+
if message:
|
668
|
+
return self._response_for_deliver(message=message)
|
669
|
+
if self._action_picking_done(picking):
|
670
|
+
return self._response_for_deliver(
|
671
|
+
message=self.msg_store.transfer_complete(picking)
|
672
|
+
)
|
673
|
+
if confirm:
|
674
|
+
precision_digits = self.env["decimal.precision"].precision_get(
|
675
|
+
"Product Unit of Measure"
|
676
|
+
)
|
677
|
+
no_quantities_done = all(
|
678
|
+
float_is_zero(move_line.qty_done, precision_digits=precision_digits)
|
679
|
+
for move_line in picking.move_line_ids.filtered(
|
680
|
+
lambda m: m.state not in ("done", "cancel")
|
681
|
+
)
|
682
|
+
)
|
683
|
+
if no_quantities_done:
|
684
|
+
return self._response_for_deliver(
|
685
|
+
message=self.msg_store.transfer_no_qty_done()
|
686
|
+
)
|
687
|
+
self._action_picking_done(picking, force=True)
|
688
|
+
return self._response_for_deliver(
|
689
|
+
message=self.msg_store.transfer_complete(picking)
|
690
|
+
)
|
691
|
+
return self._response_for_confirm_done(
|
692
|
+
picking,
|
693
|
+
message=self.msg_store.transfer_confirm_done(),
|
694
|
+
)
|
695
|
+
|
696
|
+
|
697
|
+
class ShopfloorDeliveryValidator(Component):
|
698
|
+
"""Validators for the Delivery endpoints"""
|
699
|
+
|
700
|
+
_inherit = "base.shopfloor.validator"
|
701
|
+
_name = "shopfloor.delivery.validator"
|
702
|
+
_usage = "delivery.validator"
|
703
|
+
|
704
|
+
def scan_deliver(self):
|
705
|
+
return {
|
706
|
+
"barcode": {"required": True, "type": "string"},
|
707
|
+
"picking_id": {
|
708
|
+
"coerce": to_int,
|
709
|
+
"required": False,
|
710
|
+
"nullable": True,
|
711
|
+
"type": "integer",
|
712
|
+
},
|
713
|
+
"location_id": {
|
714
|
+
"coerce": to_int,
|
715
|
+
"required": False,
|
716
|
+
"nullable": True,
|
717
|
+
"type": "integer",
|
718
|
+
},
|
719
|
+
}
|
720
|
+
|
721
|
+
def list_stock_picking(self):
|
722
|
+
return {
|
723
|
+
"location_id": {
|
724
|
+
"coerce": to_int,
|
725
|
+
"required": False,
|
726
|
+
"nullable": True,
|
727
|
+
"type": "integer",
|
728
|
+
},
|
729
|
+
}
|
730
|
+
|
731
|
+
def select(self):
|
732
|
+
return {"picking_id": {"coerce": to_int, "required": True, "type": "integer"}}
|
733
|
+
|
734
|
+
def set_qty_done_pack(self):
|
735
|
+
return {
|
736
|
+
"picking_id": {"coerce": to_int, "required": True, "type": "integer"},
|
737
|
+
"package_id": {"coerce": to_int, "required": True, "type": "integer"},
|
738
|
+
}
|
739
|
+
|
740
|
+
def set_qty_done_line(self):
|
741
|
+
return {
|
742
|
+
"picking_id": {"coerce": to_int, "required": True, "type": "integer"},
|
743
|
+
"move_line_id": {"coerce": to_int, "required": True, "type": "integer"},
|
744
|
+
}
|
745
|
+
|
746
|
+
def reset_qty_done_pack(self):
|
747
|
+
return {
|
748
|
+
"picking_id": {"coerce": to_int, "required": True, "type": "integer"},
|
749
|
+
"package_id": {"coerce": to_int, "required": True, "type": "integer"},
|
750
|
+
}
|
751
|
+
|
752
|
+
def reset_qty_done_line(self):
|
753
|
+
return {
|
754
|
+
"picking_id": {"coerce": to_int, "required": True, "type": "integer"},
|
755
|
+
"move_line_id": {"coerce": to_int, "required": True, "type": "integer"},
|
756
|
+
}
|
757
|
+
|
758
|
+
def done(self):
|
759
|
+
return {
|
760
|
+
"picking_id": {"coerce": to_int, "required": True, "type": "integer"},
|
761
|
+
"confirm": {"coerce": to_bool, "required": False, "type": "boolean"},
|
762
|
+
}
|
763
|
+
|
764
|
+
|
765
|
+
class ShopfloorDeliveryValidatorResponse(Component):
|
766
|
+
"""Validators for the Delivery endpoints responses"""
|
767
|
+
|
768
|
+
_inherit = "base.shopfloor.validator.response"
|
769
|
+
_name = "shopfloor.delivery.validator.response"
|
770
|
+
_usage = "delivery.validator.response"
|
771
|
+
|
772
|
+
_start_state = "deliver"
|
773
|
+
|
774
|
+
def _states(self):
|
775
|
+
"""List of possible next states
|
776
|
+
|
777
|
+
With the schema of the data send to the client to transition
|
778
|
+
to the next state.
|
779
|
+
"""
|
780
|
+
return {
|
781
|
+
"deliver": self._schema_deliver,
|
782
|
+
"manual_selection": self._schema_selection_list,
|
783
|
+
"confirm_done": self._schema_deliver,
|
784
|
+
}
|
785
|
+
|
786
|
+
@property
|
787
|
+
def _schema_deliver(self):
|
788
|
+
schema_picking = self.schemas_detail.picking_detail()
|
789
|
+
schema_location = self.schemas.location()
|
790
|
+
return {
|
791
|
+
"picking": {"type": "dict", "nullable": True, "schema": schema_picking},
|
792
|
+
"sublocation": {
|
793
|
+
"type": "dict",
|
794
|
+
"nullable": True,
|
795
|
+
"schema": schema_location,
|
796
|
+
},
|
797
|
+
}
|
798
|
+
|
799
|
+
@property
|
800
|
+
def _schema_selection_list(self):
|
801
|
+
schema = self.schemas_detail.picking_detail()
|
802
|
+
return {
|
803
|
+
"pickings": {"type": "list", "schema": {"type": "dict", "schema": schema}}
|
804
|
+
}
|
805
|
+
|
806
|
+
def scan_deliver(self):
|
807
|
+
return self._response_schema(next_states={"deliver"})
|
808
|
+
|
809
|
+
def list_stock_picking(self):
|
810
|
+
return self._response_schema(next_states={"manual_selection"})
|
811
|
+
|
812
|
+
def select(self):
|
813
|
+
return self._response_schema(next_states={"deliver", "manual_selection"})
|
814
|
+
|
815
|
+
def set_qty_done_pack(self):
|
816
|
+
return self._response_schema(next_states={"deliver"})
|
817
|
+
|
818
|
+
def set_qty_done_line(self):
|
819
|
+
return self._response_schema(next_states={"deliver"})
|
820
|
+
|
821
|
+
def reset_qty_done_pack(self):
|
822
|
+
return self._response_schema(next_states={"deliver"})
|
823
|
+
|
824
|
+
def reset_qty_done_line(self):
|
825
|
+
return self._response_schema(next_states={"deliver"})
|
826
|
+
|
827
|
+
def done(self):
|
828
|
+
return self._response_schema(next_states={"deliver", "confirm_done"})
|