feat: add salary settlement records

This commit is contained in:
zhx 2026-07-09 15:50:08 +08:00
parent b46c2ac951
commit 2dfbc32786
13 changed files with 881 additions and 0 deletions

View File

@ -0,0 +1,98 @@
-- Webconsole salary settlement records menu.
-- Adds the second-level menu under 主播中心 and binds the list API resource.
INSERT INTO sys_resource (id, resource_name, mapping, method, auth_type, perm, update_time)
VALUES (
'team:salary:settlement:list',
'工资记录列表',
'/team/salary/settlement/list',
'GET',
3,
'team:salary:record:list',
NOW()
)
ON DUPLICATE KEY UPDATE
resource_name = VALUES(resource_name),
mapping = VALUES(mapping),
method = VALUES(method),
auth_type = VALUES(auth_type),
perm = VALUES(perm),
update_time = NOW();
INSERT INTO sys_menu (
parent_id,
menu_name,
path,
router,
menu_type,
icon,
alias,
sort,
status,
create_time,
update_time
)
SELECT
parent.id,
'工资记录',
'team/salary-record/index',
'team/salary-record',
2,
'form',
'team:salary:record',
880,
0,
NOW(),
NOW()
FROM sys_menu parent
LEFT JOIN sys_menu existing ON existing.alias = 'team:salary:record'
WHERE (parent.alias = 'team:manaber' OR parent.router = '/team' OR parent.menu_name = '主播中心')
AND parent.menu_type = 1
AND existing.id IS NULL
LIMIT 1;
UPDATE sys_menu salary_menu
JOIN (
SELECT id
FROM sys_menu
WHERE (alias = 'team:manaber' OR router = '/team' OR menu_name = '主播中心')
AND menu_type = 1
LIMIT 1
) parent ON 1 = 1
SET
salary_menu.parent_id = parent.id,
salary_menu.menu_name = '工资记录',
salary_menu.path = 'team/salary-record/index',
salary_menu.router = 'team/salary-record',
salary_menu.menu_type = 2,
salary_menu.icon = 'form',
salary_menu.sort = 880,
salary_menu.status = 0,
salary_menu.update_time = NOW()
WHERE salary_menu.alias = 'team:salary:record';
INSERT INTO sys_menu_resource (menu_id, resource_id)
SELECT menu.id, resource.id
FROM sys_menu menu
JOIN sys_resource resource ON resource.id = 'team:salary:settlement:list'
LEFT JOIN sys_menu_resource existing
ON existing.menu_id = menu.id
AND existing.resource_id = resource.id
WHERE menu.alias = 'team:salary:record'
AND existing.id IS NULL;
-- Salary records include admin salary, so auto-grant only to roles that can manage system permissions.
INSERT INTO sys_role_menu (role_id, menu_id)
SELECT DISTINCT source_role.role_id, salary_menu.id
FROM sys_role_menu source_role
JOIN sys_menu source_menu ON source_menu.id = source_role.menu_id
JOIN sys_menu salary_menu ON salary_menu.alias = 'team:salary:record'
LEFT JOIN sys_role_menu existing
ON existing.role_id = source_role.role_id
AND existing.menu_id = salary_menu.id
WHERE source_menu.alias IN (
'sys:menu:list',
'sys:role:list',
'sys:resource:list'
)
AND existing.id IS NULL;

View File

