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.
Files changed (182) hide show
  1. odoo/addons/shopfloor/README.rst +160 -0
  2. odoo/addons/shopfloor/__init__.py +4 -0
  3. odoo/addons/shopfloor/__manifest__.py +65 -0
  4. odoo/addons/shopfloor/actions/__init__.py +15 -0
  5. odoo/addons/shopfloor/actions/change_package_lot.py +164 -0
  6. odoo/addons/shopfloor/actions/completion_info.py +42 -0
  7. odoo/addons/shopfloor/actions/data.py +329 -0
  8. odoo/addons/shopfloor/actions/data_detail.py +154 -0
  9. odoo/addons/shopfloor/actions/inventory.py +150 -0
  10. odoo/addons/shopfloor/actions/location_content_transfer_sorter.py +89 -0
  11. odoo/addons/shopfloor/actions/message.py +846 -0
  12. odoo/addons/shopfloor/actions/move_line_search.py +119 -0
  13. odoo/addons/shopfloor/actions/packaging.py +59 -0
  14. odoo/addons/shopfloor/actions/savepoint.py +44 -0
  15. odoo/addons/shopfloor/actions/schema.py +182 -0
  16. odoo/addons/shopfloor/actions/schema_detail.py +98 -0
  17. odoo/addons/shopfloor/actions/search.py +187 -0
  18. odoo/addons/shopfloor/actions/stock.py +239 -0
  19. odoo/addons/shopfloor/actions/stock_unreserve.py +66 -0
  20. odoo/addons/shopfloor/components/__init__.py +5 -0
  21. odoo/addons/shopfloor/components/scan_handler_location.py +26 -0
  22. odoo/addons/shopfloor/components/scan_handler_lot.py +26 -0
  23. odoo/addons/shopfloor/components/scan_handler_package.py +26 -0
  24. odoo/addons/shopfloor/components/scan_handler_product.py +26 -0
  25. odoo/addons/shopfloor/components/scan_handler_transfer.py +26 -0
  26. odoo/addons/shopfloor/data/shopfloor_scenario_data.xml +73 -0
  27. odoo/addons/shopfloor/demo/shopfloor_app_demo.xml +12 -0
  28. odoo/addons/shopfloor/demo/shopfloor_menu_demo.xml +64 -0
  29. odoo/addons/shopfloor/demo/shopfloor_profile_demo.xml +8 -0
  30. odoo/addons/shopfloor/demo/stock_picking_type_demo.xml +93 -0
  31. odoo/addons/shopfloor/docs/checkout_diag_seq.plantuml +61 -0
  32. odoo/addons/shopfloor/docs/checkout_diag_seq.png +0 -0
  33. odoo/addons/shopfloor/docs/cluster_picking_diag_seq.plantuml +112 -0
  34. odoo/addons/shopfloor/docs/cluster_picking_diag_seq.png +0 -0
  35. odoo/addons/shopfloor/docs/delivery_diag_seq.plantuml +56 -0
  36. odoo/addons/shopfloor/docs/delivery_diag_seq.png +0 -0
  37. odoo/addons/shopfloor/docs/location_content_transfer_diag_seq.plantuml +66 -0
  38. odoo/addons/shopfloor/docs/location_content_transfer_diag_seq.png +0 -0
  39. odoo/addons/shopfloor/docs/oca_logo.png +0 -0
  40. odoo/addons/shopfloor/docs/single_pack_transfer_diag_seq.plantuml +36 -0
  41. odoo/addons/shopfloor/docs/single_pack_transfer_diag_seq.png +0 -0
  42. odoo/addons/shopfloor/docs/zone_picking_diag_seq.plantuml +85 -0
  43. odoo/addons/shopfloor/docs/zone_picking_diag_seq.png +0 -0
  44. odoo/addons/shopfloor/exceptions.py +6 -0
  45. odoo/addons/shopfloor/i18n/ca.po +1802 -0
  46. odoo/addons/shopfloor/i18n/de.po +1791 -0
  47. odoo/addons/shopfloor/i18n/es_AR.po +2147 -0
  48. odoo/addons/shopfloor/i18n/pt_BR.po +1791 -0
  49. odoo/addons/shopfloor/i18n/shopfloor.pot +1877 -0
  50. odoo/addons/shopfloor/models/__init__.py +12 -0
  51. odoo/addons/shopfloor/models/priority_postpone_mixin.py +41 -0
  52. odoo/addons/shopfloor/models/shopfloor_app.py +9 -0
  53. odoo/addons/shopfloor/models/shopfloor_menu.py +436 -0
  54. odoo/addons/shopfloor/models/stock_location.py +76 -0
  55. odoo/addons/shopfloor/models/stock_move.py +119 -0
  56. odoo/addons/shopfloor/models/stock_move_line.py +307 -0
  57. odoo/addons/shopfloor/models/stock_package_level.py +50 -0
  58. odoo/addons/shopfloor/models/stock_picking.py +118 -0
  59. odoo/addons/shopfloor/models/stock_picking_batch.py +41 -0
  60. odoo/addons/shopfloor/models/stock_picking_type.py +26 -0
  61. odoo/addons/shopfloor/models/stock_quant.py +31 -0
  62. odoo/addons/shopfloor/models/stock_quant_package.py +101 -0
  63. odoo/addons/shopfloor/readme/CONTRIBUTORS.rst +18 -0
  64. odoo/addons/shopfloor/readme/CREDITS.rst +5 -0
  65. odoo/addons/shopfloor/readme/DESCRIPTION.rst +17 -0
  66. odoo/addons/shopfloor/readme/HISTORY.rst +4 -0
  67. odoo/addons/shopfloor/readme/ROADMAP.rst +4 -0
  68. odoo/addons/shopfloor/readme/USAGE.rst +6 -0
  69. odoo/addons/shopfloor/security/groups.xml +17 -0
  70. odoo/addons/shopfloor/services/__init__.py +16 -0
  71. odoo/addons/shopfloor/services/checkout.py +1763 -0
  72. odoo/addons/shopfloor/services/cluster_picking.py +1628 -0
  73. odoo/addons/shopfloor/services/delivery.py +828 -0
  74. odoo/addons/shopfloor/services/forms/__init__.py +1 -0
  75. odoo/addons/shopfloor/services/forms/picking_form.py +78 -0
  76. odoo/addons/shopfloor/services/location_content_transfer.py +1194 -0
  77. odoo/addons/shopfloor/services/menu.py +60 -0
  78. odoo/addons/shopfloor/services/picking_batch.py +126 -0
  79. odoo/addons/shopfloor/services/service.py +101 -0
  80. odoo/addons/shopfloor/services/single_pack_transfer.py +366 -0
  81. odoo/addons/shopfloor/services/zone_picking.py +1938 -0
  82. odoo/addons/shopfloor/static/description/icon.png +0 -0
  83. odoo/addons/shopfloor/static/description/index.html +500 -0
  84. odoo/addons/shopfloor/tests/__init__.py +83 -0
  85. odoo/addons/shopfloor/tests/common.py +324 -0
  86. odoo/addons/shopfloor/tests/models.py +29 -0
  87. odoo/addons/shopfloor/tests/test_actions_change_package_lot.py +1175 -0
  88. odoo/addons/shopfloor/tests/test_actions_data.py +376 -0
  89. odoo/addons/shopfloor/tests/test_actions_data_base.py +244 -0
  90. odoo/addons/shopfloor/tests/test_actions_data_detail.py +322 -0
  91. odoo/addons/shopfloor/tests/test_actions_search.py +248 -0
  92. odoo/addons/shopfloor/tests/test_actions_stock.py +48 -0
  93. odoo/addons/shopfloor/tests/test_checkout_auto_post.py +67 -0
  94. odoo/addons/shopfloor/tests/test_checkout_base.py +81 -0
  95. odoo/addons/shopfloor/tests/test_checkout_cancel_line.py +154 -0
  96. odoo/addons/shopfloor/tests/test_checkout_change_packaging.py +184 -0
  97. odoo/addons/shopfloor/tests/test_checkout_done.py +133 -0
  98. odoo/addons/shopfloor/tests/test_checkout_list_delivery_packaging.py +131 -0
  99. odoo/addons/shopfloor/tests/test_checkout_list_package.py +327 -0
  100. odoo/addons/shopfloor/tests/test_checkout_new_package.py +88 -0
  101. odoo/addons/shopfloor/tests/test_checkout_no_package.py +95 -0
  102. odoo/addons/shopfloor/tests/test_checkout_scan.py +174 -0
  103. odoo/addons/shopfloor/tests/test_checkout_scan_line.py +377 -0
  104. odoo/addons/shopfloor/tests/test_checkout_scan_line_base.py +25 -0
  105. odoo/addons/shopfloor/tests/test_checkout_scan_line_no_prefill_qty.py +91 -0
  106. odoo/addons/shopfloor/tests/test_checkout_scan_package_action.py +451 -0
  107. odoo/addons/shopfloor/tests/test_checkout_scan_package_action_no_prefill_qty.py +107 -0
  108. odoo/addons/shopfloor/tests/test_checkout_select.py +74 -0
  109. odoo/addons/shopfloor/tests/test_checkout_select_line.py +130 -0
  110. odoo/addons/shopfloor/tests/test_checkout_select_package_base.py +64 -0
  111. odoo/addons/shopfloor/tests/test_checkout_set_qty.py +257 -0
  112. odoo/addons/shopfloor/tests/test_checkout_summary.py +69 -0
  113. odoo/addons/shopfloor/tests/test_cluster_picking_base.py +83 -0
  114. odoo/addons/shopfloor/tests/test_cluster_picking_batch.py +109 -0
  115. odoo/addons/shopfloor/tests/test_cluster_picking_change_pack_lot.py +111 -0
  116. odoo/addons/shopfloor/tests/test_cluster_picking_is_zero.py +98 -0
  117. odoo/addons/shopfloor/tests/test_cluster_picking_scan_destination.py +376 -0
  118. odoo/addons/shopfloor/tests/test_cluster_picking_scan_destination_no_prefill_qty.py +115 -0
  119. odoo/addons/shopfloor/tests/test_cluster_picking_scan_line.py +402 -0
  120. odoo/addons/shopfloor/tests/test_cluster_picking_scan_line_location_or_pack_first.py +114 -0
  121. odoo/addons/shopfloor/tests/test_cluster_picking_scan_line_no_prefill_qty.py +70 -0
  122. odoo/addons/shopfloor/tests/test_cluster_picking_select.py +387 -0
  123. odoo/addons/shopfloor/tests/test_cluster_picking_skip.py +90 -0
  124. odoo/addons/shopfloor/tests/test_cluster_picking_stock_issue.py +364 -0
  125. odoo/addons/shopfloor/tests/test_cluster_picking_unload.py +911 -0
  126. odoo/addons/shopfloor/tests/test_delivery_base.py +155 -0
  127. odoo/addons/shopfloor/tests/test_delivery_done.py +108 -0
  128. odoo/addons/shopfloor/tests/test_delivery_list_stock_picking.py +49 -0
  129. odoo/addons/shopfloor/tests/test_delivery_reset_qty_done_line.py +119 -0
  130. odoo/addons/shopfloor/tests/test_delivery_reset_qty_done_pack.py +107 -0
  131. odoo/addons/shopfloor/tests/test_delivery_scan_deliver.py +557 -0
  132. odoo/addons/shopfloor/tests/test_delivery_select.py +38 -0
  133. odoo/addons/shopfloor/tests/test_delivery_set_qty_done_line.py +91 -0
  134. odoo/addons/shopfloor/tests/test_delivery_set_qty_done_pack.py +135 -0
  135. odoo/addons/shopfloor/tests/test_delivery_sublocation.py +180 -0
  136. odoo/addons/shopfloor/tests/test_location_content_transfer_base.py +136 -0
  137. odoo/addons/shopfloor/tests/test_location_content_transfer_get_work.py +125 -0
  138. odoo/addons/shopfloor/tests/test_location_content_transfer_mix.py +509 -0
  139. odoo/addons/shopfloor/tests/test_location_content_transfer_putaway.py +143 -0
  140. odoo/addons/shopfloor/tests/test_location_content_transfer_scan_location.py +34 -0
  141. odoo/addons/shopfloor/tests/test_location_content_transfer_set_destination_all.py +343 -0
  142. odoo/addons/shopfloor/tests/test_location_content_transfer_set_destination_package_or_line.py +1074 -0
  143. odoo/addons/shopfloor/tests/test_location_content_transfer_single.py +748 -0
  144. odoo/addons/shopfloor/tests/test_location_content_transfer_start.py +359 -0
  145. odoo/addons/shopfloor/tests/test_menu_base.py +261 -0
  146. odoo/addons/shopfloor/tests/test_menu_counters.py +61 -0
  147. odoo/addons/shopfloor/tests/test_misc.py +25 -0
  148. odoo/addons/shopfloor/tests/test_move_action_assign.py +87 -0
  149. odoo/addons/shopfloor/tests/test_openapi.py +21 -0
  150. odoo/addons/shopfloor/tests/test_picking_form.py +62 -0
  151. odoo/addons/shopfloor/tests/test_scan_anything.py +49 -0
  152. odoo/addons/shopfloor/tests/test_single_pack_transfer.py +1121 -0
  153. odoo/addons/shopfloor/tests/test_single_pack_transfer_base.py +32 -0
  154. odoo/addons/shopfloor/tests/test_single_pack_transfer_putaway.py +104 -0
  155. odoo/addons/shopfloor/tests/test_stock_split.py +204 -0
  156. odoo/addons/shopfloor/tests/test_user.py +42 -0
  157. odoo/addons/shopfloor/tests/test_zone_picking_base.py +608 -0
  158. odoo/addons/shopfloor/tests/test_zone_picking_change_pack_lot.py +140 -0
  159. odoo/addons/shopfloor/tests/test_zone_picking_select_line.py +723 -0
  160. odoo/addons/shopfloor/tests/test_zone_picking_select_line_first_scan_location.py +207 -0
  161. odoo/addons/shopfloor/tests/test_zone_picking_select_line_first_scan_location.py.bak +202 -0
  162. odoo/addons/shopfloor/tests/test_zone_picking_select_line_no_prefill_qty.py +107 -0
  163. odoo/addons/shopfloor/tests/test_zone_picking_select_picking_type.py +26 -0
  164. odoo/addons/shopfloor/tests/test_zone_picking_set_line_destination.py +643 -0
  165. odoo/addons/shopfloor/tests/test_zone_picking_set_line_destination_no_prefill_qty.py +146 -0
  166. odoo/addons/shopfloor/tests/test_zone_picking_set_line_destination_pick_pack.py +241 -0
  167. odoo/addons/shopfloor/tests/test_zone_picking_start.py +206 -0
  168. odoo/addons/shopfloor/tests/test_zone_picking_stock_issue.py +121 -0
  169. odoo/addons/shopfloor/tests/test_zone_picking_unload_all.py +353 -0
  170. odoo/addons/shopfloor/tests/test_zone_picking_unload_buffer_lines.py +113 -0
  171. odoo/addons/shopfloor/tests/test_zone_picking_unload_set_destination.py +374 -0
  172. odoo/addons/shopfloor/tests/test_zone_picking_unload_single.py +123 -0
  173. odoo/addons/shopfloor/tests/test_zone_picking_zero_check.py +43 -0
  174. odoo/addons/shopfloor/utils.py +13 -0
  175. odoo/addons/shopfloor/views/shopfloor_menu.xml +167 -0
  176. odoo/addons/shopfloor/views/stock_location.xml +20 -0
  177. odoo/addons/shopfloor/views/stock_move_line.xml +52 -0
  178. odoo/addons/shopfloor/views/stock_picking_type.xml +19 -0
  179. odoo_addon_shopfloor-16.0.1.0.0.24.dist-info/METADATA +192 -0
  180. odoo_addon_shopfloor-16.0.1.0.0.24.dist-info/RECORD +182 -0
  181. odoo_addon_shopfloor-16.0.1.0.0.24.dist-info/WHEEL +5 -0
  182. odoo_addon_shopfloor-16.0.1.0.0.24.dist-info/top_level.txt +1 -0
