增加履约高级配置
This commit is contained in:
@@ -106,6 +106,7 @@ test('mapAdminKuaishouCloudFulfillmentSource normalizes items and defaults', ()
|
||||
cloudSourceKeys: ['account_a', 'account_b'],
|
||||
cloudSkuId: 0,
|
||||
cloudSkuName: '',
|
||||
deliveryItems: [],
|
||||
vnKey: '',
|
||||
autoBuyEnabled: true,
|
||||
minAssetReserve: 0,
|
||||
|
||||
@@ -62,6 +62,7 @@ export function mapAdminCloudtentaclesSession(session: JsonObject = {}) {
|
||||
}
|
||||
|
||||
export function mapAdminKuaishouCloudFulfillmentItem(item: JsonObject = {}) {
|
||||
const deliveryItems = normalizeDeliveryItems(item);
|
||||
return {
|
||||
id: String(item.id || "").trim(),
|
||||
enabled: item.enabled !== false,
|
||||
@@ -82,6 +83,7 @@ export function mapAdminKuaishouCloudFulfillmentItem(item: JsonObject = {}) {
|
||||
: [],
|
||||
cloudSkuId: Number(item.cloudSkuId || 0) || 0,
|
||||
cloudSkuName: String(item.cloudSkuName || "").trim(),
|
||||
deliveryItems,
|
||||
vnKey: String(item.vnKey || "").trim(),
|
||||
autoBuyEnabled: item.autoBuyEnabled !== false,
|
||||
minAssetReserve: Number(item.minAssetReserve || 0) || 0,
|
||||
@@ -101,3 +103,42 @@ export function mapAdminKuaishouCloudFulfillmentSource(config: JsonObject = {})
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeDeliveryItems(item: JsonObject = {}) {
|
||||
const rawItems = Array.isArray(item.deliveryItems) ? item.deliveryItems : [];
|
||||
const normalizedItems = rawItems
|
||||
.map((value) => normalizeDeliveryItem(value))
|
||||
.filter(Boolean);
|
||||
|
||||
if (normalizedItems.length > 0) {
|
||||
return normalizedItems;
|
||||
}
|
||||
|
||||
const cloudSkuId = Number(item.cloudSkuId || 0) || 0;
|
||||
if (cloudSkuId <= 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
cloudSkuId,
|
||||
cloudSkuName: String(item.cloudSkuName || "").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 (cloudSkuId <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const quantity = Number(source.quantity || 1) || 1;
|
||||
return {
|
||||
cloudSkuId,
|
||||
cloudSkuName: String(source.cloudSkuName || source.skuName || "").trim(),
|
||||
quantity: Math.max(1, Math.round(quantity)),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ export function normalizeKuaishouCloudFlow(value: unknown) {
|
||||
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();
|
||||
@@ -48,6 +49,7 @@ export function normalizeKuaishouCloudFlow(value: unknown) {
|
||||
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",
|
||||
@@ -100,6 +102,7 @@ export function normalizeKuaishouCloudFlow(value: unknown) {
|
||||
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",
|
||||
@@ -107,6 +110,7 @@ export function normalizeKuaishouCloudFlow(value: unknown) {
|
||||
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",
|
||||
@@ -126,6 +130,13 @@ export function normalizeKuaishouCloudFlow(value: unknown) {
|
||||
};
|
||||
}
|
||||
|
||||
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 =
|
||||
@@ -204,3 +215,61 @@ export function normalizeStringArray(value: unknown) {
|
||||
}
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -45,6 +45,16 @@ import {
|
||||
} from "./task-context.js";
|
||||
import type { TaskRow } from "../../../types/repository/rows.js";
|
||||
|
||||
type CloudDeliveryPlanItem = {
|
||||
cloudSkuId: number;
|
||||
cloudSkuName: string;
|
||||
quantity: number;
|
||||
skuItem: JsonObject | null;
|
||||
knapsackItem: JsonObject | null;
|
||||
knapsackCount: number;
|
||||
missingCount: number;
|
||||
};
|
||||
|
||||
export {
|
||||
isKuaishouCloudBindUrlFresh,
|
||||
isKuaishouCloudTask,
|
||||
@@ -167,8 +177,18 @@ export async function prepareKuaishouCloudFulfillmentTask(
|
||||
},
|
||||
};
|
||||
|
||||
const knapsackItem = resolvedBinding.knapsackItem;
|
||||
let usedKnapsack = Number(knapsackItem?.count || 0) > 0;
|
||||
const deliveryPlan = resolveKuaishouCloudDeliveryPlan(flowWithResolvedBinding, {
|
||||
skuItems: Array.isArray(skuList.items) ? skuList.items : [],
|
||||
knapsackItems: Array.isArray(knapsack.items) ? knapsack.items : [],
|
||||
});
|
||||
if (deliveryPlan.items.length === 0) {
|
||||
throw createHttpError("当前任务缺少 cloud 发货物品配置", {
|
||||
statusCode: 409,
|
||||
errorCode: "kuaishou_cloud_missing_delivery_items",
|
||||
});
|
||||
}
|
||||
|
||||
let usedKnapsack = deliveryPlan.items.every((item) => item.missingCount <= 0);
|
||||
let purchaseTriggered = false;
|
||||
let assetBefore = 0;
|
||||
let assetAfter = 0;
|
||||
@@ -182,11 +202,12 @@ export async function prepareKuaishouCloudFulfillmentTask(
|
||||
}
|
||||
|
||||
const asset = await getCloudtentaclesAsset(cloudContext);
|
||||
const targetSku = resolvedBinding.skuItem;
|
||||
|
||||
if (!targetSku) {
|
||||
const missingSku = deliveryPlan.items.find(
|
||||
(item) => item.missingCount > 0 && !item.skuItem
|
||||
);
|
||||
if (missingSku) {
|
||||
throw createHttpError(
|
||||
`cloudtentacles 未找到 SKU ${flowWithResolvedBinding.binding.skuId}`,
|
||||
`cloudtentacles 未找到 SKU ${missingSku.cloudSkuId}`,
|
||||
{
|
||||
statusCode: 404,
|
||||
errorCode: "kuaishou_cloud_sku_not_found",
|
||||
@@ -195,7 +216,11 @@ export async function prepareKuaishouCloudFulfillmentTask(
|
||||
}
|
||||
|
||||
assetBefore = Number(asset.asset || 0) || 0;
|
||||
const targetPrice = Number(targetSku.price || 0) || 0;
|
||||
const targetPrice = deliveryPlan.items.reduce(
|
||||
(sum, item) =>
|
||||
sum + Math.max(0, item.missingCount) * Number(item.skuItem?.price || 0),
|
||||
0
|
||||
);
|
||||
const requiredAsset =
|
||||
targetPrice + flowWithResolvedBinding.purchase.minAssetReserve;
|
||||
if (assetBefore < requiredAsset) {
|
||||
@@ -204,7 +229,10 @@ export async function prepareKuaishouCloudFulfillmentTask(
|
||||
flow: flowWithResolvedBinding,
|
||||
assetBefore,
|
||||
requiredAsset,
|
||||
skuName: targetSku.name,
|
||||
skuName: deliveryPlan.items
|
||||
.filter((item) => item.missingCount > 0)
|
||||
.map((item) => item.cloudSkuName || `SKU ${item.cloudSkuId}`)
|
||||
.join("、"),
|
||||
});
|
||||
throw createHttpError(
|
||||
`余额不足,当前 ${assetBefore},至少需要 ${requiredAsset}`,
|
||||
@@ -215,11 +243,17 @@ export async function prepareKuaishouCloudFulfillmentTask(
|
||||
);
|
||||
}
|
||||
|
||||
await buyCloudtentaclesSku({
|
||||
...cloudContext,
|
||||
id: flowWithResolvedBinding.binding.skuId,
|
||||
count: 1,
|
||||
});
|
||||
for (const item of deliveryPlan.items) {
|
||||
if (item.missingCount <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
await buyCloudtentaclesSku({
|
||||
...cloudContext,
|
||||
id: item.cloudSkuId,
|
||||
count: item.missingCount,
|
||||
});
|
||||
}
|
||||
purchaseTriggered = true;
|
||||
|
||||
const assetResult = await getCloudtentaclesAsset(cloudContext);
|
||||
@@ -265,6 +299,13 @@ export async function prepareKuaishouCloudFulfillmentTask(
|
||||
purchaseTriggered,
|
||||
assetBefore,
|
||||
assetAfter,
|
||||
items: deliveryPlan.items.map((item) => ({
|
||||
cloudSkuId: item.cloudSkuId,
|
||||
cloudSkuName: item.cloudSkuName,
|
||||
requiredCount: item.quantity,
|
||||
knapsackCount: item.knapsackCount,
|
||||
purchasedCount: Math.max(0, item.missingCount),
|
||||
})),
|
||||
purchaseAt: purchaseTriggered
|
||||
? now
|
||||
: flowWithResolvedBinding.purchase.purchaseAt,
|
||||
@@ -296,6 +337,13 @@ export async function prepareKuaishouCloudFulfillmentTask(
|
||||
vnPhoneMasked: maskPhone(preparedBinding.vnPhone),
|
||||
purchaseTriggered,
|
||||
usedKnapsack,
|
||||
deliveryItems: deliveryPlan.items.map((item) => ({
|
||||
cloudSkuId: item.cloudSkuId,
|
||||
cloudSkuName: item.cloudSkuName,
|
||||
quantity: item.quantity,
|
||||
knapsackCount: item.knapsackCount,
|
||||
purchasedCount: Math.max(0, item.missingCount),
|
||||
})),
|
||||
resolvedByName: resolvedBinding.resolvedByName,
|
||||
actor,
|
||||
},
|
||||
@@ -766,6 +814,69 @@ export async function refreshKuaishouCloudTaskRoleInfo(
|
||||
};
|
||||
}
|
||||
|
||||
function resolveKuaishouCloudDeliveryPlan(
|
||||
flow: JsonObject,
|
||||
{
|
||||
skuItems = [],
|
||||
knapsackItems = [],
|
||||
}: { skuItems?: unknown[]; knapsackItems?: unknown[] } = {}
|
||||
): { items: CloudDeliveryPlanItem[] } {
|
||||
const normalizedSkuItems = Array.isArray(skuItems)
|
||||
? skuItems.filter(isCloudSkuLikeItem)
|
||||
: [];
|
||||
const normalizedKnapsackItems = Array.isArray(knapsackItems)
|
||||
? knapsackItems.filter(isCloudSkuLikeItem)
|
||||
: [];
|
||||
const deliveryItems = Array.isArray(flow.deliveryItems)
|
||||
? flow.deliveryItems
|
||||
: [];
|
||||
|
||||
return {
|
||||
items: deliveryItems
|
||||
.map((item: unknown) => {
|
||||
const source = item && typeof item === "object" ? item as JsonObject : {};
|
||||
const cloudSkuId = Number(source.cloudSkuId || source.skuId || 0) || 0;
|
||||
const quantity = Number(source.quantity || 1) || 1;
|
||||
if (!cloudSkuId || !Number.isInteger(quantity) || quantity <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const skuItem =
|
||||
normalizedSkuItems.find(
|
||||
(sku) => Number(sku.id || 0) === cloudSkuId
|
||||
) || null;
|
||||
const knapsackItem =
|
||||
normalizedKnapsackItems.find(
|
||||
(sku) => Number(sku.id || 0) === cloudSkuId
|
||||
) || null;
|
||||
const knapsackCount = Math.max(0, Number(knapsackItem?.count || 0) || 0);
|
||||
const cloudSkuName = String(
|
||||
source.cloudSkuName ||
|
||||
source.skuName ||
|
||||
skuItem?.name ||
|
||||
knapsackItem?.name ||
|
||||
""
|
||||
).trim();
|
||||
|
||||
return {
|
||||
cloudSkuId,
|
||||
cloudSkuName,
|
||||
quantity,
|
||||
skuItem,
|
||||
knapsackItem,
|
||||
knapsackCount,
|
||||
missingCount: Math.max(0, quantity - knapsackCount),
|
||||
};
|
||||
})
|
||||
.filter((item): item is CloudDeliveryPlanItem => Boolean(item)),
|
||||
};
|
||||
}
|
||||
|
||||
function isCloudSkuLikeItem(item: unknown): item is JsonObject {
|
||||
const current = item && typeof item === "object" ? item as JsonObject : {};
|
||||
return Number(current.id || 0) > 0;
|
||||
}
|
||||
|
||||
export {
|
||||
dispatchKuaishouCloudFulfillmentTask,
|
||||
returnKuaishouCloudFulfillmentTask,
|
||||
|
||||
@@ -23,6 +23,19 @@ import { syncKuaishouCloudRoleInfoBeforeDispatch } from "./dispatch-role-sync.js
|
||||
import { normalizeActor, parseTaskContext } from "./task-context.js";
|
||||
import type { TaskRow } from "../../../types/repository/rows.js";
|
||||
|
||||
type DispatchDeliveryItem = {
|
||||
cloudSkuId: number;
|
||||
cloudSkuName: string;
|
||||
quantity: number;
|
||||
};
|
||||
|
||||
type DispatchResultItem = DispatchDeliveryItem & {
|
||||
unitIndex: number;
|
||||
sendType: number;
|
||||
note: string;
|
||||
responseMessage: string;
|
||||
};
|
||||
|
||||
export async function dispatchKuaishouCloudFulfillmentTask(
|
||||
task: TaskRow,
|
||||
options: JsonObject = {}
|
||||
@@ -80,12 +93,51 @@ export async function dispatchKuaishouCloudFulfillmentTask(
|
||||
const syncedFlow = synced.flow;
|
||||
const syncedTaskContext = synced.taskContext;
|
||||
|
||||
const dispatchResult = await useCloudtentaclesSku({
|
||||
...cloudContext,
|
||||
id: syncedFlow.binding.skuId,
|
||||
virtualNumberId: syncedFlow.binding.vnId,
|
||||
phone: syncedFlow.binding.vnPhone,
|
||||
});
|
||||
const deliveryItems = resolveDispatchDeliveryItems(syncedFlow);
|
||||
if (deliveryItems.length === 0) {
|
||||
throw createHttpError("当前任务缺少 cloud 发货物品配置", {
|
||||
statusCode: 409,
|
||||
errorCode: "kuaishou_cloud_missing_delivery_items",
|
||||
});
|
||||
}
|
||||
|
||||
const dispatchResults: DispatchResultItem[] = [];
|
||||
for (const item of deliveryItems) {
|
||||
for (let index = 0; index < item.quantity; index += 1) {
|
||||
const dispatchResult = await useCloudtentaclesSku({
|
||||
...cloudContext,
|
||||
id: item.cloudSkuId,
|
||||
virtualNumberId: syncedFlow.binding.vnId,
|
||||
phone: syncedFlow.binding.vnPhone,
|
||||
});
|
||||
dispatchResults.push({
|
||||
cloudSkuId: item.cloudSkuId,
|
||||
cloudSkuName: item.cloudSkuName,
|
||||
unitIndex: index + 1,
|
||||
quantity: item.quantity,
|
||||
sendType: Number(dispatchResult.sendType || 0) || 0,
|
||||
note: String(dispatchResult.note || "").trim(),
|
||||
responseMessage: String(dispatchResult.responseMessage || "").trim(),
|
||||
});
|
||||
}
|
||||
}
|
||||
const firstDispatchResult: DispatchResultItem = dispatchResults[0] || {
|
||||
cloudSkuId: syncedFlow.binding.skuId,
|
||||
cloudSkuName: syncedFlow.binding.skuName,
|
||||
quantity: 1,
|
||||
unitIndex: 1,
|
||||
sendType: 0,
|
||||
note: "",
|
||||
responseMessage: "",
|
||||
};
|
||||
const dispatchSummary =
|
||||
dispatchResults.length > 1
|
||||
? `cloudtentacles 发货成功,共 ${dispatchResults.length} 次`
|
||||
: String(
|
||||
firstDispatchResult.responseMessage ||
|
||||
firstDispatchResult.note ||
|
||||
"cloudtentacles 发货成功"
|
||||
).trim();
|
||||
|
||||
const nextContext = {
|
||||
...syncedTaskContext,
|
||||
@@ -102,12 +154,9 @@ export async function dispatchKuaishouCloudFulfillmentTask(
|
||||
status: "success",
|
||||
dispatchAt: now,
|
||||
dispatchBy: actor,
|
||||
sendType: Number(dispatchResult.sendType || 0) || 0,
|
||||
note: String(
|
||||
dispatchResult.note ||
|
||||
dispatchResult.responseMessage ||
|
||||
"cloudtentacles 发货成功"
|
||||
).trim(),
|
||||
sendType: Number(firstDispatchResult.sendType || 0) || 0,
|
||||
note: dispatchSummary,
|
||||
items: dispatchResults,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -116,11 +165,7 @@ export async function dispatchKuaishouCloudFulfillmentTask(
|
||||
task_status: "dispatched_pending_return",
|
||||
delivery_status: "delivered",
|
||||
result_code: "kuaishou_cloud_dispatched",
|
||||
result_message: String(
|
||||
dispatchResult.responseMessage ||
|
||||
dispatchResult.note ||
|
||||
"cloudtentacles 发货成功"
|
||||
).trim(),
|
||||
result_message: dispatchSummary,
|
||||
user_action_status: "not_required",
|
||||
last_error: "",
|
||||
context_json: JSON.stringify(nextContext),
|
||||
@@ -142,8 +187,10 @@ export async function dispatchKuaishouCloudFulfillmentTask(
|
||||
skuId: syncedFlow.binding.skuId,
|
||||
vnId: syncedFlow.binding.vnId,
|
||||
vnPhoneMasked: maskPhone(syncedFlow.binding.vnPhone),
|
||||
sendType: dispatchResult.sendType,
|
||||
note: dispatchResult.note,
|
||||
sendType: firstDispatchResult.sendType,
|
||||
note: dispatchSummary,
|
||||
deliveryItems,
|
||||
dispatchResults,
|
||||
actor,
|
||||
},
|
||||
now
|
||||
@@ -173,6 +220,43 @@ export async function dispatchKuaishouCloudFulfillmentTask(
|
||||
};
|
||||
}
|
||||
|
||||
function resolveDispatchDeliveryItems(flow: JsonObject): DispatchDeliveryItem[] {
|
||||
const rawItems = Array.isArray(flow.deliveryItems) ? flow.deliveryItems : [];
|
||||
const items = rawItems
|
||||
.map((item: unknown) => {
|
||||
const source = item && typeof item === "object" ? item as JsonObject : {};
|
||||
const cloudSkuId = Number(source.cloudSkuId || source.skuId || 0) || 0;
|
||||
const quantity = Number(source.quantity || 1) || 1;
|
||||
if (!Number.isInteger(cloudSkuId) || cloudSkuId <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
cloudSkuId,
|
||||
cloudSkuName: String(source.cloudSkuName || source.skuName || "").trim(),
|
||||
quantity: Number.isInteger(quantity) && quantity > 0 ? quantity : 1,
|
||||
};
|
||||
})
|
||||
.filter((item): item is DispatchDeliveryItem => Boolean(item));
|
||||
|
||||
if (items.length > 0) {
|
||||
return items;
|
||||
}
|
||||
|
||||
const fallbackSkuId = Number(flow?.binding?.skuId || 0) || 0;
|
||||
if (!fallbackSkuId) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
cloudSkuId: fallbackSkuId,
|
||||
cloudSkuName: String(flow?.binding?.skuName || "").trim(),
|
||||
quantity: 1,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export async function returnKuaishouCloudFulfillmentTask(
|
||||
task: TaskRow,
|
||||
options: JsonObject = {}
|
||||
|
||||
@@ -131,6 +131,12 @@ export async function syncDeliveryTasksForOrderWithDeps(
|
||||
const cloudtentaclesConfig = parseJsonObject(fulfillmentConfig.cloudtentacles)
|
||||
const kuaishouConsumeConfig = parseJsonObject(fulfillmentConfig.kuaishouConsume)
|
||||
const kuaishouShopConfig = parseJsonObject(fulfillmentConfig.kuaishouShop)
|
||||
const deliveryItems = normalizeCloudDeliveryItems(cloudtentaclesConfig)
|
||||
const primaryDeliveryItem = deliveryItems[0] || {
|
||||
cloudSkuId: Number(cloudtentaclesConfig.skuId || 0) || 0,
|
||||
cloudSkuName: String(cloudtentaclesConfig.skuName || '').trim(),
|
||||
quantity: 1,
|
||||
}
|
||||
|
||||
for (let index = 0; index < quantity; index += 1) {
|
||||
const createdAt = getNowIso()
|
||||
@@ -172,6 +178,7 @@ export async function syncDeliveryTasksForOrderWithDeps(
|
||||
configId: String(fulfillmentConfig.configId || '').trim(),
|
||||
internalSkuCode: item.sku_code,
|
||||
internalSkuName: item.sku_name,
|
||||
deliveryItems,
|
||||
ticket: {
|
||||
code: '',
|
||||
status: 'pending',
|
||||
@@ -186,8 +193,8 @@ export async function syncDeliveryTasksForOrderWithDeps(
|
||||
binding: {
|
||||
prepareStatus: 'pending',
|
||||
cloudSourceKeys: normalizeStringArray(cloudtentaclesConfig.cloudSourceKeys),
|
||||
skuId: Number(cloudtentaclesConfig.skuId || 0) || 0,
|
||||
skuName: String(cloudtentaclesConfig.skuName || '').trim(),
|
||||
skuId: primaryDeliveryItem.cloudSkuId,
|
||||
skuName: primaryDeliveryItem.cloudSkuName,
|
||||
vnKey: '1',
|
||||
vnId: 0,
|
||||
vnPhone: '',
|
||||
@@ -394,6 +401,68 @@ function normalizeStringArray(value: unknown): string[] {
|
||||
)
|
||||
}
|
||||
|
||||
function normalizeCloudDeliveryItems(value: JsonObject): Array<{
|
||||
cloudSkuId: number
|
||||
cloudSkuName: string
|
||||
quantity: number
|
||||
}> {
|
||||
const rawItems = Array.isArray(value.deliveryItems) ? value.deliveryItems : []
|
||||
const items = rawItems
|
||||
.map((item) => normalizeCloudDeliveryItem(item))
|
||||
.filter((item): item is { cloudSkuId: number; cloudSkuName: string; quantity: number } => Boolean(item))
|
||||
|
||||
if (items.length > 0) {
|
||||
return mergeCloudDeliveryItems(items)
|
||||
}
|
||||
|
||||
const cloudSkuId = Number(value.skuId || 0) || 0
|
||||
if (!cloudSkuId) {
|
||||
return []
|
||||
}
|
||||
|
||||
return [{
|
||||
cloudSkuId,
|
||||
cloudSkuName: String(value.skuName || '').trim(),
|
||||
quantity: 1,
|
||||
}]
|
||||
}
|
||||
|
||||
function normalizeCloudDeliveryItem(value: unknown) {
|
||||
const source = value && typeof value === 'object' && !Array.isArray(value)
|
||||
? 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 mergeCloudDeliveryItems(
|
||||
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())
|
||||
}
|
||||
|
||||
function isTaskRow(task: TaskRow | DeliveryTaskRow | null | undefined): task is TaskRow {
|
||||
return Boolean(task && Number(task.id || 0) > 0)
|
||||
}
|
||||
|
||||
@@ -32,6 +32,13 @@ test('mapKuaishouCloudFulfillmentItemsToBindings maps 91kaquan rules', () => {
|
||||
profileKey: 'kuaishou_ct_assisted',
|
||||
enabled: true,
|
||||
priority: 100,
|
||||
deliveryItems: [
|
||||
{
|
||||
cloudSkuId: 28,
|
||||
cloudSkuName: '',
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
config: {
|
||||
flowType: 'kuaishou_cloud_fulfillment',
|
||||
configId: '',
|
||||
@@ -39,6 +46,13 @@ test('mapKuaishouCloudFulfillmentItemsToBindings maps 91kaquan rules', () => {
|
||||
cloudSourceKeys: ['account_a', 'account_b'],
|
||||
skuId: 28,
|
||||
skuName: '',
|
||||
deliveryItems: [
|
||||
{
|
||||
cloudSkuId: 28,
|
||||
cloudSkuName: '',
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
vnKey: '1',
|
||||
autoBuyEnabled: true,
|
||||
minAssetReserve: 0,
|
||||
@@ -63,3 +77,32 @@ test('mapKuaishouCloudFulfillmentItemsToBindings maps 91kaquan rules', () => {
|
||||
],
|
||||
)
|
||||
})
|
||||
|
||||
test('mapKuaishouCloudFulfillmentItemsToBindings maps package delivery items', () => {
|
||||
const [binding] = mapKuaishouCloudFulfillmentItemsToBindings({
|
||||
items: [
|
||||
{
|
||||
enabled: true,
|
||||
internalSkuCode: 'package-1',
|
||||
internalSkuName: '套餐 1',
|
||||
externalSkuCode: 'P001',
|
||||
cloudSourceKeys: ['account_a'],
|
||||
deliveryItems: [
|
||||
{ cloudSkuId: 73, cloudSkuName: '物品 A', quantity: 1 },
|
||||
{ cloudSkuId: 76, cloudSkuName: '物品 B', quantity: 50 },
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
assert.deepEqual(binding?.deliveryItems, [
|
||||
{ cloudSkuId: 73, cloudSkuName: '物品 A', quantity: 1 },
|
||||
{ cloudSkuId: 76, cloudSkuName: '物品 B', quantity: 50 },
|
||||
])
|
||||
assert.deepEqual(binding?.config.cloudtentacles.deliveryItems, [
|
||||
{ cloudSkuId: 73, cloudSkuName: '物品 A', quantity: 1 },
|
||||
{ cloudSkuId: 76, cloudSkuName: '物品 B', quantity: 50 },
|
||||
])
|
||||
assert.equal(binding?.config.cloudtentacles.skuId, 73)
|
||||
assert.equal(binding?.config.cloudtentacles.skuName, '物品 A')
|
||||
})
|
||||
|
||||
@@ -12,6 +12,12 @@ const KUAISHOU_CLOUD_FULFILLMENT_FILE_PATH = path.join(
|
||||
|
||||
type JsonObject = Record<string, any>;
|
||||
|
||||
type DeliveryItem = {
|
||||
cloudSkuId: number;
|
||||
cloudSkuName: string;
|
||||
quantity: number;
|
||||
};
|
||||
|
||||
export function getKuaishouCloudFulfillmentFilePath() {
|
||||
return KUAISHOU_CLOUD_FULFILLMENT_FILE_PATH;
|
||||
}
|
||||
@@ -42,13 +48,19 @@ export function mapKuaishouCloudFulfillmentItemsToBindings(config: JsonObject =
|
||||
profileKey: "kuaishou_ct_assisted",
|
||||
enabled: item.enabled !== false,
|
||||
priority: normalizePriority(item.priority),
|
||||
deliveryItems: normalizeDeliveryItems(item),
|
||||
config: {
|
||||
flowType: "kuaishou_cloud_fulfillment",
|
||||
configId: String(item.id || "").trim(),
|
||||
cloudtentacles: {
|
||||
cloudSourceKeys: normalizeStringArray(item.cloudSourceKeys),
|
||||
skuId: normalizePositiveInteger(item.cloudSkuId),
|
||||
skuName: String(item.cloudSkuName || "").trim(),
|
||||
skuId:
|
||||
normalizeDeliveryItems(item)[0]?.cloudSkuId ||
|
||||
normalizePositiveInteger(item.cloudSkuId),
|
||||
skuName:
|
||||
normalizeDeliveryItems(item)[0]?.cloudSkuName ||
|
||||
String(item.cloudSkuName || "").trim(),
|
||||
deliveryItems: normalizeDeliveryItems(item),
|
||||
vnKey: "1",
|
||||
autoBuyEnabled: item.autoBuyEnabled !== false,
|
||||
minAssetReserve: normalizeNonNegativeInteger(item.minAssetReserve, 0),
|
||||
@@ -109,6 +121,7 @@ function normalizeKuaishouCloudFulfillmentItem(rawValue: unknown) {
|
||||
|
||||
const internalSkuCode = String(rawValue.internalSkuCode || "").trim();
|
||||
const cloudSkuId = normalizePositiveInteger(rawValue.cloudSkuId);
|
||||
const deliveryItems = normalizeDeliveryItems(rawValue);
|
||||
const cloudSourceKeys = normalizeStringArray(rawValue.cloudSourceKeys);
|
||||
const externalSkuCode = String(rawValue.externalSkuCode || "").trim();
|
||||
const externalItemId = String(rawValue.externalItemId || "").trim();
|
||||
@@ -128,7 +141,7 @@ function normalizeKuaishouCloudFulfillmentItem(rawValue: unknown) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!cloudSkuId) {
|
||||
if (deliveryItems.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -157,8 +170,11 @@ function normalizeKuaishouCloudFulfillmentItem(rawValue: unknown) {
|
||||
rawValue.resolvedSkuName || rawValue.internalSkuName || ""
|
||||
).trim(),
|
||||
cloudSourceKeys,
|
||||
cloudSkuId,
|
||||
cloudSkuName: String(rawValue.cloudSkuName || "").trim(),
|
||||
cloudSkuId: deliveryItems[0]?.cloudSkuId || cloudSkuId,
|
||||
cloudSkuName:
|
||||
deliveryItems[0]?.cloudSkuName ||
|
||||
String(rawValue.cloudSkuName || "").trim(),
|
||||
deliveryItems,
|
||||
vnKey: "1",
|
||||
autoBuyEnabled: rawValue.autoBuyEnabled !== false,
|
||||
minAssetReserve: normalizeNonNegativeInteger(rawValue.minAssetReserve, 0),
|
||||
@@ -185,6 +201,64 @@ function normalizeNonNegativeInteger(value: unknown, fallback: number) {
|
||||
return Number.isInteger(parsed) && parsed >= 0 ? parsed : fallback;
|
||||
}
|
||||
|
||||
function normalizeDeliveryItems(value: JsonObject): DeliveryItem[] {
|
||||
const rawItems = Array.isArray(value.deliveryItems) ? value.deliveryItems : [];
|
||||
const items = rawItems
|
||||
.map((item: unknown) => normalizeDeliveryItem(item))
|
||||
.filter(Boolean) as DeliveryItem[];
|
||||
|
||||
if (items.length > 0) {
|
||||
return mergeDeliveryItems(items);
|
||||
}
|
||||
|
||||
const cloudSkuId = normalizePositiveInteger(value.cloudSkuId);
|
||||
if (!cloudSkuId) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
cloudSkuId,
|
||||
cloudSkuName: String(value.cloudSkuName || "").trim(),
|
||||
quantity: 1,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function normalizeDeliveryItem(value: unknown): DeliveryItem | null {
|
||||
if (!isPlainObject(value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const cloudSkuId = normalizePositiveInteger(value.cloudSkuId || value.skuId);
|
||||
if (!cloudSkuId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
cloudSkuId,
|
||||
cloudSkuName: String(value.cloudSkuName || value.skuName || "").trim(),
|
||||
quantity: normalizePositiveInteger(value.quantity) || 1,
|
||||
};
|
||||
}
|
||||
|
||||
function mergeDeliveryItems(items: DeliveryItem[]): DeliveryItem[] {
|
||||
const merged = new Map<number, DeliveryItem>();
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
function normalizePriority(value: unknown) {
|
||||
const parsed = Number(value);
|
||||
if (!Number.isFinite(parsed)) {
|
||||
|
||||
Reference in New Issue
Block a user