odoo-addon-stock-barcodes 15.0.3.1.5.2__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 +8 -5
  13. odoo/addons/stock_barcodes/tests/test_stock_barcodes_picking.py +1 -0
  14. odoo/addons/stock_barcodes/wizard/stock_barcodes_read.py +14 -23
  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.5.2.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.5.2.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.5.2.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.5.2.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)
@@ -8,10 +8,11 @@
8
8
 
9
9
  /*
10
10
  :Author: David Goodger (goodger@python.org)
11
- :Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $
11
+ :Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $
12
12
  :Copyright: This stylesheet has been placed in the public domain.
13
13
 
14
14
  Default cascading style sheet for the HTML output of Docutils.
15
+ Despite the name, some widely supported CSS2 features are used.
15
16
 
16
17
  See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
17
18
  customize this style sheet.
@@ -274,7 +275,7 @@ pre.literal-block, pre.doctest-block, pre.math, pre.code {
274
275
  margin-left: 2em ;
275
276
  margin-right: 2em }
276
277
 
277
- pre.code .ln { color: grey; } /* line numbers */
278
+ pre.code .ln { color: gray; } /* line numbers */
278
279
  pre.code, code { background-color: #eeeeee }
279
280
  pre.code .comment, code .comment { color: #5C6576 }
280
281
  pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
@@ -300,7 +301,7 @@ span.option {
300
301
  span.pre {
301
302
  white-space: pre }
302
303
 
303
- span.problematic {
304
+ span.problematic, pre.problematic {
304
305
  color: red }
305
306
 
306
307
  span.section-subtitle {
@@ -366,7 +367,7 @@ ul.auto-toc {
366
367
  !! This file is generated by oca-gen-addon-readme !!
367
368
  !! changes will be overwritten. !!
368
369
  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
369
- !! source digest: sha256:2cb108800d4184b951795fa6c906ec0d63a06035061daad42aca10893c49a5be
370
+ !! source digest: sha256:aaf3270f55f43b616a2ceb165378f0909ba3cc1a3d0d36d21a65e4c04c06998f
370
371
  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
371
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>
372
373
  <p>This module provides a barcode reader interface for stock module.</p>
@@ -556,7 +557,9 @@ If you spotted it first, help us to smash it by providing a detailed and welcome
556
557
  <div class="section" id="maintainers">
557
558
  <h2><a class="toc-backref" href="#toc-entry-16">Maintainers</a></h2>
558
559
  <p>This module is maintained by the OCA.</p>
559
- <a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a>
560
+ <a class="reference external image-reference" href="https://odoo-community.org">
561
+ <img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" />
562
+ </a>
560
563
  <p>OCA, or the Odoo Community Association, is a nonprofit organization whose
561
564
  mission is to support the collaborative development of Odoo features and
562
565
  promote its widespread use.</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:
@@ -258,6 +239,8 @@ class WizStockBarcodesRead(models.AbstractModel):
258
239
  "more_match",
259
240
  _("No stock available for this lot with screen values"),
260
241
  )
242
+ self.lot_id = False
243
+ self.lot_name = False
261
244
  return False
262
245
  if quants:
263
246
  self.set_info_from_quants(quants)
@@ -371,12 +354,14 @@ class WizStockBarcodesRead(models.AbstractModel):
371
354
  def process_barcode_packaging_id(self):
372
355
  domain = self._barcode_domain(self.barcode)
373
356
  if self.env.user.has_group("product.group_stock_packaging"):
357
+ domain.append(("product_id", "!=", False))
374
358
  packaging = self.env["product.packaging"].search(domain)
375
359
  if packaging:
376
360
  if len(packaging) > 1:
377
361
  self._set_messagge_info(
378
362
  "more_match", _("More than one package found")
379
363
  )
364
+ self.packaging_id = False
380
365
  return False
381
366
  self.action_packaging_scaned_post(packaging)
382
367
  return True
@@ -399,14 +384,15 @@ class WizStockBarcodesRead(models.AbstractModel):
399
384
  option_func = getattr(self, "process_barcode_%s" % option.field_name, False)
400
385
  if option_func:
401
386
  res = option_func()
402
- if option.required:
403
- self.play_sounds(res)
404
387
  if res:
405
388
  barcode_found = True
389
+ self.play_sounds(barcode_found)
406
390
  break
407
391
  elif self.message_type != "success":
392
+ self.play_sounds(False)
408
393
  return False
409
394
  if not barcode_found:
395
+ self.play_sounds(barcode_found)
410
396
  if self.option_group_id.ignore_filled_fields:
411
397
  self._set_messagge_info(
412
398
  "info", _("Barcode not found or field already filled")
@@ -625,7 +611,9 @@ class WizStockBarcodesRead(models.AbstractModel):
625
611
 
626
612
  def action_clean_values(self):
627
613
  options = self.option_group_id.option_ids
628
- 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
+ )
629
617
  for option in options_to_clean:
630
618
  if option.field_name == "result_package_id" and self.keep_result_package:
631
619
  continue
@@ -749,6 +737,7 @@ class WizStockBarcodesRead(models.AbstractModel):
749
737
 
750
738
  def action_confirm(self):
751
739
  if not self.check_option_required():
740
+ self.play_sounds(False)
752
741
  return False
753
742
  record = self.browse(self.ids)
754
743
  record.write(self._convert_to_write(self._cache))
@@ -839,7 +828,9 @@ class WizStockBarcodesRead(models.AbstractModel):
839
828
  options.
840
829
  sticky: Permanent notification until user removes it
841
830
  """
842
- if self.option_group_id.display_notification:
831
+ if self.option_group_id.display_notification and not self.env.context.get(
832
+ "skip_display_notification", False
833
+ ):
843
834
  message = {"message": message, "type": message_type, "sticky": sticky}
844
835
  if title:
845
836
  message["title"] = title
@@ -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"