e8d08ebdd187f99f793cd839038dd5c392cdfbd8..469a106cf617efd2e9fbe02937c355fa392ae7bc
3 天以前 gongchunyi
feat: 消息通知添加APP跳转路由
469a10 对比 | 目录
3 天以前 zss
api描述调整
31caf3 对比 | 目录
3 天以前 huminmin
修改打卡状态导出
ef2ee8 对比 | 目录
3 天以前 huminmin
生成缺勤记录
ee9530 对比 | 目录
3 天以前 huminmin
生成缺勤记录
ef75cf 对比 | 目录
3 天以前 huminmin
Merge branch 'dev_New' of http://114.132.189.42:9002/r/product-inventory-ma...
53b971 对比 | 目录
3 天以前 huminmin
生成缺勤记录
70aea4 对比 | 目录
3 天以前 zss
手动新增不合格处理的数据不合格处理的逻辑调整
537264 对比 | 目录
3 天以前 gongchunyi
fix: 产品规格型号导入与下载模版
09a694 对比 | 目录
3 天以前 zss
生产订单删除校验+生产报工记录加上工序名称字段
0f6048 对比 | 目录
3 天以前 zss
报工记录删除校验
6bad0b 对比 | 目录
3 天以前 huminmin
根据员工搜索考勤记录
4740ad 对比 | 目录
3 天以前 huminmin
导出考勤记录
a12abb 对比 | 目录
3 天以前 zss
报工记录删除校验
6f155a 对比 | 目录
3 天以前 zss
质检的删除和提交逻辑校验
e02539 对比 | 目录
3 天以前 maven
Merge remote-tracking branch 'origin/dev_New' into dev_New
a5d406 对比 | 目录
3 天以前 maven
yys 路由返回app组件路径
b3b6d1 对比 | 目录
3 天以前 gongchunyi
fix: 人员薪资导出接口缺少部门、岗位名称
ed01d6 对比 | 目录
已添加2个文件
已修改36个文件
642 ■■■■ 文件已修改
doc/20260209_create_personal_attendance_records.sql 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/controller/ProductController.java 22 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/dto/ProductModelExportDto.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/IProductModelService.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/impl/ProductModelServiceImpl.java 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/compensationperformance/controller/CompensationPerformanceController.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/compensationperformance/mapper/CompensationPerformanceMapper.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/compensationperformance/service/CompensationPerformanceService.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/compensationperformance/service/impl/CompensationPerformanceServiceImpl.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/controller/InspectionTaskController.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/controller/TimingTaskController.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/ProductOrderController.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/ProductionProductMainController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/dto/ProductionProductMainDto.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductOrderServiceImpl.java 104 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductionProductMainServiceImpl.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/domain/SysNotice.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/domain/vo/RouterVo.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/service/impl/SysMenuServiceImpl.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/service/impl/SysNoticeServiceImpl.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/service/impl/UnipushService.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/controller/QualityInspectController.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/mapper/QualityUnqualifiedMapper.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/service/impl/QualityInspectServiceImpl.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/service/impl/QualityUnqualifiedServiceImpl.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/controller/PersonalAttendanceRecordsController.java 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/dto/PersonalAttendanceRecordsDto.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/mapper/PersonalAttendanceRecordsMapper.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/pojo/PersonalAttendanceRecords.java 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/service/PersonalAttendanceRecordsService.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/service/impl/PersonalAttendanceRecordsServiceImpl.java 112 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/service/impl/StaffOnJobServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/task/PersonalAttendanceRecordsTask.java 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/compensationperformance/CompensationPerformanceMapper.xml 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductionProductMainMapper.xml 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/quality/QualityUnqualifiedMapper.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/staff/PersonalAttendanceRecordsMapper.xml 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/system/SysNoticeMapper.xml 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/20260209_create_personal_attendance_records.sql
@@ -13,5 +13,6 @@
    tenant_id       bigint not null comment '租户id',
    create_time     datetime null comment '录入时间',
    update_time     datetime null comment '更新时间',
    index idx_staff_on_job_id (staff_on_job_id)
    index idx_staff_on_job_id (staff_on_job_id),
    unique idx_staff_on_job_id_date (staff_on_job_id, date)
);
src/main/java/com/ruoyi/basic/controller/ProductController.java
@@ -5,10 +5,12 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.basic.dto.ProductDto;
import com.ruoyi.basic.dto.ProductModelDto;
import com.ruoyi.basic.dto.ProductModelExportDto;
import com.ruoyi.basic.dto.ProductTreeDto;
import com.ruoyi.basic.pojo.ProductModel;
import com.ruoyi.basic.service.IProductModelService;
import com.ruoyi.basic.service.IProductService;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.controller.BaseController;
@@ -23,6 +25,7 @@
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
@RestController
@@ -124,9 +127,20 @@
    /**
     * å¯¼å…¥äº§å“
     */
    @Log(title = "导入产品",businessType = BusinessType.IMPORT)
    @PostMapping("import")
    public AjaxResult importProduct(MultipartFile file) {
        return AjaxResult.success(productModelService.importProduct(file));
    @PostMapping("/import")
    @Log(title = "导入产品", businessType = BusinessType.IMPORT)
    public AjaxResult importProductModel(@RequestParam("file") MultipartFile file, Integer productId) {
        return productModelService.importProductModel(file, productId);
    }
    /**
     * äº§å“å¯¼å…¥æ¨¡æ¿
     */
    @GetMapping("/export")
    @ApiOperation("产品导入模板")
    @Log(title = "产品导入模板", businessType = BusinessType.EXPORT)
    public void importProduct(HttpServletResponse response) {
        ExcelUtil<ProductModelExportDto> excelUtil = new ExcelUtil<>(ProductModelExportDto.class);
        excelUtil.importTemplateExcel(response, "产品规格导入模板");
    }
}
src/main/java/com/ruoyi/basic/dto/ProductModelExportDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
package com.ruoyi.basic.dto;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import lombok.Data;
/**
 * <br>
 * äº§å“å¯¼å‡ºæ¨¡æ¿
 * </br>
 *
 * @author deslrey
 * @version 1.0
 * @since 2026/02/10 14:39
 */
@Data
public class ProductModelExportDto {
    @Excel(name = "规格型号")
    private String model;
    @Excel(name = "单位")
    private String unit;
}
src/main/java/com/ruoyi/basic/service/IProductModelService.java
@@ -6,6 +6,7 @@
import com.ruoyi.basic.dto.ProductDto;
import com.ruoyi.basic.dto.ProductModelDto;
import com.ruoyi.basic.pojo.ProductModel;
import com.ruoyi.framework.web.domain.AjaxResult;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
@@ -33,5 +34,5 @@
     */
    IPage<ProductModel> modelListPage(Page page , ProductDto productDto);
    Boolean importProduct(MultipartFile file);
    AjaxResult importProductModel(MultipartFile file, Integer productId);
}
src/main/java/com/ruoyi/basic/service/impl/ProductModelServiceImpl.java
@@ -12,17 +12,17 @@
import com.ruoyi.basic.pojo.Product;
import com.ruoyi.basic.pojo.ProductModel;
import com.ruoyi.basic.service.IProductModelService;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.common.utils.uuid.IdUtils;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
import com.ruoyi.sales.pojo.SalesLedgerProduct;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
@@ -47,13 +47,12 @@
        if (productModelDto.getId() == null) {
            ProductModel productModel = new ProductModel();
            BeanUtils.copyProperties(productModelDto,productModel);
            BeanUtils.copyProperties(productModelDto, productModel);
            return productModelMapper.insert(productModel);
        } else {
            return productModelMapper.updateById(productModelDto);
        }
    }
    @Override
