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"
+ }
+ }
+ ]
+}