@@ -7,6 +7,8 @@ import {
|
||||
calcOrderBoxCount,
|
||||
shouldHoldAutoCourierDispatch,
|
||||
validateMinPurchase,
|
||||
toBottleQuantity,
|
||||
toMinSaleQuantity,
|
||||
validateBusinessHours,
|
||||
formatBusinessHours,
|
||||
validateRedeemAmount,
|
||||
@@ -47,6 +49,30 @@ describe('validateMinPurchase', () => {
|
||||
expect(validateMinPurchase('ON_SITE_PICKUP', 1, 2, 6).ok).toBe(false);
|
||||
expect(validateMinPurchase('ON_SITE_PICKUP', 2, 2, 6).ok).toBe(true);
|
||||
});
|
||||
|
||||
it('box SKU: 1 box (6 bottles) satisfies local and cross mins', () => {
|
||||
const opts = { bottlesPerUnit: 6, saleUnit: 'BOX' as const };
|
||||
expect(validateMinPurchase('LOCAL', 1, 2, 6, opts).ok).toBe(true);
|
||||
expect(validateMinPurchase('CROSS_CITY', 1, 2, 6, opts).ok).toBe(true);
|
||||
expect(validateMinPurchase('ON_SITE_PICKUP', 1, 2, 6, opts).ok).toBe(true);
|
||||
});
|
||||
|
||||
it('box SKU message uses 箱', () => {
|
||||
const r = validateMinPurchase('LOCAL', 0, 2, 6, { bottlesPerUnit: 6, saleUnit: 'BOX' });
|
||||
expect(r.ok).toBe(false);
|
||||
expect(r.message).toContain('箱');
|
||||
});
|
||||
});
|
||||
|
||||
describe('toBottleQuantity / toMinSaleQuantity', () => {
|
||||
it('converts sale qty to bottles', () => {
|
||||
expect(toBottleQuantity(2, 1)).toBe(2);
|
||||
expect(toBottleQuantity(1, 6)).toBe(6);
|
||||
expect(toMinSaleQuantity(2, 1)).toBe(2);
|
||||
expect(toMinSaleQuantity(2, 6)).toBe(1);
|
||||
expect(toMinSaleQuantity(6, 6)).toBe(1);
|
||||
expect(toMinSaleQuantity(7, 6)).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateBusinessHours', () => {
|
||||
|
||||
@@ -7,27 +7,61 @@ export function calcBenefitAmount(product: ProductPricing): number {
|
||||
return product.benefitAmount ?? product.price;
|
||||
}
|
||||
|
||||
export type ProductSaleUnit = 'BOTTLE' | 'BOX';
|
||||
|
||||
/** 销售数量 → 瓶当量 */
|
||||
export function toBottleQuantity(quantity: number, bottlesPerUnit = 1): number {
|
||||
const qty = Math.floor(Number(quantity) || 0);
|
||||
const per = Math.floor(Number(bottlesPerUnit) || 0);
|
||||
if (qty <= 0 || per <= 0) return 0;
|
||||
return qty * per;
|
||||
}
|
||||
|
||||
/**
|
||||
* 城市起购瓶数 → 最少销售单位数量(向上取整)
|
||||
* 例:同城 2 瓶、整箱 6 瓶/箱 → minSaleQty = 1
|
||||
*/
|
||||
export function toMinSaleQuantity(minBottleQty: number, bottlesPerUnit = 1): number {
|
||||
const minBottles = Math.floor(Number(minBottleQty) || 0);
|
||||
const per = Math.floor(Number(bottlesPerUnit) || 0);
|
||||
if (minBottles <= 0) return 1;
|
||||
if (per <= 0) return minBottles;
|
||||
return Math.max(1, Math.ceil(minBottles / per));
|
||||
}
|
||||
|
||||
export function saleUnitLabel(saleUnit: ProductSaleUnit | string | null | undefined): string {
|
||||
return saleUnit === 'BOX' ? '箱' : '瓶';
|
||||
}
|
||||
|
||||
export function validateMinPurchase(
|
||||
deliveryType: 'LOCAL' | 'CROSS_CITY' | 'ON_SITE_PICKUP',
|
||||
quantity: number,
|
||||
localMinQty: number,
|
||||
crossMinQty: number,
|
||||
options?: { bottlesPerUnit?: number; saleUnit?: ProductSaleUnit },
|
||||
): { ok: boolean; message?: string } {
|
||||
const bottlesPerUnit = options?.bottlesPerUnit ?? 1;
|
||||
const saleUnit = options?.saleUnit ?? (bottlesPerUnit > 1 ? 'BOX' : 'BOTTLE');
|
||||
const bottleQty = toBottleQuantity(quantity, bottlesPerUnit);
|
||||
const unit = saleUnitLabel(saleUnit);
|
||||
|
||||
if (deliveryType === 'ON_SITE_PICKUP') {
|
||||
const min = localMinQty > 0 ? localMinQty : 2;
|
||||
if (quantity < min) {
|
||||
return { ok: false, message: `现场提货至少购买 ${min} 瓶` };
|
||||
const minBottles = localMinQty > 0 ? localMinQty : 2;
|
||||
if (bottleQty < minBottles) {
|
||||
const minSale = toMinSaleQuantity(minBottles, bottlesPerUnit);
|
||||
return { ok: false, message: `现场提货至少购买 ${minSale}${unit}` };
|
||||
}
|
||||
return { ok: true };
|
||||
}
|
||||
const min = deliveryType === 'LOCAL' ? localMinQty : crossMinQty;
|
||||
if (quantity < min) {
|
||||
const minBottles = deliveryType === 'LOCAL' ? localMinQty : crossMinQty;
|
||||
if (bottleQty < minBottles) {
|
||||
const minSale = toMinSaleQuantity(minBottles, bottlesPerUnit);
|
||||
return {
|
||||
ok: false,
|
||||
message:
|
||||
deliveryType === 'LOCAL'
|
||||
? `同城配送至少购买 ${min} 瓶`
|
||||
: `跨城配送至少购买 ${min} 瓶(1箱)`,
|
||||
? `同城配送至少购买 ${minSale}${unit}`
|
||||
: `跨城配送至少购买 ${minSale}${unit}`,
|
||||
};
|
||||
}
|
||||
return { ok: true };
|
||||
|
||||
Reference in New Issue
Block a user