@@ -76,6 +75,7 @@
    /**
     * æ ¹æ®id查询产品规格分页查询
     *
     * @param page
     * @param productDto
     * @return
@@ -88,24 +88,43 @@
    }
    @Override
    public Boolean importProduct(MultipartFile file) {
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult importProductModel(MultipartFile file, Integer productId) {
        if (productId == null) {
            return AjaxResult.error("请先选择产品再导入规格型号");
        }
        Product product = productMapper.selectById(productId);
        if (product == null) {
            return AjaxResult.error("选择的产品不存在");
        }
        try {
            ExcelUtil<ProductModel> productModelExcelUtil = new ExcelUtil<>(ProductModel.class);
            List<ProductModel> productModelList = productModelExcelUtil.importExcel(file.getInputStream());
            Map<String, List<ProductModel>> collect = productModelList.stream().collect(Collectors.groupingBy(ProductModel::getProductName));
            collect.forEach((k,v)->{
                Product product = productMapper.selectOne(new LambdaQueryWrapper<Product>().eq(Product::getProductName, k).last("LIMIT 1"));
                if (product != null) {
                    v.forEach(productModel -> {
                        productModel.setProductId(product.getId());
                    });
                    this.saveOrUpdateBatch(v);
            if (productModelList == null || productModelList.isEmpty()) {
                return AjaxResult.error("导入数据不能为空");
            }
            for (int i = 0; i < productModelList.size(); i++) {
                ProductModel item = productModelList.get(i);
                int rowNum = i + 2;
                if (StringUtils.isEmpty(item.getModel())) {
                    return AjaxResult.error("第 " + rowNum + " è¡Œå¯¼å…¥å¤±è´¥: [规格型号] ä¸èƒ½ä¸ºç©º");
                }
            });
            return true;
        }catch (Exception e) {
            e.printStackTrace();
                if (StringUtils.isEmpty(item.getUnit())) {
                    return AjaxResult.error("第 " + rowNum + " è¡Œå¯¼å…¥å¤±è´¥: [单位] ä¸èƒ½ä¸ºç©º");
                }
                item.setProductId(product.getId());
            }
            saveOrUpdateBatch(productModelList);
            return AjaxResult.success("成功导入 " + productModelList.size() + " æ¡æ•°æ®");
        } catch (Exception e) {
            log.error("导入产品规格异常", e);
            return AjaxResult.error("导入失败");
        }
        return false;
    }
}
src/main/java/com/ruoyi/compensationperformance/controller/CompensationPerformanceController.java
@@ -82,7 +82,7 @@
    @Log(title = "导出薪资管理列表", businessType = BusinessType.EXPORT)
    @PostMapping("/export")
    public void export(HttpServletResponse response) {
        List<CompensationPerformance> list = compensationPerformanceService.list();
        List<CompensationPerformance> list = compensationPerformanceService.exportList();
        ExcelUtil<CompensationPerformance> util = new ExcelUtil<>(CompensationPerformance.class);
        util.exportExcel(response, list, "导出薪资管理列表");
    }
src/main/java/com/ruoyi/compensationperformance/mapper/CompensationPerformanceMapper.java
@@ -6,6 +6,8 @@
import com.ruoyi.compensationperformance.pojo.CompensationPerformance;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
 * @author :yys
 * @date : 2025/8/8 9:54
@@ -18,7 +20,14 @@
     * @param page       åˆ†é¡µ
     * @param staffName  å‘˜å·¥å§“名
     * @param payDateStr è–ªèµ„日期
     * @return
     * @return æŸ¥è¯¢çš„人员薪资信息
     */
    IPage<CompensationPerformance> listPage(Page page, @Param("staffName") String staffName, @Param("payDateStr") String payDateStr);
    /**
     * å¯¼å‡ºäººå‘˜è–ªèµ„
     *
     * @return äººå‘˜è–ªèµ„信息
     */
    List<CompensationPerformance> exportList();
}
src/main/java/com/ruoyi/compensationperformance/service/CompensationPerformanceService.java
@@ -5,6 +5,8 @@
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.compensationperformance.pojo.CompensationPerformance;
import java.util.List;
/**
 * @author :yys
 * @date : 2025/8/8 9:55
@@ -21,4 +23,10 @@
     */
    IPage<CompensationPerformance> listPage(Page page, String staffName, String payDateStr);
    /**
     * å¯¼å‡ºäººå‘˜æ–°å¢ž
     *
     * @return äººå‘˜è–ªèµ„
     */
    List<CompensationPerformance> exportList();
}
src/main/java/com/ruoyi/compensationperformance/service/impl/CompensationPerformanceServiceImpl.java
@@ -10,6 +10,8 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
 * @author :yys
 * @date : 2025/8/8 9:55
@@ -27,4 +29,8 @@
        return compensationPerformanceMapper.listPage(page, staffName, payDateStr);
    }
    @Override
    public List<CompensationPerformance> exportList() {
        return compensationPerformanceMapper.exportList();
    }
}
src/main/java/com/ruoyi/inspectiontask/controller/InspectionTaskController.java
@@ -22,7 +22,7 @@
 * @date : 2025/9/19 10:52
 */
