odoo-addon-shopfloor 16.0.1.0.0.24__py3-none-any.whl → 16.0.2.1.0__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 (76) hide show
  1. odoo/addons/shopfloor/README.rst +1 -1
  2. odoo/addons/shopfloor/__manifest__.py +1 -1
  3. odoo/addons/shopfloor/actions/data.py +69 -34
  4. odoo/addons/shopfloor/actions/data_detail.py +20 -0
  5. odoo/addons/shopfloor/actions/message.py +94 -2
  6. odoo/addons/shopfloor/actions/move_line_search.py +2 -2
  7. odoo/addons/shopfloor/actions/packaging.py +10 -0
  8. odoo/addons/shopfloor/actions/schema.py +11 -0
  9. odoo/addons/shopfloor/actions/schema_detail.py +14 -8
  10. odoo/addons/shopfloor/actions/search.py +9 -6
  11. odoo/addons/shopfloor/components/scan_handler_product.py +2 -0
  12. odoo/addons/shopfloor/data/shopfloor_scenario_data.xml +4 -2
  13. odoo/addons/shopfloor/docs/checkout_diag_seq.plantuml +19 -5
  14. odoo/addons/shopfloor/docs/checkout_diag_seq.png +0 -0
  15. odoo/addons/shopfloor/docs/single_pack_transfer_diag_seq.plantuml +4 -4
  16. odoo/addons/shopfloor/docs/single_pack_transfer_diag_seq.png +0 -0
  17. odoo/addons/shopfloor/i18n/ca.po +26 -8
  18. odoo/addons/shopfloor/i18n/de.po +26 -8
  19. odoo/addons/shopfloor/i18n/es_AR.po +36 -10
  20. odoo/addons/shopfloor/i18n/it.po +2015 -0
  21. odoo/addons/shopfloor/i18n/pt_BR.po +26 -8
  22. odoo/addons/shopfloor/i18n/shopfloor.pot +133 -8
  23. odoo/addons/shopfloor/migrations/16.0.2.0.0/post-migration.py +43 -0
  24. odoo/addons/shopfloor/models/shopfloor_menu.py +29 -5
  25. odoo/addons/shopfloor/models/stock_move_line.py +3 -0
  26. odoo/addons/shopfloor/models/stock_picking.py +11 -0
  27. odoo/addons/shopfloor/services/checkout.py +216 -61
  28. odoo/addons/shopfloor/services/cluster_picking.py +33 -18
  29. odoo/addons/shopfloor/services/delivery.py +25 -7
  30. odoo/addons/shopfloor/services/location_content_transfer.py +42 -28
  31. odoo/addons/shopfloor/services/single_pack_transfer.py +36 -13
  32. odoo/addons/shopfloor/services/zone_picking.py +187 -67
  33. odoo/addons/shopfloor/static/description/index.html +1 -1
  34. odoo/addons/shopfloor/tests/__init__.py +2 -0
  35. odoo/addons/shopfloor/tests/common.py +3 -1
  36. odoo/addons/shopfloor/tests/test_actions_data.py +46 -7
  37. odoo/addons/shopfloor/tests/test_actions_data_base.py +15 -0
  38. odoo/addons/shopfloor/tests/test_actions_data_detail.py +30 -8
  39. odoo/addons/shopfloor/tests/test_actions_packaging.py +43 -0
  40. odoo/addons/shopfloor/tests/test_checkout_base.py +15 -5
  41. odoo/addons/shopfloor/tests/test_checkout_done.py +40 -5
  42. odoo/addons/shopfloor/tests/test_checkout_list_delivery_packaging.py +1 -0
  43. odoo/addons/shopfloor/tests/test_checkout_list_package.py +3 -1
  44. odoo/addons/shopfloor/tests/test_checkout_scan.py +19 -0
  45. odoo/addons/shopfloor/tests/test_checkout_scan_dest_location.py +99 -0
  46. odoo/addons/shopfloor/tests/test_checkout_scan_line.py +3 -2
  47. odoo/addons/shopfloor/tests/test_checkout_scan_line_no_prefill_qty.py +48 -0
  48. odoo/addons/shopfloor/tests/test_checkout_scan_package_action.py +26 -0
  49. odoo/addons/shopfloor/tests/test_checkout_scan_package_action_no_prefill_qty.py +16 -0
  50. odoo/addons/shopfloor/tests/test_checkout_select_package_base.py +4 -1
  51. odoo/addons/shopfloor/tests/test_checkout_summary.py +1 -1
  52. odoo/addons/shopfloor/tests/test_cluster_picking_unload.py +37 -8
  53. odoo/addons/shopfloor/tests/test_delivery_list_stock_picking.py +5 -0
  54. odoo/addons/shopfloor/tests/test_location_content_transfer_base.py +4 -4
  55. odoo/addons/shopfloor/tests/test_location_content_transfer_set_destination_all.py +24 -2
  56. odoo/addons/shopfloor/tests/test_location_content_transfer_set_destination_package_or_line.py +6 -4
  57. odoo/addons/shopfloor/tests/test_location_content_transfer_single.py +45 -0
  58. odoo/addons/shopfloor/tests/test_scan_anything.py +7 -0
  59. odoo/addons/shopfloor/tests/test_single_pack_transfer.py +59 -8
  60. odoo/addons/shopfloor/tests/test_zone_picking_base.py +36 -10
  61. odoo/addons/shopfloor/tests/test_zone_picking_change_pack_lot.py +2 -0
  62. odoo/addons/shopfloor/tests/test_zone_picking_complete_mix_pack_flux.py +59 -0
  63. odoo/addons/shopfloor/tests/test_zone_picking_select_line.py +33 -2
  64. odoo/addons/shopfloor/tests/test_zone_picking_select_line_first_scan_location.py +8 -3
  65. odoo/addons/shopfloor/tests/test_zone_picking_select_line_no_prefill_qty.py +19 -2
  66. odoo/addons/shopfloor/tests/test_zone_picking_set_line_destination.py +88 -19
  67. odoo/addons/shopfloor/tests/test_zone_picking_set_line_destination_package_not_allowed.py +94 -0
  68. odoo/addons/shopfloor/tests/test_zone_picking_set_line_destination_pick_pack.py +1 -5
  69. odoo/addons/shopfloor/tests/test_zone_picking_start.py +4 -4
  70. odoo/addons/shopfloor/tests/test_zone_picking_unload_all.py +1 -1
  71. odoo/addons/shopfloor/tests/test_zone_picking_unload_set_destination.py +4 -4
  72. odoo/addons/shopfloor/views/shopfloor_menu.xml +30 -0
  73. {odoo_addon_shopfloor-16.0.1.0.0.24.dist-info → odoo_addon_shopfloor-16.0.2.1.0.dist-info}/METADATA +2 -2
  74. {odoo_addon_shopfloor-16.0.1.0.0.24.dist-info → odoo_addon_shopfloor-16.0.2.1.0.dist-info}/RECORD +76 -70
  75. {odoo_addon_shopfloor-16.0.1.0.0.24.dist-info → odoo_addon_shopfloor-16.0.2.1.0.dist-info}/WHEEL +0 -0
  76. {odoo_addon_shopfloor-16.0.1.0.0.24.dist-info → odoo_addon_shopfloor-16.0.2.1.0.dist-info}/top_level.txt +0 -0
