Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[16.0][IMP] ddmrp: Refactoring of incomings calculation #371

Open
wants to merge 2 commits into
base: 16.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
234 changes: 151 additions & 83 deletions ddmrp/models/stock_buffer.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import operator as py_operator
import threading
from collections import defaultdict
from datetime import datetime, timedelta
from datetime import datetime, time, timedelta
from math import pi

from odoo import _, api, exceptions, fields, models
Expand Down Expand Up @@ -1189,12 +1189,56 @@
string="Incoming (Outside DLT)",
readonly=True,
)
stock_moves_inside_dlt_ids = fields.Many2many(
comodel_name="stock.move",
relation="stock_moves_inside_dlt",
)
stock_moves_inside_dlt_qty = fields.Float(
string="Stock Moves Qty (Within DLT)",
readonly=True,
)
stock_moves_outside_dlt_ids = fields.Many2many(
comodel_name="stock.move",
relation="stock_moves_outside_dlt",
)
stock_moves_outside_dlt_qty = fields.Float(
string="Stock Moves Qty (Outside DLT)",
readonly=True,
)
rfq_inside_dlt_ids = fields.Many2many(
comodel_name="purchase.order.line",
relation="rfq_inside_dlt",
)
rfq_inside_dlt_qty = fields.Float(
string="RFQ Qty (Inside DLT)",
readonly=True,
help="Request for Quotation total quantity that is planned inside of "
"the DLT horizon.",
)
rfq_outside_dlt_ids = fields.Many2many(
comodel_name="purchase.order.line",
relation="rfq_outside_dlt",
)
rfq_outside_dlt_qty = fields.Float(
string="RFQ Qty (Outside DLT)",
readonly=True,
help="Request for Quotation total quantity that is planned outside of "
"the DLT horizon.",
)
stock_moves_inside_dlt_qty_to_subtract = fields.Float(
string="Stock Moves Qty (Within DLT) To Subtract",
help="If a Stock Move contains quantities of open RFQs and confirmed "
"Purchase Orders, we will maintain the Stock Move information but "
"save in this field the units that need to be subtracted.",
readonly=True,
)
stock_moves_outside_dlt_qty_to_subtract = fields.Float(
string="Stock Moves Qty (Outside DLT) To Subtract",
help="If a Stock Move contains quantities of open RFQs and confirmed "
"Purchase Orders, we will maintain the Stock Move information but "
"save in this field the units that need to be subtracted.",
readonly=True,
)
net_flow_position = fields.Float(
digits="Product Unit of Measure",
readonly=True,
Expand Down Expand Up @@ -1382,17 +1426,6 @@
"domain": str([("id", "in", lines.ids)]),
}

def open_moves(self):
self.ensure_one()
# Utility method used to add an "Open Moves" button in the buffer
# planning view
domain = self._search_open_stock_moves_domain()
moves = self.env["stock.move"].search(domain)
moves = moves.filtered(
lambda move: move.location_dest_id.is_sublocation_of(self.location_id)
)
return self._stock_move_tree_view(moves)

def _get_horizon_adu_past_demand(self):
return self.adu_calculation_method.horizon_past or 0

Expand Down Expand Up @@ -1571,7 +1604,13 @@
# The safety factor allows to control the date limit
factor = self.warehouse_id.nfp_incoming_safety_factor or 1
horizon = int(self.dlt) * factor
return self._get_date_planned(force_lt=horizon)
datetime_planned = self._get_date_planned(force_lt=horizon)
target_time = time(12, 0, 0)
if datetime_planned.time() < target_time:
datetime_planned = datetime_planned.replace(
hour=12, minute=0, second=0, microsecond=0
)
return datetime_planned

def _search_stock_moves_incoming_domain(self, outside_dlt=False):
date_to = self._get_incoming_supply_date_limit()
Expand All @@ -1586,22 +1625,41 @@
("date", date_operator, date_to),
]

def _get_all_upstream_moves(self, moves):
upstream_moves = moves
while upstream_moves.mapped("move_orig_ids"):
upstream_moves = upstream_moves.mapped("move_orig_ids")
return upstream_moves.filtered(lambda x: x.state != "cancel")

