package com.ruoyi.production.service.impl; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; 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.basic.mapper.ProductMapper; import com.ruoyi.basic.mapper.ProductModelMapper; import com.ruoyi.basic.pojo.Product; import com.ruoyi.basic.pojo.ProductModel; import com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum; import com.ruoyi.common.enums.StockInUnQualifiedRecordTypeEnum; import com.ruoyi.common.enums.StockOutQualifiedRecordTypeEnum; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.bean.BeanUtils; import com.ruoyi.procurementrecord.utils.StockUtils; import com.ruoyi.production.dto.ProductStructureDto; import com.ruoyi.production.dto.ProductionProductMainDto; import com.ruoyi.production.mapper.*; import com.ruoyi.production.pojo.*; import com.ruoyi.production.service.ProductionProductMainService; import com.ruoyi.project.system.domain.SysUser; import com.ruoyi.project.system.mapper.SysUserMapper; import com.ruoyi.project.system.service.ISysNoticeService; import com.ruoyi.quality.mapper.*; import com.ruoyi.quality.pojo.*; import com.ruoyi.quality.service.IQualityInspectService; import com.ruoyi.stock.support.FinishedProductStockDimensionResolver; import lombok.AllArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; @Service @AllArgsConstructor @Transactional(rollbackFor = Exception.class) public class ProductionProductMainServiceImpl extends ServiceImpl implements ProductionProductMainService { private static final String PROCESS_VOLTAGE_SORT = "电压分选"; private static final String PROCESS_OPTICAL_INSPECTION = "光检外观"; private static final String PROCESS_PACKAGING = "包装"; private static final String INPUT_WEIGHT_PARAMETER = "投入重量/数量"; private static final String INPUT_WEIGHT_FIELD = "inputWeight"; private static final Object PRODUCT_MAIN_NO_LOCK = new Object(); private IQualityInspectService qualityInspectService; private ProductionProductMainMapper productionProductMainMapper; private ProductWorkOrderMapper productWorkOrderMapper; private ProductProcessRouteItemMapper productProcessRouteItemMapper; private SysUserMapper userMapper; private ProductionProductOutputMapper productionProductOutputMapper; private ProductModelMapper productModelMapper; private QualityInspectMapper qualityInspectMapper; private QualityUnqualifiedMapper qualityUnqualifiedMapper; private ProductProcessMapper productProcessMapper; private ProductProcessRouteMapper productProcessRouteMapper; private ProductMapper productMapper; private QualityTestStandardParamMapper qualityTestStandardParamMapper; private QualityTestStandardMapper qualityTestStandardMapper; private QualityInspectParamMapper qualityInspectParamMapper; private ProductStructureMapper productStructureMapper; private ProductionProductInputMapper productionProductInputMapper; private ProductOrderMapper productOrderMapper; private SalesLedgerProductionAccountingMapper salesLedgerProductionAccountingMapper; private StockUtils stockUtils; private FinishedProductStockDimensionResolver finishedProductStockDimensionResolver; private ISysNoticeService sysNoticeService; @Override public IPage listPageProductionProductMainDto(Page page, ProductionProductMainDto productionProductMainDto) { return productionProductMainMapper.listPageProductionProductMainDto(page, productionProductMainDto); } // 新增报工,并按照报工主流程处理投入扣库、产出、质检、入库、工单订单进度和核算数据 @Override public Boolean addProductMain(ProductionProductMainDto dto) { // 第一步:先校验报工入参,避免空值和非法数量进入后续流程 if (dto == null) { throw new ServiceException("报工参数不能为空"); } if (dto.getProductProcessRouteItemId() == null) { throw new ServiceException("工艺路线工序项不能为空"); } if (dto.getWorkOrderId() == null) { throw new ServiceException("生产工单不能为空"); } BigDecimal reportQty = dto.getQuantity(); BigDecimal scrapQty = dto.getScrapQty() == null ? BigDecimal.ZERO : dto.getScrapQty(); BigDecimal bomInputQty = dto.getInputWeight(); if (bomInputQty == null) { bomInputQty = resolveInputWeight(dto.getOtherData()); } if (reportQty == null || reportQty.compareTo(BigDecimal.ZERO) <= 0) { throw new ServiceException("报工数量必须大于0"); } if (scrapQty.compareTo(BigDecimal.ZERO) < 0) { throw new ServiceException("报废数量不能小于0"); } if (scrapQty.compareTo(reportQty) > 0) { throw new ServiceException("报废数量不能大于报工数量"); } if (bomInputQty == null || bomInputQty.compareTo(BigDecimal.ZERO) < 0) { throw new ServiceException("产品结构投入数量不能小于0"); } // 第二步:加载当前工序、工单、工艺路线和订单数据,并校验基础关联关系 ProductProcessRouteItem productProcessRouteItem = productProcessRouteItemMapper.selectById(dto.getProductProcessRouteItemId()); if (productProcessRouteItem == null) { throw new ServiceException("工艺路线工序项不存在"); } ProductProcess productProcess = productProcessMapper.selectById(productProcessRouteItem.getProcessId()); if (productProcess == null) { throw new ServiceException("当前工序不存在"); } ProductModel productModel = productModelMapper.selectById(productProcessRouteItem.getProductModelId()); if (productModel == null) { throw new ServiceException("当前工序对应的产品型号不存在"); } ProductProcessRoute productProcessRoute = productProcessRouteMapper.selectById(productProcessRouteItem.getProductRouteId()); if (productProcessRoute == null) { throw new ServiceException("工艺路线不存在"); } ProductWorkOrder productWorkOrder = productWorkOrderMapper.selectById(dto.getWorkOrderId()); if (productWorkOrder == null) { throw new ServiceException("生产工单不存在"); } if (!Objects.equals(productWorkOrder.getProductProcessRouteItemId(), productProcessRouteItem.getId())) { throw new ServiceException("生产工单与当前工序不匹配"); } ProductOrder productOrder = productOrderMapper.selectById(productWorkOrder.getProductOrderId()); if (productOrder == null) { throw new ServiceException("生产订单不存在"); } // 第三步:校验工序流转逻辑,必须是同一订单的上一道工序已报工且前序无未解除隔离记录 Integer currentDragSort = productProcessRouteItem.getDragSort(); if (currentDragSort != null && currentDragSort > 1) { boolean isPreviousReported = productionProductMainMapper.checkPreviousProcessReported( productWorkOrder.getId(), currentDragSort ); if (!isPreviousReported) { throw new ServiceException("上一道工序尚未报工,当前工序不能报工"); } ProductProcessRouteItem previousRouteItem = productProcessRouteItemMapper.selectOne( Wrappers.lambdaQuery() .eq(ProductProcessRouteItem::getProductRouteId, productProcessRouteItem.getProductRouteId()) .eq(ProductProcessRouteItem::getDragSort, currentDragSort - 1) ); if (previousRouteItem == null) { throw new ServiceException("上一道工序不存在"); } ProductWorkOrder previousWorkOrder = productWorkOrderMapper.selectOne( Wrappers.lambdaQuery() .eq(ProductWorkOrder::getProductOrderId, productOrder.getId()) .eq(ProductWorkOrder::getProductProcessRouteItemId, previousRouteItem.getId()) ); if (previousWorkOrder == null) { throw new ServiceException("上一道工序工单不存在"); } BigDecimal currentReportedQty = BigDecimal.ZERO; List currentMainList = productionProductMainMapper.selectList( Wrappers.lambdaQuery() .eq(ProductionProductMain::getWorkOrderId, productWorkOrder.getId()) ); if (CollectionUtils.isNotEmpty(currentMainList)) { List currentMainIds = currentMainList.stream().map(ProductionProductMain::getId).collect(Collectors.toList()); List currentOutputList = productionProductOutputMapper.selectList( Wrappers.lambdaQuery() .in(ProductionProductOutput::getProductMainId, currentMainIds) ); currentReportedQty = currentOutputList.stream() .map(ProductionProductOutput::getQuantity) .filter(Objects::nonNull) .reduce(BigDecimal.ZERO, BigDecimal::add); } BigDecimal previousCompleteQty = previousWorkOrder.getCompleteQuantity() == null ? BigDecimal.ZERO : previousWorkOrder.getCompleteQuantity(); // if (currentReportedQty.add(reportQty).compareTo(previousCompleteQty) > 0) { // throw new ServiceException("本次报工数量超过上道工序可流转数量"); // } List previousItems = productProcessRouteItemMapper.selectList( Wrappers.lambdaQuery() .eq(ProductProcessRouteItem::getProductRouteId, productProcessRouteItem.getProductRouteId()) .lt(ProductProcessRouteItem::getDragSort, currentDragSort) ); if (CollectionUtils.isNotEmpty(previousItems)) { List previousProcessNames = new ArrayList<>(); for (ProductProcessRouteItem item : previousItems) { ProductProcess process = productProcessMapper.selectById(item.getProcessId()); if (process != null && StringUtils.isNotBlank(process.getName())) { previousProcessNames.add(process.getName().trim()); } } if (CollectionUtils.isNotEmpty(previousProcessNames)) { List unqualifiedList = qualityUnqualifiedMapper.selectUnqualifiedByProductOrderAndProcessNames(productOrder.getId(), previousProcessNames); if (CollectionUtils.isNotEmpty(unqualifiedList)) { throw new ServiceException("前序工序存在隔离记录,当前工序不能报工"); } } } } // 第四步:控制当前工单总报工量不能超过计划量 BigDecimal currentWorkOrderReportedQty = BigDecimal.ZERO; List workOrderMainList = productionProductMainMapper.selectList( Wrappers.lambdaQuery() .eq(ProductionProductMain::getWorkOrderId, productWorkOrder.getId()) ); if (CollectionUtils.isNotEmpty(workOrderMainList)) { List workOrderMainIds = workOrderMainList.stream().map(ProductionProductMain::getId).collect(Collectors.toList()); List workOrderOutputList = productionProductOutputMapper.selectList( Wrappers.lambdaQuery() .in(ProductionProductOutput::getProductMainId, workOrderMainIds) ); currentWorkOrderReportedQty = workOrderOutputList.stream() .map(ProductionProductOutput::getQuantity) .filter(Objects::nonNull) .reduce(BigDecimal.ZERO, BigDecimal::add); } // if (productWorkOrder.getPlanQuantity() != null // && currentWorkOrderReportedQty.add(reportQty).compareTo(productWorkOrder.getPlanQuantity()) > 0) { // throw new ServiceException("本次报工数量超过工单可报数量"); // } // 第五步:生成报工单号并确定报工人信息 String productNo; synchronized (PRODUCT_MAIN_NO_LOCK) { String datePrefix = "BG" + LocalDate.now().format(DateTimeFormatter.ofPattern("yyMMdd")); QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.select("MAX(product_no) as maxNo") .likeRight("product_no", datePrefix); List> resultList = productionProductMainMapper.selectMaps(queryWrapper); int sequenceNumber = 1; if (CollectionUtils.isNotEmpty(resultList)) { Map result = resultList.get(0); if (result != null && result.get("maxNo") != null) { String lastNo = result.get("maxNo").toString(); if (lastNo.startsWith(datePrefix)) { try { sequenceNumber = Integer.parseInt(lastNo.substring(datePrefix.length())) + 1; } catch (NumberFormatException e) { sequenceNumber = 1; } } } } productNo = String.format("%s%03d", datePrefix, sequenceNumber); } Long userId = dto.getUserId(); String userName = dto.getUserName(); if (userId == null) { userId = SecurityUtils.getLoginUser().getUserId(); userName = SecurityUtils.getLoginUser().getNickName(); } else if (StringUtils.isBlank(userName)) { SysUser user = userMapper.selectUserById(userId); userName = user == null ? null : user.getNickName(); } // 第六步:先写报工主表 ProductionProductMain productionProductMain = new ProductionProductMain(); productionProductMain.setProductNo(productNo); productionProductMain.setUserId(userId); productionProductMain.setUserName(userName); productionProductMain.setProductProcessRouteItemId(dto.getProductProcessRouteItemId()); productionProductMain.setWorkOrderId(dto.getWorkOrderId()); productionProductMain.setStatus(0); productionProductMainMapper.insert(productionProductMain); // 第七步:根据 BOM 生成投入记录并扣减原料库存 List productStructureDtos = productStructureMapper.listBybomAndProcess(productProcessRoute.getBomId(), productProcess.getId()); if (CollectionUtils.isEmpty(productStructureDtos)) { ProductStructureDto productStructureDto = new ProductStructureDto(); productStructureDto.setProductModelId(productProcessRouteItem.getProductModelId()); productStructureDto.setUnitQuantity(BigDecimal.ONE); productStructureDtos.add(productStructureDto); } Set parentIds = productStructureDtos.stream() .map(ProductStructureDto::getParentId) .filter(Objects::nonNull) .collect(Collectors.toSet()); Map parentMap = new HashMap<>(); if (CollectionUtils.isNotEmpty(parentIds)) { parentMap = productStructureMapper.selectByIds(parentIds) .stream() .collect(Collectors.toMap(ProductStructureDto::getId, Function.identity(), (a, b) -> a)); } // 第七步-1:投入数量强制取前端传入的 bomInputQty BigDecimal inputBaseQty = bomInputQty; for (ProductStructureDto productStructureDto : productStructureDtos) { if (productStructureDto.getProductModelId() == null) { throw new ServiceException("投入物料产品型号不能为空"); } BigDecimal childQty = productStructureDto.getUnitQuantity(); if (childQty == null || childQty.compareTo(BigDecimal.ZERO) <= 0) { throw new ServiceException("投入物料用量必须大于0"); } BigDecimal parentQty = BigDecimal.ONE; if (productStructureDto.getParentId() != null) { ProductStructureDto parent = parentMap.get(productStructureDto.getParentId()); if (parent != null) { parentQty = parent.getUnitQuantity(); } } if (parentQty == null || parentQty.compareTo(BigDecimal.ZERO) <= 0) { throw new ServiceException("父级物料用量必须大于0"); } BigDecimal needQty = inputBaseQty; ProductionProductInput productionProductInput = new ProductionProductInput(); productionProductInput.setProductModelId(productStructureDto.getProductModelId()); productionProductInput.setQuantity(needQty == null ? BigDecimal.ZERO : needQty); productionProductInput.setProductMainId(productionProductMain.getId()); productionProductInputMapper.insert(productionProductInput); stockUtils.substractStock( productStructureDto.getProductModelId(), needQty, StockOutQualifiedRecordTypeEnum.PRODUCTION_REPORT_STOCK_OUT.getCode(), productionProductMain.getId(), null, null ); } // 第八步:写产出记录并计算本次合格数量 ProductionProductOutput productionProductOutput = new ProductionProductOutput(); productionProductOutput.setProductMainId(productionProductMain.getId()); productionProductOutput.setProductModelId(productProcessRouteItem.getProductModelId()); productionProductOutput.setQuantity(reportQty); productionProductOutput.setScrapQty(scrapQty); productionProductOutput.setOtherData(dto.getOtherData() == null ? "" : dto.getOtherData()); productionProductOutputMapper.insert(productionProductOutput); BigDecimal qualifiedQty = reportQty.subtract(scrapQty); List productProcessRouteItems = productProcessRouteItemMapper.selectList( Wrappers.lambdaQuery() .eq(ProductProcessRouteItem::getProductRouteId, productProcessRouteItem.getProductRouteId()) ); Integer maxDragSort = productProcessRouteItems.stream() .map(ProductProcessRouteItem::getDragSort) .filter(Objects::nonNull) .max(Integer::compareTo) .orElse(null); boolean isRouteLastProcess = Objects.equals(productProcessRouteItem.getDragSort(), maxDragSort); ReportStockRule reportStockRule = resolveReportStockRule(productProcessRouteItem, productProcess, productProcessRouteItems); // 第九步:如果有合格数量,则根据是否质检决定走质检或直接入库 if (qualifiedQty.compareTo(BigDecimal.ZERO) > 0) { if (Boolean.TRUE.equals(productProcessRouteItem.getIsQuality())) { Product product = productMapper.selectById(productModel.getProductId()); if (product == null) { throw new ServiceException("质检产品不存在"); } int inspectType = 1; String process = productProcess.getName(); if (reportStockRule.finishedGoodsStockIn) { inspectType = 2; process = null; } QualityInspect qualityInspect = new QualityInspect(); qualityInspect.setProductId(product.getId()); qualityInspect.setProductName(product.getProductName()); qualityInspect.setModel(productModel.getModel()); qualityInspect.setUnit(productModel.getUnit()); qualityInspect.setQuantity(reportQty); qualityInspect.setProcess(process); qualityInspect.setInspectState(0); qualityInspect.setInspectType(inspectType); qualityInspect.setDefectiveQuantity(scrapQty); qualityInspect.setProductMainId(productionProductMain.getId()); qualityInspect.setProductModelId(productModel.getId()); qualityInspectMapper.insert(qualityInspect); List qualityTestStandardList = qualityTestStandardMapper.getQualityTestStandardByProductId(product.getId(), inspectType, process); if (CollectionUtils.isNotEmpty(qualityTestStandardList)) { QualityTestStandard qualityTestStandard = qualityTestStandardList.get(0); qualityInspect.setTestStandardId(qualityTestStandard.getId()); qualityInspectMapper.updateById(qualityInspect); qualityTestStandardParamMapper.selectList( Wrappers.lambdaQuery() .eq(QualityTestStandardParam::getTestStandardId, qualityTestStandard.getId()) ).forEach(qualityTestStandardParam -> { QualityInspectParam param = new QualityInspectParam(); BeanUtils.copyProperties(qualityTestStandardParam, param); param.setId(null); param.setInspectId(qualityInspect.getId()); qualityInspectParamMapper.insert(param); }); } } else if (reportStockRule.createStockIn) { //成品入库需要电压,工序类别(铜,银) if (reportStockRule.finishedGoodsStockIn) { String processCategory = finishedProductStockDimensionResolver.resolveProcessCategory(productionProductMain.getId()); String voltage = finishedProductStockDimensionResolver.resolveVoltage(productionProductMain.getId()); stockUtils.addStock( productProcessRouteItem.getProductModelId(), qualifiedQty, StockInQualifiedRecordTypeEnum.PRODUCTION_REPORT_STOCK_IN.getCode(), productionProductMain.getId(), processCategory, voltage ); } else { stockUtils.addStockNoReview( productProcessRouteItem.getProductModelId(), qualifiedQty, StockInQualifiedRecordTypeEnum.PRODUCTION_REPORT_STOCK_IN.getCode(), productionProductMain.getId() ); } } // 第十步:更新工单、订单完成进度和计件核算数据 BigDecimal workOrderCompleteQuantity = productWorkOrder.getCompleteQuantity() == null ? BigDecimal.ZERO : productWorkOrder.getCompleteQuantity(); productWorkOrder.setCompleteQuantity(workOrderCompleteQuantity.add(qualifiedQty)); if (ObjectUtils.isNull(productWorkOrder.getActualStartTime())) { productWorkOrder.setActualStartTime(LocalDate.now()); } if (productWorkOrder.getPlanQuantity() != null && productWorkOrder.getCompleteQuantity().compareTo(productWorkOrder.getPlanQuantity()) >= 0) { productWorkOrder.setActualEndTime(LocalDate.now()); } productWorkOrderMapper.updateById(productWorkOrder); if (ObjectUtils.isNull(productOrder.getStartTime())) { productOrder.setStartTime(LocalDateTime.now()); } if (isRouteLastProcess) { BigDecimal orderCompleteQuantity = productOrder.getCompleteQuantity() == null ? BigDecimal.ZERO : productOrder.getCompleteQuantity(); productOrder.setCompleteQuantity(orderCompleteQuantity.add(qualifiedQty)); if (productOrder.getQuantity() != null && productOrder.getCompleteQuantity().compareTo(productOrder.getQuantity()) >= 0) { productOrder.setEndTime(LocalDateTime.now()); } } productOrderMapper.updateById(productOrder); SalesLedgerProductionAccounting salesLedgerProductionAccounting = SalesLedgerProductionAccounting.builder() .productMainId(productionProductMain.getId()) .schedulingUserId(userId) .schedulingUserName(userName) .finishedNum(qualifiedQty) .workHours(productProcess.getSalaryQuota()) .process(productProcess.getName()) .schedulingDate(LocalDate.now()) .tenantId(dto.getTenantId()) .build(); salesLedgerProductionAccountingMapper.insert(salesLedgerProductionAccounting); } // 第十一步:统一处理报废数量入不合格库存,避免在主流程中重复入库 handleScrapStock(productModel.getId(), scrapQty, isRouteLastProcess, productionProductMain.getId()); return true; } // 判断当前报工是否需要入库,以及是否按成品入库处理 private ReportStockRule resolveReportStockRule(ProductProcessRouteItem currentRouteItem, ProductProcess currentProcess, List routeItems) { Integer maxDragSort = CollectionUtils.isEmpty(routeItems) ? null : routeItems.stream() .map(ProductProcessRouteItem::getDragSort) .filter(Objects::nonNull) .max(Integer::compareTo) .orElse(null); boolean isRouteLastProcess = Objects.equals(currentRouteItem.getDragSort(), maxDragSort); String currentProcessName = currentProcess == null || currentProcess.getName() == null ? "" : currentProcess.getName().trim(); if (PROCESS_VOLTAGE_SORT.equals(currentProcessName)) { return new ReportStockRule(false, false); } Set processIds = CollectionUtils.isEmpty(routeItems) ? Collections.emptySet() : routeItems.stream() .map(ProductProcessRouteItem::getProcessId) .filter(Objects::nonNull) .collect(Collectors.toSet()); Map processNameMap = new HashMap<>(); if (CollectionUtils.isNotEmpty(processIds)) { processNameMap = productProcessMapper.selectBatchIds(processIds) .stream() .collect(Collectors.toMap( ProductProcess::getId, process -> process.getName() == null ? "" : process.getName().trim(), (a, b) -> a )); } Set routeProcessNames = CollectionUtils.isEmpty(routeItems) ? Collections.emptySet() : routeItems.stream() .map(ProductProcessRouteItem::getProcessId) .map(processNameMap::get) .filter(StringUtils::isNotBlank) .collect(Collectors.toSet()); boolean hasVoltageSort = routeProcessNames.contains(PROCESS_VOLTAGE_SORT); boolean hasOpticalInspection = routeProcessNames.contains(PROCESS_OPTICAL_INSPECTION); boolean hasPackaging = routeProcessNames.contains(PROCESS_PACKAGING); if (hasPackaging && PROCESS_PACKAGING.equals(currentProcessName)) { return new ReportStockRule(true, true); } if (hasPackaging && PROCESS_OPTICAL_INSPECTION.equals(currentProcessName)) { return new ReportStockRule(false, false); } if (!hasPackaging && hasVoltageSort && hasOpticalInspection && PROCESS_OPTICAL_INSPECTION.equals(currentProcessName)) { return new ReportStockRule(true, true); } return new ReportStockRule(true, isRouteLastProcess); } // 统一处理报废数量入不合格库存,避免重复入库 private void handleScrapStock(Long productModelId, BigDecimal scrapQty, boolean isRouteLastProcess, Long productMainId) { if (productModelId == null) { throw new ServiceException("报废产品型号不能为空"); } if (scrapQty == null || scrapQty.compareTo(BigDecimal.ZERO) <= 0) { return; } if (isRouteLastProcess) { stockUtils.addUnStock( productModelId, scrapQty, StockInUnQualifiedRecordTypeEnum.PRODUCTION_SCRAP.getCode(), productMainId ); } else { stockUtils.addUnStockNoReview( productModelId, scrapQty, StockInUnQualifiedRecordTypeEnum.PRODUCTION_SCRAP.getCode(), productMainId ); } } // 单次报工对应的入库规则 private static final class ReportStockRule { private final boolean createStockIn; private final boolean finishedGoodsStockIn; private ReportStockRule(boolean createStockIn, boolean finishedGoodsStockIn) { this.createStockIn = createStockIn; this.finishedGoodsStockIn = finishedGoodsStockIn; } } @Override public Boolean removeProductMain(Long id) { // 删除报工前先检查是否已经完成不合格处理 List qualityInspects = qualityInspectMapper.selectList( Wrappers.lambdaQuery().eq(QualityInspect::getProductMainId, id) ); if (!qualityInspects.isEmpty()) { List qualityUnqualifieds = qualityUnqualifiedMapper.selectList( Wrappers.lambdaQuery() .in(QualityUnqualified::getInspectId, qualityInspects.stream().map(QualityInspect::getId).collect(Collectors.toList())) ); if (!qualityUnqualifieds.isEmpty() && qualityUnqualifieds.get(0).getInspectState() == 1) { throw new ServiceException("该报工已完成不合格处理,不能删除"); } } ProductionProductMain productionProductMain = productionProductMainMapper.selectById(id); ProductProcessRouteItem productProcessRouteItem = productProcessRouteItemMapper.selectById(productionProductMain.getProductProcessRouteItemId()); ProductionProductOutput productionProductOutput = productionProductOutputMapper.selectList( Wrappers.lambdaQuery().eq(ProductionProductOutput::getProductMainId, productionProductMain.getId()) ).get(0); salesLedgerProductionAccountingMapper.delete( new LambdaQueryWrapper() .eq(SalesLedgerProductionAccounting::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 completeQty = productWorkOrder.getCompleteQuantity() == null ? BigDecimal.ZERO : productWorkOrder.getCompleteQuantity(); BigDecimal validQuantity = outputQty.subtract(scrapQty); productWorkOrder.setCompleteQuantity(completeQty.subtract(validQuantity)); productWorkOrder.setActualEndTime(null); productWorkOrderMapper.updateById(productWorkOrder); } else { throw new ServiceException("操作失败:工单信息或产出记录不存在"); } List productProcessRouteItems = productProcessRouteItemMapper.selectList( Wrappers.lambdaQuery().eq(ProductProcessRouteItem::getProductRouteId, productProcessRouteItem.getProductRouteId()) ); if (productProcessRouteItem.getDragSort() != null && productProcessRouteItems != null && productProcessRouteItem.getDragSort() == productProcessRouteItems.size()) { ProductOrder productOrder = productOrderMapper.selectById(productWorkOrder.getProductOrderId()); 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 newCompleteQty = orderCompleteQty.subtract(actualQualifiedQty); productOrder.setCompleteQuantity(newCompleteQty.compareTo(BigDecimal.ZERO) < 0 ? BigDecimal.ZERO : newCompleteQty); productOrder.setEndTime(null); productOrderMapper.updateById(productOrder); } else { throw new ServiceException("关联的生产订单不存在"); } } qualityInspectMapper.selectList( new LambdaQueryWrapper() .eq(QualityInspect::getProductMainId, productionProductMain.getId()) ).forEach(q -> { qualityInspectParamMapper.delete( new LambdaQueryWrapper() .eq(QualityInspectParam::getInspectId, q.getId()) ); qualityInspectMapper.deleteById(q.getId()); stockUtils.deleteStockInRecord(q.getId(), StockInQualifiedRecordTypeEnum.QUALITYINSPECT_STOCK_IN.getCode()); }); productionProductOutputMapper.delete( new LambdaQueryWrapper() .eq(ProductionProductOutput::getProductMainId, productionProductMain.getId()) ); productionProductInputMapper.delete( new LambdaQueryWrapper() .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()); productionProductMainMapper.deleteById(productionProductMain.getId()); return true; } @Override public ArrayList listMain(List idList) { return productionProductMainMapper.listMain(idList); } @Override public List getByProductWorkOrderId(Long productWorkOrderId) { List productionProductMainDtos = productionProductMainMapper.getByProductWorkOrderId(productWorkOrderId); if (productionProductMainDtos == null || productionProductMainDtos.isEmpty()) { return productionProductMainDtos; } List productMainIds = productionProductMainDtos.stream() .map(ProductionProductMainDto::getId) .collect(Collectors.toList()); List qualityInspects = qualityInspectMapper.selectList( Wrappers.lambdaQuery() .in(QualityInspect::getProductMainId, productMainIds) ); if (!qualityInspects.isEmpty()) { List inspectIds = qualityInspects.stream() .map(QualityInspect::getId) .collect(Collectors.toList()); List qualityUnqualifieds = qualityUnqualifiedMapper.selectList( Wrappers.lambdaQuery() .in(QualityUnqualified::getInspectId, inspectIds) ); Map inspectIdToUnqualifiedMap = qualityUnqualifieds.stream() .collect(Collectors.toMap(QualityUnqualified::getInspectId, q -> q, (q1, q2) -> q1)); Map productMainIdToInspectMap = qualityInspects.stream() .collect(Collectors.toMap(QualityInspect::getProductMainId, q -> q, (q1, q2) -> q1)); productionProductMainDtos.forEach(p -> { QualityInspect qualityInspect = productMainIdToInspectMap.get(p.getId()); if (qualityInspect != null) { p.setDefectiveQuantity(qualityInspect.getDefectiveQuantity()); p.setQualifiedQty(p.getQuantity().subtract(p.getScrapQty()).subtract(p.getDefectiveQuantity())); QualityUnqualified qualityUnqualified = inspectIdToUnqualifiedMap.get(qualityInspect.getId()); if (qualityUnqualified != null) { p.setDealResult(qualityUnqualified.getDealResult() == null ? "" : qualityUnqualified.getDealResult()); } } }); } return productionProductMainDtos; } private BigDecimal resolveInputWeight(String otherData) { if (StringUtils.isBlank(otherData)) { return null; } Object parsed; try { parsed = JSON.parse(otherData); } catch (Exception ex) { throw new ServiceException("报工参数格式错误,无法解析投入重量/数量"); } String inputWeight = StringUtils.trim(findParameterValue(parsed, INPUT_WEIGHT_PARAMETER)); if (StringUtils.isBlank(inputWeight)) { inputWeight = StringUtils.trim(findFieldValue(parsed, INPUT_WEIGHT_FIELD)); } if (StringUtils.isBlank(inputWeight)) { return null; } try { return new BigDecimal(inputWeight); } catch (NumberFormatException ex) { throw new ServiceException("报工参数中的投入重量/数量格式错误"); } } private String findParameterValue(Object node, String parameterItem) { if (node instanceof JSONArray) { JSONArray array = (JSONArray) node; for (Object item : array) { String value = findParameterValue(item, parameterItem); if (StringUtils.isNotBlank(value)) { return value; } } return null; } if (node instanceof JSONObject) { JSONObject object = (JSONObject) node; if (parameterItem.equals(StringUtils.trim(object.getString("parameterItem")))) { String value = StringUtils.trim(object.getString("value")); if (StringUtils.isNotBlank(value)) { return value; } } for (Object value : object.values()) { String matched = findParameterValue(value, parameterItem); if (StringUtils.isNotBlank(matched)) { return matched; } } } return null; } private String findFieldValue(Object node, String fieldName) { if (node instanceof JSONArray) { JSONArray array = (JSONArray) node; for (Object item : array) { String value = findFieldValue(item, fieldName); if (StringUtils.isNotBlank(value)) { return value; } } return null; } if (node instanceof JSONObject) { JSONObject object = (JSONObject) node; Object fieldValue = object.get(fieldName); if (fieldValue != null) { String value = StringUtils.trim(String.valueOf(fieldValue)); if (StringUtils.isNotBlank(value)) { return value; } } for (Object value : object.values()) { String matched = findFieldValue(value, fieldName); if (StringUtils.isNotBlank(matched)) { return matched; } } } return null; } }