From f9611212d4fa42ef75fc630d011737cab34a3952 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=92=B1=E6=B6=9B?= <15850646081@163.com> Date: Wed, 6 Sep 2023 11:40:22 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=AA=E7=A8=8E=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resource/sqlupgrade/DM/sql202307100103.sql | 0 resource/sqlupgrade/JC/sql202307100103.sql | 0 resource/sqlupgrade/Mysql/sql202307100103.sql | 0 .../sqlupgrade/Oracle/sql202307100103.sql | 0 .../sqlupgrade/SQLServer/sql202307100103.sql | 0 resource/sqlupgrade/ST/sql202307100103.sql | 0 .../salaryacct/bo/SalaryCalcItemGraph.java | 177 ++++++++++++++++++ 7 files changed, 177 insertions(+) delete mode 100644 resource/sqlupgrade/DM/sql202307100103.sql delete mode 100644 resource/sqlupgrade/JC/sql202307100103.sql delete mode 100644 resource/sqlupgrade/Mysql/sql202307100103.sql delete mode 100644 resource/sqlupgrade/Oracle/sql202307100103.sql delete mode 100644 resource/sqlupgrade/SQLServer/sql202307100103.sql delete mode 100644 resource/sqlupgrade/ST/sql202307100103.sql create mode 100644 src/com/engine/salary/entity/salaryacct/bo/SalaryCalcItemGraph.java diff --git a/resource/sqlupgrade/DM/sql202307100103.sql b/resource/sqlupgrade/DM/sql202307100103.sql deleted file mode 100644 index e69de29bb..000000000 diff --git a/resource/sqlupgrade/JC/sql202307100103.sql b/resource/sqlupgrade/JC/sql202307100103.sql deleted file mode 100644 index e69de29bb..000000000 diff --git a/resource/sqlupgrade/Mysql/sql202307100103.sql b/resource/sqlupgrade/Mysql/sql202307100103.sql deleted file mode 100644 index e69de29bb..000000000 diff --git a/resource/sqlupgrade/Oracle/sql202307100103.sql b/resource/sqlupgrade/Oracle/sql202307100103.sql deleted file mode 100644 index e69de29bb..000000000 diff --git a/resource/sqlupgrade/SQLServer/sql202307100103.sql b/resource/sqlupgrade/SQLServer/sql202307100103.sql deleted file mode 100644 index e69de29bb..000000000 diff --git a/resource/sqlupgrade/ST/sql202307100103.sql b/resource/sqlupgrade/ST/sql202307100103.sql deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/com/engine/salary/entity/salaryacct/bo/SalaryCalcItemGraph.java b/src/com/engine/salary/entity/salaryacct/bo/SalaryCalcItemGraph.java new file mode 100644 index 000000000..e9f840fd3 --- /dev/null +++ b/src/com/engine/salary/entity/salaryacct/bo/SalaryCalcItemGraph.java @@ -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 nodes; + + /** + * 根据薪资账套的薪资项目、公式详情构建实例 + * + * @param salarySobItems 薪资账套的薪资项目 + * @param expressFormulas 公式详情 + */ + public SalaryCalcItemGraph(List salarySobItems, List expressFormulas, SimpleEmployee currentUser) { + Map salaryItemMap = SalaryEntityUtil.convert2Map(salarySobItems, + SalarySobItemPO::getSalaryItemCode); + Map expressFormulaMap = SalaryEntityUtil.convert2Map(expressFormulas, + ExpressFormula::getId); + Map> formulaVarMap = ExpressFormulaBO.buildFormulaVar(expressFormulas); + Map expressionMap = ExpressFormulaBO.compile(expressFormulas, currentUser); + + Map 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 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 sort() { + // 循环遍历所有顶点 + for (SalaryCalcItemGraphNode node : nodes) { + // 循环遍历每个顶点的邻居 + for (SalaryCalcItemGraphNode destNode : node.getDestNodes()) { + // 维护每个顶点的入度 + destNode.setInDegree(destNode.getInDegree() + 1); + } + } + + // 创建一个队列 + LinkedTransferQueue queue = new LinkedTransferQueue<>(); + + // 再次循环所有顶点 + for (SalaryCalcItemGraphNode node : nodes) { + // 若该顶点入度为0,则加入队列 + if (node.getInDegree() == 0) { + queue.offer(node); + } + } + + // 存储结果的列表 + List 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 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<>(); + } + } +}