From d1a64a5e1d6bec36a26cd221a8a61656614ae7b8 Mon Sep 17 00:00:00 2001 From: lihongjie0209 Date: Wed, 20 Mar 2024 14:17:13 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E5=B7=A5=E8=B5=84=E8=AE=A1?= =?UTF-8?q?=E7=AE=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../empSalary/entity/EmpSalaryEntity.java | 6 + .../entity/EmpSalaryItemEntity.java | 1 + .../repository/EmpSalaryItemRepository.java | 7 +- .../service/EmpSalaryItemService.java | 400 +++++++++++++++++- 4 files changed, 405 insertions(+), 9 deletions(-) diff --git a/src/main/java/cn/lihongjie/coal/empSalary/entity/EmpSalaryEntity.java b/src/main/java/cn/lihongjie/coal/empSalary/entity/EmpSalaryEntity.java index dc30611f..cee7d1af 100644 --- a/src/main/java/cn/lihongjie/coal/empSalary/entity/EmpSalaryEntity.java +++ b/src/main/java/cn/lihongjie/coal/empSalary/entity/EmpSalaryEntity.java @@ -82,6 +82,12 @@ public class EmpSalaryEntity extends OrgCommonEntity { private BigDecimal item50; + + private BigDecimal yfheji; + private BigDecimal kfheji; + private BigDecimal sfheji; + + @Comment("归档状态") @ColumnDefault("'0'") private String archiveStatus = "0"; diff --git a/src/main/java/cn/lihongjie/coal/empSalaryItem/entity/EmpSalaryItemEntity.java b/src/main/java/cn/lihongjie/coal/empSalaryItem/entity/EmpSalaryItemEntity.java index 1e158b07..d348c2c0 100644 --- a/src/main/java/cn/lihongjie/coal/empSalaryItem/entity/EmpSalaryItemEntity.java +++ b/src/main/java/cn/lihongjie/coal/empSalaryItem/entity/EmpSalaryItemEntity.java @@ -61,4 +61,5 @@ public class EmpSalaryItemEntity extends OrgCommonEntity { private Integer priority; + private Boolean systemPreset; } diff --git a/src/main/java/cn/lihongjie/coal/empSalaryItem/repository/EmpSalaryItemRepository.java b/src/main/java/cn/lihongjie/coal/empSalaryItem/repository/EmpSalaryItemRepository.java index 85bbe479..0f39c652 100644 --- a/src/main/java/cn/lihongjie/coal/empSalaryItem/repository/EmpSalaryItemRepository.java +++ b/src/main/java/cn/lihongjie/coal/empSalaryItem/repository/EmpSalaryItemRepository.java @@ -6,4 +6,9 @@ import cn.lihongjie.coal.empSalaryItem.entity.EmpSalaryItemEntity; import org.springframework.stereotype.Repository; @Repository -public interface EmpSalaryItemRepository extends BaseRepository {} +public interface EmpSalaryItemRepository extends BaseRepository { + long countByNameContainsAndOrganizationId(String name, String organizationId); + + long countByNameContainsAndOrganizationIdAndIdNot( + String name, String organizationId, String id); +} diff --git a/src/main/java/cn/lihongjie/coal/empSalaryItem/service/EmpSalaryItemService.java b/src/main/java/cn/lihongjie/coal/empSalaryItem/service/EmpSalaryItemService.java index fd131071..1cb1f683 100644 --- a/src/main/java/cn/lihongjie/coal/empSalaryItem/service/EmpSalaryItemService.java +++ b/src/main/java/cn/lihongjie/coal/empSalaryItem/service/EmpSalaryItemService.java @@ -3,6 +3,10 @@ package cn.lihongjie.coal.empSalaryItem.service; import cn.lihongjie.coal.base.dto.CommonQuery; import cn.lihongjie.coal.base.dto.IdRequest; import cn.lihongjie.coal.base.service.BaseService; +import cn.lihongjie.coal.common.Ctx; +import cn.lihongjie.coal.common.GroovyScriptUtils; +import cn.lihongjie.coal.empSalary.entity.EmpSalaryEntity; +import cn.lihongjie.coal.empSalaryBatch.entity.EmpSalaryBatchEntity; import cn.lihongjie.coal.empSalaryItem.dto.CreateEmpSalaryItemDto; import cn.lihongjie.coal.empSalaryItem.dto.EmpSalaryItemDto; import cn.lihongjie.coal.empSalaryItem.dto.UpdateEmpSalaryItemDto; @@ -10,18 +14,41 @@ import cn.lihongjie.coal.empSalaryItem.entity.EmpSalaryItemEntity; import cn.lihongjie.coal.empSalaryItem.mapper.EmpSalaryItemMapper; import cn.lihongjie.coal.empSalaryItem.repository.EmpSalaryItemRepository; import cn.lihongjie.coal.empSalaryItemConfig.service.EmpSalaryItemConfigService; +import cn.lihongjie.coal.empSalaryStandard.dto.EmpSalaryStandardResult; +import cn.lihongjie.coal.empSalaryStandard.service.EmpSalaryStandardService; +import cn.lihongjie.coal.exception.BizException; +import groovy.lang.GroovyClassLoader; +import groovy.lang.Script; + +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; + +import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.RegExUtils; +import org.apache.commons.lang3.StringUtils; +import org.codehaus.groovy.control.CompilerConfiguration; +import org.jgrapht.alg.cycle.CycleDetector; +import org.jgrapht.graph.DefaultDirectedGraph; +import org.jgrapht.graph.DefaultEdge; +import org.jgrapht.traverse.TopologicalOrderIterator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.convert.ConversionService; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.domain.Specification; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.ArrayList; +import java.util.*; +import java.util.regex.Pattern; +import java.util.stream.Collectors; @Service @Slf4j @@ -34,29 +61,212 @@ public class EmpSalaryItemService @Autowired private ConversionService conversionService; - @Autowired - private EmpSalaryItemConfigService empSalaryItemConfigService; + @Autowired private EmpSalaryItemConfigService empSalaryItemConfigService; + @Autowired private EmpSalaryStandardService empSalaryStandardService; + + private static void updatePriority(List enabled) { + DefaultDirectedGraph graph = + new DefaultDirectedGraph<>(DefaultEdge.class); + + for (var def : enabled) { + + graph.addVertex(def.getCode()); + + if (CollectionUtils.isNotEmpty(def.getDependOn())) { + + for (String d : def.getDependOn()) { + + graph.addVertex(def.getCode()); + graph.addVertex(d); + + graph.addEdge(d, def.getCode()); + } + } + } + + CycleDetector cycleDetector = new CycleDetector<>(graph); + + Set cycles = cycleDetector.findCycles(); + + if (CollectionUtils.isNotEmpty(cycles)) { + + throw new BizException("存在循环依赖: " + StringUtils.join(cycles, ", ")); + } + + TopologicalOrderIterator iterator = + new TopologicalOrderIterator<>(graph); + + int order = 10; + while (iterator.hasNext()) { + Object next = iterator.next(); + + for (var it : enabled) { + + if (StringUtils.equalsIgnoreCase(it.getCode(), next + "")) { + it.setPriority(order++); + break; + } + } + } + } + + private static void updateDep( + List enabled, List allItems) { + for (EmpSalaryItemEntity item : enabled) { + + if (StringUtils.equalsAnyIgnoreCase(item.getInputType(), "1")) { + item.setFormula(""); + item.setDependOn(new ArrayList<>()); + item.setPriority(0); + + item.setFormulaShow(""); + continue; + } + + if (StringUtils.isEmpty(item.getFormulaShow())) { + throw new BizException("公式计算项公式不能为空: " + item.getName()); + } + + String formula = item.getFormulaShow(); + List dependOn = new ArrayList<>(); + for (EmpSalaryItemEntity it : allItems) { + + var tmp = RegExUtils.replaceAll(formula, Pattern.quote(it.getName()), it.getCode()); + + if (!StringUtils.equals(tmp, formula)) { + + if (it.getStatus() != 1) { + throw new BizException("公式计算项依赖项未启用: " + it.getName()); + } + + formula = tmp; + + dependOn.add(it.getCode()); + } + } + + item.setDependOn(dependOn); + + try { + GroovyScriptUtils.validate(formula); + } catch (Exception e) { + + throw new BizException("无效的公式: " + item.getName()); + } + + item.setFormula(formula); + } + } + + /** + * 更新合计项 + * + * @param enabled + * @param yfItems + * @param kfItems + */ + private static void handleHeji( + List enabled, + List yfItems, + List kfItems) { + EmpSalaryItemEntity yfHeji = + enabled.stream() + .filter(x -> StringUtils.equalsIgnoreCase(x.getCode(), "yfheji")) + .findFirst() + .orElse(null); + + EmpSalaryItemEntity kfHeji = + enabled.stream() + .filter(x -> StringUtils.equalsIgnoreCase(x.getCode(), "kfheji")) + .findFirst() + .orElse(null); + + yfHeji.setDependOn(yfItems.stream().map(EmpSalaryItemEntity::getCode).toList()); + kfHeji.setDependOn(kfItems.stream().map(EmpSalaryItemEntity::getCode).toList()); + + yfHeji.setFormula(yfHeji.getDependOn().stream().collect(Collectors.joining(" + "))); + kfHeji.setFormula(kfHeji.getDependOn().stream().collect(Collectors.joining(" + "))); + + yfHeji.setFormulaShow( + yfItems.stream().map(x -> x.getName()).collect(Collectors.joining(" + "))); + kfHeji.setFormulaShow( + kfItems.stream().map(x -> x.getName()).collect(Collectors.joining(" + "))); + + yfHeji.setPriority(1000); + kfHeji.setPriority(1000); + } + public EmpSalaryItemDto create(CreateEmpSalaryItemDto request) { EmpSalaryItemEntity entity = mapper.toEntity(request); + if (repository.countByNameContainsAndOrganizationId( + request.getName(), Ctx.currentUser().getOrganizationId()) + > 0) { + throw new BizException("名称重复或者名称为其他工资项目的子串"); + } + this.repository.save(entity); empSalaryItemConfigService.initOrg(entity.getOrganizationId()); + this.reSyncAll(entity.getOrganizationId(), null, request.getName()); return getById(entity.getId()); } + private void reSyncAll(String organizationId, String oldName, String newName) { + List allItems = this.repository.findByOrganizationId(organizationId); + + for (EmpSalaryItemEntity allItem : allItems) { + if (StringUtils.isNotEmpty(oldName)) { + if (StringUtils.isNotEmpty(allItem.getFormula())) + allItem.setFormula(allItem.getFormula().replace(oldName, newName)); + } + } + + List enabled = + allItems.stream() + .filter(x -> x.getStatus() != null && x.getStatus() == 1) + .collect(Collectors.toList()); + + List yfItems = + enabled.stream() + .filter(x -> StringUtils.equalsIgnoreCase(x.getItemType(), "1")) + .toList(); + List kfItems = + enabled.stream() + .filter(x -> StringUtils.equalsIgnoreCase(x.getItemType(), "2")) + .toList(); + + handleHeji(enabled, yfItems, kfItems); + + updateDep(enabled, allItems); + + updatePriority(enabled); + + this.repository.saveAll(allItems); + } + public EmpSalaryItemDto update(UpdateEmpSalaryItemDto request) { EmpSalaryItemEntity entity = this.repository.get(request.getId()); + String oldName = entity.getName(); + String newName = request.getName(); + + if (!StringUtils.equals(oldName, newName)) { + if (repository.countByNameContainsAndOrganizationIdAndIdNot( + request.getName(), + Ctx.currentUser().getOrganizationId(), + request.getId()) + > 0) { + throw new BizException("名称重复或者名称为其他工资项目的子串"); + } + } + this.mapper.updateEntity(entity, request); this.repository.save(entity); empSalaryItemConfigService.initOrg(entity.getOrganizationId()); + this.reSyncAll(entity.getOrganizationId(), oldName, newName); return getById(entity.getId()); } - public void delete(IdRequest request) { - this.repository.deleteAllById(request.getIds()); - } - public EmpSalaryItemDto getById(String id) { EmpSalaryItemEntity entity = repository.get(id); @@ -75,7 +285,10 @@ public class EmpSalaryItemService return page.map(this.mapper::toDto); } - + public void delete(IdRequest request) { + throw new BizException("不支持删除, 请禁用相关工资项目"); + // this.repository.deleteAllById(request.getIds()); + } public void initOrg(String organizationId) { @@ -98,5 +311,176 @@ public class EmpSalaryItemService repository.save(item); } + + EmpSalaryItemEntity item = new EmpSalaryItemEntity(); + item.setItemType("3"); + item.setInputType("2"); + item.setFormulaShow(""); + item.setFormula(""); + item.setDependOn(new ArrayList<>()); + item.setPriority(0); + item.setOrganizationId(organizationId); + item.setName("应发合计"); + item.setCode("yfheji"); + item.setRemarks(""); + item.setSortKey(51); + item.setStatus(1); + item.setSystemPreset(true); + + repository.save(item); + + EmpSalaryItemEntity item2 = new EmpSalaryItemEntity(); + item2.setItemType("3"); + item2.setInputType("2"); + item2.setFormulaShow(""); + item2.setFormula(""); + item2.setDependOn(new ArrayList<>()); + item2.setPriority(0); + item2.setOrganizationId(organizationId); + item2.setName("扣发合计"); + item2.setCode("kfheji"); + item2.setRemarks(""); + item2.setSortKey(52); + item2.setStatus(1); + item2.setSystemPreset(true); + + repository.save(item2); + + EmpSalaryItemEntity item3 = new EmpSalaryItemEntity(); + item3.setItemType("3"); + item3.setInputType("2"); + item3.setFormulaShow("应发合计 - 扣发合计"); + item3.setFormula("yfheji - kfheji"); + item3.setDependOn(Arrays.asList("yfheji", "kfheji")); + item3.setPriority(0); + item3.setOrganizationId(organizationId); + item3.setName("实发工资"); + item3.setCode("sfheji"); + item3.setRemarks(""); + item3.setSortKey(53); + item3.setStatus(1); + item3.setSystemPreset(false); + + repository.save(item3); + } + + @SneakyThrows + public void calculate( + String organizationId, EmpSalaryBatchEntity batch, List list) { + + if (list.isEmpty()) { + return; + } + + long stdTime = 0; + long newInstanceTime = 0; + long setPropertyTime = 0; + long runTime = 0; + + long x = System.nanoTime(); + Class scriptClass = genClass(organizationId); + long y = System.nanoTime(); + + long genClassTime = y - x; + + for (EmpSalaryEntity salary : list) { + + long a = System.nanoTime(); + Map stdMap = + empSalaryStandardService.calculateSalaryStandard(salary.getEmployee()).stream() + .collect(Collectors.toMap(e -> e.getName(), e -> e)); + long b = System.nanoTime(); + stdTime = stdTime + (b - a); + + Script script = (Script) scriptClass.newInstance(); + long c = System.nanoTime(); + + newInstanceTime = newInstanceTime + (c - b); + + script.setProperty("salary", salary); + script.setProperty("emp", salary.getEmployee()); + script.setProperty("stdMap", stdMap); + script.setProperty("batch", batch); + + long d = System.nanoTime(); + + setPropertyTime = setPropertyTime + (d - c); + + script.run(); + long e = System.nanoTime(); + + runTime = runTime + (e - d); + } + + long totalTime = stdTime + newInstanceTime + setPropertyTime + runTime + genClassTime; + log.info( + "totalTime: {} ns {} ms {}%\n" + + "genClassTime: {} ns {} ms {}%\n" + + "stdTime: {} ns {} ms {}%\n" + + "newInstanceTime: {} ns {} ms {}%\n" + + "setPropertyTime: {} ns {} ms {}%\n" + + "runTime: {} ns {} ms {}%\n" + , + totalTime, + totalTime / 1000000, + 1L * 100.0, + genClassTime, + genClassTime / 1000000, + genClassTime / totalTime * 100.0, + stdTime, + stdTime / 1000000, + stdTime / totalTime * 100.0, + newInstanceTime, + newInstanceTime / 1000000, + newInstanceTime / totalTime * 100.0, + setPropertyTime, + setPropertyTime / 1000000, + setPropertyTime / totalTime * 100.0, + runTime, + runTime / 1000000, + runTime / totalTime * 100.0); + } + + private Class genClass(String organizationId) { + List items = + this.findAll( + new Specification() { + @Override + public Predicate toPredicate( + Root root, + CriteriaQuery query, + CriteriaBuilder criteriaBuilder) { + return criteriaBuilder.and( + criteriaBuilder.equal( + root.get("organizationId"), organizationId), + criteriaBuilder.equal(root.get("status"), 1)); + } + }); + + items.sort(Comparator.comparing(EmpSalaryItemEntity::getPriority)); + + var script = new StringBuilder(); + + for (EmpSalaryItemEntity item : items) { + + if (StringUtils.equalsAnyIgnoreCase(item.getInputType(), "1")) { + continue; + } + + if (StringUtils.isEmpty(item.getFormula())) { + throw new BizException("公式计算项公式不能为空: " + item.getName()); + } + + script.append("salary.") + .append(item.getCode()) + .append(" = ") + .append(item.getFormula()) + .append("\n"); + } + + Class scriptClass = + new GroovyClassLoader(this.getClass().getClassLoader(), new CompilerConfiguration()) + .parseClass(script.toString()); + return scriptClass; } }