diff --git a/jituan/services/czjilu_fulfill.py b/jituan/services/czjilu_fulfill.py index ec9d5e6..2f647a9 100644 --- a/jituan/services/czjilu_fulfill.py +++ b/jituan/services/czjilu_fulfill.py @@ -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']) diff --git a/jituan/services/dashou_chat_gate.py b/jituan/services/dashou_chat_gate.py new file mode 100644 index 0000000..2460cd8 --- /dev/null +++ b/jituan/services/dashou_chat_gate.py @@ -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 diff --git a/jituan/services/yajin_jifen_switch.py b/jituan/services/yajin_jifen_switch.py new file mode 100644 index 0000000..23d3eec --- /dev/null +++ b/jituan/services/yajin_jifen_switch.py @@ -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 diff --git a/jituan/views.py b/jituan/views.py index 03ba0a4..3c9e7a1 100644 --- a/jituan/views.py +++ b/jituan/views.py @@ -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) diff --git a/products/views/chongzhi_payment.py b/products/views/chongzhi_payment.py index fe3ef0d..6e514c1 100644 --- a/products/views/chongzhi_payment.py +++ b/products/views/chongzhi_payment.py @@ -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']]) diff --git a/users/views/user_info.py b/users/views/user_info.py index 2a9f6da..7364db6 100644 --- a/users/views/user_info.py +++ b/users/views/user_info.py @@ -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,