|
|
@@ -0,0 +1,569 @@
|
|
|
+package shop.alien.store.service.impl;
|
|
|
+
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
+import lombok.RequiredArgsConstructor;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.beans.BeanUtils;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.util.StringUtils;
|
|
|
+import shop.alien.entity.store.*;
|
|
|
+import shop.alien.entity.store.dto.CartDTO;
|
|
|
+import shop.alien.entity.store.dto.CartItemDTO;
|
|
|
+import shop.alien.entity.store.vo.*;
|
|
|
+import shop.alien.mapper.*;
|
|
|
+import shop.alien.store.config.BaseRedisService;
|
|
|
+import shop.alien.store.service.CartService;
|
|
|
+import shop.alien.store.service.DiningService;
|
|
|
+
|
|
|
+import java.math.BigDecimal;
|
|
|
+import java.time.LocalDate;
|
|
|
+import java.util.*;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 点餐服务实现类
|
|
|
+ *
|
|
|
+ * @author system
|
|
|
+ * @since 2025-01-XX
|
|
|
+ */
|
|
|
+@Slf4j
|
|
|
+@Service
|
|
|
+@RequiredArgsConstructor
|
|
|
+public class DiningServiceImpl implements DiningService {
|
|
|
+
|
|
|
+ private static final String ORDER_LOCK_KEY_PREFIX = "order:lock:table:";
|
|
|
+ private static final int ORDER_LOCK_EXPIRE_SECONDS = 300; // 5分钟过期
|
|
|
+
|
|
|
+ private final StoreTableMapper storeTableMapper;
|
|
|
+ private final StoreInfoMapper storeInfoMapper;
|
|
|
+ private final StoreCuisineMapper storeCuisineMapper;
|
|
|
+ private final StoreCuisineCategoryMapper storeCuisineCategoryMapper;
|
|
|
+ private final StoreCuisineComboMapper storeCuisineComboMapper;
|
|
|
+ private final StoreOrderDetailMapper storeOrderDetailMapper;
|
|
|
+ private final LifeDiscountCouponMapper lifeDiscountCouponMapper;
|
|
|
+ private final LifeDiscountCouponUserMapper lifeDiscountCouponUserMapper;
|
|
|
+ private final CartService cartService;
|
|
|
+ private final BaseRedisService baseRedisService;
|
|
|
+ private final shop.alien.store.service.StoreOrderService storeOrderService;
|
|
|
+ private final shop.alien.mapper.StoreOrderMapper storeOrderMapper;
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public DiningPageInfoVO getDiningPageInfo(Integer tableId, Integer dinerCount) {
|
|
|
+ log.info("获取点餐页面信息, tableId={}, dinerCount={}", tableId, dinerCount);
|
|
|
+ StoreTable table = storeTableMapper.selectById(tableId);
|
|
|
+ if (table == null) {
|
|
|
+ throw new RuntimeException("桌号不存在");
|
|
|
+ }
|
|
|
+
|
|
|
+ StoreInfo storeInfo = storeInfoMapper.selectById(table.getStoreId());
|
|
|
+ if (storeInfo == null) {
|
|
|
+ throw new RuntimeException("门店不存在");
|
|
|
+ }
|
|
|
+
|
|
|
+ DiningPageInfoVO vo = new DiningPageInfoVO();
|
|
|
+ vo.setStoreName(storeInfo.getStoreName());
|
|
|
+ vo.setTableNumber(table.getTableNumber());
|
|
|
+ vo.setDinerCount(dinerCount);
|
|
|
+ vo.setStoreId(table.getStoreId());
|
|
|
+ vo.setTableId(tableId);
|
|
|
+
|
|
|
+ return vo;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<CuisineListVO> searchCuisines(Integer storeId, String keyword, Integer tableId) {
|
|
|
+ log.info("搜索菜品, storeId={}, keyword={}, tableId={}", storeId, keyword, tableId);
|
|
|
+
|
|
|
+ // 限制搜索关键词长度
|
|
|
+ if (StringUtils.hasText(keyword) && keyword.length() > 10) {
|
|
|
+ keyword = keyword.substring(0, 10);
|
|
|
+ }
|
|
|
+
|
|
|
+ LambdaQueryWrapper<StoreCuisine> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ wrapper.eq(StoreCuisine::getStoreId, storeId);
|
|
|
+ wrapper.eq(StoreCuisine::getDeleteFlag, 0);
|
|
|
+ wrapper.eq(StoreCuisine::getShelfStatus, 1); // 只查询上架的
|
|
|
+ if (StringUtils.hasText(keyword)) {
|
|
|
+ wrapper.like(StoreCuisine::getName, keyword);
|
|
|
+ }
|
|
|
+ wrapper.orderByDesc(StoreCuisine::getCreatedTime);
|
|
|
+
|
|
|
+ List<StoreCuisine> cuisines = storeCuisineMapper.selectList(wrapper);
|
|
|
+ return convertToCuisineListVO(cuisines, tableId);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<CuisineListVO> getCuisinesByCategory(Integer storeId, Integer categoryId, Integer tableId, Integer page, Integer size) {
|
|
|
+ log.info("根据分类获取菜品列表, storeId={}, categoryId={}, tableId={}, page={}, size={}", storeId, categoryId, tableId, page, size);
|
|
|
+
|
|
|
+ if (page == null || page < 1) {
|
|
|
+ page = 1;
|
|
|
+ }
|
|
|
+ if (size == null || size < 1) {
|
|
|
+ size = 12;
|
|
|
+ }
|
|
|
+
|
|
|
+ LambdaQueryWrapper<StoreCuisine> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ wrapper.eq(StoreCuisine::getStoreId, storeId);
|
|
|
+ wrapper.eq(StoreCuisine::getDeleteFlag, 0);
|
|
|
+ wrapper.eq(StoreCuisine::getShelfStatus, 1); // 只查询上架的
|
|
|
+ if (categoryId != null) {
|
|
|
+ // 这里假设菜品表有category_id字段,如果没有需要关联查询
|
|
|
+ // wrapper.eq(StoreCuisine::getCategoryId, categoryId);
|
|
|
+ }
|
|
|
+ wrapper.orderByDesc(StoreCuisine::getCreatedTime);
|
|
|
+
|
|
|
+ // 分页查询
|
|
|
+ int offset = (page - 1) * size;
|
|
|
+ wrapper.last("LIMIT " + offset + ", " + size);
|
|
|
+
|
|
|
+ List<StoreCuisine> cuisines = storeCuisineMapper.selectList(wrapper);
|
|
|
+ return convertToCuisineListVO(cuisines, tableId);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public CuisineDetailVO getCuisineDetail(Integer cuisineId, Integer tableId) {
|
|
|
+ log.info("获取菜品详情, cuisineId={}, tableId={}", cuisineId, tableId);
|
|
|
+
|
|
|
+ StoreCuisine cuisine = storeCuisineMapper.selectById(cuisineId);
|
|
|
+ if (cuisine == null) {
|
|
|
+ throw new RuntimeException("菜品不存在");
|
|
|
+ }
|
|
|
+
|
|
|
+ CuisineDetailVO vo = new CuisineDetailVO();
|
|
|
+ BeanUtils.copyProperties(cuisine, vo);
|
|
|
+
|
|
|
+ // 处理图片列表
|
|
|
+ if (StringUtils.hasText(cuisine.getImages())) {
|
|
|
+ List<String> images = Arrays.asList(cuisine.getImages().split(","));
|
|
|
+ vo.setImages(images);
|
|
|
+ } else {
|
|
|
+ vo.setImages(new ArrayList<>());
|
|
|
+ }
|
|
|
+
|
|
|
+ // 计算月售数量
|
|
|
+ vo.setMonthlySales(getMonthlySales(cuisineId));
|
|
|
+
|
|
|
+ // 获取购物车数量
|
|
|
+ CartDTO cart = cartService.getCart(tableId);
|
|
|
+ if (cart.getItems() != null) {
|
|
|
+ Optional<CartItemDTO> cartItem = cart.getItems().stream()
|
|
|
+ .filter(item -> item.getCuisineId().equals(cuisineId))
|
|
|
+ .findFirst();
|
|
|
+ vo.setCartQuantity(cartItem.map(CartItemDTO::getQuantity).orElse(0));
|
|
|
+ } else {
|
|
|
+ vo.setCartQuantity(0);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果是套餐,获取套餐包含的菜品
|
|
|
+ if (cuisine.getCuisineType() != null && cuisine.getCuisineType() == 2) {
|
|
|
+ LambdaQueryWrapper<StoreCuisineCombo> comboWrapper = new LambdaQueryWrapper<>();
|
|
|
+ comboWrapper.eq(StoreCuisineCombo::getCid, cuisineId);
|
|
|
+ comboWrapper.eq(StoreCuisineCombo::getDeleteFlag, 0);
|
|
|
+ List<StoreCuisineCombo> combos = storeCuisineComboMapper.selectList(comboWrapper);
|
|
|
+
|
|
|
+ List<CuisineComboItemVO> comboItems = combos.stream().map(combo -> {
|
|
|
+ CuisineComboItemVO item = new CuisineComboItemVO();
|
|
|
+ item.setCuisineId(combo.getSid());
|
|
|
+ item.setQuantity(combo.getSnum());
|
|
|
+ item.setCategory(combo.getCategory());
|
|
|
+ // 查询菜品名称
|
|
|
+ StoreCuisine comboCuisine = storeCuisineMapper.selectById(combo.getSid());
|
|
|
+ if (comboCuisine != null) {
|
|
|
+ item.setCuisineName(comboCuisine.getName());
|
|
|
+ }
|
|
|
+ return item;
|
|
|
+ }).collect(Collectors.toList());
|
|
|
+ vo.setComboItems(comboItems);
|
|
|
+ }
|
|
|
+
|
|
|
+ return vo;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<AvailableCouponVO> getAvailableCoupons(Integer storeId, Integer userId) {
|
|
|
+ log.info("获取可领取优惠券列表, storeId={}, userId={}", storeId, userId);
|
|
|
+
|
|
|
+ // 查询门店的优惠券
|
|
|
+ LambdaQueryWrapper<LifeDiscountCoupon> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ wrapper.eq(LifeDiscountCoupon::getStoreId, String.valueOf(storeId));
|
|
|
+ wrapper.eq(LifeDiscountCoupon::getDeleteFlag, 0);
|
|
|
+ wrapper.eq(LifeDiscountCoupon::getGetStatus, 1); // 开启领取
|
|
|
+ wrapper.gt(LifeDiscountCoupon::getSingleQty, 0); // 有库存
|
|
|
+ LocalDate now = LocalDate.now();
|
|
|
+ wrapper.le(LifeDiscountCoupon::getStartDate, now);
|
|
|
+ wrapper.ge(LifeDiscountCoupon::getEndDate, now);
|
|
|
+ wrapper.orderByDesc(LifeDiscountCoupon::getCreatedTime);
|
|
|
+
|
|
|
+ List<LifeDiscountCoupon> coupons = lifeDiscountCouponMapper.selectList(wrapper);
|
|
|
+
|
|
|
+ // 查询用户已领取的优惠券
|
|
|
+ Set<Integer> receivedCouponIds = new HashSet<>();
|
|
|
+ if (userId != null) {
|
|
|
+ LambdaQueryWrapper<LifeDiscountCouponUser> userWrapper = new LambdaQueryWrapper<>();
|
|
|
+ userWrapper.eq(LifeDiscountCouponUser::getUserId, userId);
|
|
|
+ List<LifeDiscountCouponUser> userCoupons = lifeDiscountCouponUserMapper.selectList(userWrapper);
|
|
|
+ receivedCouponIds = userCoupons.stream()
|
|
|
+ .map(LifeDiscountCouponUser::getCouponId)
|
|
|
+ .collect(Collectors.toSet());
|
|
|
+ }
|
|
|
+
|
|
|
+ final Set<Integer> finalReceivedCouponIds = receivedCouponIds;
|
|
|
+ return coupons.stream().map(coupon -> {
|
|
|
+ AvailableCouponVO vo = new AvailableCouponVO();
|
|
|
+ vo.setId(coupon.getId());
|
|
|
+ vo.setName(coupon.getName());
|
|
|
+ vo.setNominalValue(coupon.getNominalValue());
|
|
|
+ vo.setMinimumSpendingAmount(coupon.getMinimumSpendingAmount());
|
|
|
+ vo.setEndDate(coupon.getEndDate());
|
|
|
+ vo.setIsReceived(finalReceivedCouponIds.contains(coupon.getId()));
|
|
|
+ vo.setIsAvailable(coupon.getSingleQty() > 0 && coupon.getEndDate().isAfter(now) || coupon.getEndDate().isEqual(now));
|
|
|
+ return vo;
|
|
|
+ }).collect(Collectors.toList());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean receiveCoupon(Integer couponId, Integer userId) {
|
|
|
+ log.info("领取优惠券, couponId={}, userId={}", couponId, userId);
|
|
|
+
|
|
|
+ LifeDiscountCoupon coupon = lifeDiscountCouponMapper.selectById(couponId);
|
|
|
+ if (coupon == null) {
|
|
|
+ throw new RuntimeException("优惠券不存在");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查是否已领取
|
|
|
+ LambdaQueryWrapper<LifeDiscountCouponUser> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ wrapper.eq(LifeDiscountCouponUser::getUserId, userId);
|
|
|
+ wrapper.eq(LifeDiscountCouponUser::getCouponId, couponId);
|
|
|
+ LifeDiscountCouponUser existing = lifeDiscountCouponUserMapper.selectOne(wrapper);
|
|
|
+ if (existing != null) {
|
|
|
+ throw new RuntimeException("您已领取过该优惠券");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查库存
|
|
|
+ if (coupon.getSingleQty() == null || coupon.getSingleQty() <= 0) {
|
|
|
+ throw new RuntimeException("优惠券已领完");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建用户优惠券记录
|
|
|
+ LifeDiscountCouponUser userCoupon = new LifeDiscountCouponUser();
|
|
|
+ userCoupon.setUserId(userId);
|
|
|
+ userCoupon.setCouponId(couponId);
|
|
|
+ userCoupon.setReceiveTime(new Date());
|
|
|
+ userCoupon.setStatus(0); // 待使用
|
|
|
+ if (coupon.getSpecifiedDay() != null && !coupon.getSpecifiedDay().isEmpty()) {
|
|
|
+ try {
|
|
|
+ int days = Integer.parseInt(coupon.getSpecifiedDay());
|
|
|
+ LocalDate expirationDate = LocalDate.now().plusDays(days);
|
|
|
+ userCoupon.setExpirationTime(expirationDate);
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ userCoupon.setExpirationTime(coupon.getEndDate());
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ userCoupon.setExpirationTime(coupon.getEndDate());
|
|
|
+ }
|
|
|
+ lifeDiscountCouponUserMapper.insert(userCoupon);
|
|
|
+
|
|
|
+ // 更新库存
|
|
|
+ coupon.setSingleQty(coupon.getSingleQty() - 1);
|
|
|
+ lifeDiscountCouponMapper.updateById(coupon);
|
|
|
+
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public OrderConfirmVO getOrderConfirmInfo(Integer tableId, Integer dinerCount, Integer userId) {
|
|
|
+ log.info("获取订单确认页面信息, tableId={}, dinerCount={}, userId={}", tableId, dinerCount, userId);
|
|
|
+
|
|
|
+ // 获取点餐页面信息
|
|
|
+ DiningPageInfoVO pageInfo = getDiningPageInfo(tableId, dinerCount);
|
|
|
+
|
|
|
+ // 获取购物车
|
|
|
+ CartDTO cart = cartService.getCart(tableId);
|
|
|
+
|
|
|
+ // 检查订单锁定
|
|
|
+ Integer lockUserId = checkOrderLock(tableId);
|
|
|
+ boolean isLocked = lockUserId != null && !lockUserId.equals(userId);
|
|
|
+
|
|
|
+ OrderConfirmVO vo = new OrderConfirmVO();
|
|
|
+ vo.setStoreName(pageInfo.getStoreName());
|
|
|
+ vo.setTableNumber(pageInfo.getTableNumber());
|
|
|
+ vo.setDinerCount(dinerCount);
|
|
|
+ // 联系电话和备注由前端传入,这里不设置默认值
|
|
|
+ vo.setItems(cart.getItems());
|
|
|
+ vo.setTotalAmount(cart.getTotalAmount());
|
|
|
+ vo.setIsLocked(isLocked);
|
|
|
+ vo.setLockUserId(lockUserId);
|
|
|
+
|
|
|
+ // 计算餐具费(默认1元/人)
|
|
|
+ BigDecimal tablewareUnitPrice = new BigDecimal("1.00");
|
|
|
+ BigDecimal tablewareFee = BigDecimal.ZERO;
|
|
|
+ if (dinerCount != null && dinerCount > 0) {
|
|
|
+ tablewareFee = tablewareUnitPrice.multiply(BigDecimal.valueOf(dinerCount));
|
|
|
+ }
|
|
|
+ vo.setTablewareFee(tablewareFee);
|
|
|
+ vo.setTablewareUnitPrice(tablewareUnitPrice);
|
|
|
+
|
|
|
+ // 自动匹配最优惠的优惠券
|
|
|
+ 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<AvailableCouponVO> usableCoupons = availableCoupons.stream()
|
|
|
+ .filter(c -> c.getIsReceived() && c.getIsAvailable())
|
|
|
+ .filter(c -> totalWithTableware.compareTo(c.getMinimumSpendingAmount() != null ? c.getMinimumSpendingAmount() : BigDecimal.ZERO) >= 0)
|
|
|
+ .sorted((a, b) -> b.getNominalValue().compareTo(a.getNominalValue())) // 按优惠金额降序
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
+ if (!usableCoupons.isEmpty()) {
|
|
|
+ AvailableCouponVO bestCoupon = usableCoupons.get(0);
|
|
|
+ vo.setCouponId(bestCoupon.getId());
|
|
|
+ vo.setCouponName(bestCoupon.getName());
|
|
|
+ BigDecimal discountAmount = bestCoupon.getNominalValue();
|
|
|
+ BigDecimal totalAmount = cart.getTotalAmount().add(tablewareFee);
|
|
|
+ if (discountAmount.compareTo(totalAmount) > 0) {
|
|
|
+ discountAmount = totalAmount;
|
|
|
+ }
|
|
|
+ vo.setDiscountAmount(discountAmount);
|
|
|
+ vo.setPayAmount(totalAmount.subtract(discountAmount));
|
|
|
+ } else {
|
|
|
+ vo.setPayAmount(cart.getTotalAmount().add(tablewareFee));
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ vo.setPayAmount(cart.getTotalAmount().add(tablewareFee));
|
|
|
+ }
|
|
|
+ vo.setAvailableCoupons(availableCoupons);
|
|
|
+
|
|
|
+ return vo;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean lockOrder(Integer tableId, Integer userId) {
|
|
|
+ log.info("锁定订单, tableId={}, userId={}", tableId, userId);
|
|
|
+
|
|
|
+ String lockKey = ORDER_LOCK_KEY_PREFIX + tableId;
|
|
|
+ String existingLock = baseRedisService.getString(lockKey);
|
|
|
+ if (StringUtils.hasText(existingLock)) {
|
|
|
+ // 已锁定,检查是否是当前用户
|
|
|
+ if (existingLock.equals(String.valueOf(userId))) {
|
|
|
+ // 刷新过期时间
|
|
|
+ baseRedisService.setString(lockKey, existingLock, (long) ORDER_LOCK_EXPIRE_SECONDS);
|
|
|
+ return true;
|
|
|
+ } else {
|
|
|
+ // 被其他用户锁定
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 设置锁定
|
|
|
+ baseRedisService.setString(lockKey, String.valueOf(userId), (long) ORDER_LOCK_EXPIRE_SECONDS);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void unlockOrder(Integer tableId, Integer userId) {
|
|
|
+ log.info("解锁订单, tableId={}, userId={}", tableId, userId);
|
|
|
+
|
|
|
+ String lockKey = ORDER_LOCK_KEY_PREFIX + tableId;
|
|
|
+ String existingLock = baseRedisService.getString(lockKey);
|
|
|
+ if (StringUtils.hasText(existingLock) && existingLock.equals(String.valueOf(userId))) {
|
|
|
+ baseRedisService.delete(lockKey);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Integer checkOrderLock(Integer tableId) {
|
|
|
+ String lockKey = ORDER_LOCK_KEY_PREFIX + tableId;
|
|
|
+ String lockUserId = baseRedisService.getString(lockKey);
|
|
|
+ if (StringUtils.hasText(lockUserId)) {
|
|
|
+ try {
|
|
|
+ return Integer.parseInt(lockUserId);
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 转换为菜品列表VO
|
|
|
+ */
|
|
|
+ private List<CuisineListVO> convertToCuisineListVO(List<StoreCuisine> cuisines, Integer tableId) {
|
|
|
+ // 获取购物车
|
|
|
+ CartDTO cart = cartService.getCart(tableId);
|
|
|
+ Map<Integer, Integer> cartQuantityMap = new HashMap<>();
|
|
|
+ if (cart.getItems() != null) {
|
|
|
+ cartQuantityMap = cart.getItems().stream()
|
|
|
+ .collect(Collectors.toMap(CartItemDTO::getCuisineId, CartItemDTO::getQuantity));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 批量查询月售数量
|
|
|
+ Map<Integer, Integer> monthlySalesMap = new HashMap<>();
|
|
|
+ for (StoreCuisine cuisine : cuisines) {
|
|
|
+ monthlySalesMap.put(cuisine.getId(), getMonthlySales(cuisine.getId()));
|
|
|
+ }
|
|
|
+
|
|
|
+ final Map<Integer, Integer> finalCartQuantityMap = cartQuantityMap;
|
|
|
+ final Map<Integer, Integer> finalMonthlySalesMap = monthlySalesMap;
|
|
|
+
|
|
|
+ return cuisines.stream().map(cuisine -> {
|
|
|
+ CuisineListVO vo = new CuisineListVO();
|
|
|
+ BeanUtils.copyProperties(cuisine, vo);
|
|
|
+ vo.setPrice(cuisine.getTotalPrice());
|
|
|
+
|
|
|
+ // 处理首张图片
|
|
|
+ if (StringUtils.hasText(cuisine.getImages())) {
|
|
|
+ String[] images = cuisine.getImages().split(",");
|
|
|
+ vo.setFirstImage(images.length > 0 ? images[0] : null);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 设置购物车数量
|
|
|
+ vo.setCartQuantity(finalCartQuantityMap.getOrDefault(cuisine.getId(), 0));
|
|
|
+
|
|
|
+ // 设置月售数量
|
|
|
+ vo.setMonthlySales(finalMonthlySalesMap.getOrDefault(cuisine.getId(), 0));
|
|
|
+
|
|
|
+ return vo;
|
|
|
+ }).collect(Collectors.toList());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取菜品月售数量(当月1日至当前日期)
|
|
|
+ */
|
|
|
+ private Integer getMonthlySales(Integer cuisineId) {
|
|
|
+ try {
|
|
|
+ // 计算当月1日
|
|
|
+ Calendar calendar = Calendar.getInstance();
|
|
|
+ calendar.set(Calendar.DAY_OF_MONTH, 1);
|
|
|
+ calendar.set(Calendar.HOUR_OF_DAY, 0);
|
|
|
+ calendar.set(Calendar.MINUTE, 0);
|
|
|
+ calendar.set(Calendar.SECOND, 0);
|
|
|
+ calendar.set(Calendar.MILLISECOND, 0);
|
|
|
+ Date monthStart = calendar.getTime();
|
|
|
+
|
|
|
+ // 查询订单明细中该菜品的销售数量
|
|
|
+ LambdaQueryWrapper<StoreOrderDetail> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ wrapper.eq(StoreOrderDetail::getCuisineId, cuisineId);
|
|
|
+ wrapper.eq(StoreOrderDetail::getDeleteFlag, 0);
|
|
|
+ wrapper.ge(StoreOrderDetail::getCreatedTime, monthStart);
|
|
|
+ // 只统计已支付的订单
|
|
|
+ wrapper.inSql(StoreOrderDetail::getOrderId,
|
|
|
+ "SELECT id FROM store_order WHERE pay_status = 1 AND delete_flag = 0");
|
|
|
+
|
|
|
+ List<StoreOrderDetail> details = storeOrderDetailMapper.selectList(wrapper);
|
|
|
+ return details.stream().mapToInt(StoreOrderDetail::getQuantity).sum();
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("获取月售数量失败, cuisineId={}", cuisineId, e);
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public shop.alien.entity.store.vo.OrderSettlementVO getOrderSettlementInfo(Integer orderId, Integer userId) {
|
|
|
+ log.info("获取订单结算确认页面信息, orderId={}, userId={}", orderId, userId);
|
|
|
+
|
|
|
+ // 查询订单
|
|
|
+ shop.alien.entity.store.StoreOrder order = storeOrderService.getOrderById(orderId);
|
|
|
+ if (order == null) {
|
|
|
+ throw new RuntimeException("订单不存在");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 查询订单明细
|
|
|
+ LambdaQueryWrapper<shop.alien.entity.store.StoreOrderDetail> detailWrapper = new LambdaQueryWrapper<>();
|
|
|
+ detailWrapper.eq(shop.alien.entity.store.StoreOrderDetail::getOrderId, orderId);
|
|
|
+ detailWrapper.eq(shop.alien.entity.store.StoreOrderDetail::getDeleteFlag, 0);
|
|
|
+ detailWrapper.orderByDesc(shop.alien.entity.store.StoreOrderDetail::getCreatedTime);
|
|
|
+ List<shop.alien.entity.store.StoreOrderDetail> details = storeOrderDetailMapper.selectList(detailWrapper);
|
|
|
+
|
|
|
+ // 转换为CartItemDTO
|
|
|
+ List<shop.alien.entity.store.dto.CartItemDTO> items = details.stream().map(detail -> {
|
|
|
+ shop.alien.entity.store.dto.CartItemDTO item = new shop.alien.entity.store.dto.CartItemDTO();
|
|
|
+ item.setCuisineId(detail.getCuisineId());
|
|
|
+ item.setCuisineName(detail.getCuisineName());
|
|
|
+ item.setCuisineType(detail.getCuisineType());
|
|
|
+ item.setCuisineImage(detail.getCuisineImage());
|
|
|
+ item.setUnitPrice(detail.getUnitPrice());
|
|
|
+ item.setQuantity(detail.getQuantity());
|
|
|
+ item.setSubtotalAmount(detail.getSubtotalAmount());
|
|
|
+ item.setAddUserId(detail.getAddUserId());
|
|
|
+ item.setAddUserPhone(detail.getAddUserPhone());
|
|
|
+ item.setRemark(detail.getRemark());
|
|
|
+ return item;
|
|
|
+ }).collect(Collectors.toList());
|
|
|
+
|
|
|
+ // 查询门店信息
|
|
|
+ shop.alien.entity.store.StoreInfo storeInfo = storeInfoMapper.selectById(order.getStoreId());
|
|
|
+
|
|
|
+ // 检查结算锁定
|
|
|
+ String lockKey = "settlement:lock:order:" + orderId;
|
|
|
+ String lockUserIdStr = baseRedisService.getString(lockKey);
|
|
|
+ Integer lockUserId = null;
|
|
|
+ boolean isLocked = false;
|
|
|
+ if (StringUtils.hasText(lockUserIdStr)) {
|
|
|
+ try {
|
|
|
+ lockUserId = Integer.parseInt(lockUserIdStr);
|
|
|
+ isLocked = !lockUserId.equals(userId);
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ // ignore
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ shop.alien.entity.store.vo.OrderSettlementVO vo = new shop.alien.entity.store.vo.OrderSettlementVO();
|
|
|
+ vo.setOrderId(order.getId());
|
|
|
+ vo.setOrderNo(order.getOrderNo());
|
|
|
+ vo.setStoreName(storeInfo != null ? storeInfo.getStoreName() : null);
|
|
|
+ vo.setTableNumber(order.getTableNumber());
|
|
|
+ vo.setDinerCount(order.getDinerCount());
|
|
|
+ vo.setContactPhone(order.getContactPhone());
|
|
|
+ vo.setRemark(order.getRemark());
|
|
|
+ vo.setItems(items);
|
|
|
+ vo.setTotalAmount(order.getTotalAmount());
|
|
|
+ vo.setTablewareFee(order.getTablewareFee() != null ? order.getTablewareFee() : BigDecimal.ZERO);
|
|
|
+ vo.setCouponId(order.getCouponId());
|
|
|
+ vo.setDiscountAmount(order.getDiscountAmount() != null ? order.getDiscountAmount() : BigDecimal.ZERO);
|
|
|
+ vo.setPayAmount(order.getPayAmount());
|
|
|
+ vo.setIsLocked(isLocked);
|
|
|
+ vo.setLockUserId(lockUserId);
|
|
|
+
|
|
|
+ // 查询优惠券名称
|
|
|
+ if (order.getCouponId() != null) {
|
|
|
+ shop.alien.entity.store.LifeDiscountCoupon coupon = lifeDiscountCouponMapper.selectById(order.getCouponId());
|
|
|
+ if (coupon != null) {
|
|
|
+ vo.setCouponName(coupon.getName());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return vo;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean lockSettlement(Integer orderId, Integer userId) {
|
|
|
+ log.info("锁定订单结算, orderId={}, userId={}", orderId, userId);
|
|
|
+
|
|
|
+ String lockKey = "settlement:lock:order:" + orderId;
|
|
|
+ String existingLock = baseRedisService.getString(lockKey);
|
|
|
+ if (StringUtils.hasText(existingLock)) {
|
|
|
+ if (existingLock.equals(String.valueOf(userId))) {
|
|
|
+ baseRedisService.setString(lockKey, existingLock, (long) ORDER_LOCK_EXPIRE_SECONDS);
|
|
|
+ return true;
|
|
|
+ } else {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ baseRedisService.setString(lockKey, String.valueOf(userId), (long) ORDER_LOCK_EXPIRE_SECONDS);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void unlockSettlement(Integer orderId, Integer userId) {
|
|
|
+ log.info("解锁订单结算, orderId={}, userId={}", orderId, userId);
|
|
|
+
|
|
|
+ String lockKey = "settlement:lock:order:" + orderId;
|
|
|
+ String existingLock = baseRedisService.getString(lockKey);
|
|
|
+ if (StringUtils.hasText(existingLock) && existingLock.equals(String.valueOf(userId))) {
|
|
|
+ baseRedisService.delete(lockKey);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|