feat: 币种分布按角色拆分(分销商成本收入/客户费用/管理员分布)

This commit is contained in:
yumoqing 2026-07-24 15:36:18 +08:00
parent 18e7dd5afd
commit a50935f669
9 changed files with 189 additions and 76 deletions

View File

@ -491,3 +491,30 @@ async def get_distributor_financial(sor, org_filter, period='today'):
'total_calls': total_calls,
'period': period,
}
async def get_customer_currency_stats(sor, org_filter, period='today'):
"""客户费用统计:按币种+时间维度"""
today_str = _date_module_date.today().isoformat()
date_sql = ""
ns = {'org': org_filter, 'today': today_str}
if period == 'month':
date_sql = "AND f.call_date BETWEEN ${month_start}$ AND ${today}$"
ns['month_start'] = today_str[:7] + '-01'
elif period == 'year':
date_sql = "AND f.call_date BETWEEN ${year_start}$ AND ${today}$"
ns['year_start'] = today_str[:4] + '-01-01'
elif period == 'today':
date_sql = "AND f.call_date = ${today}$"
sql = f"""SELECT COALESCE(f.currency, 'CNY') as currency,
COUNT(*) as call_count,
COALESCE(SUM(f.amount), 0) as revenue,
COALESCE(SUM(f.amount_cny), 0) as revenue_cny
FROM dm_model_call_fact f
WHERE f.userorgid = ${{org}}$ {date_sql}
GROUP BY f.currency ORDER BY revenue_cny DESC"""
recs = await sor.sqlExe(sql, ns)
return [{'currency': r.currency, 'call_count': int(r.call_count or 0),
'revenue': round(float(r.revenue or 0), 2),
'revenue_cny': round(float(r.revenue_cny or 0), 2)} for r in recs]

View File

@ -16,5 +16,11 @@
"今日活跃": "Today Active",
"在线用户": "Online Users",
"成功率": "Success Rate",
"失败数": "Failed"
"失败数": "Failed",
"币种成本收入": "Currency Cost & Revenue",
"币种费用": "Currency Expenses",
"今天": "Today",
"本月": "This Month",
"本年": "This Year",
"累计": "Cumulative"
}

View File

@ -16,5 +16,11 @@
"今日活跃": "本日アクティブ",
"在线用户": "オンラインユーザー",
"成功率": "成功率",
"失败数": "失敗数"
"失败数": "失敗数",
"币种成本收入": "通貨コスト収入",
"币种费用": "通貨費用",
"今天": "今日",
"本月": "今月",
"本年": "今年",
"累计": "累計"
}

View File

@ -16,5 +16,11 @@
"今日活跃": "오늘 활성",
"在线用户": "온라인 사용자",
"成功率": "성공률",
"失败数": "실패수"
"失败数": "실패수",
"币种成本收入": "통화 비용 수익",
"币种费用": "통화 비용",
"今天": "오늘",
"本月": "이번 달",
"本年": "올해",
"累计": "누계"
}

View File

@ -16,5 +16,11 @@
"今日活跃": "今日活跃",
"在线用户": "在线用户",
"成功率": "成功率",
"失败数": "失败数"
"失败数": "失败数",
"币种成本收入": "币种成本收入",
"币种费用": "币种费用",
"今天": "今天",
"本月": "本月",
"本年": "本年",
"累计": "累计"
}

View File

@ -462,6 +462,17 @@ async def j2_is_distributor(request):
except Exception:
return False
async def j2_customer_currency(request, period='today'):
"""Jinja2: 客户币种费用"""
try:
env = request._run_ns
scope, org_filter = await _resolve_view(request)
async with get_sor_context(env, MODULE_NAME) as sor:
return await get_customer_currency_stats(sor, org_filter, period)
except Exception:
return []
# ── 监控组件 j2 函数,每段独立 .ui 通过 RefreshWidget 加载 ──
async def j2_model_perf_data(request):
@ -581,6 +592,7 @@ def load_sage_datamart():
env.api_distributor_financial = api_distributor_financial
env.j2_distributor_financial = j2_distributor_financial
env.j2_is_distributor = j2_is_distributor
env.j2_customer_currency = j2_customer_currency
env.j2_model_perf_data = j2_model_perf_data
env.j2_currency_data = j2_currency_data
env.j2_provider_roi_data = j2_provider_roi_data