@@ -7,7 +7,7 @@ Shopfloor
7
7
  !! This file is generated by oca-gen-addon-readme !!
8
8
  !! changes will be overwritten. !!
9
9
  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
10
- !! source digest: sha256:993547f893f6715d97989e36897fae01ba59faacf8a2a9f66f202d3130bba967
10
+ !! source digest: sha256:835c681f1a1e049cfb7792d6dd69c0214bfbaad4775d134f6e5a47900e8aeb4a
11
11
  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
12
12
 
13
13
  .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
@@ -6,7 +6,7 @@
6
6
  {
7
7
  "name": "Shopfloor",
8
8
  "summary": "manage warehouse operations with barcode scanners",
9
- "version": "16.0.1.0.0",
9
+ "version": "16.0.2.1.0",
10
10
  "development_status": "Beta",
11
11
  "category": "Inventory",
12
12
  "website": "https://github.com/OCA/wms",
@@ -13,34 +13,23 @@ class DataAction(Component):
13
13
  def location(self, record, **kw):
14
14
  parser = self._location_parser
15
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),
16
+ if kw.get("with_operation_progress"):
17
+ lines_blacklist = (
18
+ kw.get("progress_lines_blacklist")
19
+ or self.env["stock.move.line"].browse()
20
+ )
21
+ domain = [
22
+ ("location_id", "=", record.id),
28
23
  ("state", "in", ["partially_available", "assigned"]),
29
24
  ("picking_id.state", "=", "assigned"),
25
+ ("id", "not in", lines_blacklist.ids),
30
26
  ]
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
- }
27
+ operation_progress = self._get_operation_progress(domain)
28
+ data.update({"operation_progress": operation_progress})
29
+ return data
30
+
31
+ def locations(self, records, **kw):
32
+ return [self.location(rec, **kw) for rec in records]
44
33
 
