个税解析

This commit is contained in:
钱涛 2023-09-06 11:40:22 +08:00
parent 1451c4b192
commit f9611212d4
7 changed files with 177 additions and 0 deletions

View File

@ -0,0 +1,177 @@
package com.engine.salary.entity.salaryacct.bo;
import com.engine.salary.entity.salaryformula.ExpressFormula;
import com.engine.salary.entity.salarysob.po.SalarySobItemPO;
import com.engine.salary.util.SalaryEntityUtil;
import com.google.common.collect.Lists;
import com.googlecode.aviator.Expression;
import com.weaver.excel.formula.api.entity.FormulaVar;
import com.weaver.hrm.salary.constant.SalaryConstant;
import com.weaver.hrm.salary.enums.salaryitem.SalaryFormulaReferenceEnum;
import com.weaver.teams.domain.user.SimpleEmployee;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import java.util.*;
import java.util.concurrent.LinkedTransferQueue;
/**
* @description: 对薪资核算时涉及的薪资项目进行排序
* @author: xiajun
* @modified By: xiajun
* @date: Created in 2023/5/19 16:01
* @version:v1.0
*/
public class SalaryCalcItemGraph {
private List<SalaryCalcItemGraphNode> nodes;
/**
* 根据薪资账套的薪资项目公式详情构建实例
*
* @param salarySobItems 薪资账套的薪资项目
* @param expressFormulas 公式详情
*/
public SalaryCalcItemGraph(List<SalarySobItemPO> salarySobItems, List<ExpressFormula> expressFormulas, SimpleEmployee currentUser) {
Map<String, SalarySobItemPO> salaryItemMap = SalaryEntityUtil.convert2Map(salarySobItems,
SalarySobItemPO::getSalaryItemCode);
Map<Long, ExpressFormula> expressFormulaMap = SalaryEntityUtil.convert2Map(expressFormulas,
ExpressFormula::getId);
Map<Long, List<FormulaVar>> formulaVarMap = ExpressFormulaBO.buildFormulaVar(expressFormulas);
Map<Long, Expression> expressionMap = ExpressFormulaBO.compile(expressFormulas, currentUser);
Map<Long, SalaryCalcItemGraphNode> nodeMap = new HashMap<>();
for (SalarySobItemPO salarySobItem : salarySobItems) {
Expression expression = expressionMap.get(salarySobItem.getFormulaId());
ExpressFormula expressFormula = expressFormulaMap.get(salarySobItem.getFormulaId());
SalaryCalcItemGraphNode node = nodeMap.computeIfAbsent(salarySobItem.getSalaryItemId(), key -> new SalaryCalcItemGraphNode(salarySobItem, expressFormula, expression));
List<FormulaVar> formulaVars = expressFormula == null ? Collections.emptyList()
: formulaVarMap.getOrDefault(expressFormula.getId(), Collections.emptyList());
for (FormulaVar formulaVar : formulaVars) {
if (StringUtils.equalsIgnoreCase(formulaVar.getModule(), SalaryConstant.MODULE)
&& StringUtils.isNotEmpty(formulaVar.getFieldId())
&& StringUtils.startsWith(formulaVar.getFieldId(), SalaryFormulaReferenceEnum.CURRENT_CALC.getValue() + SalaryConstant.FORMULA_VAR_SEPARATOR)) {
String salaryItemCode = formulaVar.getFieldId().split(SalaryConstant.FORMULA_VAR_SEPARATOR)[1];
SalarySobItemPO subSalarySobItem = salaryItemMap.get(salaryItemCode);
if (subSalarySobItem == null) {
continue;
}
ExpressFormula subExpressFormula = expressFormulaMap.get(subSalarySobItem.getFormulaId());
Expression subExpression = expressionMap.get(subSalarySobItem.getFormulaId());
SalaryCalcItemGraphNode destNode = nodeMap.computeIfAbsent(subSalarySobItem.getSalaryItemId(), key -> new SalaryCalcItemGraphNode(subSalarySobItem, subExpressFormula, subExpression));
node.getDestNodes().add(destNode);
}
}
}
this.nodes = Lists.newArrayList(nodeMap.values());
}
/**
* 对薪资核算时涉及的薪资项目进行排序
* 对于一次薪资核算而言不同的薪资项目之间可能会存在引用
* 例如绩效工资=绩效比例*(基本工资+岗位工资)在计算绩效工资前需要先计算出绩效比例基本工资岗位工资
* 所以薪资核算前需要对所涉及的所有薪资项目进行一个计算排序
* 对薪资项目的排序就相当于是对一个有向无环的图进行拓扑排序一个薪资项目就相当于图中的一个顶点
* 具体算法如下
* 1根据薪资账套的薪资项目公式详情构建出所有的顶点
* 2将所有顶点的入度都初始化为0
* 3遍历所有的顶点计算每个顶点的入度
* 4创建一个队列将所有入度为0的顶点都存入队列
* 5每次从队列中取出一个点将该点存入结果集合中并将该顶点的所有邻接点的入度减1如果邻接点的入度变为了0将其加入队列中
* 6重复步骤5直到队列空了
*
* @return 排好序的薪资项目id
*/
public List<SalaryCalcItem> sort() {
// 循环遍历所有顶点
for (SalaryCalcItemGraphNode node : nodes) {
// 循环遍历每个顶点的邻居
for (SalaryCalcItemGraphNode destNode : node.getDestNodes()) {
// 维护每个顶点的入度
destNode.setInDegree(destNode.getInDegree() + 1);
}
}
// 创建一个队列
LinkedTransferQueue<SalaryCalcItemGraphNode> queue = new LinkedTransferQueue<>();
// 再次循环所有顶点
for (SalaryCalcItemGraphNode node : nodes) {
// 若该顶点入度为0则加入队列
if (node.getInDegree() == 0) {
queue.offer(node);
}
}
// 存储结果的列表
List<SalaryCalcItem> result = new ArrayList<>();
// 循环直到队列为空
while (!queue.isEmpty()) {
// 取出队列头部的顶点编号
SalaryCalcItemGraphNode node = queue.poll();
// 将该顶点加入到结果列表中
result.add(node.getSalaryCalcItem());
// 标记该顶点已被访问
node.setVisited(true);
// 遍历该顶点的邻居
for (SalaryCalcItemGraphNode destNode : node.getDestNodes()) {
// 若该邻居未被访问过
if (!destNode.isVisited()) {
// 将该邻居的入度减一
destNode.setInDegree(destNode.getInDegree() - 1);
// 若该邻居入度已经变为0
if (destNode.getInDegree() == 0) {
// 将该邻居加入到队列中
queue.offer(destNode);
}
}
}
}
// 倒序
Collections.reverse(result);
// 返回结果列表
return result;
}
@Data
private static class SalaryCalcItemGraphNode {
/**
* 薪资项目的id
*/
private SalaryCalcItem salaryCalcItem;
/**
* 入度
*/
private int inDegree;
/**
* 是否已被访问
*/
private boolean visited;
/**
* 当前顶点的邻居
*/
private List<SalaryCalcItemGraphNode> destNodes;
public SalaryCalcItemGraphNode(SalarySobItemPO salarySobItem, ExpressFormula expressFormula, Expression expression) {
this.salaryCalcItem = new SalaryCalcItem()
.setSalaryItemId(salarySobItem.getSalaryItemId())
.setSalaryItemCode(salarySobItem.getSalaryItemCode())
.setIncomeCategory(salarySobItem.getIncomeCategory())
.setUseInEmployeeSalary(salarySobItem.getUseInEmployeeSalary())
.setDataType(salarySobItem.getDataType())
.setRoundingMode(salarySobItem.getRoundingMode())
.setPattern(salarySobItem.getPattern())
.setExpressFormula(expressFormula)
.setExpression(expression);
this.inDegree = 0;
this.visited = false;
this.destNodes = new ArrayList<>();
}
}
}