昨天 d5bbd17a1428811da046ec3be3c0cc943a7ae059
src/main/java/com/ruoyi/mock/prompt/MockDataPrompt.java
@@ -1,6 +1,9 @@
package com.ruoyi.mock.prompt;
import com.ruoyi.project.system.domain.SysUser;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
@@ -12,18 +15,17 @@
    public static String buildSystemPrompt() {
        return """
            你是一个企业ERP系统的数据模拟专家。你需要根据用户提供的行业、数量、时间范围等信息,
            生成符合业务逻辑的模拟数据。
            你是一个企业ERP系统的数据模拟专家。
            要求:
            核心规则(必须严格遵守):
            1. 输出必须是纯JSON数组,不要用markdown代码块包裹,不要有任何其他文字
            2. 每个JSON对象必须包含 "entity" 字段,值为实体类型名
            3. 数据内容要符合指定行业的特征(公司名称、产品名称、联系人等要像该行业的)
            4. 所有日期字段必须在指定的时间范围内,严禁使用范围之外的日期
            5. 金额、数量等数值字段要合理
            6. 同一模块内的实体之间要有引用关系(如销售台账引用客户名称)
            7. 所有字符串字段不要使用emoji或特殊unicode字符
            8. 合同编号、批号等包含日期的字段,必须使用时间范围内的日期,不要使用示例中的具体日期
            3. 数据内容要符合指定行业的特征
            4. 金额、数量等数值字段要合理
            5. 同一模块内的实体之间要有引用关系
            6. 不要使用emoji或特殊unicode字符
            7. 最重要:日期只从"指定时间范围"中取,绝对不要用其他年份!
            8. 人员字段(如entryPerson、salesman等)必须从"系统用户列表"中选取,根据角色和部门匹配对应模块,不要编造人名!
            """;
    }