45
34
  @property
46
35
  def _location_parser(self):
@@ -51,16 +40,19 @@ class DataAction(Component):
51
40
  ("barcode", lambda rec, fname: rec[fname] if rec[fname] else rec.name),
52
41
  ]
53
42
 
54
- @ensure_model("stock.picking")
55
- def picking(self, record, **kw):
43
+ def _get_picking_parser(self, record, **kw):
56
44
  parser = self._picking_parser
57
45
  # progress is a heavy computed field,
58
46
  # and it may reduce performance significatively
59
47
  # when dealing with a large number of pickings.
60
48
  # Thus, we make it optional.
61
- if "with_progress" in kw:
49
+ if kw.get("with_progress"):
62
50
  parser.append("progress")
63
- return self._jsonify(record, parser, **kw)
51
+ return parser
52
+
53
+ @ensure_model("stock.picking")
54
+ def picking(self, record, **kw):
55
+ return self._jsonify(record, self._get_picking_parser(record, **kw), **kw)
64
56
 
65
57
  def pickings(self, record, **kw):
66
58
  return self.picking(record, multi=True)
@@ -80,6 +72,7 @@ class DataAction(Component):
80
72
  "bulk_line_count",
81
73
  "total_weight:weight",
82
74
  "scheduled_date",
75
+ "priority",
83
76
  ]
84
77
 
85
78
  @ensure_model("stock.quant.package")
@@ -93,13 +86,37 @@ class DataAction(Component):
93
86
  if with_packaging:
94
87
  parser += self._package_packaging_parser
95
88
  data = self._jsonify(record, parser, **kw)
89
+ qty = len(record.quant_ids)
96
90
  # 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"]
91
+ progress_package_key = ""
92
+ if kw.get("with_operation_progress_src"):
93
+ progress_package_key = "package_id"
94
+ elif kw.get("with_operation_progress_dest"):
95
+ progress_package_key = "result_package_id"
96
+ if progress_package_key:
97
+ lines_blacklist = (
98
+ kw.get("progress_lines_blacklist")
99
+ or self.env["stock.move.line"].browse()
101
100
  )
102
- data.update({"move_line_count": len(lines)})
101
+ domain = [
102
+ (progress_package_key, "=", record.id),
103
+ ("state", "in", ["partially_available", "assigned"]),
104
+ ("picking_id.state", "=", "assigned"),
105
+ ("id", "not in", lines_blacklist.ids),
106
+ ]
107
+ operation_progress = self._get_operation_progress(domain)
108
+ data.update({"operation_progress": operation_progress})
109
+ if kw.get("with_package_move_line_count") and data and picking:
110
+ move_line_count = self.env["stock.move.line"].search_count(
111
+ [
112
+ ("picking_id.picking_type_id", "=", picking.picking_type_id.id),
113
+ ("result_package_id", "=", record.id),
114
+ ("state", "in", ["partially_available", "assigned"]),
115
+ ]
116
+ )
117
+ qty += move_line_count
118
+ # TODO does this name really makes sense?
119
+ data.update({"move_line_count": qty})
103
120
  return data
104
121
 
105
122
  def packages(self, records, picking=None, **kw):
@@ -112,6 +129,10 @@ class DataAction(Component):
112
129
  "name",
113
130
  "shopfloor_weight:weight",
114
131
  ("package_type_id:storage_type", ["id", "name"]),
