diff --git a/gongdan/migrations/0003_club_human_kefu.py b/gongdan/migrations/0003_club_human_kefu.py new file mode 100644 index 0000000..e21957a --- /dev/null +++ b/gongdan/migrations/0003_club_human_kefu.py @@ -0,0 +1,26 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('gongdan', '0002_gongdan_target_intervene'), + ] + + operations = [ + migrations.AddField( + model_name='clubgongdanconfig', + name='human_kefu_wechat', + field=models.CharField(blank=True, default='', max_length=64, verbose_name='人工客服微信号'), + ), + migrations.AddField( + model_name='clubgongdanconfig', + name='human_kefu_link', + field=models.CharField(blank=True, default='', max_length=512, verbose_name='人工客服链接'), + ), + migrations.AddField( + model_name='clubgongdanconfig', + name='human_kefu_tip', + field=models.CharField(blank=True, default='', max_length=120, verbose_name='人工客服提示文案'), + ), + ] diff --git a/gongdan/models.py b/gongdan/models.py index a28e217..d5e79fe 100644 --- a/gongdan/models.py +++ b/gongdan/models.py @@ -85,6 +85,10 @@ class ClubGongdanConfig(QModel): club_id = models.CharField(max_length=16, unique=True, db_index=True, verbose_name='俱乐部') # 未结清被投诉/牵连时禁止提现 complaint_withdraw_block = models.BooleanField(default=False, verbose_name='投诉拦截提现') + # 投诉页人工客服(分俱乐部配置;有则前端展示) + human_kefu_wechat = models.CharField(max_length=64, blank=True, default='', verbose_name='人工客服微信号') + human_kefu_link = models.CharField(max_length=512, blank=True, default='', verbose_name='人工客服链接') + human_kefu_tip = models.CharField(max_length=120, blank=True, default='', verbose_name='人工客服提示文案') CreateTime = models.DateTimeField(auto_now_add=True) UpdateTime = models.DateTimeField(auto_now=True) diff --git a/gongdan/services.py b/gongdan/services.py index e265c9e..47f259d 100644 --- a/gongdan/services.py +++ b/gongdan/services.py @@ -58,11 +58,25 @@ def ensure_club_gongdan_config(club_id: str, *, withdraw_block: bool = False) -> def get_club_gongdan_config(club_id: str) -> dict: cid = (club_id or '').strip() if not cid: - return {'club_id': '', 'complaint_withdraw_block': False} + return { + 'club_id': '', + 'complaint_withdraw_block': False, + 'human_kefu_wechat': '', + 'human_kefu_link': '', + 'human_kefu_tip': '', + 'human_kefu_available': False, + } row = ClubGongdanConfig.query.filter(club_id=cid).first() + wechat = ((row.human_kefu_wechat if row else '') or '').strip() + link = ((row.human_kefu_link if row else '') or '').strip() + tip = ((row.human_kefu_tip if row else '') or '').strip() return { 'club_id': cid, 'complaint_withdraw_block': bool(row and row.complaint_withdraw_block), + 'human_kefu_wechat': wechat, + 'human_kefu_link': link, + 'human_kefu_tip': tip or ('添加人工客服微信咨询' if wechat else ('点击联系人工客服' if link else '')), + 'human_kefu_available': bool(wechat or link), } @@ -78,6 +92,107 @@ def set_complaint_withdraw_block(club_id: str, enabled: bool) -> dict: return get_club_gongdan_config(cid) +def update_club_gongdan_config(club_id: str, **fields) -> dict: + """更新俱乐部工单配置(提现拦截 / 人工客服联系方式)。""" + cid = (club_id or '').strip() + if not cid: + raise GongdanError('俱乐部不能为空', 'need_club') + row = ensure_club_gongdan_config(cid) + dirty = [] + if 'complaint_withdraw_block' in fields and fields['complaint_withdraw_block'] is not None: + val = bool(fields['complaint_withdraw_block']) + if row.complaint_withdraw_block != val: + row.complaint_withdraw_block = val + dirty.append('complaint_withdraw_block') + if 'human_kefu_wechat' in fields and fields['human_kefu_wechat'] is not None: + val = str(fields['human_kefu_wechat'] or '').strip()[:64] + if (row.human_kefu_wechat or '') != val: + row.human_kefu_wechat = val + dirty.append('human_kefu_wechat') + if 'human_kefu_link' in fields and fields['human_kefu_link'] is not None: + val = str(fields['human_kefu_link'] or '').strip()[:512] + if (row.human_kefu_link or '') != val: + row.human_kefu_link = val + dirty.append('human_kefu_link') + if 'human_kefu_tip' in fields and fields['human_kefu_tip'] is not None: + val = str(fields['human_kefu_tip'] or '').strip()[:120] + if (row.human_kefu_tip or '') != val: + row.human_kefu_tip = val + dirty.append('human_kefu_tip') + if dirty: + dirty.append('UpdateTime') + row.save(update_fields=dirty) + return get_club_gongdan_config(cid) + + +def resolve_inviter_complaint_target(yonghuid: str, leixing: int) -> dict: + """ + 管事/组长投诉只能针对本人邀请人,禁止搜索乱选: + - 管事投诉:打手的 yaoqingren,且对方须是管事 + - 组长投诉:管事的 yaoqingren,且对方须是组长 + 无邀请人则不可投诉。 + """ + uid = (yonghuid or '').strip() + if not uid: + raise GongdanError('请先登录', 'need_login') + + from users.models import UserDashou, UserGuanshi, UserZuzhang + + if leixing == TYPE_GUANSHI: + ds = UserDashou.query.filter(user__UserUID=uid).select_related('user').first() + if not ds: + raise GongdanError('仅打手可投诉自己的管事', 'need_dashou') + inviter = (ds.yaoqingren or '').strip() + if not inviter: + raise GongdanError('你没有邀请人,无法投诉管事', 'no_inviter') + gs = UserGuanshi.query.filter(user__UserUID=inviter, zhuangtai=1).select_related('user').first() + if not gs: + raise GongdanError('你的邀请人不是有效管事,无法投诉', 'inviter_not_guanshi') + return { + 'role': TARGET_ROLE_GUANSHI, + 'role_label': TARGET_ROLE_LABELS[TARGET_ROLE_GUANSHI], + 'user_id': inviter, + 'nicheng': _user_nicheng(inviter), + 'can_complain': True, + } + + if leixing == TYPE_ZUZHANG: + gs = UserGuanshi.query.filter(user__UserUID=uid).select_related('user').first() + if not gs: + raise GongdanError('仅管事可投诉自己的组长', 'need_guanshi') + inviter = (gs.yaoqingren or '').strip() + if not inviter: + raise GongdanError('你没有邀请人,无法投诉组长', 'no_inviter') + zz = UserZuzhang.query.filter(user__UserUID=inviter, zhuangtai=1).select_related('user').first() + if not zz: + raise GongdanError('你的邀请人不是有效组长,无法投诉', 'inviter_not_zuzhang') + return { + 'role': TARGET_ROLE_ZUZHANG, + 'role_label': TARGET_ROLE_LABELS[TARGET_ROLE_ZUZHANG], + 'user_id': inviter, + 'nicheng': _user_nicheng(inviter), + 'can_complain': True, + } + + raise GongdanError('仅管事/组长投诉需要邀请人', 'bad_type') + + +def peek_inviter_complaint_target(yonghuid: str, leixing: int) -> dict: + """前端展示用:失败时返回 can_complain=False + 原因,不抛错。""" + try: + return resolve_inviter_complaint_target(yonghuid, leixing) + except GongdanError as e: + role = TARGET_ROLE_GUANSHI if leixing == TYPE_GUANSHI else TARGET_ROLE_ZUZHANG + return { + 'role': role, + 'role_label': TARGET_ROLE_LABELS.get(role, ''), + 'user_id': '', + 'nicheng': '', + 'can_complain': False, + 'reason': str(e), + } + + def _user_nicheng(uid: str) -> str: """展示昵称:打手 nicheng → 老板 nickname → 用户{UID}。不把 UID 当昵称展示。""" uid = (uid or '').strip() @@ -103,32 +218,21 @@ def _user_nicheng(uid: str) -> str: return '用户' -def _resolve_target(leixing, guanshi_id=None, target_role=None, target_user_id=None): - """归一化被投诉对象;兼容旧 guanshi_id。管事/组长投诉必须选人。""" +def _resolve_target(leixing, guanshi_id=None, target_role=None, target_user_id=None, *, yonghuid=None): + """归一化被投诉对象。管事/组长投诉强制本人邀请人,忽略前端传入的选人。""" role = (target_role or '').strip().lower() tid = (target_user_id or '').strip() gid = (guanshi_id or '').strip() or None if leixing == TYPE_GUANSHI: - role = TARGET_ROLE_GUANSHI - tid = tid or (gid or '') - if not tid: - raise GongdanError('请选择被投诉的管事', 'need_guanshi') - from users.models import UserGuanshi - if not UserGuanshi.query.filter(user__UserUID=tid).exists(): - if not UserGuanshi.objects.filter(user__UserUID=tid).exists(): - raise GongdanError('管事不存在,请重新选择', 'guanshi_not_found') - return role, tid, tid + info = resolve_inviter_complaint_target(yonghuid, TYPE_GUANSHI) + tid = info['user_id'] + return TARGET_ROLE_GUANSHI, tid, tid if leixing == TYPE_ZUZHANG: - role = TARGET_ROLE_ZUZHANG - if not tid: - raise GongdanError('请选择被投诉的组长', 'need_zuzhang') - from users.models import UserZuzhang - if not UserZuzhang.query.filter(user__UserUID=tid).exists(): - if not UserZuzhang.objects.filter(user__UserUID=tid).exists(): - raise GongdanError('组长不存在,请重新选择', 'zuzhang_not_found') - return role, tid, None + info = resolve_inviter_complaint_target(yonghuid, TYPE_ZUZHANG) + tid = info['user_id'] + return TARGET_ROLE_ZUZHANG, tid, None if role and tid: if role not in TARGET_ROLE_LABELS and role not in ('order', 'recharge', 'penalty'): @@ -388,65 +492,34 @@ def check_complaint_blocks_withdraw(user_main, club_id=None): return True, '', {} -def search_complaint_targets(*, role, keyword='', club_id=None, page=1, page_size=20): - """按昵称/UID 搜管事或组长,供投诉选人(返回 uid 仅供提交,前端展示昵称)。""" +def search_complaint_targets(*, role, keyword='', club_id=None, page=1, page_size=20, yonghuid=None): + """已废弃搜索选人:仅返回本人邀请人(防乱投诉)。keyword/club 忽略。""" role = (role or '').strip().lower() - keyword = (keyword or '').strip() - page = max(int(page or 1), 1) - page_size = min(max(int(page_size or 20), 1), 50) - cid = (club_id or '').strip() - - from users.models import UserGuanshi, UserZuzhang, UserBoss - - rows = [] if role == TARGET_ROLE_GUANSHI: - qs = UserGuanshi.query.filter(zhuangtai=1).select_related('user') - if cid: - qs = qs.filter(user__ClubID=cid) - if keyword: - boss_uids = list( - UserBoss.query.filter(nickname__icontains=keyword).values_list('user__UserUID', flat=True)[:80] - ) - qs = qs.filter(Q(user__UserUID__icontains=keyword) | Q(user__UserUID__in=boss_uids)) - qs = qs.order_by('-id') - total = qs.count() - start = (page - 1) * page_size - for g in qs[start:start + page_size]: - uid = getattr(getattr(g, 'user', None), 'UserUID', None) - if not uid: - continue - rows.append({ - 'user_id': str(uid), - 'nicheng': _user_nicheng(str(uid)), - 'role': TARGET_ROLE_GUANSHI, - 'role_label': TARGET_ROLE_LABELS[TARGET_ROLE_GUANSHI], - }) + leixing = TYPE_GUANSHI elif role == TARGET_ROLE_ZUZHANG: - qs = UserZuzhang.query.filter(zhuangtai=1).select_related('user') - if cid: - qs = qs.filter(user__ClubID=cid) - if keyword: - boss_uids = list( - UserBoss.query.filter(nickname__icontains=keyword).values_list('user__UserUID', flat=True)[:80] - ) - qs = qs.filter(Q(user__UserUID__icontains=keyword) | Q(user__UserUID__in=boss_uids)) - qs = qs.order_by('-id') - total = qs.count() - start = (page - 1) * page_size - for z in qs[start:start + page_size]: - uid = getattr(getattr(z, 'user', None), 'UserUID', None) - if not uid: - continue - rows.append({ - 'user_id': str(uid), - 'nicheng': _user_nicheng(str(uid)), - 'role': TARGET_ROLE_ZUZHANG, - 'role_label': TARGET_ROLE_LABELS[TARGET_ROLE_ZUZHANG], - }) + leixing = TYPE_ZUZHANG else: raise GongdanError('role 须为 guanshi 或 zuzhang', 'bad_role') - - return {'list': rows, 'total': total, 'page': page, 'page_size': page_size, 'role': role} + info = peek_inviter_complaint_target(yonghuid, leixing) + rows = [] + if info.get('can_complain') and info.get('user_id'): + rows.append({ + 'user_id': info['user_id'], + 'nicheng': info.get('nicheng') or '', + 'role': info.get('role') or role, + 'role_label': info.get('role_label') or '', + }) + return { + 'list': rows, + 'total': len(rows), + 'page': 1, + 'page_size': 1, + 'role': role, + 'can_complain': bool(info.get('can_complain')), + 'reason': info.get('reason') or '', + 'invite_only': True, + } def list_gongdan_for_user(yonghuid, *, scope='created', club_id=None, page=1, page_size=20): @@ -544,7 +617,11 @@ def create_gongdan( leixing, order_id, chongzhi_id, fadan_id, guanshi_id or target_user_id, ) t_role, t_uid, guanshi_id = _resolve_target( - leixing, guanshi_id=guanshi_id, target_role=target_role, target_user_id=target_user_id or guanshi_id, + leixing, + guanshi_id=guanshi_id, + target_role=target_role, + target_user_id=target_user_id or guanshi_id, + yonghuid=yonghuid, ) gid = _gen_gongdan_id() diff --git a/gongdan/urls.py b/gongdan/urls.py index cef3362..edcc1f4 100644 --- a/gongdan/urls.py +++ b/gongdan/urls.py @@ -12,6 +12,7 @@ from gongdan.views import ( GongdanApplyInterveneView, GongdanWithdrawCheckView, GongdanSearchTargetView, + GongdanMyInviterTargetView, GongdanMetaView, ChongzhiRecordListView, GongdanAdminListView, @@ -31,6 +32,7 @@ urlpatterns = [ path('apply-intervene/', GongdanApplyInterveneView.as_view(), name='申请平台介入'), path('withdraw-block-check/', GongdanWithdrawCheckView.as_view(), name='提现投诉拦截检查'), path('search-target/', GongdanSearchTargetView.as_view(), name='搜索被投诉对象'), + path('my-inviter-target/', GongdanMyInviterTargetView.as_view(), name='本人邀请人投诉对象'), path('meta/', GongdanMetaView.as_view(), name='工单枚举'), path('chongzhi-list/', ChongzhiRecordListView.as_view(), name='我的充值记录'), path('admin/list/', GongdanAdminListView.as_view(), name='客服工单列表'), diff --git a/gongdan/views.py b/gongdan/views.py index 8014d77..74cf7a3 100644 --- a/gongdan/views.py +++ b/gongdan/views.py @@ -11,6 +11,7 @@ from rest_framework import status from gongdan.constants import ( MAX_IMAGES, OSS_PREFIX, PERM_VIEW, PERM_HANDLE, TYPE_LABELS, STATUS_LABELS, + TYPE_GUANSHI, TYPE_ZUZHANG, ) from gongdan.models import Gongdan from gongdan.services import ( @@ -29,6 +30,8 @@ from gongdan.services import ( search_complaint_targets, get_club_gongdan_config, set_complaint_withdraw_block, + update_club_gongdan_config, + peek_inviter_complaint_target, ) from utils.oss_utils import upload_to_oss, validate_image @@ -284,7 +287,7 @@ class GongdanWithdrawCheckView(APIView): class GongdanSearchTargetView(APIView): - """搜索被投诉管事/组长。POST /gongdan/search-target/""" + """本人邀请人(已禁止全库搜索)。POST /gongdan/search-target/""" permission_classes = [IsAuthenticated] def post(self, request): @@ -296,21 +299,48 @@ class GongdanSearchTargetView(APIView): club_id=_resolve_club(request), page=d.get('page') or 1, page_size=d.get('page_size') or 20, + yonghuid=request.user.UserUID, ) return _ok(data) except GongdanError as e: return _err(str(e)) +class GongdanMyInviterTargetView(APIView): + """查询本人可投诉的邀请人。POST /gongdan/my-inviter-target/ body: {leixing:4|6}""" + permission_classes = [IsAuthenticated] + + def post(self, request): + try: + leixing = int(request.data.get('leixing') or 0) + except (TypeError, ValueError): + return _err('投诉类型无效') + if leixing not in (TYPE_GUANSHI, TYPE_ZUZHANG): + return _err('仅管事/组长投诉需要邀请人') + return _ok(peek_inviter_complaint_target(request.user.UserUID, leixing)) + + class GongdanMetaView(APIView): - """类型/状态枚举,供前端展示。GET /gongdan/meta/""" + """类型/状态枚举 + 人工客服联系方式。GET /gongdan/meta/""" permission_classes = [IsAuthenticated] def get(self, request): + club_id = _resolve_club(request) + cfg = get_club_gongdan_config(club_id) if club_id else get_club_gongdan_config('') return _ok({ 'types': [{'value': k, 'label': v} for k, v in TYPE_LABELS.items()], 'statuses': [{'value': k, 'label': v} for k, v in STATUS_LABELS.items()], 'max_images': MAX_IMAGES, + 'human_kefu': { + 'wechat': cfg.get('human_kefu_wechat') or '', + 'link': cfg.get('human_kefu_link') or '', + 'tip': cfg.get('human_kefu_tip') or '', + 'available': bool(cfg.get('human_kefu_available')), + }, + 'inviter_targets': { + 'guanshi': peek_inviter_complaint_target(request.user.UserUID, TYPE_GUANSHI), + 'zuzhang': peek_inviter_complaint_target(request.user.UserUID, TYPE_ZUZHANG), + }, }) @@ -516,9 +546,9 @@ class GongdanAdminHandleView(APIView): class GongdanAdminClubConfigView(APIView): """ - 当前俱乐部工单开关。 + 当前俱乐部工单开关 + 人工客服联系方式。 GET/POST /gongdan/admin/club-config/ - 字段:complaint_withdraw_block(未结清投诉/牵连时禁止提现) + 字段:complaint_withdraw_block / human_kefu_wechat / human_kefu_link / human_kefu_tip """ permission_classes = [IsAuthenticated] @@ -545,15 +575,25 @@ class GongdanAdminClubConfigView(APIView): club_id = self._club_id(request) if not club_id: return _err('请先选择俱乐部') - raw = request.data.get('complaint_withdraw_block') - if raw is None: - return _err('缺少 complaint_withdraw_block') - if isinstance(raw, str): - enabled = raw.strip().lower() in ('1', 'true', 'yes', 'on') - else: - enabled = bool(raw) + d = request.data or {} + patch = {} + if 'complaint_withdraw_block' in d: + raw = d.get('complaint_withdraw_block') + if isinstance(raw, str): + patch['complaint_withdraw_block'] = raw.strip().lower() in ('1', 'true', 'yes', 'on') + else: + patch['complaint_withdraw_block'] = bool(raw) + for key in ('human_kefu_wechat', 'human_kefu_link', 'human_kefu_tip'): + if key in d: + patch[key] = d.get(key) + if not patch: + return _err('缺少可保存字段') try: - data = set_complaint_withdraw_block(club_id, enabled) + # 兼容旧前端只传 withdraw 开关 + if list(patch.keys()) == ['complaint_withdraw_block']: + data = set_complaint_withdraw_block(club_id, patch['complaint_withdraw_block']) + else: + data = update_club_gongdan_config(club_id, **patch) return _ok(data, '已保存') except GongdanError as e: return _err(str(e)) diff --git a/jituan/services/deal_stats.py b/jituan/services/deal_stats.py index c1087cd..6b27cb5 100644 --- a/jituan/services/deal_stats.py +++ b/jituan/services/deal_stats.py @@ -206,6 +206,75 @@ def serialize_merchant_stat(row) -> dict: } +def pool_merchant_stat_payload(row) -> dict | None: + """ + 抢单池展示用:无订单样本则不返回; + 有订单则返回成交率/罚款率;平均成交时长仅有结算样本时返回。 + """ + if not row: + return None + n = int(row.order_count or 0) + if n <= 0: + return None + full = serialize_merchant_stat(row) + out = { + 'deal_rate': full['deal_rate'], + 'fine_rate': full['fine_rate'], + 'deal_rate_text': f"{round(full['deal_rate'] * 100, 1)}%", + 'fine_rate_text': f"{round(full['fine_rate'] * 100, 1)}%", + } + sc = int(full.get('settle_duration_sample_count') or 0) + if sc > 0 and full.get('avg_deal_hours'): + h = float(full['avg_deal_hours']) + out['avg_deal_hours'] = h + if h < 1: + out['avg_deal_hours_text'] = f'{max(1, int(round(h * 60)))}分钟' + else: + out['avg_deal_hours_text'] = f'{round(h, 1)}小时' + return out + + +def batch_pool_merchant_stats(club_id: str, merchant_uids, order_club_map=None) -> dict: + """ + 批量取商家抢单池指标。 + order_club_map: {merchant_uid: order_club_id} 优先用订单所属俱乐部查指标。 + 返回 {merchant_uid: payload} + """ + from jituan.models import MerchantDealStat + uids = [str(u).strip() for u in (merchant_uids or []) if u and str(u).strip()] + if not uids: + return {} + default_club = (club_id or '').strip() + club_ids = {default_club} if default_club else set() + if order_club_map: + for cid in order_club_map.values(): + c = (cid or '').strip() + if c: + club_ids.add(c) + if not club_ids: + return {} + rows = list( + MerchantDealStat.query.filter( + club_id__in=list(club_ids), + merchant_uid__in=uids, + ) + ) + by_key = {(r.club_id, r.merchant_uid): r for r in rows} + out = {} + for uid in uids: + prefer = ((order_club_map or {}).get(uid) or default_club or '').strip() + row = by_key.get((prefer, uid)) + if not row: + for cid in club_ids: + row = by_key.get((cid, uid)) + if row: + break + payload = pool_merchant_stat_payload(row) + if payload: + out[uid] = payload + return out + + def serialize_dashou_stat(row) -> dict: n = int(row.order_count or 0) c = int(row.completed_count or 0) diff --git a/orders/views/dashou.py b/orders/views/dashou.py index 6017c58..e8c18e9 100644 --- a/orders/views/dashou.py +++ b/orders/views/dashou.py @@ -163,7 +163,7 @@ class DashouDingdanHuoquView(APIView): 'AssignedID', 'AssignedResponse', 'AssignedResponseAt', 'AssignedLaterNote', 'ProductTypeID', 'ImageURL', 'GrabRequirement', 'MembershipID', 'CommissionReq', 'Description', 'Remark', 'CreateTime', - 'MerchantNickname' + 'MerchantNickname', 'ClubID', ) # 10. 转换为列表 @@ -308,6 +308,28 @@ class DashouDingdanHuoquView(APIView): identity_tag_map = batch_identity_tags(club_id, identity_pairs) if identity_pairs else {} + # --- 商家成交指标(有样本才返回) --- + merchant_stat_map = {} + if merchant_uids: + try: + from jituan.services.deal_stats import batch_pool_merchant_stats + order_club_by_merchant = {} + for item in dingdan_list: + if item['Platform'] != 2: + continue + oid = item['OrderID'] + sj = shangjia_id_map.get(oid) + if not sj: + continue + ocid = (item.get('ClubID') or club_id or '').strip() + if sj not in order_club_by_merchant and ocid: + order_club_by_merchant[sj] = ocid + merchant_stat_map = batch_pool_merchant_stats( + club_id, merchant_uids, order_club_map=order_club_by_merchant, + ) + except Exception as e: + logger.error(f"批量商家成交指标失败: {e}", exc_info=True) + # 12. 格式化返回数据 formatted_list = [] for order in dingdan_list: @@ -320,7 +342,7 @@ class DashouDingdanHuoquView(APIView): order, yonghuid, club_id=club_id, user_clumber=user_clumber, ) ) - formatted_list.append({ + row = { 'dingdan_id': order['OrderID'], 'zhuangtai': order['Status'], 'pingtai': order['Platform'], @@ -353,7 +375,10 @@ class DashouDingdanHuoquView(APIView): 'shangjia_identity_biaoqian': identity_tag_map.get((sj_uid, SHENFEN_SHANGJIA), []) if sj_uid else [], 'zhiding_identity_biaoqian': identity_tag_map.get((zid, SHENFEN_DASHOU), []) if zid else [], 'shangjia_youzhi': bool(youzhi_map.get(sj_uid)) if sj_uid else False, - }) + } + if sj_uid and sj_uid in merchant_stat_map: + row['shangjia_deal_stat'] = merchant_stat_map[sj_uid] + formatted_list.append(row) has_more = (page * page_size) < total