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 o = calculatePricing( input(), 'RUB', '12.3', [rule], [{ ...commission, commission_rate: '0.000000' }], ).options[0]; expect(o.commission_rate).toBe('0.000000'); expect(o.fees.commission_cny).toBe('0.0000'); }); 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); }); }); describe('导入佣金的卢布分档边界', () => { const bands: CommissionRule[] = [ { ...commission, min_price: '0', max_price: '1500', min_inclusive: false, commission_rate: '0.12', }, { ...commission, min_price: '1500', max_price: '5000', min_inclusive: false, commission_rate: '0.14', }, { ...commission, min_price: '5000', max_price: null, min_inclusive: false, commission_rate: '0.205', }, ]; it.each([ ['1500', '0.12'], ['1500.01', '0.14'], ['5000', '0.14'], ['5000.01', '0.205'], ])('售价 %s 只命中对应佣金档', (price, rate) => { const i = input(); i.pricing.mode = 'fixed_sale'; i.pricing.selling_price = price; const result = calculatePricing( i, 'RUB', '12.3', [{ ...rule, max_merchandise_value: null }], bands, ); expect(result.options).toHaveLength(1); expect(Number(result.options[0].commission_rate)).toBe(Number(rate)); }); it('人民币售价换算卢布后匹配费率', () => { const i = input(); i.pricing.mode = 'fixed_sale'; i.pricing.selling_price = '150'; const result = calculatePricing( i, 'CNY', '10', [{ ...rule, max_merchandise_value: null }], bands, ); expect(result.options).toHaveLength(1); expect(Number(result.options[0].commission_rate)).toBe(0.12); i.pricing.selling_price = '150.01'; expect( Number( calculatePricing( i, 'CNY', '10', [{ ...rule, max_merchandise_value: null }], bands, ).options[0].commission_rate, ), ).toBe(0.14); }); });