Skip to content

Commit

Permalink
Merge PR OCA#693 into 13.0
Browse files Browse the repository at this point in the history
Signed-off-by guewen
  • Loading branch information
OCA-git-bot committed Aug 27, 2020
2 parents 4b44c89 + fb00973 commit 602b7a2
Show file tree
Hide file tree
Showing 16 changed files with 285 additions and 2 deletions.
4 changes: 2 additions & 2 deletions oca_dependencies.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
product-attribute https://github.com/grindtildeath/product-attribute 13.0-mig-product_packaging_dimension
stock-logistics-workflow https://github.com/grindtildeath/stock-logistics-workflow 13.0-mig-stock_quant_package_product_packaging
product-attribute
stock-logistics-warehouse
1 change: 1 addition & 0 deletions sale_stock_mto_as_mts_orderpoint/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
15 changes: 15 additions & 0 deletions sale_stock_mto_as_mts_orderpoint/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Copyright 2020 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
{
"name": "Sale Stock Mto As Mts Orderpoint",
"summary": "Materialize need from MTO route through orderpoint",
"version": "13.0.1.2.0",
"development_status": "Alpha",
"category": "Operations/Inventory/Delivery",
"website": "https://github.com/OCA/stock-logistics-workflow",
"author": "Camptocamp, Odoo Community Association (OCA)",
"license": "AGPL-3",
"application": False,
"installable": True,
"depends": ["sale_stock", "stock_orderpoint_manual_procurement"],
}
4 changes: 4 additions & 0 deletions sale_stock_mto_as_mts_orderpoint/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from . import product
from . import sale_order
from . import stock_move
from . import stock_warehouse
45 changes: 45 additions & 0 deletions sale_stock_mto_as_mts_orderpoint/models/product.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Copyright 2020 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
from odoo import models


class ProductTemplate(models.Model):

_inherit = "product.template"

def write(self, vals):
# Archive orderpoints when MTO route is removed
if "route_ids" not in vals:
return super().write(vals)
mto_products = self._filter_mto_products()
res = super().write(vals)
not_mto_products = self._filter_mto_products(mto=False)
# products to update are the intersection of both recordsets
products_to_update = mto_products & not_mto_products
if products_to_update:
products_to_update._archive_orderpoints_on_mto_removal()
return res

def _filter_mto_products(self, mto=True):
mto_route = self.env.ref("stock.route_warehouse0_mto", raise_if_not_found=False)
if mto:
func = lambda p: mto_route in p.route_ids # noqa
else:
func = lambda p: mto_route not in p.route_ids # noqa
return self.filtered(func)

def _get_orderpoints_to_archive_domain(self):
warehouses = self.env["stock.warehouse"].search([])
locations = warehouses._get_locations_for_mto_orderpoints()
return [
("product_id", "in", self.mapped("product_variant_ids").ids),
("product_min_qty", "=", 0.0),
("product_max_qty", "=", 0.0),
("location_id", "in", locations.ids),
]

def _archive_orderpoints_on_mto_removal(self):
domain = self._get_orderpoints_to_archive_domain()
ops = self.env["stock.warehouse.orderpoint"].search(domain)
if ops:
ops.write({"active": False})
79 changes: 79 additions & 0 deletions sale_stock_mto_as_mts_orderpoint/models/sale_order.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Copyright 2020 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
from odoo import models


class SaleOrderLine(models.Model):

_inherit = "sale.order.line"

def _action_launch_stock_rule(self, previous_product_uom_qty=False):
res = super()._action_launch_stock_rule(
previous_product_uom_qty=previous_product_uom_qty
)
self._run_orderpoints_for_mto_products()
return res

def _run_orderpoints_for_mto_products(self):
orderpoints_to_procure_ids = []
mto_route = self.env.ref("stock.route_warehouse0_mto", raise_if_not_found=False)
if not mto_route:
return
for line in self:
delivery_move = line.move_ids.filtered(
lambda m: m.picking_id.picking_type_code == "outgoing"
and m.state not in ("done", "cancel")
)
if (
not delivery_move.is_from_mto_route
or mto_route not in line.product_id.route_ids
):
continue
orderpoint = line._get_mto_orderpoint()
if orderpoint.procure_recommended_qty:
orderpoints_to_procure_ids.append(orderpoint.id)
wiz = (
self.env["make.procurement.orderpoint"]
.with_context(
**{
"active_model": "stock.warehouse.orderpoint",
"active_ids": orderpoints_to_procure_ids,
}
)
.create({})
)
wiz.make_procurement()

def _get_mto_orderpoint(self):
self.ensure_one()
warehouse = self.warehouse_id or self.order_id.warehouse_id
orderpoint = (
self.env["stock.warehouse.orderpoint"]
.with_context(active_test=False)
.search(
[
("product_id", "=", self.product_id.id),
(
"location_id",
"=",
warehouse._get_locations_for_mto_orderpoints().id,
),
],
limit=1,
)
)
if orderpoint and not orderpoint.active:
orderpoint.write(
{"active": True, "product_min_qty": 0.0, "product_max_qty": 0.0}
)
elif not orderpoint:
orderpoint = self.env["stock.warehouse.orderpoint"].create(
{
"product_id": self.product_id.id,
"warehouse_id": warehouse.id,
"location_id": warehouse._get_locations_for_mto_orderpoints().id,
"product_min_qty": 0.0,
"product_max_qty": 0.0,
}
)
return orderpoint
18 changes: 18 additions & 0 deletions sale_stock_mto_as_mts_orderpoint/models/stock_move.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Copyright 2020 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
from odoo import fields, models


