Skip to content

Commit

Permalink
Merge PR #320 into 14.0
Browse files Browse the repository at this point in the history
Signed-off-by LoisRForgeFlow
  • Loading branch information
OCA-git-bot committed Nov 16, 2023
2 parents bbfc44d + 2542732 commit fe901a5
Show file tree
Hide file tree
Showing 5 changed files with 204 additions and 41 deletions.
9 changes: 9 additions & 0 deletions ddmrp/data/product_adu_calculation_method_data.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,13 @@
<field name="source_future">estimates</field>
<field name="horizon_future">120</field>
</record>
<record
id="adu_calculation_method_future_120_estimates_mrp"
model="product.adu.calculation.method"
>
<field name="name">Future (Direct + Indirect)(120 days)</field>
<field name="method">future</field>
<field name="source_future">estimates_mrp</field>
<field name="horizon_future">120</field>
</record>
</odoo>
1 change: 1 addition & 0 deletions ddmrp/models/product_adu_calculation_method.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ def _get_source_selection(self):
return [
("actual", "Use actual Stock Moves"),
("estimates", "Use Demand Estimates"),
("estimates_mrp", "Use Demand Estimates + Indirect Demand from MRP Moves"),
]

name = fields.Char(string="Name", required=True)
Expand Down
118 changes: 81 additions & 37 deletions ddmrp/models/stock_buffer.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,19 +200,27 @@ def action_view_yearly_consumption(self):
action["domain"] = self._past_moves_domain(date_from, date_to, locations)
return action

def _demand_estimate_domain(self, locations, date_from=False, date_to=False):
self.ensure_one()
domain = [
("location_id", "in", locations.ids),
("product_id", "=", self.product_id.id),
]
if date_to:
domain += [("date_from", "<=", date_to)]
if date_from:
domain += [("date_to", ">=", date_from)]
return domain

def action_view_stock_demand_estimates(self):
result = self.env["ir.actions.actions"]._for_xml_id(
"stock_demand_estimate.stock_demand_estimate_action"
)
locations = self.env["stock.location"].search(
[("id", "child_of", [self.location_id.id])]
)
recs = self.env["stock.demand.estimate"].search(
[
("product_id", "=", self.product_id.id),
("location_id", "in", locations.ids),
]
)
domain = self._demand_estimate_domain(locations)
recs = self.env["stock.demand.estimate"].search(domain)
result["domain"] = [("id", "in", recs.ids)]
return result

Expand Down Expand Up @@ -1341,7 +1349,6 @@ def _search_open_stock_moves_domain(self):
"in",
["draft", "waiting", "confirmed", "partially_available", "assigned"],
),
("location_dest_id", "=", self.location_id.id),
]

@api.model
Expand Down Expand Up @@ -1369,8 +1376,11 @@ def open_moves(self):
# Utility method used to add an "Open Moves" button in the buffer
# planning view
domain = self._search_open_stock_moves_domain()
records = self.env["stock.move"].search(domain)
return self._stock_move_tree_view(records)
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 All @@ -1384,13 +1394,15 @@ def _get_dates_adu_past_demand(self, horizon):
)
return date_from, date_to

def _past_demand_estimate_domain(self, date_from, date_to, locations):
def _past_mrp_move_domain(self, date_from, date_to, locations):
self.ensure_one()
return [
("location_id", "in", locations.ids),
("product_id", "=", self.product_id.id),
("date_from", "<=", date_to),
("date_to", ">=", date_from),
("mrp_date", "<=", date_to),
("mrp_date", ">=", date_from),
("mrp_area_id.location_id", "in", locations.ids),
("mrp_type", "=", "d"),
("mrp_origin", "in", ["mrp", "mo"]),
]

