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,329 @@
|
|
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
|
+
|
5
|
+
from odoo.addons.component.core import Component
|
6
|
+
from odoo.addons.shopfloor_base.utils import ensure_model
|
7
|
+
|
8
|
+
|
9
|
+
class DataAction(Component):
|
10
|
+
_inherit = "shopfloor.data.action"
|
11
|
+
|
12
|
+
@ensure_model("stock.location")
|
13
|
+
def location(self, record, **kw):
|
14
|
+
parser = self._location_parser
|
15
|
+
data = self._jsonify(record.with_context(location=record.id), parser, **kw)
|
16
|
+
if "with_operation_progress" in kw:
|
17
|
+
operation_progress = self._get_location_operations_progress(record)
|
18
|
+
data.update({"operation_progress": operation_progress})
|
19
|
+
return data
|
20
|
+
|
21
|
+
def locations(self, record, **kw):
|
22
|
+
return self.location(record, multi=True)
|
23
|
+
|
24
|
+
def _get_location_operations_progress(self, location):
|
25
|
+
lines = self.env["stock.move.line"].search(
|
26
|
+
[
|
27
|
+
("location_id", "=", location.id),
|
28
|
+
("state", "in", ["partially_available", "assigned"]),
|
29
|
+
("picking_id.state", "=", "assigned"),
|
30
|
+
]
|
31
|
+
)
|
32
|
+
# operations_to_do = number of total operations that are pending for this location.
|
33
|
+
# operations_done = number of operations already done.
|
34
|
+
# A line with an assigned package counts as 1 operation.
|
35
|
+
operations_to_do = 0
|
36
|
+
operations_done = 0
|
37
|
+
for line in lines:
|
38
|
+
operations_done += line.qty_done if not line.package_id else 1
|
39
|
+
operations_to_do += line.reserved_uom_qty if not line.package_id else 1
|
40
|
+
return {
|
41
|
+
"done": operations_done,
|
42
|
+
"to_do": operations_to_do,
|
43
|
+
}
|
44
|
+
|
45
|
+
@property
|
46
|
+
def _location_parser(self):
|
47
|
+
return [
|
48
|
+
"id",
|
49
|
+
"name",
|
50
|
+
# Fallback to name if barcode is not valued.
|
51
|
+
("barcode", lambda rec, fname: rec[fname] if rec[fname] else rec.name),
|
52
|
+
]
|
53
|
+
|
54
|
+
@ensure_model("stock.picking")
|
55
|
+
def picking(self, record, **kw):
|
56
|
+
parser = self._picking_parser
|
57
|
+
# progress is a heavy computed field,
|
58
|
+
# and it may reduce performance significatively
|
59
|
+
# when dealing with a large number of pickings.
|
60
|
+
# Thus, we make it optional.
|
61
|
+
if "with_progress" in kw:
|
62
|
+
parser.append("progress")
|
63
|
+
return self._jsonify(record, parser, **kw)
|
64
|
+
|
65
|
+
def pickings(self, record, **kw):
|
66
|
+
return self.picking(record, multi=True)
|
67
|
+
|
68
|
+
@property
|
69
|
+
def _picking_parser(self, **kw):
|
70
|
+
return [
|
71
|
+
"id",
|
72
|
+
"name",
|
73
|
+
"origin",
|
74
|
+
"note",
|
75
|
+
("partner_id:partner", self._partner_parser),
|
76
|
+
("carrier_id:carrier", self._simple_record_parser()),
|
77
|
+
("ship_carrier_id:ship_carrier", self._simple_record_parser()),
|
78
|
+
"move_line_count",
|
79
|
+
"package_level_count",
|
80
|
+
"bulk_line_count",
|
81
|
+
"total_weight:weight",
|
82
|
+
"scheduled_date",
|
83
|
+
]
|
84
|
+
|
85
|
+
@ensure_model("stock.quant.package")
|
86
|
+
def package(self, record, picking=None, with_packaging=False, **kw):
|
87
|
+
"""Return data for a stock.quant.package
|
88
|
+
|
89
|
+
If a picking is given, it will include the number of lines of the package
|
90
|
+
for the picking.
|
91
|
+
"""
|
92
|
+
parser = self._package_parser
|
93
|
+
if with_packaging:
|
94
|
+
parser += self._package_packaging_parser
|
95
|
+
data = self._jsonify(record, parser, **kw)
|
96
|
+
# handle special cases
|
97
|
+
if data and picking:
|
98
|
+
lines = picking.move_line_ids.filtered(
|
99
|
+
lambda l: l.result_package_id == record
|
100
|
+
and l.state in ["partially_available", "assigned", "done"]
|
101
|
+
)
|
102
|
+
data.update({"move_line_count": len(lines)})
|
103
|
+
return data
|
104
|
+
|
105
|
+
def packages(self, records, picking=None, **kw):
|
106
|
+
return [self.package(rec, picking=picking, **kw) for rec in records]
|
107
|
+
|
108
|
+
@property
|
109
|
+
def _package_parser(self):
|
110
|
+
return [
|
111
|
+
"id",
|
112
|
+
"name",
|
113
|
+
"shopfloor_weight:weight",
|
114
|
+
("package_type_id:storage_type", ["id", "name"]),
|
115
|
+
]
|
116
|
+
|
117
|
+
@property
|
118
|
+
def _package_packaging_parser(self):
|
119
|
+
return [
|
120
|
+
("product_packaging_id:packaging", self._packaging_parser),
|
121
|
+
]
|
122
|
+
|
123
|
+
@ensure_model("product.packaging")
|
124
|
+
def packaging(self, record, **kw):
|
125
|
+
return self._jsonify(record, self._packaging_parser, **kw)
|
126
|
+
|
127
|
+
def packaging_list(self, record, **kw):
|
128
|
+
return self.packaging(record, multi=True)
|
129
|
+
|
130
|
+
@property
|
131
|
+
def _packaging_parser(self):
|
132
|
+
return [
|
133
|
+
"id",
|
134
|
+
("packaging_level_id:name", lambda rec, fname: rec.packaging_level_id.name),
|
135
|
+
("packaging_level_id:code", lambda rec, fname: rec.packaging_level_id.code),
|
136
|
+
"qty",
|
137
|
+
]
|
138
|
+
|
139
|
+
@ensure_model("stock.package.type")
|
140
|
+
def delivery_packaging(self, record, **kw):
|
141
|
+
return self._jsonify(record, self._delivery_packaging_parser, **kw)
|
142
|
+
|
143
|
+
def delivery_packaging_list(self, records, **kw):
|
144
|
+
return self.delivery_packaging(records, multi=True)
|
145
|
+
|
146
|
+
@property
|
147
|
+
def _delivery_packaging_parser(self):
|
148
|
+
return [
|
149
|
+
"id",
|
150
|
+
"name",
|
151
|
+
"package_carrier_type:packaging_type",
|
152
|
+
"barcode",
|
153
|
+
]
|
154
|
+
|
155
|
+
@ensure_model("stock.lot")
|
156
|
+
def lot(self, record, **kw):
|
157
|
+
return self._jsonify(record, self._lot_parser, **kw)
|
158
|
+
|
159
|
+
def lots(self, record, **kw):
|
160
|
+
return self.lot(record, multi=True)
|
161
|
+
|
162
|
+
@property
|
163
|
+
def _lot_parser(self):
|
164
|
+
return self._simple_record_parser() + ["ref", "expiration_date"]
|
165
|
+
|
166
|
+
@ensure_model("stock.move.line")
|
167
|
+
def move_line(self, record, with_picking=False, **kw):
|
168
|
+
record = record.with_context(location=record.location_id.id)
|
169
|
+
parser = self._move_line_parser
|
170
|
+
if with_picking:
|
171
|
+
parser += [("picking_id:picking", self._picking_parser)]
|
172
|
+
data = self._jsonify(record, parser)
|
173
|
+
if data:
|
174
|
+
data.update(
|
175
|
+
{
|
176
|
+
# cannot use sub-parser here
|
177
|
+
# because result might depend on picking
|
178
|
+
"package_src": self.package(
|
179
|
+
record.package_id, record.picking_id, **kw
|
180
|
+
),
|
181
|
+
"package_dest": self.package(
|
182
|
+
record.result_package_id.with_context(
|
183
|
+
picking_id=record.picking_id.id
|
184
|
+
),
|
185
|
+
record.picking_id,
|
186
|
+
**kw,
|
187
|
+
),
|
188
|
+
}
|
189
|
+
)
|
190
|
+
return data
|
191
|
+
|
192
|
+
def move_lines(self, records, **kw):
|
193
|
+
return [self.move_line(rec, **kw) for rec in records]
|
194
|
+
|
195
|
+
@property
|
196
|
+
def _move_line_parser(self):
|
197
|
+
return [
|
198
|
+
"id",
|
199
|
+
"qty_done",
|
200
|
+
"reserved_uom_qty:quantity",
|
201
|
+
("product_id:product", self._product_parser),
|
202
|
+
("lot_id:lot", self._lot_parser),
|
203
|
+
("location_id:location_src", self._location_parser),
|
204
|
+
("location_dest_id:location_dest", self._location_parser),
|
205
|
+
(
|
206
|
+
"move_id:priority",
|
207
|
+
lambda rec, fname: rec.move_id.priority or "",
|
208
|
+
),
|
209
|
+
"progress",
|
210
|
+
]
|
211
|
+
|
212
|
+
@ensure_model("stock.move")
|
213
|
+
def move(self, record, **kw):
|
214
|
+
record = record.with_context(location=record.location_id.id)
|
215
|
+
parser = self._move_parser
|
216
|
+
return self._jsonify(record, parser)
|
217
|
+
|
218
|
+
def moves(self, records, **kw):
|
219
|
+
return [self.move(rec, **kw) for rec in records]
|
220
|
+
|
221
|
+
@property
|
222
|
+
def _move_parser(self):
|
223
|
+
return [
|
224
|
+
"id",
|
225
|
+
"quantity_done",
|
226
|
+
"product_uom_qty:quantity",
|
227
|
+
("product_id:product", self._product_parser),
|
228
|
+
("location_id:location_src", self._location_parser),
|
229
|
+
("location_dest_id:location_dest", self._location_parser),
|
230
|
+
"priority",
|
231
|
+
"progress",
|
232
|
+
]
|
233
|
+
|
234
|
+
@ensure_model("stock.package_level")
|
235
|
+
def package_level(self, record, **kw):
|
236
|
+
return self._jsonify(record, self._package_level_parser)
|
237
|
+
|
238
|
+
def package_levels(self, records, **kw):
|
239
|
+
return [self.package_level(rec, **kw) for rec in records]
|
240
|
+
|
241
|
+
@property
|
242
|
+
def _package_level_parser(self):
|
243
|
+
return [
|
244
|
+
"id",
|
245
|
+
"is_done",
|
246
|
+
("picking_id:picking", self._simple_record_parser()),
|
247
|
+
("package_id:package_src", self._package_parser),
|
248
|
+
("location_dest_id:location_dest", self._location_parser),
|
249
|
+
(
|
250
|
+
"location_id:location_src",
|
251
|
+
lambda rec, fname: self.location(
|
252
|
+
fields.first(rec.move_line_ids).location_id
|
253
|
+
or fields.first(rec.move_lines).location_id
|
254
|
+
or rec.picking_id.location_id
|
255
|
+
),
|
256
|
+
),
|
257
|
+
# tnx to stock_quant_package_product_packaging
|
258
|
+
(
|
259
|
+
"package_id:product",
|
260
|
+
lambda rec, fname: self.product(rec.package_id.single_product_id),
|
261
|
+
),
|
262
|
+
# TODO: allow to pass mapped path to jsonifier
|
263
|
+
(
|
264
|
+
"package_id:quantity",
|
265
|
+
lambda rec, fname: rec.package_id.single_product_qty,
|
266
|
+
),
|
267
|
+
]
|
268
|
+
|
269
|
+
@ensure_model("product.product")
|
270
|
+
def product(self, record, **kw):
|
271
|
+
return self._jsonify(record, self._product_parser, **kw)
|
272
|
+
|
273
|
+
def products(self, record, **kw):
|
274
|
+
return self.product(record, multi=True)
|
275
|
+
|
276
|
+
@property
|
277
|
+
def _product_parser(self):
|
278
|
+
return [
|
279
|
+
"id",
|
280
|
+
"name",
|
281
|
+
"display_name",
|
282
|
+
"default_code",
|
283
|
+
"barcode",
|
284
|
+
("packaging_ids:packaging", self._product_packaging),
|
285
|
+
("uom_id:uom", self._simple_record_parser() + ["factor", "rounding"]),
|
286
|
+
("seller_ids:supplier_code", self._product_supplier_code),
|
287
|
+
]
|
288
|
+
|
289
|
+
def _product_packaging(self, rec, field):
|
290
|
+
return self._jsonify(
|
291
|
+
rec.packaging_ids.filtered(lambda x: x.qty),
|
292
|
+
self._packaging_parser,
|
293
|
+
multi=True,
|
294
|
+
)
|
295
|
+
|
296
|
+
def _product_supplier_code(self, rec, field):
|
297
|
+
supplier_info = fields.first(
|
298
|
+
rec.seller_ids.filtered(lambda x: x.product_id == rec)
|
299
|
+
)
|
300
|
+
return supplier_info.product_code or ""
|
301
|
+
|
302
|
+
@ensure_model("stock.picking.batch")
|
303
|
+
def picking_batch(self, record, with_pickings=False, **kw):
|
304
|
+
parser = self._picking_batch_parser
|
305
|
+
if with_pickings:
|
306
|
+
parser.append(("picking_ids:pickings", self._picking_parser))
|
307
|
+
return self._jsonify(record, parser, **kw)
|
308
|
+
|
309
|
+
def picking_batches(self, record, with_pickings=False, **kw):
|
310
|
+
return self.picking_batch(record, with_pickings=with_pickings, multi=True)
|
311
|
+
|
312
|
+
@property
|
313
|
+
def _picking_batch_parser(self):
|
314
|
+
return ["id", "name", "picking_count", "move_line_count", "total_weight:weight"]
|
315
|
+
|
316
|
+
@ensure_model("stock.picking.type")
|
317
|
+
def picking_type(self, record, **kw):
|
318
|
+
parser = self._picking_type_parser
|
319
|
+
return self._jsonify(record, parser, **kw)
|
320
|
+
|
321
|
+
def picking_types(self, record, **kw):
|
322
|
+
return self.picking_type(record, multi=True)
|
323
|
+
|
324
|
+
@property
|
325
|
+
def _picking_type_parser(self):
|
326
|
+
return [
|
327
|
+
"id",
|
328
|
+
"name",
|
329
|
+
]
|
@@ -0,0 +1,154 @@
|
|
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.tools.float_utils import float_round
|
4
|
+
|
5
|
+
from odoo.addons.component.core import Component
|
6
|
+
from odoo.addons.shopfloor_base.utils import ensure_model
|
7
|
+
|
8
|
+
|
9
|
+
class DataDetailAction(Component):
|
10
|
+
_inherit = "shopfloor.data.detail.action"
|
11
|
+
|
12
|
+
def _select_value_to_label(self, rec, fname):
|
13
|
+
return rec._fields[fname].convert_to_export(rec[fname], rec)
|
14
|
+
|
15
|
+
def location_detail(self, record, **kw):
|
16
|
+
return self._jsonify(
|
17
|
+
record.with_context(location=record.id), self._location_detail_parser, **kw
|
18
|
+
)
|
19
|
+
|
20
|
+
def locations_detail(self, record, **kw):
|
21
|
+
return self.location_detail(record, multi=True)
|
22
|
+
|
23
|
+
@property
|
24
|
+
def _location_detail_parser(self):
|
25
|
+
return self._location_parser + [
|
26
|
+
"complete_name",
|
27
|
+
(
|
28
|
+
"reserved_move_line_ids:reserved_move_lines",
|
29
|
+
lambda record, fname: self.move_lines(record[fname]),
|
30
|
+
),
|
31
|
+
]
|
32
|
+
|
33
|
+
@ensure_model("stock.picking")
|
34
|
+
def picking_detail(self, record, **kw):
|
35
|
+
parser = self._picking_detail_parser
|
36
|
+
# progress is a heavy computed field,
|
37
|
+
# and it may reduce performance significatively
|
38
|
+
# when dealing with a large number of pickings.
|
39
|
+
# Thus, we make it optional.
|
40
|
+
if "with_progress" in kw:
|
41
|
+
parser.append("progress")
|
42
|
+
return self._jsonify(record, parser, **kw)
|
43
|
+
|
44
|
+
def pickings_detail(self, record, **kw):
|
45
|
+
return self.picking_detail(record, multi=True)
|
46
|
+
|
47
|
+
@property
|
48
|
+
def _picking_detail_parser(self):
|
49
|
+
return self._picking_parser + [
|
50
|
+
"picking_type_code",
|
51
|
+
("priority", self._select_value_to_label),
|
52
|
+
"scheduled_date",
|
53
|
+
("picking_type_id:operation_type", ["id", "name"]),
|
54
|
+
(
|
55
|
+
"move_line_ids:move_lines",
|
56
|
+
lambda record, fname: self.move_lines(record[fname]),
|
57
|
+
),
|
58
|
+
]
|
59
|
+
|
60
|
+
def package_detail(self, record, picking=None, **kw):
|
61
|
+
# Define a new method to not overload the base one which is used in many places
|
62
|
+
data = self.package(record, picking=picking, with_packaging=True, **kw)
|
63
|
+
data.update(self._jsonify(record, self._package_detail_parser, **kw))
|
64
|
+
return data
|
65
|
+
|
66
|
+
def packages_detail(self, records, picking=None, **kw):
|
67
|
+
return [self.package_detail(rec, picking=picking) for rec in records]
|
68
|
+
|
69
|
+
@property
|
70
|
+
def _package_detail_parser(self):
|
71
|
+
return [
|
72
|
+
(
|
73
|
+
"reserved_move_line_ids:pickings",
|
74
|
+
lambda record, fname: self.pickings(record[fname].mapped("picking_id")),
|
75
|
+
),
|
76
|
+
(
|
77
|
+
"reserved_move_line_ids:move_lines",
|
78
|
+
lambda record, fname: self.move_lines(record[fname]),
|
79
|
+
),
|
80
|
+
("location_id:location", ["id", "display_name:name"]),
|
81
|
+
]
|
82
|
+
|
83
|
+
@ensure_model("stock.lot")
|
84
|
+
def lot_detail(self, record, **kw):
|
85
|
+
# Define a new method to not overload the base one which is used in many places
|
86
|
+
return self._jsonify(record, self._lot_detail_parser, **kw)
|
87
|
+
|
88
|
+
def lots_detail(self, record, **kw):
|
89
|
+
return self.lot_detail(record, multi=True)
|
90
|
+
|
91
|
+
@property
|
92
|
+
def _lot_detail_parser(self):
|
93
|
+
return self._lot_parser + [
|
94
|
+
"removal_date",
|
95
|
+
"expiration_date:expire_date",
|
96
|
+
(
|
97
|
+
"product_id:product",
|
98
|
+
lambda record, fname: self.product_detail(record[fname]),
|
99
|
+
),
|
100
|
+
]
|
101
|
+
|
102
|
+
@ensure_model("product.product")
|
103
|
+
def product_detail(self, record, **kw):
|
104
|
+
# Defined new method to not overload the base one used in many places
|
105
|
+
data = self._jsonify(record, self._product_detail_parser, **kw)
|
106
|
+
suppliers = self.env["product.supplierinfo"].search(
|
107
|
+
[("product_id", "=", record.id)]
|
108
|
+
)
|
109
|
+
data["suppliers"] = self._jsonify(
|
110
|
+
suppliers, self._product_supplierinfo_parser, multi=True
|
111
|
+
)
|
112
|
+
return data
|
113
|
+
|
114
|
+
def products_detail(self, record, **kw):
|
115
|
+
return self.product_detail(record, multi=True)
|
116
|
+
|
117
|
+
@property
|
118
|
+
def _product_parser(self):
|
119
|
+
return super()._product_parser + [
|
120
|
+
"qty_available",
|
121
|
+
("free_qty:qty_reserved", self._product_reserved_qty_subparser),
|
122
|
+
]
|
123
|
+
|
124
|
+
def _product_reserved_qty_subparser(self, rec, field_name):
|
125
|
+
# free_qty = qty_available - reserved_quantity
|
126
|
+
return float_round(
|
127
|
+
rec.qty_available - rec[field_name], precision_rounding=rec.uom_id.rounding
|
128
|
+
)
|
129
|
+
|
130
|
+
@property
|
131
|
+
def _product_detail_parser(self):
|
132
|
+
return self._product_parser + [
|
133
|
+
("image_128:image", self._product_image_url),
|
134
|
+
(
|
135
|
+
"product_tmpl_id:manufacturer",
|
136
|
+
lambda rec, fname: self._jsonify(
|
137
|
+
rec.product_tmpl_id.manufacturer_id, ["id", "name"]
|
138
|
+
),
|
139
|
+
),
|
140
|
+
]
|
141
|
+
|
142
|
+
def _product_image_url(self, record, field_name):
|
143
|
+
if not record[field_name]:
|
144
|
+
return None
|
145
|
+
return "/web/image/product.product/{}/{}".format(record.id, field_name)
|
146
|
+
|
147
|
+
@property
|
148
|
+
def _product_supplierinfo_parser(self):
|
149
|
+
return [
|
150
|
+
("id", lambda rec, fname: rec.partner_id.id),
|
151
|
+
("partner_id:partner", lambda rec, fname: rec.partner_id.name),
|
152
|
+
"product_name",
|
153
|
+
"product_code",
|
154
|
+
]
|
@@ -0,0 +1,150 @@
|
|
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
|
+
|
5
|
+
from odoo.addons.component.core import Component
|
6
|
+
|
7
|
+
|
8
|
+
class InventoryAction(Component):
|
9
|
+
"""Provide methods to work with inventories
|
10
|
+
|
11
|
+
Several processes have to create inventories at some point,
|
12
|
+
for instance when there is a stock issue.
|
13
|
+
"""
|
14
|
+
|
15
|
+
_name = "shopfloor.inventory.action"
|
16
|
+
_inherit = "shopfloor.process.action"
|
17
|
+
_usage = "inventory"
|
18
|
+
|
19
|
+
@property
|
20
|
+
def inventory_model(self):
|
21
|
+
# the _sf_inventory key bypass groups checks,
|
22
|
+
# see comment in models/stock_inventory.py
|
23
|
+
return self.env["stock.quant"].with_context(_sf_inventory=True)
|
24
|
+
|
25
|
+
def create_draft_check_empty(self, location, product, ref=None):
|
26
|
+
"""Create a draft inventory for a product with a zero quantity"""
|
27
|
+
return self._create_draft_inventory(location, product)
|
28
|
+
|
29
|
+
def _inventory_exists(self, location, product, package=None, lot=None):
|
30
|
+
"""Return if an inventory for location and product exist"""
|
31
|
+
domain = [
|
32
|
+
("location_id", "=", location.id),
|
33
|
+
("product_id", "=", product.id),
|
34
|
+
("inventory_quantity_set", "=", True),
|
35
|
+
]
|
36
|
+
if package is not None:
|
37
|
+
domain.append(("package_id", "=", package.id))
|
38
|
+
if lot is not None:
|
39
|
+
domain.append(("lot_id", "=", lot.id))
|
40
|
+
return self.inventory_model.search_count(domain)
|
41
|
+
|
42
|
+
def _get_existing_quant(self, location, product, package=None, lot=None, limit=1):
|
43
|
+
domain = [("location_id", "=", location.id), ("product_id", "=", product.id)]
|
44
|
+
if package is not None:
|
45
|
+
domain.append(("package_id", "=", package.id))
|
46
|
+
else:
|
47
|
+
domain.append(("package_id", "=", False))
|
48
|
+
if lot is not None:
|
49
|
+
domain.append(("lot_id", "=", lot.id))
|
50
|
+
else:
|
51
|
+
domain.append(("lot_id", "=", False))
|
52
|
+
return self.inventory_model.search(domain, limit=limit)
|
53
|
+
|
54
|
+
def _create_draft_inventory(self, location, product, package=None, lot=None):
|
55
|
+
quants = self._get_existing_quant(
|
56
|
+
location, product, package=package, lot=lot, limit=None
|
57
|
+
)
|
58
|
+
if quants:
|
59
|
+
for quant in quants:
|
60
|
+
if quant.inventory_quantity_set:
|
61
|
+
continue
|
62
|
+
quants.write(
|
63
|
+
{
|
64
|
+
# Set an inventory quantity to prevent the zero quant cleanup
|
65
|
+
"inventory_quantity": quant.inventory_quantity + 1,
|
66
|
+
"inventory_date": fields.Date.today(),
|
67
|
+
}
|
68
|
+
)
|
69
|
+
return quants
|
70
|
+
else:
|
71
|
+
return self.inventory_model.sudo().create(
|
72
|
+
{
|
73
|
+
"location_id": location.id,
|
74
|
+
"product_id": product.id,
|
75
|
+
"lot_id": lot.id,
|
76
|
+
"inventory_quantity": 1,
|
77
|
+
"inventory_date": fields.Date.today(),
|
78
|
+
"package_id": package.id if package else False,
|
79
|
+
}
|
80
|
+
)
|
81
|
+
|
82
|
+
def create_control_stock(
|
83
|
+
self, location, product, package=None, lot=None, name=None
|
84
|
+
):
|
85
|
+
"""Create a draft inventory so a user has to check a location
|
86
|
+
|
87
|
+
If a draft or in progress inventory already exists for the same
|
88
|
+
combination of product/package/lot, no inventory is created.
|
89
|
+
"""
|
90
|
+
if not self._inventory_exists(location, product, package=package, lot=lot):
|
91
|
+
self._create_draft_inventory(location, product, package=package, lot=lot)
|
92
|
+
|
93
|
+
def create_stock_issue(self, move, location, package, lot):
|
94
|
+
"""Create an inventory for a stock issue
|
95
|
+
|
96
|
+
It reduces the quantity in a location in a way that:
|
97
|
+
* assigned move lines in other batch transfers stay assigned.
|
98
|
+
* assigned move lines in same batch but already picked stay assigned.
|
99
|
+
"""
|
100
|
+
other_lines = self._stock_issue_get_related_move_lines(
|
101
|
+
move, location, package, lot
|
102
|
+
)
|
103
|
+
qty_to_keep = sum(other_lines.mapped("reserved_qty"))
|
104
|
+
self.create_stock_correction(move, location, package, lot, qty_to_keep)
|
105
|
+
move._action_assign()
|
106
|
+
|
107
|
+
def create_stock_correction(self, move, location, package, lot, quantity):
|
108
|
+
"""Create an inventory with a forced quantity"""
|
109
|
+
quant = self._get_existing_quant(location, move.product_id, package, lot)
|
110
|
+
if quant:
|
111
|
+
quant.with_context(
|
112
|
+
inventory_mode=True
|
113
|
+
).sudo().inventory_quantity_auto_apply = quantity
|
114
|
+
else:
|
115
|
+
self.inventory_model._update_available_quantity(
|
116
|
+
move.product_id, location, quantity, lot_id=lot, package_id=package
|
117
|
+
)
|
118
|
+
# FIXME
|
119
|
+
move.product_id.stock_quant_ids._quant_tasks()
|
120
|
+
|
121
|
+
def _stock_issue_get_related_move_lines(self, move, location, package, lot):
|
122
|
+
"""Lookup for all the other moves lines that match given move line"""
|
123
|
+
domain = [
|
124
|
+
("location_id", "=", location.id),
|
125
|
+
("product_id", "=", move.product_id.id),
|
126
|
+
("package_id", "=", package.id),
|
127
|
+
("lot_id", "=", lot.id),
|
128
|
+
("state", "in", ("assigned", "partially_available")),
|
129
|
+
]
|
130
|
+
return self.env["stock.move.line"].search(domain)
|
131
|
+
|
132
|
+
def _stock_correction_inventory_values(
|
133
|
+
self, move, location, package, lot, line_qty
|
134
|
+
):
|
135
|
+
return {
|
136
|
+
"location_id": location.id,
|
137
|
+
"product_id": move.product_id.id,
|
138
|
+
"package_id": package.id,
|
139
|
+
"lot_id": lot.id,
|
140
|
+
"inventory_quantity": line_qty,
|
141
|
+
}
|
142
|
+
|
143
|
+
def _stock_issue_product_description(self, product, package, lot):
|
144
|
+
parts = []
|
145
|
+
if package:
|
146
|
+
parts.append(package.name)
|
147
|
+
parts.append(product.name)
|
148
|
+
if lot.name:
|
149
|
+
parts.append(_("Lot: ") + lot.name)
|
150
|
+
return " - ".join(parts)
|