diff --git a/inventory_advanced_reports/README.rst b/inventory_advanced_reports/README.rst new file mode 100755 index 000000000..b5d6b63de --- /dev/null +++ b/inventory_advanced_reports/README.rst @@ -0,0 +1,49 @@ +.. image:: https://img.shields.io/badge/license-LGPL--3-green.svg + :target: https://www.gnu.org/licenses/lgpl-3.0-standalone.html + :alt: License: LGPL-3 + +Inventory Advanced Reports +========================== +Helps to manage different types of Inventory Reports like FSN Report, Out Of Stock Report, etc. + +Configuration +============= +- No additional configuration required. + +License +------- +Lesser General Public License, Version 3 (LGPL v3). +(https://www.gnu.org/licenses/lgpl-3.0-standalone.html) + +Company +------- +* `Cybrosys Techno Solutions `__ + +Credits +------- +Developer: (V15) Gayathri V, + (V16) Anusha C + +Contact : odoo@cybrosys.com + +Contacts +-------- +* Mail Contact : odoo@cybrosys.com +* Website : https://cybrosys.com + +Bug Tracker +----------- +Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. + +Maintainer +========== +.. image:: https://cybrosys.com/images/logo.png + :target: https://cybrosys.com + +This module is maintained by Cybrosys Technologies. + +For support and more information, please visit https://www.cybrosys.com + +Further information +=================== +HTML Description: ``__ diff --git a/inventory_advanced_reports/__init__.py b/inventory_advanced_reports/__init__.py new file mode 100644 index 000000000..194691531 --- /dev/null +++ b/inventory_advanced_reports/__init__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2024-TODAY Cybrosys Technologies() +# Author: Gayathri V (odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see . +# +############################################################################### +from . import controllers +from . import report +from . import wizard diff --git a/inventory_advanced_reports/__manifest__.py b/inventory_advanced_reports/__manifest__.py new file mode 100644 index 000000000..783bd5441 --- /dev/null +++ b/inventory_advanced_reports/__manifest__.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2024-TODAY Cybrosys Technologies() +# Author: Gayathri V (odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see . +# +############################################################################### +{ + "name": "Inventory Advanced Reports", + "version": "15.0.1.0.0", + "category": 'Warehouse', + "summary": "Helps to Manage different types of Inventory Reports.", + "description": "Helps to Manage different types of Inventory Reports like " + "FSN Report, Out Of Stock Report, Inventory XYZ Report, etc", + "author": "Cybrosys Techno Solutions", + 'company': 'Cybrosys Techno Solutions', + 'maintainer': 'Cybrosys Techno Solutions', + 'website': 'https://www.cybrosys.com', + "depends": ["stock", "purchase", "sale_management"], + "data": ["security/ir.model.access.csv", + "report/aging_report_views.xml", + "report/fsn_report_views.xml", + "report/xyz_report_views.xml", + "report/fsn_xyz_report_views.xml", + "report/out_of_stock_report_views.xml", + "report/over_stock_report_views.xml", + "report/age_breakdown_report_views.xml", + "report/stock_movement_report_views.xml", + "wizard/inventory_aging_report_views.xml", + "wizard/inventory_aging_data_report_views.xml", + "wizard/inventory_fsn_report_views.xml", + "wizard/inventory_fsn_data_report_views.xml", + "wizard/inventory_xyz_report_views.xml", + "wizard/inventory_xyz_data_report_views.xml", + "wizard/inventory_fsn_xyz_report_views.xml", + "wizard/inventory_fsn_xyz_data_report_views.xml", + "wizard/inventory_out_of_stock_report_views.xml", + "wizard/inventory_out_of_stock_data_report_views.xml", + "wizard/inventory_age_breakdown_report_views.xml", + "wizard/inventory_over_stock_report_views.xml", + "wizard/inventory_over_stock_data_report_views.xml", + "wizard/inventory_stock_movement_report_views.xml", + ], + 'assets': { + 'web.assets_backend': [ + 'inventory_advanced_reports/static/src/js/action_manager.js'] + }, + 'images': ['static/description/banner.png'], + 'license': 'LGPL-3', + 'installable': True, + 'auto_install': False, + 'application': False, +} diff --git a/inventory_advanced_reports/controllers/__init__.py b/inventory_advanced_reports/controllers/__init__.py new file mode 100644 index 000000000..f0901940c --- /dev/null +++ b/inventory_advanced_reports/controllers/__init__.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2024-TODAY Cybrosys Technologies() +# Author: Gayathri V (odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see . +# +############################################################################### +from . import inventory_advanced_reports diff --git a/inventory_advanced_reports/controllers/inventory_advanced_reports.py b/inventory_advanced_reports/controllers/inventory_advanced_reports.py new file mode 100644 index 000000000..287d05c2f --- /dev/null +++ b/inventory_advanced_reports/controllers/inventory_advanced_reports.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2024-TODAY Cybrosys Technologies() +# Author: Gayathri V (odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see . +# +############################################################################### +import json +from odoo import http +from odoo.http import content_disposition, request +from odoo.tools import html_escape + + +class XLSXReportController(http.Controller): + """This model is used to connect the frontend to the backend""" + + @http.route('/xlsx_reports', type='http', auth='public', methods=['POST'], + csrf=False) + def get_report_xlsx(self, model, options, output_format, report_name): + """This function is called when a post request is made to this route""" + uid = request.session.uid + report_obj = request.env[model].with_user(uid) + options = json.loads(options) + token = 'dummy-because-api-expects-one' + try: + if output_format == 'xlsx': + response = request.make_response( + None, + headers=[ + ('Content-Type', 'application/vnd.ms-excel'), + ('Content-Disposition', + content_disposition(report_name + '.xlsx')) + ]) + report_obj.get_xlsx_report(options, response) + response.set_cookie('fileToken', token) + return response + except Exception as exception: + serialise = http.serialize_exception(exception) + error = { + 'code': 200, + 'message': 'Odoo Server Error', + 'data': serialise + } + return request.make_response(html_escape(json.dumps(error))) diff --git a/inventory_advanced_reports/doc/RELEASE_NOTES.md b/inventory_advanced_reports/doc/RELEASE_NOTES.md new file mode 100755 index 000000000..237cfb274 --- /dev/null +++ b/inventory_advanced_reports/doc/RELEASE_NOTES.md @@ -0,0 +1,7 @@ +## Module + +#### 07.06.2024 +#### Version 15.0.1.0.0 +##### ADD + +- Initial Commit for Inventory Advanced Reports diff --git a/inventory_advanced_reports/report/__init__.py b/inventory_advanced_reports/report/__init__.py new file mode 100644 index 000000000..1b7b64dce --- /dev/null +++ b/inventory_advanced_reports/report/__init__.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2024-TODAY Cybrosys Technologies() +# Author: Gayathri V (odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see . +# +############################################################################### +from . import aging_report +from . import age_breakdown_report +from . import fsn_report +from . import fsn_xyz_report +from . import out_of_stock_report +from . import over_stock_report +from . import stock_movement_report +from . import xyz_report diff --git a/inventory_advanced_reports/report/age_breakdown_report.py b/inventory_advanced_reports/report/age_breakdown_report.py new file mode 100644 index 000000000..c0c4172ec --- /dev/null +++ b/inventory_advanced_reports/report/age_breakdown_report.py @@ -0,0 +1,180 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2024-TODAY Cybrosys Technologies() +# Author: Gayathri V (odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see . +# +############################################################################### +from odoo import api, models +from odoo.exceptions import ValidationError + + +class AgeBreakdownReport(models.AbstractModel): + """Create an abstract model for passing reporting values""" + _name = 'report.inventory_advanced_reports.report_inventory_breakdown' + _description = 'Age Breakdown Report' + + @api.model + def _get_report_values(self, docids, data=None): + """This function has working in get the pdf report.""" + values = data + product_ids = data['product_ids'] + category_ids = data['category_ids'] + company_ids = data['company_ids'] + age_breakdown_days = data['age_breakdown_days'] + params = [] + param_count = 0 + query = """ + SELECT + CASE + WHEN pp.default_code IS NOT NULL + THEN CONCAT(pp.default_code, ' - ', pt.name) + ELSE + pt.name + END AS product_code_and_name, + c.complete_name AS category_name, + c.id AS category_id, + pp.id AS product_id, + company.id AS company_id, + company.name AS company_name, + COALESCE(SUM(svl.remaining_qty), 0) AS qty_available, + SUM(svl.remaining_value) AS stock_value, + SUM(CASE + WHEN age.days_between >= 1 AND age.days_between <= %s + THEN svl.remaining_qty + ELSE 0 + END) AS "age_breakdown_qty_1", + SUM(CASE + WHEN age.days_between >= %s+1 AND age.days_between <= %s*2 + THEN svl.remaining_qty + ELSE 0 + END) AS "age_breakdown_qty_2", + SUM(CASE + WHEN age.days_between >= (%s*2)+1 AND age.days_between <= %s*3 + THEN svl.remaining_qty + ELSE 0 + END) AS "age_breakdown_qty_3", + SUM(CASE + WHEN age.days_between >= (%s*3)+1 AND age.days_between <= %s*4 + THEN svl.remaining_qty + ELSE 0 + END) AS "age_breakdown_qty_4", + SUM(CASE + WHEN age.days_between >= (%s*4)+1 THEN svl.remaining_qty + ELSE 0 + END) AS "age_breakdown_qty_5", + SUM(CASE + WHEN age.days_between >= 1 AND age.days_between <= %s + THEN svl.remaining_value + ELSE 0 + END) AS "age_breakdown_value_1", + SUM(CASE + WHEN age.days_between >= %s+1 AND age.days_between <= %s*2 + THEN svl.remaining_value + ELSE 0 + END) AS "age_breakdown_value_2", + SUM(CASE + WHEN age.days_between >= (%s*2)+1 AND age.days_between <= %s*3 + THEN svl.remaining_value + ELSE 0 + END) AS "age_breakdown_value_3", + SUM(CASE + WHEN age.days_between >= (%s*3)+1 AND age.days_between <= %s*4 + THEN svl.remaining_value + ELSE 0 + END) AS "age_breakdown_value_4", + SUM(CASE + WHEN age.days_between >= (%s*4)+1 THEN svl.remaining_value + ELSE 0 + END) AS "age_breakdown_value_5" + FROM product_product pp + INNER JOIN product_template pt ON pp.product_tmpl_id = pt.id + INNER JOIN product_category c ON pt.categ_id = c.id + LEFT JOIN stock_move sm ON sm.product_id = pp.id + LEFT JOIN stock_picking_type spt ON sm.picking_type_id = spt.id + LEFT JOIN res_company company ON sm.company_id = company.id + LEFT JOIN LATERAL ( + SELECT EXTRACT(day FROM CURRENT_DATE - sm.date) AS days_between + ) AS age ON true + INNER JOIN stock_valuation_layer svl ON svl.stock_move_id = sm.id + WHERE pt.detailed_type = 'product' + AND sm.state = 'done' + AND svl.remaining_value IS NOT NULL + """ + params.extend([age_breakdown_days] * 16) + if product_ids or category_ids: + query += " AND (" + if product_ids: + product_ids = [product_id for product_id in product_ids] + query += "pp.id = ANY(%s)" + params.append(product_ids) + param_count += 1 + if product_ids and category_ids: + query += " OR " + if category_ids: + category_ids = [category for category in category_ids] + params.append(category_ids) + query += "(pt.categ_id = ANY(%s))" + param_count += 1 + if product_ids or category_ids: + query += ")" + if company_ids: + company_ids = [company for company in company_ids] + query += " AND (sm.company_id = ANY(%s))" # Specify the table alias + params.append(company_ids) + param_count += 1 + query += """ + GROUP BY + CASE + WHEN pp.default_code IS NOT NULL + THEN CONCAT(pp.default_code, ' - ', + pt.name) + ELSE + pt.name + END, + c.complete_name, + company.id, + c.id, + company.name, + pp.id; + """ + self.env.cr.execute(query, params) + result_data = self.env.cr.dictfetchall() + main_header = age_breakdown_days + if result_data: + return { + 'doc_ids': docids, + 'doc_model': 'report.inventory_advanced_reports.' + 'report_inventory_breakdown', + 'data': values, + 'options': result_data, + 'main_header': self.get_header(main_header) + } + else: + raise ValidationError("No records found for the given criteria!") + + def get_header(self, main_header): + """For getting the header for the report""" + age_breakdown1 = main_header + age_breakdown2 = main_header * 2 + age_breakdown3 = main_header * 3 + age_breakdown4 = main_header * 4 + return ['1-' + str(age_breakdown1), + str(age_breakdown1 + 1) + '-' + str(age_breakdown2), + str(age_breakdown2 + 1) + '-' + str(age_breakdown3), + str(age_breakdown3 + 1) + '-' + str(age_breakdown4), + 'ABOVE ' + str(age_breakdown4)] diff --git a/inventory_advanced_reports/report/age_breakdown_report_views.xml b/inventory_advanced_reports/report/age_breakdown_report_views.xml new file mode 100644 index 000000000..4fc6ebf72 --- /dev/null +++ b/inventory_advanced_reports/report/age_breakdown_report_views.xml @@ -0,0 +1,106 @@ + + + + + Inventory Age Breakdown Report + report.inventory_advanced_reports.report_inventory_breakdown + qweb-pdf + inventory_advanced_reports.report_inventory_breakdown + inventory_advanced_reports.report_inventory_breakdown + + report + + + + diff --git a/inventory_advanced_reports/report/aging_report.py b/inventory_advanced_reports/report/aging_report.py new file mode 100644 index 000000000..9c8d66308 --- /dev/null +++ b/inventory_advanced_reports/report/aging_report.py @@ -0,0 +1,172 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2024-TODAY Cybrosys Technologies() +# Author: Gayathri V (odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see . +# +############################################################################### +from odoo import api, fields, models +from odoo.exceptions import ValidationError + + +class AgingReport(models.AbstractModel): + """Create an abstract model for passing reporting values""" + _name = 'report.inventory_advanced_reports.report_inventory_aging' + _description = 'Aging Report' + + @api.model + def _get_report_values(self, docids, data=None): + """This function has working in get the pdf report.""" + values = data + product_ids = data['product_ids'] + category_ids = data['category_ids'] + company_ids = data['company_ids'] + params = [] + param_count = 0 + query = """ + SELECT + CASE + WHEN pp.default_code IS NOT NULL + THEN CONCAT(pp.default_code, ' - ', pt.name) + ELSE + pt.name + END AS product_code_and_name, + c.complete_name AS category_name, + c.id AS category_id, + pp.id AS product_id, + company.id AS company_id, + company.name AS company_name, + COALESCE(SUM(svl.remaining_qty), 0) AS qty_available, + ( + SELECT SUM(sm_inner.product_uom_qty) + FROM stock_move sm_inner + INNER JOIN res_company company_inner ON sm_inner.company_id = company_inner.id + WHERE sm_inner.product_id = pp.id + AND sm_inner.state = 'done' + AND sm_inner.date < ( + SELECT MAX(sm_inner2.date) + FROM stock_move sm_inner2 + WHERE sm_inner2.product_id = pp.id + AND sm_inner2.state = 'done' + AND company_inner.id = sm_inner2.company_id + ) + ) AS prev_qty_available, + ( + SELECT MIN(sm_inner.date) + FROM stock_move sm_inner + WHERE sm_inner.product_id = pp.id + AND sm_inner.state = 'done' + AND (company.id IS NULL OR company.id = sm_inner.company_id) + ) AS receipt_date + FROM + product_product pp + INNER JOIN product_template pt ON pp.product_tmpl_id = pt.id + INNER JOIN product_category c ON pt.categ_id = c.id + LEFT JOIN stock_move sm ON sm.product_id = pp.id + LEFT JOIN stock_picking_type spt ON sm.picking_type_id = spt.id + LEFT JOIN res_company company ON sm.company_id = company.id + INNER JOIN stock_valuation_layer svl ON svl.stock_move_id = sm.id + WHERE + pt.detailed_type = 'product' + AND sm.state = 'done' + """ + if product_ids or category_ids: + query += " AND (" + if product_ids: + product_ids = [product_id for product_id in product_ids] + query += "pp.id = ANY(%s)" + params.append(product_ids) + param_count += 1 + if product_ids and category_ids: + query += " OR " + if category_ids: + category_ids = [category for category in category_ids] + params.append(category_ids) + query += "(pt.categ_id = ANY(%s))" + param_count += 1 + if product_ids or category_ids: + query += ")" + if company_ids: + company_ids = [company for company in company_ids] + query += " AND (sm.company_id = ANY(%s))" + params.append(company_ids) + param_count += 1 + query += """ + GROUP BY + CASE + WHEN pp.default_code IS NOT NULL + THEN CONCAT(pp.default_code, ' - ', + pt.name) + ELSE + pt.name + END, + c.complete_name, + company.id, + c.id, + company.name, + pp.id; + """ + self.env.cr.execute(query, params) + result_data = self.env.cr.dictfetchall() + today = fields.datetime.now().date() + for row in result_data: + receipt_date = row.get('receipt_date') + if receipt_date: + receipt_date = receipt_date.date() + row['days_since_receipt'] = (today - receipt_date).days + product = self.env['product.product'].browse(row.get('product_id')) + standard_price = product.standard_price + current_stock = row.get('qty_available') + prev_stock = row.get('prev_qty_available') + if prev_stock is None: + prev_stock = current_stock + row['prev_qty_available'] = current_stock + if standard_price and current_stock: + row['current_value'] = current_stock * standard_price + else: + row[ + 'current_value'] = 0 + row[ + 'prev_value'] = prev_stock * standard_price \ + if prev_stock is not None else 0 + total_current_stock = sum( + item.get('qty_available') for item in result_data if + item.get('qty_available') is not None) + if total_current_stock: + stock_percentage = (current_stock / total_current_stock) * 100 + else: + stock_percentage = 0.0 + row['stock_percentage'] = round(stock_percentage, 2) + current_value = row.get('current_value') + total_value = sum( + item.get('current_value', 0) for item in result_data) + if total_value: + stock_value_percentage = (current_value / total_value) * 100 + else: + stock_value_percentage = 0.0 + row['stock_value_percentage'] = round(stock_value_percentage, 2) + print(result_data,'fgthyujikoo') + if result_data: + return { + 'doc_ids': docids, + 'doc_model': + 'report.inventory_advanced_reports.report_inventory_aging', + 'data': values, + 'options': result_data, + } + else: + raise ValidationError("No records found for the given criteria!") diff --git a/inventory_advanced_reports/report/aging_report_views.xml b/inventory_advanced_reports/report/aging_report_views.xml new file mode 100755 index 000000000..347ae74f9 --- /dev/null +++ b/inventory_advanced_reports/report/aging_report_views.xml @@ -0,0 +1,75 @@ + + + + + Inventory Aging Report + report.inventory_advanced_reports.report_inventory_aging + qweb-pdf + inventory_advanced_reports.report_inventory_aging + inventory_advanced_reports.report_inventory_aging + + report + + + + diff --git a/inventory_advanced_reports/report/fsn_report.py b/inventory_advanced_reports/report/fsn_report.py new file mode 100644 index 000000000..1111cfdd2 --- /dev/null +++ b/inventory_advanced_reports/report/fsn_report.py @@ -0,0 +1,200 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2024-TODAY Cybrosys Technologies() +# Author: Gayathri V (odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see . +# +############################################################################### +from odoo import api, models +from datetime import datetime + +from odoo.exceptions import ValidationError + + +class FsnReport(models.AbstractModel): + """Create an abstract model for passing reporting values""" + _name = 'report.inventory_advanced_reports.report_inventory_fsn' + _description = 'FSN Report' + + @api.model + def _get_report_values(self, docids, data=None): + """This function has working in get the pdf report.""" + values = data + if data is None or not isinstance(data, dict): + raise ValueError("Invalid or missing data for the report") + product_ids = data.get('product_ids', []) + category_ids = data.get('category_ids', []) + company_ids = data.get('company_ids', []) + warehouse_ids = data.get('warehouse_ids', []) + start_date = data.get('start_date') + end_date = data.get('end_date') + fsn = data.get('fsn') + if not start_date or not end_date: + raise ValueError( + "Missing start_date or end_date in the data") + start_date = datetime.strptime(start_date, '%Y-%m-%d') + end_date = datetime.strptime(end_date, '%Y-%m-%d') + filtered_product_stock = [] + query = """ + SELECT + product_id, + product_code_and_name, + category_id, + category_name, + company_id, + warehouse_id, + opening_stock, + closing_stock, + sales, + average_stock, + CASE + WHEN sales > 0 + THEN ROUND((sales / NULLIF(average_stock, 0)), 2) + ELSE 0 + END AS turnover_ratio, + CASE + WHEN + CASE + WHEN sales > 0 + THEN ROUND((sales / NULLIF(average_stock, 0)), 2) + ELSE 0 + END > 3 THEN 'Fast Moving' + WHEN + CASE + WHEN sales > 0 + THEN ROUND((sales / NULLIF(average_stock, 0)), 2) + ELSE 0 + END >= 1 AND + CASE + WHEN sales > 0 + THEN ROUND((sales / NULLIF(average_stock, 0)), 2) + ELSE 0 + END <= 3 THEN 'Slow Moving' + ELSE 'Non Moving' + END AS fsn_classification + FROM + (SELECT + pp.id AS product_id, + pt.categ_id AS category_id, + CASE + WHEN pp.default_code IS NOT NULL + THEN CONCAT(pp.default_code, ' - ', + pt.name) + ELSE + pt.name + END AS product_code_and_name, + pc.complete_name AS category_name, + company.id AS company_id, + sw.id AS warehouse_id, + (SUM(CASE WHEN sm.date <= %s AND sld_dest.usage = 'internal' + THEN sm.product_uom_qty ELSE 0 END) - + SUM(CASE WHEN sm.date <= %s AND sld_src.usage = 'internal' + THEN sm.product_uom_qty ELSE 0 END)) AS opening_stock, + (SUM(CASE WHEN sm.date <= %s AND sld_dest.usage = 'internal' + THEN sm.product_uom_qty ELSE 0 END) - + SUM(CASE WHEN sm.date <= %s AND sld_src.usage = 'internal' + THEN sm.product_uom_qty ELSE 0 END)) AS closing_stock, + SUM(CASE WHEN sm.date BETWEEN %s AND %s + AND sld_dest.usage = 'customer' + THEN sm.product_uom_qty ELSE 0 END) AS sales, + ((SUM(CASE WHEN sm.date <= %s + AND sld_dest.usage = 'internal' + THEN sm.product_uom_qty ELSE 0 END) - + SUM(CASE WHEN sm.date <= %s AND sld_src.usage = 'internal' + THEN sm.product_uom_qty ELSE 0 END))+ + (SUM(CASE WHEN sm.date <= %s AND sld_dest.usage = 'internal' + THEN sm.product_uom_qty ELSE 0 END) - + SUM(CASE WHEN sm.date <= %s AND sld_src.usage = 'internal' + THEN sm.product_uom_qty ELSE 0 END)))/2 AS average_stock + FROM + stock_move sm + JOIN + product_product pp ON sm.product_id = pp.id + JOIN + product_template pt ON pp.product_tmpl_id = pt.id + JOIN + product_category pc ON pt.categ_id = pc.id + JOIN + res_company company ON company.id = sm.company_id + JOIN + stock_warehouse sw ON sw.company_id = company.id + LEFT JOIN + stock_location sld_dest ON sm.location_dest_id = sld_dest.id + LEFT JOIN + stock_location sld_src ON sm.location_id = sld_src.id + WHERE + sm.state = 'done' + """ + params = [ + start_date, start_date, end_date, end_date, start_date, end_date, + start_date, start_date, end_date, end_date + ] + sub_queries = [] + sub_params = [] + param_count = 0 + if product_ids or category_ids: + query += " AND (" + if product_ids: + product_ids = [product_id for product_id in product_ids] + query += "pp.id = ANY(%s)" + params.append(product_ids) + param_count += 1 + if product_ids and category_ids: + query += " OR " + if category_ids: + category_ids = [category_id for category_id in category_ids] + query += "pt.categ_id IN %s" + params.append(tuple(category_ids)) + param_count += 1 + if product_ids or category_ids: + query += ")" + if company_ids: + query += f" AND company.id IN %s" + sub_params.append(tuple(company_ids)) + param_count += 1 + if warehouse_ids: + query += f" AND sw.id IN %s" + sub_params.append(tuple(warehouse_ids)) + param_count += 1 + if sub_queries: + query += " AND " + " AND ".join(sub_queries) + query += """ + GROUP BY pp.id, pt.categ_id, + CASE + WHEN pp.default_code IS NOT NULL + THEN CONCAT(pp.default_code,' - ',pt.name) + ELSE + pt.name + END , pc.complete_name, company.id, sw.id + ) AS subquery + """ + self.env.cr.execute(query, tuple(params + sub_params)) + result_data = self.env.cr.dictfetchall() + for fsn_data in result_data: + if fsn_data.get('fsn_classification') == str(fsn): + filtered_product_stock.append(fsn_data) + if fsn == 'All' and not result_data: + raise ValidationError("No corresponding data to print") + elif fsn != 'All' and filtered_product_stock == []: + raise ValidationError("No corresponding data to print") + return { + 'doc_ids': docids, + 'doc_model': + 'report.inventory_advanced_reports.report_inventory_fsn', + 'data': values, + 'options': result_data if fsn == 'All' else filtered_product_stock, + } diff --git a/inventory_advanced_reports/report/fsn_report_views.xml b/inventory_advanced_reports/report/fsn_report_views.xml new file mode 100644 index 000000000..6d2d03e22 --- /dev/null +++ b/inventory_advanced_reports/report/fsn_report_views.xml @@ -0,0 +1,91 @@ + + + + + Inventory FSN Report + report.inventory_advanced_reports.report_inventory_fsn + qweb-pdf + inventory_advanced_reports.report_inventory_fsn + inventory_advanced_reports.report_inventory_fsn + + report + + + + diff --git a/inventory_advanced_reports/report/fsn_xyz_report.py b/inventory_advanced_reports/report/fsn_xyz_report.py new file mode 100644 index 000000000..afbd95589 --- /dev/null +++ b/inventory_advanced_reports/report/fsn_xyz_report.py @@ -0,0 +1,260 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2024-TODAY Cybrosys Technologies() +# Author: Gayathri V (odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see . +# +############################################################################### +from odoo import api, fields, models +from odoo.exceptions import ValidationError + + +class FsnXyzReport(models.AbstractModel): + """Create an abstract model for passing reporting values""" + _name = 'report.inventory_advanced_reports.report_inventory_fsn_xyz' + _description = 'FSN-XYZ Report' + + @api.model + def _get_report_values(self, docids, data=None): + """This function has working in get the pdf report.""" + values = data + if data is None or not isinstance(data, dict): + raise ValueError("Invalid or missing data for the report") + product_ids = data.get('product_ids', []) + category_ids = data.get('category_ids', []) + company_ids = data.get('company_ids', []) + warehouse_ids = data.get('warehouse_ids', []) + start_date = data.get('start_date') + end_date = data.get('end_date') + fsn = data.get('fsn') + xyz = data.get('xyz') + if not start_date or not end_date: + raise ValueError( + "Missing start_date or end_date in the data") + start_date = fields.datetime.strptime(start_date, '%Y-%m-%d') + end_date = fields.datetime.strptime(end_date, '%Y-%m-%d') + filtered_product_stock = [] + query = """ + SELECT + product_id, + product_code_and_name, + category_id, + category_name, + company_id, + warehouse_id, + opening_stock, + closing_stock, + sales, + average_stock, + current_stock, + stock_value, + stock_percentage, + CASE + WHEN sales > 0 + THEN ROUND((sales / NULLIF(average_stock, 0)), 2) + ELSE 0 + END AS turnover_ratio, + CASE + WHEN + CASE + WHEN sales > 0 + THEN + ROUND((sales / NULLIF(average_stock, 0)), 2) + ELSE 0 + END > 3 THEN 'Fast Moving' + WHEN + CASE + WHEN sales > 0 + THEN + ROUND((sales / NULLIF(average_stock, 0)), 2) + ELSE 0 + END >= 1 AND + CASE + WHEN sales > 0 + THEN + ROUND((sales / NULLIF(average_stock, 0)), 2) + ELSE 0 + END <= 3 THEN 'Slow Moving' + ELSE 'Non Moving' + END AS fsn_classification, + SUM(stock_percentage) OVER (ORDER BY stock_value DESC) + AS cumulative_stock_percentage, + CASE + WHEN SUM(stock_percentage) OVER (ORDER BY stock_value DESC) < 70 + THEN 'X' + WHEN SUM(stock_percentage) OVER (ORDER BY stock_value DESC) >= 70 + AND SUM(stock_percentage) OVER (ORDER BY stock_value DESC) <= 90 + THEN 'Y' + ELSE 'Z' + END AS xyz_classification, + CONCAT( + CASE + WHEN + CASE + WHEN sales > 0 + THEN ROUND((sales / NULLIF(average_stock, 0)), 2) + ELSE 0 + END > 3 THEN 'F' + WHEN + CASE + WHEN sales > 0 + THEN ROUND((sales / NULLIF(average_stock, 0)), 2) + ELSE 0 + END >= 1 AND + CASE + WHEN sales > 0 + THEN ROUND((sales / NULLIF(average_stock, 0)), 2) + ELSE 0 + END <= 3 THEN 'S' + ELSE 'N' + END, + CASE + WHEN SUM(stock_percentage) OVER (ORDER BY stock_value DESC) < 70 + THEN 'X' + WHEN SUM(stock_percentage) + OVER (ORDER BY stock_value DESC) >= 70 AND SUM(stock_percentage) + OVER (ORDER BY stock_value DESC) <= 90 THEN 'Y' + ELSE 'Z' + END + ) AS combined_classification + FROM + (SELECT + pp.id AS product_id, + pt.categ_id AS category_id, + CASE + WHEN pp.default_code IS NOT NULL + THEN CONCAT(pp.default_code, ' - ', + pt.name) + ELSE + pt.name + END AS product_code_and_name, + pc.complete_name AS category_name, + company.id AS company_id, + sw.id AS warehouse_id, + SUM(svl.remaining_qty) AS current_stock, + SUM(svl.remaining_value) AS stock_value, + COALESCE(ROUND((SUM(svl.remaining_value) / + NULLIF(SUM(SUM(svl.remaining_value)) + OVER (), 0)) * 100, 2),0) AS stock_percentage, + (SUM(CASE WHEN sm.date <= %s AND sld_dest.usage = 'internal' + THEN sm.product_uom_qty ELSE 0 END) - + SUM(CASE WHEN sm.date <= %s AND sld_src.usage = 'internal' + THEN sm.product_uom_qty ELSE 0 END)) AS opening_stock, + (SUM(CASE WHEN sm.date <= %s AND sld_dest.usage = 'internal' + THEN sm.product_uom_qty ELSE 0 END) - + SUM(CASE WHEN sm.date <= %s AND sld_src.usage = 'internal' + THEN sm.product_uom_qty ELSE 0 END)) AS closing_stock, + SUM(CASE WHEN sm.date BETWEEN %s AND %s + AND sld_dest.usage = 'customer' + THEN sm.product_uom_qty ELSE 0 END) AS sales, + ((SUM(CASE WHEN sm.date <= %s + AND sld_dest.usage = 'internal' + THEN sm.product_uom_qty ELSE 0 END) - + SUM(CASE WHEN sm.date <= %s AND sld_src.usage = 'internal' + THEN sm.product_uom_qty ELSE 0 END))+ + (SUM(CASE WHEN sm.date <= %s AND sld_dest.usage = 'internal' + THEN sm.product_uom_qty ELSE 0 END) - + SUM(CASE WHEN sm.date <= %s AND sld_src.usage = 'internal' + THEN sm.product_uom_qty ELSE 0 END)))/2 AS average_stock + FROM + stock_move sm + JOIN + product_product pp ON sm.product_id = pp.id + JOIN + product_template pt ON pp.product_tmpl_id = pt.id + JOIN + product_category pc ON pt.categ_id = pc.id + JOIN + res_company company ON company.id = sm.company_id + JOIN + stock_warehouse sw ON sw.company_id = company.id + JOIN + stock_valuation_layer svl ON svl.stock_move_id = sm.id + LEFT JOIN + stock_location sld_dest ON sm.location_dest_id = sld_dest.id + LEFT JOIN + stock_location sld_src ON sm.location_id = sld_src.id + WHERE + sm.state = 'done' + AND pp.active = TRUE + AND pt.active = TRUE + AND pt.type = 'product' + AND svl.remaining_value IS NOT NULL + """ + params = [ + start_date, start_date, end_date, end_date, start_date, end_date, + start_date, start_date, end_date, end_date + ] + sub_queries = [] + sub_params = [] + param_count = 0 + if product_ids or category_ids: + query += " AND (" + if product_ids: + product_ids = [product_id for product_id in product_ids] + query += "pp.id = ANY(%s)" + params.append(product_ids) + param_count += 1 + if product_ids and category_ids: + query += " OR " + if category_ids: + category_ids = [category_id for category_id in category_ids] + query += "pt.categ_id IN %s" + params.append(tuple(category_ids)) + param_count += 1 + if product_ids or category_ids: + query += ")" + if company_ids: + query += f" AND sm.company_id IN %s" + sub_params.append(tuple(company_ids)) + param_count += 1 + if warehouse_ids: + query += f" AND sw.id IN %s" + sub_params.append(tuple(warehouse_ids)) + param_count += 1 + if sub_queries: + query += " AND " + " AND ".join(sub_queries) + query += """ + GROUP BY + pp.id,pt.name, pt.categ_id,pc.complete_name, company.id, sw.id + ) AS subquery + ORDER BY stock_value DESC + """ + self.env.cr.execute(query, tuple(params + sub_params)) + result_data = self.env.cr.dictfetchall() + for fsn_data in result_data: + if ( + (fsn == 'All' and xyz == 'All') or + (fsn == 'All' and fsn_data.get('xyz_classification') == str( + xyz)) or + (xyz == 'All' and fsn_data.get('fsn_classification') == str( + fsn)) or + (fsn_data.get('fsn_classification') == str( + fsn) and fsn_data.get('xyz_classification') == str(xyz)) + ): + filtered_product_stock.append(fsn_data) + if (fsn == 'All' or xyz == 'All') and not result_data: + raise ValidationError("No corresponding data to print") + elif not filtered_product_stock: + raise ValidationError("No corresponding data to print") + return { + 'doc_ids': docids, + 'doc_model': + 'report.inventory_advanced_reports.report_inventory_fsn_xyz', + 'data': values, + 'options': filtered_product_stock, + } diff --git a/inventory_advanced_reports/report/fsn_xyz_report_views.xml b/inventory_advanced_reports/report/fsn_xyz_report_views.xml new file mode 100644 index 000000000..06fdd70b9 --- /dev/null +++ b/inventory_advanced_reports/report/fsn_xyz_report_views.xml @@ -0,0 +1,102 @@ + + + + + Inventory FSN XYZ Report + + report.inventory_advanced_reports.report_inventory_fsn_xyz + + qweb-pdf + inventory_advanced_reports.report_inventory_fsn_xyz + inventory_advanced_reports.report_inventory_fsn_xyz + + + report + + + + diff --git a/inventory_advanced_reports/report/out_of_stock_report.py b/inventory_advanced_reports/report/out_of_stock_report.py new file mode 100644 index 000000000..9fb6bf04d --- /dev/null +++ b/inventory_advanced_reports/report/out_of_stock_report.py @@ -0,0 +1,274 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2024-TODAY Cybrosys Technologies() +# Author: Gayathri V (odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see . +# +############################################################################### +from odoo import api, models +from odoo.exceptions import ValidationError + + +class OutOfStockReport(models.AbstractModel): + """Create an abstract model for passing reporting values""" + _name = 'report.inventory_advanced_reports.report_inventory_out_of_stock' + _description = 'OutOfStock Report' + + @api.model + def _get_report_values(self, docids, data=None): + """This function has working in get the pdf report.""" + values = data + if data is None or not isinstance(data, dict): + raise ValueError("Invalid or missing data for the report") + product_ids = data.get('product_ids', []) + category_ids = data.get('category_ids', []) + company_ids = data.get('company_ids', []) + warehouse_ids = data.get('warehouse_ids', []) + inventory_for_next_x_days = data.get('inventory_for_next_x_days', []) + start_date = data.get('start_date') + end_date = data.get('end_date') + if not start_date or not end_date: + raise ValueError( + "Missing start_date or end_date in the data") + query = """ + SELECT + product_id, + product_code_and_name, + category_id, + category_name, + company_id, + current_stock, + warehouse_id, + incoming_quantity, + outgoing_quantity, + virtual_stock, + sales, + ads, + advance_stock_days, + ROUND(advance_stock_days * ads, 0) AS demanded_quantity, + ROUND(CASE + WHEN ads = 0 THEN virtual_stock / 0.001 + ELSE virtual_stock / ads + END,0) AS in_stock_days, + ROUND(CASE + WHEN ads = 0 THEN GREATEST(advance_stock_days - + ROUND(virtual_stock / 0.001, 2), 0) + ELSE GREATEST(advance_stock_days - + ROUND(virtual_stock / ads, 2), 0) + END ,0) AS out_of_stock_days, + ROUND( + CASE + WHEN advance_stock_days = 0 THEN 0 + ELSE + CASE + WHEN ads = 0 THEN GREATEST(advance_stock_days - + ROUND(virtual_stock / 0.001, 2), 0) + ELSE GREATEST(advance_stock_days - + ROUND(virtual_stock / ads, 2), 0) + END + END, 2 + ) AS out_of_stock_ratio, + ROUND( + CASE + WHEN ads = 0 + THEN + GREATEST(advance_stock_days - + ROUND(virtual_stock / 0.001, 2), 0) + ELSE GREATEST(advance_stock_days - + ROUND(virtual_stock / ads, 2), 0) + END * ads, 0 + ) AS out_of_stock_qty, + ROUND( + CASE + WHEN virtual_stock = 0 THEN 0 + ELSE sales / virtual_stock + END, 2 + ) AS turnover_ratio, + CASE + WHEN + CASE + WHEN sales > 0 THEN + ROUND((sales / NULLIF(virtual_stock, 0)), 2) + ELSE 0 + END > 3 THEN 'Fast Moving' + WHEN + CASE + WHEN sales > 0 THEN + ROUND((sales / NULLIF(virtual_stock, 0)), 2) + ELSE 0 + END >= 1 AND + CASE + WHEN sales > 0 THEN + ROUND((sales / NULLIF(virtual_stock, 0)), 2) + ELSE 0 + END <= 3 THEN 'Slow Moving' + ELSE 'Non Moving' + END AS fsn_classification + FROM( + SELECT + CASE + WHEN pp.default_code IS NOT NULL + THEN CONCAT(pp.default_code, ' - ', + pt.name) + ELSE + pt.name + END AS product_code_and_name, + company.id AS company_id, + company.name AS company_name, + sm.product_id AS product_id, + pc.id AS category_id, + pc.complete_name AS category_name, + sw.id AS warehouse_id, + SUM(CASE + WHEN sld_dest.usage = 'internal' AND sm.state IN + ('assigned', 'confirmed', 'waiting') + THEN sm.product_uom_qty + ELSE 0 + END) AS incoming_quantity, + SUM(CASE + WHEN sld_src.usage = 'internal' AND sm.state IN + ('assigned', 'confirmed', 'waiting') + THEN sm.product_uom_qty + ELSE 0 + END) AS outgoing_quantity, + SUM(CASE + WHEN sld_dest.usage = 'internal' AND sm.state = 'done' + THEN sm.product_uom_qty + ELSE 0 + END) - + SUM(CASE + WHEN sld_src.usage = 'internal' AND sm.state = 'done' + THEN sm.product_uom_qty + ELSE 0 + END) AS current_stock, + SUM(CASE + WHEN sld_dest.usage = 'internal' AND sm.state = 'done' + THEN sm.product_uom_qty + ELSE 0 + END) - + SUM(CASE + WHEN sld_src.usage = 'internal' AND sm.state = 'done' + THEN sm.product_uom_qty + ELSE 0 + END)+ + SUM(CASE + WHEN sld_dest.usage = 'internal' AND sm.state + IN ('assigned', 'confirmed', 'waiting') THEN sm.product_uom_qty + ELSE 0 + END) - + SUM(CASE + WHEN sld_src.usage = 'internal' AND sm.state + IN ('assigned', 'confirmed', 'waiting') THEN sm.product_uom_qty + ELSE 0 + END) AS virtual_stock, + SUM(CASE WHEN sm.date BETWEEN %s AND %s + AND sld_dest.usage = 'customer' + THEN sm.product_uom_qty ELSE 0 END) AS sales, + ROUND(SUM(CASE + WHEN sm.date BETWEEN %s AND %s AND sld_src.usage = 'internal' + AND sm.state = 'done' THEN sm.product_uom_qty + ELSE 0 + END) / ((date %s - date %s)+1), 2) AS ads, + %s AS advance_stock_days + FROM stock_move sm + INNER JOIN product_product pp ON pp.id = sm.product_id + INNER JOIN product_template pt ON pt.id = pp.product_tmpl_id + INNER JOIN res_company company ON company.id = sm.company_id + INNER JOIN stock_warehouse sw ON sw.company_id = company.id + INNER JOIN product_category pc ON pc.id = pt.categ_id + LEFT JOIN ( + SELECT sm.id AS move_id, usage + FROM stock_location sld + INNER JOIN stock_move sm ON sld.id = sm.location_dest_id + ) sld_dest ON sm.id = sld_dest.move_id + LEFT JOIN ( + SELECT sm.id AS move_id, usage + FROM stock_location sld + INNER JOIN stock_move sm ON sld.id = sm.location_id + ) sld_src ON sm.id = sld_src.move_id + WHERE pp.active = TRUE + AND pt.active = TRUE + AND pt.type = 'product' + """ + params = [ + start_date, end_date, + start_date, end_date, + end_date, start_date, + inventory_for_next_x_days + ] + sub_queries = [] + sub_params = [] + param_count = 0 + if product_ids or category_ids: + query += " AND (" + if product_ids: + product_ids = [product_id for product_id in product_ids] + query += "pp.id = ANY(%s)" + params.append(product_ids) + param_count += 1 + if product_ids and category_ids: + query += " OR " + if category_ids: + category_ids = [category for category in category_ids] + params.append(category_ids) + query += "(pt.categ_id = ANY(%s))" + param_count += 1 + if product_ids or category_ids: + query += ")" + if company_ids: + company_ids = [company for company in company_ids] + query += " AND (sm.company_id = ANY(%s))" + sub_params.append(company_ids) + param_count += 1 + if warehouse_ids: + warehouse_ids = [warehouse for warehouse in warehouse_ids] + query += " AND (sw.id = ANY(%s))" + sub_params.append(warehouse_ids) + param_count += 1 + if sub_queries: + query += " AND " + " AND ".join(sub_queries) + query += """ GROUP BY pp.id, pt.name, pc.id, company.id, sm.product_id, + sw.id) AS sub_query """ + self.env.cr.execute(query, tuple(params + sub_params)) + result_data = self.env.cr.dictfetchall() + for data in result_data: + product_id = data.get('product_id') + out_of_stock_qty = data.get('out_of_stock_qty') + total_value = sum( + item.get('out_of_stock_qty', 0) for item in result_data) + if total_value: + out_of_stock_qty_percentage = \ + (out_of_stock_qty / total_value) * 100 + else: + out_of_stock_qty_percentage = 0.0 + data['out_of_stock_qty_percentage'] = round( + out_of_stock_qty_percentage, 2) + cost = self.env['product.product'].search([ + ('id', '=', product_id)]).standard_price + data['cost'] = cost + data['out_of_stock_value'] = out_of_stock_qty * cost + if result_data: + return { + 'doc_ids': docids, + 'doc_model': + 'report.inventory_advanced_reports.' + 'report_inventory_out_of_stock', + 'data': values, + 'options': result_data, + } + else: + raise ValidationError("No records found for the given criteria!") diff --git a/inventory_advanced_reports/report/out_of_stock_report_views.xml b/inventory_advanced_reports/report/out_of_stock_report_views.xml new file mode 100644 index 000000000..b69bfb5dd --- /dev/null +++ b/inventory_advanced_reports/report/out_of_stock_report_views.xml @@ -0,0 +1,158 @@ + + + + + Over Stock + + custom + 297 + 500 + Landscape + 30 + 23 + 5 + 5 + + 20 + 90 + + + + Inventory Out Of Stock Report + report.inventory_advanced_reports.report_inventory_out_of_stock + qweb-pdf + inventory_advanced_reports.report_inventory_out_of_stock + inventory_advanced_reports.report_inventory_out_of_stock + + + report + + + + diff --git a/inventory_advanced_reports/report/over_stock_report.py b/inventory_advanced_reports/report/over_stock_report.py new file mode 100644 index 000000000..cf5cde30b --- /dev/null +++ b/inventory_advanced_reports/report/over_stock_report.py @@ -0,0 +1,303 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2024-TODAY Cybrosys Technologies() +# Author: Gayathri V (odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see . +# +############################################################################### +from odoo import api, fields, models + +from odoo.exceptions import ValidationError + + +class OverStockReport(models.AbstractModel): + """Create an abstract model for passing reporting values""" + _name = 'report.inventory_advanced_reports.report_inventory_over_stock' + _description = 'Over Stock Report' + + @api.model + def _get_report_values(self, docids, data=None): + """This function has working in get the pdf report.""" + values = data + if data is None or not isinstance(data, dict): + raise ValueError("Invalid or missing data for the report") + product_ids = data.get('product_ids', []) + category_ids = data.get('category_ids', []) + company_ids = data.get('company_ids', []) + warehouse_ids = data.get('warehouse_ids', []) + inventory_for_next_x_days = data.get('inventory_for_next_x_days', []) + start_date = data.get('start_date') + end_date = data.get('end_date') + if not start_date or not end_date: + raise ValueError( + "Missing start_date or end_date in the data") + processed_product_ids = [] + filtered_result_data = [] + query = """ + SELECT + product_id, + product_code_and_name, + category_id, + category_name, + company_id, + current_stock, + warehouse_id, + incoming_quantity, + outgoing_quantity, + virtual_stock, + sales, + ads, + advance_stock_days, + ROUND(advance_stock_days * ads, 0) + AS demanded_quantity, + ROUND(CASE + WHEN ads = 0 THEN virtual_stock / 0.001 + ELSE virtual_stock / ads + END,0) AS in_stock_days, + ROUND(virtual_stock-(ads*advance_stock_days),0) + AS over_stock_qty, + ROUND( + CASE + WHEN virtual_stock = 0 THEN 0 + ELSE sales / virtual_stock + END, 2 + ) AS turnover_ratio, + CASE + WHEN + CASE + WHEN sales > 0 + THEN ROUND((sales / NULLIF(virtual_stock, 0)), 2) + ELSE 0 + END > 3 THEN 'Fast Moving' + WHEN + CASE + WHEN sales > 0 + THEN ROUND((sales / NULLIF(virtual_stock, 0)), 2) + ELSE 0 + END >= 1 AND + CASE + WHEN sales > 0 + THEN ROUND((sales / NULLIF(virtual_stock, 0)), 2) + ELSE 0 + END <= 3 THEN 'Slow Moving' + ELSE 'Non Moving' + END AS fsn_classification + FROM( + SELECT + CASE + WHEN pp.default_code IS NOT NULL + THEN CONCAT(pp.default_code, ' - ', + pt.name) + ELSE + pt.name + END AS product_code_and_name, + company.id AS company_id, + company.name AS company_name, + sm.product_id AS product_id, + pc.id AS category_id, + pc.complete_name AS category_name, + sw.id AS warehouse_id, + SUM(CASE + WHEN sld_dest.usage = 'internal' AND sm.state + IN ('assigned', 'confirmed', 'waiting') THEN sm.product_uom_qty + ELSE 0 + END) AS incoming_quantity, + SUM(CASE + WHEN sld_src.usage = 'internal' AND sm.state + IN ('assigned', 'confirmed', 'waiting') THEN sm.product_uom_qty + ELSE 0 + END) AS outgoing_quantity, + SUM(CASE + WHEN sld_dest.usage = 'internal' AND sm.state = 'done' + THEN sm.product_uom_qty + ELSE 0 + END) - + SUM(CASE + WHEN sld_src.usage = 'internal' AND sm.state = 'done' + THEN sm.product_uom_qty + ELSE 0 + END) AS current_stock, + SUM(CASE + WHEN sld_dest.usage = 'internal' AND sm.state = 'done' + THEN sm.product_uom_qty + ELSE 0 + END) - + SUM(CASE + WHEN sld_src.usage = 'internal' AND sm.state = 'done' + THEN sm.product_uom_qty + ELSE 0 + END)+ + SUM(CASE + WHEN sld_dest.usage = 'internal' AND sm.state + IN ('assigned', 'confirmed', 'waiting') THEN sm.product_uom_qty + ELSE 0 + END) - + SUM(CASE + WHEN sld_src.usage = 'internal' AND sm.state + IN ('assigned', 'confirmed', 'waiting') THEN sm.product_uom_qty + ELSE 0 + END) AS virtual_stock, + SUM(CASE WHEN sm.date BETWEEN %s AND %s + AND sld_dest.usage = 'customer' + THEN sm.product_uom_qty ELSE 0 END) AS sales, + ROUND(SUM(CASE + WHEN sm.date BETWEEN %s AND %s AND sld_src.usage = 'internal' + AND sm.state = 'done' THEN sm.product_uom_qty + ELSE 0 + END) / ((date %s - date %s)+1), 2) AS ads, + %s AS advance_stock_days + FROM stock_move sm + INNER JOIN product_product pp ON pp.id = sm.product_id + INNER JOIN product_template pt ON pt.id = pp.product_tmpl_id + INNER JOIN res_company company ON company.id = sm.company_id + INNER JOIN stock_warehouse sw ON sw.company_id = company.id + INNER JOIN product_category pc ON pc.id = pt.categ_id + LEFT JOIN ( + SELECT sm.id AS move_id, usage + FROM stock_location sld + INNER JOIN stock_move sm ON sld.id = sm.location_dest_id + ) sld_dest ON sm.id = sld_dest.move_id + LEFT JOIN ( + SELECT sm.id AS move_id, usage + FROM stock_location sld + INNER JOIN stock_move sm ON sld.id = sm.location_id + ) sld_src ON sm.id = sld_src.move_id + WHERE pp.active = TRUE + AND pt.active = TRUE + AND pt.type = 'product' + """ + params = [ + start_date, end_date, + start_date, end_date, + end_date, start_date, + inventory_for_next_x_days + ] + sub_queries = [] + sub_params = [] + param_count = 0 + if product_ids or category_ids: + query += " AND (" + if product_ids: + product_ids = [product_id for product_id in product_ids] + query += "pp.id = ANY(%s)" + params.append(product_ids) + param_count += 1 + if product_ids and category_ids: + query += " OR " + if category_ids: + category_ids = [category for category in category_ids] + params.append(category_ids) + query += "(pt.categ_id = ANY(%s))" + param_count += 1 + if product_ids or category_ids: + query += ")" + if company_ids: + company_ids = [company for company in company_ids] + query += " AND (sm.company_id = ANY(%s))" + sub_params.append(company_ids) + param_count += 1 + if warehouse_ids: + warehouse_ids = [warehouse for warehouse in warehouse_ids] + query += " AND (sw.id = ANY(%s))" + sub_params.append(warehouse_ids) + param_count += 1 + if sub_queries: + query += " AND " + " AND ".join(sub_queries) + query += """ GROUP BY pp.id, pt.name, pc.id, company.id, sm.product_id, + sw.id) AS sub_query """ + self.env.cr.execute(query, tuple(params + sub_params)) + result_data = self.env.cr.dictfetchall() + for data in result_data: + product_id = data.get('product_id') + if product_id not in processed_product_ids: + processed_product_ids.append( + product_id) + filtered_result_data.append(data) + for data in filtered_result_data: + over_stock_qty = data.get('over_stock_qty') + product_id = data.get('product_id') + total_qty = sum( + item.get('over_stock_qty', 0) for item in filtered_result_data) + if total_qty: + over_stock_qty_percentage = \ + (over_stock_qty / total_qty) * 100 + else: + over_stock_qty_percentage = 0.0 + data['over_stock_qty_percentage'] = round( + over_stock_qty_percentage, 2) + cost = self.env['product.product'].search([ + ('id', '=', product_id)]).standard_price + data['cost'] = cost + data['over_stock_value'] = over_stock_qty * cost + latest_po = '' + confirmed_po = self.env['purchase.order.line'].search([ + ('product_id', '=', product_id), + ('state', '=', 'purchase'), + ]) + for po in confirmed_po: + if latest_po: + if latest_po.date_approve < po.date_approve: + latest_po = po + else: + latest_po = po + data['po_qty'] = 0 + data['po_price_total'] = 0 + if latest_po: + start_date = fields.Datetime.from_string(start_date).date() + end_date = fields.Datetime.from_string(end_date).date() + po_date = fields.Datetime.from_string(latest_po.order_id.date_approve) + if start_date <= po_date.date() <= end_date: + data['po_qty'] += latest_po.product_qty + data['po_price_total'] += latest_po.price_total + data['po_date'] = po_date + data['po_currency'] = latest_po.currency_id.name + data['po_partner'] = latest_po.partner_id.name + else: + data['po_price_total'] = None + data['po_qty'] = None + data['po_currency'] = None + data['po_partner'] = None + data[ + 'po_date'] = None + else: + data['po_price_total'] = None + data['po_qty'] = None + data['po_date'] = None + data['po_partner'] = None + data['po_currency'] = None + total_value = sum( + item.get('over_stock_value', 0) for item in filtered_result_data) + for data in filtered_result_data: + over_stock_value = data.get('over_stock_value') + if total_value: + over_stock_value_percentage = \ + (over_stock_value / total_value) * 100 + else: + over_stock_value_percentage = 0.0 + data['over_stock_value_percentage'] = round( + over_stock_value_percentage, 2) + if filtered_result_data: + return { + 'doc_ids': docids, + 'doc_model': + 'report.inventory_advanced_reports.' + 'report_inventory_over_stock', + 'data': values, + 'options': filtered_result_data, + } + else: + raise ValidationError("No records found for the given criteria!") diff --git a/inventory_advanced_reports/report/over_stock_report_views.xml b/inventory_advanced_reports/report/over_stock_report_views.xml new file mode 100644 index 000000000..f22c3d4f8 --- /dev/null +++ b/inventory_advanced_reports/report/over_stock_report_views.xml @@ -0,0 +1,153 @@ + + + + + Inventory Over Stock Report + report.inventory_advanced_reports.report_inventory_over_stock + qweb-pdf + inventory_advanced_reports.report_inventory_over_stock + inventory_advanced_reports.report_inventory_over_stock + + + report + + + + diff --git a/inventory_advanced_reports/report/stock_movement_report.py b/inventory_advanced_reports/report/stock_movement_report.py new file mode 100644 index 000000000..080d1ce66 --- /dev/null +++ b/inventory_advanced_reports/report/stock_movement_report.py @@ -0,0 +1,201 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2024-TODAY Cybrosys Technologies() +# Author: Gayathri V (odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see . +# +############################################################################### +from odoo import api, models +from odoo.exceptions import ValidationError + + +class StockMovementReport(models.AbstractModel): + """Create an abstract model for passing reporting values""" + _name = 'report.inventory_advanced_reports.report_inventory_movement' + _description = 'Stock Movement Report' + + @api.model + def _get_report_values(self, docids, data=None): + """This function has working in get the pdf report.""" + values = data + if data is None or not isinstance(data, dict): + raise ValueError("Invalid or missing data for the report") + product_ids = data.get('product_ids', []) + category_ids = data.get('category_ids', []) + company_ids = data.get('company_ids', []) + warehouse_ids = data.get('warehouse_ids', []) + report_up_to_certain_date = data.get('report_up_to_certain_date', []) + up_to_certain_date = data.get('up_to_certain_date', []) + start_date = data.get('start_date') + end_date = data.get('end_date') + if not start_date or not end_date: + raise ValueError( + "Missing start_date or end_date in the data") + query = """ + SELECT + pp.id as product_id, + CASE + WHEN pp.default_code IS NOT NULL + THEN CONCAT(pp.default_code, ' - ', + pt.name) + ELSE + pt.name + END AS product_code_and_name, + pc.complete_name AS category_name, + company.name AS company_name, + """ + if report_up_to_certain_date: + query += """ + SUM(CASE WHEN sm.date <= %s AND sld_dest.usage = 'inventory' + THEN sm.product_uom_qty ELSE 0 END) AS opening_stock, + (SUM(CASE WHEN sm.date <= %s AND sld_dest.usage = 'internal' + THEN sm.product_uom_qty ELSE 0 END) - + SUM(CASE WHEN sm.date <= %s AND sld_src.usage = 'internal' + THEN sm.product_uom_qty ELSE 0 END)) AS closing_stock, + SUM(CASE WHEN sm.date <= %s AND sld_dest.usage = 'customer' + THEN sm.product_uom_qty ELSE 0 END) AS sales, + SUM(CASE WHEN sm.date <= %s AND sld_src.usage = 'customer' + THEN sm.product_uom_qty ELSE 0 END) AS sales_return, + SUM(CASE WHEN sm.date <= %s AND sld_src.usage = 'supplier' + THEN sm.product_uom_qty ELSE 0 END) AS purchase, + SUM(CASE WHEN sm.date <= %s AND sld_dest.usage = 'supplier' + THEN sm.product_uom_qty ELSE 0 END) AS purchase_return, + SUM(CASE WHEN sm.date <= %s AND sld_dest.usage = 'internal' + THEN sm.product_uom_qty ELSE 0 END) AS internal_in, + SUM(CASE WHEN sm.date <= %s AND sld_src.usage = 'internal' + THEN sm.product_uom_qty ELSE 0 END) AS internal_out, + SUM(CASE WHEN sm.date <= %s AND sld_dest.usage = 'inventory' + THEN sm.product_uom_qty ELSE 0 END) AS adj_in, + SUM(CASE WHEN sm.date <= %s AND sld_src.usage = 'inventory' + THEN sm.product_uom_qty ELSE 0 END) AS adj_out, + SUM(CASE WHEN sm.date <= %s + AND sld_dest.usage = 'production' + THEN sm.product_uom_qty ELSE 0 END) AS production_in, + SUM(CASE WHEN sm.date <= %s AND sld_src.usage = 'production' + THEN sm.product_uom_qty ELSE 0 END) AS production_out, + SUM(CASE WHEN sm.date <= %s AND sld_dest.usage = 'transit' + THEN sm.product_uom_qty ELSE 0 END) AS transit_in, + SUM(CASE WHEN sm.date <= %s AND sld_src.usage = 'transit' + THEN sm.product_uom_qty ELSE 0 END) AS transit_out + """ + params = [up_to_certain_date] * 15 + else: + query += """ + (SUM(CASE WHEN sm.date <= %s + AND sld_dest.usage = 'internal' + THEN sm.product_uom_qty ELSE 0 END) - + SUM(CASE WHEN sm.date <= %s + AND sld_src.usage = 'internal' + THEN sm.product_uom_qty ELSE 0 END)) AS opening_stock, + (SUM(CASE WHEN sm.date <= %s + AND sld_dest.usage = 'internal' + THEN sm.product_uom_qty ELSE 0 END) - + SUM(CASE WHEN sm.date <= %s + AND sld_src.usage = 'internal' + THEN sm.product_uom_qty ELSE 0 END)) AS closing_stock, + SUM(CASE WHEN sm.date BETWEEN %s AND %s + AND sld_dest.usage = 'customer' + THEN sm.product_uom_qty ELSE 0 END) AS sales, + SUM(CASE WHEN sm.date BETWEEN %s AND %s + AND sld_src.usage = 'customer' + THEN sm.product_uom_qty ELSE 0 END) AS sales_return, + SUM(CASE WHEN sm.date BETWEEN %s AND %s + AND sld_src.usage = 'supplier' + THEN sm.product_uom_qty ELSE 0 END) AS purchase, + SUM(CASE WHEN sm.date BETWEEN %s AND %s + AND sld_dest.usage = 'supplier' + THEN sm.product_uom_qty ELSE 0 END) AS purchase_return, + SUM(CASE WHEN sm.date BETWEEN %s AND %s + AND sld_dest.usage = 'internal' + THEN sm.product_uom_qty ELSE 0 END) AS internal_in, + SUM(CASE WHEN sm.date BETWEEN %s AND %s + AND sld_src.usage = 'internal' + THEN sm.product_uom_qty ELSE 0 END) AS internal_out, + SUM(CASE WHEN sm.date BETWEEN %s AND %s + AND sld_dest.usage = 'inventory' + THEN sm.product_uom_qty ELSE 0 END) AS adj_in, + SUM(CASE WHEN sm.date BETWEEN %s AND %s + AND sld_src.usage = 'inventory' + THEN sm.product_uom_qty ELSE 0 END) AS adj_out, + SUM(CASE WHEN sm.date BETWEEN %s + AND %s AND sld_dest.usage = 'production' + THEN sm.product_uom_qty ELSE 0 END) AS production_in, + SUM(CASE WHEN sm.date BETWEEN %s AND %s + AND sld_src.usage = 'production' + THEN sm.product_uom_qty ELSE 0 END) AS production_out, + SUM(CASE WHEN sm.date BETWEEN %s + AND %s AND sld_dest.usage = 'transit' + THEN sm.product_uom_qty ELSE 0 END) AS transit_in, + SUM(CASE WHEN sm.date BETWEEN %s + AND %s AND sld_src.usage = 'transit' + THEN sm.product_uom_qty ELSE 0 END) AS transit_out + """ + params = [start_date, start_date, end_date, end_date, start_date, + end_date, start_date, end_date, start_date, end_date, + start_date, end_date, start_date, end_date, start_date, + end_date, start_date, end_date, start_date, end_date, + start_date, end_date, start_date, end_date, start_date, + end_date, start_date, end_date] + query += """ + FROM stock_move sm + INNER JOIN product_product pp ON pp.id = sm.product_id + INNER JOIN product_template pt ON pt.id = pp.product_tmpl_id + INNER JOIN res_company company ON company.id = sm.company_id + INNER JOIN stock_warehouse sw ON sw.company_id = company.id + INNER JOIN product_category pc ON pc.id = pt.categ_id + """ + query += """ + LEFT JOIN stock_location sld_dest + ON sm.location_dest_id = sld_dest.id + LEFT JOIN stock_location sld_src + ON sm.location_id = sld_src.id + WHERE + sm.state = 'done' + """ + sub_queries = [] + if product_ids: + product_ids = [product_id for product_id in product_ids] + sub_queries.append("pp.id = ANY(%s)") + params.append(product_ids) + if category_ids: + category_ids = [category for category in category_ids] + sub_queries.append("pt.categ_id = ANY(%s)") + params.append(category_ids) + if sub_queries: + query += " AND (" + " OR ".join(sub_queries) + ")" + if company_ids: + company_ids = [company for company in company_ids] + query += " AND sm.company_id = ANY(%s)" + params.append(company_ids) + if warehouse_ids: + warehouse_ids = [warehouse for warehouse in warehouse_ids] + query += " AND sw.id = ANY(%s)" + params.append(warehouse_ids) + query += """ + GROUP BY pp.id,pt.name,pc.complete_name,company.name + """ + self.env.cr.execute(query, params) + result_data = self.env.cr.dictfetchall() + if result_data: + return { + 'doc_ids': docids, + 'doc_model': 'inventory.overstock.report', + 'data': values, + 'options': result_data, + } + else: + raise ValidationError("No records found for the given criteria!") diff --git a/inventory_advanced_reports/report/stock_movement_report_views.xml b/inventory_advanced_reports/report/stock_movement_report_views.xml new file mode 100644 index 000000000..170efaff4 --- /dev/null +++ b/inventory_advanced_reports/report/stock_movement_report_views.xml @@ -0,0 +1,140 @@ + + + + + Inventory Stock Movement Report + report.inventory_advanced_reports.report_inventory_movement + qweb-pdf + inventory_advanced_reports.report_inventory_movement + inventory_advanced_reports.report_inventory_movement + + + report + + + + diff --git a/inventory_advanced_reports/report/xyz_report.py b/inventory_advanced_reports/report/xyz_report.py new file mode 100644 index 000000000..86a41965a --- /dev/null +++ b/inventory_advanced_reports/report/xyz_report.py @@ -0,0 +1,142 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2024-TODAY Cybrosys Technologies() +# Author: Gayathri V (odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see . +# +############################################################################### +from odoo import api, models +from odoo.exceptions import ValidationError + + +class XyzReport(models.AbstractModel): + """Create an abstract model for passing reporting values""" + _name = 'report.inventory_advanced_reports.report_inventory_xyz' + _description = 'XYZ Report' + + @api.model + def _get_report_values(self, docids, data=None): + """This function has working in get the pdf report.""" + values = data + product_ids = data['product_ids'] + category_ids = data['category_ids'] + company_ids = data['company_ids'] + xyz = data['xyz'] + params = [] + param_count = 0 + query = """ + SELECT + CASE + WHEN pp.default_code IS NOT NULL + THEN CONCAT(pp.default_code, ' - ', pt.name) + ELSE + pt.name + END AS product_code_and_name, + svl.company_id, + company.name AS company_name, + svl.product_id, + pt.categ_id AS product_category_id, + c.complete_name AS category_name, + SUM(svl.remaining_qty) AS current_stock, + SUM(svl.remaining_value) AS stock_value + FROM stock_valuation_layer svl + INNER JOIN res_company company ON company.id = svl.company_id + INNER JOIN product_product pp ON pp.id = svl.product_id + INNER JOIN product_template pt ON pt.id = pp.product_tmpl_id + INNER JOIN product_category c ON c.id = pt.categ_id + WHERE pp.active = TRUE + AND pt.active = TRUE + AND pt.type = 'product' + AND svl.remaining_value IS NOT NULL + """ + if company_ids: + company_ids = [company_id for company_id in company_ids] + query += f" AND (company.id IS NULL OR company.id = ANY(%s))" + params.append(company_ids) + param_count += 1 + if product_ids or category_ids: + query += " AND (" + if product_ids: + product_ids = [product_id for product_id in product_ids] + query += f"pp.id = ANY(%s)" + params.append(product_ids) + param_count += 1 + if product_ids and category_ids: + query += " OR " + if category_ids: + category_ids = [category_id for category_id in + category_ids] + query += f"c.id = ANY(%s)" + params.append(category_ids) + param_count += 1 + query += ")" + query += """ + GROUP BY + svl.company_id, + company.name, + svl.product_id, + CASE + WHEN pp.default_code IS NOT NULL + THEN CONCAT(pp.default_code, ' - ', pt.name) + ELSE + pt.name + END, + pt.categ_id, + c.complete_name + ORDER BY SUM(svl.remaining_value) DESC; + """ + self.env.cr.execute(query, params) + result_data = self.env.cr.dictfetchall() + total_current_value = 0 + cumulative_stock = 0 + filtered_stock = [] + for row in result_data: + current_value = row.get('stock_value') + total_current_value += current_value + for value in result_data: + current_value = value.get('stock_value') + if total_current_value != 0 and current_value: + stock_percentage = (current_value / total_current_value) * 100 + else: + stock_percentage = 0.0 + value['stock_percentage'] = round(stock_percentage, 2) + cumulative_stock += value['stock_percentage'] + value['cumulative_stock_percentage'] = round(cumulative_stock, 2) + if cumulative_stock < 70: + xyz_classification = 'X' + elif 70 <= cumulative_stock <= 90: + xyz_classification = 'Y' + else: + xyz_classification = 'Z' + value['xyz_classification'] = xyz_classification + if result_data: + for xyz_class in result_data: + if xyz_class.get('xyz_classification') == str(xyz): + filtered_stock.append(xyz_class) + if xyz == 'All' and not result_data: + raise ValidationError("No corresponding data to print") + elif xyz != 'All' and filtered_stock == []: + raise ValidationError("No corresponding data to print") + return { + 'doc_ids': docids, + 'doc_model': + 'report.inventory_advanced_reports.report_inventory_xyz', + 'data': values, + 'options': result_data if xyz == 'All' else filtered_stock, + } + else: + raise ValidationError("No records found for the given criteria!") diff --git a/inventory_advanced_reports/report/xyz_report_views.xml b/inventory_advanced_reports/report/xyz_report_views.xml new file mode 100644 index 000000000..4e534d652 --- /dev/null +++ b/inventory_advanced_reports/report/xyz_report_views.xml @@ -0,0 +1,67 @@ + + + + + Inventory XYZ Report + report.inventory_advanced_reports.report_inventory_xyz + qweb-pdf + inventory_advanced_reports.report_inventory_xyz + inventory_advanced_reports.report_inventory_xyz + + report + + + + diff --git a/inventory_advanced_reports/security/ir.model.access.csv b/inventory_advanced_reports/security/ir.model.access.csv new file mode 100644 index 000000000..1f4afec03 --- /dev/null +++ b/inventory_advanced_reports/security/ir.model.access.csv @@ -0,0 +1,16 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_inventory_aging_report_user,access.inventory.aging.report.user,model_inventory_aging_report,base.group_user,1,1,1,1 +access_inventory_age_data_report_user,access.inventory.age.data.report.user,model_inventory_aging_data_report,base.group_user,1,1,1,1 +access_inventory_fsn_report_user,access.inventory.fsn.report.user,model_inventory_fsn_report,base.group_user,1,1,1,1 +access_inventory_aging_data_report_user,access.inventory.aging.data.report.user,model_inventory_aging_data_report,base.group_user,1,1,1,1 +access_inventory_fsn_data_report_user,access.inventory.fsn.data.report.user,model_inventory_fsn_data_report,base.group_user,1,1,1,1 +access_inventory_xyz_report_user,access.inventory.xyz.report.user,model_inventory_xyz_report,base.group_user,1,1,1,1 +access_inventory_xyz_data_report_user,access.inventory.xyz.data.report.user,model_inventory_xyz_data_report,base.group_user,1,1,1,1 +access_inventory_fsn_xyz_report_user,access.inventory.fsn.xyz.report.user,model_inventory_fsn_xyz_report,base.group_user,1,1,1,1 +access_inventory_fsn_xyz_data_report_user,access.inventory.fsn.xyz.data.report.user,model_inventory_fsn_xyz_data_report,base.group_user,1,1,1,1 +access_inventory_out_of_stock_report_user,access.inventory.out.of.stock.report.user,model_inventory_out_of_stock_report,base.group_user,1,1,1,1 +access_inventory_out_of_stock_data_report_user,access.inventory.out.of.stock.data.report.user,model_inventory_out_of_stock_data_report,base.group_user,1,1,1,1 +access_inventory_age_breakdown_report_user,access.inventory.age.breakdown.report.user,model_inventory_age_breakdown_report,base.group_user,1,1,1,1 +access_inventory_over_stock_report_user,access.inventory.over.stock.report.user,model_inventory_over_stock_report,base.group_user,1,1,1,1 +access_inventory_over_stock_data_report_user,access.inventory.over.stock.data.report.user,model_inventory_over_stock_data_report,base.group_user,1,1,1,1 +access_inventory_stock_movement_report_user,access.inventory.stock.movement.report.user,model_inventory_stock_movement_report,base.group_user,1,1,1,1 diff --git a/inventory_advanced_reports/static/description/assets/icons/check.png b/inventory_advanced_reports/static/description/assets/icons/check.png new file mode 100755 index 000000000..c8e85f51d Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/icons/check.png differ diff --git a/inventory_advanced_reports/static/description/assets/icons/chevron.png b/inventory_advanced_reports/static/description/assets/icons/chevron.png new file mode 100755 index 000000000..2089293d6 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/icons/chevron.png differ diff --git a/inventory_advanced_reports/static/description/assets/icons/cogs.png b/inventory_advanced_reports/static/description/assets/icons/cogs.png new file mode 100755 index 000000000..95d0bad62 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/icons/cogs.png differ diff --git a/inventory_advanced_reports/static/description/assets/icons/consultation.png b/inventory_advanced_reports/static/description/assets/icons/consultation.png new file mode 100755 index 000000000..8319d4baa Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/icons/consultation.png differ diff --git a/inventory_advanced_reports/static/description/assets/icons/ecom-black.png b/inventory_advanced_reports/static/description/assets/icons/ecom-black.png new file mode 100755 index 000000000..a9385ff13 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/icons/ecom-black.png differ diff --git a/inventory_advanced_reports/static/description/assets/icons/education-black.png b/inventory_advanced_reports/static/description/assets/icons/education-black.png new file mode 100755 index 000000000..3eb09b27b Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/icons/education-black.png differ diff --git a/inventory_advanced_reports/static/description/assets/icons/hotel-black.png b/inventory_advanced_reports/static/description/assets/icons/hotel-black.png new file mode 100755 index 000000000..130f613be Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/icons/hotel-black.png differ diff --git a/inventory_advanced_reports/static/description/assets/icons/license.png b/inventory_advanced_reports/static/description/assets/icons/license.png new file mode 100755 index 000000000..a5869797e Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/icons/license.png differ diff --git a/inventory_advanced_reports/static/description/assets/icons/lifebuoy.png b/inventory_advanced_reports/static/description/assets/icons/lifebuoy.png new file mode 100755 index 000000000..658d56ccc Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/icons/lifebuoy.png differ diff --git a/inventory_advanced_reports/static/description/assets/icons/logo.png b/inventory_advanced_reports/static/description/assets/icons/logo.png new file mode 100755 index 000000000..478462d3e Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/icons/logo.png differ diff --git a/inventory_advanced_reports/static/description/assets/icons/manufacturing-black.png b/inventory_advanced_reports/static/description/assets/icons/manufacturing-black.png new file mode 100755 index 000000000..697eb0e9f Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/icons/manufacturing-black.png differ diff --git a/inventory_advanced_reports/static/description/assets/icons/pos-black.png b/inventory_advanced_reports/static/description/assets/icons/pos-black.png new file mode 100755 index 000000000..97c0f90c1 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/icons/pos-black.png differ diff --git a/inventory_advanced_reports/static/description/assets/icons/puzzle.png b/inventory_advanced_reports/static/description/assets/icons/puzzle.png new file mode 100755 index 000000000..65cf854e7 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/icons/puzzle.png differ diff --git a/inventory_advanced_reports/static/description/assets/icons/restaurant-black.png b/inventory_advanced_reports/static/description/assets/icons/restaurant-black.png new file mode 100755 index 000000000..4a35eb939 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/icons/restaurant-black.png differ diff --git a/inventory_advanced_reports/static/description/assets/icons/service-black.png b/inventory_advanced_reports/static/description/assets/icons/service-black.png new file mode 100755 index 000000000..301ab51cb Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/icons/service-black.png differ diff --git a/inventory_advanced_reports/static/description/assets/icons/trading-black.png b/inventory_advanced_reports/static/description/assets/icons/trading-black.png new file mode 100755 index 000000000..9398ba2f1 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/icons/trading-black.png differ diff --git a/inventory_advanced_reports/static/description/assets/icons/training.png b/inventory_advanced_reports/static/description/assets/icons/training.png new file mode 100755 index 000000000..884ca024d Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/icons/training.png differ diff --git a/inventory_advanced_reports/static/description/assets/icons/update.png b/inventory_advanced_reports/static/description/assets/icons/update.png new file mode 100755 index 000000000..ecbc5a01a Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/icons/update.png differ diff --git a/inventory_advanced_reports/static/description/assets/icons/user.png b/inventory_advanced_reports/static/description/assets/icons/user.png new file mode 100755 index 000000000..6ffb23d9f Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/icons/user.png differ diff --git a/inventory_advanced_reports/static/description/assets/icons/wrench.png b/inventory_advanced_reports/static/description/assets/icons/wrench.png new file mode 100755 index 000000000..6c04dea0f Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/icons/wrench.png differ diff --git a/inventory_advanced_reports/static/description/assets/misc/categories.png b/inventory_advanced_reports/static/description/assets/misc/categories.png new file mode 100644 index 000000000..bedf1e0b1 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/misc/categories.png differ diff --git a/inventory_advanced_reports/static/description/assets/misc/check-box.png b/inventory_advanced_reports/static/description/assets/misc/check-box.png new file mode 100644 index 000000000..42caf24b9 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/misc/check-box.png differ diff --git a/inventory_advanced_reports/static/description/assets/misc/compass.png b/inventory_advanced_reports/static/description/assets/misc/compass.png new file mode 100644 index 000000000..d5fed8faa Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/misc/compass.png differ diff --git a/inventory_advanced_reports/static/description/assets/misc/corporate.png b/inventory_advanced_reports/static/description/assets/misc/corporate.png new file mode 100644 index 000000000..2eb13edbf Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/misc/corporate.png differ diff --git a/inventory_advanced_reports/static/description/assets/misc/customer-support.png b/inventory_advanced_reports/static/description/assets/misc/customer-support.png new file mode 100644 index 000000000..79efc72ed Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/misc/customer-support.png differ diff --git a/inventory_advanced_reports/static/description/assets/misc/cybrosys-logo.png b/inventory_advanced_reports/static/description/assets/misc/cybrosys-logo.png new file mode 100644 index 000000000..cc3cc0ccf Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/misc/cybrosys-logo.png differ diff --git a/inventory_advanced_reports/static/description/assets/misc/features.png b/inventory_advanced_reports/static/description/assets/misc/features.png new file mode 100644 index 000000000..b41769f77 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/misc/features.png differ diff --git a/inventory_advanced_reports/static/description/assets/misc/logo.png b/inventory_advanced_reports/static/description/assets/misc/logo.png new file mode 100644 index 000000000..478462d3e Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/misc/logo.png differ diff --git a/inventory_advanced_reports/static/description/assets/misc/pictures.png b/inventory_advanced_reports/static/description/assets/misc/pictures.png new file mode 100644 index 000000000..56d255fe9 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/misc/pictures.png differ diff --git a/inventory_advanced_reports/static/description/assets/misc/pie-chart.png b/inventory_advanced_reports/static/description/assets/misc/pie-chart.png new file mode 100644 index 000000000..426e05244 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/misc/pie-chart.png differ diff --git a/inventory_advanced_reports/static/description/assets/misc/reports-icon.png b/inventory_advanced_reports/static/description/assets/misc/reports-icon.png new file mode 100644 index 000000000..571820062 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/misc/reports-icon.png differ diff --git a/inventory_advanced_reports/static/description/assets/misc/right-arrow.png b/inventory_advanced_reports/static/description/assets/misc/right-arrow.png new file mode 100644 index 000000000..730984a06 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/misc/right-arrow.png differ diff --git a/inventory_advanced_reports/static/description/assets/misc/star.png b/inventory_advanced_reports/static/description/assets/misc/star.png new file mode 100644 index 000000000..2eb9ab29f Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/misc/star.png differ diff --git a/inventory_advanced_reports/static/description/assets/misc/support.png b/inventory_advanced_reports/static/description/assets/misc/support.png new file mode 100644 index 000000000..4f18b8b82 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/misc/support.png differ diff --git a/inventory_advanced_reports/static/description/assets/misc/whatsapp.png b/inventory_advanced_reports/static/description/assets/misc/whatsapp.png new file mode 100644 index 000000000..d513a5356 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/misc/whatsapp.png differ diff --git a/inventory_advanced_reports/static/description/assets/modules/1.png b/inventory_advanced_reports/static/description/assets/modules/1.png new file mode 100644 index 000000000..958601ec8 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/modules/1.png differ diff --git a/inventory_advanced_reports/static/description/assets/modules/2.png b/inventory_advanced_reports/static/description/assets/modules/2.png new file mode 100644 index 000000000..773a67300 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/modules/2.png differ diff --git a/inventory_advanced_reports/static/description/assets/modules/3.png b/inventory_advanced_reports/static/description/assets/modules/3.png new file mode 100644 index 000000000..2a52f4d41 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/modules/3.png differ diff --git a/inventory_advanced_reports/static/description/assets/modules/4.png b/inventory_advanced_reports/static/description/assets/modules/4.png new file mode 100644 index 000000000..923a3beea Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/modules/4.png differ diff --git a/inventory_advanced_reports/static/description/assets/modules/5.png b/inventory_advanced_reports/static/description/assets/modules/5.png new file mode 100644 index 000000000..dacb9817d Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/modules/5.png differ diff --git a/inventory_advanced_reports/static/description/assets/modules/6.png b/inventory_advanced_reports/static/description/assets/modules/6.png new file mode 100644 index 000000000..be55ec3ed Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/modules/6.png differ diff --git a/inventory_advanced_reports/static/description/assets/modules/budget_image.png b/inventory_advanced_reports/static/description/assets/modules/budget_image.png new file mode 100755 index 000000000..b50130c7d Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/modules/budget_image.png differ diff --git a/inventory_advanced_reports/static/description/assets/modules/credit_image.png b/inventory_advanced_reports/static/description/assets/modules/credit_image.png new file mode 100755 index 000000000..3ad04ecfd Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/modules/credit_image.png differ diff --git a/inventory_advanced_reports/static/description/assets/modules/employee_image.png b/inventory_advanced_reports/static/description/assets/modules/employee_image.png new file mode 100755 index 000000000..30ad58232 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/modules/employee_image.png differ diff --git a/inventory_advanced_reports/static/description/assets/modules/export_image.png b/inventory_advanced_reports/static/description/assets/modules/export_image.png new file mode 100755 index 000000000..492980ad0 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/modules/export_image.png differ diff --git a/inventory_advanced_reports/static/description/assets/modules/gantt_image.png b/inventory_advanced_reports/static/description/assets/modules/gantt_image.png new file mode 100755 index 000000000..1ae7cfe3b Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/modules/gantt_image.png differ diff --git a/inventory_advanced_reports/static/description/assets/modules/quotation_image.png b/inventory_advanced_reports/static/description/assets/modules/quotation_image.png new file mode 100755 index 000000000..499b1a72f Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/modules/quotation_image.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/AGE BREAKDOWN 1.png b/inventory_advanced_reports/static/description/assets/screenshots/AGE BREAKDOWN 1.png new file mode 100644 index 000000000..daca66cf2 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/AGE BREAKDOWN 1.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/AGE BREAKDOWN 2.png b/inventory_advanced_reports/static/description/assets/screenshots/AGE BREAKDOWN 2.png new file mode 100644 index 000000000..45c320698 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/AGE BREAKDOWN 2.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/AGE BREAKDOWN 3.png b/inventory_advanced_reports/static/description/assets/screenshots/AGE BREAKDOWN 3.png new file mode 100644 index 000000000..3ae57d760 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/AGE BREAKDOWN 3.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/AGE BREAKDOWN 4.png b/inventory_advanced_reports/static/description/assets/screenshots/AGE BREAKDOWN 4.png new file mode 100644 index 000000000..89cad710e Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/AGE BREAKDOWN 4.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/AGE BREAKDOWN 5.png b/inventory_advanced_reports/static/description/assets/screenshots/AGE BREAKDOWN 5.png new file mode 100644 index 000000000..aa0379c11 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/AGE BREAKDOWN 5.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/AGING 0.png b/inventory_advanced_reports/static/description/assets/screenshots/AGING 0.png new file mode 100644 index 000000000..1f4d700ce Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/AGING 0.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/AGING 1.png b/inventory_advanced_reports/static/description/assets/screenshots/AGING 1.png new file mode 100644 index 000000000..641700edb Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/AGING 1.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/AGING 2.png b/inventory_advanced_reports/static/description/assets/screenshots/AGING 2.png new file mode 100644 index 000000000..a5f6eaabe Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/AGING 2.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/AGING 3.png b/inventory_advanced_reports/static/description/assets/screenshots/AGING 3.png new file mode 100644 index 000000000..70e1e2f3e Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/AGING 3.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/AGING 4.png b/inventory_advanced_reports/static/description/assets/screenshots/AGING 4.png new file mode 100644 index 000000000..0228ed240 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/AGING 4.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/AGING 5.png b/inventory_advanced_reports/static/description/assets/screenshots/AGING 5.png new file mode 100644 index 000000000..bb35e11fe Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/AGING 5.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/AGING 6.png b/inventory_advanced_reports/static/description/assets/screenshots/AGING 6.png new file mode 100644 index 000000000..b846a5972 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/AGING 6.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/AGING 7.png b/inventory_advanced_reports/static/description/assets/screenshots/AGING 7.png new file mode 100644 index 000000000..830fedbcc Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/AGING 7.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/AGING 8.png b/inventory_advanced_reports/static/description/assets/screenshots/AGING 8.png new file mode 100644 index 000000000..ff6535c0d Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/AGING 8.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/FSN 1.png b/inventory_advanced_reports/static/description/assets/screenshots/FSN 1.png new file mode 100644 index 000000000..e4e9780b0 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/FSN 1.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/FSN 10.png b/inventory_advanced_reports/static/description/assets/screenshots/FSN 10.png new file mode 100644 index 000000000..a81c38e89 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/FSN 10.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/FSN 2.png b/inventory_advanced_reports/static/description/assets/screenshots/FSN 2.png new file mode 100644 index 000000000..f23aa8e8f Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/FSN 2.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/FSN 3.png b/inventory_advanced_reports/static/description/assets/screenshots/FSN 3.png new file mode 100644 index 000000000..d53979d90 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/FSN 3.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/FSN 4.png b/inventory_advanced_reports/static/description/assets/screenshots/FSN 4.png new file mode 100644 index 000000000..88ecf0adf Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/FSN 4.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/FSN 5.png b/inventory_advanced_reports/static/description/assets/screenshots/FSN 5.png new file mode 100644 index 000000000..114d5762e Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/FSN 5.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/FSN 6.png b/inventory_advanced_reports/static/description/assets/screenshots/FSN 6.png new file mode 100644 index 000000000..eb61750d9 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/FSN 6.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/FSN 7.png b/inventory_advanced_reports/static/description/assets/screenshots/FSN 7.png new file mode 100644 index 000000000..ca2b23338 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/FSN 7.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/FSN 8.png b/inventory_advanced_reports/static/description/assets/screenshots/FSN 8.png new file mode 100644 index 000000000..519703751 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/FSN 8.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/FSN 9.png b/inventory_advanced_reports/static/description/assets/screenshots/FSN 9.png new file mode 100644 index 000000000..c9df31f0e Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/FSN 9.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/FSN XYZ 1.png b/inventory_advanced_reports/static/description/assets/screenshots/FSN XYZ 1.png new file mode 100644 index 000000000..10a5c38fd Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/FSN XYZ 1.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/FSN XYZ 2.png b/inventory_advanced_reports/static/description/assets/screenshots/FSN XYZ 2.png new file mode 100644 index 000000000..bc0844c15 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/FSN XYZ 2.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/FSN XYZ 3.png b/inventory_advanced_reports/static/description/assets/screenshots/FSN XYZ 3.png new file mode 100644 index 000000000..5c233e2ad Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/FSN XYZ 3.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/FSN XYZ 4.png b/inventory_advanced_reports/static/description/assets/screenshots/FSN XYZ 4.png new file mode 100644 index 000000000..a39d54ff8 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/FSN XYZ 4.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/FSN XYZ 5.png b/inventory_advanced_reports/static/description/assets/screenshots/FSN XYZ 5.png new file mode 100644 index 000000000..369ee6ed4 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/FSN XYZ 5.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/FSN XYZ 6.png b/inventory_advanced_reports/static/description/assets/screenshots/FSN XYZ 6.png new file mode 100644 index 000000000..0b843d219 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/FSN XYZ 6.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/FSN XYZ 7.png b/inventory_advanced_reports/static/description/assets/screenshots/FSN XYZ 7.png new file mode 100644 index 000000000..dbddc7275 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/FSN XYZ 7.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/FSN XYZ 8.png b/inventory_advanced_reports/static/description/assets/screenshots/FSN XYZ 8.png new file mode 100644 index 000000000..56e66a7ee Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/FSN XYZ 8.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/FSN XYZ 9.png b/inventory_advanced_reports/static/description/assets/screenshots/FSN XYZ 9.png new file mode 100644 index 000000000..be06e1054 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/FSN XYZ 9.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/OUT OF STOCK 1.png b/inventory_advanced_reports/static/description/assets/screenshots/OUT OF STOCK 1.png new file mode 100644 index 000000000..cf201d020 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/OUT OF STOCK 1.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/OUT OF STOCK 2.png b/inventory_advanced_reports/static/description/assets/screenshots/OUT OF STOCK 2.png new file mode 100644 index 000000000..3a548eb02 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/OUT OF STOCK 2.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/OUT OF STOCK 3.png b/inventory_advanced_reports/static/description/assets/screenshots/OUT OF STOCK 3.png new file mode 100644 index 000000000..3f86e8e9d Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/OUT OF STOCK 3.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/OUT OF STOCK 4.png b/inventory_advanced_reports/static/description/assets/screenshots/OUT OF STOCK 4.png new file mode 100644 index 000000000..dffa240ea Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/OUT OF STOCK 4.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/OUT OF STOCK 5.png b/inventory_advanced_reports/static/description/assets/screenshots/OUT OF STOCK 5.png new file mode 100644 index 000000000..2f0f530f0 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/OUT OF STOCK 5.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/OUT OF STOCK 6.png b/inventory_advanced_reports/static/description/assets/screenshots/OUT OF STOCK 6.png new file mode 100644 index 000000000..aa319b544 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/OUT OF STOCK 6.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/OUT OF STOCK 7.png b/inventory_advanced_reports/static/description/assets/screenshots/OUT OF STOCK 7.png new file mode 100644 index 000000000..685cbe5b4 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/OUT OF STOCK 7.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/OUT OF STOCK 8.png b/inventory_advanced_reports/static/description/assets/screenshots/OUT OF STOCK 8.png new file mode 100644 index 000000000..586ce443f Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/OUT OF STOCK 8.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/OUT OF STOCK 9.png b/inventory_advanced_reports/static/description/assets/screenshots/OUT OF STOCK 9.png new file mode 100644 index 000000000..0d75c118f Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/OUT OF STOCK 9.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/OVER STOCK 1.png b/inventory_advanced_reports/static/description/assets/screenshots/OVER STOCK 1.png new file mode 100644 index 000000000..884df78ae Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/OVER STOCK 1.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/OVER STOCK 2.png b/inventory_advanced_reports/static/description/assets/screenshots/OVER STOCK 2.png new file mode 100644 index 000000000..ac48e6df7 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/OVER STOCK 2.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/OVER STOCK 3.png b/inventory_advanced_reports/static/description/assets/screenshots/OVER STOCK 3.png new file mode 100644 index 000000000..66dcd2e9a Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/OVER STOCK 3.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/OVER STOCK 4.png b/inventory_advanced_reports/static/description/assets/screenshots/OVER STOCK 4.png new file mode 100644 index 000000000..de41a2b00 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/OVER STOCK 4.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/OVER STOCK 5.png b/inventory_advanced_reports/static/description/assets/screenshots/OVER STOCK 5.png new file mode 100644 index 000000000..c8bc931e1 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/OVER STOCK 5.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/OVER STOCK 6.png b/inventory_advanced_reports/static/description/assets/screenshots/OVER STOCK 6.png new file mode 100644 index 000000000..12e90b08d Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/OVER STOCK 6.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/OVER STOCK 7.png b/inventory_advanced_reports/static/description/assets/screenshots/OVER STOCK 7.png new file mode 100644 index 000000000..3e1d6cc9d Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/OVER STOCK 7.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/OVER STOCK 8.png b/inventory_advanced_reports/static/description/assets/screenshots/OVER STOCK 8.png new file mode 100644 index 000000000..27a5db49f Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/OVER STOCK 8.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/OVER STOCK 9.png b/inventory_advanced_reports/static/description/assets/screenshots/OVER STOCK 9.png new file mode 100644 index 000000000..6e92002c3 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/OVER STOCK 9.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/STOCK MOVE 1.png b/inventory_advanced_reports/static/description/assets/screenshots/STOCK MOVE 1.png new file mode 100644 index 000000000..ad330f7ed Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/STOCK MOVE 1.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/STOCK MOVE 2.png b/inventory_advanced_reports/static/description/assets/screenshots/STOCK MOVE 2.png new file mode 100644 index 000000000..d7c658774 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/STOCK MOVE 2.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/STOCK MOVE 3.png b/inventory_advanced_reports/static/description/assets/screenshots/STOCK MOVE 3.png new file mode 100644 index 000000000..507b2d941 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/STOCK MOVE 3.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/STOCK MOVE 4.png b/inventory_advanced_reports/static/description/assets/screenshots/STOCK MOVE 4.png new file mode 100644 index 000000000..bd8c698c3 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/STOCK MOVE 4.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/STOCK MOVE 5.png b/inventory_advanced_reports/static/description/assets/screenshots/STOCK MOVE 5.png new file mode 100644 index 000000000..ce2caa470 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/STOCK MOVE 5.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/XYZ 1.png b/inventory_advanced_reports/static/description/assets/screenshots/XYZ 1.png new file mode 100644 index 000000000..082fe11dc Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/XYZ 1.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/XYZ 2.png b/inventory_advanced_reports/static/description/assets/screenshots/XYZ 2.png new file mode 100644 index 000000000..29a26ad35 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/XYZ 2.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/XYZ 3.png b/inventory_advanced_reports/static/description/assets/screenshots/XYZ 3.png new file mode 100644 index 000000000..bc6331da2 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/XYZ 3.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/XYZ 4.png b/inventory_advanced_reports/static/description/assets/screenshots/XYZ 4.png new file mode 100644 index 000000000..a1ab168f5 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/XYZ 4.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/XYZ 5.png b/inventory_advanced_reports/static/description/assets/screenshots/XYZ 5.png new file mode 100644 index 000000000..2071f5e90 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/XYZ 5.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/XYZ 6.png b/inventory_advanced_reports/static/description/assets/screenshots/XYZ 6.png new file mode 100644 index 000000000..9dcc877e4 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/XYZ 6.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/XYZ 7.png b/inventory_advanced_reports/static/description/assets/screenshots/XYZ 7.png new file mode 100644 index 000000000..57f180ee5 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/XYZ 7.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/XYZ 8.png b/inventory_advanced_reports/static/description/assets/screenshots/XYZ 8.png new file mode 100644 index 000000000..9d4d29260 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/XYZ 8.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/XYZ 9.png b/inventory_advanced_reports/static/description/assets/screenshots/XYZ 9.png new file mode 100644 index 000000000..01a072ca2 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/XYZ 9.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/hero.gif b/inventory_advanced_reports/static/description/assets/screenshots/hero.gif new file mode 100644 index 000000000..cc81b29a9 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/hero.gif differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/inv_format1.png b/inventory_advanced_reports/static/description/assets/screenshots/inv_format1.png new file mode 100755 index 000000000..84f41d13d Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/inv_format1.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/inv_format2.png b/inventory_advanced_reports/static/description/assets/screenshots/inv_format2.png new file mode 100755 index 000000000..458cbcc0f Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/inv_format2.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/inv_format3.png b/inventory_advanced_reports/static/description/assets/screenshots/inv_format3.png new file mode 100755 index 000000000..299c60d8d Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/inv_format3.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/inv_format4.png b/inventory_advanced_reports/static/description/assets/screenshots/inv_format4.png new file mode 100755 index 000000000..154e5a0af Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/inv_format4.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/inv_format5.png b/inventory_advanced_reports/static/description/assets/screenshots/inv_format5.png new file mode 100755 index 000000000..d647eab5c Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/inv_format5.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/inv_format6.png b/inventory_advanced_reports/static/description/assets/screenshots/inv_format6.png new file mode 100755 index 000000000..4846adb7c Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/inv_format6.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/inv_format7.png b/inventory_advanced_reports/static/description/assets/screenshots/inv_format7.png new file mode 100755 index 000000000..99666dc81 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/inv_format7.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/re1.png b/inventory_advanced_reports/static/description/assets/screenshots/re1.png new file mode 100755 index 000000000..ced81bc24 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/re1.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/re2.png b/inventory_advanced_reports/static/description/assets/screenshots/re2.png new file mode 100755 index 000000000..52a9c7aaa Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/re2.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/re3.png b/inventory_advanced_reports/static/description/assets/screenshots/re3.png new file mode 100755 index 000000000..6e903bbd1 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/re3.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/re_default.png b/inventory_advanced_reports/static/description/assets/screenshots/re_default.png new file mode 100755 index 000000000..5aa4e9f0b Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/re_default.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/screen/AGE BREAKDOWN 1.png b/inventory_advanced_reports/static/description/assets/screenshots/screen/AGE BREAKDOWN 1.png new file mode 100644 index 000000000..c94945339 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/screen/AGE BREAKDOWN 1.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/screen/AGE BREAKDOWN 2.png b/inventory_advanced_reports/static/description/assets/screenshots/screen/AGE BREAKDOWN 2.png new file mode 100644 index 000000000..45c320698 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/screen/AGE BREAKDOWN 2.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/screen/AGE BREAKDOWN 3.png b/inventory_advanced_reports/static/description/assets/screenshots/screen/AGE BREAKDOWN 3.png new file mode 100644 index 000000000..3ae57d760 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/screen/AGE BREAKDOWN 3.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/screen/AGE BREAKDOWN 4.png b/inventory_advanced_reports/static/description/assets/screenshots/screen/AGE BREAKDOWN 4.png new file mode 100644 index 000000000..89cad710e Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/screen/AGE BREAKDOWN 4.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/screen/AGE BREAKDOWN 5.png b/inventory_advanced_reports/static/description/assets/screenshots/screen/AGE BREAKDOWN 5.png new file mode 100644 index 000000000..aa0379c11 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/screen/AGE BREAKDOWN 5.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/screen/AGING 0.png b/inventory_advanced_reports/static/description/assets/screenshots/screen/AGING 0.png new file mode 100644 index 000000000..3b3a38954 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/screen/AGING 0.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/screen/AGING 1.png b/inventory_advanced_reports/static/description/assets/screenshots/screen/AGING 1.png new file mode 100644 index 000000000..1c8b0a007 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/screen/AGING 1.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/screen/AGING 2.png b/inventory_advanced_reports/static/description/assets/screenshots/screen/AGING 2.png new file mode 100644 index 000000000..9bb0f44a6 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/screen/AGING 2.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/screen/AGING 5.png b/inventory_advanced_reports/static/description/assets/screenshots/screen/AGING 5.png new file mode 100644 index 000000000..9aa584770 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/screen/AGING 5.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/screen/AGING 6.png b/inventory_advanced_reports/static/description/assets/screenshots/screen/AGING 6.png new file mode 100644 index 000000000..a230fac73 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/screen/AGING 6.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/screen/FSN 1.png b/inventory_advanced_reports/static/description/assets/screenshots/screen/FSN 1.png new file mode 100644 index 000000000..fbfc8b9c6 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/screen/FSN 1.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/screen/FSN 3.png b/inventory_advanced_reports/static/description/assets/screenshots/screen/FSN 3.png new file mode 100644 index 000000000..e8426b4f8 Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/screen/FSN 3.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/screen/FSN 4.png b/inventory_advanced_reports/static/description/assets/screenshots/screen/FSN 4.png new file mode 100644 index 000000000..88ecf0adf Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/screen/FSN 4.png differ diff --git a/inventory_advanced_reports/static/description/assets/screenshots/screen/FSN 6.png b/inventory_advanced_reports/static/description/assets/screenshots/screen/FSN 6.png new file mode 100644 index 000000000..cb4fb3c7a Binary files /dev/null and b/inventory_advanced_reports/static/description/assets/screenshots/screen/FSN 6.png differ diff --git a/inventory_advanced_reports/static/description/banner.png b/inventory_advanced_reports/static/description/banner.png new file mode 100644 index 000000000..89857690d Binary files /dev/null and b/inventory_advanced_reports/static/description/banner.png differ diff --git a/inventory_advanced_reports/static/description/icon.png b/inventory_advanced_reports/static/description/icon.png new file mode 100644 index 000000000..1bf9f3736 Binary files /dev/null and b/inventory_advanced_reports/static/description/icon.png differ diff --git a/inventory_advanced_reports/static/description/index.html b/inventory_advanced_reports/static/description/index.html new file mode 100644 index 000000000..8842e5e4d --- /dev/null +++ b/inventory_advanced_reports/static/description/index.html @@ -0,0 +1,2104 @@ +
+
+
+
+ +
+
+
+ Community +
+
+ Enterprise +
+ +
+
+
+
+ +
+
+
+

