| @ -0,0 +1,51 @@ | |||
| .. 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 | |||
| 
 | |||
| Advanced Inventory Reports | |||
| ========================== | |||
| Helps to Manage different types of Inventory Reports like FSN Report, Out | |||
| Of Stock Report, etc. | |||
| 
 | |||
| Configuration | |||
| ============= | |||
| - Additional configuration not required | |||
| 
 | |||
| License | |||
| ------- | |||
| Lesser General Public License, Version 3 (LGPL v3). | |||
| (https://www.gnu.org/licenses/lgpl-3.0-standalone.html) | |||
| 
 | |||
| Company | |||
| ------- | |||
| * `Cybrosys Techno Solutions <https://cybrosys.com/>`__ | |||
| 
 | |||
| Credits | |||
| ------- | |||
| Developer: | |||
|             (V16) Anusha C, | |||
|             (V17) Jumana Haseen, | |||
| 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: `<static/description/index.html>`__ | |||
| @ -0,0 +1,24 @@ | |||
| # -*- coding: utf-8 -*- | |||
| ############################################################################### | |||
| # | |||
| #  Cybrosys Technologies Pvt. Ltd. | |||
| # | |||
| #  Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) | |||
| #  Author: Jumana Haseen (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 <http://www.gnu.org/licenses/>. | |||
| # | |||
| ############################################################################### | |||
| from . import controllers | |||
| from . import report | |||
| from . import wizard | |||
| @ -0,0 +1,71 @@ | |||
| # -*- coding: utf-8 -*- | |||
| ############################################################################### | |||
| # | |||
| #  Cybrosys Technologies Pvt. Ltd. | |||
| # | |||
| #  Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) | |||
| #  Author: Jumana Haseen (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 <http://www.gnu.org/licenses/>. | |||
| # | |||
| ############################################################################### | |||
| { | |||
|     "name": "Advanced Inventory Reports", | |||
|     "version": "17.0.1.0.0", | |||
|     "category": 'Warehouse', | |||
|     "summary": """Helps to Manage different types of Inventory Reports like FSN | |||
|     Report, Out Of Stock Report, Inventory XYZ Report etc.""", | |||
|     "description": """Provides efficient management of various types of | |||
|     inventory reports, including FSN Reports, Out-of-Stock Reports to monitor | |||
|     and prevent inventory shortages, Inventory XYZ Reports for categorizing | |||
|     items based on value and volume, and other customizable reports tailored to  | |||
|     the specific needs of the business.""", | |||
|     "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.jpg'], | |||
|     'license': 'LGPL-3', | |||
|     'installable': True, | |||
|     'auto_install': False, | |||
|     'application': False, | |||
| } | |||
| @ -0,0 +1,22 @@ | |||
| # -*- coding: utf-8 -*- | |||
| ############################################################################### | |||
| # | |||
| #  Cybrosys Technologies Pvt. Ltd. | |||
| # | |||
| #  Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) | |||
| #  Author: Jumana Haseen (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 <http://www.gnu.org/licenses/>. | |||
| # | |||
| ############################################################################### | |||
| from . import inventory_advanced_reports | |||
| @ -0,0 +1,57 @@ | |||
| # -*- coding: utf-8 -*- | |||
| ############################################################################### | |||
| # | |||
| #  Cybrosys Technologies Pvt. Ltd. | |||
| # | |||
| #  Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) | |||
| #  Author: Jumana Haseen (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 <http://www.gnu.org/licenses/>. | |||
| # | |||
| ############################################################################### | |||
| import json | |||
| from odoo.http import content_disposition, request | |||
| from odoo import http | |||
| 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))) | |||
| @ -0,0 +1,6 @@ | |||
| ## Module <inventory_advanced_reports> | |||
| 
 | |||
