| | |
| | | import com.doumee.biz.system.OperationConfigBiz; |
| | | import com.doumee.biz.system.SystemDictDataBiz; |
| | | import com.doumee.config.wx.WxMiniConfig; |
| | | import com.doumee.config.wx.WxMiniUtilService; |
| | | import com.doumee.config.wx.WxPayProperties; |
| | | import com.doumee.config.wx.WxPayV3Service; |
| | | import com.wechat.pay.java.service.refund.model.Refund; |
| | | import com.doumee.core.constants.Constants; |
| | | import com.doumee.core.constants.ResponseStatus; |
| | | import com.doumee.core.exception.BusinessException; |
| | | import com.doumee.core.model.PageData; |
| | | import com.doumee.core.model.PageWrap; |
| | | import com.doumee.core.utils.DateUtil; |
| | | import com.doumee.core.utils.Tencent.MapUtil; |
| | | import com.doumee.core.utils.geocode.MapUtil; |
| | | import com.doumee.core.utils.Utils; |
| | | import com.doumee.dao.business.*; |
| | | import com.doumee.dao.business.model.*; |
| | | import com.doumee.dao.system.SystemUserMapper; |
| | | import com.doumee.dao.system.model.SystemDictData; |
| | | import com.doumee.dao.system.model.SystemUser; |
| | | import com.doumee.dao.dto.CalculateLocalPriceDTO; |
| | | import com.doumee.dao.dto.CalculateRemotePriceDTO; |
| | | import com.doumee.dao.dto.CommentOrderDTO; |
| | | import com.doumee.dao.dto.CreateOrderDTO; |
| | | import com.doumee.dao.dto.DispatchDTO; |
| | | import com.doumee.dao.dto.MyOrderDTO; |
| | | import com.doumee.dao.dto.OrderItemDTO; |
| | | import com.doumee.dao.vo.*; |
| | | import com.doumee.service.business.NoticeService; |
| | | import com.doumee.service.business.OrderLogService; |
| | | import com.doumee.service.business.OrdersService; |
| | | import com.github.binarywang.wxpay.bean.request.BaseWxPayRequest; |
| | | import com.doumee.dao.business.model.Notice; |
| | | import com.doumee.service.business.AreasService; |
| | | import com.doumee.service.business.PricingRuleService; |
| | | import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest; |
| | | import com.github.binarywang.wxpay.exception.WxPayException; |
| | | import com.github.xiaoymin.knife4j.core.util.CollectionUtils; |
| | |
| | | import org.springframework.data.redis.core.RedisTemplate; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | import org.springframework.web.context.request.RequestContextHolder; |
| | | import org.springframework.web.context.request.ServletRequestAttributes; |
| | | |
| | | import javax.servlet.http.HttpServletRequest; |
| | | import java.math.BigDecimal; |
| | | import java.math.RoundingMode; |
| | | import java.net.InetAddress; |
| | | import java.net.UnknownHostException; |
| | | import java.util.*; |
| | | import java.util.concurrent.TimeUnit; |
| | | import java.util.stream.Collectors; |
| | | |
| | | /** |
| | | * 寄存订单信息Service实现 |
| | |
| | | private OrdersRefundMapper ordersRefundMapper; |
| | | |
| | | @Autowired |
| | | private WxMiniUtilService wxMiniUtilService; |
| | | private OtherOrdersMapper otherOrdersMapper; |
| | | |
| | | @Autowired |
| | | private OrderCommentMapper orderCommentMapper; |
| | | |
| | | @Autowired |
| | | private RevenueMapper revenueMapper; |
| | | |
| | | |
| | | |
| | | @Autowired |
| | | private SystemUserMapper systemUserMapper; |
| | | |
| | | @Autowired |
| | | private PricingRuleMapper pricingRuleMapper; |
| | | |
| | | @Autowired |
| | | private PricingRuleService pricingRuleService; |
| | | |
| | | @Autowired |
| | | private RedisTemplate<String, Object> redisTemplate; |
| | | |
| | |
| | | |
| | | @Autowired |
| | | private OperationConfigBiz operationConfigBiz; |
| | | |
| | | @Autowired |
| | | private AreasService areasService; |
| | | |
| | | @Autowired |
| | | private NoticeService noticeService; |
| | | |
| | | @Autowired |
| | | private WxPayV3Service wxPayV3Service; |
| | | |
| | | @Autowired |
| | | private WxPayProperties wxPayProperties; |
| | | |
| | | @Override |
| | | public Integer create(Orders orders) { |
| | |
| | | */ |
| | | @Override |
| | | public PriceCalculateVO calculateLocalPrice(CalculateLocalPriceDTO dto) { |
| | | // 天数校验,最少1天 |
| | | int days = dto.getEstimatedDepositDays() != null && dto.getEstimatedDepositDays() > 0 |
| | | ? dto.getEstimatedDepositDays() : 1; |
| | | // 根据开始和结束时间计算天数,最少1天 |
| | | long diffMs = dto.getDepositEndTime().getTime() - dto.getDepositStartTime().getTime(); |
| | | int days = (int) Math.max(1, (diffMs / (1000 * 60 * 60 * 24)) + 1); |
| | | |
| | | // 收集所有物品类型ID |
| | | List<Integer> categoryIds = new ArrayList<>(); |
| | |
| | | itemPriceTotal += subtotal; |
| | | } |
| | | |
| | | // 保价费用:报价金额 × 保价费率(字典 INSURANCE_RATE),元→分 |
| | | // 保价费用:报价金额 × 保价费率(字典 INSURANCE_RATE),元→分(保价金额>0时计费) |
| | | long insuranceFeeFen = 0L; |
| | | if (Boolean.TRUE.equals(dto.getInsured()) && dto.getDeclaredAmount() != null) { |
| | | if (dto.getDeclaredAmount() != null && dto.getDeclaredAmount().compareTo(BigDecimal.ZERO) > 0) { |
| | | BigDecimal insuranceFeeYuan = calculateInsuranceFee(dto.getDeclaredAmount()); |
| | | insuranceFeeFen = insuranceFeeYuan.multiply(new BigDecimal(100)).longValue(); |
| | | } |
| | |
| | | // 1. 调用腾讯地图距离矩阵API计算驾车距离 |
| | | String from = dto.getFromLat() + "," + dto.getFromLgt(); |
| | | String to = dto.getToLat() + "," + dto.getToLgt(); |
| | | JSONObject distanceResult = MapUtil.distanceSingle("driving", from, to); |
| | | JSONObject distanceResult = MapUtil.direction("driving", from, to); |
| | | BigDecimal distance = distanceResult.getBigDecimal("distance"); |
| | | // distance 单位为米,转为公里 |
| | | BigDecimal distanceKm = distance.divide(new BigDecimal(1000), 2, RoundingMode.HALF_UP); |
| | |
| | | itemPriceTotal += subtotal; |
| | | } |
| | | |
| | | // 4. 保价费用:报价金额 × 保价费率(字典 INSURANCE_RATE),元→分 |
| | | // 4. 保价费用:报价金额 × 保价费率(字典 INSURANCE_RATE),元→分(保价金额>0时计费) |
| | | long insuranceFeeFen = 0L; |
| | | if (Boolean.TRUE.equals(dto.getInsured()) && dto.getDeclaredAmount() != null) { |
| | | if (dto.getDeclaredAmount() != null && dto.getDeclaredAmount().compareTo(BigDecimal.ZERO) > 0) { |
| | | BigDecimal insuranceFeeYuan = calculateInsuranceFee(dto.getDeclaredAmount()); |
| | | insuranceFeeFen = insuranceFeeYuan.multiply(new BigDecimal(100)).longValue(); |
| | | } |
| | | |
| | | // 5. 加急费用:物品价格 × 加急系数(字典 URGENT_COEFFICIENT) |
| | | long urgentFeeFen = 0L; |
| | | if (Boolean.TRUE.equals(dto.getUrgent())) { |
| | | String urgentRateStr = systemDictDataBiz.queryByCode( |
| | | Constants.OPERATION_CONFIG, Constants.OP_URGENT_COEFFICIENT).getCode(); |
| | | BigDecimal urgentRate = new BigDecimal(urgentRateStr); |
| | | urgentFeeFen = new BigDecimal(itemPriceTotal).multiply(urgentRate) |
| | | .setScale(0, RoundingMode.HALF_UP).longValue(); |
| | | } |
| | | String urgentRateStr = systemDictDataBiz.queryByCode( |
| | | Constants.OPERATION_CONFIG, Constants.OP_URGENT_COEFFICIENT).getCode(); |
| | | BigDecimal urgentRate = new BigDecimal(urgentRateStr); |
| | | urgentFeeFen = new BigDecimal(itemPriceTotal).multiply(urgentRate) |
| | | .setScale(0, RoundingMode.HALF_UP).longValue(); |
| | | |
| | | // 6. 总价格 = 物品价格 + 保价费用 + 加急费用 |
| | | long totalPrice = itemPriceTotal + insuranceFeeFen + urgentFeeFen; |
| | | // 6. 总价格 = 物品价格 + 保价费用 + 加急费用(加急时才包含加急费) |
| | | long totalPrice = itemPriceTotal + insuranceFeeFen; |
| | | if (Boolean.TRUE.equals(dto.getUrgent())) { |
| | | totalPrice += urgentFeeFen; |
| | | } |
| | | |
| | | PriceCalculateVO result = new PriceCalculateVO(); |
| | | result.setItemList(itemList); |
| | |
| | | result.setUrgentFee(urgentFeeFen); |
| | | result.setTotalPrice(totalPrice); |
| | | result.setDistance(distanceKm); |
| | | |
| | | // 7. 预计送达时长:pricing_rule type=2(fieldA=1标速达,fieldA=2极速达) |
| | | List<PricingRule> timeRules = pricingRuleMapper.selectList(new QueryWrapper<PricingRule>().lambda() |
| | | .eq(PricingRule::getDeleted, Constants.ZERO) |
| | | .eq(PricingRule::getType, Constants.TWO) |
| | | .eq(PricingRule::getCityId, dto.getCityId()) |
| | | .in(PricingRule::getFieldA, Arrays.asList("1", "2"))); |
| | | for (PricingRule tr : timeRules) { |
| | | BigDecimal baseKm = new BigDecimal(tr.getFieldB()); |
| | | int baseHours = Integer.parseInt(tr.getFieldC()); |
| | | BigDecimal extraKm = new BigDecimal(tr.getFieldD()); |
| | | int extraHours = Integer.parseInt(tr.getFieldE()); |
| | | int hours; |
| | | if (distanceKm.compareTo(baseKm) <= 0) { |
| | | hours = baseHours; |
| | | } else { |
| | | BigDecimal overDistance = distanceKm.subtract(baseKm); |
| | | int extraCount = overDistance.divide(extraKm, 0, RoundingMode.CEILING).intValue(); |
| | | hours = baseHours + extraCount * extraHours; |
| | | } |
| | | if ("1".equals(tr.getFieldA())) { |
| | | result.setStandardHours(hours); |
| | | } else if ("2".equals(tr.getFieldA())) { |
| | | result.setUrgentHours(hours); |
| | | } |
| | | } |
| | | |
| | | return result; |
| | | } |
| | | |
| | |
| | | takeLgt = BigDecimal.valueOf(takeShop.getLongitude()); |
| | | takeLocationValue = takeShop.getAddress(); |
| | | } else if (dto.getTakeLat() != null && dto.getTakeLgt() != null && StringUtils.isNotBlank(dto.getTakeLocation())) { |
| | | // 无取件门店,校验存件点与自选取件点是否在同一城市 |
| | | if (!MapUtil.isSameCity(depositShop.getLatitude(), depositShop.getLongitude(), |
| | | dto.getTakeLat().doubleValue(), dto.getTakeLgt().doubleValue())) { |
| | | throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "异地寄存订单存取点不在同一城市,如需请选择同城门店"); |
| | | } |
| | | takeLat = dto.getTakeLat(); |
| | | takeLgt = dto.getTakeLgt(); |
| | | takeLocationValue = dto.getTakeLocation(); |
| | |
| | | // ========== 3. 计算费用 ========== |
| | | PriceCalculateVO priceResult; |
| | | if (Constants.ZERO.equals(dto.getType())) { |
| | | // 就地寄存:计算天数 |
| | | long diffMs = takeTime.getTime() - depositTime.getTime(); |
| | | int days = (int) Math.max(1, (diffMs / (1000 * 60 * 60 * 24)) + 1); |
| | | |
| | | // 就地寄存 |
| | | CalculateLocalPriceDTO priceDTO = new CalculateLocalPriceDTO(); |
| | | priceDTO.setCityId(dto.getCityId()); |
| | | priceDTO.setEstimatedDepositDays(days); |
| | | priceDTO.setDepositStartTime(depositTime); |
| | | priceDTO.setDepositEndTime(takeTime); |
| | | priceDTO.setItems(dto.getItems()); |
| | | priceDTO.setInsured(dto.getDeclaredAmount() != null && dto.getDeclaredAmount().compareTo(BigDecimal.ZERO) > 0); |
| | | priceDTO.setDeclaredAmount(dto.getDeclaredAmount()); |
| | | priceResult = calculateLocalPrice(priceDTO); |
| | | } else { |
| | |
| | | priceDTO.setToLat(takeLat); |
| | | priceDTO.setToLgt(takeLgt); |
| | | priceDTO.setItems(dto.getItems()); |
| | | priceDTO.setInsured(dto.getDeclaredAmount() != null && dto.getDeclaredAmount().compareTo(BigDecimal.ZERO) > 0); |
| | | priceDTO.setDeclaredAmount(dto.getDeclaredAmount()); |
| | | priceDTO.setUrgent(Constants.ONE.equals(dto.getIsUrgent())); |
| | | priceResult = calculateRemotePrice(priceDTO); |
| | |
| | | |
| | | // 物品信息 |
| | | orders.setGoodType(dto.getGoodType()); |
| | | orders.setGoodLevel(goodTypeCategory.getRelationId()); |
| | | // 拼接物品信息:物品类型名称、尺寸名称*数量(数组字符串) |
| | | List<String> goodsParts = new ArrayList<>(); |
| | | for (ItemPriceVO itemVO : priceResult.getItemList()) { |
| | |
| | | if (member == null || StringUtils.isBlank(member.getOpenid())) { |
| | | throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "用户信息异常,无法发起支付"); |
| | | } |
| | | PayResponse payResponse = wxPay(orders, member.getOpenid(), Constants.OrdersAttach.STORAGE_ORDER); |
| | | PayResponse payResponse = wxPayV3(orders.getOutTradeNo(), orders.getTotalAmount(), orders.getId(), |
| | | member.getOpenid(), Constants.OrdersAttach.STORAGE_ORDER); |
| | | payResponse.setLockKey(lockKey); |
| | | return payResponse; |
| | | } |
| | |
| | | if (member == null || StringUtils.isBlank(member.getOpenid())) { |
| | | throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "用户信息异常,无法发起支付"); |
| | | } |
| | | return wxPay(orders, member.getOpenid(), Constants.OrdersAttach.STORAGE_ORDER); |
| | | return wxPayV3(orders.getOutTradeNo(), orders.getTotalAmount(), orders.getId(), |
| | | member.getOpenid(), Constants.OrdersAttach.STORAGE_ORDER); |
| | | } |
| | | |
| | | /** |
| | |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 唤起微信支付V3 |
| | | * |
| | | * @param outTradeNo 商户订单号 |
| | | * @param totalCents 支付金额(分) |
| | | * @param orderId 订单主键 |
| | | * @param openid 用户微信openid |
| | | * @param ordersAttach 订单支付类型 |
| | | * @return PayResponse 包含微信调起参数和订单主键 |
| | | */ |
| | | private PayResponse wxPayV3(String outTradeNo, Long totalCents, Integer orderId, |
| | | String openid, Constants.OrdersAttach ordersAttach) { |
| | | Map<String, String> payParams = wxPayV3Service.createOrder( |
| | | outTradeNo, |
| | | ordersAttach.getName(), |
| | | totalCents != null ? totalCents : 0L, |
| | | openid, |
| | | wxPayProperties.getV3NotifyUrl(), |
| | | ordersAttach.getKey() |
| | | ); |
| | | |
| | | PayResponse payResponse = new PayResponse(); |
| | | payResponse.setResponse(payParams); |
| | | payResponse.setOrderId(orderId); |
| | | return payResponse; |
| | | } |
| | | |
| | | |
| | | |
| | |
| | | |
| | | private String getOrdersPrefix() { |
| | | try { |
| | | return systemDictDataBiz.queryByCode(Constants.SYSTEM, Constants.RESOURCE_PATH).getCode() |
| | | + systemDictDataBiz.queryByCode(Constants.SYSTEM, Constants.ORDERS_FILES).getCode(); |
| | | return systemDictDataBiz.queryByCode(Constants.OSS, Constants.RESOURCE_PATH).getCode() |
| | | + systemDictDataBiz.queryByCode(Constants.OSS, Constants.ORDERS_FILES).getCode(); |
| | | } catch (Exception e) { |
| | | return ""; |
| | | } |
| | |
| | | // 寄件门店占比:fieldA=0(企业寄)/1(个人寄) |
| | | int depositFieldA = Constants.equalsInteger(depositShop.getCompanyType(), Constants.ONE) ? Constants.ZERO : Constants.ONE; |
| | | BigDecimal depositShopRata = getRevenueShareRata(cityId, depositFieldA); |
| | | // 取件门店占比:fieldA=2(企业取)/3(个人取) |
| | | int takeFieldA = Constants.equalsInteger(takeShop.getCompanyType(), Constants.ONE) ? Constants.TWO : Constants.THREE; |
| | | BigDecimal takeShopRata = getRevenueShareRata(cityId, takeFieldA); |
| | | // 取件门店占比:无取件门店时比例为0 |
| | | BigDecimal takeShopRata = BigDecimal.ZERO; |
| | | if (takeShop != null) { |
| | | int takeFieldA = Constants.equalsInteger(takeShop.getCompanyType(), Constants.ONE) ? Constants.TWO : Constants.THREE; |
| | | takeShopRata = getRevenueShareRata(cityId, takeFieldA); |
| | | } |
| | | |
| | | // 计算薪酬(分):totalAmount 为分,rata 为比例值(如 0.15 表示 15%) |
| | | long driverFee = new BigDecimal(totalAmount).multiply(driverRata).longValue(); |
| | | long depositShopFee = new BigDecimal(totalAmount).multiply(depositShopRata).longValue(); |
| | | long takeShopFee = totalAmount - driverFee - depositShopFee; |
| | | long takeShopFee = new BigDecimal(totalAmount).multiply(takeShopRata).longValue(); |
| | | |
| | | orders.setDriverFee(driverFee); |
| | | orders.setDepositShopFee(depositShopFee); |
| | |
| | | vo.setDetailList(buildDetailList(details)); |
| | | |
| | | // 逾期信息(仅待取件状态计算) |
| | | if (Integer.valueOf(Constants.OrderStatus.arrived.getStatus()).equals(o.getStatus())) { |
| | | OverdueFeeVO overdueInfo = calculateOverdueFeeInternal(o, details); |
| | | vo.setOverdue(overdueInfo.getOverdue()); |
| | | vo.setOverdueDays(overdueInfo.getOverdueDays()); |
| | | vo.setOverdueFee(overdueInfo.getOverdueFee()); |
| | | } |
| | | voList.add(vo); |
| | | } |
| | | } |
| | | |
| | | IPage<MyOrderVO> vPage = new Page<>(pageWrap.getPage(), pageWrap.getCapacity()); |
| | | PageData<MyOrderVO> pageData = PageData.from(vPage); |
| | | pageData.setRecords(voList); |
| | | pageData.setTotal(orderPage.getTotal()); |
| | | pageData.setPage(orderPage.getCurrent()); |
| | | pageData.setCapacity(orderPage.getSize()); |
| | | return pageData; |
| | | } |
| | | |
| | | @Override |
| | | public PageData<MyOrderVO> findShopOrderPage(PageWrap<MyOrderDTO> pageWrap, Integer shopId) { |
| | | MyOrderDTO model = pageWrap.getModel(); |
| | | Integer status = model != null ? model.getStatus() : null; |
| | | Integer combinedStatus = model != null ? model.getCombinedStatus() : null; |
| | | |
| | | // 解析合并状态为具体状态列表 |
| | | List<Integer> statusList = null; |
| | | if (combinedStatus != null) { |
| | | Constants.OrderCombinedStatus combined = Constants.OrderCombinedStatus.getByKey(combinedStatus); |
| | | if (combined != null) { |
| | | statusList = new ArrayList<>(); |
| | | for (int s : combined.getStatuses()) { |
| | | statusList.add(s); |
| | | } |
| | | } |
| | | } |
| | | |
| | | IPage<Orders> p = new Page<>(pageWrap.getPage(), pageWrap.getCapacity()); |
| | | MPJLambdaWrapper<Orders> wrapper = new MPJLambdaWrapper<Orders>() |
| | | .selectAll(Orders.class) |
| | | .select("s1.name", Orders::getDepositShopName) |
| | | .select("s1.link_name", Orders::getDepositShopLinkName) |
| | | .select("s1.link_phone", Orders::getDepositShopLinkPhone) |
| | | .select("s2.name", Orders::getTakeShopName) |
| | | .select("s2.address", Orders::getTakeShopAddress) |
| | | .leftJoin("shop_info s1 on s1.id = t.DEPOSIT_SHOP_ID") |
| | | .leftJoin("shop_info s2 on s2.id = t.TAKE_SHOP_ID") |
| | | .eq(Orders::getPayStatus, Constants.ONE) |
| | | .and(w -> w.eq(Orders::getDepositShopId, shopId).or().eq(Orders::getTakeShopId, shopId)) |
| | | .eq(status != null, Orders::getStatus, status) |
| | | .in(statusList != null, Orders::getStatus, statusList) |
| | | .orderByDesc(Orders::getCreateTime); |
| | | |
| | | IPage<Orders> orderPage = ordersMapper.selectJoinPage(p, Orders.class, wrapper); |
| | | List<MyOrderVO> voList = new ArrayList<>(); |
| | | if (orderPage != null && orderPage.getRecords() != null) { |
| | | for (Orders o : orderPage.getRecords()) { |
| | | MyOrderVO vo = new MyOrderVO(); |
| | | vo.setId(o.getId()); |
| | | vo.setCode(o.getCode()); |
| | | vo.setType(o.getType()); |
| | | vo.setStatus(o.getStatus()); |
| | | vo.setCreateTime(o.getCreateTime()); |
| | | vo.setExpectedTakeTime(o.getExpectedTakeTime()); |
| | | |
| | | vo.setDepositShopName(o.getDepositShopName()); |
| | | vo.setDepositShopLinkName(o.getDepositShopLinkName()); |
| | | vo.setDepositShopPhone(o.getDepositShopLinkPhone()); |
| | | |
| | | // 门店角色:存件门店=1,取件门店=2 |
| | | if (Constants.equalsInteger(o.getDepositShopId(), shopId)) { |
| | | vo.setShopRole(Constants.ONE); |
| | | } else if (Constants.equalsInteger(o.getTakeShopId(), shopId)) { |
| | | vo.setShopRole(Constants.TWO); |
| | | } |
| | | |
| | | if (o.getTakeShopId() != null) { |
| | | vo.setTakeShopName(o.getTakeShopName()); |
| | | vo.setTakeShopAddress(o.getTakeShopAddress()); |
| | | } else { |
| | | vo.setTakeLocation(o.getTakeLocation()); |
| | | vo.setTakeLocationRemark(o.getTakeLocationRemark()); |
| | | } |
| | | |
| | | vo.setTakeUser(o.getTakeUser()); |
| | | vo.setTakePhone(o.getTakePhone()); |
| | | vo.setDeclaredFee(o.getDeclaredFee()); |
| | | vo.setEstimatedAmount(o.getEstimatedAmount()); |
| | | |
| | | List<OrdersDetail> details = ordersDetailMapper.selectList( |
| | | new QueryWrapper<OrdersDetail>().lambda() |
| | | .eq(OrdersDetail::getOrderId, o.getId()) |
| | | .eq(OrdersDetail::getDeleted, Constants.ZERO)); |
| | | |
| | | vo.setDetailList(buildDetailList(details)); |
| | | |
| | | if (Integer.valueOf(Constants.OrderStatus.arrived.getStatus()).equals(o.getStatus())) { |
| | | OverdueFeeVO overdueInfo = calculateOverdueFeeInternal(o, details); |
| | | vo.setOverdue(overdueInfo.getOverdue()); |
| | |
| | | refund.setCreateTime(now); |
| | | refund.setDeleted(Constants.ZERO); |
| | | |
| | | // 调用微信退款,全额退款 |
| | | String refundCode = wxMiniUtilService.wxRefund(order.getOutTradeNo(), order.getPayAmount(), order.getPayAmount()); |
| | | refund.setRefundCode(refundCode); |
| | | refund.setRefundTime(new Date()); |
| | | // 调用微信退款V3,全额退款 |
| | | Refund refundResult = wxPayV3Service.refund(order.getOutTradeNo(), order.getPayAmount(), order.getPayAmount(), |
| | | "订单退款", wxPayProperties.getV3RefundNotifyUrl()); |
| | | refund.setRefundCode(refundResult.getOutRefundNo()); |
| | | refund.setStatus(Constants.ZERO); // 退款中 |
| | | ordersRefundMapper.insert(refund); |
| | | |
| | | order.setStatus(Constants.OrderStatus.cancelled.getStatus()); |
| | |
| | | ordersMapper.updateById(order); |
| | | |
| | | saveCancelLog(order, "会员取消订单(待寄存,全额退款)", reason, memberId); |
| | | // 通知会员:退款中 |
| | | sendOrderNotice(memberId, Constants.MemberOrderNotify.REFUNDING, orderId, |
| | | "orderNo", order.getCode()); |
| | | return; |
| | | } |
| | | |
| | |
| | | order.setCancelTime(now); |
| | | ordersMapper.updateById(order); |
| | | saveCancelLog(order, "会员申请取消订单(已寄存/已接单)", reason, memberId); |
| | | // 通知存件门店:退款申请 |
| | | if (order.getDepositShopId() != null) { |
| | | sendShopNotice(order.getDepositShopId(), Constants.ShopOrderNotify.REFUNDING, orderId, |
| | | "orderNo", order.getCode()); |
| | | } |
| | | return; |
| | | } |
| | | |
| | |
| | | orderLogService.create(log); |
| | | } |
| | | |
| | | /** |
| | | * 发送订单站内信通知 |
| | | */ |
| | | private void sendOrderNotice(Integer memberId, Constants.MemberOrderNotify notify, Integer orderId, String... params) { |
| | | Notice notice = new Notice(); |
| | | notice.setUserType(0); // 0=会员 |
| | | notice.setUserId(memberId); |
| | | notice.setTitle(notify.getTitle()); |
| | | notice.setContent(notify.format(params)); |
| | | notice.setObjId(orderId); |
| | | notice.setObjType(0); // 0=订单 |
| | | notice.setStatus(0); // 0=未读 |
| | | notice.setIsdeleted(Constants.ZERO); |
| | | notice.setCreateDate(new Date()); |
| | | noticeService.create(notice); |
| | | } |
| | | |
| | | /** |
| | | * 发送门店站内信通知 |
| | | */ |
| | | private void sendShopNotice(Integer shopId, Constants.ShopOrderNotify notify, Integer orderId, String... params) { |
| | | Notice notice = new Notice(); |
| | | notice.setUserType(2); // 2=门店 |
| | | notice.setUserId(shopId); |
| | | notice.setTitle(notify.getTitle()); |
| | | notice.setContent(notify.format(params)); |
| | | notice.setObjId(orderId); |
| | | notice.setObjType(0); // 0=订单 |
| | | notice.setStatus(0); // 0=未读 |
| | | notice.setIsdeleted(Constants.ZERO); |
| | | notice.setCreateDate(new Date()); |
| | | noticeService.create(notice); |
| | | } |
| | | |
| | | /** |
| | | * 通知存件门店和取件门店(订单完成/评价等) |
| | | */ |
| | | private void notifyBothShops(Orders order, Constants.ShopOrderNotify notify, String... params) { |
| | | if (order.getDepositShopId() != null) { |
| | | sendShopNotice(order.getDepositShopId(), notify, order.getId(), params); |
| | | } |
| | | if (order.getTakeShopId() != null) { |
| | | sendShopNotice(order.getTakeShopId(), notify, order.getId(), params); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = {Exception.class, BusinessException.class}) |
| | | public void handleStorageOrderPayNotify(String outTradeNo, String wxTradeNo) { |
| | |
| | | order.setUpdateTime(now); |
| | | // 生成会员核销码 |
| | | order.setMemberVerifyCode(generateVerifyCode()); |
| | | // 异地寄存:计算预计送达时间 |
| | | if (Constants.ONE.equals(order.getType()) |
| | | && order.getDepositLat() != null && order.getDepositLgt() != null |
| | | && order.getTakeLat() != null && order.getTakeLgt() != null) { |
| | | EstimatedDeliveryResultVO deliveryResult = calculateEstimatedDelivery( |
| | | Integer.valueOf(order.getCityId()), |
| | | order.getDepositLat().doubleValue(), order.getDepositLgt().doubleValue(), |
| | | order.getTakeLat().doubleValue(), order.getTakeLgt().doubleValue()); |
| | | // isUrgent: 0=标速达; 1=极速达 |
| | | BigDecimal hours = Constants.ONE.equals(order.getIsUrgent()) |
| | | ? deliveryResult.getExpressHours() |
| | | : deliveryResult.getStandardHours(); |
| | | if (hours != null) { |
| | | long millis = hours.multiply(new BigDecimal("3600000")) |
| | | .setScale(0, RoundingMode.HALF_UP).longValue(); |
| | | order.setEstimatedDeliveryTime(new Date(now.getTime() + millis)); |
| | | } |
| | | } |
| | | ordersMapper.updateById(order); |
| | | |
| | | // 通知会员:订单待核验 |
| | | sendOrderNotice(order.getMemberId(), Constants.MemberOrderNotify.WAIT_VERIFY, order.getId(), |
| | | "orderNo", order.getCode(), |
| | | "storeCode", order.getMemberVerifyCode()); |
| | | |
| | | // 就地寄存订单:通知存件门店待核验 |
| | | if (Constants.ZERO.equals(order.getType()) && order.getDepositShopId() != null) { |
| | | sendShopNotice(order.getDepositShopId(), Constants.ShopOrderNotify.WAIT_VERIFY, order.getId(), |
| | | "orderNo", order.getCode()); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public PayResponse payOverdueFee(Integer orderId, Integer memberId) { |
| | | // 1. 查询寄存订单 |
| | | Orders order = ordersMapper.selectOne(new QueryWrapper<Orders>().lambda() |
| | | .eq(Orders::getId, orderId) |
| | | .eq(Orders::getMemberId, memberId) |
| | | .eq(Orders::getDeleted, Constants.ZERO)); |
| | | if (order == null) { |
| | | throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "订单不存在"); |
| | | } |
| | | // 2. 校验状态:待取件(5) + 逾期(1) |
| | | if (!Constants.equalsInteger(order.getStatus(), Constants.OrderStatus.arrived.getStatus())) { |
| | | throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "当前订单状态不支持逾期支付"); |
| | | } |
| | | if (!Constants.equalsInteger(order.getOverdueStatus(), Constants.ONE)) { |
| | | throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "该订单不存在逾期费用"); |
| | | } |
| | | if (order.getOverdueAmount() == null || order.getOverdueAmount() <= 0) { |
| | | throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "逾期费用异常,无法发起支付"); |
| | | } |
| | | // 3. 查询会员 |
| | | Member member = memberMapper.selectById(memberId); |
| | | if (member == null || StringUtils.isBlank(member.getOpenid())) { |
| | | throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "用户信息异常,无法发起支付"); |
| | | } |
| | | // 4. 创建逾期费用订单 |
| | | String outTradeNo = generateOrderTradeNo(); |
| | | Date now = new Date(); |
| | | OtherOrders otherOrders = new OtherOrders(); |
| | | otherOrders.setType(Constants.TWO); // 2=逾期费用订单 |
| | | otherOrders.setMemberId(memberId); |
| | | otherOrders.setOrderId(orderId); |
| | | otherOrders.setPayAccount(order.getOverdueAmount()); |
| | | otherOrders.setPayStatus(Constants.ZERO); |
| | | otherOrders.setCode("OD" + new java.text.SimpleDateFormat("yyyyMMddHHmmss").format(now) + orderId); |
| | | otherOrders.setOutTradeNo(outTradeNo); |
| | | otherOrders.setDeleted(Constants.ZERO); |
| | | otherOrders.setCreateTime(now); |
| | | otherOrdersMapper.insert(otherOrders); |
| | | |
| | | // 5. 唤起微信支付V3 |
| | | return wxPayV3(otherOrders.getOutTradeNo(), otherOrders.getPayAccount(), otherOrders.getId(), |
| | | member.getOpenid(), Constants.OrdersAttach.OVERDUE_FEE); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = {Exception.class, BusinessException.class}) |
| | | public void handleOverdueFeePayNotify(String outTradeNo, String wxTradeNo) { |
| | | // 1. 查找逾期费用订单 |
| | | OtherOrders otherOrders = otherOrdersMapper.selectOne(new QueryWrapper<OtherOrders>().lambda() |
| | | .eq(OtherOrders::getOutTradeNo, outTradeNo) |
| | | .eq(OtherOrders::getDeleted, Constants.ZERO) |
| | | .last("limit 1")); |
| | | if (otherOrders == null) { |
| | | throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "逾期费用订单不存在: " + outTradeNo); |
| | | } |
| | | // 2. 幂等:已支付则跳过 |
| | | if (Constants.equalsInteger(otherOrders.getPayStatus(), Constants.ONE)) { |
| | | return; |
| | | } |
| | | Date now = new Date(); |
| | | // 3. 更新逾期费用订单状态 |
| | | otherOrders.setPayStatus(Constants.ONE); |
| | | otherOrders.setPayTime(now); |
| | | otherOrders.setWxExternalNo(wxTradeNo); |
| | | otherOrders.setUpdateTime(now); |
| | | otherOrdersMapper.updateById(otherOrders); |
| | | |
| | | // 4. 更新寄存订单逾期状态为已支付(2),更新总金额,重算三方收益 |
| | | if (otherOrders.getOrderId() != null) { |
| | | Orders order = ordersMapper.selectById(otherOrders.getOrderId()); |
| | | if (order != null) { |
| | | order.setOverdueStatus(Constants.TWO); // 2=已支付 |
| | | // 总金额 = 原金额 + 逾期费用 |
| | | Long overdueFee = otherOrders.getPayAccount() != null ? otherOrders.getPayAccount() : 0L; |
| | | long newTotal = (order.getTotalAmount() != null ? order.getTotalAmount() : 0L) + overdueFee; |
| | | order.setTotalAmount(newTotal); |
| | | order.setUpdateTime(now); |
| | | ordersMapper.updateById(order); |
| | | // 重算三方收益 |
| | | calculateAndSaveOrderFees(order.getId()); |
| | | } |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void deleteMyOrder(Integer orderId, Integer memberId) { |
| | | Orders order = ordersMapper.selectById(orderId); |
| | | if (order == null || !Constants.equalsInteger(order.getDeleted(), Constants.ZERO)) { |
| | | throw new BusinessException(ResponseStatus.DATA_EMPTY); |
| | | } |
| | | if (!Constants.equalsInteger(order.getMemberId(), memberId)) { |
| | | throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "无权操作此订单"); |
| | | } |
| | | // 仅已完成(7)、已取消(99)、已退款(96)可删除 |
| | | int status = Constants.formatIntegerNum(order.getStatus()); |
| | | if (status != Constants.OrderStatus.finished.getStatus() |
| | | && status != Constants.OrderStatus.cancelled.getStatus() |
| | | && status != Constants.OrderStatus.closed.getStatus()) { |
| | | throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "当前订单状态不可删除"); |
| | | } |
| | | ordersMapper.update(new UpdateWrapper<Orders>().lambda() |
| | | .set(Orders::getDeleted, Constants.ONE) |
| | | .set(Orders::getUpdateTime, new Date()) |
| | | .eq(Orders::getId, orderId)); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = {Exception.class, BusinessException.class}) |
| | | public PayResponse payShopDeposit(Integer shopId) { |
| | | // 1. 查询门店信息 |
| | | ShopInfo shopInfo = shopInfoMapper.selectById(shopId); |
| | | if (shopInfo == null) { |
| | | throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "门店不存在"); |
| | | } |
| | | // 2. 校验状态:审批通过(1)才能支付押金 |
| | | if (!Constants.equalsInteger(shopInfo.getAuditStatus(), Constants.ONE)) { |
| | | throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "当前门店状态不支持支付押金"); |
| | | } |
| | | if (shopInfo.getDepositAmount() == null || shopInfo.getDepositAmount() <= 0) { |
| | | throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "押金金额异常,无法发起支付"); |
| | | } |
| | | // 3. 查询会员openid |
| | | Member member = memberMapper.selectById(shopInfo.getRegionMemberId()); |
| | | if (member == null || StringUtils.isBlank(member.getOpenid())) { |
| | | throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "用户信息异常,无法发起支付"); |
| | | } |
| | | // 4. 创建押金订单 |
| | | String outTradeNo = generateOrderTradeNo(); |
| | | Date now = new Date(); |
| | | OtherOrders otherOrders = new OtherOrders(); |
| | | otherOrders.setType(Constants.ZERO); // 0=店铺押金订单 |
| | | otherOrders.setMemberId(shopInfo.getRegionMemberId()); |
| | | otherOrders.setPayAccount(shopInfo.getDepositAmount()); |
| | | otherOrders.setPayStatus(Constants.ZERO); |
| | | otherOrders.setCode("SD" + new java.text.SimpleDateFormat("yyyyMMddHHmmss").format(now) + shopId); |
| | | otherOrders.setOutTradeNo(outTradeNo); |
| | | otherOrders.setDeleted(Constants.ZERO); |
| | | otherOrders.setCreateTime(now); |
| | | otherOrdersMapper.insert(otherOrders); |
| | | |
| | | // 5. 唤起微信支付V3 |
| | | return wxPayV3(otherOrders.getOutTradeNo(), otherOrders.getPayAccount(), otherOrders.getId(), |
| | | member.getOpenid(), Constants.OrdersAttach.SHOP_DEPOSIT); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = {Exception.class, BusinessException.class}) |
| | | public void handleShopDepositPayNotify(String outTradeNo, String wxTradeNo) { |
| | | // 1. 查找押金订单 |
| | | OtherOrders otherOrders = otherOrdersMapper.selectOne(new QueryWrapper<OtherOrders>().lambda() |
| | | .eq(OtherOrders::getOutTradeNo, outTradeNo) |
| | | .eq(OtherOrders::getDeleted, Constants.ZERO) |
| | | .last("limit 1")); |
| | | if (otherOrders == null) { |
| | | throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "押金订单不存在: " + outTradeNo); |
| | | } |
| | | // 2. 幂等:已支付则跳过 |
| | | if (Constants.equalsInteger(otherOrders.getPayStatus(), Constants.ONE)) { |
| | | return; |
| | | } |
| | | Date now = new Date(); |
| | | // 3. 更新押金订单状态 |
| | | otherOrders.setPayStatus(Constants.ONE); |
| | | otherOrders.setPayTime(now); |
| | | otherOrders.setWxExternalNo(wxTradeNo); |
| | | otherOrders.setUpdateTime(now); |
| | | otherOrdersMapper.updateById(otherOrders); |
| | | |
| | | // 4. 查询门店信息(通过注册会员主键关联) |
| | | ShopInfo shopInfo = shopInfoMapper.selectOne(new QueryWrapper<ShopInfo>().lambda() |
| | | .eq(ShopInfo::getRegionMemberId, otherOrders.getMemberId()) |
| | | .eq(ShopInfo::getDeleted, Constants.ZERO) |
| | | .last("limit 1")); |
| | | if (shopInfo == null) { |
| | | throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "门店不存在"); |
| | | } |
| | | // 5. 更新门店状态:已支付押金 |
| | | shopInfo.setAuditStatus(Constants.THREE); // 3=已支付押金 |
| | | shopInfo.setPayStatus(Constants.ONE); |
| | | shopInfo.setPayTime(now); |
| | | shopInfo.setWxExternalNo(wxTradeNo); |
| | | shopInfo.setCode(otherOrders.getCode()); |
| | | Member member = memberMapper.selectById(otherOrders.getMemberId()); |
| | | if (member != null) { |
| | | shopInfo.setPayMemberOpenId(member.getOpenid()); |
| | | } |
| | | shopInfo.setUpdateTime(now); |
| | | shopInfoMapper.updateById(shopInfo); |
| | | |
| | | // 6. 押金支付完成后,若城市未开通则自动开通 |
| | | if (shopInfo.getAreaId() != null) { |
| | | Areas shopArea = areasBiz.resolveArea(shopInfo.getAreaId()); |
| | | if (shopArea != null && shopArea.getParentId() != null) { |
| | | Areas cityArea = areasBiz.resolveArea(shopArea.getParentId()); |
| | | if (cityArea != null && !Constants.equalsInteger(cityArea.getStatus(), Constants.ONE)) { |
| | | cityArea.setStatus(Constants.ONE); |
| | | cityArea.setEditDate(now); |
| | | areasService.updateById(cityArea); |
| | | areasService.cacheData(); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = {Exception.class, BusinessException.class}) |
| | | public void settleOrders() { |
| | | // 1. 读取结算天数配置 |
| | | SystemDictData settlementConfig = systemDictDataBiz.queryByCode(Constants.OPERATION_CONFIG, Constants.OP_SETTLEMENT_DATE); |
| | | if (settlementConfig == null || StringUtils.isBlank(settlementConfig.getCode())) { |
| | | return; |
| | | } |
| | | int days = Integer.parseInt(settlementConfig.getCode()); |
| | | // 结算截止时间 = 当前时间 - N天 |
| | | Calendar cal = Calendar.getInstance(); |
| | | cal.add(Calendar.DAY_OF_MONTH, -days); |
| | | Date deadline = cal.getTime(); |
| | | |
| | | // 2. 查询已完成的待结算订单(完成时间 <= 截止时间) |
| | | List<Orders> ordersList = ordersMapper.selectList(new QueryWrapper<Orders>().lambda() |
| | | .eq(Orders::getDeleted, Constants.ZERO) |
| | | .eq(Orders::getStatus, Constants.OrderStatus.finished.getStatus()) |
| | | .eq(Orders::getSettlementStatus, Constants.ZERO) |
| | | .le(Orders::getFinishTime, deadline)); |
| | | if (ordersList == null || ordersList.isEmpty()) { |
| | | return; |
| | | } |
| | | |
| | | Date now = new Date(); |
| | | for (Orders order : ordersList) { |
| | | // 3. 更新订单结算状态 |
| | | ordersMapper.update(new UpdateWrapper<Orders>().lambda() |
| | | .set(Orders::getSettlementStatus, Constants.ONE) |
| | | .set(Orders::getSettlementTime, now) |
| | | .set(Orders::getUpdateTime, now) |
| | | .eq(Orders::getId, order.getId())); |
| | | |
| | | // 4. 查询关联的待入账 Revenue 记录 |
| | | List<Revenue> revenues = revenueMapper.selectList(new QueryWrapper<Revenue>().lambda() |
| | | .eq(Revenue::getObjId, order.getId()) |
| | | .eq(Revenue::getObjType, Constants.ZERO) |
| | | .eq(Revenue::getVaildStatus, Constants.ZERO) |
| | | .eq(Revenue::getDeleted, Constants.ZERO)); |
| | | |
| | | for (Revenue revenue : revenues) { |
| | | Long amount = revenue.getAmount() != null ? revenue.getAmount() : 0L; |
| | | // 更新 Revenue 为已入账 |
| | | revenueMapper.update(new UpdateWrapper<Revenue>().lambda() |
| | | .set(Revenue::getVaildStatus, Constants.ONE) |
| | | .set(Revenue::getUpdateTime, now) |
| | | .eq(Revenue::getId, revenue.getId())); |
| | | |
| | | // 根据 memberType 更新余额 |
| | | if (Constants.equalsInteger(revenue.getMemberType(), Constants.ONE)) { |
| | | // 司机:通过 memberId 查 DriverInfo,更新 balance / totalBalance |
| | | DriverInfo driver = driverInfoMapper.selectOne(new QueryWrapper<DriverInfo>().lambda() |
| | | .eq(DriverInfo::getMemberId, revenue.getMemberId()) |
| | | .eq(DriverInfo::getDeleted, Constants.ZERO) |
| | | .last("limit 1")); |
| | | if (driver != null) { |
| | | driverInfoMapper.update(new UpdateWrapper<DriverInfo>().lambda() |
| | | .setSql(" BALANCE = IFNULL(BALANCE, 0) + " + amount) |
| | | .setSql(" TOTAL_BALANCE = IFNULL(TOTAL_BALANCE, 0) + " + amount) |
| | | .eq(DriverInfo::getId, driver.getId())); |
| | | } |
| | | } else if (Constants.equalsInteger(revenue.getMemberType(), Constants.TWO)) { |
| | | // 门店:通过 memberId 查 ShopInfo(regionMemberId),更新 balance / totalBalance |
| | | ShopInfo shop = shopInfoMapper.selectOne(new QueryWrapper<ShopInfo>().lambda() |
| | | .eq(ShopInfo::getRegionMemberId, revenue.getMemberId()) |
| | | .eq(ShopInfo::getDeleted, Constants.ZERO) |
| | | .last("limit 1")); |
| | | if (shop != null) { |
| | | shopInfoMapper.update(new UpdateWrapper<ShopInfo>().lambda() |
| | | .setSql(" BALANCE = IFNULL(BALANCE, 0) + " + amount) |
| | | .setSql(" TOTAL_BALANCE = IFNULL(TOTAL_BALANCE, 0) + " + amount) |
| | | .eq(ShopInfo::getId, shop.getId())); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 通知相关门店:订单已结算 |
| | | notifyBothShops(order, Constants.ShopOrderNotify.SETTLED, |
| | | "orderNo", order.getCode(), |
| | | "amount", String.valueOf(Constants.getFormatMoney( |
| | | order.getTotalAmount() != null ? order.getTotalAmount() : 0L))); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = {Exception.class, BusinessException.class}) |
| | | public void commentOrder(CommentOrderDTO dto, Integer memberId) { |
| | | // 1. 校验订单 |
| | | Orders order = ordersMapper.selectById(dto.getOrderId()); |
| | | if (order == null || Constants.equalsInteger(order.getDeleted(), Constants.ONE)) { |
| | | throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "订单不存在"); |
| | | } |
| | | if (!Constants.equalsInteger(order.getMemberId(), memberId)) { |
| | | throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "无权评价该订单"); |
| | | } |
| | | if (!Constants.equalsInteger(order.getStatus(), Constants.OrderStatus.finished.getStatus())) { |
| | | throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "当前订单状态不支持评价"); |
| | | } |
| | | if (Constants.equalsInteger(order.getCommentStatus(), Constants.ONE)) { |
| | | throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "该订单已评价"); |
| | | } |
| | | |
| | | // 2. 异地寄存订单:取件门店和司机评分校验 |
| | | boolean isRemote = Constants.equalsInteger(order.getType(), Constants.ONE); |
| | | if (isRemote) { |
| | | if (dto.getDriverScore() == null) { |
| | | throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "异地寄存订单必须评价司机"); |
| | | } |
| | | if (order.getTakeShopId() != null && dto.getTakeScore() == null) { |
| | | throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "请评价取件门店"); |
| | | } |
| | | } |
| | | |
| | | Date now = new Date(); |
| | | |
| | | // 3. 更新订单评价状态 |
| | | order.setCommentStatus(Constants.ONE); |
| | | order.setCommentInfo(dto.getContent()); |
| | | order.setCommentDepositLevel(dto.getDepositScore()); |
| | | order.setCommentTakeLevel(dto.getTakeScore()); |
| | | order.setCommentDriverLevel(dto.getDriverScore()); |
| | | order.setCommentTime(now); |
| | | order.setUpdateTime(now); |
| | | ordersMapper.updateById(order); |
| | | |
| | | // 4. 创建评价记录 |
| | | // 4.1 存件门店 |
| | | OrderComment depositComment = new OrderComment(); |
| | | depositComment.setOrderId(order.getId()); |
| | | depositComment.setOrderCode(order.getCode()); |
| | | depositComment.setMemberId(memberId); |
| | | depositComment.setTargetType(Constants.ONE); // 1=存件门店 |
| | | depositComment.setTargetId(order.getDepositShopId()); |
| | | depositComment.setScore(dto.getDepositScore()); |
| | | depositComment.setContent(dto.getContent()); |
| | | depositComment.setDeleted(Constants.ZERO); |
| | | depositComment.setCreateTime(now); |
| | | orderCommentMapper.insert(depositComment); |
| | | |
| | | // 4.2 取件门店(异地寄存且有取件门店) |
| | | if (isRemote && order.getTakeShopId() != null && dto.getTakeScore() != null) { |
| | | OrderComment takeComment = new OrderComment(); |
| | | takeComment.setOrderId(order.getId()); |
| | | takeComment.setOrderCode(order.getCode()); |
| | | takeComment.setMemberId(memberId); |
| | | takeComment.setTargetType(Constants.TWO); // 2=取件门店 |
| | | takeComment.setTargetId(order.getTakeShopId()); |
| | | takeComment.setScore(dto.getTakeScore()); |
| | | takeComment.setContent(dto.getContent()); |
| | | takeComment.setDeleted(Constants.ZERO); |
| | | takeComment.setCreateTime(now); |
| | | orderCommentMapper.insert(takeComment); |
| | | } |
| | | |
| | | // 4.3 司机(异地寄存) |
| | | if (isRemote && order.getAcceptDriver() != null && dto.getDriverScore() != null) { |
| | | OrderComment driverComment = new OrderComment(); |
| | | driverComment.setOrderId(order.getId()); |
| | | driverComment.setOrderCode(order.getCode()); |
| | | driverComment.setMemberId(memberId); |
| | | driverComment.setTargetType(Constants.THREE); // 3=司机 |
| | | driverComment.setTargetId(order.getAcceptDriver()); |
| | | driverComment.setScore(dto.getDriverScore()); |
| | | driverComment.setContent(dto.getContent()); |
| | | driverComment.setDeleted(Constants.ZERO); |
| | | driverComment.setCreateTime(now); |
| | | orderCommentMapper.insert(driverComment); |
| | | } |
| | | |
| | | // 5. 更新门店/司机平均评分 |
| | | updateTargetScore(Constants.ONE, order.getDepositShopId()); |
| | | if (isRemote && order.getTakeShopId() != null) { |
| | | updateTargetScore(Constants.TWO, order.getTakeShopId()); |
| | | } |
| | | if (isRemote && order.getAcceptDriver() != null) { |
| | | updateTargetScore(Constants.THREE, order.getAcceptDriver()); |
| | | } |
| | | |
| | | // 通知会员:订单已评价 |
| | | sendOrderNotice(memberId, Constants.MemberOrderNotify.EVALUATED, order.getId(), |
| | | "orderNo", order.getCode()); |
| | | |
| | | // 通知存件门店和取件门店:订单已评价 |
| | | notifyBothShops(order, Constants.ShopOrderNotify.EVALUATED, |
| | | "orderNo", order.getCode()); |
| | | } |
| | | |
| | | /** |
| | | * 更新评价对象(门店/司机)的平均评分 |
| | | */ |
| | | private void updateTargetScore(Integer targetType, Integer targetId) { |
| | | List<OrderComment> comments = orderCommentMapper.selectList(new QueryWrapper<OrderComment>().lambda() |
| | | .eq(OrderComment::getDeleted, Constants.ZERO) |
| | | .eq(OrderComment::getTargetType, targetType) |
| | | .eq(OrderComment::getTargetId, targetId)); |
| | | if (comments.isEmpty()) { |
| | | return; |
| | | } |
| | | double avg = comments.stream() |
| | | .mapToInt(OrderComment::getScore) |
| | | .average() |
| | | .orElse(0.0); |
| | | BigDecimal score = BigDecimal.valueOf(avg).setScale(1, BigDecimal.ROUND_HALF_UP); |
| | | Date now = new Date(); |
| | | if (Constants.equalsInteger(targetType, Constants.ONE) || Constants.equalsInteger(targetType, Constants.TWO)) { |
| | | ShopInfo shopInfo = shopInfoMapper.selectById(targetId); |
| | | if (shopInfo != null) { |
| | | shopInfo.setScore(score); |
| | | shopInfo.setUpdateTime(now); |
| | | shopInfoMapper.updateById(shopInfo); |
| | | } |
| | | } else if (Constants.equalsInteger(targetType, Constants.THREE)) { |
| | | DriverInfo driverInfo = driverInfoMapper.selectById(targetId); |
| | | if (driverInfo != null) { |
| | | driverInfo.setScore(score); |
| | | driverInfo.setUpdateTime(now); |
| | | driverInfoMapper.updateById(driverInfo); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 唤起微信支付(其他订单) |
| | | */ |
| | | private PayResponse wxPayForOtherOrder(OtherOrders otherOrders, String openid, Constants.OrdersAttach ordersAttach) { |
| | | try { |
| | | WxPayUnifiedOrderRequest request = new WxPayUnifiedOrderRequest(); |
| | | request.setBody(ordersAttach.getName()); |
| | | request.setAttach(ordersAttach.getKey()); |
| | | request.setOutTradeNo(otherOrders.getOutTradeNo()); |
| | | long totalFee = otherOrders.getPayAccount() != null ? otherOrders.getPayAccount() : 0L; |
| | | request.setTotalFee((int) totalFee); |
| | | request.setTimeStart(DateUtil.DateToString(new Date(), "yyyyMMddHHmmss")); |
| | | request.setSpbillCreateIp(Constants.getIpAddr()); |
| | | request.setOpenid(openid); |
| | | |
| | | Object response = WxMiniConfig.wxPayService.createOrder(request); |
| | | |
| | | PayResponse payResponse = new PayResponse(); |
| | | payResponse.setResponse(response); |
| | | payResponse.setOrderId(otherOrders.getId()); |
| | | return payResponse; |
| | | } catch (WxPayException e) { |
| | | throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "支付调起失败:" + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | |
| | | saveVerifyImages(order.getId(), images, Constants.FileType.ORDER_DEPOSIT.getKey(), shopId); |
| | | // 记录订单日志 |
| | | saveShopVerifyLog(order, "门店确认寄存", "门店【" + shopName + "】确认寄存", remark, shopId); |
| | | // 通知会员:门店核销成功 |
| | | if (Constants.equalsInteger(order.getType(), Constants.ONE)) { |
| | | // 异地寄存 → 待抢单 |
| | | sendOrderNotice(order.getMemberId(), Constants.MemberOrderNotify.WAIT_GRAB, order.getId(), |
| | | "orderNo", order.getCode()); |
| | | } else { |
| | | // 就地寄存 → 待取件提醒 |
| | | sendOrderNotice(order.getMemberId(), Constants.MemberOrderNotify.WAIT_PICKUP_REMIND, order.getId(), |
| | | "orderNo", order.getCode()); |
| | | } |
| | | } else if (Constants.equalsInteger(status, Constants.OrderStatus.arrived.getStatus())) { |
| | | // 异地寄存 + 无取件门店 → 无法核销(客户自取,无门店操作) |
| | | if (Constants.equalsInteger(order.getType(), Constants.ONE) && order.getTakeShopId() == null) { |
| | |
| | | releaseVerifyCode(verifyCode); |
| | | // 保存出库图片(obj_type=13 门店出库图片,最多3张) |
| | | saveVerifyImages(order.getId(), images, Constants.FileType.STORE_OUT.getKey(), shopId); |
| | | // 生成收益记录 |
| | | calculateAndSaveOrderFees(order.getId()); |
| | | generateRevenueRecords(order.getId()); |
| | | // 记录订单日志 |
| | | saveShopVerifyLog(order, "门店确认取件", "门店【" + shopName + "】确认取件,订单完成", remark, shopId); |
| | | // 通知会员:订单已完成 |
| | | sendOrderNotice(order.getMemberId(), Constants.MemberOrderNotify.FINISHED, order.getId(), |
| | | "orderNo", order.getCode()); |
| | | // 通知存件门店和取件门店:订单已完成 |
| | | String settleDays = operationConfigBiz.getConfig().getSettlementDate(); |
| | | notifyBothShops(order, Constants.ShopOrderNotify.FINISHED, |
| | | "orderNo", order.getCode(), |
| | | "settleDays", settleDays != null ? settleDays : "7"); |
| | | } else { |
| | | throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "当前订单状态不允许核销"); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = {Exception.class, BusinessException.class}) |
| | | public void confirmStoreOut(Integer orderId, Integer shopId, List<String> images, String remark) { |
| | | // 1. 查询订单 |
| | | Orders order = ordersMapper.selectById(orderId); |
| | | if (order == null || Constants.equalsInteger(order.getDeleted(), Constants.ONE)) { |
| | | throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "订单不存在"); |
| | | } |
| | | // 2. 校验状态:待取件(5) |
| | | if (!Constants.equalsInteger(order.getStatus(), Constants.OrderStatus.arrived.getStatus())) { |
| | | throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "当前订单状态不允许出库"); |
| | | } |
| | | // 3. 校验逾期状态:0=未逾期 或 2=已支付 |
| | | if (order.getOverdueStatus() != null && Constants.equalsInteger(order.getOverdueStatus(), Constants.ONE)) { |
| | | throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "订单存在逾期未支付费用,请先完成逾期费用支付"); |
| | | } |
| | | // 4. 校验确认到店时间不为空 |
| | | if (order.getConfirmArriveTime() == null) { |
| | | throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "订单尚未确认到店,无法出库"); |
| | | } |
| | | // 5. 校验门店与订单关系 |
| | | if (Constants.equalsInteger(order.getType(), Constants.ZERO)) { |
| | | // 就地寄存:取件门店即存件门店 |
| | | if (!shopId.equals(order.getDepositShopId())) { |
| | | throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "该订单不属于当前门店"); |
| | | } |
| | | } else { |
| | | // 异地寄存:校验取件门店 |
| | | if (order.getTakeShopId() == null) { |
| | | throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "该订单无取件门店,无法出库"); |
| | | } |
| | | if (!shopId.equals(order.getTakeShopId())) { |
| | | throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "该订单不属于当前门店"); |
| | | } |
| | | } |
| | | |
| | | // 6. 查询门店名称 |
| | | String shopName = ""; |
| | | ShopInfo shopInfo = shopInfoMapper.selectById(shopId); |
| | | if (shopInfo != null) { |
| | | shopName = shopInfo.getName() != null ? shopInfo.getName() : ""; |
| | | } |
| | | |
| | | // 7. 更新订单状态为已完成 |
| | | Date now = new Date(); |
| | | order.setStatus(Constants.OrderStatus.finished.getStatus()); |
| | | order.setFinishTime(now); |
| | | order.setUpdateTime(now); |
| | | ordersMapper.updateById(order); |
| | | |
| | | // 8. 释放核销码 |
| | | if (StringUtils.isNotBlank(order.getMemberVerifyCode())) { |
| | | releaseVerifyCode(order.getMemberVerifyCode()); |
| | | } |
| | | |
| | | // 9. 保存出库图片(obj_type=13 门店出库图片,最多3张) |
| | | saveVerifyImages(order.getId(), images, Constants.FileType.STORE_OUT.getKey(), shopId); |
| | | |
| | | // 10. 如果存在退款金额,先保存退款记录再调用微信退款 |
| | | // 退款记录在退款调用前落库,避免退款成功但本地异常导致无记录 |
| | | if (order.getRefundAmount() != null && order.getRefundAmount() > 0 |
| | | && StringUtils.isNotBlank(order.getOutTradeNo()) |
| | | && order.getPayAmount() != null && order.getPayAmount() > 0) { |
| | | OrdersRefund refundRecord = new OrdersRefund(); |
| | | refundRecord.setOrderId(orderId); |
| | | refundRecord.setType(3); // 出库退款 |
| | | refundRecord.setCreateTime(now); |
| | | refundRecord.setRefundRemark(remark); |
| | | refundRecord.setDeleted(Constants.ZERO); |
| | | ordersRefundMapper.insert(refundRecord); |
| | | |
| | | // 调用微信退款V3(放在最后,确保前置操作全部成功) |
| | | Refund refundResult = wxPayV3Service.refund( |
| | | order.getOutTradeNo(), order.getPayAmount(), order.getRefundAmount(), |
| | | "订单退款", wxPayProperties.getV3RefundNotifyUrl()); |
| | | |
| | | // 退款成功后回填退款单号,标记退款中 |
| | | refundRecord.setRefundCode(refundResult.getOutRefundNo()); |
| | | refundRecord.setStatus(Constants.ZERO); // 退款中 |
| | | ordersRefundMapper.updateById(refundRecord); |
| | | } |
| | | |
| | | // 11. 生成收益记录 |
| | | calculateAndSaveOrderFees(orderId); |
| | | generateRevenueRecords(orderId); |
| | | |
| | | // 12. 记录订单日志 |
| | | String logInfo = "门店【" + shopName + "】确认出库,订单完成"; |
| | | if (order.getRefundAmount() != null && order.getRefundAmount() > 0) { |
| | | logInfo += ",退款" + Constants.getFormatMoney(order.getRefundAmount()) + "元"; |
| | | } |
| | | saveShopVerifyLog(order, "门店确认出库", logInfo, remark, shopId); |
| | | // 通知会员:订单已完成 |
| | | sendOrderNotice(order.getMemberId(), Constants.MemberOrderNotify.FINISHED, order.getId(), |
| | | "orderNo", order.getCode()); |
| | | // 通知存件门店和取件门店:订单已完成 |
| | | String settleDays = operationConfigBiz.getConfig().getSettlementDate(); |
| | | notifyBothShops(order, Constants.ShopOrderNotify.FINISHED, |
| | | "orderNo", order.getCode(), |
| | | "settleDays", settleDays != null ? settleDays : "7"); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void memberConfirmReceipt(Integer orderId, Integer memberId) { |
| | | // 1. 查询订单 |
| | | Orders order = ordersMapper.selectById(orderId); |
| | | if (order == null || Constants.equalsInteger(order.getDeleted(), Constants.ONE)) { |
| | | throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "订单不存在"); |
| | | } |
| | | // 2. 校验归属 |
| | | if (!memberId.equals(order.getMemberId())) { |
| | | throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "无权操作该订单"); |
| | | } |
| | | // 3. 校验订单类型:异地寄存 |
| | | if (!Constants.ONE.equals(order.getType())) { |
| | | throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "仅异地寄存订单可操作"); |
| | | } |
| | | // 4. 校验无取件门店 |
| | | if (order.getTakeShopId() != null) { |
| | | throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "该订单有取件门店,需门店确认出库"); |
| | | } |
| | | // 5. 校验状态:已送达(5) |
| | | if (!Constants.equalsInteger(order.getStatus(), Constants.OrderStatus.arrived.getStatus())) { |
| | | throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "当前订单状态不允许确认收货"); |
| | | } |
| | | |
| | | // 6. 更新订单状态为已完成 |
| | | Date now = new Date(); |
| | | order.setStatus(Constants.OrderStatus.finished.getStatus()); |
| | | order.setFinishTime(now); |
| | | order.setUpdateTime(now); |
| | | ordersMapper.updateById(order); |
| | | |
| | | // 7. 生成收益记录 |
| | | calculateAndSaveOrderFees(orderId); |
| | | generateRevenueRecords(orderId); |
| | | |
| | | // 通知会员:订单已完成 |
| | | sendOrderNotice(memberId, Constants.MemberOrderNotify.FINISHED, orderId, |
| | | "orderNo", order.getCode()); |
| | | // 通知存件门店和取件门店:订单已完成 |
| | | String settleDays = operationConfigBiz.getConfig().getSettlementDate(); |
| | | notifyBothShops(order, Constants.ShopOrderNotify.FINISHED, |
| | | "orderNo", order.getCode(), |
| | | "settleDays", settleDays != null ? settleDays : "7"); |
| | | } |
| | | |
| | | @Override |
| | | public void calculateAndSaveOrderFees(Integer orderId) { |
| | | Orders order = ordersMapper.selectById(orderId); |
| | | if (order == null || Constants.equalsInteger(order.getDeleted(), Constants.ONE)) { |
| | | throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "订单不存在"); |
| | | } |
| | | |
| | | Long totalAmount = order.getTotalAmount() != null ? order.getTotalAmount() : 0L; |
| | | // 费率(为空时默认0) |
| | | BigDecimal depositRate = order.getDepositShopFeeRata() != null ? order.getDepositShopFeeRata() : BigDecimal.ZERO; |
| | | BigDecimal takeRate = order.getTakeShopFeeRata() != null ? order.getTakeShopFeeRata() : BigDecimal.ZERO; |
| | | BigDecimal driverRate = order.getDriverFeeRata() != null ? order.getDriverFeeRata() : BigDecimal.ZERO; |
| | | Long exceptionFeeVal = order.getExceptionFee() != null ? order.getExceptionFee() : 0L; |
| | | |
| | | //存件门店收益 |
| | | Long depositShopFee = new BigDecimal(totalAmount) |
| | | .multiply(depositRate) |
| | | .setScale(0, RoundingMode.HALF_UP) |
| | | .longValue(); |
| | | |
| | | Long takeShopFee = 0L; |
| | | Long driverFee = 0L; |
| | | |
| | | if (Constants.equalsInteger(order.getType(), Constants.TWO)) { |
| | | // 异地寄存:存件门店 + 司机 |
| | | driverFee = new BigDecimal(totalAmount) |
| | | .multiply(driverRate) |
| | | .setScale(0, RoundingMode.HALF_UP) |
| | | .longValue() |
| | | + exceptionFeeVal; |
| | | |
| | | // 异地寄存且有取件门店:加上取件门店收益 |
| | | if (order.getTakeShopId() != null) { |
| | | takeShopFee = new BigDecimal(totalAmount) |
| | | .multiply(takeRate) |
| | | .setScale(0, RoundingMode.HALF_UP) |
| | | .longValue(); |
| | | } |
| | | } |
| | | |
| | | ordersMapper.update(new UpdateWrapper<Orders>().lambda() |
| | | .eq(Orders::getId, orderId) |
| | | .set(Orders::getDepositShopFee, depositShopFee) |
| | | .set(Orders::getTakeShopFee, takeShopFee) |
| | | .set(Orders::getDriverFee, driverFee) |
| | | .set(Orders::getUpdateTime, new Date())); |
| | | } |
| | | |
| | | /** |
| | | * 生成门店/司机收益记录(未结算) |
| | | * 订单完成时调用,读取订单上已计算好的费用字段 |
| | | */ |
| | | private void generateRevenueRecords(Integer orderId) { |
| | | Orders order = ordersMapper.selectById(orderId); |
| | | if (order == null) { |
| | | return; |
| | | } |
| | | Date now = new Date(); |
| | | Long depositShopFee = order.getDepositShopFee() != null ? order.getDepositShopFee() : 0L; |
| | | Long takeShopFee = order.getTakeShopFee() != null ? order.getTakeShopFee() : 0L; |
| | | Long driverFee = order.getDriverFee() != null ? order.getDriverFee() : 0L; |
| | | |
| | | // 存件门店收益 |
| | | if (depositShopFee > 0 && order.getDepositShopId() != null) { |
| | | ShopInfo depositShop = shopInfoMapper.selectById(order.getDepositShopId()); |
| | | if (depositShop != null && depositShop.getRegionMemberId() != null) { |
| | | revenueMapper.insert(buildRevenue(depositShop.getRegionMemberId(), Constants.TWO, |
| | | depositShopFee, orderId, order.getCode())); |
| | | } |
| | | } |
| | | |
| | | // 取件门店收益(异地寄存且有取件门店) |
| | | if (takeShopFee > 0 && order.getTakeShopId() != null) { |
| | | ShopInfo takeShop = shopInfoMapper.selectById(order.getTakeShopId()); |
| | | if (takeShop != null && takeShop.getRegionMemberId() != null) { |
| | | revenueMapper.insert(buildRevenue(takeShop.getRegionMemberId(), Constants.TWO, |
| | | takeShopFee, orderId, order.getCode())); |
| | | } |
| | | } |
| | | |
| | | // 司机收益(异地寄存) |
| | | if (driverFee > 0 && order.getAcceptDriver() != null) { |
| | | DriverInfo driver = driverInfoMapper.selectById(order.getAcceptDriver()); |
| | | if (driver != null && driver.getMemberId() != null) { |
| | | revenueMapper.insert(buildRevenue(driver.getMemberId(), Constants.ONE, |
| | | driverFee, orderId, order.getCode())); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 构建收益记录 |
| | | */ |
| | | private Revenue buildRevenue(Integer memberId, Integer memberType, Long amount, Integer orderId, String orderNo) { |
| | | Revenue revenue = new Revenue(); |
| | | revenue.setMemberId(memberId); |
| | | revenue.setMemberType(memberType); // 1=司机, 2=门店 |
| | | revenue.setType(Constants.ZERO); // 0=完成订单 |
| | | revenue.setOptType(Constants.ONE); // 1=收入 |
| | | revenue.setAmount(amount); |
| | | revenue.setVaildStatus(Constants.ZERO); // 0=入账中(未结算) |
| | | revenue.setObjId(orderId); |
| | | revenue.setObjType(Constants.ZERO); // 0=订单业务 |
| | | revenue.setStatus(Constants.ZERO); // 0=成功 |
| | | revenue.setOrderNo(orderNo); |
| | | revenue.setDeleted(Constants.ZERO); |
| | | revenue.setCreateTime(new Date()); |
| | | return revenue; |
| | | } |
| | | |
| | | @Override |
| | |
| | | |
| | | // 保存附件(obj_type=3 门店入库图片,最多3张) |
| | | saveVerifyImages(order.getId(), images, Constants.FileType.ORDER_TAKE.getKey(), driverId); |
| | | |
| | | // 通知会员:订单已送达 |
| | | String destination = order.getTakeShopAddress() != null ? order.getTakeShopAddress() : ""; |
| | | if (order.getMemberVerifyCode() != null) { |
| | | sendOrderNotice(order.getMemberId(), Constants.MemberOrderNotify.ARRIVED_HAS_SHOP, order.getId(), |
| | | "orderNo", order.getCode(), |
| | | "destination", destination, |
| | | "pickupCode", order.getMemberVerifyCode()); |
| | | } else { |
| | | sendOrderNotice(order.getMemberId(), Constants.MemberOrderNotify.ARRIVED_NO_SHOP, order.getId(), |
| | | "orderNo", order.getCode(), |
| | | "destination", destination); |
| | | } |
| | | |
| | | // 通知取件门店:订单已送达 |
| | | if (order.getTakeShopId() != null) { |
| | | sendShopNotice(order.getTakeShopId(), Constants.ShopOrderNotify.ARRIVED, order.getId(), |
| | | "orderNo", order.getCode(), |
| | | "destination", destination); |
| | | } |
| | | } |
| | | |
| | | /** |
| | |
| | | + "天,逾期费用" + Constants.getFormatMoney(overdueInfo.getOverdueFee()) + "元", |
| | | null, shopId); |
| | | } else { |
| | | // 未逾期:完成订单 |
| | | order.setStatus(Constants.OrderStatus.finished.getStatus()); |
| | | // 未逾期:标记逾期状态为0,订单保持当前状态 |
| | | order.setConfirmArriveTime(now); |
| | | order.setFinishTime(now); |
| | | order.setOverdueStatus(Constants.ZERO); |
| | | |
| | | // 就地寄存:计算是否需要退款 |
| | | if (Constants.equalsInteger(order.getType(), Constants.ZERO) && !CollectionUtils.isEmpty(details)) { |
| | | int actualDays = calcActualDepositDays(now, order.getDepositTime()); |
| | | order.setDepositDays(actualDays); |
| | | |
| | | int estimatedDays = order.getEstimatedDepositDays() != null ? order.getEstimatedDepositDays() : 1; |
| | | int refundDays = estimatedDays - actualDays; |
| | | if (refundDays > 0) { |
| | | // 退款金额 = 退款天数 × Σ(物品单价 × 数量) |
| | | long dailyBaseFee = 0L; |
| | | for (OrdersDetail d : details) { |
| | | dailyBaseFee += (d.getUnitPrice() != null ? d.getUnitPrice() : 0L) |
| | | * (d.getNum() != null ? d.getNum() : 0); |
| | | } |
| | | long refundAmount = (long) refundDays * dailyBaseFee; |
| | | order.setRefundAmount(refundAmount); |
| | | } |
| | | } |
| | | |
| | | order.setUpdateTime(now); |
| | | ordersMapper.updateById(order); |
| | | |
| | | // 释放核销码 |
| | | if (StringUtils.isNotBlank(order.getMemberVerifyCode())) { |
| | | releaseVerifyCode(order.getMemberVerifyCode()); |
| | | // 退款导致总金额变化,重算三方收益 |
| | | if (order.getRefundAmount() != null && order.getRefundAmount() > 0) { |
| | | long newTotal = (order.getTotalAmount() != null ? order.getTotalAmount() : 0L) - order.getRefundAmount(); |
| | | order.setTotalAmount(newTotal); |
| | | ordersMapper.update(new UpdateWrapper<Orders>().lambda() |
| | | .eq(Orders::getId, orderId) |
| | | .set(Orders::getTotalAmount, newTotal)); |
| | | calculateAndSaveOrderFees(orderId); |
| | | } |
| | | |
| | | // 记录订单日志 |
| | | saveShopVerifyLog(order, "确认顾客到店", |
| | | "门店【" + shopName + "】确认顾客到店,订单完成", |
| | | null, shopId); |
| | | String logInfo = "门店【" + shopName + "】确认顾客到店,未逾期"; |
| | | if (order.getRefundAmount() != null && order.getRefundAmount() > 0) { |
| | | logInfo += ",需退款" + Constants.getFormatMoney(order.getRefundAmount()) + "元"; |
| | | } |
| | | saveShopVerifyLog(order, "确认顾客到店", logInfo, null, shopId); |
| | | } |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | /** |
| | | * 计算实际寄存天数(depositTime 到 now 的天数差,最少1天) |
| | | */ |
| | | private int calcActualDepositDays(Date now, Date depositTime) { |
| | | if (depositTime == null || now == null) { |
| | | return 1; |
| | | } |
| | | Calendar depositCal = Calendar.getInstance(); |
| | | depositCal.setTime(depositTime); |
| | | depositCal.set(Calendar.HOUR_OF_DAY, 0); |
| | | depositCal.set(Calendar.MINUTE, 0); |
| | | depositCal.set(Calendar.SECOND, 0); |
| | | depositCal.set(Calendar.MILLISECOND, 0); |
| | | |
| | | Calendar nowCal = Calendar.getInstance(); |
| | | nowCal.setTime(now); |
| | | nowCal.set(Calendar.HOUR_OF_DAY, 0); |
| | | nowCal.set(Calendar.MINUTE, 0); |
| | | nowCal.set(Calendar.SECOND, 0); |
| | | nowCal.set(Calendar.MILLISECOND, 0); |
| | | |
| | | long diffMs = nowCal.getTimeInMillis() - depositCal.getTimeInMillis(); |
| | | int days = (int) (diffMs / (1000 * 60 * 60 * 24)); |
| | | return Math.max(days, 1); |
| | | } |
| | | |
| | | /** |
| | | * 就地寄存逾期天数计算 |
| | | * 过了预计取件时间当天的12点后才算一天 |
| | | */ |
| | |
| | | return Math.max(days, 0); |
| | | } |
| | | |
| | | @Override |
| | | public EstimatedDeliveryResultVO calculateEstimatedDelivery(Integer cityId, |
| | | Double fromLat, Double fromLng, |
| | | Double toLat, Double toLng) { |
| | | // 腾讯地图距离矩阵API计算实际距离 |
| | | String from = fromLat + "," + fromLng; |
| | | String to = toLat + "," + toLng; |
| | | JSONObject distanceResult = MapUtil.direction("driving", from, to); |
| | | |
| | | // 获取距离(米),转公里 |
| | | int distanceMeters = distanceResult.getIntValue("distance"); |
| | | BigDecimal distanceKm = new BigDecimal(distanceMeters) |
| | | .divide(new BigDecimal("1000"), 2, RoundingMode.HALF_UP); |
| | | |
| | | // 根据pricing_rule type=2 计算 标速达(1) 和 极速达(2) 时效 |
| | | BigDecimal standardTime = pricingRuleService.calculateEstimatedTime(cityId, 1, distanceKm); |
| | | BigDecimal expressTime = pricingRuleService.calculateEstimatedTime(cityId, 2, distanceKm); |
| | | |
| | | EstimatedDeliveryResultVO vo = new EstimatedDeliveryResultVO(); |
| | | vo.setDistanceKm(distanceKm); |
| | | vo.setStandardHours(standardTime); |
| | | vo.setExpressHours(expressTime); |
| | | return vo; |
| | | } |
| | | |
| | | } |