@@ -31,29 +33,68 @@
                                           int countMin, int countMax,
                                           String dateStart, String dateEnd,
                                           String additionalInfo,
                                           List<Long> productIds,
                                           List<Long> productModelIds) {
                                           Map<Long, String> productModelIdToCategory,
                                           List<SysUser> systemUsers,
                                           List<String> existingCustomerNames,
                                           List<String> existingSupplierNames) {
        StringBuilder sb = new StringBuilder();
        sb.append("请为以下行业生成模拟ERP业务数据:\n");
        sb.append("- 行业: ").append(String.join("、", industries)).append("\n");
        sb.append("- 数据条数: 每种实体 ").append(countMin).append("-").append(countMax).append(" 条\n");
        sb.append("- 时间范围: ").append(dateStart).append(" ~ ").append(dateEnd).append("\n");
        sb.append("指定行业: ").append(String.join("、", industries)).append("\n");
        sb.append("每种实体生成: ").append(countMin).append("-").append(countMax).append(" 条\n");
        sb.append("指定时间范围: ").append(dateStart).append(" 到 ").append(dateEnd);
        sb.append("(所有日期字段必须在这个范围内!禁止使用2023、2024、2025等任何超出范围的年份!)\n");
        if (additionalInfo != null && !additionalInfo.isBlank()) {
            sb.append("- 补充信息: ").append(additionalInfo).append("\n");
            sb.append("补充信息: ").append(additionalInfo).append("\n");
        }
        sb.append("\n");
        sb.append("可用的产品ID列表: ").append(productIds.stream().map(String::valueOf).collect(Collectors.joining(","))).append("\n");
        sb.append("可用的产品规格ID列表: ").append(productModelIds.stream().map(String::valueOf).collect(Collectors.joining(","))).append("\n");
        sb.append("\n");
        // 按顶级产品分类(成品/原材料/半成品)分组列出产品规格
        Map<String, List<Map.Entry<Long, String>>> grouped = productModelIdToCategory.entrySet().stream()
                .collect(Collectors.groupingBy(
                        e -> categorize(e.getValue()),
                        java.util.LinkedHashMap::new,
                        Collectors.toList()));
        sb.append("需要生成的模块: ").append(String.join("、", modules)).append("\n\n");
        sb.append("=== 产品规格列表 ===\n");
        for (Map.Entry<String, List<Map.Entry<Long, String>>> entry : grouped.entrySet()) {
            String label = entry.getKey();
            List<Map.Entry<Long, String>> items = entry.getValue();
            sb.append("【").append(label).append("】: ");
            sb.append(items.stream()
                    .map(e -> e.getKey() + "(" + e.getValue() + ")")
                    .collect(Collectors.joining(", "))).append("\n");
        }
        sb.append("销售台账的productData只能选【成品类】规格,采购台账只能选【原材料类】规格。\n\n");
        // 系统用户列表(含角色和部门)
        sb.append("=== 系统用户列表(人员字段必须从中选取,不要编造人名)===\n");
        for (SysUser u : systemUsers) {
            String roleNames = u.getRoleNames() != null && !u.getRoleNames().isBlank() ? u.getRoleNames() : "无角色";
            String deptNames = u.getDeptNames() != null && !u.getDeptNames().isBlank() ? u.getDeptNames() : "无部门";
            sb.append(u.getNickName()).append("(角色: ").append(roleNames).append(", 部门: ").append(deptNames).append(")\n");
        }
        sb.append("选择规则:\n");
        sb.append("- 销售模块的人员(entryPerson、salesman)选角色或部门含\"销售\"的用户\n");
        sb.append("- 采购模块的人员选角色或部门含\"采购\"的用户\n");
        sb.append("- 生产模块的人员选角色或部门含\"生产\"的用户\n");
        sb.append("- 质量模块的人员选角色或部门含\"质量\"或\"质检\"的用户\n");
        sb.append("- 仓库模块的人员选角色或部门含\"仓库\"或\"库存\"的用户\n\n");
        if (!existingCustomerNames.isEmpty()) {
            sb.append("=== 已有客户(只能引用,不要生成新customer实体)===\n");
            sb.append(String.join("、", existingCustomerNames)).append("\n\n");
        }
        if (!existingSupplierNames.isEmpty()) {
            sb.append("=== 已有供应商(只能引用,不要生成新supplier实体)===\n");
            sb.append(String.join("、", existingSupplierNames)).append("\n\n");
        }
        sb.append("数据模块: ").append(String.join("、", modules)).append("\n\n");
        if (modules.contains("sales")) {
            sb.append(buildSalesPrompt(countMin, countMax, dateStart, dateEnd));
            sb.append(buildSalesPrompt(countMin, countMax, dateStart, dateEnd, existingCustomerNames));
        }
        if (modules.contains("purchase")) {
            sb.append(buildPurchasePrompt(countMin, countMax, dateStart, dateEnd));
            sb.append(buildPurchasePrompt(countMin, countMax, dateStart, dateEnd, existingSupplierNames));
        }
        if (modules.contains("quality")) {
            sb.append(buildQualityPrompt(countMin, countMax, dateStart));
@@ -65,130 +106,105 @@
            sb.append(buildStockPrompt(countMin, countMax, dateStart));
        }
        sb.append("\n请直接输出JSON数组,不要有任何其他内容:");
        sb.append("\n请直接输出JSON数组:");
        return sb.toString();
    }
    private static String buildSalesPrompt(int min, int max, String dateStart, String dateEnd) {
    /**
     * 将顶级产品名称映射为分类标签
     */
    private static String categorize(String rootProductName) {
        if (rootProductName == null) return "其他类";
        if (rootProductName.contains("成品") || rootProductName.contains("产成品")) return "成品类-销售优先选这些";
        if (rootProductName.contains("原料") || rootProductName.contains("原材料") || rootProductName.contains("材料")) return "原材料类-采购优先选这些";
        if (rootProductName.contains("半成品")) return "半成品类";
        return "其他类";
    }
    private static String buildSalesPrompt(int min, int max, String dateStart, String dateEnd,
                                            List<String> existingCustomerNames) {
        String namesHint = existingCustomerNames.isEmpty() ? ""
                : "customerName必须从以下已有客户中选择: " + String.join("、", existingCustomerNames) + "\n";
        return """
            销售模块 - 按以下格式生成(注意:示例中的日期仅作格式参考,实际日期必须在 %s ~ %s 范围内):
            {
              "entity": "customer",
              "customerName": "XX科技有限公司",
              "customerType": "企业客户",
              "contactPerson": "张三",
              "contactPhone": "13800138000",
              "companyAddress": "XX省XX市XX区XX路XX号",
              "companyPhone": "0513-XXXXXXXX",
              "taxpayerIdentificationNumber": "91110108XXXXXXXXXX",
              "maintainer": "李四",
              "maintenanceTime": "%s",
              "bankAccount": "622202XXXXXXXXXXXX",
              "basicBankAccount": "XX银行XX支行",
              "bankCode": "308100XXXXXX"
            }
            销售模块格式(所有日期必须在 %s ~ %s 范围内):
            %s注意:不要生成customer实体,只生成salesLedger!
            productData中的productModelId必须选【成品类】规格ID!
            entryPerson和salesman必须从系统用户列表中角色或部门含"销售"的用户中选取!
            {
              "entity": "salesLedger",
              "customerName": "引用上面生成的客户名称",
              "salesContractNo": "XS-年月日-序号",
              "salesContractNo": "XS-YYYYMMDD-001",
              "projectName": "XX项目",
              "entryDate": "%s",
              "entryPerson": "张三",
              "salesman": "销售员姓名",
              "entryPerson": "从用户列表中选",
              "salesman": "从用户列表中选",
              "contractAmount": 50000.00,
              "paymentMethod": "月结30天",
              "executionDate": "%s",
              "deliveryDate": "%s",
              "type": 1,
              "productData": [
                {
                  "productId": 1,
                  "productModelId": 1,
                  "quantity": 100,
                  "taxInclusiveUnitPrice": 500.00,
                  "taxInclusiveTotalPrice": 50000.00,
                  "taxExclusiveTotalPrice": 44247.79,
                  "taxRate": 13.00,
                  "unit": "件",
                  "type": 1
                }
              ]
              "productData": [{
                "productId": 1, "productModelId": 1, "quantity": 100,
                "taxInclusiveUnitPrice": 500.00, "taxInclusiveTotalPrice": 50000.00,
                "taxExclusiveTotalPrice": 44247.79, "taxRate": 13.00, "unit": "件", "type": 1
              }]
            }
            客户名称要像指定行业的公司,合同编号格式XS-年月日-序号(年月日取entryDate的日期),金额合理。
            注意 taxExclusiveTotalPrice 是不含税总价,需要根据含税总价和税率计算(=含税总价/(1+税率/100))。
            日期范围: %s ~ %s,每种实体生成%d-%d条。
            """.formatted(dateStart, dateEnd, dateStart, dateStart, dateStart, dateEnd, dateStart, dateEnd, min, max);
            salesContractNo中YYYYMMDD替换为entryDate的日期。%d-%d条。
            """.formatted(dateStart, dateEnd, namesHint, dateStart, dateStart, dateEnd, min, max);
    }
    private static String buildPurchasePrompt(int min, int max, String dateStart, String dateEnd) {
    private static String buildPurchasePrompt(int min, int max, String dateStart, String dateEnd,
                                              List<String> existingSupplierNames) {
        String namesHint = existingSupplierNames.isEmpty() ? ""
                : "supplierName必须从以下已有供应商中选择: " + String.join("、", existingSupplierNames) + "\n";
        return """
            采购模块 - 按以下格式生成(注意:示例中的日期仅作格式参考,实际日期必须在 %s ~ %s 范围内):
            {
              "entity": "supplier",
              "supplierName": "XX原材料有限公司",
              "supplierType": "原材料供应商",
              "contactUserName": "李四",
              "contactUserPhone": "13900139000",
              "companyAddress": "XX省XX市XX区XX路XX号",
              "companyPhone": "0513-XXXXXXXX",
              "taxpayerIdentificationNum": "91110108XXXXXXXXXX",
              "bankAccountName": "XX银行XX支行",
              "bankAccountNum": "622202XXXXXXXXXXXX",
              "isWhite": 0
            }
            采购模块格式(所有日期必须在 %s ~ %s 范围内):
            %s注意:不要生成supplier实体,只生成purchaseLedger!
            productData中的productModelId必须选【原材料类】规格ID!
            人员字段必须从系统用户列表中角色或部门含"采购"的用户中选取!
            {
              "entity": "purchaseLedger",
              "supplierName": "引用上面生成的供应商名称",
              "purchaseContractNumber": "CG-年月日-序号",
              "supplierName": "从已有供应商中选择",
              "purchaseContractNumber": "CG-YYYYMMDD-001",
              "projectName": "XX采购项目",
              "entryDate": "%s",
              "contractAmount": 30000.00,
              "paymentMethod": "货到付款",
              "executionDate": "%s",
              "productData": [
                {
                  "productId": 1,
                  "productModelId": 1,
                  "quantity": 50,
                  "taxInclusiveUnitPrice": 600.00,
                  "taxInclusiveTotalPrice": 30000.00,
                  "taxExclusiveTotalPrice": 26548.67,
                  "taxRate": 13.00,
                  "unit": "件",
                  "type": 2
                }
              ]
              "productData": [{
                "productId": 1, "productModelId": 1, "quantity": 50,
                "taxInclusiveUnitPrice": 600.00, "taxInclusiveTotalPrice": 30000.00,
                "taxExclusiveTotalPrice": 26548.67, "taxRate": 13.00, "unit": "件", "type": 2
              }]
            }
            供应商名称要像指定行业的供应商,合同编号格式CG-年月日-序号(年月日取entryDate的日期)。
            注意 taxExclusiveTotalPrice 是不含税总价,需要根据含税总价和税率计算(=含税总价/(1+税率/100))。
            日期范围: %s ~ %s,每种实体生成%d-%d条。
            """.formatted(dateStart, dateEnd, dateStart, dateStart, dateStart, dateEnd, min, max);
            purchaseContractNumber中YYYYMMDD替换为entryDate的日期。%d-%d条。
            """.formatted(dateStart, dateEnd, namesHint, dateStart, dateStart, min, max);
    }
    private static String buildQualityPrompt(int min, int max, String dateStart) {
        return """
            质量模块 - 按以下格式生成:
            质量模块格式:
            {
              "entity": "qualityTestStandard",
              "standardNo": "QTS-年月日-序号",
              "standardNo": "QTS-YYYYMMDD-001",
              "standardName": "XX产品检验标准",
              "inspectType": 0,
              "remark": "适用于XX行业的质量检验标准"
            }
            inspectType: 0=原材料检验, 1=过程检验, 2=出厂检验。三种类型都要覆盖。
            standardNo中的年月日使用 %s 这个日期。
            standardNo中YYYYMMDD替换为 %s。%d-%d条。
            {
              "entity": "qualityTestStandardBinding",
              "productId": 1,
              "testStandardId": 1
            }
            每种实体生成%d-%d条。
            """.formatted(dateStart, min, max);
            %d-%d条。
            """.formatted(dateStart.replace("-", ""), min, max, min, max);
    }
    private static String buildProductionPrompt(int min, int max, String dateStart, String dateEnd) {
        return """
            生产模块 - 按以下格式生成(注意:示例中的日期仅作格式参考,实际日期必须在 %s ~ %s 范围内):
            生产模块格式(所有日期必须在 %s ~ %s 范围内):
            {
              "entity": "productionPlan",
              "productModelId": 1,
@@ -205,23 +221,22 @@
              "planCompleteTime": "%s",
              "remark": "根据生产计划XX生成"
            }
            productionPlan的source可选"销售"或"内部"。
            日期范围: %s ~ %s,每种实体生成%d-%d条。
            """.formatted(dateStart, dateEnd, dateStart, dateEnd, dateEnd, dateStart, dateEnd, min, max);
            source可选"销售"或"内部"。%d-%d条。
            """.formatted(dateStart, dateEnd, dateStart, dateEnd, dateEnd, min, max);
    }
    private static String buildStockPrompt(int min, int max, String dateStart) {
        return """
            库存模块 - 按以下格式生成:
            库存模块格式:
            {
              "entity": "stockInventory",
              "productModelId": 1,
              "qualitity": 500,
              "batchNo": "BATCH-年月日-序号",
              "batchNo": "BATCH-YYYYMMDD-001",
              "warnNum": 50,
              "remark": "安全库存"
            }
            每种实体生成%d-%d条。batchNo格式BATCH-年月日-序号,年月日使用 %s 这个日期。
            """.formatted(min, max, dateStart);
            batchNo中YYYYMMDD替换为 %s。%d-%d条。
            """.formatted(dateStart.replace("-", ""), min, max);
    }
}