已添加32个文件
已修改26个文件
2028 ■■■■■ 文件已修改
src/main/java/com/ruoyi/common/enums/StaffLeaveReason.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/mapper/ProductionProductMainMapper.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductionProductMainServiceImpl.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/domain/SysDept.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/controller/QualityReportController.java 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/controller/QualityUnqualifiedController.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/dto/QualityInspectStatDto.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/dto/QualityMonthlyDetailDto.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/dto/QualityMonthlyPassRateDto.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/dto/QualityMonthlyPassRateWrapperDto.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/dto/QualityParameterStatDto.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/dto/QualityPassRateDto.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/dto/QualityTopParameterDto.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/mapper/QualityInspectMapper.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/pojo/QualityUnqualified.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/service/IQualityUnqualifiedService.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/service/QualityReportService.java 23 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/service/impl/QualityInspectServiceImpl.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/service/impl/QualityReportServiceImpl.java 100 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/service/impl/QualityUnqualifiedServiceImpl.java 102 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/controller/AnalyticsController.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/dto/StaffLeaveDto.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/mapper/StaffLeaveMapper.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/mapper/StaffOnJobMapper.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/service/AnalyticsService.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/service/impl/AnalyticsServiceImpl.java 145 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/service/impl/StaffLeaveServiceImpl.java 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/service/impl/StaffOnJobServiceImpl.java 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/vo/MonthlyTurnoverRateVo.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/vo/TotalTurnoverRateVo.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/controller/StockInRecordController.java 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/controller/StockInventoryController.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/controller/StockOutRecordController.java 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/dto/StockInRecordDto.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/dto/StockOutRecordDto.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/mapper/StockInRecordMapper.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/mapper/StockInventoryMapper.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/mapper/StockOutRecordMapper.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/pojo/StockInRecord.java 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/pojo/StockInventory.java 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/pojo/StockOutRecord.java 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/service/StockInRecordService.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/service/StockInventoryService.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/service/StockOutRecordService.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/service/impl/StockInRecordServiceImpl.java 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/service/impl/StockInventoryServiceImpl.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/service/impl/StockOutRecordServiceImpl.java 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductionProductMainMapper.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/quality/QualityInspectMapper.xml 290 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/quality/QualityTestStandardMapper.xml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/staff/StaffLeaveMapper.xml 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/staff/StaffOnJobMapper.xml 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/stock/StockInRecordMapper.xml 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/stock/StockInventoryMapper.xml 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/stock/StockOutRecordMapper.xml 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/system/SysDeptMapper.xml 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/enums/StaffLeaveReason.java
@@ -25,4 +25,18 @@
    public String getInfo() {
        return info;
    }
    /**
     * æ ¹æ®code获取枚举实例
     * @param code ç¦»èŒåŽŸå› ç¼–ç 
     * @return å¯¹åº”的枚举实例,若未找到则返回null
     */
    public static StaffLeaveReason getByCode(String code) {
        for (StaffLeaveReason reason : StaffLeaveReason.values()) {
            if (reason.getCode().equals(code)) {
                return reason;
            }
        }
        return null;
    }
}
src/main/java/com/ruoyi/production/mapper/ProductionProductMainMapper.java
@@ -4,6 +4,7 @@
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.production.dto.ProductionProductMainDto;
import com.ruoyi.production.pojo.ProductOrder;
import com.ruoyi.production.pojo.ProductionProductMain;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@@ -19,4 +20,11 @@
     * æ ¹æ®å·¥å•ID批量删除生产主表数据
     */
    int deleteByWorkOrderIds(@Param("workOrderIds") List<Long> workOrderIds);
    /**
     * æ ¹æ®æŠ¥å·¥id查询生产订单
     * @param productMainId
     * @return
     */
    ProductOrder getOrderByMainId(@Param("productMainId") Long productMainId);
}
src/main/java/com/ruoyi/production/service/impl/ProductionProductMainServiceImpl.java
@@ -75,6 +75,7 @@
    private QualityTestStandardParamMapper qualityTestStandardParamMapper;
    private QualityTestStandardBindingMapper qualityTestStandardBindingMapper;
    private QualityTestStandardMapper qualityTestStandardMapper;
    private QualityInspectParamMapper qualityInspectParamMapper;
@@ -189,12 +190,15 @@
        productionProductOutput.setProductModelId(productProcessRouteItem.getProductModelId());
        productionProductOutput.setQuantity(dto.getQuantity() != null ? dto.getQuantity() : BigDecimal.ZERO);
        productionProductOutputMapper.insert(productionProductOutput);
        /*新增质检*/
        //对应的过程检或者出厂检
        List<ProductProcessRouteItem> productProcessRouteItems = productProcessRouteItemMapper.selectList(Wrappers.<ProductProcessRouteItem>lambdaQuery().eq(ProductProcessRouteItem::getProductRouteId, productProcessRouteItem.getProductRouteId()));
        int inspectType = 1;
        String process = productProcess.getName();//工序
        if (productProcessRouteItem.getDragSort()==productProcessRouteItems.size()){
            //最后一道工序生成出厂检
            inspectType = 2;
            process = null;
        }
        Product product = productMapper.selectById(productModel.getProductId());
        QualityInspect qualityInspect = new QualityInspect();