@ -0,0 +1,98 @@
-- Webconsole salary settlement records menu.
-- Adds the second-level menu under 主播中心 and binds the list API resource.
INSERT INTO sys_resource (id, resource_name, mapping, method, auth_type, perm, update_time)
VALUES (
'team:salary:settlement:list',
'工资记录列表',
'/team/salary/settlement/list',
'GET',
3,
'team:salary:record:list',
NOW()
)
ON DUPLICATE KEY UPDATE
resource_name = VALUES(resource_name),
mapping = VALUES(mapping),
method = VALUES(method),
auth_type = VALUES(auth_type),
perm = VALUES(perm),
update_time = NOW();
INSERT INTO sys_menu (
parent_id,
menu_name,
path,
router,
menu_type,
icon,
alias,
sort,
status,
create_time,
update_time
)
SELECT
parent.id,
'工资记录',
'team/salary-record/index',
'team/salary-record',
2,
'form',
'team:salary:record',
880,
0,
NOW(),
NOW()
FROM sys_menu parent
LEFT JOIN sys_menu existing ON existing.alias = 'team:salary:record'
WHERE (parent.alias = 'team:manaber' OR parent.router = '/team' OR parent.menu_name = '主播中心')
AND parent.menu_type = 1
AND existing.id IS NULL
LIMIT 1;
UPDATE sys_menu salary_menu
JOIN (
SELECT id
FROM sys_menu
WHERE (alias = 'team:manaber' OR router = '/team' OR menu_name = '主播中心')
AND menu_type = 1
LIMIT 1
) parent ON 1 = 1
SET
salary_menu.parent_id = parent.id,
salary_menu.menu_name = '工资记录',
salary_menu.path = 'team/salary-record/index',
salary_menu.router = 'team/salary-record',
salary_menu.menu_type = 2,
salary_menu.icon = 'form',
salary_menu.sort = 880,
salary_menu.status = 0,
salary_menu.update_time = NOW()
WHERE salary_menu.alias = 'team:salary:record';
INSERT INTO sys_menu_resource (menu_id, resource_id)
SELECT menu.id, resource.id
FROM sys_menu menu
JOIN sys_resource resource ON resource.id = 'team:salary:settlement:list'
LEFT JOIN sys_menu_resource existing
ON existing.menu_id = menu.id
AND existing.resource_id = resource.id
WHERE menu.alias = 'team:salary:record'
AND existing.id IS NULL;
-- Salary records include admin salary, so auto-grant only to roles that can manage system permissions.
INSERT INTO sys_role_menu (role_id, menu_id)
SELECT DISTINCT source_role.role_id, salary_menu.id
FROM sys_role_menu source_role
JOIN sys_menu source_menu ON source_menu.id = source_role.menu_id
JOIN sys_menu salary_menu ON salary_menu.alias = 'team:salary:record'
LEFT JOIN sys_role_menu existing
ON existing.role_id = source_role.role_id
AND existing.menu_id = salary_menu.id
WHERE source_menu.alias IN (
'sys:menu:list',
'sys:role:list',
'sys:resource:list'
)
AND existing.id IS NULL;

View File

@ -0,0 +1,15 @@
package com.red.circle.other.inner.endpoint.team.bd;
import com.red.circle.other.inner.endpoint.team.bd.api.SalarySettlementRecordClientApi;
import org.springframework.cloud.openfeign.FeignClient;
/**
* BD/BD Leader/Admin 工资结算记录.
*
* @author AI Assistant
*/
@FeignClient(name = "salarySettlementRecordClient", url = "${feign.other.url}" +
SalarySettlementRecordClientApi.API_PREFIX)
public interface SalarySettlementRecordClient extends SalarySettlementRecordClientApi {
}

View File

@ -0,0 +1,25 @@
package com.red.circle.other.inner.endpoint.team.bd.api;
import com.red.circle.framework.dto.PageResult;
import com.red.circle.framework.dto.ResultResponse;
import com.red.circle.other.inner.model.cmd.team.bd.SalarySettlementRecordPageQryCmd;
import com.red.circle.other.inner.model.dto.agency.bd.SalarySettlementRecordDTO;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
/**
* BD/BD Leader/Admin 工资结算记录.
*
* @author AI Assistant
*/
public interface SalarySettlementRecordClientApi {
String API_PREFIX = "/bd/salary-settlement-record/client";
/**
* 分页查询工资结算记录.
*/
@PostMapping("/page")
ResultResponse<PageResult<SalarySettlementRecordDTO>> page(
@RequestBody SalarySettlementRecordPageQryCmd qryCmd);
}

View File

@ -0,0 +1,51 @@
package com.red.circle.other.inner.model.cmd.team.bd;
import com.red.circle.framework.core.dto.PageCommand;
import java.io.Serial;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* BD/BD Leader/Admin 工资结算记录分页查询.
*
* @author AI Assistant
*/
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
public class SalarySettlementRecordPageQryCmd extends PageCommand {
@Serial
private static final long serialVersionUID = 1L;
/**
* 来源系统.
*/
private String sysOrigin;
/**
* 身份BD | BD_LEADER | ADMIN空表示全部.
*/
private String identity;
/**
* 用户 ID.
*/
private Long userId;
/**
* 账期yyyyMMdd 半月起始日 20260701 / 20260716.
*/
private Integer billBelong;
/**
* 状态PENDING | SUCCESS | FAILED.
*/
private String status;
/**
* 幂等业务号.
*/
private String bizNo;
}