132
+ (
133
+ "quant_ids:total_quantity",
134
+ lambda rec, fname: sum(rec.quant_ids.mapped("quantity")),
135
+ ),
115
136
  ]
116
137
 
117
138
  @property
@@ -327,3 +348,17 @@ class DataAction(Component):
327
348
  "id",
328
349
  "name",
329
350
  ]
351
+
352
+ def _get_operation_progress(self, domain):
353
+ lines = self.env["stock.move.line"].search(domain)
354
+ # operations_to_do = number of total operations that are pending for this location.
355
+ # operations_done = number of operations already done.
356
+ operations_to_do = 0
357
+ operations_done = 0
358
+ for line in lines:
359
+ operations_done += line.qty_done
360
+ operations_to_do += line.reserved_uom_qty - line.qty_done
361
+ return {
362
+ "done": operations_done,
363
+ "to_do": operations_to_do,
364
+ }
@@ -152,3 +152,23 @@ class DataDetailAction(Component):
152
152
  "product_name",
153
153
  "product_code",
154
154
  ]
155
+
156
+ @ensure_model("product.packaging")
157
+ def packaging_detail(self, record, **kw):
158
+ return self._jsonify(
159
+ record.with_context(packaging=record.id),
160
+ self._packaging_detail_parser,
161
+ **kw
162
+ )
163
+
164
+ @property
165
+ def _packaging_detail_parser(self):
166
+ return self._packaging_parser + [
167
+ "packaging_length:length",
168
+ "width",
169
+ "height",
170
+ ("max_weight", lambda rec, fname: rec.package_type_id.max_weight),
171
+ "length_uom_name:length_uom",
172
+ "weight_uom_name:weight_uom",
173
+ "barcode:barcode",
174
+ ]
@@ -113,8 +113,10 @@ class MessageAction(Component):
113
113
  return {
114
114
  "message_type": "warning",
115
115
  "body": _(
116
- "Package %(package_name)s is not available in transfer %(picking_name)s."
117
- ).format(package.name, picking.name),
116
+ "Package %(package_name)s is not available in transfer %(picking_name)s.",
117
+ package_name=package.name,
118
+ picking_name=picking.name,
119
+ ),
118
120
  }
119
121
 
120
122
  def package_not_empty(self, package):
@@ -129,6 +131,17 @@ class MessageAction(Component):
129
131
  "body": _("Package {} is already used.").format(package.name),
130
132
  }
131
133
 
134
+ def package_different_picking_type(self, package, picking_type):
135
+ return {
136
+ "message_type": "warning",
137
+ "body": _(
138
+ "Package %(package_name)s contains already lines from a different "
139
+ "operation type %(picking_type_name)s.",
140
+ package_name=package.name,
141
+ picking_type_name=picking_type.name,
142
+ ),
143
+ }
144
+
132
145
  def dest_package_required(self):
133
146
  return {
134
147
  "message_type": "warning",
@@ -435,6 +448,18 @@ class MessageAction(Component):
435
448
  "body": _("No transfer found for this product."),
436
449
  }
437
450
 
451
+ def product_not_found_in_location_or_transfer(self, product, location, picking):
452
+ return {
453
+ "message_type": "error",
454
+ "body": _(
455
+ "Product %(product_name)s not found in location %(location_name)s "
456
+ "or transfer %(picking_name)s.",
457
+ product_name=product.name,
458
+ location_name=location.name,
459
+ picking_name=picking.name,
460
+ ),
461
+ }
462
+
438
463
  def x_not_found_or_already_in_dest_package(self, message_code):
439
464
  return {
440
465
  "message_type": "warning",
@@ -449,6 +474,12 @@ class MessageAction(Component):
449
474
  "body": _("Packaging not found in the current transfer."),
450
475
  }
451
476
 
477
+ def packaging_dimension_updated(self, packaging):
478
+ return {
479
+ "message_type": "success",
480
+ "body": _("Packaging {} dimension updated.").format(packaging.name),
481
+ }
482
+
452
483
  def expiration_date_missing(self):
453
484
  return {
454
485
  "message_type": "error",
@@ -512,6 +543,26 @@ class MessageAction(Component):
512
543
  "body": _("No transfer found for this lot."),
513
544
  }
514
545
 
