Skip to content

Commit 4fc061b

Browse files
[IMP] stock_release_channel: Make _compute_picking_count hookable
1 parent 8eff15f commit 4fc061b

5 files changed

Lines changed: 137 additions & 82 deletions

File tree

stock_release_channel/README.rst

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Stock Release Channels
77
!! This file is generated by oca-gen-addon-readme !!
88
!! changes will be overwritten. !!
99
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
10-
!! source digest: sha256:c70f38a0e4ec0907b099eb00dd3e99976de9b9b73f0a71bd991cb4cc17c03a1b
10+
!! source digest: sha256:2ebcba7644a7a1c9838a864f46ff6085b1575556c8511835dbded88b965bdc92
1111
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
1212
1313
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
@@ -114,6 +114,7 @@ Contributors
114114
* Sébastien Alix <sebastien.alix@camptocamp.com>
115115
* Jacques-Etienne Baudoux <je@bcim.be>
116116
* Laurent Mignon <laurent.mignon@acsone.eu>
117+
* Michael Tietz (MT Software) <mtietz@mt-software.de>
117118

118119
Design
119120
~~~~~~
@@ -145,10 +146,13 @@ promote its widespread use.
145146
.. |maintainer-sebalix| image:: https://github.com/sebalix.png?size=40px
146147
:target: https://github.com/sebalix
147148
:alt: sebalix
149+
.. |maintainer-mt-software-de| image:: https://github.com/mt-software-de.png?size=40px
150+
:target: https://github.com/mt-software-de
151+
:alt: mt-software-de
148152

149-
Current `maintainer <https://odoo-community.org/page/maintainer-role>`__:
153+
Current `maintainers <https://odoo-community.org/page/maintainer-role>`__:
150154

151-
|maintainer-sebalix|
155+
|maintainer-sebalix| |maintainer-mt-software-de|
152156

153157
This module is part of the `OCA/wms <https://github.com/OCA/wms/tree/14.0/stock_release_channel>`_ project on GitHub.
154158

stock_release_channel/__manifest__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# Copyright 2020 Camptocamp
2+
# Copyright 2023 Michael Tietz (MT Software) <mtietz@mt-software.de>
23
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
34

45
{
@@ -7,8 +8,8 @@
78
"version": "14.0.2.1.1",
89
"development_status": "Beta",
910
"license": "AGPL-3",
10-
"author": "Camptocamp, ACSONE SA/NV,Odoo Community Association (OCA)",
11-
"maintainers": ["sebalix"],
11+
"author": "Camptocamp, ACSONE SA/NV, Odoo Community Association (OCA)",
12+
"maintainers": ["sebalix", "mt-software-de"],
1213
"website": "https://github.com/OCA/wms",
1314
"depends": [
1415
"sale_stock",

stock_release_channel/models/stock_release_channel.py

Lines changed: 122 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
33

44
import logging
5+
from collections import defaultdict
6+
from copy import deepcopy
57

68
from 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

stock_release_channel/readme/CONTRIBUTORS.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* Sébastien Alix <sebastien.alix@camptocamp.com>
44
* Jacques-Etienne Baudoux <je@bcim.be>
55
* Laurent Mignon <laurent.mignon@acsone.eu>
6+
* Michael Tietz (MT Software) <mtietz@mt-software.de>
67

78
Design
89
~~~~~~

0 commit comments

Comments
 (0)