class StockMove(models.Model):

_inherit = "stock.move"

is_from_mto_route = fields.Boolean(compute="_compute_is_from_mto_route")

def _compute_is_from_mto_route(self):
mto_route = self.env.ref("stock.route_warehouse0_mto", raise_if_not_found=False)
if not mto_route:
self.update({"is_from_mto_route": False})
else:
for move in self:
move.is_from_mto_route = move.rule_id.route_id == mto_route
11 changes: 11 additions & 0 deletions sale_stock_mto_as_mts_orderpoint/models/stock_warehouse.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Copyright 2020 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
from odoo import models


class StockWarehouse(models.Model):

_inherit = "stock.warehouse"

def _get_locations_for_mto_orderpoints(self):
return self.mapped("lot_stock_id")
12 changes: 12 additions & 0 deletions sale_stock_mto_as_mts_orderpoint/readme/CONFIGURATION.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
On the original MTO route, you have two options to configure the rule to pull
from Stock:

* Keep the triggering of another rule as Supply Method on stock.rule, will
ensure that the need is materialized by the procure_recommended_qty on the
orderpoint, if the ensuing procurement (purchase order/manufacturing order)
having the MTO rule as origin is canceled.

* Change the Supply Method on stock.rule to Take From Stock, will ensure that
the need is materialized by the procure_recommended_qty on the orderpoint,
if the ensuing procurement (purchase order/manufacturing order) having the
orderpoint as origin is canceled.
1 change: 1 addition & 0 deletions sale_stock_mto_as_mts_orderpoint/readme/CONTRIBUTORS.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* Akim Juillerat <[email protected]>
13 changes: 13 additions & 0 deletions sale_stock_mto_as_mts_orderpoint/readme/DESCRIPTION.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
This module aims to materialize the triggering of another stock rule through
an orderpoint in the procurement of MTO products from a sale order.

When a sales order is confirmed, if the delivery order move is generated by
the stock rule linked to the standard MTO route, and the MTO route is marked
on the product, an orderpoint will be created on the Stock location of the
warehouse, with min/max quantities of zero.

This allows to regenerate procurement according to the procure_recommended_qty
using module `stock_orderpoint_manual_procurement`.

Finally, orderpoints with min/max quantities of zero will be archived if the
MTO route is removed on the product.
1 change: 1 addition & 0 deletions sale_stock_mto_as_mts_orderpoint/readme/ROADMAP.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* Do not rely on original MTO record and have something configurable instead.
1 change: 1 addition & 0 deletions sale_stock_mto_as_mts_orderpoint/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import test_sale_stock_mto_as_mts_orderpoint
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Copyright 2020 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
from odoo.tests.common import Form, SavepointCase


class TestSaleStockMtoAsMtsOrderpoint(SavepointCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True))
ref = cls.env.ref
cls.partner = ref("base.res_partner_2")
cls.product = cls.env["product.product"].create(
{"name": "Test MTO", "type": "product"}
)
cls.vendor_partner = ref("base.res_partner_12")
cls.env["product.supplierinfo"].create(
{
"name": cls.vendor_partner.id,
"product_tmpl_id": cls.product.product_tmpl_id.id,
"min_qty": 1.0,
"price": 1.0,
}
)

cls.warehouse = ref("stock.warehouse0")

cls.mto_route = ref("stock.route_warehouse0_mto")
cls.buy_route = ref("purchase_stock.route_warehouse0_buy")
cls.product.write({"route_ids": [(6, 0, [cls.mto_route.id, cls.buy_route.id])]})

@classmethod
def _create_sale_order(cls):
sale_form = Form(cls.env["sale.order"])
sale_form.partner_id = cls.partner
sale_form.warehouse_id = cls.warehouse
with sale_form.order_line.new() as line_form:
line_form.product_id = cls.product
line_form.product_uom_qty = 1
return sale_form.save()

def test_mto_as_mts_orderpoint(self):
order = self._create_sale_order()
orderpoint = self.env["stock.warehouse.orderpoint"].search(
[("product_id", "=", self.product.id)]
)
self.assertFalse(orderpoint)
order.action_confirm()
orderpoint = self.env["stock.warehouse.orderpoint"].search(
[("product_id", "=", self.product.id)]
)
self.assertEqual(
orderpoint.location_id, self.warehouse._get_locations_for_mto_orderpoints(),
)
self.assertAlmostEqual(orderpoint.product_min_qty, 0.0)
self.assertAlmostEqual(orderpoint.product_max_qty, 0.0)
self.product.write({"route_ids": [(5, 0, 0)]})
orderpoint = self.env["stock.warehouse.orderpoint"].search(
[("product_id", "=", self.product.id)]
)
self.assertFalse(orderpoint)
orderpoint = (
self.env["stock.warehouse.orderpoint"]
.with_context(active_test=False)
.search([("product_id", "=", self.product.id)])
)
self.assertTrue(orderpoint)

def test_cancel_sale_order_orderpoint(self):
order = self._create_sale_order()
order.action_confirm()
order.action_cancel()
order.action_draft()
order.action_confirm()
self.assertEqual(order.state, "sale")
6 changes: 6 additions & 0 deletions setup/sale_stock_mto_as_mts_orderpoint/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import setuptools

setuptools.setup(
setup_requires=['setuptools-odoo'],
odoo_addon=True,
)

0 comments on commit 602b7a2

Please sign in to comment.