|
|
@@ -0,0 +1,292 @@
|
|
|
+package shop.alien.store.service.impl;
|
|
|
+
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|
|
+import lombok.RequiredArgsConstructor;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.transaction.annotation.Transactional;
|
|
|
+import org.springframework.util.StringUtils;
|
|
|
+import shop.alien.entity.store.StoreCuisine;
|
|
|
+import shop.alien.entity.store.StoreOrder;
|
|
|
+import shop.alien.entity.store.StoreOrderDetail;
|
|
|
+import shop.alien.entity.store.StoreTable;
|
|
|
+import shop.alien.entity.store.dto.StoreBookingPlaceOrderDTO;
|
|
|
+import shop.alien.entity.store.dto.StoreBookingPlaceOrderItemDTO;
|
|
|
+import shop.alien.entity.store.vo.StoreBookingPlaceOrderResultVo;
|
|
|
+import shop.alien.mapper.StoreCuisineMapper;
|
|
|
+import shop.alien.mapper.StoreOrderDetailMapper;
|
|
|
+import shop.alien.mapper.StoreOrderMapper;
|
|
|
+import shop.alien.mapper.StoreTableMapper;
|
|
|
+import shop.alien.store.service.StoreBookingOrderService;
|
|
|
+import shop.alien.util.common.JwtUtil;
|
|
|
+import com.alibaba.fastjson.JSONObject;
|
|
|
+
|
|
|
+import java.math.BigDecimal;
|
|
|
+import java.math.RoundingMode;
|
|
|
+import java.text.SimpleDateFormat;
|
|
|
+import java.util.Date;
|
|
|
+import java.util.List;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 预订下单:不依赖 alien-dining,直接写入订单主表与明细表。
|
|
|
+ * <p>
|
|
|
+ * 同一桌已存在待支付订单时,视为「加餐」:仅追加明细(is_add_dish=1),累加总金额,不新建订单号。
|
|
|
+ * 同一待支付订单可多次加餐(多次调用本接口),不限次数。
|
|
|
+ */
|
|
|
+@Slf4j
|
|
|
+@Service
|
|
|
+@RequiredArgsConstructor
|
|
|
+@Transactional(rollbackFor = Exception.class)
|
|
|
+public class StoreBookingOrderServiceImpl extends ServiceImpl<StoreOrderMapper, StoreOrder> implements StoreBookingOrderService {
|
|
|
+
|
|
|
+ private final StoreOrderDetailMapper storeOrderDetailMapper;
|
|
|
+ private final StoreTableMapper storeTableMapper;
|
|
|
+ private final StoreCuisineMapper storeCuisineMapper;
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public StoreBookingPlaceOrderResultVo placeOrder(StoreBookingPlaceOrderDTO dto) {
|
|
|
+ if (dto.getDiningGender() != null && dto.getDiningGender() != 1 && dto.getDiningGender() != 2) {
|
|
|
+ throw new IllegalArgumentException("联系人性别仅支持:1男 2女");
|
|
|
+ }
|
|
|
+
|
|
|
+ StoreTable table = storeTableMapper.selectById(dto.getTableId());
|
|
|
+ if (table == null || table.getStoreId() == null || !table.getStoreId().equals(dto.getStoreId())) {
|
|
|
+ throw new IllegalArgumentException("桌号不存在或与门店不匹配");
|
|
|
+ }
|
|
|
+
|
|
|
+ StoreOrder pendingOrder = resolvePendingOrder(dto, table);
|
|
|
+
|
|
|
+ Integer userId = null;
|
|
|
+ String userPhone = null;
|
|
|
+ try {
|
|
|
+ JSONObject jwt = JwtUtil.getCurrentUserInfo();
|
|
|
+ if (jwt != null) {
|
|
|
+ userId = jwt.getInteger("userId");
|
|
|
+ userPhone = jwt.getString("phone");
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.debug("placeOrder: 未解析到登录用户,按匿名下单处理");
|
|
|
+ }
|
|
|
+
|
|
|
+ Date now = new Date();
|
|
|
+ BigDecimal linesSubtotal = validateAndSumLines(dto);
|
|
|
+
|
|
|
+ if (pendingOrder != null) {
|
|
|
+ return addDishesToExistingOrder(dto, table, pendingOrder, linesSubtotal, userId, userPhone, now);
|
|
|
+ }
|
|
|
+
|
|
|
+ String orderNo = generateOrderNo();
|
|
|
+
|
|
|
+ StoreOrder order = new StoreOrder();
|
|
|
+ order.setOrderNo(orderNo);
|
|
|
+ order.setStoreId(dto.getStoreId());
|
|
|
+ order.setTableId(dto.getTableId());
|
|
|
+ order.setTableNumber(table.getTableNumber());
|
|
|
+ order.setUserReservationId(dto.getUserReservationId());
|
|
|
+ order.setDinerCount(dto.getDinerCount());
|
|
|
+ order.setContactPhone(StringUtils.hasText(dto.getContactPhone()) ? dto.getContactPhone().trim() : null);
|
|
|
+ order.setDiningContactName(StringUtils.hasText(dto.getDiningContactName()) ? dto.getDiningContactName().trim() : null);
|
|
|
+ order.setDiningGender(dto.getDiningGender());
|
|
|
+ order.setPayUserId(userId);
|
|
|
+ order.setPayUserPhone(userPhone);
|
|
|
+ order.setOrderStatus(0);
|
|
|
+ order.setTotalAmount(linesSubtotal);
|
|
|
+ order.setDiscountAmount(BigDecimal.ZERO);
|
|
|
+ order.setTablewareFee(BigDecimal.ZERO);
|
|
|
+ order.setPayAmount(linesSubtotal);
|
|
|
+ order.setPayStatus(0);
|
|
|
+ order.setRemark(StringUtils.hasText(dto.getRemark()) ? dto.getRemark().trim() : null);
|
|
|
+ order.setCreatedUserId(userId);
|
|
|
+ order.setUpdatedUserId(userId);
|
|
|
+ order.setCreatedTime(now);
|
|
|
+ order.setUpdatedTime(now);
|
|
|
+ this.save(order);
|
|
|
+
|
|
|
+ insertDetailRows(order.getId(), orderNo, dto.getItems(), userId, userPhone, now, false);
|
|
|
+
|
|
|
+ StoreTable tablePatch = new StoreTable();
|
|
|
+ tablePatch.setId(table.getId());
|
|
|
+ tablePatch.setCurrentOrderId(order.getId());
|
|
|
+ tablePatch.setStatus(1);
|
|
|
+ tablePatch.setDinerCount(dto.getDinerCount());
|
|
|
+ storeTableMapper.updateById(tablePatch);
|
|
|
+
|
|
|
+ StoreBookingPlaceOrderResultVo vo = new StoreBookingPlaceOrderResultVo();
|
|
|
+ vo.setOrderId(order.getId());
|
|
|
+ vo.setOrderNo(orderNo);
|
|
|
+ vo.setTotalAmount(linesSubtotal);
|
|
|
+ vo.setAddDish(false);
|
|
|
+ log.info("预订首单成功 orderId={} orderNo={} tableId={} storeId={}", order.getId(), orderNo, dto.getTableId(), dto.getStoreId());
|
|
|
+ return vo;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 解析本桌待支付订单,用于首单/多次加餐。
|
|
|
+ * 优先级:显式 targetOrderId → 桌台 current_order_id → 按桌+店查询待支付(唯一一笔)。
|
|
|
+ */
|
|
|
+ private StoreOrder resolvePendingOrder(StoreBookingPlaceOrderDTO dto, StoreTable table) {
|
|
|
+ if (dto.getTargetOrderId() != null) {
|
|
|
+ StoreOrder o = this.getById(dto.getTargetOrderId());
|
|
|
+ if (!isPendingOrderForTable(o, dto.getStoreId(), dto.getTableId())) {
|
|
|
+ throw new IllegalArgumentException("目标订单不存在、非待支付或与门店/桌号不符,无法加餐");
|
|
|
+ }
|
|
|
+ return o;
|
|
|
+ }
|
|
|
+ Integer curId = table.getCurrentOrderId();
|
|
|
+ if (curId != null) {
|
|
|
+ StoreOrder byPointer = this.getById(curId);
|
|
|
+ if (isPendingOrderForTable(byPointer, dto.getStoreId(), dto.getTableId())) {
|
|
|
+ return byPointer;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ LambdaQueryWrapper<StoreOrder> q = new LambdaQueryWrapper<>();
|
|
|
+ q.eq(StoreOrder::getStoreId, dto.getStoreId())
|
|
|
+ .eq(StoreOrder::getTableId, dto.getTableId())
|
|
|
+ .eq(StoreOrder::getDeleteFlag, 0)
|
|
|
+ .eq(StoreOrder::getOrderStatus, 0)
|
|
|
+ .eq(StoreOrder::getPayStatus, 0)
|
|
|
+ .orderByDesc(StoreOrder::getId);
|
|
|
+ List<StoreOrder> list = this.list(q);
|
|
|
+ if (list.size() > 1) {
|
|
|
+ throw new IllegalStateException("该桌存在多笔待支付订单,数据异常,请联系管理员");
|
|
|
+ }
|
|
|
+ return list.isEmpty() ? null : list.get(0);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static boolean isPendingOrderForTable(StoreOrder o, Integer storeId, Integer tableId) {
|
|
|
+ if (o == null) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (o.getDeleteFlag() != null && o.getDeleteFlag() != 0) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (o.getOrderStatus() == null || o.getOrderStatus() != 0) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (o.getPayStatus() == null || o.getPayStatus() != 0) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return storeId.equals(o.getStoreId()) && tableId.equals(o.getTableId());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 加餐:在已有待支付订单上追加明细,累加金额;桌台置为加餐(3),与堂食约定一致。
|
|
|
+ */
|
|
|
+ private StoreBookingPlaceOrderResultVo addDishesToExistingOrder(
|
|
|
+ StoreBookingPlaceOrderDTO dto,
|
|
|
+ StoreTable table,
|
|
|
+ StoreOrder existing,
|
|
|
+ BigDecimal deltaAmount,
|
|
|
+ Integer userId,
|
|
|
+ String userPhone,
|
|
|
+ Date now) {
|
|
|
+ if (!dto.getStoreId().equals(existing.getStoreId()) || !dto.getTableId().equals(existing.getTableId())) {
|
|
|
+ throw new IllegalArgumentException("待支付订单与当前门店/桌号不一致,无法加餐");
|
|
|
+ }
|
|
|
+ BigDecimal oldTotal = existing.getTotalAmount() != null ? existing.getTotalAmount() : BigDecimal.ZERO;
|
|
|
+ BigDecimal oldPay = existing.getPayAmount() != null ? existing.getPayAmount() : BigDecimal.ZERO;
|
|
|
+ BigDecimal newTotal = oldTotal.add(deltaAmount).setScale(2, RoundingMode.HALF_UP);
|
|
|
+ BigDecimal newPay = oldPay.add(deltaAmount).setScale(2, RoundingMode.HALF_UP);
|
|
|
+
|
|
|
+ existing.setTotalAmount(newTotal);
|
|
|
+ existing.setPayAmount(newPay);
|
|
|
+ existing.setDinerCount(dto.getDinerCount());
|
|
|
+ if (StringUtils.hasText(dto.getContactPhone())) {
|
|
|
+ existing.setContactPhone(dto.getContactPhone().trim());
|
|
|
+ }
|
|
|
+ if (StringUtils.hasText(dto.getDiningContactName())) {
|
|
|
+ existing.setDiningContactName(dto.getDiningContactName().trim());
|
|
|
+ }
|
|
|
+ if (dto.getDiningGender() != null) {
|
|
|
+ existing.setDiningGender(dto.getDiningGender());
|
|
|
+ }
|
|
|
+ if (StringUtils.hasText(dto.getRemark())) {
|
|
|
+ existing.setRemark(dto.getRemark().trim());
|
|
|
+ }
|
|
|
+ if (existing.getUserReservationId() == null && dto.getUserReservationId() != null) {
|
|
|
+ existing.setUserReservationId(dto.getUserReservationId());
|
|
|
+ }
|
|
|
+ existing.setUpdatedUserId(userId);
|
|
|
+ existing.setUpdatedTime(now);
|
|
|
+ this.updateById(existing);
|
|
|
+
|
|
|
+ insertDetailRows(existing.getId(), existing.getOrderNo(), dto.getItems(), userId, userPhone, now, true);
|
|
|
+
|
|
|
+ StoreTable tablePatch = new StoreTable();
|
|
|
+ tablePatch.setId(table.getId());
|
|
|
+ tablePatch.setCurrentOrderId(existing.getId());
|
|
|
+ tablePatch.setStatus(3);
|
|
|
+ tablePatch.setDinerCount(dto.getDinerCount());
|
|
|
+ storeTableMapper.updateById(tablePatch);
|
|
|
+
|
|
|
+ StoreBookingPlaceOrderResultVo vo = new StoreBookingPlaceOrderResultVo();
|
|
|
+ vo.setOrderId(existing.getId());
|
|
|
+ vo.setOrderNo(existing.getOrderNo());
|
|
|
+ vo.setTotalAmount(newTotal);
|
|
|
+ vo.setAddDish(true);
|
|
|
+ log.info("预订加餐成功 orderId={} orderNo={} delta={} tableId={}", existing.getId(), existing.getOrderNo(), deltaAmount, dto.getTableId());
|
|
|
+ return vo;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 校验菜品归属并汇总本请求明细金额 */
|
|
|
+ private BigDecimal validateAndSumLines(StoreBookingPlaceOrderDTO dto) {
|
|
|
+ BigDecimal sum = BigDecimal.ZERO;
|
|
|
+ for (StoreBookingPlaceOrderItemDTO line : dto.getItems()) {
|
|
|
+ StoreCuisine cuisine = storeCuisineMapper.selectById(line.getCuisineId());
|
|
|
+ if (cuisine == null || cuisine.getStoreId() == null || !cuisine.getStoreId().equals(dto.getStoreId())) {
|
|
|
+ throw new IllegalArgumentException("菜品不存在或不属于该门店,cuisineId=" + line.getCuisineId());
|
|
|
+ }
|
|
|
+ BigDecimal unit = line.getUnitPrice() != null ? line.getUnitPrice() : BigDecimal.ZERO;
|
|
|
+ BigDecimal sub = unit.multiply(BigDecimal.valueOf(line.getQuantity())).setScale(2, RoundingMode.HALF_UP);
|
|
|
+ sum = sum.add(sub);
|
|
|
+ }
|
|
|
+ return sum;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void insertDetailRows(
|
|
|
+ Integer orderId,
|
|
|
+ String orderNo,
|
|
|
+ List<StoreBookingPlaceOrderItemDTO> lines,
|
|
|
+ Integer userId,
|
|
|
+ String userPhone,
|
|
|
+ Date now,
|
|
|
+ boolean addDish) {
|
|
|
+ for (StoreBookingPlaceOrderItemDTO line : lines) {
|
|
|
+ BigDecimal unit = line.getUnitPrice() != null ? line.getUnitPrice() : BigDecimal.ZERO;
|
|
|
+ int qty = line.getQuantity() != null ? line.getQuantity() : 1;
|
|
|
+ BigDecimal sub = unit.multiply(BigDecimal.valueOf(qty)).setScale(2, RoundingMode.HALF_UP);
|
|
|
+ Integer cuisineType = line.getCuisineType() != null ? line.getCuisineType() : 1;
|
|
|
+
|
|
|
+ StoreOrderDetail detail = new StoreOrderDetail();
|
|
|
+ detail.setOrderId(orderId);
|
|
|
+ detail.setOrderNo(orderNo);
|
|
|
+ detail.setCuisineId(line.getCuisineId());
|
|
|
+ detail.setCuisineName(line.getCuisineName());
|
|
|
+ detail.setCuisineType(cuisineType);
|
|
|
+ detail.setCuisineImage(line.getCuisineImage());
|
|
|
+ detail.setUnitPrice(unit);
|
|
|
+ detail.setQuantity(qty);
|
|
|
+ detail.setSubtotalAmount(sub);
|
|
|
+ detail.setAddUserId(userId);
|
|
|
+ detail.setAddUserPhone(userPhone);
|
|
|
+ detail.setIsAddDish(addDish ? 1 : 0);
|
|
|
+ if (addDish) {
|
|
|
+ detail.setAddDishTime(now);
|
|
|
+ }
|
|
|
+ detail.setCreatedUserId(userId);
|
|
|
+ detail.setUpdatedUserId(userId);
|
|
|
+ detail.setCreatedTime(now);
|
|
|
+ detail.setUpdatedTime(now);
|
|
|
+ storeOrderDetailMapper.insert(detail);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 与 alien-dining 订单号规则保持一致 */
|
|
|
+ private static String generateOrderNo() {
|
|
|
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
|
|
|
+ String timestamp = sdf.format(new Date());
|
|
|
+ String random = String.valueOf((int) (Math.random() * 10000));
|
|
|
+ return "ORD" + timestamp + String.format("%04d", Integer.parseInt(random));
|
|
|
+ }
|
|
|
+}
|