|
|
@@ -0,0 +1,538 @@
|
|
|
+package nckd.jxccl.hr.psms.plugin.operate.performance.validate;
|
|
|
+
|
|
|
+import kd.bos.dataentity.entity.DynamicObject;
|
|
|
+import kd.bos.dataentity.entity.DynamicObjectCollection;
|
|
|
+import kd.bos.entity.ExtendedDataEntity;
|
|
|
+import kd.bos.entity.validate.AbstractValidator;
|
|
|
+import kd.bos.orm.query.QFilter;
|
|
|
+import kd.bos.servicehelper.QueryServiceHelper;
|
|
|
+import nckd.jxccl.base.common.constant.FormConstant;
|
|
|
+import nckd.jxccl.base.common.enums.AppraisalResultEnum;
|
|
|
+import nckd.jxccl.base.common.utils.DateUtil;
|
|
|
+import nckd.jxccl.base.common.utils.QueryFieldBuilder;
|
|
|
+import nckd.jxccl.base.common.utils.StrFormatter;
|
|
|
+import nckd.jxccl.base.orm.helper.QFilterCommonHelper;
|
|
|
+import nckd.jxccl.hr.psms.common.PerfRankMgmtConstant;
|
|
|
+import nckd.jxccl.hr.psms.common.PositionStructureConstant;
|
|
|
+
|
|
|
+import java.math.BigDecimal;
|
|
|
+import java.math.RoundingMode;
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.Collections;
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.HashSet;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+import java.util.Set;
|
|
|
+
|
|
|
+/**
|
|
|
+* 绩效排名管理保存验证插件
|
|
|
+* @author W.Y.C
|
|
|
+* @date 2025/10/21 22:39
|
|
|
+* @version 1.0
|
|
|
+*/
|
|
|
+public class PerfRankMgmtSaveValidate extends AbstractValidator {
|
|
|
+
|
|
|
+ private final Map<String, BigDecimal> appraisalResultRatioMap = new HashMap<>();
|
|
|
+ @Override
|
|
|
+ public void validate() {
|
|
|
+ QueryFieldBuilder queryFieldBuilder = QueryFieldBuilder.create()
|
|
|
+ .addGroup(new String[]{FormConstant.NCKD_ENTRYENTITY}, PerfRankMgmtConstant.NCKD_RATIO)
|
|
|
+ .addGroup(new String[]{FormConstant.NCKD_ENTRYENTITY, PositionStructureConstant.NCKD_APPRAISALRESULT}, FormConstant.NUMBER_KEY);
|
|
|
+ DynamicObjectCollection query = QueryServiceHelper.query(PerfRankMgmtConstant.RANKRATIOCONF_ENTITYID, queryFieldBuilder.buildSelect(), new QFilter[]{QFilterCommonHelper.getEnableFilter()});
|
|
|
+
|
|
|
+ query.forEach(dynamicObject -> {
|
|
|
+ String key = dynamicObject.getString(String.join(".",FormConstant.NCKD_ENTRYENTITY, PositionStructureConstant.NCKD_APPRAISALRESULT, FormConstant.NUMBER_KEY));
|
|
|
+ BigDecimal value = dynamicObject.getBigDecimal(String.join(".",FormConstant.NCKD_ENTRYENTITY, PerfRankMgmtConstant.NCKD_RATIO));
|
|
|
+ appraisalResultRatioMap.put(key, value);
|
|
|
+ });
|
|
|
+ for (ExtendedDataEntity rowDataEntity : getDataEntities()) {
|
|
|
+ if(appraisalResultRatioMap.isEmpty()){
|
|
|
+ this.addMessage(rowDataEntity, "没有配置【排名考核结果比例】");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ DynamicObject data = rowDataEntity.getDataEntity();
|
|
|
+ // 年份校验
|
|
|
+ if (validateYear(data, rowDataEntity)) {
|
|
|
+ // 获取明细数据并验证是否为空
|
|
|
+ DynamicObjectCollection entries = data.getDynamicObjectCollection(PerfRankMgmtConstant.NCKD_PERFRANKMGMTENTRY);
|
|
|
+ if (entries.isEmpty()) {
|
|
|
+ this.addMessage(rowDataEntity, "请添加排名名单");
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 验证明细数据
|
|
|
+ ValidationContext context = validateEntries(entries, rowDataEntity);
|
|
|
+
|
|
|
+ // 验证统计数据一致性
|
|
|
+ validateDataConsistency(data, context, rowDataEntity);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ //校验考核结果人数比例
|
|
|
+ private void validateRatio(ValidationContext context, ExtendedDataEntity rowDataEntity) {
|
|
|
+ // 总人数
|
|
|
+ int totalRankCount = context.totalRankCount;
|
|
|
+ // 优秀人数
|
|
|
+ int excellentCount = context.excellentCount;
|
|
|
+ // 合格人数
|
|
|
+ int qualifiedCount = context.qualifiedCount;
|
|
|
+ // 基本合格人数
|
|
|
+ int basicCount = context.basicCount;
|
|
|
+ // 不合格人数
|
|
|
+ int failCount = context.failCount;
|
|
|
+
|
|
|
+ // 获取配置比例
|
|
|
+ BigDecimal excellentRatio = appraisalResultRatioMap.get(AppraisalResultEnum.EXCELLENT.getCode());
|
|
|
+ BigDecimal qualifiedRatio = appraisalResultRatioMap.get(AppraisalResultEnum.QUALIFIED.getCode());
|
|
|
+ BigDecimal basicallyQualifiedRatio = appraisalResultRatioMap.get(AppraisalResultEnum.BASICALLY_QUALIFIED.getCode());
|
|
|
+ BigDecimal unQualifiedRatio = appraisalResultRatioMap.get(AppraisalResultEnum.UN_QUALIFIED.getCode());
|
|
|
+
|
|
|
+ // 计算各等级的理论人数(四舍五入)
|
|
|
+ int expectedExcellent = calculateExpectedCount(totalRankCount, excellentRatio);
|
|
|
+ int expectedQualified = calculateExpectedCount(totalRankCount, qualifiedRatio);
|
|
|
+ int expectedBasic = calculateExpectedCount(totalRankCount, basicallyQualifiedRatio);
|
|
|
+ int expectedFail = calculateExpectedCount(totalRankCount, unQualifiedRatio);
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ // 校验各等级人数是否在合理范围内(允许±1的差异)
|
|
|
+ validateCountRange(AppraisalResultEnum.EXCELLENT.getName(), excellentCount, expectedExcellent, totalRankCount,rowDataEntity);
|
|
|
+ validateCountRange(AppraisalResultEnum.QUALIFIED.getName(), qualifiedCount, expectedQualified, totalRankCount,rowDataEntity);
|
|
|
+ validateCountRange(AppraisalResultEnum.BASICALLY_QUALIFIED.getName(), basicCount, expectedBasic, totalRankCount,rowDataEntity);
|
|
|
+ validateCountRange(AppraisalResultEnum.UN_QUALIFIED.getName(), failCount, expectedFail, totalRankCount,rowDataEntity);
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算理论人数(四舍五入)
|
|
|
+ */
|
|
|
+ private int calculateExpectedCount(int totalCount, BigDecimal ratio) {
|
|
|
+ // 将百分比转换为小数进行计算
|
|
|
+ BigDecimal decimalRatio = ratio.divide(BigDecimal.valueOf(100), 4, RoundingMode.HALF_UP);
|
|
|
+ BigDecimal expected = decimalRatio.multiply(BigDecimal.valueOf(totalCount));
|
|
|
+ return expected.setScale(0, RoundingMode.HALF_UP).intValue();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 校验人数范围(允许±1的差异)
|
|
|
+ */
|
|
|
+ private void validateCountRange(String levelName, int actualCount, int expectedCount, int totalCount,ExtendedDataEntity rowDataEntity) {
|
|
|
+ int difference = Math.abs(actualCount - expectedCount);
|
|
|
+ // 允许的差异阈值,可以根据总人数动态调整
|
|
|
+ int allowedDifference = Math.max(1, totalCount / 100); // 至少允许1人差异,或总人数的1%
|
|
|
+
|
|
|
+ if (difference > allowedDifference) {
|
|
|
+
|
|
|
+ this.addMessage(rowDataEntity,
|
|
|
+ StrFormatter.format("{}人数与配置比例不符,应为{}人,实际为{}人",
|
|
|
+ levelName, expectedCount,actualCount));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 验证年份字段有效性
|
|
|
+ * @param data 数据对象
|
|
|
+ * @param rowDataEntity 行数据实体
|
|
|
+ * @return: void
|
|
|
+ * @author W.Y.C
|
|
|
+ * @date: 2025/10/21 22:40
|
|
|
+ */
|
|
|
+ private boolean validateYear(DynamicObject data, ExtendedDataEntity rowDataEntity) {
|
|
|
+ int theYear = data.getInt(PerfRankMgmtConstant.NCKD_THEYEAR);
|
|
|
+ int currentYear = DateUtil.now().getYear();
|
|
|
+ if (theYear < 1900 || theYear > currentYear) {
|
|
|
+ this.addMessage(rowDataEntity, "年份不合法或超过当前年份");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 验证明细数据
|
|
|
+ * @param entries 分录
|
|
|
+ * @param rowDataEntity 行数据实体
|
|
|
+ * @return: nckd.jxccl.hr.psms.plugin.operate.performance.validate.PerfRankMgmtSaveValidate.ValidationContext
|
|
|
+ * @author W.Y.C
|
|
|
+ * @date: 2025/10/21 22:40
|
|
|
+ */
|
|
|
+ private ValidationContext validateEntries(DynamicObjectCollection entries, ExtendedDataEntity rowDataEntity) {
|
|
|
+ ValidationContext context = new ValidationContext();
|
|
|
+ Set<Long> personIds = new HashSet<>();
|
|
|
+
|
|
|
+ for (int i = 0; i < entries.size(); i++) {
|
|
|
+ DynamicObject entry = entries.get(i);
|
|
|
+ validateEntry(entry, rowDataEntity, i + 1, personIds, context);
|
|
|
+ }
|
|
|
+
|
|
|
+ return context;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 验证单条明细数据
|
|
|
+ * @param entry 分录
|
|
|
+ * @param rowDataEntity 行数据实体
|
|
|
+ * @param rowIndex 行索引
|
|
|
+ * @param personIds 已处理人员ID集合(用于去重检查)
|
|
|
+ *
|
|
|
+ * @param context 验证上下文
|
|
|
+ * @return: void
|
|
|
+ * @author W.Y.C
|
|
|
+ * @date: 2025/10/21 22:41
|
|
|
+ */
|
|
|
+ private void validateEntry(DynamicObject entry, ExtendedDataEntity rowDataEntity,
|
|
|
+ int rowIndex, Set<Long> personIds, ValidationContext context) {
|
|
|
+ DynamicObject person = entry.getDynamicObject(PerfRankMgmtConstant.NCKD_PERSON);
|
|
|
+ if (person == null) {
|
|
|
+ this.addMessage(rowDataEntity, StrFormatter.format("第{}行,请选择人员\n", rowIndex));
|
|
|
+ } else {
|
|
|
+ // 人员重复校验
|
|
|
+ long personId = person.getLong(FormConstant.ID_KEY);
|
|
|
+ if (personIds.contains(personId)) {
|
|
|
+ this.addMessage(rowDataEntity, StrFormatter.format("第{}行,人员【{}】重复;\n", rowIndex, person.getString(FormConstant.NAME_KEY)));
|
|
|
+ } else {
|
|
|
+ personIds.add(personId);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 统计各类人数
|
|
|
+ boolean isRanking = entry.getBoolean(PerfRankMgmtConstant.NCKD_ISRANKING);
|
|
|
+ boolean postAllowance = entry.getBoolean(PerfRankMgmtConstant.NCKD_POSTALLOWANCE);
|
|
|
+ DynamicObject appraisalResult = entry.getDynamicObject(PerfRankMgmtConstant.NCKD_APPRAISALRESULT);
|
|
|
+
|
|
|
+ if (isRanking) {
|
|
|
+ context.totalRankCount++;
|
|
|
+
|
|
|
+ if (postAllowance) {
|
|
|
+ context.allowanceRankCount++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (appraisalResult != null) {
|
|
|
+ String appraisalResultNumber = appraisalResult.getString(FormConstant.NUMBER_KEY);
|
|
|
+ AppraisalResultEnum appraisalResultEnum = AppraisalResultEnum.getByCode(appraisalResultNumber);
|
|
|
+
|
|
|
+ if (appraisalResultEnum != null) {
|
|
|
+ switch (appraisalResultEnum) {
|
|
|
+ case EXCELLENT:
|
|
|
+ context.excellentCount++;
|
|
|
+ break;
|
|
|
+ case BASICALLY_QUALIFIED:
|
|
|
+ context.basicCount++;
|
|
|
+ break;
|
|
|
+ case UN_QUALIFIED:
|
|
|
+ context.failCount++;
|
|
|
+ break;
|
|
|
+ case QUALIFIED:
|
|
|
+ context.qualifiedCount++;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 验证数据一致性
|
|
|
+ * @param data 数据对象
|
|
|
+ * @param context 验证上下文
|
|
|
+ * @param rowDataEntity 行数据实体
|
|
|
+ * @return: void
|
|
|
+ * @author W.Y.C
|
|
|
+ * @date: 2025/10/21 22:41
|
|
|
+ */
|
|
|
+ private void validateDataConsistency(DynamicObject data, ValidationContext context,
|
|
|
+ ExtendedDataEntity rowDataEntity) {
|
|
|
+ // 各类人数一致性校验
|
|
|
+ checkCountConsistency(data, context, rowDataEntity);
|
|
|
+
|
|
|
+ // 排名范围校验
|
|
|
+ validateRankRanges(data.getDynamicObjectCollection(PerfRankMgmtConstant.NCKD_PERFRANKMGMTENTRY),
|
|
|
+ data.getInt(PerfRankMgmtConstant.NCKD_TOPRANKS), rowDataEntity);
|
|
|
+
|
|
|
+ validateRatio(context, rowDataEntity);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 检查统计数据一致性
|
|
|
+ * @param data 数据对象
|
|
|
+ * @param context 验证上下文
|
|
|
+ * @param rowDataEntity 行数据实体
|
|
|
+ * @return: void
|
|
|
+ * @author W.Y.C
|
|
|
+ * @date: 2025/10/21 22:42
|
|
|
+ */
|
|
|
+ private void checkCountConsistency(DynamicObject data, ValidationContext context,
|
|
|
+ ExtendedDataEntity rowDataEntity) {
|
|
|
+ int topRanks = data.getInt(PerfRankMgmtConstant.NCKD_TOPRANKS);
|
|
|
+ int allowanceRanks = data.getInt(PerfRankMgmtConstant.NCKD_ALLOWANCERANKS);
|
|
|
+ int fails = data.getInt(PerfRankMgmtConstant.NCKD_FAILS);
|
|
|
+ int basics = data.getInt(PerfRankMgmtConstant.NCKD_BASICS);
|
|
|
+ int excellents = data.getInt(PerfRankMgmtConstant.NCKD_EXCELLENTS);
|
|
|
+
|
|
|
+ if (topRanks != context.totalRankCount) {
|
|
|
+ this.addMessage(rowDataEntity, "总排名人数与实际录入数据不一致;\n");
|
|
|
+ }
|
|
|
+ if (allowanceRanks != context.allowanceRankCount) {
|
|
|
+ this.addMessage(rowDataEntity, "R排名人数与实际录入数据不一致;\n");
|
|
|
+ }
|
|
|
+ if (fails != context.failCount) {
|
|
|
+ this.addMessage(rowDataEntity, "不合格人数与实际录入数据不一致;\n");
|
|
|
+ }
|
|
|
+ if (basics != context.basicCount) {
|
|
|
+ this.addMessage(rowDataEntity, "基本人数与实际录入数据不一致;\n");
|
|
|
+ }
|
|
|
+ if (excellents != context.excellentCount) {
|
|
|
+ this.addMessage(rowDataEntity, "优秀人数与实际录入数据不一致;\n");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 验证排名范围和重复情况
|
|
|
+ * @param entries 分录集合
|
|
|
+ * @param totalRankCount 总排名数
|
|
|
+ * @param rowDataEntity 行数据实体
|
|
|
+ * @return: void
|
|
|
+ * @author W.Y.C
|
|
|
+ * @date: 2025/10/21 22:42
|
|
|
+ */
|
|
|
+ private void validateRankRanges(DynamicObjectCollection entries, int totalRankCount, ExtendedDataEntity rowDataEntity) {
|
|
|
+ ValidationContext context = new ValidationContext();
|
|
|
+
|
|
|
+ // 统计每个排名出现的次数
|
|
|
+ for (int i = 0; i < entries.size(); i++) {
|
|
|
+ DynamicObject entry = entries.get(i);
|
|
|
+ DynamicObject person = entry.getDynamicObject(PerfRankMgmtConstant.NCKD_PERSON);
|
|
|
+
|
|
|
+ if (person != null) {
|
|
|
+ String personName = person.getString(FormConstant.NAME_KEY);
|
|
|
+ boolean isRanking = entry.getBoolean(PerfRankMgmtConstant.NCKD_ISRANKING);
|
|
|
+ boolean postAllowance = entry.getBoolean(PerfRankMgmtConstant.NCKD_POSTALLOWANCE);
|
|
|
+
|
|
|
+ if (isRanking) {
|
|
|
+ validateAndRecordRank(entry, PerfRankMgmtConstant.NCKD_TOPRANK, "参与排名","排名", personName, rowDataEntity, context.rankCountMap);
|
|
|
+
|
|
|
+ if (postAllowance) {
|
|
|
+ validateAndRecordRank(entry, PerfRankMgmtConstant.NCKD_ALLOWANCERANK, "享受职位津贴","R排名", personName, rowDataEntity, context.rankRCountMap);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 找出重复的排名
|
|
|
+ findDuplicateRanks(context.rankCountMap, context.duplicateRanks);
|
|
|
+ findDuplicateRanks(context.rankRCountMap, context.duplicateRRanks);
|
|
|
+
|
|
|
+ // 检查排名范围和重复率
|
|
|
+ for (int i = 0; i < entries.size(); i++) {
|
|
|
+ DynamicObject entry = entries.get(i);
|
|
|
+ boolean isRanking = entry.getBoolean(PerfRankMgmtConstant.NCKD_ISRANKING);
|
|
|
+ boolean postAllowance = entry.getBoolean(PerfRankMgmtConstant.NCKD_POSTALLOWANCE);
|
|
|
+
|
|
|
+ if (isRanking) {
|
|
|
+ // 检查排名字段是否在有效范围内
|
|
|
+ int topRank = entry.getInt(PerfRankMgmtConstant.NCKD_TOPRANK);
|
|
|
+ if (topRank < 1 || topRank > totalRankCount) {
|
|
|
+ this.addMessage(rowDataEntity,
|
|
|
+ StrFormatter.format("第{}行,排名{}超出有效范围[1-{}]\n", i + 1, topRank, totalRankCount));
|
|
|
+ }
|
|
|
+ if (postAllowance) {
|
|
|
+ // 检查R排名字段是否在有效范围内
|
|
|
+ int allowanceRank = entry.getInt(PerfRankMgmtConstant.NCKD_ALLOWANCERANK);
|
|
|
+ if (allowanceRank < 1 || allowanceRank > totalRankCount) {
|
|
|
+ this.addMessage(rowDataEntity,
|
|
|
+ StrFormatter.format("第{}行,R排名{}超出有效范围[1-{}]\n", i + 1, allowanceRank, totalRankCount));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 验证排名重复率
|
|
|
+ validateRankDuplicateRate(context, totalRankCount, rowDataEntity);
|
|
|
+
|
|
|
+ // 特殊排名组合校验
|
|
|
+ validateSpecialRankCombination(context, rowDataEntity);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 验证并记录排名值
|
|
|
+ * @param entry 分录对象
|
|
|
+ * @param rankField 排名字段名
|
|
|
+ * @param rankType 排名类型描述
|
|
|
+ * @param personName 人员姓名
|
|
|
+ * @param rowDataEntity 行数据实体
|
|
|
+ * @param rankCountMap 排名计数映射
|
|
|
+ */
|
|
|
+ private void validateAndRecordRank(DynamicObject entry, String rankField,String type, String rankType,
|
|
|
+ String personName, ExtendedDataEntity rowDataEntity,
|
|
|
+ Map<Integer, Integer> rankCountMap) {
|
|
|
+ int rankValue = entry.getInt(rankField);
|
|
|
+ if (rankValue < 1) {
|
|
|
+ this.addMessage(rowDataEntity, StrFormatter.format("人员【{}】已勾选{},{}数不能小于1或为空",
|
|
|
+ personName, type, rankType));
|
|
|
+ } else {
|
|
|
+ rankCountMap.put(rankValue, rankCountMap.getOrDefault(rankValue, 0) + 1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 查找重复的排名
|
|
|
+ * @param rankCountMap 排名计数映射
|
|
|
+ * @param duplicateRanks 重复排名集合
|
|
|
+ */
|
|
|
+ private void findDuplicateRanks(Map<Integer, Integer> rankCountMap, Set<Integer> duplicateRanks) {
|
|
|
+ for (Map.Entry<Integer, Integer> entry : rankCountMap.entrySet()) {
|
|
|
+ if (entry.getValue() > 1) {
|
|
|
+ duplicateRanks.add(entry.getKey());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 特殊排名组合校验
|
|
|
+ * 当相同排名重复出现时,其连续序列结束位置不能被其他排名占用
|
|
|
+ * @param context 上下文
|
|
|
+ * @param rowDataEntity 行数据实体
|
|
|
+ * @return: void
|
|
|
+ * @author W.Y.C
|
|
|
+ * @date: 2025/10/21 22:43
|
|
|
+ */
|
|
|
+ private void validateSpecialRankCombination(ValidationContext context, ExtendedDataEntity rowDataEntity) {
|
|
|
+ // 校验普通排名的特殊组合
|
|
|
+ List<String> invalidCombinations = new ArrayList<>();
|
|
|
+
|
|
|
+ // 校验普通排名
|
|
|
+ validateSpecialRankCombinationForType(context.rankCountMap, context.duplicateRanks, "排名", invalidCombinations);
|
|
|
+
|
|
|
+ // 校验R排名
|
|
|
+ validateSpecialRankCombinationForType(context.rankRCountMap, context.duplicateRRanks, "R排名", invalidCombinations);
|
|
|
+
|
|
|
+ // 如果有不合规的组合,添加详细的错误信息
|
|
|
+ if (!invalidCombinations.isEmpty()) {
|
|
|
+ StringBuilder errorMsg = new StringBuilder("排名设置不符合规范!\n");
|
|
|
+ errorMsg.append("【校验规则】\n");
|
|
|
+ errorMsg.append("当相同排名重复出现时,其连续序列结束位置不能被其他排名占用。\n");
|
|
|
+ errorMsg.append("【问题详情】\n");
|
|
|
+ for (int i = 0; i < invalidCombinations.size(); i++) {
|
|
|
+ errorMsg.append(StrFormatter.format("{}. {}\n", i + 1, invalidCombinations.get(i)));
|
|
|
+ }
|
|
|
+ errorMsg.append("【示例说明】\n");
|
|
|
+ errorMsg.append("❌ 不合规示例:[1,2,2,2,4] - 排名2重复3次,结束位置应为2+3-1=4,但4已在列表中\n");
|
|
|
+ errorMsg.append("✅ 合规示例:[1,1,1,4] - 排名1重复3次,结束位置应为1+3-1=3,3不在列表中\n");
|
|
|
+ errorMsg.append("【解决建议】\n");
|
|
|
+ errorMsg.append("请调整重复排名的数值或移除冲突的排名,确保连续序列结束位置未被占用;\n\n");
|
|
|
+ this.addMessage(rowDataEntity, errorMsg.toString());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 针对特定类型排名校验特殊组合规则
|
|
|
+ * @param rankCountMap 排名计数映射
|
|
|
+ * @param duplicateRanks 重复排名集合
|
|
|
+ * @param rankType 排名类型(如"排名"、"R排名")
|
|
|
+ * @param invalidCombinations 不合规组合列表
|
|
|
+ */
|
|
|
+ private void validateSpecialRankCombinationForType(Map<Integer, Integer> rankCountMap,
|
|
|
+ Set<Integer> duplicateRanks,
|
|
|
+ String rankType,
|
|
|
+ List<String> invalidCombinations) {
|
|
|
+ // 遍历所有重复的排名
|
|
|
+ for (Integer rank : duplicateRanks) {
|
|
|
+ int count = rankCountMap.get(rank);
|
|
|
+ // 计算连续序列结束值
|
|
|
+ int mark = rank + count - 1;
|
|
|
+
|
|
|
+ // 检查结束值是否在列表中存在
|
|
|
+ if (rankCountMap.containsKey(mark) && mark != rank) {
|
|
|
+ invalidCombinations.add(StrFormatter.format(
|
|
|
+ "{}{}重复{}次,结束位置{}被占用",
|
|
|
+ rankType, rank, count, mark));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 验证排名重复率是否超过阈值
|
|
|
+ * @param context 上下文
|
|
|
+ * @param totalRankCount 总排名数
|
|
|
+ * @param rowDataEntity 行数据实体
|
|
|
+ * @return: void
|
|
|
+ * @author W.Y.C
|
|
|
+ * @date: 2025/10/21 22:44
|
|
|
+ */
|
|
|
+ private void validateRankDuplicateRate(ValidationContext context, int totalRankCount, ExtendedDataEntity rowDataEntity) {
|
|
|
+ // 验证普通排名重复率
|
|
|
+ validateRankDuplicateRateForType(context.rankCountMap, context.duplicateRanks, totalRankCount,
|
|
|
+ "排名", rowDataEntity);
|
|
|
+
|
|
|
+ // 验证R排名重复率
|
|
|
+ validateRankDuplicateRateForType(context.rankRCountMap, context.duplicateRRanks, totalRankCount,
|
|
|
+ "R排名", rowDataEntity);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 针对特定类型排名验证重复率
|
|
|
+ * @param rankCountMap 排名计数映射
|
|
|
+ * @param duplicateRanks 重复排名集合
|
|
|
+ * @param totalRankCount 总排名数
|
|
|
+ * @param rankType 排名类型
|
|
|
+ * @param rowDataEntity 行数据实体
|
|
|
+ */
|
|
|
+ private void validateRankDuplicateRateForType(Map<Integer, Integer> rankCountMap,
|
|
|
+ Set<Integer> duplicateRanks,
|
|
|
+ int totalRankCount,
|
|
|
+ String rankType,
|
|
|
+ ExtendedDataEntity rowDataEntity) {
|
|
|
+ if (totalRankCount > 0) {
|
|
|
+ double duplicateRate = (double) duplicateRanks.size() / totalRankCount;
|
|
|
+ if (duplicateRate > 0.3) {
|
|
|
+ // 构建重复排名详情信息
|
|
|
+ StringBuilder detailMessage = new StringBuilder();
|
|
|
+ detailMessage.append(rankType).append("重复率超过30%,当前重复率: ");
|
|
|
+ detailMessage.append(String.format("%.1f", duplicateRate * 100)).append("%,");
|
|
|
+ detailMessage.append("具体重复的排名有: ");
|
|
|
+
|
|
|
+ List<Integer> sortedDuplicateRanks = new ArrayList<>(duplicateRanks);
|
|
|
+ Collections.sort(sortedDuplicateRanks);
|
|
|
+
|
|
|
+ for (int i = 0; i < sortedDuplicateRanks.size(); i++) {
|
|
|
+ int rank = sortedDuplicateRanks.get(i);
|
|
|
+ int count = rankCountMap.get(rank);
|
|
|
+ detailMessage.append(StrFormatter.format("{}【{}】出现{}次;", rankType, rank, count));
|
|
|
+ }
|
|
|
+ detailMessage.append("\n");
|
|
|
+ this.addMessage(rowDataEntity, detailMessage.toString());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void validateUserUniqueAcrossUnits() {
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 验证上下文对象,用于存储验证过程中的统计数据
|
|
|
+ * @author W.Y.C
|
|
|
+ * @date 2025/10/21 22:45
|
|
|
+ * @version 1.0
|
|
|
+ */
|
|
|
+ private static class ValidationContext {
|
|
|
+ int totalRankCount = 0;
|
|
|
+ int allowanceRankCount = 0;
|
|
|
+ int failCount = 0;
|
|
|
+ int basicCount = 0;
|
|
|
+ int excellentCount = 0;
|
|
|
+ int qualifiedCount = 0;
|
|
|
+ Set<Integer> rankSet = new HashSet<>(); // 记录已出现的排名
|
|
|
+ Set<Integer> duplicateRanks = new HashSet<>(); // 记录普通排名中重复的排名
|
|
|
+ Set<Integer> duplicateRRanks = new HashSet<>(); // 记录R排名中重复的排名
|
|
|
+ Map<Integer, Integer> rankCountMap = new HashMap<>(); // 记录每个普通排名出现的次数
|
|
|
+ Map<Integer, Integer> rankRCountMap = new HashMap<>(); // 记录每个R排名出现的次数
|
|
|
+ }
|
|
|
+
|
|
|
+}
|