+ Inventory Advanced Reports +

+

+ Helps to manage different types of Inventory Reports like FSN + Report, Out Of Stock Report, Inventory XYZ Report, etc.

+ + +
+
+
+ +
+
+ +
+

+ Explore This + Module

+
+ + + +
+
+ +
+

+ Overview +

+
+
+
+ This module allows you to generate inventory reports, + users can track Aging analysis, FSN classification + (inventory movement frequency classification), XYZ classification + (inventory classification based on stock value), FSN-XYZ combined + classification to define sales strategies for the existing inventories. + Overstock analysis, Out of Stock analysis and Stock movements (inventory + rotation). +
+
+ + +
+
+ +
+

+ Features +

+
+
+
+
+ + Inventory Aging Report. +
+
+ + Inventory Age Breakdown Report. +
+
+ + Inventory FSN Report. +
+
+ + Inventory XYZ Report. +
+
+ + Inventory FSN-XYZ Report. +
+
+ + Inventory Out Of Stock Report. +
+
+ + Inventory Over Stock Report. +
+
+ + Inventory Stock Movement Report. +
+
+
+
+
+ + +
+
+ +
+

+ Reports +

+
+
+
+

+ Inventory Advanced Reports +

+
+
+
+ + +
+ +
+
+
+
+
    +
  • + + +
    +

    Inventory Aging Report +

    +
    +

    +
    + This report categorizes inventory items based on their age, helping users identify aging stock that might need special attention. It categorizes products based on their time in stock, providing insights into how long items have been held. This report calculates the days since receipt, current stock levels, and their corresponding values, allowing businesses to identify slow-moving or obsolete items. By visually representing this data and offering export options, it helps companies make informed decisions about inventory management, such as restocking, discounting, or discontinuing products, ultimately optimizing their stock and reducing carrying costs. +

    + + +
    +

    Advantages of Inventory Aging Report +

    +

    +
    +
  • + arrow + Users can select specific products and + categories they want to generate the + report for. +
  • +
  • arrow The + report can be filtered by the company. +
  • +
  • arrow The + report categorizes products based on + their age, allowing you to see how long + items have been in stock. +
  • +
  • arrow The + report calculates the age of each + product from its date of receipt and + shows the number of days since receipt. +
  • +
  • arrow Displays + the current quantity available in stock + for each product and current value of + the product in stock, taking into + account its standard price. +
  • +
  • arrow + Percentage of Total Quantity and + Percentage of Total Value helps help you + understand the relative significance of + a particular product in your overall + stock. +
  • +
  • arrow Users + can export the report to Excel or PDF + formats for further analysis and + sharing. +
  • +
  • arrow + Graphical and tree views are also + available. +
  • +

    +
    +
    +

    + Screenshots +

    +
    + +
    + +
