22# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
33
44import logging
5+ from collections import defaultdict
6+ from copy import deepcopy
57
68from pytz import timezone
79
@@ -245,8 +247,8 @@ def _get_picking_to_assign_domain(self):
245247
246248 def _field_picking_domains (self ):
247249 return {
248- "count_picking_all " : [],
249- "count_picking_release_ready " : [
250+ "all " : [],
251+ "release_ready " : [
250252 ("release_ready" , "=" , True ),
251253 # FIXME not TZ friendly
252254 (
@@ -255,91 +257,135 @@ def _field_picking_domains(self):
255257 fields .Datetime .now ().replace (hour = 23 , minute = 59 ),
256258 ),
257259 ],
258- "count_picking_released " : [
260+ "released " : [
259261 ("last_release_date" , "!=" , False ),
260262 ("state" , "in" , ("assigned" , "waiting" , "confirmed" )),
261263 ],
262- "count_picking_assigned " : [
264+ "assigned " : [
263265 ("last_release_date" , "!=" , False ),
264266 ("state" , "=" , "assigned" ),
265267 ],
266- "count_picking_waiting " : [
268+ "waiting " : [
267269 ("last_release_date" , "!=" , False ),
268270 ("state" , "in" , ("waiting" , "confirmed" )),
269271 ],
270- "count_picking_late " : [
272+ "late " : [
271273 ("last_release_date" , "!=" , False ),
272274 ("scheduled_date" , "<" , fields .Datetime .now ()),
273275 ("state" , "in" , ("assigned" , "waiting" , "confirmed" )),
274276 ],
275- "count_picking_priority " : [
277+ "priority " : [
276278 ("last_release_date" , "!=" , False ),
277279 ("priority" , "=" , "1" ),
278280 ("state" , "in" , ("assigned" , "waiting" , "confirmed" )),
279281 ],
280- "count_picking_done " : [
282+ "done " : [
281283 ("state" , "=" , "done" ),
282284 ("date_done" , ">" , fields .Datetime .now ().replace (hour = 0 , minute = 0 )),
283285 ],
284286 }
285287
288+ @api .model
289+ def _get_picking_read_group_fields (self ):
290+ "Additional fields to read on read_group of stock.pickings"
291+ return []
292+
293+ @api .model
294+ def _get_picking_compute_fields (self ):
295+ """This returns a list of tuples
296+ the first value of the tuple represents the prefix of computed field
297+ and the second value represents the field used to set the computed field"""
298+ return [("count" , "release_channel_id_count" )]
299+
300+ @api .model
301+ def _get_move_read_group_fields (self ):
302+ "Additional fields to read on read_group of stock.moves"
303+ return []
304+
305+ @api .model
306+ def _get_move_compute_fields (self ):
307+ """This returns a list of tuples
308+ the first value of the tuple represents the prefix of computed field
309+ and the second value represents the field used to set the computed field"""
310+ return [("count" , "picking_id_count" )]
311+
312+ @api .model
313+ def _get_compute_field_name (self , prefix , name , domain_name ):
314+ return f"{ prefix } _{ name } _{ domain_name } "
315+
316+ @api .model
317+ def _get_default_aggregate_values (self ):
318+ picking_compute_fields = self ._get_picking_compute_fields ()
319+ move_compute_fields = self ._get_move_compute_fields ()
320+ default_values = {}
321+ for domain_name , _d in self ._field_picking_domains ().items ():
322+ for prefix , _fetch in picking_compute_fields :
323+ field = self ._get_compute_field_name (prefix , "picking" , domain_name )
324+ default_values [field ] = 0
325+ for prefix , _fetch in move_compute_fields :
326+ field = self ._get_compute_field_name (prefix , "move" , domain_name )
327+ default_values [field ] = 0
328+ return default_values
329+
286330 # TODO maybe we have to do raw SQL to include the picking + moves counts in
287331 # a single query
288332 def _compute_picking_count (self ):
289333 domains = self ._field_picking_domains ()
290- picking_ids_per_field = {}
291- for field , domain in domains .items ():
334+ picking_channels = defaultdict (
335+ lambda : {"channel_id" : False , "matched_domains" : []}
336+ )
337+ all_picking_ids = set ()
338+ channels_aggregate_values = defaultdict (lambda : defaultdict (lambda : 0 ))
339+ picking_read_fields = self ._get_picking_read_group_fields ()
340+ picking_compute_fields = self ._get_picking_compute_fields ()
341+ for domain_name , domain in domains .items ():
292342 data = self .env ["stock.picking" ].read_group (
293343 domain + [("release_channel_id" , "in" , self .ids )],
294- ["release_channel_id" , "picking_ids:array_agg(id)" ],
344+ ["release_channel_id" , "picking_ids:array_agg(id)" ]
345+ + picking_read_fields ,
295346 ["release_channel_id" ],
296347 )
297- count = {
298- row ["release_channel_id" ][0 ]: row ["release_channel_id_count" ]
299- for row in data
300- if row ["release_channel_id" ]
301- }
302- picking_ids_per_field .update (
303- {
304- (row ["release_channel_id" ][0 ], field ): row ["picking_ids" ]
305- for row in data
306- if row ["release_channel_id" ]
307- }
308- )
309-
310- for record in self :
311- record [field ] = count .get (record .id , 0 )
312-
313- all_picking_ids = [
314- pid for picking_ids in picking_ids_per_field .values () for pid in picking_ids
315- ]
348+ for row in data :
349+ channel_id = row ["release_channel_id" ] and row ["release_channel_id" ][0 ]
350+ if not channel_id :
351+ continue
352+ picking_ids = row ["picking_ids" ]
353+ all_picking_ids .update (picking_ids )
354+ for picking_id in picking_ids :
355+ picking_channels [picking_id ]["channel_id" ] = channel_id
356+ picking_channels [picking_id ]["matched_domains" ].append (domain_name )
357+
358+ for prefix , fetch in picking_compute_fields :
359+ field = self ._get_compute_field_name (prefix , "picking" , domain_name )
360+ channels_aggregate_values [channel_id ][field ] = row [fetch ]
361+ move_read_fields = self ._get_move_read_group_fields ()
362+ move_compute_fields = self ._get_move_compute_fields ()
316363 data = self .env ["stock.move" ].read_group (
317364 # TODO for now we do estimates, later we may improve the domains per
318365 # field, but now we can run one sql query on stock.move for all fields
319- [("picking_id" , "in" , all_picking_ids ), ("state" , "!=" , "cancel" )],
320- ["picking_id" ],
366+ [("picking_id" , "in" , list ( all_picking_ids ) ), ("state" , "!=" , "cancel" )],
367+ ["picking_id" ] + move_read_fields ,
321368 ["picking_id" ],
322369 )
323- move_count = {
324- row ["picking_id" ][0 ]: row ["picking_id_count" ]
325- for row in data
326- if row ["picking_id" ]
327- }
328- for field , __ in domains .items ():
329- move_field = field .replace ("picking" , "move" )
330- for record in self :
331- picking_ids = picking_ids_per_field .get ((record .id , field ), [])
332- move_estimate = sum (
333- move_count .get (picking_id , 0 ) for picking_id in picking_ids
334- )
335- record [move_field ] = move_estimate
336-
370+ for row in data :
371+ picking_id = row ["picking_id" ][0 ]
372+ for matched_domain in picking_channels [picking_id ]["matched_domains" ]:
373+ for prefix , fetch in move_compute_fields :
374+ field = self ._get_compute_field_name (prefix , "move" , matched_domain )
375+ channel_id = picking_channels [picking_id ]["channel_id" ]
376+ channels_aggregate_values [channel_id ][field ] += row [fetch ]
377+
378+ default_aggregate_values = self ._get_default_aggregate_values ()
337379 for record in self :
338- record .count_picking_full_progress = (
339- record .count_picking_release_ready
340- + record .count_picking_released
341- + record .count_picking_done
342- )
380+ values = deepcopy (default_aggregate_values )
381+ values .update (channels_aggregate_values .get (record .id , {}))
382+ for prefix , _fetch in self ._get_picking_compute_fields ():
383+ values [f"{ prefix } _picking_full_progress" ] = (
384+ values [f"{ prefix } _picking_release_ready" ]
385+ + values [f"{ prefix } _picking_released" ]
386+ + values [f"{ prefix } _picking_done" ]
387+ )
388+ record .write (values )
343389
344390 def _query_get_chain (self , pickings ):
345391 """Get all stock.picking before an outgoing one
@@ -380,7 +426,7 @@ def _compute_picking_chain(self):
380426 self .env ["stock.move" ].flush (["move_dest_ids" , "move_orig_ids" , "picking_id" ])
381427 self .env ["stock.picking" ].flush (["state" ])
382428 for channel in self :
383- domain = self ._field_picking_domains ()["count_picking_released " ]
429+ domain = self ._field_picking_domains ()["released " ]
384430 domain += [("release_channel_id" , "=" , channel .id )]
385431 released = self .env ["stock.picking" ].search (domain )
386432
@@ -408,7 +454,7 @@ def _compute_picking_chain(self):
408454 def _compute_last_done_picking (self ):
409455 for channel in self :
410456 # TODO we have one query per channel, could be better
411- domain = self ._field_picking_domains ()["count_picking_done " ]
457+ domain = self ._field_picking_domains ()["done " ]
412458 domain += [("release_channel_id" , "=" , channel .id )]
413459 picking = self .env ["stock.picking" ].search (
414460 domain , limit = 1 , order = "date_done DESC"
@@ -535,35 +581,36 @@ def _eval_code(self, pickings):
535581
536582 def action_picking_all (self ):
537583 return self ._action_picking_for_field (
538- "count_picking_all " , context = {"search_default_release_ready" : 1 }
584+ "all " , context = {"search_default_release_ready" : 1 }
539585 )
540586
541587 def action_picking_release_ready (self ):
542- return self ._action_picking_for_field ("count_picking_release_ready " )
588+ return self ._action_picking_for_field ("release_ready " )
543589
544590 def action_picking_released (self ):
545- return self ._action_picking_for_field ("count_picking_released " )
591+ return self ._action_picking_for_field ("released " )
546592
547593 def action_picking_assigned (self ):
548- return self ._action_picking_for_field ("count_picking_assigned " )
594+ return self ._action_picking_for_field ("assigned " )
549595
550596 def action_picking_waiting (self ):
551- return self ._action_picking_for_field ("count_picking_waiting " )
597+ return self ._action_picking_for_field ("waiting " )
552598
553599 def action_picking_late (self ):
554- return self ._action_picking_for_field ("count_picking_late " )
600+ return self ._action_picking_for_field ("late " )
555601
556602 def action_picking_priority (self ):
557- return self ._action_picking_for_field ("count_picking_priority " )
603+ return self ._action_picking_for_field ("priority " )
558604
559605 def action_picking_done (self ):
560- return self ._action_picking_for_field ("count_picking_done " )
606+ return self ._action_picking_for_field ("done " )
561607
562608 def _action_picking_for_field (self , field_domain , context = None ):
563609 domain = self ._field_picking_domains ()[field_domain ]
564610 domain += [("release_channel_id" , "in" , self .ids )]
565611 pickings = self .env ["stock.picking" ].search (domain )
566- field_descr = self ._fields [field_domain ]._description_string (self .env )
612+ field = self ._get_compute_field_name ("count" , "picking" , field_domain )
613+ field_descr = self ._fields [field ]._description_string (self .env )
567614 return self ._build_action (
568615 "stock_available_to_promise_release.stock_picking_release_action" ,
569616 pickings ,
@@ -573,35 +620,36 @@ def _action_picking_for_field(self, field_domain, context=None):
573620
574621 def action_move_all (self ):
575622 return self ._action_move_for_field (
576- "count_picking_all " , context = {"search_default_release_ready" : 1 }
623+ "all " , context = {"search_default_release_ready" : 1 }
577624 )
578625
579626 def action_move_release_ready (self ):
580- return self ._action_move_for_field ("count_picking_release_ready " )
627+ return self ._action_move_for_field ("release_ready " )
581628
582629 def action_move_released (self ):
583- return self ._action_move_for_field ("count_picking_released " )
630+ return self ._action_move_for_field ("released " )
584631
585632 def action_move_assigned (self ):
586- return self ._action_move_for_field ("count_picking_assigned " )
633+ return self ._action_move_for_field ("assigned " )
587634
588635 def action_move_waiting (self ):
589- return self ._action_move_for_field ("count_picking_waiting " )
636+ return self ._action_move_for_field ("waiting " )
590637
591638 def action_move_late (self ):
592- return self ._action_move_for_field ("count_picking_late " )
639+ return self ._action_move_for_field ("late " )
593640
594641 def action_move_priority (self ):
595- return self ._action_move_for_field ("count_picking_priority " )
642+ return self ._action_move_for_field ("priority " )
596643
597644 def action_move_done (self ):
598- return self ._action_move_for_field ("count_picking_done " )
645+ return self ._action_move_for_field ("done " )
599646
600647 def _action_move_for_field (self , field_domain , context = None ):
601648 domain = self ._field_picking_domains ()[field_domain ]
602649 domain += [("release_channel_id" , "in" , self .ids )]
603650 pickings = self .env ["stock.picking" ].search (domain )
604- field_descr = self ._fields [field_domain ]._description_string (self .env )
651+ field = self ._get_compute_field_name ("count" , "picking" , field_domain )
652+ field_descr = self ._fields [field ]._description_string (self .env )
605653 xmlid = "stock_available_to_promise_release.stock_move_release_action"
606654 action = self .env ["ir.actions.act_window" ]._for_xml_id (xmlid )
607655 action ["display_name" ] = "{} ({})" .format (
@@ -663,7 +711,7 @@ def _get_next_pickings_max(self):
663711 if not self .max_auto_release :
664712 raise exceptions .UserError (_ ("No Max transfers to release is configured." ))
665713
666- waiting_domain = self ._field_picking_domains ()["count_picking_waiting " ]
714+ waiting_domain = self ._field_picking_domains ()["waiting " ]
667715 waiting_domain += [("release_channel_id" , "=" , self .id )]
668716 released_in_progress = self .env ["stock.picking" ].search_count (waiting_domain )
669717
@@ -675,7 +723,7 @@ def _get_next_pickings_max(self):
675723 " progress is already at the maximum."
676724 )
677725 )
678- domain = self ._field_picking_domains ()["count_picking_release_ready " ]
726+ domain = self ._field_picking_domains ()["release_ready " ]
679727 domain += [("release_channel_id" , "=" , self .id )]
680728 next_pickings = self .env ["stock.picking" ].search (domain )
681729 # We have to use a python sort and not a order + limit on the search
@@ -685,7 +733,7 @@ def _get_next_pickings_max(self):
685733 return next_pickings .sorted (self ._pickings_sort_key )[:release_limit ]
686734
687735 def _get_next_pickings_group_commercial_partner (self ):
688- domain = self ._field_picking_domains ()["count_picking_release_ready " ]
736+ domain = self ._field_picking_domains ()["release_ready " ]
689737 domain += [("release_channel_id" , "=" , self .id )]
690738 # We have to use a python sort and not a order + limit on the search
691739 # because "date_priority" is computed and not stored. If needed, we
0 commit comments