feat: 俱乐部开关-无会员可聊天与买押金补满积分

This commit is contained in:
XingQue committed 2026-08-03 18:32:10 +08:00
1 parent 2ace9ca9ee
commit 16faf5afe4
6 files changed
+217 -52

No files matched your search

+7
View File
@@ -120,6 +120,13 @@ def fulfill_yajin_recharge(dingdan_id, paid_amount_yuan=None):
dashou.yajin += amount
dashou.save(update_fields=['yajin'])
try:
from jituan.services.yajin_jifen_switch import maybe_fill_jifen_after_yajin
club_id_for_jifen = getattr(order, 'club_id', None) or CLUB_ID_DEFAULT
maybe_fill_jifen_after_yajin(dashou, club_id_for_jifen)
except Exception as exc:
logger.error('押金补积分失败 dingdan=%s: %s', dingdan_id, exc, exc_info=True)
order.zhuangtai = 3
order.save(update_fields=['zhuangtai'])
+76
View File
@@ -0,0 +1,76 @@
"""俱乐部打手聊天门槛(config_json)。
默认与现网一致:不充会员不可聊,但押金达到门槛可聊;进行中订单旁路保留。
开启 allow_without_member 后:打手无需会员也可聊。
"""
import logging
from decimal import Decimal, InvalidOperation
logger = logging.getLogger(__name__)
ALLOW_WITHOUT_MEMBER_KEY = 'dashou_chat_allow_without_member'
YAJIN_THRESHOLD_KEY = 'dashou_chat_yajin_threshold'
DEFAULT_YAJIN_THRESHOLD = Decimal('5')
def _club(club_id: str):
cid = (club_id or '').strip()
if not cid:
return None
from jituan.models import Club
club = Club.query.filter(club_id=cid).first()
if not club and cid != cid.lower():
club = Club.query.filter(club_id=cid.lower()).first()
return club
def is_dashou_chat_allow_without_member(club_id: str) -> bool:
"""开启=不充会员也能聊;关闭(默认)=须会员或押金达标等。"""
club = _club(club_id)
if not club:
return False
try:
cfg = club.config_json or {}
raw = cfg.get(ALLOW_WITHOUT_MEMBER_KEY, False)
if isinstance(raw, str):
return raw.strip().lower() in ('1', 'true', 'yes', 'on')
return bool(raw)
except Exception:
logger.exception('read dashou_chat_allow_without_member failed club=%s', club_id)
return False
def get_dashou_chat_yajin_threshold(club_id: str) -> Decimal:
"""无会员时押金门槛;默认 5(与现网硬编码一致)。"""
club = _club(club_id)
if not club:
return DEFAULT_YAJIN_THRESHOLD
try:
cfg = club.config_json or {}
if YAJIN_THRESHOLD_KEY not in cfg:
return DEFAULT_YAJIN_THRESHOLD
return Decimal(str(cfg.get(YAJIN_THRESHOLD_KEY) or DEFAULT_YAJIN_THRESHOLD))
except (InvalidOperation, TypeError, ValueError):
return DEFAULT_YAJIN_THRESHOLD
except Exception:
logger.exception('read dashou_chat_yajin_threshold failed club=%s', club_id)
return DEFAULT_YAJIN_THRESHOLD
def set_dashou_chat_allow_without_member(club, enabled: bool) -> None:
cfg = dict(getattr(club, 'config_json', None) or {})
cfg[ALLOW_WITHOUT_MEMBER_KEY] = bool(enabled)
club.config_json = cfg
def set_dashou_chat_yajin_threshold(club, threshold) -> Decimal:
try:
val = Decimal(str(threshold))
except (InvalidOperation, TypeError, ValueError):
val = DEFAULT_YAJIN_THRESHOLD
if val < 0:
val = Decimal('0')
cfg = dict(getattr(club, 'config_json', None) or {})
cfg[YAJIN_THRESHOLD_KEY] = float(val)
club.config_json = cfg
return val
+60
View File
@@ -0,0 +1,60 @@
"""俱乐部:购买押金是否把打手积分补到 10(config_json)。
默认关闭(与现网一致:押金履约不加积分)。
开启后:押金到账时若 jifen < 10 则补到 10;已是 ≥10 不变。
"""
import logging
logger = logging.getLogger(__name__)
CONFIG_KEY = 'yajin_purchase_fill_jifen'
JIFEN_TARGET = 10
def is_yajin_purchase_fill_jifen(club_id: str) -> bool:
cid = (club_id or '').strip()
if not cid:
return False
try:
from jituan.models import Club
club = Club.query.filter(club_id=cid).first()
if not club and cid != cid.lower():
club = Club.query.filter(club_id=cid.lower()).first()
if not club:
return False
cfg = club.config_json or {}
raw = cfg.get(CONFIG_KEY, False)
if isinstance(raw, str):
return raw.strip().lower() in ('1', 'true', 'yes', 'on')
return bool(raw)
except Exception:
logger.exception('read yajin_purchase_fill_jifen failed club=%s', cid)
return False
def set_yajin_purchase_fill_jifen(club, enabled: bool) -> None:
cfg = dict(getattr(club, 'config_json', None) or {})
cfg[CONFIG_KEY] = bool(enabled)
club.config_json = cfg
def maybe_fill_jifen_after_yajin(dashou, club_id: str) -> bool:
"""
若俱乐部开启「押金送积分」且当前积分 < 10,补到 10。
调用方须已对 dashou 加锁;返回是否改动了积分。
"""
if not is_yajin_purchase_fill_jifen(club_id):
return False
try:
cur = int(getattr(dashou, 'jifen', 0) or 0)
except (TypeError, ValueError):
cur = 0
if cur >= JIFEN_TARGET:
return False
dashou.jifen = JIFEN_TARGET
dashou.save(update_fields=['jifen'])
logger.info(
'yajin fill jifen club=%s uid=%s %s->%s',
club_id, getattr(getattr(dashou, 'user', None), 'UserUID', ''), cur, JIFEN_TARGET,
)
return True
+42 -50
View File
@@ -631,17 +631,15 @@ class ClubManageView(APIView):
}
def _serialize(self, club, full=False):
from jituan.services.default_invite import (
get_club_default_dashou_invite,
get_club_default_dashou_invite_raw,
get_club_fallback_dashou_invite,
)
from jituan.services.default_invite import get_club_default_dashou_invite
from jituan.services.force_phone_auth import is_club_force_phone_auth
from jituan.services.dashou_chat_gate import (
get_dashou_chat_yajin_threshold,
is_dashou_chat_allow_without_member,
)
from jituan.services.yajin_jifen_switch import is_yajin_purchase_fill_jifen
# 后台表单必须展示「库里真实保存值」,不能用带回落的 effective,
# 否则一保存就会把系统固定回落码写进 config_json,看起来像改不动。
invite_raw = get_club_default_dashou_invite_raw(club)
invite_fallback = get_club_fallback_dashou_invite(club.club_id)
cfg_json = club.config_json or {}
base = {
'club_id': club.club_id,
'name': club.name,
@@ -656,10 +654,11 @@ class ClubManageView(APIView):
'goeasy_appkey': club.goeasy_appkey,
'template_id': club.template_id,
'sort_order': club.sort_order,
'default_dashou_invite': invite_raw,
'default_dashou_invite_effective': get_club_default_dashou_invite(club.club_id),
'default_dashou_invite_fallback': invite_fallback,
'default_dashou_invite': get_club_default_dashou_invite(club.club_id),
'force_phone_auth': is_club_force_phone_auth(club.club_id),
'dashou_chat_allow_without_member': is_dashou_chat_allow_without_member(club.club_id),
'dashou_chat_yajin_threshold': float(get_dashou_chat_yajin_threshold(club.club_id)),
'yajin_purchase_fill_jifen': is_yajin_purchase_fill_jifen(club.club_id),
}
if full:
mch_key = self._effective_mch_key(club)
@@ -679,7 +678,7 @@ class ClubManageView(APIView):
'encoding_aes_key': club.encoding_aes_key,
'goeasy_secret': club.goeasy_secret,
'template_max_per_minute': club.template_max_per_minute,
'default_dashou_invite_raw': invite_raw,
'default_dashou_invite_raw': (cfg_json.get('default_dashou_invite') or '').strip(),
})
return base
@@ -726,15 +725,15 @@ class ClubManageView(APIView):
return Response({'code': 400, 'msg': 'mini_pay_channel 仅支持 wechat/fubei'})
setattr(club, field, val)
update_fields.append(field)
# config_json 相关字段按顺序合并到同一份 dict,避免互相覆盖
touched_config = False
if 'mch_key' in request.data:
self._sync_mch_key_to_config_json(club, request.data.get('mch_key'))
touched_config = True
if 'config_json' not in update_fields:
update_fields.append('config_json')
if 'default_dashou_invite' in request.data:
from jituan.services.default_invite import set_club_default_dashou_invite
set_club_default_dashou_invite(club, request.data.get('default_dashou_invite'))
touched_config = True
if 'config_json' not in update_fields:
update_fields.append('config_json')
if 'force_phone_auth' in request.data:
from jituan.services.force_phone_auth import set_club_force_phone_auth
raw = request.data.get('force_phone_auth')
@@ -743,15 +742,35 @@ class ClubManageView(APIView):
else:
enabled = bool(raw)
set_club_force_phone_auth(club, enabled)
touched_config = True
if touched_config:
# 再赋一次新 dict,确保 JSONField 一定落库
club.config_json = dict(club.config_json or {})
if 'config_json' not in update_fields:
update_fields.append('config_json')
if 'dashou_chat_allow_without_member' in request.data:
from jituan.services.dashou_chat_gate import set_dashou_chat_allow_without_member
raw = request.data.get('dashou_chat_allow_without_member')
if isinstance(raw, str):
enabled = raw.strip().lower() in ('1', 'true', 'yes', 'on')
else:
enabled = bool(raw)
set_dashou_chat_allow_without_member(club, enabled)
if 'config_json' not in update_fields:
update_fields.append('config_json')
if 'dashou_chat_yajin_threshold' in request.data:
from jituan.services.dashou_chat_gate import set_dashou_chat_yajin_threshold
set_dashou_chat_yajin_threshold(club, request.data.get('dashou_chat_yajin_threshold'))
if 'config_json' not in update_fields:
update_fields.append('config_json')
if 'yajin_purchase_fill_jifen' in request.data:
from jituan.services.yajin_jifen_switch import set_yajin_purchase_fill_jifen
raw = request.data.get('yajin_purchase_fill_jifen')
if isinstance(raw, str):
enabled = raw.strip().lower() in ('1', 'true', 'yes', 'on')
else:
enabled = bool(raw)
set_yajin_purchase_fill_jifen(club, enabled)
if 'config_json' not in update_fields:
update_fields.append('config_json')
if update_fields:
club.save(update_fields=update_fields)
club.refresh_from_db()
return Response({
'code': 0,
'msg': '保存成功',
@@ -779,34 +798,7 @@ class ClubManageView(APIView):
'data': self._serialize(club, full=True),
})
if action in ('seed_invite_status', 'seed_invite'):
from jituan.services.club_seed_invite import (
club_invite_seed_status,
ensure_club_seed_invite,
)
if action == 'seed_invite_status':
return Response({
'code': 0,
'data': club_invite_seed_status(club_id),
})
result = ensure_club_seed_invite(club_id)
if not result.get('ok'):
return Response({'code': 400, 'msg': result.get('msg') or '种子失败'})
# 刷新默认邀请码到序列化结果
club = Club.query.filter(club_id=club_id).first()
return Response({
'code': 0,
'msg': result.get('msg') or 'ok',
'data': {
**result,
'club': self._serialize(club, full=True) if club else None,
},
})
return Response({
'code': 400,
'msg': '无效 action,支持 list/get/update/create/seed_invite/seed_invite_status',
})
return Response({'code': 400, 'msg': '无效 action,支持 list/get/update/create'})
except Exception:
logger.exception('ClubManageView 异常')
return Response({'code': 99, 'msg': '俱乐部配置操作失败'}, status=500)
+10
View File
@@ -1094,9 +1094,19 @@ class DsqrgmdhView(APIView):
dashou = UserDashou.objects.select_for_update().get(user=user)
dashou.yajin += yajin_jine
dashou.save(update_fields=['yajin'])
try:
from jituan.services.yajin_jifen_switch import maybe_fill_jifen_after_yajin
from jituan.services.club_user import get_user_club_id
maybe_fill_jifen_after_yajin(
dashou,
getattr(czjilu, 'club_id', None) or get_user_club_id(user) or '',
)
except Exception as jifen_exc:
logger.error('余额抵扣押金补积分失败: %s', jifen_exc, exc_info=True)
czjilu.zhuangtai = MEMBER_PAID_STATUS
czjilu.save(update_fields=['zhuangtai'])
response_data['yajin'] = dashou.yajin
response_data['jifen'] = dashou.jifen
except UserDashou.DoesNotExist:
setattr(locked_profile, identity['balance_field'], current_balance)
locked_profile.save(update_fields=[identity['balance_field']])
+22 -2
View File
@@ -1140,8 +1140,28 @@ class DashouJianquanView(APIView):
'msg': '您尚未注册打手身份,请先注册'
})
# ========== 3. 条件一:押金 >= 5 元 ==========
if dashou.yajin and dashou.yajin >= 5:
from jituan.services.club_user import get_user_club_id
from jituan.services.dashou_chat_gate import (
get_dashou_chat_yajin_threshold,
is_dashou_chat_allow_without_member,
)
club_id = get_user_club_id(user) or ''
# 俱乐部开启「不充会员也可用聊天」→ 直接通过
if is_dashou_chat_allow_without_member(club_id):
return Response({
'code': 0,
'allow': 1,
'msg': '鉴权通过(俱乐部允许无会员聊天)'
})
# ========== 3. 押金达到俱乐部门槛 ==========
threshold = get_dashou_chat_yajin_threshold(club_id)
try:
yajin = Decimal(str(getattr(dashou, 'yajin', 0) or 0))
except Exception:
yajin = Decimal('0')
if yajin >= threshold:
return Response({
'code': 0,
'allow': 1,