WACP3 实现企微审批补偿拉取 POC
这个提交包含在:
@@ -1,6 +1,7 @@
|
||||
const { getWecomAppAccessToken, sanitizeWecomResponse } = require("./wecomAccessToken");
|
||||
|
||||
const WECOM_API_BASE = "https://qyapi.weixin.qq.com";
|
||||
const APPROVAL_GET_INFO_ENDPOINT = "/cgi-bin/oa/getapprovalinfo";
|
||||
const APPROVAL_CREATE_TEMPLATE_ENDPOINT = "/cgi-bin/oa/approval/create_template";
|
||||
const APPROVAL_APPLY_EVENT_ENDPOINT = "/cgi-bin/oa/applyevent";
|
||||
|
||||
@@ -106,6 +107,32 @@ function summarizeApprovalPayload(payload) {
|
||||
};
|
||||
}
|
||||
|
||||
function extractApprovalInfoSpNos(payload) {
|
||||
if (!payload || typeof payload !== "object") return [];
|
||||
const candidates = [
|
||||
payload.sp_no_list,
|
||||
payload.spNoList,
|
||||
payload.info?.sp_no_list,
|
||||
];
|
||||
const list = candidates.find((item) => Array.isArray(item)) || [];
|
||||
return list.map((item) => toSafeString(item, 160)).filter(Boolean);
|
||||
}
|
||||
|
||||
function extractApprovalInfoNextCursor(payload) {
|
||||
if (!payload || typeof payload !== "object") return "";
|
||||
return toSafeString(payload.next_cursor ?? payload.nextCursor ?? payload.info?.next_cursor, 160);
|
||||
}
|
||||
|
||||
function summarizeApprovalInfoPayload(payload) {
|
||||
const spNos = extractApprovalInfoSpNos(payload);
|
||||
const nextCursor = extractApprovalInfoNextCursor(payload);
|
||||
return {
|
||||
spNoCount: spNos.length,
|
||||
spNoPrefixes: spNos.map(maskApprovalIdentifier),
|
||||
nextCursorPresent: Boolean(nextCursor),
|
||||
};
|
||||
}
|
||||
|
||||
function sanitizeApprovalApiResponse(payload) {
|
||||
if (!payload || typeof payload !== "object") return null;
|
||||
const result = sanitizeWecomResponse(payload) ?? {};
|
||||
@@ -128,6 +155,12 @@ function sanitizeApprovalApiResponse(payload) {
|
||||
result.notifyerCount = summary.notifyerCount;
|
||||
result.commentCount = summary.commentCount;
|
||||
}
|
||||
if (Array.isArray(payload.sp_no_list) || Array.isArray(payload.spNoList)) {
|
||||
const summary = summarizeApprovalInfoPayload(payload);
|
||||
result.spNoCount = summary.spNoCount;
|
||||
result.spNoPrefixes = summary.spNoPrefixes;
|
||||
result.nextCursorPresent = summary.nextCursorPresent;
|
||||
}
|
||||
return Object.keys(result).length ? result : null;
|
||||
}
|
||||
|
||||
@@ -341,6 +374,148 @@ async function fetchWecomApprovalDetail({
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeApprovalInfoFilter(filter) {
|
||||
if (!filter || typeof filter !== "object") return null;
|
||||
const key = toSafeString(filter.key, 80);
|
||||
const value = toSafeString(filter.value, 200);
|
||||
if (!key || !value) return null;
|
||||
return { key, value };
|
||||
}
|
||||
|
||||
function normalizeApprovalInfoRequest({
|
||||
startTime,
|
||||
endTime,
|
||||
size,
|
||||
nextCursor,
|
||||
filters,
|
||||
}) {
|
||||
const normalizedStart = Math.floor(Number(startTime));
|
||||
const normalizedEnd = Math.floor(Number(endTime));
|
||||
const normalizedSize = Math.max(1, Math.min(100, Math.floor(Number(size || 100))));
|
||||
|
||||
const body = {
|
||||
starttime: normalizedStart,
|
||||
endtime: normalizedEnd,
|
||||
size: normalizedSize,
|
||||
};
|
||||
const cursor = toSafeString(nextCursor, 160);
|
||||
if (cursor) body.next_cursor = cursor;
|
||||
|
||||
const normalizedFilters = Array.isArray(filters)
|
||||
? filters.map(normalizeApprovalInfoFilter).filter(Boolean)
|
||||
: [];
|
||||
if (normalizedFilters.length) {
|
||||
body.filters = normalizedFilters;
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
async function fetchWecomApprovalInfoList({
|
||||
config,
|
||||
startTime,
|
||||
endTime,
|
||||
size = 100,
|
||||
nextCursor = "",
|
||||
filters = [],
|
||||
fetchImpl = fetch,
|
||||
timeoutMs = config?.wecomRequestTimeoutMs ?? 10000,
|
||||
}) {
|
||||
const normalizedStart = Math.floor(Number(startTime));
|
||||
const normalizedEnd = Math.floor(Number(endTime));
|
||||
if (!Number.isFinite(normalizedStart) || !Number.isFinite(normalizedEnd) || normalizedStart <= 0 || normalizedEnd <= 0) {
|
||||
return {
|
||||
ok: false,
|
||||
skipped: true,
|
||||
retryable: false,
|
||||
code: "approval_compensation_window_invalid",
|
||||
message: "Missing or invalid approval compensation time window",
|
||||
tokenSource: "WECOM_AGENT_SECRET",
|
||||
endpoint: `POST ${APPROVAL_GET_INFO_ENDPOINT}`,
|
||||
response: null,
|
||||
spNos: [],
|
||||
nextCursor: "",
|
||||
summary: null,
|
||||
};
|
||||
}
|
||||
if (normalizedStart >= normalizedEnd) {
|
||||
return {
|
||||
ok: false,
|
||||
skipped: true,
|
||||
retryable: false,
|
||||
code: "approval_compensation_window_invalid",
|
||||
message: "Approval compensation startTime must be before endTime",
|
||||
tokenSource: "WECOM_AGENT_SECRET",
|
||||
endpoint: `POST ${APPROVAL_GET_INFO_ENDPOINT}`,
|
||||
response: null,
|
||||
spNos: [],
|
||||
nextCursor: "",
|
||||
summary: null,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const token = await getApprovalAccessToken({ config, fetchImpl, timeoutMs });
|
||||
const requestBody = normalizeApprovalInfoRequest({
|
||||
startTime: normalizedStart,
|
||||
endTime: normalizedEnd,
|
||||
size,
|
||||
nextCursor,
|
||||
filters,
|
||||
});
|
||||
const result = await fetchWecomApprovalJson(APPROVAL_GET_INFO_ENDPOINT, token.accessToken, requestBody, {
|
||||
fetchImpl,
|
||||
timeoutMs,
|
||||
});
|
||||
|
||||
if (!result.ok) {
|
||||
const error = classifyWecomApprovalResult(result, "wecom_approval_info_failed");
|
||||
return {
|
||||
ok: false,
|
||||
skipped: false,
|
||||
retryable: false,
|
||||
code: error.code,
|
||||
message: error.message,
|
||||
tokenSource: token.tokenSource,
|
||||
endpoint: `POST ${APPROVAL_GET_INFO_ENDPOINT}`,
|
||||
response: error.response,
|
||||
spNos: [],
|
||||
nextCursor: "",
|
||||
summary: null,
|
||||
};
|
||||
}
|
||||
|
||||
const spNos = extractApprovalInfoSpNos(result.payload);
|
||||
const nextCursorValue = extractApprovalInfoNextCursor(result.payload);
|
||||
return {
|
||||
ok: true,
|
||||
skipped: false,
|
||||
retryable: false,
|
||||
code: "wecom_approval_info_fetched",
|
||||
message: null,
|
||||
tokenSource: token.tokenSource,
|
||||
endpoint: `POST ${APPROVAL_GET_INFO_ENDPOINT}`,
|
||||
response: sanitizeApprovalApiResponse(result.payload),
|
||||
spNos,
|
||||
nextCursor: nextCursorValue,
|
||||
summary: summarizeApprovalInfoPayload(result.payload),
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
ok: false,
|
||||
skipped: false,
|
||||
retryable: Boolean(error?.details?.retryable),
|
||||
code: error?.code || "wecom_approval_info_failed",
|
||||
message: sanitizeErrorMessage(error?.message || error),
|
||||
tokenSource: "WECOM_AGENT_SECRET",
|
||||
endpoint: `POST ${APPROVAL_GET_INFO_ENDPOINT}`,
|
||||
response: error?.details?.response || null,
|
||||
spNos: [],
|
||||
nextCursor: "",
|
||||
summary: null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function extractTemplateId(payload) {
|
||||
return toSafeString(payload?.template_id ?? payload?.templateId, 160);
|
||||
}
|
||||
@@ -473,13 +648,16 @@ module.exports = {
|
||||
APPROVAL_POC_TARGETS,
|
||||
APPROVAL_APPLY_EVENT_ENDPOINT,
|
||||
APPROVAL_CREATE_TEMPLATE_ENDPOINT,
|
||||
APPROVAL_GET_INFO_ENDPOINT,
|
||||
WecomApprovalPocError,
|
||||
fetchWecomApprovalApplyEvent,
|
||||
fetchWecomApprovalDetail,
|
||||
fetchWecomApprovalInfoList,
|
||||
fetchWecomApprovalTemplateCreate,
|
||||
fetchWecomApprovalTemplateDetail,
|
||||
maskApprovalIdentifier,
|
||||
sanitizeApprovalApiResponse,
|
||||
summarizeApprovalInfoPayload,
|
||||
summarizeApprovalPayload,
|
||||
summarizeTemplatePayload,
|
||||
};
|
||||
|
||||
在新工单中引用
屏蔽一个用户