def _past_moves_domain(self, date_from, date_to, locations):
Expand Down Expand Up @@ -1419,24 +1431,24 @@ def _calc_adu_past_demand(self):
.with_context(active_test=False)
.search([("id", "child_of", self.location_id.ids)])
)
if self.adu_calculation_method.source_past == "estimates":
qty = 0.0
domain = self._past_demand_estimate_domain(date_from, date_to, locations)
qty = 0.0
if self.adu_calculation_method.source_past == "estimates_mrp":
domain = self._past_mrp_move_domain(date_from, date_to, locations)
for mrp_move in self.env["mrp.move"].search(domain):
qty += -mrp_move.mrp_qty
if self.adu_calculation_method.source_past in ["estimates", "estimates_mrp"]:
domain = self._demand_estimate_domain(locations, date_from, date_to)
for estimate in self.env["stock.demand.estimate"].search(domain):
qty += estimate.get_quantity_by_date_range(
fields.Date.from_string(date_from), fields.Date.from_string(date_to)
)
return qty / horizon
elif self.adu_calculation_method.source_past == "actual":
qty = 0.0
domain = self._past_moves_domain(date_from, date_to, locations)
for group in self.env["stock.move"].read_group(
domain, ["product_id", "product_qty"], ["product_id"]
):
qty += group["product_qty"]
return qty / horizon
else:
return 0.0
return qty / horizon

def _get_horizon_adu_future_demand(self):
return self.adu_calculation_method.horizon_future or 1
Expand All @@ -1451,13 +1463,15 @@ def _get_dates_adu_future_demand(self, horizon):
)
return date_from, date_to

def _future_demand_estimate_domain(self, date_from, date_to, locations):
def _future_mrp_move_domain(self, date_from, date_to, locations):
self.ensure_one()
return [
("location_id", "in", locations.ids),
("product_id", "=", self.product_id.id),
("date_from", "<=", date_to),
("date_to", ">=", date_from),
("mrp_date", "<=", date_to),
("mrp_date", ">=", date_from),
("mrp_area_id.location_id", "in", locations.ids),
("mrp_type", "=", "d"),
("mrp_origin", "in", ["mrp", "mo"]),
]

def _future_moves_domain(self, date_from, date_to, locations):
Expand All @@ -1482,24 +1496,24 @@ def _calc_adu_future_demand(self):
locations = self.env["stock.location"].search(
[("id", "child_of", [self.location_id.id])]
)
if self.adu_calculation_method.source_future == "estimates":
qty = 0.0
domain = self._future_demand_estimate_domain(date_from, date_to, locations)
qty = 0.0
if self.adu_calculation_method.source_future == "estimates_mrp":
domain = self._future_mrp_move_domain(date_from, date_to, locations)
for mrp_move in self.env["mrp.move"].search(domain):
qty += -mrp_move.mrp_qty
if self.adu_calculation_method.source_future in ["estimates", "estimates_mrp"]:
domain = self._demand_estimate_domain(locations, date_from, date_to)
for estimate in self.env["stock.demand.estimate"].search(domain):
qty += estimate.get_quantity_by_date_range(
fields.Date.from_string(date_from), fields.Date.from_string(date_to)
)
return qty / horizon
elif self.adu_calculation_method.source_future == "actual":
qty = 0.0
domain = self._future_moves_domain(date_from, date_to, locations)
for group in self.env["stock.move"].read_group(
domain, ["product_id", "product_qty"], ["product_id"]
):
qty += group["product_qty"]
return qty / horizon
else:
return 0.0
return qty / horizon

def _calc_adu_blended(self):
self.ensure_one()
Expand Down Expand Up @@ -1871,7 +1885,7 @@ def action_view_qualified_demand_mrp(self):
result["domain"] = [("id", "in", mrp_moves.ids)]
return result

