odoo-addon-stock-barcodes 15.0.3.1.6__py3-none-any.whl → 15.0.3.2.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 (23) hide show
  1. odoo/addons/stock_barcodes/README.rst +1 -1
  2. odoo/addons/stock_barcodes/__manifest__.py +1 -1
  3. odoo/addons/stock_barcodes/hooks.py +15 -13
  4. odoo/addons/stock_barcodes/i18n/es.po +69 -133
  5. odoo/addons/stock_barcodes/i18n/stock_barcodes.pot +55 -2
  6. odoo/addons/stock_barcodes/models/__init__.py +1 -0
  7. odoo/addons/stock_barcodes/models/stock_barcodes_action.py +1 -1
  8. odoo/addons/stock_barcodes/models/stock_move.py +37 -0
  9. odoo/addons/stock_barcodes/models/stock_move_line.py +6 -12
  10. odoo/addons/stock_barcodes/models/stock_picking.py +9 -36
  11. odoo/addons/stock_barcodes/models/stock_picking_type.py +5 -6
  12. odoo/addons/stock_barcodes/static/description/index.html +1 -1
  13. odoo/addons/stock_barcodes/tests/test_stock_barcodes_picking.py +1 -0
  14. odoo/addons/stock_barcodes/wizard/stock_barcodes_read.py +3 -20
  15. odoo/addons/stock_barcodes/wizard/stock_barcodes_read_picking.py +230 -24
  16. odoo/addons/stock_barcodes/wizard/stock_barcodes_read_picking_views.xml +12 -1
  17. odoo/addons/stock_barcodes/wizard/stock_barcodes_read_todo.py +42 -105
  18. odoo/addons/stock_barcodes/wizard/stock_barcodes_read_todo_view.xml +45 -3
  19. odoo/addons/stock_barcodes/wizard/stock_barcodes_read_views.xml +0 -1
  20. {odoo_addon_stock_barcodes-15.0.3.1.6.dist-info → odoo_addon_stock_barcodes-15.0.3.2.0.dist-info}/METADATA +4 -4
  21. {odoo_addon_stock_barcodes-15.0.3.1.6.dist-info → odoo_addon_stock_barcodes-15.0.3.2.0.dist-info}/RECORD +23 -22
  22. {odoo_addon_stock_barcodes-15.0.3.1.6.dist-info → odoo_addon_stock_barcodes-15.0.3.2.0.dist-info}/WHEEL +1 -1
  23. {odoo_addon_stock_barcodes-15.0.3.1.6.dist-info → odoo_addon_stock_barcodes-15.0.3.2.0.dist-info}/top_level.txt +0 -0
@@ -26,17 +26,15 @@ class StockPickingType(models.Model):
26
26
  "picking_mode": "picking",
27
27
  }
28
28
  if self.code == "outgoing":
