Skip to content

Commit

Permalink
[IMP] ddmrp: Reformulate incoming quantities calculation
Browse files Browse the repository at this point in the history
Split incoming quantities between inside or outside DLT.
Make it more heritable, to facilitate consideration of other data sources.
  • Loading branch information
BernatPForgeFlow committed Sep 25, 2023
1 parent 16e18ff commit 8d0a475
Show file tree
Hide file tree
Showing 3 changed files with 289 additions and 28 deletions.
85 changes: 67 additions & 18 deletions ddmrp/models/stock_buffer.py
Original file line number Diff line number Diff line change
Expand Up @@ -1168,6 +1168,20 @@ def _compute_product_vendor_code(self):
string="Incoming (Outside DLT)",
readonly=True,
)
stock_moves_inside_dlt_qty = fields.Float(
string="Stock Moves Qty (Within DLT)",
readonly=True,
)
stock_moves_outside_dlt_qty = fields.Float(
string="Stock Moves Qty (Outside DLT)",
readonly=True,
)
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_qty = fields.Float(
string="RFQ Qty (Outside DLT)",
readonly=True,
Expand Down Expand Up @@ -1659,24 +1673,42 @@ def _calc_qualified_demand(self, current_date=False):
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_qty(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 sum(pols.mapped("product_qty"))
return 0

def _get_stock_moves_dlt_qty(self, outside_dlt=False):
self.ensure_one()
moves = self._search_stock_moves_incoming(outside_dlt)
return sum(moves.mapped("product_qty"))

def _calc_incoming_dlt_qty(self):
for rec in self:
rec.stock_moves_inside_dlt_qty = rec._get_stock_moves_dlt_qty()
rec.stock_moves_outside_dlt_qty = rec._get_stock_moves_dlt_qty(
outside_dlt=True
)
rec.rfq_inside_dlt_qty = rec._get_rfq_dlt_qty()
rec.rfq_outside_dlt_qty = rec._get_rfq_dlt_qty(outside_dlt=True)
rec.incoming_dlt_qty = (
rec.stock_moves_inside_dlt_qty + rec.rfq_inside_dlt_qty
)
rec.incoming_outside_dlt_qty = (
rec.stock_moves_outside_dlt_qty + rec.rfq_outside_dlt_qty
)
return True

def _calc_net_flow_position(self):
Expand Down Expand Up @@ -1819,17 +1851,18 @@ def _search_purchase_order_lines_incoming(self, outside_dlt=False):

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")
pos = 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)
while moves.mapped("move_orig_ids"):
moves = moves.mapped("move_orig_ids")
mos = moves.mapped("production_id")
result = self.env["ir.actions.actions"]._for_xml_id(
"mrp.mrp_production_action"
Expand All @@ -1852,6 +1885,22 @@ def action_view_supply_inside_dlt_window(self):
def action_view_supply_outside_dlt_window(self):
return self.action_view_supply(outside_dlt=True)

def action_view_rfq_inside_dlt_window(self):
pols = self._search_purchase_order_lines_incoming()
pos = pols.mapped("order_id")
result = self.env["ir.actions.actions"]._for_xml_id("purchase.purchase_rfq")
result["context"] = {}
result["domain"] = [("id", "in", pos.ids)]
return result

def action_view_rfq_outside_dlt_window(self):
pols = self._search_purchase_order_lines_incoming(outside_dlt=True)
pos = pols.mapped("order_id")
result = self.env["ir.actions.actions"]._for_xml_id("purchase.purchase_rfq")
result["context"] = {}
result["domain"] = [("id", "in", pos.ids)]
return result

def action_view_qualified_demand_pickings(self):
moves = self.qualified_demand_stock_move_ids
picks = moves.mapped("picking_id")
Expand Down
162 changes: 162 additions & 0 deletions ddmrp/tests/test_ddmrp.py
Original file line number Diff line number Diff line change
Expand Up @@ -1104,3 +1104,165 @@ def test_44_resupply_from_another_warehouse(self):
buffer_distributed.distributed_source_location_id,
self.warehouse.lot_stock_id,
)

def test_45_action_view_supply_buffer_purchase(self):
"""
Verify that the view incoming quantities action for a purchased buffer
displays the correct results.
"""
self.buffer_purchase.auto_procure = True
self.buffer_purchase.auto_procure_option = "standard"
self.buffer_purchase.cron_actions()
pol = self.pol_model.search([("product_id", "=", self.product_purchased.id)])
# Check that RFQs are correctly computed
po_inside_dlt_ids = self.buffer_purchase.action_view_rfq_inside_dlt_window()[
"domain"
][0][2]
po_outside_dlt_ids = self.buffer_purchase.action_view_rfq_outside_dlt_window()[
"domain"
][0][2]
self.assertEqual(po_inside_dlt_ids, pol.order_id.ids)
self.assertEqual(len(po_outside_dlt_ids), 0)
pol.date_planned += timedelta(days=1)
po_inside_dlt_ids = self.buffer_purchase.action_view_rfq_inside_dlt_window()[
"domain"
][0][2]
po_outside_dlt_ids = self.buffer_purchase.action_view_rfq_outside_dlt_window()[
"domain"
][0][2]
self.assertEqual(len(po_inside_dlt_ids), 0)
self.assertEqual(po_outside_dlt_ids, pol.order_id.ids)
# Check that incoming quantities are correctly computed
pol.order_id.button_confirm()
po_inside_dlt_ids = self.buffer_purchase.action_view_supply(outside_dlt=False)[
"domain"
][0][2]
po_outside_dlt_ids = self.buffer_purchase.action_view_supply(outside_dlt=True)[
"domain"
][0][2]
self.assertEqual(len(po_inside_dlt_ids), 0)
self.assertEqual(po_outside_dlt_ids, pol.order_id.ids)
pol.mapped("move_ids.picking_id").scheduled_date -= timedelta(days=1)
po_inside_dlt_ids = self.buffer_purchase.action_view_supply(outside_dlt=False)[
"domain"
][0][2]
po_outside_dlt_ids = self.buffer_purchase.action_view_supply(outside_dlt=True)[
"domain"
][0][2]
self.assertEqual(po_inside_dlt_ids, pol.order_id.ids)
self.assertEqual(len(po_outside_dlt_ids), 0)

def test_46_action_view_supply_buffer_manufacture(self):
"""
Verify that the view incoming quantities action for a manufactured buffer
displays the correct results.
"""
self.quant.quantity = 0
self.buffer_a.buffer_profile_id = self.buffer_profile_mmm.id
self.buffer_a.auto_procure = True
self.buffer_a.auto_procure_option = "standard"
self.buffer_a.cron_actions()
mo = self.buffer_a.mrp_production_ids.filtered(
lambda x: x.product_id == self.productA
)
# Check that MOs are correctly computed
mo_inside_dlt_ids = self.buffer_a.action_view_supply(outside_dlt=False)[
"domain"
][0][2]
mo_outside_dlt_ids = self.buffer_a.action_view_supply(outside_dlt=True)[
"domain"
][0][2]
self.assertEqual(mo_inside_dlt_ids, mo.ids)
self.assertEqual(len(mo_outside_dlt_ids), 0)
mo.date_planned_finished += timedelta(days=1)
mo_inside_dlt_ids = self.buffer_a.action_view_supply(outside_dlt=False)[
"domain"
][0][2]
mo_outside_dlt_ids = self.buffer_a.action_view_supply(outside_dlt=True)[
"domain"
][0][2]
self.assertEqual(len(mo_inside_dlt_ids), 0)
self.assertEqual(mo_outside_dlt_ids, mo.ids)

def test_47_action_view_supply_buffer_purchase_3_steps(self):
"""
Verify that the view incoming quantities action for a purchased buffer displays
the correct results with a 3-step configuration.
"""
self.warehouse.reception_steps = "three_steps"
self.buffer_purchase.auto_procure = True
self.buffer_purchase.auto_procure_option = "standard"
self.buffer_purchase.cron_actions()
pol = self.pol_model.search([("product_id", "=", self.product_purchased.id)])
# Check that RFQs are correctly computed
po_inside_dlt_ids = self.buffer_purchase.action_view_rfq_inside_dlt_window()[
"domain"
][0][2]
po_outside_dlt_ids = self.buffer_purchase.action_view_rfq_outside_dlt_window()[
"domain"
][0][2]
self.assertEqual(po_inside_dlt_ids, pol.order_id.ids)
self.assertEqual(len(po_outside_dlt_ids), 0)
pol.date_planned += timedelta(days=1)
po_inside_dlt_ids = self.buffer_purchase.action_view_rfq_inside_dlt_window()[
"domain"
][0][2]
po_outside_dlt_ids = self.buffer_purchase.action_view_rfq_outside_dlt_window()[
"domain"
][0][2]
self.assertEqual(len(po_inside_dlt_ids), 0)
self.assertEqual(po_outside_dlt_ids, pol.order_id.ids)
# Check that incoming quantities are correctly computed
pol.order_id.button_confirm()
po_inside_dlt_ids = self.buffer_purchase.action_view_supply(outside_dlt=False)[
"domain"
][0][2]
po_outside_dlt_ids = self.buffer_purchase.action_view_supply(outside_dlt=True)[
"domain"
][0][2]
self.assertEqual(len(po_inside_dlt_ids), 0)
self.assertEqual(po_outside_dlt_ids, pol.order_id.ids)
moves = pol.mapped("move_ids")
while moves.mapped("move_dest_ids"):
moves = moves.mapped("move_dest_ids")
moves.mapped("picking_id").scheduled_date -= timedelta(days=1)
po_inside_dlt_ids = self.buffer_purchase.action_view_supply(outside_dlt=False)[
"domain"
][0][2]
po_outside_dlt_ids = self.buffer_purchase.action_view_supply(outside_dlt=True)[
"domain"
][0][2]
self.assertEqual(po_inside_dlt_ids, pol.order_id.ids)
self.assertEqual(len(po_outside_dlt_ids), 0)

def test_48_action_view_supply_buffer_manufacture_3_steps(self):
"""
Verify that the view incoming quantities action for a manufactured buffer displays
the correct results with a 3-step configuration.
"""
self.warehouse.manufacture_steps = "pbm_sam"
self.quant.quantity = 0
self.buffer_a.buffer_profile_id = self.buffer_profile_mmm.id
self.buffer_a.auto_procure = True
self.buffer_a.auto_procure_option = "standard"
self.buffer_a.cron_actions()
mo = self.env["mrp.production"].search([("product_id", "=", self.productA.id)])
# Check that MOs are correctly computed
mo_inside_dlt_ids = self.buffer_a.action_view_supply(outside_dlt=False)[
"domain"
][0][2]
mo_outside_dlt_ids = self.buffer_a.action_view_supply(outside_dlt=True)[
"domain"
][0][2]
self.assertEqual(mo_inside_dlt_ids, mo.ids)
self.assertEqual(len(mo_outside_dlt_ids), 0)
move = self.buffer_a._search_stock_moves_incoming()
move.picking_id.scheduled_date += timedelta(days=1)
mo_inside_dlt_ids = self.buffer_a.action_view_supply(outside_dlt=False)[
"domain"
][0][2]
mo_outside_dlt_ids = self.buffer_a.action_view_supply(outside_dlt=True)[
"domain"
][0][2]
self.assertEqual(len(mo_inside_dlt_ids), 0)
self.assertEqual(mo_outside_dlt_ids, mo.ids)
70 changes: 60 additions & 10 deletions ddmrp/views/stock_buffer_view.xml
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,6 @@
string="Incoming Outside DLT"
optional="hide"
/>
<field
name="rfq_outside_dlt_qty"
string="RFQ Qty Outside DLT"
optional="hide"
/>
<button
title="Open Non-completed Moves"
name="open_moves"
Expand All @@ -74,10 +69,28 @@
name="action_view_supply_outside_dlt_window"
icon="fa-warning"
type="object"
attrs="{'invisible':[('incoming_outside_dlt_qty', '=', 0), ('rfq_outside_dlt_qty', '=', 0)]}"
attrs="{'invisible':[('incoming_outside_dlt_qty', '=', 0)]}"
/>
<field name="rfq_inside_dlt_qty" invisible="1" />
<field name="rfq_outside_dlt_qty" invisible="1" />
<button
title="No stock available on source location for distributed buffer"
title="Some RFQ quantities are inside the DLT Horizon and may require confirming.
Press this button to display the involved RFQs"
name="action_view_rfq_inside_dlt_window"
icon="fa-warning"
type="object"
attrs="{'invisible':[('rfq_inside_dlt_qty', '=', 0)]}"
/>
<button
title="Some RFQ quantities are outside of the DLT Horizon and may require rescheduling.
Press this button to display the involved RFQs"
name="action_view_rfq_outside_dlt_window"
icon="fa-warning"
type="object"
attrs="{'invisible':[('rfq_outside_dlt_qty', '=', 0)]}"
/>
<button
title="No stock available in source location for distributed buffer"
name="action_dummy"
icon="fa-warning"
type="object"
Expand Down Expand Up @@ -382,24 +395,61 @@
<label for="incoming_dlt_qty" />
<div name="incoming_dlt_qty" class="o_row">
<field name="incoming_dlt_qty" />
<field
name="stock_moves_inside_dlt_qty"
invisible="1"
/>
<button
title="View Incoming (Within DLT)"
name="action_view_supply_inside_dlt_window"
icon="fa-search"
type="object"
attrs="{'invisible': [('incoming_dlt_qty', '=', 0)]}"
attrs="{'invisible': [('stock_moves_inside_dlt_qty', '=', 0)]}"
/>
<field name="rfq_outside_dlt_qty" invisible="1" />
<field name="rfq_inside_dlt_qty" invisible="1" />
<div
class="o_row"
attrs="{'invisible':[('incoming_outside_dlt_qty', '=', 0), ('rfq_outside_dlt_qty', '=', 0)]}"
attrs="{'invisible':[('rfq_inside_dlt_qty', '=', 0)]}"
>
<button
title="Some RFQ quantities are inside the DLT Horizon and may require confirming.
Press this button to display the involved RFQs"
name="action_view_rfq_inside_dlt_window"
icon="fa-warning"
type="object"
/>
</div>
<field
name="incoming_outside_dlt_qty"
invisible="1"
/>
<div
class="o_row"
attrs="{'invisible':[('incoming_outside_dlt_qty', '=', 0)]}"
>
<field
name="stock_moves_outside_dlt_qty"
invisible="1"
/>
<button
title="Some incoming quantities are outside of the DLT Horizon and may require rescheduling.
Press this button to display the involved supply orders"
name="action_view_supply_outside_dlt_window"
icon="fa-warning"
type="object"
attrs="{'invisible':[('stock_moves_outside_dlt_qty', '=', 0)]}"
/>
<field
name="rfq_outside_dlt_qty"
invisible="1"
/>
<button
title="Some RFQ quantities are outside of the DLT Horizon and may require rescheduling.
Press this button to display the involved RFQs"
name="action_view_rfq_outside_dlt_window"
icon="fa-warning"
type="object"
attrs="{'invisible':[('rfq_outside_dlt_qty', '=', 0)]}"
/>
(Outside DLT:
<field
Expand Down

0 comments on commit 8d0a475

Please sign in to comment.