diff --git a/accounting/creditlimit.py b/accounting/creditlimit.py index a7c2845..269acf2 100644 --- a/accounting/creditlimit.py +++ b/accounting/creditlimit.py @@ -1,6 +1,103 @@ from appPublic.log import debug, exception +from appPublic.uniqueID import getID from datetime import datetime + +async def get_credit_stats(sor, orgid): + """ + Get credit summary statistics for an organization. + Returns total_credit, total_used, total_available, usage_pct, customer_count. + """ + sql = """ + SELECT + COALESCE(SUM(credit_limit), 0) as total_credit, + COALESCE(SUM(used_credit), 0) as total_used, + COALESCE(SUM(available_credit), 0) as total_available, + COUNT(*) as customer_count, + COUNT(CASE WHEN status = 'active' THEN 1 END) as active_count, + COUNT(CASE WHEN status = 'expired' THEN 1 END) as expired_count + FROM credit_limit + WHERE orgid = ${orgid}$ + """ + recs = await sor.sqlExe(sql, {'orgid': orgid}) + if recs and len(recs) > 0: + r = recs[0] + total_credit = float(r.total_credit or 0) + total_used = float(r.total_used or 0) + total_available = float(r.total_available or 0) + usage_pct = round((total_used / total_credit * 100), 1) if total_credit > 0 else 0 + return { + 'total_credit': total_credit, + 'total_used': total_used, + 'total_available': total_available, + 'usage_pct': usage_pct, + 'customer_count': int(r.customer_count or 0), + 'active_count': int(r.active_count or 0), + 'expired_count': int(r.expired_count or 0) + } + return { + 'total_credit': 0, 'total_used': 0, 'total_available': 0, + 'usage_pct': 0, 'customer_count': 0, 'active_count': 0, 'expired_count': 0 + } + + +async def get_my_credit_list(sor, orgid): + """ + Get all credit limit records for the current user's organization, + with organization name and account info for display. + """ + sql = """ + SELECT + cl.*, + org.orgname as orgname_text, + sub.name as subject_name, + CASE + WHEN cl.credit_limit > 0 THEN ROUND(cl.used_credit / cl.credit_limit * 100, 1) + ELSE 0 + END as usage_pct + FROM credit_limit cl + LEFT JOIN organization org ON cl.orgid = org.id + LEFT JOIN account acc ON cl.accountid = acc.id + LEFT JOIN subject sub ON acc.subjectid = sub.id + WHERE cl.orgid = ${orgid}$ + ORDER BY cl.created_at DESC + """ + recs = await sor.sqlExe(sql, {'orgid': orgid}) + return recs + + +async def get_all_customer_credits(sor, orgid, status_filter=None): + """ + Get all customer credit limits for management view. + For distributor sales to see all their customers' credit status. + """ + where_clause = "WHERE cl.orgid = ${orgid}$" + params = {'orgid': orgid} + if status_filter and status_filter != 'all': + where_clause += " AND cl.status = ${status}$" + params['status'] = status_filter + + sql = f""" + SELECT + cl.*, + org.orgname as orgname_text, + sub.name as subject_name, + acc.balance as account_balance, + CASE + WHEN cl.credit_limit > 0 THEN ROUND(cl.used_credit / cl.credit_limit * 100, 1) + ELSE 0 + END as usage_pct + FROM credit_limit cl + LEFT JOIN organization org ON cl.orgid = org.id + LEFT JOIN account acc ON cl.accountid = acc.id + LEFT JOIN subject sub ON acc.subjectid = sub.id + {where_clause} + ORDER BY cl.updated_at DESC + """ + recs = await sor.sqlExe(sql, params) + return recs + + async def get_credit_limit_for_account(sor, accid): """ Get active credit limit for an account. @@ -52,8 +149,6 @@ async def set_credit_limit(sor, accountid, orgid, credit_limit_amount, Set or update credit limit for an account. If a credit limit already exists, update it; otherwise create new. """ - from appPublic.uniqueID import getID - # Check if credit limit exists existing = await get_credit_limit_for_account(sor, accountid) diff --git a/accounting/init.py b/accounting/init.py index 08b069a..e9ae4b8 100644 --- a/accounting/init.py +++ b/accounting/init.py @@ -10,7 +10,7 @@ from .getaccount import getAccountBalance, getCustomerBalance, getAccountByName, from .stats import get_accounting_stats from .recharge import RechargeBiz, recharge_accounting from .consume import consume_accounting -from .creditlimit import get_credit_limit_for_account, update_used_credit, set_credit_limit +from .creditlimit import get_credit_limit_for_account, update_used_credit, set_credit_limit, get_credit_stats, get_my_credit_list, get_all_customer_credits async def all_my_accounts(request): env = request._run_ns @@ -77,3 +77,34 @@ def load_accounting(): g.get_credit_limit_for_account = get_credit_limit_for_account g.update_used_credit = update_used_credit g.set_credit_limit = set_credit_limit + g.get_credit_stats = get_credit_stats + g.get_my_credit_list = get_my_credit_list + g.get_all_customer_credits = get_all_customer_credits + g.get_credit_stats_web = get_credit_stats_web + g.get_my_credits_web = get_my_credits_web + g.get_all_credits_web = get_all_credits_web + + +async def get_credit_stats_web(request): + """Web wrapper for get_credit_stats - used in Jinja2 .ui templates""" + env = request._run_ns + userorgid = await env.get_userorgid() + async with get_sor_context(env, 'accounting') as sor: + return await get_credit_stats(sor, userorgid) + + +async def get_my_credits_web(request): + """Web wrapper for get_my_credit_list - used in Jinja2 .ui templates""" + env = request._run_ns + userorgid = await env.get_userorgid() + async with get_sor_context(env, 'accounting') as sor: + return await get_my_credit_list(sor, userorgid) + + +async def get_all_credits_web(request): + """Web wrapper for get_all_customer_credits - used in Jinja2 .ui templates""" + env = request._run_ns + userorgid = await env.get_userorgid() + status_filter = getattr(request, '_params_kw', {}).get('status', None) if hasattr(request, '_params_kw') else None + async with get_sor_context(env, 'accounting') as sor: + return await get_all_customer_credits(sor, userorgid, status_filter) diff --git a/wwwroot/credit_limit/api/credit_summary.dspy b/wwwroot/credit_limit/api/credit_summary.dspy new file mode 100644 index 0000000..5c859a7 --- /dev/null +++ b/wwwroot/credit_limit/api/credit_summary.dspy @@ -0,0 +1,43 @@ + +orgid = await get_userorgid() + +db = DBPools() +dbname = get_module_dbname('accounting') +async with db.sqlorContext(dbname) as sor: + sql = """ + SELECT + COALESCE(SUM(credit_limit), 0) as total_credit, + COALESCE(SUM(used_credit), 0) as total_used, + COALESCE(SUM(available_credit), 0) as total_available, + COUNT(*) as customer_count, + COUNT(CASE WHEN status = 'active' THEN 1 END) as active_count, + COUNT(CASE WHEN status = 'expired' THEN 1 END) as expired_count + FROM credit_limit + WHERE orgid = ${orgid}$ + """ + recs = await sor.sqlExe(sql, {'orgid': orgid}) + if recs and len(recs) > 0: + r = recs[0] + total_credit = float(r.total_credit or 0) + total_used = float(r.total_used or 0) + total_available = float(r.total_available or 0) + usage_pct = round((total_used / total_credit * 100), 1) if total_credit > 0 else 0 + return json.dumps({ + "status": "ok", + "data": { + "total_credit": total_credit, + "total_used": total_used, + "total_available": total_available, + "usage_pct": usage_pct, + "customer_count": int(r.customer_count or 0), + "active_count": int(r.active_count or 0), + "expired_count": int(r.expired_count or 0) + } + }) + return json.dumps({ + "status": "ok", + "data": { + "total_credit": 0, "total_used": 0, "total_available": 0, + "usage_pct": 0, "customer_count": 0, "active_count": 0, "expired_count": 0 + } + }) diff --git a/wwwroot/credit_limit/api/set_credit_form.ui b/wwwroot/credit_limit/api/set_credit_form.ui new file mode 100644 index 0000000..4d2d739 --- /dev/null +++ b/wwwroot/credit_limit/api/set_credit_form.ui @@ -0,0 +1,63 @@ +{ + "widgettype": "Form", + "options": { + "width": "100%", + "padding": "16px", + "url": "{{entire_url('/accounting/credit_limit/api/set_customer_credit.dspy')}}", + "method": "POST", + "fields": [ + { + "name": "id", + "label": "id", + "uitype": "hidden", + "value": "{{params_kw.get('id', '')}}" + }, + { + "name": "accountid", + "label": "账户ID", + "uitype": "str", + "required": true, + "value": "{{params_kw.get('accountid', '')}}" + }, + { + "name": "credit_limit", + "label": "授信额度", + "uitype": "float", + "required": true, + "value": "{{params_kw.get('credit_limit', '0')}}" + }, + { + "name": "valid_from", + "label": "生效日期", + "uitype": "date", + "value": "{{params_kw.get('valid_from', '')}}" + }, + { + "name": "valid_to", + "label": "失效日期", + "uitype": "date", + "value": "{{params_kw.get('valid_to', '')}}" + }, + { + "name": "remark", + "label": "备注", + "uitype": "str", + "value": "{{params_kw.get('remark', '')}}" + } + ], + "buttons": [ + { + "label": "保存", + "actiontype": "submit", + "bgcolor": "#3B82F6", + "color": "#FFFFFF" + }, + { + "label": "取消", + "actiontype": "close", + "bgcolor": "#475569", + "color": "#FFFFFF" + } + ] + } +} diff --git a/wwwroot/credit_limit/api/set_customer_credit.dspy b/wwwroot/credit_limit/api/set_customer_credit.dspy new file mode 100644 index 0000000..7c295ad --- /dev/null +++ b/wwwroot/credit_limit/api/set_customer_credit.dspy @@ -0,0 +1,105 @@ + +ns = params_kw.copy() +for k, v in ns.items(): + if v == 'NaN' or v == 'null' or v == '': + ns[k] = None + +accountid = ns.get('accountid') +if not accountid: + return { + "widgettype": "Error", + "options": { + "title": "参数错误", + "cwidth": 16, + "cheight": 9, + "timeout": 3, + "message": "账户ID不能为空" + } + } + +credit_limit_amount = float(ns.get('credit_limit', 0) or 0) +if credit_limit_amount <= 0: + return { + "widgettype": "Error", + "options": { + "title": "参数错误", + "cwidth": 16, + "cheight": 9, + "timeout": 3, + "message": "授信额度必须大于0" + } + } + +valid_from = ns.get('valid_from') +valid_to = ns.get('valid_to') +remark = ns.get('remark') +record_id = ns.get('id') + +user_id = await get_user() +orgid = await get_userorgid() + +db = DBPools() +dbname = get_module_dbname('accounting') +async with db.sqlorContext(dbname) as sor: + if record_id: + sql = """ + UPDATE credit_limit + SET credit_limit = ${credit_limit}$, + available_credit = ${credit_limit}$ - used_credit, + valid_from = ${valid_from}$, + valid_to = ${valid_to}$, + remark = ${remark}$, + updated_at = CURRENT_TIMESTAMP + WHERE id = ${id}$ AND orgid = ${orgid}$ + """ + await sor.sqlExe(sql, { + 'credit_limit': credit_limit_amount, + 'valid_from': valid_from, + 'valid_to': valid_to, + 'remark': remark, + 'id': record_id, + 'orgid': orgid + }) + debug(f'Updated credit limit {record_id} to {credit_limit_amount}') + else: + new_id = uuid() + now = datetime.now() + data = { + 'id': new_id, + 'accountid': accountid, + 'orgid': orgid, + 'credit_limit': credit_limit_amount, + 'used_credit': 0, + 'available_credit': credit_limit_amount, + 'valid_from': valid_from, + 'valid_to': valid_to, + 'status': 'active', + 'created_at': now, + 'updated_at': now, + 'created_by': user_id, + 'remark': remark + } + await sor.C('credit_limit', data) + debug(f'Created credit limit for {accountid}: {credit_limit_amount}') + + return { + "widgettype": "Message", + "options": { + "cwidth": 16, + "cheight": 9, + "title": "授信额度设置成功", + "timeout": 3, + "message": "ok" + } + } + +return { + "widgettype": "Error", + "options": { + "title": "设置失败", + "cwidth": 16, + "cheight": 9, + "timeout": 3, + "message": "failed" + } +} diff --git a/wwwroot/credit_limit/credit_manage.ui b/wwwroot/credit_limit/credit_manage.ui new file mode 100644 index 0000000..aaaabde --- /dev/null +++ b/wwwroot/credit_limit/credit_manage.ui @@ -0,0 +1,276 @@ +{% set credits = get_all_credits_web(request) %} +{ + "widgettype": "VBox", + "options": { + "width": "100%", + "gap": "12px", + "padding": "4px" + }, + "subwidgets": [ + { + "widgettype": "HBox", + "options": { + "alignItems": "center", + "justifyContent": "space-between" + }, + "subwidgets": [ + { + "widgettype": "Text", + "options": { + "text": "客户额度管理 (共{{credits|length}}条)", + "fontSize": "16px", + "fontWeight": "600", + "color": "#F1F5F9" + } + }, + { + "widgettype": "Button", + "options": { + "label": "新增授信", + "bgcolor": "#3B82F6", + "color": "#FFFFFF", + "borderRadius": "6px", + "padding": "6px 14px" + }, + "binds": [{ + "wid": "self", + "event": "click", + "actiontype": "urlwidget", + "target": "PopupWindow", + "popup_options": { + "title": "新增客户授信", + "width": "480px", + "height": "520px" + }, + "options": { + "url": "{{entire_url('/accounting/credit_limit/api/set_credit_form.ui')}}" + } + }] + } + ] + }, + { + "widgettype": "VBox", + "options": { + "bgcolor": "#1E293B", + "borderRadius": "10px", + "border": "1px solid #334155", + "width": "100%" + }, + "subwidgets": [ + { + "widgettype": "HBox", + "options": { + "bgcolor": "#0F172A", + "padding": "10px 16px", + "borderRadius": "10px 10px 0 0", + "alignItems": "center" + }, + "subwidgets": [ + { + "widgettype": "Text", + "options": {"text": "客户名称", "fontSize": "12px", "fontWeight": "600", "color": "#94A3B8", "cwidth": 10} + }, + { + "widgettype": "Text", + "options": {"text": "授信额度", "fontSize": "12px", "fontWeight": "600", "color": "#94A3B8", "cwidth": 8} + }, + { + "widgettype": "Text", + "options": {"text": "已用/剩余", "fontSize": "12px", "fontWeight": "600", "color": "#94A3B8", "cwidth": 10} + }, + { + "widgettype": "Text", + "options": {"text": "使用率", "fontSize": "12px", "fontWeight": "600", "color": "#94A3B8", "cwidth": 6} + }, + { + "widgettype": "Text", + "options": {"text": "状态", "fontSize": "12px", "fontWeight": "600", "color": "#94A3B8", "cwidth": 4} + }, + { + "widgettype": "Text", + "options": {"text": "操作", "fontSize": "12px", "fontWeight": "600", "color": "#94A3B8", "cwidth": 6} + } + ] + }, +{% if credits|length == 0 %} + { + "widgettype": "VBox", + "options": { + "padding": "30px", + "alignItems": "center" + }, + "subwidgets": [ + { + "widgettype": "Text", + "options": { + "text": "暂无客户授信记录", + "fontSize": "14px", + "color": "#64748B" + } + } + ] + } +{% else %} +{% for c in credits %} + { + "widgettype": "HBox", + "options": { + "padding": "12px 16px", + "alignItems": "center", + "border": "0 0 1px 0", + "borderColor": "#334155" + }, + "subwidgets": [ + { + "widgettype": "VBox", + "options": {"cwidth": 10, "gap": "2px"}, + "subwidgets": [ + { + "widgettype": "Text", + "options": { + "text": "{{c.orgname_text or c.accountid}}", + "fontSize": "13px", + "fontWeight": "500", + "color": "#F1F5F9" + } + }, + { + "widgettype": "Text", + "options": { + "text": "{{c.subject_name or ''}}", + "fontSize": "11px", + "color": "#64748B" + } + } + ] + }, + { + "widgettype": "Text", + "options": { + "text": "¥{{'%.2f' % c.credit_limit}}", + "fontSize": "13px", + "fontWeight": "600", + "color": "#3B82F6", + "cwidth": 8 + } + }, + { + "widgettype": "VBox", + "options": {"cwidth": 10, "gap": "2px"}, + "subwidgets": [ + { + "widgettype": "Text", + "options": { + "text": "已用 ¥{{'%.2f' % c.used_credit}}", + "fontSize": "12px", + "color": "#F59E0B" + } + }, + { + "widgettype": "Text", + "options": { + "text": "剩余 ¥{{'%.2f' % c.available_credit}}", + "fontSize": "12px", + "color": "#22C55E" + } + } + ] + }, + { + "widgettype": "HBox", + "options": {"cwidth": 6, "alignItems": "center", "gap": "4px"}, + "subwidgets": [ + { + "widgettype": "HBox", + "options": { + "bgcolor": "#334155", + "borderRadius": "3px", + "height": "6px", + "width": "50px" + }, + "subwidgets": [ + { + "widgettype": "HBox", + "options": { + "bgcolor": "{{'#22C55E' if c.usage_pct < 60 else ('#F59E0B' if c.usage_pct < 85 else '#EF4444')}}", + "borderRadius": "3px", + "height": "6px", + "width": "{{c.usage_pct}}%" + }, + "subwidgets": [] + } + ] + }, + { + "widgettype": "Text", + "options": { + "text": "{{c.usage_pct}}%", + "fontSize": "11px", + "color": "{{'#22C55E' if c.usage_pct < 60 else ('#F59E0B' if c.usage_pct < 85 else '#EF4444')}}" + } + } + ] + }, + { + "widgettype": "VBox", + "options": {"cwidth": 4}, + "subwidgets": [ + { + "widgettype": "Text", + "options": { + "text": "{{'生效' if c.status == 'active' else ('停用' if c.status == 'inactive' else '过期')}}", + "fontSize": "11px", + "fontWeight": "600", + "color": "{{'#22C55E' if c.status == 'active' else '#EF4444'}}" + } + } + ] + }, + { + "widgettype": "HBox", + "options": {"cwidth": 6, "gap": "4px"}, + "subwidgets": [ + { + "widgettype": "Button", + "options": { + "label": "调整", + "bgcolor": "#475569", + "color": "#FFFFFF", + "borderRadius": "4px", + "padding": "4px 8px", + "fontSize": "11px" + }, + "binds": [{ + "wid": "self", + "event": "click", + "actiontype": "urlwidget", + "target": "PopupWindow", + "popup_options": { + "title": "调整授信额度", + "width": "480px", + "height": "520px" + }, + "options": { + "url": "{{entire_url('/accounting/credit_limit/api/set_credit_form.ui')}}", + "params_kw": { + "id": "{{c.id}}", + "accountid": "{{c.accountid}}", + "credit_limit": "{{c.credit_limit}}", + "valid_from": "{{c.valid_from or ''}}", + "valid_to": "{{c.valid_to or ''}}", + "remark": "{{c.remark or ''}}" + } + } + }] + } + ] + } + ] + }{% if not loop.last %},{% endif %} +{% endfor %} +{% endif %} + ] + } + ] +} diff --git a/wwwroot/credit_limit/credit_overview.ui b/wwwroot/credit_limit/credit_overview.ui new file mode 100644 index 0000000..b6cfd30 --- /dev/null +++ b/wwwroot/credit_limit/credit_overview.ui @@ -0,0 +1,289 @@ +{% set credits = get_my_credits_web(request) %} +{ + "widgettype": "VBox", + "options": { + "width": "100%", + "gap": "12px", + "padding": "4px" + }, + "subwidgets": [ +{% if credits|length == 0 %} + { + "widgettype": "VBox", + "options": { + "bgcolor": "#1E293B", + "padding": "40px", + "borderRadius": "10px", + "border": "1px solid #334155", + "alignItems": "center" + }, + "subwidgets": [ + { + "widgettype": "Text", + "options": { + "text": "暂无信用额度记录", + "fontSize": "16px", + "color": "#94A3B8" + } + }, + { + "widgettype": "Text", + "options": { + "text": "请联系您的分销商销售人员为您设置信用额度", + "fontSize": "13px", + "color": "#64748B", + "marginTop": "8px" + } + } + ] + } +{% else %} +{% for c in credits %} + { + "widgettype": "VBox", + "options": { + "bgcolor": "#1E293B", + "padding": "16px", + "borderRadius": "10px", + "border": "1px solid #334155", + "gap": "12px" + }, + "subwidgets": [ + { + "widgettype": "HBox", + "options": { + "alignItems": "center", + "justifyContent": "space-between" + }, + "subwidgets": [ + { + "widgettype": "VBox", + "options": {"gap": "2px"}, + "subwidgets": [ + { + "widgettype": "Text", + "options": { + "text": "{{c.orgname_text or '未知客户'}}", + "fontSize": "16px", + "fontWeight": "600", + "color": "#F1F5F9" + } + }, + { + "widgettype": "Text", + "options": { + "text": "{{c.subject_name or ''}} | 账户: {{c.accountid}}", + "fontSize": "12px", + "color": "#64748B" + } + } + ] + }, + { + "widgettype": "VBox", + "options": { + "bgcolor": "{{'#16A34A22' if c.status == 'active' else '#EF444422'}}", + "padding": "4px 12px", + "borderRadius": "12px" + }, + "subwidgets": [ + { + "widgettype": "Text", + "options": { + "text": "{{'生效' if c.status == 'active' else ('停用' if c.status == 'inactive' else '已过期')}}", + "fontSize": "12px", + "fontWeight": "600", + "color": "{{'#22C55E' if c.status == 'active' else '#EF4444'}}" + } + } + ] + } + ] + }, + { + "widgettype": "HBox", + "options": { + "gap": "12px", + "alignItems": "center" + }, + "subwidgets": [ + { + "widgettype": "VBox", + "options": {"flex": "1", "gap": "4px"}, + "subwidgets": [ + { + "widgettype": "HBox", + "options": {"justifyContent": "space-between"}, + "subwidgets": [ + { + "widgettype": "Text", + "options": { + "text": "额度使用", + "fontSize": "12px", + "color": "#94A3B8" + } + }, + { + "widgettype": "Text", + "options": { + "text": "{{c.usage_pct}}%", + "fontSize": "12px", + "fontWeight": "600", + "color": "{{'#22C55E' if c.usage_pct < 60 else ('#F59E0B' if c.usage_pct < 85 else '#EF4444')}}" + } + } + ] + }, + { + "widgettype": "HBox", + "options": { + "bgcolor": "#334155", + "borderRadius": "4px", + "height": "8px", + "width": "100%" + }, + "subwidgets": [ + { + "widgettype": "HBox", + "options": { + "bgcolor": "{{'#22C55E' if c.usage_pct < 60 else ('#F59E0B' if c.usage_pct < 85 else '#EF4444')}}", + "borderRadius": "4px", + "height": "8px", + "width": "{{c.usage_pct}}%" + }, + "subwidgets": [] + } + ] + } + ] + } + ] + }, + { + "widgettype": "HBox", + "options": { + "gap": "8px" + }, + "subwidgets": [ + { + "widgettype": "VBox", + "options": { + "flex": "1", + "bgcolor": "#0F172A", + "padding": "10px", + "borderRadius": "6px", + "alignItems": "center" + }, + "subwidgets": [ + { + "widgettype": "Text", + "options": { + "text": "¥{{'%.2f' % c.credit_limit}}", + "fontSize": "16px", + "fontWeight": "700", + "color": "#3B82F6" + } + }, + { + "widgettype": "Text", + "options": { + "text": "授信额度", + "fontSize": "11px", + "color": "#64748B", + "marginTop": "2px" + } + } + ] + }, + { + "widgettype": "VBox", + "options": { + "flex": "1", + "bgcolor": "#0F172A", + "padding": "10px", + "borderRadius": "6px", + "alignItems": "center" + }, + "subwidgets": [ + { + "widgettype": "Text", + "options": { + "text": "¥{{'%.2f' % c.used_credit}}", + "fontSize": "16px", + "fontWeight": "700", + "color": "#F59E0B" + } + }, + { + "widgettype": "Text", + "options": { + "text": "已用额度", + "fontSize": "11px", + "color": "#64748B", + "marginTop": "2px" + } + } + ] + }, + { + "widgettype": "VBox", + "options": { + "flex": "1", + "bgcolor": "#0F172A", + "padding": "10px", + "borderRadius": "6px", + "alignItems": "center" + }, + "subwidgets": [ + { + "widgettype": "Text", + "options": { + "text": "¥{{'%.2f' % c.available_credit}}", + "fontSize": "16px", + "fontWeight": "700", + "color": "#22C55E" + } + }, + { + "widgettype": "Text", + "options": { + "text": "剩余额度", + "fontSize": "11px", + "color": "#64748B", + "marginTop": "2px" + } + } + ] + } + ] + }, + { + "widgettype": "HBox", + "options": { + "justifyContent": "space-between" + }, + "subwidgets": [ + { + "widgettype": "Text", + "options": { + "text": "有效期: {{c.valid_from or '不限'}} ~ {{c.valid_to or '不限'}}", + "fontSize": "11px", + "color": "#64748B" + } + }, + { + "widgettype": "Text", + "options": { + "text": "更新于 {{c.updated_at}}", + "fontSize": "11px", + "color": "#475569" + } + } + ] + } + ] + }{% if not loop.last %},{% endif %} +{% endfor %} +{% endif %} + ] +} diff --git a/wwwroot/credit_limit/hub.ui b/wwwroot/credit_limit/hub.ui new file mode 100644 index 0000000..e1af8d7 --- /dev/null +++ b/wwwroot/credit_limit/hub.ui @@ -0,0 +1,301 @@ +{% set cstats = get_credit_stats_web(request) %} +{ + "widgettype": "VBox", + "options": { + "width": "100%", + "height": "100%", + "padding": "16px", + "gap": "16px" + }, + "subwidgets": [ + { + "widgettype": "Text", + "options": { + "text": "信用额度管理", + "fontSize": "22px", + "fontWeight": "700", + "color": "#F1F5F9" + } + }, + { + "widgettype": "ResponsableBox", + "options": { + "gap": "12px", + "minWidth": "200px" + }, + "subwidgets": [ + { + "widgettype": "VBox", + "options": { + "bgcolor": "#1E293B", + "padding": "16px", + "borderRadius": "10px", + "border": "1px solid #334155", + "flex": "1", + "minHeight": "100px" + }, + "subwidgets": [ + { + "widgettype": "HBox", + "options": { + "alignItems": "center", + "marginBottom": "8px" + }, + "subwidgets": [ + { + "widgettype": "Svg", + "options": { + "svg": "", + "width": "20px", + "height": "20px" + } + }, + {"widgettype": "Filler"} + ] + }, + { + "widgettype": "Text", + "options": { + "text": "¥{{'%.2f' % cstats.total_credit}}", + "fontSize": "26px", + "fontWeight": "700", + "color": "#3B82F6", + "lineHeight": "1.2" + } + }, + { + "widgettype": "Text", + "options": { + "text": "授信总额度", + "fontSize": "13px", + "color": "#94A3B8", + "marginTop": "4px" + } + } + ] + }, + { + "widgettype": "VBox", + "options": { + "bgcolor": "#1E293B", + "padding": "16px", + "borderRadius": "10px", + "border": "1px solid #334155", + "flex": "1", + "minHeight": "100px" + }, + "subwidgets": [ + { + "widgettype": "HBox", + "options": { + "alignItems": "center", + "marginBottom": "8px" + }, + "subwidgets": [ + { + "widgettype": "Svg", + "options": { + "svg": "", + "width": "20px", + "height": "20px" + } + }, + {"widgettype": "Filler"} + ] + }, + { + "widgettype": "Text", + "options": { + "text": "¥{{'%.2f' % cstats.total_used}}", + "fontSize": "26px", + "fontWeight": "700", + "color": "#F59E0B", + "lineHeight": "1.2" + } + }, + { + "widgettype": "Text", + "options": { + "text": "已用额度", + "fontSize": "13px", + "color": "#94A3B8", + "marginTop": "4px" + } + } + ] + }, + { + "widgettype": "VBox", + "options": { + "bgcolor": "#1E293B", + "padding": "16px", + "borderRadius": "10px", + "border": "1px solid #334155", + "flex": "1", + "minHeight": "100px" + }, + "subwidgets": [ + { + "widgettype": "HBox", + "options": { + "alignItems": "center", + "marginBottom": "8px" + }, + "subwidgets": [ + { + "widgettype": "Svg", + "options": { + "svg": "", + "width": "20px", + "height": "20px" + } + }, + {"widgettype": "Filler"} + ] + }, + { + "widgettype": "Text", + "options": { + "text": "¥{{'%.2f' % cstats.total_available}}", + "fontSize": "26px", + "fontWeight": "700", + "color": "#22C55E", + "lineHeight": "1.2" + } + }, + { + "widgettype": "Text", + "options": { + "text": "剩余额度", + "fontSize": "13px", + "color": "#94A3B8", + "marginTop": "4px" + } + } + ] + }, + { + "widgettype": "VBox", + "options": { + "bgcolor": "#1E293B", + "padding": "16px", + "borderRadius": "10px", + "border": "1px solid #334155", + "flex": "1", + "minHeight": "100px" + }, + "subwidgets": [ + { + "widgettype": "HBox", + "options": { + "alignItems": "center", + "marginBottom": "8px" + }, + "subwidgets": [ + { + "widgettype": "Svg", + "options": { + "svg": "", + "width": "20px", + "height": "20px" + } + }, + {"widgettype": "Filler"} + ] + }, + { + "widgettype": "Text", + "options": { + "text": "{{cstats.usage_pct}}%", + "fontSize": "26px", + "fontWeight": "700", + "color": "#A78BFA", + "lineHeight": "1.2" + } + }, + { + "widgettype": "Text", + "options": { + "text": "额度使用率 ({{cstats.active_count}}/{{cstats.customer_count}}户)", + "fontSize": "13px", + "color": "#94A3B8", + "marginTop": "4px" + } + } + ] + } + ] + }, + { + "widgettype": "HBox", + "options": { + "gap": "8px", + "alignItems": "center" + }, + "subwidgets": [ + { + "widgettype": "Button", + "options": { + "label": "我的额度", + "bgcolor": "#3B82F6", + "color": "#FFFFFF", + "borderRadius": "6px", + "padding": "8px 16px" + }, + "binds": [{ + "wid": "self", + "event": "click", + "actiontype": "urlwidget", + "target": "app.credit_content", + "options": {"url": "{{entire_url('/accounting/credit_limit/credit_overview.ui')}}"}, + "mode": "replace" + }] + }, + { + "widgettype": "Button", + "options": { + "label": "客户额度管理", + "bgcolor": "#475569", + "color": "#FFFFFF", + "borderRadius": "6px", + "padding": "8px 16px" + }, + "binds": [{ + "wid": "self", + "event": "click", + "actiontype": "urlwidget", + "target": "app.credit_content", + "options": {"url": "{{entire_url('/accounting/credit_limit/credit_manage.ui')}}"}, + "mode": "replace" + }] + }, + { + "widgettype": "Button", + "options": { + "label": "全部客户查询", + "bgcolor": "#475569", + "color": "#FFFFFF", + "borderRadius": "6px", + "padding": "8px 16px" + }, + "binds": [{ + "wid": "self", + "event": "click", + "actiontype": "urlwidget", + "target": "app.credit_content", + "options": {"url": "{{entire_url('/accounting/credit_limit/index.ui')}}"}, + "mode": "replace" + }] + } + ] + }, + { + "widgettype": "VBox", + "id": "credit_content", + "options": { + "width": "100%", + "flex": "1" + } + } + ] +}