diff --git a/stock_average_daily_sale/__init__.py b/stock_average_daily_sale/__init__.py index 0650744..64face1 100644 --- a/stock_average_daily_sale/__init__.py +++ b/stock_average_daily_sale/__init__.py @@ -1 +1 @@ -from . import models +from . import models, wizards diff --git a/stock_average_daily_sale/__manifest__.py b/stock_average_daily_sale/__manifest__.py index 8194f54..582a9d3 100644 --- a/stock_average_daily_sale/__manifest__.py +++ b/stock_average_daily_sale/__manifest__.py @@ -13,18 +13,23 @@ "sale", "stock_storage_type_putaway_abc", "product_abc_classification", + "product_abc_classification_sale_stock", "product_route_mto", "stock_location_zone", ], "data": [ "security/stock_average_daily_sale_config.xml", "security/stock_average_daily_sale.xml", + "security/stock_average_daily_sale_demo.xml", "views/stock_average_daily_sale_config.xml", "views/stock_average_daily_sale.xml", + "views/abc_classification_profile.xml", "views/stock_warehouse.xml", "data/ir_cron.xml", ], + "external_dependencies": {"python": ["freezegun"]}, "demo": [ "demo/stock_average_daily_sale_config.xml", + "demo/stock_move.xml", ], } diff --git a/stock_average_daily_sale/demo/stock_average_daily_sale_config.xml b/stock_average_daily_sale/demo/stock_average_daily_sale_config.xml index 4a8e836..2b90e84 100644 --- a/stock_average_daily_sale/demo/stock_average_daily_sale_config.xml +++ b/stock_average_daily_sale/demo/stock_average_daily_sale_config.xml @@ -6,6 +6,10 @@ model="stock.average.daily.sale.config" id="stock_average_daily_sale_config_level_a" > + a 2 week @@ -17,6 +21,10 @@ model="stock.average.daily.sale.config" id="stock_average_daily_sale_config_level_b" > + b 13 week @@ -28,6 +36,10 @@ model="stock.average.daily.sale.config" id="stock_average_daily_sale_config_level_c" > + c 26 week diff --git a/stock_average_daily_sale/demo/stock_move.xml b/stock_average_daily_sale/demo/stock_move.xml new file mode 100644 index 0000000..20ba109 --- /dev/null +++ b/stock_average_daily_sale/demo/stock_move.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/stock_average_daily_sale/models/__init__.py b/stock_average_daily_sale/models/__init__.py index f6a34f5..756ee7b 100644 --- a/stock_average_daily_sale/models/__init__.py +++ b/stock_average_daily_sale/models/__init__.py @@ -1,3 +1,4 @@ from . import stock_warehouse # isort:skip from . import stock_average_daily_sale_config # isort:skip from . import stock_average_daily_sale # isort:skip +from . import abc_classification_profile diff --git a/stock_average_daily_sale/models/abc_classification_profile.py b/stock_average_daily_sale/models/abc_classification_profile.py new file mode 100644 index 0000000..e6275ab --- /dev/null +++ b/stock_average_daily_sale/models/abc_classification_profile.py @@ -0,0 +1,15 @@ +# Copyright 2023 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class AbcClassificationProfile(models.Model): + + _inherit = "abc.classification.profile" + + stock_average_daily_sale_config_ids = fields.One2many( + comodel_name="stock.average.daily.sale.config", + inverse_name="abc_classification_profile_id", + string="Average Daily Sale Configurations", + ) diff --git a/stock_average_daily_sale/models/stock_average_daily_sale.py b/stock_average_daily_sale/models/stock_average_daily_sale.py index 5eb4d9c..0dfb148 100644 --- a/stock_average_daily_sale/models/stock_average_daily_sale.py +++ b/stock_average_daily_sale/models/stock_average_daily_sale.py @@ -22,6 +22,11 @@ class StockAverageDailySale(models.Model): _order = "abc_classification_level ASC, product_id ASC" _description = "Average Daily Sale for Products" + abc_classification_profile_id = fields.Many2one( + comodel_name="abc.classification.profile", + required=True, + index=True, + ) abc_classification_level = fields.Selection( selection=ABC_SELECTION, required=True, readonly=True, index=True ) @@ -282,6 +287,7 @@ class StockAverageDailySale(models.Model): date_to, config_id, abc_classification_level, + cfg.abc_classification_profile_id, sale_ok, is_mto, sqty.qty_in_stock as qty_in_stock, diff --git a/stock_average_daily_sale/models/stock_average_daily_sale_config.py b/stock_average_daily_sale/models/stock_average_daily_sale_config.py index 95496a6..4b2521f 100644 --- a/stock_average_daily_sale/models/stock_average_daily_sale_config.py +++ b/stock_average_daily_sale/models/stock_average_daily_sale_config.py @@ -13,6 +13,11 @@ class StockAverageDailySaleConfig(models.Model): _name = "stock.average.daily.sale.config" _description = "Average daily sales computation parameters" + abc_classification_profile_id = fields.Many2one( + comodel_name="abc.classification.profile", + required=True, + ondelete="cascade", + ) abc_classification_level = fields.Selection( selection=ABC_SELECTION, required=True, readonly=True ) diff --git a/stock_average_daily_sale/security/stock_average_daily_sale_demo.xml b/stock_average_daily_sale/security/stock_average_daily_sale_demo.xml new file mode 100644 index 0000000..44ad925 --- /dev/null +++ b/stock_average_daily_sale/security/stock_average_daily_sale_demo.xml @@ -0,0 +1,14 @@ + + + + + stock.average.daily.sale.demo access user + + + + + + + + diff --git a/stock_average_daily_sale/views/abc_classification_profile.xml b/stock_average_daily_sale/views/abc_classification_profile.xml new file mode 100644 index 0000000..600e734 --- /dev/null +++ b/stock_average_daily_sale/views/abc_classification_profile.xml @@ -0,0 +1,20 @@ + + + + + abc.classification.profile.form (in stock_average_daily_sale) + abc.classification.profile + + + + + + + + diff --git a/stock_average_daily_sale/wizards/__init__.py b/stock_average_daily_sale/wizards/__init__.py new file mode 100644 index 0000000..d11cee8 --- /dev/null +++ b/stock_average_daily_sale/wizards/__init__.py @@ -0,0 +1 @@ +from . import stock_average_daily_sale_demo diff --git a/stock_average_daily_sale/wizards/stock_average_daily_sale_demo.py b/stock_average_daily_sale/wizards/stock_average_daily_sale_demo.py new file mode 100644 index 0000000..be60f4b --- /dev/null +++ b/stock_average_daily_sale/wizards/stock_average_daily_sale_demo.py @@ -0,0 +1,91 @@ +# Copyright 2023 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +import logging + +from dateutil.relativedelta import relativedelta +from freezegun import freeze_time + +from odoo import _, api, models +from odoo.fields import Date, Datetime + +_logger = logging.getLogger(__name__) + + +class StockAverageDailySaleDemo(models.TransientModel): + + _name = "stock.average.daily.sale.demo" + _description = "Wizard to populate demo data with past moves for Average Daily Sale" + + def _create_move(self, product, origin_location, qty): + suppliers = self.env.ref("stock.stock_location_suppliers") + customers = self.env.ref("stock.stock_location_customers") + move_obj = self.env["stock.move"] + # Create first an incoming move to avoid negative quantities + move = move_obj.create( + { + "product_id": product.id, + "name": product.name, + "location_id": suppliers.id, + "location_dest_id": customers.id, + "product_uom_qty": qty, + } + ) + move._action_confirm() + move._action_assign() + move.quantity_done = move.product_uom_qty + move._action_done() + + # Create the OUT move + move = move_obj.create( + { + "product_id": product.id, + "name": product.name, + "location_id": origin_location.id, + "location_dest_id": customers.id, + "product_uom_qty": qty, + "priority": "1", + } + ) + return move + + @api.model + def _create_movement(self, product): + now = Datetime.now() + stock = self.env.ref("stock.stock_location_stock") + move_1_date = Date.to_string(now - relativedelta(weeks=11)) + with freeze_time(move_1_date): + move = self._create_move(product, stock, 10.0) + move._action_confirm() + move._action_assign() + move.quantity_done = move.product_uom_qty + move._action_done() + move.priority = "1" + move_2_date = Date.to_string(now - relativedelta(weeks=9)) + with freeze_time(move_2_date): + move = self._create_move(product, stock, 12.0) + move._action_confirm() + move._action_assign() + move.quantity_done = move.product_uom_qty + move._action_done() + move.priority = "1" + + @api.model + def _action_create_data(self): + """ + This is called through an xml function in order to populate + demo data with past moves as the report depends on that. + """ + module = self.env["ir.module.module"].search( + [("name", "=", "stock_average_daily_sale"), ("demo", "=", True)] + ) + if not module: + _logger.warning( + _("You cannot call the _action_create_data() on production database.") + ) + return + product = self.env.ref("product.product_product_25") + self._create_movement(product) + product = self.env.ref("product.product_product_27") + self._create_movement(product) + + self.env["stock.average.daily.sale"].refresh_view()