| #### 10.01.2025 | |||
| #### Version 17.0.1.0.0 | |||
| ##### ADD | |||
| - Initial Commit for Advanced Inventory Reports | |||
| @ -0,0 +1,29 @@ | |||
| # -*- coding: utf-8 -*- | |||
| ############################################################################### | |||
| # | |||
| #  Cybrosys Technologies Pvt. Ltd. | |||
| # | |||
| #  Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) | |||
| #  Author: Jumana Haseen (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 <http://www.gnu.org/licenses/>. | |||
| # | |||
| ############################################################################### | |||
| from . import age_breakdown_report | |||
| from . import aging_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 | |||
| @ -0,0 +1,180 @@ | |||
| # -*- coding: utf-8 -*- | |||
| ############################################################################### | |||
| # | |||
| #  Cybrosys Technologies Pvt. Ltd. | |||
| # | |||
| #  Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) | |||
| #  Author: Jumana Haseen (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 <http://www.gnu.org/licenses/>. | |||
| # | |||
| ############################################################################### | |||
| 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->>'en_US') | |||
|                 ELSE | |||
|                     pt.name->>'en_US' | |||
|             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->>'en_US') | |||
|                                     ELSE | |||
|                                         pt.name->>'en_US' | |||
|                                 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)] | |||
| @ -0,0 +1,106 @@ | |||
| <?xml version="1.0" encoding="UTF-8"?> | |||
| <odoo> | |||
|     <!--    Action for the report--> | |||
|     <record id="report_inventory_age_breakdown_action" | |||
|             model="ir.actions.report"> | |||
|         <field name="name">Inventory Age Breakdown Report</field> | |||
|         <field name="model">report.inventory_advanced_reports.report_inventory_breakdown</field> | |||
|         <field name="report_type">qweb-pdf</field> | |||
|         <field name="report_name">inventory_advanced_reports.report_inventory_breakdown</field> | |||
|         <field name="report_file">inventory_advanced_reports.report_inventory_breakdown</field> | |||
|         <field name="binding_model_id" | |||
|                ref="model_inventory_age_breakdown_report"/> | |||
|         <field name="binding_type">report</field> | |||
|     </record> | |||
| <!--    Template for the report --> | |||
|     <template id="report_inventory_breakdown"> | |||
|         <t t-call="web.html_container"> | |||
|             <t t-call="web.external_layout"> | |||
|                 <div class="page"> | |||
|                     <div class="text-center"> | |||
|                         <h1>Inventory Age Breakdown Report</h1> | |||
|                     </div> | |||
|                 </div> | |||
|                 <table class="table table-condensed table-bordered table-striped"> | |||
|                     <thead> | |||
|                         <tr> | |||
|                             <th align="center"/> | |||
|                             <th align="center"/> | |||
|                             <th align="center"/> | |||
|                             <th align="center"/> | |||
|                             <th align="center"/> | |||
|                             <t t-foreach="main_header" t-as="header"> | |||
|                             <th align="center" colspan="2"><t t-esc="header"/></th> | |||
|                             </t> | |||
|                         </tr> | |||
|                         <tr> | |||
|                             <th align="center">Sl.no</th> | |||
|                             <th align="center">PRODUCT</th> | |||
|                             <th align="center">CATEGORY</th> | |||
|                             <th align="center">TOTAL STOCK</th> | |||
|                             <th align="center">STOCK VALUE</th> | |||
|                             <th align="center">STOCK</th> | |||
|                             <th align="center">VALUE</th> | |||
|                             <th align="center">STOCK</th> | |||
|                             <th align="center">VALUE</th> | |||
|                             <th align="center">STOCK</th> | |||
|                             <th align="center">VALUE</th> | |||
|                             <th align="center">STOCK</th> | |||
|                             <th align="center">VALUE</th> | |||
|                             <th align="center">STOCK</th> | |||
|                             <th align="center">VALUE</th> | |||
|                         </tr> | |||
|                     </thead> | |||
|                     <tbody> | |||
|                         <tr t-foreach="options" t-as="new"> | |||
|                             <t t-log="new"/> | |||
|                             <td> | |||
|                                 <t t-esc="new_index + 1"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['product_code_and_name']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['category_name']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['qty_available']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['stock_value']"/> | |||
|                             </td> | |||
|                             <td><t t-esc="new['age_breakdown_qty_1']"/>% | |||
|                             </td> | |||
|                             <td><t t-esc="new['age_breakdown_value_1']"/>% | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['age_breakdown_qty_2']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['age_breakdown_value_2']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['age_breakdown_qty_3']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['age_breakdown_value_3']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['age_breakdown_qty_4']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['age_breakdown_value_4']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['age_breakdown_qty_5']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['age_breakdown_value_5']"/> | |||
|                             </td> | |||
|                         </tr> | |||
|                     </tbody> | |||
|                 </table> | |||
|             </t> | |||
|         </t> | |||
|     </template> | |||
| </odoo> | |||
| @ -0,0 +1,172 @@ | |||
| # -*- coding: utf-8 -*- | |||
| ############################################################################### | |||
| # | |||
| #  Cybrosys Technologies Pvt. Ltd. | |||
| # | |||
| #  Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) | |||
| #  Author: Jumana Haseen (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 <http://www.gnu.org/licenses/>. | |||
| # | |||
| ############################################################################### | |||
| 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->>'en_US') | |||
|                             ELSE | |||
|                                 pt.name->>'en_US' | |||
|                         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->>'en_US') | |||
|                             ELSE | |||
|                                 pt.name->>'en_US' | |||
|                         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) | |||
|         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!") | |||
| @ -0,0 +1,75 @@ | |||
| <?xml version="1.0" encoding="UTF-8"?> | |||
| <odoo> | |||
|     <!--    Action for the report--> | |||
|     <record id="report_inventory_aging_action" model="ir.actions.report"> | |||
|         <field name="name">Inventory Aging Report</field> | |||
|         <field name="model">report.inventory_advanced_reports.report_inventory_aging</field> | |||
|         <field name="report_type">qweb-pdf</field> | |||
|         <field name="report_name">inventory_advanced_reports.report_inventory_aging</field> | |||
|         <field name="report_file">inventory_advanced_reports.report_inventory_aging</field> | |||
|         <field name="binding_model_id" | |||
|                ref="model_inventory_aging_report"/> | |||
|         <field name="binding_type">report</field> | |||
|     </record> | |||
| <!--    Template for the report--> | |||
|     <template id="report_inventory_aging"> | |||
|         <t t-call="web.html_container"> | |||
|             <t t-call="web.external_layout"> | |||
|                 <div class="page"> | |||
|                     <div class="text-center"> | |||
|                         <h1>Inventory Aging Report</h1> | |||
|                     </div> | |||
|                 </div> | |||
|                 <table class="table table-condensed table-bordered table-striped"> | |||
|                     <thead> | |||
|                         <tr> | |||
|                             <th align="center">Sl.no</th> | |||
|                             <th align="center">PRODUCT</th> | |||
|                             <th align="center">CATEGORY</th> | |||
|                             <th align="center">CURRENT STOCK</th> | |||
|                             <th align="center">CURRENT VALUE</th> | |||
|                             <th align="center">STOCK QUANT(%)</th> | |||
|                             <th align="center">STOCK VALUE(%)</th> | |||
|                             <th align="center">OLDEST STOCK AGE</th> | |||
|                             <th align="center">OLDEST STOCK</th> | |||
|                             <th align="center">OLDEST STOCK VALUE</th> | |||
|                         </tr> | |||
|                     </thead> | |||
|                     <tbody> | |||
|                         <tr t-foreach="options" t-as="new"> | |||
|                             <t t-log="new"/> | |||
|                             <td> | |||
|                                 <t t-esc="new_index + 1"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['product_code_and_name']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['category_name']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['qty_available']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['current_value']"/> | |||
|                             </td> | |||
|                             <td><t t-esc="new['stock_percentage']"/>% | |||
|                             </td> | |||
|                             <td><t t-esc="new['stock_value_percentage']"/>% | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['days_since_receipt']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['prev_qty_available']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['prev_value']"/> | |||
|                             </td> | |||
|                         </tr> | |||
|                     </tbody> | |||
|                 </table> | |||
|             </t> | |||
|         </t> | |||
|     </template> | |||
| </odoo> | |||
| @ -0,0 +1,199 @@ | |||
| # -*- coding: utf-8 -*- | |||
| ############################################################################### | |||
| # | |||
| #  Cybrosys Technologies Pvt. Ltd. | |||
| # | |||
| #  Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) | |||
| #  Author: Jumana Haseen (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 <http://www.gnu.org/licenses/>. | |||
| # | |||
| ############################################################################### | |||
| 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->>'en_US') | |||
|                         ELSE | |||
|                             pt.name->>'en_US' | |||
|                     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->>'en_US') | |||
|                         ELSE | |||
|                             pt.name->>'en_US' | |||
|                     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, | |||
|         } | |||
| @ -0,0 +1,91 @@ | |||
| <?xml version="1.0" encoding="UTF-8" ?> | |||
| <odoo> | |||
| <!--    Action for the report--> | |||
|     <record id="report_inventory_fsn_action" model="ir.actions.report"> | |||
|         <field name="name">Inventory FSN Report</field> | |||
|         <field name="model">report.inventory_advanced_reports.report_inventory_fsn</field> | |||
|         <field name="report_type">qweb-pdf</field> | |||
|         <field name="report_name">inventory_advanced_reports.report_inventory_fsn</field> | |||
|         <field name="report_file">inventory_advanced_reports.report_inventory_fsn</field> | |||
|         <field name="binding_model_id" | |||
|                ref="model_inventory_fsn_report"/> | |||
|         <field name="binding_type">report</field> | |||
|     </record> | |||
| <!--    Template for the report--> | |||
|     <template id="report_inventory_fsn"> | |||
|         <t t-call="web.html_container"> | |||
|             <t t-call="web.external_layout"> | |||
|                 <br/><br/><br/><br/> | |||
|                 <div class="page"> | |||
|                     <div class="text-center"> | |||
|                         <h1>Inventory FSN Report</h1> | |||
|                     </div> | |||
|                 </div> | |||
|                 <br/> | |||
|                 <br/> | |||
|                 <div> | |||
|                     <div> | |||
|                         <t t-if="data.get('start_date') and data.get('end_date')"> | |||
|                             <strong> | |||
|                                 Start Date: | |||
|                             </strong> | |||
|                             <span t-esc="data['start_date']"/> | |||
|                             <br/> | |||
|                             <strong> | |||
|                                 End Date: | |||
|                             </strong> | |||
|                             <span t-esc="data['end_date']"/> | |||
|                         </t> | |||
|                     </div> | |||
|                 </div> | |||
|                 <br/> | |||
|                 <table class="table table-condensed table-bordered table-striped"> | |||
|                     <thead> | |||
|                         <tr> | |||
|                             <th align="center">Sl.no</th> | |||
|                             <th align="center">PRODUCT</th> | |||
|                             <th align="center">CATEGORY</th> | |||
|                             <th align="center">OPENING STOCK</th> | |||
|                             <th align="center">CLOSING STOCK</th> | |||
|                             <th align="center">AVERAGE STOCK</th> | |||
|                             <th align="center">SALES</th> | |||
|                             <th align="center">TURNOVER RATIO</th> | |||
|                             <th align="center">FSN CLASSIFICATION</th> | |||
|                         </tr> | |||
|                     </thead> | |||
|                     <tbody> | |||
|                         <tr t-foreach="options" t-as="new"> | |||
|                             <td> | |||
|                                 <t t-esc="new_index + 1"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['product_code_and_name']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['category_name']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['opening_stock']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['closing_stock']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['average_stock']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['sales']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['turnover_ratio']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['fsn_classification']"/> | |||
|                             </td> | |||
|                         </tr> | |||
|                     </tbody> | |||
|                 </table> | |||
|             </t> | |||
|         </t> | |||
|     </template> | |||
| </odoo> | |||
| @ -0,0 +1,260 @@ | |||
| # -*- coding: utf-8 -*- | |||
| ############################################################################### | |||
| # | |||
| #  Cybrosys Technologies Pvt. Ltd. | |||
| # | |||
| #  Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) | |||
| #  Author: Jumana Haseen (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 <http://www.gnu.org/licenses/>. | |||
| # | |||
| ############################################################################### | |||
| 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->>'en_US') | |||
|                         ELSE | |||
|                             pt.name->>'en_US' | |||
|                     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, | |||
|         } | |||
| @ -0,0 +1,102 @@ | |||
| <?xml version="1.0" encoding="UTF-8" ?> | |||
| <odoo> | |||
| <!--    Action for the report--> | |||
|     <record id="report_inventory_fsn_xyz_action" model="ir.actions.report"> | |||
|         <field name="name">Inventory FSN XYZ Report</field> | |||
|         <field name="model"> | |||
|             report.inventory_advanced_reports.report_inventory_fsn_xyz | |||
|         </field> | |||
|         <field name="report_type">qweb-pdf</field> | |||
|         <field name="report_name">inventory_advanced_reports.report_inventory_fsn_xyz</field> | |||
|         <field name="report_file">inventory_advanced_reports.report_inventory_fsn_xyz</field> | |||
|         <field name="paperformat_id" ref="base.paperformat_euro"/> | |||
|         <field name="binding_model_id" | |||
|                ref="model_inventory_fsn_xyz_report"/> | |||
|         <field name="binding_type">report</field> | |||
|     </record> | |||
| <!--    Template for the report--> | |||
|     <template id="report_inventory_fsn_xyz"> | |||
|         <t t-call="web.html_container"> | |||
|             <t t-call="web.external_layout"> | |||
|             <br/><br/><br/><br/> | |||
|                 <div class="page"> | |||
|                     <div class="text-center"> | |||
|                         <h1>Inventory FSN-XYZ Report</h1> | |||
|                     </div> | |||
|                 </div> | |||
|                 <br/> | |||
|                 <br/> | |||
|                 <div> | |||
|                     <div> | |||
|                         <t t-if="data.get('start_date') and data.get('end_date')"> | |||
|                             <strong> | |||
|                                 Start Date: | |||
|                             </strong> | |||
|                             <span t-esc="data['start_date']"/> | |||
|                             <br/> | |||
|                             <strong> | |||
|                                 End Date: | |||
|                             </strong> | |||
|                             <span t-esc="data['end_date']"/> | |||
|                         </t> | |||
|                     </div> | |||
|                 </div> | |||
|                 <br/> | |||
|                 <table class="table table-condensed table-bordered table-striped"> | |||
|                     <thead> | |||
|                         <tr> | |||
|                             <th align="center">Sl.no</th> | |||
|                             <th align="center">PRODUCT</th> | |||
|                             <th align="center">CATEGORY</th> | |||
|                             <th align="center">AVERAGE STOCK</th> | |||
|                             <th align="center">SALES</th> | |||
|                             <th align="center">TURNOVER RATIO</th> | |||
|                             <th align="center">CURRENT STOCK</th> | |||
|                             <th align="center">STOCK VALUE</th> | |||
|                             <th align="center">FSN CLASSIFICATION</th> | |||
|                             <th align="center">XYZ CLASSIFICATION</th> | |||
|                             <th align="center">FSN-XYZ CLASSIFICATION</th> | |||
|                         </tr> | |||
|                     </thead> | |||
|                     <tbody> | |||
|                         <tr t-foreach="options" t-as="new"> | |||
|                             <td> | |||
|                                 <t t-esc="new_index + 1"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['product_code_and_name']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['category_name']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['average_stock']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['sales']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['turnover_ratio']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['current_stock']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['stock_value']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['fsn_classification']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['xyz_classification']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['combined_classification']"/> | |||
|                             </td> | |||
|                         </tr> | |||
|                     </tbody> | |||
|                 </table> | |||
|             </t> | |||
|         </t> | |||
|     </template> | |||
| </odoo> | |||
| @ -0,0 +1,274 @@ | |||
| # -*- coding: utf-8 -*- | |||
| ############################################################################### | |||
| # | |||
| #  Cybrosys Technologies Pvt. Ltd. | |||
| # | |||
| #  Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) | |||
| #  Author: Jumana Haseen (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 <http://www.gnu.org/licenses/>. | |||
| # | |||
| ############################################################################### | |||
| 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->>'en_US') | |||
|                                 ELSE | |||
|                                     pt.name->>'en_US' | |||
|                             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!") | |||
| @ -0,0 +1,158 @@ | |||
| <?xml version="1.0" encoding="UTF-8" ?> | |||
| <odoo> | |||
| <!--    Custom paper format for the report--> | |||
|     <record id="paperformat_inventory_reports" model="report.paperformat"> | |||
|         <field name="name">Over Stock</field> | |||
|         <field name="default" eval="True"/> | |||
|         <field name="format">custom</field> | |||
|         <field name="page_height">297</field> | |||
|         <field name="page_width">500</field> | |||
|         <field name="orientation">Landscape</field> | |||
|         <field name="margin_top">30</field> | |||
|         <field name="margin_bottom">23</field> | |||
|         <field name="margin_left">5</field> | |||
|         <field name="margin_right">5</field> | |||
|         <field name="header_line" eval="False"/> | |||
|         <field name="header_spacing">20</field> | |||
|         <field name="dpi">90</field> | |||
|     </record> | |||
| <!--    Action for the report --> | |||
|     <record id="report_inventory_out_of_stock_action" model="ir.actions.report"> | |||
|         <field name="name">Inventory Out Of Stock Report</field> | |||
|         <field name="model">report.inventory_advanced_reports.report_inventory_out_of_stock</field> | |||
|         <field name="report_type">qweb-pdf</field> | |||
|         <field name="report_name">inventory_advanced_reports.report_inventory_out_of_stock</field> | |||
|         <field name="report_file">inventory_advanced_reports.report_inventory_out_of_stock</field> | |||
|         <field name="binding_model_id" | |||
|                ref="model_inventory_out_of_stock_report"/> | |||
|         <field name="paperformat_id" | |||
|                ref="inventory_advanced_reports.paperformat_inventory_reports"/> | |||
|         <field name="binding_type">report</field> | |||
|     </record> | |||
| <!--    Template for the report--> | |||
|     <template id="report_inventory_out_of_stock"> | |||
|         <t t-call="web.html_container"> | |||
|             <t t-call="web.external_layout"> | |||
|                 <br/> | |||
|                 <br/> | |||
|                 <br/> | |||
|                 <br/> | |||
|                 <div class="page"> | |||
|                     <div class="text-center"> | |||
|                         <h1>Inventory Out Of Stock Report</h1> | |||
|                     </div> | |||
|                 </div> | |||
|                 <br/> | |||
|                 <br/> | |||
|                 <div> | |||
|                     <div> | |||
|                         <t t-if="data.get('start_date') and data.get('end_date')"> | |||
|                             <strong> | |||
|                                 Sales History from: | |||
|                             </strong> | |||
|                             <span t-esc="data['start_date']"/> | |||
|                             <br/> | |||
|                             <strong> | |||
|                                 Sales History Upto: | |||
|                             </strong> | |||
|                             <span t-esc="data['end_date']"/> | |||
|                             <br/> | |||
|                             <strong> | |||
|                                 Inventory Analysis For Next: | |||
|                             </strong> | |||
|                             <span t-esc="data['inventory_for_next_x_days']"/> | |||
|                             days | |||
|                         </t> | |||
|                     </div> | |||
|                 </div> | |||
|                 <br/> | |||
|                 <table class="table table-condensed table-bordered table-striped"> | |||
|                     <thead> | |||
|                         <tr> | |||
|                             <th align="center">Sl.no</th> | |||
|                             <th align="center">PRODUCT</th> | |||
|                             <th align="center">CATEGORY</th> | |||
|                             <th align="center">CURRENT STOCK</th> | |||
|                             <th align="center">INCOMING</th> | |||
|                             <th align="center">OUTGOING</th> | |||
|                             <th align="center">VIRTUAL STOCK</th> | |||
|                             <th align="center">SALES</th> | |||
|                             <th align="center">ADS</th> | |||
|                             <th align="center">DEMANDED QTY</th> | |||
|                             <th align="center">IN STOCK DAYS</th> | |||
|                             <th align="center">OUT OF STOCK DAYS</th> | |||
|                             <th align="center">OUT OF STOCK RATIO</th> | |||
|                             <th align="center">COST PRICE</th> | |||
|                             <th align="center">OUT OF STOCK QTY</th> | |||
|                             <th align="center">OUT OF STOCK QTY(%)</th> | |||
|                             <th align="center">OUT OF STOCK VALUE(%)</th> | |||
|                             <th align="center">TURNOVER RATIO(%)</th> | |||
|                             <th align="center">FSN CLASSIFICATION</th> | |||
|                         </tr> | |||
|                     </thead> | |||
|                     <tbody> | |||
|                         <tr t-foreach="options" t-as="new"> | |||
|                             <td> | |||
|                                 <t t-esc="new_index + 1"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['product_code_and_name']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['category_name']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['current_stock']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['incoming_quantity']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['outgoing_quantity']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['virtual_stock']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['sales']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['ads']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['demanded_quantity']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['in_stock_days']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['out_of_stock_days']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['out_of_stock_ratio']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['cost']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['out_of_stock_qty']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['out_of_stock_qty_percentage']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['out_of_stock_value']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['turnover_ratio']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['fsn_classification']"/> | |||
|                             </td> | |||
|                         </tr> | |||
|                     </tbody> | |||
|                 </table> | |||
|             </t> | |||
|         </t> | |||
|     </template> | |||
| </odoo> | |||
| @ -0,0 +1,302 @@ | |||
| # -*- coding: utf-8 -*- | |||
| ############################################################################### | |||
| # | |||
| #  Cybrosys Technologies Pvt. Ltd. | |||
| # | |||
| #  Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) | |||
| #  Author: Jumana Haseen (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 <http://www.gnu.org/licenses/>. | |||
| # | |||
| ############################################################################### | |||
| 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->>'en_US') | |||
|                         ELSE | |||
|                             pt.name->>'en_US' | |||
|                     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.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!") | |||
| @ -0,0 +1,153 @@ | |||
| <?xml version="1.0" encoding="UTF-8" ?> | |||
| <odoo> | |||
| <!--    Action for the report--> | |||
|     <record id="report_inventory_over_stock_action" model="ir.actions.report"> | |||
|         <field name="name">Inventory Over Stock Report</field> | |||
|         <field name="model">report.inventory_advanced_reports.report_inventory_over_stock</field> | |||
|         <field name="report_type">qweb-pdf</field> | |||
|         <field name="report_name">inventory_advanced_reports.report_inventory_over_stock</field> | |||
|         <field name="report_file">inventory_advanced_reports.report_inventory_over_stock</field> | |||
|         <field name="binding_model_id" | |||
|                ref="model_inventory_over_stock_report"/> | |||
|         <field name="paperformat_id" | |||
|                ref="inventory_advanced_reports.paperformat_inventory_reports"/> | |||
|         <field name="binding_type">report</field> | |||
|     </record> | |||
| <!--    Template for the report--> | |||
|     <template id="report_inventory_over_stock"> | |||
|         <t t-call="web.html_container"> | |||
|             <t t-call="web.external_layout"> | |||
|                 <br/> | |||
|                 <br/> | |||
|                 <br/> | |||
|                 <br/> | |||
|                 <div class="page"> | |||
|                     <div class="text-center"> | |||
|                         <h1>Inventory Over Stock Report</h1> | |||
|                     </div> | |||
|                 </div> | |||
|                 <br/> | |||
|                 <br/> | |||
|                 <div> | |||
|                     <div> | |||
|                         <t t-if="data.get('start_date') and data.get('end_date')"> | |||
|                             <strong> | |||
|                                 Sales History from: | |||
|                             </strong> | |||
|                             <span t-esc="data['start_date']"/> | |||
|                             <br/> | |||
|                             <strong> | |||
|                                 Sales History Up to: | |||
|                             </strong> | |||
|                             <span t-esc="data['end_date']"/> | |||
|                             <br/> | |||
|                             <strong> | |||
|                                 Inventory Analysis For Next: | |||
|                             </strong> | |||
|                             <span t-esc="data['inventory_for_next_x_days']"/>days | |||
|                         </t> | |||
|                     </div> | |||
|                 </div> | |||
|                 <br/> | |||
|                 <table class="table table-condensed table-bordered table-striped"> | |||
|                     <thead> | |||
|                         <tr> | |||
|                             <th align="center">Sl.no</th> | |||
|                             <th align="center">PRODUCT</th> | |||
|                             <th align="center">CATEGORY</th> | |||
|                             <th align="center">CURRENT STOCK</th> | |||
|                             <th align="center">INCOMING</th> | |||
|                             <th align="center">OUTGOING</th> | |||
|                             <th align="center">VIRTUAL STOCK</th> | |||
|                             <th align="center">SALES</th> | |||
|                             <th align="center">ADS</th> | |||
|                             <th align="center">DEMANDED QTY</th> | |||
|                             <th align="center">COVERAGE DAYS</th> | |||
|                             <th align="center">OVER STOCK QTY</th> | |||
|                             <th align="center">OVER STOCK QTY(%)</th> | |||
|                             <th align="center">OVER STOCK VALUE</th> | |||
|                             <th align="center">OVER STOCK VALUE(%)</th> | |||
|                             <th align="center">TURNOVER RATIO(%)</th> | |||
|                             <th align="center">FSN CLASSIFICATION</th> | |||
|                             <th align="center">LAST PO DATE</th> | |||
|                             <th align="center">LAST PO QTY</th> | |||
|                             <th align="center">LAST PO PRICE</th> | |||
|                             <th align="center">CURRENCY</th> | |||
|                             <th align="center">PARTNER</th> | |||
|                         </tr> | |||
|                     </thead> | |||
|                     <tbody> | |||
|                         <tr t-foreach="options" t-as="new"> | |||
|                             <td> | |||
|                                 <t t-esc="new_index + 1"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['product_code_and_name']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['category_name']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['current_stock']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['incoming_quantity']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['outgoing_quantity']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['virtual_stock']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['sales']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['ads']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['demanded_quantity']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['in_stock_days']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['over_stock_qty']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['over_stock_qty_percentage']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['over_stock_value']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['over_stock_value_percentage']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['turnover_ratio']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['fsn_classification']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['po_date']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['po_qty']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['po_price_total']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['po_currency']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['po_partner']"/> | |||
|                             </td> | |||
|                         </tr> | |||
|                     </tbody> | |||
|                 </table> | |||
|             </t> | |||
|         </t> | |||
|     </template> | |||
| </odoo> | |||
| @ -0,0 +1,201 @@ | |||
| # -*- coding: utf-8 -*- | |||
| ############################################################################### | |||
| # | |||
| #  Cybrosys Technologies Pvt. Ltd. | |||
| # | |||
| #  Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) | |||
| #  Author: Jumana Haseen (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 <http://www.gnu.org/licenses/>. | |||
| # | |||
| ############################################################################### | |||
| 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->>'en_US') | |||
|                             ELSE | |||
|                                 pt.name->>'en_US' | |||
|                             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!") | |||
| @ -0,0 +1,140 @@ | |||
| <?xml version="1.0" encoding="UTF-8" ?> | |||
| <odoo> | |||
|     <!--    Action for the report--> | |||
|     <record id="report_inventory_stock_movement_action" | |||
|             model="ir.actions.report"> | |||
|         <field name="name">Inventory Stock Movement Report</field> | |||
|         <field name="model">report.inventory_advanced_reports.report_inventory_movement</field> | |||
|         <field name="report_type">qweb-pdf</field> | |||
|         <field name="report_name">inventory_advanced_reports.report_inventory_movement</field> | |||
|         <field name="report_file">inventory_advanced_reports.report_inventory_movement</field> | |||
|         <field name="binding_model_id" | |||
|                ref="model_inventory_stock_movement_report"/> | |||
|         <field name="paperformat_id" | |||
|                ref="inventory_advanced_reports.paperformat_inventory_reports"/> | |||
|         <field name="binding_type">report</field> | |||
|     </record> | |||
|     <!--    Template for the report--> | |||
|     <template id="report_inventory_movement"> | |||
|         <t t-call="web.html_container"> | |||
|             <t t-call="web.external_layout"> | |||
|                 <br/> | |||
|                 <br/> | |||
|                 <br/> | |||
|                 <br/> | |||
|                 <br/> | |||
|                 <div class="page"> | |||
|                     <div class="text-center"> | |||
|                         <h1>Inventory Stock Movement Report</h1> | |||
|                     </div> | |||
|                 </div> | |||
|                 <br/> | |||
|                 <br/> | |||
|                 <div> | |||
|                     <div> | |||
|                         <t t-if="data.get('start_date') and data.get('end_date') and not data['up_to_certain_date']"> | |||
|                             <strong> | |||
|                                 Date from: | |||
|                             </strong> | |||
|                             <span t-esc="data['start_date']"/> | |||
|                             <br/> | |||
|                             <strong> | |||
|                                 Date to: | |||
|                             </strong> | |||
|                             <span t-esc="data['end_date']"/> | |||
|                         </t> | |||
|                         <t t-if="data.get('up_to_certain_date')"> | |||
|                             <strong> | |||
|                                 Stock Movements Up To: | |||
|                             </strong> | |||
|                             <span t-esc="data['up_to_certain_date']"/> | |||
|                         </t> | |||
|                     </div> | |||
|                 </div> | |||
|                 <br/> | |||
|                 <table class="table table-condensed table-bordered table-striped"> | |||
|                     <thead> | |||
|                         <tr> | |||
|                             <th align="center">Sl.no</th> | |||
|                             <th align="center">COMPANY</th> | |||
|                             <th align="center">PRODUCT</th> | |||
|                             <th align="center">CATEGORY</th> | |||
|                             <th align="center">OPENING STOCK</th> | |||
|                             <th align="center">SALES</th> | |||
|                             <th align="center">SALES RETURN</th> | |||
|                             <th align="center">PURCHASE</th> | |||
|                             <th align="center">PURCHASE RETURN</th> | |||
|                             <th align="center">INTERNAL IN</th> | |||
|                             <th align="center">INTERNAL OUT</th> | |||
|                             <th align="center">ADJUSTMENT IN</th> | |||
|                             <th align="center">ADJUSTMENT OUT</th> | |||
|                             <th align="center">PRODUCTION IN</th> | |||
|                             <th align="center">PRODUCTION OUT</th> | |||
|                             <th align="center">TRANSIT IN</th> | |||
|                             <th align="center">TRANSIT OUT</th> | |||
|                             <th align="center">CLOSING STOCK</th> | |||
|                         </tr> | |||
|                     </thead> | |||
|                     <tbody> | |||
|                         <tr t-foreach="options" t-as="new"> | |||
|                             <td> | |||
|                                 <t t-esc="new_index + 1"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['company_name']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['product_code_and_name']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['category_name']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['opening_stock']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['sales']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['sales_return']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['purchase']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['purchase_return']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['internal_in']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['internal_out']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['adj_in']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['adj_out']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['production_in']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['production_out']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['transit_in']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['transit_out']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['closing_stock']"/> | |||
|                             </td> | |||
|                         </tr> | |||
|                     </tbody> | |||
|                 </table> | |||
|             </t> | |||
|         </t> | |||
|     </template> | |||
| </odoo> | |||
| @ -0,0 +1,142 @@ | |||
| # -*- coding: utf-8 -*- | |||
| ############################################################################### | |||
| # | |||
| #  Cybrosys Technologies Pvt. Ltd. | |||
| # | |||
| #  Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) | |||
| #  Author: Jumana Haseen (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 <http://www.gnu.org/licenses/>. | |||
| # | |||
| ############################################################################### | |||
| 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->>'en_US') | |||
|                 ELSE | |||
|                     pt.name->>'en_US' | |||
|             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->>'en_US') | |||
|                 ELSE | |||
|                     pt.name->>'en_US' | |||
|             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!") | |||
| @ -0,0 +1,67 @@ | |||
| <?xml version="1.0" encoding="UTF-8"?> | |||
| <odoo> | |||
|     <!--    action for the report--> | |||
|     <record id="report_inventory_xyz_action" model="ir.actions.report"> | |||
|         <field name="name">Inventory XYZ Report</field> | |||
|         <field name="model">report.inventory_advanced_reports.report_inventory_xyz</field> | |||
|         <field name="report_type">qweb-pdf</field> | |||
|         <field name="report_name">inventory_advanced_reports.report_inventory_xyz</field> | |||
|         <field name="report_file">inventory_advanced_reports.report_inventory_xyz</field> | |||
|         <field name="binding_model_id" | |||
|                ref="model_inventory_aging_report"/> | |||
|         <field name="binding_type">report</field> | |||
|     </record> | |||
| <!--    Template for the report--> | |||
|     <template id="report_inventory_xyz"> | |||
|         <t t-call="web.html_container"> | |||
|             <t t-call="web.external_layout"> | |||
|                 <div class="page"> | |||
|                     <div class="text-center"> | |||
|                         <h1>Inventory XYZ Report</h1> | |||
|                     </div> | |||
|                 </div> | |||
|                 <table class="table table-condensed table-bordered table-striped"> | |||
|                     <thead> | |||
|                         <tr> | |||
|                             <th align="center">Sl.no</th> | |||
|                             <th align="center">PRODUCT</th> | |||
|                             <th align="center">CATEGORY</th> | |||
|                             <th align="center">CURRENT STOCK</th> | |||
|                             <th align="center">STOCK VALUE</th> | |||
|                             <th align="center">STOCK VALUE(%)</th> | |||
|                             <th align="center">CUMULATIVE STOCK(%)</th> | |||
|                             <th align="center">XYZ CLASSIFICATION</th> | |||
|                         </tr> | |||
|                     </thead> | |||
|                     <tbody> | |||
|                         <tr t-foreach="options" t-as="new"> | |||
|                             <t t-log="new"/> | |||
|                             <td> | |||
|                                 <t t-esc="new_index + 1"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['product_code_and_name']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['category_name']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['current_stock']"/> | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['stock_value']"/> | |||
|                             </td> | |||
|                             <td><t t-esc="new['stock_percentage']"/>% | |||
|                             </td> | |||
|                             <td><t t-esc="new['cumulative_stock_percentage']"/>% | |||
|                             </td> | |||
|                             <td> | |||
|                                 <t t-esc="new['xyz_classification']"/> | |||
|                             </td> | |||
|                         </tr> | |||
|                     </tbody> | |||
|                 </table> | |||
|             </t> | |||
|         </t> | |||
|     </template> | |||
| </odoo> | |||
| 
 | 