@@ -0,0 +1,557 @@
1
+ # Copyright 2020 Camptocamp SA (http://www.camptocamp.com)
2
+ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
3
+
4
+ from .test_delivery_base import DeliveryCommonCase
5
+
6
+ # pylint: disable=missing-return
7
+
8
+
9
+ class DeliveryScanDeliverCase(DeliveryCommonCase):
10
+ """Tests for /scan_deliver"""
11
+
12
+ @classmethod
13
+ def setUpClassBaseData(cls):
14
+ super().setUpClassBaseData()
15
+ cls.product_e.tracking = "lot"
16
+ cls.picking = picking = cls._create_picking(
17
+ lines=[
18
+ # we'll put A and B in a single package
19
+ (cls.product_a, 10),
20
+ (cls.product_b, 10),
21
+ # C alone in a package
22
+ (cls.product_c, 10),
23
+ # D as raw product
24
+ (cls.product_d, 10),
25
+ # E as raw product with a lot
26
+ (cls.product_e, 10),
27
+ # F in two different packages
28
+ (cls.product_f, 10),
29
+ # G in a package with quantity of one.
30
+ (cls.product_g, 10),
31
+ ]
32
+ )
33
+ cls.pack1_moves = picking.move_ids[:2]
34
+ cls.pack2_move = picking.move_ids[2]
35
+ cls.pack3_move = picking.move_ids[5]
36
+ cls.pack4_move = picking.move_ids[6]
37
+ cls.raw_move = picking.move_ids[3]
38
+ cls.raw_lot_move = picking.move_ids[4]
39
+ cls._fill_stock_for_moves(cls.pack1_moves, in_package=True)
40
+ cls._fill_stock_for_moves(cls.pack2_move, in_package=True)
41
+ cls._fill_stock_for_moves(cls.pack4_move, in_package=True)
42
+ cls._fill_stock_for_moves(cls.raw_move)
43
+ cls._fill_stock_for_moves(cls.raw_lot_move, in_lot=True)
44
+ # Set a lot for A for the mixed package (A + B)
45
+ cls.product_a_lot = cls.env["stock.lot"].create(
46
+ {"product_id": cls.product_a.id, "company_id": cls.env.company.id}
47
+ )
48
+ cls.product_a_quant = cls.env["stock.quant"].search(
49
+ [("product_id", "=", cls.product_a.id)]
50
+ )
51
+ cls.product_a_quant.sudo().lot_id = cls.product_a_lot
52
+ # Fill stock for F moves (two packages)
53
+ for __ in range(2):
54
+ product_f_pkg = cls.env["stock.quant.package"].create({})
55
+ cls._update_qty_in_location(
56
+ cls.pack3_move.location_id,
57
+ cls.pack3_move.product_id,
58
+ 5,
59
+ package=product_f_pkg,
60
+ )
61
+ picking.action_assign()
62
+ # Add a packaging on the raw product
63
+ cls.packaging = (
64
+ cls.env["product.packaging"]
65
+ .sudo()
66
+ .create(
67
+ {
68
+ "name": "TEST PACKAGING",
69
+ "product_id": cls.raw_move.product_id.id,
70
+ "qty": 10,
71
+ "product_uom_id": cls.raw_move.product_id.uom_id.id,
72
+ "barcode": "TEST_PACKAGING",
73
+ }
74
+ )
75
+ )
76
+ # Some records not related at all to the processed picking
77
+ cls.free_package = cls.env["stock.quant.package"].create(
78
+ {"name": "FREE_PACKAGE"}
79
+ )
80
+ cls.free_lot = cls.env["stock.lot"].create(
81
+ {
82
+ "name": "FREE_LOT",
83
+ "product_id": cls.product_a.id,
84
+ "company_id": cls.env.company.id,
85
+ }
86
+ )
87
+ cls.free_product = (
88
+ cls.env["product.product"]
89
+ .sudo()
90
+ .create({"name": "FREE_PRODUCT", "barcode": "FREE_PRODUCT"})
91
+ )
92
+
93
+ def test_scan_deliver_scan_picking_ok(self):
94
+ response = self.service.dispatch(
95
+ "scan_deliver",
96
+ params={
97
+ "barcode": self.picking.name,
98
+ "picking_id": None,
99
+ "location_id": None,
100
+ },
101
+ )
102
+ self.assert_response_deliver(response, picking=self.picking)
103
+
104
+ def test_scan_deliver_error_barcode_not_found(self):
105
+ response = self.service.dispatch(
106
+ "scan_deliver", params={"barcode": "NO VALID BARCODE", "picking_id": None}
107
+ )
108
+ self.assert_response_deliver(
109
+ response,
110
+ message=self.service.msg_store.barcode_not_found(),
111
+ )
112
+
113
+ def test_scan_deliver_error_barcode_not_found_keep_picking(self):
114
+ response = self.service.dispatch(
115
+ "scan_deliver",
116
+ params={"barcode": "NO VALID BARCODE", "picking_id": self.picking.id},
117
+ )
118
+ self.assert_response_deliver(
119
+ response,
120
+ # if the client was working on a picking (it sends picking_id, then
121
+ # send refreshed data)
122
+ picking=self.picking,
123
+ message=self.service.msg_store.barcode_not_found(),
124
+ )
125
+
126
+ def _test_scan_set_done_ok(self, move_lines, barcode, qties=None):
127
+ response = self.service.dispatch("scan_deliver", params={"barcode": barcode})
128
+ self.assert_qty_done(move_lines, qties)
129
+ picking = move_lines.move_id.picking_id
130
+ if picking.state == "done":
131
+ self.assert_response_deliver(
132
+ response, message=self.msg_store.transfer_complete(picking)
133
+ )
134
+ else:
135
+ self.assert_response_deliver(response, picking=picking)
136
+
137
+ def test_scan_deliver_scan_package(self):
138
+ move_lines = self.pack1_moves.mapped("move_line_ids")
139
+ package = move_lines.mapped("package_id")
140
+ self.assertEqual(self.picking.state, "assigned")
141
+ self._test_scan_set_done_ok(move_lines, package.name)
142
+ self.assertEqual(self.picking.state, "assigned")
143
+
144
+ def test_scan_deliver_scan_package_with_prepackaged_product(self):
145
+ """Check scanning a package process it entirely.
146
+
147
+ "Process as pre-packaged product" option is enabled to create a backorder.
148
+ """
149
+ self.menu.sudo().allow_prepackaged_product = True
150
+ move_lines = self.pack1_moves.mapped("move_line_ids")
151
+ package = move_lines.mapped("package_id")
152
+ self.assertEqual(self.picking.state, "assigned")
153
+ response = self.service.dispatch(
154
+ "scan_deliver", params={"barcode": package.name}
155
+ )
156
+ self.assert_response_deliver(
157
+ response, message=self.service.msg_store.transfer_complete(self.picking)
158
+ )
159
+ for line in move_lines:
160
+ self.assertEqual(line.move_id.product_uom_qty, line.move_id.quantity_done)
161
+ self.assertEqual(line.move_id.state, "done")
162
+ self.assertEqual(self.picking.state, "done")
163
+ self.assertTrue(self.picking.backorder_ids)
164
+
165
+ def test_scan_deliver_scan_package_no_move_lines(self):
166
+ response = self.service.dispatch(
167
+ "scan_deliver",
168
+ params={"barcode": self.free_package.name, "picking_id": self.picking.id},
169
+ )
170
+ self.assert_response_deliver(
171
+ response,
172
+ picking=self.picking,
173
+ message=self.service.msg_store.cannot_move_something_in_picking_type(),
174
+ )
175
+
176
+ def test_scan_deliver_scan_product_not_in_package(self):
177
+ """Check scanning product increment quantity done by one."""
178
+ for qty_done in range(1, 3):
179
+ response = self.service.dispatch(
180
+ "scan_deliver",
181
+ params={
182
+ "barcode": self.product_d.barcode,
183
+ "picking_id": self.picking.id,
184
+ },
185
+ )
186
+ self.assertEqual(self.raw_move.move_line_ids.qty_done, qty_done)
187
+
188
+ self.assert_response_deliver(
189
+ response,
190
+ picking=self.picking,
191
+ )
192
+
193
+ def test_scan_deliver_scan_product_in_package_multiple(self):
194
+ """Check product scanned alone in a package but quantity more than one."""
195
+ response = self.service.dispatch(
196
+ "scan_deliver",
197
+ params={"barcode": self.product_c.barcode, "picking_id": self.picking.id},
198
+ )
199
+ self.assert_response_deliver(
200
+ response,
201
+ picking=self.picking,
202
+ message=self.service.msg_store.product_not_unitary_in_package_scan_package(),
203
+ )
204
+
205
+ def test_scan_deliver_scan_product_in_multiple_packages(self):
206
+ response = self.service.dispatch(
207
+ "scan_deliver",
208
+ params={"barcode": self.product_f.barcode, "picking_id": self.picking.id},
209
+ )
210
+ self.assert_response_deliver(
211
+ response,
212
+ picking=self.picking,
213
+ message=self.service.msg_store.product_multiple_packages_scan_package(),
214
+ )
215
+
216
+ def test_scan_deliver_scan_product_in_mixed_package(self):
217
+ response = self.service.dispatch(
218
+ "scan_deliver",
219
+ params={"barcode": self.product_a.barcode, "picking_id": self.picking.id},
220
+ )
221
+ self.assert_response_deliver(
222
+ response,
223
+ picking=self.picking,
224
+ message=self.service.msg_store.product_mixed_package_scan_package(),
225
+ )
226
+
227
+ def test_scan_deliver_scan_product_tracked_by_lot(self):
228
+ response = self.service.dispatch(
229
+ "scan_deliver",
230
+ params={"barcode": self.product_e.barcode, "picking_id": self.picking.id},
231
+ )
232
+ self.assert_response_deliver(
233
+ response,
234
+ picking=self.picking,
235
+ message=self.service.msg_store.scan_lot_on_product_tracked_by_lot(),
236
+ )
237
+
238
+ def test_scan_deliver_scan_raw_product_ok(self):
239
+ self._test_scan_set_done_ok(
240
+ self.raw_move.mapped("move_line_ids"),
241
+ self.product_d.barcode,
242
+ [1], # When scanning a product we want to process only 1 qty
243
+ )
244
+
245
+ def test_scan_deliver_scan_raw_product_in_multiple_pickings(self):
246
+ # Scan a raw product (not related to a package or lot) which is present
247
+ # in multiple delivery operations (so two different moves).
248
+ # We should be able to process these two moves one after the other.
249
+ self.picking.do_unreserve()
250
+ self.raw_move.product_uom_qty = 1
251
+ self.picking.action_assign()
252
+ picking2 = self._create_picking(
253
+ lines=[
254
+ # D as raw product
255
+ (self.product_d, 1),
256
+ ]
257
+ )
258
+ raw_move2 = picking2.move_ids
259
+ self._fill_stock_for_moves(raw_move2)
260
+ picking2.action_assign()
261
+ # Scan the first move
262
+ self._test_scan_set_done_ok(
263
+ self.raw_move.mapped("move_line_ids"), self.product_d.barcode
264
+ )
265
+ # Scan the second move
266
+ # NOTE: we do not use '_test_scan_set_done_ok' here as we expect
267
+ # the delivery to be complete (we process its only move line).
268
+ response = self.service.dispatch(
269
+ "scan_deliver", params={"barcode": self.product_d.barcode}
270
+ )
271
+ self.assert_response_deliver(
272
+ response, message=self.service.msg_store.transfer_complete(self.picking)
273
+ )
274
+ self.assertEqual(raw_move2.quantity_done, 1)
275
+ self.assertEqual(raw_move2.state, "done")
276
+
277
+ def test_scan_deliver_scan_product_not_found(self):
278
+ response = self.service.dispatch(
279
+ "scan_deliver", params={"barcode": self.free_product.barcode}
280
+ )
281
+ self.assert_response_deliver(
282
+ response,
283
+ message=self.service.msg_store.product_not_found_in_pickings(),
284
+ )
285
+
286
+ def test_scan_deliver_scan_lot(self):
287
+ """Check scanning a lot process only one piece/unit of this lot."""
288
+ line = self.raw_lot_move.move_line_ids
289
+ lot = line.lot_id
290
+ response = self.service.dispatch("scan_deliver", params={"barcode": lot.name})
291
+ self.assert_response_deliver(
292
+ response,
293
+ picking=self.picking,
294
+ )
295
+ self.assertEqual(line.qty_done, 1)
296
+ self.assertEqual(line.state, "assigned")
297
+ for _ in range(int(line.reserved_uom_qty) - 1):
298
+ self.service.dispatch(
299
+ "scan_deliver",
300
+ params={
301
+ "barcode": lot.name,
302
+ "picking_id": self.picking.id,
303
+ },
304
+ )
305
+ self.assertEqual(line.qty_done, self.raw_lot_move.product_uom_qty)
306
+
307
+ def test_scan_deliver_scan_lot_with_prepackaged_product(self):
308
+ """Check scanning a lot process only one piece/unit of this lot.
309
+
310
+ "Process as pre-packaged product" option is enabled to create a backorder.
311
+ """
312
+ self.menu.sudo().allow_prepackaged_product = True
313
+ line = self.raw_lot_move.move_line_ids
314
+ lot = line.lot_id
315
+ response = self.service.dispatch("scan_deliver", params={"barcode": lot.name})
316
+ self.assert_response_deliver(
317
+ response, message=self.service.msg_store.transfer_complete(self.picking)
318
+ )
319
+ self.assertEqual(line.qty_done, 1)
320
+ self.assertEqual(line.move_id.state, "done")
321
+ self.assertEqual(self.picking.state, "done")
322
+ self.assertTrue(self.picking.backorder_ids)
323
+
324
+ def test_scan_deliver_scan_lot_not_found(self):
325
+ response = self.service.dispatch("scan_deliver", params={"barcode": "FREE_LOT"})
326
+ self.assert_response_deliver(
327
+ response,
328
+ message=self.service.msg_store.lot_not_found_in_pickings(),
329
+ )
330
+
331
+ def test_scan_deliver_scan_lot_in_mixed_package(self):
332
+ response = self.service.dispatch(
333
+ "scan_deliver", params={"barcode": self.product_a_lot.name}
334
+ )
335
+ self.assert_response_deliver(
336
+ response,
337
+ picking=self.picking,
338
+ message=self.service.msg_store.lot_mixed_package_scan_package(),
339
+ )
340
+
341
+ def test_scan_deliver_scan_product_packaging(self):
342
+ """Check scanning a product packaging use the packaging quantity.
343
+
344
+ Quantity on the line is the packaging quantity
345
+ """
346
+ # Scan a product packaging having the same qty than the qty to ship.
347
+ # We have 10 qties to ship and we scan a product packaging of 10 qties.
348
+ line = self.raw_move.mapped("move_line_ids")
349
+ response = self.service.dispatch(
350
+ "scan_deliver", params={"barcode": self.packaging.barcode}
351
+ )
352
+ self.assert_response_deliver(response, picking=self.picking)
353
+ self.assertEqual(line.qty_done, self.packaging.qty)
354
+
355
+ def test_scan_deliver_scan_product_packaging_with_prepackaged_product(self):
356
+ """Check scanning a product packaging use the packaging quantity.
357
+
358
+ Quantity on the line is the packaging quantity
359
+
360
+ "Process as pre-packaged product" option is enabled to create a backorder.
361
+ """
362
+ # Scan a product packaging having the same qty than the qty to ship.
363
+ # We have 10 qties to ship and we scan a product packaging of 10 qties.
364
+ self.menu.sudo().allow_prepackaged_product = True
365
+ line = self.raw_move.mapped("move_line_ids")
366
+ response = self.service.dispatch(
367
+ "scan_deliver", params={"barcode": self.packaging.barcode}
368
+ )
369
+ self.assert_response_deliver(
370
+ response, message=self.service.msg_store.transfer_complete(self.picking)
371
+ )
372
+ self.assertEqual(line.qty_done, self.packaging.qty)
373
+
374
+ def test_scan_deliver_scan_product_packaging_partial_qty(self):
375
+ # Scan a product packaging with a smaller qty than the move line
376
+ # We have 10 qties to ship but we scan a product packaging of 5 qties.
377
+ # -> Processed 5 over 10 qties
378
+ # Then we scan a second time the product packaging all qties will be processed
379
+ # -> Processed 10/10
380
+ self.packaging.qty = 5
381
+ line = self.raw_move.mapped("move_line_ids")
382
+ self.assertEqual(line.move_id.product_qty, 10)
383
+ response = self.service.dispatch(
384
+ "scan_deliver", params={"barcode": self.packaging.barcode}
385
+ )
386
+ self.assert_response_deliver(response, picking=self.picking)
387
+ self.assertEqual(line.qty_done, self.packaging.qty)
388
+ self.assertTrue(line.move_id.product_qty > self.packaging.qty)
389
+ # Process the remaining qties, still by scanning the packaging
390
+ response = self.service.dispatch(
391
+ "scan_deliver", params={"barcode": self.packaging.barcode}
392
+ )
393
+ self.assert_response_deliver(response, picking=self.picking)
394
+ self.assertEqual(line.move_id.product_qty, line.move_id.quantity_done)
395
+ self.assertEqual(line.move_id.state, "assigned")
396
+
397
+ def test_scan_deliver_scan_product_packaging_partial_qty_with_prepackaged_product(
398
+ self,
399
+ ):
400
+ # Scan a product packaging with a smaller qty than the move line
401
+ # while the "Process pre-packaged product" option is enabled.
402
+ # We have 10 qties to ship but we scan a product packaging of 5 qties.
403
+ # -> Ship 5 (creating a backorder for the 5 remaining)
404
+ # Then we scan a second time the product packaging to process the backorder
405
+ # -> Ship 5 (again)
406
+ self.menu.sudo().allow_prepackaged_product = True
407
+ self.packaging.qty = 5
408
+ line = self.raw_move.mapped("move_line_ids")
409
+ self.assertEqual(line.move_id.product_qty, 10)
410
+ response = self.service.dispatch(
411
+ "scan_deliver", params={"barcode": self.packaging.barcode}
412
+ )
413
+ self.assert_response_deliver(
414
+ response,
415
+ message=self.service.msg_store.transfer_complete(self.picking),
416
+ )
417
+ self.assertEqual(line.qty_done, self.packaging.qty)
418
+ self.assertEqual(line.move_id.product_qty, self.packaging.qty)
419
+ self.assertEqual(line.move_id.state, "done")
420
+ self.assertTrue(self.picking.backorder_ids)
421
+ # Process the backorder
422
+ backorder = self.picking.backorder_ids
423
+ backorder_raw_move = backorder.move_ids.filtered_domain(
424
+ [("product_id", "=", self.product_d.id)]
425
+ )
426
+ backorder_line = backorder_raw_move.move_line_ids
427
+ response = self.service.dispatch(
428
+ "scan_deliver", params={"barcode": self.packaging.barcode}
429
+ )
430
+ self.assert_response_deliver(
431
+ response, message=self.service.msg_store.transfer_complete(backorder)
432
+ )
433
+ self.assertEqual(backorder_line.move_id.product_qty, self.packaging.qty)
434
+ self.assertEqual(backorder_line.move_id.state, "done")
435
+
436
+ def test_scan_deliver_scan_product_alone_in_package_qty_one(self):
437
+ """Check scanning a product alone in a package with a quantity of one."""
438
+ self.picking.action_cancel()
439
+ pick = self._create_picking(
440
+ lines=[
441
+ (self.product_c, 1),
442
+ ]
443
+ )
444
+ pack_move = pick.move_ids[:1]
445
+ self._fill_stock_for_moves(pack_move, in_package=True)
446
+ pick.action_assign()
447
+ move_lines = pick.move_ids.mapped("move_line_ids")
448
+ self._test_scan_set_done_ok(move_lines, self.product_c.barcode, [1])
449
+
450
+ def test_scan_deliver_picking_done(self):
451
+ # Set qty done for all lines (packages/raw product/lot...), picking is
452
+ # automatically set to done when the last line is completed
453
+ package1 = self.pack1_moves.mapped("move_line_ids").mapped("package_id")
454
+ package2 = self.pack2_move.mapped("move_line_ids").mapped("package_id")
455
+ package4 = self.pack4_move.mapped("move_line_ids").mapped("package_id")
456
+ self.service.dispatch(
457
+ "set_qty_done_pack",
458
+ params={"package_id": package1.id, "picking_id": self.picking.id},
459
+ )
460
+ self.assertEqual(self.picking.state, "assigned")
461
+ self.service.dispatch(
462
+ "set_qty_done_pack",
463
+ params={"package_id": package2.id, "picking_id": self.picking.id},
464
+ )
465
+ self.assertEqual(self.picking.state, "assigned")
466
+
467
+ self.service.dispatch(
468
+ "set_qty_done_pack",
469
+ params={"package_id": package4.id, "picking_id": self.picking.id},
470
+ )
471
+ self.assertEqual(self.picking.state, "assigned")
472
+
473
+ # When a product is scanned, we process only one unit of it
474
+ for _ in range(int(self.raw_move.product_uom_qty)):
475
+ self.service.dispatch(
476
+ "scan_deliver",
477
+ params={
478
+ "barcode": self.raw_move.product_id.barcode,
479
+ "picking_id": self.picking.id,
480
+ },
481
+ )
482
+ self.assertEqual(self.picking.state, "assigned")
483
+
484
+ # When a lot is scanned, we process only one unit of it
485
+ lot = self.raw_lot_move.move_line_ids.lot_id
486
+ for _ in range(int(self.raw_lot_move.product_uom_qty)):
487
+ response = self.service.dispatch(
488
+ "scan_deliver",
489
+ params={"barcode": lot.name, "picking_id": self.picking.id},
490
+ )
491
+ self.assertEqual(self.picking.state, "assigned")
492
+ packages_f = self.pack3_move.move_line_ids.mapped("package_id")
493
+ # While all lines are not processed, response still returns the picking
494
+ self.assert_response_deliver(
495
+ response,
496
+ picking=self.picking,
497
+ )
498
+ response = None
499
+ # Once all lines are processed, the last response has no picking returned
500
+ for package in packages_f:
501
+ response = self.service.dispatch(
502
+ "set_qty_done_pack",
503
+ params={"package_id": package.id, "picking_id": self.picking.id},
504
+ )
505
+ self.assertEqual(self.picking.state, "done")
506
+ self.assert_response_deliver(
507
+ response,
508
+ message=self.service.msg_store.transfer_complete(self.picking),
509
+ )
510
+
511
+
512
+ class DeliveryScanDeliverSpecialCase(DeliveryCommonCase):
513
+ """Special cases with different setup for /scan_deliver"""
514
+
515
+ def test_scan_deliver_error_picking_wrong_type(self):
516
+ picking = self._create_picking(
517
+ picking_type=self.wh.out_type_id, lines=[(self.product_a, 10)]
518
+ )
519
+ self._fill_stock_for_moves(picking.move_ids, in_package=True)
520
+ picking.action_assign()
521
+ response = self.service.dispatch(
522
+ "scan_deliver", params={"barcode": picking.name}
523
+ )
524
+ self.assert_response_deliver(
525
+ response,
526
+ message={
527
+ "message_type": "error",
528
+ "body": "You cannot move this using this menu.",
529
+ },
530
+ )
531
+
532
+ def test_scan_deliver_error_picking_unavailable(self):
533
+ picking = self._create_picking(lines=[(self.product_a, 10)])
534
+ response = self.service.dispatch(
535
+ "scan_deliver", params={"barcode": picking.name}
536
+ )
537
+ self.assert_response_deliver(
538
+ response,
539
+ message={
540
+ "message_type": "error",
541
+ "body": "Transfer {} is not available.".format(picking.name),
542
+ },
543
+ )
544
+
545
+ def test_scan_deliver_error_picking_already_done(self):
546
+ picking = self._create_picking(lines=[(self.product_a, 10)])
547
+ self._fill_stock_for_moves(picking.move_ids, in_package=True)
548
+ picking.action_assign()
549
+ picking.move_line_ids.qty_done = picking.move_line_ids.reserved_uom_qty
550
+ picking._action_done()
551
+ response = self.service.dispatch(
552
+ "scan_deliver", params={"barcode": picking.name}
553
+ )
554
+ self.assert_response_deliver(
555
+ response,
556
+ message={"message_type": "info", "body": "Operation already processed."},
557
+ )
@@ -0,0 +1,38 @@
1
+ # Copyright 2020 Camptocamp SA
2
+ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
3
+
4
+ from .test_delivery_base import DeliveryCommonCase
5
+
6
+ # pylint: disable=missing-return
7
+
8
+
9
+ class DeliverySelectCase(DeliveryCommonCase):
10
+ """Tests for /select"""
11
+
12
+ @classmethod
13
+ def setUpClassBaseData(cls):
14
+ super().setUpClassBaseData()
15
+ cls.picking1 = cls._create_picking(
16
+ lines=[(cls.product_a, 10), (cls.product_b, 10)]
17
+ )
18
+ cls.picking2 = cls._create_picking(
19
+ lines=[(cls.product_a, 10), (cls.product_b, 10)]
20
+ )
21
+ cls._fill_stock_for_moves(cls.picking1.move_ids)
22
+ cls._fill_stock_for_moves(cls.picking2.move_ids)
23
+ cls.pickings = cls.picking1 | cls.picking2
24
+ cls.pickings.action_assign()
25
+
26
+ def test_select_ok(self):
27
+ response = self.service.dispatch(
28
+ "select", params={"picking_id": self.picking1.id}
29
+ )
30
+ self.assert_response_deliver(response, picking=self.picking1)
31
+
32
+ def test_select_not_found(self):
33
+ response = self.service.dispatch("select", params={"picking_id": -1})
34
+ self.assert_response_manual_selection(
35
+ response,
36
+ pickings=self.pickings,
37
+ message=self.service.msg_store.stock_picking_not_found(),
38
+ )
@@ -0,0 +1,91 @@
1
+ # Copyright 2020 Camptocamp SA
2
+ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
3
+
4
+ from .test_delivery_base import DeliveryCommonCase
5
+
6
+ # pylint: disable=missing-return
7
+
8
+
9
+ class DeliverySetQtyDoneLineCase(DeliveryCommonCase):
10
+ """Tests for /set_qty_done_line"""
11
+
12
+ @classmethod
13
+ def setUpClassBaseData(cls):
14
+ super().setUpClassBaseData()
15
+ cls.picking = picking = cls._create_picking(
16
+ lines=[
17
+ # put A in a package
18
+ (cls.product_a, 10),
19
+ # B as raw product
20
+ (cls.product_b, 10),
21
+ ]
22
+ )
23
+ cls.pack1_move = picking.move_ids[0]
24
+ cls.raw_move = picking.move_ids[1]
25
+ cls._fill_stock_for_moves(cls.pack1_move, in_package=True)
26
+ cls._fill_stock_for_moves(cls.raw_move)
27
+ picking.action_assign()
28
+
29
+ def _test_set_qty_done_line_ok(self, move_line):
30
+ response = self.service.dispatch(
31
+ "set_qty_done_line",
32
+ params={"move_line_id": move_line.id, "picking_id": self.picking.id},
33
+ )
34
+ self.assert_qty_done(move_line)
35
+ self.assert_response_deliver(response, picking=self.picking)
36
+
37
+ def test_set_qty_done_line_picking_not_found(self):
38
+ move_line = self.pack1_move.mapped("move_line_ids")
39
+ response = self.service.dispatch(
40
+ "set_qty_done_line", params={"move_line_id": move_line.id, "picking_id": -1}
41
+ )
42
+ self.assert_response_deliver(
43
+ response, message=self.service.msg_store.stock_picking_not_found()
44
+ )
45
+
46
+ def test_set_qty_done_line_picking_canceled(self):
47
+ move_line = self.pack1_move.mapped("move_line_ids")
48
+ self.picking.action_cancel()
49
+ response = self.service.dispatch(
50
+ "set_qty_done_line",
51
+ params={"move_line_id": move_line.id, "picking_id": self.picking.id},
52
+ )
53
+ self.assert_response_deliver(
54
+ response,
55
+ message=self.service.msg_store.stock_picking_not_available(self.picking),
56
+ )
57
+
58
+ def test_set_qty_done_line_line_not_found(self):
59
+ response = self.service.dispatch(
60
+ "set_qty_done_line",
61
+ params={"move_line_id": -1, "picking_id": self.picking.id},
62
+ )
63
+ self.assert_response_deliver(
64
+ response,
65
+ picking=self.picking,
66
+ message=self.service.msg_store.record_not_found(),
67
+ )
68
+
69
+ def test_set_qty_done_line_ok(self):
70
+ move_line = self.raw_move.mapped("move_line_ids")
71
+ self._test_set_qty_done_line_ok(move_line)
72
+ # picking is still assigned as only one move line have been processed
73
+ self.assertEqual(self.picking.state, "assigned")
74
+
75
+ def test_set_qty_done_line_picking_done(self):
76
+ # process the first move line with a package
77
+ move_line = self.pack1_move.mapped("move_line_ids")
78
+ package = move_line.mapped("package_id")
79
+ self.service.dispatch(
80
+ "set_qty_done_pack",
81
+ params={"package_id": package.id, "picking_id": self.picking.id},
82
+ )
83
+ self.assertEqual(self.picking.state, "assigned")
84
+ # process the remaining move line
85
+ move_line = self.raw_move.mapped("move_line_ids")
86
+ self.service.dispatch(
87
+ "set_qty_done_line",
88
+ params={"move_line_id": move_line.id, "picking_id": self.picking.id},
89
+ )
90
+ # picking is done once all its moves have been processed
91
+ self.assertEqual(self.picking.state, "done")