feat: 提现审核按俱乐部隔离并加固打款商户号,新增小溪审核目录复制脚本
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,11 +1,24 @@
|
||||
"""集团级资金处置权限:提现审核、加余额等。俱乐部 000001 超级管理不能绕过。"""
|
||||
"""集团级资金处置权限:加余额等仍仅集团高管。
|
||||
|
||||
提现审核:权限码 005bb + 按俱乐部隔离——
|
||||
- 子公司视图:持有 005bb 且任职含当前俱乐部 → 可审本俱乐部(同意/拒绝/自动打款)
|
||||
- 集团汇总视图 / 跨俱乐部:须集团资金处置权(GROUP_OWNER / GROUP_SUPER_ADMIN)
|
||||
俱乐部本地 000001 不能绕过「跨店」闸门,但可审本店提现。
|
||||
"""
|
||||
from django.db.models import Q
|
||||
|
||||
from jituan.constants import DATA_SCOPE_ALL
|
||||
from jituan.models import AdminAssignment
|
||||
from jituan.services.admin_context import GROUP_MANAGE_ROLES, build_admin_club_context, is_system_super_admin
|
||||
from jituan.services.admin_context import (
|
||||
GROUP_MANAGE_ROLES,
|
||||
build_admin_club_context,
|
||||
get_allowed_club_ids,
|
||||
is_system_super_admin,
|
||||
)
|
||||
|
||||
MSG_GROUP_FINANCE_REQUIRED = '该操作仅集团高管可执行(需在数据范围配置中为集团级任职)'
|
||||
MSG_WITHDRAW_CROSS_CLUB = '跨俱乐部提现审核仅集团高管可执行,请切换到具体俱乐部后再操作'
|
||||
MSG_WITHDRAW_CLUB_DENIED = '无权审核该俱乐部提现'
|
||||
|
||||
|
||||
def has_group_finance_exec(user, request=None):
|
||||
@@ -35,16 +48,32 @@ def deny_group_finance_exec(user, request=None):
|
||||
|
||||
|
||||
def require_withdraw_audit_access(request, phone):
|
||||
"""提现审核:须 005bb 且集团资金处置权。"""
|
||||
"""
|
||||
提现审核访问:
|
||||
1) 须权限码 005bb
|
||||
2) 集团高管:可在 ALL_CLUBS 或任意子公司视图操作
|
||||
3) 俱乐部管理员:仅 SINGLE_CLUB 且当前 club 在其任职范围内
|
||||
"""
|
||||
from backend.utils import verify_kefu_permission
|
||||
from rest_framework.response import Response
|
||||
from jituan.services.club_context import resolve_club_id_from_request, resolve_club_scope
|
||||
|
||||
kefu, permissions = verify_kefu_permission(request, phone)
|
||||
if kefu is None:
|
||||
return None, permissions
|
||||
if '005bb' not in permissions:
|
||||
return None, Response({'code': 403, 'msg': '您无权操作提现审核'})
|
||||
deny = deny_group_finance_exec(request.user, request)
|
||||
if deny:
|
||||
return None, deny
|
||||
|
||||
if has_group_finance_exec(request.user, request):
|
||||
return kefu, None
|
||||
|
||||
scope = resolve_club_scope(request)
|
||||
if scope == DATA_SCOPE_ALL:
|
||||
return None, Response({'code': 403, 'msg': MSG_WITHDRAW_CROSS_CLUB})
|
||||
|
||||
club_id = resolve_club_id_from_request(request)
|
||||
allowed = get_allowed_club_ids(request.user)
|
||||
if not club_id or club_id not in allowed:
|
||||
return None, Response({'code': 403, 'msg': MSG_WITHDRAW_CLUB_DENIED})
|
||||
|
||||
return kefu, None
|
||||
|
||||
@@ -15,6 +15,24 @@ def club_id_for_tixian_yonghuid_safe(yonghuid):
|
||||
return club_id_for_tixian_yonghuid(yonghuid)
|
||||
|
||||
|
||||
def resolve_payout_club_id(*, audit=None, jilu=None, yonghuid=None, user=None):
|
||||
"""
|
||||
打款商户归属俱乐部:优先提现单/审核单上的 club_id,再回落用户归属。
|
||||
避免用户迁店后用新俱乐部商户号打旧单资金。
|
||||
"""
|
||||
for obj in (audit, jilu):
|
||||
if obj is None:
|
||||
continue
|
||||
cid = (getattr(obj, 'club_id', None) or '').strip()
|
||||
if cid:
|
||||
return cid
|
||||
if user is not None:
|
||||
return get_user_club_id(user) or ''
|
||||
if yonghuid:
|
||||
return club_id_for_tixian_yonghuid(yonghuid) or ''
|
||||
return ''
|
||||
|
||||
|
||||
def filter_tixianjilu_by_request(qs, request):
|
||||
return filter_queryset_by_club(qs, request, club_field='club_id')
|
||||
|
||||
@@ -22,6 +40,12 @@ def filter_tixianjilu_by_request(qs, request):
|
||||
def forbid_if_tixian_out_of_scope(request, tixian_record):
|
||||
"""提现记录不在当前俱乐部范围时返回 Response。"""
|
||||
if resolve_club_scope(request) == DATA_SCOPE_ALL:
|
||||
from jituan.services.group_finance_gate import has_group_finance_exec
|
||||
if not has_group_finance_exec(request.user, request):
|
||||
return Response({
|
||||
'code': 403,
|
||||
'msg': '跨俱乐部提现操作仅集团高管可执行,请切换到具体俱乐部',
|
||||
})
|
||||
return None
|
||||
club_id = resolve_club_id_from_request(request)
|
||||
rec_club = getattr(tixian_record, 'club_id', None) or club_id_for_tixian_yonghuid_safe(
|
||||
|
||||
@@ -396,21 +396,27 @@ def get_wechat_v3_config(club_id=None):
|
||||
|
||||
# 共用商户的俱乐部:默认用 settings 私钥;仅当库中路径存在且文件在盘上才覆盖
|
||||
# (避免旧库里曾填过、但原先被忽略的无效路径突然接管提现)
|
||||
private_key_path = path_defaults['PRIVATE_KEY_PATH']
|
||||
if cid not in SHARED_WX_MERCHANT_CLUBS:
|
||||
private_key_path = (
|
||||
_pick_club_field(club, cfg_json, 'private_key_path')
|
||||
or private_key_path
|
||||
# 非共享俱乐部:严禁回落星阙私钥/商户,缺配置直接空值,由上层拒绝打款
|
||||
if cid in SHARED_WX_MERCHANT_CLUBS:
|
||||
private_key_path = path_defaults['PRIVATE_KEY_PATH']
|
||||
if club:
|
||||
uploaded_key = (getattr(club, 'private_key_path', None) or '').strip()
|
||||
if uploaded_key and os.path.isfile(uploaded_key):
|
||||
private_key_path = uploaded_key
|
||||
platform_cert_dir = (
|
||||
_pick_club_field(club, cfg_json, 'platform_cert_dir')
|
||||
or path_defaults['PLATFORM_CERT_DIR']
|
||||
)
|
||||
elif club:
|
||||
uploaded_key = (getattr(club, 'private_key_path', None) or '').strip()
|
||||
if uploaded_key and os.path.isfile(uploaded_key):
|
||||
private_key_path = uploaded_key
|
||||
else:
|
||||
private_key_path = _pick_club_field(club, cfg_json, 'private_key_path')
|
||||
platform_cert_dir = _pick_club_field(club, cfg_json, 'platform_cert_dir')
|
||||
if not mch_id or not api_v3_key or not private_key_path:
|
||||
logger.error(
|
||||
'[WX_V3_CONFIG] club=%s 提现商户配置不完整(mch=%s key=%s pem=%s),'
|
||||
'禁止回落星阙商户,避免串号打款',
|
||||
cid, bool(mch_id), bool(api_v3_key), bool(private_key_path),
|
||||
)
|
||||
|
||||
platform_cert_dir = (
|
||||
_pick_club_field(club, cfg_json, 'platform_cert_dir')
|
||||
or path_defaults['PLATFORM_CERT_DIR']
|
||||
)
|
||||
transfer_scene_id = (
|
||||
_pick_club_field(club, cfg_json, 'transfer_scene_id', 'TRANSFER_SCENE_ID')
|
||||
or path_defaults['TRANSFER_SCENE_ID']
|
||||
|
||||
Reference in New Issue
Block a user