oca-technical/odoo-bringout-oca-ddmrp-ddmrp_history/ddmrp_history/models/stock_buffer.py
2025-08-29 15:43:03 +02:00

309 lines
10 KiB
Python

# Copyright 2017-20 ForgeFlow S.L. (https://www.forgeflow.com)
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
import json
import logging
from math import pi
from odoo import _, fields, models
_logger = logging.getLogger(__name__)
try:
import numpy as np
import pandas as pd
from bokeh.embed import components
from bokeh.models import DatetimeTickFormatter, HoverTool
from bokeh.plotting import figure
except (ImportError, IOError) as err:
_logger.debug(err)
class StockBuffer(models.Model):
_inherit = "stock.buffer"
planning_history_chart = fields.Text(
string="Historical Chart",
compute="_compute_history_chart",
)
execution_history_chart = fields.Text(
string="Execution Historical Chart",
compute="_compute_execution_history_chart",
)
def _prepare_history_data(self):
self.ensure_one()
data = {
"buffer_id": self.id,
"date": fields.Datetime.now(),
"top_of_red": self.top_of_red,
"top_of_yellow": self.top_of_yellow,
"top_of_green": self.top_of_green,
"net_flow_position": self.net_flow_position,
"on_hand_position": self.product_location_qty_available_not_res,
"adu": self.adu,
}
return data
def cron_actions(self, only_nfp=False):
res = super().cron_actions(only_nfp=only_nfp)
data = self._prepare_history_data()
if not self.env.context.get("no_ddmrp_history"):
self.env["ddmrp.history"].sudo().create(data)
return res
def _compute_history_chart(self):
def stacked(df, categories):
areas = {}
last = np.zeros(len(df[categories[0]]))
for cat in categories:
_next = last + df[cat]
areas[cat] = np.hstack((last[::-1], _next))
last = _next
return areas
hex_colors = self._get_colors_hex_map(pallete="planning")
planning_colors = [
hex_colors["1_red"],
hex_colors["2_yellow"],
hex_colors["3_green"],
]
for rec in self:
history = self.env["ddmrp.history"].search(
[("buffer_id", "=", rec.id)], order="date"
)
if len(history) < 2:
rec.planning_history_chart = json.dumps(
{
"div": _("Not enough data available."),
"script": "",
}
)
continue
N = len(history)
categories = ["top_of_red", "top_of_yellow", "top_of_green"]
data = {}
dates = [r.date for r in history]
data["date"] = dates
data[categories[0]] = [r.top_of_red for r in history]
data[categories[1]] = [r.top_of_yellow - r.top_of_red for r in history]
data[categories[2]] = [r.top_of_green - r.top_of_yellow for r in history]
data["net_flow_position"] = [r.net_flow_position for r in history]
data["on_hand_position"] = [r.on_hand_position for r in history]
df = pd.DataFrame(data)
df = df.set_index(["date"])
areas = stacked(df, categories)
x2 = np.hstack((data["date"][::-1], data["date"]))
tops = [
data[categories[0]][i] + data[categories[1]][i] + data[categories[2]][i]
for i in range(N)
] + [max(data["on_hand_position"]), max(data["net_flow_position"])]
top_y = max(tops)
min_y = min(
[0, min(data["on_hand_position"]), min(data["net_flow_position"])]
)
if top_y <= min_y:
top_y = min_y + 100
p = figure(
frame_height=400,
x_range=(dates[0], dates[-1]),
y_range=(min_y, top_y),
x_axis_type="datetime",
)
p.sizing_mode = "stretch_width"
p.toolbar.logo = None
p.grid.minor_grid_line_color = "#eeeeee"
p.patches(
[x2] * len(areas),
[areas[cat] for cat in categories],
color=planning_colors,
alpha=0.8,
line_color=None,
)
date_format = (
self.env["res.lang"]._lang_get(self.env.lang or "en_US").date_format
)
p.xaxis.formatter = DatetimeTickFormatter(
hours=date_format,
days=date_format,
months=date_format,
years=date_format,
)
p.xaxis.major_label_orientation = pi / 4
p.xaxis.axis_label_text_font = "helvetica"
unit = rec.product_uom.name
hover = HoverTool(
tooltips=[("qty", "$y %s" % unit)], point_policy="follow_mouse"
)
p.add_tools(hover)
p.line(dates, data["net_flow_position"], line_width=3)
p.line(dates, data["on_hand_position"], line_width=3, line_dash="dotted")
script, div = components(p, wrap_script=False)
json_data = json.dumps(
{
"div": div,
"script": script,
}
)
rec.planning_history_chart = json_data
def _compute_execution_history_chart(self):
start_stack = 0
def stacked(df, categories):
areas = {}
last = np.zeros(len(df[categories[0]]))
last += start_stack
for cat in categories:
_next = last + df[cat]
areas[cat] = np.hstack((last[::-1], _next))
last = _next
return areas
hex_colors = self._get_colors_hex_map(pallete="execution")
execution_colors = [
hex_colors["0_dark_red"],
hex_colors["1_red"],
hex_colors["2_yellow"],
hex_colors["3_green"],
hex_colors["2_yellow"],
hex_colors["1_red"],
hex_colors["0_dark_red"],
]
history_model = self.env["ddmrp.history"]
for rec in self:
domain = [("buffer_id", "=", rec.id)]
history_oh = history_model.search(
domain, order="on_hand_position desc", limit=1
)
history_tog = history_model.search(
domain + [("top_of_green", "!=", False)],
order="top_of_green desc",
limit=1,
)
finish_stack = max(history_oh.on_hand_position, history_tog.top_of_green)
history = history_model.search(
domain, order="on_hand_position asc", limit=1
)
start_stack = history.on_hand_position
if start_stack >= 0.0:
start_stack = 0.0
history = history_model.search(domain, order="date")
if len(history) < 2:
rec.execution_history_chart = json.dumps(
{
"div": _("Not enough data available."),
"script": "",
}
)
continue
N = len(history)
categories = [
"dark_red_low",
"top_of_red_low",
"top_of_yellow_low",
"top_of_green",
"top_of_yellow",
"top_of_red",
"dark_red",
]
data = {}
dates = [r.date for r in history]
data["date"] = dates
data[categories[0]] = [(0 - start_stack) for r in history]
data[categories[1]] = [(r.top_of_red / 2) for r in history]
data[categories[2]] = [(r.top_of_red / 2) for r in history]
data[categories[3]] = [r.top_of_green - r.top_of_yellow for r in history]
data[categories[4]] = [
(r.top_of_green - r.top_of_red - (r.top_of_green - r.top_of_yellow)) / 2
for r in history
]
data[categories[5]] = [
(r.top_of_green - r.top_of_red - (r.top_of_green - r.top_of_yellow)) / 2
for r in history
]
data[categories[6]] = [
finish_stack
- r.top_of_red
- (r.top_of_green - r.top_of_yellow)
- (r.top_of_green - r.top_of_red - (r.top_of_green - r.top_of_yellow))
for r in history
]
data["on_hand_position"] = [r.on_hand_position for r in history]
df = pd.DataFrame(data)
df = df.set_index(["date"])
areas = stacked(df, categories)
x2 = np.hstack((data["date"][::-1], data["date"]))
tops = [
data[categories[0]][i]
+ data[categories[1]][i]
+ data[categories[2]][i]
+ data[categories[3]][i]
+ data[categories[4]][i]
+ data[categories[5]][i]
+ data[categories[6]][i]
for i in range(N)
]
top_y = max(tops)
p = figure(
frame_height=400,
x_range=(dates[0], dates[-1]),
y_range=(start_stack, top_y or 100),
x_axis_type="datetime",
)
p.sizing_mode = "stretch_width"
p.toolbar.logo = None
p.grid.minor_grid_line_color = "#eeeeee"
p.patches(
[x2] * len(areas),
[areas[cat] for cat in categories],
color=execution_colors,
alpha=0.8,
line_color=None,
)
date_format = (
self.env["res.lang"]._lang_get(self.env.lang or "en_US").date_format
)
p.xaxis.formatter = DatetimeTickFormatter(
hours=date_format,
days=date_format,
months=date_format,
years=date_format,
)
p.xaxis.major_label_orientation = pi / 4
unit = rec.product_uom.name
hover = HoverTool(
tooltips=[("qty", "$y %s" % unit)], point_policy="follow_mouse"
)
p.add_tools(hover)
p.line(dates, data["on_hand_position"], line_width=3, line_dash="dotted")
script, div = components(p, wrap_script=False)
json_data = json.dumps(
{
"div": div,
"script": script,
}
)
rec.execution_history_chart = json_data