@RestController
@Api(tags = "巡检任务管理")
@Api(tags = "巡检任务记录")
@RequestMapping("/inspectionTask")
public class InspectionTaskController extends BaseController {
src/main/java/com/ruoyi/inspectiontask/controller/TimingTaskController.java
@@ -23,7 +23,7 @@
 * @date : 2025/9/19 10:53
 */
@RestController
@Api(tags = "定时任务管理")
@Api(tags = "巡检管理")
@RequestMapping("/timingTask")
public class TimingTaskController extends BaseController {
src/main/java/com/ruoyi/production/controller/ProductOrderController.java
@@ -53,29 +53,6 @@
    @PostMapping("/export")
    public void export(HttpServletResponse response, ProductOrderDto productOrderDto) {
        List<ProductOrderDto> list = productOrderService.pageProductOrder(new Page<>(1, -1), productOrderDto).getRecords();
        if (list != null && !list.isEmpty()) {
            list.forEach(item -> {
                // åˆ¤ç©º
                if (item.getQuantity() == null || item.getCompleteQuantity() == null) {
                    item.setCompletionStatus(BigDecimal.ZERO);
                    return;
                }
                // åˆ¤é›¶
                if (item.getQuantity().compareTo(BigDecimal.ZERO) == 0) {
                    item.setCompletionStatus(BigDecimal.ZERO);
                    return;
                }
                BigDecimal progress = item.getCompleteQuantity()
                        .divide(item.getQuantity(), 4, BigDecimal.ROUND_HALF_UP)
                        .multiply(new BigDecimal(100))
                        .setScale(2, BigDecimal.ROUND_HALF_UP);
                item.setCompletionStatus(progress);
            });
        }
        ExcelUtil<ProductOrderDto> util = new ExcelUtil<>(ProductOrderDto.class);
        util.exportExcel(response, list, "生产订单数据");
    }
src/main/java/com/ruoyi/production/controller/ProductionProductMainController.java
@@ -48,9 +48,7 @@
    @ApiOperation("删除报工")
    @DeleteMapping("/delete")
    @Transactional(rollbackFor = Exception.class)
    public R delete(@RequestBody ProductionProductMainDto productionProductMainDto) {
        return R.ok(productionProductMainService.removeProductMain(productionProductMainDto.getId()));
    }
src/main/java/com/ruoyi/production/dto/ProductionProductMainDto.java
@@ -55,6 +55,8 @@
    private LocalDate schedulingDate;
    private String schedulingUserName;
    private String customerName;
    //工序
    @Excel(name = "工序")
    private String process;
    private BigDecimal workHours;
    private BigDecimal wages;
src/main/java/com/ruoyi/production/service/impl/ProductOrderServiceImpl.java
@@ -4,6 +4,7 @@
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.enums.StockOutQualifiedRecordTypeEnum;
@@ -145,92 +146,27 @@
    @Override
    public Boolean delete(Long[] ids) {
        //批量查询productOrder
        List<ProductOrder> productOrders = productOrderMapper.selectList(
                new LambdaQueryWrapper<ProductOrder>()
                        .in(ProductOrder::getId, ids)
        );
        if (!org.springframework.util.CollectionUtils.isEmpty(productOrders)) {
            // æ‰¹é‡æŸ¥è¯¢processRouteItems
            List<ProductProcessRouteItem> allRouteItems = productProcessRouteItemMapper.selectList(
                    new LambdaQueryWrapper<ProductProcessRouteItem>()
                            .in(ProductProcessRouteItem::getProductOrderId, ids)
            );
            if (!com.baomidou.mybatisplus.core.toolkit.CollectionUtils.isEmpty(allRouteItems)) {
                // èŽ·å–è¦åˆ é™¤çš„å·¥åºé¡¹ID
                List<Long> routeItemIds = allRouteItems.stream()
                        .map(ProductProcessRouteItem::getId)
                        .collect(Collectors.toList());
                // æŸ¥è¯¢å…³è”的工单ID
                List<ProductWorkOrder> workOrders = productWorkOrderMapper.selectList(
                        new LambdaQueryWrapper<ProductWorkOrder>()
                                .in(ProductWorkOrder::getProductProcessRouteItemId, routeItemIds)
                );
                if (!com.baomidou.mybatisplus.core.toolkit.CollectionUtils.isEmpty(workOrders)) {
                    List<Long> workOrderIds = workOrders.stream()
                            .map(ProductWorkOrder::getId)
                            .collect(Collectors.toList());
                    // æŸ¥è¯¢å…³è”的生产主表ID
                    List<ProductionProductMain> productMains = productionProductMainMapper.selectList(
                            new LambdaQueryWrapper<ProductionProductMain>()
                                    .in(ProductionProductMain::getWorkOrderId, workOrderIds)
                    );
                    List<Long> productMainIds = productMains.stream()
                            .map(ProductionProductMain::getId)
                            .collect(Collectors.toList());
                    // åˆ é™¤äº§å‡ºè¡¨ã€æŠ•入表数据
                    if (!com.baomidou.mybatisplus.core.toolkit.CollectionUtils.isEmpty(productMainIds)) {
                        productionProductOutputMapper.deleteByProductMainIds(productMainIds);
                        productionProductInputMapper.deleteByProductMainIds(productMainIds);
                        List<QualityInspect> qualityInspects = qualityInspectMapper.selectList(
                                new LambdaQueryWrapper<QualityInspect>()
                                        .in(QualityInspect::getProductMainId, productMainIds)
                        );
                        //删除出库记录
                        for (Long productMainId : productMainIds) {
                            //删除生产出库记录
                            stockUtils.deleteStockOutRecord(productMainId, StockOutQualifiedRecordTypeEnum.PRODUCTION_REPORT_STOCK_OUT.getCode());
                            //删除报废的入库记录
                            stockUtils.deleteStockInRecord(productMainId, StockInUnQualifiedRecordTypeEnum.PRODUCTION_SCRAP.getCode());
                        }
                        qualityInspects.forEach(qualityInspect -> {
                            //inspectState=1 å·²æäº¤ ä¸èƒ½åˆ é™¤
                            if (qualityInspect.getInspectState() == 1) {
                                throw new RuntimeException("已提交的检验单不能删除");
                            }
                        });
                        qualityInspectMapper.deleteByProductMainIds(productMainIds);
                        salesLedgerProductionAccountingMapper.delete(new LambdaQueryWrapper<SalesLedgerProductionAccounting>()
                                .in(SalesLedgerProductionAccounting::getProductMainId, productMainIds));
                    }
                    // åˆ é™¤ç”Ÿäº§ä¸»è¡¨æ•°æ®
                    productionProductMainMapper.deleteByWorkOrderIds(workOrderIds);
                    // åˆ é™¤å·¥å•数据
                    productWorkOrderMapper.delete(new LambdaQueryWrapper<ProductWorkOrder>()
                            .in(ProductWorkOrder::getProductProcessRouteItemId, routeItemIds));
                }
        //如果已经开始生产,不能删除
        //查询生产订单下的工单
        List<ProductWorkOrder> productWorkOrders = productWorkOrderMapper.selectList(Wrappers.<ProductWorkOrder>lambdaQuery().in(ProductWorkOrder::getProductOrderId, ids));
        if (productWorkOrders.size()>0){
            //判断是否有报工数据
            List<ProductionProductMain> productionProductMains = productionProductMainMapper.selectList(Wrappers.<ProductionProductMain>lambdaQuery()
                    .in(ProductionProductMain::getWorkOrderId, productWorkOrders.stream().map(ProductWorkOrder::getId).collect(Collectors.toList())));
            if (productionProductMains.size()>0){
                throw new RuntimeException("生产订单已经开始生产,不能删除");
            }
            // æ‰¹é‡åˆ é™¤processRouteItem
            productProcessRouteItemMapper.delete(new LambdaQueryWrapper<ProductProcessRouteItem>()
                    .in(ProductProcessRouteItem::getProductOrderId, ids));
            // æ‰¹é‡åˆ é™¤productProcessRoute
            productProcessRouteMapper.delete(new LambdaQueryWrapper<ProductProcessRoute>()
                    .in(ProductProcessRoute::getProductOrderId, ids));
            // æ‰¹é‡åˆ é™¤productOrder
            productOrderMapper.delete(new LambdaQueryWrapper<ProductOrder>()
                    .in(ProductOrder::getId, ids));
            //删除工单
            productWorkOrderMapper.delete(Wrappers.<ProductWorkOrder>lambdaQuery().in(ProductWorkOrder::getProductOrderId, ids));
        }
        //删除工艺路线
        productProcessRouteItemMapper.delete(new LambdaQueryWrapper<ProductProcessRouteItem>()
                .in(ProductProcessRouteItem::getProductOrderId, ids));
        productProcessRouteMapper.delete(new LambdaQueryWrapper<ProductProcessRoute>()
                .in(ProductProcessRoute::getProductOrderId, ids));
        //删除生产订单
        productOrderMapper.delete(new LambdaQueryWrapper<ProductOrder>()
                .in(ProductOrder::getId, ids));
        return true;
    }
src/main/java/com/ruoyi/production/service/impl/ProductionProductMainServiceImpl.java
@@ -39,6 +39,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Service
@AllArgsConstructor
@@ -60,6 +61,7 @@
    private ProductModelMapper productModelMapper;
    private QualityInspectMapper qualityInspectMapper;
    private QualityUnqualifiedMapper qualityUnqualifiedMapper;
    private ProductProcessMapper productProcessMapper;
    private ProductProcessRouteMapper productProcessRouteMapper;
@@ -255,8 +257,16 @@
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean removeProductMain(Long id) {
        //判断该条报工是否不合格处理,如果不合格处理了,则不允许删除
        List<QualityInspect> qualityInspects = qualityInspectMapper.selectList(Wrappers.<QualityInspect>lambdaQuery().eq(QualityInspect::getProductMainId, id));
        if (qualityInspects.size() > 0){
            List<QualityUnqualified> qualityUnqualifieds = qualityUnqualifiedMapper.selectList(Wrappers.<QualityUnqualified>lambdaQuery()
                    .in(QualityUnqualified::getInspectId, qualityInspects.stream().map(QualityInspect::getId).collect(Collectors.toList())));
            if (qualityUnqualifieds.size() > 0 && qualityUnqualifieds.get(0).getInspectState()==1) {
                throw new ServiceException("该条报工已经不合格处理了,不允许删除");
            }
        }
        ProductionProductMain productionProductMain = productionProductMainMapper.selectById(id);
        //该报工对应的工艺路线详情
        ProductProcessRouteItem productProcessRouteItem = productProcessRouteItemMapper.selectById(productionProductMain.getProductProcessRouteItemId());
@@ -281,7 +291,6 @@
        } else {
            throw new ServiceException("操作失败:工单信息或产出记录不存在");
        }
        //判断是否是最后一道工序
        List<ProductProcessRouteItem> productProcessRouteItems = productProcessRouteItemMapper.selectList(Wrappers.<ProductProcessRouteItem>lambdaQuery().eq(ProductProcessRouteItem::getProductRouteId, productProcessRouteItem.getProductRouteId()));
        if (productProcessRouteItem.getDragSort() != null && productProcessRouteItems != null && productProcessRouteItem.getDragSort() == productProcessRouteItems.size()) {
@@ -290,15 +299,10 @@
                BigDecimal orderCompleteQty = productOrder.getCompleteQuantity() == null ? BigDecimal.ZERO : productOrder.getCompleteQuantity();
                BigDecimal totalQty = productionProductOutput.getQuantity() != null ? productionProductOutput.getQuantity() : BigDecimal.ZERO;
                BigDecimal scrapQty = productionProductOutput.getScrapQty() != null ? productionProductOutput.getScrapQty() : BigDecimal.ZERO;
                BigDecimal actualQualifiedQty = totalQty.subtract(scrapQty);
                BigDecimal newCompleteQty = orderCompleteQty.subtract(actualQualifiedQty);
                productOrder.setCompleteQuantity(newCompleteQty.compareTo(BigDecimal.ZERO) < 0 ? BigDecimal.ZERO : newCompleteQty);
                productOrder.setEndTime(null);
                productOrderMapper.updateById(productOrder);
            } else {
                throw new ServiceException("关联的生产订单不存在");
src/main/java/com/ruoyi/project/system/domain/SysNotice.java
@@ -48,6 +48,9 @@
    /** è·³è½¬è·¯å¾„ */
    private String jumpPath;
    /** APP跳转路径 */
    private String appJumpPath;
    /** åˆ›å»ºè€… */
    @TableField(fill = FieldFill.INSERT)
    private String createBy;
src/main/java/com/ruoyi/project/system/domain/vo/RouterVo.java
@@ -36,6 +36,20 @@
     */
    private String component;
    public String getAppComponent() {
        return appComponent;
    }
    public void setAppComponent(String appComponent) {
        this.appComponent = appComponent;
    }
    /**
     * app组件地址
     */
    private String appComponent;
    /**
     * è·¯ç”±å‚数:如 {"id": 1, "name": "ry"}
     */
src/main/java/com/ruoyi/project/system/service/impl/SysMenuServiceImpl.java
@@ -172,6 +172,7 @@
            router.setName(getRouteName(menu));
            router.setPath(getRouterPath(menu));
            router.setComponent(getComponent(menu));
            router.setAppComponent(menu.getAppComponent());
            router.setQuery(menu.getQuery());
            router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), StringUtils.equals("1", menu.getIsCache()), menu.getPath()));
            List<SysMenu> cMenus = menu.getChildren();
@@ -187,6 +188,7 @@
                List<RouterVo> childrenList = new ArrayList<RouterVo>();
                RouterVo children = new RouterVo();
                children.setPath(menu.getPath());
                children.setAppComponent(menu.getAppComponent());
                children.setComponent(menu.getComponent());
                children.setName(getRouteName(menu.getRouteName(), menu.getPath()));
                children.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), StringUtils.equals("1", menu.getIsCache()), menu.getPath()));
