diff --git a/accounting/init.py b/accounting/init.py index 5721684..74fbbfc 100644 --- a/accounting/init.py +++ b/accounting/init.py @@ -7,6 +7,7 @@ from .accounting_config import Accounting from .bill import write_bill from .openaccount import openOwnerAccounts, openProviderAccounts, openResellerAccounts, openCustomerAccounts, openRetailRelationshipAccounts from .getaccount import getAccountBalance, getCustomerBalance, getAccountByName, get_account_total_amount +from .stats import get_accounting_stats from .recharge import RechargeBiz, recharge_accounting from .consume import consume_accounting @@ -71,3 +72,4 @@ def load_accounting(): g.get_accdetail = get_accdetail g.all_my_accounts = all_my_accounts g.openRetailRelationshipAccounts = openRetailRelationshipAccounts + g.get_accounting_stats = get_accounting_stats diff --git a/accounting/stats.py b/accounting/stats.py new file mode 100644 index 0000000..27f5afc --- /dev/null +++ b/accounting/stats.py @@ -0,0 +1,77 @@ +from appPublic.log import debug, exception +from sqlor.dbpools import get_sor_context +from datetime import datetime, timedelta + +async def get_accounting_stats(request): + """Get accounting statistics for the current user's organization""" + env = request._run_ns + userorgid = await env.get_userorgid() + + now = datetime.now() + today_start = now.strftime('%Y-%m-%d') + month_start = now.strftime('%Y-%m-01') + tomorrow = (now + timedelta(days=1)).strftime('%Y-%m-%d') + + stats = { + 'total_balance': 0, + 'today_amount': 0, + 'month_amount': 0, + 'account_count': 0 + } + + async with get_sor_context(request._run_ns, 'accounting') as sor: + # Total balance across all accounts + sql_balance = """ + SELECT COALESCE(SUM(CASE WHEN balance_at = '1' THEN balance ELSE -balance END), 0) as total + FROM account + WHERE orgid = ${orgid}$ + """ + recs = await sor.sqlExe(sql_balance, {'orgid': userorgid}) + if recs: + stats['total_balance'] = float(recs[0].total or 0) + + # Account count + sql_count = """ + SELECT COUNT(*) as cnt FROM account WHERE orgid = ${orgid}$ + """ + recs = await sor.sqlExe(sql_count, {'orgid': userorgid}) + if recs: + stats['account_count'] = int(recs[0].cnt or 0) + + # Today's consumption (acc_dir=1 means debit/consumption) + sql_today = """ + SELECT COALESCE(SUM(amount), 0) as total + FROM acc_detail a + JOIN account b ON a.accountid = b.id + WHERE b.orgid = ${orgid}$ + AND a.acc_dir = 1 + AND a.acc_date >= ${from_date}$ + AND a.acc_date < ${to_date}$ + """ + recs = await sor.sqlExe(sql_today, { + 'orgid': userorgid, + 'from_date': today_start, + 'to_date': tomorrow + }) + if recs: + stats['today_amount'] = float(recs[0].total or 0) + + # This month's consumption + sql_month = """ + SELECT COALESCE(SUM(amount), 0) as total + FROM acc_detail a + JOIN account b ON a.accountid = b.id + WHERE b.orgid = ${orgid}$ + AND a.acc_dir = 1 + AND a.acc_date >= ${from_date}$ + AND a.acc_date < ${to_date}$ + """ + recs = await sor.sqlExe(sql_month, { + 'orgid': userorgid, + 'from_date': month_start, + 'to_date': tomorrow + }) + if recs: + stats['month_amount'] = float(recs[0].total or 0) + + return stats diff --git a/wwwroot/index.ui b/wwwroot/index.ui index 368ebdf..54327f0 100644 --- a/wwwroot/index.ui +++ b/wwwroot/index.ui @@ -35,6 +35,40 @@ } ] }, + { + "widgettype": "ResponsableBox", + "options": { + "gap": "16px", + "minWidth": "200px", + "marginBottom": "24px" + }, + "subwidgets": [ + { + "widgettype": "urlwidget", + "options": { + "url": "{{entire_url('/accounting/stat_total_balance.ui')}}" + } + }, + { + "widgettype": "urlwidget", + "options": { + "url": "{{entire_url('/accounting/stat_today_consumption.ui')}}" + } + }, + { + "widgettype": "urlwidget", + "options": { + "url": "{{entire_url('/accounting/stat_month_consumption.ui')}}" + } + }, + { + "widgettype": "urlwidget", + "options": { + "url": "{{entire_url('/accounting/stat_account_count.ui')}}" + } + } + ] + }, { "widgettype": "ResponsableBox", "options": { diff --git a/wwwroot/stat_account_count.ui b/wwwroot/stat_account_count.ui new file mode 100644 index 0000000..d2b49a3 --- /dev/null +++ b/wwwroot/stat_account_count.ui @@ -0,0 +1,53 @@ +{% set stats = get_accounting_stats(request) %} +{ + "widgettype": "VBox", + "options": { + "bgcolor": "#1E293B", + "padding": "20px", + "borderRadius": "12px", + "border": "1px solid #334155", + "flex": "1", + "minHeight": "110px" + }, + "subwidgets": [ + { + "widgettype": "HBox", + "options": { + "alignItems": "center", + "marginBottom": "12px" + }, + "subwidgets": [ + { + "widgettype": "Svg", + "options": { + "svg": "", + "width": "24px", + "height": "24px" + } + }, + { + "widgettype": "Filler" + } + ] + }, + { + "widgettype": "Text", + "options": { + "text": "{{stats.account_count}}", + "fontSize": "32px", + "fontWeight": "700", + "color": "#F1F5F9", + "lineHeight": "1.1" + } + }, + { + "widgettype": "Text", + "options": { + "text": "账户数量", + "fontSize": "14px", + "color": "#94A3B8", + "marginTop": "4px" + } + } + ] +} diff --git a/wwwroot/stat_month_consumption.ui b/wwwroot/stat_month_consumption.ui new file mode 100644 index 0000000..8dcb266 --- /dev/null +++ b/wwwroot/stat_month_consumption.ui @@ -0,0 +1,53 @@ +{% set stats = get_accounting_stats(request) %} +{ + "widgettype": "VBox", + "options": { + "bgcolor": "#1E293B", + "padding": "20px", + "borderRadius": "12px", + "border": "1px solid #334155", + "flex": "1", + "minHeight": "110px" + }, + "subwidgets": [ + { + "widgettype": "HBox", + "options": { + "alignItems": "center", + "marginBottom": "12px" + }, + "subwidgets": [ + { + "widgettype": "Svg", + "options": { + "svg": "", + "width": "24px", + "height": "24px" + } + }, + { + "widgettype": "Filler" + } + ] + }, + { + "widgettype": "Text", + "options": { + "text": "¥{{'%.2f' % stats.month_amount}}", + "fontSize": "32px", + "fontWeight": "700", + "color": "#F1F5F9", + "lineHeight": "1.1" + } + }, + { + "widgettype": "Text", + "options": { + "text": "本月消费", + "fontSize": "14px", + "color": "#94A3B8", + "marginTop": "4px" + } + } + ] +} diff --git a/wwwroot/stat_today_consumption.ui b/wwwroot/stat_today_consumption.ui new file mode 100644 index 0000000..4f4f5ee --- /dev/null +++ b/wwwroot/stat_today_consumption.ui @@ -0,0 +1,53 @@ +{% set stats = get_accounting_stats(request) %} +{ + "widgettype": "VBox", + "options": { + "bgcolor": "#1E293B", + "padding": "20px", + "borderRadius": "12px", + "border": "1px solid #334155", + "flex": "1", + "minHeight": "110px" + }, + "subwidgets": [ + { + "widgettype": "HBox", + "options": { + "alignItems": "center", + "marginBottom": "12px" + }, + "subwidgets": [ + { + "widgettype": "Svg", + "options": { + "svg": "", + "width": "24px", + "height": "24px" + } + }, + { + "widgettype": "Filler" + } + ] + }, + { + "widgettype": "Text", + "options": { + "text": "¥{{'%.2f' % stats.today_amount}}", + "fontSize": "32px", + "fontWeight": "700", + "color": "#F1F5F9", + "lineHeight": "1.1" + } + }, + { + "widgettype": "Text", + "options": { + "text": "今日消费", + "fontSize": "14px", + "color": "#94A3B8", + "marginTop": "4px" + } + } + ] +} diff --git a/wwwroot/stat_total_balance.ui b/wwwroot/stat_total_balance.ui new file mode 100644 index 0000000..4687c21 --- /dev/null +++ b/wwwroot/stat_total_balance.ui @@ -0,0 +1,53 @@ +{% set stats = get_accounting_stats(request) %} +{ + "widgettype": "VBox", + "options": { + "bgcolor": "#1E293B", + "padding": "20px", + "borderRadius": "12px", + "border": "1px solid #334155", + "flex": "1", + "minHeight": "110px" + }, + "subwidgets": [ + { + "widgettype": "HBox", + "options": { + "alignItems": "center", + "marginBottom": "12px" + }, + "subwidgets": [ + { + "widgettype": "Svg", + "options": { + "svg": "", + "width": "24px", + "height": "24px" + } + }, + { + "widgettype": "Filler" + } + ] + }, + { + "widgettype": "Text", + "options": { + "text": "¥{{'%.2f' % stats.total_balance}}", + "fontSize": "32px", + "fontWeight": "700", + "color": "#F1F5F9", + "lineHeight": "1.1" + } + }, + { + "widgettype": "Text", + "options": { + "text": "账户总余额", + "fontSize": "14px", + "color": "#94A3B8", + "marginTop": "4px" + } + } + ] +}