TOrderServiceImpl.java 60 KB

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