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