删除旧履约配置逻辑

This commit is contained in:
yml
2026-05-27 13:12:07 +08:00
parent dd6776c4e4
commit c7e2a225bc
34 changed files with 13 additions and 3215 deletions
@@ -52,54 +52,6 @@ CREATE TABLE IF NOT EXISTS fulfillment_profile_requirements (
UNIQUE(profile_id, role_key)
);
CREATE TABLE IF NOT EXISTS sku_fulfillment_bindings (
id BIGSERIAL PRIMARY KEY,
sku_code TEXT NOT NULL,
provider TEXT NOT NULL DEFAULT '',
platform TEXT NOT NULL DEFAULT '',
shop_id TEXT NOT NULL DEFAULT '',
profile_id BIGINT NOT NULL REFERENCES fulfillment_profiles(id) ON DELETE CASCADE,
enabled BOOLEAN NOT NULL DEFAULT TRUE,
priority INTEGER NOT NULL DEFAULT 100,
config_json JSONB NOT NULL DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_sku_fulfillment_bindings_lookup
ON sku_fulfillment_bindings(sku_code, provider, platform, shop_id, enabled, priority);
CREATE TABLE IF NOT EXISTS product_match_rules (
id BIGSERIAL PRIMARY KEY,
provider TEXT NOT NULL DEFAULT '',
platform TEXT NOT NULL DEFAULT '',
shop_id TEXT NOT NULL DEFAULT '',
external_item_id TEXT NOT NULL DEFAULT '',
external_sku_code TEXT NOT NULL DEFAULT '',
external_sku_name TEXT NOT NULL DEFAULT '',
external_sku_name_normalized TEXT NOT NULL DEFAULT '',
resolved_sku_code TEXT NOT NULL,
enabled BOOLEAN NOT NULL DEFAULT TRUE,
priority INTEGER NOT NULL DEFAULT 100,
config_json JSONB NOT NULL DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_product_match_rules_unique
ON product_match_rules(
provider,
platform,
shop_id,
external_item_id,
external_sku_code,
external_sku_name_normalized,
resolved_sku_code
);
CREATE INDEX IF NOT EXISTS idx_product_match_rules_lookup
ON product_match_rules(provider, platform, shop_id, enabled, priority, id);
CREATE TABLE IF NOT EXISTS orders (
id BIGSERIAL PRIMARY KEY,
provider TEXT NOT NULL,
@@ -0,0 +1,2 @@
DROP TABLE IF EXISTS product_match_rules;
DROP TABLE IF EXISTS sku_fulfillment_bindings;
@@ -27,28 +27,6 @@ type FulfillmentProfileRequirementRow = {
updated_at: string
}
type SkuFulfillmentBindingRow = {
id: number
sku_code: string
provider: string
platform: string
shop_id: string
profile_id: number
enabled: boolean
priority: number
config_json: string | Record<string, unknown>
created_at: string
updated_at: string
profile_key?: string
name?: string
profile_name?: string
executor_key?: string
requires_claim?: boolean
auto_dispatch?: boolean
inventory_strategy?: string
profile_config_json?: string | Record<string, unknown>
}
type FulfillmentProfileUpsertInput = {
profileKey: string
name: string
@@ -69,26 +47,6 @@ export type FulfillmentProfileRequirementInput = {
configJson?: string | Record<string, unknown>
}
type SkuFulfillmentBindingUpsertInput = {
skuCode: string
provider?: string
platform?: string
shopId?: string
profileId: number | string
enabled?: boolean
priority?: number | string
configJson?: string | Record<string, unknown>
createdAt: string
updatedAt: string
}
type FulfillmentBindingResolveInput = {
skuCode: string
provider?: string
platform?: string
shopId?: string
}
export async function getFulfillmentProfileByKey(profileKey: string): Promise<FulfillmentProfileRow | null> {
const result = await query<FulfillmentProfileRow>(
'SELECT * FROM fulfillment_profiles WHERE profile_key = $1 LIMIT 1',
@@ -195,113 +153,3 @@ export async function listFulfillmentProfileRequirements(
return result.rows
}
export async function upsertSkuFulfillmentBinding(
input: SkuFulfillmentBindingUpsertInput,
): Promise<SkuFulfillmentBindingRow | null> {
const existing = await query<SkuFulfillmentBindingRow>(
`
SELECT *
FROM sku_fulfillment_bindings
WHERE sku_code = $1 AND provider = $2 AND platform = $3 AND shop_id = $4
LIMIT 1
`,
[input.skuCode, input.provider || '', input.platform || '', input.shopId || ''],
)
if (!existing.rows[0]) {
const inserted = await query<SkuFulfillmentBindingRow>(
`
INSERT INTO sku_fulfillment_bindings (
sku_code,
provider,
platform,
shop_id,
profile_id,
enabled,
priority,
config_json,
created_at,
updated_at
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8::jsonb, $9, $10)
RETURNING *
`,
[
input.skuCode,
input.provider || '',
input.platform || '',
input.shopId || '',
Number(input.profileId),
input.enabled !== false,
Number(input.priority || 100),
input.configJson || '{}',
input.createdAt,
input.updatedAt,
],
)
return inserted.rows[0] || null
}
const updated = await query<SkuFulfillmentBindingRow>(
`
UPDATE sku_fulfillment_bindings
SET
profile_id = $1,
enabled = $2,
priority = $3,
config_json = $4::jsonb,
updated_at = $5
WHERE id = $6
RETURNING *
`,
[
Number(input.profileId),
input.enabled !== false,
Number(input.priority || 100),
input.configJson || '{}',
input.updatedAt,
Number(existing.rows[0].id),
],
)
return updated.rows[0] || null
}
export async function resolveFulfillmentBinding({
skuCode,
provider = '',
platform = '',
shopId = '',
}: FulfillmentBindingResolveInput): Promise<SkuFulfillmentBindingRow | null> {
const result = await query<SkuFulfillmentBindingRow>(
`
SELECT
sfb.*,
fp.profile_key,
fp.name AS profile_name,
fp.executor_key,
fp.requires_claim,
fp.auto_dispatch,
fp.inventory_strategy,
fp.config_json AS profile_config_json
FROM sku_fulfillment_bindings sfb
JOIN fulfillment_profiles fp ON fp.id = sfb.profile_id
WHERE sfb.enabled = TRUE
AND sfb.sku_code = $1
AND (sfb.provider = '' OR sfb.provider = $2)
AND (sfb.platform = '' OR sfb.platform = $3)
AND (sfb.shop_id = '' OR sfb.shop_id = $4)
ORDER BY
CASE WHEN sfb.shop_id = '' THEN 1 ELSE 0 END,
CASE WHEN sfb.platform = '' THEN 1 ELSE 0 END,
CASE WHEN sfb.provider = '' THEN 1 ELSE 0 END,
sfb.priority ASC,
sfb.id ASC
LIMIT 1
`,
[skuCode, provider, platform, shopId],
)
return result.rows[0] || null
}
@@ -17,13 +17,6 @@ type OrderPlatformLookupInput = {
platformOrderId: string
}
type OrderPlatformCandidateLookupInput = {
provider?: string
platform?: string
shopIds?: unknown[]
platformOrderId?: string
}
export async function findOrderByPlatformOrderId({
provider = '91kaquan',
platform,
@@ -43,34 +36,6 @@ export async function findOrderByPlatformOrderId({
return result.rows[0] || null
}
export async function findOrderByPlatformOrderIdCandidates({
provider = '91kaquan',
platform,
shopIds = [],
platformOrderId,
}: OrderPlatformCandidateLookupInput): Promise<OrderRow | null> {
const normalizedShopIds = [...new Set((Array.isArray(shopIds) ? shopIds : [])
.map((item) => String(item || '').trim())
.filter(Boolean))]
if (!platform || !platformOrderId || normalizedShopIds.length === 0) {
return null
}
const result = await query<OrderRow>(
`
SELECT *
FROM orders
WHERE provider = $1 AND platform = $2 AND shop_id = ANY($3::text[]) AND platform_order_id = $4
ORDER BY id DESC
LIMIT 1
`,
[provider, platform, normalizedShopIds, platformOrderId],
)
return result.rows[0] || null
}
export async function createOrder(input: OrderCreateInput): Promise<OrderRow | null> {
const result = await query<OrderRow>(
`
@@ -1,169 +0,0 @@
import { query } from '../db/client.js'
type ProductMatchRuleRow = {
id: number
provider: string
platform: string
shop_id: string
external_item_id: string
external_sku_code: string
external_sku_name: string
external_sku_name_normalized: string
resolved_sku_code: string
enabled: boolean
priority: number
config_json: string | Record<string, unknown>
created_at: string
updated_at: string
matched_by?: string
match_score?: number
}
type ProductMatchRuleUpsertInput = {
provider?: string
platform?: string
shopId?: string
externalItemId?: string
externalSkuCode?: string
externalSkuName?: string
externalSkuNameNormalized?: string
resolvedSkuCode: string
enabled?: boolean
priority?: number | string
configJson?: string | Record<string, unknown>
createdAt: string
updatedAt: string
}
type ProductMatchRuleResolveInput = {
provider?: string
platform?: string
shopId?: string
externalItemId?: string
externalSkuCode?: string
externalSkuNameNormalized?: string
}
export async function upsertProductMatchRule(
input: ProductMatchRuleUpsertInput,
): Promise<ProductMatchRuleRow | null> {
const result = await query<ProductMatchRuleRow>(
`
INSERT INTO product_match_rules (
provider,
platform,
shop_id,
external_item_id,
external_sku_code,
external_sku_name,
external_sku_name_normalized,
resolved_sku_code,
enabled,
priority,
config_json,
created_at,
updated_at
) VALUES (
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11::jsonb, $12, $13
)
ON CONFLICT (
provider,
platform,
shop_id,
external_item_id,
external_sku_code,
external_sku_name_normalized,
resolved_sku_code
) DO UPDATE
SET
external_sku_name = EXCLUDED.external_sku_name,
enabled = EXCLUDED.enabled,
priority = EXCLUDED.priority,
config_json = EXCLUDED.config_json,
updated_at = EXCLUDED.updated_at
RETURNING *
`,
[
input.provider || '',
input.platform || '',
input.shopId || '',
input.externalItemId || '',
input.externalSkuCode || '',
input.externalSkuName || '',
input.externalSkuNameNormalized || '',
input.resolvedSkuCode,
input.enabled !== false,
Number(input.priority || 100),
input.configJson || '{}',
input.createdAt,
input.updatedAt,
],
)
return result.rows[0] || null
}
export async function resolveProductMatchRule({
provider = '',
platform = '',
shopId = '',
externalItemId = '',
externalSkuCode = '',
externalSkuNameNormalized = '',
}: ProductMatchRuleResolveInput): Promise<ProductMatchRuleRow | null> {
const result = await query<ProductMatchRuleRow>(
`
SELECT
pmr.*,
CASE
WHEN $4 != '' AND pmr.external_sku_code = $4 THEN 'external_sku_code'
WHEN $5 != '' AND pmr.external_item_id = $5 THEN 'external_item_id'
WHEN $6 != '' AND pmr.external_sku_name_normalized = $6 THEN 'external_sku_name_exact'
WHEN $6 != '' AND pmr.external_sku_name_normalized != '' AND (
position(pmr.external_sku_name_normalized in $6) > 0
OR position($6 in pmr.external_sku_name_normalized) > 0
) THEN 'external_sku_name_contains'
ELSE ''
END AS matched_by,
CASE
WHEN $4 != '' AND pmr.external_sku_code = $4 THEN 400
WHEN $5 != '' AND pmr.external_item_id = $5 THEN 300
WHEN $6 != '' AND pmr.external_sku_name_normalized = $6 THEN 200
WHEN $6 != '' AND pmr.external_sku_name_normalized != '' AND (
position(pmr.external_sku_name_normalized in $6) > 0
OR position($6 in pmr.external_sku_name_normalized) > 0
) THEN 100
ELSE 0
END AS match_score
FROM product_match_rules pmr
WHERE pmr.enabled = TRUE
AND (pmr.provider = '' OR pmr.provider = $1)
AND (pmr.platform = '' OR pmr.platform = $2)
AND (pmr.shop_id = '' OR pmr.shop_id = $3)
AND (
($4 != '' AND pmr.external_sku_code = $4)
OR ($5 != '' AND pmr.external_item_id = $5)
OR (
$6 != ''
AND pmr.external_sku_name_normalized != ''
AND (
pmr.external_sku_name_normalized = $6
OR position(pmr.external_sku_name_normalized in $6) > 0
OR position($6 in pmr.external_sku_name_normalized) > 0
)
)
)
ORDER BY
match_score DESC,
CASE WHEN pmr.shop_id = '' THEN 1 ELSE 0 END,
CASE WHEN pmr.platform = '' THEN 1 ELSE 0 END,
CASE WHEN pmr.provider = '' THEN 1 ELSE 0 END,
pmr.priority ASC,
pmr.id ASC
LIMIT 1
`,
[provider, platform, shopId, externalSkuCode, externalItemId, externalSkuNameNormalized],
)
return result.rows[0] || null
}
@@ -2,7 +2,6 @@ import { Router } from "express";
import { requireAdminRoles } from "./session.js";
import cloudtentaclesRouter from "./platform-config/cloudtentacles.js";
import kuaishouCloudFulfillmentRouter from "./platform-config/kuaishou-cloud-fulfillment.js";
import kuaishouEticketRouter from "./platform-config/kuaishou-eticket.js";
import ninetyoneRouter from "./platform-config/ninetyone.js";
import notificationsRouter from "./platform-config/notifications.js";
@@ -14,6 +13,5 @@ router.use("/platform-config", notificationsRouter);
router.use("/platform-config", kuaishouEticketRouter);
router.use("/platform-config", ninetyoneRouter);
router.use("/platform-config", cloudtentaclesRouter);
router.use("/platform-config", kuaishouCloudFulfillmentRouter);
export default router;
@@ -1,51 +0,0 @@
import { Router } from "express";
import {
getAdminKuaishouCloudFulfillmentConfig,
updateAdminKuaishouCloudFulfillmentConfig,
} from "../../../services/admin/platform-config/kuaishou-cloud-fulfillment-service.js";
import type { AdminKuaishouCloudFulfillmentConfigRouteBody } from "../../../types/admin/route-inputs.js";
import { createJsonHandler } from "../session.js";
import type { JsonRecord } from "../../../types/json.js";
const router = Router();
router.get(
"/kuaishou-cloud-fulfillment",
createJsonHandler(() => getAdminKuaishouCloudFulfillmentConfig(), {
successMessage: "ok",
errorMessage: "读取新履约配置失败",
scope: "[admin/platform-config/kuaishou-cloud-fulfillment]",
})
);
router.post(
"/kuaishou-cloud-fulfillment",
createJsonHandler(
(req) =>
updateAdminKuaishouCloudFulfillmentConfig(
req.body as AdminKuaishouCloudFulfillmentConfigRouteBody
),
{
successMessage: "新履约配置已保存",
errorMessage: "保存新履约配置失败",
scope: "[admin/platform-config/kuaishou-cloud-fulfillment]",
audit: (_req, data) => {
const result = data as JsonRecord;
return {
action: "platform_kuaishou_cloud_fulfillment_updated",
targetType: "platform_config",
targetId: "kuaishou_cloud_fulfillment",
data: {
itemCount: Array.isArray(result.source?.items)
? result.source.items.length
: 0,
filePath: String(result.filePath || "").trim(),
},
};
},
}
)
);
export default router;
@@ -3,7 +3,6 @@ import assert from 'node:assert/strict'
import {
mapAdminCloudtentaclesSession,
mapAdminKuaishouCloudFulfillmentSource,
mapAdminKuaishouEticketSourceConfig,
maskPhone,
maskSecret,
@@ -69,54 +68,3 @@ test('mapAdminCloudtentaclesSession derives masked fields and token presence', (
},
)
})
test('mapAdminKuaishouCloudFulfillmentSource normalizes items and defaults', () => {
assert.deepEqual(
mapAdminKuaishouCloudFulfillmentSource({
enabled: true,
items: [
{
id: ' item-1 ',
provider: '',
platform: '',
cloudSourceKeys: [' account_a ', 'account_b'],
cloudSkuId: '0',
autoConsumeAfterDispatch: true,
kuaishouConsumeShopId: ' ks1 ',
notes: ' note ',
},
],
}),
{
enabled: true,
items: [
{
id: 'item-1',
enabled: true,
priority: 100,
provider: '91kaquan',
platform: 'kuaishou',
shopId: '',
internalSkuCode: '',
internalSkuName: '',
externalSkuCode: '',
externalItemId: '',
externalSkuName: '',
resolvedSkuName: '',
cloudSourceKeys: ['account_a', 'account_b'],
cloudSkuId: 0,
cloudSkuName: '',
deliveryItems: [],
vnKey: '',
autoBuyEnabled: true,
minAssetReserve: 0,
autoReturnNumberAfterDispatch: false,
autoConsumeAfterDispatch: true,
kuaishouConsumeShopId: 'ks1',
kuaishouConsumeShopName: '',
notes: 'note',
},
],
},
)
})
@@ -60,85 +60,3 @@ export function mapAdminCloudtentaclesSession(session: JsonObject = {}) {
hasToken: Boolean(String(session.token || "").trim()),
};
}
export function mapAdminKuaishouCloudFulfillmentItem(item: JsonObject = {}) {
const deliveryItems = normalizeDeliveryItems(item);
return {
id: String(item.id || "").trim(),
enabled: item.enabled !== false,
priority: Number(item.priority || 100) || 100,
provider: String(item.provider || "91kaquan").trim() || "91kaquan",
platform: String(item.platform || "kuaishou").trim() || "kuaishou",
shopId: String(item.shopId || "").trim(),
internalSkuCode: String(item.internalSkuCode || "").trim(),
internalSkuName: String(item.internalSkuName || "").trim(),
externalSkuCode: String(item.externalSkuCode || "").trim(),
externalItemId: String(item.externalItemId || "").trim(),
externalSkuName: String(item.externalSkuName || "").trim(),
resolvedSkuName: String(item.resolvedSkuName || "").trim(),
cloudSourceKeys: Array.isArray(item.cloudSourceKeys)
? item.cloudSourceKeys
.map((value) => String(value || "").trim())
.filter(Boolean)
: [],
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,
autoReturnNumberAfterDispatch: item.autoReturnNumberAfterDispatch === true,
autoConsumeAfterDispatch: item.autoConsumeAfterDispatch === true,
kuaishouConsumeShopId: String(item.kuaishouConsumeShopId || "").trim(),
kuaishouConsumeShopName: String(item.kuaishouConsumeShopName || "").trim(),
notes: String(item.notes || "").trim(),
};
}
export function mapAdminKuaishouCloudFulfillmentSource(config: JsonObject = {}) {
return {
enabled: config.enabled !== false,
items: (Array.isArray(config.items) ? config.items : []).map((item) =>
mapAdminKuaishouCloudFulfillmentItem(item)
),
};
}
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)),
};
}
@@ -1,32 +0,0 @@
import {
getKuaishouCloudFulfillmentConfig,
getKuaishouCloudFulfillmentFilePath,
saveKuaishouCloudFulfillmentConfig,
} from '../../order/kuaishou-cloud-fulfillment-config-service.js'
import { mapAdminKuaishouCloudFulfillmentSource } from './cloudtentacles/mappers.js'
type JsonObject = Record<string, any>
export function getAdminKuaishouCloudFulfillmentConfig() {
const config = getKuaishouCloudFulfillmentConfig()
return {
filePath: getKuaishouCloudFulfillmentFilePath(),
source: mapAdminKuaishouCloudFulfillmentSource(config),
}
}
/** @param {AdminKuaishouCloudFulfillmentConfigInput} [payload] */
export async function updateAdminKuaishouCloudFulfillmentConfig(
payload: JsonObject = {},
) {
const saved = saveKuaishouCloudFulfillmentConfig({
enabled: payload.enabled !== false,
items: Array.isArray(payload.items) ? payload.items : [],
})
return {
filePath: getKuaishouCloudFulfillmentFilePath(),
source: mapAdminKuaishouCloudFulfillmentSource(saved),
}
}
@@ -7,7 +7,6 @@ import { formatFenToAmount, normalizeFen } from '../../utils/money.js'
import { parseTaskContext as parseTaskContextValue } from '../../utils/task-json.js'
import { nowIso } from '../../utils/time.js'
import { buildClaimUrl } from './claim-service.js'
import { getKuaishouCloudFulfillmentConfig } from '../order/kuaishou-cloud-fulfillment-config-service.js'
import type { ClaimTokenRow, OrderItemRow, OrderRow, TaskRow } from '../../types/repository/rows.js'
export const CLAIM_TERMINAL_STATUSES = new Set(['expired', 'closed'])
@@ -144,13 +143,9 @@ function resolveClaimOrderItemDisplaySkuName(
const fulfillment = isPlainObject(kuaishouCloudFulfillment) ? kuaishouCloudFulfillment : {}
const binding = isPlainObject(fulfillment.binding) ? fulfillment.binding : {}
const ticket = isPlainObject(fulfillment.ticket) ? fulfillment.ticket : {}
const matchedConfigItem = resolveClaimFulfillmentConfigItem(orderItem, fulfillment, binding)
const skuCode = String(orderItem.sku_code || '').trim()
const rawSkuName = String(orderItem.sku_name || '').trim()
const candidates = [
matchedConfigItem?.internalSkuName,
matchedConfigItem?.cloudSkuName,
matchedConfigItem?.resolvedSkuName,
fulfillment.internalSkuName,
binding.skuName,
ticket.goodsTitle,
@@ -168,40 +163,6 @@ function resolveClaimOrderItemDisplaySkuName(
return rawSkuName || skuCode
}
function resolveClaimFulfillmentConfigItem(
orderItem: OrderItemRow,
fulfillment: JsonObject,
binding: JsonObject,
) {
const config = getKuaishouCloudFulfillmentConfig()
const rawItems: unknown[] = Array.isArray(config.items) ? config.items : []
const items = rawItems.filter(isPlainObject)
const configId = String(fulfillment.configId || '').trim()
const skuCode = String(orderItem.sku_code || '').trim()
const skuName = String(orderItem.sku_name || '').trim()
const cloudSkuId = Number(binding.skuId || 0) || 0
return items.find((item) => {
if (configId && String(item.id || '').trim() === configId) {
return true
}
if (cloudSkuId > 0 && Number(item.cloudSkuId || 0) === cloudSkuId) {
return true
}
return [
item.internalSkuCode,
item.externalSkuCode,
item.externalItemId,
item.externalSkuName,
].some((value) => {
const normalized = String(value || '').trim()
return normalized && (normalized === skuCode || normalized === skuName)
})
}) || null
}
export function mapClaimKuaishouCloudFulfillment(task: TaskRow, order: OrderRow) {
if (String(task?.executor_key || '').trim() !== 'kuaishou_ct_assisted') {
return null
@@ -139,7 +139,6 @@ test('syncDeliveryTasksForOrderWithDeps creates kuaishou cloud task from cloudte
const result = await syncDeliveryTasksForOrderWithDeps(paidOrder, dynamicOrderItems, {
listTasksByOrderId: async () => [],
resolveFulfillmentBinding: async () => null,
getFulfillmentProfileByKey: async () => ({
id: 2,
profile_key: 'kuaishou_ct_assisted',
@@ -1,8 +1,5 @@
import { createTask, listTasksByOrderId, updateTask } from '../../repositories/task-repo.js'
import {
getFulfillmentProfileByKey,
resolveFulfillmentBinding,
} from '../../repositories/fulfillment-profile-repo.js'
import { getFulfillmentProfileByKey } from '../../repositories/fulfillment-profile-repo.js'
import { createTaskClaimToken } from '../claim/claim-service.js'
import { notifyTaskAutoManualReview } from '../notification/domain-notifications.js'
import { nowIso } from '../../utils/time.js'
@@ -39,12 +36,6 @@ type DeliveryTaskDeps = {
createTask?: typeof createTask
listTasksByOrderId?: typeof listTasksByOrderId
updateTask?: typeof updateTask
resolveFulfillmentBinding?: (input: {
skuCode: string
provider?: string
platform?: string
shopId?: string
}) => Promise<FulfillmentBindingLike | null>
getFulfillmentProfileByKey?: (profileKey: string) => Promise<FulfillmentBindingLike | null>
createTaskClaimToken?: (taskId: number | string) => Promise<ClaimTokenLike>
notifyTaskAutoManualReview?: (payload: {
@@ -84,7 +75,6 @@ export async function syncDeliveryTasksForOrderWithDeps(
createTask: createDeliveryTask = createTask,
listTasksByOrderId: listTasks = listTasksByOrderId,
updateTask: updateDeliveryTask = updateTask,
resolveFulfillmentBinding: resolveBinding = resolveFulfillmentBinding,
getFulfillmentProfileByKey: getProfileByKey = getFulfillmentProfileByKey,
createTaskClaimToken: createClaimToken = createTaskClaimToken,
notifyTaskAutoManualReview: notifyManualReview = notifyTaskAutoManualReview,
@@ -118,14 +108,7 @@ export async function syncDeliveryTasksForOrderWithDeps(
const tasks: DeliveryTaskRow[] = []
for (const item of orderItems) {
const binding = await resolveBinding({
skuCode: item.sku_code,
provider: order.provider,
platform: order.platform,
shopId: order.shop_id,
})
const profile = binding || await resolveDynamicCloudtentaclesProfile(item, getProfileByKey)
const profile = await resolveDynamicCloudtentaclesProfile(item, getProfileByKey)
if (!profile) {
continue
@@ -1,108 +0,0 @@
import test from 'node:test'
import assert from 'node:assert/strict'
import { mapKuaishouCloudFulfillmentItemsToBindings } from './kuaishou-cloud-fulfillment-config-service.js'
test('mapKuaishouCloudFulfillmentItemsToBindings maps 91kaquan rules', () => {
assert.deepEqual(
mapKuaishouCloudFulfillmentItemsToBindings({
items: [
{
enabled: true,
provider: '91kaquan',
platform: 'kuaishou',
shopId: '91kaquan',
internalSkuCode: 'sku-1',
internalSkuName: '内部商品',
externalSkuCode: '952',
externalItemId: '952',
externalSkuName: '测试2',
cloudSourceKeys: ['account_a', 'account_b'],
cloudSkuId: 28,
},
],
}),
[
{
provider: '91kaquan',
platform: 'kuaishou',
shopId: '91kaquan',
skuCode: 'sku-1',
skuName: '内部商品',
profileKey: 'kuaishou_ct_assisted',
enabled: true,
priority: 100,
deliveryItems: [
{
cloudSkuId: 28,
cloudSkuName: '',
quantity: 1,
},
],
config: {
flowType: 'kuaishou_cloud_fulfillment',
configId: '',
cloudtentacles: {
cloudSourceKeys: ['account_a', 'account_b'],
skuId: 28,
skuName: '',
deliveryItems: [
{
cloudSkuId: 28,
cloudSkuName: '',
quantity: 1,
},
],
vnKey: '1',
autoBuyEnabled: true,
minAssetReserve: 0,
autoReturnNumberAfterDispatch: false,
},
kuaishouConsume: {
shopId: '',
shopName: '',
autoConsumeAfterDispatch: false,
},
notes: '',
},
match: {
externalSkuCode: '952',
externalItemId: '952',
externalSkuName: '测试2',
config: {
resolvedSkuName: '内部商品',
},
},
},
],
)
})
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')
})
@@ -1,279 +0,0 @@
import path from "node:path";
import { PROJECT_ROOT } from "../../config/runtime.js";
import { readJsonFile, writeJsonFile } from "../../utils/json-file-store.js";
import { resolveKuaishouEticketShopConfig } from "../platforms/kuaishou-eticket/source-config-service.js";
const KUAISHOU_CLOUD_FULFILLMENT_FILE_PATH = path.join(
PROJECT_ROOT,
"data",
"kuaishou-cloud-fulfillment.json"
);
type JsonObject = Record<string, any>;
type DeliveryItem = {
cloudSkuId: number;
cloudSkuName: string;
quantity: number;
};
export function getKuaishouCloudFulfillmentFilePath() {
return KUAISHOU_CLOUD_FULFILLMENT_FILE_PATH;
}
export function getKuaishouCloudFulfillmentConfig() {
return loadKuaishouCloudFulfillmentConfigFromFile();
}
export function saveKuaishouCloudFulfillmentConfig(rawValue: unknown) {
return writeJsonFile(
KUAISHOU_CLOUD_FULFILLMENT_FILE_PATH,
rawValue,
normalizeKuaishouCloudFulfillmentConfig
);
}
export function mapKuaishouCloudFulfillmentItemsToBindings(config: JsonObject = {}) {
const items = Array.isArray(config.items) ? config.items : [];
return items
.filter((item) => item && item.enabled !== false)
.map((item) => ({
provider: String(item.provider || "91kaquan").trim() || "91kaquan",
platform: String(item.platform || "kuaishou").trim() || "kuaishou",
shopId: String(item.shopId || "").trim(),
skuCode: String(item.internalSkuCode || "").trim(),
skuName: String(item.internalSkuName || "").trim(),
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:
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),
autoReturnNumberAfterDispatch:
item.autoReturnNumberAfterDispatch === true,
},
kuaishouConsume: {
shopId: String(item.kuaishouConsumeShopId || "").trim(),
shopName: String(item.kuaishouConsumeShopName || "").trim(),
autoConsumeAfterDispatch: item.autoConsumeAfterDispatch === true,
},
notes: String(item.notes || "").trim(),
},
match: {
externalSkuCode: String(item.externalSkuCode || "").trim(),
externalItemId: String(item.externalItemId || "").trim(),
externalSkuName: String(item.externalSkuName || "").trim(),
config: {
resolvedSkuName: String(
item.resolvedSkuName || item.internalSkuName || ""
).trim(),
},
},
}))
.filter(
(item) =>
item.skuCode &&
(item.match.externalSkuCode ||
item.match.externalItemId ||
item.match.externalSkuName)
);
}
function loadKuaishouCloudFulfillmentConfigFromFile() {
return readJsonFile(
KUAISHOU_CLOUD_FULFILLMENT_FILE_PATH,
() => normalizeKuaishouCloudFulfillmentConfig({}),
normalizeKuaishouCloudFulfillmentConfig
);
}
function normalizeKuaishouCloudFulfillmentConfig(rawValue: unknown) {
const source = isPlainObject(rawValue) ? rawValue : {};
return {
enabled: source.enabled !== false,
items: Array.isArray(source.items)
? source.items
.map((item: unknown) => normalizeKuaishouCloudFulfillmentItem(item))
.filter(Boolean)
: [],
};
}
function normalizeKuaishouCloudFulfillmentItem(rawValue: unknown) {
if (!isPlainObject(rawValue)) {
return null;
}
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();
const externalSkuName = String(rawValue.externalSkuName || "").trim();
const kuaishouConsumeShopId = String(
rawValue.kuaishouConsumeShopId || ""
).trim();
const kuaishouConsumeShopName = String(
rawValue.kuaishouConsumeShopName || ""
).trim();
const kuaishouShopConfig = resolveKuaishouEticketShopConfig({
shopId: kuaishouConsumeShopId,
shopName: kuaishouConsumeShopName,
});
if (!internalSkuCode) {
return null;
}
if (deliveryItems.length === 0) {
return null;
}
if (cloudSourceKeys.length === 0) {
return null;
}
if (!externalSkuCode && !externalItemId && !externalSkuName) {
return null;
}
return {
id: String(rawValue.id || internalSkuCode).trim() || internalSkuCode,
enabled: rawValue.enabled !== false,
priority: normalizePriority(rawValue.priority),
provider: String(rawValue.provider || "91kaquan").trim() || "91kaquan",
platform: String(rawValue.platform || "kuaishou").trim() || "kuaishou",
shopId: String(rawValue.shopId || "").trim(),
internalSkuCode,
internalSkuName:
String(rawValue.internalSkuName || "").trim() || internalSkuCode,
externalSkuCode,
externalItemId,
externalSkuName,
resolvedSkuName: String(
rawValue.resolvedSkuName || rawValue.internalSkuName || ""
).trim(),
cloudSourceKeys,
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),
autoReturnNumberAfterDispatch:
rawValue.autoReturnNumberAfterDispatch === true,
autoConsumeAfterDispatch: rawValue.autoConsumeAfterDispatch === true,
kuaishouConsumeShopId: String(
kuaishouShopConfig?.shopId || kuaishouConsumeShopId
).trim(),
kuaishouConsumeShopName: String(
kuaishouShopConfig?.kshopName || kuaishouConsumeShopName
).trim(),
notes: String(rawValue.notes || "").trim(),
};
}
function normalizePositiveInteger(value: unknown) {
const parsed = Number(value);
return Number.isInteger(parsed) && parsed > 0 ? parsed : 0;
}
function normalizeNonNegativeInteger(value: unknown, fallback: number) {
const parsed = Number(value);
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)) {
return 100;
}
return Math.max(1, Math.round(parsed));
}
function isPlainObject(value: unknown): value is JsonObject {
return Object.prototype.toString.call(value) === "[object Object]";
}
function normalizeStringArray(value: unknown) {
if (Array.isArray(value)) {
return value.map((v: unknown) => String(v || "").trim()).filter(Boolean);
}
return [];
}
@@ -1,7 +1,6 @@
import {
createOrder,
findOrderByPlatformOrderId,
findOrderByPlatformOrderIdCandidates,
updateOrder,
} from '../../repositories/order-repo.js'
import { replaceOrderItems } from '../../repositories/order-item-repo.js'
@@ -32,7 +31,6 @@ type SourceOrderEvent = {
provider: string
platform: string
shopId: string
shopIdAliases?: string[]
shopName: string
platformOrderId: string
orderStatus: string
@@ -89,18 +87,12 @@ export async function upsertOrderFromSource(
{ sourceLabel = 'source' }: UpsertOrderSourceOptions = {},
): Promise<UpsertOrderResult> {
const now = nowIso()
const exactExisting = await findOrderByPlatformOrderId({
const existing = await findOrderByPlatformOrderId({
provider: event.provider,
platform: event.platform,
shopId: event.shopId,
platformOrderId: event.platformOrderId,
})
const existing = exactExisting || await findOrderByPlatformOrderIdCandidates({
provider: event.provider,
platform: event.platform,
shopIds: resolveEventShopIdCandidates(event),
platformOrderId: event.platformOrderId,
})
logIntegration('[order-service]', `开始处理 ${sourceLabel} 订单 upsert`, {
provider: event.provider,
@@ -115,9 +107,7 @@ export async function upsertOrderFromSource(
event.items.map((item) => resolveOrderItemForFulfillment({
provider: event.provider,
platform: event.platform,
shopId: event.shopId,
item,
...(event.shopIdAliases !== undefined ? { shopIdAliases: event.shopIdAliases } : {}),
})),
)
const configuredItems = (resolvedItems as FulfillmentOrderItem[]).filter((item) => item.isConfigured)
@@ -214,13 +204,6 @@ export async function upsertOrderFromSource(
}
}
function resolveEventShopIdCandidates(event: Pick<SourceOrderEvent, 'shopId' | 'shopIdAliases'>): string[] {
return [...new Set([
String(event?.shopId || '').trim(),
...(Array.isArray(event?.shopIdAliases) ? event.shopIdAliases : []).map((item) => String(item || '').trim()),
].filter(Boolean))]
}
const ORDER_STATUS_PRIORITY = {
created: 0,
paid: 1,
@@ -1,18 +0,0 @@
import test from 'node:test'
import assert from 'node:assert/strict'
import { resolveShopIdCandidates } from './product-match-service.js'
test('resolveShopIdCandidates merges primary and alias shop ids', () => {
assert.deepEqual(
resolveShopIdCandidates({
shopId: '10',
shopIdAliases: ['4269276762', '10', ''],
}),
['10', '4269276762'],
)
})
test('resolveShopIdCandidates preserves wildcard fallback when ids are empty', () => {
assert.deepEqual(resolveShopIdCandidates({}), [''])
})
@@ -1,6 +1,5 @@
import { resolveFulfillmentBinding } from '../../repositories/fulfillment-profile-repo.js'
import { resolveProductMatchRule } from '../../repositories/product-match-rule-repo.js'
import {
normalizeCloudtentaclesMatchName,
resolveCloudtentaclesSkuByProductName,
type CloudtentaclesNameMatchResult,
} from './cloudtentacles-name-match-service.js'
@@ -21,8 +20,6 @@ export type FulfillmentItem = {
type ResolveOrderItemForFulfillmentInput = {
provider?: string
platform?: string
shopId?: string
shopIdAliases?: string[]
item?: FulfillmentItem
}
@@ -35,9 +32,7 @@ type FulfillmentItemCandidate = {
externalSkuCode: string
externalSkuName: string
externalSkuNameNormalized: string
matchedRule: Awaited<ReturnType<typeof resolveProductMatchRule>>
resolvedSkuCode: string
binding: Awaited<ReturnType<typeof resolveFulfillmentBinding>>
cloudtentaclesNameMatch: CloudtentaclesNameMatchResult | null
isConfigured: boolean
}
@@ -53,23 +48,14 @@ export type ResolvedFulfillmentItem = FulfillmentItem & {
isConfigured: boolean
}
type ShopIdCandidatesInput = {
shopId?: string
shopIdAliases?: string[]
}
export async function resolveOrderItemForFulfillment({
provider = '',
platform = '',
shopId = '',
shopIdAliases = [],
item = {},
}: ResolveOrderItemForFulfillmentInput): Promise<ResolvedFulfillmentItem> {
const candidate = await resolveConfiguredItemCandidate({
provider,
platform,
shopId,
shopIdAliases,
item,
})
const {
@@ -77,21 +63,16 @@ export async function resolveOrderItemForFulfillment({
externalSkuCode,
externalSkuName,
externalSkuNameNormalized,
matchedRule,
binding,
cloudtentaclesNameMatch,
} = candidate
const resolvedSkuCode = pickFirstNonEmpty([
cloudtentaclesNameMatch?.cloudSkuName,
matchedRule?.resolved_sku_code,
externalSkuCode,
externalItemId,
])
const resolvedSkuName = pickFirstNonEmpty([
cloudtentaclesNameMatch?.cloudSkuName,
readConfigValue(matchedRule?.config_json, 'resolvedSkuName'),
readConfigValue(matchedRule?.config_json, 'internalProductName'),
item.skuName,
externalSkuName,
resolvedSkuCode,
@@ -103,11 +84,7 @@ export async function resolveOrderItemForFulfillment({
externalSkuName,
externalSkuNameNormalized,
resolvedSkuCode,
matchedProductRuleId: matchedRule ? Number(matchedRule.id) : null,
matchedProductRuleBy: String(matchedRule?.matched_by || '').trim(),
matchedFulfillmentBindingId: binding ? Number(binding.id) : null,
matchedFulfillmentProfileKey: String(binding?.profile_key || '').trim(),
matchMode: cloudtentaclesNameMatch?.matchMode || String(matchedRule?.matched_by || '').trim(),
matchMode: cloudtentaclesNameMatch?.matchMode || '',
cloudtentacles: cloudtentaclesNameMatch
? {
matchMode: cloudtentaclesNameMatch.matchMode,
@@ -141,16 +118,12 @@ export async function resolveOrderItemForFulfillment({
export async function hasConfiguredOrderItems({
provider = '',
platform = '',
shopId = '',
shopIdAliases = [],
items = [],
}: HasConfiguredOrderItemsInput): Promise<boolean> {
const candidates = await Promise.all(
(Array.isArray(items) ? items : []).map((item) => resolveConfiguredItemCandidate({
provider,
platform,
shopId,
shopIdAliases,
item,
})),
)
@@ -159,13 +132,7 @@ export async function hasConfiguredOrderItems({
}
export function normalizeProductName(value: unknown): string {
return String(value || '')
.toLowerCase()
.replace(/[【】\[\]()()]/g, ' ')
.replace(/(自动发货|秒发|极速发货|官方直充|官方充值)/gi, ' ')
.replace(/[^\p{L}\p{N}]+/gu, ' ')
.replace(/\s+/g, ' ')
.trim()
return normalizeCloudtentaclesMatchName(value)
}
function pickFirstNonEmpty(values: unknown[]): string {
@@ -182,8 +149,6 @@ function pickFirstNonEmpty(values: unknown[]): string {
async function resolveConfiguredItemCandidate({
provider = '',
platform = '',
shopId = '',
shopIdAliases = [],
item = {},
}: ResolveOrderItemForFulfillmentInput): Promise<FulfillmentItemCandidate> {
const externalItemId = pickFirstNonEmpty([
@@ -201,9 +166,6 @@ async function resolveConfiguredItemCandidate({
externalSkuCode,
])
const externalSkuNameNormalized = normalizeProductName(externalSkuName)
const shopIdCandidates = resolveShopIdCandidates({ shopId, shopIdAliases })
let matchedRule = null
let binding = null
if (isOpen91KuaishouOrder(provider, platform)) {
const cloudtentaclesNameMatch = await resolveCloudtentaclesSkuByProductName(externalSkuName)
@@ -218,48 +180,13 @@ async function resolveConfiguredItemCandidate({
externalSkuCode,
externalSkuName,
externalSkuNameNormalized,
matchedRule: null,
resolvedSkuCode,
binding: null,
cloudtentaclesNameMatch,
isConfigured: Boolean(cloudtentaclesNameMatch),
}
}
for (const candidateShopId of shopIdCandidates) {
const candidateRule = await resolveProductMatchRule({
provider,
platform,
shopId: candidateShopId,
externalItemId,
externalSkuCode,
externalSkuNameNormalized,
})
if (candidateRule) {
matchedRule = candidateRule
}
const resolvedSkuCode = pickFirstNonEmpty([
candidateRule?.resolved_sku_code,
externalSkuCode,
externalItemId,
])
binding = resolvedSkuCode
? await resolveFulfillmentBinding({
skuCode: resolvedSkuCode,
provider,
platform,
shopId: candidateShopId,
})
: null
if (binding) {
break
}
}
const resolvedSkuCode = pickFirstNonEmpty([
matchedRule?.resolved_sku_code,
externalSkuCode,
externalItemId,
])
@@ -269,52 +196,17 @@ async function resolveConfiguredItemCandidate({
externalSkuCode,
externalSkuName,
externalSkuNameNormalized,
matchedRule,
resolvedSkuCode,
binding,
cloudtentaclesNameMatch: null,
isConfigured: Boolean(binding),
isConfigured: false,
}
}
export function resolveShopIdCandidates({
shopId = '',
shopIdAliases = [],
}: ShopIdCandidatesInput = {}): string[] {
const candidates = [...new Set([
String(shopId || '').trim(),
...(Array.isArray(shopIdAliases) ? shopIdAliases : []).map((item) => String(item || '').trim()),
].filter(Boolean))]
return candidates.length > 0 ? candidates : ['']
}
function readConfigValue(rawValue: unknown, key: string): string {
if (!rawValue) {
return ''
}
const parsed = typeof rawValue === 'string' ? safeParseJson(rawValue) : rawValue
if (!isPlainObject(parsed)) {
return ''
}
return String(parsed[key] || '').trim()
}
function safeParseJson(rawValue: unknown): unknown {
try {
return JSON.parse(String(rawValue || '{}'))
} catch {
return null
}
}
function isPlainObject(value: unknown): value is Record<string, unknown> {
return Object.prototype.toString.call(value) === '[object Object]'
}
function isOpen91KuaishouOrder(provider: unknown, platform: unknown) {
return String(provider || '').trim() === '91kaquan'
&& String(platform || '').trim() === 'kuaishou'
}
function isPlainObject(value: unknown): value is Record<string, unknown> {
return Object.prototype.toString.call(value) === '[object Object]'
}
@@ -14,7 +14,6 @@ import type {
AdminCloudtentaclesTestLoginInput,
AdminCloudtentaclesValidateSessionInput,
AdminCloudtentaclesVirtualNumberInput,
AdminKuaishouCloudFulfillmentConfigInput,
AdminKuaishouEticketConsumeInput,
AdminKuaishouEticketDetailQueryInput,
AdminKuaishouEticketShopInfoInput,
@@ -38,7 +37,6 @@ export type AdminKuaishouEticketDetailQueryRouteBody = AdminKuaishouEticketDetai
export type AdminKuaishouEticketConsumeRouteBody = AdminKuaishouEticketConsumeInput
export type AdminKuaishouEticketShopInfoRouteBody = AdminKuaishouEticketShopInfoInput
export type AdminCloudtentaclesSourceConfigRouteBody = AdminCloudtentaclesSourceConfigInput
export type AdminKuaishouCloudFulfillmentConfigRouteBody = AdminKuaishouCloudFulfillmentConfigInput
export type AdminCloudtentaclesSendSmsCodeRouteBody = AdminCloudtentaclesSendSmsCodeInput
export type AdminCloudtentaclesTestLoginRouteBody = AdminCloudtentaclesTestLoginInput
export type AdminCloudtentaclesValidateSessionRouteBody = AdminCloudtentaclesValidateSessionInput
@@ -1,34 +1,3 @@
export type AdminKuaishouCloudFulfillmentItemInput = {
id?: string
enabled?: boolean
priority?: number | string
provider?: string
platform?: string
shopId?: string
internalSkuCode?: string
internalSkuName?: string
externalSkuCode?: string
externalItemId?: string
externalSkuName?: string
resolvedSkuName?: string
cloudSourceKeys?: string[]
cloudSkuId?: number | string
cloudSkuName?: string
vnKey?: string
autoBuyEnabled?: boolean
minAssetReserve?: number | string
autoReturnNumberAfterDispatch?: boolean
autoConsumeAfterDispatch?: boolean
kuaishouConsumeShopId?: string
kuaishouConsumeShopName?: string
notes?: string
}
export type AdminKuaishouCloudFulfillmentConfigInput = {
enabled?: boolean
items?: AdminKuaishouCloudFulfillmentItemInput[]
}
export type AdminKuaishouEticketShopConfigWriteItemInput = {
shopId?: string
kshopName?: string