mirror of
https://codeup.aliyun.com/64f7d6b8ce01efaafef1e678/coal/coal.git
synced 2026-01-25 07:46:40 +08:00
完善
This commit is contained in:
184
src/main/java/cn/lihongjie/coal/common/GroovyScriptManager.java
Normal file
184
src/main/java/cn/lihongjie/coal/common/GroovyScriptManager.java
Normal file
@@ -0,0 +1,184 @@
|
||||
package cn.lihongjie.coal.common;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import com.github.benmanes.caffeine.cache.CaffeineSpec;
|
||||
|
||||
import groovy.lang.GroovyClassLoader;
|
||||
import groovy.lang.GroovyCodeSource;
|
||||
import groovy.lang.Script;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.codehaus.groovy.control.CompilerConfiguration;
|
||||
import org.springframework.cglib.core.ReflectUtils;
|
||||
import org.springframework.util.DigestUtils;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@Slf4j
|
||||
public class GroovyScriptManager {
|
||||
|
||||
private static final AtomicInteger counter = new AtomicInteger(0);
|
||||
private final String name;
|
||||
private final Cache<String, Class> scriptCache;
|
||||
private final GroovyClassLoader groovyClassLoader;
|
||||
private boolean logCompileTime = false;
|
||||
|
||||
public GroovyScriptManager() {
|
||||
this(true);
|
||||
}
|
||||
|
||||
public GroovyScriptManager(boolean logCompileTime) {
|
||||
this((c) -> {}, logCompileTime);
|
||||
}
|
||||
|
||||
public GroovyScriptManager(
|
||||
Consumer<CompilerConfiguration> configCustomizer, boolean logCompileTime) {
|
||||
this(null, configCustomizer, logCompileTime);
|
||||
}
|
||||
|
||||
public GroovyScriptManager(
|
||||
String name, Consumer<CompilerConfiguration> configCustomizer, boolean logCompileTime) {
|
||||
this(name, "maximumSize=1000,expireAfterAccess=1h", configCustomizer, logCompileTime);
|
||||
}
|
||||
|
||||
public GroovyScriptManager(
|
||||
String name,
|
||||
String specification,
|
||||
Consumer<CompilerConfiguration> configConsumer,
|
||||
boolean logCompileTime) {
|
||||
|
||||
this.name = StringUtils.defaultIfBlank(name, "GroovyScriptManager-" + counter.get());
|
||||
scriptCache = Caffeine.from(CaffeineSpec.parse(specification)).build();
|
||||
|
||||
CompilerConfiguration config = new CompilerConfiguration();
|
||||
configConsumer.accept(config);
|
||||
|
||||
this.groovyClassLoader = new GroovyClassLoader(this.getClass().getClassLoader(), config);
|
||||
this.logCompileTime = logCompileTime;
|
||||
|
||||
counter.incrementAndGet();
|
||||
|
||||
log.info("GroovyScriptManager: {} created", name);
|
||||
}
|
||||
|
||||
/** 高性能版本API, 允许自定义缓存key */
|
||||
public Class compile(String key, Supplier<String> scriptSupplier) {
|
||||
|
||||
StringBuilder logText = new StringBuilder();
|
||||
|
||||
logText.append("GroovyScriptManager: ").append(name).append("\n");
|
||||
|
||||
logText.append("compile groovy script: ").append(key).append("\n");
|
||||
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
try {
|
||||
|
||||
return scriptCache.get(
|
||||
key,
|
||||
k -> {
|
||||
long getStart = System.currentTimeMillis();
|
||||
String script = scriptSupplier.get();
|
||||
|
||||
long getEnd = System.currentTimeMillis();
|
||||
|
||||
logText.append("getScriptTime: ").append(getEnd - getStart).append("ms\n");
|
||||
GroovyCodeSource codeSource =
|
||||
new GroovyCodeSource(script, key, "/groovy/script");
|
||||
codeSource.setCachable(false);
|
||||
|
||||
long parseStart = System.currentTimeMillis();
|
||||
Class parsedClass = this.groovyClassLoader.parseClass(codeSource);
|
||||
long parseEnd = System.currentTimeMillis();
|
||||
|
||||
logText.append("parseTime: ").append(parseEnd - parseStart).append("ms\n");
|
||||
return parsedClass;
|
||||
});
|
||||
} finally {
|
||||
long end = System.currentTimeMillis();
|
||||
|
||||
logText.append("totalTime: ").append(end - start).append("ms");
|
||||
}
|
||||
}
|
||||
|
||||
public Script newInstance(String key, Supplier<String> scriptSupplier) {
|
||||
|
||||
Class aClass = compile(key, scriptSupplier);
|
||||
|
||||
Object o = ReflectUtils.newInstance(aClass);
|
||||
|
||||
if (o instanceof Script) {
|
||||
return (Script) o;
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"script must be instance of groovy.lang.Script, get " + aClass.getName());
|
||||
}
|
||||
}
|
||||
|
||||
public void runWithScript(
|
||||
String key, Supplier<String> scriptSupplier, Consumer<Script> scriptCustomizer) {
|
||||
Script script = newInstance(key, scriptSupplier);
|
||||
|
||||
scriptCustomizer.accept(script);
|
||||
|
||||
script.run();
|
||||
}
|
||||
|
||||
/** 易用性API, key 为 脚本内容的md5 */
|
||||
public Class compile(Supplier<String> scriptSupplier) {
|
||||
return compile(
|
||||
DigestUtils.md5DigestAsHex(scriptSupplier.get().getBytes(StandardCharsets.UTF_8)),
|
||||
scriptSupplier);
|
||||
}
|
||||
|
||||
public Class compile(String script) {
|
||||
return compile(
|
||||
DigestUtils.md5DigestAsHex(script.getBytes(StandardCharsets.UTF_8)), () -> script);
|
||||
}
|
||||
|
||||
public Script newInstance(Supplier<String> scriptSupplier) {
|
||||
return newInstance(
|
||||
DigestUtils.md5DigestAsHex(scriptSupplier.get().getBytes(StandardCharsets.UTF_8)),
|
||||
scriptSupplier);
|
||||
}
|
||||
|
||||
public Script newInstance(String script) {
|
||||
return newInstance(
|
||||
DigestUtils.md5DigestAsHex(script.getBytes(StandardCharsets.UTF_8)), () -> script);
|
||||
}
|
||||
|
||||
public void runWithScript(Supplier<String> scriptSupplier, Consumer<Script> scriptCustomizer) {
|
||||
runWithScript(
|
||||
DigestUtils.md5DigestAsHex(scriptSupplier.get().getBytes(StandardCharsets.UTF_8)),
|
||||
scriptSupplier,
|
||||
scriptCustomizer);
|
||||
}
|
||||
|
||||
public void runWithScript(String script, Consumer<Script> scriptCustomizer) {
|
||||
runWithScript(
|
||||
DigestUtils.md5DigestAsHex(script.getBytes(StandardCharsets.UTF_8)),
|
||||
() -> script,
|
||||
scriptCustomizer);
|
||||
}
|
||||
|
||||
public void invalidateAll() {
|
||||
scriptCache.invalidateAll();
|
||||
}
|
||||
|
||||
public void invalidate(String key) {
|
||||
scriptCache.invalidate(key);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public void destroy() {
|
||||
scriptCache.invalidateAll();
|
||||
groovyClassLoader.close();
|
||||
}
|
||||
}
|
||||
41
src/main/java/cn/lihongjie/coal/common/MapUtils.java
Normal file
41
src/main/java/cn/lihongjie/coal/common/MapUtils.java
Normal file
@@ -0,0 +1,41 @@
|
||||
package cn.lihongjie.coal.common;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@UtilityClass
|
||||
public class MapUtils {
|
||||
|
||||
public static <K, V> Map<K, V> keepKey(Map<K, V> src, Collection<K> keys) {
|
||||
return keepOnlyKeys(src, keys::contains, HashMap::new);
|
||||
}
|
||||
|
||||
public static <K, V> Map<K, V> keepOnlyKeys(
|
||||
Map<K, V> src, Predicate<K> predicate, Supplier<Map<K, V>> mapSupplier) {
|
||||
Map<K, V> result = mapSupplier.get();
|
||||
|
||||
for (Map.Entry<K, V> entry : src.entrySet()) {
|
||||
if (predicate.test(entry.getKey())) {
|
||||
result.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <K, V> void merge(Map<K, V> src, Map<K, V> target, Predicate<K> predicate) {
|
||||
|
||||
for (Map.Entry<K, V> entry : src.entrySet()) {
|
||||
if (predicate.test(entry.getKey())) {
|
||||
target.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static <K, V> void merge(Map<K, V> src, Map<K, V> target, Collection<K> keys) {
|
||||
|
||||
merge(src, target, keys::contains);
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,8 @@ import cn.lihongjie.coal.empMonthAttendance.dto.UpdateEmpMonthAttendanceDto;
|
||||
import cn.lihongjie.coal.empMonthAttendance.entity.EmpMonthAttendanceEntity;
|
||||
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.Mappings;
|
||||
import org.mapstruct.control.DeepClone;
|
||||
|
||||
@Mapper(
|
||||
@@ -22,4 +24,9 @@ public interface EmpMonthAttendanceMapper
|
||||
CreateEmpMonthAttendanceDto,
|
||||
UpdateEmpMonthAttendanceDto> {
|
||||
CreateEmpMonthAttendanceDto toCreateDto(UpdateEmpMonthAttendanceDto dto);
|
||||
|
||||
@Mappings(
|
||||
@Mapping(target = "employee", ignore = true)
|
||||
)
|
||||
EmpMonthAttendanceDto toDto2(EmpMonthAttendanceEntity empMonthAttendance);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package cn.lihongjie.coal.empSalary.dto;
|
||||
import cn.lihongjie.coal.base.dto.OrgCommonDto;
|
||||
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -13,9 +15,13 @@ public class CreateEmpSalaryDto extends OrgCommonDto {
|
||||
|
||||
|
||||
@ManyToOne
|
||||
@NotNull
|
||||
@NotEmpty
|
||||
private String batch;
|
||||
|
||||
@ManyToOne
|
||||
@NotNull
|
||||
@NotEmpty
|
||||
private String employee;
|
||||
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package cn.lihongjie.coal.empSalary.dto;
|
||||
|
||||
import cn.lihongjie.coal.base.dto.OrgCommonDto;
|
||||
import cn.lihongjie.coal.base.dto.SimpleDto;
|
||||
import cn.lihongjie.coal.common.DictCode;
|
||||
import cn.lihongjie.coal.empSalaryBatch.dto.EmpSalaryBatchDto;
|
||||
import cn.lihongjie.coal.employee.dto.EmployeeDto;
|
||||
import cn.lihongjie.coal.pojoProcessor.DictTranslate;
|
||||
|
||||
import jakarta.persistence.ManyToOne;
|
||||
@@ -26,7 +26,7 @@ public class EmpSalaryDto extends OrgCommonDto {
|
||||
private EmpSalaryBatchDto batch;
|
||||
|
||||
@ManyToOne
|
||||
private EmployeeDto employee;
|
||||
private SimpleDto employee;
|
||||
|
||||
|
||||
@Comment("员工快照")
|
||||
|
||||
@@ -3,6 +3,8 @@ package cn.lihongjie.coal.empSalary.dto;
|
||||
import cn.lihongjie.coal.base.dto.OrgCommonDto;
|
||||
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -13,9 +15,13 @@ public class UpdateEmpSalaryDto extends OrgCommonDto {
|
||||
|
||||
|
||||
@ManyToOne
|
||||
@NotNull
|
||||
@NotEmpty
|
||||
private String batch;
|
||||
|
||||
@ManyToOne
|
||||
@NotNull
|
||||
@NotEmpty
|
||||
private String employee;
|
||||
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import cn.lihongjie.coal.employee.entity.EmployeeEntity;
|
||||
import cn.lihongjie.coal.pojoProcessor.DictTranslate;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
|
||||
import lombok.Data;
|
||||
@@ -33,11 +34,14 @@ public class EmpSalaryEntity extends OrgCommonEntity {
|
||||
"item41", "item42", "item43", "item44", "item45", "item46", "item47", "item48",
|
||||
"item49", "item50", "yfheji", "kfheji", "sfheji");
|
||||
|
||||
@ManyToOne private EmpSalaryBatchEntity batch;
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
private EmpSalaryBatchEntity batch;
|
||||
|
||||
@ManyToOne private EmployeeEntity employee;
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
private EmployeeEntity employee;
|
||||
|
||||
@ManyToOne private EmpMonthAttendanceEntity empMonthAttendance;
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
private EmpMonthAttendanceEntity empMonthAttendance;
|
||||
|
||||
private BigDecimal item0;
|
||||
private BigDecimal item1;
|
||||
|
||||
@@ -5,10 +5,12 @@ import cn.lihongjie.coal.base.mapper.CommonEntityMapper;
|
||||
import cn.lihongjie.coal.base.mapper.CommonMapper;
|
||||
import cn.lihongjie.coal.empSalary.dto.CreateEmpSalaryDto;
|
||||
import cn.lihongjie.coal.empSalary.dto.EmpSalaryDto;
|
||||
import cn.lihongjie.coal.empSalary.dto.RecalculatePreviewDto;
|
||||
import cn.lihongjie.coal.empSalary.dto.UpdateEmpSalaryDto;
|
||||
import cn.lihongjie.coal.empSalary.entity.EmpSalaryEntity;
|
||||
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.MappingTarget;
|
||||
import org.mapstruct.control.DeepClone;
|
||||
|
||||
@Mapper(
|
||||
@@ -16,4 +18,6 @@ import org.mapstruct.control.DeepClone;
|
||||
uses = {CommonMapper.class, CommonEntityMapper.class},
|
||||
mappingControl = DeepClone.class)
|
||||
public interface EmpSalaryMapper
|
||||
extends BaseMapper<EmpSalaryEntity, EmpSalaryDto, CreateEmpSalaryDto, UpdateEmpSalaryDto> {}
|
||||
extends BaseMapper<EmpSalaryEntity, EmpSalaryDto, CreateEmpSalaryDto, UpdateEmpSalaryDto> {
|
||||
void updateItems(RecalculatePreviewDto recDto, @MappingTarget UpdateEmpSalaryDto request);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import cn.lihongjie.coal.base.dto.IdRequest;
|
||||
import cn.lihongjie.coal.base.entity.OrgCommonEntity;
|
||||
import cn.lihongjie.coal.base.service.BaseService;
|
||||
import cn.lihongjie.coal.common.GroovyScriptUtils;
|
||||
import cn.lihongjie.coal.common.MapUtils;
|
||||
import cn.lihongjie.coal.common.ReflectUtils;
|
||||
import cn.lihongjie.coal.empMonthAttendance.dto.EmpMonthAttendanceDto;
|
||||
import cn.lihongjie.coal.empMonthAttendance.entity.EmpMonthAttendanceEntity;
|
||||
@@ -19,8 +20,10 @@ import cn.lihongjie.coal.empSalaryBatch.mapper.EmpSalaryBatchMapper;
|
||||
import cn.lihongjie.coal.empSalaryBatch.service.EmpSalaryBatchService;
|
||||
import cn.lihongjie.coal.empSalaryItem.entity.EmpSalaryItemEntity;
|
||||
import cn.lihongjie.coal.empSalaryItem.service.EmpSalaryItemService;
|
||||
import cn.lihongjie.coal.employee.dto.EmployeeCalculateDto;
|
||||
import cn.lihongjie.coal.employee.dto.EmployeeDto;
|
||||
import cn.lihongjie.coal.employee.entity.EmployeeEntity;
|
||||
import cn.lihongjie.coal.employee.mapper.EmployeeMapper;
|
||||
import cn.lihongjie.coal.employee.service.EmployeeService;
|
||||
import cn.lihongjie.coal.exception.BizException;
|
||||
|
||||
@@ -70,12 +73,7 @@ public class EmpSalaryService extends BaseService<EmpSalaryEntity, EmpSalaryRepo
|
||||
|
||||
@Autowired private ConversionService conversionService;
|
||||
|
||||
public EmpSalaryDto create(CreateEmpSalaryDto request) {
|
||||
EmpSalaryEntity entity = mapper.toEntity(request);
|
||||
|
||||
this.repository.save(entity);
|
||||
return getById(entity.getId());
|
||||
}
|
||||
@Autowired EmployeeMapper employeeMapper;
|
||||
|
||||
@PersistenceContext EntityManager em;
|
||||
@Autowired RedissonClient redissonClient;
|
||||
@@ -108,11 +106,35 @@ public class EmpSalaryService extends BaseService<EmpSalaryEntity, EmpSalaryRepo
|
||||
|
||||
@Autowired EmpSalaryItemService empSalaryItemService;
|
||||
|
||||
private static void assertBatchEditable(EmpSalaryBatchEntity salaryBatch) {
|
||||
if (salaryBatch.getStatus() != 1) {
|
||||
throw new BizException("批次不属于编辑状态, 无法编辑或删除");
|
||||
}
|
||||
}
|
||||
|
||||
private static void removeCommonField(Map<String, Object> ctx) {
|
||||
ReflectUtils.getAllFieldsList(OrgCommonEntity.class)
|
||||
.forEach(
|
||||
f -> {
|
||||
ctx.remove(f.getName());
|
||||
});
|
||||
}
|
||||
|
||||
public EmpSalaryDto create(CreateEmpSalaryDto request) {
|
||||
assertBatchEditable(request.getBatch());
|
||||
EmpSalaryEntity entity = mapper.toEntity(request);
|
||||
|
||||
this.repository.save(entity);
|
||||
return getById(entity.getId());
|
||||
}
|
||||
|
||||
public EmpSalaryDto update(UpdateEmpSalaryDto request) {
|
||||
|
||||
assertBatchEditable(request.getBatch());
|
||||
|
||||
var recDto = recalculatePreview(request);
|
||||
|
||||
updateItems(recDto, request);
|
||||
this.mapper.updateItems(recDto, request);
|
||||
|
||||
EmpSalaryEntity entity = this.repository.get(request.getId());
|
||||
if (this.repository.containArchived(request.getId())) {
|
||||
@@ -125,21 +147,6 @@ public class EmpSalaryService extends BaseService<EmpSalaryEntity, EmpSalaryRepo
|
||||
return getById(entity.getId());
|
||||
}
|
||||
|
||||
private void updateItems(RecalculatePreviewDto recDto, UpdateEmpSalaryDto request) {
|
||||
|
||||
for (String key : EmpSalaryEntity.ITEM_KEYS) {
|
||||
ReflectUtils.writeField(
|
||||
request, key, conversionService.convert(ReflectUtils.readField(recDto, key), BigDecimal.class));
|
||||
}
|
||||
}
|
||||
|
||||
public void delete(IdRequest request) {
|
||||
if (this.repository.containArchived(request)) {
|
||||
throw new BizException("部分数据已归档,无法编辑或删除");
|
||||
}
|
||||
this.repository.deleteAllById(request.getIds());
|
||||
}
|
||||
|
||||
@Autowired EmpSalaryBatchService batchService;
|
||||
@Autowired EmployeeService employeeService;
|
||||
|
||||
@@ -193,47 +200,23 @@ public class EmpSalaryService extends BaseService<EmpSalaryEntity, EmpSalaryRepo
|
||||
.getResultList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 按照ID进行重新计算
|
||||
*
|
||||
* @param ids
|
||||
*/
|
||||
public void recalculate(IdRequest ids) {
|
||||
private void assertBatchEditable(String batch) {
|
||||
|
||||
List<EmpSalaryEntity> salaries =
|
||||
em.createQuery(
|
||||
"select s from EmpSalaryEntity s join fetch s.employee join fetch s.batch join fetch s.empMonthAttendance where s.id in :ids",
|
||||
EmpSalaryEntity.class)
|
||||
.setParameter("ids", ids.getIds())
|
||||
.getResultList();
|
||||
EmpSalaryBatchEntity salaryBatch = batchService.get(batch);
|
||||
|
||||
recalculate(salaries, true);
|
||||
assertBatchEditable(salaryBatch);
|
||||
}
|
||||
|
||||
/** 针对某个员工重新计算, 用于编辑表单的预览 */
|
||||
@SneakyThrows
|
||||
public RecalculatePreviewDto recalculatePreview(UpdateEmpSalaryDto dto) {
|
||||
public void delete(IdRequest request) {
|
||||
|
||||
EmpSalaryBatchEntity batch = this.batchService.get(dto.getBatch());
|
||||
List<EmpSalaryEntity> all = this.findAllByIds(request.getIds());
|
||||
|
||||
EmpSalaryEntity salary = this.get(dto.getId());
|
||||
all.stream().map(x -> x.getBatch().getId()).distinct().forEach(this::assertBatchEditable);
|
||||
|
||||
GroovyClassLoader groovyClassLoader = null;
|
||||
groovyClassLoader = initClassLoader(groovyClassLoader);
|
||||
|
||||
Script scriptObj = initScriptClass(groovyClassLoader, genScript(batch));
|
||||
|
||||
Map<String, Object> ctx = buildCtx(null, null, null, salary);
|
||||
|
||||
// 覆盖工资项目
|
||||
|
||||
overWriteSalaryItem(ctx, ReflectUtils.toMap(dto));
|
||||
|
||||
scriptObj.setBinding(new Binding(Map.of("salary", ctx)));
|
||||
|
||||
scriptObj.run();
|
||||
|
||||
return buildDto(ctx);
|
||||
if (this.repository.containArchived(request)) {
|
||||
throw new BizException("部分数据已归档,无法编辑或删除");
|
||||
}
|
||||
this.repository.deleteAllById(request.getIds());
|
||||
}
|
||||
|
||||
private RecalculatePreviewDto buildDto(Map<String, Object> ctx) {
|
||||
@@ -264,6 +247,57 @@ public class EmpSalaryService extends BaseService<EmpSalaryEntity, EmpSalaryRepo
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 按照ID进行重新计算
|
||||
*
|
||||
* @param ids
|
||||
*/
|
||||
public void recalculate(IdRequest ids) {
|
||||
|
||||
List<EmpSalaryEntity> salaries =
|
||||
em.createQuery(
|
||||
"""
|
||||
select s from EmpSalaryEntity s
|
||||
left join fetch s.employee e
|
||||
left join fetch e.department
|
||||
left join fetch e.jobPost
|
||||
|
||||
left join fetch s.batch
|
||||
left join fetch s.empMonthAttendance
|
||||
where s.id in :ids""",
|
||||
EmpSalaryEntity.class)
|
||||
.setParameter("ids", ids.getIds())
|
||||
.getResultList();
|
||||
|
||||
|
||||
salaries.stream().map(x -> x.getBatch().getId()).distinct().forEach(this::assertBatchEditable);
|
||||
|
||||
recalculate(salaries, true);
|
||||
}
|
||||
|
||||
/** 针对某个员工重新计算, 用于编辑表单的预览 */
|
||||
@SneakyThrows
|
||||
public RecalculatePreviewDto recalculatePreview(UpdateEmpSalaryDto dto) {
|
||||
|
||||
EmpSalaryBatchEntity batch = this.batchService.get(dto.getBatch());
|
||||
|
||||
EmpSalaryEntity salary = this.get(dto.getId());
|
||||
|
||||
Script scriptObj = empSalaryItemService.newScriptInstance(batch.getOrganizationId());
|
||||
|
||||
Map<String, Object> ctx = buildPreviewCtx(dto, salary);
|
||||
|
||||
// 覆盖工资项目
|
||||
|
||||
overWriteSalaryItem(ctx, ReflectUtils.toMap(dto));
|
||||
|
||||
scriptObj.setBinding(new Binding(Map.of("salary", ctx)));
|
||||
|
||||
scriptObj.run();
|
||||
|
||||
return buildDto(ctx);
|
||||
}
|
||||
|
||||
/** 重新计算 */
|
||||
@SneakyThrows
|
||||
public void recalculate(List<EmpSalaryEntity> salaries, boolean update) {
|
||||
@@ -274,7 +308,7 @@ public class EmpSalaryService extends BaseService<EmpSalaryEntity, EmpSalaryRepo
|
||||
|
||||
EmpSalaryBatchEntity batch = salaries.get(0).getBatch();
|
||||
|
||||
StopWatch stopWatch = new StopWatch("initSalary: " + batch.getId());
|
||||
StopWatch stopWatch = new StopWatch("recalculate: " + batch.getId());
|
||||
|
||||
stopWatch.start("getLock");
|
||||
RLock lock = redissonClient.getLock("batchModify." + batch.getId());
|
||||
@@ -295,26 +329,19 @@ public class EmpSalaryService extends BaseService<EmpSalaryEntity, EmpSalaryRepo
|
||||
checkBatchStatus(batch);
|
||||
stopWatch.stop();
|
||||
|
||||
// 查询考勤数据
|
||||
|
||||
stopWatch.start("genScript");
|
||||
// 生成计算脚本
|
||||
String script = genScript(batch);
|
||||
|
||||
stopWatch.stop();
|
||||
|
||||
stopWatch.start("parseScript");
|
||||
groovyClassLoader = initClassLoader(groovyClassLoader);
|
||||
|
||||
Script scriptObj = initScriptClass(groovyClassLoader, script);
|
||||
|
||||
Script scriptObj = empSalaryItemService.newScriptInstance(batch.getOrganizationId());
|
||||
|
||||
stopWatch.stop();
|
||||
|
||||
List<EmployeeDto> dtoByIds =
|
||||
this.employeeService.getDtoByIds(
|
||||
salaries.stream().map(e -> e.getEmployee().getId()).toList());
|
||||
var dtoByIds = salaries.stream().map(x -> x.getEmployee()).map(employeeMapper::toCalculateDto).toList();
|
||||
|
||||
Map<String, EmployeeDto> dtoMap =
|
||||
|
||||
Map<String, EmployeeCalculateDto> dtoMap =
|
||||
dtoByIds.stream().collect(Collectors.toMap(e -> e.getId(), e -> e));
|
||||
|
||||
// 执行计算脚本
|
||||
@@ -323,11 +350,9 @@ public class EmpSalaryService extends BaseService<EmpSalaryEntity, EmpSalaryRepo
|
||||
|
||||
var employee = salary.getEmployee();
|
||||
|
||||
EmpMonthAttendanceEntity attendance = salary.getEmpMonthAttendance();
|
||||
stopWatch.start("buildCtx: " + employee.getName());
|
||||
|
||||
Map<String, Object> ctx =
|
||||
buildCtx(batch, dtoMap.get(employee.getId()), attendance, salary);
|
||||
Map<String, Object> ctx = buildRecalculateCtx(salary);
|
||||
|
||||
stopWatch.stop();
|
||||
|
||||
@@ -347,7 +372,6 @@ public class EmpSalaryService extends BaseService<EmpSalaryEntity, EmpSalaryRepo
|
||||
salary.update(salaryEntity);
|
||||
|
||||
stopWatch.stop();
|
||||
// salaries.add(salaryEntity);
|
||||
}
|
||||
|
||||
// 保存到数据库
|
||||
@@ -380,11 +404,10 @@ public class EmpSalaryService extends BaseService<EmpSalaryEntity, EmpSalaryRepo
|
||||
public void initSalary(InitSalaryDto initSalaryDto) {
|
||||
|
||||
EmpSalaryBatchEntity batch = batchService.get(initSalaryDto.getBatchId());
|
||||
|
||||
List<EmployeeDto> employees = employeeService.getDtoByIds(initSalaryDto.getEmployeesIds());
|
||||
|
||||
StopWatch stopWatch = new StopWatch("initSalary: " + batch.getId());
|
||||
|
||||
var employees = employeeService.getCalculateDtoByIds(initSalaryDto.getEmployeesIds());
|
||||
|
||||
stopWatch.start("getLock");
|
||||
RLock lock = redissonClient.getLock("batchModify." + batch.getId());
|
||||
|
||||
@@ -394,7 +417,6 @@ public class EmpSalaryService extends BaseService<EmpSalaryEntity, EmpSalaryRepo
|
||||
if (!tryLock) {
|
||||
batchModifing(batch);
|
||||
}
|
||||
GroovyClassLoader groovyClassLoader = null;
|
||||
|
||||
try {
|
||||
|
||||
@@ -418,12 +440,6 @@ public class EmpSalaryService extends BaseService<EmpSalaryEntity, EmpSalaryRepo
|
||||
|
||||
stopWatch.stop();
|
||||
|
||||
stopWatch.start("genScript");
|
||||
// 生成计算脚本
|
||||
String script = genScript(batch);
|
||||
|
||||
stopWatch.stop();
|
||||
|
||||
stopWatch.start("getItems");
|
||||
// 获取工资项
|
||||
List<EmpSalaryItemEntity> items =
|
||||
@@ -434,16 +450,14 @@ public class EmpSalaryService extends BaseService<EmpSalaryEntity, EmpSalaryRepo
|
||||
.filter(
|
||||
x ->
|
||||
BooleanUtils.isTrue(x.getInherit())
|
||||
&& StringUtils.equalsAny(x.getInputType(), "0"))
|
||||
&& StringUtils.equalsAny(x.getInputType(), "1"))
|
||||
.filter(x -> ObjectUtils.notEqual(x.getStatus(), 0))
|
||||
.toList();
|
||||
|
||||
stopWatch.stop();
|
||||
|
||||
stopWatch.start("parseScript");
|
||||
groovyClassLoader = initClassLoader(groovyClassLoader);
|
||||
|
||||
Script scriptObj = initScriptClass(groovyClassLoader, script);
|
||||
stopWatch.start("newScriptInstance");
|
||||
Script scriptObj = empSalaryItemService.newScriptInstance(batch.getOrganizationId());
|
||||
|
||||
stopWatch.stop();
|
||||
|
||||
@@ -463,7 +477,7 @@ public class EmpSalaryService extends BaseService<EmpSalaryEntity, EmpSalaryRepo
|
||||
|
||||
List<EmpSalaryEntity> salaries = new ArrayList<>();
|
||||
|
||||
for (EmployeeDto employee : employees) {
|
||||
for (var employee : employees) {
|
||||
|
||||
EmpMonthAttendanceEntity attendance =
|
||||
attMap.containsKey(employee.getId())
|
||||
@@ -471,12 +485,9 @@ public class EmpSalaryService extends BaseService<EmpSalaryEntity, EmpSalaryRepo
|
||||
: null;
|
||||
stopWatch.start("buildCtx: " + employee.getName());
|
||||
|
||||
Map<String, Object> ctx = buildCtx(batch, employee, attendance, null);
|
||||
|
||||
stopWatch.stop();
|
||||
|
||||
// todo 处理继承项
|
||||
Map<String, Object> ctx = buildInitCtx(batch, employee, attendance);
|
||||
|
||||
// 继承历史工资数据
|
||||
if (salaryHisMap.containsKey(employee.getId())
|
||||
&& CollectionUtils.isNotEmpty(inheritItems)) {
|
||||
|
||||
@@ -488,6 +499,7 @@ public class EmpSalaryService extends BaseService<EmpSalaryEntity, EmpSalaryRepo
|
||||
salaryHisMap.get(employee.getId()), inheritItem.getCode()));
|
||||
}
|
||||
}
|
||||
stopWatch.stop();
|
||||
|
||||
// 计算
|
||||
|
||||
@@ -521,14 +533,114 @@ public class EmpSalaryService extends BaseService<EmpSalaryEntity, EmpSalaryRepo
|
||||
stopWatch.stop();
|
||||
}
|
||||
|
||||
if (groovyClassLoader != null) {
|
||||
groovyClassLoader.close();
|
||||
}
|
||||
|
||||
log.info(stopWatch.prettyPrint());
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, Object> buildInitCtx(
|
||||
EmpSalaryBatchEntity batchEntity,
|
||||
EmployeeCalculateDto employeeEntity,
|
||||
EmpMonthAttendanceEntity monthAttendanceEntity) {
|
||||
|
||||
var employeeCtx = buildCtx(employeeEntity);
|
||||
var batchCtx = buildCtx(batchEntity);
|
||||
var empMonthAttendanceCtx = buildCtx(monthAttendanceEntity);
|
||||
var salaryCtx = new HashMap<String, Object>();
|
||||
|
||||
HashMap<String, Object> map = new HashMap<>();
|
||||
map.putAll(salaryCtx);
|
||||
map.putAll(employeeCtx);
|
||||
map.putAll(batchCtx);
|
||||
map.putAll(empMonthAttendanceCtx);
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑预览的时候构建上下文 从数据库中取所有冗余数据 + 前端传过来的工资数据
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Map<String, Object> buildPreviewCtx(UpdateEmpSalaryDto dto, EmpSalaryEntity entity) {
|
||||
|
||||
EmpSalaryDto salaryDto = this.mapper.toDto(entity);
|
||||
|
||||
Map<String, Object> userInput = ReflectUtils.toMap(dto);
|
||||
|
||||
Map<String, Object> ctx = ReflectUtils.toMap(salaryDto);
|
||||
|
||||
MapUtils.merge(userInput, ctx, EmpSalaryEntity.ITEM_KEYS);
|
||||
|
||||
return ctx;
|
||||
}
|
||||
|
||||
public Map<String, Object> buildRecalculateCtx(EmpSalaryEntity entity) {
|
||||
|
||||
var employeeCtx = buildCtx(employeeMapper.toCalculateDto(entity.getEmployee()));
|
||||
var batchCtx = buildCtx(entity.getBatch());
|
||||
var empMonthAttendanceCtx = buildCtx(entity.getEmpMonthAttendance());
|
||||
var salaryCtx = buildCtx(entity, true);
|
||||
|
||||
HashMap<String, Object> map = new HashMap<>();
|
||||
map.putAll(salaryCtx);
|
||||
map.putAll(employeeCtx);
|
||||
map.putAll(batchCtx);
|
||||
map.putAll(empMonthAttendanceCtx);
|
||||
return map;
|
||||
}
|
||||
|
||||
private Map<String, Object> buildCtx(EmpSalaryEntity entity, boolean onlyItems) {
|
||||
|
||||
if (entity == null) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
EmpSalaryDto salary = empSalaryMapper.toDto(entity);
|
||||
|
||||
Map<String, Object> map = ReflectUtils.toMap(salary);
|
||||
|
||||
if (onlyItems) {
|
||||
|
||||
return MapUtils.keepKey(map, EmpSalaryEntity.ITEM_KEYS);
|
||||
} else {
|
||||
|
||||
removeCommonField(map);
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Object> buildCtx(EmpMonthAttendanceEntity empMonthAttendance) {
|
||||
|
||||
if (empMonthAttendance == null) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
var attendance = empMonthAttendanceMapper.toDto2(empMonthAttendance);
|
||||
|
||||
Map<String, Object> map = ReflectUtils.toMap(attendance);
|
||||
|
||||
map.put("attendanceId", empMonthAttendance.getId());
|
||||
|
||||
removeCommonField(map);
|
||||
return map;
|
||||
}
|
||||
|
||||
private Map<String, Object> buildCtx(EmpSalaryBatchEntity batch) {
|
||||
|
||||
if (batch == null) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
var batchDto = empSalaryBatchMapper.toDto(batch);
|
||||
|
||||
Map<String, Object> batchMap = ReflectUtils.toMap(batchDto);
|
||||
|
||||
batchMap.put("batchId", batch.getId());
|
||||
|
||||
removeCommonField(batchMap);
|
||||
|
||||
return batchMap;
|
||||
}
|
||||
|
||||
@Autowired EmpMonthAttendanceMapper empMonthAttendanceMapper;
|
||||
|
||||
private String genScript(EmpSalaryBatchEntity batch) {
|
||||
@@ -544,13 +656,38 @@ public class EmpSalaryService extends BaseService<EmpSalaryEntity, EmpSalaryRepo
|
||||
return script;
|
||||
}
|
||||
|
||||
private Map<String, Object> buildCtx(EmployeeCalculateDto employee) {
|
||||
|
||||
if (employee == null) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
Map<String, Object> empMap = ReflectUtils.toMap(employee);
|
||||
|
||||
empMap.put("empId", employee.getId());
|
||||
empMap.put("empName", employee.getName());
|
||||
empMap.put("empCode", employee.getCode());
|
||||
|
||||
Try.run(() -> empMap.put("departmentId", employee.getDepartment().getId()));
|
||||
Try.run(() -> empMap.put("departmentCode", employee.getDepartment().getCode()));
|
||||
Try.run(() -> empMap.put("departmentName", employee.getDepartment().getName()));
|
||||
Try.run(() -> empMap.put("jobPostId", employee.getJobPost().getId()));
|
||||
Try.run(() -> empMap.put("jobPostCode", employee.getJobPost().getCode()));
|
||||
Try.run(() -> empMap.put("jobPostName", employee.getJobPost().getName()));
|
||||
|
||||
removeCommonField(empMap);
|
||||
return empMap;
|
||||
}
|
||||
|
||||
private @NotNull Map<String, List<EmpMonthAttendanceEntity>> queryAttendanceMap(
|
||||
EmpSalaryBatchEntity batch, List<EmployeeDto> employees) {
|
||||
EmpSalaryBatchEntity batch, List<EmployeeCalculateDto> employees) {
|
||||
List<EmpMonthAttendanceEntity> attendances =
|
||||
em.createQuery(
|
||||
"select a from EmpMonthAttendanceEntity a where a.employee.id in :empIds and a.yearMonth = :yearMonth",
|
||||
EmpMonthAttendanceEntity.class)
|
||||
.setParameter("empIds", employees.stream().map(EmployeeDto::getId).toList())
|
||||
.setParameter(
|
||||
"empIds",
|
||||
employees.stream().map(EmployeeCalculateDto::getId).toList())
|
||||
.setParameter("yearMonth", batch.getBatchYearMonth())
|
||||
.getResultList();
|
||||
|
||||
@@ -559,29 +696,11 @@ public class EmpSalaryService extends BaseService<EmpSalaryEntity, EmpSalaryRepo
|
||||
return attMap;
|
||||
}
|
||||
|
||||
private void checkBatchStatus(EmpSalaryBatchEntity batch) {}
|
||||
private void checkBatchStatus(EmpSalaryBatchEntity batch) {
|
||||
|
||||
private void checkDuplicate(EmpSalaryBatchEntity batch, List<EmployeeDto> employees) {
|
||||
List<EmpSalaryEntity> exists =
|
||||
em.createQuery(
|
||||
"select s from EmpSalaryEntity s where s.batch.id = :batchId and s.employee.id in :empIds",
|
||||
EmpSalaryEntity.class)
|
||||
.setParameter("batchId", batch.getId())
|
||||
.setParameter("empIds", employees.stream().map(EmployeeDto::getId).toList())
|
||||
.getResultList();
|
||||
|
||||
if (!exists.isEmpty()) {
|
||||
assertBatchEditable(batch);
|
||||
|
||||
for (EmpSalaryEntity exist : exists) {
|
||||
log.warn("批次 {} 员工 {} 工资数据已存在", batch.getId(), exist.getEmployee().getName());
|
||||
}
|
||||
|
||||
throw new BizException(
|
||||
"部分员工工资数据已存在: {}",
|
||||
exists.stream()
|
||||
.map(e -> e.getEmployee().getName())
|
||||
.collect(Collectors.joining(",")));
|
||||
}
|
||||
}
|
||||
|
||||
@Autowired EmpSalaryMapper empSalaryMapper;
|
||||
@@ -595,16 +714,52 @@ public class EmpSalaryService extends BaseService<EmpSalaryEntity, EmpSalaryRepo
|
||||
return groovyClassLoader;
|
||||
}
|
||||
|
||||
private void checkDuplicate(EmpSalaryBatchEntity batch, List<EmployeeCalculateDto> employees) {
|
||||
List<EmpSalaryEntity> exists =
|
||||
em.createQuery(
|
||||
"select s from EmpSalaryEntity s where s.batch.id = :batchId and s.employee.id in :empIds",
|
||||
EmpSalaryEntity.class)
|
||||
.setParameter("batchId", batch.getId())
|
||||
.setParameter(
|
||||
"empIds",
|
||||
employees.stream().map(EmployeeCalculateDto::getId).toList())
|
||||
.getResultList();
|
||||
|
||||
if (!exists.isEmpty()) {
|
||||
|
||||
for (EmpSalaryEntity exist : exists) {
|
||||
log.warn("批次 {} 员工 {} 工资数据已存在", batch.getName(), exist.getEmployee().getName());
|
||||
}
|
||||
|
||||
throw new BizException(
|
||||
"部分员工工资数据已存在: {}",
|
||||
exists.stream()
|
||||
.map(e -> e.getEmployee().getName())
|
||||
.collect(Collectors.joining(",")));
|
||||
}
|
||||
}
|
||||
|
||||
private List<EmpSalaryEntity> queryHisSalary(List<String> employeesIds) {
|
||||
|
||||
return new ArrayList<>();
|
||||
// return this.em
|
||||
// .createQuery(
|
||||
// "select s from EmpSalaryEntity s where s.employee.id in :empIds
|
||||
// and rank() over (partition by s.employee.id order by s.createTime desc ) = 1",
|
||||
// EmpSalaryEntity.class)
|
||||
// .setParameter("empIds", employeesIds)
|
||||
// .getResultList();
|
||||
em.createNativeQuery(
|
||||
"""
|
||||
with tmp1 as (
|
||||
|
||||
select s.id, rank() over (partition by employee_id order by batch_year_month desc ) rk from t_emp_salary s where s.employee_id in :ids
|
||||
)
|
||||
|
||||
select id from tmp1 where rk = 1
|
||||
|
||||
""",
|
||||
String.class)
|
||||
.setParameter("ids", employeesIds)
|
||||
.getResultList();
|
||||
|
||||
return em.createQuery(
|
||||
"select s from EmpSalaryEntity s where s.employee.id in :ids",
|
||||
EmpSalaryEntity.class)
|
||||
.setParameter("ids", employeesIds)
|
||||
.getResultList();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -659,11 +814,7 @@ public class EmpSalaryService extends BaseService<EmpSalaryEntity, EmpSalaryRepo
|
||||
|
||||
// 移除通用字段
|
||||
|
||||
ReflectUtils.getAllFieldsList(OrgCommonEntity.class)
|
||||
.forEach(
|
||||
f -> {
|
||||
ctx.remove(f.getName());
|
||||
});
|
||||
removeCommonField(ctx);
|
||||
|
||||
return ctx;
|
||||
}
|
||||
@@ -674,15 +825,4 @@ public class EmpSalaryService extends BaseService<EmpSalaryEntity, EmpSalaryRepo
|
||||
|
||||
log.info("删除工资数据 {} 条", cnt);
|
||||
}
|
||||
|
||||
public void sendToAudit(IdRequest request) {
|
||||
|
||||
EmpSalaryBatchEntity batch = this.batchService.get(request.getId());
|
||||
|
||||
if (!StringUtils.equalsAny(batch.getBatchStatus(), "1")) {
|
||||
throw new BizException("批次状态不正确");
|
||||
}
|
||||
|
||||
batch.setBatchStatus("2");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package cn.lihongjie.coal.empSalaryItem.repository;
|
||||
import cn.lihongjie.coal.base.dao.BaseRepository;
|
||||
import cn.lihongjie.coal.empSalaryItem.entity.EmpSalaryItemEntity;
|
||||
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
@@ -11,4 +13,17 @@ public interface EmpSalaryItemRepository extends BaseRepository<EmpSalaryItemEnt
|
||||
|
||||
long countByNameContainsAndOrganizationIdAndIdNot(
|
||||
String name, String organizationId, String id);
|
||||
|
||||
@Query(
|
||||
value =
|
||||
"""
|
||||
select md5(string_agg(t.f, ',')) from (
|
||||
select f from (
|
||||
select code ,formula f from t_emp_salary_item where organization_id = :organizationId
|
||||
union all
|
||||
select code, item_expression f from t_emp_salary_sys_item
|
||||
) as t order by code asc) as t
|
||||
""",
|
||||
nativeQuery = true)
|
||||
String computeCacheKey(@Param("organizationId") String organizationId);
|
||||
}
|
||||
|
||||
@@ -4,27 +4,27 @@ 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.empSalary.entity.EmpSalaryEntity;
|
||||
import cn.lihongjie.coal.empSalaryBatch.entity.EmpSalaryBatchEntity;
|
||||
import cn.lihongjie.coal.common.GroovyScriptManager;
|
||||
import cn.lihongjie.coal.empSalaryItem.dto.CreateEmpSalaryItemDto;
|
||||
import cn.lihongjie.coal.empSalaryItem.dto.EmpSalaryItemDto;
|
||||
import cn.lihongjie.coal.empSalaryItem.dto.UpdateEmpSalaryItemDto;
|
||||
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.empSalaryStandard.dto.EmpSalaryStandardResult;
|
||||
import cn.lihongjie.coal.empSalaryStandard.service.EmpSalaryStandardService;
|
||||
import cn.lihongjie.coal.empSalarySysItem.entity.EmpSalarySysItemEntity;
|
||||
import cn.lihongjie.coal.empSalarySysItem.service.EmpSalarySysItemService;
|
||||
import cn.lihongjie.coal.exception.BizException;
|
||||
|
||||
import groovy.lang.GroovyClassLoader;
|
||||
import groovy.lang.Closure;
|
||||
import groovy.lang.Script;
|
||||
|
||||
import io.vavr.Tuple;
|
||||
import io.vavr.Tuple2;
|
||||
import io.vavr.collection.Stream;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.annotation.PreDestroy;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.PersistenceContext;
|
||||
import jakarta.persistence.criteria.CriteriaBuilder;
|
||||
@@ -32,7 +32,6 @@ 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;
|
||||
@@ -43,6 +42,7 @@ import org.codehaus.groovy.ast.CodeVisitorSupport;
|
||||
import org.codehaus.groovy.ast.builder.AstStringCompiler;
|
||||
import org.codehaus.groovy.ast.expr.VariableExpression;
|
||||
import org.codehaus.groovy.control.CompilerConfiguration;
|
||||
import org.codehaus.groovy.control.customizers.SecureASTCustomizer;
|
||||
import org.jgrapht.alg.cycle.CycleDetector;
|
||||
import org.jgrapht.graph.DefaultDirectedGraph;
|
||||
import org.jgrapht.graph.DefaultEdge;
|
||||
@@ -56,8 +56,10 @@ import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@@ -74,6 +76,7 @@ public class EmpSalaryItemService
|
||||
@Autowired private EmpSalaryStandardService empSalaryStandardService;
|
||||
|
||||
@PersistenceContext EntityManager em;
|
||||
private GroovyScriptManager groovyScriptManager;
|
||||
|
||||
public List<EmpSalaryItemEntity> findAllBy(
|
||||
String organizationId, List<String> dependOn, List<String> dependOnSysItem) {
|
||||
@@ -176,6 +179,47 @@ public class EmpSalaryItemService
|
||||
|
||||
@Autowired EmpSalarySysItemService empSalarySysItemService;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
|
||||
groovyScriptManager =
|
||||
new GroovyScriptManager(
|
||||
"salaryItemScript",
|
||||
new Consumer<CompilerConfiguration>() {
|
||||
@Override
|
||||
public void accept(CompilerConfiguration compilerConfiguration) {
|
||||
|
||||
compilerConfiguration.addCompilationCustomizers(
|
||||
new SecureASTCustomizer() {
|
||||
{
|
||||
setAllowedImports(List.of("java.lang.Math"));
|
||||
|
||||
setAllowedReceiversClasses(
|
||||
Arrays.asList(
|
||||
Object[].class,
|
||||
Arrays.class,
|
||||
java.math.RoundingMode.class,
|
||||
Closure.class,
|
||||
Map.class,
|
||||
Object.class,
|
||||
Math.class,
|
||||
Integer.class,
|
||||
Float.class,
|
||||
Double.class,
|
||||
Long.class,
|
||||
BigDecimal.class));
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void destroy() {
|
||||
groovyScriptManager.destroy();
|
||||
}
|
||||
|
||||
private void updateDep(List<EmpSalaryItemEntity> enabled, List<EmpSalaryItemEntity> allItems) {
|
||||
|
||||
List<EmpSalarySysItemEntity> sysItems =
|
||||
@@ -593,82 +637,6 @@ public class EmpSalaryItemService
|
||||
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);
|
||||
}
|
||||
|
||||
public String genScript(String organizationId) {
|
||||
|
||||
List<EmpSalaryItemEntity> items =
|
||||
@@ -759,8 +727,26 @@ public class EmpSalaryItemService
|
||||
|
||||
script.append("// 工资项目开始\n");
|
||||
|
||||
StringBuilder roundScript = new StringBuilder();
|
||||
roundScript.append("// 保留小数位处理\n");
|
||||
|
||||
for (EmpSalaryItemEntity item : items) {
|
||||
|
||||
|
||||
|
||||
roundScript
|
||||
.append("salary.")
|
||||
.append(item.getCode())
|
||||
.append(" = ")
|
||||
.append(" ROUND( ")
|
||||
.append(" salary.")
|
||||
.append(item.getCode())
|
||||
.append(" , ")
|
||||
.append(ObjectUtils.defaultIfNull(item.getDecimalPlaces(), 2))
|
||||
.append(" , ")
|
||||
.append(StringUtils.defaultIfBlank(item.getDecimalPlacesHandler(), "0"))
|
||||
.append(");\n");
|
||||
|
||||
if (StringUtils.equalsAnyIgnoreCase(item.getInputType(), "1")) {
|
||||
|
||||
script.append("/** \n")
|
||||
@@ -793,6 +779,9 @@ public class EmpSalaryItemService
|
||||
.append(" ?: 0")
|
||||
.append(";\n");
|
||||
|
||||
|
||||
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -827,56 +816,19 @@ public class EmpSalaryItemService
|
||||
.append(" = (")
|
||||
.append(item.getFormula())
|
||||
.append(") ?: 0 ;\n");
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
script.append("// 工资项目结束\n");
|
||||
|
||||
roundScript.append("// 保留小数位处理\n");
|
||||
|
||||
script.append(roundScript);
|
||||
return script.toString();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public List<EmpSalaryItemEntity> getItems(String organizationId) {
|
||||
|
||||
return this.findAll(
|
||||
@@ -892,4 +844,20 @@ public class EmpSalaryItemService
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public String computeCacheKey(String organizationId) {
|
||||
|
||||
return this.repository.computeCacheKey(organizationId);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public Script newScriptInstance(String organizationId){
|
||||
|
||||
|
||||
|
||||
return groovyScriptManager.newInstance(computeCacheKey(organizationId), () -> genScript(organizationId));
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
package cn.lihongjie.coal.employee.dto;
|
||||
|
||||
import cn.lihongjie.coal.base.dto.OrgCommonDto;
|
||||
import cn.lihongjie.coal.common.DictCode;
|
||||
import cn.lihongjie.coal.department.dto.DepartmentDto;
|
||||
import cn.lihongjie.coal.jobPost.dto.JobPostDto;
|
||||
import cn.lihongjie.coal.pojoProcessor.DictTranslate;
|
||||
|
||||
import jakarta.persistence.ManyToOne;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import org.hibernate.annotations.Comment;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
@Data
|
||||
public class EmployeeCalculateDto extends OrgCommonDto {
|
||||
@Comment("性别")
|
||||
private String sex;
|
||||
|
||||
@Comment("性别-名称")
|
||||
@DictTranslate(dictKey = DictCode.SEX)
|
||||
private String sexName;
|
||||
|
||||
@Comment("民族")
|
||||
private String nation;
|
||||
|
||||
@Comment("民族-名称")
|
||||
@DictTranslate(dictKey = DictCode.NATION)
|
||||
private String nationName;
|
||||
|
||||
@Comment("婚姻状况")
|
||||
private String marriage;
|
||||
|
||||
@Comment("婚姻状况-名称")
|
||||
@DictTranslate(dictKey = DictCode.MARRIAGE)
|
||||
private String marriageName;
|
||||
|
||||
@Comment("入职时间")
|
||||
private LocalDate entryDate;
|
||||
|
||||
@Comment("身份证号")
|
||||
private String idCard;
|
||||
|
||||
@Comment("学历")
|
||||
private String education;
|
||||
|
||||
@Comment("学历-名称")
|
||||
@DictTranslate(dictKey = DictCode.EDUCATION)
|
||||
private String educationName;
|
||||
|
||||
@Comment("毕业学校")
|
||||
private String school;
|
||||
|
||||
@Comment("籍贯")
|
||||
private String nativePlace;
|
||||
|
||||
@Comment("住址")
|
||||
private String address;
|
||||
|
||||
@Comment("手机号")
|
||||
private String phone;
|
||||
|
||||
@Comment("部门")
|
||||
@ManyToOne
|
||||
private DepartmentDto department;
|
||||
|
||||
@Comment("岗位")
|
||||
@ManyToOne
|
||||
private JobPostDto jobPost;
|
||||
|
||||
@Comment("银行编码")
|
||||
private String bank;
|
||||
|
||||
@DictTranslate(dictKey = DictCode.BANK)
|
||||
private String bankName;
|
||||
|
||||
@Comment("银行卡号")
|
||||
private String bankCardNumber;
|
||||
|
||||
@Comment("收款人姓名")
|
||||
private String bankCardName;
|
||||
|
||||
@Comment("离职时间")
|
||||
private LocalDate resignDate;
|
||||
|
||||
@Comment("离职原因")
|
||||
private String resignReason;
|
||||
|
||||
@Comment("员工状态")
|
||||
private String empStatus;
|
||||
|
||||
@DictTranslate(dictKey = DictCode.EMP_STATUS)
|
||||
private String empStatusName;
|
||||
|
||||
@Comment("养老保险基数")
|
||||
private Double insurance1Base;
|
||||
|
||||
@Comment("养老保险比例")
|
||||
private Double insurance1Percent;
|
||||
|
||||
@Comment("医疗保险基数")
|
||||
private Double insurance2Base;
|
||||
|
||||
@Comment("医疗保险比例")
|
||||
private Double insurance2Percent;
|
||||
|
||||
@Comment("失业保险基数")
|
||||
private Double insurance3Base;
|
||||
|
||||
@Comment("失业保险比例")
|
||||
private Double insurance3Percent;
|
||||
|
||||
@Comment("工伤保险基数")
|
||||
private Double insurance4Base;
|
||||
|
||||
@Comment("工伤保险比例")
|
||||
private Double insurance4Percent;
|
||||
|
||||
@Comment("生育保险基数")
|
||||
private Double insurance5Base;
|
||||
|
||||
@Comment("生育保险比例")
|
||||
private Double insurance5Percent;
|
||||
|
||||
@Comment("住房公积金基数")
|
||||
private Double insurance6Base;
|
||||
|
||||
@Comment("住房公积金比例")
|
||||
private Double insurance6Percent;
|
||||
|
||||
@Comment("工龄")
|
||||
private Double workAge;
|
||||
|
||||
@Comment("年龄")
|
||||
private Double age;
|
||||
|
||||
@Comment("出生日期")
|
||||
private LocalDate birthday;
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package cn.lihongjie.coal.employee.mapper;
|
||||
|
||||
import cn.lihongjie.coal.base.mapper.BaseMapper;
|
||||
import cn.lihongjie.coal.employee.dto.CreateEmployeeDto;
|
||||
import cn.lihongjie.coal.employee.dto.EmployeeCalculateDto;
|
||||
import cn.lihongjie.coal.employee.dto.EmployeeDto;
|
||||
import cn.lihongjie.coal.employee.dto.UpdateEmployeeDto;
|
||||
import cn.lihongjie.coal.employee.entity.EmployeeEntity;
|
||||
@@ -17,4 +18,6 @@ public interface EmployeeMapper
|
||||
extends BaseMapper<EmployeeEntity, EmployeeDto, CreateEmployeeDto, UpdateEmployeeDto> {
|
||||
|
||||
|
||||
|
||||
EmployeeCalculateDto toCalculateDto(EmployeeEntity list);
|
||||
}
|
||||
|
||||
@@ -10,10 +10,7 @@ import cn.lihongjie.coal.department.dto.DepartmentDto;
|
||||
import cn.lihongjie.coal.department.entity.DepartmentEntity;
|
||||
import cn.lihongjie.coal.department.service.DepartmentService;
|
||||
import cn.lihongjie.coal.dictionary.service.DictionaryService;
|
||||
import cn.lihongjie.coal.employee.dto.CreateEmployeeDto;
|
||||
import cn.lihongjie.coal.employee.dto.EmployeeDto;
|
||||
import cn.lihongjie.coal.employee.dto.ImportEmpFromExcelRequest;
|
||||
import cn.lihongjie.coal.employee.dto.UpdateEmployeeDto;
|
||||
import cn.lihongjie.coal.employee.dto.*;
|
||||
import cn.lihongjie.coal.employee.entity.EmployeeEntity;
|
||||
import cn.lihongjie.coal.employee.mapper.EmployeeMapper;
|
||||
import cn.lihongjie.coal.employee.repository.EmployeeRepository;
|
||||
@@ -267,6 +264,25 @@ public class EmployeeService extends BaseService<EmployeeEntity, EmployeeReposit
|
||||
|
||||
return collect;
|
||||
}
|
||||
public List<EmployeeCalculateDto> getCalculateDtoByIds(List<String> employeesIds) {
|
||||
|
||||
|
||||
List<EmployeeEntity> entity = em.createQuery("select e from EmployeeEntity e left join fetch e.jobPost left join fetch e.department where e.id in :ids", EmployeeEntity.class)
|
||||
.setParameter("ids", employeesIds)
|
||||
.getResultList();
|
||||
|
||||
|
||||
List<EmployeeCalculateDto> collect =
|
||||
entity.stream()
|
||||
.map(mapper::toCalculateDto)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
pojoProcessor.process(collect, false);
|
||||
|
||||
return collect;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void refreshAge(String organizationId) {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user