Dashboard update
@ -6,14 +6,14 @@ PAYU_MERCHANT_SALT=DusXSSjqqSMTPSpw32hlFXF6LKY2Zm3y
|
||||
PAYU_SUCCESS_URL=http://localhost:4200/payment-success
|
||||
PAYU_FAILURE_URL=http://localhost:4200/payment-failure
|
||||
|
||||
FLASK_ENV=production
|
||||
FLASK_ENV=development
|
||||
|
||||
MYSQL_HOST=db
|
||||
MYSQL_USER=vendinguser
|
||||
MYSQL_PASSWORD=vendingpass
|
||||
MYSQL_DATABASE=vending
|
||||
|
||||
SQLITE_DB_PATH=machines
|
||||
SQLITE_DB_PATH=machines.db
|
||||
|
||||
BREVO_SMTP_EMAIL=smukeshsn2000@gmail.com
|
||||
BREVO_SMTP_KEY=your-brevo-smtp-key
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
from app import db # Add this if not already there
|
||||
from sqlalchemy import func # Add this
|
||||
from sqlalchemy import func, extract # Add this
|
||||
from flask import Blueprint, request, jsonify, send_from_directory
|
||||
from app.services.services import MachineService, UserService, ProductService, serial_service, TransactionService, RoleService
|
||||
from app.models.models import Machine, User, Transaction, Product, VendingSlot, Role
|
||||
@ -853,6 +853,10 @@ def get_current_user_from_token():
|
||||
return None
|
||||
|
||||
|
||||
from flask import jsonify, request
|
||||
from sqlalchemy import func, extract
|
||||
from datetime import datetime
|
||||
|
||||
@bp.route('/dashboard-metrics', methods=['GET'])
|
||||
def get_dashboard_metrics_role_based():
|
||||
"""Get dashboard metrics filtered by user role"""
|
||||
@ -877,16 +881,12 @@ def get_dashboard_metrics_role_based():
|
||||
|
||||
# ROLE-BASED FILTERING
|
||||
if user_role in ['Management', 'SuperAdmin', 'Admin']:
|
||||
# Show overall system stats
|
||||
response = get_admin_dashboard_metrics(filter_type)
|
||||
elif user_role == 'Client':
|
||||
# Show only this client's data
|
||||
response = get_Client_dashboard_metrics(current_user.id, filter_type)
|
||||
elif user_role == 'Refiller':
|
||||
# Show product/warehouse related stats
|
||||
response = get_refiller_dashboard_metrics(filter_type)
|
||||
elif user_role == 'Servicer':
|
||||
# Show machine maintenance stats
|
||||
response = get_servicer_dashboard_metrics(filter_type)
|
||||
else:
|
||||
return jsonify({'error': 'Invalid role'}), 403
|
||||
@ -914,23 +914,52 @@ def get_admin_dashboard_metrics(filter_type):
|
||||
machine_count = Machine.query.count()
|
||||
machine_title = 'All Machines'
|
||||
|
||||
# Client count (users with role 'Client')
|
||||
# Client count
|
||||
clients = User.query.filter_by(roles='Client').count()
|
||||
|
||||
# Company users (Admin, SuperAdmin, Management, Refiller, Servicer)
|
||||
# Company users
|
||||
company_users = User.query.filter(
|
||||
User.roles.in_(['Management', 'SuperAdmin', 'Admin', 'Refiller', 'Servicer'])
|
||||
).count()
|
||||
|
||||
# Active and inactive machines
|
||||
active_machines = Machine.query.filter_by(operation_status='active').count()
|
||||
inactive_machines = Machine.query.filter_by(operation_status='inactive').count()
|
||||
|
||||
# COUNT TRANSACTIONS AND CALCULATE SALES
|
||||
transactions_count = Transaction.query.count()
|
||||
|
||||
# Calculate total sales (sum of all successful transaction amounts)
|
||||
from sqlalchemy import func
|
||||
# Calculate total sales
|
||||
total_sales = db.session.query(func.sum(Transaction.amount))\
|
||||
.filter(Transaction.status == 'Success')\
|
||||
.scalar() or 0.0
|
||||
|
||||
# PAYMENT BREAKDOWN
|
||||
payment_breakdown = get_payment_breakdown()
|
||||
|
||||
# MACHINE OPERATION STATUS
|
||||
machine_operation_status = {
|
||||
'Online': Machine.query.filter_by(connection_status='online').count(),
|
||||
'Offline': Machine.query.filter_by(connection_status='offline').count(),
|
||||
'Down (Planned)': Machine.query.filter_by(operation_status='down_planned').count(),
|
||||
'Down': Machine.query.filter_by(operation_status='down').count(),
|
||||
'Standby': Machine.query.filter_by(operation_status='standby').count(),
|
||||
'Terminated': Machine.query.filter_by(operation_status='terminated').count(),
|
||||
'N/A': Machine.query.filter(Machine.operation_status.is_(None)).count()
|
||||
}
|
||||
|
||||
# MACHINE STOCK STATUS - Calculate based on vending slots
|
||||
machine_stock_status = calculate_machine_stock_status()
|
||||
|
||||
# PRODUCT SALES YEARLY
|
||||
product_sales_yearly = get_product_sales_yearly()
|
||||
|
||||
# SALES YEARLY
|
||||
sales_yearly = get_sales_yearly()
|
||||
|
||||
# TOP SELLING PRODUCTS
|
||||
top_selling_products = get_top_selling_products()
|
||||
|
||||
return {
|
||||
'machine_title': machine_title,
|
||||
'machine_count': machine_count,
|
||||
@ -939,13 +968,21 @@ def get_admin_dashboard_metrics(filter_type):
|
||||
'client_users': clients,
|
||||
'transactions': transactions_count,
|
||||
'sales': f'{total_sales:.2f}',
|
||||
'active_machines': active_machines,
|
||||
'inactive_machines': inactive_machines,
|
||||
'payment_breakdown': payment_breakdown,
|
||||
'machine_operation_status': machine_operation_status,
|
||||
'machine_stock_status': machine_stock_status,
|
||||
'product_sales_yearly': product_sales_yearly,
|
||||
'sales_yearly': sales_yearly,
|
||||
'top_selling_products': top_selling_products,
|
||||
'user_role': 'admin',
|
||||
'user_type': 'Overall System'
|
||||
}
|
||||
|
||||
|
||||
def get_Client_dashboard_metrics(user_id, filter_type):
|
||||
"""Get metrics for Client/Client - Only their own data"""
|
||||
"""Get metrics for Client - Only their own data"""
|
||||
# Get only machines belonging to this client
|
||||
if filter_type == 'active':
|
||||
machine_count = Machine.query.filter_by(
|
||||
@ -963,28 +1000,53 @@ def get_Client_dashboard_metrics(user_id, filter_type):
|
||||
machine_count = Machine.query.filter_by(client_id=user_id).count()
|
||||
machine_title = 'My Machines'
|
||||
|
||||
# For Client, clients = 1 (themselves)
|
||||
clients = 1
|
||||
|
||||
# Company users not relevant for Client
|
||||
company_users = 0
|
||||
|
||||
# GET THIS CLIENT'S TRANSACTIONS AND SALES
|
||||
# Get machine IDs for this client
|
||||
client_machine_ids = [m.machine_id for m in Machine.query.filter_by(client_id=user_id).all()]
|
||||
|
||||
# Active and inactive machines for this client
|
||||
active_machines = Machine.query.filter_by(client_id=user_id, operation_status='active').count()
|
||||
inactive_machines = Machine.query.filter_by(client_id=user_id, operation_status='inactive').count()
|
||||
|
||||
# Count transactions for this client's machines
|
||||
transactions_count = Transaction.query.filter(
|
||||
Transaction.machine_id.in_(client_machine_ids)
|
||||
).count() if client_machine_ids else 0
|
||||
|
||||
# Calculate sales for this client's machines
|
||||
from sqlalchemy import func
|
||||
total_sales = db.session.query(func.sum(Transaction.amount))\
|
||||
.filter(Transaction.machine_id.in_(client_machine_ids))\
|
||||
.filter(Transaction.status == 'Success')\
|
||||
.scalar() or 0.0 if client_machine_ids else 0.0
|
||||
|
||||
# PAYMENT BREAKDOWN for client's machines
|
||||
payment_breakdown = get_payment_breakdown(client_machine_ids)
|
||||
|
||||
# MACHINE OPERATION STATUS for client's machines
|
||||
machine_operation_status = {
|
||||
'Online': Machine.query.filter(Machine.client_id == user_id, Machine.connection_status == 'online').count(),
|
||||
'Offline': Machine.query.filter(Machine.client_id == user_id, Machine.connection_status == 'offline').count(),
|
||||
'Down (Planned)': Machine.query.filter(Machine.client_id == user_id, Machine.operation_status == 'down_planned').count(),
|
||||
'Down': Machine.query.filter(Machine.client_id == user_id, Machine.operation_status == 'down').count(),
|
||||
'Standby': Machine.query.filter(Machine.client_id == user_id, Machine.operation_status == 'standby').count(),
|
||||
'Terminated': Machine.query.filter(Machine.client_id == user_id, Machine.operation_status == 'terminated').count(),
|
||||
'N/A': Machine.query.filter(Machine.client_id == user_id, Machine.operation_status.is_(None)).count()
|
||||
}
|
||||
|
||||
# MACHINE STOCK STATUS for client's machines
|
||||
machine_stock_status = calculate_machine_stock_status(client_machine_ids)
|
||||
|
||||
# PRODUCT SALES YEARLY for client's machines
|
||||
product_sales_yearly = get_product_sales_yearly(client_machine_ids)
|
||||
|
||||
# SALES YEARLY for client's machines
|
||||
sales_yearly = get_sales_yearly(client_machine_ids)
|
||||
|
||||
# TOP SELLING PRODUCTS for client's machines
|
||||
top_selling_products = get_top_selling_products(client_machine_ids)
|
||||
|
||||
return {
|
||||
'machine_title': machine_title,
|
||||
'machine_count': machine_count,
|
||||
@ -993,6 +1055,14 @@ def get_Client_dashboard_metrics(user_id, filter_type):
|
||||
'client_users': clients,
|
||||
'transactions': transactions_count,
|
||||
'sales': f'{total_sales:.2f}',
|
||||
'active_machines': active_machines,
|
||||
'inactive_machines': inactive_machines,
|
||||
'payment_breakdown': payment_breakdown,
|
||||
'machine_operation_status': machine_operation_status,
|
||||
'machine_stock_status': machine_stock_status,
|
||||
'product_sales_yearly': product_sales_yearly,
|
||||
'sales_yearly': sales_yearly,
|
||||
'top_selling_products': top_selling_products,
|
||||
'user_role': 'Client',
|
||||
'user_type': 'My Business'
|
||||
}
|
||||
@ -1000,21 +1070,32 @@ def get_Client_dashboard_metrics(user_id, filter_type):
|
||||
|
||||
def get_refiller_dashboard_metrics(filter_type):
|
||||
"""Get metrics for Refiller - Product/warehouse focused"""
|
||||
# Machines they need to service
|
||||
machine_count = Machine.query.count()
|
||||
|
||||
# Products in warehouse
|
||||
product_count = Product.query.count()
|
||||
|
||||
# Transaction count (all)
|
||||
transactions_count = Transaction.query.count()
|
||||
|
||||
# Total sales
|
||||
from sqlalchemy import func
|
||||
total_sales = db.session.query(func.sum(Transaction.amount))\
|
||||
.filter(Transaction.status == 'Success')\
|
||||
.scalar() or 0.0
|
||||
|
||||
active_machines = Machine.query.filter_by(operation_status='active').count()
|
||||
inactive_machines = Machine.query.filter_by(operation_status='inactive').count()
|
||||
|
||||
payment_breakdown = get_payment_breakdown()
|
||||
machine_operation_status = {
|
||||
'Online': Machine.query.filter_by(connection_status='online').count(),
|
||||
'Offline': Machine.query.filter_by(connection_status='offline').count(),
|
||||
'Down (Planned)': Machine.query.filter_by(operation_status='down_planned').count(),
|
||||
'Down': Machine.query.filter_by(operation_status='down').count(),
|
||||
'Standby': Machine.query.filter_by(operation_status='standby').count(),
|
||||
'Terminated': Machine.query.filter_by(operation_status='terminated').count(),
|
||||
'N/A': Machine.query.filter(Machine.operation_status.is_(None)).count()
|
||||
}
|
||||
machine_stock_status = calculate_machine_stock_status()
|
||||
product_sales_yearly = get_product_sales_yearly()
|
||||
sales_yearly = get_sales_yearly()
|
||||
top_selling_products = get_top_selling_products()
|
||||
|
||||
return {
|
||||
'machine_title': 'Machines to Service',
|
||||
'machine_count': machine_count,
|
||||
@ -1023,7 +1104,15 @@ def get_refiller_dashboard_metrics(filter_type):
|
||||
'client_users': 0,
|
||||
'transactions': transactions_count,
|
||||
'sales': f'{total_sales:.2f}',
|
||||
'active_machines': active_machines,
|
||||
'inactive_machines': inactive_machines,
|
||||
'product_count': product_count,
|
||||
'payment_breakdown': payment_breakdown,
|
||||
'machine_operation_status': machine_operation_status,
|
||||
'machine_stock_status': machine_stock_status,
|
||||
'product_sales_yearly': product_sales_yearly,
|
||||
'sales_yearly': sales_yearly,
|
||||
'top_selling_products': top_selling_products,
|
||||
'user_role': 'refiller',
|
||||
'user_type': 'Inventory Management'
|
||||
}
|
||||
@ -1031,7 +1120,6 @@ def get_refiller_dashboard_metrics(filter_type):
|
||||
|
||||
def get_servicer_dashboard_metrics(filter_type):
|
||||
"""Get metrics for Servicer - Machine maintenance focused"""
|
||||
# All machines they can service
|
||||
if filter_type == 'active':
|
||||
machine_count = Machine.query.filter_by(operation_status='active').count()
|
||||
machine_title = 'Active Machines'
|
||||
@ -1042,18 +1130,31 @@ def get_servicer_dashboard_metrics(filter_type):
|
||||
machine_count = Machine.query.count()
|
||||
machine_title = 'Total Machines'
|
||||
|
||||
# Machines needing maintenance
|
||||
maintenance_count = Machine.query.filter_by(operation_status='maintenance').count()
|
||||
|
||||
# Transaction count (all)
|
||||
transactions_count = Transaction.query.count()
|
||||
|
||||
# Total sales
|
||||
from sqlalchemy import func
|
||||
total_sales = db.session.query(func.sum(Transaction.amount))\
|
||||
.filter(Transaction.status == 'Success')\
|
||||
.scalar() or 0.0
|
||||
|
||||
active_machines = Machine.query.filter_by(operation_status='active').count()
|
||||
inactive_machines = Machine.query.filter_by(operation_status='inactive').count()
|
||||
|
||||
payment_breakdown = get_payment_breakdown()
|
||||
machine_operation_status = {
|
||||
'Online': Machine.query.filter_by(connection_status='online').count(),
|
||||
'Offline': Machine.query.filter_by(connection_status='offline').count(),
|
||||
'Down (Planned)': Machine.query.filter_by(operation_status='down_planned').count(),
|
||||
'Down': Machine.query.filter_by(operation_status='down').count(),
|
||||
'Standby': Machine.query.filter_by(operation_status='standby').count(),
|
||||
'Terminated': Machine.query.filter_by(operation_status='terminated').count(),
|
||||
'N/A': Machine.query.filter(Machine.operation_status.is_(None)).count()
|
||||
}
|
||||
machine_stock_status = calculate_machine_stock_status()
|
||||
product_sales_yearly = get_product_sales_yearly()
|
||||
sales_yearly = get_sales_yearly()
|
||||
top_selling_products = get_top_selling_products()
|
||||
|
||||
return {
|
||||
'machine_title': machine_title,
|
||||
'machine_count': machine_count,
|
||||
@ -1062,11 +1163,374 @@ def get_servicer_dashboard_metrics(filter_type):
|
||||
'client_users': 0,
|
||||
'transactions': transactions_count,
|
||||
'sales': f'{total_sales:.2f}',
|
||||
'active_machines': active_machines,
|
||||
'inactive_machines': inactive_machines,
|
||||
'maintenance_count': maintenance_count,
|
||||
'payment_breakdown': payment_breakdown,
|
||||
'machine_operation_status': machine_operation_status,
|
||||
'machine_stock_status': machine_stock_status,
|
||||
'product_sales_yearly': product_sales_yearly,
|
||||
'sales_yearly': sales_yearly,
|
||||
'top_selling_products': top_selling_products,
|
||||
'user_role': 'servicer',
|
||||
'user_type': 'Machine Maintenance'
|
||||
}
|
||||
|
||||
|
||||
# HELPER FUNCTIONS
|
||||
|
||||
def get_payment_breakdown(machine_ids=None):
|
||||
"""Calculate payment breakdown from transactions"""
|
||||
query = db.session.query(
|
||||
Transaction.payment_type,
|
||||
func.sum(Transaction.amount).label('total')
|
||||
)
|
||||
|
||||
if machine_ids:
|
||||
query = query.filter(Transaction.machine_id.in_(machine_ids))
|
||||
|
||||
query = query.filter(Transaction.status == 'Success')\
|
||||
.group_by(Transaction.payment_type)
|
||||
|
||||
payment_data = query.all()
|
||||
|
||||
breakdown = {
|
||||
'cash': 0.0,
|
||||
'cashless': 0.0,
|
||||
'upi_wallet_card': 0.0,
|
||||
'upi_wallet_paytm': 0.0,
|
||||
'refund': 0.0,
|
||||
'refund_processed': 0.0,
|
||||
'total': 0.0
|
||||
}
|
||||
|
||||
for payment_type, total in payment_data:
|
||||
if payment_type and total:
|
||||
payment_type_lower = payment_type.lower()
|
||||
if 'cash' in payment_type_lower and 'cashless' not in payment_type_lower:
|
||||
breakdown['cash'] += float(total)
|
||||
elif 'cashless' in payment_type_lower:
|
||||
breakdown['cashless'] += float(total)
|
||||
elif 'phonepe' in payment_type_lower or 'card' in payment_type_lower:
|
||||
breakdown['upi_wallet_card'] += float(total)
|
||||
elif 'paytm' in payment_type_lower:
|
||||
breakdown['upi_wallet_paytm'] += float(total)
|
||||
|
||||
# Calculate refunds
|
||||
refund_query = db.session.query(func.sum(Transaction.return_amount))
|
||||
if machine_ids:
|
||||
refund_query = refund_query.filter(Transaction.machine_id.in_(machine_ids))
|
||||
|
||||
breakdown['refund'] = float(refund_query.filter(
|
||||
Transaction.amount_receiving_status == 'Dispense failed refund'
|
||||
).scalar() or 0.0)
|
||||
|
||||
breakdown['refund_processed'] = float(refund_query.filter(
|
||||
Transaction.amount_receiving_status == 'Refund processed'
|
||||
).scalar() or 0.0)
|
||||
|
||||
breakdown['total'] = sum([
|
||||
breakdown['cash'],
|
||||
breakdown['cashless'],
|
||||
breakdown['upi_wallet_card'],
|
||||
breakdown['upi_wallet_paytm']
|
||||
])
|
||||
|
||||
return breakdown
|
||||
|
||||
|
||||
def calculate_machine_stock_status(machine_ids=None):
|
||||
"""Calculate machine stock status based on vending slots"""
|
||||
query = Machine.query
|
||||
if machine_ids:
|
||||
query = query.filter(Machine.machine_id.in_(machine_ids))
|
||||
|
||||
machines = query.all()
|
||||
|
||||
status_counts = {
|
||||
'75 - 100%': 0,
|
||||
'50 - 75%': 0,
|
||||
'25 - 50%': 0,
|
||||
'0 - 25%': 0,
|
||||
'N/A': 0
|
||||
}
|
||||
|
||||
for machine in machines:
|
||||
slots = VendingSlot.query.filter_by(machine_id=machine.machine_id).all()
|
||||
if not slots:
|
||||
status_counts['N/A'] += 1
|
||||
continue
|
||||
|
||||
total_slots = len(slots)
|
||||
filled_slots = sum(1 for slot in slots if slot.units and slot.units > 0)
|
||||
|
||||
if total_slots == 0:
|
||||
status_counts['N/A'] += 1
|
||||
else:
|
||||
fill_percentage = (filled_slots / total_slots) * 100
|
||||
if fill_percentage >= 75:
|
||||
status_counts['75 - 100%'] += 1
|
||||
elif fill_percentage >= 50:
|
||||
status_counts['50 - 75%'] += 1
|
||||
elif fill_percentage >= 25:
|
||||
status_counts['25 - 50%'] += 1
|
||||
elif fill_percentage > 0:
|
||||
status_counts['0 - 25%'] += 1
|
||||
else:
|
||||
status_counts['0 - 25%'] += 1
|
||||
|
||||
return status_counts
|
||||
|
||||
|
||||
def get_product_sales_yearly(machine_ids=None):
|
||||
"""Get product sales aggregated by year"""
|
||||
query = db.session.query(
|
||||
extract('year', Transaction.created_at).label('year'),
|
||||
func.sum(Transaction.amount).label('total_sales')
|
||||
)
|
||||
|
||||
if machine_ids:
|
||||
query = query.filter(Transaction.machine_id.in_(machine_ids))
|
||||
|
||||
query = query.filter(Transaction.status == 'Success')\
|
||||
.group_by(extract('year', Transaction.created_at))\
|
||||
.order_by('year')
|
||||
|
||||
results = query.all()
|
||||
|
||||
return [{'year': str(int(year)), 'amount': float(total_sales)}
|
||||
for year, total_sales in results if year]
|
||||
|
||||
|
||||
def get_sales_yearly(machine_ids=None):
|
||||
"""Get sales aggregated by year"""
|
||||
return get_product_sales_yearly(machine_ids)
|
||||
|
||||
|
||||
def get_top_selling_products(machine_ids=None, limit=10):
|
||||
"""Get top selling products"""
|
||||
query = db.session.query(
|
||||
Transaction.product_name,
|
||||
func.sum(Transaction.quantity).label('total_quantity')
|
||||
)
|
||||
|
||||
if machine_ids:
|
||||
query = query.filter(Transaction.machine_id.in_(machine_ids))
|
||||
|
||||
query = query.filter(Transaction.status == 'Success')\
|
||||
.group_by(Transaction.product_name)\
|
||||
.order_by(func.sum(Transaction.quantity).desc())\
|
||||
.limit(limit)
|
||||
|
||||
results = query.all()
|
||||
|
||||
return [{'product_name': product_name, 'quantity': int(total_quantity)}
|
||||
for product_name, total_quantity in results]
|
||||
|
||||
@bp.route('/product-sales', methods=['GET'])
|
||||
def get_product_sales_filtered():
|
||||
"""Get product sales data filtered by time range"""
|
||||
try:
|
||||
current_user = get_current_user_from_token()
|
||||
if not current_user:
|
||||
return jsonify({'error': 'Authentication required'}), 401
|
||||
|
||||
time_range = request.args.get('time_range', 'year')
|
||||
user_role = current_user.roles
|
||||
|
||||
print(f"Product Sales Filter - User: {current_user.username}, Range: {time_range}")
|
||||
|
||||
# Get machine IDs based on role
|
||||
machine_ids = None
|
||||
if user_role == 'Client':
|
||||
machine_ids = [m.machine_id for m in Machine.query.filter_by(client_id=current_user.id).all()]
|
||||
|
||||
# Fetch data based on time range
|
||||
product_sales = get_sales_data_by_range(time_range, machine_ids)
|
||||
|
||||
return jsonify({'product_sales': product_sales}), 200
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error in product sales filter: {str(e)}")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@bp.route('/sales-data', methods=['GET'])
|
||||
def get_sales_filtered():
|
||||
"""Get sales data filtered by time range"""
|
||||
try:
|
||||
current_user = get_current_user_from_token()
|
||||
if not current_user:
|
||||
return jsonify({'error': 'Authentication required'}), 401
|
||||
|
||||
time_range = request.args.get('time_range', 'year')
|
||||
user_role = current_user.roles
|
||||
|
||||
print(f"Sales Filter - User: {current_user.username}, Range: {time_range}")
|
||||
|
||||
# Get machine IDs based on role
|
||||
machine_ids = None
|
||||
if user_role == 'Client':
|
||||
machine_ids = [m.machine_id for m in Machine.query.filter_by(client_id=current_user.id).all()]
|
||||
|
||||
# Fetch data based on time range
|
||||
sales_data = get_sales_data_by_range(time_range, machine_ids)
|
||||
|
||||
return jsonify({'sales_data': sales_data}), 200
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error in sales filter: {str(e)}")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@bp.route('/top-products', methods=['GET'])
|
||||
def get_top_products_filtered():
|
||||
"""Get top selling products filtered by time range"""
|
||||
try:
|
||||
current_user = get_current_user_from_token()
|
||||
if not current_user:
|
||||
return jsonify({'error': 'Authentication required'}), 401
|
||||
|
||||
time_range = request.args.get('time_range', 'year')
|
||||
user_role = current_user.roles
|
||||
|
||||
print(f"Top Products Filter - User: {current_user.username}, Range: {time_range}")
|
||||
|
||||
# Get machine IDs based on role
|
||||
machine_ids = None
|
||||
if user_role == 'Client':
|
||||
machine_ids = [m.machine_id for m in Machine.query.filter_by(client_id=current_user.id).all()]
|
||||
|
||||
# Fetch data based on time range
|
||||
top_products = get_top_products_by_range(time_range, machine_ids)
|
||||
|
||||
return jsonify({'top_products': top_products}), 200
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error in top products filter: {str(e)}")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
# Helper function to get date filter based on time range
|
||||
def get_date_filter(time_range):
|
||||
"""Return date filter based on time range"""
|
||||
now = datetime.now()
|
||||
|
||||
if time_range == 'day':
|
||||
# Last 24 hours
|
||||
return now - timedelta(days=1)
|
||||
elif time_range == 'week':
|
||||
# Last 7 days
|
||||
return now - timedelta(weeks=1)
|
||||
elif time_range == 'month':
|
||||
# Last 30 days
|
||||
return now - timedelta(days=30)
|
||||
elif time_range == 'year':
|
||||
# All time, grouped by year
|
||||
return None
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def get_sales_data_by_range(time_range, machine_ids=None):
|
||||
"""Get sales data aggregated by time range"""
|
||||
date_filter = get_date_filter(time_range)
|
||||
|
||||
# Base query
|
||||
query = db.session.query(Transaction.created_at, Transaction.amount)\
|
||||
.filter(Transaction.status == 'Success')
|
||||
|
||||
# Apply machine filter if needed
|
||||
if machine_ids:
|
||||
query = query.filter(Transaction.machine_id.in_(machine_ids))
|
||||
|
||||
# Apply date filter
|
||||
if date_filter:
|
||||
query = query.filter(Transaction.created_at >= date_filter)
|
||||
|
||||
transactions = query.all()
|
||||
|
||||
# Aggregate based on time range
|
||||
if time_range == 'day':
|
||||
# Group by hour
|
||||
aggregated = {}
|
||||
for txn in transactions:
|
||||
hour = txn.created_at.strftime('%H:00')
|
||||
if hour not in aggregated:
|
||||
aggregated[hour] = 0
|
||||
aggregated[hour] += float(txn.amount)
|
||||
|
||||
return [{'year': k, 'amount': v} for k, v in sorted(aggregated.items())]
|
||||
|
||||
elif time_range == 'week':
|
||||
# Group by day
|
||||
aggregated = {}
|
||||
for txn in transactions:
|
||||
day = txn.created_at.strftime('%a') # Mon, Tue, etc.
|
||||
if day not in aggregated:
|
||||
aggregated[day] = 0
|
||||
aggregated[day] += float(txn.amount)
|
||||
|
||||
# Sort by weekday order
|
||||
days_order = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
|
||||
return [{'year': day, 'amount': aggregated.get(day, 0)} for day in days_order]
|
||||
|
||||
elif time_range == 'month':
|
||||
# Group by day
|
||||
aggregated = {}
|
||||
for txn in transactions:
|
||||
day = txn.created_at.strftime('%d')
|
||||
if day not in aggregated:
|
||||
aggregated[day] = 0
|
||||
aggregated[day] += float(txn.amount)
|
||||
|
||||
return [{'year': k, 'amount': v} for k, v in sorted(aggregated.items())]
|
||||
|
||||
else: # year or all time
|
||||
# Group by year
|
||||
query = db.session.query(
|
||||
extract('year', Transaction.created_at).label('year'),
|
||||
func.sum(Transaction.amount).label('total_sales')
|
||||
).filter(Transaction.status == 'Success')
|
||||
|
||||
if machine_ids:
|
||||
query = query.filter(Transaction.machine_id.in_(machine_ids))
|
||||
|
||||
query = query.group_by(extract('year', Transaction.created_at))\
|
||||
.order_by('year')
|
||||
|
||||
results = query.all()
|
||||
|
||||
return [{'year': str(int(year)), 'amount': float(total_sales)}
|
||||
for year, total_sales in results if year]
|
||||
|
||||
|
||||
def get_top_products_by_range(time_range, machine_ids=None, limit=10):
|
||||
"""Get top selling products filtered by time range"""
|
||||
date_filter = get_date_filter(time_range)
|
||||
|
||||
# Base query
|
||||
query = db.session.query(
|
||||
Transaction.product_name,
|
||||
func.sum(Transaction.quantity).label('total_quantity')
|
||||
).filter(Transaction.status == 'Success')
|
||||
|
||||
# Apply machine filter if needed
|
||||
if machine_ids:
|
||||
query = query.filter(Transaction.machine_id.in_(machine_ids))
|
||||
|
||||
# Apply date filter
|
||||
if date_filter:
|
||||
query = query.filter(Transaction.created_at >= date_filter)
|
||||
|
||||
query = query.group_by(Transaction.product_name)\
|
||||
.order_by(func.sum(Transaction.quantity).desc())\
|
||||
.limit(limit)
|
||||
|
||||
results = query.all()
|
||||
|
||||
return [{'product_name': product_name, 'quantity': int(total_quantity)}
|
||||
for product_name, total_quantity in results]
|
||||
# Additional utility routes for PayU integration
|
||||
|
||||
@bp.route('/verify-payu-hash', methods=['POST'])
|
||||
@ -1603,7 +2067,7 @@ def get_roles():
|
||||
return jsonify({'error': 'Authentication required'}), 401
|
||||
|
||||
# Only Management and SuperAdmin can view roles
|
||||
if current_user.roles not in ['Management', 'SuperAdmin']:
|
||||
if current_user.roles not in ['Management', 'SuperAdmin','Admin']:
|
||||
return jsonify({'error': 'Permission denied'}), 403
|
||||
|
||||
roles = RoleService.get_all_roles()
|
||||
@ -1622,7 +2086,7 @@ def create_role():
|
||||
return jsonify({'error': 'Authentication required'}), 401
|
||||
|
||||
# Only Management and SuperAdmin can create roles
|
||||
if current_user.roles not in ['Management', 'SuperAdmin']:
|
||||
if current_user.roles not in ['Management', 'SuperAdmin','Admin']:
|
||||
return jsonify({'error': 'Permission denied'}), 403
|
||||
|
||||
data = request.json
|
||||
@ -1645,7 +2109,7 @@ def update_role(role_id):
|
||||
return jsonify({'error': 'Authentication required'}), 401
|
||||
|
||||
# Only Management and SuperAdmin can update roles
|
||||
if current_user.roles not in ['Management', 'SuperAdmin']:
|
||||
if current_user.roles not in ['Management', 'SuperAdmin','Admin']:
|
||||
return jsonify({'error': 'Permission denied'}), 403
|
||||
|
||||
data = request.json
|
||||
@ -1668,7 +2132,7 @@ def delete_role(role_id):
|
||||
return jsonify({'error': 'Authentication required'}), 401
|
||||
|
||||
# Only Management and SuperAdmin can delete roles
|
||||
if current_user.roles not in ['Management', 'SuperAdmin']:
|
||||
if current_user.roles not in ['Management', 'SuperAdmin','Admin']:
|
||||
return jsonify({'error': 'Permission denied'}), 403
|
||||
|
||||
RoleService.delete_role(role_id)
|
||||
@ -1690,7 +2154,7 @@ def get_available_permissions():
|
||||
return jsonify({'error': 'Authentication required'}), 401
|
||||
|
||||
# Only Management and SuperAdmin can view permissions
|
||||
if current_user.roles not in ['Management', 'SuperAdmin']:
|
||||
if current_user.roles not in ['Management', 'SuperAdmin','Admin']:
|
||||
return jsonify({'error': 'Permission denied'}), 403
|
||||
|
||||
# Define all available permissions
|
||||
|
||||
|
Before Width: | Height: | Size: 353 KiB After Width: | Height: | Size: 353 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 2.1 MiB After Width: | Height: | Size: 2.1 MiB |
18
Machine-Backend/reset_password.py
Normal file
@ -0,0 +1,18 @@
|
||||
from app import create_app, db
|
||||
from app.models.models import User
|
||||
from werkzeug.security import generate_password_hash
|
||||
|
||||
app = create_app()
|
||||
|
||||
with app.app_context():
|
||||
email = "test@example.com"
|
||||
new_password = "test123"
|
||||
|
||||
user = User.query.filter_by(email=email).first()
|
||||
if not user:
|
||||
print("User not found ❌")
|
||||
else:
|
||||
# Hash password in the way your model expects
|
||||
user.password = generate_password_hash(new_password, method='pbkdf2:sha256')
|
||||
db.session.commit()
|
||||
print(f"Password reset successfully for {email}. Login with: {new_password}")
|
||||