package com.engine.salary.service.impl; import cn.hutool.core.util.StrUtil; import com.engine.common.util.ServiceUtil; import com.engine.core.impl.Service; import com.engine.kq.service.KQGroupService; import com.engine.kq.service.impl.KQGroupServiceImpl; import com.engine.salary.cache.SalaryCacheKey; import com.engine.salary.constant.SalaryDefaultTenantConstant; import com.engine.salary.constant.SalaryFormulaFieldConstant; import com.engine.salary.entity.datacollection.AddUpDeduction; import com.engine.salary.entity.datacollection.AddUpSituation; import com.engine.salary.entity.datacollection.DataCollectionEmployee; import com.engine.salary.entity.datacollection.dto.AttendQuoteDataDTO; import com.engine.salary.entity.datacollection.po.OtherDeductionPO; import com.engine.salary.entity.salaryacct.bo.*; import com.engine.salary.entity.salaryacct.po.SalaryAcctEmployeePO; import com.engine.salary.entity.salaryacct.po.SalaryAcctRecordPO; import com.engine.salary.entity.salaryacct.po.SalaryAcctResultPO; import com.engine.salary.entity.salaryacct.po.SalaryAcctResultTempPO; import com.engine.salary.entity.salaryarchive.dto.SalaryArchiveDataDTO; import com.engine.salary.entity.salaryformula.ExpressFormula; import com.engine.salary.entity.salaryformula.po.FormulaVar; import com.engine.salary.entity.salaryitem.po.SalaryItemPO; import com.engine.salary.entity.salarysob.dto.SalarySobCycleDTO; import com.engine.salary.entity.salarysob.po.SalarySobBackItemPO; import com.engine.salary.entity.salarysob.po.SalarySobItemPO; import com.engine.salary.enums.salaryformula.SalaryFormulaReferenceEnum; import com.engine.salary.formlua.entity.standard.ExcelResult; import com.engine.salary.service.*; import com.engine.salary.sys.enums.TaxDeclarationFunctionEnum; import com.engine.salary.util.SalaryDateUtil; import com.engine.salary.util.SalaryEntityUtil; import com.google.common.collect.Lists; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.math.NumberUtils; import org.springframework.util.StopWatch; import weaver.general.BaseBean; import weaver.general.Util; import weaver.hrm.User; import java.time.Month; import java.util.*; import java.util.stream.Collectors; /** * 薪资核算-核算 *

Copyright: Copyright (c) 2022

*

Company: 泛微软件

* * @author qiantao * @version 1.0 **/ @Slf4j public class SalaryAcctCalculateServiceImpl extends Service implements SalaryAcctCalculateService { BaseBean bb = new BaseBean(); //公式运行时间超时提醒阈值 private final String formulaRunOvertimeThreshold = bb.getPropValue("hrmSalaryCustom", "formulaRunOvertimeThreshold"); private SalaryAcctResultService getSalaryAcctResultService(User user) { return ServiceUtil.getService(SalaryAcctResultServiceImpl.class, user); } private SalaryEmployeeService getSalaryEmployeeService(User user) { return ServiceUtil.getService(SalaryEmployeeServiceImpl.class, user); } private SalaryArchiveService getSalaryArchiveService(User user) { return ServiceUtil.getService(SalaryArchiveServiceImpl.class, user); } private AddUpSituationService getAddUpSituationService(User user) { return ServiceUtil.getService(AddUpSituationServiceImpl.class, user); } private AddUpDeductionService getAddUpDeductionService(User user) { return ServiceUtil.getService(AddUpDeductionServiceImpl.class, user); } private OtherDeductionService getOtherDeductionService(User user) { return ServiceUtil.getService(OtherDeductionServiceImpl.class, user); } private SIAccountService getSIAccountService(User user) { return ServiceUtil.getService(SIAccountServiceImpl.class, user); } private AttendQuoteDataService getAttendQuoteDataService(User user) { return ServiceUtil.getService(AttendQuoteDataServiceImpl.class, user); } private FormulaRunService getFormulaRunService(User user) { return ServiceUtil.getService(FormulaRunServiceImpl.class, user); } private SalaryAcctResultTempService getSalaryAcctResultTempService(User user) { return ServiceUtil.getService(SalaryAcctResultTempServiceImpl.class, user); } private ProgressService getSalaryAcctProgressService(User user) { return (ProgressService) ServiceUtil.getService(ProgressServiceImpl.class, user); } private SalaryAcctEmployeeService getSalaryAcctEmployeeService(User user) { return ServiceUtil.getService(SalaryAcctEmployeeServiceImpl.class, user); } private VariableArchiveService getVariableArchiveService(User user) { return ServiceUtil.getService(VariableArchiveServiceImpl.class, user); } private KQGroupService getKQGroupService() { User user = new User(); user.setUid(1); user.setLoginid("sysadmin"); return (KQGroupService) ServiceUtil.getService(KQGroupServiceImpl.class, user); } @Override public void calculate(SalaryAcctCalculateBO salaryAcctCalculateBO, DataCollectionEmployee simpleEmployee, List salarySobBackItems) { log.info("开始核算V3 {}", salaryAcctCalculateBO); StopWatch sw = new StopWatch("核算耗时明细,id:" + salaryAcctCalculateBO.getSalaryAcctRecordPO().getId() + ""); Date now = new Date(); try { // 数据库字段加密用 // 1、查询人员信息 sw.start("查询人员信息"); List employeeIds = SalaryEntityUtil.properties(salaryAcctCalculateBO.getSalaryAcctEmployeePOS(), SalaryAcctEmployeePO::getEmployeeId, Collectors.toList()); List simpleEmployees = getSalaryEmployeeService(user).getEmployeeByIdsAll(employeeIds); SalarySobCycleDTO salarySobCycleDTO = salaryAcctCalculateBO.getSalarySobCycleDTO(); List taxAgentIds = salaryAcctCalculateBO.getSalarySobPO().getTaxAgentIds(); sw.stop(); // 2、查询薪资档案的数据 sw.start("查询薪资档案的数据"); List salaryArchiveData = getSalaryArchiveService(user).getSalaryArchiveData(salarySobCycleDTO.getSalaryCycle(), employeeIds, taxAgentIds); sw.stop(); // 3、查询往期累计情况(查询的是上个税款所属期的的累计情况) sw.start("查询往期累计情况"); List addUpSituationPOS; if (salarySobCycleDTO.getTaxCycle().getMonth() == Month.JANUARY) { // 3.1、如果当前税款所属期是本年度第一个税款所属期,就不需要查询往期累计情况 addUpSituationPOS = Collections.emptyList(); } else { addUpSituationPOS = getAddUpSituationService(user).getAddUpSituationList(salarySobCycleDTO.getTaxCycle().plusMonths(-1), employeeIds); } sw.stop(); // 4、查询累计专项附加扣除 sw.start("查询累计专项附加扣除"); List addUpDeductionPOS = getAddUpDeductionService(user).getAddUpDeductionList(salarySobCycleDTO.getTaxCycle(), employeeIds, taxAgentIds); sw.stop(); // 5、查询其他免税扣除 sw.start("查询其他免税扣除"); List otherDeductionPOS = getOtherDeductionService(user).getOtherDeductionList(salarySobCycleDTO.getTaxCycle(), employeeIds, taxAgentIds); sw.stop(); //6、查询社保福利 sw.start("查询社保福利"); List> welfareData = new ArrayList<>(); taxAgentIds.forEach(taxAgentId -> { welfareData.addAll(getSIAccountService(user).welfareData(salarySobCycleDTO.getSocialSecurityCycle().toString(), employeeIds, taxAgentId)); }); sw.stop(); // 7、查询考勤数据 sw.start("查询考勤数据"); List attendQuoteDataDTOS = getAttendQuoteDataService(user).getAttendQuoteData(salarySobCycleDTO.getSalaryMonth(), salarySobCycleDTO.getSalarySobId(), employeeIds); sw.stop(); // 8、查询薪资核算人员的薪资核算结果 sw.start("查询薪资核算人员的薪资核算结果"); List salaryAcctEmployeeIds = SalaryEntityUtil.properties(salaryAcctCalculateBO.getSalaryAcctEmployeePOS(), SalaryAcctEmployeePO::getId, Collectors.toList()); List salaryAcctResultPOS = getSalaryAcctResultService(user).listBySalaryAcctEmployeeIds(salaryAcctEmployeeIds); Map empItemValueMap = SalaryEntityUtil.convert2Map(salaryAcctResultPOS, p -> p.getSalaryAcctEmpId() + "_" + p.getSalaryItemId(), SalaryAcctResultPO::getResultValue); sw.stop(); // 查询浮动薪资 sw.start("查询浮动薪资"); List> variableArchiveList = getVariableArchiveService(user).listBySalaryMonthAndEmployeeIds(salarySobCycleDTO.getSalaryMonth(), employeeIds, taxAgentIds); sw.stop(); // 薪资回算时回算前的核算结果 (没有回算项) sw.start("查询薪资回算时回算前的核算结果"); Map> collect = salaryAcctResultPOS.stream().collect(Collectors.groupingBy(k -> k.getEmployeeId() + "-" + k.getTaxAgentId() + "-" + k.getSalaryItemId())); Map salaryAcctResultPOMap = new HashMap<>(); for (Map.Entry> et : collect.entrySet()) { salaryAcctResultPOMap.put(et.getKey(), et.getValue().get(0).getOriginResultValue()); } sw.stop(); // 9、查询相同税款所属期内涉及合并计税的其他薪资核算结果 sw.start("查询相同税款所属期内涉及合并计税的其他薪资核算结果"); Set otherSalaryAcctRecordIds = SalaryEntityUtil.properties(salaryAcctCalculateBO.getOtherSalaryAcctRecordPOS(), SalaryAcctRecordPO::getId); List otherSalaryAcctResultPOS = getSalaryAcctResultService(user).listBySalaryAcctRecordIdsAndEmployeeIds(otherSalaryAcctRecordIds, employeeIds); Map> otherSalaryAcctResultPOMap = SalaryEntityUtil.group2Map(otherSalaryAcctResultPOS, e -> e.getEmployeeId() + "_" + e.getTaxAgentId()); sw.stop(); // 9.1、查询相同税款所属期内设计合并计税的其他薪资核算人员 sw.start("查询相同税款所属期内设计合并计税的其他薪资核算人员"); List otherSalaryAcctEmployeePOS = getSalaryAcctEmployeeService(user).listBySalaryAcctRecordIdsAndEmployeeIds(otherSalaryAcctRecordIds, employeeIds); Map> otherSalaryAcctEmployeePOMap = SalaryEntityUtil.group2Map(otherSalaryAcctEmployeePOS, salaryAcctEmployeePO -> salaryAcctEmployeePO.getEmployeeId() + "_" + salaryAcctEmployeePO.getTaxAgentId()); sw.stop(); // 查询上个薪资所属月核算人员薪资数据 sw.start("查询上个薪资所属月核算人员薪资数据"); List lastMonthResultPOS = getSalaryAcctResultService(user).listBySobSalaryMonth(SalaryDateUtil.toDate(salarySobCycleDTO.getSalaryMonth().minusMonths(1), 1), salaryAcctCalculateBO.getSalarySobPO().getId(), employeeIds); sw.stop(); // 10、转换成公式编辑器中的变量 sw.start("转换成公式编辑器中的变量"); KQGroupService kqGroupService = getKQGroupService(); CalculateFormulaVarBO calculateFormulaVarBO = new CalculateFormulaVarBO(simpleEmployees, salaryArchiveData, addUpSituationPOS, addUpDeductionPOS, otherDeductionPOS, welfareData, attendQuoteDataDTOS, salaryAcctResultPOS, variableArchiveList,lastMonthResultPOS); Map> formulaVarMap = calculateFormulaVarBO.convert2FormulaVar(salaryAcctCalculateBO, kqGroupService); sw.stop(); sw.start("数据结构准备"); // 本次薪资核算所用的薪资账套下的薪资项目 Map salaryItemIdKeySalarySobItemPOMap = SalaryEntityUtil.convert2Map(salaryAcctCalculateBO.getSalarySobItemPOS(), SalarySobItemPO::getSalaryItemId); // 本次薪资核算所用的公式 Map expressFormulaMap = SalaryEntityUtil.convert2Map(salaryAcctCalculateBO.getExpressFormulas(), ExpressFormula::getId); // 系统内的薪资项目 Map salaryItemMap = SalaryEntityUtil.convert2Map(salaryAcctCalculateBO.getSalaryItemPOS(), SalaryItemPO::getId); // 获取薪资回算的薪资项目ID Set salarySobBackItemIds = SalaryEntityUtil.properties(salarySobBackItems, SalarySobBackItemPO::getSalaryItemId); Map salarySobBackItemMap = SalaryEntityUtil.convert2Map(salarySobBackItems, SalarySobBackItemPO::getSalaryItemId); List salaryAcctResultTempPOS = Lists.newArrayList(); sw.stop(); // 开始核算 sw.start("核算耗时"); StringBuffer noticeMsg = new StringBuffer(); for (SalaryAcctEmployeePO salaryAcctEmployeePO : salaryAcctCalculateBO.getSalaryAcctEmployeePOS()) { Long salaryAcctEmployeePOId = salaryAcctEmployeePO.getId(); List lockItems = salaryAcctEmployeePO.getLockItems(); //1 获取当前薪资核算人员的公式中的变量的值 List formulaVarValues = formulaVarMap.get(salaryAcctEmployeePO.getEmployeeId() + "_" + salaryAcctEmployeePO.getTaxAgentId()); //2 人员信息 List empInfo = formulaVarMap.get(salaryAcctEmployeePO.getEmployeeId() + ""); formulaVarValues.addAll(empInfo); Map formulaVarValueMap = SalaryEntityUtil.convert2Map(formulaVarValues, CalculateFormulaVarBO.FormulaVarValue::getFieldId, CalculateFormulaVarBO.FormulaVarValue::getFieldValue); // 按照计算好的优先级计算薪资项目的值 for (List salaryItemIds : salaryAcctCalculateBO.getSalaryItemIdWithPriorityList()) { // 同一运算优先级下的薪资项目逐个独立运算 for (Long salaryItemId : salaryItemIds) { String resultValue; SalaryItemPO salaryItemPO = salaryItemMap.get(salaryItemId); ExpressFormula expressFormula; String defaultValue; if (salarySobBackItemMap.containsKey(salaryItemId)) { // 如果薪资账套的回算项目中重新定义了回算项目公式,则使用薪资账套下的公式 SalarySobBackItemPO salarySobBackItemPO = salarySobBackItemMap.get(salaryItemId); expressFormula = expressFormulaMap.get(salarySobBackItemPO.getFormulaId()); defaultValue = salarySobBackItemPO.getDefaultValue(); } else if (salaryItemIdKeySalarySobItemPOMap.containsKey(salaryItemId)) { // 如果薪资账套下重新定义了薪资项目的公式,则使用薪资账套下的公式,否则使用薪资项目本身的公式 SalarySobItemPO salarySobItemPO = salaryItemIdKeySalarySobItemPOMap.get(salaryItemId); expressFormula = expressFormulaMap.get(salarySobItemPO.getFormulaId()); defaultValue = salarySobItemPO.getDefaultValue(); } else { expressFormula = expressFormulaMap.get(salaryItemPO.getFormulaId()); defaultValue = salaryItemPO.getDefaultValue(); } if (Objects.nonNull(expressFormula)) { // 运行公式 ExcelResult result = runExpressFormula(expressFormula, formulaVarValueMap, simpleEmployee); resultValue = result.getData2String(); //公式异常信息 if (!result.isStatus()) { String username = empInfo.stream().filter(emp -> StringUtils.equals("employeeInfo_username", emp.getFieldId())).findFirst().map(CalculateFormulaVarBO.FormulaVarValue::getFieldValue).orElse(""); String errorMsg = String.format("%s的%s核算异常,原因:%s \r\n", username, salaryItemPO.getName(), result.getErrorMsg()); noticeMsg.append(errorMsg); } //提醒运行超时 if (StringUtils.isNotBlank(formulaRunOvertimeThreshold) && result.getRunTime() > Long.parseLong(formulaRunOvertimeThreshold)) { String username = empInfo.stream().filter(emp -> StringUtils.equals("employeeInfo_username", emp.getFieldId())).findFirst().map(CalculateFormulaVarBO.FormulaVarValue::getFieldValue).orElse(""); String errorMsg = String.format("%s的%s核算超时,耗时:%s毫秒 \r\n", username, salaryItemPO.getName(), result.getRunTime()); noticeMsg.append(errorMsg); } } else { // 处理取值类型为“输入/导入”的薪资项目 String key = SalaryFormulaReferenceEnum.SALARY_ITEM.getValue() + SalaryFormulaFieldConstant.FIELD_ID_SEPARATOR + salaryItemPO.getCode(); resultValue = formulaVarValueMap.getOrDefault(key, StringUtils.EMPTY); if (StrUtil.isBlank(resultValue)) { resultValue = Util.null2String(defaultValue); } } // 处理薪资档案 if (Objects.equals(salaryItemPO.getUseInEmployeeSalary(), NumberUtils.INTEGER_ONE)) { String key = SalaryFormulaReferenceEnum.SALARY_ARCHIVES.getValue() + SalaryFormulaFieldConstant.FIELD_ID_SEPARATOR + salaryItemPO.getCode(); resultValue = formulaVarValueMap.getOrDefault(key, StringUtils.EMPTY); } // 处理合并计税 resultValue = handleConsolidatedTax(resultValue, salaryItemPO, salaryAcctCalculateBO, otherSalaryAcctEmployeePOMap.get(salaryAcctEmployeePO.getEmployeeId() + "_" + salaryAcctEmployeePO.getTaxAgentId()), otherSalaryAcctResultPOMap.get(salaryAcctEmployeePO.getEmployeeId() + "_" + salaryAcctEmployeePO.getTaxAgentId())); // 处理小数点 resultValue = SalaryAcctFormulaBO.roundResultValue(resultValue, salaryItemPO, salarySobBackItems, salarySobBackItemMap, salaryItemIdKeySalarySobItemPOMap); //是否锁定 if (lockItems != null && lockItems.contains(salaryItemId)) { resultValue = empItemValueMap.getOrDefault(salaryAcctEmployeePOId + "_" + salaryItemId, StringUtils.EMPTY); } // 将已经计算过的薪资项目的值转换成公式变量的值添加到集合中 String key = SalaryFormulaReferenceEnum.SALARY_ITEM.getValue() + SalaryFormulaFieldConstant.FIELD_ID_SEPARATOR + salaryItemPO.getCode(); formulaVarValueMap.put(key, resultValue); // 值保存薪资账套下的薪资项目的核算结果 if (salaryItemIdKeySalarySobItemPOMap.containsKey(salaryItemId) || salarySobBackItemIds.contains(salaryItemId)) { // 转换成薪资核算结果po SalaryAcctResultTempPO salaryAcctResultTempPO = new SalaryAcctResultTempPO() .setSalaryAcctRecordId(salaryAcctEmployeePO.getSalaryAcctRecordId()) .setSalaryAcctEmpId(salaryAcctEmployeePOId) .setEmployeeId(salaryAcctEmployeePO.getEmployeeId()) .setTaxAgentId(salaryAcctEmployeePO.getTaxAgentId()) .setSalarySobId(salaryAcctEmployeePO.getSalarySobId()) .setSalaryItemId(salaryItemPO.getId()) .setResultValue(resultValue) .setOriginResultValue(salaryAcctResultPOMap.get(salaryAcctEmployeePO.getEmployeeId() + "-" + salaryAcctEmployeePO.getTaxAgentId() + "-" + salaryItemId) == null ? StringUtils.EMPTY : salaryAcctResultPOMap.get(salaryAcctEmployeePO.getEmployeeId() + "-" + salaryAcctEmployeePO.getTaxAgentId() + "-" + salaryItemId)) .setCalculateKey(salaryAcctCalculateBO.getCalculateKey()) .setCreator((long) user.getUID()) .setCreateTime(now) .setUpdateTime(now) .setTenantKey(SalaryDefaultTenantConstant.DEFAULT_TENANT_KEY) .setDeleteType(0); salaryAcctResultTempPOS.add(salaryAcctResultTempPO); } } } } sw.stop(); // 保存新的薪资核算结果(临时存储) sw.start("保存新的薪资核算结果(临时存储)"); getSalaryAcctResultTempService(user).batchSave(salaryAcctResultTempPOS); sw.stop(); // 更新薪资核算进度 sw.start("更新薪资核算进度"); getSalaryAcctProgressService(user).getAndAddCalculatedQty(SalaryCacheKey.ACCT_PROGRESS + salaryAcctCalculateBO.getSalaryAcctRecordPO().getId(), salaryAcctCalculateBO.getSalaryAcctEmployeePOS().size(), noticeMsg.toString() ); sw.stop(); log.info(sw.prettyPrint()); // 记录子线程执行结果 salaryAcctCalculateBO.getResults().add(new SalaryAcctCalculateBO.Result(true, StringUtils.EMPTY)); } catch (Exception e) { log.error("薪资核算失败:{}", e.getMessage(), e); salaryAcctCalculateBO.getResults().add(new SalaryAcctCalculateBO.Result(false, e.getMessage())); } finally { // 数据库字段加密用 // DSTenantKeyThreadVar.tenantKey.remove(); // 子线程执行完毕 salaryAcctCalculateBO.getChildMonitor().countDown(); } } /** * 运行公式 * * @param expressFormula * @param formulaVarValueMap * @return */ private ExcelResult runExpressFormula(ExpressFormula expressFormula, Map formulaVarValueMap, DataCollectionEmployee simpleEmployee) { // 给公式中的变量填入值 ExcelResult result = new ExcelResult(); try { List formulaVars = ExpressFormulaBO.buildFormulaVar4Accounting(expressFormula, formulaVarValueMap); result = getFormulaRunService(user).run(expressFormula, formulaVars, simpleEmployee); } catch (Exception e) { log.error("express execute fail ", e); result.setStatus(false); result.setErrorMsg(e.getMessage()); } //核算出错,给个默认值 if (!result.isStatus() || result.getData() == null) { if ("number".equals(expressFormula.getReturnType())) { result.setData("0"); } else { result.setData(StringUtils.EMPTY); } } return result; } /** * 处理合并计税 * * @return */ private String handleConsolidatedTax(String resultValue, SalaryItemPO salaryItemPO, SalaryAcctCalculateBO salaryAcctCalculateBO, List otherSalaryAcctEmployeePOS, List otherSalaryAcctResultPOS) { // 如果相同税款所属期内没有其他薪资核算人员,就不存在合并计税 if (salaryAcctCalculateBO.getTaxDeclarationFunction() == TaxDeclarationFunctionEnum.CLOSURE || CollectionUtils.isEmpty(otherSalaryAcctEmployeePOS) || CollectionUtils.isEmpty(otherSalaryAcctResultPOS)) { return resultValue; } // 相同税款所属期内其他薪资核算记录 Set otherSalaryAcctRecordIds = SalaryEntityUtil.properties(otherSalaryAcctEmployeePOS, SalaryAcctEmployeePO::getSalaryAcctRecordId); List otherSalaryAcctRecordPOS = salaryAcctCalculateBO.getOtherSalaryAcctRecordPOS().stream().filter(e -> otherSalaryAcctRecordIds.contains(e.getId())).collect(Collectors.toList()); // 相同税款所属期内,同一个个税扣缴义务人下的同一个人存在多次工资薪金类型的薪资核算记录,那么只有最早创建的薪资核算记录可以扣减如下数据:减除费用、专项扣除、专项附加扣除、其他扣除 // 根据薪资核算记录的创建时间判断是否需要做合并计税处理 boolean needConsolidatedTax = otherSalaryAcctRecordPOS.stream().anyMatch(salaryAcctRecordPO -> salaryAcctCalculateBO.getSalaryAcctRecordPO().getCreateTime().compareTo(salaryAcctRecordPO.getCreateTime()) > 0); if (!needConsolidatedTax) { return resultValue; } // 合并计税处理 return SalaryAcctConsolidatedTaxBO.handleConsolidatedTaxValue(resultValue, salaryItemPO, salaryAcctCalculateBO.getSalaryItemPOS(), otherSalaryAcctResultPOS); } }