546
+ def lot_not_found_in_location(self, lot, location):
547
+ return {
548
+ "message_type": "error",
549
+ "body": _(
550
+ "Lot %(lot_name)s not found in location %(location_name)s",
551
+ lot_name=lot.name,
552
+ location_name=location.name,
553
+ ),
554
+ }
555
+
556
+ def lot_not_found_in_picking(self, lot, picking):
557
+ return {
558
+ "message_type": "error",
559
+ "body": _(
560
+ "Lot %(lot_name)s not found in transfer %(picking_name)s",
561
+ lot_name=lot.name,
562
+ pcking_name=picking.name,
563
+ ),
564
+ }
565
+
515
566
  def batch_transfer_complete(self):
516
567
  return {
517
568
  "message_type": "success",
@@ -662,6 +713,12 @@ class MessageAction(Component):
662
713
  "body": _("No lines to process."),
663
714
  }
664
715
 
716
+ def no_lines_to_process_set_quantities(self):
717
+ return {
718
+ "message_type": "info",
719
+ "body": _("No lines to process, set quantities on some"),
720
+ }
721
+
665
722
  def location_empty(self, location):
666
723
  return {
667
724
  "message_type": "error",
@@ -686,6 +743,12 @@ class MessageAction(Component):
686
743
  "body": _("You must not pick more than {} units.").format(quantity),
687
744
  }
688
745
 
746
+ def unable_to_pick_qty(self):
747
+ return {
748
+ "message_type": "error",
749
+ "body": _("You cannot process that much units."),
750
+ }
751
+
689
752
  def lot_replaced_by_lot(self, old_lot, new_lot):
690
753
  return {
691
754
  "message_type": "success",
@@ -729,6 +792,26 @@ class MessageAction(Component):
729
792
  ),
730
793
  }
731
794
 
795
+ def package_not_found_in_location(self, package, location):
796
+ return {
797
+ "message_type": "error",
798
+ "body": _(
799
+ "Package %(package_name)s not found in location %(location_name)s",
800
+ package_name=package.name,
801
+ location_name=location.name,
802
+ ),
803
+ }
804
+
805
+ def package_not_found_in_picking(self, package, picking):
806
+ return {
807
+ "message_type": "error",
808
+ "body": _(
809
+ "Package %(package_name)s not found in transfer %(picking_name)s",
810
+ package_name=package.name,
811
+ picking_name=picking.name,
812
+ ),
813
+ }
814
+
732
815
  def cannot_change_lot_already_picked(self, lot):
733
816
  return {
734
817
  "message_type": "error",
@@ -844,3 +927,12 @@ class MessageAction(Component):
844
927
  "message_type": "warning",
845
928
  "body": _("No line to pack found."),
846
929
  }
930
+
931
+ def package_transfer_not_allowed_scan_location(self):
932
+ return {
933
+ "message_type": "warning",
934
+ "body": _(
935
+ "Transferring to a different package is not allowed, "
936
+ "please scan a location instead."
937
+ ),
938
+ }
@@ -101,8 +101,8 @@ class MoveLineSearch(Component):
101
101
  )
102
102
  elif order == "location":
103
103
  return lambda line: (
104
- line.move_id.location_id.shopfloor_picking_sequence or "",
105
- line.move_id.location_id.name,
104
+ line.location_id.shopfloor_picking_sequence or "",
105
+ line.location_id.name,
106
106
  line.move_id.date,
107
107
  line.move_id.id,
108
108
  )
@@ -57,3 +57,13 @@ class PackagingAction(Component):
57
57
 
58
58
  def package_has_several_lots(self, package):
59
59
  return len(package.quant_ids.lot_id) > 1
60
+
61
+ def is_complete_mix_pack(self, package):
62
+ """Check if a package is mixed and completely reserved.
63
+
64
+ Will return true if the package has multiple distinct products and
65
+ all the package quantities are reserved.
66
+ """
67
+ return self.package_has_several_products(package) and all(
68
+ quant.quantity == quant.reserved_quantity for quant in package.quant_ids
69
+ )
@@ -26,6 +26,8 @@ class ShopfloorSchemaAction(Component):
26
26
  "ship_carrier": self._schema_dict_of(self._simple_record(), required=False),
