sync: 以本地为准同步全量变更至 GitHub

含四端需求文档、前端/后端/超管/触客宝迭代及部署脚本更新;未拉取远程。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Manus AI
2026-05-30 04:24:56 +08:00
parent 711783baf5
commit 5517457929
579 changed files with 45472 additions and 8111 deletions

View File

@@ -0,0 +1,207 @@
<?php
namespace app\chukebao\controller;
use library\ResponseHelper;
use think\Db;
/**
* 模块 G · 转移申请(需求七 §7.7 · 需求九 §9.1
* 铁律:触客宝仅「申请」,执行须存客宝审批;不在前端直调即时 transfer。
* 统一写入存客宝真源表 ck_wechat_account_transfer与 cunkebao WechatAccountTransferController 同表,
* 故触客宝提交的申请可在存客宝 H5 审批列表直接出现并由其执行 allot
*/
class TransferController extends BaseController
{
private const SUPPORTED_TYPES = ['session', 'wechat_account', 'contact_group'];
/** 数字微信号账号 id → wechatId整号转移以 wechatId 为 targetId 口径,与 allot 一致) */
private function resolveWechatId(int $accountId): string
{
return (string)Db::table('s2_wechat_account')->where('id', $accountId)->value('wechatId');
}
private function createApply($type)
{
$companyId = (int)$this->getUserInfo('companyId');
$applicantUserId = (int)$this->getUserInfo('id');
$applicantName = (string)($this->getUserInfo('username') ?: '');
$toAccountId = trim((string)$this->request->param('toAccountId', $this->request->param('toKefuId', '')));
$reason = (string)$this->request->param('reason', '');
// targetId 口径wechat_account → wechatIdsession → wechatFriendIdcontact_group → groupId
if ($type === 'wechat_account') {
$accId = (int)$this->request->param('wechatAccountId', 0);
$targetId = $this->resolveWechatId($accId);
if ($targetId === '') {
return ResponseHelper::error('微信号不存在,无法转移', 400);
}
} elseif ($type === 'contact_group') {
$groupType = (int)$this->request->param('groupType', 1);
if ($groupType !== 1) {
return ResponseHelper::error('仅好友分组支持转移申请', 400);
}
$wechatAccountId = (int)$this->request->param('wechatAccountId', 0);
if ($wechatAccountId <= 0) {
return ResponseHelper::error('wechatAccountId 缺失', 400);
}
$targetId = (string)(int)$this->request->param('groupId', 0);
} else {
$targetId = (string)$this->request->param('wechatFriendId', 0);
}
if ($toAccountId === '' || $targetId === '' || $targetId === '0') {
return ResponseHelper::error('参数缺失(目标客服/对象)', 400);
}
// 校验目标客服归属同公司
$toAccount = Db::table('s2_company_account')
->where('id', $toAccountId)
->where('departmentId', $companyId)
->field('id, userName, realName, nickname')
->find();
$toAccountName = $toAccount
? (($toAccount['realName'] ?: $toAccount['nickname'] ?: $toAccount['userName']) ?: '')
: '';
$affected = 0;
try {
if ($type === 'session') {
$affected = 1;
} elseif ($type === 'wechat_account') {
$affected = (int)Db::table('s2_wechat_friend')->where('ownerWechatId', $targetId)->count();
} elseif ($type === 'contact_group') {
$wechatAccountId = (int)$this->request->param('wechatAccountId', 0);
$ownerWechatId = $this->resolveWechatId($wechatAccountId);
if ($ownerWechatId === '') {
return ResponseHelper::error('微信号不存在,无法发起分组转移', 400);
}
$affected = (int)Db::table('s2_wechat_friend')
->where('ownerWechatId', $ownerWechatId)
->where('groupIds', (int)$targetId)
->where('isDeleted', 0)
->count();
}
} catch (\Throwable $e) {
}
if ($type === 'contact_group' && $affected <= 0) {
return ResponseHelper::error('本分组暂无好友,无法发起转移', 400);
}
$now = time();
$applyId = Db::name('wechat_account_transfer')->insertGetId([
'companyId' => $companyId,
'type' => $type,
'targetId' => $targetId,
'targetName' => (string)$this->request->param('targetName', ''),
'toAccountId' => $toAccountId,
'toAccountName' => $toAccountName,
'applicantUserId' => $applicantUserId,
'applicantName' => $applicantName,
'reason' => $this->buildReason($type, $reason),
'affectedFriendCount' => $affected,
'status' => 'pending',
'createTime' => $now,
'updateTime' => $now,
]);
return ResponseHelper::success([
'applyId' => (int)$applyId,
'status' => 'pending',
'affectedFriendCount' => $affected,
], '转移申请已提交,等待存客宝审批');
}
/** POST /v1/kefu/wechat-account/transfer/apply */
public function applyWechatAccount()
{
return $this->createApply('wechat_account');
}
/** POST /v1/kefu/session/transfer/apply */
public function applySession()
{
return $this->createApply('session');
}
/** POST /v1/kefu/transfer/apply */
public function apply()
{
$type = trim((string)$this->request->param('type', ''));
if (!in_array($type, self::SUPPORTED_TYPES, true)) {
return ResponseHelper::error('转移类型不支持', 400);
}
return $this->createApply($type);
}
/** GET /v1/kefu/wechat-account/transfer/status?applyId= */
public function status()
{
$companyId = (int)$this->getUserInfo('companyId');
$applyId = (int)$this->request->param('applyId', 0);
if (!$applyId) {
return ResponseHelper::error('applyId 缺失', 400);
}
$row = Db::name('wechat_account_transfer')
->where('companyId', $companyId)
->where('id', $applyId)
->find();
if (!$row) {
return ResponseHelper::error('申请不存在', 404);
}
return ResponseHelper::success([
'applyId' => (int)$row['id'],
'status' => $row['status'],
'reason' => $row['reviewComment'] ?? '',
'executedCount' => (int)($row['executedCount'] ?? 0),
]);
}
/** GET /v1/kefu/wechatGroup/transfer/preview */
public function groupPreview()
{
$groupType = (int)$this->request->param('groupType', 1);
$groupId = (int)$this->request->param('groupId', 0);
$wechatAccountId = (int)$this->request->param('wechatAccountId', 0);
if ($groupType !== 1) {
return ResponseHelper::error('仅好友分组支持预览', 400);
}
if ($groupId <= 0 || $wechatAccountId <= 0) {
return ResponseHelper::error('参数缺失groupId/wechatAccountId', 400);
}
$ownerWechatId = $this->resolveWechatId($wechatAccountId);
if ($ownerWechatId === '') {
return ResponseHelper::error('微信号不存在', 404);
}
$companyId = (int)$this->getUserInfo('companyId');
$groupName = (string)Db::table('s2_chat_groups')
->where('companyId', $companyId)
->where('id', $groupId)
->value('groupName');
$count = (int)Db::table('s2_wechat_friend')
->where('ownerWechatId', $ownerWechatId)
->where('groupIds', $groupId)
->where('isDeleted', 0)
->count();
return ResponseHelper::success([
'groupId' => $groupId,
'groupType' => 1,
'groupName' => $groupName ?: '未命名分组',
'count' => $count,
]);
}
private function buildReason(string $type, string $reason): string
{
if ($type !== 'contact_group') {
return $reason;
}
$payload = [
'rawReason' => $reason,
'groupType' => (int)$this->request->param('groupType', 1),
'groupId' => (int)$this->request->param('groupId', 0),
'wechatAccountId' => (int)$this->request->param('wechatAccountId', 0),
];
return json_encode($payload, JSON_UNESCAPED_UNICODE);
}
}