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.dromara.contractor.service.ISubUserSalaryDetailService;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -85,4 +86,16 @@ public class SubUserSalaryDetailController extends BaseController {
|
|||||||
subUserSalaryDetailService.export(response, dto);
|
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 String remark;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工时
|
||||||
|
*/
|
||||||
|
private Double workHour;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当日总工资
|
||||||
|
*/
|
||||||
|
private BigDecimal totalSalary;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,4 +28,10 @@ public class SubConstructionUserSalaryDto {
|
|||||||
*/
|
*/
|
||||||
@NotBlank(message = "时间不能为空")
|
@NotBlank(message = "时间不能为空")
|
||||||
private String time;
|
private String time;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户
|
||||||
|
*/
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -36,6 +36,10 @@ public class SubConstructionUserSalaryVo implements Serializable {
|
|||||||
*/
|
*/
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户id
|
||||||
|
*/
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 人员姓名
|
* 人员姓名
|
||||||
@ -83,7 +87,7 @@ public class SubConstructionUserSalaryVo implements Serializable {
|
|||||||
private BigDecimal salary;
|
private BigDecimal salary;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 时间
|
* 发放时间
|
||||||
*/
|
*/
|
||||||
private String time;
|
private String time;
|
||||||
|
|
||||||
@ -91,4 +95,9 @@ public class SubConstructionUserSalaryVo implements Serializable {
|
|||||||
|
|
||||||
private String blank;
|
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.usersalarydetail.SubUserSalaryDetailVo;
|
||||||
import org.dromara.contractor.domain.vo.usersalaryperiod.SubConstructionUserSalaryVo;
|
import org.dromara.contractor.domain.vo.usersalaryperiod.SubConstructionUserSalaryVo;
|
||||||
import org.springframework.validation.annotation.Validated;
|
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.io.IOException;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
@ -95,6 +97,14 @@ public interface ISubUserSalaryDetailService extends IService<SubUserSalaryDetai
|
|||||||
*/
|
*/
|
||||||
void export(HttpServletResponse response, SubConstructionUserSalaryDto dto) throws IOException;
|
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.setGender(wordsResult.getGender() != null ? wordsResult.getGender().getWords() : "");
|
||||||
vo.setImage(upload);
|
vo.setImage(upload);
|
||||||
} else {
|
} 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.setIssuingAuthority(wordsResult.getIssuingAuthority() != null ? wordsResult.getIssuingAuthority().getWords() : "");
|
||||||
vo.setIssuingDate(wordsResult.getIssuingDate() != null ? wordsResult.getIssuingDate().getWords() : "");
|
vo.setIssuingDate(wordsResult.getIssuingDate() != null ? wordsResult.getIssuingDate().getWords() : "");
|
||||||
vo.setImage(upload);
|
vo.setImage(upload);
|
||||||
|
|||||||
@ -3,11 +3,15 @@ package org.dromara.contractor.service.impl;
|
|||||||
import cn.hutool.core.bean.BeanUtil;
|
import cn.hutool.core.bean.BeanUtil;
|
||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
import cn.hutool.core.collection.CollectionUtil;
|
import cn.hutool.core.collection.CollectionUtil;
|
||||||
|
import cn.hutool.core.date.DateUtil;
|
||||||
import com.alibaba.excel.EasyExcel;
|
import com.alibaba.excel.EasyExcel;
|
||||||
import com.alibaba.excel.ExcelWriter;
|
import com.alibaba.excel.ExcelWriter;
|
||||||
import com.alibaba.excel.write.metadata.WriteSheet;
|
import com.alibaba.excel.write.metadata.WriteSheet;
|
||||||
import com.alibaba.excel.write.metadata.fill.FillConfig;
|
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.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.plugins.pagination.Page;
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
import jakarta.annotation.Resource;
|
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.Workbook;
|
||||||
import org.apache.poi.ss.usermodel.WorkbookFactory;
|
import org.apache.poi.ss.usermodel.WorkbookFactory;
|
||||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
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.constant.HttpStatus;
|
||||||
import org.dromara.common.core.exception.ServiceException;
|
import org.dromara.common.core.exception.ServiceException;
|
||||||
import org.dromara.common.core.utils.ObjectUtils;
|
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.constructionuser.SubConstructionUserVo;
|
||||||
import org.dromara.contractor.domain.vo.usersalarydetail.SubUserSalaryDetailVo;
|
import org.dromara.contractor.domain.vo.usersalarydetail.SubUserSalaryDetailVo;
|
||||||
import org.dromara.contractor.domain.vo.usersalaryperiod.SubConstructionUserSalaryVo;
|
import org.dromara.contractor.domain.vo.usersalaryperiod.SubConstructionUserSalaryVo;
|
||||||
|
import org.dromara.contractor.excel.DynamicSalaryData;
|
||||||
import org.dromara.contractor.mapper.SubUserSalaryDetailMapper;
|
import org.dromara.contractor.mapper.SubUserSalaryDetailMapper;
|
||||||
import org.dromara.contractor.service.ISubConstructionUserService;
|
import org.dromara.contractor.service.ISubConstructionUserService;
|
||||||
import org.dromara.contractor.service.ISubUserSalaryDetailService;
|
import org.dromara.contractor.service.ISubUserSalaryDetailService;
|
||||||
|
import org.dromara.gps.domain.vo.ConstructionUser;
|
||||||
import org.dromara.project.domain.BusAttendance;
|
import org.dromara.project.domain.BusAttendance;
|
||||||
import org.dromara.project.domain.BusProject;
|
import org.dromara.project.domain.BusProject;
|
||||||
import org.dromara.project.domain.BusProjectTeam;
|
import org.dromara.project.domain.BusProjectTeam;
|
||||||
@ -51,6 +58,7 @@ import org.springframework.beans.BeanUtils;
|
|||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.scheduling.annotation.Async;
|
import org.springframework.scheduling.annotation.Async;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
import software.amazon.awssdk.utils.CollectionUtils;
|
import software.amazon.awssdk.utils.CollectionUtils;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
@ -65,6 +73,7 @@ import java.time.YearMonth;
|
|||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
import org.dromara.contractor.excel.SalaryExcelReader;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 员工每日工资Service业务层处理
|
* 员工每日工资Service业务层处理
|
||||||
@ -263,76 +272,53 @@ public class SubUserSalaryDetailServiceImpl extends ServiceImpl<SubUserSalaryDet
|
|||||||
@Override
|
@Override
|
||||||
public TableDataInfo<SubConstructionUserSalaryVo> salaryPageList(SubConstructionUserSalaryDto dto, PageQuery pageQuery) {
|
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();
|
String time = dto.getTime();
|
||||||
YearMonth parse = YearMonth.parse(time, DateTimeFormatter.ofPattern("yyyy-MM"));
|
YearMonth parse = YearMonth.parse(time, DateTimeFormatter.ofPattern("yyyy-MM"));
|
||||||
LocalDate start = parse.atDay(1);
|
LocalDate start = parse.atDay(1);
|
||||||
LocalDate end = parse.atEndOfMonth();
|
LocalDate end = parse.atEndOfMonth();
|
||||||
|
|
||||||
List<Long> userIds = rows.stream().map(SubConstructionUserVo::getSysUserId).toList();
|
QueryWrapper<SubUserSalaryDetail> queryWrapper = new QueryWrapper<>();
|
||||||
//考勤数据
|
queryWrapper.select("user_id", "SUM(work_hour) as workHour", "SUM(total_salary) as totalSalary","max(create_time) as createTime")
|
||||||
List<BusAttendance> attendanceList = attendanceService.lambdaQuery()
|
.eq("project_id", dto.getProjectId())
|
||||||
.eq(BusAttendance::getProjectId, dto.getProjectId())
|
.between("report_date", start, end)
|
||||||
.in(BusAttendance::getUserId, userIds)
|
.eq(dto.getTeamId()!=null,"team_id", dto.getTeamId())
|
||||||
.between(BusAttendance::getClockDate, start, end)
|
.like(StringUtils.isNotBlank(dto.getUserName()),"user_name", dto.getUserName())
|
||||||
.list();
|
.groupBy("user_id");
|
||||||
//工资数据
|
|
||||||
List<SubUserSalaryDetail> salaryDetailList = this.lambdaQuery()
|
|
||||||
.eq(SubUserSalaryDetail::getProjectId, dto.getProjectId())
|
|
||||||
.in(SubUserSalaryDetail::getUserId, userIds)
|
|
||||||
.between(SubUserSalaryDetail::getReportDate, start, end)
|
|
||||||
.list();
|
|
||||||
|
|
||||||
|
|
||||||
|
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<>();
|
ArrayList<SubConstructionUserSalaryVo> vos = new ArrayList<>();
|
||||||
for (SubConstructionUserVo row : rows) {
|
for (SubUserSalaryDetail detail : records) {
|
||||||
SubConstructionUserSalaryVo vo = new SubConstructionUserSalaryVo();
|
SubConstructionUserSalaryVo vo = new SubConstructionUserSalaryVo();
|
||||||
BeanUtil.copyProperties(row,vo);
|
vo.setId(detail.getId());
|
||||||
// vo.setSfzNumber(idCardEncryptorUtil.decrypt(vo.getSfzNumber()));
|
|
||||||
vo.setTime(dto.getTime());
|
vo.setTime(dto.getTime());
|
||||||
// 获取工资
|
vo.setTotalSalary(detail.getTotalSalary());
|
||||||
BigDecimal salary = row.getSalary();
|
vo.setCreateTime(DateUtil.format(detail.getCreateTime(), "yyyy-MM-dd HH:mm:ss"));
|
||||||
if (salary == null || salary.compareTo(BigDecimal.ZERO) == 0) {
|
vo.setWorkDay(detail.getWorkHour());
|
||||||
String typeOfWork = row.getTypeOfWork();
|
vo.setProjectId(dto.getProjectId());
|
||||||
String wageMeasureUnit = row.getWageMeasureUnit();
|
vo.setUserId(detail.getUserId());
|
||||||
if (StringUtils.isNotEmpty(typeOfWork) && StringUtils.isNotEmpty(wageMeasureUnit)) {
|
SubConstructionUser constructionUser = collect.get(detail.getUserId());
|
||||||
BusWorkWage workWage = workWageService.lambdaQuery()
|
if(constructionUser != null){
|
||||||
.eq(BusWorkWage::getProjectId, row.getProjectId())
|
if(constructionUser.getSfzNumber() != null){
|
||||||
.eq(BusWorkWage::getWorkType, typeOfWork)
|
vo.setSfzNumber(idCardEncryptorUtil.decrypt(constructionUser.getSfzNumber()));
|
||||||
.eq(BusWorkWage::getWageMeasureUnit, wageMeasureUnit)
|
|
||||||
.one();
|
|
||||||
if (workWage != null) {
|
|
||||||
salary = workWage.getWage();
|
|
||||||
} else {
|
|
||||||
salary = BigDecimal.ZERO;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
salary = BigDecimal.ZERO;
|
|
||||||
}
|
}
|
||||||
|
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);
|
vos.add(vo);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new TableDataInfo<>(vos,subConstructionUserVoTableDataInfo.getTotal());
|
return new TableDataInfo<>(vos,result.getTotal());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -342,27 +328,34 @@ public class SubUserSalaryDetailServiceImpl extends ServiceImpl<SubUserSalaryDet
|
|||||||
if (project == null) {
|
if (project == null) {
|
||||||
throw new ServiceException("项目不存在");
|
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. 解析年月和查询薪资明细
|
// 2. 解析年月和查询薪资明细
|
||||||
YearMonth yearMonth = YearMonth.parse(dto.getTime(), DateTimeFormatter.ofPattern("yyyy-MM"));
|
YearMonth yearMonth = YearMonth.parse(dto.getTime(), DateTimeFormatter.ofPattern("yyyy-MM"));
|
||||||
LocalDate start = yearMonth.atDay(1);
|
LocalDate start = yearMonth.atDay(1);
|
||||||
LocalDate end = yearMonth.atEndOfMonth();
|
LocalDate end = yearMonth.atEndOfMonth();
|
||||||
|
|
||||||
List<Long> userIds = userList.stream().map(SubConstructionUser::getSysUserId).toList();
|
QueryWrapper<SubUserSalaryDetail> queryWrapper = new QueryWrapper<>();
|
||||||
List<SubUserSalaryDetail> salaryDetails = this.lambdaQuery()
|
queryWrapper.select("user_id", "SUM(work_hour) as workHour", "SUM(total_salary) as totalSalary","max(create_time) as createTime")
|
||||||
.eq(SubUserSalaryDetail::getProjectId, dto.getProjectId())
|
.eq("project_id", dto.getProjectId())
|
||||||
.in(SubUserSalaryDetail::getUserId, userIds)
|
.between("report_date", start, end)
|
||||||
.between(SubUserSalaryDetail::getReportDate, 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();
|
.list();
|
||||||
|
|
||||||
// 3. 设置响应头(下载文件配置)
|
// 3. 设置响应头(下载文件配置)
|
||||||
@ -465,7 +458,7 @@ public class SubUserSalaryDetailServiceImpl extends ServiceImpl<SubUserSalaryDet
|
|||||||
.build();
|
.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);
|
excelWriter.fill(getTeamSummary(project, teamNameMap.get(teamId), yearMonth), writeSheet);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -474,7 +467,7 @@ public class SubUserSalaryDetailServiceImpl extends ServiceImpl<SubUserSalaryDet
|
|||||||
WriteSheet noTeamSheet = EasyExcel.writerSheet("无班组").build();
|
WriteSheet noTeamSheet = EasyExcel.writerSheet("无班组").build();
|
||||||
FillConfig fillConfig = FillConfig.builder().forceNewRow(true).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);
|
excelWriter.fill(getTeamSummary(project, null, yearMonth), noTeamSheet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -498,7 +491,7 @@ public class SubUserSalaryDetailServiceImpl extends ServiceImpl<SubUserSalaryDet
|
|||||||
}
|
}
|
||||||
|
|
||||||
private List<SubConstructionUserSalaryVo> getTeamData(List<SubConstructionUser> rows,
|
private List<SubConstructionUserSalaryVo> getTeamData(List<SubConstructionUser> rows,
|
||||||
List<SubUserSalaryDetail> salaryDetailList,Long teamId){
|
Map<Long, SubUserSalaryDetail> map,Long teamId){
|
||||||
List<SubConstructionUser> list1 ;
|
List<SubConstructionUser> list1 ;
|
||||||
if(teamId == null){
|
if(teamId == null){
|
||||||
list1 = rows.stream().filter(row -> row.getTeamId() == null && Arrays.asList("1","2").contains(row.getExitStatus())).toList();
|
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();
|
SubConstructionUserSalaryVo vo = new SubConstructionUserSalaryVo();
|
||||||
BeanUtil.copyProperties(row,vo);
|
BeanUtil.copyProperties(row,vo);
|
||||||
vo.setOrder(i);
|
vo.setOrder(i);
|
||||||
vo.setTotalSalary(salaryDetailList.stream().filter(detail -> detail.getUserId().equals(row.getSysUserId()))
|
SubUserSalaryDetail detail1 = map.get(row.getSysUserId());
|
||||||
.map(SubUserSalaryDetail::getDailySalary)
|
vo.setTotalSalary(detail1.getTotalSalary());
|
||||||
.reduce(BigDecimal.ZERO, BigDecimal::add)
|
|
||||||
);
|
|
||||||
vos.add(vo);
|
vos.add(vo);
|
||||||
i++;
|
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;
|
package org.dromara.job.attendance;
|
||||||
|
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollectionUtil;
|
||||||
import cn.hutool.core.date.DateUtil;
|
import cn.hutool.core.date.DateUtil;
|
||||||
import com.aizuda.snailjob.client.job.core.annotation.JobExecutor;
|
import com.aizuda.snailjob.client.job.core.annotation.JobExecutor;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
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.domain.SubConstructionUser;
|
||||||
import org.dromara.contractor.service.ISubConstructionUserService;
|
import org.dromara.contractor.service.ISubConstructionUserService;
|
||||||
import org.dromara.project.constant.BusProjectConstant;
|
import org.dromara.project.constant.BusProjectConstant;
|
||||||
import org.dromara.project.domain.BusAttendance;
|
import org.dromara.project.domain.*;
|
||||||
import org.dromara.project.domain.BusAttendanceRule;
|
|
||||||
import org.dromara.project.domain.BusProject;
|
|
||||||
import org.dromara.project.domain.BusUserProjectRelevancy;
|
|
||||||
import org.dromara.project.domain.enums.BusAttendanceClockStatusEnum;
|
import org.dromara.project.domain.enums.BusAttendanceClockStatusEnum;
|
||||||
import org.dromara.project.domain.enums.BusAttendanceCommuterEnum;
|
import org.dromara.project.domain.enums.BusAttendanceCommuterEnum;
|
||||||
import org.dromara.project.service.*;
|
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.scheduling.annotation.Scheduled;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
import java.time.DayOfWeek;
|
import java.time.DayOfWeek;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.LocalTime;
|
import java.time.LocalTime;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Component
|
@Component
|
||||||
@ -53,6 +53,11 @@ public class AttendanceJob {
|
|||||||
@Resource
|
@Resource
|
||||||
private ISubConstructionUserService constructionUserService;
|
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 * * * ?")
|
// @Scheduled(cron = "0 0/10 * * * ?")
|
||||||
@JobExecutor(name = "clockInMiss")
|
@JobExecutor(name = "clockInMiss")
|
||||||
@ -126,7 +131,7 @@ public class AttendanceJob {
|
|||||||
|
|
||||||
for (BusUserProjectRelevancy relevancy : relevancyList) {
|
for (BusUserProjectRelevancy relevancy : relevancyList) {
|
||||||
|
|
||||||
if (attendanceUserIds.contains(relevancy.getUserId()) || "0".equals(relevancy.getUserType())) {
|
if (attendanceUserIds.contains(relevancy.getUserId()) || noClockUserTypes.contains(relevancy.getUserType())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,6 +161,7 @@ public class AttendanceJob {
|
|||||||
}else {
|
}else {
|
||||||
busAttendance.setClockStatus(BusAttendanceClockStatusEnum.UNCLOCK.getValue());
|
busAttendance.setClockStatus(BusAttendanceClockStatusEnum.UNCLOCK.getValue());
|
||||||
}
|
}
|
||||||
|
busAttendance.setSalary(computeSalary(constructionUser, null));
|
||||||
|
|
||||||
missList.add(busAttendance);
|
missList.add(busAttendance);
|
||||||
}
|
}
|
||||||
@ -204,7 +210,9 @@ public class AttendanceJob {
|
|||||||
//管理员关联多个项目,需要记录是否已生成缺卡记录
|
//管理员关联多个项目,需要记录是否已生成缺卡记录
|
||||||
HashSet<Long> manageUserIds = new HashSet<>();
|
HashSet<Long> manageUserIds = new HashSet<>();
|
||||||
|
|
||||||
|
|
||||||
List<BusAttendance> missList = new ArrayList<>();
|
List<BusAttendance> missList = new ArrayList<>();
|
||||||
|
|
||||||
for (BusAttendanceRule rule : list) {
|
for (BusAttendanceRule rule : list) {
|
||||||
|
|
||||||
LocalTime clockOutTime = rule.getClockOutTime();
|
LocalTime clockOutTime = rule.getClockOutTime();
|
||||||
@ -235,16 +243,22 @@ public class AttendanceJob {
|
|||||||
List<BusUserProjectRelevancy> relevancyList = userProjectRelevancyService.list(Wrappers.lambdaQuery(BusUserProjectRelevancy.class)
|
List<BusUserProjectRelevancy> relevancyList = userProjectRelevancyService.list(Wrappers.lambdaQuery(BusUserProjectRelevancy.class)
|
||||||
.eq(BusUserProjectRelevancy::getProjectId, rule.getProjectId()));
|
.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::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();
|
List<Long> attendanceUserIds = attendanceList.stream().map(BusAttendance::getUserId).toList();
|
||||||
|
|
||||||
|
|
||||||
for (BusUserProjectRelevancy relevancy : relevancyList) {
|
for (BusUserProjectRelevancy relevancy : relevancyList) {
|
||||||
if (attendanceUserIds.contains(relevancy.getUserId()) || "0".equals(relevancy.getUserType())) {
|
if (attendanceUserIds.contains(relevancy.getUserId()) || noClockUserTypes.contains(relevancy.getUserType())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
BusAttendance busAttendance = new BusAttendance();
|
BusAttendance busAttendance = new BusAttendance();
|
||||||
@ -263,8 +277,6 @@ public class AttendanceJob {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
busAttendance.setUserId(relevancy.getUserId());
|
busAttendance.setUserId(relevancy.getUserId());
|
||||||
busAttendance.setUserName(constructionUser.getUserName());
|
busAttendance.setUserName(constructionUser.getUserName());
|
||||||
|
|
||||||
@ -276,7 +288,8 @@ public class AttendanceJob {
|
|||||||
}else {
|
}else {
|
||||||
busAttendance.setClockStatus(BusAttendanceClockStatusEnum.UNCLOCK.getValue());
|
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);
|
missList.add(busAttendance);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -313,4 +326,28 @@ public class AttendanceJob {
|
|||||||
|
|
||||||
return false;
|
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.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.LocalTime;
|
import java.time.LocalTime;
|
||||||
@ -102,4 +103,9 @@ public class BusAttendance extends BaseEntity {
|
|||||||
* 处理 0-未处理,1-已处理
|
* 处理 0-未处理,1-已处理
|
||||||
*/
|
*/
|
||||||
private String handle;
|
private String handle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 薪水
|
||||||
|
*/
|
||||||
|
private BigDecimal salary;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,4 +30,9 @@ public class AttendanceUserDataDetailVo {
|
|||||||
* 迟到或早退的分钟
|
* 迟到或早退的分钟
|
||||||
*/
|
*/
|
||||||
private Integer minuteCount;
|
private Integer minuteCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 出勤天数
|
||||||
|
*/
|
||||||
|
private Double workDay;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -95,4 +95,10 @@ public interface IBusWorkWageService extends IService<BusWorkWage> {
|
|||||||
* @return 工种薪水分页对象视图
|
* @return 工种薪水分页对象视图
|
||||||
*/
|
*/
|
||||||
Page<BusWorkWageVo> getVoPage(Page<BusWorkWage> workWagePage);
|
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.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.math.BigDecimal;
|
||||||
import java.time.*;
|
import java.time.*;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
@ -104,6 +105,8 @@ public class BusAttendanceServiceImpl extends ServiceImpl<BusAttendanceMapper, B
|
|||||||
|
|
||||||
private final ISubUserSalaryDetailService userSalaryDetailService;
|
private final ISubUserSalaryDetailService userSalaryDetailService;
|
||||||
|
|
||||||
|
private final IBusWorkWageService workWageService;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private IdCardEncryptorUtil idCardEncryptorUtil;
|
private IdCardEncryptorUtil idCardEncryptorUtil;
|
||||||
|
|
||||||
@ -149,7 +152,7 @@ public class BusAttendanceServiceImpl extends ServiceImpl<BusAttendanceMapper, B
|
|||||||
Long userId = req.getUserId();
|
Long userId = req.getUserId();
|
||||||
String clockMonth = req.getClockMonth();
|
String clockMonth = req.getClockMonth();
|
||||||
SubConstructionUser constructionUser = constructionUserService.getById(userId);
|
SubConstructionUser constructionUser = constructionUserService.getById(userId);
|
||||||
if ( constructionUser == null) {
|
if (constructionUser == null) {
|
||||||
throw new ServiceException("施工人员信息不存在", HttpStatus.NOT_FOUND);
|
throw new ServiceException("施工人员信息不存在", HttpStatus.NOT_FOUND);
|
||||||
}
|
}
|
||||||
// 解析月份
|
// 解析月份
|
||||||
@ -372,45 +375,43 @@ public class BusAttendanceServiceImpl extends ServiceImpl<BusAttendanceMapper, B
|
|||||||
BusAttendanceCommuterEnum.CLOCKOUT.getValue().equals(attendance.getClockType())).toList();
|
BusAttendanceCommuterEnum.CLOCKOUT.getValue().equals(attendance.getClockType())).toList();
|
||||||
|
|
||||||
if (clockTypeByTime == 1 && CollectionUtils.isEmpty(inAttendances)) {
|
if (clockTypeByTime == 1 && CollectionUtils.isEmpty(inAttendances)) {
|
||||||
BusAttendance attendance = new BusAttendance();
|
BusAttendance attendance = new BusAttendance();
|
||||||
// 上班打卡
|
// 上班打卡
|
||||||
attendance.setClockType(BusAttendanceCommuterEnum.CLOCKIN.getValue());
|
attendance.setClockType(BusAttendanceCommuterEnum.CLOCKIN.getValue());
|
||||||
//打卡时间
|
//打卡时间
|
||||||
attendance.setRuleTime(busAttendanceRuleVo.getClockInTime());
|
attendance.setRuleTime(busAttendanceRuleVo.getClockInTime());
|
||||||
// 判断是否为迟到
|
// 判断是否为迟到
|
||||||
if (isLate(now, busAttendanceRuleVo)) {
|
if (isLate(now, busAttendanceRuleVo)) {
|
||||||
attendance.setClockStatus(BusAttendanceClockStatusEnum.LATE.getValue());
|
attendance.setClockStatus(BusAttendanceClockStatusEnum.LATE.getValue());
|
||||||
attendance.setMinuteCount(getMinutesDifference(now, busAttendanceRuleVo.getClockInTime()));
|
attendance.setMinuteCount(getMinutesDifference(now, busAttendanceRuleVo.getClockInTime()));
|
||||||
} else {
|
} else {
|
||||||
attendance.setClockStatus(BusAttendanceClockStatusEnum.NORMAL.getValue());
|
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.setSalary(computeSalary(constructionUser, inAttendances));
|
||||||
attendance.setClockDate(localDate);
|
return this.save(attendance);
|
||||||
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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
boolean save = this.save(attendance);
|
} else if (clockTypeByTime == 2 || CollectionUtils.isNotEmpty(inAttendances)) {
|
||||||
//插入工资
|
|
||||||
userSalaryDetailService.insertByAttendance(userId, attendance.getClockDate());
|
|
||||||
return save;
|
|
||||||
|
|
||||||
} else if (clockTypeByTime == 2 || CollectionUtils.isEmpty(outAttendances)) {
|
|
||||||
|
|
||||||
if (CollectionUtil.isNotEmpty(outAttendances)) {
|
if (CollectionUtil.isNotEmpty(outAttendances)) {
|
||||||
BusAttendance busAttendance = outAttendances.getFirst();
|
BusAttendance busAttendance = outAttendances.getFirst();
|
||||||
@ -426,7 +427,8 @@ public class BusAttendanceServiceImpl extends ServiceImpl<BusAttendanceMapper, B
|
|||||||
} else {
|
} else {
|
||||||
busAttendance.setClockStatus(BusAttendanceClockStatusEnum.NORMAL.getValue());
|
busAttendance.setClockStatus(BusAttendanceClockStatusEnum.NORMAL.getValue());
|
||||||
busAttendance.setMinuteCount(0);
|
busAttendance.setMinuteCount(0);
|
||||||
}updateById(busAttendance);
|
}
|
||||||
|
updateById(busAttendance);
|
||||||
} else {
|
} else {
|
||||||
BusAttendance attendance = new BusAttendance();
|
BusAttendance attendance = new BusAttendance();
|
||||||
// 下班打卡
|
// 下班打卡
|
||||||
@ -460,10 +462,9 @@ public class BusAttendanceServiceImpl extends ServiceImpl<BusAttendanceMapper, B
|
|||||||
log.error("异步发送系统消息失败,用户ID: {}, 消息: {}", userId, "打卡成功", e);
|
log.error("异步发送系统消息失败,用户ID: {}, 消息: {}", userId, "打卡成功", e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
boolean save = this.save(attendance);
|
//计算工资
|
||||||
//插入工资
|
attendance.setSalary(computeSalary(constructionUser, inAttendances));
|
||||||
userSalaryDetailService.insertByAttendance(userId, attendance.getClockDate());
|
return this.save(attendance);
|
||||||
return save;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -503,6 +504,31 @@ public class BusAttendanceServiceImpl extends ServiceImpl<BusAttendanceMapper, B
|
|||||||
return attendedUserIds;
|
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));
|
.orderByAsc(BusAttendance::getClockDate));
|
||||||
|
|
||||||
// 处理正常出勤记录(去重日期)
|
// 处理正常出勤记录(去重日期)
|
||||||
List<AttendanceUserDataDetailVo> workList = attendanceList.stream()
|
|
||||||
|
// 过滤有效考勤记录并按日期分组
|
||||||
|
Map<LocalDate, List<BusAttendanceVo>> dateAttendanceMap = attendanceList.stream()
|
||||||
.filter(a -> validStatusList.contains(a.getClockStatus()))
|
.filter(a -> validStatusList.contains(a.getClockStatus()))
|
||||||
.map(this::convertToDetailVo)
|
.collect(Collectors.groupingBy(BusAttendanceVo::getClockDate));
|
||||||
.distinct() // 去除重复日期
|
List<AttendanceUserDataDetailVo> workList = new ArrayList<>();
|
||||||
.collect(Collectors.toList());
|
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);
|
vo.setWork(workList);
|
||||||
|
|
||||||
// 处理迟到记录
|
// 处理迟到记录
|
||||||
@ -1185,10 +1221,10 @@ public class BusAttendanceServiceImpl extends ServiceImpl<BusAttendanceMapper, B
|
|||||||
List<SubConstructionUser> users = entry.getValue();
|
List<SubConstructionUser> users = entry.getValue();
|
||||||
SubConstructionUser constructionUser = users.getFirst();
|
SubConstructionUser constructionUser = users.getFirst();
|
||||||
String teamName = constructionUser.getTeamName();
|
String teamName = constructionUser.getTeamName();
|
||||||
if(teamId == 0){
|
if (teamId == 0) {
|
||||||
teamName = "无班组";
|
teamName = "无班组";
|
||||||
}
|
}
|
||||||
if(StringUtils.isBlank(teamName)){
|
if (StringUtils.isBlank(teamName)) {
|
||||||
teamName = teamId.toString();
|
teamName = teamId.toString();
|
||||||
}
|
}
|
||||||
System.out.println("name:" + teamName);
|
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 + 1, 10 * 256); // 是否离场
|
||||||
sheet.setColumnWidth(3 + daysInMonth + 2, 15 * 256); // 签字
|
sheet.setColumnWidth(3 + daysInMonth + 2, 15 * 256); // 签字
|
||||||
|
|
||||||
|
// ==================== 创建样式 ====================
|
||||||
|
CellStyle borderStyle = createBorderStyle(workbook);
|
||||||
|
// CellStyle numberStyle = createNumberStyle(workbook);
|
||||||
|
|
||||||
|
|
||||||
// ==================== 表头部分 ====================
|
// ==================== 表头部分 ====================
|
||||||
Row titleRow = sheet.createRow(0);
|
Row titleRow = sheet.createRow(0);
|
||||||
Cell titleCell = titleRow.createCell(0);
|
Cell titleCell = titleRow.createCell(0);
|
||||||
@ -1247,13 +1288,75 @@ public class BusAttendanceServiceImpl extends ServiceImpl<BusAttendanceMapper, B
|
|||||||
// ==================== 数据表头 ====================
|
// ==================== 数据表头 ====================
|
||||||
Row headerRow = sheet.createRow(2);
|
Row headerRow = sheet.createRow(2);
|
||||||
writeHeaderRow(headerRow, daysInMonth);
|
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;
|
int rowIndex = 3;
|
||||||
for (SubConstructionUser user : users) {
|
for (SubConstructionUser user : users) {
|
||||||
Row row = sheet.createRow(rowIndex++);
|
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) {
|
private CellStyle createProjectCellStyle(Workbook workbook) {
|
||||||
CellStyle style = workbook.createCellStyle();
|
CellStyle style = workbook.createCellStyle();
|
||||||
Font font = workbook.createFont();
|
Font font = workbook.createFont();
|
||||||
@ -1339,20 +1478,29 @@ public class BusAttendanceServiceImpl extends ServiceImpl<BusAttendanceMapper, B
|
|||||||
row.createCell(2 + daysInMonth + 3).setCellValue("签字");
|
row.createCell(2 + daysInMonth + 3).setCellValue("签字");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeDataRow(Row row, SubConstructionUser user, List<BusAttendance> attendanceList, LocalDate start, LocalDate end, int daysInMonth) {
|
private void writeDataRow(Row row, SubConstructionUser user, List<BusAttendance> attendanceList, LocalDate start, LocalDate end, int daysInMonth,CellStyle borderStyle,CellStyle numberBorderStyle) {
|
||||||
int index = row.getRowNum();
|
int index = row.getRowNum()-2;
|
||||||
row.createCell(0).setCellValue(index);
|
row.createCell(0).setCellValue(index);
|
||||||
row.createCell(1).setCellValue(user.getUserName());
|
row.createCell(1).setCellValue(user.getUserName());
|
||||||
row.createCell(2).setCellValue(idCardEncryptorUtil.decrypt(user.getSfzNumber()));
|
row.createCell(2).setCellValue(idCardEncryptorUtil.decrypt(user.getSfzNumber()));
|
||||||
|
|
||||||
for (int i = 1; i <= daysInMonth; i++) {
|
for (int i = 1; i <= daysInMonth; i++) {
|
||||||
LocalDate date = start.plusDays(i - 1);
|
LocalDate date = start.plusDays(i - 1);
|
||||||
String value = getAttendanceValue(user.getSysUserId(), date, attendanceList);
|
Cell cell = row.createCell(2 + i);
|
||||||
row.createCell(2 + i).setCellValue(value);
|
Double value = getAttendanceValue(user.getSysUserId(), date, attendanceList);
|
||||||
|
cell.setCellValue(value);
|
||||||
|
// 设置数字格式样式
|
||||||
|
cell.setCellStyle(numberBorderStyle);
|
||||||
}
|
}
|
||||||
|
|
||||||
double total = countAttendance(user.getSysUserId(), attendanceList, start, end);
|
// double total = countAttendance(user.getSysUserId(), attendanceList, start, end);
|
||||||
row.createCell(2 + daysInMonth + 1).setCellValue(total);
|
// ==================== 在这里设置合计列公式 ====================
|
||||||
|
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()) ? "未离场" : "已离场";
|
String leaveStatus = "0".equals(user.getExitStatus()) ? "未离场" : "已离场";
|
||||||
row.createCell(2 + daysInMonth + 2).setCellValue(leaveStatus);
|
row.createCell(2 + daysInMonth + 2).setCellValue(leaveStatus);
|
||||||
@ -1360,14 +1508,14 @@ public class BusAttendanceServiceImpl extends ServiceImpl<BusAttendanceMapper, B
|
|||||||
row.createCell(2 + daysInMonth + 3).setCellValue("");
|
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()
|
List<BusAttendance> validRecords = attendanceList.stream()
|
||||||
.filter(a -> a.getUserId().equals(userId) && a.getClockDate().equals(date))
|
.filter(a -> a.getUserId().equals(userId) && a.getClockDate().equals(date))
|
||||||
.filter(a -> !a.getClockStatus().equals("4")) // 排除缺卡记录
|
.filter(a -> !a.getClockStatus().equals("4")) // 排除缺卡记录
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
if (validRecords.isEmpty()) {
|
if (validRecords.isEmpty()) {
|
||||||
return "0"; // 无有效打卡
|
return 0D; // 无有效打卡
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否有有效出勤状态(1,2,3,5)
|
// 检查是否有有效出勤状态(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()));
|
.anyMatch(a -> Arrays.asList("1", "2", "3", "5").contains(a.getClockStatus()));
|
||||||
|
|
||||||
if (!hasValidStatus) {
|
if (!hasValidStatus) {
|
||||||
return "0"; // 状态无效,如补卡、其他异常
|
return 0D; // 状态无效,如补卡、其他异常
|
||||||
}
|
}
|
||||||
|
|
||||||
// 判断是否为半勤(仅一次有效打卡)
|
// 判断是否为半勤(仅一次有效打卡)
|
||||||
if (validRecords.size() == 1) {
|
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) {
|
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;
|
LocalDate current = start;
|
||||||
|
|
||||||
while (!current.isAfter(end)) {
|
while (!current.isAfter(end)) {
|
||||||
String value = getAttendanceValue(userId, current, attendanceList);
|
Double value = getAttendanceValue(userId, current, attendanceList);
|
||||||
if ("1".equals(value)) {
|
total+=value;
|
||||||
total += 1.0;
|
|
||||||
} else if ("0.5".equals(value)) {
|
|
||||||
total += 0.5;
|
|
||||||
}
|
|
||||||
current = current.plusDays(1);
|
current = current.plusDays(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -248,4 +248,11 @@ public class BusWorkWageServiceImpl extends ServiceImpl<BusWorkWageMapper, BusWo
|
|||||||
workWageVoPage.setRecords(workWageVoList);
|
workWageVoPage.setRecords(workWageVoList);
|
||||||
return workWageVoPage;
|
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