27
27
  "scheduled_date": {"type": "string", "nullable": False, "required": True},
28
28
  "progress": {"type": "float", "nullable": True},
29
+ "location_dest": self._schema_dict_of(self.location(), required=False),
30
+ "priority": {"type": "string", "nullable": True, "required": False},
29
31
  }
30
32
 
31
33
  def move_line(self, with_packaging=False, with_picking=False):
@@ -91,6 +93,15 @@ class ShopfloorSchemaAction(Component):
91
93
  "weight": {"required": True, "nullable": True, "type": "float"},
92
94
  "move_line_count": {"required": False, "nullable": True, "type": "integer"},
93
95
  "storage_type": self._schema_dict_of(self._simple_record()),
96
+ "operation_progress": {
97
+ "type": "dict",
98
+ "required": False,
99
+ "schema": {
100
+ "done": {"type": "float", "required": False},
101
+ "to_do": {"type": "float", "required": False},
102
+ },
103
+ },
104
+ "total_quantity": {"required": False, "type": "float"},
94
105
  }
95
106
  if with_packaging:
96
107
  schema["packaging"] = self._schema_dict_of(self.packaging())
@@ -88,11 +88,17 @@ class ShopfloorSchemaDetailAction(Component):
88
88
  "product_code": {"type": "string", "nullable": True, "required": False},
89
89
  }
90
90
 
91
- # TODO
92
- # def packaging_detail(self):
93
- # schema = self.packaging()
94
- # schema.update(
95
- # {
96
- # }
97
- # )
98
- # return schema
91
+ def packaging_detail(self):
92
+ schema = self.packaging()
93
+ schema.update(
94
+ {
95
+ "length": {"type": "float", "nullable": True, "required": False},
96
+ "width": {"type": "float", "nullable": True, "required": False},
97
+ "height": {"type": "float", "nullable": True, "required": False},
98
+ "max_weight": {"type": "float", "nullable": True, "required": False},
99
+ "length_uom": {"type": "string", "nullable": True, "required": False},
100
+ "weight_uom": {"type": "string", "nullable": True, "required": False},
101
+ "barcode": {"type": "string", "nullable": True, "required": False},
102
+ }
103
+ )
104
+ return schema
@@ -1,4 +1,5 @@
1
1
  # Copyright 2020 Camptocamp SA (http://www.camptocamp.com)
2
+ # Copyright 2024 Michael Tietz (MT Software) <mtietz@mt-software.de>
2
3
  # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
3
4
 
4
5
  from odoo.osv.expression import AND
@@ -74,18 +75,20 @@ class SearchAction(Component):
74
75
  barcode = barcode or ""
75
76
  return self.generic_find(barcode, types=types, handler_kw=handler_kw)
76
77
 
77
- def generic_find(self, barcode, types=None, handler_kw=None):
78
+ def _find_record_by_type(self, barcode, btype, handler_kw=None):
78
79
  handler_kw = handler_kw or {}
80
+ handler = self._barcode_type_handler.get(btype)
81
+ if not handler:
82
+ return
83
+ return handler(barcode, **handler_kw.get(btype, {}))
84
+
85
+ def generic_find(self, barcode, types=None, handler_kw=None):
79
86
  _types = types or self._barcode_type_handler.keys()
80
87
  # TODO: decide the best default order in case we don't pass `types`
81
88
  for btype in _types:
82
- handler = self._barcode_type_handler.get(btype)
83
- if not handler:
84
- continue
85
- record = handler(barcode, **handler_kw.get(btype, {}))
89
+ record = self._find_record_by_type(barcode, btype, handler_kw)
86
90
  if record:
87
91
  return self._make_search_result(record=record, code=barcode, type=btype)
88
-
89
92
  return self._make_search_result(type="none")
90
93
 
91
94
  def location_from_scan(self, barcode, limit=1):
@@ -16,6 +16,8 @@ class ProductHandler(Component):
16
16
 
17
17
  def search(self, identifier):
18
18
  res = self._search.find(identifier, types=("product", "packaging"))
19
+ if res.record and res.type == "packaging":
20
+ return res.record.product_id
19
21
  return res.record if res.record else self.env["product.product"]
20
22
 
21
23
  @property
