import { createTask, listTasksByOrderId, updateTask } from '../../repositories/task-repo.js' import { getFulfillmentProfileByKey, listFulfillmentProfileRequirements, resolveFulfillmentBinding, } from '../../repositories/fulfillment-profile-repo.js' import { reserveCdkForTask } from './cdk-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, }) const profile = binding || await getFulfillmentProfileByKey('manual_review') if (!profile) { continue } 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)) 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, primaryRequirement: primaryRequirement ? { roleKey: String(primaryRequirement.role_key || primaryRequirement.roleKey || 'primary_code'), credentialType: String(primaryRequirement.credential_type || primaryRequirement.credentialType || 'tencent_code'), } : 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 if (['link_generated', 'claimed', 'role_confirmed', 'redeeming', 'redeemed'].includes(task.task_status)) { return task } 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 (!task.skuCode) { return updateTask(task.id, { task_status: 'manual_review', last_error: '未匹配到 SKU,无法为任务分配 CDK', 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 reserveCdkForTask({ skuCode: task.skuCode, 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: '库存不足,等待可用 CDK', 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 (String(profile?.executor_key || '').trim() === 'manual_dispatch') { return 'manual_review' } return 'paid' } function parseTaskContext(task) { try { return JSON.parse(String(task?.context_json || '{}')) } catch { return {} } }