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: "批量请求处理失败" }; // 捕获批量处理过程中的异常
|
||
}
|
||
};
|