Files
order_site/apps/backend/src/services/order/delivery-task-service.ts
T

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)
}