def _search_stock_moves_incoming(self, outside_dlt=False):
domain = self._search_stock_moves_incoming_domain(outside_dlt=outside_dlt)
moves = self.env["stock.move"].search(domain)
moves = moves.filtered(
lambda move: not move.location_id.is_sublocation_of(self.location_id)
and move.location_dest_id.is_sublocation_of(self.location_id)
)
if self.warehouse_id.reception_steps != "one_step":
attr = f'stock_moves_{"outside_" if outside_dlt else "inside_"}dlt_qty_to_subtract'
upstream_moves = self._get_all_upstream_moves(moves)
qty_to_subtract = sum(moves.mapped("product_uom_qty")) - sum(
upstream_moves.mapped("product_uom_qty")
)
setattr(self, attr, qty_to_subtract)
return moves

def _get_incoming_by_days(self):
self.ensure_one()
moves = self._search_stock_moves_incoming()
incoming_by_days = {}
pols = self.rfq_inside_dlt_ids
pol_dates = [dt.date() for dt in pols.mapped("date_planned")]
moves = self.stock_moves_inside_dlt_ids
move_dates = [dt.date() for dt in moves.mapped("date")]
for move_date in move_dates:
incoming_by_days[move_date] = 0.0
dates = list(set(pol_dates) | set(move_dates))
for date in dates:
incoming_by_days[date] = 0.0
for pol in pols:
date = pol.date_planned.date()
incoming_by_days[date] += pol.product_qty

Check warning on line 1662 in ddmrp/models/stock_buffer.py

View check run for this annotation

Codecov / codecov/patch

ddmrp/models/stock_buffer.py#L1661-L1662

Added lines #L1661 - L1662 were not covered by tests
for move in moves:
date = move.date.date()
incoming_by_days[date] += move.product_qty
Expand Down Expand Up @@ -1682,24 +1740,49 @@
rec.qualified_demand_mrp_move_ids = mrp_moves
return True

