Skip to content

Commit

Permalink
Merge PR #699 into 14.0
Browse files Browse the repository at this point in the history
Signed-off-by jbaudoux
  • Loading branch information
OCA-git-bot committed Nov 9, 2023
2 parents 8eff15f + 4fc061b commit cccfc21
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 82 deletions.
10 changes: 7 additions & 3 deletions stock_release_channel/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Stock Release Channels
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:c70f38a0e4ec0907b099eb00dd3e99976de9b9b73f0a71bd991cb4cc17c03a1b
!! source digest: sha256:2ebcba7644a7a1c9838a864f46ff6085b1575556c8511835dbded88b965bdc92
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
Expand Down Expand Up @@ -114,6 +114,7 @@ Contributors
* Sébastien Alix <[email protected]>
* Jacques-Etienne Baudoux <[email protected]>
* Laurent Mignon <[email protected]>
* Michael Tietz (MT Software) <[email protected]>

Design
~~~~~~
Expand Down Expand Up @@ -145,10 +146,13 @@ promote its widespread use.
.. |maintainer-sebalix| image:: https://github.com/sebalix.png?size=40px
:target: https://github.com/sebalix
:alt: sebalix
.. |maintainer-mt-software-de| image:: https://github.com/mt-software-de.png?size=40px
:target: https://github.com/mt-software-de
:alt: mt-software-de

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

|maintainer-sebalix|
|maintainer-sebalix| |maintainer-mt-software-de|

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

Expand Down
5 changes: 3 additions & 2 deletions stock_release_channel/__manifest__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Copyright 2020 Camptocamp
# Copyright 2023 Michael Tietz (MT Software) <[email protected]>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)

