diff --git a/beesdoo_stock_coverage/README.md b/beesdoo_stock_coverage/README.md deleted file mode 100644 index 5082494..0000000 --- a/beesdoo_stock_coverage/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# todo -- initializing the computed fields - - daily cron - - manual trigger diff --git a/beesdoo_stock_coverage/README.rst b/beesdoo_stock_coverage/README.rst new file mode 100644 index 0000000..b235324 --- /dev/null +++ b/beesdoo_stock_coverage/README.rst @@ -0,0 +1,58 @@ +======================== +Product - Stock Coverage +======================== + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! 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-odoo-cae%2Fodoo--addons--hr--incubator-lightgray.png?logo=github + :target: https://github.com/odoo-cae/odoo-addons-hr-incubator/tree/12.0/beesdoo_stock_coverage + :alt: odoo-cae/odoo-addons-hr-incubator + +|badge1| |badge2| |badge3| + +Compute estimated stock coverage based on product sales over a date range. + +**Table of contents** + +.. contents:: + :local: + +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 +~~~~~~~ + +* Coop IT Easy SCRLfs + +Contributors +~~~~~~~~~~~~ + +* Robin Keunen + + +Maintainers +~~~~~~~~~~~ + +This module is part of the `odoo-cae/odoo-addons-hr-incubator `_ project on GitHub. + +You are welcome to contribute. diff --git a/beesdoo_stock_coverage/__manifest__.py b/beesdoo_stock_coverage/__manifest__.py new file mode 100644 index 0000000..acfaf6f --- /dev/null +++ b/beesdoo_stock_coverage/__manifest__.py @@ -0,0 +1,16 @@ +# Copyright 2020 Coop IT Easy SCRL fs +# Robin Keunen +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +{ + "name": "Product - Stock Coverage", + "version": "12.0.0.0.1", + "category": "Product", + "summary": "Compute estimated stock coverage based on product sales over a date range.", + "author": "Coop IT Easy SCRLfs", + "website": "https://www.coopiteasy.be", + "license": "AGPL-3", + "depends": ["point_of_sale", "sale", "l10n_be"], # fixme + "data": ["views/product_template_view.xml", "data/cron.xml"], + "installable": True, + "application": False, +} diff --git a/beesdoo_stock_coverage/__openerp__.py b/beesdoo_stock_coverage/__openerp__.py deleted file mode 100644 index 4f07560..0000000 --- a/beesdoo_stock_coverage/__openerp__.py +++ /dev/null @@ -1,16 +0,0 @@ -# -*- encoding: utf-8 -*- -{ - "name": "Product - Stock Coverage", - "version": "9.0.1", - "category": "Product", - "description": """ -Shows figures in the product form related to stock coverage -There are settings in Inventory/settings to define the calculation range and -the display range. - """, - "author": "coop it easy", - "website": "coopiteasy.be", - "license": "AGPL-3", - "depends": ["product", "purchase", "point_of_sale", "stock"], - "data": ["views/product_template_view.xml", "data/cron.xml"], -} diff --git a/beesdoo_stock_coverage/data/cron.xml b/beesdoo_stock_coverage/data/cron.xml index 12775ac..a067a34 100644 --- a/beesdoo_stock_coverage/data/cron.xml +++ b/beesdoo_stock_coverage/data/cron.xml @@ -1,14 +1,17 @@ - - - - Stock Coverage - Update Article Consumption - 24 - hours - -1 - - product.template - _batch_compute_total_consumption - () - - + + + + + Stock Coverage - Update Article Sales Statistics + 1 + days + -1 + + code + + model.cron_compute_stock_coverage() + diff --git a/beesdoo_stock_coverage/models/product_template.py b/beesdoo_stock_coverage/models/product_template.py index 342f3a7..0821c24 100644 --- a/beesdoo_stock_coverage/models/product_template.py +++ b/beesdoo_stock_coverage/models/product_template.py @@ -1,126 +1,64 @@ -# -*- encoding: utf-8 -*- -from openerp import models, fields, api -import datetime as dt +# Copyright 2020 Coop IT Easy SCRL fs +# Robin Keunen +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import models, fields, api, _ +from odoo.exceptions import ValidationError class ProductTemplate(models.Model): _inherit = "product.template" - consumption_calculation_method = fields.Selection( - selection=[("sales_history", "Sales History")], - string="Consumption Calculation Method", - default="sales_history", - ) - calculation_range = fields.Integer("Calculation range (days)", default=14) - - average_consumption = fields.Float( - string="Average Consumption", - compute="_compute_average_daily_consumption", - readonly=True, - digits=(100, 2), + @api.multi + @api.constrains("computation_range") + def _check_computation_range(self): + for template in self: + if template.computation_range <= 0: + raise ValidationError( + _("Computation range must be greater than 0.") + ) + + computation_range = fields.Integer("Computation range (days)", default=14) + range_sales = fields.Float( + string="Sales over Range", compute="_compute_stock_coverage" ) - - total_consumption = fields.Float( - string="Total Consumption", - default=0, - compute="_compute_total_consumption", - store=True, - readonly=True, - digits=(100, 2), + daily_sales = fields.Float( + string="Daily Sales", compute="_compute_stock_coverage" ) - - estimated_stock_coverage = fields.Float( - string="Estimated Stock Coverage (days)", - compute="_compute_estimated_stock_coverage", - default=0, - digits=(100, 2), - readonly=True, + stock_coverage = fields.Float( + string="Stock Coverage (days)", compute="_compute_stock_coverage" ) @api.multi - @api.depends("total_consumption") - def _compute_average_daily_consumption(self): - for template in self: - if template.calculation_range > 0: - avg = template.total_consumption / template.calculation_range - else: - avg = 0 - template.average_consumption = avg - - return True - - @api.multi - @api.depends("calculation_range") - def _compute_total_consumption(self): + def _compute_stock_coverage(self): + query = """ + select template.id as product_template_id, + sum(pol.qty) as total_sales, + sum(pol.qty) / template.computation_range as daily_sales + from pos_order_line pol + join pos_order po ON pol.order_id = po.id + join product_product product ON pol.product_id = product.id + join product_template template ON product.product_tmpl_id = template.id + where po.state in ('done', 'invoiced', 'paid') + and template.active + and pol.create_date + BETWEEN date_trunc('day', now()) - template.computation_range * interval '1 days' + and date_trunc('day', now()) + and template.id in %(template_ids)s + group by product_template_id + """ + self.env.cr.execute(query, {"template_ids": tuple(self.ids)}) + results = {pid: (qty, avg) for pid, qty, avg in self.env.cr.fetchall()} for template in self: - products = self.env["product.product"].search( - [("product_tmpl_id", "=", template.id)] - ) - - today = dt.date.today() - pol_date_limit = today - dt.timedelta( - days=template.calculation_range - ) - - order_lines = self.env["pos.order.line"].search( - [ - ("product_id", "in", products.ids), - ( - "create_date", - ">", - fields.Datetime.to_string(pol_date_limit), - ), # noqa - ] - ) - - if order_lines: - order_lines = order_lines.filtered( - lambda ol: ol.order_id.state - in ["done", "invoiced", "paid"] - ) # noqa - template.total_consumption = sum(order_lines.mapped("qty")) - else: - template.total_consumption = 0 - return True - - @api.multi - @api.depends("total_consumption") - def _compute_estimated_stock_coverage(self): - for product_template in self: - qty = product_template.qty_available - avg = product_template.average_consumption - if avg > 0: - product_template.estimated_stock_coverage = qty / avg + qty, avg = results.get(template.id, (0, 0)) + template.range_sales = qty + template.daily_sales = avg + if avg != 0: + template.stock_coverage = template.virtual_available / avg else: - # todo what would be a good default value? (not float(inf)) - product_template.estimated_stock_coverage = 9999 - - return True + template.stock_coverage = 9999 @api.model - def _batch_compute_total_consumption(self): - products = self.env["product.template"].search([("active", "=", True)]) - - query = """ - select - template.id as product_template_id, - sum(pol.qty) as total_consumption - from pos_order_line pol - join pos_order po ON pol.order_id = po.id - join product_product product ON pol.product_id = product.id - join product_template template ON product.product_tmpl_id = template.id - where po.state in ('done', 'invoiced', 'paid') - and template.active - and pol.create_date - BETWEEN date_trunc('day', now()) - calculation_range * interval '1 days' - and date_trunc('day', now()) - group by product_template_id - """ # noqa - - self.env.cr.execute(query) - results = {pid: qty for pid, qty in self.env.cr.fetchall()} - - for product in products: - product.total_consumption = results.get( - product.id, product.total_consumption - ) + def cron_compute_stock_coverage(self): + templates = self.env["product.template"].search([]) + templates._compute_stock_coverage() diff --git a/beesdoo_stock_coverage/readme/CONTRIBUTORS.rst b/beesdoo_stock_coverage/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000..1798654 --- /dev/null +++ b/beesdoo_stock_coverage/readme/CONTRIBUTORS.rst @@ -0,0 +1,2 @@ +* Robin Keunen + diff --git a/beesdoo_stock_coverage/readme/DESCRIPTION.rst b/beesdoo_stock_coverage/readme/DESCRIPTION.rst new file mode 100644 index 0000000..fac23c2 --- /dev/null +++ b/beesdoo_stock_coverage/readme/DESCRIPTION.rst @@ -0,0 +1 @@ +Compute estimated stock coverage based on product sales over a date range. diff --git a/beesdoo_stock_coverage/static/description/index.html b/beesdoo_stock_coverage/static/description/index.html new file mode 100644 index 0000000..919df7c --- /dev/null +++ b/beesdoo_stock_coverage/static/description/index.html @@ -0,0 +1,414 @@ + + + + + + +Product - Stock Coverage + + + +
+

