Merge remote-tracking branch 'origin/dev' into dev
This commit is contained in:
Binary file not shown.
@ -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<SubConstructionUserSalaryVo> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
|
||||
}
|
||||
@ -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<SubUserSalaryDetai
|
||||
* @param reportDate 日报日期
|
||||
* @return 是否新增成功
|
||||
*/
|
||||
Boolean insertByAttendance(Long userId, LocalDate reportDate);
|
||||
void insertByAttendance(Long userId, LocalDate reportDate);
|
||||
|
||||
/**
|
||||
* 获取员工每日工资对视图
|
||||
@ -78,4 +83,18 @@ public interface ISubUserSalaryDetailService extends IService<SubUserSalaryDetai
|
||||
*/
|
||||
Page<SubUserSalaryDetailVo> getVoPage(Page<SubUserSalaryDetail> userSalaryDetailPage);
|
||||
|
||||
|
||||
/**
|
||||
* 工资计算与导出
|
||||
*/
|
||||
TableDataInfo<SubConstructionUserSalaryVo> salaryPageList(@Validated SubConstructionUserSalaryDto dto, PageQuery pageQuery);
|
||||
|
||||
|
||||
/**
|
||||
* 导出
|
||||
*/
|
||||
void export(HttpServletResponse response, SubConstructionUserSalaryDto dto) throws IOException;
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -918,12 +918,11 @@ public class SubConstructionUserServiceImpl extends ServiceImpl<SubConstructionU
|
||||
Long projectId = req.getProjectId();
|
||||
Long teamId = req.getTeamId();
|
||||
String typeOfWork = req.getTypeOfWork();
|
||||
String clockDate = req.getClockDate();
|
||||
String clockMonth = req.getClockDate();
|
||||
// 联表查询
|
||||
LambdaQueryWrapper<BusAttendance> 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<SubConstructionU
|
||||
throw new ServiceException("不能查看大于当前月份的记录", HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
// 计算当月第一天 / 最后一天
|
||||
Date start = DateUtils.toDate(yearMonth.atDay(1));
|
||||
Date end = DateUtils.toDate(yearMonth.atEndOfMonth());
|
||||
LocalDate start = yearMonth.atDay(1);
|
||||
LocalDate end = yearMonth.atEndOfMonth();
|
||||
attendanceLqw.between(BusAttendance::getClockDate, start, end);
|
||||
List<Long> 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<SubConstructionU
|
||||
return constructionUserAttendanceTotalPage;
|
||||
}
|
||||
// 获取施工人员id列表
|
||||
List<Long> userIdList = constructionUserList.stream().map(SubConstructionUser::getId).toList();
|
||||
List<Long> userIdList = constructionUserList.stream().map(SubConstructionUser::getSysUserId).toList();
|
||||
// 关联查询施工人员考勤列表
|
||||
attendanceLqw.in(BusAttendance::getUserId, userIdList);
|
||||
Map<Long, List<BusAttendance>> userIdBusAttendanceListMap = attendanceService.list(attendanceLqw)
|
||||
@ -975,7 +974,7 @@ public class SubConstructionUserServiceImpl extends ServiceImpl<SubConstructionU
|
||||
// 填充信息
|
||||
List<SubConstructionUserAttendanceTotalVo> 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<SubConstructionU
|
||||
return constructionUserAttendanceTotalPage;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取施工人员身份证信息
|
||||
*
|
||||
|
||||
@ -1,34 +1,70 @@
|
||||
package org.dromara.contractor.service.impl;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import com.alibaba.excel.EasyExcel;
|
||||
import com.alibaba.excel.ExcelWriter;
|
||||
import com.alibaba.excel.write.metadata.WriteSheet;
|
||||
import com.alibaba.excel.write.metadata.fill.FillConfig;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.pdfbox.io.IOUtils;
|
||||
import org.apache.poi.ss.usermodel.Sheet;
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
import org.apache.poi.ss.usermodel.WorkbookFactory;
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||
import org.dromara.common.core.constant.HttpStatus;
|
||||
import org.dromara.common.core.exception.ServiceException;
|
||||
import org.dromara.common.core.utils.ObjectUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.dromara.common.utils.IdCardEncryptorUtil;
|
||||
import org.dromara.contractor.domain.SubConstructionUser;
|
||||
import org.dromara.contractor.domain.SubUserSalaryDetail;
|
||||
import org.dromara.contractor.domain.SubUserSalaryPeriod;
|
||||
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.exportvo.BusConstructionUserExportVo;
|
||||
import org.dromara.contractor.domain.vo.constructionuser.SubConstructionUserVo;
|
||||
import org.dromara.contractor.domain.vo.usersalarydetail.SubUserSalaryDetailVo;
|
||||
import org.dromara.contractor.domain.vo.usersalaryperiod.SubConstructionUserSalaryVo;
|
||||
import org.dromara.contractor.mapper.SubUserSalaryDetailMapper;
|
||||
import org.dromara.contractor.service.ISubConstructionUserService;
|
||||
import org.dromara.contractor.service.ISubUserSalaryDetailService;
|
||||
import org.dromara.project.domain.BusAttendance;
|
||||
import org.dromara.project.domain.BusProject;
|
||||
import org.dromara.project.domain.BusProjectTeam;
|
||||
import org.dromara.project.domain.BusWorkWage;
|
||||
import org.dromara.project.service.IBusAttendanceService;
|
||||
import org.dromara.project.service.IBusProjectService;
|
||||
import org.dromara.project.service.IBusProjectTeamService;
|
||||
import org.dromara.project.service.IBusWorkWageService;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Service;
|
||||
import software.amazon.awssdk.utils.CollectionUtils;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.net.URLEncoder;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.time.YearMonth;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 员工每日工资Service业务层处理
|
||||
@ -47,6 +83,21 @@ public class SubUserSalaryDetailServiceImpl extends ServiceImpl<SubUserSalaryDet
|
||||
@Resource
|
||||
private ISubConstructionUserService constructionUserService;
|
||||
|
||||
@Resource
|
||||
private IdCardEncryptorUtil idCardEncryptorUtil;
|
||||
|
||||
@Resource
|
||||
@Lazy
|
||||
private IBusAttendanceService attendanceService;
|
||||
|
||||
@Resource
|
||||
@Lazy
|
||||
private IBusProjectService projectService;
|
||||
|
||||
@Resource
|
||||
@Lazy
|
||||
private IBusProjectTeamService projectTeamService;
|
||||
|
||||
/**
|
||||
* 查询员工每日工资
|
||||
*
|
||||
@ -97,13 +148,14 @@ public class SubUserSalaryDetailServiceImpl extends ServiceImpl<SubUserSalaryDet
|
||||
*/
|
||||
@Async
|
||||
@Override
|
||||
public Boolean insertByAttendance(Long userId, LocalDate reportDate) {
|
||||
//查询当天工资,已存在则跳过
|
||||
List<SubUserSalaryDetail> list = this.list(new LambdaQueryWrapper<SubUserSalaryDetail>()
|
||||
public void insertByAttendance(Long userId, LocalDate reportDate) {
|
||||
//打一次卡 半天工资 如果已存在直接X2
|
||||
SubUserSalaryDetail one = this.getOne(new LambdaQueryWrapper<SubUserSalaryDetail>()
|
||||
.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<SubUserSalaryDet
|
||||
salary = BigDecimal.ZERO;
|
||||
}
|
||||
}
|
||||
// 保存当日工资
|
||||
|
||||
// 保存当日一半工资
|
||||
SubUserSalaryDetail save = new SubUserSalaryDetail();
|
||||
save.setProjectId(constructionUser.getProjectId());
|
||||
save.setTeamId(constructionUser.getTeamId());
|
||||
save.setUserId(userId);
|
||||
save.setUserName(constructionUser.getUserName());
|
||||
save.setReportDate(reportDate);
|
||||
save.setDailySalary(salary);
|
||||
save.setDailySalary(salary.divide(new BigDecimal(2),2, RoundingMode.HALF_UP));
|
||||
save.setRemark(salary.compareTo(BigDecimal.ZERO) == 0 ? "当前人员未设置工资" : null);
|
||||
return this.save(save);
|
||||
this.save(save);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -207,5 +260,276 @@ public class SubUserSalaryDetailServiceImpl extends ServiceImpl<SubUserSalaryDet
|
||||
return userSalaryDetailVoPage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TableDataInfo<SubConstructionUserSalaryVo> salaryPageList(SubConstructionUserSalaryDto dto, PageQuery pageQuery) {
|
||||
|
||||
SubConstructionUserQueryReq req = new SubConstructionUserQueryReq();
|
||||
req.setProjectId(dto.getProjectId());
|
||||
req.setTeamId(dto.getTeamId());
|
||||
req.setUserName(dto.getUserName());
|
||||
|
||||
TableDataInfo<SubConstructionUserVo> subConstructionUserVoTableDataInfo = constructionUserService.queryPageList(req, pageQuery);
|
||||
List<SubConstructionUserVo> rows = subConstructionUserVoTableDataInfo.getRows();
|
||||
if(CollectionUtil.isEmpty(rows)){
|
||||
return TableDataInfo.build();
|
||||
}
|
||||
String time = dto.getTime();
|
||||
YearMonth parse = YearMonth.parse(time, DateTimeFormatter.ofPattern("yyyy-MM"));
|
||||
LocalDate start = parse.atDay(1);
|
||||
LocalDate end = parse.atEndOfMonth();
|
||||
|
||||
List<Long> userIds = rows.stream().map(SubConstructionUserVo::getSysUserId).toList();
|
||||
//考勤数据
|
||||
List<BusAttendance> attendanceList = attendanceService.lambdaQuery()
|
||||
.eq(BusAttendance::getProjectId, dto.getProjectId())
|
||||
.in(BusAttendance::getUserId, userIds)
|
||||
.between(BusAttendance::getClockDate, start, end)
|
||||
.list();
|
||||
//工资数据
|
||||
List<SubUserSalaryDetail> salaryDetailList = this.lambdaQuery()
|
||||
.eq(SubUserSalaryDetail::getProjectId, dto.getProjectId())
|
||||
.in(SubUserSalaryDetail::getUserId, userIds)
|
||||
.between(SubUserSalaryDetail::getReportDate, start, end)
|
||||
.list();
|
||||
|
||||
|
||||
ArrayList<SubConstructionUserSalaryVo> 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<BusAttendance> list = attendanceList.stream().filter(attendance -> attendance.getUserId().equals(row.getSysUserId())
|
||||
&& Arrays.asList("1", "2", "3", "5").contains(attendance.getClockStatus())
|
||||
).toList();
|
||||
vo.setWorkDay(list.size()*0.5);
|
||||
|
||||
vos.add(vo);
|
||||
}
|
||||
|
||||
return new TableDataInfo<>(vos,subConstructionUserVoTableDataInfo.getTotal());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void export(HttpServletResponse response, SubConstructionUserSalaryDto dto) throws IOException {
|
||||
// 1. 查询项目和人员数据
|
||||
BusProject project = projectService.getById(dto.getProjectId());
|
||||
if (project == null) {
|
||||
throw new ServiceException("项目不存在");
|
||||
}
|
||||
|
||||
List<SubConstructionUser> userList = constructionUserService.lambdaQuery()
|
||||
.eq(SubConstructionUser::getProjectId, dto.getProjectId())
|
||||
.eq(dto.getTeamId() != null, SubConstructionUser::getTeamId, dto.getTeamId())
|
||||
.like(StringUtils.isNotBlank(dto.getUserName()), SubConstructionUser::getUserName, dto.getUserName())
|
||||
.list();
|
||||
|
||||
if (userList.isEmpty()) {
|
||||
throw new ServiceException("暂无数据");
|
||||
}
|
||||
|
||||
// 2. 解析年月和查询薪资明细
|
||||
YearMonth yearMonth = YearMonth.parse(dto.getTime(), DateTimeFormatter.ofPattern("yyyy-MM"));
|
||||
LocalDate start = yearMonth.atDay(1);
|
||||
LocalDate end = yearMonth.atEndOfMonth();
|
||||
|
||||
List<Long> userIds = userList.stream().map(SubConstructionUser::getSysUserId).toList();
|
||||
List<SubUserSalaryDetail> salaryDetails = this.lambdaQuery()
|
||||
.eq(SubUserSalaryDetail::getProjectId, dto.getProjectId())
|
||||
.in(SubUserSalaryDetail::getUserId, userIds)
|
||||
.between(SubUserSalaryDetail::getReportDate, start, end)
|
||||
.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(核心:克隆模板 + 命名)
|
||||
// 存储 <sheet名称, sheet对象> 映射,方便后续 EasyExcel 匹配
|
||||
Map<String, Sheet> 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<Long> teamIds = userList.stream()
|
||||
.map(SubConstructionUser::getTeamId)
|
||||
.filter(Objects::nonNull)
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
Map<Long, String> teamNameMap = new HashMap<>();
|
||||
if (!teamIds.isEmpty()) {
|
||||
List<BusProjectTeam> 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<String, Sheet> 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<SubConstructionUserSalaryVo> getTeamData(List<SubConstructionUser> rows,
|
||||
List<SubUserSalaryDetail> salaryDetailList,Long teamId){
|
||||
List<SubConstructionUser> 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<SubConstructionUserSalaryVo> 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<String, Object> getTeamSummary(BusProject project,String teamName,YearMonth parse) {
|
||||
|
||||
// 模拟汇总数据
|
||||
return Map.of(
|
||||
"time", parse.format(DateTimeFormatter.ofPattern("yyyy年MM月")),
|
||||
"projectName", project.getProjectName(),
|
||||
"teamName", teamName == null? "无班组":teamName
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -7,7 +7,8 @@ public enum FormalitiesStatusEnum {
|
||||
|
||||
TOSTART("待开始", "0"),
|
||||
PROCESSING("处理中", "1"),
|
||||
DELETE("已完成", "2");
|
||||
DELETE("已完成", "2"),
|
||||
NOHANDLE("不需要办理", "3");
|
||||
|
||||
private final String text;
|
||||
|
||||
|
||||
@ -252,7 +252,8 @@ public class BusFormalitiesAreConsolidatedServiceImpl extends ServiceImpl<BusFor
|
||||
//TODO 做一些数据校验,如唯一约束
|
||||
if (!FormalitiesStatusEnum.TOSTART.getText().equals(entity.getProcessingStatus())
|
||||
&& !FormalitiesStatusEnum.PROCESSING.getText().equals(entity.getProcessingStatus())
|
||||
&& !FormalitiesStatusEnum.DELETE.getText().equals(entity.getProcessingStatus())) {
|
||||
&& !FormalitiesStatusEnum.DELETE.getText().equals(entity.getProcessingStatus())
|
||||
&& !FormalitiesStatusEnum.NOHANDLE.getText().equals(entity.getProcessingStatus())) {
|
||||
throw new ServiceException("办理状态错误!!");
|
||||
}
|
||||
}
|
||||
@ -265,9 +266,22 @@ public class BusFormalitiesAreConsolidatedServiceImpl extends ServiceImpl<BusFor
|
||||
* @return 是否删除成功
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Boolean deleteWithValidByIds(Collection<Long> 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<BusFormalitiesAnnex>()
|
||||
.eq(BusFormalitiesAnnex::getFormalitiesId, id));
|
||||
if (count > 0L) {
|
||||
busFormalitiesAnnexService.getBaseMapper().delete(new LambdaQueryWrapper<BusFormalitiesAnnex>()
|
||||
.eq(BusFormalitiesAnnex::getFormalitiesId, id));
|
||||
}
|
||||
}
|
||||
}
|
||||
return baseMapper.deleteByIds(ids) > 0;
|
||||
}
|
||||
@ -282,6 +296,12 @@ public class BusFormalitiesAreConsolidatedServiceImpl extends ServiceImpl<BusFor
|
||||
throw new ServiceException("数据不存在");
|
||||
}
|
||||
|
||||
if (FormalitiesStatusEnum.NOHANDLE.getText().equals(bo.getProcessingStatus())){
|
||||
if (!FormalitiesStatusEnum.TOSTART.getText().equals(busFormalitiesAreConsolidated.getProcessingStatus())) {
|
||||
throw new ServiceException("只有待开始数据能修改为’不需要办理‘状态");
|
||||
}
|
||||
}
|
||||
|
||||
busFormalitiesAreConsolidated.setProcessingStatus(bo.getProcessingStatus());
|
||||
if (FormalitiesStatusEnum.DELETE.getText().equals(busFormalitiesAreConsolidated.getProcessingStatus())) {
|
||||
if (busFormalitiesAreConsolidated.getHead() == null) {
|
||||
|
||||
@ -6,6 +6,8 @@ import lombok.RequiredArgsConstructor;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.constraints.*;
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import org.dromara.project.domain.dto.attendance.AttendanceExportDto;
|
||||
import org.dromara.project.domain.vo.attendance.BusAttendanceClockDateForTwoWeekVo;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.dromara.common.idempotent.annotation.RepeatSubmit;
|
||||
@ -102,4 +104,16 @@ public class BusAttendanceController extends BaseController {
|
||||
@PathVariable Long[] ids) {
|
||||
return toAjax(busAttendanceService.deleteWithValidByIds(List.of(ids), true));
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/list/clockDate/twoWeek")
|
||||
public R<List<BusAttendanceClockDateForTwoWeekVo>> getClockDateForTwoWeekList(Long projectId) {
|
||||
return R.ok(busAttendanceService.getClockDateForTwoWeekList(projectId));
|
||||
}
|
||||
|
||||
@GetMapping("/exportList")
|
||||
public void exportList(AttendanceExportDto dto, HttpServletResponse response) {
|
||||
busAttendanceService.getExportList(dto, response);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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<Boolean> punchCardByFace(@RequestPart("file") MultipartFile file, BusAttendancePunchCardByFaceReq req) {
|
||||
return R.ok(attendanceService.punchCardByFace(file, req));
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
|
||||
/**
|
||||
* 出勤人数
|
||||
|
||||
@ -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<BusAttendance, BusAttendanceVo> {
|
||||
|
||||
|
||||
Page<BusAttendanceVo> queryPageList(BusAttendanceBo bo, PageQuery pageQuery);
|
||||
|
||||
}
|
||||
|
||||
@ -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<BusAttendance>{
|
||||
* 指定月份的打卡人员信息
|
||||
*/
|
||||
AttendanceUserInfoVo getAttendanceUserInfo(AttendanceUserCountDto dto);
|
||||
|
||||
/**
|
||||
* 近两周打卡统计
|
||||
*/
|
||||
List<BusAttendanceClockDateForTwoWeekVo> getClockDateForTwoWeekList(Long projectId);
|
||||
|
||||
/**
|
||||
* 获取导出的考勤数据
|
||||
*/
|
||||
void getExportList(AttendanceExportDto dto,HttpServletResponse response);
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -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<BusAttendanceMapper, B
|
||||
|
||||
private final ISubUserSalaryDetailService userSalaryDetailService;
|
||||
|
||||
@Resource
|
||||
private IdCardEncryptorUtil idCardEncryptorUtil;
|
||||
|
||||
|
||||
// 出勤状态(正常、迟到、早退)
|
||||
@ -367,7 +373,6 @@ public class BusAttendanceServiceImpl extends ServiceImpl<BusAttendanceMapper, B
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public List<String> getPunchRangeByProjectIdAndUserId(Long projectId, Long userId) {
|
||||
BusUserProjectRelevancy relevancy = userProjectRelevancyService.getOne(Wrappers.lambdaQuery(BusUserProjectRelevancy.class)
|
||||
@ -811,6 +816,7 @@ public class BusAttendanceServiceImpl extends ServiceImpl<BusAttendanceMapper, B
|
||||
|
||||
/**
|
||||
* 将BusAttendanceVo转换为AttendanceUserDataDetailVo
|
||||
*
|
||||
* @param attendance 考勤记录
|
||||
* @return 考勤详情VO
|
||||
*/
|
||||
@ -849,4 +855,331 @@ public class BusAttendanceServiceImpl extends ServiceImpl<BusAttendanceMapper, B
|
||||
|
||||
return attendanceUserInfoVo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<BusAttendanceClockDateForTwoWeekVo> getClockDateForTwoWeekList(Long projectId) {
|
||||
LocalDate now = LocalDate.now();
|
||||
//往前14天,包含今天
|
||||
LocalDate startDate = now.minusDays(13);
|
||||
List<BusAttendance> list = list(Wrappers.<BusAttendance>lambdaQuery().eq(BusAttendance::getProjectId, projectId)
|
||||
.between(BusAttendance::getClockDate, startDate, now)
|
||||
);
|
||||
|
||||
// 按日期分组
|
||||
Map<LocalDate, List<BusAttendance>> groupedByDate = list.stream()
|
||||
.collect(Collectors.groupingBy(BusAttendance::getClockDate));
|
||||
|
||||
// 按日期和用户ID分组
|
||||
Map<LocalDate, Map<Long, List<BusAttendance>>> dateUserMap = new HashMap<>();
|
||||
for (Map.Entry<LocalDate, List<BusAttendance>> entry : groupedByDate.entrySet()) {
|
||||
LocalDate date = entry.getKey();
|
||||
Map<Long, List<BusAttendance>> userAttendanceMap = entry.getValue().stream()
|
||||
.collect(Collectors.groupingBy(BusAttendance::getUserId));
|
||||
dateUserMap.put(date, userAttendanceMap);
|
||||
}
|
||||
|
||||
// 统计全勤、半勤、缺卡人数
|
||||
ArrayList<BusAttendanceClockDateForTwoWeekVo> busAttendanceClockDateForTwoWeekVos = new ArrayList<>();
|
||||
|
||||
List<String> list1 = Arrays.asList("1", "2", "3", "5");
|
||||
|
||||
for (Map.Entry<LocalDate, Map<Long, List<BusAttendance>>> dateEntry : dateUserMap.entrySet()) {
|
||||
LocalDate date = dateEntry.getKey();
|
||||
Map<Long, List<BusAttendance>> userAttendanceMap = dateEntry.getValue();
|
||||
|
||||
int full = 0, half = 0, absent = 0;
|
||||
|
||||
for (Map.Entry<Long, List<BusAttendance>> userEntry : userAttendanceMap.entrySet()) {
|
||||
List<BusAttendance> 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<SubConstructionUser> 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<Long> 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<BusAttendance> attendanceList = list(Wrappers.lambdaQuery(BusAttendance.class)
|
||||
.in(BusAttendance::getUserId, userIds)
|
||||
.between(BusAttendance::getClockDate, start, end)
|
||||
);
|
||||
|
||||
Map<Long, List<SubConstructionUser>> teamUserMap = list.stream()
|
||||
.filter(user -> user.getTeamId() != null)
|
||||
.collect(Collectors.groupingBy(SubConstructionUser::getTeamId));
|
||||
|
||||
Workbook workbook = new HSSFWorkbook();
|
||||
|
||||
for (Map.Entry<Long, List<SubConstructionUser>> entry : teamUserMap.entrySet()) {
|
||||
Long teamId = entry.getKey();
|
||||
List<SubConstructionUser> 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<BusAttendance> 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<BusAttendance> attendanceList) {
|
||||
List<BusAttendance> validRecords = attendanceList.stream()
|
||||
.filter(a -> a.getUserId().equals(userId) && a.getClockDate().equals(date))
|
||||
.filter(a -> !a.getClockStatus().equals("4")) // 排除缺卡记录
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (validRecords.isEmpty()) {
|
||||
return "0"; // 无有效打卡
|
||||
}
|
||||
|
||||
// 检查是否有有效出勤状态(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<BusAttendance> 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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -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<BusProjectTeamM
|
||||
|
||||
@Resource
|
||||
private IBusUserProjectRelevancyService userProjectRelevancyService;
|
||||
@Resource
|
||||
private SysUserRoleMapper userRoleMapper;
|
||||
|
||||
|
||||
/**
|
||||
* 查询项目班组下的成员
|
||||
@ -180,7 +182,29 @@ public class BusProjectTeamMemberServiceImpl extends ServiceImpl<BusProjectTeamM
|
||||
relevancy.setProjectId(req.getProjectId());
|
||||
userProjectRelevancyService.save(relevancy);
|
||||
}
|
||||
|
||||
//设置基础角色
|
||||
List<SysUserRole> sysUserRoles = userRoleMapper.selectList(Wrappers.<SysUserRole>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<Long> 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();
|
||||
}
|
||||
|
||||
|
||||
@ -39,30 +39,26 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
public class ChatServerHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
|
||||
|
||||
// 移除 @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);
|
||||
|
||||
@ -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 获取发送给该房间的所有聊天记录
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user