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,377 @@
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 .test_checkout_scan_line_base import CheckoutScanLineCaseBase
4
+
5
+
6
+ # pylint: disable=missing-return
7
+ class CheckoutScanLineCase(CheckoutScanLineCaseBase):
8
+ @classmethod
9
+ def setUpClassBaseData(cls, *args, **kwargs):
10
+ super().setUpClassBaseData(*args, **kwargs)
11
+ cls.delivery_packaging = (
12
+ cls.env["stock.package.type"]
13
+ .sudo()
14
+ .create(
15
+ {
16
+ "name": "DelivBox",
17
+ "barcode": "DelivBox",
18
+ }
19
+ )
20
+ )
21
+
22
+ def test_scan_line_package_ok(self):
23
+ picking = self._create_picking(
24
+ lines=[(self.product_a, 10), (self.product_b, 10)]
25
+ )
26
+ move1 = picking.move_ids[0]
27
+ move2 = picking.move_ids[1]
28
+ # put the lines in 2 separate packages (only the first line should be selected
29
+ # by the package barcode)
30
+ self._fill_stock_for_moves(move1, in_package=True)
31
+ self._fill_stock_for_moves(move2, in_package=True)
32
+ picking.action_assign()
33
+ move_line = move1.move_line_ids
34
+ self._test_scan_line_ok(move_line.package_id.name, move_line)
35
+
36
+ def test_scan_line_package_ok_packing_info_empty_info(self):
37
+ picking = self._create_picking(
38
+ lines=[(self.product_a, 10), (self.product_b, 10)]
39
+ )
40
+ move1 = picking.move_ids[0]
41
+ move2 = picking.move_ids[1]
42
+ # put the lines in 2 separate packages (only the first line should be selected
43
+ # by the package barcode)
44
+ self._fill_stock_for_moves(move1, in_package=True)
45
+ self._fill_stock_for_moves(move2, in_package=True)
46
+ picking.action_assign()
47
+ move_line = move1.move_line_ids
48
+ self._test_scan_line_ok(move_line.package_id.name, move_line)
49
+
50
+ def test_scan_line_package_several_lines_ok(self):
51
+ picking = self._create_picking(
52
+ lines=[(self.product_a, 10), (self.product_b, 10)]
53
+ )
54
+ # put all the lines in the same source package
55
+ self._fill_stock_for_moves(picking.move_ids, in_package=True)
56
+ picking.action_assign()
57
+ package = picking.move_line_ids.mapped("package_id")
58
+ self._test_scan_line_ok(package.name, picking.move_line_ids)
59
+
60
+ def test_scan_line_product_ok(self):
61
+ picking = self._create_picking(
62
+ lines=[(self.product_a, 10), (self.product_b, 10)]
63
+ )
64
+ # do not put them in a package, we'll pack units here
65
+ self._fill_stock_for_moves(picking.move_ids)
66
+ picking.action_assign()
67
+ # The product a is scanned, so selected and quantity updated
68
+ line_a = picking.move_line_ids.filtered(
69
+ lambda l: l.product_id == self.product_a
70
+ )
71
+ # Because not part of a package other lines are selected also
72
+ related_lines = picking.move_line_ids - line_a
73
+ selected_lines = picking.move_line_ids
74
+ self._test_scan_line_ok(
75
+ self.product_a.barcode, selected_lines, related_lines=related_lines
76
+ )
77
+
78
+ def test_scan_line_product_several_lines_ok(self):
79
+ picking = self._create_picking(
80
+ lines=[(self.product_a, 10), (self.product_a, 10), (self.product_b, 10)]
81
+ )
82
+ self._fill_stock_for_moves(picking.move_ids)
83
+ picking.action_assign()
84
+ # The product a is scanned, so selected and quantity updated
85
+ lines_a = picking.move_line_ids.filtered(
86
+ lambda l: l.product_id == self.product_a
87
+ )
88
+ # Because not part of a package other lines are selected also
89
+ related_lines = picking.move_line_ids - lines_a
90
+ selected_lines = picking.move_line_ids
91
+ self._test_scan_line_ok(
92
+ self.product_a.barcode, selected_lines, related_lines=related_lines
93
+ )
94
+
95
+ def test_scan_line_product_packaging_ok(self):
96
+ picking = self._create_picking(
97
+ lines=[(self.product_a, 10), (self.product_a, 10), (self.product_b, 10)]
98
+ )
99
+ self._fill_stock_for_moves(picking.move_ids)
100
+ picking.action_assign()
101
+ lines_a = picking.move_line_ids.filtered(
102
+ lambda l: l.product_id == self.product_a
103
+ )
104
+ # when we scan the packaging of the product, we should select the
105
+ # lines as if the product was scanned
106
+ # Because not part of a package other lines are selected also
107
+ related_lines = picking.move_line_ids - lines_a
108
+ selected_lines = picking.move_line_ids
109
+ self._test_scan_line_ok(
110
+ self.product_a_packaging.barcode, selected_lines, related_lines
111
+ )
112
+
113
+ def test_scan_line_product_lot_ok(self):
114
+ picking = self._create_picking(
115
+ lines=[(self.product_a, 1), (self.product_a, 1), (self.product_b, 1)]
116
+ )
117
+ for move in picking.move_ids:
118
+ self._fill_stock_for_moves(move, in_lot=True)
119
+ picking.action_assign()
120
+ first_line = picking.move_line_ids[0]
121
+ lot = first_line.lot_id
122
+ self._test_scan_line_ok(lot.name, first_line)
123
+
124
+ def test_scan_line_product_in_one_package_all_package_lines_ok(self):
125
+ picking = self._create_picking(
126
+ lines=[(self.product_a, 10), (self.product_b, 10)]
127
+ )
128
+ self._fill_stock_for_moves(picking.move_ids, in_package=True)
129
+ picking.action_assign()
130
+ # Product_a and product_b are in the same package, when we scan product_a,
131
+ # we expect to work on all the lines of the package. If product_a was in
132
+ # more than one package, it would be an error.
133
+ self._test_scan_line_ok(self.product_a.barcode, picking.move_line_ids)
134
+
135
+ def _test_scan_line_error(self, picking, barcode, message):
136
+ """Test errors for /scan_line
137
+
138
+ :param picking: the picking we are currently working with (selected)
139
+ :param barcode: the barcode we scan
140
+ :param message: the dict of expected error message
141
+ """
142
+ response = self.service.dispatch(
143
+ "scan_line", params={"picking_id": picking.id, "barcode": barcode}
144
+ )
145
+ self.assert_response(
146
+ response,
147
+ next_state="select_line",
148
+ data=self._data_for_select_line(picking),
149
+ message=message,
150
+ )
151
+
152
+ def test_scan_line_error_barcode_not_found(self):
153
+ picking = self._create_picking(lines=[(self.product_a, 10)])
154
+ self._fill_stock_for_moves(picking.move_ids, in_package=True)
155
+ picking.action_assign()
156
+ self._test_scan_line_error(
157
+ picking,
158
+ "NOT A BARCODE",
159
+ {"message_type": "error", "body": "Barcode not found"},
160
+ )
161
+
162
+ def test_scan_line_error_package_not_in_picking(self):
163
+ picking = self._create_picking(lines=[(self.product_a, 10)])
164
+ self._fill_stock_for_moves(picking.move_ids, in_package=True)
165
+ picking2 = self._create_picking(lines=[(self.product_a, 10)])
166
+ self._fill_stock_for_moves(picking2.move_ids, in_package=True)
167
+ (picking | picking2).action_assign()
168
+ package = picking2.move_line_ids.package_id
169
+ # we work with picking, but we scan the package of picking2
170
+ self._test_scan_line_error(
171
+ picking,
172
+ package.name,
173
+ {
174
+ "message_type": "error",
175
+ "body": "Package {} is not in the current transfer.".format(
176
+ package.name
177
+ ),
178
+ },
179
+ )
180
+
181
+ def test_scan_line_error_product_tracked_by_lot(self):
182
+ self.product_a.tracking = "lot"
183
+ picking = self._create_picking(lines=[(self.product_a, 10)])
184
+ self._fill_stock_for_moves(picking.move_ids, in_package=True)
185
+ picking.action_assign()
186
+ # product tracked by lot, but we scan the product barcode, user
187
+ # has to scan the lot
188
+ self._test_scan_line_error(
189
+ picking,
190
+ self.product_a.barcode,
191
+ {
192
+ "message_type": "warning",
193
+ "body": "Product tracked by lot, please scan one.",
194
+ },
195
+ )
196
+
197
+ def test_scan_line_error_product_in_two_packages(self):
198
+ picking = self._create_picking(
199
+ lines=[(self.product_a, 10), (self.product_a, 10)],
200
+ # when action_confirm is called, it would merge the moves
201
+ confirm=False,
202
+ )
203
+ self._fill_stock_for_moves(picking.move_ids[0], in_package=True)
204
+ self._fill_stock_for_moves(picking.move_ids[1], in_package=True)
205
+ picking.action_assign()
206
+ self._test_scan_line_error(
207
+ picking,
208
+ self.product_a.barcode,
209
+ {
210
+ "message_type": "warning",
211
+ "body": "This product is part of multiple"
212
+ " packages, please scan a package.",
213
+ },
214
+ )
215
+
216
+ def test_scan_line_error_product_in_one_package_and_unit(self):
217
+ picking = self._create_picking(
218
+ lines=[(self.product_a, 10), (self.product_a, 10)],
219
+ # when action_confirm is called, it would merge the moves
220
+ # we want to keep them separated to put a part in a package
221
+ confirm=False,
222
+ )
223
+ # put the product in one package and the other as unit
224
+ self._fill_stock_for_moves(picking.move_ids[0], in_package=True)
225
+ self._fill_stock_for_moves(picking.move_ids[1])
226
+ picking.action_assign()
227
+ self._test_scan_line_error(
228
+ picking,
229
+ self.product_a.barcode,
230
+ {
231
+ "message_type": "warning",
232
+ "body": "This product is part of multiple"
233
+ " packages, please scan a package.",
234
+ },
235
+ )
236
+
237
+ def test_scan_line_error_product_not_in_picking(self):
238
+ picking = self._create_picking(lines=[(self.product_a, 10)])
239
+ self._fill_stock_for_moves(picking.move_ids, in_package=True)
240
+ picking.action_assign()
241
+ self._test_scan_line_error(
242
+ picking,
243
+ self.product_b.barcode,
244
+ {
245
+ "message_type": "error",
246
+ "body": "Product is not in the current transfer.",
247
+ },
248
+ )
249
+
250
+ def test_scan_line_error_lot_not_in_picking(self):
251
+ picking = self._create_picking(lines=[(self.product_a, 10)])
252
+ self._fill_stock_for_moves(picking.move_ids, in_lot=True)
253
+ picking.action_assign()
254
+ lot = self.env["stock.lot"].create(
255
+ {"product_id": self.product_a.id, "company_id": self.env.company.id}
256
+ )
257
+ self._test_scan_line_error(
258
+ picking,
259
+ lot.name,
260
+ {"message_type": "error", "body": "Lot is not in the current transfer."},
261
+ )
262
+
263
+ def test_scan_line_error_lot_in_two_packages(self):
264
+ picking = self._create_picking(
265
+ lines=[(self.product_a, 10), (self.product_a, 10)],
266
+ # when action_confirm is called, it would merge the moves
267
+ confirm=False,
268
+ )
269
+ # we want the same lot to be used in 2 lines with different packages
270
+ lot = self.env["stock.lot"].create(
271
+ {"product_id": self.product_a.id, "company_id": self.env.company.id}
272
+ )
273
+ self._fill_stock_for_moves(picking.move_ids[0], in_package=True, in_lot=lot)
274
+ self._fill_stock_for_moves(picking.move_ids[1], in_package=True, in_lot=lot)
275
+ picking.action_assign()
276
+ self._test_scan_line_error(
277
+ picking,
278
+ lot.name,
279
+ {
280
+ "message_type": "warning",
281
+ "body": "This lot is part of multiple"
282
+ " packages, please scan a package.",
283
+ },
284
+ )
285
+
286
+ def test_scan_line_error_lot_in_one_package_and_unit(self):
287
+ picking = self._create_picking(
288
+ lines=[(self.product_a, 10), (self.product_a, 10)],
289
+ # when action_confirm is called, it would merge the moves
290
+ confirm=False,
291
+ )
292
+ # we want the same lot to be used in 2 lines with different packages
293
+ lot = self.env["stock.lot"].create(
294
+ {"product_id": self.product_a.id, "company_id": self.env.company.id}
295
+ )
296
+ self._fill_stock_for_moves(picking.move_ids[0], in_package=True, in_lot=lot)
297
+ self._fill_stock_for_moves(picking.move_ids[1], in_lot=lot)
298
+ picking.action_assign()
299
+ self._test_scan_line_error(
300
+ picking,
301
+ lot.name,
302
+ {
303
+ "message_type": "warning",
304
+ "body": "This lot is part of multiple"
305
+ " packages, please scan a package.",
306
+ },
307
+ )
308
+
309
+ def test_scan_line_all_lines_done(self):
310
+ picking = self._create_picking(
311
+ lines=[(self.product_a, 10), (self.product_b, 10)]
312
+ )
313
+ self._fill_stock_for_moves(picking.move_ids, in_package=True)
314
+ picking.action_assign()
315
+ # set all lines as done
316
+ picking.move_line_ids.write({"qty_done": 10.0, "shopfloor_checkout_done": True})
317
+ response = self.service.dispatch(
318
+ "scan_line",
319
+ params={
320
+ "picking_id": picking.id,
321
+ # the barcode doesn't matter as we have no
322
+ # lines to pack anymore
323
+ "barcode": self.product_a.barcode,
324
+ },
325
+ )
326
+ self.assert_response(
327
+ response,
328
+ next_state="summary",
329
+ data={
330
+ "picking": self._stock_picking_data(picking, done=True),
331
+ "all_processed": True,
332
+ },
333
+ )
334
+
335
+ def test_scan_line_delivery_package_ok(self):
336
+ picking = self._create_picking(
337
+ lines=[(self.product_a, 10), (self.product_b, 10)]
338
+ )
339
+ move1 = picking.move_ids[0]
340
+ move2 = picking.move_ids[1]
341
+ # put the lines in 2 separate packages (only the first line should be selected
342
+ # by the package barcode)
343
+ self._fill_stock_for_moves(move1, in_package=True)
344
+ self._fill_stock_for_moves(move2, in_package=True)
345
+ picking.action_assign()
346
+ result_pkgs = picking.move_line_ids.result_package_id
347
+ response = self.service.dispatch(
348
+ "scan_line",
349
+ params={
350
+ "picking_id": picking.id,
351
+ "barcode": self.delivery_packaging.barcode,
352
+ },
353
+ )
354
+ # back to same state
355
+ self.assertEqual(response["next_state"], "select_line")
356
+ self.assertEqual(
357
+ response["message"],
358
+ self.msg_store.confirm_put_all_goods_in_delivery_package(
359
+ self.delivery_packaging
360
+ ),
361
+ )
362
+ self.assertTrue(response["data"]["select_line"]["need_confirm_pack_all"])
363
+ response = self.service.dispatch(
364
+ "scan_line",
365
+ params={
366
+ "picking_id": picking.id,
367
+ "barcode": self.delivery_packaging.barcode,
368
+ "confirm_pack_all": True,
369
+ },
370
+ )
371
+ # move to summary as all lines are done
372
+ self.assertEqual(response["next_state"], "summary")
373
+ self.assertTrue(response["message"]["body"].startswith("Goods packed into "))
374
+ self.assertNotEqual(
375
+ result_pkgs.sorted("id"),
376
+ picking.move_line_ids.result_package_id.sorted("id"),
377
+ )
@@ -0,0 +1,25 @@
1
+ # Copyright 2021 Camptocamp SA (https://www.camptocamp.com)
2
+ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
3
+ from .test_checkout_base import CheckoutCommonCase
4
+ from .test_checkout_select_package_base import CheckoutSelectPackageMixin
5
+
6
+
7
+ class CheckoutScanLineCaseBase(CheckoutCommonCase, CheckoutSelectPackageMixin):
8
+ def _test_scan_line_ok(
9
+ self, barcode, selected_lines, related_lines=None, packing_info=""
10
+ ):
11
+ """Test /scan_line with a valid return
12
+
13
+ :param barcode: the barcode we scan
14
+ :selected_lines: expected move lines returned by the endpoint
15
+ """
16
+ picking = selected_lines.mapped("picking_id")
17
+ response = self.service.dispatch(
18
+ "scan_line", params={"picking_id": picking.id, "barcode": barcode}
19
+ )
20
+ self._assert_selected(
21
+ response,
22
+ selected_lines,
23
+ related_lines=related_lines,
24
+ packing_info=packing_info,
25
+ )
@@ -0,0 +1,91 @@
1
+ # Copyright 2022 Camptocamp SA (http://www.camptocamp.com)
2
+ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
3
+ from .test_checkout_scan_line_base import CheckoutScanLineCaseBase
4
+
5
+ # pylint: disable=missing-return
6
+
7
+
8
+ class CheckoutScanLineNoPrefillQtyCase(CheckoutScanLineCaseBase):
9
+ @classmethod
10
+ def setUpClass(cls):
11
+ super().setUpClass()
12
+ cls.menu.sudo().no_prefill_qty = True
13
+ cls.picking = cls._create_picking(
14
+ lines=[(cls.product_a, 3), (cls.product_a, 1), (cls.product_b, 10)],
15
+ confirm=False,
16
+ )
17
+ cls.picking.move_ids._action_confirm(merge=False)
18
+ cls.picking.action_confirm()
19
+ for move in cls.picking.move_ids:
20
+ cls._fill_stock_for_moves(move, in_lot=True)
21
+ cls.picking.action_assign()
22
+ cls.move_lines = cls.picking.move_line_ids
23
+
24
+ def _assert_quantity_done(self, barcode, selected_lines, qties):
25
+ picking = selected_lines.mapped("picking_id")
26
+ response = self.service.dispatch(
27
+ "scan_line", params={"picking_id": picking.id, "barcode": barcode}
28
+ )
29
+ response_lines = response["data"]["select_package"]["selected_move_lines"]
30
+ for response_line, qty in zip(response_lines, qties):
31
+ self.assertEqual(response_line["qty_done"], qty)
32
+
33
+ def test_scan_line_product_exist_in_two_lines(self):
34
+ """Check scanning a product only increment the quantity done on one line."""
35
+ # All lines are selected because not in a package
36
+ selected_lines = self.picking.move_line_ids
37
+ response = self.service.dispatch(
38
+ "scan_line",
39
+ params={
40
+ "picking_id": self.picking.id,
41
+ "barcode": self.product_a.barcode,
42
+ },
43
+ )
44
+ self._assert_selected_qties(
45
+ response,
46
+ selected_lines,
47
+ {selected_lines[0]: 1, selected_lines[1]: 0, selected_lines[2]: 0},
48
+ )
49
+
50
+ def test_scan_line_product_no_prefill_ok(self):
51
+ picking = self._create_picking(
52
+ lines=[(self.product_a, 10), (self.product_b, 10)]
53
+ )
54
+ # do not put them in a package, we'll pack units here
55
+ self._fill_stock_for_moves(picking.move_ids)
56
+ picking.action_assign()
57
+ line_a = picking.move_line_ids.filtered(
58
+ lambda l: l.product_id == self.product_a
59
+ )
60
+ # When no_prefill_qty is enabled in the checkout menu, prefilled qty
61
+ # should be 1.0 if a product is scanned
62
+ qties = [1.0] * len(line_a)
63
+ self._assert_quantity_done(self.product_a.barcode, line_a, qties)
64
+
65
+ def test_scan_line_product_packaging_no_prefill_ok(self):
66
+ picking = self._create_picking(
67
+ lines=[(self.product_a, 10), (self.product_a, 10), (self.product_b, 10)]
68
+ )
69
+ self._fill_stock_for_moves(picking.move_ids)
70
+ picking.action_assign()
71
+ lines_a = picking.move_line_ids.filtered(
72
+ lambda l: l.product_id == self.product_a
73
+ )
74
+ # When no_prefill_qty is enabled in the checkout menu, prefilled qty
75
+ # should be the packaging qty, if a packaging is scanned
76
+ qties = [3.0] * len(lines_a)
77
+ self._assert_quantity_done(self.product_a_packaging.barcode, lines_a, qties)
78
+
79
+ def test_scan_line_product_lot_no_prefill_ok(self):
80
+ picking = self._create_picking(
81
+ lines=[(self.product_a, 1), (self.product_a, 1), (self.product_b, 1)]
82
+ )
83
+ for move in picking.move_ids:
84
+ self._fill_stock_for_moves(move, in_lot=True)
85
+ picking.action_assign()
86
+ first_line = picking.move_line_ids[0]
87
+ lot = first_line.lot_id
88
+ # When no_prefill_qty is enabled in the checkout menu, prefilled qty
89
+ # should be the packaging qty, if a packaging is scanned
90
+ qties = [1.0] * len(first_line)
91
+ self._assert_quantity_done(lot.name, first_line, qties)