"""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 from tenant_db import get_tenant_conn 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.""" conn = get_tenant_conn(g.tenant_id) cur = conn.cursor() today = datetime.utcnow().date() month_start = today.replace(day=1) try: # Sales today cur.execute( """SELECT COUNT(*) as count, COALESCE(SUM(total), 0) as total FROM sales WHERE DATE(created_at) = %s""", (today,) ) today_sales = cur.fetchone() # Sales this month cur.execute( """SELECT COUNT(*) as count, COALESCE(SUM(total), 0) as total FROM sales WHERE DATE(created_at) >= %s""", (month_start,) ) month_sales = cur.fetchone() # Top 5 products today cur.execute( """SELECT si.name, SUM(si.quantity) as qty, SUM(si.subtotal) as revenue FROM sale_items si JOIN sales s ON si.sale_id = s.id WHERE DATE(s.created_at) = %s GROUP BY si.name ORDER BY revenue DESC LIMIT 5""", (today,) ) top_products = cur.fetchall() # Hourly sales today (0-23) cur.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,) ) hourly = cur.fetchall() hourly_map = {row[0]: {'count': row[1], 'total': row[2]} for row in hourly} return jsonify({ 'today': { 'sales_count': today_sales[0], 'sales_total': float(today_sales[1]) if today_sales[1] is not None else 0, }, 'month': { 'sales_count': month_sales[0], 'sales_total': float(month_sales[1]) if month_sales[1] is not None else 0, }, 'top_products': [ {'name': row[0], 'quantity': row[1], 'revenue': float(row[2]) if row[2] is not None else 0} for row in top_products ], 'hourly_sales': [ {'hour': h, 'count': hourly_map.get(h, {}).get('count', 0), 'total': float(hourly_map.get(h, {}).get('total', 0))} for h in range(24) ], }) finally: cur.close() conn.close() @dashboard_stats_bp.route('/stats/employees', methods=['GET']) @require_auth() def get_employee_stats(): """Sales per employee today.""" conn = get_tenant_conn(g.tenant_id) cur = conn.cursor() today = datetime.utcnow().date() try: cur.execute( """SELECT e.name, COUNT(s.id) as sales, COALESCE(SUM(s.total), 0) as total FROM sales s JOIN employees e ON s.employee_id = e.id WHERE DATE(s.created_at) = %s GROUP BY e.name ORDER BY total DESC""", (today,) ) rows = cur.fetchall() return jsonify({ 'employees': [ {'name': row[0], 'sales': row[1], 'total': float(row[2]) if row[2] is not None else 0} for row in rows ] }) finally: cur.close() conn.close()