Files
koc-loop/lib/task-customer-chat-service.ts
ABAPPLO ab78b187c0 feat: 项目层级树+合作方绑定KOC资源+客户群全量可选+精简编辑表单,移除复盘报表
- 项目支持父子层级:树形折叠展示、详情面板内联管理总群成员、推送聚合子项目任务、环检测与子项目删除守卫
- 外部KOC合作方绑定 KOC 资源账号(唯一占用、换绑/解绑、资源页一键设为合作方),列表直显联系方式
- 绑定客户群改为全量外部群缓存(含各群主),同步按钮强刷;member_count 缺失以 member_list 兜底
- 任务/项目编辑模式隐藏不可变字段(平台/内容类型/品牌/总群成员),详情面板维护成员时保留 parent_id
- 删除复盘报表整栏(导航/页面/task_report_list/导出路由/CSV lib 及测试)
2026-09-23 11:13:17 +08:00

112 lines
3.0 KiB
TypeScript

import type { DatabaseClient } from "./database";
import {
addTaskJoinWay,
deleteJoinWay,
getJoinWayQrCode,
updateTaskJoinWay,
type WecomConfig,
} from "./wecom-client";
type FetchLike = typeof fetch;
function text(value: unknown) {
return String(value ?? "").trim();
}
export type BindTaskCustomerChatResult = {
customerChatId: string;
customerChatName: string;
joinConfigId: string;
joinQrCode: string;
qrError?: string;
};
export async function bindTaskCustomerChat(
db: DatabaseClient,
params: { taskId: string; chatId: string; chatName: string },
config: WecomConfig,
fetchImpl: FetchLike = fetch,
): Promise<BindTaskCustomerChatResult> {
const taskId = text(params.taskId);
const chatId = text(params.chatId);
const chatName = text(params.chatName).slice(0, 255);
if (!taskId) throw new Error("缺少 taskId");
if (!chatId) throw new Error("请选择要绑定的客户群");
const task = await db
.prepare("SELECT id, name, join_config_id FROM tasks WHERE id = ?")
.bind(taskId)
.first<{ id: string; name: string; join_config_id: string | null }>();
if (!task) throw new Error("任务不存在");
const joinInput = {
chatIdList: [chatId],
roomBaseName: task.name,
state: `task-${taskId}`,
};
let configId = text(task.join_config_id);
if (configId) {
try {
await updateTaskJoinWay(config, { ...joinInput, configId }, fetchImpl);
} catch {
// 活码配置可能已被企微侧清理,降级为重新创建
configId = (await addTaskJoinWay(config, joinInput, fetchImpl)).configId;
}
} else {
configId = (await addTaskJoinWay(config, joinInput, fetchImpl)).configId;
}
let joinQrCode = "";
let qrError: string | undefined;
try {
joinQrCode = (await getJoinWayQrCode(config, configId, fetchImpl)).qrCode;
} catch (error) {
qrError = `活码二维码获取失败:${error instanceof Error ? error.message : String(error)}`;
}
await db
.prepare(
`UPDATE tasks
SET customer_chat_id = ?, customer_chat_name = ?, join_config_id = ?, join_qr_code = ?
WHERE id = ?`,
)
.bind(chatId, chatName, configId, joinQrCode, taskId)
.run();
return {
customerChatId: chatId,
customerChatName: chatName,
joinConfigId: configId,
joinQrCode,
qrError,
};
}
export async function unbindTaskCustomerChat(
db: DatabaseClient,
taskId: string,
config: WecomConfig,
fetchImpl: FetchLike = fetch,
): Promise<void> {
const id = text(taskId);
if (!id) throw new Error("缺少 taskId");
const task = await db
.prepare("SELECT join_config_id FROM tasks WHERE id = ?")
.bind(id)
.first<{ join_config_id: string | null }>();
if (!task) throw new Error("任务不存在");
const configId = text(task.join_config_id);
if (configId) {
await deleteJoinWay(config, configId, fetchImpl);
}
await db
.prepare(
`UPDATE tasks
SET customer_chat_id = '', customer_chat_name = '', join_config_id = '', join_qr_code = ''
WHERE id = ?`,
)
.bind(id)
.run();
}