mirror of
https://codeup.aliyun.com/64f7d6b8ce01efaafef1e678/coal/coal.git
synced 2026-01-25 07:46:40 +08:00
完善工资计算
This commit is contained in:
@@ -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";
|
||||
|
||||
@@ -61,4 +61,5 @@ public class EmpSalaryItemEntity extends OrgCommonEntity {
|
||||
private Integer priority;
|
||||
|
||||
|
||||
private Boolean systemPreset;
|
||||
}
|
||||
|
||||
@@ -6,4 +6,9 @@ import cn.lihongjie.coal.empSalaryItem.entity.EmpSalaryItemEntity;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface EmpSalaryItemRepository extends BaseRepository<EmpSalaryItemEntity> {}
|
||||
public interface EmpSalaryItemRepository extends BaseRepository<EmpSalaryItemEntity> {
|
||||
long countByNameContainsAndOrganizationId(String name, String organizationId);
|
||||
|
||||
long countByNameContainsAndOrganizationIdAndIdNot(
|
||||
String name, String organizationId, String id);
|
||||
}
|
||||
|
||||
@@ -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<EmpSalaryItemEntity> enabled) {
|
||||
DefaultDirectedGraph<String, DefaultEdge> 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<String, DefaultEdge> cycleDetector = new CycleDetector<>(graph);
|
||||
|
||||
Set<String> cycles = cycleDetector.findCycles();
|
||||
|
||||
if (CollectionUtils.isNotEmpty(cycles)) {
|
||||
|
||||
throw new BizException("存在循环依赖: " + StringUtils.join(cycles, ", "));
|
||||
}
|
||||
|
||||
TopologicalOrderIterator<String, DefaultEdge> 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<EmpSalaryItemEntity> enabled, List<EmpSalaryItemEntity> 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<String> 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<EmpSalaryItemEntity> enabled,
|
||||
List<EmpSalaryItemEntity> yfItems,
|
||||
List<EmpSalaryItemEntity> 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<EmpSalaryItemEntity> 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<EmpSalaryItemEntity> enabled =
|
||||
allItems.stream()
|
||||
.filter(x -> x.getStatus() != null && x.getStatus() == 1)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
List<EmpSalaryItemEntity> yfItems =
|
||||
enabled.stream()
|
||||
.filter(x -> StringUtils.equalsIgnoreCase(x.getItemType(), "1"))
|
||||
.toList();
|
||||
List<EmpSalaryItemEntity> 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<EmpSalaryEntity> 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<String, EmpSalaryStandardResult> 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<EmpSalaryItemEntity> items =
|
||||
this.findAll(
|
||||
new Specification<EmpSalaryItemEntity>() {
|
||||
@Override
|
||||
public Predicate toPredicate(
|
||||
Root<EmpSalaryItemEntity> 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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user