From b8999084c5dd9aa194d7228638f2918f2d3b2a65 Mon Sep 17 00:00:00 2001 From: florian-dacosta Date: Wed, 22 Apr 2015 20:28:48 +0200 Subject: [PATCH 1/6] ADD stock_dynamic_mto --- stock_dynamic_mto/__init__.py | 1 + stock_dynamic_mto/__openerp__.py | 42 +++++++++++++++ stock_dynamic_mto/procurement.py | 80 ++++++++++++++++++++++++++++ stock_dynamic_mto/pull_rule_view.xml | 18 +++++++ 4 files changed, 141 insertions(+) create mode 100644 stock_dynamic_mto/__init__.py create mode 100644 stock_dynamic_mto/__openerp__.py create mode 100644 stock_dynamic_mto/procurement.py create mode 100644 stock_dynamic_mto/pull_rule_view.xml diff --git a/stock_dynamic_mto/__init__.py b/stock_dynamic_mto/__init__.py new file mode 100644 index 000000000..2cac6b055 --- /dev/null +++ b/stock_dynamic_mto/__init__.py @@ -0,0 +1 @@ +from . import procurement diff --git a/stock_dynamic_mto/__openerp__.py b/stock_dynamic_mto/__openerp__.py new file mode 100644 index 000000000..bb542d353 --- /dev/null +++ b/stock_dynamic_mto/__openerp__.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Module for OpenERP +# Copyright (C) 2015 Akretion (http://www.akretion.com). All Rights Reserved +# @author Florian DA COSTA +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################### +{ + "name": "Stock Dynamic MTO", + "version": "1.0", + "category": "Stock", + "description": """ +Stock Dynamic MTO +====================== +The purpose of the module is to give the possibility to a pull rule +make to order or make to stock depending of the virtual stock of a product. +""", + "license": "AGPL-3", + "author": "Akretion,Odoo Community Association (OCA)", + "website": "http://www.akretion.com/", + "depends": [ + "stock", + ], + "data": [ + "pull_rule_view.xml", + ], + "installable": True, +} diff --git a/stock_dynamic_mto/procurement.py b/stock_dynamic_mto/procurement.py new file mode 100644 index 000000000..a1d44c9c9 --- /dev/null +++ b/stock_dynamic_mto/procurement.py @@ -0,0 +1,80 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Module for OpenERP +# Copyright (C) 2015 Akretion (http://www.akretion.com). All Rights Reserved +# @author Benoît GUILLOT +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################### +from openerp import models, fields, api + + +class ProcurementRule(models.Model): + _inherit = 'procurement.rule' + + mts_pull_rule_id = fields.Many2one('procurement.rule', + string="MTS Rule") + + +class ProcurementOrder(models.Model): + _inherit = 'procurement.order' + + @api.multi + def get_mto_qty_to_order(self): + self.ensure_one() + uom_obj = self.env['product.uom'] + proc_warehouse = self.with_context(warehouse=self.warehouse_id.id) + virtual_available = proc_warehouse.product_id.virtual_available + qty_available = uom_obj._compute_qty(self.product_id.uom_id.id, + virtual_available, + self.product_uom.id) + if qty_available > 0: + if qty_available >= self.product_qty: + return 0.0 + else: + return self.product_qty - qty_available + return self.product_qty + + @api.model + def _run(self, procurement): + uom_obj = self.env['product.uom'] + rule_id = procurement.rule_id + rule_mts = rule_id and rule_id.mts_pull_rule_id or False + if rule_mts: + needed_qty = procurement.get_mto_qty_to_order() + if needed_qty == 0.0: + procurement.write({'rule_id': rule_mts.id}) + return super(ProcurementOrder, self)._run(procurement) + else: + if needed_qty != procurement.product_qty: + mts_qty = procurement.product_qty - needed_qty + mts_uos_qty = uom_obj._compute_qty( + procurement.product_uom.id, + mts_qty, + procurement.product_uos.id) + default_vals = { + 'product_qty': mts_qty, + 'product_uos_qty': mts_uos_qty, + } + uos_qty = procurement.product_uos_qty + update_vals = { + 'product_qty': needed_qty, + 'product_uos_qty': uos_qty - mts_uos_qty, + } + mts_proc = procurement.copy(default=default_vals) + mts_proc.run() + procurement.write(update_vals) + return super(ProcurementOrder, self)._run(procurement) diff --git a/stock_dynamic_mto/pull_rule_view.xml b/stock_dynamic_mto/pull_rule_view.xml new file mode 100644 index 000000000..43f252a93 --- /dev/null +++ b/stock_dynamic_mto/pull_rule_view.xml @@ -0,0 +1,18 @@ + + + + + procurement.rule.dynamic.mto + procurement.rule + + + + + + + + + + From 93e9ffe169a4eb62d5de2f37771d934c964dfc3f Mon Sep 17 00:00:00 2001 From: florian-dacosta Date: Thu, 23 Apr 2015 10:37:23 +0200 Subject: [PATCH 2/6] fix copyright --- stock_dynamic_mto/procurement.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stock_dynamic_mto/procurement.py b/stock_dynamic_mto/procurement.py index a1d44c9c9..aa9d4b1bb 100644 --- a/stock_dynamic_mto/procurement.py +++ b/stock_dynamic_mto/procurement.py @@ -3,7 +3,7 @@ # # Module for OpenERP # Copyright (C) 2015 Akretion (http://www.akretion.com). All Rights Reserved -# @author Benoît GUILLOT +# @author Florian DA COSTA # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as From 80e714bea9500b36e8b8916e19b14268704e7210 Mon Sep 17 00:00:00 2001 From: florian-dacosta Date: Thu, 23 Apr 2015 10:48:08 +0200 Subject: [PATCH 3/6] fix description --- stock_dynamic_mto/__openerp__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/stock_dynamic_mto/__openerp__.py b/stock_dynamic_mto/__openerp__.py index bb542d353..10ad40def 100644 --- a/stock_dynamic_mto/__openerp__.py +++ b/stock_dynamic_mto/__openerp__.py @@ -27,7 +27,8 @@ Stock Dynamic MTO ====================== The purpose of the module is to give the possibility to a pull rule -make to order or make to stock depending of the virtual stock of a product. +make to order to act like a make to stock rule depending of the virtual +stock of a product. """, "license": "AGPL-3", "author": "Akretion,Odoo Community Association (OCA)", From 4eddb59d86149ca7fb7bbc364191a681cfd71ddd Mon Sep 17 00:00:00 2001 From: florian-dacosta Date: Thu, 23 Apr 2015 11:27:36 +0200 Subject: [PATCH 4/6] fix pep8 --- stock_dynamic_mto/__openerp__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stock_dynamic_mto/__openerp__.py b/stock_dynamic_mto/__openerp__.py index 10ad40def..7f6b1970e 100644 --- a/stock_dynamic_mto/__openerp__.py +++ b/stock_dynamic_mto/__openerp__.py @@ -27,7 +27,7 @@ Stock Dynamic MTO ====================== The purpose of the module is to give the possibility to a pull rule -make to order to act like a make to stock rule depending of the virtual +make to order to act like a make to stock rule depending of the virtual stock of a product. """, "license": "AGPL-3", From fe31fa5e582d4e5ecaf62f9199c92273c498565c Mon Sep 17 00:00:00 2001 From: Florian da Costa Date: Wed, 29 Apr 2015 17:36:21 +0200 Subject: [PATCH 5/6] refactore creating a new route, a new procurement action and separate procurement rules to handle the mts+mto scenario --- stock_dynamic_mto/__init__.py | 1 - stock_dynamic_mto/pull_rule_view.xml | 18 -- stock_mts_mto_rule/README.rst | 62 +++++++ stock_mts_mto_rule/__init__.py | 1 + stock_mts_mto_rule/__openerp__.py | 38 ++++ stock_mts_mto_rule/data/stock_data.xml | 17 ++ stock_mts_mto_rule/model/__init__.py | 5 + .../model}/procurement.py | 75 ++++---- .../model/rule.py | 38 ++-- stock_mts_mto_rule/model/warehouse.py | 166 ++++++++++++++++++ stock_mts_mto_rule/view/pull_rule.xml | 21 +++ stock_mts_mto_rule/view/warehouse.xml | 17 ++ 12 files changed, 386 insertions(+), 73 deletions(-) delete mode 100644 stock_dynamic_mto/__init__.py delete mode 100644 stock_dynamic_mto/pull_rule_view.xml create mode 100644 stock_mts_mto_rule/README.rst create mode 100644 stock_mts_mto_rule/__init__.py create mode 100644 stock_mts_mto_rule/__openerp__.py create mode 100644 stock_mts_mto_rule/data/stock_data.xml create mode 100644 stock_mts_mto_rule/model/__init__.py rename {stock_dynamic_mto => stock_mts_mto_rule/model}/procurement.py (52%) rename stock_dynamic_mto/__openerp__.py => stock_mts_mto_rule/model/rule.py (64%) create mode 100644 stock_mts_mto_rule/model/warehouse.py create mode 100644 stock_mts_mto_rule/view/pull_rule.xml create mode 100644 stock_mts_mto_rule/view/warehouse.xml diff --git a/stock_dynamic_mto/__init__.py b/stock_dynamic_mto/__init__.py deleted file mode 100644 index 2cac6b055..000000000 --- a/stock_dynamic_mto/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import procurement diff --git a/stock_dynamic_mto/pull_rule_view.xml b/stock_dynamic_mto/pull_rule_view.xml deleted file mode 100644 index 43f252a93..000000000 --- a/stock_dynamic_mto/pull_rule_view.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - procurement.rule.dynamic.mto - procurement.rule - - - - - - - - - - diff --git a/stock_mts_mto_rule/README.rst b/stock_mts_mto_rule/README.rst new file mode 100644 index 000000000..a3049f0d8 --- /dev/null +++ b/stock_mts_mto_rule/README.rst @@ -0,0 +1,62 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :alt: License: AGPL-3 + +Stock MTS+MTO Rule +================== + +This module add a Make To Stock + Make to Order Route. + +If you choose the make to stock + make to order rule instead of the make to +order route, the creation of a purchase order will depend on the virtual stock. +There are 3 cases : + +1. The virtual stock of the product is 0 + => It will act exactly like the make to order route. + +2. The virtual stock is equal to the quantity ordered + => It will act exactly like a make to stock route + +3. The virtual stock is more than 0 but less than ordered quantity + => On part of the products will be taken from stock and a purchase order + will be created for the rest. So it will act like both make to order and + make to stock rule. + +Example : +We have a virtual stock of : 1 product A +A sale Order is made for 3 products A. +2 Procurements will be created : + +1. 1 with a make to stock rule and a quantity of 1 + +2. 1 with a make to order rule and a quantity of 2. + +After validation, a purchase order with 2 products will be created. + +Usage +===== + +You have to choose the mts+mto route on the product form. +You should not choose both mts+mto route and mto route. + +Credits +======= + +Contributors +------------ + +* Florian da Costa + +Maintainer +---------- + +.. image:: http://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: http://odoo-community.org + +This module is maintained by the OCA. + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +To contribute to this module, please visit http://odoo-community.org. diff --git a/stock_mts_mto_rule/__init__.py b/stock_mts_mto_rule/__init__.py new file mode 100644 index 000000000..9186ee3ad --- /dev/null +++ b/stock_mts_mto_rule/__init__.py @@ -0,0 +1 @@ +from . import model diff --git a/stock_mts_mto_rule/__openerp__.py b/stock_mts_mto_rule/__openerp__.py new file mode 100644 index 000000000..ea19cac08 --- /dev/null +++ b/stock_mts_mto_rule/__openerp__.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- + +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2015 Akretion (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +{'name': 'Stock MTS+MTO Rule', + 'version': '1.0', + 'author': 'Akretion,Odoo Community Association (OCA)', + 'website': 'http://www.akretion.com', + 'license': 'AGPL-3', + 'category': 'Warehouse', + 'summary': 'Add a MTS+MTO route', + 'depends': ['stock', + ], + 'demo': [], + 'data': ['data/stock_data.xml', + 'view/pull_rule.xml', + 'view/warehouse.xml', + ], + 'installable': True, + } diff --git a/stock_mts_mto_rule/data/stock_data.xml b/stock_mts_mto_rule/data/stock_data.xml new file mode 100644 index 000000000..8297d5a83 --- /dev/null +++ b/stock_mts_mto_rule/data/stock_data.xml @@ -0,0 +1,17 @@ + + + + + + + + Make To Order + Make To Stock + 5 + + + + + + diff --git a/stock_mts_mto_rule/model/__init__.py b/stock_mts_mto_rule/model/__init__.py new file mode 100644 index 000000000..cb1cbd4ab --- /dev/null +++ b/stock_mts_mto_rule/model/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- + +from . import rule +from . import warehouse +from . import procurement diff --git a/stock_dynamic_mto/procurement.py b/stock_mts_mto_rule/model/procurement.py similarity index 52% rename from stock_dynamic_mto/procurement.py rename to stock_mts_mto_rule/model/procurement.py index aa9d4b1bb..b128c6458 100644 --- a/stock_dynamic_mto/procurement.py +++ b/stock_mts_mto_rule/model/procurement.py @@ -19,14 +19,7 @@ # along with this program. If not, see . # ############################################################################### -from openerp import models, fields, api - - -class ProcurementRule(models.Model): - _inherit = 'procurement.rule' - - mts_pull_rule_id = fields.Many2one('procurement.rule', - string="MTS Rule") +from openerp import models, api class ProcurementOrder(models.Model): @@ -48,33 +41,51 @@ class ProcurementOrder(models.Model): return self.product_qty - qty_available return self.product_qty + @api.model + def _get_mts_mto_procurement(self, proc, rule, qty, uos_qty): + return { + 'name': rule.name, + 'origin': proc.rule_id.name, + 'product_qty': qty, + 'product_uos_qty': uos_qty, + 'rule_id': rule.id, + } + @api.model def _run(self, procurement): - uom_obj = self.env['product.uom'] - rule_id = procurement.rule_id - rule_mts = rule_id and rule_id.mts_pull_rule_id or False - if rule_mts: + if procurement.rule_id and \ + procurement.rule_id.action == 'split_procurement': + uom_obj = self.env['product.uom'] needed_qty = procurement.get_mto_qty_to_order() + rule = procurement.rule_id if needed_qty == 0.0: - procurement.write({'rule_id': rule_mts.id}) - return super(ProcurementOrder, self)._run(procurement) + mts_vals = self._get_mts_mto_procurement( + procurement, rule.mts_rule_id, procurement.product_qty, + procurement.product_uos_qty) + mts_proc = procurement.copy(mts_vals) + mts_proc.run() + elif needed_qty == procurement.product_qty: + mto_vals = self._get_mts_mto_procurement( + procurement, rule.mto_rule_id, procurement.product_qty, + procurement.product_uos_qty) + mto_proc = procurement.copy(mto_vals) + mto_proc.run() else: - if needed_qty != procurement.product_qty: - mts_qty = procurement.product_qty - needed_qty - mts_uos_qty = uom_obj._compute_qty( - procurement.product_uom.id, - mts_qty, - procurement.product_uos.id) - default_vals = { - 'product_qty': mts_qty, - 'product_uos_qty': mts_uos_qty, - } - uos_qty = procurement.product_uos_qty - update_vals = { - 'product_qty': needed_qty, - 'product_uos_qty': uos_qty - mts_uos_qty, - } - mts_proc = procurement.copy(default=default_vals) - mts_proc.run() - procurement.write(update_vals) + mts_qty = procurement.product_qty - needed_qty + mts_uos_qty = uom_obj._compute_qty( + procurement.product_uom.id, + mts_qty, + procurement.product_uos.id) + mts_vals = self._get_mts_mto_procurement( + procurement, rule.mts_rule_id, mts_qty, mts_uos_qty) + mts_proc = procurement.copy(mts_vals) + mts_proc.run() + + uos_qty = procurement.product_uos_qty + mto_vals = self._get_mts_mto_procurement( + procurement, rule.mto_rule_id, needed_qty, + uos_qty - mts_uos_qty) + + mto_proc = procurement.copy(mto_vals) + mto_proc.run() return super(ProcurementOrder, self)._run(procurement) diff --git a/stock_dynamic_mto/__openerp__.py b/stock_mts_mto_rule/model/rule.py similarity index 64% rename from stock_dynamic_mto/__openerp__.py rename to stock_mts_mto_rule/model/rule.py index 7f6b1970e..481ffedce 100644 --- a/stock_dynamic_mto/__openerp__.py +++ b/stock_mts_mto_rule/model/rule.py @@ -19,25 +19,19 @@ # along with this program. If not, see . # ############################################################################### -{ - "name": "Stock Dynamic MTO", - "version": "1.0", - "category": "Stock", - "description": """ -Stock Dynamic MTO -====================== -The purpose of the module is to give the possibility to a pull rule -make to order to act like a make to stock rule depending of the virtual -stock of a product. -""", - "license": "AGPL-3", - "author": "Akretion,Odoo Community Association (OCA)", - "website": "http://www.akretion.com/", - "depends": [ - "stock", - ], - "data": [ - "pull_rule_view.xml", - ], - "installable": True, -} +from openerp import models, api, fields +from openerp.tools.translate import _ + + +class ProcurementRule(models.Model): + _inherit = 'procurement.rule' + + mts_rule_id = fields.Many2one('procurement.rule', + string="MTS Rule") + mto_rule_id = fields.Many2one('procurement.rule', + string="MTO Rule") + + @api.model + def _get_action(self): + return [('split_procurement', _('Choose between MTS and MTO'))] + \ + super(ProcurementRule, self)._get_action() diff --git a/stock_mts_mto_rule/model/warehouse.py b/stock_mts_mto_rule/model/warehouse.py new file mode 100644 index 000000000..b2b0b0b74 --- /dev/null +++ b/stock_mts_mto_rule/model/warehouse.py @@ -0,0 +1,166 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Module for OpenERP +# Copyright (C) 2015 Akretion (http://www.akretion.com). All Rights Reserved +# @author Florian DA COSTA +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################### +from openerp import models, api, fields, exceptions +from openerp.tools.translate import _ + + +class Warehouse(models.Model): + _inherit = 'stock.warehouse' + + mto_mts_management = fields.Boolean( + 'Use MTO+MTS rules') + mts_mto_rule_id = fields.Many2one('procurement.rule', + 'MTO+MTS rule') + + @api.model + def _get_mts_mto_mto_rule(self, warehouse): + return { + 'name': self._format_routename(warehouse, _('MTS+MTO : MTO')), + 'route_id': False, + } + + @api.model + def _get_mts_mto_mts_rule(self, warehouse): + return { + 'name': self._format_routename(warehouse, _('MTS+MTO : MTS')), + 'route_id': False, + 'procure_method': 'make_to_stock', + } + + @api.model + def _get_mts_mto_rule(self, warehouse): + route_model = self.env['stock.location.route'] + try: + mts_mto_route = self.env.ref( + 'stock_mts_mto_rule.route_mto_mts') + except: + mts_mto_route = route_model.search([ + ('name', 'like', 'Make To Order + Make To Stock') + ]) + if not mts_mto_route: + raise exceptions.Warning(_( + 'Can\'t find any generic MTS+MTO route.')) + + return { + 'name': self._format_routename(warehouse, _('MTS+MTO')), + 'route_id': mts_mto_route.id, + 'action': 'split_procurement', + } + + @api.model + def create_mts_mto_rules(self, warehouse): + model_rule = warehouse.mto_pull_id + mts_pull_vals = self._get_mts_mto_mts_rule(warehouse) + mts_pull = model_rule.copy(mts_pull_vals) + mto_pull_vals = self._get_mts_mto_mto_rule(warehouse) + mto_pull_vals['mts_pull_rule_id'] = mts_pull.id + mto_pull = model_rule.copy(mto_pull_vals) + + mts_mto_pull_vals = self._get_mts_mto_rule(warehouse) + mts_mto_pull_vals.update({'mts_rule_id': mts_pull.id, + 'mto_rule_id': mto_pull.id}) + mts_mto_pull = model_rule.copy(mts_mto_pull_vals) + + res = { + 'mts_rule_id': mts_pull.id, + 'mto_rule_id': mto_pull.id, + 'mts_mto_rule_id': mts_mto_pull.id, + } + return res + + @api.multi + def create_routes(self, warehouse): + res = super(Warehouse, self).create_routes(warehouse) + if warehouse.mto_mts_management: + vals = self.create_mts_mto_rules(warehouse) + res['mts_mto_rule_id'] = vals.get('mts_mto_rule_id', False) + return res + + @api.multi + def write(self, vals): + if 'mto_mts_management' in vals: + if vals.get("mto_mts_management"): + for warehouse in self: + if not warehouse.mts_mto_rule_id: + rule_vals = self.create_mts_mto_rules(warehouse) + vals['mts_mto_rule_id'] = rule_vals.get( + 'mts_mto_rule_id', False) + else: + for warehouse in self: + if warehouse.mts_mto_rule_id: + warehouse.mts_mto_rule_id.mts_rule_id.unlink() + warehouse.mts_mto_rule_id.mto_rule_id.unlink() + warehouse.mts_mto_rule_id.unlink() + return super(Warehouse, self).write(vals) + + @api.model + def get_all_routes_for_wh(self, warehouse): + all_routes = super(Warehouse, self).get_all_routes_for_wh(warehouse) + if ( + warehouse.mto_mts_management and + warehouse.mts_mto_mto_rule_id.route_id + ): + all_routes += [warehouse.mts_mto_rule_id.route_id.id] + return all_routes + + @api.model + def _handle_renaming(self, warehouse, name, code): + res = super(Warehouse, self)._handle_renaming(warehouse, name, code) + + if warehouse.mts_mto_rule_id: + warehouse.mts_mto_rule_id.mts_rule_id.name = ( + warehouse.mts_mto_rule_id.mts_rule_id.name.replace( + warehouse.name, name, 1) + ) + warehouse.mts_mto_rule_id.mto_rule_id.name = ( + warehouse.mts_mto_rule_id.mto_rule_id.name.replace( + warehouse.name, name, 1) + ) + warehouse.mts_mto_rule_id.name = ( + warehouse.mts_mto_rule_id.name.replace( + warehouse.name, name, 1) + ) + return res + + @api.multi + def change_route(self, warehouse, new_reception_step=False, + new_delivery_step=False): + res = super(Warehouse, self).change_route( + warehouse, + new_reception_step=new_reception_step, + new_delivery_step=new_delivery_step) + + mts_mto_rule_id = warehouse.mts_mto_rule_id + if new_delivery_step and mts_mto_rule_id: + model_rule = warehouse.mto_pull_id + rule_ids = [ + mts_mto_rule_id.id, + mts_mto_rule_id.mts_rule_id.id, + mts_mto_rule_id.mto_rule_id.id + ] + pull_model = self.env['procurement.rule'] + vals = { + 'location_id': model_rule.location_id.id, + 'location_src_id': model_rule.location_src_id.id, + } + pull_model.write(rule_ids, vals) + return res diff --git a/stock_mts_mto_rule/view/pull_rule.xml b/stock_mts_mto_rule/view/pull_rule.xml new file mode 100644 index 000000000..e21a59ff8 --- /dev/null +++ b/stock_mts_mto_rule/view/pull_rule.xml @@ -0,0 +1,21 @@ + + + + + procurement.rule.mts.mto + procurement.rule + + + + + + + + + + + diff --git a/stock_mts_mto_rule/view/warehouse.xml b/stock_mts_mto_rule/view/warehouse.xml new file mode 100644 index 000000000..5dc891581 --- /dev/null +++ b/stock_mts_mto_rule/view/warehouse.xml @@ -0,0 +1,17 @@ + + + + + + view_warehouse_inherited + stock.warehouse + + + + + + + + + + From f95bdc0e80cabae029d8f365cd870512ed771ec2 Mon Sep 17 00:00:00 2001 From: Florian da Costa Date: Tue, 12 May 2015 18:57:17 +0200 Subject: [PATCH 6/6] add test + use standard mts and mto rules --- stock_mts_mto_rule/model/procurement.py | 24 +++- stock_mts_mto_rule/model/warehouse.py | 99 ++++++----------- stock_mts_mto_rule/tests/__init__.py | 2 + .../tests/test_mto_mts_route.py | 104 ++++++++++++++++++ 4 files changed, 163 insertions(+), 66 deletions(-) create mode 100644 stock_mts_mto_rule/tests/__init__.py create mode 100644 stock_mts_mto_rule/tests/test_mto_mts_route.py diff --git a/stock_mts_mto_rule/model/procurement.py b/stock_mts_mto_rule/model/procurement.py index b128c6458..cb563241f 100644 --- a/stock_mts_mto_rule/model/procurement.py +++ b/stock_mts_mto_rule/model/procurement.py @@ -29,7 +29,8 @@ class ProcurementOrder(models.Model): def get_mto_qty_to_order(self): self.ensure_one() uom_obj = self.env['product.uom'] - proc_warehouse = self.with_context(warehouse=self.warehouse_id.id) + stock_location = self.warehouse_id.lot_stock_id.id + proc_warehouse = self.with_context(location=stock_location) virtual_available = proc_warehouse.product_id.virtual_available qty_available = uom_obj._compute_qty(self.product_id.uom_id.id, virtual_available, @@ -43,14 +44,33 @@ class ProcurementOrder(models.Model): @api.model def _get_mts_mto_procurement(self, proc, rule, qty, uos_qty): + origin = (proc.group_id and (proc.group_id.name + ":") or "") + \ + (proc.rule_id and proc.rule_id.name or proc.origin or "/") return { 'name': rule.name, - 'origin': proc.rule_id.name, + 'origin': origin, 'product_qty': qty, 'product_uos_qty': uos_qty, 'rule_id': rule.id, } + @api.model + def _check(self, procurement): + if procurement.rule_id and \ + procurement.rule_id.action == 'split_procurement': + if procurement.state == 'running': + return True + return super(ProcurementOrder, self)._check(procurement) + + @api.multi + def run(self, autocommit=False): + res = super(ProcurementOrder, self).run(autocommit=autocommit) + for proc in self: + if proc.rule_id and \ + proc.rule_id.action == 'split_procurement': + proc.check() + return res + @api.model def _run(self, procurement): if procurement.rule_id and \ diff --git a/stock_mts_mto_rule/model/warehouse.py b/stock_mts_mto_rule/model/warehouse.py index b2b0b0b74..6f06ada0f 100644 --- a/stock_mts_mto_rule/model/warehouse.py +++ b/stock_mts_mto_rule/model/warehouse.py @@ -27,28 +27,17 @@ class Warehouse(models.Model): _inherit = 'stock.warehouse' mto_mts_management = fields.Boolean( - 'Use MTO+MTS rules') + 'Use MTO+MTS rules', + help='If this new route is selected on product form view, a ' + 'purchase order will be created only if the virtual stock is ' + 'less than 0 else, the product will be taken from stocks') mts_mto_rule_id = fields.Many2one('procurement.rule', 'MTO+MTS rule') - @api.model - def _get_mts_mto_mto_rule(self, warehouse): - return { - 'name': self._format_routename(warehouse, _('MTS+MTO : MTO')), - 'route_id': False, - } - - @api.model - def _get_mts_mto_mts_rule(self, warehouse): - return { - 'name': self._format_routename(warehouse, _('MTS+MTO : MTS')), - 'route_id': False, - 'procure_method': 'make_to_stock', - } - @api.model def _get_mts_mto_rule(self, warehouse): route_model = self.env['stock.location.route'] + pull_model = self.env['procurement.rule'] try: mts_mto_route = self.env.ref( 'stock_mts_mto_rule.route_mto_mts') @@ -60,55 +49,50 @@ class Warehouse(models.Model): raise exceptions.Warning(_( 'Can\'t find any generic MTS+MTO route.')) + if not warehouse.mto_pull_id: + raise exceptions.Warning(_( + 'Can\'t find MTO Rule on the warehouse')) + + mts_rules = pull_model.search( + [('location_src_id', '=', warehouse.lot_stock_id.id), + ('route_id', '=', warehouse.delivery_route_id.id)]) + if not mts_rules: + raise exceptions.Warning(_( + 'Can\'t find MTS Rule on the warehouse')) return { 'name': self._format_routename(warehouse, _('MTS+MTO')), 'route_id': mts_mto_route.id, 'action': 'split_procurement', + 'mto_rule_id': warehouse.mto_pull_id.id, + 'mts_rule_id': mts_rules[0].id, + 'warehouse_id': warehouse.id, + 'location_id': warehouse.mto_pull_id.location_id.id, + 'picking_type_id': warehouse.mto_pull_id.picking_type_id.id, } - @api.model - def create_mts_mto_rules(self, warehouse): - model_rule = warehouse.mto_pull_id - mts_pull_vals = self._get_mts_mto_mts_rule(warehouse) - mts_pull = model_rule.copy(mts_pull_vals) - mto_pull_vals = self._get_mts_mto_mto_rule(warehouse) - mto_pull_vals['mts_pull_rule_id'] = mts_pull.id - mto_pull = model_rule.copy(mto_pull_vals) - - mts_mto_pull_vals = self._get_mts_mto_rule(warehouse) - mts_mto_pull_vals.update({'mts_rule_id': mts_pull.id, - 'mto_rule_id': mto_pull.id}) - mts_mto_pull = model_rule.copy(mts_mto_pull_vals) - - res = { - 'mts_rule_id': mts_pull.id, - 'mto_rule_id': mto_pull.id, - 'mts_mto_rule_id': mts_mto_pull.id, - } - return res - @api.multi def create_routes(self, warehouse): + pull_model = self.env['procurement.rule'] res = super(Warehouse, self).create_routes(warehouse) if warehouse.mto_mts_management: - vals = self.create_mts_mto_rules(warehouse) - res['mts_mto_rule_id'] = vals.get('mts_mto_rule_id', False) + mts_mto_pull_vals = self._get_mts_mto_rule(warehouse) + mts_mto_pull = pull_model.create(mts_mto_pull_vals) + res['mts_mto_rule_id'] = mts_mto_pull.id return res @api.multi def write(self, vals): + pull_model = self.env['procurement.rule'] if 'mto_mts_management' in vals: if vals.get("mto_mts_management"): for warehouse in self: if not warehouse.mts_mto_rule_id: - rule_vals = self.create_mts_mto_rules(warehouse) - vals['mts_mto_rule_id'] = rule_vals.get( - 'mts_mto_rule_id', False) + rule_vals = self._get_mts_mto_rule(warehouse) + mts_mto_pull = pull_model.create(rule_vals) + vals['mts_mto_rule_id'] = mts_mto_pull.id else: for warehouse in self: if warehouse.mts_mto_rule_id: - warehouse.mts_mto_rule_id.mts_rule_id.unlink() - warehouse.mts_mto_rule_id.mto_rule_id.unlink() warehouse.mts_mto_rule_id.unlink() return super(Warehouse, self).write(vals) @@ -117,7 +101,7 @@ class Warehouse(models.Model): all_routes = super(Warehouse, self).get_all_routes_for_wh(warehouse) if ( warehouse.mto_mts_management and - warehouse.mts_mto_mto_rule_id.route_id + warehouse.mts_mto_rule_id.route_id ): all_routes += [warehouse.mts_mto_rule_id.route_id.id] return all_routes @@ -127,14 +111,6 @@ class Warehouse(models.Model): res = super(Warehouse, self)._handle_renaming(warehouse, name, code) if warehouse.mts_mto_rule_id: - warehouse.mts_mto_rule_id.mts_rule_id.name = ( - warehouse.mts_mto_rule_id.mts_rule_id.name.replace( - warehouse.name, name, 1) - ) - warehouse.mts_mto_rule_id.mto_rule_id.name = ( - warehouse.mts_mto_rule_id.mto_rule_id.name.replace( - warehouse.name, name, 1) - ) warehouse.mts_mto_rule_id.name = ( warehouse.mts_mto_rule_id.name.replace( warehouse.name, name, 1) @@ -151,16 +127,11 @@ class Warehouse(models.Model): mts_mto_rule_id = warehouse.mts_mto_rule_id if new_delivery_step and mts_mto_rule_id: - model_rule = warehouse.mto_pull_id - rule_ids = [ - mts_mto_rule_id.id, - mts_mto_rule_id.mts_rule_id.id, - mts_mto_rule_id.mto_rule_id.id - ] pull_model = self.env['procurement.rule'] - vals = { - 'location_id': model_rule.location_id.id, - 'location_src_id': model_rule.location_src_id.id, - } - pull_model.write(rule_ids, vals) + warehouse.mts_mto_rule_id.location_id = ( + warehouse.mto_pull_id.location_id) + mts_rules = pull_model.search( + [('location_src_id', '=', warehouse.lot_stock_id.id), + ('route_id', '=', warehouse.delivery_route_id.id)]) + warehouse.mts_mto_rule_id.mts_rule_id = mts_rules[0].id return res diff --git a/stock_mts_mto_rule/tests/__init__.py b/stock_mts_mto_rule/tests/__init__.py new file mode 100644 index 000000000..96aebab0d --- /dev/null +++ b/stock_mts_mto_rule/tests/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +from . import test_mto_mts_route diff --git a/stock_mts_mto_rule/tests/test_mto_mts_route.py b/stock_mts_mto_rule/tests/test_mto_mts_route.py new file mode 100644 index 000000000..bc38de754 --- /dev/null +++ b/stock_mts_mto_rule/tests/test_mto_mts_route.py @@ -0,0 +1,104 @@ +# Author: Florian da Costa +# Copyright 2015 Akretion +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +from openerp.tests.common import TransactionCase +from datetime import datetime + + +class TestMtoMtsRoute(TransactionCase): + + def test_standard_mto_route(self): + mto_route = self.env.ref('stock.route_warehouse0_mto') + self.product.route_ids = [(6, 0, [mto_route.id])] + self.procurement.run() + self.assertEqual(self.warehouse.mto_pull_id, + self.procurement.rule_id) + self.assertEqual('make_to_order', + self.procurement.move_ids[0].procure_method) + self.assertEqual(self.procurement.product_qty, + self.procurement.move_ids[0].product_uom_qty) + self.assertEqual('waiting', + self.procurement.move_ids[0].state) + + def test_standard_mts_route(self): + self.procurement.run() + self.assertEqual('make_to_stock', + self.procurement.move_ids[0].procure_method) + self.assertEqual(self.procurement.product_qty, + self.procurement.move_ids[0].product_uom_qty) + self.assertEqual('confirmed', + self.procurement.move_ids[0].state) + + def test_mts_mto_route_split(self): + mto_mts_route = self.env.ref('stock_mts_mto_rule.route_mto_mts') + self.product.route_ids = [(6, 0, [mto_mts_route.id])] + self.quant.qty = 1.0 + self.procurement.run() + moves = self.env['stock.move'].search( + [('group_id', '=', self.group.id)]) + self.assertEqual(2, len(moves)) + self.assertEqual(1.0, moves[0].product_uom_qty) + + def test_mts_mto_route_mts_only(self): + mto_mts_route = self.env.ref('stock_mts_mto_rule.route_mto_mts') + self.product.route_ids = [(6, 0, [mto_mts_route.id])] + self.quant.qty = 0.0 + self.procurement.run() + moves = self.env['stock.move'].search( + [('group_id', '=', self.group.id)]) + self.assertEqual(1, len(moves)) + self.assertEqual(2.0, moves[0].product_uom_qty) + self.assertEqual('make_to_order', + moves[0].procure_method) + + def test_mts_mto_route_mto_only(self): + mto_mts_route = self.env.ref('stock_mts_mto_rule.route_mto_mts') + self.product.route_ids = [(6, 0, [mto_mts_route.id])] + self.quant.qty = 3.0 + self.procurement.run() + moves = self.env['stock.move'].search( + [('group_id', '=', self.group.id)]) + self.assertEqual(1, len(moves)) + self.assertEqual(2.0, moves[0].product_uom_qty) + self.assertEqual('make_to_stock', + moves[0].procure_method) + + def setUp(self): + super(TestMtoMtsRoute, self).setUp() + self.warehouse = self.env.ref('stock.warehouse0') + self.warehouse.mto_mts_management = True + self.product = self.env.ref('product.product_product_4') + self.company_partner = self.env.ref('base.main_partner') + self.group = self.env['procurement.group'].create({ + 'name': 'test', + }) + self.procurement = self.env['procurement.order'].create({ + 'location_id': self.env.ref('stock.stock_location_customers').id, + 'product_id': self.product.id, + 'product_qty': 2.0, + 'product_uom': 1, + 'warehouse_id': self.warehouse.id, + 'priority': '1', + 'date_planned': datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + 'name': self.product.name, + 'origin': 'test', + 'group_id': self.group.id, + }) + self.quant = self.env['stock.quant'].create({ + 'owner_id': self.company_partner.id, + 'location_id': self.env.ref('stock.stock_location_stock').id, + 'product_id': self.product.id, + 'qty': 0.0, + })