Browse Source

维护订单服务费逻辑 开发服务费查询接口

lutong 3 tuần trước cách đây
mục cha
commit
98f5904bd3
20 tập tin đã thay đổi với 390 bổ sung125 xóa
  1. 19 0
      alien-dining/src/main/java/shop/alien/dining/controller/DiningController.java
  2. 4 3
      alien-dining/src/main/java/shop/alien/dining/controller/StoreOrderController.java
  3. 13 0
      alien-dining/src/main/java/shop/alien/dining/service/DiningService.java
  4. 2 1
      alien-dining/src/main/java/shop/alien/dining/service/StoreOrderService.java
  5. 35 15
      alien-dining/src/main/java/shop/alien/dining/service/impl/DiningServiceImpl.java
  6. 44 101
      alien-dining/src/main/java/shop/alien/dining/service/impl/StoreOrderServiceImpl.java
  7. 149 0
      alien-dining/src/main/java/shop/alien/dining/support/DiningOrderServiceFeeCalculator.java
  8. 4 0
      alien-entity/src/main/java/shop/alien/entity/store/StoreOrder.java
  9. 4 4
      alien-entity/src/main/java/shop/alien/entity/store/dto/CreateOrderDTO.java
  10. 7 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/UpdateOrderCouponDTO.java
  11. 23 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/DiningServiceFeeEstimateVO.java
  12. 27 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/DiningServiceFeeMatchedItemVO.java
  13. 4 1
      alien-entity/src/main/java/shop/alien/entity/store/vo/OrderConfirmVO.java
  14. 3 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/OrderDetailWithChangeLogVO.java
  15. 3 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/OrderInfoVO.java
  16. 3 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/OrderSettlementVO.java
  17. 3 0
      alien-entity/src/main/resources/db/migration/store_order_service_fee.sql
  18. 17 0
      alien-store/src/main/java/shop/alien/store/controller/DiningServiceController.java
  19. 16 0
      alien-store/src/main/java/shop/alien/store/controller/dining/StoreDiningPathProxyController.java
  20. 10 0
      alien-store/src/main/java/shop/alien/store/feign/DiningServiceFeign.java

+ 19 - 0
alien-dining/src/main/java/shop/alien/dining/controller/DiningController.java

@@ -15,6 +15,7 @@ import shop.alien.dining.service.DiningWalkInReservationService;
 import shop.alien.dining.util.TokenUtil;
 
 import javax.validation.Valid;
+import java.math.BigDecimal;
 import java.util.List;
 
 /**
@@ -89,6 +90,24 @@ public class DiningController {
         }
     }
 
+    @ApiOperation(value = "预估服务费(当前时刻+本店本桌)", notes = "免登录。按服务器当前时间(上海时区)匹配 store_service_fee_rule;tableId 为 store_table.id,须与 storeId 一致。可选 dinerCount(按人收费默认1)、goodsSubtotal(消费额比例费;不传按0)。")
+    @GetMapping("/service-fee/estimate")
+    public R<DiningServiceFeeEstimateVO> estimateServiceFee(
+            @ApiParam(value = "门店ID", required = true) @RequestParam Integer storeId,
+            @ApiParam(value = "桌号ID(store_table.id)", required = true) @RequestParam Integer tableId,
+            @ApiParam(value = "就餐人数,按人收费规则用") @RequestParam(required = false) Integer dinerCount,
+            @ApiParam(value = "菜品成交价小计(元),消费额比例收费用") @RequestParam(required = false) BigDecimal goodsSubtotal) {
+        log.info("DiningController.estimateServiceFee?storeId={}, tableId={}, dinerCount={}, goodsSubtotal={}",
+                storeId, tableId, dinerCount, goodsSubtotal);
+        try {
+            DiningServiceFeeEstimateVO vo = diningService.estimateServiceFee(storeId, tableId, dinerCount, goodsSubtotal);
+            return R.data(vo);
+        } catch (Exception e) {
+            log.error("预估服务费失败: {}", e.getMessage(), e);
+            return R.fail("预估服务费失败: " + e.getMessage());
+        }
+    }
+
     @ApiOperation(value = "获取点餐页面信息", notes = "首客选桌时传就餐人数,餐桌置为就餐中并保存人数;后续用户选同一桌可不传就餐人数,将使用表中已保存人数")
     @GetMapping("/page-info")
     public R<DiningPageInfoVO> getDiningPageInfo(

+ 4 - 3
alien-dining/src/main/java/shop/alien/dining/controller/StoreOrderController.java

@@ -229,7 +229,8 @@ public class StoreOrderController {
         }
     }
 
-    @ApiOperation(value = "创建订单(下单)", notes = "从购物车创建订单,不立即支付。绑定的预约须为「已到店(2)」且当前桌在其占用桌位中;userReservationId 可选,不传则按 tableId 解析本桌当日已到店预约(不必与预约人为同一登录账号,便于同伴下单)。备注创建/更新传入,加餐更新时覆盖。")
+    @ApiOperation(value = "创建订单(下单)", notes = "从购物车创建订单,不立即支付。绑定的预约须为「已到店(2)」且当前桌在其占用桌位中;userReservationId 可选,不传则按 tableId 解析本桌当日已到店预约(不必与预约人为同一登录账号,便于同伴下单)。备注创建/更新传入,加餐更新时覆盖。"
+            + "订单总金额、餐具费、服务费、优惠金额、实付金额均由前端传入,后端不做金额校验。")
     @PostMapping("/create")
     public R<shop.alien.entity.store.vo.OrderSuccessVO> createOrder(@Valid @RequestBody CreateOrderDTO dto) {
         log.info("StoreOrderController.createOrder?dto={}", dto);
@@ -521,7 +522,7 @@ public class StoreOrderController {
         }
     }
 
-    @ApiOperation(value = "更新订单优惠券", notes = "重新选择优惠券")
+    @ApiOperation(value = "更新订单优惠券", notes = "重新选择优惠券。discountAmount、payAmount 由前端按新券重算后传入;不传则保留订单原优惠与实付。")
     @PostMapping("/update-coupon")
     public R<StoreOrder> updateOrderCoupon(@Valid @RequestBody shop.alien.entity.store.dto.UpdateOrderCouponDTO dto) {
         log.info("StoreOrderController.updateOrderCoupon?dto={}", dto);
@@ -531,7 +532,7 @@ public class StoreOrderController {
             if (userId == null) {
                 return R.fail("用户未登录");
             }
-            StoreOrder order = orderService.updateOrderCoupon(dto.getOrderId(), dto.getCouponId());
+            StoreOrder order = orderService.updateOrderCoupon(dto);
             return R.data(order);
         } catch (Exception e) {
             log.error("更新订单优惠券失败: {}", e.getMessage(), e);

+ 13 - 0
alien-dining/src/main/java/shop/alien/dining/service/DiningService.java

@@ -3,6 +3,7 @@ package shop.alien.dining.service;
 import shop.alien.entity.store.vo.TableDiningStatusVO;
 import shop.alien.entity.store.vo.*;
 
+import java.math.BigDecimal;
 import java.util.List;
 
 /**
@@ -147,4 +148,16 @@ public interface DiningService {
      * @return 锁定用户ID,如果未锁定返回null
      */
     Integer checkSettlementLock(Integer orderId);
