| | |
| | | import com.doumee.service.business.OrderLogService; |
| | | import com.doumee.service.business.OrdersService; |
| | | import com.doumee.service.business.AreasService; |
| | | import com.doumee.service.business.PricingRuleService; |
| | | import com.github.binarywang.wxpay.bean.request.BaseWxPayRequest; |
| | | import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest; |
| | | import com.github.binarywang.wxpay.exception.WxPayException; |
| | |
| | | |
| | | @Autowired |
| | | private PricingRuleMapper pricingRuleMapper; |
| | | |
| | | @Autowired |
| | | private PricingRuleService pricingRuleService; |
| | | |
| | | @Autowired |
| | | private RedisTemplate<String, Object> redisTemplate; |
| | | |
| | |
| | | */ |
| | | @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(); |
| | | } |
| | |
| | | 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(); |
| | | } |
| | |
| | | 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()) { |
| | |
| | | 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); |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | @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); |
| | | } |
| | | |
| | | @Override |
| | | public void calculateAndSaveOrderFees(Integer orderId) { |
| | | Orders order = ordersMapper.selectById(orderId); |
| | | if (order == null || Constants.equalsInteger(order.getDeleted(), Constants.ONE)) { |
| | |
| | | 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.distanceSingle("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; |
| | | } |
| | | |
| | | } |