| After Width: | Height: | Size: 3.6 KiB | 
| After Width: | Height: | Size: 310 B | 
| After Width: | Height: | Size: 1.3 KiB | 
| After Width: | Height: | Size: 1.4 KiB | 
| After Width: | Height: | Size: 576 B | 
| After Width: | Height: | Size: 733 B | 
| After Width: | Height: | Size: 911 B | 
| After Width: | Height: | Size: 1.1 KiB | 
| After Width: | Height: | Size: 1.2 KiB | 
| After Width: | Height: | Size: 673 B | 
| After Width: | Height: | Size: 878 B | 
| After Width: | Height: | Size: 653 B | 
| After Width: | Height: | Size: 905 B | 
| After Width: | Height: | Size: 839 B | 
| After Width: | Height: | Size: 427 B | 
| After Width: | Height: | Size: 627 B | 
| After Width: | Height: | Size: 1.2 KiB | 
| After Width: | Height: | Size: 988 B | 
| After Width: | Height: | Size: 1.2 KiB | 
| After Width: | Height: | Size: 1.5 KiB | 
| After Width: | Height: | Size: 1.1 KiB | 
| After Width: | Height: | Size: 1.9 KiB | 
| After Width: | Height: | Size: 1.1 KiB | 
| After Width: | Height: | Size: 2.1 KiB | 
| After Width: | Height: | Size: 4.4 KiB | 
| After Width: | Height: | Size: 589 B | 
| After Width: | Height: | Size: 3.4 KiB | 
| After Width: | Height: | Size: 1.7 KiB | 
| After Width: | Height: | Size: 2.3 KiB | 
| After Width: | Height: | Size: 1.5 KiB | 
| After Width: | Height: | Size: 967 B | 
| After Width: | Height: | Size: 1.6 KiB | 
| After Width: | Height: | Size: 3.8 KiB | 
| After Width: | Height: | Size: 5.0 KiB | 
| After Width: | Height: | Size: 55 KiB | 
| After Width: | Height: | Size: 75 KiB | 
| After Width: | Height: | Size: 45 KiB | 
| After Width: | Height: | Size: 49 KiB | 
| After Width: | Height: | Size: 51 KiB | 
| After Width: | Height: | Size: 47 KiB | 
| After Width: | Height: | Size: 80 KiB | 
| After Width: | Height: | Size: 75 KiB | 
| After Width: | Height: | Size: 49 KiB | 
| After Width: | Height: | Size: 74 KiB | 
| After Width: | Height: | Size: 39 KiB | 
| After Width: | Height: | Size: 76 KiB | 
| After Width: | Height: | Size: 68 KiB | 
| After Width: | Height: | Size: 48 KiB | 
| After Width: | Height: | Size: 68 KiB | 
| After Width: | Height: | Size: 25 KiB | 
| After Width: | Height: | Size: 70 KiB | 
| After Width: | Height: | Size: 78 KiB | 
| After Width: | Height: | Size: 62 KiB | 
| After Width: | Height: | Size: 90 KiB | 
| After Width: | Height: | Size: 80 KiB | 
| After Width: | Height: | Size: 84 KiB | 
| After Width: | Height: | Size: 89 KiB | 
| After Width: | Height: | Size: 85 KiB | 
| After Width: | Height: | Size: 92 KiB | 
| After Width: | Height: | Size: 88 KiB | 
| After Width: | Height: | Size: 137 KiB | 
| After Width: | Height: | Size: 88 KiB | 
| After Width: | Height: | Size: 286 KiB | 
| After Width: | Height: | Size: 53 KiB | 
| After Width: | Height: | Size: 83 KiB | 
| After Width: | Height: | Size: 99 KiB | 
| After Width: | Height: | Size: 61 KiB | 
| After Width: | Height: | Size: 99 KiB | 
| After Width: | Height: | Size: 45 KiB | 
| After Width: | Height: | Size: 99 KiB | 
| After Width: | Height: | Size: 102 KiB | 
| After Width: | Height: | Size: 53 KiB | 
| After Width: | Height: | Size: 75 KiB | 
| After Width: | Height: | Size: 83 KiB | 
| After Width: | Height: | Size: 103 KiB | 
| After Width: | Height: | Size: 122 KiB |