结算修改

This commit is contained in:
tianfeng 2025-11-26 18:53:55 +08:00
parent 122b048e27
commit b0167925fb
3 changed files with 534 additions and 7 deletions

View File

@ -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(

View File

@ -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

View File

@ -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<TeamBillCycle> teamBillCycles = queryPendingBills(billBelong);
List<TeamBillCycle> teamBillCycles = queryPendingBills(Arrays.asList(1983879651333181442L), billBelong);
log.info("找到 {} 个待结算账单", teamBillCycles.size());
// 3. 收集所有结算结果
List<SalaryExportData> exportDataList = new ArrayList<>();
List<MemberSalaryDetail> 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<TeamMemberTarget> teamMemberTargets = getTeamMemberTargets(teamBillCycle);
// 获取团队区域政策
TeamPolicyManager regionPolicyManager = teamPolicyManagerService.getReleaseByRegionAndType(
teamBillCycle.getSysOrigin(),
teamBillCycle.getRegion()
);
// 计算成员目标
List<TeamBillMemberTarget> 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<TeamMemberTarget> getTeamMemberTargets(TeamBillCycle teamBillCycle) {
Integer billBelong = teamBillCycle.getBillBelong();
Integer batchNumber = TeamBillCycleUtils.extractBatch(billBelong);
List<TeamMemberTarget> 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<TeamBillCycle> 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<TeamBillCycle> queryPendingBills(List<Long> 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<SalaryExportData> summaryList,
List<MemberSalaryDetail> 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<SalaryExportData> 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<MemberSalaryDetail> 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;
}
}