diff --git a/setup/stock_demand_estimate_matrix/odoo/addons/stock_demand_estimate_matrix b/setup/stock_demand_estimate_matrix/odoo/addons/stock_demand_estimate_matrix new file mode 120000 index 000000000000..73de46a1c71f --- /dev/null +++ b/setup/stock_demand_estimate_matrix/odoo/addons/stock_demand_estimate_matrix @@ -0,0 +1 @@ +../../../../stock_demand_estimate_matrix \ No newline at end of file diff --git a/setup/stock_demand_estimate_matrix/setup.py b/setup/stock_demand_estimate_matrix/setup.py new file mode 100644 index 000000000000..28c57bb64031 --- /dev/null +++ b/setup/stock_demand_estimate_matrix/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/stock_demand_estimate_matrix/README.rst b/stock_demand_estimate_matrix/README.rst new file mode 100644 index 000000000000..5376a9f58db6 --- /dev/null +++ b/stock_demand_estimate_matrix/README.rst @@ -0,0 +1,99 @@ +============================ +Stock Demand Estimate Matrix +============================ + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! 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%2Fstock--logistics--warehouse-lightgray.png?logo=github + :target: https://github.com/OCA/stock-logistics-warehouse/tree/14.0/stock_demand_estimate_matrix + :alt: OCA/stock-logistics-warehouse +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/stock-logistics-warehouse-14-0/stock-logistics-warehouse-14-0-stock_demand_estimate_matrix + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/153/14.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module allows to create demand estimates for a given product and +location, on configurable time periods. + +The module does not provide in itself any specific usage of the estimates. + +**Table of contents** + +.. contents:: + :local: + +Installation +============ + +This module relies on: + +* The OCA module '2D matrix for x2many fields', and can be downloaded from + Github: https://github.com/OCA/web/tree/13.0/web_widget_x2many_2d_matrix +* The OCA module 'Date Range', and can be downloaded from + Github: https://github.com/OCA/server-ux/tree/13.0/date_range + +Usage +===== + +Go to *Inventory > Configuration > Date Ranges* and define your estimating periods. + +Go to *Inventory > Demand Planning > Create Demand Estimates* to create or +update your demand estimates. + +Go to *Inventory > Demand Planning > Demand Estimates* to review the +estimates created. + +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 +~~~~~~~ + +* ForgeFlow + +Contributors +~~~~~~~~~~~~ + +* Jordi Ballester Alomar +* Lois Rilo +* Pimolnat Suntian + +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/stock-logistics-warehouse `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/stock_demand_estimate_matrix/__init__.py b/stock_demand_estimate_matrix/__init__.py new file mode 100644 index 000000000000..aee8895e7a31 --- /dev/null +++ b/stock_demand_estimate_matrix/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import wizards diff --git a/stock_demand_estimate_matrix/__manifest__.py b/stock_demand_estimate_matrix/__manifest__.py new file mode 100644 index 000000000000..6c7566aae74a --- /dev/null +++ b/stock_demand_estimate_matrix/__manifest__.py @@ -0,0 +1,20 @@ +# Copyright 2019 ForgeFlow S.L. (https://www.forgeflow.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +{ + "name": "Stock Demand Estimate Matrix", + "summary": "Allows to create demand estimates.", + "version": "15.0.1.0.0", + "author": "ForgeFlow, Odoo Community Association (OCA)", + "development_status": "Production/Stable", + "website": "https://github.com/OCA/stock-logistics-warehouse", + "category": "Warehouse", + "depends": ["stock_demand_estimate", "web_widget_x2many_2d_matrix", "date_range"], + "data": [ + "security/ir.model.access.csv", + "views/stock_demand_estimate_view.xml", + "views/date_range.xml", + "wizards/stock_demand_estimate_wizard_view.xml", + ], + "license": "AGPL-3", + "installable": True, +} diff --git a/stock_demand_estimate_matrix/i18n/stock_demand_estimate_matrix.pot b/stock_demand_estimate_matrix/i18n/stock_demand_estimate_matrix.pot new file mode 100644 index 000000000000..c33967409708 --- /dev/null +++ b/stock_demand_estimate_matrix/i18n/stock_demand_estimate_matrix.pot @@ -0,0 +1,261 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * stock_demand_estimate_matrix +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \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_demand_estimate_matrix +#: model_terms:ir.ui.view,arch_db:stock_demand_estimate_matrix.demand_estimate_wizard_view_form +#: model_terms:ir.ui.view,arch_db:stock_demand_estimate_matrix.stock_demand_estimate_sheet_view_form +msgid "Cancel" +msgstr "" + +#. module: stock_demand_estimate_matrix +#: model:ir.actions.act_window,name:stock_demand_estimate_matrix.stock_demand_estimate_wizard_action +#: model:ir.ui.menu,name:stock_demand_estimate_matrix.stock_demand_estimate_wizard_menu +msgid "Create Stock Demand Estimates" +msgstr "" + +#. module: stock_demand_estimate_matrix +#: model:ir.model.fields,field_description:stock_demand_estimate_matrix.field_stock_demand_estimate_sheet__create_uid +#: model:ir.model.fields,field_description:stock_demand_estimate_matrix.field_stock_demand_estimate_sheet_line__create_uid +#: model:ir.model.fields,field_description:stock_demand_estimate_matrix.field_stock_demand_estimate_wizard__create_uid +msgid "Created by" +msgstr "" + +#. module: stock_demand_estimate_matrix +#: model:ir.model.fields,field_description:stock_demand_estimate_matrix.field_stock_demand_estimate_sheet__create_date +#: model:ir.model.fields,field_description:stock_demand_estimate_matrix.field_stock_demand_estimate_sheet_line__create_date +#: model:ir.model.fields,field_description:stock_demand_estimate_matrix.field_stock_demand_estimate_wizard__create_date +msgid "Created on" +msgstr "" + +#. module: stock_demand_estimate_matrix +#: model:ir.model.fields,field_description:stock_demand_estimate_matrix.field_stock_demand_estimate_sheet__date_start +#: model:ir.model.fields,field_description:stock_demand_estimate_matrix.field_stock_demand_estimate_wizard__date_start +msgid "Date From" +msgstr "" + +#. module: stock_demand_estimate_matrix +#: model:ir.model,name:stock_demand_estimate_matrix.model_date_range +msgid "Date Range" +msgstr "" + +#. module: stock_demand_estimate_matrix +#: model:ir.model.fields,field_description:stock_demand_estimate_matrix.field_stock_demand_estimate_sheet__date_range_type_id +#: model:ir.model.fields,field_description:stock_demand_estimate_matrix.field_stock_demand_estimate_wizard__date_range_type_id +msgid "Date Range Type" +msgstr "" + +#. module: stock_demand_estimate_matrix +#: model:ir.ui.menu,name:stock_demand_estimate_matrix.date_range_menu +msgid "Date Ranges" +msgstr "" + +#. module: stock_demand_estimate_matrix +#: model:ir.model.fields,field_description:stock_demand_estimate_matrix.field_stock_demand_estimate_wizard__date_end +msgid "Date To" +msgstr "" + +#. module: stock_demand_estimate_matrix +#: model:ir.model.fields,field_description:stock_demand_estimate_matrix.field_stock_demand_estimate_sheet__date_end +msgid "Date to" +msgstr "" + +#. module: stock_demand_estimate_matrix +#: model:ir.model.fields,field_description:stock_demand_estimate_matrix.field_date_range__days +msgid "Days between dates" +msgstr "" + +#. module: stock_demand_estimate_matrix +#: model:ir.model.fields,field_description:stock_demand_estimate_matrix.field_date_range__display_name +#: model:ir.model.fields,field_description:stock_demand_estimate_matrix.field_stock_demand_estimate__display_name +#: model:ir.model.fields,field_description:stock_demand_estimate_matrix.field_stock_demand_estimate_sheet__display_name +#: model:ir.model.fields,field_description:stock_demand_estimate_matrix.field_stock_demand_estimate_sheet_line__display_name +#: model:ir.model.fields,field_description:stock_demand_estimate_matrix.field_stock_demand_estimate_wizard__display_name +msgid "Display Name" +msgstr "" + +#. module: stock_demand_estimate_matrix +#: model:ir.model.fields,field_description:stock_demand_estimate_matrix.field_stock_demand_estimate_sheet_line__estimate_id +msgid "Estimate" +msgstr "" + +#. module: stock_demand_estimate_matrix +#: code:addons/stock_demand_estimate_matrix/wizards/stock_demand_estimate_wizard.py:0 +#, python-format +msgid "Estimate Sheet" +msgstr "" + +#. module: stock_demand_estimate_matrix +#: model_terms:ir.ui.view,arch_db:stock_demand_estimate_matrix.stock_demand_estimate_sheet_view_form +msgid "Estimated quantity" +msgstr "" + +#. module: stock_demand_estimate_matrix +#: model:ir.model.fields,field_description:stock_demand_estimate_matrix.field_stock_demand_estimate_sheet__line_ids +msgid "Estimates" +msgstr "" + +#. module: stock_demand_estimate_matrix +#: model:ir.model.fields,field_description:stock_demand_estimate_matrix.field_stock_demand_estimate__date_range_id +msgid "Estimating Period" +msgstr "" + +#. module: stock_demand_estimate_matrix +#: model:ir.model.fields,field_description:stock_demand_estimate_matrix.field_date_range__id +#: model:ir.model.fields,field_description:stock_demand_estimate_matrix.field_stock_demand_estimate__id +#: model:ir.model.fields,field_description:stock_demand_estimate_matrix.field_stock_demand_estimate_sheet__id +#: model:ir.model.fields,field_description:stock_demand_estimate_matrix.field_stock_demand_estimate_sheet_line__id +#: model:ir.model.fields,field_description:stock_demand_estimate_matrix.field_stock_demand_estimate_wizard__id +msgid "ID" +msgstr "" + +#. module: stock_demand_estimate_matrix +#: model:ir.model.fields,field_description:stock_demand_estimate_matrix.field_date_range____last_update +#: model:ir.model.fields,field_description:stock_demand_estimate_matrix.field_stock_demand_estimate____last_update +#: model:ir.model.fields,field_description:stock_demand_estimate_matrix.field_stock_demand_estimate_sheet____last_update +#: model:ir.model.fields,field_description:stock_demand_estimate_matrix.field_stock_demand_estimate_sheet_line____last_update +#: model:ir.model.fields,field_description:stock_demand_estimate_matrix.field_stock_demand_estimate_wizard____last_update +msgid "Last Modified on" +msgstr "" + +#. module: stock_demand_estimate_matrix +#: model:ir.model.fields,field_description:stock_demand_estimate_matrix.field_stock_demand_estimate_sheet__write_uid +#: model:ir.model.fields,field_description:stock_demand_estimate_matrix.field_stock_demand_estimate_sheet_line__write_uid +#: model:ir.model.fields,field_description:stock_demand_estimate_matrix.field_stock_demand_estimate_wizard__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: stock_demand_estimate_matrix +#: model:ir.model.fields,field_description:stock_demand_estimate_matrix.field_stock_demand_estimate_sheet__write_date +#: model:ir.model.fields,field_description:stock_demand_estimate_matrix.field_stock_demand_estimate_sheet_line__write_date +#: model:ir.model.fields,field_description:stock_demand_estimate_matrix.field_stock_demand_estimate_wizard__write_date +msgid "Last Updated on" +msgstr "" + +#. module: stock_demand_estimate_matrix +#: model:ir.model.fields,field_description:stock_demand_estimate_matrix.field_stock_demand_estimate_sheet__location_id +#: model:ir.model.fields,field_description:stock_demand_estimate_matrix.field_stock_demand_estimate_wizard__location_id +msgid "Location" +msgstr "" + +#. module: stock_demand_estimate_matrix +#: model:ir.model.fields,field_description:stock_demand_estimate_matrix.field_stock_demand_estimate_sheet_line__date_range_id +#: model_terms:ir.ui.view,arch_db:stock_demand_estimate_matrix.demand_estimate_wizard_view_form +#: model_terms:ir.ui.view,arch_db:stock_demand_estimate_matrix.stock_demand_estimate_sheet_view_form +msgid "Period" +msgstr "" + +#. module: stock_demand_estimate_matrix +#: model:ir.model.fields,field_description:stock_demand_estimate_matrix.field_stock_demand_estimate_sheet_line__value_x +msgid "Period Name" +msgstr "" + +#. module: stock_demand_estimate_matrix +#: model_terms:ir.ui.view,arch_db:stock_demand_estimate_matrix.demand_estimate_wizard_view_form +msgid "Prepare" +msgstr "" + +#. module: stock_demand_estimate_matrix +#: model:ir.model.fields,field_description:stock_demand_estimate_matrix.field_stock_demand_estimate_sheet_line__product_id +msgid "Product" +msgstr "" + +#. module: stock_demand_estimate_matrix +#: model:ir.model.fields,field_description:stock_demand_estimate_matrix.field_stock_demand_estimate_sheet_line__value_y +msgid "Product Name" +msgstr "" + +#. module: stock_demand_estimate_matrix +#: model:ir.model.fields,field_description:stock_demand_estimate_matrix.field_stock_demand_estimate_sheet__product_ids +#: model:ir.model.fields,field_description:stock_demand_estimate_matrix.field_stock_demand_estimate_wizard__product_ids +#: model_terms:ir.ui.view,arch_db:stock_demand_estimate_matrix.demand_estimate_wizard_view_form +msgid "Products" +msgstr "" + +#. module: stock_demand_estimate_matrix +#: model:ir.model.fields,field_description:stock_demand_estimate_matrix.field_stock_demand_estimate_sheet_line__product_uom_qty +msgid "Quantity" +msgstr "" + +#. module: stock_demand_estimate_matrix +#: model:ir.model,name:stock_demand_estimate_matrix.model_stock_demand_estimate +msgid "Stock Demand Estimate Line" +msgstr "" + +#. module: stock_demand_estimate_matrix +#: model:ir.model,name:stock_demand_estimate_matrix.model_stock_demand_estimate_sheet +#: model_terms:ir.ui.view,arch_db:stock_demand_estimate_matrix.stock_demand_estimate_sheet_view_form +msgid "Stock Demand Estimate Sheet" +msgstr "" + +#. module: stock_demand_estimate_matrix +#: model:ir.model,name:stock_demand_estimate_matrix.model_stock_demand_estimate_sheet_line +msgid "Stock Demand Estimate Sheet Line" +msgstr "" + +#. module: stock_demand_estimate_matrix +#: model:ir.model,name:stock_demand_estimate_matrix.model_stock_demand_estimate_wizard +#: model_terms:ir.ui.view,arch_db:stock_demand_estimate_matrix.demand_estimate_wizard_view_form +msgid "Stock Demand Estimate Wizard" +msgstr "" + +#. module: stock_demand_estimate_matrix +#: code:addons/stock_demand_estimate_matrix/wizards/stock_demand_estimate_sheet.py:0 +#, python-format +msgid "Stock Demand Estimates" +msgstr "" + +#. module: stock_demand_estimate_matrix +#: model:ir.model.fields,field_description:stock_demand_estimate_matrix.field_stock_demand_estimate_sheet_line__location_id +msgid "Stock Location" +msgstr "" + +#. module: stock_demand_estimate_matrix +#: code:addons/stock_demand_estimate_matrix/wizards/stock_demand_estimate_wizard.py:0 +#, python-format +msgid "The start date cannot be later than the end date." +msgstr "" + +#. module: stock_demand_estimate_matrix +#: code:addons/stock_demand_estimate_matrix/wizards/stock_demand_estimate_sheet.py:0 +#, python-format +msgid "There is no ranges created." +msgstr "" + +#. module: stock_demand_estimate_matrix +#: model:ir.model.fields,field_description:stock_demand_estimate_matrix.field_stock_demand_estimate_sheet_line__product_uom +msgid "Unit of measure" +msgstr "" + +#. module: stock_demand_estimate_matrix +#: model_terms:ir.ui.view,arch_db:stock_demand_estimate_matrix.stock_demand_estimate_sheet_view_form +msgid "Validate" +msgstr "" + +#. module: stock_demand_estimate_matrix +#: code:addons/stock_demand_estimate_matrix/wizards/stock_demand_estimate_wizard.py:0 +#, python-format +msgid "You must select at least one product." +msgstr "" + +#. module: stock_demand_estimate_matrix +#: model_terms:ir.ui.view,arch_db:stock_demand_estimate_matrix.demand_estimate_wizard_view_form +msgid "or" +msgstr "" + +#. module: stock_demand_estimate_matrix +#: model_terms:ir.ui.view,arch_db:stock_demand_estimate_matrix.demand_estimate_wizard_view_form +#: model_terms:ir.ui.view,arch_db:stock_demand_estimate_matrix.stock_demand_estimate_sheet_view_form +msgid "to" +msgstr "" diff --git a/stock_demand_estimate_matrix/models/__init__.py b/stock_demand_estimate_matrix/models/__init__.py new file mode 100644 index 000000000000..e701aeac94ae --- /dev/null +++ b/stock_demand_estimate_matrix/models/__init__.py @@ -0,0 +1,2 @@ +from . import stock_demand_estimate +from . import date_range diff --git a/stock_demand_estimate_matrix/models/date_range.py b/stock_demand_estimate_matrix/models/date_range.py new file mode 100644 index 000000000000..96418aa53c54 --- /dev/null +++ b/stock_demand_estimate_matrix/models/date_range.py @@ -0,0 +1,19 @@ +# Copyright 2019 ForgeFlow S.L. (https://www.forgeflow.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import api, fields, models + + +class DateRange(models.Model): + _inherit = "date.range" + + days = fields.Integer( + string="Days between dates", + compute="_compute_days", + readonly=True, + ) + + @api.depends("date_start", "date_end") + def _compute_days(self): + for rec in self.filtered(lambda x: x.date_start and x.date_end): + rec.days = abs((rec.date_end - rec.date_start).days) + 1 diff --git a/stock_demand_estimate_matrix/models/stock_demand_estimate.py b/stock_demand_estimate_matrix/models/stock_demand_estimate.py new file mode 100644 index 000000000000..c1f3d80b18cc --- /dev/null +++ b/stock_demand_estimate_matrix/models/stock_demand_estimate.py @@ -0,0 +1,39 @@ +# Copyright 2019 ForgeFlow S.L. (https://www.forgeflow.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import api, fields, models + + +class StockDemandEstimate(models.Model): + _inherit = "stock.demand.estimate" + + date_range_id = fields.Many2one( + comodel_name="date.range", string="Estimating Period", ondelete="restrict" + ) + + @api.depends( + "date_range_id", + "manual_duration", + "manual_date_from", + "manual_date_to", + ) + def _compute_dates(self): + date_range_records = self.filtered(lambda r: r.date_range_id) + res = super(StockDemandEstimate, self - date_range_records)._compute_dates() + for rec in date_range_records: + rec.date_from = rec.date_range_id.date_start + rec.date_to = rec.date_range_id.date_end + rec.duration = rec.date_range_id.days + return res + + def name_get(self): + date_range_records = self.filtered(lambda r: r.date_range_id) + res = super(StockDemandEstimate, self - date_range_records).name_get() + for rec in date_range_records: + name = "{} - {} - {}".format( + rec.date_range_id.name, + rec.product_id.name, + rec.location_id.name, + ) + res.append((rec.id, name)) + return res diff --git a/stock_demand_estimate_matrix/readme/CONTRIBUTORS.rst b/stock_demand_estimate_matrix/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000000..f20a4c7c0917 --- /dev/null +++ b/stock_demand_estimate_matrix/readme/CONTRIBUTORS.rst @@ -0,0 +1,3 @@ +* Jordi Ballester Alomar +* Lois Rilo +* Pimolnat Suntian diff --git a/stock_demand_estimate_matrix/readme/DESCRIPTION.rst b/stock_demand_estimate_matrix/readme/DESCRIPTION.rst new file mode 100644 index 000000000000..9f8401d71537 --- /dev/null +++ b/stock_demand_estimate_matrix/readme/DESCRIPTION.rst @@ -0,0 +1,4 @@ +This module allows to create demand estimates for a given product and +location, on configurable time periods. + +The module does not provide in itself any specific usage of the estimates. diff --git a/stock_demand_estimate_matrix/readme/INSTALL.rst b/stock_demand_estimate_matrix/readme/INSTALL.rst new file mode 100644 index 000000000000..7f06842c9a53 --- /dev/null +++ b/stock_demand_estimate_matrix/readme/INSTALL.rst @@ -0,0 +1,6 @@ +This module relies on: + +* The OCA module '2D matrix for x2many fields', and can be downloaded from + Github: https://github.com/OCA/web/tree/13.0/web_widget_x2many_2d_matrix +* The OCA module 'Date Range', and can be downloaded from + Github: https://github.com/OCA/server-ux/tree/13.0/date_range diff --git a/stock_demand_estimate_matrix/readme/USAGE.rst b/stock_demand_estimate_matrix/readme/USAGE.rst new file mode 100644 index 000000000000..f59ade24694e --- /dev/null +++ b/stock_demand_estimate_matrix/readme/USAGE.rst @@ -0,0 +1,7 @@ +Go to *Inventory > Configuration > Date Ranges* and define your estimating periods. + +Go to *Inventory > Demand Planning > Create Demand Estimates* to create or +update your demand estimates. + +Go to *Inventory > Demand Planning > Demand Estimates* to review the +estimates created. diff --git a/stock_demand_estimate_matrix/security/ir.model.access.csv b/stock_demand_estimate_matrix/security/ir.model.access.csv new file mode 100644 index 000000000000..512e45b60ef1 --- /dev/null +++ b/stock_demand_estimate_matrix/security/ir.model.access.csv @@ -0,0 +1,4 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_stock_demand_estimate_sheet,access stock.demand.estimate.sheet,model_stock_demand_estimate_sheet,base.group_user,1,1,1,1 +access_stock_demand_estimate_sheet_line,access stock.demand.estimate.sheet.line,model_stock_demand_estimate_sheet_line,base.group_user,1,1,1,1 +access_stock_demand_estimate_wizard,access stock.demand.estimate.wizard,model_stock_demand_estimate_wizard,base.group_user,1,1,1,1 diff --git a/stock_demand_estimate_matrix/static/description/icon.png b/stock_demand_estimate_matrix/static/description/icon.png new file mode 100644 index 000000000000..3a0328b516c4 Binary files /dev/null and b/stock_demand_estimate_matrix/static/description/icon.png differ diff --git a/stock_demand_estimate_matrix/static/description/index.html b/stock_demand_estimate_matrix/static/description/index.html new file mode 100644 index 000000000000..49a99a6e58ea --- /dev/null +++ b/stock_demand_estimate_matrix/static/description/index.html @@ -0,0 +1,443 @@ + + + + + + +Stock Demand Estimate Matrix + + + +
+

