172 lines
5.1 KiB
TypeScript
172 lines
5.1 KiB
TypeScript
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<FulfillmentBindingLike | null>
|
|
createTaskClaimToken?: (taskId: number | string) => Promise<ClaimTokenLike>
|
|
notifyTaskAutoManualReview?: (payload: {
|
|
task: unknown
|
|
reason: string
|
|
source: string
|
|
}) => Promise<unknown> | unknown
|
|
nowIso?: () => string
|
|
randomId?: (prefix?: string) => string
|
|
}
|
|
|
|
export async function syncDeliveryTasksForOrder(
|
|
order: OrderRow,
|
|
orderItems: OrderItemRow[],
|
|
): Promise<TaskRow[]> {
|
|
return syncDeliveryTasksForOrderWithDeps(order, orderItems)
|
|
}
|
|
|
|
export async function syncDeliveryTasksForOrderWithDeps(
|
|
order: OrderRow,
|
|
orderItems: OrderItemRow[],
|
|
deps: DeliveryTaskDeps = {},
|
|
): Promise<TaskRow[]> {
|
|
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)
|
|
}
|