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 d34efea8..b4163eae 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 @@ -172,13 +172,6 @@ public class TeamBillSettleListener implements MessageListener { .setOwnSalary(ownSalary(teamBillMemberTargets)) .setTotalSalary(ownTotalSalary(teamBillMemberTargets)); - teamBillCycleService.updateSelective(new TeamBillCycle() - .setId(teamBillCycle.getId()) - .setRegion(teamBillCycle.getRegion()) - .setStatus(TeamBillCycleStatus.SETTLED) - .setSettleResult(settleResult) - ); - if (CollectionUtils.isEmpty(teamMemberTargets)) { return null; } @@ -187,6 +180,13 @@ public class TeamBillSettleListener implements MessageListener { return settleResult; } + teamBillCycleService.updateSelective(new TeamBillCycle() + .setId(teamBillCycle.getId()) + .setRegion(teamBillCycle.getRegion()) + .setStatus(TeamBillCycleStatus.SETTLED) + .setSettleResult(settleResult) + ); + if (Objects.isNull(regionPolicyManager.getPolicyType()) || Objects.equals(TeamPolicyTypeEnum.MONEY.name(), regionPolicyManager.getPolicyType())) { log.warn("美金入账{}", teamProfile); @@ -215,6 +215,7 @@ public class TeamBillSettleListener implements MessageListener { teamBillCycle.getTeamId(), TeamBillCycleUtils.extractMonth(billBelong) ); + teamMemberTargets.forEach(teamMemberTarget -> teamMemberTarget.setHistory(!Objects.equals(teamMemberTarget.getBillBelong(), billBelong))); } else { // 上半月结算:直接查询当前批次数据 teamMemberTargets = teamMemberTargetService.listByBillBelong( 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 6cd65f81..4322947c 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 @@ -14,6 +14,7 @@ import java.util.List; import lombok.Data; import lombok.experimental.Accessors; import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.index.CompoundIndex; import org.springframework.data.mongodb.core.mapping.Document; /** @@ -24,6 +25,7 @@ import org.springframework.data.mongodb.core.mapping.Document; @Data @Accessors(chain = true) @Document("team_bill_cycle") +@CompoundIndex(name = "idx_billBelong_status", def = "{'billBelong': 1, 'status': 1}") public class TeamBillCycle implements Serializable { @Serial diff --git a/rc-service/rc-service-other/other-start/src/test/java/TeamSalaryExportTest.java b/rc-service/rc-service-other/other-start/src/test/java/TeamSalaryExportTest.java new file mode 100644 index 00000000..1526ee04 --- /dev/null +++ b/rc-service/rc-service-other/other-start/src/test/java/TeamSalaryExportTest.java @@ -0,0 +1,524 @@ +import com.red.circle.OtherServiceApplication; +import com.red.circle.other.app.listener.team.TeamBillSettleListener; +import com.red.circle.other.infra.database.mongo.dto.team.TeamBillMemberTarget; +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.entity.team.team.TeamMemberTarget; +import com.red.circle.other.infra.database.mongo.entity.team.team.TeamPolicyManager; +import com.red.circle.other.infra.database.mongo.entity.team.team.TeamProfile; +import com.red.circle.other.infra.database.mongo.entity.user.region.SysRegionConfig; +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.TeamMemberTargetService; +import com.red.circle.other.infra.database.mongo.service.team.team.TeamPolicyManagerService; +import com.red.circle.other.infra.database.mongo.service.team.team.TeamProfileService; +import com.red.circle.other.infra.database.mongo.service.user.region.SysRegionConfigService; +import com.red.circle.other.inner.enums.team.TeamBillCycleStatus; +import com.red.circle.tool.core.collection.CollectionUtils; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +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.test.context.junit4.SpringRunner; + +import java.io.FileOutputStream; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +@Slf4j +@SpringBootTest(classes = OtherServiceApplication.class) +@RunWith(SpringRunner.class) +public class TeamSalaryExportTest { + + @Autowired + private TeamBillSettleListener teamBillSettleListener; + + @Autowired + private TeamProfileService teamProfileService; + + @Autowired + private TeamBillCycleService teamBillCycleService; + + @Autowired + private SysRegionConfigService sysRegionConfigService; + + @Autowired + private MongoTemplate mongoTemplate; + + @Autowired + private TeamMemberTargetService teamMemberTargetService; + + @Autowired + private TeamPolicyManagerService teamPolicyManagerService; + + /** + * 导出团队工资到Excel (测试模式,不发工资) + */ + @Test + public void exportTeamSalaryToExcel() { + try { + // 1. 获取当前账单 + Integer billBelong = TeamBillCycleUtils.getCalcBillBelong(); + log.info("当前账单: {}", billBelong); + + // 2. 查询所有待结算的账单 +// List teamBillCycles = queryPendingBills(billBelong); + List teamBillCycles = queryPendingBills(Arrays.asList(1983879651333181442L), billBelong); + log.info("找到 {} 个待结算账单", teamBillCycles.size()); + + // 3. 收集所有结算结果 + List exportDataList = new ArrayList<>(); + List memberDetailList = new ArrayList<>(); + + for (TeamBillCycle teamBillCycle : teamBillCycles) { + if (teamBillCycle.getTeamId() != 1983879651333181442L) { + continue; + } + + try { + // 获取团队信息 + TeamProfile teamProfile = teamProfileService.getById(teamBillCycle.getTeamId()); + if (Objects.isNull(teamProfile)) { + log.warn("团队不存在: {}", teamBillCycle.getTeamId()); + continue; + } + + // 获取区域配置 + SysRegionConfig regionConfig = sysRegionConfigService.getById(teamProfile.getRegion()); + + // 获取团队成员目标数据 + List teamMemberTargets = getTeamMemberTargets(teamBillCycle); + + // 获取团队区域政策 + TeamPolicyManager regionPolicyManager = teamPolicyManagerService.getReleaseByRegionAndType( + teamBillCycle.getSysOrigin(), + teamBillCycle.getRegion() + ); + + // 计算成员目标 + List teamBillMemberTargets = TeamBillCycleUtils.calculatorPolicyTarget( + teamMemberTargets, regionPolicyManager); + + // 收集成员明细数据 + if (CollectionUtils.isNotEmpty(teamBillMemberTargets)) { + for (TeamBillMemberTarget memberTarget : teamBillMemberTargets) { + MemberSalaryDetail detail = new MemberSalaryDetail(); + detail.setTeamId(teamProfile.getId()); + detail.setUserId(memberTarget.getUserId()); + detail.setRegionName(Objects.nonNull(regionConfig) ? regionConfig.getRegionName() : ""); + detail.setCountryName(Objects.nonNull(teamProfile.getCountry()) ? + teamProfile.getCountry().getCountryName() : ""); + detail.setBillBelong(teamBillCycle.getBillBelong()); + detail.setOnlineTime(memberTarget.getOnlineTime()); + detail.setAcceptGiftValue(memberTarget.getAcceptGiftValue()); + detail.setGiveGiftValue(memberTarget.getGiveGiftValue()); + detail.setRoomValue(memberTarget.getRoomValue()); + detail.setLevel(memberTarget.getLevel()); + detail.setSatisfyLevel(memberTarget.getSatisfyLevel()); + detail.setMemberSalary(memberTarget.getMemberSalary()); + detail.setOwnSalary(memberTarget.getOwnSalary()); + detail.setTotalSalary(memberTarget.getTotalSalary()); + + memberDetailList.add(detail); + } + } + + // 调用结算方法 (isPay = false, 不发工资) + TeamBillSettleResult settleResult = teamBillSettleListener.processBill( + teamProfile, teamBillCycle, false); + + if (Objects.nonNull(settleResult)) { + SalaryExportData data = new SalaryExportData(); + data.setTeamId(teamProfile.getId()); + data.setOwnUserId(teamProfile.getOwnUserId()); + data.setRegionId(teamProfile.getRegion()); + data.setRegionName(Objects.nonNull(regionConfig) ? regionConfig.getRegionName() : ""); + data.setCountryCode(Objects.nonNull(teamProfile.getCountry()) ? + teamProfile.getCountry().getCountryCode() : ""); + data.setCountryName(Objects.nonNull(teamProfile.getCountry()) ? + teamProfile.getCountry().getCountryName() : ""); + data.setBillBelong(teamBillCycle.getBillBelong()); + data.setSysOrigin(teamProfile.getSysOrigin()); + data.setWorkMembers(settleResult.getWorkMembers()); + data.setFinishMembers(settleResult.getFinishMembers()); + data.setMemberSalary(settleResult.getMemberSalary()); + data.setOwnSalary(settleResult.getOwnSalary()); + data.setTotalSalary(settleResult.getTotalSalary()); + + exportDataList.add(data); + + log.info("团队 {} 结算完成: 工作人数={}, 完成人数={}, 总工资={}", + teamProfile.getId(), + settleResult.getWorkMembers(), + settleResult.getFinishMembers(), + settleResult.getTotalSalary()); + } + } catch (Exception e) { + log.error("处理团队账单失败: teamId={}, billBelong={}", + teamBillCycle.getTeamId(), teamBillCycle.getBillBelong(), e); + } + } + + // 4. 创建Excel并写入数据 + if (CollectionUtils.isNotEmpty(exportDataList)) { + String filePath = createExcel(exportDataList, memberDetailList, billBelong); + log.info("==========================================="); + log.info("导出成功! 共导出 {} 个团队, {} 个成员", exportDataList.size(), memberDetailList.size()); + log.info("文件路径: {}", filePath); + log.info("==========================================="); + } else { + log.warn("没有数据可导出"); + } + + } catch (Exception e) { + log.error("导出失败", e); + } + } + + /** + * 获取团队成员目标数据 + */ + private List getTeamMemberTargets(TeamBillCycle teamBillCycle) { + Integer billBelong = teamBillCycle.getBillBelong(); + Integer batchNumber = TeamBillCycleUtils.extractBatch(billBelong); + List teamMemberTargets; + + if (batchNumber != null && batchNumber == 2) { + teamMemberTargets = teamMemberTargetService.listByMonthlyBillBelong( + teamBillCycle.getTeamId(), + TeamBillCycleUtils.extractMonth(billBelong) + ); + teamMemberTargets.forEach(teamMemberTarget -> teamMemberTarget.setHistory(!Objects.equals(teamMemberTarget.getBillBelong(), billBelong))); + } else { + // 上半月结算:直接查询当前批次数据 + teamMemberTargets = teamMemberTargetService.listByBillBelong( + teamBillCycle.getTeamId(), + billBelong + ); + } + return teamMemberTargets; + } + + /** + * 查询待结算的账单 + */ + private List queryPendingBills(Integer billBelong) { + Criteria criteria = Criteria.where("billBelong").lte(billBelong) + .and("status").is(TeamBillCycleStatus.UNPAID); + + return mongoTemplate.find( + Query.query(criteria), + TeamBillCycle.class + ); + } + + /** + * 查询待结算的账单 + */ + private List queryPendingBills(List teamIds, Integer billBelong) { + Criteria criteria = Criteria.where("billBelong") + .lte(billBelong) + .and("status").is(TeamBillCycleStatus.UNPAID) + .and("teamId").in(teamIds); + + return mongoTemplate.find( + Query.query(criteria), + TeamBillCycle.class + ); + } + + /** + * 创建Excel文件 + */ + private String createExcel(List summaryList, + List detailList, + Integer billBelong) throws Exception { + Workbook workbook = new XSSFWorkbook(); + + // 创建Sheet1: 团队工资汇总 + Sheet sheet1 = workbook.createSheet("团队工资汇总"); + createSummarySheet(workbook, sheet1, summaryList); + + // 创建Sheet2: 成员工资明细 + Sheet sheet2 = workbook.createSheet("成员工资明细"); + createMemberDetailSheet(workbook, sheet2, detailList); + + // 生成文件名和路径 + String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss")); + String fileName = String.format("team_salary_%s_%s.xlsx", billBelong, timestamp); + String filePath = "D:/workspace/likei-services/rc-service/rc-service-other/other-start/src/test/resources/export/" + fileName; + + // 确保目录存在 + java.io.File dir = new java.io.File("D:/workspace/likei-services/rc-service/rc-service-other/other-start/src/test/resources/export/"); + if (!dir.exists()) { + dir.mkdirs(); + } + + // 写入文件 + try (FileOutputStream fileOut = new FileOutputStream(filePath)) { + workbook.write(fileOut); + } + + workbook.close(); + return filePath; + } + + /** + * 创建汇总Sheet + */ + private void createSummarySheet(Workbook workbook, Sheet sheet, List dataList) { + // 创建样式 + CellStyle headerStyle = createHeaderStyle(workbook); + CellStyle dataStyle = createDataStyle(workbook); + CellStyle moneyStyle = createMoneyStyle(workbook); + + // 创建表头 + Row headerRow = sheet.createRow(0); + String[] headers = { + "团队ID", "团长用户ID", "区域ID", "区域名称", "国家代码", "国家名称", + "账单周期", "系统来源", "工作人数", "完成人数", + "成员工资", "团长工资", "总工资" + }; + + for (int i = 0; i < headers.length; i++) { + Cell cell = headerRow.createCell(i); + cell.setCellValue(headers[i]); + cell.setCellStyle(headerStyle); + } + + // 填充数据 + int rowIndex = 1; + for (SalaryExportData data : dataList) { + Row row = sheet.createRow(rowIndex++); + + int colIndex = 0; + createCell(row, colIndex++, String.valueOf(data.getTeamId()), dataStyle); + createCell(row, colIndex++, String.valueOf(data.getOwnUserId()), dataStyle); + createCell(row, colIndex++, data.getRegionId(), dataStyle); + createCell(row, colIndex++, data.getRegionName(), dataStyle); + createCell(row, colIndex++, data.getCountryCode(), dataStyle); + createCell(row, colIndex++, data.getCountryName(), dataStyle); + createCell(row, colIndex++, String.valueOf(data.getBillBelong()), dataStyle); + createCell(row, colIndex++, data.getSysOrigin(), dataStyle); + createCell(row, colIndex++, String.valueOf(data.getWorkMembers()), dataStyle); + createCell(row, colIndex++, String.valueOf(data.getFinishMembers()), dataStyle); + createCell(row, colIndex++, data.getMemberSalary().doubleValue(), moneyStyle); + createCell(row, colIndex++, data.getOwnSalary().doubleValue(), moneyStyle); + createCell(row, colIndex++, data.getTotalSalary().doubleValue(), moneyStyle); + } + + // 自动调整列宽 + for (int i = 0; i < headers.length; i++) { + sheet.autoSizeColumn(i); + sheet.setColumnWidth(i, sheet.getColumnWidth(i) + 1024); + } + } + + /** + * 创建成员明细Sheet + */ + private void createMemberDetailSheet(Workbook workbook, Sheet sheet, List detailList) { + // 创建样式 + CellStyle headerStyle = createHeaderStyle(workbook); + CellStyle dataStyle = createDataStyle(workbook); + CellStyle numberStyle = createNumberStyle(workbook); + CellStyle moneyStyle = createMoneyStyle(workbook); + + // 创建表头 + Row headerRow = sheet.createRow(0); + String[] headers = { + "团队ID", "用户ID", "区域名称", "国家名称", "账单周期", + "总在线时长(分钟)", "收礼物值", "送礼物值", "房间流水", + "等级", "是否达标", "成员工资", "团长工资", "总工资" + }; + + for (int i = 0; i < headers.length; i++) { + Cell cell = headerRow.createCell(i); + cell.setCellValue(headers[i]); + cell.setCellStyle(headerStyle); + } + + // 填充数据 + int rowIndex = 1; + for (MemberSalaryDetail detail : detailList) { + Row row = sheet.createRow(rowIndex++); + + int colIndex = 0; + createCell(row, colIndex++, String.valueOf(detail.getTeamId()), dataStyle); + createCell(row, colIndex++, String.valueOf(detail.getUserId()), dataStyle); + createCell(row, colIndex++, detail.getRegionName(), dataStyle); + createCell(row, colIndex++, detail.getCountryName(), dataStyle); + createCell(row, colIndex++, String.valueOf(detail.getBillBelong()), dataStyle); + + // 在线时长 + Long onlineTime = detail.getOnlineTime() != null ? detail.getOnlineTime() : 0L; + createCell(row, colIndex++, onlineTime.doubleValue(), numberStyle); + + // 礼物和流水 + createCell(row, colIndex++, detail.getAcceptGiftValue() != null ? detail.getAcceptGiftValue().doubleValue() : 0, numberStyle); + createCell(row, colIndex++, detail.getGiveGiftValue() != null ? detail.getGiveGiftValue().doubleValue() : 0, numberStyle); + createCell(row, colIndex++, detail.getRoomValue() != null ? detail.getRoomValue().doubleValue() : 0, numberStyle); + + // 等级和达标 + createCell(row, colIndex++, String.valueOf(detail.getLevel() != null ? detail.getLevel() : 0), dataStyle); + createCell(row, colIndex++, Boolean.TRUE.equals(detail.getSatisfyLevel()) ? "是" : "否", dataStyle); + + // 工资 + createCell(row, colIndex++, detail.getMemberSalary() != null ? detail.getMemberSalary().doubleValue() : 0, moneyStyle); + createCell(row, colIndex++, detail.getOwnSalary() != null ? detail.getOwnSalary().doubleValue() : 0, moneyStyle); + createCell(row, colIndex++, detail.getTotalSalary() != null ? detail.getTotalSalary().doubleValue() : 0, moneyStyle); + } + + // 自动调整列宽 + for (int i = 0; i < headers.length; i++) { + sheet.autoSizeColumn(i); + sheet.setColumnWidth(i, sheet.getColumnWidth(i) + 1024); + } + } + + /** + * 创建单元格 + */ + private void createCell(Row row, int column, String value, CellStyle style) { + Cell cell = row.createCell(column); + cell.setCellValue(value); + cell.setCellStyle(style); + } + + /** + * 创建单元格 (数字) + */ + private void createCell(Row row, int column, double value, CellStyle style) { + Cell cell = row.createCell(column); + cell.setCellValue(value); + cell.setCellStyle(style); + } + + /** + * 创建表头样式 + */ + private CellStyle createHeaderStyle(Workbook workbook) { + CellStyle style = workbook.createCellStyle(); + style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex()); + style.setFillPattern(FillPatternType.SOLID_FOREGROUND); + style.setBorderBottom(BorderStyle.THIN); + style.setBorderTop(BorderStyle.THIN); + style.setBorderLeft(BorderStyle.THIN); + style.setBorderRight(BorderStyle.THIN); + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); + + Font font = workbook.createFont(); + font.setBold(true); + font.setFontHeightInPoints((short) 11); + style.setFont(font); + + return style; + } + + /** + * 创建数据样式 + */ + private CellStyle createDataStyle(Workbook workbook) { + CellStyle style = workbook.createCellStyle(); + style.setBorderBottom(BorderStyle.THIN); + style.setBorderTop(BorderStyle.THIN); + style.setBorderLeft(BorderStyle.THIN); + style.setBorderRight(BorderStyle.THIN); + style.setAlignment(HorizontalAlignment.LEFT); + style.setVerticalAlignment(VerticalAlignment.CENTER); + + return style; + } + + /** + * 创建金额样式 + */ + private CellStyle createMoneyStyle(Workbook workbook) { + CellStyle style = workbook.createCellStyle(); + style.setBorderBottom(BorderStyle.THIN); + style.setBorderTop(BorderStyle.THIN); + style.setBorderLeft(BorderStyle.THIN); + style.setBorderRight(BorderStyle.THIN); + style.setAlignment(HorizontalAlignment.RIGHT); + style.setVerticalAlignment(VerticalAlignment.CENTER); + + // 设置数字格式为两位小数 + DataFormat format = workbook.createDataFormat(); + style.setDataFormat(format.getFormat("0.00")); + + return style; + } + + /** + * 创建数字样式(整数) + */ + private CellStyle createNumberStyle(Workbook workbook) { + CellStyle style = workbook.createCellStyle(); + style.setBorderBottom(BorderStyle.THIN); + style.setBorderTop(BorderStyle.THIN); + style.setBorderLeft(BorderStyle.THIN); + style.setBorderRight(BorderStyle.THIN); + style.setAlignment(HorizontalAlignment.RIGHT); + style.setVerticalAlignment(VerticalAlignment.CENTER); + + // 设置数字格式为整数 + DataFormat format = workbook.createDataFormat(); + style.setDataFormat(format.getFormat("0")); + + return style; + } + + /** + * 导出数据实体 - 汇总 + */ + @Data + static class SalaryExportData { + private Long teamId; + private Long ownUserId; + private String regionId; + private String regionName; + private String countryCode; + private String countryName; + private Integer billBelong; + private String sysOrigin; + private Integer workMembers; + private Integer finishMembers; + private BigDecimal memberSalary; + private BigDecimal ownSalary; + private BigDecimal totalSalary; + } + + /** + * 导出数据实体 - 成员明细 + */ + @Data + static class MemberSalaryDetail { + private Long teamId; + private Long userId; + private String regionName; + private String countryName; + private Integer billBelong; + private Long onlineTime; + private Long acceptGiftValue; + private Long giveGiftValue; + private Long roomValue; + private Integer level; + private Boolean satisfyLevel; + private BigDecimal memberSalary; + private BigDecimal ownSalary; + private BigDecimal totalSalary; + } +}