244 lines
7.9 KiB
TypeScript
244 lines
7.9 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import {
|
|
calculatePricing,
|
|
type LogisticsRule,
|
|
type CommissionRule,
|
|
} from '../src/pricing/pricing-engine.js';
|
|
import { pricingPreviewRequestSchema, pricingInputKey } from '@atlas/contracts';
|
|
const id = 'ad877f74-36d0-4ef9-a711-cd904b37912f';
|
|
const input = () =>
|
|
pricingPreviewRequestSchema.parse({
|
|
store_id: id,
|
|
platform_product_category_id: id,
|
|
fulfillment: 'rfbs',
|
|
purchase_cost_cny: '28',
|
|
package: {
|
|
shipping_weight_g: '380',
|
|
length_mm: '220',
|
|
width_mm: '160',
|
|
height_mm: '90',
|
|
status: 'confirmed',
|
|
},
|
|
pricing: {
|
|
rate_card_id: id,
|
|
transport_method_ids: [id],
|
|
default_transport_method_id: id,
|
|
domestic_shipping_and_label_fee_cny: '3',
|
|
ad_rate: '0.03',
|
|
last_mile_rate: '0.02',
|
|
withdrawal_rate: '0.01',
|
|
other_rate: '0.02',
|
|
desired_markup_rate: '0.5',
|
|
list_price_discount_rate: '0.2',
|
|
},
|
|
});
|
|
const rule: LogisticsRule = {
|
|
id,
|
|
product_type_id: id,
|
|
transport_method_id: id,
|
|
transport_name: '标准',
|
|
product_type_name: 'ExtraSmall',
|
|
fixed_fee_cny: '3.12',
|
|
per_billable_kg_fee_cny: '36.4',
|
|
min_charge_cny: null,
|
|
surcharge_rules: [],
|
|
merchandise_currency_code: 'CNY',
|
|
min_merchandise_value: null,
|
|
max_merchandise_value: '135',
|
|
min_value_inclusive: true,
|
|
max_value_inclusive: true,
|
|
min_actual_weight_kg: '0.001',
|
|
min_actual_weight_inclusive: true,
|
|
max_actual_weight_kg: '0.5',
|
|
max_sum_dimensions_cm: '90',
|
|
max_longest_side_cm: '60',
|
|
max_second_longest_side_cm: null,
|
|
billing_basis: 'actual_only',
|
|
volume_divisor: null,
|
|
volume_condition_sum_gt_cm: null,
|
|
volume_round_scale: null,
|
|
billing_round_scale: null,
|
|
max_volume_weight_kg: null,
|
|
volume_limit_round_scale: null,
|
|
};
|
|
const commission: CommissionRule = {
|
|
id,
|
|
currency_code: 'RUB',
|
|
min_price: null,
|
|
max_price: null,
|
|
min_inclusive: true,
|
|
max_inclusive: true,
|
|
commission_rate: '0.12',
|
|
version: 'test',
|
|
};
|
|
describe('十进制定价引擎', () => {
|
|
it('目标加价与明细一致,CNY / RUB 都按最终币种舍入', () => {
|
|
const c = calculatePricing(input(), 'CNY', '12.3', [rule], [commission])
|
|
.options[0];
|
|
expect(c.selling_price).toBe('77.44');
|
|
expect(c.profit_cny).toBe('14.00');
|
|
expect(c.list_price).toBe('96.80');
|
|
expect(c.fees.logistics_cny).toBe('16.9520');
|
|
const rub = calculatePricing(
|
|
input(),
|
|
'RUB',
|
|
'12.3',
|
|
[rule],
|
|
[commission],
|
|
).options[0];
|
|
expect(rub.selling_price).toBe('952.52');
|
|
expect(rub.can_confirm).toBe(true);
|
|
});
|
|
it('固定售价保持原价并识别亏损', () => {
|
|
const i = input();
|
|
i.pricing.mode = 'fixed_sale';
|
|
i.pricing.selling_price = '10';
|
|
const o = calculatePricing(i, 'CNY', '12.3', [rule], [commission])
|
|
.options[0];
|
|
expect(o.selling_price).toBe('10.00');
|
|
expect(o.can_confirm).toBe(false);
|
|
expect(Number(o.profit_cny)).toBeLessThan(0);
|
|
});
|
|
it('实际重、尺寸和开区间边界精确生效', () => {
|
|
const i = input();
|
|
i.package.shipping_weight_g = '500';
|
|
expect(
|
|
calculatePricing(i, 'CNY', '12.3', [rule], [commission]).options,
|
|
).toHaveLength(1);
|
|
i.package.shipping_weight_g = '500.001';
|
|
expect(
|
|
calculatePricing(i, 'CNY', '12.3', [rule], [commission]).options,
|
|
).toHaveLength(0);
|
|
i.package.shipping_weight_g = '380';
|
|
expect(
|
|
calculatePricing(
|
|
i,
|
|
'CNY',
|
|
'12.3',
|
|
[
|
|
{
|
|
...rule,
|
|
min_actual_weight_kg: '0.380',
|
|
min_actual_weight_inclusive: false,
|
|
},
|
|
],
|
|
[commission],
|
|
).options,
|
|
).toHaveLength(0);
|
|
});
|
|
it('先舍入售价再检查货值及佣金档,不跨档蒙混', () => {
|
|
const i = input();
|
|
expect(
|
|
calculatePricing(
|
|
i,
|
|
'CNY',
|
|
'12.3',
|
|
[{ ...rule, max_merchandise_value: '77.439' }],
|
|
[commission],
|
|
).options,
|
|
).toHaveLength(0);
|
|
expect(
|
|
calculatePricing(
|
|
i,
|
|
'CNY',
|
|
'12.3',
|
|
[rule],
|
|
[{ ...commission, max_price: '952.51' }],
|
|
).options,
|
|
).toHaveLength(0);
|
|
});
|
|
it('总费率达到 100% 拒绝,未知附加费不能漏算', () => {
|
|
const i = input();
|
|
i.pricing.ad_rate = '0.9';
|
|
expect(
|
|
calculatePricing(i, 'CNY', '12.3', [rule], [commission]).options,
|
|
).toHaveLength(0);
|
|
expect(
|
|
calculatePricing(
|
|
input(),
|
|
'CNY',
|
|
'12.3',
|
|
[{ ...rule, surcharge_rules: [{ unknown: true }] }],
|
|
[commission],
|
|
).options,
|
|
).toHaveLength(0);
|
|
});
|
|
it('体积重按指定位数进位并检查进位后的上限', () => {
|
|
const i = input();
|
|
i.package = {
|
|
shipping_weight_g: '5000',
|
|
length_mm: '500',
|
|
width_mm: '400',
|
|
height_mm: '301',
|
|
status: 'confirmed',
|
|
};
|
|
const volume = {
|
|
...rule,
|
|
max_actual_weight_kg: '30',
|
|
max_sum_dimensions_cm: '310',
|
|
billing_basis: 'max_actual_volume',
|
|
volume_divisor: '12000',
|
|
volume_round_scale: 3,
|
|
max_volume_weight_kg: '6',
|
|
volume_limit_round_scale: 0,
|
|
};
|
|
expect(
|
|
calculatePricing(
|
|
i,
|
|
'CNY',
|
|
'12.3',
|
|
[{ ...volume, max_merchandise_value: null }],
|
|
[commission],
|
|
).options[0].billable_weight_kg,
|
|
).toBe('5.017');
|
|
expect(
|
|
calculatePricing(
|
|
i,
|
|
'CNY',
|
|
'12.3',
|
|
[{ ...volume, max_volume_weight_kg: '5' }],
|
|
[commission],
|
|
).options,
|
|
).toHaveLength(0);
|
|
});
|
|
it('默认运输失效时不偷偷选择其他方式', () => {
|
|
const i = input();
|
|
i.pricing.default_transport_method_id =
|
|
'8d6df67c-1fa3-45b9-9766-d4f5c813b903';
|
|
expect(
|
|
calculatePricing(i, 'CNY', '12.3', [rule], [commission])
|
|
.recommended_option_id,
|
|
).toBeNull();
|
|
});
|
|
it('格式化金额与 JSONB 字段重排不会使同一输入失效', () => {
|
|
const original = input();
|
|
const reordered = JSON.parse(
|
|
JSON.stringify(original, (_key, value) =>
|
|
value && typeof value === 'object' && !Array.isArray(value)
|
|
? Object.fromEntries(Object.entries(value).reverse())
|
|
: value,
|
|
),
|
|
);
|
|
reordered.purchase_cost_cny = '28.00';
|
|
reordered.package.shipping_weight_g = '380.000';
|
|
reordered.pricing.ad_rate = '0.030000';
|
|
expect(pricingInputKey(original)).toBe(pricingInputKey(reordered));
|
|
reordered.package.length_mm = '221';
|
|
expect(pricingInputKey(original)).not.toBe(pricingInputKey(reordered));
|
|
});
|
|
it('显示利润舍入为零时,真实微亏仍禁止确认', () => {
|
|
const i = input();
|
|
i.pricing.mode = 'fixed_sale';
|
|
i.pricing.selling_price = '59.94';
|
|
const option = calculatePricing(
|
|
i,
|
|
'CNY',
|
|
'12.3',
|
|
[{ ...rule, fixed_fee_cny: '3.121' }],
|
|
[commission],
|
|
).options[0];
|
|
expect(option.profit_cny).toBe('-0.00');
|
|
expect(option.can_confirm).toBe(false);
|
|
});
|
|
});
|