+
+    /**
+     * 按当前服务器时刻(上海时区日历+时钟),根据 store_service_fee_rule 预估本店本桌服务费。
+     * feeType=3 时依赖菜品成交价小计 goodsSubtotal(不含餐具);不传则按 0 仅返回非比例项。
+     *
+     * @param storeId       门店 ID
+     * @param tableId       store_table.id
+     * @param dinerCount    就餐人数(feeType=1 时用,可空默认 1)
+     * @param goodsSubtotal 菜品小计(元),feeType=3 时用
+     * @return 合计与分项
+     */
+    DiningServiceFeeEstimateVO estimateServiceFee(Integer storeId, Integer tableId, Integer dinerCount, BigDecimal goodsSubtotal);
 }

+ 2 - 1
alien-dining/src/main/java/shop/alien/dining/service/StoreOrderService.java

@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.IService;
 import shop.alien.entity.store.StoreOrder;
 import shop.alien.entity.store.dto.CreateOrderDTO;
+import shop.alien.entity.store.dto.UpdateOrderCouponDTO;
 import shop.alien.entity.store.vo.OrderChangeLogBatchVO;
 import shop.alien.entity.store.vo.OrderInfoVO;
 import shop.alien.entity.store.vo.StoreOrderPageVO;
@@ -107,7 +108,7 @@ public interface StoreOrderService extends IService<StoreOrder> {
      * @param couponId 优惠券ID(可为空,表示不使用优惠券)
      * @return 订单信息
      */
-    StoreOrder updateOrderCoupon(Integer orderId, Integer couponId);
+    StoreOrder updateOrderCoupon(UpdateOrderCouponDTO dto);
 
     /**
      * 管理员重置餐桌(删除购物车数据、未支付/已取消的订单数据,并重置餐桌表)

+ 35 - 15
alien-dining/src/main/java/shop/alien/dining/service/impl/DiningServiceImpl.java

@@ -14,6 +14,7 @@ import shop.alien.mapper.*;
 import shop.alien.dining.config.BaseRedisService;
 import shop.alien.dining.service.CartService;
 import shop.alien.dining.service.DiningService;
+import shop.alien.dining.support.DiningOrderServiceFeeCalculator;
 
 import java.math.BigDecimal;
 import java.time.LocalDate;
@@ -44,6 +45,22 @@ public class DiningServiceImpl implements DiningService {
     private final shop.alien.dining.service.StoreOrderService storeOrderService;
     private final shop.alien.mapper.StoreOrderMapper storeOrderMapper;
     private final shop.alien.dining.service.OrderLockService orderLockService;
+    private final DiningOrderServiceFeeCalculator diningOrderServiceFeeCalculator;
+
+    @Override
+    public DiningServiceFeeEstimateVO estimateServiceFee(Integer storeId, Integer tableId, Integer dinerCount, BigDecimal goodsSubtotal) {
+        if (storeId == null || tableId == null) {
+            throw new RuntimeException("门店ID与桌号ID不能为空");
+        }
+        StoreTable table = storeTableMapper.selectById(tableId);
+        if (table == null) {
+            throw new RuntimeException("桌号不存在");
+        }
+        if (table.getStoreId() == null || !table.getStoreId().equals(storeId)) {
+            throw new RuntimeException("桌号与门店不匹配");
+        }
+        return diningOrderServiceFeeCalculator.estimate(storeId, tableId, new Date(), dinerCount, goodsSubtotal);
+    }
 
     @Override
     public TableDiningStatusVO getTableDiningStatus(Integer tableId) {
@@ -385,12 +402,15 @@ public class DiningServiceImpl implements DiningService {
         vo.setTablewareFee(tablewareFee);
         vo.setTablewareUnitPrice(tablewareUnitPrice);
 
-        // 自动匹配最优惠的优惠券
+        // 服务费由前端根据门店规则自行计算,下单时随 CreateOrderDTO 传入;此处占位 0
+        vo.setServiceFee(BigDecimal.ZERO);
+
+        BigDecimal totalWithTableware = cart.getTotalAmount().add(tablewareFee);
+
+        // 自动匹配最优惠的优惠券(基数不含服务费,与前端本地加服务费后的实付可能不一致,以提交订单时传参为准)
         List<AvailableCouponVO> availableCoupons = new ArrayList<>();
         if (userId != null && cart.getTotalAmount().compareTo(BigDecimal.ZERO) > 0) {
             availableCoupons = getAvailableCoupons(pageInfo.getStoreId(), userId);
-            // 过滤出可用的优惠券(已领取且满足最低消费)
-            BigDecimal totalWithTableware = cart.getTotalAmount().add(tablewareFee);
             // 需要查询优惠券详情来计算实际优惠金额(用于排序)
             List<Integer> couponIds = availableCoupons.stream()
                     .filter(c -> c.getIsReceived() && c.getIsAvailable())
@@ -425,15 +445,14 @@ public class DiningServiceImpl implements DiningService {
                 // 根据优惠券类型计算优惠金额
                 // 需要查询优惠券详情来计算折扣券的优惠金额
                 LifeDiscountCoupon couponDetail = lifeDiscountCouponMapper.selectById(bestCoupon.getId());
-                BigDecimal discountAmount = calculateDiscountAmount(couponDetail, cart.getTotalAmount().add(tablewareFee));
-                BigDecimal totalAmount = cart.getTotalAmount().add(tablewareFee);
+                BigDecimal discountAmount = calculateDiscountAmount(couponDetail, totalWithTableware);
                 vo.setDiscountAmount(discountAmount);
-                vo.setPayAmount(totalAmount.subtract(discountAmount));
+                vo.setPayAmount(totalWithTableware.subtract(discountAmount));
             } else {
-                vo.setPayAmount(cart.getTotalAmount().add(tablewareFee));
+                vo.setPayAmount(totalWithTableware);
             }
         } else {
-            vo.setPayAmount(cart.getTotalAmount().add(tablewareFee));
+            vo.setPayAmount(totalWithTableware);
         }
         vo.setAvailableCoupons(availableCoupons);
 
@@ -603,6 +622,7 @@ public class DiningServiceImpl implements DiningService {
         vo.setItems(items);
         vo.setTotalAmount(order.getTotalAmount());
         vo.setTablewareFee(order.getTablewareFee() != null ? order.getTablewareFee() : BigDecimal.ZERO);
+        vo.setServiceFee(order.getServiceFee() != null ? order.getServiceFee() : BigDecimal.ZERO);
         vo.setCouponId(order.getCouponId());
         vo.setDiscountAmount(order.getDiscountAmount() != null ? order.getDiscountAmount() : BigDecimal.ZERO);
         vo.setPayAmount(order.getPayAmount());
@@ -639,11 +659,11 @@ public class DiningServiceImpl implements DiningService {
      * 计算优惠金额:根据优惠券类型(满减券或折扣券)计算
      *
      * @param coupon           优惠券对象
-     * @param totalWithTableware 订单总金额(含餐具费)
+     * @param totalBeforeDiscount 应付基数(含菜品、餐具费、服务费)
      * @return 优惠金额
      */
-    private BigDecimal calculateDiscountAmount(LifeDiscountCoupon coupon, BigDecimal totalWithTableware) {
-        if (coupon == null || totalWithTableware == null || totalWithTableware.compareTo(BigDecimal.ZERO) <= 0) {
+    private BigDecimal calculateDiscountAmount(LifeDiscountCoupon coupon, BigDecimal totalBeforeDiscount) {
+        if (coupon == null || totalBeforeDiscount == null || totalBeforeDiscount.compareTo(BigDecimal.ZERO) <= 0) {
             return BigDecimal.ZERO;
         }
 
@@ -656,9 +676,9 @@ public class DiningServiceImpl implements DiningService {
             BigDecimal discountRate = coupon.getDiscountRate();
             if (discountRate != null && discountRate.compareTo(BigDecimal.ZERO) > 0 && discountRate.compareTo(new BigDecimal(100)) <= 0) {
                 // 计算折扣后的金额
-                BigDecimal discountedAmount = totalWithTableware.multiply(discountRate).divide(new BigDecimal(100), 2, BigDecimal.ROUND_HALF_UP);
+                BigDecimal discountedAmount = totalBeforeDiscount.multiply(discountRate).divide(new BigDecimal(100), 2, BigDecimal.ROUND_HALF_UP);
                 // 优惠金额 = 原价 - 折扣后价格
-                discountAmount = totalWithTableware.subtract(discountedAmount);
+                discountAmount = totalBeforeDiscount.subtract(discountedAmount);
             }
         } else {
             // 满减券(默认或couponType=1):使用nominalValue
@@ -667,8 +687,8 @@ public class DiningServiceImpl implements DiningService {
                 discountAmount = BigDecimal.ZERO;
             }
             // 优惠金额不能超过订单总金额
-            if (discountAmount.compareTo(totalWithTableware) > 0) {
-                discountAmount = totalWithTableware;
+            if (discountAmount.compareTo(totalBeforeDiscount) > 0) {
+                discountAmount = totalBeforeDiscount;
             }
         }
 

+ 44 - 101
alien-dining/src/main/java/shop/alien/dining/service/impl/StoreOrderServiceImpl.java

@@ -21,6 +21,7 @@ import shop.alien.mapper.UserReservationMapper;
 import shop.alien.mapper.UserReservationTableMapper;
 import shop.alien.entity.store.dto.CartItemDTO;
 import shop.alien.entity.store.dto.CreateOrderDTO;
+import shop.alien.entity.store.dto.UpdateOrderCouponDTO;
 import shop.alien.entity.store.vo.OrderChangeLogBatchVO;
 import shop.alien.entity.store.vo.OrderChangeLogItemVO;
 import shop.alien.entity.store.vo.OrderInfoVO;
@@ -194,11 +195,28 @@ public class StoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper, StoreOr
             }
         }
 
+        BigDecimal serviceFee = dto.getServiceFee() != null ? dto.getServiceFee() : BigDecimal.ZERO;
+
         Date now = new Date();
+
+        Set<Integer> cartCuisineIds = cart.getItems().stream()
+                .map(CartItemDTO::getCuisineId)
+                .filter(Objects::nonNull)
+                .filter(id -> id > 0)
+                .collect(Collectors.toSet());
+        Map<Integer, BigDecimal> listUnitByCuisine = cartCuisineIds.isEmpty()
+                ? Collections.emptyMap()
+                : DiningMenuPricing.resolveListUnitPriceByCuisineId(cartCuisineIds, storeCuisineMapper);
+        Map<Integer, BigDecimal> saleUnitByCuisine = cartCuisineIds.isEmpty()
+                ? Collections.emptyMap()
+                : DiningMenuPricing.resolveSaleUnitPrice(table.getStoreId(), listUnitByCuisine, storeProductDiscountRuleMapper);
+
         StoreOrder order = null;
         String orderNo = null;
         boolean isUpdate = false; // 是否是更新订单
-        
+        /** 更新订单场景下,写库前的优惠券 ID,用于加餐换券/取消券时作废旧 {@link StoreCouponUsage} */
+        Integer previousCouponIdBeforeUpdate = null;
+
         // 检查桌号是否已绑定订单
         if (table.getCurrentOrderId() != null) {
             // 查询已存在的订单
@@ -207,6 +225,7 @@ public class StoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper, StoreOr
                 // 订单存在且是待支付状态,更新订单
                 isUpdate = true;
                 order = existingOrder;
+                previousCouponIdBeforeUpdate = order.getCouponId();
                 orderNo = order.getOrderNo(); // 使用原订单号
                 log.info("桌号已绑定订单,更新订单信息, orderId={}, orderNo={}", order.getId(), orderNo);
                 
@@ -214,6 +233,7 @@ public class StoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper, StoreOr
                 order.setDinerCount(dto.getDinerCount());
                 order.setContactPhone(dto.getContactPhone());
                 order.setTablewareFee(tablewareFee);
+                order.setServiceFee(serviceFee);
                 order.setTotalAmount(totalAmount);
                 order.setCouponId(dto.getCouponId());
                 order.setCurrentCouponId(dto.getCouponId());
@@ -275,6 +295,7 @@ public class StoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper, StoreOr
             order.setDinerCount(dto.getDinerCount());
             order.setContactPhone(dto.getContactPhone());
             order.setTablewareFee(tablewareFee);
+            order.setServiceFee(serviceFee);
             order.setPayUserId(userId);
             order.setPayUserPhone(userPhone);
             order.setOrderStatus(0); // 待支付
@@ -324,13 +345,13 @@ public class StoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper, StoreOr
             }
         }
         
-        // 如果是更新订单,且优惠券发生变化,需要处理旧优惠券的使用记录
-        if (isUpdate && finalOrder.getCouponId() != null && 
-            (dto.getCouponId() == null || !finalOrder.getCouponId().equals(dto.getCouponId()))) {
+        // 如果是更新订单,且优惠券发生变化,需要处理旧优惠券的使用记录(须在写入新 couponId 前记录 previousCouponIdBeforeUpdate)
+        if (isUpdate && previousCouponIdBeforeUpdate != null
+                && (dto.getCouponId() == null || !dto.getCouponId().equals(previousCouponIdBeforeUpdate))) {
             // 订单的优惠券被替换或取消,需要将旧优惠券使用记录状态改为"已取消"
             LambdaQueryWrapper<StoreCouponUsage> oldUsageWrapper = new LambdaQueryWrapper<>();
             oldUsageWrapper.eq(StoreCouponUsage::getOrderId, finalOrder.getId());
-            oldUsageWrapper.eq(StoreCouponUsage::getCouponId, finalOrder.getCouponId());
+            oldUsageWrapper.eq(StoreCouponUsage::getCouponId, previousCouponIdBeforeUpdate);
             oldUsageWrapper.eq(StoreCouponUsage::getDeleteFlag, 0);
             oldUsageWrapper.orderByDesc(StoreCouponUsage::getCreatedTime);
             oldUsageWrapper.last("LIMIT 1");
@@ -340,7 +361,7 @@ public class StoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper, StoreOr
                 oldUsage.setUpdatedTime(new Date());
                 storeCouponUsageMapper.updateById(oldUsage);
                 log.info("更新订单时替换优惠券:取消旧优惠券使用记录, orderId={}, oldCouponId={}, newCouponId={}", 
-                        finalOrder.getId(), finalOrder.getCouponId(), dto.getCouponId());
+                        finalOrder.getId(), previousCouponIdBeforeUpdate, dto.getCouponId());
             }
         }
 
@@ -349,18 +370,6 @@ public class StoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper, StoreOr
         // 餐具的特殊ID(用于标识餐具项)
         final Integer TABLEWARE_CUISINE_ID = -1;
 
-        java.util.Set<Integer> cartCuisineIds = cart.getItems().stream()
-                .map(CartItemDTO::getCuisineId)
-                .filter(Objects::nonNull)
-                .filter(id -> id > 0)
-                .collect(Collectors.toSet());
-        java.util.Map<Integer, java.math.BigDecimal> listUnitByCuisine = cartCuisineIds.isEmpty()
-                ? java.util.Collections.emptyMap()
-                : DiningMenuPricing.resolveListUnitPriceByCuisineId(cartCuisineIds, storeCuisineMapper);
-        java.util.Map<Integer, java.math.BigDecimal> saleUnitByCuisine = cartCuisineIds.isEmpty()
-                ? java.util.Collections.emptyMap()
-                : DiningMenuPricing.resolveSaleUnitPrice(table.getStoreId(), listUnitByCuisine, storeProductDiscountRuleMapper);
-
         List<StoreOrderDetail> orderDetails = cart.getItems().stream()
                 .map(item -> {
                     StoreOrderDetail detail = new StoreOrderDetail();
@@ -508,35 +517,14 @@ public class StoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper, StoreOr
         // 验证订单状态
         StoreOrder order = validateOrderForOperation(orderId, 0, "支付");
 
-        // 支付时重新计算优惠券金额和实付金额(此时订单金额已确定,不会再变化)
-        BigDecimal discountAmount = BigDecimal.ZERO;
-        BigDecimal tablewareFee = order.getTablewareFee() != null ? order.getTablewareFee() : BigDecimal.ZERO;
-        BigDecimal totalAmount = order.getTotalAmount() != null ? order.getTotalAmount() : BigDecimal.ZERO;
-        BigDecimal totalWithTableware = totalAmount.add(tablewareFee);
-        
         if (order.getCouponId() != null) {
-            // 查询优惠券信息
             LifeDiscountCoupon coupon = lifeDiscountCouponMapper.selectById(order.getCouponId());
             if (coupon == null) {
                 throw new RuntimeException("优惠券不存在");
             }
-            
-            // 验证最低消费(菜品总价 + 餐具费)
-            if (coupon.getMinimumSpendingAmount() != null
-                    && totalWithTableware.compareTo(coupon.getMinimumSpendingAmount()) < 0) {
-                throw new RuntimeException("订单金额未达到优惠券最低消费要求(" + coupon.getMinimumSpendingAmount() + "元)");
-            }
-            
-            // 计算优惠金额:根据优惠券类型(满减券或折扣券)计算
-            discountAmount = calculateDiscountAmount(coupon, totalWithTableware);
         }
-        
-        // 计算实付金额(菜品总价 + 餐具费 - 优惠金额)
-        BigDecimal payAmount = totalWithTableware.subtract(discountAmount);
-        
-        // 更新订单信息(包括优惠金额和实付金额)
-        order.setDiscountAmount(discountAmount);
-        order.setPayAmount(payAmount);
+
+        // 优惠与实付以订单当前字段为准(前端下单/换券时已写入,不做后台重算)
         order.setOrderStatus(1); // 已支付
         order.setPayStatus(1); // 已支付
         order.setPayType(payType);
@@ -972,37 +960,34 @@ public class StoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper, StoreOr
     }
 
     @Override
-    public StoreOrder updateOrderCoupon(Integer orderId, Integer couponId) {
+    public StoreOrder updateOrderCoupon(UpdateOrderCouponDTO dto) {
+        if (dto == null || dto.getOrderId() == null) {
+            throw new RuntimeException("订单ID不能为空");
+        }
+        Integer orderId = dto.getOrderId();
+        Integer couponId = dto.getCouponId();
         log.info("更新订单优惠券, orderId={}, couponId={}", orderId, couponId);
 
         // 验证订单状态
         StoreOrder order = validateOrderForOperation(orderId, 0, "修改优惠券");
 
-        BigDecimal discountAmount = BigDecimal.ZERO;
-
         if (couponId != null) {
-            // 验证优惠券
             LifeDiscountCoupon coupon = lifeDiscountCouponMapper.selectById(couponId);
             if (coupon == null) {
                 throw new RuntimeException("优惠券不存在");
             }
-
-            // 验证优惠券是否属于该门店
             if (!coupon.getStoreId().equals(String.valueOf(order.getStoreId()))) {
                 throw new RuntimeException("优惠券不属于该门店");
             }
-
-            // 验证最低消费(菜品总价 + 餐具费)
-            BigDecimal totalWithTableware = order.getTotalAmount().add(order.getTablewareFee() != null ? order.getTablewareFee() : BigDecimal.ZERO);
-            if (coupon.getMinimumSpendingAmount() != null
-                    && totalWithTableware.compareTo(coupon.getMinimumSpendingAmount()) < 0) {
-                throw new RuntimeException("订单金额未达到优惠券最低消费要求");
-            }
-
-            // 计算优惠金额:根据优惠券类型(满减券或折扣券)计算
-            discountAmount = calculateDiscountAmount(coupon, totalWithTableware);
         }
 
+        BigDecimal discountAmount = dto.getDiscountAmount() != null
+                ? dto.getDiscountAmount()
+                : (order.getDiscountAmount() != null ? order.getDiscountAmount() : BigDecimal.ZERO);
+        BigDecimal payAmount = dto.getPayAmount() != null
+                ? dto.getPayAmount()
+                : (order.getPayAmount() != null ? order.getPayAmount() : BigDecimal.ZERO);
+
         // 如果之前有优惠券,且优惠券发生变化(包括取消优惠券),需要处理旧优惠券使用记录
         if (order.getCouponId() != null && (couponId == null || !order.getCouponId().equals(couponId))) {
             // 清除旧优惠券的使用标记(如果取消优惠券或更换优惠券)
@@ -1064,10 +1049,6 @@ public class StoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper, StoreOr
             }
         }
 
-        // 重新计算实付金额(菜品总价 + 餐具费 - 优惠金额)
-        BigDecimal tablewareFee = order.getTablewareFee() != null ? order.getTablewareFee() : BigDecimal.ZERO;
-        BigDecimal payAmount = order.getTotalAmount().add(tablewareFee).subtract(discountAmount);
-
         order.setCouponId(couponId);
         order.setCurrentCouponId(couponId);
         order.setDiscountAmount(discountAmount);
@@ -1552,6 +1533,7 @@ public class StoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper, StoreOr
         vo.setItems(items);
         vo.setTotalAmount(order.getTotalAmount());
         vo.setTablewareFee(order.getTablewareFee());
+        vo.setServiceFee(order.getServiceFee() != null ? order.getServiceFee() : BigDecimal.ZERO);
         vo.setCouponId(order.getCouponId());
         vo.setCouponName(couponName);
         vo.setCouponType(couponType);
@@ -1706,6 +1688,7 @@ public class StoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper, StoreOr
         vo.setRemark(order.getRemark());
         vo.setTotalAmount(order.getTotalAmount());
         vo.setTablewareFee(order.getTablewareFee());
+        vo.setServiceFee(order.getServiceFee() != null ? order.getServiceFee() : BigDecimal.ZERO);
         vo.setCouponId(order.getCouponId());
         vo.setCouponName(couponName);
         vo.setDiscountAmount(order.getDiscountAmount());
@@ -2131,44 +2114,4 @@ public class StoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper, StoreOr
         return new Object[]{userId, userPhone};
     }
 
-    /**
-     * 计算优惠金额:根据优惠券类型(满减券或折扣券)计算
-     *
-     * @param coupon           优惠券对象
-     * @param totalWithTableware 订单总金额(含餐具费)
-     * @return 优惠金额
-     */
-    private BigDecimal calculateDiscountAmount(LifeDiscountCoupon coupon, BigDecimal totalWithTableware) {
-        if (coupon == null || totalWithTableware == null || totalWithTableware.compareTo(BigDecimal.ZERO) <= 0) {
-            return BigDecimal.ZERO;
-        }
-
-        Integer couponType = coupon.getCouponType();
-        BigDecimal discountAmount = BigDecimal.ZERO;
-
-        if (couponType != null && couponType == 2) {
-            // 折扣券:根据折扣率计算优惠金额
-            // discountRate: 0-100,例如80表示8折,优惠金额 = 订单金额 * (100 - discountRate) / 100
-            BigDecimal discountRate = coupon.getDiscountRate();
-            if (discountRate != null && discountRate.compareTo(BigDecimal.ZERO) > 0 && discountRate.compareTo(new BigDecimal(100)) <= 0) {
-                // 计算折扣后的金额
-                BigDecimal discountedAmount = totalWithTableware.multiply(discountRate).divide(new BigDecimal(100), 2, BigDecimal.ROUND_HALF_UP);
-                // 优惠金额 = 原价 - 折扣后价格
-                discountAmount = totalWithTableware.subtract(discountedAmount);
-            }
-        } else {
-            // 满减券(默认或couponType=1):使用nominalValue
-            discountAmount = coupon.getNominalValue();
-            if (discountAmount == null) {
-                discountAmount = BigDecimal.ZERO;
-            }
-            // 优惠金额不能超过订单总金额
-            if (discountAmount.compareTo(totalWithTableware) > 0) {
-                discountAmount = totalWithTableware;
-            }
-        }
-
-        return discountAmount;
-    }
-
 }

+ 149 - 0
alien-dining/src/main/java/shop/alien/dining/support/DiningOrderServiceFeeCalculator.java

@@ -0,0 +1,149 @@
+package shop.alien.dining.support;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Component;
+import shop.alien.entity.store.StoreServiceFeeRule;
+import shop.alien.entity.store.vo.DiningServiceFeeEstimateVO;
+import shop.alien.entity.store.vo.DiningServiceFeeMatchedItemVO;
+import shop.alien.mapper.StoreServiceFeeRuleMapper;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.time.LocalDate;
+import java.time.LocalTime;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 按 {@link StoreServiceFeeRule} 在指定时刻为指定门店、桌台计算服务费(与下单入参口径无关,供前端展示/入单参考)。
+ */
+@Component
+@RequiredArgsConstructor
+public class DiningOrderServiceFeeCalculator {
+
+    private static final ZoneId SHANGHAI = ZoneId.of("Asia/Shanghai");
+    private static final String MODE_CUSTOM = "CUSTOM";
+
+    private final StoreServiceFeeRuleMapper storeServiceFeeRuleMapper;
+
+    /**
+     * @param storeId       门店 ID
+     * @param tableId       桌台 store_table.id
+     * @param at            计算时刻(一般为服务器当前时间)
+     * @param dinerCount    就餐人数,用于 feeType=1,默认至少 1
+     * @param goodsSubtotal 菜品成交价小计(不含餐具),用于 feeType=3;比例 feeValue 为百分数
+     */
+    public DiningServiceFeeEstimateVO estimate(
+            Integer storeId, Integer tableId, Date at, Integer dinerCount, BigDecimal goodsSubtotal) {
+
+        DiningServiceFeeEstimateVO vo = new DiningServiceFeeEstimateVO();
+        vo.setServiceFee(BigDecimal.ZERO);
+
+        if (storeId == null || tableId == null || at == null) {
+            return vo;
+        }
+        if (goodsSubtotal == null) {
+            goodsSubtotal = BigDecimal.ZERO;
+        }
+        int diners = dinerCount != null && dinerCount > 0 ? dinerCount : 1;
+
+        List<StoreServiceFeeRule> rules = storeServiceFeeRuleMapper.selectList(
+                new LambdaQueryWrapper<StoreServiceFeeRule>()
+                        .eq(StoreServiceFeeRule::getStoreId, storeId)
+                        .eq(StoreServiceFeeRule::getTableId, tableId)
+                        .eq(StoreServiceFeeRule::getStatus, 1));
+
+        if (rules == null || rules.isEmpty()) {
+            return vo;
+        }
+
+        ZonedDateTime zdt = at.toInstant().atZone(SHANGHAI);
+        LocalDate localDate = zdt.toLocalDate();
+        LocalTime localTime = zdt.toLocalTime();
+        int weekdayBit = weekdayMaskBit(zdt);
+
+        BigDecimal sum = BigDecimal.ZERO;
+        List<DiningServiceFeeMatchedItemVO> items = new ArrayList<>();
+
+        for (StoreServiceFeeRule rule : rules) {
+            if (!isRuleEffectiveOnDate(rule, localDate)) {
+                continue;
+            }
+            Integer mask = rule.getWeekdayMask();
+            if (mask == null || mask <= 0) {
+                continue;
+            }
+            if ((mask & weekdayBit) == 0) {
+                continue;
+            }
+            LocalTime st = rule.getStartTime();
+            LocalTime et = rule.getEndTime();
+            if (st == null || et == null) {
+                continue;
+            }
+            if (localTime.isBefore(st) || !localTime.isBefore(et)) {
+                continue;
+            }
+            BigDecimal part = amountForRule(rule, diners, goodsSubtotal);
+            if (part != null && part.compareTo(BigDecimal.ZERO) > 0) {
+                sum = sum.add(part);
+                DiningServiceFeeMatchedItemVO line = new DiningServiceFeeMatchedItemVO();
+                line.setRuleId(rule.getId());
+                line.setFeeName(rule.getFeeName());
+                line.setFeeType(rule.getFeeType());
+                line.setAmount(part);
+                items.add(line);
+            }
+        }
+
+        vo.setServiceFee(sum.setScale(2, RoundingMode.HALF_UP));
+        vo.setItems(items);
+        return vo;
+    }
+
+    private static boolean isRuleEffectiveOnDate(StoreServiceFeeRule rule, LocalDate day) {
+        if (MODE_CUSTOM.equals(rule.getEffectiveMode())) {
+            Date sd = rule.getStartDate();
+            Date ed = rule.getEndDate();
+            if (sd == null || ed == null) {
+                return false;
+            }
+            LocalDate start = sd.toInstant().atZone(SHANGHAI).toLocalDate();
+            LocalDate end = ed.toInstant().atZone(SHANGHAI).toLocalDate();
+            return !day.isBefore(start) && !day.isAfter(end);
+        }
+        return true;
+    }
+
+    /** weekdayMask:周一 = 2^0 … 周日 = 2^6(与门店 B 端配置一致) */
+    private static int weekdayMaskBit(ZonedDateTime zdt) {
+        int v = zdt.getDayOfWeek().getValue();
+        return 1 << (v - 1);
+    }
+
+    private static BigDecimal amountForRule(StoreServiceFeeRule rule, int dinerCount, BigDecimal goodsSubtotal) {
+        Integer feeType = rule.getFeeType();
+        BigDecimal feeValue = rule.getFeeValue();
+        if (feeType == null || feeValue == null) {
+            return BigDecimal.ZERO;
+        }
+        switch (feeType) {
+            case 1:
+                return feeValue.multiply(BigDecimal.valueOf(dinerCount)).setScale(2, RoundingMode.HALF_UP);
+            case 2:
+                return feeValue.setScale(2, RoundingMode.HALF_UP);
+            case 3:
+                if (goodsSubtotal.compareTo(BigDecimal.ZERO) <= 0) {
+                    return BigDecimal.ZERO;
+                }
+                return goodsSubtotal.multiply(feeValue)
+                        .divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
+            default:
+                return BigDecimal.ZERO;
+        }
+    }
+}

+ 4 - 0
alien-entity/src/main/java/shop/alien/entity/store/StoreOrder.java

@@ -74,6 +74,10 @@ public class StoreOrder {
     @TableField("tableware_fee")
     private BigDecimal tablewareFee;
 
+    @ApiModelProperty(value = "服务费(按 store_service_fee_rule 在下单时刻匹配并快照)")
+    @TableField("service_fee")
+    private BigDecimal serviceFee;
+
     @ApiModelProperty(value = "订单状态(0:待支付, 1:已支付, 2:已取消, 3:已完成)")
     @TableField("order_status")
     private Integer orderStatus;

+ 4 - 4
alien-entity/src/main/java/shop/alien/entity/store/dto/CreateOrderDTO.java

@@ -7,10 +7,7 @@ import lombok.Data;
 import javax.validation.constraints.NotNull;
 
 /**
- * 创建订单DTO(订单总金额、餐具费、优惠金额、实付金额完全采用前端传参,不做后台金额校验)
- *
- * @author system
- * @since 2025-01-XX
+ * 创建订单DTO(订单总金额、餐具费、服务费、优惠金额、实付金额完全采用前端传参,不做后台金额校验)
  */
 @Data
 @ApiModel(value = "CreateOrderDTO对象", description = "创建订单,金额完全采用前端传参")
@@ -35,6 +32,9 @@ public class CreateOrderDTO {
     @ApiModelProperty(value = "餐具费(前端传参,不做后台校验)")
     private java.math.BigDecimal tablewareFee;
 
+    @ApiModelProperty(value = "服务费(前端传参,不做后台校验)")
+    private java.math.BigDecimal serviceFee;
+
     @ApiModelProperty(value = "优惠金额(前端传参,不做后台校验)")
     private java.math.BigDecimal discountAmount;
 

+ 7 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/UpdateOrderCouponDTO.java

@@ -5,6 +5,7 @@ import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 
 import javax.validation.constraints.NotNull;
+import java.math.BigDecimal;
 
 /**
  * 更新订单优惠券DTO
@@ -22,4 +23,10 @@ public class UpdateOrderCouponDTO {
 
     @ApiModelProperty(value = "优惠券ID(可为空,表示不使用优惠券)")
     private Integer couponId;
+
+    @ApiModelProperty(value = "优惠金额(前端传参,与实付需自行与订单金额核对;不做后台校验)")
+    private BigDecimal discountAmount;
+
+    @ApiModelProperty(value = "实付金额(前端传参,不做后台校验)")
+    private BigDecimal payAmount;
 }

+ 23 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/DiningServiceFeeEstimateVO.java

@@ -0,0 +1,23 @@
+package shop.alien.entity.store.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 当前时刻、门店、桌台维度服务费预估(与 store_service_fee_rule 配置一致)
+ */
+@Data
+@ApiModel(value = "DiningServiceFeeEstimateVO", description = "点餐-服务费预估")
+public class DiningServiceFeeEstimateVO {
+
+    @ApiModelProperty("服务费合计(元),未命中规则时为 0")
+    private BigDecimal serviceFee;
+
+    @ApiModelProperty("分项明细,便于页面展示")
+    private List<DiningServiceFeeMatchedItemVO> items = new ArrayList<>();
+}

+ 27 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/DiningServiceFeeMatchedItemVO.java

@@ -0,0 +1,27 @@
+package shop.alien.entity.store.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * 服务费预估:单条命中规则产生的金额(展示用)
+ */
+@Data
+@ApiModel(value = "DiningServiceFeeMatchedItemVO", description = "服务费命中项")
+public class DiningServiceFeeMatchedItemVO {
+
+    @ApiModelProperty("规则主键 id")
+    private Integer ruleId;
+
+    @ApiModelProperty("服务费名称")
+    private String feeName;
+
+    @ApiModelProperty("类型:1按人数 2按桌台 3按消费金额(比例为%)")
+    private Integer feeType;
+
+    @ApiModelProperty("本项金额(元)")
+    private BigDecimal amount;
+}

+ 4 - 1
alien-entity/src/main/java/shop/alien/entity/store/vo/OrderConfirmVO.java

@@ -43,6 +43,9 @@ public class OrderConfirmVO {
     @ApiModelProperty(value = "餐具费")
     private BigDecimal tablewareFee;
 
+    @ApiModelProperty(value = "服务费(由前端自行计算展示;下单时传入 CreateOrderDTO.serviceFee;接口此处多为 0)")
+    private BigDecimal serviceFee;
+
     @ApiModelProperty(value = "餐具费单价")
     private BigDecimal tablewareUnitPrice;
 
@@ -55,7 +58,7 @@ public class OrderConfirmVO {
     @ApiModelProperty(value = "优惠金额")
     private BigDecimal discountAmount;
 
-    @ApiModelProperty(value = "应付金额(菜品总价+餐具费-优惠券)")
+    @ApiModelProperty(value = "应付金额(菜品总价+餐具费+服务费-优惠券)")
     private BigDecimal payAmount;
 
     @ApiModelProperty(value = "可用优惠券列表")

+ 3 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/OrderDetailWithChangeLogVO.java

@@ -48,6 +48,9 @@ public class OrderDetailWithChangeLogVO {
     @ApiModelProperty(value = "餐具费")
     private BigDecimal tablewareFee;
 
+    @ApiModelProperty(value = "服务费")
+    private BigDecimal serviceFee;
+
     @ApiModelProperty(value = "优惠券ID")
     private Integer couponId;
 

+ 3 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/OrderInfoVO.java

@@ -70,6 +70,9 @@ public class OrderInfoVO {
     @ApiModelProperty(value = "餐具费")
     private BigDecimal tablewareFee;
 
+    @ApiModelProperty(value = "服务费(下单时按门店规则快照)")
+    private BigDecimal serviceFee;
+
     @ApiModelProperty(value = "优惠券ID")
     private Integer couponId;
 

+ 3 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/OrderSettlementVO.java

@@ -48,6 +48,9 @@ public class OrderSettlementVO {
     @ApiModelProperty(value = "餐具费")
     private BigDecimal tablewareFee;
 
+    @ApiModelProperty(value = "服务费")
+    private BigDecimal serviceFee;
+
     @ApiModelProperty(value = "优惠券ID")
     private Integer couponId;
 

+ 3 - 0
alien-entity/src/main/resources/db/migration/store_order_service_fee.sql

@@ -0,0 +1,3 @@
+-- 点餐订单:服务端按桌台时段规则计算的服务费快照(元,与 total_amount / tableware_fee 口径一致)
+ALTER TABLE `store_order`
+    ADD COLUMN `service_fee` decimal(12, 2) NOT NULL DEFAULT 0.00 COMMENT '服务费(按门店规则快照)' AFTER `tableware_fee`;

+ 17 - 0
alien-store/src/main/java/shop/alien/store/controller/DiningServiceController.java

@@ -16,6 +16,7 @@ import shop.alien.entity.store.vo.*;
 import shop.alien.store.feign.DiningServiceFeign;
 
 import javax.servlet.http.HttpServletRequest;
+import java.math.BigDecimal;
 import java.util.List;
 
 /**
@@ -64,6 +65,22 @@ public class DiningServiceController {
         }
     }
 
+    @ApiOperation(value = "预估服务费(当前时刻+本店本桌)", notes = "免登录。tableId 须为该门店下的 store_table.id;可选 dinerCount、goodsSubtotal(菜品小计元,比例收费用)。")
+    @ApiOperationSupport(order = 100)
+    @GetMapping("/service-fee/estimate")
+    public R<DiningServiceFeeEstimateVO> getServiceFeeEstimate(
+            @ApiParam(value = "门店ID", required = true) @RequestParam Integer storeId,
+            @ApiParam(value = "桌号ID(store_table.id)", required = true) @RequestParam Integer tableId,
+            @ApiParam(value = "就餐人数") @RequestParam(required = false) Integer dinerCount,
+            @ApiParam(value = "菜品小计(元)") @RequestParam(required = false) BigDecimal goodsSubtotal) {
+        try {
+            return diningServiceFeign.estimateServiceFee(storeId, tableId, dinerCount, goodsSubtotal);
+        } catch (Exception e) {
+            log.error("预估服务费失败: {}", e.getMessage(), e);
+            return R.fail("预估服务费失败: " + e.getMessage());
+        }
+    }
+
     @ApiOperation(value = "获取点餐页面信息", notes = "首客选桌时传就餐人数;餐桌已就餐中时可不传就餐人数,将使用已保存人数")
     @ApiOperationSupport(order = 1)
     @GetMapping("/page-info")

+ 16 - 0
alien-store/src/main/java/shop/alien/store/controller/dining/StoreDiningPathProxyController.java

@@ -13,6 +13,7 @@ import shop.alien.store.feign.DiningServiceFeign;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.validation.Valid;
+import java.math.BigDecimal;
 import java.util.List;
 
 /**
@@ -48,6 +49,21 @@ public class StoreDiningPathProxyController {
         }
     }
 
+    @ApiOperation(value = "预估服务费(当前时刻+本店本桌)", notes = "免登录。透传 alien-dining GET /store/dining/service-fee/estimate;tableId 须为该门店下的 store_table.id。")
+    @GetMapping("/service-fee/estimate")
+    public R<DiningServiceFeeEstimateVO> estimateServiceFee(
+            @ApiParam(value = "门店ID", required = true) @RequestParam Integer storeId,
+            @ApiParam(value = "桌号ID(store_table.id)", required = true) @RequestParam Integer tableId,
+            @ApiParam(value = "就餐人数") @RequestParam(required = false) Integer dinerCount,
+            @ApiParam(value = "菜品小计(元)") @RequestParam(required = false) BigDecimal goodsSubtotal) {
+        try {
+            return diningServiceFeign.estimateServiceFee(storeId, tableId, dinerCount, goodsSubtotal);
+        } catch (Exception e) {
+            log.error("estimateServiceFee: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
     @ApiOperation(value = "提交到店就餐信息", notes = "需 Authorization。写入 user_reservation(已到店),关联本桌;store_table 置就餐中并写 diner_count。返回预约ID。"
             + "startTime、若传的 endTime 须为 yyyy-MM-dd HH:mm(无秒);不传 endTime 由后端推算;可选 reservationDate 须与 startTime 同日。"
             + "与同桌有效预订时段冲突时报错。请求体见 DiningWalkInReservationDTO。")

+ 10 - 0
alien-store/src/main/java/shop/alien/store/feign/DiningServiceFeign.java

@@ -51,6 +51,16 @@ public interface DiningServiceFeign {
     R<TableDiningStatusVO> getTableDiningStatus(@RequestParam("tableId") Integer tableId);
 
     /**
+     * 预估服务费(当前服务器时刻 + 门店 + 桌,免登录)
+     */
+    @GetMapping("/store/dining/service-fee/estimate")
+    R<DiningServiceFeeEstimateVO> estimateServiceFee(
+            @RequestParam("storeId") Integer storeId,
+            @RequestParam("tableId") Integer tableId,
+            @RequestParam(value = "dinerCount", required = false) Integer dinerCount,
+            @RequestParam(value = "goodsSubtotal", required = false) BigDecimal goodsSubtotal);
+
+    /**
      * 提交到店就餐信息(写入 user_reservation,状态已到店),需登录
      *
      * @return R.data 为 user_reservation.id