255 lines
12 KiB
Java
255 lines
12 KiB
Java
package com.engine.salary.entity.salaryacct.bo;
|
||
|
||
import com.engine.salary.constant.SalaryFormulaFieldConstant;
|
||
import com.engine.salary.entity.salaryformula.ExpressFormula;
|
||
import com.engine.salary.entity.salaryformula.po.FormulaVar;
|
||
import com.engine.salary.entity.salaryitem.po.SalaryItemPO;
|
||
import com.engine.salary.entity.salarysob.po.SalarySobBackItemPO;
|
||
import com.engine.salary.entity.salarysob.po.SalarySobItemPO;
|
||
import com.engine.salary.enums.salaryformula.SalaryFormulaReferenceEnum;
|
||
import com.engine.salary.exception.SalaryRunTimeException;
|
||
import com.engine.salary.util.SalaryEntityUtil;
|
||
import com.engine.salary.util.SalaryI18nUtil;
|
||
import com.google.common.collect.Lists;
|
||
import com.google.common.collect.Maps;
|
||
import lombok.AllArgsConstructor;
|
||
import lombok.Builder;
|
||
import lombok.Data;
|
||
import lombok.NoArgsConstructor;
|
||
import org.apache.commons.collections4.CollectionUtils;
|
||
import org.apache.commons.lang3.StringUtils;
|
||
import weaver.hrm.User;
|
||
|
||
import java.util.*;
|
||
import java.util.regex.Matcher;
|
||
import java.util.regex.Pattern;
|
||
import java.util.stream.Collectors;
|
||
|
||
/**
|
||
* 薪资核算-薪资项目运算优先级
|
||
* <p>Copyright: Copyright (c) 2022</p>
|
||
* <p>Company: 泛微软件</p>
|
||
*
|
||
* @author qiantao
|
||
* @version 1.0
|
||
**/
|
||
public class SalaryAcctCalculatePriorityBO {
|
||
|
||
/**
|
||
* 公式中变量的fieldId的正则表达式
|
||
*/
|
||
private static final String SALARY_REGEX = "(\\w+)" + SalaryFormulaFieldConstant.FIELD_ID_SEPARATOR + "(\\w+)";
|
||
|
||
/**
|
||
* 解析公式中变量的fieldId的正则表达式
|
||
*/
|
||
private static final Pattern SALARY_PATTERN = Pattern.compile(SALARY_REGEX);
|
||
|
||
/**
|
||
* 计算优先级
|
||
*
|
||
* @param salarySobItems
|
||
* @param salaryItems
|
||
* @param expressFormulas
|
||
* @return 根据计算优先级已经排好序,集合中是薪资账套中的薪资项目
|
||
*/
|
||
public static List<List<Long>> calculatePriority(List<SalarySobItemPO> salarySobItems,
|
||
List<SalaryItemPO> salaryItems,
|
||
List<ExpressFormula> expressFormulas,
|
||
List<SalarySobBackItemPO> salarySobBackItems,
|
||
Set<String> issuedFieldIds,
|
||
User user) {
|
||
// 公式详情
|
||
Map<Long, List<FormulaVar>> formulaIdKeyMap = ExpressFormulaBO.buildFormulaVar(expressFormulas);
|
||
// key:薪资项目的id,value:薪资项目的po
|
||
Map<Long, SalaryItemPO> salaryItemPOMap = SalaryEntityUtil.convert2Map(salaryItems, SalaryItemPO::getId);
|
||
// key:薪资项目的code,value:薪资项目的po
|
||
Map<String, SalaryItemPO> codeKeySalaryItemPOMap = SalaryEntityUtil.convert2Map(salaryItems, SalaryItemPO::getCode);
|
||
// key:薪资项目的id,value:薪资账套下的薪资项目副本的po
|
||
Map<Long, SalarySobItemPO> salaryItemIdKeySalarySobItemPOMap = SalaryEntityUtil.convert2Map(salarySobItems, SalarySobItemPO::getSalaryItemId);
|
||
// 薪资账套项目+薪资回算项目
|
||
Set<Long> salarySobItemsAndBackItems = SalaryEntityUtil.properties(salarySobItems, SalarySobItemPO::getSalaryItemId);
|
||
salarySobItemsAndBackItems.addAll(SalaryEntityUtil.properties(salarySobBackItems, SalarySobBackItemPO::getSalaryItemId));
|
||
Map<Long, SalaryItemIdWithPriority> salaryItemIdWithPriorityMap = Maps.newHashMapWithExpectedSize(salarySobItemsAndBackItems.size());
|
||
// key:薪资回算项目id,value:薪资回算项目副本PO
|
||
Map<Long, SalarySobBackItemPO> salarySobBackItemPOMap = SalaryEntityUtil.convert2Map(salarySobBackItems, SalarySobBackItemPO::getSalaryItemId);
|
||
for (Long salaryItemId : salarySobItemsAndBackItems) {
|
||
calculate(salaryItemId, salaryItemPOMap, codeKeySalaryItemPOMap, salaryItemIdKeySalarySobItemPOMap, formulaIdKeyMap, salaryItemIdWithPriorityMap, null, salarySobBackItemPOMap, issuedFieldIds, user);
|
||
}
|
||
return SalaryEntityUtil.group2Map(salaryItemIdWithPriorityMap.values(), SalaryItemIdWithPriority::getPriority).values().stream()
|
||
.sorted(Comparator.comparingInt(list -> list.get(0).getPriority()))
|
||
.map(list -> SalaryEntityUtil.properties(list, SalaryItemIdWithPriority::getSalaryItemId, Collectors.toList()))
|
||
.collect(Collectors.toList());
|
||
}
|
||
|
||
|
||
/**
|
||
* 计算薪资账套中的薪资项目的计算优先级
|
||
*
|
||
* @param currentSalaryItemId
|
||
* @param salaryItemPOMap
|
||
* @param salaryItemIdKeySalarySobItemPOMap
|
||
* @param codeKeySalaryItemPOMap
|
||
* @param formulaIdKeyMap
|
||
* @param salaryItemIdWithPriorityMap
|
||
* @param pre
|
||
*/
|
||
private static void calculate(Long currentSalaryItemId,
|
||
Map<Long, SalaryItemPO> salaryItemPOMap,
|
||
Map<String, SalaryItemPO> codeKeySalaryItemPOMap,
|
||
Map<Long, SalarySobItemPO> salaryItemIdKeySalarySobItemPOMap,
|
||
Map<Long, List<FormulaVar>> formulaIdKeyMap,
|
||
Map<Long, SalaryItemIdWithPriority> salaryItemIdWithPriorityMap,
|
||
SalaryItemIdWithPriority pre,
|
||
Map<Long, SalarySobBackItemPO> salarySobBackItemPOMap,
|
||
Set<String> issuedFieldIds,
|
||
User user) {
|
||
List<Long> salaryItemIds = Lists.newArrayList();
|
||
// 获取公式详情
|
||
List<FormulaVar> formulaVars;
|
||
if (salaryItemIdKeySalarySobItemPOMap.containsKey(currentSalaryItemId)) {
|
||
// 如果薪资项目在薪资账套中有副本,则取薪资账套中设置的公式
|
||
SalarySobItemPO salarySobItemPO = salaryItemIdKeySalarySobItemPOMap.get(currentSalaryItemId);
|
||
formulaVars = formulaIdKeyMap.getOrDefault(salarySobItemPO.getFormulaId(), Collections.emptyList());
|
||
} else if(salarySobBackItemPOMap.containsKey(currentSalaryItemId)){
|
||
// 如果薪资项目在薪资账套中没有副本,则取薪资回算中设置的公式
|
||
SalarySobBackItemPO salarySobBackItemPO = salarySobBackItemPOMap.get(currentSalaryItemId);
|
||
formulaVars = formulaIdKeyMap.getOrDefault(salarySobBackItemPO.getFormulaId(), Collections.emptyList());
|
||
} else {
|
||
// 如果薪资项目在薪资账套及回算薪资项目中没有有副本,则取薪资项目中设置的公式
|
||
SalaryItemPO salaryItemPO = salaryItemPOMap.get(currentSalaryItemId);
|
||
formulaVars = formulaIdKeyMap.getOrDefault(salaryItemPO.getFormulaId(), Collections.emptyList());
|
||
}
|
||
// 解析公式详情中的变量,找出引用了哪些其他的薪资项目(需要先计算出引用的薪资项目才能计算当前的薪资项目)
|
||
for (FormulaVar formulaVar : formulaVars) {
|
||
String fieldId = formulaVar.getFieldId();
|
||
if (StringUtils.isEmpty(fieldId)) {
|
||
continue;
|
||
}
|
||
Matcher matcher = SALARY_PATTERN.matcher(fieldId);
|
||
if (matcher.find()) {
|
||
SalaryFormulaReferenceEnum referenceEnum = SalaryFormulaReferenceEnum.parseByValue(matcher.group(1));
|
||
// 分析公式中的回算变量包含哪些
|
||
loadSalaryCalcFormula(referenceEnum, issuedFieldIds, matcher.group(2));
|
||
if (referenceEnum == SalaryFormulaReferenceEnum.SALARY_ITEM) {
|
||
SalaryItemPO salaryItemPO = codeKeySalaryItemPOMap.get(matcher.group(2));
|
||
if (salaryItemPO == null) {
|
||
continue;
|
||
}
|
||
salaryItemIds.add(salaryItemPO.getId());
|
||
}
|
||
}
|
||
}
|
||
if (CollectionUtils.isEmpty(salaryItemIds)) {
|
||
SalaryItemIdWithPriority current = salaryItemIdWithPriorityMap.computeIfAbsent(currentSalaryItemId, k -> SalaryItemIdWithPriority.builder()
|
||
.priority(0)
|
||
.salaryItemId(currentSalaryItemId)
|
||
.preList(Collections.emptyList())
|
||
.build());
|
||
addPre(current, pre, salaryItemPOMap, user);
|
||
updatePriority(current);
|
||
return;
|
||
}
|
||
for (Long salaryItemId : salaryItemIds) {
|
||
SalaryItemIdWithPriority current = salaryItemIdWithPriorityMap.computeIfAbsent(currentSalaryItemId, k -> SalaryItemIdWithPriority.builder()
|
||
.priority(1)
|
||
.salaryItemId(currentSalaryItemId)
|
||
.preList(Collections.emptyList())
|
||
.build());
|
||
addPre(current, pre, salaryItemPOMap, user);
|
||
updatePriority(current);
|
||
calculate(salaryItemId, salaryItemPOMap, codeKeySalaryItemPOMap, salaryItemIdKeySalarySobItemPOMap, formulaIdKeyMap, salaryItemIdWithPriorityMap, current, Collections.emptyMap(), issuedFieldIds, user);
|
||
}
|
||
}
|
||
|
||
private static void loadSalaryCalcFormula(SalaryFormulaReferenceEnum referenceEnum, Set<String> issuedFieldIds, String fieldId) {
|
||
if (referenceEnum == SalaryFormulaReferenceEnum.ISSUED) {
|
||
issuedFieldIds.add(fieldId);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 薪资项目被哪些薪资项目引用
|
||
*
|
||
* @param current
|
||
* @param pre
|
||
*/
|
||
private static void addPre(SalaryItemIdWithPriority current, SalaryItemIdWithPriority pre, Map<Long, SalaryItemPO> salaryItemPOMap, User user) {
|
||
if (pre == null) {
|
||
return;
|
||
}
|
||
checkLoop(current, pre, salaryItemPOMap, user);
|
||
if (CollectionUtils.isEmpty(current.getPreList())) {
|
||
current.setPreList(Lists.newArrayList(pre));
|
||
} else {
|
||
boolean isExist = current.getPreList().stream().anyMatch(e -> Objects.equals(e.getSalaryItemId(), pre.getSalaryItemId()));
|
||
if (!isExist) {
|
||
current.getPreList().add(pre);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 检查薪资项目之间是否存在相互引用
|
||
*
|
||
* @param current
|
||
*/
|
||
private static void checkLoop(SalaryItemIdWithPriority current, SalaryItemIdWithPriority pre, Map<Long, SalaryItemPO> salaryItemPOMap, User user) {
|
||
if (Objects.equals(pre.getSalaryItemId(), current.getSalaryItemId())) {
|
||
SalaryItemPO preSalaryItemPO = salaryItemPOMap.get(pre.getSalaryItemId());
|
||
SalaryItemPO currentSalaryItemPO = salaryItemPOMap.get(current.getSalaryItemId());
|
||
String errMsg = (SalaryI18nUtil.getI18nLabel(user.getLanguage(),542636, "以下项目的公式中存在相互引用") + ":{0}、{1}")
|
||
.replace("{0}", Optional.ofNullable(preSalaryItemPO).map(SalaryItemPO::getName).orElse(StringUtils.EMPTY))
|
||
.replace("{1}", Optional.ofNullable(currentSalaryItemPO).map(SalaryItemPO::getName).orElse(StringUtils.EMPTY));
|
||
// String errMsg = SalaryI18nUtil.getI18nLabel(user.getLanguage(),101426, "{0}和{1}的公式中存在相互引用")
|
||
// .replace("{0}", Optional.ofNullable(preSalaryItemPO).map(SalaryItemPO::getName).orElse(StringUtils.EMPTY))
|
||
// .replace("{1}", Optional.ofNullable(currentSalaryItemPO).map(SalaryItemPO::getName).orElse(StringUtils.EMPTY));
|
||
throw new SalaryRunTimeException(errMsg);
|
||
}
|
||
if (CollectionUtils.isEmpty(pre.getPreList())) {
|
||
return;
|
||
}
|
||
for (SalaryItemIdWithPriority salaryItemIdWithPriority : pre.getPreList()) {
|
||
checkLoop(current, salaryItemIdWithPriority, salaryItemPOMap, user);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 更新薪资账套中的薪资项目的计算优先级
|
||
*
|
||
* @param current
|
||
*/
|
||
private static void updatePriority(SalaryItemIdWithPriority current) {
|
||
List<SalaryItemIdWithPriority> preList = current.getPreList();
|
||
if (CollectionUtils.isEmpty(preList)) {
|
||
return;
|
||
}
|
||
preList.stream()
|
||
.filter(e -> e.getPriority() <= current.getPriority())
|
||
.forEach(e -> e.setPriority(current.getPriority() + 1));
|
||
}
|
||
|
||
|
||
@Data
|
||
@Builder
|
||
@NoArgsConstructor
|
||
@AllArgsConstructor
|
||
private static class SalaryItemIdWithPriority {
|
||
|
||
/**
|
||
* 薪资账套中的薪资项目的计算优先级(数字越小,计算优先级越高,从0开始计算)
|
||
*/
|
||
private Integer priority;
|
||
|
||
/**
|
||
* 薪资项目的id
|
||
*/
|
||
private Long salaryItemId;
|
||
|
||
/**
|
||
* 当前层级中的上层
|
||
*/
|
||
private List<SalaryItemIdWithPriority> preList;
|
||
}
|
||
}
|