Update main.py
Browse files
main.py
CHANGED
|
@@ -762,8 +762,8 @@ def admin_remove_member_from_org(org_id, member_uid):
|
|
| 762 |
@app.route('/api/admin/dashboard/stats', methods=['GET'])
|
| 763 |
def get_admin_dashboard_stats():
|
| 764 |
"""
|
| 765 |
-
Retrieves complete global statistics, including
|
| 766 |
-
|
| 767 |
"""
|
| 768 |
try:
|
| 769 |
verify_admin_and_get_uid(request.headers.get('Authorization'))
|
|
@@ -772,52 +772,54 @@ def get_admin_dashboard_stats():
|
|
| 772 |
all_users_docs = list(db.collection('users').stream())
|
| 773 |
all_orgs_docs = list(db.collection('organizations').stream())
|
| 774 |
|
| 775 |
-
|
| 776 |
-
logging.info(f"Admin Dashboard: Retrieved {len(all_users_docs)} users and {len(all_orgs_docs)} organizations")
|
| 777 |
-
|
| 778 |
-
# Data structures for aggregation
|
| 779 |
-
user_sales_data = {}
|
| 780 |
-
global_item_revenue = {} # Will now be nested: {item: {currency: total}}
|
| 781 |
-
global_expense_totals = {} # Will now be nested: {category: {currency: total}}
|
| 782 |
-
phone_to_user_map = {}
|
| 783 |
global_sales_rev_by_curr, global_cogs_by_curr, global_expenses_by_curr = {}, {}, {}
|
| 784 |
|
| 785 |
-
# --- First Pass:
|
| 786 |
pending_approvals, approved_users, admin_count = 0, 0, 0
|
| 787 |
approved_phone_numbers = []
|
|
|
|
| 788 |
for doc in all_users_docs:
|
| 789 |
user_data = doc.to_dict()
|
| 790 |
-
|
| 791 |
-
if user_data.get('
|
| 792 |
-
|
| 793 |
-
|
| 794 |
-
|
| 795 |
-
|
| 796 |
-
|
| 797 |
-
|
| 798 |
-
|
| 799 |
-
|
| 800 |
-
|
| 801 |
-
|
| 802 |
-
|
| 803 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 804 |
org_stats = {'total': len(all_orgs_docs)}
|
| 805 |
|
| 806 |
-
#
|
| 807 |
-
logging.info(f"Admin Dashboard: User stats - Total: {user_stats['total']}, Admins: {user_stats['admins']}, Approved: {user_stats['approvedForBot']}, Pending: {user_stats['pendingApproval']}")
|
| 808 |
-
|
| 809 |
-
# --- Second Pass: Aggregate all financial data ---
|
| 810 |
sales_count = 0
|
| 811 |
for phone in approved_phone_numbers:
|
| 812 |
try:
|
| 813 |
bot_data_id = phone.lstrip('+')
|
| 814 |
bot_user_ref = db.collection('users').document(bot_data_id)
|
|
|
|
| 815 |
user_sales_data[phone] = {'total_revenue_by_currency': {}, 'item_sales': {}}
|
| 816 |
last_seen_currency_code = normalize_currency_code(phone_to_user_map.get(phone, {}).get('defaultCurrency'), 'USD')
|
| 817 |
|
| 818 |
-
user_sales_count = 0
|
| 819 |
-
user_expenses_count = 0
|
| 820 |
-
|
| 821 |
for sale_doc in bot_user_ref.collection('sales').stream():
|
| 822 |
details = sale_doc.to_dict().get('details', {})
|
| 823 |
currency_code = normalize_currency_code(details.get('currency'), last_seen_currency_code)
|
|
@@ -829,7 +831,6 @@ def get_admin_dashboard_stats():
|
|
| 829 |
global_sales_rev_by_curr[currency_code] = global_sales_rev_by_curr.get(currency_code, 0) + sale_revenue
|
| 830 |
global_cogs_by_curr[currency_code] = global_cogs_by_curr.get(currency_code, 0) + (cost * quantity)
|
| 831 |
sales_count += 1
|
| 832 |
-
user_sales_count += 1
|
| 833 |
|
| 834 |
user_sales_data[phone]['total_revenue_by_currency'][currency_code] = user_sales_data[phone]['total_revenue_by_currency'].get(currency_code, 0) + sale_revenue
|
| 835 |
|
|
@@ -846,54 +847,28 @@ def get_admin_dashboard_stats():
|
|
| 846 |
global_expenses_by_curr[currency_code] = global_expenses_by_curr.get(currency_code, 0) + amount
|
| 847 |
if category not in global_expense_totals: global_expense_totals[category] = {}
|
| 848 |
global_expense_totals[category][currency_code] = global_expense_totals[category].get(currency_code, 0) + amount
|
| 849 |
-
user_expenses_count += 1
|
| 850 |
-
|
| 851 |
-
# Log per-user processing results
|
| 852 |
-
if user_sales_count > 0 or user_expenses_count > 0:
|
| 853 |
-
logging.info(f"Admin Dashboard: User {phone} - Sales: {user_sales_count}, Expenses: {user_expenses_count}")
|
| 854 |
-
|
| 855 |
except Exception as e:
|
| 856 |
logging.error(f"Admin stats: Could not process data for phone {phone}. Error: {e}")
|
| 857 |
continue
|
| 858 |
|
| 859 |
-
# Log financial aggregation results
|
| 860 |
-
logging.info(f"Admin Dashboard: Financial aggregation - Total sales: {sales_count}, Currencies found: {list(set(global_sales_rev_by_curr.keys()) | set(global_expenses_by_curr.keys()))}")
|
| 861 |
-
logging.info(f"Admin Dashboard: Revenue by currency: {global_sales_rev_by_curr}")
|
| 862 |
-
logging.info(f"Admin Dashboard: Expenses by currency: {global_expenses_by_curr}")
|
| 863 |
-
|
| 864 |
# --- Post-Processing: Generate Leaderboards For Each Currency ---
|
| 865 |
all_currencies = set(global_sales_rev_by_curr.keys()) | set(global_expenses_by_curr.keys())
|
| 866 |
|
| 867 |
-
leaderboards = {
|
| 868 |
-
'topUsersByRevenue': {},
|
| 869 |
-
'topSellingItems': {},
|
| 870 |
-
'topExpenses': {}
|
| 871 |
-
}
|
| 872 |
|
| 873 |
for currency in all_currencies:
|
| 874 |
-
# Rank Users for this currency
|
| 875 |
users_in_curr = [(phone, data['total_revenue_by_currency'].get(currency, 0)) for phone, data in user_sales_data.items() if data['total_revenue_by_currency'].get(currency, 0) > 0]
|
| 876 |
sorted_users = sorted(users_in_curr, key=lambda item: item[1], reverse=True)
|
| 877 |
-
leaderboards['topUsersByRevenue'][currency] = [{
|
| 878 |
-
'displayName': phone_to_user_map.get(phone, {}).get('displayName'),
|
| 879 |
-
'uid': phone_to_user_map.get(phone, {}).get('uid'),
|
| 880 |
-
'totalRevenue': round(revenue, 2)
|
| 881 |
-
} for phone, revenue in sorted_users[:5]]
|
| 882 |
|
| 883 |
-
# Rank Items for this currency
|
| 884 |
items_in_curr = [(name, totals.get(currency, 0)) for name, totals in global_item_revenue.items() if totals.get(currency, 0) > 0]
|
| 885 |
sorted_items = sorted(items_in_curr, key=lambda item: item[1], reverse=True)
|
| 886 |
leaderboards['topSellingItems'][currency] = [{'item': name, 'totalRevenue': round(revenue, 2)} for name, revenue in sorted_items[:5]]
|
| 887 |
|
| 888 |
-
# Rank Expenses for this currency
|
| 889 |
expenses_in_curr = [(name, totals.get(currency, 0)) for name, totals in global_expense_totals.items() if totals.get(currency, 0) > 0]
|
| 890 |
sorted_expenses = sorted(expenses_in_curr, key=lambda item: item[1], reverse=True)
|
| 891 |
leaderboards['topExpenses'][currency] = [{'category': name, 'totalAmount': round(amount, 2)} for name, amount in sorted_expenses[:5]]
|
| 892 |
|
| 893 |
-
# Log leaderboard generation results
|
| 894 |
-
for currency in all_currencies:
|
| 895 |
-
logging.info(f"Admin Dashboard: Leaderboard for {currency} - Top users: {len(leaderboards['topUsersByRevenue'][currency])}, Top items: {len(leaderboards['topSellingItems'][currency])}, Top expenses: {len(leaderboards['topExpenses'][currency])}")
|
| 896 |
-
|
| 897 |
# --- Final Assembly ---
|
| 898 |
global_net_profit_by_curr = {}
|
| 899 |
for curr in all_currencies:
|
|
@@ -910,29 +885,12 @@ def get_admin_dashboard_stats():
|
|
| 910 |
'totalSalesCount': sales_count,
|
| 911 |
}
|
| 912 |
|
| 913 |
-
|
| 914 |
-
|
| 915 |
-
'
|
| 916 |
-
|
| 917 |
-
'systemStats': system_stats,
|
| 918 |
-
'leaderboards': leaderboards
|
| 919 |
-
}
|
| 920 |
-
|
| 921 |
-
logging.info(f"Admin Dashboard: Final response structure - userStats keys: {list(user_stats.keys())}, systemStats keys: {list(system_stats.keys())}, leaderboards keys: {list(leaderboards.keys())}")
|
| 922 |
-
logging.info(f"Admin Dashboard: System stats summary - Total currencies: {len(all_currencies)}, Net profit by currency: {global_net_profit_by_curr}")
|
| 923 |
-
|
| 924 |
-
# Log the complete response data
|
| 925 |
-
logging.info(f"Admin Dashboard: Complete response data: {response_data}")
|
| 926 |
-
|
| 927 |
-
# Log response data size for debugging
|
| 928 |
-
import json
|
| 929 |
-
response_size = len(json.dumps(response_data))
|
| 930 |
-
logging.info(f"Admin Dashboard: Response data size: {response_size} bytes")
|
| 931 |
-
|
| 932 |
-
return jsonify(response_data), 200
|
| 933 |
|
| 934 |
except PermissionError as e:
|
| 935 |
-
logging.error(f"Admin Dashboard: Permission denied - {e}")
|
| 936 |
return jsonify({'error': str(e)}), 403
|
| 937 |
except Exception as e:
|
| 938 |
logging.error(f"Admin failed to fetch dashboard stats: {e}", exc_info=True)
|
|
|
|
| 762 |
@app.route('/api/admin/dashboard/stats', methods=['GET'])
|
| 763 |
def get_admin_dashboard_stats():
|
| 764 |
"""
|
| 765 |
+
Retrieves complete global statistics, including an accurate total user count
|
| 766 |
+
and separate Top 5 leaderboards for each currency.
|
| 767 |
"""
|
| 768 |
try:
|
| 769 |
verify_admin_and_get_uid(request.headers.get('Authorization'))
|
|
|
|
| 772 |
all_users_docs = list(db.collection('users').stream())
|
| 773 |
all_orgs_docs = list(db.collection('organizations').stream())
|
| 774 |
|
| 775 |
+
user_sales_data, global_item_revenue, global_expense_totals, phone_to_user_map = {}, {}, {}, {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 776 |
global_sales_rev_by_curr, global_cogs_by_curr, global_expenses_by_curr = {}, {}, {}
|
| 777 |
|
| 778 |
+
# --- First Pass: Process all documents to gather stats and approved phones ---
|
| 779 |
pending_approvals, approved_users, admin_count = 0, 0, 0
|
| 780 |
approved_phone_numbers = []
|
| 781 |
+
|
| 782 |
for doc in all_users_docs:
|
| 783 |
user_data = doc.to_dict()
|
| 784 |
+
# This check ensures we only process documents that are actual user profiles
|
| 785 |
+
if user_data.get('email'):
|
| 786 |
+
phone = user_data.get('phone')
|
| 787 |
+
if user_data.get('phoneStatus') == 'pending':
|
| 788 |
+
pending_approvals += 1
|
| 789 |
+
elif user_data.get('phoneStatus') == 'approved' and phone:
|
| 790 |
+
approved_users += 1
|
| 791 |
+
approved_phone_numbers.append(phone)
|
| 792 |
+
phone_to_user_map[phone] = {
|
| 793 |
+
'displayName': user_data.get('displayName', 'N/A'), 'uid': user_data.get('uid'),
|
| 794 |
+
'defaultCurrency': user_data.get('defaultCurrency', 'USD')
|
| 795 |
+
}
|
| 796 |
+
if user_data.get('isAdmin', False):
|
| 797 |
+
admin_count += 1
|
| 798 |
+
|
| 799 |
+
# --- THE FIX IS HERE: Create a filtered list of only user profiles for an accurate count ---
|
| 800 |
+
user_profile_docs = [doc for doc in all_users_docs if doc.to_dict().get('email')]
|
| 801 |
+
|
| 802 |
+
# Assemble the accurate user stats
|
| 803 |
+
user_stats = {
|
| 804 |
+
'total': len(user_profile_docs), # Use the length of the filtered list
|
| 805 |
+
'admins': admin_count,
|
| 806 |
+
'approvedForBot': approved_users,
|
| 807 |
+
'pendingApproval': pending_approvals
|
| 808 |
+
}
|
| 809 |
+
# --- END OF FIX ---
|
| 810 |
+
|
| 811 |
org_stats = {'total': len(all_orgs_docs)}
|
| 812 |
|
| 813 |
+
# --- Second Pass: Aggregate financial data from bot documents ---
|
|
|
|
|
|
|
|
|
|
| 814 |
sales_count = 0
|
| 815 |
for phone in approved_phone_numbers:
|
| 816 |
try:
|
| 817 |
bot_data_id = phone.lstrip('+')
|
| 818 |
bot_user_ref = db.collection('users').document(bot_data_id)
|
| 819 |
+
|
| 820 |
user_sales_data[phone] = {'total_revenue_by_currency': {}, 'item_sales': {}}
|
| 821 |
last_seen_currency_code = normalize_currency_code(phone_to_user_map.get(phone, {}).get('defaultCurrency'), 'USD')
|
| 822 |
|
|
|
|
|
|
|
|
|
|
| 823 |
for sale_doc in bot_user_ref.collection('sales').stream():
|
| 824 |
details = sale_doc.to_dict().get('details', {})
|
| 825 |
currency_code = normalize_currency_code(details.get('currency'), last_seen_currency_code)
|
|
|
|
| 831 |
global_sales_rev_by_curr[currency_code] = global_sales_rev_by_curr.get(currency_code, 0) + sale_revenue
|
| 832 |
global_cogs_by_curr[currency_code] = global_cogs_by_curr.get(currency_code, 0) + (cost * quantity)
|
| 833 |
sales_count += 1
|
|
|
|
| 834 |
|
| 835 |
user_sales_data[phone]['total_revenue_by_currency'][currency_code] = user_sales_data[phone]['total_revenue_by_currency'].get(currency_code, 0) + sale_revenue
|
| 836 |
|
|
|
|
| 847 |
global_expenses_by_curr[currency_code] = global_expenses_by_curr.get(currency_code, 0) + amount
|
| 848 |
if category not in global_expense_totals: global_expense_totals[category] = {}
|
| 849 |
global_expense_totals[category][currency_code] = global_expense_totals[category].get(currency_code, 0) + amount
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 850 |
except Exception as e:
|
| 851 |
logging.error(f"Admin stats: Could not process data for phone {phone}. Error: {e}")
|
| 852 |
continue
|
| 853 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 854 |
# --- Post-Processing: Generate Leaderboards For Each Currency ---
|
| 855 |
all_currencies = set(global_sales_rev_by_curr.keys()) | set(global_expenses_by_curr.keys())
|
| 856 |
|
| 857 |
+
leaderboards = {'topUsersByRevenue': {}, 'topSellingItems': {}, 'topExpenses': {}}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 858 |
|
| 859 |
for currency in all_currencies:
|
|
|
|
| 860 |
users_in_curr = [(phone, data['total_revenue_by_currency'].get(currency, 0)) for phone, data in user_sales_data.items() if data['total_revenue_by_currency'].get(currency, 0) > 0]
|
| 861 |
sorted_users = sorted(users_in_curr, key=lambda item: item[1], reverse=True)
|
| 862 |
+
leaderboards['topUsersByRevenue'][currency] = [{'displayName': phone_to_user_map.get(phone, {}).get('displayName'), 'uid': phone_to_user_map.get(phone, {}).get('uid'), 'totalRevenue': round(revenue, 2)} for phone, revenue in sorted_users[:5]]
|
|
|
|
|
|
|
|
|
|
|
|
|
| 863 |
|
|
|
|
| 864 |
items_in_curr = [(name, totals.get(currency, 0)) for name, totals in global_item_revenue.items() if totals.get(currency, 0) > 0]
|
| 865 |
sorted_items = sorted(items_in_curr, key=lambda item: item[1], reverse=True)
|
| 866 |
leaderboards['topSellingItems'][currency] = [{'item': name, 'totalRevenue': round(revenue, 2)} for name, revenue in sorted_items[:5]]
|
| 867 |
|
|
|
|
| 868 |
expenses_in_curr = [(name, totals.get(currency, 0)) for name, totals in global_expense_totals.items() if totals.get(currency, 0) > 0]
|
| 869 |
sorted_expenses = sorted(expenses_in_curr, key=lambda item: item[1], reverse=True)
|
| 870 |
leaderboards['topExpenses'][currency] = [{'category': name, 'totalAmount': round(amount, 2)} for name, amount in sorted_expenses[:5]]
|
| 871 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 872 |
# --- Final Assembly ---
|
| 873 |
global_net_profit_by_curr = {}
|
| 874 |
for curr in all_currencies:
|
|
|
|
| 885 |
'totalSalesCount': sales_count,
|
| 886 |
}
|
| 887 |
|
| 888 |
+
return jsonify({
|
| 889 |
+
'userStats': user_stats, 'organizationStats': org_stats,
|
| 890 |
+
'systemStats': system_stats, 'leaderboards': leaderboards
|
| 891 |
+
}), 200
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 892 |
|
| 893 |
except PermissionError as e:
|
|
|
|
| 894 |
return jsonify({'error': str(e)}), 403
|
| 895 |
except Exception as e:
|
| 896 |
logging.error(f"Admin failed to fetch dashboard stats: {e}", exc_info=True)
|