优化快手云领取发货校验
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
import { createTaskEvent } from "../../../repositories/task-event-repo.js";
|
||||
import { updateTask } from "../../../repositories/task-repo.js";
|
||||
import type { TaskRow } from "../../../types/repository/rows.js";
|
||||
import { createHttpError } from "../../../utils/http.js";
|
||||
import { getCloudtentaclesBindInfo } from "../../platforms/cloudtentacles/virtual-number-service.js";
|
||||
import {
|
||||
normalizeKuaishouCloudFlow,
|
||||
normalizeKuaishouCloudRoleInfo,
|
||||
type JsonObject,
|
||||
} from "./domain.js";
|
||||
|
||||
export async function syncKuaishouCloudRoleInfoBeforeDispatch(
|
||||
task: TaskRow,
|
||||
input: JsonObject = {}
|
||||
) {
|
||||
const now = String(input.now || "").trim() || new Date().toISOString();
|
||||
const source =
|
||||
String(input.source || "system_before_dispatch").trim() ||
|
||||
"system_before_dispatch";
|
||||
const errorCodePrefix =
|
||||
String(input.errorCodePrefix || "kuaishou_cloud").trim() ||
|
||||
"kuaishou_cloud";
|
||||
const cloudContext =
|
||||
input.cloudContext && typeof input.cloudContext === "object"
|
||||
? input.cloudContext
|
||||
: {};
|
||||
const taskContext =
|
||||
input.taskContext && typeof input.taskContext === "object"
|
||||
? input.taskContext
|
||||
: {};
|
||||
const flow = normalizeKuaishouCloudFlow(
|
||||
input.flow || taskContext.kuaishouCloudFulfillment
|
||||
);
|
||||
|
||||
if (!flow.binding.vnId || !flow.binding.vnKey) {
|
||||
throw createHttpError("当前任务缺少可同步角色的虚拟号信息,暂时不能发货", {
|
||||
statusCode: 409,
|
||||
errorCode: `${errorCodePrefix}_dispatch_missing_bind_info_context`,
|
||||
});
|
||||
}
|
||||
|
||||
const bindInfoResult = await getCloudtentaclesBindInfo({
|
||||
...cloudContext,
|
||||
key: flow.binding.vnKey,
|
||||
id: flow.binding.vnId,
|
||||
});
|
||||
const roleInfo = normalizeKuaishouCloudRoleInfo(bindInfoResult.bindInfo);
|
||||
|
||||
if (!roleInfo.name || !roleInfo.rid) {
|
||||
throw createHttpError(
|
||||
"cloudtentacles 还没有同步到客户绑定角色,请稍后刷新角色信息后再发货",
|
||||
{
|
||||
statusCode: 409,
|
||||
errorCode: `${errorCodePrefix}_dispatch_role_not_ready`,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const confirmedRoleName = String(
|
||||
flow.binding.roleName || flow.role.name || task.role_name || ""
|
||||
).trim();
|
||||
const confirmedRoleId = String(
|
||||
flow.binding.roleId || flow.role.rid || task.role_id || ""
|
||||
).trim();
|
||||
const roleMatches = isSameKuaishouCloudRole(
|
||||
{
|
||||
name: confirmedRoleName,
|
||||
rid: confirmedRoleId,
|
||||
},
|
||||
{
|
||||
name: roleInfo.name,
|
||||
rid: roleInfo.rid,
|
||||
}
|
||||
);
|
||||
|
||||
if (!roleMatches) {
|
||||
await createTaskEvent(
|
||||
task.id,
|
||||
"kuaishou_cloud_dispatch_role_mismatch",
|
||||
{
|
||||
source,
|
||||
vnId: flow.binding.vnId,
|
||||
confirmedRoleName,
|
||||
confirmedRoleId,
|
||||
cloudtentaclesRoleName: roleInfo.name,
|
||||
cloudtentaclesRoleId: roleInfo.rid,
|
||||
actor: input.actor || null,
|
||||
},
|
||||
now
|
||||
);
|
||||
|
||||
throw createHttpError(
|
||||
`cloudtentacles 当前绑定角色(${roleInfo.name}/${roleInfo.rid})与客户确认角色(${confirmedRoleName || "-"}/${confirmedRoleId || "-"})不一致,请重新刷新角色后再发货`,
|
||||
{
|
||||
statusCode: 409,
|
||||
errorCode: `${errorCodePrefix}_dispatch_role_mismatch`,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const nextFlow = normalizeKuaishouCloudFlow({
|
||||
...flow,
|
||||
binding: {
|
||||
...flow.binding,
|
||||
roleName: roleInfo.name,
|
||||
roleId: roleInfo.rid,
|
||||
},
|
||||
role: {
|
||||
...flow.role,
|
||||
status: "ready",
|
||||
name: roleInfo.name,
|
||||
rid: roleInfo.rid,
|
||||
refreshedAt: now,
|
||||
errorMessage: "",
|
||||
rawInfo: roleInfo.rawInfo,
|
||||
},
|
||||
});
|
||||
const nextContext = {
|
||||
...taskContext,
|
||||
kuaishouCloudFulfillment: nextFlow,
|
||||
};
|
||||
|
||||
const updatedTask = await updateTask(task.id, {
|
||||
role_id: roleInfo.rid,
|
||||
role_name: roleInfo.name,
|
||||
context_json: JSON.stringify(nextContext),
|
||||
updated_at: now,
|
||||
});
|
||||
|
||||
if (!updatedTask) {
|
||||
throw createHttpError("发货前角色信息同步失败", {
|
||||
statusCode: 500,
|
||||
errorCode: `${errorCodePrefix}_dispatch_role_update_failed`,
|
||||
});
|
||||
}
|
||||
|
||||
await createTaskEvent(
|
||||
task.id,
|
||||
"kuaishou_cloud_role_info_synced_before_dispatch",
|
||||
{
|
||||
source,
|
||||
roleName: roleInfo.name,
|
||||
roleId: roleInfo.rid,
|
||||
vnId: flow.binding.vnId,
|
||||
actor: input.actor || null,
|
||||
},
|
||||
now
|
||||
);
|
||||
|
||||
return {
|
||||
task: updatedTask,
|
||||
taskContext: nextContext,
|
||||
flow: nextFlow,
|
||||
roleInfo,
|
||||
};
|
||||
}
|
||||
|
||||
function isSameKuaishouCloudRole(
|
||||
confirmedRole: { name: string; rid: string },
|
||||
cloudtentaclesRole: { name: string; rid: string }
|
||||
) {
|
||||
if (confirmedRole.rid && cloudtentaclesRole.rid) {
|
||||
return confirmedRole.rid === cloudtentaclesRole.rid;
|
||||
}
|
||||
|
||||
const confirmedName = normalizeKuaishouCloudRoleNameForCompare(
|
||||
confirmedRole.name
|
||||
);
|
||||
const cloudtentaclesName = normalizeKuaishouCloudRoleNameForCompare(
|
||||
cloudtentaclesRole.name
|
||||
);
|
||||
|
||||
if (!confirmedName || !cloudtentaclesName) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return confirmedName === cloudtentaclesName;
|
||||
}
|
||||
|
||||
function normalizeKuaishouCloudRoleNameForCompare(value: unknown) {
|
||||
const name = String(value || "").trim();
|
||||
if (!name) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const parts = name
|
||||
.split("-")
|
||||
.map((part) => part.trim())
|
||||
.filter(Boolean);
|
||||
const devicePart = parts[0] || "";
|
||||
const channelPart = parts[1] || "";
|
||||
if (
|
||||
parts.length >= 3 &&
|
||||
isKuaishouCloudDevicePart(devicePart) &&
|
||||
isKuaishouCloudChannelPart(channelPart)
|
||||
) {
|
||||
return normalizeTextForCompare(parts.slice(2).join("-"));
|
||||
}
|
||||
|
||||
return normalizeTextForCompare(name);
|
||||
}
|
||||
|
||||
function isKuaishouCloudDevicePart(value: string) {
|
||||
return ["安卓", "android", "ios", "苹果"].includes(value.toLowerCase());
|
||||
}
|
||||
|
||||
function isKuaishouCloudChannelPart(value: string) {
|
||||
return ["qq", "微信", "vx", "wechat", "v"].includes(value.toLowerCase());
|
||||
}
|
||||
|
||||
function normalizeTextForCompare(value: unknown) {
|
||||
return String(value || "")
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/\s+/g, "");
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
type JsonObject,
|
||||
} from "./domain.js";
|
||||
import { resolvePersistedCloudtentaclesContextBySourceKeys } from "./cloudtentacles-context.js";
|
||||
import { syncKuaishouCloudRoleInfoBeforeDispatch } from "./dispatch-role-sync.js";
|
||||
import { normalizeActor, parseTaskContext } from "./task-context.js";
|
||||
import type { TaskRow } from "../../../types/repository/rows.js";
|
||||
|
||||
@@ -68,25 +69,36 @@ export async function dispatchKuaishouCloudFulfillmentTask(
|
||||
return { task, flow };
|
||||
}
|
||||
|
||||
const synced = await syncKuaishouCloudRoleInfoBeforeDispatch(task, {
|
||||
now,
|
||||
actor,
|
||||
source: options.source || "system_before_dispatch",
|
||||
cloudContext,
|
||||
taskContext,
|
||||
flow,
|
||||
});
|
||||
const syncedFlow = synced.flow;
|
||||
const syncedTaskContext = synced.taskContext;
|
||||
|
||||
const dispatchResult = await useCloudtentaclesSku({
|
||||
...cloudContext,
|
||||
id: flow.binding.skuId,
|
||||
virtualNumberId: flow.binding.vnId,
|
||||
phone: flow.binding.vnPhone,
|
||||
id: syncedFlow.binding.skuId,
|
||||
virtualNumberId: syncedFlow.binding.vnId,
|
||||
phone: syncedFlow.binding.vnPhone,
|
||||
});
|
||||
|
||||
const nextContext = {
|
||||
...taskContext,
|
||||
...syncedTaskContext,
|
||||
kuaishouCloudFulfillment: {
|
||||
...flow,
|
||||
...syncedFlow,
|
||||
ticket: {
|
||||
...flow.ticket,
|
||||
...syncedFlow.ticket,
|
||||
code: ticketCode || persistedTicketCode,
|
||||
capturedAt: ticketCode ? now : flow.ticket.capturedAt,
|
||||
capturedBy: ticketCode && actor ? actor : flow.ticket.capturedBy,
|
||||
capturedAt: ticketCode ? now : syncedFlow.ticket.capturedAt,
|
||||
capturedBy: ticketCode && actor ? actor : syncedFlow.ticket.capturedBy,
|
||||
},
|
||||
dispatch: {
|
||||
...flow.dispatch,
|
||||
...syncedFlow.dispatch,
|
||||
status: "success",
|
||||
dispatchAt: now,
|
||||
dispatchBy: actor,
|
||||
@@ -127,9 +139,9 @@ export async function dispatchKuaishouCloudFulfillmentTask(
|
||||
{
|
||||
source: String(options.source || "system").trim() || "system",
|
||||
ticketCodeMasked: maskCode(ticketCode || persistedTicketCode),
|
||||
skuId: flow.binding.skuId,
|
||||
vnId: flow.binding.vnId,
|
||||
vnPhoneMasked: maskPhone(flow.binding.vnPhone),
|
||||
skuId: syncedFlow.binding.skuId,
|
||||
vnId: syncedFlow.binding.vnId,
|
||||
vnPhoneMasked: maskPhone(syncedFlow.binding.vnPhone),
|
||||
sendType: dispatchResult.sendType,
|
||||
note: dispatchResult.note,
|
||||
actor,
|
||||
|
||||
Reference in New Issue
Block a user