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;
/**
* 薪资核算-薪资项目运算优先级
*
Copyright: Copyright (c) 2022
* Company: 泛微软件
*
* @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> calculatePriority(List salarySobItems,
List salaryItems,
List expressFormulas,
List salarySobBackItems,
Set issuedFieldIds,
User user) {
// 公式详情
Map> formulaIdKeyMap = ExpressFormulaBO.buildFormulaVar(expressFormulas);
// key:薪资项目的id,value:薪资项目的po
Map salaryItemPOMap = SalaryEntityUtil.convert2Map(salaryItems, SalaryItemPO::getId);
// key:薪资项目的code,value:薪资项目的po
Map codeKeySalaryItemPOMap = SalaryEntityUtil.convert2Map(salaryItems, SalaryItemPO::getCode);
// key:薪资项目的id,value:薪资账套下的薪资项目副本的po
Map salaryItemIdKeySalarySobItemPOMap = SalaryEntityUtil.convert2Map(salarySobItems, SalarySobItemPO::getSalaryItemId);
// 薪资账套项目+薪资回算项目
Set salarySobItemsAndBackItems = SalaryEntityUtil.properties(salarySobItems, SalarySobItemPO::getSalaryItemId);
salarySobItemsAndBackItems.addAll(SalaryEntityUtil.properties(salarySobBackItems, SalarySobBackItemPO::getSalaryItemId));
Map salaryItemIdWithPriorityMap = Maps.newHashMapWithExpectedSize(salarySobItemsAndBackItems.size());
// key:薪资回算项目id,value:薪资回算项目副本PO
Map 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 salaryItemPOMap,
Map codeKeySalaryItemPOMap,
Map salaryItemIdKeySalarySobItemPOMap,
Map> formulaIdKeyMap,
Map salaryItemIdWithPriorityMap,
SalaryItemIdWithPriority pre,
Map salarySobBackItemPOMap,
Set issuedFieldIds,
User user) {
List salaryItemIds = Lists.newArrayList();
// 获取公式详情
List 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 issuedFieldIds, String fieldId) {
if (referenceEnum == SalaryFormulaReferenceEnum.ISSUED) {
issuedFieldIds.add(fieldId);
}
}
/**
* 薪资项目被哪些薪资项目引用
*
* @param current
* @param pre
*/
private static void addPre(SalaryItemIdWithPriority current, SalaryItemIdWithPriority pre, Map 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 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 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 preList;
}
}