From 5ce1a14341b56c0fdd91bb244f8b4e8323584f3f Mon Sep 17 00:00:00 2001 From: tianfeng <769204422@qq.com> Date: Thu, 13 Nov 2025 17:59:08 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BB=93=E7=AE=97=E4=BF=AE=E6=94=B9=E4=B8=BA?= =?UTF-8?q?=E7=B4=AF=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inner/model/cmd/team/TeamBillCmd.java | 5 + .../listener/team/TeamBillSettleListener.java | 28 ++- .../other/app/scheduler/TeamBillTask.java | 83 +++------ .../mongo/dto/team/SettlementBatch.java | 50 +++++ .../mongo/entity/team/team/TeamBillCycle.java | 29 +++ .../service/team/TeamBillCycleUtils.java | 79 ++++++++ .../team/team/TeamMemberTargetService.java | 32 ++++ .../team/impl/TeamBillCycleServiceImpl.java | 134 +++++++++++--- .../impl/TeamMemberTargetServiceImpl.java | 171 ++++++++++++++++++ .../src/test/java/TeamMonthBillTest.java | 14 +- 10 files changed, 518 insertions(+), 107 deletions(-) create mode 100644 rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/dto/team/SettlementBatch.java diff --git a/rc-service/rc-inner-api/other-inner/other-inner-model/src/main/java/com/red/circle/other/inner/model/cmd/team/TeamBillCmd.java b/rc-service/rc-inner-api/other-inner/other-inner-model/src/main/java/com/red/circle/other/inner/model/cmd/team/TeamBillCmd.java index c254f385..dd8fb4ea 100644 --- a/rc-service/rc-inner-api/other-inner/other-inner-model/src/main/java/com/red/circle/other/inner/model/cmd/team/TeamBillCmd.java +++ b/rc-service/rc-inner-api/other-inner/other-inner-model/src/main/java/com/red/circle/other/inner/model/cmd/team/TeamBillCmd.java @@ -41,4 +41,9 @@ public class TeamBillCmd implements Serializable { */ private Long operationBackUser; + // ===== 新增:批次模式字段 ===== + private Boolean batchMode; + private Integer settlementBatch; + private Integer monthlyBillBelong; + } diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/listener/team/TeamBillSettleListener.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/listener/team/TeamBillSettleListener.java index a50c46a5..c805ddb1 100644 --- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/listener/team/TeamBillSettleListener.java +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/listener/team/TeamBillSettleListener.java @@ -22,6 +22,7 @@ import com.red.circle.other.infra.database.rds.entity.team.BillDiamondBalance; import com.red.circle.other.infra.database.rds.service.team.BillDiamondBalanceService; import com.red.circle.other.inner.endpoint.team.target.TeamProfileClient; import com.red.circle.other.inner.enums.team.*; +import com.red.circle.other.inner.enums.team.TeamMemberTargetIndex; import com.red.circle.other.inner.model.dto.agency.agency.TeamMemberDTO; import com.red.circle.other.inner.model.dto.agency.agency.TeamRemark; import com.red.circle.other.inner.model.dto.agency.agency.TeamSetting; @@ -148,17 +149,24 @@ public class TeamBillSettleListener implements MessageListener { return; } - List teamMemberTargets = teamMemberTargetService.listByBillBelong( - teamBillCycle.getTeamId(), - teamBillCycle.getBillBelong()); - log.warn("团队账单结算teamMemberTargets."+teamMemberTargets); + Integer billBelong = teamBillCycle.getBillBelong(); + Integer batchNumber = TeamBillCycleUtils.extractBatch(billBelong); + List teamMemberTargets; -// teamMemberTargets.forEach(teamMemberTarget -> { -// TeamMember teamMember = teamMemberService.getByMemberId(teamMemberTarget.getUserId()); -// if (Objects.nonNull(teamMember) && TeamMemberRole.OWN.eq(teamMember.getRole())) { -// teamMemberTarget.setIsOwn(true); -// } -// }); + if (batchNumber != null && batchNumber == 2) { + teamMemberTargets = teamMemberTargetService.listByMonthlyBillBelong( + teamBillCycle.getTeamId(), + TeamBillCycleUtils.extractMonth(billBelong) + ); + } else { + // 上半月结算:直接查询当前批次数据 + teamMemberTargets = teamMemberTargetService.listByBillBelong( + teamBillCycle.getTeamId(), + billBelong + ); + } + + log.warn("团队账单结算teamMemberTargets."+teamMemberTargets); // 获得区域配置细信息 SysRegionConfig regionConfig = sysRegionConfigService.getById(teamProfile.getRegion()); 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 ce0ed696..bcb5e4f1 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,6 +5,7 @@ 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; @@ -13,7 +14,9 @@ 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 lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -57,50 +60,6 @@ public class TeamBillTask { processTeamBill(); } -// /** -// * 10分钟, 检查是否存在没有补目标用户. -// */ -// @Scheduled(cron = "0 */10 * * * ?") -// @TaskCacheLock(key = "CHECK_NOT_COUNT_ANCHOR_TARGET", expireSecond = 50) -// public void checkNotCountAnchorGiftTarget() { -// -// List giftGiveRunningWaters = giftGiveRunningWaterService.listThisMonthAnchorNotCountTarget( -// 1000); -// if (CollectionUtils.isEmpty(giftGiveRunningWaters)) { -// return; -// } -// -// for (GiftGiveRunningWater runningWater : giftGiveRunningWaters) { -// for (GiftAcceptUser acceptUser : runningWater.getAcceptUsers()) { -// -// if (!Objects.equals(acceptUser.getAnchor(), Boolean.TRUE)) { -// continue; -// } -// -// if (BigDecimalUtils.isNullOrLteZero(acceptUser.getTargetAmount())) { -// giftGiveRunningWaterService.updateAcceptUserCountTargetAmountTrue( -// runningWater.getId(), -// 0L, -// acceptUser.getAcceptUserId()); -// continue; -// } -// -// // 累计目标 -// TeamTargetBillRes teamTargetBillRes = teamTargetManager -// .incrementGiftValue(acceptUser.getAcceptUserId(), -// acceptUser.getTargetAmount()); -// if (teamTargetBillRes.getSuccess()) { -// // 标记已被统计 -// giftGiveRunningWaterService.updateAcceptUserCountTargetAmountTrue( -// runningWater.getId(), -// teamTargetBillRes.getBillId(), -// acceptUser.getAcceptUserId()); -// } -// -// } -// } -// } -// private void processTeamBill() { log.warn("开始执行账单处理:{},{}", ZonedDateTimeUtils.nowAsiaRiyadhToInt(), LocalDateTime.now()); @@ -112,25 +71,25 @@ public class TeamBillTask { log.warn("结束账单处理"); } - /** - * 创建本月账单,发出结算信号. - */ - private void consumeProcessPayOutBill() { - teamProfileService.scanStatusAvailable(teamProfiles -> { + /** + * 创建本月账单,发出结算信号. + */ + private void consumeProcessPayOutBill() { + teamProfileService.scanStatusAvailable(teamProfiles -> { + for (TeamProfile teamProfile : teamProfiles) { + TeamBillCmd cmd = new TeamBillCmd() + .setSysOrigin(teamProfile.getSysOrigin()) + .setTeamId(teamProfile.getId()) + .setRegion(teamProfile.getRegion()) + .setOperationTime(TimestampUtils.now()) + .setOperationBackUser(teamProfile.getCreateUser()); - for (TeamProfile teamProfile : teamProfiles) { - teamBillCycleService.createIfAbsent(new TeamBillCmd() - .setSysOrigin(teamProfile.getSysOrigin()) - .setTeamId(teamProfile.getId()) - .setRegion(teamProfile.getRegion()) - .setOperationTime(TimestampUtils.now()) - .setOperationBackUser(teamProfile.getCreateUser())); - - otherMqMessage.teamBillSettle(new TeamBillSettleEvent().setTeamId(teamProfile.getId())); - } - }, 5000); - - } + teamBillCycleService.createIfAbsent(cmd); + otherMqMessage.teamBillSettle(new TeamBillSettleEvent() + .setTeamId(teamProfile.getId())); + } + }, 5000); + } } diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/dto/team/SettlementBatch.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/dto/team/SettlementBatch.java new file mode 100644 index 00000000..2e98f91a --- /dev/null +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/dto/team/SettlementBatch.java @@ -0,0 +1,50 @@ +package com.red.circle.other.infra.database.mongo.dto.team; + +import com.red.circle.other.inner.enums.team.TeamBillCycleStatus; +import lombok.Data; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +@Data +public class SettlementBatch { + + /** + * 批次号,用于标识当前是当月的第几个结算批次。 + *
    + *
  • {@code 1} 表示上半月(1日 - 15日)
  • + *
  • {@code 2} 表示下半月(16日 - 月末)
  • + *
+ */ + private Integer batchNumber; + + /** + * 批次标题,用于展示用途,格式如:"2025年11月(上半月)"。 + */ + private String batchTitle; + + /** + * 本批次统计周期的开始日期(包含),例如:{@code 2025-11-01}。 + */ + private LocalDate periodStart; + + /** + * 本批次统计周期的结束日期(包含),例如:{@code 2025-11-15}(上半月)或 {@code 2025-11-30}(下半月)。 + */ + private LocalDate periodEnd; + + /** + * 实际执行结算操作的时间戳,精确到秒,例如:{@code 2025-11-16T10:30:00}。 + */ + private LocalDateTime settlementTime; + + /** + * 本批次的结算结果详情,包含金额、条目数等汇总信息。 + */ + private TeamBillSettleResult batchResult; + + /** + * 本批次的结算状态,如:待结算、结算中、已结算、结算失败等。 + */ + private TeamBillCycleStatus batchStatus; +} \ No newline at end of file diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/entity/team/team/TeamBillCycle.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/entity/team/team/TeamBillCycle.java index 75c3d3d5..6cd65f81 100644 --- a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/entity/team/team/TeamBillCycle.java +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/entity/team/team/TeamBillCycle.java @@ -2,12 +2,14 @@ package com.red.circle.other.infra.database.mongo.entity.team.team; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import com.red.circle.other.infra.database.mongo.dto.team.SettlementBatch; import com.red.circle.other.infra.database.mongo.dto.team.TeamBillSettleResult; import com.red.circle.other.inner.model.dto.agency.agency.TeamRemark; import com.red.circle.other.inner.enums.team.TeamBillCycleStatus; import java.io.Serial; import java.io.Serializable; import java.sql.Timestamp; +import java.time.LocalDate; import java.util.List; import lombok.Data; import lombok.experimental.Accessors; @@ -54,6 +56,33 @@ public class TeamBillCycle implements Serializable { */ private TeamBillCycleStatus status; + // ===== 新增字段(用于批次模式)===== + + /** + * 是否启用批次模式. + * true: 月度账单模式(两次结算共享一个账期) + * false: 传统半月账单模式(每次结算独立账期) + * 默认:false(兼容历史数据) + */ + private Boolean batchMode; + + /** + * 结算批次号(仅批次模式下有效). + * 1 = 上半月(1-15号) + * 2 = 下半月(16-月末) + * null: 传统模式(兼容历史数据) + */ + private Integer settlementBatch; + + /** + * 月度账期(仅批次模式下有效). + * 格式:yyyyMM (如 202511) + * 用于前端按月聚合查询 + * null: 传统模式(兼容历史数据) + */ + private Integer monthlyBillBelong; + + /** * 结算结果. */ 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 5b07d785..d339e75c 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 @@ -540,4 +540,83 @@ public final class TeamBillCycleUtils { return strings[0] + "-" + strings[1]; } + /** + * 获取月度账期(用于批次模式) + * 返回格式:202511 + */ + public static Integer getMonthlyBillBelong() { + LocalDate now = ZonedDateTimeAsiaRiyadhUtils.now().toLocalDate(); + return Integer.parseInt(now.format(DateTimeFormatter.ofPattern("yyyyMM"))); + } + + /** + * 获取当前结算批次 + * 返回:1 = 上半月, 2 = 下半月 + */ + public static Integer getCurrentSettlementBatch() { + LocalDate now = ZonedDateTimeAsiaRiyadhUtils.now().toLocalDate(); + return now.getDayOfMonth() < 16 ? 1 : 2; + } + + /** + * 判断是否启用批次模式(通过区域配置) + */ + public static Boolean isBatchModeEnabled(String region) { + // 从配置中读取,或者硬编码 + // 例如:return "SA".equals(region); // 沙特地区启用批次模式 + return true; + } + + /** + * 从 billBelong 提取月份(兼容新旧格式) + * 20251101 -> 202511 + * 20251116 -> 202511 + */ + public static Integer extractMonth(Integer billBelong) { + if (billBelong == null) { + return null; + } + String billStr = String.valueOf(billBelong); + if (billStr.length() == 6) { + // 已经是月度格式 + return billBelong; + } + // 提取年月部分 + return Integer.parseInt(billStr.substring(0, 6)); + } + + /** + * 从 billBelong 提取批次号(兼容新旧格式) + * 20251101 -> 1 + * 20251116 -> 2 + */ + public static Integer extractBatch(Integer billBelong) { + if (billBelong == null) { + return null; + } + String billStr = String.valueOf(billBelong); + if (billStr.length() != 8) { + return null; + } + int day = Integer.parseInt(billStr.substring(6, 8)); + return day == 1 ? 1 : 2; + } + + /** + * 生成批次标题 + * 202511, 1 -> "2025年11月(上半月)" + * 202511, 2 -> "2025年11月(下半月)" + */ + public static String generateBatchTitle(Integer monthlyBillBelong, Integer batchNumber) { + if (monthlyBillBelong == null) { + return ""; + } + String billStr = String.valueOf(monthlyBillBelong); + int year = Integer.parseInt(billStr.substring(0, 4)); + int month = Integer.parseInt(billStr.substring(4, 6)); + + String batchDesc = batchNumber == 1 ? "上半月" : "下半月"; + return String.format("%d年%d月(%s)", year, month, batchDesc); + } + } diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/team/team/TeamMemberTargetService.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/team/team/TeamMemberTargetService.java index 28e5ce86..1698b217 100644 --- a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/team/team/TeamMemberTargetService.java +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/team/team/TeamMemberTargetService.java @@ -216,4 +216,36 @@ public interface TeamMemberTargetService { TeamSalaryStatisticsCO statisticsTeamSalaryByTeamIdAndBillBelong( Long teamId, Integer billBelong); + /** + * 按月查询团队成员目标(聚合半月数据). + * 用于前端按月展示,会自动聚合同一个月的多条记录 + * + * @param teamId 团队ID + * @param monthlyBillBelong 月度账期(202511) + * @return 该月所有成员目标列表(可能包含两个批次的数据) + */ + List listByMonthlyBillBelong(Long teamId, Integer monthlyBillBelong); + + /** + * 按月查询单个成员目标(聚合半月数据). + * 用于查询指定用户的月度数据 + * + * @param teamId 团队ID + * @param userId 用户ID + * @param monthlyBillBelong 月度账期(202511) + * @return 该月该成员的所有目标记录(通常1-2条) + */ + List listMemberMonthlyTarget(Long teamId, Long userId, Integer monthlyBillBelong); + + /** + * 按月统计成员目标(聚合后返回月度汇总). + * 用于前端展示月度累计数据,会自动合并同一个月的多条记录 + * + * @param teamId 团队ID + * @param userId 用户ID + * @param monthlyBillBelong 月度账期(202511) + * @return 月度汇总后的成员目标(dailyTargets 包含整月数据,settlementResult 为累计值) + */ + TeamMemberTarget aggregateMonthlyTarget(Long teamId, Long userId, Integer monthlyBillBelong); + } 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 f132ceeb..923eb81d 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 @@ -8,6 +8,7 @@ import com.red.circle.component.mongodb.config.MongoPrimaryService; import com.red.circle.framework.mybatis.constant.PageConstant; import com.red.circle.other.infra.database.mongo.dto.team.TeamBillSettleResult; import com.red.circle.other.infra.database.mongo.entity.team.team.TeamBillCycle; +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.inner.model.cmd.team.TeamBillCmd; import com.red.circle.other.inner.model.cmd.team.TeamBillCycleBackQryCmd; @@ -30,6 +31,7 @@ import java.util.Set; import java.util.function.Consumer; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; import org.springframework.data.domain.Sort; import org.springframework.data.mongodb.core.MongoTemplate; @@ -47,6 +49,7 @@ import static com.red.circle.other.infra.database.mongo.service.team.TeamBillCyc */ @Service @RequiredArgsConstructor +@Slf4j public class TeamBillCycleServiceImpl implements TeamBillCycleService { private final MongoTemplate mongoTemplate; @@ -220,27 +223,114 @@ public class TeamBillCycleServiceImpl implements TeamBillCycleService { } - @Override - public void createIfAbsent(TeamBillCmd param) { - if (existsPrimaryBillBelong(param.getTeamId())) { - return; + /** + * 创建账单(兼容批次模式) + */ + @Override + public void createIfAbsent(TeamBillCmd cmd) { + // 计算 billBelong(保持原有逻辑) + Integer billBelong = TeamBillCycleUtils.getCalcBillBelong(); + + // 检查是否已存在 + boolean existing = existsPrimaryBillBelong(cmd.getTeamId(), billBelong); + if (existing) { + return; + } + + // 创建账单 + TeamBillCycle bill = new TeamBillCycle() + .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); } - mongoTemplate.save(new TeamBillCycle() - .setId(IdWorkerUtils.getId()) - .setSysOrigin(param.getSysOrigin()) - .setTeamId(param.getTeamId()) - .setStatus(TeamBillCycleStatus.UNPAID) - .setRegion(param.getRegion()) - .setInternalRemarks(Lists.newArrayList()) - .setSettleResult(null) - .setBillBelong(getCalcBillBelong()) - .setBillTitle(getCalcBillBelong().toString()) - .setCreateTime(param.getOperationTime()) - .setUpdateTime(TimestampUtils.now()) - .setCreateUser(param.getOperationBackUser()) - .setUpdateUser(param.getOperationBackUser()) - ); - } + + private String generateBillTitle(Integer billBelong) { + String[] dateRange = TeamBillCycleUtils.parseBillBelongToDateRange(billBelong); + return dateRange[0] + " - " + dateRange[1]; + } + + /** + * 创建或更新账单批次 + * 关键逻辑: + * - 第1批次:创建新账单 + * - 第2批次:更新现有账单 + */ + /*public void createOrUpdateBatch(TeamBillCmd cmd) { + // 查找当前月是否已有账单 + TeamBillCycle existing = findByTeamAndBillBelong( + cmd.getTeamId(), + cmd.getBillBelong() + ); + + if (existing == null) { + // 第1批次:创建新账单 + TeamBillCycle newBill = new TeamBillCycle() + .setId(IdWorkerUtils.getId()) + .setSysOrigin(cmd.getSysOrigin()) + .setTeamId(cmd.getTeamId()) + .setRegion(cmd.getRegion()) + .setBillBelong(cmd.getBillBelong()) // 月度格式:202511 + .setBillTitle(parseBillTitle(cmd.getBillBelong())) // "2025年11月" + .setSettlementBatch(cmd.getSettlementBatch()) + .setBatchStartDate(cmd.getBatchStartDate()) + .setBatchEndDate(cmd.getBatchEndDate()) + .setStatus(TeamBillCycleStatus.PAY_OUT) + .setCreateTime(cmd.getOperationTime()) + .setCreateUser(cmd.getOperationBackUser()); + + save(newBill); + log.info("创建账单:billBelong={}, batch={}", + cmd.getBillBelong(), cmd.getSettlementBatch()); + } else { + // 第2批次:更新现有账单 + existing.setSettlementBatch(cmd.getSettlementBatch()); + existing.setBatchStartDate(cmd.getBatchStartDate()); + existing.setBatchEndDate(cmd.getBatchEndDate()); + existing.setStatus(TeamBillCycleStatus.PAY_OUT); + existing.setUpdateTime(cmd.getOperationTime()); + existing.setUpdateUser(cmd.getOperationBackUser()); + + updateById(existing); + log.info("更新账单:billBelong={}, batch={}", + cmd.getBillBelong(), cmd.getSettlementBatch()); + } + } + + *//** + * 解析账单标题 + *//* + private String parseBillTitle(Integer billBelong) { + String billStr = String.valueOf(billBelong); + int year = Integer.parseInt(billStr.substring(0, 4)); + int month = Integer.parseInt(billStr.substring(4, 6)); + return String.format("%d年%d月", year, month); + }*/ @Override public List listLastMonthByTeamIds(Set teamIds) { @@ -297,11 +387,11 @@ public class TeamBillCycleServiceImpl implements TeamBillCycleService { return cycleList.get(0); } - private boolean existsPrimaryBillBelong(Long teamId) { + private boolean existsPrimaryBillBelong(Long teamId, Integer billBelong) { return mongoPrimaryService.exists("team_bill_cycle", Filters.and( Filters.eq("teamId", teamId), - Filters.eq("billBelong", getCalcBillBelong()) + Filters.eq("billBelong", billBelong) ) ); } 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 7e45ec5d..60c09ba0 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 @@ -659,4 +659,175 @@ public class TeamMemberTargetServiceImpl implements TeamMemberTargetService { return statistics; } + @Override + public List listByMonthlyBillBelong(Long teamId, Integer monthlyBillBelong) { + // 构造查询条件:billBelong 前6位匹配月度账期 + // 例如:monthlyBillBelong = 202511,匹配 20251101 和 20251116 + Criteria criteria = Criteria.where("teamId").is(teamId) + .and("history").is(Boolean.FALSE) + .and("billBelong").regex("^" + monthlyBillBelong); + + return mongoTemplate.find(Query.query(criteria) + .with(Sort.by(Order.asc("billBelong"))), + TeamMemberTarget.class); + } + + @Override + public List listMemberMonthlyTarget(Long teamId, Long userId, Integer monthlyBillBelong) { + // 查询指定用户在指定月份的所有目标记录 + Criteria criteria = Criteria.where("teamId").is(teamId) + .and("userId").is(userId) + .and("history").is(Boolean.FALSE) + .and("billBelong").regex("^" + monthlyBillBelong); + + return mongoTemplate.find(Query.query(criteria) + .with(Sort.by(Order.asc("billBelong"))), + TeamMemberTarget.class); + } + + @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; + } + + // 2. 如果只有一条记录,直接返回 + if (monthlyTargets.size() == 1) { + return monthlyTargets.get(0); + } + + // 3. 多条记录时,聚合数据 + TeamMemberTarget aggregated = new TeamMemberTarget(); + TeamMemberTarget first = monthlyTargets.get(0); + + // 复制基础信息(使用第一条记录的信息) + aggregated.setId(first.getId()); + aggregated.setTimeId(first.getTimeId()); + aggregated.setSysOrigin(first.getSysOrigin()); + aggregated.setTeamId(first.getTeamId()); + aggregated.setUserId(first.getUserId()); + aggregated.setIsOwn(first.getIsOwn()); + aggregated.setBillBelong(monthlyBillBelong); // 设置为月度格式 + aggregated.setBillTitle(generateMonthlyBillTitle(monthlyBillBelong)); + aggregated.setHistory(Boolean.FALSE); + aggregated.setCreateTime(first.getCreateTime()); + 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); + + // 5. 聚合结算结果(如果有多条,取总和) + List settlementResults = monthlyTargets.stream() + .map(TeamMemberTarget::getSettlementResult) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + if (CollectionUtils.isNotEmpty(settlementResults)) { + TeamMemberTargetSettlementResult aggregatedResult = new TeamMemberTargetSettlementResult(); + + // 取最高等级 + aggregatedResult.setLevel(settlementResults.stream() + .map(TeamMemberTargetSettlementResult::getLevel) + .filter(Objects::nonNull) + .max(Integer::compareTo) + .orElse(null)); + + // 判断是否满足等级(都满足才算满足) + aggregatedResult.setSatisfyLevel(settlementResults.stream() + .allMatch(r -> Boolean.TRUE.equals(r.getSatisfyLevel()))); + + // 累加金额 + aggregatedResult.setMemberSalary(settlementResults.stream() + .map(TeamMemberTargetSettlementResult::getMemberSalary) + .filter(Objects::nonNull) + .reduce(BigDecimal.ZERO, BigDecimal::add)); + + aggregatedResult.setOwnSalary(settlementResults.stream() + .map(TeamMemberTargetSettlementResult::getOwnSalary) + .filter(Objects::nonNull) + .reduce(BigDecimal.ZERO, BigDecimal::add)); + + aggregatedResult.setTotalSalary(settlementResults.stream() + .map(TeamMemberTargetSettlementResult::getTotalSalary) + .filter(Objects::nonNull) + .reduce(BigDecimal.ZERO, BigDecimal::add)); + + // 累加业绩数据(这些字段已经在 dailyTargets 中,这里主要用于快速查看) + aggregatedResult.setAcceptGiftValue(aggregated.sumAcceptGiftValue()); + aggregatedResult.setGiveGiftValue(aggregated.sumGiveGiftValue()); + aggregatedResult.setRoomValue(aggregated.sumRoomValue()); + aggregatedResult.setOnlineTime(aggregated.sumAllTargetTime()); + + // 道具奖励(取任一非空值) + aggregatedResult.setPropsRewards(settlementResults.stream() + .map(TeamMemberTargetSettlementResult::getPropsRewards) + .filter(Objects::nonNull) + .findFirst() + .orElse(null)); + + // 道具领取状态(任一已领取则算已领取) + aggregatedResult.setPropsReceived(settlementResults.stream() + .anyMatch(r -> Boolean.TRUE.equals(r.getPropsReceived()))); + + aggregated.setSettlementResult(aggregatedResult); + } + + // 6. 状态(取最终状态,按优先级:CREDITED > UNDONE > AUTO > NOT_RECORDED) + TeamMemberTargetStatus finalStatus = monthlyTargets.stream() + .map(TeamMemberTarget::getStatus) + .filter(Objects::nonNull) + .max((s1, s2) -> compareStatus(s1, s2)) + .orElse(TeamMemberTargetStatus.NOT_RECORDED); + aggregated.setStatus(finalStatus); + + // 7. 合并备注 + List allRemarks = monthlyTargets.stream() + .map(TeamMemberTarget::getRemarks) + .filter(CollectionUtils::isNotEmpty) + .flatMap(List::stream) + .collect(Collectors.toList()); + if (CollectionUtils.isNotEmpty(allRemarks)) { + aggregated.setRemarks(allRemarks); + } + + return aggregated; + } + + /** + * 生成月度账单标题 + * 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); + } + + /** + * 比较状态优先级 + * 返回值: 1 表示 s1 优先级高, -1 表示 s2 优先级高, 0 表示相等 + */ + private int compareStatus(TeamMemberTargetStatus s1, TeamMemberTargetStatus s2) { + // 定义优先级顺序:CREDITED > UNDONE > AUTO > NOT_RECORDED + Map priority = Map.of( + TeamMemberTargetStatus.CREDITED, 4, + TeamMemberTargetStatus.UNDONE, 3, + TeamMemberTargetStatus.AUTO, 2, + TeamMemberTargetStatus.NOT_RECORDED, 1 + ); + + int p1 = priority.getOrDefault(s1, 0); + int p2 = priority.getOrDefault(s2, 0); + + return Integer.compare(p1, p2); + } + } diff --git a/rc-service/rc-service-other/other-start/src/test/java/TeamMonthBillTest.java b/rc-service/rc-service-other/other-start/src/test/java/TeamMonthBillTest.java index fa8122bb..4a35a4e8 100644 --- a/rc-service/rc-service-other/other-start/src/test/java/TeamMonthBillTest.java +++ b/rc-service/rc-service-other/other-start/src/test/java/TeamMonthBillTest.java @@ -68,19 +68,7 @@ public class TeamMonthBillTest { */ @Test public void processTeamBillSettle() { - String json = " [\n" + - "\"1970912960966479874\",\n" + - "\"1965724731682754561\",\n" + - "\"1982985325435207682\",\n" + - "\"1978213327176196097\",\n" + - "\"1981316214519701506\",\n" + - "\"1980616977268658178\",\n" + - "\"1982190084063019010\",\n" + - "\"1982431621845925889\",\n" + - "\"1982372655623356418\",\n" + - "\"1980617113277353986\",\n" + - "\"1978447372520308737\"\n" + - " ]"; + String json = "['1984235784268349441']"; List teamList = JSON.parseArray(json, String.class); for (String teamId : teamList) { Long id = Long.parseLong(teamId);