odoo-addon-stock-barcodes 15.0.3.1.5.2__py3-none-any.whl → 16.0.1.0.0.18__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 (59) hide show
  1. odoo/addons/stock_barcodes/README.rst +6 -6
  2. odoo/addons/stock_barcodes/__manifest__.py +8 -7
  3. odoo/addons/stock_barcodes/data/stock_barcodes_option.xml +1 -2
  4. odoo/addons/stock_barcodes/hooks.py +15 -13
  5. odoo/addons/stock_barcodes/i18n/es.po +2 -2
  6. odoo/addons/stock_barcodes/i18n/it.po +5 -5
  7. odoo/addons/stock_barcodes/i18n/stock_barcodes.pot +190 -160
  8. odoo/addons/stock_barcodes/models/__init__.py +1 -1
  9. odoo/addons/stock_barcodes/models/stock_barcodes_action.py +2 -1
  10. odoo/addons/stock_barcodes/models/stock_barcodes_option.py +2 -4
  11. odoo/addons/stock_barcodes/models/stock_move.py +37 -0
  12. odoo/addons/stock_barcodes/models/stock_move_line.py +8 -14
  13. odoo/addons/stock_barcodes/models/stock_picking.py +9 -36
  14. odoo/addons/stock_barcodes/models/stock_picking_type.py +5 -6
  15. odoo/addons/stock_barcodes/models/stock_quant.py +3 -4
  16. odoo/addons/stock_barcodes/security/ir.model.access.csv +0 -2
  17. odoo/addons/stock_barcodes/static/description/index.html +5 -4
  18. odoo/addons/stock_barcodes/static/src/css/stock.scss +41 -5
  19. odoo/addons/stock_barcodes/static/src/utils/barcode_handler_field.esm.js +36 -0
  20. odoo/addons/stock_barcodes/static/src/utils/barcodes_models_utils.esm.js +25 -0
  21. odoo/addons/stock_barcodes/static/src/views/form_view.esm.js +17 -0
  22. odoo/addons/stock_barcodes/static/src/views/kanban_renderer.esm.js +149 -0
  23. odoo/addons/stock_barcodes/static/src/views/view_compiler.esm.js +16 -0
  24. odoo/addons/stock_barcodes/static/src/views/views.esm.js +193 -0
  25. odoo/addons/stock_barcodes/static/src/widgets/boolean_toggle.esm.js +26 -0
  26. odoo/addons/stock_barcodes/static/src/widgets/numeric_step.esm.js +40 -0
  27. odoo/addons/stock_barcodes/static/src/widgets/numeric_step.xml +17 -0
  28. odoo/addons/stock_barcodes/static/src/widgets/view_button.esm.js +8 -0
  29. odoo/addons/stock_barcodes/static/src/widgets/view_button.xml +14 -0
  30. odoo/addons/stock_barcodes/tests/common.py +167 -0
  31. odoo/addons/stock_barcodes/tests/test_stock_barcodes.py +4 -172
  32. odoo/addons/stock_barcodes/tests/test_stock_barcodes_new_lot.py +2 -2
  33. odoo/addons/stock_barcodes/tests/test_stock_barcodes_picking.py +8 -8
  34. odoo/addons/stock_barcodes/views/stock_barcodes_option_view.xml +1 -1
  35. odoo/addons/stock_barcodes/views/stock_picking_views.xml +4 -3
  36. odoo/addons/stock_barcodes/wizard/stock_barcodes_read.py +56 -94
  37. odoo/addons/stock_barcodes/wizard/stock_barcodes_read_inventory.py +1 -4
  38. odoo/addons/stock_barcodes/wizard/stock_barcodes_read_inventory_views.xml +14 -15
  39. odoo/addons/stock_barcodes/wizard/stock_barcodes_read_picking.py +256 -92
  40. odoo/addons/stock_barcodes/wizard/stock_barcodes_read_picking_views.xml +59 -37
  41. odoo/addons/stock_barcodes/wizard/stock_barcodes_read_todo.py +33 -107
  42. odoo/addons/stock_barcodes/wizard/stock_barcodes_read_todo_view.xml +70 -28
  43. odoo/addons/stock_barcodes/wizard/stock_barcodes_read_views.xml +172 -189
  44. odoo/addons/stock_barcodes/wizard/stock_production_lot.py +2 -2
  45. odoo/addons/stock_barcodes/wizard/stock_production_lot_views.xml +6 -8
  46. {odoo_addon_stock_barcodes-15.0.3.1.5.2.dist-info → odoo_addon_stock_barcodes-16.0.1.0.0.18.dist-info}/METADATA +11 -11
  47. odoo_addon_stock_barcodes-16.0.1.0.0.18.dist-info/RECORD +66 -0
  48. {odoo_addon_stock_barcodes-15.0.3.1.5.2.dist-info → odoo_addon_stock_barcodes-16.0.1.0.0.18.dist-info}/WHEEL +1 -1
  49. odoo/addons/stock_barcodes/migrations/15.0.1.0.0/pre-migration.py +0 -14
  50. odoo/addons/stock_barcodes/models/stock_barcodes_read_log.py +0 -51
  51. odoo/addons/stock_barcodes/static/src/css/stock.css +0 -5
  52. odoo/addons/stock_barcodes/static/src/js/barcodes_models_mixin.js +0 -34
  53. odoo/addons/stock_barcodes/static/src/js/basic_controller.js +0 -412
  54. odoo/addons/stock_barcodes/static/src/js/basic_fields.js +0 -59
  55. odoo/addons/stock_barcodes/static/src/js/form_view.js +0 -24
  56. odoo/addons/stock_barcodes/static/src/js/kanban_renderer.js +0 -47
  57. odoo/addons/stock_barcodes/static/src/js/numeric_step.js +0 -43
  58. odoo_addon_stock_barcodes-15.0.3.1.5.2.dist-info/RECORD +0 -62
  59. {odoo_addon_stock_barcodes-15.0.3.1.5.2.dist-info → odoo_addon_stock_barcodes-16.0.1.0.0.18.dist-info}/top_level.txt +0 -0
