2 天以前 f350465db70d6f9db9494483ee0cec7793a9007b
src/main/java/com/ruoyi/production/service/impl/ProductionProductMainServiceImpl.java
@@ -1,5 +1,7 @@
package com.ruoyi.production.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
@@ -11,10 +13,11 @@
import com.ruoyi.basic.mapper.ProductModelMapper;
import com.ruoyi.basic.pojo.Product;
import com.ruoyi.basic.pojo.ProductModel;
import com.ruoyi.common.enums.AuditEnum;
import com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum;
import com.ruoyi.common.enums.StockOutQualifiedRecordTypeEnum;
import com.ruoyi.common.enums.StockInUnQualifiedRecordTypeEnum;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.procurementrecord.utils.StockUtils;
import com.ruoyi.production.dto.ProductStructureDto;
@@ -22,29 +25,35 @@
import com.ruoyi.production.mapper.*;
import com.ruoyi.production.pojo.*;
import com.ruoyi.production.service.ProductionProductMainService;
import com.ruoyi.production.vo.ProductAuditVo;
import com.ruoyi.project.system.domain.SysUser;
import com.ruoyi.project.system.mapper.SysUserMapper;
import com.ruoyi.quality.mapper.*;
import com.ruoyi.quality.pojo.*;
import com.ruoyi.quality.service.IQualityInspectService;
import com.ruoyi.sales.mapper.SalesLedgerMapper;
import lombok.AllArgsConstructor;
import org.springframework.aop.framework.AopContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.ruoyi.production.mapper.ProductionProductMainMapper;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.stream.Collectors;
import static cn.hutool.core.date.LocalDateTimeUtil.between;
@Service
@AllArgsConstructor
@Transactional(rollbackFor = Exception.class)
public class ProductionProductMainServiceImpl extends ServiceImpl<ProductionProductMainMapper, ProductionProductMain> implements ProductionProductMainService {
    private final SalesLedgerMapper salesLedgerMapper;
    private final ProductionMachineRecordMapper productionMachineRecordMapper;
    private IQualityInspectService qualityInspectService;
    private ProductionProductMainMapper productionProductMainMapper;
@@ -60,6 +69,7 @@
    private ProductModelMapper productModelMapper;
    private QualityInspectMapper qualityInspectMapper;
    private QualityUnqualifiedMapper qualityUnqualifiedMapper;
    private ProductProcessMapper productProcessMapper;
    private ProductProcessRouteMapper productProcessRouteMapper;
@@ -80,6 +90,8 @@
    private SalesLedgerProductionAccountingMapper salesLedgerProductionAccountingMapper;
    private ProductionReportPersonMapper productionReportPersonMapper;
    private StockUtils stockUtils;
@@ -89,8 +101,11 @@
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean addProductMain(ProductionProductMainDto dto) {
        SysUser user = userMapper.selectUserById(dto.getUserId());
        LocalDateTime now = LocalDateTime.now();
        ProductWorkOrder productWorkOrder = productWorkOrderMapper.selectById(dto.getWorkOrderId());
        ProductionProductMain productionProductMain = new ProductionProductMain();
        //当前工艺路线对应的工序详情
        ProductProcessRouteItem productProcessRouteItem = productProcessRouteItemMapper.selectById(dto.getProductProcessRouteItemId());
@@ -102,7 +117,7 @@
        //工艺路线中当前工序对应的产出规格型号
        ProductModel productModel = productModelMapper.selectById(productProcessRouteItem.getProductModelId());
        //查询该生产订单对应的bom
        ProductProcessRoute productProcessRoute = productProcessRouteMapper.selectById(productProcessRouteItem.getProductRouteId());
        ProductProcessRoute productProcessRoute = Optional.ofNullable(productProcessRouteMapper.selectById(productProcessRouteItem.getProductRouteId())).orElse(new ProductProcessRoute());
        /*新增报工主表*/
        //查询最大报工编号
        String datePrefix = "BG" + LocalDate.now().format(DateTimeFormatter.ofPattern("yyMMdd"));
@@ -131,15 +146,61 @@
        }
        String productNo = String.format("%s%03d", datePrefix, sequenceNumber);
        productionProductMain.setProductNo(productNo);
        productionProductMain.setUserId(dto.getUserId());
        productionProductMain.setUserName(dto.getUserName());
        // 修改班组信息 以第一人作为原先setUserId setUserName
        Assert.isTrue(CollUtil.isNotEmpty(dto.getTeamList()), "班组信息不能为空");
        productionProductMain.setTeamIds(dto.getTeamList().stream().map(ProductionProductMainDto.Team::getUserId).map(String::valueOf).collect(Collectors.joining(",")));
        productionProductMain.setTeamNames(dto.getTeamList().stream().map(ProductionProductMainDto.Team::getUserName).collect(Collectors.joining(",")));
        //报工人 是 谁报工就是是谁
        productionProductMain.setUserId(SecurityUtils.getUserId());
        productionProductMain.setUserName(SecurityUtils.getLoginUser().getNickName());
        productionProductMain.setProductProcessRouteItemId(dto.getProductProcessRouteItemId());
        productionProductMain.setWorkOrderId(dto.getWorkOrderId());
//        productionProductMain.setAuditUserId(dto.getAuditUserId());
//        productionProductMain.setAuditUserName(dto.getAuditUserName());
        productionProductMain.setStatus(0);
        if (ObjectUtils.isNotEmpty(dto.getStartTime()) && ObjectUtils.isNotEmpty(dto.getEndTime())) {
            productionProductMain.setStartTime(dto.getStartTime());
            productionProductMain.setEndTime(dto.getEndTime());
        } else {
            productionProductMain.setStartTime(productWorkOrder.getStartProductTime());
            productionProductMain.setEndTime(now);
        }
        productionProductMain.setDeviceId(dto.getDeviceId());
        productionProductMain.setDeviceName(dto.getDeviceName());
        // 审批人为自己 直接通过审批
        productionProductMain.setAuditUserName(SecurityUtils.getUsername());
        productionProductMain.setAuditUserId(SecurityUtils.getUserId());
        productionProductMainMapper.insert(productionProductMain);
        /*保存报工班组人员产量时长明细*/
        List<ProductionProductMainDto.Person> personList = dto.getPersonList();
        BigDecimal personSum = BigDecimal.ZERO;
        if (CollUtil.isNotEmpty(personList)) {
            for (ProductionProductMainDto.Person person : personList) {
                BigDecimal personQty = person.getQuantity() == null ? BigDecimal.ZERO : person.getQuantity();
                personSum = personSum.add(personQty);
                ProductionReportPerson reportPerson = new ProductionReportPerson();
                reportPerson.setProductMainId(productionProductMain.getId());
                reportPerson.setUserId(person.getUserId());
                reportPerson.setUserName(person.getUserName());
                reportPerson.setQuantity(personQty);
                reportPerson.setWorkHour(person.getWorkHour() != null ? person.getWorkHour() : 0);
                productionReportPersonMapper.insert(reportPerson);
            }
        }
        BigDecimal totalQuantity = dto.getQuantity() == null ? BigDecimal.ZERO : dto.getQuantity();
        if (CollUtil.isNotEmpty(personList) && personSum.compareTo(totalQuantity) != 0) {
            throw new ServiceException("各班组产量之和必须等于报工总产量");
        }
        /*新增报工投入表*/
        List<ProductStructureDto> productStructureDtos = productStructureMapper.listBybomAndProcess(productProcessRoute.getBomId(), productProcess.getId());
        if (productStructureDtos.size() == 0) {
        List<ProductStructureDto> productStructureDtos = productStructureMapper.listBybomAndProcess(Optional.ofNullable(productProcessRoute.getBomId()).orElse(0), productProcess.getId());
        if (productStructureDtos.isEmpty()) {
            //如果该工序没有产品结构的投入品,那这个投入品和产出品是同一个
            ProductStructureDto productStructureDto = new ProductStructureDto();
            productStructureDto.setProductModelId(productProcessRouteItem.getProductModelId());
@@ -147,14 +208,12 @@
            productStructureDtos.add(productStructureDto);
        }
        for (ProductStructureDto productStructureDto : productStructureDtos) {
            ProductionProductInput productionProductInput = new ProductionProductInput();
            productionProductInput.setProductModelId(productStructureDto.getProductModelId());
            productionProductInput.setQuantity(productStructureDto.getUnitQuantity().multiply(dto.getQuantity()));
            productionProductInput.setProductMainId(productionProductMain.getId());
            productionProductInputMapper.insert(productionProductInput);
            stockUtils.substractStock(productStructureDto.getProductModelId(), productionProductInput.getQuantity(), StockOutQualifiedRecordTypeEnum.PRODUCTION_REPORT_STOCK_OUT.getCode(), productionProductMain.getId());
//            stockUtils.substractStock(productStructureDto.getProductModelId(), productionProductInput.getQuantity(), StockOutQualifiedRecordTypeEnum.PRODUCTION_REPORT_STOCK_OUT.getCode(), productionProductMain.getId());
        }
        /*新增报工产出表*/
        ProductionProductOutput productionProductOutput = new ProductionProductOutput();
@@ -162,9 +221,155 @@
        productionProductOutput.setProductModelId(productProcessRouteItem.getProductModelId());
        productionProductOutput.setQuantity(dto.getQuantity() != null ? dto.getQuantity() : BigDecimal.ZERO);
        productionProductOutput.setScrapQty(dto.getScrapQty() != null ? dto.getScrapQty() : BigDecimal.ZERO);
        productionProductOutput.setReplenishQty(dto.getReplenishQty());
        productionProductOutput.setAddQty(dto.getAddQty() != null ? dto.getAddQty() : BigDecimal.ZERO);
        productionProductOutputMapper.insert(productionProductOutput);
        //合格数量=报工数量-报废数量
        BigDecimal productQty = productionProductOutput.getQuantity().subtract(productionProductOutput.getScrapQty());
        //工序流转数量=产出数量+加放数(报废仅做数据记录,不扣减工序流转基数,各工序总产量保持连贯)
        BigDecimal addQty = productionProductOutput.getAddQty() == null ? BigDecimal.ZERO : productionProductOutput.getAddQty();
        BigDecimal productQty = productionProductOutput.getQuantity().add(addQty);
        //只有工序流转数量>0才能增加相应数据
        if (productQty.compareTo(BigDecimal.ZERO) > 0) {
            List<ProductProcessRouteItem> productProcessRouteItems = productProcessRouteItemMapper.selectList(Wrappers.<ProductProcessRouteItem>lambdaQuery().eq(ProductProcessRouteItem::getProductRouteId, productProcessRouteItem.getProductRouteId()));
            /*更新工单和生产订单*/
            productWorkOrder.setCompleteQuantity(productWorkOrder.getCompleteQuantity().add(productQty));
            // 查询该工单对应的最早上机时间(按上机时间升序,取第一条)
            List<ProductionMachineRecord> machineRecords = productionMachineRecordMapper.selectList(
                    Wrappers.<ProductionMachineRecord>lambdaQuery()
                            .eq(ProductionMachineRecord::getWorkOrderId, productWorkOrder.getId())
                            .isNotNull(ProductionMachineRecord::getMachineStartTime)
                            .orderByAsc(ProductionMachineRecord::getMachineStartTime)
            );
            if (!machineRecords.isEmpty()) {
                productWorkOrder.setActualStartTime(machineRecords.get(0).getMachineStartTime());
            }
            // 仅完成数量达到计划数量时才填充实际结束时间,未完成则清空已填的结束时间(允许继续分批报工)
            if (productWorkOrder.getCompleteQuantity().compareTo(productWorkOrder.getPlanQuantity()) >= 0) {
                productWorkOrder.setActualEndTime(now);
                productWorkOrder.setEndProductTime(now);
                productWorkOrderMapper.updateById(productWorkOrder);
            } else {
                // updateById 默认忽略 null 字段,需用 UpdateWrapper 显式清空(自愈历史脏数据)
                productWorkOrderMapper.update(null, Wrappers.<ProductWorkOrder>lambdaUpdate()
                        .eq(ProductWorkOrder::getId, productWorkOrder.getId())
                        .set(ProductWorkOrder::getActualEndTime, null)
                        .set(ProductWorkOrder::getEndProductTime, null));
            }
            //生产订单
            ProductOrder productOrder = productOrderMapper.selectById(productWorkOrder.getProductOrderId());
            if (ObjectUtils.isNull(productOrder.getStartTime())) {
                productOrder.setStartTime(now);//开始时间
            }
            if (productProcessRouteItem.getDragSort() >= productProcessRouteItems.size() -1 ) {
                //如果是最后一道工序报工之后生产订单完成数量+
                productOrder.setCompleteQuantity(productOrder.getCompleteQuantity().add(productQty));
                if (productOrder.getCompleteQuantity().compareTo(productOrder.getQuantity()) >= 0) {
                    productOrder.setEndTime(now);//结束时间
                }
            }
            productOrderMapper.updateById(productOrder);
        }
        ProductAuditVo productAuditVo = new ProductAuditVo();
        productAuditVo.setId(productionProductMain.getId());
        productAuditVo.setAuditStatus(AuditEnum.AUDIT_SUCCESS);
        productAuditVo.setAuditOpinion("");
        ((ProductionProductMainService) AopContext.currentProxy()).auditProductMain(productAuditVo);
//        nextAddProductMain(productionProductOutput) // 由于需要审核,所以需要拆封下来
        return true;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void auditProductMain(ProductAuditVo productAuditVo) {
        ProductionProductMain productionProductMain = productionProductMainMapper.selectById(productAuditVo.getId());
        // 当前审批人 要与当前登录人为同一人
        if (productionProductMain.getAuditUserId() != -1) {
            Assert.isTrue(SecurityUtils.getUserId().equals(productionProductMain.getAuditUserId()), "当前登录用户不是当前审批人");
        }
        // 状态必须为待审核状态
        if (!Objects.equals(productionProductMain.getAuditStatus(), AuditEnum.NO_AUDIT.getCode())) {
            throw new ServiceException("当前状态已审核,不能重复审核");
        }
        LambdaQueryWrapper<ProductionProductOutput> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ProductionProductOutput::getProductMainId, productionProductMain.getId());
        queryWrapper.last("limit 1");
        ProductionProductOutput productionProductOutput = productionProductOutputMapper.selectOne(queryWrapper);
        ProductProcessRouteItem productProcessRouteItem = productProcessRouteItemMapper.selectById(productionProductMain.getProductProcessRouteItemId());
        switch (productAuditVo.getAuditStatus()) {
            case NO_AUDIT:
                throw new ServiceException("修改审核状态失败,不能为未审核状态");
            case AUDIT_SUCCESS:
                Assert.isTrue(productionProductOutput != null, "没有找到对应的报工产出记录");
                ((ProductionProductMainService) AopContext.currentProxy()).nextAddProductMain(productionProductOutput);
                break;
            case AUDIT_FAIL:
                // 减少 更新工单和生产订单
                ProductWorkOrder productWorkOrder = productWorkOrderMapper.selectById(productionProductMain.getWorkOrderId());
                ProductOrder productOrder = productOrderMapper.selectById(productWorkOrder.getProductOrderId());
                //工序流转数量=产出数量+加放数(与报工累加口径一致,报废不扣减流转基数)
                BigDecimal processQty = productionProductOutput.getQuantity().add(
                        productionProductOutput.getAddQty() == null ? BigDecimal.ZERO : productionProductOutput.getAddQty());
                productWorkOrder.setCompleteQuantity(
                        productWorkOrder.getCompleteQuantity()
                                .subtract(processQty)
                                .max(BigDecimal.ZERO)
                );
                List<ProductProcessRouteItem> productProcessRouteItems = productProcessRouteItemMapper.selectList(Wrappers.<ProductProcessRouteItem>lambdaQuery().eq(ProductProcessRouteItem::getProductRouteId, productProcessRouteItem.getProductRouteId()));
                if (productProcessRouteItem.getDragSort() == productProcessRouteItems.size()) {
                    //如果是最后一道工序报工之后生产订单 需要扣除
                    productOrder.setCompleteQuantity(
                            productOrder.getCompleteQuantity()
                                    .subtract(processQty)
                                    .max(BigDecimal.ZERO)
                    );
                    if (productOrder.getCompleteQuantity().compareTo(processQty) > 0) {
                        productOrder.setEndTime(null);
                    }
                    productOrderMapper.updateById(productOrder);
                }
                if (productWorkOrder.getCompleteQuantity().compareTo(processQty) > 0) {
                    productWorkOrder.setActualEndTime(null);
                }
                productWorkOrderMapper.updateById(productWorkOrder);
                break;
        }
        // 修改状态
        ProductionProductMain updateDate = new ProductionProductMain();
        updateDate.setId(productionProductMain.getId());
        updateDate.setAuditStatus(productAuditVo.getAuditStatus().getCode());
        updateDate.setSureAuditUserId(SecurityUtils.getUserId());
        updateDate.setSureAuditUserName(SecurityUtils.getUsername());
        updateDate.setAuditTime(LocalDateTime.now());
        updateDate.setAuditOpinion(productAuditVo.getAuditOpinion());
        productionProductMainMapper.updateById(updateDate);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void nextAddProductMain(@NotNull ProductionProductOutput productionProductOutput) {
        //实际入库数量=产出数量+加放数-报废数量
        ProductionProductMain productionProductMain = productionProductMainMapper.selectById(productionProductOutput.getProductMainId());
        BigDecimal addQty = productionProductOutput.getAddQty() == null ? BigDecimal.ZERO : productionProductOutput.getAddQty();
        BigDecimal productQty = productionProductOutput.getQuantity().add(addQty).subtract(productionProductOutput.getScrapQty());
        ProductProcessRouteItem productProcessRouteItem = productProcessRouteItemMapper.selectById(productionProductMain.getProductProcessRouteItemId());
        ProductProcess productProcess = productProcessMapper.selectById(productProcessRouteItem.getProcessId());
        //工艺路线中当前工序对应的产出规格型号
        ProductModel productModel = productModelMapper.selectById(productProcessRouteItem.getProductModelId());
        SysUser user = userMapper.selectUserById(SecurityUtils.getUserId());
        //只有合格数量>0才能增加相应数据
        if (productQty.compareTo(BigDecimal.ZERO) > 0) {
            /*新增质检*/
@@ -173,90 +378,235 @@
                //对应的过程检或者出厂检
                int inspectType = 1;
                String process = productProcess.getName();//工序
                if (productProcessRouteItem.getDragSort() == productProcessRouteItems.size()) {
                if (productProcessRouteItem.getDragSort() == (productProcessRouteItems.size()-1)) {
                    //最后一道工序生成出厂检
                    inspectType = 2;
                    process = null;
                }
                Product product = productMapper.selectById(productModel.getProductId());
                QualityInspect qualityInspect = new QualityInspect();
                qualityInspect.setProductId(product.getId());
                qualityInspect.setProductName(product.getProductName());
                qualityInspect.setModel(productModel.getModel());
                qualityInspect.setUnit(productModel.getUnit());
                qualityInspect.setQuantity(productQty);
                qualityInspect.setProcess(process);
                qualityInspect.setInspectState(0);
                qualityInspect.setInspectType(inspectType);
                qualityInspect.setProductMainId(productionProductMain.getId());
                qualityInspect.setProductModelId(productModel.getId());
                qualityInspectMapper.insert(qualityInspect);
                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, qualityTestStandard.get(0).getId()))//默认获取最新的
                            .forEach(qualityTestStandardParam -> {
                                QualityInspectParam param = new QualityInspectParam();
                                BeanUtils.copyProperties(qualityTestStandardParam, param);
                                param.setId(null);
                                param.setInspectId(qualityInspect.getId());
                                qualityInspectParamMapper.insert(param);
                            });
                // 排产下达时已为该工单预生成质检单(product_main_id IS NULL),报工审核时认领并更新为实际数量
                QualityInspect preInspect = qualityInspectMapper.selectOne(
                        Wrappers.<QualityInspect>lambdaQuery()
                                .eq(QualityInspect::getWorkOrderId, productionProductMain.getWorkOrderId())
                                .isNull(QualityInspect::getProductMainId)
                                .last("limit 1"));
                if (preInspect != null) {
                    preInspect.setProductMainId(productionProductMain.getId());
                    preInspect.setQuantity(productQty);
                    if (preInspect.getInspectState() != null && preInspect.getInspectState() == 1
                            && preInspect.getInspectType() != null && preInspect.getInspectType() == 2) {
                        // 已提交且为出厂检:删除按计划数量入库的记录,按实际数量重新入库
                        stockUtils.deleteStockInRecord(preInspect.getId(), StockInQualifiedRecordTypeEnum.QUALITYINSPECT_STOCK_IN.getCode());
                        stockUtils.addStock(productModel.getId(), productQty, StockInQualifiedRecordTypeEnum.QUALITYINSPECT_STOCK_IN.getCode(), preInspect.getId(), "-", "-", "-");
                    }
                    qualityInspectMapper.updateById(preInspect);
                } else {
                    insertQualityInspect(productProcessRouteItem, inspectType, process, productionProductMain.getId(), productionProductMain.getWorkOrderId(), productQty);
                }
            }else {
            } else {
                //直接入库
                stockUtils.addStock(productProcessRouteItem.getProductModelId(), productionProductOutput.getQuantity(), StockInQualifiedRecordTypeEnum.PRODUCTION_REPORT_STOCK_IN.getCode(), productionProductMain.getId());
            }
            /*更新工单和生产订单*/
            ProductWorkOrder productWorkOrder = productWorkOrderMapper.selectById(dto.getWorkOrderId());
            productWorkOrder.setCompleteQuantity(productWorkOrder.getCompleteQuantity().add(productQty));
            if (ObjectUtils.isNull(productWorkOrder.getActualStartTime())) {
                productWorkOrder.setActualStartTime(LocalDate.now());//实际开始时间
            }
            if (productWorkOrder.getCompleteQuantity().compareTo(productWorkOrder.getPlanQuantity()) == 0) {
                productWorkOrder.setActualEndTime(LocalDate.now());//实际结束时间
            }
            productWorkOrderMapper.updateById(productWorkOrder);
            //生产订单
            ProductOrder productOrder = productOrderMapper.selectById(productWorkOrder.getProductOrderId());
            if (ObjectUtils.isNull(productOrder.getStartTime())) {
                productOrder.setStartTime(LocalDateTime.now());//开始时间
            }
            if (productProcessRouteItem.getDragSort() == productProcessRouteItems.size()) {
                //如果是最后一道工序报工之后生产订单完成数量+
                productOrder.setCompleteQuantity(productOrder.getCompleteQuantity().add(productQty));
                if (productOrder.getCompleteQuantity().compareTo(productOrder.getQuantity()) == 0) {
                    productOrder.setEndTime(LocalDateTime.now());//结束时间
                if (productProcessRouteItem.getDragSort() == (productProcessRouteItems.size()-1)) {
                    //最后一道工序才会入库
                    stockUtils.addStock(productProcessRouteItem.getProductModelId(), productQty, StockInQualifiedRecordTypeEnum.PRODUCTION_REPORT_STOCK_IN.getCode(), productionProductMain.getId(), "-", "-", "-");
                }
            }
            productOrderMapper.updateById(productOrder);
            /*添加生产核算*/
            SalesLedgerProductionAccounting salesLedgerProductionAccounting = SalesLedgerProductionAccounting.builder()
                    .productMainId(productionProductMain.getId())
                    .schedulingUserId(user.getUserId())
                    .schedulingUserName(user.getNickName())
                    .finishedNum(productQty)
                    .workHours(productProcess.getSalaryQuota())
                    .process(productProcess.getName())
                    .schedulingDate(LocalDate.now())
                    .tenantId(dto.getTenantId())
                    .build();
            salesLedgerProductionAccountingMapper.insert(salesLedgerProductionAccounting);
        }
        //如果报废数量>0,需要进入报废的库存
        if (ObjectUtils.isNotEmpty(dto.getScrapQty())) {
            if (dto.getScrapQty().compareTo(BigDecimal.ZERO) > 0) {
                stockUtils.addUnStock(productModel.getId(), dto.getScrapQty(), StockInUnQualifiedRecordTypeEnum.PRODUCTION_SCRAP.getCode(), productionProductMain.getId());
            /*添加生产核算        区分工序是计件还是计时*/
            BigDecimal workHours = productProcess.getSalaryQuota();
            List<ProductionMachineRecord> productionMachineRecords = productionMachineRecordMapper.selectList(Wrappers.<ProductionMachineRecord>lambdaQuery().eq(ProductionMachineRecord::getWorkOrderId, productionProductMain.getWorkOrderId()));
            // 报工班组人员产量时长明细(有明细则按人生成核算,无明细则回退机台操作员逻辑)
            List<ProductionReportPerson> personList = productionReportPersonMapper.selectList(Wrappers.<ProductionReportPerson>lambdaQuery().eq(ProductionReportPerson::getProductMainId, productionProductMain.getId()));
            // 设备取报工单设备,未填写时回退取该工单任一台机
            Long reportDeviceId = productionProductMain.getDeviceId() != null
                    ? productionProductMain.getDeviceId()
                    : productionMachineRecords.stream().map(ProductionMachineRecord::getMachineId).filter(Objects::nonNull).findFirst().orElse(null);
            if (CollUtil.isNotEmpty(personList)) {
                // 每次报工独立按人生成核算(支持分批报工直至完成),不受上机记录报工状态影响
                BigDecimal netQty = productQty;
                BigDecimal rawQuantity = productionProductOutput.getQuantity() == null ? BigDecimal.ZERO : productionProductOutput.getQuantity();
                for (ProductionReportPerson person : personList) {
                    BigDecimal personQty = person.getQuantity() == null ? BigDecimal.ZERO : person.getQuantity();
                    BigDecimal finishedNum;
                    if (netQty.compareTo(rawQuantity) == 0 || rawQuantity.compareTo(BigDecimal.ZERO) == 0) {
                        // 无报废(或毛产量为0)时净值即毛产量,直接取个人产量
                        finishedNum = personQty;
                    } else {
                        // 有报废时按各人毛产量占比分摊净值,四舍五入保留2位
                        finishedNum = personQty.multiply(netQty).divide(rawQuantity, 2, BigDecimal.ROUND_HALF_UP);
                    }
                    SalesLedgerProductionAccounting salesLedgerProductionAccounting = SalesLedgerProductionAccounting.builder()
                            .productMainId(productionProductMain.getId())
                            .schedulingUserId(person.getUserId())
                            .schedulingUserName(person.getUserName())
                            .finishedNum(finishedNum)
                            .workHours(workHours)
                            .process(productProcess.getName())
                            .schedulingDate(LocalDate.now())
                            .tenantId(productionProductOutput.getTenantId())
                            .deviceId(reportDeviceId)
                            .workHour(person.getWorkHour() == null ? 0L : person.getWorkHour().longValue())
                            .build();
                    salesLedgerProductionAccountingMapper.insert(salesLedgerProductionAccounting);
                }
                // 该工单上机记录本次已报工
                for (ProductionMachineRecord productionMachineRecord : productionMachineRecords) {
                    if (!productionMachineRecord.getReportStatus()) {
                        productionMachineRecord.setReportStatus(true);
                        productionMachineRecordMapper.updateById(productionMachineRecord);
                    }
                }
            } else if (ObjectUtils.isNotEmpty(productionMachineRecords)) {
                // 兼容旧数据:未传人员明细则回退按未报工的机台上机操作员生成核算
                for (ProductionMachineRecord productionMachineRecord : productionMachineRecords) {
                    //说明已经添加过了,不添加了
                    if (productionMachineRecord.getReportStatus()) {
                        continue;
                    }
                    for (String s : productionMachineRecord.getOperatorId().split(",")) {
                        Long minutes = 0L;
                        if (productionMachineRecord.getMachineStartTime() != null) {
                            minutes = between(productionMachineRecord.getMachineStartTime(), LocalDateTime.now()).toMinutes();
                        }
                        SalesLedgerProductionAccounting salesLedgerProductionAccounting = SalesLedgerProductionAccounting.builder()
                                .productMainId(productionProductMain.getId())
                                .schedulingUserId(Long.parseLong(s))
                                .schedulingUserName(Optional.of(userMapper.selectUserById(Long.parseLong(s))).orElse(new SysUser()).getNickName())
                                .finishedNum(productQty)
                                .workHours(workHours)
                                .process(productProcess.getName())
                                .schedulingDate(LocalDate.now())
                                .tenantId(productionProductOutput.getTenantId())
                                .deviceId(productionMachineRecord.getMachineId())
                                .workHour(minutes)
                                .build();
                        salesLedgerProductionAccountingMapper.insert(salesLedgerProductionAccounting);
                    }
                    productionMachineRecord.setReportStatus(true);
                    productionMachineRecordMapper.updateById(productionMachineRecord);
                }
            }
        }
        return true;
    }
    /**
     * 生成质检单并回填检测标准/检验参数
     */
    private void insertQualityInspect(ProductProcessRouteItem routeItem, Integer inspectType, String process,
                                      Long productMainId, Long workOrderId, BigDecimal quantity) {
        ProductModel productModel = productModelMapper.selectById(routeItem.getProductModelId());
        Product product = productMapper.selectById(productModel.getProductId());
        QualityInspect qualityInspect = new QualityInspect();
        qualityInspect.setProductId(product.getId());
        qualityInspect.setProductName(product.getProductName());
        qualityInspect.setModel(productModel.getModel());
        qualityInspect.setUnit(productModel.getUnit());
        qualityInspect.setQuantity(quantity);
        qualityInspect.setProcess(process);
        qualityInspect.setInspectState(0);
        qualityInspect.setInspectType(inspectType);
        qualityInspect.setProductMainId(productMainId);
        qualityInspect.setWorkOrderId(workOrderId);
        qualityInspect.setProductModelId(productModel.getId());
        qualityInspectMapper.insert(qualityInspect);
        List<QualityTestStandard> qualityTestStandard = qualityTestStandardMapper.getQualityTestStandardByProductId(product.getId(), inspectType, process);
        if (!qualityTestStandard.isEmpty()) {
            qualityInspect.setTestStandardId(qualityTestStandard.get(0).getId());
            qualityInspectMapper.updateById(qualityInspect);
            qualityTestStandardParamMapper.selectList(Wrappers.<QualityTestStandardParam>lambdaQuery()
                            .eq(QualityTestStandardParam::getTestStandardId, qualityTestStandard.get(0).getId()))//默认获取最新的
                    .forEach(qualityTestStandardParam -> {
                        QualityInspectParam param = new QualityInspectParam();
                        BeanUtils.copyProperties(qualityTestStandardParam, param);
                        param.setId(null);
                        param.setInspectId(qualityInspect.getId());
                        qualityInspectParamMapper.insert(param);
                    });
        }
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void preGenerateQualityInspectForOrder(Long productOrderId) {
        if (productOrderId == null) {
            return;
        }
        ProductProcessRoute productProcessRoute = productProcessRouteMapper.selectOne(
                Wrappers.<ProductProcessRoute>lambdaQuery()
                        .eq(ProductProcessRoute::getProductOrderId, productOrderId));
        if (productProcessRoute == null) {
            return;
        }
        List<ProductProcessRouteItem> items = productProcessRouteItemMapper.selectList(
                Wrappers.<ProductProcessRouteItem>lambdaQuery()
                        .eq(ProductProcessRouteItem::getProductRouteId, productProcessRoute.getId())
                        .orderByAsc(ProductProcessRouteItem::getDragSort)
                        .orderByAsc(ProductProcessRouteItem::getId));
        if (items.isEmpty()) {
            return;
        }
        // 第一道工序需质检才预生成
        ProductProcessRouteItem first = items.get(0);
        if (!Boolean.TRUE.equals(first.getIsQuality())) {
            return;
        }
        ProductWorkOrder workOrder = productWorkOrderMapper.selectOne(
                Wrappers.<ProductWorkOrder>lambdaQuery()
                        .eq(ProductWorkOrder::getProductProcessRouteItemId, first.getId()));
        if (workOrder == null) {
            return;
        }
        // 该工单已存在预生成质检单则不重复生成
        Long existCount = qualityInspectMapper.selectCount(
                Wrappers.<QualityInspect>lambdaQuery()
                        .eq(QualityInspect::getWorkOrderId, workOrder.getId())
                        .isNull(QualityInspect::getProductMainId));
        if (existCount != null && existCount > 0) {
            return;
        }
        boolean onlyOne = items.size() == 1;
        int inspectType = onlyOne ? 2 : 1;
        String process = null;
        if (!onlyOne) {
            ProductProcess processEntity = productProcessMapper.selectById(first.getProcessId());
            if (processEntity != null) {
                process = processEntity.getName();
            }
        }
        insertQualityInspect(first, inspectType, process, null, workOrder.getId(), workOrder.getPlanQuantity());
    }
    @Override
    public void cleanPreGeneratedQualityInspects(List<Long> workOrderIds) {
        if (CollUtil.isEmpty(workOrderIds)) {
            return;
        }
        List<QualityInspect> preInspects = qualityInspectMapper.selectList(
                Wrappers.<QualityInspect>lambdaQuery()
                        .in(QualityInspect::getWorkOrderId, workOrderIds)
                        .isNull(QualityInspect::getProductMainId));
        if (CollUtil.isEmpty(preInspects)) {
            return;
        }
        for (QualityInspect pre : preInspects) {
            if (pre.getInspectState() != null && pre.getInspectState() == 1) {
                throw new ServiceException("已提交的检验单不能删除");
            }
        }
        List<Long> inspectIds = preInspects.stream().map(QualityInspect::getId).collect(Collectors.toList());
        qualityInspectParamMapper.delete(
                Wrappers.<QualityInspectParam>lambdaQuery().in(QualityInspectParam::getInspectId, inspectIds));
        qualityInspectMapper.deleteBatchIds(inspectIds);
    }
    @Override
    public Boolean removeProductMain(Long id) {
        //判断该条报工是否不合格处理,如果不合格处理了,则不允许删除
        List<QualityInspect> qualityInspects = qualityInspectMapper.selectList(Wrappers.<QualityInspect>lambdaQuery().eq(QualityInspect::getProductMainId, id));
        if (qualityInspects.size() > 0) {
            List<QualityUnqualified> qualityUnqualifieds = qualityUnqualifiedMapper.selectList(Wrappers.<QualityUnqualified>lambdaQuery()
                    .in(QualityUnqualified::getInspectId, qualityInspects.stream().map(QualityInspect::getId).collect(Collectors.toList())));
            if (qualityUnqualifieds.size() > 0 && qualityUnqualifieds.get(0).getInspectState() == 1) {
                throw new ServiceException("该条报工已经不合格处理了,不允许删除");
            }
        }
        ProductionProductMain productionProductMain = productionProductMainMapper.selectById(id);
        //该报工对应的工艺路线详情
        ProductProcessRouteItem productProcessRouteItem = productProcessRouteItemMapper.selectById(productionProductMain.getProductProcessRouteItemId());
@@ -266,22 +616,30 @@
                new LambdaQueryWrapper<SalesLedgerProductionAccounting>()
                        .eq(SalesLedgerProductionAccounting::getProductMainId, productionProductMain.getId())
        );
        /*删除报工班组人员产量时长明细*/
        productionReportPersonMapper.delete(
                new LambdaQueryWrapper<ProductionReportPerson>()
                        .eq(ProductionReportPerson::getProductMainId, productionProductMain.getId())
        );
        /*更新工单和生产订单*/
        ProductWorkOrder productWorkOrder = productWorkOrderMapper.selectById(productionProductMain.getWorkOrderId());
        if (productWorkOrder != null && productionProductOutput != null) {
            BigDecimal outputQty = productionProductOutput.getQuantity() == null ? BigDecimal.ZERO : productionProductOutput.getQuantity();
            BigDecimal scrapQty = productionProductOutput.getScrapQty() == null ? BigDecimal.ZERO : productionProductOutput.getScrapQty();
            BigDecimal addQty = productionProductOutput.getAddQty() == null ? BigDecimal.ZERO : productionProductOutput.getAddQty();
            BigDecimal completeQty = productWorkOrder.getCompleteQuantity() == null ? BigDecimal.ZERO : productWorkOrder.getCompleteQuantity();
            BigDecimal validQuantity = outputQty.subtract(scrapQty);
            // 必须为审核通过的才会减少数量
            productWorkOrder.setCompleteQuantity(completeQty.subtract(validQuantity));
            BigDecimal validQuantity = outputQty.add(addQty);
            if (productionProductMain.getAuditStatus() != 2) {
                productWorkOrder.setCompleteQuantity(completeQty.subtract(validQuantity).max(BigDecimal.ZERO));
            }
            productWorkOrder.setActualEndTime(null);
            productWorkOrderMapper.updateById(productWorkOrder);
        } else {
            throw new ServiceException("操作失败:工单信息或产出记录不存在");
        }
        //判断是否是最后一道工序
        List<ProductProcessRouteItem> productProcessRouteItems = productProcessRouteItemMapper.selectList(Wrappers.<ProductProcessRouteItem>lambdaQuery().eq(ProductProcessRouteItem::getProductRouteId, productProcessRouteItem.getProductRouteId()));
        if (productProcessRouteItem.getDragSort() != null && productProcessRouteItems != null && productProcessRouteItem.getDragSort() == productProcessRouteItems.size()) {
@@ -289,16 +647,11 @@
            if (productOrder != null) {
                BigDecimal orderCompleteQty = productOrder.getCompleteQuantity() == null ? BigDecimal.ZERO : productOrder.getCompleteQuantity();
                BigDecimal totalQty = productionProductOutput.getQuantity() != null ? productionProductOutput.getQuantity() : BigDecimal.ZERO;
                BigDecimal scrapQty = productionProductOutput.getScrapQty() != null ? productionProductOutput.getScrapQty() : BigDecimal.ZERO;
                BigDecimal actualQualifiedQty = totalQty.subtract(scrapQty);
                BigDecimal addQty = productionProductOutput.getAddQty() != null ? productionProductOutput.getAddQty() : BigDecimal.ZERO;
                BigDecimal actualQualifiedQty = totalQty.add(addQty);
                BigDecimal newCompleteQty = orderCompleteQty.subtract(actualQualifiedQty);
                productOrder.setCompleteQuantity(newCompleteQty.compareTo(BigDecimal.ZERO) < 0 ? BigDecimal.ZERO : newCompleteQty);
                productOrder.setEndTime(null);
                productOrderMapper.updateById(productOrder);
            } else {
                throw new ServiceException("关联的生产订单不存在");
@@ -313,7 +666,7 @@
                    new LambdaQueryWrapper<QualityInspectParam>()
                            .eq(QualityInspectParam::getInspectId, q.getId()));
            qualityInspectMapper.deleteById(q.getId());
                stockUtils.deleteStockInRecord(q.getId(), StockInQualifiedRecordTypeEnum.QUALITYINSPECT_STOCK_IN.getCode());
            stockUtils.deleteStockInRecord(q.getId(), StockInQualifiedRecordTypeEnum.QUALITYINSPECT_STOCK_IN.getCode());
        });
        // 删除产出记录
@@ -322,12 +675,10 @@
        //删除投入记录
        productionProductInputMapper.delete(new LambdaQueryWrapper<ProductionProductInput>()
                .eq(ProductionProductInput::getProductMainId, productionProductMain.getId()));
        //删除报废的入库记录
        stockUtils.deleteStockInRecord(productionProductMain.getId(), StockInUnQualifiedRecordTypeEnum.PRODUCTION_SCRAP.getCode());
        //删除不需要质检的合格入库
        stockUtils.deleteStockInRecord(productionProductMain.getId(), StockInQualifiedRecordTypeEnum.PRODUCTION_REPORT_STOCK_IN.getCode());
        //删除投入对应的出库记录
        stockUtils.deleteStockOutRecord(productionProductMain.getId(), StockOutQualifiedRecordTypeEnum.PRODUCTION_REPORT_STOCK_OUT.getCode());
//        stockUtils.deleteStockOutRecord(productionProductMain.getId(), StockOutQualifiedRecordTypeEnum.PRODUCTION_REPORT_STOCK_OUT.getCode());
        // 删除主表
        productionProductMainMapper.deleteById(productionProductMain.getId());
        return true;
@@ -337,4 +688,4 @@
    public ArrayList<Long> listMain(List<Long> idList) {
        return productionProductMainMapper.listMain(idList);
    }
}
}