package com.ruoyi.mock.service.impl; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; import com.ruoyi.ai.assistant.Assistant; import com.ruoyi.approve.mapper.ApprovalInstanceMapper; import com.ruoyi.approve.mapper.ApprovalInstanceNodeMapper; import com.ruoyi.approve.mapper.ApprovalRecordMapper; import com.ruoyi.approve.mapper.ApprovalTaskMapper; import com.ruoyi.approve.pojo.ApprovalInstance; import com.ruoyi.approve.pojo.ApprovalInstanceNode; import com.ruoyi.approve.pojo.ApprovalRecord; import com.ruoyi.approve.pojo.ApprovalTask; import com.ruoyi.approve.service.ApprovalInstanceService; import com.ruoyi.basic.mapper.ProductMapper; import com.ruoyi.basic.mapper.ProductModelMapper; import com.ruoyi.basic.pojo.Customer; import com.ruoyi.basic.pojo.Product; import com.ruoyi.basic.pojo.ProductModel; import com.ruoyi.basic.pojo.SupplierManage; import com.ruoyi.basic.service.ICustomerService; import com.ruoyi.basic.service.ISupplierService; import com.ruoyi.common.enums.ReviewStatusEnum; import com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum; import com.ruoyi.common.enums.StockOutQualifiedRecordTypeEnum; import com.ruoyi.common.utils.OrderUtils; import com.ruoyi.mock.dto.DataGenerateRequest; import com.ruoyi.mock.prompt.MockDataPrompt; import com.ruoyi.mock.service.DataGenerateService; import com.ruoyi.mock.vo.DataGenerateResult; import com.ruoyi.mock.vo.DataGenerateResult.ModuleSummary; import com.ruoyi.procurementrecord.utils.StockUtils; import com.ruoyi.production.bean.dto.ProductionOrderPickDto; import com.ruoyi.production.bean.dto.ProductionPlanDto; import com.ruoyi.production.bean.dto.ProductionProductMainDto; import com.ruoyi.production.bean.vo.ProductionBomStructureVo; import com.ruoyi.production.mapper.*; import com.ruoyi.production.pojo.*; import com.ruoyi.production.service.ProductionOrderPickService; import com.ruoyi.production.service.ProductionOrderService; import com.ruoyi.production.service.ProductionPlanService; import com.ruoyi.production.service.ProductionProductMainService; import com.ruoyi.purchase.dto.PurchaseLedgerDto; import com.ruoyi.purchase.pojo.PurchaseLedger; import com.ruoyi.purchase.service.IPurchaseLedgerService; import com.ruoyi.project.system.domain.SysDept; import com.ruoyi.project.system.domain.SysUser; import com.ruoyi.project.system.mapper.SysDeptMapper; import com.ruoyi.project.system.mapper.SysUserDeptMapper; import com.ruoyi.project.system.mapper.SysUserMapper; import com.ruoyi.quality.mapper.QualityInspectMapper; import com.ruoyi.quality.pojo.QualityInspect; import com.ruoyi.quality.pojo.QualityTestStandard; import com.ruoyi.quality.pojo.QualityTestStandardBinding; import com.ruoyi.quality.service.IQualityInspectService; import com.ruoyi.quality.service.IQualityTestStandardService; import com.ruoyi.quality.service.QualityTestStandardBindingService; import com.ruoyi.sales.dto.SalesLedgerDto; import com.ruoyi.sales.dto.ShippingInfoDto; import com.ruoyi.sales.mapper.SalesLedgerMapper; import com.ruoyi.sales.mapper.SalesLedgerProductMapper; import com.ruoyi.sales.mapper.ShippingInfoMapper; import com.ruoyi.sales.mapper.ShippingProductDetailMapper; import com.ruoyi.sales.pojo.SalesLedger; import com.ruoyi.sales.pojo.SalesLedgerProduct; import com.ruoyi.sales.pojo.ShippingInfo; import com.ruoyi.sales.pojo.ShippingProductDetail; import com.ruoyi.sales.service.ISalesLedgerService; import com.ruoyi.sales.service.ShippingInfoService; import com.ruoyi.stock.mapper.StockInRecordMapper; import com.ruoyi.stock.mapper.StockInventoryMapper; import com.ruoyi.stock.mapper.StockOutRecordMapper; import com.ruoyi.stock.pojo.StockInventory; import com.ruoyi.stock.pojo.StockOutRecord; import com.ruoyi.stock.dto.StockInventoryDto; import com.ruoyi.stock.service.StockInRecordService; import com.ruoyi.stock.service.StockOutRecordService; import com.ruoyi.stock.service.StockInventoryService; import com.ruoyi.technology.mapper.TechnologyBomStructureMapper; import com.ruoyi.technology.mapper.TechnologyRoutingMapper; import com.ruoyi.technology.mapper.TechnologyRoutingOperationMapper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.ThreadLocalRandom; import java.util.stream.Collectors; @Slf4j @Service @RequiredArgsConstructor public class DataGenerateServiceImpl implements DataGenerateService { private final Assistant assistant; private final ProductMapper productMapper; private final ProductModelMapper productModelMapper; private final ICustomerService customerService; private final ISalesLedgerService salesLedgerService; private final ISupplierService supplierService; private final IPurchaseLedgerService purchaseLedgerService; private final IQualityTestStandardService qualityTestStandardService; private final QualityTestStandardBindingService qualityTestStandardBindingService; private final ProductionPlanService productionPlanService; private final ProductionOrderService productionOrderService; private final StockInventoryService stockInventoryService; // 采购完整流程需要的service private final ApprovalInstanceService approvalInstanceService; private final ApprovalInstanceMapper approvalInstanceMapper; private final ApprovalInstanceNodeMapper approvalInstanceNodeMapper; private final ApprovalTaskMapper approvalTaskMapper; private final ApprovalRecordMapper approvalRecordMapper; private final IQualityInspectService qualityInspectService; private final QualityInspectMapper qualityInspectMapper; private final StockInRecordService stockInRecordService; private final StockInRecordMapper stockInRecordMapper; // 销售完整流程需要的service/mapper private final SalesLedgerMapper salesLedgerMapper; private final SalesLedgerProductMapper salesLedgerProductMapper; private final ProductionPlanMapper productionPlanMapper; private final ProductionOrderMapper productionOrderMapper; private final ProductionOperationTaskMapper productionOperationTaskMapper; private final ProductionProductMainService productionProductMainService; private final ShippingInfoService shippingInfoService; private final ShippingInfoMapper shippingInfoMapper; private final ShippingProductDetailMapper shippingProductDetailMapper; private final StockUtils stockUtils; private final StockOutRecordMapper stockOutRecordMapper; private final StockOutRecordService stockOutRecordService; private final StockInventoryMapper stockInventoryMapper; // 工艺数据检测 private final TechnologyRoutingMapper technologyRoutingMapper; private final TechnologyRoutingOperationMapper technologyRoutingOperationMapper; private final TechnologyBomStructureMapper technologyBomStructureMapper; // 领料 private final ProductionOrderPickService productionOrderPickService; private final ProductionOrderBomMapper productionOrderBomMapper; private final ProductionBomStructureMapper productionBomStructureMapper; // 用户/角色查询 private final SysUserMapper sysUserMapper; private final SysDeptMapper sysDeptMapper; private final SysUserDeptMapper sysUserDeptMapper; @Override public DataGenerateResult generate(DataGenerateRequest request) { DataGenerateResult result = new DataGenerateResult(); List summaries = new ArrayList<>(); List errors = new ArrayList<>(); int totalGenerated = 0; try { // 构建产品规格分类:通过Product树根节点判断成品/原材料 Map productModelIdToCategory = buildProductModelCategoryMap(); List productModelIds = new ArrayList<>(productModelIdToCategory.keySet()); // 加载已有客户/供应商(不新增,使用已有数据) Map customerNameToId = loadExistingCustomers(); Map supplierNameToId = loadExistingSuppliers(); List existingCustomerNames = new ArrayList<>(customerNameToId.keySet()); List existingSupplierNames = new ArrayList<>(supplierNameToId.keySet()); // 加载系统用户信息(昵称、角色、部门) List systemUsers = loadSystemUsersWithDetail(); String systemPrompt = MockDataPrompt.buildSystemPrompt(); String userMessage = MockDataPrompt.buildUserMessage( request.getModules(), request.getIndustries(), request.getCountMin(), request.getCountMax(), request.getDateStart(), request.getDateEnd(), request.getAdditionalInfo(), productModelIdToCategory, systemUsers, existingCustomerNames, existingSupplierNames); String fullPrompt = systemPrompt + "\n\n" + userMessage; log.info("调用AI生成模拟数据, modules={}, industries={}", request.getModules(), request.getIndustries()); String llmResponse = assistant.chat(fullPrompt); log.info("AI返回数据长度: {}", llmResponse != null ? llmResponse.length() : 0); if (llmResponse == null || llmResponse.isBlank()) { result.setStatus("FAILED"); errors.add("AI未返回任何数据"); result.setErrors(errors); return result; } List entities = parseJsonResponse(llmResponse); if (entities.isEmpty()) { result.setStatus("FAILED"); errors.add("无法解析AI返回的数据"); result.setErrors(errors); return result; } // 修正AI生成的日期到指定时间范围内 String dateStart = request.getDateStart(); String dateEnd = request.getDateEnd(); if (dateStart != null && dateEnd != null) { fixDatesInRange(entities, dateStart, dateEnd); } Map> grouped = entities.stream() .collect(Collectors.groupingBy(e -> e.getString("entity"))); // Tier 0: 基础数据(客户和供应商不自动生成,使用已有数据) if (grouped.containsKey("qualityTestStandard")) { ModuleSummary s = createQualityStandards(grouped.get("qualityTestStandard")); summaries.add(s); totalGenerated += s.getSuccessCount(); } // Tier 1: 业务单据 if (grouped.containsKey("salesLedger")) { ModuleSummary s = createSalesLedgers(grouped.get("salesLedger"), customerNameToId, request.getDateStart()); summaries.add(s); totalGenerated += s.getSuccessCount(); } if (grouped.containsKey("purchaseLedger")) { ModuleSummary s = createPurchaseLedgers(grouped.get("purchaseLedger"), supplierNameToId, request.getAdditionalInfo(), request.getDateStart(), request.getDateEnd()); summaries.add(s); totalGenerated += s.getSuccessCount(); } if (grouped.containsKey("productionPlan")) { ModuleSummary s = createProductionPlans(grouped.get("productionPlan")); summaries.add(s); totalGenerated += s.getSuccessCount(); } // Tier 2 if (grouped.containsKey("productionOrder")) { ModuleSummary s = createProductionOrders(grouped.get("productionOrder")); summaries.add(s); totalGenerated += s.getSuccessCount(); } if (grouped.containsKey("qualityTestStandardBinding")) { ModuleSummary s = createQualityBindings(grouped.get("qualityTestStandardBinding")); summaries.add(s); totalGenerated += s.getSuccessCount(); } if (grouped.containsKey("stockInventory")) { ModuleSummary s = createStockInventories(grouped.get("stockInventory")); summaries.add(s); totalGenerated += s.getSuccessCount(); } result.setStatus(errors.isEmpty() ? "SUCCESS" : "PARTIAL"); result.setTotalGenerated(totalGenerated); result.setModuleSummaries(summaries); result.setErrors(errors); } catch (Exception e) { log.error("数据生成失败", e); result.setStatus("FAILED"); errors.add("数据生成异常: " + e.getMessage()); result.setErrors(errors); } return result; } private List parseJsonResponse(String response) { try { String trimmed = response.trim(); if (trimmed.startsWith("```")) { int start = trimmed.indexOf("\n"); int end = trimmed.lastIndexOf("```"); if (start > 0 && end > start) { trimmed = trimmed.substring(start + 1, end).trim(); } } return JSON.parseArray(trimmed, JSONObject.class); } catch (Exception e) { log.warn("JSON解析失败,尝试提取数组片段: {}", e.getMessage()); int start = response.indexOf('['); int end = response.lastIndexOf(']'); if (start >= 0 && end > start) { return JSON.parseArray(response.substring(start, end + 1), JSONObject.class); } return List.of(); } } /** * 修正AI生成的日期到指定时间范围内。 * 遍历所有实体的所有字段,如果发现日期字符串(yyyy-MM-dd格式)超出范围,替换为范围内的随机日期。 */ private void fixDatesInRange(List entities, String dateStart, String dateEnd) { LocalDate start; LocalDate end; try { start = LocalDate.parse(dateStart); end = LocalDate.parse(dateEnd); } catch (Exception e) { log.warn("日期范围解析失败: {} ~ {}", dateStart, dateEnd); return; } long daysBetween = ChronoUnit.DAYS.between(start, end); if (daysBetween <= 0) { daysBetween = 1; } // 所有已知的日期字段名 java.util.Set dateFields = java.util.Set.of( "entryDate", "executionDate", "deliveryDate", "requiredDate", "promisedDeliveryDate", "planCompleteTime", "maintenanceTime", "checkTime", "registerDate" ); for (JSONObject entity : entities) { for (String key : dateFields) { String val = entity.getString(key); if (val != null && val.matches("\\d{4}-\\d{2}-\\d{2}")) { try { LocalDate d = LocalDate.parse(val); if (d.isBefore(start) || d.isAfter(end)) { LocalDate fixed = start.plusDays(ThreadLocalRandom.current().nextLong(daysBetween + 1)); entity.put(key, fixed.toString()); log.debug("修正日期 {}: {} -> {}", key, val, fixed); } } catch (Exception ignored) { } } else if (val != null && !val.matches("\\d{4}-\\d{2}-\\d{2}")) { // AI返回了无效日期格式,直接替换为范围内的随机日期 LocalDate fixed = start.plusDays(ThreadLocalRandom.current().nextLong(daysBetween + 1)); entity.put(key, fixed.toString()); log.debug("修正无效日期 {}: {} -> {}", key, val, fixed); } } // 修正合同编号中的日期: CG-20230601-001 -> CG-YYYYMMDD-001 for (String key : java.util.Set.of("purchaseContractNumber", "salesContractNo", "standardNo", "batchNo")) { String val = entity.getString(key); if (val != null && val.matches(".*\\d{8}.*")) { String datePart = val.replaceAll(".*?(\\d{8}).*", "$1"); try { // 验证提取到的是有效日期 String dateStr = datePart.substring(0, 4) + "-" + datePart.substring(4, 6) + "-" + datePart.substring(6, 8); LocalDate d = LocalDate.parse(dateStr); if (d.isBefore(start) || d.isAfter(end)) { LocalDate fixed = start.plusDays(ThreadLocalRandom.current().nextLong(daysBetween + 1)); String newDatePart = fixed.format(java.time.format.DateTimeFormatter.BASIC_ISO_DATE); String newVal = val.replace(datePart, newDatePart); entity.put(key, newVal); log.debug("修正编号 {}: {} -> {}", key, val, newVal); } } catch (Exception ignored) { } } } } } // ---- Tier 0: 基础数据 ---- /** * 从数据库加载已有客户名称→ID映射 */ private Map loadExistingCustomers() { Map map = new HashMap<>(); List customers = customerService.list(); for (Customer c : customers) { if (c.getCustomerName() != null) { map.put(c.getCustomerName(), c.getId()); } } return map; } /** * 从数据库加载已有供应商名称→ID映射 */ private Map loadExistingSuppliers() { Map map = new HashMap<>(); List suppliers = supplierService.list(); for (SupplierManage s : suppliers) { if (s.getSupplierName() != null) { map.put(s.getSupplierName(), s.getId()); } } return map; } /** * 加载系统用户信息(含角色和部门名称) */ private List loadSystemUsersWithDetail() { return sysUserMapper.selectUserListWithDetail(); } /** * 构建产品规格ID→顶级产品分类名称的映射 * 通过Product树向上找到根节点,判断"成品"/"原材料"/"半成品" */ private Map buildProductModelCategoryMap() { // 加载所有Product节点 List allProducts = productMapper.selectList(null); Map productMap = allProducts.stream() .collect(Collectors.toMap(Product::getId, p -> p, (a, b) -> a)); // 对每个Product,沿parentId向上找到根节点名称 Map productIdToRootName = new HashMap<>(); for (Product p : allProducts) { if (productIdToRootName.containsKey(p.getId())) continue; // 向上遍历找根 List chain = new ArrayList<>(); Long currentId = p.getId(); while (currentId != null) { chain.add(currentId); Product current = productMap.get(currentId); if (current == null || current.getParentId() == null) break; currentId = current.getParentId(); } // 根节点的productName就是分类名 Product root = productMap.get(chain.get(chain.size() - 1)); String rootName = root != null ? root.getProductName() : "其他"; for (Long id : chain) { productIdToRootName.put(id, rootName); } } // 对每个ProductModel,通过productId找到其所属分类 List allModels = productModelMapper.selectList(null); Map result = new HashMap<>(); for (ProductModel m : allModels) { String category = m.getProductId() != null ? productIdToRootName.getOrDefault(m.getProductId(), "其他") : "其他"; result.put(m.getId(), category); } return result; } private ModuleSummary createCustomers(List items, Map nameToId) { int success = 0, fail = 0; for (JSONObject item : items) { try { Customer c = new Customer(); c.setCustomerName(item.getString("customerName")); c.setCustomerType(item.getString("customerType")); c.setContactPerson(item.getString("contactPerson")); c.setContactPhone(item.getString("contactPhone")); c.setCompanyAddress(item.getString("companyAddress")); c.setCompanyPhone(item.getString("companyPhone")); c.setTaxpayerIdentificationNumber(item.getString("taxpayerIdentificationNumber")); c.setMaintainer(item.getString("maintainer")); if (item.containsKey("maintenanceTime")) { String mtStr = item.getString("maintenanceTime"); if (mtStr != null && mtStr.matches("\\d{4}-\\d{2}-\\d{2}")) { c.setMaintenanceTime(java.sql.Date.valueOf(mtStr)); } } else { c.setMaintenanceTime(new java.sql.Date(System.currentTimeMillis())); } if (item.containsKey("bankAccount")) { c.setBankAccount(item.getString("bankAccount")); } if (item.containsKey("basicBankAccount")) { c.setBasicBankAccount(item.getString("basicBankAccount")); } if (item.containsKey("bankCode")) { c.setBankCode(item.getString("bankCode")); } customerService.insertCustomer(c); if (c.getId() != null) { nameToId.put(c.getCustomerName(), c.getId()); } success++; } catch (Exception e) { log.warn("创建客户失败: {}", e.getMessage()); fail++; } } return summary("sales", "客户", items.size(), success, fail); } private ModuleSummary createSuppliers(List items, Map nameToId) { int success = 0, fail = 0; for (JSONObject item : items) { try { SupplierManage s = new SupplierManage(); s.setSupplierName(item.getString("supplierName")); s.setSupplierType(item.getString("supplierType")); s.setContactUserName(item.getString("contactUserName")); s.setContactUserPhone(item.getString("contactUserPhone")); s.setCompanyAddress(item.getString("companyAddress")); s.setCompanyPhone(item.getString("companyPhone")); s.setTaxpayerIdentificationNum(item.getString("taxpayerIdentificationNum")); s.setBankAccountName(item.getString("bankAccountName")); s.setBankAccountNum(item.getString("bankAccountNum")); if (item.containsKey("isWhite")) { s.setIsWhite(item.getInteger("isWhite")); } supplierService.saveSupplier(s); if (s.getId() != null) { nameToId.put(s.getSupplierName(), s.getId()); } success++; } catch (Exception e) { log.warn("创建供应商失败: {}", e.getMessage()); fail++; } } return summary("purchase", "供应商", items.size(), success, fail); } private ModuleSummary createQualityStandards(List items) { int success = 0, fail = 0; for (JSONObject item : items) { try { QualityTestStandard std = new QualityTestStandard(); std.setStandardNo(item.getString("standardNo")); std.setStandardName(item.getString("standardName")); std.setInspectType(item.getInteger("inspectType")); std.setRemark(item.getString("remark")); qualityTestStandardService.save(std); success++; } catch (Exception e) { log.warn("创建检测标准失败: {}", e.getMessage()); fail++; } } return summary("quality", "检测标准", items.size(), success, fail); } // ---- Tier 1: 业务单据 ---- private ModuleSummary createSalesLedgers(List items, Map customerNameToId, String dateStart) { int success = 0, fail = 0; for (JSONObject item : items) { try { String customerName = item.getString("customerName"); Long customerId = customerNameToId.get(customerName); SalesLedgerDto dto = new SalesLedgerDto(); dto.setCustomerId(customerId); dto.setCustomerName(customerName); dto.setProjectName(item.getString("projectName")); dto.setSalesman(item.getString("salesman")); dto.setPaymentMethod(item.getString("paymentMethod")); dto.setType(item.getInteger("type")); // 录入人:优先用AI返回的,否则从"销售"角色/部门随机选 String entryPerson = item.getString("entryPerson"); if (entryPerson == null || entryPerson.isBlank()) { SysUser salesUser = randomUserByKeyword("销售"); entryPerson = salesUser != null ? salesUser.getNickName() : "系统"; } dto.setEntryPerson(entryPerson); // 业务员:优先用AI返回的,否则从"销售"角色/部门随机选 String salesman = dto.getSalesman(); if (salesman == null || salesman.isBlank()) { SysUser salesUser = randomUserByKeyword("销售"); salesman = salesUser != null ? salesUser.getNickName() : "系统"; dto.setSalesman(salesman); } // customerId为null会导致"客户不存在"异常,跳过 if (customerId == null) { log.warn("跳过销售台账,客户[{}]不存在", customerName); fail++; continue; } LocalDate entryDate = null; if (item.containsKey("entryDate")) { String entryDateStr = item.getString("entryDate"); if (entryDateStr != null && entryDateStr.matches("\\d{4}-\\d{2}-\\d{2}")) { entryDate = LocalDate.parse(entryDateStr); dto.setEntryDate(java.sql.Date.valueOf(entryDateStr)); } else { dto.setEntryDate(java.sql.Date.valueOf(dateStart)); } } if (item.containsKey("executionDate")) { String execDateStr = item.getString("executionDate"); if (execDateStr != null && execDateStr.matches("\\d{4}-\\d{2}-\\d{2}")) { dto.setExecutionDate(LocalDate.parse(execDateStr)); } else { dto.setExecutionDate(LocalDate.parse(dateStart)); } } if (item.containsKey("deliveryDate")) { String delDateStr = item.getString("deliveryDate"); if (delDateStr != null && delDateStr.matches("\\d{4}-\\d{2}-\\d{2}")) { dto.setDeliveryDate(LocalDate.parse(delDateStr)); } else { dto.setDeliveryDate(LocalDate.parse(dateStart)); } } JSONArray productData = item.getJSONArray("productData"); boolean hasProduction = false; if (productData != null) { List products = new ArrayList<>(); for (int i = 0; i < productData.size(); i++) { JSONObject pd = productData.getJSONObject(i); SalesLedgerProduct slp = new SalesLedgerProduct(); slp.setProductId(pd.getLong("productId")); slp.setProductModelId(pd.getLong("productModelId")); slp.setQuantity(pd.getBigDecimal("quantity")); slp.setTaxInclusiveUnitPrice(pd.getBigDecimal("taxInclusiveUnitPrice")); slp.setTaxInclusiveTotalPrice(pd.getBigDecimal("taxInclusiveTotalPrice")); slp.setTaxRate(pd.getBigDecimal("taxRate")); slp.setUnit(pd.getString("unit")); slp.setType(pd.getInteger("type")); if (pd.containsKey("taxExclusiveTotalPrice")) { slp.setTaxExclusiveTotalPrice(pd.getBigDecimal("taxExclusiveTotalPrice")); } else if (pd.getBigDecimal("taxInclusiveTotalPrice") != null && pd.getBigDecimal("taxRate") != null) { slp.setTaxExclusiveTotalPrice( pd.getBigDecimal("taxInclusiveTotalPrice").divide( BigDecimal.ONE.add(pd.getBigDecimal("taxRate").divide(new BigDecimal("100"), 4, RoundingMode.HALF_UP)), 2, RoundingMode.HALF_UP)); } else { slp.setTaxExclusiveTotalPrice(BigDecimal.ZERO); } slp.setIsProduction(true); products.add(slp); hasProduction = true; } dto.setProductData(products); } salesLedgerService.addOrUpdateSalesLedger(dto); // addOrUpdateSalesLedger内部会自动生成合同号并insert,但dto上没有回写 // 直接用dto的entryDate和customerId查最新创建的记录 SalesLedger savedLedger = salesLedgerMapper.selectOne( new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() .eq(SalesLedger::getCustomerId, dto.getCustomerId()) .orderByDesc(SalesLedger::getId) .last("limit 1")); if (savedLedger != null && hasProduction) { processSalesFullFlow(savedLedger, entryDate); } success++; } catch (Exception e) { log.warn("创建销售台账失败: {}", e.getMessage(), e); fail++; } } return summary("sales", "销售台账", items.size(), success, fail); } // ==================== 销售完整流程 ==================== /** * 销售完整流程: 生产计划合并下发 → 领料 → 报工 → 质检(可选) → 入库 → 发货 → 出库 * 时间以销售录入日期为基准,各环节递推0-3天 */ private void processSalesFullFlow(SalesLedger salesLedger, LocalDate entryDate) { try { LocalDate baseDate = entryDate != null ? entryDate : LocalDate.now(); // 1. 查询销售台账产品(含生产计划) List products = salesLedgerProductMapper.selectList( new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() .eq(SalesLedgerProduct::getSalesLedgerId, salesLedger.getId()) .eq(SalesLedgerProduct::getIsProduction, true)); if (products.isEmpty()) { log.info("销售台账[{}]无需生产,跳过生产流程", salesLedger.getSalesContractNo()); return; } // 检查产品是否具备完整的工艺数据(工艺路线、工序、BOM) List missingTechProductIds = checkTechnologyDataReadiness(products); if (!missingTechProductIds.isEmpty()) { log.warn("销售台账[{}]以下产品缺少工艺数据(工艺路线/工序/BOM),跳过生产流程: {}", salesLedger.getSalesContractNo(), missingTechProductIds); return; } // 2. 生产计划合并下发 → 生产订单 LocalDate planDate = baseDate.plusDays(ThreadLocalRandom.current().nextInt(0, 4)); processProductionCombine(salesLedger, planDate); // 3. 领料(物料不足自动入库后再领) LocalDate pickDate = planDate.plusDays(ThreadLocalRandom.current().nextInt(0, 3)); processMaterialPick(salesLedger, pickDate); // 4. 生产报工 LocalDate reportDate = pickDate.plusDays(ThreadLocalRandom.current().nextInt(1, 4)); processProductionReport(salesLedger, reportDate); // 5. 质检提交 + 入库审批 LocalDate qualityDate = reportDate.plusDays(ThreadLocalRandom.current().nextInt(0, 4)); processSalesQualityAndStockIn(salesLedger, qualityDate); // 6. 发货 + 发货审批 + 出库审批 LocalDate shipDate = qualityDate.plusDays(ThreadLocalRandom.current().nextInt(0, 4)); processSalesShipping(salesLedger, shipDate); log.info("销售台账[{}]完整流程处理完毕", salesLedger.getSalesContractNo()); } catch (Exception e) { log.warn("销售完整流程处理失败[{}]: {}", salesLedger.getSalesContractNo(), e.getMessage()); } } /** * 查询角色或部门含指定关键字的用户,随机返回一个 */ private SysUser randomUserByKeyword(String keyword) { try { List userIds = new ArrayList<>(sysUserMapper.getUserByRole(keyword)); List depts = sysDeptMapper.selectList( new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() .like(SysDept::getDeptName, keyword) .eq(SysDept::getDelFlag, "0") .eq(SysDept::getStatus, "0")); if (!depts.isEmpty()) { List deptIds = depts.stream().map(SysDept::getDeptId).collect(Collectors.toList()); List deptUserIds = sysUserDeptMapper.selectDistinctUserIdsByDeptIds(deptIds); userIds.addAll(deptUserIds); } if (userIds.isEmpty()) { return null; } List distinctIds = userIds.stream().distinct().collect(Collectors.toList()); Long randomId = distinctIds.get(ThreadLocalRandom.current().nextInt(distinctIds.size())); return sysUserMapper.selectUserById(randomId); } catch (Exception e) { log.warn("查询[{}]角色/部门用户失败: {}", keyword, e.getMessage()); return null; } } /** * 查询角色或部门含"生产"的用户,随机返回一个 */ private SysUser randomProductionUser() { return randomUserByKeyword("生产"); } /** * 检查产品是否具备完整的工艺数据(工艺路线、工序、BOM) * @return 缺少工艺数据的产品规格ID列表(空列表表示全部就绪) */ private List checkTechnologyDataReadiness(List products) { List missingIds = new ArrayList<>(); for (SalesLedgerProduct slp : products) { Long productModelId = slp.getProductModelId(); if (productModelId == null) continue; // 1. 检查工艺路线 List routings = technologyRoutingMapper.selectList( new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() .eq(com.ruoyi.technology.pojo.TechnologyRouting::getProductModelId, productModelId)); if (routings.isEmpty()) { log.warn("产品规格[{}]缺少工艺路线", productModelId); missingIds.add(productModelId); continue; } Long routingId = routings.get(0).getId(); // 2. 检查工艺路线是否有关联工序 List routingOps = technologyRoutingOperationMapper.selectList( new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() .eq(com.ruoyi.technology.pojo.TechnologyRoutingOperation::getTechnologyRoutingId, routingId)); if (routingOps.isEmpty()) { log.warn("产品规格[{}]工艺路线[{}]缺少工序", productModelId, routingId); missingIds.add(productModelId); continue; } // 3. 检查BOM(可选但有更好) com.ruoyi.technology.pojo.TechnologyRouting routing = routings.get(0); if (routing.getBomId() != null) { List bomStructures = technologyBomStructureMapper.selectList( new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() .eq(com.ruoyi.technology.pojo.TechnologyBomStructure::getBomId, routing.getBomId())); if (bomStructures.isEmpty()) { log.warn("产品规格[{}]BOM[{}]缺少产品结构", productModelId, routing.getBomId()); missingIds.add(productModelId); } } } return missingIds; } /** * 生产计划合并下发 → 生产订单(按productModelId分组,同型号合并下发) */ private void processProductionCombine(SalesLedger salesLedger, LocalDate planDate) { try { List plans = productionPlanMapper.selectList( new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() .eq(ProductionPlan::getSalesLedgerId, salesLedger.getId()) .and(w -> w.eq(ProductionPlan::getStatus, 0).or().isNull(ProductionPlan::getStatus))); if (plans.isEmpty()) { log.info("销售台账[{}]无待下发生产计划", salesLedger.getSalesContractNo()); return; } // 按productModelId分组,同型号才能合并下发;productModelId为null的单独逐个下发 Map> grouped = plans.stream() .filter(p -> p.getProductModelId() != null) .collect(Collectors.groupingBy(ProductionPlan::getProductModelId)); for (Map.Entry> entry : grouped.entrySet()) { try { List sameModelPlans = entry.getValue(); BigDecimal totalQty = sameModelPlans.stream() .map(p -> Optional.ofNullable(p.getQtyRequired()).orElse(BigDecimal.ZERO)) .reduce(BigDecimal.ZERO, BigDecimal::add); ProductionPlanDto combineDto = new ProductionPlanDto(); combineDto.setIds(sameModelPlans.stream().map(ProductionPlan::getId).collect(Collectors.toList())); combineDto.setTotalAssignedQuantity(totalQty); combineDto.setPlanCompleteTime(planDate.plusDays(ThreadLocalRandom.current().nextInt(3, 7))); productionPlanService.combine(combineDto); // 修正生产订单的planCompleteTime List orders = productionOrderMapper.selectList( new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() .like(ProductionOrder::getProductionPlanIds, String.valueOf(sameModelPlans.get(0).getId()))); for (ProductionOrder order : orders) { order.setPlanCompleteTime(planDate.plusDays(ThreadLocalRandom.current().nextInt(3, 7))); order.setStartTime(planDate.atStartOfDay()); productionOrderMapper.updateById(order); } log.info("销售台账[{}]生产计划下发成功, productModelId={}, 计划数: {}", salesLedger.getSalesContractNo(), entry.getKey(), sameModelPlans.size()); } catch (Exception e) { log.warn("销售台账[{}]生产计划下发失败, productModelId={}: {}", salesLedger.getSalesContractNo(), entry.getKey(), e.getMessage()); } } } catch (Exception e) { log.warn("生产计划下发失败[{}]: {}", salesLedger.getSalesContractNo(), e.getMessage()); } } /** * 领料: 查询生产订单的BOM领料清单,检查库存,不足则先自动入库审批后再领料 */ private void processMaterialPick(SalesLedger salesLedger, LocalDate pickDate) { try { List orders = findOrdersForSalesLedger(salesLedger); if (orders.isEmpty()) { log.info("销售台账[{}]无生产订单,跳过领料", salesLedger.getSalesContractNo()); return; } for (ProductionOrder order : orders) { try { // 查询订单BOM领料清单 List pickList = productionOrderService.pick(order.getId()); if (pickList == null || pickList.isEmpty()) { log.info("生产订单[{}]无领料清单", order.getId()); continue; } // 检查库存不足的物料,先自动入库 ensureMaterialStock(pickList, pickDate); // 重新获取领料清单(入库后批号和库存量已更新) pickList = productionOrderService.pick(order.getId()); if (pickList == null || pickList.isEmpty()) { log.info("生产订单[{}]重新获取领料清单为空", order.getId()); continue; } // 执行领料 List pickDtoList = new ArrayList<>(); for (com.ruoyi.production.bean.vo.ProductionOrderPickVo pickVo : pickList) { ProductionOrderPickDto pickDto = new ProductionOrderPickDto(); pickDto.setProductionOrderId(order.getId()); pickDto.setProductModelId(pickVo.getProductModelId()); pickDto.setPickQuantity(pickVo.getDemandedQuantity()); pickDto.setPickType((byte) 1); pickDto.setOperationName(pickVo.getOperationName()); pickDto.setTechnologyOperationId(pickVo.getTechnologyOperationId()); pickDto.setDemandedQuantity(pickVo.getDemandedQuantity()); pickDto.setBom(pickVo.getBom()); // 设置批号(用更新后的批号列表) if (pickVo.getBatchNoList() != null && !pickVo.getBatchNoList().isEmpty()) { pickDto.setBatchNoList(pickVo.getBatchNoList()); pickDto.setBatchNo(pickVo.getBatchNoList().get(0)); } pickDtoList.add(pickDto); } if (!pickDtoList.isEmpty()) { ProductionOrderPickDto batchDto = new ProductionOrderPickDto(); batchDto.setPickList(pickDtoList); productionOrderPickService.savePick(batchDto); log.info("生产订单[{}]领料成功, 领料项数: {}", order.getId(), pickDtoList.size()); } } catch (Exception e) { log.warn("生产订单[{}]领料失败: {}", order.getId(), e.getMessage()); } } } catch (Exception e) { log.warn("领料流程失败[{}]: {}", salesLedger.getSalesContractNo(), e.getMessage()); } } /** * 确保物料库存充足,不足则先自动入库审批 */ private void ensureMaterialStock(List pickList, LocalDate pickDate) { LocalDateTime pickDateTime = pickDate.atTime(9, 0, 0).plusMinutes(ThreadLocalRandom.current().nextInt(0, 480)); String pickDateStr = pickDate.format(DateTimeFormatter.BASIC_ISO_DATE); for (com.ruoyi.production.bean.vo.ProductionOrderPickVo pickVo : pickList) { BigDecimal demanded = pickVo.getDemandedQuantity() != null ? pickVo.getDemandedQuantity() : BigDecimal.ZERO; BigDecimal stockQty = pickVo.getStockQuantity() != null ? pickVo.getStockQuantity() : BigDecimal.ZERO; BigDecimal shortage = demanded.subtract(stockQty); if (shortage.compareTo(BigDecimal.ZERO) <= 0) { continue; // 库存充足 } // 库存不足,自动入库补充 try { StockInventoryDto dto = new StockInventoryDto(); dto.setProductModelId(pickVo.getProductModelId()); dto.setQualitity(shortage); dto.setRecordType(StockInQualifiedRecordTypeEnum.CUSTOMIZATION_STOCK_IN.getCode()); dto.setRecordId(0L); stockInventoryService.addStockInRecordOnly(dto); // 查找刚创建的入库记录 List records = stockInRecordService.list( new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() .eq(com.ruoyi.stock.pojo.StockInRecord::getProductModelId, pickVo.getProductModelId()) .eq(com.ruoyi.stock.pojo.StockInRecord::getApprovalStatus, 0) .orderByDesc(com.ruoyi.stock.pojo.StockInRecord::getId) .last("limit 1")); if (!records.isEmpty()) { com.ruoyi.stock.pojo.StockInRecord record = records.get(0); record.setCreateTime(pickDateTime); record.setBatchNo(fixBatchNoDate(record.getBatchNo(), pickDateStr)); record.setInboundBatches(fixBatchNoDate(record.getInboundBatches(), pickDateStr)); stockInRecordMapper.updateById(record); // 审批通过 stockInRecordService.batchApprove(List.of(record.getId()), ReviewStatusEnum.APPROVED.getCode()); log.info("物料[{}]自动入库审批完成, 入库数量: {}", pickVo.getProductModelId(), shortage); } } catch (Exception e) { log.warn("物料[{}]自动入库失败: {}", pickVo.getProductModelId(), e.getMessage()); } } } /** * 生产报工: 遍历工单逐个报工,从生产角色用户中随机选取报工人 */ private void processProductionReport(SalesLedger salesLedger, LocalDate reportDate) { try { List orders = findOrdersForSalesLedger(salesLedger); if (orders.isEmpty()) { log.info("销售台账[{}]无生产订单", salesLedger.getSalesContractNo()); return; } for (ProductionOrder order : orders) { List tasks = productionOperationTaskMapper.selectList( new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() .eq(ProductionOperationTask::getProductionOrderId, order.getId()) .orderByAsc(ProductionOperationTask::getId)); for (ProductionOperationTask task : tasks) { if (task.getStatus() != null && task.getStatus() >= 3) continue; // 每次报工随机选一个生产用户 SysUser productionUser = randomProductionUser(); Long userId = productionUser != null ? productionUser.getUserId() : 1L; String userName = productionUser != null ? productionUser.getNickName() : "系统"; // 报工 ProductionProductMainDto reportDto = new ProductionProductMainDto(); reportDto.setProductionOperationTaskId(task.getId()); reportDto.setQuantity(task.getPlanQuantity()); reportDto.setScrapQty(BigDecimal.ZERO); reportDto.setUserId(userId); reportDto.setUserName(userName); productionProductMainService.addProductMain(reportDto); // 修正报工时间(重新从DB读取,避免覆盖addProductMain更新的completeQuantity) fixProductionReportTimes(task.getId(), reportDate); } // 修正生产订单时间(重新从DB读取,避免覆盖addProductMain更新的completeQuantity) fixProductionOrderTimes(order.getId(), reportDate); } log.info("销售台账[{}]生产报工完成, 订单数: {}", salesLedger.getSalesContractNo(), orders.size()); } catch (Exception e) { log.warn("生产报工失败[{}]: {}", salesLedger.getSalesContractNo(), e.getMessage()); } } /** * 查找销售台账关联的生产订单 */ private List findOrdersForSalesLedger(SalesLedger salesLedger) { List orders = new ArrayList<>(); List plans = productionPlanMapper.selectList( new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() .eq(ProductionPlan::getSalesLedgerId, salesLedger.getId())); for (ProductionPlan plan : plans) { List po = productionOrderMapper.selectList( new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() .like(ProductionOrder::getProductionPlanIds, String.valueOf(plan.getId()))); orders.addAll(po); } // 去重 return orders.stream() .collect(Collectors.toMap(ProductionOrder::getId, o -> o, (a, b) -> a)) .values().stream().collect(Collectors.toList()); } /** * 修正生产报工相关时间(重新从DB读取task,避免覆盖addProductMain已更新的completeQuantity) */ private void fixProductionReportTimes(Long taskId, LocalDate reportDate) { try { ProductionOperationTask freshTask = productionOperationTaskMapper.selectById(taskId); if (freshTask != null) { freshTask.setActualStartTime(reportDate); freshTask.setActualEndTime(reportDate); freshTask.setStatus(4); productionOperationTaskMapper.updateById(freshTask); } } catch (Exception e) { log.warn("修正生产报工时间失败: {}", e.getMessage()); } } /** * 修正生产订单时间(重新从DB读取order,避免覆盖addProductMain已更新的completeQuantity) */ private void fixProductionOrderTimes(Long orderId, LocalDate reportDate) { try { ProductionOrder freshOrder = productionOrderMapper.selectById(orderId); if (freshOrder != null) { freshOrder.setStartTime(reportDate.minusDays(1).atStartOfDay()); freshOrder.setEndTime(reportDate.plusDays(1).atStartOfDay()); freshOrder.setStatus(3); productionOrderMapper.updateById(freshOrder); } } catch (Exception e) { log.warn("修正生产订单时间失败: {}", e.getMessage()); } } /** * 销售质检提交 + 入库审批 */ private void processSalesQualityAndStockIn(SalesLedger salesLedger, LocalDate qualityDate) { try { LocalDateTime qualityDateTime = qualityDate.atTime(9, 0, 0) .plusMinutes(ThreadLocalRandom.current().nextInt(0, 480)); // 查所有未提交的质检单(inspectType=1过程检验, 2=出厂检验,由生产报工生成) List allUnsubmitted = qualityInspectService.list( new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() .eq(QualityInspect::getInspectState, 0) .in(QualityInspect::getInspectType, java.util.Arrays.asList(1, 2))); for (QualityInspect qi : allUnsubmitted) { qualityInspectService.autoSubmit(qi.getId()); qi.setCreateTime(qualityDateTime); qualityInspectMapper.updateById(qi); } log.info("销售台账[{}]质检提交完成, 质检单数: {}", salesLedger.getSalesContractNo(), allUnsubmitted.size()); // 入库审批:审批所有待审批的入库记录 LocalDate stockDate = qualityDate.plusDays(ThreadLocalRandom.current().nextInt(0, 3)); LocalDateTime stockDateTime = stockDate.atTime(9, 0, 0) .plusMinutes(ThreadLocalRandom.current().nextInt(0, 480)); String stockDateStr = stockDate.format(DateTimeFormatter.BASIC_ISO_DATE); List stockRecords = stockInRecordService.list( new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() .eq(com.ruoyi.stock.pojo.StockInRecord::getApprovalStatus, 0)); List recordIds = stockRecords.stream() .map(com.ruoyi.stock.pojo.StockInRecord::getId) .collect(Collectors.toList()); if (!recordIds.isEmpty()) { for (com.ruoyi.stock.pojo.StockInRecord sr : stockRecords) { sr.setCreateTime(stockDateTime); sr.setBatchNo(fixBatchNoDate(sr.getBatchNo(), stockDateStr)); sr.setInboundBatches(fixBatchNoDate(sr.getInboundBatches(), stockDateStr)); stockInRecordMapper.updateById(sr); } stockInRecordService.batchApprove(recordIds, ReviewStatusEnum.APPROVED.getCode()); for (com.ruoyi.stock.pojo.StockInRecord sr : stockRecords) { StockInventory si = stockInventoryMapper.selectOne( new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() .eq(StockInventory::getProductModelId, sr.getProductModelId()) .eq(StockInventory::getBatchNo, sr.getBatchNo())); if (si != null) { si.setBatchNo(fixBatchNoDate(si.getBatchNo(), stockDateStr)); stockInventoryMapper.updateById(si); } } log.info("销售台账[{}]入库审批完成, 入库记录数: {}", salesLedger.getSalesContractNo(), recordIds.size()); } } catch (Exception e) { log.warn("销售质检入库失败[{}]: {}", salesLedger.getSalesContractNo(), e.getMessage()); } } /** * 发货 + 发货审批通过 + 出库审批 */ private void processSalesShipping(SalesLedger salesLedger, LocalDate shipDate) { try { LocalDateTime shipDateTime = shipDate.atTime(9, 0, 0) .plusMinutes(ThreadLocalRandom.current().nextInt(0, 480)); String shipDateStr = shipDate.format(DateTimeFormatter.BASIC_ISO_DATE); // 查找有库存的产品规格 List products = salesLedgerProductMapper.selectList( new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() .eq(SalesLedgerProduct::getSalesLedgerId, salesLedger.getId())); List details = new ArrayList<>(); for (SalesLedgerProduct slp : products) { StockInventory si = stockInventoryMapper.selectOne( new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() .eq(StockInventory::getProductModelId, slp.getProductModelId()) .gt(StockInventory::getQualitity, BigDecimal.ZERO) .orderByDesc(StockInventory::getId) .last("limit 1")); if (si != null) { ShippingProductDetail detail = new ShippingProductDetail(); detail.setStockInventoryId(si.getId()); detail.setProductModelId(slp.getProductModelId()); detail.setBatchNo(si.getBatchNo()); detail.setQuantity(slp.getQuantity()); details.add(detail); } } if (details.isEmpty()) { log.info("销售台账[{}]无可用库存,跳过发货", salesLedger.getSalesContractNo()); return; } // 创建发货单 ShippingInfoDto shipDto = new ShippingInfoDto(); shipDto.setSalesLedgerId(salesLedger.getId()); shipDto.setSalesLedgerProductId(products.get(0).getId()); shipDto.setCreateTime(shipDateTime); shipDto.setBatchNoDetailList(details); shippingInfoService.addReq(shipDto); // 找到发货单和审批实例 ShippingInfo shippingInfo = shippingInfoMapper.selectOne( new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() .eq(ShippingInfo::getShippingNo, shipDto.getShippingNo()) .last("limit 1")); if (shippingInfo != null) { shippingInfo.setCreateTime(shipDateTime); shippingInfo.setShippingDate(java.sql.Date.valueOf(shipDate)); // 根据客户地址生成发货车牌号 shippingInfo.setShippingCarNumber(generateCarNumber(salesLedger.getCustomerName())); shippingInfoMapper.updateById(shippingInfo); // 发货审批自动通过 ApprovalInstance shipApproval = approvalInstanceMapper.selectOne( new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() .eq(ApprovalInstance::getBusinessId, shippingInfo.getId()) .eq(ApprovalInstance::getBusinessType, 7L) .eq(ApprovalInstance::getDeleted, 0) .orderByDesc(ApprovalInstance::getId) .last("limit 1")); if (shipApproval != null) { LocalDate shipApproveDate = shipDate.plusDays(ThreadLocalRandom.current().nextInt(0, 3)); LocalDateTime shipApproveDateTime = shipApproveDate.atTime(9, 0, 0) .plusMinutes(ThreadLocalRandom.current().nextInt(0, 480)); approvalInstanceService.autoApprove(shipApproval.getId()); fixApprovalTimes(shipApproval.getId(), shipApproveDateTime); shippingInfo.setStatus("已发货"); shippingInfo.setShippingDate(java.sql.Date.valueOf(shipApproveDate)); shippingInfoMapper.updateById(shippingInfo); } // 出库审批 LocalDate outDate = shipDate.plusDays(ThreadLocalRandom.current().nextInt(0, 3)); LocalDateTime outDateTime = outDate.atTime(9, 0, 0) .plusMinutes(ThreadLocalRandom.current().nextInt(0, 480)); String outDateStr = outDate.format(DateTimeFormatter.BASIC_ISO_DATE); List outRecords = stockOutRecordMapper.selectList( new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() .eq(StockOutRecord::getRecordId, shippingInfo.getId()) .eq(StockOutRecord::getRecordType, String.valueOf(StockOutQualifiedRecordTypeEnum.SALE_SHIP_STOCK_OUT.getCode()))); List outIds = new ArrayList<>(); for (StockOutRecord sr : outRecords) { if (sr.getApprovalStatus() == null || sr.getApprovalStatus() == 0 || sr.getApprovalStatus() == 3) { sr.setCreateTime(outDateTime); sr.setOutboundBatches(fixBatchNoDate(sr.getOutboundBatches(), outDateStr)); sr.setBatchNo(fixBatchNoDate(sr.getBatchNo(), outDateStr)); stockOutRecordMapper.updateById(sr); outIds.add(sr.getId()); } } if (!outIds.isEmpty()) { // 出库审批通过(走正式审批流程,内部会扣减库存) stockOutRecordService.batchApprove(outIds, ReviewStatusEnum.APPROVED.getCode()); log.info("销售台账[{}]出库审批完成, 出库记录数: {}", salesLedger.getSalesContractNo(), outIds.size()); } } log.info("销售台账[{}]发货流程完成", salesLedger.getSalesContractNo()); } catch (Exception e) { log.warn("销售发货流程失败[{}]: {}", salesLedger.getSalesContractNo(), e.getMessage()); } } private ModuleSummary createPurchaseLedgers(List items, Map supplierNameToId, String additionalInfo, String dateStart, String dateEnd) { // 是否需要质检,从补充信息判断,默认不需要 boolean needQualityInspect = additionalInfo != null && (additionalInfo.contains("质检") || additionalInfo.contains("需要检验")); int success = 0, fail = 0; for (JSONObject item : items) { try { String supplierName = item.getString("supplierName"); Long supplierId = supplierNameToId.get(supplierName); PurchaseLedgerDto dto = new PurchaseLedgerDto(); dto.setSupplierId(supplierId); dto.setSupplierName(supplierName); dto.setPurchaseContractNumber(item.getString("purchaseContractNumber")); dto.setProjectName(item.getString("projectName")); dto.setContractAmount(item.getBigDecimal("contractAmount")); dto.setPaymentMethod(item.getString("paymentMethod")); String entryDateStr = item.getString("entryDate"); LocalDate entryDate = null; if (item.containsKey("entryDate") && entryDateStr != null && entryDateStr.matches("\\d{4}-\\d{2}-\\d{2}")) { entryDate = LocalDate.parse(entryDateStr); dto.setEntryDate(java.sql.Date.valueOf(entryDateStr)); } else { dto.setEntryDate(java.sql.Date.valueOf(dateStart)); } if (item.containsKey("executionDate")) { String execDateStr = item.getString("executionDate"); if (execDateStr != null && execDateStr.matches("\\d{4}-\\d{2}-\\d{2}")) { dto.setExecutionDate(java.sql.Date.valueOf(execDateStr)); } else { dto.setExecutionDate(java.sql.Date.valueOf(dateStart)); } } JSONArray productData = item.getJSONArray("productData"); if (productData != null) { List products = new ArrayList<>(); for (int i = 0; i < productData.size(); i++) { JSONObject pd = productData.getJSONObject(i); SalesLedgerProduct slp = new SalesLedgerProduct(); slp.setProductId(pd.getLong("productId")); slp.setProductModelId(pd.getLong("productModelId")); slp.setQuantity(pd.getBigDecimal("quantity")); slp.setTaxInclusiveUnitPrice(pd.getBigDecimal("taxInclusiveUnitPrice")); slp.setTaxInclusiveTotalPrice(pd.getBigDecimal("taxInclusiveTotalPrice")); slp.setTaxRate(pd.getBigDecimal("taxRate")); slp.setUnit(pd.getString("unit")); slp.setType(2); // 是否质检:从补充信息判断,默认不需要 slp.setIsChecked(needQualityInspect); if (pd.containsKey("taxExclusiveTotalPrice")) { slp.setTaxExclusiveTotalPrice(pd.getBigDecimal("taxExclusiveTotalPrice")); } else if (pd.getBigDecimal("taxInclusiveTotalPrice") != null && pd.getBigDecimal("taxRate") != null) { slp.setTaxExclusiveTotalPrice( pd.getBigDecimal("taxInclusiveTotalPrice").divide( BigDecimal.ONE.add(pd.getBigDecimal("taxRate").divide(new BigDecimal("100"), 4, RoundingMode.HALF_UP)), 2, RoundingMode.HALF_UP)); } else { slp.setTaxExclusiveTotalPrice(BigDecimal.ZERO); } products.add(slp); } dto.setProductData(products); } purchaseLedgerService.addOrEditPurchase(dto); // 通过合同号找到刚创建的采购台账 PurchaseLedger savedLedger = purchaseLedgerService.getOne( new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() .eq(PurchaseLedger::getPurchaseContractNumber, dto.getPurchaseContractNumber()) .last("limit 1")); if (savedLedger != null) { // 走完整流程:审核通过 → 质检(可选) → 入库 + 入库审核通过 processPurchaseFullFlow(savedLedger, needQualityInspect, entryDate, dateEnd); } success++; } catch (Exception e) { log.warn("创建采购台账失败: {}", e.getMessage(), e); fail++; } } return summary("purchase", "采购台账", items.size(), success, fail); } /** * 采购完整流程: 审核通过 → 质检(可选) → 入库审核通过 * 各环节时间以采购录入日期为基准,偶尔推迟1-3天 */ private void processPurchaseFullFlow(PurchaseLedger purchaseLedger, boolean needQualityInspect, LocalDate entryDate, String dateEnd) { try { // 基准日期:采购录入日期,为空则用当天 LocalDate baseDate = entryDate != null ? entryDate : LocalDate.now(); // 审批时间:基于录入日期,随机推0-3天 LocalDate approveDate = baseDate.plusDays(ThreadLocalRandom.current().nextInt(0, 4)); LocalDateTime approveDateTime = approveDate.atTime(9, 0, 0) .plusMinutes(ThreadLocalRandom.current().nextInt(0, 480)); // 1. 审批自动通过 ApprovalInstance approvalInstance = approvalInstanceMapper.selectOne( new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() .eq(ApprovalInstance::getBusinessId, purchaseLedger.getId()) .eq(ApprovalInstance::getBusinessType, 5L) .eq(ApprovalInstance::getDeleted, 0) .orderByDesc(ApprovalInstance::getId) .last("limit 1")); if (approvalInstance != null) { approvalInstanceService.autoApprove(approvalInstance.getId()); // 修正审批相关时间为采购录入日期范围 fixApprovalTimes(approvalInstance.getId(), approveDateTime); log.info("采购台账[{}]审批通过, 审批日期: {}", purchaseLedger.getPurchaseContractNumber(), approveDate); } // 2. 质检流程(如果需要质检) if (needQualityInspect) { // 质检时间:审批之后再推0-3天 LocalDate inspectDate = approveDate.plusDays(ThreadLocalRandom.current().nextInt(0, 4)); processQualityInspect(purchaseLedger, inspectDate); } // 3. 入库审批通过 // 入库时间:质检时间(有质检)或审批时间之后再推0-3天 LocalDate stockBaseDate = needQualityInspect ? approveDate.plusDays(ThreadLocalRandom.current().nextInt(1, 4)) : approveDate.plusDays(ThreadLocalRandom.current().nextInt(0, 4)); processStockInApprove(purchaseLedger, stockBaseDate); } catch (Exception e) { log.warn("采购完整流程处理失败[{}]: {}", purchaseLedger.getPurchaseContractNumber(), e.getMessage()); } } /** * 修正审批实例及关联节点、任务、记录的时间 */ private void fixApprovalTimes(Long instanceId, LocalDateTime dateTime) { try { // 更新审批实例的finishTime和createTime ApprovalInstance instance = approvalInstanceMapper.selectById(instanceId); if (instance != null) { instance.setFinishTime(dateTime); instance.setCreateTime(dateTime.minusHours(1)); approvalInstanceMapper.updateById(instance); } // 更新审批实例节点的finishTime List nodes = approvalInstanceNodeMapper.selectList( new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() .eq(ApprovalInstanceNode::getInstanceId, instanceId)); for (ApprovalInstanceNode node : nodes) { node.setFinishTime(dateTime); node.setCreateTime(dateTime.minusHours(1)); approvalInstanceNodeMapper.updateById(node); } // 更新审批任务的createTime List tasks = approvalTaskMapper.selectList( new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() .eq(ApprovalTask::getInstanceId, instanceId)); for (ApprovalTask task : tasks) { task.setCreateTime(dateTime.minusMinutes(30)); approvalTaskMapper.updateById(task); } // 更新审批记录的createTime List records = approvalRecordMapper.selectList( new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() .eq(ApprovalRecord::getInstanceId, instanceId)); for (ApprovalRecord record : records) { record.setCreateTime(dateTime.minusMinutes(10)); approvalRecordMapper.updateById(record); } } catch (Exception e) { log.warn("修正审批时间失败: {}", e.getMessage()); } } /** * 质检: 找到采购关联的质检单,自动提交为合格 */ private void processQualityInspect(PurchaseLedger purchaseLedger, LocalDate inspectDate) { try { LocalDateTime inspectDateTime = inspectDate.atTime(9, 0, 0) .plusMinutes(ThreadLocalRandom.current().nextInt(0, 480)); List inspectList = qualityInspectService.list( new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() .eq(QualityInspect::getPurchaseLedgerId, purchaseLedger.getId())); for (QualityInspect qi : inspectList) { if (qi.getInspectState() == null || qi.getInspectState() == 0) { qualityInspectService.autoSubmit(qi.getId()); // 修正质检时间 qi.setCreateTime(inspectDateTime); qualityInspectMapper.updateById(qi); log.info("采购台账[{}]质检单[{}]自动提交合格, 质检日期: {}", purchaseLedger.getPurchaseContractNumber(), qi.getId(), inspectDate); } } } catch (Exception e) { log.warn("质检流程处理失败[{}]: {}", purchaseLedger.getPurchaseContractNumber(), e.getMessage()); } } /** * 入库审核: 找到入库记录并审批通过,并修正时间与批号日期 */ private void processStockInApprove(PurchaseLedger purchaseLedger, LocalDate stockDate) { try { LocalDateTime stockDateTime = stockDate.atTime(9, 0, 0) .plusMinutes(ThreadLocalRandom.current().nextInt(0, 480)); String stockDateStr = stockDate.format(java.time.format.DateTimeFormatter.BASIC_ISO_DATE); List stockRecords = stockInRecordService.list( new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() .eq(com.ruoyi.stock.pojo.StockInRecord::getRecordId, purchaseLedger.getId()) .eq(com.ruoyi.stock.pojo.StockInRecord::getRecordType, String.valueOf(com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum.PURCHASE_STOCK_IN.getCode()))); if (stockRecords.isEmpty()) { stockRecords = stockInRecordService.list( new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() .eq(com.ruoyi.stock.pojo.StockInRecord::getRecordId, purchaseLedger.getId()) .eq(com.ruoyi.stock.pojo.StockInRecord::getRecordType, String.valueOf(com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum.CUSTOMIZATION_UNSTOCK_OUT.getCode()))); } for (QualityInspect qi : qualityInspectService.list( new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() .eq(QualityInspect::getPurchaseLedgerId, purchaseLedger.getId()))) { List qiRecords = stockInRecordService.list( new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() .eq(com.ruoyi.stock.pojo.StockInRecord::getRecordId, qi.getId())); stockRecords.addAll(qiRecords); } stockRecords = stockRecords.stream() .collect(Collectors.toMap(com.ruoyi.stock.pojo.StockInRecord::getId, r -> r, (a, b) -> a)) .values().stream().collect(Collectors.toList()); if (!stockRecords.isEmpty()) { List recordIds = stockRecords.stream() .filter(r -> r.getApprovalStatus() == null || r.getApprovalStatus() == 0) .map(com.ruoyi.stock.pojo.StockInRecord::getId) .collect(Collectors.toList()); if (!recordIds.isEmpty()) { // 先修正入库记录的createTime和批号日期(batchApprove内部会用batchNo创建StockInventory) for (Long recordId : recordIds) { com.ruoyi.stock.pojo.StockInRecord sr = stockInRecordMapper.selectById(recordId); if (sr != null) { sr.setCreateTime(stockDateTime); sr.setBatchNo(fixBatchNoDate(sr.getBatchNo(), stockDateStr)); String s = OrderUtils.countTodayByCreateTime(stockInRecordMapper, "RK", "inbound_batches", stockDateTime); sr.setInboundBatches(s); stockInRecordMapper.updateById(sr); } } stockInRecordService.batchApprove(recordIds, ReviewStatusEnum.APPROVED.getCode()); log.info("采购台账[{}]入库审批通过, 入库日期: {}, 入库记录数: {}", purchaseLedger.getPurchaseContractNumber(), stockDate, recordIds.size()); } } } catch (Exception e) { log.warn("入库审批失败[{}]: {}", purchaseLedger.getPurchaseContractNumber(), e.getMessage()); } } /** * 替换批号中的日期部分(yyyyMMdd → 指定日期) */ /** * 根据客户名称查地址,推断省份简称生成车牌号 * 格式: 省份简称 + 字母 + 5位随机数字字母, 如 苏A12345 */ private String generateCarNumber(String customerName) { // 省份简称到城市字母的常见映射 Map provinceCityMap = new HashMap<>(); provinceCityMap.put("京", new String[]{"A", "B", "C", "E", "F", "G"}); provinceCityMap.put("沪", new String[]{"A", "B", "D", "E", "F"}); provinceCityMap.put("粤", new String[]{"A", "B", "E", "F", "G", "H", "J", "K"}); provinceCityMap.put("苏", new String[]{"A", "B", "E", "F", "G", "H", "J", "K", "L", "M"}); provinceCityMap.put("浙", new String[]{"A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L"}); provinceCityMap.put("鲁", new String[]{"A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "P", "Q", "R", "S", "T", "U", "V", "W", "Y"}); provinceCityMap.put("豫", new String[]{"A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "P", "Q", "R", "S", "U"}); provinceCityMap.put("川", new String[]{"A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"}); provinceCityMap.put("渝", new String[]{"A", "B", "C", "D"}); provinceCityMap.put("鄂", new String[]{"A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "P", "Q", "R", "S"}); provinceCityMap.put("湘", new String[]{"A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "S"}); provinceCityMap.put("闽", new String[]{"A", "B", "C", "D", "E", "F", "G", "H", "J", "K"}); provinceCityMap.put("赣", new String[]{"A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M"}); provinceCityMap.put("皖", new String[]{"A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "P", "R", "S"}); provinceCityMap.put("冀", new String[]{"A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "R", "T"}); provinceCityMap.put("辽", new String[]{"A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "P"}); provinceCityMap.put("吉", new String[]{"A", "B", "C", "D", "E", "F", "G", "H", "J", "K"}); provinceCityMap.put("黑", new String[]{"A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "P", "R"}); provinceCityMap.put("晋", new String[]{"A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M"}); provinceCityMap.put("陕", new String[]{"A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "V"}); provinceCityMap.put("桂", new String[]{"A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "P", "R"}); provinceCityMap.put("云", new String[]{"A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "P", "Q", "R", "S"}); provinceCityMap.put("贵", new String[]{"A", "B", "C", "D", "E", "F", "G", "H", "J"}); provinceCityMap.put("甘", new String[]{"A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "P"}); provinceCityMap.put("琼", new String[]{"A", "B", "C", "D", "E", "F"}); provinceCityMap.put("宁", new String[]{"A", "B", "C", "D", "E"}); provinceCityMap.put("青", new String[]{"A", "B", "C", "D", "E", "F", "G", "H"}); provinceCityMap.put("藏", new String[]{"A", "B", "C", "D", "E", "F", "G", "H", "J"}); provinceCityMap.put("蒙", new String[]{"A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M"}); provinceCityMap.put("新", new String[]{"A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "P", "Q", "R"}); provinceCityMap.put("津", new String[]{"A", "B", "C", "E", "F", "G", "H", "J", "K", "L", "M", "N", "Q", "R"}); // 地址关键词到省份简称的映射 Map addressToProvince = new HashMap<>(); addressToProvince.put("北京", "京"); addressToProvince.put("上海", "沪"); addressToProvince.put("广州", "粤"); addressToProvince.put("深圳", "粤"); addressToProvince.put("东莞", "粤"); addressToProvince.put("佛山", "粤"); addressToProvince.put("南京", "苏"); addressToProvince.put("苏州", "苏"); addressToProvince.put("无锡", "苏"); addressToProvince.put("南通", "苏"); addressToProvince.put("常州", "苏"); addressToProvince.put("徐州", "苏"); addressToProvince.put("杭州", "浙"); addressToProvince.put("宁波", "浙"); addressToProvince.put("温州", "浙"); addressToProvince.put("嘉兴", "浙"); addressToProvince.put("济南", "鲁"); addressToProvince.put("青岛", "鲁"); addressToProvince.put("郑州", "豫"); addressToProvince.put("成都", "川"); addressToProvince.put("重庆", "渝"); addressToProvince.put("武汉", "鄂"); addressToProvince.put("长沙", "湘"); addressToProvince.put("福州", "闽"); addressToProvince.put("厦门", "闽"); addressToProvince.put("南昌", "赣"); addressToProvince.put("合肥", "皖"); addressToProvince.put("石家庄", "冀"); addressToProvince.put("沈阳", "辽"); addressToProvince.put("大连", "辽"); addressToProvince.put("长春", "吉"); addressToProvince.put("哈尔滨", "黑"); addressToProvince.put("太原", "晋"); addressToProvince.put("西安", "陕"); addressToProvince.put("南宁", "桂"); addressToProvince.put("昆明", "云"); addressToProvince.put("贵阳", "贵"); addressToProvince.put("兰州", "甘"); addressToProvince.put("海口", "琼"); addressToProvince.put("银川", "宁"); addressToProvince.put("西宁", "青"); addressToProvince.put("拉萨", "藏"); addressToProvince.put("呼和浩特", "蒙"); addressToProvince.put("乌鲁木齐", "新"); addressToProvince.put("天津", "津"); // 省名映射 addressToProvince.put("江苏", "苏"); addressToProvince.put("浙江", "浙"); addressToProvince.put("山东", "鲁"); addressToProvince.put("河南", "豫"); addressToProvince.put("四川", "川"); addressToProvince.put("湖北", "鄂"); addressToProvince.put("湖南", "湘"); addressToProvince.put("福建", "闽"); addressToProvince.put("江西", "赣"); addressToProvince.put("安徽", "皖"); addressToProvince.put("河北", "冀"); addressToProvince.put("辽宁", "辽"); addressToProvince.put("吉林", "吉"); addressToProvince.put("黑龙江", "黑"); addressToProvince.put("山西", "晋"); addressToProvince.put("陕西", "陕"); addressToProvince.put("广东", "粤"); addressToProvince.put("广西", "桂"); addressToProvince.put("云南", "云"); addressToProvince.put("贵州", "贵"); addressToProvince.put("甘肃", "甘"); addressToProvince.put("海南", "琼"); addressToProvince.put("宁夏", "宁"); addressToProvince.put("青海", "青"); addressToProvince.put("西藏", "藏"); addressToProvince.put("内蒙古", "蒙"); addressToProvince.put("新疆", "新"); String province = "苏"; // 默认江苏 // 尝试从客户地址推断省份 if (customerName != null) { try { Customer customer = customerService.getOne( new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() .eq(Customer::getCustomerName, customerName) .last("limit 1")); if (customer != null && customer.getCompanyAddress() != null) { String addr = customer.getCompanyAddress(); for (Map.Entry e : addressToProvince.entrySet()) { if (addr.contains(e.getKey())) { province = e.getValue(); break; } } } } catch (Exception e) { log.debug("查客户地址失败: {}", e.getMessage()); } } // 生成车牌号: 省份简称 + 城市字母 + 5位随机字符(数字+大写字母) String[] cities = provinceCityMap.getOrDefault(province, new String[]{"A", "B", "C"}); String city = cities[ThreadLocalRandom.current().nextInt(cities.length)]; String chars = "ABCDEFGHJKLMNPQRSTUVWXYZ0123456789"; StringBuilder suffix = new StringBuilder(); for (int i = 0; i < 5; i++) { suffix.append(chars.charAt(ThreadLocalRandom.current().nextInt(chars.length()))); } return province + city + suffix; } private String fixBatchNoDate(String batchNo, String newDate) { if (batchNo == null || batchNo.length() < 8) { return batchNo; } // 批号格式: yyyyMMdd-产品编码-NNN String prefix = batchNo.substring(0, 8); if (prefix.matches("\\d{8}")) { return newDate + batchNo.substring(8); } return batchNo; } private ModuleSummary createProductionPlans(List items) { int success = 0, fail = 0; List createdPlans = new ArrayList<>(); for (JSONObject item : items) { try { ProductionPlan plan = new ProductionPlan(); plan.setProductModelId(item.getLong("productModelId")); plan.setQtyRequired(item.getBigDecimal("qtyRequired")); plan.setSource(item.getString("source")); plan.setRemark(item.getString("remark")); if (item.containsKey("requiredDate")) { String dateStr = item.getString("requiredDate"); if (dateStr != null && dateStr.matches("\\d{4}-\\d{2}-\\d{2}")) { plan.setRequiredDate(LocalDate.parse(dateStr)); } } if (item.containsKey("promisedDeliveryDate")) { String dateStr = item.getString("promisedDeliveryDate"); if (dateStr != null && dateStr.matches("\\d{4}-\\d{2}-\\d{2}")) { plan.setPromisedDeliveryDate(LocalDate.parse(dateStr)); } } productionPlanService.save(plan); createdPlans.add(plan); success++; } catch (Exception e) { log.warn("创建生产计划失败: {}", e.getMessage()); fail++; } } // 按productModelId分组,自动合并下发 Map> grouped = createdPlans.stream() .filter(p -> p.getProductModelId() != null) .collect(Collectors.groupingBy(ProductionPlan::getProductModelId)); for (Map.Entry> entry : grouped.entrySet()) { try { List planIds = entry.getValue().stream().map(ProductionPlan::getId).collect(Collectors.toList()); BigDecimal totalQty = entry.getValue().stream() .map(ProductionPlan::getQtyRequired) .reduce(BigDecimal.ZERO, BigDecimal::add); ProductionPlanDto combineDto = new ProductionPlanDto(); combineDto.setIds(planIds); combineDto.setTotalAssignedQuantity(totalQty); combineDto.setPlanCompleteTime(LocalDate.now().plusDays(ThreadLocalRandom.current().nextInt(3, 10))); productionPlanService.combine(combineDto); log.info("生产计划自动下发成功, productModelId={}, 计划数: {}, 下发数量: {}", entry.getKey(), planIds.size(), totalQty); } catch (Exception e) { log.warn("生产计划自动下发失败, productModelId={}: {}", entry.getKey(), e.getMessage()); } } return summary("production", "生产计划", items.size(), success, fail); } // ---- Tier 2: 后续单据 ---- private ModuleSummary createProductionOrders(List items) { int success = 0, fail = 0; for (JSONObject item : items) { try { ProductionOrder order = new ProductionOrder(); order.setProductModelId(item.getLong("productModelId")); order.setQuantity(item.getBigDecimal("quantity")); if (item.containsKey("planCompleteTime")) { String dateStr = item.getString("planCompleteTime"); if (dateStr != null && dateStr.matches("\\d{4}-\\d{2}-\\d{2}")) { order.setPlanCompleteTime(LocalDate.parse(dateStr)); } } productionOrderService.saveProductionOrder(order); success++; } catch (Exception e) { log.warn("创建生产订单失败: {}", e.getMessage()); fail++; } } return summary("production", "生产订单", items.size(), success, fail); } private ModuleSummary createQualityBindings(List items) { int success = 0, fail = 0; for (JSONObject item : items) { try { QualityTestStandardBinding binding = new QualityTestStandardBinding(); binding.setProductId(item.getLong("productId")); binding.setTestStandardId(item.getInteger("testStandardId")); qualityTestStandardBindingService.add(List.of(binding)); success++; } catch (Exception e) { log.warn("创建指标绑定失败: {}", e.getMessage()); fail++; } } return summary("quality", "指标绑定", items.size(), success, fail); } private ModuleSummary createStockInventories(List items) { int success = 0, fail = 0; for (JSONObject item : items) { try { StockInventoryDto dto = new StockInventoryDto(); dto.setProductModelId(item.getLong("productModelId")); dto.setQualitity(item.getBigDecimal("qualitity")); dto.setBatchNo(item.getString("batchNo")); dto.setWarnNum(item.getBigDecimal("warnNum")); dto.setRemark(item.getString("remark")); stockInventoryService.addstockInventory(dto); success++; } catch (Exception e) { log.warn("创建库存记录失败: {}", e.getMessage()); fail++; } } return summary("stock", "库存记录", items.size(), success, fail); } private ModuleSummary summary(String module, String entityName, int total, int success, int fail) { ModuleSummary s = new ModuleSummary(); s.setModule(module); s.setEntityName(entityName); s.setGeneratedCount(total); s.setSuccessCount(success); s.setFailCount(fail); return s; } }