Stock Demand Estimate Matrix

+ + +

Beta License: AGPL-3 OCA/stock-logistics-warehouse Translate me on Weblate Try me on Runbot

+

This module allows to create demand estimates for a given product and +location, on configurable time periods.

+

The module does not provide in itself any specific usage of the estimates.

+

Table of contents

+ +
+

Installation

+

This module relies on:

+ +
+
+

Usage

+

Go to Inventory > Configuration > Date Ranges and define your estimating periods.

+

Go to Inventory > Demand Planning > Create Demand Estimates to create or +update your demand estimates.

+

Go to Inventory > Demand Planning > Demand Estimates to review the +estimates created.

+
+
+

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

+
    +
  • ForgeFlow
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

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/stock-logistics-warehouse project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/stock_demand_estimate_matrix/tests/__init__.py b/stock_demand_estimate_matrix/tests/__init__.py new file mode 100644 index 000000000000..c8d75c468168 --- /dev/null +++ b/stock_demand_estimate_matrix/tests/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from . import test_stock_demand_estimate diff --git a/stock_demand_estimate_matrix/tests/test_stock_demand_estimate.py b/stock_demand_estimate_matrix/tests/test_stock_demand_estimate.py new file mode 100644 index 000000000000..e334572162f6 --- /dev/null +++ b/stock_demand_estimate_matrix/tests/test_stock_demand_estimate.py @@ -0,0 +1,201 @@ +# Copyright 2019 ForgeFlow S.L. (https://www.forgeflow.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from dateutil.rrule import MONTHLY + +from odoo import fields +from odoo.exceptions import ValidationError +from odoo.tests.common import SavepointCase + + +class TestStockDemandEstimate(SavepointCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.res_users_model = cls.env["res.users"] + cls.product_model = cls.env["product.product"] + cls.stock_location_model = cls.env["stock.location"] + cls.estimate_model = cls.env["stock.demand.estimate"] + + cls.g_stock_manager = cls.env.ref("stock.group_stock_manager") + cls.g_stock_user = cls.env.ref("stock.group_stock_user") + cls.company = cls.env.ref("base.main_company") + + # Create users: + cls.manager = cls._create_user( + "user_1", + [cls.g_stock_manager], + cls.company, + ).id + cls.user = cls._create_user( + "user_2", + [cls.g_stock_user], + cls.company, + ).id + cls.drt_monthly = cls.env["date.range.type"].create( + {"name": "Month", "allow_overlap": False} + ) + + generator = cls.env["date.range.generator"].create( + { + "date_start": "1943-01-01", + "name_prefix": "1943-", + "type_id": cls.drt_monthly.id, + "duration_count": 1, + "unit_of_time": str(MONTHLY), + "count": 12, + } + ) + generator.action_apply() + + # Create a product: + cls.product_1 = cls.product_model.create( + {"name": "Test Product 1", "type": "product", "default_code": "PROD1"} + ) + # Create a location: + cls.location = cls.stock_location_model.create( + {"name": "Place", "usage": "production"} + ) + + @classmethod + def _create_user(cls, login, groups, company): + group_ids = [group.id for group in groups] + user = cls.res_users_model.create( + { + "name": login, + "login": login, + "password": "demo", + "email": "example@yourcompany.com", + "company_id": company.id, + "company_ids": [(4, company.id)], + "groups_id": [(6, 0, group_ids)], + } + ) + return user + + def test_01_demand_estimate_wizard(self): + """Tests creation of demand estimates using wizard.""" + sheets = self.env["stock.demand.estimate.sheet"].search([]) + for sheet in sheets: + sheet.unlink() + wiz = self.env["stock.demand.estimate.wizard"] + wiz = wiz.create( + { + "date_start": "1943-01-01", + "date_end": "1943-12-31", + "location_id": self.location.id, + "date_range_type_id": self.drt_monthly.id, + "product_ids": [(6, 0, [self.product_1.id])], + } + ) + wiz.create_sheet() + sheets = self.env["stock.demand.estimate.sheet"].search([]) + for sheet in sheets: + self.assertEqual( + len(sheet.line_ids), + 12, + "There should be 12 lines.", + ) + self.assertEqual( + fields.Date.to_string(sheet.date_start), + "1943-01-01", + "The date start should be 1943-01-01", + ) + self.assertEqual( + fields.Date.to_string(sheet.date_end), + "1943-12-31", + "The date end should be 1943-12-31", + ) + self.assertEqual( + sheet.location_id.id, + self.location.id, + "Wrong location", + ) + for line in sheet.line_ids: + line.product_uom_qty = 1 + self.assertEqual( + line.product_id.id, + self.product_1.id, + "The product does not match in the line", + ) + self.assertEqual( + line.location_id.id, + self.location.id, + "The product does not match in the line", + ) + sheet.button_validate() + ranges = self.env["date.range"].search( + [("type_id", "=", self.drt_monthly.id)], + ) + estimates = self.env["stock.demand.estimate"].search( + [("date_range_id", "in", ranges.ids)] + ) + self.assertEqual( + len(estimates), + 12, + "There should be 12 estimate records.", + ) + for estimate in estimates: + self.assertEqual( + estimate.product_id.id, + self.product_1.id, + "The product does not match in the estimate", + ) + self.assertEqual( + estimate.location_id.id, + self.location.id, + "The product does not match in the estimate", + ) + + sheets = self.env["stock.demand.estimate.sheet"].search([]) + for sheet in sheets: + sheet.unlink() + wiz = self.env["stock.demand.estimate.wizard"] + wiz = wiz.create( + { + "date_start": "1943-01-01", + "date_end": "1943-12-31", + "location_id": self.location.id, + "date_range_type_id": self.drt_monthly.id, + "product_ids": [(6, 0, [self.product_1.id])], + } + ) + wiz.create_sheet() + sheets = self.env["stock.demand.estimate.sheet"].search([]) + for sheet in sheets: + for line in sheet.line_ids: + self.assertEqual( + line.product_uom_qty, + 1, + "The quantity should be 1", + ) + + def test_02_invalid_dates(self): + wiz = self.env["stock.demand.estimate.wizard"] + with self.assertRaises(ValidationError): + wiz.create( + { + "date_start": "1943-12-31", + "date_end": "1943-01-01", + "location_id": self.location.id, + "date_range_type_id": self.drt_monthly.id, + "product_ids": [(6, 0, [self.product_1.id])], + } + ) + + def test_03_computed_fields(self): + date_range = self.env["date.range"].search( + [("type_id", "=", self.drt_monthly.id)], limit=1 + ) + estimate = self.estimate_model.create( + { + "product_id": self.product_1.id, + "location_id": self.location.id, + "date_range_id": date_range.id, + "product_uom_qty": 100.0, + } + ) + expected_date_from = date_range.date_start + expected_date_to = date_range.date_end + self.assertEqual(estimate.date_from, expected_date_from) + self.assertEqual(estimate.date_to, expected_date_to) diff --git a/stock_demand_estimate_matrix/views/date_range.xml b/stock_demand_estimate_matrix/views/date_range.xml new file mode 100644 index 000000000000..3112531692f1 --- /dev/null +++ b/stock_demand_estimate_matrix/views/date_range.xml @@ -0,0 +1,10 @@ + + + + diff --git a/stock_demand_estimate_matrix/views/stock_demand_estimate_view.xml b/stock_demand_estimate_matrix/views/stock_demand_estimate_view.xml new file mode 100644 index 000000000000..f0835e5912a8 --- /dev/null +++ b/stock_demand_estimate_matrix/views/stock_demand_estimate_view.xml @@ -0,0 +1,52 @@ + + + + stock.demand.estimate.tree + stock.demand.estimate + + + + + + + top + false + + + + + stock.demand.estimate.pivot + stock.demand.estimate + + + + + + + + + stock.demand.estimate.search + stock.demand.estimate + + + + + + + + + tree,pivot + + diff --git a/stock_demand_estimate_matrix/wizards/__init__.py b/stock_demand_estimate_matrix/wizards/__init__.py new file mode 100644 index 000000000000..0bab6fa52051 --- /dev/null +++ b/stock_demand_estimate_matrix/wizards/__init__.py @@ -0,0 +1,5 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from . import stock_demand_estimate_wizard +from . import stock_demand_estimate_sheet +from . import stock_demand_estimate_sheet_line diff --git a/stock_demand_estimate_matrix/wizards/stock_demand_estimate_sheet.py b/stock_demand_estimate_matrix/wizards/stock_demand_estimate_sheet.py new file mode 100644 index 000000000000..cf96d9833006 --- /dev/null +++ b/stock_demand_estimate_matrix/wizards/stock_demand_estimate_sheet.py @@ -0,0 +1,161 @@ +# Copyright 2019 ForgeFlow S.L. (https://www.forgeflow.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import _, api, fields, models +from odoo.exceptions import UserError +from odoo.osv import expression + + +class StockDemandEstimateSheet(models.TransientModel): + _name = "stock.demand.estimate.sheet" + _description = "Stock Demand Estimate Sheet" + + date_start = fields.Date( + string="Date From", + readonly=True, + ) + date_end = fields.Date( + string="Date to", + readonly=True, + ) + date_range_type_id = fields.Many2one( + string="Date Range Type", + comodel_name="date.range.type", + readonly=True, + ) + location_id = fields.Many2one( + comodel_name="stock.location", + string="Location", + readonly=True, + ) + line_ids = fields.Many2many( + string="Estimates", + comodel_name="stock.demand.estimate.sheet.line", + relation="stock_demand_estimate_line_rel", + ) + product_ids = fields.Many2many( + string="Products", + comodel_name="product.product", + ) + + @api.onchange( + "date_start", + "date_end", + "date_range_type_id", + ) + def _onchange_dates(self): + for sheet in self: + if not all([sheet.date_start, sheet.date_end, sheet.date_range_type_id]): + return + ranges = sheet._get_ranges() + if not ranges: + raise UserError(_("There is no ranges created.")) + estimates = self.env["stock.demand.estimate"].search( + [ + ("product_id", "in", sheet.product_ids.ids), + ("date_range_id", "in", ranges.ids), + ("location_id", "=", sheet.location_id.id), + ] + ) + lines = [] + for product in sheet.product_ids: + for _range in ranges: + estimate = estimates.filtered( + lambda x: ( + x.date_range_id == _range and x.product_id == product + ) + ) + if estimate: + uom_id = fields.first(estimate).product_uom.id + uom_qty = estimate[0].product_uom_qty + estimate_id = estimate[0].id + else: + uom_id = product.uom_id.id + uom_qty = 0.0 + estimate_id = None + lines.append( + ( + 0, + 0, + sheet._get_default_estimate_line( + _range, + product, + uom_id, + uom_qty, + estimate_id=estimate_id, + ), + ) + ) + sheet.line_ids = lines + + def _get_ranges(self): + domain_1 = [ + "&", + ("type_id", "=", self.date_range_type_id.id), + "|", + "&", + ("date_start", ">=", self.date_start), + ("date_start", "<=", self.date_end), + "&", + ("date_end", ">=", self.date_start), + ("date_end", "<=", self.date_end), + ] + domain_2 = [ + "&", + ("type_id", "=", self.date_range_type_id.id), + "&", + ("date_start", "<=", self.date_start), + ("date_end", ">=", self.date_start), + ] + domain = expression.OR([domain_1, domain_2]) + ranges = self.env["date.range"].search(domain) + return ranges + + def _get_default_estimate_line( + self, _range, product, uom_id, uom_qty, estimate_id=None + ): + name_y = "{} - {}".format(product.name, product.uom_id.name) + if product.default_code: + name_y += "[{}] {}".format(product.default_code, name_y) + values = { + "value_x": _range.name, + "value_y": name_y, + "date_range_id": _range.id, + "product_id": product.id, + "product_uom": uom_id, + "product_uom_qty": uom_qty, + "location_id": self.location_id.id, + "estimate_id": estimate_id, + } + return values + + @api.model + def _prepare_estimate_data(self, line): + return { + "date_range_id": line.date_range_id.id, + "product_id": line.product_id.id, + "location_id": line.location_id.id, + "product_uom_qty": line.product_uom_qty, + "product_uom": line.product_id.uom_id.id, + } + + def button_validate(self): + res = [] + for line in self.line_ids: + if line.estimate_id: + line.estimate_id.product_uom_qty = line.product_uom_qty + res.append(line.estimate_id.id) + else: + data = self._prepare_estimate_data(line) + estimate = self.env["stock.demand.estimate"].create(data) + res.append(estimate.id) + res = { + "domain": [("id", "in", res)], + "name": _("Stock Demand Estimates"), + "src_model": "stock.demand.estimate.wizard", + "view_type": "form", + "view_mode": "tree", + "res_model": "stock.demand.estimate", + "type": "ir.actions.act_window", + } + return res diff --git a/stock_demand_estimate_matrix/wizards/stock_demand_estimate_sheet_line.py b/stock_demand_estimate_matrix/wizards/stock_demand_estimate_sheet_line.py new file mode 100644 index 000000000000..ec694f59d09d --- /dev/null +++ b/stock_demand_estimate_matrix/wizards/stock_demand_estimate_sheet_line.py @@ -0,0 +1,20 @@ +# Copyright 2019 ForgeFlow S.L. (https://www.forgeflow.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import fields, models + + +class StockDemandEstimateSheetLine(models.TransientModel): + _name = "stock.demand.estimate.sheet.line" + _description = "Stock Demand Estimate Sheet Line" + + estimate_id = fields.Many2one(comodel_name="stock.demand.estimate") + date_range_id = fields.Many2one(comodel_name="date.range", string="Period") + location_id = fields.Many2one( + comodel_name="stock.location", string="Stock Location" + ) + product_id = fields.Many2one(comodel_name="product.product", string="Product") + value_x = fields.Char(string="Period Name") + value_y = fields.Char(string="Product Name") + product_uom = fields.Many2one(comodel_name="uom.uom", string="Unit of measure") + product_uom_qty = fields.Float(string="Quantity", digits="Product UoM") diff --git a/stock_demand_estimate_matrix/wizards/stock_demand_estimate_wizard.py b/stock_demand_estimate_matrix/wizards/stock_demand_estimate_wizard.py new file mode 100644 index 000000000000..0e8f8eca7ade --- /dev/null +++ b/stock_demand_estimate_matrix/wizards/stock_demand_estimate_wizard.py @@ -0,0 +1,91 @@ +# Copyright 2019 ForgeFlow S.L. (https://www.forgeflow.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import _, api, fields, models +from odoo.exceptions import UserError, ValidationError + + +class DemandEstimateWizard(models.TransientModel): + _name = "stock.demand.estimate.wizard" + _description = "Stock Demand Estimate Wizard" + + date_start = fields.Date( + string="Date From", + required=True, + ) + date_end = fields.Date( + string="Date To", + required=True, + ) + date_range_type_id = fields.Many2one( + string="Date Range Type", + comodel_name="date.range.type", + required=True, + ) + location_id = fields.Many2one( + comodel_name="stock.location", + string="Location", + required=True, + ) + product_ids = fields.Many2many( + comodel_name="product.product", + string="Products", + ) + + @api.onchange("date_range_type_id") + def _onchange_date_range_type_id(self): + if self.date_range_type_id.company_id: + return { + "domain": { + "location_id": [ + ("company_id", "=", self.date_range_type_id.company_id.id) + ] + } + } + return {} + + @api.constrains("date_start", "date_end") + def _check_start_end_dates(self): + self.ensure_one() + if self.date_start > self.date_end: + raise ValidationError( + _("The start date cannot be later than the end date.") + ) + + def _prepare_demand_estimate_sheet(self): + self.ensure_one() + return { + "date_start": self.date_start, + "date_end": self.date_end, + "date_range_type_id": self.date_range_type_id.id, + "location_id": self.location_id.id, + } + + def create_sheet(self): + self.ensure_one() + if not self.product_ids: + raise UserError(_("You must select at least one product.")) + + # 2d matrix widget need real records to work + sheet = self.env["stock.demand.estimate.sheet"].create( + { + "date_start": self.date_start, + "date_end": self.date_end, + "date_range_type_id": self.date_range_type_id.id, + "location_id": self.location_id.id, + "product_ids": [(6, 0, self.product_ids.ids)], + } + ) + sheet._onchange_dates() + + res = { + "name": _("Estimate Sheet"), + "src_model": "stock.demand.estimate.wizard", + "view_type": "form", + "view_mode": "form", + "target": "new", + "res_model": "stock.demand.estimate.sheet", + "res_id": sheet.id, + "type": "ir.actions.act_window", + } + return res diff --git a/stock_demand_estimate_matrix/wizards/stock_demand_estimate_wizard_view.xml b/stock_demand_estimate_matrix/wizards/stock_demand_estimate_wizard_view.xml new file mode 100644 index 000000000000..a14f93c15524 --- /dev/null +++ b/stock_demand_estimate_matrix/wizards/stock_demand_estimate_wizard_view.xml @@ -0,0 +1,100 @@ + + + + stock.demand.estimate.sheet.form + stock.demand.estimate.sheet + +
+ + + + + + + + +
+ + + + + + + + + +
+
+ + + + + stock.demand.estimate.wizard.form + stock.demand.estimate.wizard + +
+ + + + + + + + + +
+ + + +
+
+ + + + + + Create Stock Demand Estimates + stock.demand.estimate.wizard + form + new + + + +