package cn.iocoder.yudao.module.hrm.service.salary;
|
|
import cn.hutool.core.collection.CollUtil;
|
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
import cn.iocoder.yudao.module.hrm.controller.admin.salary.vo.HrmSalaryCalculationPageReqVO;
|
import cn.iocoder.yudao.module.hrm.dal.dataobject.approval.HrmLeaveApplicationDO;
|
import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO;
|
import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmEmployeeSalaryDO;
|
import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmEmployeeSocialSecurityDO;
|
import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmLeaveTypeConfigDO;
|
import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmSalaryCalculationDO;
|
import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmSalaryPaymentDetailDO;
|
import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmSalaryPaymentDO;
|
import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmSocialSecuritySchemeDO;
|
import cn.iocoder.yudao.module.hrm.dal.mysql.approval.HrmLeaveApplicationMapper;
|
import cn.iocoder.yudao.module.hrm.dal.mysql.employee.HrmEmployeeMapper;
|
import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmEmployeeSalaryMapper;
|
import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmEmployeeSocialSecurityMapper;
|
import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmLeaveTypeConfigMapper;
|
import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmSalaryCalculationMapper;
|
import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmSalaryPaymentDetailMapper;
|
import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmSalaryPaymentMapper;
|
import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmSocialSecuritySchemeMapper;
|
import cn.iocoder.yudao.module.hrm.dal.redis.HrmNoRedisDAO;
|
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
|
import jakarta.annotation.Resource;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.validation.annotation.Validated;
|
|
import java.math.BigDecimal;
|
import java.math.RoundingMode;
|
import java.time.LocalDate;
|
import java.time.LocalDateTime;
|
import java.time.YearMonth;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.stream.Collectors;
|
|
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.*;
|
|
/**
|
* 酬核算 Service 实现类
|
*
|
* @author 芋道源码
|
*/
|
@Service
|
@Validated
|
public class HrmSalaryCalculationServiceImpl implements HrmSalaryCalculationService {
|
|
/**
|
* 个税起征点(每月5000元)
|
*/
|
private static final BigDecimal TAX_THRESHOLD = new BigDecimal("5000");
|
|
/**
|
* 默认缴纳比例(当员工社保档案中没有配置或方案中没有配置时使用)
|
*/
|
private static final BigDecimal DEFAULT_PENSION_PERSONAL_RATIO = new BigDecimal("0.08");
|
private static final BigDecimal DEFAULT_MEDICAL_PERSONAL_RATIO = new BigDecimal("0.02");
|
private static final BigDecimal DEFAULT_UNEMPLOYMENT_PERSONAL_RATIO = new BigDecimal("0.005");
|
private static final BigDecimal DEFAULT_HOUSING_FUND_PERSONAL_RATIO = new BigDecimal("0.12");
|
|
@Resource
|
private HrmSalaryCalculationMapper salaryCalculationMapper;
|
@Resource
|
private HrmEmployeeSalaryMapper employeeSalaryMapper;
|
@Resource
|
private HrmEmployeeMapper employeeMapper;
|
@Resource
|
private HrmEmployeeSocialSecurityMapper employeeSocialSecurityMapper;
|
@Resource
|
private HrmSocialSecuritySchemeMapper socialSecuritySchemeMapper;
|
@Resource
|
private HrmNoRedisDAO noRedisDAO;
|
@Resource
|
private DeptApi deptApi;
|
@Resource
|
private HrmDeductionService deductionService;
|
@Resource
|
private HrmLeaveApplicationMapper leaveApplicationMapper;
|
@Resource
|
private HrmLeaveTypeConfigMapper leaveTypeConfigMapper;
|
@Resource
|
private HrmSalaryPaymentMapper salaryPaymentMapper;
|
@Resource
|
private HrmSalaryPaymentDetailMapper salaryPaymentDetailMapper;
|
|
@Override
|
public PageResult<HrmSalaryCalculationDO> getSalaryCalculationPage(HrmSalaryCalculationPageReqVO pageReqVO) {
|
return salaryCalculationMapper.selectPage(pageReqVO);
|
}
|
|
@Override
|
public HrmSalaryCalculationDO getSalaryCalculation(Long id) {
|
return salaryCalculationMapper.selectById(id);
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public void calculateSalary(String period) {
|
calculateSalary(period, null);
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public void calculateSalary(String period, Long deptId) {
|
// 解析核算周期的日期
|
LocalDate effectiveDate = parsePeriodToDate(period);
|
|
// 1. 获取所有在职员工(员工状态:1-在职,2-试用)
|
List<HrmEmployeeDO> employees = employeeMapper.selectListByStatus(null);
|
// 过滤在职和试用期员工
|
employees = employees.stream()
|
.filter(e -> e.getEmployeeStatus() != null
|
&& (e.getEmployeeStatus() == 1 || e.getEmployeeStatus() == 2))
|
.filter(e -> deptId == null || deptId.equals(e.getDeptId()))
|
.toList();
|
|
// 2. 遍历员工进行薪酬核算
|
for (HrmEmployeeDO employee : employees) {
|
// employeeId 是员工ID,用于关联薪酬档案、社保档案、核算记录
|
Long employeeId = employee.getId();
|
// 2.1 检查该员工当月是否已存在核算记录(按员工ID查询)
|
List<HrmSalaryCalculationDO> existCalculations = salaryCalculationMapper.selectListByUserIdAndPeriod(employeeId, period);
|
if (CollUtil.isNotEmpty(existCalculations)) {
|
// 如果存在多条记录,提示异常数据
|
if (existCalculations.size() > 1) {
|
throw exception(SALARY_CALCULATION_DUPLICATE, employee.getName(), period);
|
}
|
continue; // 已存在则跳过
|
}
|
|
// 2.2 获取员工薪酬档案(按员工ID查询)
|
HrmEmployeeSalaryDO employeeSalary = employeeSalaryMapper.selectLatestByUserId(employeeId);
|
if (employeeSalary == null) {
|
// 如果没有薪酬档案,使用员工表中的基本工资创建默认数据
|
employeeSalary = buildDefaultEmployeeSalary(employeeId, employee);
|
}
|
|
// 2.3 获取员工社保公积金档案(按员工ID查询,取最新生效的)
|
HrmEmployeeSocialSecurityDO socialSecurity = employeeSocialSecurityMapper.selectLatestByUserId(employeeId);
|
// 2.4 如果有社保档案,获取绑定的方案
|
HrmSocialSecuritySchemeDO scheme = null;
|
if (socialSecurity != null && socialSecurity.getSocialSecuritySchemeId() != null) {
|
scheme = socialSecuritySchemeMapper.selectById(socialSecurity.getSocialSecuritySchemeId());
|
}
|
|
// 2.5 构建核算记录(包含扣除计算,通过方案获取比例)
|
HrmSalaryCalculationDO calculation = buildSalaryCalculation(period, employeeId, employee, employeeSalary, socialSecurity, scheme, effectiveDate);
|
|
// 2.6 生成核算单号
|
String no = noRedisDAO.generateSalaryCalculationNo();
|
while (salaryCalculationMapper.selectByNo(no) != null) {
|
no = noRedisDAO.generateSalaryCalculationNo();
|
}
|
calculation.setNo(no);
|
|
// 2.7 保存核算记录
|
salaryCalculationMapper.insert(calculation);
|
}
|
}
|
|
/**
|
* 解析核算周期为日期(如 2024-01 -> 2024-01-01)
|
*/
|
private LocalDate parsePeriodToDate(String period) {
|
String[] parts = period.split("-");
|
int year = Integer.parseInt(parts[0]);
|
int month = Integer.parseInt(parts[1]);
|
return LocalDate.of(year, month, 1);
|
}
|
|
/**
|
* 构建默认的员工薪酬档案(基于员工表的基本工资)
|
*/
|
private HrmEmployeeSalaryDO buildDefaultEmployeeSalary(Long employeeId, HrmEmployeeDO employee) {
|
return HrmEmployeeSalaryDO.builder()
|
.userId(employeeId) // 使用员工ID
|
.salaryStructureId(employee.getSalaryStructureId())
|
.baseSalary(employee.getBaseSalary() != null ? employee.getBaseSalary() : BigDecimal.ZERO)
|
.performanceSalary(BigDecimal.ZERO)
|
.mealAllowance(BigDecimal.ZERO)
|
.transportAllowance(BigDecimal.ZERO)
|
.otherAllowance(BigDecimal.ZERO)
|
.status(0)
|
.build();
|
}
|
|
/**
|
* 构建薪酬核算记录(包含社保、公积金、个税、请假扣除计算)
|
* 通过员工社保档案绑定的方案获取缴纳比例
|
*/
|
private HrmSalaryCalculationDO buildSalaryCalculation(String period, Long employeeId, HrmEmployeeDO employee,
|
HrmEmployeeSalaryDO employeeSalary,
|
HrmEmployeeSocialSecurityDO socialSecurity,
|
HrmSocialSecuritySchemeDO scheme,
|
LocalDate effectiveDate) {
|
// 计算应发薪资
|
BigDecimal baseSalary = employeeSalary.getBaseSalary() != null ? employeeSalary.getBaseSalary() : BigDecimal.ZERO;
|
BigDecimal performanceSalary = employeeSalary.getPerformanceSalary() != null ? employeeSalary.getPerformanceSalary() : BigDecimal.ZERO;
|
BigDecimal mealAllowance = employeeSalary.getMealAllowance() != null ? employeeSalary.getMealAllowance() : BigDecimal.ZERO;
|
BigDecimal transportAllowance = employeeSalary.getTransportAllowance() != null ? employeeSalary.getTransportAllowance() : BigDecimal.ZERO;
|
BigDecimal otherAllowance = employeeSalary.getOtherAllowance() != null ? employeeSalary.getOtherAllowance() : BigDecimal.ZERO;
|
BigDecimal overtimePay = BigDecimal.ZERO;
|
|
// 应发工资总额
|
BigDecimal totalIncome = baseSalary.add(performanceSalary).add(mealAllowance)
|
.add(transportAllowance).add(otherAllowance).add(overtimePay);
|
|
// 获取社保基数(员工档案中的个性化基数)
|
BigDecimal socialSecurityBase = (socialSecurity != null && socialSecurity.getSocialSecurityBase() != null)
|
? socialSecurity.getSocialSecurityBase() : baseSalary;
|
|
// 获取公积金基数(员工档案中的个性化基数)
|
BigDecimal housingFundBase = (socialSecurity != null && socialSecurity.getHousingFundBase() != null)
|
? socialSecurity.getHousingFundBase() : baseSalary;
|
|
// 从方案中获取缴纳比例(优先使用方案中的比例,否则使用默认比例)
|
BigDecimal pensionPersonalRatio = (scheme != null && scheme.getPensionPersonalRatio() != null)
|
? scheme.getPensionPersonalRatio() : DEFAULT_PENSION_PERSONAL_RATIO;
|
BigDecimal medicalPersonalRatio = (scheme != null && scheme.getMedicalPersonalRatio() != null)
|
? scheme.getMedicalPersonalRatio() : DEFAULT_MEDICAL_PERSONAL_RATIO;
|
BigDecimal unemploymentPersonalRatio = (scheme != null && scheme.getUnemploymentPersonalRatio() != null)
|
? scheme.getUnemploymentPersonalRatio() : DEFAULT_UNEMPLOYMENT_PERSONAL_RATIO;
|
BigDecimal housingFundPersonalRatio = (scheme != null && scheme.getHousingFundPersonalRatio() != null)
|
? scheme.getHousingFundPersonalRatio() : DEFAULT_HOUSING_FUND_PERSONAL_RATIO;
|
|
// 社保扣除 = 基数 × (养老个人比例 + 医疗个人比例 + 失业个人比例)
|
BigDecimal socialSecurityDeduction = socialSecurityBase
|
.multiply(pensionPersonalRatio)
|
.add(socialSecurityBase.multiply(medicalPersonalRatio))
|
.add(socialSecurityBase.multiply(unemploymentPersonalRatio))
|
.setScale(2, RoundingMode.HALF_UP);
|
|
// 公积金扣除 = 基数 × 公积金个人比例
|
BigDecimal housingFundDeduction = housingFundBase.multiply(housingFundPersonalRatio).setScale(2, RoundingMode.HALF_UP);
|
|
// 计算请假扣款
|
BigDecimal leaveDeduction = calculateLeaveDeduction(employee, period, baseSalary);
|
|
// 计算个税扣除
|
// 应纳税所得额 = 应发工资 - 社保扣除 - 公积金扣除 - 起征点(5000)
|
BigDecimal taxableIncome = totalIncome
|
.subtract(socialSecurityDeduction)
|
.subtract(housingFundDeduction)
|
.subtract(TAX_THRESHOLD);
|
// 如果应纳税所得额小于0,则不缴纳个税
|
if (taxableIncome.compareTo(BigDecimal.ZERO) < 0) {
|
taxableIncome = BigDecimal.ZERO;
|
}
|
BigDecimal taxDeduction = deductionService.calculateTaxDeduction(taxableIncome, effectiveDate);
|
|
BigDecimal otherDeduction = BigDecimal.ZERO;
|
|
// 计算实发工资 = 应发 - 扣除
|
BigDecimal totalDeduction = socialSecurityDeduction.add(housingFundDeduction)
|
.add(taxDeduction).add(otherDeduction).add(leaveDeduction);
|
BigDecimal actualSalary = totalIncome.subtract(totalDeduction);
|
|
return HrmSalaryCalculationDO.builder()
|
.period(period)
|
.userId(employeeId) // 使用员工ID
|
.deptId(employee.getDeptId())
|
.baseSalary(baseSalary)
|
.performanceSalary(performanceSalary)
|
.overtimePay(overtimePay)
|
.mealAllowance(mealAllowance)
|
.transportAllowance(transportAllowance)
|
.otherAllowance(otherAllowance)
|
.socialSecurityDeduction(socialSecurityDeduction)
|
.housingFundDeduction(housingFundDeduction)
|
.taxDeduction(taxDeduction)
|
.otherDeduction(otherDeduction)
|
.leaveDeduction(leaveDeduction)
|
.actualSalary(actualSalary)
|
.workDays(22) // 默认工作日
|
.overtimeHours(BigDecimal.ZERO)
|
.status(0) // 待确认
|
.build();
|
}
|
|
/**
|
* 计算请假扣款
|
*
|
* @param employee 员工信息
|
* @param period 核算周期(如2026-02)
|
* @param baseSalary 基本工资
|
* @return 请假扣款金额
|
*/
|
private BigDecimal calculateLeaveDeduction(HrmEmployeeDO employee, String period, BigDecimal baseSalary) {
|
// 解析周期获取月份开始和结束时间
|
YearMonth yearMonth = YearMonth.parse(period);
|
LocalDateTime monthStart = yearMonth.atDay(1).atStartOfDay();
|
LocalDateTime monthEnd = yearMonth.atEndOfMonth().atTime(23, 59, 59);
|
|
// 查询员工在该月已审批通过的请假记录
|
// 注意:请假申请的 userId 是系统用户ID,需要通过员工表获取
|
Long systemUserId = employee.getUserId();
|
if (systemUserId == null) {
|
return BigDecimal.ZERO;
|
}
|
List<HrmLeaveApplicationDO> leaveApplications = leaveApplicationMapper
|
.selectApprovedListByUserIdAndTimeRange(systemUserId, monthStart, monthEnd);
|
|
if (leaveApplications.isEmpty()) {
|
return BigDecimal.ZERO;
|
}
|
|
// 获取所有请假类型配置
|
List<HrmLeaveTypeConfigDO> configList = leaveTypeConfigMapper.selectList();
|
Map<Integer, BigDecimal> configMap = configList.stream()
|
.filter(c -> c.getStatus() == 0) // 只取启用的配置
|
.collect(Collectors.toMap(HrmLeaveTypeConfigDO::getLeaveType, HrmLeaveTypeConfigDO::getDeductionRatio));
|
|
// 计算日工资(基本工资 / 21.75,按国家规定的工作日计算)
|
BigDecimal dailySalary = baseSalary.divide(new BigDecimal("21.75"), 2, RoundingMode.HALF_UP);
|
|
// 累计请假扣款
|
BigDecimal totalLeaveDeduction = BigDecimal.ZERO;
|
for (HrmLeaveApplicationDO application : leaveApplications) {
|
Integer leaveType = application.getLeaveType();
|
BigDecimal duration = application.getDuration() != null ? application.getDuration() : BigDecimal.ZERO;
|
BigDecimal deductionRatio = configMap.getOrDefault(leaveType, BigDecimal.ZERO);
|
|
// 请假扣款 = 日工资 × 请假天数 × 扣款比例
|
BigDecimal deduction = dailySalary.multiply(duration).multiply(deductionRatio)
|
.setScale(2, RoundingMode.HALF_UP);
|
totalLeaveDeduction = totalLeaveDeduction.add(deduction);
|
}
|
|
return totalLeaveDeduction;
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public void confirmSalaryCalculation(List<Long> ids) {
|
if (ids == null || ids.isEmpty()) {
|
return;
|
}
|
LocalDateTime confirmTime = LocalDateTime.now();
|
String period = null;
|
for (Long id : ids) {
|
HrmSalaryCalculationDO salaryCalculation = salaryCalculationMapper.selectById(id);
|
if (salaryCalculation == null) {
|
throw exception(SALARY_CALCULATION_NOT_EXISTS);
|
}
|
if (salaryCalculation.getStatus() != 0) {
|
throw exception(SALARY_CALCULATION_CONFIRM_FAIL);
|
}
|
salaryCalculation.setStatus(10); // 已确认
|
salaryCalculation.setConfirmTime(confirmTime);
|
salaryCalculationMapper.updateById(salaryCalculation);
|
// 记录周期
|
period = salaryCalculation.getPeriod();
|
}
|
|
// 检查该周期是否所有核算记录都已确认,自动生成发放台账
|
if (period != null) {
|
autoGeneratePaymentIfAllConfirmed(period);
|
}
|
}
|
|
/**
|
* 检查该周期是否所有核算记录都已确认,如果全部确认则自动生成发放台账
|
*/
|
private void autoGeneratePaymentIfAllConfirmed(String period) {
|
// 查询该周期所有核算记录
|
List<HrmSalaryCalculationDO> allCalculations = salaryCalculationMapper.selectListByPeriod(period);
|
if (CollUtil.isEmpty(allCalculations)) {
|
return;
|
}
|
// 检查是否全部已确认(状态=10)
|
boolean allConfirmed = allCalculations.stream()
|
.allMatch(c -> c.getStatus() == 10);
|
if (!allConfirmed) {
|
return;
|
}
|
// 检查是否已存在发放台账
|
HrmSalaryPaymentDO existPayment = salaryPaymentMapper.selectByPeriod(period);
|
if (existPayment != null) {
|
return;
|
}
|
// 自动生成发放台账
|
generatePaymentInternal(period, allCalculations);
|
}
|
|
private HrmSalaryCalculationDO validateSalaryCalculationExists(Long id) {
|
HrmSalaryCalculationDO salaryCalculation = salaryCalculationMapper.selectById(id);
|
if (salaryCalculation == null) {
|
throw exception(SALARY_CALCULATION_NOT_EXISTS);
|
}
|
return salaryCalculation;
|
}
|
|
@Override
|
public List<HrmSalaryCalculationDO> getSalaryCalculationListByPeriod(String period) {
|
return salaryCalculationMapper.selectListByPeriod(period);
|
}
|
|
@Override
|
public List<HrmSalaryCalculationDO> previewSalaryCalculation(String period, Long deptId) {
|
// 解析核算周期的日期
|
LocalDate effectiveDate = parsePeriodToDate(period);
|
|
// 1. 获取所有在职员工(员工状态:1-在职,2-试用)
|
List<HrmEmployeeDO> employees = employeeMapper.selectListByStatus(null);
|
// 过滤在职和试用期员工
|
employees = employees.stream()
|
.filter(e -> e.getEmployeeStatus() != null
|
&& (e.getEmployeeStatus() == 1 || e.getEmployeeStatus() == 2))
|
.filter(e -> deptId == null || deptId.equals(e.getDeptId()))
|
.toList();
|
|
// 2. 遍历员工实时计算薪酬
|
List<HrmSalaryCalculationDO> resultList = new java.util.ArrayList<>();
|
for (HrmEmployeeDO employee : employees) {
|
Long employeeId = employee.getId();
|
|
// 2.1 获取员工薪酬档案(按员工ID查询)
|
HrmEmployeeSalaryDO employeeSalary = employeeSalaryMapper.selectLatestByUserId(employeeId);
|
if (employeeSalary == null) {
|
// 如果没有薪酬档案,使用员工表中的基本工资创建默认数据
|
employeeSalary = buildDefaultEmployeeSalary(employeeId, employee);
|
}
|
|
// 2.2 获取员工社保公积金档案(按员工ID查询,取最新生效的)
|
HrmEmployeeSocialSecurityDO socialSecurity = employeeSocialSecurityMapper.selectLatestByUserId(employeeId);
|
// 2.3 如果有社保档案,获取绑定的方案
|
HrmSocialSecuritySchemeDO scheme = null;
|
if (socialSecurity != null && socialSecurity.getSocialSecuritySchemeId() != null) {
|
scheme = socialSecuritySchemeMapper.selectById(socialSecurity.getSocialSecuritySchemeId());
|
}
|
|
// 2.4 构建核算记录(实时计算,不存储)
|
HrmSalaryCalculationDO calculation = buildSalaryCalculation(period, employeeId, employee, employeeSalary, socialSecurity, scheme, effectiveDate);
|
resultList.add(calculation);
|
}
|
return resultList;
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public void saveSalaryCalculationList(List<HrmSalaryCalculationDO> calculationList) {
|
for (HrmSalaryCalculationDO calculation : calculationList) {
|
if (calculation.getId() == null) {
|
// 新增记录,生成核算单号
|
String no = noRedisDAO.generateSalaryCalculationNo();
|
while (salaryCalculationMapper.selectByNo(no) != null) {
|
no = noRedisDAO.generateSalaryCalculationNo();
|
}
|
calculation.setNo(no);
|
calculation.setStatus(0); // 待确认
|
salaryCalculationMapper.insert(calculation);
|
} else {
|
// 更新已有记录
|
salaryCalculationMapper.updateById(calculation);
|
}
|
}
|
}
|
|
/**
|
* 内部生成发放台账方法(供自动生成调用)
|
*/
|
private void generatePaymentInternal(String period, List<HrmSalaryCalculationDO> calculationList) {
|
// 生成发放台账
|
String no = generatePaymentNo();
|
BigDecimal totalAmount = calculationList.stream()
|
.map(c -> c.getActualSalary() != null ? c.getActualSalary() : BigDecimal.ZERO)
|
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
Integer totalCount = calculationList.size();
|
|
HrmSalaryPaymentDO payment = HrmSalaryPaymentDO.builder()
|
.no(no)
|
.period(period)
|
.totalAmount(totalAmount)
|
.totalCount(totalCount)
|
.status(0) // 待发放
|
.build();
|
salaryPaymentMapper.insert(payment);
|
|
// 生成发放明细(不更新核算状态)
|
for (HrmSalaryCalculationDO calculation : calculationList) {
|
HrmSalaryPaymentDetailDO detail = HrmSalaryPaymentDetailDO.builder()
|
.paymentId(payment.getId())
|
.calculationId(calculation.getId())
|
.userId(calculation.getUserId())
|
.deptId(calculation.getDeptId())
|
.actualSalary(calculation.getActualSalary())
|
.status(0) // 待发放
|
.build();
|
salaryPaymentDetailMapper.insert(detail);
|
}
|
}
|
|
private String generatePaymentNo() {
|
String no = noRedisDAO.generateSalaryPaymentNo();
|
while (salaryPaymentMapper.selectByNo(no) != null) {
|
no = noRedisDAO.generateSalaryPaymentNo();
|
}
|
return no;
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public void revokeSalaryCalculation(List<Long> ids) {
|
if (ids == null || ids.isEmpty()) {
|
return;
|
}
|
for (Long id : ids) {
|
HrmSalaryCalculationDO calculation = salaryCalculationMapper.selectById(id);
|
if (calculation == null) {
|
throw exception(SALARY_CALCULATION_NOT_EXISTS);
|
}
|
// 校验状态必须是已确认(10)
|
if (calculation.getStatus() != 10) {
|
throw exception(SALARY_CALCULATION_REVOKE_FAIL);
|
}
|
// 检查是否已发放(状态=20)
|
if (calculation.getStatus() == 20) {
|
throw exception(SALARY_CALCULATION_ALREADY_PAY);
|
}
|
// 删除关联的发放明细
|
HrmSalaryPaymentDetailDO detail = salaryPaymentDetailMapper.selectByCalculationId(id);
|
if (detail != null) {
|
// 如果明细已发放,不允许撤销
|
if (detail.getStatus() == 10) {
|
throw exception(SALARY_CALCULATION_ALREADY_PAY);
|
}
|
salaryPaymentDetailMapper.deleteById(detail.getId());
|
// 更新发放台账的总金额和总人数
|
updatePaymentStatistics(detail.getPaymentId());
|
}
|
// 恢复核算状态为待确认
|
calculation.setStatus(0);
|
calculation.setConfirmTime(null);
|
salaryCalculationMapper.updateById(calculation);
|
}
|
}
|
|
/**
|
* 更新发放台账的统计信息
|
*/
|
private void updatePaymentStatistics(Long paymentId) {
|
HrmSalaryPaymentDO payment = salaryPaymentMapper.selectById(paymentId);
|
if (payment == null) {
|
return;
|
}
|
// 重新计算总金额和总人数
|
List<HrmSalaryPaymentDetailDO> details = salaryPaymentDetailMapper.selectListByPaymentId(paymentId);
|
if (CollUtil.isEmpty(details)) {
|
// 如果没有明细了,删除发放台账
|
salaryPaymentMapper.deleteById(paymentId);
|
return;
|
}
|
BigDecimal totalAmount = details.stream()
|
.map(d -> d.getActualSalary() != null ? d.getActualSalary() : BigDecimal.ZERO)
|
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
payment.setTotalAmount(totalAmount);
|
payment.setTotalCount(details.size());
|
salaryPaymentMapper.updateById(payment);
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public void deleteSalaryCalculation(Long id) {
|
HrmSalaryCalculationDO calculation = salaryCalculationMapper.selectById(id);
|
if (calculation == null) {
|
throw exception(SALARY_CALCULATION_NOT_EXISTS);
|
}
|
// 仅待确认状态可删除
|
if (calculation.getStatus() != 0) {
|
throw exception(SALARY_CALCULATION_DELETE_FAIL);
|
}
|
salaryCalculationMapper.deleteById(id);
|
}
|
|
}
|