feat(credit): redesign credit limit UI with dashboard, overview and management views
- Add hub.ui as main entry with stat cards (total/used/available/usage%) - Add credit_overview.ui for user's own credit visualization with progress bars - Add credit_manage.ui for distributor sales to manage customer credits - Add set_credit_form.ui and set_customer_credit.dspy for credit adjustment - Add credit_summary.dspy API for stats data - Enhance creditlimit.py with get_credit_stats, get_my_credit_list, get_all_customer_credits - Register new functions in init.py with ServerEnv
This commit is contained in:
parent
195a7bfb46
commit
78ff190789
@ -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)
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
43
wwwroot/credit_limit/api/credit_summary.dspy
Normal file
43
wwwroot/credit_limit/api/credit_summary.dspy
Normal file
@ -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
|
||||
}
|
||||
})
|
||||
63
wwwroot/credit_limit/api/set_credit_form.ui
Normal file
63
wwwroot/credit_limit/api/set_credit_form.ui
Normal file
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
105
wwwroot/credit_limit/api/set_customer_credit.dspy
Normal file
105
wwwroot/credit_limit/api/set_customer_credit.dspy
Normal file
@ -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"
|
||||
}
|
||||
}
|
||||
276
wwwroot/credit_limit/credit_manage.ui
Normal file
276
wwwroot/credit_limit/credit_manage.ui
Normal file
@ -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 %}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
289
wwwroot/credit_limit/credit_overview.ui
Normal file
289
wwwroot/credit_limit/credit_overview.ui
Normal file
@ -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 %}
|
||||
]
|
||||
}
|
||||
301
wwwroot/credit_limit/hub.ui
Normal file
301
wwwroot/credit_limit/hub.ui
Normal file
@ -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": "<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#3B82F6\" stroke-width=\"2\"><path d=\"M12 2v20M17 5H9.5a3.5 3.5 0 000 7h5a3.5 3.5 0 010 7H6\"/></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": "<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#F59E0B\" stroke-width=\"2\"><path d=\"M21 12a9 9 0 11-18 0 9 9 0 0118 0z\"/><path d=\"M9 12l2 2 4-4\"/></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": "<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#22C55E\" stroke-width=\"2\"><path d=\"M2.25 18.75a60.07 60.07 0 0115.797 2.101c.727.198 1.453-.342 1.453-1.096V18.75M3.75 4.5v.75A.75.75 0 013 6h-.75m0 0v-.375c0-.621.504-1.125 1.125-1.125H20.25c.621 0 1.125.504 1.125 1.125v8.25c0 .621-.504 1.125-1.125 1.125H3.375a.75.75 0 01-.75-.75V4.5\"/></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": "<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#A78BFA\" stroke-width=\"2\"><path d=\"M3 3v18h18\"/><path d=\"M18.7 8l-5.1 5.2-2.8-2.7L7 14.3\"/></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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user