@@ -202,6 +204,7 @@
                RouterVo children = new RouterVo();
                String routerPath = innerLinkReplaceEach(menu.getPath());
                children.setPath(routerPath);
                children.setAppComponent(menu.getAppComponent());
                children.setComponent(UserConstants.INNER_LINK);
                children.setName(getRouteName(menu.getRouteName(), routerPath));
                children.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), menu.getPath()));
src/main/java/com/ruoyi/project/system/service/impl/SysNoticeServiceImpl.java
@@ -144,10 +144,12 @@
    }
    @Override
    public void simpleNoticeByUser(String title, String message,  List<Long> consigneeId, String jumpPath) {
    public void simpleNoticeByUser(String title, String message, List<Long> consigneeId, String jumpPath) {
        Long userId = SecurityUtils.getLoginUser().getUserId();
        Long tenantId = SecurityUtils.getLoginUser().getTenantId();
        List<SysNotice> sysNotices = consigneeId.stream().map(it -> convertSysNotice(title, message, it,tenantId, jumpPath, userId)).collect(Collectors.toList());
        List<SysNotice> sysNotices = consigneeId.stream()
                .map(it -> convertSysNotice(title, message, it, tenantId, jumpPath, unipushService.convertWebPathToAppPath(jumpPath), userId))
                .collect(Collectors.toList());
        sysNoticeService.saveBatch(sysNotices);
        try {
            unipushService.sendClientMessage(sysNotices);
@@ -200,6 +202,7 @@
                        it.getUserId(),
                        it.getTenantId(),
                        jumpPath,
                        unipushService.convertWebPathToAppPath(jumpPath),
                        userId
                ))
                .collect(Collectors.toList());
@@ -213,7 +216,7 @@
    }
    private SysNotice convertSysNotice(String title,String message,Long consigneeId, Long tenantId,String jumpPath,Long currentUserId) {
    private SysNotice convertSysNotice(String title,String message,Long consigneeId, Long tenantId,String jumpPath,String appJumpPath,Long currentUserId) {
        SysNotice sysNotice = new SysNotice();
        sysNotice.setNoticeType("1");
        sysNotice.setNoticeTitle(title);//标题
@@ -222,6 +225,7 @@
        sysNotice.setConsigneeId(consigneeId);
        sysNotice.setSenderId(currentUserId);
        sysNotice.setJumpPath(jumpPath);
        sysNotice.setAppJumpPath(appJumpPath);
        sysNotice.setTenantId(tenantId);
        return sysNotice;
    }
src/main/java/com/ruoyi/project/system/service/impl/UnipushService.java
@@ -95,7 +95,7 @@
    /**
     * å°† Web ç«¯åˆ†å±‚全路径转换为 App ç«¯ç»„件路由
     */
    private String convertWebPathToAppPath(String webPath) {
    public String convertWebPathToAppPath(String webPath) {
        if (StringUtils.isEmpty(webPath)) {
            return DEFAULT_APP_PAGE;
        }
src/main/java/com/ruoyi/quality/controller/QualityInspectController.java
@@ -63,16 +63,19 @@
        if(CollectionUtils.isEmpty(ids)){
            return AjaxResult.error("请选择至少一条数据");
        }
        //如果已经提交就不允许删除
        List<QualityInspect> qualityInspects = qualityInspectService.listByIds(ids);
        for (QualityInspect qualityInspect : qualityInspects) {
            if(qualityInspect.getInspectState()==1){
               throw new RuntimeException("已提交的数据不允许删除");
            }
        }
        //删除检验参数
        qualityInspectParamService.remove(Wrappers.<QualityInspectParam>lambdaQuery()
        .in(QualityInspectParam::getInspectId,ids));
        //删除检验附件
        qualityInspectFileService.remove(Wrappers.<QualityInspectFile>lambdaQuery()
        .in(QualityInspectFile::getInspectId,ids));
        //删除入库记录
        for (Integer id : ids) {
            stockUtils.deleteStockInRecord(Long.valueOf(id), StockInQualifiedRecordTypeEnum.QUALITYINSPECT_STOCK_IN.getCode());
        }
        //删除检验单
        return AjaxResult.success(qualityInspectService.removeBatchByIds(ids));
    }
src/main/java/com/ruoyi/quality/mapper/QualityUnqualifiedMapper.java
@@ -19,4 +19,7 @@
    List<QualityUnqualified> qualityUnqualifiedExport(@Param("qualityUnqualified") QualityUnqualified qualityUnqualified);
    QualityUnqualified getUnqualified(@Param("id") Integer id);
    //手动新增不合格的时候,根据产品名称和规格型号查出对应的规格型号id
    Long getModelId(@Param("productName") String productName, @Param("model") String model);
}
src/main/java/com/ruoyi/quality/service/impl/QualityInspectServiceImpl.java
@@ -82,8 +82,12 @@
    @Override
    public int submit(QualityInspect inspect) {
        QualityInspect qualityInspect = qualityInspectMapper.selectById(inspect.getId());
        //提交前必须判断是否合格
        if (ObjectUtils.isNull(qualityInspect.getCheckResult())) {
            throw new RuntimeException("请先判断是否合格");
        }
        /*判断不合格*/
        if (ObjectUtils.isNotNull(qualityInspect.getCheckResult()) && qualityInspect.getCheckResult().equals("不合格")) {
        if (qualityInspect.getCheckResult().equals("不合格")) {
            QualityUnqualified qualityUnqualified = new QualityUnqualified();
            BeanUtils.copyProperties(qualityInspect, qualityUnqualified);
            qualityUnqualified.setInspectState(0);//待处理
src/main/java/com/ruoyi/quality/service/impl/QualityUnqualifiedServiceImpl.java
@@ -132,21 +132,21 @@
                case "让步放行":
                    //调用提交合格的接口
                    stockUtils.addStock(qualityInspect.getProductModelId(), unqualified.getQuantity(), StockInQualifiedRecordTypeEnum.DEFECTIVE_PASS.getCode(), unqualified.getId());
                    qualityInspect.setCheckResult("合格");
                    qualityInspectService.submit(qualityInspect);
                    break;
                default:
                    break;
            }
        } else {
            //查询对应的规格型号id
            Long modelId = qualityUnqualifiedMapper.getModelId(qualityUnqualified.getProductName(), qualityUnqualified.getModel());
            switch (qualityUnqualified.getDealResult()) {
                case "报废":
                    //调用不合格库存接口 å…¥ä¸åˆæ ¼åº“
                    stockUtils.addUnStock(Long.valueOf(unqualified.getModel()), unqualified.getQuantity(), StockInUnQualifiedRecordTypeEnum.DEFECTIVE_SCRAP.getCode(), unqualified.getId());
                    stockUtils.addUnStock(modelId, unqualified.getQuantity(), StockInUnQualifiedRecordTypeEnum.DEFECTIVE_SCRAP.getCode(), unqualified.getId());
                    break;
                case "让步放行":
                    //调用提交合格的接口
                    stockUtils.addStock(Long.valueOf(unqualified.getModel()), unqualified.getQuantity(), StockInQualifiedRecordTypeEnum.DEFECTIVE_PASS.getCode(), unqualified.getId());
                    stockUtils.addStock(modelId, unqualified.getQuantity(), StockInQualifiedRecordTypeEnum.DEFECTIVE_PASS.getCode(), unqualified.getId());
                    break;
                default:
                    break;
src/main/java/com/ruoyi/staff/controller/PersonalAttendanceRecordsController.java
@@ -3,12 +3,13 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.staff.dto.PersonalAttendanceRecordsDto;
import com.ruoyi.staff.pojo.HolidayApplication;
import com.ruoyi.staff.pojo.PersonalAttendanceRecords;
import com.ruoyi.staff.service.HolidayApplicationService;
import com.ruoyi.staff.service.PersonalAttendanceRecordsService;
import org.springframework.beans.factory.annotation.Autowired;
import io.swagger.annotations.Api;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
/**
 * <p>
@@ -20,8 +21,9 @@
 */
@RestController
@RequestMapping("/personalAttendanceRecords")
@Api(tags = "打卡签到")
public class PersonalAttendanceRecordsController {
    @Autowired
    @Resource
    private PersonalAttendanceRecordsService personalAttendanceRecordsService;
    // æ–°å¢ž
@@ -36,8 +38,16 @@
        return AjaxResult.success(personalAttendanceRecordsService.listPage(page, personalAttendanceRecordsDto));
    }
    // ä»Šæ—¥è€ƒå‹¤æ•°æ®
    @GetMapping("/today")
    public AjaxResult todayInfo(PersonalAttendanceRecordsDto personalAttendanceRecordsDto){
        return AjaxResult.success(personalAttendanceRecordsService.todayInfo(personalAttendanceRecordsDto));
    }
    // å¯¼å‡º
    @PostMapping("/export")
    public void export(HttpServletResponse response, PersonalAttendanceRecordsDto personalAttendanceRecordsDto) {
        personalAttendanceRecordsService.export(response, personalAttendanceRecordsDto);
    }
}
src/main/java/com/ruoyi/staff/dto/PersonalAttendanceRecordsDto.java
@@ -6,8 +6,14 @@
@Data
public class PersonalAttendanceRecordsDto extends PersonalAttendanceRecords {
    @Excel(name = "姓名", sort = 3)
    private String staffName;
    @Excel(name = "工号", sort = 4)
    private String staffNo;
    @Excel(name = "部门", sort = 2)
    private String deptName;
    private Long deptId;
}
src/main/java/com/ruoyi/staff/mapper/PersonalAttendanceRecordsMapper.java
@@ -10,6 +10,10 @@
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
/**
 * <p>
 *  Mapper æŽ¥å£
@@ -21,4 +25,8 @@
@Mapper
public interface PersonalAttendanceRecordsMapper extends BaseMapper<PersonalAttendanceRecords> {
    IPage<PersonalAttendanceRecordsDto> listPage(Page page, @Param("params") PersonalAttendanceRecordsDto personalAttendanceRecordsDto);
    List<StaffOnJob> selectStaffWithoutAttendanceRecordBeforeTime(@Param("date") LocalDate date, @Param("entryDeadline") LocalDateTime entryDeadline);
    boolean existsAttendanceRecord(@Param("staffOnJobId") Long staffOnJobId, @Param("date") LocalDate date);
}
src/main/java/com/ruoyi/staff/pojo/PersonalAttendanceRecords.java
@@ -11,6 +11,7 @@
import java.time.LocalDateTime;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
@@ -41,25 +42,32 @@
    @ApiModelProperty("日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    @Excel(name = "日期", sort = 1, dateFormat = "yyyy-MM-dd")
    private LocalDate date;
    @ApiModelProperty("工作开始时间")
    @JsonFormat(pattern = "HH:mm")
    @DateTimeFormat(pattern = "HH:mm")
    @Excel(name = "上班时间", sort = 5, dateFormat = "HH:mm")
    private LocalDateTime workStartAt;
    @ApiModelProperty("工作结束时间")
    @JsonFormat(pattern = "HH:mm")
    @DateTimeFormat(pattern = "HH:mm")
    @Excel(name = "下班时间", sort = 6, dateFormat = "HH:mm")
    private LocalDateTime workEndAt;
    @ApiModelProperty("工作时长")
    @Excel(name = "工时(小时)", sort = 7)
    private BigDecimal workHours;
    @ApiModelProperty("状态 0正常 1迟到 2早退")
    private Byte status;
    @ApiModelProperty("状态 0正常 1迟到 2早退 3迟到早退 4缺勤")
    @Excel(name = "状态", sort = 8,readConverterExp = "0=正常,1=迟到,2=早退,3=迟到、早退,4=缺勤")
    private Integer status;
    @ApiModelProperty("备注")
    @Excel(name = "备注", sort = 9)
    private String remark;
    @ApiModelProperty("租户id")
src/main/java/com/ruoyi/staff/service/PersonalAttendanceRecordsService.java
@@ -8,6 +8,8 @@
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.staff.pojo.StaffOnJob;
import javax.servlet.http.HttpServletResponse;
/**
 * <p>
 *  æœåŠ¡ç±»
@@ -22,4 +24,6 @@
    int add(PersonalAttendanceRecords personalAttendanceRecords);
    PersonalAttendanceRecordsDto todayInfo(PersonalAttendanceRecordsDto personalAttendanceRecordsDto);
    void export(HttpServletResponse response, PersonalAttendanceRecordsDto personalAttendanceRecordsDto);
}
src/main/java/com/ruoyi/staff/service/impl/PersonalAttendanceRecordsServiceImpl.java
@@ -3,28 +3,34 @@
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.exception.base.BaseException;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.project.system.domain.SysDept;
import com.ruoyi.project.system.mapper.SysDeptMapper;
import com.ruoyi.project.system.service.ISysDictDataService;
import com.ruoyi.staff.dto.PersonalAttendanceRecordsDto;
import com.ruoyi.staff.dto.StaffOnJobDto;
import com.ruoyi.staff.mapper.StaffOnJobMapper;
import com.ruoyi.staff.pojo.PersonalAttendanceRecords;
import com.ruoyi.staff.mapper.PersonalAttendanceRecordsMapper;
import com.ruoyi.staff.pojo.StaffOnJob;
import com.ruoyi.staff.service.PersonalAttendanceRecordsService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.staff.task.PersonalAttendanceRecordsTask;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.servlet.http.HttpServletResponse;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
/**
 * <p>
@@ -41,6 +47,9 @@
    @Autowired
    private StaffOnJobMapper staffOnJobMapper;
    @Autowired
    private PersonalAttendanceRecordsTask personalAttendanceRecordsTask;
    @Autowired
    private ISysDictDataService dictDataService;
@@ -62,26 +71,43 @@
            throw new BaseException("当前用户没有对应的员工信息");
        }
        // å½“前时间
        LocalDateTime currentDateTime = LocalDateTime.now();
        // å¦‚果打卡时间超过考勤下班时间不能打卡
        // èŽ·å–è€ƒå‹¤ä¸‹ç­æ—¶é—´ç‚¹
        String[] timeConfigs = getAttendanceTimeConfig();
        String timeConfig = timeConfigs[1];
        String[] timeParts = timeConfig.split(":");
        int standardHour = Integer.parseInt(timeParts[0]);
        int standardMinute = Integer.parseInt(timeParts[1]);
        // å½“前时间
        int actualHour = currentDateTime.getHour();
        int actualMinute = currentDateTime.getMinute();
        // åˆ¤æ–­æ‰“卡时间是否晚于当前时间
        if (actualHour > standardHour || (actualHour == standardHour && actualMinute > standardMinute)) {
            throw new BaseException("打卡时间不能晚于下班时间");
        }
        // æ ¹æ®å‘˜å·¥ID和当前日期查询打卡记录
        QueryWrapper<PersonalAttendanceRecords> attendanceQueryWrapper = new QueryWrapper<>();
        attendanceQueryWrapper.eq("staff_on_job_id", staffOnJob.getId())
                .eq("date", currentDate);
        PersonalAttendanceRecords attendanceRecord = personalAttendanceRecordsMapper.selectOne(attendanceQueryWrapper);
        DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("HH:mm");
        // æ ¹æ®å­—典设置的考勤时间判断迟到早退
        if (attendanceRecord == null) {
            // ä¸å­˜åœ¨æ‰“卡记录,创建新记录
            personalAttendanceRecords.setStaffOnJobId(staffOnJob.getId());
            personalAttendanceRecords.setDate(currentDate);
            personalAttendanceRecords.setWorkStartAt(LocalDateTime.now());
            personalAttendanceRecords.setStatus(determineAttendanceStatus(personalAttendanceRecords.getWorkStartAt(), true));
            personalAttendanceRecords.setWorkStartAt(currentDateTime);
            personalAttendanceRecords.setStatus(determineAttendanceStatus(personalAttendanceRecords, true));
            personalAttendanceRecords.setRemark(personalAttendanceRecords.getRemark());
            personalAttendanceRecords.setTenantId(staffOnJob.getTenantId());
            return personalAttendanceRecordsMapper.insert(personalAttendanceRecords);
        } else {
            if (attendanceRecord.getWorkEndAt() == null) {
                // æ›´æ–°å·¥ä½œç»“束时间和工作时长
                attendanceRecord.setWorkEndAt(LocalDateTime.now());
                attendanceRecord.setWorkEndAt(currentDateTime);
                // è®¡ç®—工作时长(精确到分钟,保留2位小数)
                LocalDateTime startTime = attendanceRecord.getWorkStartAt();
                LocalDateTime endTime = attendanceRecord.getWorkEndAt();
@@ -91,7 +117,7 @@
                        .divide(BigDecimal.valueOf(60), 2, RoundingMode.HALF_UP);
                attendanceRecord.setWorkHours(workHours);
                // æ›´æ–°è€ƒå‹¤çŠ¶æ€
                attendanceRecord.setStatus(determineAttendanceStatus(attendanceRecord.getWorkEndAt(), false));
                attendanceRecord.setStatus(determineAttendanceStatus(attendanceRecord, false));
                return personalAttendanceRecordsMapper.updateById(attendanceRecord);
            } else {
                throw new BaseException("您已经打过卡了,无需重复打卡!!!");
@@ -99,27 +125,36 @@
        }
    }
    // èŽ·å–è€ƒå‹¤æ—¶é—´é…ç½®
    private String[] getAttendanceTimeConfig() {
        String[] timeConfigs = new String[2];
        try {
            String dictType = "sys_work_time";
            // èŽ·å–ä¸Šç­æ—¶é—´é…ç½®ï¼Œé»˜è®¤ä¸º09:00
            String startTimeConfig = dictDataService.selectDictLabel(dictType, "start_at");
            timeConfigs[0] = (startTimeConfig == null || startTimeConfig.trim().isEmpty()) ? "09:00" : startTimeConfig;
            // èŽ·å–ä¸‹ç­æ—¶é—´é…ç½®ï¼Œé»˜è®¤ä¸º18:00
            String endTimeConfig = dictDataService.selectDictLabel(dictType, "end_at");
            timeConfigs[1] = (endTimeConfig == null || endTimeConfig.trim().isEmpty()) ? "18:00" : endTimeConfig;
            return timeConfigs;
        } catch (Exception e) {
            timeConfigs[0] = "09:00"; // é»˜è®¤ä¸Šç­æ—¶é—´
            timeConfigs[1] = "18:00"; // é»˜è®¤ä¸‹ç­æ—¶é—´
            return timeConfigs;
        }
    }
    // æ ¹æ®å®žé™…时间和是否上班时间判断考勤状态
    // 0 æ­£å¸¸ 1 è¿Ÿåˆ° 2 æ—©é€€
    private byte determineAttendanceStatus(LocalDateTime actualTime, boolean isStart) {
    // 0 æ­£å¸¸ 1 è¿Ÿåˆ° 2 æ—©é€€ 3 è¿Ÿåˆ°æ—©é€€ 4 ç¼ºå‹¤
    private Integer determineAttendanceStatus(PersonalAttendanceRecords attendanceRecord, boolean isStart) {
        LocalDateTime actualTime = isStart ? attendanceRecord.getWorkStartAt() : attendanceRecord.getWorkEndAt();
        try {
            // èŽ·å–è€ƒå‹¤æ—¶é—´é…ç½®
            String dictType = "sys_work_time"; // è€ƒå‹¤æ—¶é—´å­—典类型
            String timeConfig;
            if (isStart) {
                // ä¸Šç­æ—¶é—´é…ç½®ï¼Œé»˜è®¤ä¸º09:00
                timeConfig = dictDataService.selectDictLabel(dictType, "work_start_time");
                if (timeConfig == null || timeConfig.trim().isEmpty()) {
                    timeConfig = "09:00";
                }
            } else {
                // ä¸‹ç­æ—¶é—´é…ç½®ï¼Œé»˜è®¤ä¸º18:00
                timeConfig = dictDataService.selectDictLabel(dictType, "work_end_time");
                if (timeConfig == null || timeConfig.trim().isEmpty()) {
                    timeConfig = "18:00";
                }
            }
            String[] timeConfigs = getAttendanceTimeConfig();
            String timeConfig = isStart ? timeConfigs[0] : timeConfigs[1];
            // è§£æžæ ‡å‡†æ—¶é—´
            String[] timeParts = timeConfig.split(":");
@@ -139,6 +174,9 @@
            } else {
                // ä¸‹ç­æ‰“卡:早于标准时间算早退
                if (actualHour < standardHour || (actualHour == standardHour && actualMinute < standardMinute)) {
                    if (attendanceRecord.getStatus() == 1) {
                        return 3; // è¿Ÿåˆ°æ—©é€€
                    }
                    return 2; // æ—©é€€
                }
            }
@@ -154,6 +192,17 @@
    @Override
    public IPage<PersonalAttendanceRecordsDto> listPage(Page page, PersonalAttendanceRecordsDto personalAttendanceRecordsDto) {
        boolean admin = SecurityUtils.isAdmin(SecurityUtils.getUserId());
        if (!admin) {
            QueryWrapper<StaffOnJob> staffQueryWrapper = new QueryWrapper<>();
            staffQueryWrapper.eq("staff_no", SecurityUtils.getUsername());
            StaffOnJob staffOnJob = staffOnJobMapper.selectOne(staffQueryWrapper);
            if (staffOnJob == null) {
                return new Page<>(page.getCurrent(), page.getSize(), 0);
            }
            personalAttendanceRecordsDto.setStaffOnJobId(staffOnJob.getId());
        }
        return personalAttendanceRecordsMapper.listPage(page,personalAttendanceRecordsDto);
    }
@@ -194,4 +243,21 @@
        return resultDto;
    }
    @Override
    public void export(HttpServletResponse response, PersonalAttendanceRecordsDto personalAttendanceRecordsDto) {
        boolean admin = SecurityUtils.isAdmin(SecurityUtils.getUserId());
        if (!admin) {
            QueryWrapper<StaffOnJob> staffQueryWrapper = new QueryWrapper<>();
            staffQueryWrapper.eq("staff_no", SecurityUtils.getUsername());
            StaffOnJob staffOnJob = staffOnJobMapper.selectOne(staffQueryWrapper);
            if (staffOnJob == null) {
                throw new ServiceException("没有员工信息,无法导出考勤记录");
            }
            personalAttendanceRecordsDto.setStaffOnJobId(staffOnJob.getId());
        }
        List<PersonalAttendanceRecordsDto> personalAttendanceRecords = personalAttendanceRecordsMapper.listPage(new Page<>(1, Integer.MAX_VALUE), personalAttendanceRecordsDto).getRecords();
        ExcelUtil<PersonalAttendanceRecordsDto> util = new ExcelUtil<PersonalAttendanceRecordsDto>(PersonalAttendanceRecordsDto.class);
        util.exportExcel(response, personalAttendanceRecords, "考勤记录导出");
    }
}
src/main/java/com/ruoyi/staff/service/impl/StaffOnJobServiceImpl.java
@@ -180,7 +180,7 @@
    public void staffOnJobExport(HttpServletResponse response, StaffOnJob staffOnJob) {
        List<StaffOnJobDto> staffOnJobs = staffOnJobMapper.staffOnJobList(staffOnJob);
        ExcelUtil<StaffOnJobDto> util = new ExcelUtil<StaffOnJobDto>(StaffOnJobDto.class);
        util.exportExcel(response, staffOnJobs, "在职员工台账导出");
        util.exportExcel(response, staffOnJobs, "员工台账导出");
    }
    @Override
src/main/java/com/ruoyi/staff/task/PersonalAttendanceRecordsTask.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,74 @@
package com.ruoyi.staff.task;
import com.ruoyi.staff.pojo.PersonalAttendanceRecords;
import com.ruoyi.staff.pojo.StaffOnJob;
import com.ruoyi.staff.service.PersonalAttendanceRecordsService;
import com.ruoyi.staff.mapper.PersonalAttendanceRecordsMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
/**
 * ä¸ªäººè€ƒå‹¤è®°å½•定时任务
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-02-09
 */
@Slf4j
@Component
public class PersonalAttendanceRecordsTask {
    @Autowired
    private PersonalAttendanceRecordsMapper personalAttendanceRecordsMapper;
    @Autowired
    private PersonalAttendanceRecordsService personalAttendanceRecordsService;
    /**
     * æ¯å¤©å‡Œæ™¨ç”Ÿæˆæ˜¨æ—¥çš„缺勤记录
     * å®šæ—¶ä»»åŠ¡ï¼šæ¯å¤©å‡Œæ™¨1点执行
     * æŽ’除今天刚入职的员工
     */
    @Scheduled(cron = "0 0 1 * * ?")
    public void generateAbsenceRecords() {
        try {
            // èŽ·å–æ˜¨æ—¥æ—¥æœŸ
            LocalDate yesterday = LocalDate.now().minusDays(1);
            // ç›´æŽ¥æŸ¥è¯¢æ˜¨å¤©æ²¡æœ‰è€ƒå‹¤è®°å½•的在职员工(排除今天刚入职的)
            LocalDateTime todayStart = LocalDate.now().atStartOfDay();
            List<StaffOnJob> staffWithoutAttendance = personalAttendanceRecordsMapper.selectStaffWithoutAttendanceRecordBeforeTime(yesterday, todayStart);
            // éåŽ†æ²¡æœ‰è€ƒå‹¤è®°å½•çš„å‘˜å·¥ï¼Œç”Ÿæˆç¼ºå‹¤è®°å½•
            for (StaffOnJob staff : staffWithoutAttendance) {
                try {
                    boolean exists = personalAttendanceRecordsMapper.existsAttendanceRecord(staff.getId(), yesterday);
                    if (exists) {
                        continue;
                    }
                    PersonalAttendanceRecords absenceRecord = new PersonalAttendanceRecords();
                    absenceRecord.setStaffOnJobId(staff.getId());
                    absenceRecord.setDate(yesterday);
                    absenceRecord.setStatus(4); // è®¾ç½®çŠ¶æ€ä¸ºç¼ºå‹¤
                    absenceRecord.setRemark("系统自动生成-缺勤");
                    personalAttendanceRecordsService.save(absenceRecord);
                } catch (Exception e) {
                    log.error("为员工{}生成缺勤记录失败:{}", staff.getStaffName(), e.getMessage(), e);
                }
            }
            log.info("昨日缺勤记录生成完成");
        } catch (Exception e) {
            log.error("生成昨日缺勤记录任务执行失败:{}", e.getMessage(), e);
        }
    }
}
src/main/resources/mapper/compensationperformance/CompensationPerformanceMapper.xml
@@ -22,4 +22,16 @@
        </where>
        ORDER BY cp.pay_date DESC, cp.id DESC
    </select>
    <select id="exportList" resultType="com.ruoyi.compensationperformance.pojo.CompensationPerformance">
        SELECT cp.*,
               soj.staff_name AS staffName,
               sp.post_name   AS postName,
               sd.dept_name   AS deptName
        FROM compensation_performance cp
                 LEFT JOIN staff_on_job soj ON soj.id = cp.staff_id
                 LEFT JOIN sys_post sp ON sp.post_id = soj.sys_post_id
                 LEFT JOIN sys_dept sd ON sd.dept_id = soj.sys_dept_id
        ORDER BY cp.pay_date DESC, cp.id DESC
    </select>
</mapper>
src/main/resources/mapper/production/ProductionProductMainMapper.xml
@@ -18,6 +18,7 @@
        pwo.status as workOrderStatus,
        u.nick_name as nickName,
        p.product_name as productName,
        pp.name as process,
        pm.model as productModelName,
        ppo.quantity,
        ppo.scrap_qty,
@@ -26,6 +27,8 @@
        from
        production_product_main ppm
        left join product_work_order pwo on pwo.id = ppm.work_order_id
        left join product_process_route_item ppri on ppri.id = pwo.product_process_route_item_id
        left join product_process pp on pp.id = ppri.process_id
        left join product_order po on po.id = pwo.product_order_id
        left join production_product_output ppo on ppm.id = ppo.product_main_id
        left join product_model pm on pm.id = ppo.product_model_id
src/main/resources/mapper/quality/QualityUnqualifiedMapper.xml
@@ -89,4 +89,11 @@
            1=1
        and qu.id = #{id}
    </select>
    <select id="getModelId" resultType="java.lang.Long">
        select pm.id
        from product_model pm
        left join product p on pm.product_id=p.id
        where pm.model=#{model}
          and  p.product_name=#{productName}
    </select>
</mapper>
src/main/resources/mapper/staff/PersonalAttendanceRecordsMapper.xml
@@ -29,11 +29,39 @@
        LEFT JOIN
        sys_dept sd ON sd.dept_id = soj.sys_dept_id
        where 1=1
        <if test="params.staffOnJobId != null and params.staffOnJobId > 0">
            AND personal_attendance_records.staff_on_job_id = #{params.staffOnJobId}
        </if>
        <if test="params.deptId != null and params.deptId > 0">
            AND sys_dept.dept_id = #{params.deptId}
            AND sd.dept_id = #{params.deptId}
        </if>
        <if test="params.date != null">
            AND personal_attendance_records.date = DATE_FORMAT(#{params.date},'%Y-%m-%d')
            and personal_attendance_records.date &gt;= #{params.date}
            and personal_attendance_records.date &lt; DATE_ADD(DATE(#{params.date}), INTERVAL 1 DAY)
        </if>
    </select>
    <!-- æŸ¥è¯¢æŒ‡å®šæ—¥æœŸæ²¡æœ‰è€ƒå‹¤è®°å½•的在职员工(在指定时间之前入职的) -->
    <select id="selectStaffWithoutAttendanceRecordBeforeTime" resultType="com.ruoyi.staff.pojo.StaffOnJob">
        SELECT soj.*
        FROM staff_on_job soj
        WHERE soj.staff_state = 1
        AND soj.create_time &lt; #{entryDeadline}
        AND NOT EXISTS (
        SELECT 1
        FROM personal_attendance_records par
        WHERE par.staff_on_job_id = soj.id
        AND par.date = #{date}
        )
    </select>
    <!-- æ£€æŸ¥æŒ‡å®šå‘˜å·¥åœ¨æŒ‡å®šæ—¥æœŸæ˜¯å¦å·²å­˜åœ¨è€ƒå‹¤è®°å½• -->
    <select id="existsAttendanceRecord" resultType="boolean">
        SELECT EXISTS (
        SELECT 1
        FROM personal_attendance_records
        WHERE staff_on_job_id = #{staffOnJobId}
        AND date = #{date}
        )
    </select>
</mapper>
src/main/resources/mapper/system/SysNoticeMapper.xml
@@ -31,6 +31,7 @@
               sender_id,
               consignee_id,
               jump_path,
               app_jump_path,
               tenant_id
        from sys_notice
    </sql>
@@ -75,6 +76,7 @@
        <if test="senderId != null and senderId != ''">sender_id,</if>
        <if test="consigneeId != null and consigneeId != ''">consignee_id,</if>
        <if test="jumpPath != null and jumpPath != ''">jump_path,</if>
        <if test="appJumpPath != null and appJumpPath != ''">app_jump_path,</if>
        <if test="createBy != null and createBy != ''">create_by,</if>
        <if test="tenantId != null and tenantId != ''">tenant_id,</if>
             create_time
@@ -87,6 +89,7 @@
        <if test="senderId != null and senderId != ''">#{senderId},</if>
        <if test="consigneeId != null and consigneeId != ''">#{consigneeId},</if>
        <if test="jumpPath != null and jumpPath != ''">#{jumpPath},</if>
        <if test="appJumpPath != null and appJumpPath != ''">#{appJumpPath},</if>
        <if test="pathParms != null and pathParms != ''">#{queryParms},</if>
        <if test="createBy != null and createBy != ''">#{createBy},</if>
        <if test="tenantId != null and tenantId != ''">#{tenantId},</if>