@@ -209,12 +213,12 @@
        qualityInspect.setProductMainId(productionProductMain.getId());
        qualityInspect.setProductModelId(productModel.getId());
        qualityInspectMapper.insert(qualityInspect);
        List<QualityTestStandardBinding> qualityTestStandardBindings = qualityTestStandardBindingMapper.selectList(
                new LambdaQueryWrapper<QualityTestStandardBinding>()
                        .eq(QualityTestStandardBinding::getProductId, product.getId()));
        if (qualityTestStandardBindings.size()>0){
        List<QualityTestStandard> qualityTestStandard = qualityTestStandardMapper.getQualityTestStandardByProductId(product.getId(), inspectType,process);
        if (qualityTestStandard.size()>0){
            qualityInspect.setTestStandardId(qualityTestStandard.get(0).getId());
            qualityInspectMapper.updateById(qualityInspect);
            qualityTestStandardParamMapper.selectList(Wrappers.<QualityTestStandardParam>lambdaQuery()
                    .eq(QualityTestStandardParam::getTestStandardId,qualityTestStandardBindings.get(0).getTestStandardId()))
                    .eq(QualityTestStandardParam::getTestStandardId,qualityTestStandard.get(0).getId()))//默认获取最新的
                    .forEach(qualityTestStandardParam -> {
                QualityInspectParam param = new QualityInspectParam();
                BeanUtils.copyProperties(qualityTestStandardParam, param);
src/main/java/com/ruoyi/project/system/domain/SysDept.java
@@ -54,6 +54,9 @@
    /** éƒ¨é—¨ç¼–号 */
    private String deptNick;
    /** å‘˜å·¥æ•°é‡ */
    private Integer staffCount;
    
    /** å­éƒ¨é—¨ */
    private List<SysDept> children = new ArrayList<SysDept>();
@@ -192,6 +195,14 @@
        this.deptNick = deptNick;
    }
    public Integer getStaffCount() {
        return staffCount;
    }
    public void setStaffCount(Integer staffCount) {
        this.staffCount = staffCount;
    }
    @Override
    public String toString() {
        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
@@ -199,6 +210,7 @@
            .append("parentId", getParentId())
            .append("ancestors", getAncestors())
            .append("deptName", getDeptName())
            .append("staffCount", getStaffCount())
            .append("orderNum", getOrderNum())
            .append("leader", getLeader())
            .append("phone", getPhone())
src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java
@@ -108,6 +108,7 @@
    private final QualityInspectMapper qualityInspectMapper;
    private final QualityTestStandardBindingMapper qualityTestStandardBindingMapper;
    private final QualityTestStandardParamMapper qualityTestStandardParamMapper;
    private final QualityTestStandardMapper qualityTestStandardMapper;
    private final QualityInspectParamMapper qualityInspectParamMapper;
    private final ProcurementRecordMapper procurementRecordStorageMapper;
@@ -229,12 +230,12 @@
        qualityInspect.setUnit(saleProduct.getUnit());
        qualityInspect.setQuantity(saleProduct.getQuantity());
        qualityInspectMapper.insert(qualityInspect);
        List<QualityTestStandardBinding> qualityTestStandardBindings = qualityTestStandardBindingMapper.selectList(
                new LambdaQueryWrapper<QualityTestStandardBinding>()
                        .eq(QualityTestStandardBinding::getProductId, saleProduct.getProductId()));
        if (qualityTestStandardBindings.size()>0){
        List<QualityTestStandard> qualityTestStandard = qualityTestStandardMapper.getQualityTestStandardByProductId(saleProduct.getProductId(), 0,null);
        if (qualityTestStandard.size()>0){
            qualityInspect.setTestStandardId(qualityTestStandard.get(0).getId());
            qualityInspectMapper.updateById(qualityInspect);
            qualityTestStandardParamMapper.selectList(Wrappers.<QualityTestStandardParam>lambdaQuery()
                    .eq(QualityTestStandardParam::getTestStandardId,qualityTestStandardBindings.get(0).getTestStandardId()))
                    .eq(QualityTestStandardParam::getTestStandardId,qualityTestStandard.get(0).getId()))
                    .forEach(qualityTestStandardParam -> {
                        QualityInspectParam param = new QualityInspectParam();
                        com.ruoyi.common.utils.bean.BeanUtils.copyProperties(qualityTestStandardParam, param);
src/main/java/com/ruoyi/quality/controller/QualityReportController.java
@@ -1,15 +1,12 @@
package com.ruoyi.quality.controller;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.quality.pojo.QualityTestStandardParam;
import com.ruoyi.quality.service.QualityReportService;
import com.ruoyi.quality.service.QualityTestStandardParamService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
 * <p>
@@ -19,6 +16,7 @@
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-01-14 03:39:49
 */
@Api(tags = "质量管理")
@RestController
@RequestMapping("/qualityReport")
public class QualityReportController {
@@ -26,6 +24,58 @@
    @Autowired
    private QualityReportService qualityReportService;
    /**
     * èŽ·å–æ£€éªŒç»Ÿè®¡æ•°æ®
     */
    @ApiOperation("获取检验统计数据")
    @GetMapping("/getInspectStatistics")
    public AjaxResult getInspectStatistics() {
        return AjaxResult.success(qualityReportService.getInspectStatistics());
    }
    /**
     * èŽ·å–åˆæ ¼çŽ‡ç»Ÿè®¡æ•°æ®
     */
    @ApiOperation("获取合格率统计数据")
    @GetMapping("/getPassRateStatistics")
    public AjaxResult getPassRateStatistics() {
        return AjaxResult.success(qualityReportService.getPassRateStatistics());
    }
    /**
     * èŽ·å–æœˆåº¦åˆæ ¼çŽ‡ç»Ÿè®¡æ•°æ®
     */
    @ApiOperation("获取月度合格率统计数据")
    @GetMapping("/getMonthlyPassRateStatistics")
    public AjaxResult getMonthlyPassRateStatistics(@RequestParam("year") String year) {
        return AjaxResult.success(qualityReportService.getMonthlyPassRateStatistics(year));
    }
    /**
     * èŽ·å–å¹´åº¦æ€»åˆæ ¼çŽ‡ç»Ÿè®¡æ•°æ®
     */
    @ApiOperation("获取年度总合格率统计数据")
    @GetMapping("/getYearlyPassRateStatistics")
    public AjaxResult getYearlyPassRateStatistics(@RequestParam("year") String year) {
        return AjaxResult.success(qualityReportService.getYearlyPassRateStatistics(year));
    }
    /**
     * èŽ·å–æœˆåº¦å®Œæˆæ˜Žç»†æ•°æ®
     */
    @ApiOperation("获取月度完成明细数据")
    @GetMapping("/getMonthlyCompletionDetails")
    public AjaxResult getMonthlyCompletionDetails(@RequestParam("year") String year) {
        return AjaxResult.success(qualityReportService.getMonthlyCompletionDetails(year));
    }
    /**
     * èŽ·å–çƒ­ç‚¹æ£€æµ‹æŒ‡æ ‡ç»Ÿè®¡
     */
    @ApiOperation("获取热点检测指标统计")
    @GetMapping("/getTopParameters")
    public AjaxResult getTopParameters(@RequestParam("inspectType") Integer inspectType) {
        return AjaxResult.success(qualityReportService.getTopParameters(inspectType));
    }
}
src/main/java/com/ruoyi/quality/controller/QualityUnqualifiedController.java
@@ -86,5 +86,15 @@
        qualityUnqualifiedService.qualityUnqualifiedExport(response, qualityUnqualified);
    }
    /**
     * ä¸åˆæ ¼ç®¡ç†å¤„理
     * @param qualityUnqualified
     * @return
     */
    @PostMapping("/deal")
    public AjaxResult deal(@RequestBody QualityUnqualified qualityUnqualified) {
        return AjaxResult.success(qualityUnqualifiedService.deal(qualityUnqualified));
    }
}
src/main/java/com/ruoyi/quality/dto/QualityInspectStatDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
package com.ruoyi.quality.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.math.BigDecimal;
import java.io.Serializable;
@Data
@ApiModel(value = "QualityInspectStatDto", description = "质量检验统计DTO")
public class QualityInspectStatDto implements Serializable {
    private static final long serialVersionUID = 1L;
    @ApiModelProperty(value = "类别(0:原材料检验;1:过程检验;2:出厂检验)")
    private Integer inspectType;
    @ApiModelProperty(value = "总数量")
    private BigDecimal totalCount;
    @ApiModelProperty(value = "已完成数量")
    private BigDecimal completedCount;
}
src/main/java/com/ruoyi/quality/dto/QualityMonthlyDetailDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,29 @@
package com.ruoyi.quality.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
/**
 * è´¨é‡æœˆåº¦å®Œæˆæ˜Žç»†DTO
 */
@Data
@ApiModel(value = "QualityMonthlyDetailDto", description = "质量月度完成明细DTO")
public class QualityMonthlyDetailDto implements Serializable {
    private static final long serialVersionUID = 1L;
    @ApiModelProperty(value = "月份")
    private String month;
    @ApiModelProperty(value = "原材料检验完成数")
    private BigDecimal rawMaterialCount;
    @ApiModelProperty(value = "过程检验完成数")
    private BigDecimal processCount;
    @ApiModelProperty(value = "出厂检验完成数")
    private BigDecimal outgoingCount;
}
src/main/java/com/ruoyi/quality/dto/QualityMonthlyPassRateDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,41 @@
package com.ruoyi.quality.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
/**
 * è´¨é‡æœˆåº¦åˆæ ¼çŽ‡ç»Ÿè®¡DTO
 */
@Data
@ApiModel(value = "QualityMonthlyPassRateDto", description = "质量月度合格率统计DTO")
public class QualityMonthlyPassRateDto implements Serializable {
    private static final long serialVersionUID = 1L;
    @ApiModelProperty(value = "月份(一月, äºŒæœˆ...)")
    private String month;
    @ApiModelProperty(value = "类别(0:原材料检验;1:过程检验;2:出厂检验)")
    private Integer inspectType;
    @ApiModelProperty(value = "总数量")
    private BigDecimal totalCount;
    @ApiModelProperty(value = "已完成数量")
    private BigDecimal completedCount;
    @ApiModelProperty(value = "合格数量")
    private BigDecimal qualifiedCount;
    @ApiModelProperty(value = "不合格数量")
    private BigDecimal unqualifiedCount;
    @ApiModelProperty(value = "完成占比")
    private BigDecimal completionRate;
    @ApiModelProperty(value = "合格率占比")
    private BigDecimal passRate;
}
src/main/java/com/ruoyi/quality/dto/QualityMonthlyPassRateWrapperDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
package com.ruoyi.quality.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
 * è´¨é‡æœˆåº¦åˆæ ¼çŽ‡åŒ…è£…DTO(按月分组)
 */
@Data
@ApiModel(value = "QualityMonthlyPassRateWrapperDto", description = "质量月度合格率包装DTO(按月分组)")
public class QualityMonthlyPassRateWrapperDto implements Serializable {
    private static final long serialVersionUID = 1L;
    @ApiModelProperty(value = "月份")
    private String month;
    @ApiModelProperty(value = "原材料检验数据")
    private QualityPassRateDto rawMaterial;
    @ApiModelProperty(value = "过程检验数据")
    private QualityPassRateDto process;
    @ApiModelProperty(value = "出厂检验数据")
    private QualityPassRateDto outgoing;
}
src/main/java/com/ruoyi/quality/dto/QualityParameterStatDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,26 @@
package com.ruoyi.quality.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
/**
 * æ£€éªŒæŒ‡æ ‡ç»Ÿè®¡DTO
 */
@Data
@ApiModel(value = "QualityParameterStatDto", description = "检验指标统计DTO")
public class QualityParameterStatDto implements Serializable {
    private static final long serialVersionUID = 1L;
    @ApiModelProperty(value = "指标名称")
    private String name;
    @ApiModelProperty(value = "数量")
    private BigDecimal count;
    @ApiModelProperty(value = "百分比")
    private BigDecimal percentage;
}
src/main/java/com/ruoyi/quality/dto/QualityPassRateDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,39 @@
package com.ruoyi.quality.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
/**
 * è´¨é‡åˆæ ¼çŽ‡ç»Ÿè®¡DTO
 */
@Data
@ApiModel(value = "QualityPassRateDto", description = "质量合格率统计DTO")
public class QualityPassRateDto implements Serializable {
    private static final long serialVersionUID = 1L;
    @ApiModelProperty(value = "类别(0:原材料检验;1:过程检验;2:出厂检验)")
    private Integer inspectType;
    @ApiModelProperty(value = "总数量")
    private BigDecimal totalCount;
    @ApiModelProperty(value = "已完成数量")
    private BigDecimal completedCount;
    @ApiModelProperty(value = "合格数量")
    private BigDecimal qualifiedCount;
    @ApiModelProperty(value = "不合格数量")
    private BigDecimal unqualifiedCount;
    @ApiModelProperty(value = "完成占比")
    private BigDecimal completionRate;
    @ApiModelProperty(value = "合格率占比")
    private BigDecimal passRate;
}
src/main/java/com/ruoyi/quality/dto/QualityTopParameterDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
package com.ruoyi.quality.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.List;
/**
 * è´¨æ£€çƒ­ç‚¹æŒ‡æ ‡ç»Ÿè®¡ç»“æžœDTO (单类型)
 */
@Data
@ApiModel(value = "QualityTopParameterDto", description = "质检热点指标统计结果DTO (单类型)")
public class QualityTopParameterDto implements Serializable {
    private static final long serialVersionUID = 1L;
    @ApiModelProperty(value = "总检测项次数量")
    private BigDecimal totalCount;
    @ApiModelProperty(value = "指标统计列表 (Top 4 + å…¶ä»–)")
    private List<QualityParameterStatDto> list;
}
src/main/java/com/ruoyi/quality/mapper/QualityInspectMapper.java
@@ -4,9 +4,14 @@
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.quality.pojo.QualityInspect;
import com.ruoyi.quality.pojo.QualityTestStandard;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import com.ruoyi.quality.dto.QualityInspectStatDto;
import com.ruoyi.quality.dto.QualityPassRateDto;
import com.ruoyi.quality.dto.QualityMonthlyPassRateDto;
import com.ruoyi.quality.dto.QualityMonthlyDetailDto;
import com.ruoyi.quality.dto.QualityParameterStatDto;
import java.util.List;
@@ -22,4 +27,34 @@
     * æ ¹æ®ç”Ÿäº§ä¸»è¡¨ID批量删除过程检验
     */
    int deleteByProductMainIds(@Param("productMainIds") List<Long> productMainIds);
    /**
     * èŽ·å–æ£€éªŒç»Ÿè®¡æ•°æ®
     */
    List<QualityInspectStatDto> getInspectStatistics();
    /**
     * èŽ·å–åˆæ ¼çŽ‡ç»Ÿè®¡æ•°æ®
     */
    List<QualityPassRateDto> getPassRateStatistics();
    /**
     * èŽ·å–æœˆåº¦åˆæ ¼çŽ‡ç»Ÿè®¡æ•°æ®
     */
    List<QualityMonthlyPassRateDto> getMonthlyPassRateStatistics(@Param("year") String year);
    /**
     * èŽ·å–å¹´åº¦åˆæ ¼çŽ‡ç»Ÿè®¡æ•°æ®
     */
    List<QualityPassRateDto> getYearlyPassRateStatistics(@Param("year") String year);
    /**
     * èŽ·å–æœˆåº¦å®Œæˆæ˜Žç»†æ•°æ®
     */
    List<QualityMonthlyDetailDto> getMonthlyCompletionDetails(@Param("year") String year);
    /**
     * èŽ·å–çƒ­ç‚¹æ£€æµ‹æŒ‡æ ‡ Top 4 + å…¶ä»–
     */
    List<QualityParameterStatDto> getTopParameters(@Param("inspectType") Integer inspectType);
}
src/main/java/com/ruoyi/quality/pojo/QualityUnqualified.java
@@ -133,5 +133,6 @@
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
    @ApiModelProperty("关联检测id")
    private Long inspectId;
}
src/main/java/com/ruoyi/quality/service/IQualityUnqualifiedService.java
@@ -14,4 +14,7 @@
    IPage<QualityUnqualified> qualityUnqualifiedListPage(Page page, QualityUnqualified qualityUnqualified);
    void qualityUnqualifiedExport(HttpServletResponse response, QualityUnqualified qualityUnqualified);
    int deal(QualityUnqualified qualityUnqualified);
}
src/main/java/com/ruoyi/quality/service/QualityReportService.java
@@ -1,7 +1,13 @@
package com.ruoyi.quality.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.quality.pojo.QualityTestStandardParam;
import com.ruoyi.quality.dto.QualityInspectStatDto;
import com.ruoyi.quality.dto.QualityPassRateDto;
import com.ruoyi.quality.dto.QualityMonthlyDetailDto;
import com.ruoyi.quality.dto.QualityParameterStatDto;
import com.ruoyi.quality.dto.QualityMonthlyPassRateWrapperDto;
import com.ruoyi.quality.dto.QualityTopParameterDto;
import java.util.List;
/**
 * <p>
@@ -11,6 +17,17 @@
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-01-13 03:39:49
 */
public interface QualityReportService  {
public interface QualityReportService {
    List<QualityInspectStatDto> getInspectStatistics();
    List<QualityPassRateDto> getPassRateStatistics();
    List<QualityMonthlyPassRateWrapperDto> getMonthlyPassRateStatistics(String year);
    List<QualityPassRateDto> getYearlyPassRateStatistics(String year);
    List<QualityMonthlyDetailDto> getMonthlyCompletionDetails(String year);
    QualityTopParameterDto getTopParameters(Integer inspectType);
}
src/main/java/com/ruoyi/quality/service/impl/QualityInspectServiceImpl.java
@@ -96,6 +96,7 @@
            List<QualityInspectParam> inspectParams = qualityInspectParamService.list(Wrappers.<QualityInspectParam>lambdaQuery().eq(QualityInspectParam::getInspectId, inspect.getId()));
            String text = inspectParams.stream().map(QualityInspectParam::getParameterItem).collect(Collectors.joining(","));
            qualityUnqualified.setDefectivePhenomena(text+"这些指标中存在不合格");//不合格现象
            qualityUnqualified.setInspectId(qualityInspect.getId());
            qualityUnqualifiedMapper.insert(qualityUnqualified);
        }
@@ -158,22 +159,26 @@
        }else if (qualityInspect.getInspectType() == 2) {
            //查询UnitPrice/TotalPrice
            SalesLedgerProduct salesLedgerProduct = salesLedgerProductMapper.selectSalesLedgerProductByMainId(qualityInspect.getProductMainId());
            ProcurementAddDto procurementRecordOutAdd = new ProcurementAddDto();
            procurementRecordOutAdd.setType(2);
            procurementRecordOutAdd.setTypeName("生产出厂检验合格入库");
            procurementRecordOutAdd.setNickName(loginUser.getNickName());
            List<Details> details = new ArrayList<>();
            Details details1 = new Details();
            details1.setInboundQuantity(qualityInspect.getQuantity());
            details1.setId(Math.toIntExact(salesLedgerProduct.getId()));
            details1.setUnitPrice(salesLedgerProduct.getTaxInclusiveUnitPrice());
            details1.setTotalPrice(salesLedgerProduct.getTaxInclusiveTotalPrice());
            details1.setProductModelId(salesLedgerProduct.getProductModelId());
            details.add(details1);
            procurementRecordOutAdd.setDetails(details);
            procurementRecordOutAdd.setQualityInspectId(qualityInspect.getId());
            procurementRecordService.add(procurementRecordOutAdd);
            if (ObjectUtils.isNull(qualityInspect.getProductMainId())){
                //如果是手动新增的出厂检
            }else {
                SalesLedgerProduct salesLedgerProduct = salesLedgerProductMapper.selectSalesLedgerProductByMainId(qualityInspect.getProductMainId());
                ProcurementAddDto procurementRecordOutAdd = new ProcurementAddDto();
                procurementRecordOutAdd.setType(2);
                procurementRecordOutAdd.setTypeName("生产出厂检验合格入库");
                procurementRecordOutAdd.setNickName(loginUser.getNickName());
                List<Details> details = new ArrayList<>();
                Details details1 = new Details();
                details1.setInboundQuantity(qualityInspect.getQuantity());
                details1.setId(Math.toIntExact(salesLedgerProduct.getId()));
                details1.setUnitPrice(salesLedgerProduct.getTaxInclusiveUnitPrice());
                details1.setTotalPrice(salesLedgerProduct.getTaxInclusiveTotalPrice());
                details1.setProductModelId(salesLedgerProduct.getProductModelId());
                details.add(details1);
                procurementRecordOutAdd.setDetails(details);
                procurementRecordOutAdd.setQualityInspectId(qualityInspect.getId());
                procurementRecordService.add(procurementRecordOutAdd);
            }
        }
        qualityInspect.setInspectState(1);//已提交
        return qualityInspectMapper.updateById(qualityInspect);
