TOrderServiceImpl.java 62 KB

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