- dashboard_stats_bp.py: endpoints /pos/api/dashboard/stats and /pos/api/dashboard/stats/employees (sales today/month, hourly, top products, employee productivity) - dashboard-stats.js: renders hourly sales bar chart and top products doughnut chart using Chart.js - chart.umd.min.js: vendored Chart.js v4.4.2
108 lines
3.4 KiB
Python
108 lines
3.4 KiB
Python
"""Dashboard Stats Blueprint — In-app real-time analytics.
|
|
|
|
Endpoints for sales, productivity, and top products charts.
|
|
"""
|
|
from flask import Blueprint, request, jsonify, g
|
|
from functools import wraps
|
|
from datetime import datetime, timedelta
|
|
from decimal import Decimal
|
|
import json
|
|
|
|
dashboard_stats_bp = Blueprint('dashboard_stats', __name__, url_prefix='/pos/api/dashboard')
|
|
|
|
|
|
from middleware import require_auth
|
|
|
|
|
|
class DecimalEncoder(json.JSONEncoder):
|
|
def default(self, o):
|
|
if isinstance(o, Decimal):
|
|
return float(o)
|
|
return super().default(o)
|
|
|
|
|
|
@dashboard_stats_bp.route('/stats', methods=['GET'])
|
|
@require_auth()
|
|
def get_stats():
|
|
"""Summary stats for today and this month."""
|
|
from tenant_db import get_tenant_db
|
|
db = get_tenant_db()
|
|
today = datetime.utcnow().date()
|
|
month_start = today.replace(day=1)
|
|
|
|
# Sales today
|
|
today_sales = db.execute(
|
|
"""SELECT COUNT(*) as count, COALESCE(SUM(total), 0) as total
|
|
FROM sales WHERE DATE(created_at) = %s""", (today,)
|
|
).fetchone()
|
|
|
|
# Sales this month
|
|
month_sales = db.execute(
|
|
"""SELECT COUNT(*) as count, COALESCE(SUM(total), 0) as total
|
|
FROM sales WHERE DATE(created_at) >= %s""", (month_start,)
|
|
).fetchone()
|
|
|
|
# Top 5 products today
|
|
top_products = db.execute(
|
|
"""SELECT p.name, SUM(si.quantity) as qty, SUM(si.total) as revenue
|
|
FROM sale_items si
|
|
JOIN sales s ON si.sale_id = s.id_sale
|
|
JOIN parts p ON si.part_id = p.id_part
|
|
WHERE DATE(s.created_at) = %s
|
|
GROUP BY p.name
|
|
ORDER BY revenue DESC
|
|
LIMIT 5""", (today,)
|
|
).fetchall()
|
|
|
|
# Hourly sales today (0-23)
|
|
hourly = db.execute(
|
|
"""SELECT EXTRACT(HOUR FROM created_at)::int as hour,
|
|
COUNT(*) as count, COALESCE(SUM(total), 0) as total
|
|
FROM sales WHERE DATE(created_at) = %s
|
|
GROUP BY hour ORDER BY hour""", (today,)
|
|
).fetchall()
|
|
hourly_map = {row['hour']: {'count': row['count'], 'total': row['total']} for row in hourly}
|
|
|
|
return jsonify({
|
|
'today': {
|
|
'sales_count': today_sales['count'],
|
|
'sales_total': today_sales['total'],
|
|
},
|
|
'month': {
|
|
'sales_count': month_sales['count'],
|
|
'sales_total': month_sales['total'],
|
|
},
|
|
'top_products': [
|
|
{'name': row['name'], 'quantity': row['qty'], 'revenue': row['revenue']}
|
|
for row in top_products
|
|
],
|
|
'hourly_sales': [
|
|
{'hour': h, 'count': hourly_map.get(h, {}).get('count', 0),
|
|
'total': hourly_map.get(h, {}).get('total', 0)}
|
|
for h in range(24)
|
|
],
|
|
}, cls=DecimalEncoder)
|
|
|
|
|
|
@dashboard_stats_bp.route('/stats/employees', methods=['GET'])
|
|
@require_auth()
|
|
def get_employee_stats():
|
|
"""Sales per employee today."""
|
|
from tenant_db import get_tenant_db
|
|
db = get_tenant_db()
|
|
today = datetime.utcnow().date()
|
|
rows = db.execute(
|
|
"""SELECT e.name, COUNT(s.id_sale) as sales, COALESCE(SUM(s.total), 0) as total
|
|
FROM sales s
|
|
JOIN employees e ON s.employee_id = e.id_employee
|
|
WHERE DATE(s.created_at) = %s
|
|
GROUP BY e.name
|
|
ORDER BY total DESC""", (today,)
|
|
).fetchall()
|
|
return jsonify({
|
|
'employees': [
|
|
{'name': row['name'], 'sales': row['sales'], 'total': row['total']}
|
|
for row in rows
|
|
]
|
|
}, cls=DecimalEncoder)
|