昨天 d5bbd17a1428811da046ec3be3c0cc943a7ae059
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
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;
 
/**
 * 各模块的 AI Prompt 模板
 */
public final class MockDataPrompt {
 
    private MockDataPrompt() {}
 
    public static String buildSystemPrompt() {
        return """
            你是一个企业ERP系统的数据模拟专家。
 
            核心规则(必须严格遵守):
            1. 输出必须是纯JSON数组,不要用markdown代码块包裹,不要有任何其他文字
            2. 每个JSON对象必须包含 "entity" 字段,值为实体类型名
            3. 数据内容要符合指定行业的特征
            4. 金额、数量等数值字段要合理
            5. 同一模块内的实体之间要有引用关系
            6. 不要使用emoji或特殊unicode字符
            7. 最重要:日期只从"指定时间范围"中取,绝对不要用其他年份!
            8. 人员字段(如entryPerson、salesman等)必须从"系统用户列表"中选取,根据角色和部门匹配对应模块,不要编造人名!
            """;
    }
 
    public static String buildUserMessage(List<String> modules, List<String> industries,
                                           int countMin, int countMax,
                                           String dateStart, String dateEnd,
                                           String additionalInfo,
                                           Map<Long, String> productModelIdToCategory,
                                           List<SysUser> systemUsers,
                                           List<String> existingCustomerNames,
                                           List<String> existingSupplierNames) {
        StringBuilder sb = new StringBuilder();
        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("\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("=== 产品规格列表 ===\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, existingCustomerNames));
        }
        if (modules.contains("purchase")) {
            sb.append(buildPurchasePrompt(countMin, countMax, dateStart, dateEnd, existingSupplierNames));
        }
        if (modules.contains("quality")) {
            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, dateStart));
        }
 
        sb.append("\n请直接输出JSON数组:");
        return sb.toString();
    }
 
    /**
     * 将顶级产品名称映射为分类标签
     */
    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 范围内):
            %s注意:不要生成customer实体,只生成salesLedger!
            productData中的productModelId必须选【成品类】规格ID!
            entryPerson和salesman必须从系统用户列表中角色或部门含"销售"的用户中选取!
            {
              "entity": "salesLedger",
              "customerName": "引用上面生成的客户名称",
              "salesContractNo": "XS-YYYYMMDD-001",
              "projectName": "XX项目",
              "entryDate": "%s",
              "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
              }]
            }
            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,
                                              List<String> existingSupplierNames) {
        String namesHint = existingSupplierNames.isEmpty() ? ""
                : "supplierName必须从以下已有供应商中选择: " + String.join("、", existingSupplierNames) + "\n";
        return """
            采购模块格式(所有日期必须在 %s ~ %s 范围内):
            %s注意:不要生成supplier实体,只生成purchaseLedger!
            productData中的productModelId必须选【原材料类】规格ID!
            人员字段必须从系统用户列表中角色或部门含"采购"的用户中选取!
            {
              "entity": "purchaseLedger",
              "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
              }]
            }
            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-YYYYMMDD-001",
              "standardName": "XX产品检验标准",
              "inspectType": 0,
              "remark": "适用于XX行业的质量检验标准"
            }
            inspectType: 0=原材料检验, 1=过程检验, 2=出厂检验。三种类型都要覆盖。
            standardNo中YYYYMMDD替换为 %s。%d-%d条。
            {
              "entity": "qualityTestStandardBinding",
              "productId": 1,
              "testStandardId": 1
            }
            %d-%d条。
            """.formatted(dateStart.replace("-", ""), min, max, min, max);
    }
 
    private static String buildProductionPrompt(int min, int max, String dateStart, String dateEnd) {
        return """
            生产模块格式(所有日期必须在 %s ~ %s 范围内):
            {
              "entity": "productionPlan",
              "productModelId": 1,
              "qtyRequired": 200,
              "requiredDate": "%s",
              "source": "销售",
              "promisedDeliveryDate": "%s",
              "remark": "XX客户订单需求"
            }
            {
              "entity": "productionOrder",
              "productModelId": 1,
              "quantity": 200,
              "planCompleteTime": "%s",
              "remark": "根据生产计划XX生成"
            }
            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-YYYYMMDD-001",
              "warnNum": 50,
              "remark": "安全库存"
            }
            batchNo中YYYYMMDD替换为 %s。%d-%d条。
            """.formatted(dateStart.replace("-", ""), min, max);
    }
}