From 4c6119b7b7d2e6c2baa2eef9c253335b2d57b5db Mon Sep 17 00:00:00 2001 From: Tran Anh Tuan Date: Tue, 16 Apr 2024 14:35:47 +0700 Subject: [PATCH] [IMP] stock_orderpoint_mto_as_mts --- stock_orderpoint_mto_as_mts/README.rst | 3 +- stock_orderpoint_mto_as_mts/__manifest__.py | 9 +- .../data/stock_data.xml | 8 -- .../models/__init__.py | 4 +- stock_orderpoint_mto_as_mts/models/product.py | 45 ------- .../models/product_product.py | 127 ++++++++++++++++++ .../models/sale_order.py | 86 ------------ .../models/stock_move.py | 18 --- .../models/stock_warehouse.py | 8 +- .../readme/CONTRIBUTORS.rst | 1 + .../static/description/index.html | 16 +-- stock_orderpoint_mto_as_mts/tests/__init__.py | 2 +- .../test_sale_stock_mto_as_mts_orderpoint.py | 103 -------------- .../tests/test_stock_orderpoint_mto_as_mts.py | 54 ++++++++ .../views/stock_warehouse_views.xml | 14 ++ 15 files changed, 220 insertions(+), 278 deletions(-) delete mode 100644 stock_orderpoint_mto_as_mts/data/stock_data.xml delete mode 100644 stock_orderpoint_mto_as_mts/models/product.py create mode 100644 stock_orderpoint_mto_as_mts/models/product_product.py delete mode 100644 stock_orderpoint_mto_as_mts/models/sale_order.py delete mode 100644 stock_orderpoint_mto_as_mts/models/stock_move.py delete mode 100644 stock_orderpoint_mto_as_mts/tests/test_sale_stock_mto_as_mts_orderpoint.py create mode 100644 stock_orderpoint_mto_as_mts/tests/test_stock_orderpoint_mto_as_mts.py create mode 100644 stock_orderpoint_mto_as_mts/views/stock_warehouse_views.xml diff --git a/stock_orderpoint_mto_as_mts/README.rst b/stock_orderpoint_mto_as_mts/README.rst index 5495717a..e4f8c93a 100644 --- a/stock_orderpoint_mto_as_mts/README.rst +++ b/stock_orderpoint_mto_as_mts/README.rst @@ -7,7 +7,7 @@ Sale Stock Mto As Mts Orderpoint !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:9d20edbec95507caa98c58ff6804d63697bb6169bf0b50f59bffba1a8b383b0b + !! source digest: sha256:c8b1ce07ee2789445838ad44716c9e14a30bc2f72ed5b4cf970336217b7fdf4e !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png @@ -81,6 +81,7 @@ Contributors * Akim Juillerat * Jacques-Etienne Baudoux (BCIM) +* ACSONE SA/NV * Dung Tran Other credits diff --git a/stock_orderpoint_mto_as_mts/__manifest__.py b/stock_orderpoint_mto_as_mts/__manifest__.py index 74b1ff0c..a8531070 100644 --- a/stock_orderpoint_mto_as_mts/__manifest__.py +++ b/stock_orderpoint_mto_as_mts/__manifest__.py @@ -11,8 +11,13 @@ "license": "AGPL-3", "application": False, "installable": True, - "depends": ["sale_stock", "stock_orderpoint_manual_procurement"], + "depends": [ + "base_partition", + "product_route_mto", + "stock", + "stock_orderpoint_default_location", + ], "data": [ - "data/stock_data.xml", + "views/stock_warehouse_views.xml", ], } diff --git a/stock_orderpoint_mto_as_mts/data/stock_data.xml b/stock_orderpoint_mto_as_mts/data/stock_data.xml deleted file mode 100644 index b0c97c58..00000000 --- a/stock_orderpoint_mto_as_mts/data/stock_data.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/stock_orderpoint_mto_as_mts/models/__init__.py b/stock_orderpoint_mto_as_mts/models/__init__.py index e5bab062..30f05258 100644 --- a/stock_orderpoint_mto_as_mts/models/__init__.py +++ b/stock_orderpoint_mto_as_mts/models/__init__.py @@ -1,4 +1,2 @@ -from . import product -from . import sale_order -from . import stock_move +from . import product_product from . import stock_warehouse diff --git a/stock_orderpoint_mto_as_mts/models/product.py b/stock_orderpoint_mto_as_mts/models/product.py deleted file mode 100644 index 95f998d2..00000000 --- a/stock_orderpoint_mto_as_mts/models/product.py +++ /dev/null @@ -1,45 +0,0 @@ -# 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}) diff --git a/stock_orderpoint_mto_as_mts/models/product_product.py b/stock_orderpoint_mto_as_mts/models/product_product.py new file mode 100644 index 00000000..aacf3ab0 --- /dev/null +++ b/stock_orderpoint_mto_as_mts/models/product_product.py @@ -0,0 +1,127 @@ +# Copyright 2023 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import api, models + + +class ProductProduct(models.Model): + _inherit = "product.product" + + def _get_warehouse_missing_orderpoint_for_mto(self): + self.ensure_one() + res = False + wh_obj = self.env["stock.warehouse"] + for product in self: + if not product.purchase_ok: + continue + wh_domain = [("mto_as_mts", "=", True)] + if product.company_id: + wh_domain.append(("company_id", "=", product.company_id.id)) + wh = wh_obj.search(wh_domain) + wh_orderpoint = product.orderpoint_ids.warehouse_id + wh_wo_orderpoint = wh - wh_orderpoint + if wh_wo_orderpoint: + res = wh_wo_orderpoint + return res + + def _create_default_orderpoint_for_mto(self, warehouses): + self.ensure_one() + orderpoints = self.env["stock.warehouse.orderpoint"] + orderpoint_obj = self.env["stock.warehouse.orderpoint"] + for warehouse in warehouses: + orderpoint = orderpoint_obj.with_context(active_test=False).search( + [ + ("product_id", "=", self.id), + ( + "location_id", + "=", + warehouse._get_locations_for_mto_orderpoints().id, + ), + ], + limit=1, + order="id", + ) + if orderpoint and not orderpoint.active: + orderpoint.write( + {"active": True, "product_min_qty": 0.0, "product_max_qty": 0.0} + ) + elif not orderpoint: + vals = self._prepare_missing_orderpoint_vals(warehouse) + orderpoint = orderpoint_obj.create(vals) + orderpoints |= orderpoint + return orderpoints + + def _prepare_missing_orderpoint_vals(self, warehouse): + self.ensure_one() + return { + "warehouse_id": warehouse.id, + "product_id": self.id, + "company_id": warehouse.company_id.id, + "product_min_qty": 0, + "product_max_qty": 0, + "location_id": warehouse._get_locations_for_mto_orderpoints().id, + "product_uom": self.uom_id.id, + } + + def _ensure_default_orderpoint_for_mto(self): + """Ensure that a default orderpoint is created for the MTO products. + + that have no orderpoint yet. + """ + for product in self: + wh = product._get_warehouse_missing_orderpoint_for_mto() + product._create_default_orderpoint_for_mto(wh) + + @api.model_create_multi + def create(self, vals_list): + products = super().create(vals_list) + products.sudo()._ensure_default_orderpoint_for_mto() + return products + + def write(self, vals): + # Archive orderpoints when MTO route is removed + if "route_ids" not in vals: + res = super().write(vals) + self.sudo()._ensure_default_orderpoint_for_mto() + return res + 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): + if mto: + func = lambda p: p.is_mto # noqa + else: + func = lambda p: not p.is_mto # noqa + return self.filtered(func) + + def _get_orderpoints_to_archive_domain(self): + domain = [] + warehouses = self.env["stock.warehouse"].search( + [("mto_as_mts", "=", True), ("archive_orderpoints_mto_removal", "=", True)] + ) + if warehouses: + locations = self.env["stock.location"] + for warehouse in warehouses: + locations |= warehouse._get_locations_for_mto_orderpoints() + domain.extend( + [ + ("product_id", "in", self.ids), + ("product_min_qty", "=", 0.0), + ("product_max_qty", "=", 0.0), + ("location_id", "in", locations.ids), + ] + ) + return domain + + def _archive_orderpoints_on_mto_removal(self): + domain = self._get_orderpoints_to_archive_domain() + if domain: + ops = self.env["stock.warehouse.orderpoint"].search(domain) + if ops: + ops.write({"active": False}) diff --git a/stock_orderpoint_mto_as_mts/models/sale_order.py b/stock_orderpoint_mto_as_mts/models/sale_order.py deleted file mode 100644 index 6846c143..00000000 --- a/stock_orderpoint_mto_as_mts/models/sale_order.py +++ /dev/null @@ -1,86 +0,0 @@ -# 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_moves = line.move_ids.filtered( - lambda m: m.picking_id.picking_type_code == "outgoing" - and m.state not in ("done", "cancel") - ) - for delivery_move in delivery_moves: - if ( - not delivery_move.is_from_mto_route - or mto_route not in line.product_id.route_ids - ): - continue - orderpoint = line._get_mto_orderpoint(delivery_move.product_id) - 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, product_id): - 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", "=", 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"] - .sudo() - .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 diff --git a/stock_orderpoint_mto_as_mts/models/stock_move.py b/stock_orderpoint_mto_as_mts/models/stock_move.py deleted file mode 100644 index a9039460..00000000 --- a/stock_orderpoint_mto_as_mts/models/stock_move.py +++ /dev/null @@ -1,18 +0,0 @@ -# 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 diff --git a/stock_orderpoint_mto_as_mts/models/stock_warehouse.py b/stock_orderpoint_mto_as_mts/models/stock_warehouse.py index 2d742d1b..75c86539 100644 --- a/stock_orderpoint_mto_as_mts/models/stock_warehouse.py +++ b/stock_orderpoint_mto_as_mts/models/stock_warehouse.py @@ -1,11 +1,15 @@ # Copyright 2020 Camptocamp SA # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) -from odoo import models +from odoo import fields, models class StockWarehouse(models.Model): _inherit = "stock.warehouse" + mto_as_mts = fields.Boolean(default=False) + archive_orderpoints_mto_removal = fields.Boolean(default=False) + def _get_locations_for_mto_orderpoints(self): - return self.mapped("lot_stock_id") + self.ensure_one() + return self.default_orderpoint_location_id or self.lot_stock_id diff --git a/stock_orderpoint_mto_as_mts/readme/CONTRIBUTORS.rst b/stock_orderpoint_mto_as_mts/readme/CONTRIBUTORS.rst index 12845dd9..91796eae 100644 --- a/stock_orderpoint_mto_as_mts/readme/CONTRIBUTORS.rst +++ b/stock_orderpoint_mto_as_mts/readme/CONTRIBUTORS.rst @@ -1,3 +1,4 @@ * Akim Juillerat * Jacques-Etienne Baudoux (BCIM) +* ACSONE SA/NV * Dung Tran diff --git a/stock_orderpoint_mto_as_mts/static/description/index.html b/stock_orderpoint_mto_as_mts/static/description/index.html index 5669b646..1045deab 100644 --- a/stock_orderpoint_mto_as_mts/static/description/index.html +++ b/stock_orderpoint_mto_as_mts/static/description/index.html @@ -2,17 +2,16 @@ - + Sale Stock Mto As Mts Orderpoint