29
- location_dest_id = (
29
+ vals["location_dest_id"] = (
30
30
  self.default_location_dest_id.id
31
31
  or self.env.ref("stock.stock_location_customers").id
32
32
  )
33
- vals["location_dest_id"] = location_dest_id
34
- if self.code == "incoming":
35
- location_src_id = (
33
+ elif self.code == "incoming":
34
+ vals["location_id"] = (
36
35
  self.default_location_src_id.id
37
36
  or self.env.ref("stock.stock_location_suppliers").id
38
37
  )
39
- vals["location_id"] = location_src_id
40
38
  if self.barcode_option_group_id.get_option_value(
41
39
  "location_id", "filled_default"
42
40
  ):
@@ -46,8 +44,8 @@ class StockPickingType(models.Model):
46
44
  ):
47
45
  vals["location_dest_id"] = self.default_location_dest_id.id
48
46
  wiz = self.env["wiz.stock.barcodes.read.picking"].create(vals)
49
- wiz.determine_todo_action()
50
47
  wiz.fill_pending_moves()
48
+ wiz.determine_todo_action()
51
49
  action = self.env["ir.actions.actions"]._for_xml_id(
52
50
  "stock_barcodes.action_stock_barcodes_read_picking"
53
51
  )
@@ -55,6 +53,7 @@ class StockPickingType(models.Model):
55
53
  return action
56
54
 
57
55
  def action_barcode_new_picking(self):
56
+ self.ensure_one()
58
57
  picking = (
59
58
  self.env["stock.picking"]
60
59
  .with_context(default_immediate_transfer=True)
@@ -367,7 +367,7 @@ ul.auto-toc {
367
367
  !! This file is generated by oca-gen-addon-readme !!
368
368
  !! changes will be overwritten. !!
369
369
  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
370
- !! source digest: sha256:6658299983dc274c7a7bbc0ed6f36e8a9b679249c86b3295a92c78fdf305c00f
370
+ !! source digest: sha256:aaf3270f55f43b616a2ceb165378f0909ba3cc1a3d0d36d21a65e4c04c06998f
371
371
  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
372
372
  <p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/stock-logistics-barcode/tree/15.0/stock_barcodes"><img alt="OCA/stock-logistics-barcode" src="https://img.shields.io/badge/github-OCA%2Fstock--logistics--barcode-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/stock-logistics-barcode-15-0/stock-logistics-barcode-15-0-stock_barcodes"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/stock-logistics-barcode&amp;target_branch=15.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
373
373
  <p>This module provides a barcode reader interface for stock module.</p>
@@ -294,6 +294,7 @@ class TestStockBarcodesPicking(TestStockBarcodes):
294
294
  self.product_tracking.categ_id.removal_strategy_id = self.env.ref(
295
295
  "stock.removal_lifo"
296
296
  )
297
+ self.wiz_scan_picking_out.action_clean_values()
297
298
  self.action_barcode_scanned(self.wiz_scan_picking_out, "8433281006850")
298
299
  self.assertEqual(self.wiz_scan_picking_out.lot_id, lot_3)
299
300
 
@@ -76,7 +76,6 @@ class WizStockBarcodesRead(models.AbstractModel):
76
76
  show_scan_log = fields.Boolean(compute="_compute_is_manual_qty")
77
77
  # Technical field to allow use in attrs
78
78
  display_menu = fields.Boolean()
79
- qty_available = fields.Float(compute="_compute_qty_available")
80
79
  auto_lot = fields.Boolean(
81
80
  string="Get lots automatically",
82
81
  help="If checked the lot will be set automatically with the same "
@@ -122,24 +121,6 @@ class WizStockBarcodesRead(models.AbstractModel):
122
121
  for rec in self:
123
122
  rec.create_lot = rec.option_group_id.create_lot
124
123
 
125
- @api.depends("location_id", "product_id", "lot_id")
126
- def _compute_qty_available(self):
127
- if not self.product_id or self.location_id.usage != "internal":
128
- self.qty_available = 0.0
129
- return
130
- domain_quant = [
131
- ("product_id", "=", self.product_id.id),
132
- ("location_id", "=", self.location_id.id),
133
- ]
134
- if self.lot_id:
135
- domain_quant.append(("lot_id", "=", self.lot_id.id))
136
- # if self.package_id:
137
- # domain_quant.append(('package_id', '=', self.package_id.id))
138
- groups = self.env["stock.quant"].read_group(
139
- domain_quant, ["quantity"], [], orderby="id"
140
- )
141
- self.qty_available = groups[0]["quantity"]
142
-
143
124
  @api.depends("product_id")
144
125
  def _compute_display_assign_serial(self):
145
126
  for rec in self:
@@ -630,7 +611,9 @@ class WizStockBarcodesRead(models.AbstractModel):
630
611
 
631
612
  def action_clean_values(self):
632
613
  options = self.option_group_id.option_ids
633
- options_to_clean = options.filtered("clean_after_done")
614
+ options_to_clean = options.filtered(
615
+ lambda op: op.clean_after_done and op.field_name in self
616
+ )
634
617
  for option in options_to_clean:
635
618
  if option.field_name == "result_package_id" and self.keep_result_package:
636
619
  continue
@@ -1,11 +1,13 @@
1
1
  # Copyright 2019 Sergio Teruel <sergio.teruel@tecnativa.com>
2
2
  # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
3
3
  import logging
4
+ from collections import OrderedDict, defaultdict
4
5
 
5
6
  from odoo import _, api, fields, models
6
7
  from odoo.exceptions import ValidationError
7
8
  from odoo.fields import first
8
- from odoo.tools.float_utils import float_compare
9
+ from odoo.tools.float_utils import float_compare, float_round
10
+ from odoo.tools.safe_eval import safe_eval
9
11
 
10
12
  _logger = logging.getLogger(__name__)
11
13
 
@@ -74,6 +76,9 @@ class WizStockBarcodesReadPicking(models.TransientModel):
74
76
  picking_location_id = fields.Many2one(related="picking_id.location_id")
75
77
  picking_location_dest_id = fields.Many2one(related="picking_id.location_dest_id")
76
78
  company_id = fields.Many2one(related="picking_id.company_id")
79
+ todo_line_is_extra_line = fields.Boolean(related="todo_line_id.is_extra_line")
80
+ forced_todo_key = fields.Char()
81
+ qty_available = fields.Float(compute="_compute_qty_available")
77
82
 
78
83
  @api.depends("todo_line_id")
79
84
  def _compute_todo_line_display_ids(self):
@@ -85,6 +90,9 @@ class WizStockBarcodesReadPicking(models.TransientModel):
85
90
  if self.option_group_id.show_pending_moves:
86
91
  self.pending_move_ids = self.todo_line_ids.filtered(
87
92
  lambda t: t.state == "pending"
93
+ and any(
94
+ sm.barcode_backorder_action == "pending" for sm in t.stock_move_ids
95
+ )
88
96
  )
89
97
  else:
90
98
  self.pending_move_ids = False
@@ -108,6 +116,40 @@ class WizStockBarcodesReadPicking(models.TransientModel):
108
116
  rec.total_product_uom_qty += line.product_uom_qty
109
117
  rec.total_product_qty_done += line.quantity_done
110
118
 
119
+ @api.depends("location_id", "product_id", "lot_id")
120
+ def _compute_qty_available(self):
121
+ if not self.product_id or self.location_id.usage != "internal":
122
+ self.qty_available = 0.0
123
+ return
124
+ domain_quant = [
125
+ ("product_id", "=", self.product_id.id),
126
+ ("location_id", "=", self.location_id.id),
127
+ ]
128
+ if self.lot_id:
129
+ domain_quant.append(("lot_id", "=", self.lot_id.id))
130
+ # if self.package_id:
131
+ # domain_quant.append(('package_id', '=', self.package_id.id))
132
+ groups = self.env["stock.quant"].read_group(
133
+ domain_quant, ["quantity"], [], orderby="id"
134
+ )
135
+ self.qty_available = groups[0]["quantity"]
136
+ # Unexpected done quantities must reduce qty_available
137
+ if self.lot_id:
138
+ done_move_lines = self.move_line_ids.filtered(
139
+ lambda m: m.product_id == self.product_id and m.lot_id == self.lot_id
140
+ )
141
+ else:
142
+ done_move_lines = self.move_line_ids.filtered(
143
+ lambda m: m.product_id == self.product_id
144
+ )
145
+ for sml in done_move_lines:
146
+ over_done_qty = float_round(
147
+ sml.qty_done - sml.product_uom_qty,
148
+ precision_rounding=sml.product_uom_id.rounding,
149
+ )
150
+ if over_done_qty > 0.0:
151
+ self.qty_available -= over_done_qty
152
+
111
153
  def name_get(self):
112
154
  return [
113
155
  (
@@ -141,8 +183,8 @@ class WizStockBarcodesReadPicking(models.TransientModel):
141
183
  # view, so for create a candidate picking with the same default picking
142
184
  # we need create it in this onchange
143
185
  self._set_default_picking()
144
- self.determine_todo_action()
145
186
  self.fill_pending_moves()
187
+ self.determine_todo_action()
146
188
 
147
189
  def get_sorted_move_lines(self, move_lines):
148
190
  location_field = self.option_group_id.location_field_to_sort
@@ -180,12 +222,8 @@ class WizStockBarcodesReadPicking(models.TransientModel):
180
222
  return move_lines
181
223
 
182
224
  def fill_pending_moves(self):
183
- if (
184
- self.option_group_id.barcode_guided_mode != "guided"
185
- and self.option_group_id.show_pending_moves
186
- and not self.todo_line_ids
187
- ):
188
- self.fill_todo_records()
225
+ # TODO: Unify method
226
+ self.fill_todo_records()
189
227
 
190
228
  def get_moves_or_move_lines(self):
191
229
  if self.option_group_id.source_pending_moves == "move_line_ids":
@@ -193,9 +231,12 @@ class WizStockBarcodesReadPicking(models.TransientModel):
193
231
  else:
194
232
  return self.picking_id.move_lines
195
233
 
234
+ def get_moves(self):
235
+ return self.picking_id.move_lines
236
+
196
237
  def fill_todo_records(self):
197
238
  move_lines = self.get_sorted_move_lines(self.get_moves_or_move_lines())
198
- self.env["wiz.stock.barcodes.read.todo"].fill_records(self, [move_lines])
239
+ self.fill_records([move_lines])
199
240
 
200
241
  @api.model
201
242
  def _get_fields_filled_special(self):
@@ -211,12 +252,6 @@ class WizStockBarcodesReadPicking(models.TransientModel):
211
252
  self.visible_force_done = self.env.context.get("visible_force_done", False)
212
253
  if not self.option_group_id.barcode_guided_mode == "guided":
213
254
  return False
214
- if not self.todo_line_ids:
215
- self.fill_todo_records()
216
- # When scanning all information in one step (e.g. using GS-1), the
217
- # status and qty processed might have not been update, we ensure it
218
- # invalidating the cache.
219
- self.todo_line_ids.invalidate_cache()
220
255
  self.todo_line_id = (
221
256
  forced_todo_line
222
257
  or self.todo_line_ids.filtered(lambda t: t._origin.state == "pending")[:1]
@@ -278,9 +313,24 @@ class WizStockBarcodesReadPicking(models.TransientModel):
278
313
  if not self.keep_screen_values or self.todo_line_id.state != "pending":
279
314
  if not self.env.context.get("skip_clean_values", False):
280
315
  self.action_clean_values()
281
- self.determine_todo_action()
316
+ keep_vals = {}
282
317
  else:
283
- self.action_show_step()
318
+ keep_vals = self._convert_to_write(self._cache)
319
+ self.fill_todo_records()
320
+ if self.forced_todo_key:
321
+ self.todo_line_id = self.pending_move_ids.filtered(
322
+ lambda ln: str(self._group_key(ln)) == self.forced_todo_key
323
+ )[:1]
324
+ self.selected_pending_move_id = self.todo_line_id
325
+ self.determine_todo_action(self.todo_line_id)
326
+ else:
327
+ self.determine_todo_action()
328
+ self.action_show_step()
329
+ if keep_vals:
330
+ self.update_keep_values(keep_vals)
331
+ # Force refresh candidate pickings to show green if not pending moves
332
+ if not self.pending_move_ids:
333
+ self._set_candidate_pickings(self.picking_id)
284
334
  # Now we can add read log with details.
285
335
  _logger.info("Add scanned log barcode:{}".format(self.barcode))
286
336
  self._add_read_log(log_detail=move_dic)
@@ -290,6 +340,13 @@ class WizStockBarcodesReadPicking(models.TransientModel):
290
340
  self._add_read_log()
291
341
  return res
292
342
 
343
+ def update_keep_values(self, keep_vals):
344
+ options = self.option_group_id.option_ids
345
+ fields_to_keep = options.filtered(
346
+ lambda op: self._fields[op.field_name].type != "float"
347
+ ).mapped("field_name")
348
+ self.update({f_name: keep_vals[f_name] for f_name in fields_to_keep})
349
+
293
350
  def action_manual_entry(self):
294
351
  result = super().action_manual_entry()
295
352
  if result:
@@ -456,6 +513,10 @@ class WizStockBarcodesReadPicking(models.TransientModel):
456
513
  domain = self._prepare_stock_moves_domain()
457
514
  if self.option_group_id.barcode_guided_mode == "guided":
458
515
  moves_todo = self.todo_line_id.stock_move_ids
516
+ elif self.picking_id:
517
+ moves_todo = self.picking_id.move_lines.filtered(
518
+ lambda sm: sm.product_id == self.product_id
519
+ )
459
520
  else:
460
521
  moves_todo = StockMove.search(domain)
461
522
  if not getattr(
@@ -615,13 +676,14 @@ class WizStockBarcodesReadPicking(models.TransientModel):
615
676
  # link this new lines to the todo line details
616
677
  # If user scan a product distinct of the todo line we need link to other
617
678
  # alternative move
618
- if move_to_link_in_todo_line and self.todo_line_id:
619
- todo_line = self.todo_line_id
620
- else:
621
- todo_line = self.todo_line_ids.filtered(
622
- lambda ln: ln.product_id == self.product_id
623
- )
624
- todo_line.line_ids = [(4, sml.id) for sml in stock_move_lines]
679
+ if self.option_group_id.source_pending_moves != "move_line_ids":
680
+ if move_to_link_in_todo_line and self.todo_line_id:
681
+ todo_line = self.todo_line_id
682
+ else:
683
+ todo_line = self.todo_line_ids.filtered(
684
+ lambda ln: ln.product_id == self.product_id
685
+ )
686
+ todo_line.line_ids = [(4, sml.id) for sml in stock_move_lines]
625
687
  self.update_fields_after_process_stock(moves_todo)
626
688
  return move_lines_dic
627
689
 
@@ -784,6 +846,150 @@ class WizStockBarcodesReadPicking(models.TransientModel):
784
846
  return bool(self.location_dest_id)
785
847
  return super()._option_required_hook(option_required)
786
848
 
849
+ def _group_key(self, line):
850
+ group_key_for_todo_records = self.option_group_id.group_key_for_todo_records
851
+ if group_key_for_todo_records:
852
+ return safe_eval(group_key_for_todo_records, globals_dict={"object": line})
853
+ if self.option_group_id.source_pending_moves == "move_line_ids":
854
+ return (
855
+ line.location_id.id,
856
+ line.product_id.id,
857
+ line.lot_id.id,
858
+ line.package_id.id,
859
+ )
860
+ else:
861
+ return (line.location_id.id, line.product_id.id)
862
+
863
+ def _get_all_products_quantities_in_package(self, package):
864
+ res = {}
865
+ for quant in package._get_contained_quants():
866
+ if quant.product_id not in res:
867
+ res[quant.product_id] = 0
868
+ res[quant.product_id] += quant.quantity
869
+ return res
870
+
871
+ def _prepare_fill_record_values(self, line, position):
872
+ vals = {
873
+ "wiz_barcode_id": self.id,
874
+ "product_id": line.product_id.id,
875
+ "product_uom_qty": line.product_uom_qty,
876
+ "name": "To do action",
877
+ "position_index": position,
878
+ "picking_code": line.picking_code,
879
+ }
880
+ if line._name == "stock.move.line":
881
+ package_product_dic = self._get_all_products_quantities_in_package(
882
+ line.package_id
883
+ )
884
+ vals.update(
885
+ {
886
+ "location_id": line.location_id.id,
887
+ "location_dest_id": line.location_dest_id.id,
888
+ "lot_id": line.lot_id.id,
889
+ "package_id": line.package_id.id,
890
+ "result_package_id": line.result_package_id.id,
891
+ "uom_id": line.product_uom_id.id,
892
+ "product_qty_reserved": line.product_qty,
893
+ "line_ids": [(6, 0, line.ids)],
894
+ "stock_move_ids": [(6, 0, line.move_id.ids)],
895
+ "package_product_qty": package_product_dic
896
+ and package_product_dic[line.product_id]
897
+ or 0.0,
898
+ "is_stock_move_line_origin": True,
899
+ }
900
+ )
901
+ else:
902
+ vals.update(
903
+ {
904
+ "location_id": (line.move_line_ids[:1] or line).location_id.id,
905
+ "location_dest_id": (
906
+ line.move_line_ids[:1] or line
907
+ ).location_dest_id.id,
908
+ "uom_id": line.product_uom.id,
909
+ "product_qty_reserved": line.move_line_ids
910
+ and sum(line.move_line_ids.mapped("product_qty"))
911
+ or line.product_uom_qty,
912
+ "line_ids": [(6, 0, line.move_line_ids.ids)],
913
+ "stock_move_ids": [(6, 0, line.ids)],
914
+ "is_stock_move_line_origin": False,
915
+ }
916
+ )
917
+ return vals
918
+
919
+ def _update_fill_record_values(self, line, vals):
920
+ vals["product_uom_qty"] += line.product_uom_qty
921
+ if vals["is_stock_move_line_origin"]:
922
+ vals["product_qty_reserved"] += line.product_qty
923
+ vals["line_ids"][0][2].append(line.id)
924
+ vals["stock_move_ids"][0][2].append(line.move_id.id)
925
+ else:
926
+ vals["product_qty_reserved"] += (
927
+ line.move_line_ids
928
+ and sum(line.move_line_ids.mapped("product_qty"))
929
+ or line.product_uom_qty
930
+ )
931
+ vals["line_ids"][0][2].extend(line.move_line_ids.ids)
932
+ vals["stock_move_ids"][0][2].extend(line.ids)
933
+ return vals
934
+
935
+ @api.model
936
+ def fill_records(self, lines_list):
937
+ """
938
+ :param lines_list: browse list
939
+ :return:
940
+ """
941
+ self.forced_todo_key = str(
942
+ self._group_key(self.todo_line_id or self.selected_pending_move_id)
943
+ )
944
+ self.todo_line_ids.unlink()
945
+ self.todo_line_id = False
946
+ # self.position_index = 0
947
+ todo_vals = OrderedDict()
948
+ position = 0
949
+ move_qty_dic = defaultdict(float)
950
+ is_stock_move_line_origin = lines_list[0]._name == "stock.move.line"
951
+ for lines in lines_list:
952
+ for line in lines:
953
+ key = self._group_key(line)
954
+ if key not in todo_vals:
955
+ todo_vals[key] = self._prepare_fill_record_values(line, position)
956
+ position += 1
957
+ else:
958
+ todo_vals[key] = self._update_fill_record_values(
959
+ line, todo_vals[key]
960
+ )
961
+ if is_stock_move_line_origin:
962
+ move_qty_dic[line.move_id] += max(
963
+ line.product_uom_qty, line.qty_done
964
+ )
965
+ else:
966
+ move_qty_dic[line] += max(line.product_uom_qty, line.quantity_done)
967
+ for move in self.get_moves():
968
+ qty = move_qty_dic[move]
969
+ if (
970
+ move.barcode_backorder_action == "pending"
971
+ and move.product_uom_qty > qty
972
+ ):
973
+ vals = self._prepare_fill_record_values(move, position)
974
+ vals.update(
975
+ {
976
+ "product_uom_qty": move.product_uom_qty - qty,
977
+ "product_qty_reserved": 0.0,
978
+ "line_ids": False,
979
+ "is_extra_line": True,
980
+ }
981
+ )
982
+ todo_vals[
983
+ (
984
+ move,
985
+ "M",
986
+ )
987
+ ] = vals
988
+ position += 1
989
+ self.todo_line_ids = self.env["wiz.stock.barcodes.read.todo"].create(
990
+ list(todo_vals.values())
991
+ )
992
+
787
993
 
788
994
  class WizCandidatePicking(models.TransientModel):
789
995
  """
@@ -127,6 +127,8 @@
127
127
  <field name="picking_location_id" invisible="1" />
128
128
  <field name="picking_location_dest_id" invisible="1" />
129
129
  <field name="company_id" invisible="1" />
130
+ <field name="todo_line_is_extra_line" invisible="1" />
131
+ <field name="qty_available" invisible="1" />
130
132
  </field>
131
133
  <field name="location_id" position="attributes">
132
134
  <attribute
@@ -161,6 +163,12 @@
161
163
  </div>
162
164
  </group>
163
165
  </group>
166
+ <group name="scan_fields" position="attributes">
167
+ <!-- hide group scan_fields for extra todo lines -->
168
+ <attribute
169
+ name="attrs"
170
+ >{'invisible': [('todo_line_is_extra_line', '!=', False)]}</attribute>
171
+ </group>
164
172
  <group name="scan_fields" position="after">
165
173
  <group
166
174
  string="Pending moves"
@@ -173,11 +181,14 @@
173
181
  force_save="1"
174
182
  mode="tree,kanban"
175
183
  >
176
- <tree>
184
+ <tree decoration-warning="is_extra_line">
177
185
  <field name="state" invisible="1" />
186
+ <field name="is_extra_line" invisible="1" />
178
187
  <field name="product_id" />
179
188
  <field name="product_uom_qty" />
180
189
  <field name="line_ids" invisible="1" />
190
+ <field name="location_id" optional="hide" />
191
+ <field name="location_dest_id" optional="hide" />
181
192
  <field
182
193
  name="lot_id"
183
194
  groups="stock.group_production_lot"
@@ -1,9 +1,8 @@
1
1
  # Copyright 2019 Sergio Teruel <sergio.teruel@tecnativa.com>
2
2
  # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
3
- from collections import OrderedDict
4
3
 
5
4
  from odoo import api, fields, models
6
- from odoo.tools.safe_eval import safe_eval
5
+ from odoo.tools.float_utils import float_compare
7
6
 
8
7
 
9
8
  class WizStockBarcodesReadTodo(models.TransientModel):
@@ -62,111 +61,38 @@ class WizStockBarcodesReadTodo(models.TransientModel):
62
61
  stock_move_ids = fields.Many2many(comodel_name="stock.move")
63
62
  position_index = fields.Integer()
64
63
  picking_code = fields.Char("Type of Operation")
65
-
66
- def _group_key(self, wiz, line):
67
- group_key_for_todo_records = wiz.option_group_id.group_key_for_todo_records
68
- if group_key_for_todo_records:
69
- return safe_eval(group_key_for_todo_records, globals_dict={"object": line})
70
- if wiz.option_group_id.source_pending_moves == "move_line_ids":
71
- return (line.location_id, line.product_id, line.lot_id, line.package_id)
72
- else:
73
- return (line.location_id, line.product_id)
74
-
75
- def _get_all_products_quantities_in_package(self, package):
76
- res = {}
77
- for quant in package._get_contained_quants():
78
- if quant.product_id not in res:
79
- res[quant.product_id] = 0
80
- res[quant.product_id] += quant.quantity
81
- return res
82
-
83
- def _prepare_fill_record_values(self, wiz_barcode, line, position):
84
- vals = {
85
- "product_id": line.product_id.id,
86
- "product_uom_qty": line.product_uom_qty,
87
- "name": "To do action",
88
- "position_index": position,
89
- "picking_code": line.picking_code,
90
- }
91
- if wiz_barcode.option_group_id.source_pending_moves == "move_line_ids":
92
- package_product_dic = self._get_all_products_quantities_in_package(
93
- line.package_id
94
- )
95
- vals.update(
96
- {
97
- "location_id": line.location_id.id,
98
- "location_dest_id": line.location_dest_id.id,
99
- "lot_id": line.lot_id.id,
100
- "package_id": line.package_id.id,
101
- "result_package_id": line.result_package_id.id,
102
- "uom_id": line.product_uom_id.id,
103
- "product_qty_reserved": line.product_qty,
104
- "line_ids": [(6, 0, line.ids)],
105
- "stock_move_ids": [(6, 0, line.move_id.ids)],
106
- "package_product_qty": package_product_dic
107
- and package_product_dic[line.product_id]
108
- or 0.0,
109
- }
110
- )
111
- else:
112
- vals.update(
113
- {
114
- "location_id": (line.move_line_ids[:1] or line).location_id.id,
115
- "location_dest_id": (
116
- line.move_line_ids[:1] or line
117
- ).location_dest_id.id,
118
- "uom_id": line.product_uom.id,
119
- "product_qty_reserved": line.move_line_ids
120
- and sum(line.move_line_ids.mapped("product_qty"))
121
- or line.product_uom_qty,
122
- "line_ids": [(6, 0, line.move_line_ids.ids)],
123
- "stock_move_ids": [(6, 0, line.ids)],
124
- }
125
- )
126
- return vals
127
-
128
- def _update_fill_record_values(self, wiz_barcode, line, vals):
129
- vals["product_uom_qty"] += line.product_uom_qty
130
- if wiz_barcode.option_group_id.source_pending_moves == "move_line_ids":
131
- vals["product_qty_reserved"] += line.product_qty
132
- vals["line_ids"][0][2].append(line.id)
133
- vals["stock_move_ids"][0][2].append(line.move_id.id)
134
- else:
135
- vals["product_qty_reserved"] += (
136
- line.move_line_ids
137
- and sum(line.move_line_ids.mapped("product_qty"))
138
- or line.product_uom_qty
139
- )
140
- vals["line_ids"][0][2].extend(line.move_line_ids.ids)
141
- vals["stock_move_ids"][0][2].extend(line.ids)
142
- return vals
143
-
144
- @api.model
145
- def fill_records(self, wiz_barcode, lines_list):
146
- """
147
- :param lines_list: browse list
148
- :return:
149
- """
150
- wiz_barcode.todo_line_ids = self.browse()
151
- todo_vals = OrderedDict()
152
- position = 0
153
- for lines in lines_list:
154
- for line in lines:
155
- key = self._group_key(wiz_barcode, line)
156
- if key not in todo_vals:
157
- todo_vals[key] = self._prepare_fill_record_values(
158
- wiz_barcode, line, position
159
- )
160
- position += 1
161
- else:
162
- todo_vals[key] = self._update_fill_record_values(
163
- wiz_barcode, line, todo_vals[key]
164
- )
165
- wiz_barcode.todo_line_ids = self.create(list(todo_vals.values()))
64
+ is_extra_line = fields.Boolean()
65
+ # Used in kanban view
66
+ is_stock_move_line_origin = fields.Boolean()
166
67
 
167
68
  def action_todo_next(self):
168
69
  self.state = "done_forced"
169
70
  self.line_ids.barcode_scan_state = "done_forced"
71
+ for sml in self.line_ids:
72
+ if (
73
+ float_compare(
74
+ sml.product_uom_qty,
75
+ sml.qty_done,
76
+ precision_rounding=sml.product_uom_id.rounding,
77
+ )
78
+ == 0
79
+ ):
80
+ continue
81
+ if sml.move_id.state == "confirmed" and sml.qty_done:
82
+ sml.move_id.state = "partially_available"
83
+ if sml.move_id.state in ["partially_available", "assigned"]:
84
+ sml.product_uom_qty = sml.qty_done
85
+ if self.is_extra_line or not self.is_stock_move_line_origin:
86
+ barcode_backorder_action = self.env.context.get(
87
+ "barcode_backorder_action", "create_backorder"
88
+ )
89
+ self.stock_move_ids.barcode_backorder_action = barcode_backorder_action
90
+ if barcode_backorder_action == "pending":
91
+ self.stock_move_ids.move_line_ids.unlink()
92
+ self.stock_move_ids._action_assign()
93
+ wiz_barcode = self.wiz_barcode_id
94
+ self.wiz_barcode_id.fill_todo_records()
95
+ self.wiz_barcode_id = wiz_barcode
170
96
  self.wiz_barcode_id.determine_todo_action()
171
97
 
172
98
  def action_reset_lines(self):
@@ -174,6 +100,7 @@ class WizStockBarcodesReadTodo(models.TransientModel):
174
100
  self.line_ids.barcode_scan_state = "pending"
175
101
  self.line_ids.qty_done = 0.0
176
102
  self.wiz_barcode_id.action_clean_values()
103
+ self.wiz_barcode_id.fill_todo_records()
177
104
  self.wiz_barcode_id.determine_todo_action()
178
105
 
179
106
  def action_back_line(self):
@@ -201,11 +128,21 @@ class WizStockBarcodesReadTodo(models.TransientModel):
201
128
  )
202
129
  def _compute_state(self):
203
130
  for rec in self:
204
- if rec.qty_done >= rec.product_uom_qty or (
131
+ if float_compare(
132
+ rec.qty_done,
133
+ rec.product_uom_qty,
134
+ precision_rounding=rec.uom_id.rounding,
135
+ ) > -1 or (
205
136
  rec.wiz_barcode_id.option_group_id.source_pending_moves
206
137
  == "move_line_ids"
207
138
  and rec.line_ids
208
- and not any(ln.barcode_scan_state == "pending" for ln in rec.line_ids)
139
+ and (
140
+ sum(rec.stock_move_ids.mapped("quantity_done"))
141
+ >= sum(rec.stock_move_ids.mapped("product_uom_qty"))
142
+ or not any(
143
+ ln.barcode_scan_state == "pending" for ln in rec.line_ids
144
+ )
145
+ )
209
146
  ):
210
147
  rec.state = "done"
211
148
  else: