feat: 提现审核按俱乐部隔离并加固打款商户号,新增小溪审核目录复制脚本
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
387
jituan/management/commands/copy_lxs_shenhe_catalog_to_xx.py
Normal file
387
jituan/management/commands/copy_lxs_shenhe_catalog_to_xx.py
Normal file
@@ -0,0 +1,387 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
把龙先生(lxs)审核版商品目录复制到小溪(xx)。
|
||||||
|
|
||||||
|
范围(必须全部满足):
|
||||||
|
- 源俱乐部固定 lxs(name 含「龙先生」)
|
||||||
|
- 目标俱乐部固定 xx(name 含「小溪」或「小西」)
|
||||||
|
- 仅全局 ShangpinLeixing.shenhezhuangtai=2 的类型
|
||||||
|
- 仅公共目录 dianpu IS NULL
|
||||||
|
- 专区/商品均要求 shenhezhuangtai=2
|
||||||
|
|
||||||
|
步骤:
|
||||||
|
1) (可选)清空 xx 下上述审核类型的专区+商品(只删 DB,不删 OSS)
|
||||||
|
2) 从 lxs 复制同类型专区+商品到 xx(图片复用相对路径;销量归零)
|
||||||
|
3) 确保 xx 的 ClubShangpinLeixingConfig 启用这些审核类型(图标可复用源配置)
|
||||||
|
|
||||||
|
不碰:正常商品(shenhezhuangtai=1)、其它俱乐部、店铺商品、全局类型表、订单等。
|
||||||
|
|
||||||
|
用法:
|
||||||
|
python manage.py copy_lxs_shenhe_catalog_to_xx --list
|
||||||
|
python manage.py copy_lxs_shenhe_catalog_to_xx
|
||||||
|
python manage.py copy_lxs_shenhe_catalog_to_xx --apply --i-confirm=lxs-to-xx
|
||||||
|
python manage.py copy_lxs_shenhe_catalog_to_xx --apply --i-confirm=lxs-to-xx --clear-target
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
|
from django.db import transaction
|
||||||
|
|
||||||
|
SOURCE_CLUB = 'lxs'
|
||||||
|
SOURCE_NAME_HINT = '龙先生'
|
||||||
|
TARGET_CLUB = 'xx'
|
||||||
|
TARGET_NAME_HINTS = ('小溪', '小西')
|
||||||
|
SHENHE = 2
|
||||||
|
CONFIRM_TOKEN = 'lxs-to-xx'
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = '复制龙先生审核专区/商品到小溪(xx)(默认 dry-run)'
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument('--apply', action='store_true', help='真正写库')
|
||||||
|
parser.add_argument(
|
||||||
|
'--i-confirm',
|
||||||
|
type=str,
|
||||||
|
default='',
|
||||||
|
help=f'必须等于 {CONFIRM_TOKEN} 才允许 --apply',
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--clear-target',
|
||||||
|
action='store_true',
|
||||||
|
help='写入前先清空 xx 下对应审核专区/商品(只删 DB)',
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--list',
|
||||||
|
action='store_true',
|
||||||
|
help='只列出审核类型及 lxs/xx 下专区商品数量',
|
||||||
|
)
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
from jituan.models import Club, ClubShangpinLeixingConfig
|
||||||
|
from products.models import Shangpin, ShangpinLeixing, ShangpinZhuanqu
|
||||||
|
|
||||||
|
apply = bool(options.get('apply'))
|
||||||
|
confirm = (options.get('i_confirm') or '').strip()
|
||||||
|
clear_target = bool(options.get('clear_target'))
|
||||||
|
|
||||||
|
src_club = Club.query.filter(club_id=SOURCE_CLUB).first()
|
||||||
|
if not src_club:
|
||||||
|
raise CommandError(f'源俱乐部 {SOURCE_CLUB} 不存在')
|
||||||
|
if SOURCE_NAME_HINT not in (src_club.name or ''):
|
||||||
|
raise CommandError(
|
||||||
|
f'安全拦截:{SOURCE_CLUB} 的 name={src_club.name!r} 不含「{SOURCE_NAME_HINT}」'
|
||||||
|
)
|
||||||
|
|
||||||
|
dst_club = Club.query.filter(club_id=TARGET_CLUB).first()
|
||||||
|
if not dst_club:
|
||||||
|
raise CommandError(
|
||||||
|
f'目标俱乐部 {TARGET_CLUB} 不存在。请先在后台创建小溪俱乐部(club_id=xx)。'
|
||||||
|
)
|
||||||
|
if not any(h in (dst_club.name or '') for h in TARGET_NAME_HINTS):
|
||||||
|
raise CommandError(
|
||||||
|
f'安全拦截:{TARGET_CLUB} 的 name={dst_club.name!r} '
|
||||||
|
f'不含「{"/".join(TARGET_NAME_HINTS)}」'
|
||||||
|
)
|
||||||
|
|
||||||
|
audit_types = list(
|
||||||
|
ShangpinLeixing.query.filter(shenhezhuangtai=SHENHE).order_by('id')
|
||||||
|
)
|
||||||
|
if not audit_types:
|
||||||
|
raise CommandError('没有任何审核专用商品类型(shenhezhuangtai=2)')
|
||||||
|
if len(audit_types) != 3:
|
||||||
|
self.stdout.write(self.style.WARNING(
|
||||||
|
f'注意:当前审核类型数量={len(audit_types)}(预期常为 3),将按实际全部处理。'
|
||||||
|
))
|
||||||
|
|
||||||
|
leixing_ids = [lx.id for lx in audit_types]
|
||||||
|
|
||||||
|
def counts(club_id):
|
||||||
|
z = ShangpinZhuanqu.query.filter(
|
||||||
|
club_id=club_id,
|
||||||
|
leixing_id__in=leixing_ids,
|
||||||
|
shenhezhuangtai=SHENHE,
|
||||||
|
dianpu__isnull=True,
|
||||||
|
).count()
|
||||||
|
p = Shangpin.query.filter(
|
||||||
|
club_id=club_id,
|
||||||
|
leixing_id__in=leixing_ids,
|
||||||
|
shenhezhuangtai=SHENHE,
|
||||||
|
dianpu__isnull=True,
|
||||||
|
).count()
|
||||||
|
return z, p
|
||||||
|
|
||||||
|
def per_type(club_id):
|
||||||
|
rows = []
|
||||||
|
for lx in audit_types:
|
||||||
|
z = ShangpinZhuanqu.query.filter(
|
||||||
|
club_id=club_id,
|
||||||
|
leixing_id=lx.id,
|
||||||
|
shenhezhuangtai=SHENHE,
|
||||||
|
dianpu__isnull=True,
|
||||||
|
).count()
|
||||||
|
p = Shangpin.query.filter(
|
||||||
|
club_id=club_id,
|
||||||
|
leixing_id=lx.id,
|
||||||
|
shenhezhuangtai=SHENHE,
|
||||||
|
dianpu__isnull=True,
|
||||||
|
).count()
|
||||||
|
rows.append((lx, z, p))
|
||||||
|
return rows
|
||||||
|
|
||||||
|
if options.get('list'):
|
||||||
|
self.stdout.write(self.style.NOTICE('审核类型及两侧存量:'))
|
||||||
|
for lx in audit_types:
|
||||||
|
sz, sp = (
|
||||||
|
ShangpinZhuanqu.query.filter(
|
||||||
|
club_id=SOURCE_CLUB, leixing_id=lx.id,
|
||||||
|
shenhezhuangtai=SHENHE, dianpu__isnull=True,
|
||||||
|
).count(),
|
||||||
|
Shangpin.query.filter(
|
||||||
|
club_id=SOURCE_CLUB, leixing_id=lx.id,
|
||||||
|
shenhezhuangtai=SHENHE, dianpu__isnull=True,
|
||||||
|
).count(),
|
||||||
|
)
|
||||||
|
dz, dp = (
|
||||||
|
ShangpinZhuanqu.query.filter(
|
||||||
|
club_id=TARGET_CLUB, leixing_id=lx.id,
|
||||||
|
shenhezhuangtai=SHENHE, dianpu__isnull=True,
|
||||||
|
).count(),
|
||||||
|
Shangpin.query.filter(
|
||||||
|
club_id=TARGET_CLUB, leixing_id=lx.id,
|
||||||
|
shenhezhuangtai=SHENHE, dianpu__isnull=True,
|
||||||
|
).count(),
|
||||||
|
)
|
||||||
|
self.stdout.write(
|
||||||
|
f' id={lx.id}「{lx.jieshao}」 '
|
||||||
|
f'lxs: zones={sz} products={sp} | '
|
||||||
|
f'xx: zones={dz} products={dp}'
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
if apply and confirm != CONFIRM_TOKEN:
|
||||||
|
raise CommandError(
|
||||||
|
f'安全拦截:--apply 必须带 --i-confirm={CONFIRM_TOKEN}\n'
|
||||||
|
f' python manage.py copy_lxs_shenhe_catalog_to_xx '
|
||||||
|
f'--apply --i-confirm={CONFIRM_TOKEN}'
|
||||||
|
)
|
||||||
|
|
||||||
|
src_z_total, src_p_total = counts(SOURCE_CLUB)
|
||||||
|
dst_z_total, dst_p_total = counts(TARGET_CLUB)
|
||||||
|
|
||||||
|
src_zones = list(
|
||||||
|
ShangpinZhuanqu.query.filter(
|
||||||
|
club_id=SOURCE_CLUB,
|
||||||
|
leixing_id__in=leixing_ids,
|
||||||
|
shenhezhuangtai=SHENHE,
|
||||||
|
dianpu__isnull=True,
|
||||||
|
).order_by('leixing_id', 'paixu', 'id')
|
||||||
|
)
|
||||||
|
src_products = list(
|
||||||
|
Shangpin.query.filter(
|
||||||
|
club_id=SOURCE_CLUB,
|
||||||
|
leixing_id__in=leixing_ids,
|
||||||
|
shenhezhuangtai=SHENHE,
|
||||||
|
dianpu__isnull=True,
|
||||||
|
).order_by('leixing_id', 'paixu', 'id')
|
||||||
|
)
|
||||||
|
|
||||||
|
src_zone_ids = {z.id for z in src_zones}
|
||||||
|
orphan = [p for p in src_products if p.zhuanqu_id not in src_zone_ids]
|
||||||
|
if orphan:
|
||||||
|
sample = ', '.join(f'{p.id}:{p.biaoqian}' for p in orphan[:5])
|
||||||
|
raise CommandError(
|
||||||
|
f'源商品有 {len(orphan)} 条专区不在本批审核专区内,已中止。样例: {sample}'
|
||||||
|
)
|
||||||
|
|
||||||
|
if src_z_total == 0 and src_p_total == 0:
|
||||||
|
raise CommandError('龙先生(lxs)下没有可复制的审核专区/商品')
|
||||||
|
|
||||||
|
mode = 'APPLY' if apply else 'DRY-RUN'
|
||||||
|
self.stdout.write(self.style.NOTICE(
|
||||||
|
f'\n===== {mode} 小溪审核目录 ← 龙先生 =====\n'
|
||||||
|
f'源: {SOURCE_CLUB} ({src_club.name}) '
|
||||||
|
f'专区={src_z_total} 商品={src_p_total}\n'
|
||||||
|
f'目标: {TARGET_CLUB} ({dst_club.name}) '
|
||||||
|
f'现有 专区={dst_z_total} 商品={dst_p_total}\n'
|
||||||
|
f'clear_target={clear_target}\n'
|
||||||
|
f'审核类型数={len(audit_types)} ids={leixing_ids}\n'
|
||||||
|
f'OSS: 不删文件;复制时复用龙先生相对路径(同一 COS 域名拼接)\n'
|
||||||
|
))
|
||||||
|
|
||||||
|
self.stdout.write('—— 分类型存量 ——')
|
||||||
|
for lx, sz, sp in per_type(SOURCE_CLUB):
|
||||||
|
self.stdout.write(f' [lxs] id={lx.id}「{lx.jieshao}」 zones={sz} products={sp}')
|
||||||
|
for lx, dz, dp in per_type(TARGET_CLUB):
|
||||||
|
self.stdout.write(f' [xx] id={lx.id}「{lx.jieshao}」 zones={dz} products={dp}')
|
||||||
|
|
||||||
|
self.stdout.write('—— 将从 lxs 复制的专区样例 ——')
|
||||||
|
for z in src_zones[:40]:
|
||||||
|
self.stdout.write(
|
||||||
|
f' zq#{z.id} leixing={z.leixing_id} {z.mingzi!r}'
|
||||||
|
)
|
||||||
|
if len(src_zones) > 40:
|
||||||
|
self.stdout.write(f' ... 其余 {len(src_zones) - 40} 个省略')
|
||||||
|
|
||||||
|
self.stdout.write('—— 将从 lxs 复制的商品样例 ——')
|
||||||
|
for p in src_products[:15]:
|
||||||
|
self.stdout.write(
|
||||||
|
f' sp#{p.id} leixing={p.leixing_id} {p.biaoqian!r} ¥{p.jiage}'
|
||||||
|
)
|
||||||
|
if len(src_products) > 15:
|
||||||
|
self.stdout.write(f' ... 其余 {len(src_products) - 15} 个省略')
|
||||||
|
|
||||||
|
if not apply:
|
||||||
|
self.stdout.write(self.style.WARNING(
|
||||||
|
'\n以上仅计划:未删库、未写库。确认无误后:\n'
|
||||||
|
f' python manage.py copy_lxs_shenhe_catalog_to_xx '
|
||||||
|
f'--apply --i-confirm={CONFIRM_TOKEN}\n'
|
||||||
|
f'若目标已有旧审核目录需先清空,再加 --clear-target\n'
|
||||||
|
))
|
||||||
|
return
|
||||||
|
|
||||||
|
stats = {
|
||||||
|
'deleted_products': 0,
|
||||||
|
'deleted_zones': 0,
|
||||||
|
'zones_created': 0,
|
||||||
|
'products_created': 0,
|
||||||
|
'cfg_created': 0,
|
||||||
|
'cfg_enabled': 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
with transaction.atomic():
|
||||||
|
if clear_target:
|
||||||
|
n_prod, _ = Shangpin.query.filter(
|
||||||
|
club_id=TARGET_CLUB,
|
||||||
|
leixing_id__in=leixing_ids,
|
||||||
|
shenhezhuangtai=SHENHE,
|
||||||
|
dianpu__isnull=True,
|
||||||
|
).delete()
|
||||||
|
n_zone, _ = ShangpinZhuanqu.query.filter(
|
||||||
|
club_id=TARGET_CLUB,
|
||||||
|
leixing_id__in=leixing_ids,
|
||||||
|
shenhezhuangtai=SHENHE,
|
||||||
|
dianpu__isnull=True,
|
||||||
|
).delete()
|
||||||
|
stats['deleted_products'] = n_prod
|
||||||
|
stats['deleted_zones'] = n_zone
|
||||||
|
|
||||||
|
zone_id_map = {}
|
||||||
|
for z in src_zones:
|
||||||
|
if z.leixing_id not in leixing_ids:
|
||||||
|
raise CommandError(f'专区 {z.id} 类型 {z.leixing_id} 不在审核列表')
|
||||||
|
# 未 clear 时若同名专区已存在则复用,避免重复堆叠
|
||||||
|
exist = None
|
||||||
|
if not clear_target:
|
||||||
|
exist = ShangpinZhuanqu.query.filter(
|
||||||
|
club_id=TARGET_CLUB,
|
||||||
|
leixing_id=z.leixing_id,
|
||||||
|
mingzi=z.mingzi,
|
||||||
|
shenhezhuangtai=SHENHE,
|
||||||
|
dianpu__isnull=True,
|
||||||
|
).first()
|
||||||
|
if exist:
|
||||||
|
zone_id_map[z.id] = exist.id
|
||||||
|
continue
|
||||||
|
row = ShangpinZhuanqu.query.create(
|
||||||
|
mingzi=z.mingzi,
|
||||||
|
leixing_id=z.leixing_id,
|
||||||
|
club_id=TARGET_CLUB,
|
||||||
|
tupian_url=z.tupian_url or '',
|
||||||
|
shenhezhuangtai=SHENHE,
|
||||||
|
paixu=int(z.paixu or 0),
|
||||||
|
shangjia_zhuangtai=bool(getattr(z, 'shangjia_zhuangtai', True)),
|
||||||
|
fengjin_zhuangtai=bool(getattr(z, 'fengjin_zhuangtai', True)),
|
||||||
|
dianpu=None,
|
||||||
|
dianpu_leixing=None,
|
||||||
|
)
|
||||||
|
zone_id_map[z.id] = row.id
|
||||||
|
stats['zones_created'] += 1
|
||||||
|
|
||||||
|
for p in src_products:
|
||||||
|
if p.leixing_id not in leixing_ids:
|
||||||
|
raise CommandError(f'商品 {p.id} 类型 {p.leixing_id} 不在审核列表')
|
||||||
|
dst_zq = zone_id_map.get(p.zhuanqu_id)
|
||||||
|
if not dst_zq:
|
||||||
|
raise CommandError(
|
||||||
|
f'商品「{p.biaoqian}」id={p.id} 无法映射 zhuanqu_id={p.zhuanqu_id}'
|
||||||
|
)
|
||||||
|
if not clear_target:
|
||||||
|
exist_p = Shangpin.query.filter(
|
||||||
|
club_id=TARGET_CLUB,
|
||||||
|
leixing_id=p.leixing_id,
|
||||||
|
zhuanqu_id=dst_zq,
|
||||||
|
biaoqian=p.biaoqian,
|
||||||
|
shenhezhuangtai=SHENHE,
|
||||||
|
dianpu__isnull=True,
|
||||||
|
).first()
|
||||||
|
if exist_p:
|
||||||
|
continue
|
||||||
|
Shangpin.query.create(
|
||||||
|
biaoqian=p.biaoqian,
|
||||||
|
jiage=p.jiage,
|
||||||
|
kucun=p.kucun,
|
||||||
|
leixing_id=p.leixing_id,
|
||||||
|
zhuanqu_id=dst_zq,
|
||||||
|
zhenshi_xiaoliang=0,
|
||||||
|
duiwai_xiaoliang=0,
|
||||||
|
jieshao=p.jieshao,
|
||||||
|
xiadan_xuzhi=p.xiadan_xuzhi,
|
||||||
|
guize_tupian=p.guize_tupian or '',
|
||||||
|
tupian_url=p.tupian_url or '',
|
||||||
|
yaoqiuleixing=p.yaoqiuleixing,
|
||||||
|
huiyuan_id=p.huiyuan_id,
|
||||||
|
yongjin=p.yongjin,
|
||||||
|
kaioi_ewai_dashou_fencheng=bool(p.kaioi_ewai_dashou_fencheng),
|
||||||
|
ewai_dashou_fencheng=p.ewai_dashou_fencheng,
|
||||||
|
paixu=int(p.paixu or 0),
|
||||||
|
shenhezhuangtai=SHENHE,
|
||||||
|
shangjia_zhuangtai=bool(p.shangjia_zhuangtai),
|
||||||
|
fengjin_zhuangtai=bool(p.fengjin_zhuangtai),
|
||||||
|
shenhe_zhuangtai=bool(getattr(p, 'shenhe_zhuangtai', True)),
|
||||||
|
club_id=TARGET_CLUB,
|
||||||
|
shi_zhuanpan=False,
|
||||||
|
dianpu=None,
|
||||||
|
dianpu_leixing=None,
|
||||||
|
)
|
||||||
|
stats['products_created'] += 1
|
||||||
|
|
||||||
|
# 启用审核类型上架配置
|
||||||
|
for lx in audit_types:
|
||||||
|
src_cfg = ClubShangpinLeixingConfig.query.filter(
|
||||||
|
club_id=SOURCE_CLUB, leixing_id=lx.id,
|
||||||
|
).first()
|
||||||
|
dst_cfg = ClubShangpinLeixingConfig.query.filter(
|
||||||
|
club_id=TARGET_CLUB, leixing_id=lx.id,
|
||||||
|
).first()
|
||||||
|
icon = ''
|
||||||
|
paixu = int(getattr(lx, 'paixu', 0) or 0)
|
||||||
|
if src_cfg:
|
||||||
|
icon = src_cfg.tupian_url or ''
|
||||||
|
paixu = int(src_cfg.paixu or paixu)
|
||||||
|
if dst_cfg is None:
|
||||||
|
ClubShangpinLeixingConfig.query.create(
|
||||||
|
club_id=TARGET_CLUB,
|
||||||
|
leixing_id=lx.id,
|
||||||
|
is_enabled=True,
|
||||||
|
tupian_url=icon,
|
||||||
|
paixu=paixu,
|
||||||
|
)
|
||||||
|
stats['cfg_created'] += 1
|
||||||
|
else:
|
||||||
|
changed = False
|
||||||
|
if not dst_cfg.is_enabled:
|
||||||
|
dst_cfg.is_enabled = True
|
||||||
|
changed = True
|
||||||
|
if icon and not (dst_cfg.tupian_url or '').strip():
|
||||||
|
dst_cfg.tupian_url = icon
|
||||||
|
changed = True
|
||||||
|
if changed:
|
||||||
|
dst_cfg.save()
|
||||||
|
stats['cfg_enabled'] += 1
|
||||||
|
|
||||||
|
self.stdout.write(self.style.SUCCESS(
|
||||||
|
f'\n完成: {stats}\n'
|
||||||
|
f'龙先生(lxs)未改动;小溪(xx)已具备审核目录。\n'
|
||||||
|
f'请用小溪小程序(CLUB_ID=xx)重新编译验证。'
|
||||||
|
))
|
||||||
153
jituan/management/commands/seed_club_withdraw_audit_perm.py
Normal file
153
jituan/management/commands/seed_club_withdraw_audit_perm.py
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
"""确保提现审核权限码 005bb,并按俱乐部挂到指定角色(可重复执行)。
|
||||||
|
|
||||||
|
权限码全局共用;隔离靠 Role.ClubID + 请求头俱乐部。
|
||||||
|
集团高管仍可跨俱乐部;俱乐部管理员仅本俱乐部。
|
||||||
|
|
||||||
|
用法示例:
|
||||||
|
python manage.py seed_club_withdraw_audit_perm
|
||||||
|
python manage.py seed_club_withdraw_audit_perm --club-id lxs --role-name 管理员
|
||||||
|
python manage.py seed_club_withdraw_audit_perm --all-clubs --role-name 管理员
|
||||||
|
python manage.py seed_club_withdraw_audit_perm --all-clubs --role-name 管理员 --dry-run
|
||||||
|
"""
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
|
from django.db import transaction
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from gvsdsdk.models import Permission, Role, RolePermission
|
||||||
|
|
||||||
|
from jituan.models import Club
|
||||||
|
|
||||||
|
WITHDRAW_AUDIT_PERM = '005bb'
|
||||||
|
WITHDRAW_AUDIT_NAME = '提现审核(按俱乐部)'
|
||||||
|
WITHDRAW_AUDIT_DESC = (
|
||||||
|
'审核本俱乐部提现申请(同意/拒绝/自动打款待收款)。'
|
||||||
|
'跨俱乐部仅集团高管可操作;须配合后台角色 ClubID 与数据范围任职。'
|
||||||
|
)
|
||||||
|
WITHDRAW_AUDIT_CATEGORY = '提现管理'
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = '写入 005bb 提现审核权限码,并按俱乐部挂到角色'
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument(
|
||||||
|
'--club-id', default='', help='仅处理该俱乐部角色(如 lxs / xq)',
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--all-clubs', action='store_true', help='处理全部启用中的俱乐部',
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--role-name', default='',
|
||||||
|
help='角色名(如「管理员」);为空则只确保权限码存在,不挂角色',
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--dry-run', action='store_true', help='只打印不写入',
|
||||||
|
)
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
dry_run = options['dry_run']
|
||||||
|
club_id = (options.get('club_id') or '').strip()
|
||||||
|
all_clubs = options.get('all_clubs')
|
||||||
|
role_name = (options.get('role_name') or '').strip()
|
||||||
|
|
||||||
|
sample = Permission.objects.filter(PermStatus=1).first()
|
||||||
|
tenant_uuid = sample.TenantUUID if sample else uuid.UUID(
|
||||||
|
'00000000-0000-0000-0000-000000000001'
|
||||||
|
).bytes
|
||||||
|
|
||||||
|
perm = Permission.objects.filter(PermCode=WITHDRAW_AUDIT_PERM).first()
|
||||||
|
if perm:
|
||||||
|
self.stdout.write(f' 权限码已存在: {WITHDRAW_AUDIT_PERM}')
|
||||||
|
if not dry_run and (perm.PermName != WITHDRAW_AUDIT_NAME or not perm.PermDesc):
|
||||||
|
perm.PermName = WITHDRAW_AUDIT_NAME
|
||||||
|
perm.PermDesc = WITHDRAW_AUDIT_DESC
|
||||||
|
perm.PermCategory = WITHDRAW_AUDIT_CATEGORY
|
||||||
|
perm.save(update_fields=['PermName', 'PermDesc', 'PermCategory'])
|
||||||
|
self.stdout.write(self.style.SUCCESS(' 已更新 005bb 名称/说明'))
|
||||||
|
else:
|
||||||
|
if dry_run:
|
||||||
|
self.stdout.write(f'would create perm {WITHDRAW_AUDIT_PERM}')
|
||||||
|
else:
|
||||||
|
perm = Permission.objects.create(
|
||||||
|
PermUUID=uuid.uuid4().bytes,
|
||||||
|
TenantUUID=tenant_uuid,
|
||||||
|
PermCode=WITHDRAW_AUDIT_PERM,
|
||||||
|
PermName=WITHDRAW_AUDIT_NAME,
|
||||||
|
PermDesc=WITHDRAW_AUDIT_DESC,
|
||||||
|
PermCategory=WITHDRAW_AUDIT_CATEGORY,
|
||||||
|
PermStatus=1,
|
||||||
|
CreateTime=timezone.now(),
|
||||||
|
)
|
||||||
|
self.stdout.write(self.style.SUCCESS(f' 新增权限: {WITHDRAW_AUDIT_PERM}'))
|
||||||
|
|
||||||
|
if not role_name:
|
||||||
|
self.stdout.write(
|
||||||
|
self.style.WARNING(
|
||||||
|
'未指定 --role-name:仅确保权限码。'
|
||||||
|
'给俱乐部挂权请加:--club-id lxs --role-name 管理员'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
if not club_id and not all_clubs:
|
||||||
|
raise CommandError('挂角色时请指定 --club-id 或 --all-clubs')
|
||||||
|
|
||||||
|
if all_clubs:
|
||||||
|
club_ids = list(
|
||||||
|
Club.query.filter(status=1).values_list('club_id', flat=True)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
if not Club.query.filter(club_id=club_id, status=1).exists():
|
||||||
|
raise CommandError(f'俱乐部不存在或未启用: {club_id}')
|
||||||
|
club_ids = [club_id]
|
||||||
|
|
||||||
|
if not perm and dry_run:
|
||||||
|
self.stdout.write('dry-run: skip role binding (perm not loaded)')
|
||||||
|
return
|
||||||
|
|
||||||
|
linked = 0
|
||||||
|
missing_roles = 0
|
||||||
|
with transaction.atomic():
|
||||||
|
for cid in club_ids:
|
||||||
|
role = Role.objects.filter(
|
||||||
|
RoleName=role_name, ClubID=cid, RoleStatus=1,
|
||||||
|
).first()
|
||||||
|
if not role:
|
||||||
|
missing_roles += 1
|
||||||
|
self.stdout.write(
|
||||||
|
self.style.WARNING(f' [{cid}] 无角色「{role_name}」,跳过')
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
exists = RolePermission.objects.filter(
|
||||||
|
RoleUUID=role.RoleUUID, PermUUID=perm.PermUUID,
|
||||||
|
).exists()
|
||||||
|
if exists:
|
||||||
|
self.stdout.write(f' [{cid}] 角色「{role_name}」已有 {WITHDRAW_AUDIT_PERM}')
|
||||||
|
continue
|
||||||
|
if dry_run:
|
||||||
|
self.stdout.write(
|
||||||
|
f'would link {WITHDRAW_AUDIT_PERM} -> [{cid}] {role_name}'
|
||||||
|
)
|
||||||
|
linked += 1
|
||||||
|
continue
|
||||||
|
RolePermission.objects.create(
|
||||||
|
RolePermUUID=uuid.uuid4().bytes,
|
||||||
|
RoleUUID=role.RoleUUID,
|
||||||
|
PermUUID=perm.PermUUID,
|
||||||
|
CreateTime=timezone.now(),
|
||||||
|
)
|
||||||
|
linked += 1
|
||||||
|
self.stdout.write(
|
||||||
|
self.style.SUCCESS(
|
||||||
|
f' [{cid}] 已给角色「{role_name}」挂上 {WITHDRAW_AUDIT_PERM}'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.stdout.write(
|
||||||
|
self.style.SUCCESS(
|
||||||
|
f'完成:挂权 {linked},缺角色 {missing_roles}。'
|
||||||
|
f'另请确认该账号在「数据范围配置」有对应俱乐部 SINGLE_CLUB 任职。'
|
||||||
|
)
|
||||||
|
)
|
||||||
@@ -44,6 +44,12 @@ EXTRA_PERMISSIONS = [
|
|||||||
'管理小程序身份装饰标签(与用户管理-身份装饰标签菜单对应)',
|
'管理小程序身份装饰标签(与用户管理-身份装饰标签菜单对应)',
|
||||||
'用户管理',
|
'用户管理',
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
'005bb',
|
||||||
|
'提现审核(按俱乐部)',
|
||||||
|
'审核本俱乐部提现(同意/拒绝/自动打款)。跨俱乐部仅集团高管;须角色 ClubID + 数据范围任职。',
|
||||||
|
'提现管理',
|
||||||
|
),
|
||||||
# ---------- 店铺管理(999 系列:列表/详情/复制/提现等) ----------
|
# ---------- 店铺管理(999 系列:列表/详情/复制/提现等) ----------
|
||||||
('999a', '店铺状态管理', '修改店铺封禁/正常状态', '店铺管理'),
|
('999a', '店铺状态管理', '修改店铺封禁/正常状态', '店铺管理'),
|
||||||
('999b', '店铺余额与创建', '修改可提现余额、添加新店铺', '店铺管理'),
|
('999b', '店铺余额与创建', '修改可提现余额、添加新店铺', '店铺管理'),
|
||||||
|
|||||||
@@ -1,11 +1,24 @@
|
|||||||
"""集团级资金处置权限:提现审核、加余额等。俱乐部 000001 超级管理不能绕过。"""
|
"""集团级资金处置权限:加余额等仍仅集团高管。
|
||||||
|
|
||||||
|
提现审核:权限码 005bb + 按俱乐部隔离——
|
||||||
|
- 子公司视图:持有 005bb 且任职含当前俱乐部 → 可审本俱乐部(同意/拒绝/自动打款)
|
||||||
|
- 集团汇总视图 / 跨俱乐部:须集团资金处置权(GROUP_OWNER / GROUP_SUPER_ADMIN)
|
||||||
|
俱乐部本地 000001 不能绕过「跨店」闸门,但可审本店提现。
|
||||||
|
"""
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
|
||||||
from jituan.constants import DATA_SCOPE_ALL
|
from jituan.constants import DATA_SCOPE_ALL
|
||||||
from jituan.models import AdminAssignment
|
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_GROUP_FINANCE_REQUIRED = '该操作仅集团高管可执行(需在数据范围配置中为集团级任职)'
|
||||||
|
MSG_WITHDRAW_CROSS_CLUB = '跨俱乐部提现审核仅集团高管可执行,请切换到具体俱乐部后再操作'
|
||||||
|
MSG_WITHDRAW_CLUB_DENIED = '无权审核该俱乐部提现'
|
||||||
|
|
||||||
|
|
||||||
def has_group_finance_exec(user, request=None):
|
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):
|
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 backend.utils import verify_kefu_permission
|
||||||
from rest_framework.response import Response
|
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)
|
kefu, permissions = verify_kefu_permission(request, phone)
|
||||||
if kefu is None:
|
if kefu is None:
|
||||||
return None, permissions
|
return None, permissions
|
||||||
if '005bb' not in permissions:
|
if '005bb' not in permissions:
|
||||||
return None, Response({'code': 403, 'msg': '您无权操作提现审核'})
|
return None, Response({'code': 403, 'msg': '您无权操作提现审核'})
|
||||||
deny = deny_group_finance_exec(request.user, request)
|
|
||||||
if deny:
|
if has_group_finance_exec(request.user, request):
|
||||||
return None, deny
|
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
|
return kefu, None
|
||||||
|
|||||||
@@ -15,6 +15,24 @@ def club_id_for_tixian_yonghuid_safe(yonghuid):
|
|||||||
return club_id_for_tixian_yonghuid(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):
|
def filter_tixianjilu_by_request(qs, request):
|
||||||
return filter_queryset_by_club(qs, request, club_field='club_id')
|
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):
|
def forbid_if_tixian_out_of_scope(request, tixian_record):
|
||||||
"""提现记录不在当前俱乐部范围时返回 Response。"""
|
"""提现记录不在当前俱乐部范围时返回 Response。"""
|
||||||
if resolve_club_scope(request) == DATA_SCOPE_ALL:
|
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
|
return None
|
||||||
club_id = resolve_club_id_from_request(request)
|
club_id = resolve_club_id_from_request(request)
|
||||||
rec_club = getattr(tixian_record, 'club_id', None) or club_id_for_tixian_yonghuid_safe(
|
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 私钥;仅当库中路径存在且文件在盘上才覆盖
|
# 共用商户的俱乐部:默认用 settings 私钥;仅当库中路径存在且文件在盘上才覆盖
|
||||||
# (避免旧库里曾填过、但原先被忽略的无效路径突然接管提现)
|
# (避免旧库里曾填过、但原先被忽略的无效路径突然接管提现)
|
||||||
private_key_path = path_defaults['PRIVATE_KEY_PATH']
|
# 非共享俱乐部:严禁回落星阙私钥/商户,缺配置直接空值,由上层拒绝打款
|
||||||
if cid not in SHARED_WX_MERCHANT_CLUBS:
|
if cid in SHARED_WX_MERCHANT_CLUBS:
|
||||||
private_key_path = (
|
private_key_path = path_defaults['PRIVATE_KEY_PATH']
|
||||||
_pick_club_field(club, cfg_json, 'private_key_path')
|
if club:
|
||||||
or private_key_path
|
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:
|
else:
|
||||||
uploaded_key = (getattr(club, 'private_key_path', None) or '').strip()
|
private_key_path = _pick_club_field(club, cfg_json, 'private_key_path')
|
||||||
if uploaded_key and os.path.isfile(uploaded_key):
|
platform_cert_dir = _pick_club_field(club, cfg_json, 'platform_cert_dir')
|
||||||
private_key_path = uploaded_key
|
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 = (
|
transfer_scene_id = (
|
||||||
_pick_club_field(club, cfg_json, 'transfer_scene_id', 'TRANSFER_SCENE_ID')
|
_pick_club_field(club, cfg_json, 'transfer_scene_id', 'TRANSFER_SCENE_ID')
|
||||||
or path_defaults['TRANSFER_SCENE_ID']
|
or path_defaults['TRANSFER_SCENE_ID']
|
||||||
|
|||||||
@@ -1105,7 +1105,7 @@ def query_wechat_transfer_bill(out_bill_no, club_id=None):
|
|||||||
try:
|
try:
|
||||||
rec = TixianAutoRecord.query.filter(tixian_id=out_bill_no).first()
|
rec = TixianAutoRecord.query.filter(tixian_id=out_bill_no).first()
|
||||||
if rec:
|
if rec:
|
||||||
club_id = club_id_for_tixian_yonghuid(rec.yonghuid)
|
club_id = (getattr(rec, 'club_id', None) or '').strip() or club_id_for_tixian_yonghuid(rec.yonghuid)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -1152,8 +1152,8 @@ def _parse_package_info(raw):
|
|||||||
def _apply_platform_accounting(auto_record):
|
def _apply_platform_accounting(auto_record):
|
||||||
"""平台收支统计(幂等由 mark_transfer_success 外层保证只调一次)"""
|
"""平台收支统计(幂等由 mark_transfer_success 外层保证只调一次)"""
|
||||||
from jituan.services.szjilu_accounting import apply_szjilu_expense
|
from jituan.services.szjilu_accounting import apply_szjilu_expense
|
||||||
from jituan.services.wechat_pay import club_id_for_tixian_yonghuid
|
from jituan.services.tixian_club import resolve_payout_club_id
|
||||||
payout_club = club_id_for_tixian_yonghuid(auto_record.yonghuid)
|
payout_club = resolve_payout_club_id(audit=None, jilu=auto_record, yonghuid=auto_record.yonghuid)
|
||||||
update_daily_payout(auto_record.shijidaozhang, payout_club)
|
update_daily_payout(auto_record.shijidaozhang, payout_club)
|
||||||
apply_szjilu_expense(auto_record.shijidaozhang, payout_club)
|
apply_szjilu_expense(auto_record.shijidaozhang, payout_club)
|
||||||
|
|
||||||
|
|||||||
@@ -202,17 +202,12 @@ def process_audit_collect(request):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
from users.tixian_shenhe_services import get_auto_withdraw_openid
|
from users.tixian_shenhe_services import get_auto_withdraw_openid
|
||||||
from jituan.services.club_user import get_user_club_id
|
|
||||||
from jituan.services.wechat_pay import get_wechat_v3_config
|
from jituan.services.wechat_pay import get_wechat_v3_config
|
||||||
|
from jituan.services.tixian_club import resolve_payout_club_id
|
||||||
|
|
||||||
payout_club_id = get_user_club_id(user_main)
|
|
||||||
payout_openid = get_auto_withdraw_openid(user_main)
|
payout_openid = get_auto_withdraw_openid(user_main)
|
||||||
if not payout_openid:
|
if not payout_openid:
|
||||||
return Response({'code': 10, 'msg': '用户未绑定微信,无法收款'})
|
return Response({'code': 10, 'msg': '用户未绑定微信,无法收款'})
|
||||||
wx_cfg = get_wechat_v3_config(payout_club_id)
|
|
||||||
if not wx_cfg.get('APPID') or not wx_cfg.get('MCHID') or not wx_cfg.get('TRANSFER_SCENE_ID'):
|
|
||||||
logger.error('微信打款配置不完整 club=%s APPID=%s MCHID=%s', payout_club_id, wx_cfg.get('APPID'), wx_cfg.get('MCHID'))
|
|
||||||
return Response({'code': 11, 'msg': '系统配置异常,请联系客服'})
|
|
||||||
|
|
||||||
ctx, err = resolve_collect_context(
|
ctx, err = resolve_collect_context(
|
||||||
yonghuid,
|
yonghuid,
|
||||||
@@ -228,6 +223,18 @@ def process_audit_collect(request):
|
|||||||
tixianjilu_id_val = ctx['jilu'].id if ctx.get('jilu') else audit.tixianjilu_id
|
tixianjilu_id_val = ctx['jilu'].id if ctx.get('jilu') else audit.tixianjilu_id
|
||||||
leixing = audit.leixing
|
leixing = audit.leixing
|
||||||
|
|
||||||
|
# 打款商户严格跟提现单 club_id,禁止因用户迁店串到其他俱乐部商户号
|
||||||
|
payout_club_id = resolve_payout_club_id(
|
||||||
|
audit=audit, jilu=ctx.get('jilu'), user=user_main,
|
||||||
|
)
|
||||||
|
wx_cfg = get_wechat_v3_config(payout_club_id)
|
||||||
|
if not wx_cfg.get('APPID') or not wx_cfg.get('MCHID') or not wx_cfg.get('TRANSFER_SCENE_ID'):
|
||||||
|
logger.error(
|
||||||
|
'微信打款配置不完整 club=%s APPID=%s MCHID=%s audit=%s',
|
||||||
|
payout_club_id, wx_cfg.get('APPID'), wx_cfg.get('MCHID'), getattr(audit, 'id', None),
|
||||||
|
)
|
||||||
|
return Response({'code': 11, 'msg': '系统配置异常,请联系客服'})
|
||||||
|
|
||||||
amount_ok, amount_msg = validate_collect_amount(audit)
|
amount_ok, amount_msg = validate_collect_amount(audit)
|
||||||
if not amount_ok:
|
if not amount_ok:
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
@@ -303,10 +310,9 @@ def process_audit_collect(request):
|
|||||||
quota_reserved = True
|
quota_reserved = True
|
||||||
|
|
||||||
tixian_id = generate_tixian_id()
|
tixian_id = generate_tixian_id()
|
||||||
from jituan.services.club_user import get_user_club_id
|
|
||||||
TixianAutoRecord.query.create(
|
TixianAutoRecord.query.create(
|
||||||
tixian_id=tixian_id,
|
tixian_id=tixian_id,
|
||||||
club_id=get_user_club_id(user_main),
|
club_id=payout_club_id,
|
||||||
yonghuid=yonghuid,
|
yonghuid=yonghuid,
|
||||||
leixing=leixing,
|
leixing=leixing,
|
||||||
jine=jine,
|
jine=jine,
|
||||||
|
|||||||
@@ -2285,6 +2285,8 @@ class AddtxshView(APIView):
|
|||||||
|
|
||||||
# 🔴【修改】排序改为按申请时间升序(最早申请排最前)
|
# 🔴【修改】排序改为按申请时间升序(最早申请排最前)
|
||||||
queryset = Tixianjilu.query.filter(query).order_by('CreateTime')
|
queryset = Tixianjilu.query.filter(query).order_by('CreateTime')
|
||||||
|
from jituan.services.tixian_club import filter_tixianjilu_by_request
|
||||||
|
queryset = filter_tixianjilu_by_request(queryset, request)
|
||||||
|
|
||||||
# 🔴【修改】分页逻辑:如果是搜索模式,不分页,返回所有匹配记录
|
# 🔴【修改】分页逻辑:如果是搜索模式,不分页,返回所有匹配记录
|
||||||
if search_uid:
|
if search_uid:
|
||||||
@@ -2297,8 +2299,11 @@ class AddtxshView(APIView):
|
|||||||
current_count = len(records)
|
current_count = len(records)
|
||||||
has_more = (offset + current_count) < total_count
|
has_more = (offset + current_count) < total_count
|
||||||
|
|
||||||
# 获取状态统计(统计与搜索无关,依然按筛选条件统计)
|
# 获取状态统计(统计与搜索无关,依然按筛选条件统计;按俱乐部隔离)
|
||||||
_agg = Tixianjilu.query.filter(leixing=leixing).aggregate(
|
_status_qs = filter_tixianjilu_by_request(
|
||||||
|
Tixianjilu.query.filter(leixing=leixing), request,
|
||||||
|
)
|
||||||
|
_agg = _status_qs.aggregate(
|
||||||
awaiting=Count('id', filter=Q(zhuangtai=1)),
|
awaiting=Count('id', filter=Q(zhuangtai=1)),
|
||||||
processed=Count('id', filter=Q(zhuangtai__in=[2, 3])),
|
processed=Count('id', filter=Q(zhuangtai__in=[2, 3])),
|
||||||
)
|
)
|
||||||
@@ -2393,6 +2398,11 @@ class AdtixianqkView(APIView):
|
|||||||
'data': None
|
'data': None
|
||||||
}, status=status.HTTP_404_NOT_FOUND)
|
}, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
from jituan.services.tixian_club import forbid_if_tixian_out_of_scope
|
||||||
|
deny = forbid_if_tixian_out_of_scope(request, withdrawal)
|
||||||
|
if deny:
|
||||||
|
return deny
|
||||||
|
|
||||||
# 检查状态,只有待审核的可以处理
|
# 检查状态,只有待审核的可以处理
|
||||||
if withdrawal.zhuangtai != 1:
|
if withdrawal.zhuangtai != 1:
|
||||||
return Response({
|
return Response({
|
||||||
@@ -2418,9 +2428,10 @@ class AdtixianqkView(APIView):
|
|||||||
try:
|
try:
|
||||||
from jituan.services.szjilu_accounting import apply_szjilu_expense
|
from jituan.services.szjilu_accounting import apply_szjilu_expense
|
||||||
tixian_jine = withdrawal.jine
|
tixian_jine = withdrawal.jine
|
||||||
from jituan.services.club_user import get_user_club_id
|
from jituan.services.tixian_club import resolve_payout_club_id
|
||||||
payout_club = get_user_club_id(
|
payout_club = resolve_payout_club_id(
|
||||||
User.query.filter(UserUID=withdrawal.yonghuid).first()
|
jilu=withdrawal,
|
||||||
|
user=User.query.filter(UserUID=withdrawal.yonghuid).first(),
|
||||||
)
|
)
|
||||||
update_daily_payout(tixian_jine, payout_club)
|
update_daily_payout(tixian_jine, payout_club)
|
||||||
apply_szjilu_expense(tixian_jine, payout_club)
|
apply_szjilu_expense(tixian_jine, payout_club)
|
||||||
|
|||||||
@@ -699,14 +699,15 @@ class KefuWithdrawActionView(APIView):
|
|||||||
except User.DoesNotExist:
|
except User.DoesNotExist:
|
||||||
raise ValueError(f'用户{tixian.yonghuid}不存在')
|
raise ValueError(f'用户{tixian.yonghuid}不存在')
|
||||||
|
|
||||||
from jituan.services.club_user import get_user_club_id
|
from jituan.services.tixian_club import resolve_payout_club_id
|
||||||
|
|
||||||
if action == 1:
|
if action == 1:
|
||||||
tixian.zhuangtai = 2
|
tixian.zhuangtai = 2
|
||||||
try:
|
try:
|
||||||
from jituan.services.szjilu_accounting import apply_szjilu_expense
|
from jituan.services.szjilu_accounting import apply_szjilu_expense
|
||||||
update_daily_payout(tixian.jine, get_user_club_id(user))
|
payout_club = resolve_payout_club_id(jilu=tixian, user=user)
|
||||||
apply_szjilu_expense(tixian.jine, get_user_club_id(user))
|
update_daily_payout(tixian.jine, payout_club)
|
||||||
|
apply_szjilu_expense(tixian.jine, payout_club)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f'更新收支记录失败: {e}')
|
logger.error(f'更新收支记录失败: {e}')
|
||||||
else:
|
else:
|
||||||
|
|||||||
Reference in New Issue
Block a user