package com.ruoyi.app.order; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.ruoyi.app.order.dto.OrderCreateInput; import com.ruoyi.app.order.dto.OrderCreatItem; import com.ruoyi.app.order.dto.OrderPushBodyDto; import com.ruoyi.app.utils.DateUtil; import com.ruoyi.app.utils.OperatingUtil; import com.ruoyi.app.utils.PayPush; import com.ruoyi.app.utils.event.PushEventService; import com.ruoyi.common.annotation.Anonymous; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.utils.MessageUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.framework.manager.AsyncManager; import com.ruoyi.system.domain.*; import com.ruoyi.system.mapper.PosOrderPromotionMapper; import com.ruoyi.system.service.*; import com.ruoyi.system.utils.Auth; import com.ruoyi.system.utils.GetArea; import com.ruoyi.system.utils.JwtUtil; import com.ruoyi.system.utils.OrderLogHelper; import com.ruoyi.system.dto.PromotionCalcResponse; import com.ruoyi.system.domain.dto.ApplyInvoiceDto; import jakarta.validation.Valid; import org.springframework.web.bind.annotation.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.annotation.Transactional; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; import java.util.Optional; import java.util.TimerTask; import java.util.stream.Collectors; /** * 用户订单Controller * * @author ruoyi * @date 2020-04-01 */ @RestController @RequestMapping("/system/userOrder") public class UserOrderController extends BaseController { @Autowired private IOrderParentService orderParentService; @Autowired private IPosOrderService posOrderService; @Autowired private IPosStoreService posStoreService; @Autowired private IInfoAddressService infoAddressService; @Autowired private IOperatingHoursService operatingHoursService; @Autowired private IUserFootprintService userFootprintService; @Autowired private IInfoUserService infoUserService; @Autowired private PushEventService pushEventService; @Autowired private OrderLogHelper orderLogHelper; @Autowired private OrderPromotionHelper orderPromotionHelper; @Autowired private PosOrderPromotionMapper posOrderPromotionMapper; @Autowired private OrderInvoiceService orderInvoiceService; /** * 申请电子发票(订单完成后,客户主动申请:B2C 邮箱 / B2B 统编 / 载具) */ @PostMapping("/applyInvoice") @Auth @Anonymous public AjaxResult applyInvoice(@RequestHeader String token, @RequestBody @Valid ApplyInvoiceDto dto) { Long userId = Long.valueOf(new JwtUtil().getusid(token)); return AjaxResult.success(orderInvoiceService.applyInvoice(dto, userId)); } /** * 查询订单电子发票(状态 / 发票号 / 凭证);门店不可开票时 invoiceStatus=null */ @GetMapping("/getInvoice") @Auth @Anonymous public AjaxResult getInvoice(@RequestHeader String token, @RequestParam Long orderId) { Long userId = Long.valueOf(new JwtUtil().getusid(token)); return AjaxResult.success(orderInvoiceService.getInvoice(orderId, userId)); } /** * 捐赠机构列表(客户端选择器,仅 enabled=1,按机构名/简称/捐赠码搜索;分页) */ @GetMapping("/loveOrg/list") @Auth @Anonymous public TableDataInfo loveOrgList(@RequestHeader String token, @RequestParam(required = false) String keyword) { startPage(); return getDataTable(orderInvoiceService.listLoveOrg(keyword)); } /** * 创建订单 */ @PostMapping("/createOrder") @Auth @Anonymous @Transactional(rollbackFor = Exception.class) public AjaxResult createOrder(@RequestHeader String token, @RequestBody OrderCreateInput input) { JwtUtil jwtUtil = new JwtUtil(); String id = jwtUtil.getusid(token); String shdz=null; if(input.getType()==0){ InfoAddress usadd = infoAddressService.getById(input.getShdzId()); if (usadd == null) { throw new ServiceException(MessageUtils.message("no.address.not.exist")); } shdz= JSON.toJSONString(usadd); } createOrderParent(input,Long.valueOf(id),shdz); createOrderChild(input,Long.valueOf(id),Long.valueOf(id),shdz); OrderParent result = orderParentService.getOne(new LambdaQueryWrapper().eq(OrderParent::getDdId, input.getDdId().toString())); System.out.println("创建返回信息:"+result); return super.success(result); } //创建父订单 private void createOrderParent(OrderCreateInput input,Long UserId,String shdz){ OrderParent orderParent = new OrderParent(); orderParent.setUserId(UserId); orderParent.setOrderStatus(0L); orderParent.setTotalAmount(input.getTotalAmount()); orderParent.setActualPayAmount(input.getActualPayAmount()); orderParent.setPaymentMethod(input.getPaymentMethod()); // orderParent.setPaymentTime(new Date()); orderParent.setCreateTime(new Date()); // orderParent.setUpdateTime(new Date()); orderParent.setDdId(input.getDdId().toString()); orderParent.setTableId(input.getTableId()); orderParent.setShdzId(input.getShdzId()); orderParent.setShAddress(shdz); orderParent.setType(input.getType()); orderParent.setRemarks(input.getRemarks()); orderParentService.save(orderParent); } //创建子订单 private void createOrderChild(OrderCreateInput input,Long orderParentId,Long userId,String shdz){ // 检查items是否为空 if (input.getItems() == null || input.getItems().isEmpty()) { throw new ServiceException(MessageUtils.message("订单商品不能为空")); } String ddId = input.getDdId().toString(); List mdIdList = input.getItems().stream().map(OrderCreatItem::getMdId).collect(Collectors.toList()); // 将Long类型的门店ID转换为Integer类型用于查询(因为PosStore.getId()返回Integer) List mdIdIntegerList = mdIdList.stream().map(Long::intValue).collect(Collectors.toList()); List storeList = posStoreService.list(new LambdaQueryWrapper().in(PosStore::getId, mdIdIntegerList)); LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.in(OperatingHours::getMdId, mdIdList); List hourslist = operatingHoursService.list(wrapper); boolean isMultiStore = input.getItems().size() > 1; // 循环items,为每个item创建一条PosOrder int index = 0; for (OrderCreatItem item : input.getItems()) { // 检查门店是否开放 checkStoreOpen(storeList,hourslist,item); index++; // 单店铺:子订单号与父订单一致;多店铺:子订单号追加序号 String subddId = isMultiStore ? ddId + String.format("%03d", index) : ddId; PosOrder posOrder = new PosOrder(); posOrder.setIsDisplay(false); handleIsDisplay(posOrder); posOrder.setShdzId(input.getShdzId()); // 设置子订单的基本信息 posOrder.setDdId(subddId); posOrder.setParentDdId(input.getDdId().toString()); // 设置父订单ID posOrder.setCretim(new Date()); // 根据item设置订单信息 posOrder.setShId(item.getShId()); posOrder.setMdId(item.getMdId()); posOrder.setShdzId(input.getShdzId()); posOrder.setUserId(userId); posOrder.setAmount(item.getAmount()); posOrder.setRemarks(item.getRemarks()); posOrder.setType(item.getType()); //外卖设置配送状态为 if(0L==posOrder.getType()){ posOrder.setDeliveryStatus(0L); } posOrder.setDelryTime(item.getDelryTime()); posOrder.setFood(JSON.toJSONString(item.getFood())); if("1".equals(input.getPaymentMethod()) || "2".equals(input.getPaymentMethod())){ posOrder.setCollectPayment("1"); } // 设置支付相关信息(从父订单继承) posOrder.setPayType(input.getPaymentMethod()); //设置桌号(从父订单继承) posOrder.setTableId(input.getTableId()); posOrder.setTableNo(input.getTableNo()); posOrder.setLogo(item.getLogo()); posOrder.setPosName(item.getPosName()); posOrder.setJvli(item.getJvli()); posOrder.setFreight(item.getFreight()); posOrder.setShdzId(input.getShdzId()); posOrder.setShAddress(shdz); posOrder.setFoodAmount(item.getFoodAmount()); posOrder.setPickUpTime(input.getPickUpTime()); posOrder.setReservePhone(input.getReservePhone()); // 新状态字段初始化 // 堂食订单(type=2)自动接单,跳过商家确认 if (item.getType() != null && item.getType() == 2) { posOrder.setState(1L); } else { posOrder.setState(0L); } posOrder.setAfterSaleStatus(0L); posOrder.setDeliveryStatus(null); // 外送到付(payType=1):payStatus=1;其他:payStatus=0 if (item.getType() != null && item.getType() == 0 && "1".equals(input.getPaymentMethod())) { posOrder.setPayStatus(1L); } else { posOrder.setPayStatus(0L); } Optional storeOptional = storeList.stream() .filter(x -> item.getMdId().equals(Long.valueOf(x.getId()))) .findFirst(); if (storeOptional.isEmpty()) { throw new ServiceException(MessageUtils.message("no.mendian.not.exist")); } PosStore store = storeOptional.get(); posOrder.setLongitude(store.getLongitude()); posOrder.setLatitude(store.getLatitude()); //设置订单分类 storeList.stream().filter(s -> Long.valueOf(s.getId()).equals(item.getMdId())).findFirst().ifPresent(st -> { posOrder.setOrderCategory(String.valueOf(st.getType())); }); // 保存子订单 // --- 促销:算价 + 校验 + 回填优惠字段(save之前) --- PromotionCalcResponse calc = orderPromotionHelper.calcPromotion(item, userId); orderPromotionHelper.validatePromotionAmount(calc, item.getPromoReduceAmount()); orderPromotionHelper.fillPromotionFields(posOrder, calc); createFootprint(userId, item.getMdId()); setPickUpNum(posOrder); posOrderService.save(posOrder); // --- 促销:写入快照 + 标记券已使用(save之后,需要posOrder.id) --- if (calc != null) { orderPromotionHelper.savePromotionSnapshots(posOrder.getId(), calc); if (calc.getCouponId() != null) { orderPromotionHelper.markCouponUsed(calc.getCouponId(), posOrder.getId(), userId); } } // 创建订单成功,添加操作日志 InfoUser uUser = infoUserService.getOne(new LambdaQueryWrapper().eq(InfoUser::getUserId, userId)); String uName = uUser != null ? uUser.getNickName() : ""; orderLogHelper.log(subddId, 4, userId, uName, "用户" + uName + "创建订单"); // 创建订单成功,给商家推送"您有新订单了" InfoUser shUser = infoUserService.getById(item.getShId()); if (shUser != null && !StringUtils.isEmpty(shUser.getCid())) { PayPush push = new PayPush(); String title = "no.message.push.message"; String content = "no.message.push.new.order"; String body = OrderPushBodyDto.getJson(subddId, String.valueOf(posOrder.getState()), 0); Long shUserId = shUser.getUserId(); String cid = shUser.getCid(); AsyncManager.me().execute(new TimerTask() { @Override public void run() { PayPush.shPushHandleLocal(push, pushEventService, shUserId, cid, title, content, body, "", subddId); } }); } } } //生成取餐码,根据门店id,当天取餐码从1开始累加 private void setPickUpNum(PosOrder order) { if (order == null || order.getMdId() == null) { return; } try { // 获取当前日期,格式为 yyyy-MM-dd SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); String today = dateFormat.format(new Date()); // 查询当天该门店的最大取餐码 LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(PosOrder::getMdId, order.getMdId()); queryWrapper.like(PosOrder::getCretim, today); queryWrapper.orderByDesc(PosOrder::getPickUpNum); queryWrapper.last("LIMIT 1"); PosOrder lastOrder = posOrderService.getOne(queryWrapper); long nextNum = 1L; if (lastOrder != null && lastOrder.getPickUpNum() != null) { // 直接使用Long类型的取餐码 nextNum = lastOrder.getPickUpNum() + 1; } // 生成新的取餐码:直接使用Long数字 order.setPickUpNum(nextNum); logger.info("生成取餐码成功:门店ID={}, 取餐码={}", order.getMdId(), nextNum); } catch (Exception e) { logger.error("生成取餐码失败:门店ID={}, 错误信息={}", order.getMdId(), e.getMessage(), e); // 生成失败时使用时间戳作为备用方案 Long fallbackPickUpNum = System.currentTimeMillis() % 10000; order.setPickUpNum(fallbackPickUpNum); } } //设置显示状态 private void handleIsDisplay(PosOrder posOrder){ if (StringUtils.isEmpty(posOrder.getDelryTime())) { posOrder.setIsDisplay(true); posOrder.setDisplayTime(new Date()); } //预约单不在时间范围内不显示 if (StringUtils.isNotEmpty(posOrder.getDelryTime())) { int minute = DateUtil.getAppoinmentOrderHandleTime(); boolean isNear = DateUtil.isNearOrPastStartTime(posOrder.getDelryTime(), minute); if (isNear) { posOrder.setIsDisplay(true); posOrder.setDisplayTime(new Date()); } } } private void createFootprint(Long uid,Long mdId){ QueryWrapper Wrapper = new QueryWrapper<>(); Wrapper.eq("user_id", uid); Wrapper.eq("md_id", mdId); UserFootprint foot = userFootprintService.getOne(Wrapper); DateUtil dateString = new DateUtil(); if (foot == null) { UserFootprint usf = new UserFootprint(); usf.setUserId(uid); usf.setMdId(mdId); usf.setCretim(dateString.GetDate()); userFootprintService.saveOrUpdate(usf); } else { foot.setCretim(dateString.GetDate()); userFootprintService.saveOrUpdate(foot); } } /** * 检查门店是否开放 */ private void checkStoreOpen( List storeList,List hourslist, OrderCreatItem item) { List storeHourslist = hourslist.stream().filter(x-> x.getMdId().equals(item.getMdId())).collect(Collectors.toList()); PosStore store = storeList.stream() .filter(x -> Long.valueOf(x.getId()).equals(item.getMdId())) .findFirst() .orElseThrow(() -> new ServiceException(MessageUtils.message("no.mendian.not.exist"))); if (storeHourslist.isEmpty()) { throw new ServiceException(store.getPosName()+MessageUtils.message("no.mendian.not.set.business.hours")); } if (store.getState() == 1) { throw new ServiceException(store.getPosName()+MessageUtils.message("no.mendian.is.closed")); } boolean isOperating = OperatingUtil.storeIsOperatingOfDay(storeHourslist); if (!isOperating) { throw new ServiceException(store.getPosName()+MessageUtils.message("no.mendian.is.closed")); } } /** * 获取父订单详情 */ @GetMapping("/getParentOrderDetail") @Auth @Anonymous public AjaxResult getParentOrderDetail(@RequestHeader String token, @RequestParam String ddId){ OrderParent result = orderParentService.getOne(new LambdaQueryWrapper().eq(OrderParent::getDdId,ddId)); List posOrders = posOrderService.list(new LambdaQueryWrapper().eq(PosOrder::getParentDdId,ddId).orderBy(true,true,PosOrder::getCretim)); result.setItems(posOrders); // 转为JSONObject以追加非实体字段(promotions) JSONObject org = JSON.parseObject(JSON.toJSONStringWithDateFormat(result, "yyyy-MM-dd HH:mm:ss")); JSONArray items = org.getJSONArray("items"); if (items != null) { for (int i = 0; i < items.size(); i++) { JSONObject item = items.getJSONObject(i); Long orderId = item.getLong("id"); List promotions = posOrderPromotionMapper.selectByOrderId(orderId); item.put("promotions", promotions); } } return super.success(org); } /** * 计算两个经纬度之间的距离 */ @GetMapping("/getDistance") @Anonymous public AjaxResult getDistance(@RequestParam Double lat1, @RequestParam Double lng1, @RequestParam Double lat2, @RequestParam Double lng2) { double distance = GetArea.getDistance(lat1, lng1, lat2, lng2); return success(distance); } /** * 用户端订单列表(Tab分页) */ @GetMapping("/orderList") @Auth @Anonymous public AjaxResult orderList(@RequestHeader String token, @RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "10") int size, @RequestParam(required = false) String tab, @RequestParam(required = false) String type) { JwtUtil jwtUtil = new JwtUtil(); String userId = jwtUtil.getusid(token); LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq(PosOrder::getUserId, Long.valueOf(userId)); // 类型筛选 if (type != null && !type.isEmpty()) { wrapper.eq(PosOrder::getType, Long.valueOf(type)); } switch (tab) { case "unpaid": wrapper.eq(PosOrder::getPayStatus, 0L).eq(PosOrder::getType, 0L); break; case "active": wrapper.in(PosOrder::getState, 0L, 1L, 2L) .eq(PosOrder::getAfterSaleStatus, 0L) .and(w -> w.eq(PosOrder::getPayStatus, 1L).or().in(PosOrder::getType, 1L, 2L)); break; case "completed": wrapper.eq(PosOrder::getState, 3L).eq(PosOrder::getAfterSaleStatus, 0L); break; case "cancelled": wrapper.eq(PosOrder::getState, 4L).eq(PosOrder::getAfterSaleStatus, 0L); break; case "refund": wrapper.gt(PosOrder::getAfterSaleStatus, 0L); break; default: break; } wrapper.orderByDesc(PosOrder::getCretim); // 分页 com.baomidou.mybatisplus.extension.plugins.pagination.Page pageParam = new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(page, size); com.baomidou.mybatisplus.extension.plugins.pagination.Page result = posOrderService.page(pageParam, wrapper); // 附加关联数据 for (PosOrder order : result.getRecords()) { enrichOrderData(order); } java.util.Map data = new java.util.LinkedHashMap<>(); data.put("records", result.getRecords()); data.put("total", result.getTotal()); data.put("page", page); data.put("size", size); return success(result); } private void enrichOrderData(PosOrder order) { try { if (order.getShId() != null) { InfoUser shanghu = infoUserService.getById(order.getShId()); order.setPriceAll(shanghu != null ? String.valueOf(shanghu.getNickName()) : null); } } catch (Exception ignored) {} } /** * 确认取餐(自取类型订单,state=2 → state=3) */ @Transactional(rollbackFor = Exception.class) @GetMapping("/confirmPickup") @Auth @Anonymous public AjaxResult confirmPickup(@RequestHeader String token, @RequestParam Long id) { JwtUtil jwtUtil = new JwtUtil(); String userId = jwtUtil.getusid(token); PosOrder order = posOrderService.getOne( new LambdaQueryWrapper() .eq(PosOrder::getId, id) .eq(PosOrder::getUserId, Long.valueOf(userId)) ); if (order == null) { return error("订单不存在"); } if (order.getType() == null || order.getType() != 1) { return error("仅自取订单可确认取餐"); } if (order.getState() == null || order.getState() != 2) { return error("当前订单状态不可确认取餐"); } if (order.getPayStatus() == null || order.getPayStatus() != 1) { return error("订单未支付,不可确认取餐"); } if (order.getAfterSaleStatus() != null && order.getAfterSaleStatus() > 0) { return error("订单存在售后处理中,不可确认取餐"); } order.setState(3L); posOrderService.updateById(order); // 确认取餐成功,添加操作日志 InfoUser uUser = infoUserService.getOne(new LambdaQueryWrapper().eq(InfoUser::getUserId, Long.valueOf(userId))); String uName = uUser != null ? uUser.getNickName() : ""; orderLogHelper.log(String.valueOf(order.getDdId()), 4, Long.valueOf(userId), uName, "用户" + uName + "确认取餐"); return success("确认取餐成功"); } /** * 用户确认完成外送订单(骑手已送达后,state=2 → state=3) */ @Transactional(rollbackFor = Exception.class) @GetMapping("/confirmDelivery") @Auth @Anonymous public AjaxResult confirmDelivery(@RequestHeader String token, @RequestParam Long id) { JwtUtil jwtUtil = new JwtUtil(); String userId = jwtUtil.getusid(token); PosOrder order = posOrderService.getOne( new LambdaQueryWrapper() .eq(PosOrder::getId, id) .eq(PosOrder::getUserId, Long.valueOf(userId)) ); if (order == null) { return error("订单不存在"); } if (order.getState() == null || order.getState() != 2L) { return error("当前订单状态不可确认完成"); } if (order.getDeliveryStatus() == null || order.getDeliveryStatus() != 3L) { return error("骑手尚未送达,不可确认完成"); } if (order.getAfterSaleStatus() != null && order.getAfterSaleStatus() > 0) { return error("订单存在售后处理中,不可确认完成"); } PosOrder update = new PosOrder(); update.setId(order.getId()); update.setState(3L); posOrderService.updateById(update); // 用户确认完成,添加操作日志 InfoUser uUser = infoUserService.getOne(new LambdaQueryWrapper().eq(InfoUser::getUserId, Long.valueOf(userId))); String uName = uUser != null ? uUser.getNickName() : ""; orderLogHelper.log(String.valueOf(order.getDdId()), 4, Long.valueOf(userId), uName, "用户" + uName + "确认完成订单"); return success("确认完成成功"); } /** * 取消订单(未接单前,state=0 → state=4) */ @GetMapping("/cancelOrder") @Auth @Anonymous public AjaxResult cancelOrder(@RequestHeader String token, @RequestParam Long id) { JwtUtil jwtUtil = new JwtUtil(); String userId = jwtUtil.getusid(token); PosOrder order = posOrderService.getOne( new LambdaQueryWrapper() .eq(PosOrder::getId, id) .eq(PosOrder::getUserId, Long.valueOf(userId)) ); if (order == null) { return error("订单不存在"); } if (order.getState() == null || order.getState() != 0) { return error("仅待处理状态的订单可取消"); } order.setState(4L); posOrderService.updateById(order); // 取消订单成功,添加操作日志 InfoUser uUser = infoUserService.getOne(new LambdaQueryWrapper().eq(InfoUser::getUserId, Long.valueOf(userId))); String uName = uUser != null ? uUser.getNickName() : ""; orderLogHelper.log(String.valueOf(order.getDdId()), 4, Long.valueOf(userId), uName, "用户" + uName + "取消订单"); return success("取消订单成功"); } }