@@ -20,7 +20,8 @@
20
20
  "unload_package_at_destination": true,
21
21
  "multiple_move_single_pack": true,
22
22
  "no_prefill_qty": true,
23
- "scan_location_or_pack_first": true
23
+ "scan_location_or_pack_first": true,
24
+ "allow_alternative_destination_package": true
24
25
  }
25
26
  </field>
26
27
  </record>
@@ -43,7 +44,8 @@
43
44
  {
44
45
  "no_prefill_qty": true,
45
46
  "show_oneline_package_content": true,
46
- "auto_post_line": true
47
+ "auto_post_line": true,
48
+ "scan_location_or_pack_first": true
47
49
  }
48
50
  </field>
49
51
  </record>
@@ -1,7 +1,7 @@
1
1
  # Diagram to generate with PlantUML (https://plantuml.com/)
2
2
  #
3
3
  # $ sudo apt install plantuml
4
- # $ plantuml delivery_diag_seq.plantuml
4
+ # $ plantuml checkout_diag_seq.plantuml
5
5
  #
6
6
 
7
7
  @startuml
@@ -32,18 +32,29 @@ manual_selection -> select_line: **/select**(picking_id)
32
32
  select_document -> select_line: **/scan_document**(barcode, picking_id=None) \n(scan a picking a product or ???)
33
33
 
34
34
  == /select_line ==
35
- select_line -> select_package: **/select_line**(picking_id, package_id, move_line_ids)
35
+ select_line -> select_package: **/select_line**(picking_id, package_id, move_line_id)
36
+
37
+ == /scan_line ==
38
+ select_line -> select_package: **/scan_line**(picking_id, barcode, confirm_pack_all, confirm_lot)
36
39
 
37
40
  == /list_delivery_packaging ==
38
41
  select_package -> select_delivery_packaging: **/list_delivery_packaging**(picking_id, selected_line_ids)
39
42
 
40
-
41
43
  == /list_dest_package ==
42
44
  select_package -> select_dest_package: **/list_dest_package**(picking_id, selected_line_ids)
43
45
  select_package -> select_package: **/list_dest_package**(picking_id, selected_line_ids) \n No Valid package to select
44
46
 
47
+ == edit line ==
48
+ select_package -> change_quantity: (State change without call to backend)
49
+
45
50
  == /set_dest_package ==
46
- select_dest_package -> select_line: **/set_dest_package(picking_id, selected_line_ids, package_id)
51
+ select_dest_package -> select_line: **/set_dest_package**(picking_id, selected_line_ids, package_id)
52
+
53
+ == /set_custom_qty ==
54
+ change_quantity -> select_package: **/set_custom_qty**(picking_id, selected_line_ids, move_line_id, qty_done)
55
+
56
+ == /reset_line_qty ==
57
+ select_package -> select_package: **/reset_line_qty**(picking_id, selected_line_ids, move_line_id)
47
58
 
48
59
  == /scan_package_action ==
49
60
  select_delivery_packaging -> summary: **/scan_package_action**(picking_id, selected_line_ids, barcode)
