From 0a21af783a08b5763faa3e4110e8030841161fe3 Mon Sep 17 00:00:00 2001 From: tianfeng <769204422@qq.com> Date: Fri, 12 Dec 2025 15:51:18 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9Eadmin=E5=B7=A5=E8=B5=84?= =?UTF-8?q?=E7=BB=93=E7=AE=97=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../scheduler/AdminSalarySettlementTask.java | 89 ++++ .../AdminSalarySettlementServiceImpl.java | 408 ++++++++++++++++++ .../clientobject/AdminSalarySettlementCO.java | 95 ++++ .../service/AdminSalarySettlementService.java | 30 ++ .../bd/AdminSalarySettlementRecord.java | 192 +++++++++ .../service/bd/AdminSalaryPolicyService.java | 29 ++ .../AdminSalarySettlementRecordService.java | 38 ++ .../bd/impl/AdminSalaryPolicyServiceImpl.java | 51 +++ ...dminSalarySettlementRecordServiceImpl.java | 64 +++ .../wallet/domain/salary/SalaryEvent.java | 2 + .../wallet/domain/salary/SalaryType.java | 2 +- 11 files changed, 999 insertions(+), 1 deletion(-) create mode 100644 rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/scheduler/AdminSalarySettlementTask.java create mode 100644 rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/service/AdminSalarySettlementServiceImpl.java create mode 100644 rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/AdminSalarySettlementCO.java create mode 100644 rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/service/AdminSalarySettlementService.java create mode 100644 rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/entity/bd/AdminSalarySettlementRecord.java create mode 100644 rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/bd/AdminSalaryPolicyService.java create mode 100644 rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/bd/AdminSalarySettlementRecordService.java create mode 100644 rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/bd/impl/AdminSalaryPolicyServiceImpl.java create mode 100644 rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/bd/impl/AdminSalarySettlementRecordServiceImpl.java 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 new file mode 100644 index 00000000..c2285e67 --- /dev/null +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/scheduler/AdminSalarySettlementTask.java @@ -0,0 +1,89 @@ +package com.red.circle.other.app.scheduler; + +import com.red.circle.component.redis.annotation.TaskCacheLock; +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结算之后) + * + * @author AI Assistant + * @since 2025-12-13 + */ +@Slf4j +@Component +@RequiredArgsConstructor +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() { + long startTime = System.currentTimeMillis(); + log.warn("========== 管理员工资结算定时任务开始 =========="); + + try { + // 获取当前日期的账期 + Integer currentBillBelong = TeamBillCycleUtils.getCalcBillBelong(); + + // 计算需要结算的账期(往前推一个账期) + Integer billBelong = calculateSettlementBillBelong(currentBillBelong); + String billTitle = TeamBillCycleUtils.parseBillBelongToDateRangeStr(billBelong); + + log.warn("结算周期:{} - {}", billBelong, billTitle); + + // 执行管理员工资结算 + adminSalarySettlementService.processAllAdminSettlement(billBelong, billTitle); + + log.warn("========== 管理员工资结算定时任务完成,耗时:{} ms ==========", + System.currentTimeMillis() - startTime); + } catch (Exception e) { + log.error("========== 管理员工资结算定时任务异常 ==========", e); + } + } + + /** + * 计算需要结算的账期. + * 规则: + * - 如果当前账期是本月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 processAdminSalarySettlementTest(Integer billBelong) { + String billTitle = TeamBillCycleUtils.parseBillBelongToDateRangeStr(billBelong); + adminSalarySettlementService.processAllAdminSettlement(billBelong, billTitle); + } +} diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/service/AdminSalarySettlementServiceImpl.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/service/AdminSalarySettlementServiceImpl.java new file mode 100644 index 00000000..351d8ec5 --- /dev/null +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/service/AdminSalarySettlementServiceImpl.java @@ -0,0 +1,408 @@ +package com.red.circle.other.app.service; + +import com.red.circle.common.business.core.enums.SysOriginPlatformEnum; +import com.red.circle.common.business.dto.cmd.AppExtCommand; +import com.red.circle.other.app.command.bd.query.BdTeamSummaryQryExe; +import com.red.circle.other.app.dto.clientobject.AdminSalarySettlementCO; +import com.red.circle.other.app.dto.clientobject.BdTeamSummaryCO; +import com.red.circle.other.app.dto.h5.RechargeSummaryVO; +import com.red.circle.other.app.service.recharge.RechargeUserService; +import com.red.circle.other.infra.database.mongo.entity.bd.AdminSalaryPolicy; +import com.red.circle.other.infra.database.mongo.entity.bd.AdminSalarySettlementRecord; +import com.red.circle.other.infra.database.mongo.entity.bd.BdSalarySettlementRecord; +import com.red.circle.other.infra.database.mongo.service.bd.AdminSalaryPolicyService; +import com.red.circle.other.infra.database.mongo.service.bd.AdminSalarySettlementRecordService; +import com.red.circle.other.infra.database.mongo.service.bd.BdSalarySettlementRecordService; +import com.red.circle.other.infra.enums.AdminPolicyType; +import com.red.circle.other.infra.enums.BdSettlementStatus; +import com.red.circle.tool.core.collection.CollectionUtils; +import com.red.circle.tool.core.sequence.IdWorkerUtils; +import com.red.circle.wallet.inner.endpoint.salary.UserSalaryAccountClient; +import com.red.circle.wallet.inner.model.cmd.SalaryIssueInnerCmd; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.sql.Timestamp; +import java.util.List; + +/** + * 管理员工资结算服务实现. + * + * @author AI Assistant + * @since 2025-12-13 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class AdminSalarySettlementServiceImpl implements AdminSalarySettlementService { + + private final AdminSalaryPolicyService adminSalaryPolicyService; + private final AdminSalarySettlementRecordService adminSalarySettlementRecordService; + private final BdSalarySettlementRecordService bdSalarySettlementRecordService; + private final BdTeamSummaryQryExe bdTeamSummaryQryExe; + private final RechargeUserService rechargeUserService; + private final UserSalaryAccountClient userSalaryAccountClient; + + @Override + public void processAllAdminSettlement(Integer billBelong, String billTitle) { + log.warn("开始处理所有管理员工资结算,结算周期:{}", billBelong); + + // TODO: 查询所有管理员列表 + List adminUserIds = List.of(); // 需要从数据库查询所有管理员ID + + if (CollectionUtils.isEmpty(adminUserIds)) { + log.warn("没有找到任何管理员数据"); + return; + } + + log.warn("共找到 {} 个管理员", adminUserIds.size()); + + int successCount = 0; + int failCount = 0; + + for (Long adminUserId : adminUserIds) { + try { + processSingleAdminSettlement(adminUserId, billBelong, billTitle); + successCount++; + } catch (Exception e) { + log.error("管理员 {} 工资结算失败", adminUserId, e); + failCount++; + } + } + + log.warn("管理员工资结算完成,成功:{},失败:{}", successCount, failCount); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public AdminSalarySettlementCO processSingleAdminSettlement(Long adminUserId, Integer billBelong, String billTitle) { + log.info("开始处理管理员 {} 的工资结算", adminUserId); + + // 1. 生成幂等业务号 + String bizNo = generateBizNo(adminUserId, billBelong); + + // 2. 幂等性检查 + if (adminSalarySettlementRecordService.existsByBizNo(bizNo)) { + log.warn("管理员 {} 在周期 {} 已经结算过,跳过", adminUserId, billBelong); + AdminSalarySettlementRecord existingRecord = adminSalarySettlementRecordService.getByBizNo(bizNo); + return convertToCO(existingRecord); + } + + // 3. 初始化结算记录 + AdminSalarySettlementRecord record = new AdminSalarySettlementRecord(); + record.setId(IdWorkerUtils.getIdStr()); + record.setTimeId(IdWorkerUtils.getId()); + record.setSysOrigin(SysOriginPlatformEnum.LIKEI.name()); + record.setAdminUserId(adminUserId); + record.setBillBelong(billBelong); + record.setBillTitle(billTitle); + record.setBizNo(bizNo); + record.setStatus(BdSettlementStatus.PENDING); + + // 4. 计算各个政策的工资 + try { + // 4.1 RECHARGE_AGENT 政策 + calculateRechargeAgentPolicy(adminUserId, record); + + // 4.2 BD_LEADER 政策 + calculateBdLeaderPolicy(adminUserId, billBelong, record); + + // 4.3 BD_TEAM_SALARY 和 BD_TEAM_RECHARGE 政策(取最高值) + calculateBdTeamPolicies(adminUserId, billBelong, record); + + // 4.4 计算总工资 + BigDecimal totalSalary = BigDecimal.ZERO; + if (record.getRechargeAgentSalary() != null) { + totalSalary = totalSalary.add(record.getRechargeAgentSalary()); + } + if (record.getBdLeaderSalary() != null) { + totalSalary = totalSalary.add(record.getBdLeaderSalary()); + } + if (record.getBdPolicyFinalSalary() != null) { + totalSalary = totalSalary.add(record.getBdPolicyFinalSalary()); + } + record.setTotalSalary(totalSalary.setScale(2, RoundingMode.DOWN)); + + } catch (Exception e) { + log.error("管理员 {} 工资计算失败", adminUserId, e); + record.setStatus(BdSettlementStatus.FAILED); + record.setFailureReason(e.getMessage()); + adminSalarySettlementRecordService.create(record); + throw e; + } + + // 5. 保存结算记录 + adminSalarySettlementRecordService.create(record); + + // 6. 如果工资大于 0,则发放工资 + if (record.getTotalSalary().compareTo(BigDecimal.ZERO) > 0) { + try { + issueSalary(adminUserId, record); + log.info("管理员 {} 工资发放成功,金额:{} USD", adminUserId, record.getTotalSalary()); + } catch (Exception e) { + log.error("管理员 {} 工资发放失败", adminUserId, e); + adminSalarySettlementRecordService.updateStatus(record.getId(), BdSettlementStatus.FAILED, e.getMessage()); + record.setStatus(BdSettlementStatus.FAILED); + record.setFailureReason(e.getMessage()); + throw e; + } + } else { + log.info("管理员 {} 本周期未达到结算标准,无工资发放", adminUserId); + } + + // 7. 更新状态为成功 + adminSalarySettlementRecordService.updateStatus(record.getId(), BdSettlementStatus.SUCCESS, null); + record.setStatus(BdSettlementStatus.SUCCESS); + + return convertToCO(record); + } + + /** + * 计算 RECHARGE_AGENT 政策工资. + */ + private void calculateRechargeAgentPolicy(Long adminUserId, AdminSalarySettlementRecord record) { + // 构建命令对象 + AppExtCommand cmd = new AppExtCommand(); + cmd.setReqUserId(adminUserId); + + // 获取充值汇总数据 + RechargeSummaryVO rechargeSummary = rechargeUserService.getRechargeSummary(cmd); + + if (rechargeSummary == null || rechargeSummary.getCurrentMonthRecharge() == null) { + record.setRechargeAgentAmount(BigDecimal.ZERO); + record.setRechargeAgentSalary(BigDecimal.ZERO); + return; + } + + BigDecimal rechargeAmount = rechargeSummary.getCurrentMonthRecharge(); + record.setRechargeAgentAmount(rechargeAmount.setScale(2, RoundingMode.DOWN)); + + // 查询 RECHARGE_AGENT 政策 + List policies = adminSalaryPolicyService.listEnabledByType( + AdminPolicyType.RECHARGE_AGENT.getCode()); + + if (CollectionUtils.isEmpty(policies)) { + record.setRechargeAgentSalary(BigDecimal.ZERO); + return; + } + + // 按充值金额倒序匹配 + policies.sort((p1, p2) -> p2.getRequiredCoinSellerRecharge().compareTo(p1.getRequiredCoinSellerRecharge())); + + for (AdminSalaryPolicy policy : policies) { + if (rechargeAmount.compareTo(policy.getRequiredCoinSellerRecharge()) >= 0) { + // 计算工资:充值金额 × 百分比 + BigDecimal salary = rechargeAmount.multiply(policy.getAdminPercentage()) + .divide(new BigDecimal("100"), 2, RoundingMode.DOWN); + record.setRechargeAgentSalary(salary); + record.setRechargeAgentPercentage(policy.getAdminPercentage()); + return; + } + } + + record.setRechargeAgentSalary(BigDecimal.ZERO); + } + + /** + * 计算 BD_LEADER 政策工资. + */ + private void calculateBdLeaderPolicy(Long adminUserId, Integer billBelong, AdminSalarySettlementRecord record) { + // 查询 BD Leader 团队数据 + BdTeamSummaryCO teamSummary = bdTeamSummaryQryExe.execute(adminUserId); + + if (teamSummary == null || teamSummary.getBdLeaderTeams() == null) { + record.setBdLeaderTeamSalary(BigDecimal.ZERO); + record.setBdLeaderSalary(BigDecimal.ZERO); + return; + } + + BigDecimal teamSalary = teamSummary.getBdLeaderTeams().getTotalIncome(); + if (teamSalary == null) { + teamSalary = BigDecimal.ZERO; + } + record.setBdLeaderTeamSalary(teamSalary.setScale(2, RoundingMode.DOWN)); + + // 查询 BD_LEADER 政策 + List policies = adminSalaryPolicyService.listEnabledByType( + AdminPolicyType.BD_LEADER.getCode()); + + if (CollectionUtils.isEmpty(policies)) { + record.setBdLeaderSalary(BigDecimal.ZERO); + return; + } + + // 按等级倒序匹配 + policies.sort((p1, p2) -> p2.getLevel().compareTo(p1.getLevel())); + + for (AdminSalaryPolicy policy : policies) { + if (teamSalary.compareTo(policy.getRequiredTeamSalary()) >= 0) { + // 工资 = Basic Salary + Reward Amount + BigDecimal salary = policy.getBasicSalary(); + if (policy.getRewardAmount() != null) { + salary = salary.add(policy.getRewardAmount()); + } + record.setBdLeaderSalary(salary.setScale(2, RoundingMode.DOWN)); + record.setBdLeaderHitLevel(policy.getLevel()); + record.setBdLeaderBasicSalary(policy.getBasicSalary()); + record.setBdLeaderRewardAmount(policy.getRewardAmount()); + return; + } + } + + record.setBdLeaderSalary(BigDecimal.ZERO); + } + + /** + * 计算 BD_TEAM_SALARY 和 BD_TEAM_RECHARGE 政策工资(取最高值). + */ + private void calculateBdTeamPolicies(Long adminUserId, Integer billBelong, AdminSalarySettlementRecord record) { + // 从 BD 结算记录查询数据 + BdSalarySettlementRecord bdRecord = bdSalarySettlementRecordService.getByUserIdAndBillBelong( + adminUserId, billBelong); + + if (bdRecord == null) { + record.setBdTeamSalaryAmount(BigDecimal.ZERO); + record.setBdTeamSalarySalary(BigDecimal.ZERO); + record.setBdTeamRechargeAmount(BigDecimal.ZERO); + record.setBdTeamRechargeSalary(BigDecimal.ZERO); + record.setBdPolicyFinalSalary(BigDecimal.ZERO); + return; + } + + // 计算 BD_TEAM_SALARY 政策 + BigDecimal teamSalaryAmount = bdRecord.getTeamSalaryAmount() != null ? + bdRecord.getTeamSalaryAmount() : BigDecimal.ZERO; + record.setBdTeamSalaryAmount(teamSalaryAmount.setScale(2, RoundingMode.DOWN)); + + BigDecimal teamSalarySalary = calculateBdTeamSalaryPolicy(teamSalaryAmount, record); + + // 计算 BD_TEAM_RECHARGE 政策 + BigDecimal teamRechargeAmount = bdRecord.getTeamRechargeAmount() != null ? + bdRecord.getTeamRechargeAmount() : BigDecimal.ZERO; + record.setBdTeamRechargeAmount(teamRechargeAmount.setScale(2, RoundingMode.DOWN)); + + BigDecimal teamRechargeSalary = calculateBdTeamRechargePolicy(teamRechargeAmount, record); + + // 取最高值 + if (teamSalarySalary.compareTo(teamRechargeSalary) >= 0) { + record.setBdPolicyFinalSalary(teamSalarySalary); + record.setBdPolicyHitType(AdminPolicyType.BD_TEAM_SALARY.getCode()); + } else { + record.setBdPolicyFinalSalary(teamRechargeSalary); + record.setBdPolicyHitType(AdminPolicyType.BD_TEAM_RECHARGE.getCode()); + } + } + + /** + * 计算 BD_TEAM_SALARY 政策. + */ + private BigDecimal calculateBdTeamSalaryPolicy(BigDecimal teamSalaryAmount, AdminSalarySettlementRecord record) { + List policies = adminSalaryPolicyService.listEnabledByType( + AdminPolicyType.BD_TEAM_SALARY.getCode()); + + if (CollectionUtils.isEmpty(policies)) { + record.setBdTeamSalarySalary(BigDecimal.ZERO); + return BigDecimal.ZERO; + } + + // 按等级倒序匹配 + policies.sort((p1, p2) -> p2.getLevel().compareTo(p1.getLevel())); + + for (AdminSalaryPolicy policy : policies) { + if (teamSalaryAmount.compareTo(policy.getRequiredTeamAmount()) >= 0) { + // 工资 = 团队工资 × 提成比例 + BigDecimal salary = teamSalaryAmount.multiply(policy.getCommissionRate()) + .divide(new BigDecimal("100"), 2, RoundingMode.DOWN); + record.setBdTeamSalarySalary(salary); + record.setBdTeamSalaryHitLevel(policy.getLevel()); + record.setBdTeamSalaryCommissionRate(policy.getCommissionRate()); + return salary; + } + } + + record.setBdTeamSalarySalary(BigDecimal.ZERO); + return BigDecimal.ZERO; + } + + /** + * 计算 BD_TEAM_RECHARGE 政策. + */ + private BigDecimal calculateBdTeamRechargePolicy(BigDecimal teamRechargeAmount, AdminSalarySettlementRecord record) { + List policies = adminSalaryPolicyService.listEnabledByType( + AdminPolicyType.BD_TEAM_RECHARGE.getCode()); + + if (CollectionUtils.isEmpty(policies)) { + record.setBdTeamRechargeSalary(BigDecimal.ZERO); + return BigDecimal.ZERO; + } + + // 按等级倒序匹配 + policies.sort((p1, p2) -> p2.getLevel().compareTo(p1.getLevel())); + + for (AdminSalaryPolicy policy : policies) { + if (teamRechargeAmount.compareTo(policy.getRequiredTeamAmount()) >= 0) { + // 工资 = 团队充值 × 提成比例 + BigDecimal salary = teamRechargeAmount.multiply(policy.getCommissionRate()) + .divide(new BigDecimal("100"), 2, RoundingMode.DOWN); + record.setBdTeamRechargeSalary(salary); + record.setBdTeamRechargeHitLevel(policy.getLevel()); + record.setBdTeamRechargeCommissionRate(policy.getCommissionRate()); + return salary; + } + } + + record.setBdTeamRechargeSalary(BigDecimal.ZERO); + return BigDecimal.ZERO; + } + + /** + * 发放工资到工资账户. + */ + private void issueSalary(Long adminUserId, AdminSalarySettlementRecord record) { + SalaryIssueInnerCmd innerCmd = new SalaryIssueInnerCmd(); + innerCmd.setBillBelong(record.getBillBelong()); + innerCmd.setUserId(adminUserId); + innerCmd.setSysOrigin(record.getSysOrigin()); + innerCmd.setSalaryType("ADMIN_SALARY"); + innerCmd.setEventDesc("SALARY_ADMIN"); + innerCmd.setAmount(record.getTotalSalary()); + innerCmd.setTrackId(record.getId()); + innerCmd.setBdId(adminUserId); + userSalaryAccountClient.issueSalary(innerCmd); + } + + /** + * 生成业务号(幂等性保证). + */ + private String generateBizNo(Long adminUserId, Integer billBelong) { + return String.format("ADMIN_SALARY_%d_%d", adminUserId, billBelong); + } + + /** + * 转换为 CO 对象. + */ + private AdminSalarySettlementCO convertToCO(AdminSalarySettlementRecord record) { + AdminSalarySettlementCO co = new AdminSalarySettlementCO(); + co.setAdminUserId(record.getAdminUserId()); + co.setBillBelong(record.getBillBelong()); + co.setBillTitle(record.getBillTitle()); + co.setRechargeAgentAmount(record.getRechargeAgentAmount()); + co.setRechargeAgentSalary(record.getRechargeAgentSalary()); + co.setBdLeaderTeamSalary(record.getBdLeaderTeamSalary()); + co.setBdLeaderSalary(record.getBdLeaderSalary()); + co.setBdTeamSalaryAmount(record.getBdTeamSalaryAmount()); + co.setBdTeamSalarySalary(record.getBdTeamSalarySalary()); + co.setBdTeamRechargeAmount(record.getBdTeamRechargeAmount()); + co.setBdTeamRechargeSalary(record.getBdTeamRechargeSalary()); + co.setBdPolicyFinalSalary(record.getBdPolicyFinalSalary()); + co.setBdPolicyHitType(record.getBdPolicyHitType()); + co.setTotalSalary(record.getTotalSalary()); + co.setStatus(record.getStatus() != null ? record.getStatus().name() : null); + co.setFailureReason(record.getFailureReason()); + return co; + } +} diff --git a/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/AdminSalarySettlementCO.java b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/AdminSalarySettlementCO.java new file mode 100644 index 00000000..06d4158d --- /dev/null +++ b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/AdminSalarySettlementCO.java @@ -0,0 +1,95 @@ +package com.red.circle.other.app.dto.clientobject; + +import lombok.Data; + +import java.math.BigDecimal; + +/** + * 管理员工资结算结果. + * + * @author AI Assistant + * @since 2025-12-13 + */ +@Data +public class AdminSalarySettlementCO { + + /** + * 管理员用户 ID. + */ + private Long adminUserId; + + /** + * 账期. + */ + private Integer billBelong; + + /** + * 账期标题. + */ + private String billTitle; + + /** + * 充值代理充值金额. + */ + private BigDecimal rechargeAgentAmount; + + /** + * RECHARGE_AGENT 政策工资. + */ + private BigDecimal rechargeAgentSalary; + + /** + * BD Leader 团队工资总和. + */ + private BigDecimal bdLeaderTeamSalary; + + /** + * BD_LEADER 政策工资. + */ + private BigDecimal bdLeaderSalary; + + /** + * BD 团队工资总和. + */ + private BigDecimal bdTeamSalaryAmount; + + /** + * BD_TEAM_SALARY 政策工资. + */ + private BigDecimal bdTeamSalarySalary; + + /** + * BD 团队充值总和. + */ + private BigDecimal bdTeamRechargeAmount; + + /** + * BD_TEAM_RECHARGE 政策工资. + */ + private BigDecimal bdTeamRechargeSalary; + + /** + * BD 政策最终工资. + */ + private BigDecimal bdPolicyFinalSalary; + + /** + * 命中的 BD 政策类型. + */ + private String bdPolicyHitType; + + /** + * 管理员总工资. + */ + private BigDecimal totalSalary; + + /** + * 结算状态. + */ + private String status; + + /** + * 失败原因. + */ + private String failureReason; +} diff --git a/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/service/AdminSalarySettlementService.java b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/service/AdminSalarySettlementService.java new file mode 100644 index 00000000..b2ad4ff0 --- /dev/null +++ b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/service/AdminSalarySettlementService.java @@ -0,0 +1,30 @@ +package com.red.circle.other.app.service; + +import com.red.circle.other.app.dto.clientobject.AdminSalarySettlementCO; + +/** + * 管理员工资结算服务. + * + * @author AI Assistant + * @since 2025-12-13 + */ +public interface AdminSalarySettlementService { + + /** + * 处理所有管理员的工资结算. + * + * @param billBelong 结算周期 + * @param billTitle 结算周期标题 + */ + void processAllAdminSettlement(Integer billBelong, String billTitle); + + /** + * 处理单个管理员的工资结算. + * + * @param adminUserId 管理员用户 ID + * @param billBelong 结算周期 + * @param billTitle 结算周期标题 + * @return 结算结果 + */ + AdminSalarySettlementCO processSingleAdminSettlement(Long adminUserId, Integer billBelong, String billTitle); +} diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/entity/bd/AdminSalarySettlementRecord.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/entity/bd/AdminSalarySettlementRecord.java new file mode 100644 index 00000000..2d81d74a --- /dev/null +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/entity/bd/AdminSalarySettlementRecord.java @@ -0,0 +1,192 @@ +package com.red.circle.other.infra.database.mongo.entity.bd; + +import com.red.circle.other.infra.enums.BdSettlementStatus; +import lombok.Data; +import lombok.experimental.Accessors; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.index.Indexed; +import org.springframework.data.mongodb.core.mapping.Document; + +import java.io.Serial; +import java.io.Serializable; +import java.math.BigDecimal; +import java.sql.Timestamp; + +/** + * 管理员工资结算记录. + * + * @author AI Assistant + * @since 2025-12-13 + */ +@Data +@Accessors(chain = true) +@Document("admin_salary_settlement_record") +public class AdminSalarySettlementRecord implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * ID. + */ + @Id + private String id; + + /** + * 时间ID(用于排序). + */ + private Long timeId; + + /** + * 系统来源. + */ + private String sysOrigin; + + /** + * 管理员用户 ID. + */ + @Indexed + private Long adminUserId; + + /** + * 账期(yyyyMMdd 格式,如 20250101). + */ + @Indexed + private Integer billBelong; + + /** + * 账期标题(如 "2025-01-01 ~ 2025-01-15"). + */ + private String billTitle; + + /** + * 业务号(幂等性保证). + */ + @Indexed(unique = true) + private String bizNo; + + // ==================== RECHARGE_AGENT 政策数据 ==================== + + /** + * 充值代理充值金额(美元). + */ + private BigDecimal rechargeAgentAmount; + + /** + * RECHARGE_AGENT 政策计算工资. + */ + private BigDecimal rechargeAgentSalary; + + /** + * RECHARGE_AGENT 命中的百分比. + */ + private BigDecimal rechargeAgentPercentage; + + // ==================== BD_LEADER 政策数据 ==================== + + /** + * BD Leader 团队工资总和(美元). + */ + private BigDecimal bdLeaderTeamSalary; + + /** + * BD Leader 政策计算工资. + */ + private BigDecimal bdLeaderSalary; + + /** + * BD Leader 命中的等级. + */ + private Integer bdLeaderHitLevel; + + /** + * BD Leader Basic Salary. + */ + private BigDecimal bdLeaderBasicSalary; + + /** + * BD Leader Reward Amount. + */ + private BigDecimal bdLeaderRewardAmount; + + // ==================== BD_TEAM_SALARY 政策数据 ==================== + + /** + * BD 团队工资总和(美元). + */ + private BigDecimal bdTeamSalaryAmount; + + /** + * BD_TEAM_SALARY 政策计算工资. + */ + private BigDecimal bdTeamSalarySalary; + + /** + * BD_TEAM_SALARY 命中的等级. + */ + private Integer bdTeamSalaryHitLevel; + + /** + * BD_TEAM_SALARY 提成比例. + */ + private BigDecimal bdTeamSalaryCommissionRate; + + // ==================== BD_TEAM_RECHARGE 政策数据 ==================== + + /** + * BD 团队充值总和(美元). + */ + private BigDecimal bdTeamRechargeAmount; + + /** + * BD_TEAM_RECHARGE 政策计算工资. + */ + private BigDecimal bdTeamRechargeSalary; + + /** + * BD_TEAM_RECHARGE 命中的等级. + */ + private Integer bdTeamRechargeHitLevel; + + /** + * BD_TEAM_RECHARGE 提成比例. + */ + private BigDecimal bdTeamRechargeCommissionRate; + + // ==================== 最终结算结果 ==================== + + /** + * BD 政策最终工资(max(BD_TEAM_SALARY, BD_TEAM_RECHARGE)). + */ + private BigDecimal bdPolicyFinalSalary; + + /** + * 命中的 BD 政策类型. + */ + private String bdPolicyHitType; + + /** + * 管理员总工资. + */ + private BigDecimal totalSalary; + + /** + * 结算状态. + */ + private BdSettlementStatus status; + + /** + * 失败原因. + */ + private String failureReason; + + /** + * 创建时间. + */ + private Timestamp createTime; + + /** + * 更新时间. + */ + private Timestamp updateTime; +} diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/bd/AdminSalaryPolicyService.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/bd/AdminSalaryPolicyService.java new file mode 100644 index 00000000..28f59500 --- /dev/null +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/bd/AdminSalaryPolicyService.java @@ -0,0 +1,29 @@ +package com.red.circle.other.infra.database.mongo.service.bd; + +import com.red.circle.other.infra.database.mongo.entity.bd.AdminSalaryPolicy; + +import java.util.List; + +/** + * 管理员工资政策服务. + * + * @author AI Assistant + * @since 2025-12-13 + */ +public interface AdminSalaryPolicyService { + + /** + * 根据政策类型查询启用的政策列表. + */ + List listEnabledByType(String policyType); + + /** + * 创建政策. + */ + void create(AdminSalaryPolicy policy); + + /** + * 批量创建政策. + */ + void batchCreate(List policies); +} diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/bd/AdminSalarySettlementRecordService.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/bd/AdminSalarySettlementRecordService.java new file mode 100644 index 00000000..a3a13105 --- /dev/null +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/bd/AdminSalarySettlementRecordService.java @@ -0,0 +1,38 @@ +package com.red.circle.other.infra.database.mongo.service.bd; + +import com.red.circle.other.infra.database.mongo.entity.bd.AdminSalarySettlementRecord; +import com.red.circle.other.infra.enums.BdSettlementStatus; + +/** + * 管理员工资结算记录服务. + * + * @author AI Assistant + * @since 2025-12-13 + */ +public interface AdminSalarySettlementRecordService { + + /** + * 创建结算记录. + */ + void create(AdminSalarySettlementRecord record); + + /** + * 根据业务号查询. + */ + AdminSalarySettlementRecord getByBizNo(String bizNo); + + /** + * 根据业务号判断是否存在. + */ + boolean existsByBizNo(String bizNo); + + /** + * 根据用户ID和账期查询. + */ + AdminSalarySettlementRecord getByUserIdAndBillBelong(Long adminUserId, Integer billBelong); + + /** + * 更新状态. + */ + void updateStatus(String id, BdSettlementStatus status, String failureReason); +} diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/bd/impl/AdminSalaryPolicyServiceImpl.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/bd/impl/AdminSalaryPolicyServiceImpl.java new file mode 100644 index 00000000..8287301a --- /dev/null +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/bd/impl/AdminSalaryPolicyServiceImpl.java @@ -0,0 +1,51 @@ +package com.red.circle.other.infra.database.mongo.service.bd.impl; + +import com.red.circle.other.infra.database.mongo.entity.bd.AdminSalaryPolicy; +import com.red.circle.other.infra.database.mongo.service.bd.AdminSalaryPolicyService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.stereotype.Service; + +import java.sql.Timestamp; +import java.util.List; + +/** + * 管理员工资政策服务实现. + * + * @author AI Assistant + * @since 2025-12-13 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class AdminSalaryPolicyServiceImpl implements AdminSalaryPolicyService { + + private final MongoTemplate mongoTemplate; + + @Override + public List listEnabledByType(String policyType) { + Query query = new Query(Criteria.where("policyType").is(policyType) + .and("enabled").is(true)); + return mongoTemplate.find(query, AdminSalaryPolicy.class); + } + + @Override + public void create(AdminSalaryPolicy policy) { + policy.setCreateTime(new Timestamp(System.currentTimeMillis())); + policy.setUpdateTime(new Timestamp(System.currentTimeMillis())); + mongoTemplate.insert(policy); + } + + @Override + public void batchCreate(List policies) { + Timestamp now = new Timestamp(System.currentTimeMillis()); + policies.forEach(policy -> { + policy.setCreateTime(now); + policy.setUpdateTime(now); + }); + mongoTemplate.insertAll(policies); + } +} diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/bd/impl/AdminSalarySettlementRecordServiceImpl.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/bd/impl/AdminSalarySettlementRecordServiceImpl.java new file mode 100644 index 00000000..fa71451a --- /dev/null +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/bd/impl/AdminSalarySettlementRecordServiceImpl.java @@ -0,0 +1,64 @@ +package com.red.circle.other.infra.database.mongo.service.bd.impl; + +import com.red.circle.other.infra.database.mongo.entity.bd.AdminSalarySettlementRecord; +import com.red.circle.other.infra.database.mongo.service.bd.AdminSalarySettlementRecordService; +import com.red.circle.other.infra.enums.BdSettlementStatus; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.core.query.Update; +import org.springframework.stereotype.Service; + +import java.sql.Timestamp; + +/** + * 管理员工资结算记录服务实现. + * + * @author AI Assistant + * @since 2025-12-13 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class AdminSalarySettlementRecordServiceImpl implements AdminSalarySettlementRecordService { + + private final MongoTemplate mongoTemplate; + + @Override + public void create(AdminSalarySettlementRecord record) { + record.setCreateTime(new Timestamp(System.currentTimeMillis())); + record.setUpdateTime(new Timestamp(System.currentTimeMillis())); + mongoTemplate.insert(record); + } + + @Override + public AdminSalarySettlementRecord getByBizNo(String bizNo) { + Query query = new Query(Criteria.where("bizNo").is(bizNo)); + return mongoTemplate.findOne(query, AdminSalarySettlementRecord.class); + } + + @Override + public boolean existsByBizNo(String bizNo) { + Query query = new Query(Criteria.where("bizNo").is(bizNo)); + return mongoTemplate.exists(query, AdminSalarySettlementRecord.class); + } + + @Override + public AdminSalarySettlementRecord getByUserIdAndBillBelong(Long adminUserId, Integer billBelong) { + Query query = new Query(Criteria.where("adminUserId").is(adminUserId) + .and("billBelong").is(billBelong)); + return mongoTemplate.findOne(query, AdminSalarySettlementRecord.class); + } + + @Override + public void updateStatus(String id, BdSettlementStatus status, String failureReason) { + Query query = new Query(Criteria.where("id").is(id)); + Update update = new Update() + .set("status", status) + .set("failureReason", failureReason) + .set("updateTime", new Timestamp(System.currentTimeMillis())); + mongoTemplate.updateFirst(query, update, AdminSalarySettlementRecord.class); + } +} diff --git a/rc-service/rc-service-wallet/wallet-domain/src/main/java/com/red/circle/wallet/domain/salary/SalaryEvent.java b/rc-service/rc-service-wallet/wallet-domain/src/main/java/com/red/circle/wallet/domain/salary/SalaryEvent.java index 69742ffe..178454d9 100644 --- a/rc-service/rc-service-wallet/wallet-domain/src/main/java/com/red/circle/wallet/domain/salary/SalaryEvent.java +++ b/rc-service/rc-service-wallet/wallet-domain/src/main/java/com/red/circle/wallet/domain/salary/SalaryEvent.java @@ -14,6 +14,8 @@ public enum SalaryEvent { SALARY_BD_LEADER("SALARY_BD_LEADER", "BD_LEADER工资"), + SALARY_ADMIN("SALARY_ADMIN", "ADMIN工资"), + /** * 工资调整. */ diff --git a/rc-service/rc-service-wallet/wallet-domain/src/main/java/com/red/circle/wallet/domain/salary/SalaryType.java b/rc-service/rc-service-wallet/wallet-domain/src/main/java/com/red/circle/wallet/domain/salary/SalaryType.java index 03e209b7..013fc09c 100644 --- a/rc-service/rc-service-wallet/wallet-domain/src/main/java/com/red/circle/wallet/domain/salary/SalaryType.java +++ b/rc-service/rc-service-wallet/wallet-domain/src/main/java/com/red/circle/wallet/domain/salary/SalaryType.java @@ -23,7 +23,7 @@ public enum SalaryType { /** * 管理员工资 */ - ADMIN_SALARY("ADMIN_SALARY", "ADMIN工资"), + ADMIN_SALARY(" ", "ADMIN工资"), ;