UserOrderController.java 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785
  1. package com.ruoyi.app.order;
  2. import com.alibaba.fastjson.JSON;
  3. import com.alibaba.fastjson.JSONArray;
  4. import com.alibaba.fastjson.JSONObject;
  5. import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  6. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
  7. import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
  8. import com.ruoyi.app.order.dto.OrderCreateInput;
  9. import com.ruoyi.app.order.dto.OrderCreatItem;
  10. import com.ruoyi.app.order.dto.OrderPushBodyDto;
  11. import com.ruoyi.app.pay.LinePayRefundService;
  12. import com.ruoyi.app.utils.DateUtil;
  13. import com.ruoyi.app.utils.OperatingUtil;
  14. import com.ruoyi.app.utils.PayPush;
  15. import com.ruoyi.app.utils.event.PushEventService;
  16. import com.github.pagehelper.PageHelper;
  17. import com.ruoyi.common.annotation.Anonymous;
  18. import com.ruoyi.common.core.controller.BaseController;
  19. import com.ruoyi.common.core.domain.AjaxResult;
  20. import com.ruoyi.common.core.page.TableDataInfo;
  21. import com.ruoyi.common.exception.ServiceException;
  22. import com.ruoyi.common.utils.MessageUtils;
  23. import com.ruoyi.common.utils.StringUtils;
  24. import com.ruoyi.framework.manager.AsyncManager;
  25. import com.ruoyi.system.domain.*;
  26. import com.ruoyi.system.mapper.PosOrderPromotionMapper;
  27. import com.ruoyi.system.service.*;
  28. import com.ruoyi.system.utils.Auth;
  29. import com.ruoyi.system.utils.GetArea;
  30. import com.ruoyi.system.utils.JwtUtil;
  31. import com.ruoyi.system.utils.OrderLogHelper;
  32. import com.ruoyi.system.dto.PromotionCalcResponse;
  33. import com.ruoyi.system.domain.dto.ApplyInvoiceDto;
  34. import jakarta.validation.Valid;
  35. import org.springframework.web.bind.annotation.*;
  36. import org.springframework.beans.factory.annotation.Autowired;
  37. import org.springframework.transaction.annotation.Transactional;
  38. import java.text.SimpleDateFormat;
  39. import java.util.Date;
  40. import java.util.List;
  41. import java.util.Optional;
  42. import java.util.TimerTask;
  43. import java.util.stream.Collectors;
  44. /**
  45. * 用户订单Controller
  46. *
  47. * @author ruoyi
  48. * @date 2020-04-01
  49. */
  50. @RestController
  51. @RequestMapping("/system/userOrder")
  52. public class UserOrderController extends BaseController {
  53. @Autowired
  54. private IOrderParentService orderParentService;
  55. /** 支付方式闸门(031):全系统唯一支付方式可用性判定入口,禁止在本类重复实现。 */
  56. @Autowired
  57. private PaymentMethodGateService paymentMethodGateService;
  58. @Autowired
  59. private IPosOrderService posOrderService;
  60. @Autowired
  61. private IPosStoreService posStoreService;
  62. @Autowired
  63. private IInfoAddressService infoAddressService;
  64. @Autowired
  65. private IOperatingHoursService operatingHoursService;
  66. @Autowired
  67. private IUserFootprintService userFootprintService;
  68. @Autowired
  69. private IInfoUserService infoUserService;
  70. @Autowired
  71. private PushEventService pushEventService;
  72. @Autowired
  73. private OrderLogHelper orderLogHelper;
  74. @Autowired
  75. private OrderPromotionHelper orderPromotionHelper;
  76. @Autowired
  77. private PosOrderPromotionMapper posOrderPromotionMapper;
  78. @Autowired
  79. private OrderInvoiceService orderInvoiceService;
  80. @Autowired
  81. private IInfoInvoiceService infoInvoiceService;
  82. @Autowired
  83. private IPosOrderLinePaymentService linePaymentService;
  84. @Autowired
  85. private LinePayRefundService linePayRefundService;
  86. @Autowired
  87. private OrderLifecycleService orderLifecycleService;
  88. @Autowired
  89. private DeliveryOrderNotificationService deliveryOrderNotificationService;
  90. @Autowired
  91. private MerchantNotificationRouter merchantNotificationRouter;
  92. @Autowired //029 商家自配送:配置解析与下单判定
  93. private SelfDeliveryService selfDeliveryService;
  94. /**
  95. * 申请电子发票(订单完成后,客户主动申请:B2C 邮箱 / B2B 统编 / 载具)
  96. */
  97. @PostMapping("/applyInvoice")
  98. @Auth
  99. @Anonymous
  100. public AjaxResult applyInvoice(@RequestHeader String token, @RequestBody @Valid ApplyInvoiceDto dto) {
  101. Long userId = Long.valueOf(new JwtUtil().getusid(token));
  102. return AjaxResult.success(orderInvoiceService.applyInvoice(dto, userId));
  103. }
  104. /**
  105. * 查询订单电子发票(状态 / 发票号 / 凭证);门店不可开票时 invoiceStatus=null
  106. */
  107. @GetMapping("/getInvoice")
  108. @Auth
  109. @Anonymous
  110. public AjaxResult getInvoice(@RequestHeader String token, @RequestParam Long orderId) {
  111. Long userId = Long.valueOf(new JwtUtil().getusid(token));
  112. return AjaxResult.success(orderInvoiceService.getInvoice(orderId, userId));
  113. }
  114. /**
  115. * 捐赠机构列表(客户端选择器,仅 enabled=1,按机构名/简称/捐赠码搜索;分页)
  116. */
  117. @GetMapping("/loveOrg/list")
  118. @Auth
  119. @Anonymous
  120. public TableDataInfo loveOrgList(@RequestHeader String token,
  121. @RequestParam(defaultValue = "1") int pageNum,
  122. @RequestParam(defaultValue = "20") int pageSize,
  123. @RequestParam(required = false) String keyword) {
  124. PageHelper.startPage(pageNum, pageSize);
  125. return getDataTable(orderInvoiceService.listLoveOrg(keyword));
  126. }
  127. /**
  128. * 会员发票夹(010 Phase 11):我的发票列表,按 userId 查已开发票(不含码图)。
  129. */
  130. @GetMapping("/myInvoices")
  131. @Auth
  132. @Anonymous
  133. public TableDataInfo myInvoices(@RequestHeader String token,
  134. @RequestParam(defaultValue = "1") int pageNum,
  135. @RequestParam(defaultValue = "10") int pageSize) {
  136. Long userId = Long.valueOf(new JwtUtil().getusid(token));
  137. PageHelper.startPage(pageNum, pageSize);
  138. List<com.ruoyi.system.domain.vo.PosOrderInvoiceVo> list = orderInvoiceService.listMyInvoices(userId);
  139. return getDataTable(list);
  140. }
  141. /**
  142. * 创建订单
  143. */
  144. @PostMapping("/createOrder")
  145. @Auth
  146. @Anonymous
  147. @Transactional(rollbackFor = Exception.class)
  148. public AjaxResult createOrder(@RequestHeader String token, @RequestBody OrderCreateInput input)
  149. {
  150. JwtUtil jwtUtil = new JwtUtil();
  151. String id = jwtUtil.getusid(token);
  152. String shdz=null;
  153. if(input.getType()==0){
  154. InfoAddress usadd = infoAddressService.getById(input.getShdzId());
  155. if (usadd == null) {
  156. throw new ServiceException(MessageUtils.message("no.address.not.exist"));
  157. }
  158. shdz= JSON.toJSONString(usadd);
  159. }
  160. // 现金支付(4):堂食/自取放行(可用性由下方 PaymentMethodGateService 按商家勾选管控);外送仍仅商家创建订单可用
  161. if (OrderLifecycleService.PAY_TYPE_CASH.equals(input.getPaymentMethod())
  162. && input.getType() != 1 && input.getType() != 2) {
  163. throw new ServiceException(MessageUtils.message("no.paytype.cash.merchant.only"));
  164. }
  165. // === 发票意图(010 Phase 11)===
  166. // 校验:载具/捐赠/统编验真前置,不通过拦截下单(联网校验用第一个可开票门店的 ezPay 凭证)
  167. if (!StringUtils.isEmpty(input.getInvoiceChoice()) && !"PAPER".equals(input.getInvoiceChoice())) {
  168. Long invoiceStoreId = input.getItems() == null ? null : input.getItems().stream()
  169. .map(OrderCreatItem::getMdId)
  170. .filter(md -> orderInvoiceService.canInvoice(md))
  171. .findFirst().orElse(null);
  172. if (invoiceStoreId == null) {
  173. throw new ServiceException("所选门店暂不支持电子发票");
  174. }
  175. orderInvoiceService.validateCheckoutInvoiceIntent(input.getInvoiceChoice(), input.getCarrierNum(),
  176. input.getLoveCode(), input.getBuyerUbn(), input.getBuyerEmail(), invoiceStoreId);
  177. }
  178. createOrderParent(input,Long.valueOf(id),shdz);
  179. createOrderChild(input,Long.valueOf(id),Long.valueOf(id),shdz);
  180. // saveAsDefault:把载具/统编存为默认抬头(同一用户仅一条 is_default=1)
  181. if (Boolean.TRUE.equals(input.getSaveAsDefault()) && !StringUtils.isEmpty(input.getInvoiceChoice())) {
  182. saveDefaultInvoiceProfile(input, Long.valueOf(id));
  183. }
  184. OrderParent result = orderParentService.getOne(new LambdaQueryWrapper<OrderParent>().eq(OrderParent::getDdId, input.getDdId().toString()));
  185. System.out.println("创建返回信息:"+result);
  186. return super.success(result);
  187. }
  188. //创建父订单
  189. private void createOrderParent(OrderCreateInput input,Long UserId,String shdz){
  190. OrderParent orderParent = new OrderParent();
  191. orderParent.setUserId(UserId);
  192. orderParent.setOrderStatus(0L);
  193. orderParent.setTotalAmount(input.getTotalAmount());
  194. orderParent.setActualPayAmount(input.getActualPayAmount());
  195. orderParent.setPaymentMethod(input.getPaymentMethod());
  196. // orderParent.setPaymentTime(new Date());
  197. orderParent.setCreateTime(new Date());
  198. // orderParent.setUpdateTime(new Date());
  199. orderParent.setDdId(input.getDdId().toString());
  200. orderParent.setTableId(input.getTableId());
  201. orderParent.setShdzId(input.getShdzId());
  202. orderParent.setShAddress(shdz);
  203. orderParent.setType(input.getType());
  204. orderParent.setRemarks(input.getRemarks());
  205. orderParentService.save(orderParent);
  206. }
  207. //创建子订单
  208. private void createOrderChild(OrderCreateInput input,Long orderParentId,Long userId,String shdz){
  209. // 检查items是否为空
  210. if (input.getItems() == null || input.getItems().isEmpty()) {
  211. throw new ServiceException(MessageUtils.message("订单商品不能为空"));
  212. }
  213. String ddId = input.getDdId().toString();
  214. List<Long> mdIdList = input.getItems().stream().map(OrderCreatItem::getMdId).collect(Collectors.toList());
  215. // 将Long类型的门店ID转换为Integer类型用于查询(因为PosStore.getId()返回Integer)
  216. List<Integer> mdIdIntegerList = mdIdList.stream().map(Long::intValue).collect(Collectors.toList());
  217. List<PosStore> storeList = posStoreService.list(new LambdaQueryWrapper<PosStore>().in(PosStore::getId, mdIdIntegerList));
  218. LambdaQueryWrapper<OperatingHours> wrapper = new LambdaQueryWrapper<>();
  219. wrapper.in(OperatingHours::getMdId, mdIdList);
  220. List<OperatingHours> hourslist = operatingHoursService.list(wrapper);
  221. // 031 支付方式闸门:整单支付方式唯一,涉及每个门店(主账号)都必须满足
  222. // 平台开关 ∩ 商家接受 ∩ 就绪度,堵住绕过界面直传被禁方式下单的漏洞(spec FR-004)。
  223. // US7:带整单用餐类型,现金仅自取/堂食可用且需商家勾选,外送直传现金被拒。
  224. for (PosStore store : storeList) {
  225. paymentMethodGateService.assertUsableForOrder(input.getPaymentMethod(),
  226. PaymentMethodGateService.GateScope.MERCHANT,
  227. store.getId().longValue(), store.getUserId(), input.getType());
  228. }
  229. boolean isMultiStore = input.getItems().size() > 1;
  230. // 循环items,为每个item创建一条PosOrder
  231. int index = 0;
  232. for (OrderCreatItem item : input.getItems()) {
  233. // 检查门店是否开放
  234. checkStoreOpen(storeList,hourslist,item);
  235. index++;
  236. // 单店铺:子订单号与父订单一致;多店铺:子订单号追加序号
  237. String subddId = isMultiStore ? ddId + String.format("%03d", index) : ddId;
  238. PosOrder posOrder = new PosOrder();
  239. posOrder.setIsDisplay(false);
  240. handleIsDisplay(posOrder);
  241. posOrder.setShdzId(input.getShdzId());
  242. // 设置子订单的基本信息
  243. posOrder.setDdId(subddId);
  244. posOrder.setParentDdId(input.getDdId().toString()); // 设置父订单ID
  245. posOrder.setCretim(new Date());
  246. // 根据item设置订单信息
  247. posOrder.setMdId(item.getMdId());
  248. posOrder.setShdzId(input.getShdzId());
  249. posOrder.setUserId(userId);
  250. posOrder.setAmount(item.getAmount());
  251. posOrder.setRemarks(item.getRemarks());
  252. posOrder.setType(item.getType());
  253. posOrder.setDelryTime(item.getDelryTime());
  254. posOrder.setFood(JSON.toJSONString(item.getFood()));
  255. if("1".equals(input.getPaymentMethod())){
  256. posOrder.setCollectPayment("1");
  257. }
  258. // 设置支付相关信息(从父订单继承)
  259. posOrder.setPayType(input.getPaymentMethod());
  260. //设置桌号(从父订单继承)
  261. posOrder.setTableId(input.getTableId());
  262. posOrder.setTableNo(input.getTableNo());
  263. posOrder.setLogo(item.getLogo());
  264. posOrder.setPosName(item.getPosName());
  265. posOrder.setJvli(item.getJvli());
  266. posOrder.setFreight(item.getFreight());
  267. posOrder.setShdzId(input.getShdzId());
  268. posOrder.setShAddress(shdz);
  269. posOrder.setFoodAmount(item.getFoodAmount());
  270. posOrder.setPickUpTime(input.getPickUpTime());
  271. posOrder.setReservePhone(input.getReservePhone());
  272. // 新状态字段初始化
  273. // 堂食订单(type=2)自动接单,跳过商家确认
  274. if (item.getType() != null && item.getType() == 2) {
  275. posOrder.setState(1L);
  276. } else {
  277. posOrder.setState(0L);
  278. }
  279. posOrder.setAfterSaleStatus(0L);
  280. posOrder.setDeliveryStatus(Long.valueOf(0L).equals(item.getType()) ? 0L : null);
  281. // 外送到付(payType=1):payStatus=1;现金(4)自取/堂食同到付线下收款语义:payStatus=1;其他:payStatus=0
  282. if (item.getType() != null && item.getType() == 0 && "1".equals(input.getPaymentMethod())) {
  283. posOrder.setPayStatus(1L);
  284. } else if ("4".equals(input.getPaymentMethod())
  285. && (item.getType() != null && (item.getType() == 1 || item.getType() == 2))) {
  286. posOrder.setPayStatus(1L);
  287. } else {
  288. posOrder.setPayStatus(0L);
  289. }
  290. Optional<PosStore> storeOptional = storeList.stream()
  291. .filter(x -> item.getMdId().equals(Long.valueOf(x.getId())))
  292. .findFirst();
  293. if (storeOptional.isEmpty()) {
  294. throw new ServiceException(MessageUtils.message("no.mendian.not.exist"));
  295. }
  296. PosStore store = storeOptional.get();
  297. // 保持已有客户端 shId 的兼容性;缺失时以实际门店归属回填,夜市摊位会自然归属夜市主。
  298. posOrder.setShId(resolveOrderMerchantId(item.getShId(), store));
  299. posOrder.setLongitude(store.getLongitude());
  300. posOrder.setLatitude(store.getLatitude());
  301. // 029 商家自配送:外送单按门店时段判定并固化快照(即时=下单时刻;预约按 delryTime 时间段开始时刻)
  302. posOrder.setSelfDelivery(Long.valueOf(0L).equals(item.getType())
  303. && selfDeliveryService.isSelfDelivery(store,
  304. selfDeliveryService.resolveJudgeTime(item.getDelryTime())) ? 1 : 0);
  305. //设置订单分类
  306. storeList.stream().filter(s -> Long.valueOf(s.getId()).equals(item.getMdId())).findFirst().ifPresent(st -> {
  307. posOrder.setOrderCategory(String.valueOf(st.getType()));
  308. });
  309. // 保存子订单
  310. // --- 促销:算价 + 校验 + 回填优惠字段(save之前) ---
  311. PromotionCalcResponse calc = orderPromotionHelper.calcPromotion(item, userId);
  312. orderPromotionHelper.validatePromotionAmount(calc, item.getPromoReduceAmount());
  313. orderPromotionHelper.fillPromotionFields(posOrder, calc);
  314. createFootprint(userId, item.getMdId());
  315. setPickUpNum(posOrder);
  316. posOrderService.save(posOrder);
  317. // 发票意图落 pos_order_invoice 意图行(010 Phase 11,方案B):仅开发票门店插,
  318. // issueTriggered=0 未触发;出餐 autoIssue 在该行落结果并置 issueTriggered=1
  319. if (!StringUtils.isEmpty(input.getInvoiceChoice()) && orderInvoiceService.canInvoice(item.getMdId())) {
  320. String choice = input.getInvoiceChoice();
  321. PosOrderInvoice intent = new PosOrderInvoice();
  322. intent.setOrderId(posOrder.getId());
  323. intent.setOrderNo(subddId);
  324. intent.setStoreId(item.getMdId());
  325. intent.setInvoiceChoice(choice);
  326. intent.setCarrierNum(input.getCarrierNum());
  327. intent.setBuyerUbn(input.getBuyerUbn());
  328. intent.setBuyerName(input.getBuyerName());
  329. intent.setBuyerEmail(input.getBuyerEmail());
  330. intent.setLoveCode(input.getLoveCode());
  331. if ("PHONE_BARCODE".equals(choice)) {
  332. intent.setCarrierType("0");
  333. } else if ("CITIZEN".equals(choice)) {
  334. intent.setCarrierType("1");
  335. } else if ("MEMBER".equals(choice)) {
  336. // 【决定 2026-08-05】会员载具走 PrintFlag=Y 纸本,不传 ezPay 电子载具(见 OrderInvoiceService.buildAutoIssueDto 决策备注);carrierType 留空
  337. } else {
  338. intent.setCarrierType(input.getCarrierType());
  339. }
  340. orderInvoiceService.captureInvoiceIntent(intent);
  341. }
  342. // --- 促销:写入快照 + 标记券已使用(save之后,需要posOrder.id) ---
  343. if (calc != null) {
  344. orderPromotionHelper.savePromotionSnapshots(posOrder.getId(), calc);
  345. if (calc.getCouponId() != null) {
  346. orderPromotionHelper.markCouponUsed(calc.getCouponId(), posOrder.getId(), userId);
  347. }
  348. }
  349. // 创建订单成功,添加操作日志
  350. InfoUser uUser = infoUserService.getOne(new LambdaQueryWrapper<InfoUser>().eq(InfoUser::getUserId, userId));
  351. String uName = uUser != null ? uUser.getNickName() : "";
  352. orderLogHelper.log(subddId, 4, userId, uName, "用户" + uName + "创建订单");
  353. if (Long.valueOf(0L).equals(posOrder.getType())) {
  354. // 到付订单创建后立即开放;在线支付订单由支付成功回调开放。
  355. deliveryOrderNotificationService.notifyOrderAvailable(posOrder);
  356. } else {
  357. // 自取和堂食保持原流程,创建后直接通知商家。
  358. String body = OrderPushBodyDto.getJson(subddId, String.valueOf(posOrder.getState()), 0);
  359. AsyncManager.me().execute(new TimerTask() {
  360. @Override
  361. public void run() {
  362. merchantNotificationRouter.sendStoreNotification(posOrder.getMdId(), posOrder.getShId(),
  363. "no.message.push.message", "no.message.push.new.order", body, subddId);
  364. }
  365. });
  366. }
  367. }
  368. }
  369. /**
  370. * 用户下单兼容旧客户端:已传 shId 时保持原值;未传时只能以实际门店归属确定收款商家。
  371. */
  372. static Long resolveOrderMerchantId(Long requestedShId, PosStore store) {
  373. return requestedShId != null ? requestedShId : store.getUserId();
  374. }
  375. //保存默认发票抬头(010 Phase 11:结账勾「同意设置为默认」;同一用户仅一条 is_default=1)
  376. private void saveDefaultInvoiceProfile(OrderCreateInput input, Long userId) {
  377. String choice = input.getInvoiceChoice();
  378. if ("PAPER".equals(choice) || "LOVE_CODE".equals(choice) || "MEMBER".equals(choice)) {
  379. return; // 纸本/捐赠/会员载具不存抬头(会员载具=会员手机号,无需记忆)
  380. }
  381. InfoInvoice profile = new InfoInvoice();
  382. profile.setUserId(userId);
  383. profile.setIsDefault(1);
  384. switch (choice) {
  385. case "PHONE_BARCODE":
  386. profile.setCategory("B2C");
  387. profile.setTitleName("手机条码载具");
  388. profile.setBuyerName("手机条码载具");
  389. profile.setCarrierType("0");
  390. profile.setCarrierNum(input.getCarrierNum());
  391. break;
  392. case "CITIZEN":
  393. profile.setCategory("B2C");
  394. profile.setTitleName("自然人凭证载具");
  395. profile.setBuyerName("自然人凭证载具");
  396. profile.setCarrierType("1");
  397. profile.setCarrierNum(input.getCarrierNum());
  398. break;
  399. case "COMPANY":
  400. profile.setCategory("B2B");
  401. profile.setTitleName(input.getBuyerName());
  402. profile.setBuyerName(input.getBuyerName());
  403. profile.setBuyerUbn(input.getBuyerUbn());
  404. break;
  405. default:
  406. return;
  407. }
  408. // 先清掉该用户其它默认(同一用户仅一条为 1),再存新的
  409. InfoInvoice reset = new InfoInvoice();
  410. reset.setIsDefault(0);
  411. infoInvoiceService.update(reset, new LambdaQueryWrapper<InfoInvoice>()
  412. .eq(InfoInvoice::getUserId, userId)
  413. .eq(InfoInvoice::getIsDefault, 1));
  414. infoInvoiceService.save(profile);
  415. }
  416. //生成取餐码,根据门店id,当天取餐码从1开始累加
  417. private void setPickUpNum(PosOrder order) {
  418. if (order == null || order.getMdId() == null) {
  419. return;
  420. }
  421. try {
  422. // 获取当前日期,格式为 yyyy-MM-dd
  423. SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
  424. String today = dateFormat.format(new Date());
  425. // 查询当天该门店的最大取餐码
  426. LambdaQueryWrapper<PosOrder> queryWrapper = new LambdaQueryWrapper<>();
  427. queryWrapper.eq(PosOrder::getMdId, order.getMdId());
  428. queryWrapper.like(PosOrder::getCretim, today);
  429. queryWrapper.orderByDesc(PosOrder::getPickUpNum);
  430. queryWrapper.last("LIMIT 1");
  431. PosOrder lastOrder = posOrderService.getOne(queryWrapper);
  432. long nextNum = 1L;
  433. if (lastOrder != null && lastOrder.getPickUpNum() != null) {
  434. // 直接使用Long类型的取餐码
  435. nextNum = lastOrder.getPickUpNum() + 1;
  436. }
  437. // 生成新的取餐码:直接使用Long数字
  438. order.setPickUpNum(nextNum);
  439. logger.info("生成取餐码成功:门店ID={}, 取餐码={}", order.getMdId(), nextNum);
  440. } catch (Exception e) {
  441. logger.error("生成取餐码失败:门店ID={}, 错误信息={}", order.getMdId(), e.getMessage(), e);
  442. // 生成失败时使用时间戳作为备用方案
  443. Long fallbackPickUpNum = System.currentTimeMillis() % 10000;
  444. order.setPickUpNum(fallbackPickUpNum);
  445. }
  446. }
  447. //设置显示状态
  448. private void handleIsDisplay(PosOrder posOrder){
  449. if (StringUtils.isEmpty(posOrder.getDelryTime())) {
  450. posOrder.setIsDisplay(true);
  451. posOrder.setDisplayTime(new Date());
  452. }
  453. //预约单不在时间范围内不显示
  454. if (StringUtils.isNotEmpty(posOrder.getDelryTime())) {
  455. int minute = DateUtil.getAppoinmentOrderHandleTime();
  456. boolean isNear = DateUtil.isNearOrPastStartTime(posOrder.getDelryTime(), minute);
  457. if (isNear) {
  458. posOrder.setIsDisplay(true);
  459. posOrder.setDisplayTime(new Date());
  460. }
  461. }
  462. }
  463. private void createFootprint(Long uid,Long mdId){
  464. QueryWrapper<UserFootprint> Wrapper = new QueryWrapper<>();
  465. Wrapper.eq("user_id", uid);
  466. Wrapper.eq("md_id", mdId);
  467. UserFootprint foot = userFootprintService.getOne(Wrapper);
  468. DateUtil dateString = new DateUtil();
  469. if (foot == null) {
  470. UserFootprint usf = new UserFootprint();
  471. usf.setUserId(uid);
  472. usf.setMdId(mdId);
  473. usf.setCretim(dateString.GetDate());
  474. userFootprintService.saveOrUpdate(usf);
  475. } else {
  476. foot.setCretim(dateString.GetDate());
  477. userFootprintService.saveOrUpdate(foot);
  478. }
  479. }
  480. /**
  481. * 检查门店是否开放
  482. */
  483. private void checkStoreOpen( List<PosStore> storeList,List<OperatingHours> hourslist, OrderCreatItem item) {
  484. List<OperatingHours> storeHourslist = hourslist.stream().filter(x-> x.getMdId().equals(item.getMdId())).collect(Collectors.toList());
  485. PosStore store = storeList.stream()
  486. .filter(x -> Long.valueOf(x.getId()).equals(item.getMdId()))
  487. .findFirst()
  488. .orElseThrow(() -> new ServiceException(MessageUtils.message("no.mendian.not.exist")));
  489. if (storeHourslist.isEmpty()) {
  490. throw new ServiceException(store.getPosName()+MessageUtils.message("no.mendian.not.set.business.hours"));
  491. }
  492. if (store.getState() == 1) {
  493. throw new ServiceException(store.getPosName()+MessageUtils.message("no.mendian.is.closed"));
  494. }
  495. boolean isOperating = OperatingUtil.storeIsOperatingOfDay(storeHourslist);
  496. if (!isOperating) {
  497. throw new ServiceException(store.getPosName()+MessageUtils.message("no.mendian.is.closed"));
  498. }
  499. }
  500. /**
  501. * 获取父订单详情
  502. */
  503. @GetMapping("/getParentOrderDetail")
  504. @Auth
  505. @Anonymous
  506. public AjaxResult getParentOrderDetail(@RequestHeader String token, @RequestParam String ddId){
  507. OrderParent result = orderParentService.getOne(new LambdaQueryWrapper<OrderParent>().eq(OrderParent::getDdId,ddId));
  508. List<PosOrder> posOrders = posOrderService.list(new LambdaQueryWrapper<PosOrder>().eq(PosOrder::getParentDdId,ddId).orderBy(true,true,PosOrder::getCretim));
  509. result.setItems(posOrders);
  510. // 转为JSONObject以追加非实体字段(promotions)
  511. JSONObject org = JSON.parseObject(JSON.toJSONStringWithDateFormat(result, "yyyy-MM-dd HH:mm:ss"));
  512. JSONArray items = org.getJSONArray("items");
  513. if (items != null) {
  514. for (int i = 0; i < items.size(); i++) {
  515. JSONObject item = items.getJSONObject(i);
  516. Long orderId = item.getLong("id");
  517. List<PosOrderPromotion> promotions = posOrderPromotionMapper.selectByOrderId(orderId);
  518. item.put("promotions", promotions);
  519. }
  520. }
  521. return super.success(org);
  522. }
  523. /**
  524. * 计算两个经纬度之间的距离
  525. */
  526. @GetMapping("/getDistance")
  527. @Anonymous
  528. public AjaxResult getDistance(@RequestParam Double lat1, @RequestParam Double lng1,
  529. @RequestParam Double lat2, @RequestParam Double lng2) {
  530. double distance = GetArea.getDistance(lat1, lng1, lat2, lng2);
  531. return success(distance);
  532. }
  533. /**
  534. * 用户端订单列表(Tab分页)
  535. */
  536. @GetMapping("/orderList")
  537. @Auth
  538. @Anonymous
  539. public AjaxResult orderList(@RequestHeader String token,
  540. @RequestParam(defaultValue = "1") int page,
  541. @RequestParam(defaultValue = "10") int size,
  542. @RequestParam(required = false) String tab,
  543. @RequestParam(required = false) String type) {
  544. JwtUtil jwtUtil = new JwtUtil();
  545. String userId = jwtUtil.getusid(token);
  546. LambdaQueryWrapper<PosOrder> wrapper = new LambdaQueryWrapper<>();
  547. wrapper.eq(PosOrder::getUserId, Long.valueOf(userId));
  548. // 类型筛选
  549. if (type != null && !type.isEmpty()) {
  550. wrapper.eq(PosOrder::getType, Long.valueOf(type));
  551. }
  552. applyTabFilter(wrapper, tab);
  553. wrapper.orderByDesc(PosOrder::getCretim);
  554. // 分页
  555. com.baomidou.mybatisplus.extension.plugins.pagination.Page<PosOrder> pageParam =
  556. new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(page, size);
  557. com.baomidou.mybatisplus.extension.plugins.pagination.Page<PosOrder> result = posOrderService.page(pageParam, wrapper);
  558. // 附加关联数据
  559. for (PosOrder order : result.getRecords()) {
  560. enrichOrderData(order);
  561. }
  562. java.util.Map<String, Object> data = new java.util.LinkedHashMap<>();
  563. data.put("records", result.getRecords());
  564. data.put("total", result.getTotal());
  565. data.put("page", page);
  566. data.put("size", size);
  567. return success(result);
  568. }
  569. /** 订单列表页签过滤条件(031 US7 抽取便于单测):unpaid 仅在线支付;active 放行到付与现金自取/堂食。 */
  570. static void applyTabFilter(LambdaQueryWrapper<PosOrder> wrapper, String tab) {
  571. switch (tab) {
  572. case "unpaid":
  573. // 在线支付订单不区分配送类型;自取/堂食未付款也必须先留在待付款。
  574. wrapper.eq(PosOrder::getPayStatus, 0L)
  575. .in(PosOrder::getPayType, "2", "3")
  576. .ne(PosOrder::getState, 4L)
  577. .eq(PosOrder::getAfterSaleStatus, 0L);
  578. break;
  579. case "active":
  580. wrapper.in(PosOrder::getState, 0L, 1L, 2L)
  581. .eq(PosOrder::getAfterSaleStatus, 0L)
  582. // 到付/现金自取堂食可以线下收款;OMG、LINE Pay 等在线订单必须已付款才能履约。
  583. .and(w -> w.eq(PosOrder::getPayStatus, 1L)
  584. .or(cod -> cod.in(PosOrder::getPayType, "1", "4")
  585. .in(PosOrder::getType, 1L, 2L)));
  586. break;
  587. case "completed":
  588. wrapper.eq(PosOrder::getState, 3L).eq(PosOrder::getAfterSaleStatus, 0L);
  589. break;
  590. case "cancelled":
  591. wrapper.eq(PosOrder::getState, 4L).eq(PosOrder::getAfterSaleStatus, 0L);
  592. break;
  593. case "refund":
  594. wrapper.gt(PosOrder::getAfterSaleStatus, 0L);
  595. break;
  596. default:
  597. break;
  598. }
  599. }
  600. private void enrichOrderData(PosOrder order) {
  601. try {
  602. if (order.getShId() != null) {
  603. InfoUser shanghu = infoUserService.getById(order.getShId());
  604. order.setPriceAll(shanghu != null ? String.valueOf(shanghu.getNickName()) : null);
  605. }
  606. } catch (Exception ignored) {}
  607. }
  608. /**
  609. * 确认取餐(自取类型订单,state=2 → state=3)
  610. */
  611. @Transactional(rollbackFor = Exception.class)
  612. @GetMapping("/confirmPickup")
  613. @Auth
  614. @Anonymous
  615. public AjaxResult confirmPickup(@RequestHeader String token, @RequestParam Long id) {
  616. JwtUtil jwtUtil = new JwtUtil();
  617. String userId = jwtUtil.getusid(token);
  618. PosOrder order = posOrderService.getOne(
  619. new LambdaQueryWrapper<PosOrder>()
  620. .eq(PosOrder::getId, id)
  621. .eq(PosOrder::getUserId, Long.valueOf(userId))
  622. );
  623. if (order == null) {
  624. return error("订单不存在");
  625. }
  626. if (order.getType() == null || order.getType() != 1) {
  627. return error("仅自取订单可确认取餐");
  628. }
  629. if (order.getState() == null || order.getState() != 2) {
  630. return error("当前订单状态不可确认取餐");
  631. }
  632. if (order.getPayStatus() == null || order.getPayStatus() != 1) {
  633. return error("订单未支付,不可确认取餐");
  634. }
  635. if (order.getAfterSaleStatus() != null && order.getAfterSaleStatus() > 0) {
  636. return error("订单存在售后处理中,不可确认取餐");
  637. }
  638. if (orderLifecycleService.isRealLineOrder(order)) {
  639. orderLifecycleService.completePaidLineOrder(order.getId());
  640. } else {
  641. order.setState(3L);
  642. posOrderService.updateById(order);
  643. }
  644. // 确认取餐成功,添加操作日志
  645. InfoUser uUser = infoUserService.getOne(new LambdaQueryWrapper<InfoUser>().eq(InfoUser::getUserId, Long.valueOf(userId)));
  646. String uName = uUser != null ? uUser.getNickName() : "";
  647. orderLogHelper.log(String.valueOf(order.getDdId()), 4, Long.valueOf(userId), uName, "用户" + uName + "确认取餐");
  648. return success("确认取餐成功");
  649. }
  650. /**
  651. * 用户确认完成外送订单(骑手已送达后,state=2 → state=3)
  652. */
  653. @Transactional(rollbackFor = Exception.class)
  654. @GetMapping("/confirmDelivery")
  655. @Auth
  656. @Anonymous
  657. public AjaxResult confirmDelivery(@RequestHeader String token, @RequestParam Long id) {
  658. JwtUtil jwtUtil = new JwtUtil();
  659. String userId = jwtUtil.getusid(token);
  660. PosOrder order = posOrderService.getOne(
  661. new LambdaQueryWrapper<PosOrder>()
  662. .eq(PosOrder::getId, id)
  663. .eq(PosOrder::getUserId, Long.valueOf(userId))
  664. );
  665. if (order == null) {
  666. return error("订单不存在");
  667. }
  668. if (order.getState() == null || order.getState() != 2L) {
  669. return error("当前订单状态不可确认完成");
  670. }
  671. if (order.getDeliveryStatus() == null || order.getDeliveryStatus() != 3L) {
  672. return error("骑手尚未送达,不可确认完成");
  673. }
  674. if (order.getAfterSaleStatus() != null && order.getAfterSaleStatus() > 0) {
  675. return error("订单存在售后处理中,不可确认完成");
  676. }
  677. if (orderLifecycleService.isRealLineOrder(order)) {
  678. orderLifecycleService.completePaidLineOrder(order.getId());
  679. } else {
  680. PosOrder update = new PosOrder();
  681. update.setId(order.getId());
  682. update.setState(3L);
  683. posOrderService.updateById(update);
  684. }
  685. // 用户确认完成,添加操作日志
  686. InfoUser uUser = infoUserService.getOne(new LambdaQueryWrapper<InfoUser>().eq(InfoUser::getUserId, Long.valueOf(userId)));
  687. String uName = uUser != null ? uUser.getNickName() : "";
  688. orderLogHelper.log(String.valueOf(order.getDdId()), 4, Long.valueOf(userId), uName, "用户" + uName + "确认完成订单");
  689. return success("确认完成成功");
  690. }
  691. /**
  692. * 取消订单(未接单前,state=0 → state=4)
  693. */
  694. @GetMapping("/cancelOrder")
  695. @Auth
  696. @Anonymous
  697. public AjaxResult cancelOrder(@RequestHeader String token, @RequestParam Long id) {
  698. JwtUtil jwtUtil = new JwtUtil();
  699. String userId = jwtUtil.getusid(token);
  700. PosOrder order = posOrderService.getOne(
  701. new LambdaQueryWrapper<PosOrder>()
  702. .eq(PosOrder::getId, id)
  703. .eq(PosOrder::getUserId, Long.valueOf(userId))
  704. );
  705. if (order == null) {
  706. return error("订单不存在");
  707. }
  708. if (order.getState() == null || order.getState() != 0) {
  709. return error("仅待处理状态的订单可取消");
  710. }
  711. PosOrder update = new PosOrder();
  712. update.setState(4L);
  713. boolean cancelled = posOrderService.update(update, new LambdaUpdateWrapper<PosOrder>()
  714. .eq(PosOrder::getId, order.getId())
  715. .eq(PosOrder::getState, 0L));
  716. if (!cancelled) {
  717. return error("订单状态已变化,请刷新后重试");
  718. }
  719. // 取消落库后按最新支付状态退款,避免旧订单对象覆盖退款状态或漏掉并发成功回调。
  720. PosOrder latest = posOrderService.getById(order.getId());
  721. if (latest != null && Long.valueOf(1L).equals(latest.getPayStatus())) {
  722. linePaymentService.getByDdId(String.valueOf(latest.getDdId())).stream()
  723. .filter(payment -> "PAID".equals(payment.getStatus()))
  724. .findFirst()
  725. .ifPresent(payment -> linePayRefundService
  726. .createRefundIntent(payment.getId(), "USER_CANCEL"));
  727. }
  728. // 取消订单成功,添加操作日志
  729. InfoUser uUser = infoUserService.getOne(new LambdaQueryWrapper<InfoUser>().eq(InfoUser::getUserId, Long.valueOf(userId)));
  730. String uName = uUser != null ? uUser.getNickName() : "";
  731. orderLogHelper.log(String.valueOf(order.getDdId()), 4, Long.valueOf(userId), uName, "用户" + uName + "取消订单");
  732. deliveryOrderNotificationService.notifyOrderCancelled(order, Long.valueOf(userId));
  733. return success("取消订单成功");
  734. }
  735. }