View File

@ -0,0 +1,127 @@
package com.red.circle.other.inner.model.dto.agency.bd;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.red.circle.framework.dto.DTO;
import java.io.Serial;
import java.math.BigDecimal;
import java.sql.Timestamp;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* BD/BD Leader/Admin 工资结算记录.
*
* @author AI Assistant
*/
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
public class SalarySettlementRecordDTO extends DTO {
@Serial
private static final long serialVersionUID = 1L;
private String id;
@JsonSerialize(using = ToStringSerializer.class)
private Long timeId;
private String sysOrigin;
/**
* 统一用户 ID按身份分别对应 bdUserId/bdLeaderUserId/adminUserId.
*/
@JsonSerialize(using = ToStringSerializer.class)
private Long userId;
private String identity;
@JsonSerialize(using = ToStringSerializer.class)
private Long bdUserId;
@JsonSerialize(using = ToStringSerializer.class)
private Long bdLeaderUserId;
@JsonSerialize(using = ToStringSerializer.class)
private Long adminUserId;
private Boolean isAdmin;
private Integer billBelong;
private String billTitle;
private String bizNo;
private String status;
private String failureReason;
private Integer agencyNumber;
private Integer bdNumber;
private BigDecimal teamSalaryAmount;
private BigDecimal teamRechargeAmount;
private String hitPolicyType;
private Integer hitLevel;
private BigDecimal commissionRate;
private BigDecimal basicSalary;
private BigDecimal rewardAmount;
private Boolean rewardEligible;
private Integer newRechargeAgentCount;
private BigDecimal settlementSalary;
private BigDecimal rechargeAgentAmount;
private BigDecimal rechargeAgentSalary;
private BigDecimal rechargeAgentPercentage;
private BigDecimal bdLeaderTeamSalary;
private BigDecimal bdLeaderSalary;
private Integer bdLeaderHitLevel;
private BigDecimal bdLeaderBasicSalary;
private BigDecimal bdLeaderRewardAmount;
private BigDecimal bdTeamSalaryAmount;
private BigDecimal bdTeamSalarySalary;
private Integer bdTeamSalaryHitLevel;
private BigDecimal bdTeamSalaryCommissionRate;
private BigDecimal bdTeamRechargeAmount;
private BigDecimal bdTeamRechargeSalary;
private Integer bdTeamRechargeHitLevel;
private BigDecimal bdTeamRechargeCommissionRate;
private BigDecimal bdPolicyFinalSalary;
private String bdPolicyHitType;
private BigDecimal totalSalary;
private Timestamp createTime;
private Timestamp updateTime;
}

View File

@ -1,10 +1,14 @@
package com.red.circle.console.adapter.app.team;
import com.red.circle.console.app.dto.clienobject.team.TeamSalaryCO;
import com.red.circle.console.app.dto.clienobject.team.SalarySettlementRecordCO;
import com.red.circle.console.app.service.admin.UserAccountService;
import com.red.circle.console.app.service.app.team.TeamSalaryBackService;
import com.red.circle.framework.dto.PageResult;
import com.red.circle.framework.web.controller.BaseController;
import com.red.circle.other.inner.model.cmd.team.TeamSalaryBackQryCmd;
import com.red.circle.other.inner.model.cmd.team.bd.SalarySettlementRecordPageQryCmd;
import java.util.Objects;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@ -20,6 +24,9 @@ import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/team/salary")
public class TeamSalaryRestController extends BaseController {
private static final String SALARY_RECORD_MENU_ALIAS = "team:salary:record";
private final UserAccountService userAccountService;
private final TeamSalaryBackService teamSalaryBackService;
/**
@ -30,4 +37,24 @@ public class TeamSalaryRestController extends BaseController {
return teamSalaryBackService.listByCondition(query);
}
/**
* BD/BD Leader/Admin 工资结算记录.
*/
@GetMapping("/settlement/list")
public PageResult<SalarySettlementRecordCO> listSettlementRecords(
SalarySettlementRecordPageQryCmd query) {
checkSalaryRecordPermission();
query.setPageQuery(getPage());
return teamSalaryBackService.listSettlementRecords(query);
}
private void checkSalaryRecordPermission() {
Long reqUserId = getReqUserId();
// 工资记录包含 Admin 薪资不能只依赖菜单隐藏接口层也必须校验显式菜单权限
if (Objects.isNull(reqUserId)
|| !userAccountService.hasButtonsAlias(reqUserId.intValue(), SALARY_RECORD_MENU_ALIAS)) {
throw new RuntimeException("无权限访问工资记录");
}
}
}

