From 7b394c8c55a5caf8c157a5b40b16307137655157 Mon Sep 17 00:00:00 2001 From: Damien Crier Date: Mon, 23 Sep 2019 15:40:39 +0200 Subject: [PATCH 1/3] ADD module 'stock_minimum_shelf_life' --- stock_minimum_shelf_life/__init__.py | 3 ++ stock_minimum_shelf_life/__manifest__.py | 22 +++++++++ stock_minimum_shelf_life/models/__init__.py | 6 +++ .../models/procurement_group.py | 16 +++++++ .../models/res_company.py | 13 ++++++ .../models/res_config_settings.py | 14 ++++++ stock_minimum_shelf_life/models/stock_move.py | 45 +++++++++++++++++++ 7 files changed, 119 insertions(+) create mode 100644 stock_minimum_shelf_life/__init__.py create mode 100644 stock_minimum_shelf_life/__manifest__.py create mode 100644 stock_minimum_shelf_life/models/__init__.py create mode 100644 stock_minimum_shelf_life/models/procurement_group.py create mode 100644 stock_minimum_shelf_life/models/res_company.py create mode 100644 stock_minimum_shelf_life/models/res_config_settings.py create mode 100644 stock_minimum_shelf_life/models/stock_move.py diff --git a/stock_minimum_shelf_life/__init__.py b/stock_minimum_shelf_life/__init__.py new file mode 100644 index 000000000000..4b76c7b2d5c9 --- /dev/null +++ b/stock_minimum_shelf_life/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from . import models diff --git a/stock_minimum_shelf_life/__manifest__.py b/stock_minimum_shelf_life/__manifest__.py new file mode 100644 index 000000000000..1fda3539f755 --- /dev/null +++ b/stock_minimum_shelf_life/__manifest__.py @@ -0,0 +1,22 @@ +# Copyright 2019 Camptocamp +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +{ + "name": "Stock minimum shelf life", + "version": "12.0.1.0.0", + "license": "AGPL-3", + "summary": "Allows to set a minimum date in procurement group to force the" + " reservation mecanism to take only product that expiry date is" + " after the minimum date.", + "author": "Camptocamp," + "Odoo Community Association (OCA)", + "website": "https://github.com/OCA/stock-logistics-warehouse", + "category": "Warehouse", + "depends": [ + "stock", + "product_expiry", + ], + "data": [ + ], + "installable": True, + "development_status": 'Alpha', +} diff --git a/stock_minimum_shelf_life/models/__init__.py b/stock_minimum_shelf_life/models/__init__.py new file mode 100644 index 000000000000..93df2e27c623 --- /dev/null +++ b/stock_minimum_shelf_life/models/__init__.py @@ -0,0 +1,6 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from . import res_company +from . import res_config_settings +from . import procurement_group +from . import stock_move diff --git a/stock_minimum_shelf_life/models/procurement_group.py b/stock_minimum_shelf_life/models/procurement_group.py new file mode 100644 index 000000000000..0306d340766c --- /dev/null +++ b/stock_minimum_shelf_life/models/procurement_group.py @@ -0,0 +1,16 @@ +# Copyright 2019 Camptocamp +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from datetime import datetime +from dateutil.relativedelta import relativedelta + +from odoo import api, models + + +class ProcurementGroup(models.Model): + _inherit = 'procurement.group' + + @api.model + def _get_lot_min_expiration_date(self): + months_adjust = self.env.user.company_id.minimum_shelf_life + return datetime.now() + relativedelta(hour=0, minute=0, second=0, + months=+months_adjust) diff --git a/stock_minimum_shelf_life/models/res_company.py b/stock_minimum_shelf_life/models/res_company.py new file mode 100644 index 000000000000..07a0a44b3903 --- /dev/null +++ b/stock_minimum_shelf_life/models/res_company.py @@ -0,0 +1,13 @@ +# Copyright 2019 Camptocamp +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import fields, models + + +class ResCompany(models.Model): + _inherit = 'res.company' + + minimum_shelf_life = fields.Integer( + default=6, + string="Minimum shelf life (in months)" + ) diff --git a/stock_minimum_shelf_life/models/res_config_settings.py b/stock_minimum_shelf_life/models/res_config_settings.py new file mode 100644 index 000000000000..73d7b2700974 --- /dev/null +++ b/stock_minimum_shelf_life/models/res_config_settings.py @@ -0,0 +1,14 @@ +# Copyright 2019 Camptocamp +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import fields, models + + +class ResConfigSettings(models.TransientModel): + _inherit = 'res.config.settings' + + minimum_shelf_life = fields.Integer( + related='company_id.minimum_shelf_life', + string="Minimum shelf life (in months)", + readonly=False + ) diff --git a/stock_minimum_shelf_life/models/stock_move.py b/stock_minimum_shelf_life/models/stock_move.py new file mode 100644 index 000000000000..6d41c19d37fb --- /dev/null +++ b/stock_minimum_shelf_life/models/stock_move.py @@ -0,0 +1,45 @@ +# Copyright 2019 Camptocamp +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import models + + +class StockQuant(models.Model): + _inherit = 'stock.quant' + + def _gather(self, product_id, location_id, lot_id=None, package_id=None, + owner_id=None, strict=False): + res = super()._gather( + product_id, location_id, lot_id=lot_id, + package_id=package_id, owner_id=owner_id, strict=strict + ) + + # filter the quants that respect the minimum shelf life date + if "minimum_expiry_shelf_life" in self.env.context: + quants = self.browse() + minimum_expiry_shelf_life = ( + self.env.context.get('minimum_expiry_shelf_life') + ) + for rec in res: + if rec.lot_id.removal_date > minimum_expiry_shelf_life: + quants |= rec + return quants + + return res + + +class StockMove(models.Model): + _inherit = 'stock.move' + + def _action_assign(self): + for rec in self: + if rec.picking_id.group_id.sale_id: + min_shelf_life_date = ( + rec.picking_id.group_id._get_lot_min_expiration_date() + ) + return super(StockMove, + rec.with_context( + minimum_expiry_shelf_life=min_shelf_life_date + ))._action_assign() + else: + return super()._action_assign() From 1bb3e5b0e391302e960081a387dbff3cdcb9b888 Mon Sep 17 00:00:00 2001 From: Damien Crier Date: Mon, 23 Sep 2019 17:06:13 +0200 Subject: [PATCH 2/3] code optimization --- stock_minimum_shelf_life/models/stock_move.py | 43 ++++++++++++------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/stock_minimum_shelf_life/models/stock_move.py b/stock_minimum_shelf_life/models/stock_move.py index 6d41c19d37fb..20d03ebcc4a4 100644 --- a/stock_minimum_shelf_life/models/stock_move.py +++ b/stock_minimum_shelf_life/models/stock_move.py @@ -1,6 +1,8 @@ # Copyright 2019 Camptocamp # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from datetime import datetime +from odoo.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT as dt_fmt from odoo import models @@ -16,13 +18,14 @@ def _gather(self, product_id, location_id, lot_id=None, package_id=None, # filter the quants that respect the minimum shelf life date if "minimum_expiry_shelf_life" in self.env.context: - quants = self.browse() minimum_expiry_shelf_life = ( self.env.context.get('minimum_expiry_shelf_life') ) - for rec in res: - if rec.lot_id.removal_date > minimum_expiry_shelf_life: - quants |= rec + quants = self.search( + [('id', 'in', res.ids), + ('lot_id.removal_date', '>', minimum_expiry_shelf_life) + ] + ) return quants return res @@ -32,14 +35,24 @@ class StockMove(models.Model): _inherit = 'stock.move' def _action_assign(self): - for rec in self: - if rec.picking_id.group_id.sale_id: - min_shelf_life_date = ( - rec.picking_id.group_id._get_lot_min_expiration_date() - ) - return super(StockMove, - rec.with_context( - minimum_expiry_shelf_life=min_shelf_life_date - ))._action_assign() - else: - return super()._action_assign() + proc_sales_moves = self.filtered( + lambda m: m.picking_id.group_id.sale_id + ) + groups = proc_sales_moves.mapped('picking_id.group_id') + dates = {} + for k, v in [ + (g._get_lot_min_expiration_date().strftime(dt_fmt), + g.id) for g in groups]: + dates.setdefault(k, []).append(v) + + for dt, groups_ids in dates.items(): + min_shelf_life_date = datetime.strptime(dt, dt_fmt) + moves_filtered = proc_sales_moves.filtered( + lambda m: m.picking_id.group_id.id in groups_ids + ) + super(StockMove, moves_filtered.with_context( + minimum_expiry_shelf_life=min_shelf_life_date + ))._action_assign() + + # run action_assign on other moves + super(StockMove, self - proc_sales_moves)._action_assign() From 98f8dde05784b16497b2d224846f74b06bc0fe1a Mon Sep 17 00:00:00 2001 From: Damien Crier Date: Tue, 24 Sep 2019 11:11:21 +0200 Subject: [PATCH 3/3] add config view --- stock_minimum_shelf_life/__manifest__.py | 1 + .../views/res_config_settings.xml | 29 +++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 stock_minimum_shelf_life/views/res_config_settings.xml diff --git a/stock_minimum_shelf_life/__manifest__.py b/stock_minimum_shelf_life/__manifest__.py index 1fda3539f755..fe27fdae9e39 100644 --- a/stock_minimum_shelf_life/__manifest__.py +++ b/stock_minimum_shelf_life/__manifest__.py @@ -16,6 +16,7 @@ "product_expiry", ], "data": [ + "views/res_config_settings.xml", ], "installable": True, "development_status": 'Alpha', diff --git a/stock_minimum_shelf_life/views/res_config_settings.xml b/stock_minimum_shelf_life/views/res_config_settings.xml new file mode 100644 index 000000000000..7fda029addff --- /dev/null +++ b/stock_minimum_shelf_life/views/res_config_settings.xml @@ -0,0 +1,29 @@ + + + + res.config.settings.view.form.shelf.life + res.config.settings + + + + + + +
+
+
+
+ +
+
+
+