@@ -52,10 +63,13 @@ select_delivery_packaging -> summary: **/scan_package_action**(picking_id, selec
52
63
  summary -> select_line: **/cancel_line**(picking_id, package_id=None, line_id=None)
53
64
 
54
65
  == /select ==
55
- summary -> select_line: **/select**(picking_id) \nContinue checkout
66
+ summary -> select_line: **/select**(picking_id) \nContinue checkout
56
67
 
57
68
  == /done ==
58
69
  summary -> select_document: **/done(picking_id)**
70
+ summary -> select_child_location: **/done(picking_id)**
59
71
 
72
+ == /scan_dest_location ==
73
+ select_child_location -> select_document: **/scan_dest_location(picking_id, barcode)**
60
74
 
61
75
  @enduml
@@ -23,14 +23,14 @@ header <img:oca_logo.png>
23
23
  title <size:35>Single Pack Transfer scenario</size>
24
24
 
25
25
  == /start ==
26
- start -> scan_location: **/start**(barcode[pack|location], confirmation=False)
27
- start -> start: **/start**(barcode[pack|location], confirmation=False)
26
+ start -> scan_location: **/start**(barcode[pack|location], confirmation=None)
27
+ start -> start: **/start**(barcode[pack|location], confirmation=None)
28
28
 
29
29
  == /cancel ==
30
30
  scan_location -> start: **/cancel**(package_level_id)
31
31
 
32
32
  == /validate ==
33
- scan_location -> scan_location: **/validate**(package_level_id, location_barcode, confirmation=False)
34
- scan_location -> start: **/validate**(package_level_id, location_barcode, confirmation=False)
33
+ scan_location -> scan_location: **/validate**(package_level_id, location_barcode, confirmation=None)
34
+ scan_location -> start: **/validate**(package_level_id, location_barcode, confirmation=None)
35
35
 
36
36
  @enduml
@@ -897,6 +897,12 @@ msgstr ""
897
897
  msgid "Packaging not found in the current transfer."
898
898
  msgstr ""
899
899
 
900
+ #. module: shopfloor
901
+ #: code:addons/shopfloor/actions/message.py:0
902
+ #, python-format
903
+ msgid "Packaging {} dimension updated."
904
+ msgstr ""
905
+
900
906
  #. module: shopfloor
901
907
  #: code:addons/shopfloor/actions/message.py:0
902
908
  #, python-format
@@ -1063,6 +1069,11 @@ msgstr ""
1063
1069
  msgid "Restart the operation, someone has canceled it."
1064
1070
  msgstr ""
1065
1071
 
1072
+ #. module: shopfloor
1073
+ #: model:ir.model.fields,field_description:shopfloor.field_shopfloor_menu__scan_location_or_pack_first
1074
+ msgid "Restrict scannable barcode at work selection"
1075
+ msgstr ""
1076
+
1066
1077
  #. module: shopfloor
1067
1078
  #: model_terms:ir.ui.view,arch_db:shopfloor.view_stock_move_line_detailed_operation_tree
1068
1079
  msgid "SF Priority"
@@ -1094,11 +1105,6 @@ msgstr ""
1094
1105
  msgid "Scan Location Or Pack First Is Possible"
1095
1106
  msgstr ""
1096
1107
 
1097
- #. module: shopfloor
1098
- #: model:ir.model.fields,field_description:shopfloor.field_shopfloor_menu__scan_location_or_pack_first
1099
- msgid "Scan first location or pack"
1100
- msgstr ""
1101
-
1102
1108
  #. module: shopfloor
1103
1109
  #: code:addons/shopfloor/actions/message.py:0
1104
1110
  #, python-format
@@ -1638,6 +1644,18 @@ msgid ""
1638
1644
  "with the remaining lines."
1639
1645
  msgstr ""
1640
1646
 
1647
+ #. module: shopfloor
1648
+ #: model:ir.model.fields,help:shopfloor.field_shopfloor_menu__scan_location_or_pack_first
1649
+ msgid ""
1650
+ "When checked, the user will be restricted by the type of object barcode "
1651
+ "that he can scan to select the document/transfer/move line to work on."
1652
+ msgstr ""
1653
+
1654
+ #. module: shopfloor
1655
+ #: model_terms:ir.ui.view,arch_db:shopfloor.shopfloor_menu_form_view
1656
+ msgid "When choosing an order to pack, do not allow to scan a product."
1657
+ msgstr ""
1658
+
1641
1659
  #. module: shopfloor
1642
1660
  #: model:ir.model.fields,help:shopfloor.field_shopfloor_menu__allow_get_work
1643
1661
  msgid ""
@@ -1657,10 +1675,10 @@ msgid "When picking, prevent unloading the whole bin when full."
1657
1675
  msgstr ""
1658
1676
 
1659
1677
  #. module: shopfloor
1660
- #: model:ir.model.fields,help:shopfloor.field_shopfloor_menu__scan_location_or_pack_first
1678
+ #: model_terms:ir.ui.view,arch_db:shopfloor.shopfloor_menu_form_view
1661
1679
  msgid ""
1662
- "When selecting work, force the user to first scan a location or pack,then "
1663
- "the product or lot."
1680
+ "When selecting a move line, force the user to first scan a package or a "
1681
+ "location and not a product or a lot."
1664
1682
  msgstr ""
1665
1683
 
1666
1684
  #. module: shopfloor