收敛后台工单服务入口

This commit is contained in:
yml2213
2026-08-21 17:45:50 +08:00
parent 900c45c88f
commit d807012500
3 changed files with 204 additions and 519 deletions
@@ -1,90 +1,21 @@
import { WORK_ORDER_STATUS } from '../../domain/work-order-status.js'
import {
acceptWorkOrderAndSettle,
acceptWorkOrderShareAndSettle,
cancelAssignedWorkOrder,
cancelWorkOrderShare,
returnAssignedWorkOrderToHall,
unassignWorkOrder,
countWorkOrderPendingSharingSubmissions,
createWorkProductMatchLog,
createWorkOrder,
createWorkOrderEvent,
getWorkOrderByOrderItemId,
listWorkOrders,
listWorkProductRules,
listWorkProductRuleMappings,
updateWorkOrder,
type WorkOrderRow,
} from '../../repositories/worker-platform/index.js'
import type { JsonObject } from '../../types/json.js'
import type { OrderItemRow, OrderRow } from '../../types/repository/rows.js'
import { createHttpError } from '../../utils/http.js'
import { randomId } from '../../utils/random.js'
import { nowIso } from '../../utils/time.js'
import { safeParseJson } from '../admin/admin-query-utils.js'
import { getWorkerFinanceConfig } from './worker-finance-config-service.js'
import { getWorkerProductMatchConfig } from './worker-product-match-config-service.js'
import {
createWorkOrderMaterialAdminNotification,
resolveAdminNotificationEntity,
} from '../admin/admin-notification-service.js'
import {
publishWorkerWalletRealtimeChange,
publishWorkOrderRealtimeChange,
} from '../realtime/realtime-event-service.js'
import {
mapWorkOrderAdmin,
mapWorkOrderShare,
normalizeAmountFen,
normalizeInteger,
normalizeOptionalId,
normalizeRequirementFields,
normalizePositiveInteger,
resolveMatchingProductRuleDecision,
resolveKuaishouWorkProductMatchContext,
resolveSkuNameQuantity,
resolveWorkOrderRulePricing,
resolveWorkOrderSharingEnabled,
resolveWorkOrderOperationalStatus,
} from './mappers.js'
import { normalizeWorkOrderTimeoutPolicy } from './work-order-timeout-policy.js'
import { getRequiredWorkOrder, getRequiredWorker } from './worker-session-context-service.js'
import { resolveWorkOrderRealtimeWorkerIds } from './admin-work-order-realtime-service.js'
/** 后台接单平台服务兼容入口,保留旧模块导入路径。 */
export {
listAdminWorkerUsers,
resetAdminWorkerPassword,
reviewAdminWorkerUser,
} from './admin-worker-user-service.js'
acceptAdminWorkOrder,
acceptAdminWorkOrderShare,
acceptAdminWorkOrders,
cancelAdminWorkOrderShare,
} from './admin-work-order-acceptance-service.js'
export { assignAdminWorkOrderToWorker } from './admin-work-order-assignment-service.js'
export { getAdminWorkOrderEvents, listAdminWorkOrders } from './admin-work-order-query-service.js'
export {
listAdminWorkerCancelRequests,
reviewAdminWorkerCancelRequest,
} from './admin-worker-cancel-service.js'
cancelAdminWorkOrder,
unassignAdminWorkOrder,
} from './admin-work-order-cancellation-service.js'
export { deductAdminWorkOrderPendingDeposit } from './admin-work-order-deposit-service.js'
export { getAdminWorkOrderEvents, listAdminWorkOrders } from './admin-work-order-query-service.js'
export {
getAdminWorkOrderSharing,
updateAdminWorkOrderSharing,
} from './admin-work-order-sharing-service.js'
export {
resolveAdminMaterialRewardAmount,
submitAdminWorkOrderMaterial,
} from './admin-work-order-material-service.js'
export {
createAdminMockWorkOrder,
resolveAdminMockWorkOrderInput,
} from './admin-mock-work-order-service.js'
export {
deleteAdminWorkOrder,
markAdminWorkOrderProblem,
@@ -95,448 +26,26 @@ export {
unpublishAdminWorkOrder,
updateAdminWorkOrder,
} from './admin-work-order-management-service.js'
export async function acceptAdminWorkOrder(workOrderId: number | string, actorName = '') {
let workOrder = await getRequiredWorkOrder(workOrderId)
if (!mapWorkOrderAdmin(workOrder).capabilities.canAccept) {
throw createHttpError('只有待验收订单可以通过验收', {
statusCode: 409,
errorCode: 'work_order_accept_status_invalid',
})
}
// 历史拼单订单可能仍保留 open,验收前先补齐母工单生命周期状态。
if (workOrder.status === WORK_ORDER_STATUS.OPEN) {
const promoted = await updateWorkOrder(workOrder.id, {
status: WORK_ORDER_STATUS.PENDING_ACCEPTANCE,
updated_at: nowIso(),
})
if (promoted) workOrder = promoted
}
const now = nowIso()
const { order: updated, failureReason } = await acceptWorkOrderAndSettle({
workOrderId: workOrder.id,
unfreezeDays: getWorkerFinanceConfig().depositUnfreezeDays,
now,
actorType: 'admin',
actorName,
})
if (failureReason === 'sharing_pending_submissions') {
throw createHttpError('还有拼单打手未提交验收资料,暂不能统一验收', {
statusCode: 409,
errorCode: 'work_order_sharing_pending_submissions',
})
}
if (!updated) {
throw createHttpError('验收失败,订单状态可能已变化', {
statusCode: 409,
errorCode: 'work_order_accept_conflict',
})
}
await resolveAdminNotificationEntity('work_order', Number(workOrder.id), 'accepted')
const workerIds = await resolveWorkOrderRealtimeWorkerIds(workOrder)
publishWorkOrderRealtimeChange({
workOrderId: Number(workOrder.id),
workerIds,
})
for (const workerId of workerIds) {
publishWorkerWalletRealtimeChange(workerId)
}
return { order: mapWorkOrderAdmin(updated || workOrder) }
}
export async function acceptAdminWorkOrderShare(
workOrderId: number | string,
shareId: number | string,
actorName = '',
) {
const result = await acceptWorkOrderShareAndSettle({
workOrderId: Number(workOrderId),
shareId: Number(shareId),
unfreezeDays: getWorkerFinanceConfig().depositUnfreezeDays,
now: nowIso(),
actorType: 'admin',
actorName,
})
if (result.failureReason === 'work_order_not_accepting') {
throw createHttpError('当前母工单不能验收拼单份额', {
statusCode: 409,
errorCode: 'work_order_sharing_accept_status_invalid',
})
}
if (result.failureReason === 'share_not_submitted' || !result.share || !result.order) {
throw createHttpError('该拼单参与者尚未提交验收资料或已被验收', {
statusCode: 409,
errorCode: 'work_order_sharing_share_accept_status_invalid',
})
}
const workerId = Number(result.share.worker_id || 0)
publishWorkOrderRealtimeChange({
workOrderId: Number(workOrderId),
workerIds: workerId > 0 ? [workerId] : [],
})
if (workerId > 0) publishWorkerWalletRealtimeChange(workerId)
if (result.order.status === WORK_ORDER_STATUS.ACCEPTED) {
await resolveAdminNotificationEntity('work_order', Number(workOrderId), 'accepted')
}
return {
order: mapWorkOrderAdmin(result.order),
share: mapWorkOrderShare(result.share),
}
}
export async function cancelAdminWorkOrderShare(
workOrderId: number | string,
shareId: number | string,
payload: JsonObject = {},
actorName = '',
) {
const result = await cancelWorkOrderShare({
workOrderId: Number(workOrderId),
shareId: Number(shareId),
now: nowIso(),
reason: String(payload.reason || '').trim(),
actorName,
})
if (result.failureReason === 'work_order_not_cancellable') {
throw createHttpError('当前拼单状态不能撤销参与者', {
statusCode: 409,
errorCode: 'work_order_sharing_cancel_status_invalid',
})
}
if (result.failureReason === 'share_not_found') {
throw createHttpError('未找到该拼单参与记录', {
statusCode: 404,
errorCode: 'work_order_sharing_share_not_found',
})
}
if (result.failureReason === 'share_status_not_cancellable' || !result.share || !result.order) {
throw createHttpError('只有已参与或已提交验收的拼单份额可以撤单', {
statusCode: 409,
errorCode: 'work_order_sharing_share_cancel_status_invalid',
})
}
const workerId = Number(result.share.worker_id || 0)
publishWorkOrderRealtimeChange({
workOrderId: Number(workOrderId),
workerIds: await resolveWorkOrderRealtimeWorkerIds(result.order),
hallChanged: true,
})
if (workerId > 0) publishWorkerWalletRealtimeChange(workerId)
return {
order: mapWorkOrderAdmin(result.order),
share: mapWorkOrderShare(result.share),
releasedDepositAmount: result.releasedDepositAmount,
}
}
export async function acceptAdminWorkOrders(payload: JsonObject = {}, actorName = '') {
const workOrderIds = [
...new Set(
(Array.isArray(payload.workOrderIds) ? payload.workOrderIds : [])
.map((item) => normalizeOptionalId(item))
.filter((item): item is number => item !== null),
),
]
if (workOrderIds.length === 0) {
throw createHttpError('请至少选择一个待验收订单', {
statusCode: 400,
errorCode: 'work_order_batch_accept_required',
})
}
const workOrders = await Promise.all(
workOrderIds.map((workOrderId) => getRequiredWorkOrder(workOrderId)),
)
if (
workOrders.some(
(workOrder) =>
resolveWorkOrderOperationalStatus(workOrder) !== WORK_ORDER_STATUS.PENDING_ACCEPTANCE,
)
) {
throw createHttpError('选中的订单包含非待验收状态,请刷新后重试', {
statusCode: 409,
errorCode: 'work_order_batch_accept_status_invalid',
})
}
const pendingSharingCounts = await Promise.all(
workOrders.map((workOrder) => countWorkOrderPendingSharingSubmissions(workOrder.id)),
)
if (pendingSharingCounts.some((count) => count > 0)) {
throw createHttpError('选中的订单存在拼单打手未提交验收资料,暂不能统一验收', {
statusCode: 409,
errorCode: 'work_order_batch_accept_sharing_pending_submissions',
})
}
const results = []
for (const workOrder of workOrders) {
results.push(await acceptAdminWorkOrder(workOrder.id, actorName))
}
return { orders: results.map((result) => result.order) }
}
export async function unassignAdminWorkOrder(workOrderId: number | string, actorName = '') {
const workOrder = await getRequiredWorkOrder(workOrderId)
if (workOrder.status !== WORK_ORDER_STATUS.IN_PROGRESS || !workOrder.assigned_worker_id) {
throw createHttpError('只有进行中且已指派打手的订单可以取消指派', {
statusCode: 409,
errorCode: 'work_order_unassign_status_invalid',
})
}
const now = nowIso()
const { order: updated, failureReason } = await unassignWorkOrder({
workOrderId: workOrder.id,
now,
actorName,
})
if (failureReason === 'work_order_not_in_progress') {
throw createHttpError('订单状态已变化,无法取消指派', {
statusCode: 409,
errorCode: 'work_order_unassign_status_invalid',
})
}
if (failureReason === 'work_order_no_worker') {
throw createHttpError('该订单未指派打手', {
statusCode: 409,
errorCode: 'work_order_unassign_no_worker',
})
}
if (!updated) {
throw createHttpError('取消指派失败,订单状态可能已变化', {
statusCode: 409,
errorCode: 'work_order_unassign_conflict',
})
}
publishWorkOrderRealtimeChange({
workOrderId: Number(workOrder.id),
workerIds: [Number(workOrder.assigned_worker_id)],
})
publishWorkerWalletRealtimeChange(Number(workOrder.assigned_worker_id))
return { order: mapWorkOrderAdmin(updated) }
}
export async function cancelAdminWorkOrder(
workOrderId: number | string,
payload: JsonObject = {},
actorName = '',
) {
const workOrder = await getRequiredWorkOrder(workOrderId)
if (workOrder.status !== WORK_ORDER_STATUS.IN_PROGRESS || !workOrder.assigned_worker_id) {
throw createHttpError('只有代练中且已接单的订单可以撤单', {
statusCode: 409,
errorCode: 'work_order_cancel_status_invalid',
})
}
const action = String(payload.action || '').trim()
if (!['return_to_hall', 'cancel_order'].includes(action)) {
throw createHttpError('请选择撤单处理方式', {
statusCode: 400,
errorCode: 'work_order_cancel_action_required',
})
}
const reason = String(payload.reason || '').trim()
if (action === 'cancel_order' && !reason) {
throw createHttpError('取消订单时请填写退款或撤单原因', {
statusCode: 400,
errorCode: 'work_order_cancel_reason_required',
})
}
const input = {
workOrderId: workOrder.id,
now: nowIso(),
reason,
actorName,
}
const { order: updated, failureReason } =
action === 'return_to_hall'
? await returnAssignedWorkOrderToHall(input)
: await cancelAssignedWorkOrder(input)
if (failureReason === 'work_order_not_in_progress') {
throw createHttpError('订单状态已变化,无法撤单', {
statusCode: 409,
errorCode: 'work_order_cancel_status_invalid',
})
}
if (failureReason === 'work_order_no_worker') {
throw createHttpError('该订单尚未被打手接取', {
statusCode: 409,
errorCode: 'work_order_cancel_no_worker',
})
}
if (!updated) {
throw createHttpError('撤单失败,订单状态可能已变化', {
statusCode: 409,
errorCode: 'work_order_cancel_conflict',
})
}
const workerId = Number(workOrder.assigned_worker_id)
publishWorkOrderRealtimeChange({
workOrderId: Number(workOrder.id),
workerIds: workerId ? [workerId] : [],
hallChanged: action === 'return_to_hall',
})
if (workerId) {
publishWorkerWalletRealtimeChange(workerId)
}
return { order: mapWorkOrderAdmin(updated), action }
}
export {
createAdminMockWorkOrder,
resolveAdminMockWorkOrderInput,
} from './admin-mock-work-order-service.js'
export {
resolveAdminMaterialRewardAmount,
submitAdminWorkOrderMaterial,
} from './admin-work-order-material-service.js'
export {
getAdminWorkerHallConfig,
getAdminWorkerPlatformSummary,
saveAdminWorkerHallConfig,
} from './admin-worker-platform-summary-service.js'
export async function syncWorkerOrdersForSourceOrder(
order: OrderRow,
orderItems: OrderItemRow[],
options: { source?: string; autoOnly?: boolean; sourceMetadata?: JsonObject } = {},
) {
const [rules, mappings] = await Promise.all([
listWorkProductRules({ enabled: true }),
listWorkProductRuleMappings({ enabled: true }),
])
const productMatchConfig = getWorkerProductMatchConfig()
const created: WorkOrderRow[] = []
const skipped: Array<{ orderItemId: number; reason: string }> = []
for (const item of orderItems) {
const match = resolveMatchingProductRuleDecision(order, item, rules, mappings, {
legacyFallbackEnabled: productMatchConfig.legacyFallbackEnabled,
})
const matchContext = resolveKuaishouWorkProductMatchContext(item)
if (matchContext.sellerId) {
const snapshot = safeParseJson(item.item_snapshot_json)
await createWorkProductMatchLog({
orderId: Number(order.id),
orderItemId: Number(item.id),
source: options.source || 'source_order',
sellerId: matchContext.sellerId,
relItemId: matchContext.relItemId,
itemTitle: matchContext.itemTitle,
relSkuId: matchContext.relSkuId,
skuNick: matchContext.skuNick,
matchStatus: match.reason,
ruleId: match.rule?.id || null,
mappingId: match.mappingId,
candidatesJson: JSON.stringify(match.candidates),
rawPayloadJson: JSON.stringify(
options.sourceMetadata?.kuaishouSendCodeRawPayload ||
snapshot.kuaishouSendCodeRawPayload ||
snapshot.kuaishouSendCode ||
{},
),
now: nowIso(),
})
}
if (!match.rule) {
skipped.push({
orderItemId: Number(item.id),
reason: match.reason === 'ambiguous' ? 'rule_match_ambiguous' : 'rule_not_matched',
})
continue
}
const rule = match.rule
if (options.autoOnly && !rule.auto_create) {
skipped.push({ orderItemId: Number(item.id), reason: 'auto_create_disabled' })
continue
}
if (await getWorkOrderByOrderItemId(item.id)) {
skipped.push({ orderItemId: Number(item.id), reason: 'already_exists' })
continue
}
const fields = normalizeRequirementFields(safeParseJson(rule.requirement_json).fields)
const skuQuantity = resolveSkuNameQuantity(item.sku_name)
const unitPriceFen = Number(rule.unit_price_fen || 0)
// 按数量计价:单价 × SKU 名称文字中的规格数量(如 指挥官秘钥15个);未启用则用固定接单金额
const { sharingTotalQuantity, sharingUnitReward, rewardAmount } = resolveWorkOrderRulePricing({
skuQuantity,
unitPriceFen,
fixedRewardAmount: Number(rule.reward_amount || 0),
sharingEnabled: rule.sharing_enabled === true,
sharingAutoFromOrder: rule.sharing_auto_from_order === true,
sharingTotalQuantity: Number(rule.sharing_total_quantity || 1),
sharingUnitReward: Number(rule.sharing_unit_reward || 0),
})
const sharingEnabled = resolveWorkOrderSharingEnabled(
rule.sharing_enabled === true,
sharingTotalQuantity,
)
const now = nowIso()
const materialComplete = fields.every((field) => !field.required)
const workOrder = await createWorkOrder({
workOrderNo: randomId('WO'),
productRuleId: Number(rule.id),
orderId: Number(order.id),
orderItemId: Number(item.id),
taskId: null,
platformOrderId: order.platform_order_id,
productName: item.sku_name || item.sku_code,
categoryId: rule.category_id,
status: materialComplete ? WORK_ORDER_STATUS.UNASSIGNED : WORK_ORDER_STATUS.PENDING_MATERIAL,
rewardAmount,
requiredDepositAmount: 0,
depositThresholdAmount: 0,
sharingEnabled,
sharingTotalQuantity,
sharingUnitReward,
timeoutMinutes: Number(rule.timeout_minutes || 0),
timeoutPolicy: String(rule.timeout_policy || 'reopen').trim(),
materialJson: JSON.stringify({
source: {
orderId: Number(order.id),
orderItemId: Number(item.id),
provider: order.provider,
platform: order.platform,
shopId: order.shop_id,
shopName: order.shop_name,
skuCode: item.sku_code,
skuName: item.sku_name,
skuQuantity,
quantity: Number(item.quantity || 1),
orderAmountFen: Number(order.total_amount || 0),
currency: order.currency || 'CNY',
syncSource: options.source || 'source_order',
...options.sourceMetadata,
},
}),
requirementJson: JSON.stringify({ fields }),
now,
})
if (workOrder) {
created.push(workOrder)
await createWorkOrderEvent({
workOrderId: workOrder.id,
actorType: 'system',
actorId: options.source || 'source_order',
eventType: 'source_synced',
fromStatus: '',
toStatus: workOrder.status,
payloadJson: JSON.stringify({
ruleKey: rule.rule_key,
orderId: order.id,
orderItemId: item.id,
}),
now,
})
if (workOrder.status === WORK_ORDER_STATUS.PENDING_MATERIAL) {
await createWorkOrderMaterialAdminNotification({
workOrderId: Number(workOrder.id),
workOrderNo: workOrder.work_order_no,
productName: workOrder.product_name,
})
}
}
}
return {
created: created.map((workOrder) => mapWorkOrderAdmin(workOrder)),
skipped,
createdCount: created.length,
skippedCount: skipped.length,
}
}
export {
listAdminWorkerCancelRequests,
reviewAdminWorkerCancelRequest,
} from './admin-worker-cancel-service.js'
export {
listAdminWorkerUsers,
resetAdminWorkerPassword,
reviewAdminWorkerUser,
} from './admin-worker-user-service.js'
export { syncWorkerOrdersForSourceOrder } from './sync-worker-orders-service.js'
@@ -49,3 +49,4 @@ export {
export * from './worker-product-match-config-service.js'
export * from './worker-announcement-config-service.js'
export * from './worker-hall-config-service.js'
export * from './sync-worker-orders-service.js'
@@ -0,0 +1,175 @@
import { WORK_ORDER_STATUS } from '../../domain/work-order-status.js'
import {
createWorkProductMatchLog,
createWorkOrder,
createWorkOrderEvent,
getWorkOrderByOrderItemId,
listWorkProductRuleMappings,
listWorkProductRules,
type WorkOrderRow,
} from '../../repositories/worker-platform/index.js'
import type { JsonObject } from '../../types/json.js'
import type { OrderItemRow, OrderRow } from '../../types/repository/rows.js'
import { randomId } from '../../utils/random.js'
import { nowIso } from '../../utils/time.js'
import { safeParseJson } from '../admin/admin-query-utils.js'
import { createWorkOrderMaterialAdminNotification } from '../admin/admin-notification-service.js'
import {
mapWorkOrderAdmin,
normalizeRequirementFields,
resolveKuaishouWorkProductMatchContext,
resolveMatchingProductRuleDecision,
resolveSkuNameQuantity,
resolveWorkOrderRulePricing,
resolveWorkOrderSharingEnabled,
} from './mappers.js'
import { getWorkerProductMatchConfig } from './worker-product-match-config-service.js'
export async function syncWorkerOrdersForSourceOrder(
order: OrderRow,
orderItems: OrderItemRow[],
options: { source?: string; autoOnly?: boolean; sourceMetadata?: JsonObject } = {},
) {
const [rules, mappings] = await Promise.all([
listWorkProductRules({ enabled: true }),
listWorkProductRuleMappings({ enabled: true }),
])
const productMatchConfig = getWorkerProductMatchConfig()
const created: WorkOrderRow[] = []
const skipped: Array<{ orderItemId: number; reason: string }> = []
for (const item of orderItems) {
const match = resolveMatchingProductRuleDecision(order, item, rules, mappings, {
legacyFallbackEnabled: productMatchConfig.legacyFallbackEnabled,
})
const matchContext = resolveKuaishouWorkProductMatchContext(item)
if (matchContext.sellerId) {
const snapshot = safeParseJson(item.item_snapshot_json)
await createWorkProductMatchLog({
orderId: Number(order.id),
orderItemId: Number(item.id),
source: options.source || 'source_order',
sellerId: matchContext.sellerId,
relItemId: matchContext.relItemId,
itemTitle: matchContext.itemTitle,
relSkuId: matchContext.relSkuId,
skuNick: matchContext.skuNick,
matchStatus: match.reason,
ruleId: match.rule?.id || null,
mappingId: match.mappingId,
candidatesJson: JSON.stringify(match.candidates),
rawPayloadJson: JSON.stringify(
options.sourceMetadata?.kuaishouSendCodeRawPayload ||
snapshot.kuaishouSendCodeRawPayload ||
snapshot.kuaishouSendCode ||
{},
),
now: nowIso(),
})
}
if (!match.rule) {
skipped.push({
orderItemId: Number(item.id),
reason: match.reason === 'ambiguous' ? 'rule_match_ambiguous' : 'rule_not_matched',
})
continue
}
const rule = match.rule
if (options.autoOnly && !rule.auto_create) {
skipped.push({ orderItemId: Number(item.id), reason: 'auto_create_disabled' })
continue
}
if (await getWorkOrderByOrderItemId(item.id)) {
skipped.push({ orderItemId: Number(item.id), reason: 'already_exists' })
continue
}
const fields = normalizeRequirementFields(safeParseJson(rule.requirement_json).fields)
const skuQuantity = resolveSkuNameQuantity(item.sku_name)
const { sharingTotalQuantity, sharingUnitReward, rewardAmount } = resolveWorkOrderRulePricing({
skuQuantity,
unitPriceFen: Number(rule.unit_price_fen || 0),
fixedRewardAmount: Number(rule.reward_amount || 0),
sharingEnabled: rule.sharing_enabled === true,
sharingAutoFromOrder: rule.sharing_auto_from_order === true,
sharingTotalQuantity: Number(rule.sharing_total_quantity || 1),
sharingUnitReward: Number(rule.sharing_unit_reward || 0),
})
const sharingEnabled = resolveWorkOrderSharingEnabled(
rule.sharing_enabled === true,
sharingTotalQuantity,
)
const now = nowIso()
const materialComplete = fields.every((field) => !field.required)
const workOrder = await createWorkOrder({
workOrderNo: randomId('WO'),
productRuleId: Number(rule.id),
orderId: Number(order.id),
orderItemId: Number(item.id),
taskId: null,
platformOrderId: order.platform_order_id,
productName: item.sku_name || item.sku_code,
categoryId: rule.category_id,
status: materialComplete ? WORK_ORDER_STATUS.UNASSIGNED : WORK_ORDER_STATUS.PENDING_MATERIAL,
rewardAmount,
requiredDepositAmount: 0,
depositThresholdAmount: 0,
sharingEnabled,
sharingTotalQuantity,
sharingUnitReward,
timeoutMinutes: Number(rule.timeout_minutes || 0),
timeoutPolicy: String(rule.timeout_policy || 'reopen').trim(),
materialJson: JSON.stringify({
source: {
orderId: Number(order.id),
orderItemId: Number(item.id),
provider: order.provider,
platform: order.platform,
shopId: order.shop_id,
shopName: order.shop_name,
skuCode: item.sku_code,
skuName: item.sku_name,
skuQuantity,
quantity: Number(item.quantity || 1),
orderAmountFen: Number(order.total_amount || 0),
currency: order.currency || 'CNY',
syncSource: options.source || 'source_order',
...options.sourceMetadata,
},
}),
requirementJson: JSON.stringify({ fields }),
now,
})
if (!workOrder) continue
created.push(workOrder)
await createWorkOrderEvent({
workOrderId: workOrder.id,
actorType: 'system',
actorId: options.source || 'source_order',
eventType: 'source_synced',
fromStatus: '',
toStatus: workOrder.status,
payloadJson: JSON.stringify({
ruleKey: rule.rule_key,
orderId: order.id,
orderItemId: item.id,
}),
now,
})
if (workOrder.status === WORK_ORDER_STATUS.PENDING_MATERIAL) {
await createWorkOrderMaterialAdminNotification({
workOrderId: Number(workOrder.id),
workOrderNo: workOrder.work_order_no,
productName: workOrder.product_name,
})
}
}
return {
created: created.map((workOrder) => mapWorkOrderAdmin(workOrder)),
skipped,
createdCount: created.length,
skippedCount: skipped.length,
}
}