accounting/wwwroot/recharge_reverse_submit.dspy

109 lines
4.5 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# recharge_reverse_submit.dspy — 充值错帐冲正提交2026-09-08
# 参数bill_id要冲正的充值账单、reason冲正原因记入审计
# 逻辑校验归属→防重复冲正→取原单费率→recharge_accounting(RECHARGE_REVERSE)
# 引擎自动翻转借贷方向accounting_config.py endswith('_REVERSE'))→审计留痕
# orderid='REV:<bill_id>' 既防重复冲正,又让列表能识别「已冲正」
dbname = get_module_dbname('accounting')
uid = await get_user()
if not uid:
return json.dumps({'success': False, 'error': '未登录'}, ensure_ascii=False)
userorgid = await get_userorgid() or ''
bill_id = (params_kw or {}).get('bill_id', '') or ''
reason = (params_kw or {}).get('reason', '') or ''
if not bill_id:
return json.dumps({'success': False, 'error': '缺少 bill_id'}, ensure_ascii=False)
async def _is_owner_role(sor, uid):
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", {})
return any((getattr(r, 'orgtypeid', '') or '') == 'owner' for r in (recs or []))
async def _customer_belongs(sor, customerid, resellerid):
if not customerid or not resellerid or resellerid == '0':
return False
recs = await sor.sqlExe(
"SELECT id FROM organization WHERE id=${c}$ AND parentid=${r}$",
{'c': customerid, 'r': resellerid})
await sor.sqlExe("COMMIT", {})
if recs:
return True
recs2 = await sor.sqlExe(
"SELECT id FROM discount_customer_bind WHERE customerid=${c}$ AND resellerid=${r}$ LIMIT 1",
{'c': customerid, 'r': resellerid})
await sor.sqlExe("COMMIT", {})
return bool(recs2)
db = DBPools()
async with db.sqlorContext(dbname) as sor:
# 1. 取原充值账单
recs = await sor.sqlExe(
"SELECT id, customerid, orderid, business_op, amount FROM bill WHERE id=${b}$",
{'b': bill_id})
await sor.sqlExe("COMMIT", {})
if not recs:
return json.dumps({'success': False, 'error': '账单不存在'}, ensure_ascii=False)
bill = recs[0]
if getattr(bill, 'business_op', '') != 'RECHARGE':
return json.dumps({'success': False, 'error': '只能冲正充值RECHARGE账单'}, ensure_ascii=False)
customerid = getattr(bill, 'customerid', '') or ''
# 2. 归属校验reseller 财务只能冲正自己名下客户的充值)
is_owner = await _is_owner_role(sor, uid)
if not is_owner and not await _customer_belongs(sor, customerid, userorgid):
return json.dumps({'success': False, 'error': '无权冲正该账单(客户不归属当前组织)'}, ensure_ascii=False)
# 3. 防重复冲正:已存在 orderid='REV:<bill_id>' 的冲正单则拒绝
rev_orderid = 'REV:' + bill_id
exist = await sor.sqlExe(
"SELECT id FROM bill WHERE orderid=${o}$ AND business_op='RECHARGE_REVERSE' LIMIT 1",
{'o': rev_orderid})
await sor.sqlExe("COMMIT", {})
if exist:
return json.dumps({'success': False, 'error': '该充值账单已冲正过,不可重复冲正'}, ensure_ascii=False)
# 4. 取原单充值费率payment_log.id = bill.orderid代客充值/在线充值都写了 payment_log
feerate = 0.0
orig_orderid = getattr(bill, 'orderid', '') or ''
if orig_orderid:
prec = await sor.sqlExe(
"SELECT pay_feerate FROM payment_log WHERE id=${o}$ LIMIT 1", {'o': orig_orderid})
await sor.sqlExe("COMMIT", {})
if prec:
try:
feerate = float(getattr(prec[0], 'pay_feerate', 0) or 0)
except (TypeError, ValueError):
feerate = 0.0
# 5. 冲正记账(引擎按 endswith('_REVERSE') 翻转借贷方向transdate=当前营业日)
amount = float(getattr(bill, 'amount', 0) or 0)
biz_date = await get_business_date(sor)
try:
await recharge_accounting(sor, customerid, 'RECHARGE_REVERSE', rev_orderid,
biz_date, amount, feerate)
await sor.sqlExe("COMMIT", {})
except Exception as e:
return json.dumps({'success': False,
'error': '冲正记账失败:%s' % str(e)[:200]}, ensure_ascii=False)
# 6. 审计留痕(旁路,失败不阻断)
try:
from app_audit import audit_log
op_recs = await sor.sqlExe("SELECT username FROM users WHERE id=${u}$", {'u': uid})
op_name = (op_recs[0].username if op_recs else uid)
await audit_log(sor, uid, op_name, 'recharge_reverse', target=customerid,
detail='充值错帐冲正 bill=%s 金额=%s 原因=%s' % (bill_id, amount, reason or '(未填)'),
result='ok', client_ip='')
await sor.sqlExe("COMMIT", {})
except Exception:
pass
return json.dumps({'success': True,
'message': '冲正成功:¥%.2f(原账单 %s' % (amount, bill_id)},
ensure_ascii=False)