6 小时以前 027a1c30ea0216080c3472c6c6b28acec4077c7d
优化数据
已修改2个文件
205 ■■■■ 文件已修改
src/main/java/com/ruoyi/mock/prompt/MockDataPrompt.java 60 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/mock/service/impl/DataGenerateServiceImpl.java 145 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/mock/prompt/MockDataPrompt.java
@@ -19,10 +19,11 @@
            1. 输出必须是纯JSON数组,不要用markdown代码块包裹,不要有任何其他文字
            2. 每个JSON对象必须包含 "entity" 字段,值为实体类型名
            3. 数据内容要符合指定行业的特征(公司名称、产品名称、联系人等要像该行业的)
            4. 日期字段在指定的时间范围内随机分布
            4. 所有日期字段必须在指定的时间范围内,严禁使用范围之外的日期
            5. 金额、数量等数值字段要合理
            6. 同一模块内的实体之间要有引用关系(如销售台账引用客户名称)
            7. 所有字符串字段不要使用emoji或特殊unicode字符
            8. 合同编号、批号等包含日期的字段,必须使用时间范围内的日期,不要使用示例中的具体日期
            """;
    }
@@ -55,13 +56,13 @@
            sb.append(buildPurchasePrompt(countMin, countMax, dateStart, dateEnd));
        }
        if (modules.contains("quality")) {
            sb.append(buildQualityPrompt(countMin, countMax));
            sb.append(buildQualityPrompt(countMin, countMax, dateStart));
        }
        if (modules.contains("production")) {
            sb.append(buildProductionPrompt(countMin, countMax, dateStart, dateEnd));
        }
        if (modules.contains("stock")) {
            sb.append(buildStockPrompt(countMin, countMax));
            sb.append(buildStockPrompt(countMin, countMax, dateStart));
        }
        sb.append("\n请直接输出JSON数组,不要有任何其他内容:");
@@ -70,7 +71,7 @@
    private static String buildSalesPrompt(int min, int max, String dateStart, String dateEnd) {
        return """
            销售模块 - 按以下格式生成:
            销售模块 - 按以下格式生成(注意:示例中的日期仅作格式参考,实际日期必须在 %s ~ %s 范围内):
            {
              "entity": "customer",
              "customerName": "XX科技有限公司",
@@ -81,7 +82,7 @@
              "companyPhone": "0513-XXXXXXXX",
              "taxpayerIdentificationNumber": "91110108XXXXXXXXXX",
              "maintainer": "李四",
              "maintenanceTime": "2026-06-01",
              "maintenanceTime": "%s",
              "bankAccount": "622202XXXXXXXXXXXX",
              "basicBankAccount": "XX银行XX支行",
              "bankCode": "308100XXXXXX"
@@ -89,15 +90,15 @@
            {
              "entity": "salesLedger",
              "customerName": "引用上面生成的客户名称",
              "salesContractNo": "XS-20260601-001",
              "salesContractNo": "XS-年月日-序号",
              "projectName": "XX项目",
              "entryDate": "2026-06-01",
              "entryDate": "%s",
              "entryPerson": "张三",
              "salesman": "销售员姓名",
              "contractAmount": 50000.00,
              "paymentMethod": "月结30天",
              "executionDate": "2026-06-01",
              "deliveryDate": "2026-07-01",
              "executionDate": "%s",
              "deliveryDate": "%s",
              "type": 1,
              "productData": [
                {
@@ -113,15 +114,15 @@
                }
              ]
            }
            客户名称要像指定行业的公司,合同编号格式XS-年月日-序号,金额合理。
            客户名称要像指定行业的公司,合同编号格式XS-年月日-序号(年月日取entryDate的日期),金额合理。
            注意 taxExclusiveTotalPrice 是不含税总价,需要根据含税总价和税率计算(=含税总价/(1+税率/100))。
            日期范围: %s ~ %s,每种实体生成%d-%d条。
            """.formatted(dateStart, dateEnd, min, max);
            """.formatted(dateStart, dateEnd, dateStart, dateStart, dateStart, dateEnd, dateStart, dateEnd, min, max);
    }
    private static String buildPurchasePrompt(int min, int max, String dateStart, String dateEnd) {
        return """
            采购模块 - 按以下格式生成:
            采购模块 - 按以下格式生成(注意:示例中的日期仅作格式参考,实际日期必须在 %s ~ %s 范围内):
            {
              "entity": "supplier",
              "supplierName": "XX原材料有限公司",
@@ -138,12 +139,12 @@
            {
              "entity": "purchaseLedger",
              "supplierName": "引用上面生成的供应商名称",
              "purchaseContractNumber": "CG-20260601-001",
              "purchaseContractNumber": "CG-年月日-序号",
              "projectName": "XX采购项目",
              "entryDate": "2026-06-01",
              "entryDate": "%s",
              "contractAmount": 30000.00,
              "paymentMethod": "货到付款",
              "executionDate": "2026-06-01",
              "executionDate": "%s",
              "productData": [
                {
                  "productId": 1,
@@ -158,68 +159,69 @@
                }
              ]
            }
            供应商名称要像指定行业的供应商,合同编号格式CG-年月日-序号。
            供应商名称要像指定行业的供应商,合同编号格式CG-年月日-序号(年月日取entryDate的日期)。
            注意 taxExclusiveTotalPrice 是不含税总价,需要根据含税总价和税率计算(=含税总价/(1+税率/100))。
            日期范围: %s ~ %s,每种实体生成%d-%d条。
            """.formatted(dateStart, dateEnd, min, max);
            """.formatted(dateStart, dateEnd, dateStart, dateStart, dateStart, dateEnd, min, max);
    }
    private static String buildQualityPrompt(int min, int max) {
    private static String buildQualityPrompt(int min, int max, String dateStart) {
        return """
            质量模块 - 按以下格式生成:
            {
              "entity": "qualityTestStandard",
              "standardNo": "QTS-20260601-001",
              "standardNo": "QTS-年月日-序号",
              "standardName": "XX产品检验标准",
              "inspectType": 0,
              "remark": "适用于XX行业的质量检验标准"
            }
            inspectType: 0=原材料检验, 1=过程检验, 2=出厂检验。三种类型都要覆盖。
            standardNo中的年月日使用 %s 这个日期。
            {
              "entity": "qualityTestStandardBinding",
              "productId": 1,
              "testStandardId": 1
            }
            每种实体生成%d-%d条。
            """.formatted(min, max);
            """.formatted(dateStart, min, max);
    }
    private static String buildProductionPrompt(int min, int max, String dateStart, String dateEnd) {
        return """
            生产模块 - 按以下格式生成:
            生产模块 - 按以下格式生成(注意:示例中的日期仅作格式参考,实际日期必须在 %s ~ %s 范围内):
            {
              "entity": "productionPlan",
              "productModelId": 1,
              "qtyRequired": 200,
              "requiredDate": "2026-06-15",
              "requiredDate": "%s",
              "source": "销售",
              "promisedDeliveryDate": "2026-07-01",
              "promisedDeliveryDate": "%s",
              "remark": "XX客户订单需求"
            }
            {
              "entity": "productionOrder",
              "productModelId": 1,
              "quantity": 200,
              "planCompleteTime": "2026-06-30",
              "planCompleteTime": "%s",
              "remark": "根据生产计划XX生成"
            }
            productionPlan的source可选"销售"或"内部"。
            日期范围: %s ~ %s,每种实体生成%d-%d条。
            """.formatted(dateStart, dateEnd, min, max);
            """.formatted(dateStart, dateEnd, dateStart, dateEnd, dateEnd, dateStart, dateEnd, min, max);
    }
    private static String buildStockPrompt(int min, int max) {
    private static String buildStockPrompt(int min, int max, String dateStart) {
        return """
            库存模块 - 按以下格式生成:
            {
              "entity": "stockInventory",
              "productModelId": 1,
              "qualitity": 500,
              "batchNo": "BATCH-20260601-001",
              "batchNo": "BATCH-年月日-序号",
              "warnNum": 50,
              "remark": "安全库存"
            }
            每种实体生成%d-%d条。batchNo格式BATCH-年月日-序号。
            """.formatted(min, max);
            每种实体生成%d-%d条。batchNo格式BATCH-年月日-序号,年月日使用 %s 这个日期。
            """.formatted(min, max, dateStart);
    }
}
src/main/java/com/ruoyi/mock/service/impl/DataGenerateServiceImpl.java
@@ -4,12 +4,16 @@
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.pojo.ApprovalInstance;
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.SupplierManage;
import com.ruoyi.basic.service.ICustomerService;
import com.ruoyi.basic.service.ISupplierService;
import com.ruoyi.common.enums.ReviewStatusEnum;
import com.ruoyi.mock.dto.DataGenerateRequest;
import com.ruoyi.mock.prompt.MockDataPrompt;
import com.ruoyi.mock.service.DataGenerateService;
@@ -20,15 +24,19 @@
import com.ruoyi.production.service.ProductionOrderService;
import com.ruoyi.production.service.ProductionPlanService;
import com.ruoyi.purchase.dto.PurchaseLedgerDto;
import com.ruoyi.purchase.pojo.PurchaseLedger;
import com.ruoyi.purchase.service.IPurchaseLedgerService;
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.pojo.SalesLedgerProduct;
import com.ruoyi.sales.service.ISalesLedgerService;
import com.ruoyi.stock.dto.StockInventoryDto;
import com.ruoyi.stock.service.StockInRecordService;
import com.ruoyi.stock.service.StockInventoryService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -37,10 +45,13 @@
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;
@Slf4j
@@ -61,6 +72,12 @@
    private final ProductionPlanService productionPlanService;
    private final ProductionOrderService productionOrderService;
    private final StockInventoryService stockInventoryService;
    // 采购完整流程需要的service
    private final ApprovalInstanceService approvalInstanceService;
    private final ApprovalInstanceMapper approvalInstanceMapper;
    private final IQualityInspectService qualityInspectService;
    private final StockInRecordService stockInRecordService;
    @Override
    public DataGenerateResult generate(DataGenerateRequest request) {
@@ -134,7 +151,8 @@
                totalGenerated += s.getSuccessCount();
            }
            if (grouped.containsKey("purchaseLedger")) {
                ModuleSummary s = createPurchaseLedgers(grouped.get("purchaseLedger"), supplierNameToId);
                ModuleSummary s = createPurchaseLedgers(grouped.get("purchaseLedger"), supplierNameToId,
                        request.getAdditionalInfo(), request.getDateEnd());
                summaries.add(s);
                totalGenerated += s.getSuccessCount();
            }
