Browse Source

FEB 27: [ADD] Initial commit 'inventory_advanced_reports'

pull/370/head
Cybrosys Technologies 4 months ago
parent
commit
c258e89f99
  1. 51
      inventory_advanced_reports/README.rst
  2. 24
      inventory_advanced_reports/__init__.py
  3. 71
      inventory_advanced_reports/__manifest__.py
  4. 22
      inventory_advanced_reports/controllers/__init__.py
  5. 57
      inventory_advanced_reports/controllers/inventory_advanced_reports.py
  6. 6
      inventory_advanced_reports/doc/RELEASE_NOTES.md
  7. 29
      inventory_advanced_reports/report/__init__.py
  8. 180
      inventory_advanced_reports/report/age_breakdown_report.py
  9. 106
      inventory_advanced_reports/report/age_breakdown_report_views.xml
  10. 172
      inventory_advanced_reports/report/aging_report.py
  11. 75
      inventory_advanced_reports/report/aging_report_views.xml
  12. 199
      inventory_advanced_reports/report/fsn_report.py
  13. 91
      inventory_advanced_reports/report/fsn_report_views.xml
  14. 260
      inventory_advanced_reports/report/fsn_xyz_report.py
  15. 102
      inventory_advanced_reports/report/fsn_xyz_report_views.xml
  16. 274
      inventory_advanced_reports/report/out_of_stock_report.py
  17. 158
      inventory_advanced_reports/report/out_of_stock_report_views.xml
  18. 302
      inventory_advanced_reports/report/over_stock_report.py
  19. 153
      inventory_advanced_reports/report/over_stock_report_views.xml
  20. 201
      inventory_advanced_reports/report/stock_movement_report.py
  21. 140
      inventory_advanced_reports/report/stock_movement_report_views.xml
  22. 142
      inventory_advanced_reports/report/xyz_report.py
  23. 67
      inventory_advanced_reports/report/xyz_report_views.xml
  24. 16
      inventory_advanced_reports/security/ir.model.access.csv
  25. BIN
      inventory_advanced_reports/static/description/assets/icons/check.png
  26. BIN
      inventory_advanced_reports/static/description/assets/icons/chevron.png
  27. BIN
      inventory_advanced_reports/static/description/assets/icons/cogs.png
  28. BIN
      inventory_advanced_reports/static/description/assets/icons/consultation.png
  29. BIN
      inventory_advanced_reports/static/description/assets/icons/ecom-black.png
  30. BIN
      inventory_advanced_reports/static/description/assets/icons/education-black.png
  31. BIN
      inventory_advanced_reports/static/description/assets/icons/hotel-black.png
  32. BIN
      inventory_advanced_reports/static/description/assets/icons/license.png
  33. BIN
      inventory_advanced_reports/static/description/assets/icons/lifebuoy.png
  34. BIN
      inventory_advanced_reports/static/description/assets/icons/manufacturing-black.png
  35. BIN
      inventory_advanced_reports/static/description/assets/icons/pos-black.png
  36. BIN
      inventory_advanced_reports/static/description/assets/icons/puzzle.png
  37. BIN
      inventory_advanced_reports/static/description/assets/icons/restaurant-black.png
  38. BIN
      inventory_advanced_reports/static/description/assets/icons/service-black.png
  39. BIN
      inventory_advanced_reports/static/description/assets/icons/trading-black.png
  40. BIN
      inventory_advanced_reports/static/description/assets/icons/training.png
  41. BIN
      inventory_advanced_reports/static/description/assets/icons/update.png
  42. BIN
      inventory_advanced_reports/static/description/assets/icons/user.png
  43. BIN
      inventory_advanced_reports/static/description/assets/icons/wrench.png
  44. BIN
      inventory_advanced_reports/static/description/assets/misc/categories.png
  45. BIN
      inventory_advanced_reports/static/description/assets/misc/check-box.png
  46. BIN
      inventory_advanced_reports/static/description/assets/misc/compass.png
  47. BIN
      inventory_advanced_reports/static/description/assets/misc/corporate.png
  48. BIN
      inventory_advanced_reports/static/description/assets/misc/customer-support.png
  49. BIN
      inventory_advanced_reports/static/description/assets/misc/cybrosys-logo.png
  50. BIN
      inventory_advanced_reports/static/description/assets/misc/features.png
  51. BIN
      inventory_advanced_reports/static/description/assets/misc/logo.png
  52. BIN
      inventory_advanced_reports/static/description/assets/misc/pictures.png
  53. BIN
      inventory_advanced_reports/static/description/assets/misc/pie-chart.png
  54. BIN
      inventory_advanced_reports/static/description/assets/misc/reports-icon.png
  55. BIN
      inventory_advanced_reports/static/description/assets/misc/right-arrow.png
  56. BIN
      inventory_advanced_reports/static/description/assets/misc/star.png
  57. BIN
      inventory_advanced_reports/static/description/assets/misc/support.png
  58. BIN
      inventory_advanced_reports/static/description/assets/misc/whatsapp.png
  59. BIN
      inventory_advanced_reports/static/description/assets/modules/1.png
  60. BIN
      inventory_advanced_reports/static/description/assets/modules/2.png
  61. BIN
      inventory_advanced_reports/static/description/assets/modules/3.png
  62. BIN
      inventory_advanced_reports/static/description/assets/modules/4.png
  63. BIN
      inventory_advanced_reports/static/description/assets/modules/5.png
  64. BIN
      inventory_advanced_reports/static/description/assets/modules/6.png
  65. BIN
      inventory_advanced_reports/static/description/assets/screenshots/AGE BREAKDOWN 1.png
  66. BIN
      inventory_advanced_reports/static/description/assets/screenshots/AGE BREAKDOWN 2.png
  67. BIN
      inventory_advanced_reports/static/description/assets/screenshots/AGE BREAKDOWN 3.png
  68. BIN
      inventory_advanced_reports/static/description/assets/screenshots/AGE BREAKDOWN 4.png
  69. BIN
      inventory_advanced_reports/static/description/assets/screenshots/AGE BREAKDOWN 5.png
  70. BIN
      inventory_advanced_reports/static/description/assets/screenshots/AGING 0.png
  71. BIN
      inventory_advanced_reports/static/description/assets/screenshots/AGING 1.png
  72. BIN
      inventory_advanced_reports/static/description/assets/screenshots/AGING 2.png
  73. BIN
      inventory_advanced_reports/static/description/assets/screenshots/AGING 3.png
  74. BIN
      inventory_advanced_reports/static/description/assets/screenshots/AGING 4.png
  75. BIN
      inventory_advanced_reports/static/description/assets/screenshots/AGING 5.png
  76. BIN
      inventory_advanced_reports/static/description/assets/screenshots/AGING 6.png
  77. BIN
      inventory_advanced_reports/static/description/assets/screenshots/AGING 7.png
  78. BIN
      inventory_advanced_reports/static/description/assets/screenshots/AGING 8.png
  79. BIN
      inventory_advanced_reports/static/description/assets/screenshots/FSN 1.png
  80. BIN
      inventory_advanced_reports/static/description/assets/screenshots/FSN 10.png
  81. BIN
      inventory_advanced_reports/static/description/assets/screenshots/FSN 2.png
  82. BIN
      inventory_advanced_reports/static/description/assets/screenshots/FSN 3.png
  83. BIN
      inventory_advanced_reports/static/description/assets/screenshots/FSN 4.png
  84. BIN
      inventory_advanced_reports/static/description/assets/screenshots/FSN 5.png
  85. BIN
      inventory_advanced_reports/static/description/assets/screenshots/FSN 6.png
  86. BIN
      inventory_advanced_reports/static/description/assets/screenshots/FSN 7.png
  87. BIN
      inventory_advanced_reports/static/description/assets/screenshots/FSN 8.png
  88. BIN
      inventory_advanced_reports/static/description/assets/screenshots/FSN 9.png
  89. BIN
      inventory_advanced_reports/static/description/assets/screenshots/FSN XYZ 1.png
  90. BIN
      inventory_advanced_reports/static/description/assets/screenshots/FSN XYZ 2.png
  91. BIN
      inventory_advanced_reports/static/description/assets/screenshots/FSN XYZ 3.png
  92. BIN
      inventory_advanced_reports/static/description/assets/screenshots/FSN XYZ 4.png
  93. BIN
      inventory_advanced_reports/static/description/assets/screenshots/FSN XYZ 5.png
  94. BIN
      inventory_advanced_reports/static/description/assets/screenshots/FSN XYZ 6.png
  95. BIN
      inventory_advanced_reports/static/description/assets/screenshots/FSN XYZ 7.png
  96. BIN
      inventory_advanced_reports/static/description/assets/screenshots/FSN XYZ 8.png
  97. BIN
      inventory_advanced_reports/static/description/assets/screenshots/FSN XYZ 9.png
  98. BIN
      inventory_advanced_reports/static/description/assets/screenshots/OUT OF STOCK 1.png
  99. BIN
      inventory_advanced_reports/static/description/assets/screenshots/OUT OF STOCK 2.png
  100. BIN
      inventory_advanced_reports/static/description/assets/screenshots/OUT OF STOCK 3.png

