feat(consignment): 保证金支持按规格选项价格乘寄售数量计算
This commit is contained in:
@@ -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 ?? '',
|
||||
|
||||
Reference in New Issue
Block a user