Use monthly team billing cycle

This commit is contained in:
hy001 2026-05-17 22:40:43 +08:00
parent d9ff507e5f
commit 9deeef5004
10 changed files with 508 additions and 449 deletions

View File

@ -6,13 +6,12 @@ import com.red.circle.other.domain.gateway.AdminTaskGateway;
import com.red.circle.other.app.dto.cmd.AdminTaskQueryCmd; import com.red.circle.other.app.dto.cmd.AdminTaskQueryCmd;
import com.red.circle.other.infra.database.mongo.service.team.TeamBillCycleUtils; import com.red.circle.other.infra.database.mongo.service.team.TeamBillCycleUtils;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.format.DateTimeFormatter; import java.util.ArrayList;
import java.util.ArrayList; import java.util.List;
import java.util.List; import java.util.Map;
import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
@ -27,10 +26,10 @@ public class AdminTaskQueryCmdExe {
private final AdminTaskGateway adminTaskGateway; private final AdminTaskGateway adminTaskGateway;
public List<AdminTaskRecord> execute(AdminTaskQueryCmd cmd) { public List<AdminTaskRecord> execute(AdminTaskQueryCmd cmd) {
LocalDate cycleStartDate = cmd.getCycleStartDate() != null ? cmd.getCycleStartDate() : LocalDate cycleStartDate = cmd.getCycleStartDate() != null ? cmd.getCycleStartDate() :
LocalDate.parse(TeamBillCycleUtils.getCalcBillBelong().toString(), DateTimeFormatter.ofPattern("yyyyMMdd")); TeamBillCycleUtils.getBillBelongStartDate(TeamBillCycleUtils.getCalcBillBelong());
List<AdminTaskRecord> records = adminTaskGateway.findByUserIdAndCycle(cmd.getUserId(), cycleStartDate); List<AdminTaskRecord> records = adminTaskGateway.findByUserIdAndCycle(cmd.getUserId(), cycleStartDate);
Map<String, AdminTaskRecord> taskRecordMap = records.stream().collect(Collectors.toMap(AdminTaskRecord::getTaskType, e -> e)); Map<String, AdminTaskRecord> taskRecordMap = records.stream().collect(Collectors.toMap(AdminTaskRecord::getTaskType, e -> e));
List<AdminTaskRecord> recordList = new ArrayList<>(); List<AdminTaskRecord> recordList = new ArrayList<>();
@ -46,10 +45,10 @@ public class AdminTaskQueryCmdExe {
/** /**
* 查询单个任务 * 查询单个任务
*/ */
public AdminTaskRecord findByUserIdAndTaskTypeAndCycle(Long userId, String taskType, LocalDate cycleStartDate) { public AdminTaskRecord findByUserIdAndTaskTypeAndCycle(Long userId, String taskType, LocalDate cycleStartDate) {
LocalDate queryDate = cycleStartDate != null ? cycleStartDate : LocalDate queryDate = cycleStartDate != null ? cycleStartDate :
LocalDate.parse(TeamBillCycleUtils.getCalcBillBelong().toString(), DateTimeFormatter.ofPattern("yyyyMMdd")); TeamBillCycleUtils.getBillBelongStartDate(TeamBillCycleUtils.getCalcBillBelong());
return adminTaskGateway.findByUserIdAndTaskTypeAndCycle(userId, taskType, queryDate); return adminTaskGateway.findByUserIdAndTaskTypeAndCycle(userId, taskType, queryDate);
} }
} }

View File

@ -92,12 +92,13 @@ public class TeamMemberWorkQryExe {
.setTargets(Lists.newArrayList()); .setTargets(Lists.newArrayList());
} }
Map<Integer, TeamMemberTarget> teamMemberTargetMap = Optional.ofNullable( Map<Integer, TeamMemberTarget> teamMemberTargetMap = Optional.ofNullable(
teamMemberTargetService.listByGeBillBelong( teamMemberTargetService.listByGeBillBelong(
teamMember.getTeamId(), teamMember.getMemberId(), billBelong)) teamMember.getTeamId(), teamMember.getMemberId(), billBelong))
.map(target -> target.stream() .map(target -> target.stream()
.collect(Collectors.toMap(TeamMemberTarget::getBillBelong, v -> v))) .collect(Collectors.toMap(TeamMemberTarget::getBillBelong, v -> v, (v1, v2) -> v2)))
.orElseGet(Maps::newHashMap); .orElseGet(Maps::newHashMap);
Map<Integer, TeamMemberTarget> monthlyTargetCache = Maps.newHashMap();
List<TeamBillCycle> unpaidBillCycles = teamBillCycles.stream() List<TeamBillCycle> unpaidBillCycles = teamBillCycles.stream()
.filter( .filter(
@ -114,18 +115,24 @@ public class TeamMemberWorkQryExe {
TeamMemberWorkCO teamMemberWorkCO = new TeamMemberWorkCO() TeamMemberWorkCO teamMemberWorkCO = new TeamMemberWorkCO()
.setMemberProfile(userProfile) .setMemberProfile(userProfile)
.setTargets(teamBillCycles.stream() .setTargets(teamBillCycles.stream()
.map(bill -> { .map(bill -> {
TeamMemberWorkBillCO co = new TeamMemberWorkBillCO() Integer monthlyBillBelong = TeamBillCycleUtils.extractMonth(bill.getBillBelong());
.setId(bill.getId()) TeamMemberWorkBillCO co = new TeamMemberWorkBillCO()
.setBillBelong(bill.getBillBelong()) .setId(bill.getId())
.setBillTitle(TeamBillCycleUtils.parseBillBelongToDateRangeStr(bill.getBillBelong())) .setBillBelong(monthlyBillBelong)
.setStatus(bill.getStatus().name()); .setBillTitle(TeamBillCycleUtils.parseBillBelongToDateRangeStr(monthlyBillBelong))
.setStatus(bill.getStatus().name());
TeamMemberTarget target = teamMemberTargetMap.get(bill.getBillBelong());
if (Objects.isNull(target)) { TeamMemberTarget target = resolveMonthlyTarget(
return co; teamMember.getTeamId(),
} teamMember.getMemberId(),
bill,
teamMemberTargetMap,
monthlyTargetCache);
if (Objects.isNull(target)) {
return co;
}
co.setTarget(new TeamMemberWorkTargetCO() co.setTarget(new TeamMemberWorkTargetCO()
.setId(target.getTimeId()) .setId(target.getTimeId())
@ -182,13 +189,45 @@ public class TeamMemberWorkQryExe {
return stringList; return stringList;
} }
private Map<String, String> getMetadata(SysRegionConfig regionConfig) { private Map<String, String> getMetadata(SysRegionConfig regionConfig) {
return Optional.ofNullable(regionConfig) return Optional.ofNullable(regionConfig)
.map(SysRegionConfig::getMetadata).orElseGet(CollectionUtils::newHashMap); .map(SysRegionConfig::getMetadata).orElseGet(CollectionUtils::newHashMap);
} }
/** private TeamMemberTarget resolveMonthlyTarget(Long teamId,
* 是否开启每日自动结算工资. Long memberId,
TeamBillCycle bill,
Map<Integer, TeamMemberTarget> targetMap,
Map<Integer, TeamMemberTarget> monthlyTargetCache) {
Integer billBelong = bill.getBillBelong();
Integer monthlyBillBelong = TeamBillCycleUtils.extractMonth(billBelong);
TeamMemberTarget cachedMonthlyTarget = monthlyTargetCache.get(monthlyBillBelong);
if (Objects.nonNull(cachedMonthlyTarget)) {
return cachedMonthlyTarget;
}
TeamMemberTarget aggregatedTarget = teamMemberTargetService.aggregateMonthlyTarget(
teamId, memberId, monthlyBillBelong);
if (Objects.nonNull(aggregatedTarget)) {
monthlyTargetCache.put(monthlyBillBelong, aggregatedTarget);
return aggregatedTarget;
}
TeamMemberTarget monthlyTarget = targetMap.get(monthlyBillBelong);
if (Objects.nonNull(monthlyTarget)) {
return monthlyTarget;
}
TeamMemberTarget exactTarget = targetMap.get(billBelong);
if (Objects.equals(billBelong, monthlyBillBelong)) {
return exactTarget;
}
return exactTarget;
}
/**
* 是否开启每日自动结算工资.
*/ */
private boolean isOpenDailyAutoSalary(Map<String, String> metadata) { private boolean isOpenDailyAutoSalary(Map<String, String> metadata) {
return Objects.equals(metadata.get("openDailyAutoSalary"), "true"); return Objects.equals(metadata.get("openDailyAutoSalary"), "true");

View File

@ -5,15 +5,12 @@ import com.red.circle.other.app.service.AdminSalarySettlementService;
import com.red.circle.other.infra.database.mongo.service.team.TeamBillCycleUtils; import com.red.circle.other.infra.database.mongo.service.team.TeamBillCycleUtils;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.time.LocalDate; /**
import java.time.format.DateTimeFormatter; * 管理员工资结算定时任务.
* 每月1号2点执行整月结算在BD和BD Leader结算之后.
/**
* 管理员工资结算定时任务.
* 每月1号和16号 2点执行半月结算在BD和BD Leader结算之后
* *
* @author AI Assistant * @author AI Assistant
* @since 2025-12-13 * @since 2025-12-13
@ -25,12 +22,12 @@ public class AdminSalarySettlementTask {
private final AdminSalarySettlementService adminSalarySettlementService; private final AdminSalarySettlementService adminSalarySettlementService;
/** /**
* 每月1号和16号 2点执行半月结算. * 每月1号2点执行上月整月结算.
*/ */
@Scheduled(cron = "0 0 2 1,16 * ?", zone = "Asia/Riyadh") @Scheduled(cron = "0 0 2 1 * ?", zone = "Asia/Riyadh")
@TaskCacheLock(key = "ADMIN_SALARY_SETTLEMENT", expireSecond = 86400) @TaskCacheLock(key = "ADMIN_SALARY_SETTLEMENT", expireSecond = 86400)
public void processAdminSalarySettlement() { public void processAdminSalarySettlement() {
long startTime = System.currentTimeMillis(); long startTime = System.currentTimeMillis();
log.warn("========== 管理员工资结算定时任务开始 =========="); log.warn("========== 管理员工资结算定时任务开始 ==========");
@ -54,30 +51,12 @@ public class AdminSalarySettlementTask {
} }
} }
/** /**
* 计算需要结算的账期. * 计算需要结算的上月整月账期.
* 规则 */
* - 如果当前账期是本月1号如20250101则结算上个月16号的账期如20241216 private Integer calculateSettlementBillBelong(Integer currentBillBelong) {
* - 如果当前账期是本月16号如20250116则结算本月1号的账期如20250101 return TeamBillCycleUtils.getPreviousMonthlyBillBelong(currentBillBelong);
*/ }
private Integer calculateSettlementBillBelong(Integer currentBillBelong) {
String billStr = String.valueOf(currentBillBelong);
int year = Integer.parseInt(billStr.substring(0, 4));
int month = Integer.parseInt(billStr.substring(4, 6));
int day = Integer.parseInt(billStr.substring(6, 8));
LocalDate settlementDate;
if (day == 1) {
// 当前是本月1号结算上个月16号
settlementDate = LocalDate.of(year, month, 1).minusMonths(1).withDayOfMonth(16);
} else {
// 当前是本月16号结算本月1号
settlementDate = LocalDate.of(year, month, 1);
}
return Integer.parseInt(settlementDate.format(DateTimeFormatter.ofPattern("yyyyMMdd")));
}
/** /**
* 测试方法手动触发. * 测试方法手动触发.

View File

@ -5,16 +5,12 @@ import com.red.circle.other.app.service.BdLeaderSalarySettlementService;
import com.red.circle.other.infra.database.mongo.service.team.TeamBillCycleUtils; import com.red.circle.other.infra.database.mongo.service.team.TeamBillCycleUtils;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.time.LocalDate; /**
import java.time.ZoneId; * BD Leader 工资结算定时任务.
import java.time.format.DateTimeFormatter; * 每月1号0点执行整月结算.
/**
* BD Leader 工资结算定时任务.
* 每月1号和16号 0点执行半月结算
* *
* @author AI Assistant * @author AI Assistant
* @since 2025-10-29 * @since 2025-10-29
@ -26,12 +22,12 @@ public class BdLeaderSalarySettlementTask {
private final BdLeaderSalarySettlementService bdLeaderSalarySettlementService; private final BdLeaderSalarySettlementService bdLeaderSalarySettlementService;
/** /**
* 每月1号和16号 1点半执行半月结算. * 每月1号1点执行上月整月结算.
*/ */
@Scheduled(cron = "0 0 1 1,16 * ?", zone = "Asia/Riyadh") @Scheduled(cron = "0 0 1 1 * ?", zone = "Asia/Riyadh")
@TaskCacheLock(key = "BD_LEADER_SALARY_SETTLEMENT", expireSecond = 86400) @TaskCacheLock(key = "BD_LEADER_SALARY_SETTLEMENT", expireSecond = 86400)
public void processBdLeaderSalarySettlement() { public void processBdLeaderSalarySettlement() {
long startTime = System.currentTimeMillis(); long startTime = System.currentTimeMillis();
log.warn("========== BD Leader工资结算定时任务开始 =========="); log.warn("========== BD Leader工资结算定时任务开始 ==========");
@ -55,30 +51,12 @@ public class BdLeaderSalarySettlementTask {
} }
} }
/** /**
* 计算需要结算的账期 * 计算需要结算的上月整月账期.
* 规则 */
* - 如果当前账期是本月1号如20250101则结算上个月16号的账期如20241216 private Integer calculateSettlementBillBelong(Integer currentBillBelong) {
* - 如果当前账期是本月16号如20250116则结算本月1号的账期如20250101 return TeamBillCycleUtils.getPreviousMonthlyBillBelong(currentBillBelong);
*/ }
private Integer calculateSettlementBillBelong(Integer currentBillBelong) {
String billStr = String.valueOf(currentBillBelong);
int year = Integer.parseInt(billStr.substring(0, 4));
int month = Integer.parseInt(billStr.substring(4, 6));
int day = Integer.parseInt(billStr.substring(6, 8));
LocalDate settlementDate;
if (day == 1) {
// 当前是本月1号结算上个月16号
settlementDate = LocalDate.of(year, month, 1).minusMonths(1).withDayOfMonth(16);
} else {
// 当前是本月16号结算本月1号
settlementDate = LocalDate.of(year, month, 1);
}
return Integer.parseInt(settlementDate.format(DateTimeFormatter.ofPattern("yyyyMMdd")));
}
/** /**
* 测试方法手动触发. * 测试方法手动触发.

View File

@ -5,16 +5,12 @@ import com.red.circle.other.app.service.BdSalarySettlementService;
import com.red.circle.other.infra.database.mongo.service.team.TeamBillCycleUtils; import com.red.circle.other.infra.database.mongo.service.team.TeamBillCycleUtils;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.time.LocalDate; /**
import java.time.ZoneId; * BD 工资结算定时任务.
import java.time.format.DateTimeFormatter; * 每月1号0点执行整月结算.
/**
* BD 工资结算定时任务.
* 每月1号和16号 0点执行半月结算
* *
* @author AI Assistant * @author AI Assistant
* @since 2025-10-29 * @since 2025-10-29
@ -26,12 +22,12 @@ public class BdSalarySettlementTask {
private final BdSalarySettlementService bdSalarySettlementService; private final BdSalarySettlementService bdSalarySettlementService;
/** /**
* 每月1号和16号 1点执行半月结算. * 每月1号0点30分执行上月整月结算.
*/ */
@Scheduled(cron = "0 30 0 1,16 * ?", zone = "Asia/Riyadh") @Scheduled(cron = "0 30 0 1 * ?", zone = "Asia/Riyadh")
@TaskCacheLock(key = "BD_SALARY_SETTLEMENT", expireSecond = 86400) @TaskCacheLock(key = "BD_SALARY_SETTLEMENT", expireSecond = 86400)
public void processBdSalarySettlement() { public void processBdSalarySettlement() {
long startTime = System.currentTimeMillis(); long startTime = System.currentTimeMillis();
log.warn("========== BD工资结算定时任务开始 =========="); log.warn("========== BD工资结算定时任务开始 ==========");
@ -55,30 +51,12 @@ public class BdSalarySettlementTask {
} }
} }
/** /**
* 计算需要结算的账期 * 计算需要结算的上月整月账期.
* 规则 */
* - 如果当前账期是本月1号如20250101则结算上个月16号的账期如20241216 private Integer calculateSettlementBillBelong(Integer currentBillBelong) {
* - 如果当前账期是本月16号如20250116则结算本月1号的账期如20250101 return TeamBillCycleUtils.getPreviousMonthlyBillBelong(currentBillBelong);
*/ }
private Integer calculateSettlementBillBelong(Integer currentBillBelong) {
String billStr = String.valueOf(currentBillBelong);
int year = Integer.parseInt(billStr.substring(0, 4));
int month = Integer.parseInt(billStr.substring(4, 6));
int day = Integer.parseInt(billStr.substring(6, 8));
LocalDate settlementDate;
if (day == 1) {
// 当前是本月1号结算上个月16号
settlementDate = LocalDate.of(year, month, 1).minusMonths(1).withDayOfMonth(16);
} else {
// 当前是本月16号结算本月1号
settlementDate = LocalDate.of(year, month, 1);
}
return Integer.parseInt(settlementDate.format(DateTimeFormatter.ofPattern("yyyyMMdd")));
}
/** /**
* 测试方法手动触发. * 测试方法手动触发.

View File

@ -5,21 +5,16 @@ import com.red.circle.mq.business.model.event.team.TeamBillSettleEvent;
import com.red.circle.mq.rocket.business.producer.TeamSalaryMqMessage; import com.red.circle.mq.rocket.business.producer.TeamSalaryMqMessage;
import com.red.circle.other.infra.database.mongo.entity.team.team.TeamProfile; import com.red.circle.other.infra.database.mongo.entity.team.team.TeamProfile;
import com.red.circle.other.infra.database.mongo.service.gift.GiftGiveRunningWaterService; import com.red.circle.other.infra.database.mongo.service.gift.GiftGiveRunningWaterService;
import com.red.circle.other.infra.database.mongo.service.team.TeamBillCycleUtils; import com.red.circle.other.infra.database.mongo.service.team.team.TeamBillCycleService;
import com.red.circle.other.infra.database.mongo.service.team.team.TeamBillCycleService; import com.red.circle.other.infra.database.mongo.service.team.team.TeamProfileService;
import com.red.circle.other.infra.database.mongo.service.team.team.TeamProfileService; import com.red.circle.other.infra.utils.ZonedDateTimeUtils;
import com.red.circle.other.infra.utils.ZonedDateTimeUtils; import com.red.circle.other.inner.model.cmd.team.TeamBillCmd;
import com.red.circle.other.inner.model.cmd.team.TeamBillCmd; import com.red.circle.tool.core.date.TimestampUtils;
import com.red.circle.tool.core.date.TimestampUtils;
import java.time.LocalDateTime;
import java.time.LocalDate; import java.util.ArrayList;
import java.time.LocalDateTime; import java.util.List;
import java.time.YearMonth; import java.util.concurrent.atomic.AtomicInteger;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -39,25 +34,21 @@ public class TeamBillTask {
private final TeamSalaryMqMessage otherMqMessage; private final TeamSalaryMqMessage otherMqMessage;
private final TeamProfileService teamProfileService; private final TeamProfileService teamProfileService;
private final TeamBillCycleService teamBillCycleService; private final TeamBillCycleService teamBillCycleService;
private final GiftGiveRunningWaterService giftGiveRunningWaterService; private final GiftGiveRunningWaterService giftGiveRunningWaterService;
/** /**
* 每月1号和16号 0点执行半月结算 * 每月1号0点执行整月结算并创建新月账期.
*/ */
@Scheduled(cron = "0 0 0 1,16 * ?", zone = "Asia/Riyadh") @Scheduled(cron = "0 0 0 1 * ?", zone = "Asia/Riyadh")
@TaskCacheLock(key = "PROCESS_TEAM_BILL_HALF_MONTH", expireSecond = 86400) @TaskCacheLock(key = "PROCESS_TEAM_BILL_MONTH", expireSecond = 86400)
public void processTeamHalfMonthBill() { public void processTeamMonthBill() {
long startTime = System.currentTimeMillis(); long startTime = System.currentTimeMillis();
log.info("exec process_team_half_month_bill start"); log.info("exec process_team_month_bill start");
// 判断是上半月还是下半月 processTeamBill();
LocalDate now = LocalDate.now(ZoneId.of("Asia/Riyadh")); log.info("exec process_team_month_bill end with {}",
String billPeriod = now.getDayOfMonth() == 1 ? "first_half" : "second_half"; System.currentTimeMillis() - startTime);
}
processTeamBill();
log.info("exec process_team_half_month_bill end with {}",
System.currentTimeMillis() - startTime);
}
public void processTeamMonthBillTest() { public void processTeamMonthBillTest() {
processTeamBill(); processTeamBill();

View File

@ -15,12 +15,11 @@ import com.red.circle.other.infra.database.mongo.service.team.TeamBillCycleUtils
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.format.DateTimeFormatter; import java.util.List;
import java.util.List; import java.util.stream.Collectors;
import java.util.stream.Collectors;
/** /**
* 管理员任务服务实现 * 管理员任务服务实现
@ -55,10 +54,10 @@ public class AdminTaskServiceImpl implements AdminTaskService {
return adminTaskConvertor.toRecordCO(record); return adminTaskConvertor.toRecordCO(record);
} }
@Override @Override
public AdminTaskSummaryCO queryTaskSummary(AdminTaskQueryCmd cmd) { public AdminTaskSummaryCO queryTaskSummary(AdminTaskQueryCmd cmd) {
LocalDate cycleStartDate = cmd.getCycleStartDate() != null ? cmd.getCycleStartDate() : LocalDate cycleStartDate = cmd.getCycleStartDate() != null ? cmd.getCycleStartDate() :
LocalDate.parse(TeamBillCycleUtils.getCalcBillBelong().toString(), DateTimeFormatter.ofPattern("yyyyMMdd")); TeamBillCycleUtils.getBillBelongStartDate(TeamBillCycleUtils.getCalcBillBelong());
List<AdminTaskRecord> records = adminTaskQueryCmdExe.execute(cmd); List<AdminTaskRecord> records = adminTaskQueryCmdExe.execute(cmd);
@ -107,20 +106,20 @@ public class AdminTaskServiceImpl implements AdminTaskService {
return summary; return summary;
} }
@Override @Override
public boolean isTaskCompleted(Long userId, String taskType, LocalDate cycleStartDate) { public boolean isTaskCompleted(Long userId, String taskType, LocalDate cycleStartDate) {
LocalDate queryDate = cycleStartDate != null ? cycleStartDate : LocalDate queryDate = cycleStartDate != null ? cycleStartDate :
LocalDate.parse(TeamBillCycleUtils.getCalcBillBelong().toString(), DateTimeFormatter.ofPattern("yyyyMMdd")); TeamBillCycleUtils.getBillBelongStartDate(TeamBillCycleUtils.getCalcBillBelong());
AdminTaskRecord record = adminTaskQueryCmdExe.findByUserIdAndTaskTypeAndCycle(userId, taskType, queryDate); AdminTaskRecord record = adminTaskQueryCmdExe.findByUserIdAndTaskTypeAndCycle(userId, taskType, queryDate);
return record != null && record.isCompleted(); return record != null && record.isCompleted();
} }
/** /**
* 确保任务已初始化兜底逻辑 * 确保任务已初始化兜底逻辑
*/ */
private void ensureTasksInitialized(Long userId, LocalDate cycleStartDate) { private void ensureTasksInitialized(Long userId, LocalDate cycleStartDate) {
LocalDate queryDate = cycleStartDate != null ? cycleStartDate : LocalDate queryDate = cycleStartDate != null ? cycleStartDate :
LocalDate.parse(TeamBillCycleUtils.getCalcBillBelong().toString(), DateTimeFormatter.ofPattern("yyyyMMdd")); TeamBillCycleUtils.getBillBelongStartDate(TeamBillCycleUtils.getCalcBillBelong());
// 检查是否存在任务记录 // 检查是否存在任务记录
boolean exists = adminTaskGateway.existsByUserIdAndCycle(userId, queryDate); boolean exists = adminTaskGateway.existsByUserIdAndCycle(userId, queryDate);
@ -142,18 +141,10 @@ public class AdminTaskServiceImpl implements AdminTaskService {
} }
} }
/** /**
* 计算周期结束日期 * 计算周期结束日期
*/ */
private LocalDate calculateCycleEndDate(LocalDate cycleStartDate) { private LocalDate calculateCycleEndDate(LocalDate cycleStartDate) {
int dayOfMonth = cycleStartDate.getDayOfMonth(); return cycleStartDate.withDayOfMonth(cycleStartDate.lengthOfMonth());
}
if (dayOfMonth == 1) { }
// 1号开始结束于15号
return LocalDate.of(cycleStartDate.getYear(), cycleStartDate.getMonth(), 15);
} else {
// 16号开始结束于月末
return cycleStartDate.withDayOfMonth(cycleStartDate.lengthOfMonth());
}
}
}

View File

@ -665,31 +665,16 @@ public final class TeamBillCycleUtils {
} }
public static Integer getCalcBillBelong() { public static Integer getCalcBillBelong() {
LocalDate now = ZonedDateTimeAsiaRiyadhUtils.now().toLocalDate(); return getMonthlyBillBelong();
return calculateCurrentBillBelong(now); }
}
/**
/** * 计算当前整月账期的 billBelong返回格式202510.
* 计算当前账期的 billBelong */
* 规则 private static Integer calculateCurrentBillBelong(LocalDate now) {
* - 1-15号当前账期是本月1号20251001 return Integer.parseInt(now.format(DateTimeFormatter.ofPattern("yyyyMM")));
* - 16-月末当前账期是本月16号20251016 }
*/
private static Integer calculateCurrentBillBelong(LocalDate now) {
int day = now.getDayOfMonth();
LocalDate billDate;
if (day < 16) {
// 当前是上半月账期开始日期是本月1号
billDate = now.withDayOfMonth(1);
} else {
// 当前是下半月账期开始日期是本月16号
billDate = now.withDayOfMonth(16);
}
return Integer.parseInt(billDate.format(DateTimeFormatter.ofPattern("yyyyMMdd")));
}
/** /**
* 解析 billBelong 为日期范围. * 解析 billBelong 为日期范围.
@ -758,10 +743,18 @@ public final class TeamBillCycleUtils {
* 获取月度账期用于批次模式 * 获取月度账期用于批次模式
* 返回格式202511 * 返回格式202511
*/ */
public static Integer getMonthlyBillBelong() { public static Integer getMonthlyBillBelong() {
LocalDate now = ZonedDateTimeAsiaRiyadhUtils.now().toLocalDate(); LocalDate now = ZonedDateTimeAsiaRiyadhUtils.now().toLocalDate();
return Integer.parseInt(now.format(DateTimeFormatter.ofPattern("yyyyMM"))); return calculateCurrentBillBelong(now);
} }
/**
* 获取上一个整月账期返回格式202510.
*/
public static Integer getPreviousMonthlyBillBelong(Integer billBelong) {
YearMonth yearMonth = parseYearMonth(billBelong);
return Integer.parseInt(yearMonth.minusMonths(1).format(DateTimeFormatter.ofPattern("yyyyMM")));
}
/** /**
* 获取当前结算批次 * 获取当前结算批次
@ -775,11 +768,9 @@ public final class TeamBillCycleUtils {
/** /**
* 判断是否启用批次模式通过区域配置 * 判断是否启用批次模式通过区域配置
*/ */
public static Boolean isBatchModeEnabled(String region) { public static Boolean isBatchModeEnabled(String region) {
// 从配置中读取或者硬编码 return false;
// 例如return "SA".equals(region); // 沙特地区启用批次模式 }
return true;
}
/** /**
* billBelong 提取月份兼容新旧格式 * billBelong 提取月份兼容新旧格式
@ -833,30 +824,56 @@ public final class TeamBillCycleUtils {
return String.format("%d年%d月%s", year, month, batchDesc); return String.format("%d年%d月%s", year, month, batchDesc);
} }
public static LocalDateTime getBillBelongStartTime(Integer billBelong) { public static LocalDate getBillBelongStartDate(Integer billBelong) {
// billBelong 格式20251101 20251116 String billStr = String.valueOf(billBelong);
int year = billBelong / 10000; // 2025 if (billStr.length() == 6) {
int month = (billBelong / 100) % 100; // 11 return parseYearMonth(billBelong).atDay(1);
int day = billBelong % 100; // 01 16 }
if (billStr.length() == 8) {
return LocalDateTime.of(year, month, day, 0, 0, 0); return LocalDate.of(
} Integer.parseInt(billStr.substring(0, 4)),
Integer.parseInt(billStr.substring(4, 6)),
public static LocalDateTime getBillBelongEndTime(Integer billBelong) { Integer.parseInt(billStr.substring(6, 8)));
// billBelong 格式20251101 20251116 }
int year = billBelong / 10000; // 2025 throw new IllegalArgumentException("Unsupported billBelong: " + billBelong);
int month = (billBelong / 100) % 100; // 11 }
int day = billBelong % 100; // 01 16
public static LocalDate getBillBelongEndDate(Integer billBelong) {
LocalDateTime startTime = LocalDateTime.of(year, month, day, 0, 0, 0); String billStr = String.valueOf(billBelong);
if (billStr.length() == 6) {
if (day == 1) { return parseYearMonth(billBelong).atEndOfMonth();
// 如果是1号结束时间是当月16号0点 }
return LocalDateTime.of(year, month, 16, 0, 0, 0); if (billStr.length() == 8) {
} else { LocalDate startDate = getBillBelongStartDate(billBelong);
// 如果是16号结束时间是下个月1号0点 return startDate.getDayOfMonth() == 1
return startTime.plusMonths(1).withDayOfMonth(1); ? startDate.withDayOfMonth(15)
} : startDate.withDayOfMonth(startDate.lengthOfMonth());
} }
throw new IllegalArgumentException("Unsupported billBelong: " + billBelong);
} }
public static LocalDateTime getBillBelongStartTime(Integer billBelong) {
return getBillBelongStartDate(billBelong).atStartOfDay();
}
public static LocalDateTime getBillBelongEndTime(Integer billBelong) {
return getBillBelongEndDate(billBelong).plusDays(1).atStartOfDay();
}
private static YearMonth parseYearMonth(Integer billBelong) {
if (billBelong == null) {
throw new IllegalArgumentException("billBelong is null");
}
String billStr = String.valueOf(billBelong);
if (billStr.length() == 8) {
billStr = billStr.substring(0, 6);
}
if (billStr.length() != 6) {
throw new IllegalArgumentException("Unsupported billBelong: " + billBelong);
}
return YearMonth.of(
Integer.parseInt(billStr.substring(0, 4)),
Integer.parseInt(billStr.substring(4, 6)));
}
}

View File

@ -21,13 +21,15 @@ import com.red.circle.tool.core.date.ZonedDateTimeAsiaRiyadhUtils;
import com.red.circle.tool.core.sequence.IdWorkerUtils; import com.red.circle.tool.core.sequence.IdWorkerUtils;
import com.red.circle.tool.core.text.StringUtils; import com.red.circle.tool.core.text.StringUtils;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatter; import java.util.ArrayList;
import java.util.List; import java.util.Comparator;
import java.util.Map; import java.util.LinkedHashMap;
import java.util.Objects; import java.util.List;
import java.util.Set; import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@ -244,12 +246,11 @@ public class TeamBillCycleServiceImpl implements TeamBillCycleService {
/** /**
* 创建账单兼容批次模式 * 创建账单兼容批次模式
*/ */
@Override @Override
public void createIfAbsent(TeamBillCmd cmd) { public void createIfAbsent(TeamBillCmd cmd) {
// 计算 billBelong保持原有逻辑 Integer billBelong = TeamBillCycleUtils.getCalcBillBelong();
Integer billBelong = TeamBillCycleUtils.getCalcBillBelong();
// 检查是否已存在
// 检查是否已存在
boolean existing = existsPrimaryBillBelong(cmd.getTeamId(), billBelong); boolean existing = existsPrimaryBillBelong(cmd.getTeamId(), billBelong);
if (existing) { if (existing) {
return; return;
@ -260,33 +261,18 @@ public class TeamBillCycleServiceImpl implements TeamBillCycleService {
.setId(IdWorkerUtils.getId()) .setId(IdWorkerUtils.getId())
.setSysOrigin(cmd.getSysOrigin()) .setSysOrigin(cmd.getSysOrigin())
.setTeamId(cmd.getTeamId()) .setTeamId(cmd.getTeamId())
.setRegion(cmd.getRegion()) .setRegion(cmd.getRegion())
.setBillBelong(billBelong) .setBillBelong(billBelong)
.setBillTitle(generateBillTitle(billBelong)) .setBillTitle(generateBillTitle(billBelong))
.setStatus(TeamBillCycleStatus.UNPAID) .setBatchMode(false)
.setCreateTime(cmd.getOperationTime()) .setMonthlyBillBelong(billBelong)
.setCreateUser(cmd.getOperationBackUser()); .setStatus(TeamBillCycleStatus.UNPAID)
.setCreateTime(cmd.getOperationTime())
Boolean batchMode = TeamBillCycleUtils.isBatchModeEnabled(cmd.getRegion()); .setCreateUser(cmd.getOperationBackUser());
Integer currentBatch = TeamBillCycleUtils.getCurrentSettlementBatch();
Integer monthlyBillBelong = TeamBillCycleUtils.getMonthlyBillBelong(); mongoTemplate.save(bill);
// ===== 新增如果启用批次模式添加批次信息 ===== log.info("创建账单成功:{}", bill);
if (batchMode) { }
bill.setBatchMode(true);
bill.setSettlementBatch(currentBatch);
bill.setMonthlyBillBelong(monthlyBillBelong);
// 覆盖账单标题增加批次说明
String batchTitle = TeamBillCycleUtils.generateBatchTitle(
cmd.getMonthlyBillBelong(),
cmd.getSettlementBatch()
);
bill.setBillTitle(batchTitle);
}
mongoTemplate.save(bill);
log.info("创建账单成功:{}", bill);
}
private String generateBillTitle(Integer billBelong) { private String generateBillTitle(Integer billBelong) {
String[] dateRange = TeamBillCycleUtils.parseBillBelongToDateRange(billBelong); String[] dateRange = TeamBillCycleUtils.parseBillBelongToDateRange(billBelong);
@ -375,12 +361,22 @@ public class TeamBillCycleServiceImpl implements TeamBillCycleService {
TeamBillCycle.class); TeamBillCycle.class);
} }
@Override @Override
public TeamBillCycle getTeamUnpaidBill(Long teamId) { public TeamBillCycle getTeamUnpaidBill(Long teamId) {
return mongoTemplate.findOne(Query.query(Criteria.where("teamId").is(teamId) TeamBillCycle currentMonthBill = mongoTemplate.findOne(
.and("status").is(TeamBillCycleStatus.UNPAID)), Query.query(Criteria.where("teamId").is(teamId)
TeamBillCycle.class); .and("status").is(TeamBillCycleStatus.UNPAID)
} .and("billBelong").is(getCalcBillBelong()))
.with(Sort.by(Sort.Order.desc("id"))),
TeamBillCycle.class);
if (Objects.nonNull(currentMonthBill)) {
return currentMonthBill;
}
return mongoTemplate.findOne(Query.query(Criteria.where("teamId").is(teamId)
.and("status").is(TeamBillCycleStatus.UNPAID))
.with(Sort.by(Sort.Order.desc("id"))),
TeamBillCycle.class);
}
@Override @Override
public Long getLatestBillIdByTeamId(Long teamId) { public Long getLatestBillIdByTeamId(Long teamId) {
@ -394,11 +390,19 @@ public class TeamBillCycleServiceImpl implements TeamBillCycleService {
} }
@Override @Override
public TeamBillCycle getLatestByTeamId(Long teamId) { public TeamBillCycle getLatestByTeamId(Long teamId) {
TeamBillCycle currentMonthBill = mongoTemplate.findOne(
List<TeamBillCycle> cycleList = listLatestBill(teamId, 1); Query.query(Criteria.where("teamId").is(teamId)
if (CollectionUtils.isEmpty(cycleList)) { .and("billBelong").is(getCalcBillBelong()))
.with(Sort.by(Sort.Order.desc("id"))),
TeamBillCycle.class);
if (Objects.nonNull(currentMonthBill)) {
return currentMonthBill;
}
List<TeamBillCycle> cycleList = listLatestBill(teamId, 1);
if (CollectionUtils.isEmpty(cycleList)) {
return null; return null;
} }
@ -435,6 +439,13 @@ public class TeamBillCycleServiceImpl implements TeamBillCycleService {
Integer normalizedYearMonth = yearMonth > 999999 Integer normalizedYearMonth = yearMonth > 999999
? Integer.valueOf(yearMonth.toString().substring(0, 6)) ? Integer.valueOf(yearMonth.toString().substring(0, 6))
: yearMonth; : yearMonth;
TeamBillCycle monthlyBill = mongoTemplate.findOne(
Query.query(Criteria.where("teamId").is(teamId).and("billBelong").is(normalizedYearMonth))
.with(Sort.by(Sort.Order.desc("id"))),
TeamBillCycle.class);
if (Objects.nonNull(monthlyBill)) {
return monthlyBill;
}
return mongoTemplate.findOne( return mongoTemplate.findOne(
Query.query(getBillBelongCriteria(normalizedYearMonth).and("teamId").is(teamId)) Query.query(getBillBelongCriteria(normalizedYearMonth).and("teamId").is(teamId))
.with(Sort.by(Sort.Order.desc("billBelong"), Sort.Order.desc("id"))), .with(Sort.by(Sort.Order.desc("billBelong"), Sort.Order.desc("id"))),
@ -490,14 +501,38 @@ public class TeamBillCycleServiceImpl implements TeamBillCycleService {
return mongoTemplate.find(Query.query(new Criteria()), TeamBillCycle.class); return mongoTemplate.find(Query.query(new Criteria()), TeamBillCycle.class);
} }
@Override @Override
public List<TeamBillCycle> listByGeBillBelong(Long teamId, Integer billBelong, Integer size) { public List<TeamBillCycle> listByGeBillBelong(Long teamId, Integer billBelong, Integer size) {
Criteria criteria = Criteria.where("teamId").is(teamId).and("billBelong").gte(billBelong); int limit = Objects.nonNull(size) ? size : PageConstant.DEFAULT_LIMIT_SIZE;
return mongoTemplate.find(Query.query(criteria) Criteria criteria = Criteria.where("teamId").is(teamId).and("billBelong").gte(billBelong);
.with(Sort.by(Sort.Order.desc("id"))) List<TeamBillCycle> billCycles = mongoTemplate.find(Query.query(criteria)
.limit(size), .with(Sort.by(Sort.Order.desc("id")))
TeamBillCycle.class); .limit(limit * 3),
} TeamBillCycle.class);
if (CollectionUtils.isEmpty(billCycles)) {
return billCycles;
}
Map<Integer, TeamBillCycle> monthBillMap = new LinkedHashMap<>();
billCycles.stream()
.sorted(Comparator
.comparing((TeamBillCycle bill) -> TeamBillCycleUtils.extractMonth(bill.getBillBelong()))
.reversed()
.thenComparing(this::monthlyBillPriority)
.thenComparing(TeamBillCycle::getId, Comparator.reverseOrder()))
.forEach(bill -> monthBillMap.putIfAbsent(
TeamBillCycleUtils.extractMonth(bill.getBillBelong()), bill));
return new ArrayList<>(monthBillMap.values()).stream()
.limit(limit)
.collect(Collectors.toList());
}
private int monthlyBillPriority(TeamBillCycle bill) {
return Objects.equals(bill.getBillBelong(), TeamBillCycleUtils.extractMonth(bill.getBillBelong()))
? 0
: 1;
}
@Override @Override
public List<TeamBillCycle> listLatestBill(Long teamId, Integer size) { public List<TeamBillCycle> listLatestBill(Long teamId, Integer size) {
@ -579,62 +614,39 @@ public class TeamBillCycleServiceImpl implements TeamBillCycleService {
@Override @Override
public void billStatusPayOut() { public void billStatusPayOut() {
// 计算当前账期的 billBelong // 计算当前账期的 billBelong
Integer currentBillBelong = getCalcBillBelong(); Integer currentBillBelong = getCalcBillBelong();
Integer billBelong = calculateSettlementBillBelong(currentBillBelong); Integer billBelong = TeamBillCycleUtils.getPreviousMonthlyBillBelong(currentBillBelong);
// 将小于当前账期的 UNPAID 状态改为 PAY_OUT // 只出整月账单历史半月账单不再自动进入结算避免重复结算
mongoTemplate.updateMulti( mongoTemplate.updateMulti(
Query.query( Query.query(
Criteria.where("billBelong").lte(billBelong) payableMonthlyBillBelongCriteria(billBelong)
.and("status").is(TeamBillCycleStatus.UNPAID)), .and("status").is(TeamBillCycleStatus.UNPAID)),
new Update() new Update()
.set("updateTime", TimestampUtils.now()) .set("updateTime", TimestampUtils.now())
.set("status", TeamBillCycleStatus.PAY_OUT), .set("status", TeamBillCycleStatus.PAY_OUT),
TeamBillCycle.class); TeamBillCycle.class);
} }
public void billStatusPayOutTest() { public void billStatusPayOutTest() {
// 计算当前账期的 billBelong // 计算当前账期的 billBelong
Integer currentBillBelong = 20251201; Integer currentBillBelong = 202512;
Integer billBelong = calculateSettlementBillBelong(currentBillBelong); Integer billBelong = TeamBillCycleUtils.getPreviousMonthlyBillBelong(currentBillBelong);
// 将小于当前账期的 UNPAID 状态改为 PAY_OUT // 将小于当前账期的 UNPAID 状态改为 PAY_OUT
List<TeamBillCycle> teamBillCycles = mongoTemplate.find( List<TeamBillCycle> teamBillCycles = mongoTemplate.find(
Query.query( Query.query(
Criteria.where("billBelong").lte(billBelong) payableMonthlyBillBelongCriteria(billBelong)
.and("status").is(TeamBillCycleStatus.UNPAID)), .and("status").is(TeamBillCycleStatus.UNPAID)),
TeamBillCycle.class); TeamBillCycle.class);
System.out.println(); System.out.println();
} }
private Criteria payableMonthlyBillBelongCriteria(Integer billBelong) {
return Criteria.where("billBelong").gte(100000).lte(billBelong);
/** }
* 计算需要结算的账期
* 规则
* - 如果当前账期是本月1号如20250101则结算上个月16号的账期如20241216
* - 如果当前账期是本月16号如20250116则结算本月1号的账期如20250101
*/
private Integer calculateSettlementBillBelong(Integer currentBillBelong) {
String billStr = String.valueOf(currentBillBelong);
int year = Integer.parseInt(billStr.substring(0, 4));
int month = Integer.parseInt(billStr.substring(4, 6));
int day = Integer.parseInt(billStr.substring(6, 8));
LocalDate settlementDate;
if (day == 1) {
// 当前是本月1号结算上个月16号
settlementDate = LocalDate.of(year, month, 1).minusMonths(1).withDayOfMonth(16);
} else {
// 当前是本月16号结算本月1号
settlementDate = LocalDate.of(year, month, 1);
}
return Integer.parseInt(settlementDate.format(DateTimeFormatter.ofPattern("yyyyMMdd")));
}
@Override @Override
public void scanPayOut(Consumer<List<TeamBillCycle>> execute, Integer limit) { public void scanPayOut(Consumer<List<TeamBillCycle>> execute, Integer limit) {

View File

@ -15,16 +15,18 @@ import com.red.circle.tool.core.collection.CollectionUtils;
import com.red.circle.tool.core.date.TimestampUtils; import com.red.circle.tool.core.date.TimestampUtils;
import com.red.circle.tool.core.date.ZonedDateTimeAsiaRiyadhUtils; import com.red.circle.tool.core.date.ZonedDateTimeAsiaRiyadhUtils;
import com.red.circle.tool.core.date.ZonedId; import com.red.circle.tool.core.date.ZonedId;
import com.red.circle.tool.core.sequence.IdWorkerUtils; import com.red.circle.tool.core.sequence.IdWorkerUtils;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.ZonedDateTime; import java.sql.Timestamp;
import java.time.temporal.TemporalAdjusters; import java.time.ZonedDateTime;
import java.util.List; import java.time.temporal.TemporalAdjusters;
import java.util.Map; import java.util.List;
import java.util.Objects; import java.util.Map;
import java.util.Set; import java.util.Objects;
import java.util.stream.Collectors; import java.util.Set;
import java.util.TreeMap;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Order; import org.springframework.data.domain.Sort.Order;
@ -656,15 +658,20 @@ public class TeamMemberTargetServiceImpl implements TeamMemberTargetService {
return statistics; return statistics;
} }
@Override @Override
public List<TeamMemberTarget> listByMonthlyBillBelong(Long teamId, Integer monthlyBillBelong) { public List<TeamMemberTarget> listByMonthlyBillBelong(Long teamId, Integer monthlyBillBelong) {
long prefix = monthlyBillBelong.longValue(); long prefix = monthlyBillBelong.longValue();
long min = prefix * 100L + 1; // 202511 20251101 long min = prefix * 100L + 1; // 202511 20251101
long max = (prefix + 1) * 100L - 1; // 202511 20251199 long max = (prefix + 1) * 100L - 1; // 202511 20251199
Criteria criteria = Criteria.where("teamId").is(teamId) Criteria criteria = new Criteria().andOperator(
// .and("history").is(Boolean.FALSE) Criteria.where("teamId").is(teamId),
.and("billBelong").gte(min).lte(max); Criteria.where("history").is(Boolean.FALSE),
new Criteria().orOperator(
Criteria.where("billBelong").is(monthlyBillBelong),
Criteria.where("billBelong").gte(min).lte(max)
)
);
return mongoTemplate.find(Query.query(criteria) return mongoTemplate.find(Query.query(criteria)
.with(Sort.by(Order.asc("billBelong"))), .with(Sort.by(Order.asc("billBelong"))),
@ -672,16 +679,21 @@ public class TeamMemberTargetServiceImpl implements TeamMemberTargetService {
} }
@Override @Override
public List<TeamMemberTarget> listMemberMonthlyTarget(Long teamId, Long userId, Integer monthlyBillBelong) { public List<TeamMemberTarget> listMemberMonthlyTarget(Long teamId, Long userId, Integer monthlyBillBelong) {
long prefix = monthlyBillBelong.longValue(); long prefix = monthlyBillBelong.longValue();
long min = prefix * 100L + 1; // 202511 20251101 long min = prefix * 100L + 1; // 202511 20251101
long max = (prefix + 1) * 100L - 1; // 202511 20251199 long max = (prefix + 1) * 100L - 1; // 202511 20251199
// 查询指定用户在指定月份的所有目标记录 // 查询指定用户在指定月份的所有目标记录
Criteria criteria = Criteria.where("teamId").is(teamId) Criteria criteria = new Criteria().andOperator(
.and("userId").is(userId) Criteria.where("teamId").is(teamId),
// .and("history").is(Boolean.FALSE) Criteria.where("userId").is(userId),
.and("billBelong").gte(min).lte(max); Criteria.where("history").is(Boolean.FALSE),
new Criteria().orOperator(
Criteria.where("billBelong").is(monthlyBillBelong),
Criteria.where("billBelong").gte(min).lte(max)
)
);
return mongoTemplate.find(Query.query(criteria) return mongoTemplate.find(Query.query(criteria)
.with(Sort.by(Order.asc("billBelong"))), .with(Sort.by(Order.asc("billBelong"))),
@ -689,13 +701,20 @@ public class TeamMemberTargetServiceImpl implements TeamMemberTargetService {
} }
@Override @Override
public TeamMemberTarget aggregateMonthlyTarget(Long teamId, Long userId, Integer monthlyBillBelong) { public TeamMemberTarget aggregateMonthlyTarget(Long teamId, Long userId, Integer monthlyBillBelong) {
// 1. 查询该月的所有记录可能1条或2条 // 1. 查询该月的所有记录可能1条或2条
List<TeamMemberTarget> monthlyTargets = listMemberMonthlyTarget(teamId, userId, monthlyBillBelong); List<TeamMemberTarget> monthlyTargets = listMemberMonthlyTarget(teamId, userId, monthlyBillBelong);
if (CollectionUtils.isEmpty(monthlyTargets)) { if (CollectionUtils.isEmpty(monthlyTargets)) {
return null; return null;
} }
List<TeamMemberTarget> exactMonthlyTargets = monthlyTargets.stream()
.filter(target -> Objects.equals(target.getBillBelong(), monthlyBillBelong))
.collect(Collectors.toList());
if (exactMonthlyTargets.size() == monthlyTargets.size()) {
return exactMonthlyTargets.get(exactMonthlyTargets.size() - 1);
}
// 2. 如果只有一条记录直接返回 // 2. 如果只有一条记录直接返回
if (monthlyTargets.size() == 1) { if (monthlyTargets.size() == 1) {
@ -720,11 +739,7 @@ public class TeamMemberTargetServiceImpl implements TeamMemberTargetService {
aggregated.setUpdateTime(monthlyTargets.get(monthlyTargets.size() - 1).getUpdateTime()); aggregated.setUpdateTime(monthlyTargets.get(monthlyTargets.size() - 1).getUpdateTime());
// 4. 合并每日目标数据dailyTargets // 4. 合并每日目标数据dailyTargets
List<TeamMemberTargetIndex> allDailyTargets = monthlyTargets.stream() aggregated.setDailyTargets(mergeDailyTargets(monthlyTargets));
.flatMap(target -> target.getDailyTargets().stream())
.sorted((d1, d2) -> d1.getDateNumber().compareTo(d2.getDateNumber()))
.collect(Collectors.toList());
aggregated.setDailyTargets(allDailyTargets);
// 5. 聚合结算结果如果有多条取总和 // 5. 聚合结算结果如果有多条取总和
List<TeamMemberTargetSettlementResult> settlementResults = monthlyTargets.stream() List<TeamMemberTargetSettlementResult> settlementResults = monthlyTargets.stream()
@ -829,15 +844,75 @@ public class TeamMemberTargetServiceImpl implements TeamMemberTargetService {
* 生成月度账单标题 * 生成月度账单标题
* 202511 -> "2025年11月" * 202511 -> "2025年11月"
*/ */
private String generateMonthlyBillTitle(Integer monthlyBillBelong) { private String generateMonthlyBillTitle(Integer monthlyBillBelong) {
String billStr = String.valueOf(monthlyBillBelong); String billStr = String.valueOf(monthlyBillBelong);
int year = Integer.parseInt(billStr.substring(0, 4)); int year = Integer.parseInt(billStr.substring(0, 4));
int month = Integer.parseInt(billStr.substring(4, 6)); int month = Integer.parseInt(billStr.substring(4, 6));
return String.format("%d年%d月", year, month); return String.format("%d年%d月", year, month);
} }
/** private List<TeamMemberTargetIndex> mergeDailyTargets(List<TeamMemberTarget> monthlyTargets) {
* 比较状态优先级 Map<Integer, TeamMemberTargetIndex> dailyTargetMap = new TreeMap<>();
monthlyTargets.stream()
.map(TeamMemberTarget::getDailyTargets)
.filter(CollectionUtils::isNotEmpty)
.flatMap(List::stream)
.filter(Objects::nonNull)
.filter(dailyTarget -> Objects.nonNull(dailyTarget.getDateNumber()))
.forEach(dailyTarget -> {
TeamMemberTargetIndex target = dailyTargetMap.computeIfAbsent(
dailyTarget.getDateNumber(),
dateNumber -> new TeamMemberTargetIndex()
.setDateNumber(dateNumber)
.setCreateTime(dailyTarget.getCreateTime())
.setUpdateTime(dailyTarget.getUpdateTime()));
target.setOwnOnlineTime(sumLong(target.getOwnOnlineTime(), dailyTarget.getOwnOnlineTime()));
target.setOtherOnlineTime(sumLong(target.getOtherOnlineTime(), dailyTarget.getOtherOnlineTime()));
target.setAcceptGiftValue(sumLong(target.getAcceptGiftValue(), dailyTarget.getAcceptGiftValue()));
target.setGiveGiftValue(sumLong(target.getGiveGiftValue(), dailyTarget.getGiveGiftValue()));
target.setRoomValue(sumLong(target.getRoomValue(), dailyTarget.getRoomValue()));
target.setOverFlowGiftValue(sumLong(target.getOverFlowGiftValue(), dailyTarget.getOverFlowGiftValue()));
target.setEffectiveDay(maxInteger(target.getEffectiveDay(), dailyTarget.getEffectiveDay()));
target.setCreateTime(minTimestamp(target.getCreateTime(), dailyTarget.getCreateTime()));
target.setUpdateTime(maxTimestamp(target.getUpdateTime(), dailyTarget.getUpdateTime()));
});
return List.copyOf(dailyTargetMap.values());
}
private Long sumLong(Long left, Long right) {
return (Objects.nonNull(left) ? left : 0L) + (Objects.nonNull(right) ? right : 0L);
}
private Integer maxInteger(Integer left, Integer right) {
if (Objects.isNull(left) && Objects.isNull(right)) {
return null;
}
return Math.max(Objects.nonNull(left) ? left : 0, Objects.nonNull(right) ? right : 0);
}
private Timestamp minTimestamp(Timestamp left, Timestamp right) {
if (Objects.isNull(left)) {
return right;
}
if (Objects.isNull(right)) {
return left;
}
return left.before(right) ? left : right;
}
private Timestamp maxTimestamp(Timestamp left, Timestamp right) {
if (Objects.isNull(left)) {
return right;
}
if (Objects.isNull(right)) {
return left;
}
return left.after(right) ? left : right;
}
/**
* 比较状态优先级
* 返回值: 1 表示 s1 优先级高, -1 表示 s2 优先级高, 0 表示相等 * 返回值: 1 表示 s1 优先级高, -1 表示 s2 优先级高, 0 表示相等
*/ */
private int compareStatus(TeamMemberTargetStatus s1, TeamMemberTargetStatus s2) { private int compareStatus(TeamMemberTargetStatus s1, TeamMemberTargetStatus s2) {