package com.ruoyi.production.service.impl; 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.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.alibaba.fastjson2.JSON; import com.ruoyi.approve.bean.dto.ApprovalInstanceDto; import com.ruoyi.approve.bean.vo.ApprovalTemplateVo; import com.ruoyi.approve.service.ApprovalInstanceService; import com.ruoyi.approve.service.ApprovalTemplateService; 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.TypeEnums; import com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum; import com.ruoyi.common.enums.StockOutQualifiedRecordTypeEnum; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.utils.bean.BeanUtils; import com.ruoyi.procurementrecord.utils.StockUtils; import com.ruoyi.production.bean.dto.ProductionProductMainDto; import com.ruoyi.production.enums.ProductOrderStatusEnum; 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.quality.mapper.*; import com.ruoyi.quality.pojo.*; import com.ruoyi.stock.pojo.StockInRecord; import com.ruoyi.stock.service.StockInRecordService; import com.ruoyi.stock.dto.StockInventoryDto; import com.ruoyi.stock.service.StockInventoryService; import com.ruoyi.technology.mapper.TechnologyOperationMapper; import com.ruoyi.technology.mapper.TechnologyRoutingOperationMapper; import com.ruoyi.technology.pojo.TechnologyOperation; import com.ruoyi.technology.pojo.TechnologyRoutingOperation; 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.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; @Service @AllArgsConstructor @Transactional(rollbackFor = Exception.class) public class ProductionProductMainServiceImpl extends ServiceImpl implements ProductionProductMainService { /** * 生产报工大类:只区分小提琴/大提琴/贝斯,不再区分零件。 * key为入库规范值,value为产品名称匹配别名(现场数据贝斯写作“贝司”)。 */ private static final Map> MAJOR_CATEGORY_PATTERNS = new LinkedHashMap<>() {{ put("小提琴", List.of("小提琴")); put("大提琴", List.of("大提琴")); put("贝斯", List.of("贝斯", "贝司")); }}; private final ProductionProductMainMapper productionProductMainMapper; private final SysUserMapper userMapper; private final ProductionProductOutputMapper productionProductOutputMapper; private final ProductModelMapper productModelMapper; private final QualityInspectMapper qualityInspectMapper; private final QualityUnqualifiedMapper qualityUnqualifiedMapper; private final ProductMapper productMapper; private final QualityTestStandardParamMapper qualityTestStandardParamMapper; private final QualityTestStandardMapper qualityTestStandardMapper; private final QualityInspectParamMapper qualityInspectParamMapper; private final ProductionProductInputMapper productionProductInputMapper; private final ProductionAccountMapper productionAccountMapper; private final ProductionOperationTaskMapper productionOperationTaskMapper; private final ProductionOrderMapper productionOrderMapper; private final ProductionOrderRoutingOperationMapper productionOrderRoutingOperationMapper; private final ProductionOrderRoutingOperationParamMapper productionOrderRoutingOperationParamMapper; private final TechnologyRoutingOperationMapper technologyRoutingOperationMapper; private final TechnologyOperationMapper technologyOperationMapper; private final ApprovalTemplateService approvalTemplateService; private final ApprovalInstanceService approvalInstanceService; private final StockUtils stockUtils; private final StockInRecordService stockInRecordService; private final StockInventoryService stockInventoryService; @Override public IPage listPageProductionProductMainDto(Page page, ProductionProductMainDto productionProductMainDto) { // 分页查询生产报工主表 IPage result = productionProductMainMapper.listPageProductionProductMainDto(page, productionProductMainDto); fillOperationParamList(result.getRecords()); return result; } @Override public IPage pageProductionProductMain(Page page, ProductionProductMainDto productionProductMainDto) { // 分页查询生产报工主表 return listPageProductionProductMainDto(page, productionProductMainDto); } @Override public ProductionProductMainDto getProductionProductMainInfo(Long id) { // 获取生产产品主表详情 return listPageProductionProductMainDto(new Page<>(1, 1), new ProductionProductMainDto() {{ setId(id); }}).getRecords().stream().findFirst().orElse(null); } private void fillOperationParamList(List recordList) { // 填充工序参数列表 if (recordList == null || recordList.isEmpty()) { return; } // 遍历处理数据并组装结果 Set mainIdSet = recordList.stream() .map(ProductionProductMainDto::getId) .filter(Objects::nonNull) .collect(Collectors.toCollection(LinkedHashSet::new)); if (mainIdSet.isEmpty()) { recordList.forEach(item -> item.setProductionOperationParamList(Collections.emptyList())); return; } // 查询并准备业务数据 List paramList = productionOrderRoutingOperationParamMapper.selectList( Wrappers.lambdaQuery() .in(ProductionOrderRoutingOperationParam::getProductionProductMainId, mainIdSet) .orderByAsc(ProductionOrderRoutingOperationParam::getId)); Map> paramGroupMap = new HashMap<>(); for (ProductionOrderRoutingOperationParam param : paramList) { if (param == null || param.getProductionProductMainId() == null) { continue; } paramGroupMap.computeIfAbsent(param.getProductionProductMainId(), key -> new ArrayList<>()).add(param); } Set missingMainIdSet = new LinkedHashSet<>(); for (ProductionProductMainDto item : recordList) { Long mainId = item.getId(); if (mainId == null) { item.setProductionOperationParamList(Collections.emptyList()); continue; } List params = paramGroupMap.get(mainId); if (params != null && !params.isEmpty()) { item.setProductionOperationParamList(params); continue; } missingMainIdSet.add(mainId); } if (missingMainIdSet.isEmpty()) { return; } // 兼容历史数据:旧报工记录没有按报工ID落参数快照时,回退展示工序模板参数。 List mainList = productionProductMainMapper.selectBatchIds(missingMainIdSet); Map mainIdToTaskIdMap = mainList.stream() .filter(Objects::nonNull) .filter(item -> item.getId() != null) .collect(Collectors.toMap(ProductionProductMain::getId, ProductionProductMain::getProductionOperationTaskId, (left, right) -> left)); Set taskIdSet = mainIdToTaskIdMap.values().stream() .filter(Objects::nonNull) .collect(Collectors.toCollection(LinkedHashSet::new)); if (taskIdSet.isEmpty()) { for (ProductionProductMainDto item : recordList) { if (item.getId() != null && missingMainIdSet.contains(item.getId())) { item.setProductionOperationParamList(Collections.emptyList()); } } return; } List taskList = productionOperationTaskMapper.selectList( Wrappers.lambdaQuery() .in(ProductionOperationTask::getId, taskIdSet)); Map taskIdToRoutingOperationIdMap = taskList.stream() .filter(Objects::nonNull) .filter(item -> item.getId() != null) .collect(Collectors.toMap(ProductionOperationTask::getId, ProductionOperationTask::getProductionOrderRoutingOperationId, (left, right) -> left)); Set routingOperationIdSet = taskIdToRoutingOperationIdMap.values().stream() .filter(Objects::nonNull) .collect(Collectors.toCollection(LinkedHashSet::new)); if (routingOperationIdSet.isEmpty()) { for (ProductionProductMainDto item : recordList) { if (item.getId() != null && missingMainIdSet.contains(item.getId())) { item.setProductionOperationParamList(Collections.emptyList()); } } return; } List fallbackParamList = productionOrderRoutingOperationParamMapper.selectList( Wrappers.lambdaQuery() .in(ProductionOrderRoutingOperationParam::getProductionOrderRoutingOperationId, routingOperationIdSet) .isNull(ProductionOrderRoutingOperationParam::getProductionProductMainId) .orderByAsc(ProductionOrderRoutingOperationParam::getId)); Map> fallbackGroupMap = new HashMap<>(); for (ProductionOrderRoutingOperationParam param : fallbackParamList) { if (param == null || param.getProductionOrderRoutingOperationId() == null) { continue; } fallbackGroupMap.computeIfAbsent(param.getProductionOrderRoutingOperationId(), key -> new ArrayList<>()).add(param); } for (ProductionProductMainDto item : recordList) { Long mainId = item.getId(); if (mainId == null || !missingMainIdSet.contains(mainId)) { continue; } Long taskId = mainIdToTaskIdMap.get(mainId); Long routingOperationId = taskId == null ? null : taskIdToRoutingOperationIdMap.get(taskId); if (routingOperationId == null) { item.setProductionOperationParamList(Collections.emptyList()); continue; } item.setProductionOperationParamList(fallbackGroupMap.getOrDefault(routingOperationId, Collections.emptyList())); } } @Override public Boolean addProductMain(ProductionProductMainDto dto) { // 新增生产报工主记录 Long taskId = resolveTaskId(dto); if (taskId == null) { throw new ServiceException("请传入生产工单ID"); } return addProductMainByProductionTask(dto); } @Override public Boolean saveProductionProductMain(ProductionProductMainDto productionProductMainDto) { // 保存生产报工主记录 return addProductMain(productionProductMainDto); } @Override public Boolean removeProductMain(Long id) { // 删除生产报工主记录 ProductionProductMain currentMain = productionProductMainMapper.selectById(id); if (currentMain == null) { return true; } return removeProductMainByProductionTask(currentMain); } private Boolean addProductMainByProductionTask(ProductionProductMainDto dto) { // 按生产任务新增报工主记录 Long taskId = resolveTaskId(dto); if (taskId == null) { throw new ServiceException("生产工单ID不能为空"); } SysUser user = userMapper.selectUserById(dto.getUserId()); ProductionOperationTask productionOperationTask = productionOperationTaskMapper.selectById(taskId); if (productionOperationTask == null) { throw new ServiceException("生产工单不存在"); } ProductionOrderRoutingOperation routingOperation = productionOrderRoutingOperationMapper.selectById(productionOperationTask.getProductionOrderRoutingOperationId()); if (routingOperation == null) { throw new ServiceException("订单工艺路线工序不存在"); } ProductionOrder productionOrder = productionOrderMapper.selectById(productionOperationTask.getProductionOrderId()); if (productionOrder == null) { throw new ServiceException("生产订单不存在"); } TechnologyRoutingOperation technologyRoutingOperation = technologyRoutingOperationMapper.selectById(routingOperation.getTechnologyRoutingOperationId()); TechnologyOperation technologyOperation = technologyRoutingOperation == null ? null : technologyOperationMapper.selectById(technologyRoutingOperation.getTechnologyOperationId()); ProductModel productModel = productModelMapper.selectById( routingOperation.getProductModelId() != null ? routingOperation.getProductModelId() : productionOrder.getProductModelId()); if (productModel == null) { throw new ServiceException("产品规格不存在"); } // 报工完成时对计数的零件做区分:合格/轻微返工/严重返工/报废四类数量。 BigDecimal qualifiedQty = defaultDecimal(dto.getQuantity()); BigDecimal reworkMinorQty = defaultDecimal(dto.getReworkMinorQty()); BigDecimal reworkMajorQty = defaultDecimal(dto.getReworkMajorQty()); BigDecimal scrapQtyInput = defaultDecimal(dto.getScrapQty()); if (qualifiedQty.compareTo(BigDecimal.ZERO) < 0 || reworkMinorQty.compareTo(BigDecimal.ZERO) < 0 || reworkMajorQty.compareTo(BigDecimal.ZERO) < 0 || scrapQtyInput.compareTo(BigDecimal.ZERO) < 0) { throw new ServiceException("报工数量不能为负数"); } if (qualifiedQty.add(reworkMinorQty).add(reworkMajorQty).add(scrapQtyInput).compareTo(BigDecimal.ZERO) <= 0) { throw new ServiceException("请按合格/轻微返工/严重返工/报废分类填写零件数量"); } ProductionProductMain productionProductMain = new ProductionProductMain(); productionProductMain.setProductNo(generateProductNo()); productionProductMain.setMajorCategory(resolveMajorCategory(productModel)); productionProductMain.setUserId(user == null ? dto.getUserId() : user.getUserId()); productionProductMain.setUserName(user == null ? dto.getUserName() : user.getNickName()); productionProductMain.setProductionOperationTaskId(taskId); productionProductMain.setStatus(0); productionProductMain.setReportType(dto.getReportType() != null ? dto.getReportType() : deriveReportType(qualifiedQty, reworkMinorQty, reworkMajorQty, scrapQtyInput)); productionProductMain.setAuditStatus(dto.getAuditStatus() == null ? 0 : dto.getAuditStatus()); productionProductMain.setAuditRemark(dto.getAuditRemark()); productionProductMain.setWorkHour(dto.getWorkHour()); productionProductMainMapper.insert(productionProductMain); syncOperationParamInputValue(dto, routingOperation.getId(), productionProductMain.getId()); createReportApprovalInstance(productionProductMain); // 需求变更:生产报工不再按零件(BOM结构)拆分投入,投入即本次报工的产品本身,按大类统计。 ProductionProductInput productionProductInput = new ProductionProductInput(); productionProductInput.setProductionProductMainId(productionProductMain.getId()); productionProductInput.setProductModelId(productModel.getId()); productionProductInput.setInputQuantity(qualifiedQty); productionProductInput.setQuantity(qualifiedQty); productionProductInputMapper.insert(productionProductInput); ProductionProductOutput productionProductOutput = new ProductionProductOutput(); productionProductOutput.setProductionProductMainId(productionProductMain.getId()); productionProductOutput.setProductModelId(productModel.getId()); productionProductOutput.setQuantity(defaultDecimal(dto.getQuantity())); productionProductOutput.setScrapQty(defaultDecimal(dto.getScrapQty())); productionProductOutput.setReworkMinorQty(reworkMinorQty); productionProductOutput.setReworkMajorQty(reworkMajorQty); productionProductOutputMapper.insert(productionProductOutput); BigDecimal reportQty = defaultDecimal(productionProductOutput.getQuantity()); BigDecimal scrapQty = defaultDecimal(productionProductOutput.getScrapQty()); BigDecimal productQty = reportQty; String qualifiedBatchNo = null; List routingOperationList = productionOrderRoutingOperationMapper.selectList( Wrappers.lambdaQuery() .eq(ProductionOrderRoutingOperation::getOrderRoutingId, routingOperation.getOrderRoutingId()) .eq(ProductionOrderRoutingOperation::getProductionOrderId, routingOperation.getProductionOrderId())); boolean isLastOperation = routingOperation.getDragSort() != null && routingOperation.getDragSort().equals(routingOperationList.size()); if (productQty.compareTo(BigDecimal.ZERO) > 0) { if (Boolean.TRUE.equals(routingOperation.getIsQuality())) { // 质检工序先生成检验单,非质检工序直接入合格品库存。 int inspectType = isLastOperation ? 2 : 1; String process = isLastOperation ? null : technologyOperation == null ? null : technologyOperation.getName(); 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 = qualityTestStandardMapper.getQualityTestStandardByProductId(product.getId(), inspectType, process); if (!qualityTestStandard.isEmpty()) { qualityInspect.setTestStandardId(qualityTestStandard.get(0).getId()); qualityInspectMapper.updateById(qualityInspect); qualityTestStandardParamMapper.selectList(Wrappers.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); }); } } else if(isLastOperation){ //(最后一道工序才入库) StockInventoryDto stockInventoryDto = new StockInventoryDto(); stockInventoryDto.setRecordId(productionProductMain.getId()); stockInventoryDto.setRecordType(String.valueOf(StockInQualifiedRecordTypeEnum.PRODUCTION_REPORT_STOCK_IN.getCode())); stockInventoryDto.setQualitity(productQty); stockInventoryDto.setProductModelId(productModel.getId()); stockInventoryService.addStockInRecordOnly(stockInventoryDto); qualifiedBatchNo = resolveLatestStockInBatchNo( productionProductMain.getId(), StockInQualifiedRecordTypeEnum.PRODUCTION_REPORT_STOCK_IN.getCode(), productModel.getId(), "0"); } productionOperationTask.setCompleteQuantity(defaultDecimal(productionOperationTask.getCompleteQuantity()).add(productQty)); if (ObjectUtils.isNull(productionOperationTask.getActualStartTime())) { productionOperationTask.setActualStartTime(LocalDate.now()); } // 报工驱动工单状态流转:有产出即进行中,达到计划量即完工。 productionOperationTask.setStatus(3); if (productionOperationTask.getPlanQuantity() != null && productionOperationTask.getCompleteQuantity().compareTo(productionOperationTask.getPlanQuantity()) >= 0) { productionOperationTask.setActualEndTime(LocalDate.now()); productionOperationTask.setStatus(4); } productionOperationTaskMapper.updateById(productionOperationTask); if (ObjectUtils.isNull(productionOrder.getStartTime())) { productionOrder.setStartTime(LocalDateTime.now()); } // 仅待开始状态才推进到进行中,避免非末道工序报工时覆盖已完成状态。 if (ProductOrderStatusEnum.WAIT.getCode().equals(productionOrder.getStatus())) { productionOrder.setStatus(ProductOrderStatusEnum.RUNNING.getCode()); } if (isLastOperation) { productionOrder.setCompleteQuantity(defaultDecimal(productionOrder.getCompleteQuantity()).add(productQty)); if (productionOrder.getQuantity() != null && productionOrder.getCompleteQuantity().compareTo(productionOrder.getQuantity()) >= 0) { productionOrder.setEndTime(LocalDateTime.now()); productionOrder.setStatus(ProductOrderStatusEnum.FINISHED.getCode()); } } productionOrderMapper.updateById(productionOrder); BigDecimal workHours = BigDecimal.ZERO; if (technologyOperation != null && technologyOperation.getSalaryQuota() != null) { workHours = Integer.valueOf(1).equals(technologyOperation.getType()) ? technologyOperation.getSalaryQuota().multiply(productQty) : technologyOperation.getSalaryQuota(); } ProductionAccount productionAccount = new ProductionAccount(); productionAccount.setProductionProductMainId(productionProductMain.getId()); productionAccount.setSchedulingUserId(user == null ? null : user.getUserId()); productionAccount.setSchedulingUserName(user == null ? dto.getUserName() : user.getNickName()); productionAccount.setFinishedNum(productQty); productionAccount.setWorkHours(technologyOperation != null ? technologyOperation.getSalaryQuota() : null); productionAccount.setTechnologyOperationName(technologyOperation == null ? null : technologyOperation.getName()); productionAccount.setSchedulingDate(LocalDateTime.now()); } if (scrapQty.compareTo(BigDecimal.ZERO) > 0) { stockUtils.addUnStockWithBatchNo( productModel.getId(), scrapQty, StockInQualifiedRecordTypeEnum.PRODUCTION_SCRAP.getCode(), productionProductMain.getId(), qualifiedBatchNo); } // 区分出的返工零件生成不合格品待处理单,进入不合格品管理的报工处理闭环。 createReworkUnqualifiedRecords(productionProductMain, productModel, reworkMinorQty, reworkMajorQty); return true; } private Integer deriveReportType(BigDecimal qualifiedQty, BigDecimal reworkMinorQty, BigDecimal reworkMajorQty, BigDecimal scrapQty) { if (qualifiedQty.compareTo(BigDecimal.ZERO) > 0) { return 0; } if (reworkMinorQty.compareTo(BigDecimal.ZERO) > 0) { return 1; } if (reworkMajorQty.compareTo(BigDecimal.ZERO) > 0) { return 2; } if (scrapQty.compareTo(BigDecimal.ZERO) > 0) { return 3; } return 0; } private void createReworkUnqualifiedRecords(ProductionProductMain productionProductMain, ProductModel productModel, BigDecimal reworkMinorQty, BigDecimal reworkMajorQty) { if (reworkMinorQty.compareTo(BigDecimal.ZERO) <= 0 && reworkMajorQty.compareTo(BigDecimal.ZERO) <= 0) { return; } Product product = productMapper.selectById(productModel.getProductId()); if (product == null) { throw new ServiceException("产品不存在,无法生成报工返工待处理单"); } if (reworkMinorQty.compareTo(BigDecimal.ZERO) > 0) { insertReworkUnqualified(productionProductMain, product, productModel, reworkMinorQty, 0); } if (reworkMajorQty.compareTo(BigDecimal.ZERO) > 0) { insertReworkUnqualified(productionProductMain, product, productModel, reworkMajorQty, 1); } } private void insertReworkUnqualified(ProductionProductMain productionProductMain, Product product, ProductModel productModel, BigDecimal quantity, int dealType) { QualityUnqualified qualityUnqualified = new QualityUnqualified(); qualityUnqualified.setInspectType(1); qualityUnqualified.setInspectState(0); qualityUnqualified.setCheckTime(new Date()); qualityUnqualified.setCheckName(productionProductMain.getUserName()); qualityUnqualified.setProductId(product.getId()); qualityUnqualified.setProductName(product.getProductName()); qualityUnqualified.setModel(productModel.getModel()); qualityUnqualified.setUnit(productModel.getUnit()); qualityUnqualified.setQuantity(quantity); qualityUnqualified.setProductModelId(productModel.getId()); qualityUnqualified.setProductMainId(productionProductMain.getId()); qualityUnqualified.setDefectivePhenomena("生产报工【" + productionProductMain.getProductNo() + "】计数区分-" + (dealType == 0 ? "轻微返工" : "严重返工")); qualityUnqualified.setDealType(dealType); qualityUnqualified.setDealResult("报工"); qualityUnqualifiedMapper.insert(qualityUnqualified); } private void createReportApprovalInstance(ProductionProductMain productionProductMain) { if (productionProductMain == null || productionProductMain.getId() == null) { return; } ApprovalTemplateVo templateVo = approvalTemplateService.getEnabledTemplateByBusinessType( TypeEnums.PRODUCTION_REPORT_APPROVAL.getCode() ); if (templateVo == null) { throw new ServiceException("请先配置报工审批模板"); } ApprovalInstanceDto approvalInstanceDto = new ApprovalInstanceDto(); approvalInstanceDto.setTemplateId(templateVo.getId()); approvalInstanceDto.setTemplateName(templateVo.getTemplateName()); approvalInstanceDto.setBusinessId(productionProductMain.getId()); approvalInstanceDto.setBusinessType(TypeEnums.PRODUCTION_REPORT_APPROVAL.getCode()); approvalInstanceDto.setTitle(productionProductMain.getProductNo()); approvalInstanceDto.setApplicantId(productionProductMain.getUserId()); approvalInstanceDto.setApplicantName(productionProductMain.getUserName()); approvalInstanceDto.setApplyTime(LocalDateTime.now()); approvalInstanceDto.setStatus("PENDING"); approvalInstanceDto.setFormConfig(JSON.toJSONString(buildReportApprovalFormConfig(productionProductMain))); approvalInstanceService.add(approvalInstanceDto); } private Map buildReportApprovalFormConfig(ProductionProductMain productionProductMain) { Map formConfig = new LinkedHashMap<>(); formConfig.put("summaryPlaceholder", "报工审批"); formConfig.put("approvalType", "production_report"); ProductionOperationTask task = productionOperationTaskMapper.selectById(productionProductMain.getProductionOperationTaskId()); ProductionProductOutput output = productionProductOutputMapper.selectOne( Wrappers.lambdaQuery() .eq(ProductionProductOutput::getProductionProductMainId, productionProductMain.getId()) .last("limit 1")); BigDecimal schedulingNum = task == null ? BigDecimal.ZERO : defaultDecimal(task.getPlanQuantity()); BigDecimal finishedNum = output == null ? BigDecimal.ZERO : defaultDecimal(output.getQuantity()); BigDecimal scrapQty = output == null ? BigDecimal.ZERO : defaultDecimal(output.getScrapQty()); BigDecimal reworkMinorNum = output == null ? BigDecimal.ZERO : defaultDecimal(output.getReworkMinorQty()); BigDecimal reworkMajorNum = output == null ? BigDecimal.ZERO : defaultDecimal(output.getReworkMajorQty()); BigDecimal pendingNum = schedulingNum.subtract(finishedNum); if (pendingNum.compareTo(BigDecimal.ZERO) < 0) { pendingNum = BigDecimal.ZERO; } List> fields = new ArrayList<>(); fields.add(buildReportField("productNo", "报工单号", "text", productionProductMain.getProductNo())); fields.add(buildReportField("userName", "报工人员", "text", productionProductMain.getUserName())); fields.add(buildReportField("teamInfo", "班组信息", "text", productionProductMain.getUserName())); fields.add(buildReportField("workOrderNo", "工单编号", "text", resolveWorkOrderNo(productionProductMain.getProductionOperationTaskId()))); fields.add(buildReportField("schedulingNum", "排产数量", "number", schedulingNum)); fields.add(buildReportField("pendingNum", "待生产数量", "number", pendingNum)); fields.add(buildReportField("finishedNum", "合格数量", "number", finishedNum)); fields.add(buildReportField("reworkMinorQty", "轻微返工数量", "number", reworkMinorNum)); fields.add(buildReportField("reworkMajorQty", "严重返工数量", "number", reworkMajorNum)); fields.add(buildReportField("scrapQty", "报废数量", "number", scrapQty)); fields.add(buildReportField("reportType", "报工结果", "select", resolveReportTypeLabel(productionProductMain.getReportType()))); fields.add(buildReportField("workHour", "工时", "number", productionProductMain.getWorkHour())); fields.add(buildReportField("auditStatus", "审核状态", "select", resolveAuditStatusLabel(productionProductMain.getAuditStatus()))); fields.add(buildReportField("auditRemark", "审核意见", "textarea", productionProductMain.getAuditRemark())); formConfig.put("fields", fields); Map formPayload = new LinkedHashMap<>(); formPayload.put("productNo", productionProductMain.getProductNo()); formPayload.put("userName", productionProductMain.getUserName()); formPayload.put("teamInfo", productionProductMain.getUserName()); formPayload.put("workOrderNo", resolveWorkOrderNo(productionProductMain.getProductionOperationTaskId())); formPayload.put("schedulingNum", schedulingNum); formPayload.put("pendingNum", pendingNum); formPayload.put("finishedNum", finishedNum); formPayload.put("scrapQty", scrapQty); formPayload.put("reworkMinorQty", reworkMinorNum); formPayload.put("reworkMajorQty", reworkMajorNum); formPayload.put("reportType", resolveReportTypeLabel(productionProductMain.getReportType())); formPayload.put("workHour", productionProductMain.getWorkHour()); formPayload.put("auditStatus", resolveAuditStatusLabel(productionProductMain.getAuditStatus())); formPayload.put("auditRemark", productionProductMain.getAuditRemark()); formConfig.put("formPayload", formPayload); return formConfig; } private Map buildReportField(String key, String label, String type, Object defaultValue) { Map field = new LinkedHashMap<>(); field.put("key", key); field.put("label", label); field.put("type", type); field.put("required", Boolean.FALSE); field.put("defaultValue", defaultValue); return field; } private String resolveWorkOrderNo(Long productionOperationTaskId) { if (productionOperationTaskId == null) { return null; } ProductionOperationTask task = productionOperationTaskMapper.selectById(productionOperationTaskId); if (task != null && task.getWorkOrderNo() != null) { return task.getWorkOrderNo(); } if (task == null || task.getProductionOrderRoutingOperationId() == null) { return null; } ProductionOrderRoutingOperation routingOperation = productionOrderRoutingOperationMapper.selectById(task.getProductionOrderRoutingOperationId()); if (routingOperation == null || routingOperation.getProductionOrderId() == null) { return null; } ProductionOrder productionOrder = productionOrderMapper.selectById(routingOperation.getProductionOrderId()); return productionOrder == null ? null : productionOrder.getNpsNo(); } private String resolveReportTypeLabel(Integer reportType) { if (reportType == null) { return "—"; } switch (reportType) { case 0: return "合格"; case 1: return "轻微返工"; case 2: return "严重返工"; case 3: return "报废"; default: return "—"; } } private String resolveAuditStatusLabel(Integer auditStatus) { if (auditStatus == null) { return "待审核"; } switch (auditStatus) { case 0: return "待审核"; case 1: return "审核通过"; case 2: return "审核不通过"; default: return "—"; } } private String resolveLatestStockInBatchNo(Long recordId, String recordType, Long productModelId, String stockType) { if (recordId == null || productModelId == null) { return null; } StockInRecord stockInRecord = stockInRecordService.getOne( Wrappers.lambdaQuery() .eq(StockInRecord::getRecordId, recordId) .eq(StockInRecord::getRecordType, recordType) .eq(StockInRecord::getProductModelId, productModelId) .eq(StockInRecord::getType, stockType) .orderByDesc(StockInRecord::getId) .last("limit 1"), false); if (stockInRecord == null) { throw new ServiceException("未找到对应的入库申请记录"); } return stockInRecord.getBatchNo(); } private void syncOperationParamInputValue(ProductionProductMainDto dto, Long productionOrderRoutingOperationId, Long productionProductMainId) { if (dto == null || productionOrderRoutingOperationId == null || productionProductMainId == null) { return; } List paramList = dto.getProductionOperationParamList(); if (paramList == null || paramList.isEmpty()) { return; } Set sourceParamIdSet = paramList.stream() .filter(Objects::nonNull) .map(ProductionOrderRoutingOperationParam::getId) .filter(Objects::nonNull) .collect(Collectors.toCollection(LinkedHashSet::new)); if (sourceParamIdSet.isEmpty()) { return; } List dbParamList = productionOrderRoutingOperationParamMapper.selectList( Wrappers.lambdaQuery() .in(ProductionOrderRoutingOperationParam::getId, sourceParamIdSet) .eq(ProductionOrderRoutingOperationParam::getProductionOrderRoutingOperationId, productionOrderRoutingOperationId)); if (dbParamList == null || dbParamList.isEmpty()) { return; } Map dbParamMap = dbParamList.stream() .filter(item -> item != null && item.getId() != null) .collect(Collectors.toMap(ProductionOrderRoutingOperationParam::getId, item -> item, (left, right) -> left)); for (ProductionOrderRoutingOperationParam param : paramList) { if (param == null || param.getId() == null) { continue; } ProductionOrderRoutingOperationParam dbParam = dbParamMap.get(param.getId()); if (dbParam == null) { throw new ServiceException("工序参数不存在或不属于当前工单工序,ID=" + param.getId()); } productionOrderRoutingOperationParamMapper.insert(buildReportParamSnapshot(dbParam, param.getInputValue(), productionProductMainId)); } } private ProductionOrderRoutingOperationParam buildReportParamSnapshot(ProductionOrderRoutingOperationParam source, String inputValue, Long productionProductMainId) { ProductionOrderRoutingOperationParam target = new ProductionOrderRoutingOperationParam(); target.setProductionOrderId(source.getProductionOrderId()); target.setTechnologyRoutingOperationParamId(source.getTechnologyRoutingOperationParamId()); target.setParamCode(source.getParamCode()); target.setParamName(source.getParamName()); target.setParamType(source.getParamType()); target.setParamFormat(source.getParamFormat()); target.setUnit(source.getUnit()); target.setIsRequired(source.getIsRequired()); target.setRemark(source.getRemark()); target.setParamId(source.getParamId()); target.setTechnologyOperationId(source.getTechnologyOperationId()); target.setTechnologyOperationParamId(source.getTechnologyOperationParamId()); target.setStandardValue(source.getStandardValue()); target.setInputValue(inputValue); target.setProductionOrderRoutingOperationId(source.getProductionOrderRoutingOperationId()); target.setProductionProductMainId(productionProductMainId); return target; } /** * 需求:生产报工只区分产品大类(小提琴/大提琴/贝斯),不再区分零件。 * 从产出产品自身名称开始沿产品树向上做关键字匹配,命中即取对应规范大类;未命中返回 null。 * 产品树顶层为“成品/半成品/原料”等库存分类,不能直接当作大类,因此必须自底向上取第一个命中的层级。 */ private String resolveMajorCategory(ProductModel productModel) { if (productModel == null || productModel.getProductId() == null) { return null; } Product product = productMapper.selectById(productModel.getProductId()); Set visited = new LinkedHashSet<>(); while (product != null && visited.add(product.getId())) { String category = matchMajorCategory(product.getProductName()); if (category != null) { return category; } if (product.getParentId() == null || product.getParentId() == 0L) { break; } product = productMapper.selectById(product.getParentId()); } return null; } static String matchMajorCategory(String productName) { if (productName == null || productName.isEmpty()) { return null; } // 琴盒/琴套等配件名称可能带“贝司”等字样(如 SA贝司套),排除后避免误归大类。 if (productName.contains("琴盒") || productName.contains("琴套") || productName.contains("弓盒")) { return null; } for (Map.Entry> entry : MAJOR_CATEGORY_PATTERNS.entrySet()) { for (String alias : entry.getValue()) { if (productName.contains(alias)) { return entry.getKey(); } } } return null; } private Boolean removeProductMainByProductionTask(ProductionProductMain productionProductMain) { // 按生产任务回滚并删除报工主记录 List qualityInspects = qualityInspectMapper.selectList( Wrappers.lambdaQuery().eq(QualityInspect::getProductMainId, productionProductMain.getId())); // 参数与前置条件校验 if (qualityInspects.size() > 0) { List qualityUnqualifieds = qualityUnqualifiedMapper.selectList( Wrappers.lambdaQuery() // 遍历处理数据并组装结果 .in(QualityUnqualified::getInspectId, qualityInspects.stream().map(QualityInspect::getId).collect(Collectors.toList()))); if (qualityUnqualifieds.size() > 0 && qualityUnqualifieds.get(0).getInspectState() == 1) { throw new ServiceException("该条报工已经不合格处理了,不允许删除"); } } // 报工计数区分直接产生的返工待处理单:已处理的不允许删除报工,未处理的随报工一并删除。 List reworkUnqualifieds = qualityUnqualifiedMapper.selectList( Wrappers.lambdaQuery() .eq(QualityUnqualified::getProductMainId, productionProductMain.getId())); if (reworkUnqualifieds.stream().anyMatch(q -> Integer.valueOf(1).equals(q.getInspectState()))) { throw new ServiceException("该条报工的返工零件已做不合格处理,不允许删除"); } ProductionProductOutput productionProductOutput = productionProductOutputMapper.selectList( Wrappers.lambdaQuery() .eq(ProductionProductOutput::getProductionProductMainId, productionProductMain.getId())) .stream().findFirst().orElse(null); productionAccountMapper.delete(new LambdaQueryWrapper() .eq(ProductionAccount::getProductionProductMainId, productionProductMain.getId())); ProductionOperationTask productionOperationTask = productionOperationTaskMapper.selectById(productionProductMain.getProductionOperationTaskId()); if (productionOperationTask != null && productionProductOutput != null) { BigDecimal reportQuantity = defaultDecimal(productionProductOutput.getQuantity()); productionOperationTask.setCompleteQuantity(defaultDecimal(productionOperationTask.getCompleteQuantity()).subtract(reportQuantity)); productionOperationTask.setActualEndTime(null); if (defaultDecimal(productionOperationTask.getCompleteQuantity()).compareTo(BigDecimal.ZERO) <= 0) { productionOperationTask.setCompleteQuantity(BigDecimal.ZERO); productionOperationTask.setActualStartTime(null); productionOperationTask.setStatus(2); } else { productionOperationTask.setStatus(3); } // 持久化或输出处理结果 productionOperationTaskMapper.updateById(productionOperationTask); ProductionOrder productionOrder = productionOrderMapper.selectById(productionOperationTask.getProductionOrderId()); ProductionOrderRoutingOperation routingOperation = productionOrderRoutingOperationMapper.selectById(productionOperationTask.getProductionOrderRoutingOperationId()); if (productionOrder != null && routingOperation != null) { // 只有最后一道工序的报工才会影响生产订单完工数量。 List routingOperationList = productionOrderRoutingOperationMapper.selectList( Wrappers.lambdaQuery() .eq(ProductionOrderRoutingOperation::getOrderRoutingId, routingOperation.getOrderRoutingId()) .eq(ProductionOrderRoutingOperation::getProductionOrderId, routingOperation.getProductionOrderId())); boolean isLastOperation = routingOperation.getDragSort() != null && routingOperation.getDragSort().equals(routingOperationList.size()); if (isLastOperation) { BigDecimal newCompleteQty = defaultDecimal(productionOrder.getCompleteQuantity()).subtract(reportQuantity); productionOrder.setCompleteQuantity(newCompleteQty.compareTo(BigDecimal.ZERO) < 0 ? BigDecimal.ZERO : newCompleteQty); productionOrder.setEndTime(null); } if (defaultDecimal(productionOrder.getCompleteQuantity()).compareTo(BigDecimal.ZERO) <= 0) { productionOrder.setStartTime(null); productionOrder.setStatus(ProductOrderStatusEnum.WAIT.getCode()); } else { productionOrder.setStatus(ProductOrderStatusEnum.RUNNING.getCode()); } productionOrderMapper.updateById(productionOrder); } } qualityUnqualifiedMapper.delete(new LambdaQueryWrapper() .eq(QualityUnqualified::getProductMainId, productionProductMain.getId())); 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::getProductionProductMainId, productionProductMain.getId())); productionProductInputMapper.delete(new LambdaQueryWrapper() .eq(ProductionProductInput::getProductionProductMainId, productionProductMain.getId())); productionOrderRoutingOperationParamMapper.delete( Wrappers.lambdaQuery() .eq(ProductionOrderRoutingOperationParam::getProductionProductMainId, productionProductMain.getId())); stockUtils.deleteStockInRecord(productionProductMain.getId(), StockInQualifiedRecordTypeEnum.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; } private String generateProductNo() { // 生成下一个生产产品编号 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 (resultList != null && !resultList.isEmpty()) { 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; } } } } return String.format("%s%03d", datePrefix, sequenceNumber); } private BigDecimal defaultDecimal(BigDecimal value) { // 将空数量兜底为0,避免空指针异常 return value == null ? BigDecimal.ZERO : value; } private Long resolveTaskId(ProductionProductMainDto dto) { // 从入参中解析生产工单ID并校验 if (dto == null) { return null; } return dto.getProductionOperationTaskId(); } private boolean isCountingWageReport(ProductionProductMain productionProductMain) { return productionProductMain != null && productionProductMain.getAuditStatus() != null && productionProductMain.getAuditStatus() == 1 && productionProductMain.getReportType() != null && (productionProductMain.getReportType() == 0 || productionProductMain.getReportType() == 1); } private boolean isExcludedFromWage(ProductionProductMain productionProductMain) { return productionProductMain != null && productionProductMain.getReportType() != null && productionProductMain.getReportType() != 0 && productionProductMain.getReportType() != 1; } @Override public Boolean auditProductMain(ProductionProductMainDto productionProductMainDto) { if (productionProductMainDto == null || productionProductMainDto.getId() == null) { throw new ServiceException("参数错误"); } ProductionProductMain productionProductMain = productionProductMainMapper.selectById(productionProductMainDto.getId()); if (productionProductMain == null) { throw new ServiceException("报工不存在"); } Integer auditStatus = productionProductMainDto.getAuditStatus(); if (auditStatus == null) { throw new ServiceException("审核状态不能为空"); } productionProductMain.setAuditStatus(auditStatus); productionProductMain.setAuditRemark(productionProductMainDto.getAuditRemark()); productionProductMainMapper.updateById(productionProductMain); productionAccountMapper.delete(new LambdaQueryWrapper() .eq(ProductionAccount::getProductionProductMainId, productionProductMain.getId())); if (auditStatus == 1 && !isExcludedFromWage(productionProductMain)) { rebuildProductionAccount(productionProductMain); } return true; } private void rebuildProductionAccount(ProductionProductMain productionProductMain) { if (productionProductMain == null || productionProductMain.getId() == null) { return; } ProductionProductOutput output = productionProductOutputMapper.selectOne( Wrappers.lambdaQuery() .eq(ProductionProductOutput::getProductionProductMainId, productionProductMain.getId()) .last("limit 1")); if (output == null) { return; } ProductionOperationTask productionOperationTask = productionOperationTaskMapper.selectById(productionProductMain.getProductionOperationTaskId()); if (productionOperationTask == null) { return; } ProductionOrderRoutingOperation routingOperation = productionOrderRoutingOperationMapper.selectById(productionOperationTask.getProductionOrderRoutingOperationId()); if (routingOperation == null) { return; } ProductionOrder productionOrder = productionOrderMapper.selectById(productionOperationTask.getProductionOrderId()); if (productionOrder == null) { return; } TechnologyRoutingOperation technologyRoutingOperation = technologyRoutingOperationMapper.selectById(routingOperation.getTechnologyRoutingOperationId()); TechnologyOperation technologyOperation = technologyRoutingOperation == null ? null : technologyOperationMapper.selectById(technologyRoutingOperation.getTechnologyOperationId()); SysUser user = userMapper.selectUserById(productionProductMain.getUserId()); BigDecimal productQty = defaultDecimal(output.getQuantity()); if (productQty.compareTo(BigDecimal.ZERO) <= 0) { return; } ProductionAccount productionAccount = new ProductionAccount(); productionAccount.setProductionProductMainId(productionProductMain.getId()); productionAccount.setSchedulingUserId(user == null ? productionProductMain.getUserId() : user.getUserId()); productionAccount.setSchedulingUserName(user == null ? productionProductMain.getUserName() : user.getNickName()); productionAccount.setFinishedNum(productQty); productionAccount.setWorkHours(technologyOperation != null ? technologyOperation.getSalaryQuota() : null); productionAccount.setTechnologyOperationName(technologyOperation == null ? null : technologyOperation.getName()); productionAccount.setSchedulingDate(LocalDateTime.now()); productionAccountMapper.insert(productionAccount); } @Override public ArrayList listMain(List idList) { // 查询主表ID集合 return productionProductMainMapper.listMain(idList); } }