src/main/java/com/ruoyi/quality/service/impl/QualityReportServiceImpl.java
@@ -1,9 +1,109 @@
package com.ruoyi.quality.service.impl;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.quality.dto.QualityPassRateDto;
import com.ruoyi.quality.dto.QualityInspectStatDto;
import com.ruoyi.quality.dto.QualityMonthlyPassRateDto;
import com.ruoyi.quality.dto.QualityMonthlyDetailDto;
import com.ruoyi.quality.dto.QualityParameterStatDto;
import com.ruoyi.quality.dto.QualityMonthlyPassRateWrapperDto;
import com.ruoyi.quality.dto.QualityTopParameterDto;
import com.ruoyi.quality.mapper.QualityInspectMapper;
import com.ruoyi.quality.service.QualityReportService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Service
public class QualityReportServiceImpl implements QualityReportService {
    @Autowired
    private QualityInspectMapper qualityInspectMapper;
    @Override
    public List<QualityInspectStatDto> getInspectStatistics() {
        return qualityInspectMapper.getInspectStatistics();
    }
    @Override
    public List<QualityPassRateDto> getPassRateStatistics() {
        return qualityInspectMapper.getPassRateStatistics();
    }
    @Override
    public List<QualityMonthlyPassRateWrapperDto> getMonthlyPassRateStatistics(String year) {
        if (StringUtils.isEmpty(year)) {
            return new ArrayList<>();
        }
        List<QualityMonthlyPassRateDto> flatData = qualityInspectMapper.getMonthlyPassRateStatistics(year);
        // æŒ‰æœˆä»½åˆ†ç»„,并保持顺序
        Map<String, List<QualityMonthlyPassRateDto>> groupedByMonth = flatData.stream()
                .collect(Collectors.groupingBy(QualityMonthlyPassRateDto::getMonth, LinkedHashMap::new, Collectors.toList()));
        List<QualityMonthlyPassRateWrapperDto> result = new ArrayList<>();
        groupedByMonth.forEach((month, dtos) -> {
            QualityMonthlyPassRateWrapperDto wrapper = new QualityMonthlyPassRateWrapperDto();
            wrapper.setMonth(month);
            for (QualityMonthlyPassRateDto dto : dtos) {
                QualityPassRateDto passRateDto = new QualityPassRateDto();
                BeanUtils.copyProperties(dto, passRateDto);
                if (dto.getInspectType() == 0) {
                    wrapper.setRawMaterial(passRateDto);
                } else if (dto.getInspectType() == 1) {
                    wrapper.setProcess(passRateDto);
                } else if (dto.getInspectType() == 2) {
                    wrapper.setOutgoing(passRateDto);
                }
            }
            result.add(wrapper);
        });
        return result;
    }
    @Override
    public List<QualityPassRateDto> getYearlyPassRateStatistics(String year) {
        if (StringUtils.isEmpty(year)) {
            return new ArrayList<>();
        }
        return qualityInspectMapper.getYearlyPassRateStatistics(year);
    }
    @Override
    public List<QualityMonthlyDetailDto> getMonthlyCompletionDetails(String year) {
        if (StringUtils.isEmpty(year)) {
            return new ArrayList<>();
        }
        return qualityInspectMapper.getMonthlyCompletionDetails(year);
    }
    @Override
    public QualityTopParameterDto getTopParameters(Integer inspectType) {
        if (inspectType == null) {
            return new QualityTopParameterDto();
        }
        List<QualityParameterStatDto> list = qualityInspectMapper.getTopParameters(inspectType);
        BigDecimal total = list.stream()
                .map(QualityParameterStatDto::getCount)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        QualityTopParameterDto result = new QualityTopParameterDto();
        result.setTotalCount(total);
        result.setList(list);
        return result;
    }
}
src/main/java/com/ruoyi/quality/service/impl/QualityUnqualifiedServiceImpl.java
@@ -1,10 +1,20 @@
package com.ruoyi.quality.service.impl;
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.utils.bean.BeanUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.production.mapper.ProductProcessRouteItemMapper;
import com.ruoyi.production.mapper.ProductProcessRouteMapper;
import com.ruoyi.production.mapper.ProductWorkOrderMapper;
import com.ruoyi.production.mapper.ProductionProductMainMapper;
import com.ruoyi.production.pojo.*;
import com.ruoyi.production.service.ProductOrderService;
import com.ruoyi.quality.mapper.QualityInspectMapper;
import com.ruoyi.quality.mapper.QualityUnqualifiedMapper;
import com.ruoyi.quality.pojo.QualityInspect;
@@ -16,6 +26,9 @@
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletResponse;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.List;
@AllArgsConstructor
@@ -23,6 +36,13 @@
public class QualityUnqualifiedServiceImpl extends ServiceImpl<QualityUnqualifiedMapper, QualityUnqualified>  implements IQualityUnqualifiedService {
    private QualityUnqualifiedMapper qualityUnqualifiedMapper;
    private IQualityInspectService qualityInspectService;
    private ProductOrderService productOrderService;
    private ProductionProductMainMapper productionProductMainMapper;
    private ProductProcessRouteMapper productProcessRouteMapper;
    private ProductProcessRouteItemMapper productProcessRouteItemMapper;
    private ProductWorkOrderMapper productWorkOrderMapper;
    @Override
    public IPage<QualityUnqualified> qualityUnqualifiedListPage(Page page, QualityUnqualified qualityUnqualified) {
@@ -35,4 +55,86 @@
        ExcelUtil<QualityUnqualified> util = new ExcelUtil<QualityUnqualified>(QualityUnqualified.class);
        util.exportExcel(response, qualityUnqualifieds, "不合格管理导出");
    }
    @Override
    public int deal(QualityUnqualified qualityUnqualified) {
        QualityUnqualified unqualified = qualityUnqualifiedMapper.selectById(qualityUnqualified.getId());
        QualityInspect qualityInspect = qualityInspectService.getById(unqualified.getInspectId());
        switch (qualityUnqualified.getDealResult()) {
            case "返修":
            case "返工":
                //判断质检表是否有相关的报工id,如果有报工id,那么返工需要重新创建生产订单重新生产
                if (ObjectUtils.isNotNull(qualityInspect.getProductMainId())){
                    //返工需要重新创建生产订单重新生产
                    ProductOrder productOrder = productionProductMainMapper.getOrderByMainId(qualityInspect.getProductMainId());
                    ProductOrder order = new ProductOrder();
                    BeanUtils.copyProperties(productOrder, order);
                    order.setId(null);
                    order.setQuantity(unqualified.getQuantity());
                    order.setCompleteQuantity(BigDecimal.ZERO);
                    order.setStartTime(null);
                    order.setEndTime(null);
                    productOrderService.save(order);
                    //新增生产订单下的工艺路线主表
                    ProductProcessRoute productProcessRoute = productProcessRouteMapper.selectList(Wrappers.<ProductProcessRoute>lambdaQuery().eq(ProductProcessRoute::getProductOrderId,productOrder.getId()).orderByDesc(ProductProcessRoute::getId)).get(0);
                    ProductProcessRoute newProcessRoute = new ProductProcessRoute();
                    BeanUtils.copyProperties(productProcessRoute, newProcessRoute);
                    newProcessRoute.setId(null);
                    newProcessRoute.setProductOrderId(order.getId());
                    productProcessRouteMapper.insert(newProcessRoute);
                    //新增生产订单下的工艺路线子表
                    List<ProductProcessRouteItem> processRouteItems = productProcessRouteItemMapper.selectList(new QueryWrapper<ProductProcessRouteItem>().lambda().eq(ProductProcessRouteItem::getProductRouteId, productProcessRoute.getId()));
                    // ç”Ÿæˆå½“前日期的前缀:年月日
                    String datePrefix = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
                    for (ProductProcessRouteItem processRouteItem : processRouteItems) {
                        ProductProcessRouteItem productProcessRouteItem = new ProductProcessRouteItem();
                        BeanUtils.copyProperties(processRouteItem, productProcessRouteItem);
                        productProcessRouteItem.setId(null);
                        productProcessRouteItem.setProductRouteId(newProcessRoute.getId());
                        int insert = productProcessRouteItemMapper.insert(productProcessRouteItem);
                        if (insert > 0) {
                            // æŸ¥è¯¢ä»Šæ—¥å·²å­˜åœ¨çš„æœ€å¤§å·¥å•号
                            QueryWrapper<ProductWorkOrder> queryWrapper = new QueryWrapper<>();
                            queryWrapper.likeRight("work_order_no", datePrefix)
                                    .orderByDesc("work_order_no")
                                    .last("LIMIT 1");
                            ProductWorkOrder lastWorkOrder = productWorkOrderMapper.selectOne(queryWrapper);
                            int sequenceNumber = 1; // é»˜è®¤åºå·
                            if (lastWorkOrder != null && lastWorkOrder.getWorkOrderNo() != null) {
                                String lastNo = lastWorkOrder.getWorkOrderNo().toString();
                                if (lastNo.startsWith(datePrefix)) {
                                    String seqStr = lastNo.substring(datePrefix.length());
                                    try {
                                        sequenceNumber = Integer.parseInt(seqStr) + 1;
                                    } catch (NumberFormatException e) {
                                        sequenceNumber = 1;
                                    }
                                }
                            }
                            // ç”Ÿæˆå®Œæ•´çš„工单号
                            String workOrderNoStr = String.format("%s%03d", datePrefix, sequenceNumber);
                            ProductWorkOrder productWorkOrder = new ProductWorkOrder();
                            productWorkOrder.setProductProcessRouteItemId(productProcessRouteItem.getId());
                            productWorkOrder.setProductOrderId(order.getId());
                            productWorkOrder.setPlanQuantity(order.getQuantity());
                            productWorkOrder.setWorkOrderNo(workOrderNoStr);
                            productWorkOrder.setStatus(1);
                            productWorkOrderMapper.insert(productWorkOrder);
                        }
                    }
                }
                break;
            case "报废":
                break;
            case "让步放行":
                //调用提交合格的接口
                qualityInspect.setCheckResult("合格");
                qualityInspectService.submit(qualityInspect);
                break;
            default:
                break;
        }
        qualityUnqualified.setInspectState(1);//已处理
        return qualityUnqualifiedMapper.updateById(qualityUnqualified);
    }
}
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java
@@ -146,45 +146,7 @@
        // 2. æ‰§è¡Œåˆ é™¤æ“ä½œ
        int result = salesLedgerProductMapper.deleteBatchIds(Arrays.asList(ids));
        //删除对应的生产订单
        //批量查询productOrder
        List<ProductOrder> productOrders = productOrderMapper.selectList(
                new LambdaQueryWrapper<ProductOrder>()
                        .in(ProductOrder::getProductModelId, ids)
        );
        if (!CollectionUtils.isEmpty(productOrders)) {
            List<Long> orderIds = productOrders.stream()
                    .map(ProductOrder::getId)
                    .collect(Collectors.toList());
            // æ‰¹é‡æŸ¥è¯¢processRouteItems
            List<ProductProcessRouteItem> allRouteItems = productProcessRouteItemMapper.selectList(
                    new LambdaQueryWrapper<ProductProcessRouteItem>()
                            .in(ProductProcessRouteItem::getProductOrderId, orderIds)
            );
            if (!CollectionUtils.isEmpty(allRouteItems)) {
                List<Long> routeItemIds = allRouteItems.stream()
                        .map(ProductProcessRouteItem::getId)
                        .collect(Collectors.toList());
                // æ‰¹é‡åˆ é™¤workOrder
                productWorkOrderMapper.delete(new LambdaQueryWrapper<ProductWorkOrder>()
                                .in(ProductWorkOrder::getProductProcessRouteItemId, routeItemIds));
            }
            // æ‰¹é‡åˆ é™¤productProcessRouteItem
            productProcessRouteItemMapper.delete(new LambdaQueryWrapper<ProductProcessRouteItem>()
                            .in(ProductProcessRouteItem::getProductOrderId, orderIds));
            // æ‰¹é‡åˆ é™¤productProcessRoute
            productProcessRouteMapper.delete(new LambdaQueryWrapper<ProductProcessRoute>()
                    .in(ProductProcessRoute::getProductOrderId, orderIds));
            // æ‰¹é‡åˆ é™¤productOrder
            productOrderMapper.delete(new LambdaQueryWrapper<ProductOrder>()
                    .in(ProductOrder::getProductModelId, ids));
        }
        deleteProductionData(Arrays.asList(ids));
        // 3. å¯¹æ¯ä¸ªä¸»è¡¨ID进行金额更新
        for (Long salesLedgerId : mainIds) {
src/main/java/com/ruoyi/staff/controller/AnalyticsController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,32 @@
package com.ruoyi.staff.controller;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.staff.service.AnalyticsService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@RequestMapping("/staff/analytics")
public class AnalyticsController {
    @Resource
    private AnalyticsService analyticsService;
    @GetMapping("/reason")
    public AjaxResult staffLeaveReasonAnalytics() {
        return AjaxResult.success(analyticsService.staffLeaveReasonAnalytics());
    }
    @GetMapping("/monthly_turnover_rate")
    public AjaxResult getMonthlyTurnoverRateFor12Months() {
        return AjaxResult.success(analyticsService.getMonthlyTurnoverRateFor12Months());
    }
    @GetMapping("/total_statistic")
    public AjaxResult getTotalStatistic() {
        return AjaxResult.success(analyticsService.getTotalStatistic());
    }
}
src/main/java/com/ruoyi/staff/dto/StaffLeaveDto.java
@@ -1,11 +1,8 @@
package com.ruoyi.staff.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import com.ruoyi.staff.pojo.StaffLeave;
import lombok.Data;
import java.util.Date;
@Data
public class StaffLeaveDto extends StaffLeave {
@@ -95,4 +92,11 @@
     */
    @Excel(name = "紧急联系人电话", sort = 15)
    private String emergencyContactPhone;
    private int count;
    /**
     * ç¦»èŒåŽŸå› æ–‡æœ¬
     */
    private String reasonText;
}
src/main/java/com/ruoyi/staff/mapper/StaffLeaveMapper.java
@@ -10,6 +10,7 @@
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.time.LocalDate;
import java.util.List;
@@ -18,4 +19,8 @@
    IPage<StaffLeaveDto> staffLeaveListPage(Page page, @Param("c") StaffLeaveDto staffLeaveDto);
    List<StaffLeaveDto> staffLeaveList(@Param("c") StaffLeaveDto staffLeaveDto);
    List<StaffLeaveDto> staffLeaveReasonAnalytics();
    Integer countLeaveByMonth(@Param("monthStart") LocalDate monthStart, @Param("monthEnd") LocalDate monthEnd);
}
src/main/java/com/ruoyi/staff/mapper/StaffOnJobMapper.java
@@ -8,6 +8,7 @@
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.time.LocalDate;
import java.util.List;
@Mapper
@@ -16,4 +17,19 @@
    IPage<StaffOnJobDto> staffOnJobListPage(Page page, @Param("staffOnJob") StaffOnJob staffOnJob);
    List<StaffOnJobDto> staffOnJobList(@Param("staffOnJob") StaffOnJob staffOnJob);
}
    /**
     * ç»Ÿè®¡æŒ‡å®šæ—¥æœŸçš„在职员工数
     * @param date æ—¥æœŸ
     * @return åœ¨èŒå‘˜å·¥æ•°
     */
    Integer countOnJobStaffByDate(@Param("date") LocalDate date);
    /**
     * ç»Ÿè®¡æŒ‡å®šæœˆä»½çš„æ–°å…¥èŒå‘˜å·¥æ•°
     * @param monthStart æœˆä»½å¼€å§‹æ—¥æœŸ
     * @param monthEnd æœˆä»½ç»“束日期
     * @return æ–°å…¥èŒå‘˜å·¥æ•°
     */
    Integer countNewHireByMonth(@Param("monthStart") LocalDate monthStart, @Param("monthEnd") LocalDate monthEnd);
}
src/main/java/com/ruoyi/staff/service/AnalyticsService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,20 @@
package com.ruoyi.staff.service;
import com.ruoyi.staff.dto.StaffLeaveDto;
import com.ruoyi.staff.vo.MonthlyTurnoverRateVo;
import com.ruoyi.staff.vo.TotalTurnoverRateVo;
import java.util.List;
public interface AnalyticsService {
    List<StaffLeaveDto> staffLeaveReasonAnalytics();
    List<MonthlyTurnoverRateVo> getMonthlyTurnoverRateFor12Months();
    /**
     * æŸ¥è¯¢æ€»æµåŠ¨çŽ‡ã€æµå¤±çŽ‡ä»¥åŠåœ¨èŒå‘˜å·¥æ•°
     * @return æ€»ç»Ÿè®¡ç»“æžœ
     */
    TotalTurnoverRateVo getTotalStatistic();
}
src/main/java/com/ruoyi/staff/service/impl/AnalyticsServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,145 @@
package com.ruoyi.staff.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.enums.StaffLeaveReason;
import com.ruoyi.staff.dto.StaffLeaveDto;
import com.ruoyi.staff.mapper.StaffLeaveMapper;
import com.ruoyi.staff.mapper.StaffOnJobMapper;
import com.ruoyi.staff.pojo.StaffLeave;
import com.ruoyi.staff.service.AnalyticsService;
import com.ruoyi.staff.vo.MonthlyTurnoverRateVo;
import com.ruoyi.staff.vo.TotalTurnoverRateVo;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
@AllArgsConstructor
@Service
public class AnalyticsServiceImpl extends ServiceImpl<StaffLeaveMapper, StaffLeave> implements AnalyticsService {
    @Autowired
    private StaffLeaveMapper staffLeaveMapper;
    @Autowired
    private StaffOnJobMapper staffOnJobMapper;
    @Override
    public List<StaffLeaveDto> staffLeaveReasonAnalytics() {
        List<StaffLeaveDto> result = staffLeaveMapper.staffLeaveReasonAnalytics();
        result.forEach(dto -> {
            String reasonCode = dto.getReason();
            StaffLeaveReason reasonEnum = StaffLeaveReason.getByCode(reasonCode);
            if (reasonEnum != null) {
                dto.setReasonText(reasonEnum.getInfo());
            } else {
                dto.setReasonText("未知原因");
            }
        });
        return result;
    }
    @Override
    public List<MonthlyTurnoverRateVo> getMonthlyTurnoverRateFor12Months() {
        List<MonthlyTurnoverRateVo> result = new ArrayList<>();
        LocalDate now = LocalDate.now();
        DateTimeFormatter monthFormatter = DateTimeFormatter.ofPattern("yyyy-MM");
        // è®¡ç®—è¿‘12个月的数据
        for (int i = 11; i >= 0; i--) {
            LocalDate currentMonth = now.minusMonths(i);
            LocalDate monthStart = currentMonth.withDayOfMonth(1);
            LocalDate monthEnd = currentMonth.withDayOfMonth(currentMonth.lengthOfMonth());
            MonthlyTurnoverRateVo vo = new MonthlyTurnoverRateVo();
            vo.setMonth(currentMonth.format(monthFormatter));
            vo.setMonthStartDate(monthStart);
            vo.setMonthEndDate(monthEnd);
            // æœˆåˆå‘˜å·¥æ•°ï¼ˆä¸Šæœˆæœ«åœ¨èŒå‘˜å·¥æ•°ï¼‰
            LocalDate lastMonthEnd = monthStart.minusDays(1);
            Integer beginMonthStaffCount = staffOnJobMapper.countOnJobStaffByDate(lastMonthEnd);
            vo.setBeginMonthStaffCount(beginMonthStaffCount != null ? beginMonthStaffCount : 0);
            // æœˆæœ«å‘˜å·¥æ•°
            Integer endMonthStaffCount = staffOnJobMapper.countOnJobStaffByDate(monthEnd);
            vo.setEndMonthStaffCount(endMonthStaffCount != null ? endMonthStaffCount : 0);
            // æœˆåº¦å…¥èŒå‘˜å·¥æ•°
            Integer newHireCount = staffOnJobMapper.countNewHireByMonth(monthStart, monthEnd);
            vo.setNewHireCount(newHireCount != null ? newHireCount : 0);
            // æœˆåº¦ç¦»èŒå‘˜å·¥æ•°
            Integer leaveCount = staffLeaveMapper.countLeaveByMonth(monthStart, monthEnd);
            vo.setLeaveCount(leaveCount != null ? leaveCount : 0);
            // è®¡ç®—流失率:流失率 = æœˆåº¦ç¦»èŒå‘˜å·¥æ•° / æœˆåˆå‘˜å·¥æ•° * 100%
            Double turnoverRate = 0.0;
            if (vo.getBeginMonthStaffCount() > 0) {
                turnoverRate = (double) vo.getLeaveCount() / vo.getBeginMonthStaffCount() * 100;
                // ä¿ç•™ä¸¤ä½å°æ•°
                turnoverRate = Math.round(turnoverRate * 100.0) / 100.0;
            }
            vo.setTurnoverRate(turnoverRate);
            // è®¡ç®—流动率:流动率 = (月度入职员工数 + æœˆåº¦ç¦»èŒå‘˜å·¥æ•°) / æœˆåˆå‘˜å·¥æ•° * 100%
            Double flowRate = 0.0;
            if (vo.getBeginMonthStaffCount() > 0) {
                flowRate = (double) (vo.getNewHireCount() + vo.getLeaveCount()) / vo.getBeginMonthStaffCount() * 100;
                // ä¿ç•™ä¸¤ä½å°æ•°
                flowRate = Math.round(flowRate * 100.0) / 100.0;
            }
            vo.setFlowRate(flowRate);
            result.add(vo);
        }
        return result;
    }
    @Override
    public TotalTurnoverRateVo getTotalStatistic() {
        TotalTurnoverRateVo result = new TotalTurnoverRateVo();
        LocalDate now = LocalDate.now();
        // èŽ·å–å½“å‰åœ¨èŒå‘˜å·¥æ•°
        Integer currentOnJobCount = staffOnJobMapper.countOnJobStaffByDate(now);
        result.setCurrentOnJobCount(currentOnJobCount);
        // èŽ·å–æœ¬æœˆçš„å¼€å§‹å’Œç»“æŸæ—¥æœŸ
        LocalDate monthStartDate = now.withDayOfMonth(1);
        LocalDate monthEndDate = now.withDayOfMonth(now.lengthOfMonth());
        // èŽ·å–æœˆåˆå‘˜å·¥æ•°ï¼ˆå³ä¸Šæœˆæœ«å‘˜å·¥æ•°ï¼‰
        Integer beginMonthStaffCount = staffOnJobMapper.countOnJobStaffByDate(monthStartDate.minusDays(1));
        // èŽ·å–æœ¬æœˆæ–°å…¥èŒå‘˜å·¥æ•°
        Integer newHireCount = staffOnJobMapper.countNewHireByMonth(monthStartDate, monthEndDate);
        // èŽ·å–æœ¬æœˆç¦»èŒå‘˜å·¥æ•°
        Integer leaveCount = staffLeaveMapper.countLeaveByMonth(monthStartDate, monthEndDate);
        // è®¡ç®—总流动率 = (入职人数 + ç¦»èŒäººæ•°) / æœˆåˆå‘˜å·¥æ•° * 100%
        Double totalFlowRate = 0.0;
        if (beginMonthStaffCount > 0) {
            totalFlowRate = (double) (newHireCount + leaveCount) / beginMonthStaffCount * 100;
            // ä¿ç•™ä¸¤ä½å°æ•°
            totalFlowRate = Math.round(totalFlowRate * 100.0) / 100.0;
        }
        result.setTotalFlowRate(totalFlowRate);
        // è®¡ç®—总流失率 = ç¦»èŒäººæ•° / æœˆåˆå‘˜å·¥æ•° * 100%
        Double totalTurnoverRate = 0.0;
        if (beginMonthStaffCount > 0) {
            totalTurnoverRate = (double) leaveCount / beginMonthStaffCount * 100;
            // ä¿ç•™ä¸¤ä½å°æ•°
            totalTurnoverRate = Math.round(totalTurnoverRate * 100.0) / 100.0;
        }
        result.setTotalTurnoverRate(totalTurnoverRate);
        return result;
    }
}
src/main/java/com/ruoyi/staff/service/impl/StaffLeaveServiceImpl.java
@@ -11,6 +11,7 @@
import com.ruoyi.staff.pojo.StaffOnJob;
import com.ruoyi.staff.service.StaffLeaveService;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.ruoyi.staff.pojo.StaffLeave;
import org.springframework.transaction.annotation.Transactional;
@@ -23,8 +24,10 @@
@AllArgsConstructor
@Service
public class StaffLeaveServiceImpl extends ServiceImpl<StaffLeaveMapper, StaffLeave> implements StaffLeaveService {
    @Autowired
    private StaffLeaveMapper staffLeaveMapper;
    @Autowired
    private StaffOnJobMapper staffOnJobMapper;
    //新增离职列表分页查询
@@ -97,6 +100,5 @@
        ExcelUtil<StaffLeaveDto> util = new ExcelUtil<StaffLeaveDto>(StaffLeaveDto.class);
        util.exportExcel(response, staffLeaves, "员工离职导出");
    }
}
src/main/java/com/ruoyi/staff/service/impl/StaffOnJobServiceImpl.java
@@ -23,6 +23,7 @@
import lombok.AllArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
@@ -39,12 +40,14 @@
@Service
public class StaffOnJobServiceImpl extends ServiceImpl<StaffOnJobMapper, StaffOnJob>  implements IStaffOnJobService {
    @Autowired
    private StaffOnJobMapper staffOnJobMapper;
    @Autowired
    private SysPostMapper sysPostMapper;
    @Autowired
    private StaffContractMapper staffContractMapper;
    @Autowired
    private StaffLeaveMapper staffLeaveMapper;
src/main/java/com/ruoyi/staff/vo/MonthlyTurnoverRateVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,39 @@
package com.ruoyi.staff.vo;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.time.LocalDate;
/**
 * æœˆåº¦å‘˜å·¥æµåŠ¨çŽ‡å’Œæµå¤±çŽ‡ç»Ÿè®¡VO
 */
@Data
public class MonthlyTurnoverRateVo {
    @ApiModelProperty("月份")
    private String month;
    @ApiModelProperty("月初员工数")
    private Integer beginMonthStaffCount;
    @ApiModelProperty("月末员工数")
    private Integer endMonthStaffCount;
    @ApiModelProperty("月度入职员工数")
    private Integer newHireCount;
    @ApiModelProperty("月度离职员工数")
    private Integer leaveCount;
    @ApiModelProperty("流失率(%)")
    private Double turnoverRate;
    @ApiModelProperty("流动率(%)")
    private Double flowRate;
    @ApiModelProperty("月份开始日期")
    private LocalDate monthStartDate;
    @ApiModelProperty("月份结束日期")
    private LocalDate monthEndDate;
}
src/main/java/com/ruoyi/staff/vo/TotalTurnoverRateVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,19 @@
package com.ruoyi.staff.vo;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
 * å‘˜å·¥æ€»æµåŠ¨çŽ‡ã€æµå¤±çŽ‡åŠåœ¨èŒå‘˜å·¥æ•°ç»Ÿè®¡VO
 */
@Data
public class TotalTurnoverRateVo {
    @ApiModelProperty("总流动率(%)")
    private Double totalFlowRate;
    @ApiModelProperty("总流失率(%)")
    private Double totalTurnoverRate;
    @ApiModelProperty("当前在职员工数")
    private Integer currentOnJobCount;
}
src/main/java/com/ruoyi/stock/controller/StockInRecordController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,54 @@
package com.ruoyi.stock.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.stock.dto.StockInRecordDto;
import com.ruoyi.stock.service.StockInRecordService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@Api(tags = "采购入库")
@RequestMapping("/stockInRecord")
public class StockInRecordController {
    @Autowired
    private StockInRecordService stockInRecordService;
    @GetMapping("/listPageByProduction")
    @Log(title = "生产入库-入库管理-列表", businessType = BusinessType.OTHER)
    @ApiOperation(value = "入库管理列表")
    public AjaxResult listPage(Page page, StockInRecordDto stockInRecordDto) {
        IPage<StockInRecordDto> result = stockInRecordService.listPage(page, stockInRecordDto);
        return AjaxResult.success(result);
    }
    @PostMapping("")
    @Log(title = "入库管理-新增入库", businessType = BusinessType.INSERT)
    public AjaxResult add(@RequestBody StockInRecordDto stockInRecordDto) {
        return AjaxResult.success(stockInRecordService.add(stockInRecordDto));
    }
    @PutMapping("/{id}")
    @Log(title = "入库管理-更新入库", businessType = BusinessType.UPDATE)
    public AjaxResult update(@PathVariable("id") Long id, @RequestBody StockInRecordDto stockInRecordDto) {
        return AjaxResult.success(stockInRecordService.update(id, stockInRecordDto));
    }
    @DeleteMapping("")
    @Log(title = "入库管理-删除入库", businessType = BusinessType.DELETE)
    public AjaxResult delete(@RequestBody List<Long> ids) {
        if(CollectionUtils.isEmpty(ids)){
            return AjaxResult.error("请选择至少一条数据");
        }
        return AjaxResult.success(stockInRecordService.batchDelete(ids));
    }
}
src/main/java/com/ruoyi/stock/controller/StockInventoryController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
package com.ruoyi.stock.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * <p>
 * åº“存表 å‰ç«¯æŽ§åˆ¶å™¨
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-01-21 04:16:36
 */
@RestController
@RequestMapping("/stockInventory")
public class StockInventoryController {
}
src/main/java/com/ruoyi/stock/controller/StockOutRecordController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,59 @@
package com.ruoyi.stock.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.stock.dto.StockOutRecordDto;
import com.ruoyi.stock.service.StockOutRecordService;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
 * <p>
 * å‡ºåº“记录表 å‰ç«¯æŽ§åˆ¶å™¨
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-01-21 05:27:04
 */
@RestController
@RequestMapping("/stockOutRecord")
public class StockOutRecordController {
    @Autowired
    private StockOutRecordService stockOutRecordService;
    @GetMapping("/listPageByProduction")
    @Log(title = "生产出库-出库管理-列表", businessType = BusinessType.OTHER)
    @ApiOperation(value = "出库管理列表")
    public AjaxResult listPage(Page page, StockOutRecordDto stockOutRecordDto) {
        IPage<StockOutRecordDto> result = stockOutRecordService.listPage(page, stockOutRecordDto);
        return AjaxResult.success(result);
    }
    @PostMapping("")
    @Log(title = "出库管理-新增出库", businessType = BusinessType.INSERT)
    public AjaxResult add(@RequestBody StockOutRecordDto stockOutRecordDto) {
        return AjaxResult.success(stockOutRecordService.add(stockOutRecordDto));
    }
    @PutMapping("/{id}")
    @Log(title = "出库管理-更新出库", businessType = BusinessType.UPDATE)
    public AjaxResult update(@PathVariable("id") Long id, @RequestBody StockOutRecordDto stockOutRecordDto) {
        return AjaxResult.success(stockOutRecordService.update(id, stockOutRecordDto));
    }
    @DeleteMapping("")
    @Log(title = "出库管理-删除出库", businessType = BusinessType.DELETE)
    public AjaxResult delete(@RequestBody List<Long> ids) {
        if(CollectionUtils.isEmpty(ids)){
            return AjaxResult.error("请选择至少一条数据");
        }
        return AjaxResult.success(stockOutRecordService.batchDelete(ids));
    }
}
src/main/java/com/ruoyi/stock/dto/StockInRecordDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
package com.ruoyi.stock.dto;
import com.ruoyi.stock.pojo.StockInRecord;
public class StockInRecordDto extends StockInRecord {
    /**
     * äº§å“åç§°
     */
    private String productName;
    /**
     * äº§å“è§„æ ¼
     */
    private String model;
    /**
     * äº§å“å•位
     */
    private String unit;
}
src/main/java/com/ruoyi/stock/dto/StockOutRecordDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
package com.ruoyi.stock.dto;
import com.ruoyi.stock.pojo.StockOutRecord;
public class StockOutRecordDto extends StockOutRecord {
    /**
     * äº§å“åç§°
     */
    private String productName;
    /**
     * äº§å“è§„æ ¼
     */
    private String model;
    /**
     * äº§å“å•位
     */
    private String unit;
}
src/main/java/com/ruoyi/stock/mapper/StockInRecordMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,12 @@
package com.ruoyi.stock.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.stock.dto.StockInRecordDto;
import com.ruoyi.stock.pojo.StockInRecord;
import org.apache.ibatis.annotations.Param;
public interface StockInRecordMapper extends BaseMapper<StockInRecord> {
    IPage<StockInRecordDto> listPage(Page page, @Param("params") StockInRecordDto stockInRecordDto);
}
src/main/java/com/ruoyi/stock/mapper/StockInventoryMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
package com.ruoyi.stock.mapper;
import com.ruoyi.stock.pojo.StockInventory;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
 * <p>
 * åº“存表 Mapper æŽ¥å£
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-01-21 04:16:36
 */
@Mapper
public interface StockInventoryMapper extends BaseMapper<StockInventory> {
}
src/main/java/com/ruoyi/stock/mapper/StockOutRecordMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,23 @@
package com.ruoyi.stock.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.stock.dto.StockOutRecordDto;
import com.ruoyi.stock.pojo.StockOutRecord;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
 * <p>
 * å‡ºåº“记录表 Mapper æŽ¥å£
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-01-21 05:27:04
 */
@Mapper
public interface StockOutRecordMapper extends BaseMapper<StockOutRecord> {
    IPage<StockOutRecordDto> listPage(Page page, @Param("params") StockOutRecordDto stockOutRecordDto);
}
src/main/java/com/ruoyi/stock/pojo/StockInRecord.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,64 @@
package com.ruoyi.stock.pojo;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Data
@TableName("stock_in_record")
@ApiModel("入库管理")
public class StockInRecord {
    private static final long serialVersionUID = 1L;
    /**
     * åºå·
     */
    @TableId(type = IdType.AUTO)
    private Long id;
    @ApiModelProperty(value = "入库批次")
    private String inboundBatches;
    @ApiModelProperty(value = "入库数量")
    private BigDecimal stockInNum;
    @ApiModelProperty(value = "记录类型")
    private String recordType;
    @ApiModelProperty(value = "记录ID")
    private Long recordId;
    @ApiModelProperty(value = "产品规格ID")
    private Long productModelId;
    @ApiModelProperty(value = "备注")
    private String remark;
    @ApiModelProperty(value = "创建时间")
    @TableField(fill = FieldFill.INSERT)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createTime;
    @ApiModelProperty(value = "创建用户")
    @TableField(fill = FieldFill.INSERT)
    private Integer createUser;
    @ApiModelProperty(value = "修改时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    @ApiModelProperty(value = "修改用户")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Integer updateUser;
    @ApiModelProperty(value = "租户ID")
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
}
src/main/java/com/ruoyi/stock/pojo/StockInventory.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,54 @@
package com.ruoyi.stock.pojo;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
/**
 * <p>
 * åº“存表
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-01-21 04:16:36
 */
@Getter
@Setter
@TableName("stock_inventory")
@ApiModel(value = "StockInventory对象", description = "库存表")
public class StockInventory implements Serializable {
    private static final long serialVersionUID = 1L;
    @ApiModelProperty("主键id")
    private Long id;
    @ApiModelProperty("规格id")
    private Integer productModelId;
    @ApiModelProperty("数量")
    private BigDecimal qualitity;
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    @ApiModelProperty("更新时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Integer updateTime;
    @ApiModelProperty("版本号")
    private Integer version;
    @ApiModelProperty("被订单锁定数量")
    private BigDecimal lockedQuantity;
    @ApiModelProperty("预警数量")
    private Integer warnNum;
}
src/main/java/com/ruoyi/stock/pojo/StockOutRecord.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,69 @@
package com.ruoyi.stock.pojo;
import com.baomidou.mybatisplus.annotation.*;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
 * <p>
 * å‡ºåº“记录表
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-01-21 05:27:04
 */
@Getter
@Setter
@TableName("stock_out_record")
@ApiModel(value = "StockOutRecord对象", description = "出库记录表")
public class StockOutRecord implements Serializable {
    private static final long serialVersionUID = 1L;
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;
    @ApiModelProperty("入库批次")
    private String outboundBatches;
    @ApiModelProperty("入库数量")
    private BigDecimal stockOutNum;
    @ApiModelProperty("入库来源id")
    private Integer recordId;
    @ApiModelProperty("入库类型")
    private String recordType;
    @ApiModelProperty("产品规格id")
    private Integer productModelId;
    @ApiModelProperty("备注")
    private String remark;
    @ApiModelProperty("创建时间")
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    @ApiModelProperty("更新时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    @ApiModelProperty("创建人")
    @TableField(fill = FieldFill.INSERT)
    private Integer createUser;
    @ApiModelProperty("更新人")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Integer updateUser;
    @ApiModelProperty("租户id")
    @TableField(fill = FieldFill.INSERT)
    private Integer tenantId;
}
src/main/java/com/ruoyi/stock/service/StockInRecordService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,19 @@
package com.ruoyi.stock.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.stock.dto.StockInRecordDto;
import com.ruoyi.stock.pojo.StockInRecord;
import java.util.List;
public interface StockInRecordService extends IService<StockInRecord> {
    IPage<StockInRecordDto> listPage(Page page, StockInRecordDto stockInRecordDto);
    int add(StockInRecordDto stockInRecordDto);
    int update(Long id, StockInRecordDto stockInRecordDto);
    int batchDelete(List<Long> ids);
}
src/main/java/com/ruoyi/stock/service/StockInventoryService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,16 @@
package com.ruoyi.stock.service;
import com.ruoyi.stock.pojo.StockInventory;
import com.baomidou.mybatisplus.extension.service.IService;
/**
 * <p>
 * åº“存表 æœåŠ¡ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-01-21 04:16:36
 */
public interface StockInventoryService extends IService<StockInventory> {
}
src/main/java/com/ruoyi/stock/service/StockOutRecordService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,27 @@
package com.ruoyi.stock.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.stock.dto.StockOutRecordDto;
import com.ruoyi.stock.pojo.StockOutRecord;
import java.util.List;
/**
 * <p>
 * å‡ºåº“记录表 æœåŠ¡ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-01-21 05:27:04
 */
public interface StockOutRecordService extends IService<StockOutRecord> {
    IPage<StockOutRecordDto> listPage(Page page, StockOutRecordDto stockOutRecordDto);
    int add(StockOutRecordDto stockOutRecordDto);
    int update(Long id, StockOutRecordDto stockOutRecordDto);
    int batchDelete(List<Long> ids);
}
src/main/java/com/ruoyi/stock/service/impl/StockInRecordServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,60 @@
package com.ruoyi.stock.service.impl;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.exception.base.BaseException;
import com.ruoyi.common.utils.OrderUtils;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.staff.pojo.StaffOnJob;
import com.ruoyi.stock.dto.StockInRecordDto;
import com.ruoyi.stock.mapper.StockInRecordMapper;
import com.ruoyi.stock.pojo.StockInRecord;
import com.ruoyi.stock.service.StockInRecordService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
public class StockInRecordServiceImpl extends ServiceImpl<StockInRecordMapper, StockInRecord> implements StockInRecordService {
    @Autowired
    private StockInRecordMapper stockInRecordMapper;
    @Override
    public IPage<StockInRecordDto> listPage(Page page, StockInRecordDto stockInRecordDto) {
        return stockInRecordMapper.listPage(page, stockInRecordDto);
    }
    // æ–°å¢žå…¥åº“
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int add(StockInRecordDto stockInRecordDto) {
        String no = OrderUtils.countTodayByCreateTime(stockInRecordMapper, "RK");
        stockInRecordDto.setInboundBatches(no);
        StockInRecord stockInRecord = new StockInRecord();
        BeanUtils.copyProperties(stockInRecordDto, stockInRecord);
        return stockInRecordMapper.insert(stockInRecord);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int update(Long id, StockInRecordDto stockInRecordDto) {
        // åˆ¤æ–­å¯¹è±¡æ˜¯å¦å­˜åœ¨
        StockInRecord stockInRecord = stockInRecordMapper.selectById(id);
        if (stockInRecord == null){
            throw new BaseException("该入库记录不存在,无法更新!!!");
        }
        String[] ignoreProperties = {"id", "inbound_batches"};//排除id属性
        BeanUtils.copyProperties(stockInRecordDto, stockInRecord, ignoreProperties);
        return stockInRecordMapper.updateById(stockInRecord);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int batchDelete(List<Long> ids) {
        return stockInRecordMapper.deleteBatchIds(ids);
    }
}
src/main/java/com/ruoyi/stock/service/impl/StockInventoryServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,20 @@
package com.ruoyi.stock.service.impl;
import com.ruoyi.stock.pojo.StockInventory;
import com.ruoyi.stock.mapper.StockInventoryMapper;
import com.ruoyi.stock.service.StockInventoryService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
 * <p>
 * åº“存表 æœåŠ¡å®žçŽ°ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-01-21 04:16:36
 */
@Service
public class StockInventoryServiceImpl extends ServiceImpl<StockInventoryMapper, StockInventory> implements StockInventoryService {
}
src/main/java/com/ruoyi/stock/service/impl/StockOutRecordServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,63 @@
package com.ruoyi.stock.service.impl;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.exception.base.BaseException;
import com.ruoyi.common.utils.OrderUtils;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.stock.dto.StockOutRecordDto;
import com.ruoyi.stock.mapper.StockOutRecordMapper;
import com.ruoyi.stock.pojo.StockInRecord;
import com.ruoyi.stock.pojo.StockOutRecord;
import com.ruoyi.stock.service.StockOutRecordService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
 * <p>
 * å‡ºåº“记录表 æœåŠ¡å®žçŽ°ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-01-21 05:27:04
 */
@Service
public class StockOutRecordServiceImpl extends ServiceImpl<StockOutRecordMapper, StockOutRecord> implements StockOutRecordService {
    @Autowired
    private StockOutRecordMapper stockOutRecordMapper;
    @Override
    public IPage<StockOutRecordDto> listPage(Page page, StockOutRecordDto stockOutRecordDto) {
        return stockOutRecordMapper.listPage(page, stockOutRecordDto);
    }
    @Override
    public int add(StockOutRecordDto stockOutRecordDto) {
        String no = OrderUtils.countTodayByCreateTime(stockOutRecordMapper, "CK");
        stockOutRecordDto.setOutboundBatches(no);
        StockInRecord stockInRecord = new StockInRecord();
        BeanUtils.copyProperties(stockOutRecordDto, stockInRecord);
        return stockOutRecordMapper.insert(stockOutRecordDto);
    }
    @Override
    public int update(Long id, StockOutRecordDto stockOutRecordDto) {
        // åˆ¤æ–­å¯¹è±¡æ˜¯å¦å­˜åœ¨
        StockOutRecord stockOutRecord = stockOutRecordMapper.selectById(id);
        if (stockOutRecord == null){
            throw new BaseException("该出库记录不存在,无法更新!!!");
        }
        String[] ignoreProperties = {"id", "inbound_batches"};//排除id属性
        BeanUtils.copyProperties(stockOutRecordDto, stockOutRecord, ignoreProperties);
        return stockOutRecordMapper.updateById(stockOutRecord);
    }
    @Override
    public int batchDelete(List<Long> ids) {
        return stockOutRecordMapper.deleteBatchIds(ids);
    }
}
src/main/resources/mapper/production/ProductionProductMainMapper.xml
@@ -48,6 +48,13 @@
        order by ppm.id
    </select>
    <select id="getOrderByMainId" resultType="com.ruoyi.production.pojo.ProductOrder">
        select po.*
        from product_order po
        left join  product_work_order pwo on po.id = pwo.product_order_id
        left join production_product_main pm on work_order_id=pwo.id
        where pm.id=#{productMainId}
    </select>
    <delete id="deleteByWorkOrderIds" parameterType="java.util.List">
        DELETE FROM production_product_main
src/main/resources/mapper/quality/QualityInspectMapper.xml
@@ -23,7 +23,7 @@
            AND check_time &gt;= DATE_FORMAT(#{qualityInspect.entryDateStart},'%Y-%m-%d')
        </if>
        <if test="qualityInspect.entryDateEnd != null and qualityInspect.entryDateEnd != '' ">
            AND  check_time &lt;= DATE_FORMAT(#{qualityInspect.entryDateEnd},'%Y-%m-%d')
            AND check_time &lt;= DATE_FORMAT(#{qualityInspect.entryDateEnd},'%Y-%m-%d')
        </if>
        ORDER BY check_time DESC
    </select>
@@ -55,4 +55,292 @@
        </foreach>
    </delete>
    <select id="getInspectStatistics" resultType="com.ruoyi.quality.dto.QualityInspectStatDto">
        SELECT 0                                                                                   AS inspectType,
               COALESCE((SELECT SUM(inbound_num) FROM procurement_record_storage WHERE type = 1), 0) +
               COALESCE((SELECT SUM(quantity) FROM quality_unqualified WHERE inspect_type = 0), 0) AS totalCount,
               COALESCE((SELECT SUM(inbound_num)
                         FROM procurement_record_storage
                         WHERE type = 1
                           AND quality_inspect_id != 0
                           AND quality_inspect_id IS NOT NULL), 0) +
               COALESCE((SELECT SUM(quantity) FROM quality_unqualified WHERE inspect_type = 0), 0) AS completedCount
        UNION ALL
        SELECT 1                                                   AS inspectType,
               COALESCE((SELECT SUM(inbound_num)
                         FROM procurement_record_storage
                         WHERE type = 2
                           AND sales_ledger_product_id = 0), 0)    AS totalCount,
               COALESCE((SELECT SUM(inbound_num)
                         FROM procurement_record_storage
                         WHERE type = 2
                           AND sales_ledger_product_id = 0
                           AND quality_inspect_id != 0
                           AND quality_inspect_id IS NOT NULL), 0) AS completedCount
        UNION ALL
        SELECT 2                                                   AS inspectType,
               COALESCE((SELECT SUM(inbound_num)
                         FROM procurement_record_storage
                         WHERE type = 2
                           AND sales_ledger_product_id != 0), 0)   AS totalCount,
               COALESCE((SELECT SUM(inbound_num)
                         FROM procurement_record_storage
                         WHERE type = 2
                           AND sales_ledger_product_id != 0
                           AND quality_inspect_id != 0
                           AND quality_inspect_id IS NOT NULL), 0) AS completedCount
    </select>
    <select id="getPassRateStatistics" resultType="com.ruoyi.quality.dto.QualityPassRateDto">
        SELECT t.inspectType,
               COALESCE(prs.totalCount, 0)                                            AS totalCount,
               COALESCE(qi.completedCount, 0)                                         AS completedCount,
               COALESCE(qi.qualifiedCount, 0)                                         AS qualifiedCount,
               COALESCE(qi.unqualifiedCount, 0)                                       AS unqualifiedCount,
               IF(COALESCE(prs.totalCount, 0) = 0, 0,
                  ROUND(COALESCE(qi.completedCount, 0) / prs.totalCount * 100, 2))    AS completionRate,
               IF(COALESCE(qi.completedCount, 0) = 0, 0,
                  ROUND(COALESCE(qi.qualifiedCount, 0) / qi.completedCount * 100, 2)) AS passRate
        FROM (SELECT 0 AS inspectType
              UNION ALL
              SELECT 1
              UNION ALL
              SELECT 2) t
                 LEFT JOIN (SELECT inspect_type,
                                   SUM(quantity)                                 AS completedCount,
                                   SUM(IF(check_result = '合格', quantity, 0))   AS qualifiedCount,
                                   SUM(IF(check_result = '不合格', quantity, 0)) AS unqualifiedCount
                            FROM quality_inspect
                            WHERE inspect_state = 1
                            GROUP BY inspect_type) qi
                           ON t.inspectType = qi.inspect_type
                 LEFT JOIN (SELECT 0           AS inspectType,
                                   COALESCE((SELECT SUM(inbound_num) FROM procurement_record_storage WHERE type = 1),
                                            0) +
                                   COALESCE((SELECT SUM(quantity) FROM quality_unqualified WHERE inspect_type = 0),
                                            0) AS totalCount
                            UNION ALL
                            SELECT 1                                                AS inspectType,
                                   COALESCE((SELECT SUM(inbound_num)
                                             FROM procurement_record_storage
                                             WHERE type = 2
                                               AND sales_ledger_product_id = 0), 0) AS totalCount
                            UNION ALL
                            SELECT 2                                                 AS inspectType,
                                   COALESCE((SELECT SUM(inbound_num)
                                             FROM procurement_record_storage
                                             WHERE type = 2
                                               AND sales_ledger_product_id != 0), 0) AS totalCount) prs
                           ON t.inspectType = prs.inspectType
        ORDER BY t.inspectType;
    </select>
    <select id="getMonthlyPassRateStatistics" resultType="com.ruoyi.quality.dto.QualityMonthlyPassRateDto">
        WITH RECURSIVE
            months AS (SELECT 1 AS month_num
                       UNION ALL
                       SELECT month_num + 1
                       FROM months
                       WHERE month_num &lt; 12),
            types AS (SELECT 0 AS inspectType
                      UNION ALL
                      SELECT 1
                      UNION ALL
                      SELECT 2),
            base AS (SELECT m.month_num, t.inspectType
                     FROM months m,
                          types t),
            qi_data AS (SELECT MONTH(check_time)                                               AS month_num,
                               inspect_type,
                               SUM(quantity)                                                   AS completedCount,
                               SUM(CASE WHEN check_result = '合格' THEN quantity ELSE 0 END)   AS qualifiedCount,
                               SUM(CASE WHEN check_result = '不合格' THEN quantity ELSE 0 END) AS unqualifiedCount
                        FROM quality_inspect
                        WHERE YEAR(check_time) = #{year}
                          AND inspect_state = 1
                        GROUP BY MONTH(check_time), inspect_type),
            prs_data AS (SELECT month_num,
                                inspectType,
                                SUM(totalCount) AS totalCount
                         FROM (SELECT MONTH(create_time) AS month_num,
                                      0                  AS inspectType,
                                      inbound_num        AS totalCount
                               FROM procurement_record_storage
                               WHERE type = 1
                                 AND YEAR(create_time) = #{year}
                               UNION ALL
                               SELECT MONTH(check_time) AS month_num,
                                      0                 AS inspectType,
                                      quantity          AS totalCount
                               FROM quality_unqualified
                               WHERE inspect_type = 0
                                 AND YEAR(check_time) = #{year}
                               UNION ALL
                               SELECT MONTH(create_time) AS month_num,
                                      CASE
                                          WHEN sales_ledger_product_id = 0 THEN 1
                                          ELSE 2
                                          END            AS inspectType,
                                      inbound_num        AS totalCount
                               FROM procurement_record_storage
                               WHERE type = 2
                                 AND YEAR(create_time) = #{year}) sub
                         GROUP BY month_num, inspectType)
        SELECT CASE b.month_num
                   WHEN 1 THEN '一月'
                   WHEN 2 THEN '二月'
                   WHEN 3 THEN '三月'
                   WHEN 4 THEN '四月'
                   WHEN 5 THEN '五月'
                   WHEN 6 THEN '六月'
                   WHEN 7 THEN '七月'
                   WHEN 8 THEN '八月'
                   WHEN 9 THEN '九月'
                   WHEN 10 THEN '十月'
                   WHEN 11 THEN '十一月'
                   WHEN 12 THEN '十二月'
                   END                         AS month,
               b.inspectType,
               COALESCE(p.totalCount, 0)       AS totalCount,
               COALESCE(q.completedCount, 0)   AS completedCount,
               COALESCE(q.qualifiedCount, 0)   AS qualifiedCount,
               COALESCE(q.unqualifiedCount, 0) AS unqualifiedCount,
               CASE
                   WHEN COALESCE(p.totalCount, 0) = 0 THEN 0
                   ELSE ROUND(COALESCE(q.completedCount, 0) / p.totalCount * 100, 2)
                   END                         AS completionRate,
               CASE
                   WHEN COALESCE(q.completedCount, 0) = 0 THEN 0
                   ELSE ROUND(COALESCE(q.qualifiedCount, 0) / q.completedCount * 100, 2)
                   END                         AS passRate
        FROM base b
                 LEFT JOIN qi_data q ON b.month_num = q.month_num AND b.inspectType = q.inspect_type
                 LEFT JOIN prs_data p ON b.month_num = p.month_num AND b.inspectType = p.inspectType
        ORDER BY b.month_num, b.inspectType
    </select>
    <select id="getYearlyPassRateStatistics" resultType="com.ruoyi.quality.dto.QualityPassRateDto">
        SELECT t.inspectType,
               COALESCE(prs.totalCount, 0)      AS totalCount,
               COALESCE(qi.completedCount, 0)   AS completedCount,
               COALESCE(qi.qualifiedCount, 0)   AS qualifiedCount,
               COALESCE(qi.unqualifiedCount, 0) AS unqualifiedCount,
               CASE
                   WHEN COALESCE(prs.totalCount, 0) = 0 THEN 0
                   ELSE ROUND(COALESCE(qi.completedCount, 0) / prs.totalCount * 100, 2)
                   END                          AS completionRate,
               CASE
                   WHEN COALESCE(qi.completedCount, 0) = 0 THEN 0
                   ELSE ROUND(COALESCE(qi.qualifiedCount, 0) / qi.completedCount * 100, 2)
                   END                          AS passRate
        FROM (SELECT 0 AS inspectType
              UNION ALL
              SELECT 1
              UNION ALL
              SELECT 2) t
                 LEFT JOIN (SELECT inspect_type,
                                   SUM(quantity)                                                   AS completedCount,
                                   SUM(CASE WHEN check_result = '合格' THEN quantity ELSE 0 END)   AS qualifiedCount,
                                   SUM(CASE WHEN check_result = '不合格' THEN quantity ELSE 0 END) AS unqualifiedCount
                            FROM quality_inspect
                            WHERE YEAR(check_time) = #{year}
                              AND inspect_state = 1
                            GROUP BY inspect_type) qi
                           ON t.inspectType = qi.inspect_type
                 LEFT JOIN (SELECT 0                                               AS inspectType,
                                   COALESCE((SELECT SUM(inbound_num)
                                             FROM procurement_record_storage
                                             WHERE type = 1
                                               AND YEAR(create_time) = #{year}), 0) +
                                   COALESCE((SELECT SUM(quantity)
                                             FROM quality_unqualified
                                             WHERE inspect_type = 0
                                               AND YEAR(check_time) = #{year}), 0) AS totalCount
                            UNION ALL
                            SELECT 1                                                AS inspectType,
                                   COALESCE((SELECT SUM(inbound_num)
                                             FROM procurement_record_storage
                                             WHERE type = 2
                                               AND sales_ledger_product_id = 0
                                               AND YEAR(create_time) = #{year}), 0) AS totalCount
                            UNION ALL
                            SELECT 2                                                AS inspectType,
                                   COALESCE((SELECT SUM(inbound_num)
                                             FROM procurement_record_storage
                                             WHERE type = 2
                                               AND sales_ledger_product_id != 0
                                               AND YEAR(create_time) = #{year}), 0) AS totalCount) prs
                           ON t.inspectType = prs.inspectType
        ORDER BY t.inspectType
    </select>
    <select id="getMonthlyCompletionDetails" resultType="com.ruoyi.quality.dto.QualityMonthlyDetailDto">
        WITH RECURSIVE
            months AS (SELECT 1 AS month_num
                       UNION ALL
                       SELECT month_num + 1
                       FROM months
                       WHERE month_num &lt; 12),
            qi_data AS (SELECT MONTH(check_time)                                        AS month_num,
                               SUM(CASE WHEN inspect_type = 0 THEN quantity ELSE 0 END) AS rawMaterialCount,
                               SUM(CASE WHEN inspect_type = 1 THEN quantity ELSE 0 END) AS processCount,
                               SUM(CASE WHEN inspect_type = 2 THEN quantity ELSE 0 END) AS outgoingCount
                        FROM quality_inspect
                        WHERE YEAR(check_time) = #{year}
                          AND inspect_state = 1
                        GROUP BY MONTH(check_time))
        SELECT CASE m.month_num
                   WHEN 1 THEN '一月'
                   WHEN 2 THEN '二月'
                   WHEN 3 THEN '三月'
                   WHEN 4 THEN '四月'
                   WHEN 5 THEN '五月'
                   WHEN 6 THEN '六月'
                   WHEN 7 THEN '七月'
                   WHEN 8 THEN '八月'
                   WHEN 9 THEN '九月'
                   WHEN 10 THEN '十月'
                   WHEN 11 THEN '十一月'
                   WHEN 12 THEN '十二月'
                   END                         AS month,
               COALESCE(d.rawMaterialCount, 0) AS rawMaterialCount,
               COALESCE(d.processCount, 0)     AS processCount,
               COALESCE(d.outgoingCount, 0)    AS outgoingCount
        FROM months m
                 LEFT JOIN qi_data d ON m.month_num = d.month_num
        ORDER BY m.month_num
    </select>
    <select id="getTopParameters" resultType="com.ruoyi.quality.dto.QualityParameterStatDto">
        WITH parameter_counts AS (SELECT qip.parameter_item            AS name,
                                         SUM(COALESCE(qi.quantity, 0)) AS count
                                  FROM quality_inspect qi
                                           JOIN quality_inspect_param qip ON qip.inspect_id = qi.id
                                  WHERE qi.inspect_type = #{inspectType}
                                  GROUP BY qip.parameter_item),
             ranked AS (SELECT name,
                               count,
                               ROW_NUMBER() OVER (ORDER BY count DESC) AS rn
                        FROM parameter_counts),
             total AS (SELECT SUM(count) AS total_count
                       FROM parameter_counts)
        SELECT name,
               count,
               CASE
                   WHEN (SELECT total_count FROM total) = 0 THEN 0
                   ELSE ROUND(count / (SELECT total_count FROM total) * 100, 2)
                   END AS percentage
        FROM (SELECT name, count, rn
              FROM ranked
              WHERE rn &lt;= 4
              UNION ALL
              SELECT '其他检测' AS name,
                     SUM(count) AS count,
                     5          AS rn
              FROM ranked
              WHERE rn &gt; 4
              HAVING SUM(count) &gt; 0) t
        ORDER BY rn
    </select>
</mapper>
src/main/resources/mapper/quality/QualityTestStandardMapper.xml
@@ -30,5 +30,6 @@
        <if test="process!='' and process!=null">
            and pp.name = #{process}
        </if>
        order by qts.id desc
    </select>
</mapper>
src/main/resources/mapper/staff/StaffLeaveMapper.xml
@@ -61,4 +61,24 @@
            AND soj.staff_name LIKE CONCAT('%',#{c.staffName},'%')
        </if>
    </select>
    <select id="staffLeaveReasonAnalytics" resultType="com.ruoyi.staff.dto.StaffLeaveDto">
        SELECT
        staff_leave.reason as reason,
        COUNT(*) as count
        FROM staff_leave
        LEFT JOIN
        staff_on_job soj ON soj.id = staff_leave.staff_on_job_id
        where 1=1
        GROUP BY staff_leave.reason
    </select>
    <!-- ç»Ÿè®¡æŒ‡å®šæœˆä»½çš„离职员工数 -->
    <select id="countLeaveByMonth" resultType="java.lang.Integer">
        SELECT COUNT(*)
        FROM staff_leave sl
        LEFT JOIN staff_on_job soj ON sl.staff_on_job_id = soj.id
        WHERE DATE_FORMAT(sl.create_time, '%Y-%m-%d') BETWEEN #{monthStart} AND #{monthEnd}
        AND soj.staff_state = 0
    </select>
</mapper>
src/main/resources/mapper/staff/StaffOnJobMapper.xml
@@ -43,4 +43,19 @@
            AND staff_name LIKE CONCAT('%',#{staffOnJob.staffName},'%')
        </if>
    </select>
    <!-- ç»Ÿè®¡æŒ‡å®šæ—¥æœŸçš„在职员工数 -->
    <select id="countOnJobStaffByDate" resultType="java.lang.Integer">
        SELECT COUNT(*)
        FROM staff_on_job
        WHERE staff_state = 1
        AND DATE_FORMAT(create_time, '%Y-%m-%d') &lt;= #{date}
    </select>
    <!-- ç»Ÿè®¡æŒ‡å®šæœˆä»½çš„æ–°å…¥èŒå‘˜å·¥æ•° -->
    <select id="countNewHireByMonth" resultType="java.lang.Integer">
        SELECT COUNT(*)
        FROM staff_on_job
        WHERE staff_state = 1
        AND DATE_FORMAT(create_time, '%Y-%m-%d') BETWEEN #{monthStart} AND #{monthEnd}
    </select>
</mapper>
src/main/resources/mapper/stock/StockInRecordMapper.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.stock.mapper.StockInRecordMapper">
    <select id="listPage" resultType="com.ruoyi.stock.dto.StockInRecordDto">
        SELECT
        sir.*
        pm.product_name as productName,
        pm.model,
        pm.unit
        FROM stock_in_record as sir
        LEFT JOIN product_models as pm on sir.product_model_id = pm.id
        <where>
            <if test="params.timeStr != null and params.timeStr != ''">
                and sir.create_time like concat('%',#{param.timeStr},'%')
            </if>
        </where>
        order by sir.id desc
    </select>
</mapper>
src/main/resources/mapper/stock/StockInventoryMapper.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.stock.mapper.StockInventoryMapper">
    <!-- é€šç”¨æŸ¥è¯¢æ˜ å°„结果 -->
    <resultMap id="BaseResultMap" type="com.ruoyi.stock.pojo.StockInventory">
        <result column="id" property="id" />
        <result column="product_model_id" property="productModelId" />
        <result column="qualitity" property="qualitity" />
        <result column="create_time" property="createTime" />
        <result column="update_time" property="updateTime" />
        <result column="version" property="version" />
        <result column="locked_quantity" property="lockedQuantity" />
        <result column="warn_num" property="warnNum" />
    </resultMap>
</mapper>
src/main/resources/mapper/stock/StockOutRecordMapper.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.stock.mapper.StockOutRecordMapper">
    <!-- é€šç”¨æŸ¥è¯¢æ˜ å°„结果 -->
    <resultMap id="BaseResultMap" type="com.ruoyi.stock.pojo.StockOutRecord">
        <id column="id" property="id" />
        <result column="outbound_batches" property="outboundBatches" />
        <result column="stock_out_num" property="stockOutNum" />
        <result column="record_id" property="recordId" />
        <result column="record_type" property="recordType" />
        <result column="product_model_id" property="productModelId" />
        <result column="remark" property="remark" />
        <result column="create_time" property="createTime" />
        <result column="update_time" property="updateTime" />
        <result column="create_user" property="createUser" />
        <result column="update_user" property="updateUser" />
        <result column="tenant_id" property="tenantId" />
    </resultMap>
    <select id="listPage" resultType="com.ruoyi.stock.dto.StockOutRecordDto">
        SELECT
        sor.*
        pm.product_name as productName,
        pm.model,
        pm.unit
        FROM stock_out_record as sor
        LEFT JOIN product_models as pm on sor.product_model_id = pm.id
        <where>
            <if test="params.timeStr != null and params.timeStr != ''">
                and sor.create_time like concat('%',#{param.timeStr},'%')
            </if>
        </where>
        order by sor.id desc
    </select>
</mapper>
src/main/resources/mapper/system/SysDeptMapper.xml
@@ -20,32 +20,37 @@
        <result property="createTime" column="create_time" />
        <result property="updateBy"   column="update_by"   />
        <result property="updateTime" column="update_time" />
        <result property="staffCount" column="staff_count" />
    </resultMap>
    
    <sql id="selectDeptVo">
        select d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.phone, d.email, d.status, d.del_flag, d.create_by, d.create_time 
        from sys_dept d
    </sql>
    <select id="selectDeptList" parameterType="com.ruoyi.project.system.domain.SysDept" resultMap="SysDeptResult">
        <include refid="selectDeptVo"/>
        where d.del_flag = '0'
        select d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.phone, d.email, d.status, d.del_flag, d.create_by, d.create_time,
        count(distinct soj.id) as staff_count
        from sys_dept d
        left join staff_on_job soj on soj.sys_dept_id = d.dept_id and soj.staff_state = '1'
        where d.del_flag = '0'
        <if test="deptId != null and deptId != 0">
            AND dept_id = #{deptId}
            AND d.dept_id = #{deptId}
        </if>
        <if test="parentId != null and parentId != 0">
            AND parent_id = #{parentId}
        <if test="parentId != null and parentId != 0">
            AND d.parent_id = #{parentId}
        </if>
        <if test="deptName != null and deptName != ''">
            AND dept_name like concat('%', #{deptName}, '%')
            AND d.dept_name like concat('%', #{deptName}, '%')
        </if>
        <if test="status != null and status != ''">
            AND status = #{status}
            AND d.status = #{status}
        </if>
        <!-- æ•°æ®èŒƒå›´è¿‡æ»¤ -->
        ${params.dataScope}
        group by d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.phone, d.email, d.status, d.del_flag, d.create_by, d.create_time
        order by d.parent_id, d.order_num
    </select>
    </select>
    
    <select id="selectDeptListByRoleId" resultType="Long">
        select d.dept_id