449 lines
13 KiB
JavaScript
449 lines
13 KiB
JavaScript
|
|
const request = require("../request");
|
|||
|
|
const utils = require("../utils");
|
|||
|
|
const information = require("../externalcontact-info");
|
|||
|
|
const token = require("../token");
|
|||
|
|
const dayjs = require("dayjs");
|
|||
|
|
const api = require("../../api");
|
|||
|
|
|
|||
|
|
exports.main = async (context) => {
|
|||
|
|
switch (context.type) {
|
|||
|
|
case "editExternalContact":
|
|||
|
|
return await exports.editExternalContact(context);
|
|||
|
|
case "transferExternalcontact":
|
|||
|
|
return await exports.transferExternalcontact(context);
|
|||
|
|
case "removeExternalContact":
|
|||
|
|
return await exports.removeExternalContact(context);
|
|||
|
|
case "asyncExternalContactRemark":
|
|||
|
|
return await exports.asyncExternalContactRemark(context);
|
|||
|
|
case "batchGetBehaviorData":
|
|||
|
|
return await exports.batchGetBehaviorData(context);
|
|||
|
|
case "getCorpAllStaffUseBehaviorDataToday":
|
|||
|
|
return await exports.getCorpAllStaffUseBehaviorDataToday(context);
|
|||
|
|
case "getAllCorpYesterdayBehaviorData":
|
|||
|
|
return await exports.getAllCorpYesterdayBehaviorData(context);
|
|||
|
|
default: {
|
|||
|
|
return {
|
|||
|
|
success: false,
|
|||
|
|
message: "未找到对应的操作类型",
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
exports.removeExternalContact = async (context) => {
|
|||
|
|
return true;
|
|||
|
|
};
|
|||
|
|
// handoverUserId 是 string 离职成员的userid
|
|||
|
|
// externalUserIds 是 array 客户的external_userid列表,每次最多支持100个客户
|
|||
|
|
// takeoverUserId 是 string 接替成员的userid
|
|||
|
|
exports.transferExternalcontact = async (context) => {
|
|||
|
|
const {
|
|||
|
|
corpId,
|
|||
|
|
externalUserIds = [],
|
|||
|
|
handoverUserId,
|
|||
|
|
takeoverUserId,
|
|||
|
|
permanentCode,
|
|||
|
|
} = context;
|
|||
|
|
const accessToken = await token.getToken({
|
|||
|
|
corpId,
|
|||
|
|
permanentCode,
|
|||
|
|
});
|
|||
|
|
let url = `https://qyapi.weixin.qq.com/cgi-bin/externalcontact/transfer_customer?access_token=${accessToken}`;
|
|||
|
|
let params = {
|
|||
|
|
external_userid: externalUserIds,
|
|||
|
|
handover_userid: handoverUserId,
|
|||
|
|
takeover_userid: takeoverUserId,
|
|||
|
|
};
|
|||
|
|
let { errcode, errmsg } = await request.main(url, params, "POST");
|
|||
|
|
if (errcode !== 0) {
|
|||
|
|
return {
|
|||
|
|
context,
|
|||
|
|
success: false,
|
|||
|
|
message: errmsg,
|
|||
|
|
errcode,
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
return {
|
|||
|
|
success: true,
|
|||
|
|
message: "转移客户成功",
|
|||
|
|
};
|
|||
|
|
};
|
|||
|
|
/**
|
|||
|
|
* 编辑外部联系人
|
|||
|
|
* @param {string} corpId 企业ID
|
|||
|
|
* @param {string} externalUserId 外部联系人的userid
|
|||
|
|
* @param {string} userId 企业成员的userid
|
|||
|
|
* @param {string} env 云开发环境ID
|
|||
|
|
*/
|
|||
|
|
exports.editExternalContact = async ({ corpId, externalUserId, userId }) => {
|
|||
|
|
let res = await information.getNameByexternalUserId({
|
|||
|
|
corpId,
|
|||
|
|
externalUserId,
|
|||
|
|
type: "getNameByexternalUserId",
|
|||
|
|
});
|
|||
|
|
if (!res || !res.data) return;
|
|||
|
|
let { follow_user, external_contact } = res.data;
|
|||
|
|
let tags = [];
|
|||
|
|
const user =
|
|||
|
|
Array.isArray(follow_user) &&
|
|||
|
|
follow_user.find((item) => item.userid === userId);
|
|||
|
|
let remark = external_contact.name;
|
|||
|
|
if (user) remark = user.remark;
|
|||
|
|
follow_user.forEach((item) => {
|
|||
|
|
if (item.userid === userId && item.tags.length > 0) {
|
|||
|
|
item.tags.forEach((tag) => {
|
|||
|
|
tags.push(tag.tag_id);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
console.log("编辑外部联系人tags", tags);
|
|||
|
|
console.log("编辑外部联系人", res.data);
|
|||
|
|
console.log("remark", remark);
|
|||
|
|
return await api.getMemberApi({
|
|||
|
|
type: "asyncCustomerTag",
|
|||
|
|
corpId,
|
|||
|
|
name: remark,
|
|||
|
|
externalUserId,
|
|||
|
|
userId,
|
|||
|
|
tags,
|
|||
|
|
});
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 同步备注名
|
|||
|
|
*/
|
|||
|
|
/**
|
|||
|
|
* 同步备注名
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
exports.asyncExternalContactRemark = async ({
|
|||
|
|
corpId,
|
|||
|
|
externalUserId,
|
|||
|
|
list = [],
|
|||
|
|
permanentCode = "",
|
|||
|
|
}) => {
|
|||
|
|
const accessToken = await token.getToken({
|
|||
|
|
corpId,
|
|||
|
|
permanentCode,
|
|||
|
|
});
|
|||
|
|
if (!externalUserId)
|
|||
|
|
return {
|
|||
|
|
success: false,
|
|||
|
|
message: "externalUserId不能为空",
|
|||
|
|
};
|
|||
|
|
console.log("accessToken", accessToken);
|
|||
|
|
let res = await information.getNameByexternalUserId({
|
|||
|
|
corpId,
|
|||
|
|
externalUserId,
|
|||
|
|
type: "getNameByexternalUserId",
|
|||
|
|
});
|
|||
|
|
console.log("外部联系人信息", res);
|
|||
|
|
let { follow_user } = res.data;
|
|||
|
|
let userIds = follow_user.map((item) => item.userid);
|
|||
|
|
if (userIds.length === 0) {
|
|||
|
|
return {
|
|||
|
|
success: false,
|
|||
|
|
message: "该客户不是本机构的外部联系人",
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
// 把 list 排序 本人在前,其他人在后
|
|||
|
|
list.sort((a, b) => {
|
|||
|
|
if (a.relationship === "本人") {
|
|||
|
|
return -1;
|
|||
|
|
} else {
|
|||
|
|
return 1;
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
// 逗号隔开
|
|||
|
|
let mySelf = list
|
|||
|
|
.filter((item) => item.relationship === "本人")
|
|||
|
|
.map((item) => item.name)
|
|||
|
|
.join("、");
|
|||
|
|
let other = list
|
|||
|
|
.filter((item) => item.relationship !== "本人")
|
|||
|
|
.map((item) => item.name)
|
|||
|
|
.join("、");
|
|||
|
|
let remark = "";
|
|||
|
|
if (mySelf) remark = `${mySelf}(本人)`;
|
|||
|
|
if (other) remark = remark ? `${remark},${other}(家属)` : `${other}(家属)`;
|
|||
|
|
|
|||
|
|
const handler = async (userId) => {
|
|||
|
|
let url = `https://qyapi.weixin.qq.com/cgi-bin/externalcontact/remark?access_token=${accessToken}`;
|
|||
|
|
let params = {
|
|||
|
|
userid: userId,
|
|||
|
|
external_userid: externalUserId,
|
|||
|
|
remark,
|
|||
|
|
};
|
|||
|
|
await request.main(url, params, "POST");
|
|||
|
|
};
|
|||
|
|
await utils.processInBatches(userIds, handler, 10);
|
|||
|
|
return {
|
|||
|
|
success: true,
|
|||
|
|
message: "同步备注名成功",
|
|||
|
|
};
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
exports.batchGetBehaviorData = async ({
|
|||
|
|
dates,
|
|||
|
|
permanentCode,
|
|||
|
|
accessToken,
|
|||
|
|
corpId,
|
|||
|
|
}) => {
|
|||
|
|
if (!accessToken)
|
|||
|
|
accessToken = await token.getToken({ corpId, permanentCode });
|
|||
|
|
let startTime = "",
|
|||
|
|
endTime = "";
|
|||
|
|
if (dates && Array.isArray(dates) && dates.length === 2) {
|
|||
|
|
startTime = dayjs(dates[0]).startOf("day").unix();
|
|||
|
|
endTime = dayjs(dates[1]).endOf("day").unix();
|
|||
|
|
}
|
|||
|
|
console.log("startTime", startTime);
|
|||
|
|
console.log("endTime", endTime);
|
|||
|
|
const { data } = await api.getCorpApi({
|
|||
|
|
corpId,
|
|||
|
|
permanentCode,
|
|||
|
|
accessToken,
|
|||
|
|
type: "getOpenedAccount",
|
|||
|
|
});
|
|||
|
|
const userIds = data && Array.isArray(data) ? data.map((i) => i.userid) : [];
|
|||
|
|
const query = {
|
|||
|
|
corpId,
|
|||
|
|
permanentCode,
|
|||
|
|
startTime,
|
|||
|
|
endTime,
|
|||
|
|
};
|
|||
|
|
const taskFn = (userId) => {
|
|||
|
|
return addBehaviorStatistics({ ...query, userId });
|
|||
|
|
};
|
|||
|
|
await utils.executeWithConcurrency(userIds, taskFn);
|
|||
|
|
return {
|
|||
|
|
success: true,
|
|||
|
|
message: "批量获取成功",
|
|||
|
|
};
|
|||
|
|
};
|
|||
|
|
async function addBehaviorStatistics({
|
|||
|
|
userId,
|
|||
|
|
corpId,
|
|||
|
|
permanentCode,
|
|||
|
|
startTime,
|
|||
|
|
endTime,
|
|||
|
|
}) {
|
|||
|
|
const { success, data } = await exports.getUseBehaviorData({
|
|||
|
|
startTime,
|
|||
|
|
endTime,
|
|||
|
|
userIds: [userId],
|
|||
|
|
corpId,
|
|||
|
|
permanentCode,
|
|||
|
|
});
|
|||
|
|
if (success) {
|
|||
|
|
const list = data
|
|||
|
|
.filter(
|
|||
|
|
(i) =>
|
|||
|
|
i.newContactCount !== 0 ||
|
|||
|
|
i.newApplyCnt !== 0 ||
|
|||
|
|
i.chatCnt !== 0 ||
|
|||
|
|
i.negativeFeedbackCnt !== 0 ||
|
|||
|
|
i.avgReplyTime !== 0 ||
|
|||
|
|
i.replyPercentage !== 0 ||
|
|||
|
|
i.messageCnt !== 0
|
|||
|
|
)
|
|||
|
|
.map((i) => {
|
|||
|
|
return {
|
|||
|
|
...i,
|
|||
|
|
corpId,
|
|||
|
|
userId,
|
|||
|
|
};
|
|||
|
|
});
|
|||
|
|
await addBehaviorStatisticsInBatches(list);
|
|||
|
|
return {
|
|||
|
|
success: true,
|
|||
|
|
message: "获取成功",
|
|||
|
|
userId,
|
|||
|
|
};
|
|||
|
|
} else {
|
|||
|
|
return data;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function addBehaviorStatisticsInBatches(data, maxConcurrency = 10) {
|
|||
|
|
const results = [];
|
|||
|
|
let index = 0;
|
|||
|
|
while (index < data.length) {
|
|||
|
|
const batch = data.slice(index, index + maxConcurrency); // 获取当前批次
|
|||
|
|
const promises = batch.map((doc) =>
|
|||
|
|
api.getCorpApi({
|
|||
|
|
type: "addBehaviorStatistics",
|
|||
|
|
data: doc,
|
|||
|
|
})
|
|||
|
|
);
|
|||
|
|
// 等待当前批次完成
|
|||
|
|
const batchResults = await Promise.all(promises);
|
|||
|
|
results.push(...batchResults);
|
|||
|
|
// 更新索引,进入下一个批次
|
|||
|
|
index += maxConcurrency;
|
|||
|
|
}
|
|||
|
|
return results;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
*
|
|||
|
|
* @param
|
|||
|
|
* new_apply_cnt 发起申请数,成员通过「搜索手机号」、「扫一扫」、「从微信好友中添加」、「从群聊中添加」、「添加共享、分配给我的客户」、「添加单向、双向删除好友关系的好友」、「从新的联系人推荐中添加」等渠道主动向客户发起的好友申请数量。
|
|||
|
|
new_contact_cnt 新增客户数,成员新添加的客户数量。
|
|||
|
|
chat_cnt 聊天总数, 成员有主动发送过消息的单聊总数。
|
|||
|
|
message_cnt 发送消息数,成员在单聊中发送的消息总数。
|
|||
|
|
reply_percentage 已回复聊天占比,浮点型,客户主动发起聊天后,成员在一个自然日内有回复过消息的聊天数/客户主动发起的聊天数比例,不包括群聊,仅在确有聊天时返回。
|
|||
|
|
avg_reply_time 平均首次回复时长,单位为分钟,即客户主动发起聊天后,成员在一个自然日内首次回复的时长间隔为首次回复时长,所有聊天的首次回复总时长/已回复的聊天总数即为平均首次回复时长,不包括群聊,仅在确有聊天时返回。
|
|||
|
|
negative_feedback_cnt 删除/拉黑成员的客户数,即将成员删除或加入黑名单的客户数。
|
|||
|
|
* @returns
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
exports.getUseBehaviorData = async ({
|
|||
|
|
corpId,
|
|||
|
|
userIds,
|
|||
|
|
startTime,
|
|||
|
|
endTime,
|
|||
|
|
accessToken,
|
|||
|
|
permanentCode,
|
|||
|
|
}) => {
|
|||
|
|
try {
|
|||
|
|
// 如果没有传递 accessToken,则获取一个新的 token
|
|||
|
|
if (!accessToken)
|
|||
|
|
accessToken = await token.getToken({ corpId, permanentCode });
|
|||
|
|
// 构建请求 URL 和请求参数
|
|||
|
|
const url = `https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get_user_behavior_data?access_token=${accessToken}`;
|
|||
|
|
const params = {
|
|||
|
|
userid: userIds,
|
|||
|
|
start_time: startTime,
|
|||
|
|
end_time: endTime,
|
|||
|
|
};
|
|||
|
|
// 发送 POST 请求
|
|||
|
|
const response = await request.main(url, params, "POST");
|
|||
|
|
// 检查请求结果
|
|||
|
|
if (response.errcode === 0) {
|
|||
|
|
// 假设 errcode 为 0 表示成功
|
|||
|
|
let list = response.behavior_data;
|
|||
|
|
if (list && Array.isArray(list)) {
|
|||
|
|
list = list.map((i) => {
|
|||
|
|
return {
|
|||
|
|
statTime: i.stat_time, // 时间
|
|||
|
|
newContactCount: i.new_contact_cnt || 0,
|
|||
|
|
newApplyCnt: i.new_apply_cnt || 0,
|
|||
|
|
chatCnt: i.chat_cnt || 0,
|
|||
|
|
negativeFeedbackCnt: i.negative_feedback_cnt || 0,
|
|||
|
|
avgReplyTime: i.avg_reply_time || 0,
|
|||
|
|
replyPercentage: i.reply_percentage || 0,
|
|||
|
|
messageCnt: i.message_cnt || 0,
|
|||
|
|
};
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
return {
|
|||
|
|
success: true,
|
|||
|
|
data: list, // 假设接口返回的数据字段是 behavior_data
|
|||
|
|
message: "请求成功",
|
|||
|
|
};
|
|||
|
|
} else {
|
|||
|
|
return {
|
|||
|
|
success: false,
|
|||
|
|
errcode: response.errcode,
|
|||
|
|
message: response.errmsg || "请求失败",
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
} catch (err) {
|
|||
|
|
console.error("请求失败:", err); // 打印错误日志以便调试
|
|||
|
|
return {
|
|||
|
|
success: false,
|
|||
|
|
message: "请求失败,系统错误",
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
exports.getAllCorpYesterdayBehaviorData = async () => {
|
|||
|
|
// 获取所以机构
|
|||
|
|
try {
|
|||
|
|
const corpRes = await api.getCorpApi({
|
|||
|
|
type: "getCorpList",
|
|||
|
|
});
|
|||
|
|
const corpIds =
|
|||
|
|
corpRes.data && Array.isArray(corpRes.data)
|
|||
|
|
? corpRes.data.map((i) => i.corpId)
|
|||
|
|
: [];
|
|||
|
|
const yesterdayTime = dayjs().subtract(1, "day").format("YYYY-MM-DD");
|
|||
|
|
const dates = [yesterdayTime, yesterdayTime];
|
|||
|
|
const taskFn = (corpId) => {
|
|||
|
|
return this.batchGetBehaviorData({ dates, corpId });
|
|||
|
|
};
|
|||
|
|
await utils.executeWithConcurrency(corpIds, taskFn);
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
success: true,
|
|||
|
|
message: "批量获取成功",
|
|||
|
|
};
|
|||
|
|
} catch (err) {
|
|||
|
|
return {
|
|||
|
|
success: false,
|
|||
|
|
message: "批量获取失败",
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
exports.getCorpAllStaffUseBehaviorDataToday = async ({
|
|||
|
|
corpId,
|
|||
|
|
userIds,
|
|||
|
|
dates,
|
|||
|
|
}) => {
|
|||
|
|
let accessToken;
|
|||
|
|
try {
|
|||
|
|
// 获取 accessToken
|
|||
|
|
accessToken = await token.getToken({ corpId });
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error("Error getting access token:", error);
|
|||
|
|
return { success: false, message: "获取访问令牌失败" }; // 获取令牌失败,返回错误信息
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const maxConcurrency = 10; // 设置最大并发数
|
|||
|
|
const results = []; // 存储所有的结果
|
|||
|
|
let index = 0;
|
|||
|
|
// 设置查询的开始和结束时间(单位:秒)
|
|||
|
|
const startTime = dayjs().startOf("day").unix();
|
|||
|
|
const endTime = dayjs().endOf("day").unix();
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
while (index < userIds.length) {
|
|||
|
|
const batch = userIds.slice(index, index + maxConcurrency); // 当前批次的用户ID
|
|||
|
|
const promises = batch.map(async (userId) => {
|
|||
|
|
try {
|
|||
|
|
const { data } = await exports.getUseBehaviorData({
|
|||
|
|
corpId,
|
|||
|
|
userIds: [userId],
|
|||
|
|
startTime,
|
|||
|
|
endTime,
|
|||
|
|
accessToken,
|
|||
|
|
});
|
|||
|
|
const item = data && Array.isArray(data) ? data[0] : {};
|
|||
|
|
return { userId, ...item };
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error(`Error fetching data for userId: ${userId}`, error);
|
|||
|
|
return []; // 失败时返回空数组,继续处理其他用户
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 等待当前批次完成
|
|||
|
|
const batchResults = await Promise.all(promises);
|
|||
|
|
results.push(...batchResults); // 合并结果
|
|||
|
|
|
|||
|
|
// 更新索引,进入下一个批次
|
|||
|
|
index += maxConcurrency;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 返回最终结果
|
|||
|
|
return {
|
|||
|
|
success: true,
|
|||
|
|
data: results,
|
|||
|
|
message: "获取成功",
|
|||
|
|
};
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error("Error in processing the batch requests:", error);
|
|||
|
|
return { success: false, message: "批量请求处理失败" }; // 捕获批量处理过程中的异常
|
|||
|
|
}
|
|||
|
|
};
|