Skip to content

Commit 4e56965

Browse files
committed
[18.0][MIG] sale_stock_mto_as_mts_orderpoint: Migration to 18.0
1 parent 9cf5534 commit 4e56965

File tree

14 files changed

+187
-99
lines changed

14 files changed

+187
-99
lines changed

sale_stock_mto_as_mts_orderpoint/README.rst

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,6 @@ by the stock rule linked to the standard MTO route or the MTO route is
3737
marked on the product, an orderpoint will be created on the Stock
3838
location of the warehouse, with min/max quantities of zero.
3939

40-
This allows to regenerate procurement according to the
41-
procure_recommended_qty using module
42-
stock_orderpoint_manual_procurement.
43-
4440
Finally, orderpoints with min/max quantities of zero will be archived if
4541
the MTO route is removed on the product.
4642

@@ -86,6 +82,7 @@ Contributors
8682
Trobz:
8783

8884
- Dung Tran <[email protected]>
85+
- Chau Le <[email protected]>
8986

9087
Other credits
9188
-------------

sale_stock_mto_as_mts_orderpoint/__manifest__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@
33
{
44
"name": "Sale Stock Mto As Mts Orderpoint",
55
"summary": "Materialize need from MTO route through orderpoint",
6-
"version": "14.0.1.1.1",
6+
"version": "18.0.1.0.0",
77
"development_status": "Alpha",
88
"category": "Operations/Inventory/Delivery",
99
"website": "https://github.com/OCA/stock-logistics-workflow",
1010
"author": "Camptocamp, Odoo Community Association (OCA)",
1111
"license": "AGPL-3",
1212
"application": False,
1313
"installable": True,
14-
"depends": ["sale_stock", "stock_orderpoint_manual_procurement"],
14+
"depends": ["sale_stock", "purchase_stock"],
1515
"data": [
1616
"data/stock_data.xml",
1717
"views/stock_warehouse_views.xml",
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="utf-8" ?>
22
<odoo noupdate="1">
3-
<record id="stock.route_warehouse0_mto" model="stock.location.route">
3+
<record id="stock.route_warehouse0_mto" model="stock.route">
44
<field name="active" eval="True" />
55
</record>
66
</odoo>

sale_stock_mto_as_mts_orderpoint/migrations/14.0.1.1.0/post-migrate.py

Lines changed: 0 additions & 8 deletions
This file was deleted.

sale_stock_mto_as_mts_orderpoint/models/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@
22
from . import product_product
33
from . import sale_order
44
from . import stock_move
5+
from . import stock_warehouse_orderpoint
56
from . import stock_warehouse

sale_stock_mto_as_mts_orderpoint/models/product_template.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,9 @@ def _template_is_not_mto(self):
2929
return not self._template_is_mto()
3030

3131
def _filter_mto_products(self, mto=True):
32-
mto_route = self.env.ref("stock.route_warehouse0_mto", raise_if_not_found=False)
3332
if mto:
3433
return self.filtered(lambda t: t._template_is_mto())
35-
else:
36-
return self.filtered(lambda t: t._template_is_not_mto())
34+
return self.filtered(lambda t: t._template_is_not_mto())
3735

3836
def _get_orderpoints_to_archive_domain(self):
3937
warehouses = self.env["stock.warehouse"].search([])

sale_stock_mto_as_mts_orderpoint/models/sale_order.py

Lines changed: 56 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,11 @@ def _action_launch_stock_rule(self, previous_product_uom_qty=False):
1414
return res
1515

1616
def _run_orderpoints_for_mto_products(self):
17-
orderpoints_to_procure_ids = []
1817
mto_route = self.env.ref("stock.route_warehouse0_mto", raise_if_not_found=False)
1918
if not mto_route:
2019
return
20+
21+
orderpoints_to_procure_ids = []
2122
for line in self:
2223
delivery_moves = line.move_ids.filtered(
2324
lambda m: m.picking_id.picking_type_code == "outgoing"
@@ -26,62 +27,73 @@ def _run_orderpoints_for_mto_products(self):
2627
for delivery_move in delivery_moves:
2728
if (
2829
not delivery_move.is_from_mto_route
29-
or line.product_id._variant_is_not_mto()
30+
and mto_route not in delivery_move.product_id.route_ids
3031
):
3132
continue
3233
if not delivery_move.warehouse_id.mto_as_mts:
3334
continue
3435
orderpoint = line._get_mto_orderpoint(delivery_move.product_id)
3536
if orderpoint.procure_recommended_qty:
3637
orderpoints_to_procure_ids.append(orderpoint.id)
37-
wiz = (
38-
self.env["make.procurement.orderpoint"]
39-
.with_context(
40-
**{
41-
"active_model": "stock.warehouse.orderpoint",
42-
"active_ids": orderpoints_to_procure_ids,
43-
}
44-
)
45-
.create({})
38+
39+
orderpoints_to_procure = self.env["stock.warehouse.orderpoint"].browse(
40+
orderpoints_to_procure_ids
4641
)
47-
wiz.make_procurement()
42+
43+
procurements = []
44+
45+
for item in orderpoints_to_procure:
46+
if not item.procure_recommended_qty:
47+
continue
48+
if item.product_uom.rounding <= 0:
49+
continue
50+
51+
values = item._prepare_procurement_values()
52+
values["date_planned"] = item.procure_recommended_date
53+
54+
procurements.append(
55+
self.env["procurement.group"].Procurement(
56+
item.product_id,
57+
item.procure_recommended_qty,
58+
item.product_uom,
59+
item.location_id,
60+
item.name,
61+
item.name,
62+
item.company_id,
63+
values,
64+
)
65+
)
66+
67+
if procurements:
68+
self.env["procurement.group"].run(procurements)
4869

4970
def _get_mto_orderpoint(self, product_id):
5071
self.ensure_one()
5172
warehouse = self.warehouse_id or self.order_id.warehouse_id
52-
orderpoint = (
53-
self.env["stock.warehouse.orderpoint"]
54-
.with_context(active_test=False)
55-
.search(
56-
[
57-
("product_id", "=", product_id.id),
58-
(
59-
"location_id",
60-
"=",
61-
warehouse._get_locations_for_mto_orderpoints().id,
62-
),
63-
],
64-
limit=1,
65-
)
73+
location = warehouse._get_locations_for_mto_orderpoints()
74+
orderpoint_model = self.env["stock.warehouse.orderpoint"].with_context(
75+
active_test=False
6676
)
67-
if orderpoint and not orderpoint.active:
68-
orderpoint.write(
69-
{"active": True, "product_min_qty": 0.0, "product_max_qty": 0.0}
70-
)
71-
elif not orderpoint:
72-
orderpoint = (
73-
self.env["stock.warehouse.orderpoint"]
74-
.sudo()
75-
.create(
76-
{
77-
"product_id": product_id.id,
78-
"warehouse_id": warehouse.id,
79-
"location_id": (
80-
warehouse._get_locations_for_mto_orderpoints().id
81-
),
82-
"product_min_qty": 0.0,
83-
"product_max_qty": 0.0,
84-
}
77+
78+
orderpoint = orderpoint_model.search_fetch(
79+
[("product_id", "=", product_id.id), ("location_id", "=", location.id)],
80+
["active"],
81+
limit=1,
82+
)
83+
84+
if orderpoint:
85+
if not orderpoint.active:
86+
orderpoint.write(
87+
{"active": True, "product_min_qty": 0.0, "product_max_qty": 0.0}
8588
)
89+
else:
90+
orderpoint = orderpoint_model.sudo().create(
91+
{
92+
"product_id": product_id.id,
93+
"warehouse_id": warehouse.id,
94+
"location_id": location.id,
95+
"product_min_qty": 0.0,
96+
"product_max_qty": 0.0,
97+
}
8698
)
8799
return orderpoint

sale_stock_mto_as_mts_orderpoint/models/stock_move.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,6 @@ def _compute_is_from_mto_route(self):
1212
mto_route = self.env.ref("stock.route_warehouse0_mto", raise_if_not_found=False)
1313
if not mto_route:
1414
self.update({"is_from_mto_route": False})
15-
else:
16-
for move in self:
17-
move.is_from_mto_route = move.rule_id.route_id == mto_route
15+
return
16+
for move in self:
17+
move.is_from_mto_route = move.rule_id.route_id == mto_route

sale_stock_mto_as_mts_orderpoint/models/stock_warehouse.py

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,25 @@ class StockWarehouse(models.Model):
99
mto_as_mts = fields.Boolean(inverse="_inverse_mto_as_mts")
1010

1111
def _get_locations_for_mto_orderpoints(self):
12+
self.fetch(["lot_stock_id"])
1213
return self.mapped("lot_stock_id")
1314

1415
def _inverse_mto_as_mts(self):
1516
mto_route = self.env.ref("stock.route_warehouse0_mto", raise_if_not_found=False)
1617
if not mto_route:
1718
return
18-
for warehouse in self:
19-
if warehouse.mto_as_mts:
20-
wh_mto_rules = self.env["stock.rule"].search(
21-
[
22-
("route_id", "=", mto_route.id),
23-
"|",
24-
("warehouse_id", "=", warehouse.id),
25-
("picking_type_id.warehouse_id", "=", warehouse.id),
26-
]
27-
)
28-
if wh_mto_rules:
29-
wh_mto_rules.active = False
19+
20+
warehouses_with_mto = self.filtered("mto_as_mts")
21+
if not warehouses_with_mto:
22+
return
23+
24+
domain = [
25+
("route_id", "=", mto_route.id),
26+
"|",
27+
("warehouse_id", "in", warehouses_with_mto.ids),
28+
("picking_type_id.warehouse_id", "in", warehouses_with_mto.ids),
29+
]
30+
31+
wh_mto_rules = self.env["stock.rule"].search(domain)
32+
if wh_mto_rules:
33+
wh_mto_rules.write({"active": False})
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# Copyright 2016-20 ForgeFlow S.L. (https://www.forgeflow.com)
2+
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
3+
4+
5+
from odoo import api, fields, models
6+
from odoo.tools import float_compare, float_round
7+
8+
9+
class StockWarehouseOrderpoint(models.Model):
10+
_inherit = "stock.warehouse.orderpoint"
11+
12+
procure_recommended_qty = fields.Float(
13+
string="Procure Recommendation",
14+
compute="_compute_procure_recommended",
15+
digits="Product Unit of Measure",
16+
)
17+
procure_recommended_date = fields.Date(
18+
string="Recommended Request Date", compute="_compute_procure_recommended"
19+
)
20+
21+
def _get_procure_recommended_qty(self, virtual_qty, op_qtys):
22+
self.ensure_one()
23+
24+
target_qty = max(self.product_min_qty, self.product_max_qty)
25+
qty = target_qty - virtual_qty
26+
27+
if self.qty_multiple > 0:
28+
remainder = qty % self.qty_multiple
29+
if (
30+
float_compare(
31+
remainder, 0.0, precision_rounding=self.product_uom.rounding
32+
)
33+
> 0
34+
):
35+
qty += self.qty_multiple - remainder
36+
37+
if float_compare(qty, 0.0, precision_rounding=self.product_uom.rounding) <= 0:
38+
return 0.0
39+
40+
qty -= op_qtys.get(self.id, 0.0)
41+
qty_rounded = float_round(qty, precision_rounding=self.product_uom.rounding)
42+
43+
return qty_rounded if qty_rounded > 0 else 0.0
44+
45+
@api.depends("product_min_qty", "product_id", "qty_multiple")
46+
def _compute_procure_recommended(self):
47+
# '_quantity_in_progress' override in 'purchase_stock' method has not
48+
# been designed to work with NewIds (resulting in KeyError exceptions).
49+
# To circumvent this, we knowingly skip such records here.
50+
ops = self.filtered("id")
51+
# No need to call '_quantity_in_progress()' if there is no orderpoint,
52+
# this leads to a heavy request done by a call to `read_group` with
53+
# an empty domain on 'purchase.order.line' in the
54+
# 'product.product._get_quantity_in_progress()' method.
55+
op_qtys = ops._quantity_in_progress() if ops else {}
56+
57+
for op in self:
58+
if not op.id:
59+
op.update(
60+
{
61+
"procure_recommended_qty": False,
62+
"procure_recommended_date": False,
63+
}
64+
)
65+
continue
66+
67+
virtual_qty = op.with_context(
68+
location=op.location_id.id
69+
).product_id.virtual_available
70+
71+
qty = 0.0
72+
if (
73+
float_compare(
74+
virtual_qty,
75+
op.product_min_qty,
76+
precision_rounding=op.product_uom.rounding or 0.01,
77+
)
78+
< 0
79+
):
80+
qty = op._get_procure_recommended_qty(virtual_qty, op_qtys)
81+
82+
op.update(
83+
{
84+
"procure_recommended_qty": qty,
85+
"procure_recommended_date": op.lead_days_date,
86+
}
87+
)

0 commit comments

Comments
 (0)