TOrderServiceImpl.java 54 KB

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