@@ -357,7 +375,11 @@
        return summary("sales", "销售台账", items.size(), success, fail);
    }
    private ModuleSummary createPurchaseLedgers(List<JSONObject> items, Map<String, Long> supplierNameToId) {
    private ModuleSummary createPurchaseLedgers(List<JSONObject> items, Map<String, Long> supplierNameToId,
                                                 String additionalInfo, String dateEnd) {
        // 是否需要质检,从补充信息判断,默认不需要
        boolean needQualityInspect = additionalInfo != null
                && (additionalInfo.contains("质检") || additionalInfo.contains("需要检验"));
        int success = 0, fail = 0;
        for (JSONObject item : items) {
            try {
@@ -371,8 +393,12 @@
                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")) {
                    dto.setEntryDate(java.sql.Date.valueOf(item.getString("entryDate")));
                    entryDate = LocalDate.parse(entryDateStr);
                    dto.setEntryDate(java.sql.Date.valueOf(entryDateStr));
                }
                if (item.containsKey("executionDate")) {
                    dto.setExecutionDate(java.sql.Date.valueOf(item.getString("executionDate")));
@@ -391,6 +417,8 @@
                        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) {
@@ -406,6 +434,15 @@
                    dto.setProductData(products);
                }
                purchaseLedgerService.addOrEditPurchase(dto);
                // 通过合同号找到刚创建的采购台账
                PurchaseLedger savedLedger = purchaseLedgerService.getOne(
                        new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<PurchaseLedger>()
                                .eq(PurchaseLedger::getPurchaseContractNumber, dto.getPurchaseContractNumber())
                                .last("limit 1"));
                if (savedLedger != null) {
                    // 走完整流程:审核通过 → 质检(可选) → 入库 + 入库审核通过
                    processPurchaseFullFlow(savedLedger, needQualityInspect, entryDate, dateEnd);
                }
                success++;
            } catch (Exception e) {
                log.warn("创建采购台账失败: {}", e.getMessage());
@@ -415,6 +452,108 @@
        return summary("purchase", "采购台账", items.size(), success, fail);
    }
    /**
     * 采购完整流程: 审核通过 → 质检(可选) → 入库审核通过
     */
    private void processPurchaseFullFlow(PurchaseLedger purchaseLedger, boolean needQualityInspect,
                                          LocalDate entryDate, String dateEnd) {
        try {
            // 1. 审批自动通过
            ApprovalInstance approvalInstance = approvalInstanceMapper.selectOne(
                    new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<ApprovalInstance>()
                            .eq(ApprovalInstance::getBusinessId, purchaseLedger.getId())
                            .eq(ApprovalInstance::getBusinessType, 5L)
                            .eq(ApprovalInstance::getDeleted, 0)
                            .orderByDesc(ApprovalInstance::getId)
                            .last("limit 1"));
            if (approvalInstance != null) {
                // 结算审批开始时间:基于录入日期,随机推0-3天
                LocalDate baseDate = entryDate != null ? entryDate : LocalDate.now();
                LocalDate approveDate = baseDate.plusDays(ThreadLocalRandom.current().nextInt(0, 4));
                // 使用autoApprove完成审批
                approvalInstanceService.autoApprove(approvalInstance.getId());
                log.info("采购台账[{}]审批通过, 审批日期: {}", purchaseLedger.getPurchaseContractNumber(), approveDate);
            }
            // 2. 质检流程(如果需要质检)
            if (needQualityInspect) {
                processQualityInspect(purchaseLedger, entryDate, dateEnd);
            }
            // 3. 入库审批通过
            processStockInApprove(purchaseLedger, dateEnd);
        } catch (Exception e) {
            log.warn("采购完整流程处理失败[{}]: {}", purchaseLedger.getPurchaseContractNumber(), e.getMessage());
        }
    }
    /**
     * 质检: 找到采购关联的质检单,自动提交为合格
     */
    private void processQualityInspect(PurchaseLedger purchaseLedger, LocalDate entryDate, String dateEnd) {
        try {
            List<QualityInspect> inspectList = qualityInspectService.list(
                    new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<QualityInspect>()
                            .eq(QualityInspect::getPurchaseLedgerId, purchaseLedger.getId()));
            for (QualityInspect qi : inspectList) {
                if (qi.getInspectState() == null || qi.getInspectState() == 0) {
                    qualityInspectService.autoSubmit(qi.getId());
                    log.info("采购台账[{}]质检单[{}]自动提交合格", purchaseLedger.getPurchaseContractNumber(), qi.getId());
                }
            }
        } catch (Exception e) {
            log.warn("质检流程处理失败[{}]: {}", purchaseLedger.getPurchaseContractNumber(), e.getMessage());
        }
    }
    /**
     * 入库审核: 找到入库记录并审批通过
     */
    private void processStockInApprove(PurchaseLedger purchaseLedger, String dateEnd) {
        try {
            List<com.ruoyi.stock.pojo.StockInRecord> stockRecords = stockInRecordService.list(
                    new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<com.ruoyi.stock.pojo.StockInRecord>()
                            .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())));
            // 如果按PURCHASE_STOCK_IN找不到,尝试CUSTOMIZATION_UNSTOCK_OUT(质检合格入库)
            if (stockRecords.isEmpty()) {
                stockRecords = stockInRecordService.list(
                        new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<com.ruoyi.stock.pojo.StockInRecord>()
                                .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())));
            }
            // 也尝试按质检单ID查找(质检单的recordId是质检单ID)
            for (QualityInspect qi : qualityInspectService.list(
                    new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<QualityInspect>()
                            .eq(QualityInspect::getPurchaseLedgerId, purchaseLedger.getId()))) {
                List<com.ruoyi.stock.pojo.StockInRecord> qiRecords = stockInRecordService.list(
                        new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<com.ruoyi.stock.pojo.StockInRecord>()
                                .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<Long> recordIds = stockRecords.stream()
                        .filter(r -> r.getApprovalStatus() == null || r.getApprovalStatus() == 0)
                        .map(com.ruoyi.stock.pojo.StockInRecord::getId)
                        .collect(Collectors.toList());
                if (!recordIds.isEmpty()) {
                    stockInRecordService.batchApprove(recordIds, ReviewStatusEnum.APPROVED.getCode());
                    log.info("采购台账[{}]入库审批通过, 入库记录数: {}", purchaseLedger.getPurchaseContractNumber(), recordIds.size());
                }
            }
        } catch (Exception e) {
            log.warn("入库审批失败[{}]: {}", purchaseLedger.getPurchaseContractNumber(), e.getMessage());
        }
    }
    private ModuleSummary createProductionPlans(List<JSONObject> items) {
        int success = 0, fail = 0;
        for (JSONObject item : items) {