fix: fix performance salary calculation
Some checks failed
Build and Push Backend / docker (push) Failing after 6s

This commit is contained in:
irving
2025-10-31 21:31:56 -04:00
parent e7ccadaea0
commit 8f89955405
9 changed files with 227 additions and 61 deletions

View File

@@ -45,6 +45,7 @@ import com.starry.admin.modules.shop.service.IPlayCouponDetailsService;
import com.starry.admin.modules.weichat.entity.order.*;
import com.starry.admin.modules.weichat.service.WxCustomMpService;
import com.starry.admin.modules.withdraw.service.IEarningsService;
import com.starry.admin.utils.DateRangeUtils;
import com.starry.admin.utils.SecurityUtils;
import com.starry.common.utils.ConvertUtil;
import com.starry.common.utils.IdUtils;
@@ -456,8 +457,17 @@ public class PlayOrderInfoServiceImpl extends ServiceImpl<PlayOrderInfoMapper, P
public List<PlayOrderInfoEntity> clerkSelectOrderInfoList(String clerkId, String startTime, String endTime) {
LambdaQueryWrapper<PlayOrderInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(PlayOrderInfoEntity::getAcceptBy, clerkId);
if (StringUtils.isNotBlank(startTime) && StringUtils.isNotBlank(endTime)) {
lambdaQueryWrapper.between(PlayOrderInfoEntity::getPurchaserTime, startTime, endTime);
String normalizedStart = DateRangeUtils.normalizeStartOptional(startTime);
String normalizedEnd = DateRangeUtils.normalizeEndOptional(endTime);
if (StrUtil.isNotBlank(normalizedStart) && StrUtil.isNotBlank(normalizedEnd)) {
lambdaQueryWrapper.between(PlayOrderInfoEntity::getPurchaserTime, normalizedStart, normalizedEnd);
} else {
if (StrUtil.isNotBlank(normalizedStart)) {
lambdaQueryWrapper.ge(PlayOrderInfoEntity::getPurchaserTime, normalizedStart);
}
if (StrUtil.isNotBlank(normalizedEnd)) {
lambdaQueryWrapper.le(PlayOrderInfoEntity::getPurchaserTime, normalizedEnd);
}
}
return this.baseMapper.selectList(lambdaQueryWrapper);
}

View File

@@ -16,6 +16,7 @@ import com.starry.admin.modules.statistics.module.vo.ClerkPerformanceOverviewRes
import com.starry.admin.modules.statistics.module.vo.PlayClerkPerformanceInfoQueryVo;
import com.starry.admin.modules.statistics.module.vo.PlayClerkPerformanceInfoReturnVo;
import com.starry.admin.modules.statistics.service.IPlayClerkPerformanceService;
import com.starry.admin.utils.DateRangeUtils;
import com.starry.common.result.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
@@ -69,19 +70,27 @@ public class PlayClerkPerformanceController {
public R listByPage(
@ApiParam(value = "查询条件", required = true) @Validated @RequestBody PlayClerkPerformanceInfoQueryVo vo) {
IPage<PlayClerkUserInfoEntity> page = clerkUserInfoService.selectByPage(vo);
IPage<PlayClerkPerformanceInfoReturnVo> voPage = page.convert(u -> {
IPage<PlayClerkPerformanceInfoReturnVo> voPage = page.convert(user -> {
List<PlayClerkLevelInfoEntity> clerkLevelInfoEntity = playClerkLevelInfoService.selectAll();
String startTime = vo.getEndOrderTime() != null ? vo.getEndOrderTime().get(0) : "";
String endTime = vo.getEndOrderTime() != null ? vo.getEndOrderTime().get(1) : "";
List<PlayOrderInfoEntity> orderInfoEntities = playOrderInfoService.clerkSelectOrderInfoList(u.getId(),
String rawStart = vo.getEndOrderTime() != null && vo.getEndOrderTime().size() > 0
? vo.getEndOrderTime().get(0)
: null;
String rawEnd = vo.getEndOrderTime() != null && vo.getEndOrderTime().size() > 1
? vo.getEndOrderTime().get(1)
: null;
String startTime = DateRangeUtils.normalizeStartOptional(rawStart);
String endTime = DateRangeUtils.normalizeEndOptional(rawEnd);
List<PlayOrderInfoEntity> orderInfoEntities = playOrderInfoService.clerkSelectOrderInfoList(user.getId(),
startTime, endTime);
List<PlayPersonnelGroupInfoEntity> groupInfoEntities = playPersonnelGroupInfoService.selectAll();
return playClerkPerformanceService.getClerkPerformanceInfo(u, orderInfoEntities, clerkLevelInfoEntity,
groupInfoEntities);
return playClerkPerformanceService.getClerkPerformanceInfo(user, orderInfoEntities, clerkLevelInfoEntity,
groupInfoEntities, startTime, endTime);
});
voPage.setRecords(voPage.getRecords().stream()
.sorted(Comparator.comparing(PlayClerkPerformanceInfoReturnVo::getOrderNumber).reversed())
.collect(Collectors.toList()));
return R.ok(voPage);
}

View File

@@ -32,7 +32,7 @@ public interface IPlayClerkPerformanceService {
*/
PlayClerkPerformanceInfoReturnVo getClerkPerformanceInfo(PlayClerkUserInfoEntity userInfo,
List<PlayOrderInfoEntity> orderInfoEntities, List<PlayClerkLevelInfoEntity> clerkLevelInfoEntity,
List<PlayPersonnelGroupInfoEntity> groupInfoEntities);
List<PlayPersonnelGroupInfoEntity> groupInfoEntities, String startTime, String endTime);
ClerkPerformanceOverviewResponseVo queryOverview(ClerkPerformanceOverviewQueryVo vo);

View File

@@ -9,6 +9,7 @@ import com.starry.admin.modules.clerk.module.entity.PlayClerkLevelInfoEntity;
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
import com.starry.admin.modules.clerk.service.IPlayClerkLevelInfoService;
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
import com.starry.admin.modules.order.module.constant.OrderConstant;
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
import com.starry.admin.modules.order.service.IPlayOrderInfoService;
import com.starry.admin.modules.personnel.module.entity.PlayPersonnelGroupInfoEntity;
@@ -26,12 +27,13 @@ import com.starry.admin.modules.statistics.module.vo.ClerkPerformanceSnapshotVo;
import com.starry.admin.modules.statistics.module.vo.ClerkPerformanceTrendPointVo;
import com.starry.admin.modules.statistics.module.vo.PlayClerkPerformanceInfoReturnVo;
import com.starry.admin.modules.statistics.service.IPlayClerkPerformanceService;
import com.starry.admin.modules.withdraw.mapper.EarningsLineMapper;
import com.starry.admin.utils.DateRangeUtils;
import com.starry.admin.utils.SecurityUtils;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@@ -51,9 +53,6 @@ import org.springframework.stereotype.Service;
@Service
public class PlayClerkPerformanceServiceImpl implements IPlayClerkPerformanceService {
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@Resource
private IPlayClerkUserInfoService clerkUserInfoService;
@@ -66,10 +65,14 @@ public class PlayClerkPerformanceServiceImpl implements IPlayClerkPerformanceSer
@Resource
private IPlayPersonnelGroupInfoService playPersonnelGroupInfoService;
@Resource
private EarningsLineMapper earningsLineMapper;
@Override
public PlayClerkPerformanceInfoReturnVo getClerkPerformanceInfo(PlayClerkUserInfoEntity userInfo,
List<PlayOrderInfoEntity> orderInfoEntities, List<PlayClerkLevelInfoEntity> clerkLevelInfoEntities,
List<PlayPersonnelGroupInfoEntity> groupInfoEntities) {
List<PlayPersonnelGroupInfoEntity> groupInfoEntities, String startTime, String endTime) {
Set<String> customIds = new HashSet<>();
int orderContinueNumber = 0;
int orderRefundNumber = 0;
@@ -79,28 +82,31 @@ public class PlayClerkPerformanceServiceImpl implements IPlayClerkPerformanceSer
BigDecimal orderTotalAmount = BigDecimal.ZERO;
BigDecimal orderRewardAmount = BigDecimal.ZERO;
BigDecimal orderRefundAmount = BigDecimal.ZERO;
BigDecimal estimatedRevenue = BigDecimal.ZERO;
for (PlayOrderInfoEntity orderInfoEntity : orderInfoEntities) {
customIds.add(orderInfoEntity.getPurchaserBy());
finalAmount = finalAmount.add(defaultZero(orderInfoEntity.getFinalAmount()));
if ("1".equals(orderInfoEntity.getFirstOrder())) {
if (OrderConstant.YesNoFlag.YES.getCode().equals(orderInfoEntity.getFirstOrder())) {
orderFirstAmount = orderFirstAmount.add(defaultZero(orderInfoEntity.getFinalAmount()));
} else {
orderContinueNumber++;
orderTotalAmount = orderTotalAmount.add(defaultZero(orderInfoEntity.getFinalAmount()));
}
if ("2".equals(orderInfoEntity.getPlaceType())) {
if (OrderConstant.PlaceType.REWARD.getCode().equals(orderInfoEntity.getPlaceType())) {
orderRewardAmount = orderRewardAmount.add(defaultZero(orderInfoEntity.getFinalAmount()));
}
if ("1".equals(orderInfoEntity.getRefundType())) {
if (OrderConstant.OrderRefundFlag.REFUNDED.getCode().equals(orderInfoEntity.getRefundType())) {
orderRefundNumber++;
orderRefundAmount = orderRefundAmount.add(defaultZero(orderInfoEntity.getRefundAmount()));
}
if ("1".equals(orderInfoEntity.getOrdersExpiredState())) {
if (OrderConstant.OrdersExpiredState.EXPIRED.getCode().equals(orderInfoEntity.getOrdersExpiredState())) {
ordersExpiredNumber++;
}
estimatedRevenue = estimatedRevenue.add(defaultZero(orderInfoEntity.getEstimatedRevenue()));
}
BigDecimal estimatedRevenue =
calculateEarningsAmount(userInfo.getId(), orderInfoEntities, startTime, endTime);
PlayClerkPerformanceInfoReturnVo returnVo = new PlayClerkPerformanceInfoReturnVo();
returnVo.setClerkId(userInfo.getId());
returnVo.setClerkNickname(userInfo.getNickname());
@@ -149,7 +155,7 @@ public class PlayClerkPerformanceServiceImpl implements IPlayClerkPerformanceSer
for (PlayClerkUserInfoEntity clerk : clerks) {
List<PlayOrderInfoEntity> orders = playOrderInfoService.clerkSelectOrderInfoList(clerk.getId(),
range.startTime, range.endTime);
snapshots.add(buildSnapshot(clerk, orders, levelNameMap, groupNameMap));
snapshots.add(buildSnapshot(clerk, orders, levelNameMap, groupNameMap, range.startTime, range.endTime));
}
int total = snapshots.size();
ClerkPerformanceOverviewSummaryVo summary = aggregateSummary(snapshots);
@@ -184,7 +190,8 @@ public class PlayClerkPerformanceServiceImpl implements IPlayClerkPerformanceSer
(a, b) -> a));
List<PlayOrderInfoEntity> orders = playOrderInfoService.clerkSelectOrderInfoList(clerk.getId(),
range.startTime, range.endTime);
ClerkPerformanceSnapshotVo snapshot = buildSnapshot(clerk, orders, levelNameMap, groupNameMap);
ClerkPerformanceSnapshotVo snapshot =
buildSnapshot(clerk, orders, levelNameMap, groupNameMap, range.startTime, range.endTime);
ClerkPerformanceDetailResponseVo responseVo = new ClerkPerformanceDetailResponseVo();
responseVo.setProfile(buildProfile(clerk, levelNameMap, groupNameMap));
responseVo.setSnapshot(snapshot);
@@ -262,7 +269,7 @@ public class PlayClerkPerformanceServiceImpl implements IPlayClerkPerformanceSer
for (PlayOrderInfoEntity order : orders) {
BigDecimal finalAmount = defaultZero(order.getFinalAmount());
gmv = gmv.add(finalAmount);
if ("1".equals(order.getFirstOrder())) {
if (OrderConstant.YesNoFlag.YES.getCode().equals(order.getFirstOrder())) {
firstAmount = firstAmount.add(finalAmount);
} else {
continuedAmount = continuedAmount.add(finalAmount);
@@ -408,8 +415,27 @@ public class PlayClerkPerformanceServiceImpl implements IPlayClerkPerformanceSer
return summary;
}
private BigDecimal calculateEarningsAmount(String clerkId, List<PlayOrderInfoEntity> orders, String startTime,
String endTime) {
if (StrUtil.isBlank(clerkId) || CollectionUtil.isEmpty(orders)) {
return BigDecimal.ZERO;
}
List<String> orderIds = orders.stream()
.map(PlayOrderInfoEntity::getId)
.filter(StrUtil::isNotBlank)
.collect(Collectors.toList());
if (CollectionUtil.isEmpty(orderIds)) {
return BigDecimal.ZERO;
}
String normalizedStart = DateRangeUtils.normalizeStartOptional(startTime);
String normalizedEnd = DateRangeUtils.normalizeEndOptional(endTime);
BigDecimal sum = earningsLineMapper.sumAmountByClerkAndOrderIds(clerkId, orderIds, normalizedStart,
normalizedEnd);
return defaultZero(sum);
}
private ClerkPerformanceSnapshotVo buildSnapshot(PlayClerkUserInfoEntity clerk, List<PlayOrderInfoEntity> orders,
Map<String, String> levelNameMap, Map<String, String> groupNameMap) {
Map<String, String> levelNameMap, Map<String, String> groupNameMap, String startTime, String endTime) {
ClerkPerformanceSnapshotVo snapshot = new ClerkPerformanceSnapshotVo();
snapshot.setClerkId(clerk.getId());
snapshot.setClerkNickname(clerk.getNickname());
@@ -422,7 +448,6 @@ public class PlayClerkPerformanceServiceImpl implements IPlayClerkPerformanceSer
BigDecimal continuedAmount = BigDecimal.ZERO;
BigDecimal rewardAmount = BigDecimal.ZERO;
BigDecimal refundAmount = BigDecimal.ZERO;
BigDecimal estimatedRevenue = BigDecimal.ZERO;
int firstCount = 0;
int continuedCount = 0;
int refundCount = 0;
@@ -432,28 +457,28 @@ public class PlayClerkPerformanceServiceImpl implements IPlayClerkPerformanceSer
BigDecimal finalAmount = defaultZero(order.getFinalAmount());
gmv = gmv.add(finalAmount);
userOrderMap.merge(order.getPurchaserBy(), 1, Integer::sum);
if ("1".equals(order.getFirstOrder())) {
if (OrderConstant.YesNoFlag.YES.getCode().equals(order.getFirstOrder())) {
firstCount++;
firstAmount = firstAmount.add(finalAmount);
} else {
continuedCount++;
continuedAmount = continuedAmount.add(finalAmount);
}
if ("2".equals(order.getPlaceType())) {
if (OrderConstant.PlaceType.REWARD.getCode().equals(order.getPlaceType())) {
rewardAmount = rewardAmount.add(finalAmount);
}
if ("1".equals(order.getRefundType())) {
if (OrderConstant.OrderRefundFlag.REFUNDED.getCode().equals(order.getRefundType())) {
refundCount++;
refundAmount = refundAmount.add(defaultZero(order.getRefundAmount()));
}
if ("1".equals(order.getOrdersExpiredState())) {
if (OrderConstant.OrdersExpiredState.EXPIRED.getCode().equals(order.getOrdersExpiredState())) {
expiredCount++;
}
estimatedRevenue = estimatedRevenue.add(defaultZero(order.getEstimatedRevenue()));
}
int orderCount = orders.size();
int userCount = userOrderMap.size();
int continuedUserCount = (int) userOrderMap.values().stream().filter(cnt -> cnt > 1).count();
BigDecimal estimatedRevenue = calculateEarningsAmount(clerk.getId(), orders, startTime, endTime);
snapshot.setGmv(gmv);
snapshot.setFirstOrderAmount(firstAmount);
snapshot.setContinuedOrderAmount(continuedAmount);
@@ -504,41 +529,19 @@ public class PlayClerkPerformanceServiceImpl implements IPlayClerkPerformanceSer
String startStr;
String endStr;
if (CollectionUtil.isNotEmpty(endOrderTime) && endOrderTime.size() >= 2) {
startStr = normalizeStart(endOrderTime.get(0));
endStr = normalizeEnd(endOrderTime.get(1));
startStr = DateRangeUtils.normalizeStart(endOrderTime.get(0));
endStr = DateRangeUtils.normalizeEnd(endOrderTime.get(1));
} else {
LocalDate end = LocalDate.now();
LocalDate start = end.minusDays(6);
startStr = start.format(DATE_FORMATTER) + " 00:00:00";
endStr = end.format(DATE_FORMATTER) + " 23:59:59";
startStr = start.format(DateRangeUtils.DATE_FORMATTER) + " 00:00:00";
endStr = end.format(DateRangeUtils.DATE_FORMATTER) + " 23:59:59";
}
LocalDate startDate = LocalDate.parse(startStr.substring(0, 10), DATE_FORMATTER);
LocalDate endDate = LocalDate.parse(endStr.substring(0, 10), DATE_FORMATTER);
LocalDate startDate = LocalDate.parse(startStr.substring(0, 10), DateRangeUtils.DATE_FORMATTER);
LocalDate endDate = LocalDate.parse(endStr.substring(0, 10), DateRangeUtils.DATE_FORMATTER);
return new DateRange(startStr, endStr, startDate, endDate);
}
private String normalizeStart(String raw) {
if (StrUtil.isBlank(raw)) {
return LocalDate.now().minusDays(6).format(DATE_FORMATTER) + " 00:00:00";
}
if (raw.length() > 10) {
LocalDateTime.parse(raw, DATE_TIME_FORMATTER);
return raw;
}
return raw + " 00:00:00";
}
private String normalizeEnd(String raw) {
if (StrUtil.isBlank(raw)) {
return LocalDate.now().format(DATE_FORMATTER) + " 23:59:59";
}
if (raw.length() > 10) {
LocalDateTime.parse(raw, DATE_TIME_FORMATTER);
return raw;
}
return raw + " 23:59:59";
}
private BigDecimal calcPercentage(int numerator, int denominator) {
if (denominator <= 0) {
return BigDecimal.ZERO;

View File

@@ -33,6 +33,7 @@ import com.starry.admin.modules.weichat.entity.order.PlayClerkOrderInfoQueryVo;
import com.starry.admin.modules.weichat.entity.order.PlayClerkOrderListReturnVo;
import com.starry.admin.modules.weichat.service.WxCustomMpService;
import com.starry.admin.modules.weichat.service.WxCustomUserService;
import com.starry.admin.utils.DateRangeUtils;
import com.starry.admin.utils.SecurityUtils;
import com.starry.admin.utils.SmsUtils;
import com.starry.common.redis.RedisCache;
@@ -126,11 +127,13 @@ public class WxClerkController {
PlayClerkUserInfoEntity entity = clerkUserInfoService
.selectById(ThreadLocalRequestDetail.getClerkUserInfo().getId());
List<PlayClerkLevelInfoEntity> clerkLevelInfoEntity = playClerkLevelInfoService.selectAll();
String startTime = DateRangeUtils.normalizeStartOptional(vo.getStartTime());
String endTime = DateRangeUtils.normalizeEndOptional(vo.getEndTime());
List<PlayOrderInfoEntity> orderInfoEntities = playOrderInfoService.clerkSelectOrderInfoList(entity.getId(),
vo.getStartTime(), vo.getEndTime());
startTime, endTime);
List<PlayPersonnelGroupInfoEntity> groupInfoEntities = playPersonnelGroupInfoService.selectAll();
return R.ok(playClerkPerformanceService.getClerkPerformanceInfo(entity, orderInfoEntities, clerkLevelInfoEntity,
groupInfoEntities));
groupInfoEntities, startTime, endTime));
}
/**

View File

@@ -178,10 +178,16 @@ public class WxCustomMpService {
data.add(new WxMpTemplateData("thing11", commodityName));
data.add(new WxMpTemplateData("amount8", money));
templateMessage.setData(data);
PlayClerkUserInfoEntity clerkUserInfo =
StringUtils.isBlank(openId) ? null : clerkUserInfoService.selectByOpenid(openId);
String clerkId = clerkUserInfo == null ? null : clerkUserInfo.getId();
String clerkNickname = clerkUserInfo == null ? null : clerkUserInfo.getNickname();
try {
proxyWxMpService(tenantId).getTemplateMsgService().sendTemplateMsg(templateMessage);
} catch (WxErrorException e) {
log.error("接单成功发送消息异常", e);
log.error(
"接单成功发送消息异常, tenantId={}, tenantName={}, orderId={}, orderNo={}, recipientType=clerk, clerkId={}, openId={}, nickname={}",
tenantId, tenant.getTenantName(), orderId, orderNo, clerkId, openId, clerkNickname, e);
}
}

View File

@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.starry.admin.modules.withdraw.entity.EarningsLineEntity;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@@ -34,4 +35,28 @@ public interface EarningsLineMapper extends BaseMapper<EarningsLineEntity> {
" AND (status = 'available' OR (status = 'frozen' AND unlock_time <= #{now})) " +
"ORDER BY unlock_time ASC")
List<EarningsLineEntity> selectWithdrawableLines(@Param("clerkId") String clerkId, @Param("now") LocalDateTime now);
@Select("<script>" +
"SELECT COALESCE(SUM(el.amount), 0) " +
"FROM play_earnings_line el " +
"JOIN play_order_info oi ON oi.id = el.order_id " +
"WHERE el.deleted = 0 " +
" AND oi.deleted = 0 " +
" AND el.clerk_id = #{clerkId} " +
" AND el.status &lt;&gt; 'reversed' " +
"<if test='orderIds != null and orderIds.size &gt; 0'>" +
" AND el.order_id IN " +
" <foreach item='id' collection='orderIds' open='(' separator=',' close=')'>#{id}</foreach>" +
"</if>" +
"<if test='startTime != null'>" +
" AND oi.purchaser_time &gt;= #{startTime}" +
"</if>" +
"<if test='endTime != null'>" +
" AND oi.purchaser_time &lt;= #{endTime}" +
"</if>" +
"</script>")
BigDecimal sumAmountByClerkAndOrderIds(@Param("clerkId") String clerkId,
@Param("orderIds") Collection<String> orderIds,
@Param("startTime") String startTime,
@Param("endTime") String endTime);
}

View File

@@ -0,0 +1,69 @@
package com.starry.admin.utils;
import cn.hutool.core.util.StrUtil;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
/**
* Utilities for normalizing date/time range inputs.
*/
public final class DateRangeUtils {
public static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private DateRangeUtils() {}
public static String normalizeStart(String raw) {
if (StrUtil.isBlank(raw)) {
return LocalDate.now().minusDays(6).format(DATE_FORMATTER) + " 00:00:00";
}
if (raw.length() > 10) {
LocalDateTime.parse(raw, DATE_TIME_FORMATTER);
return raw;
}
return raw + " 00:00:00";
}
public static String normalizeEnd(String raw) {
if (StrUtil.isBlank(raw)) {
return LocalDate.now().format(DATE_FORMATTER) + " 23:59:59";
}
if (raw.length() > 10) {
LocalDateTime.parse(raw, DATE_TIME_FORMATTER);
return raw;
}
return raw + " 23:59:59";
}
public static String normalizeStartOptional(String raw) {
if (StrUtil.isBlank(raw)) {
return null;
}
return normalizeFlexible(raw, false);
}
public static String normalizeEndOptional(String raw) {
if (StrUtil.isBlank(raw)) {
return null;
}
return normalizeFlexible(raw, true);
}
private static String normalizeFlexible(String raw, boolean isEnd) {
String candidate = raw.trim().replace('T', ' ').replace("Z", "");
if (candidate.length() <= 10) {
candidate = candidate + (isEnd ? " 23:59:59" : " 00:00:00");
} else if (candidate.length() > 19) {
candidate = candidate.substring(0, 19);
}
try {
LocalDateTime.parse(candidate, DATE_TIME_FORMATTER);
return candidate;
} catch (DateTimeParseException ex) {
return candidate;
}
}
}