fix: 免审验收容错与成本中心归一回填超时修复
- worker:等级升级计数移出验收事务,瞬时错误重试一次,最终失败转人工待办且不再返回 500 - worker:待验收阶段修改验收资料时保留 reviewMode/evidenceStatus/evidenceDueAt - cost-center:迁移 107 新增人工待归一覆盖部分索引(生产实测候选聚合 19.6s → 391ms) - cost-center:归一人工商品/按工单回填商品/模板预览前端超时放宽到 300s - cost-center:批处理接口语句超时放宽到 60s(SET LOCAL,在线接口仍为 15s)
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import test from 'node:test'
|
||||
|
||||
/**
|
||||
* 2026-10-29 生产事故:VIP5 免审验收卡在「待验收」,原因是验收结算事务里
|
||||
* 顺带跑「已验收订单去重计数」做等级升级,库被回填压住时撞上 15s statement_timeout,
|
||||
* 整个验收事务回滚。升级必须挪到事务提交后,失败也不能影响验收。
|
||||
*/
|
||||
test('验收结算仓库不再在事务内调用等级升级(必须放到提交后)', async () => {
|
||||
const source = await readFile(new URL('./work-order-settlement-repo.ts', import.meta.url), 'utf8')
|
||||
|
||||
assert.equal(source.includes('maybeUpgradeWorkerLevelWithClient'), false)
|
||||
assert.ok(source.includes('upgradeWorkerLevelsAfterCommit(upgradeWorkerIds'))
|
||||
})
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { PoolClient } from 'pg'
|
||||
|
||||
import { withTransaction } from '../../db/client.js'
|
||||
import { logWarn } from '../../utils/logger.js'
|
||||
import {
|
||||
resolveSharingPartCancelPlan,
|
||||
resolveSharingPartCancelRefund,
|
||||
@@ -21,13 +22,34 @@ import {
|
||||
enqueueDepositUnfreezeWithClient,
|
||||
getOutstandingDepositAmountWithClient,
|
||||
} from './work-order-deposit-repo.js'
|
||||
import { maybeUpgradeWorkerLevelWithClient } from './worker-ranking-repo.js'
|
||||
import { maybeUpgradeWorkerLevel } from './worker-ranking-repo.js'
|
||||
import type { WorkOrderRow, WorkOrderShareRow } from './types.js'
|
||||
import {
|
||||
releaseConsignmentPublishDepositBySubmissionWithClient,
|
||||
settleConsignmentSupplierPayoutByWorkOrderWithClient,
|
||||
} from './consignment-deposit-repo.js'
|
||||
|
||||
/**
|
||||
* 验收事务提交后再跑自动升级。
|
||||
*
|
||||
* 升级要先算「已验收订单去重计数」,在大打手身上是秒级查询;放进验收事务里会把
|
||||
* 整笔验收拖到 statement_timeout(2026-10-29 生产事故:VIP5 免审验收因此 500)。
|
||||
* 这里失败只记日志,不影响已经提交的验收与结算。
|
||||
*/
|
||||
async function upgradeWorkerLevelsAfterCommit(workerIds: number[], now: string) {
|
||||
const uniqueWorkerIds = [...new Set(workerIds.map((id) => Number(id)).filter((id) => id > 0))]
|
||||
for (const workerId of uniqueWorkerIds) {
|
||||
try {
|
||||
await maybeUpgradeWorkerLevel(workerId, now)
|
||||
} catch (error) {
|
||||
logWarn('[worker/level-upgrade]', '验收后自动升级计算失败,已跳过', {
|
||||
workerId,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 拼单母单收尾:标记已验收,并补寄卖的发布押金释放与供货结算。
|
||||
*
|
||||
@@ -97,7 +119,11 @@ export async function acceptWorkOrderAndSettle(input: {
|
||||
order: WorkOrderRow | null
|
||||
failureReason: 'sharing_pending_submissions' | null
|
||||
}> {
|
||||
return withTransaction(async (client) => {
|
||||
const upgradeWorkerIds: number[] = []
|
||||
const result = await withTransaction<{
|
||||
order: WorkOrderRow | null
|
||||
failureReason: 'sharing_pending_submissions' | null
|
||||
}>(async (client) => {
|
||||
const currentResult = await client.query<WorkOrderRow>(
|
||||
`
|
||||
SELECT *
|
||||
@@ -339,7 +365,7 @@ export async function acceptWorkOrderAndSettle(input: {
|
||||
`,
|
||||
[input.now, Number(share.id)],
|
||||
)
|
||||
await maybeUpgradeWorkerLevelWithClient(client, shareWorkerId, input.now)
|
||||
upgradeWorkerIds.push(shareWorkerId)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -513,7 +539,7 @@ export async function acceptWorkOrderAndSettle(input: {
|
||||
})
|
||||
|
||||
if (workerId > 0) {
|
||||
await maybeUpgradeWorkerLevelWithClient(client, workerId, input.now)
|
||||
upgradeWorkerIds.push(workerId)
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -521,6 +547,8 @@ export async function acceptWorkOrderAndSettle(input: {
|
||||
failureReason: null,
|
||||
}
|
||||
})
|
||||
if (result.order) await upgradeWorkerLevelsAfterCommit(upgradeWorkerIds, input.now)
|
||||
return result
|
||||
}
|
||||
|
||||
/** 单独验收一个拼单份额,只结算该参与者,不影响其他份额。 */
|
||||
@@ -537,7 +565,12 @@ export async function acceptWorkOrderShareAndSettle(input: {
|
||||
share: WorkOrderShareRow | null
|
||||
failureReason: 'work_order_not_accepting' | 'share_not_submitted' | null
|
||||
}> {
|
||||
return withTransaction(async (client) => {
|
||||
const upgradeWorkerIds: number[] = []
|
||||
const result = await withTransaction<{
|
||||
order: WorkOrderRow | null
|
||||
share: WorkOrderShareRow | null
|
||||
failureReason: 'work_order_not_accepting' | 'share_not_submitted' | null
|
||||
}>(async (client) => {
|
||||
// in_progress 仅用于解围历史问题单处置产生的"份额已提交、母单却回到进行中"
|
||||
// 的卡死存量单;正常拼单流程母单只会是 open / pending_acceptance。
|
||||
const orderResult = await client.query<WorkOrderRow>(
|
||||
@@ -689,8 +722,8 @@ export async function acceptWorkOrderShareAndSettle(input: {
|
||||
[input.now, input.shareId],
|
||||
)
|
||||
if (workerId > 0) {
|
||||
// 先落库份额已验收状态,再按去重后的订单数计算等级进度。
|
||||
await maybeUpgradeWorkerLevelWithClient(client, workerId, input.now)
|
||||
// 等级升级要跑全量已验收订单计数,挪到事务提交后执行,避免拖垮验收事务。
|
||||
upgradeWorkerIds.push(workerId)
|
||||
}
|
||||
const progressResult = await client.query<{
|
||||
joined_quantity: number
|
||||
@@ -737,6 +770,8 @@ export async function acceptWorkOrderShareAndSettle(input: {
|
||||
failureReason: null,
|
||||
}
|
||||
})
|
||||
if (result.share) await upgradeWorkerLevelsAfterCommit(upgradeWorkerIds, input.now)
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { query } from '../../db/client.js'
|
||||
import { query, withTransaction } from '../../db/client.js'
|
||||
import type { WorkerUserRow } from './types.js'
|
||||
import type { PoolClient } from 'pg'
|
||||
|
||||
@@ -221,6 +221,21 @@ export async function maybeUpgradeWorkerLevelWithClient(
|
||||
return getWorkerUserByIdWithClient(client, workerId)
|
||||
}
|
||||
|
||||
/**
|
||||
* 验收后的自动升级:单独开事务执行。
|
||||
*
|
||||
* 升级要先做一次「已验收订单去重计数」,这在大打手身上是秒级查询;
|
||||
* 放进验收结算事务里会把验收本身拖到 15s statement_timeout 上(2026-10-29 生产事故),
|
||||
* 所以验收事务提交后再跑,失败了也不影响验收结果。
|
||||
*/
|
||||
export async function maybeUpgradeWorkerLevel(
|
||||
workerId: number,
|
||||
now: string,
|
||||
): Promise<WorkerUserRow | null> {
|
||||
if (!(Number(workerId) > 0)) return null
|
||||
return withTransaction((client) => maybeUpgradeWorkerLevelWithClient(client, workerId, now))
|
||||
}
|
||||
|
||||
export function shouldApplyAutomaticWorkerLevel(input: {
|
||||
currentLevelId: number | null
|
||||
currentThreshold: number
|
||||
|
||||
Reference in New Issue
Block a user