View File

@ -3,14 +3,18 @@ package com.red.circle.console.app.service.app.team;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.red.circle.console.app.convertor.team.TeamAppConvertor;
import com.red.circle.console.app.dto.clienobject.team.SalarySettlementRecordCO;
import com.red.circle.console.app.dto.clienobject.team.TeamSalaryCO;
import com.red.circle.console.app.dto.clienobject.team.TeamSalaryDetailsCO;
import com.red.circle.framework.core.asserts.ResponseAssert;
import com.red.circle.framework.dto.PageResult;
import com.red.circle.other.inner.endpoint.team.bd.SalarySettlementRecordClient;
import com.red.circle.other.inner.endpoint.team.target.TeamProfileClient;
import com.red.circle.other.inner.model.cmd.team.TeamSalaryBackQryCmd;
import com.red.circle.other.inner.model.cmd.team.bd.SalarySettlementRecordPageQryCmd;
import com.red.circle.other.inner.model.dto.agency.agency.TeamSalaryPaymentDTO;
import com.red.circle.other.inner.model.dto.agency.agency.TeamSalaryPaymentDetailsDTO;
import com.red.circle.other.inner.model.dto.agency.bd.SalarySettlementRecordDTO;
import com.red.circle.tool.core.collection.CollectionUtils;
import com.red.circle.tool.core.text.StringUtils;
import com.red.circle.other.inner.model.dto.user.UserProfileDTO;
@ -22,6 +26,7 @@ import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
/**
@ -37,6 +42,7 @@ public class TeamSalaryBackServiceImpl implements TeamSalaryBackService {
private final TeamProfileClient teamProfileClient;
private final UserProfileClient userProfileClient;
private final RegionConfigClient regionConfigClient;
private final SalarySettlementRecordClient salarySettlementRecordClient;
@Override
public PageResult<TeamSalaryCO> listByCondition(TeamSalaryBackQryCmd query) {
@ -113,6 +119,25 @@ public class TeamSalaryBackServiceImpl implements TeamSalaryBackService {
return page;
}
@Override
public PageResult<SalarySettlementRecordCO> listSettlementRecords(
SalarySettlementRecordPageQryCmd query) {
PageResult<SalarySettlementRecordDTO> pageResult = ResponseAssert.requiredSuccess(
salarySettlementRecordClient.page(query));
PageResult<SalarySettlementRecordCO> page = pageResult.convert(this::toSettlementRecordCO);
if (pageResult.getTotal() <= 0) {
return page;
}
Map<Long, UserProfileDTO> userProfileMap = getSettlementUserProfileMap(pageResult);
page.setRecords(page.getRecords().stream().peek(record -> {
// 后台工资记录按身份统一展示用户资料统一按结算用户 userId 补齐
record.setUserProfile(userProfileMap.get(record.getUserId()));
}).collect(Collectors.toList()));
return page;
}
private Map<String, String> getMapRegionName(TeamSalaryBackQryCmd query) {
return ResponseAssert.requiredSuccess(
regionConfigClient.mapRegionNameBySysOrigin(query.getSysOrigin()));
@ -147,4 +172,24 @@ public class TeamSalaryBackServiceImpl implements TeamSalaryBackService {
return ResponseAssert.requiredSuccess(userProfileClient.mapByUserIds(userIds));
}
private SalarySettlementRecordCO toSettlementRecordCO(SalarySettlementRecordDTO record) {
SalarySettlementRecordCO co = new SalarySettlementRecordCO();
BeanUtils.copyProperties(record, co);
return co;
}
private Map<Long, UserProfileDTO> getSettlementUserProfileMap(
PageResult<SalarySettlementRecordDTO> pageResult) {
Set<Long> userIds = pageResult.getRecords().stream()
.map(SalarySettlementRecordDTO::getUserId)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
if (CollectionUtils.isEmpty(userIds)) {
return Maps.newHashMap();
}
return ResponseAssert.requiredSuccess(userProfileClient.mapByUserIds(userIds));
}
}

View File

@ -0,0 +1,27 @@
package com.red.circle.console.app.dto.clienobject.team;
import com.red.circle.other.inner.model.dto.agency.bd.SalarySettlementRecordDTO;
import com.red.circle.other.inner.model.dto.user.UserProfileDTO;
import java.io.Serial;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* BD/BD Leader/Admin 工资结算记录.
*
* @author AI Assistant
*/
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
public class SalarySettlementRecordCO extends SalarySettlementRecordDTO {
@Serial
private static final long serialVersionUID = 1L;
/**
* 结算用户资料.
*/
private UserProfileDTO userProfile;
}

