@ -0,0 +1,45 @@ |
|||||
|
.. image:: https://img.shields.io/badge/license-AGPL--3-blue.svg |
||||
|
:target: https://www.gnu.org/licenses/agpl-3.0-standalone.html |
||||
|
:alt: License: AGPL-3 |
||||
|
|
||||
|
Product Expiry Dashboard |
||||
|
======================== |
||||
|
This Module help to analyse products and their expiry |
||||
|
|
||||
|
Configuration |
||||
|
============= |
||||
|
* No configuration needed |
||||
|
|
||||
|
Company |
||||
|
------- |
||||
|
* `Cybrosys Techno Solutions <https://cybrosys.com/>`__ |
||||
|
|
||||
|
Credits |
||||
|
------- |
||||
|
* Developer: (V16) Hafeesul Ali, Kailas Krishna |
||||
|
Contact: odoo@cybrosys.com |
||||
|
|
||||
|
Contacts |
||||
|
-------- |
||||
|
* Mail Contact : odoo@cybrosys.com |
||||
|
* Website : https://cybrosys.com |
||||
|
|
||||
|
License |
||||
|
------- |
||||
|
General Public License, Version 3 (AGPL v3). |
||||
|
(https://www.gnu.org/licenses/agpl-3.0-standalone.html) |
||||
|
|
||||
|
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 `Our Website <https://cybrosys.com/>`__ |
||||
|
|
||||
|
Further information |
||||
|
=================== |
||||
|
HTML Description: `<static/description/index.html>`__ |
@ -0,0 +1,22 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################# |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
||||
|
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>) |
||||
|
# |
||||
|
# You can modify it under the terms of the GNU AFFERO |
||||
|
# GENERAL PUBLIC LICENSE (AGPL 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 AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE |
||||
|
# (AGPL v3) along with this program. |
||||
|
# If not, see <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
############################################################################# |
||||
|
from . import models |
@ -0,0 +1,48 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################# |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
||||
|
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>) |
||||
|
# |
||||
|
# You can modify it under the terms of the GNU AFFERO |
||||
|
# GENERAL PUBLIC LICENSE (AGPL 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 AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE |
||||
|
# (AGPL v3) along with this program. |
||||
|
# If not, see <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
############################################################################# |
||||
|
{ |
||||
|
'name': 'Product Expiry Dashboard', |
||||
|
'version': '16.0.1.0.0', |
||||
|
'category': 'Productivity', |
||||
|
'summary': 'This module provides visualized product expiry data', |
||||
|
'description': 'This module presents visualized charts showcasing ' |
||||
|
'product expiration data categorized by location and ' |
||||
|
'category', |
||||
|
'author': 'Cybrosys Techno Solutions', |
||||
|
'company': 'Cybrosys Techno Solutions', |
||||
|
'maintainer': 'Cybrosys Techno Solutions', |
||||
|
'website': 'https://www.cybrosys.com', |
||||
|
'depends': ['product_expiry', 'stock'], |
||||
|
'data': ['views/product_expiry_views.xml'], |
||||
|
'assets': { |
||||
|
'web.assets_backend': [ |
||||
|
'odoo_product_expiry_dashboard/static/src/js/product_expiry_action.js', |
||||
|
'odoo_product_expiry_dashboard/static/src/xml/product_expiry_dashboard.xml', |
||||
|
'odoo_product_expiry_dashboard/static/src/css/style.css', |
||||
|
'https://cdn.jsdelivr.net/npm/chart.js' |
||||
|
]}, |
||||
|
'images': ['static/description/banner.jpg'], |
||||
|
'license': 'AGPL-3', |
||||
|
'installable': True, |
||||
|
'auto_install': False, |
||||
|
'application': False |
||||
|
} |
@ -0,0 +1,6 @@ |
|||||
|
## Module <odoo_product_expiry_dashboard> |
||||
|
|
||||
|
#### 17.08.2024 |
||||
|
#### Version 16.0.1.0.0 |
||||
|
#### ADD |
||||
|
- Initial commit for Product Expiry Dashboard |
@ -0,0 +1,22 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################# |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
||||
|
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>) |
||||
|
# |
||||
|
# You can modify it under the terms of the GNU AFFERO |
||||
|
# GENERAL PUBLIC LICENSE (AGPL 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 AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE |
||||
|
# (AGPL v3) along with this program. |
||||
|
# If not, see <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
############################################################################# |
||||
|
from . import stock_lot |
@ -0,0 +1,304 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################# |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
||||
|
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>) |
||||
|
# |
||||
|
# You can modify it under the terms of the GNU AFFERO |
||||
|
# GENERAL PUBLIC LICENSE (AGPL 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 AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE |
||||
|
# (AGPL v3) along with this program. |
||||
|
# If not, see <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
############################################################################# |
||||
|
from odoo import api, fields, models |
||||
|
|
||||
|
|
||||
|
class StockLot(models.Model): |
||||
|
_inherit = "stock.lot" |
||||
|
|
||||
|
@api.model |
||||
|
def search_params(self, start_date, end_date, enabled_companies): |
||||
|
"""Method to get domain for searching.""" |
||||
|
if start_date and end_date: |
||||
|
search_params = [('expiration_date', '>=', start_date), |
||||
|
('expiration_date', '<=', end_date), |
||||
|
('company_id', 'in', enabled_companies)] |
||||
|
elif start_date: |
||||
|
search_params = [('expiration_date', '>=', start_date), |
||||
|
('company_id', 'in', enabled_companies)] |
||||
|
else: |
||||
|
search_params = [('company_id', 'in', enabled_companies)] |
||||
|
return search_params |
||||
|
|
||||
|
@api.model |
||||
|
def get_product_expiry(self, *args): |
||||
|
"""Method to get products that expires in 1 day ,7 days,30 days |
||||
|
and 120 days. |
||||
|
Args: |
||||
|
*args(dict):Start date and End date to add filtration |
||||
|
Returns: |
||||
|
dict: A dict contains 1 day ,7 day ,30 day,120 day, and their |
||||
|
respective counts of products that will expire. |
||||
|
""" |
||||
|
data = [{"one_day": [], "counts": 0}, |
||||
|
{"seven_day": [], "counts": 0}, |
||||
|
{"thirty_day": [], "counts": 0}, |
||||
|
{"one_twenty_day": [], "counts": 0}] |
||||
|
search_params = self.search_params(args[0].get('start_date'), |
||||
|
args[0].get('end_date'), args[1]) |
||||
|
for record in self.search(search_params): |
||||
|
if record.expiration_date and record.product_qty != 0: |
||||
|
date_difference = (fields.Date.to_date( |
||||
|
record.expiration_date) - fields.Date.today()).days |
||||
|
if date_difference == 1: |
||||
|
data[0]["one_day"].append(record.id) |
||||
|
data[0]["counts"] += record.product_qty |
||||
|
elif 7 >= date_difference >= 1: |
||||
|
data[1]["seven_day"].append(record.id) |
||||
|
data[1]["counts"] += record.product_qty |
||||
|
elif 30 >= date_difference > 7: |
||||
|
data[2]["thirty_day"].append(record.id) |
||||
|
data[2]["counts"] += record.product_qty |
||||
|
elif 120 >= date_difference > 30: |
||||
|
data[3]["one_twenty_day"].append(record.id) |
||||
|
return data |
||||
|
|
||||
|
@api.model |
||||
|
def get_product_expired_today(self, enabled_companies): |
||||
|
""" |
||||
|
Method to get products that expired today |
||||
|
Returns: |
||||
|
int:count of products. |
||||
|
""" |
||||
|
count = len([record for record in self.search([]) if |
||||
|
record.expiration_date and |
||||
|
fields.Date.to_date(record.expiration_date) == fields.date.today() and |
||||
|
record.company_id.id in enabled_companies]) |
||||
|
return count |
||||
|
|
||||
|
@api.model |
||||
|
def get_expired_product(self, *args): |
||||
|
""" |
||||
|
Method to get products that expired |
||||
|
Args: |
||||
|
*args(dict):Start date and End date to add filtration. |
||||
|
Returns: |
||||
|
dict: A dict that contains expired products and their count. |
||||
|
""" |
||||
|
search_params = self.search_params(args[0].get('start_date'), |
||||
|
args[0].get('end_date')) |
||||
|
products_dict = {record: {'product_qty': record.product_qty, |
||||
|
'product': record.product_id.name} |
||||
|
for record in self.search(search_params) |
||||
|
if record.expiration_date and record.product_qty != 0 |
||||
|
and record.product_expiry_alert} |
||||
|
expired_products_dict = {product: sum(data['product_qty'] |
||||
|
for lot, data in |
||||
|
products_dict.items() if |
||||
|
data['product'] == product) |
||||
|
for product in set( |
||||
|
data['product'] for lot, data in products_dict.items())} |
||||
|
return expired_products_dict |
||||
|
|
||||
|
@api.model |
||||
|
def get_product_expiry_by_category(self, *args): |
||||
|
""" |
||||
|
Method to get category of products that expired. |
||||
|
Args: |
||||
|
*args(dict):Start date and End date to add filtration. |
||||
|
Returns: |
||||
|
dict: A dict that contains expired products category and their |
||||
|
count. |
||||
|
""" |
||||
|
search_params = self.search_params(args[0].get('start_date'), |
||||
|
args[0].get('end_date')) |
||||
|
products_category_dict = {record: {'product_qty': record.product_qty, |
||||
|
'product_category': |
||||
|
record.product_id.categ_id.name} |
||||
|
for record in self.search(search_params) |
||||
|
if record.expiration_date and |
||||
|
record.product_qty != 0 |
||||
|
and record.product_expiry_alert} |
||||
|
expired_product_category_dict = {product: sum(data['product_qty'] |
||||
|
for lot, data in |
||||
|
products_category_dict. |
||||
|
items() if |
||||
|
data['product_category'] |
||||
|
== product) |
||||
|
for product in |
||||
|
set(data['product_category'] |
||||
|
for lot, data in |
||||
|
products_category_dict.items())} |
||||
|
return expired_product_category_dict |
||||
|
|
||||
|
@api.model |
||||
|
def get_near_expiry_product(self, *args): |
||||
|
""" |
||||
|
Method to get products that will expire in coming 7 days. |
||||
|
Returns: |
||||
|
dict:A dict that contains products and their count |
||||
|
""" |
||||
|
search_params = self.search_params(args[0].get('start_date'), |
||||
|
args[0].get('end_date')) |
||||
|
if len(search_params) != 0: |
||||
|
product_dict = {record: {'product_name': record.product_id.name, |
||||
|
'product_qty': record.product_qty |
||||
|
} |
||||
|
for record in self.search(search_params) |
||||
|
if |
||||
|
record.expiration_date and record.product_qty != 0} |
||||
|
|
||||
|
else: |
||||
|
product_dict = {record: {'product_name': record.product_id.name, |
||||
|
'product_qty': record.product_qty |
||||
|
} |
||||
|
for record in self.search([]) |
||||
|
if |
||||
|
record.expiration_date and record.product_qty and 7 >= |
||||
|
(fields.Date.to_date(record.expiration_date) - |
||||
|
fields.Date.today()).days > 0} |
||||
|
nearby_exp_products_dict = {product: sum( |
||||
|
data['product_qty'] for lot, data in product_dict.items() if |
||||
|
data['product_name'] == product) |
||||
|
for product in |
||||
|
set(data['product_name'] for lot, data in |
||||
|
product_dict.items()) |
||||
|
} |
||||
|
return nearby_exp_products_dict |
||||
|
|
||||
|
@api.model |
||||
|
def get_near_expiry_category(self, *args): |
||||
|
""" |
||||
|
Method to get cate'gory of products that will expire in coming 7 days. |
||||
|
Returns: |
||||
|
dict:A dict that contains category and their count |
||||
|
""" |
||||
|
search_params = self.search_params(args[0].get('start_date'), |
||||
|
args[0].get('end_date')) |
||||
|
if len(search_params) != 0: |
||||
|
product_dict = { |
||||
|
record: {'category': record.product_id.categ_id.name, |
||||
|
'product_qty': record.product_qty |
||||
|
} |
||||
|
for record in self.search(search_params) |
||||
|
if |
||||
|
record.expiration_date and record.product_qty != 0} |
||||
|
else: |
||||
|
product_dict = {record: {'category': record.product_id.categ_id.name, |
||||
|
'product_qty': record.product_qty |
||||
|
} |
||||
|
for record in self.search([]) |
||||
|
if |
||||
|
record.expiration_date and record.product_qty and 7 >= |
||||
|
(fields.Date.to_date(record.expiration_date) - |
||||
|
fields.Date.today()).days > 0} |
||||
|
|
||||
|
nearby_exp_products_dict = {product: sum( |
||||
|
data['product_qty'] for lot, data in product_dict.items() if |
||||
|
data['category'] == product) |
||||
|
for product in |
||||
|
set(data['category'] for lot, data in |
||||
|
product_dict.items()) |
||||
|
} |
||||
|
return nearby_exp_products_dict |
||||
|
|
||||
|
@api.model |
||||
|
def get_expire_product_location(self, *args): |
||||
|
"""Method to get products locations that will expire |
||||
|
in coming 7 days |
||||
|
Returns: |
||||
|
dict:A dict of location and their respective count |
||||
|
""" |
||||
|
search_params = self.search_params(args[0].get('start_date'), |
||||
|
args[0].get('end_date')) |
||||
|
if len(search_params) != 0: |
||||
|
location_dict = { |
||||
|
record: {'location': location.location_id.display_name, |
||||
|
'count': location.inventory_quantity_auto_apply} for |
||||
|
record in self.search(search_params) if |
||||
|
record.expiration_date and record.product_qty != 0 |
||||
|
for location in record.quant_ids if |
||||
|
location.inventory_quantity_auto_apply > 0} |
||||
|
else: |
||||
|
location_dict = { |
||||
|
record: {'location': location.location_id.display_name, |
||||
|
'count': location.inventory_quantity_auto_apply} for |
||||
|
record in self.search([]) if |
||||
|
record.expiration_date and record.product_qty and 7 >= ( |
||||
|
fields.Date.to_date(record.expiration_date) - fields.Date.today()).days > 0 |
||||
|
for location in record.quant_ids if |
||||
|
location.inventory_quantity_auto_apply > 0} |
||||
|
nearby_expiry_location = {product: sum( |
||||
|
data['count'] for lot, data in location_dict.items() if |
||||
|
data['location'] == product) for product in set( |
||||
|
data['location'] for lot, data in location_dict.items())} |
||||
|
return nearby_expiry_location |
||||
|
|
||||
|
@api.model |
||||
|
def get_expire_product_warehouse(self, *args): |
||||
|
"""Method to get products warehouse that will expire |
||||
|
in coming 7 days |
||||
|
Returns: |
||||
|
dict:A dict of warehouse and their respective counts. |
||||
|
""" |
||||
|
search_params = self.search_params(args[0].get('start_date'), |
||||
|
args[0].get('end_date')) |
||||
|
if len(search_params) != 0: |
||||
|
warehouse_dict = { |
||||
|
record: { |
||||
|
'warehouse': location.location_id.warehouse_id.display_name, |
||||
|
'count': location.inventory_quantity_auto_apply} for |
||||
|
record in self.search([]) if |
||||
|
record.expiration_date and record.product_qty != 0 |
||||
|
for location in record.quant_ids if |
||||
|
location.inventory_quantity_auto_apply > 0} |
||||
|
else: |
||||
|
warehouse_dict = { |
||||
|
record: { |
||||
|
'warehouse': location.location_id.warehouse_id.display_name, |
||||
|
'count': location.inventory_quantity_auto_apply} for |
||||
|
record in self.search([]) if |
||||
|
record.expiration_date and record.product_qty and 7 >= ( |
||||
|
fields.Date.to_date( |
||||
|
record.expiration_date) - fields.Date.today()).days > 0 |
||||
|
for location in record.quant_ids if |
||||
|
location.inventory_quantity_auto_apply > 0} |
||||
|
nearby_expiry_warehouse = {product: sum( |
||||
|
data['count'] for lot, data in warehouse_dict.items() if |
||||
|
data['warehouse'] == product) for product in set( |
||||
|
data['warehouse'] for lot, data in warehouse_dict.items())} |
||||
|
return nearby_expiry_warehouse |
||||
|
|
||||
|
@api.model |
||||
|
def get_today_expire(self): |
||||
|
"""Method to get products that will expire current day |
||||
|
Returns: |
||||
|
A dict which includes list of products id, |
||||
|
list of products name, list of products quantity, and |
||||
|
list of products category. |
||||
|
""" |
||||
|
data = {'id': [], 'name': [], 'qty': [], 'categ': []} |
||||
|
for line in self.search([]): |
||||
|
if line.expiration_date and line.expiration_date.date() == fields.date.today(): |
||||
|
data['id'].append(line.id) |
||||
|
data['name'].append(line.product_id.name) |
||||
|
data['qty'].append(line.product_qty) |
||||
|
found_category = False |
||||
|
for rec in data['categ']: |
||||
|
for key, value in rec.items(): |
||||
|
if line.product_id.categ_id.name == key: |
||||
|
rec[key] += line.product_qty |
||||
|
found_category = True |
||||
|
break |
||||
|
if not found_category: |
||||
|
data['categ'].append({line.product_id.categ_id.name: line.product_qty}) |
||||
|
return data |
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: 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: 79 KiB |
After Width: | Height: | Size: 82 KiB |
After Width: | Height: | Size: 82 KiB |
After Width: | Height: | Size: 84 KiB |
After Width: | Height: | Size: 79 KiB |
After Width: | Height: | Size: 77 KiB |
After Width: | Height: | Size: 63 KiB |
After Width: | Height: | Size: 182 KiB |
After Width: | Height: | Size: 122 KiB |
After Width: | Height: | Size: 185 KiB |
After Width: | Height: | Size: 77 KiB |
After Width: | Height: | Size: 182 KiB |
After Width: | Height: | Size: 71 KiB |
After Width: | Height: | Size: 187 KiB |
After Width: | Height: | Size: 114 KiB |
After Width: | Height: | Size: 116 KiB |
After Width: | Height: | Size: 301 KiB |
After Width: | Height: | Size: 97 KiB |
After Width: | Height: | Size: 11 KiB |
@ -0,0 +1,631 @@ |
|||||
|
<div style="background-color: #714B67; min-height: 600px; width: 100%; padding: 15px; position: relative;"> |
||||
|
<!-- TITLE BAR --> |
||||
|
<div |
||||
|
style="border-bottom: 1px solid #875A7B; padding: 15px; display: flex; justify-content: space-between; align-items: center;"> |
||||
|
<img src="assets/misc/cybrosys-logo.png" width="42" height="42" style="width: 42px; height: 42px;"/> |
||||
|
<div> |
||||
|
<div style="color: #7C7BAD; font-size: 14px; font-family: 'Montserrat', sans-serif; font-weight: bold; background-color: white; display: inline-block; padding: 3px 10px; border-radius: 50px;" |
||||
|
class="mr-2"> |
||||
|
<i class="fa fa-check mr-1"></i>Community |
||||
|
</div> |
||||
|
<div style="color: #875A7B; font-size: 14px; font-family: 'Montserrat', sans-serif; font-weight: bold; background-color: white; display: inline-block; padding: 3px 10px; border-radius: 50px;" |
||||
|
class="mr-2"> |
||||
|
<i class="fa fa-check mr-1"></i>Enterprise |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- END OF TITLE BAR --> |
||||
|
|
||||
|
<!-- APP HERO --> |
||||
|
<h1 style="color: #FFFFFF; font-weight: bolder; font-size: 50px; text-align: center; margin-top: 50px;">Product Expiry Dashboard</h1> |
||||
|
<p style="color:#FFFFFF; padding: 8px 15px; text-align: center; font-size: 24px;">Allows To View All Product Details Those Are About To Expire.</p> |
||||
|
<!-- END OF APP HERO --> |
||||
|
<img src="./assets/screenshots/hero.gif" |
||||
|
style="width: 75%; height: auto; position: absolute; margin-left: auto; margin-right: auto; top: 45%; left: 12%; right: auto;"/> |
||||
|
</div> |
||||
|
|
||||
|
<!-- NAVIGATION SECTION --> |
||||
|
<div class="d-flex align-items-center" style="border-bottom: 2px solid #714B67; padding: 15px 0px; margin-top: 300px;"> |
||||
|
<div class="d-flex justify-content-center align-items-center mr-2" |
||||
|
style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;"> |
||||
|
<img src="assets/misc/compass.png"/> |
||||
|
</div> |
||||
|
<h2 class="mt-2" style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;">Explore This |
||||
|
Module</h2> |
||||
|
</div> |
||||
|
<div class="row my-4" style="font-family: 'Montserrat', sans-serif;"> |
||||
|
<div class="col-sm-12 col-md-6 my-3"> |
||||
|
<a href="#overview"> |
||||
|
<div class="d-flex justify-content-between align-items-center" |
||||
|
style="background-color: #f5f5f5; padding: 30px; width: 100%;"> |
||||
|
<div> |
||||
|
<span style="color: #714B67; font-size: 24px; font-weight: 500; display: block;">Overview</span> |
||||
|
<span |
||||
|
style="color: #714B67; font-size: 16px; font-weight: 400; color:#282F33; display: block;">Learn |
||||
|
more about this |
||||
|
module</span> |
||||
|
</div> |
||||
|
<img src="assets/misc/right-arrow.png" width="36" height="36"/> |
||||
|
</div> |
||||
|
</a> |
||||
|
</div> |
||||
|
<div class="col-sm-12 col-md-6 my-3"> |
||||
|
<a href="#features"> |
||||
|
<div class="d-flex justify-content-between align-items-center" |
||||
|
style="background-color: #f5f5f5; padding: 30px; width: 100%;"> |
||||
|
<div> |
||||
|
<span style="color: #714B67; font-size: 24px; font-weight: 500; display: block;">Features</span> |
||||
|
<span |
||||
|
style="color: #714B67; font-size: 16px; font-weight: 400; color:#282F33; display: block;">View |
||||
|
features of this |
||||
|
module</span> |
||||
|
</div> |
||||
|
<img src="assets/misc/right-arrow.png" width="36" height="36"/> |
||||
|
</div> |
||||
|
</a> |
||||
|
</div> |
||||
|
<div class="col-sm-12 col-md-6 my-3"> |
||||
|
<a href="#screenshots"> |
||||
|
<div class="d-flex justify-content-between align-items-center" |
||||
|
style="background-color: #f5f5f5; padding: 30px; width: 100%;"> |
||||
|
<div> |
||||
|
<span style="color: #714B67; font-size: 24px; font-weight: 500; display: block;">Screenshots</span> |
||||
|
<span |
||||
|
style="color: #714B67; font-size: 16px; font-weight: 400; color:#282F33; display: block;">View |
||||
|
screenshots for this |
||||
|
module</span> |
||||
|
</div> |
||||
|
<img src="assets/misc/right-arrow.png" width="36" height="36"/> |
||||
|
</div> |
||||
|
</a> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- END OF NAVIGATION SECTION --> |
||||
|
|
||||
|
<!-- OVERVIEW SECTION --> |
||||
|
<div class="d-flex align-items-center" style="border-bottom: 2px solid #714B67; padding: 15px 0px;" id="overview"> |
||||
|
<div class="d-flex justify-content-center align-items-center mr-2" |
||||
|
style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;"> |
||||
|
<img src="assets/misc/pie-chart.png"/> |
||||
|
</div> |
||||
|
<h2 class="mt-2" style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;">Overview |
||||
|
</h2> |
||||
|
</div> |
||||
|
<div class="row" style="font-family: 'Montserrat', sans-serif; font-weight: 400; font-size: 14px; line-height: 200%;"> |
||||
|
<div class="col-sm-12 py-4"> |
||||
|
The Product Expiry Dashboard provides information on the number of |
||||
|
expired and nearly expiring products. It also offers a graphical view of |
||||
|
these products, along with their respective locations and warehouses. |
||||
|
This application facilitates swift decision-making by allowing users to |
||||
|
monitor items that are approaching their expiration dates. |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- END OF OVERVIEW SECTION --> |
||||
|
|
||||
|
<!-- FEATURES SECTION --> |
||||
|
<div class="d-flex align-items-center" style="border-bottom: 2px solid #714B67; padding: 15px 0px;" id="features"> |
||||
|
<div class="d-flex justify-content-center align-items-center mr-2" |
||||
|
style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;"> |
||||
|
<img src="assets/misc/features.png"/> |
||||
|
</div> |
||||
|
<h2 class="mt-2" style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;">Features |
||||
|
</h2> |
||||
|
</div> |
||||
|
<div class="row" style="font-family: 'Montserrat', sans-serif; font-weight: 400; font-size: 14px; line-height: 200%;"> |
||||
|
<div class="col-sm-12 col-md-6"> |
||||
|
<div class="d-flex align-items-center" style="margin-top: 30px; margin-bottom: 30px"> |
||||
|
<img src="assets/misc/check-box.png" class="mr-2"/> |
||||
|
<span style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">Contains Tiles for displaying Count of Products which will Expire soon based on their Expiry Date..</span> |
||||
|
</div> |
||||
|
<div class="d-flex align-items-center" style="margin-top: 30px; margin-bottom: 30px"> |
||||
|
<img src="assets/misc/check-box.png" class="mr-2"/> |
||||
|
<span style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">Analyse your products expiry on which location and date.</span> |
||||
|
</div> |
||||
|
<div class="d-flex align-items-center" style="margin-top: 30px; margin-bottom: 30px"> |
||||
|
<img src="assets/misc/check-box.png" class="mr-2"/> |
||||
|
<span style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">Shows all Products which are already Expired.</span> |
||||
|
</div> |
||||
|
<div class="d-flex align-items-center" style="margin-top: 30px; margin-bottom: 30px"> |
||||
|
<img src="assets/misc/check-box.png" class="mr-2"/> |
||||
|
<span style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">Graphical view of Expired Products.</span> |
||||
|
</div> |
||||
|
<div class="d-flex align-items-center" style="margin-top: 30px; margin-bottom: 30px"> |
||||
|
<img src="assets/misc/check-box.png" class="mr-2"/> |
||||
|
<span style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">Graphical view of Nearly Expiring Products based on their Location and Warehouse.</span> |
||||
|
</div> |
||||
|
<div class="d-flex align-items-center" style="margin-top: 40px; margin-bottom: 40px"> |
||||
|
<img src="assets/misc/check-box.png" class="mr-2"/> |
||||
|
<span style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">Community & Enterprise Support.</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- END OF FEATURES SECTION --> |
||||
|
|
||||
|
<!-- SCREENSHOTS SECTION --> |
||||
|
<div class="d-flex align-items-center" style="border-bottom: 2px solid #714B67; padding: 15px 0px;" id="screenshots"> |
||||
|
<div class="d-flex justify-content-center align-items-center mr-2" |
||||
|
style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;"> |
||||
|
<img src="assets/misc/pictures.png"/> |
||||
|
</div> |
||||
|
<h2 class="mt-2" style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;">Screenshots |
||||
|
</h2> |
||||
|
</div> |
||||
|
<div class="row"> |
||||
|
<div class="col-sm-12"> |
||||
|
<div style="display: block; margin: 30px auto;"> |
||||
|
<h3 style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">Product Expiry Dashboard.</h3> |
||||
|
<p style="font-family: 'Roboto', sans-serif !important; font-weight: 400 !important; color: #282F33 !important; font-size: 1rem !important;"> |
||||
|
Navigate to Inventory --> Product Expiry Dashboard menu. |
||||
|
</p> |
||||
|
<img src="assets/screenshots/01.png" class="img-thumbnail"> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="row"> |
||||
|
<div class="col-sm-12"> |
||||
|
<div style="display: block; margin: 30px auto;"> |
||||
|
<h3 style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">Product Expiry Dashboard.</h3> |
||||
|
<p style="font-family: 'Roboto', sans-serif !important; font-weight: 400 !important; color: #282F33 !important; font-size: 1rem !important;"> |
||||
|
1.We can view items that have reached their expiration date |
||||
|
today, those expiring within a day, a week, thirty days, and |
||||
|
one hundred and twenty days </br> |
||||
|
2.Additionally, we can also observe items that have already |
||||
|
expired, as well as expired items categorized by their |
||||
|
respective product categories |
||||
|
</p> |
||||
|
<img src="assets/screenshots/1.png" class="img-thumbnail"> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="row"> |
||||
|
<div class="col-sm-12"> |
||||
|
<div style="display: block; margin: 30px auto;"> |
||||
|
<h3 style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">Different Charts.</h3> |
||||
|
<p style="font-family: 'Roboto', sans-serif !important; font-weight: 400 !important; color: #282F33 !important; font-size: 1rem !important;"> |
||||
|
We can view products expiring within 7 days by categorized by category, location and warehouse. |
||||
|
</p> |
||||
|
<img src="assets/screenshots/2.png" class="img-thumbnail"> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="row"> |
||||
|
<div class="col-sm-12"> |
||||
|
<div style="display: block; margin: 30px auto;"> |
||||
|
<h3 style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">Product Expired Today</h3> |
||||
|
<p style="font-family: 'Roboto', sans-serif !important; font-weight: 400 !important; color: #282F33 !important; font-size: 1rem !important;"> |
||||
|
We can see count of products expired today, with a clickable button |
||||
|
that redirects to the product list view. |
||||
|
</p> |
||||
|
<img src="assets/screenshots/3.png" class="img-thumbnail"> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="row"> |
||||
|
<div class="col-sm-12"> |
||||
|
<div style="display: block; margin: 30px auto;"> |
||||
|
<h3 style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">List View of Products Expired Today.</h3> |
||||
|
<img src="assets/screenshots/4.png" class="img-thumbnail"> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="row"> |
||||
|
<div class="col-sm-12"> |
||||
|
<div style="display: block; margin: 30px auto;"> |
||||
|
<h3 style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">Products Expiry Cards</h3> |
||||
|
<p style="font-family: 'Roboto', sans-serif !important; font-weight: 400 !important; color: #282F33 !important; font-size: 1rem !important;"> |
||||
|
Cards displaying product count expiring tomorrow, in 7 days, |
||||
|
30 days and 120 days, each clickable to redirect to its corresponding list view. |
||||
|
</p> |
||||
|
<img src="assets/screenshots/5.png" class="img-thumbnail"> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="row"> |
||||
|
<div class="col-sm-12"> |
||||
|
<div style="display: block; margin: 30px auto;"> |
||||
|
<h3 style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">List View of Products Expiry in Tomorrow</h3> |
||||
|
<img src="assets/screenshots/6.png" class="img-thumbnail"> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="row"> |
||||
|
<div class="col-sm-12"> |
||||
|
<div style="display: block; margin: 30px auto;"> |
||||
|
<h3 style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">Card Filtration</h3> |
||||
|
<p style="font-family: 'Roboto', sans-serif !important; font-weight: 400 !important; color: #282F33 !important; font-size: 1rem !important;"> |
||||
|
The cards can be filtered using date. |
||||
|
</p> |
||||
|
<img src="assets/screenshots/7.png" class="img-thumbnail"> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="row"> |
||||
|
<div class="col-sm-12"> |
||||
|
<div style="display: block; margin: 30px auto;"> |
||||
|
<h3 style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">Graphs based on Card Filtration</h3> |
||||
|
<img src="assets/screenshots/8.png" class="img-thumbnail"> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="row"> |
||||
|
<div class="col-sm-12"> |
||||
|
<div style="display: block; margin: 30px auto;"> |
||||
|
<h3 style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">Total Expired Products</h3> |
||||
|
<p style="font-family: 'Roboto', sans-serif !important; font-weight: 400 !important; color: #282F33 !important; font-size: 1rem !important;"> |
||||
|
Total expired products based on product name and product catrgory. |
||||
|
</p> |
||||
|
<img src="assets/screenshots/9.png" class="img-thumbnail"> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- END OF SCREENSHOTS SECTION --> |
||||
|
|
||||
|
<!-- RELATED PRODUCTS --> |
||||
|
<div class="d-flex align-items-center" style="border-bottom: 2px solid #714B67; padding: 15px 0px;"> |
||||
|
<div class="d-flex justify-content-center align-items-center mr-2" |
||||
|
style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;"> |
||||
|
<img src="assets/misc/categories.png"/> |
||||
|
</div> |
||||
|
<h2 class="mt-2" style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;">Related |
||||
|
Products |
||||
|
</h2> |
||||
|
</div> |
||||
|
<div class="row"> |
||||
|
<div class="col-sm-12"> |
||||
|
<div id="demo1" class="row carousel slide" data-ride="carousel"> |
||||
|
<!-- The slideshow --> |
||||
|
<div class="carousel-inner" style="padding: 30px;"> |
||||
|
<div class="carousel-item" style="min-height: 198.656px;"> |
||||
|
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" style="float:left"> |
||||
|
<a href="https://apps.odoo.com/apps/modules/16.0/product_management_app/" |
||||
|
target="_blank"> |
||||
|
<div style="border-radius:10px"> |
||||
|
<img class="img img-responsive center-block" |
||||
|
style="border-radius: 0px;" |
||||
|
src="./assets/modules/module01.png"> |
||||
|
</div> |
||||
|
</a> |
||||
|
</div> |
||||
|
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" style="float:left"> |
||||
|
<a href="https://apps.odoo.com/apps/modules/16.0/odoo_dynamic_dashboard/" |
||||
|
target="_blank"> |
||||
|
<div style="border-radius:10px"> |
||||
|
<img class="img img-responsive center-block" |
||||
|
style="border-radius: 0px;" |
||||
|
src="./assets/modules/module02.png"> |
||||
|
</div> |
||||
|
</a> |
||||
|
</div> |
||||
|
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" style="float:left"> |
||||
|
<a href="https://apps.odoo.com/apps/modules/16.0/inventory_stock_dashboard_odoo/" |
||||
|
target="_blank"> |
||||
|
<div style="border-radius:10px"> |
||||
|
<img class="img img-responsive center-block" |
||||
|
style="border-radius: 0px;" |
||||
|
src="./assets/modules/module03.png"> |
||||
|
</div> |
||||
|
</a> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="carousel-item active" style="min-height: 198.656px;"> |
||||
|
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" style="float:left"> |
||||
|
<a href="https://apps.odoo.com/apps/modules/16.0/project_dashboard_odoo/" |
||||
|
target="_blank"> |
||||
|
<div style="border-radius:10px"> |
||||
|
<img class="img img-responsive center-block" |
||||
|
style="border-radius: 0px;" |
||||
|
src="./assets/modules/module04.png"> |
||||
|
</div> |
||||
|
</a> |
||||
|
</div> |
||||
|
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" style="float:left"> |
||||
|
<a href="https://apps.odoo.com/apps/modules/16.0/crm_dashboard/" |
||||
|
target="_blank"> |
||||
|
<div style="border-radius:10px"> |
||||
|
<img class="img img-responsive center-block" |
||||
|
style="border-radius: 0px;" |
||||
|
src="./assets/modules/module05.png"> |
||||
|
</div> |
||||
|
</a> |
||||
|
</div> |
||||
|
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" style="float:left"> |
||||
|
<a href="https://apps.odoo.com/apps/modules/16.0/dashboard_pos/" |
||||
|
target="_blank"> |
||||
|
<div style="border-radius:10px"> |
||||
|
<img class="img img-responsive center-block" |
||||
|
style="border-radius: 0px;" |
||||
|
src="./assets/modules/module06.png"> |
||||
|
</div> |
||||
|
</a> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- Left and right controls --> |
||||
|
<a class="carousel-control-prev" href="#demo1" data-slide="prev" style="width:35px; color:#000"> <span |
||||
|
class="carousel-control-prev-icon"><i class="fa fa-chevron-left" style="font-size:24px"></i></span> |
||||
|
</a> <a class="carousel-control-next" href="#demo1" data-slide="next" style="width:35px; color:#000"> |
||||
|
<span class="carousel-control-next-icon"><i class="fa fa-chevron-right" |
||||
|
style="font-size:24px"></i></span> |
||||
|
</a> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- END OF RELATED PRODUCTS --> |
||||
|
|
||||
|
<!-- OUR SERVICES --> |
||||
|
|
||||
|
<div class="d-flex align-items-center" style="border-bottom: 2px solid #714B67; padding: 15px 0px;"> |
||||
|
<div class="d-flex justify-content-center align-items-center mr-2" |
||||
|
style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;"> |
||||
|
<img src="assets/misc/star.png"/> |
||||
|
</div> |
||||
|
<h2 class="mt-2" style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;">Our Services |
||||
|
</h2> |
||||
|
</div> |
||||
|
|
||||
|
<div class="container my-5"> |
||||
|
<div class="row"> |
||||
|
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4"> |
||||
|
<div class="d-flex justify-content-center align-items-center mx-3 my-3" |
||||
|
style="background-color: #1dd1a1 !important; border-radius: 15px !important; height: 80px; width: 80px;"> |
||||
|
<img src="assets/icons/cogs.png" class="img-responsive" height="48px" width="48px"> |
||||
|
</div> |
||||
|
<h6 class="text-center" style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;"> |
||||
|
Odoo |
||||
|
Customization</h6> |
||||
|
</div> |
||||
|
|
||||
|
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4"> |
||||
|
<div class="d-flex justify-content-center align-items-center mx-3 my-3" |
||||
|
style="background-color: #ff6b6b !important; border-radius: 15px !important; height: 80px; width: 80px;"> |
||||
|
<img src="assets/icons/wrench.png" class="img-responsive" height="48px" width="48px"> |
||||
|
</div> |
||||
|
<h6 class="text-center" style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;"> |
||||
|
Odoo |
||||
|
Implementation</h6> |
||||
|
</div> |
||||
|
|
||||
|
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4"> |
||||
|
<div class="d-flex justify-content-center align-items-center mx-3 my-3" |
||||
|
style="background-color: #6462CD !important; border-radius: 15px !important; height: 80px; width: 80px;"> |
||||
|
<img src="assets/icons/lifebuoy.png" class="img-responsive" height="48px" width="48px"> |
||||
|
</div> |
||||
|
<h6 class="text-center" style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;"> |
||||
|
Odoo |
||||
|
Support</h6> |
||||
|
</div> |
||||
|
|
||||
|
|
||||
|
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4"> |
||||
|
<div class="d-flex justify-content-center align-items-center mx-3 my-3" |
||||
|
style="background-color: #ffa801 !important; border-radius: 15px !important; height: 80px; width: 80px;"> |
||||
|
<img src="assets/icons/user.png" class="img-responsive" height="48px" width="48px"> |
||||
|
</div> |
||||
|
<h6 class="text-center" style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;"> |
||||
|
Hire |
||||
|
Odoo |
||||
|
Developer</h6> |
||||
|
</div> |
||||
|
|
||||
|
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4"> |
||||
|
<div class="d-flex justify-content-center align-items-center mx-3 my-3" |
||||
|
style="background-color: #54a0ff !important; border-radius: 15px !important; height: 80px; width: 80px;"> |
||||
|
<img src="assets/icons/puzzle.png" class="img-responsive" height="48px" width="48px"> |
||||
|
</div> |
||||
|
<h6 class="text-center" style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;"> |
||||
|
Odoo |
||||
|
Integration</h6> |
||||
|
</div> |
||||
|
|
||||
|
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4"> |
||||
|
<div class="d-flex justify-content-center align-items-center mx-3 my-3" |
||||
|
style="background-color: #6d7680 !important; border-radius: 15px !important; height: 80px; width: 80px;"> |
||||
|
<img src="assets/icons/update.png" class="img-responsive" height="48px" width="48px"> |
||||
|
</div> |
||||
|
<h6 class="text-center" style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;"> |
||||
|
Odoo |
||||
|
Migration</h6> |
||||
|
</div> |
||||
|
|
||||
|
|
||||
|
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4"> |
||||
|
<div class="d-flex justify-content-center align-items-center mx-3 my-3" |
||||
|
style="background-color: #786fa6 !important; border-radius: 15px !important; height: 80px; width: 80px;"> |
||||
|
<img src="assets/icons/consultation.png" class="img-responsive" height="48px" width="48px"> |
||||
|
</div> |
||||
|
<h6 class="text-center" style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;"> |
||||
|
Odoo |
||||
|
Consultancy</h6> |
||||
|
</div> |
||||
|
|
||||
|
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4"> |
||||
|
<div class="d-flex justify-content-center align-items-center mx-3 my-3" |
||||
|
style="background-color: #f8a5c2 !important; border-radius: 15px !important; height: 80px; width: 80px;"> |
||||
|
<img src="assets/icons/training.png" class="img-responsive" height="48px" width="48px"> |
||||
|
</div> |
||||
|
<h6 class="text-center" style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;"> |
||||
|
Odoo |
||||
|
Implementation</h6> |
||||
|
</div> |
||||
|
|
||||
|
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4"> |
||||
|
<div class="d-flex justify-content-center align-items-center mx-3 my-3" |
||||
|
style="background-color: #e6be26 !important; border-radius: 15px !important; height: 80px; width: 80px;"> |
||||
|
<img src="assets/icons/license.png" class="img-responsive" height="48px" width="48px"> |
||||
|
</div> |
||||
|
<h6 class="text-center" style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;"> |
||||
|
Odoo |
||||
|
Licensing Consultancy</h6> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- END OF END OF OUR SERVICES --> |
||||
|
|
||||
|
<!-- OUR INDUSTRIES --> |
||||
|
<div class="d-flex align-items-center" style="border-bottom: 2px solid #714B67; padding: 15px 0px;"> |
||||
|
<div class="d-flex justify-content-center align-items-center mr-2" |
||||
|
style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;"> |
||||
|
<img src="assets/misc/corporate.png"/> |
||||
|
</div> |
||||
|
<h2 class="mt-2" style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;">Our |
||||
|
Industries |
||||
|
</h2> |
||||
|
</div> |
||||
|
|
||||
|
<div class="container my-5"> |
||||
|
<div class="row"> |
||||
|
<div class="col-lg-3"> |
||||
|
<div class="my-4 d-flex flex-column justify-content-center" |
||||
|
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;"> |
||||
|
<img src="assets/icons/trading-black.png" class="img-responsive mb-3" height="48px" width="48px"> |
||||
|
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;"> |
||||
|
Trading |
||||
|
</h5> |
||||
|
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;"> |
||||
|
Easily procure |
||||
|
and |
||||
|
sell your products</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="col-lg-3"> |
||||
|
<div class="my-4 d-flex flex-column justify-content-center" |
||||
|
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;"> |
||||
|
<img src="assets/icons/pos-black.png" class="img-responsive mb-3" height="48px" width="48px"> |
||||
|
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;"> |
||||
|
POS |
||||
|
</h5> |
||||
|
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;"> |
||||
|
Easy |
||||
|
configuration |
||||
|
and convivial experience</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="col-lg-3"> |
||||
|
<div class="my-4 d-flex flex-column justify-content-center" |
||||
|
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;"> |
||||
|
<img src="assets/icons/education-black.png" class="img-responsive mb-3" height="48px" width="48px"> |
||||
|
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;"> |
||||
|
Education |
||||
|
</h5> |
||||
|
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;"> |
||||
|
A platform for |
||||
|
educational management</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="col-lg-3"> |
||||
|
<div class="my-4 d-flex flex-column justify-content-center" |
||||
|
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;"> |
||||
|
<img src="assets/icons/manufacturing-black.png" class="img-responsive mb-3" height="48px" |
||||
|
width="48px"> |
||||
|
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;"> |
||||
|
Manufacturing |
||||
|
</h5> |
||||
|
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;"> |
||||
|
Plan, track and |
||||
|
schedule your operations</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="col-lg-3"> |
||||
|
<div class="my-4 d-flex flex-column justify-content-center" |
||||
|
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;"> |
||||
|
<img src="assets/icons/ecom-black.png" class="img-responsive mb-3" height="48px" width="48px"> |
||||
|
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;"> |
||||
|
E-commerce & Website |
||||
|
</h5> |
||||
|
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;"> |
||||
|
Mobile |
||||
|
friendly, |
||||
|
awe-inspiring product pages</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="col-lg-3"> |
||||
|
<div class="my-4 d-flex flex-column justify-content-center" |
||||
|
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;"> |
||||
|
<img src="assets/icons/service-black.png" class="img-responsive mb-3" height="48px" width="48px"> |
||||
|
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;"> |
||||
|
Service Management |
||||
|
</h5> |
||||
|
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;"> |
||||
|
Keep track of |
||||
|
services and invoice</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="col-lg-3"> |
||||
|
<div class="my-4 d-flex flex-column justify-content-center" |
||||
|
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;"> |
||||
|
<img src="assets/icons/restaurant-black.png" class="img-responsive mb-3" height="48px" width="48px"> |
||||
|
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;"> |
||||
|
Restaurant |
||||
|
</h5> |
||||
|
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;"> |
||||
|
Run your bar or |
||||
|
restaurant methodically</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="col-lg-3"> |
||||
|
<div class="my-4 d-flex flex-column justify-content-center" |
||||
|
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;"> |
||||
|
<img src="assets/icons/hotel-black.png" class="img-responsive mb-3" height="48px" width="48px"> |
||||
|
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;"> |
||||
|
Hotel Management |
||||
|
</h5> |
||||
|
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;"> |
||||
|
An |
||||
|
all-inclusive |
||||
|
hotel management application</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- END OF END OF OUR INDUSTRIES --> |
||||
|
|
||||
|
<!-- SUPPORT --> |
||||
|
<div class="d-flex align-items-center" style="border-bottom: 2px solid #714B67; padding: 15px 0px;"> |
||||
|
<div class="d-flex justify-content-center align-items-center mr-2" |
||||
|
style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;"> |
||||
|
<img src="assets/misc/customer-support.png"/> |
||||
|
</div> |
||||
|
<h2 class="mt-2" style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;">Support</h2> |
||||
|
</div> |
||||
|
<div class="container mt-5"> |
||||
|
<div class="row"> |
||||
|
<div class="col-sm-12 col-md-6"> |
||||
|
<div style="background-color: #F6F8F9; padding: 30px; display: flex; align-items: center;"> |
||||
|
<div class="mr-4 d-flex justify-content-center align-items-center" |
||||
|
style="background-color: #714B67; display: inline-block; height: 70px; width: 70px; display: flex; align-items: center; justify-content: center;"> |
||||
|
<img src="assets/misc/support.png" height="48" width="48" style="width: 42px; height: 42px;" /> |
||||
|
</div> |
||||
|
<div> |
||||
|
<h4>Need Help?</h4> |
||||
|
<p style="line-height: 100%;">Got questions or need help? Get in touch.</p> |
||||
|
<a href="mailto:odoo@cybrosys.com"> |
||||
|
<p style="font-weight: 400; font-size: 28px; line-height: 80%; color: #714B67;">odoo@cybrosys.com</p> |
||||
|
</a> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="col-sm-12 col-md-6"> |
||||
|
<div style="background-color: #F6F8F9; padding: 30px; display: flex; align-items: center;"> |
||||
|
<div class="mr-4 d-flex justify-content-center align-items-center" |
||||
|
style="background-color: #2AC44D; display: inline-block; height: 70px; width: 70px; display: flex; align-items: center; justify-content: center;"> |
||||
|
<img src="assets/misc/whatsapp.png" height="52" width="52" style="width: 52px; height: 52px;" /> |
||||
|
</div> |
||||
|
<div> |
||||
|
<h4>WhatsApp</h4> |
||||
|
<p style="line-height: 100%;">Say hi to us on WhatsApp!</p> |
||||
|
<a href="https://api.whatsapp.com/send?phone=918606827707"> |
||||
|
<p style="font-weight: 400; font-size: 28px; line-height: 80%; color: #714B67;">+91 8606827707</p> |
||||
|
</a> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="row"> |
||||
|
<div class="col-sm-12 my-5 d-flex justify-content-center align-items-center"> |
||||
|
<img src="assets/misc/logo.png" width="144" height="31" style="width:144px; height: 31px; margin-top: 40px;" /> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- END OF SUPPORT --> |
@ -0,0 +1,76 @@ |
|||||
|
.order-card { |
||||
|
color: #fff; |
||||
|
} |
||||
|
|
||||
|
.bg-c-blue { |
||||
|
background: linear-gradient(45deg,#4099ff,#73b4ff); |
||||
|
} |
||||
|
|
||||
|
.bg-c-green { |
||||
|
background: linear-gradient(45deg,#2ed8b6,#59e0c5); |
||||
|
} |
||||
|
|
||||
|
.bg-c-yellow { |
||||
|
background: linear-gradient(45deg,#FFB64D,#ffcb80); |
||||
|
} |
||||
|
|
||||
|
.bg-c-pink { |
||||
|
background: linear-gradient(45deg,#FF5370,#ff869a); |
||||
|
} |
||||
|
|
||||
|
.card { |
||||
|
border-radius: 5px; |
||||
|
-webkit-box-shadow: 0 1px 2.94px 0.06px rgba(4,26,55,0.16); |
||||
|
box-shadow: 0 1px 2.94px 0.06px rgba(4,26,55,0.16); |
||||
|
border: none; |
||||
|
margin-bottom: 30px; |
||||
|
-webkit-transition: all 0.3s ease-in-out; |
||||
|
transition: all 0.3s ease-in-out; |
||||
|
} |
||||
|
|
||||
|
.card .card-block { |
||||
|
padding: 25px; |
||||
|
} |
||||
|
|
||||
|
.order-card i { |
||||
|
font-size: 26px; |
||||
|
} |
||||
|
|
||||
|
.f-left { |
||||
|
float: left; |
||||
|
} |
||||
|
|
||||
|
.f-right { |
||||
|
float: right; |
||||
|
} |
||||
|
|
||||
|
.card-block:hover{ |
||||
|
box-shadow: 5px 5px 10px #ccc; |
||||
|
} |
||||
|
|
||||
|
.expiry_dashboard_row{ |
||||
|
padding-left: 50px; |
||||
|
padding-right: 50px; |
||||
|
} |
||||
|
|
||||
|
.dashboard-heading{ |
||||
|
min-height: 140px; |
||||
|
padding: 20px; |
||||
|
text-align: center; |
||||
|
background: #376270; |
||||
|
} |
||||
|
|
||||
|
.banner-heading { |
||||
|
color: #ffffff; |
||||
|
font-size: 44px; |
||||
|
margin: 0; |
||||
|
margin-top: 20px; |
||||
|
} |
||||
|
|
||||
|
.product_expired_heading{ |
||||
|
background: aliceblue; |
||||
|
width: 236px; |
||||
|
float: left; |
||||
|
border-radius: 16px; |
||||
|
height: 102px; |
||||
|
} |
@ -0,0 +1,602 @@ |
|||||
|
/** @odoo-module */ |
||||
|
import { registry } from "@web/core/registry" |
||||
|
import rpc from 'web.rpc'; |
||||
|
const { Component, onWillStart, useState } = owl; |
||||
|
const actionRegistry = registry.category("actions"); |
||||
|
var session = require('web.session'); |
||||
|
|
||||
|
export class ProductExpiryDashboard extends Component { |
||||
|
setup() { |
||||
|
super.setup(...arguments); |
||||
|
this.state = useState({ |
||||
|
data: null, |
||||
|
charts:[] |
||||
|
}) |
||||
|
onWillStart(async () => { |
||||
|
await this.fetch_products_expiry() |
||||
|
await this.product_expired_today() |
||||
|
await this.get_today_expire_products() |
||||
|
await this.get_today_expire_products_category() |
||||
|
await this.near_exp_products() |
||||
|
await this.near_exp_category() |
||||
|
await this.get_expire_product_location() |
||||
|
await this.get_expire_product_warehouse() |
||||
|
await this.render_expired_products_graph() |
||||
|
await this.expiry_by_category() |
||||
|
}); |
||||
|
} |
||||
|
// Filter products based on the specified start and end dates.
|
||||
|
async filter_date(ev) { |
||||
|
// Get the start and end dates selected by the user from input fields.
|
||||
|
var start_date = $("#start_date").val(); |
||||
|
var end_date = $("#end_date").val(); |
||||
|
if (this.state.charts.length != 0) { |
||||
|
this.state.charts.forEach((item)=> { |
||||
|
item.destroy() |
||||
|
}); |
||||
|
} |
||||
|
// Fetch and display products that fall within the specified date range.
|
||||
|
await this.fetch_products_expiry(start_date, end_date); |
||||
|
await this.near_exp_products(start_date, end_date); |
||||
|
await this.near_exp_category(start_date, end_date); |
||||
|
await this.get_expire_product_location(start_date, end_date); |
||||
|
await this.get_expire_product_warehouse(start_date, end_date); |
||||
|
await this.render_expired_products_graph(start_date, end_date); |
||||
|
await this.expiry_by_category(start_date, end_date); |
||||
|
} |
||||
|
fetch_products_expiry(start_date, end_date) { |
||||
|
// Remove existing elements before updating the product expiry counts.
|
||||
|
$("#one_day").remove(); |
||||
|
$("#seven_day").remove(); |
||||
|
$("#thirty_day").remove(); |
||||
|
$("#one_twenty_day").remove(); |
||||
|
var self = this; |
||||
|
// Prepare the date dictionary to pass as an argument in the RPC query.
|
||||
|
var date_dict = { 'start_date': start_date, 'end_date': end_date }; |
||||
|
// Send an RPC query to fetch product expiry data from the server.
|
||||
|
rpc.query({ |
||||
|
model: 'stock.lot', |
||||
|
method: 'get_product_expiry', |
||||
|
args: [date_dict, session.user_context.allowed_company_ids] |
||||
|
}).then(function(result) { |
||||
|
self.state.data = result |
||||
|
var seven_day = result[0]['counts'] + result[1]['counts']; |
||||
|
// Update the HTML elements to display the product counts for different expiry periods.
|
||||
|
$(".one-day").append('<center>' |
||||
|
+ '<span style="font-size: xxx-large;" id="one_day">' + result[0]['counts'] + '</span>' |
||||
|
+ '</center>'); |
||||
|
$(".seven-day").append('<center>' |
||||
|
+ '<span style="font-size: xxx-large;" id="seven_day">' + seven_day + '</span>' |
||||
|
+ '</center>'); |
||||
|
$(".thirty-day").append('<center>' |
||||
|
+ '<span style="font-size: xxx-large;" id="thirty_day">' + result[2]['counts'] + '</span>' |
||||
|
+ '</center>'); |
||||
|
$(".one-twenty-day").append('<center>' |
||||
|
+ '<span style="font-size: xxx-large;" id="one_twenty_day">' + result[3]['counts'] + '</span>' |
||||
|
+ '</center>'); |
||||
|
}); |
||||
|
} |
||||
|
// Fetches the count of products that have expired today using an
|
||||
|
// RPC query and updates the HTML element to display the count.
|
||||
|
product_expired_today() { |
||||
|
// Send an RPC query to fetch the count of products that have expired today.
|
||||
|
rpc.query({ |
||||
|
model: 'stock.lot', |
||||
|
method: 'get_product_expired_today', |
||||
|
args: [session.user_context.allowed_company_ids] |
||||
|
}).then(function (result) { |
||||
|
// Update the HTML element to display the count of products expired today.
|
||||
|
$('.product_expired_heading').append('<p style="font-size: 38px;margin-top: -10px;">' + result + '</p>'); |
||||
|
|
||||
|
}); |
||||
|
} |
||||
|
// Fetches the list of products that have expired today
|
||||
|
click_expired_today() { |
||||
|
var self = this; |
||||
|
rpc.query({ |
||||
|
model: 'stock.lot', |
||||
|
method: 'get_today_expire', |
||||
|
}).then(function (result) { |
||||
|
var id = [] |
||||
|
for( var data in result['id']){ |
||||
|
id.push(parseInt(result['id'][data])) |
||||
|
} |
||||
|
// Perform the action to display products that have expired on the current day.
|
||||
|
self.env.services['action'].doAction({ |
||||
|
name: "Products Expired Today", |
||||
|
type: 'ir.actions.act_window', |
||||
|
res_model: 'stock.lot', |
||||
|
views: [[false, 'tree'], [false, 'form']], |
||||
|
domain: [['id', 'in', id]], |
||||
|
target: 'current', |
||||
|
context: { |
||||
|
'create': false |
||||
|
} |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
// Fetches the list of products that have expired within 1 day(tomorrow)
|
||||
|
one_day_click() { |
||||
|
// Perform the action to display products that are expiring in one day with negative product quantities.
|
||||
|
this.env.services['action'].doAction({ |
||||
|
name: "Expiry Tomorrow", |
||||
|
type: 'ir.actions.act_window', |
||||
|
res_model: 'stock.lot', |
||||
|
views: [[false, 'tree'], [false, 'form']], |
||||
|
domain: [['id', 'in', this.state.data[0]['one_day']]], |
||||
|
target: 'current', |
||||
|
context: { |
||||
|
'create': false |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
// Fetches the list of products that have expired with in 7days
|
||||
|
seven_day_click() { |
||||
|
const ids = [...this.state.data[1]['seven_day'], ...this.state.data[0]['one_day']] |
||||
|
// Perform the action to display products that are expiring within the seven-day range.
|
||||
|
this.env.services['action'].doAction({ |
||||
|
name: "Expiry in Seven Days", |
||||
|
type: 'ir.actions.act_window', |
||||
|
res_model: 'stock.lot', |
||||
|
views: [[false, 'tree'], [false, 'form']], |
||||
|
domain: [['id', 'in', ids]], |
||||
|
target: 'current', |
||||
|
context: { |
||||
|
'create': false |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
// Fetches the list of products that have expired in 30 days
|
||||
|
thirty_day_click() { |
||||
|
const ids = [...this.state.data[2]['thirty_day']] |
||||
|
// Perform the action to display products that are expiring within the thirty-day range.
|
||||
|
this.env.services['action'].doAction({ |
||||
|
name: "Expiry in Thirty Days", |
||||
|
type: 'ir.actions.act_window', |
||||
|
res_model: 'stock.lot', |
||||
|
views: [[false, 'tree'], [false, 'form']], |
||||
|
domain: [['id', 'in', ids]], |
||||
|
target: 'current', |
||||
|
context: { |
||||
|
'create': false |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
// Fetches the list of products that have expired in 120 days
|
||||
|
one_twenty_day_click() { |
||||
|
const ids = [...this.state.data[3]['one_twenty_day']] |
||||
|
// Perform the action to display products that are expiring within the one hundred twenty-day range.
|
||||
|
this.env.services['action'].doAction({ |
||||
|
name: "Expiry in One Twenty Days", |
||||
|
type: 'ir.actions.act_window', |
||||
|
res_model: 'stock.lot', |
||||
|
views: [[false, 'tree'], [false, 'form']], |
||||
|
domain: [['id', 'in', ids]], |
||||
|
target: 'current', |
||||
|
context: { |
||||
|
'create': false |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
// Line Chart for Products Expired Today
|
||||
|
get_today_expire_products() { |
||||
|
// Initialize arrays to hold product warehouse names and their corresponding quantities.
|
||||
|
var expire_product_name = []; |
||||
|
var expire_product_qty = []; |
||||
|
// Send an RPC query to fetch data about products that are about to expire, categorized by their warehouses, from the server.
|
||||
|
rpc.query({ |
||||
|
model: 'stock.lot', |
||||
|
method: 'get_today_expire', |
||||
|
}).then(function (result) { |
||||
|
for( var data in result['name']){ |
||||
|
expire_product_name.push(result['name'][data]) |
||||
|
expire_product_qty.push(parseInt(result['qty'][data])) |
||||
|
} |
||||
|
// Render the bar chart using Chart.js.
|
||||
|
const ctx = $('#today_expire_products'); |
||||
|
new Chart(ctx, { |
||||
|
type: 'line', |
||||
|
data: { |
||||
|
labels: expire_product_name, |
||||
|
datasets: [{ |
||||
|
label: 'Quantity', |
||||
|
data: expire_product_qty, |
||||
|
borderWidth: 1 |
||||
|
}] |
||||
|
}, |
||||
|
options: { |
||||
|
plugins: { |
||||
|
legend: { |
||||
|
position: 'bottom', |
||||
|
}, |
||||
|
title: { |
||||
|
display: true, |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
// Bar chart for Products Expired Today by Category
|
||||
|
get_today_expire_products_category() { |
||||
|
// Initialize arrays to hold product warehouse names and their corresponding quantities.
|
||||
|
var expire_product_categ_name = []; |
||||
|
var expire_product_qty = []; |
||||
|
// Send an RPC query to fetch data about products that are about to expire, categorized by their warehouses, from the server.
|
||||
|
rpc.query({ |
||||
|
model: 'stock.lot', |
||||
|
method: 'get_today_expire', |
||||
|
}).then(function (result) { |
||||
|
// Extract product warehouse names and their corresponding quantities from the fetched data.
|
||||
|
for( var data in result['categ']){ |
||||
|
for (var key in result['categ'][data]) { |
||||
|
if (result['categ'][data].hasOwnProperty(key)) { |
||||
|
var value = result['categ'][data][key]; |
||||
|
} |
||||
|
expire_product_categ_name.push(key) |
||||
|
expire_product_qty.push(parseInt(value)) |
||||
|
} |
||||
|
} |
||||
|
// Render the bar chart using Chart.js.
|
||||
|
const ctx = $('#today_expire_products_category'); |
||||
|
new Chart(ctx, { |
||||
|
type: 'bar', |
||||
|
data: { |
||||
|
labels: expire_product_categ_name, |
||||
|
datasets: [{ |
||||
|
label: 'Quantity', |
||||
|
data: expire_product_qty, |
||||
|
backgroundColor: [ |
||||
|
'rgba(75, 192, 192, 0.2)', |
||||
|
'rgba(54, 162, 235, 0.2)', |
||||
|
'rgba(153, 102, 255, 0.2)', |
||||
|
'rgba(201, 203, 207, 0.2)', |
||||
|
'rgba(255, 99, 132, 0.2)', |
||||
|
'rgba(255, 159, 64, 0.2)', |
||||
|
'rgba(255, 205, 86, 0.2)', |
||||
|
], |
||||
|
borderColor: [ |
||||
|
'rgb(75, 192, 192)', |
||||
|
'rgb(54, 162, 235)', |
||||
|
'rgb(153, 102, 255)', |
||||
|
'rgb(201, 203, 207)', |
||||
|
'rgb(255, 99, 132)', |
||||
|
'rgb(255, 159, 64)', |
||||
|
'rgb(255, 205, 86)', |
||||
|
], |
||||
|
borderWidth: 1 |
||||
|
}] |
||||
|
}, |
||||
|
options: { |
||||
|
plugins: { |
||||
|
legend: { |
||||
|
position: 'bottom', |
||||
|
}, |
||||
|
title: { |
||||
|
display: true, |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
// chart for products expire in 7 days
|
||||
|
near_exp_products(start_date, end_date) { |
||||
|
var self = this; |
||||
|
// Initialize arrays to hold product names and their corresponding near expiry quantities.
|
||||
|
var product_array = []; |
||||
|
var nearby_expire_qty = []; |
||||
|
// Prepare the date dictionary to pass as an argument in the RPC query.
|
||||
|
var date_dict = { 'start_date': start_date, 'end_date': end_date }; |
||||
|
// Send an RPC query to fetch data about products that are near their expiry date from the server.
|
||||
|
rpc.query({ |
||||
|
model: 'stock.lot', |
||||
|
method: 'get_near_expiry_product', |
||||
|
args: [date_dict], |
||||
|
}).then(function (result) { |
||||
|
// Extract product names and their corresponding near expiry quantities from the fetched data.
|
||||
|
$.each(result, function (index, name) { |
||||
|
product_array.push(index); |
||||
|
nearby_expire_qty.push(name); |
||||
|
}); |
||||
|
// Render the bar chart using Chart.js.
|
||||
|
const ctx = $('#nearby_expire_product'); |
||||
|
self.state.charts.push(new Chart(ctx, { |
||||
|
type: 'bar', |
||||
|
data: { |
||||
|
labels: product_array, |
||||
|
datasets: [{ |
||||
|
label: 'Quantity', |
||||
|
data: nearby_expire_qty, |
||||
|
backgroundColor: [ |
||||
|
'rgba(255, 205, 86, 0.2)', |
||||
|
'rgba(75, 192, 192, 0.2)', |
||||
|
'rgba(54, 162, 235, 0.2)', |
||||
|
'rgba(153, 102, 255, 0.2)', |
||||
|
'rgba(201, 203, 207, 0.2)', |
||||
|
'rgba(255, 99, 132, 0.2)', |
||||
|
'rgba(255, 159, 64, 0.2)', |
||||
|
], |
||||
|
borderColor: [ |
||||
|
'rgb(255, 205, 86)', |
||||
|
'rgb(75, 192, 192)', |
||||
|
'rgb(54, 162, 235)', |
||||
|
'rgb(153, 102, 255)', |
||||
|
'rgb(201, 203, 207)', |
||||
|
'rgb(255, 99, 132)', |
||||
|
'rgb(255, 159, 64)', |
||||
|
], |
||||
|
borderWidth: 1 |
||||
|
}] |
||||
|
}, |
||||
|
options: { |
||||
|
plugins: { |
||||
|
legend: { |
||||
|
position: 'bottom', |
||||
|
}, |
||||
|
title: { |
||||
|
display: true, |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
})); |
||||
|
}); |
||||
|
} |
||||
|
// Chart for products category expire in 7 days
|
||||
|
near_exp_category(start_date, end_date) { |
||||
|
var self = this; |
||||
|
// Initialize arrays to hold product category names and their corresponding near expiry quantities.
|
||||
|
var product_category_array = []; |
||||
|
var nearby_expire_qty = []; |
||||
|
// Prepare the date dictionary to pass as an argument in the RPC query.
|
||||
|
var date_dict = { 'start_date': start_date, 'end_date': end_date }; |
||||
|
// Send an RPC query to fetch data about products that are near their expiry date, categorized by their product categories.
|
||||
|
rpc.query({ |
||||
|
model: 'stock.lot', |
||||
|
method: 'get_near_expiry_category', |
||||
|
args: [date_dict], |
||||
|
}).then(function (result) { |
||||
|
// Extract product category names and their corresponding near expiry quantities from the fetched data.
|
||||
|
$.each(result, function (index, name) { |
||||
|
product_category_array.push(index); |
||||
|
nearby_expire_qty.push(name); |
||||
|
}); |
||||
|
// Render the bar chart using Chart.js.
|
||||
|
const ctx = $('#nearby_expire_catg'); |
||||
|
self.state.charts.push(new Chart(ctx, { |
||||
|
type: 'bar', |
||||
|
data: { |
||||
|
labels: product_category_array, |
||||
|
datasets: [{ |
||||
|
label: 'Quantity', |
||||
|
data: nearby_expire_qty, |
||||
|
backgroundColor: [ |
||||
|
'rgba(255, 159, 64, 0.2)', |
||||
|
'rgba(255, 205, 86, 0.2)', |
||||
|
'rgba(75, 192, 192, 0.2)', |
||||
|
'rgba(54, 162, 235, 0.2)', |
||||
|
'rgba(153, 102, 255, 0.2)', |
||||
|
'rgba(201, 203, 207, 0.2)', |
||||
|
'rgba(255, 99, 132, 0.2)', |
||||
|
], |
||||
|
borderWidth: 1 |
||||
|
}] |
||||
|
}, |
||||
|
options: { |
||||
|
plugins: { |
||||
|
legend: { |
||||
|
position: 'bottom', |
||||
|
}, |
||||
|
title: { |
||||
|
display: true, |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
})); |
||||
|
}); |
||||
|
} |
||||
|
// Expiry in Seven Days
|
||||
|
get_expire_product_location(start_date, end_date) { |
||||
|
var self = this; |
||||
|
// Initialize arrays to hold product location names and their corresponding quantities.
|
||||
|
var product_location_array = []; |
||||
|
var nearby_expire_qty = []; |
||||
|
// Prepare the date dictionary to pass as an argument in the RPC query.
|
||||
|
var date_dict = { 'start_date': start_date, 'end_date': end_date }; |
||||
|
// Send an RPC query to fetch data about products that are about to expire, categorized by their locations, from the server.
|
||||
|
rpc.query({ |
||||
|
model: 'stock.lot', |
||||
|
method: 'get_expire_product_location', |
||||
|
args: [date_dict], |
||||
|
}).then(function (result) { |
||||
|
// Extract product location names and their corresponding quantities from the fetched data.
|
||||
|
$.each(result, function (index, name) { |
||||
|
product_location_array.push(index); |
||||
|
nearby_expire_qty.push(name); |
||||
|
}); |
||||
|
// Render the pie chart using Chart.js.
|
||||
|
const ctx = $('#nearby_expire_location'); |
||||
|
self.state.charts.push(new Chart(ctx, { |
||||
|
type: 'pie', |
||||
|
data: { |
||||
|
labels: product_location_array, |
||||
|
datasets: [{ |
||||
|
label: 'Quantity', |
||||
|
data: nearby_expire_qty, |
||||
|
borderWidth: 1 |
||||
|
}] |
||||
|
}, |
||||
|
options: { |
||||
|
plugins: { |
||||
|
legend: { |
||||
|
position: 'bottom', |
||||
|
}, |
||||
|
title: { |
||||
|
display: true, |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
})); |
||||
|
}); |
||||
|
} |
||||
|
// Products by Warehouse Expire in 7 Days
|
||||
|
get_expire_product_warehouse(start_date, end_date) { |
||||
|
var self = this; |
||||
|
// Initialize arrays to hold product warehouse names and their corresponding quantities.
|
||||
|
var product_warehouse_array = []; |
||||
|
var nearby_expire_qty = []; |
||||
|
// Prepare the date dictionary to pass as an argument in the RPC query.
|
||||
|
var date_dict = { 'start_date': start_date, 'end_date': end_date }; |
||||
|
// Send an RPC query to fetch data about products that are about to expire, categorized by their warehouses, from the server.
|
||||
|
rpc.query({ |
||||
|
model: 'stock.lot', |
||||
|
method: 'get_expire_product_warehouse', |
||||
|
args: [date_dict], |
||||
|
}).then(function (result) { |
||||
|
// Extract product warehouse names and their corresponding quantities from the fetched data.
|
||||
|
$.each(result, function (index, name) { |
||||
|
product_warehouse_array.push(index); |
||||
|
nearby_expire_qty.push(name); |
||||
|
}); |
||||
|
// Render the bar chart using Chart.js.
|
||||
|
const ctx = $('#nearby_expire_warehouse'); |
||||
|
self.state.charts.push(new Chart(ctx, { |
||||
|
type: 'doughnut', |
||||
|
data: { |
||||
|
labels: product_warehouse_array, |
||||
|
datasets: [{ |
||||
|
label: 'Quantity', |
||||
|
data: nearby_expire_qty, |
||||
|
backgroundColor: [ |
||||
|
'rgba(75, 192, 192, 0.2)', |
||||
|
'rgba(54, 162, 235, 0.2)', |
||||
|
'rgba(153, 102, 255, 0.2)', |
||||
|
'rgba(201, 203, 207, 0.2)', |
||||
|
'rgba(255, 99, 132, 0.2)', |
||||
|
'rgba(255, 159, 64, 0.2)', |
||||
|
'rgba(255, 205, 86, 0.2)', |
||||
|
], |
||||
|
borderColor: [ |
||||
|
'rgb(75, 192, 192)', |
||||
|
'rgb(54, 162, 235)', |
||||
|
'rgb(153, 102, 255)', |
||||
|
'rgb(201, 203, 207)', |
||||
|
'rgb(255, 99, 132)', |
||||
|
'rgb(255, 159, 64)', |
||||
|
'rgb(255, 205, 86)', |
||||
|
], |
||||
|
borderWidth: 1 |
||||
|
}] |
||||
|
}, |
||||
|
options: { |
||||
|
plugins: { |
||||
|
legend: { |
||||
|
position: 'bottom', |
||||
|
}, |
||||
|
title: { |
||||
|
display: true, |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
})); |
||||
|
}); |
||||
|
} |
||||
|
// Expired Products
|
||||
|
render_expired_products_graph(start_date, end_date) { |
||||
|
// Check if a chart with the ID 'expired_product_count' already exists and destroy it if it does.
|
||||
|
let chartStatus = Chart.getChart('expired_product_count'); |
||||
|
if (chartStatus !== undefined) { |
||||
|
chartStatus.destroy(); |
||||
|
} |
||||
|
// Initialize arrays to hold product names and their corresponding expired quantities.
|
||||
|
var product_array = []; |
||||
|
var expired_qty_array = []; |
||||
|
// Prepare the date dictionary to pass as an argument in the RPC query.
|
||||
|
var date_dict = { 'start_date': start_date, 'end_date': end_date }; |
||||
|
// Send an RPC query to fetch data about expired products from the server.
|
||||
|
let data = rpc.query({ |
||||
|
model: 'stock.lot', |
||||
|
method: 'get_expired_product', |
||||
|
args: [date_dict,] |
||||
|
}).then(function (result) { |
||||
|
// Extract product names and their corresponding expired quantities from the fetched data.
|
||||
|
$.each(result, function (index, name) { |
||||
|
product_array.push(index); |
||||
|
expired_qty_array.push(name); |
||||
|
}); |
||||
|
// Render the pie chart using Chart.js.
|
||||
|
const ctx = $('#expired_product_count'); |
||||
|
new Chart(ctx, { |
||||
|
type: 'pie', |
||||
|
data: { |
||||
|
labels: product_array, |
||||
|
datasets: [{ |
||||
|
label: 'Quantity', |
||||
|
data: expired_qty_array, |
||||
|
borderWidth: 1 |
||||
|
}] |
||||
|
}, |
||||
|
options: { |
||||
|
plugins: { |
||||
|
legend: { |
||||
|
position: 'bottom', |
||||
|
}, |
||||
|
title: { |
||||
|
display: true, |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
// Expired Products by Category
|
||||
|
expiry_by_category(start_date, end_date) { |
||||
|
// Check if a chart with the ID 'expired_product_category_count' already exists and destroy it if it does.
|
||||
|
let chartStatus = Chart.getChart('expired_product_category_count'); |
||||
|
if (chartStatus !== undefined) { |
||||
|
chartStatus.destroy(); |
||||
|
} |
||||
|
// Initialize arrays to hold product category names and their corresponding expired quantities.
|
||||
|
var product_category_array = []; |
||||
|
var expired_qty_array = []; |
||||
|
// Prepare the date dictionary to pass as an argument in the RPC query.
|
||||
|
var date_dict = { 'start_date': start_date, 'end_date': end_date }; |
||||
|
// Send an RPC query to fetch data about expired products categorized by their product categories from the server.
|
||||
|
rpc.query({ |
||||
|
model: 'stock.lot', |
||||
|
method: 'get_product_expiry_by_category', |
||||
|
args: [date_dict,session.user_context.allowed_company_ids] |
||||
|
}).then(function (result) { |
||||
|
// Extract product category names and their corresponding expired quantities from the fetched data.
|
||||
|
$.each(result, function (index, name) { |
||||
|
product_category_array.push(index); |
||||
|
expired_qty_array.push(name); |
||||
|
}); |
||||
|
// Render the bar chart using Chart.js.
|
||||
|
const ctx = $('#expired_product_category_count'); |
||||
|
new Chart(ctx, { |
||||
|
type: 'polarArea', |
||||
|
data: { |
||||
|
labels: product_category_array, |
||||
|
datasets: [{ |
||||
|
label: 'Quantity', |
||||
|
data: expired_qty_array, |
||||
|
borderWidth: 1 |
||||
|
}] |
||||
|
}, |
||||
|
options: { |
||||
|
plugins: { |
||||
|
legend: { |
||||
|
position: 'bottom', |
||||
|
}, |
||||
|
title: { |
||||
|
display: true, |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
ProductExpiryDashboard.template = "ProductExpiryDashboard" |
||||
|
actionRegistry.add('product_expiry', ProductExpiryDashboard); |
@ -0,0 +1,178 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8" ?> |
||||
|
<template id="expiry_dashboard_template"> |
||||
|
<!-- Product Expiry Dashboard Template --> |
||||
|
<t t-name="ProductExpiryDashboard" owl="1"> |
||||
|
<!-- Main component for the Product Expiry Dashboard --> |
||||
|
<!-- Container for the entire dashboard content --> |
||||
|
<div class="o_action_manager " |
||||
|
style="height:100%; overflow-y: scroll; overflow-x: scroll;"> |
||||
|
<!-- Main section of the dashboard --> |
||||
|
<section class="dashboard_main_section" id="main_section_manager"> |
||||
|
<!-- Dashboard header --> |
||||
|
<div class="dashboard-heading shadow-sm"> |
||||
|
<!-- Section to display products expired today --> |
||||
|
<div class="product_expired_heading card-block" t-on-click="click_expired_today"> |
||||
|
<!-- Heading for products expired today --> |
||||
|
<h2 style="margin-top: 8px;">Products Expired Today</h2> |
||||
|
<hr/> |
||||
|
</div> |
||||
|
<!-- Centered banner for the dashboard --> |
||||
|
<center> |
||||
|
<div style="width: 544px"> |
||||
|
<h1 class="banner-heading">Product Expiry Dashboard</h1> |
||||
|
</div> |
||||
|
</center> |
||||
|
<!-- Date filtering form --> |
||||
|
<div class="col-12 " style="color: white; display: flex; justify-content: flex-end;"> |
||||
|
<form class="form-group"> |
||||
|
<span>Start Date: </span> |
||||
|
<input type="date" id="start_date" name="start_date" t-on-change="filter_date" style="border-radius: 5px; font-family: monospace; padding: 5px; border: none;"/> |
||||
|
<span> End Date: </span> |
||||
|
<input type="date" id="end_date" name="end_date" t-on-change="filter_date" style="border-radius: 5px; font-family: monospace; padding: 5px; border: none;"/> |
||||
|
</form> |
||||
|
</div> |
||||
|
</div> |
||||
|
<br/> |
||||
|
<!-- Row for displaying cards showing expiries within different timeframes --> |
||||
|
<div class="row expiry_dashboard_row"> |
||||
|
<div class="col-md-4 col-xl-3"> |
||||
|
<!-- Card for products expiring in 1 day --> |
||||
|
<div class="card bg-c-blue order-card"> |
||||
|
<div class="card-block one-day" t-on-click="one_day_click"> |
||||
|
<h2 style="color:white; text-align:center;">Expiry Tomorrow</h2> |
||||
|
<hr/> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="col-md-4 col-xl-3"> |
||||
|
<!-- Card for products expiring in 7 days --> |
||||
|
<div class="card bg-c-green order-card"> |
||||
|
<div class="card-block seven-day" t-on-click="seven_day_click"> |
||||
|
<h2 style="color:white; text-align:center;">Expiry in 7 Days</h2> |
||||
|
<hr/> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="col-md-4 col-xl-3"> |
||||
|
<!-- Card for products expiring in 30 days --> |
||||
|
<div class="card bg-c-yellow order-card"> |
||||
|
<div class="card-block thirty-day" t-on-click="thirty_day_click"> |
||||
|
<h2 style="color:white; text-align:center;">Expiry in 30 Days</h2> |
||||
|
<hr/> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="col-md-4 col-xl-3"> |
||||
|
<!-- Card for products expiring in 120 days --> |
||||
|
<div class="card bg-c-pink order-card"> |
||||
|
<div class="card-block one-twenty-day" t-on-click="one_twenty_day_click"> |
||||
|
<h2 style="color:white; text-align:center;">Expiry in 120 Days</h2> |
||||
|
<hr/> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<br/> |
||||
|
<!-- Row for displaying charts related to expired products --> |
||||
|
<div class="row"> |
||||
|
<div class="col-md"> |
||||
|
<div class="shadow-sm m-2 p-4" |
||||
|
style="border-radius:5px; background-color: snow;" |
||||
|
t-on-click="click_expired_today"> |
||||
|
<center> |
||||
|
<h2 class="chart_heading" style="margin-top: 8px;">Products Expired Today</h2> |
||||
|
</center> |
||||
|
<!-- Chart for displaying the count of expired products --> |
||||
|
<div> |
||||
|
<canvas id="today_expire_products"/> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="col-md"> |
||||
|
<div class="shadow-sm m-2 p-4" |
||||
|
style="border-radius:5px; background-color: snow;"> |
||||
|
<center> |
||||
|
<h2 class="chart_heading" style="margin-top: 8px;">Products Expired Today by Category</h2> |
||||
|
</center> |
||||
|
<!-- Chart for displaying the count of expired products by category --> |
||||
|
<div> |
||||
|
<canvas id="today_expire_products_category"/> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- Row for displaying charts related to products expiring within 7 days --> |
||||
|
<div class="row"> |
||||
|
<div class="col-md"> |
||||
|
<div class="shadow-sm m-2 p-4" |
||||
|
style="border-radius:5px; background-color: snow;" |
||||
|
t-on-click="seven_day_click"> |
||||
|
<center> |
||||
|
<h2 class="chart_heading" style="margin-top: 8px;">Products Expire in 7 Days</h2> |
||||
|
</center> |
||||
|
<!-- Chart for displaying products expiring within 7 days --> |
||||
|
<div> |
||||
|
<canvas t-ref="asd" id="nearby_expire_product"/> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="col-md"> |
||||
|
<div class="shadow-sm m-2 p-4" |
||||
|
style="border-radius:5px; background-color: snow;"> |
||||
|
<center> |
||||
|
<h2 class="chart_heading" style="margin-top: 8px;">Products Category Expire in 7 Days</h2> |
||||
|
</center> |
||||
|
<!-- Chart for displaying products expiring by category within 7 days --> |
||||
|
<div> |
||||
|
<canvas id="nearby_expire_catg"/> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- Row for displaying charts related to products expiring within 7 days and their location/warehouse --> |
||||
|
<div class="row"> |
||||
|
<div class="col-lg-3"> |
||||
|
<div class="shadow-sm m-2 p-4" |
||||
|
style="border-radius:5px; background-color: snow;"> |
||||
|
<center> |
||||
|
<!-- Chart for displaying products expiring by location within 7 days --> |
||||
|
<h2 class="chart_heading" style="margin-top: 8px;">Location Expire in 7 Days</h2> |
||||
|
<canvas id="nearby_expire_location"/> |
||||
|
</center> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="col-lg-3"> |
||||
|
<div class="shadow-sm m-2 p-4" |
||||
|
style="border-radius:5px; background-color: snow;"> |
||||
|
<center> |
||||
|
<!-- Chart for displaying products expiring by warehouse within 7 days --> |
||||
|
<h2 class="chart_heading" style="margin-top: 8px;">Products by Warehouse Expire in 7 Days</h2> |
||||
|
<canvas id="nearby_expire_warehouse"/> |
||||
|
</center> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="col-lg-3"> |
||||
|
<div class="shadow-sm m-2 p-4" |
||||
|
style="border-radius:5px; background-color: snow;"> |
||||
|
<center> |
||||
|
<!-- Chart for displaying products expiring by location within 7 days --> |
||||
|
<h2 class="chart_heading" style="margin-top: 8px;">Expired Products</h2> |
||||
|
<canvas id="expired_product_count"/> |
||||
|
</center> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="col-lg-3"> |
||||
|
<div class="shadow-sm m-2 p-4" |
||||
|
style="border-radius:5px; background-color: snow;"> |
||||
|
<center> |
||||
|
<!-- Chart for displaying products expiring by warehouse within 7 days --> |
||||
|
<h2 class="chart_heading" style="margin-top: 8px;">Expired Products by Category</h2> |
||||
|
<canvas id="expired_product_category_count"/> |
||||
|
</center> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</section> |
||||
|
</div> |
||||
|
</t> |
||||
|
</template> |
@ -0,0 +1,21 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<odoo> |
||||
|
<data> |
||||
|
<!-- Record for defining a client action for the Expiry Dashboard --> |
||||
|
<record id="action_expiry_dashboard" model="ir.actions.client"> |
||||
|
<!-- Name of the client action --> |
||||
|
<field name="name">Expiry Dashboard</field> |
||||
|
<!-- Tag to identify the client action as related to product expiry --> |
||||
|
<field name="tag">product_expiry</field> |
||||
|
<!-- Target for opening the client action in the current window --> |
||||
|
<field name="target">current</field> |
||||
|
</record> |
||||
|
<!-- Menu item for accessing the Product Expiry Dashboard --> |
||||
|
<menuitem |
||||
|
id="expiry_dashboard" |
||||
|
name="Product Expiry Dashboard" |
||||
|
action="action_expiry_dashboard" |
||||
|
parent="stock.menu_stock_root" |
||||
|
sequence="1"/> |
||||
|
</data> |
||||
|
</odoo> |