{
Expand All @@ -7,8 +8,8 @@
"version": "14.0.2.1.1",
"development_status": "Beta",
"license": "AGPL-3",
"author": "Camptocamp, ACSONE SA/NV,Odoo Community Association (OCA)",
"maintainers": ["sebalix"],
"author": "Camptocamp, ACSONE SA/NV, Odoo Community Association (OCA)",
"maintainers": ["sebalix", "mt-software-de"],
"website": "https://github.com/OCA/wms",
"depends": [
"sale_stock",
Expand Down
196 changes: 122 additions & 74 deletions stock_release_channel/models/stock_release_channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)

import logging
from collections import defaultdict
from copy import deepcopy

from pytz import timezone

Expand Down Expand Up @@ -245,8 +247,8 @@ def _get_picking_to_assign_domain(self):

def _field_picking_domains(self):
return {
"count_picking_all": [],
"count_picking_release_ready": [
"all": [],
"release_ready": [
("release_ready", "=", True),
# FIXME not TZ friendly
(
Expand All @@ -255,91 +257,135 @@ def _field_picking_domains(self):
fields.Datetime.now().replace(hour=23, minute=59),
),
],
"count_picking_released": [
"released": [
("last_release_date", "!=", False),
("state", "in", ("assigned", "waiting", "confirmed")),
],
"count_picking_assigned": [
"assigned": [
("last_release_date", "!=", False),
("state", "=", "assigned"),
],
"count_picking_waiting": [
"waiting": [
("last_release_date", "!=", False),
("state", "in", ("waiting", "confirmed")),
],
"count_picking_late": [
"late": [
("last_release_date", "!=", False),
("scheduled_date", "<", fields.Datetime.now()),
("state", "in", ("assigned", "waiting", "confirmed")),
],
"count_picking_priority": [
"priority": [
("last_release_date", "!=", False),
("priority", "=", "1"),
("state", "in", ("assigned", "waiting", "confirmed")),
],
"count_picking_done": [
"done": [
("state", "=", "done"),
("date_done", ">", fields.Datetime.now().replace(hour=0, minute=0)),
],
}

@api.model
def _get_picking_read_group_fields(self):
"Additional fields to read on read_group of stock.pickings"
return []

@api.model
def _get_picking_compute_fields(self):
"""This returns a list of tuples
the first value of the tuple represents the prefix of computed field
and the second value represents the field used to set the computed field"""
return [("count", "release_channel_id_count")]

@api.model
def _get_move_read_group_fields(self):
"Additional fields to read on read_group of stock.moves"
return []

@api.model
def _get_move_compute_fields(self):
"""This returns a list of tuples
the first value of the tuple represents the prefix of computed field
and the second value represents the field used to set the computed field"""
return [("count", "picking_id_count")]

@api.model
def _get_compute_field_name(self, prefix, name, domain_name):
return f"{prefix}_{name}_{domain_name}"

@api.model
def _get_default_aggregate_values(self):
picking_compute_fields = self._get_picking_compute_fields()
move_compute_fields = self._get_move_compute_fields()
default_values = {}
for domain_name, _d in self._field_picking_domains().items():
for prefix, _fetch in picking_compute_fields:
field = self._get_compute_field_name(prefix, "picking", domain_name)
default_values[field] = 0
for prefix, _fetch in move_compute_fields:
field = self._get_compute_field_name(prefix, "move", domain_name)
default_values[field] = 0
return default_values

# TODO maybe we have to do raw SQL to include the picking + moves counts in
# a single query
def _compute_picking_count(self):
domains = self._field_picking_domains()
picking_ids_per_field = {}
for field, domain in domains.items():
picking_channels = defaultdict(
lambda: {"channel_id": False, "matched_domains": []}
)
all_picking_ids = set()
channels_aggregate_values = defaultdict(lambda: defaultdict(lambda: 0))
picking_read_fields = self._get_picking_read_group_fields()
picking_compute_fields = self._get_picking_compute_fields()
for domain_name, domain in domains.items():
data = self.env["stock.picking"].read_group(
domain + [("release_channel_id", "in", self.ids)],
["release_channel_id", "picking_ids:array_agg(id)"],
["release_channel_id", "picking_ids:array_agg(id)"]
+ picking_read_fields,
["release_channel_id"],
)
count = {
row["release_channel_id"][0]: row["release_channel_id_count"]
for row in data
if row["release_channel_id"]
}
picking_ids_per_field.update(
{
(row["release_channel_id"][0], field): row["picking_ids"]
for row in data
if row["release_channel_id"]
}
)

for record in self:
record[field] = count.get(record.id, 0)

all_picking_ids = [
pid for picking_ids in picking_ids_per_field.values() for pid in picking_ids
]
for row in data:
channel_id = row["release_channel_id"] and row["release_channel_id"][0]
if not channel_id:
continue
picking_ids = row["picking_ids"]
all_picking_ids.update(picking_ids)
for picking_id in picking_ids:
picking_channels[picking_id]["channel_id"] = channel_id
picking_channels[picking_id]["matched_domains"].append(domain_name)

for prefix, fetch in picking_compute_fields:
field = self._get_compute_field_name(prefix, "picking", domain_name)
channels_aggregate_values[channel_id][field] = row[fetch]
move_read_fields = self._get_move_read_group_fields()
move_compute_fields = self._get_move_compute_fields()
data = self.env["stock.move"].read_group(
# TODO for now we do estimates, later we may improve the domains per
# field, but now we can run one sql query on stock.move for all fields
[("picking_id", "in", all_picking_ids), ("state", "!=", "cancel")],
["picking_id"],
[("picking_id", "in", list(all_picking_ids)), ("state", "!=", "cancel")],
["picking_id"] + move_read_fields,
["picking_id"],
)
move_count = {
row["picking_id"][0]: row["picking_id_count"]
for row in data
if row["picking_id"]
}
for field, __ in domains.items():
move_field = field.replace("picking", "move")
for record in self:
picking_ids = picking_ids_per_field.get((record.id, field), [])
move_estimate = sum(
move_count.get(picking_id, 0) for picking_id in picking_ids
)
record[move_field] = move_estimate

for row in data:
picking_id = row["picking_id"][0]
for matched_domain in picking_channels[picking_id]["matched_domains"]:
for prefix, fetch in move_compute_fields:
field = self._get_compute_field_name(prefix, "move", matched_domain)
channel_id = picking_channels[picking_id]["channel_id"]
channels_aggregate_values[channel_id][field] += row[fetch]

default_aggregate_values = self._get_default_aggregate_values()
for record in self:
record.count_picking_full_progress = (
record.count_picking_release_ready
+ record.count_picking_released
+ record.count_picking_done
)
values = deepcopy(default_aggregate_values)
values.update(channels_aggregate_values.get(record.id, {}))
for prefix, _fetch in self._get_picking_compute_fields():
values[f"{prefix}_picking_full_progress"] = (
values[f"{prefix}_picking_release_ready"]
+ values[f"{prefix}_picking_released"]
+ values[f"{prefix}_picking_done"]
)
record.write(values)

def _query_get_chain(self, pickings):
"""Get all stock.picking before an outgoing one
Expand Down Expand Up @@ -380,7 +426,7 @@ def _compute_picking_chain(self):
self.env["stock.move"].flush(["move_dest_ids", "move_orig_ids", "picking_id"])
self.env["stock.picking"].flush(["state"])
for channel in self:
domain = self._field_picking_domains()["count_picking_released"]
domain = self._field_picking_domains()["released"]
domain += [("release_channel_id", "=", channel.id)]
released = self.env["stock.picking"].search(domain)

Expand Down Expand Up @@ -408,7 +454,7 @@ def _compute_picking_chain(self):
def _compute_last_done_picking(self):
for channel in self:
# TODO we have one query per channel, could be better
domain = self._field_picking_domains()["count_picking_done"]
domain = self._field_picking_domains()["done"]
domain += [("release_channel_id", "=", channel.id)]
picking = self.env["stock.picking"].search(
domain, limit=1, order="date_done DESC"
Expand Down Expand Up @@ -535,35 +581,36 @@ def _eval_code(self, pickings):

def action_picking_all(self):
return self._action_picking_for_field(
"count_picking_all", context={"search_default_release_ready": 1}
"all", context={"search_default_release_ready": 1}
)

def action_picking_release_ready(self):
return self._action_picking_for_field("count_picking_release_ready")
return self._action_picking_for_field("release_ready")

def action_picking_released(self):
return self._action_picking_for_field("count_picking_released")
return self._action_picking_for_field("released")

def action_picking_assigned(self):
return self._action_picking_for_field("count_picking_assigned")
return self._action_picking_for_field("assigned")

def action_picking_waiting(self):
return self._action_picking_for_field("count_picking_waiting")
return self._action_picking_for_field("waiting")

def action_picking_late(self):
return self._action_picking_for_field("count_picking_late")
return self._action_picking_for_field("late")

def action_picking_priority(self):
return self._action_picking_for_field("count_picking_priority")
return self._action_picking_for_field("priority")

def action_picking_done(self):
return self._action_picking_for_field("count_picking_done")
return self._action_picking_for_field("done")

def _action_picking_for_field(self, field_domain, context=None):
domain = self._field_picking_domains()[field_domain]
domain += [("release_channel_id", "in", self.ids)]
pickings = self.env["stock.picking"].search(domain)
field_descr = self._fields[field_domain]._description_string(self.env)
field = self._get_compute_field_name("count", "picking", field_domain)
field_descr = self._fields[field]._description_string(self.env)
return self._build_action(
"stock_available_to_promise_release.stock_picking_release_action",
pickings,
Expand All @@ -573,35 +620,36 @@ def _action_picking_for_field(self, field_domain, context=None):

def action_move_all(self):
return self._action_move_for_field(
"count_picking_all", context={"search_default_release_ready": 1}
"all", context={"search_default_release_ready": 1}
)

def action_move_release_ready(self):
return self._action_move_for_field("count_picking_release_ready")
return self._action_move_for_field("release_ready")

def action_move_released(self):
return self._action_move_for_field("count_picking_released")
return self._action_move_for_field("released")

def action_move_assigned(self):
return self._action_move_for_field("count_picking_assigned")
return self._action_move_for_field("assigned")

def action_move_waiting(self):
return self._action_move_for_field("count_picking_waiting")
return self._action_move_for_field("waiting")

def action_move_late(self):
return self._action_move_for_field("count_picking_late")
return self._action_move_for_field("late")

def action_move_priority(self):
return self._action_move_for_field("count_picking_priority")
return self._action_move_for_field("priority")

def action_move_done(self):
return self._action_move_for_field("count_picking_done")
return self._action_move_for_field("done")

def _action_move_for_field(self, field_domain, context=None):
domain = self._field_picking_domains()[field_domain]
domain += [("release_channel_id", "in", self.ids)]
pickings = self.env["stock.picking"].search(domain)
field_descr = self._fields[field_domain]._description_string(self.env)
field = self._get_compute_field_name("count", "picking", field_domain)
field_descr = self._fields[field]._description_string(self.env)
xmlid = "stock_available_to_promise_release.stock_move_release_action"
action = self.env["ir.actions.act_window"]._for_xml_id(xmlid)
action["display_name"] = "{} ({})".format(
Expand Down Expand Up @@ -663,7 +711,7 @@ def _get_next_pickings_max(self):
if not self.max_auto_release:
raise exceptions.UserError(_("No Max transfers to release is configured."))

waiting_domain = self._field_picking_domains()["count_picking_waiting"]
waiting_domain = self._field_picking_domains()["waiting"]
waiting_domain += [("release_channel_id", "=", self.id)]
released_in_progress = self.env["stock.picking"].search_count(waiting_domain)

Expand All @@ -675,7 +723,7 @@ def _get_next_pickings_max(self):
" progress is already at the maximum."
)
)
domain = self._field_picking_domains()["count_picking_release_ready"]
domain = self._field_picking_domains()["release_ready"]
domain += [("release_channel_id", "=", self.id)]
next_pickings = self.env["stock.picking"].search(domain)
# We have to use a python sort and not a order + limit on the search
Expand All @@ -685,7 +733,7 @@ def _get_next_pickings_max(self):
return next_pickings.sorted(self._pickings_sort_key)[:release_limit]

def _get_next_pickings_group_commercial_partner(self):
domain = self._field_picking_domains()["count_picking_release_ready"]
domain = self._field_picking_domains()["release_ready"]
domain += [("release_channel_id", "=", self.id)]
# We have to use a python sort and not a order + limit on the search
# because "date_priority" is computed and not stored. If needed, we
Expand Down
1 change: 1 addition & 0 deletions stock_release_channel/readme/CONTRIBUTORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* Sébastien Alix <[email protected]>
* Jacques-Etienne Baudoux <[email protected]>
* Laurent Mignon <[email protected]>
* Michael Tietz (MT Software) <[email protected]>

Design
~~~~~~
Expand Down
Loading

0 comments on commit cccfc21

Please sign in to comment.