Files
order_site/apps/backend/src/services/fulfillment/kuaishou-cloud/domain.ts
T
2026-05-26 22:56:41 +08:00

276 lines
9.1 KiB
TypeScript

import { resolveCloudtentaclesConfig } from "../../platforms/cloudtentacles/helpers.js";
import {
maskCode as maskCodeValue,
maskPhone as maskPhoneValue,
} from "../../../utils/masking.js";
export const KUAISHOU_CLOUD_FIXED_VN_KEY = "1";
export type JsonObject = Record<string, any>;
export function isKuaishouCloudTask(task: unknown) {
const source = task && typeof task === "object" ? task as JsonObject : {};
return String(source.executor_key || "").trim() === "kuaishou_ct_assisted";
}
export function normalizeKuaishouCloudFlow(value: unknown) {
const source: JsonObject = value && typeof value === "object" ? value as JsonObject : {};
const binding =
source.binding && typeof source.binding === "object" ? source.binding : {};
const role =
source.role && typeof source.role === "object" ? source.role : {};
const purchase =
source.purchase && typeof source.purchase === "object"
? source.purchase
: {};
const dispatch =
source.dispatch && typeof source.dispatch === "object"
? source.dispatch
: {};
const returnNumber =
source.returnNumber && typeof source.returnNumber === "object"
? source.returnNumber
: {};
const consume =
source.consume && typeof source.consume === "object" ? source.consume : {};
const ticket =
source.ticket && typeof source.ticket === "object" ? source.ticket : {};
const deliveryItems = normalizeDeliveryItems(source, binding);
const roleName = String(role.name || binding.roleName || "").trim();
const roleId = String(role.rid || binding.roleId || "").trim();
const bindPreparedAt = binding.bindPreparedAt || null;
const bindExpiresAt =
binding.bindExpiresAt ||
resolveKuaishouCloudBindUrlExpiresAt(bindPreparedAt);
return {
...source,
configId: String(source.configId || "").trim(),
internalSkuCode: String(source.internalSkuCode || "").trim(),
internalSkuName: String(source.internalSkuName || "").trim(),
deliveryItems,
ticket: {
code: String(ticket.code || "").trim(),
status: String(ticket.status || "pending").trim() || "pending",
capturedAt: ticket.capturedAt || null,
capturedBy: ticket.capturedBy || null,
verifiedAt: ticket.verifiedAt || null,
oid: String(ticket.oid || "").trim(),
formToken: String(ticket.formToken || "").trim(),
leftCount: Number(ticket.leftCount || 0) || 0,
goodsTitle: String(ticket.goodsTitle || "").trim(),
},
binding: {
prepareStatus:
String(binding.prepareStatus || "pending").trim() || "pending",
cloudSourceKeys: normalizeStringArray(binding.cloudSourceKeys),
resolvedSourceKey: String(binding.resolvedSourceKey || "").trim(),
skuId: Number(binding.skuId || 0) || 0,
skuName: String(binding.skuName || "").trim(),
vnKey:
String(binding.vnKey || KUAISHOU_CLOUD_FIXED_VN_KEY).trim() ||
KUAISHOU_CLOUD_FIXED_VN_KEY,
vnId: Number(binding.vnId || 0) || 0,
vnPhone: String(binding.vnPhone || "").trim(),
bindUrl: String(binding.bindUrl || "").trim(),
bindPreparedAt,
bindExpiresAt,
bindProbeAt: binding.bindProbeAt || null,
bindProbeStatus: String(binding.bindProbeStatus || "").trim(),
bindProbeMessage: String(binding.bindProbeMessage || "").trim(),
roleName,
roleId,
},
role: {
status:
String(
role.status || (roleName || roleId ? "ready" : "pending")
).trim() || "pending",
name: roleName,
rid: roleId,
refreshedAt: role.refreshedAt || null,
errorMessage: String(role.errorMessage || "").trim(),
rawInfo:
role.rawInfo && typeof role.rawInfo === "object" ? role.rawInfo : null,
},
purchase: {
autoBuyEnabled: purchase.autoBuyEnabled !== false,
minAssetReserve: Number(purchase.minAssetReserve || 0) || 0,
usedKnapsack: purchase.usedKnapsack === true,
purchaseTriggered: purchase.purchaseTriggered === true,
assetBefore: Number(purchase.assetBefore || 0) || 0,
assetAfter: Number(purchase.assetAfter || 0) || 0,
purchaseAt: purchase.purchaseAt || null,
items: Array.isArray(purchase.items) ? purchase.items : [],
},
dispatch: {
status: String(dispatch.status || "pending").trim() || "pending",
dispatchAt: dispatch.dispatchAt || null,
dispatchBy: dispatch.dispatchBy || null,
sendType: Number(dispatch.sendType || 0) || 0,
note: String(dispatch.note || "").trim(),
items: Array.isArray(dispatch.items) ? dispatch.items : [],
},
returnNumber: {
status: String(returnNumber.status || "pending").trim() || "pending",
returnedAt: returnNumber.returnedAt || null,
returnedBy: returnNumber.returnedBy || null,
autoReturnEnabled: returnNumber.autoReturnEnabled === true,
},
consume: {
status: String(consume.status || "pending").trim() || "pending",
shopId: String(consume.shopId || "").trim(),
shopName: String(consume.shopName || "").trim(),
autoConsumeEnabled: consume.autoConsumeEnabled === true,
consumedAt: consume.consumedAt || null,
errorMessage: String(consume.errorMessage || "").trim(),
},
notes: String(source.notes || "").trim(),
};
}
export function normalizeKuaishouCloudDeliveryItems(value: unknown) {
const source: JsonObject = value && typeof value === "object" ? value as JsonObject : {};
const binding =
source.binding && typeof source.binding === "object" ? source.binding : {};
return normalizeDeliveryItems(source, binding);
}
export function normalizeKuaishouCloudRoleInfo(value: unknown) {
const rawInfo: JsonObject | null = value && typeof value === "object" ? value as JsonObject : null;
const nestedBindInfo =
rawInfo?.sBindInfo && typeof rawInfo.sBindInfo === "object"
? rawInfo.sBindInfo
: null;
const source = nestedBindInfo || rawInfo;
return {
name: String(
source?.name ||
source?.roleName ||
source?.nickname ||
source?.sRoleName ||
""
).trim(),
rid: String(
source?.rid ||
source?.roleId ||
source?.uid ||
source?.sRoleId ||
source?.sUserId ||
""
).trim(),
rawInfo,
};
}
export function maskPhone(value: unknown) {
return maskPhoneValue(value);
}
export function maskCode(value: unknown) {
return maskCodeValue(value);
}
export function resolveKuaishouCloudBindUrlExpiresAt(preparedAt: unknown) {
const preparedTime = Date.parse(String(preparedAt || ""));
if (!Number.isFinite(preparedTime)) {
return null;
}
const config = resolveCloudtentaclesConfig();
const ttlSeconds = Number(config.bindUrlTtlSeconds || 600);
return new Date(preparedTime + Math.max(1, ttlSeconds) * 1000).toISOString();
}
export function isKuaishouCloudBindUrlFresh(flow: unknown, now = new Date()) {
const normalizedFlow = normalizeKuaishouCloudFlow(flow);
if (!normalizedFlow.binding.bindUrl) {
return false;
}
const expiresAt = normalizedFlow.binding.bindExpiresAt;
if (!expiresAt) {
return false;
}
const expiresTime = Date.parse(String(expiresAt || ""));
if (!Number.isFinite(expiresTime)) {
return false;
}
return expiresTime > now.getTime();
}
export function normalizeStringArray(value: unknown) {
if (Array.isArray(value)) {
return value.map((v) => String(v || "").trim()).filter(Boolean);
}
if (typeof value === "string") {
return value
.split(",")
.map((v) => v.trim())
.filter(Boolean);
}
return [];
}
function normalizeDeliveryItems(source: JsonObject, binding: JsonObject) {
const rawItems = Array.isArray(source.deliveryItems) ? source.deliveryItems : [];
const items = rawItems
.map((item) => normalizeDeliveryItem(item))
.filter(Boolean) as Array<{ cloudSkuId: number; cloudSkuName: string; quantity: number }>;
if (items.length > 0) {
return mergeDeliveryItems(items);
}
const skuId = Number(binding.skuId || 0) || 0;
if (!skuId) {
return [];
}
return [
{
cloudSkuId: skuId,
cloudSkuName: String(binding.skuName || "").trim(),
quantity: 1,
},
];
}
function normalizeDeliveryItem(value: unknown) {
const source = value && typeof value === "object" ? value as JsonObject : {};
const cloudSkuId = Number(source.cloudSkuId || source.skuId || 0) || 0;
if (!Number.isInteger(cloudSkuId) || cloudSkuId <= 0) {
return null;
}
const quantity = Number(source.quantity || 1) || 1;
return {
cloudSkuId,
cloudSkuName: String(source.cloudSkuName || source.skuName || "").trim(),
quantity: Number.isInteger(quantity) && quantity > 0 ? quantity : 1,
};
}
function mergeDeliveryItems(
items: Array<{ cloudSkuId: number; cloudSkuName: string; quantity: number }>
) {
const merged = new Map<number, { cloudSkuId: number; cloudSkuName: string; quantity: number }>();
for (const item of items) {
const existing = merged.get(item.cloudSkuId);
if (existing) {
existing.quantity += item.quantity;
existing.cloudSkuName = existing.cloudSkuName || item.cloudSkuName;
continue;
}
merged.set(item.cloudSkuId, { ...item });
}
return Array.from(merged.values());
}