feat: 打手/商家分端企业微信客服配置
This commit is contained in:
1 parent
29e77a796f
commit
b49da23bd7
8 files changed
+242
-8
No files matched your search
@@ -541,8 +541,11 @@ class KehuGetDingdanLianjieView(APIView):
|
||||
# ========== 新增:确定俱乐部配置 ==========
|
||||
# 默认头像(相对路径)
|
||||
default_avatar = 'a_long/morentouxiang.jpg'
|
||||
# 从 settings 获取配置
|
||||
kefu_link = getattr(settings, 'KEFU_LINK', '')
|
||||
# 商家端企微客服(库配置优先,空则 settings)
|
||||
from jituan.services.club_context import resolve_club_id_from_request
|
||||
from jituan.services.wecom_kefu import resolve_side_link
|
||||
club_id = resolve_club_id_from_request(request)
|
||||
kefu_link = resolve_side_link(club_id, 'merchant') or getattr(settings, 'KEFU_LINK', '')
|
||||
oss_url = getattr(settings, 'COS_DOMAIN', '')
|
||||
goeasy_appkey = getattr(settings, 'GOEASY_APPKEY', '')
|
||||
|
||||
|
||||
@@ -77,13 +77,16 @@ class GetDynamicConfigView(APIView):
|
||||
cos_region = getattr(settings, 'COS_REGION', 'ap-shanghai')
|
||||
goeasy_host = 'hangzhou.goeasy.io'
|
||||
goeasy_appkey = getattr(settings, 'GOEASY_APPKEY', '')
|
||||
kefu_link = getattr(settings, 'KEFU_LINK', '')
|
||||
kefu_enterprise_id = getattr(settings, 'KEFU_ENTERPRISE_ID', '')
|
||||
rule_img = get_tupianpeizhi_url(request, 1)
|
||||
avatar_img = get_tupianpeizhi_url(request, 2)
|
||||
morentouxiang = avatar_img or 'beijing/morentouxiang.jpg'
|
||||
dashouguize = rule_img or 'a_long/dashouguize.jpg'
|
||||
|
||||
from jituan.services.club_context import resolve_club_id_from_request
|
||||
from jituan.services.wecom_kefu import miniapp_kefu_payload
|
||||
club_id = resolve_club_id_from_request(request)
|
||||
kefu_payload = miniapp_kefu_payload(club_id)
|
||||
|
||||
# 构造返回数据
|
||||
data = {
|
||||
"code": 0,
|
||||
@@ -102,10 +105,7 @@ class GetDynamicConfigView(APIView):
|
||||
"morentouxiang": morentouxiang,
|
||||
"dashouguize": dashouguize
|
||||
},
|
||||
"kefu": {
|
||||
"link": kefu_link,
|
||||
"enterpriseId": kefu_enterprise_id
|
||||
},
|
||||
"kefu": kefu_payload,
|
||||
"miniappAssets": build_miniapp_assets_payload(request),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('jituan', '0028_deal_config_timeout_minutes'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ClubWecomKefuConfig',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('club_id', models.CharField(db_index=True, max_length=16, unique=True, verbose_name='俱乐部')),
|
||||
('dashou_link', models.CharField(blank=True, default='', max_length=512, verbose_name='打手端客服链接')),
|
||||
('dashou_corp_id', models.CharField(blank=True, default='', max_length=64, verbose_name='打手端企业ID')),
|
||||
('merchant_link', models.CharField(blank=True, default='', max_length=512, verbose_name='商家端客服链接')),
|
||||
('merchant_corp_id', models.CharField(blank=True, default='', max_length=64, verbose_name='商家端企业ID')),
|
||||
('updated_by', models.CharField(blank=True, default='', max_length=32)),
|
||||
('CreateTime', models.DateTimeField(auto_now_add=True)),
|
||||
('UpdateTime', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '企业微信客服配置',
|
||||
'db_table': 'club_wecom_kefu_config',
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -688,6 +688,25 @@ class ClubRoleAgreementSign(QModel):
|
||||
]
|
||||
|
||||
|
||||
class ClubWecomKefuConfig(QModel):
|
||||
"""
|
||||
俱乐部企业微信客服链接(小程序 wx.openCustomerServiceChat)。
|
||||
打手端 / 商家端各一套,互不影响;未配置时小程序可读 settings 兜底。
|
||||
"""
|
||||
club_id = models.CharField(max_length=16, unique=True, db_index=True, verbose_name='俱乐部')
|
||||
dashou_link = models.CharField(max_length=512, blank=True, default='', verbose_name='打手端客服链接')
|
||||
dashou_corp_id = models.CharField(max_length=64, blank=True, default='', verbose_name='打手端企业ID')
|
||||
merchant_link = models.CharField(max_length=512, blank=True, default='', verbose_name='商家端客服链接')
|
||||
merchant_corp_id = models.CharField(max_length=64, blank=True, default='', verbose_name='商家端企业ID')
|
||||
updated_by = models.CharField(max_length=32, blank=True, default='')
|
||||
CreateTime = models.DateTimeField(auto_now_add=True)
|
||||
UpdateTime = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
db_table = 'club_wecom_kefu_config'
|
||||
verbose_name = '企业微信客服配置'
|
||||
|
||||
|
||||
class ClubOrderDealConfig(QModel):
|
||||
"""俱乐部:成交指标统计开关 + 可控自动结算开关(默认全关)。"""
|
||||
club_id = models.CharField(max_length=16, unique=True, db_index=True, verbose_name='俱乐部')
|
||||
|
||||
@@ -111,6 +111,8 @@ KEFU_MENU_ROW_DEFS = [
|
||||
'perm_codes': ['phbj666', '8080a']},
|
||||
{'page_id': 'miniapp.recharge-copy', 'name': '充值页文案', 'path': '/miniapp/recharge-copy', 'parent_id': 'miniapp', 'sort_order': 119,
|
||||
'perm_codes': ['8080a']},
|
||||
{'page_id': 'miniapp.wecom-kefu', 'name': '企微客服链接', 'path': '/miniapp/wecom-kefu', 'parent_id': 'miniapp', 'sort_order': 1195,
|
||||
'perm_codes': ['8080a']},
|
||||
{'page_id': 'miniapp.role-agreement', 'name': '角色协议', 'path': '/miniapp/role-agreement', 'parent_id': 'miniapp', 'sort_order': 120,
|
||||
'perm_codes': ['xycfg001', '8080a']},
|
||||
{'page_id': 'shop', 'name': '店铺管理', 'path': '', 'parent_id': '', 'sort_order': 120},
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
"""俱乐部企业微信客服链接:打手端 / 商家端分端配置。"""
|
||||
from django.conf import settings
|
||||
|
||||
from jituan.models import ClubWecomKefuConfig
|
||||
|
||||
LINK_MAX = 512
|
||||
CORP_MAX = 64
|
||||
|
||||
|
||||
def _settings_fallback():
|
||||
return {
|
||||
'link': (getattr(settings, 'KEFU_LINK', '') or '').strip(),
|
||||
'enterpriseId': (getattr(settings, 'KEFU_ENTERPRISE_ID', '') or '').strip(),
|
||||
}
|
||||
|
||||
|
||||
def get_or_create_wecom_kefu(club_id: str):
|
||||
cid = (club_id or '').strip()
|
||||
if not cid:
|
||||
return None
|
||||
row = ClubWecomKefuConfig.query.filter(club_id=cid).first()
|
||||
if not row:
|
||||
row = ClubWecomKefuConfig(club_id=cid)
|
||||
row.save()
|
||||
return row
|
||||
|
||||
|
||||
def config_to_dict(row) -> dict:
|
||||
if not row:
|
||||
return {
|
||||
'club_id': '',
|
||||
'dashou': {'link': '', 'enterpriseId': ''},
|
||||
'merchant': {'link': '', 'enterpriseId': ''},
|
||||
'updated_by': '',
|
||||
}
|
||||
return {
|
||||
'club_id': row.club_id,
|
||||
'dashou': {
|
||||
'link': (row.dashou_link or '').strip(),
|
||||
'enterpriseId': (row.dashou_corp_id or '').strip(),
|
||||
},
|
||||
'merchant': {
|
||||
'link': (row.merchant_link or '').strip(),
|
||||
'enterpriseId': (row.merchant_corp_id or '').strip(),
|
||||
},
|
||||
'updated_by': (row.updated_by or '').strip(),
|
||||
}
|
||||
|
||||
|
||||
def _pick_side(payload: dict, key: str) -> dict:
|
||||
raw = payload.get(key)
|
||||
if isinstance(raw, dict):
|
||||
return raw
|
||||
return {}
|
||||
|
||||
|
||||
def save_wecom_kefu(club_id: str, data: dict, updated_by: str = '') -> dict:
|
||||
row = get_or_create_wecom_kefu(club_id)
|
||||
if not row:
|
||||
raise ValueError('缺少俱乐部')
|
||||
|
||||
dashou = _pick_side(data, 'dashou')
|
||||
merchant = _pick_side(data, 'merchant')
|
||||
|
||||
# 兼容扁平字段
|
||||
dashou_link = dashou.get('link', data.get('dashou_link'))
|
||||
dashou_corp = dashou.get('enterpriseId', data.get('dashou_corp_id', data.get('dashou_enterpriseId')))
|
||||
merchant_link = merchant.get('link', data.get('merchant_link'))
|
||||
merchant_corp = merchant.get(
|
||||
'enterpriseId',
|
||||
data.get('merchant_corp_id', data.get('merchant_enterpriseId')),
|
||||
)
|
||||
|
||||
if dashou_link is not None:
|
||||
row.dashou_link = str(dashou_link or '').strip()[:LINK_MAX]
|
||||
if dashou_corp is not None:
|
||||
row.dashou_corp_id = str(dashou_corp or '').strip()[:CORP_MAX]
|
||||
if merchant_link is not None:
|
||||
row.merchant_link = str(merchant_link or '').strip()[:LINK_MAX]
|
||||
if merchant_corp is not None:
|
||||
row.merchant_corp_id = str(merchant_corp or '').strip()[:CORP_MAX]
|
||||
if updated_by:
|
||||
row.updated_by = str(updated_by)[:32]
|
||||
row.save()
|
||||
return config_to_dict(row)
|
||||
|
||||
|
||||
def miniapp_kefu_payload(club_id: str) -> dict:
|
||||
"""
|
||||
小程序动态配置用。
|
||||
分端优先读库;空则回落 settings(兼容旧单链接)。
|
||||
顶层 link / enterpriseId 对齐打手端,兼容旧前端。
|
||||
"""
|
||||
fb = _settings_fallback()
|
||||
row = None
|
||||
cid = (club_id or '').strip()
|
||||
if cid:
|
||||
row = ClubWecomKefuConfig.query.filter(club_id=cid).first()
|
||||
|
||||
dashou_link = ((row.dashou_link if row else '') or '').strip() or fb['link']
|
||||
dashou_corp = ((row.dashou_corp_id if row else '') or '').strip() or fb['enterpriseId']
|
||||
merchant_link = ((row.merchant_link if row else '') or '').strip() or fb['link']
|
||||
merchant_corp = ((row.merchant_corp_id if row else '') or '').strip() or fb['enterpriseId']
|
||||
|
||||
return {
|
||||
'link': dashou_link,
|
||||
'enterpriseId': dashou_corp,
|
||||
'dashou': {
|
||||
'link': dashou_link,
|
||||
'enterpriseId': dashou_corp,
|
||||
},
|
||||
'merchant': {
|
||||
'link': merchant_link,
|
||||
'enterpriseId': merchant_corp,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def resolve_side_link(club_id: str, side: str = 'merchant') -> str:
|
||||
"""商家链接页等只需 link 字符串时用。"""
|
||||
payload = miniapp_kefu_payload(club_id)
|
||||
key = 'merchant' if side in ('merchant', 'shangjia') else 'dashou'
|
||||
return (payload.get(key) or {}).get('link') or payload.get('link') or ''
|
||||
@@ -127,6 +127,7 @@ from jituan.views_order_deal import (
|
||||
MerchantDealStatView,
|
||||
DashouDealStatView,
|
||||
)
|
||||
from jituan.views_wecom_kefu import WecomKefuConfigView
|
||||
|
||||
urlpatterns = [
|
||||
path('auth/wechat-login', ClubWechatLoginView.as_view(), name='jituan_wechat_login'),
|
||||
@@ -156,6 +157,7 @@ urlpatterns = [
|
||||
path('houtai/order-deal-config', OrderDealConfigView.as_view(), name='jituan_order_deal_config'),
|
||||
path('houtai/merchant-deal-stat', MerchantDealStatView.as_view(), name='jituan_merchant_deal_stat'),
|
||||
path('houtai/dashou-deal-stat', DashouDealStatView.as_view(), name='jituan_dashou_deal_stat'),
|
||||
path('houtai/wecom-kefu-config', WecomKefuConfigView.as_view(), name='jituan_wecom_kefu_config'),
|
||||
path('houtai/payment-channel-manage', ClubPaymentChannelManageView.as_view(), name='jituan_payment_channel_manage'),
|
||||
path('houtai/club-cert-upload', ClubCertUploadView.as_view(), name='jituan_club_cert_upload'),
|
||||
path('houtai/admin-assignments', ClubAdminAssignmentListView.as_view(), name='jituan_admin_assignments'),
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
"""客服后台:企业微信客服链接(打手端 / 商家端)。"""
|
||||
from rest_framework.parsers import JSONParser
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from backend.utils import verify_kefu_permission, has_club_super_perm, has_perm_code
|
||||
from jituan.services.club_context import resolve_club_id_from_request
|
||||
from jituan.services import wecom_kefu as svc
|
||||
|
||||
PERM_CODE = '8080a'
|
||||
|
||||
|
||||
def _auth(request):
|
||||
phone = (request.data.get('phone') or request.data.get('username') or '').strip()
|
||||
if not phone:
|
||||
return None, Response({'code': 401, 'msg': '缺少客服账号'})
|
||||
kefu, permissions = verify_kefu_permission(request, phone)
|
||||
if kefu is None:
|
||||
return None, permissions
|
||||
user = getattr(request, 'user', None)
|
||||
if getattr(user, 'IsSuperuser', False) or has_club_super_perm(permissions):
|
||||
return kefu, None
|
||||
if has_perm_code(permissions, PERM_CODE):
|
||||
return kefu, None
|
||||
return None, Response({'code': 403, 'msg': '无权限配置企业微信客服'})
|
||||
|
||||
|
||||
class WecomKefuConfigView(APIView):
|
||||
"""
|
||||
POST /jituan/houtai/wecom-kefu-config
|
||||
action: get | save
|
||||
body.save: dashou: {link, enterpriseId}, merchant: {link, enterpriseId}
|
||||
"""
|
||||
permission_classes = [IsAuthenticated]
|
||||
parser_classes = [JSONParser]
|
||||
|
||||
def post(self, request):
|
||||
kefu, err = _auth(request)
|
||||
if err:
|
||||
return err
|
||||
club_id = (resolve_club_id_from_request(request) or '').strip()
|
||||
if not club_id:
|
||||
return Response({'code': 400, 'msg': '请先选择俱乐部'})
|
||||
action = (request.data.get('action') or 'get').strip().lower()
|
||||
if action == 'get':
|
||||
row = svc.get_or_create_wecom_kefu(club_id)
|
||||
return Response({'code': 0, 'msg': 'ok', 'data': svc.config_to_dict(row)})
|
||||
if action == 'save':
|
||||
try:
|
||||
operator = getattr(request.user, 'UserUID', '') or ''
|
||||
data = svc.save_wecom_kefu(club_id, request.data, updated_by=operator)
|
||||
return Response({'code': 0, 'msg': '已保存', 'data': data})
|
||||
except ValueError as e:
|
||||
return Response({'code': 400, 'msg': str(e)})
|
||||
return Response({'code': 400, 'msg': '未知 action'})
|
||||
Reference in new issue
Block a user