# recharge_reverse_submit.dspy — 充值错帐冲正提交(2026-09-08) # 参数:bill_id(要冲正的充值账单)、reason(冲正原因,记入审计) # 逻辑:校验归属→防重复冲正→取原单费率→recharge_accounting(RECHARGE_REVERSE) # 引擎自动翻转借贷方向(accounting_config.py endswith('_REVERSE'))→审计留痕 # orderid='REV:' 既防重复冲正,又让列表能识别「已冲正」 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:' 的冲正单则拒绝 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)