考勤,土地

This commit is contained in:
zt
2025-08-09 19:46:37 +08:00
parent 4ad8c2e20c
commit a6c915ce04
10 changed files with 338 additions and 45 deletions

View File

@ -11,12 +11,13 @@ import com.itextpdf.text.Image;
import com.itextpdf.text.pdf.PdfContentByte;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfStamper;
import org.dromara.system.domain.vo.SysOssVo;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.*;
import java.net.FileNameMap;
import java.net.URLConnection;
import java.util.concurrent.CompletableFuture;
/**
* @author lilemy
@ -121,22 +122,145 @@ public class PdfBoxQrCodeGenerator {
* @param y 坐标
* @return 插入二维码后的PDF文件流
*/
public static ByteArrayOutputStream addQRCodeToPDF(String srcPdf, byte[] qrCodeBytes, int pageNum, float x, float y) throws IOException, DocumentException {
// public static ByteArrayOutputStream addQRCodeToPDF(String srcPdf, byte[] qrCodeBytes, int pageNum, float x, float y) throws IOException, DocumentException {
//
// PdfReader reader = new PdfReader(srcPdf);
// ByteArrayOutputStream pdfOut = new ByteArrayOutputStream();
// PdfStamper stamper = new PdfStamper(reader, pdfOut);
//
// PdfContentByte content = stamper.getOverContent(pageNum);
// Image image = Image.getInstance(qrCodeBytes);
// image.setAbsolutePosition(x, y); // 坐标:左下角为原点
// image.scaleAbsolute(100, 100); // 设置二维码大小
//
// content.addImage(image);
// stamper.close();
// reader.close();
//
// return pdfOut;
// }
/**
* 在PDF每一页的指定位置添加二维码并返回数据流根据页面方向自动调整位置
*
* @param srcPdf 原PDF文件路径可以是本地路径或网络地址
* @param qrCodeBytes 二维码图片字节数组
* @param x X坐标默认位置
* @param y Y坐标默认位置
* @return 插入二维码后的PDF文件流
*/
public static ByteArrayOutputStream addQRCodeToPDFOnAllPages(String srcPdf, byte[] qrCodeBytes, float x, float y)
throws IOException, DocumentException {
PdfReader reader = new PdfReader(srcPdf);
PdfReader reader = null;
PdfStamper stamper = null;
ByteArrayOutputStream pdfOut = new ByteArrayOutputStream();
PdfStamper stamper = new PdfStamper(reader, pdfOut);
PdfContentByte content = stamper.getOverContent(pageNum);
Image image = Image.getInstance(qrCodeBytes);
image.setAbsolutePosition(x, y); // 坐标:左下角为原点
image.scaleAbsolute(100, 100); // 设置二维码大小
try {
// 判断是网络地址还是本地文件路径
if (srcPdf.startsWith("http://") || srcPdf.startsWith("https://")) {
// 网络地址从URL读取PDF
java.net.URL url = new java.net.URL(srcPdf);
java.net.HttpURLConnection connection = (java.net.HttpURLConnection) url.openConnection();
connection.setConnectTimeout(10000); // 连接超时10秒
connection.setReadTimeout(30000); // 读取超时30秒
connection.setRequestMethod("GET");
content.addImage(image);
stamper.close();
reader.close();
int responseCode = connection.getResponseCode();
if (responseCode != java.net.HttpURLConnection.HTTP_OK) {
throw new IOException("无法从URL获取PDF文件HTTP响应码: " + responseCode + ", URL: " + srcPdf);
}
java.io.InputStream inputStream = connection.getInputStream();
reader = new PdfReader(inputStream);
} else {
// 本地文件路径:检查文件是否存在
File srcFile = new File(srcPdf);
if (!srcFile.exists()) {
throw new IOException("源PDF文件不存在: " + srcPdf);
}
reader = new PdfReader(srcPdf);
}
stamper = new PdfStamper(reader, pdfOut);
int numberOfPages = reader.getNumberOfPages();
// 遍历每一页并添加二维码
for (int pageNum = 1; pageNum <= numberOfPages; pageNum++) {
// 获取页面尺寸
com.itextpdf.text.Rectangle pageSize = reader.getPageSize(pageNum);
float pageWidth = pageSize.getWidth();
float pageHeight = pageSize.getHeight();
// 根据页面方向确定二维码位置
float qrX, qrY;
float newWidth, newHeight;
// 判断页面方向:宽度大于高度为横版,否则为竖版
if (pageWidth > pageHeight) {
// 横版页面:二维码放在右下角
qrX = (pageWidth - 90); // 距离右边90点
qrY = 24; // 距离底部24点
newWidth = 67;
newHeight = 79;
} else {
// 竖版页面:二维码放在左上角
qrX = 226; // 距离左边226点
qrY = pageHeight - 185; // 距离顶部185点
newWidth = 69;
newHeight = 80;
}
PdfContentByte content = stamper.getOverContent(pageNum);
Image image = Image.getInstance(qrCodeBytes);
image.setAbsolutePosition(qrX, qrY); // 坐标:左下角为原点
image.scaleAbsolute(newWidth, newHeight); // 设置二维码大小
content.addImage(image);
}
} finally {
if (stamper != null) {
try {
stamper.close();
} catch (Exception e) {
// 忽略关闭异常
}
}
if (reader != null) {
try {
reader.close();
} catch (Exception e) {
// 忽略关闭异常
}
}
}
return pdfOut;
}
// public static void main(String[] args) {
// String path = "C:\\Users\\YuanJie\\Desktop\\test.pdf";
// String outputPath = "C:\\Users\\YuanJie\\Desktop\\test1.pdf";
//
// String params = "ID[" + 11111 + "] finish";
// byte[] bytes = PdfBoxQrCodeGenerator.generateQRCodeBytes(params);
//
// try {
// System.out.println("二维码字节大小: " + bytes.length + " 字节");
//
// // 在每一页添加二维码
// PdfBoxQrCodeGenerator.addQRCodeToPDFOnAllPages(path, outputPath, bytes, 450, 700);
//
// System.out.println("PDF文件已成功生成到: " + outputPath);
// System.out.println("生成的PDF大小: " + new File(outputPath).length() + " 字节");
//
// } catch (Exception e) {
// e.printStackTrace();
// System.out.println("图纸管理 => 审核结束,向文件添加二维码失败, 错误");
// }
// }
}

