122 lines
6.0 KiB
Plaintext
122 lines
6.0 KiB
Plaintext
# recharge_reverse_list.dspy — 充值错帐处理:充值账单列表(2026-09-08)
|
||
# 归属过滤:reseller 财务角色只看自己名下客户;owner 财务角色看全部。
|
||
# 每条账单一个「冲正」按钮(conform 确认),已冲正的显示标记禁用。
|
||
# 返回 widget 描述(VBox 卡片列表),由 recharge_reverse.ui 的 rev_list 容器加载。
|
||
|
||
dbname = get_module_dbname('accounting')
|
||
uid = await get_user()
|
||
if not uid:
|
||
return {"widgettype": "Text", "options": {"text": "请先登录", "color": "#EF4444"}}
|
||
userorgid = await get_userorgid() or ''
|
||
|
||
db = DBPools()
|
||
async with db.sqlorContext(dbname) as sor:
|
||
# 操作者是否 owner 财务角色(不受归属限制)
|
||
roles_recs = await sor.sqlExe(
|
||
"SELECT r.orgtypeid FROM userrole ur JOIN role r ON ur.roleid=r.id WHERE ur.userid=${u}$",
|
||
{'u': uid})
|
||
await sor.sqlExe("COMMIT", {})
|
||
is_owner = any((getattr(r, 'orgtypeid', '') or '') == 'owner' for r in (roles_recs or []))
|
||
|
||
# 充值账单(RECHARGE + 冲正 RECHARGE_REVERSE 一起查,冲正行用于判「已冲正」)
|
||
if is_owner:
|
||
bills = await sor.sqlExe(
|
||
"SELECT b.id, b.customerid, b.orderid, b.business_op, b.amount, b.bill_date, "
|
||
"COALESCE(o.orgname,'') AS orgname FROM bill b "
|
||
"LEFT JOIN organization o ON o.id=b.customerid "
|
||
"WHERE b.business_op IN ('RECHARGE','RECHARGE_REVERSE') "
|
||
"ORDER BY b.bill_date DESC, b.bill_timestamp DESC LIMIT 100", {})
|
||
else:
|
||
# reseller:只看自己名下客户(organization.parentid 或 discount_customer_bind)
|
||
bills = await sor.sqlExe(
|
||
"SELECT b.id, b.customerid, b.orderid, b.business_op, b.amount, b.bill_date, "
|
||
"COALESCE(o.orgname,'') AS orgname FROM bill b "
|
||
"LEFT JOIN organization o ON o.id=b.customerid "
|
||
"WHERE b.business_op IN ('RECHARGE','RECHARGE_REVERSE') "
|
||
"AND (o.parentid=${r}$ OR EXISTS ("
|
||
" SELECT 1 FROM discount_customer_bind dcb "
|
||
" WHERE dcb.customerid=b.customerid AND dcb.resellerid=${r}$)) "
|
||
"ORDER BY b.bill_date DESC, b.bill_timestamp DESC LIMIT 100", {'r': userorgid})
|
||
await sor.sqlExe("COMMIT", {})
|
||
|
||
# 已冲正集合:冲正单的 orderid='REV:<原bill.id>'
|
||
reversed_ids = set()
|
||
reverses = [b for b in (bills or []) if getattr(b, 'business_op', '') == 'RECHARGE_REVERSE']
|
||
for rv in reverses:
|
||
oid = getattr(rv, 'orderid', '') or ''
|
||
if oid.startswith('REV:'):
|
||
reversed_ids.add(oid[4:])
|
||
|
||
submit_url = entire_url('/accounting/recharge_reverse_submit.dspy')
|
||
list_url = entire_url('/accounting/recharge_reverse_list.dspy')
|
||
|
||
cards = []
|
||
for b in (bills or []):
|
||
bop = getattr(b, 'business_op', '')
|
||
bid = getattr(b, 'id', '')
|
||
if bop == 'RECHARGE_REVERSE':
|
||
# 冲正单本身只展示不再冲正
|
||
cards.append({
|
||
"widgettype": "HBox",
|
||
"options": {"bgcolor": "#F0FDF4", "border": "1px solid #BBF7D0",
|
||
"borderRadius": "8px", "padding": "10px 14px", "gap": "12px",
|
||
"alignItems": "center"},
|
||
"subwidgets": [
|
||
{"widgettype": "Text", "options": {"text": "冲正单", "bgcolor": "#16A34A",
|
||
"color": "#FFFFFF", "borderRadius": "6px",
|
||
"padding": "2px 8px", "cfontsize": 0.85}},
|
||
{"widgettype": "Text", "options": {"text": str(getattr(b, 'orgname', '') or getattr(b, 'customerid', '')), "halign": "left", "css": "filler"}},
|
||
{"widgettype": "Text", "options": {"text": "¥" + str(getattr(b, 'amount', 0)), "halign": "left"}},
|
||
{"widgettype": "Text", "options": {"text": str(getattr(b, 'bill_date', '')), "color": "#94A3B8", "cfontsize": 0.85, "halign": "left"}},
|
||
]})
|
||
continue
|
||
is_rev = bid in reversed_ids
|
||
row = [
|
||
{"widgettype": "Text", "options": {"text": "充值", "bgcolor": "#2563EB" if not is_rev else "#94A3B8",
|
||
"color": "#FFFFFF", "borderRadius": "6px",
|
||
"padding": "2px 8px", "cfontsize": 0.85}},
|
||
{"widgettype": "Text", "options": {"text": str(getattr(b, 'orgname', '') or getattr(b, 'customerid', '')), "halign": "left", "css": "filler"}},
|
||
{"widgettype": "Text", "options": {"text": "¥" + str(getattr(b, 'amount', 0)), "halign": "left"}},
|
||
{"widgettype": "Text", "options": {"text": str(getattr(b, 'bill_date', '')), "color": "#94A3B8", "cfontsize": 0.85, "halign": "left"}},
|
||
]
|
||
if is_rev:
|
||
row.append({"widgettype": "Text", "options": {"text": "已冲正", "color": "#16A34A", "cfontsize": 0.85, "halign": "left"}})
|
||
else:
|
||
# 冲正按钮:conform 确认 → script fetch POST → 成功后刷新列表
|
||
script = (
|
||
"var body=new URLSearchParams();"
|
||
"body.append('bill_id'," + json.dumps(bid) + ");"
|
||
"body.append('reason',(window.prompt&&window.prompt('冲正原因(记入审计)',''))||'');"
|
||
"var resp=await fetch(" + json.dumps(submit_url) + ",{method:'POST',body:body});"
|
||
"var rj=await resp.json();"
|
||
"if(rj.success){"
|
||
" var lc=bricks.getWidgetById('rev_list',bricks.app);"
|
||
" if(lc){lc.clear_widgets();var lr=await fetch(" + json.dumps(list_url) + ");var ld=await lr.json();"
|
||
" var lw=await bricks.widgetBuild(ld,lc);if(lw)lc.add_widget(lw);}"
|
||
" new bricks.Message({title:'冲正成功',message:rj.message||'已冲正'});"
|
||
"}else{new bricks.Message({title:'冲正失败',message:rj.error||'失败'});}")
|
||
row.append({
|
||
"widgettype": "Button",
|
||
"options": {"label": "冲正", "css": "small danger"},
|
||
"binds": [{
|
||
"wid": "self", "event": "click", "actiontype": "script", "target": "self",
|
||
"conform": {"title": "冲正确认",
|
||
"message": "确认冲正该笔充值 ¥" + str(getattr(b, 'amount', 0)) + "?冲正将翻转原记账方向(客户余额相应扣回),操作记入审计且不可自动撤销。",
|
||
"conform": {"label": "确认冲正"},
|
||
"discard": {"label": "取消"}},
|
||
"script": script}]})
|
||
cards.append({
|
||
"widgettype": "HBox",
|
||
"options": {"bgcolor": "#1E293B" if not is_rev else "#26313F",
|
||
"border": "1px solid #334155", "borderRadius": "8px",
|
||
"padding": "10px 14px", "gap": "12px", "alignItems": "center"},
|
||
"subwidgets": row})
|
||
|
||
if not cards:
|
||
cards = [{"widgettype": "Text", "options": {"text": "暂无充值账单", "color": "#94A3B8", "padding": "20px"}}]
|
||
|
||
return {
|
||
"widgettype": "VBox",
|
||
"options": {"width": "100%", "gap": "8px"},
|
||
"subwidgets": cards}
|