@@ -1,19 +1,38 @@
package com.ruoyi.oa.service.impl ;
import cn.hutool.core.bean.BeanUtil ;
import com.ruoyi.common.core.domain.R ;
import com.ruoyi.common.core.domain.entity.SysUser ;
import com.ruoyi.common.core.service.UserService ;
import com.ruoyi.common.utils.StringUtils ;
import com.ruoyi.common.core.page.TableDataInfo ;
import com.ruoyi.common.core.domain.PageQuery ;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page ;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper ;
import com.baomidou.mybatisplus.core.toolkit.Wrappers ;
import com.ruoyi.flowable.utils.TaskUtils ;
import com.ruoyi.oa.domain.OaSalaryItem ;
import com.ruoyi.oa.domain.vo.CalcResultVo ;
import com.ruoyi.oa.domain.vo.SysOaHolidayVo ;
import com.ruoyi.oa.mapper.OaSalaryItemMapper ;
import com.ruoyi.oa.service.IOaSalaryItemService ;
import com.ruoyi.oa.service.ISysOaHolidayService ;
import com.ruoyi.oa.utils.OwnHttpUtils ;
import com.ruoyi.system.service.ISysDeptService ;
import com.ruoyi.system.service.ISysRoleService ;
import com.ruoyi.system.service.ISysUserService ;
import com.ruoyi.workflow.mapper.WfDeployFormMapper ;
import com.ruoyi.workflow.service.IWfTaskService ;
import liquibase.pro.packaged.A ;
import lombok.RequiredArgsConstructor ;
import org.apache.catalina.User ;
import org.flowable.engine.HistoryService ;
import org.flowable.engine.RepositoryService ;
import org.flowable.engine.TaskService ;
import org.flowable.engine.history.HistoricProcessInstance ;
import org.flowable.engine.history.HistoricProcessInstanceQuery ;
import org.flowable.engine.repository.Deployment ;
import org.flowable.task.api.TaskQuery ;
import org.springframework.beans.factory.annotation.Autowired ;
import org.springframework.stereotype.Service ;
import com.ruoyi.oa.domain.bo.OaSalaryBo ;
@@ -22,9 +41,15 @@ import com.ruoyi.oa.domain.OaSalary;
import com.ruoyi.oa.mapper.OaSalaryMapper ;
import com.ruoyi.oa.service.IOaSalaryService ;
import java.util.List ;
import java.util.Map ;
import java.u til.Collection ;
import javax.annotation.Resource ;
import java.text.SimpleDateFormat ;
import java.time.Instant ;
import java.time.LocalDate ;
import java.time.LocalDateTime ;
import java.time.ZoneId ;
import java.time.format.DateTimeFormatter ;
import java.time.format.DateTimeParseException ;
import java.util.* ;
/**
* 薪资管理Service业务层处理
@@ -38,9 +63,29 @@ public class OaSalaryServiceImpl implements IOaSalaryService {
private final OaSalaryMapper baseMapper ;
private final IWfTaskService wfTaskService ;
private final WfDeployFormMapper deployFormMapper ;
@Resource
private RepositoryService repositoryService ;
@Resource
private TaskService taskService ;
@Resource
private HistoryService historyService ;
@Autowired
private ISysUserService userService ;
@Autowired
private ISysOaHolidayService holidayService ;
@Autowired
private OaSalaryItemMapper salaryItemMapper ;
/**
* 查询薪资管理
*/
@@ -59,6 +104,19 @@ public class OaSalaryServiceImpl implements IOaSalaryService {
return TableDataInfo . build ( result ) ;
}
@Override
public R < Void > importSalaryUser ( ) {
SysUser sysUser = new SysUser ( ) ;
List < SysUser > sysUsers = userService . selectUserList ( sysUser ) ;
for ( SysUser user : sysUsers ) {
OaSalary oaSalary = new OaSalary ( ) ;
oaSalary . setUserId ( user . getUserId ( ) ) ;
oaSalary . setBaseSalary ( Double . valueOf ( user . getLaborCost ( ) ) ) ;
oaSalary . setPayTime ( new Date ( ) ) ;
baseMapper . insert ( oaSalary ) ;
}
return R . ok ( ) ;
}
/**
* 查询薪资管理列表
*/
@@ -120,23 +178,279 @@ public class OaSalaryServiceImpl implements IOaSalaryService {
return baseMapper . deleteBatchIds ( ids ) > 0 ;
}
/**
* 计算薪资
* @param bo
*/
@Override
public void calcSalary ( OaSalaryBo bo ) {
// 1先获取对应的json路径 "/home/ubuntu/lyq/fad_oa_kqdeal/202411.json"
String jsonPath = OwnHttpUtils . getJsonName ( bo . getMonthStr ( ) , bo . getFilePath ( ) ) ;
// 2拿到所有的用户
SysUser sysUser = new SysUser ( ) ;
Date yearMonth = StringToDate ( combineYearMonth ( bo . getMonthStr ( ) ) ) ;
List < SysUser > sysUsers = userService . selectUserList ( sysUser ) ;
// 3遍历user列表 拿出所有的nickName分别拿到他们的json 进行第二步分析
for ( SysUser user : sysUsers ) {
OaSalary salary = baseMapper . selectVoByUserId ( user . getUserId ( ) ) ;
if ( salary = = null ) {
return ;
}
// 3.1 拿到一个人打卡机的记录
CalcResultVo res = OwnHttpUtils . getBaseCalc ( jsonPath , user . getNickName ( ) ) ;
// 3.2 通过打卡机拿到的记录去和差旅表、请假申请表、假期表做比对
HistoricProcessInstanceQuery historicProcessInstanceQuery = historyService . createHistoricProcessInstanceQuery ( )
. startedBy ( String . valueOf ( user . getUserId ( ) ) )
. orderByProcessInstanceStartTime ( )
. desc ( ) ;
// 3.3 查询到他所有的任务 其中包括了差旅申请和请假申请的事
List < HistoricProcessInstance > list = historicProcessInstanceQuery . list ( ) ;
for ( HistoricProcessInstance historicProcessInstance : list ) {
Deployment deployment = repositoryService . createDeploymentQuery ( )
. deploymentId ( historicProcessInstance . getDeploymentId ( ) ) . singleResult ( ) ;
// 这个deployment可以拿到他的分类信息 这里我需要的是 trip和absence
if ( deployment . getCategory ( ) = = " absence " | | deployment . getCategory ( ) = = " trip " ) {
TaskQuery taskQuery = taskService . createTaskQuery ( )
. taskCategory ( deployment . getCategory ( ) ) ;
taskQuery . list ( ) . forEach ( task - > {
Map < String , Object > processVariables = task . getProcessVariables ( ) ;
String startTime = String . valueOf ( processVariables . get ( " startTime " ) ) ;
String endTime = String . valueOf ( processVariables . get ( " endTime " ) ) ;
List < String > timeRange = getDateRange ( startTime , endTime ) ;
if ( deployment . getCategory ( ) . equals ( " absence " ) ) {
// 这里的item为1时则为是上午 否则为下午
Long startTimeItem = ( Long ) processVariables . get ( " startTimeItem " ) ;
Long endTimeItem = ( Long ) processVariables . get ( " endTimeItem " ) ;
}
// 计算缺席的某天是否请假或者出差
List < String > absentDates = res . getAbsentDates ( ) ;
List < String > realDates = new ArrayList < > ( ) ;
List < String > real05Dates = new ArrayList < > ( ) ;
for ( String absentDate : absentDates ) {
// 形如2024-11-01
String fullDate = combineYearMonthAndDay ( bo . getMonthStr ( ) , absentDate ) ;
boolean contains = timeRange . contains ( fullDate ) ;
// 如果为false 则说明此天没有请假或者出差则将其记录
if ( ! contains ) {
realDates . add ( fullDate ) ;
}
}
for ( String real05Date : real05Dates ) {
String fullDate = combineYearMonthAndDay ( bo . getMonthStr ( ) , real05Date ) ;
boolean contains = timeRange . contains ( fullDate ) ;
if ( ! contains ) {
real05Dates . add ( fullDate ) ;
}
}
// 这次已经将缺勤拿出来了 现在还要判断今天是否为节假日
List < String > realDate2 = new ArrayList < > ( ) ;
for ( String realDate : realDates ) {
SysOaHolidayVo sysOaHolidayVo = holidayService . queryHolidayByDate ( StringToDate ( realDate ) ) ;
if ( sysOaHolidayVo ! = null & & sysOaHolidayVo . getType ( ) ! = 0 ) {
// 如果不为节假日则加入进去
realDate2 . add ( realDate ) ;
}
}
List < String > real05Date2 = new ArrayList < > ( ) ;
for ( String real05Date : real05Dates ) {
SysOaHolidayVo sysOaHolidayVo = holidayService . queryHolidayByDate ( StringToDate ( real05Date ) ) ;
if ( sysOaHolidayVo ! = null & & sysOaHolidayVo . getType ( ) ! = 0 ) {
// 如果不为节假日则加入进去
real05Date2 . add ( real05Date ) ;
}
// TODO 思考这里有两种情况 首先可能是车间员工他们有一个打卡表可以通过Attendence表进行比对, 但是是否还会出现上下午的问题呢
// TODO 第二种情况是职工,他们只能通过差旅报销去判断
}
buildItemAbsent ( realDate2 , salary , 1 . 0 , yearMonth ) ;
buildItemAbsent ( real05Date2 , salary , 0 . 5 , yearMonth ) ;
} ) ;
}
}
// 3.4创建其他的item包括了好多罚金
// 3.4.1 迟到罚金
StringBuilder sb = new StringBuilder ( ) ;
for ( String lateDate : res . getLateDates ( ) ) {
sb . append ( lateDate ) ;
sb . append ( " , " ) ;
}
sb . append ( " 迟到日期 " ) ;
OaSalaryItem oaSalaryItem = new OaSalaryItem ( ) ;
oaSalaryItem . setSalaryId ( salary . getSalaryId ( ) ) ;
oaSalaryItem . setPrice ( Double . valueOf ( res . getLateFee ( ) ) ) ;
oaSalaryItem . setType ( 4L ) ;
oaSalaryItem . setFlag ( 0L ) ;
oaSalaryItem . setReason ( sb . toString ( ) ) ;
oaSalaryItem . setSignTime ( yearMonth ) ;
salaryItemMapper . insert ( oaSalaryItem ) ;
// 3.4.2 早退罚金
sb = new StringBuilder ( ) ;
for ( String lateDate : res . getLeaveEarly10Dates ( ) ) {
sb . append ( lateDate ) ;
sb . append ( " , " ) ;
}
sb . append ( " 早退日期十分钟 " ) ;
for ( String lateDate : res . getLeaveEarly30Dates ( ) ) {
sb . append ( lateDate ) ;
sb . append ( " , " ) ;
}
sb . append ( " 早退日期三十分钟 " ) ;
oaSalaryItem = new OaSalaryItem ( ) ;
oaSalaryItem . setSalaryId ( salary . getSalaryId ( ) ) ;
oaSalaryItem . setPrice ( Double . valueOf ( res . getLeaveEarlyFee ( ) ) ) ;
oaSalaryItem . setType ( 5L ) ;
oaSalaryItem . setFlag ( 0L ) ;
oaSalaryItem . setReason ( sb . toString ( ) ) ;
oaSalaryItem . setSignTime ( yearMonth ) ;
salaryItemMapper . insert ( oaSalaryItem ) ;
// 3.4.3 打卡罚金
sb = new StringBuilder ( ) ;
for ( String lateDate : res . getLateDates ( ) ) {
sb . append ( lateDate ) ;
sb . append ( " , " ) ;
}
sb . append ( " 未打卡 " ) ;
oaSalaryItem = new OaSalaryItem ( ) ;
oaSalaryItem . setSalaryId ( salary . getSalaryId ( ) ) ;
oaSalaryItem . setPrice ( Double . valueOf ( res . getNotAttendanceFee ( ) ) ) ;
oaSalaryItem . setType ( 6L ) ;
oaSalaryItem . setFlag ( 0L ) ;
oaSalaryItem . setReason ( sb . toString ( ) ) ;
oaSalaryItem . setSignTime ( yearMonth ) ;
salaryItemMapper . insert ( oaSalaryItem ) ;
}
}
private void buildItemAbsent ( List < String > realDate2 , OaSalary salary , Double flag , Date yearMonth ) {
// 最终这个为缺勤日期
OaSalaryItem oaSalaryItem = new OaSalaryItem ( ) ;
StringBuilder sb = new StringBuilder ( ) ;
// 接下来将他拼接成一个字符串为扣款原因矿工问题
for ( String s : realDate2 ) {
sb . append ( s ) ;
sb . append ( " , " ) ;
}
if ( flag > 0 . 5 ) {
sb . append ( " 旷工整天 " ) ;
} else {
sb . append ( " 旷工半天 " ) ;
}
oaSalaryItem . setType ( 3L ) ;
oaSalaryItem . setReason ( String . valueOf ( sb ) ) ;
oaSalaryItem . setPrice ( 2 * salary . getBaseSalary ( ) * realDate2 . size ( ) * flag ) ;
oaSalaryItem . setFlag ( 0L ) ;
oaSalaryItem . setSignTime ( yearMonth ) ;
oaSalaryItem . setSalaryId ( salary . getSalaryId ( ) ) ;
salaryItemMapper . insert ( oaSalaryItem ) ;
}
/**
* 获取从 startTime 到 endTime( 含首尾) 的所有日期列表
*
* @param startTime 字符串格式 "yyyy-MM-dd" 的开始日期
* @param endTime 字符串格式 "yyyy-MM-dd" 的结束日期
* @return 日期列表,每个元素形如 "yyyy-MM-dd"
*/
public static List < String > getDateRange ( String startTime , String endTime ) {
// 1. 定义解析与输出格式
DateTimeFormatter formatter = DateTimeFormatter . ofPattern ( " yyyy-MM-dd " ) ;
try {
// 2. 解析成 LocalDate
LocalDate startDate = LocalDate . parse ( startTime , formatter ) ;
LocalDate endDate = LocalDate . parse ( endTime , formatter ) ;
// 若开始日期大于结束日期,则抛异常(也可根据需求改成自动调换顺序)
if ( startDate . isAfter ( endDate ) ) {
throw new IllegalArgumentException ( " 开始日期不得晚于结束日期: "
+ startTime + " > " + endTime ) ;
}
// 3. 循环遍历日期,加入列表
List < String > dateList = new ArrayList < > ( ) ;
LocalDate currentDate = startDate ;
while ( ! currentDate . isAfter ( endDate ) ) {
// 每次加入 "yyyy-MM-dd" 格式字符串
dateList . add ( currentDate . format ( formatter ) ) ;
// 加 1 天
currentDate = currentDate . plusDays ( 1 ) ;
}
return dateList ;
} catch ( DateTimeParseException e ) {
// 如果传入的日期字符串格式不对,抛出异常或自行处理
throw new IllegalArgumentException ( " 日期格式错误,要求 yyyy-MM-dd " , e ) ;
}
}
/**
* 将形如 "202411" (yyyyMM) 和 "11-04" (MM-dd) 拼装成 "2024-11-04" (yyyy-MM-dd)
*
* @param yearMonth 形如 "202411" 的字符串 (前4位为年份, 后2位为月份)
* @param monthDay 形如 "11-04" 的字符串 (前2位为月份, 后2位为天数)
* @return 拼装好的 "yyyy-MM-dd"
*/
private String combineYearMonthAndDay ( String yearMonth , String monthDay ) {
// 1. 拆解第一个参数: "202411" => 年 (2024), 月 (11)
String year = yearMonth . substring ( 0 , 4 ) ; // "2024"
String monthFromYearMonth = yearMonth . substring ( 4 , 6 ) ; // "11"
// 2. 拆解第二个参数: "11-04" => 月 (11), 日 (04)
String [ ] parts = monthDay . split ( " - " ) ;
if ( parts . length ! = 2 ) {
throw new IllegalArgumentException ( " 传入的 monthDay 格式不正确, 应形如 'MM-dd'。实际传入: " + monthDay ) ;
}
String monthFromMonthDay = parts [ 0 ] ; // "11"
String day = parts [ 1 ] ; // "04"
// 3. 校验两个“月”是否一致(可选)
if ( ! monthFromYearMonth . equals ( monthFromMonthDay ) ) {
throw new IllegalArgumentException ( " yearMonth 与 monthDay 中的 '月' 不匹配: "
+ monthFromYearMonth + " != " + monthFromMonthDay ) ;
}
// 4. 组合成 yyyy-MM-dd 格式
return year + " - " + monthFromYearMonth + " - " + day ;
}
/**
* 将形如 "202411" (yyyyMM) 和 "11-04" (MM-dd) 拼装成 "2024-11-04" (yyyy-MM-dd)
*
* @param yearMonth 形如 "202411" 的字符串 (前4位为年份, 后2位为月份)
* @return 拼装好的 "yyyy-MM-dd"
*/
private String combineYearMonth ( String yearMonth ) {
// 1. 拆解第一个参数: "202411" => 年 (2024), 月 (11)
String year = yearMonth . substring ( 0 , 4 ) ; // "2024"
String monthFromYearMonth = yearMonth . substring ( 4 , 6 ) ; // "11"
// 4. 组合成 yyyy-MM-dd 格式
return year + " - " + monthFromYearMonth ;
}
private Date StringToDate ( String dateStr ) {
DateTimeFormatter formatter = DateTimeFormatter . ofPattern ( " yyyy-MM-dd HH:mm:ss " ) ;
// 1. 先解析成 LocalDateTime
LocalDateTime localDateTime = LocalDateTime . parse ( dateStr , formatter ) ;
// 2. 转换为 Instant( 指定时区)
Instant instant = localDateTime . atZone ( ZoneId . systemDefault ( ) ) . toInstant ( ) ;
// 3. 转换成旧版 Date
return Date . from ( instant ) ;
}
private String DateToString ( Date date ) {
// 2. 构造解析器,指定格式。例如 "yyyy-MM-dd"
SimpleDateFormat sdf = new SimpleDateFormat ( " yyyy-MM-dd " ) ;
// 3. 调用 parse() 将字符串转为 Date
return sdf . format ( date ) ;
}
}