TOrderServiceImpl.java 59 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386
  1. package com.ylx.massage.service.impl;
  2. import cn.hutool.core.collection.CollectionUtil;
  3. import cn.hutool.json.JSONUtil;
  4. import com.alibaba.fastjson.JSONArray;
  5. import com.alibaba.fastjson.JSONObject;
  6. import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  7. import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
  8. import com.baomidou.mybatisplus.core.toolkit.StringUtils;
  9. import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
  10. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
  11. import com.ylx.common.config.WechatAccountConfig;
  12. import com.ylx.common.constant.MassageConstants;
  13. import com.ylx.common.core.domain.R;
  14. import com.ylx.common.exception.ServiceException;
  15. import com.ylx.common.utils.SecurityUtils;
  16. import com.ylx.massage.domain.*;
  17. import com.ylx.massage.domain.vo.*;
  18. import com.ylx.massage.enums.BillTypeEnum;
  19. import com.ylx.massage.enums.DiscountTypeEnum;
  20. import com.ylx.massage.enums.JsStatusEnum;
  21. import com.ylx.massage.enums.OrderStatusEnum;
  22. import com.ylx.massage.mapper.TOrderMapper;
  23. import com.ylx.massage.service.*;
  24. import com.ylx.massage.utils.*;
  25. import lombok.extern.slf4j.Slf4j;
  26. import org.apache.commons.compress.utils.Lists;
  27. import org.springframework.stereotype.Service;
  28. import org.springframework.transaction.annotation.Transactional;
  29. import javax.annotation.Resource;
  30. import java.math.BigDecimal;
  31. import java.math.RoundingMode;
  32. import java.time.LocalDate;
  33. import java.time.LocalDateTime;
  34. import java.time.LocalTime;
  35. import java.util.*;
  36. import java.util.stream.Collectors;
  37. /**
  38. * 订单表 服务实现类
  39. */
  40. @Service
  41. @Slf4j
  42. public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> implements TOrderService {
  43. @Resource
  44. private TOrderMapper orderMapper;
  45. @Resource
  46. private WechatAccountConfig wxPayProperties;
  47. @Resource
  48. private LocationUtil locationUtil;
  49. @Resource
  50. private TWxUserService wxUserService;
  51. @Resource
  52. private TRechargeService rechargeService;
  53. @Resource
  54. private TXiangmuService xiangmuService;
  55. @Resource
  56. private OrderNumberGenerator generator;
  57. @Resource
  58. private TJsService jsService;
  59. @Resource
  60. private TAddressService addressService;
  61. @Resource
  62. private TConsumptionLogService consumptionLogService;
  63. @Resource
  64. private MassageUtil massageUtil;
  65. @Resource
  66. private CouponReceiveService couponReceiveService;
  67. @Resource
  68. private CouponService couponService;
  69. @Resource
  70. private WeChatUtil weChatUtil;
  71. @Resource
  72. private RefundVoucherService refundVoucherService;
  73. @Resource
  74. private OrderValidationService orderValidationService;
  75. @Resource
  76. private OrderNotificationService orderNotificationService;
  77. @Resource
  78. private OrderAllocationLogService allocationLogService;
  79. @Resource
  80. private CancelOrderApplicationService cancelOrderApplicationService;
  81. /**
  82. * 判断是否免车费
  83. * 时间段判断:
  84. * - 白天时段(7:30-20:00):距离 ≤ 技师白天免车费里程 → 免费
  85. * - 夜间时段(20:00-7:30):距离 ≤ 技师夜间免车费里程 → 免费
  86. *
  87. * @param js
  88. * @param distance
  89. * @return Boolean
  90. */
  91. public Boolean isFree(TJs js, BigDecimal distance) {
  92. Date date = new Date();
  93. //白天免车费(07.30-20.00)
  94. long current = Long.parseLong(DateTimeUtils.numTime(date));
  95. if (current >= MassageConstants.START_FREE && current <= MassageConstants.END_FREE) {
  96. if (js.getDaytimeMileage().compareTo(distance) >= 0) {
  97. //免车费
  98. return Boolean.TRUE;
  99. } else {
  100. return Boolean.FALSE;
  101. }
  102. } else {
  103. //夜间免车费(20.00-07.30)
  104. if (js.getNigthMileage().compareTo(distance) >= 0) {
  105. //免车费
  106. return Boolean.TRUE;
  107. } else {
  108. return Boolean.FALSE;
  109. }
  110. }
  111. }
  112. /**
  113. * 添加订单
  114. *
  115. * @param order
  116. * @return TOrder
  117. */
  118. @Override
  119. @Transactional(rollbackFor = Exception.class)
  120. public TOrder addOrder(TOrder order) {
  121. String jsId = order.getcJsId();
  122. // 1. 基础参数校验
  123. if (StringUtils.isBlank(jsId)) {
  124. throw new ServiceException("请选择技师");
  125. }
  126. if (order.getcGoods().isEmpty()) {
  127. throw new ServiceException("请选择项目");
  128. }
  129. TJs js = jsService.getById(jsId);
  130. if (js == null) {
  131. throw new ServiceException("技师不存在");
  132. }
  133. Integer techType = js.getTechType();
  134. // 虚拟技师
  135. if(techType.equals(1)){
  136. //虚拟技师订单
  137. order.setVirtualOrderFlag(1);
  138. //虚拟技师订单未分配
  139. order.setVirtualOrderAllocation(1);
  140. }else{
  141. //真实订单
  142. order.setVirtualOrderFlag(0);
  143. order.setVirtualOrderAllocation(0);
  144. }
  145. // 2. 【新增】订单状态锁校验 - 检查技师是否可以接单
  146. // 确保技师没有进行中的订单,保证服务时间互斥
  147. log.info("开始校验技师 {} 是否可以接单,订单号:{}", order.getcJsId(), order.getOrderNo());
  148. orderValidationService.canAcceptOrder(order.getcJsId(), order);
  149. log.info("技师 {} 接单校验通过,继续创建订单", order.getcJsId());
  150. //优惠卷减免
  151. // List<CouponReceiveVo> coupons = couponReceiveService.getByOpenId(order.getcOpenId());
  152. // BigDecimal preferential = this.setCoupon(coupons);
  153. // order.setPreferential(preferential);
  154. // 生成订单号
  155. order.setOrderNo(generator.generateNextOrderNumber(OrderNumberGenerator.KEY_PREFIX_ORDER));
  156. //订单价格
  157. List<TXiangmu> list = JSONObject.parseArray(order.getcGoods().toJSONString(), TXiangmu.class);
  158. BigDecimal sum = list.stream().map(TXiangmu::getSum).reduce(BigDecimal.ZERO, BigDecimal::add);
  159. //订单总金额
  160. order.setdTotalMoney(sum);
  161. //获取用户默认地址
  162. TAddress address = addressService.getByOpenId(order.getcOpenId());
  163. if (address == null) {
  164. throw new ServiceException("请先添加地址");
  165. }
  166. //添加技师位置信息
  167. locationUtil.geoAdd(LocationUtil.GEO_KEY_USER, js.getcOpenId() + order.getOrderNo(), Double.parseDouble(js.getLongitude().toString()), Double.parseDouble(js.getLatitude().toString()));
  168. //添加用户位置信息
  169. locationUtil.geoAdd(LocationUtil.GEO_KEY_USER, order.getcOpenId() + order.getOrderNo(), Double.parseDouble(address.getLongitude().toString()), Double.parseDouble(address.getLatitude().toString()));
  170. //计算距离
  171. double distance = locationUtil.getDistance(js.getcOpenId() + order.getOrderNo(), order.getcOpenId() + order.getOrderNo());
  172. log.info("技师与用户之间的距离:{}", distance);
  173. locationUtil.remove(LocationUtil.GEO_KEY_USER, js.getcOpenId() + order.getOrderNo(), order.getcOpenId() + order.getOrderNo());
  174. order.setDistance(new BigDecimal(distance));
  175. //计算车费
  176. if (order.getDistance() != null && order.getDistance().compareTo(BigDecimal.ZERO) > 0 && StringUtils.isBlank(order.getParentNo())) {
  177. //判断是否可以免车费
  178. if (!this.isFree(js, order.getDistance())) {
  179. BigDecimal bigDecimal = massageUtil.calculateTaxiFare(order.getDistance(), js.getDeptId());
  180. order.setFare(bigDecimal.setScale(MassageConstants.INTEGER_TWO, RoundingMode.HALF_UP));
  181. }
  182. }
  183. //总价 = 订单 + 车费
  184. order.setTotalPrice(sum.add(Optional.ofNullable(order.getFare()).orElse(BigDecimal.ZERO)));
  185. if (order.getParentNo() != null && order.getOrderType() == 2) {
  186. //升级订单 补差价
  187. TOrder partOrder = this.getByNo(order.getParentNo());
  188. order.setPriceDifference(order.getTotalPrice().subtract(partOrder.getTotalPrice()));
  189. }
  190. order.setAddress(address.getAddress());
  191. order.setName(address.getName());
  192. order.setLatitude(address.getLatitude());
  193. order.setLongitude(address.getLongitude());
  194. order.setcPhone(address.getPhone());
  195. //设置用户姓名
  196. order.setcName(address.getUserName());
  197. order.setAtlasAdd(address.getAtlasAdd());
  198. order.setDeptId(js.getDeptId());
  199. order.setDeptName(js.getCity());
  200. //设置订单状态:待支付
  201. order.setnStatus(OrderStatusEnum.WAIT_PAY.getCode());
  202. order.setDtCreateTime(LocalDateTime.now());
  203. Date date = DateTimeUtils.addMinute(new Date(), 10);
  204. order.setcTime(DateTimeUtils.formatDate(date, "yyyy-MM-dd HH:mm:ss"));
  205. save(order);
  206. return order;
  207. }
  208. private BigDecimal setCoupon(List<CouponReceiveVo> coupons) {
  209. //过滤过期的优惠券
  210. coupons = coupons.stream().filter(coupon -> coupon.getExpirationTime().after(new Date())).collect(Collectors.toList());
  211. //无门槛优惠券
  212. List<CouponReceiveVo> collect = coupons.stream().filter(coupon -> coupon.getDiscountType().equals(DiscountTypeEnum.NO_THRESHOLD.getCode())).collect(Collectors.toList());
  213. //支付成功 后 删除优惠卷
  214. // couponReceiveService.removeCoupons(collect);
  215. //计算优惠金额
  216. return collect.stream().map(CouponReceiveVo::getDiscountValue).reduce(BigDecimal.ZERO, BigDecimal::add);
  217. }
  218. @Override
  219. public void payNotifyOrder(String outTradeNo) {
  220. //查询未支付的订单
  221. LambdaQueryWrapper<TOrder> queryWrapper = new LambdaQueryWrapper<>();
  222. queryWrapper.eq(TOrder::getOrderNo, outTradeNo).eq(TOrder::getnStatus, OrderStatusEnum.WAIT_PAY.getCode());
  223. TOrder orderNew = this.getOne(queryWrapper);
  224. if (orderNew == null) {
  225. log.error("订单 {} 未支付状态不存在", outTradeNo);
  226. return;
  227. }
  228. // 设置微信支付
  229. orderNew.setPayType(1);
  230. TWxUser user = wxUserService.getByOpenId(orderNew.getcOpenId());
  231. orderPayManage(user, orderNew);
  232. }
  233. @Override
  234. public Object updateAddressById(TOrder borrow) {
  235. TOrder order = this.getById(borrow.getcId());
  236. if (borrow.getLatitude() != null && borrow.getLatitude() != 0 && borrow.getLongitude() != null && borrow.getLongitude() != 0) {
  237. order.setAtlasAdd(borrow.getAtlasAdd());
  238. order.setcName(borrow.getcName());
  239. order.setcPhone(borrow.getcPhone());
  240. order.setName(borrow.getName());
  241. order.setAddress(borrow.getAddress());
  242. order.setLatitude(borrow.getLatitude());
  243. order.setLongitude(borrow.getLongitude());
  244. TJs js = jsService.getById(order.getcJsId());
  245. //添加技师位置信息
  246. locationUtil.geoAdd(LocationUtil.GEO_KEY_USER, js.getcOpenId() + order.getOrderNo(), Double.parseDouble(js.getLongitude().toString()), Double.parseDouble(js.getLatitude().toString()));
  247. //添加用户位置信息
  248. locationUtil.geoAdd(LocationUtil.GEO_KEY_USER, order.getcOpenId() + order.getOrderNo(), Double.parseDouble(borrow.getLongitude().toString()), Double.parseDouble(borrow.getLatitude().toString()));
  249. double distance = locationUtil.getDistance(js.getcOpenId() + order.getOrderNo(), order.getcOpenId() + order.getOrderNo());
  250. locationUtil.remove(LocationUtil.GEO_KEY_USER, js.getcOpenId() + order.getOrderNo(), order.getcOpenId() + order.getOrderNo());
  251. order.setDistance(new BigDecimal(distance));
  252. //计算车费
  253. if (order.getDistance() != null && order.getDistance().compareTo(BigDecimal.ZERO) > 0) {
  254. //判断是否可以免车费
  255. if (!this.isFree(js, order.getDistance())) {
  256. BigDecimal bigDecimal = massageUtil.calculateTaxiFare(order.getDistance(), order.getDeptId());
  257. order.setFare(bigDecimal.setScale(MassageConstants.INTEGER_TWO, RoundingMode.HALF_UP));
  258. }
  259. }
  260. order.setTotalPrice(order.getdTotalMoney().add(Optional.ofNullable(order.getFare()).orElse(BigDecimal.ZERO)));
  261. this.updateById(order);
  262. }
  263. return order;
  264. }
  265. @Override
  266. public Object depart(TOrder order) {
  267. LambdaQueryWrapper<TOrder> wrapper = new LambdaQueryWrapper<>();
  268. wrapper.eq(TOrder::getcId, order.getcId()).eq(TOrder::getnStatus, OrderStatusEnum.RECEIVED_ORDER.getCode());
  269. //设置订单状态:已出发
  270. order.setnStatus(OrderStatusEnum.DEPART.getCode());
  271. order.setDepartTime(new Date());
  272. order.setDepartLongitude(Optional.ofNullable(order.getDepartLongitude()).orElse(BigDecimal.ZERO));
  273. order.setDepartLatitude(Optional.ofNullable(order.getDepartLatitude()).orElse(BigDecimal.ZERO));
  274. return this.update(order, wrapper);
  275. }
  276. @Override
  277. public Integer getOrderNum(String jsid, Date startDate, Date endDate) {
  278. return orderMapper.getOrderNum(jsid, startDate, endDate);
  279. }
  280. @Override
  281. public Integer getAddNum(String jsid, Date startDate, Date endDate) {
  282. return orderMapper.getAddNum(jsid, startDate, endDate);
  283. }
  284. @Override
  285. public Integer getUpgradeNum(String jsid, Date startDate, Date endDate) {
  286. return orderMapper.getUpgradeNum(jsid, startDate, endDate);
  287. }
  288. @Override
  289. public BigDecimal getTurnover(String jsid, Date startDate, Date endDate) {
  290. return orderMapper.getTurnover(jsid, startDate, endDate);
  291. }
  292. @Override
  293. @Transactional(rollbackFor = Exception.class)
  294. public TOrder transferOrder(TOrder order) {
  295. // ========== 第1步:参数校验 ==========
  296. if (StringUtils.isBlank(order.getcId())) {
  297. throw new ServiceException("订单id不能为空");
  298. }
  299. if (StringUtils.isBlank(order.getcJsId())) {
  300. throw new ServiceException("转单技师ID不能为空");
  301. }
  302. // 定义操作结果(默认为失败)
  303. Integer operationResult = 1; // 1-失败
  304. // 定义操作记录所需的变量
  305. String orderId = null;
  306. String orderNo = null;
  307. String oldTechnicianId = null;
  308. String oldTechnicianName = null;
  309. Integer oldTechnicianStatusBefore = null;
  310. Integer oldTechnicianStatusAfter = null;
  311. String newTechnicianId = null;
  312. String newTechnicianName = null;
  313. Integer newTechnicianStatusBefore = null;
  314. Integer newTechnicianStatusAfter = null;
  315. Integer orderStatusBefore = null;
  316. Integer orderStatusAfter = null;
  317. String operatorId = null;
  318. String operatorName = null;
  319. String operationReason = "虚拟订单分配";
  320. try {
  321. // ========== 第2步:查询原订单信息 ==========
  322. TOrder oldOrder = this.getById(order.getcId());
  323. if (oldOrder == null) {
  324. throw new ServiceException("订单不存在");
  325. }
  326. //原技师ID
  327. oldTechnicianId = oldOrder.getcJsId();
  328. //新技师ID
  329. newTechnicianId = order.getcJsId();
  330. // 记录订单操作前状态
  331. orderStatusBefore = oldOrder.getnStatus();
  332. orderId = oldOrder.getcId();
  333. orderNo = oldOrder.getOrderNo();
  334. log.info("开始转单操作 - 订单号:{}, 原技师ID:{}, 新技师ID:{}, 订单状态:{}", oldOrder.getOrderNo(), oldTechnicianId, newTechnicianId, orderStatusBefore);
  335. // ========== 第3步:查询原技师信息 ==========
  336. TJs oldTechnician = jsService.getById(oldTechnicianId);
  337. if (oldTechnician == null) {
  338. throw new ServiceException("原技师不存在");
  339. }
  340. oldTechnicianName = oldTechnician.getcName();
  341. oldTechnicianStatusBefore = oldTechnician.getnStatus();
  342. // ========== 第4步:查询新技师信息 ==========
  343. TJs newTechnician = jsService.getById(newTechnicianId);
  344. if (newTechnician == null) {
  345. throw new ServiceException("新技师不存在");
  346. }
  347. newTechnicianName = newTechnician.getcName();
  348. newTechnicianStatusBefore = newTechnician.getnStatus();
  349. // ========== 第5步:更新订单技师信息 ==========
  350. oldOrder.setOldJsId(oldTechnicianId); // 保存原技师ID
  351. oldOrder.setcJsId(newTechnicianId); // 更新为新技师ID
  352. log.info("更新订单技师 - 订单号:{}, 原技师:[ID:{}, 姓名:{}], 新技师:[ID:{}, 姓名:{}]", oldOrder.getOrderNo(), oldTechnicianId, oldTechnicianName, newTechnicianId, newTechnicianName);
  353. if (!this.updateById(oldOrder)) {
  354. throw new ServiceException("转单失败:更新订单技师信息失败");
  355. }
  356. // 记录订单操作后状态(转单后状态通常保持不变)
  357. orderStatusAfter = oldOrder.getnStatus();
  358. // ========== 第6步:更新新技师状态(可服务 → 服务中)==========
  359. TJs newJsUpdate = new TJs();
  360. newJsUpdate.setId(newTechnicianId);
  361. newJsUpdate.setnStatus(JsStatusEnum.JS_SERVICE.getCode());
  362. if (!jsService.updateById(newJsUpdate)) {
  363. throw new ServiceException("转单失败:更新新技师状态失败");
  364. }
  365. newTechnicianStatusAfter = JsStatusEnum.JS_SERVICE.getCode();
  366. // ========== 第7步:更新原技师状态(服务中 → 可服务)==========
  367. TJs oldJsUpdate = new TJs();
  368. oldJsUpdate.setId(oldTechnicianId);
  369. oldJsUpdate.setnStatus(JsStatusEnum.JS_SERVICEABLE.getCode());
  370. if (!jsService.updateById(oldJsUpdate)) {
  371. throw new ServiceException("转单失败:更新原技师状态失败");
  372. }
  373. oldTechnicianStatusAfter = JsStatusEnum.JS_SERVICEABLE.getCode();
  374. log.info("更新技师状态完成 - 新技师:{} {}→{}, 原技师:{} {}→{}",
  375. newTechnicianName, getStatusName(newTechnicianStatusBefore), getStatusName(newTechnicianStatusAfter),
  376. oldTechnicianName, getStatusName(oldTechnicianStatusBefore), getStatusName(oldTechnicianStatusAfter));
  377. // ========== 第8步:获取操作人信息 ==========
  378. operatorId = SecurityUtils.getUserId() != null ? SecurityUtils.getUserId().toString() : "ADMIN";
  379. operatorName = SecurityUtils.getUsername() != null ? SecurityUtils.getUsername() : "系统管理员";
  380. // ========== 第9步:转单成功,设置操作结果为成功 ==========
  381. operationResult = 0; // 0-成功
  382. log.info("转单操作完成 - 订单号:{}", oldOrder.getOrderNo());
  383. return oldOrder;
  384. } catch (ServiceException e) {
  385. // 业务异常,操作失败
  386. log.error("转单操作失败 - 订单号:{}, 错误信息:{}", orderNo, e.getMessage());
  387. operationResult = 1; // 1-失败
  388. throw e;
  389. } catch (Exception e) {
  390. // 系统异常,操作失败
  391. log.error("转单操作异常 - 订单号:{}, 异常信息:{}", orderNo, e.getMessage(), e);
  392. operationResult = 1; // 1-失败
  393. throw new ServiceException("转单操作异常:" + e.getMessage());
  394. } finally {
  395. // ========== 第10步:记录转单操作日志(无论成功或失败都记录)==========
  396. try {
  397. // 只有在获取到基本信息后才记录日志
  398. if (orderId != null && orderNo != null && oldTechnicianId != null && newTechnicianId != null) {
  399. allocationLogService.recordTransferOrder(
  400. orderId, // orderId
  401. orderNo, // orderNo
  402. oldTechnicianId, // oldTechnicianId
  403. oldTechnicianName, // oldTechnicianName
  404. oldTechnicianStatusBefore, // oldTechnicianStatusBefore
  405. oldTechnicianStatusAfter, // oldTechnicianStatusAfter
  406. newTechnicianId, // newTechnicianId
  407. newTechnicianName, // newTechnicianName
  408. newTechnicianStatusBefore, // newTechnicianStatusBefore
  409. newTechnicianStatusAfter, // newTechnicianStatusAfter
  410. orderStatusBefore, // orderStatusBefore
  411. orderStatusAfter, // orderStatusAfter
  412. operatorId, // operatorId
  413. operatorName, // operatorName
  414. operationReason, // operationReason
  415. operationResult // operationResult(0-成功,1-失败)
  416. );
  417. String resultDesc = operationResult == 0 ? "成功" : "失败";
  418. log.info("转单操作记录已保存 - 订单号:{}, 操作结果:{}", orderNo, resultDesc);
  419. }
  420. } catch (Exception e) {
  421. // 记录日志失败不影响转单操作
  422. log.error("记录转单操作日志失败 - 订单号:{}, 错误信息:{}", orderNo, e.getMessage(), e);
  423. }
  424. }
  425. }
  426. /**
  427. * 获取技师状态名称
  428. *
  429. * @param status 状态码
  430. * @return String 状态名称
  431. */
  432. private String getStatusName(Integer status) {
  433. if (status == null) {
  434. return "未知";
  435. }
  436. switch (status) {
  437. case 0:
  438. return "可服务";
  439. case 1:
  440. return "服务中";
  441. case 2:
  442. return "不可服务";
  443. default:
  444. return "未知(" + status + ")";
  445. }
  446. }
  447. @Override
  448. public List<HomeBlock> getBlock(Date start, Date end, String deptId) {
  449. return orderMapper.getBlock(start, end, deptId);
  450. }
  451. @Override
  452. public OrderVerificationVo verification(TOrder order) {
  453. if (StringUtils.isBlank(order.getCouponReceiveId())) {
  454. throw new ServiceException("认领优惠券id为空");
  455. }
  456. if (StringUtils.isBlank(order.getcId())) {
  457. throw new ServiceException("订单id为空");
  458. }
  459. OrderVerificationVo orderVerificationVo = new OrderVerificationVo();
  460. TOrder tOrder = this.getById(order.getcId());
  461. orderVerificationVo.setCouponReceiveId(order.getCouponReceiveId());
  462. CouponReceive couponReceive = couponReceiveService.getById(order.getCouponReceiveId());
  463. Coupon coupon = couponService.getById(couponReceive.getCouponId());
  464. log.info("订单信息,{}", tOrder);
  465. log.info("优惠卷信息,{}", coupon);
  466. //折扣券
  467. if (coupon.getDiscountType() == 2) {
  468. //判断门槛金额
  469. if (tOrder.getTotalPrice().compareTo(coupon.getThresholdAmount()) >= 0) {
  470. //折扣值
  471. BigDecimal divide = coupon.getRebValue().divide(new BigDecimal(10));
  472. //优惠后的金额 = 订单总金额*折扣值
  473. BigDecimal bigDecimal = tOrder.getTotalPrice().multiply(divide).setScale(MassageConstants.INTEGER_TWO, RoundingMode.HALF_UP);
  474. //优惠值
  475. orderVerificationVo.setPreferential(tOrder.getTotalPrice().subtract(bigDecimal));
  476. orderVerificationVo.setTotalPrice(bigDecimal);
  477. } else {
  478. throw new ServiceException("不满足优惠券门槛金额");
  479. }
  480. } else {
  481. if (tOrder.getTotalPrice().compareTo(coupon.getThresholdAmount()) >= 0) {
  482. //优惠值
  483. orderVerificationVo.setPreferential(coupon.getDiscountValue());
  484. orderVerificationVo.setTotalPrice(tOrder.getTotalPrice().subtract(coupon.getDiscountValue()));
  485. } else {
  486. throw new ServiceException("不满足优惠券门槛金额");
  487. }
  488. }
  489. if (orderVerificationVo.getTotalPrice().compareTo(BigDecimal.ZERO) < 0) {
  490. throw new ServiceException("当前项目不可用");
  491. }
  492. return orderVerificationVo;
  493. }
  494. private TOrder getOrder(TOrder tOrder) {
  495. if (updateById(tOrder)) {
  496. return tOrder;
  497. } else {
  498. throw new ServiceException("优惠券核销失败");
  499. }
  500. }
  501. /**
  502. * 支付订单
  503. *
  504. * @param order
  505. * @return R
  506. */
  507. @Override
  508. public R payOrder(TOrder order) throws Exception {
  509. // 根据订单ID查询订单信息
  510. TOrder orderNew = getById(order.getcId());
  511. if (!orderNew.getnStatus().equals(OrderStatusEnum.WAIT_PAY.getCode())) {
  512. throw new ServiceException("该订单已经支付或者超时被取消");
  513. }
  514. TJs js = jsService.getById(orderNew.getcJsId());
  515. if (StringUtils.isBlank(orderNew.getParentNo())) {
  516. if (null == js || js.getnStatus().equals(JsStatusEnum.JS_SERVICE.getCode())) {
  517. throw new ServiceException("该技师已在服务中请重新下单");
  518. }
  519. }
  520. orderNew.setPayType(order.getPayType());
  521. //优惠券核销
  522. if (StringUtils.isNotBlank(order.getCouponReceiveId())) {
  523. orderNew.setCouponReceiveId(order.getCouponReceiveId());
  524. orderNew.setPreferential(order.getPreferential());
  525. orderNew.setTotalPrice(order.getTotalPrice());
  526. if (!updateById(orderNew)) {
  527. throw new ServiceException("支付失败");
  528. }
  529. }
  530. //判断支付方式
  531. if (order.getPayType().equals(MassageConstants.INTEGER_ONE)) {
  532. //微信支付
  533. R resp = rechargeService.getPay(orderNew.getOrderNo(), orderNew.getTotalPrice(), orderNew.getcOpenId(), BillTypeEnum.WX_PAY.getInfo(), BillTypeEnum.WX_PAY.getCode().toString());
  534. //添加待接单消息通知(技师侧)这块的逻辑在回调接口中
  535. //orderNotificationService.sendPendingRemindNotification(orderNew);
  536. return resp;
  537. }
  538. TWxUser user = wxUserService.getByOpenId(orderNew.getcOpenId());
  539. if (null == user) {
  540. throw new ServiceException("用户不存在");
  541. }
  542. //现金支付
  543. if (order.getPayType().equals(MassageConstants.INTEGER_THREE)) {
  544. //现金支付
  545. orderPayManage(user, orderNew);
  546. //添加待接单消息通知(技师侧)
  547. orderNotificationService.sendPendingRemindNotification(orderNew);
  548. return R.ok();
  549. }
  550. if (user.getdBalance().compareTo(orderNew.getTotalPrice()) < MassageConstants.INTEGER_ZERO) {
  551. throw new ServiceException("账户金额不够请充值");
  552. } else {
  553. orderPayManage(user, orderNew);
  554. //添加待接单消息通知(技师侧)
  555. orderNotificationService.sendPendingRemindNotification(orderNew);
  556. return R.ok();
  557. }
  558. }
  559. /**
  560. * 新订单通知
  561. *
  562. * @param order
  563. */
  564. public void newOrderNotification(TOrder order) {
  565. cn.hutool.json.JSONObject param = JSONUtil.createObj();
  566. //订单号
  567. param.set("character_string9", JSONUtil.createObj().set("value", order.getOrderNo()));
  568. //电话
  569. param.set("phone_number14", JSONUtil.createObj().set("value", order.getcPhone()));
  570. param.set("thing18", JSONUtil.createObj().set("value", order.getcName()));
  571. param.set("time6", JSONUtil.createObj().set("value", DateTimeUtils.formatDate(new Date(), DateTimeUtils.DATE_NUMBER_YEAR_MONTH_FORMAT)));
  572. param.set("thing27", JSONUtil.createObj().set("value", order.getName()));
  573. TJs js = jsService.getById(order.getcJsId());
  574. weChatUtil.notification(js.getcOpenId(), wxPayProperties.getTemplateId1(), param);
  575. }
  576. /**
  577. * 订单支付管理
  578. *
  579. * @param user
  580. * @param orderNew
  581. */
  582. @Transactional(rollbackFor = Exception.class)
  583. public void orderPayManage(TWxUser user, TOrder orderNew) {
  584. //更新优惠卷状态
  585. if (StringUtils.isNotBlank(orderNew.getCouponReceiveId())) {
  586. CouponReceive couponReceive = new CouponReceive();
  587. couponReceive.setId(orderNew.getCouponReceiveId());
  588. couponReceive.setUseState(MassageConstants.INTEGER_TWO);
  589. if (!couponReceiveService.updateById(couponReceive)) {
  590. log.error("优惠券状态更新失败id:,{}", orderNew.getCouponReceiveId());
  591. }
  592. }
  593. // 更新用户金额 及下单此时
  594. TWxUser paramUser = new TWxUser();
  595. paramUser.setcOpenid(user.getcOpenid());
  596. // 余额支付
  597. if (orderNew.getPayType().equals(MassageConstants.INTEGER_TWO)) {
  598. paramUser.setdBalance(user.getdBalance().subtract(orderNew.getTotalPrice()));
  599. }
  600. paramUser.setdMoney(user.getdMoney().add(orderNew.getTotalPrice()));
  601. paramUser.setnNum(user.getnNum() + MassageConstants.INTEGER_ONE);
  602. paramUser.setId(user.getId());
  603. wxUserService.updateById(paramUser);
  604. //增加消费记录
  605. TConsumptionLog tConsumptionLog = new TConsumptionLog();
  606. tConsumptionLog.setAmount(orderNew.getTotalPrice().negate());
  607. tConsumptionLog.setBillNo(orderNew.getOrderNo());
  608. tConsumptionLog.setOpenId(orderNew.getcOpenId());
  609. if (orderNew.getPayType().equals(MassageConstants.INTEGER_TWO)) {
  610. tConsumptionLog.setBillType(BillTypeEnum.BALANCE_PAYMENT.getCode());
  611. tConsumptionLog.setNote("余额支付");
  612. } else if(orderNew.getPayType().equals(MassageConstants.INTEGER_ONE)){
  613. tConsumptionLog.setBillType(BillTypeEnum.WX_PAY.getCode());
  614. tConsumptionLog.setNote("微信支付");
  615. } else {
  616. tConsumptionLog.setBillType(BillTypeEnum.CASH_PAYMENT.getCode());
  617. tConsumptionLog.setNote("现金支付");
  618. }
  619. consumptionLogService.save(tConsumptionLog);
  620. // 更新项目数据
  621. JSONArray objects = orderNew.getcGoods();
  622. objects.forEach(item -> {
  623. UpdateWrapper<TXiangmu> wrapper = new UpdateWrapper<>();
  624. // 获取参数
  625. wrapper.lambda().eq(TXiangmu::getcId, ((JSONObject) item).getString("cId"));
  626. // 设置数量
  627. wrapper.setSql(" n_sale_number = n_sale_number + " + ((JSONObject) item).getInteger("number"));
  628. xiangmuService.update(wrapper);
  629. });
  630. TOrder orderParam = new TOrder();
  631. orderParam.setPayType(orderNew.getPayType());
  632. orderParam.setcId(orderNew.getcId());
  633. orderParam.setnStatus(OrderStatusEnum.WAIT_JD.getCode());
  634. orderParam.setPayTime(new Date());
  635. //加钟的订单支付完直接服务中
  636. if (StringUtils.isNotBlank(orderNew.getParentNo())) {
  637. orderParam.setnStatus(OrderStatusEnum.SERVICE.getCode());
  638. }
  639. // orderParam.setnStatus(OrderStatusEnum.SERVICE.getCode());
  640. //更新及技师状态
  641. updateJs(orderNew);
  642. updateById(orderParam);
  643. //
  644. //this.newOrderNotification(orderNew);
  645. //添加待接单消息通知(技师侧)
  646. orderNotificationService.sendPendingRemindNotification(orderNew);
  647. //电话通知
  648. TJs js = jsService.getById(orderNew.getcJsId());
  649. Sendvoice.sendPhone(js.getcPhone());
  650. }
  651. /**
  652. * 拒绝订单
  653. *
  654. * @param order
  655. */
  656. @Override
  657. public Boolean jujue(TOrder order) {
  658. TOrder orderNew = getById(order.getcId());
  659. TWxUser user = wxUserService.getByOpenId(orderNew.getcOpenId());
  660. // 更新用户金额 及下单此时
  661. TWxUser paramUser = new TWxUser();
  662. paramUser.setcOpenid(user.getcOpenid());
  663. paramUser.setId(user.getId());
  664. // 余额记录
  665. TConsumptionLog tConsumptionLog = new TConsumptionLog();
  666. tConsumptionLog.setAmount(orderNew.getTotalPrice());
  667. tConsumptionLog.setBillNo(orderNew.getOrderNo());
  668. tConsumptionLog.setOpenId(orderNew.getcOpenId());
  669. if (orderNew.getPayType() == 2) {
  670. // 金额归还对应账户
  671. paramUser.setdBalance(user.getdBalance().add(orderNew.getTotalPrice()));
  672. tConsumptionLog.setBillType(BillTypeEnum.REFUSE_ACCEPT_REFUND.getCode());
  673. tConsumptionLog.setNote("拒绝接单退款到余额");
  674. } else {
  675. // 微信支付
  676. // 生成退款单退款
  677. RefundVoucher refundVoucher = new RefundVoucher();
  678. refundVoucher.setRefundNo(generator.generateNextOrderNumber(OrderNumberGenerator.KEY_PREFIX_REFUND));
  679. refundVoucher.setOrderNo(orderNew.getOrderNo());
  680. refundVoucher.setMoney(orderNew.getTotalPrice());
  681. refundVoucher.setOpenId(orderNew.getcOpenId());
  682. refundVoucher.setReStatus(MassageConstants.INTEGER_ZERO);
  683. refundVoucher.setReason("技师拒绝接单");
  684. refundVoucherService.save(refundVoucher);
  685. tConsumptionLog.setBillType(BillTypeEnum.REFUSE_ACCEPT_REFUND.getCode());
  686. tConsumptionLog.setNote("拒绝接单退款到余额");
  687. // 微信退款原路返回
  688. rechargeService.refund(refundVoucher.getRefundNo(), null, orderNew.getOrderNo(), orderNew.getTotalPrice());
  689. }
  690. consumptionLogService.save(tConsumptionLog);
  691. //退优惠卷
  692. if (StringUtils.isNotBlank(orderNew.getCouponReceiveId())) {
  693. CouponReceive couponReceive = couponReceiveService.getById(orderNew.getCouponReceiveId());
  694. couponReceive.setUseState(MassageConstants.INTEGER_ZERO);
  695. couponReceiveService.updateById(couponReceive);
  696. }
  697. log.info("余额支付退款user:{}", user);
  698. // 消费金额对应减少
  699. paramUser.setdMoney(user.getdMoney().subtract(orderNew.getTotalPrice()));
  700. // 下单次数减一
  701. paramUser.setnNum(user.getnNum() - MassageConstants.INTEGER_ONE);
  702. wxUserService.updateById(paramUser);
  703. // 更新项目数据
  704. JSONArray objects = orderNew.getcGoods();
  705. objects.forEach(item -> {
  706. UpdateWrapper<TXiangmu> wrapper = new UpdateWrapper<>();
  707. // 获取参数
  708. wrapper.lambda().eq(TXiangmu::getcId, ((JSONObject) item).getString("cId"));
  709. // 设置数量
  710. wrapper.setSql(" n_sale_number = n_sale_number - " + ((JSONObject) item).getInteger("number"));
  711. xiangmuService.update(wrapper);
  712. });
  713. TOrder orderParam = new TOrder();
  714. orderParam.setcId(orderNew.getcId());
  715. orderParam.setnStatus(OrderStatusEnum.REFUSE.getCode());
  716. orderParam.setReasonRefusal(order.getReasonRefusal());
  717. updateJs(orderNew);
  718. return updateById(orderParam);
  719. }
  720. /**
  721. * 确认服务完成
  722. *
  723. * @param order
  724. * @return Boolean
  725. */
  726. @Override
  727. @Transactional(rollbackFor = Exception.class)
  728. public Boolean confirm(TOrder order) {
  729. // 获取订单信息
  730. TOrder orderNew = getById(order.getcId());
  731. if (!orderNew.getnStatus().equals(OrderStatusEnum.SERVICE.getCode())) {
  732. throw new ServiceException("订单状态不是服务中");
  733. }
  734. // 更新技师信息
  735. TJs jsParam = new TJs();
  736. jsParam.setId(orderNew.getcJsId());
  737. jsParam.setnStatus(JsStatusEnum.JS_SERVICEABLE.getCode());
  738. //判断热度标识
  739. List<TOrder> list = list(new LambdaQueryWrapper<TOrder>().eq(TOrder::getcJsId, orderNew.getcJsId())
  740. .ge(TOrder::getDtCreateTime, DateTimeUtils.addDays(new Date(), -3))
  741. .ge(TOrder::getnStatus, OrderStatusEnum.WAIT_EVALUATE.getCode()));
  742. if (list.size() >= 2) {
  743. // 设置热度标识:1
  744. jsParam.setnB3(MassageConstants.INTEGER_ONE);
  745. }
  746. // 更新技师状态
  747. jsService.updateById(jsParam);
  748. // 更新技师钱包金额
  749. TJs jsById = jsService.getById(orderNew.getcJsId());
  750. // 获取技师抽成
  751. BigDecimal multiply = orderNew.getTotalPrice().multiply(new BigDecimal(jsById.getnBili()));
  752. multiply = multiply.divide(new BigDecimal(100), MassageConstants.INTEGER_TWO, RoundingMode.HALF_UP);
  753. // 获取技师所对应的用户
  754. TWxUser jsUser = wxUserService.getByOpenId(jsById.getcOpenId());
  755. // 更新余额
  756. jsUser.setdBalance(jsUser.getdBalance().add(multiply));
  757. // 更新总钱数
  758. jsUser.setdAllMoney(jsUser.getdAllMoney().add(multiply));
  759. wxUserService.updateById(jsUser);
  760. //增加消费记录
  761. TConsumptionLog tConsumptionLog = new TConsumptionLog();
  762. tConsumptionLog.setAmount(multiply);
  763. tConsumptionLog.setBillNo(orderNew.getOrderNo());
  764. tConsumptionLog.setOpenId(jsUser.getcOpenid());
  765. tConsumptionLog.setBillType(BillTypeEnum.INCOME.getCode());
  766. tConsumptionLog.setNote("技师收益");
  767. consumptionLogService.save(tConsumptionLog);
  768. // 如果该技师有推荐人员 一级
  769. if (StringUtils.isNotBlank(jsUser.getcUpUser())) {
  770. // 获取技师上级对应的用户
  771. TWxUser jsUp = wxUserService.getByOpenId(jsUser.getcUpUser());
  772. extracted(orderNew, jsUp);
  773. //二级
  774. if (StringUtils.isNotBlank(jsUp.getcUpUser())) {
  775. TWxUser jsUpTwo = wxUserService.getByOpenId(jsUp.getcUpUser());
  776. extracted(orderNew, jsUpTwo);
  777. //三级
  778. if (StringUtils.isNotBlank(jsUpTwo.getcUpUser())) {
  779. TWxUser jsUpThree = wxUserService.getByOpenId(jsUpTwo.getcUpUser());
  780. extracted(orderNew, jsUpThree);
  781. }
  782. }
  783. }
  784. // 更新订单
  785. // 订单状态:待评价
  786. orderNew.setnStatus(OrderStatusEnum.WAIT_EVALUATE.getCode());
  787. orderNew.setEndTime(LocalDateTime.now());
  788. updateById(orderNew);
  789. // 添加订单完成消息通知(用户侧)
  790. orderNotificationService.sendCompletedNotification(orderNew);
  791. return true;
  792. }
  793. private void extracted(TOrder orderNew, TWxUser jsUp) {
  794. log.info("TOrderServiceImpl->extracted->jsUp,{}", JSONUtil.toJsonStr(jsUp));
  795. log.info("TOrderServiceImpl->extracted->orderNew,{}",JSONUtil.toJsonStr(orderNew));
  796. BigDecimal up = orderNew.getdTotalMoney().multiply(new BigDecimal("10"));
  797. up = up.divide(new BigDecimal(100), 2, RoundingMode.HALF_UP);
  798. // 更新余额
  799. jsUp.setdBalance(jsUp.getdBalance().add(up));
  800. // 更新总钱数
  801. jsUp.setdAllMoney(jsUp.getdAllMoney().add(up));
  802. jsUp.setDistributionAmount(up);
  803. wxUserService.updateById(jsUp);
  804. //记录分销收益
  805. TConsumptionLog tConsumptionLog = new TConsumptionLog();
  806. tConsumptionLog.setAmount(up);
  807. tConsumptionLog.setBillNo(orderNew.getOrderNo());
  808. tConsumptionLog.setOpenId(jsUp.getcOpenid());
  809. tConsumptionLog.setBillType(BillTypeEnum.DISTRIBUTION.getCode());
  810. tConsumptionLog.setNote("分销收益");
  811. consumptionLogService.save(tConsumptionLog);
  812. }
  813. /**
  814. * 获取技师当天可预约时间
  815. *
  816. * @param technicianId 技师ID
  817. * @param dateStr 查询日期(格式:yyyy-MM-dd),为null则查询当天
  818. * @return TechnicianAvailabilityVo 技师当天可预约时间VO
  819. */
  820. @Override
  821. public TechnicianAvailabilityVo getTechnicianAvailability(String technicianId, String dateStr) {
  822. // 1. 参数校验
  823. if (StringUtils.isBlank(technicianId)) {
  824. throw new ServiceException("技师ID不能为空");
  825. }
  826. // 2. 查询技师信息
  827. TJs js = jsService.getById(technicianId);
  828. if (js == null) {
  829. throw new ServiceException("技师不存在");
  830. }
  831. // 3. 解析日期,默认为当天
  832. LocalDate queryDate;
  833. if (StringUtils.isBlank(dateStr)) {
  834. queryDate = LocalDate.now();
  835. } else {
  836. try {
  837. queryDate = LocalDate.parse(dateStr);
  838. } catch (Exception e) {
  839. throw new ServiceException("日期格式错误,请使用 yyyy-MM-dd 格式");
  840. }
  841. }
  842. // 4. 定义当天的所有时间段(以30分钟为间隔,从00:00到23:30)
  843. List<TimeSlotVo> timeSlots = new ArrayList<>();
  844. for (int hour = 0; hour < 24; hour++) {
  845. // 每小时生成两个时间段:xx:00 和 xx:30
  846. timeSlots.add(TimeSlotVo.builder()
  847. .time(String.format("%02d:00", hour))
  848. .available(true)
  849. .build());
  850. timeSlots.add(TimeSlotVo.builder()
  851. .time(String.format("%02d:30", hour))
  852. .available(true)
  853. .build());
  854. }
  855. // 5. 查询技师当天所有进行中的订单
  856. // 开始时间
  857. LocalDateTime startOfDay = queryDate.atStartOfDay();
  858. // 结束时间
  859. LocalDateTime endOfDay = queryDate.plusDays(1).atStartOfDay();
  860. log.info("开始时间:{},结束时间:{}", startOfDay, endOfDay);
  861. LambdaQueryWrapper<TOrder> queryWrapper = new LambdaQueryWrapper<>();
  862. queryWrapper.eq(TOrder::getcJsId, technicianId)
  863. .in(TOrder::getnStatus, OrderStatusEnum.WAIT_JD.getCode(),
  864. OrderStatusEnum.RECEIVED_ORDER.getCode(),
  865. OrderStatusEnum.DEPART.getCode(),
  866. OrderStatusEnum.ARRIVED.getCode(),
  867. OrderStatusEnum.SERVICE.getCode())
  868. .ge(TOrder::getDtCreateTime, startOfDay)
  869. .lt(TOrder::getDtCreateTime, endOfDay)
  870. .eq(TOrder::getIsDelete, 0);
  871. List<TOrder> orders = this.list(queryWrapper);
  872. log.info("技师{},在{}天共有 {} 个进行中的订单", technicianId, queryDate, orders.size());
  873. // 6. 标记不可预约的时间段
  874. LocalDateTime now = LocalDateTime.now();
  875. for (TOrder order : orders) {
  876. // 6.1 计算订单的开始时间和结束时间
  877. LocalDateTime orderStart = OrderTimeRangeUtils.estimateStartTime(order);
  878. LocalDateTime orderEnd = OrderTimeRangeUtils.estimateEndTime(order);
  879. if (orderStart == null || orderEnd == null) {
  880. log.warn("订单 {} 的时间信息不完整,跳过", order.getOrderNo());
  881. continue;
  882. }
  883. // 6.2 限制在查询日期范围内
  884. LocalDateTime effectiveStart = orderStart.isBefore(startOfDay) ? startOfDay : orderStart;
  885. LocalDateTime effectiveEnd = orderEnd.isAfter(endOfDay) ? endOfDay : orderEnd;
  886. // 6.3 标记不可预约的时间段
  887. markTimeSlotsUnavailable(timeSlots, effectiveStart, effectiveEnd, order.getOrderNo());
  888. }
  889. // 7. 根据查询日期判断是否可预约
  890. LocalDate today = LocalDate.now();
  891. if (queryDate.isBefore(today)) {
  892. // 查询日期是过去的日期,所有时间段都不可预约
  893. markAllTimeSlotsUnavailable(timeSlots, "日期已过期");
  894. } else if (queryDate.equals(today)) {
  895. // 查询日期是今天,标记过去的时间为不可预约
  896. markPastTimeSlotsUnavailable(timeSlots, now);
  897. }
  898. // 查询日期是未来的日期,所有时间段默认可预约,无需处理
  899. // 8. 构建返回结果
  900. return TechnicianAvailabilityVo.builder()
  901. .date(queryDate.toString())
  902. .technicianId(technicianId)
  903. .technicianName(js.getcName())
  904. .timeSlots(timeSlots)
  905. .build();
  906. }
  907. /**
  908. * 标记指定时间范围内的时间段为不可预约
  909. *
  910. * @param timeSlots 时间段列表
  911. * @param start 开始时间
  912. * @param end 结束时间
  913. * @param orderNo 订单号
  914. */
  915. private void markTimeSlotsUnavailable(List<TimeSlotVo> timeSlots, LocalDateTime start, LocalDateTime end, String orderNo) {
  916. LocalTime startTime = start.toLocalTime();
  917. LocalTime endTime = end.toLocalTime();
  918. for (TimeSlotVo slot : timeSlots) {
  919. LocalTime slotTime = LocalTime.parse(slot.getTime());
  920. // 判断时间段是否在订单时间范围内
  921. boolean isInRange = !slotTime.isBefore(startTime) && slotTime.isBefore(endTime);
  922. if (isInRange) {
  923. slot.setAvailable(false);
  924. slot.setReason("已有订单");
  925. slot.setOrderNo(orderNo);
  926. }
  927. }
  928. }
  929. /**
  930. * 标记所有时间段为不可预约
  931. *
  932. * @param timeSlots 时间段列表
  933. * @param reason 不可预约原因
  934. */
  935. private void markAllTimeSlotsUnavailable(List<TimeSlotVo> timeSlots, String reason) {
  936. for (TimeSlotVo slot : timeSlots) {
  937. slot.setAvailable(false);
  938. slot.setReason(reason);
  939. slot.setOrderNo(null);
  940. }
  941. }
  942. /**
  943. * 标记过去的时间段为不可预约
  944. *
  945. * @param timeSlots 时间段列表
  946. * @param now 当前时间
  947. */
  948. private void markPastTimeSlotsUnavailable(List<TimeSlotVo> timeSlots, LocalDateTime now) {
  949. LocalTime currentTime = now.toLocalTime();
  950. for (TimeSlotVo slot : timeSlots) {
  951. LocalTime slotTime = LocalTime.parse(slot.getTime());
  952. // 如果当前时间已经过了这个时间段,标记为不可预约
  953. if (slotTime.isBefore(currentTime)) {
  954. slot.setAvailable(false);
  955. slot.setReason("已过期");
  956. slot.setOrderNo(null);
  957. }
  958. }
  959. }
  960. /**
  961. * 取消订单
  962. *
  963. * @param order
  964. * @return Boolean
  965. */
  966. @Override
  967. @Transactional(rollbackFor = Exception.class)
  968. public Boolean cancle(TOrder order) {
  969. // 获取订单信息
  970. // 根据orderid查询订单信息
  971. TOrder orderNew = getById(order.getcId());
  972. //待接单
  973. if (Objects.equals(orderNew.getnStatus(), OrderStatusEnum.WAIT_JD.getCode())) {
  974. TWxUser user = wxUserService.getByOpenId(orderNew.getcOpenId());
  975. // 更新用户金额 及下单此时
  976. TWxUser paramUser = new TWxUser();
  977. paramUser.setId(user.getId());
  978. paramUser.setcOpenid(user.getcOpenid());
  979. TConsumptionLog tConsumptionLog = new TConsumptionLog();
  980. tConsumptionLog.setAmount(orderNew.getTotalPrice());
  981. tConsumptionLog.setBillNo(orderNew.getOrderNo());
  982. tConsumptionLog.setOpenId(orderNew.getcOpenId());
  983. // 余额支付
  984. if (orderNew.getPayType() == 2) {
  985. // 金额归还对应账户
  986. paramUser.setdBalance(user.getdBalance().add(orderNew.getTotalPrice()));
  987. // 余额记录
  988. tConsumptionLog.setBillType(BillTypeEnum.CANCEL_ACCEPT_REFUND.getCode());
  989. tConsumptionLog.setNote("取消订单退款到余额");
  990. //自己取消的不退优惠卷
  991. } else {
  992. // 微信支付
  993. // 生成退款单退款
  994. RefundVoucher refundVoucher = new RefundVoucher();
  995. refundVoucher.setRefundNo(generator.generateNextOrderNumber(OrderNumberGenerator.KEY_PREFIX_REFUND));
  996. refundVoucher.setOrderNo(orderNew.getOrderNo());
  997. refundVoucher.setMoney(orderNew.getTotalPrice());
  998. refundVoucher.setOpenId(orderNew.getcOpenId());
  999. refundVoucher.setReStatus(MassageConstants.INTEGER_ZERO);
  1000. refundVoucher.setReason("技师拒绝接单");
  1001. refundVoucherService.save(refundVoucher);
  1002. tConsumptionLog.setBillType(BillTypeEnum.CANCEL_WX_REFUND.getCode());
  1003. tConsumptionLog.setNote("取消订单退款到微信");
  1004. // 微信退款原路返回
  1005. rechargeService.refund(refundVoucher.getRefundNo(), null, orderNew.getOrderNo(), orderNew.getTotalPrice());
  1006. }
  1007. consumptionLogService.save(tConsumptionLog);
  1008. // 消费金额对应减少
  1009. paramUser.setdMoney(user.getdMoney().subtract(orderNew.getTotalPrice()));
  1010. // 下单次数减一
  1011. paramUser.setnNum(user.getnNum() - MassageConstants.INTEGER_ONE);
  1012. wxUserService.updateById(paramUser);
  1013. // 更新项目数据
  1014. JSONArray objects = orderNew.getcGoods();
  1015. objects.forEach(item -> {
  1016. UpdateWrapper<TXiangmu> wrapper = new UpdateWrapper<>();
  1017. // 获取参数
  1018. wrapper.lambda().eq(TXiangmu::getcId, ((JSONObject) item).getString("cId"));
  1019. // 设置数量
  1020. wrapper.setSql(" n_sale_number = n_sale_number - " + ((JSONObject) item).getInteger("number"));
  1021. xiangmuService.update(wrapper);
  1022. });
  1023. TOrder orderParam = new TOrder();
  1024. orderParam.setcId(orderNew.getcId());
  1025. orderParam.setnStatus(OrderStatusEnum.CANCEL.getCode());
  1026. //更新技师状态
  1027. TJs tJs = new TJs();
  1028. tJs.setId(orderNew.getcJsId());
  1029. tJs.setnStatus(JsStatusEnum.JS_SERVICEABLE.getCode());
  1030. jsService.updateById(tJs);
  1031. updateById(orderParam);
  1032. // 添加取消订单通知(用户侧)
  1033. orderNotificationService.sendCancelledNotification(orderNew);
  1034. // 添加取消订单通知(技师侧)
  1035. orderNotificationService.sendTechnicianCancelledNotification(orderNew);
  1036. return true;
  1037. } else if (Objects.equals(orderNew.getnStatus(), OrderStatusEnum.WAIT_PAY.getCode())) {//待付款
  1038. TOrder orderParam = new TOrder();
  1039. orderParam.setcId(orderNew.getcId());
  1040. orderParam.setnStatus(OrderStatusEnum.CANCEL.getCode());
  1041. updateById(orderParam);
  1042. // 添加取消订单通知
  1043. orderNotificationService.sendCancelledNotification(orderNew);
  1044. // 添加取消订单通知(技师侧)
  1045. orderNotificationService.sendTechnicianCancelledNotification(orderNew);
  1046. return true;
  1047. } else {
  1048. return false;
  1049. }
  1050. }
  1051. @Override
  1052. public TOrder getByNo(String orderNo) {
  1053. LambdaQueryWrapper<TOrder> objectLambdaQueryWrapper = new LambdaQueryWrapper<>();
  1054. return this.getOne(objectLambdaQueryWrapper.eq(TOrder::getOrderNo, orderNo));
  1055. }
  1056. // private TOrder gettOrder(TOrder order) {
  1057. // LambdaUpdateWrapper<TOrder> objectLambdaUpdateWrapper = new LambdaUpdateWrapper<>();
  1058. // objectLambdaUpdateWrapper.eq(TOrder::getOrderNo, order.getOrderNo());
  1059. // return this.getOne(objectLambdaUpdateWrapper);
  1060. // }
  1061. @Override
  1062. public Page<TOrder> getAll(Page<TOrder> page, TOrder order) {
  1063. Page<TOrder> orderPage = orderMapper.getAll(page, order);
  1064. if (orderPage != null && CollectionUtil.isNotEmpty(orderPage.getRecords())) {
  1065. ArrayList<TOrder> ordersList = Lists.newArrayList();
  1066. orderPage.getRecords().forEach(orders -> {
  1067. orders.setStatusName(OrderStatusEnum.getDescByCode(orders.getnStatus()));
  1068. orders.setJsPhone(orders.getJs().getcPhone());
  1069. orders.setJsName(orders.getJs().getcName());
  1070. if (StringUtils.isEmpty(orders.getcTime())) {
  1071. orders.setRemainingTime(0L);
  1072. }
  1073. if (StringUtils.isNotBlank(orders.getcTime()) && DateTimeUtils.dateStringToStamp(orders.getcTime()) > DateTimeUtils.dateToStamp(new Date())) {
  1074. orders.setRemainingTime((DateTimeUtils.dateStringToStamp(orders.getcTime()) - DateTimeUtils.dateToStamp(new Date())) / 1000);
  1075. }
  1076. if (StringUtils.isNotBlank(orders.getcTime()) && DateTimeUtils.dateStringToStamp(orders.getcTime()) < DateTimeUtils.dateToStamp(new Date())) {
  1077. orders.setRemainingTime(0L);
  1078. }
  1079. if (StringUtils.isNotBlank(orders.getOldJsId())) {
  1080. orders.setOldJs(jsService.getById(orders.getOldJsId()));
  1081. }
  1082. ordersList.add(orders);
  1083. });
  1084. orderPage.setRecords(ordersList);
  1085. }
  1086. return orderPage;
  1087. }
  1088. @Override
  1089. @Transactional(rollbackFor = Exception.class)
  1090. public void takingOrders(TOrder order) {
  1091. String orderId = order.getcId();
  1092. if (orderId == null || StringUtils.isBlank(orderId)) {
  1093. throw new IllegalArgumentException("订单ID不能为空");
  1094. }
  1095. TOrder orderNew = this.getById(orderId);
  1096. // 【新增】订单状态锁校验 - 检查技师是否可以接单
  1097. log.info("开始校验技师 {} 是否可以接单,订单号:{}", orderNew.getcJsId(), orderNew.getOrderNo());
  1098. orderValidationService.canAcceptOrder(orderNew.getcJsId(), orderNew);
  1099. log.info("技师 {} 接单校验通过,继续接单流程", orderNew.getcJsId());
  1100. // 检查订单对应的技师是否存在
  1101. // updateJs (orderNew);
  1102. TOrder orderParam = new TOrder();
  1103. orderParam.setcId(orderId);
  1104. //设置订单状态:已接单
  1105. orderParam.setnStatus(OrderStatusEnum.RECEIVED_ORDER.getCode());
  1106. orderParam.setAcceptanceTime(LocalDateTime.now());
  1107. this.updateById(orderParam);
  1108. // 已接单消息通知(用户侧)
  1109. orderNotificationService.sendReceivedNotification(orderNew);
  1110. // 已接单消息通知(技师侧)
  1111. orderNotificationService.sendTechnicianReceivedNotification(orderNew);
  1112. }
  1113. private void updateJs(TOrder orderNew) {
  1114. TJs js = jsService.getById(orderNew.getcJsId());
  1115. if (js == null) {
  1116. throw new IllegalStateException("无法找到对应的技师");
  1117. }
  1118. if (Objects.equals(js.getnStatus(), JsStatusEnum.JS_SERVICEABLE.getCode())) {
  1119. // 更新技师状态
  1120. js.setnStatus(JsStatusEnum.JS_SERVICE.getCode());
  1121. // 确保js.getnNum()不为null,避免 NullPointerException
  1122. int num = js.getnNum() == null ? 0 : js.getnNum();
  1123. js.setnNum(num + MassageConstants.INTEGER_ONE);
  1124. } else {
  1125. // 更新技师状态
  1126. js.setnStatus(JsStatusEnum.JS_SERVICEABLE.getCode());
  1127. // 确保js.getnNum()不为null,避免 NullPointerException
  1128. int num = js.getnNum() == null ? 0 : js.getnNum();
  1129. js.setnNum(num - MassageConstants.INTEGER_ONE);
  1130. }
  1131. jsService.updateById(js);
  1132. }
  1133. /**
  1134. * 申请取消订单(退单申请)
  1135. *
  1136. * 业务流程:
  1137. * 1. 校验订单状态(仅进行中的订单可申请退单)
  1138. * 2. 创建退单申请记录
  1139. * 3. 更新订单状态为"退单待审核"
  1140. *
  1141. * @param order 订单对象
  1142. * @return Boolean 申请结果
  1143. */
  1144. @Override
  1145. @Transactional(rollbackFor = Exception.class)
  1146. public Boolean applyCancle(String cId, String cancelReason) {
  1147. log.info("开始处理退单申请,订单ID:{}", cId);
  1148. // 1. 参数校验
  1149. if (StringUtils.isBlank(cId)) {
  1150. throw new ServiceException("订单ID不能为空");
  1151. }
  1152. if (StringUtils.isBlank(cancelReason)) {
  1153. throw new ServiceException("退单原因不能为空");
  1154. }
  1155. // 2. 根据订单ID查询订单信息
  1156. TOrder existingOrder = this.getById(cId);
  1157. if (existingOrder == null) {
  1158. throw new ServiceException("订单不存在");
  1159. }
  1160. // 3. 创建退单申请记录(内部会校验订单状态和其他业务规则)
  1161. String applicationId;
  1162. try {
  1163. applicationId = cancelOrderApplicationService.createApplication(existingOrder.getcId(), cancelReason);
  1164. log.info("退单申请记录创建成功,申请ID:{}", applicationId);
  1165. } catch (ServiceException e) {
  1166. log.error("创建退单申请失败:{}", e.getMessage());
  1167. throw e;
  1168. }
  1169. // 4. 更新订单状态为"退单待审核"
  1170. existingOrder.setnStatus(OrderStatusEnum.CANCEL_APPLICATION_PENDING.getCode());
  1171. this.updateById(existingOrder);
  1172. log.info("退单申请处理完成,订单ID:{},申请ID:{}", existingOrder.getcId(), applicationId);
  1173. return Boolean.TRUE;
  1174. }
  1175. /**
  1176. * 取消退单申请
  1177. * 用户主动取消退单申请,恢复订单状态
  1178. *
  1179. * 业务流程:
  1180. * 1. 参数校验(订单ID不能为空)
  1181. * 2. 查询订单和退单申请记录
  1182. * 3. 校验订单状态必须为"退单待审核"(6)
  1183. * 4. 校验退单审核状态必须为"待审核"(0)
  1184. * 5. 调用退单申请服务取消申请
  1185. * 6. 恢复订单状态到申请前的原始状态
  1186. *
  1187. * @param order 订单对象,需要包含cId(订单ID)
  1188. * @return Boolean 操作结果
  1189. */
  1190. @Override
  1191. @Transactional(rollbackFor = Exception.class)
  1192. public Boolean cancelApplyCancle(TOrder order) {
  1193. log.info("开始取消退单申请,订单ID:{}", order.getcId());
  1194. // 1. 参数校验
  1195. if (StringUtils.isBlank(order.getcId())) {
  1196. throw new ServiceException("订单ID不能为空");
  1197. }
  1198. // 2. 查询订单信息
  1199. TOrder existingOrder = this.getById(order.getcId());
  1200. if (existingOrder == null) {
  1201. throw new ServiceException("订单不存在");
  1202. }
  1203. // 3. 校验订单状态 - 只有"退单待审核"状态的订单才能取消申请
  1204. Integer currentStatus = existingOrder.getnStatus();
  1205. if (!OrderStatusEnum.CANCEL_APPLICATION_PENDING.getCode().equals(currentStatus)) {
  1206. throw new ServiceException("当前订单状态不允许取消退单申请");
  1207. }
  1208. // 4. 查询退单申请记录
  1209. com.ylx.massage.domain.CancelOrderApplication application = cancelOrderApplicationService.getInfoByOrderId(order.getcId());
  1210. if (application == null) {
  1211. throw new ServiceException("退单申请记录不存在");
  1212. }
  1213. // 5. 校验退单申请状态 - 只有"待审核"的申请才能取消
  1214. if (application.getAuditStatus() != 0) {
  1215. throw new ServiceException("当前退单申请状态不允许取消");
  1216. }
  1217. // 6. 获取订单原始状态(在申请退单时保存的状态)
  1218. Integer originalStatus = application.getOrderStatus();
  1219. if (originalStatus == null) {
  1220. throw new ServiceException("无法获取订单原始状态,取消申请失败");
  1221. }
  1222. // 7. 调用退单申请服务取消申请
  1223. try {
  1224. cancelOrderApplicationService.cancelApplication(order.getcId());
  1225. log.info("退单申请记录取消成功,订单ID:{}", order.getcId());
  1226. } catch (ServiceException e) {
  1227. log.error("取消退单申请失败:{}", e.getMessage());
  1228. throw e;
  1229. }
  1230. // 8. 恢复订单状态到原始状态
  1231. existingOrder.setnStatus(originalStatus);
  1232. boolean updated = this.updateById(existingOrder);
  1233. if (!updated) {
  1234. throw new ServiceException("恢复订单状态失败");
  1235. }
  1236. log.info("取消退单申请处理完成,订单ID:{},恢复到状态:{}", order.getcId(), originalStatus);
  1237. return Boolean.TRUE;
  1238. }
  1239. }