考勤,土地
This commit is contained in:
		@ -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("图纸管理 => 审核结束,向文件添加二维码失败, 错误");
 | 
			
		||||
//        }
 | 
			
		||||
//    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
 | 
			
		||||
@ -71,5 +71,10 @@ public class OutMonthPlanBo extends BaseEntity {
 | 
			
		||||
     */
 | 
			
		||||
    private String completeAuditStatus;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 是否设计
 | 
			
		||||
     */
 | 
			
		||||
    private Boolean isDesign;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -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);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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)
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 计算打卡时间归属的考勤日(关键:解决跨天场景的日期映射)
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user