diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/admintask/AdminTaskQueryCmdExe.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/admintask/AdminTaskQueryCmdExe.java index fd51df48..f11746c4 100644 --- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/admintask/AdminTaskQueryCmdExe.java +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/admintask/AdminTaskQueryCmdExe.java @@ -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.infra.database.mongo.service.team.TeamBillCycleUtils; import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Component; - -import java.time.LocalDate; -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; +import org.springframework.stereotype.Component; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; import java.util.stream.Collectors; /** @@ -27,10 +26,10 @@ public class AdminTaskQueryCmdExe { private final AdminTaskGateway adminTaskGateway; - public List execute(AdminTaskQueryCmd cmd) { - LocalDate cycleStartDate = cmd.getCycleStartDate() != null ? cmd.getCycleStartDate() : - LocalDate.parse(TeamBillCycleUtils.getCalcBillBelong().toString(), DateTimeFormatter.ofPattern("yyyyMMdd")); - List records = adminTaskGateway.findByUserIdAndCycle(cmd.getUserId(), cycleStartDate); + public List execute(AdminTaskQueryCmd cmd) { + LocalDate cycleStartDate = cmd.getCycleStartDate() != null ? cmd.getCycleStartDate() : + TeamBillCycleUtils.getBillBelongStartDate(TeamBillCycleUtils.getCalcBillBelong()); + List records = adminTaskGateway.findByUserIdAndCycle(cmd.getUserId(), cycleStartDate); Map taskRecordMap = records.stream().collect(Collectors.toMap(AdminTaskRecord::getTaskType, e -> e)); List recordList = new ArrayList<>(); @@ -46,10 +45,10 @@ public class AdminTaskQueryCmdExe { /** * 查询单个任务 */ - public AdminTaskRecord findByUserIdAndTaskTypeAndCycle(Long userId, String taskType, LocalDate cycleStartDate) { - LocalDate queryDate = cycleStartDate != null ? cycleStartDate : - LocalDate.parse(TeamBillCycleUtils.getCalcBillBelong().toString(), DateTimeFormatter.ofPattern("yyyyMMdd")); - return adminTaskGateway.findByUserIdAndTaskTypeAndCycle(userId, taskType, queryDate); - } + public AdminTaskRecord findByUserIdAndTaskTypeAndCycle(Long userId, String taskType, LocalDate cycleStartDate) { + LocalDate queryDate = cycleStartDate != null ? cycleStartDate : + TeamBillCycleUtils.getBillBelongStartDate(TeamBillCycleUtils.getCalcBillBelong()); + return adminTaskGateway.findByUserIdAndTaskTypeAndCycle(userId, taskType, queryDate); + } } diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/team/query/TeamMemberWorkQryExe.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/team/query/TeamMemberWorkQryExe.java index 423a72ec..2be9ea8b 100644 --- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/team/query/TeamMemberWorkQryExe.java +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/team/query/TeamMemberWorkQryExe.java @@ -92,12 +92,13 @@ public class TeamMemberWorkQryExe { .setTargets(Lists.newArrayList()); } - Map teamMemberTargetMap = Optional.ofNullable( - teamMemberTargetService.listByGeBillBelong( - teamMember.getTeamId(), teamMember.getMemberId(), billBelong)) - .map(target -> target.stream() - .collect(Collectors.toMap(TeamMemberTarget::getBillBelong, v -> v))) - .orElseGet(Maps::newHashMap); + Map teamMemberTargetMap = Optional.ofNullable( + teamMemberTargetService.listByGeBillBelong( + teamMember.getTeamId(), teamMember.getMemberId(), billBelong)) + .map(target -> target.stream() + .collect(Collectors.toMap(TeamMemberTarget::getBillBelong, v -> v, (v1, v2) -> v2))) + .orElseGet(Maps::newHashMap); + Map monthlyTargetCache = Maps.newHashMap(); List unpaidBillCycles = teamBillCycles.stream() .filter( @@ -114,18 +115,24 @@ public class TeamMemberWorkQryExe { TeamMemberWorkCO teamMemberWorkCO = new TeamMemberWorkCO() .setMemberProfile(userProfile) - .setTargets(teamBillCycles.stream() - .map(bill -> { - TeamMemberWorkBillCO co = new TeamMemberWorkBillCO() - .setId(bill.getId()) - .setBillBelong(bill.getBillBelong()) - .setBillTitle(TeamBillCycleUtils.parseBillBelongToDateRangeStr(bill.getBillBelong())) - .setStatus(bill.getStatus().name()); - - TeamMemberTarget target = teamMemberTargetMap.get(bill.getBillBelong()); - if (Objects.isNull(target)) { - return co; - } + .setTargets(teamBillCycles.stream() + .map(bill -> { + Integer monthlyBillBelong = TeamBillCycleUtils.extractMonth(bill.getBillBelong()); + TeamMemberWorkBillCO co = new TeamMemberWorkBillCO() + .setId(bill.getId()) + .setBillBelong(monthlyBillBelong) + .setBillTitle(TeamBillCycleUtils.parseBillBelongToDateRangeStr(monthlyBillBelong)) + .setStatus(bill.getStatus().name()); + + TeamMemberTarget target = resolveMonthlyTarget( + teamMember.getTeamId(), + teamMember.getMemberId(), + bill, + teamMemberTargetMap, + monthlyTargetCache); + if (Objects.isNull(target)) { + return co; + } co.setTarget(new TeamMemberWorkTargetCO() .setId(target.getTimeId()) @@ -182,13 +189,45 @@ public class TeamMemberWorkQryExe { return stringList; } - private Map getMetadata(SysRegionConfig regionConfig) { - return Optional.ofNullable(regionConfig) - .map(SysRegionConfig::getMetadata).orElseGet(CollectionUtils::newHashMap); - } - - /** - * 是否开启每日自动结算工资. + private Map getMetadata(SysRegionConfig regionConfig) { + return Optional.ofNullable(regionConfig) + .map(SysRegionConfig::getMetadata).orElseGet(CollectionUtils::newHashMap); + } + + private TeamMemberTarget resolveMonthlyTarget(Long teamId, + Long memberId, + TeamBillCycle bill, + Map targetMap, + Map 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 metadata) { return Objects.equals(metadata.get("openDailyAutoSalary"), "true"); diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/scheduler/AdminSalarySettlementTask.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/scheduler/AdminSalarySettlementTask.java index d89e3920..c3f34cc8 100644 --- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/scheduler/AdminSalarySettlementTask.java +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/scheduler/AdminSalarySettlementTask.java @@ -5,15 +5,12 @@ import com.red.circle.other.app.service.AdminSalarySettlementService; import com.red.circle.other.infra.database.mongo.service.team.TeamBillCycleUtils; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Component; - -import java.time.LocalDate; -import java.time.format.DateTimeFormatter; - -/** - * 管理员工资结算定时任务. - * 每月1号和16号 2点执行半月结算(在BD和BD Leader结算之后) +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +/** + * 管理员工资结算定时任务. + * 每月1号2点执行整月结算(在BD和BD Leader结算之后). * * @author AI Assistant * @since 2025-12-13 @@ -25,12 +22,12 @@ public class AdminSalarySettlementTask { private final AdminSalarySettlementService adminSalarySettlementService; - /** - * 每月1号和16号 2点执行半月结算. - */ - @Scheduled(cron = "0 0 2 1,16 * ?", zone = "Asia/Riyadh") - @TaskCacheLock(key = "ADMIN_SALARY_SETTLEMENT", expireSecond = 86400) - public void processAdminSalarySettlement() { + /** + * 每月1号2点执行上月整月结算. + */ + @Scheduled(cron = "0 0 2 1 * ?", zone = "Asia/Riyadh") + @TaskCacheLock(key = "ADMIN_SALARY_SETTLEMENT", expireSecond = 86400) + public void processAdminSalarySettlement() { long startTime = System.currentTimeMillis(); log.warn("========== 管理员工资结算定时任务开始 =========="); @@ -54,30 +51,12 @@ public class AdminSalarySettlementTask { } } - /** - * 计算需要结算的账期. - * 规则: - * - 如果当前账期是本月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"))); - } + /** + * 计算需要结算的上月整月账期. + */ + private Integer calculateSettlementBillBelong(Integer currentBillBelong) { + return TeamBillCycleUtils.getPreviousMonthlyBillBelong(currentBillBelong); + } /** * 测试方法(手动触发). diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/scheduler/BdLeaderSalarySettlementTask.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/scheduler/BdLeaderSalarySettlementTask.java index 0a9a9d9f..88de9321 100644 --- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/scheduler/BdLeaderSalarySettlementTask.java +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/scheduler/BdLeaderSalarySettlementTask.java @@ -5,16 +5,12 @@ import com.red.circle.other.app.service.BdLeaderSalarySettlementService; import com.red.circle.other.infra.database.mongo.service.team.TeamBillCycleUtils; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Component; - -import java.time.LocalDate; -import java.time.ZoneId; -import java.time.format.DateTimeFormatter; - -/** - * BD Leader 工资结算定时任务. - * 每月1号和16号 0点执行半月结算 +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +/** + * BD Leader 工资结算定时任务. + * 每月1号0点执行整月结算. * * @author AI Assistant * @since 2025-10-29 @@ -26,12 +22,12 @@ public class BdLeaderSalarySettlementTask { private final BdLeaderSalarySettlementService bdLeaderSalarySettlementService; - /** - * 每月1号和16号 1点半执行半月结算. - */ - @Scheduled(cron = "0 0 1 1,16 * ?", zone = "Asia/Riyadh") - @TaskCacheLock(key = "BD_LEADER_SALARY_SETTLEMENT", expireSecond = 86400) - public void processBdLeaderSalarySettlement() { + /** + * 每月1号1点执行上月整月结算. + */ + @Scheduled(cron = "0 0 1 1 * ?", zone = "Asia/Riyadh") + @TaskCacheLock(key = "BD_LEADER_SALARY_SETTLEMENT", expireSecond = 86400) + public void processBdLeaderSalarySettlement() { long startTime = System.currentTimeMillis(); log.warn("========== BD Leader工资结算定时任务开始 =========="); @@ -55,30 +51,12 @@ public class BdLeaderSalarySettlementTask { } } - /** - * 计算需要结算的账期 - * 规则: - * - 如果当前账期是本月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"))); - } + /** + * 计算需要结算的上月整月账期. + */ + private Integer calculateSettlementBillBelong(Integer currentBillBelong) { + return TeamBillCycleUtils.getPreviousMonthlyBillBelong(currentBillBelong); + } /** * 测试方法(手动触发). diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/scheduler/BdSalarySettlementTask.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/scheduler/BdSalarySettlementTask.java index 90241772..a4452696 100644 --- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/scheduler/BdSalarySettlementTask.java +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/scheduler/BdSalarySettlementTask.java @@ -5,16 +5,12 @@ import com.red.circle.other.app.service.BdSalarySettlementService; import com.red.circle.other.infra.database.mongo.service.team.TeamBillCycleUtils; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Component; - -import java.time.LocalDate; -import java.time.ZoneId; -import java.time.format.DateTimeFormatter; - -/** - * BD 工资结算定时任务. - * 每月1号和16号 0点执行半月结算 +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +/** + * BD 工资结算定时任务. + * 每月1号0点执行整月结算. * * @author AI Assistant * @since 2025-10-29 @@ -26,12 +22,12 @@ public class BdSalarySettlementTask { private final BdSalarySettlementService bdSalarySettlementService; - /** - * 每月1号和16号 1点执行半月结算. - */ - @Scheduled(cron = "0 30 0 1,16 * ?", zone = "Asia/Riyadh") - @TaskCacheLock(key = "BD_SALARY_SETTLEMENT", expireSecond = 86400) - public void processBdSalarySettlement() { + /** + * 每月1号0点30分执行上月整月结算. + */ + @Scheduled(cron = "0 30 0 1 * ?", zone = "Asia/Riyadh") + @TaskCacheLock(key = "BD_SALARY_SETTLEMENT", expireSecond = 86400) + public void processBdSalarySettlement() { long startTime = System.currentTimeMillis(); log.warn("========== BD工资结算定时任务开始 =========="); @@ -55,30 +51,12 @@ public class BdSalarySettlementTask { } } - /** - * 计算需要结算的账期 - * 规则: - * - 如果当前账期是本月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"))); - } + /** + * 计算需要结算的上月整月账期. + */ + private Integer calculateSettlementBillBelong(Integer currentBillBelong) { + return TeamBillCycleUtils.getPreviousMonthlyBillBelong(currentBillBelong); + } /** * 测试方法(手动触发). diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/scheduler/TeamBillTask.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/scheduler/TeamBillTask.java index e19d1a0b..403da5f1 100644 --- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/scheduler/TeamBillTask.java +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/scheduler/TeamBillTask.java @@ -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.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.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.TeamProfileService; -import com.red.circle.other.infra.utils.ZonedDateTimeUtils; -import com.red.circle.other.inner.model.cmd.team.TeamBillCmd; -import com.red.circle.tool.core.date.TimestampUtils; - -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.YearMonth; -import java.time.ZoneId; -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; +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.utils.ZonedDateTimeUtils; +import com.red.circle.other.inner.model.cmd.team.TeamBillCmd; +import com.red.circle.tool.core.date.TimestampUtils; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -39,25 +34,21 @@ public class TeamBillTask { private final TeamSalaryMqMessage otherMqMessage; private final TeamProfileService teamProfileService; private final TeamBillCycleService teamBillCycleService; - private final GiftGiveRunningWaterService giftGiveRunningWaterService; - - /** - * 每月1号和16号 0点执行半月结算 - */ - @Scheduled(cron = "0 0 0 1,16 * ?", zone = "Asia/Riyadh") - @TaskCacheLock(key = "PROCESS_TEAM_BILL_HALF_MONTH", expireSecond = 86400) - public void processTeamHalfMonthBill() { - long startTime = System.currentTimeMillis(); - log.info("exec process_team_half_month_bill start"); - - // 判断是上半月还是下半月 - LocalDate now = LocalDate.now(ZoneId.of("Asia/Riyadh")); - String billPeriod = now.getDayOfMonth() == 1 ? "first_half" : "second_half"; - - processTeamBill(); - log.info("exec process_team_half_month_bill end with {}", - System.currentTimeMillis() - startTime); - } + private final GiftGiveRunningWaterService giftGiveRunningWaterService; + + /** + * 每月1号0点执行整月结算,并创建新月账期. + */ + @Scheduled(cron = "0 0 0 1 * ?", zone = "Asia/Riyadh") + @TaskCacheLock(key = "PROCESS_TEAM_BILL_MONTH", expireSecond = 86400) + public void processTeamMonthBill() { + long startTime = System.currentTimeMillis(); + log.info("exec process_team_month_bill start"); + + processTeamBill(); + log.info("exec process_team_month_bill end with {}", + System.currentTimeMillis() - startTime); + } public void processTeamMonthBillTest() { processTeamBill(); diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/service/AdminTaskServiceImpl.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/service/AdminTaskServiceImpl.java index 22080885..c30eafd4 100644 --- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/service/AdminTaskServiceImpl.java +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/service/AdminTaskServiceImpl.java @@ -15,12 +15,11 @@ import com.red.circle.other.infra.database.mongo.service.team.TeamBillCycleUtils import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.time.LocalDate; -import java.time.format.DateTimeFormatter; -import java.util.List; -import java.util.stream.Collectors; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDate; +import java.util.List; +import java.util.stream.Collectors; /** * 管理员任务服务实现 @@ -55,10 +54,10 @@ public class AdminTaskServiceImpl implements AdminTaskService { return adminTaskConvertor.toRecordCO(record); } - @Override - public AdminTaskSummaryCO queryTaskSummary(AdminTaskQueryCmd cmd) { - LocalDate cycleStartDate = cmd.getCycleStartDate() != null ? cmd.getCycleStartDate() : - LocalDate.parse(TeamBillCycleUtils.getCalcBillBelong().toString(), DateTimeFormatter.ofPattern("yyyyMMdd")); + @Override + public AdminTaskSummaryCO queryTaskSummary(AdminTaskQueryCmd cmd) { + LocalDate cycleStartDate = cmd.getCycleStartDate() != null ? cmd.getCycleStartDate() : + TeamBillCycleUtils.getBillBelongStartDate(TeamBillCycleUtils.getCalcBillBelong()); List records = adminTaskQueryCmdExe.execute(cmd); @@ -107,20 +106,20 @@ public class AdminTaskServiceImpl implements AdminTaskService { return summary; } - @Override - public boolean isTaskCompleted(Long userId, String taskType, LocalDate cycleStartDate) { - LocalDate queryDate = cycleStartDate != null ? cycleStartDate : - LocalDate.parse(TeamBillCycleUtils.getCalcBillBelong().toString(), DateTimeFormatter.ofPattern("yyyyMMdd")); - AdminTaskRecord record = adminTaskQueryCmdExe.findByUserIdAndTaskTypeAndCycle(userId, taskType, queryDate); - return record != null && record.isCompleted(); + @Override + public boolean isTaskCompleted(Long userId, String taskType, LocalDate cycleStartDate) { + LocalDate queryDate = cycleStartDate != null ? cycleStartDate : + TeamBillCycleUtils.getBillBelongStartDate(TeamBillCycleUtils.getCalcBillBelong()); + AdminTaskRecord record = adminTaskQueryCmdExe.findByUserIdAndTaskTypeAndCycle(userId, taskType, queryDate); + return record != null && record.isCompleted(); } /** * 确保任务已初始化(兜底逻辑) */ - private void ensureTasksInitialized(Long userId, LocalDate cycleStartDate) { - LocalDate queryDate = cycleStartDate != null ? cycleStartDate : - LocalDate.parse(TeamBillCycleUtils.getCalcBillBelong().toString(), DateTimeFormatter.ofPattern("yyyyMMdd")); + private void ensureTasksInitialized(Long userId, LocalDate cycleStartDate) { + LocalDate queryDate = cycleStartDate != null ? cycleStartDate : + TeamBillCycleUtils.getBillBelongStartDate(TeamBillCycleUtils.getCalcBillBelong()); // 检查是否存在任务记录 boolean exists = adminTaskGateway.existsByUserIdAndCycle(userId, queryDate); @@ -142,18 +141,10 @@ public class AdminTaskServiceImpl implements AdminTaskService { } } - /** - * 计算周期结束日期 - */ - private LocalDate calculateCycleEndDate(LocalDate cycleStartDate) { - int dayOfMonth = cycleStartDate.getDayOfMonth(); - - if (dayOfMonth == 1) { - // 1号开始,结束于15号 - return LocalDate.of(cycleStartDate.getYear(), cycleStartDate.getMonth(), 15); - } else { - // 16号开始,结束于月末 - return cycleStartDate.withDayOfMonth(cycleStartDate.lengthOfMonth()); - } - } -} + /** + * 计算周期结束日期 + */ + private LocalDate calculateCycleEndDate(LocalDate cycleStartDate) { + return cycleStartDate.withDayOfMonth(cycleStartDate.lengthOfMonth()); + } +} diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/team/TeamBillCycleUtils.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/team/TeamBillCycleUtils.java index 615d5bcb..c73a7fac 100644 --- a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/team/TeamBillCycleUtils.java +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/team/TeamBillCycleUtils.java @@ -665,31 +665,16 @@ public final class TeamBillCycleUtils { } - public static Integer getCalcBillBelong() { - LocalDate now = ZonedDateTimeAsiaRiyadhUtils.now().toLocalDate(); - return calculateCurrentBillBelong(now); - } - - /** - * 计算当前账期的 billBelong - * 规则: - * - 1-15号:当前账期是本月1号(20251001) - * - 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"))); - } + public static Integer getCalcBillBelong() { + return getMonthlyBillBelong(); + } + + /** + * 计算当前整月账期的 billBelong,返回格式:202510. + */ + private static Integer calculateCurrentBillBelong(LocalDate now) { + return Integer.parseInt(now.format(DateTimeFormatter.ofPattern("yyyyMM"))); + } /** * 解析 billBelong 为日期范围. @@ -758,10 +743,18 @@ public final class TeamBillCycleUtils { * 获取月度账期(用于批次模式) * 返回格式:202511 */ - public static Integer getMonthlyBillBelong() { - LocalDate now = ZonedDateTimeAsiaRiyadhUtils.now().toLocalDate(); - return Integer.parseInt(now.format(DateTimeFormatter.ofPattern("yyyyMM"))); - } + public static Integer getMonthlyBillBelong() { + LocalDate now = ZonedDateTimeAsiaRiyadhUtils.now().toLocalDate(); + 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) { - // 从配置中读取,或者硬编码 - // 例如:return "SA".equals(region); // 沙特地区启用批次模式 - return true; - } + public static Boolean isBatchModeEnabled(String region) { + return false; + } /** * 从 billBelong 提取月份(兼容新旧格式) @@ -833,30 +824,56 @@ public final class TeamBillCycleUtils { return String.format("%d年%d月(%s)", year, month, batchDesc); } - public static LocalDateTime getBillBelongStartTime(Integer billBelong) { - // billBelong 格式:20251101 或 20251116 - int year = billBelong / 10000; // 2025 - int month = (billBelong / 100) % 100; // 11 - int day = billBelong % 100; // 01 或 16 - - return LocalDateTime.of(year, month, day, 0, 0, 0); - } - - public static LocalDateTime getBillBelongEndTime(Integer billBelong) { - // billBelong 格式:20251101 或 20251116 - int year = billBelong / 10000; // 2025 - int month = (billBelong / 100) % 100; // 11 - int day = billBelong % 100; // 01 或 16 - - LocalDateTime startTime = LocalDateTime.of(year, month, day, 0, 0, 0); - - if (day == 1) { - // 如果是1号,结束时间是当月16号0点 - return LocalDateTime.of(year, month, 16, 0, 0, 0); - } else { - // 如果是16号,结束时间是下个月1号0点 - return startTime.plusMonths(1).withDayOfMonth(1); - } - } - -} + public static LocalDate getBillBelongStartDate(Integer billBelong) { + String billStr = String.valueOf(billBelong); + if (billStr.length() == 6) { + return parseYearMonth(billBelong).atDay(1); + } + if (billStr.length() == 8) { + return LocalDate.of( + Integer.parseInt(billStr.substring(0, 4)), + Integer.parseInt(billStr.substring(4, 6)), + Integer.parseInt(billStr.substring(6, 8))); + } + throw new IllegalArgumentException("Unsupported billBelong: " + billBelong); + } + + public static LocalDate getBillBelongEndDate(Integer billBelong) { + String billStr = String.valueOf(billBelong); + if (billStr.length() == 6) { + return parseYearMonth(billBelong).atEndOfMonth(); + } + if (billStr.length() == 8) { + LocalDate startDate = getBillBelongStartDate(billBelong); + return startDate.getDayOfMonth() == 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))); + } + +} diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/team/team/impl/TeamBillCycleServiceImpl.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/team/team/impl/TeamBillCycleServiceImpl.java index d29d7756..36ec8055 100644 --- a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/team/team/impl/TeamBillCycleServiceImpl.java +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/team/team/impl/TeamBillCycleServiceImpl.java @@ -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.text.StringUtils; -import java.math.BigDecimal; -import java.time.LocalDate; -import java.time.format.DateTimeFormatter; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; +import java.math.BigDecimal; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; import java.util.function.Consumer; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; @@ -244,12 +246,11 @@ public class TeamBillCycleServiceImpl implements TeamBillCycleService { /** * 创建账单(兼容批次模式) */ - @Override - public void createIfAbsent(TeamBillCmd cmd) { - // 计算 billBelong(保持原有逻辑) - Integer billBelong = TeamBillCycleUtils.getCalcBillBelong(); - - // 检查是否已存在 + @Override + public void createIfAbsent(TeamBillCmd cmd) { + Integer billBelong = TeamBillCycleUtils.getCalcBillBelong(); + + // 检查是否已存在 boolean existing = existsPrimaryBillBelong(cmd.getTeamId(), billBelong); if (existing) { return; @@ -260,33 +261,18 @@ public class TeamBillCycleServiceImpl implements TeamBillCycleService { .setId(IdWorkerUtils.getId()) .setSysOrigin(cmd.getSysOrigin()) .setTeamId(cmd.getTeamId()) - .setRegion(cmd.getRegion()) - .setBillBelong(billBelong) - .setBillTitle(generateBillTitle(billBelong)) - .setStatus(TeamBillCycleStatus.UNPAID) - .setCreateTime(cmd.getOperationTime()) - .setCreateUser(cmd.getOperationBackUser()); - - Boolean batchMode = TeamBillCycleUtils.isBatchModeEnabled(cmd.getRegion()); - Integer currentBatch = TeamBillCycleUtils.getCurrentSettlementBatch(); - Integer monthlyBillBelong = TeamBillCycleUtils.getMonthlyBillBelong(); - // ===== 新增:如果启用批次模式,添加批次信息 ===== - 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); - } + .setRegion(cmd.getRegion()) + .setBillBelong(billBelong) + .setBillTitle(generateBillTitle(billBelong)) + .setBatchMode(false) + .setMonthlyBillBelong(billBelong) + .setStatus(TeamBillCycleStatus.UNPAID) + .setCreateTime(cmd.getOperationTime()) + .setCreateUser(cmd.getOperationBackUser()); + + mongoTemplate.save(bill); + log.info("创建账单成功:{}", bill); + } private String generateBillTitle(Integer billBelong) { String[] dateRange = TeamBillCycleUtils.parseBillBelongToDateRange(billBelong); @@ -375,12 +361,22 @@ public class TeamBillCycleServiceImpl implements TeamBillCycleService { TeamBillCycle.class); } - @Override - public TeamBillCycle getTeamUnpaidBill(Long teamId) { - return mongoTemplate.findOne(Query.query(Criteria.where("teamId").is(teamId) - .and("status").is(TeamBillCycleStatus.UNPAID)), - TeamBillCycle.class); - } + @Override + public TeamBillCycle getTeamUnpaidBill(Long teamId) { + TeamBillCycle currentMonthBill = mongoTemplate.findOne( + Query.query(Criteria.where("teamId").is(teamId) + .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 public Long getLatestBillIdByTeamId(Long teamId) { @@ -394,11 +390,19 @@ public class TeamBillCycleServiceImpl implements TeamBillCycleService { } - @Override - public TeamBillCycle getLatestByTeamId(Long teamId) { - - List cycleList = listLatestBill(teamId, 1); - if (CollectionUtils.isEmpty(cycleList)) { + @Override + public TeamBillCycle getLatestByTeamId(Long teamId) { + TeamBillCycle currentMonthBill = mongoTemplate.findOne( + Query.query(Criteria.where("teamId").is(teamId) + .and("billBelong").is(getCalcBillBelong())) + .with(Sort.by(Sort.Order.desc("id"))), + TeamBillCycle.class); + if (Objects.nonNull(currentMonthBill)) { + return currentMonthBill; + } + + List cycleList = listLatestBill(teamId, 1); + if (CollectionUtils.isEmpty(cycleList)) { return null; } @@ -435,6 +439,13 @@ public class TeamBillCycleServiceImpl implements TeamBillCycleService { Integer normalizedYearMonth = yearMonth > 999999 ? Integer.valueOf(yearMonth.toString().substring(0, 6)) : 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( Query.query(getBillBelongCriteria(normalizedYearMonth).and("teamId").is(teamId)) .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); } - @Override - public List listByGeBillBelong(Long teamId, Integer billBelong, Integer size) { - Criteria criteria = Criteria.where("teamId").is(teamId).and("billBelong").gte(billBelong); - return mongoTemplate.find(Query.query(criteria) - .with(Sort.by(Sort.Order.desc("id"))) - .limit(size), - TeamBillCycle.class); - } + @Override + public List listByGeBillBelong(Long teamId, Integer billBelong, Integer size) { + int limit = Objects.nonNull(size) ? size : PageConstant.DEFAULT_LIMIT_SIZE; + Criteria criteria = Criteria.where("teamId").is(teamId).and("billBelong").gte(billBelong); + List billCycles = mongoTemplate.find(Query.query(criteria) + .with(Sort.by(Sort.Order.desc("id"))) + .limit(limit * 3), + TeamBillCycle.class); + if (CollectionUtils.isEmpty(billCycles)) { + return billCycles; + } + + Map 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 public List listLatestBill(Long teamId, Integer size) { @@ -579,62 +614,39 @@ public class TeamBillCycleServiceImpl implements TeamBillCycleService { @Override - public void billStatusPayOut() { - // 计算当前账期的 billBelong - Integer currentBillBelong = getCalcBillBelong(); - Integer billBelong = calculateSettlementBillBelong(currentBillBelong); - - // 将小于当前账期的 UNPAID 状态改为 PAY_OUT - mongoTemplate.updateMulti( - Query.query( - Criteria.where("billBelong").lte(billBelong) - .and("status").is(TeamBillCycleStatus.UNPAID)), - new Update() + public void billStatusPayOut() { + // 计算当前账期的 billBelong + Integer currentBillBelong = getCalcBillBelong(); + Integer billBelong = TeamBillCycleUtils.getPreviousMonthlyBillBelong(currentBillBelong); + + // 只出整月账单,历史半月账单不再自动进入结算,避免重复结算。 + mongoTemplate.updateMulti( + Query.query( + payableMonthlyBillBelongCriteria(billBelong) + .and("status").is(TeamBillCycleStatus.UNPAID)), + new Update() .set("updateTime", TimestampUtils.now()) .set("status", TeamBillCycleStatus.PAY_OUT), TeamBillCycle.class); } - public void billStatusPayOutTest() { - // 计算当前账期的 billBelong - Integer currentBillBelong = 20251201; - Integer billBelong = calculateSettlementBillBelong(currentBillBelong); - - // 将小于当前账期的 UNPAID 状态改为 PAY_OUT - List teamBillCycles = mongoTemplate.find( - Query.query( - Criteria.where("billBelong").lte(billBelong) - .and("status").is(TeamBillCycleStatus.UNPAID)), - TeamBillCycle.class); - System.out.println(); - } - - - - /** - * 计算需要结算的账期 - * 规则: - * - 如果当前账期是本月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"))); - } + public void billStatusPayOutTest() { + // 计算当前账期的 billBelong + Integer currentBillBelong = 202512; + Integer billBelong = TeamBillCycleUtils.getPreviousMonthlyBillBelong(currentBillBelong); + + // 将小于当前账期的 UNPAID 状态改为 PAY_OUT + List teamBillCycles = mongoTemplate.find( + Query.query( + payableMonthlyBillBelongCriteria(billBelong) + .and("status").is(TeamBillCycleStatus.UNPAID)), + TeamBillCycle.class); + System.out.println(); + } + + private Criteria payableMonthlyBillBelongCriteria(Integer billBelong) { + return Criteria.where("billBelong").gte(100000).lte(billBelong); + } @Override public void scanPayOut(Consumer> execute, Integer limit) { diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/team/team/impl/TeamMemberTargetServiceImpl.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/team/team/impl/TeamMemberTargetServiceImpl.java index f33772e5..e4938721 100644 --- a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/team/team/impl/TeamMemberTargetServiceImpl.java +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/team/team/impl/TeamMemberTargetServiceImpl.java @@ -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.ZonedDateTimeAsiaRiyadhUtils; import com.red.circle.tool.core.date.ZonedId; -import com.red.circle.tool.core.sequence.IdWorkerUtils; - -import java.math.BigDecimal; -import java.time.ZonedDateTime; -import java.time.temporal.TemporalAdjusters; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; +import com.red.circle.tool.core.sequence.IdWorkerUtils; + +import java.math.BigDecimal; +import java.sql.Timestamp; +import java.time.ZonedDateTime; +import java.time.temporal.TemporalAdjusters; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.TreeMap; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Order; @@ -656,15 +658,20 @@ public class TeamMemberTargetServiceImpl implements TeamMemberTargetService { return statistics; } - @Override - public List listByMonthlyBillBelong(Long teamId, Integer monthlyBillBelong) { - long prefix = monthlyBillBelong.longValue(); - long min = prefix * 100L + 1; // 202511 → 20251101 - long max = (prefix + 1) * 100L - 1; // 202511 → 20251199 - - Criteria criteria = Criteria.where("teamId").is(teamId) -// .and("history").is(Boolean.FALSE) - .and("billBelong").gte(min).lte(max); + @Override + public List listByMonthlyBillBelong(Long teamId, Integer monthlyBillBelong) { + long prefix = monthlyBillBelong.longValue(); + long min = prefix * 100L + 1; // 202511 → 20251101 + long max = (prefix + 1) * 100L - 1; // 202511 → 20251199 + + Criteria criteria = new Criteria().andOperator( + Criteria.where("teamId").is(teamId), + 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) .with(Sort.by(Order.asc("billBelong"))), @@ -672,16 +679,21 @@ public class TeamMemberTargetServiceImpl implements TeamMemberTargetService { } @Override - public List listMemberMonthlyTarget(Long teamId, Long userId, Integer monthlyBillBelong) { - long prefix = monthlyBillBelong.longValue(); - long min = prefix * 100L + 1; // 202511 → 20251101 - long max = (prefix + 1) * 100L - 1; // 202511 → 20251199 - - // 查询指定用户在指定月份的所有目标记录 - Criteria criteria = Criteria.where("teamId").is(teamId) - .and("userId").is(userId) -// .and("history").is(Boolean.FALSE) - .and("billBelong").gte(min).lte(max); + public List listMemberMonthlyTarget(Long teamId, Long userId, Integer monthlyBillBelong) { + long prefix = monthlyBillBelong.longValue(); + long min = prefix * 100L + 1; // 202511 → 20251101 + long max = (prefix + 1) * 100L - 1; // 202511 → 20251199 + + // 查询指定用户在指定月份的所有目标记录 + Criteria criteria = new Criteria().andOperator( + Criteria.where("teamId").is(teamId), + Criteria.where("userId").is(userId), + 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) .with(Sort.by(Order.asc("billBelong"))), @@ -689,13 +701,20 @@ public class TeamMemberTargetServiceImpl implements TeamMemberTargetService { } @Override - public TeamMemberTarget aggregateMonthlyTarget(Long teamId, Long userId, Integer monthlyBillBelong) { - // 1. 查询该月的所有记录(可能1条或2条) - List monthlyTargets = listMemberMonthlyTarget(teamId, userId, monthlyBillBelong); - - if (CollectionUtils.isEmpty(monthlyTargets)) { - return null; - } + public TeamMemberTarget aggregateMonthlyTarget(Long teamId, Long userId, Integer monthlyBillBelong) { + // 1. 查询该月的所有记录(可能1条或2条) + List monthlyTargets = listMemberMonthlyTarget(teamId, userId, monthlyBillBelong); + + if (CollectionUtils.isEmpty(monthlyTargets)) { + return null; + } + + List 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. 如果只有一条记录,直接返回 if (monthlyTargets.size() == 1) { @@ -720,11 +739,7 @@ public class TeamMemberTargetServiceImpl implements TeamMemberTargetService { aggregated.setUpdateTime(monthlyTargets.get(monthlyTargets.size() - 1).getUpdateTime()); // 4. 合并每日目标数据(dailyTargets) - List allDailyTargets = monthlyTargets.stream() - .flatMap(target -> target.getDailyTargets().stream()) - .sorted((d1, d2) -> d1.getDateNumber().compareTo(d2.getDateNumber())) - .collect(Collectors.toList()); - aggregated.setDailyTargets(allDailyTargets); + aggregated.setDailyTargets(mergeDailyTargets(monthlyTargets)); // 5. 聚合结算结果(如果有多条,取总和) List settlementResults = monthlyTargets.stream() @@ -829,15 +844,75 @@ public class TeamMemberTargetServiceImpl implements TeamMemberTargetService { * 生成月度账单标题 * 202511 -> "2025年11月" */ - private String generateMonthlyBillTitle(Integer monthlyBillBelong) { - String billStr = String.valueOf(monthlyBillBelong); - int year = Integer.parseInt(billStr.substring(0, 4)); - int month = Integer.parseInt(billStr.substring(4, 6)); - return String.format("%d年%d月", year, month); - } - - /** - * 比较状态优先级 + private String generateMonthlyBillTitle(Integer monthlyBillBelong) { + String billStr = String.valueOf(monthlyBillBelong); + int year = Integer.parseInt(billStr.substring(0, 4)); + int month = Integer.parseInt(billStr.substring(4, 6)); + return String.format("%d年%d月", year, month); + } + + private List mergeDailyTargets(List monthlyTargets) { + Map 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 表示相等 */ private int compareStatus(TeamMemberTargetStatus s1, TeamMemberTargetStatus s2) {