From 202fe6b5783d5e32474e5d5dad05f624a8cf0204 Mon Sep 17 00:00:00 2001 From: yumoqing Date: Thu, 14 May 2026 17:45:56 +0800 Subject: [PATCH 1/9] bugfix --- accounting/init.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/accounting/init.py b/accounting/init.py index a47ac48..5721684 100644 --- a/accounting/init.py +++ b/accounting/init.py @@ -19,16 +19,10 @@ async def all_my_accounts(request): b.id, a.name, b.balance_at, -case when c.balance is null then 0.00 else c.balance end as balance +b.balance from subject a, - account b left join - ( - select a.* - from acc_balance a, - (select accountid, max(acc_date) max_date from acc_balance group by accountid) b - where a.accountid=b.accountid and a.acc_date=b.max_date - ) c on c.accountid = b.id + account b where b.subjectid = a.id and b.orgid = ${orgid}$ """ From 9fe2b31407d79d60686823fd12e272a36c92d48a Mon Sep 17 00:00:00 2001 From: yumoqing Date: Thu, 21 May 2026 12:46:36 +0800 Subject: [PATCH 2/9] feat: add json table definitions for all models (converted from xlsx) --- models/acc_balance.json | 36 +++++++++++++ models/acc_detail.json | 70 +++++++++++++++++++++++++ models/account.json | 99 +++++++++++++++++++++++++++++++++++ models/account_config.json | 61 +++++++++++++++++++++ models/accounting_config.json | 98 ++++++++++++++++++++++++++++++++++ models/accounting_log.json | 68 ++++++++++++++++++++++++ models/bill.json | 77 +++++++++++++++++++++++++++ models/bill_detail.json | 67 ++++++++++++++++++++++++ models/ledger.json | 48 +++++++++++++++++ models/subject.json | 53 +++++++++++++++++++ 10 files changed, 677 insertions(+) create mode 100644 models/acc_balance.json create mode 100644 models/acc_detail.json create mode 100644 models/account.json create mode 100644 models/account_config.json create mode 100644 models/accounting_config.json create mode 100644 models/accounting_log.json create mode 100644 models/bill.json create mode 100644 models/bill_detail.json create mode 100644 models/ledger.json create mode 100644 models/subject.json diff --git a/models/acc_balance.json b/models/acc_balance.json new file mode 100644 index 0000000..0e75eeb --- /dev/null +++ b/models/acc_balance.json @@ -0,0 +1,36 @@ +{ + "summary": [ + { + "name": "acc_balance", + "title": "账户余额表", + "primary": [ + "id" + ] + } + ], + "fields": [ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32 + }, + { + "name": "accountid", + "title": "账户id", + "type": "str", + "length": 32 + }, + { + "name": "acc_date", + "title": "记账日期", + "type": "date" + }, + { + "name": "balance", + "title": "账户余额", + "type": "float", + "length": 20 + } + ] +} \ No newline at end of file diff --git a/models/acc_detail.json b/models/acc_detail.json new file mode 100644 index 0000000..80a6a23 --- /dev/null +++ b/models/acc_detail.json @@ -0,0 +1,70 @@ +{ + "summary": [ + { + "name": "acc_detail", + "title": "账户明细表", + "primary": [ + "id" + ] + } + ], + "fields": [ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32 + }, + { + "name": "accountid", + "title": "账户id", + "type": "str", + "length": 32 + }, + { + "name": "acc_no", + "title": "明细顺序号", + "type": "short" + }, + { + "name": "acc_date", + "title": "记账日期", + "type": "date" + }, + { + "name": "acc_timestamp", + "title": "记账时间戳", + "type": "timestamp" + }, + { + "name": "acc_dir", + "title": "记账方向", + "type": "str", + "length": 1 + }, + { + "name": "summary", + "title": "摘要", + "type": "str", + "length": 255 + }, + { + "name": "amount", + "title": "记账金额", + "type": "float", + "length": 18 + }, + { + "name": "balance", + "title": "账户余额", + "type": "float", + "length": 18 + }, + { + "name": "acclogid", + "title": "账务流水id", + "type": "str", + "length": 32 + } + ] +} \ No newline at end of file diff --git a/models/account.json b/models/account.json new file mode 100644 index 0000000..3558c4d --- /dev/null +++ b/models/account.json @@ -0,0 +1,99 @@ +{ + "summary": [ + { + "name": "account", + "title": "机构账户表", + "primary": [ + "id" + ] + } + ], + "fields": [ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32 + }, + { + "name": "accounting_orgid", + "title": "账本机构", + "type": "str", + "length": 32 + }, + { + "name": "orgid", + "title": "主机构id", + "type": "str", + "length": 32 + }, + { + "name": "org1id", + "title": "从机构id", + "type": "str", + "length": 32 + }, + { + "name": "subjectid", + "title": "科目号", + "type": "str", + "length": 21 + }, + { + "name": "balance_at", + "title": "余额方向", + "type": "str", + "length": 1 + }, + { + "name": "max_detailno", + "title": "最大明细顺序号", + "type": "short" + }, + { + "name": "balance", + "title": "余额", + "type": "float", + "length": 20 + } + ], + "indexes": [ + { + "name": "idx1", + "idxtype": "unique", + "idxfields": [ + "accounting_orgid", + "orgid", + "subjectid", + "org1id" + ] + } + ], + "codes": [ + { + "field": "subjectid", + "table": "subject", + "valuefield": "id", + "textfield": "name" + }, + { + "field": "orgid", + "table": "organization", + "valuefield": "id", + "textfield": "orgname" + }, + { + "field": "balance_at", + "table": "appcodes_kv", + "valuefield": "k", + "textfield": "v", + "cond": "parentid='accounting_dir'" + }, + { + "field": "org1id", + "table": "organization", + "valuefield": "id", + "textfield": "orgname" + } + ] +} \ No newline at end of file diff --git a/models/account_config.json b/models/account_config.json new file mode 100644 index 0000000..3709c3b --- /dev/null +++ b/models/account_config.json @@ -0,0 +1,61 @@ +{ + "summary": [ + { + "name": "account_config", + "title": "账户配置表", + "primary": [ + "id" + ] + } + ], + "fields": [ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32 + }, + { + "name": "subjectid", + "title": "科目id", + "type": "str", + "length": 32 + }, + { + "name": "partytype", + "title": "主参与方类型", + "type": "str", + "length": 255, + "nullable": "no" + }, + { + "name": "party1type", + "title": "从参与方类型", + "type": "str", + "length": 255, + "nullable": "yes" + } + ], + "codes": [ + { + "field": "party1type", + "table": "appcodes_kv", + "valuefield": "k", + "textfield": "v", + "cond": "parentid='partytype'" + }, + { + "field": "subjectid", + "table": "subject", + "valuefield": "id", + "textfield": "name" + }, + { + "field": "partytype", + "table": "appcodes_kv", + "valuefield": "k", + "textfield": "v", + "cond": "parentid='partytype'" + } + ] +} \ No newline at end of file diff --git a/models/accounting_config.json b/models/accounting_config.json new file mode 100644 index 0000000..35de6f4 --- /dev/null +++ b/models/accounting_config.json @@ -0,0 +1,98 @@ +{ + "summary": [ + { + "name": "accounting_config", + "title": "记账配置表", + "primary": [ + "id" + ] + } + ], + "fields": [ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32 + }, + { + "name": "action", + "title": "交易", + "type": "str", + "length": 255 + }, + { + "name": "accounting_orgtype", + "title": "账务机构", + "type": "str", + "length": 256 + }, + { + "name": "accounting_dir", + "title": "记账方向", + "type": "str", + "length": 255 + }, + { + "name": "orgtype", + "title": "机构类型", + "type": "str", + "length": 32 + }, + { + "name": "org1type", + "title": "从机构类型", + "type": "str", + "length": 32, + "nullable": "yes" + }, + { + "name": "subjectid", + "title": "科目id", + "type": "str", + "length": 21 + }, + { + "name": "amt_pattern", + "title": "金额模板", + "type": "str", + "length": 255 + } + ], + "codes": [ + { + "field": "accounting_orgtype", + "table": "appcodes_kv", + "valuefield": "k", + "textfield": "v", + "cond": "parentid='partytype'" + }, + { + "field": "accounting_dir", + "table": "appcodes_kv", + "valuefield": "k", + "textfield": "v", + "cond": "parentid='accounting_dir'" + }, + { + "field": "orgtype", + "table": "appcodes_kv", + "valuefield": "k", + "textfield": "v", + "cond": "parentid='partytype'" + }, + { + "field": "org1type", + "table": "appcodes_kv", + "valuefield": "k", + "textfield": "v", + "cond": "parentid='partytype'" + }, + { + "field": "subjectid", + "table": "subject", + "valuefield": "id", + "textfield": "name" + } + ] +} \ No newline at end of file diff --git a/models/accounting_log.json b/models/accounting_log.json new file mode 100644 index 0000000..9a5a4ff --- /dev/null +++ b/models/accounting_log.json @@ -0,0 +1,68 @@ +{ + "summary": [ + { + "name": "accounting_log", + "title": "账务流水表", + "primary": [ + "id" + ] + } + ], + "fields": [ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32 + }, + { + "name": "accountid", + "title": "账户id", + "type": "str", + "length": 32 + }, + { + "name": "acc_date", + "title": "记账日期", + "type": "date" + }, + { + "name": "acc_timestamp", + "title": "记账时间戳", + "type": "timestamp" + }, + { + "name": "acc_dir", + "title": "记账方向", + "type": "str", + "length": 1 + }, + { + "name": "summary", + "title": "摘要", + "type": "str", + "length": 255 + }, + { + "name": "amount", + "title": "记账金额", + "type": "float", + "length": 18 + }, + { + "name": "billid", + "title": "账单id", + "type": "str", + "length": 32 + } + ], + "codes": [ + { + "field": "acc_dir", + "table": "appcodes_kv", + "valuefield": "k", + "textfield": "v", + "cond": "parentid='accounting_dir'" + } + ] +} \ No newline at end of file diff --git a/models/bill.json b/models/bill.json new file mode 100644 index 0000000..548cc50 --- /dev/null +++ b/models/bill.json @@ -0,0 +1,77 @@ +{ + "summary": [ + { + "name": "bill", + "title": "账单", + "primary": [ + "id" + ] + } + ], + "fields": [ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32 + }, + { + "name": "customerid", + "title": "客户编号", + "type": "str", + "length": 32 + }, + { + "name": "resellerid", + "title": "商户id", + "type": "str", + "length": 32 + }, + { + "name": "productid", + "title": "产品id", + "type": "str", + "length": 32 + }, + { + "name": "resourceid", + "title": "资源id", + "type": "str", + "length": 32 + }, + { + "name": "orderid", + "title": "订单编号", + "type": "str", + "length": 32 + }, + { + "name": "business_op", + "title": "业务操作", + "type": "str", + "length": 255 + }, + { + "name": "amount", + "title": "金额", + "type": "float", + "length": 18 + }, + { + "name": "bill_date", + "title": "账单日期", + "type": "date" + }, + { + "name": "bill_timestamp", + "title": "账单时间戳", + "type": "timestamp" + }, + { + "name": "bill_state", + "title": "账单状态", + "type": "str", + "length": 1 + } + ] +} \ No newline at end of file diff --git a/models/bill_detail.json b/models/bill_detail.json new file mode 100644 index 0000000..019dcda --- /dev/null +++ b/models/bill_detail.json @@ -0,0 +1,67 @@ +{ + "summary": [ + { + "name": "bill_detail", + "title": "账单明细", + "primary": [ + "id" + ] + } + ], + "fields": [ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32 + }, + { + "name": "accounting_orgid", + "title": "账务机构id", + "type": "str", + "length": 32 + }, + { + "name": "billid", + "title": "账单ID", + "type": "str", + "length": 32 + }, + { + "name": "description", + "title": "账务说明", + "type": "str", + "length": 255 + }, + { + "name": "participantid", + "title": "记账方id", + "type": "str", + "length": 32 + }, + { + "name": "participanttype", + "title": "记账方类型", + "type": "str", + "length": 255 + }, + { + "name": "subjectname", + "title": "科目名称", + "type": "str", + "length": 255 + }, + { + "name": "accounting_dir", + "title": "记账方向", + "type": "str", + "length": 255 + }, + { + "name": "amount", + "title": "账单金额", + "type": "float", + "length": 18 + } + ] +} \ No newline at end of file diff --git a/models/ledger.json b/models/ledger.json new file mode 100644 index 0000000..801f74a --- /dev/null +++ b/models/ledger.json @@ -0,0 +1,48 @@ +{ + "summary": [ + { + "name": "ledger", + "title": "总账表", + "primary": [ + "id" + ] + } + ], + "fields": [ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32 + }, + { + "name": "accounting_orgid", + "title": "账本机构", + "type": "str", + "length": 32 + }, + { + "name": "subjectid", + "title": "科目号", + "type": "str", + "length": 32 + }, + { + "name": "acc_date", + "title": "账务日期", + "type": "date" + }, + { + "name": "d_balance", + "title": "借方余额", + "type": "float", + "length": 18 + }, + { + "name": "c_balance", + "title": "贷方余额", + "type": "float", + "length": 18 + } + ] +} \ No newline at end of file diff --git a/models/subject.json b/models/subject.json new file mode 100644 index 0000000..f660d9c --- /dev/null +++ b/models/subject.json @@ -0,0 +1,53 @@ +{ + "summary": [ + { + "name": "subject", + "title": "科目表", + "primary": [ + "id" + ] + } + ], + "fields": [ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32 + }, + { + "name": "name", + "title": "科目名称", + "type": "str", + "length": 255 + }, + { + "name": "balance_side", + "title": "余额方向", + "type": "str", + "length": 1 + }, + { + "name": "subjecttype", + "title": "科目类别", + "type": "str", + "length": 30 + } + ], + "codes": [ + { + "field": "balance_side", + "table": "appcodes_kv", + "valuefield": "k", + "textfield": "v", + "cond": "parentid='balance_side'" + }, + { + "field": "subjecttype", + "table": "appcodes_kv", + "valuefield": "k", + "textfield": "v", + "cond": "parentid='subjecttype'" + } + ] +} \ No newline at end of file From 865d9b136e58c6f565ef211e8e37d92bce5d80c7 Mon Sep 17 00:00:00 2001 From: yumoqing Date: Mon, 25 May 2026 18:32:04 +0800 Subject: [PATCH 3/9] feat: modernize accounting UI - add index.ui with card navigation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Create index.ui with standardized card layout for 我的账户 and 账单明细 - Add page header with Title2 + description text - Use consistent SVG icon styling (36px, 1.5 stroke width) - Card style: #1E293B bg, 12px radius, #334155 borders --- wwwroot/index.ui | 157 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 wwwroot/index.ui diff --git a/wwwroot/index.ui b/wwwroot/index.ui new file mode 100644 index 0000000..368ebdf --- /dev/null +++ b/wwwroot/index.ui @@ -0,0 +1,157 @@ +{ + "widgettype": "VBox", + "options": { + "width": "100%", + "height": "100%", + "padding": "0" + }, + "subwidgets": [ + { + "widgettype": "HBox", + "options": { + "width": "100%", + "alignItems": "center", + "marginBottom": "24px" + }, + "subwidgets": [ + { + "widgettype": "Title2", + "options": { + "text": "计费管理", + "color": "#F1F5F9", + "fontWeight": "700" + } + }, + { + "widgettype": "Filler" + }, + { + "widgettype": "Text", + "options": { + "text": "账户管理、账单明细与计费配置", + "fontSize": "14px", + "color": "#64748B" + } + } + ] + }, + { + "widgettype": "ResponsableBox", + "options": { + "gap": "16px", + "minWidth": "250px" + }, + "subwidgets": [ + { + "widgettype": "VBox", + "options": { + "bgcolor": "#1E293B", + "padding": "24px", + "borderRadius": "12px", + "border": "1px solid #334155", + "cursor": "pointer" + }, + "binds": [ + { + "wid": "self", + "event": "click", + "actiontype": "urlwidget", + "target": "app.accounting_content", + "options": { + "url": "{{entire_url('myaccounts')}}" + }, + "mode": "replace" + } + ], + "subwidgets": [ + { + "widgettype": "Svg", + "options": { + "svg": "", + "width": "36px", + "height": "36px", + "marginBottom": "16px" + } + }, + { + "widgettype": "Title4", + "options": { + "text": "我的账户", + "color": "#F1F5F9", + "fontWeight": "600", + "marginBottom": "8px" + } + }, + { + "widgettype": "Text", + "options": { + "text": "查看账户余额与充值记录", + "fontSize": "14px", + "color": "#94A3B8" + } + } + ] + }, + { + "widgettype": "VBox", + "options": { + "bgcolor": "#1E293B", + "padding": "24px", + "borderRadius": "12px", + "border": "1px solid #334155", + "cursor": "pointer" + }, + "binds": [ + { + "wid": "self", + "event": "click", + "actiontype": "urlwidget", + "target": "app.accounting_content", + "options": { + "url": "{{entire_url('accdetail')}}" + }, + "mode": "replace" + } + ], + "subwidgets": [ + { + "widgettype": "Svg", + "options": { + "svg": "", + "width": "36px", + "height": "36px", + "marginBottom": "16px" + } + }, + { + "widgettype": "Title4", + "options": { + "text": "账单明细", + "color": "#F1F5F9", + "fontWeight": "600", + "marginBottom": "8px" + } + }, + { + "widgettype": "Text", + "options": { + "text": "查看计费明细与消费流水", + "fontSize": "14px", + "color": "#94A3B8" + } + } + ] + } + ] + }, + { + "widgettype": "VBox", + "id": "accounting_content", + "options": { + "width": "100%", + "flex": "1", + "marginTop": "20px" + } + } + ] +} From 16163ecdc3c266ec787d6bd4064434d604390a0a Mon Sep 17 00:00:00 2001 From: yumoqing Date: Mon, 25 May 2026 18:40:40 +0800 Subject: [PATCH 4/9] feat: add accounting module stat cards - total balance, daily/monthly consumption, account count - Create stats.py with get_accounting_stats() helper function - Add 4 stat widgets: stat_total_balance, stat_today_consumption, stat_month_consumption, stat_account_count - Update index.ui to display stat cards row above navigation cards - Register get_accounting_stats in load_accounting() --- accounting/init.py | 2 + accounting/stats.py | 77 +++++++++++++++++++++++++++++++ wwwroot/index.ui | 34 ++++++++++++++ wwwroot/stat_account_count.ui | 53 +++++++++++++++++++++ wwwroot/stat_month_consumption.ui | 53 +++++++++++++++++++++ wwwroot/stat_today_consumption.ui | 53 +++++++++++++++++++++ wwwroot/stat_total_balance.ui | 53 +++++++++++++++++++++ 7 files changed, 325 insertions(+) create mode 100644 accounting/stats.py create mode 100644 wwwroot/stat_account_count.ui create mode 100644 wwwroot/stat_month_consumption.ui create mode 100644 wwwroot/stat_today_consumption.ui create mode 100644 wwwroot/stat_total_balance.ui 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" + } + } + ] +} From 5c10c2cb30002dc9e6c277171974a7e97483f839 Mon Sep 17 00:00:00 2001 From: yumoqing Date: Tue, 26 May 2026 11:57:59 +0800 Subject: [PATCH 5/9] fix: add dark mode background (#0B1120) to accounting index.ui (dataviz branch) --- wwwroot/index.ui | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wwwroot/index.ui b/wwwroot/index.ui index 368ebdf..1389f29 100644 --- a/wwwroot/index.ui +++ b/wwwroot/index.ui @@ -3,7 +3,8 @@ "options": { "width": "100%", "height": "100%", - "padding": "0" + "padding": "0", + "bgcolor": "#0B1120" }, "subwidgets": [ { From 5bf21ac024f470a4780c1025c244d01693a431ec Mon Sep 17 00:00:00 2001 From: yumoqing Date: Tue, 26 May 2026 13:57:32 +0800 Subject: [PATCH 6/9] fix: add filler css + overflowY:auto to content container --- wwwroot/index.ui | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wwwroot/index.ui b/wwwroot/index.ui index ec151b2..17a45de 100644 --- a/wwwroot/index.ui +++ b/wwwroot/index.ui @@ -182,10 +182,10 @@ { "widgettype": "VBox", "id": "accounting_content", + "css": "filler", "options": { "width": "100%", - "flex": "1", - "marginTop": "20px" + "overflowY": "auto" } } ] From cb525425673105a971cb38b4e70029123f97fbf2 Mon Sep 17 00:00:00 2001 From: yumoqing Date: Wed, 27 May 2026 13:16:01 +0800 Subject: [PATCH 7/9] feat: add load_path.py RBAC permission registration script --- scripts/load_path.py | 99 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 scripts/load_path.py diff --git a/scripts/load_path.py b/scripts/load_path.py new file mode 100644 index 0000000..fdd29a7 --- /dev/null +++ b/scripts/load_path.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 +""" +accounting 模块 RBAC 权限管理脚本 + +使用方法: + cd ~/repos/sage + ./py3/bin/python ~/accounting/scripts/load_path.py + +每次代码变更如有新 path 出现,需同步更新此脚本。 +""" + +import subprocess +import os +import sys + +def find_sage_root(): + candidates = [ + os.path.expanduser("~/repos/sage"), + os.path.expanduser("~/sage"), + os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))), + ] + for c in candidates: + if os.path.isdir(os.path.join(c, "py3")) and os.path.isdir(os.path.join(c, "wwwroot")): + return c + return None + +SAGE_ROOT = find_sage_root() +if not SAGE_ROOT: + print("ERROR: Cannot find Sage root directory") + sys.exit(1) + +PYTHON = os.path.join(SAGE_ROOT, "py3", "bin", "python") +SET_PERM_SCRIPT = os.path.join(SAGE_ROOT, "set_role_perm.py") + +MOD = "accounting" + +# ============================================================ +# 权限路径定义 — 每次新增页面或API时同步更新 +# ============================================================ + +# any — 无需登录(菜单、登录页等) +PATHS_ANY = [ + f"/accounting/usermenu.ui",] + +# logined — 需要认证的页面和 API +PATHS_LOGINED = [ + f"/accounting", + f"/accounting/acc_balance", + f"/accounting/acc_detail", + f"/accounting/accdetail.dspy", + f"/accounting/accdetail.ui", + f"/accounting/account", + f"/accounting/account_config", + f"/accounting/accounting_config", + f"/accounting/accounting_log", + f"/accounting/get_user_balance.dspy", + f"/accounting/index.ui", + f"/accounting/myaccounts.dspy", + f"/accounting/myaccounts.ui", + f"/accounting/mybalance.dspy", + f"/accounting/oca.dspy", + f"/accounting/open_customer_accounts.dspy", + f"/accounting/open_owner_accounts.dspy", + f"/accounting/open_provider_accounts.dspy", + f"/accounting/open_reseller_accounts.dspy", + f"/accounting/open_reseller_provider_accounts.dspy", + f"/accounting/stat_account_count.ui", + f"/accounting/stat_month_consumption.ui", + f"/accounting/stat_today_consumption.ui", + f"/accounting/stat_total_balance.ui", + f"/accounting/subject",] + +# ============================================================ +# 执行注册 +# ============================================================ + +def run_set_perm(role, path): + cmd = [PYTHON, SET_PERM_SCRIPT, role, path] + result = subprocess.run(cmd, capture_output=True, text=True) + return result.returncode == 0 + +def register_role_paths(role, paths): + count = 0 + for p in paths: + if run_set_perm(role, p): + count += 1 + print(f" {role}: {count}/{len(paths)} paths registered") + return count + +def main(): + print(f"Sage root: {SAGE_ROOT}") + total = 0 + total += register_role_paths("any", PATHS_ANY) + total += register_role_paths("logined", PATHS_LOGINED) + print(f"\nDone. Total {total} permission entries registered.") + print("NOTE: Restart Sage after permission changes to reload RBAC cache.") + +if __name__ == "__main__": + main() From af10e4a8107fdda56078d0ed0ccba41eeaff8d3f Mon Sep 17 00:00:00 2001 From: yumoqing Date: Wed, 27 May 2026 13:23:22 +0800 Subject: [PATCH 8/9] refactor(models): convert to json format per database-table-definition-spec --- models/acc_balance.json | 3 ++- models/acc_detail.json | 6 ++++-- models/account.json | 3 ++- models/accounting_log.json | 3 ++- models/bill.json | 3 ++- models/bill_detail.json | 3 ++- models/ledger.json | 6 ++++-- 7 files changed, 18 insertions(+), 9 deletions(-) diff --git a/models/acc_balance.json b/models/acc_balance.json index 0e75eeb..14d8d0a 100644 --- a/models/acc_balance.json +++ b/models/acc_balance.json @@ -30,7 +30,8 @@ "name": "balance", "title": "账户余额", "type": "float", - "length": 20 + "length": 20, + "dec": 2 } ] } \ No newline at end of file diff --git a/models/acc_detail.json b/models/acc_detail.json index 80a6a23..b0d687b 100644 --- a/models/acc_detail.json +++ b/models/acc_detail.json @@ -52,13 +52,15 @@ "name": "amount", "title": "记账金额", "type": "float", - "length": 18 + "length": 18, + "dec": 2 }, { "name": "balance", "title": "账户余额", "type": "float", - "length": 18 + "length": 18, + "dec": 2 }, { "name": "acclogid", diff --git a/models/account.json b/models/account.json index 3558c4d..859b82a 100644 --- a/models/account.json +++ b/models/account.json @@ -54,7 +54,8 @@ "name": "balance", "title": "余额", "type": "float", - "length": 20 + "length": 20, + "dec": 2 } ], "indexes": [ diff --git a/models/accounting_log.json b/models/accounting_log.json index 9a5a4ff..a7c5fea 100644 --- a/models/accounting_log.json +++ b/models/accounting_log.json @@ -47,7 +47,8 @@ "name": "amount", "title": "记账金额", "type": "float", - "length": 18 + "length": 18, + "dec": 2 }, { "name": "billid", diff --git a/models/bill.json b/models/bill.json index 548cc50..30f57fd 100644 --- a/models/bill.json +++ b/models/bill.json @@ -55,7 +55,8 @@ "name": "amount", "title": "金额", "type": "float", - "length": 18 + "length": 18, + "dec": 2 }, { "name": "bill_date", diff --git a/models/bill_detail.json b/models/bill_detail.json index 019dcda..ea799c0 100644 --- a/models/bill_detail.json +++ b/models/bill_detail.json @@ -61,7 +61,8 @@ "name": "amount", "title": "账单金额", "type": "float", - "length": 18 + "length": 18, + "dec": 2 } ] } \ No newline at end of file diff --git a/models/ledger.json b/models/ledger.json index 801f74a..f3d3b16 100644 --- a/models/ledger.json +++ b/models/ledger.json @@ -36,13 +36,15 @@ "name": "d_balance", "title": "借方余额", "type": "float", - "length": 18 + "length": 18, + "dec": 2 }, { "name": "c_balance", "title": "贷方余额", "type": "float", - "length": 18 + "length": 18, + "dec": 2 } ] } \ No newline at end of file From be97eaf7b5edfa4867c9caa6a5e0ffd1e499faa3 Mon Sep 17 00:00:00 2001 From: yumoqing Date: Thu, 28 May 2026 16:14:57 +0800 Subject: [PATCH 9/9] fix: remove hardcoded dark theme colors from index.ui --- wwwroot/index.ui | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/wwwroot/index.ui b/wwwroot/index.ui index 17a45de..ec82a5b 100644 --- a/wwwroot/index.ui +++ b/wwwroot/index.ui @@ -3,8 +3,8 @@ "options": { "width": "100%", "height": "100%", - "padding": "0", - "bgcolor": "#0B1120" + "padding": "0" + }, "subwidgets": [ { @@ -19,7 +19,6 @@ "widgettype": "Title2", "options": { "text": "计费管理", - "color": "#F1F5F9", "fontWeight": "700" } }, @@ -30,8 +29,8 @@ "widgettype": "Text", "options": { "text": "账户管理、账单明细与计费配置", - "fontSize": "14px", - "color": "#64748B" + "fontSize": "14px" + } } ] @@ -80,10 +79,8 @@ { "widgettype": "VBox", "options": { - "bgcolor": "#1E293B", "padding": "24px", "borderRadius": "12px", - "border": "1px solid #334155", "cursor": "pointer" }, "binds": [ @@ -112,7 +109,6 @@ "widgettype": "Title4", "options": { "text": "我的账户", - "color": "#F1F5F9", "fontWeight": "600", "marginBottom": "8px" } @@ -121,8 +117,8 @@ "widgettype": "Text", "options": { "text": "查看账户余额与充值记录", - "fontSize": "14px", - "color": "#94A3B8" + "fontSize": "14px" + } } ] @@ -130,10 +126,8 @@ { "widgettype": "VBox", "options": { - "bgcolor": "#1E293B", "padding": "24px", "borderRadius": "12px", - "border": "1px solid #334155", "cursor": "pointer" }, "binds": [ @@ -162,7 +156,6 @@ "widgettype": "Title4", "options": { "text": "账单明细", - "color": "#F1F5F9", "fontWeight": "600", "marginBottom": "8px" } @@ -171,8 +164,8 @@ "widgettype": "Text", "options": { "text": "查看计费明细与消费流水", - "fontSize": "14px", - "color": "#94A3B8" + "fontSize": "14px" + } } ]