diff --git a/xinnengyuan/ruoyi-admin/src/main/resources/excelTemplate/salary_template.xlsx b/xinnengyuan/ruoyi-admin/src/main/resources/excelTemplate/salary_template.xlsx new file mode 100644 index 00000000..4001e01f Binary files /dev/null and b/xinnengyuan/ruoyi-admin/src/main/resources/excelTemplate/salary_template.xlsx differ diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/contractor/controller/SubUserSalaryDetailController.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/contractor/controller/SubUserSalaryDetailController.java index cf7db725..050ece26 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/contractor/controller/SubUserSalaryDetailController.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/contractor/controller/SubUserSalaryDetailController.java @@ -11,12 +11,18 @@ import org.dromara.common.log.enums.BusinessType; import org.dromara.common.mybatis.core.page.PageQuery; import org.dromara.common.mybatis.core.page.TableDataInfo; import org.dromara.common.web.core.BaseController; +import org.dromara.contractor.domain.SubUserSalaryDetail; +import org.dromara.contractor.domain.dto.constructionuser.SubConstructionUserQueryReq; import org.dromara.contractor.domain.dto.usersalarydetail.SubUserSalaryDetailQueryReq; +import org.dromara.contractor.domain.dto.usersalaryperiod.SubConstructionUserSalaryDto; +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.service.ISubUserSalaryDetailService; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import java.io.IOException; import java.util.List; /** @@ -65,4 +71,19 @@ public class SubUserSalaryDetailController extends BaseController { return R.ok(subUserSalaryDetailService.queryById(id)); } + + /** + * 工资计算与导出 + */ + @GetMapping("/salaryPageList") + public TableDataInfo salaryPageList(@Validated SubConstructionUserSalaryDto dto, PageQuery pageQuery) { + return subUserSalaryDetailService.salaryPageList(dto, pageQuery); + } + + + @GetMapping("/export") + public void export(HttpServletResponse response, SubConstructionUserSalaryDto dto) throws IOException { + subUserSalaryDetailService.export(response, dto); + } + } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/contractor/domain/dto/usersalaryperiod/SubConstructionUserSalaryDto.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/contractor/domain/dto/usersalaryperiod/SubConstructionUserSalaryDto.java new file mode 100644 index 00000000..af4afefd --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/contractor/domain/dto/usersalaryperiod/SubConstructionUserSalaryDto.java @@ -0,0 +1,31 @@ +package org.dromara.contractor.domain.dto.usersalaryperiod; + +import com.alibaba.excel.annotation.ExcelProperty; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +public class SubConstructionUserSalaryDto { + + /** + * 人员姓名 + */ + private String userName; + + /** + * 项目id + */ + @NotBlank(message = "项目id不能为空") + private Long projectId; + + /** + * 班组id + */ + private Long teamId; + + /** + * 时间 + */ + @NotBlank(message = "时间不能为空") + private String time; +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/contractor/domain/vo/usersalaryperiod/SubConstructionUserSalaryVo.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/contractor/domain/vo/usersalaryperiod/SubConstructionUserSalaryVo.java new file mode 100644 index 00000000..7231f0a5 --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/contractor/domain/vo/usersalaryperiod/SubConstructionUserSalaryVo.java @@ -0,0 +1,83 @@ +package org.dromara.contractor.domain.vo.usersalaryperiod; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import org.dromara.common.excel.annotation.ExcelDictFormat; +import org.dromara.common.excel.convert.ExcelDictConvert; +import org.dromara.common.translation.annotation.Translation; +import org.dromara.common.translation.constant.TransConstant; +import org.dromara.contractor.domain.SubConstructionUser; + +import java.io.Serial; +import java.io.Serializable; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.Date; + + +/** + * 施工人员视图对象 bus_construction_user + * + * @author lilemy + * @date 2025-03-07 + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = SubConstructionUser.class) +public class SubConstructionUserSalaryVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键id + */ + private Long id; + + + /** + * 人员姓名 + */ + private String userName; + + /** + * 项目id + */ + private Long projectId; + + /** + * 身份证号码 + */ + private String sfzNumber; + + /** + * 银行卡号 + */ + private String yhkNumber; + + /** + * 开户行 + */ + private String yhkOpeningBank; + + + private BigDecimal totalSalary; + + /** + * 用户Id + */ + private Long sysUserId; + + private Double workDay; + + private BigDecimal salary; + + private String time; + + private Integer order; + + private String blank; + +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/contractor/service/ISubUserSalaryDetailService.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/contractor/service/ISubUserSalaryDetailService.java index df483b38..83d2d2d9 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/contractor/service/ISubUserSalaryDetailService.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/contractor/service/ISubUserSalaryDetailService.java @@ -3,12 +3,17 @@ package org.dromara.contractor.service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.IService; +import jakarta.servlet.http.HttpServletResponse; import org.dromara.common.mybatis.core.page.PageQuery; import org.dromara.common.mybatis.core.page.TableDataInfo; import org.dromara.contractor.domain.SubUserSalaryDetail; import org.dromara.contractor.domain.dto.usersalarydetail.SubUserSalaryDetailQueryReq; +import org.dromara.contractor.domain.dto.usersalaryperiod.SubConstructionUserSalaryDto; import org.dromara.contractor.domain.vo.usersalarydetail.SubUserSalaryDetailVo; +import org.dromara.contractor.domain.vo.usersalaryperiod.SubConstructionUserSalaryVo; +import org.springframework.validation.annotation.Validated; +import java.io.IOException; import java.time.LocalDate; import java.util.List; @@ -52,7 +57,7 @@ public interface ISubUserSalaryDetailService extends IService getVoPage(Page userSalaryDetailPage); + + /** + * 工资计算与导出 + */ + TableDataInfo salaryPageList(@Validated SubConstructionUserSalaryDto dto, PageQuery pageQuery); + + + /** + * 导出 + */ + void export(HttpServletResponse response, SubConstructionUserSalaryDto dto) throws IOException; + + + } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/contractor/service/impl/SubConstructionUserServiceImpl.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/contractor/service/impl/SubConstructionUserServiceImpl.java index b2008d26..c588ed74 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/contractor/service/impl/SubConstructionUserServiceImpl.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/contractor/service/impl/SubConstructionUserServiceImpl.java @@ -918,12 +918,11 @@ public class SubConstructionUserServiceImpl extends ServiceImpl attendanceLqw = Wrappers.lambdaQuery(BusAttendance.class) .eq(BusAttendance::getProjectId, projectId); - if (ObjectUtils.isNotEmpty(clockDate)) { - String clockMonth = clockDate.substring(0, 7); + if (ObjectUtils.isNotEmpty(clockMonth)) { // 校验月份格式 if (!DateConstant.YEAR_MONTH_PATTERN.matcher(clockMonth).matches()) { throw new ServiceException("月份格式不正确", HttpStatus.BAD_REQUEST); @@ -935,13 +934,13 @@ public class SubConstructionUserServiceImpl extends ServiceImpl userIdList = attendanceService.list(attendanceLqw) .stream().map(BusAttendance::getUserId).toList(); if (CollUtil.isNotEmpty(userIdList)) { - lqw.in(SubConstructionUser::getId, userIdList); + lqw.in(SubConstructionUser::getSysUserId, userIdList); } } // 模糊查询 @@ -967,7 +966,7 @@ public class SubConstructionUserServiceImpl extends ServiceImpl userIdList = constructionUserList.stream().map(SubConstructionUser::getId).toList(); + List userIdList = constructionUserList.stream().map(SubConstructionUser::getSysUserId).toList(); // 关联查询施工人员考勤列表 attendanceLqw.in(BusAttendance::getUserId, userIdList); Map> userIdBusAttendanceListMap = attendanceService.list(attendanceLqw) @@ -975,7 +974,7 @@ public class SubConstructionUserServiceImpl extends ServiceImpl userAttendanceTotalList = constructionUserList.stream().map(constructionUser -> { SubConstructionUserAttendanceTotalVo constructionUserAttendanceTotalResp = new SubConstructionUserAttendanceTotalVo(); - Long id = constructionUser.getId(); + Long id = constructionUser.getSysUserId(); BeanUtils.copyProperties(constructionUser, constructionUserAttendanceTotalResp); // 关联施工人员考勤信息 int attendanceDays = 0; @@ -1036,6 +1035,9 @@ public class SubConstructionUserServiceImpl extends ServiceImpl list = this.list(new LambdaQueryWrapper() + public void insertByAttendance(Long userId, LocalDate reportDate) { + //打一次卡 半天工资 如果已存在直接X2 + SubUserSalaryDetail one = this.getOne(new LambdaQueryWrapper() .eq(SubUserSalaryDetail::getUserId, userId) .eq(SubUserSalaryDetail::getReportDate, reportDate)); - if (CollUtil.isNotEmpty(list)) { - return true; + if (ObjectUtils.isNotEmpty(one)) { + one.setDailySalary(one.getDailySalary().multiply(new BigDecimal("2"))); + this.updateById(one); } SubConstructionUser constructionUser = constructionUserService.getBySysUserId(userId); @@ -127,16 +179,17 @@ public class SubUserSalaryDetailServiceImpl extends ServiceImpl salaryPageList(SubConstructionUserSalaryDto dto, PageQuery pageQuery) { + + SubConstructionUserQueryReq req = new SubConstructionUserQueryReq(); + req.setProjectId(dto.getProjectId()); + req.setTeamId(dto.getTeamId()); + req.setUserName(dto.getUserName()); + + TableDataInfo subConstructionUserVoTableDataInfo = constructionUserService.queryPageList(req, pageQuery); + List 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 userIds = rows.stream().map(SubConstructionUserVo::getSysUserId).toList(); + //考勤数据 + List attendanceList = attendanceService.lambdaQuery() + .eq(BusAttendance::getProjectId, dto.getProjectId()) + .in(BusAttendance::getUserId, userIds) + .between(BusAttendance::getClockDate, start, end) + .list(); + //工资数据 + List salaryDetailList = this.lambdaQuery() + .eq(SubUserSalaryDetail::getProjectId, dto.getProjectId()) + .in(SubUserSalaryDetail::getUserId, userIds) + .between(SubUserSalaryDetail::getReportDate, start, end) + .list(); + + + ArrayList vos = new ArrayList<>(); + for (SubConstructionUserVo row : rows) { + SubConstructionUserSalaryVo vo = new SubConstructionUserSalaryVo(); + BeanUtil.copyProperties(row,vo); + vo.setSfzNumber(idCardEncryptorUtil.decrypt(vo.getSfzNumber())); + 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.setSalary(salary); + vo.setTotalSalary(salaryDetailList.stream().filter(detail -> detail.getUserId().equals(row.getSysUserId())) + .map(SubUserSalaryDetail::getDailySalary) + .reduce(BigDecimal.ZERO, BigDecimal::add) + ); + List 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()); + } + + @Override + public void export(HttpServletResponse response, SubConstructionUserSalaryDto dto) throws IOException { + // 1. 查询项目和人员数据 + BusProject project = projectService.getById(dto.getProjectId()); + if (project == null) { + throw new ServiceException("项目不存在"); + } + + List 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 userIds = userList.stream().map(SubConstructionUser::getSysUserId).toList(); + List salaryDetails = this.lambdaQuery() + .eq(SubUserSalaryDetail::getProjectId, dto.getProjectId()) + .in(SubUserSalaryDetail::getUserId, userIds) + .between(SubUserSalaryDetail::getReportDate, start, end) + .list(); + + // 3. 设置响应头(下载文件配置) + setResponseHeader(response, yearMonth.getYear(), yearMonth.getMonthValue()); + + // 5. 读取模板文件到字节数组(复用模板) + byte[] templateBytes; + try (InputStream templateStream = Thread.currentThread().getContextClassLoader() + .getResourceAsStream("excelTemplate/salary_template.xlsx")) { + if (templateStream == null) { + throw new ServiceException("模板文件不存在(路径:excelTemplate/salary_template.xlsx)"); + } + templateBytes = IOUtils.toByteArray(templateStream); + } + + // 6. POI 处理多 sheet(核心:克隆模板 + 命名) + // 存储 映射,方便后续 EasyExcel 匹配 + Map sheetMap = new LinkedHashMap<>(); + try (Workbook workbook = WorkbookFactory.create(new ByteArrayInputStream(templateBytes)); + ByteArrayOutputStream bos = new ByteArrayOutputStream()) { + + // 校验模板是否有有效 sheet + if (workbook.getNumberOfSheets() == 0) { + throw new ServiceException("模板文件中无任何 sheet 页,请检查模板"); + } + + // 6.1 预处理班组信息(获取班组ID + 班组名称映射) + List teamIds = userList.stream() + .map(SubConstructionUser::getTeamId) + .filter(Objects::nonNull) + .distinct() + .collect(Collectors.toList()); + Map teamNameMap = new HashMap<>(); + if (!teamIds.isEmpty()) { + List teams = projectTeamService.lambdaQuery() + .in(BusProjectTeam::getId, teamIds) + .list(); + teamNameMap = teams.stream() + .collect(Collectors.toMap( + BusProjectTeam::getId, + BusProjectTeam::getTeamName, + (oldVal, newVal) -> newVal // 避免重复ID冲突 + )); + } + + // 6.2 克隆模板:为每个班组生成 sheet(关键修正) + for (Long teamId : teamIds) { + // ① 克隆原始模板 sheet(返回克隆后的 Sheet 对象) + Sheet newSheet = workbook.cloneSheet(0); + // ② 生成班组名称(避免空名称) + String teamName = teamNameMap.getOrDefault(teamId, "未知班组_" + teamId); + // ③ 给新 sheet 命名(通过 sheet 对象获取索引) + int newSheetIndex = workbook.getSheetIndex(newSheet); + workbook.setSheetName(newSheetIndex, teamName); + // ④ 存入映射,方便后续填充 + sheetMap.put(teamName, newSheet); + } + + // 6.3 克隆模板:处理“无班组”数据(如果需要) + boolean needNoTeamSheet = dto.getTeamId() == null; + if (needNoTeamSheet) { + Sheet noTeamSheet = workbook.cloneSheet(0); + String noTeamName = "无班组"; + int noTeamSheetIndex = workbook.getSheetIndex(noTeamSheet); + workbook.setSheetName(noTeamSheetIndex, noTeamName); + sheetMap.put(noTeamName, noTeamSheet); + } + + // 6.4 删除原始模板 sheet(避免最终文件保留空模板) + workbook.removeSheetAt(0); + + // 6.5 将 POI 处理后的 workbook 写入字节数组,供 EasyExcel 使用 + workbook.write(bos); + byte[] workbookBytes = bos.toByteArray(); + + // 7. EasyExcel 填充数据(按 sheet 映射顺序填充) + try (InputStream workbookStream = new ByteArrayInputStream(workbookBytes); + ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()) + .withTemplate(workbookStream) + .build()) { + + // 7.1 填充“班组”sheet 数据 + for (Map.Entry entry : sheetMap.entrySet()) { + String sheetName = entry.getKey(); + // 跳过“无班组”sheet,后续单独处理 + if ("无班组".equals(sheetName)) { + continue; + } + // 根据 sheet 名称反查班组ID(用于过滤数据) + Long teamId = teamNameMap.entrySet().stream() + .filter(e -> sheetName.equals(e.getValue())) + .map(Map.Entry::getKey) + .findFirst() + .orElse(null); + + // 创建 EasyExcel 的 WriteSheet(通过名称匹配 sheet) + WriteSheet writeSheet = EasyExcel.writerSheet(sheetName).build(); + FillConfig fillConfig = FillConfig.builder() + .forceNewRow(true) // 强制新增行,不覆盖模板内容 + .build(); + + // 填充列表数据和汇总数据 + excelWriter.fill(getTeamData(userList, salaryDetails, teamId), fillConfig, writeSheet); + excelWriter.fill(getTeamSummary(project, teamNameMap.get(teamId), yearMonth), writeSheet); + } + + // 7.2 填充“无班组”sheet 数据 + if (needNoTeamSheet) { + WriteSheet noTeamSheet = EasyExcel.writerSheet("无班组").build(); + FillConfig fillConfig = FillConfig.builder().forceNewRow(true).build(); + + excelWriter.fill(getTeamData(userList, salaryDetails, null), fillConfig, noTeamSheet); + excelWriter.fill(getTeamSummary(project, null, yearMonth), noTeamSheet); + } + } + } + } + + /** + * 设置响应头信息 + */ + private void setResponseHeader(HttpServletResponse response, int year, int month) throws IOException { + // 文件名编码,解决中文乱码 + String fileName = URLEncoder.encode(year + "年" + month + "月多部门考勤表.xlsx", "UTF-8"); + // 设置内容类型 + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + // 设置下载文件名 + response.setHeader("Content-Disposition", "attachment;filename*=UTF-8''" + fileName); + // 禁止缓存 + response.setHeader("Pragma", "no-cache"); + response.setHeader("Cache-Control", "no-cache"); + response.setDateHeader("Expires", 0); + } + + private List getTeamData(List rows, + List salaryDetailList,Long teamId){ + List list1 ; + if(teamId == null){ + list1 = rows.stream().filter(row -> row.getTeamId() == null && Arrays.asList("1","2").contains(row.getExitStatus())).toList(); + }else { + list1 = rows.stream().filter(row -> teamId.equals(row.getTeamId())).toList(); + } + ArrayList vos = new ArrayList<>(); + int i =1; + for (SubConstructionUser row : list1) { + 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) + ); + vos.add(vo); + i++; + } + return vos; + } + + + private Map getTeamSummary(BusProject project,String teamName,YearMonth parse) { + + // 模拟汇总数据 + return Map.of( + "time", parse.format(DateTimeFormatter.ofPattern("yyyy年MM月")), + "projectName", project.getProjectName(), + "teamName", teamName == null? "无班组":teamName + ); + } } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/formalities/enums/FormalitiesStatusEnum.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/formalities/enums/FormalitiesStatusEnum.java index 895422f3..3b69e780 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/formalities/enums/FormalitiesStatusEnum.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/formalities/enums/FormalitiesStatusEnum.java @@ -7,7 +7,8 @@ public enum FormalitiesStatusEnum { TOSTART("待开始", "0"), PROCESSING("处理中", "1"), - DELETE("已完成", "2"); + DELETE("已完成", "2"), + NOHANDLE("不需要办理", "3"); private final String text; diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/formalities/service/impl/BusFormalitiesAreConsolidatedServiceImpl.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/formalities/service/impl/BusFormalitiesAreConsolidatedServiceImpl.java index f4a97052..048389d3 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/formalities/service/impl/BusFormalitiesAreConsolidatedServiceImpl.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/formalities/service/impl/BusFormalitiesAreConsolidatedServiceImpl.java @@ -252,7 +252,8 @@ public class BusFormalitiesAreConsolidatedServiceImpl extends ServiceImpl ids, Boolean isValid) { if (isValid) { //TODO 做一些业务上的校验,判断是否需要校验 + for (Long id : ids) { + BusFormalitiesAreConsolidated selected = baseMapper.selectById(id); + if (selected == null || !FormalitiesStatusEnum.TOSTART.getText().equals(selected.getProcessingStatus())) { + throw new ServiceException("只能删除待开始数据"); + } + Long count = busFormalitiesAnnexService.getBaseMapper().selectCount(new LambdaQueryWrapper() + .eq(BusFormalitiesAnnex::getFormalitiesId, id)); + if (count > 0L) { + busFormalitiesAnnexService.getBaseMapper().delete(new LambdaQueryWrapper() + .eq(BusFormalitiesAnnex::getFormalitiesId, id)); + } + } } return baseMapper.deleteByIds(ids) > 0; } @@ -282,6 +296,12 @@ public class BusFormalitiesAreConsolidatedServiceImpl extends ServiceImpl> getClockDateForTwoWeekList(Long projectId) { + return R.ok(busAttendanceService.getClockDateForTwoWeekList(projectId)); + } + + @GetMapping("/exportList") + public void exportList(AttendanceExportDto dto, HttpServletResponse response) { + busAttendanceService.getExportList(dto, response); + } + } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/controller/app/BusAttendanceAppController.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/controller/app/BusAttendanceAppController.java index 641ef4b4..16fe6c47 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/controller/app/BusAttendanceAppController.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/controller/app/BusAttendanceAppController.java @@ -64,7 +64,7 @@ public class BusAttendanceAppController extends BaseController { /** * 人脸坐标打卡 */ - @RepeatSubmit(interval = 3, timeUnit = TimeUnit.SECONDS,message = "3分钟内禁止重复打卡") + @RepeatSubmit(interval = 3, timeUnit = TimeUnit.MINUTES,message = "3分钟内禁止重复打卡") @PostMapping("/punch/card/face") public R punchCardByFace(@RequestPart("file") MultipartFile file, BusAttendancePunchCardByFaceReq req) { return R.ok(attendanceService.punchCardByFace(file, req)); diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/domain/dto/attendance/AttendanceExportDto.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/domain/dto/attendance/AttendanceExportDto.java new file mode 100644 index 00000000..63a884d0 --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/domain/dto/attendance/AttendanceExportDto.java @@ -0,0 +1,33 @@ +package org.dromara.project.domain.dto.attendance; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class AttendanceExportDto { + /** + * 人员姓名 + */ + private String userName; + + /** + * 项目id + */ + @NotNull(message = "项目id不能为空") + private Long projectId; + + /** + * 班组id + */ + private Long teamId; + + /** + * 工种 + */ + private String typeOfWork; + + /** + * 打卡月份 + */ + private String clockDate; +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/domain/vo/attendance/BusAttendanceClockDateForTwoWeekVo.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/domain/vo/attendance/BusAttendanceClockDateForTwoWeekVo.java index e9a41a61..d509b9b0 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/domain/vo/attendance/BusAttendanceClockDateForTwoWeekVo.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/domain/vo/attendance/BusAttendanceClockDateForTwoWeekVo.java @@ -5,6 +5,7 @@ import lombok.Data; import java.io.Serial; import java.io.Serializable; +import java.time.LocalDate; import java.util.Date; /** @@ -20,10 +21,7 @@ public class BusAttendanceClockDateForTwoWeekVo implements Serializable { /** * 打卡日期 */ - @JsonFormat(shape = JsonFormat.Shape.STRING, - pattern = "yyyy-MM-dd", - timezone = "GMT+8") - private Date clockDate; + private LocalDate clockDate; /** * 出勤人数 diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/mapper/BusAttendanceMapper.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/mapper/BusAttendanceMapper.java index a9a886fa..6a957402 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/mapper/BusAttendanceMapper.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/mapper/BusAttendanceMapper.java @@ -1,6 +1,9 @@ package org.dromara.project.mapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.dromara.common.mybatis.core.page.PageQuery; import org.dromara.project.domain.BusAttendance; +import org.dromara.project.domain.bo.BusAttendanceBo; import org.dromara.project.domain.vo.BusAttendanceVo; import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; @@ -12,4 +15,7 @@ import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; */ public interface BusAttendanceMapper extends BaseMapperPlus { + + Page queryPageList(BusAttendanceBo bo, PageQuery pageQuery); + } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/IBusAttendanceService.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/IBusAttendanceService.java index 2d83ec28..5b2e8f92 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/IBusAttendanceService.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/IBusAttendanceService.java @@ -1,7 +1,10 @@ package org.dromara.project.service; +import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.constraints.NotNull; +import org.dromara.common.core.domain.R; import org.dromara.project.domain.dto.attendance.AttendanceCountDto; +import org.dromara.project.domain.dto.attendance.AttendanceExportDto; import org.dromara.project.domain.dto.attendance.AttendanceUserCountDto; import org.dromara.project.domain.dto.attendance.BusAttendancePunchCardByFaceReq; import org.dromara.project.domain.vo.BusAttendanceVo; @@ -153,4 +156,16 @@ public interface IBusAttendanceService extends IService{ * 指定月份的打卡人员信息 */ AttendanceUserInfoVo getAttendanceUserInfo(AttendanceUserCountDto dto); + + /** + * 近两周打卡统计 + */ + List getClockDateForTwoWeekList(Long projectId); + + /** + * 获取导出的考勤数据 + */ + void getExportList(AttendanceExportDto dto,HttpServletResponse response); + + } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/impl/BusAttendanceServiceImpl.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/impl/BusAttendanceServiceImpl.java index 301c5e55..9a30ef49 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/impl/BusAttendanceServiceImpl.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/impl/BusAttendanceServiceImpl.java @@ -6,13 +6,16 @@ import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONArray; import cn.hutool.json.JSONUtil; 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; +import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.apache.poi.ss.formula.functions.T; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.ss.util.CellRangeAddress; import org.dromara.common.core.constant.HttpStatus; import org.dromara.common.core.exception.ServiceException; import org.dromara.common.core.utils.MapstructUtils; @@ -21,14 +24,15 @@ import org.dromara.common.domain.GeoPoint; import org.dromara.common.mybatis.core.page.PageQuery; import org.dromara.common.mybatis.core.page.TableDataInfo; import org.dromara.common.satoken.utils.LoginHelper; +import org.dromara.common.utils.IdCardEncryptorUtil; import org.dromara.common.utils.JSTUtil; import org.dromara.contractor.domain.SubConstructionUser; -import org.dromara.contractor.domain.vo.constructionuser.SubConstructionUserVo; import org.dromara.contractor.service.ISubConstructionUserService; import org.dromara.contractor.service.ISubUserSalaryDetailService; import org.dromara.project.domain.*; import org.dromara.project.domain.bo.BusAttendanceBo; import org.dromara.project.domain.dto.attendance.AttendanceCountDto; +import org.dromara.project.domain.dto.attendance.AttendanceExportDto; import org.dromara.project.domain.dto.attendance.AttendanceUserCountDto; import org.dromara.project.domain.dto.attendance.BusAttendancePunchCardByFaceReq; import org.dromara.project.domain.enums.BusAttendanceClockStatusEnum; @@ -47,10 +51,10 @@ import org.dromara.websocket.ChatServerHandler; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; -import java.time.Duration; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; +import java.io.IOException; +import java.io.OutputStream; +import java.time.*; +import java.time.format.DateTimeFormatter; import java.util.*; import java.util.stream.Collectors; @@ -98,6 +102,8 @@ public class BusAttendanceServiceImpl extends ServiceImpl getPunchRangeByProjectIdAndUserId(Long projectId, Long userId) { BusUserProjectRelevancy relevancy = userProjectRelevancyService.getOne(Wrappers.lambdaQuery(BusUserProjectRelevancy.class) @@ -433,7 +438,7 @@ public class BusAttendanceServiceImpl extends ServiceImpl matchingRange = JSTUtil.findMatchingRange(req.getLat(), req.getLng(), punchRangeList); return matchingRange != null; @@ -634,7 +639,7 @@ public class BusAttendanceServiceImpl extends ServiceImpl attendanceList = baseMapper.selectList(Wrappers.lambdaQuery() .eq(BusAttendance::getProjectId, dto.getProjectId()) - .in(BusAttendance::getClockStatus,Arrays.asList("1","2","3","5","7")) + .in(BusAttendance::getClockStatus, Arrays.asList("1", "2", "3", "5", "7")) .eq(BusAttendance::getClockDate, dto.getDate()) ); List attendanceUserIds = attendanceList.stream().map(BusAttendance::getUserId).toList(); @@ -642,7 +647,7 @@ public class BusAttendanceServiceImpl extends ServiceImpllambdaQuery() .eq(SubConstructionUser::getProjectId, dto.getProjectId())); - attendanceCountVo.setTotalCount((int)count); + attendanceCountVo.setTotalCount((int) count); //出勤人数 HashSet longs = new HashSet<>(attendanceUserIds); int size = longs.size(); @@ -656,15 +661,15 @@ public class BusAttendanceServiceImpl extends ServiceImpl attendanceList = baseMapper.selectList(Wrappers.lambdaQuery() .eq(BusAttendance::getProjectId, dto.getProjectId()) - .in(BusAttendance::getClockStatus,Arrays.asList("1","2","3","5","7")) + .in(BusAttendance::getClockStatus, Arrays.asList("1", "2", "3", "5", "7")) .eq(BusAttendance::getClockDate, dto.getDate()) ); List attendanceUserIds = attendanceList.stream().map(BusAttendance::getUserId).toList(); - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq(SubConstructionUser::getProjectId, dto.getProjectId()); - if("1".equals(dto.getClockStatus()) && CollectionUtil.isEmpty(attendanceUserIds)){ + if ("1".equals(dto.getClockStatus()) && CollectionUtil.isEmpty(attendanceUserIds)) { TableDataInfo rspData = new TableDataInfo<>(); rspData.setCode(cn.hutool.http.HttpStatus.HTTP_OK); rspData.setMsg("查询成功"); @@ -673,10 +678,10 @@ public class BusAttendanceServiceImpl extends ServiceImpl result = constructionUserService.page(pageQuery.build(), wrapper); List records = result.getRecords(); @@ -688,14 +693,14 @@ public class BusAttendanceServiceImpl extends ServiceImpl earlyList = attendanceList.stream().filter(a -> "3".equals(a.getClockStatus())) + List earlyList = attendanceList.stream().filter(a -> "3".equals(a.getClockStatus())) .map(BusAttendanceVo::getClockDate) .toList(); attendanceUserCountVo.setEarlyNum(earlyList.size()); - List absentList = attendanceList.stream().filter(a -> "4".equals(a.getClockStatus())) + List absentList = attendanceList.stream().filter(a -> "4".equals(a.getClockStatus())) .map(BusAttendanceVo::getClockDate) .toList(); attendanceUserCountVo.setAbsentNum(absentList.size()); @@ -811,6 +816,7 @@ public class BusAttendanceServiceImpl extends ServiceImpl getClockDateForTwoWeekList(Long projectId) { + LocalDate now = LocalDate.now(); + //往前14天,包含今天 + LocalDate startDate = now.minusDays(13); + List list = list(Wrappers.lambdaQuery().eq(BusAttendance::getProjectId, projectId) + .between(BusAttendance::getClockDate, startDate, now) + ); + + // 按日期分组 + Map> groupedByDate = list.stream() + .collect(Collectors.groupingBy(BusAttendance::getClockDate)); + + // 按日期和用户ID分组 + Map>> dateUserMap = new HashMap<>(); + for (Map.Entry> entry : groupedByDate.entrySet()) { + LocalDate date = entry.getKey(); + Map> userAttendanceMap = entry.getValue().stream() + .collect(Collectors.groupingBy(BusAttendance::getUserId)); + dateUserMap.put(date, userAttendanceMap); + } + + // 统计全勤、半勤、缺卡人数 + ArrayList busAttendanceClockDateForTwoWeekVos = new ArrayList<>(); + + List list1 = Arrays.asList("1", "2", "3", "5"); + + for (Map.Entry>> dateEntry : dateUserMap.entrySet()) { + LocalDate date = dateEntry.getKey(); + Map> userAttendanceMap = dateEntry.getValue(); + + int full = 0, half = 0, absent = 0; + + for (Map.Entry> userEntry : userAttendanceMap.entrySet()) { + List records = userEntry.getValue(); + + int a = 0; + for (BusAttendance record : records) { + if (list1.contains(record.getClockStatus())) { + a += 1; + } + } + if (a >= 2) { + full++; + } else if (a == 1) { + half++; + } else { + absent++; + } + } + BusAttendanceClockDateForTwoWeekVo busAttendanceClockDateForTwoWeekVo = new BusAttendanceClockDateForTwoWeekVo(); + busAttendanceClockDateForTwoWeekVo.setClockDate(date); + busAttendanceClockDateForTwoWeekVo.setAttendance(full); + busAttendanceClockDateForTwoWeekVo.setHalfAttendance(half); + busAttendanceClockDateForTwoWeekVo.setAbsenteeism(absent); + + busAttendanceClockDateForTwoWeekVos.add(busAttendanceClockDateForTwoWeekVo); + } + return busAttendanceClockDateForTwoWeekVos; + } + + @Override + public void getExportList(AttendanceExportDto dto, HttpServletResponse response) { + + BusProject project = projectService.getById(dto.getProjectId()); + + if (project == null) { + throw new ServiceException("项目不存在"); + } + + List list = constructionUserService.list(Wrappers.lambdaQuery(SubConstructionUser.class) + .eq(SubConstructionUser::getProjectId, dto.getProjectId()) + .eq(dto.getTeamId() != null, SubConstructionUser::getTeamId, dto.getTeamId()) + .eq(StrUtil.isNotBlank(dto.getTypeOfWork()), SubConstructionUser::getTypeOfWork, dto.getTypeOfWork()) + .like(StringUtils.isNotBlank(dto.getUserName()), SubConstructionUser::getUserName, dto.getUserName()) + ); + + if (list.isEmpty()) { + throw new ServiceException("未查询到相关人员"); + } + + List userIds = list.stream().map(SubConstructionUser::getSysUserId).toList(); + String clockDate = dto.getClockDate(); + + YearMonth yearMonth = YearMonth.parse(clockDate, DateTimeFormatter.ofPattern("yyyy-MM")); + LocalDate start = yearMonth.atDay(1); + LocalDate end = yearMonth.atEndOfMonth(); + + int daysInMonth = yearMonth.lengthOfMonth(); // 动态获取天数 + + List attendanceList = list(Wrappers.lambdaQuery(BusAttendance.class) + .in(BusAttendance::getUserId, userIds) + .between(BusAttendance::getClockDate, start, end) + ); + + Map> teamUserMap = list.stream() + .filter(user -> user.getTeamId() != null) + .collect(Collectors.groupingBy(SubConstructionUser::getTeamId)); + + Workbook workbook = new HSSFWorkbook(); + + for (Map.Entry> entry : teamUserMap.entrySet()) { + Long teamId = entry.getKey(); + List users = entry.getValue(); + SubConstructionUser constructionUser = users.getFirst(); + String teamName = constructionUser.getTeamName(); + Sheet sheet = workbook.createSheet(teamName); + + // ==================== 设置列宽 ==================== + sheet.setColumnWidth(0, 5* 256); // 序号 + sheet.setColumnWidth(1, 10* 256); // 姓名/日期 + sheet.setColumnWidth(2, 30* 256); // 身份证号 + + // 日期列(每天一列) + for (int i = 3; i < 3 + daysInMonth; i++) { + sheet.setColumnWidth(i, 5* 256); // 每天列宽 15 字符 + } + + sheet.setColumnWidth(3 + daysInMonth, 5* 256); // 合计 + sheet.setColumnWidth(3 + daysInMonth + 1, 10* 256); // 是否离场 + sheet.setColumnWidth(3 + daysInMonth + 2, 15* 256); // 签字 + + // ==================== 表头部分 ==================== + Row titleRow = sheet.createRow(0); + Cell titleCell = titleRow.createCell(0); + titleCell.setCellValue("建筑施工企业现场人员考勤表(" + clockDate + "月)"); + titleCell.setCellStyle(createTitleCellStyle(workbook)); + + // 合并标题行,横跨所有列 + int totalColumns = 3 + daysInMonth + 3; // 总列数 + sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, totalColumns - 1)); + + // ==================== 第二行:项目名称和班组类别 ==================== + Row projectRow = sheet.createRow(1); + + // 设置项目名称(左对齐,占据前半部分) + Cell projectNameCell = projectRow.createCell(0); + projectNameCell.setCellValue("项目名称:" + project.getProjectName()); + projectNameCell.setCellStyle(createProjectCellStyle(workbook)); // 可选:设置样式 + + int midColumn = (totalColumns / 2) - 1; + + // 设置班组类别(右对齐,占据后半部分) + int teamCol = midColumn +1; // + Cell teamNameCell = projectRow.createCell(teamCol); + teamNameCell.setCellValue("班组类别:" + teamName); + teamNameCell.setCellStyle(createProjectCellStyle(workbook)); + + // 合并项目名称区域(从第 0 列到中间) + if (midColumn >= 0) { + sheet.addMergedRegion(new CellRangeAddress(1, 1, 0, midColumn)); + } + + // 合并班组类别区域(从 teamCol 开始,到最后一列) + if (teamCol < totalColumns) { + sheet.addMergedRegion(new CellRangeAddress(1, 1, teamCol, totalColumns - 1)); + } + + // ==================== 数据表头 ==================== + Row headerRow = sheet.createRow(2); + writeHeaderRow(headerRow, daysInMonth); + + // ==================== 数据行 ==================== + int rowIndex = 3; + for (SubConstructionUser user : users) { + Row row = sheet.createRow(rowIndex++); + writeDataRow(row, user, attendanceList, start, end, daysInMonth); + } + + // ==================== 表尾部分 ==================== + + sheet.createRow(rowIndex++); + + Row footerRow1 = sheet.createRow(rowIndex++); + int allTotalColumns = 3 + daysInMonth + 3; + int quarter = allTotalColumns / 4; + + int[] colSpans = { + quarter - 3, // 第一列减少3列 + quarter + 2, // 第二列增加2列 + quarter + 2, // 第三列增加2列 + allTotalColumns - (quarter - 3 + quarter + 2 + quarter + 2) // 自动计算最后一列 + }; + + int startCol = 0; + String[] footerLabels = {"制表人:", "班组负责人:", "项目负责人:", "制表日期:"}; + for (int i = 0; i < 4; i++) { + int endCol = startCol + colSpans[i] - 1; + + // 合并单元格 + sheet.addMergedRegion(new CellRangeAddress(rowIndex - 1, rowIndex - 1, startCol, endCol)); + + // 创建单元格并设置内容 + Cell cell = footerRow1.createCell(startCol); + cell.setCellValue(footerLabels[i]); + cell.setCellStyle(createLeftAlignedCellStyle(workbook)); // 左对齐 + + startCol = endCol + 1; + } + + Row footerRow2 = sheet.createRow(rowIndex++); + footerRow2.createCell(0).setCellValue("注:"); + footerRow2.createCell(1).setCellValue("1、本考勤表必须按照每日实际工时进行填写;"); + Row footerRow3 = sheet.createRow(rowIndex++); + footerRow3.createCell(0).setCellValue(""); + footerRow3.createCell(1).setCellValue("2、本考勤表作为工资计发的重要依据。"); + + } + + try (OutputStream outputStream = response.getOutputStream()) { + workbook.write(outputStream); + workbook.close(); + } catch (IOException e) { + log.error("导出失败", e); + throw new ServiceException("导出失败"); + } + } + + private CellStyle createProjectCellStyle(Workbook workbook) { + CellStyle style = workbook.createCellStyle(); + Font font = workbook.createFont(); + font.setFontHeightInPoints((short) 10); + style.setFont(font); + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); + return style; + } + + private CellStyle createTitleCellStyle(Workbook workbook) { + CellStyle style = workbook.createCellStyle(); + Font font = workbook.createFont(); + font.setFontHeightInPoints((short) 16); + font.setBold(true); + style.setFont(font); + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); + return style; + } + + private void writeHeaderRow(Row row, int daysInMonth) { + row.createCell(0).setCellValue("序号"); + row.createCell(1).setCellValue("姓名/日期"); + row.createCell(2).setCellValue("身份证号"); + + for (int i = 1; i <= daysInMonth; i++) { + row.createCell(2 + i).setCellValue(String.valueOf(i)); + } + + row.createCell(2 + daysInMonth + 1).setCellValue("合计"); + row.createCell(2 + daysInMonth + 2).setCellValue("是否离场"); + row.createCell(2 + daysInMonth + 3).setCellValue("签字"); + } + + private void writeDataRow(Row row, SubConstructionUser user, List attendanceList, LocalDate start, LocalDate end, int daysInMonth) { + int index = row.getRowNum(); + 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); + } + + double total = countAttendance(user.getSysUserId(), attendanceList, start, end); + row.createCell(2 + daysInMonth + 1).setCellValue(total); + + String leaveStatus = "0".equals(user.getExitStatus()) ? "未离场" : "已离场"; + row.createCell(2 + daysInMonth + 2).setCellValue(leaveStatus); + + row.createCell(2 + daysInMonth + 3).setCellValue(""); + } + + private String getAttendanceValue(Long userId, LocalDate date, List attendanceList) { + List 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"; // 无有效打卡 + } + + // 检查是否有有效出勤状态(1,2,3,5) + boolean hasValidStatus = validRecords.stream() + .anyMatch(a -> Arrays.asList("1", "2", "3", "5").contains(a.getClockStatus())); + + if (!hasValidStatus) { + return "0"; // 状态无效,如补卡、其他异常 + } + + // 判断是否为半勤(仅一次有效打卡) + if (validRecords.size() == 1) { + return "0.5"; + } + + // 正常出勤(两次有效打卡) + return "1"; + } + + private double countAttendance(Long userId, List attendanceList, LocalDate start, LocalDate end) { + double total = 0.0; + 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; + } + current = current.plusDays(1); + } + + return total; + } + + private CellStyle createLeftAlignedCellStyle(Workbook workbook) { + CellStyle style = workbook.createCellStyle(); + style.setAlignment(HorizontalAlignment.LEFT); // 左对齐 + style.setVerticalAlignment(VerticalAlignment.CENTER); + return style; + } + + } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/impl/BusProjectTeamMemberServiceImpl.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/impl/BusProjectTeamMemberServiceImpl.java index c02cc8b1..9f3e53e6 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/impl/BusProjectTeamMemberServiceImpl.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/impl/BusProjectTeamMemberServiceImpl.java @@ -31,17 +31,16 @@ import org.dromara.project.domain.vo.projectteammember.BusProjectTeamMemberVo; import org.dromara.project.mapper.BusProjectTeamMemberMapper; import org.dromara.project.service.*; import org.dromara.system.domain.SysUser; +import org.dromara.system.domain.SysUserRole; import org.dromara.system.domain.vo.SysUserVo; +import org.dromara.system.mapper.SysUserRoleMapper; import org.dromara.system.service.ISysUserService; import org.springframework.beans.BeanUtils; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; -import java.util.Date; -import java.util.List; -import java.util.Map; -import java.util.Objects; +import java.util.*; import java.util.stream.Collectors; /** @@ -77,6 +76,9 @@ public class BusProjectTeamMemberServiceImpl extends ServiceImpl sysUserRoles = userRoleMapper.selectList(Wrappers.lambdaQuery() + .eq(SysUserRole::getUserId, constructionUser.getSysUserId()) + .eq(SysUserRole::getProjectId, req.getProjectId()) + .in(SysUserRole::getRoleId, Arrays.asList(2L, 3L)) + ); + if (CollUtil.isEmpty(sysUserRoles)) { + SysUserRole sysUserRole = new SysUserRole(); + sysUserRole.setUserId(constructionUser.getSysUserId()); + sysUserRole.setRoleId("0".equals(req.getPostId()) ? 2L : 3L); + sysUserRole.setProjectId(req.getProjectId()); + userRoleMapper.insert(sysUserRole); + } else { + Long roleId = "0".equals(req.getPostId()) ? 2L : 3L; + List list1 = sysUserRoles.stream().map(SysUserRole::getRoleId).toList(); + if (!list1.contains(roleId)){ + SysUserRole sysUserRole = new SysUserRole(); + sysUserRole.setUserId(constructionUser.getSysUserId()); + sysUserRole.setRoleId(roleId); + sysUserRole.setProjectId(req.getProjectId()); + userRoleMapper.insert(sysUserRole); + } + } return projectTeamMember.getId(); } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/ChatServerHandler.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/ChatServerHandler.java index f9fc6388..251d2806 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/ChatServerHandler.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/ChatServerHandler.java @@ -39,30 +39,26 @@ import java.util.concurrent.ConcurrentHashMap; public class ChatServerHandler extends SimpleChannelInboundHandler { // 移除 @Autowired 注解 + private static ChatHistoryServiceImpl chatHistoryService; + private static ChatGroupServiceImpl chatGroupService; + private static SysUserServiceImpl sysUserService; + private static SysOssServiceImpl sysOssService; @Autowired - private ChatHistoryServiceImpl chatHistoryService; + public void setChatHistoryService(ChatHistoryServiceImpl service) { + chatHistoryService = service; + } @Autowired - private ChatGroupServiceImpl chatGroupService; + public void setChatGroupService(ChatGroupServiceImpl service){ + chatGroupService = service; + } @Autowired - private SysUserServiceImpl sysUserService; + public void setSysUserService(SysUserServiceImpl service){ + sysUserService = service; + } @Autowired - private SysOssServiceImpl sysOssService; -// @Autowired -// public void setChatHistoryService(ChatHistoryServiceImpl service) { -// chatHistoryService = service; -// } -// @Autowired -// public void setChatGroupService(ChatGroupServiceImpl service){ -// chatGroupService = service; -// } -// @Autowired -// public void setSysUserService(SysUserServiceImpl service){ -// sysUserService = service; -// } -// @Autowired -// public void setSysOssService(SysOssServiceImpl service){ -// sysOssService = service; -// } + public void setSysOssService(SysOssServiceImpl service){ + sysOssService = service; + } // 存储所有连接的客户端Channel private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/controller/ChatGroupController.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/controller/ChatGroupController.java index 6b41bbe2..d43ed0b9 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/controller/ChatGroupController.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/controller/ChatGroupController.java @@ -156,6 +156,11 @@ public class ChatGroupController { return R.fail(); } + @GetMapping("/testSys") + public void testSys(Long userId, String message){ + chatServerHandler.sendSystemMessageToUser(userId, message,"1"); + } + /*** * 获取房间聊天记录 传递房间ID 获取发送给该房间的所有聊天记录 */