View File

@ -2,8 +2,10 @@ package com.red.circle.console.app.service.app.team;
import com.red.circle.console.app.dto.clienobject.team.TeamSalaryCO;
import com.red.circle.console.app.dto.clienobject.team.SalarySettlementRecordCO;
import com.red.circle.framework.dto.PageResult;
import com.red.circle.other.inner.model.cmd.team.TeamSalaryBackQryCmd;
import com.red.circle.other.inner.model.cmd.team.bd.SalarySettlementRecordPageQryCmd;
/**
* 团队自动发送工资凭证.
@ -17,4 +19,10 @@ public interface TeamSalaryBackService {
*/
PageResult<TeamSalaryCO> listByCondition(TeamSalaryBackQryCmd query);
/**
* 查询 BD/BD Leader/Admin 工资结算记录.
*/
PageResult<SalarySettlementRecordCO> listSettlementRecords(
SalarySettlementRecordPageQryCmd query);
}

View File

@ -0,0 +1,32 @@
package com.red.circle.other.app.inner.endpoint.team.bd;
import com.red.circle.framework.dto.PageResult;
import com.red.circle.framework.dto.ResultResponse;
import com.red.circle.other.app.inner.service.team.SalarySettlementRecordClientService;
import com.red.circle.other.inner.endpoint.team.bd.api.SalarySettlementRecordClientApi;
import com.red.circle.other.inner.model.cmd.team.bd.SalarySettlementRecordPageQryCmd;
import com.red.circle.other.inner.model.dto.agency.bd.SalarySettlementRecordDTO;
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* BD/BD Leader/Admin 工资结算记录.
*
* @author AI Assistant
*/
@RestController
@RequestMapping(value = SalarySettlementRecordClientApi.API_PREFIX,
produces = MediaType.APPLICATION_JSON_VALUE)
@RequiredArgsConstructor
public class SalarySettlementRecordClientEndpoint implements SalarySettlementRecordClientApi {
private final SalarySettlementRecordClientService salarySettlementRecordClientService;
@Override
public ResultResponse<PageResult<SalarySettlementRecordDTO>> page(
SalarySettlementRecordPageQryCmd qryCmd) {
return ResultResponse.success(salarySettlementRecordClientService.page(qryCmd));
}
}

View File

@ -0,0 +1,18 @@
package com.red.circle.other.app.inner.service.team;
import com.red.circle.framework.dto.PageResult;
import com.red.circle.other.inner.model.cmd.team.bd.SalarySettlementRecordPageQryCmd;
import com.red.circle.other.inner.model.dto.agency.bd.SalarySettlementRecordDTO;
/**
* BD/BD Leader/Admin 工资结算记录.
*
* @author AI Assistant
*/
public interface SalarySettlementRecordClientService {
/**
* 分页查询工资结算记录.
*/
PageResult<SalarySettlementRecordDTO> page(SalarySettlementRecordPageQryCmd qryCmd);
}

View File

