feat: 企微客服支持点单端独立配置

点单端(boss)与打手端(dashou)分字段存储,接口下发 kefu.boss,顶层 link 仍兼容打手端。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
XingQueandCursor committed 2026-08-12 17:43:32 +08:00
1 parent 181bf3aaaa
commit 644fd47f0a
4 files changed
+73 -17

No files matched your search

@@ -0,0 +1,27 @@
# Generated manually for 点单端企微客服
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('jituan', '0030_fund_freeze_dashou_yajin_role'),
]
operations = [
migrations.AddField(
model_name='clubwecomkefuconfig',
name='boss_link',
field=models.CharField(blank=True, default='', max_length=512, verbose_name='点单端客服链接'),
),
migrations.AddField(
model_name='clubwecomkefuconfig',
name='boss_corp_id',
field=models.CharField(blank=True, default='', max_length=64, verbose_name='点单端企业ID'),
),
migrations.AlterModelOptions(
name='clubwecomkefuconfig',
options={'verbose_name': '企业微信客服配置'},
),
]
+3 -1
View File
@@ -692,9 +692,11 @@ class ClubRoleAgreementSign(QModel):
class ClubWecomKefuConfig(QModel):
"""
俱乐部企业微信客服链接(小程序 wx.openCustomerServiceChat)。
打手端 / 商家端各一套,互不影响;未配置时小程序可读 settings 兜底。
点单端 / 打手端 / 商家端各一套,互不影响;未配置时小程序可读 settings 兜底。
"""
club_id = models.CharField(max_length=16, unique=True, db_index=True, verbose_name='俱乐部')
boss_link = models.CharField(max_length=512, blank=True, default='', verbose_name='点单端客服链接')
boss_corp_id = models.CharField(max_length=64, blank=True, default='', verbose_name='点单端企业ID')
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='商家端客服链接')
+41 -14
View File
@@ -1,4 +1,4 @@
"""俱乐部企业微信客服链接:打手端 / 商家端分端配置。"""
"""俱乐部企业微信客服链接:点单端 / 打手端 / 商家端分端配置。"""
from django.conf import settings
from jituan.models import ClubWecomKefuConfig
@@ -25,24 +25,28 @@ def get_or_create_wecom_kefu(club_id: str):
return row
def _side_dict(link: str, corp: str) -> dict:
return {
'link': (link or '').strip(),
'enterpriseId': (corp or '').strip(),
}
def config_to_dict(row) -> dict:
if not row:
empty = {'link': '', 'enterpriseId': ''}
return {
'club_id': '',
'dashou': {'link': '', 'enterpriseId': ''},
'merchant': {'link': '', 'enterpriseId': ''},
'boss': dict(empty),
'dashou': dict(empty),
'merchant': dict(empty),
'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(),
},
'boss': _side_dict(getattr(row, 'boss_link', ''), getattr(row, 'boss_corp_id', '')),
'dashou': _side_dict(row.dashou_link, row.dashou_corp_id),
'merchant': _side_dict(row.merchant_link, row.merchant_corp_id),
'updated_by': (row.updated_by or '').strip(),
}
@@ -59,10 +63,15 @@ def save_wecom_kefu(club_id: str, data: dict, updated_by: str = '') -> dict:
if not row:
raise ValueError('缺少俱乐部')
boss = _pick_side(data, 'boss') or _pick_side(data, 'diandan') or _pick_side(data, 'normal')
dashou = _pick_side(data, 'dashou')
merchant = _pick_side(data, 'merchant')
# 兼容扁平字段
boss_link = boss.get('link', data.get('boss_link', data.get('diandan_link')))
boss_corp = boss.get(
'enterpriseId',
data.get('boss_corp_id', data.get('boss_enterpriseId', data.get('diandan_corp_id'))),
)
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'))
@@ -71,6 +80,10 @@ def save_wecom_kefu(club_id: str, data: dict, updated_by: str = '') -> dict:
data.get('merchant_corp_id', data.get('merchant_enterpriseId')),
)
if boss_link is not None:
row.boss_link = str(boss_link or '').strip()[:LINK_MAX]
if boss_corp is not None:
row.boss_corp_id = str(boss_corp or '').strip()[:CORP_MAX]
if dashou_link is not None:
row.dashou_link = str(dashou_link or '').strip()[:LINK_MAX]
if dashou_corp is not None:
@@ -89,7 +102,10 @@ def miniapp_kefu_payload(club_id: str) -> dict:
"""
小程序动态配置用。
分端优先读库;空则回落 settings(兼容旧单链接)。
顶层 link / enterpriseId 对齐打手端,兼容旧前端。
- boss:点单端平台客服 / 我的联系客服
- dashou:打手端
- merchant:商家端
顶层 link / enterpriseId 仍对齐打手端,兼容旧前端浮窗。
"""
fb = _settings_fallback()
row = None
@@ -97,6 +113,8 @@ def miniapp_kefu_payload(club_id: str) -> dict:
if cid:
row = ClubWecomKefuConfig.query.filter(club_id=cid).first()
boss_link = ((getattr(row, 'boss_link', '') if row else '') or '').strip() or fb['link']
boss_corp = ((getattr(row, 'boss_corp_id', '') if row else '') or '').strip() or fb['enterpriseId']
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']
@@ -105,6 +123,10 @@ def miniapp_kefu_payload(club_id: str) -> dict:
return {
'link': dashou_link,
'enterpriseId': dashou_corp,
'boss': {
'link': boss_link,
'enterpriseId': boss_corp,
},
'dashou': {
'link': dashou_link,
'enterpriseId': dashou_corp,
@@ -119,5 +141,10 @@ def miniapp_kefu_payload(club_id: str) -> dict:
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'
if side in ('boss', 'diandan', 'normal', 'laoban'):
key = 'boss'
elif side in ('merchant', 'shangjia'):
key = 'merchant'
else:
key = 'dashou'
return (payload.get(key) or {}).get('link') or payload.get('link') or ''
+2 -2
View File
@@ -1,4 +1,4 @@
"""客服后台:企业微信客服链接(打手端 / 商家端)。"""
"""客服后台:企业微信客服链接(点单端 / 打手端 / 商家端)。"""
import logging
from django.db import OperationalError, ProgrammingError
@@ -35,7 +35,7 @@ class WecomKefuConfigView(APIView):
"""
POST /jituan/houtai/wecom-kefu-config
action: get | save
body.save: dashou: {link, enterpriseId}, merchant: {link, enterpriseId}
body.save: boss / dashou / merchant: {link, enterpriseId}
"""
permission_classes = [IsAuthenticated]
parser_classes = [JSONParser]