From 0071730109fdb7c1deda0c8f9af5dce55d0fe035 Mon Sep 17 00:00:00 2001 From: hda Date: Tue, 29 Nov 2022 19:43:17 +0100 Subject: [PATCH] [16.0][ADD] stock_production_lot_expired_date --- .../addons/stock_production_lot_expired_date | 1 + .../setup.py | 6 + stock_production_lot_expired_date/README.rst | 84 +++++++ stock_production_lot_expired_date/__init__.py | 1 + .../__manifest__.py | 15 ++ .../stock_production_lot_expired_date.pot | 59 +++++ .../models/__init__.py | 2 + .../models/res_config_settings.py | 23 ++ .../models/stock_lot.py | 57 +++++ .../readme/CONTRIBUTORS.rst | 2 + .../readme/DESCRIPTION.rst | 4 + .../readme/USAGE.rst | 2 + .../tests/__init__.py | 1 + .../test_stock_production_lot_expired_date.py | 228 ++++++++++++++++++ .../views/res_config_settings.xml | 28 +++ 15 files changed, 513 insertions(+) create mode 120000 setup/stock_production_lot_expired_date/odoo/addons/stock_production_lot_expired_date create mode 100644 setup/stock_production_lot_expired_date/setup.py create mode 100644 stock_production_lot_expired_date/README.rst create mode 100644 stock_production_lot_expired_date/__init__.py create mode 100644 stock_production_lot_expired_date/__manifest__.py create mode 100644 stock_production_lot_expired_date/i18n/stock_production_lot_expired_date.pot create mode 100644 stock_production_lot_expired_date/models/__init__.py create mode 100644 stock_production_lot_expired_date/models/res_config_settings.py create mode 100644 stock_production_lot_expired_date/models/stock_lot.py create mode 100644 stock_production_lot_expired_date/readme/CONTRIBUTORS.rst create mode 100644 stock_production_lot_expired_date/readme/DESCRIPTION.rst create mode 100644 stock_production_lot_expired_date/readme/USAGE.rst create mode 100644 stock_production_lot_expired_date/tests/__init__.py create mode 100644 stock_production_lot_expired_date/tests/test_stock_production_lot_expired_date.py create mode 100644 stock_production_lot_expired_date/views/res_config_settings.xml diff --git a/setup/stock_production_lot_expired_date/odoo/addons/stock_production_lot_expired_date b/setup/stock_production_lot_expired_date/odoo/addons/stock_production_lot_expired_date new file mode 120000 index 00000000000..014c7446959 --- /dev/null +++ b/setup/stock_production_lot_expired_date/odoo/addons/stock_production_lot_expired_date @@ -0,0 +1 @@ +../../../../stock_production_lot_expired_date \ No newline at end of file diff --git a/setup/stock_production_lot_expired_date/setup.py b/setup/stock_production_lot_expired_date/setup.py new file mode 100644 index 00000000000..28c57bb6403 --- /dev/null +++ b/setup/stock_production_lot_expired_date/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/stock_production_lot_expired_date/README.rst b/stock_production_lot_expired_date/README.rst new file mode 100644 index 00000000000..18b0d811944 --- /dev/null +++ b/stock_production_lot_expired_date/README.rst @@ -0,0 +1,84 @@ +================================= +Stock production lot expired date +================================= + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fproduct--attribute-lightgray.png?logo=github + :target: https://github.com/OCA/product-attribute/tree/16.0/stock_production_lot_expired_date + :alt: OCA/product-attribute +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/product-attribute-16-0/product-attribute-16-0-stock_production_lot_expired_date + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/webui/builds.html?repo=OCA/product-attribute&target_branch=16.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module allows to facilitate the input of stock production lot expired dates: + +* Add settings on stock to choose the base date for stock production lot +* Add onchanges on stock production lot to compute expired dates from base date + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +# Go to *Settings > Inventory > Traceability* + There you can set the base date under *Base date to compute expiration dates* + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Camptocamp +* ACSONE SA/NV + +Contributors +~~~~~~~~~~~~ + +* Julien Coux +* Hughes Damry + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +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. + +This module is part of the `OCA/product-attribute `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/stock_production_lot_expired_date/__init__.py b/stock_production_lot_expired_date/__init__.py new file mode 100644 index 00000000000..0650744f6bc --- /dev/null +++ b/stock_production_lot_expired_date/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/stock_production_lot_expired_date/__manifest__.py b/stock_production_lot_expired_date/__manifest__.py new file mode 100644 index 00000000000..e79be8a20f5 --- /dev/null +++ b/stock_production_lot_expired_date/__manifest__.py @@ -0,0 +1,15 @@ +# Copyright 2016 Julien Coux (Camptocamp) +# Copyright 2022 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "Stock production lot expired date", + "version": "16.0.1.0.0", + "author": "Camptocamp, ACSONE SA/NV, Odoo Community Association (OCA)", + "license": "AGPL-3", + "category": "Product", + "depends": ["product_expiry", "stock"], + "website": "https://github.com/OCA/product-attribute", + "data": ["views/res_config_settings.xml"], + "installable": True, +} diff --git a/stock_production_lot_expired_date/i18n/stock_production_lot_expired_date.pot b/stock_production_lot_expired_date/i18n/stock_production_lot_expired_date.pot new file mode 100644 index 00000000000..93cb82a27ff --- /dev/null +++ b/stock_production_lot_expired_date/i18n/stock_production_lot_expired_date.pot @@ -0,0 +1,59 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * stock_production_lot_expired_date +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2022-11-30 11:04+0000\n" +"PO-Revision-Date: 2022-11-30 11:04+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: stock_production_lot_expired_date +#. odoo-python +#: code:addons/stock_production_lot_expired_date/models/res_config_settings.py:0 +#, python-format +msgid "Alert date" +msgstr "" + +#. module: stock_production_lot_expired_date +#: model:ir.model.fields,field_description:stock_production_lot_expired_date.field_res_config_settings__production_lot_base_date +msgid "Base date to compute expiration dates" +msgstr "" + +#. module: stock_production_lot_expired_date +#: model:ir.model,name:stock_production_lot_expired_date.model_res_config_settings +msgid "Config Settings" +msgstr "" + +#. module: stock_production_lot_expired_date +#. odoo-python +#: code:addons/stock_production_lot_expired_date/models/res_config_settings.py:0 +#, python-format +msgid "Expiration date" +msgstr "" + +#. module: stock_production_lot_expired_date +#: model:ir.model,name:stock_production_lot_expired_date.model_stock_lot +msgid "Lot/Serial" +msgstr "" + +#. module: stock_production_lot_expired_date +#. odoo-python +#: code:addons/stock_production_lot_expired_date/models/res_config_settings.py:0 +#, python-format +msgid "Removal date" +msgstr "" + +#. module: stock_production_lot_expired_date +#. odoo-python +#: code:addons/stock_production_lot_expired_date/models/res_config_settings.py:0 +#, python-format +msgid "Use date" +msgstr "" diff --git a/stock_production_lot_expired_date/models/__init__.py b/stock_production_lot_expired_date/models/__init__.py new file mode 100644 index 00000000000..41ec6f5fae6 --- /dev/null +++ b/stock_production_lot_expired_date/models/__init__.py @@ -0,0 +1,2 @@ +from . import res_config_settings +from . import stock_lot diff --git a/stock_production_lot_expired_date/models/res_config_settings.py b/stock_production_lot_expired_date/models/res_config_settings.py new file mode 100644 index 00000000000..a55da7738dd --- /dev/null +++ b/stock_production_lot_expired_date/models/res_config_settings.py @@ -0,0 +1,23 @@ +# Copyright 2016 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import _, api, fields, models + + +class ResConfig(models.TransientModel): + _inherit = "res.config.settings" + + production_lot_base_date = fields.Selection( + selection="_selection_production_lot_base_date", + string="Base date to compute expiration dates", + config_parameter="stock_production_lot_expired_date.production_lot_base_date", + ) + + @api.model + def _selection_production_lot_base_date(self): + return [ + ("use", _("Use date")), + ("expiration", _("Expiration date")), + ("alert", _("Alert date")), + ("removal", _("Removal date")), + ] diff --git a/stock_production_lot_expired_date/models/stock_lot.py b/stock_production_lot_expired_date/models/stock_lot.py new file mode 100644 index 00000000000..a6248902d43 --- /dev/null +++ b/stock_production_lot_expired_date/models/stock_lot.py @@ -0,0 +1,57 @@ +# Copyright 2016-2018 Julien Coux (Camptocamp) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from dateutil.relativedelta import relativedelta + +from odoo import api, models + + +class StockLot(models.Model): + _inherit = "stock.lot" + + @api.model + def _get_product_expired_times(self, product): + return { + "expiration_time": product.expiration_time, + "use_time": product.use_time, + "removal_time": product.removal_time, + "alert_time": product.alert_time, + } + + def _apply_onchange_interval_date(self, from_field): + self.ensure_one() + base_date = self.env["ir.config_parameter"].get_param( + "stock_production_lot_expired_date.production_lot_base_date" + ) + if self.product_id and base_date == from_field: + if getattr(self, from_field + "_date"): + to_fields = ["alert", "expiration", "removal", "use"] + to_fields.remove(from_field) + times = self._get_product_expired_times(self.product_id) + from_time = times[from_field + "_time"] + from_date = getattr(self, from_field + "_date") + values = {} + for index in [0, 1, 2]: + if times[to_fields[index] + "_time"]: + days = from_time - times[to_fields[index] + "_time"] + values[to_fields[index] + "_date"] = from_date - relativedelta( + days=days + ) + if values: + self.write(values) + + @api.onchange("removal_date") + def onchange_removal_date(self): + self._apply_onchange_interval_date("removal") + + @api.onchange("alert_date") + def onchange_alert_date(self): + self._apply_onchange_interval_date("alert") + + @api.onchange("expiration_date") + def onchange_expiration_date(self): + self._apply_onchange_interval_date("expiration") + + @api.onchange("use_date") + def onchange_use_date(self): + self._apply_onchange_interval_date("use") diff --git a/stock_production_lot_expired_date/readme/CONTRIBUTORS.rst b/stock_production_lot_expired_date/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000000..f86af36272b --- /dev/null +++ b/stock_production_lot_expired_date/readme/CONTRIBUTORS.rst @@ -0,0 +1,2 @@ +* Julien Coux +* Hughes Damry diff --git a/stock_production_lot_expired_date/readme/DESCRIPTION.rst b/stock_production_lot_expired_date/readme/DESCRIPTION.rst new file mode 100644 index 00000000000..6e50df48dd9 --- /dev/null +++ b/stock_production_lot_expired_date/readme/DESCRIPTION.rst @@ -0,0 +1,4 @@ +This module allows to facilitate the input of stock production lot expired dates: + +* Add settings on stock to choose the base date for stock production lot +* Add onchanges on stock production lot to compute expired dates from base date diff --git a/stock_production_lot_expired_date/readme/USAGE.rst b/stock_production_lot_expired_date/readme/USAGE.rst new file mode 100644 index 00000000000..d0de5be05bc --- /dev/null +++ b/stock_production_lot_expired_date/readme/USAGE.rst @@ -0,0 +1,2 @@ +# Go to *Settings > Inventory > Traceability* + There you can set the base date under *Base date to compute expiration dates* diff --git a/stock_production_lot_expired_date/tests/__init__.py b/stock_production_lot_expired_date/tests/__init__.py new file mode 100644 index 00000000000..e2df2638f8f --- /dev/null +++ b/stock_production_lot_expired_date/tests/__init__.py @@ -0,0 +1 @@ +from . import test_stock_production_lot_expired_date diff --git a/stock_production_lot_expired_date/tests/test_stock_production_lot_expired_date.py b/stock_production_lot_expired_date/tests/test_stock_production_lot_expired_date.py new file mode 100644 index 00000000000..960de78af7e --- /dev/null +++ b/stock_production_lot_expired_date/tests/test_stock_production_lot_expired_date.py @@ -0,0 +1,228 @@ +# Copyright 2016 Julien Coux (Camptocamp) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from datetime import datetime, timedelta + +from odoo.tests.common import TransactionCase + + +class TestStockLotExpirationDates(TransactionCase): + @classmethod + def setUpClass(cls): + super(TestStockLotExpirationDates, cls).setUpClass() + cls.product_model = cls.env["product.product"] + cls.production_lot_model = cls.env["stock.lot"] + cls.category = cls.env.ref("product.product_category_all") + cls.category_2 = cls.env.ref("product.product_category_2") + cls.category_3 = cls.env.ref("product.product_category_3") + cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True)) + + cls.product = cls.product_model.create( + { + "name": "Unittest product", + "type": "product", + "categ_id": cls.category.id, + "use_time": 10, + "expiration_time": 11, + "alert_time": 12, + "removal_time": 13, + } + ) + cls.production_lot = cls.production_lot_model.create( + { + "name": "000001", + "product_id": cls.product.id, + "company_id": cls.env.company.id, + } + ) + + cls.product_2 = cls.product_model.create( + { + "name": "Unittest product 2", + "type": "product", + "categ_id": cls.category_2.id, + "use_time": -13, + "expiration_time": -12, + "alert_time": -11, + "removal_time": -10, + } + ) + cls.production_lot_2 = cls.production_lot_model.create( + { + "name": "000001", + "product_id": cls.product_2.id, + "company_id": cls.env.company.id, + } + ) + + cls.product_3 = cls.product_model.create( + { + "name": "Unittest product 3", + "type": "product", + "categ_id": cls.category_3.id, + "use_time": 0, + "expiration_time": 0, + "alert_time": 0, + "removal_time": 0, + } + ) + cls.production_lot_3 = cls.production_lot_model.create( + { + "name": "000001", + "product_id": cls.product_3.id, + "company_id": cls.env.company.id, + } + ) + + day_1 = timedelta(days=1) + cls.date_20 = datetime.strptime("2016-12-20 10:00:00", "%Y-%m-%d %H:%M:%S") + cls.date_21 = cls.date_20 + day_1 + cls.date_22 = cls.date_21 + day_1 + cls.date_23 = cls.date_22 + day_1 + cls.date_24 = cls.date_23 + day_1 + cls.date_25 = cls.date_24 + day_1 + cls.date_26 = cls.date_25 + day_1 + + def _set_lot_base_date(self, base_date): + """It is faster to set directly the param instead + of relying on the config setting execution""" + self.env["ir.config_parameter"].set_param( + "stock_production_lot_expired_date.production_lot_base_date", base_date + ) + + def test_1_onchange_use_date(self): + for base_date in [None, "alert", "expiration", "removal", "use"]: + self._set_lot_base_date(base_date) + for production_lot in [ + self.production_lot, + self.production_lot_2, + self.production_lot_3, + ]: + production_lot.use_date = False + production_lot.expiration_date = False + production_lot.alert_date = False + production_lot.removal_date = False + production_lot.use_date = self.date_23 + production_lot.onchange_use_date() + date_must_change = ( + base_date == "use" and production_lot != self.production_lot_3 + ) + self.assertEqual(production_lot.use_date, self.date_23) + self.assertEqual( + production_lot.expiration_date, + self.date_24 if date_must_change else False, + ) + self.assertEqual( + production_lot.alert_date, + self.date_25 if date_must_change else False, + ) + self.assertEqual( + production_lot.removal_date, + self.date_26 if date_must_change else False, + ) + # Check the onchange does not fails with no value + production_lot.use_date = False + production_lot.onchange_use_date() + + def test_2_onchange_expiration_date(self): + for base_date in [None, "alert", "expiration", "removal", "use"]: + self._set_lot_base_date(base_date) + for production_lot in [ + self.production_lot, + self.production_lot_2, + self.production_lot_3, + ]: + production_lot.use_date = False + production_lot.expiration_date = False + production_lot.alert_date = False + production_lot.removal_date = False + production_lot.expiration_date = self.date_23 + production_lot.onchange_expiration_date() + date_must_change = ( + base_date == "expiration" + and production_lot != self.production_lot_3 + ) + self.assertEqual( + production_lot.use_date, + self.date_22 if date_must_change else False, + ) + self.assertEqual(production_lot.expiration_date, self.date_23) + self.assertEqual( + production_lot.alert_date, + self.date_24 if date_must_change else False, + ) + self.assertEqual( + production_lot.removal_date, + self.date_25 if date_must_change else False, + ) + # Check the onchange not fails with no value + production_lot.expiration_date = False + production_lot.onchange_expiration_date() + + def test_3_onchange_alert_date(self): + for base_date in [None, "alert", "expiration", "removal", "use"]: + self._set_lot_base_date(base_date) + for production_lot in [ + self.production_lot, + self.production_lot_2, + self.production_lot_3, + ]: + production_lot.use_date = False + production_lot.expiration_date = False + production_lot.alert_date = False + production_lot.removal_date = False + production_lot.alert_date = self.date_23 + production_lot.onchange_alert_date() + date_must_change = ( + base_date == "alert" and production_lot != self.production_lot_3 + ) + self.assertEqual( + production_lot.use_date, + self.date_21 if date_must_change else False, + ) + self.assertEqual( + production_lot.expiration_date, + self.date_22 if date_must_change else False, + ) + self.assertEqual(production_lot.alert_date, self.date_23) + self.assertEqual( + production_lot.removal_date, + self.date_24 if date_must_change else False, + ) + # Check the onchange not fails with no value + production_lot.alert_date = False + production_lot.onchange_alert_date() + + def test_4_onchange_removal_date(self): + for base_date in [None, "alert", "expiration", "removal", "use"]: + self._set_lot_base_date(base_date) + for production_lot in [ + self.production_lot, + self.production_lot_2, + self.production_lot_3, + ]: + production_lot.use_date = False + production_lot.expiration_date = False + production_lot.alert_date = False + production_lot.removal_date = False + production_lot.removal_date = self.date_23 + production_lot.onchange_removal_date() + date_must_change = ( + base_date == "removal" and production_lot != self.production_lot_3 + ) + self.assertEqual( + production_lot.use_date, + self.date_20 if date_must_change else False, + ) + self.assertEqual( + production_lot.expiration_date, + self.date_21 if date_must_change else False, + ) + self.assertEqual( + production_lot.alert_date, + self.date_22 if date_must_change else False, + ) + self.assertEqual(production_lot.removal_date, self.date_23) + # Check the onchange not fails with no value + production_lot.removal_date = False + production_lot.onchange_removal_date() diff --git a/stock_production_lot_expired_date/views/res_config_settings.xml b/stock_production_lot_expired_date/views/res_config_settings.xml new file mode 100644 index 00000000000..1468dbef777 --- /dev/null +++ b/stock_production_lot_expired_date/views/res_config_settings.xml @@ -0,0 +1,28 @@ + + + + stock.config.settings.form (in stock_production_lot_expired_date) + res.config.settings + + + +
+
+
+
+
+