fix(consignment): 修复内部匹配 delay=0 黑洞、匹配改写报酬不恢复、草稿未发布即生效
P0-1: internalMatch.delayMinutes 归一化后 <=0 直接回大厅,不再依赖 fallbackToHall 进入 internal_matching(手动/自动两条入口共用 resolveInternalMatchDelayMinutes)。 P0-2: 新增 work_orders.consignment_original_reward_fen(迁移 145),匹配改写报酬前固化原值;两个统一释放入口(releaseConsignmentAllocationsForWorkOrderWithClient / releaseConsignmentInventoryBindingForWorkOrderWithClient)恢复并清空,覆盖释放、取消、超时、驳回等全部路径;二次匹配不再覆盖原值。 P1-1: 打手端分类、工单配置解析、寄售仓库筛选项改读 consignment_profile_versions.config_json(current_version 快照),后台仍读写草稿;版本缺失时回退草稿兼容历史数据。 测试:新增配置归一化/报酬恢复/发布快照单测,集成测试补草稿隔离与快照发布用例;隔离库 35/35 通过,worker-platform 单测 415 通过。
This commit is contained in:
@@ -546,6 +546,8 @@ export async function fulfillWorkOrderWithConsignment(input: {
|
||||
eventType: 'consignment_fulfilled',
|
||||
materialJson: updatedMaterial,
|
||||
rewardAmountFen: effectiveRewardAmountFen,
|
||||
// 内部现货履约按成本价改写报酬时,先固化原值供后续释放恢复。
|
||||
captureOriginalReward: effectiveRewardAmountFen !== previousRewardAmountFen,
|
||||
extraPayload: {
|
||||
inventoryId: inv.id,
|
||||
inventoryNo: inv.inventory_no,
|
||||
@@ -806,6 +808,8 @@ export async function assignWorkOrderWithConsignmentWorker(input: {
|
||||
await updateWorkOrder(workOrder.id, {
|
||||
material_json: updatedMaterial,
|
||||
reward_amount: rewardAmountFen,
|
||||
// 指派供货卖家本人时会把报酬改写为结算价;先固化原值,释放库存时恢复。
|
||||
capture_consignment_original_reward: rewardAmountFen !== previousRewardAmountFen,
|
||||
updated_at: now,
|
||||
})
|
||||
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import test from 'node:test'
|
||||
|
||||
import { resolveInternalMatchDelayMinutes } from './consignment-match-engine-service.js'
|
||||
|
||||
test('内部匹配等待时长:0/负数/缺失都归一为 0(直接发大厅)', () => {
|
||||
assert.equal(resolveInternalMatchDelayMinutes({ delayMinutes: 0 }), 0)
|
||||
assert.equal(resolveInternalMatchDelayMinutes({ delayMinutes: -5 }), 0)
|
||||
assert.equal(resolveInternalMatchDelayMinutes({ delayMinutes: '0' }), 0)
|
||||
assert.equal(resolveInternalMatchDelayMinutes({}), 0)
|
||||
assert.equal(resolveInternalMatchDelayMinutes(null), 0)
|
||||
assert.equal(resolveInternalMatchDelayMinutes(undefined), 0)
|
||||
assert.equal(resolveInternalMatchDelayMinutes({ delayMinutes: 30 }), 30)
|
||||
assert.equal(resolveInternalMatchDelayMinutes({ delayMinutes: '12.6' }), 13)
|
||||
})
|
||||
|
||||
test('内部匹配入口:0 分钟必须直接返回,不能依赖 fallbackToHall 进入 internal_matching', async () => {
|
||||
const source = await readFile(
|
||||
new URL('./consignment-match-engine-service.ts', import.meta.url),
|
||||
'utf8',
|
||||
)
|
||||
const start = source.indexOf('export async function enterConsignmentInternalMatching')
|
||||
const end = source.indexOf('export async function reenterConsignmentInternalMatching', start)
|
||||
assert.ok(start >= 0 && end > start, '找不到 enterConsignmentInternalMatching')
|
||||
const body = source.slice(start, end)
|
||||
|
||||
assert.ok(
|
||||
body.includes(
|
||||
'const delayMinutes = resolveInternalMatchDelayMinutes(profile.config.internalMatch)',
|
||||
),
|
||||
'进入内部匹配必须使用统一的等待时长解析',
|
||||
)
|
||||
assert.ok(
|
||||
body.includes('if (delayMinutes <= 0) return false'),
|
||||
'0 分钟必须直接返回,禁止进入 internal_matching',
|
||||
)
|
||||
assert.ok(
|
||||
!body.includes('delayMinutes <= 0 && !profile.config.internalMatch.fallbackToHall'),
|
||||
'不允许再用 fallbackToHall 绕过 0 分钟保护(会生成无截止时间的悬空工单)',
|
||||
)
|
||||
})
|
||||
|
||||
test('匹配改写报酬:首次固化原值,所有统一释放路径都必须恢复', async () => {
|
||||
const [engineSrc, allocationSrc, consignmentSrc, managementSrc, fulfillmentSrc, giftSrc] =
|
||||
await Promise.all([
|
||||
readFile(new URL('./consignment-match-engine-service.ts', import.meta.url), 'utf8'),
|
||||
readFile(
|
||||
new URL(
|
||||
'../../repositories/worker-platform/consignment-allocation-repo.ts',
|
||||
import.meta.url,
|
||||
),
|
||||
'utf8',
|
||||
),
|
||||
readFile(
|
||||
new URL('../../repositories/worker-platform/consignment-repo.ts', import.meta.url),
|
||||
'utf8',
|
||||
),
|
||||
readFile(
|
||||
new URL(
|
||||
'../../repositories/worker-platform/work-order-management-repo.ts',
|
||||
import.meta.url,
|
||||
),
|
||||
'utf8',
|
||||
),
|
||||
readFile(new URL('./consignment-fulfillment-service.ts', import.meta.url), 'utf8'),
|
||||
readFile(
|
||||
new URL('../../repositories/worker-platform/work-order-gift-repo.ts', import.meta.url),
|
||||
'utf8',
|
||||
),
|
||||
])
|
||||
|
||||
assert.ok(
|
||||
engineSrc.includes(
|
||||
'consignment_original_reward_fen = COALESCE(consignment_original_reward_fen, reward_amount)',
|
||||
),
|
||||
'整单匹配必须固化匹配前的原始报酬(且二次匹配不覆盖)',
|
||||
)
|
||||
assert.ok(
|
||||
engineSrc.includes('previousRewardAmountFen: originalRewardFen'),
|
||||
'material 快照应沿用首次匹配的原始报酬',
|
||||
)
|
||||
|
||||
const releaseStart = allocationSrc.indexOf(
|
||||
'export async function releaseConsignmentAllocationsForWorkOrderWithClient',
|
||||
)
|
||||
const releaseEnd = allocationSrc.indexOf(
|
||||
'export async function markConsignmentAllocationsConsumedWithClient',
|
||||
releaseStart,
|
||||
)
|
||||
assert.ok(releaseStart >= 0 && releaseEnd > releaseStart, '找不到整单释放函数')
|
||||
assert.ok(
|
||||
allocationSrc
|
||||
.slice(releaseStart, releaseEnd)
|
||||
.includes('await restoreConsignmentOriginalRewardWithClient(client, workOrderId, now)'),
|
||||
'新分配释放路径必须恢复原始报酬',
|
||||
)
|
||||
|
||||
const bindingStart = consignmentSrc.indexOf(
|
||||
'export async function releaseConsignmentInventoryBindingForWorkOrderWithClient',
|
||||
)
|
||||
assert.ok(bindingStart >= 0, '找不到旧绑定释放函数')
|
||||
assert.ok(
|
||||
consignmentSrc
|
||||
.slice(bindingStart, bindingStart + 3000)
|
||||
.includes(
|
||||
'restoreConsignmentOriginalRewardWithClient(client, Number(workOrderId), input.now)',
|
||||
),
|
||||
'旧绑定释放路径必须恢复原始报酬',
|
||||
)
|
||||
|
||||
assert.ok(
|
||||
managementSrc.includes('capture_consignment_original_reward') &&
|
||||
managementSrc.includes(
|
||||
'consignment_original_reward_fen = COALESCE(consignment_original_reward_fen, reward_amount)',
|
||||
),
|
||||
'通用工单更新补丁必须支持固化原始报酬',
|
||||
)
|
||||
assert.ok(
|
||||
fulfillmentSrc.includes('capture_consignment_original_reward:') &&
|
||||
fulfillmentSrc.includes('captureOriginalReward:'),
|
||||
'旧履约/指派链路改写报酬时必须固化原值',
|
||||
)
|
||||
assert.ok(
|
||||
giftSrc.includes('captureOriginalReward') &&
|
||||
giftSrc.includes('COALESCE(consignment_original_reward_fen, reward_amount)'),
|
||||
'parkWorkOrderWaitingFriendAddWithClient 必须支持固化原始报酬',
|
||||
)
|
||||
})
|
||||
@@ -74,6 +74,19 @@ function addMinutesIso(minutes: number, now: string): string | null {
|
||||
return new Date(new Date(now).getTime() + normalized * 60 * 1000).toISOString()
|
||||
}
|
||||
|
||||
/**
|
||||
* 内部匹配等待时长(分钟)。0 表示不等待、直接发大厅。
|
||||
*
|
||||
* 注意:0 绝不能进入 internal_matching —— addMinutesIso(0) 返回 null,
|
||||
* 工单会变成「内部匹配中且无截止时间」,而超时任务只处理 deadline 非空的工单,
|
||||
* 最终就是永久悬空(既不在大厅也不超时)。
|
||||
*/
|
||||
export function resolveInternalMatchDelayMinutes(
|
||||
config: { delayMinutes?: unknown } | null | undefined,
|
||||
): number {
|
||||
return Math.max(0, Math.round(Number(config?.delayMinutes || 0)))
|
||||
}
|
||||
|
||||
function inventorySalePriceFen(inventory: ConsignmentInventoryRow): number {
|
||||
return Math.max(0, Math.round(Number(inventory.sale_price_fen || inventory.cost_price_fen || 0)))
|
||||
}
|
||||
@@ -272,8 +285,11 @@ export async function enterConsignmentInternalMatching(
|
||||
if (!profile) return false
|
||||
// 关闭匹配:新单直接进大厅,不进入内部匹配队列。
|
||||
if (profile.config.matching.mode === 'off') return false
|
||||
const delayMinutes = Math.max(0, profile.config.internalMatch.delayMinutes)
|
||||
if (delayMinutes <= 0 && !profile.config.internalMatch.fallbackToHall) return false
|
||||
const delayMinutes = resolveInternalMatchDelayMinutes(profile.config.internalMatch)
|
||||
// 0 分钟 = 不等待,直接发大厅。此时不能进入 internal_matching:
|
||||
// addMinutesIso(0) 返回 null,工单会变成「内部匹配中且无截止时间」,
|
||||
// 超时任务只处理 deadline 非空的工单,结果就是永久悬空。
|
||||
if (delayMinutes <= 0) return false
|
||||
const deadline = addMinutesIso(delayMinutes, now)
|
||||
const result = await query<WorkOrderRow>(
|
||||
`
|
||||
@@ -321,7 +337,7 @@ export async function reenterConsignmentInternalMatching(
|
||||
if (!profile) return false
|
||||
// 关闭匹配:退回大厅后不再重新排队等内部库存。
|
||||
if (profile.config.matching.mode === 'off') return false
|
||||
const delayMinutes = Math.max(0, profile.config.internalMatch.delayMinutes)
|
||||
const delayMinutes = resolveInternalMatchDelayMinutes(profile.config.internalMatch)
|
||||
if (delayMinutes <= 0) return false
|
||||
const deadline = addMinutesIso(delayMinutes, now)
|
||||
const result = await query<WorkOrderRow>(
|
||||
@@ -516,6 +532,10 @@ async function applyWholeOrderMatch(
|
||||
if (!allocation) return { applied: false }
|
||||
|
||||
const config = profile?.config
|
||||
// 首次匹配时固化原报酬;二次匹配沿用同一原值,避免在已被改写的金额上继续扣服务费。
|
||||
const originalRewardFen = Number(
|
||||
current.consignment_original_reward_fen ?? current.reward_amount ?? 0,
|
||||
)
|
||||
const grossRewardFen =
|
||||
(unitPriceFen || Math.max(0, Number(current.reward_amount || 0))) * requiredQuantity
|
||||
const serviceFeeFen = config ? buildServiceFeeFen(config, grossRewardFen) : 0
|
||||
@@ -540,7 +560,7 @@ async function applyWholeOrderMatch(
|
||||
grossRewardFen,
|
||||
serviceFeeFen,
|
||||
netRewardFen,
|
||||
previousRewardAmountFen: Number(current.reward_amount || 0),
|
||||
previousRewardAmountFen: originalRewardFen,
|
||||
workerId: Number(inventory.worker_id),
|
||||
workerName: inventory.worker_name || '',
|
||||
matchedAt: now,
|
||||
@@ -569,6 +589,7 @@ async function applyWholeOrderMatch(
|
||||
deadline_at = $4,
|
||||
timeout_minutes = $5,
|
||||
timeout_policy = $6,
|
||||
consignment_original_reward_fen = COALESCE(consignment_original_reward_fen, reward_amount),
|
||||
reward_amount = $7,
|
||||
material_json = $8::jsonb,
|
||||
hall_visible_at = NULL,
|
||||
|
||||
@@ -23,6 +23,10 @@ import {
|
||||
releaseConsignmentForWorkOrder,
|
||||
} from './consignment-match-engine-service.js'
|
||||
import { fulfillWorkOrderWithConsignment } from './consignment-fulfillment-service.js'
|
||||
import {
|
||||
listWorkerConsignmentProfiles,
|
||||
resolveConsignmentProfileForCategory,
|
||||
} from './consignment-profile-service.js'
|
||||
import {
|
||||
computeConsignmentPrice,
|
||||
normalizeConsignmentSpecValues,
|
||||
@@ -713,6 +717,10 @@ async function main() {
|
||||
assert(row.status === 'in_progress', `status=${row.status}`)
|
||||
assert(Number(row.assigned_worker_id) === sellerId, `assigned=${row.assigned_worker_id}`)
|
||||
assert(Number(row.reward_amount) === 8550, `reward=${row.reward_amount}`)
|
||||
assert(
|
||||
Number(row.consignment_original_reward_fen) === 10000,
|
||||
`原始报酬=${row.consignment_original_reward_fen}`,
|
||||
)
|
||||
assert(
|
||||
Number(row.material_json.consignment.inventoryId) === Number(invARow.id),
|
||||
'material 未写库存',
|
||||
@@ -821,6 +829,15 @@ async function main() {
|
||||
[Number(exactWo.id)],
|
||||
)
|
||||
assert(allocation.rows[0]!.status === 'released', `allocation=${allocation.rows[0]!.status}`)
|
||||
const restored = await query<Record<string, any>>(
|
||||
'SELECT reward_amount, consignment_original_reward_fen FROM work_orders WHERE id = $1',
|
||||
[Number(exactWo.id)],
|
||||
)
|
||||
assert(
|
||||
Number(restored.rows[0]!.reward_amount) === 10000,
|
||||
`释放后报酬应恢复原值 10000,实际 ${restored.rows[0]!.reward_amount}`,
|
||||
)
|
||||
assert(restored.rows[0]!.consignment_original_reward_fen === null, '恢复后原始报酬字段必须清空')
|
||||
})
|
||||
await query('UPDATE consignment_inventories SET allow_matching = FALSE WHERE id = $1', [
|
||||
Number(invARow.id),
|
||||
@@ -1291,7 +1308,29 @@ async function main() {
|
||||
assert(event.rows.length === 1, '缺少超时事件')
|
||||
})
|
||||
|
||||
// ---------- 8.5 草稿隔离(P1-1)----------
|
||||
await check('配置草稿未发布前不得影响打手端/工单读取', async () => {
|
||||
await query(
|
||||
`UPDATE consignment_profiles SET config_json = jsonb_set(config_json, '{matching,exactOnly}', 'false'::jsonb) WHERE id = $1`,
|
||||
[profileId],
|
||||
)
|
||||
const workerProfiles = await listWorkerConsignmentProfiles()
|
||||
const mine = workerProfiles.find((item) => Number(item.profileId) === profileId)
|
||||
assert(mine, '打手端必须能读到该分类配置')
|
||||
assert(
|
||||
mine!.config.matching.exactOnly === true,
|
||||
`草稿 exactOnly=false 不应影响打手端,实际 ${mine!.config.matching.exactOnly}`,
|
||||
)
|
||||
const snapshot = await resolveConsignmentProfileForCategory(categoryId)
|
||||
assert(snapshot, '工单必须能解析到寄卖配置')
|
||||
assert(
|
||||
snapshot!.config.matching.exactOnly === true,
|
||||
`工单解析配置必须读已发布快照,实际 ${snapshot!.config.matching.exactOnly}`,
|
||||
)
|
||||
})
|
||||
|
||||
// ---------- 9. 死配置探测 ----------
|
||||
// P1-1:匹配只读已发布版本快照,改草稿不生效,必须连快照一起改(等价于重新发布)。
|
||||
await query(
|
||||
`UPDATE consignment_profiles SET config_json = jsonb_set(config_json, '{matching,exactOnly}', 'false'::jsonb) WHERE id = $1`,
|
||||
[profileId],
|
||||
@@ -1300,10 +1339,18 @@ async function main() {
|
||||
`UPDATE consignment_profiles SET config_json = jsonb_set(config_json, '{matching,preferLowReward}', 'false'::jsonb) WHERE id = $1`,
|
||||
[profileId],
|
||||
)
|
||||
await query(
|
||||
`UPDATE consignment_profile_versions SET config_json = jsonb_set(config_json, '{matching,exactOnly}', 'false'::jsonb) WHERE id = $1`,
|
||||
[versionId],
|
||||
)
|
||||
await query(
|
||||
`UPDATE consignment_profile_versions SET config_json = jsonb_set(config_json, '{matching,preferLowReward}', 'false'::jsonb) WHERE id = $1`,
|
||||
[versionId],
|
||||
)
|
||||
await check('配置开关 exactOnly=false 应允许自动匹配非精确库存', async () => {
|
||||
const profile = await query<Record<string, any>>(
|
||||
'SELECT config_json FROM consignment_profiles WHERE id = $1',
|
||||
[profileId],
|
||||
'SELECT config_json FROM consignment_profile_versions WHERE id = $1',
|
||||
[versionId],
|
||||
)
|
||||
assert(profile.rows[0]!.config_json.matching.exactOnly === false, 'exactOnly 未更新')
|
||||
// 此时唯一在库可匹配的只剩 3钻3璀(高配)库存,1钻1璀 订单只能靠它。
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
buildServiceFeeFen,
|
||||
normalizeConsignmentProfileConfig,
|
||||
resolvePriceDropSuggestion,
|
||||
resolvePublishedProfileConfig,
|
||||
} from './consignment-profile-service.js'
|
||||
|
||||
test('寄卖配置:缺省值必须可安全用于发布(保证金/服务费默认关闭)', () => {
|
||||
@@ -107,6 +108,23 @@ test('合并迁移 142 必须包含寄卖配置、库存数量/售价与分配
|
||||
}
|
||||
})
|
||||
|
||||
test('寄卖配置:打手端/工单/仓库只认已发布版本快照,草稿改了也不生效', () => {
|
||||
const published = resolvePublishedProfileConfig({
|
||||
// 草稿:后台刚保存但还没发布
|
||||
config_json: { tabTitle: '草稿标题', matching: { mode: 'auto' } },
|
||||
// 当前发布版本快照
|
||||
current_version_config_json: { tabTitle: '已发布标题', matching: { mode: 'off' } },
|
||||
})
|
||||
assert.equal(published.tabTitle, '已发布标题', '必须读已发布快照而不是草稿')
|
||||
assert.equal(published.matching.mode, 'off', '匹配开关不能被未发布草稿改写')
|
||||
|
||||
// 历史数据没有版本快照(或版本被删)时回退草稿,避免老配置突然不可用
|
||||
const legacy = resolvePublishedProfileConfig({ config_json: { tabTitle: '老配置' } })
|
||||
assert.equal(legacy.tabTitle, '老配置')
|
||||
const empty = resolvePublishedProfileConfig({})
|
||||
assert.equal(empty.tabTitle, '')
|
||||
})
|
||||
|
||||
test('打手端寄卖分类:苏尔南手枪固定排第一', async () => {
|
||||
const source = await readFile(
|
||||
new URL('./consignment-profile-service.ts', import.meta.url),
|
||||
|
||||
@@ -117,6 +117,8 @@ export type ConsignmentProfileRow = {
|
||||
category_name?: string
|
||||
category_parent_id?: number | null
|
||||
current_version_no?: number | null
|
||||
/** 当前发布版本快照(consignment_profile_versions.config_json) */
|
||||
current_version_config_json?: unknown
|
||||
}
|
||||
|
||||
export type ConsignmentProfileSnapshot = {
|
||||
@@ -342,12 +344,29 @@ const PROFILE_SELECT = `
|
||||
p.*,
|
||||
c.name AS category_name,
|
||||
c.parent_id AS category_parent_id,
|
||||
v.version_no AS current_version_no
|
||||
v.version_no AS current_version_no,
|
||||
v.config_json AS current_version_config_json
|
||||
FROM consignment_profiles p
|
||||
INNER JOIN work_categories c ON c.id = p.category_id
|
||||
LEFT JOIN consignment_profile_versions v ON v.id = p.current_version_id
|
||||
`
|
||||
|
||||
/**
|
||||
* 已发布口径:打手端分类、工单匹配、寄售仓库筛选项都必须读当前发布版本快照
|
||||
* (consignment_profile_versions.config_json),而 consignment_profiles.config_json 只是草稿。
|
||||
* 后台保存草稿只写 config_json,若读取端也读草稿,就会出现「未发布即生效」。
|
||||
* 历史数据没有版本快照时回退草稿,保证老配置不会突然不可用。
|
||||
*/
|
||||
export function resolvePublishedProfileConfig(row: {
|
||||
current_version_config_json?: unknown
|
||||
config_json?: unknown
|
||||
}): ConsignmentProfileConfig {
|
||||
const published = row.current_version_config_json
|
||||
return normalizeConsignmentProfileConfig(
|
||||
published === undefined || published === null ? row.config_json : published,
|
||||
)
|
||||
}
|
||||
|
||||
export async function listAdminConsignmentProfiles() {
|
||||
const [categories, profiles] = await Promise.all([
|
||||
listAllWorkCategories(),
|
||||
@@ -514,7 +533,7 @@ export async function listWorkerConsignmentProfiles() {
|
||||
`,
|
||||
)
|
||||
return result.rows.map((row) => {
|
||||
const config = normalizeConsignmentProfileConfig(row.config_json)
|
||||
const config = resolvePublishedProfileConfig(row)
|
||||
return {
|
||||
profileId: Number(row.id),
|
||||
categoryId: Number(row.category_id),
|
||||
@@ -659,7 +678,7 @@ export async function listAdminConsignmentInventoryFilterOptions(): Promise<{
|
||||
if (!effectiveProfile) continue
|
||||
if (!ownProfile && !inventoryCountByCategoryId.get(Number(category.id))) continue
|
||||
|
||||
const config = normalizeConsignmentProfileConfig(effectiveProfile.config_json)
|
||||
const config = resolvePublishedProfileConfig(effectiveProfile)
|
||||
items.push({
|
||||
categoryId: Number(category.id),
|
||||
categoryKey: category.category_key,
|
||||
@@ -710,7 +729,7 @@ export async function resolveConsignmentProfileForCategory(
|
||||
categoryName: row.category_name || '',
|
||||
versionId: Number(row.current_version_id),
|
||||
versionNo: Number(row.current_version_no || 1),
|
||||
config: normalizeConsignmentProfileConfig(row.config_json),
|
||||
config: resolvePublishedProfileConfig(row),
|
||||
}
|
||||
}
|
||||
return null
|
||||
@@ -733,7 +752,7 @@ export async function getPublishedConsignmentProfileById(
|
||||
categoryName: row.category_name || '',
|
||||
versionId: Number(row.current_version_id),
|
||||
versionNo: Number(row.current_version_no || 1),
|
||||
config: normalizeConsignmentProfileConfig(row.config_json),
|
||||
config: resolvePublishedProfileConfig(row),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -203,9 +203,7 @@ test('已确认加好友后解绑:现货作废下架,绝不回池被下一
|
||||
reason: '后台撤单',
|
||||
})
|
||||
|
||||
const invStatement = statements.find((item) =>
|
||||
item.sql.includes('UPDATE consignment_inventories'),
|
||||
)!
|
||||
const invStatement = statements.find((item) => item.sql.includes('已确认加好友,作废下架'))!
|
||||
// 作废下架而不是回库:status 必须落在 archived,并且顺手关掉匹配开关
|
||||
assert.ok(invStatement.sql.includes("status = 'archived'"))
|
||||
assert.ok(invStatement.sql.includes('allow_matching = FALSE'))
|
||||
@@ -441,7 +439,7 @@ test('工单解绑回库时同步摘掉工单上的寄售标记', async () => {
|
||||
reason: '后台撤单',
|
||||
})
|
||||
|
||||
const detach = statements.find((item) => item.sql.includes('UPDATE work_orders'))!
|
||||
const detach = statements.find((item) => item.sql.includes("material_json - 'consignment'"))!
|
||||
assert.ok(detach.sql.includes("material_json - 'consignment'"))
|
||||
assert.deepEqual(detach.values.slice(2), [100154, '9'])
|
||||
// 摘标记必须发生在现货改状态之后,否则工单会继续显示已失效的寄售履约
|
||||
@@ -2811,13 +2809,15 @@ test('确认加好友后的取消/退单/作废路径都要没收卖家发布押
|
||||
const hookCount = src.split('forfeitConsignmentPublishDepositByWorkOrderWithClient(').length - 1
|
||||
const forfeitFlags = src.split('isWorkOrderGiftDepositForfeitable(workOrder)').length - 1
|
||||
assert.ok(forfeitFlags > 0, `${file} 应保留押金不退判定`)
|
||||
assert.equal(hookCount, forfeitFlags, `${file} 每处"押金不退"判定都要同步没收卖家发布押金`)
|
||||
assert.equal(
|
||||
src.split('isWorkOrderGiftDepositForfeitable(workOrder)\n // 确认加好友之后该单押金不退')
|
||||
.length - 1,
|
||||
forfeitFlags,
|
||||
`${file} 没收动作必须紧跟判定、位于同一事务里`,
|
||||
)
|
||||
// 超时处置等路径会无条件没收(不依赖 isWorkOrderGiftDepositForfeitable),
|
||||
// 因此这里只要求“每处判定都有对应没收”,允许存在额外的无条件没收调用。
|
||||
assert.ok(hookCount >= forfeitFlags, `${file} 每处"押金不退"判定都要同步没收卖家发布押金`)
|
||||
const followedCount = (
|
||||
src.match(
|
||||
/isWorkOrderGiftDepositForfeitable\(workOrder\)\r?\n\s*\/\/ 确认加好友之后该单押金不退:/g,
|
||||
) || []
|
||||
).length
|
||||
assert.equal(followedCount, forfeitFlags, `${file} 没收动作必须紧跟判定、位于同一事务里`)
|
||||
}
|
||||
|
||||
const serviceSrc = await readFile.readFile(
|
||||
|
||||
Reference in New Issue
Block a user