feat: 足额免配送费

This commit is contained in:
Kroytz committed 2026-07-16 00:22:26 +08:00
1 parent be9601ff91
commit 2aa950e5f4
14 files changed
+72 -9

No files matched your search

@@ -0,0 +1,3 @@
ALTER TABLE `yshop_store_shop`
ADD COLUMN `free_delivery_amount` decimal(10,2) NOT NULL DEFAULT '0.00'
COMMENT '满额免配送费门槛,0表示不启用' AFTER `delivery_rules`;
+3 -2
View File
@@ -13702,6 +13702,7 @@ CREATE TABLE `yshop_store_shop` (
`min_price` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '起送价钱',
`delivery_price` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '配送价格',
`delivery_rules` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '配送规则JSON:[{minKm,maxKm,price}]',
`free_delivery_amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '满额免配送费门槛,0表示不启用',
`notice` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '公告',
`status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否营业:0=否,1=是',
`admin_id` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '管理员id',
@@ -13718,8 +13719,8 @@ CREATE TABLE `yshop_store_shop` (
-- Records of yshop_store_shop
-- ----------------------------
BEGIN;
INSERT INTO `yshop_store_shop` VALUES (2, '意向餐饮店', '18000000000', 'https://apidc.yixiang.co/admin-api/infra/file/4/get/44723283500f656cdd5906e13f2997cb292d17f516065ff4b3b38f9810bce4cb.jpg', 'https://apidc.yixiang.co/admin-api/infra/file/4/get/44723283500f656cdd5906e13f2997cb292d17f516065ff4b3b38f9810bce4cb.jpg', '源汇区人民东路', '河南省漯河市源汇区团结路50号-20', '2023-08-19 09:00:00', '2023-08-19 23:00:00', '114.04877877439665', '33.57093831826829', 10, 10.00, 20.00, '[{"minKm":0,"maxKm":3,"price":8},{"minKm":3,"maxKm":5,"price":12},{"minKm":5,"maxKm":10,"price":18}]', '新店开张', 1, '126', '1', '2023-08-06 11:48:43', '2023-09-14 17:30:59', '1', '1', b'0');
INSERT INTO `yshop_store_shop` VALUES (3, 'yshop', '18000000000', 'https://apidc.yixiang.co/admin-api/infra/file/4/get/44723283500f656cdd5906e13f2997cb292d17f516065ff4b3b38f9810bce4cb.jpg', 'https://apidc.yixiang.co/admin-api/infra/file/4/get/44723283500f656cdd5906e13f2997cb292d17f516065ff4b3b38f9810bce4cb.jpg', '67号', '北京市东城区', '2023-08-06 13:35:02', '2023-08-06 15:35:02', '116.40403593222032', '39.91643892263258', 0, 1.00, 2.00, NULL, '2222', 1, '100', '2', '2023-08-06 13:14:53', '2024-03-26 15:42:20', '1', '1', b'0');
INSERT INTO `yshop_store_shop` VALUES (2, '意向餐饮店', '18000000000', 'https://apidc.yixiang.co/admin-api/infra/file/4/get/44723283500f656cdd5906e13f2997cb292d17f516065ff4b3b38f9810bce4cb.jpg', 'https://apidc.yixiang.co/admin-api/infra/file/4/get/44723283500f656cdd5906e13f2997cb292d17f516065ff4b3b38f9810bce4cb.jpg', '源汇区人民东路', '河南省漯河市源汇区团结路50号-20', '2023-08-19 09:00:00', '2023-08-19 23:00:00', '114.04877877439665', '33.57093831826829', 10, 10.00, 20.00, '[{"minKm":0,"maxKm":3,"price":8},{"minKm":3,"maxKm":5,"price":12},{"minKm":5,"maxKm":10,"price":18}]', 0.00, '新店开张', 1, '126', '1', '2023-08-06 11:48:43', '2023-09-14 17:30:59', '1', '1', b'0');
INSERT INTO `yshop_store_shop` VALUES (3, 'yshop', '18000000000', 'https://apidc.yixiang.co/admin-api/infra/file/4/get/44723283500f656cdd5906e13f2997cb292d17f516065ff4b3b38f9810bce4cb.jpg', 'https://apidc.yixiang.co/admin-api/infra/file/4/get/44723283500f656cdd5906e13f2997cb292d17f516065ff4b3b38f9810bce4cb.jpg', '67号', '北京市东城区', '2023-08-06 13:35:02', '2023-08-06 15:35:02', '116.40403593222032', '39.91643892263258', 0, 1.00, 2.00, NULL, 0.00, '2222', 1, '100', '2', '2023-08-06 13:14:53', '2024-03-26 15:42:20', '1', '1', b'0');
COMMIT;
-- ----------------------------
@@ -234,6 +234,9 @@ public class AppStoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper,Store
&& sumPrice.compareTo(storeShopDO.getMinPrice()) < 0) {
throw exception(STORE_ORDER_MIN_PRICE_NOT_MET);
}
if (OrderLogEnum.ORDER_TAKE_OUT.getValue().equals(param.getOrderType())) {
postagePrice = DeliveryCalculator.applyFreeDelivery(storeShopDO, sumPrice, postagePrice);
}
//计算优惠券价格
if(StrUtil.isNotBlank(param.getCouponId())){
@@ -67,6 +67,10 @@ public class StoreShopBaseVO {
@Schema(description = "配送规则(按距离区间阶梯计费)", example = "[{\"minKm\":1,\"maxKm\":3,\"price\":8}]")
private List<DeliveryRule> deliveryRules;
@Schema(description = "满额免配送费门槛,0或空表示不启用", example = "50")
@DecimalMin(value = "0", message = "免配送费门槛不能小于0")
private BigDecimal freeDeliveryAmount;
@Schema(description = "公告", required = true)
@NotNull(message = "公告不能为空")
private String notice;
@@ -24,6 +24,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.math.BigDecimal;
import java.util.List;
import static co.yixiang.yshop.framework.common.pojo.CommonResult.success;
@@ -80,8 +81,9 @@ public class AppStoreController {
@Operation(summary = "预估配送费")
public CommonResult<DeliveryFeeRespVO> getDeliveryFee(@RequestParam("shopId") Long shopId,
@RequestParam("lng") double lng,
@RequestParam("lat") double lat) {
return success(appStoreShopService.getDeliveryFee(shopId, lng, lat));
@RequestParam("lat") double lat,
@RequestParam(value = "amount", required = false) BigDecimal amount) {
return success(appStoreShopService.getDeliveryFee(shopId, lng, lat, amount));
}
@@ -57,6 +57,9 @@ public class AppStoreShopVO {
@Schema(description = "配送规则JSON文本", example = "[{\"minKm\":1,\"maxKm\":3,\"price\":8}]")
private String deliveryRules;
@Schema(description = "满额免配送费门槛,0或空表示不启用", example = "50")
private BigDecimal freeDeliveryAmount;
@Schema(description = "公告", required = true)
private String notice;
@@ -84,6 +84,10 @@ public class StoreShopDO extends BaseDO {
*/
@TableField(typeHandler = DeliveryRuleListTypeHandler.class)
private List<DeliveryRule> deliveryRules;
/**
* 满额免配送费门槛,0或空表示不启用
*/
private BigDecimal freeDeliveryAmount;
/**
* 公告
*/
@@ -5,6 +5,7 @@ import co.yixiang.yshop.module.store.controller.app.storeshop.vo.DeliveryFeeResp
import co.yixiang.yshop.module.store.dal.dataobject.storeshop.StoreShopDO;
import com.baomidou.mybatisplus.extension.service.IService;
import java.math.BigDecimal;
import java.util.List;
/**
@@ -30,6 +31,6 @@ public interface AppStoreShopService extends IService<StoreShopDO> {
* @param lat 收货地址纬度
* @return 配送费及是否超出配送范围
*/
DeliveryFeeRespVO getDeliveryFee(Long shopId, double lng, double lat);
DeliveryFeeRespVO getDeliveryFee(Long shopId, double lng, double lat, BigDecimal amount);
}
@@ -38,7 +38,7 @@ public class AppStoreShopServiceImpl extends ServiceImpl<StoreShopMapper, StoreS
}
@Override
public DeliveryFeeRespVO getDeliveryFee(Long shopId, double lng, double lat) {
public DeliveryFeeRespVO getDeliveryFee(Long shopId, double lng, double lat, BigDecimal amount) {
StoreShopDO shop = getById(shopId);
DeliveryFeeRespVO vo = new DeliveryFeeRespVO();
vo.setOutOfRange(true);
@@ -49,7 +49,8 @@ public class AppStoreShopServiceImpl extends ServiceImpl<StoreShopMapper, StoreS
vo.setMaxDistanceKm(shop.getDistance());
double distanceKm = DeliveryCalculator.calcDistanceKm(
shop.getLng(), shop.getLat(), String.valueOf(lng), String.valueOf(lat));
BigDecimal price = DeliveryCalculator.calcDeliveryPrice(shop, distanceKm);
BigDecimal price = DeliveryCalculator.applyFreeDelivery(shop, amount,
DeliveryCalculator.calcDeliveryPrice(shop, distanceKm));
vo.setDistanceKm(distanceKm);
if (price != null) {
vo.setOutOfRange(false);
@@ -83,4 +83,12 @@ public class DeliveryCalculator {
return shop.getDeliveryPrice();
}
public static BigDecimal applyFreeDelivery(StoreShopDO shop, BigDecimal orderAmount,
BigDecimal deliveryPrice) {
BigDecimal threshold = shop.getFreeDeliveryAmount();
return deliveryPrice != null && orderAmount != null && threshold != null
&& threshold.signum() > 0 && orderAmount.compareTo(threshold) >= 0
? BigDecimal.ZERO : deliveryPrice;
}
}
@@ -0,0 +1,25 @@
package co.yixiang.yshop.module.store.service.storeshop;
import co.yixiang.yshop.module.store.dal.dataobject.storeshop.StoreShopDO;
import org.junit.jupiter.api.Test;
import java.math.BigDecimal;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
class DeliveryCalculatorTest {
@Test
void applyFreeDeliveryUsesConfiguredThreshold() {
StoreShopDO shop = StoreShopDO.builder()
.freeDeliveryAmount(new BigDecimal("30"))
.build();
BigDecimal fee = new BigDecimal("8");
assertEquals(fee, DeliveryCalculator.applyFreeDelivery(shop, new BigDecimal("29.99"), fee));
assertEquals(BigDecimal.ZERO,
DeliveryCalculator.applyFreeDelivery(shop, new BigDecimal("30"), fee));
assertNull(DeliveryCalculator.applyFreeDelivery(shop, new BigDecimal("30"), null));
}
}
@@ -163,7 +163,7 @@ const amount = computed(() =>{
})
// 选地址/切换外卖后预估配送费(基于收货地址距离匹配配送规则)
watch([address, store, orderType], async () => {
watch([address, store, orderType, total], async () => {
if (orderType.value !== 'takeout' || !store.value.id) {
deliveryFee.value = 0
outOfRange.value = false
@@ -183,7 +183,8 @@ watch([address, store, orderType], async () => {
const res = await getDeliveryFee({
shopId: store.value.id,
lng: address.value.longitude,
lat: address.value.latitude
lat: address.value.latitude,
amount: total.value.toFixed(2)
})
outOfRange.value = !!res.outOfRange
deliveryFee.value = parseFloat(res.deliveryPrice) || 0
@@ -20,6 +20,7 @@ export interface ShopVO {
minPrice: number
deliveryPrice: number
deliveryRules: DeliveryRule[]
freeDeliveryAmount?: number
notice: string
status: boolean
adminId: string
@@ -49,6 +49,10 @@
<el-form-item label="默认配送费" prop="deliveryPrice">
<el-input v-model="formData.deliveryPrice" placeholder="未配置规则或无匹配时的兜底配送费" />
</el-form-item>
<el-form-item label="满额免配送费">
<el-input-number v-model="formData.freeDeliveryAmount" :min="0" :precision="2" />
<span class="delivery-rule-tip">元,0或不填表示不启用</span>
</el-form-item>
<el-form-item label="配送规则">
<div style="width: 100%;">
<div v-for="(rule, index) in formData.deliveryRules" :key="index" class="delivery-rule-row">
@@ -127,6 +131,7 @@ const formData = ref<any>({
minPrice: undefined,
deliveryPrice: undefined,
deliveryRules: [],
freeDeliveryAmount: undefined,
notice: undefined,
status: undefined,
adminId: undefined,
@@ -278,6 +283,7 @@ const resetForm = () => {
minPrice: undefined,
deliveryPrice: undefined,
deliveryRules: [],
freeDeliveryAmount: undefined,
notice: undefined,
status: 1,
adminId: undefined,