@@ -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,22 +76,30 @@ 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):
80
85
  """Technical field to display only the first record in kanban view"""
81
86
  self.todo_line_display_ids = self.todo_line_id
82
87
 
83
- @api.depends("todo_line_ids")
88
+ @api.depends("todo_line_ids", "picking_id.move_line_ids.qty_done")
84
89
  def _compute_pending_move_ids(self):
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
91
99
 
92
- @api.depends("todo_line_ids")
100
+ @api.depends(
101
+ "todo_line_ids", "todo_line_ids.qty_done", "picking_id.move_line_ids.qty_done"
102
+ )
93
103
  def _compute_move_line_ids(self):
94
104
  self.move_line_ids = self.picking_id.move_line_ids.filtered("qty_done").sorted(
95
105
  key=lambda sml: (sml.write_date, sml.create_date), reverse=True
@@ -100,7 +110,7 @@ class WizStockBarcodesReadPicking(models.TransientModel):
100
110
  self.total_product_uom_qty = 0.0
101
111
  self.total_product_qty_done = 0.0
102
112
  for rec in self:
103
- product_moves = rec.picking_id.move_lines.filtered(
113
+ product_moves = rec.picking_id.move_ids.filtered(
104
114
  lambda ln: ln.product_id.ids == self.product_id.ids
105
115
  and ln.state != "cancel"
106
116
  )
@@ -108,6 +118,40 @@ class WizStockBarcodesReadPicking(models.TransientModel):
108
118
  rec.total_product_uom_qty += line.product_uom_qty
109
119
  rec.total_product_qty_done += line.quantity_done
110
120
 
121
+ @api.depends("location_id", "product_id", "lot_id")
122
+ def _compute_qty_available(self):
123
+ if not self.product_id or self.location_id.usage != "internal":
124
+ self.qty_available = 0.0
125
+ return
126
+ domain_quant = [
127
+ ("product_id", "=", self.product_id.id),
128
+ ("location_id", "=", self.location_id.id),
129
+ ]
130
+ if self.lot_id:
131
+ domain_quant.append(("lot_id", "=", self.lot_id.id))
132
+ # if self.package_id:
133
+ # domain_quant.append(('package_id', '=', self.package_id.id))
134
+ groups = self.env["stock.quant"].read_group(
135
+ domain_quant, ["quantity"], [], orderby="id"
136
+ )
137
+ self.qty_available = groups[0]["quantity"]
138
+ # Unexpected done quantities must reduce qty_available
139
+ if self.lot_id:
140
+ done_move_lines = self.move_line_ids.filtered(
141
+ lambda m: m.product_id == self.product_id and m.lot_id == self.lot_id
142
+ )
143
+ else:
144
+ done_move_lines = self.move_line_ids.filtered(
145
+ lambda m: m.product_id == self.product_id
146
+ )
147
+ for sml in done_move_lines:
148
+ over_done_qty = float_round(
149
+ sml.qty_done - sml.reserved_uom_qty,
150
+ precision_rounding=sml.product_uom_id.rounding,
151
+ )
152
+ if over_done_qty > 0.0:
153
+ self.qty_available -= over_done_qty
154
+
111
155
  def name_get(self):
112
156
  return [
113
157
  (
@@ -126,14 +170,15 @@ class WizStockBarcodesReadPicking(models.TransientModel):
126
170
  if picking_id:
127
171
  self._set_candidate_pickings(self.env["stock.picking"].browse(picking_id))
128
172
 
129
- @api.model
130
- def create(self, vals):
173
+ @api.model_create_multi
174
+ def create(self, vals_list):
131
175
  # When user click any view button the wizard record is create and the
132
176
  # picking candidates have been lost, so we need set it.
133
- wiz = super().create(vals)
134
- if wiz.picking_id:
135
- wiz._set_candidate_pickings(wiz.picking_id)
136
- return wiz
177
+ wizards = super().create(vals_list)
178
+ for wiz in wizards:
179
+ if wiz.picking_id:
180
+ wiz._set_candidate_pickings(wiz.picking_id)
181
+ return wizards
137
182
 
138
183
  @api.onchange("picking_id")
139
184
  def onchange_picking_id(self):
@@ -141,8 +186,8 @@ class WizStockBarcodesReadPicking(models.TransientModel):
141
186
  # view, so for create a candidate picking with the same default picking
142
187
  # we need create it in this onchange
143
188
  self._set_default_picking()
144
- self.determine_todo_action()
145
189
  self.fill_pending_moves()
190
+ self.determine_todo_action()
146
191
 
147
192
  def get_sorted_move_lines(self, move_lines):
148
193
  location_field = self.option_group_id.location_field_to_sort
@@ -175,27 +220,26 @@ class WizStockBarcodesReadPicking(models.TransientModel):
175
220
  def _get_stock_move_lines_todo(self):
176
221
  move_lines = self.picking_id.move_line_ids.filtered(
177
222
  lambda ml: (not ml.barcode_scan_state or ml.barcode_scan_state == "pending")
178
- and ml.qty_done < ml.product_qty
223
+ and ml.qty_done < ml.reserved_qty
179
224
  )
180
225
  return move_lines
181
226
 
182
227
  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()
228
+ # TODO: Unify method
229
+ self.fill_todo_records()
189
230
 
190
231
  def get_moves_or_move_lines(self):
191
232
  if self.option_group_id.source_pending_moves == "move_line_ids":
192
233
  return self.picking_id.move_line_ids.filtered(lambda ln: ln.move_id)
193
234
  else:
194
- return self.picking_id.move_lines
235
+ return self.picking_id.move_ids
236
+
237
+ def get_moves(self):
238
+ return self.picking_id.move_ids
195
239
 
196
240
  def fill_todo_records(self):
197
241
  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])
242
+ self.fill_records([move_lines])
199
243
 
200
244
  @api.model
201
245
  def _get_fields_filled_special(self):
@@ -211,12 +255,6 @@ class WizStockBarcodesReadPicking(models.TransientModel):
211
255
  self.visible_force_done = self.env.context.get("visible_force_done", False)
212
256
  if not self.option_group_id.barcode_guided_mode == "guided":
213
257
  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
258
  self.todo_line_id = (
221
259
  forced_todo_line
222
260
  or self.todo_line_ids.filtered(lambda t: t._origin.state == "pending")[:1]
@@ -264,11 +302,7 @@ class WizStockBarcodesReadPicking(models.TransientModel):
264
302
  self.picking_product_qty = move_line.qty_done
265
303
 
266
304
  def action_done(self):
267
- # Skip read log creation to be able to pass log_detail when available.
268
- res = super(
269
- WizStockBarcodesReadPicking,
270
- self.with_context(_stock_barcodes_skip_read_log=True),
271
- ).action_done()
305
+ res = super().action_done()
272
306
  if res:
273
307
  move_dic = self._process_stock_move_line()
274
308
  if move_dic:
@@ -278,18 +312,34 @@ class WizStockBarcodesReadPicking(models.TransientModel):
278
312
  if not self.keep_screen_values or self.todo_line_id.state != "pending":
279
313
  if not self.env.context.get("skip_clean_values", False):
280
314
  self.action_clean_values()
281
- self.determine_todo_action()
315
+ keep_vals = {}
282
316
  else:
283
- self.action_show_step()
284
- # Now we can add read log with details.
285
- _logger.info("Add scanned log barcode:{}".format(self.barcode))
286
- self._add_read_log(log_detail=move_dic)
317
+ keep_vals = self._convert_to_write(self._cache)
318
+ self.fill_todo_records()
319
+ if self.forced_todo_key:
320
+ self.todo_line_id = self.pending_move_ids.filtered(
321
+ lambda ln: str(self._group_key(ln)) == self.forced_todo_key
322
+ )[:1]
323
+ self.selected_pending_move_id = self.todo_line_id
324
+ self.determine_todo_action(self.todo_line_id)
325
+ else:
326
+ self.determine_todo_action()
327
+ self.action_show_step()
328
+ if keep_vals:
329
+ self.update_keep_values(keep_vals)
330
+ # Force refresh candidate pickings to show green if not pending moves
331
+ if not self.pending_move_ids:
332
+ self._set_candidate_pickings(self.picking_id)
287
333
  return move_dic
288
- # Add read log normally.
289
- _logger.info("Add scanned log barcode:{}".format(self.barcode))
290
- self._add_read_log()
291
334
  return res
292
335
 
336
+ def update_keep_values(self, keep_vals):
337
+ options = self.option_group_id.option_ids
338
+ fields_to_keep = options.filtered(
339
+ lambda op: self._fields[op.field_name].type != "float"
340
+ ).mapped("field_name")
341
+ self.update({f_name: keep_vals[f_name] for f_name in fields_to_keep})
342
+
293
343
  def action_manual_entry(self):
294
344
  result = super().action_manual_entry()
295
345
  if result:
@@ -456,6 +506,10 @@ class WizStockBarcodesReadPicking(models.TransientModel):
456
506
  domain = self._prepare_stock_moves_domain()
457
507
  if self.option_group_id.barcode_guided_mode == "guided":
458
508
  moves_todo = self.todo_line_id.stock_move_ids
509
+ elif self.picking_id:
510
+ moves_todo = self.picking_id.move_ids.filtered(
511
+ lambda sm: sm.product_id == self.product_id
512
+ )
459
513
  else:
460
514
  moves_todo = StockMove.search(domain)
461
515
  if not getattr(
@@ -530,9 +584,9 @@ class WizStockBarcodesReadPicking(models.TransientModel):
530
584
  return False
531
585
  move_lines_dic = {}
532
586
  for line in lines:
533
- if line.product_uom_qty and len(lines) > 1:
587
+ if line.reserved_uom_qty and len(lines) > 1:
534
588
  assigned_qty = min(
535
- max(line.product_uom_qty - line.qty_done, 0.0), available_qty
589
+ max(line.reserved_uom_qty - line.qty_done, 0.0), available_qty
536
590
  )
537
591
  else:
538
592
  assigned_qty = available_qty
@@ -563,7 +617,7 @@ class WizStockBarcodesReadPicking(models.TransientModel):
563
617
  elif line.result_package_id == line.package_id:
564
618
  sml_vals.update({"result_package_id": False})
565
619
  self._update_stock_move_line(line, sml_vals)
566
- if line.qty_done >= line.product_uom_qty:
620
+ if line.qty_done >= line.reserved_uom_qty:
567
621
  line.barcode_scan_state = "done"
568
622
  elif self.env.context.get("done_forced"):
569
623
  line.barcode_scan_state = "done_forced"
@@ -597,7 +651,7 @@ class WizStockBarcodesReadPicking(models.TransientModel):
597
651
  move_to_link_in_todo_line = True
598
652
  if not moves_to_link:
599
653
  move_to_link_in_todo_line = False
600
- moves_to_link = self.picking_id.move_lines.filtered(
654
+ moves_to_link = self.picking_id.move_ids.filtered(
601
655
  lambda mv: mv.product_id == self.product_id
602
656
  )
603
657
  stock_move_lines = self.create_new_stock_move_line(
@@ -615,13 +669,14 @@ class WizStockBarcodesReadPicking(models.TransientModel):
615
669
  # link this new lines to the todo line details
616
670
  # If user scan a product distinct of the todo line we need link to other
617
671
  # 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]
672
+ if self.option_group_id.source_pending_moves != "move_line_ids":
673
+ if move_to_link_in_todo_line and self.todo_line_id:
674
+ todo_line = self.todo_line_id
675
+ else:
676
+ todo_line = self.todo_line_ids.filtered(
677
+ lambda ln: ln.product_id == self.product_id
678
+ )
679
+ todo_line.line_ids = [(4, sml.id) for sml in stock_move_lines]
625
680
  self.update_fields_after_process_stock(moves_todo)
626
681
  return move_lines_dic
627
682
 
@@ -695,46 +750,6 @@ class WizStockBarcodesReadPicking(models.TransientModel):
695
750
  return False
696
751
  return res
697
752
 
698
- def _prepare_scan_log_values(self, log_detail=False):
699
- # Store in read log line each line added with the quantities assigned
700
- vals = super()._prepare_scan_log_values(log_detail=log_detail)
701
- vals["picking_id"] = self.picking_id.id
702
- if log_detail:
703
- vals["log_line_ids"] = [
704
- (0, 0, {"move_line_id": x[0], "product_qty": x[1]})
705
- for x in log_detail.items()
706
- ]
707
- return vals
708
-
709
- def remove_scanning_log(self, scanning_log):
710
- for log in scanning_log:
711
- for log_scan_line in log.log_line_ids:
712
- sml = log_scan_line.move_line_id
713
- if sml.state not in ["draft", "assigned", "confirmed"]:
714
- raise ValidationError(
715
- _(
716
- "You cannot remove an entry linked to a operation "
717
- "in state new, assigned or confirmed"
718
- )
719
- )
720
- qty = sml.qty_done - log_scan_line.product_qty
721
- log_scan_line.move_line_id.qty_done = max(qty, 0.0)
722
- if sml.state == "draft" and sml.move_id.quantity_done == 0.0:
723
- # This move has been created by the last scan, remove it.
724
- sml.move_id.unlink()
725
- self.picking_product_qty = sum(
726
- log.log_line_ids.mapped("move_line_id.move_id.quantity_done")
727
- )
728
- log.unlink()
729
-
730
- def action_undo_last_scan(self):
731
- res = super().action_undo_last_scan()
732
- log_scan = first(
733
- self.scan_log_ids.filtered(lambda x: x.create_uid == self.env.user)
734
- )
735
- self.remove_scanning_log(log_scan)
736
- return res
737
-
738
753
  def get_lot_by_removal_strategy(self):
739
754
  quants = first(
740
755
  self.env["stock.quant"]._gather(self.product_id, self.location_id)
@@ -784,6 +799,155 @@ class WizStockBarcodesReadPicking(models.TransientModel):
784
799
  return bool(self.location_dest_id)
785
800
  return super()._option_required_hook(option_required)
786
801
 
802
+ def _group_key(self, line):
803
+ group_key_for_todo_records = self.option_group_id.group_key_for_todo_records
804
+ if group_key_for_todo_records:
805
+ return safe_eval(group_key_for_todo_records, globals_dict={"object": line})
806
+ if self.option_group_id.source_pending_moves == "move_line_ids":
807
+ return (
808
+ line.location_id.id,
809
+ line.product_id.id,
810
+ line.lot_id.id,
811
+ line.package_id.id,
812
+ )
813
+ else:
814
+ return (line.location_id.id, line.product_id.id)
815
+
816
+ def _get_all_products_quantities_in_package(self, package):
817
+ res = {}
818
+ # TODO: Check if domain is applied and we must recover _get_contained_quants
819
+ for quant in package.quant_ids:
820
+ if quant.product_id not in res:
821
+ res[quant.product_id] = 0
822
+ res[quant.product_id] += quant.quantity
823
+ return res
824
+
825
+ def _prepare_fill_record_values(self, line, position):
826
+ vals = {
827
+ "wiz_barcode_id": self.id,
828
+ "product_id": line.product_id.id,
829
+ "name": "To do action",
830
+ "position_index": position,
831
+ "picking_code": line.picking_code,
832
+ }
833
+ if line._name == "stock.move.line":
834
+ package_product_dic = self._get_all_products_quantities_in_package(
835
+ line.package_id
836
+ )
837
+ vals.update(
838
+ {
839
+ "location_id": line.location_id.id,
840
+ "location_dest_id": line.location_dest_id.id,
841
+ "lot_id": line.lot_id.id,
842
+ "package_id": line.package_id.id,
843
+ "result_package_id": line.result_package_id.id,
844
+ "uom_id": line.product_uom_id.id,
845
+ "product_uom_qty": line.reserved_uom_qty,
846
+ "product_qty_reserved": line.reserved_qty,
847
+ "line_ids": [(6, 0, line.ids)],
848
+ "stock_move_ids": [(6, 0, line.move_id.ids)],
849
+ "package_product_qty": package_product_dic
850
+ and package_product_dic[line.product_id]
851
+ or 0.0,
852
+ "is_stock_move_line_origin": True,
853
+ }
854
+ )
855
+ else:
856
+ vals.update(
857
+ {
858
+ "location_id": (line.move_line_ids[:1] or line).location_id.id,
859
+ "location_dest_id": (
860
+ line.move_line_ids[:1] or line
861
+ ).location_dest_id.id,
862
+ "uom_id": line.product_uom.id,
863
+ "product_uom_qty": line.product_uom_qty,
864
+ "product_qty_reserved": line.move_line_ids
865
+ # TODO: Use reserved_qty or reserved_uom_qty
866
+ and sum(line.move_line_ids.mapped("reserved_qty"))
867
+ or line.product_uom_qty,
868
+ "line_ids": [(6, 0, line.move_line_ids.ids)],
869
+ "stock_move_ids": [(6, 0, line.ids)],
870
+ "is_stock_move_line_origin": False,
871
+ }
872
+ )
873
+ return vals
874
+
875
+ def _update_fill_record_values(self, line, vals):
876
+ if vals["is_stock_move_line_origin"]:
877
+ vals["product_uom_qty"] += line.reserved_uom_qty
878
+ vals["product_qty_reserved"] += line.reserved_qty
879
+ vals["line_ids"][0][2].append(line.id)
880
+ vals["stock_move_ids"][0][2].append(line.move_id.id)
881
+ else:
882
+ vals["product_uom_qty"] += line.product_uom_qty
883
+ vals["product_qty_reserved"] += (
884
+ line.move_line_ids
885
+ # TODO: Use reserved_qty or reserved_uom_qty
886
+ and sum(line.move_line_ids.mapped("reserved_qty"))
887
+ or line.product_uom_qty
888
+ )
889
+ vals["line_ids"][0][2].extend(line.move_line_ids.ids)
890
+ vals["stock_move_ids"][0][2].extend(line.ids)
891
+ return vals
892
+
893
+ @api.model
894
+ def fill_records(self, lines_list):
895
+ """
896
+ :param lines_list: browse list
897
+ :return:
898
+ """
899
+ self.forced_todo_key = str(
900
+ self._group_key(self.todo_line_id or self.selected_pending_move_id)
901
+ )
902
+ self.todo_line_ids.unlink()
903
+ self.todo_line_id = False
904
+ # self.position_index = 0
905
+ todo_vals = OrderedDict()
906
+ position = 0
907
+ move_qty_dic = defaultdict(float)
908
+ is_stock_move_line_origin = lines_list[0]._name == "stock.move.line"
909
+ for lines in lines_list:
910
+ for line in lines:
911
+ key = self._group_key(line)
912
+ if key not in todo_vals:
913
+ todo_vals[key] = self._prepare_fill_record_values(line, position)
914
+ position += 1
915
+ else:
916
+ todo_vals[key] = self._update_fill_record_values(
917
+ line, todo_vals[key]
918
+ )
919
+ if is_stock_move_line_origin:
920
+ move_qty_dic[line.move_id] += max(
921
+ line.reserved_uom_qty, line.qty_done
922
+ )
923
+ else:
924
+ move_qty_dic[line] += max(line.product_uom_qty, line.quantity_done)
925
+ for move in self.get_moves():
926
+ qty = move_qty_dic[move]
927
+ if (
928
+ move.barcode_backorder_action == "pending"
929
+ and move.product_uom_qty > qty
930
+ ):
931
+ vals = self._prepare_fill_record_values(move, position)
932
+ vals.update(
933
+ {
934
+ "product_uom_qty": move.product_uom_qty - qty,
935
+ "product_qty_reserved": 0.0,
936
+ "line_ids": False,
937
+ "is_extra_line": True,
938
+ }
939
+ )
940
+ todo_vals[
941
+ (
942
+ move,
943
+ "M",
944
+ )
945
+ ] = vals
946
+ position += 1
947
+ self.todo_line_ids = self.env["wiz.stock.barcodes.read.todo"].create(
948
+ list(todo_vals.values())
949
+ )
950
+
787
951
 
788
952
  class WizCandidatePicking(models.TransientModel):
789
953
  """
@@ -850,9 +1014,9 @@ class WizCandidatePicking(models.TransientModel):
850
1014
  qty_demand = 0
851
1015
  qty_done = 0
852
1016
  candidate.product_qty_reserved = sum(
853
- candidate.picking_id.mapped("move_lines.reserved_availability")
1017
+ candidate.picking_id.mapped("move_ids.reserved_availability")
854
1018
  )
855
- for move in candidate.picking_id.move_lines:
1019
+ for move in candidate.picking_id.move_ids:
856
1020
  qty_reserved += move.reserved_availability
857
1021
  qty_demand += move.product_uom_qty
858
1022
  qty_done += move.quantity_done