def _calc_incoming_dlt_qty(self):
for rec in self:
moves = self._search_stock_moves_incoming()
rec.incoming_dlt_qty = sum(moves.mapped("product_qty"))
outside_dlt_moves = self._search_stock_moves_incoming(outside_dlt=True)
rec.incoming_outside_dlt_qty = sum(outside_dlt_moves.mapped("product_qty"))
if rec.item_type == "purchased":
cut_date = rec._get_incoming_supply_date_limit()
# FIXME: filter using order_id.state while
# https://github.com/odoo/odoo/pull/58966 is not merged.
# Can be changed in v14.
pols = rec.purchase_line_ids.filtered(
lambda l: l.date_planned > fields.Datetime.to_datetime(cut_date)
and l.order_id.state in ("draft", "sent")
def _get_rfq_dlt(self, outside_dlt=False):
self.ensure_one()
if self.item_type == "purchased":
cut_date = self._get_incoming_supply_date_limit()
if not outside_dlt:
pols = self.purchase_line_ids.filtered(
lambda l: l.date_planned <= fields.Datetime.to_datetime(cut_date)
and l.state in ("draft", "sent")
)
rec.rfq_outside_dlt_qty = sum(pols.mapped("product_qty"))
else:
rec.rfq_outside_dlt_qty = 0.0
pols = self.purchase_line_ids.filtered(
lambda l: l.date_planned > fields.Datetime.to_datetime(cut_date)
and l.state in ("draft", "sent")
)
return pols
return self.env["purchase.order.line"]

def _calc_incoming_dlt_qty(self):
for rec in self:
rec.rfq_inside_dlt_ids = rec._get_rfq_dlt()
rec.rfq_outside_dlt_ids = rec._get_rfq_dlt(outside_dlt=True)
rec.rfq_inside_dlt_qty = sum(rec.rfq_inside_dlt_ids.mapped("product_qty"))
rec.rfq_outside_dlt_qty = sum(rec.rfq_outside_dlt_ids.mapped("product_qty"))

rec.stock_moves_inside_dlt_ids = rec._search_stock_moves_incoming()
rec.stock_moves_outside_dlt_ids = rec._search_stock_moves_incoming(
outside_dlt=True
)
rec.stock_moves_inside_dlt_qty = (
sum(rec.stock_moves_inside_dlt_ids.mapped("product_qty"))
- rec.stock_moves_inside_dlt_qty_to_subtract
)
rec.stock_moves_outside_dlt_qty = (
sum(rec.stock_moves_outside_dlt_ids.mapped("product_qty"))
- rec.stock_moves_outside_dlt_qty_to_subtract
)

rec.incoming_dlt_qty = (
rec.rfq_inside_dlt_qty + rec.stock_moves_inside_dlt_qty
)
rec.incoming_outside_dlt_qty = (
rec.rfq_outside_dlt_qty + rec.stock_moves_outside_dlt_qty
)
rec.incoming_total_qty = rec.incoming_dlt_qty + rec.incoming_outside_dlt_qty
return True

Expand Down Expand Up @@ -1827,72 +1910,57 @@
wizard.make_procurement()
return True

def _search_purchase_order_lines_incoming(self, outside_dlt=False):
cut_date = self._get_incoming_supply_date_limit()
if not outside_dlt:
pols = self.purchase_line_ids.filtered(
lambda l: l.date_planned <= fields.Datetime.to_datetime(cut_date)
and l.order_id.state in ("draft", "sent")
)
else:
pols = self.purchase_line_ids.filtered(
lambda l: l.date_planned > fields.Datetime.to_datetime(cut_date)
and l.order_id.state in ("draft", "sent")
)
return pols
def action_view_supply_moves(self):
result = self.env["ir.actions.actions"]._for_xml_id(

Check warning on line 1914 in ddmrp/models/stock_buffer.py

View check run for this annotation

Codecov / codecov/patch

ddmrp/models/stock_buffer.py#L1914

Added line #L1914 was not covered by tests
"ddmrp.stock_move_action_ddmrp"
)
result["context"] = {}
moves = self.stock_moves_inside_dlt_ids + self.stock_moves_outside_dlt_ids
result["domain"] = [("id", "in", moves.ids)]
return result

Check warning on line 1920 in ddmrp/models/stock_buffer.py

View check run for this annotation

Codecov / codecov/patch

ddmrp/models/stock_buffer.py#L1917-L1920

Added lines #L1917 - L1920 were not covered by tests

def action_view_supply(self, outside_dlt=False):
if self.item_type == "purchased":
pols = self._search_purchase_order_lines_incoming(outside_dlt)
moves = self._search_stock_moves_incoming(outside_dlt)
while moves.mapped("move_orig_ids"):
moves = moves.mapped("move_orig_ids")
pos = pols.mapped("order_id") + moves.mapped("purchase_line_id.order_id")
result = self.env["ir.actions.actions"]._for_xml_id("purchase.purchase_rfq")
# Remove the context since the action display RFQ and not PO.
result["context"] = {}
result["domain"] = [("id", "in", pos.ids)]
elif self.item_type == "manufactured":
moves = self._search_stock_moves_incoming(outside_dlt)
mos = moves.mapped("production_id")
result = self.env["ir.actions.actions"]._for_xml_id(
"mrp.mrp_production_action"
)
result["context"] = {}
result["domain"] = [("id", "in", mos.ids)]
else:
moves = self._search_stock_moves_incoming(outside_dlt)
picks = moves.mapped("picking_id")
result = self.env["ir.actions.actions"]._for_xml_id(
"stock.action_picking_tree_all"
)
result["context"] = {}
result["domain"] = [("id", "in", picks.ids)]
def action_view_supply_moves_inside_dlt_window(self):
result = self.env["ir.actions.actions"]._for_xml_id(

Check warning on line 1923 in ddmrp/models/stock_buffer.py

View check run for this annotation

Codecov / codecov/patch

ddmrp/models/stock_buffer.py#L1923

Added line #L1923 was not covered by tests
"ddmrp.stock_move_action_ddmrp"
)
result["context"] = {}
result["domain"] = [("id", "in", self.stock_moves_inside_dlt_ids.ids)]
return result

Check warning on line 1928 in ddmrp/models/stock_buffer.py

View check run for this annotation

Codecov / codecov/patch

ddmrp/models/stock_buffer.py#L1926-L1928

Added lines #L1926 - L1928 were not covered by tests

def action_view_supply_moves_outside_dlt_window(self):
result = self.env["ir.actions.actions"]._for_xml_id(

Check warning on line 1931 in ddmrp/models/stock_buffer.py

View check run for this annotation

Codecov / codecov/patch

ddmrp/models/stock_buffer.py#L1931

Added line #L1931 was not covered by tests
"ddmrp.stock_move_action_ddmrp"
)
result["context"] = {}
result["domain"] = [("id", "in", self.stock_moves_outside_dlt_ids.ids)]

Check warning on line 1935 in ddmrp/models/stock_buffer.py

View check run for this annotation

Codecov / codecov/patch

ddmrp/models/stock_buffer.py#L1934-L1935

Added lines #L1934 - L1935 were not covered by tests
return result

def action_view_supply_inside_dlt_window(self):
return self.action_view_supply()
def action_view_supply_rfq_inside_dlt_window(self):
result = self.env["ir.actions.actions"]._for_xml_id("purchase.purchase_rfq")
result["context"] = {}
result["domain"] = [("id", "in", self.rfq_inside_dlt_ids.order_id.ids)]
return result

Check warning on line 1942 in ddmrp/models/stock_buffer.py

View check run for this annotation

Codecov / codecov/patch

ddmrp/models/stock_buffer.py#L1939-L1942

Added lines #L1939 - L1942 were not covered by tests

def action_view_supply_outside_dlt_window(self):
return self.action_view_supply(outside_dlt=True)
def action_view_supply_rfq_outside_dlt_window(self):
result = self.env["ir.actions.actions"]._for_xml_id("purchase.purchase_rfq")
result["context"] = {}
result["domain"] = [("id", "in", self.rfq_outside_dlt_ids.order_id.ids)]
return result

Check warning on line 1948 in ddmrp/models/stock_buffer.py

View check run for this annotation

Codecov / codecov/patch

ddmrp/models/stock_buffer.py#L1945-L1948

Added lines #L1945 - L1948 were not covered by tests

def action_view_qualified_demand_pickings(self):
moves = self.qualified_demand_stock_move_ids
picks = moves.mapped("picking_id")
def action_view_qualified_demand_moves(self):
result = self.env["ir.actions.actions"]._for_xml_id(
"stock.action_picking_tree_all"
"ddmrp.stock_move_action_ddmrp"
)
result["context"] = {}
result["domain"] = [("id", "in", picks.ids)]
result["domain"] = [("id", "in", self.qualified_demand_stock_move_ids.ids)]

Check warning on line 1955 in ddmrp/models/stock_buffer.py

View check run for this annotation

Codecov / codecov/patch

ddmrp/models/stock_buffer.py#L1955

Added line #L1955 was not covered by tests
return result

def action_view_qualified_demand_mrp(self):
mrp_moves = self.qualified_demand_mrp_move_ids
result = self.env["ir.actions.actions"]._for_xml_id(
"mrp_multi_level.mrp_move_action"
)
result["context"] = {}
result["domain"] = [("id", "in", mrp_moves.ids)]
result["domain"] = [("id", "in", self.qualified_demand_mrp_move_ids.ids)]

Check warning on line 1963 in ddmrp/models/stock_buffer.py

View check run for this annotation

Codecov / codecov/patch

ddmrp/models/stock_buffer.py#L1963

Added line #L1963 was not covered by tests
return result

def action_view_past_adu_direct_demand(self):
Expand Down
46 changes: 46 additions & 0 deletions ddmrp/models/stock_move.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,49 @@
buffer.cron_actions(only_nfp="out")
for buffer in in_buffers.with_context(no_ddmrp_history=True):
buffer.cron_actions(only_nfp="in")

def action_open_ddmrp_source(self):
moves = self

Check warning on line 89 in ddmrp/models/stock_move.py

View check run for this annotation

Codecov / codecov/patch

ddmrp/models/stock_move.py#L89

Added line #L89 was not covered by tests
if self.move_orig_ids:
moves = self.move_orig_ids

Check warning on line 91 in ddmrp/models/stock_move.py

View check run for this annotation

Codecov / codecov/patch

ddmrp/models/stock_move.py#L91

Added line #L91 was not covered by tests
while moves.mapped("move_orig_ids"):
moves = moves.mapped("move_orig_ids")

Check warning on line 93 in ddmrp/models/stock_move.py

View check run for this annotation

Codecov / codecov/patch

ddmrp/models/stock_move.py#L93

Added line #L93 was not covered by tests
if self.move_dest_ids:
moves = self.move_dest_ids

Check warning on line 95 in ddmrp/models/stock_move.py

View check run for this annotation

Codecov / codecov/patch

ddmrp/models/stock_move.py#L95

Added line #L95 was not covered by tests
while moves.mapped("move_dest_ids"):
moves = moves.mapped("move_dest_ids")

Check warning on line 97 in ddmrp/models/stock_move.py

View check run for this annotation

Codecov / codecov/patch

ddmrp/models/stock_move.py#L97

Added line #L97 was not covered by tests
if moves.mapped("purchase_line_id").filtered(lambda x: x.state != "cancel"):
result = self.env["ir.actions.actions"]._for_xml_id("purchase.purchase_rfq")

Check warning on line 99 in ddmrp/models/stock_move.py

View check run for this annotation

Codecov / codecov/patch

ddmrp/models/stock_move.py#L99

Added line #L99 was not covered by tests
result["domain"] = [
(
"id",
"in",
moves.filtered(lambda x: x.state != "cancel").mapped(
"purchase_line_id.order_id.id"
),
)
]
elif moves.mapped("sale_line_id"):
result = self.env["ir.actions.actions"]._for_xml_id(

Check warning on line 110 in ddmrp/models/stock_move.py

View check run for this annotation

Codecov / codecov/patch

ddmrp/models/stock_move.py#L110

Added line #L110 was not covered by tests
"sale.action_quotations"
)
result["domain"] = [("id", "in", moves.mapped("sale_line_id.order_id.id"))]

Check warning on line 113 in ddmrp/models/stock_move.py

View check run for this annotation

Codecov / codecov/patch

ddmrp/models/stock_move.py#L113

Added line #L113 was not covered by tests
elif moves.mapped("production_id"):
result = self.env["ir.actions.actions"]._for_xml_id(

Check warning on line 115 in ddmrp/models/stock_move.py

View check run for this annotation

Codecov / codecov/patch

ddmrp/models/stock_move.py#L115

Added line #L115 was not covered by tests
"mrp.mrp_production_action"
)
result["domain"] = [("id", "in", moves.mapped("production_id.id"))]

Check warning on line 118 in ddmrp/models/stock_move.py

View check run for this annotation

Codecov / codecov/patch

ddmrp/models/stock_move.py#L118

Added line #L118 was not covered by tests
elif moves.mapped("raw_material_production_id"):
result = self.env["ir.actions.actions"]._for_xml_id(

Check warning on line 120 in ddmrp/models/stock_move.py

View check run for this annotation

Codecov / codecov/patch

ddmrp/models/stock_move.py#L120

Added line #L120 was not covered by tests
"mrp.mrp_production_action"
)
result["domain"] = [

Check warning on line 123 in ddmrp/models/stock_move.py

View check run for this annotation

Codecov / codecov/patch

ddmrp/models/stock_move.py#L123

Added line #L123 was not covered by tests
("id", "in", moves.mapped("raw_material_production_id.id"))
]
else:
result = self.env["ir.actions.actions"]._for_xml_id(

Check warning on line 127 in ddmrp/models/stock_move.py

View check run for this annotation

Codecov / codecov/patch

ddmrp/models/stock_move.py#L127

Added line #L127 was not covered by tests
"stock.action_picking_tree_all"
)
result["domain"] = [("id", "in", moves.mapped("picking_id.id"))]
result["context"] = {}
return result

Check warning on line 132 in ddmrp/models/stock_move.py

View check run for this annotation

Codecov / codecov/patch

ddmrp/models/stock_move.py#L130-L132

Added lines #L130 - L132 were not covered by tests