Product - Stock Coverage

+ + +

Beta License: AGPL-3 odoo-cae/odoo-addons-hr-incubator

+

Compute estimated stock coverage based on product sales over a date range.

+

Table of contents

+ +
+

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

+
    +
  • Coop IT Easy SCRLfs
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is part of the odoo-cae/odoo-addons-hr-incubator project on GitHub.

+

You are welcome to contribute.

+
+
+
+ + diff --git a/beesdoo_stock_coverage/tests/test_stock_coverage.py b/beesdoo_stock_coverage/tests/test_stock_coverage.py index 49849b5..847cc0b 100644 --- a/beesdoo_stock_coverage/tests/test_stock_coverage.py +++ b/beesdoo_stock_coverage/tests/test_stock_coverage.py @@ -1,105 +1,22 @@ -# -*- coding: utf-8 -*- +# Copyright 2020 Coop IT Easy SCRL fs +# Robin Keunen +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). -import datetime as dt -from openerp.tests.common import TransactionCase -_datetimes = map( - lambda d: d.strftime("%Y-%m-%d %H:%M:%S"), - (dt.datetime.now() - dt.timedelta(days=d) for d in range(0, 24, 2)), -) - -_quantities = [ - 0.64, - 6.45, - 9.65, - 1.76, - 9.14, - 3.99, - 6.92, - 2.25, - 6.91, - 1.44, - 6.52, - 1.44, -] +from odoo.tests.common import TransactionCase class TestProductTemplate(TransactionCase): - def setUp(self, *args, **kwargs): - result = super(TestProductTemplate, self).setUp(*args, **kwargs) - - test_product_template = self.env["product.template"].create( - { - "name": "test product template", - "calculation_range": 14, - "consumption_calculation_method": "sales_history", - "product_template_id": 0, - } - ) - - pid = ( - self.env["product.product"] - .search([("product_tmpl_id", "=", test_product_template.id)]) - .ids - ).pop() - - for date, qty in zip(_datetimes, _quantities): - ( - self.env["pos.order.line"].create( - {"create_date": date, "qty": qty, "product_id": pid} - ) - ) - - def _product_available(*args, **kwargs): - products = self.env["product.product"].search( - [("product_tmpl_id", "=", test_product_template.id)] - ) - mock_data = { - "qty_available": 53.2, - "incoming_qty": 14, - "outgoing_qty": 4.1, - "virtual_available": 53.2 + 14 - 4.1, - } - return {pid: mock_data for pid in products.ids} - - # mock area fixme - # ProductTemplate._product_available = _product_available - # ProductProduct._product_available = _product_available - # Order = namedtuple('Order', ['id', 'state']) - # PosOrderLine.order_id = Order('1', 'done') - - test_product_template._compute_total_consumption() - self.product_template_id = test_product_template.id - - return result - - def test_create(self): - """Create a simple product template""" - Template = self.env["product.template"] - product = Template.create({"name": "Test create product"}) - self.assertEqual(product.name, "Test create product") - - def test_compute_average_daily_consumption(self): - """Test computed field average_daily_consumption""" - ProductTemplate = self.env["product.template"] - product_template = ProductTemplate.browse(self.product_template_id) - - computed_value = product_template.average_consumption - expected_value = 4.08 - self.assertAlmostEqual(computed_value, expected_value, 7) - - def test_compute_total_consumption(self): - """Test total consumption was computed in setup""" - ProductTemplate = self.env["product.template"] - product_template = ProductTemplate.browse(self.product_template_id) - computed_value = product_template.total_consumption - expected_value = 57.11 - self.assertAlmostEqual(computed_value, expected_value) - - # def test_compute_estimated_stock_coverage(self): fixme - # """Test computed field estimated_stock_coverage""" - # ProductTemplate = self.env['product.template'] - # product_template = ProductTemplate.browse(self.product_template_id) - # computed_value = product_template.estimated_stock_coverage - # expected_value = 13.04 - # self.assertAlmostEqual(computed_value, expected_value) + def setUp(self): + super(TestProductTemplate, self).setUp() + self.product1 = self.browse_ref( + "point_of_sale.whiteboard_pen" + ).product_tmpl_id + + def test_compute_stock_coverage(self): + self.product1._compute_stock_coverage() + print("***") + print(self.product1.range_sales) + print(self.product1.daily_sales) + print(self.product1.stock_coverage) + print("***") diff --git a/beesdoo_stock_coverage/views/product_template_view.xml b/beesdoo_stock_coverage/views/product_template_view.xml index 5eb634e..9447a7f 100644 --- a/beesdoo_stock_coverage/views/product_template_view.xml +++ b/beesdoo_stock_coverage/views/product_template_view.xml @@ -1,40 +1,32 @@ - - template.consumption.form + + product.template.product.form product.template - - - - - - - - + + + + + + - - template.consumption.tree + + product.template.product.tree product.template - - - - - - - - - - - + + + + +