+
+
+
+
+ +
+
+
+
+
    +
  • + + +
    +

    Inventory Age Breakdown Report +

    +
    +

    +
    +This report provides a detailed analysis of how long products have been sitting in your inventory. It categorizes products into different age groups based on the number of days they have been in stock, making it easy to identify slow-moving items and potential issues with inventory turnover. The report shows the quantity and value of products in each age category, helping businesses make informed decisions about inventory management and sales strategies. This report helps in optimizing stock levels and reducing holding costs while ensuring that products are available to meet customer demand. + + +

    +

    + Advantages of Inventory Age Breakdown Report +

    +
    +
    +
    +
  • arrow Users + can customize the age periods for + analysis. +
  • +
  • arrow The + report displays both the quantity and + value of products. +
  • +
  • arrow + Businesses can use this report to make + informed decisions about restocking, + discounting, or discontinuing specific + products. +
  • +
  • arrow Users + can filter the report by + company,product, category and age + period. +
  • +
  • arrow The + report is available in both PDF and + Excel formats. +
  • +
  • arrow The + report can be displayed in graphical and + tree views. +
  • +

    +
    +
    +

    + Screenshots +

    +
    + +
    + + +
+
+
+
+
+ + +
+
+
+
+
    +
  • + + +
    +

    Inventory FSN Report +

    +
    +

    +
    +The FSN Report (Fast Moving, Slow Moving, Non-Moving Report) is used for analyzing and categorizing inventory items based on their movement and turnover rates within a specified time frame. This report provides insights into the performance of products, helping businesses make informed decisions about stock management and optimization.The report provides valuable insights into product performance, enabling companies to maximize profits and ensure efficient stock management.

    + F (Fast): Stock turnover ratio > 3.
    +S (Slow): Stock turnover ratio between 1 and 3.
    +N (Non-moving): Stock turnover ratio < 1.
    + + +

    +

    + Advantages of Inventory FSN Report +

    +
    +
    +
    +
  • arrow Products + are classified into three main + categories - Fast Moving, Slow Moving, + and Non-Moving. +
  • +
  • arrow + Fast-moving products are those with high + turnover rates, while slow-moving + products have moderate turnover, and + non-moving products have low or no + sales. +
  • +
  • arrow The + report allows users to set a specific + start and end date for analysis. +
  • +
  • arrow Users + can filter the report by selecting + specific products, product categories, + companies and warehouses. +
  • +
  • arrow The + report provides data on opening and + closing stock, sales, average stock, + turnover ratio, and the FSN + classification for each product. +
  • +
  • arrow Users + can choose between graphical and list + views to visualize the data. +
  • +
  • arrow The + report can be exported to PDF and Excel + formats. +
  • +

    +
    +
    +

    + Screenshots +

    +
    + +
    + +
