@ -0,0 +1,49 @@ |
|||||
|
.. image:: https://img.shields.io/badge/license-LGPL--3-green.svg |
||||
|
:target: https://www.gnu.org/licenses/lgpl-3.0-standalone.html |
||||
|
:alt: License: LGPL-3 |
||||
|
|
||||
|
Inventory Advanced Reports |
||||
|
========================== |
||||
|
Helps to manage different types of Inventory Reports like FSN Report, Out Of Stock Report, etc. |
||||
|
|
||||
|
Configuration |
||||
|
============= |
||||
|
- No additional configuration required. |
||||
|
|
||||
|
License |
||||
|
------- |
||||
|
Lesser General Public License, Version 3 (LGPL v3). |
||||
|
(https://www.gnu.org/licenses/lgpl-3.0-standalone.html) |
||||
|
|
||||
|
Company |
||||
|
------- |
||||
|
* `Cybrosys Techno Solutions <https://cybrosys.com/>`__ |
||||
|
|
||||
|
Credits |
||||
|
------- |
||||
|
Developer: (V15) Gayathri V, |
||||
|
(V16) Anusha C |
||||
|
|
||||
|
Contact : odoo@cybrosys.com |
||||
|
|
||||
|
Contacts |
||||
|
-------- |
||||
|
* Mail Contact : odoo@cybrosys.com |
||||
|
* Website : https://cybrosys.com |
||||
|
|
||||
|
Bug Tracker |
||||
|
----------- |
||||
|
Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. |
||||
|
|
||||
|
Maintainer |
||||
|
========== |
||||
|
.. image:: https://cybrosys.com/images/logo.png |
||||
|
:target: https://cybrosys.com |
||||
|
|
||||
|
This module is maintained by Cybrosys Technologies. |
||||
|
|
||||
|
For support and more information, please visit https://www.cybrosys.com |
||||
|
|
||||
|
Further information |
||||
|
=================== |
||||
|
HTML Description: `<static/description/index.html>`__ |
@ -0,0 +1,24 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################### |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
||||
|
# Author: Gayathri V (odoo@cybrosys.com) |
||||
|
# |
||||
|
# You can modify it under the terms of the GNU LESSER |
||||
|
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. |
||||
|
# |
||||
|
# This program is distributed in the hope that it will be useful, |
||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE |
||||
|
# (LGPL v3) along with this program. |
||||
|
# If not, see <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
############################################################################### |
||||
|
from . import controllers |
||||
|
from . import report |
||||
|
from . import wizard |
@ -0,0 +1,67 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################### |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
||||
|
# Author: Gayathri V (odoo@cybrosys.com) |
||||
|
# |
||||
|
# You can modify it under the terms of the GNU LESSER |
||||
|
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. |
||||
|
# |
||||
|
# This program is distributed in the hope that it will be useful, |
||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE |
||||
|
# (LGPL v3) along with this program. |
||||
|
# If not, see <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
############################################################################### |
||||
|
{ |
||||
|
"name": "Inventory Advanced Reports", |
||||
|
"version": "15.0.1.0.0", |
||||
|
"category": 'Warehouse', |
||||
|
"summary": "Helps to Manage different types of Inventory Reports.", |
||||
|
"description": "Helps to Manage different types of Inventory Reports like " |
||||
|
"FSN Report, Out Of Stock Report, Inventory XYZ Report, etc", |
||||
|
"author": "Cybrosys Techno Solutions", |
||||
|
'company': 'Cybrosys Techno Solutions', |
||||
|
'maintainer': 'Cybrosys Techno Solutions', |
||||
|
'website': 'https://www.cybrosys.com', |
||||
|
"depends": ["stock", "purchase", "sale_management"], |
||||
|
"data": ["security/ir.model.access.csv", |
||||
|
"report/aging_report_views.xml", |
||||
|
"report/fsn_report_views.xml", |
||||
|
"report/xyz_report_views.xml", |
||||
|
"report/fsn_xyz_report_views.xml", |
||||
|
"report/out_of_stock_report_views.xml", |
||||
|
"report/over_stock_report_views.xml", |
||||
|
"report/age_breakdown_report_views.xml", |
||||
|
"report/stock_movement_report_views.xml", |
||||
|
"wizard/inventory_aging_report_views.xml", |
||||
|
"wizard/inventory_aging_data_report_views.xml", |
||||
|
"wizard/inventory_fsn_report_views.xml", |
||||
|
"wizard/inventory_fsn_data_report_views.xml", |
||||
|
"wizard/inventory_xyz_report_views.xml", |
||||
|
"wizard/inventory_xyz_data_report_views.xml", |
||||
|
"wizard/inventory_fsn_xyz_report_views.xml", |
||||
|
"wizard/inventory_fsn_xyz_data_report_views.xml", |
||||
|
"wizard/inventory_out_of_stock_report_views.xml", |
||||
|
"wizard/inventory_out_of_stock_data_report_views.xml", |
||||
|
"wizard/inventory_age_breakdown_report_views.xml", |
||||
|
"wizard/inventory_over_stock_report_views.xml", |
||||
|
"wizard/inventory_over_stock_data_report_views.xml", |
||||
|
"wizard/inventory_stock_movement_report_views.xml", |
||||
|
], |
||||
|
'assets': { |
||||
|
'web.assets_backend': [ |
||||
|
'inventory_advanced_reports/static/src/js/action_manager.js'] |
||||
|
}, |
||||
|
'images': ['static/description/banner.png'], |
||||
|
'license': 'LGPL-3', |
||||
|
'installable': True, |
||||
|
'auto_install': False, |
||||
|
'application': False, |
||||
|
} |
@ -0,0 +1,22 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################### |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
||||
|
# Author: Gayathri V (odoo@cybrosys.com) |
||||
|
# |
||||
|
# You can modify it under the terms of the GNU LESSER |
||||
|
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. |
||||
|
# |
||||
|
# This program is distributed in the hope that it will be useful, |
||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE |
||||
|
# (LGPL v3) along with this program. |
||||
|
# If not, see <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
############################################################################### |
||||
|
from . import inventory_advanced_reports |
@ -0,0 +1,58 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################### |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
||||
|
# Author: Gayathri V (odoo@cybrosys.com) |
||||
|
# |
||||
|
# You can modify it under the terms of the GNU LESSER |
||||
|
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. |
||||
|
# |
||||
|
# This program is distributed in the hope that it will be useful, |
||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE |
||||
|
# (LGPL v3) along with this program. |
||||
|
# If not, see <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
############################################################################### |
||||
|
import json |
||||
|
from odoo import http |
||||
|
from odoo.http import content_disposition, request |
||||
|
from odoo.tools import html_escape |
||||
|
|
||||
|
|
||||
|
class XLSXReportController(http.Controller): |
||||
|
"""This model is used to connect the frontend to the backend""" |
||||
|
|
||||
|
@http.route('/xlsx_reports', type='http', auth='public', methods=['POST'], |
||||
|
csrf=False) |
||||
|
def get_report_xlsx(self, model, options, output_format, report_name): |
||||
|
"""This function is called when a post request is made to this route""" |
||||
|
uid = request.session.uid |
||||
|
report_obj = request.env[model].with_user(uid) |
||||
|
options = json.loads(options) |
||||
|
token = 'dummy-because-api-expects-one' |
||||
|
try: |
||||
|
if output_format == 'xlsx': |
||||
|
response = request.make_response( |
||||
|
None, |
||||
|
headers=[ |
||||
|
('Content-Type', 'application/vnd.ms-excel'), |
||||
|
('Content-Disposition', |
||||
|
content_disposition(report_name + '.xlsx')) |
||||
|
]) |
||||
|
report_obj.get_xlsx_report(options, response) |
||||
|
response.set_cookie('fileToken', token) |
||||
|
return response |
||||
|
except Exception as exception: |
||||
|
serialise = http.serialize_exception(exception) |
||||
|
error = { |
||||
|
'code': 200, |
||||
|
'message': 'Odoo Server Error', |
||||
|
'data': serialise |
||||
|
} |
||||
|
return request.make_response(html_escape(json.dumps(error))) |
@ -0,0 +1,7 @@ |
|||||
|
## Module <inventory_advanced_reports> |
||||
|
|
||||
|
#### 07.06.2024 |
||||
|
#### Version 15.0.1.0.0 |
||||
|
##### ADD |
||||
|
|
||||
|
- Initial Commit for Inventory Advanced Reports |
@ -0,0 +1,29 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################### |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
||||
|
# Author: Gayathri V (odoo@cybrosys.com) |
||||
|
# |
||||
|
# You can modify it under the terms of the GNU LESSER |
||||
|
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. |
||||
|
# |
||||
|
# This program is distributed in the hope that it will be useful, |
||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE |
||||
|
# (LGPL v3) along with this program. |
||||
|
# If not, see <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
############################################################################### |
||||
|
from . import aging_report |
||||
|
from . import age_breakdown_report |
||||
|
from . import fsn_report |
||||
|
from . import fsn_xyz_report |
||||
|
from . import out_of_stock_report |
||||
|
from . import over_stock_report |
||||
|
from . import stock_movement_report |
||||
|
from . import xyz_report |
@ -0,0 +1,180 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################### |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
||||
|
# Author: Gayathri V (odoo@cybrosys.com) |
||||
|
# |
||||
|
# You can modify it under the terms of the GNU LESSER |
||||
|
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. |
||||
|
# |
||||
|
# This program is distributed in the hope that it will be useful, |
||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE |
||||
|
# (LGPL v3) along with this program. |
||||
|
# If not, see <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) |
||||
|
ELSE |
||||
|
pt.name |
||||
|
END AS product_code_and_name, |
||||
|
c.complete_name AS category_name, |
||||
|
c.id AS category_id, |
||||
|
pp.id AS product_id, |
||||
|
company.id AS company_id, |
||||
|
company.name AS company_name, |
||||
|
COALESCE(SUM(svl.remaining_qty), 0) AS qty_available, |
||||
|
SUM(svl.remaining_value) AS stock_value, |
||||
|
SUM(CASE |
||||
|
WHEN age.days_between >= 1 AND age.days_between <= %s |
||||
|
THEN svl.remaining_qty |
||||
|
ELSE 0 |
||||
|
END) AS "age_breakdown_qty_1", |
||||
|
SUM(CASE |
||||
|
WHEN age.days_between >= %s+1 AND age.days_between <= %s*2 |
||||
|
THEN svl.remaining_qty |
||||
|
ELSE 0 |
||||
|
END) AS "age_breakdown_qty_2", |
||||
|
SUM(CASE |
||||
|
WHEN age.days_between >= (%s*2)+1 AND age.days_between <= %s*3 |
||||
|
THEN svl.remaining_qty |
||||
|
ELSE 0 |
||||
|
END) AS "age_breakdown_qty_3", |
||||
|
SUM(CASE |
||||
|
WHEN age.days_between >= (%s*3)+1 AND age.days_between <= %s*4 |
||||
|
THEN svl.remaining_qty |
||||
|
ELSE 0 |
||||
|
END) AS "age_breakdown_qty_4", |
||||
|
SUM(CASE |
||||
|
WHEN age.days_between >= (%s*4)+1 THEN svl.remaining_qty |
||||
|
ELSE 0 |
||||
|
END) AS "age_breakdown_qty_5", |
||||
|
SUM(CASE |
||||
|
WHEN age.days_between >= 1 AND age.days_between <= %s |
||||
|
THEN svl.remaining_value |
||||
|
ELSE 0 |
||||
|
END) AS "age_breakdown_value_1", |
||||
|
SUM(CASE |
||||
|
WHEN age.days_between >= %s+1 AND age.days_between <= %s*2 |
||||
|
THEN svl.remaining_value |
||||
|
ELSE 0 |
||||
|
END) AS "age_breakdown_value_2", |
||||
|
SUM(CASE |
||||
|
WHEN age.days_between >= (%s*2)+1 AND age.days_between <= %s*3 |
||||
|
THEN svl.remaining_value |
||||
|
ELSE 0 |
||||
|
END) AS "age_breakdown_value_3", |
||||
|
SUM(CASE |
||||
|
WHEN age.days_between >= (%s*3)+1 AND age.days_between <= %s*4 |
||||
|
THEN svl.remaining_value |
||||
|
ELSE 0 |
||||
|
END) AS "age_breakdown_value_4", |
||||
|
SUM(CASE |
||||
|
WHEN age.days_between >= (%s*4)+1 THEN svl.remaining_value |
||||
|
ELSE 0 |
||||
|
END) AS "age_breakdown_value_5" |
||||
|
FROM product_product pp |
||||
|
INNER JOIN product_template pt ON pp.product_tmpl_id = pt.id |
||||
|
INNER JOIN product_category c ON pt.categ_id = c.id |
||||
|
LEFT JOIN stock_move sm ON sm.product_id = pp.id |
||||
|
LEFT JOIN stock_picking_type spt ON sm.picking_type_id = spt.id |
||||
|
LEFT JOIN res_company company ON sm.company_id = company.id |
||||
|
LEFT JOIN LATERAL ( |
||||
|
SELECT EXTRACT(day FROM CURRENT_DATE - sm.date) AS days_between |
||||
|
) AS age ON true |
||||
|
INNER JOIN stock_valuation_layer svl ON svl.stock_move_id = sm.id |
||||
|
WHERE pt.detailed_type = 'product' |
||||
|
AND sm.state = 'done' |
||||
|
AND svl.remaining_value IS NOT NULL |
||||
|
""" |
||||
|
params.extend([age_breakdown_days] * 16) |
||||
|
if product_ids or category_ids: |
||||
|
query += " AND (" |
||||
|
if product_ids: |
||||
|
product_ids = [product_id for product_id in product_ids] |
||||
|
query += "pp.id = ANY(%s)" |
||||
|
params.append(product_ids) |
||||
|
param_count += 1 |
||||
|
if product_ids and category_ids: |
||||
|
query += " OR " |
||||
|
if category_ids: |
||||
|
category_ids = [category for category in category_ids] |
||||
|
params.append(category_ids) |
||||
|
query += "(pt.categ_id = ANY(%s))" |
||||
|
param_count += 1 |
||||
|
if product_ids or category_ids: |
||||
|
query += ")" |
||||
|
if company_ids: |
||||
|
company_ids = [company for company in company_ids] |
||||
|
query += " AND (sm.company_id = ANY(%s))" # Specify the table alias |
||||
|
params.append(company_ids) |
||||
|
param_count += 1 |
||||
|
query += """ |
||||
|
GROUP BY |
||||
|
CASE |
||||
|
WHEN pp.default_code IS NOT NULL |
||||
|
THEN CONCAT(pp.default_code, ' - ', |
||||
|
pt.name) |
||||
|
ELSE |
||||
|
pt.name |
||||
|
END, |
||||
|
c.complete_name, |
||||
|
company.id, |
||||
|
c.id, |
||||
|
company.name, |
||||
|
pp.id; |
||||
|
""" |
||||
|
self.env.cr.execute(query, params) |
||||
|
result_data = self.env.cr.dictfetchall() |
||||
|
main_header = age_breakdown_days |
||||
|
if result_data: |
||||
|
return { |
||||
|
'doc_ids': docids, |
||||
|
'doc_model': 'report.inventory_advanced_reports.' |
||||
|
'report_inventory_breakdown', |
||||
|
'data': values, |
||||
|
'options': result_data, |
||||
|
'main_header': self.get_header(main_header) |
||||
|
} |
||||
|
else: |
||||
|
raise ValidationError("No records found for the given criteria!") |
||||
|
|
||||
|
def get_header(self, main_header): |
||||
|
"""For getting the header for the report""" |
||||
|
age_breakdown1 = main_header |
||||
|
age_breakdown2 = main_header * 2 |
||||
|
age_breakdown3 = main_header * 3 |
||||
|
age_breakdown4 = main_header * 4 |
||||
|
return ['1-' + str(age_breakdown1), |
||||
|
str(age_breakdown1 + 1) + '-' + str(age_breakdown2), |
||||
|
str(age_breakdown2 + 1) + '-' + str(age_breakdown3), |
||||
|
str(age_breakdown3 + 1) + '-' + str(age_breakdown4), |
||||
|
'ABOVE ' + str(age_breakdown4)] |
@ -0,0 +1,106 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<odoo> |
||||
|
<!-- Action for the report--> |
||||
|
<record id="report_inventory_age_breakdown_action" |
||||
|
model="ir.actions.report"> |
||||
|
<field name="name">Inventory Age Breakdown Report</field> |
||||
|
<field name="model">report.inventory_advanced_reports.report_inventory_breakdown</field> |
||||
|
<field name="report_type">qweb-pdf</field> |
||||
|
<field name="report_name">inventory_advanced_reports.report_inventory_breakdown</field> |
||||
|
<field name="report_file">inventory_advanced_reports.report_inventory_breakdown</field> |
||||
|
<field name="binding_model_id" |
||||
|
ref="model_inventory_age_breakdown_report"/> |
||||
|
<field name="binding_type">report</field> |
||||
|
</record> |
||||
|
<!-- Template for the report --> |
||||
|
<template id="report_inventory_breakdown"> |
||||
|
<t t-call="web.html_container"> |
||||
|
<t t-call="web.external_layout"> |
||||
|
<div class="page"> |
||||
|
<div class="text-center"> |
||||
|
<h1>Inventory Age Breakdown Report</h1> |
||||
|
</div> |
||||
|
</div> |
||||
|
<table class="table table-condensed table-bordered table-striped"> |
||||
|
<thead> |
||||
|
<tr> |
||||
|
<th align="center"/> |
||||
|
<th align="center"/> |
||||
|
<th align="center"/> |
||||
|
<th align="center"/> |
||||
|
<th align="center"/> |
||||
|
<t t-foreach="main_header" t-as="header"> |
||||
|
<th align="center" colspan="2"><t t-esc="header"/></th> |
||||
|
</t> |
||||
|
</tr> |
||||
|
<tr> |
||||
|
<th align="center">Sl.no</th> |
||||
|
<th align="center">PRODUCT</th> |
||||
|
<th align="center">CATEGORY</th> |
||||
|
<th align="center">TOTAL STOCK</th> |
||||
|
<th align="center">STOCK VALUE</th> |
||||
|
<th align="center">STOCK</th> |
||||
|
<th align="center">VALUE</th> |
||||
|
<th align="center">STOCK</th> |
||||
|
<th align="center">VALUE</th> |
||||
|
<th align="center">STOCK</th> |
||||
|
<th align="center">VALUE</th> |
||||
|
<th align="center">STOCK</th> |
||||
|
<th align="center">VALUE</th> |
||||
|
<th align="center">STOCK</th> |
||||
|
<th align="center">VALUE</th> |
||||
|
</tr> |
||||
|
</thead> |
||||
|
<tbody> |
||||
|
<tr t-foreach="options" t-as="new"> |
||||
|
<t t-log="new"/> |
||||
|
<td> |
||||
|
<t t-esc="new_index + 1"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['product_code_and_name']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['category_name']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['qty_available']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['stock_value']"/> |
||||
|
</td> |
||||
|
<td><t t-esc="new['age_breakdown_qty_1']"/>% |
||||
|
</td> |
||||
|
<td><t t-esc="new['age_breakdown_value_1']"/>% |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['age_breakdown_qty_2']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['age_breakdown_value_2']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['age_breakdown_qty_3']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['age_breakdown_value_3']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['age_breakdown_qty_4']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['age_breakdown_value_4']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['age_breakdown_qty_5']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['age_breakdown_value_5']"/> |
||||
|
</td> |
||||
|
</tr> |
||||
|
</tbody> |
||||
|
</table> |
||||
|
</t> |
||||
|
</t> |
||||
|
</template> |
||||
|
</odoo> |
@ -0,0 +1,172 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################### |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
||||
|
# Author: Gayathri V (odoo@cybrosys.com) |
||||
|
# |
||||
|
# You can modify it under the terms of the GNU LESSER |
||||
|
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. |
||||
|
# |
||||
|
# This program is distributed in the hope that it will be useful, |
||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE |
||||
|
# (LGPL v3) along with this program. |
||||
|
# If not, see <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) |
||||
|
ELSE |
||||
|
pt.name |
||||
|
END AS product_code_and_name, |
||||
|
c.complete_name AS category_name, |
||||
|
c.id AS category_id, |
||||
|
pp.id AS product_id, |
||||
|
company.id AS company_id, |
||||
|
company.name AS company_name, |
||||
|
COALESCE(SUM(svl.remaining_qty), 0) AS qty_available, |
||||
|
( |
||||
|
SELECT SUM(sm_inner.product_uom_qty) |
||||
|
FROM stock_move sm_inner |
||||
|
INNER JOIN res_company company_inner ON sm_inner.company_id = company_inner.id |
||||
|
WHERE sm_inner.product_id = pp.id |
||||
|
AND sm_inner.state = 'done' |
||||
|
AND sm_inner.date < ( |
||||
|
SELECT MAX(sm_inner2.date) |
||||
|
FROM stock_move sm_inner2 |
||||
|
WHERE sm_inner2.product_id = pp.id |
||||
|
AND sm_inner2.state = 'done' |
||||
|
AND company_inner.id = sm_inner2.company_id |
||||
|
) |
||||
|
) AS prev_qty_available, |
||||
|
( |
||||
|
SELECT MIN(sm_inner.date) |
||||
|
FROM stock_move sm_inner |
||||
|
WHERE sm_inner.product_id = pp.id |
||||
|
AND sm_inner.state = 'done' |
||||
|
AND (company.id IS NULL OR company.id = sm_inner.company_id) |
||||
|
) AS receipt_date |
||||
|
FROM |
||||
|
product_product pp |
||||
|
INNER JOIN product_template pt ON pp.product_tmpl_id = pt.id |
||||
|
INNER JOIN product_category c ON pt.categ_id = c.id |
||||
|
LEFT JOIN stock_move sm ON sm.product_id = pp.id |
||||
|
LEFT JOIN stock_picking_type spt ON sm.picking_type_id = spt.id |
||||
|
LEFT JOIN res_company company ON sm.company_id = company.id |
||||
|
INNER JOIN stock_valuation_layer svl ON svl.stock_move_id = sm.id |
||||
|
WHERE |
||||
|
pt.detailed_type = 'product' |
||||
|
AND sm.state = 'done' |
||||
|
""" |
||||
|
if product_ids or category_ids: |
||||
|
query += " AND (" |
||||
|
if product_ids: |
||||
|
product_ids = [product_id for product_id in product_ids] |
||||
|
query += "pp.id = ANY(%s)" |
||||
|
params.append(product_ids) |
||||
|
param_count += 1 |
||||
|
if product_ids and category_ids: |
||||
|
query += " OR " |
||||
|
if category_ids: |
||||
|
category_ids = [category for category in category_ids] |
||||
|
params.append(category_ids) |
||||
|
query += "(pt.categ_id = ANY(%s))" |
||||
|
param_count += 1 |
||||
|
if product_ids or category_ids: |
||||
|
query += ")" |
||||
|
if company_ids: |
||||
|
company_ids = [company for company in company_ids] |
||||
|
query += " AND (sm.company_id = ANY(%s))" |
||||
|
params.append(company_ids) |
||||
|
param_count += 1 |
||||
|
query += """ |
||||
|
GROUP BY |
||||
|
CASE |
||||
|
WHEN pp.default_code IS NOT NULL |
||||
|
THEN CONCAT(pp.default_code, ' - ', |
||||
|
pt.name) |
||||
|
ELSE |
||||
|
pt.name |
||||
|
END, |
||||
|
c.complete_name, |
||||
|
company.id, |
||||
|
c.id, |
||||
|
company.name, |
||||
|
pp.id; |
||||
|
""" |
||||
|
self.env.cr.execute(query, params) |
||||
|
result_data = self.env.cr.dictfetchall() |
||||
|
today = fields.datetime.now().date() |
||||
|
for row in result_data: |
||||
|
receipt_date = row.get('receipt_date') |
||||
|
if receipt_date: |
||||
|
receipt_date = receipt_date.date() |
||||
|
row['days_since_receipt'] = (today - receipt_date).days |
||||
|
product = self.env['product.product'].browse(row.get('product_id')) |
||||
|
standard_price = product.standard_price |
||||
|
current_stock = row.get('qty_available') |
||||
|
prev_stock = row.get('prev_qty_available') |
||||
|
if prev_stock is None: |
||||
|
prev_stock = current_stock |
||||
|
row['prev_qty_available'] = current_stock |
||||
|
if standard_price and current_stock: |
||||
|
row['current_value'] = current_stock * standard_price |
||||
|
else: |
||||
|
row[ |
||||
|
'current_value'] = 0 |
||||
|
row[ |
||||
|
'prev_value'] = prev_stock * standard_price \ |
||||
|
if prev_stock is not None else 0 |
||||
|
total_current_stock = sum( |
||||
|
item.get('qty_available') for item in result_data if |
||||
|
item.get('qty_available') is not None) |
||||
|
if total_current_stock: |
||||
|
stock_percentage = (current_stock / total_current_stock) * 100 |
||||
|
else: |
||||
|
stock_percentage = 0.0 |
||||
|
row['stock_percentage'] = round(stock_percentage, 2) |
||||
|
current_value = row.get('current_value') |
||||
|
total_value = sum( |
||||
|
item.get('current_value', 0) for item in result_data) |
||||
|
if total_value: |
||||
|
stock_value_percentage = (current_value / total_value) * 100 |
||||
|
else: |
||||
|
stock_value_percentage = 0.0 |
||||
|
row['stock_value_percentage'] = round(stock_value_percentage, 2) |
||||
|
print(result_data,'fgthyujikoo') |
||||
|
if result_data: |
||||
|
return { |
||||
|
'doc_ids': docids, |
||||
|
'doc_model': |
||||
|
'report.inventory_advanced_reports.report_inventory_aging', |
||||
|
'data': values, |
||||
|
'options': result_data, |
||||
|
} |
||||
|
else: |
||||
|
raise ValidationError("No records found for the given criteria!") |
@ -0,0 +1,75 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<odoo> |
||||
|
<!-- Action for the report--> |
||||
|
<record id="report_inventory_aging_action" model="ir.actions.report"> |
||||
|
<field name="name">Inventory Aging Report</field> |
||||
|
<field name="model">report.inventory_advanced_reports.report_inventory_aging</field> |
||||
|
<field name="report_type">qweb-pdf</field> |
||||
|
<field name="report_name">inventory_advanced_reports.report_inventory_aging</field> |
||||
|
<field name="report_file">inventory_advanced_reports.report_inventory_aging</field> |
||||
|
<field name="binding_model_id" |
||||
|
ref="model_inventory_aging_report"/> |
||||
|
<field name="binding_type">report</field> |
||||
|
</record> |
||||
|
<!-- Template for the report--> |
||||
|
<template id="report_inventory_aging"> |
||||
|
<t t-call="web.html_container"> |
||||
|
<t t-call="web.external_layout"> |
||||
|
<div class="page"> |
||||
|
<div class="text-center"> |
||||
|
<h1>Inventory Aging Report</h1> |
||||
|
</div> |
||||
|
</div> |
||||
|
<table class="table table-condensed table-bordered table-striped"> |
||||
|
<thead> |
||||
|
<tr> |
||||
|
<th align="center">Sl.no</th> |
||||
|
<th align="center">PRODUCT</th> |
||||
|
<th align="center">CATEGORY</th> |
||||
|
<th align="center">CURRENT STOCK</th> |
||||
|
<th align="center">CURRENT VALUE</th> |
||||
|
<th align="center">STOCK QUANT(%)</th> |
||||
|
<th align="center">STOCK VALUE(%)</th> |
||||
|
<th align="center">OLDEST STOCK AGE</th> |
||||
|
<th align="center">OLDEST STOCK</th> |
||||
|
<th align="center">OLDEST STOCK VALUE</th> |
||||
|
</tr> |
||||
|
</thead> |
||||
|
<tbody> |
||||
|
<tr t-foreach="options" t-as="new"> |
||||
|
<t t-log="new"/> |
||||
|
<td> |
||||
|
<t t-esc="new_index + 1"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['product_code_and_name']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['category_name']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['qty_available']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['current_value']"/> |
||||
|
</td> |
||||
|
<td><t t-esc="new['stock_percentage']"/>% |
||||
|
</td> |
||||
|
<td><t t-esc="new['stock_value_percentage']"/>% |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['days_since_receipt']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['prev_qty_available']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['prev_value']"/> |
||||
|
</td> |
||||
|
</tr> |
||||
|
</tbody> |
||||
|
</table> |
||||
|
</t> |
||||
|
</t> |
||||
|
</template> |
||||
|
</odoo> |
@ -0,0 +1,200 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################### |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
||||
|
# Author: Gayathri V (odoo@cybrosys.com) |
||||
|
# |
||||
|
# You can modify it under the terms of the GNU LESSER |
||||
|
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. |
||||
|
# |
||||
|
# This program is distributed in the hope that it will be useful, |
||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE |
||||
|
# (LGPL v3) along with this program. |
||||
|
# If not, see <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) |
||||
|
ELSE |
||||
|
pt.name |
||||
|
END AS product_code_and_name, |
||||
|
pc.complete_name AS category_name, |
||||
|
company.id AS company_id, |
||||
|
sw.id AS warehouse_id, |
||||
|
(SUM(CASE WHEN sm.date <= %s AND sld_dest.usage = 'internal' |
||||
|
THEN sm.product_uom_qty ELSE 0 END) - |
||||
|
SUM(CASE WHEN sm.date <= %s AND sld_src.usage = 'internal' |
||||
|
THEN sm.product_uom_qty ELSE 0 END)) AS opening_stock, |
||||
|
(SUM(CASE WHEN sm.date <= %s AND sld_dest.usage = 'internal' |
||||
|
THEN sm.product_uom_qty ELSE 0 END) - |
||||
|
SUM(CASE WHEN sm.date <= %s AND sld_src.usage = 'internal' |
||||
|
THEN sm.product_uom_qty ELSE 0 END)) AS closing_stock, |
||||
|
SUM(CASE WHEN sm.date BETWEEN %s AND %s |
||||
|
AND sld_dest.usage = 'customer' |
||||
|
THEN sm.product_uom_qty ELSE 0 END) AS sales, |
||||
|
((SUM(CASE WHEN sm.date <= %s |
||||
|
AND sld_dest.usage = 'internal' |
||||
|
THEN sm.product_uom_qty ELSE 0 END) - |
||||
|
SUM(CASE WHEN sm.date <= %s AND sld_src.usage = 'internal' |
||||
|
THEN sm.product_uom_qty ELSE 0 END))+ |
||||
|
(SUM(CASE WHEN sm.date <= %s AND sld_dest.usage = 'internal' |
||||
|
THEN sm.product_uom_qty ELSE 0 END) - |
||||
|
SUM(CASE WHEN sm.date <= %s AND sld_src.usage = 'internal' |
||||
|
THEN sm.product_uom_qty ELSE 0 END)))/2 AS average_stock |
||||
|
FROM |
||||
|
stock_move sm |
||||
|
JOIN |
||||
|
product_product pp ON sm.product_id = pp.id |
||||
|
JOIN |
||||
|
product_template pt ON pp.product_tmpl_id = pt.id |
||||
|
JOIN |
||||
|
product_category pc ON pt.categ_id = pc.id |
||||
|
JOIN |
||||
|
res_company company ON company.id = sm.company_id |
||||
|
JOIN |
||||
|
stock_warehouse sw ON sw.company_id = company.id |
||||
|
LEFT JOIN |
||||
|
stock_location sld_dest ON sm.location_dest_id = sld_dest.id |
||||
|
LEFT JOIN |
||||
|
stock_location sld_src ON sm.location_id = sld_src.id |
||||
|
WHERE |
||||
|
sm.state = 'done' |
||||
|
""" |
||||
|
params = [ |
||||
|
start_date, start_date, end_date, end_date, start_date, end_date, |
||||
|
start_date, start_date, end_date, end_date |
||||
|
] |
||||
|
sub_queries = [] |
||||
|
sub_params = [] |
||||
|
param_count = 0 |
||||
|
if product_ids or category_ids: |
||||
|
query += " AND (" |
||||
|
if product_ids: |
||||
|
product_ids = [product_id for product_id in product_ids] |
||||
|
query += "pp.id = ANY(%s)" |
||||
|
params.append(product_ids) |
||||
|
param_count += 1 |
||||
|
if product_ids and category_ids: |
||||
|
query += " OR " |
||||
|
if category_ids: |
||||
|
category_ids = [category_id for category_id in category_ids] |
||||
|
query += "pt.categ_id IN %s" |
||||
|
params.append(tuple(category_ids)) |
||||
|
param_count += 1 |
||||
|
if product_ids or category_ids: |
||||
|
query += ")" |
||||
|
if company_ids: |
||||
|
query += f" AND company.id IN %s" |
||||
|
sub_params.append(tuple(company_ids)) |
||||
|
param_count += 1 |
||||
|
if warehouse_ids: |
||||
|
query += f" AND sw.id IN %s" |
||||
|
sub_params.append(tuple(warehouse_ids)) |
||||
|
param_count += 1 |
||||
|
if sub_queries: |
||||
|
query += " AND " + " AND ".join(sub_queries) |
||||
|
query += """ |
||||
|
GROUP BY pp.id, pt.categ_id, |
||||
|
CASE |
||||
|
WHEN pp.default_code IS NOT NULL |
||||
|
THEN CONCAT(pp.default_code,' - ',pt.name) |
||||
|
ELSE |
||||
|
pt.name |
||||
|
END , pc.complete_name, company.id, sw.id |
||||
|
) AS subquery |
||||
|
""" |
||||
|
self.env.cr.execute(query, tuple(params + sub_params)) |
||||
|
result_data = self.env.cr.dictfetchall() |
||||
|
for fsn_data in result_data: |
||||
|
if fsn_data.get('fsn_classification') == str(fsn): |
||||
|
filtered_product_stock.append(fsn_data) |
||||
|
if fsn == 'All' and not result_data: |
||||
|
raise ValidationError("No corresponding data to print") |
||||
|
elif fsn != 'All' and filtered_product_stock == []: |
||||
|
raise ValidationError("No corresponding data to print") |
||||
|
return { |
||||
|
'doc_ids': docids, |
||||
|
'doc_model': |
||||
|
'report.inventory_advanced_reports.report_inventory_fsn', |
||||
|
'data': values, |
||||
|
'options': result_data if fsn == 'All' else filtered_product_stock, |
||||
|
} |
@ -0,0 +1,91 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8" ?> |
||||
|
<odoo> |
||||
|
<!-- Action for the report--> |
||||
|
<record id="report_inventory_fsn_action" model="ir.actions.report"> |
||||
|
<field name="name">Inventory FSN Report</field> |
||||
|
<field name="model">report.inventory_advanced_reports.report_inventory_fsn</field> |
||||
|
<field name="report_type">qweb-pdf</field> |
||||
|
<field name="report_name">inventory_advanced_reports.report_inventory_fsn</field> |
||||
|
<field name="report_file">inventory_advanced_reports.report_inventory_fsn</field> |
||||
|
<field name="binding_model_id" |
||||
|
ref="model_inventory_fsn_report"/> |
||||
|
<field name="binding_type">report</field> |
||||
|
</record> |
||||
|
<!-- Template for the report--> |
||||
|
<template id="report_inventory_fsn"> |
||||
|
<t t-call="web.html_container"> |
||||
|
<t t-call="web.external_layout"> |
||||
|
<br/><br/><br/><br/> |
||||
|
<div class="page"> |
||||
|
<div class="text-center"> |
||||
|
<h1>Inventory FSN Report</h1> |
||||
|
</div> |
||||
|
</div> |
||||
|
<br/> |
||||
|
<br/> |
||||
|
<div> |
||||
|
<div> |
||||
|
<t t-if="data.get('start_date') and data.get('end_date')"> |
||||
|
<strong> |
||||
|
Start Date: |
||||
|
</strong> |
||||
|
<span t-esc="data['start_date']"/> |
||||
|
<br/> |
||||
|
<strong> |
||||
|
End Date: |
||||
|
</strong> |
||||
|
<span t-esc="data['end_date']"/> |
||||
|
</t> |
||||
|
</div> |
||||
|
</div> |
||||
|
<br/> |
||||
|
<table class="table table-condensed table-bordered table-striped"> |
||||
|
<thead> |
||||
|
<tr> |
||||
|
<th align="center">Sl.no</th> |
||||
|
<th align="center">PRODUCT</th> |
||||
|
<th align="center">CATEGORY</th> |
||||
|
<th align="center">OPENING STOCK</th> |
||||
|
<th align="center">CLOSING STOCK</th> |
||||
|
<th align="center">AVERAGE STOCK</th> |
||||
|
<th align="center">SALES</th> |
||||
|
<th align="center">TURNOVER RATIO</th> |
||||
|
<th align="center">FSN CLASSIFICATION</th> |
||||
|
</tr> |
||||
|
</thead> |
||||
|
<tbody> |
||||
|
<tr t-foreach="options" t-as="new"> |
||||
|
<td> |
||||
|
<t t-esc="new_index + 1"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['product_code_and_name']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['category_name']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['opening_stock']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['closing_stock']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['average_stock']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['sales']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['turnover_ratio']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['fsn_classification']"/> |
||||
|
</td> |
||||
|
</tr> |
||||
|
</tbody> |
||||
|
</table> |
||||
|
</t> |
||||
|
</t> |
||||
|
</template> |
||||
|
</odoo> |
@ -0,0 +1,260 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################### |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
||||
|
# Author: Gayathri V (odoo@cybrosys.com) |
||||
|
# |
||||
|
# You can modify it under the terms of the GNU LESSER |
||||
|
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. |
||||
|
# |
||||
|
# This program is distributed in the hope that it will be useful, |
||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE |
||||
|
# (LGPL v3) along with this program. |
||||
|
# If not, see <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) |
||||
|
ELSE |
||||
|
pt.name |
||||
|
END AS product_code_and_name, |
||||
|
pc.complete_name AS category_name, |
||||
|
company.id AS company_id, |
||||
|
sw.id AS warehouse_id, |
||||
|
SUM(svl.remaining_qty) AS current_stock, |
||||
|
SUM(svl.remaining_value) AS stock_value, |
||||
|
COALESCE(ROUND((SUM(svl.remaining_value) / |
||||
|
NULLIF(SUM(SUM(svl.remaining_value)) |
||||
|
OVER (), 0)) * 100, 2),0) AS stock_percentage, |
||||
|
(SUM(CASE WHEN sm.date <= %s AND sld_dest.usage = 'internal' |
||||
|
THEN sm.product_uom_qty ELSE 0 END) - |
||||
|
SUM(CASE WHEN sm.date <= %s AND sld_src.usage = 'internal' |
||||
|
THEN sm.product_uom_qty ELSE 0 END)) AS opening_stock, |
||||
|
(SUM(CASE WHEN sm.date <= %s AND sld_dest.usage = 'internal' |
||||
|
THEN sm.product_uom_qty ELSE 0 END) - |
||||
|
SUM(CASE WHEN sm.date <= %s AND sld_src.usage = 'internal' |
||||
|
THEN sm.product_uom_qty ELSE 0 END)) AS closing_stock, |
||||
|
SUM(CASE WHEN sm.date BETWEEN %s AND %s |
||||
|
AND sld_dest.usage = 'customer' |
||||
|
THEN sm.product_uom_qty ELSE 0 END) AS sales, |
||||
|
((SUM(CASE WHEN sm.date <= %s |
||||
|
AND sld_dest.usage = 'internal' |
||||
|
THEN sm.product_uom_qty ELSE 0 END) - |
||||
|
SUM(CASE WHEN sm.date <= %s AND sld_src.usage = 'internal' |
||||
|
THEN sm.product_uom_qty ELSE 0 END))+ |
||||
|
(SUM(CASE WHEN sm.date <= %s AND sld_dest.usage = 'internal' |
||||
|
THEN sm.product_uom_qty ELSE 0 END) - |
||||
|
SUM(CASE WHEN sm.date <= %s AND sld_src.usage = 'internal' |
||||
|
THEN sm.product_uom_qty ELSE 0 END)))/2 AS average_stock |
||||
|
FROM |
||||
|
stock_move sm |
||||
|
JOIN |
||||
|
product_product pp ON sm.product_id = pp.id |
||||
|
JOIN |
||||
|
product_template pt ON pp.product_tmpl_id = pt.id |
||||
|
JOIN |
||||
|
product_category pc ON pt.categ_id = pc.id |
||||
|
JOIN |
||||
|
res_company company ON company.id = sm.company_id |
||||
|
JOIN |
||||
|
stock_warehouse sw ON sw.company_id = company.id |
||||
|
JOIN |
||||
|
stock_valuation_layer svl ON svl.stock_move_id = sm.id |
||||
|
LEFT JOIN |
||||
|
stock_location sld_dest ON sm.location_dest_id = sld_dest.id |
||||
|
LEFT JOIN |
||||
|
stock_location sld_src ON sm.location_id = sld_src.id |
||||
|
WHERE |
||||
|
sm.state = 'done' |
||||
|
AND pp.active = TRUE |
||||
|
AND pt.active = TRUE |
||||
|
AND pt.type = 'product' |
||||
|
AND svl.remaining_value IS NOT NULL |
||||
|
""" |
||||
|
params = [ |
||||
|
start_date, start_date, end_date, end_date, start_date, end_date, |
||||
|
start_date, start_date, end_date, end_date |
||||
|
] |
||||
|
sub_queries = [] |
||||
|
sub_params = [] |
||||
|
param_count = 0 |
||||
|
if product_ids or category_ids: |
||||
|
query += " AND (" |
||||
|
if product_ids: |
||||
|
product_ids = [product_id for product_id in product_ids] |
||||
|
query += "pp.id = ANY(%s)" |
||||
|
params.append(product_ids) |
||||
|
param_count += 1 |
||||
|
if product_ids and category_ids: |
||||
|
query += " OR " |
||||
|
if category_ids: |
||||
|
category_ids = [category_id for category_id in category_ids] |
||||
|
query += "pt.categ_id IN %s" |
||||
|
params.append(tuple(category_ids)) |
||||
|
param_count += 1 |
||||
|
if product_ids or category_ids: |
||||
|
query += ")" |
||||
|
if company_ids: |
||||
|
query += f" AND sm.company_id IN %s" |
||||
|
sub_params.append(tuple(company_ids)) |
||||
|
param_count += 1 |
||||
|
if warehouse_ids: |
||||
|
query += f" AND sw.id IN %s" |
||||
|
sub_params.append(tuple(warehouse_ids)) |
||||
|
param_count += 1 |
||||
|
if sub_queries: |
||||
|
query += " AND " + " AND ".join(sub_queries) |
||||
|
query += """ |
||||
|
GROUP BY |
||||
|
pp.id,pt.name, pt.categ_id,pc.complete_name, company.id, sw.id |
||||
|
) AS subquery |
||||
|
ORDER BY stock_value DESC |
||||
|
""" |
||||
|
self.env.cr.execute(query, tuple(params + sub_params)) |
||||
|
result_data = self.env.cr.dictfetchall() |
||||
|
for fsn_data in result_data: |
||||
|
if ( |
||||
|
(fsn == 'All' and xyz == 'All') or |
||||
|
(fsn == 'All' and fsn_data.get('xyz_classification') == str( |
||||
|
xyz)) or |
||||
|
(xyz == 'All' and fsn_data.get('fsn_classification') == str( |
||||
|
fsn)) or |
||||
|
(fsn_data.get('fsn_classification') == str( |
||||
|
fsn) and fsn_data.get('xyz_classification') == str(xyz)) |
||||
|
): |
||||
|
filtered_product_stock.append(fsn_data) |
||||
|
if (fsn == 'All' or xyz == 'All') and not result_data: |
||||
|
raise ValidationError("No corresponding data to print") |
||||
|
elif not filtered_product_stock: |
||||
|
raise ValidationError("No corresponding data to print") |
||||
|
return { |
||||
|
'doc_ids': docids, |
||||
|
'doc_model': |
||||
|
'report.inventory_advanced_reports.report_inventory_fsn_xyz', |
||||
|
'data': values, |
||||
|
'options': filtered_product_stock, |
||||
|
} |
@ -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> |
||||
|
<!-- Report for the report--> |
||||
|
<template id="report_inventory_fsn_xyz"> |
||||
|
<t t-call="web.html_container"> |
||||
|
<t t-call="web.external_layout"> |
||||
|
<br/><br/><br/><br/> |
||||
|
<div class="page"> |
||||
|
<div class="text-center"> |
||||
|
<h1>Inventory FSN-XYZ Report</h1> |
||||
|
</div> |
||||
|
</div> |
||||
|
<br/> |
||||
|
<br/> |
||||
|
<div> |
||||
|
<div> |
||||
|
<t t-if="data.get('start_date') and data.get('end_date')"> |
||||
|
<strong> |
||||
|
Start Date: |
||||
|
</strong> |
||||
|
<span t-esc="data['start_date']"/> |
||||
|
<br/> |
||||
|
<strong> |
||||
|
End Date: |
||||
|
</strong> |
||||
|
<span t-esc="data['end_date']"/> |
||||
|
</t> |
||||
|
</div> |
||||
|
</div> |
||||
|
<br/> |
||||
|
<table class="table table-condensed table-bordered table-striped"> |
||||
|
<thead> |
||||
|
<tr> |
||||
|
<th align="center">Sl.no</th> |
||||
|
<th align="center">PRODUCT</th> |
||||
|
<th align="center">CATEGORY</th> |
||||
|
<th align="center">AVERAGE STOCK</th> |
||||
|
<th align="center">SALES</th> |
||||
|
<th align="center">TURNOVER RATIO</th> |
||||
|
<th align="center">CURRENT STOCK</th> |
||||
|
<th align="center">STOCK VALUE</th> |
||||
|
<th align="center">FSN CLASSIFICATION</th> |
||||
|
<th align="center">XYZ CLASSIFICATION</th> |
||||
|
<th align="center">FSN-XYZ CLASSIFICATION</th> |
||||
|
</tr> |
||||
|
</thead> |
||||
|
<tbody> |
||||
|
<tr t-foreach="options" t-as="new"> |
||||
|
<td> |
||||
|
<t t-esc="new_index + 1"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['product_code_and_name']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['category_name']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['average_stock']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['sales']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['turnover_ratio']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['current_stock']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['stock_value']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['fsn_classification']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['xyz_classification']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['combined_classification']"/> |
||||
|
</td> |
||||
|
</tr> |
||||
|
</tbody> |
||||
|
</table> |
||||
|
</t> |
||||
|
</t> |
||||
|
</template> |
||||
|
</odoo> |
@ -0,0 +1,274 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################### |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
||||
|
# Author: Gayathri V (odoo@cybrosys.com) |
||||
|
# |
||||
|
# You can modify it under the terms of the GNU LESSER |
||||
|
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. |
||||
|
# |
||||
|
# This program is distributed in the hope that it will be useful, |
||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE |
||||
|
# (LGPL v3) along with this program. |
||||
|
# If not, see <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) |
||||
|
ELSE |
||||
|
pt.name |
||||
|
END AS product_code_and_name, |
||||
|
company.id AS company_id, |
||||
|
company.name AS company_name, |
||||
|
sm.product_id AS product_id, |
||||
|
pc.id AS category_id, |
||||
|
pc.complete_name AS category_name, |
||||
|
sw.id AS warehouse_id, |
||||
|
SUM(CASE |
||||
|
WHEN sld_dest.usage = 'internal' AND sm.state IN |
||||
|
('assigned', 'confirmed', 'waiting') |
||||
|
THEN sm.product_uom_qty |
||||
|
ELSE 0 |
||||
|
END) AS incoming_quantity, |
||||
|
SUM(CASE |
||||
|
WHEN sld_src.usage = 'internal' AND sm.state IN |
||||
|
('assigned', 'confirmed', 'waiting') |
||||
|
THEN sm.product_uom_qty |
||||
|
ELSE 0 |
||||
|
END) AS outgoing_quantity, |
||||
|
SUM(CASE |
||||
|
WHEN sld_dest.usage = 'internal' AND sm.state = 'done' |
||||
|
THEN sm.product_uom_qty |
||||
|
ELSE 0 |
||||
|
END) - |
||||
|
SUM(CASE |
||||
|
WHEN sld_src.usage = 'internal' AND sm.state = 'done' |
||||
|
THEN sm.product_uom_qty |
||||
|
ELSE 0 |
||||
|
END) AS current_stock, |
||||
|
SUM(CASE |
||||
|
WHEN sld_dest.usage = 'internal' AND sm.state = 'done' |
||||
|
THEN sm.product_uom_qty |
||||
|
ELSE 0 |
||||
|
END) - |
||||
|
SUM(CASE |
||||
|
WHEN sld_src.usage = 'internal' AND sm.state = 'done' |
||||
|
THEN sm.product_uom_qty |
||||
|
ELSE 0 |
||||
|
END)+ |
||||
|
SUM(CASE |
||||
|
WHEN sld_dest.usage = 'internal' AND sm.state |
||||
|
IN ('assigned', 'confirmed', 'waiting') THEN sm.product_uom_qty |
||||
|
ELSE 0 |
||||
|
END) - |
||||
|
SUM(CASE |
||||
|
WHEN sld_src.usage = 'internal' AND sm.state |
||||
|
IN ('assigned', 'confirmed', 'waiting') THEN sm.product_uom_qty |
||||
|
ELSE 0 |
||||
|
END) AS virtual_stock, |
||||
|
SUM(CASE WHEN sm.date BETWEEN %s AND %s |
||||
|
AND sld_dest.usage = 'customer' |
||||
|
THEN sm.product_uom_qty ELSE 0 END) AS sales, |
||||
|
ROUND(SUM(CASE |
||||
|
WHEN sm.date BETWEEN %s AND %s AND sld_src.usage = 'internal' |
||||
|
AND sm.state = 'done' THEN sm.product_uom_qty |
||||
|
ELSE 0 |
||||
|
END) / ((date %s - date %s)+1), 2) AS ads, |
||||
|
%s AS advance_stock_days |
||||
|
FROM stock_move sm |
||||
|
INNER JOIN product_product pp ON pp.id = sm.product_id |
||||
|
INNER JOIN product_template pt ON pt.id = pp.product_tmpl_id |
||||
|
INNER JOIN res_company company ON company.id = sm.company_id |
||||
|
INNER JOIN stock_warehouse sw ON sw.company_id = company.id |
||||
|
INNER JOIN product_category pc ON pc.id = pt.categ_id |
||||
|
LEFT JOIN ( |
||||
|
SELECT sm.id AS move_id, usage |
||||
|
FROM stock_location sld |
||||
|
INNER JOIN stock_move sm ON sld.id = sm.location_dest_id |
||||
|
) sld_dest ON sm.id = sld_dest.move_id |
||||
|
LEFT JOIN ( |
||||
|
SELECT sm.id AS move_id, usage |
||||
|
FROM stock_location sld |
||||
|
INNER JOIN stock_move sm ON sld.id = sm.location_id |
||||
|
) sld_src ON sm.id = sld_src.move_id |
||||
|
WHERE pp.active = TRUE |
||||
|
AND pt.active = TRUE |
||||
|
AND pt.type = 'product' |
||||
|
""" |
||||
|
params = [ |
||||
|
start_date, end_date, |
||||
|
start_date, end_date, |
||||
|
end_date, start_date, |
||||
|
inventory_for_next_x_days |
||||
|
] |
||||
|
sub_queries = [] |
||||
|
sub_params = [] |
||||
|
param_count = 0 |
||||
|
if product_ids or category_ids: |
||||
|
query += " AND (" |
||||
|
if product_ids: |
||||
|
product_ids = [product_id for product_id in product_ids] |
||||
|
query += "pp.id = ANY(%s)" |
||||
|
params.append(product_ids) |
||||
|
param_count += 1 |
||||
|
if product_ids and category_ids: |
||||
|
query += " OR " |
||||
|
if category_ids: |
||||
|
category_ids = [category for category in category_ids] |
||||
|
params.append(category_ids) |
||||
|
query += "(pt.categ_id = ANY(%s))" |
||||
|
param_count += 1 |
||||
|
if product_ids or category_ids: |
||||
|
query += ")" |
||||
|
if company_ids: |
||||
|
company_ids = [company for company in company_ids] |
||||
|
query += " AND (sm.company_id = ANY(%s))" |
||||
|
sub_params.append(company_ids) |
||||
|
param_count += 1 |
||||
|
if warehouse_ids: |
||||
|
warehouse_ids = [warehouse for warehouse in warehouse_ids] |
||||
|
query += " AND (sw.id = ANY(%s))" |
||||
|
sub_params.append(warehouse_ids) |
||||
|
param_count += 1 |
||||
|
if sub_queries: |
||||
|
query += " AND " + " AND ".join(sub_queries) |
||||
|
query += """ GROUP BY pp.id, pt.name, pc.id, company.id, sm.product_id, |
||||
|
sw.id) AS sub_query """ |
||||
|
self.env.cr.execute(query, tuple(params + sub_params)) |
||||
|
result_data = self.env.cr.dictfetchall() |
||||
|
for data in result_data: |
||||
|
product_id = data.get('product_id') |
||||
|
out_of_stock_qty = data.get('out_of_stock_qty') |
||||
|
total_value = sum( |
||||
|
item.get('out_of_stock_qty', 0) for item in result_data) |
||||
|
if total_value: |
||||
|
out_of_stock_qty_percentage = \ |
||||
|
(out_of_stock_qty / total_value) * 100 |
||||
|
else: |
||||
|
out_of_stock_qty_percentage = 0.0 |
||||
|
data['out_of_stock_qty_percentage'] = round( |
||||
|
out_of_stock_qty_percentage, 2) |
||||
|
cost = self.env['product.product'].search([ |
||||
|
('id', '=', product_id)]).standard_price |
||||
|
data['cost'] = cost |
||||
|
data['out_of_stock_value'] = out_of_stock_qty * cost |
||||
|
if result_data: |
||||
|
return { |
||||
|
'doc_ids': docids, |
||||
|
'doc_model': |
||||
|
'report.inventory_advanced_reports.' |
||||
|
'report_inventory_out_of_stock', |
||||
|
'data': values, |
||||
|
'options': result_data, |
||||
|
} |
||||
|
else: |
||||
|
raise ValidationError("No records found for the given criteria!") |
@ -0,0 +1,158 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8" ?> |
||||
|
<odoo> |
||||
|
<!-- Custom paper format for the report--> |
||||
|
<record id="paperformat_inventory_reports" model="report.paperformat"> |
||||
|
<field name="name">Over Stock</field> |
||||
|
<field name="default" eval="True"/> |
||||
|
<field name="format">custom</field> |
||||
|
<field name="page_height">297</field> |
||||
|
<field name="page_width">500</field> |
||||
|
<field name="orientation">Landscape</field> |
||||
|
<field name="margin_top">30</field> |
||||
|
<field name="margin_bottom">23</field> |
||||
|
<field name="margin_left">5</field> |
||||
|
<field name="margin_right">5</field> |
||||
|
<field name="header_line" eval="False"/> |
||||
|
<field name="header_spacing">20</field> |
||||
|
<field name="dpi">90</field> |
||||
|
</record> |
||||
|
<!-- Action for the report --> |
||||
|
<record id="report_inventory_out_of_stock_action" model="ir.actions.report"> |
||||
|
<field name="name">Inventory Out Of Stock Report</field> |
||||
|
<field name="model">report.inventory_advanced_reports.report_inventory_out_of_stock</field> |
||||
|
<field name="report_type">qweb-pdf</field> |
||||
|
<field name="report_name">inventory_advanced_reports.report_inventory_out_of_stock</field> |
||||
|
<field name="report_file">inventory_advanced_reports.report_inventory_out_of_stock</field> |
||||
|
<field name="binding_model_id" |
||||
|
ref="model_inventory_out_of_stock_report"/> |
||||
|
<field name="paperformat_id" |
||||
|
ref="inventory_advanced_reports.paperformat_inventory_reports"/> |
||||
|
<field name="binding_type">report</field> |
||||
|
</record> |
||||
|
<!-- Template for the report--> |
||||
|
<template id="report_inventory_out_of_stock"> |
||||
|
<t t-call="web.html_container"> |
||||
|
<t t-call="web.external_layout"> |
||||
|
<br/> |
||||
|
<br/> |
||||
|
<br/> |
||||
|
<br/> |
||||
|
<div class="page"> |
||||
|
<div class="text-center"> |
||||
|
<h1>Inventory Out Of Stock Report</h1> |
||||
|
</div> |
||||
|
</div> |
||||
|
<br/> |
||||
|
<br/> |
||||
|
<div> |
||||
|
<div> |
||||
|
<t t-if="data.get('start_date') and data.get('end_date')"> |
||||
|
<strong> |
||||
|
Sales History from: |
||||
|
</strong> |
||||
|
<span t-esc="data['start_date']"/> |
||||
|
<br/> |
||||
|
<strong> |
||||
|
Sales History Upto: |
||||
|
</strong> |
||||
|
<span t-esc="data['end_date']"/> |
||||
|
<br/> |
||||
|
<strong> |
||||
|
Inventory Analysis For Next: |
||||
|
</strong> |
||||
|
<span t-esc="data['inventory_for_next_x_days']"/> |
||||
|
days |
||||
|
</t> |
||||
|
</div> |
||||
|
</div> |
||||
|
<br/> |
||||
|
<table class="table table-condensed table-bordered table-striped"> |
||||
|
<thead> |
||||
|
<tr> |
||||
|
<th align="center">Sl.no</th> |
||||
|
<th align="center">PRODUCT</th> |
||||
|
<th align="center">CATEGORY</th> |
||||
|
<th align="center">CURRENT STOCK</th> |
||||
|
<th align="center">INCOMING</th> |
||||
|
<th align="center">OUTGOING</th> |
||||
|
<th align="center">VIRTUAL STOCK</th> |
||||
|
<th align="center">SALES</th> |
||||
|
<th align="center">ADS</th> |
||||
|
<th align="center">DEMANDED QTY</th> |
||||
|
<th align="center">IN STOCK DAYS</th> |
||||
|
<th align="center">OUT OF STOCK DAYS</th> |
||||
|
<th align="center">OUT OF STOCK RATIO</th> |
||||
|
<th align="center">COST PRICE</th> |
||||
|
<th align="center">OUT OF STOCK QTY</th> |
||||
|
<th align="center">OUT OF STOCK QTY(%)</th> |
||||
|
<th align="center">OUT OF STOCK VALUE(%)</th> |
||||
|
<th align="center">TURNOVER RATIO(%)</th> |
||||
|
<th align="center">FSN CLASSIFICATION</th> |
||||
|
</tr> |
||||
|
</thead> |
||||
|
<tbody> |
||||
|
<tr t-foreach="options" t-as="new"> |
||||
|
<td> |
||||
|
<t t-esc="new_index + 1"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['product_code_and_name']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['category_name']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['current_stock']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['incoming_quantity']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['outgoing_quantity']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['virtual_stock']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['sales']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['ads']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['demanded_quantity']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['in_stock_days']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['out_of_stock_days']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['out_of_stock_ratio']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['cost']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['out_of_stock_qty']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['out_of_stock_qty_percentage']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['out_of_stock_value']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['turnover_ratio']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['fsn_classification']"/> |
||||
|
</td> |
||||
|
</tr> |
||||
|
</tbody> |
||||
|
</table> |
||||
|
</t> |
||||
|
</t> |
||||
|
</template> |
||||
|
</odoo> |
@ -0,0 +1,303 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################### |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
||||
|
# Author: Gayathri V (odoo@cybrosys.com) |
||||
|
# |
||||
|
# You can modify it under the terms of the GNU LESSER |
||||
|
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. |
||||
|
# |
||||
|
# This program is distributed in the hope that it will be useful, |
||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE |
||||
|
# (LGPL v3) along with this program. |
||||
|
# If not, see <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) |
||||
|
ELSE |
||||
|
pt.name |
||||
|
END AS product_code_and_name, |
||||
|
company.id AS company_id, |
||||
|
company.name AS company_name, |
||||
|
sm.product_id AS product_id, |
||||
|
pc.id AS category_id, |
||||
|
pc.complete_name AS category_name, |
||||
|
sw.id AS warehouse_id, |
||||
|
SUM(CASE |
||||
|
WHEN sld_dest.usage = 'internal' AND sm.state |
||||
|
IN ('assigned', 'confirmed', 'waiting') THEN sm.product_uom_qty |
||||
|
ELSE 0 |
||||
|
END) AS incoming_quantity, |
||||
|
SUM(CASE |
||||
|
WHEN sld_src.usage = 'internal' AND sm.state |
||||
|
IN ('assigned', 'confirmed', 'waiting') THEN sm.product_uom_qty |
||||
|
ELSE 0 |
||||
|
END) AS outgoing_quantity, |
||||
|
SUM(CASE |
||||
|
WHEN sld_dest.usage = 'internal' AND sm.state = 'done' |
||||
|
THEN sm.product_uom_qty |
||||
|
ELSE 0 |
||||
|
END) - |
||||
|
SUM(CASE |
||||
|
WHEN sld_src.usage = 'internal' AND sm.state = 'done' |
||||
|
THEN sm.product_uom_qty |
||||
|
ELSE 0 |
||||
|
END) AS current_stock, |
||||
|
SUM(CASE |
||||
|
WHEN sld_dest.usage = 'internal' AND sm.state = 'done' |
||||
|
THEN sm.product_uom_qty |
||||
|
ELSE 0 |
||||
|
END) - |
||||
|
SUM(CASE |
||||
|
WHEN sld_src.usage = 'internal' AND sm.state = 'done' |
||||
|
THEN sm.product_uom_qty |
||||
|
ELSE 0 |
||||
|
END)+ |
||||
|
SUM(CASE |
||||
|
WHEN sld_dest.usage = 'internal' AND sm.state |
||||
|
IN ('assigned', 'confirmed', 'waiting') THEN sm.product_uom_qty |
||||
|
ELSE 0 |
||||
|
END) - |
||||
|
SUM(CASE |
||||
|
WHEN sld_src.usage = 'internal' AND sm.state |
||||
|
IN ('assigned', 'confirmed', 'waiting') THEN sm.product_uom_qty |
||||
|
ELSE 0 |
||||
|
END) AS virtual_stock, |
||||
|
SUM(CASE WHEN sm.date BETWEEN %s AND %s |
||||
|
AND sld_dest.usage = 'customer' |
||||
|
THEN sm.product_uom_qty ELSE 0 END) AS sales, |
||||
|
ROUND(SUM(CASE |
||||
|
WHEN sm.date BETWEEN %s AND %s AND sld_src.usage = 'internal' |
||||
|
AND sm.state = 'done' THEN sm.product_uom_qty |
||||
|
ELSE 0 |
||||
|
END) / ((date %s - date %s)+1), 2) AS ads, |
||||
|
%s AS advance_stock_days |
||||
|
FROM stock_move sm |
||||
|
INNER JOIN product_product pp ON pp.id = sm.product_id |
||||
|
INNER JOIN product_template pt ON pt.id = pp.product_tmpl_id |
||||
|
INNER JOIN res_company company ON company.id = sm.company_id |
||||
|
INNER JOIN stock_warehouse sw ON sw.company_id = company.id |
||||
|
INNER JOIN product_category pc ON pc.id = pt.categ_id |
||||
|
LEFT JOIN ( |
||||
|
SELECT sm.id AS move_id, usage |
||||
|
FROM stock_location sld |
||||
|
INNER JOIN stock_move sm ON sld.id = sm.location_dest_id |
||||
|
) sld_dest ON sm.id = sld_dest.move_id |
||||
|
LEFT JOIN ( |
||||
|
SELECT sm.id AS move_id, usage |
||||
|
FROM stock_location sld |
||||
|
INNER JOIN stock_move sm ON sld.id = sm.location_id |
||||
|
) sld_src ON sm.id = sld_src.move_id |
||||
|
WHERE pp.active = TRUE |
||||
|
AND pt.active = TRUE |
||||
|
AND pt.type = 'product' |
||||
|
""" |
||||
|
params = [ |
||||
|
start_date, end_date, |
||||
|
start_date, end_date, |
||||
|
end_date, start_date, |
||||
|
inventory_for_next_x_days |
||||
|
] |
||||
|
sub_queries = [] |
||||
|
sub_params = [] |
||||
|
param_count = 0 |
||||
|
if product_ids or category_ids: |
||||
|
query += " AND (" |
||||
|
if product_ids: |
||||
|
product_ids = [product_id for product_id in product_ids] |
||||
|
query += "pp.id = ANY(%s)" |
||||
|
params.append(product_ids) |
||||
|
param_count += 1 |
||||
|
if product_ids and category_ids: |
||||
|
query += " OR " |
||||
|
if category_ids: |
||||
|
category_ids = [category for category in category_ids] |
||||
|
params.append(category_ids) |
||||
|
query += "(pt.categ_id = ANY(%s))" |
||||
|
param_count += 1 |
||||
|
if product_ids or category_ids: |
||||
|
query += ")" |
||||
|
if company_ids: |
||||
|
company_ids = [company for company in company_ids] |
||||
|
query += " AND (sm.company_id = ANY(%s))" |
||||
|
sub_params.append(company_ids) |
||||
|
param_count += 1 |
||||
|
if warehouse_ids: |
||||
|
warehouse_ids = [warehouse for warehouse in warehouse_ids] |
||||
|
query += " AND (sw.id = ANY(%s))" |
||||
|
sub_params.append(warehouse_ids) |
||||
|
param_count += 1 |
||||
|
if sub_queries: |
||||
|
query += " AND " + " AND ".join(sub_queries) |
||||
|
query += """ GROUP BY pp.id, pt.name, pc.id, company.id, sm.product_id, |
||||
|
sw.id) AS sub_query """ |
||||
|
self.env.cr.execute(query, tuple(params + sub_params)) |
||||
|
result_data = self.env.cr.dictfetchall() |
||||
|
for data in result_data: |
||||
|
product_id = data.get('product_id') |
||||
|
if product_id not in processed_product_ids: |
||||
|
processed_product_ids.append( |
||||
|
product_id) |
||||
|
filtered_result_data.append(data) |
||||
|
for data in filtered_result_data: |
||||
|
over_stock_qty = data.get('over_stock_qty') |
||||
|
product_id = data.get('product_id') |
||||
|
total_qty = sum( |
||||
|
item.get('over_stock_qty', 0) for item in filtered_result_data) |
||||
|
if total_qty: |
||||
|
over_stock_qty_percentage = \ |
||||
|
(over_stock_qty / total_qty) * 100 |
||||
|
else: |
||||
|
over_stock_qty_percentage = 0.0 |
||||
|
data['over_stock_qty_percentage'] = round( |
||||
|
over_stock_qty_percentage, 2) |
||||
|
cost = self.env['product.product'].search([ |
||||
|
('id', '=', product_id)]).standard_price |
||||
|
data['cost'] = cost |
||||
|
data['over_stock_value'] = over_stock_qty * cost |
||||
|
latest_po = '' |
||||
|
confirmed_po = self.env['purchase.order.line'].search([ |
||||
|
('product_id', '=', product_id), |
||||
|
('state', '=', 'purchase'), |
||||
|
]) |
||||
|
for po in confirmed_po: |
||||
|
if latest_po: |
||||
|
if latest_po.date_approve < po.date_approve: |
||||
|
latest_po = po |
||||
|
else: |
||||
|
latest_po = po |
||||
|
data['po_qty'] = 0 |
||||
|
data['po_price_total'] = 0 |
||||
|
if latest_po: |
||||
|
start_date = fields.Datetime.from_string(start_date).date() |
||||
|
end_date = fields.Datetime.from_string(end_date).date() |
||||
|
po_date = fields.Datetime.from_string(latest_po.order_id.date_approve) |
||||
|
if start_date <= po_date.date() <= end_date: |
||||
|
data['po_qty'] += latest_po.product_qty |
||||
|
data['po_price_total'] += latest_po.price_total |
||||
|
data['po_date'] = po_date |
||||
|
data['po_currency'] = latest_po.currency_id.name |
||||
|
data['po_partner'] = latest_po.partner_id.name |
||||
|
else: |
||||
|
data['po_price_total'] = None |
||||
|
data['po_qty'] = None |
||||
|
data['po_currency'] = None |
||||
|
data['po_partner'] = None |
||||
|
data[ |
||||
|
'po_date'] = None |
||||
|
else: |
||||
|
data['po_price_total'] = None |
||||
|
data['po_qty'] = None |
||||
|
data['po_date'] = None |
||||
|
data['po_partner'] = None |
||||
|
data['po_currency'] = None |
||||
|
total_value = sum( |
||||
|
item.get('over_stock_value', 0) for item in filtered_result_data) |
||||
|
for data in filtered_result_data: |
||||
|
over_stock_value = data.get('over_stock_value') |
||||
|
if total_value: |
||||
|
over_stock_value_percentage = \ |
||||
|
(over_stock_value / total_value) * 100 |
||||
|
else: |
||||
|
over_stock_value_percentage = 0.0 |
||||
|
data['over_stock_value_percentage'] = round( |
||||
|
over_stock_value_percentage, 2) |
||||
|
if filtered_result_data: |
||||
|
return { |
||||
|
'doc_ids': docids, |
||||
|
'doc_model': |
||||
|
'report.inventory_advanced_reports.' |
||||
|
'report_inventory_over_stock', |
||||
|
'data': values, |
||||
|
'options': filtered_result_data, |
||||
|
} |
||||
|
else: |
||||
|
raise ValidationError("No records found for the given criteria!") |
@ -0,0 +1,153 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8" ?> |
||||
|
<odoo> |
||||
|
<!-- Action for the report--> |
||||
|
<record id="report_inventory_over_stock_action" model="ir.actions.report"> |
||||
|
<field name="name">Inventory Over Stock Report</field> |
||||
|
<field name="model">report.inventory_advanced_reports.report_inventory_over_stock</field> |
||||
|
<field name="report_type">qweb-pdf</field> |
||||
|
<field name="report_name">inventory_advanced_reports.report_inventory_over_stock</field> |
||||
|
<field name="report_file">inventory_advanced_reports.report_inventory_over_stock</field> |
||||
|
<field name="binding_model_id" |
||||
|
ref="model_inventory_over_stock_report"/> |
||||
|
<field name="paperformat_id" |
||||
|
ref="inventory_advanced_reports.paperformat_inventory_reports"/> |
||||
|
<field name="binding_type">report</field> |
||||
|
</record> |
||||
|
<!-- Template for the report--> |
||||
|
<template id="report_inventory_over_stock"> |
||||
|
<t t-call="web.html_container"> |
||||
|
<t t-call="web.external_layout"> |
||||
|
<br/> |
||||
|
<br/> |
||||
|
<br/> |
||||
|
<br/> |
||||
|
<div class="page"> |
||||
|
<div class="text-center"> |
||||
|
<h1>Inventory Over Stock Report</h1> |
||||
|
</div> |
||||
|
</div> |
||||
|
<br/> |
||||
|
<br/> |
||||
|
<div> |
||||
|
<div> |
||||
|
<t t-if="data.get('start_date') and data.get('end_date')"> |
||||
|
<strong> |
||||
|
Sales History from: |
||||
|
</strong> |
||||
|
<span t-esc="data['start_date']"/> |
||||
|
<br/> |
||||
|
<strong> |
||||
|
Sales History Up to: |
||||
|
</strong> |
||||
|
<span t-esc="data['end_date']"/> |
||||
|
<br/> |
||||
|
<strong> |
||||
|
Inventory Analysis For Next: |
||||
|
</strong> |
||||
|
<span t-esc="data['inventory_for_next_x_days']"/>days |
||||
|
</t> |
||||
|
</div> |
||||
|
</div> |
||||
|
<br/> |
||||
|
<table class="table table-condensed table-bordered table-striped"> |
||||
|
<thead> |
||||
|
<tr> |
||||
|
<th align="center">Sl.no</th> |
||||
|
<th align="center">PRODUCT</th> |
||||
|
<th align="center">CATEGORY</th> |
||||
|
<th align="center">CURRENT STOCK</th> |
||||
|
<th align="center">INCOMING</th> |
||||
|
<th align="center">OUTGOING</th> |
||||
|
<th align="center">VIRTUAL STOCK</th> |
||||
|
<th align="center">SALES</th> |
||||
|
<th align="center">ADS</th> |
||||
|
<th align="center">DEMANDED QTY</th> |
||||
|
<th align="center">COVERAGE DAYS</th> |
||||
|
<th align="center">OVER STOCK QTY</th> |
||||
|
<th align="center">OVER STOCK QTY(%)</th> |
||||
|
<th align="center">OVER STOCK VALUE</th> |
||||
|
<th align="center">OVER STOCK VALUE(%)</th> |
||||
|
<th align="center">TURNOVER RATIO(%)</th> |
||||
|
<th align="center">FSN CLASSIFICATION</th> |
||||
|
<th align="center">LAST PO DATE</th> |
||||
|
<th align="center">LAST PO QTY</th> |
||||
|
<th align="center">LAST PO PRICE</th> |
||||
|
<th align="center">CURRENCY</th> |
||||
|
<th align="center">PARTNER</th> |
||||
|
</tr> |
||||
|
</thead> |
||||
|
<tbody> |
||||
|
<tr t-foreach="options" t-as="new"> |
||||
|
<td> |
||||
|
<t t-esc="new_index + 1"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['product_code_and_name']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['category_name']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['current_stock']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['incoming_quantity']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['outgoing_quantity']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['virtual_stock']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['sales']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['ads']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['demanded_quantity']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['in_stock_days']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['over_stock_qty']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['over_stock_qty_percentage']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['over_stock_value']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['over_stock_value_percentage']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['turnover_ratio']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['fsn_classification']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['po_date']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['po_qty']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['po_price_total']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['po_currency']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['po_partner']"/> |
||||
|
</td> |
||||
|
</tr> |
||||
|
</tbody> |
||||
|
</table> |
||||
|
</t> |
||||
|
</t> |
||||
|
</template> |
||||
|
</odoo> |
@ -0,0 +1,201 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################### |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
||||
|
# Author: Gayathri V (odoo@cybrosys.com) |
||||
|
# |
||||
|
# You can modify it under the terms of the GNU LESSER |
||||
|
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. |
||||
|
# |
||||
|
# This program is distributed in the hope that it will be useful, |
||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE |
||||
|
# (LGPL v3) along with this program. |
||||
|
# If not, see <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) |
||||
|
ELSE |
||||
|
pt.name |
||||
|
END AS product_code_and_name, |
||||
|
pc.complete_name AS category_name, |
||||
|
company.name AS company_name, |
||||
|
""" |
||||
|
if report_up_to_certain_date: |
||||
|
query += """ |
||||
|
SUM(CASE WHEN sm.date <= %s AND sld_dest.usage = 'inventory' |
||||
|
THEN sm.product_uom_qty ELSE 0 END) AS opening_stock, |
||||
|
(SUM(CASE WHEN sm.date <= %s AND sld_dest.usage = 'internal' |
||||
|
THEN sm.product_uom_qty ELSE 0 END) - |
||||
|
SUM(CASE WHEN sm.date <= %s AND sld_src.usage = 'internal' |
||||
|
THEN sm.product_uom_qty ELSE 0 END)) AS closing_stock, |
||||
|
SUM(CASE WHEN sm.date <= %s AND sld_dest.usage = 'customer' |
||||
|
THEN sm.product_uom_qty ELSE 0 END) AS sales, |
||||
|
SUM(CASE WHEN sm.date <= %s AND sld_src.usage = 'customer' |
||||
|
THEN sm.product_uom_qty ELSE 0 END) AS sales_return, |
||||
|
SUM(CASE WHEN sm.date <= %s AND sld_src.usage = 'supplier' |
||||
|
THEN sm.product_uom_qty ELSE 0 END) AS purchase, |
||||
|
SUM(CASE WHEN sm.date <= %s AND sld_dest.usage = 'supplier' |
||||
|
THEN sm.product_uom_qty ELSE 0 END) AS purchase_return, |
||||
|
SUM(CASE WHEN sm.date <= %s AND sld_dest.usage = 'internal' |
||||
|
THEN sm.product_uom_qty ELSE 0 END) AS internal_in, |
||||
|
SUM(CASE WHEN sm.date <= %s AND sld_src.usage = 'internal' |
||||
|
THEN sm.product_uom_qty ELSE 0 END) AS internal_out, |
||||
|
SUM(CASE WHEN sm.date <= %s AND sld_dest.usage = 'inventory' |
||||
|
THEN sm.product_uom_qty ELSE 0 END) AS adj_in, |
||||
|
SUM(CASE WHEN sm.date <= %s AND sld_src.usage = 'inventory' |
||||
|
THEN sm.product_uom_qty ELSE 0 END) AS adj_out, |
||||
|
SUM(CASE WHEN sm.date <= %s |
||||
|
AND sld_dest.usage = 'production' |
||||
|
THEN sm.product_uom_qty ELSE 0 END) AS production_in, |
||||
|
SUM(CASE WHEN sm.date <= %s AND sld_src.usage = 'production' |
||||
|
THEN sm.product_uom_qty ELSE 0 END) AS production_out, |
||||
|
SUM(CASE WHEN sm.date <= %s AND sld_dest.usage = 'transit' |
||||
|
THEN sm.product_uom_qty ELSE 0 END) AS transit_in, |
||||
|
SUM(CASE WHEN sm.date <= %s AND sld_src.usage = 'transit' |
||||
|
THEN sm.product_uom_qty ELSE 0 END) AS transit_out |
||||
|
""" |
||||
|
params = [up_to_certain_date] * 15 |
||||
|
else: |
||||
|
query += """ |
||||
|
(SUM(CASE WHEN sm.date <= %s |
||||
|
AND sld_dest.usage = 'internal' |
||||
|
THEN sm.product_uom_qty ELSE 0 END) - |
||||
|
SUM(CASE WHEN sm.date <= %s |
||||
|
AND sld_src.usage = 'internal' |
||||
|
THEN sm.product_uom_qty ELSE 0 END)) AS opening_stock, |
||||
|
(SUM(CASE WHEN sm.date <= %s |
||||
|
AND sld_dest.usage = 'internal' |
||||
|
THEN sm.product_uom_qty ELSE 0 END) - |
||||
|
SUM(CASE WHEN sm.date <= %s |
||||
|
AND sld_src.usage = 'internal' |
||||
|
THEN sm.product_uom_qty ELSE 0 END)) AS closing_stock, |
||||
|
SUM(CASE WHEN sm.date BETWEEN %s AND %s |
||||
|
AND sld_dest.usage = 'customer' |
||||
|
THEN sm.product_uom_qty ELSE 0 END) AS sales, |
||||
|
SUM(CASE WHEN sm.date BETWEEN %s AND %s |
||||
|
AND sld_src.usage = 'customer' |
||||
|
THEN sm.product_uom_qty ELSE 0 END) AS sales_return, |
||||
|
SUM(CASE WHEN sm.date BETWEEN %s AND %s |
||||
|
AND sld_src.usage = 'supplier' |
||||
|
THEN sm.product_uom_qty ELSE 0 END) AS purchase, |
||||
|
SUM(CASE WHEN sm.date BETWEEN %s AND %s |
||||
|
AND sld_dest.usage = 'supplier' |
||||
|
THEN sm.product_uom_qty ELSE 0 END) AS purchase_return, |
||||
|
SUM(CASE WHEN sm.date BETWEEN %s AND %s |
||||
|
AND sld_dest.usage = 'internal' |
||||
|
THEN sm.product_uom_qty ELSE 0 END) AS internal_in, |
||||
|
SUM(CASE WHEN sm.date BETWEEN %s AND %s |
||||
|
AND sld_src.usage = 'internal' |
||||
|
THEN sm.product_uom_qty ELSE 0 END) AS internal_out, |
||||
|
SUM(CASE WHEN sm.date BETWEEN %s AND %s |
||||
|
AND sld_dest.usage = 'inventory' |
||||
|
THEN sm.product_uom_qty ELSE 0 END) AS adj_in, |
||||
|
SUM(CASE WHEN sm.date BETWEEN %s AND %s |
||||
|
AND sld_src.usage = 'inventory' |
||||
|
THEN sm.product_uom_qty ELSE 0 END) AS adj_out, |
||||
|
SUM(CASE WHEN sm.date BETWEEN %s |
||||
|
AND %s AND sld_dest.usage = 'production' |
||||
|
THEN sm.product_uom_qty ELSE 0 END) AS production_in, |
||||
|
SUM(CASE WHEN sm.date BETWEEN %s AND %s |
||||
|
AND sld_src.usage = 'production' |
||||
|
THEN sm.product_uom_qty ELSE 0 END) AS production_out, |
||||
|
SUM(CASE WHEN sm.date BETWEEN %s |
||||
|
AND %s AND sld_dest.usage = 'transit' |
||||
|
THEN sm.product_uom_qty ELSE 0 END) AS transit_in, |
||||
|
SUM(CASE WHEN sm.date BETWEEN %s |
||||
|
AND %s AND sld_src.usage = 'transit' |
||||
|
THEN sm.product_uom_qty ELSE 0 END) AS transit_out |
||||
|
""" |
||||
|
params = [start_date, start_date, end_date, end_date, start_date, |
||||
|
end_date, start_date, end_date, start_date, end_date, |
||||
|
start_date, end_date, start_date, end_date, start_date, |
||||
|
end_date, start_date, end_date, start_date, end_date, |
||||
|
start_date, end_date, start_date, end_date, start_date, |
||||
|
end_date, start_date, end_date] |
||||
|
query += """ |
||||
|
FROM stock_move sm |
||||
|
INNER JOIN product_product pp ON pp.id = sm.product_id |
||||
|
INNER JOIN product_template pt ON pt.id = pp.product_tmpl_id |
||||
|
INNER JOIN res_company company ON company.id = sm.company_id |
||||
|
INNER JOIN stock_warehouse sw ON sw.company_id = company.id |
||||
|
INNER JOIN product_category pc ON pc.id = pt.categ_id |
||||
|
""" |
||||
|
query += """ |
||||
|
LEFT JOIN stock_location sld_dest |
||||
|
ON sm.location_dest_id = sld_dest.id |
||||
|
LEFT JOIN stock_location sld_src |
||||
|
ON sm.location_id = sld_src.id |
||||
|
WHERE |
||||
|
sm.state = 'done' |
||||
|
""" |
||||
|
sub_queries = [] |
||||
|
if product_ids: |
||||
|
product_ids = [product_id for product_id in product_ids] |
||||
|
sub_queries.append("pp.id = ANY(%s)") |
||||
|
params.append(product_ids) |
||||
|
if category_ids: |
||||
|
category_ids = [category for category in category_ids] |
||||
|
sub_queries.append("pt.categ_id = ANY(%s)") |
||||
|
params.append(category_ids) |
||||
|
if sub_queries: |
||||
|
query += " AND (" + " OR ".join(sub_queries) + ")" |
||||
|
if company_ids: |
||||
|
company_ids = [company for company in company_ids] |
||||
|
query += " AND sm.company_id = ANY(%s)" |
||||
|
params.append(company_ids) |
||||
|
if warehouse_ids: |
||||
|
warehouse_ids = [warehouse for warehouse in warehouse_ids] |
||||
|
query += " AND sw.id = ANY(%s)" |
||||
|
params.append(warehouse_ids) |
||||
|
query += """ |
||||
|
GROUP BY pp.id,pt.name,pc.complete_name,company.name |
||||
|
""" |
||||
|
self.env.cr.execute(query, params) |
||||
|
result_data = self.env.cr.dictfetchall() |
||||
|
if result_data: |
||||
|
return { |
||||
|
'doc_ids': docids, |
||||
|
'doc_model': 'inventory.overstock.report', |
||||
|
'data': values, |
||||
|
'options': result_data, |
||||
|
} |
||||
|
else: |
||||
|
raise ValidationError("No records found for the given criteria!") |
@ -0,0 +1,140 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8" ?> |
||||
|
<odoo> |
||||
|
<!-- Action for the report--> |
||||
|
<record id="report_inventory_stock_movement_action" |
||||
|
model="ir.actions.report"> |
||||
|
<field name="name">Inventory Stock Movement Report</field> |
||||
|
<field name="model">report.inventory_advanced_reports.report_inventory_movement</field> |
||||
|
<field name="report_type">qweb-pdf</field> |
||||
|
<field name="report_name">inventory_advanced_reports.report_inventory_movement</field> |
||||
|
<field name="report_file">inventory_advanced_reports.report_inventory_movement</field> |
||||
|
<field name="binding_model_id" |
||||
|
ref="model_inventory_stock_movement_report"/> |
||||
|
<field name="paperformat_id" |
||||
|
ref="inventory_advanced_reports.paperformat_inventory_reports"/> |
||||
|
<field name="binding_type">report</field> |
||||
|
</record> |
||||
|
<!-- Template for the report--> |
||||
|
<template id="report_inventory_movement"> |
||||
|
<t t-call="web.html_container"> |
||||
|
<t t-call="web.external_layout"> |
||||
|
<br/> |
||||
|
<br/> |
||||
|
<br/> |
||||
|
<br/> |
||||
|
<br/> |
||||
|
<div class="page"> |
||||
|
<div class="text-center"> |
||||
|
<h1>Inventory Stock Movement Report</h1> |
||||
|
</div> |
||||
|
</div> |
||||
|
<br/> |
||||
|
<br/> |
||||
|
<div> |
||||
|
<div> |
||||
|
<t t-if="data.get('start_date') and data.get('end_date') and not data['up_to_certain_date']"> |
||||
|
<strong> |
||||
|
Date from: |
||||
|
</strong> |
||||
|
<span t-esc="data['start_date']"/> |
||||
|
<br/> |
||||
|
<strong> |
||||
|
Date to: |
||||
|
</strong> |
||||
|
<span t-esc="data['end_date']"/> |
||||
|
</t> |
||||
|
<t t-if="data.get('up_to_certain_date')"> |
||||
|
<strong> |
||||
|
Stock Movements Up To: |
||||
|
</strong> |
||||
|
<span t-esc="data['up_to_certain_date']"/> |
||||
|
</t> |
||||
|
</div> |
||||
|
</div> |
||||
|
<br/> |
||||
|
<table class="table table-condensed table-bordered table-striped"> |
||||
|
<thead> |
||||
|
<tr> |
||||
|
<th align="center">Sl.no</th> |
||||
|
<th align="center">COMPANY</th> |
||||
|
<th align="center">PRODUCT</th> |
||||
|
<th align="center">CATEGORY</th> |
||||
|
<th align="center">OPENING STOCK</th> |
||||
|
<th align="center">SALES</th> |
||||
|
<th align="center">SALES RETURN</th> |
||||
|
<th align="center">PURCHASE</th> |
||||
|
<th align="center">PURCHASE RETURN</th> |
||||
|
<th align="center">INTERNAL IN</th> |
||||
|
<th align="center">INTERNAL OUT</th> |
||||
|
<th align="center">ADJUSTMENT IN</th> |
||||
|
<th align="center">ADJUSTMENT OUT</th> |
||||
|
<th align="center">PRODUCTION IN</th> |
||||
|
<th align="center">PRODUCTION OUT</th> |
||||
|
<th align="center">TRANSIT IN</th> |
||||
|
<th align="center">TRANSIT OUT</th> |
||||
|
<th align="center">CLOSING STOCK</th> |
||||
|
</tr> |
||||
|
</thead> |
||||
|
<tbody> |
||||
|
<tr t-foreach="options" t-as="new"> |
||||
|
<td> |
||||
|
<t t-esc="new_index + 1"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['company_name']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['product_code_and_name']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['category_name']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['opening_stock']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['sales']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['sales_return']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['purchase']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['purchase_return']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['internal_in']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['internal_out']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['adj_in']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['adj_out']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['production_in']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['production_out']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['transit_in']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['transit_out']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['closing_stock']"/> |
||||
|
</td> |
||||
|
</tr> |
||||
|
</tbody> |
||||
|
</table> |
||||
|
</t> |
||||
|
</t> |
||||
|
</template> |
||||
|
</odoo> |
@ -0,0 +1,142 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################### |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
||||
|
# Author: Gayathri V (odoo@cybrosys.com) |
||||
|
# |
||||
|
# You can modify it under the terms of the GNU LESSER |
||||
|
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. |
||||
|
# |
||||
|
# This program is distributed in the hope that it will be useful, |
||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE |
||||
|
# (LGPL v3) along with this program. |
||||
|
# If not, see <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) |
||||
|
ELSE |
||||
|
pt.name |
||||
|
END AS product_code_and_name, |
||||
|
svl.company_id, |
||||
|
company.name AS company_name, |
||||
|
svl.product_id, |
||||
|
pt.categ_id AS product_category_id, |
||||
|
c.complete_name AS category_name, |
||||
|
SUM(svl.remaining_qty) AS current_stock, |
||||
|
SUM(svl.remaining_value) AS stock_value |
||||
|
FROM stock_valuation_layer svl |
||||
|
INNER JOIN res_company company ON company.id = svl.company_id |
||||
|
INNER JOIN product_product pp ON pp.id = svl.product_id |
||||
|
INNER JOIN product_template pt ON pt.id = pp.product_tmpl_id |
||||
|
INNER JOIN product_category c ON c.id = pt.categ_id |
||||
|
WHERE pp.active = TRUE |
||||
|
AND pt.active = TRUE |
||||
|
AND pt.type = 'product' |
||||
|
AND svl.remaining_value IS NOT NULL |
||||
|
""" |
||||
|
if company_ids: |
||||
|
company_ids = [company_id for company_id in company_ids] |
||||
|
query += f" AND (company.id IS NULL OR company.id = ANY(%s))" |
||||
|
params.append(company_ids) |
||||
|
param_count += 1 |
||||
|
if product_ids or category_ids: |
||||
|
query += " AND (" |
||||
|
if product_ids: |
||||
|
product_ids = [product_id for product_id in product_ids] |
||||
|
query += f"pp.id = ANY(%s)" |
||||
|
params.append(product_ids) |
||||
|
param_count += 1 |
||||
|
if product_ids and category_ids: |
||||
|
query += " OR " |
||||
|
if category_ids: |
||||
|
category_ids = [category_id for category_id in |
||||
|
category_ids] |
||||
|
query += f"c.id = ANY(%s)" |
||||
|
params.append(category_ids) |
||||
|
param_count += 1 |
||||
|
query += ")" |
||||
|
query += """ |
||||
|
GROUP BY |
||||
|
svl.company_id, |
||||
|
company.name, |
||||
|
svl.product_id, |
||||
|
CASE |
||||
|
WHEN pp.default_code IS NOT NULL |
||||
|
THEN CONCAT(pp.default_code, ' - ', pt.name) |
||||
|
ELSE |
||||
|
pt.name |
||||
|
END, |
||||
|
pt.categ_id, |
||||
|
c.complete_name |
||||
|
ORDER BY SUM(svl.remaining_value) DESC; |
||||
|
""" |
||||
|
self.env.cr.execute(query, params) |
||||
|
result_data = self.env.cr.dictfetchall() |
||||
|
total_current_value = 0 |
||||
|
cumulative_stock = 0 |
||||
|
filtered_stock = [] |
||||
|
for row in result_data: |
||||
|
current_value = row.get('stock_value') |
||||
|
total_current_value += current_value |
||||
|
for value in result_data: |
||||
|
current_value = value.get('stock_value') |
||||
|
if total_current_value != 0 and current_value: |
||||
|
stock_percentage = (current_value / total_current_value) * 100 |
||||
|
else: |
||||
|
stock_percentage = 0.0 |
||||
|
value['stock_percentage'] = round(stock_percentage, 2) |
||||
|
cumulative_stock += value['stock_percentage'] |
||||
|
value['cumulative_stock_percentage'] = round(cumulative_stock, 2) |
||||
|
if cumulative_stock < 70: |
||||
|
xyz_classification = 'X' |
||||
|
elif 70 <= cumulative_stock <= 90: |
||||
|
xyz_classification = 'Y' |
||||
|
else: |
||||
|
xyz_classification = 'Z' |
||||
|
value['xyz_classification'] = xyz_classification |
||||
|
if result_data: |
||||
|
for xyz_class in result_data: |
||||
|
if xyz_class.get('xyz_classification') == str(xyz): |
||||
|
filtered_stock.append(xyz_class) |
||||
|
if xyz == 'All' and not result_data: |
||||
|
raise ValidationError("No corresponding data to print") |
||||
|
elif xyz != 'All' and filtered_stock == []: |
||||
|
raise ValidationError("No corresponding data to print") |
||||
|
return { |
||||
|
'doc_ids': docids, |
||||
|
'doc_model': |
||||
|
'report.inventory_advanced_reports.report_inventory_xyz', |
||||
|
'data': values, |
||||
|
'options': result_data if xyz == 'All' else filtered_stock, |
||||
|
} |
||||
|
else: |
||||
|
raise ValidationError("No records found for the given criteria!") |
@ -0,0 +1,67 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<odoo> |
||||
|
<!-- action for the report--> |
||||
|
<record id="report_inventory_xyz_action" model="ir.actions.report"> |
||||
|
<field name="name">Inventory XYZ Report</field> |
||||
|
<field name="model">report.inventory_advanced_reports.report_inventory_xyz</field> |
||||
|
<field name="report_type">qweb-pdf</field> |
||||
|
<field name="report_name">inventory_advanced_reports.report_inventory_xyz</field> |
||||
|
<field name="report_file">inventory_advanced_reports.report_inventory_xyz</field> |
||||
|
<field name="binding_model_id" |
||||
|
ref="model_inventory_aging_report"/> |
||||
|
<field name="binding_type">report</field> |
||||
|
</record> |
||||
|
<!-- Template for the report--> |
||||
|
<template id="report_inventory_xyz"> |
||||
|
<t t-call="web.html_container"> |
||||
|
<t t-call="web.external_layout"> |
||||
|
<div class="page"> |
||||
|
<div class="text-center"> |
||||
|
<h1>Inventory XYZ Report</h1> |
||||
|
</div> |
||||
|
</div> |
||||
|
<table class="table table-condensed table-bordered table-striped"> |
||||
|
<thead> |
||||
|
<tr> |
||||
|
<th align="center">Sl.no</th> |
||||
|
<th align="center">PRODUCT</th> |
||||
|
<th align="center">CATEGORY</th> |
||||
|
<th align="center">CURRENT STOCK</th> |
||||
|
<th align="center">STOCK VALUE</th> |
||||
|
<th align="center">STOCK VALUE(%)</th> |
||||
|
<th align="center">CUMULATIVE STOCK(%)</th> |
||||
|
<th align="center">XYZ CLASSIFICATION</th> |
||||
|
</tr> |
||||
|
</thead> |
||||
|
<tbody> |
||||
|
<tr t-foreach="options" t-as="new"> |
||||
|
<t t-log="new"/> |
||||
|
<td> |
||||
|
<t t-esc="new_index + 1"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['product_code_and_name']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['category_name']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['current_stock']"/> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['stock_value']"/> |
||||
|
</td> |
||||
|
<td><t t-esc="new['stock_percentage']"/>% |
||||
|
</td> |
||||
|
<td><t t-esc="new['cumulative_stock_percentage']"/>% |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="new['xyz_classification']"/> |
||||
|
</td> |
||||
|
</tr> |
||||
|
</tbody> |
||||
|
</table> |
||||
|
</t> |
||||
|
</t> |
||||
|
</template> |
||||
|
</odoo> |
|
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 310 B |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 576 B |
After Width: | Height: | Size: 733 B |
After Width: | Height: | Size: 911 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 673 B |
After Width: | Height: | Size: 878 B |
After Width: | Height: | Size: 653 B |
After Width: | Height: | Size: 905 B |
After Width: | Height: | Size: 839 B |
After Width: | Height: | Size: 427 B |
After Width: | Height: | Size: 627 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 988 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 589 B |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 967 B |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 5.0 KiB |
After Width: | Height: | Size: 55 KiB |
After Width: | Height: | Size: 75 KiB |
After Width: | Height: | Size: 45 KiB |
After Width: | Height: | Size: 49 KiB |
After Width: | Height: | Size: 51 KiB |
After Width: | Height: | Size: 47 KiB |
After Width: | Height: | Size: 59 KiB |
After Width: | Height: | Size: 60 KiB |
After Width: | Height: | Size: 60 KiB |
After Width: | Height: | Size: 60 KiB |
After Width: | Height: | Size: 56 KiB |
After Width: | Height: | Size: 58 KiB |
After Width: | Height: | Size: 45 KiB |
After Width: | Height: | Size: 76 KiB |
After Width: | Height: | Size: 49 KiB |
After Width: | Height: | Size: 77 KiB |
After Width: | Height: | Size: 33 KiB |
After Width: | Height: | Size: 56 KiB |
After Width: | Height: | Size: 42 KiB |
After Width: | Height: | Size: 64 KiB |
After Width: | Height: | Size: 42 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 42 KiB |
After Width: | Height: | Size: 48 KiB |
After Width: | Height: | Size: 80 KiB |
After Width: | Height: | Size: 53 KiB |
After Width: | Height: | Size: 60 KiB |
After Width: | Height: | Size: 65 KiB |
After Width: | Height: | Size: 57 KiB |
After Width: | Height: | Size: 67 KiB |
After Width: | Height: | Size: 86 KiB |
After Width: | Height: | Size: 57 KiB |
After Width: | Height: | Size: 42 KiB |
After Width: | Height: | Size: 57 KiB |
After Width: | Height: | Size: 69 KiB |
After Width: | Height: | Size: 40 KiB |
After Width: | Height: | Size: 119 KiB |
After Width: | Height: | Size: 67 KiB |
After Width: | Height: | Size: 52 KiB |
After Width: | Height: | Size: 67 KiB |
After Width: | Height: | Size: 34 KiB |