View File

@ -384,7 +384,7 @@ public class DesDrawingServiceImpl extends ServiceImpl<DesDrawingMapper, DesDraw
String params = "ID[" + drawing.getId() + "] finish";
byte[] bytes = PdfBoxQrCodeGenerator.generateQRCodeBytes(params);
try {
ByteArrayOutputStream baos = PdfBoxQrCodeGenerator.addQRCodeToPDF(ossVo.getUrl(), bytes, 1, 1510, 900);
ByteArrayOutputStream baos = PdfBoxQrCodeGenerator.addQRCodeToPDFOnAllPages(ossVo.getUrl(), bytes,1510, 900);
FileNameMap fileNameMap = URLConnection.getFileNameMap();
String originalName = drawing.getOriginalName();
String contentType = fileNameMap.getContentTypeFor(originalName);

View File

@ -106,7 +106,10 @@ public class OutTableController extends BaseController {
// 方法1: 使用 YearMonth 进行比较(推荐)
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM");
YearMonth yearMonth = YearMonth.parse(month, formatter);
//当前月
LocalDate now = LocalDate.now();
YearMonth yearMonth = YearMonth.parse(now.format(formatter));
YearMonth paramMonth = YearMonth.parse(month);
//构建返回数据
List<OutMonthlyConstructionVo> outMonthlyConstructionVos = new ArrayList<>();
@ -117,12 +120,12 @@ public class OutTableController extends BaseController {
vo.setProjectName(busProjectVo.getProjectName());
//1.总产值的计算 2.预计累计产值(截止当前月) 3.月预计产值
List<BusProject> subProjects = projectService.lambdaQuery().eq(BusProject::getPId, busProjectVo.getId()).list();
List<Long> subProjectIds = subProjects.stream().map(BusProject::getId).toList();
// List<BusProject> subProjects = projectService.lambdaQuery().eq(BusProject::getPId, busProjectVo.getId()).list();
// List<Long> subProjectIds = subProjects.stream().map(BusProject::getId).toList();
//所有分项工程
List<PgsProgressCategory> collect = pgsProgressCategories.stream()
.filter(category -> subProjectIds.contains(category.getProjectId())).toList();
// List<PgsProgressCategory> collect = pgsProgressCategories.stream()
// .filter(category -> subProjectIds.contains(category.getProjectId())).toList();
BigDecimal totalValue = BigDecimal.ZERO;
BigDecimal estimatedTotalValue = BigDecimal.ZERO;
@ -132,13 +135,10 @@ public class OutTableController extends BaseController {
for (OutMonthPlanAudit planAudit : planAudits) {
totalValue = totalValue.add(planAudit.getConstructionValue());
YearMonth planMonth = YearMonth.parse(planAudit.getPlanMonth(), formatter);
// 比较大小
if (planMonth.isBefore(yearMonth)) {
if(!planMonth.isAfter(yearMonth)){
estimatedTotalValue=estimatedTotalValue.add(planAudit.getConstructionValue());
} else if (planMonth.isAfter(yearMonth)) {
} else {
estimatedTotalValue = estimatedTotalValue.add(planAudit.getConstructionValue());
}
if(planMonth.equals(paramMonth)){
monthlyEstimatedValue = planAudit.getConstructionValue();
}
}
@ -388,28 +388,28 @@ public class OutTableController extends BaseController {
vo.setProjectId(projectVo.getId());
List<OutMonthPlan> list = monthPlanService.lambdaQuery().eq(OutMonthPlan::getProjectId, projectVo.getId())
List<OutMonthPlan> list = monthPlanService.lambdaQuery()
.eq(OutMonthPlan::getProjectId, projectVo.getId())
.eq(OutMonthPlan::getValueType, bo.getValueType())
.list();
// 计算累计完成值
BigDecimal totalValue = list.stream()
.map(OutMonthPlan::getCompleteValue)
BigDecimal totalValue = list.stream()
.map(OutMonthPlan::getPlanValue)
.filter(Objects::nonNull)
.reduce(BigDecimal.ZERO, BigDecimal::add);
vo.setTotalValue(totalValue);
OutMonthPlan matchedPlan = list.stream()
.filter(plan -> Objects.equals(plan.getValueType(), bo.getValueType())
&& Objects.equals(plan.getPlanMonth(), bo.getMonth()))
.filter(plan -> Objects.equals(plan.getPlanMonth(), bo.getMonth()))
.findFirst()
.orElse(null);
List<OutMonthPlan> matchedPlans = list.stream()
.filter(plan -> Objects.equals(plan.getValueType(), bo.getValueType())
&& plan.getPlanMonth() != null
&& bo.getMonth() != null
&& plan.getPlanMonth().compareTo(bo.getMonth()) <= 0)
.toList();
// List<OutMonthPlan> matchedPlans = list.stream()
// .filter(plan -> plan.getPlanMonth() != null
// && bo.getMonth() != null
// && plan.getPlanMonth().compareTo(bo.getMonth()) <= 0)
// .toList();
vo.setMonthEstimatedValue(matchedPlan == null? BigDecimal.ZERO :matchedPlan.getPlanValue());
@ -417,7 +417,7 @@ public class OutTableController extends BaseController {
vo.setMonthCompletionValue(matchedPlan == null? BigDecimal.ZERO :matchedPlan.getCompleteValue());
// 计算累计完成值
BigDecimal accumulatedCompleteValue = matchedPlans.stream()
BigDecimal accumulatedCompleteValue = list.stream()
.map(OutMonthPlan::getCompleteValue)
.filter(Objects::nonNull)
.reduce(BigDecimal.ZERO, BigDecimal::add);

View File

@ -71,5 +71,10 @@ public class OutMonthPlanBo extends BaseEntity {
*/
private String completeAuditStatus;
/**
* 是否设计
*/
private Boolean isDesign;
}

View File

@ -35,10 +35,7 @@ import org.dromara.out.service.IOutMonthPlanService;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.temporal.TemporalAdjusters;
import java.util.List;
import java.util.Map;
import java.util.Collection;
import java.util.Set;
import java.util.*;
import java.util.stream.Collectors;
/**
@ -142,6 +139,17 @@ public class OutMonthPlanServiceImpl extends ServiceImpl<OutMonthPlanMapper, Out
public Boolean updateByBo(OutMonthPlanBo bo) {
OutMonthPlan update = MapstructUtils.convert(bo, OutMonthPlan.class);
validEntityBeforeSave(update);
OutMonthPlan outMonthPlan = baseMapper.selectById(update.getId());
String status;
if(bo.getIsDesign()){
status = outMonthPlan.getPlanAuditStatus();
}else {
status = outMonthPlan.getCompleteAuditStatus();
}
if(Arrays.asList(BusinessStatusEnum.FINISH.getStatus(),BusinessStatusEnum.WAITING.getStatus()).contains(status)){
String msg = BusinessStatusEnum.WAITING.getStatus().equals(status) ? "计划正在审核中,请勿修改" : "计划已审核完成,请勿修改";
throw new ServiceException(msg);
}
return baseMapper.updateById(update) > 0;
}
@ -173,6 +181,17 @@ public class OutMonthPlanServiceImpl extends ServiceImpl<OutMonthPlanMapper, Out
if(isValid){
//TODO 做一些业务上的校验,判断是否需要校验
}
List<OutMonthPlan> outMonthPlans = baseMapper.selectList(Wrappers.<OutMonthPlan>lambdaQuery().in(OutMonthPlan::getId, ids));
if (!outMonthPlans.isEmpty()) {
List<String> statusList = outMonthPlans.stream().map(OutMonthPlan::getValueType).collect(Collectors.toList());
if (statusList.contains(BusinessStatusEnum.WAITING.getStatus())) {
throw new ServiceException("计划正在审核中,请勿删除");
}
if (statusList.contains(BusinessStatusEnum.FINISH.getStatus())) {
throw new ServiceException("计划已审核完成,请勿删除");
}
}
return baseMapper.deleteByIds(ids) > 0;
}

View File

@ -9,6 +9,7 @@ import org.dromara.contractor.service.ISubConstructionUserService;
import org.dromara.project.domain.dto.attendance.BusAttendancePunchCardByFaceReq;
import org.dromara.project.domain.vo.BusAttendanceRuleVo;
import org.dromara.project.domain.vo.BusAttendanceVo;
import org.dromara.project.domain.vo.BusMonthAttendanceVo;
import org.dromara.project.service.IBusAttendanceRuleService;
import org.dromara.project.service.IBusAttendanceService;
import org.springframework.validation.annotation.Validated;
@ -72,12 +73,28 @@ public class BusAttendanceAppController extends BaseController {
/**
* 获取用户当天打卡记录
*/
@PostMapping("/getTodayAttendance/{projectId}")
@GetMapping("/getTodayAttendance/{projectId}")
public R<List<BusAttendanceVo>> getTodayAttendance(@NotNull @PathVariable("projectId") Long projectId){
return R.ok(attendanceService.getTodayAttendance(projectId));
}
/**
* 获取用户指定月份打卡记录
*/
@GetMapping("/getMonthAttendance/{projectId}")
public R<List<BusMonthAttendanceVo>> getMonthAttendance(@NotNull @PathVariable("projectId") Long projectId,
@NotNull String month){
return R.ok(attendanceService.getMonthAttendance(projectId,month));
}
/**
* 获取用户打卡异常记录
*/
@GetMapping("/getAbnormalAttendance/{projectId}")
public R<List<BusAttendanceVo>> getAbnormalAttendance(@NotNull @PathVariable("projectId") Long projectId){
return R.ok(attendanceService.getAbnormalAttendance(projectId));
}

View File

@ -0,0 +1,42 @@
package org.dromara.project.domain.vo;
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.project.domain.BusAttendance;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
/**
* 考勤视图对象 bus_attendance
*
* @author Lion Li
* @date 2025-08-05
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = BusAttendance.class)
public class BusMonthAttendanceVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 打卡日期
*/
private LocalDate clockDate;
/**
* 考勤记录
*/
private List<BusAttendanceVo> list;
}

View File

@ -1,5 +1,6 @@
package org.dromara.project.service;
import jakarta.validation.constraints.NotNull;
import org.dromara.project.domain.dto.attendance.BusAttendancePunchCardByFaceReq;
import org.dromara.project.domain.vo.BusAttendanceVo;
import org.dromara.project.domain.bo.BusAttendanceBo;
@ -8,6 +9,8 @@ import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import com.baomidou.mybatisplus.extension.service.IService;
import org.dromara.project.domain.vo.BusMonthAttendanceVo;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.multipart.MultipartFile;
import java.util.Collection;
@ -108,4 +111,14 @@ public interface IBusAttendanceService extends IService<BusAttendance>{
* 获取用户当天打卡记录
*/
List<BusAttendanceVo> getTodayAttendance(Long projectId);
/**
* 获取用户指定月份的打卡记录
*/
List<BusMonthAttendanceVo> getMonthAttendance(Long projectId, String month);
/**
* 获取用户指定月份的打卡记录
*/
List<BusAttendanceVo> getAbnormalAttendance(@NotNull @PathVariable("projectId") Long projectId);
}

View File

@ -30,6 +30,7 @@ import org.dromara.project.domain.dto.attendance.BusAttendancePunchCardByFaceReq
import org.dromara.project.domain.enums.BusAttendanceClockStatusEnum;
import org.dromara.project.domain.enums.BusAttendanceCommuterEnum;
import org.dromara.project.domain.vo.BusAttendanceRuleVo;
import org.dromara.project.domain.vo.BusMonthAttendanceVo;
import org.dromara.project.domain.vo.BusProjectPunchrangeVo;
import org.dromara.project.service.*;
import org.dromara.system.domain.vo.SysOssVo;
@ -401,6 +402,73 @@ public class BusAttendanceServiceImpl extends ServiceImpl<BusAttendanceMapper, B
);
}
@Override
public List<BusMonthAttendanceVo> getMonthAttendance(Long projectId, String month) {
// 解析月份字符串获取开始和结束日期
LocalDate startDate = LocalDate.parse(month + "-01");
LocalDate endDate = startDate.withDayOfMonth(startDate.lengthOfMonth());
// 获取当前用户ID
Long userId = LoginHelper.getUserId();
List<BusUserProjectRelevancy> relevancyList = userProjectRelevancyService.list(
Wrappers.lambdaQuery(BusUserProjectRelevancy.class)
.eq(BusUserProjectRelevancy::getProjectId, projectId)
.eq(BusUserProjectRelevancy::getUserId, userId));
boolean b = relevancyList.stream().allMatch(relevancy -> "1".equals(relevancy.getUserType()));
// 查询该月的所有考勤记录
List<BusAttendanceVo> attendanceList = baseMapper.selectVoList(Wrappers.lambdaQuery(BusAttendance.class)
.eq(BusAttendance::getUserId, userId)
.eq(b, BusAttendance::getProjectId, projectId)
.ge(BusAttendance::getClockDate, startDate)
.le(BusAttendance::getClockDate, endDate)
.orderByAsc(BusAttendance::getClockDate));
// 按日期分组考勤记录
Map<LocalDate, List<BusAttendanceVo>> attendanceMap = attendanceList.stream()
.collect(Collectors.groupingBy(BusAttendanceVo::getClockDate));
// 生成每日考勤记录
List<BusMonthAttendanceVo> result = new ArrayList<>();
LocalDate currentDate = startDate;
while (!currentDate.isAfter(endDate)) {
BusMonthAttendanceVo busMonthAttendanceVo = new BusMonthAttendanceVo();
busMonthAttendanceVo.setClockDate(currentDate);
busMonthAttendanceVo.setList(attendanceMap.getOrDefault(currentDate, Collections.emptyList()));
result.add(busMonthAttendanceVo);
currentDate = currentDate.plusDays(1);
}
return result;
}
@Override
public List<BusAttendanceVo> getAbnormalAttendance(Long projectId) {
List<String> abnormalList = Arrays.asList(BusAttendanceClockStatusEnum.LATE.getValue(),
BusAttendanceClockStatusEnum.LEAVEEARLY.getValue(),
BusAttendanceClockStatusEnum.UNCLOCK.getValue());
// 获取当前用户ID
Long userId = LoginHelper.getUserId();
List<BusUserProjectRelevancy> relevancyList = userProjectRelevancyService.list(
Wrappers.lambdaQuery(BusUserProjectRelevancy.class)
.eq(BusUserProjectRelevancy::getProjectId, projectId)
.eq(BusUserProjectRelevancy::getUserId, userId));
boolean b = relevancyList.stream().allMatch(relevancy -> "1".equals(relevancy.getUserType()));
return baseMapper.selectVoList(Wrappers.lambdaQuery(BusAttendance.class)
.eq(BusAttendance::getUserId, userId)
.eq(b, BusAttendance::getProjectId, projectId)
.in(BusAttendance::getClockStatus, abnormalList)
);
}
/**
* 计算打卡时间归属的考勤日(关键:解决跨天场景的日期映射)
*/

View File

@ -326,8 +326,6 @@ public class BusReissueCardServiceImpl extends ServiceImpl<BusReissueCardMapper,
}
save(bean);
return bean.getId();
}
@ -357,6 +355,13 @@ public class BusReissueCardServiceImpl extends ServiceImpl<BusReissueCardMapper,
if (processEvent.getSubmit()) {
busReissueCard.setStatus(BusinessStatusEnum.WAITING.getStatus());
}
if(BusinessStatusEnum.FINISH.getStatus().equals(processEvent.getStatus())){
BusAttendance byId = attendanceService.getById(busReissueCard.getAttendanceId());
byId.setClockStatus(BusAttendanceClockStatusEnum.REISSUE.getValue());
}
if(processEvent.getSubmit())
baseMapper.updateById(busReissueCard);
}