@ -0,0 +1,310 @@
package com.red.circle.other.app.inner.service.team.impl;
import com.red.circle.framework.dto.PageQuery;
import com.red.circle.framework.dto.PageResult;
import com.red.circle.other.app.inner.service.team.SalarySettlementRecordClientService;
import com.red.circle.other.infra.database.mongo.entity.bd.AdminSalarySettlementRecord;
import com.red.circle.other.infra.database.mongo.entity.bd.BdLeaderSalarySettlementRecord;
import com.red.circle.other.infra.database.mongo.entity.bd.BdSalarySettlementRecord;
import com.red.circle.other.infra.enums.BdSettlementStatus;
import com.red.circle.other.inner.model.cmd.team.bd.SalarySettlementRecordPageQryCmd;
import com.red.circle.other.inner.model.dto.agency.bd.SalarySettlementRecordDTO;
import com.red.circle.tool.core.text.StringUtils;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.function.Function;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Sort;
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;
/**
* BD/BD Leader/Admin 工资结算记录.
*
* @author AI Assistant
*/
@Service
@RequiredArgsConstructor
public class SalarySettlementRecordClientServiceImpl implements SalarySettlementRecordClientService {
private static final String IDENTITY_BD = "BD";
private static final String IDENTITY_BD_LEADER = "BD_LEADER";
private static final String IDENTITY_ADMIN = "ADMIN";
private static final int DEFAULT_CURSOR = 1;
private static final int DEFAULT_LIMIT = 20;
private final MongoTemplate mongoTemplate;
@Override
public PageResult<SalarySettlementRecordDTO> page(SalarySettlementRecordPageQryCmd qryCmd) {
SalarySettlementRecordPageQryCmd query = Objects.requireNonNullElseGet(qryCmd,
SalarySettlementRecordPageQryCmd::new);
PageBounds pageBounds = getPageBounds(query);
String identity = normalizeIdentity(query.getIdentity());
if (StringUtils.isNotBlank(query.getIdentity()) && !isSupportedIdentity(identity)) {
return buildPage(Collections.emptyList(), 0, pageBounds);
}
if (StringUtils.isNotBlank(identity)) {
return pageSingleIdentity(query, identity, pageBounds);
}
List<SalarySettlementRecordDTO> records = new ArrayList<>();
long total = 0;
total += count(query, "bdUserId", BdSalarySettlementRecord.class);
total += count(query, "bdLeaderUserId", BdLeaderSalarySettlementRecord.class);
total += count(query, "adminUserId", AdminSalarySettlementRecord.class);
records.addAll(listRecords(query, "bdUserId", BdSalarySettlementRecord.class,
this::toBdRecordDTO, 0, pageBounds.fetchSize()));
records.addAll(listRecords(query, "bdLeaderUserId", BdLeaderSalarySettlementRecord.class,
this::toBdLeaderRecordDTO, 0, pageBounds.fetchSize()));
records.addAll(listRecords(query, "adminUserId", AdminSalarySettlementRecord.class,
this::toAdminRecordDTO, 0, pageBounds.fetchSize()));
// 三类记录分散在不同 collection全部身份查询只各取当前页所需前 N 条再合并排序
records.sort((left, right) -> compareTimestampDesc(left.getCreateTime(), right.getCreateTime()));
Collection<SalarySettlementRecordDTO> pageRecords = slice(records, pageBounds);
return buildPage(pageRecords, total, pageBounds);
}
private PageResult<SalarySettlementRecordDTO> pageSingleIdentity(
SalarySettlementRecordPageQryCmd query, String identity, PageBounds pageBounds) {
if (Objects.equals(identity, IDENTITY_BD)) {
return pageTypedRecords(query, "bdUserId", BdSalarySettlementRecord.class,
this::toBdRecordDTO, pageBounds);
}
if (Objects.equals(identity, IDENTITY_BD_LEADER)) {
return pageTypedRecords(query, "bdLeaderUserId", BdLeaderSalarySettlementRecord.class,
this::toBdLeaderRecordDTO, pageBounds);
}
return pageTypedRecords(query, "adminUserId", AdminSalarySettlementRecord.class,
this::toAdminRecordDTO, pageBounds);
}
private <T> PageResult<SalarySettlementRecordDTO> pageTypedRecords(
SalarySettlementRecordPageQryCmd query, String userIdField, Class<T> entityClass,
Function<T, SalarySettlementRecordDTO> mapper, PageBounds pageBounds) {
long total = count(query, userIdField, entityClass);
List<SalarySettlementRecordDTO> records = listRecords(query, userIdField, entityClass, mapper,
pageBounds.skip(), pageBounds.limit());
return buildPage(records, total, pageBounds);
}
private PageBounds getPageBounds(SalarySettlementRecordPageQryCmd query) {
PageQuery pageQuery = query.getPageQuery();
int cursor = pageQuery == null || pageQuery.getCursor() == null ? DEFAULT_CURSOR
: pageQuery.getCursor();
int limit = pageQuery == null || pageQuery.getLimit() == null ? DEFAULT_LIMIT
: pageQuery.getLimit();
int safeCursor = cursor <= 0 ? DEFAULT_CURSOR : cursor;
int safeLimit = limit <= 0 ? DEFAULT_LIMIT : limit;
return new PageBounds(safeCursor, safeLimit);
}
private Collection<SalarySettlementRecordDTO> slice(List<SalarySettlementRecordDTO> records,
PageBounds pageBounds) {
int fromIndex = Math.max(pageBounds.skip(), 0);
int toIndex = Math.min(fromIndex + pageBounds.limit(), records.size());
return fromIndex >= records.size()
? Collections.emptyList()
: records.subList(fromIndex, toIndex);
}
private PageResult<SalarySettlementRecordDTO> buildPage(
Collection<SalarySettlementRecordDTO> records, long total, PageBounds pageBounds) {
PageResult<SalarySettlementRecordDTO> pageResult = new PageResult<>();
pageResult.setCurrent(pageBounds.cursor());
pageResult.setSize(pageBounds.limit());
pageResult.setTotal(total);
pageResult.setRecords(records);
return pageResult;
}
private <T> long count(SalarySettlementRecordPageQryCmd query, String userIdField,
Class<T> entityClass) {
return mongoTemplate.count(buildQuery(query, userIdField), entityClass);
}
private <T> List<SalarySettlementRecordDTO> listRecords(SalarySettlementRecordPageQryCmd query,
String userIdField, Class<T> entityClass, Function<T, SalarySettlementRecordDTO> mapper,
int skip, int limit) {
Query mongoQuery = buildQuery(query, userIdField);
mongoQuery.skip(skip);
mongoQuery.limit(limit);
return mongoTemplate.find(mongoQuery, entityClass)
.stream()
.map(mapper)
.toList();
}
private Query buildQuery(SalarySettlementRecordPageQryCmd query, String userIdField) {
List<Criteria> criteriaList = new ArrayList<>();
if (StringUtils.isNotBlank(query.getSysOrigin())) {
criteriaList.add(Criteria.where("sysOrigin").is(query.getSysOrigin()));
}
if (Objects.nonNull(query.getUserId())) {
criteriaList.add(Criteria.where(userIdField).is(query.getUserId()));
}
if (Objects.nonNull(query.getBillBelong())) {
criteriaList.add(Criteria.where("billBelong").is(query.getBillBelong()));
}
if (StringUtils.isNotBlank(query.getStatus())) {
BdSettlementStatus status = parseStatus(query.getStatus());
criteriaList.add(Objects.isNull(status)
? Criteria.where("_id").is("__invalid_status__")
: Criteria.where("status").is(status));
}
if (StringUtils.isNotBlank(query.getBizNo())) {
criteriaList.add(Criteria.where("bizNo").is(query.getBizNo()));
}
Query mongoQuery = criteriaList.isEmpty()
? new Query()
: new Query(new Criteria().andOperator(criteriaList.toArray(new Criteria[0])));
mongoQuery.with(Sort.by(Sort.Order.desc("createTime")));
return mongoQuery;
}
private BdSettlementStatus parseStatus(String status) {
try {
return BdSettlementStatus.valueOf(status.trim().toUpperCase(Locale.ROOT));
} catch (IllegalArgumentException ex) {
return null;
}
}
private String normalizeIdentity(String identity) {
return StringUtils.isBlank(identity) ? null : identity.trim().toUpperCase(Locale.ROOT);
}
private boolean isSupportedIdentity(String identity) {
return Objects.equals(identity, IDENTITY_BD)
|| Objects.equals(identity, IDENTITY_BD_LEADER)
|| Objects.equals(identity, IDENTITY_ADMIN);
}
private int compareTimestampDesc(Timestamp left, Timestamp right) {
if (Objects.isNull(left) && Objects.isNull(right)) {
return 0;
}
if (Objects.isNull(left)) {
return 1;
}
if (Objects.isNull(right)) {
return -1;
}
return right.compareTo(left);
}
private SalarySettlementRecordDTO toBdRecordDTO(BdSalarySettlementRecord record) {
String status = record.getStatus() == null ? null : record.getStatus().name();
String hitPolicyType = record.getHitPolicyType() == null ? null : record.getHitPolicyType().name();
return new SalarySettlementRecordDTO()
.setId(record.getId())
.setTimeId(record.getTimeId())
.setSysOrigin(record.getSysOrigin())
.setIdentity(IDENTITY_BD)
.setUserId(record.getBdUserId())
.setBdUserId(record.getBdUserId())
.setIsAdmin(record.getIsAdmin())
.setBillBelong(record.getBillBelong())
.setBillTitle(record.getBillTitle())
.setAgencyNumber(record.getAgencyNumber())
.setTeamSalaryAmount(record.getTeamSalaryAmount())
.setTeamRechargeAmount(record.getTeamRechargeAmount())
.setHitPolicyType(hitPolicyType)
.setHitLevel(record.getHitLevel())
.setCommissionRate(record.getCommissionRate())
.setSettlementSalary(record.getSettlementSalary())
.setStatus(status)
.setFailureReason(record.getFailureReason())
.setBizNo(record.getBizNo())
.setCreateTime(record.getCreateTime())
.setUpdateTime(record.getUpdateTime());
}
private SalarySettlementRecordDTO toBdLeaderRecordDTO(BdLeaderSalarySettlementRecord record) {
String status = record.getStatus() == null ? null : record.getStatus().name();
return new SalarySettlementRecordDTO()
.setId(record.getId())
.setTimeId(record.getTimeId())
.setSysOrigin(record.getSysOrigin())
.setIdentity(IDENTITY_BD_LEADER)
.setUserId(record.getBdLeaderUserId())
.setBdLeaderUserId(record.getBdLeaderUserId())
.setIsAdmin(record.getIsAdmin())
.setBillBelong(record.getBillBelong())
.setBillTitle(record.getBillTitle())
.setBdNumber(record.getBdNumber())
.setTeamSalaryAmount(record.getTeamSalaryAmount())
.setHitLevel(record.getHitLevel())
.setBasicSalary(record.getBasicSalary())
.setRewardAmount(record.getRewardAmount())
.setRewardEligible(record.getRewardEligible())
.setNewRechargeAgentCount(record.getNewRechargeAgentCount())
.setSettlementSalary(record.getSettlementSalary())
.setStatus(status)
.setFailureReason(record.getFailureReason())
.setBizNo(record.getBizNo())
.setCreateTime(record.getCreateTime())
.setUpdateTime(record.getUpdateTime());
}
private SalarySettlementRecordDTO toAdminRecordDTO(AdminSalarySettlementRecord record) {
String status = record.getStatus() == null ? null : record.getStatus().name();
return new SalarySettlementRecordDTO()
.setId(record.getId())
.setTimeId(record.getTimeId())
.setSysOrigin(record.getSysOrigin())
.setIdentity(IDENTITY_ADMIN)
.setUserId(record.getAdminUserId())
.setAdminUserId(record.getAdminUserId())
.setBillBelong(record.getBillBelong())
.setBillTitle(record.getBillTitle())
.setBizNo(record.getBizNo())
.setRechargeAgentAmount(record.getRechargeAgentAmount())
.setRechargeAgentSalary(record.getRechargeAgentSalary())
.setRechargeAgentPercentage(record.getRechargeAgentPercentage())
.setBdLeaderTeamSalary(record.getBdLeaderTeamSalary())
.setBdLeaderSalary(record.getBdLeaderSalary())
.setBdLeaderHitLevel(record.getBdLeaderHitLevel())
.setBdLeaderBasicSalary(record.getBdLeaderBasicSalary())
.setBdLeaderRewardAmount(record.getBdLeaderRewardAmount())
.setBdTeamSalaryAmount(record.getBdTeamSalaryAmount())
.setBdTeamSalarySalary(record.getBdTeamSalarySalary())
.setBdTeamSalaryHitLevel(record.getBdTeamSalaryHitLevel())
.setBdTeamSalaryCommissionRate(record.getBdTeamSalaryCommissionRate())
.setBdTeamRechargeAmount(record.getBdTeamRechargeAmount())
.setBdTeamRechargeSalary(record.getBdTeamRechargeSalary())
.setBdTeamRechargeHitLevel(record.getBdTeamRechargeHitLevel())
.setBdTeamRechargeCommissionRate(record.getBdTeamRechargeCommissionRate())
.setBdPolicyFinalSalary(record.getBdPolicyFinalSalary())
.setBdPolicyHitType(record.getBdPolicyHitType())
.setTotalSalary(record.getTotalSalary())
.setSettlementSalary(record.getTotalSalary())
.setStatus(status)
.setFailureReason(record.getFailureReason())
.setCreateTime(record.getCreateTime())
.setUpdateTime(record.getUpdateTime());
}
private record PageBounds(int cursor, int limit) {
private int skip() {
return Math.max((cursor - 1) * limit, 0);
}
private int fetchSize() {
long size = (long) cursor * limit;
return size > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) size;
}
}
}