View File

@ -42,6 +42,7 @@ PATHS_LOGINED = [
f"/{MOD}/sect_user_apikeys.ui",
f"/{MOD}/sect_distributor_financial.ui",
f"/{MOD}/sect_distributor_currency.ui",
f"/{MOD}/sect_customer_currency.ui",
]
def run(role, path):

View File

@ -99,8 +99,16 @@
"widgettype": "VBox",
"options": {"css": "card", "width": "40%", "padding": "16px"},
"subwidgets": [
{% if is_distributor %}
{"widgettype": "Title4", "options": {"fontWeight": "600", "otext": "币种成本收入", "i18n": true, "marginBottom": "8px"}},
{"widgettype": "RefreshWidget", "options": {"period_seconds": 60, "url": "{{entire_url('sect_distributor_currency.ui')}}", "width": "100%"}}
{% elif is_admin %}
{"widgettype": "Title4", "options": {"fontWeight": "600", "otext": "币种分布", "i18n": true, "marginBottom": "8px"}},
{"widgettype": "RefreshWidget", "options": {"period_seconds": 60, "url": "{{entire_url('sect_currency.ui')}}", "width": "100%"}}
{% else %}
{"widgettype": "Title4", "options": {"fontWeight": "600", "otext": "币种费用", "i18n": true, "marginBottom": "8px"}},
{"widgettype": "RefreshWidget", "options": {"period_seconds": 60, "url": "{{entire_url('sect_customer_currency.ui')}}", "width": "100%"}}
{% endif %}
]
}
]

View File

@ -0,0 +1,41 @@
{% set today_data = j2_customer_currency(request, 'today') %}
{% set month_data = j2_customer_currency(request, 'month') %}
{% set year_data = j2_customer_currency(request, 'year') %}
{% set all_data = j2_customer_currency(request, 'all') %}
{% macro sum_cny(data) %}{% set total = namespace(v=0) %}{% for r in data %}{% set total.v = total.v + r.revenue_cny %}{% endfor %}{{ '%.2f' % total.v }}{% endmacro %}
{
"widgettype": "VBox",
"options": {"width": "100%"},
"subwidgets": [
{"widgettype": "HBox", "options": {"width": "100%", "gap": "10px", "marginBottom": "12px"},
"subwidgets": [
{"widgettype": "VBox", "options": {"css": "card", "padding": "10px", "borderRadius": "6px", "width": "25%"},
"subwidgets": [
{"widgettype": "Text", "options": {"otext": "今天", "i18n": true, "fontWeight": "600", "fontSize": "12px"}},
{"widgettype": "Text", "options": {"text": {{json.dumps('¥' + sum_cny(today_data), ensure_ascii=False)}},"fontWeight": "700", "fontSize": "16px", "marginTop": "4px"}}
]
},
{"widgettype": "VBox", "options": {"css": "card", "padding": "10px", "borderRadius": "6px", "width": "25%"},
"subwidgets": [
{"widgettype": "Text", "options": {"otext": "本月", "i18n": true, "fontWeight": "600", "fontSize": "12px"}},
{"widgettype": "Text", "options": {"text": {{json.dumps('¥' + sum_cny(month_data), ensure_ascii=False)}},"fontWeight": "700", "fontSize": "16px", "marginTop": "4px"}}
]
},
{"widgettype": "VBox", "options": {"css": "card", "padding": "10px", "borderRadius": "6px", "width": "25%"},
"subwidgets": [
{"widgettype": "Text", "options": {"otext": "本年", "i18n": true, "fontWeight": "600", "fontSize": "12px"}},
{"widgettype": "Text", "options": {"text": {{json.dumps('¥' + sum_cny(year_data), ensure_ascii=False)}},"fontWeight": "700", "fontSize": "16px", "marginTop": "4px"}}
]
},
{"widgettype": "VBox", "options": {"css": "card", "padding": "10px", "borderRadius": "6px", "width": "25%"},
"subwidgets": [
{"widgettype": "Text", "options": {"otext": "累计", "i18n": true, "fontWeight": "600", "fontSize": "12px"}},
{"widgettype": "Text", "options": {"text": {{json.dumps('¥' + sum_cny(all_data), ensure_ascii=False)}},"fontWeight": "700", "fontSize": "16px", "marginTop": "4px"}}
]
}
]
}
]
}