+
+
+
+
+ +
+
+
+
+
    +
  • + + +
    +

    Inventory XYZ Report +

    +
    +

    +
    +The XYZ Report used for inventory management that categorizes products based on their value and significance in a company's stock. This classification helps businesses identify the products that are vital for their operations and profitability. The report groups products into three categories: X, Y, and Z. 'X' represents the most important and valuable products (high value), 'Y' includes products of moderate significance, and 'Z' consists of less critical items (low value). This categorization is based on factors such as stock value, stock percentage, and cumulative stock percentage. The XYZ Report helps businesses focus on the items that affect their profits the most.

    + X Class: Top 70% of inventory value.
    +Y Class: The next 20% of inventory value.
    +Z Class: The bottom 10% of inventory value. +

    + + +
    +

    Advantages of Inventory XYZ Report +

    +
    +
  • +
  • arrow + Classifies products into three + categories - X, Y, and Z, based on their + importance and value +
  • +
  • arrow Users + can select specific products, product + categories, companies, warehouses for + analysis. +
  • +
  • arrow The + report calculates and displays the + cumulative stock percentages, helping + businesses understand the collective + impact of their inventory. +
  • +
  • arrow Users + can generate and export reports in both + PDF and Excel formats. +
  • +
  • arrow Provides + graphical and tree representations. +
  • +
  • arrow The + report provides valuable insights for + making informed decisions about + inventory priorities and management. +
  • +

    + + +
    +
    +

    + Screenshots +

    +
    + +
    +
+
+
+
+
+ + +
+
+
+
+
    +
  • + + +
    +

    Inventory FSN-XYZ Report +

    +
    +

    +
    +The FSN-XYZ Report helps to analyze and classify the inventory based on two key dimensions. Firstly, it categorizes items into Fast-Moving (F), Slow-Moving (S), and Non-Moving (N) based on their turnover ratios, helping companies identify which products are selling rapidly and which are stagnating. Secondly, it classifies inventory into X, Y, and Z categories based on their contribution to the overall stock value, enabling businesses to focus their resources on high-value items. This report offers essential insights for optimizing inventory management and making strategic decisions to improve profitability. + + +

    +

    Advantages of Inventory FSN-XYZ Report +

    +
    +
  • +
  • arrow The + report classifies items into Fast-Moving + (F), Slow-Moving (S), or Non-Moving (N) + based on turnover ratios, enabling quick + identification of sales performance. +
  • +
  • arrow + Inventory is labeled as X, Y, or Z to + prioritize high-value items for + management decisions. +
  • +
  • arrow Users + can specify a start and end date for the + report, allowing for time-bound analysis + of inventory performance. +
  • +
  • arrow Users + can filter the report based on specific + products, product categories, companies, + warehouses, f/s/n and x/y/z +
  • +
  • arrow The + report can be exported in PDF and Excel + formats. +
  • +
  • arrow Users + have the option to view the report data + in graphical and tree formats. +
  • +
  • arrow The + report offers a combined classification + that combines FSN and XYZ categories, + simplifying decision-making based on + both turnover and stock value. +
  • +
  • arrow It + calculates the cumulative stock + percentage, helping users understand the + collective contribution of items to the + stock value. +
  • +

    + + +
    +
    +

    + Screenshots +

    +
    + +
    +
+
+
+
+
+ + +
+
+
+
+
    +
  • + + +
    +

    Inventory Out Of Stock Report +

    +
    +

    +
    +The Out of Stock report provide insights into the availability of products within a specified time period. This report offers valuable information regarding current stock levels, incoming and outgoing quantities, virtual stock, sales data, and more. It calculates essential performance metrics such as Advanced Stock Days (ADS), Demanded Quantity, In Stock Days, Out of Stock Days, and Turnover Ratio. Additionally, the report categorizes products into Fast-Moving, Slow-Moving, or Non-Moving classifications based on their sales performance. By identifying items that are low or out of stock, this report helps businesses optimize their inventory management and ensure products are available to meet customer demand.

    + + +
    +

    Advantages of Inventory Out Of Stock Report +

    +
    +
  • +
  • arrow The + report provides current stock levels, + incoming and outgoing quantities, + virtual stock, and sales data. +
  • +
  • arrow It + calculates the Advanced Stock Days, + helping businesses understand how many + days their current stock will last based + on sales data. +
  • +
  • arrow The + report calculates the quantity of a + product demanded by customers, allowing + for better stock planning. +
  • +
  • arrow It + measures the number of days a product is + expected to remain in stock, providing + insights into stock sufficiency. +
  • +
  • arrowThe + report also calculates the number of + days a product is projected to be out of + stock, helping in preventing stockouts. +
  • +
  • arrow The + report computes the Turnover Ratio, + which is the ratio of sales to virtual + stock. It assists in identifying + fast-moving and slow-moving products. +
  • +
  • arrow Users + can filter the report based on criteria + like date range, specific warehouses, + product selection, product categories, + and company. +
  • +
  • arrow The + report includes cost information, + allowing users to assess the financial + impact of out-of-stock situations. +
  • +
  • arrow It + offers the flexibility to export the + report data in PDF or Excel formats. +
  • +
  • arrow The + report often includes graphical and tree + representations. +
  • +

    + + +
    +
    +

    + Screenshots +

    +
    + +
    +
+
+
+
+
+ + +
+
+
+
+
    +
  • + + +
    +

    Inventory Over Stock Report +

    +
    +

    +The Over Stock report helps to analyze and understand situations where a surplus of products exists in their stock. This report provides detailed insights into various inventory parameters, such as current stock levels, sales history, advanced stock days, and turnover ratios. By identifying overstocked items, businesses can take informed actions to mitigate financial losses, reduce storage costs, and optimize their inventory strategies. Additionally, it offers a way to categorize products as Fast-Moving, Slow-Moving, or Non-Moving based on their sales performance, aiding in better decision-making for inventory control. The Over Stock Report ultimately assists organizations in maintaining a healthy balance of stock, enhancing overall efficiency, and minimizing excess holding of goods.

    + + +
    +

    Advantages of Inventory Over Stock Report +

    +
    +
  • +
  • arrow The + report provides insights into advanced + stock days and sales history, allowing + users to forecast how many days the + current inventory will last. +
  • +
  • arrow Various + inventory metrics are calculated and + displayed in the report, including + current stock, incoming and outgoing + quantities, virtual stock, sales, ADS, + and more. +
  • +
  • arrow The + report identifies products with + excessive stock levels and calculates + overstock quantities and percentages. +
  • +
  • arrow It + calculates the turnover ratio for each + product, indicating how quickly items + are sold. +
  • +
  • arrow Products + are classified as "Fast Moving," "Slow + Moving," or "Non Moving" based on their + sales performance. +
  • +
  • arrow The + report includes information about the + last purchase order date, quantity, + price, currency, and partner for each + product. +
  • +
  • arrow Users + can filter the report based on criteria + like date range, specific warehouses, + product selection, product categories, + and company. +
  • +
  • arrow Users + can generate the report in both PDF and + Excel formats. +
  • +
  • arrow Users + can switch between tree and graph views + for more comprehensive data analysis. +

    + + +
    +
    +

    + Screenshots +

    +
    + +
    +
+
+
+
+
+ + +
+
+
+
+
    +
  • + + +
    +

    Inventory Stock Movement Report +

    +
    +

    +The Stock Movement Report provides insights into the flow of products. It includes information on the opening and closing stock quantities, sales, sales returns, purchases, purchase returns, internal movements, adjustments, production-related movements, and transit-related movements for various products. The report offers a breakdown by product, product category, and company, making it a valuable tool for tracking stock movements, optimizing inventory levels, and assessing the overall performance of the supply chain. The report can be generated in both PDF and Excel formats for easy sharing and analysis, providing a comprehensive view of how inventory items have been managed within the specified date range or up to a certain date.

    + + +
    +

    Advantages of Inventory Stock Movement Report +

    +
    +
  • +
  • arrow The + report breaks down stock movements into + various categories, such as sales, sales + returns, purchases, purchase returns, + internal movements, adjustments, + production-related movements, and + transit-related movements. +
  • +
  • arrow The + report provides information on the + opening stock (stock at the beginning of + the selected period) and closing stock + (stock at the end of the selected period + or up to a certain date). +
  • +
  • arrow Users + can choose to generate the report in PDF + or Excel format. +
  • +
  • arrow The + report includes detailed information + about each product, its category, the + company it belongs to, and the quantity + of stock involved in various types of + movements. +
  • +
  • arrow Users + can generate the report for a specified + date range or up to a certain date, + depending on their reporting needs. +
  • +
  • arrow Users + can filter the report based on criteria + like date range, specific warehouses, + product selection, product categories, + and company. +
  • +

    + + +
    +
    +

    + Screenshots +

    +
    + +
    +
+
+
+
+
+
+
+
+
+ + +
+
+

Suggested Products

+
+ + +
+
+ + +
+
+ +
+

+ Our Services +

+
+
+
+
+
+ +
+
+ Odoo + Customization
+
+
+
+ +
+
+ Odoo + Implementation
+
+
+
+ +
+
+ Odoo + Support
+
+
+
+ +
+
+ Hire + Odoo + Developer
+
+
+
+ +
+
+ Odoo + Integration
+
+
+
+ +
+
+ Odoo + Migration
+
+
+
+ +
+
+ Odoo + Consultancy
+
+
+
+ +
+
+ Odoo + Implementation
+
+
+
+ +
+
+ Odoo + Licensing Consultancy
+
+
+
+ + +
+
+ +
+

+ Our + Industries +

+
+
+
+
+
+ +
+ Trading +
+

+ Easily procure + and + sell your products

+
+
+
+
+ +
+ POS +
+

+ Easy + configuration + and convivial experience

+
+
+
+
+ +
+ Education +
+

+ A platform for + educational management

+
+
+
+
+ +
+ Manufacturing +
+

+ Plan, track and + schedule your operations

+
+
+
+
+ +
+ E-commerce & Website +
+

+ Mobile + friendly, + awe-inspiring product pages

+
+
+
+
+ +
+ Service Management +
+

+ Keep track of + services and invoice

+
+
+
+
+ +
+ Restaurant +
+

+ Run your bar or + restaurant methodically

+
+
+
+
+ +
+ Hotel Management +
+

+ An + all-inclusive + hotel management application

+
+
+
+
+ + +
+
+ +
+

+ Support +

+
+
+
+
+
+
+ +
+
+

Need Help?

+

Got questions or need help? + Get in touch.

+ +

+ odoo@cybrosys.com

+
+
+
+
+
+
+
+ +
+
+

WhatsApp

+

Say hi to us on WhatsApp!

+ +

+ +91 86068 + 27707

