308 lines
9.6 KiB
JavaScript
308 lines
9.6 KiB
JavaScript
import { createTask, listTasksByOrderId, updateTask } from '../../repositories/task-repo.js'
|
|
import {
|
|
listFulfillmentProfileRequirements,
|
|
resolveFulfillmentBinding,
|
|
} from '../../repositories/fulfillment-profile-repo.js'
|
|
import { reserveInventoryForTask } from './inventory-service.js'
|
|
import { createTaskClaimToken } from '../claim/claim-service.js'
|
|
import { nowIso } from '../../utils/time.js'
|
|
import { randomId } from '../../utils/random.js'
|
|
|
|
export async function syncDeliveryTasksForOrder(order, orderItems) {
|
|
const existingTasks = await listTasksByOrderId(order.id)
|
|
|
|
if (existingTasks.length > 0) {
|
|
if (order.pay_status !== 'paid') {
|
|
return existingTasks
|
|
}
|
|
|
|
const itemMap = new Map(orderItems.map((item) => [item.id, item]))
|
|
return Promise.all(existingTasks.map((task) => preparePaidTask({
|
|
...task,
|
|
skuCode: itemMap.get(task.order_item_id)?.sku_code || '',
|
|
skuName: itemMap.get(task.order_item_id)?.sku_name || '',
|
|
})))
|
|
}
|
|
|
|
const tasks = []
|
|
|
|
for (const item of orderItems) {
|
|
const binding = await resolveFulfillmentBinding({
|
|
skuCode: item.sku_code,
|
|
provider: order.provider,
|
|
platform: order.platform,
|
|
shopId: order.shop_id,
|
|
})
|
|
|
|
if (!binding) {
|
|
continue
|
|
}
|
|
const profile = binding
|
|
const requirements = await listFulfillmentProfileRequirements(profile.profile_id || profile.id)
|
|
const primaryRequirement = requirements.find((requirement) => requirement.is_required !== false) || requirements[0] || null
|
|
const quantity = Math.max(1, Number(item.quantity || 1))
|
|
const fulfillmentConfig = parseJsonObject(profile.config_json)
|
|
|
|
for (let index = 0; index < quantity; index += 1) {
|
|
const createdAt = nowIso()
|
|
const initialStatus = order.pay_status === 'paid'
|
|
? resolvePaidTaskStatus(profile)
|
|
: 'pending_payment'
|
|
|
|
const task = await createTask({
|
|
orderId: order.id,
|
|
orderItemId: item.id,
|
|
unitIndex: index + 1,
|
|
provider: order.provider,
|
|
platform: order.platform,
|
|
shopId: order.shop_id,
|
|
shopName: order.shop_name,
|
|
platformOrderId: order.platform_order_id,
|
|
taskNo: randomId('DT'),
|
|
profileId: Number(profile.profile_id || profile.id),
|
|
executorKey: String(profile.executor_key || 'manual_dispatch'),
|
|
taskStatus: initialStatus,
|
|
inventoryStatus: profile.requires_claim ? 'pending' : 'not_required',
|
|
deliveryStatus: initialStatus === 'redeemed' ? 'delivered' : 'pending',
|
|
resultCode: '',
|
|
resultMessage: '',
|
|
claimToken: '',
|
|
claimExpiresAt: null,
|
|
automationMode: profile.auto_dispatch ? 'automatic' : 'manual',
|
|
requiresClaim: Boolean(profile.requires_claim),
|
|
userActionStatus: profile.requires_claim ? 'pending_claim' : 'not_required',
|
|
attemptCount: 0,
|
|
lastError: '',
|
|
contextJson: JSON.stringify({
|
|
profileKey: String(profile.profile_key || ''),
|
|
profileName: String(profile.profile_name || profile.name || ''),
|
|
skuCode: item.sku_code,
|
|
skuName: item.sku_name,
|
|
inventorySkuCode: item.sku_code,
|
|
primaryRequirement: primaryRequirement
|
|
? {
|
|
roleKey: String(primaryRequirement.role_key || primaryRequirement.roleKey || 'primary_code'),
|
|
credentialType: String(primaryRequirement.credential_type || primaryRequirement.credentialType || 'tencent_code'),
|
|
}
|
|
: null,
|
|
kuaishouCloudFulfillment: isKuaishouCloudExecutor(profile.executor_key)
|
|
? {
|
|
flowType: 'kuaishou_cloud_fulfillment',
|
|
configId: String(fulfillmentConfig.configId || '').trim(),
|
|
internalSkuCode: item.sku_code,
|
|
internalSkuName: item.sku_name,
|
|
ticket: {
|
|
code: '',
|
|
capturedAt: null,
|
|
capturedBy: null,
|
|
},
|
|
binding: {
|
|
prepareStatus: 'pending',
|
|
cloudSourceKey: String(fulfillmentConfig.cloudtentacles?.cloudSourceKey || 'default').trim() || 'default',
|
|
skuId: Number(fulfillmentConfig.cloudtentacles?.skuId || 0) || 0,
|
|
skuName: String(fulfillmentConfig.cloudtentacles?.skuName || '').trim(),
|
|
vnKey: '1',
|
|
vnId: 0,
|
|
vnPhone: '',
|
|
bindUrl: '',
|
|
bindPreparedAt: null,
|
|
},
|
|
purchase: {
|
|
autoBuyEnabled: fulfillmentConfig.cloudtentacles?.autoBuyEnabled !== false,
|
|
minAssetReserve: Number(fulfillmentConfig.cloudtentacles?.minAssetReserve || 0) || 0,
|
|
usedKnapsack: false,
|
|
purchaseTriggered: false,
|
|
assetBefore: 0,
|
|
assetAfter: 0,
|
|
purchaseAt: null,
|
|
},
|
|
dispatch: {
|
|
status: 'pending',
|
|
dispatchAt: null,
|
|
dispatchBy: null,
|
|
sendType: 0,
|
|
note: '',
|
|
},
|
|
returnNumber: {
|
|
status: 'pending',
|
|
returnedAt: null,
|
|
returnedBy: null,
|
|
autoReturnEnabled: fulfillmentConfig.cloudtentacles?.autoReturnNumberAfterDispatch === true,
|
|
},
|
|
consume: {
|
|
status: 'pending',
|
|
shopId: String(fulfillmentConfig.kuaishouConsume?.shopId || '').trim(),
|
|
autoConsumeEnabled: fulfillmentConfig.kuaishouConsume?.autoConsumeAfterDispatch === true,
|
|
},
|
|
notes: String(fulfillmentConfig.notes || '').trim(),
|
|
}
|
|
: null,
|
|
}),
|
|
createdAt,
|
|
updatedAt: createdAt,
|
|
})
|
|
|
|
tasks.push({
|
|
...task,
|
|
skuCode: item.sku_code,
|
|
skuName: item.sku_name,
|
|
})
|
|
}
|
|
}
|
|
|
|
if (order.pay_status !== 'paid') {
|
|
return tasks
|
|
}
|
|
|
|
return Promise.all(tasks.map((task) => preparePaidTask(task)))
|
|
}
|
|
|
|
async function preparePaidTask(task) {
|
|
const now = nowIso()
|
|
const taskContext = parseTaskContext(task)
|
|
const primaryRequirement = taskContext.primaryRequirement || null
|
|
const inventorySkuCode = String(taskContext.inventorySkuCode || task.skuCode || '').trim()
|
|
|
|
if (['link_generated', 'claimed', 'role_confirmed', 'redeeming', 'redeemed', 'closed'].includes(task.task_status)) {
|
|
return task
|
|
}
|
|
|
|
if (isKuaishouCloudExecutor(task.executor_key)) {
|
|
if ([
|
|
'pending_binding_prepare',
|
|
'waiting_binding',
|
|
'dispatched_pending_return',
|
|
'completed',
|
|
'manual_review',
|
|
'failed',
|
|
].includes(String(task.task_status || '').trim())) {
|
|
return task
|
|
}
|
|
|
|
return updateTask(task.id, {
|
|
task_status: 'pending_binding_prepare',
|
|
inventory_status: 'not_required',
|
|
user_action_status: 'pending',
|
|
last_error: task.last_error || '等待准备绑定资源',
|
|
updated_at: now,
|
|
})
|
|
}
|
|
|
|
if (!task.requires_claim || String(task.executor_key || '').trim() === 'manual_dispatch') {
|
|
return updateTask(task.id, {
|
|
task_status: 'manual_review',
|
|
inventory_status: 'not_required',
|
|
user_action_status: 'not_required',
|
|
last_error: task.last_error || '当前任务需要人工履约处理',
|
|
updated_at: now,
|
|
})
|
|
}
|
|
|
|
if (!inventorySkuCode) {
|
|
return updateTask(task.id, {
|
|
task_status: 'manual_review',
|
|
last_error: '未匹配到 SKU,无法为任务分配库存凭据',
|
|
updated_at: now,
|
|
})
|
|
}
|
|
|
|
if (task.primary_inventory_item_id && task.primary_claim_token_id) {
|
|
return updateTask(task.id, {
|
|
task_status: 'link_generated',
|
|
last_error: '',
|
|
updated_at: now,
|
|
})
|
|
}
|
|
|
|
if (!primaryRequirement?.credentialType) {
|
|
return updateTask(task.id, {
|
|
task_status: 'manual_review',
|
|
last_error: '履约档案未配置库存要求,无法自动分配库存',
|
|
updated_at: now,
|
|
})
|
|
}
|
|
|
|
const reserved = await reserveInventoryForTask({
|
|
skuCode: inventorySkuCode,
|
|
taskId: task.id,
|
|
credentialType: primaryRequirement.credentialType,
|
|
roleKey: primaryRequirement.roleKey || 'primary_code',
|
|
})
|
|
|
|
if (!reserved) {
|
|
return updateTask(task.id, {
|
|
task_status: 'waiting_inventory',
|
|
inventory_status: 'pending',
|
|
last_error: '库存不足,等待可用库存凭据',
|
|
updated_at: now,
|
|
})
|
|
}
|
|
|
|
const claimToken = await createTaskClaimToken(task.id)
|
|
|
|
return updateTask(task.id, {
|
|
task_status: 'link_generated',
|
|
inventory_status: 'reserved',
|
|
claim_token: claimToken.token,
|
|
claim_expires_at: claimToken.expired_at,
|
|
user_action_status: 'pending_claim',
|
|
last_error: '',
|
|
updated_at: now,
|
|
})
|
|
}
|
|
|
|
function resolvePaidTaskStatus(profile) {
|
|
if (Boolean(profile?.requires_claim)) {
|
|
return 'paid'
|
|
}
|
|
|
|
if (isKuaishouCloudExecutor(profile?.executor_key)) {
|
|
return 'pending_binding_prepare'
|
|
}
|
|
|
|
if (String(profile?.executor_key || '').trim() === 'manual_dispatch') {
|
|
return 'manual_review'
|
|
}
|
|
|
|
return 'paid'
|
|
}
|
|
|
|
function parseTaskContext(task) {
|
|
const value = task?.context_json
|
|
|
|
if (!value) {
|
|
return {}
|
|
}
|
|
|
|
if (typeof value === 'object') {
|
|
return value
|
|
}
|
|
|
|
try {
|
|
return JSON.parse(String(value || '{}'))
|
|
} catch {
|
|
return {}
|
|
}
|
|
}
|
|
|
|
function parseJsonObject(value) {
|
|
if (!value) {
|
|
return {}
|
|
}
|
|
|
|
if (typeof value === 'object' && !Array.isArray(value)) {
|
|
return value
|
|
}
|
|
|
|
try {
|
|
const parsed = JSON.parse(String(value || '{}'))
|
|
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {}
|
|
} catch {
|
|
return {}
|
|
}
|
|
}
|
|
|
|
function isKuaishouCloudExecutor(value) {
|
|
return String(value || '').trim() === 'kuaishou_ct_assisted'
|
|
}
|