def action_view_past_adu(self):
def action_view_past_adu_direct_demand(self):
horizon = self._get_horizon_adu_past_demand()
date_from, date_to = self._get_dates_adu_past_demand(horizon)
locations = self.env["stock.location"].search(
Expand All @@ -1886,7 +1900,7 @@ def action_view_past_adu(self):
result["context"] = {}
result["domain"] = [("id", "in", moves.ids)]
else:
domain = self._past_demand_estimate_domain(date_from, date_to, locations)
domain = self._demand_estimate_domain(locations, date_from, date_to)
estimates = self.env["stock.demand.estimate"].search(domain)
result = self.env["ir.actions.actions"]._for_xml_id(
"stock_demand_estimate.stock_demand_estimate_action"
Expand All @@ -1895,7 +1909,22 @@ def action_view_past_adu(self):
result["domain"] = [("id", "in", estimates.ids)]
return result

def action_view_future_adu(self):
def action_view_past_adu_indirect_demand(self):
horizon = self._get_horizon_adu_past_demand()
date_from, date_to = self._get_dates_adu_past_demand(horizon)
locations = self.env["stock.location"].search(
[("id", "child_of", [self.location_id.id])]
)
domain = self._past_mrp_move_domain(date_from, date_to, locations)
mrp_moves = self.env["mrp.move"].search(domain)
result = self.env["ir.actions.actions"]._for_xml_id(
"mrp_multi_level.mrp_move_action"
)
result["context"] = {}
result["domain"] = [("id", "in", mrp_moves.ids)]
return result

def action_view_future_adu_direct_demand(self):
horizon = self._get_horizon_adu_future_demand()
date_from, date_to = self._get_dates_adu_future_demand(horizon)
locations = self.env["stock.location"].search(
Expand All @@ -1910,7 +1939,7 @@ def action_view_future_adu(self):
result["context"] = {}
result["domain"] = [("id", "in", moves.ids)]
else:
domain = self._future_demand_estimate_domain(date_from, date_to, locations)
domain = self._demand_estimate_domain(locations, date_from, date_to)
estimates = self.env["stock.demand.estimate"].search(domain)
result = self.env["ir.actions.actions"]._for_xml_id(
"stock_demand_estimate.stock_demand_estimate_action"
Expand All @@ -1919,6 +1948,21 @@ def action_view_future_adu(self):
result["domain"] = [("id", "in", estimates.ids)]
return result

def action_view_future_adu_indirect_demand(self):
horizon = self._get_horizon_adu_future_demand()
date_from, date_to = self._get_dates_adu_future_demand(horizon)
locations = self.env["stock.location"].search(
[("id", "child_of", [self.location_id.id])]
)
domain = self._future_mrp_move_domain(date_from, date_to, locations)
mrp_moves = self.env["mrp.move"].search(domain)
result = self.env["ir.actions.actions"]._for_xml_id(
"mrp_multi_level.mrp_move_action"
)
result["context"] = {}
result["domain"] = [("id", "in", mrp_moves.ids)]
return result

@api.model
def cron_ddmrp_adu(self, automatic=False):
"""calculate ADU for each DDMRP buffer. Called by cronjob."""
Expand Down
95 changes: 95 additions & 0 deletions ddmrp/tests/test_ddmrp.py
Original file line number Diff line number Diff line change
Expand Up @@ -1104,3 +1104,98 @@ def test_44_resupply_from_another_warehouse(self):
buffer_distributed.distributed_source_location_id,
self.warehouse.lot_stock_id,
)

def test_45_adu_calculation_blended_120_days_estimated_mrp(self):
"""Test blended ADU calculation method with direct and indirect demand."""
mrpMoveModel = self.env["mrp.move"]
mrpAreaModel = self.env["mrp.area"]
productMrpAreaModel = self.env["product.mrp.area"]
method = self.aducalcmethodModel.create(
{
"name": "Blended (120 d. estimates_mrp past, 120 d. estimates_mrp future)",
"method": "blended",
"source_past": "estimates_mrp",
"horizon_past": 120,
"factor_past": 0.5,
"source_future": "estimates_mrp",
"horizon_future": 120,
"factor_future": 0.5,
"company_id": self.main_company.id,
}
)
self.buffer_a.adu_calculation_method = method.id
mrp_area_id = mrpAreaModel.create(
{
"name": "WH/Stock",
"warehouse_id": self.warehouse.id,
"location_id": self.stock_location.id,
}
)
product_mrp_area_id = productMrpAreaModel.create(
{
"mrp_area_id": mrp_area_id.id,
"product_id": self.productA.id,
}
)
today = fields.Date.today()

# Past.
# create estimate: 120 units / 120 days = 1 unit/day
# create mrp move: 120 units / 120 days = 1 unit/day
dt = self.calendar.plan_days(-1 * 120, datetime.today())
estimate_date_from = dt.date()
estimate_date_to = (datetime.today() - timedelta(days=1)).date()
self.estimateModel.create(
{
"manual_date_from": estimate_date_from,
"manual_date_to": estimate_date_to,
"product_id": self.productA.id,
"product_uom_qty": 120,
"product_uom": self.productA.uom_id.id,
"location_id": self.stock_location.id,
}
)
mrpMoveModel.create(
{
"mrp_area_id": product_mrp_area_id.mrp_area_id.id,
"product_id": product_mrp_area_id.product_id.id,
"product_mrp_area_id": product_mrp_area_id.id,
"mrp_qty": -120,
"current_qty": 0,
"mrp_date": today - timedelta(days=5),
"current_date": None,
"mrp_type": "d",
"mrp_origin": "mrp",
}
)

# Future.
# create estimate: 120 units / 120 days = 1 unit/day
# create mrp move: 120 units / 120 days = 1 unit/day
self.estimateModel.create(
{
"manual_date_from": self.estimate_date_from,
"manual_date_to": self.estimate_date_to,
"product_id": self.productA.id,
"product_uom_qty": 120,
"product_uom": self.productA.uom_id.id,
"location_id": self.stock_location.id,
}
)
mrpMoveModel.create(
{
"mrp_area_id": product_mrp_area_id.mrp_area_id.id,
"product_id": product_mrp_area_id.product_id.id,
"product_mrp_area_id": product_mrp_area_id.id,
"mrp_qty": -120,
"current_qty": 0,
"mrp_date": today + timedelta(days=5),
"current_date": None,
"mrp_type": "d",
"mrp_origin": "mrp",
}
)

self.bufferModel.cron_ddmrp_adu()
to_assert_value = 2 * 0.5 + 2 * 0.5
self.assertEqual(self.buffer_a.adu, to_assert_value)
22 changes: 18 additions & 4 deletions ddmrp/views/stock_buffer_view.xml
Original file line number Diff line number Diff line change
Expand Up @@ -230,19 +230,33 @@
<div name="adu" class="o_row">
<field name="adu" force_save="1" />
<button
title="View ADU (Past)"
name="action_view_past_adu"
title="View ADU (Past - Direct Demand)"
name="action_view_past_adu_direct_demand"
icon="fa-search"
type="object"
attrs="{'invisible': ['|', ('adu_calculation_method_type', 'in', ['fixed', 'future']), ('adu', '=', 0)]}"
/>
<button
title="View ADU (Future)"
name="action_view_future_adu"
title="View ADU (Past - Indirect Demand)"
name="action_view_past_adu_indirect_demand"
icon="fa-search"
type="object"
attrs="{'invisible': ['|', '|', ('adu_calculation_method_type', 'in', ['fixed', 'future']), ('used_in_bom_count', '=', 0), ('adu', '=', 0)]}"
/>
<button
title="View ADU (Future - Direct Demand)"
name="action_view_future_adu_direct_demand"
icon="fa-search"
type="object"
attrs="{'invisible': ['|', ('adu_calculation_method_type', 'in', ['fixed', 'past']), ('adu', '=', 0)]}"
/>
<button
title="View ADU (Future - Indirect Demand)"
name="action_view_future_adu_indirect_demand"
icon="fa-search"
type="object"
attrs="{'invisible': ['|', '|', ('adu_calculation_method_type', 'in', ['fixed', 'past']), ('used_in_bom_count', '=', 0), ('adu', '=', 0)]}"
/>
</div>
<field
name="lead_days"
Expand Down

0 comments on commit fe901a5

Please sign in to comment.