+
+
+
+
+
+
+
+ +
+
+
+ + + + + + diff --git a/inventory_advanced_reports/static/src/js/action_manager.js b/inventory_advanced_reports/static/src/js/action_manager.js new file mode 100755 index 000000000..2635bf948 --- /dev/null +++ b/inventory_advanced_reports/static/src/js/action_manager.js @@ -0,0 +1,19 @@ +/** @odoo-module */ +import { registry } from "@web/core/registry"; +import framework from 'web.framework'; +import session from 'web.session'; + +registry.category("ir.actions.report handlers").add("xlsx", async (action) => { + if (action.report_type === 'xlsx') { + framework.blockUI(); + var def = $.Deferred(); + session.get_file({ + url: '/xlsx_reports', + data: action.data, + success: def.resolve.bind(def), + error: (error) => this.call('crash_manager', 'rpc_error', error), + complete: framework.unblockUI, + }); + return def; + } +}); \ No newline at end of file diff --git a/inventory_advanced_reports/wizard/__init__.py b/inventory_advanced_reports/wizard/__init__.py new file mode 100644 index 000000000..11da6f27c --- /dev/null +++ b/inventory_advanced_reports/wizard/__init__.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2024-TODAY Cybrosys Technologies() +# Author: Gayathri V (odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see . +# +############################################################################### +from . import inventory_age_breakdown_report +from . import inventory_aging_data_report +from . import inventory_aging_report +from . import inventory_fsn_data_report +from . import inventory_fsn_report +from . import inventory_fsn_xyz_data_report +from . import inventory_fsn_xyz_report +from . import inventory_out_of_stock_data_report +from . import inventory_out_of_stock_report +from . import inventory_over_stock_data_report +from . import inventory_over_stock_report +from . import inventory_stock_movement_report +from . import inventory_xyz_data_report +from . import inventory_xyz_report diff --git a/inventory_advanced_reports/wizard/inventory_age_breakdown_report.py b/inventory_advanced_reports/wizard/inventory_age_breakdown_report.py new file mode 100644 index 000000000..0944a8309 --- /dev/null +++ b/inventory_advanced_reports/wizard/inventory_age_breakdown_report.py @@ -0,0 +1,274 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2024-TODAY Cybrosys Technologies() +# Author: Gayathri V (odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see . +# +############################################################################### +import io +import json +from odoo import fields, models +from odoo.exceptions import ValidationError + +try: + from odoo.tools.misc import xlsxwriter +except ImportError: + import xlsxwriter + + +class InventoryAgeBreakdownReport(models.TransientModel): + """This model is for creating a wizard for inventory age breakdown report""" + _name = "inventory.age.breakdown.report" + _description = "Inventory Age Breakdown Report" + + product_ids = fields.Many2many( + "product.product", string="Products", + help="Select the products you want to generate the report for") + category_ids = fields.Many2many( + "product.category", string="Product Categories", + help="Select the product categories you want to generate the report for" + ) + company_ids = fields.Many2many( + 'res.company', string="Company", + help="Select the companies you want to generate the report for" + ) + age_breakdown_days = fields.Integer(string="Age Breakdown Days", default=30) + + def get_report_data(self): + """Function to return necessary data for printing""" + params = [] + param_count = 0 + query = """ + SELECT + CASE + WHEN pp.default_code IS NOT NULL + THEN CONCAT(pp.default_code, ' - ', pt.name) + ELSE + pt.name + END AS product_code_and_name, + c.complete_name AS category_name, + c.id AS category_id, + pp.id AS product_id, + company.id AS company_id, + company.name AS company_name, + COALESCE(SUM(svl.remaining_qty), 0) AS qty_available, + SUM(svl.remaining_value) AS stock_value, + SUM(CASE + WHEN age.days_between >= 1 AND age.days_between <= %s + THEN svl.remaining_qty + ELSE 0 + END) AS "age_breakdown_qty_1", + SUM(CASE + WHEN age.days_between >= %s+1 AND age.days_between <= %s*2 + THEN svl.remaining_qty + ELSE 0 + END) AS "age_breakdown_qty_2", + SUM(CASE + WHEN age.days_between >= (%s*2)+1 AND age.days_between <= %s*3 + THEN svl.remaining_qty + ELSE 0 + END) AS "age_breakdown_qty_3", + SUM(CASE + WHEN age.days_between >= (%s*3)+1 AND age.days_between <= %s*4 + THEN svl.remaining_qty + ELSE 0 + END) AS "age_breakdown_qty_4", + SUM(CASE + WHEN age.days_between >= (%s*4)+1 THEN svl.remaining_qty + ELSE 0 + END) AS "age_breakdown_qty_5", + SUM(CASE + WHEN age.days_between >= 1 AND age.days_between <= %s + THEN svl.remaining_value + ELSE 0 + END) AS "age_breakdown_value_1", + SUM(CASE + WHEN age.days_between >= %s+1 AND age.days_between <= %s*2 + THEN svl.remaining_value + ELSE 0 + END) AS "age_breakdown_value_2", + SUM(CASE + WHEN age.days_between >= (%s*2)+1 AND age.days_between <= %s*3 + THEN svl.remaining_value + ELSE 0 + END) AS "age_breakdown_value_3", + SUM(CASE + WHEN age.days_between >= (%s*3)+1 AND age.days_between <= %s*4 + THEN svl.remaining_value + ELSE 0 + END) AS "age_breakdown_value_4", + SUM(CASE + WHEN age.days_between >= (%s*4)+1 THEN svl.remaining_value + ELSE 0 + END) AS "age_breakdown_value_5" + FROM product_product pp + INNER JOIN product_template pt ON pp.product_tmpl_id = pt.id + INNER JOIN product_category c ON pt.categ_id = c.id + LEFT JOIN stock_move sm ON sm.product_id = pp.id + LEFT JOIN stock_picking_type spt ON sm.picking_type_id = spt.id + LEFT JOIN res_company company ON sm.company_id = company.id + LEFT JOIN LATERAL ( + SELECT EXTRACT(day FROM CURRENT_DATE - sm.date) AS days_between + ) AS age ON true + INNER JOIN stock_valuation_layer svl ON svl.stock_move_id = sm.id + WHERE pt.detailed_type = 'product' + AND sm.state = 'done' + AND svl.remaining_value IS NOT NULL + """ + params.extend([self.age_breakdown_days] * 16) + if self.product_ids or self.category_ids: + query += " AND (" + if self.product_ids: + product_ids = [product_id.id for product_id in self.product_ids] + query += "pp.id = ANY(%s)" + params.append(product_ids) + param_count += 1 + if self.product_ids and self.category_ids: + query += " OR " + if self.category_ids: + category_ids = [category.id for category in self.category_ids] + params.append(category_ids) + query += "(pt.categ_id = ANY(%s))" + param_count += 1 + if self.product_ids or self.category_ids: + query += ")" + if self.company_ids: + company_ids = [company.id for company in self.company_ids] + query += " AND (sm.company_id = ANY(%s))" + params.append(company_ids) + param_count += 1 + query += """ + GROUP BY + CASE + WHEN pp.default_code IS NOT NULL + THEN CONCAT(pp.default_code, ' - ', pt.name) + ELSE + pt.name + END, + c.complete_name, + company.id, + c.id, + company.name, + pp.id; + """ + self.env.cr.execute(query, params) + result_data = self.env.cr.dictfetchall() + main_header = self.age_breakdown_days + if result_data: + data = { + 'result_data': result_data, + 'main_header': main_header + } + return data + else: + raise ValidationError("No records found for the given criteria!") + + def get_header(self, main_header): + """This function for getting the header in report""" + age_breakdown1 = main_header + age_breakdown2 = main_header * 2 + age_breakdown3 = main_header * 3 + age_breakdown4 = main_header * 4 + return ['1-' + str(age_breakdown1), + str(age_breakdown1 + 1) + '-' + str(age_breakdown2), + str(age_breakdown2 + 1) + '-' + str(age_breakdown3), + str(age_breakdown3 + 1) + '-' + str(age_breakdown4), + 'ABOVE ' + str(age_breakdown4)] + + def action_pdf(self): + """This function is for printing pdf report""" + data = { + 'model_id': self.id, + 'product_ids': self.product_ids.ids, + 'category_ids': self.category_ids.ids, + 'company_ids': self.company_ids.ids, + 'age_breakdown_days': self.age_breakdown_days, + } + return ( + self.env.ref( + 'inventory_advanced_reports.' + 'report_inventory_age_breakdown_action') + .report_action(None, data=data)) + + def action_excel(self): + """This function is for printing excel report""" + data = self.get_report_data() + return { + 'type': 'ir.actions.report', + 'data': {'model': 'inventory.age.breakdown.report', + 'options': json.dumps( + data, default=fields.date_utils.json_default), + 'output_format': 'xlsx', + 'report_name': 'Excel Report', + }, + 'report_type': 'xlsx', + } + + def get_xlsx_report(self, data, response): + """Excel sheet format for printing the data""" + datas = data['result_data'] + main_header = data['main_header'] + output = io.BytesIO() + workbook = xlsxwriter.Workbook(output, {'in_memory': True}) + sheet = workbook.add_worksheet() + sheet.set_margins(0.5, 0.5, 0.5, 0.5) + cell_format = workbook.add_format( + {'font_size': '12px', 'align': 'left'}) + header_style = workbook.add_format( + {'font_name': 'Times', 'bold': True, 'left': 1, 'bottom': 1, + 'right': 1, 'top': 1, 'align': 'center'}) + text_style = workbook.add_format( + {'font_name': 'Times', 'left': 1, 'bottom': 1, 'right': 1, 'top': 1, + 'align': 'left'}) + head = workbook.add_format( + {'align': 'center', 'bold': True, 'font_size': '20px'}) + sheet.merge_range('C2:I3', 'Inventory Age Breakdown Report', head) + + headers = ['Product', 'Category', 'Total Stock', 'Stock Value', 'Stock', + 'Value', 'Stock', 'Value', 'Stock', 'Value', 'Stock', + 'Value', 'Stock', 'Value'] + main_headers = self.get_header(main_header) + for col, header in enumerate(main_headers): + sheet.merge_range(7, col * 2 + 4, 7, col * 2 + 5, header, + header_style) + for col, header in enumerate(headers): + sheet.write(8, col, header, header_style) + sheet.set_column('A:B', 27, cell_format) + sheet.set_column('C:D', 13, cell_format) + row = 9 + number = 1 + for val in datas: + sheet.write(row, 0, val['product_code_and_name'], text_style) + sheet.write(row, 1, val['category_name'], text_style) + sheet.write(row, 2, val['qty_available'], text_style) + sheet.write(row, 3, val['stock_value'], text_style) + sheet.write(row, 4, val['age_breakdown_qty_1'], text_style) + sheet.write(row, 5, val['age_breakdown_value_1'], text_style) + sheet.write(row, 6, val['age_breakdown_qty_2'], text_style) + sheet.write(row, 7, val['age_breakdown_value_2'], text_style) + sheet.write(row, 8, val['age_breakdown_qty_3'], text_style) + sheet.write(row, 9, val['age_breakdown_value_3'], text_style) + sheet.write(row, 10, val['age_breakdown_qty_4'], text_style) + sheet.write(row, 11, val['age_breakdown_value_4'], text_style) + sheet.write(row, 12, val['age_breakdown_qty_5'], text_style) + sheet.write(row, 13, val['age_breakdown_value_5'], text_style) + row += 1 + number += 1 + workbook.close() + output.seek(0) + response.stream.write(output.read()) + output.close() diff --git a/inventory_advanced_reports/wizard/inventory_age_breakdown_report_views.xml b/inventory_advanced_reports/wizard/inventory_age_breakdown_report_views.xml new file mode 100644 index 000000000..742843858 --- /dev/null +++ b/inventory_advanced_reports/wizard/inventory_age_breakdown_report_views.xml @@ -0,0 +1,45 @@ + + + + + inventory.age.breakdown.report.view.form + inventory.age.breakdown.report + +
+ + + + + + + + + + + +
+
+
+
+
+
+ + + Inventory Age Breakdown Report + ir.actions.act_window + inventory.age.breakdown.report + form + + new + + + +
\ No newline at end of file diff --git a/inventory_advanced_reports/wizard/inventory_aging_data_report.py b/inventory_advanced_reports/wizard/inventory_aging_data_report.py new file mode 100644 index 000000000..0559343e0 --- /dev/null +++ b/inventory_advanced_reports/wizard/inventory_aging_data_report.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2024-TODAY Cybrosys Technologies() +# Author: Gayathri V (odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see . +# +############################################################################### +from odoo import fields, models + + +class InventoryAgingDataReport(models.TransientModel): + """This model is for creating a wizard for viewing the report data""" + _name = "inventory.aging.data.report" + _description = "Inventory Aging Data Report" + + product_id = fields.Many2one("product.product", string="Product") + category_id = fields.Many2one("product.category", string="Category") + company_id = fields.Many2one("res.company", string="Company") + qty_available = fields.Float(string="Current Stock") + current_value = fields.Float(string="Current Value") + stock_percentage = fields.Float(string="Stock Qty(%)") + stock_value_percentage = fields.Float(string="Stock Value(%)") + days_since_receipt = fields.Integer(string="Oldest Stock Age") + prev_qty_available = fields.Float(string="Oldest Qty") + prev_value = fields.Float(string="Oldest Stock Value") + data_id = fields.Many2one('inventory.aging.report', string="Aging Data") diff --git a/inventory_advanced_reports/wizard/inventory_aging_data_report_views.xml b/inventory_advanced_reports/wizard/inventory_aging_data_report_views.xml new file mode 100644 index 000000000..aa498b0c2 --- /dev/null +++ b/inventory_advanced_reports/wizard/inventory_aging_data_report_views.xml @@ -0,0 +1,34 @@ + + + + + inventory.aging.data.report.view.graph + inventory.aging.data.report + + + + + + + + + + + inventory.aging.data.report.view.tree + inventory.aging.data.report + + + + + + + + + + + + + + + + diff --git a/inventory_advanced_reports/wizard/inventory_aging_report.py b/inventory_advanced_reports/wizard/inventory_aging_report.py new file mode 100644 index 000000000..1aef33d72 --- /dev/null +++ b/inventory_advanced_reports/wizard/inventory_aging_report.py @@ -0,0 +1,300 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2024-TODAY Cybrosys Technologies() +# Author: Gayathri V (odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see . +# +############################################################################### +import io +import json +from odoo import fields, models, _ +from odoo.exceptions import ValidationError + +try: + from odoo.tools.misc import xlsxwriter +except ImportError: + import xlsxwriter + + +class InventoryAgingReport(models.TransientModel): + """This model is for creating a wizard for inventory aging report""" + _name = "inventory.aging.report" + _description = "Inventory Aging Report" + + product_ids = fields.Many2many( + "product.product", string="Products", + help="Select the products you want to generate the report for") + category_ids = fields.Many2many( + "product.category", string="Product Categories", + help="Select the product categories you want to generate the report for" + ) + company_ids = fields.Many2many( + 'res.company', string="Company", + help="Select the companies you want to generate the report for" + ) + + def get_report_data(self): + """Function for returning datas for printing""" + params = [] + param_count = 0 + query = """ + SELECT + CASE + WHEN pp.default_code IS NOT NULL + THEN CONCAT(pp.default_code, ' - ', pt.name) + ELSE + pt.name + END AS product_code_and_name, + c.complete_name AS category_name, + c.id AS category_id, + pp.id AS product_id, + company.id AS company_id, + company.name AS company_name, + COALESCE(SUM(svl.remaining_qty), 0) AS qty_available, + (SELECT SUM(sm_inner.product_uom_qty) + FROM stock_move sm_inner + INNER JOIN res_company company_inner + ON sm_inner.company_id = company_inner.id + WHERE sm_inner.product_id = pp.id + AND sm_inner.state = 'done' + AND sm_inner.date < ( + SELECT MAX(sm_inner2.date) + FROM stock_move sm_inner2 + WHERE sm_inner2.product_id = pp.id + AND sm_inner2.state = 'done' + AND company_inner.id = sm_inner2.company_id + ) + ) AS prev_qty_available, + ( + SELECT MIN(sm_inner.date) + FROM stock_move sm_inner + WHERE sm_inner.product_id = pp.id + AND sm_inner.state = 'done' + AND (company.id IS NULL OR company.id = sm_inner.company_id) + ) AS receipt_date + FROM product_product pp + INNER JOIN product_template pt ON pp.product_tmpl_id = pt.id + INNER JOIN product_category c ON pt.categ_id = c.id + LEFT JOIN stock_move sm ON sm.product_id = pp.id + LEFT JOIN stock_picking_type spt ON sm.picking_type_id = spt.id + LEFT JOIN res_company company ON sm.company_id = company.id + INNER JOIN stock_valuation_layer svl ON svl.stock_move_id = sm.id + WHERE pt.detailed_type = 'product' + AND sm.state = 'done' + """ + if self.product_ids or self.category_ids: + query += " AND (" + if self.product_ids: + product_ids = [product_id.id for product_id in self.product_ids] + query += "pp.id = ANY(%s)" + params.append(product_ids) + param_count += 1 + if self.product_ids and self.category_ids: + query += " OR " + if self.category_ids: + category_ids = [category.id for category in self.category_ids] + params.append(category_ids) + query += "(pt.categ_id = ANY(%s))" + param_count += 1 + if self.product_ids or self.category_ids: + query += ")" + if self.company_ids: + company_ids = [company.id for company in self.company_ids] + query += " AND (sm.company_id = ANY(%s))" + params.append(company_ids) + param_count += 1 + query += """ + GROUP BY + CASE + WHEN pp.default_code IS NOT NULL + THEN CONCAT(pp.default_code, ' - ', pt.name) + ELSE + pt.name + END, + c.complete_name, + company.id, + c.id, + company.name, + pp.id; + """ + self.env.cr.execute(query, params) + result_data = self.env.cr.dictfetchall() + today = fields.datetime.now().date() + for row in result_data: + receipt_date = row.get('receipt_date') + if receipt_date: + receipt_date = receipt_date.date() # Ensure it's a date object + row['days_since_receipt'] = (today - receipt_date).days + product = self.env['product.product'].browse(row.get('product_id')) + standard_price = product.standard_price + current_stock = row.get('qty_available') + prev_stock = row.get('prev_qty_available') + if prev_stock is None: + prev_stock = current_stock + row['prev_qty_available'] = current_stock + if standard_price and current_stock: + row['current_value'] = current_stock * standard_price + else: + row[ + 'current_value'] = 0 + row[ + 'prev_value'] = prev_stock * standard_price \ + if prev_stock is not None else 0 + total_current_stock = sum( + item.get('qty_available') for item in result_data if + item.get('qty_available') is not None) + if total_current_stock: + stock_percentage = (current_stock / total_current_stock) * 100 + else: + stock_percentage = 0.0 + row['stock_percentage'] = round(stock_percentage, 2) + current_value = row.get('current_value') + total_value = sum( + item.get('current_value', 0) for item in result_data) + if total_value: + stock_value_percentage = (current_value / total_value) * 100 + else: + stock_value_percentage = 0.0 + row['stock_value_percentage'] = round(stock_value_percentage, 2) + if result_data: + data = { + 'result_data': result_data, + } + return data + else: + raise ValidationError("No records found for the given criteria!") + + def action_pdf(self): + """Function for printing the pdf report""" + data = { + 'model_id': self.id, + 'product_ids': self.product_ids.ids, + 'category_ids': self.category_ids.ids, + 'company_ids': self.company_ids.ids, + + } + return ( + self.env.ref( + 'inventory_advanced_reports.report_inventory_aging_action') + .report_action(None, data=data)) + + def action_excel(self): + """This function is for printing excel report""" + data = self.get_report_data() + return { + 'type': 'ir.actions.report', + 'data': {'model': 'inventory.aging.report', + 'options': json.dumps( + data, default=fields.date_utils.json_default), + 'output_format': 'xlsx', + 'report_name': 'Excel Report', + }, + 'report_type': 'xlsx', + } + + def get_xlsx_report(self, data, response): + """Excel sheet format for printing the data""" + datas = data['result_data'] + output = io.BytesIO() + workbook = xlsxwriter.Workbook(output, {'in_memory': True}) + sheet = workbook.add_worksheet() + sheet.set_margins(0.5, 0.5, 0.5, 0.5) + cell_format = workbook.add_format( + {'font_size': '12px', 'align': 'left'}) + header_style = workbook.add_format( + {'font_name': 'Times', 'bold': True, 'left': 1, 'bottom': 1, + 'right': 1, 'top': 1, 'align': 'center'}) + text_style = workbook.add_format( + {'font_name': 'Times', 'left': 1, 'bottom': 1, 'right': 1, 'top': 1, + 'align': 'left'}) + head = workbook.add_format( + {'align': 'center', 'bold': True, 'font_size': '20px'}) + sheet.merge_range('C2:F3', 'Inventory Aging Report', head) + + headers = ['Product', 'Category', 'Current Stock', 'Current Value', + 'Stock Quant(%)', 'Stock Value(%)', 'Oldest Stock Age', + 'Oldest Stock', 'Oldest Stock Value'] + for col, header in enumerate(headers): + sheet.write(8, col, header, header_style) + sheet.set_column('A:B', 27, cell_format) + sheet.set_column('C:D', 13, cell_format) + sheet.set_column('E:F', 13, cell_format) + sheet.set_column('G:H', 13, cell_format) + sheet.set_column('I:J', 15, cell_format) + row = 9 + number = 1 + for val in datas: + sheet.write(row, 0, val['product_code_and_name'], text_style) + sheet.write(row, 1, val['category_name'], text_style) + sheet.write(row, 2, val['qty_available'], text_style) + sheet.write(row, 3, val['current_value'], text_style) + sheet.write(row, 4, val['stock_percentage'], text_style) + sheet.write(row, 5, val['stock_value_percentage'], text_style) + sheet.write(row, 6, val['days_since_receipt'], text_style) + sheet.write(row, 7, val['prev_qty_available'], text_style) + sheet.write(row, 8, val['prev_value'], text_style) + row += 1 + number += 1 + workbook.close() + output.seek(0) + response.stream.write(output.read()) + output.close() + + def display_report_views(self): + """Function for viewing tree and graph view""" + data = self.get_report_data() + for data_values in data.get('result_data'): + data_values['data_id'] = self.id + self.generate_data(data_values) + graph_view_id = self.env.ref( + 'inventory_advanced_reports.' + 'inventory_aging_data_report_view_graph').id + tree_view_id = self.env.ref( + 'inventory_advanced_reports.' + 'inventory_aging_data_report_view_tree').id + graph_report = self.env.context.get("graph_report", False) + report_views = [[tree_view_id, "tree"], + [graph_view_id, 'graph']] + view_mode = "tree,graph" + if graph_report: + report_views = [[graph_view_id, 'graph'], + [tree_view_id, "tree"]] + view_mode = "graph,tree" + return { + 'name': _('Inventory Age Report'), + 'domain': [('data_id', '=', self.id)], + 'res_model': 'inventory.aging.data.report', + 'view_mode': view_mode, + 'type': 'ir.actions.act_window', + 'views': report_views, + } + + def generate_data(self, data_values): + """Function for creating record in inventory aging data report model""" + return self.env['inventory.aging.data.report'].create({ + 'product_id': data_values.get('product_id'), + 'category_id': data_values.get('category_id'), + 'company_id': data_values.get('company_id'), + 'qty_available': data_values.get('qty_available'), + 'current_value': data_values.get('current_value'), + 'stock_percentage': data_values.get('stock_percentage'), + 'stock_value_percentage': data_values.get('stock_value_percentage'), + 'days_since_receipt': data_values.get('days_since_receipt'), + 'prev_qty_available': data_values.get('prev_qty_available'), + 'prev_value': data_values.get('prev_value'), + 'data_id': self.id, + }) diff --git a/inventory_advanced_reports/wizard/inventory_aging_report_views.xml b/inventory_advanced_reports/wizard/inventory_aging_report_views.xml new file mode 100644 index 000000000..1a27fb3ee --- /dev/null +++ b/inventory_advanced_reports/wizard/inventory_aging_report_views.xml @@ -0,0 +1,47 @@ + + + + + inventory.aging.report.view.form + inventory.aging.report + +
+ + + + + + + + + + +
+
+
+
+
+
+ + + Inventory Aging Report + ir.actions.act_window + inventory.aging.report + form + + new + + + +
\ No newline at end of file diff --git a/inventory_advanced_reports/wizard/inventory_fsn_data_report.py b/inventory_advanced_reports/wizard/inventory_fsn_data_report.py new file mode 100644 index 000000000..afdb1140c --- /dev/null +++ b/inventory_advanced_reports/wizard/inventory_fsn_data_report.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2024-TODAY Cybrosys Technologies() +# Author: Gayathri V (odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see . +# +############################################################################### +from odoo import fields, models + + +class InventoryFSNDataReport(models.TransientModel): + """This model is for creating a wizard for viewing the report data""" + _name = "inventory.fsn.data.report" + _description = "Inventory FSN Data Report" + + product_id = fields.Many2one("product.product", string="Product") + category_id = fields.Many2one("product.category", string="Category") + company_id = fields.Many2one("res.company", string="Company") + warehouse_id = fields.Many2one("stock.warehouse", string="Warehouse") + opening_stock = fields.Float(string="Opening Stock") + closing_stock = fields.Float(string="Closing Value") + average_stock = fields.Float(string="Average Stock") + sales = fields.Float(string="Sales") + turnover_ratio = fields.Float(string="Turnover Ratio") + fsn_classification = fields.Char(string="FSN Classification") + data_id = fields.Many2one('inventory.fsn.report', string="FSN Data") diff --git a/inventory_advanced_reports/wizard/inventory_fsn_data_report_views.xml b/inventory_advanced_reports/wizard/inventory_fsn_data_report_views.xml new file mode 100644 index 000000000..aa1b35134 --- /dev/null +++ b/inventory_advanced_reports/wizard/inventory_fsn_data_report_views.xml @@ -0,0 +1,33 @@ + + + + + inventory.fsn.data.report.view.graph + inventory.fsn.data.report + + + + + + + + + + inventory.fsn.data.report.view.tree + inventory.fsn.data.report + + + + + + + + + + + + + + + + diff --git a/inventory_advanced_reports/wizard/inventory_fsn_report.py b/inventory_advanced_reports/wizard/inventory_fsn_report.py new file mode 100644 index 000000000..5c747a76f --- /dev/null +++ b/inventory_advanced_reports/wizard/inventory_fsn_report.py @@ -0,0 +1,362 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2024-TODAY Cybrosys Technologies() +# Author: Gayathri V (odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see . +# +############################################################################### +import io +import json +from odoo import fields, models, _ +from odoo.exceptions import ValidationError + +try: + from odoo.tools.misc import xlsxwriter +except ImportError: + import xlsxwriter + + +class InventoryFsnReport(models.TransientModel): + """This model is for creating a wizard for inventory turnover report.""" + _name = 'inventory.fsn.report' + _description = 'Inventory FSN Report' + + start_date = fields.Date('Start Date', + help="Start date to analyse the report", + required=True) + end_date = fields.Date('End Date', help="End date to analyse the report", + required=True) + warehouse_ids = fields.Many2many( + "stock.warehouse", string="Warehouses", + help="Select the warehouses to generate the report") + product_ids = fields.Many2many( + "product.product", string="Products", + help="Select the products you want to generate the report for") + category_ids = fields.Many2many( + "product.category", string="Product categories", + help="Select the product categories you want to generate the report for" + ) + company_ids = fields.Many2many( + "res.company", string="Company", default=lambda self: self.env.company, + help="Select the companies you want to generate the report for") + fsn = fields.Selection([ + ('fast_moving', 'Fast Moving'), + ('slow_moving', 'Slow Moving'), + ('non_moving', 'Non Moving'), + ('all', 'All') + ], string='FSN Category', default="all", required=True) + + def get_report_data(self): + """Function for returning data for printing""" + fsn = dict(self._fields['fsn'].selection).get(self.fsn) + if self.start_date > self.end_date: + raise ValidationError( + "Start date cant be greater than end date") + start_date = self.start_date + end_date = self.end_date + filtered_product_stock = [] + query = """ + SELECT + product_id, + product_code_and_name, + category_id, + category_name, + company_id, + warehouse_id, + opening_stock, + closing_stock, + sales, + average_stock, + CASE + WHEN sales > 0 + THEN ROUND((sales / NULLIF(average_stock, 0)), 2) + ELSE 0 + END AS turnover_ratio, + CASE + WHEN + CASE + WHEN sales > 0 + THEN ROUND((sales / NULLIF(average_stock, 0)), + 2) + ELSE 0 + END > 3 THEN 'Fast Moving' + WHEN + CASE + WHEN sales > 0 + THEN ROUND((sales / NULLIF(average_stock, 0)), + 2) + ELSE 0 + END >= 1 AND + CASE + WHEN sales > 0 + THEN ROUND((sales / NULLIF(average_stock, 0)), + 2) + ELSE 0 + END <= 3 THEN 'Slow Moving' + ELSE 'Non Moving' + END AS fsn_classification + FROM + (SELECT + pp.id AS product_id, + pt.categ_id AS category_id, + CASE + WHEN pp.default_code IS NOT NULL + THEN CONCAT(pp.default_code, ' - ', + pt.name) + ELSE + pt.name + END AS product_code_and_name, + pc.complete_name AS category_name, + company.id AS company_id, + sw.id AS warehouse_id, + (SUM(CASE WHEN sm.date <= %s + AND sld_dest.usage = 'internal' + THEN sm.product_uom_qty ELSE 0 END) - + SUM(CASE WHEN sm.date <= %s + AND sld_src.usage = 'internal' THEN sm.product_uom_qty + ELSE 0 END)) AS opening_stock, + (SUM(CASE WHEN sm.date <= %s + AND sld_dest.usage = 'internal' THEN sm.product_uom_qty + ELSE 0 END) - + SUM(CASE WHEN sm.date <= %s + AND sld_src.usage = 'internal' THEN sm.product_uom_qty + ELSE 0 END)) AS closing_stock, + SUM(CASE WHEN sm.date BETWEEN %s + AND %s AND sld_dest.usage = 'customer' + THEN sm.product_uom_qty ELSE 0 END) AS sales, + ((SUM(CASE WHEN sm.date <= %s + AND sld_dest.usage = 'internal' THEN sm.product_uom_qty + ELSE 0 END) - + SUM(CASE WHEN sm.date <= %s + AND sld_src.usage = 'internal' THEN sm.product_uom_qty + ELSE 0 END))+ + (SUM(CASE WHEN sm.date <= %s + AND sld_dest.usage = 'internal' THEN sm.product_uom_qty + ELSE 0 END) - + SUM(CASE WHEN sm.date <= %s + AND sld_src.usage = 'internal' THEN sm.product_uom_qty + ELSE 0 END)))/2 AS average_stock + FROM + stock_move sm + JOIN + product_product pp ON sm.product_id = pp.id + JOIN + product_template pt ON pp.product_tmpl_id = pt.id + JOIN + product_category pc ON pt.categ_id = pc.id + JOIN + res_company company ON company.id = sm.company_id + JOIN + stock_warehouse sw ON sw.company_id = company.id + LEFT JOIN + stock_location sld_dest + ON sm.location_dest_id = sld_dest.id + LEFT JOIN + stock_location sld_src ON sm.location_id = sld_src.id + WHERE + sm.state = 'done' + """ + params = [ + start_date, start_date, end_date, end_date, start_date, end_date, + start_date, start_date, end_date, end_date + ] + sub_queries = [] + sub_params = [] + param_count = 0 + if self.product_ids or self.category_ids: + query += " AND (" + if self.product_ids: + product_ids = [product_id.id for product_id in self.product_ids] + query += "pp.id = ANY(%s)" + params.append(product_ids) + param_count += 1 + if self.product_ids and self.category_ids: + query += " OR " + if self.category_ids: + category_ids = [category_id.id for category_id in self.category_ids] + query += "pt.categ_id IN %s" + params.append(tuple(category_ids)) + param_count += 1 + if self.product_ids or self.category_ids: + query += ")" + if self.company_ids: + query += f" AND company.id IN %s" + sub_params.append(tuple(self.company_ids.ids)) + param_count += 1 + if self.warehouse_ids: + query += f" AND sw.id IN %s" + sub_params.append(tuple(self.warehouse_ids.ids)) + param_count += 1 + if sub_queries: + query += " AND " + " AND ".join(sub_queries) + query += """ + GROUP BY pp.id, pt.categ_id,CASE + WHEN pp.default_code IS NOT NULL + THEN CONCAT(pp.default_code, ' - ', pt.name) + ELSE + pt.name + END, pc.complete_name, company.id, sw.id + ) AS subquery + """ + self.env.cr.execute(query, tuple(params + sub_params)) + result_data = self.env.cr.dictfetchall() + for fsn_data in result_data: + if fsn_data.get('fsn_classification') == str(fsn): + filtered_product_stock.append(fsn_data) + if fsn == 'All' and not result_data: + raise ValidationError("No corresponding data to print") + elif fsn != 'All' and filtered_product_stock == []: + raise ValidationError("No corresponding data to print") + data = { + 'data': result_data if fsn == 'All' else filtered_product_stock, + 'start_date': start_date, + 'end_date': end_date + } + return data + + def action_pdf(self): + """Function for printing the pdf""" + data = { + 'model_id': self.id, + 'product_ids': self.product_ids.ids, + 'category_ids': self.category_ids.ids, + 'company_ids': self.company_ids.ids, + 'warehouse_ids': self.warehouse_ids.ids, + 'start_date': self.start_date, + "end_date": self.end_date, + "fsn": dict(self._fields['fsn'].selection).get(self.fsn) + } + return ( + self.env.ref( + 'inventory_advanced_reports.report_inventory_fsn_action') + .report_action(None, data=data)) + + def action_excel(self): + """This function is for printing excel report""" + data = self.get_report_data() + return { + 'type': 'ir.actions.report', + 'data': {'model': 'inventory.fsn.report', + 'options': json.dumps( + data, default=fields.date_utils.json_default), + 'output_format': 'xlsx', + 'report_name': 'Excel Report', + }, + 'report_type': 'xlsx', + } + + def get_xlsx_report(self, data, response): + """Excel sheet format for printing the data""" + datas = data['data'] + start_date = data['start_date'] + end_date = data['end_date'] + output = io.BytesIO() + workbook = xlsxwriter.Workbook(output, {'in_memory': True}) + sheet = workbook.add_worksheet() + sheet.set_margins(0.5, 0.5, 0.5, 0.5) + cell_format = workbook.add_format( + {'font_size': '12px', 'align': 'left'}) + header_style = workbook.add_format( + {'font_name': 'Times', 'bold': True, 'left': 1, 'bottom': 1, + 'right': 1, 'top': 1, 'align': 'center'}) + text_style = workbook.add_format( + {'font_name': 'Times', 'left': 1, 'bottom': 1, 'right': 1, 'top': 1, + 'align': 'left'}) + head = workbook.add_format( + {'align': 'center', 'bold': True, 'font_size': '20px'}) + sheet.merge_range('B2:F3', 'Inventory FSN Report', head) + bold_format = workbook.add_format( + {'bold': True, 'font_size': '10px', 'align': 'left'}) + txt = workbook.add_format({'font_size': '10px', 'align': 'left'}) + if start_date and end_date: + sheet.write('A5', 'Start Date: ', bold_format) + sheet.write('B5', start_date, txt) + sheet.write('A6', 'End Date: ', bold_format) + sheet.write('B6', end_date, txt) + headers = ['Product', 'Category', 'Opening Stock', 'Closing Value', + 'Average Stock', 'Sales', 'Turnover Ratio', + 'FSN Classification'] + for col, header in enumerate(headers): + sheet.write(8, col, header, header_style) + sheet.set_column('A:A', 27, cell_format) + sheet.set_column('B:B', 24, cell_format) + sheet.set_column('C:D', 13, cell_format) + sheet.set_column('E:F', 13, cell_format) + sheet.set_column('G:H', 13, cell_format) + row = 9 + number = 1 + for val in datas: + sheet.write(row, 0, val['product_code_and_name'], text_style) + sheet.write(row, 1, val['category_name'], text_style) + sheet.write(row, 2, val['opening_stock'], text_style) + sheet.write(row, 3, val['closing_stock'], text_style) + sheet.write(row, 4, val['average_stock'], text_style) + sheet.write(row, 5, val['sales'], text_style) + sheet.write(row, 6, val['turnover_ratio'], text_style) + sheet.write(row, 7, val['fsn_classification'], text_style) + row += 1 + number += 1 + workbook.close() + output.seek(0) + response.stream.write(output.read()) + output.close() + + def display_report_views(self): + """Function for displaying graph and tree view of data""" + data = self.get_report_data() + for data_values in data.get('data'): + data_values['data_id'] = self.id + self.generate_data(data_values) + graph_view_id = self.env.ref( + 'inventory_advanced_reports.' + 'inventory_fsn_data_report_view_graph').id + tree_view_id = self.env.ref( + 'inventory_advanced_reports.' + 'inventory_fsn_data_report_view_tree').id + graph_report = self.env.context.get("graph_report", False) + report_views = [(tree_view_id, 'tree'), + (graph_view_id, 'graph')] + view_mode = "tree,graph" + if graph_report: + report_views = [(graph_view_id, 'graph'), + (tree_view_id, 'tree')] + view_mode = "graph,tree" + return { + 'name': _('Inventory FSN Report'), + 'domain': [('data_id', '=', self.id)], + 'res_model': 'inventory.fsn.data.report', + 'view_mode': view_mode, + 'type': 'ir.actions.act_window', + 'views': report_views + } + + def generate_data(self, data_values): + """Function for creating data in model inventory fsn data report + model""" + return self.env['inventory.fsn.data.report'].create({ + 'product_id': data_values.get('product_id'), + 'category_id': data_values.get('category_id'), + 'company_id': data_values.get('company_id'), + 'warehouse_id': data_values.get('warehouse_id'), + 'opening_stock': data_values.get('opening_stock'), + 'closing_stock': data_values.get('closing_stock'), + 'average_stock': data_values.get('average_stock'), + 'sales': data_values.get('sales'), + 'turnover_ratio': data_values.get('turnover_ratio'), + 'fsn_classification': data_values.get('fsn_classification'), + 'data_id': self.id, + }) diff --git a/inventory_advanced_reports/wizard/inventory_fsn_report_views.xml b/inventory_advanced_reports/wizard/inventory_fsn_report_views.xml new file mode 100644 index 000000000..134fe38bd --- /dev/null +++ b/inventory_advanced_reports/wizard/inventory_fsn_report_views.xml @@ -0,0 +1,71 @@ + + + + + inventory.fsn.report.view.form + inventory.fsn.report + +
+ + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+ + + Inventory FSN Report + ir.actions.act_window + inventory.fsn.report + form + + new + + + +
diff --git a/inventory_advanced_reports/wizard/inventory_fsn_xyz_data_report.py b/inventory_advanced_reports/wizard/inventory_fsn_xyz_data_report.py new file mode 100644 index 000000000..286b92be0 --- /dev/null +++ b/inventory_advanced_reports/wizard/inventory_fsn_xyz_data_report.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2024-TODAY Cybrosys Technologies() +# Author: Gayathri V (odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see . +# +############################################################################### +from odoo import fields, models + + +class InventoryFsnXyzDataReport(models.TransientModel): + """This model is for creating a wizard for viewing the report data""" + _name = "inventory.fsn.xyz.data.report" + _description = "Inventory FSN-XYZ Data Report" + + product_id = fields.Many2one("product.product", string="Product") + category_id = fields.Many2one("product.category", string="Category") + company_id = fields.Many2one("res.company", string="Company") + average_stock = fields.Float(string="Average Stock") + sales = fields.Float(string="Sales") + turnover_ratio = fields.Float(string="Turnover Ratio") + current_stock = fields.Float(string="Current Stock") + stock_value = fields.Float(string="Stock Value") + fsn_classification = fields.Char(string="FSN Classification") + xyz_classification = fields.Char(string="XYZ Classification") + combined_classification = fields.Char(string="FSN-XYZ Classification") + data_id = fields.Many2one('inventory.fsn.xyz.report', string="FSN-XYZ Data") diff --git a/inventory_advanced_reports/wizard/inventory_fsn_xyz_data_report_views.xml b/inventory_advanced_reports/wizard/inventory_fsn_xyz_data_report_views.xml new file mode 100644 index 000000000..5a220d8cf --- /dev/null +++ b/inventory_advanced_reports/wizard/inventory_fsn_xyz_data_report_views.xml @@ -0,0 +1,34 @@ + + + + + inventory.fsn.xyz.data.report.view.graph + inventory.fsn.xyz.data.report + + + + + + + + + + inventory.fsn.xyz.data.report.view.tree + inventory.fsn.xyz.data.report + + + + + + + + + + + + + + + + + diff --git a/inventory_advanced_reports/wizard/inventory_fsn_xyz_report.py b/inventory_advanced_reports/wizard/inventory_fsn_xyz_report.py new file mode 100644 index 000000000..97b658538 --- /dev/null +++ b/inventory_advanced_reports/wizard/inventory_fsn_xyz_report.py @@ -0,0 +1,428 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2024-TODAY Cybrosys Technologies() +# Author: Gayathri V (odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see . +# +############################################################################### +import io +import json +from odoo import fields, models, _ +from odoo.exceptions import ValidationError + +try: + from odoo.tools.misc import xlsxwriter +except ImportError: + import xlsxwriter + + +class InventoryFsnXyzReport(models.TransientModel): + """This model is for creating a wizard for inventory turnover report.""" + _name = 'inventory.fsn.xyz.report' + _description = 'Inventory FSN-XYZ Report' + + start_date = fields.Date('Start Date', + help="Start date to analyse the report", + required=True) + end_date = fields.Date('End Date', help="End date to analyse the report", + required=True) + warehouse_ids = fields.Many2many( + "stock.warehouse", string="Warehouses", + help="Select the warehouses to generate the report") + product_ids = fields.Many2many( + "product.product", string="Products", + help="Select the products you want to generate the report for") + category_ids = fields.Many2many( + "product.category", string="Product categories", + help="Select the product categories you want to generate the report for" + ) + company_ids = fields.Many2many( + "res.company", string="Company", default=lambda self: self.env.company, + help="Select the companies you want to generate the report for") + fsn = fields.Selection([ + ('fast_moving', 'Fast Moving'), + ('slow_moving', 'Slow Moving'), + ('non_moving', 'Non Moving'), + ('all', 'All') + ], string='FSN Category', default="all", required=True) + xyz = fields.Selection([('x', 'X'), ('y', 'Y'), ('z', 'Z'), ('all', 'All')], + string="XYZ Classification", default='all', + required=True) + + def get_report_data(self): + """Function for returning datas for printing""" + fsn = dict(self._fields['fsn'].selection).get(self.fsn) + xyz = dict(self._fields['xyz'].selection).get(self.xyz) + start_date = self.start_date + end_date = self.end_date + filtered_product_stock = [] + query = """ + SELECT + product_id, + product_code_and_name, + category_id, + category_name, + company_id, + warehouse_id, + opening_stock, + closing_stock, + sales, + average_stock, + current_stock, + stock_value, + CASE + WHEN sales > 0 THEN + ROUND((sales / NULLIF(average_stock, 0)), 2) + ELSE 0 + END AS turnover_ratio, + CASE + WHEN + CASE + WHEN sales > 0 THEN + ROUND((sales / NULLIF(average_stock, 0)), 2) + ELSE 0 + END > 3 THEN 'Fast Moving' + WHEN + CASE + WHEN sales > 0 THEN + ROUND((sales / NULLIF(average_stock, 0)), 2) + ELSE 0 + END >= 1 AND + CASE + WHEN sales > 0 THEN + ROUND((sales / NULLIF(average_stock, 0)), 2) + ELSE 0 + END <= 3 THEN 'Slow Moving' + ELSE 'Non Moving' + END AS fsn_classification, + stock_percentage, + SUM(stock_percentage) + OVER (ORDER BY stock_value DESC) + AS cumulative_stock_percentage, + CASE + WHEN SUM(stock_percentage) OVER (ORDER BY stock_value DESC) < 70 + THEN 'X' + WHEN SUM(stock_percentage) + OVER (ORDER BY stock_value DESC) >= 70 AND + SUM(stock_percentage) OVER (ORDER BY stock_value DESC) <= 90 + THEN 'Y' + ELSE 'Z' + END AS xyz_classification, + CONCAT( + CASE + WHEN + CASE + WHEN sales > 0 THEN + ROUND((sales / NULLIF(average_stock, 0)), 2) + ELSE 0 + END > 3 THEN 'F' + WHEN + CASE + WHEN sales > 0 THEN + ROUND((sales / NULLIF(average_stock, 0)), 2) + ELSE 0 + END >= 1 AND + CASE + WHEN sales > 0 THEN + ROUND((sales / NULLIF(average_stock, 0)), 2) + ELSE 0 + END <= 3 THEN 'S' + ELSE 'N' + END, + CASE + WHEN SUM(stock_percentage) + OVER (ORDER BY stock_value DESC) < 70 THEN 'X' + WHEN SUM(stock_percentage) + OVER (ORDER BY stock_value DESC) >= 70 + AND SUM(stock_percentage) + OVER (ORDER BY stock_value DESC) <= 90 THEN 'Y' + ELSE 'Z' + END + ) AS combined_classification + FROM + (SELECT + pp.id AS product_id, + pt.categ_id AS category_id, + CASE + WHEN pp.default_code IS NOT NULL + THEN CONCAT(pp.default_code, ' - ', + pt.name) + ELSE + pt.name + END AS product_code_and_name, + pc.complete_name AS category_name, + company.id AS company_id, + sw.id AS warehouse_id, + SUM(svl.remaining_qty) AS current_stock, + SUM(svl.remaining_value) AS stock_value, + COALESCE(ROUND((SUM(svl.remaining_value) / + NULLIF(SUM(SUM(svl.remaining_value)) + OVER (), 0)) * 100, 2),0) AS stock_percentage, + (SUM(CASE WHEN sm.date <= %s AND sld_dest.usage = 'internal' + THEN sm.product_uom_qty ELSE 0 END) - + SUM(CASE WHEN sm.date <= %s AND sld_src.usage = 'internal' + THEN sm.product_uom_qty ELSE 0 END)) AS opening_stock, + (SUM(CASE WHEN sm.date <= %s AND sld_dest.usage = 'internal' + THEN sm.product_uom_qty ELSE 0 END) - + SUM(CASE WHEN sm.date <= %s AND sld_src.usage = 'internal' + THEN sm.product_uom_qty ELSE 0 END)) AS closing_stock, + SUM(CASE WHEN sm.date BETWEEN %s AND %s + AND sld_dest.usage = 'customer' + THEN sm.product_uom_qty ELSE 0 END) AS sales, + ((SUM(CASE WHEN sm.date <= %s + AND sld_dest.usage = 'internal' THEN sm.product_uom_qty + ELSE 0 END) - + SUM(CASE WHEN sm.date <= %s AND sld_src.usage = 'internal' + THEN sm.product_uom_qty ELSE 0 END))+ + (SUM(CASE WHEN sm.date <= %s AND sld_dest.usage = 'internal' + THEN sm.product_uom_qty ELSE 0 END) - + SUM(CASE WHEN sm.date <= %s AND sld_src.usage = 'internal' + THEN sm.product_uom_qty ELSE 0 END)))/2 AS average_stock + FROM + stock_move sm + JOIN + product_product pp ON sm.product_id = pp.id + JOIN + product_template pt ON pp.product_tmpl_id = pt.id + JOIN + product_category pc ON pt.categ_id = pc.id + JOIN + res_company company ON company.id = sm.company_id + JOIN + stock_warehouse sw ON sw.company_id = company.id + JOIN + stock_valuation_layer svl ON svl.stock_move_id = sm.id + LEFT JOIN + stock_location sld_dest ON sm.location_dest_id = sld_dest.id + LEFT JOIN + stock_location sld_src ON sm.location_id = sld_src.id + WHERE + sm.state = 'done' + AND pp.active = TRUE + AND pt.active = TRUE + AND pt.type = 'product' + AND svl.remaining_value IS NOT NULL + """ + params = [ + start_date, start_date, end_date, end_date, start_date, end_date, + start_date, start_date, end_date, end_date + ] + sub_queries = [] + sub_params = [] + param_count = 0 + if self.product_ids or self.category_ids: + query += " AND (" + if self.product_ids: + product_ids = [product_id.id for product_id in self.product_ids] + query += "pp.id = ANY(%s)" + params.append(product_ids) + param_count += 1 + if self.product_ids and self.category_ids: + query += " OR " + if self.category_ids: + category_ids = [category_id.id for category_id in self.category_ids] + query += "pt.categ_id IN %s" + params.append(tuple(category_ids)) + param_count += 1 + if self.product_ids or self.category_ids: + query += ")" + if self.company_ids: + query += f" AND sm.company_id IN %s" # Specify the table alias + sub_params.append(tuple(self.company_ids.ids)) + param_count += 1 + if self.warehouse_ids: + query += f" AND sw.id IN %s" # Specify the table alias + sub_params.append(tuple(self.warehouse_ids.ids)) + param_count += 1 + if sub_queries: + query += " AND " + " AND ".join(sub_queries) + query += """ + GROUP BY + pp.id,pt.name, pt.categ_id,pc.complete_name, company.id, sw.id + ) AS subquery + ORDER BY stock_value DESC + """ + self.env.cr.execute(query, tuple(params + sub_params)) + result_data = self.env.cr.dictfetchall() + for fsn_data in result_data: + if ( + (fsn == 'All' and xyz == 'All') or + (fsn == 'All' and fsn_data.get('xyz_classification') == str( + xyz)) or + (xyz == 'All' and fsn_data.get('fsn_classification') == str( + fsn)) or + (fsn_data.get('fsn_classification') == str( + fsn) and fsn_data.get('xyz_classification') == str(xyz)) + ): + filtered_product_stock.append(fsn_data) + if (fsn == 'All' or xyz == 'All') and not result_data: + raise ValidationError("No corresponding data to print") + elif not filtered_product_stock: + raise ValidationError("No corresponding data to print") + data = { + 'data': filtered_product_stock, + 'start_date': start_date, + 'end_date': end_date + } + return data + + def action_pdf(self): + """Function for printing pdf report""" + data = { + 'model_id': self.id, + 'product_ids': self.product_ids.ids, + 'category_ids': self.category_ids.ids, + 'company_ids': self.company_ids.ids, + 'warehouse_ids': self.warehouse_ids.ids, + 'start_date': self.start_date, + "end_date": self.end_date, + "fsn": dict(self._fields['fsn'].selection).get(self.fsn), + "xyz": dict(self._fields['xyz'].selection).get(self.xyz) + } + return ( + self.env.ref( + 'inventory_advanced_reports.report_inventory_fsn_xyz_action') + .report_action(None, data=data)) + + def action_excel(self): + """This function is for printing excel report""" + data = self.get_report_data() + return { + 'type': 'ir.actions.report', + 'data': {'model': 'inventory.fsn.xyz.report', + 'options': json.dumps( + data, default=fields.date_utils.json_default), + 'output_format': 'xlsx', + 'report_name': 'Excel Report', + }, + 'report_type': 'xlsx', + } + + def get_xlsx_report(self, data, response): + """Excel sheet format for printing the data""" + datas = data['data'] + start_date = data['start_date'] + end_date = data['end_date'] + output = io.BytesIO() + workbook = xlsxwriter.Workbook(output, {'in_memory': True}) + sheet = workbook.add_worksheet() + sheet.set_margins(0.5, 0.5, 0.5, 0.5) + cell_format = workbook.add_format( + {'font_size': '12px', 'align': 'left'}) + header_style = workbook.add_format( + {'font_name': 'Times', 'bold': True, 'left': 1, 'bottom': 1, + 'right': 1, 'top': 1, 'align': 'center'}) + text_style = workbook.add_format( + {'font_name': 'Times', 'left': 1, 'bottom': 1, 'right': 1, 'top': 1, + 'align': 'left'}) + head = workbook.add_format( + {'align': 'center', 'bold': True, 'font_size': '20px'}) + sheet.merge_range('E2:I3', 'Inventory FSN-XYZ Report', head) + bold_format = workbook.add_format( + {'bold': True, 'font_size': '10px', 'align': 'left'}) + txt = workbook.add_format({'font_size': '10px', 'align': 'left'}) + if start_date and end_date: + sheet.write('A5', 'Start Date: ', bold_format) + sheet.write('B5', start_date, txt) + sheet.write('A6', 'End Date: ', bold_format) + sheet.write('B6', end_date, txt) + headers = ['Product', 'Category', 'Opening Stock', 'Closing Stock', + 'Average Stock', 'Sales', 'Turnover Ratio', 'Current Stock', + 'Stock Value', 'Stock Value(%)', 'Cumulative Value(%)', + 'FSN Classification', 'XYZ Classification', + 'FSN-XYZ Classification'] + for col, header in enumerate(headers): + sheet.write(8, col, header, header_style) + sheet.set_column('A:A', 27, cell_format) + sheet.set_column('B:B', 24, cell_format) + sheet.set_column('C:D', 13, cell_format) + sheet.set_column('E:F', 13, cell_format) + sheet.set_column('G:H', 15, cell_format) + sheet.set_column('I:J', 15, cell_format) + sheet.set_column('K:L', 17, cell_format) + sheet.set_column('M:N', 17, cell_format) + row = 9 + number = 1 + for val in datas: + sheet.write(row, 0, val['product_code_and_name'], text_style) + sheet.write(row, 1, val['category_name'], text_style) + sheet.write(row, 2, val['opening_stock'], text_style) + sheet.write(row, 3, val['closing_stock'], text_style) + sheet.write(row, 4, val['average_stock'], text_style) + sheet.write(row, 5, val['sales'], text_style) + sheet.write(row, 6, val['turnover_ratio'], text_style) + sheet.write(row, 7, val['current_stock'], text_style) + sheet.write(row, 8, val['stock_value'], text_style) + sheet.write(row, 9, val['stock_percentage'], text_style) + sheet.write(row, 10, val['cumulative_stock_percentage'], text_style) + sheet.write(row, 11, val['fsn_classification'], text_style) + sheet.write(row, 12, val['xyz_classification'], text_style) + sheet.write(row, 13, val['combined_classification'], text_style) + row += 1 + number += 1 + workbook.close() + output.seek(0) + response.stream.write(output.read()) + output.close() + + def display_report_views(self): + """Function for displaying graph and tree view of the data""" + data = self.get_report_data() + for data_values in data.get('data'): + data_values['data_id'] = self.id + self.generate_data(data_values) + graph_view_id = self.env.ref( + 'inventory_advanced_reports.' + 'inventory_fsn_xyz_data_report_view_graph').id + tree_view_id = self.env.ref( + 'inventory_advanced_reports.' + 'inventory_fsn_xyz_data_report_view_tree').id + graph_report = self.env.context.get("graph_report", False) + report_views = [(tree_view_id, 'tree'), + (graph_view_id, 'graph')] + view_mode = "tree,graph" + if graph_report: + report_views = [(graph_view_id, 'graph'), + (tree_view_id, 'tree')] + view_mode = "graph,tree" + return { + 'name': _('Inventory FSN-XYZ Report'), + 'domain': [('data_id', '=', self.id)], + 'res_model': 'inventory.fsn.xyz.data.report', + 'view_mode': view_mode, + 'type': 'ir.actions.act_window', + 'views': report_views + } + + def generate_data(self, data_values): + """Function for creating a record in model inventory fsn xyz data + 'report""" + return self.env['inventory.fsn.xyz.data.report'].create({ + 'product_id': data_values.get('product_id'), + 'category_id': data_values.get('category_id'), + 'company_id': data_values.get('company_id'), + 'average_stock': data_values.get('average_stock'), + 'sales': data_values.get('sales'), + 'turnover_ratio': data_values.get('turnover_ratio'), + 'current_stock': data_values.get('current_stock'), + 'stock_value': data_values.get('stock_value'), + 'fsn_classification': data_values.get('fsn_classification'), + 'xyz_classification': data_values.get('xyz_classification'), + 'combined_classification': data_values.get( + 'combined_classification'), + 'data_id': self.id, + }) diff --git a/inventory_advanced_reports/wizard/inventory_fsn_xyz_report_views.xml b/inventory_advanced_reports/wizard/inventory_fsn_xyz_report_views.xml new file mode 100644 index 000000000..9fa1011a6 --- /dev/null +++ b/inventory_advanced_reports/wizard/inventory_fsn_xyz_report_views.xml @@ -0,0 +1,72 @@ + + + + + inventory.fsn.xyz.report.view.form + inventory.fsn.xyz.report + +
+ + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+ + + Inventory FSN-XYZ Report + ir.actions.act_window + inventory.fsn.xyz.report + form + + new + + + +
diff --git a/inventory_advanced_reports/wizard/inventory_out_of_stock_data_report.py b/inventory_advanced_reports/wizard/inventory_out_of_stock_data_report.py new file mode 100644 index 000000000..837118681 --- /dev/null +++ b/inventory_advanced_reports/wizard/inventory_out_of_stock_data_report.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2024-TODAY Cybrosys Technologies() +# Author: Gayathri V (odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see . +# +############################################################################### +from odoo import fields, models + + +class InventoryOutOfStockDataReport(models.TransientModel): + """This model is for creating a wizard for viewing the report data""" + _name = "inventory.out.of.stock.data.report" + _description = "Inventory Out Of Stock Data Report" + + product_id = fields.Many2one("product.product", string="Product") + category_id = fields.Many2one("product.category", string="Category") + company_id = fields.Many2one("res.company", string="Company") + warehouse_id = fields.Many2one("stock.warehouse", string="Warehouse") + virtual_stock = fields.Float(string="Forecasted QTY") + sales = fields.Float(string="Sales") + ads = fields.Float(string="ADS") + demanded_quantity = fields.Float(string="Demanded QTY") + in_stock_days = fields.Float(string="In Stock Days") + out_of_stock_days = fields.Float(string="Out Of Stock Days") + out_of_stock_ratio = fields.Float(string="Out Of Stock Ratio") + cost = fields.Float(string="Cost Price") + out_of_stock_qty = fields.Float(string="Out Of Stock QTY") + out_of_stock_qty_percentage = fields.Float(string="Out Of Stock QTY(%)") + out_of_stock_value = fields.Float(string="Out Of Stock Value(%)") + turnover_ratio = fields.Float(string="Turnover Ratio") + fsn_classification = fields.Char(string="FSN Classification") + data_id = fields.Many2one('inventory.out.of.stock.report', + string="Out Of Stock Data") diff --git a/inventory_advanced_reports/wizard/inventory_out_of_stock_data_report_views.xml b/inventory_advanced_reports/wizard/inventory_out_of_stock_data_report_views.xml new file mode 100644 index 000000000..96caf9a43 --- /dev/null +++ b/inventory_advanced_reports/wizard/inventory_out_of_stock_data_report_views.xml @@ -0,0 +1,40 @@ + + + + + inventory.out.of.stock.data.report.view.graph + inventory.out.of.stock.data.report + + + + + + + + + + inventory.out.of.stock.data.report.view.tree + inventory.out.of.stock.data.report + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inventory_advanced_reports/wizard/inventory_out_of_stock_report.py b/inventory_advanced_reports/wizard/inventory_out_of_stock_report.py new file mode 100644 index 000000000..222b9204c --- /dev/null +++ b/inventory_advanced_reports/wizard/inventory_out_of_stock_report.py @@ -0,0 +1,453 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2024-TODAY Cybrosys Technologies() +# Author: Gayathri V (odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see . +# +############################################################################### +import io +import json +from odoo import fields, models, _ +from odoo.exceptions import ValidationError + +try: + from odoo.tools.misc import xlsxwriter +except ImportError: + import xlsxwriter + + +class InventoryOutOfStockReport(models.TransientModel): + """This model is for creating a wizard for inventory turnover report.""" + _name = 'inventory.out.of.stock.report' + _description = 'Inventory Out of Stock Report' + + start_date = fields.Date('Start Date', + help="Start date to analyse the report", + required=True) + end_date = fields.Date('End Date', help="End date to analyse the report", + required=True) + warehouse_ids = fields.Many2many( + "stock.warehouse", string="Warehouses", + help="Select the warehouses to generate the report") + product_ids = fields.Many2many( + "product.product", string="Products", + help="Select the products you want to generate the report for") + category_ids = fields.Many2many( + "product.category", string="Product categories", + help="Select the product categories you want to generate the report for" + ) + company_ids = fields.Many2many( + "res.company", string="Company", default=lambda self: self.env.company, + help="Select the companies you want to generate the report for") + inventory_for_next_x_days = fields.Integer( + string="Inventory For Next X Days", + help="Select next number of days for the inventory") + + def get_report_data(self): + """Function for returning data to print""" + query = """ + SELECT + product_id, + product_code_and_name, + category_id, + category_name, + company_id, + current_stock, + warehouse_id, + incoming_quantity, + outgoing_quantity, + virtual_stock, + sales, + ads, + advance_stock_days, + ROUND(advance_stock_days * ads, 0) AS demanded_quantity, + ROUND(CASE + WHEN ads = 0 THEN virtual_stock / 0.001 + ELSE virtual_stock / ads + END,0) AS in_stock_days, + ROUND(CASE + WHEN ads = 0 THEN GREATEST(advance_stock_days - + ROUND(virtual_stock / 0.001, 2), 0) + ELSE GREATEST(advance_stock_days - + ROUND(virtual_stock / ads, 2), 0) + END ,0) AS out_of_stock_days, + ROUND( + CASE + WHEN advance_stock_days = 0 THEN 0 + ELSE + CASE + WHEN ads = 0 THEN GREATEST(advance_stock_days - + ROUND(virtual_stock / 0.001, 2), 0) + ELSE GREATEST(advance_stock_days - + ROUND(virtual_stock / ads, 2), 0) + END + END, 2 + ) AS out_of_stock_ratio, + ROUND( + CASE + WHEN ads = 0 THEN GREATEST(advance_stock_days - + ROUND(virtual_stock / 0.001, 2), 0) + ELSE GREATEST(advance_stock_days - + ROUND(virtual_stock / ads, 2), 0) + END * ads, 0 + ) AS out_of_stock_qty, + ROUND( + CASE + WHEN virtual_stock = 0 THEN 0 + ELSE sales / virtual_stock + END, 2 + ) AS turnover_ratio, + CASE + WHEN + CASE + WHEN sales > 0 THEN + ROUND((sales / NULLIF(virtual_stock, 0)), 2) + ELSE 0 + END > 3 THEN 'Fast Moving' + WHEN + CASE + WHEN sales > 0 THEN + ROUND((sales / NULLIF(virtual_stock, 0)), 2) + ELSE 0 + END >= 1 AND + CASE + WHEN sales > 0 THEN + ROUND((sales / NULLIF(virtual_stock, 0)), 2) + ELSE 0 + END <= 3 THEN 'Slow Moving' + ELSE 'Non Moving' + END AS fsn_classification + FROM( + SELECT + CASE + WHEN pp.default_code IS NOT NULL + THEN CONCAT(pp.default_code, ' - ', + pt.name) + ELSE + pt.name + END AS product_code_and_name, + company.id AS company_id, + company.name AS company_name, + sm.product_id AS product_id, + pc.id AS category_id, + pc.complete_name AS category_name, + sw.id AS warehouse_id, + SUM(CASE + WHEN sld_dest.usage = 'internal' AND sm.state + IN ('assigned', 'confirmed', 'waiting') + THEN sm.product_uom_qty + ELSE 0 + END) AS incoming_quantity, + SUM(CASE + WHEN sld_src.usage = 'internal' AND sm.state + IN ('assigned', 'confirmed', 'waiting') + THEN sm.product_uom_qty + ELSE 0 + END) AS outgoing_quantity, + SUM(CASE + WHEN sld_dest.usage = 'internal' AND sm.state = 'done' + THEN sm.product_uom_qty + ELSE 0 + END) - + SUM(CASE + WHEN sld_src.usage = 'internal' AND sm.state = 'done' + THEN sm.product_uom_qty + ELSE 0 + END) AS current_stock, + SUM(CASE + WHEN sld_dest.usage = 'internal' AND sm.state = 'done' + THEN sm.product_uom_qty + ELSE 0 + END) - + SUM(CASE + WHEN sld_src.usage = 'internal' AND sm.state = 'done' + THEN sm.product_uom_qty + ELSE 0 + END)+ + SUM(CASE + WHEN sld_dest.usage = 'internal' AND sm.state + IN ('assigned', 'confirmed', 'waiting') + THEN sm.product_uom_qty + ELSE 0 + END) - + SUM(CASE + WHEN sld_src.usage = 'internal' AND sm.state + IN ('assigned', 'confirmed', 'waiting') + THEN sm.product_uom_qty + ELSE 0 + END) AS virtual_stock, + SUM(CASE WHEN sm.date BETWEEN %s AND %s + AND sld_dest.usage = 'customer' + THEN sm.product_uom_qty ELSE 0 END) AS sales, + ROUND(SUM(CASE + WHEN sm.date BETWEEN %s AND %s + AND sld_src.usage = 'internal' AND sm.state = 'done' + THEN sm.product_uom_qty + ELSE 0 + END) / ((date %s - date %s)+1), 2) AS ads, + %s AS advance_stock_days + FROM stock_move sm + INNER JOIN product_product pp ON pp.id = sm.product_id + INNER JOIN product_template pt ON pt.id = pp.product_tmpl_id + INNER JOIN res_company company ON company.id = sm.company_id + INNER JOIN stock_warehouse sw ON sw.company_id = company.id + INNER JOIN product_category pc ON pc.id = pt.categ_id + LEFT JOIN ( + SELECT sm.id AS move_id, usage + FROM stock_location sld + INNER JOIN stock_move sm ON sld.id = sm.location_dest_id + ) sld_dest ON sm.id = sld_dest.move_id + LEFT JOIN ( + SELECT sm.id AS move_id, usage + FROM stock_location sld + INNER JOIN stock_move sm ON sld.id = sm.location_id + ) sld_src ON sm.id = sld_src.move_id + WHERE pp.active = TRUE + AND pt.active = TRUE + AND pt.type = 'product' + """ + params = [ + self.start_date, self.end_date, + self.start_date, self.end_date, + self.end_date, self.start_date, + self.inventory_for_next_x_days + ] + sub_queries = [] + sub_params = [] + param_count = 0 + if self.product_ids or self.category_ids: + query += " AND (" + if self.product_ids: + product_ids = [product_id.id for product_id in self.product_ids] + query += "pp.id = ANY(%s)" + params.append(product_ids) + param_count += 1 + if self.product_ids and self.category_ids: + query += " OR " + if self.category_ids: + category_ids = [category.id for category in self.category_ids] + params.append(category_ids) + query += "(pt.categ_id = ANY(%s))" + param_count += 1 + if self.product_ids or self.category_ids: + query += ")" + if self.company_ids: + company_ids = [company.id for company in self.company_ids] + query += " AND (sm.company_id = ANY(%s))" # Specify the table alias + sub_params.append(company_ids) + param_count += 1 + if self.warehouse_ids: + warehouse_ids = [warehouse.id for warehouse in self.warehouse_ids] + query += " AND (sw.id = ANY(%s))" # Specify the table alias + sub_params.append(warehouse_ids) + param_count += 1 + if sub_queries: + query += " AND " + " AND ".join(sub_queries) + query += """ GROUP BY pp.id, pt.name, pc.id, company.id, sm.product_id, + sw.id) AS sub_query """ + self.env.cr.execute(query, tuple(params + sub_params)) + result_data = self.env.cr.dictfetchall() + for data in result_data: + product_id = data.get('product_id') + out_of_stock_qty = data.get('out_of_stock_qty') + total_value = sum( + item.get('out_of_stock_qty', 0) for item in result_data) + if total_value: + out_of_stock_qty_percentage = \ + (out_of_stock_qty / total_value) * 100 + else: + out_of_stock_qty_percentage = 0.0 + data['out_of_stock_qty_percentage'] = round( + out_of_stock_qty_percentage, 2) + cost = self.env['product.product'].search([ + ('id', '=', product_id)]).standard_price + data['cost'] = cost + data['out_of_stock_value'] = out_of_stock_qty * cost + if result_data: + data = { + 'data': result_data, + 'start_date': self.start_date, + 'end_date': self.end_date, + 'inventory_for_next_x_days': self.inventory_for_next_x_days + } + return data + else: + raise ValidationError("No records found for the given criteria!") + + def action_pdf(self): + """Function for printing the pdf""" + data = { + 'model_id': self.id, + 'product_ids': self.product_ids.ids, + 'category_ids': self.category_ids.ids, + 'company_ids': self.company_ids.ids, + 'warehouse_ids': self.warehouse_ids.ids, + 'start_date': self.start_date, + "end_date": self.end_date, + "inventory_for_next_x_days": self.inventory_for_next_x_days + } + return ( + self.env.ref( + 'inventory_advanced_reports.' + 'report_inventory_out_of_stock_action') + .report_action(None, data=data)) + + def action_excel(self): + """This function is for printing excel report""" + data = self.get_report_data() + return { + 'type': 'ir.actions.report', + 'data': {'model': 'inventory.out.of.stock.report', + 'options': json.dumps( + data, default=fields.date_utils.json_default), + 'output_format': 'xlsx', + 'report_name': 'Excel Report', + }, + 'report_type': 'xlsx', + } + + def get_xlsx_report(self, data, response): + """Excel formats for the Excel report to print""" + datas = data['data'] + start_date = data['start_date'] + end_date = data['end_date'] + inventory_for_next_x_days = data['inventory_for_next_x_days'] + output = io.BytesIO() + workbook = xlsxwriter.Workbook(output, {'in_memory': True}) + sheet = workbook.add_worksheet() + sheet.set_margins(0.5, 0.5, 0.5, 0.5) + cell_format = workbook.add_format( + {'font_size': '12px', 'align': 'left'}) + header_style = workbook.add_format( + {'font_name': 'Times', 'bold': True, 'left': 1, 'bottom': 1, + 'right': 1, 'top': 1, 'align': 'center'}) + text_style = workbook.add_format( + {'font_name': 'Times', 'left': 1, 'bottom': 1, 'right': 1, 'top': 1, + 'align': 'left'}) + head = workbook.add_format( + {'align': 'center', 'bold': True, 'font_size': '20px'}) + sheet.merge_range('H2:L3', 'Inventory Out OF Stock Report', head) + bold_format = workbook.add_format( + {'bold': True, 'font_size': '10px', 'align': 'left'}) + txt = workbook.add_format({'font_size': '10px', 'align': 'left'}) + if start_date and end_date: + sheet.write('A5', 'Sales History From: ', bold_format) + sheet.write('B5', start_date, txt) + sheet.write('A6', 'Sales History Upto: ', bold_format) + sheet.write('B6', end_date, txt) + sheet.write('A7', 'Inventory Analysis For Next: ', bold_format) + sheet.write('B7', str(inventory_for_next_x_days) + ' days', txt) + headers = ['Product', 'Category', 'Current Stock', 'Incoming', + 'Outgoing', 'Virtual Stock', 'Sales', 'ADS', 'Demanded QTY', + 'In Stock Days', 'Out Of Stock Days', 'Out Of Stock Ratio', + 'Cost Price', 'Out Of Stock QTY', 'Out Of Stock QTY(%)', + 'Out Of Stock Value(%)', 'Turnover Ratio', + 'FSN Classification'] + for col, header in enumerate(headers): + sheet.write(8, col, header, header_style) + sheet.set_column('A:A', 27, cell_format) + sheet.set_column('B:B', 24, cell_format) + sheet.set_column('C:D', 10, cell_format) + sheet.set_column('E:F', 10, cell_format) + sheet.set_column('G:H', 10, cell_format) + sheet.set_column('I:J', 15, cell_format) + sheet.set_column('K:L', 15, cell_format) + sheet.set_column('M:N', 15, cell_format) + sheet.set_column('O:P', 17, cell_format) + sheet.set_column('Q:R', 15, cell_format) + sheet.set_column('S:T', 15, cell_format) + row = 9 + number = 1 + for val in datas: + sheet.write(row, 0, val['product_code_and_name'], text_style) + sheet.write(row, 1, val['category_name'], text_style) + sheet.write(row, 2, val['current_stock'], text_style) + sheet.write(row, 3, val['incoming_quantity'], text_style) + sheet.write(row, 4, val['outgoing_quantity'], text_style) + sheet.write(row, 5, val['virtual_stock'], text_style) + sheet.write(row, 6, val['sales'], text_style) + sheet.write(row, 7, val['ads'], text_style) + sheet.write(row, 8, val['demanded_quantity'], text_style) + sheet.write(row, 9, val['in_stock_days'], text_style) + sheet.write(row, 10, val['out_of_stock_days'], text_style) + sheet.write(row, 11, val['out_of_stock_ratio'], text_style) + sheet.write(row, 12, val['cost'], text_style) + sheet.write(row, 13, val['out_of_stock_qty'], text_style) + sheet.write(row, 14, val['out_of_stock_qty_percentage'], text_style) + sheet.write(row, 15, val['out_of_stock_value'], text_style) + sheet.write(row, 16, val['turnover_ratio'], text_style) + sheet.write(row, 17, val['fsn_classification'], text_style) + row += 1 + number += 1 + workbook.close() + output.seek(0) + response.stream.write(output.read()) + output.close() + + def display_report_views(self): + """Function for displaying the graph and tree view of data""" + data = self.get_report_data() + for data_values in data.get('data'): + data_values['data_id'] = self.id + self.generate_data(data_values) + graph_view_id = self.env.ref( + 'inventory_advanced_reports.' + 'inventory_out_of_stock_data_report_view_graph').id + tree_view_id = self.env.ref( + 'inventory_advanced_reports.' + 'inventory_out_of_stock_data_report_view_tree').id + graph_report = self.env.context.get("graph_report", False) + report_views = [(tree_view_id, 'tree'), + (graph_view_id, 'graph')] + view_mode = "tree,graph" + if graph_report: + report_views = [(graph_view_id, 'graph'), + (tree_view_id, 'tree')] + view_mode = "graph,tree" + return { + 'name': _('Inventory Out Of Stock Report'), + 'domain': [('data_id', '=', self.id)], + 'res_model': 'inventory.out.of.stock.data.report', + 'view_mode': view_mode, + 'type': 'ir.actions.act_window', + 'views': report_views + } + + def generate_data(self, data_values): + """Function for creating record in model inventory out of stock data + report""" + return self.env['inventory.out.of.stock.data.report'].create({ + 'product_id': data_values.get('product_id'), + 'category_id': data_values.get('category_id'), + 'company_id': data_values.get('company_id'), + 'warehouse_id': data_values.get('warehouse_id'), + 'virtual_stock': data_values.get('virtual_stock'), + 'sales': data_values.get('sales'), + 'ads': data_values.get('ads'), + 'demanded_quantity': data_values.get('demanded_quantity'), + 'in_stock_days': data_values.get('in_stock_days'), + 'out_of_stock_days': data_values.get('out_of_stock_days'), + 'out_of_stock_ratio': data_values.get('out_of_stock_ratio'), + 'cost': data_values.get('cost'), + 'out_of_stock_qty': data_values.get('out_of_stock_qty'), + 'out_of_stock_qty_percentage': data_values.get( + 'out_of_stock_qty_percentage'), + 'out_of_stock_value': data_values.get('out_of_stock_value'), + 'turnover_ratio': data_values.get('turnover_ratio'), + 'fsn_classification': data_values.get('fsn_classification'), + 'data_id': self.id, + }) diff --git a/inventory_advanced_reports/wizard/inventory_out_of_stock_report_views.xml b/inventory_advanced_reports/wizard/inventory_out_of_stock_report_views.xml new file mode 100644 index 000000000..397ba3001 --- /dev/null +++ b/inventory_advanced_reports/wizard/inventory_out_of_stock_report_views.xml @@ -0,0 +1,71 @@ + + + + + inventory.out.of.stock.report.view.form + inventory.out.of.stock.report + +
+ + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+ + + Inventory Out Of Stock Report + ir.actions.act_window + inventory.out.of.stock.report + form + + new + + + +
diff --git a/inventory_advanced_reports/wizard/inventory_over_stock_data_report.py b/inventory_advanced_reports/wizard/inventory_over_stock_data_report.py new file mode 100644 index 000000000..3954acf1c --- /dev/null +++ b/inventory_advanced_reports/wizard/inventory_over_stock_data_report.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2024-TODAY Cybrosys Technologies() +# Author: Gayathri V (odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see . +# +############################################################################### +from odoo import fields, models + + +class InventoryOverStockDataReport(models.TransientModel): + """This model is for creating a wizard for viewing the report data""" + _name = "inventory.over.stock.data.report" + _description = "Inventory Over Stock Data Report" + + product_id = fields.Many2one("product.product", string="Product") + category_id = fields.Many2one("product.category", string="Category") + company_id = fields.Many2one("res.company", string="Company") + warehouse_id = fields.Many2one("stock.warehouse", string="Warehouse") + virtual_stock = fields.Float(string="Forecasted QTY") + sales = fields.Float(string="Sales") + ads = fields.Float(string="ADS") + demanded_quantity = fields.Float(string="Demanded QTY") + in_stock_days = fields.Float(string="Coverage Days") + over_stock_qty = fields.Float(string="Over Stock QTY") + over_stock_qty_percentage = fields.Float(string="Over Stock QTY(%)") + over_stock_value = fields.Float(string="Over Stock Value") + over_stock_value_percentage = fields.Float(string="Over Stock Value(%)") + turnover_ratio = fields.Float(string="Turnover Ratio") + fsn_classification = fields.Char(string="FSN Classification") + po_date = fields.Datetime(string="Last PO Date") + po_qty = fields.Float(string="Last PO QTY") + po_price_total = fields.Float(string="Last PO Price") + po_currency_id = fields.Many2one("res.currency", string="Currency") + po_partner_id = fields.Many2one("res.partner", string="Partner") + data_id = fields.Many2one('inventory.over.stock.report', + string="Over Stock Data") diff --git a/inventory_advanced_reports/wizard/inventory_over_stock_data_report_views.xml b/inventory_advanced_reports/wizard/inventory_over_stock_data_report_views.xml new file mode 100644 index 000000000..b578f6569 --- /dev/null +++ b/inventory_advanced_reports/wizard/inventory_over_stock_data_report_views.xml @@ -0,0 +1,43 @@ + + + + + inventory.over.stock.data.report.view.graph + inventory.over.stock.data.report + + + + + + + + + + inventory.over.stock.data.report.view.tree + inventory.over.stock.data.report + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inventory_advanced_reports/wizard/inventory_over_stock_report.py b/inventory_advanced_reports/wizard/inventory_over_stock_report.py new file mode 100644 index 000000000..67428b59f --- /dev/null +++ b/inventory_advanced_reports/wizard/inventory_over_stock_report.py @@ -0,0 +1,495 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2024-TODAY Cybrosys Technologies() +# Author: Gayathri V (odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see . +# +############################################################################### +import io +import json +from odoo import fields, models, _ +from odoo.exceptions import ValidationError + +try: + from odoo.tools.misc import xlsxwriter +except ImportError: + import xlsxwriter + + +class InventoryOverStockReport(models.TransientModel): + """This model is for creating a wizard for inventory Over Stock report.""" + _name = 'inventory.over.stock.report' + _description = 'Inventory Over Stock Report' + + start_date = fields.Date('Start Date', + help="Start date to analyse the report", + required=True) + end_date = fields.Date('End Date', help="End date to analyse the report", + required=True) + warehouse_ids = fields.Many2many( + "stock.warehouse", string="Warehouses", + help="Select the warehouses to generate the report") + product_ids = fields.Many2many( + "product.product", string="Products", + help="Select the products you want to generate the report for") + category_ids = fields.Many2many( + "product.category", string="Product categories", + help="Select the product categories you want to generate the report for" + ) + company_ids = fields.Many2many( + "res.company", string="Company", default=lambda self: self.env.company, + help="Select the companies you want to generate the report for") + inventory_for_next_x_days = fields.Integer( + string="Inventory For Next X Days", + help="Select next number of days for the inventory") + + def get_report_data(self): + """Function for returning data to print""" + processed_product_ids = [] + filtered_result_data = [] + query = """ + SELECT + product_id, + product_code_and_name, + category_id, + category_name, + company_id, + current_stock, + warehouse_id, + incoming_quantity, + outgoing_quantity, + virtual_stock, + sales, + ads, + advance_stock_days, + ROUND(advance_stock_days * ads, 0) AS demanded_quantity, + ROUND(CASE + WHEN ads = 0 THEN virtual_stock / 0.001 + ELSE virtual_stock / ads + END,0) AS in_stock_days, + ROUND(virtual_stock-(ads*advance_stock_days),0) + AS over_stock_qty, + ROUND( + CASE + WHEN virtual_stock = 0 THEN 0 + ELSE sales / virtual_stock + END, 2 + ) AS turnover_ratio, + CASE + WHEN + CASE + WHEN sales > 0 THEN + ROUND((sales / NULLIF(virtual_stock, 0)), 2) + ELSE 0 + END > 3 THEN 'Fast Moving' + WHEN + CASE + WHEN sales > 0 THEN + ROUND((sales / NULLIF(virtual_stock, 0)), 2) + ELSE 0 + END >= 1 AND + CASE + WHEN sales > 0 THEN + ROUND((sales / NULLIF(virtual_stock, 0)), 2) + ELSE 0 + END <= 3 THEN 'Slow Moving' + ELSE 'Non Moving' + END AS fsn_classification + FROM( + SELECT + CASE + WHEN pp.default_code IS NOT NULL + THEN CONCAT(pp.default_code, ' - ', + pt.name) + ELSE + pt.name + END AS product_code_and_name, + company.id AS company_id, + company.name AS company_name, + sm.product_id AS product_id, + pc.id AS category_id, + pc.complete_name AS category_name, + sw.id AS warehouse_id, + SUM(CASE + WHEN sld_dest.usage = 'internal' AND sm.state + IN ('assigned', 'confirmed', 'waiting') + THEN sm.product_uom_qty + ELSE 0 + END) AS incoming_quantity, + SUM(CASE + WHEN sld_src.usage = 'internal' AND sm.state + IN ('assigned', 'confirmed', 'waiting') + THEN sm.product_uom_qty + ELSE 0 + END) AS outgoing_quantity, + SUM(CASE + WHEN sld_dest.usage = 'internal' AND sm.state = 'done' + THEN sm.product_uom_qty + ELSE 0 + END) - + SUM(CASE + WHEN sld_src.usage = 'internal' AND sm.state = 'done' + THEN sm.product_uom_qty + ELSE 0 + END) AS current_stock, + SUM(CASE + WHEN sld_dest.usage = 'internal' AND sm.state = 'done' + THEN sm.product_uom_qty + ELSE 0 + END) - + SUM(CASE + WHEN sld_src.usage = 'internal' AND sm.state = 'done' + THEN sm.product_uom_qty + ELSE 0 + END)+ + SUM(CASE + WHEN sld_dest.usage = 'internal' AND sm.state + IN ('assigned', 'confirmed', 'waiting') + THEN sm.product_uom_qty + ELSE 0 + END) - + SUM(CASE + WHEN sld_src.usage = 'internal' AND sm.state + IN ('assigned', 'confirmed', 'waiting') + THEN sm.product_uom_qty + ELSE 0 + END) AS virtual_stock, + SUM(CASE WHEN sm.date BETWEEN %s AND %s + AND sld_dest.usage = 'customer' + THEN sm.product_uom_qty ELSE 0 END) AS sales, + ROUND(SUM(CASE + WHEN sm.date BETWEEN %s AND %s AND sld_src.usage = 'internal' + AND sm.state = 'done' THEN sm.product_uom_qty + ELSE 0 + END) / ((date %s - date %s)+1), 2) AS ads,%s AS advance_stock_days + FROM stock_move sm + INNER JOIN product_product pp ON pp.id = sm.product_id + INNER JOIN product_template pt ON pt.id = pp.product_tmpl_id + INNER JOIN res_company company ON company.id = sm.company_id + INNER JOIN stock_warehouse sw ON sw.company_id = company.id + INNER JOIN product_category pc ON pc.id = pt.categ_id + LEFT JOIN ( + SELECT sm.id AS move_id, usage + FROM stock_location sld + INNER JOIN stock_move sm ON sld.id = sm.location_dest_id + ) sld_dest ON sm.id = sld_dest.move_id + LEFT JOIN ( + SELECT sm.id AS move_id, usage + FROM stock_location sld + INNER JOIN stock_move sm ON sld.id = sm.location_id + ) sld_src ON sm.id = sld_src.move_id + WHERE pp.active = TRUE + AND pt.active = TRUE + AND pt.type = 'product' + """ + params = [ + self.start_date, self.end_date, + self.start_date, self.end_date, + self.end_date, self.start_date, + self.inventory_for_next_x_days + ] + sub_queries = [] + sub_params = [] + param_count = 0 + if self.product_ids or self.category_ids: + query += " AND (" + if self.product_ids: + product_ids = [product_id.id for product_id in self.product_ids] + query += "pp.id = ANY(%s)" + params.append(product_ids) + param_count += 1 + if self.product_ids and self.category_ids: + query += " OR " + if self.category_ids: + category_ids = [category.id for category in self.category_ids] + params.append(category_ids) + query += "(pt.categ_id = ANY(%s))" + param_count += 1 + if self.product_ids or self.category_ids: + query += ")" + if self.company_ids: + company_ids = [company.id for company in self.company_ids] + query += " AND (sm.company_id = ANY(%s))" # Specify the table alias + sub_params.append(company_ids) + param_count += 1 + if self.warehouse_ids: + warehouse_ids = [warehouse.id for warehouse in self.warehouse_ids] + query += " AND (sw.id = ANY(%s))" # Specify the table alias + sub_params.append(warehouse_ids) + param_count += 1 + if sub_queries: + query += " AND " + " AND ".join(sub_queries) + query += """ GROUP BY pp.id, pt.name, pc.id, company.id, sm.product_id, + sw.id) AS sub_query """ + self.env.cr.execute(query, tuple(params + sub_params)) + result_data = self.env.cr.dictfetchall() + for data in result_data: + product_id = data.get('product_id') + if product_id not in processed_product_ids: + processed_product_ids.append( + product_id) + filtered_result_data.append(data) + for data in filtered_result_data: + over_stock_qty = data.get('over_stock_qty') + product_id = data.get('product_id') + total_qty = sum( + item.get('over_stock_qty', 0) for item in filtered_result_data) + if total_qty: + over_stock_qty_percentage = \ + (over_stock_qty / total_qty) * 100 + else: + over_stock_qty_percentage = 0.0 + data['over_stock_qty_percentage'] = round( + over_stock_qty_percentage, 2) + cost = self.env['product.product'].search([ + ('id', '=', product_id)]).standard_price + data['cost'] = cost + data['over_stock_value'] = over_stock_qty * cost + latest_po = '' + confirmed_po = self.env['purchase.order.line'].search([ + ('product_id', '=', product_id), + ('state', '=', 'purchase'), + ]) + for po in confirmed_po: + if latest_po: + if latest_po.date_approve < po.date_approve: + latest_po = po + else: + latest_po = po + data['po_qty'] = 0 + data['po_price_total'] = 0 + if latest_po: + po_date = fields.Datetime.from_string(latest_po.order_id.date_approve) + if self.start_date <= po_date.date() <= self.end_date: + data['po_qty'] += latest_po.product_qty + data['po_price_total'] += latest_po.price_total + data['po_date'] = po_date + data['po_currency'] = latest_po.currency_id.name + data['po_currency_id'] = latest_po.currency_id.id + data['po_partner'] = latest_po.partner_id.name + data['po_partner_id'] = latest_po.partner_id.id + else: + data['po_price_total'] = None + data['po_qty'] = None + data['po_currency'] = None + data['po_currency_id'] = None + data['po_partner'] = None + data['po_partner_id'] = None + data[ + 'po_date'] = None + else: + data['po_price_total'] = None + data['po_qty'] = None + data['po_date'] = None + data['po_partner'] = None + data['po_partner_id'] = None + data['po_currency'] = None + data['po_currency_id'] = None + total_value = sum( + item.get('over_stock_value', 0) for item in filtered_result_data) + for data in filtered_result_data: + over_stock_value = data.get('over_stock_value') + if total_value: + over_stock_value_percentage = \ + (over_stock_value / total_value) * 100 + else: + over_stock_value_percentage = 0.0 + data['over_stock_value_percentage'] = round( + over_stock_value_percentage, 2) + if filtered_result_data: + data = { + 'data': filtered_result_data, + 'start_date': self.start_date, + 'end_date': self.end_date, + 'inventory_for_next_x_days': self.inventory_for_next_x_days + } + return data + else: + raise ValidationError("No records found for the given criteria!") + + def action_pdf(self): + """Function for printing pdf report""" + data = { + 'model_id': self.id, + 'product_ids': self.product_ids.ids, + 'category_ids': self.category_ids.ids, + 'company_ids': self.company_ids.ids, + 'warehouse_ids': self.warehouse_ids.ids, + 'start_date': self.start_date, + "end_date": self.end_date, + "inventory_for_next_x_days": self.inventory_for_next_x_days + } + return ( + self.env.ref( + 'inventory_advanced_reports.' + 'report_inventory_over_stock_action') + .report_action(None, data=data)) + + def action_excel(self): + """This function is for printing excel report""" + data = self.get_report_data() + return { + 'type': 'ir.actions.report', + 'data': {'model': 'inventory.over.stock.report', + 'options': json.dumps + (data, default=fields.date_utils.json_default), + 'output_format': 'xlsx', + 'report_name': 'Excel Report', + }, + 'report_type': 'xlsx', + } + + def get_xlsx_report(self, data, response): + """Excel format to print the Excel report. """ + datas = data['data'] + start_date = data['start_date'] + end_date = data['end_date'] + inventory_for_next_x_days = data['inventory_for_next_x_days'] + output = io.BytesIO() + workbook = xlsxwriter.Workbook(output, {'in_memory': True}) + sheet = workbook.add_worksheet() + sheet.set_margins(0.5, 0.5, 0.5, 0.5) + cell_format = workbook.add_format( + {'font_size': '12px', 'align': 'left'}) + header_style = workbook.add_format( + {'font_name': 'Times', 'bold': True, 'left': 1, 'bottom': 1, + 'right': 1, 'top': 1, 'align': 'center'}) + text_style = workbook.add_format( + {'font_name': 'Times', 'left': 1, 'bottom': 1, 'right': 1, 'top': 1, + 'align': 'left'}) + head = workbook.add_format( + {'align': 'center', 'bold': True, 'font_size': '20px'}) + sheet.merge_range('I2:M3', 'Inventory Over Stock Report', head) + bold_format = workbook.add_format( + {'bold': True, 'font_size': '10px', 'align': 'left'}) + txt = workbook.add_format({'font_size': '10px', 'align': 'left'}) + if start_date and end_date: + sheet.write('A5', 'Sales History From: ', bold_format) + sheet.write('B5', start_date, txt) + sheet.write('A6', 'Sales History Upto: ', bold_format) + sheet.write('B6', end_date, txt) + sheet.write('A7', 'Inventory Analysis For Next: ', bold_format) + sheet.write('B7', str(inventory_for_next_x_days) + ' days', txt) + headers = ['Product', 'Category', 'Current Stock', 'Incoming', + 'Outgoing', 'Virtual Stock', 'Sales', 'ADS', 'Demanded QTY', + 'Coverage Days', 'Over Stock QTY', 'Over Stock QTY(%)', + 'Over Stock Value', 'Over Stock Value(%)', 'Turnover Ratio', + 'FSN Classification', 'Last PO Date', 'Last PO QTY', + 'Last PO Price', 'Currency', 'Partner'] + for col, header in enumerate(headers): + sheet.write(8, col, header, header_style) + sheet.set_column('A:A', 27, cell_format) + sheet.set_column('B:B', 24, cell_format) + sheet.set_column('C:D', 10, cell_format) + sheet.set_column('E:F', 10, cell_format) + sheet.set_column('G:H', 10, cell_format) + sheet.set_column('I:J', 15, cell_format) + sheet.set_column('K:L', 15, cell_format) + sheet.set_column('M:N', 15, cell_format) + sheet.set_column('O:P', 15, cell_format) + sheet.set_column('Q:Q', 15, cell_format) + sheet.set_column('R:R', 13, cell_format) + sheet.set_column('S:T', 13, cell_format) + sheet.set_column('U:V', 13, cell_format) + row = 9 + number = 1 + for val in datas: + sheet.write(row, 0, val['product_code_and_name'], text_style) + sheet.write(row, 1, val['category_name'], text_style) + sheet.write(row, 2, val['current_stock'], text_style) + sheet.write(row, 3, val['incoming_quantity'], text_style) + sheet.write(row, 4, val['outgoing_quantity'], text_style) + sheet.write(row, 5, val['virtual_stock'], text_style) + sheet.write(row, 6, val['sales'], text_style) + sheet.write(row, 7, val['ads'], text_style) + sheet.write(row, 8, val['demanded_quantity'], text_style) + sheet.write(row, 9, val['in_stock_days'], text_style) + sheet.write(row, 10, val['over_stock_qty'], text_style) + sheet.write(row, 11, val['over_stock_qty_percentage'], text_style) + sheet.write(row, 12, val['over_stock_value'], text_style) + sheet.write(row, 13, val['over_stock_value_percentage'], text_style) + sheet.write(row, 14, val['turnover_ratio'], text_style) + sheet.write(row, 15, val['fsn_classification'], text_style) + sheet.write(row, 16, val['po_date'], text_style) + sheet.write(row, 17, val['po_qty'], text_style) + sheet.write(row, 18, val['po_price_total'], text_style) + sheet.write(row, 19, val['po_currency'], text_style) + sheet.write(row, 20, val['po_partner'], text_style) + row += 1 + number += 1 + workbook.close() + output.seek(0) + response.stream.write(output.read()) + output.close() + + def display_report_views(self): + """Function for displaying the graph and tree view of the data""" + data = self.get_report_data() + for data_values in data.get('data'): + data_values['data_id'] = self.id + self.generate_data(data_values) + graph_view_id = self.env.ref( + 'inventory_advanced_reports.' + 'inventory_over_stock_data_report_view_graph').id + tree_view_id = self.env.ref( + 'inventory_advanced_reports.' + 'inventory_over_stock_data_report_view_tree').id + graph_report = self.env.context.get("graph_report", False) + report_views = [(tree_view_id, 'tree'), + (graph_view_id, 'graph')] + view_mode = "tree,graph" + if graph_report: + report_views = [(graph_view_id, 'graph'), + (tree_view_id, 'tree')] + view_mode = "graph,tree" + return { + 'name': _('Inventory Over Stock Report'), + 'domain': [('data_id', '=', self.id)], + 'res_model': 'inventory.over.stock.data.report', + 'view_mode': view_mode, + 'type': 'ir.actions.act_window', + 'views': report_views + } + + def generate_data(self, data_values): + """Function to create record in model inventory over stock data + report""" + return self.env['inventory.over.stock.data.report'].create({ + 'product_id': data_values.get('product_id'), + 'category_id': data_values.get('category_id'), + 'company_id': data_values.get('company_id'), + 'warehouse_id': data_values.get('warehouse_id'), + 'virtual_stock': data_values.get('virtual_stock'), + 'sales': data_values.get('sales'), + 'ads': data_values.get('ads'), + 'demanded_quantity': data_values.get('demanded_quantity'), + 'in_stock_days': data_values.get('in_stock_days'), + 'over_stock_qty': data_values.get('over_stock_qty'), + 'over_stock_qty_percentage': + data_values.get('over_stock_qty_percentage'), + 'over_stock_value': data_values.get('over_stock_value'), + 'over_stock_value_percentage': + data_values.get('over_stock_value_percentage'), + 'turnover_ratio': data_values.get('turnover_ratio'), + 'fsn_classification': data_values.get('fsn_classification'), + 'po_date': data_values.get('po_date'), + 'po_qty': data_values.get('po_qty'), + 'po_price_total': data_values.get('po_price_total'), + 'po_currency_id': data_values.get('po_currency_id'), + 'po_partner_id': data_values.get('po_partner_id'), + 'data_id': self.id, + }) diff --git a/inventory_advanced_reports/wizard/inventory_over_stock_report_views.xml b/inventory_advanced_reports/wizard/inventory_over_stock_report_views.xml new file mode 100644 index 000000000..6cd331142 --- /dev/null +++ b/inventory_advanced_reports/wizard/inventory_over_stock_report_views.xml @@ -0,0 +1,71 @@ + + + + + inventory.over.stock.report.view.form + inventory.over.stock.report + +
+ + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+ + + Inventory Over Stock Report + ir.actions.act_window + inventory.over.stock.report + form + + new + + + +
diff --git a/inventory_advanced_reports/wizard/inventory_stock_movement_report.py b/inventory_advanced_reports/wizard/inventory_stock_movement_report.py new file mode 100644 index 000000000..c2c3b82fb --- /dev/null +++ b/inventory_advanced_reports/wizard/inventory_stock_movement_report.py @@ -0,0 +1,325 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2024-TODAY Cybrosys Technologies() +# Author: Gayathri V (odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see . +# +############################################################################### +import io +import json +from odoo import fields, models +from odoo.exceptions import ValidationError + +try: + from odoo.tools.misc import xlsxwriter +except ImportError: + import xlsxwriter + + +class InventoryStockMovementReport(models.TransientModel): + """This model is for creating a wizard for inventory Over Stock report.""" + _name = 'inventory.stock.movement.report' + _description = 'Inventory Stock Movement Report' + + start_date = fields.Date('Start Date', + default=lambda self: fields.Date.today(), + help="Start date to analyze the report") + end_date = fields.Date('End Date', + default=lambda self: fields.Date.today(), + help="End date to analyse the report") + warehouse_ids = fields.Many2many( + "stock.warehouse", string="Warehouses", + help="Select the warehouses to generate the report") + product_ids = fields.Many2many( + "product.product", string="Products", + help="Select the products you want to generate the report for") + category_ids = fields.Many2many( + "product.category", string="Product categories", + help="Select the product categories you want to generate the report for" + ) + company_ids = fields.Many2many( + "res.company", string="Company", default=lambda self: self.env.company, + help="Select the companies you want to generate the report for") + report_up_to_certain_date = fields.Boolean(string="Date upto") + up_to_certain_date = fields.Date(string="Movements Upto") + + def get_report_data(self): + """Function for returning the values for printing""" + query = """ + SELECT + pp.id as product_id, + CASE + WHEN pp.default_code IS NOT NULL + THEN CONCAT(pp.default_code, ' - ', pt.name) + ELSE + pt.name + END AS product_code_and_name, + pc.complete_name AS category_name, + company.name AS company_name, + """ + if self.report_up_to_certain_date: + query += """ + SUM(CASE WHEN sm.date <= %s AND sld_dest.usage = 'inventory' + THEN sm.product_uom_qty ELSE 0 END) AS opening_stock, + (SUM(CASE WHEN sm.date <= %s AND sld_dest.usage = 'internal' + THEN sm.product_uom_qty ELSE 0 END) - + SUM(CASE WHEN sm.date <= %s AND sld_src.usage = 'internal' + THEN sm.product_uom_qty ELSE 0 END)) AS closing_stock, + SUM(CASE WHEN sm.date <= %s AND sld_dest.usage = 'customer' + THEN sm.product_uom_qty ELSE 0 END) AS sales, + SUM(CASE WHEN sm.date <= %s AND sld_src.usage = 'customer' + THEN sm.product_uom_qty ELSE 0 END) AS sales_return, + SUM(CASE WHEN sm.date <= %s AND sld_src.usage = 'supplier' + THEN sm.product_uom_qty ELSE 0 END) AS purchase, + SUM(CASE WHEN sm.date <= %s AND sld_dest.usage = 'supplier' + THEN sm.product_uom_qty ELSE 0 END) AS purchase_return, + SUM(CASE WHEN sm.date <= %s AND sld_dest.usage = 'internal' + THEN sm.product_uom_qty ELSE 0 END) AS internal_in, + SUM(CASE WHEN sm.date <= %s AND sld_src.usage = 'internal' + THEN sm.product_uom_qty ELSE 0 END) AS internal_out, + SUM(CASE WHEN sm.date <= %s AND sld_dest.usage = 'inventory' + THEN sm.product_uom_qty ELSE 0 END) AS adj_in, + SUM(CASE WHEN sm.date <= %s AND sld_src.usage = 'inventory' + THEN sm.product_uom_qty ELSE 0 END) AS adj_out, + SUM(CASE WHEN sm.date <= %s AND sld_dest.usage = 'production' + THEN sm.product_uom_qty ELSE 0 END) AS production_in, + SUM(CASE WHEN sm.date <= %s AND sld_src.usage = 'production' + THEN sm.product_uom_qty ELSE 0 END) AS production_out, + SUM(CASE WHEN sm.date <= %s AND sld_dest.usage = 'transit' + THEN sm.product_uom_qty ELSE 0 END) AS transit_in, + SUM(CASE WHEN sm.date <= %s AND sld_src.usage = 'transit' + THEN sm.product_uom_qty ELSE 0 END) AS transit_out + """ + params = [self.up_to_certain_date] * 15 + else: + query += """ + (SUM(CASE WHEN sm.date <= %s AND sld_dest.usage = 'internal' + THEN sm.product_uom_qty ELSE 0 END) - + SUM(CASE WHEN sm.date <= %s AND sld_src.usage = 'internal' + THEN sm.product_uom_qty ELSE 0 END)) AS opening_stock, + (SUM(CASE WHEN sm.date <= %s AND sld_dest.usage = 'internal' + THEN sm.product_uom_qty ELSE 0 END) - + SUM(CASE WHEN sm.date <= %s AND sld_src.usage = 'internal' + THEN sm.product_uom_qty ELSE 0 END)) AS closing_stock, + SUM(CASE WHEN sm.date BETWEEN %s AND %s + AND sld_dest.usage = 'customer' + THEN sm.product_uom_qty ELSE 0 END) AS sales, + SUM(CASE WHEN sm.date BETWEEN %s + AND %s AND sld_src.usage = 'customer' THEN sm.product_uom_qty + ELSE 0 END) AS sales_return, + SUM(CASE WHEN sm.date BETWEEN %s AND %s + AND sld_src.usage = 'supplier' THEN sm.product_uom_qty + ELSE 0 END) AS purchase, + SUM(CASE WHEN sm.date BETWEEN %s + AND %s AND sld_dest.usage = 'supplier' THEN sm.product_uom_qty + ELSE 0 END) AS purchase_return, + SUM(CASE WHEN sm.date BETWEEN %s AND %s + AND sld_dest.usage = 'internal' THEN sm.product_uom_qty + ELSE 0 END) AS internal_in, + SUM(CASE WHEN sm.date BETWEEN %s AND %s + AND sld_src.usage = 'internal' THEN sm.product_uom_qty + ELSE 0 END) AS internal_out, + SUM(CASE WHEN sm.date BETWEEN %s AND %s + AND sld_dest.usage = 'inventory' THEN sm.product_uom_qty + ELSE 0 END) AS adj_in, + SUM(CASE WHEN sm.date BETWEEN %s AND %s + AND sld_src.usage = 'inventory' THEN sm.product_uom_qty + ELSE 0 END) AS adj_out, + SUM(CASE WHEN sm.date BETWEEN %s AND %s + AND sld_dest.usage = 'production' THEN sm.product_uom_qty + ELSE 0 END) AS production_in, + SUM(CASE WHEN sm.date BETWEEN %s + AND %s AND sld_src.usage = 'production' THEN sm.product_uom_qty + ELSE 0 END) AS production_out, + SUM(CASE WHEN sm.date BETWEEN %s AND %s + AND sld_dest.usage = 'transit' THEN sm.product_uom_qty + ELSE 0 END) AS transit_in, + SUM(CASE WHEN sm.date BETWEEN %s AND %s + AND sld_src.usage = 'transit' THEN sm.product_uom_qty + ELSE 0 END) AS transit_out + """ + params = [self.start_date, self.start_date, self.end_date, + self.end_date, self.start_date, + self.end_date, self.start_date, self.end_date, + self.start_date, self.end_date, self.start_date, + self.end_date, self.start_date, self.end_date, + self.start_date, self.end_date, self.start_date, + self.end_date, self.start_date, self.end_date, + self.start_date, + self.end_date, self.start_date, self.end_date, + self.start_date, + self.end_date, self.start_date, self.end_date] + query += """ + FROM stock_move sm + INNER JOIN product_product pp ON pp.id = sm.product_id + INNER JOIN product_template pt ON pt.id = pp.product_tmpl_id + INNER JOIN res_company company ON company.id = sm.company_id + INNER JOIN stock_warehouse sw ON sw.company_id = company.id + INNER JOIN product_category pc ON pc.id = pt.categ_id + """ + query += """ + LEFT JOIN stock_location sld_dest + ON sm.location_dest_id = sld_dest.id + LEFT JOIN stock_location sld_src + ON sm.location_id = sld_src.id + WHERE + sm.state = 'done' + """ + sub_queries = [] + if self.product_ids: + product_ids = [product_id.id for product_id in self.product_ids] + sub_queries.append("pp.id = ANY(%s)") + params.append(product_ids) + if self.category_ids: + category_ids = [category.id for category in self.category_ids] + sub_queries.append("pt.categ_id = ANY(%s)") + params.append(category_ids) + if sub_queries: + query += " AND (" + " OR ".join(sub_queries) + ")" + if self.company_ids: + company_ids = [company.id for company in self.company_ids] + query += " AND sm.company_id = ANY(%s)" + params.append(company_ids) + if self.warehouse_ids: + warehouse_ids = [warehouse.id for warehouse in self.warehouse_ids] + query += " AND sw.id = ANY(%s)" + params.append(warehouse_ids) + query += """ + GROUP BY pp.id,pt.name,pc.complete_name,company.name + """ + self.env.cr.execute(query, params) + result_data = self.env.cr.dictfetchall() + if result_data: + data = { + 'data': result_data, + 'start_date': self.start_date, + 'end_date': self.end_date, + 'up_to_certain_date': self.up_to_certain_date + } + return data + else: + raise ValidationError("No records found for the given criteria!") + + def action_pdf(self): + """Function for printing the pdf report""" + data = { + 'model_id': self.id, + 'product_ids': self.product_ids.ids, + 'category_ids': self.category_ids.ids, + 'company_ids': self.company_ids.ids, + 'warehouse_ids': self.warehouse_ids.ids, + 'start_date': self.start_date, + "end_date": self.end_date, + "report_up_to_certain_date": self.report_up_to_certain_date, + "up_to_certain_date": self.up_to_certain_date + } + return ( + self.env.ref( + 'inventory_advanced_reports.' + 'report_inventory_stock_movement_action') + .report_action(None, data=data)) + + def action_excel(self): + """This function is for printing excel report""" + data = self.get_report_data() + return { + 'type': 'ir.actions.report', + 'data': {'model': 'inventory.stock.movement.report', + 'options': json.dumps + (data, default=fields.date_utils.json_default), + 'output_format': 'xlsx', + 'report_name': 'Excel Report', + }, + 'report_type': 'xlsx', + } + + def get_xlsx_report(self, data, response): + """Excel format to print the data in Excel sheet""" + datas = data['data'] + start_date = data['start_date'] + end_date = data['end_date'] + up_to_certain_date = data['up_to_certain_date'] + output = io.BytesIO() + workbook = xlsxwriter.Workbook(output, {'in_memory': True}) + sheet = workbook.add_worksheet() + sheet.set_margins(0.5, 0.5, 0.5, 0.5) + cell_format = workbook.add_format( + {'font_size': '12px', 'align': 'left'}) + header_style = workbook.add_format( + {'font_name': 'Times', 'bold': True, 'left': 1, 'bottom': 1, + 'right': 1, 'top': 1, 'align': 'center'}) + text_style = workbook.add_format( + {'font_name': 'Times', 'left': 1, 'bottom': 1, 'right': 1, 'top': 1, + 'align': 'left'}) + head = workbook.add_format( + {'align': 'center', 'bold': True, 'font_size': '20px'}) + bold_format = workbook.add_format( + {'bold': True, 'font_size': '10px', 'align': 'left'}) + txt = workbook.add_format({'font_size': '10px', 'align': 'left'}) + if start_date and end_date and not up_to_certain_date: + sheet.write('A6', 'From Date: ', bold_format) + sheet.write('B6', start_date, txt) + sheet.write('A7', 'To Date: ', bold_format) + sheet.write('B7', end_date, txt) + if up_to_certain_date: + sheet.write('A7', 'Stock Movements Up To: ', bold_format) + sheet.write('B7', up_to_certain_date, txt) + sheet.merge_range('E2:K3', 'Inventory Stock Movement Report', head) + headers = ['Company', 'Product', 'Category', 'Opening Stock', 'Sales', + 'Sales Return', 'Purchase', 'Purchase Return', 'Internal In', + 'Internal Out', 'Adjustment In', 'Adjustment Out', + 'Production In', 'Production Out', 'Transit In', + 'Transit Out', 'Closing Stock'] + for col, header in enumerate(headers): + sheet.write(8, col, header, header_style) + sheet.set_column('A:A', 23, cell_format) + sheet.set_column('B:B', 27, cell_format) + sheet.set_column('C:C', 25, cell_format) + sheet.set_column('D:D', 13, cell_format) + sheet.set_column('E:E', 13, cell_format) + sheet.set_column('F:G', 15, cell_format) + sheet.set_column('H:I', 15, cell_format) + sheet.set_column('J:K', 15, cell_format) + sheet.set_column('L:M', 15, cell_format) + sheet.set_column('N:O', 15, cell_format) + sheet.set_column('P:Q', 15, cell_format) + row = 9 + number = 1 + for val in datas: + sheet.write(row, 0, val['company_name'], text_style) + sheet.write(row, 1, val['product_code_and_name'], text_style) + sheet.write(row, 2, val['category_name'], text_style) + sheet.write(row, 3, val['opening_stock'], text_style) + sheet.write(row, 4, val['sales'], text_style) + sheet.write(row, 5, val['sales_return'], text_style) + sheet.write(row, 6, val['purchase'], text_style) + sheet.write(row, 7, val['purchase_return'], text_style) + sheet.write(row, 8, val['internal_in'], text_style) + sheet.write(row, 9, val['internal_out'], text_style) + sheet.write(row, 10, val['adj_in'], text_style) + sheet.write(row, 11, val['adj_out'], text_style) + sheet.write(row, 12, val['production_in'], text_style) + sheet.write(row, 13, val['production_out'], text_style) + sheet.write(row, 14, val['transit_in'], text_style) + sheet.write(row, 15, val['transit_out'], text_style) + sheet.write(row, 16, val['closing_stock'], text_style) + row += 1 + number += 1 + workbook.close() + output.seek(0) + response.stream.write(output.read()) + output.close() diff --git a/inventory_advanced_reports/wizard/inventory_stock_movement_report_views.xml b/inventory_advanced_reports/wizard/inventory_stock_movement_report_views.xml new file mode 100644 index 000000000..70c834922 --- /dev/null +++ b/inventory_advanced_reports/wizard/inventory_stock_movement_report_views.xml @@ -0,0 +1,69 @@ + + + + + inventory.stock.movement.report.view.form + inventory.stock.movement.report + +
+ + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+ + + Inventory Stock Movement Report + ir.actions.act_window + inventory.stock.movement.report + form + + new + + + +
diff --git a/inventory_advanced_reports/wizard/inventory_xyz_data_report.py b/inventory_advanced_reports/wizard/inventory_xyz_data_report.py new file mode 100644 index 000000000..c528fc686 --- /dev/null +++ b/inventory_advanced_reports/wizard/inventory_xyz_data_report.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2024-TODAY Cybrosys Technologies() +# Author: Gayathri V (odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see . +# +############################################################################### +from odoo import fields, models + + +class InventoryXyzDataReport(models.TransientModel): + """This model is for creating a wizard for viewing the report data""" + _name = "inventory.xyz.data.report" + _description = "Inventory XYZ Data Report" + + product_id = fields.Many2one("product.product", string="Product") + category_id = fields.Many2one("product.category", string="Category") + company_id = fields.Many2one("res.company", string="Company") + current_stock = fields.Float(string="Current Stock") + stock_value = fields.Float(string="Stock Value") + stock_percentage = fields.Float(string="Stock Value(%)") + cumulative_stock_percentage = fields.Float(string="CUMULATIVE STOCK( %)") + xyz_classification = fields.Char(string="XYZ CLASSIFICATION") + data_id = fields.Many2one('inventory.xyz.report', string="XYZ Data") diff --git a/inventory_advanced_reports/wizard/inventory_xyz_data_report_views.xml b/inventory_advanced_reports/wizard/inventory_xyz_data_report_views.xml new file mode 100644 index 000000000..5876ddcda --- /dev/null +++ b/inventory_advanced_reports/wizard/inventory_xyz_data_report_views.xml @@ -0,0 +1,32 @@ + + + + + inventory.xyz.data.report.view.graph + inventory.xyz.data.report + + + + + + + + + + + inventory.xyz.data.report.view.tree + inventory.xyz.data.report + + + + + + + + + + + + + + diff --git a/inventory_advanced_reports/wizard/inventory_xyz_report.py b/inventory_advanced_reports/wizard/inventory_xyz_report.py new file mode 100644 index 000000000..69482125b --- /dev/null +++ b/inventory_advanced_reports/wizard/inventory_xyz_report.py @@ -0,0 +1,273 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2024-TODAY Cybrosys Technologies() +# Author: Gayathri V (odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see . +# +############################################################################### +import io +import json +from odoo import fields, models, _ +from odoo.exceptions import ValidationError + +try: + from odoo.tools.misc import xlsxwriter +except ImportError: + import xlsxwriter + + +class InventoryXyzReport(models.TransientModel): + """This model is for creating a wizard for inventory aging report""" + _name = "inventory.xyz.report" + _description = "Inventory XYZ Report" + + product_ids = fields.Many2many( + "product.product", string="Products", + help="Select the products you want to generate the report for") + category_ids = fields.Many2many( + "product.category", string="Product Categories", + help="Select the product categories you want to generate the report for" + ) + company_ids = fields.Many2many( + 'res.company', string="Company", + help="Select the companies you want to generate the report for" + ) + xyz = fields.Selection([('x', 'X'), ('y', 'Y'), ('z', 'Z'), ('all', 'All')], + string="XYZ Classification", default='all', + required=True) + + def get_report_data(self): + """Function for returning data to print""" + xyz = dict(self._fields['xyz'].selection).get(self.xyz) + params = [] + param_count = 0 + query = """ + SELECT + CASE + WHEN pp.default_code IS NOT NULL + THEN CONCAT(pp.default_code, ' - ', + pt.name) + ELSE + pt.name + END AS product_code_and_name, + svl.company_id, + company.name AS company_name, + svl.product_id, + pt.categ_id AS category_id, + c.complete_name AS category_name, + SUM(svl.remaining_qty) AS current_stock, + SUM(svl.remaining_value) AS stock_value + FROM stock_valuation_layer svl + INNER JOIN res_company company ON company.id = svl.company_id + INNER JOIN product_product pp ON pp.id = svl.product_id + INNER JOIN product_template pt ON pt.id = pp.product_tmpl_id + INNER JOIN product_category c ON c.id = pt.categ_id + WHERE pp.active = TRUE + AND pt.active = TRUE + AND pt.type = 'product' + AND svl.remaining_value IS NOT NULL + """ + if self.company_ids: + company_ids = [company_id.id for company_id in self.company_ids] + query += f" AND (company.id IS NULL OR company.id = ANY(%s))" + params.append(company_ids) + param_count += 1 + if self.product_ids or self.category_ids: + query += " AND (" + if self.product_ids: + product_ids = [product_id.id for product_id in self.product_ids] + query += f"pp.id = ANY(%s)" + params.append(product_ids) + param_count += 1 + if self.product_ids and self.category_ids: + query += " OR " + if self.category_ids: + category_ids = [category_id.id for category_id in + self.category_ids] + query += f"c.id = ANY(%s)" + params.append(category_ids) + param_count += 1 + query += ")" + query += """ + GROUP BY + svl.company_id, + company.name, + svl.product_id, + CASE + WHEN pp.default_code IS NOT NULL + THEN CONCAT(pp.default_code, ' - ', pt.name) + ELSE + pt.name + END, + pt.categ_id, + c.complete_name + ORDER BY SUM(svl.remaining_value) DESC; + """ + self.env.cr.execute(query, params) + result_data = self.env.cr.dictfetchall() + total_current_value = 0 + cumulative_stock = 0 + filtered_stock = [] + for row in result_data: + current_value = row.get('stock_value') + total_current_value += current_value + for value in result_data: + current_value = value.get('stock_value') + if total_current_value != 0 and current_value: + stock_percentage = (current_value / total_current_value) * 100 + else: + stock_percentage = 0.0 + value['stock_percentage'] = round(stock_percentage, 2) + cumulative_stock += value['stock_percentage'] + value['cumulative_stock_percentage'] = round(cumulative_stock, 2) + if cumulative_stock < 70: + xyz_classification = 'X' + elif 70 <= cumulative_stock <= 90: + xyz_classification = 'Y' + else: + xyz_classification = 'Z' + value['xyz_classification'] = xyz_classification + if result_data: + for xyz_class in result_data: + if xyz_class.get('xyz_classification') == str(xyz): + filtered_stock.append(xyz_class) + if xyz == 'All' and not result_data: + raise ValidationError("No corresponding data to print") + elif xyz != 'All' and filtered_stock == []: + raise ValidationError("No corresponding data to print") + data = { + 'data': result_data if xyz == 'All' else filtered_stock, + } + return data + else: + raise ValidationError("No records found for the given criteria!") + + def action_pdf(self): + """Function for printing the pdf report""" + data = { + 'model_id': self.id, + 'product_ids': self.product_ids.ids, + 'category_ids': self.category_ids.ids, + 'company_ids': self.company_ids.ids, + "xyz": dict(self._fields['xyz'].selection).get(self.xyz) + + } + return ( + self.env.ref( + 'inventory_advanced_reports.report_inventory_xyz_action') + .report_action(None, data=data)) + + def action_excel(self): + """This function is for printing excel report""" + data = self.get_report_data() + return { + 'type': 'ir.actions.report', + 'data': {'model': 'inventory.xyz.report', + 'options': json.dumps + (data, default=fields.date_utils.json_default), + 'output_format': 'xlsx', + 'report_name': 'Excel Report', + }, + 'report_type': 'xlsx', + } + + def get_xlsx_report(self, data, response): + """Excel formats for printing data in Excel sheets""" + datas = data['data'] + output = io.BytesIO() + workbook = xlsxwriter.Workbook(output, {'in_memory': True}) + sheet = workbook.add_worksheet() + sheet.set_margins(0.5, 0.5, 0.5, 0.5) + cell_format = workbook.add_format( + {'font_size': '12px', 'align': 'left'}) + header_style = workbook.add_format( + {'font_name': 'Times', 'bold': True, 'left': 1, 'bottom': 1, + 'right': 1, 'top': 1, 'align': 'center'}) + text_style = workbook.add_format( + {'font_name': 'Times', 'left': 1, 'bottom': 1, 'right': 1, 'top': 1, + 'align': 'left'}) + head = workbook.add_format( + {'align': 'center', 'bold': True, 'font_size': '20px'}) + sheet.merge_range('B2:E3', 'Inventory XYZ Report', head) + headers = ['Product', 'Category', 'Current Stock', 'Stock Value', + 'Cumulative Stock', 'XYZ Calculation'] + for col, header in enumerate(headers): + sheet.write(8, col, header, header_style) + sheet.set_column('A:B', 27, cell_format) + sheet.set_column('C:D', 15, cell_format) + sheet.set_column('E:F', 15, cell_format) + row = 9 + number = 1 + for val in datas: + sheet.write(row, 0, val['product_code_and_name'], text_style) + sheet.write(row, 1, val['category_name'], text_style) + sheet.write(row, 2, val['current_stock'], text_style) + sheet.write(row, 3, val['stock_value'], text_style) + sheet.write(row, 4, val['stock_percentage'], text_style) + sheet.write(row, 5, val['cumulative_stock_percentage'], text_style) + sheet.write(row, 5, val['xyz_classification'], text_style) + row += 1 + number += 1 + workbook.close() + output.seek(0) + response.stream.write(output.read()) + output.close() + + def display_report_views(self): + """Function for displaying graph and tree view of the data""" + data = self.get_report_data() + for data_values in data.get('data'): + data_values['data_id'] = self.id + self.generate_data(data_values) + graph_view_id = self.env.ref( + 'inventory_advanced_reports.' + 'inventory_xyz_data_report_view_graph').id + tree_view_id = self.env.ref( + 'inventory_advanced_reports.' + 'inventory_xyz_data_report_view_tree').id + graph_report = self.env.context.get("graph_report", False) + report_views = [(tree_view_id, 'tree'), + (graph_view_id, 'graph')] + view_mode = "tree,graph" + if graph_report: + report_views = [(graph_view_id, 'graph'), + (tree_view_id, 'tree')] + view_mode = "graph,tree" + return { + 'name': _('Inventory XYZ Report'), + 'domain': [('data_id', '=', self.id)], + 'res_model': 'inventory.xyz.data.report', + 'view_mode': view_mode, + 'type': 'ir.actions.act_window', + 'views': report_views + } + + def generate_data(self, data_values): + """Function for creating record in the model inventory cyz data + report""" + return self.env['inventory.xyz.data.report'].create({ + 'product_id': data_values.get('product_id'), + 'category_id': data_values.get('category_id'), + 'company_id': data_values.get('company_id'), + 'current_stock': data_values.get('current_stock'), + 'stock_value': data_values.get('stock_value'), + 'stock_percentage': data_values.get('stock_percentage'), + 'cumulative_stock_percentage': data_values.get( + 'cumulative_stock_percentage'), + 'xyz_classification': data_values.get('xyz_classification'), + 'data_id': self.id, + }) diff --git a/inventory_advanced_reports/wizard/inventory_xyz_report_views.xml b/inventory_advanced_reports/wizard/inventory_xyz_report_views.xml new file mode 100644 index 000000000..131cb0b90 --- /dev/null +++ b/inventory_advanced_reports/wizard/inventory_xyz_report_views.xml @@ -0,0 +1,48 @@ + + + + + inventory.xyz.report.view.form + inventory.xyz.report + +
+ + + + + + + + + + + +
+
+
+
+
+
+ + + Inventory XYZ Report + ir.actions.act_window + inventory.xyz.report + form + + new + + + +