fix(cost-center): 禁止追溯改价,改价只能当前或未来生效

- 后端统一校验:retroactive 请求直接拒绝,effectiveFrom 早于当前时间拒绝
- 去掉「追溯至历史订单」入口,改价只影响之后的新订单
- 历史订单成本与成交价由快照固化,不再受改价影响
This commit is contained in:
yml2213
2026-11-01 14:53:25 +08:00
parent fed459c7aa
commit ceb786f32e
6 changed files with 46 additions and 47 deletions
@@ -1,6 +1,7 @@
import { query } from '../../db/client.js'
import type { AdminSession } from '../admin/admin-auth-service.js'
import type { JsonObject } from '../../types/json.js'
import { resolvePriceEffectiveFrom } from './cost-center-normalizers.js'
import { createHttpError } from '../../utils/http.js'
import { formatShanghaiDate, shiftDate } from '../../utils/finance-date-range.js'
import { nowIso } from '../../utils/time.js'
@@ -304,11 +305,7 @@ export async function setAdminAffiliateProductPrice(
}
const now = nowIso()
let effectiveFrom = payload.effectiveFrom ? String(payload.effectiveFrom) : now
if (payload.retroactive === true || payload.retroactive === 'true') {
const today = formatShanghaiDate(new Date())
effectiveFrom = shiftDate(today, -30) + 'T00:00:00+08:00'
}
const effectiveFrom = resolvePriceEffectiveFrom(payload)
const changeReason = String(payload.changeReason || '配置自动发货采购成本').trim()
const existingResult = await query<{ id: number; amount_fen: number; effective_from: string }>(
@@ -246,3 +246,32 @@ export function requiredRow<T>(row: T | undefined | null, message: string): T {
}
return row
}
/**
* 成本改价的生效时间:只允许当前或未来,不允许追溯。
* 历史订单的成本与成交价已固化在快照里,改价不得回写过去。
*/
export function resolvePriceEffectiveFrom(payload: Record<string, unknown>): string {
if (payload.retroactive === true || payload.retroactive === 'true') {
throw createHttpError('成本改价不支持追溯历史订单;历史订单成本已固化', {
statusCode: 400,
errorCode: 'cost_retroactive_not_allowed',
})
}
const raw = payload.effectiveFrom ?? payload.effective_from
if (!raw) return new Date().toISOString()
const time = Date.parse(String(raw))
if (!Number.isFinite(time)) {
throw createHttpError('生效时间格式无效', {
statusCode: 400,
errorCode: 'cost_effective_from_invalid',
})
}
if (time < Date.now() - 60_000) {
throw createHttpError('生效时间不能早于当前时间;历史订单成本已固化', {
statusCode: 400,
errorCode: 'cost_effective_from_in_past',
})
}
return new Date(time).toISOString()
}
@@ -16,6 +16,7 @@ import { nowIso } from '../../utils/time.js'
import { normalizeMatchText, normalizeSkuSeriesEquivalence } from '../product-match/normalize.js'
import { resolveSkuNameQuantity } from '../product-match/pricing.js'
import { setAdminAffiliateProductPrice } from './affiliate-product-service.js'
import { resolvePriceEffectiveFrom } from './cost-center-normalizers.js'
import { ensureVoucherRuleForSku } from '../fulfillment/channel-assign-service.js'
import { syncAdminWorkProductRulePrice } from '../worker-platform/admin-worker-catalog-service.js'
import {
@@ -1367,11 +1368,7 @@ export async function setAdminProductPrice(
}
const now = nowIso()
let effectiveFrom = payload.effectiveFrom ? String(payload.effectiveFrom) : now
if (payload.retroactive === true || payload.retroactive === 'true') {
const today = formatShanghaiDate(new Date())
effectiveFrom = shiftDate(today, -30) + 'T00:00:00+08:00'
}
const effectiveFrom = resolvePriceEffectiveFrom(payload)
const changeReason = String(payload.changeReason || '在商品与成本中心统一调整价格').trim()
// 2. 如果是接单模板商品 (tpl:)