package com.ylx.massage.service.impl; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.json.JSONUtil; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import com.baomidou.mybatisplus.core.toolkit.StringUtils; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.ylx.common.config.WechatAccountConfig; import com.ylx.common.constant.MassageConstants; import com.ylx.common.core.domain.R; import com.ylx.common.exception.ServiceException; import com.ylx.common.utils.SecurityUtils; import com.ylx.massage.domain.*; import com.ylx.massage.domain.vo.*; import com.ylx.massage.enums.BillTypeEnum; import com.ylx.massage.enums.DiscountTypeEnum; import com.ylx.massage.enums.JsStatusEnum; import com.ylx.massage.enums.OrderStatusEnum; import com.ylx.massage.mapper.TOrderMapper; import com.ylx.massage.service.*; import com.ylx.massage.utils.*; import lombok.extern.slf4j.Slf4j; import org.apache.commons.compress.utils.Lists; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.util.*; import java.util.stream.Collectors; /** * 订单表 服务实现类 */ @Service @Slf4j public class TOrderServiceImpl extends ServiceImpl implements TOrderService { @Resource private TOrderMapper orderMapper; @Resource private WechatAccountConfig wxPayProperties; @Resource private LocationUtil locationUtil; @Resource private TWxUserService wxUserService; @Resource private TRechargeService rechargeService; @Resource private TXiangmuService xiangmuService; @Resource private OrderNumberGenerator generator; @Resource private TJsService jsService; @Resource private TAddressService addressService; @Resource private TConsumptionLogService consumptionLogService; @Resource private MassageUtil massageUtil; @Resource private CouponReceiveService couponReceiveService; @Resource private CouponService couponService; @Resource private WeChatUtil weChatUtil; @Resource private RefundVoucherService refundVoucherService; @Resource private OrderValidationService orderValidationService; @Resource private OrderNotificationService orderNotificationService; @Resource private OrderAllocationLogService allocationLogService; /** * 判断是否免车费 * 时间段判断: * - 白天时段(7:30-20:00):距离 ≤ 技师白天免车费里程 → 免费 * - 夜间时段(20:00-7:30):距离 ≤ 技师夜间免车费里程 → 免费 * * @param js * @param distance * @return Boolean */ public Boolean isFree(TJs js, BigDecimal distance) { Date date = new Date(); //白天免车费(07.30-20.00) long current = Long.parseLong(DateTimeUtils.numTime(date)); if (current >= MassageConstants.START_FREE && current <= MassageConstants.END_FREE) { if (js.getDaytimeMileage().compareTo(distance) >= 0) { //免车费 return Boolean.TRUE; } else { return Boolean.FALSE; } } else { //夜间免车费(20.00-07.30) if (js.getNigthMileage().compareTo(distance) >= 0) { //免车费 return Boolean.TRUE; } else { return Boolean.FALSE; } } } /** * 添加订单 * * @param order * @return TOrder */ @Override @Transactional(rollbackFor = Exception.class) public TOrder addOrder(TOrder order) { String jsId = order.getcJsId(); // 1. 基础参数校验 if (StringUtils.isBlank(jsId)) { throw new ServiceException("请选择技师"); } if (order.getcGoods().isEmpty()) { throw new ServiceException("请选择项目"); } TJs js = jsService.getById(jsId); if (js == null) { throw new ServiceException("技师不存在"); } Integer techType = js.getTechType(); // 虚拟技师 if(techType.equals(1)){ //虚拟技师订单 order.setVirtualOrderFlag(1); //虚拟技师订单未分配 order.setVirtualOrderAllocation(1); }else{ //真实订单 order.setVirtualOrderFlag(0); order.setVirtualOrderAllocation(0); } // 2. 【新增】订单状态锁校验 - 检查技师是否可以接单 // 确保技师没有进行中的订单,保证服务时间互斥 log.info("开始校验技师 {} 是否可以接单,订单号:{}", order.getcJsId(), order.getOrderNo()); orderValidationService.canAcceptOrder(order.getcJsId(), order); log.info("技师 {} 接单校验通过,继续创建订单", order.getcJsId()); //优惠卷减免 // List coupons = couponReceiveService.getByOpenId(order.getcOpenId()); // BigDecimal preferential = this.setCoupon(coupons); // order.setPreferential(preferential); // 生成订单号 order.setOrderNo(generator.generateNextOrderNumber(OrderNumberGenerator.KEY_PREFIX_ORDER)); //订单价格 List list = JSONObject.parseArray(order.getcGoods().toJSONString(), TXiangmu.class); BigDecimal sum = list.stream().map(TXiangmu::getSum).reduce(BigDecimal.ZERO, BigDecimal::add); //订单总金额 order.setdTotalMoney(sum); //获取用户默认地址 TAddress address = addressService.getByOpenId(order.getcOpenId()); if (address == null) { throw new ServiceException("请先添加地址"); } //添加技师位置信息 locationUtil.geoAdd(LocationUtil.GEO_KEY_USER, js.getcOpenId() + order.getOrderNo(), Double.parseDouble(js.getLongitude().toString()), Double.parseDouble(js.getLatitude().toString())); //添加用户位置信息 locationUtil.geoAdd(LocationUtil.GEO_KEY_USER, order.getcOpenId() + order.getOrderNo(), Double.parseDouble(address.getLongitude().toString()), Double.parseDouble(address.getLatitude().toString())); //计算距离 double distance = locationUtil.getDistance(js.getcOpenId() + order.getOrderNo(), order.getcOpenId() + order.getOrderNo()); log.info("技师与用户之间的距离:{}", distance); locationUtil.remove(LocationUtil.GEO_KEY_USER, js.getcOpenId() + order.getOrderNo(), order.getcOpenId() + order.getOrderNo()); order.setDistance(new BigDecimal(distance)); //计算车费 if (order.getDistance() != null && order.getDistance().compareTo(BigDecimal.ZERO) > 0 && StringUtils.isBlank(order.getParentNo())) { //判断是否可以免车费 if (!this.isFree(js, order.getDistance())) { BigDecimal bigDecimal = massageUtil.calculateTaxiFare(order.getDistance(), js.getDeptId()); order.setFare(bigDecimal.setScale(MassageConstants.INTEGER_TWO, RoundingMode.HALF_UP)); } } //总价 = 订单 + 车费 order.setTotalPrice(sum.add(Optional.ofNullable(order.getFare()).orElse(BigDecimal.ZERO))); if (order.getParentNo() != null && order.getOrderType() == 2) { //升级订单 补差价 TOrder partOrder = this.getByNo(order.getParentNo()); order.setPriceDifference(order.getTotalPrice().subtract(partOrder.getTotalPrice())); } order.setAddress(address.getAddress()); order.setName(address.getName()); order.setLatitude(address.getLatitude()); order.setLongitude(address.getLongitude()); order.setcPhone(address.getPhone()); order.setcName(address.getUserName()); order.setAtlasAdd(address.getAtlasAdd()); order.setDeptId(js.getDeptId()); order.setDeptName(js.getCity()); //设置订单状态:待支付 order.setnStatus(OrderStatusEnum.WAIT_PAY.getCode()); order.setDtCreateTime(LocalDateTime.now()); Date date = DateTimeUtils.addMinute(new Date(), 10); order.setcTime(DateTimeUtils.formatDate(date, "yyyy-MM-dd HH:mm:ss")); save(order); return order; } private BigDecimal setCoupon(List coupons) { //过滤过期的优惠券 coupons = coupons.stream().filter(coupon -> coupon.getExpirationTime().after(new Date())).collect(Collectors.toList()); //无门槛优惠券 List collect = coupons.stream().filter(coupon -> coupon.getDiscountType().equals(DiscountTypeEnum.NO_THRESHOLD.getCode())).collect(Collectors.toList()); //支付成功 后 删除优惠卷 // couponReceiveService.removeCoupons(collect); //计算优惠金额 return collect.stream().map(CouponReceiveVo::getDiscountValue).reduce(BigDecimal.ZERO, BigDecimal::add); } @Override public void payNotifyOrder(String outTradeNo) { //查询未支付的订单 LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(TOrder::getOrderNo, outTradeNo).eq(TOrder::getnStatus, OrderStatusEnum.WAIT_PAY.getCode()); TOrder orderNew = this.getOne(queryWrapper); if (orderNew == null) { log.error("订单 {} 未支付状态不存在", outTradeNo); return; } // 设置微信支付 orderNew.setPayType(1); TWxUser user = wxUserService.getByOpenId(orderNew.getcOpenId()); orderPayManage(user, orderNew); } @Override public Object updateAddressById(TOrder borrow) { TOrder order = this.getById(borrow.getcId()); if (borrow.getLatitude() != null && borrow.getLatitude() != 0 && borrow.getLongitude() != null && borrow.getLongitude() != 0) { order.setAtlasAdd(borrow.getAtlasAdd()); order.setcName(borrow.getcName()); order.setcPhone(borrow.getcPhone()); order.setName(borrow.getName()); order.setAddress(borrow.getAddress()); order.setLatitude(borrow.getLatitude()); order.setLongitude(borrow.getLongitude()); TJs js = jsService.getById(order.getcJsId()); //添加位置信息 locationUtil.geoAdd(LocationUtil.GEO_KEY_USER, js.getcOpenId() + order.getOrderNo(), Double.parseDouble(js.getLongitude().toString()), Double.parseDouble(js.getLatitude().toString())); locationUtil.geoAdd(LocationUtil.GEO_KEY_USER, order.getcOpenId() + order.getOrderNo(), Double.parseDouble(borrow.getLongitude().toString()), Double.parseDouble(borrow.getLatitude().toString())); double distance = locationUtil.getDistance(js.getcOpenId() + order.getOrderNo(), order.getcOpenId() + order.getOrderNo()); locationUtil.remove(LocationUtil.GEO_KEY_USER, js.getcOpenId() + order.getOrderNo(), order.getcOpenId() + order.getOrderNo()); order.setDistance(new BigDecimal(distance)); //计算车费 if (order.getDistance() != null && order.getDistance().compareTo(BigDecimal.ZERO) > 0) { //判断是否可以免车费 if (!this.isFree(js, order.getDistance())) { BigDecimal bigDecimal = massageUtil.calculateTaxiFare(order.getDistance(), order.getDeptId()); order.setFare(bigDecimal.setScale(MassageConstants.INTEGER_TWO, RoundingMode.HALF_UP)); } } order.setTotalPrice(order.getdTotalMoney().add(Optional.ofNullable(order.getFare()).orElse(BigDecimal.ZERO))); this.updateById(order); } return order; } @Override public Object depart(TOrder order) { LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq(TOrder::getcId, order.getcId()).eq(TOrder::getnStatus, OrderStatusEnum.RECEIVED_ORDER.getCode()); //设置订单状态:已出发 order.setnStatus(OrderStatusEnum.DEPART.getCode()); order.setDepartTime(new Date()); order.setDepartLongitude(Optional.ofNullable(order.getDepartLongitude()).orElse(BigDecimal.ZERO)); order.setDepartLatitude(Optional.ofNullable(order.getDepartLatitude()).orElse(BigDecimal.ZERO)); return this.update(order, wrapper); } @Override public Integer getOrderNum(String jsid, Date startDate, Date endDate) { return orderMapper.getOrderNum(jsid, startDate, endDate); } @Override public Integer getAddNum(String jsid, Date startDate, Date endDate) { return orderMapper.getAddNum(jsid, startDate, endDate); } @Override public Integer getUpgradeNum(String jsid, Date startDate, Date endDate) { return orderMapper.getUpgradeNum(jsid, startDate, endDate); } @Override public BigDecimal getTurnover(String jsid, Date startDate, Date endDate) { return orderMapper.getTurnover(jsid, startDate, endDate); } @Override @Transactional(rollbackFor = Exception.class) public TOrder transferOrder(TOrder order) { // ========== 第1步:参数校验 ========== if (StringUtils.isBlank(order.getcId())) { throw new ServiceException("订单id不能为空"); } if (StringUtils.isBlank(order.getcJsId())) { throw new ServiceException("转单技师ID不能为空"); } // 定义操作结果(默认为失败) Integer operationResult = 1; // 1-失败 // 定义操作记录所需的变量 String orderId = null; String orderNo = null; String oldTechnicianId = null; String oldTechnicianName = null; Integer oldTechnicianStatusBefore = null; Integer oldTechnicianStatusAfter = null; String newTechnicianId = null; String newTechnicianName = null; Integer newTechnicianStatusBefore = null; Integer newTechnicianStatusAfter = null; Integer orderStatusBefore = null; Integer orderStatusAfter = null; String operatorId = null; String operatorName = null; String operationReason = "虚拟订单分配"; try { // ========== 第2步:查询原订单信息 ========== TOrder oldOrder = this.getById(order.getcId()); if (oldOrder == null) { throw new ServiceException("订单不存在"); } //原技师ID oldTechnicianId = oldOrder.getcJsId(); //新技师ID newTechnicianId = order.getcJsId(); // 记录订单操作前状态 orderStatusBefore = oldOrder.getnStatus(); orderId = oldOrder.getcId(); orderNo = oldOrder.getOrderNo(); log.info("开始转单操作 - 订单号:{}, 原技师ID:{}, 新技师ID:{}, 订单状态:{}", oldOrder.getOrderNo(), oldTechnicianId, newTechnicianId, orderStatusBefore); // ========== 第3步:查询原技师信息 ========== TJs oldTechnician = jsService.getById(oldTechnicianId); if (oldTechnician == null) { throw new ServiceException("原技师不存在"); } oldTechnicianName = oldTechnician.getcName(); oldTechnicianStatusBefore = oldTechnician.getnStatus(); // ========== 第4步:查询新技师信息 ========== TJs newTechnician = jsService.getById(newTechnicianId); if (newTechnician == null) { throw new ServiceException("新技师不存在"); } newTechnicianName = newTechnician.getcName(); newTechnicianStatusBefore = newTechnician.getnStatus(); // ========== 第5步:更新订单技师信息 ========== oldOrder.setOldJsId(oldTechnicianId); // 保存原技师ID oldOrder.setcJsId(newTechnicianId); // 更新为新技师ID log.info("更新订单技师 - 订单号:{}, 原技师:[ID:{}, 姓名:{}], 新技师:[ID:{}, 姓名:{}]", oldOrder.getOrderNo(), oldTechnicianId, oldTechnicianName, newTechnicianId, newTechnicianName); if (!this.updateById(oldOrder)) { throw new ServiceException("转单失败:更新订单技师信息失败"); } // 记录订单操作后状态(转单后状态通常保持不变) orderStatusAfter = oldOrder.getnStatus(); // ========== 第6步:更新新技师状态(可服务 → 服务中)========== TJs newJsUpdate = new TJs(); newJsUpdate.setId(newTechnicianId); newJsUpdate.setnStatus(JsStatusEnum.JS_SERVICE.getCode()); if (!jsService.updateById(newJsUpdate)) { throw new ServiceException("转单失败:更新新技师状态失败"); } newTechnicianStatusAfter = JsStatusEnum.JS_SERVICE.getCode(); // ========== 第7步:更新原技师状态(服务中 → 可服务)========== TJs oldJsUpdate = new TJs(); oldJsUpdate.setId(oldTechnicianId); oldJsUpdate.setnStatus(JsStatusEnum.JS_SERVICEABLE.getCode()); if (!jsService.updateById(oldJsUpdate)) { throw new ServiceException("转单失败:更新原技师状态失败"); } oldTechnicianStatusAfter = JsStatusEnum.JS_SERVICEABLE.getCode(); log.info("更新技师状态完成 - 新技师:{} {}→{}, 原技师:{} {}→{}", newTechnicianName, getStatusName(newTechnicianStatusBefore), getStatusName(newTechnicianStatusAfter), oldTechnicianName, getStatusName(oldTechnicianStatusBefore), getStatusName(oldTechnicianStatusAfter)); // ========== 第8步:获取操作人信息 ========== operatorId = SecurityUtils.getUserId() != null ? SecurityUtils.getUserId().toString() : "ADMIN"; operatorName = SecurityUtils.getUsername() != null ? SecurityUtils.getUsername() : "系统管理员"; // ========== 第9步:转单成功,设置操作结果为成功 ========== operationResult = 0; // 0-成功 log.info("转单操作完成 - 订单号:{}", oldOrder.getOrderNo()); return oldOrder; } catch (ServiceException e) { // 业务异常,操作失败 log.error("转单操作失败 - 订单号:{}, 错误信息:{}", orderNo, e.getMessage()); operationResult = 1; // 1-失败 throw e; } catch (Exception e) { // 系统异常,操作失败 log.error("转单操作异常 - 订单号:{}, 异常信息:{}", orderNo, e.getMessage(), e); operationResult = 1; // 1-失败 throw new ServiceException("转单操作异常:" + e.getMessage()); } finally { // ========== 第10步:记录转单操作日志(无论成功或失败都记录)========== try { // 只有在获取到基本信息后才记录日志 if (orderId != null && orderNo != null && oldTechnicianId != null && newTechnicianId != null) { allocationLogService.recordTransferOrder( orderId, // orderId orderNo, // orderNo oldTechnicianId, // oldTechnicianId oldTechnicianName, // oldTechnicianName oldTechnicianStatusBefore, // oldTechnicianStatusBefore oldTechnicianStatusAfter, // oldTechnicianStatusAfter newTechnicianId, // newTechnicianId newTechnicianName, // newTechnicianName newTechnicianStatusBefore, // newTechnicianStatusBefore newTechnicianStatusAfter, // newTechnicianStatusAfter orderStatusBefore, // orderStatusBefore orderStatusAfter, // orderStatusAfter operatorId, // operatorId operatorName, // operatorName operationReason, // operationReason operationResult // operationResult(0-成功,1-失败) ); String resultDesc = operationResult == 0 ? "成功" : "失败"; log.info("转单操作记录已保存 - 订单号:{}, 操作结果:{}", orderNo, resultDesc); } } catch (Exception e) { // 记录日志失败不影响转单操作 log.error("记录转单操作日志失败 - 订单号:{}, 错误信息:{}", orderNo, e.getMessage(), e); } } } /** * 获取技师状态名称 * * @param status 状态码 * @return String 状态名称 */ private String getStatusName(Integer status) { if (status == null) { return "未知"; } switch (status) { case 0: return "可服务"; case 1: return "服务中"; case 2: return "不可服务"; default: return "未知(" + status + ")"; } } @Override public List getBlock(Date start, Date end, String deptId) { return orderMapper.getBlock(start, end, deptId); } @Override public OrderVerificationVo verification(TOrder order) { if (StringUtils.isBlank(order.getCouponReceiveId())) { throw new ServiceException("认领优惠券id为空"); } if (StringUtils.isBlank(order.getcId())) { throw new ServiceException("订单id为空"); } OrderVerificationVo orderVerificationVo = new OrderVerificationVo(); TOrder tOrder = this.getById(order.getcId()); orderVerificationVo.setCouponReceiveId(order.getCouponReceiveId()); CouponReceive couponReceive = couponReceiveService.getById(order.getCouponReceiveId()); Coupon coupon = couponService.getById(couponReceive.getCouponId()); log.info("订单信息,{}", tOrder); log.info("优惠卷信息,{}", coupon); //折扣券 if (coupon.getDiscountType() == 2) { //判断门槛金额 if (tOrder.getTotalPrice().compareTo(coupon.getThresholdAmount()) >= 0) { //折扣值 BigDecimal divide = coupon.getRebValue().divide(new BigDecimal(10)); //优惠后的金额 = 订单总金额*折扣值 BigDecimal bigDecimal = tOrder.getTotalPrice().multiply(divide).setScale(MassageConstants.INTEGER_TWO, RoundingMode.HALF_UP); //优惠值 orderVerificationVo.setPreferential(tOrder.getTotalPrice().subtract(bigDecimal)); orderVerificationVo.setTotalPrice(bigDecimal); } else { throw new ServiceException("不满足优惠券门槛金额"); } } else { if (tOrder.getTotalPrice().compareTo(coupon.getThresholdAmount()) >= 0) { //优惠值 orderVerificationVo.setPreferential(coupon.getDiscountValue()); orderVerificationVo.setTotalPrice(tOrder.getTotalPrice().subtract(coupon.getDiscountValue())); } else { throw new ServiceException("不满足优惠券门槛金额"); } } if (orderVerificationVo.getTotalPrice().compareTo(BigDecimal.ZERO) < 0) { throw new ServiceException("当前项目不可用"); } return orderVerificationVo; } private TOrder getOrder(TOrder tOrder) { if (updateById(tOrder)) { return tOrder; } else { throw new ServiceException("优惠券核销失败"); } } /** * 支付订单 * * @param order * @return R */ @Override public R payOrder(TOrder order) throws Exception { // 根据订单ID查询订单信息 TOrder orderNew = getById(order.getcId()); if (!orderNew.getnStatus().equals(OrderStatusEnum.WAIT_PAY.getCode())) { throw new ServiceException("该订单已经支付或者超时被取消"); } TJs js = jsService.getById(orderNew.getcJsId()); if (StringUtils.isBlank(orderNew.getParentNo())) { if (null == js || js.getnStatus().equals(JsStatusEnum.JS_SERVICE.getCode())) { throw new ServiceException("该技师已在服务中请重新下单"); } } orderNew.setPayType(order.getPayType()); //优惠券核销 if (StringUtils.isNotBlank(order.getCouponReceiveId())) { orderNew.setCouponReceiveId(order.getCouponReceiveId()); orderNew.setPreferential(order.getPreferential()); orderNew.setTotalPrice(order.getTotalPrice()); if (!updateById(orderNew)) { throw new ServiceException("支付失败"); } } //判断支付方式 if (order.getPayType().equals(MassageConstants.INTEGER_ONE)) { //微信支付 R resp = rechargeService.getPay(orderNew.getOrderNo(), orderNew.getTotalPrice(), orderNew.getcOpenId(), BillTypeEnum.WX_PAY.getInfo(), BillTypeEnum.WX_PAY.getCode().toString()); //添加待接单消息通知(技师侧)这块的逻辑在回调接口中 //orderNotificationService.sendPendingRemindNotification(orderNew); return resp; } TWxUser user = wxUserService.getByOpenId(orderNew.getcOpenId()); if (null == user) { throw new ServiceException("用户不存在"); } //现金支付 if (order.getPayType().equals(MassageConstants.INTEGER_THREE)) { //现金支付 orderPayManage(user, orderNew); //添加待接单消息通知(技师侧) orderNotificationService.sendPendingRemindNotification(orderNew); return R.ok(); } if (user.getdBalance().compareTo(orderNew.getTotalPrice()) < MassageConstants.INTEGER_ZERO) { throw new ServiceException("账户金额不够请充值"); } else { orderPayManage(user, orderNew); //添加待接单消息通知(技师侧) orderNotificationService.sendPendingRemindNotification(orderNew); return R.ok(); } } /** * 新订单通知 * * @param order */ public void newOrderNotification(TOrder order) { cn.hutool.json.JSONObject param = JSONUtil.createObj(); //订单号 param.set("character_string9", JSONUtil.createObj().set("value", order.getOrderNo())); //电话 param.set("phone_number14", JSONUtil.createObj().set("value", order.getcPhone())); param.set("thing18", JSONUtil.createObj().set("value", order.getcName())); param.set("time6", JSONUtil.createObj().set("value", DateTimeUtils.formatDate(new Date(), DateTimeUtils.DATE_NUMBER_YEAR_MONTH_FORMAT))); param.set("thing27", JSONUtil.createObj().set("value", order.getName())); TJs js = jsService.getById(order.getcJsId()); weChatUtil.notification(js.getcOpenId(), wxPayProperties.getTemplateId1(), param); } /** * 订单支付管理 * * @param user * @param orderNew */ @Transactional(rollbackFor = Exception.class) public void orderPayManage(TWxUser user, TOrder orderNew) { //更新优惠卷状态 if (StringUtils.isNotBlank(orderNew.getCouponReceiveId())) { CouponReceive couponReceive = new CouponReceive(); couponReceive.setId(orderNew.getCouponReceiveId()); couponReceive.setUseState(MassageConstants.INTEGER_TWO); if (!couponReceiveService.updateById(couponReceive)) { log.error("优惠券状态更新失败id:,{}", orderNew.getCouponReceiveId()); } } // 更新用户金额 及下单此时 TWxUser paramUser = new TWxUser(); paramUser.setcOpenid(user.getcOpenid()); // 余额支付 if (orderNew.getPayType().equals(MassageConstants.INTEGER_TWO)) { paramUser.setdBalance(user.getdBalance().subtract(orderNew.getTotalPrice())); } paramUser.setdMoney(user.getdMoney().add(orderNew.getTotalPrice())); paramUser.setnNum(user.getnNum() + MassageConstants.INTEGER_ONE); paramUser.setId(user.getId()); wxUserService.updateById(paramUser); //增加消费记录 TConsumptionLog tConsumptionLog = new TConsumptionLog(); tConsumptionLog.setAmount(orderNew.getTotalPrice().negate()); tConsumptionLog.setBillNo(orderNew.getOrderNo()); tConsumptionLog.setOpenId(orderNew.getcOpenId()); if (orderNew.getPayType().equals(MassageConstants.INTEGER_TWO)) { tConsumptionLog.setBillType(BillTypeEnum.BALANCE_PAYMENT.getCode()); tConsumptionLog.setNote("余额支付"); } else if(orderNew.getPayType().equals(MassageConstants.INTEGER_ONE)){ tConsumptionLog.setBillType(BillTypeEnum.WX_PAY.getCode()); tConsumptionLog.setNote("微信支付"); } else { tConsumptionLog.setBillType(BillTypeEnum.CASH_PAYMENT.getCode()); tConsumptionLog.setNote("现金支付"); } consumptionLogService.save(tConsumptionLog); // 更新项目数据 JSONArray objects = orderNew.getcGoods(); objects.forEach(item -> { UpdateWrapper wrapper = new UpdateWrapper<>(); // 获取参数 wrapper.lambda().eq(TXiangmu::getcId, ((JSONObject) item).getString("cId")); // 设置数量 wrapper.setSql(" n_sale_number = n_sale_number + " + ((JSONObject) item).getInteger("number")); xiangmuService.update(wrapper); }); TOrder orderParam = new TOrder(); orderParam.setPayType(orderNew.getPayType()); orderParam.setcId(orderNew.getcId()); orderParam.setnStatus(OrderStatusEnum.WAIT_JD.getCode()); orderParam.setPayTime(new Date()); //加钟的订单支付完直接服务中 if (StringUtils.isNotBlank(orderNew.getParentNo())) { orderParam.setnStatus(OrderStatusEnum.SERVICE.getCode()); } // orderParam.setnStatus(OrderStatusEnum.SERVICE.getCode()); //更新及技师状态 updateJs(orderNew); updateById(orderParam); // //this.newOrderNotification(orderNew); //添加待接单消息通知(技师侧) orderNotificationService.sendPendingRemindNotification(orderNew); //电话通知 TJs js = jsService.getById(orderNew.getcJsId()); Sendvoice.sendPhone(js.getcPhone()); } /** * 拒绝订单 * * @param order */ @Override public Boolean jujue(TOrder order) { TOrder orderNew = getById(order.getcId()); TWxUser user = wxUserService.getByOpenId(orderNew.getcOpenId()); // 更新用户金额 及下单此时 TWxUser paramUser = new TWxUser(); paramUser.setcOpenid(user.getcOpenid()); paramUser.setId(user.getId()); // 余额记录 TConsumptionLog tConsumptionLog = new TConsumptionLog(); tConsumptionLog.setAmount(orderNew.getTotalPrice()); tConsumptionLog.setBillNo(orderNew.getOrderNo()); tConsumptionLog.setOpenId(orderNew.getcOpenId()); if (orderNew.getPayType() == 2) { // 金额归还对应账户 paramUser.setdBalance(user.getdBalance().add(orderNew.getTotalPrice())); tConsumptionLog.setBillType(BillTypeEnum.REFUSE_ACCEPT_REFUND.getCode()); tConsumptionLog.setNote("拒绝接单退款到余额"); } else { // 微信支付 // 生成退款单退款 RefundVoucher refundVoucher = new RefundVoucher(); refundVoucher.setRefundNo(generator.generateNextOrderNumber(OrderNumberGenerator.KEY_PREFIX_REFUND)); refundVoucher.setOrderNo(orderNew.getOrderNo()); refundVoucher.setMoney(orderNew.getTotalPrice()); refundVoucher.setOpenId(orderNew.getcOpenId()); refundVoucher.setReStatus(MassageConstants.INTEGER_ZERO); refundVoucher.setReason("技师拒绝接单"); refundVoucherService.save(refundVoucher); tConsumptionLog.setBillType(BillTypeEnum.REFUSE_ACCEPT_REFUND.getCode()); tConsumptionLog.setNote("拒绝接单退款到余额"); // 微信退款原路返回 rechargeService.refund(refundVoucher.getRefundNo(), null, orderNew.getOrderNo(), orderNew.getTotalPrice()); } consumptionLogService.save(tConsumptionLog); //退优惠卷 if (StringUtils.isNotBlank(orderNew.getCouponReceiveId())) { CouponReceive couponReceive = couponReceiveService.getById(orderNew.getCouponReceiveId()); couponReceive.setUseState(MassageConstants.INTEGER_ZERO); couponReceiveService.updateById(couponReceive); } log.info("余额支付退款user:{}", user); // 消费金额对应减少 paramUser.setdMoney(user.getdMoney().subtract(orderNew.getTotalPrice())); // 下单次数减一 paramUser.setnNum(user.getnNum() - MassageConstants.INTEGER_ONE); wxUserService.updateById(paramUser); // 更新项目数据 JSONArray objects = orderNew.getcGoods(); objects.forEach(item -> { UpdateWrapper wrapper = new UpdateWrapper<>(); // 获取参数 wrapper.lambda().eq(TXiangmu::getcId, ((JSONObject) item).getString("cId")); // 设置数量 wrapper.setSql(" n_sale_number = n_sale_number - " + ((JSONObject) item).getInteger("number")); xiangmuService.update(wrapper); }); TOrder orderParam = new TOrder(); orderParam.setcId(orderNew.getcId()); orderParam.setnStatus(OrderStatusEnum.REFUSE.getCode()); orderParam.setReasonRefusal(order.getReasonRefusal()); updateJs(orderNew); return updateById(orderParam); } /** * 确认服务完成 * * @param order * @return Boolean */ @Override @Transactional(rollbackFor = Exception.class) public Boolean confirm(TOrder order) { // 获取订单信息 TOrder orderNew = getById(order.getcId()); if (!orderNew.getnStatus().equals(OrderStatusEnum.SERVICE.getCode())) { throw new ServiceException("订单状态不是服务中"); } // 更新技师信息 TJs jsParam = new TJs(); jsParam.setId(orderNew.getcJsId()); jsParam.setnStatus(JsStatusEnum.JS_SERVICEABLE.getCode()); //判断热度标识 List list = list(new LambdaQueryWrapper().eq(TOrder::getcJsId, orderNew.getcJsId()) .ge(TOrder::getDtCreateTime, DateTimeUtils.addDays(new Date(), -3)) .ge(TOrder::getnStatus, OrderStatusEnum.WAIT_EVALUATE.getCode())); if (list.size() >= 2) { // 设置热度标识:1 jsParam.setnB3(MassageConstants.INTEGER_ONE); } // 更新技师状态 jsService.updateById(jsParam); // 更新技师钱包金额 TJs jsById = jsService.getById(orderNew.getcJsId()); // 获取技师抽成 BigDecimal multiply = orderNew.getTotalPrice().multiply(new BigDecimal(jsById.getnBili())); multiply = multiply.divide(new BigDecimal(100), MassageConstants.INTEGER_TWO, RoundingMode.HALF_UP); // 获取技师所对应的用户 TWxUser jsUser = wxUserService.getByOpenId(jsById.getcOpenId()); // 更新余额 jsUser.setdBalance(jsUser.getdBalance().add(multiply)); // 更新总钱数 jsUser.setdAllMoney(jsUser.getdAllMoney().add(multiply)); wxUserService.updateById(jsUser); //增加消费记录 TConsumptionLog tConsumptionLog = new TConsumptionLog(); tConsumptionLog.setAmount(multiply); tConsumptionLog.setBillNo(orderNew.getOrderNo()); tConsumptionLog.setOpenId(jsUser.getcOpenid()); tConsumptionLog.setBillType(BillTypeEnum.INCOME.getCode()); tConsumptionLog.setNote("技师收益"); consumptionLogService.save(tConsumptionLog); // 如果该技师有推荐人员 一级 if (StringUtils.isNotBlank(jsUser.getcUpUser())) { // 获取技师上级对应的用户 TWxUser jsUp = wxUserService.getByOpenId(jsUser.getcUpUser()); extracted(orderNew, jsUp); //二级 if (StringUtils.isNotBlank(jsUp.getcUpUser())) { TWxUser jsUpTwo = wxUserService.getByOpenId(jsUp.getcUpUser()); extracted(orderNew, jsUpTwo); //三级 if (StringUtils.isNotBlank(jsUpTwo.getcUpUser())) { TWxUser jsUpThree = wxUserService.getByOpenId(jsUpTwo.getcUpUser()); extracted(orderNew, jsUpThree); } } } // 更新订单 // 订单状态:待评价 orderNew.setnStatus(OrderStatusEnum.WAIT_EVALUATE.getCode()); orderNew.setEndTime(LocalDateTime.now()); updateById(orderNew); // 添加订单完成消息通知(用户侧) orderNotificationService.sendCompletedNotification(orderNew); return true; } private void extracted(TOrder orderNew, TWxUser jsUp) { log.info("TOrderServiceImpl->extracted->jsUp,{}", JSONUtil.toJsonStr(jsUp)); log.info("TOrderServiceImpl->extracted->orderNew,{}",JSONUtil.toJsonStr(orderNew)); BigDecimal up = orderNew.getdTotalMoney().multiply(new BigDecimal("10")); up = up.divide(new BigDecimal(100), 2, RoundingMode.HALF_UP); // 更新余额 jsUp.setdBalance(jsUp.getdBalance().add(up)); // 更新总钱数 jsUp.setdAllMoney(jsUp.getdAllMoney().add(up)); jsUp.setDistributionAmount(up); wxUserService.updateById(jsUp); //记录分销收益 TConsumptionLog tConsumptionLog = new TConsumptionLog(); tConsumptionLog.setAmount(up); tConsumptionLog.setBillNo(orderNew.getOrderNo()); tConsumptionLog.setOpenId(jsUp.getcOpenid()); tConsumptionLog.setBillType(BillTypeEnum.DISTRIBUTION.getCode()); tConsumptionLog.setNote("分销收益"); consumptionLogService.save(tConsumptionLog); } /** * 获取技师当天可预约时间 * * @param technicianId 技师ID * @param dateStr 查询日期(格式:yyyy-MM-dd),为null则查询当天 * @return 技师当天可预约时间VO */ @Override public TechnicianAvailabilityVo getTechnicianAvailability(String technicianId, String dateStr) { // 1. 参数校验 if (StringUtils.isBlank(technicianId)) { throw new ServiceException("技师ID不能为空"); } // 2. 查询技师信息 TJs js = jsService.getById(technicianId); if (js == null) { throw new ServiceException("技师不存在"); } // 3. 解析日期,默认为当天 LocalDate queryDate; if (StringUtils.isBlank(dateStr)) { queryDate = LocalDate.now(); } else { try { queryDate = LocalDate.parse(dateStr); } catch (Exception e) { throw new ServiceException("日期格式错误,请使用 yyyy-MM-dd 格式"); } } // 4. 定义当天的所有时间段(以30分钟为间隔,从00:00到23:30) List timeSlots = new ArrayList<>(); for (int hour = 0; hour < 24; hour++) { // 每小时生成两个时间段:xx:00 和 xx:30 timeSlots.add(TimeSlotVo.builder() .time(String.format("%02d:00", hour)) .available(true) .build()); timeSlots.add(TimeSlotVo.builder() .time(String.format("%02d:30", hour)) .available(true) .build()); } // 5. 查询技师当天所有进行中的订单 // 开始时间 LocalDateTime startOfDay = queryDate.atStartOfDay(); // 结束时间 LocalDateTime endOfDay = queryDate.plusDays(1).atStartOfDay(); log.info("开始时间:{},结束时间:{}", startOfDay, endOfDay); LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(TOrder::getcJsId, technicianId) .in(TOrder::getnStatus, OrderStatusEnum.WAIT_JD.getCode(), OrderStatusEnum.RECEIVED_ORDER.getCode(), OrderStatusEnum.DEPART.getCode(), OrderStatusEnum.ARRIVED.getCode(), OrderStatusEnum.SERVICE.getCode()) .ge(TOrder::getDtCreateTime, startOfDay) .lt(TOrder::getDtCreateTime, endOfDay) .eq(TOrder::getIsDelete, 0); List orders = this.list(queryWrapper); log.info("技师{},在{}天共有 {} 个进行中的订单", technicianId, queryDate, orders.size()); // 6. 标记不可预约的时间段 LocalDateTime now = LocalDateTime.now(); for (TOrder order : orders) { // 6.1 计算订单的开始时间和结束时间 LocalDateTime orderStart = OrderTimeRangeUtils.estimateStartTime(order); LocalDateTime orderEnd = OrderTimeRangeUtils.estimateEndTime(order); if (orderStart == null || orderEnd == null) { log.warn("订单 {} 的时间信息不完整,跳过", order.getOrderNo()); continue; } // 6.2 限制在查询日期范围内 LocalDateTime effectiveStart = orderStart.isBefore(startOfDay) ? startOfDay : orderStart; LocalDateTime effectiveEnd = orderEnd.isAfter(endOfDay) ? endOfDay : orderEnd; // 6.3 标记不可预约的时间段 markTimeSlotsUnavailable(timeSlots, effectiveStart, effectiveEnd, order.getOrderNo()); } // 7. 根据查询日期判断是否可预约 LocalDate today = LocalDate.now(); if (queryDate.isBefore(today)) { // 查询日期是过去的日期,所有时间段都不可预约 markAllTimeSlotsUnavailable(timeSlots, "日期已过期"); } else if (queryDate.equals(today)) { // 查询日期是今天,标记过去的时间为不可预约 markPastTimeSlotsUnavailable(timeSlots, now); } // 查询日期是未来的日期,所有时间段默认可预约,无需处理 // 8. 构建返回结果 return TechnicianAvailabilityVo.builder() .date(queryDate.toString()) .technicianId(technicianId) .technicianName(js.getcName()) .timeSlots(timeSlots) .build(); } /** * 标记指定时间范围内的时间段为不可预约 * * @param timeSlots 时间段列表 * @param start 开始时间 * @param end 结束时间 * @param orderNo 订单号 */ private void markTimeSlotsUnavailable(List timeSlots, LocalDateTime start, LocalDateTime end, String orderNo) { LocalDate date = start.toLocalDate(); LocalTime startTime = start.toLocalTime(); LocalTime endTime = end.toLocalTime(); for (TimeSlotVo slot : timeSlots) { LocalTime slotTime = LocalTime.parse(slot.getTime()); // 判断时间段是否在订单时间范围内 boolean isInRange = !slotTime.isBefore(startTime) && slotTime.isBefore(endTime); if (isInRange) { slot.setAvailable(false); slot.setReason("已有订单"); slot.setOrderNo(orderNo); } } } /** * 标记所有时间段为不可预约 * * @param timeSlots 时间段列表 * @param reason 不可预约原因 */ private void markAllTimeSlotsUnavailable(List timeSlots, String reason) { for (TimeSlotVo slot : timeSlots) { slot.setAvailable(false); slot.setReason(reason); slot.setOrderNo(null); } } /** * 标记过去的时间段为不可预约 * * @param timeSlots 时间段列表 * @param now 当前时间 */ private void markPastTimeSlotsUnavailable(List timeSlots, LocalDateTime now) { LocalTime currentTime = now.toLocalTime(); for (TimeSlotVo slot : timeSlots) { LocalTime slotTime = LocalTime.parse(slot.getTime()); // 如果当前时间已经过了这个时间段,标记为不可预约 if (slotTime.isBefore(currentTime)) { slot.setAvailable(false); slot.setReason("已过期"); slot.setOrderNo(null); } } } /** * 取消订单 * * @param order * @return Boolean */ @Override @Transactional(rollbackFor = Exception.class) public Boolean cancle(TOrder order) { // 获取订单信息 // 根据orderid查询订单信息 TOrder orderNew = getById(order.getcId()); //待接单 if (Objects.equals(orderNew.getnStatus(), OrderStatusEnum.WAIT_JD.getCode())) { TWxUser user = wxUserService.getByOpenId(orderNew.getcOpenId()); // 更新用户金额 及下单此时 TWxUser paramUser = new TWxUser(); paramUser.setId(user.getId()); paramUser.setcOpenid(user.getcOpenid()); TConsumptionLog tConsumptionLog = new TConsumptionLog(); tConsumptionLog.setAmount(orderNew.getTotalPrice()); tConsumptionLog.setBillNo(orderNew.getOrderNo()); tConsumptionLog.setOpenId(orderNew.getcOpenId()); // 余额支付 if (orderNew.getPayType() == 2) { // 金额归还对应账户 paramUser.setdBalance(user.getdBalance().add(orderNew.getTotalPrice())); // 余额记录 tConsumptionLog.setBillType(BillTypeEnum.CANCEL_ACCEPT_REFUND.getCode()); tConsumptionLog.setNote("取消订单退款到余额"); //自己取消的不退优惠卷 } else { // 微信支付 // 生成退款单退款 RefundVoucher refundVoucher = new RefundVoucher(); refundVoucher.setRefundNo(generator.generateNextOrderNumber(OrderNumberGenerator.KEY_PREFIX_REFUND)); refundVoucher.setOrderNo(orderNew.getOrderNo()); refundVoucher.setMoney(orderNew.getTotalPrice()); refundVoucher.setOpenId(orderNew.getcOpenId()); refundVoucher.setReStatus(MassageConstants.INTEGER_ZERO); refundVoucher.setReason("技师拒绝接单"); refundVoucherService.save(refundVoucher); tConsumptionLog.setBillType(BillTypeEnum.CANCEL_WX_REFUND.getCode()); tConsumptionLog.setNote("取消订单退款到微信"); // 微信退款原路返回 rechargeService.refund(refundVoucher.getRefundNo(), null, orderNew.getOrderNo(), orderNew.getTotalPrice()); } consumptionLogService.save(tConsumptionLog); // 消费金额对应减少 paramUser.setdMoney(user.getdMoney().subtract(orderNew.getTotalPrice())); // 下单次数减一 paramUser.setnNum(user.getnNum() - MassageConstants.INTEGER_ONE); wxUserService.updateById(paramUser); // 更新项目数据 JSONArray objects = orderNew.getcGoods(); objects.forEach(item -> { UpdateWrapper wrapper = new UpdateWrapper<>(); // 获取参数 wrapper.lambda().eq(TXiangmu::getcId, ((JSONObject) item).getString("cId")); // 设置数量 wrapper.setSql(" n_sale_number = n_sale_number - " + ((JSONObject) item).getInteger("number")); xiangmuService.update(wrapper); }); TOrder orderParam = new TOrder(); orderParam.setcId(orderNew.getcId()); orderParam.setnStatus(OrderStatusEnum.CANCEL.getCode()); //更新技师状态 TJs tJs = new TJs(); tJs.setId(orderNew.getcJsId()); tJs.setnStatus(JsStatusEnum.JS_SERVICEABLE.getCode()); jsService.updateById(tJs); updateById(orderParam); // 添加取消订单通知(用户侧) orderNotificationService.sendCancelledNotification(orderNew); // 添加取消订单通知(技师侧) orderNotificationService.sendTechnicianCancelledNotification(orderNew); return true; } else if (Objects.equals(orderNew.getnStatus(), OrderStatusEnum.WAIT_PAY.getCode())) {//待付款 TOrder orderParam = new TOrder(); orderParam.setcId(orderNew.getcId()); orderParam.setnStatus(OrderStatusEnum.CANCEL.getCode()); updateById(orderParam); // 添加取消订单通知 orderNotificationService.sendCancelledNotification(orderNew); // 添加取消订单通知(技师侧) orderNotificationService.sendTechnicianCancelledNotification(orderNew); return true; } else { return false; } } @Override public TOrder getByNo(String orderNo) { LambdaQueryWrapper objectLambdaQueryWrapper = new LambdaQueryWrapper<>(); return this.getOne(objectLambdaQueryWrapper.eq(TOrder::getOrderNo, orderNo)); } // private TOrder gettOrder(TOrder order) { // LambdaUpdateWrapper objectLambdaUpdateWrapper = new LambdaUpdateWrapper<>(); // objectLambdaUpdateWrapper.eq(TOrder::getOrderNo, order.getOrderNo()); // return this.getOne(objectLambdaUpdateWrapper); // } @Override public Page getAll(Page page, TOrder order) { Page orderPage = orderMapper.getAll(page, order); if (orderPage != null && CollectionUtil.isNotEmpty(orderPage.getRecords())) { ArrayList ordersList = Lists.newArrayList(); orderPage.getRecords().forEach(orders -> { orders.setStatusName(OrderStatusEnum.getDescByCode(orders.getnStatus())); orders.setJsPhone(orders.getJs().getcPhone()); orders.setJsName(orders.getJs().getcName()); if (StringUtils.isEmpty(orders.getcTime())) { orders.setRemainingTime(0L); } if (StringUtils.isNotBlank(orders.getcTime()) && DateTimeUtils.dateStringToStamp(orders.getcTime()) > DateTimeUtils.dateToStamp(new Date())) { orders.setRemainingTime((DateTimeUtils.dateStringToStamp(orders.getcTime()) - DateTimeUtils.dateToStamp(new Date())) / 1000); } if (StringUtils.isNotBlank(orders.getcTime()) && DateTimeUtils.dateStringToStamp(orders.getcTime()) < DateTimeUtils.dateToStamp(new Date())) { orders.setRemainingTime(0L); } if (StringUtils.isNotBlank(orders.getOldJsId())) { orders.setOldJs(jsService.getById(orders.getOldJsId())); } ordersList.add(orders); }); orderPage.setRecords(ordersList); } return orderPage; } @Override @Transactional(rollbackFor = Exception.class) public void takingOrders(TOrder order) { String orderId = order.getcId(); if (orderId == null || StringUtils.isBlank(orderId)) { throw new IllegalArgumentException("订单ID不能为空"); } TOrder orderNew = this.getById(orderId); // 【新增】订单状态锁校验 - 检查技师是否可以接单 log.info("开始校验技师 {} 是否可以接单,订单号:{}", orderNew.getcJsId(), orderNew.getOrderNo()); orderValidationService.canAcceptOrder(orderNew.getcJsId(), orderNew); log.info("技师 {} 接单校验通过,继续接单流程", orderNew.getcJsId()); // 检查订单对应的技师是否存在 // updateJs (orderNew); TOrder orderParam = new TOrder(); orderParam.setcId(orderId); //设置订单状态:已接单 orderParam.setnStatus(OrderStatusEnum.RECEIVED_ORDER.getCode()); orderParam.setAcceptanceTime(LocalDateTime.now()); this.updateById(orderParam); // 已接单消息通知(用户侧) orderNotificationService.sendReceivedNotification(orderNew); // 已接单消息通知(技师侧) orderNotificationService.sendTechnicianReceivedNotification(orderNew); } private void updateJs(TOrder orderNew) { TJs js = jsService.getById(orderNew.getcJsId()); if (js == null) { throw new IllegalStateException("无法找到对应的技师"); } if (Objects.equals(js.getnStatus(), JsStatusEnum.JS_SERVICEABLE.getCode())) { // 更新技师状态 js.setnStatus(JsStatusEnum.JS_SERVICE.getCode()); // 确保js.getnNum()不为null,避免 NullPointerException int num = js.getnNum() == null ? 0 : js.getnNum(); js.setnNum(num + MassageConstants.INTEGER_ONE); } else { // 更新技师状态 js.setnStatus(JsStatusEnum.JS_SERVICEABLE.getCode()); // 确保js.getnNum()不为null,避免 NullPointerException int num = js.getnNum() == null ? 0 : js.getnNum(); js.setnNum(num - MassageConstants.INTEGER_ONE); } jsService.updateById(js); } }