BUG和工资
This commit is contained in:
@ -21,6 +21,7 @@ import org.dromara.contractor.domain.vo.usersalaryperiod.SubConstructionUserSala
|
||||
import org.dromara.contractor.service.ISubUserSalaryDetailService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
@ -85,4 +86,16 @@ public class SubUserSalaryDetailController extends BaseController {
|
||||
subUserSalaryDetailService.export(response, dto);
|
||||
}
|
||||
|
||||
@PutMapping("/import")
|
||||
public R<Void> importData(@RequestParam("file") MultipartFile file,
|
||||
@RequestParam("month") String month) {
|
||||
subUserSalaryDetailService.importData(file, month);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
@GetMapping("/detailList")
|
||||
public R<List<SubUserSalaryDetail>> detailList( SubConstructionUserSalaryDto dto) {
|
||||
return R.ok(subUserSalaryDetailService.detailList(dto));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -65,4 +65,13 @@ public class SubUserSalaryDetail extends BaseEntity {
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 工时
|
||||
*/
|
||||
private Double workHour;
|
||||
|
||||
/**
|
||||
* 当日总工资
|
||||
*/
|
||||
private BigDecimal totalSalary;
|
||||
}
|
||||
|
||||
@ -28,4 +28,10 @@ public class SubConstructionUserSalaryDto {
|
||||
*/
|
||||
@NotBlank(message = "时间不能为空")
|
||||
private String time;
|
||||
|
||||
/**
|
||||
* 用户
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
}
|
||||
|
||||
@ -36,6 +36,10 @@ public class SubConstructionUserSalaryVo implements Serializable {
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 用户id
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 人员姓名
|
||||
@ -83,7 +87,7 @@ public class SubConstructionUserSalaryVo implements Serializable {
|
||||
private BigDecimal salary;
|
||||
|
||||
/**
|
||||
* 时间
|
||||
* 发放时间
|
||||
*/
|
||||
private String time;
|
||||
|
||||
@ -91,4 +95,9 @@ public class SubConstructionUserSalaryVo implements Serializable {
|
||||
|
||||
private String blank;
|
||||
|
||||
/**
|
||||
* 上传时间
|
||||
*/
|
||||
private String createTime;
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,23 @@
|
||||
package org.dromara.contractor.excel;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 动态日期考勤数据实体类(无班组字段)
|
||||
*/
|
||||
@Data
|
||||
public class DynamicSalaryData {
|
||||
// 基础固定字段
|
||||
private Integer serialNumber; // 序号
|
||||
private String name; // 姓名
|
||||
private String idCard; // 身份证号
|
||||
private Double total; // 合计出勤天数
|
||||
private String isLeave; // 是否离场(未离场/已离场)
|
||||
private String signature; // 签字(可选)
|
||||
|
||||
// 动态日期考勤:key=完整日期(如“2025-09-01”),value=考勤状态(0=未出勤,1=出勤)
|
||||
private Map<String, Double> dailyAttendance = new LinkedHashMap<>();
|
||||
}
|
||||
@ -0,0 +1,176 @@
|
||||
package org.dromara.contractor.excel;
|
||||
|
||||
import com.alibaba.excel.context.AnalysisContext;
|
||||
import com.alibaba.excel.event.AnalysisEventListener;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.common.utils.IdCardEncryptorUtil;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
public class DynamicSalaryListener extends AnalysisEventListener<Map<Integer, String>> {
|
||||
private final List<DynamicSalaryData> allAttendanceList;
|
||||
private final String month;
|
||||
private static final int DATE_COL_START_INDEX = 3; // 日期列起始索引(第4列)
|
||||
private int dateColEndIndex;
|
||||
private final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||
private boolean isHeadInitialized = false;
|
||||
// 写死表头行号(0-based,对应Excel第3行)
|
||||
private static final int FIXED_HEAD_ROW_NUMBER = 2;
|
||||
|
||||
public DynamicSalaryListener(List<DynamicSalaryData> allAttendanceList, String month) {
|
||||
this.allAttendanceList = allAttendanceList;
|
||||
this.month = month;
|
||||
// 月份格式校验
|
||||
try {
|
||||
LocalDate.parse(month + "-01", dateFormatter);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException("月份格式错误!需传入yyyy-MM格式(如2025-09)");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
|
||||
// 仅处理固定行号的表头,且仅初始化1次
|
||||
int currentRowIndex = context.readRowHolder().getRowIndex();
|
||||
if (currentRowIndex != FIXED_HEAD_ROW_NUMBER || isHeadInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 解析“合计”列索引
|
||||
for (Map.Entry<Integer, String> entry : headMap.entrySet()) {
|
||||
String headValue = entry.getValue().trim();
|
||||
if ("合计".equals(headValue)) {
|
||||
this.dateColEndIndex = entry.getKey() - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 校验表头有效性
|
||||
if (dateColEndIndex < DATE_COL_START_INDEX) {
|
||||
throw new RuntimeException(
|
||||
String.format("行号%d(Excel第%d行)未找到“合计”列,Excel格式不符合要求",
|
||||
FIXED_HEAD_ROW_NUMBER, FIXED_HEAD_ROW_NUMBER + 1)
|
||||
);
|
||||
}
|
||||
|
||||
isHeadInitialized = true;
|
||||
log.info("Sheet【{}】- 月份:{},日期列范围=索引{}~{}(共{}天)",
|
||||
context.readSheetHolder().getSheetName(), // 打印当前Sheet名
|
||||
month, DATE_COL_START_INDEX, dateColEndIndex,
|
||||
dateColEndIndex - DATE_COL_START_INDEX + 1);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void invoke(Map<Integer, String> rowData, AnalysisContext context) {
|
||||
// 【核心优化:强化无效行过滤】
|
||||
// 1. 获取第1列(序号)和第2列(姓名)的值,用于判断是否为有效数据行
|
||||
String serialNumberStr = getCellValue(rowData, 0);
|
||||
String name = getCellValue(rowData, 1);
|
||||
|
||||
// 2. 过滤规则:
|
||||
// - 序号为空或非数字(有效数据行序号是1、2、3...)
|
||||
// - 姓名为空或包含“姓名/日期”“制表人”“注:”“签字”“项目负责人”等关键词
|
||||
// - 整行无有效数据(所有列均为空)
|
||||
if (serialNumberStr == null || serialNumberStr.isEmpty()
|
||||
|| !serialNumberStr.matches("\\d+") // 序号必须是数字
|
||||
|| name == null || name.isEmpty()
|
||||
|| name.contains("姓名/日期")
|
||||
|| name.contains("制表人:")
|
||||
|| name.contains("注:")
|
||||
|| name.contains("签字")
|
||||
|| name.contains("项目负责人")
|
||||
|| isAllColumnsEmpty(rowData)) { // 过滤空行
|
||||
log.debug("Sheet【{}】- 跳过无效行:序号={},姓名={}",
|
||||
context.readSheetHolder().getSheetName(), serialNumberStr, name);
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 封装有效考勤数据(原有逻辑不变)
|
||||
DynamicSalaryData attendance = new DynamicSalaryData();
|
||||
attendance.setSerialNumber(parseInt(serialNumberStr));
|
||||
attendance.setName(name);
|
||||
attendance.setIdCard(getCellValue(rowData, 2));
|
||||
attendance.setTotal(parseDouble(getCellValue(rowData, dateColEndIndex + 1)));
|
||||
attendance.setIsLeave(getCellValue(rowData, dateColEndIndex + 2));
|
||||
attendance.setSignature(getCellValue(rowData, dateColEndIndex + 3));
|
||||
|
||||
// 4. 解析动态日期列
|
||||
for (int colIndex = DATE_COL_START_INDEX; colIndex <= dateColEndIndex; colIndex++) {
|
||||
int day = colIndex - DATE_COL_START_INDEX + 1;
|
||||
String fullDate = month + "-" + String.format("%02d", day);
|
||||
Double attendStatus = parseDouble(getCellValue(rowData, colIndex));
|
||||
attendance.getDailyAttendance().put(fullDate, attendStatus == null ? 0 : attendStatus);
|
||||
}
|
||||
|
||||
allAttendanceList.add(attendance);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void doAfterAllAnalysed(AnalysisContext context) {
|
||||
log.info("Sheet【{}】读取完成!累计汇总【{}】条有效考勤记录",
|
||||
context.readSheetHolder().getSheetName(), allAttendanceList.size());
|
||||
}
|
||||
|
||||
|
||||
// ------------------- 新增工具方法:判断整行是否为空 -------------------
|
||||
/**
|
||||
* 判断行数据是否所有列均为空(过滤空行)
|
||||
*/
|
||||
private boolean isAllColumnsEmpty(Map<Integer, String> rowData) {
|
||||
if (rowData == null || rowData.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
for (String value : rowData.values()) {
|
||||
if (value != null && !value.trim().isEmpty()) {
|
||||
return false; // 存在非空列,不是空行
|
||||
}
|
||||
}
|
||||
return true; // 所有列均为空,是空行
|
||||
}
|
||||
|
||||
|
||||
// 原有工具方法(不变)
|
||||
private String getCellValue(Map<Integer, String> rowData, int colIndex) {
|
||||
String value = rowData.get(colIndex);
|
||||
return value == null ? null : value.trim();
|
||||
}
|
||||
|
||||
private Integer parseInt(String value) {
|
||||
if (value == null || value.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return Integer.parseInt(value);
|
||||
} catch (NumberFormatException e) {
|
||||
log.warn("非数字值:{},已忽略", value);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Double parseDouble(String value) {
|
||||
if (value == null || value.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return Double.parseDouble(value);
|
||||
} catch (NumberFormatException e) {
|
||||
log.warn("非数字值:{},已忽略", value);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------- 重置表头初始化状态(用于多Sheet遍历) -------------------
|
||||
/**
|
||||
* 切换Sheet时调用,重置表头初始化状态(确保新Sheet重新解析表头)
|
||||
*/
|
||||
public void resetHeadInitialized() {
|
||||
this.isHeadInitialized = false;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,111 @@
|
||||
package org.dromara.contractor.excel;
|
||||
|
||||
import com.alibaba.excel.EasyExcel;
|
||||
import com.alibaba.excel.ExcelReader;
|
||||
import com.alibaba.excel.read.metadata.ReadSheet;
|
||||
import com.alibaba.excel.support.ExcelTypeEnum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.common.utils.IdCardEncryptorUtil;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@Slf4j
|
||||
public class SalaryExcelReader {
|
||||
|
||||
private static final int GLOBAL_HEAD_ROW_NUMBER = 3;
|
||||
|
||||
/**
|
||||
* 【新增】通过输入流解析Excel(支持前端上传的流)
|
||||
* @param inputStream Excel文件输入流(如MultipartFile.getInputStream())
|
||||
* @param month 月份(格式:yyyy-MM)
|
||||
* @return 汇总的考勤数据列表
|
||||
*/
|
||||
public static List<DynamicSalaryData> readAllAttendanceByStream(InputStream inputStream, String month) {
|
||||
List<DynamicSalaryData> allAttendanceList = new ArrayList<>();
|
||||
DynamicSalaryListener listener = new DynamicSalaryListener(allAttendanceList, month);
|
||||
|
||||
try (
|
||||
// 1. 构建Excel读取器:读取源为 InputStream,全局设置表头行号
|
||||
ExcelReader excelReader = EasyExcel.read(inputStream)
|
||||
.excelType(ExcelTypeEnum.XLSX) // 自动识别xlsx/xls格式(兼容前端上传的两种格式)
|
||||
.headRowNumber(GLOBAL_HEAD_ROW_NUMBER) // 强制表头行号=2
|
||||
.registerReadListener(listener) // 注册监听器
|
||||
.build()
|
||||
) {
|
||||
// 2. 获取所有Sheet并逐个解析
|
||||
List<ReadSheet> readSheetList = excelReader.excelExecutor().sheetList();
|
||||
log.info("从流中发现Excel共【{}】个Sheet,开始逐个解析", readSheetList.size());
|
||||
|
||||
for (ReadSheet readSheet : readSheetList) {
|
||||
log.info("开始解析Sheet【{}】(索引:{})",
|
||||
readSheet.getSheetName(), readSheet.getSheetNo());
|
||||
// 切换Sheet前重置表头初始化状态
|
||||
listener.resetHeadInitialized();
|
||||
// 读取当前Sheet(流解析,无本地文件)
|
||||
excelReader.read(readSheet);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Excel流解析失败!月份:{}", month, e);
|
||||
throw new RuntimeException("Excel流解析异常:" + e.getMessage(), e);
|
||||
}
|
||||
|
||||
log.info("所有Sheet流解析完成!共汇总【{}】条有效考勤记录", allAttendanceList.size());
|
||||
return allAttendanceList;
|
||||
}
|
||||
|
||||
public static List<DynamicSalaryData> readAllAttendance(String excelFilePath, String month) {
|
||||
List<DynamicSalaryData> allAttendanceList = new ArrayList<>();
|
||||
DynamicSalaryListener listener = new DynamicSalaryListener(allAttendanceList, month);
|
||||
|
||||
try (
|
||||
// 【核心修改1:在读取器初始化时就全局设置表头行号】
|
||||
ExcelReader excelReader = EasyExcel.read(excelFilePath)
|
||||
.excelType(ExcelTypeEnum.XLSX)
|
||||
.headRowNumber(GLOBAL_HEAD_ROW_NUMBER) // 全局强制设置表头行号=2
|
||||
.registerReadListener(listener) // 注册监听器
|
||||
.build()
|
||||
) {
|
||||
// 【核心修改2:获取所有Sheet后,直接读取(无需重新构建ReadSheet)】
|
||||
List<ReadSheet> readSheetList = excelReader.excelExecutor().sheetList();
|
||||
log.info("发现Excel共【{}】个Sheet,全局表头行号配置:{}",
|
||||
readSheetList.size(), GLOBAL_HEAD_ROW_NUMBER);
|
||||
|
||||
for (ReadSheet readSheet : readSheetList) {
|
||||
log.info("开始解析Sheet【{}】(索引:{})",
|
||||
readSheet.getSheetName(), readSheet.getSheetNo());
|
||||
// 切换Sheet前重置表头初始化状态
|
||||
listener.resetHeadInitialized();
|
||||
// 直接读取Sheet(此时Sheet已继承全局的headRowNumber=2配置)
|
||||
excelReader.read(readSheet);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Excel读取失败!文件路径:{},全局表头行号:{}",
|
||||
excelFilePath, GLOBAL_HEAD_ROW_NUMBER, e);
|
||||
throw new RuntimeException("Excel读取异常:" + e.getMessage());
|
||||
}
|
||||
|
||||
log.info("所有Sheet解析完成!共汇总【{}】条有效考勤记录", allAttendanceList.size());
|
||||
return allAttendanceList;
|
||||
}
|
||||
|
||||
|
||||
// ------------------- 测试示例 -------------------
|
||||
public static void main(String[] args) {
|
||||
String excelPath = "C:\\Users\\YuanJie\\Desktop\\test.xlsx";
|
||||
String inputMonth = "2025-09";
|
||||
List<DynamicSalaryData> attendanceList = readAllAttendance(excelPath, inputMonth);
|
||||
|
||||
// 打印部分数据验证
|
||||
System.out.println("=== 解析结果预览 ===");
|
||||
for (int i = 0; i < Math.min(3, attendanceList.size()); i++) {
|
||||
DynamicSalaryData data = attendanceList.get(i);
|
||||
System.out.printf("序号:%d,姓名:%s,身份证号:%s,合计出勤:%d天%n",
|
||||
data.getSerialNumber(), data.getName(), data.getIdCard(), data.getTotal());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -12,6 +12,8 @@ import org.dromara.contractor.domain.dto.usersalaryperiod.SubConstructionUserSal
|
||||
import org.dromara.contractor.domain.vo.usersalarydetail.SubUserSalaryDetailVo;
|
||||
import org.dromara.contractor.domain.vo.usersalaryperiod.SubConstructionUserSalaryVo;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDate;
|
||||
@ -95,6 +97,14 @@ public interface ISubUserSalaryDetailService extends IService<SubUserSalaryDetai
|
||||
*/
|
||||
void export(HttpServletResponse response, SubConstructionUserSalaryDto dto) throws IOException;
|
||||
|
||||
/**
|
||||
* 导入
|
||||
*/
|
||||
void importData( MultipartFile file, String month);
|
||||
|
||||
/**
|
||||
* 人员详情
|
||||
*/
|
||||
List<SubUserSalaryDetail> detailList( SubConstructionUserSalaryDto dto);
|
||||
|
||||
}
|
||||
|
||||
@ -1127,7 +1127,14 @@ public class SubConstructionUserServiceImpl extends ServiceImpl<SubConstructionU
|
||||
vo.setGender(wordsResult.getGender() != null ? wordsResult.getGender().getWords() : "");
|
||||
vo.setImage(upload);
|
||||
} else {
|
||||
vo.setExpiryDate(wordsResult.getExpiryDate() != null ? wordsResult.getExpiryDate().getWords() : "");
|
||||
if (wordsResult.getExpiryDate() != null ) {
|
||||
vo.setExpiryDate(wordsResult.getExpiryDate().getWords());
|
||||
if ("长期".equals(wordsResult.getExpiryDate().getWords())) {
|
||||
vo.setExpiryDate("9999-12-31");
|
||||
}
|
||||
}else {
|
||||
vo.setExpiryDate("");
|
||||
}
|
||||
vo.setIssuingAuthority(wordsResult.getIssuingAuthority() != null ? wordsResult.getIssuingAuthority().getWords() : "");
|
||||
vo.setIssuingDate(wordsResult.getIssuingDate() != null ? wordsResult.getIssuingDate().getWords() : "");
|
||||
vo.setImage(upload);
|
||||
|
||||
@ -3,11 +3,15 @@ package org.dromara.contractor.service.impl;
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import com.alibaba.excel.EasyExcel;
|
||||
import com.alibaba.excel.ExcelWriter;
|
||||
import com.alibaba.excel.write.metadata.WriteSheet;
|
||||
import com.alibaba.excel.write.metadata.fill.FillConfig;
|
||||
import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import jakarta.annotation.Resource;
|
||||
@ -19,6 +23,7 @@ import org.apache.poi.ss.usermodel.Sheet;
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
import org.apache.poi.ss.usermodel.WorkbookFactory;
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||
import org.dromara.bigscreen.domain.BusConstructionUser;
|
||||
import org.dromara.common.core.constant.HttpStatus;
|
||||
import org.dromara.common.core.exception.ServiceException;
|
||||
import org.dromara.common.core.utils.ObjectUtils;
|
||||
@ -36,9 +41,11 @@ import org.dromara.contractor.domain.exportvo.BusConstructionUserExportVo;
|
||||
import org.dromara.contractor.domain.vo.constructionuser.SubConstructionUserVo;
|
||||
import org.dromara.contractor.domain.vo.usersalarydetail.SubUserSalaryDetailVo;
|
||||
import org.dromara.contractor.domain.vo.usersalaryperiod.SubConstructionUserSalaryVo;
|
||||
import org.dromara.contractor.excel.DynamicSalaryData;
|
||||
import org.dromara.contractor.mapper.SubUserSalaryDetailMapper;
|
||||
import org.dromara.contractor.service.ISubConstructionUserService;
|
||||
import org.dromara.contractor.service.ISubUserSalaryDetailService;
|
||||
import org.dromara.gps.domain.vo.ConstructionUser;
|
||||
import org.dromara.project.domain.BusAttendance;
|
||||
import org.dromara.project.domain.BusProject;
|
||||
import org.dromara.project.domain.BusProjectTeam;
|
||||
@ -51,6 +58,7 @@ import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import software.amazon.awssdk.utils.CollectionUtils;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
@ -65,6 +73,7 @@ import java.time.YearMonth;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import org.dromara.contractor.excel.SalaryExcelReader;
|
||||
|
||||
/**
|
||||
* 员工每日工资Service业务层处理
|
||||
@ -263,76 +272,53 @@ public class SubUserSalaryDetailServiceImpl extends ServiceImpl<SubUserSalaryDet
|
||||
@Override
|
||||
public TableDataInfo<SubConstructionUserSalaryVo> salaryPageList(SubConstructionUserSalaryDto dto, PageQuery pageQuery) {
|
||||
|
||||
SubConstructionUserQueryReq req = new SubConstructionUserQueryReq();
|
||||
req.setProjectId(dto.getProjectId());
|
||||
req.setTeamId(dto.getTeamId());
|
||||
req.setUserName(dto.getUserName());
|
||||
|
||||
TableDataInfo<SubConstructionUserVo> subConstructionUserVoTableDataInfo = constructionUserService.queryPageList(req, pageQuery);
|
||||
List<SubConstructionUserVo> rows = subConstructionUserVoTableDataInfo.getRows();
|
||||
if(CollectionUtil.isEmpty(rows)){
|
||||
return TableDataInfo.build();
|
||||
}
|
||||
String time = dto.getTime();
|
||||
YearMonth parse = YearMonth.parse(time, DateTimeFormatter.ofPattern("yyyy-MM"));
|
||||
LocalDate start = parse.atDay(1);
|
||||
LocalDate end = parse.atEndOfMonth();
|
||||
|
||||
List<Long> userIds = rows.stream().map(SubConstructionUserVo::getSysUserId).toList();
|
||||
//考勤数据
|
||||
List<BusAttendance> attendanceList = attendanceService.lambdaQuery()
|
||||
.eq(BusAttendance::getProjectId, dto.getProjectId())
|
||||
.in(BusAttendance::getUserId, userIds)
|
||||
.between(BusAttendance::getClockDate, start, end)
|
||||
.list();
|
||||
//工资数据
|
||||
List<SubUserSalaryDetail> salaryDetailList = this.lambdaQuery()
|
||||
.eq(SubUserSalaryDetail::getProjectId, dto.getProjectId())
|
||||
.in(SubUserSalaryDetail::getUserId, userIds)
|
||||
.between(SubUserSalaryDetail::getReportDate, start, end)
|
||||
.list();
|
||||
QueryWrapper<SubUserSalaryDetail> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.select("user_id", "SUM(work_hour) as workHour", "SUM(total_salary) as totalSalary","max(create_time) as createTime")
|
||||
.eq("project_id", dto.getProjectId())
|
||||
.between("report_date", start, end)
|
||||
.eq(dto.getTeamId()!=null,"team_id", dto.getTeamId())
|
||||
.like(StringUtils.isNotBlank(dto.getUserName()),"user_name", dto.getUserName())
|
||||
.groupBy("user_id");
|
||||
|
||||
|
||||
Page<SubUserSalaryDetail> result = this.page(pageQuery.build(), queryWrapper);
|
||||
List<SubUserSalaryDetail> records = result.getRecords();
|
||||
List<Long> userIds = records.stream().map(SubUserSalaryDetail::getUserId).toList();
|
||||
Map<Long, SubConstructionUser> collect = new HashMap<>();
|
||||
if(CollectionUtil.isNotEmpty(userIds)){
|
||||
List<SubConstructionUser> subConstructionUsers = constructionUserService.list(Wrappers.lambdaQuery(SubConstructionUser.class)
|
||||
.in(SubConstructionUser::getSysUserId, userIds));
|
||||
collect = subConstructionUsers.stream().collect(Collectors.toMap(SubConstructionUser::getSysUserId, vo -> vo));
|
||||
}
|
||||
ArrayList<SubConstructionUserSalaryVo> vos = new ArrayList<>();
|
||||
for (SubConstructionUserVo row : rows) {
|
||||
for (SubUserSalaryDetail detail : records) {
|
||||
SubConstructionUserSalaryVo vo = new SubConstructionUserSalaryVo();
|
||||
BeanUtil.copyProperties(row,vo);
|
||||
// vo.setSfzNumber(idCardEncryptorUtil.decrypt(vo.getSfzNumber()));
|
||||
vo.setId(detail.getId());
|
||||
vo.setTime(dto.getTime());
|
||||
// 获取工资
|
||||
BigDecimal salary = row.getSalary();
|
||||
if (salary == null || salary.compareTo(BigDecimal.ZERO) == 0) {
|
||||
String typeOfWork = row.getTypeOfWork();
|
||||
String wageMeasureUnit = row.getWageMeasureUnit();
|
||||
if (StringUtils.isNotEmpty(typeOfWork) && StringUtils.isNotEmpty(wageMeasureUnit)) {
|
||||
BusWorkWage workWage = workWageService.lambdaQuery()
|
||||
.eq(BusWorkWage::getProjectId, row.getProjectId())
|
||||
.eq(BusWorkWage::getWorkType, typeOfWork)
|
||||
.eq(BusWorkWage::getWageMeasureUnit, wageMeasureUnit)
|
||||
.one();
|
||||
if (workWage != null) {
|
||||
salary = workWage.getWage();
|
||||
} else {
|
||||
salary = BigDecimal.ZERO;
|
||||
}
|
||||
} else {
|
||||
salary = BigDecimal.ZERO;
|
||||
vo.setTotalSalary(detail.getTotalSalary());
|
||||
vo.setCreateTime(DateUtil.format(detail.getCreateTime(), "yyyy-MM-dd HH:mm:ss"));
|
||||
vo.setWorkDay(detail.getWorkHour());
|
||||
vo.setProjectId(dto.getProjectId());
|
||||
vo.setUserId(detail.getUserId());
|
||||
SubConstructionUser constructionUser = collect.get(detail.getUserId());
|
||||
if(constructionUser != null){
|
||||
if(constructionUser.getSfzNumber() != null){
|
||||
vo.setSfzNumber(idCardEncryptorUtil.decrypt(constructionUser.getSfzNumber()));
|
||||
}
|
||||
vo.setUserName(constructionUser.getUserName());
|
||||
vo.setYhkNumber(constructionUser.getYhkNumber());
|
||||
vo.setYhkOpeningBank(constructionUser.getYhkOpeningBank());
|
||||
}
|
||||
vo.setSalary(salary);
|
||||
vo.setTotalSalary(salaryDetailList.stream().filter(detail -> detail.getUserId().equals(row.getSysUserId()))
|
||||
.map(SubUserSalaryDetail::getDailySalary)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add)
|
||||
);
|
||||
List<BusAttendance> list = attendanceList.stream().filter(attendance -> attendance.getUserId().equals(row.getSysUserId())
|
||||
&& Arrays.asList("1", "2", "3", "5").contains(attendance.getClockStatus())
|
||||
).toList();
|
||||
vo.setWorkDay(list.size()*0.5);
|
||||
|
||||
vos.add(vo);
|
||||
}
|
||||
|
||||
return new TableDataInfo<>(vos,subConstructionUserVoTableDataInfo.getTotal());
|
||||
return new TableDataInfo<>(vos,result.getTotal());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -342,27 +328,34 @@ public class SubUserSalaryDetailServiceImpl extends ServiceImpl<SubUserSalaryDet
|
||||
if (project == null) {
|
||||
throw new ServiceException("项目不存在");
|
||||
}
|
||||
|
||||
List<SubConstructionUser> userList = constructionUserService.lambdaQuery()
|
||||
.eq(SubConstructionUser::getProjectId, dto.getProjectId())
|
||||
.eq(dto.getTeamId() != null, SubConstructionUser::getTeamId, dto.getTeamId())
|
||||
.like(StringUtils.isNotBlank(dto.getUserName()), SubConstructionUser::getUserName, dto.getUserName())
|
||||
.list();
|
||||
|
||||
if (userList.isEmpty()) {
|
||||
throw new ServiceException("暂无数据");
|
||||
}
|
||||
|
||||
// 2. 解析年月和查询薪资明细
|
||||
YearMonth yearMonth = YearMonth.parse(dto.getTime(), DateTimeFormatter.ofPattern("yyyy-MM"));
|
||||
LocalDate start = yearMonth.atDay(1);
|
||||
LocalDate end = yearMonth.atEndOfMonth();
|
||||
|
||||
List<Long> userIds = userList.stream().map(SubConstructionUser::getSysUserId).toList();
|
||||
List<SubUserSalaryDetail> salaryDetails = this.lambdaQuery()
|
||||
.eq(SubUserSalaryDetail::getProjectId, dto.getProjectId())
|
||||
.in(SubUserSalaryDetail::getUserId, userIds)
|
||||
.between(SubUserSalaryDetail::getReportDate, start, end)
|
||||
QueryWrapper<SubUserSalaryDetail> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.select("user_id", "SUM(work_hour) as workHour", "SUM(total_salary) as totalSalary","max(create_time) as createTime")
|
||||
.eq("project_id", dto.getProjectId())
|
||||
.between("report_date", start, end)
|
||||
.eq(dto.getUserId()!=null,"user_id", dto.getUserId())
|
||||
.eq(dto.getTeamId()!=null,"team_id", dto.getTeamId())
|
||||
.like(StringUtils.isNotBlank(dto.getUserName()),"user_name", dto.getUserName())
|
||||
.groupBy("user_id");
|
||||
|
||||
List<SubUserSalaryDetail> salaryDetailsList= baseMapper.selectList(queryWrapper);
|
||||
|
||||
if (salaryDetailsList.isEmpty()) {
|
||||
throw new ServiceException("暂无数据");
|
||||
}
|
||||
Map<Long, SubUserSalaryDetail> map = salaryDetailsList.stream().collect(Collectors.toMap(SubUserSalaryDetail::getUserId, vo -> vo));
|
||||
|
||||
|
||||
List<Long> userIds = salaryDetailsList.stream().map(SubUserSalaryDetail::getUserId).toList();
|
||||
|
||||
|
||||
|
||||
List<SubConstructionUser> userList = constructionUserService.lambdaQuery()
|
||||
.in(SubConstructionUser::getSysUserId, userIds)
|
||||
.list();
|
||||
|
||||
// 3. 设置响应头(下载文件配置)
|
||||
@ -465,7 +458,7 @@ public class SubUserSalaryDetailServiceImpl extends ServiceImpl<SubUserSalaryDet
|
||||
.build();
|
||||
|
||||
// 填充列表数据和汇总数据
|
||||
excelWriter.fill(getTeamData(userList, salaryDetails, teamId), fillConfig, writeSheet);
|
||||
excelWriter.fill(getTeamData(userList, map, teamId), fillConfig, writeSheet);
|
||||
excelWriter.fill(getTeamSummary(project, teamNameMap.get(teamId), yearMonth), writeSheet);
|
||||
}
|
||||
|
||||
@ -474,7 +467,7 @@ public class SubUserSalaryDetailServiceImpl extends ServiceImpl<SubUserSalaryDet
|
||||
WriteSheet noTeamSheet = EasyExcel.writerSheet("无班组").build();
|
||||
FillConfig fillConfig = FillConfig.builder().forceNewRow(true).build();
|
||||
|
||||
excelWriter.fill(getTeamData(userList, salaryDetails, null), fillConfig, noTeamSheet);
|
||||
excelWriter.fill(getTeamData(userList, map, null), fillConfig, noTeamSheet);
|
||||
excelWriter.fill(getTeamSummary(project, null, yearMonth), noTeamSheet);
|
||||
}
|
||||
}
|
||||
@ -498,7 +491,7 @@ public class SubUserSalaryDetailServiceImpl extends ServiceImpl<SubUserSalaryDet
|
||||
}
|
||||
|
||||
private List<SubConstructionUserSalaryVo> getTeamData(List<SubConstructionUser> rows,
|
||||
List<SubUserSalaryDetail> salaryDetailList,Long teamId){
|
||||
Map<Long, SubUserSalaryDetail> map,Long teamId){
|
||||
List<SubConstructionUser> list1 ;
|
||||
if(teamId == null){
|
||||
list1 = rows.stream().filter(row -> row.getTeamId() == null && Arrays.asList("1","2").contains(row.getExitStatus())).toList();
|
||||
@ -511,10 +504,8 @@ public class SubUserSalaryDetailServiceImpl extends ServiceImpl<SubUserSalaryDet
|
||||
SubConstructionUserSalaryVo vo = new SubConstructionUserSalaryVo();
|
||||
BeanUtil.copyProperties(row,vo);
|
||||
vo.setOrder(i);
|
||||
vo.setTotalSalary(salaryDetailList.stream().filter(detail -> detail.getUserId().equals(row.getSysUserId()))
|
||||
.map(SubUserSalaryDetail::getDailySalary)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add)
|
||||
);
|
||||
SubUserSalaryDetail detail1 = map.get(row.getSysUserId());
|
||||
vo.setTotalSalary(detail1.getTotalSalary());
|
||||
vos.add(vo);
|
||||
i++;
|
||||
}
|
||||
@ -532,4 +523,109 @@ public class SubUserSalaryDetailServiceImpl extends ServiceImpl<SubUserSalaryDet
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void importData(MultipartFile file, String month) {
|
||||
// 1. 校验文件合法性
|
||||
if (file.isEmpty()) {
|
||||
throw new IllegalArgumentException("上传的Excel文件不能为空!");
|
||||
}
|
||||
// 校验文件格式(仅允许xlsx/xls)
|
||||
String originalFilename = file.getOriginalFilename();
|
||||
if (originalFilename == null || !(originalFilename.endsWith(".xlsx"))) {
|
||||
throw new IllegalArgumentException("仅支持上传Excel文件(.xlsx 格式)!");
|
||||
}
|
||||
List<DynamicSalaryData> dataList;
|
||||
try {
|
||||
// 2. 将 MultipartFile 转为 InputStream,传给工具类解析
|
||||
dataList = SalaryExcelReader.readAllAttendanceByStream(file.getInputStream(), month);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Excel流解析失败:" + e.getMessage(), e);
|
||||
}
|
||||
if(CollUtil.isEmpty(dataList)){
|
||||
throw new ServiceException("未读取到数据");
|
||||
}
|
||||
|
||||
Map<String, Map<String, Double>> dataMap = dataList.stream().collect(Collectors.toMap(vo -> idCardEncryptorUtil.encrypt(vo.getIdCard()), DynamicSalaryData::getDailyAttendance));
|
||||
Set<String> cards = dataMap.keySet();
|
||||
|
||||
YearMonth parse = YearMonth.parse(month, DateTimeFormatter.ofPattern("yyyy-MM"));
|
||||
LocalDate start = parse.atDay(1);
|
||||
LocalDate end = parse.atEndOfMonth();
|
||||
|
||||
//人员数据
|
||||
List<SubConstructionUser> list = constructionUserService.list(Wrappers.<SubConstructionUser>lambdaQuery()
|
||||
.in(SubConstructionUser::getSfzNumber, cards));
|
||||
|
||||
List<Long> userIds = list.stream().map(SubConstructionUser::getSysUserId).toList();
|
||||
//考勤数据
|
||||
List<BusAttendance> attendanceList = attendanceService
|
||||
.list(Wrappers.<BusAttendance>lambdaQuery()
|
||||
.in(BusAttendance::getUserId, userIds)
|
||||
.between(BusAttendance::getClockDate, start, end)
|
||||
);
|
||||
// 将 attendanceList 转换为 Map<String, BigDecimal> 格式
|
||||
Map<String, BigDecimal> attendanceSalaryMap = new HashMap<>();
|
||||
|
||||
// 按 userId+日期 分组,只保留每组的第一条记录的salary
|
||||
attendanceList.stream()
|
||||
.collect(Collectors.groupingBy(
|
||||
attendance -> attendance.getUserId() + "_" + attendance.getClockDate().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")),
|
||||
Collectors.toList()
|
||||
))
|
||||
.forEach((key, dateList) -> attendanceSalaryMap.put(key, dateList.getFirst().getSalary()));
|
||||
|
||||
|
||||
List<SubUserSalaryDetail> addList = new ArrayList<>();
|
||||
for(SubConstructionUser constructionUser : list){
|
||||
Map<String, Double> stringIntegerMap = dataMap.get(constructionUser.getSfzNumber());
|
||||
if(stringIntegerMap != null){
|
||||
for(Map.Entry<String, Double> entry : stringIntegerMap.entrySet()){
|
||||
String key = entry.getKey();
|
||||
Double value = entry.getValue();
|
||||
|
||||
SubUserSalaryDetail subUserSalaryDetail = new SubUserSalaryDetail();
|
||||
subUserSalaryDetail.setProjectId(constructionUser.getProjectId());
|
||||
subUserSalaryDetail.setTeamId(constructionUser.getTeamId());
|
||||
subUserSalaryDetail.setUserId(constructionUser.getSysUserId());
|
||||
subUserSalaryDetail.setUserName(constructionUser.getUserName());
|
||||
subUserSalaryDetail.setReportDate(LocalDate.parse(key, DateTimeFormatter.ofPattern("yyyy-MM-dd")));
|
||||
subUserSalaryDetail.setWorkHour(value);
|
||||
|
||||
String attendanceKey = constructionUser.getSysUserId()+"_"+key;
|
||||
BigDecimal bigDecimal = attendanceSalaryMap.get(attendanceKey);
|
||||
if(bigDecimal == null){
|
||||
bigDecimal = BigDecimal.ZERO;
|
||||
}
|
||||
subUserSalaryDetail.setDailySalary(bigDecimal);
|
||||
subUserSalaryDetail.setTotalSalary(bigDecimal.multiply(new BigDecimal(value.toString())));
|
||||
addList.add(subUserSalaryDetail);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
baseMapper.delete(Wrappers.<SubUserSalaryDetail>lambdaQuery()
|
||||
.in(SubUserSalaryDetail::getUserId, userIds)
|
||||
.between(SubUserSalaryDetail::getReportDate, start, end)
|
||||
);
|
||||
baseMapper.insertBatch(addList);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<SubUserSalaryDetail> detailList(SubConstructionUserSalaryDto dto) {
|
||||
|
||||
LambdaQueryWrapper<SubUserSalaryDetail> wrapper = new LambdaQueryWrapper<>();
|
||||
|
||||
YearMonth parse = YearMonth.parse(dto.getTime(), DateTimeFormatter.ofPattern("yyyy-MM"));
|
||||
LocalDate start = parse.atDay(1);
|
||||
LocalDate end = parse.atEndOfMonth();
|
||||
|
||||
wrapper.eq(SubUserSalaryDetail::getProjectId, dto.getProjectId());
|
||||
wrapper.eq( dto.getTeamId()!=null,SubUserSalaryDetail::getTeamId, dto.getTeamId());
|
||||
wrapper.eq(dto.getUserId()!=null,SubUserSalaryDetail::getUserId, dto.getUserId());
|
||||
wrapper.between(SubUserSalaryDetail::getReportDate,start, end);
|
||||
|
||||
return this.list(wrapper);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package org.dromara.job.attendance;
|
||||
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import com.aizuda.snailjob.client.job.core.annotation.JobExecutor;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
@ -12,10 +13,7 @@ import org.dromara.common.core.utils.DateUtils;
|
||||
import org.dromara.contractor.domain.SubConstructionUser;
|
||||
import org.dromara.contractor.service.ISubConstructionUserService;
|
||||
import org.dromara.project.constant.BusProjectConstant;
|
||||
import org.dromara.project.domain.BusAttendance;
|
||||
import org.dromara.project.domain.BusAttendanceRule;
|
||||
import org.dromara.project.domain.BusProject;
|
||||
import org.dromara.project.domain.BusUserProjectRelevancy;
|
||||
import org.dromara.project.domain.*;
|
||||
import org.dromara.project.domain.enums.BusAttendanceClockStatusEnum;
|
||||
import org.dromara.project.domain.enums.BusAttendanceCommuterEnum;
|
||||
import org.dromara.project.service.*;
|
||||
@ -25,11 +23,13 @@ import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.DayOfWeek;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.util.*;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@ -53,6 +53,11 @@ public class AttendanceJob {
|
||||
@Resource
|
||||
private ISubConstructionUserService constructionUserService;
|
||||
|
||||
@Resource
|
||||
private IBusWorkWageService workWageService;
|
||||
|
||||
//0系统管理员 1普通人员 2项目管理员 3分包 只有1才生成缺卡记录 无需打卡人员
|
||||
private static final List<String> noClockUserTypes = Arrays.asList("0","2");
|
||||
|
||||
// @Scheduled(cron = "0 0/10 * * * ?")
|
||||
@JobExecutor(name = "clockInMiss")
|
||||
@ -126,7 +131,7 @@ public class AttendanceJob {
|
||||
|
||||
for (BusUserProjectRelevancy relevancy : relevancyList) {
|
||||
|
||||
if (attendanceUserIds.contains(relevancy.getUserId()) || "0".equals(relevancy.getUserType())) {
|
||||
if (attendanceUserIds.contains(relevancy.getUserId()) || noClockUserTypes.contains(relevancy.getUserType())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -156,6 +161,7 @@ public class AttendanceJob {
|
||||
}else {
|
||||
busAttendance.setClockStatus(BusAttendanceClockStatusEnum.UNCLOCK.getValue());
|
||||
}
|
||||
busAttendance.setSalary(computeSalary(constructionUser, null));
|
||||
|
||||
missList.add(busAttendance);
|
||||
}
|
||||
@ -204,7 +210,9 @@ public class AttendanceJob {
|
||||
//管理员关联多个项目,需要记录是否已生成缺卡记录
|
||||
HashSet<Long> manageUserIds = new HashSet<>();
|
||||
|
||||
|
||||
List<BusAttendance> missList = new ArrayList<>();
|
||||
|
||||
for (BusAttendanceRule rule : list) {
|
||||
|
||||
LocalTime clockOutTime = rule.getClockOutTime();
|
||||
@ -235,16 +243,22 @@ public class AttendanceJob {
|
||||
List<BusUserProjectRelevancy> relevancyList = userProjectRelevancyService.list(Wrappers.lambdaQuery(BusUserProjectRelevancy.class)
|
||||
.eq(BusUserProjectRelevancy::getProjectId, rule.getProjectId()));
|
||||
|
||||
//查询当天已打下班卡人员
|
||||
List<BusAttendance> attendanceList = attendanceService.list(Wrappers.lambdaQuery(BusAttendance.class)
|
||||
//查询当天打卡人员
|
||||
List<BusAttendance> allAttendanceList = attendanceService.list(Wrappers.lambdaQuery(BusAttendance.class)
|
||||
.eq(BusAttendance::getClockDate, date)
|
||||
.eq(BusAttendance::getClockType, BusAttendanceCommuterEnum.CLOCKOUT.getValue())
|
||||
);
|
||||
|
||||
List<BusAttendance> inAttendanceList = allAttendanceList.stream()
|
||||
.filter(attendance -> BusAttendanceCommuterEnum.CLOCKIN.getValue().equals(attendance.getClockType())).toList();
|
||||
|
||||
List<BusAttendance> attendanceList = allAttendanceList.stream()
|
||||
.filter(attendance -> BusAttendanceCommuterEnum.CLOCKOUT.getValue().equals(attendance.getClockType())).toList();
|
||||
|
||||
List<Long> attendanceUserIds = attendanceList.stream().map(BusAttendance::getUserId).toList();
|
||||
|
||||
|
||||
for (BusUserProjectRelevancy relevancy : relevancyList) {
|
||||
if (attendanceUserIds.contains(relevancy.getUserId()) || "0".equals(relevancy.getUserType())) {
|
||||
if (attendanceUserIds.contains(relevancy.getUserId()) || noClockUserTypes.contains(relevancy.getUserType())) {
|
||||
continue;
|
||||
}
|
||||
BusAttendance busAttendance = new BusAttendance();
|
||||
@ -263,8 +277,6 @@ public class AttendanceJob {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
|
||||
busAttendance.setUserId(relevancy.getUserId());
|
||||
busAttendance.setUserName(constructionUser.getUserName());
|
||||
|
||||
@ -276,7 +288,8 @@ public class AttendanceJob {
|
||||
}else {
|
||||
busAttendance.setClockStatus(BusAttendanceClockStatusEnum.UNCLOCK.getValue());
|
||||
}
|
||||
|
||||
List<BusAttendance> list1 = inAttendanceList.stream().filter(vo -> relevancy.getUserId().equals(vo.getUserId())).toList();
|
||||
busAttendance.setSalary(computeSalary(constructionUser, list1));
|
||||
missList.add(busAttendance);
|
||||
}
|
||||
|
||||
@ -313,4 +326,28 @@ public class AttendanceJob {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算工资
|
||||
*/
|
||||
public BigDecimal computeSalary(SubConstructionUser constructionUser, List<BusAttendance> inAttendances) {
|
||||
if (CollectionUtil.isNotEmpty(inAttendances)) {
|
||||
BusAttendance first = inAttendances.getFirst();
|
||||
if (first.getSalary().compareTo(BigDecimal.ZERO) > 0) {
|
||||
return first.getSalary();
|
||||
}
|
||||
}
|
||||
|
||||
if (constructionUser.getSalary() != null && constructionUser.getSalary().compareTo(BigDecimal.ZERO) > 0) {
|
||||
return constructionUser.getSalary();
|
||||
}
|
||||
|
||||
String typeOfWork = constructionUser.getTypeOfWork();
|
||||
BusWorkWage workWageByWorkType = workWageService.getWorkWageByWorkType(typeOfWork);
|
||||
if (workWageByWorkType == null || workWageByWorkType.getWage() == null) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
return workWageByWorkType.getWage();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
@ -102,4 +103,9 @@ public class BusAttendance extends BaseEntity {
|
||||
* 处理 0-未处理,1-已处理
|
||||
*/
|
||||
private String handle;
|
||||
|
||||
/**
|
||||
* 薪水
|
||||
*/
|
||||
private BigDecimal salary;
|
||||
}
|
||||
|
||||
@ -30,4 +30,9 @@ public class AttendanceUserDataDetailVo {
|
||||
* 迟到或早退的分钟
|
||||
*/
|
||||
private Integer minuteCount;
|
||||
|
||||
/**
|
||||
* 出勤天数
|
||||
*/
|
||||
private Double workDay;
|
||||
}
|
||||
|
||||
@ -95,4 +95,10 @@ public interface IBusWorkWageService extends IService<BusWorkWage> {
|
||||
* @return 工种薪水分页对象视图
|
||||
*/
|
||||
Page<BusWorkWageVo> getVoPage(Page<BusWorkWage> workWagePage);
|
||||
|
||||
|
||||
/**
|
||||
* 根据工种获取薪水设置
|
||||
*/
|
||||
BusWorkWage getWorkWageByWorkType(String workType);
|
||||
}
|
||||
|
||||
@ -54,6 +54,7 @@ import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.*;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
@ -104,6 +105,8 @@ public class BusAttendanceServiceImpl extends ServiceImpl<BusAttendanceMapper, B
|
||||
|
||||
private final ISubUserSalaryDetailService userSalaryDetailService;
|
||||
|
||||
private final IBusWorkWageService workWageService;
|
||||
|
||||
@Resource
|
||||
private IdCardEncryptorUtil idCardEncryptorUtil;
|
||||
|
||||
@ -149,7 +152,7 @@ public class BusAttendanceServiceImpl extends ServiceImpl<BusAttendanceMapper, B
|
||||
Long userId = req.getUserId();
|
||||
String clockMonth = req.getClockMonth();
|
||||
SubConstructionUser constructionUser = constructionUserService.getById(userId);
|
||||
if ( constructionUser == null) {
|
||||
if (constructionUser == null) {
|
||||
throw new ServiceException("施工人员信息不存在", HttpStatus.NOT_FOUND);
|
||||
}
|
||||
// 解析月份
|
||||
@ -372,45 +375,43 @@ public class BusAttendanceServiceImpl extends ServiceImpl<BusAttendanceMapper, B
|
||||
BusAttendanceCommuterEnum.CLOCKOUT.getValue().equals(attendance.getClockType())).toList();
|
||||
|
||||
if (clockTypeByTime == 1 && CollectionUtils.isEmpty(inAttendances)) {
|
||||
BusAttendance attendance = new BusAttendance();
|
||||
// 上班打卡
|
||||
attendance.setClockType(BusAttendanceCommuterEnum.CLOCKIN.getValue());
|
||||
//打卡时间
|
||||
attendance.setRuleTime(busAttendanceRuleVo.getClockInTime());
|
||||
// 判断是否为迟到
|
||||
if (isLate(now, busAttendanceRuleVo)) {
|
||||
attendance.setClockStatus(BusAttendanceClockStatusEnum.LATE.getValue());
|
||||
attendance.setMinuteCount(getMinutesDifference(now, busAttendanceRuleVo.getClockInTime()));
|
||||
} else {
|
||||
attendance.setClockStatus(BusAttendanceClockStatusEnum.NORMAL.getValue());
|
||||
BusAttendance attendance = new BusAttendance();
|
||||
// 上班打卡
|
||||
attendance.setClockType(BusAttendanceCommuterEnum.CLOCKIN.getValue());
|
||||
//打卡时间
|
||||
attendance.setRuleTime(busAttendanceRuleVo.getClockInTime());
|
||||
// 判断是否为迟到
|
||||
if (isLate(now, busAttendanceRuleVo)) {
|
||||
attendance.setClockStatus(BusAttendanceClockStatusEnum.LATE.getValue());
|
||||
attendance.setMinuteCount(getMinutesDifference(now, busAttendanceRuleVo.getClockInTime()));
|
||||
} else {
|
||||
attendance.setClockStatus(BusAttendanceClockStatusEnum.NORMAL.getValue());
|
||||
}
|
||||
// 填充信息
|
||||
attendance.setUserId(userId);
|
||||
attendance.setProjectId(req.getProjectId());
|
||||
attendance.setClockDate(localDate);
|
||||
attendance.setClockTime(now);
|
||||
attendance.setUserName(constructionUser.getUserName());
|
||||
// 记录打卡坐标
|
||||
attendance.setLat(req.getLat());
|
||||
attendance.setLng(req.getLng());
|
||||
attendance.setClockLocation(JSTUtil.getLocationName(req.getLat(), req.getLng()));
|
||||
// 上传人脸照
|
||||
SysOssVo upload = ossService.upload(file);
|
||||
attendance.setFacePic(upload.getOssId().toString());
|
||||
CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
chatServerHandler.sendSystemMessageToUser(userId, "打卡成功", "1");
|
||||
} catch (Exception e) {
|
||||
log.error("异步发送系统消息失败,用户ID: {}, 消息: {}", userId, "打卡成功", e);
|
||||
}
|
||||
// 填充信息
|
||||
attendance.setUserId(userId);
|
||||
attendance.setProjectId(req.getProjectId());
|
||||
attendance.setClockDate(localDate);
|
||||
attendance.setClockTime(now);
|
||||
attendance.setUserName(constructionUser.getUserName());
|
||||
// 记录打卡坐标
|
||||
attendance.setLat(req.getLat());
|
||||
attendance.setLng(req.getLng());
|
||||
attendance.setClockLocation(JSTUtil.getLocationName(req.getLat(), req.getLng()));
|
||||
// 上传人脸照
|
||||
SysOssVo upload = ossService.upload(file);
|
||||
attendance.setFacePic(upload.getOssId().toString());
|
||||
CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
chatServerHandler.sendSystemMessageToUser(userId, "打卡成功", "1");
|
||||
} catch (Exception e) {
|
||||
log.error("异步发送系统消息失败,用户ID: {}, 消息: {}", userId, "打卡成功", e);
|
||||
}
|
||||
});
|
||||
});
|
||||
//计算工资
|
||||
attendance.setSalary(computeSalary(constructionUser, inAttendances));
|
||||
return this.save(attendance);
|
||||
|
||||
boolean save = this.save(attendance);
|
||||
//插入工资
|
||||
userSalaryDetailService.insertByAttendance(userId, attendance.getClockDate());
|
||||
return save;
|
||||
|
||||
} else if (clockTypeByTime == 2 || CollectionUtils.isEmpty(outAttendances)) {
|
||||
} else if (clockTypeByTime == 2 || CollectionUtils.isNotEmpty(inAttendances)) {
|
||||
|
||||
if (CollectionUtil.isNotEmpty(outAttendances)) {
|
||||
BusAttendance busAttendance = outAttendances.getFirst();
|
||||
@ -426,7 +427,8 @@ public class BusAttendanceServiceImpl extends ServiceImpl<BusAttendanceMapper, B
|
||||
} else {
|
||||
busAttendance.setClockStatus(BusAttendanceClockStatusEnum.NORMAL.getValue());
|
||||
busAttendance.setMinuteCount(0);
|
||||
}updateById(busAttendance);
|
||||
}
|
||||
updateById(busAttendance);
|
||||
} else {
|
||||
BusAttendance attendance = new BusAttendance();
|
||||
// 下班打卡
|
||||
@ -460,10 +462,9 @@ public class BusAttendanceServiceImpl extends ServiceImpl<BusAttendanceMapper, B
|
||||
log.error("异步发送系统消息失败,用户ID: {}, 消息: {}", userId, "打卡成功", e);
|
||||
}
|
||||
});
|
||||
boolean save = this.save(attendance);
|
||||
//插入工资
|
||||
userSalaryDetailService.insertByAttendance(userId, attendance.getClockDate());
|
||||
return save;
|
||||
//计算工资
|
||||
attendance.setSalary(computeSalary(constructionUser, inAttendances));
|
||||
return this.save(attendance);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -503,6 +504,31 @@ public class BusAttendanceServiceImpl extends ServiceImpl<BusAttendanceMapper, B
|
||||
return attendedUserIds;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 计算工资
|
||||
*/
|
||||
public BigDecimal computeSalary(SubConstructionUser constructionUser, List<BusAttendance> inAttendances) {
|
||||
if (CollectionUtil.isNotEmpty(inAttendances)) {
|
||||
BusAttendance first = inAttendances.getFirst();
|
||||
if (first.getSalary().compareTo(BigDecimal.ZERO) > 0) {
|
||||
return first.getSalary();
|
||||
}
|
||||
}
|
||||
|
||||
if (constructionUser.getSalary() != null && constructionUser.getSalary().compareTo(BigDecimal.ZERO) > 0) {
|
||||
return constructionUser.getSalary();
|
||||
}
|
||||
|
||||
String typeOfWork = constructionUser.getTypeOfWork();
|
||||
BusWorkWage workWageByWorkType = workWageService.getWorkWageByWorkType(typeOfWork);
|
||||
if (workWageByWorkType == null || workWageByWorkType.getWage() == null) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
return workWageByWorkType.getWage();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算实际打卡时间与规定时间的分钟差
|
||||
*
|
||||
@ -1003,11 +1029,21 @@ public class BusAttendanceServiceImpl extends ServiceImpl<BusAttendanceMapper, B
|
||||
.orderByAsc(BusAttendance::getClockDate));
|
||||
|
||||
// 处理正常出勤记录(去重日期)
|
||||
List<AttendanceUserDataDetailVo> workList = attendanceList.stream()
|
||||
|
||||
// 过滤有效考勤记录并按日期分组
|
||||
Map<LocalDate, List<BusAttendanceVo>> dateAttendanceMap = attendanceList.stream()
|
||||
.filter(a -> validStatusList.contains(a.getClockStatus()))
|
||||
.map(this::convertToDetailVo)
|
||||
.distinct() // 去除重复日期
|
||||
.collect(Collectors.toList());
|
||||
.collect(Collectors.groupingBy(BusAttendanceVo::getClockDate));
|
||||
List<AttendanceUserDataDetailVo> workList = new ArrayList<>();
|
||||
for (Map.Entry<LocalDate, List<BusAttendanceVo>> entry : dateAttendanceMap.entrySet()) {
|
||||
LocalDate key = entry.getKey();
|
||||
List<BusAttendanceVo> value = entry.getValue();
|
||||
AttendanceUserDataDetailVo detailVo = new AttendanceUserDataDetailVo();
|
||||
detailVo.setClockDate(key);
|
||||
detailVo.setWeek(key.getDayOfWeek().getValue());
|
||||
detailVo.setWorkDay(value.size()*0.5);
|
||||
workList.add(detailVo);
|
||||
}
|
||||
vo.setWork(workList);
|
||||
|
||||
// 处理迟到记录
|
||||
@ -1185,10 +1221,10 @@ public class BusAttendanceServiceImpl extends ServiceImpl<BusAttendanceMapper, B
|
||||
List<SubConstructionUser> users = entry.getValue();
|
||||
SubConstructionUser constructionUser = users.getFirst();
|
||||
String teamName = constructionUser.getTeamName();
|
||||
if(teamId == 0){
|
||||
if (teamId == 0) {
|
||||
teamName = "无班组";
|
||||
}
|
||||
if(StringUtils.isBlank(teamName)){
|
||||
if (StringUtils.isBlank(teamName)) {
|
||||
teamName = teamId.toString();
|
||||
}
|
||||
System.out.println("name:" + teamName);
|
||||
@ -1208,6 +1244,11 @@ public class BusAttendanceServiceImpl extends ServiceImpl<BusAttendanceMapper, B
|
||||
sheet.setColumnWidth(3 + daysInMonth + 1, 10 * 256); // 是否离场
|
||||
sheet.setColumnWidth(3 + daysInMonth + 2, 15 * 256); // 签字
|
||||
|
||||
// ==================== 创建样式 ====================
|
||||
CellStyle borderStyle = createBorderStyle(workbook);
|
||||
// CellStyle numberStyle = createNumberStyle(workbook);
|
||||
|
||||
|
||||
// ==================== 表头部分 ====================
|
||||
Row titleRow = sheet.createRow(0);
|
||||
Cell titleCell = titleRow.createCell(0);
|
||||
@ -1247,13 +1288,75 @@ public class BusAttendanceServiceImpl extends ServiceImpl<BusAttendanceMapper, B
|
||||
// ==================== 数据表头 ====================
|
||||
Row headerRow = sheet.createRow(2);
|
||||
writeHeaderRow(headerRow, daysInMonth);
|
||||
// 设置表头边框
|
||||
for (int i = 0; i < totalColumns; i++) {
|
||||
Cell cell = headerRow.getCell(i);
|
||||
if (cell != null) {
|
||||
cell.setCellStyle(borderStyle);
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 数据行 ====================
|
||||
CellStyle numberBorderStyle = createNumberBorderStyle(workbook);
|
||||
|
||||
int rowIndex = 3;
|
||||
for (SubConstructionUser user : users) {
|
||||
Row row = sheet.createRow(rowIndex++);
|
||||
writeDataRow(row, user, attendanceList, start, end, daysInMonth);
|
||||
writeDataRow(row, user, attendanceList, start, end, daysInMonth,borderStyle,numberBorderStyle);
|
||||
|
||||
// 设置数据行边框
|
||||
for (int i = 0; i < totalColumns; i++) {
|
||||
Cell cell = row.getCell(i);
|
||||
if (cell != null) {
|
||||
cell.setCellStyle(borderStyle);
|
||||
}
|
||||
}
|
||||
}
|
||||
// ==================== 设置月份天数列为数字格式 + 边框 ====================
|
||||
|
||||
|
||||
// 表头
|
||||
for (int i = 3; i < 3 + daysInMonth; i++) {
|
||||
Cell headerCell = headerRow.getCell(i);
|
||||
if (headerCell != null) {
|
||||
headerCell.setCellStyle(numberBorderStyle);
|
||||
}
|
||||
}
|
||||
|
||||
// 数据行
|
||||
for (int i = 3; i < 3 + daysInMonth; i++) {
|
||||
for (int j = 3; j < rowIndex; j++) {
|
||||
Row dataRow = sheet.getRow(j);
|
||||
if (dataRow != null) {
|
||||
Cell dataCell = dataRow.getCell(i);
|
||||
if (dataCell != null) {
|
||||
dataCell.setCellStyle(numberBorderStyle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// ==================== 设置合计列公式 ====================
|
||||
// for (int i = 4; i < rowIndex; i++) {
|
||||
// Row dataRow = sheet.getRow(i);
|
||||
// if (dataRow != null) {
|
||||
// Cell sumCell = dataRow.createCell(3 + daysInMonth);
|
||||
//
|
||||
// // 构建求和公式:从 D 列到第 (3 + daysInMonth) 列
|
||||
// String startCol = "D"; // 第 3 列
|
||||
// int endColIndex = 3 + daysInMonth; // 最后一列的索引(例如 33)
|
||||
// String endCol = getColumnName(endColIndex); // 将列号转为列名(如 AF)
|
||||
//
|
||||
// // 拼接公式:=SUM(D4:AF4)
|
||||
// StringBuilder formula = new StringBuilder();
|
||||
// formula.append("SUM(");
|
||||
// formula.append(startCol).append(i).append(":");
|
||||
// formula.append(endCol).append(i);
|
||||
// formula.append(")");
|
||||
//
|
||||
// sumCell.setCellFormula(formula.toString());
|
||||
// sumCell.setCellStyle(borderStyle);
|
||||
// }
|
||||
// }
|
||||
|
||||
// ==================== 表尾部分 ====================
|
||||
|
||||
@ -1304,6 +1407,42 @@ public class BusAttendanceServiceImpl extends ServiceImpl<BusAttendanceMapper, B
|
||||
}
|
||||
}
|
||||
|
||||
private String getColumnName(int columnNum) {
|
||||
StringBuilder columnName = new StringBuilder();
|
||||
while (columnNum > 0) {
|
||||
columnNum--;
|
||||
char c = (char) ('A' + (columnNum % 26));
|
||||
columnName.insert(0, c);
|
||||
columnNum /= 26;
|
||||
}
|
||||
return columnName.toString();
|
||||
}
|
||||
private CellStyle createBorderStyle(Workbook workbook) {
|
||||
CellStyle style = workbook.createCellStyle();
|
||||
style.setBorderTop(BorderStyle.THIN);
|
||||
style.setBorderBottom(BorderStyle.THIN);
|
||||
style.setBorderLeft(BorderStyle.THIN);
|
||||
style.setBorderRight(BorderStyle.THIN);
|
||||
return style;
|
||||
}
|
||||
private CellStyle createNumberBorderStyle(Workbook workbook) {
|
||||
CellStyle style = workbook.createCellStyle();
|
||||
DataFormat format = workbook.createDataFormat();
|
||||
style.setDataFormat(format.getFormat("0")); // 强制显示 0
|
||||
style.setBorderTop(BorderStyle.THIN);
|
||||
style.setBorderBottom(BorderStyle.THIN);
|
||||
style.setBorderLeft(BorderStyle.THIN);
|
||||
style.setBorderRight(BorderStyle.THIN);
|
||||
return style;
|
||||
}
|
||||
|
||||
private CellStyle createNumberStyle(Workbook workbook) {
|
||||
CellStyle style = workbook.createCellStyle();
|
||||
DataFormat format = workbook.createDataFormat();
|
||||
style.setDataFormat(format.getFormat("#"));
|
||||
return style;
|
||||
}
|
||||
|
||||
private CellStyle createProjectCellStyle(Workbook workbook) {
|
||||
CellStyle style = workbook.createCellStyle();
|
||||
Font font = workbook.createFont();
|
||||
@ -1339,20 +1478,29 @@ public class BusAttendanceServiceImpl extends ServiceImpl<BusAttendanceMapper, B
|
||||
row.createCell(2 + daysInMonth + 3).setCellValue("签字");
|
||||
}
|
||||
|
||||
private void writeDataRow(Row row, SubConstructionUser user, List<BusAttendance> attendanceList, LocalDate start, LocalDate end, int daysInMonth) {
|
||||
int index = row.getRowNum();
|
||||
private void writeDataRow(Row row, SubConstructionUser user, List<BusAttendance> attendanceList, LocalDate start, LocalDate end, int daysInMonth,CellStyle borderStyle,CellStyle numberBorderStyle) {
|
||||
int index = row.getRowNum()-2;
|
||||
row.createCell(0).setCellValue(index);
|
||||
row.createCell(1).setCellValue(user.getUserName());
|
||||
row.createCell(2).setCellValue(idCardEncryptorUtil.decrypt(user.getSfzNumber()));
|
||||
|
||||
for (int i = 1; i <= daysInMonth; i++) {
|
||||
LocalDate date = start.plusDays(i - 1);
|
||||
String value = getAttendanceValue(user.getSysUserId(), date, attendanceList);
|
||||
row.createCell(2 + i).setCellValue(value);
|
||||
Cell cell = row.createCell(2 + i);
|
||||
Double value = getAttendanceValue(user.getSysUserId(), date, attendanceList);
|
||||
cell.setCellValue(value);
|
||||
// 设置数字格式样式
|
||||
cell.setCellStyle(numberBorderStyle);
|
||||
}
|
||||
|
||||
double total = countAttendance(user.getSysUserId(), attendanceList, start, end);
|
||||
row.createCell(2 + daysInMonth + 1).setCellValue(total);
|
||||
// double total = countAttendance(user.getSysUserId(), attendanceList, start, end);
|
||||
// ==================== 在这里设置合计列公式 ====================
|
||||
Cell sumCell = row.createCell(2 + daysInMonth + 1);// 合计列
|
||||
String startCol = "D"; // 第3列
|
||||
String endCol = getColumnName(3 + daysInMonth); // 如 AF
|
||||
String formula = "SUM(" + startCol + (row.getRowNum() + 1) + ":" + endCol + (row.getRowNum() + 1) + ")";
|
||||
sumCell.setCellFormula(formula);
|
||||
sumCell.setCellStyle(borderStyle);
|
||||
|
||||
String leaveStatus = "0".equals(user.getExitStatus()) ? "未离场" : "已离场";
|
||||
row.createCell(2 + daysInMonth + 2).setCellValue(leaveStatus);
|
||||
@ -1360,14 +1508,14 @@ public class BusAttendanceServiceImpl extends ServiceImpl<BusAttendanceMapper, B
|
||||
row.createCell(2 + daysInMonth + 3).setCellValue("");
|
||||
}
|
||||
|
||||
private String getAttendanceValue(Long userId, LocalDate date, List<BusAttendance> attendanceList) {
|
||||
private Double getAttendanceValue(Long userId, LocalDate date, List<BusAttendance> attendanceList) {
|
||||
List<BusAttendance> validRecords = attendanceList.stream()
|
||||
.filter(a -> a.getUserId().equals(userId) && a.getClockDate().equals(date))
|
||||
.filter(a -> !a.getClockStatus().equals("4")) // 排除缺卡记录
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (validRecords.isEmpty()) {
|
||||
return "0"; // 无有效打卡
|
||||
return 0D; // 无有效打卡
|
||||
}
|
||||
|
||||
// 检查是否有有效出勤状态(1,2,3,5)
|
||||
@ -1375,16 +1523,16 @@ public class BusAttendanceServiceImpl extends ServiceImpl<BusAttendanceMapper, B
|
||||
.anyMatch(a -> Arrays.asList("1", "2", "3", "5").contains(a.getClockStatus()));
|
||||
|
||||
if (!hasValidStatus) {
|
||||
return "0"; // 状态无效,如补卡、其他异常
|
||||
return 0D; // 状态无效,如补卡、其他异常
|
||||
}
|
||||
|
||||
// 判断是否为半勤(仅一次有效打卡)
|
||||
if (validRecords.size() == 1) {
|
||||
return "0.5";
|
||||
return 0.5D;
|
||||
}
|
||||
|
||||
// 正常出勤(两次有效打卡)
|
||||
return "1";
|
||||
return 1D;
|
||||
}
|
||||
|
||||
private double countAttendance(Long userId, List<BusAttendance> attendanceList, LocalDate start, LocalDate end) {
|
||||
@ -1392,12 +1540,8 @@ public class BusAttendanceServiceImpl extends ServiceImpl<BusAttendanceMapper, B
|
||||
LocalDate current = start;
|
||||
|
||||
while (!current.isAfter(end)) {
|
||||
String value = getAttendanceValue(userId, current, attendanceList);
|
||||
if ("1".equals(value)) {
|
||||
total += 1.0;
|
||||
} else if ("0.5".equals(value)) {
|
||||
total += 0.5;
|
||||
}
|
||||
Double value = getAttendanceValue(userId, current, attendanceList);
|
||||
total+=value;
|
||||
current = current.plusDays(1);
|
||||
}
|
||||
|
||||
|
||||
@ -248,4 +248,11 @@ public class BusWorkWageServiceImpl extends ServiceImpl<BusWorkWageMapper, BusWo
|
||||
workWageVoPage.setRecords(workWageVoList);
|
||||
return workWageVoPage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BusWorkWage getWorkWageByWorkType(String workType) {
|
||||
|
||||
return baseMapper.selectOne(new LambdaQueryWrapper<BusWorkWage>().eq(BusWorkWage::getWorkType, workType)
|
||||
.last("limit 1"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user