51
inventory_advanced_reports/README.rst

@ -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>`__

24
inventory_advanced_reports/__init__.py

@ -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

71
inventory_advanced_reports/__manifest__.py

@ -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,
}

22
inventory_advanced_reports/controllers/__init__.py

@ -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

57
inventory_advanced_reports/controllers/inventory_advanced_reports.py

@ -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)))

6
inventory_advanced_reports/doc/RELEASE_NOTES.md

@ -0,0 +1,6 @@
## Module <inventory_advanced_reports>
#### 10.01.2025
#### Version 17.0.1.0.0
##### ADD
- Initial Commit for Advanced Inventory Reports

29
inventory_advanced_reports/report/__init__.py

@ -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

180
inventory_advanced_reports/report/age_breakdown_report.py

@ -0,0 +1,180 @@
# -*- coding: utf-8 -*-
###############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2024-TODAY Cybrosys Technologies(<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)]

106
inventory_advanced_reports/report/age_breakdown_report_views.xml

@ -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>

172
inventory_advanced_reports/report/aging_report.py

@ -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!")

75
inventory_advanced_reports/report/aging_report_views.xml

@ -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>

199
inventory_advanced_reports/report/fsn_report.py

@ -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,
}

91
inventory_advanced_reports/report/fsn_report_views.xml

@ -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>

260
inventory_advanced_reports/report/fsn_xyz_report.py

@ -0,0 +1,260 @@
# -*- coding: utf-8 -*-
###############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2024-TODAY Cybrosys Technologies(<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,
}

102
inventory_advanced_reports/report/fsn_xyz_report_views.xml

@ -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>

274
inventory_advanced_reports/report/out_of_stock_report.py

@ -0,0 +1,274 @@
# -*- coding: utf-8 -*-
###############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2024-TODAY Cybrosys Technologies(<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!")

158
inventory_advanced_reports/report/out_of_stock_report_views.xml

@ -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>

302
inventory_advanced_reports/report/over_stock_report.py

@ -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!")

153
inventory_advanced_reports/report/over_stock_report_views.xml

@ -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>

201
inventory_advanced_reports/report/stock_movement_report.py

@ -0,0 +1,201 @@
# -*- coding: utf-8 -*-
###############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2024-TODAY Cybrosys Technologies(<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!")

140
inventory_advanced_reports/report/stock_movement_report_views.xml

@ -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>

142
inventory_advanced_reports/report/xyz_report.py

@ -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!")

67
inventory_advanced_reports/report/xyz_report_views.xml

@ -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>

16
inventory_advanced_reports/security/ir.model.access.csv

@ -0,0 +1,16 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_inventory_aging_report_user,access.inventory.aging.report.user,model_inventory_aging_report,base.group_user,1,1,1,1
access_inventory_age_data_report_user,access.inventory.age.data.report.user,model_inventory_aging_data_report,base.group_user,1,1,1,1
access_inventory_fsn_report_user,access.inventory.fsn.report.user,model_inventory_fsn_report,base.group_user,1,1,1,1
access_inventory_aging_data_report_user,access.inventory.aging.data.report.user,model_inventory_aging_data_report,base.group_user,1,1,1,1
access_inventory_fsn_data_report_user,access.inventory.fsn.data.report.user,model_inventory_fsn_data_report,base.group_user,1,1,1,1
access_inventory_xyz_report_user,access.inventory.xyz.report.user,model_inventory_xyz_report,base.group_user,1,1,1,1
access_inventory_xyz_data_report_user,access.inventory.xyz.data.report.user,model_inventory_xyz_data_report,base.group_user,1,1,1,1
access_inventory_fsn_xyz_report_user,access.inventory.fsn.xyz.report.user,model_inventory_fsn_xyz_report,base.group_user,1,1,1,1
access_inventory_fsn_xyz_data_report_user,access.inventory.fsn.xyz.data.report.user,model_inventory_fsn_xyz_data_report,base.group_user,1,1,1,1
access_inventory_out_of_stock_report_user,access.inventory.out.of.stock.report.user,model_inventory_out_of_stock_report,base.group_user,1,1,1,1
access_inventory_out_of_stock_data_report_user,access.inventory.out.of.stock.data.report.user,model_inventory_out_of_stock_data_report,base.group_user,1,1,1,1
access_inventory_age_breakdown_report_user,access.inventory.age.breakdown.report.user,model_inventory_age_breakdown_report,base.group_user,1,1,1,1
access_inventory_over_stock_report_user,access.inventory.over.stock.report.user,model_inventory_over_stock_report,base.group_user,1,1,1,1
access_inventory_over_stock_data_report_user,access.inventory.over.stock.data.report.user,model_inventory_over_stock_data_report,base.group_user,1,1,1,1
access_inventory_stock_movement_report_user,access.inventory.stock.movement.report.user,model_inventory_stock_movement_report,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_inventory_aging_report_user access.inventory.aging.report.user model_inventory_aging_report base.group_user 1 1 1 1
3 access_inventory_age_data_report_user access.inventory.age.data.report.user model_inventory_aging_data_report base.group_user 1 1 1 1
4 access_inventory_fsn_report_user access.inventory.fsn.report.user model_inventory_fsn_report base.group_user 1 1 1 1
5 access_inventory_aging_data_report_user access.inventory.aging.data.report.user model_inventory_aging_data_report base.group_user 1 1 1 1
6 access_inventory_fsn_data_report_user access.inventory.fsn.data.report.user model_inventory_fsn_data_report base.group_user 1 1 1 1
7 access_inventory_xyz_report_user access.inventory.xyz.report.user model_inventory_xyz_report base.group_user 1 1 1 1
8 access_inventory_xyz_data_report_user access.inventory.xyz.data.report.user model_inventory_xyz_data_report base.group_user 1 1 1 1
9 access_inventory_fsn_xyz_report_user access.inventory.fsn.xyz.report.user model_inventory_fsn_xyz_report base.group_user 1 1 1 1
10 access_inventory_fsn_xyz_data_report_user access.inventory.fsn.xyz.data.report.user model_inventory_fsn_xyz_data_report base.group_user 1 1 1 1
11 access_inventory_out_of_stock_report_user access.inventory.out.of.stock.report.user model_inventory_out_of_stock_report base.group_user 1 1 1 1
12 access_inventory_out_of_stock_data_report_user access.inventory.out.of.stock.data.report.user model_inventory_out_of_stock_data_report base.group_user 1 1 1 1
13 access_inventory_age_breakdown_report_user access.inventory.age.breakdown.report.user model_inventory_age_breakdown_report base.group_user 1 1 1 1
14 access_inventory_over_stock_report_user access.inventory.over.stock.report.user model_inventory_over_stock_report base.group_user 1 1 1 1
15 access_inventory_over_stock_data_report_user access.inventory.over.stock.data.report.user model_inventory_over_stock_data_report base.group_user 1 1 1 1
16 access_inventory_stock_movement_report_user access.inventory.stock.movement.report.user model_inventory_stock_movement_report base.group_user 1 1 1 1

BIN
inventory_advanced_reports/static/description/assets/icons/check.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

BIN
inventory_advanced_reports/static/description/assets/icons/chevron.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 B

BIN
inventory_advanced_reports/static/description/assets/icons/cogs.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
inventory_advanced_reports/static/description/assets/icons/consultation.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
inventory_advanced_reports/static/description/assets/icons/ecom-black.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 576 B

BIN
inventory_advanced_reports/static/description/assets/icons/education-black.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 733 B

BIN
inventory_advanced_reports/static/description/assets/icons/hotel-black.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 911 B

BIN
inventory_advanced_reports/static/description/assets/icons/license.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
inventory_advanced_reports/static/description/assets/icons/lifebuoy.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
inventory_advanced_reports/static/description/assets/icons/manufacturing-black.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 673 B

BIN
inventory_advanced_reports/static/description/assets/icons/pos-black.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 878 B

BIN
inventory_advanced_reports/static/description/assets/icons/puzzle.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 653 B

BIN
inventory_advanced_reports/static/description/assets/icons/restaurant-black.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 905 B

BIN
inventory_advanced_reports/static/description/assets/icons/service-black.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 839 B

BIN
inventory_advanced_reports/static/description/assets/icons/trading-black.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 427 B

BIN
inventory_advanced_reports/static/description/assets/icons/training.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 627 B

BIN
inventory_advanced_reports/static/description/assets/icons/update.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
inventory_advanced_reports/static/description/assets/icons/user.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 988 B

BIN
inventory_advanced_reports/static/description/assets/icons/wrench.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
inventory_advanced_reports/static/description/assets/misc/categories.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
inventory_advanced_reports/static/description/assets/misc/check-box.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
inventory_advanced_reports/static/description/assets/misc/compass.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
inventory_advanced_reports/static/description/assets/misc/corporate.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
inventory_advanced_reports/static/description/assets/misc/customer-support.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
inventory_advanced_reports/static/description/assets/misc/cybrosys-logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

BIN
inventory_advanced_reports/static/description/assets/misc/features.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 589 B

BIN
inventory_advanced_reports/static/description/assets/misc/logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
inventory_advanced_reports/static/description/assets/misc/pictures.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
inventory_advanced_reports/static/description/assets/misc/pie-chart.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
inventory_advanced_reports/static/description/assets/misc/reports-icon.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
inventory_advanced_reports/static/description/assets/misc/right-arrow.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 967 B

BIN
inventory_advanced_reports/static/description/assets/misc/star.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
inventory_advanced_reports/static/description/assets/misc/support.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

BIN
inventory_advanced_reports/static/description/assets/misc/whatsapp.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

BIN
inventory_advanced_reports/static/description/assets/modules/1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

BIN
inventory_advanced_reports/static/description/assets/modules/2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

BIN
inventory_advanced_reports/static/description/assets/modules/3.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

BIN
inventory_advanced_reports/static/description/assets/modules/4.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

BIN
inventory_advanced_reports/static/description/assets/modules/5.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

BIN
inventory_advanced_reports/static/description/assets/modules/6.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

BIN
inventory_advanced_reports/static/description/assets/screenshots/AGE BREAKDOWN 1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

BIN
inventory_advanced_reports/static/description/assets/screenshots/AGE BREAKDOWN 2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

BIN
inventory_advanced_reports/static/description/assets/screenshots/AGE BREAKDOWN 3.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

BIN
inventory_advanced_reports/static/description/assets/screenshots/AGE BREAKDOWN 4.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

BIN
inventory_advanced_reports/static/description/assets/screenshots/AGE BREAKDOWN 5.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

BIN
inventory_advanced_reports/static/description/assets/screenshots/AGING 0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

BIN
inventory_advanced_reports/static/description/assets/screenshots/AGING 1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

BIN
inventory_advanced_reports/static/description/assets/screenshots/AGING 2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

BIN
inventory_advanced_reports/static/description/assets/screenshots/AGING 3.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

BIN
inventory_advanced_reports/static/description/assets/screenshots/AGING 4.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
inventory_advanced_reports/static/description/assets/screenshots/AGING 5.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

BIN
inventory_advanced_reports/static/description/assets/screenshots/AGING 6.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

BIN
inventory_advanced_reports/static/description/assets/screenshots/AGING 7.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

BIN
inventory_advanced_reports/static/description/assets/screenshots/AGING 8.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

BIN
inventory_advanced_reports/static/description/assets/screenshots/FSN 1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

BIN
inventory_advanced_reports/static/description/assets/screenshots/FSN 10.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

BIN
inventory_advanced_reports/static/description/assets/screenshots/FSN 2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

BIN
inventory_advanced_reports/static/description/assets/screenshots/FSN 3.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

BIN
inventory_advanced_reports/static/description/assets/screenshots/FSN 4.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

BIN
inventory_advanced_reports/static/description/assets/screenshots/FSN 5.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

BIN
inventory_advanced_reports/static/description/assets/screenshots/FSN 6.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

BIN
inventory_advanced_reports/static/description/assets/screenshots/FSN 7.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

BIN
inventory_advanced_reports/static/description/assets/screenshots/FSN 8.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 286 KiB

BIN
inventory_advanced_reports/static/description/assets/screenshots/FSN 9.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

BIN
inventory_advanced_reports/static/description/assets/screenshots/FSN XYZ 1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

BIN
inventory_advanced_reports/static/description/assets/screenshots/FSN XYZ 2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

BIN
inventory_advanced_reports/static/description/assets/screenshots/FSN XYZ 3.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

BIN
inventory_advanced_reports/static/description/assets/screenshots/FSN XYZ 4.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

BIN
inventory_advanced_reports/static/description/assets/screenshots/FSN XYZ 5.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

BIN
inventory_advanced_reports/static/description/assets/screenshots/FSN XYZ 6.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

BIN
inventory_advanced_reports/static/description/assets/screenshots/FSN XYZ 7.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

BIN
inventory_advanced_reports/static/description/assets/screenshots/FSN XYZ 8.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

BIN
inventory_advanced_reports/static/description/assets/screenshots/FSN XYZ 9.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

BIN
inventory_advanced_reports/static/description/assets/screenshots/OUT OF STOCK 1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

BIN
inventory_advanced_reports/static/description/assets/screenshots/OUT OF STOCK 2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

BIN
inventory_advanced_reports/static/description/assets/screenshots/OUT OF STOCK 3.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save