gongchunyi
7 天以前 7828b982da116070fe11526733609ecce23631b6
src/main/java/com/ruoyi/quality/service/impl/QualityInspectServiceImpl.java
@@ -2,6 +2,7 @@
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -9,9 +10,13 @@
import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.config.Configure;
import com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum;
import com.ruoyi.common.enums.StockOutQualifiedRecordTypeEnum;
import com.ruoyi.common.utils.HackLoopTableRenderPolicy;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.approve.pojo.ApproveProcess;
import com.ruoyi.approve.service.IApproveProcessService;
import com.ruoyi.approve.vo.ApproveProcessVO;
import com.ruoyi.common.enums.ApproveTypeEnum;
import com.ruoyi.procurementrecord.service.ProcurementRecordService;
import com.ruoyi.procurementrecord.utils.StockUtils;
import com.ruoyi.quality.dto.QualityInspectDto;
@@ -23,7 +28,11 @@
import com.ruoyi.quality.pojo.QualityUnqualified;
import com.ruoyi.quality.service.IQualityInspectParamService;
import com.ruoyi.quality.service.IQualityInspectService;
import com.ruoyi.purchase.mapper.PurchaseLedgerMapper;
import com.ruoyi.purchase.pojo.PurchaseLedger;
import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
import com.ruoyi.sales.pojo.SalesLedgerProduct;
import com.ruoyi.framework.security.LoginUser;
import lombok.AllArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
@@ -32,9 +41,12 @@
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
@AllArgsConstructor
@@ -53,13 +65,19 @@
    private SalesLedgerProductMapper salesLedgerProductMapper;
    private PurchaseLedgerMapper purchaseLedgerMapper;
    private ProcurementRecordService procurementRecordService;
    private IApproveProcessService approveProcessService;
    @Override
    public int add(QualityInspectDto qualityInspectDto) {
        QualityInspect qualityInspect = new QualityInspect();
        BeanUtils.copyProperties(qualityInspectDto, qualityInspect);
        qualityInspect.setInspectState(0);//默认未提交
        ensureQualifiedSplitDefaults(qualityInspect);
        // 前端若已传合格/不合格数量,defaults 不会写 pass_rate,这里补算列表展示用合格率
        refreshPassRateFromQuantities(qualityInspect);
        qualityInspectMapper.insert(qualityInspect);
        for (QualityInspectParam qualityInspectParam : qualityInspectDto.getQualityInspectParams()) {
            qualityInspectParam.setInspectId(qualityInspect.getId());
@@ -82,26 +100,236 @@
    @Override
    public int submit(QualityInspect inspect) {
        QualityInspect qualityInspect = qualityInspectMapper.selectById(inspect.getId());
        //提交前必须判断是否合格
        if (ObjectUtils.isNull(qualityInspect.getCheckResult())) {
            throw new RuntimeException("请先判断是否合格");
        if (qualityInspect == null) {
            throw new RuntimeException("质检单不存在");
        }
        /*判断不合格*/
        if (qualityInspect.getCheckResult().equals("不合格")) {
        if (Objects.equals(qualityInspect.getInspectState(), 1)) {
            throw new RuntimeException("该质检单已提交,不能重复提交");
        }
        if (inspect != null) {
            if (inspect.getQualifiedQuantity() != null) {
                qualityInspect.setQualifiedQuantity(inspect.getQualifiedQuantity());
            }
            if (inspect.getUnqualifiedQuantity() != null) {
                qualityInspect.setUnqualifiedQuantity(inspect.getUnqualifiedQuantity());
            }
        }
        validateAndCalculateQuantities(qualityInspect);
        BigDecimal qualifiedQty = qualityInspect.getQualifiedQuantity();
        BigDecimal unqualifiedQty = qualityInspect.getUnqualifiedQuantity();
        if (unqualifiedQty.compareTo(BigDecimal.ZERO) > 0) {
            QualityUnqualified qualityUnqualified = new QualityUnqualified();
            BeanUtils.copyProperties(qualityInspect, qualityUnqualified);
            qualityUnqualified.setInspectState(0);//待处理
            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.setId(null);
            qualityUnqualified.setQuantity(unqualifiedQty);
            qualityUnqualified.setInspectState(0);
            qualityUnqualified.setDefectivePhenomena(buildDefectivePhenomena(qualityInspect));
            qualityUnqualified.setInspectId(qualityInspect.getId());
            qualityUnqualifiedMapper.insert(qualityUnqualified);
        } else {
            //合格直接入库
            stockUtils.addStock(qualityInspect.getProductModelId(), qualityInspect.getQuantity(), StockInQualifiedRecordTypeEnum.QUALITYINSPECT_STOCK_IN.getCode(), qualityInspect.getId());
        }
        qualityInspect.setInspectState(1);//已提交
        return qualityInspectMapper.updateById(qualityInspect);
        if (qualifiedQty.compareTo(BigDecimal.ZERO) > 0) {
            if (Objects.equals(qualityInspect.getInspectType(), 0)) {
                Long ledgerId = qualityInspect.getPurchaseLedgerId();
                PurchaseLedger purchaseLedger = ledgerId == null ? null : purchaseLedgerMapper.selectById(ledgerId);
                if (purchaseLedger != null) {
                    submitQualifiedInboundApprove(qualityInspect);
                } else {
                    // 手动新增的原材料检验:无采购台账,不走采购入库审批,直接入合格库存(与过程/出厂检验一致)
                    stockUtils.addStock(
                            null,
                            null,
                            qualityInspect.getProductModelId(),
                            qualifiedQty,
                            StockInQualifiedRecordTypeEnum.QUALITYINSPECT_STOCK_IN.getCode(),
                            qualityInspect.getId()
                    );
                    syncQualifiedInboundToPurchaseProducts(qualityInspect, qualifiedQty);
                }
            } else {
                stockUtils.addStock(
                        qualityInspect.getPurchaseLedgerId() == null ? null : qualityInspect.getPurchaseLedgerId(),
                        null,
                        qualityInspect.getProductModelId(),
                        qualifiedQty,
                        StockInQualifiedRecordTypeEnum.QUALITYINSPECT_STOCK_IN.getCode(),
                        qualityInspect.getId()
                );
                syncQualifiedInboundToPurchaseProducts(qualityInspect, qualifiedQty);
            }
        }
        qualityInspect.setCheckResult(resolveCheckResult(qualifiedQty, unqualifiedQty));
        qualityInspect.setInspectState(1);
        int updated = qualityInspectMapper.updateById(qualityInspect);
        refreshPurchaseLedgerStockStatusByInspect(qualityInspect.getPurchaseLedgerId());
        return updated;
    }
    private void validateAndCalculateQuantities(QualityInspect qualityInspect) {
        if (qualityInspect.getQualifiedQuantity() == null || qualityInspect.getUnqualifiedQuantity() == null) {
            throw new RuntimeException("请填写合格数量和不合格数量");
        }
        if (qualityInspect.getQualifiedQuantity().compareTo(BigDecimal.ZERO) < 0
                || qualityInspect.getUnqualifiedQuantity().compareTo(BigDecimal.ZERO) < 0) {
            throw new RuntimeException("合格数量和不合格数量不能为负数");
        }
        if (qualityInspect.getQuantity() == null) {
            throw new RuntimeException("质检单总数量异常");
        }
        BigDecimal total = qualityInspect.getQualifiedQuantity().add(qualityInspect.getUnqualifiedQuantity());
        BigDecimal qtyScaled = qualityInspect.getQuantity().setScale(4, RoundingMode.HALF_UP);
        BigDecimal sumScaled = total.setScale(4, RoundingMode.HALF_UP);
        if (sumScaled.compareTo(qtyScaled) > 0) {
            throw new RuntimeException("合格数量与不合格数量之和不能超过总数量");
        }
        qualityInspect.setPassRate(calculatePassRate(qualityInspect.getQualifiedQuantity(), qualityInspect.getQuantity()));
    }
    /**
     * 新增时若未拆分合格/不合格,默认全部为待检合格数
     */
    private void ensureQualifiedSplitDefaults(QualityInspect q) {
        if (q.getQuantity() == null) {
            return;
        }
        if (q.getQualifiedQuantity() == null && q.getUnqualifiedQuantity() == null) {
            q.setQualifiedQuantity(q.getQuantity());
            q.setUnqualifiedQuantity(BigDecimal.ZERO);
            q.setPassRate(calculatePassRate(q.getQualifiedQuantity(), q.getQuantity()));
            if (q.getCheckResult() == null || q.getCheckResult().isEmpty()) {
                q.setCheckResult("合格");
            }
        }
    }
    private BigDecimal calculatePassRate(BigDecimal qualifiedQty, BigDecimal totalQty) {
        if (totalQty == null || totalQty.compareTo(BigDecimal.ZERO) <= 0 || qualifiedQty == null) {
            return BigDecimal.ZERO;
        }
        return qualifiedQty.multiply(BigDecimal.valueOf(100))
                .divide(totalQty, 2, RoundingMode.HALF_UP);
    }
    private void refreshPassRateFromQuantities(QualityInspect q) {
        if (q.getQuantity() == null || q.getQualifiedQuantity() == null) {
            return;
        }
        q.setPassRate(calculatePassRate(q.getQualifiedQuantity(), q.getQuantity()));
    }
    private String resolveCheckResult(BigDecimal qualifiedQty, BigDecimal unqualifiedQty) {
        if (unqualifiedQty.compareTo(BigDecimal.ZERO) <= 0) {
            return "合格";
        }
        if (qualifiedQty.compareTo(BigDecimal.ZERO) <= 0) {
            return "不合格";
        }
        return "部分合格";
    }
    private String buildDefectivePhenomena(QualityInspect qualityInspect) {
        if (ObjectUtils.isNotEmpty(qualityInspect.getDefectivePhenomena())) {
            return qualityInspect.getDefectivePhenomena();
        }
        List<QualityInspectParam> inspectParams = qualityInspectParamService.list(
                Wrappers.<QualityInspectParam>lambdaQuery().eq(QualityInspectParam::getInspectId, qualityInspect.getId()));
        if (inspectParams.isEmpty()) {
            return "质检不合格数量:" + qualityInspect.getUnqualifiedQuantity();
        }
        String text = inspectParams.stream().map(QualityInspectParam::getParameterItem).collect(Collectors.joining(","));
        return text + "等指标检验不合格,不合格数量:" + qualityInspect.getUnqualifiedQuantity();
    }
    private void submitQualifiedInboundApprove(QualityInspect qualityInspect) {
        PurchaseLedger purchaseLedger = purchaseLedgerMapper.selectById(qualityInspect.getPurchaseLedgerId());
        if (purchaseLedger == null) {
            throw new RuntimeException("提交失败,采购台账不存在");
        }
        String approveUserIds = ObjectUtils.isNotEmpty(qualityInspect.getApproveUserIds())
                ? qualityInspect.getApproveUserIds()
                : purchaseLedger.getApproveUserIds();
        if (ObjectUtils.isEmpty(approveUserIds)) {
            throw new RuntimeException("提交失败,请先维护质检单审批人(或采购台账审批人)");
        }
        String approveReason = "原材料质检入库审批:" + purchaseLedger.getPurchaseContractNumber();
        String approveRemark = "qualityQualifiedInbound:" + qualityInspect.getId() + ":" + purchaseLedger.getId();
        ApproveProcess exist = approveProcessService.getOne(new LambdaQueryWrapper<ApproveProcess>()
                .eq(ApproveProcess::getApproveType, ApproveTypeEnum.STOCK_IN.getCode())
                .eq(ApproveProcess::getApproveRemark, approveRemark)
                .eq(ApproveProcess::getApproveDelete, 0)
                .orderByDesc(ApproveProcess::getCreateTime)
                .last("limit 1"));
        if (exist != null && !Objects.equals(exist.getApproveStatus(), 3)) {
            throw new RuntimeException("提交失败,该质检单已发起入库审批,审批完成前不能重复提交");
        }
        LoginUser loginUser = SecurityUtils.getLoginUser();
        ApproveProcessVO approveProcessVO = new ApproveProcessVO();
        approveProcessVO.setApproveType(ApproveTypeEnum.STOCK_IN.getCode());
        approveProcessVO.setApproveDeptId(loginUser.getCurrentDeptId());
        approveProcessVO.setApproveReason(approveReason);
        approveProcessVO.setApproveRemark(approveRemark);
        approveProcessVO.setApproveUserIds(approveUserIds);
        approveProcessVO.setApproveUser(loginUser.getUserId());
        approveProcessVO.setApproveTime(java.time.LocalDate.now().toString());
        try {
            approveProcessService.addApprove(approveProcessVO);
        } catch (Exception e) {
            throw new RuntimeException("提交失败,入库审批发起异常:" + e.getMessage());
        }
        qualityInspect.setApprovalStatus(2);
        qualityInspect.setApproveUserIds(approveUserIds);
        qualityInspectMapper.updateById(qualityInspect);
    }
    @Override
    public void executeQualifiedInboundApproval(Integer inspectId) {
        if (inspectId == null) {
            throw new RuntimeException("审批失败,质检单ID不能为空");
        }
        QualityInspect qualityInspect = qualityInspectMapper.selectById(inspectId);
        if (qualityInspect == null) {
            throw new RuntimeException("审批失败,质检单不存在");
        }
        if (!Objects.equals(qualityInspect.getInspectType(), 0)) {
            throw new RuntimeException("审批失败,仅原材料检验支持入库审批");
        }
        if (!Objects.equals(qualityInspect.getInspectState(), 1)) {
            throw new RuntimeException("审批失败,当前质检单状态不允许入库");
        }
        BigDecimal qualifiedQty = qualityInspect.getQualifiedQuantity();
        if (qualifiedQty == null || qualifiedQty.compareTo(BigDecimal.ZERO) <= 0) {
            throw new RuntimeException("审批失败,无合格数量可入库");
        }
        stockUtils.addStock(
                qualityInspect.getPurchaseLedgerId() == null ? null : qualityInspect.getPurchaseLedgerId().longValue(),
                null,
                qualityInspect.getProductModelId(),
                qualifiedQty,
                StockInQualifiedRecordTypeEnum.QUALITYINSPECT_STOCK_IN.getCode(),
                qualityInspect.getId()
        );
        syncQualifiedInboundToPurchaseProducts(qualityInspect, qualifiedQty);
        refreshPurchaseLedgerStockStatusByInspect(qualityInspect.getPurchaseLedgerId());
        qualityInspect.setApprovalStatus(3);
        qualityInspectMapper.updateById(qualityInspect);
    }
    @Override
    public void markQualifiedInboundApprovalStatus(Integer inspectId, Integer approvalStatus) {
        if (inspectId == null || approvalStatus == null) {
            return;
        }
        QualityInspect qualityInspect = qualityInspectMapper.selectById(inspectId);
        if (qualityInspect == null) {
            return;
        }
        qualityInspect.setApprovalStatus(approvalStatus);
        qualityInspectMapper.updateById(qualityInspect);
    }
    /*生成检验报告*/
@@ -158,6 +386,13 @@
    @Override
    public int updateQualityInspect(QualityInspectDto qualityInspectDto) {
        QualityInspect existing = qualityInspectMapper.selectById(qualityInspectDto.getId());
        if (existing == null) {
            throw new RuntimeException("质检单不存在");
        }
        if (Objects.equals(existing.getInspectState(), 1)) {
            throw new RuntimeException("已提交的数据不允许修改");
        }
        if (ObjectUtils.isNotNull(qualityInspectDto.getQualityInspectParams())) {
            qualityInspectParamService.remove(Wrappers.<QualityInspectParam>lambdaQuery().eq(QualityInspectParam::getInspectId, qualityInspectDto.getId()));
            for (QualityInspectParam qualityInspectParam : qualityInspectDto.getQualityInspectParams()) {
@@ -167,11 +402,29 @@
        }
        QualityInspect qualityInspect = new QualityInspect();
        BeanUtils.copyProperties(qualityInspectDto, qualityInspect);
        qualityInspect.setQuantity(existing.getQuantity());
        BigDecimal qf = qualityInspect.getQualifiedQuantity() != null ? qualityInspect.getQualifiedQuantity() : existing.getQualifiedQuantity();
        BigDecimal uqf = qualityInspect.getUnqualifiedQuantity() != null ? qualityInspect.getUnqualifiedQuantity() : existing.getUnqualifiedQuantity();
        if (qf == null || uqf == null) {
            BigDecimal qty = existing.getQuantity() != null ? existing.getQuantity() : BigDecimal.ZERO;
            if ("不合格".equals(existing.getCheckResult())) {
                qf = BigDecimal.ZERO;
                uqf = qty;
            } else {
                qf = qty;
                uqf = BigDecimal.ZERO;
            }
        }
        qualityInspect.setQualifiedQuantity(qf);
        qualityInspect.setUnqualifiedQuantity(uqf);
        validateAndCalculateQuantities(qualityInspect);
        qualityInspect.setCheckResult(resolveCheckResult(qf, uqf));
        return qualityInspectMapper.updateById(qualityInspect);
    }
    @Override
    public IPage<QualityInspect> qualityInspectListPage(Page page, QualityInspect qualityInspect) {
    public IPage<QualityInspect> qualityInspectListPage(Page<?> page, QualityInspect qualityInspect) {
        return qualityInspectMapper.qualityInspectListPage(page, qualityInspect);
    }
@@ -193,5 +446,113 @@
    }
    private void refreshPurchaseLedgerStockStatusByInspect(Long purchaseLedgerId) {
        if (purchaseLedgerId == null) {
            return;
        }
        List<SalesLedgerProduct> products = salesLedgerProductMapper.selectList(new LambdaQueryWrapper<SalesLedgerProduct>()
                .eq(SalesLedgerProduct::getSalesLedgerId, purchaseLedgerId)
                .eq(SalesLedgerProduct::getType, 2));
        if (products == null || products.isEmpty()) {
            return;
        }
        boolean allInbound = true;
        boolean anyInbound = false;
        for (SalesLedgerProduct product : products) {
            BigDecimal orderQty = product.getQuantity() == null ? BigDecimal.ZERO : product.getQuantity();
            BigDecimal totalInboundQty = product.getStockedQuantity() == null ? BigDecimal.ZERO : product.getStockedQuantity();
            if (totalInboundQty.compareTo(BigDecimal.ZERO) > 0) {
                anyInbound = true;
            }
            if (totalInboundQty.compareTo(orderQty) < 0) {
                allInbound = false;
            }
        }
        PurchaseLedger purchaseLedger = purchaseLedgerMapper.selectById(purchaseLedgerId);
        if (purchaseLedger == null) {
            return;
        }
        int targetStockStatus = allInbound ? 2 : (anyInbound ? 1 : 0);
        if (!Objects.equals(purchaseLedger.getStockStatus(), targetStockStatus)) {
            purchaseLedger.setStockStatus(targetStockStatus);
            purchaseLedgerMapper.updateById(purchaseLedger);
        }
    }
    private void syncQualifiedInboundToPurchaseProducts(QualityInspect qualityInspect, BigDecimal inboundQty) {
        if (qualityInspect == null) {
            return;
        }
        if (!Objects.equals(qualityInspect.getInspectType(), 0) || qualityInspect.getPurchaseLedgerId() == null) {
            return;
        }
        if (qualityInspect.getProductModelId() == null || inboundQty == null) {
            return;
        }
        if (inboundQty.compareTo(BigDecimal.ZERO) <= 0) {
            return;
        }
        List<SalesLedgerProduct> lines = salesLedgerProductMapper.selectList(new LambdaQueryWrapper<SalesLedgerProduct>()
                .eq(SalesLedgerProduct::getSalesLedgerId, qualityInspect.getPurchaseLedgerId())
                .eq(SalesLedgerProduct::getType, 2)
                .eq(SalesLedgerProduct::getProductModelId, qualityInspect.getProductModelId())
                .eq(SalesLedgerProduct::getIsChecked, true)
                .orderByAsc(SalesLedgerProduct::getId));
        if (lines == null || lines.isEmpty()) {
            return;
        }
        BigDecimal remaining = inboundQty;
        SalesLedgerProduct fallbackLine = null;
        for (SalesLedgerProduct line : lines) {
            if (remaining.compareTo(BigDecimal.ZERO) <= 0) {
                break;
            }
            BigDecimal orderQty = line.getQuantity() == null ? BigDecimal.ZERO : line.getQuantity();
            BigDecimal stocked = line.getStockedQuantity() == null ? BigDecimal.ZERO : line.getStockedQuantity();
            BigDecimal canFill = orderQty.subtract(stocked);
            if (canFill.compareTo(BigDecimal.ZERO) <= 0) {
                fallbackLine = line;
                continue;
            }
            BigDecimal add = canFill.min(remaining);
            BigDecimal newStocked = stocked.add(add);
            int status;
            if (newStocked.compareTo(BigDecimal.ZERO) <= 0) {
                status = 0;
            } else if (orderQty.compareTo(BigDecimal.ZERO) > 0 && newStocked.compareTo(orderQty) < 0) {
                status = 1;
            } else {
                status = 2;
            }
            line.setStockedQuantity(newStocked);
            line.setProductStockStatus(status);
            line.fillRemainingQuantity();
            salesLedgerProductMapper.updateById(line);
            remaining = remaining.subtract(add);
            fallbackLine = line;
        }
        // 允许多入库:若仍有剩余,累计到最后一行,确保 remaining_shipped_quantity 能同步增长
        if (remaining.compareTo(BigDecimal.ZERO) > 0 && fallbackLine != null) {
            BigDecimal orderQty = fallbackLine.getQuantity() == null ? BigDecimal.ZERO : fallbackLine.getQuantity();
            BigDecimal stocked = fallbackLine.getStockedQuantity() == null ? BigDecimal.ZERO : fallbackLine.getStockedQuantity();
            BigDecimal newStocked = stocked.add(remaining);
            int status;
            if (newStocked.compareTo(BigDecimal.ZERO) <= 0) {
                status = 0;
            } else if (orderQty.compareTo(BigDecimal.ZERO) > 0 && newStocked.compareTo(orderQty) < 0) {
                status = 1;
            } else {
                status = 2;
            }
            fallbackLine.setStockedQuantity(newStocked);
            fallbackLine.setProductStockStatus(status);
            fallbackLine.fillRemainingQuantity();
            salesLedgerProductMapper.updateById(fallbackLine);
        }
    }
}