增加履约高级配置

This commit is contained in:
yml2213
2026-05-26 22:56:41 +08:00
parent 5377f8893d
commit e8010728a4
18 changed files with 858 additions and 62 deletions
@@ -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)) {