高峰出行日收费规则

This commit is contained in:
attiya 2025-04-27 16:59:58 +08:00
parent d1e8cf67cc
commit b3dd06f1c7
5 changed files with 230 additions and 34 deletions

View File

@ -9,6 +9,6 @@ import com.cdzy.orders.model.entity.EbikeOrderDetails;
* @author attiya
* @since 2025-04-25
*/
public interface EbikeOrderPaymentItemsMapper extends BaseMapper<EbikeOrderDetails> {
public interface EbikeOrderDetailsMapper extends BaseMapper<EbikeOrderDetails> {
}

View File

@ -0,0 +1,25 @@
package com.cdzy.orders.model.dto.res;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* @author attiya
* @since 2025-04-27
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class TimeSegment implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
LocalDateTime start;
LocalDateTime end;
}

View File

@ -2,7 +2,7 @@ package com.cdzy.orders.service.impl;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import com.cdzy.orders.model.entity.EbikeOrderDetails;
import com.cdzy.orders.mapper.EbikeOrderPaymentItemsMapper;
import com.cdzy.orders.mapper.EbikeOrderDetailsMapper;
import com.cdzy.orders.service.EbikeOrderDetailsService;
import org.springframework.stereotype.Service;
@ -13,6 +13,6 @@ import org.springframework.stereotype.Service;
* @since 2025-04-25
*/
@Service
public class EbikeOrderDetailsServiceImpl extends ServiceImpl<EbikeOrderPaymentItemsMapper, EbikeOrderDetails> implements EbikeOrderDetailsService {
public class EbikeOrderDetailsServiceImpl extends ServiceImpl<EbikeOrderDetailsMapper, EbikeOrderDetails> implements EbikeOrderDetailsService {
}

View File

@ -6,11 +6,13 @@ import com.cdzy.common.model.JsonResult;
import com.cdzy.common.model.ResGPSDto;
import com.cdzy.orders.component.EbikeCoreHandler;
import com.cdzy.orders.enums.*;
import com.cdzy.orders.mapper.EbikeOrderDetailsMapper;
import com.cdzy.orders.mapper.UserOrdersMapper;
import com.cdzy.orders.model.dto.req.ReqBikeDto;
import com.cdzy.orders.model.dto.req.ReqOrderDto;
import com.cdzy.orders.model.dto.res.RedisPoint;
import com.cdzy.orders.model.dto.res.RspBikeDto;
import com.cdzy.orders.model.dto.res.TimeSegment;
import com.cdzy.orders.model.entity.EbikeOrderDetails;
import com.cdzy.orders.model.entity.EbikeUserOrders;
import com.cdzy.orders.service.UserOrdersService;
@ -20,6 +22,8 @@ import com.cdzy.orders.uitls.TimeUtils;
import com.ebike.feign.clients.MaintenanceFeignClient;
import com.ebike.feign.clients.OperateFeignClient;
import com.ebike.feign.model.res.ResFeignEbikeSysRcostsetDto;
import com.ebike.feign.model.res.ResFeignEbikeSysRcostsetTimePeriodDto;
import com.ebike.feign.model.res.ResFeignEbikeSysRcostsetWeekDto;
import com.ebike.feign.model.res.ResFeignOrderPaymentDto;
import com.ebike.feign.model.rsp.FeignEbikeBikeInfoDto;
import com.ebike.feign.model.rsp.FeignEbikeEcuInfo;
@ -64,6 +68,9 @@ public class UserOrdersServiceImpl extends ServiceImpl<UserOrdersMapper, EbikeUs
@Resource
EbikeCoreHandler ebikeCoreHandler;
@Resource
EbikeOrderDetailsMapper orderDetailsMapper;
@Override
@Transactional
public Long saveRide(ReqOrderDto orderDto) {
@ -370,13 +377,14 @@ public class UserOrdersServiceImpl extends ServiceImpl<UserOrdersMapper, EbikeUs
//时长费用计算
Character timeDivisionCharging = feignEbikeSysRcostsetDto.getTimeDivisionCharging();
BigDecimal decimal = switch (timeDivisionCharging) {
case TIME_SLOT -> timeSlotCostCalculation(list,minutes, userOrders, feignEbikeSysRcostsetDto,userOrders.getOrderId());
case WEEK -> weekCostCalculation(list,minutes, userOrders, feignEbikeSysRcostsetDto,userOrders.getOrderId());
default -> defaultCostCalculation(list,minutes, feignEbikeSysRcostsetDto,userOrders.getOrderId());
case TIME_SLOT -> timeSlotCostCalculation(list, minutes, userOrders, feignEbikeSysRcostsetDto);
case WEEK -> weekCostCalculation(list, userOrders, feignEbikeSysRcostsetDto);
default -> defaultCostCalculation(list, userOrders, feignEbikeSysRcostsetDto, userOrders.getOrderId());
};
totalAmount = totalAmount.add(decimal);
userOrders.setStatus(OrderStatus.PENDING_PAYMENT);
userOrders.setTotalAmount(totalAmount);
orderDetailsMapper.insertBatch(list);
}
/**
@ -387,31 +395,106 @@ public class UserOrdersServiceImpl extends ServiceImpl<UserOrdersMapper, EbikeUs
* @param feignEbikeSysRcostsetDto 计费规则
* @return 计费后总金额
*/
BigDecimal timeSlotCostCalculation(List<EbikeOrderDetails> list, long minutes, EbikeUserOrders userOrders, ResFeignEbikeSysRcostsetDto feignEbikeSysRcostsetDto,long orderId) {
BigDecimal timeSlotCostCalculation(List<EbikeOrderDetails> list, long minutes, EbikeUserOrders userOrders, ResFeignEbikeSysRcostsetDto feignEbikeSysRcostsetDto) {
List<ResFeignEbikeSysRcostsetTimePeriodDto> ebikeSysRcostsetTimePeriodDtos = feignEbikeSysRcostsetDto.getEbikeSysRcostsetTimePeriodDtos();
//TODO:时间是否跨天划分不同自然时间段每一段才判断在高峰时间段内有多长时间并判断是否在高峰时间内起步
return new BigDecimal(0);
}
/**
* 按照时间段计费
*
* @param minutes 骑行总分钟
* @param userOrders 订单信息
* @param feignEbikeSysRcostsetDto 计费规则
* @return 计费后总金额
*/
BigDecimal weekCostCalculation(List<EbikeOrderDetails> list, long minutes, EbikeUserOrders userOrders, ResFeignEbikeSysRcostsetDto feignEbikeSysRcostsetDto,long orderId) {
return new BigDecimal(0);
BigDecimal weekCostCalculation(List<EbikeOrderDetails> list,EbikeUserOrders userOrders, ResFeignEbikeSysRcostsetDto feignEbikeSysRcostsetDto) {
BigDecimal decimal = new BigDecimal(0);
LocalDateTime startTime = userOrders.getStartTime();
LocalDateTime endTime = userOrders.getEndTime();
boolean crossDay = TimeUtils.isCrossDay(startTime, endTime);
Long orderId = userOrders.getOrderId();
if (!crossDay) {
//未跨天
TimeSegment timeSegment = new TimeSegment(startTime, endTime);
boolean checked = checkWeek(timeSegment, feignEbikeSysRcostsetDto);
BigDecimal dailiedFee = dailyFee(timeSegment, feignEbikeSysRcostsetDto, true, checked, list, orderId);
decimal = decimal.add(dailiedFee);
} else {
//跨天
List<TimeSegment> timeSegments = TimeUtils.splitByDays(startTime, endTime);
for (int i = 0; i < timeSegments.size(); i++) {
TimeSegment timeSegment = timeSegments.get(i);
boolean checked = checkWeek(timeSegment, feignEbikeSysRcostsetDto);
BigDecimal dailiedFee = dailyFee(timeSegment, feignEbikeSysRcostsetDto, i < 1, checked, list, orderId);
decimal = decimal.add(dailiedFee);
}
}
return decimal;
}
/**
* 按照时间段计费(默认
* 根据自然时间段计算高峰出行日费用
*
* @param minutes 骑行总分钟
* @param feignEbikeSysRcostsetDto 计费规则
* @return 计费后总金额
* @param timeSegment 时间段
* @param feignEbikeSysRcostsetDto 规则
* @return 金额
*/
BigDecimal defaultCostCalculation(List<EbikeOrderDetails> list, long minutes, ResFeignEbikeSysRcostsetDto feignEbikeSysRcostsetDto,long orderId) {
BigDecimal dailyFee(TimeSegment timeSegment, ResFeignEbikeSysRcostsetDto feignEbikeSysRcostsetDto, boolean isStart, boolean isWeek, List<EbikeOrderDetails> list, long orderId) {
BigDecimal decimal = new BigDecimal(0);
List<ResFeignEbikeSysRcostsetWeekDto> weekDtos = feignEbikeSysRcostsetDto.getEbikeSysRcostsetWeekDtos();
LocalDateTime startTime = timeSegment.getStart();
String weekNumber = TimeUtils.getDayOfWeekNumber(startTime);
LocalDateTime endTime = timeSegment.getEnd();
long minutes = TimeUtils.betweenMinutes(startTime, endTime);
BigDecimal minutesNew = BigDecimal.valueOf(minutes);
if (isWeek) {
//高峰日出现计算
for (ResFeignEbikeSysRcostsetWeekDto weekCostCalculation : weekDtos) {
//根据符合要求的规则计算费用
if (weekCostCalculation.getWeek().contains(weekNumber)) {
if (isStart) {
EbikeOrderDetails orderDetails = new EbikeOrderDetails();
orderDetails.setOrderId(orderId);
orderDetails.setItemAmount(weekCostCalculation.getStartupCost());
orderDetails.setItemType(OrderDetailsType.TRAVEL_EXPENSES_ON_PEAK_DAYS);
orderDetails.setItemName("高峰日出行费用-起步费用");
orderDetails.setCalculationRule(JSONObject.toJSONString(feignEbikeSysRcostsetDto));
list.add(orderDetails);
Integer startupDuration = weekCostCalculation.getStartupDuration();
if (minutes > startupDuration) {
BigDecimal startupDurationNew = BigDecimal.valueOf(startupDuration);
//超出时长总时长减去起步时长
minutesNew = minutesNew.subtract(startupDurationNew);
}
}
//时长计费
BigDecimal durationCost = weekCostCalculation.getDurationCost();
//时长多久计费一次
Integer duration = weekCostCalculation.getDuration();
//总计费几次向上取整
int ceil = NumberUtils.divideAndCeil(duration, minutesNew.intValue());
BigDecimal ceilCost = BigDecimal.valueOf(ceil);
//最终值
BigDecimal multiply = durationCost.multiply(ceilCost);
EbikeOrderDetails durationCostOrderDetails = new EbikeOrderDetails();
durationCostOrderDetails.setOrderId(orderId);
durationCostOrderDetails.setItemAmount(multiply);
durationCostOrderDetails.setItemType(OrderDetailsType.TRAVEL_EXPENSES_ON_PEAK_DAYS);
durationCostOrderDetails.setItemName("高峰日出行费用-时长计费");
durationCostOrderDetails.setCalculationRule(JSONObject.toJSONString(feignEbikeSysRcostsetDto));
list.add(durationCostOrderDetails);
decimal = decimal.add(multiply);
}
}
} else {
//非高峰出行日计费
if (isStart) {
//起始计费
Integer startupDuration = feignEbikeSysRcostsetDto.getStartupDuration();
BigDecimal startupCost = feignEbikeSysRcostsetDto.getStartupCost();
EbikeOrderDetails orderDetails = new EbikeOrderDetails();
@ -424,16 +507,17 @@ public class UserOrdersServiceImpl extends ServiceImpl<UserOrdersMapper, EbikeUs
decimal = decimal.add(startupCost);
//超出起步时长计费
if (minutes > startupDuration) {
BigDecimal minutesNew = BigDecimal.valueOf(minutes);
BigDecimal startupDurationNew = BigDecimal.valueOf(startupDuration);
//超出时长
BigDecimal subtract = minutesNew.subtract(startupDurationNew);
minutesNew = minutesNew.subtract(startupDurationNew);
}
}
//时长计费
BigDecimal durationCost = feignEbikeSysRcostsetDto.getDurationCost();
//时长多久计费一次
Integer duration = feignEbikeSysRcostsetDto.getDuration();
//总计费几次向上取整
int ceil = NumberUtils.divideAndCeil(duration, subtract.intValue());
int ceil = NumberUtils.divideAndCeil(duration, minutesNew.intValue());
BigDecimal ceilCost = BigDecimal.valueOf(ceil);
//最终值
BigDecimal multiply = durationCost.multiply(ceilCost);
@ -449,4 +533,36 @@ public class UserOrdersServiceImpl extends ServiceImpl<UserOrdersMapper, EbikeUs
}
return decimal;
}
/**
* 检查是否高峰期
*
* @param timeSegment 自然时间段
* @param feignEbikeSysRcostsetDto 计费规则
* @return 是否
*/
boolean checkWeek(TimeSegment timeSegment, ResFeignEbikeSysRcostsetDto feignEbikeSysRcostsetDto) {
List<ResFeignEbikeSysRcostsetWeekDto> weekDtos = feignEbikeSysRcostsetDto.getEbikeSysRcostsetWeekDtos();
LocalDateTime startTime = timeSegment.getStart();
String weekNumber = TimeUtils.getDayOfWeekNumber(startTime);
for (ResFeignEbikeSysRcostsetWeekDto weekCostCalculation : weekDtos) {
if (weekCostCalculation.getWeek().contains(weekNumber)) {
return true;
}
}
return false;
}
/**
* 按照时间段计费(默认
*
* @param userOrders 订单信息
* @param feignEbikeSysRcostsetDto 计费规则
* @return 计费后总金额
*/
BigDecimal defaultCostCalculation(List<EbikeOrderDetails> list, EbikeUserOrders userOrders, ResFeignEbikeSysRcostsetDto feignEbikeSysRcostsetDto, long orderId) {
TimeSegment timeSegment = new TimeSegment(userOrders.getStartTime(), userOrders.getEndTime());
return dailyFee(timeSegment, feignEbikeSysRcostsetDto, true, false, list, orderId);
}
}

View File

@ -1,9 +1,13 @@
package com.cdzy.orders.uitls;
import com.cdzy.orders.model.dto.res.TimeSegment;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
@ -72,8 +76,8 @@ public class TimeUtils {
* 计算两个时间之间的分钟差
*/
public static long betweenMinutes(LocalDateTime start, LocalDateTime end) {
Objects.requireNonNull(start, "start time must not be null");
Objects.requireNonNull(end, "end time must not be null");
Objects.requireNonNull(start, "开始时间不能为空");
Objects.requireNonNull(end, "结束时间不能为空");
return ChronoUnit.MINUTES.between(start, end);
}
@ -132,4 +136,55 @@ public class TimeUtils {
Objects.requireNonNull(time, "time must not be null");
return time.atZone(zoneId != null ? zoneId : ZoneId.systemDefault());
}
/**
* 获取星期几的数字字符串表示ISO-8601 标准周一=1 周日=7
* @param dateTime 目标时间
* @return 1-7 的整数
*/
public static String getDayOfWeekNumber(LocalDateTime dateTime) {
return String.valueOf(dateTime.getDayOfWeek().getValue());
}
/**
* 判断两个时间是否跨天
* @param time1 时间1
* @param time2 时间2
* @return true=跨天 | false=未跨天
*/
public static boolean isCrossDay(LocalDateTime time1, LocalDateTime time2) {
return !time1.toLocalDate().equals(time2.toLocalDate());
}
/**
* 计算两个时间的自然日间隔天数忽略时间部分
* @param start 开始时间
* @param end 结束时间
* @return 间隔自然日天数end >= start 返回正数反之返回负数
*/
public static long daysBetween(LocalDateTime start, LocalDateTime end) {
return ChronoUnit.DAYS.between(
start.toLocalDate(),
end.toLocalDate()
);
}
/**
* 按自然日拆分时间段
* @param start 开始时间
* @param end 结束时间
* @return 时间段列表
*/
public static List<TimeSegment> splitByDays(LocalDateTime start, LocalDateTime end) {
List<TimeSegment> segments = new ArrayList<>();
LocalDateTime currentStart = start;
while (currentStart.isBefore(end)) {
LocalDateTime nextDay = currentStart.plusDays(1).truncatedTo(ChronoUnit.DAYS);
LocalDateTime segmentEnd = nextDay.isBefore(end) ? nextDay : end;
segments.add(new TimeSegment(currentStart, segmentEnd));
currentStart = segmentEnd;
}
return segments;
}
}