Commit 116b22c7 by zhangxingmin

push

parent 3848bb2f
package com.yd.csf.api.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* <p>
* 评定职级表 前端控制器
* </p>
*
* @author zxm
* @since 2026-04-20
*/
@RestController
@RequestMapping("/evaluateRank")
public class EvaluateRankController {
}
package com.yd.csf.api.handler;
import com.xxl.job.core.handler.annotation.XxlJob;
import com.yd.common.enums.CommonEnum;
import com.yd.common.utils.RandomStringGenerator;
import com.yd.csf.feign.enums.OprSourceEnum;
import com.yd.csf.service.dto.GradeQueryParam;
import com.yd.csf.service.model.AgentAccumulatedFyc;
import com.yd.csf.service.model.AgentAccumulatedFycLog;
import com.yd.csf.service.model.EvaluateRank;
import com.yd.csf.service.model.MemberGradeConfig;
import com.yd.csf.service.service.IAgentAccumulatedFycLogService;
import com.yd.csf.service.service.IAgentAccumulatedFycService;
import com.yd.csf.service.service.IEvaluateRankService;
import com.yd.csf.service.service.IMemberGradeConfigService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 评定职级定时任务处理器 - XXL-Job定时任务执行器
* 使用@XxlJob注解方式
*/
@Component
@Slf4j
public class EvaluateRankJobHandler {
@Resource
private IAgentAccumulatedFycService iAgentAccumulatedFycService;
@Resource
private IAgentAccumulatedFycLogService iAgentAccumulatedFycLogService;
@Resource
private IEvaluateRankService iEvaluateRankService;
@Resource
private IMemberGradeConfigService iMemberGradeConfigService;
/**
* 评定职级定时任务,每月1号0点执行(XXL-Job任务执行入口方法)
*/
@XxlJob("evaluateRankJobHandler")
public void execute() throws Exception {
//查询积分总表记录(所有转介人积分总表记录)
List<AgentAccumulatedFyc> allList = iAgentAccumulatedFycService.queryAll();
if (CollectionUtils.isEmpty(allList)) {
return;
}
//过滤出转介人唯一业务ID列表
List<String> agentIdList = allList.stream()
.map(AgentAccumulatedFyc::getAgentId)
.collect(Collectors.toList());
// 构建批量查询参数列表(业务员ID和晋升职级累计积分入参)
List<GradeQueryParam> paramList = allList.stream()
.map(fyc -> new GradeQueryParam(fyc.getAgentId(), fyc.getPromotion()))
.collect(Collectors.toList());
// 最新评定记录(用于获取上一次的 afterGrade / afterPromotion)
Map<String, EvaluateRank> latestRankMap = iEvaluateRankService.getLatestByBrokerBizIds(agentIdList);
//查询每个业务员已有的评定记录数量
Map<String, Long> rankCountMap = iEvaluateRankService.countGroupByBrokerBizId(agentIdList);
// 批量查询各业务员对应的等级配置
Map<String, MemberGradeConfig> gradeConfigMap = iMemberGradeConfigService.batchGetGradeByPromotion(paramList);
List<EvaluateRank> toSaveList = new ArrayList<>();
for (AgentAccumulatedFyc fyc : allList) {
String agentId = fyc.getAgentId();
Long existingCount = rankCountMap.getOrDefault(agentId, 0L);
//最新评定记录
EvaluateRank latestRank = latestRankMap.get(agentId);
//查询当前业务员的晋升职级累计积分对应的职级对象
MemberGradeConfig gradeConfig = gradeConfigMap.get(agentId);
EvaluateRank evaluateRank = new EvaluateRank();
evaluateRank.setBrokerBizId(fyc.getAgentId());
evaluateRank.setBrokerName(fyc.getAgentName());
//评定职级表唯一业务ID
evaluateRank.setEvaluateRankBizId(RandomStringGenerator.generateBizId16(CommonEnum.UID_TYPE_EVALUATE_RANK.getCode()));
if (existingCount == 0) {
//首次评级:评定前职级和评定前累计已生效积分 = 初始化职级和已生效积分
//评定前职级 = 初始化等级
evaluateRank.setBeforeGrade(fyc.getInitGrade());
//评定前晋升职级累计积分 = 初始化已生效积分
evaluateRank.setBeforePromotion(fyc.getInitEffect());
//评定后职级 = 晋升职级累计积分对应职级
evaluateRank.setAfterGrade(gradeConfig != null ? gradeConfig.getGradeCode() : "G1001");
//评定后晋升职级累计积分
evaluateRank.setAfterPromotion(fyc.getPromotion());
//查询评定结果
evaluateRank.setResult(getResult(evaluateRank.getBeforeGrade(),evaluateRank.getAfterGrade()));
//评定来源 1-每月1号0点定时评级
evaluateRank.setSource(1);
} else {
// 非首次评级
//评定前职级 = 当前业务员评定职级记录最新一条记录的评定后的职级
evaluateRank.setBeforeGrade(latestRank.getAfterGrade());
//评定前晋升职级累计积分 = 当前业务员评定职级记录最新一条记录的评定后的晋升职级累计积分
evaluateRank.setBeforePromotion(latestRank.getAfterPromotion());
//评定后职级 = 积分总表的晋升职级累计积分对应职级
evaluateRank.setAfterGrade(gradeConfig != null ? gradeConfig.getGradeCode() : "G1001");
//评定后晋升职级累计积分 = 积分总表的晋升职级累计积分
evaluateRank.setAfterPromotion(fyc.getPromotion());
//查询评定结果
evaluateRank.setResult(getResult(evaluateRank.getBeforeGrade(),evaluateRank.getAfterGrade()));
//评定来源 1-每月1号0点定时评级
evaluateRank.setSource(1);
}
//当前等级
fyc.setCurrentGradeCode(evaluateRank.getAfterGrade());
}
// 批量保存评定职级记录
if (CollectionUtils.isNotEmpty(toSaveList)) {
iEvaluateRankService.saveBatch(toSaveList);
}
//更新积分总表的当前等级
iAgentAccumulatedFycService.saveOrUpdateBatch(allList);
//新增积分总表操作日志表
List<AgentAccumulatedFycLog> addLogList = allList.stream().map(dto -> {
AgentAccumulatedFycLog log = new AgentAccumulatedFycLog();
BeanUtils.copyProperties(dto,log);
log.setFycBizId(dto.getFycBizId());
log.setFycLogBizId(RandomStringGenerator.generateBizId16(CommonEnum.UID_TYPE_AGENT_ACCUMULATED_FYC_LOG.getCode()));
//操作来源
log.setSourceType(OprSourceEnum.CALM_TASK_EVALUATE_RANK.getItemValue());
return log;
}).collect(Collectors.toList());
iAgentAccumulatedFycLogService.saveOrUpdateBatch(addLogList);
}
/**
* 查询评定结果(1-平级 2-升级 3-降级)
* @param beforeGrade 评定前职级
* @param afterGrade 评定后职级
* @return 1-平级 2-升级 3-降级
*/
public Integer getResult(String beforeGrade, String afterGrade) {
// 若任一参数为空,视为无法比较,默认返回平级(可根据业务调整)
if (beforeGrade == null || afterGrade == null) {
return 1;
}
// 提取职级代码中的数字部分,例如 "G1001" -> 1001
int beforeLevel = extractLevel(beforeGrade);
int afterLevel = extractLevel(afterGrade);
if (beforeLevel == afterLevel) {
return 1; // 平级
} else if (beforeLevel < afterLevel) {
return 2; // 升级
} else {
return 3; // 降级
}
}
/**
* 从职级代码中提取等级数值
* @param gradeCode 职级代码,如 "G1001"
* @return 数字部分,若格式异常则返回 0
*/
private int extractLevel(String gradeCode) {
if (gradeCode == null || gradeCode.length() < 2) {
return 0;
}
try {
// 去掉首字母 'G',解析剩余数字
return Integer.parseInt(gradeCode.substring(1));
} catch (NumberFormatException e) {
log.warn("Invalid grade code format: {}", gradeCode, e);
return 0;
}
}
}
\ No newline at end of file
......@@ -7,6 +7,7 @@ public enum OprSourceEnum {
CALM_TASK_FREEZE("冷静期定时计算-冻结","CALM_TASK_FREEZE"),
CALM_TASK_DEFROST("冷静期定时计算-解冻","CALM_TASK_DEFROST"),
CALM_TASK_EVALUATE_RANK("评级定时任务计算职级","CALM_TASK_EVALUATE_RANK"),
;
//字典项标签(名称)
......
package com.yd.csf.service.dao;
import com.yd.csf.service.model.EvaluateRank;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* 评定职级表 Mapper 接口
* </p>
*
* @author zxm
* @since 2026-04-20
*/
public interface EvaluateRankMapper extends BaseMapper<EvaluateRank> {
}
package com.yd.csf.service.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
/**
* 职级评定批量查询入参
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class GradeQueryParam {
/**
* 业务员唯一业务ID
*/
private String agentId;
/**
* 晋升职级累计积分
*/
private BigDecimal promotionValue;
}
\ No newline at end of file
......@@ -45,6 +45,24 @@ public class AgentAccumulatedFyc implements Serializable {
private String agentId;
/**
* 业务员名称
*/
@TableField("agent_name")
private String agentName;
/**
* 初始化已生效积分(不限业务场景)
*/
@TableField("init_effect")
private BigDecimal initEffect;
/**
* 初始化等级(初始化已生效积分对应等级)
*/
@TableField("init_grade")
private String initGrade;
/**
* 未生效累计积分(不限业务场景)
*/
@TableField("no_effect")
......
......@@ -56,6 +56,24 @@ public class AgentAccumulatedFycLog implements Serializable {
private String agentId;
/**
* 业务员名称
*/
@TableField("agent_name")
private String agentName;
/**
* 初始化已生效积分(不限业务场景)
*/
@TableField("init_effect")
private BigDecimal initEffect;
/**
* 初始化等级(初始化已生效积分对应等级)
*/
@TableField("init_grade")
private String initGrade;
/**
* 未生效累计积分(不限业务场景)
*/
@TableField("no_effect")
......
package com.yd.csf.service.model;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import lombok.Getter;
import lombok.Setter;
/**
* <p>
* 评定职级表
* </p>
*
* @author zxm
* @since 2026-04-20
*/
@Getter
@Setter
@TableName("evaluate_rank")
public class EvaluateRank implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 评定职级表唯一业务ID
*/
@TableField("evaluate_rank_biz_id")
private String evaluateRankBizId;
/**
* 评定来源 1-每月1号0点定时评级
*/
@TableField("source")
private Integer source;
/**
* 转介人名称
*/
@TableField("broker_name")
private String brokerName;
/**
* 转介人业务id
*/
@TableField("broker_biz_id")
private String brokerBizId;
/**
* 评定结果(1-平级 2-升级 3-降级)
*/
@TableField("result")
private Integer result;
/**
* 评定前职级
*/
@TableField("before_grade")
private String beforeGrade;
/**
* 评定后职级
*/
@TableField("after_grade")
private String afterGrade;
/**
* 评定前晋升职级累计积分(晋升职级累计积分 = 已生效累计积分 - 已生效累计非首期佣金积分值)
*/
@TableField("before_promotion")
private BigDecimal beforePromotion;
/**
* 评定后晋升职级累计积分(晋升职级累计积分 = 已生效累计积分 - 已生效累计非首期佣金积分值)
*/
@TableField("after_promotion")
private BigDecimal afterPromotion;
/**
* 状态(0:停用 1:启用)
*/
@TableField("status")
private Integer status;
/**
* 所属租户唯一业务ID(冗余)
*/
@TableField("tenant_biz_id")
private String tenantBizId;
/**
* 所属项目唯一业务ID(冗余)
*/
@TableField("project_biz_id")
private String projectBizId;
/**
* 通用备注
*/
@TableField("remark")
private String remark;
/**
* 删除标识: 0-正常, 1-删除
*/
@TableField("is_deleted")
private Integer isDeleted;
/**
* 创建人ID
*/
@TableField("creator_id")
private String creatorId;
/**
* 更新人ID
*/
@TableField("updater_id")
private String updaterId;
/**
* 创建时间
*/
@TableField("create_time")
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField("update_time")
private LocalDateTime updateTime;
}
......@@ -18,4 +18,6 @@ public interface IAgentAccumulatedFycService extends IService<AgentAccumulatedFy
AgentAccumulatedFyc queryOne(String agentId);
List<AgentAccumulatedFyc> queryList(List<String> agentIdList);
List<AgentAccumulatedFyc> queryAll();
}
package com.yd.csf.service.service;
import com.yd.csf.service.model.EvaluateRank;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
import java.util.Map;
/**
* <p>
* 评定职级表 服务类
* </p>
*
* @author zxm
* @since 2026-04-20
*/
public interface IEvaluateRankService extends IService<EvaluateRank> {
/**
* 根据转介人业务ID列表统计每个转介人的评定记录数量
*
* @param brokerBizIdList 转介人业务ID列表
* @return Map,key=brokerBizId,value=记录数量
*/
Map<String, Long> countGroupByBrokerBizId(List<String> brokerBizIdList);
/**
* 根据业务员ID列表,获取每个业务员最新一条评定记录(按创建时间倒序)
* @param brokerBizIdList 业务员ID列表
* @return Map<brokerBizId, EvaluateRank> 最新评定记录
*/
Map<String, EvaluateRank> getLatestByBrokerBizIds(List<String> brokerBizIdList);
}
package com.yd.csf.service.service;
import com.yd.csf.service.dto.GradeQueryParam;
import com.yd.csf.service.model.MemberGradeConfig;
import com.baomidou.mybatisplus.extension.service.IService;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
/**
* <p>
......@@ -15,5 +18,10 @@ import java.math.BigDecimal;
*/
public interface IMemberGradeConfigService extends IService<MemberGradeConfig> {
MemberGradeConfig queryOne(BigDecimal fycValue);
/**
* 批量根据积分值查询对应的等级配置
* @param paramList 业务员积分参数列表
* @return Map<业务员ID, 匹配的等级配置>(未匹配到的不包含在Map中)
*/
Map<String, MemberGradeConfig> batchGetGradeByPromotion(List<GradeQueryParam> paramList);
}
......@@ -38,4 +38,9 @@ public class AgentAccumulatedFycServiceImpl extends ServiceImpl<AgentAccumulated
.in(CollectionUtils.isNotEmpty(agentIdList),AgentAccumulatedFyc::getAgentId,agentIdList));
}
@Override
public List<AgentAccumulatedFyc> queryAll() {
return this.baseMapper.selectList(null);
}
}
package com.yd.csf.service.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.yd.csf.service.model.EvaluateRank;
import com.yd.csf.service.dao.EvaluateRankMapper;
import com.yd.csf.service.service.IEvaluateRankService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* <p>
* 评定职级表 服务实现类
* </p>
*
* @author zxm
* @since 2026-04-20
*/
@Service
public class EvaluateRankServiceImpl extends ServiceImpl<EvaluateRankMapper, EvaluateRank> implements IEvaluateRankService {
/**
* 根据转介人业务ID列表统计每个转介人的评定记录数量
*
* @param brokerBizIdList 转介人业务ID列表
* @return Map,key=brokerBizId,value=记录数量
*/
@Override
public Map<String, Long> countGroupByBrokerBizId(List<String> brokerBizIdList) {
if (CollectionUtils.isEmpty(brokerBizIdList)) {
return Collections.emptyMap();
}
QueryWrapper<EvaluateRank> queryWrapper = new QueryWrapper<>();
queryWrapper.select("broker_biz_id", "count(1) as count")
.in("broker_biz_id", brokerBizIdList)
.eq("is_deleted", 0) // 只统计未删除的记录
.groupBy("broker_biz_id");
List<Map<String, Object>> list = this.listMaps(queryWrapper);
return list.stream().collect(Collectors.toMap(
map -> (String) map.get("broker_biz_id"),
map -> (Long) map.get("count")
));
}
/**
* 根据业务员ID列表,获取每个业务员最新一条评定记录(按创建时间倒序)
* @param brokerBizIdList 业务员ID列表
* @return Map<brokerBizId, EvaluateRank> 最新评定记录
*/
@Override
public Map<String, EvaluateRank> getLatestByBrokerBizIds(List<String> brokerBizIdList) {
if (CollectionUtils.isEmpty(brokerBizIdList)) {
return Collections.emptyMap();
}
// 使用子查询或窗口函数取每个业务员最新一条
QueryWrapper<EvaluateRank> wrapper = new QueryWrapper<>();
wrapper.in("broker_biz_id", brokerBizIdList)
.eq("is_deleted", 0)
.orderByDesc("create_time"); // 按创建时间倒序
List<EvaluateRank> allList = this.list(wrapper);
// 按 brokerBizId 分组,每组取第一条即为最新
return allList.stream().collect(Collectors.toMap(
EvaluateRank::getBrokerBizId,
Function.identity(),
(existing, replacement) -> existing // 遇到重复 key 保留第一个(即最新的)
));
}
}
package com.yd.csf.service.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.yd.csf.service.dto.GradeQueryParam;
import com.yd.csf.service.model.MemberGradeConfig;
import com.yd.csf.service.dao.MemberGradeConfigMapper;
import com.yd.csf.service.service.IMemberGradeConfigService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.*;
/**
* <p>
......@@ -17,24 +19,80 @@ import java.math.BigDecimal;
* @author zxm
* @since 2025-10-15
*/
@Slf4j
@Service
public class MemberGradeConfigServiceImpl extends ServiceImpl<MemberGradeConfigMapper, MemberGradeConfig> implements IMemberGradeConfigService {
/**
* 根据积分查询范围内的等级
* @param fycValue
* @return
* 批量根据积分值查询对应的等级配置
* @param paramList 业务员积分参数列表
* @return Map<业务员ID, 匹配的等级配置>(未匹配到的不包含在Map中)
*/
@Override
public MemberGradeConfig queryOne(BigDecimal fycValue) {
LambdaQueryWrapper<MemberGradeConfig> wrapper = new LambdaQueryWrapper<>();
wrapper.le(MemberGradeConfig::getMinValue, fycValue) // min_value <= fycValue
.and(w -> w.isNull(MemberGradeConfig::getMaxValue) // max_value IS NULL
.or()
.gt(MemberGradeConfig::getMaxValue, fycValue) // 或 fycValue < max_value
)
public Map<String, MemberGradeConfig> batchGetGradeByPromotion(List<GradeQueryParam> paramList) {
if (CollectionUtils.isEmpty(paramList)) {
return Collections.emptyMap();
}
// 一次性查询所有启用且未删除的等级配置
List<MemberGradeConfig> allGrades = this.lambdaQuery()
.eq(MemberGradeConfig::getStatus, 1)
.last(" limit 1");
return getOne(wrapper);
.eq(MemberGradeConfig::getIsDeleted, 0)
.list();
if (CollectionUtils.isEmpty(allGrades)) {
log.warn("未查询到任何启用的等级配置");
return Collections.emptyMap();
}
// 按 minValue 升序排序,确保后续匹配的稳定性
allGrades.sort(Comparator.comparing(MemberGradeConfig::getMinValue));
// 构建结果 Map
Map<String, MemberGradeConfig> resultMap = new HashMap<>();
for (GradeQueryParam param : paramList) {
String agentId = param.getAgentId();
BigDecimal promotionValue = param.getPromotionValue();
if (promotionValue == null) {
log.warn("业务员 {} 的晋升积分为空,跳过", agentId);
continue;
}
// 在内存中匹配等级(左闭右开区间)
MemberGradeConfig matched = allGrades.stream()
.filter(g -> g.getMinValue().compareTo(promotionValue) <= 0
&& g.getMaxValue().compareTo(promotionValue) > 0)
.findFirst()
.orElse(null);
if (matched != null) {
resultMap.put(agentId, matched);
} else {
log.warn("业务员 {} 的晋升积分 {} 未匹配到任何等级", agentId, promotionValue);
}
}
// 检测是否有多个配置匹配同一积分(配置错误告警)
checkGradeOverlap(allGrades, paramList);
return resultMap;
}
/**
* 检查等级区间是否有重叠(用于运维告警)
*/
private void checkGradeOverlap(List<MemberGradeConfig> allGrades, List<GradeQueryParam> paramList) {
// 简单检查:遍历参数,统计每个积分值匹配到的配置数量,超过1则告警
for (GradeQueryParam param : paramList) {
long count = allGrades.stream()
.filter(g -> g.getMinValue().compareTo(param.getPromotionValue()) <= 0
&& g.getMaxValue().compareTo(param.getPromotionValue()) > 0)
.count();
if (count > 1) {
log.error("配置错误:业务员 {} 的积分 {} 匹配到 {} 个等级配置,请检查区间重叠!",
param.getAgentId(), param.getPromotionValue(), count);
}
}
}
}
......@@ -21,7 +21,7 @@ public class MyBatisPlusCodeGenerator {
})
.strategyConfig(builder -> {
builder.addInclude(
"agent_accumulated_fyc_log"
"evaluate_rank"
)
.entityBuilder()
......
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yd.csf.service.dao.EvaluateRankMapper">
</mapper>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment