feat(consignment): 保证金支持按规格选项价格乘寄售数量计算

This commit is contained in:
2026-11-08 12:52:24 +08:00
parent ac2411ab6e
commit 01c2517f29
9 changed files with 436 additions and 17 deletions
@@ -55,6 +55,30 @@ test('保证金:固定金额或按数量计算', () => {
assert.equal(buildDepositAmountFen(perQuantity, 1), 500)
})
test('保证金:按数量(规格)取选项价格 × 寄售数量', () => {
const config = normalizeConsignmentProfileConfig({
deposit: {
enabled: true,
mode: 'per_spec',
specKey: 'diamondLevel',
specPrices: { '1': 100, '3': 300, '5': 500 },
},
})
assert.equal(config.deposit.mode, 'per_spec')
assert.equal(config.deposit.specKey, 'diamondLevel')
assert.deepEqual(config.deposit.specPrices, { '1': 100, '3': 300, '5': 500 })
assert.equal(buildDepositAmountFen(config, 5, { diamondLevel: '3' }), 1500)
// 没配价格的选项 / 字段缺失都按 0 计,不阻塞发布
assert.equal(buildDepositAmountFen(config, 2, { diamondLevel: '2' }), 0)
assert.equal(buildDepositAmountFen(config, 2, {}), 0)
const sanitized = normalizeConsignmentProfileConfig({
deposit: { mode: 'per_spec', specPrices: { '1': -5, '2': 'x', ' 3 ': 299.6 } },
})
assert.deepEqual(sanitized.deposit.specPrices, { '1': 0, '3': 300 })
})
test('服务费:按比例或固定金额,且不超过成交金额', () => {
const percent = normalizeConsignmentProfileConfig({
serviceFee: { enabled: true, mode: 'percent', percent: 10 },
@@ -25,9 +25,14 @@ export type {
export type ConsignmentDepositConfig = {
enabled: boolean
mode: 'fixed' | 'per_quantity'
/** fixed = 固定金额;per_quantity = 每件 × 寄售数量;per_spec = 规格选项价格 × 寄售数量 */
mode: 'fixed' | 'per_quantity' | 'per_spec'
fixedAmountFen: number
unitAmountFen: number
/** per_spec:取哪个规格字段的选项价格 */
specKey: string
/** per_spec:选项值 -> 每件保证金(分) */
specPrices: Record<string, number>
}
export type ConsignmentServiceFeeConfig = {
@@ -156,6 +161,8 @@ export const DEFAULT_CONSIGNMENT_PROFILE_CONFIG: ConsignmentProfileConfig = {
mode: 'fixed',
fixedAmountFen: 0,
unitAmountFen: 0,
specKey: '',
specPrices: {},
},
serviceFee: {
enabled: false,
@@ -233,6 +240,21 @@ function decimalOne(value: unknown, fallback: number, min: number, max: number):
return Math.min(max, Math.max(min, rounded))
}
/** 按数量(规格):选项值 → 每件保证金(分)。 */
function normalizeDepositSpecPrices(value: unknown): Record<string, number> {
const source = record(value)
const result: Record<string, number> = {}
for (const [rawKey, rawValue] of Object.entries(source)) {
const key = String(rawKey).trim().slice(0, 50)
if (!key) continue
const parsed = Number(rawValue)
if (!Number.isFinite(parsed)) continue
result[key] = Math.min(100_000_000, Math.max(0, Math.round(parsed)))
if (Object.keys(result).length >= 500) break
}
return result
}
export function normalizeConsignmentProfileConfig(value: unknown): ConsignmentProfileConfig {
const outer = record(value)
// 兼容直接存 config 的场景,以及库存/提报里 { config: {...}, profileId... } 的快照包装。
@@ -258,7 +280,12 @@ export function normalizeConsignmentProfileConfig(value: unknown): ConsignmentPr
: matching.mode === 'manual'
? 'manual'
: 'off'
const depositMode = deposit.mode === 'per_quantity' ? 'per_quantity' : 'fixed'
const depositMode =
deposit.mode === 'per_quantity'
? 'per_quantity'
: deposit.mode === 'per_spec'
? 'per_spec'
: 'fixed'
const serviceFeeMode = serviceFee.mode === 'fixed' ? 'fixed' : 'percent'
const priceDropMode = priceDrop.mode === 'fixed' ? 'fixed' : 'percent'
const timeoutPolicy: ConsignmentTimeoutConfig['policy'] =
@@ -298,6 +325,8 @@ export function normalizeConsignmentProfileConfig(value: unknown): ConsignmentPr
mode: depositMode,
fixedAmountFen: integer(deposit.fixedAmountFen, 0, 0, 100_000_000),
unitAmountFen: integer(deposit.unitAmountFen, 0, 0, 100_000_000),
specKey: text(deposit.specKey, 50),
specPrices: normalizeDepositSpecPrices(deposit.specPrices),
},
serviceFee: {
enabled: bool(serviceFee.enabled, false),
@@ -826,10 +855,21 @@ async function countWorkerActiveConsignmentQuantities(
)
}
export function buildDepositAmountFen(config: ConsignmentProfileConfig, quantity: number): number {
export function buildDepositAmountFen(
config: ConsignmentProfileConfig,
quantity: number,
specValues: Record<string, unknown> = {},
): number {
if (!config.deposit.enabled) return 0
const count = Math.max(1, Math.round(Number(quantity || 1)))
if (config.deposit.mode === 'per_quantity') {
return Math.max(0, Math.round(config.deposit.unitAmountFen)) * Math.max(1, quantity)
return Math.max(0, Math.round(config.deposit.unitAmountFen)) * count
}
if (config.deposit.mode === 'per_spec') {
const selected = String(specValues[config.deposit.specKey] ?? '').trim()
const unitFen = Math.max(0, Math.round(Number(config.deposit.specPrices[selected] ?? 0)))
// 未配置价格的选项按 0 计(与后台配置口径一致,不阻塞发布)
return unitFen * count
}
return Math.max(0, Math.round(config.deposit.fixedAmountFen))
}
@@ -466,7 +466,7 @@ export async function publishWorkerConsignment(workerId: number, payload: JsonOb
const priceCapFen = priceResolution.capFen
const depositAmountFen = profile?.config.deposit.enabled
? buildDepositAmountFen(profile.config, quantity)
? buildDepositAmountFen(profile.config, quantity, specValues)
: getConsignmentPublishDepositFen()
const sellerPhone = String(
intakeValues.sellerPhone ?? payload.sellerPhone ?? worker.phone ?? '',