import { createTask, listTasksByOrderId, updateTask } from '../../repositories/task-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 { planFulfillmentTaskForOrderItem, type FulfillmentBindingLike, } from '../fulfillment/planner.js' import { preparePaidFulfillmentTask } from '../fulfillment/executors/registry.js' import type { FulfillmentPrepareDeps } from '../fulfillment/executors/types.js' import { nowIso } from '../../utils/time.js' import { randomId } from '../../utils/random.js' import { TASK_STATUS, resolveInitialPaidTaskStatus, } from '../../domain/task-status.js' import type { OrderItemRow, OrderRow, TaskRow } from '../../types/repository/rows.js' type ClaimTokenLike = { token: string expired_at: string [key: string]: unknown } type DeliveryTaskRow = TaskRow & { skuCode?: string skuName?: string } type DeliveryTaskDeps = { createTask?: typeof createTask listTasksByOrderId?: typeof listTasksByOrderId updateTask?: typeof updateTask getFulfillmentProfileByKey?: (profileKey: string) => Promise createTaskClaimToken?: (taskId: number | string) => Promise notifyTaskAutoManualReview?: (payload: { task: unknown reason: string source: string }) => Promise | unknown nowIso?: () => string randomId?: (prefix?: string) => string } export async function syncDeliveryTasksForOrder( order: OrderRow, orderItems: OrderItemRow[], ): Promise { return syncDeliveryTasksForOrderWithDeps(order, orderItems) } export async function syncDeliveryTasksForOrderWithDeps( order: OrderRow, orderItems: OrderItemRow[], deps: DeliveryTaskDeps = {}, ): Promise { const { createTask: createDeliveryTask = createTask, listTasksByOrderId: listTasks = listTasksByOrderId, updateTask: updateDeliveryTask = updateTask, getFulfillmentProfileByKey: getProfileByKey = getFulfillmentProfileByKey, createTaskClaimToken: createClaimToken = createTaskClaimToken, notifyTaskAutoManualReview: notifyManualReview = notifyTaskAutoManualReview, nowIso: getNowIso = nowIso, randomId: createRandomId = randomId, } = deps const runtimeDeps: FulfillmentPrepareDeps = { updateTask: updateDeliveryTask, createTaskClaimToken: createClaimToken, notifyTaskAutoManualReview: notifyManualReview, nowIso: getNowIso, } const existingTasks = await listTasks(order.id) if (existingTasks.length > 0) { if (order.pay_status !== 'paid') { return existingTasks } return preparePaidTasks(existingTasks, runtimeDeps) } const tasks: DeliveryTaskRow[] = [] for (const item of orderItems) { const plan = await planFulfillmentTaskForOrderItem({ order, item, getProfileByKey, }) if (!plan) { continue } const quantity = Math.max(1, Number(item.quantity || 1)) for (let index = 0; index < quantity; index += 1) { const createdAt = getNowIso() const initialStatus = order.pay_status === 'paid' ? resolvePaidTaskStatus(plan.profile) : TASK_STATUS.PENDING_PAYMENT const task = await createDeliveryTask({ 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: createRandomId('DT'), profileId: plan.profileId, executorKey: plan.executorKey, taskStatus: initialStatus, deliveryStatus: 'pending', resultCode: '', resultMessage: '', claimToken: '', claimExpiresAt: null, automationMode: plan.autoDispatch ? 'automatic' : 'manual', requiresClaim: plan.requiresClaim, userActionStatus: plan.requiresClaim ? 'pending_claim' : 'not_required', attemptCount: 0, lastError: '', contextJson: JSON.stringify(plan.context), createdAt, updatedAt: createdAt, }) if (task) { tasks.push({ ...task, skuCode: item.sku_code, skuName: item.sku_name, }) } } } if (order.pay_status !== 'paid') { return tasks } const preparedTasks = await Promise.all( tasks.map((task) => preparePaidFulfillmentTask(task, runtimeDeps)), ) return preparedTasks.filter(isTaskRow) } async function preparePaidTasks( tasks: TaskRow[], deps: FulfillmentPrepareDeps, ) { const preparedTasks = await Promise.all( tasks.map((task) => preparePaidFulfillmentTask(task, deps)), ) return preparedTasks.filter(isTaskRow) } function resolvePaidTaskStatus(profile: FulfillmentBindingLike | null | undefined): string { return resolveInitialPaidTaskStatus(profile) } function isTaskRow(task: TaskRow | DeliveryTaskRow | null | undefined): task is TaskRow { return Boolean(task && Number(task.id || 0) > 0) }