feat: 完善履约路由、订单服务与自动核销
This commit is contained in:
@@ -152,6 +152,8 @@ export async function createAdminMockWorkOrder(payload: JsonObject = {}) {
|
||||
? JSON.stringify({
|
||||
profileId: serviceProfile.profileId,
|
||||
categoryId: serviceProfile.categoryId,
|
||||
sourceCategoryId: serviceProfile.sourceCategoryId,
|
||||
inherited: serviceProfile.inherited,
|
||||
versionId: serviceProfile.versionId,
|
||||
versionNo: serviceProfile.versionNo,
|
||||
config: serviceProfile.config,
|
||||
|
||||
@@ -13,7 +13,10 @@ import {
|
||||
publishWorkerWalletRealtimeChange,
|
||||
publishWorkOrderRealtimeChange,
|
||||
} from '../realtime/realtime-event-service.js'
|
||||
import { consumeIndustryVouchersBeforeWorkOrderAssign } from './publish-voucher-service.js'
|
||||
import {
|
||||
scheduleVoucherConsumeForWorkOrder,
|
||||
type VoucherConsumeScheduleResult,
|
||||
} from '../fulfillment/voucher-consume-queue-service.js'
|
||||
import {
|
||||
mapWorkOrderAdmin,
|
||||
normalizeOptionalId,
|
||||
@@ -65,8 +68,7 @@ export async function assignAdminWorkOrderToWorker(
|
||||
})
|
||||
}
|
||||
|
||||
// 已有拼单份额的订单不能再按整单指派;放在核销电子凭证之前,
|
||||
// 避免“凭证已核销但指派失败”的副作用。
|
||||
// 已有拼单份额的订单不能再按整单指派。
|
||||
const activeShares = (await listWorkOrderShares(workOrder.id)).filter(
|
||||
(share) => share.status !== 'cancelled',
|
||||
)
|
||||
@@ -77,26 +79,6 @@ export async function assignAdminWorkOrderToWorker(
|
||||
})
|
||||
}
|
||||
|
||||
const voucherConsume = await consumeIndustryVouchersBeforeWorkOrderAssign(workOrder)
|
||||
if (!voucherConsume.ok) {
|
||||
const failedAt = nowIso()
|
||||
await createWorkOrderEvent({
|
||||
workOrderId: workOrder.id,
|
||||
actorType: 'admin',
|
||||
actorId: actorName,
|
||||
eventType: 'assign_voucher_consume_failed',
|
||||
fromStatus: workOrder.status,
|
||||
toStatus: workOrder.status,
|
||||
payloadJson: JSON.stringify({ workerId: worker.id, voucherConsume }),
|
||||
now: failedAt,
|
||||
})
|
||||
throw createHttpError(voucherConsume.errorMessage || '电子凭证核销失败,订单未指派', {
|
||||
statusCode: 409,
|
||||
errorCode: 'work_order_assign_voucher_consume_failed',
|
||||
context: voucherConsume,
|
||||
})
|
||||
}
|
||||
|
||||
const now = nowIso()
|
||||
const { order: updated, failureReason } = await assignWorkOrderToWorker({
|
||||
workOrderId: workOrder.id,
|
||||
@@ -108,38 +90,70 @@ export async function assignAdminWorkOrderToWorker(
|
||||
actorName,
|
||||
})
|
||||
if (failureReason === 'work_order_not_assignable') {
|
||||
throw createHttpError(
|
||||
resolveAssignFailureMessage(voucherConsume.voucherCount, '订单状态已变化,无法指派'),
|
||||
{ statusCode: 409, errorCode: 'work_order_not_assignable' },
|
||||
)
|
||||
throw createHttpError('订单状态已变化,无法指派', {
|
||||
statusCode: 409,
|
||||
errorCode: 'work_order_not_assignable',
|
||||
})
|
||||
}
|
||||
if (failureReason === 'worker_active_order_limit') {
|
||||
throw createHttpError(
|
||||
resolveAssignFailureMessage(voucherConsume.voucherCount, '该打手已达到最大同时接单量'),
|
||||
{ statusCode: 409, errorCode: 'worker_active_order_limit' },
|
||||
)
|
||||
throw createHttpError('该打手已达到最大同时接单量', {
|
||||
statusCode: 409,
|
||||
errorCode: 'worker_active_order_limit',
|
||||
})
|
||||
}
|
||||
if (failureReason === 'worker_deposit_insufficient') {
|
||||
throw createHttpError(
|
||||
resolveAssignFailureMessage(voucherConsume.voucherCount, '该打手余额不足,无法冻结所需押金'),
|
||||
{ statusCode: 409, errorCode: 'worker_deposit_insufficient' },
|
||||
)
|
||||
throw createHttpError('该打手余额不足,无法冻结所需押金', {
|
||||
statusCode: 409,
|
||||
errorCode: 'worker_deposit_insufficient',
|
||||
})
|
||||
}
|
||||
if (failureReason === 'work_order_has_shares') {
|
||||
throw createHttpError(
|
||||
resolveAssignFailureMessage(
|
||||
voucherConsume.voucherCount,
|
||||
'该订单已有拼单份额,不能按整单指派;请使用拼单管理',
|
||||
),
|
||||
{ statusCode: 409, errorCode: 'work_order_has_shares' },
|
||||
)
|
||||
throw createHttpError('该订单已有拼单份额,不能按整单指派;请使用拼单管理', {
|
||||
statusCode: 409,
|
||||
errorCode: 'work_order_has_shares',
|
||||
})
|
||||
}
|
||||
if (!updated) {
|
||||
throw createHttpError(
|
||||
resolveAssignFailureMessage(voucherConsume.voucherCount, '指派失败,订单状态可能已变化'),
|
||||
{ statusCode: 409, errorCode: 'work_order_assign_conflict' },
|
||||
)
|
||||
throw createHttpError('指派失败,订单状态可能已变化', {
|
||||
statusCode: 409,
|
||||
errorCode: 'work_order_assign_conflict',
|
||||
})
|
||||
}
|
||||
|
||||
let voucherConsume: VoucherConsumeScheduleResult
|
||||
try {
|
||||
voucherConsume = await scheduleVoucherConsumeForWorkOrder(updated, 'work_order_assigned')
|
||||
} catch (error) {
|
||||
voucherConsume = {
|
||||
ok: false,
|
||||
voucherCount: 0,
|
||||
consumedCount: 0,
|
||||
alreadyConsumedCount: 0,
|
||||
failedCount: 1,
|
||||
voucherCodes: [],
|
||||
errorMessage: error instanceof Error ? error.message : '安排自动核销失败',
|
||||
queuedCount: 0,
|
||||
jobIds: [],
|
||||
dueAt: '',
|
||||
status: 'failed',
|
||||
}
|
||||
}
|
||||
await createWorkOrderEvent({
|
||||
workOrderId: workOrder.id,
|
||||
actorType: 'admin',
|
||||
actorId: actorName,
|
||||
eventType:
|
||||
voucherConsume.status === 'queued'
|
||||
? 'assign_voucher_consume_scheduled'
|
||||
: voucherConsume.status === 'failed'
|
||||
? 'assign_voucher_consume_schedule_failed'
|
||||
: 'assign_voucher_consume_schedule_skipped',
|
||||
fromStatus: workOrder.status,
|
||||
toStatus: updated.status,
|
||||
payloadJson: JSON.stringify({ workerId: worker.id, voucherConsume }),
|
||||
now,
|
||||
}).catch(() => undefined)
|
||||
|
||||
publishWorkOrderRealtimeChange({
|
||||
workOrderId: Number(workOrder.id),
|
||||
workerIds: [Number(worker.id)],
|
||||
@@ -149,10 +163,6 @@ export async function assignAdminWorkOrderToWorker(
|
||||
return { order: mapWorkOrderAdmin(updated), voucherConsume }
|
||||
}
|
||||
|
||||
function resolveAssignFailureMessage(voucherCount: number, fallback: string) {
|
||||
return voucherCount > 0 ? `电子凭证已核销,但${fallback},请重试指派` : fallback
|
||||
}
|
||||
|
||||
function resolveAssignedDeadlineAt(timeoutMinutes: number, now: string): string | null {
|
||||
if (timeoutMinutes <= 0) return null
|
||||
return new Date(new Date(now).getTime() + timeoutMinutes * 60 * 1000).toISOString()
|
||||
|
||||
@@ -22,9 +22,9 @@ import { resolveAdminNotificationEntity } from '../admin/admin-notification-serv
|
||||
import { enqueueCustomerOrderNotification } from '../notification/customer-order-notification-service.js'
|
||||
import { publishWorkOrderRealtimeChange } from '../realtime/realtime-event-service.js'
|
||||
import {
|
||||
consumeIndustryVouchersBeforeWorkOrderPublish,
|
||||
type WorkOrderVoucherConsumeResult,
|
||||
} from './publish-voucher-service.js'
|
||||
scheduleVoucherConsumeForWorkOrder,
|
||||
type VoucherConsumeScheduleResult,
|
||||
} from '../fulfillment/voucher-consume-queue-service.js'
|
||||
import {
|
||||
mapWorkOrderAdmin,
|
||||
mapWorkOrderShare,
|
||||
@@ -347,7 +347,7 @@ export async function reopenAdminCancelledWorkOrder(workOrderId: number | string
|
||||
export async function publishAdminWorkOrder(
|
||||
workOrderId: number | string,
|
||||
actorName = '',
|
||||
options: { requireVoucherConsume?: boolean } = {},
|
||||
_options: { requireVoucherConsume?: boolean } = {},
|
||||
) {
|
||||
const workOrder = await getRequiredWorkOrder(workOrderId)
|
||||
if (workOrder.status !== WORK_ORDER_STATUS.UNASSIGNED) {
|
||||
@@ -364,39 +364,6 @@ export async function publishAdminWorkOrder(
|
||||
}
|
||||
|
||||
const now = nowIso()
|
||||
let voucherConsume: WorkOrderVoucherConsumeResult
|
||||
try {
|
||||
voucherConsume = await consumeIndustryVouchersBeforeWorkOrderPublish(workOrder)
|
||||
} catch (error) {
|
||||
voucherConsume = {
|
||||
ok: false,
|
||||
voucherCount: 0,
|
||||
consumedCount: 0,
|
||||
alreadyConsumedCount: 0,
|
||||
failedCount: 1,
|
||||
voucherCodes: [],
|
||||
errorMessage: error instanceof Error ? error.message : '电子凭证核销失败',
|
||||
}
|
||||
}
|
||||
if (!voucherConsume.ok) {
|
||||
await createWorkOrderEvent({
|
||||
workOrderId: workOrder.id,
|
||||
actorType: 'system',
|
||||
actorId: 'kuaishou_send_code',
|
||||
eventType: 'publish_voucher_consume_failed',
|
||||
fromStatus: workOrder.status,
|
||||
toStatus: workOrder.status,
|
||||
payloadJson: JSON.stringify(voucherConsume),
|
||||
now: nowIso(),
|
||||
})
|
||||
if (options.requireVoucherConsume === true) {
|
||||
throw createHttpError(voucherConsume.errorMessage || '电子凭证核销失败,订单未发布', {
|
||||
statusCode: 409,
|
||||
errorCode: 'work_order_publish_voucher_consume_failed',
|
||||
context: { voucherConsume },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const published = await publishUnassignedWorkOrder({
|
||||
workOrderId: workOrder.id,
|
||||
@@ -411,6 +378,39 @@ export async function publishAdminWorkOrder(
|
||||
}
|
||||
|
||||
const currentOrder = await getRequiredWorkOrder(workOrder.id)
|
||||
let voucherConsume: VoucherConsumeScheduleResult
|
||||
try {
|
||||
voucherConsume = await scheduleVoucherConsumeForWorkOrder(currentOrder, 'work_order_published')
|
||||
} catch (error) {
|
||||
voucherConsume = {
|
||||
ok: false,
|
||||
voucherCount: 0,
|
||||
consumedCount: 0,
|
||||
alreadyConsumedCount: 0,
|
||||
failedCount: 1,
|
||||
voucherCodes: [],
|
||||
errorMessage: error instanceof Error ? error.message : '安排自动核销失败',
|
||||
queuedCount: 0,
|
||||
jobIds: [],
|
||||
dueAt: '',
|
||||
status: 'failed',
|
||||
}
|
||||
}
|
||||
await createWorkOrderEvent({
|
||||
workOrderId: currentOrder.id,
|
||||
actorType: 'system',
|
||||
actorId: 'voucher_consume_policy',
|
||||
eventType:
|
||||
voucherConsume.status === 'queued'
|
||||
? 'publish_voucher_consume_scheduled'
|
||||
: voucherConsume.status === 'failed'
|
||||
? 'publish_voucher_consume_schedule_failed'
|
||||
: 'publish_voucher_consume_schedule_skipped',
|
||||
fromStatus: currentOrder.status,
|
||||
toStatus: currentOrder.status,
|
||||
payloadJson: JSON.stringify(voucherConsume),
|
||||
now: nowIso(),
|
||||
}).catch(() => undefined)
|
||||
publishWorkOrderRealtimeChange({ workOrderId: Number(workOrder.id), hallChanged: true })
|
||||
await enqueueCustomerOrderNotification({
|
||||
orderId: currentOrder.order_id,
|
||||
|
||||
@@ -47,9 +47,9 @@ import { assignAdminWorkOrderToWorker } from './admin-work-order-assignment-serv
|
||||
import { unassignAdminWorkOrder } from './admin-work-order-cancellation-service.js'
|
||||
import { mapWorkOrderAdmin } from './mappers.js'
|
||||
import {
|
||||
consumeIndustryVouchersBeforeWorkOrderPublish,
|
||||
type WorkOrderVoucherConsumeResult,
|
||||
} from './publish-voucher-service.js'
|
||||
scheduleVoucherConsumeForWorkOrder,
|
||||
type VoucherConsumeScheduleResult,
|
||||
} from '../fulfillment/voucher-consume-queue-service.js'
|
||||
import {
|
||||
type ConsignmentMatchOption,
|
||||
type ConsignmentMatchResult,
|
||||
@@ -383,7 +383,7 @@ export async function fulfillWorkOrderWithConsignment(input: {
|
||||
}): Promise<{
|
||||
order: ReturnType<typeof mapWorkOrderAdmin>
|
||||
inventory: ReturnType<typeof mapConsignmentInventory>
|
||||
voucherConsume: WorkOrderVoucherConsumeResult
|
||||
voucherConsume: VoucherConsumeScheduleResult
|
||||
}> {
|
||||
const workOrder = await getWorkOrderById(input.workOrderId)
|
||||
if (!workOrder) {
|
||||
@@ -576,13 +576,10 @@ export async function fulfillWorkOrderWithConsignment(input: {
|
||||
return { updatedOrder: wo, updatedInventory: inv }
|
||||
})
|
||||
|
||||
// 顺带核销快手电子凭证。
|
||||
// 核销要走快手外部接口、无法和上面的数据库事务合并回滚,因此这里只能做两件事:
|
||||
// 1) 失败时不吞错:写事件留痕,并把 voucherConsume.ok=false 交给前端提示运营人工核销;
|
||||
// 2) 绝不因为"凭证没核销"就谎报成功(此前前端无条件弹成功提示,属于假成功)。
|
||||
let voucherConsume: WorkOrderVoucherConsumeResult
|
||||
// 行内发货完成后安排延迟核销,外部平台调用不阻塞当前履约事务。
|
||||
let voucherConsume: VoucherConsumeScheduleResult
|
||||
try {
|
||||
voucherConsume = await consumeIndustryVouchersBeforeWorkOrderPublish(updatedOrder)
|
||||
voucherConsume = await scheduleVoucherConsumeForWorkOrder(updatedOrder, 'work_order_assigned')
|
||||
} catch (error) {
|
||||
voucherConsume = {
|
||||
ok: false,
|
||||
@@ -591,7 +588,11 @@ export async function fulfillWorkOrderWithConsignment(input: {
|
||||
alreadyConsumedCount: 0,
|
||||
failedCount: 1,
|
||||
voucherCodes: [],
|
||||
errorMessage: error instanceof Error ? error.message : '电子凭证核销失败',
|
||||
errorMessage: error instanceof Error ? error.message : '安排自动核销失败',
|
||||
queuedCount: 0,
|
||||
jobIds: [],
|
||||
dueAt: '',
|
||||
status: 'failed',
|
||||
}
|
||||
}
|
||||
if (!voucherConsume.ok) {
|
||||
@@ -599,8 +600,13 @@ export async function fulfillWorkOrderWithConsignment(input: {
|
||||
await createWorkOrderEvent({
|
||||
workOrderId: Number(updatedOrder.id),
|
||||
actorType: 'system',
|
||||
actorId: 'kuaishou_send_code',
|
||||
eventType: 'consignment_fulfill_voucher_consume_failed',
|
||||
actorId: 'voucher_consume_policy',
|
||||
eventType:
|
||||
voucherConsume.status === 'queued'
|
||||
? 'consignment_fulfill_voucher_consume_scheduled'
|
||||
: voucherConsume.status === 'failed'
|
||||
? 'consignment_fulfill_voucher_consume_schedule_failed'
|
||||
: 'consignment_fulfill_voucher_consume_schedule_skipped',
|
||||
fromStatus: String(updatedOrder.status),
|
||||
toStatus: String(updatedOrder.status),
|
||||
payloadJson: JSON.stringify(voucherConsume),
|
||||
@@ -668,7 +674,7 @@ export async function assignWorkOrderWithConsignmentWorker(input: {
|
||||
}): Promise<{
|
||||
order: ReturnType<typeof mapWorkOrderAdmin>
|
||||
inventory: ReturnType<typeof mapConsignmentInventory>
|
||||
voucherConsume: WorkOrderVoucherConsumeResult
|
||||
voucherConsume: VoucherConsumeScheduleResult
|
||||
}> {
|
||||
const workOrder = await getWorkOrderById(input.workOrderId)
|
||||
if (!workOrder) {
|
||||
|
||||
@@ -5,7 +5,10 @@ import {
|
||||
isOrderServiceMaterialRequired,
|
||||
normalizeProfileConfig,
|
||||
} from './order-service-profile-service.js'
|
||||
import { backfillPendingOrderServiceSnapshotsForCategory } from '../../repositories/worker-platform/order-service-profile-repo.js'
|
||||
import {
|
||||
backfillPendingOrderServiceSnapshotsForCategory,
|
||||
getPublishedOrderServiceProfileByCategoryWithInheritance,
|
||||
} from '../../repositories/worker-platform/order-service-profile-repo.js'
|
||||
|
||||
function config(patch: Record<string, unknown> = {}) {
|
||||
return normalizeProfileConfig({
|
||||
@@ -181,4 +184,40 @@ test('发布配置:待提交且无有效拼单份额的工单可更新到最
|
||||
assert.equal(count, 2)
|
||||
assert.doesNotMatch(capturedSql, /service_profile_snapshot_json IS NULL/)
|
||||
assert.match(capturedSql, /share\.status <> 'cancelled'/)
|
||||
assert.match(capturedSql, /WITH affected_categories/)
|
||||
assert.match(capturedSql, /child_profile\.status <> 'archived'/)
|
||||
assert.match(capturedSql, /wo\.category_id IN/)
|
||||
assert.match(capturedSql, /'sourceCategoryId', p\.category_id/)
|
||||
assert.match(capturedSql, /'inherited', wo\.category_id <> p\.category_id/)
|
||||
})
|
||||
|
||||
test('订单服务配置:二级分类优先,未发布时沿分类链继承父级', async () => {
|
||||
let capturedSql = ''
|
||||
const client = {
|
||||
query: async (sql: string) => {
|
||||
capturedSql = sql
|
||||
return {
|
||||
rows: [
|
||||
{
|
||||
profile_id: 9,
|
||||
category_id: 3,
|
||||
source_category_id: 3,
|
||||
version_id: 19,
|
||||
version_no: 4,
|
||||
config_json: { fields: [] },
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
} as unknown as Parameters<typeof getPublishedOrderServiceProfileByCategoryWithInheritance>[1]
|
||||
|
||||
const resolved = await getPublishedOrderServiceProfileByCategoryWithInheritance(81, client)
|
||||
|
||||
assert.equal(resolved?.profile_id, 9)
|
||||
assert.equal(resolved?.category_id, 3)
|
||||
assert.equal(resolved?.source_category_id, 3)
|
||||
assert.equal(resolved?.version_no, 4)
|
||||
assert.match(capturedSql, /WITH RECURSIVE category_chain/)
|
||||
assert.match(capturedSql, /ORDER BY chain\.depth ASC/)
|
||||
assert.match(capturedSql, /p\.status <> 'archived'/)
|
||||
})
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
backfillPendingOrderServiceSnapshotsForCategory,
|
||||
createOrderServiceProfileVersion,
|
||||
getOrderServiceProfileVersion,
|
||||
getPublishedOrderServiceProfileByCategory,
|
||||
getPublishedOrderServiceProfileByCategoryWithInheritance,
|
||||
listOrderServiceProfiles,
|
||||
setOrderServiceProfileCurrentVersion,
|
||||
upsertOrderServiceProfile,
|
||||
@@ -83,6 +83,8 @@ export type OrderServiceProfileConfig = {
|
||||
export type OrderServiceProfileSnapshot = {
|
||||
profileId: number
|
||||
categoryId: number
|
||||
sourceCategoryId: number
|
||||
inherited: boolean
|
||||
versionId: number
|
||||
versionNo: number
|
||||
config: OrderServiceProfileConfig
|
||||
@@ -307,11 +309,14 @@ export async function getPublishedOrderServiceProfileSnapshotByCategory(
|
||||
categoryId: number | string,
|
||||
client?: Pick<PoolClient, 'query'>,
|
||||
): Promise<OrderServiceProfileSnapshot | null> {
|
||||
const row = await getPublishedOrderServiceProfileByCategory(categoryId, client)
|
||||
const row = await getPublishedOrderServiceProfileByCategoryWithInheritance(categoryId, client)
|
||||
if (!row) return null
|
||||
const targetCategoryId = Number(categoryId)
|
||||
return {
|
||||
profileId: Number(row.profile_id),
|
||||
categoryId: Number(row.category_id),
|
||||
categoryId: targetCategoryId,
|
||||
sourceCategoryId: Number(row.source_category_id),
|
||||
inherited: Number(row.source_category_id) !== targetCategoryId,
|
||||
versionId: Number(row.version_id),
|
||||
versionNo: Number(row.version_no),
|
||||
config: normalizeProfileConfig(row.config_json),
|
||||
@@ -329,6 +334,8 @@ export async function getOrderServiceProfileSnapshotByVersion(input: {
|
||||
return {
|
||||
profileId: Number(input.profileId),
|
||||
categoryId: Number(input.categoryId),
|
||||
sourceCategoryId: Number(input.categoryId),
|
||||
inherited: false,
|
||||
versionId: Number(version.id),
|
||||
versionNo: Number(version.version_no),
|
||||
config: normalizeProfileConfig(version.config_json),
|
||||
|
||||
@@ -560,6 +560,13 @@ async function resolveProfileConfig(
|
||||
})
|
||||
if (snapshotFromVersion) return snapshotFromVersion.config
|
||||
}
|
||||
if (order.category_id) {
|
||||
const published = await getPublishedOrderServiceProfileSnapshotByCategory(
|
||||
Number(order.category_id),
|
||||
client,
|
||||
)
|
||||
if (published) return published.config
|
||||
}
|
||||
const legacy = safeParseJson(order.requirement_json)
|
||||
if (Array.isArray(legacy.fields)) {
|
||||
return {
|
||||
|
||||
@@ -8,7 +8,7 @@ import { query } from '../../db/client.js'
|
||||
* - 订单不存在(91 未到/缺失):用快手数据兜底建单 + 建工单 + 凭证回填
|
||||
*
|
||||
* 幂等依据:orders 唯一键 provider+platform+shop_id+platform_order_id
|
||||
* (由 91 侧查重逻辑保证同 oid 单条记录)、work_orders.order_item_id 唯一索引、
|
||||
* (由 91 侧查重逻辑保证同 oid 单条记录)、work_orders 未取消工单对 order_item_id 的唯一索引、
|
||||
* listWorkOrdersByPlatformOrderId 查重。
|
||||
*
|
||||
* 本服务只写 orders / order_items / work_orders,不触碰
|
||||
|
||||
@@ -125,6 +125,7 @@ export async function syncWorkerOrdersForSourceOrder(
|
||||
sourceMetadata?: JsonObject
|
||||
skipChannelAssign?: boolean
|
||||
preferredWorkRuleKey?: string
|
||||
retryCancelledWorkOrder?: boolean
|
||||
} = {},
|
||||
) {
|
||||
const [rules, ruleMappings] = await Promise.all([
|
||||
@@ -252,7 +253,10 @@ export async function syncWorkerOrdersForSourceOrder(
|
||||
|
||||
// 已有工单:跳过匹配日志,避免 PDD 定时同步对旧单反复刷「命中」日志。
|
||||
const existingWorkOrder = await getWorkOrderByOrderItemId(item.id)
|
||||
if (existingWorkOrder) {
|
||||
if (
|
||||
existingWorkOrder &&
|
||||
!(options.retryCancelledWorkOrder === true && existingWorkOrder.status === 'cancelled')
|
||||
) {
|
||||
skipped.push({ orderItemId: Number(item.id), reason: 'already_exists' })
|
||||
decisions.push(
|
||||
buildWorkerConsumerDecision(item, {
|
||||
@@ -425,6 +429,8 @@ export async function syncWorkerOrdersForSourceOrder(
|
||||
? JSON.stringify({
|
||||
profileId: serviceProfile.profileId,
|
||||
categoryId: serviceProfile.categoryId,
|
||||
sourceCategoryId: serviceProfile.sourceCategoryId,
|
||||
inherited: serviceProfile.inherited,
|
||||
versionId: serviceProfile.versionId,
|
||||
versionNo: serviceProfile.versionNo,
|
||||
config: serviceProfile.config,
|
||||
|
||||
@@ -2554,14 +2554,15 @@ test('履约与指派两条路径都要过服务端规格复检(不只依赖
|
||||
assert.ok(src.includes('consignment_match_note_required'))
|
||||
})
|
||||
|
||||
test('内部履约的电子凭证核销失败要写事件留痕(不能静默假成功)', async () => {
|
||||
test('内部履约要投递延迟核销任务并写事件留痕', async () => {
|
||||
const readFile = await import('node:fs/promises')
|
||||
const src = await readFile.readFile(
|
||||
new URL('./consignment-fulfillment-service.ts', import.meta.url),
|
||||
'utf8',
|
||||
)
|
||||
|
||||
assert.ok(src.includes('consignment_fulfill_voucher_consume_failed'))
|
||||
assert.ok(src.includes('consignment_fulfill_voucher_consume_scheduled'))
|
||||
assert.ok(src.includes('scheduleVoucherConsumeForWorkOrder'))
|
||||
assert.ok(src.includes('if (!voucherConsume.ok)'))
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user