3 天以前 33250ae12ae0649010436bcc636e44d6acd6d86b
修改客户,销售,协同,报价,质量
已添加14个文件
1539 ■■■■■ 文件已修改
doc/20260615_add_plan_person_and_executor_to_routing_operation.sql 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/mock_data_check.md 167 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/mock_data_generate.md 243 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/routing_operation_plan_person.md 91 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/mock/controller/DataCheckController.java 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/mock/dto/DataCheckRequest.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/mock/dto/DataGenerateRequest.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/mock/prompt/MockDataPrompt.java 213 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/mock/service/DataCheckService.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/mock/service/DataGenerateService.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/mock/service/impl/DataCheckServiceImpl.java 146 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/mock/service/impl/DataGenerateServiceImpl.java 466 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/mock/vo/DataCheckResult.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/mock/vo/DataGenerateResult.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/20260615_add_plan_person_and_executor_to_routing_operation.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,8 @@
# ç”Ÿäº§å·¥åºæ–°å¢žè®¡åˆ’人员和计划执行人员字段
ALTER TABLE technology_routing_operation
    ADD plan_person bigint NULL COMMENT '计划人员ID',
    ADD executor bigint NULL COMMENT '计划执行人员ID';
ALTER TABLE production_order_routing_operation
    ADD plan_person bigint NULL COMMENT '计划人员ID',
    ADD executor bigint NULL COMMENT '计划执行人员ID';
docs/mock_data_check.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,167 @@
# æ•°æ®æ¨¡æ‹Ÿ - åŸºç¡€æ•°æ®æ£€æµ‹
## æ¶‰åŠé¡µé¢
- æ•°æ®æ¨¡æ‹Ÿæ“ä½œé¡µé¢ï¼ˆæ–°å¢žï¼‰
## API
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| POST | /mock/dataCheck | æ£€æµ‹æŒ‡å®šæ¨¡å—的基础数据是否就绪 |
**请求参数:**
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| modules | List\<String\> | æ˜¯ | è¦æ£€æµ‹çš„æ¨¡å—列表,可选值:sales(销售)、purchase(采购)、quality(质量) |
请求体示例:
```json
{ "modules": ["sales", "purchase", "quality"] }
```
**响应:**
```json
{
  "code": 200,
  "msg": "操作成功",
  "data": {
    "totalItems": 6,
    "passedItems": 3,
    "items": [
      {
        "module": "common",
        "itemName": "产品数据",
        "minRequired": 1,
        "currentCount": 5,
        "passed": true,
        "message": "通过"
      },
      {
        "module": "sales",
        "itemName": "客户数据",
        "minRequired": 1,
        "currentCount": 0,
        "passed": false,
        "message": "缺少客户数据,请先在【基础数据-客户管理】中添加至少1条客户"
      }
    ]
  }
}
```
## æ£€æµ‹è§„则
### é”€å”®æ¨¡å— (sales)
| æ£€æµ‹é¡¹ | æœ€ä½Žæ•°é‡ | æœªé€šè¿‡æç¤º |
|--------|----------|------------|
| äº§å“æ•°æ® | 1 | ç¼ºå°‘产品数据,请先在【基础数据-产品管理】中添加至少1条产品 |
| å®¢æˆ·æ•°æ® | 1 | ç¼ºå°‘客户数据,请先在【基础数据-客户管理】中添加至少1条客户 |
| æŠ¥ä»·å®¡æ‰¹æ¨¡æ¿ | 1 | ç¼ºå°‘报价审批模板,请先在【系统管理-审批模板】中创建报价审批模板 |
| å‘货审批模板 | 1 | ç¼ºå°‘发货审批模板,请先在【系统管理-审批模板】中创建发货审批模板 |
### é‡‡è´­æ¨¡å— (purchase)
| æ£€æµ‹é¡¹ | æœ€ä½Žæ•°é‡ | æœªé€šè¿‡æç¤º |
|--------|----------|------------|
| äº§å“æ•°æ® | 1 | ç¼ºå°‘产品数据,请先在【基础数据-产品管理】中添加至少1条产品 |
| ä¾›åº”商数据 | 1 | ç¼ºå°‘供应商数据,请先在【基础数据-供应商管理】中添加至少1条供应商 |
| é‡‡è´­å®¡æ‰¹æ¨¡æ¿ | 1 | ç¼ºå°‘采购审批模板,请先在【系统管理-审批模板】中创建采购审批模板 |
### è´¨é‡æ¨¡å— (quality)
| æ£€æµ‹é¡¹ | æœ€ä½Žæ•°é‡ | æœªé€šè¿‡æç¤º |
|--------|----------|------------|
| äº§å“æ•°æ® | 1 | ç¼ºå°‘产品数据,请先在【基础数据-产品管理】中添加至少1条产品 |
| æ£€æµ‹æ ‡å‡† | 1 | ç¼ºå°‘检测标准,请先在【质量管理-检测标准】中创建检测标准 |
| æŒ‡æ ‡ç»‘定 | 1 | ç¼ºå°‘指标绑定,请先在【质量管理-检测标准绑定】中将检测标准与产品绑定 |
## å‰ç«¯ä¿®æ”¹ç‚¹
### 1. æ•°æ®æ¨¡æ‹Ÿé¡µé¢å…¥å£
在左侧菜单新增"数据模拟"菜单项,路由 `/mock`。
### 2. æ•°æ®æ£€æµ‹åŒºåŸŸ
```html
<template>
  <div class="mock-container">
    <el-card header="基础数据检测">
      <el-checkbox-group v-model="selectedModules">
        <el-checkbox label="sales">销售模块</el-checkbox>
        <el-checkbox label="purchase">采购模块</el-checkbox>
        <el-checkbox label="quality">质量模块</el-checkbox>
      </el-checkbox-group>
      <el-button type="primary" @click="handleCheck" :loading="checking">
        å¼€å§‹æ£€æµ‹
      </el-button>
    </el-card>
    <el-card v-if="checkResult" header="检测结果" style="margin-top: 16px">
      <el-alert
        :title="`通过 ${checkResult.passedItems} / ${checkResult.totalItems} é¡¹`"
        :type="checkResult.passedItems === checkResult.totalItems ? 'success' : 'warning'"
        :closable="false"
        show-icon
      />
      <el-table :data="checkResult.items" style="margin-top: 12px">
        <el-table-column prop="module" label="模块" width="100" />
        <el-table-column prop="itemName" label="检测项" width="160" />
        <el-table-column prop="minRequired" label="最低要求" width="80" />
        <el-table-column prop="currentCount" label="当前数量" width="80" />
        <el-table-column label="状态" width="80">
          <template #default="{ row }">
            <el-tag :type="row.passed ? 'success' : 'danger'">
              {{ row.passed ? '通过' : '未通过' }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="message" label="提示" />
      </el-table>
    </el-card>
  </div>
</template>
```
### 3. data æ•°æ®
```js
data() {
  return {
    selectedModules: ['sales', 'purchase', 'quality'],
    checking: false,
    checkResult: null,
  }
}
```
### 4. æ–¹æ³•
```js
import request from '@/utils/request'
methods: {
  async handleCheck() {
    if (this.selectedModules.length === 0) {
      this.$message.warning('请至少选择一个模块')
      return
    }
    this.checking = true
    try {
      const res = await request.post('/mock/dataCheck', {
        modules: this.selectedModules
      })
      this.checkResult = res.data
    } finally {
      this.checking = false
    }
  }
}
```
## æ³¨æ„äº‹é¡¹
- äº§å“æ•°æ®ï¼ˆå…¬å…±åŸºç¡€ï¼‰åœ¨å¤šä¸ªæ¨¡å—间共享,检测结果中只出现一次,不会重复展示
- æ£€æµ‹ä»…做只读查询,不写入任何数据
- æ¨¡å—参数为空数组时返回空检测列表
- å»ºè®®åœ¨æ•°æ®æ¨¡æ‹Ÿå¼€å§‹å‰å…ˆè°ƒç”¨æ­¤æŽ¥å£ç¡®è®¤åŸºç¡€æ•°æ®å°±ç»ª
docs/mock_data_generate.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,243 @@
# æ•°æ®æ¨¡æ‹Ÿ - AI æ•°æ®ç”Ÿæˆ
## æ¶‰åŠé¡µé¢
- æ•°æ®æ¨¡æ‹Ÿæ“ä½œé¡µé¢ï¼ˆæ–°å¢ž/修改)
## API
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| POST | /mock/generate | AI ç”Ÿæˆæ¨¡æ‹Ÿæ•°æ®ï¼ˆå«å‰ç½®åŸºç¡€æ•°æ®æ£€æµ‹ï¼‰ |
**请求参数:**
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| modules | List\<String\> | æ˜¯ | è¦ç”Ÿæˆæ•°æ®çš„æ¨¡å—:sales(销售)、purchase(采购)、quality(质量)、production(生产)、stock(库存) |
| industries | List\<String\> | æ˜¯ | è¡Œä¸šï¼Œå¦‚:["机械制造", "食品加工", "电子装配"] |
| countMin | int | å¦ | æ¯ä¸ªå®žä½“生成条数最小值,默认 3 |
| countMax | int | å¦ | æ¯ä¸ªå®žä½“生成条数最大值,默认 10 |
| dateStart | String | å¦ | æ—¶é—´èŒƒå›´å¼€å§‹ï¼ˆyyyy-MM-dd) |
| dateEnd | String | å¦ | æ—¶é—´èŒƒå›´ç»“束(yyyy-MM-dd) |
| additionalInfo | String | å¦ | å…¶ä»–补充描述,如"产品以金属零部件为主" |
请求体示例:
```json
{
  "modules": ["sales", "purchase", "quality"],
  "industries": ["机械制造", "电子装配"],
  "countMin": 3,
  "countMax": 8,
  "dateStart": "2026-01-01",
  "dateEnd": "2026-06-01",
  "additionalInfo": "产品以金属零部件和电子元器件为主"
}
```
**响应(成功):**
```json
{
  "code": 200,
  "msg": "操作成功",
  "data": {
    "status": "SUCCESS",
    "totalGenerated": 24,
    "moduleSummaries": [
      { "module": "sales", "entityName": "客户", "generatedCount": 5, "successCount": 5, "failCount": 0 },
      { "module": "sales", "entityName": "销售台账", "generatedCount": 5, "successCount": 5, "failCount": 0 },
      { "module": "purchase", "entityName": "供应商", "generatedCount": 4, "successCount": 4, "failCount": 0 },
      { "module": "purchase", "entityName": "采购台账", "generatedCount": 4, "successCount": 4, "failCount": 0 },
      { "module": "quality", "entityName": "检测标准", "generatedCount": 3, "successCount": 3, "failCount": 0 },
      { "module": "quality", "entityName": "指标绑定", "generatedCount": 3, "successCount": 3, "failCount": 0 }
    ],
    "errors": [],
    "checkResult": null
  }
}
```
**响应(基础数据不足,前置检测未通过):**
```json
{
  "code": 500,
  "msg": "基础数据不足,请先补充后再生成",
  "data": {
    "status": "CHECK_FAILED",
    "checkResult": {
      "totalItems": 6,
      "passedItems": 3,
      "items": [
        { "module": "common", "itemName": "产品数据", "passed": true, "message": "通过" },
        { "module": "sales", "itemName": "客户数据", "passed": false, "message": "缺少客户数据,请先..." }
      ]
    }
  }
}
```
## å·¥ä½œæµç¨‹
1. ç”¨æˆ·åœ¨é¡µé¢é€‰æ‹©æ¨¡å— + å¡«å†™è¡Œä¸š/数量/时间等信息
2. è°ƒç”¨ `POST /mock/generate`
3. åŽç«¯é¦–先调用 `POST /mock/dataCheck` æ£€æµ‹åŸºç¡€æ•°æ®æ˜¯å¦å°±ç»ª
4. è‹¥åŸºç¡€æ•°æ®ä¸è¶³ï¼Œè¿”回检测结果,提示用户先补齐
5. è‹¥åŸºç¡€æ•°æ®å°±ç»ªï¼Œè°ƒç”¨ AI å¤§æ¨¡åž‹ç”Ÿæˆç¬¦åˆè¡Œä¸šç‰¹å¾çš„ JSON æ•°æ®
6. æŒ‰ä¾èµ–顺序创建数据(客户→销售台账、供应商→采购台账、检测标准→指标绑定...)
7. è¿”回生成摘要
## å‰ç«¯ä¿®æ”¹ç‚¹
### 1. æ•°æ®ç”ŸæˆåŒºåŸŸï¼ˆåœ¨æ•°æ®æ£€æµ‹åŒºåŸŸä¸‹æ–¹ï¼‰
```html
<el-card header="AI æ•°æ®ç”Ÿæˆ" style="margin-top: 16px">
  <el-form :model="generateForm" label-width="100px">
    <el-form-item label="选择模块">
      <el-checkbox-group v-model="generateForm.modules">
        <el-checkbox label="sales">销售</el-checkbox>
        <el-checkbox label="purchase">采购</el-checkbox>
        <el-checkbox label="quality">质量</el-checkbox>
        <el-checkbox label="production">生产</el-checkbox>
        <el-checkbox label="stock">库存</el-checkbox>
      </el-checkbox-group>
    </el-form-item>
    <el-form-item label="行业">
      <el-select
        v-model="generateForm.industries"
        multiple
        filterable
        allow-create
        placeholder="输入或选择行业"
      >
        <el-option label="机械制造" value="机械制造" />
        <el-option label="食品加工" value="食品加工" />
        <el-option label="电子装配" value="电子装配" />
        <el-option label="汽车零部件" value="汽车零部件" />
        <el-option label="医疗器械" value="医疗器械" />
        <el-option label="化工材料" value="化工材料" />
        <el-option label="纺织服装" value="纺织服装" />
        <el-option label="家具制造" value="家具制造" />
      </el-select>
    </el-form-item>
    <el-row :gutter="16">
      <el-col :span="12">
        <el-form-item label="生成条数">
          <el-slider
            v-model="generateForm.countRange"
            range
            :min="1"
            :max="50"
            :marks="{1:'1', 10:'10', 20:'20', 50:'50'}"
          />
        </el-form-item>
      </el-col>
      <el-col :span="12">
        <el-form-item label="时间范围">
          <el-date-picker
            v-model="generateForm.dateRange"
            type="daterange"
            range-separator="至"
            start-placeholder="开始日期"
            end-placeholder="结束日期"
            value-format="yyyy-MM-dd"
          />
        </el-form-item>
      </el-col>
    </el-row>
    <el-form-item label="补充信息">
      <el-input
        v-model="generateForm.additionalInfo"
        type="textarea"
        :rows="2"
        placeholder="如:产品以金属零部件为主,客户集中在华东地区"
      />
    </el-form-item>
    <el-form-item>
      <el-button type="primary" @click="handleGenerate" :loading="generating">
        å¼€å§‹ç”Ÿæˆ
      </el-button>
      <el-button @click="handleCheckFirst" :loading="checking">
        å…ˆæ£€æµ‹å†ç”Ÿæˆ
      </el-button>
    </el-form-item>
  </el-form>
</el-card>
```
### 3. data æ•°æ®
```js
data() {
  return {
    generateForm: {
      modules: [],
      industries: [],
      countRange: [3, 10],
      dateRange: [],
      additionalInfo: '',
    },
    generating: false,
    generateResult: null,
  }
}
```
### 4. æ–¹æ³•
```js
methods: {
  // ç›´æŽ¥ç”Ÿæˆï¼ˆå†…部自动检测)
  async handleGenerate() {
    if (this.generateForm.modules.length === 0) {
      this.$message.warning('请至少选择一个模块')
      return
    }
    this.generating = true
    try {
      const res = await request.post('/mock/generate', {
        modules: this.generateForm.modules,
        industries: this.generateForm.industries,
        countMin: this.generateForm.countRange[0],
        countMax: this.generateForm.countRange[1],
        dateStart: this.generateForm.dateRange[0] || null,
        dateEnd: this.generateForm.dateRange[1] || null,
        additionalInfo: this.generateForm.additionalInfo,
      })
      if (res.code === 200) {
        this.generateResult = res.data
        this.$message.success(`成功生成 ${res.data.totalGenerated} æ¡æ•°æ®`)
      } else {
        // åŸºç¡€æ•°æ®ä¸è¶³
        this.generateResult = res.data
        this.checkResult = res.data.checkResult
        this.$message.warning(res.msg)
      }
    } finally {
      this.generating = false
    }
  },
  // å…ˆæ£€æµ‹å†ç”Ÿæˆ
  async handleCheckFirst() {
    await this.handleCheck()
    if (this.checkResult && this.checkResult.passedItems === this.checkResult.totalItems) {
      await this.handleGenerate()
    } else {
      this.$message.warning('请先补齐基础数据后再生成')
    }
  },
}
```
## æ³¨æ„äº‹é¡¹
- `POST /mock/generate` å†…部会自动调用数据检测,不需要手动分两步走
- AI ç”Ÿæˆçš„æ•°æ®ä¼šå°½é‡ç¬¦åˆè¡Œä¸šç‰¹å¾ï¼Œä½†å»ºè®®ç”ŸæˆåŽæ£€æŸ¥å…³é”®æ•°æ®çš„合理性
- ç”Ÿäº§æ¨¡å—和库存模块依赖产品数据(Product/ProductModel),请确保基础数据中有产品
- é”€å”®å°è´¦çš„合同金额由产品明细自动计算,AI åªéœ€ç”Ÿæˆäº§å“è¡Œçš„单价和数量
- äº§ç”Ÿçš„æ•°æ®é€šè¿‡æœåŠ¡å±‚åˆ›å»ºï¼Œä¼šè‡ªåŠ¨å¡«å…… tenantId/deptId/createUser ç­‰å­—段
docs/routing_operation_plan_person.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,91 @@
# ç”Ÿäº§å·¥åºæ–°å¢žè®¡åˆ’人员和计划执行人员
## æ¶‰åŠé¡µé¢
- å·¥è‰ºè·¯çº¿å·¥åºç®¡ç†é¡µé¢ï¼ˆtechnology_routing_operation)
## API
涉及以下接口的请求/响应新增字段:
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| GET | /technologyRoutingOperation/page | åˆ†é¡µæŸ¥è¯¢ |
| GET | /technologyRoutingOperation/list | åˆ—表查询 |
| GET | /technologyRoutingOperation/{id} | è¯¦æƒ… |
| POST | /technologyRoutingOperation/add | æ–°å¢ž |
| PUT | /technologyRoutingOperation | ä¿®æ”¹ |
**新增响应/请求字段:**
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| planPerson | Long | å¦ | è®¡åˆ’人员ID |
| executor | Long | å¦ | è®¡åˆ’执行人员ID |
| planPersonName | String | å¦ | è®¡åˆ’人员姓名(响应字段,后端自动关联查询) |
| executorName | String | å¦ | è®¡åˆ’执行人员姓名(响应字段,后端自动关联查询) |
## å‰ç«¯ä¿®æ”¹ç‚¹
### 1. è¡¨å•新增字段
在工序表单中新增两个人员选择下拉框:
```html
<el-form-item label="计划人员" prop="planPerson">
  <el-select v-model="processForm.planPerson"
             placeholder="请选择计划人员"
             clearable
             filterable
             style="width: 100%">
    <el-option v-for="item in employeeOptions"
               :key="item.id"
               :label="item.staffName"
               :value="item.id" />
  </el-select>
</el-form-item>
<el-form-item label="计划执行人员" prop="executor">
  <el-select v-model="processForm.executor"
             placeholder="请选择计划执行人员"
             clearable
             filterable
             style="width: 100%">
    <el-option v-for="item in employeeOptions"
               :key="item.id"
               :label="item.staffName"
               :value="item.id" />
  </el-select>
</el-form-item>
```
### 2. data æ•°æ®
```js
data() {
  return {
    processForm: {
      planPerson: null,
      executor: null,
      // ...其他字段
    },
    employeeOptions: [],  // å‘˜å·¥åˆ—表,需从接口获取
  }
}
```
### 3. è¡¨æ ¼åˆ—新增
列表中可直接使用后端返回的姓名展示:
```html
<el-table-column label="计划人员" prop="planPersonName" />
<el-table-column label="计划执行人员" prop="executorName" />
```
## æ³¨æ„äº‹é¡¹
- `planPerson` å’Œ `executor` å­˜å‚¨çš„æ˜¯å‘˜å·¥ID(Long类型),提交表单时传ID
- å“åº”中 `planPersonName` å’Œ `executorName` ç”±åŽç«¯é€šè¿‡ `staff_on_job` è¡¨è‡ªåŠ¨å…³è”æŸ¥è¯¢ï¼Œå‰ç«¯æ— éœ€é¢å¤–è½¬æ¢
- å‘˜å·¥ä¸‹æ‹‰åˆ—表数据需从员工接口获取(参考现有 `employeeOptions` æ•°æ®æºï¼‰
- æ•°æ®åº“迁移SQL:`doc/20260615_add_plan_person_and_executor_to_routing_operation.sql`
- ç”Ÿäº§è®¢å•工序(`production_order_routing_operation`)同步支持这两个字段
src/main/java/com/ruoyi/mock/controller/DataCheckController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,54 @@
package com.ruoyi.mock.controller;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.mock.dto.DataCheckRequest;
import com.ruoyi.mock.dto.DataGenerateRequest;
import com.ruoyi.mock.service.DataCheckService;
import com.ruoyi.mock.service.DataGenerateService;
import com.ruoyi.mock.vo.DataCheckResult;
import com.ruoyi.mock.vo.DataCheckResult.CheckItem;
import com.ruoyi.mock.vo.DataGenerateResult;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/mock")
@RequiredArgsConstructor
@Tag(name = "数据模拟")
public class DataCheckController {
    private final DataCheckService dataCheckService;
    private final DataGenerateService dataGenerateService;
    @PostMapping("/dataCheck")
    @Operation(summary = "检测基础数据")
    public R<DataCheckResult> dataCheck(@RequestBody DataCheckRequest request) {
        DataCheckResult result = dataCheckService.check(request);
        return R.ok(result);
    }
    @PostMapping("/generate")
    @Operation(summary = "AI生成模拟数据")
    public R<?> generate(@RequestBody DataGenerateRequest request) {
        // å…ˆæ ¡éªŒåŸºç¡€æ•°æ®
        DataCheckRequest checkRequest = new DataCheckRequest();
        checkRequest.setModules(request.getModules());
        DataCheckResult checkResult = dataCheckService.check(checkRequest);
        boolean allPassed = checkResult.getItems().stream().allMatch(CheckItem::isPassed);
        if (!allPassed) {
            DataGenerateResult failResult = new DataGenerateResult();
            failResult.setStatus("CHECK_FAILED");
            failResult.setCheckResult(checkResult);
            return R.fail(failResult, "基础数据不足,请先补充后再生成");
        }
        DataGenerateResult result = dataGenerateService.generate(request);
        return R.ok(result);
    }
}
src/main/java/com/ruoyi/mock/dto/DataCheckRequest.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,15 @@
package com.ruoyi.mock.dto;
import lombok.Data;
import java.util.List;
/**
 * åŸºç¡€æ•°æ®æ£€æµ‹è¯·æ±‚
 */
@Data
public class DataCheckRequest {
    /** éœ€è¦æ£€æµ‹çš„æ¨¡å—: sales / purchase / quality */
    private List<String> modules;
}
src/main/java/com/ruoyi/mock/dto/DataGenerateRequest.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,33 @@
package com.ruoyi.mock.dto;
import lombok.Data;
import java.util.List;
/**
 * AI数据生成请求
 */
@Data
public class DataGenerateRequest {
    /** è¦ç”Ÿæˆæ•°æ®çš„æ¨¡å—: sales / purchase / quality / production / stock */
    private List<String> modules;
    /** è¡Œä¸šï¼ˆå¤šä¸ªï¼‰ï¼Œå¦‚: ["机械制造", "食品加工", "电子装配"] */
    private List<String> industries;
    /** æ¯ä¸ªå®žä½“生成数量的最小值 */
    private int countMin;
    /** æ¯ä¸ªå®žä½“生成数量的最大值 */
    private int countMax;
    /** æ—¶é—´èŒƒå›´-开始, yyyy-MM-dd */
    private String dateStart;
    /** æ—¶é—´èŒƒå›´-结束, yyyy-MM-dd */
    private String dateEnd;
    /** å…¶ä»–补充描述 */
    private String additionalInfo;
}
src/main/java/com/ruoyi/mock/prompt/MockDataPrompt.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,213 @@
package com.ruoyi.mock.prompt;
import java.util.List;
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. åŒä¸€æ¨¡å—内的实体之间要有引用关系(如销售台账引用客户名称)
            7. æ‰€æœ‰å­—符串字段不要使用emoji或特殊unicode字符
            """;
    }
    public static String buildUserMessage(List<String> modules, List<String> industries,
                                           int countMin, int countMax,
                                           String dateStart, String dateEnd,
                                           String additionalInfo,
                                           List<Long> productIds,
                                           List<Long> productModelIds) {
        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");
        if (additionalInfo != null && !additionalInfo.isBlank()) {
            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");
        sb.append("需要生成的模块: ").append(String.join("、", modules)).append("\n\n");
        if (modules.contains("sales")) {
            sb.append(buildSalesPrompt(countMin, countMax, dateStart, dateEnd));
        }
        if (modules.contains("purchase")) {
            sb.append(buildPurchasePrompt(countMin, countMax, dateStart, dateEnd));
        }
        if (modules.contains("quality")) {
            sb.append(buildQualityPrompt(countMin, countMax));
        }
        if (modules.contains("production")) {
            sb.append(buildProductionPrompt(countMin, countMax, dateStart, dateEnd));
        }
        if (modules.contains("stock")) {
            sb.append(buildStockPrompt(countMin, countMax));
        }
        sb.append("\n请直接输出JSON数组,不要有任何其他内容:");
        return sb.toString();
    }
    private static String buildSalesPrompt(int min, int max, String dateStart, String dateEnd) {
        return """
            é”€å”®æ¨¡å— - æŒ‰ä»¥ä¸‹æ ¼å¼ç”Ÿæˆ:
            {
              "entity": "customer",
              "customerName": "XX科技有限公司",
              "customerType": "企业客户",
              "contactPerson": "张三",
              "contactPhone": "13800138000",
              "companyAddress": "XX省XX市XX区XXè·¯XX号",
              "taxpayerIdentificationNumber": "91110108XXXXXXXXXX"
            }
            {
              "entity": "salesLedger",
              "customerName": "引用上面生成的客户名称",
              "salesContractNo": "XS-20260601-001",
              "projectName": "XX项目",
              "entryDate": "2026-06-01",
              "salesman": "销售员姓名",
              "contractAmount": 50000.00,
              "paymentMethod": "月结30天",
              "executionDate": "2026-06-01",
              "deliveryDate": "2026-07-01",
              "type": 1,
              "productData": [
                {
                  "productId": 1,
                  "productModelId": 1,
                  "quantity": 100,
                  "taxInclusiveUnitPrice": 500.00,
                  "taxInclusiveTotalPrice": 50000.00,
                  "taxRate": 13.00,
                  "unit": "ä»¶",
                  "type": 1
                }
              ]
            }
            å®¢æˆ·åç§°è¦åƒæŒ‡å®šè¡Œä¸šçš„公司,合同编号格式XS-年月日-序号,金额合理。
            æ—¥æœŸèŒƒå›´: %s ~ %s,每种实体生成%d-%d条。
            """.formatted(dateStart, dateEnd, min, max);
    }
    private static String buildPurchasePrompt(int min, int max, String dateStart, String dateEnd) {
        return """
            é‡‡è´­æ¨¡å— - æŒ‰ä»¥ä¸‹æ ¼å¼ç”Ÿæˆ:
            {
              "entity": "supplier",
              "supplierName": "XX原材料有限公司",
              "supplierType": "原材料供应商",
              "contactUserName": "李四",
              "contactUserPhone": "13900139000",
              "companyAddress": "XX省XX市XX区XXè·¯XX号",
              "taxpayerIdentificationNum": "91110108XXXXXXXXXX",
              "bankAccountName": "XX银行XX支行",
              "bankAccountNum": "622202XXXXXXXXXXXX",
              "isWhite": 0
            }
            {
              "entity": "purchaseLedger",
              "supplierName": "引用上面生成的供应商名称",
              "purchaseContractNumber": "CG-20260601-001",
              "projectName": "XX采购项目",
              "entryDate": "2026-06-01",
              "contractAmount": 30000.00,
              "paymentMethod": "货到付款",
              "executionDate": "2026-06-01",
              "productData": [
                {
                  "productId": 1,
                  "productModelId": 1,
                  "quantity": 50,
                  "taxInclusiveUnitPrice": 600.00,
                  "taxInclusiveTotalPrice": 30000.00,
                  "taxRate": 13.00,
                  "unit": "ä»¶",
                  "type": 2
                }
              ]
            }
            ä¾›åº”商名称要像指定行业的供应商,合同编号格式CG-年月日-序号。
            æ—¥æœŸèŒƒå›´: %s ~ %s,每种实体生成%d-%d条。
            """.formatted(dateStart, dateEnd, min, max);
    }
    private static String buildQualityPrompt(int min, int max) {
        return """
            è´¨é‡æ¨¡å— - æŒ‰ä»¥ä¸‹æ ¼å¼ç”Ÿæˆ:
            {
              "entity": "qualityTestStandard",
              "standardNo": "QTS-20260601-001",
              "standardName": "XX产品检验标准",
              "inspectType": 0,
              "remark": "适用于XX行业的质量检验标准"
            }
            inspectType: 0=原材料检验, 1=过程检验, 2=出厂检验。三种类型都要覆盖。
            {
              "entity": "qualityTestStandardBinding",
              "productId": 1,
              "testStandardId": 1
            }
            æ¯ç§å®žä½“生成%d-%d条。
            """.formatted(min, max);
    }
    private static String buildProductionPrompt(int min, int max, String dateStart, String dateEnd) {
        return """
            ç”Ÿäº§æ¨¡å— - æŒ‰ä»¥ä¸‹æ ¼å¼ç”Ÿæˆ:
            {
              "entity": "productionPlan",
              "productModelId": 1,
              "qtyRequired": 200,
              "requiredDate": "2026-06-15",
              "source": "销售",
              "promisedDeliveryDate": "2026-07-01",
              "remark": "XX客户订单需求"
            }
            {
              "entity": "productionOrder",
              "productModelId": 1,
              "quantity": 200,
              "planCompleteTime": "2026-06-30",
              "remark": "根据生产计划XX生成"
            }
            productionPlan的source可选"销售"或"内部"。
            æ—¥æœŸèŒƒå›´: %s ~ %s,每种实体生成%d-%d条。
            """.formatted(dateStart, dateEnd, min, max);
    }
    private static String buildStockPrompt(int min, int max) {
        return """
            åº“存模块 - æŒ‰ä»¥ä¸‹æ ¼å¼ç”Ÿæˆ:
            {
              "entity": "stockInventory",
              "productModelId": 1,
              "qualitity": 500,
              "batchNo": "BATCH-20260601-001",
              "warnNum": 50,
              "remark": "安全库存"
            }
            æ¯ç§å®žä½“生成%d-%d条。batchNo格式BATCH-年月日-序号。
            """.formatted(min, max);
    }
}
src/main/java/com/ruoyi/mock/service/DataCheckService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,15 @@
package com.ruoyi.mock.service;
import com.ruoyi.mock.dto.DataCheckRequest;
import com.ruoyi.mock.vo.DataCheckResult;
/**
 * åŸºç¡€æ•°æ®æ£€æµ‹æœåŠ¡
 */
public interface DataCheckService {
    /**
     * æ£€æµ‹æŒ‡å®šæ¨¡å—的基础数据是否就绪
     */
    DataCheckResult check(DataCheckRequest request);
}
src/main/java/com/ruoyi/mock/service/DataGenerateService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,15 @@
package com.ruoyi.mock.service;
import com.ruoyi.mock.dto.DataGenerateRequest;
import com.ruoyi.mock.vo.DataGenerateResult;
/**
 * AI数据生成服务
 */
public interface DataGenerateService {
    /**
     * æ ¹æ®è¯·æ±‚参数调用AI生成模拟数据并创建
     */
    DataGenerateResult generate(DataGenerateRequest request);
}
src/main/java/com/ruoyi/mock/service/impl/DataCheckServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,146 @@
package com.ruoyi.mock.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ruoyi.approve.pojo.ApprovalTemplate;
import com.ruoyi.approve.mapper.ApprovalTemplateMapper;
import com.ruoyi.basic.mapper.CustomerMapper;
import com.ruoyi.basic.mapper.ProductMapper;
import com.ruoyi.basic.mapper.SupplierManageMapper;
import com.ruoyi.common.enums.TypeEnums;
import com.ruoyi.mock.dto.DataCheckRequest;
import com.ruoyi.mock.service.DataCheckService;
import com.ruoyi.mock.vo.DataCheckResult;
import com.ruoyi.mock.vo.DataCheckResult.CheckItem;
import com.ruoyi.quality.mapper.QualityTestStandardBindingMapper;
import com.ruoyi.quality.mapper.QualityTestStandardMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@Service
@RequiredArgsConstructor
public class DataCheckServiceImpl implements DataCheckService {
    private final ProductMapper productMapper;
    private final CustomerMapper customerMapper;
    private final SupplierManageMapper supplierManageMapper;
    private final ApprovalTemplateMapper approvalTemplateMapper;
    private final QualityTestStandardMapper qualityTestStandardMapper;
    private final QualityTestStandardBindingMapper qualityTestStandardBindingMapper;
    @Override
    public DataCheckResult check(DataCheckRequest request) {
        List<String> modules = request.getModules();
        if (modules == null) {
            modules = List.of();
        }
        Map<String, CheckItem> itemMap = new LinkedHashMap<>();
        if (!modules.isEmpty()) {
            addProductCheck(itemMap);
        }
        for (String module : modules) {
            switch (module) {
                case "sales" -> addSalesChecks(itemMap);
                case "purchase" -> addPurchaseChecks(itemMap);
                case "quality" -> addQualityChecks(itemMap);
            }
        }
        List<CheckItem> items = new ArrayList<>(itemMap.values());
        DataCheckResult result = new DataCheckResult();
        result.setItems(items);
        result.setTotalItems(items.size());
        result.setPassedItems((int) items.stream().filter(CheckItem::isPassed).count());
        return result;
    }
    private void addProductCheck(Map<String, CheckItem> itemMap) {
        String key = "common:产品数据";
        if (itemMap.containsKey(key)) {
            return;
        }
        long count = productMapper.selectCount(null);
        itemMap.put(key, buildItem("common", "产品数据", 1, (int) count,
                "缺少产品数据,请先在【基础数据-产品管理】中添加至少1条产品"));
    }
    private void addSalesChecks(Map<String, CheckItem> itemMap) {
        String customerKey = "sales:客户数据";
        if (!itemMap.containsKey(customerKey)) {
            long count = customerMapper.selectCount(null);
            itemMap.put(customerKey, buildItem("sales", "客户数据", 1, (int) count,
                    "缺少客户数据,请先在【基础数据-客户管理】中添加至少1条客户"));
        }
        String quotationKey = "sales:报价审批模板";
        if (!itemMap.containsKey(quotationKey)) {
            long count = approvalTemplateMapper.selectCount(
                    new LambdaQueryWrapper<ApprovalTemplate>()
                            .eq(ApprovalTemplate::getBusinessType, TypeEnums.QUOTATION_APPROVAL.getCode()));
            itemMap.put(quotationKey, buildItem("sales", "报价审批模板", 1, (int) count,
                    "缺少报价审批模板,请先在【系统管理-审批模板】中创建报价审批模板"));
        }
        String shippingKey = "sales:发货审批模板";
        if (!itemMap.containsKey(shippingKey)) {
            long count = approvalTemplateMapper.selectCount(
                    new LambdaQueryWrapper<ApprovalTemplate>()
                            .eq(ApprovalTemplate::getBusinessType, TypeEnums.SHIPPING_APPROVAL.getCode()));
            itemMap.put(shippingKey, buildItem("sales", "发货审批模板", 1, (int) count,
                    "缺少发货审批模板,请先在【系统管理-审批模板】中创建发货审批模板"));
        }
    }
    private void addPurchaseChecks(Map<String, CheckItem> itemMap) {
        String supplierKey = "purchase:供应商数据";
        if (!itemMap.containsKey(supplierKey)) {
            long count = supplierManageMapper.selectCount(null);
            itemMap.put(supplierKey, buildItem("purchase", "供应商数据", 1, (int) count,
                    "缺少供应商数据,请先在【基础数据-供应商管理】中添加至少1条供应商"));
        }
        String tplKey = "purchase:采购审批模板";
        if (!itemMap.containsKey(tplKey)) {
            long count = approvalTemplateMapper.selectCount(
                    new LambdaQueryWrapper<ApprovalTemplate>()
                            .eq(ApprovalTemplate::getBusinessType, TypeEnums.PURCHASE_APPROVAL.getCode()));
            itemMap.put(tplKey, buildItem("purchase", "采购审批模板", 1, (int) count,
                    "缺少采购审批模板,请先在【系统管理-审批模板】中创建采购审批模板"));
        }
    }
    private void addQualityChecks(Map<String, CheckItem> itemMap) {
        String standardKey = "quality:检测标准";
        if (!itemMap.containsKey(standardKey)) {
            long count = qualityTestStandardMapper.selectCount(null);
            itemMap.put(standardKey, buildItem("quality", "检测标准", 1, (int) count,
                    "缺少检测标准,请先在【质量管理-检测标准】中创建检测标准"));
        }
        String bindingKey = "quality:指标绑定";
        if (!itemMap.containsKey(bindingKey)) {
            long count = qualityTestStandardBindingMapper.selectCount(null);
            itemMap.put(bindingKey, buildItem("quality", "指标绑定", 1, (int) count,
                    "缺少指标绑定,请先在【质量管理-检测标准绑定】中将检测标准与产品绑定"));
        }
    }
    private CheckItem buildItem(String module, String itemName, int minRequired, int currentCount, String failMessage) {
        CheckItem item = new CheckItem();
        item.setModule(module);
        item.setItemName(itemName);
        item.setMinRequired(minRequired);
        item.setCurrentCount(currentCount);
        item.setPassed(currentCount >= minRequired);
        item.setMessage(item.isPassed() ? "通过" : failMessage);
        return item;
    }
}
src/main/java/com/ruoyi/mock/service/impl/DataGenerateServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,466 @@
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.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.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.production.pojo.ProductionOrder;
import com.ruoyi.production.pojo.ProductionPlan;
import com.ruoyi.production.service.ProductionOrderService;
import com.ruoyi.production.service.ProductionPlanService;
import com.ruoyi.purchase.dto.PurchaseLedgerDto;
import com.ruoyi.purchase.service.IPurchaseLedgerService;
import com.ruoyi.quality.pojo.QualityTestStandard;
import com.ruoyi.quality.pojo.QualityTestStandardBinding;
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.StockInventoryService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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;
    @Override
    public DataGenerateResult generate(DataGenerateRequest request) {
        DataGenerateResult result = new DataGenerateResult();
        List<ModuleSummary> summaries = new ArrayList<>();
        List<String> errors = new ArrayList<>();
        int totalGenerated = 0;
        try {
            List<Long> productIds = productMapper.selectList(null).stream()
                    .map(p -> p.getId()).collect(Collectors.toList());
            List<Long> productModelIds = productModelMapper.selectList(null).stream()
                    .map(m -> m.getId()).collect(Collectors.toList());
            String systemPrompt = MockDataPrompt.buildSystemPrompt();
            String userMessage = MockDataPrompt.buildUserMessage(
                    request.getModules(), request.getIndustries(),
                    request.getCountMin(), request.getCountMax(),
                    request.getDateStart(), request.getDateEnd(),
                    request.getAdditionalInfo(),
                    productIds, productModelIds);
            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<JSONObject> entities = parseJsonResponse(llmResponse);
            if (entities.isEmpty()) {
                result.setStatus("FAILED");
                errors.add("无法解析AI返回的数据");
                result.setErrors(errors);
                return result;
            }
            Map<String, List<JSONObject>> grouped = entities.stream()
                    .collect(Collectors.groupingBy(e -> e.getString("entity")));
            // åç§°â†’ID æ˜ å°„表,用于创建业务单据时回填外键
            Map<String, Long> customerNameToId = new HashMap<>();
            Map<String, Long> supplierNameToId = new HashMap<>();
            // Tier 0: åŸºç¡€æ•°æ®
            if (grouped.containsKey("customer")) {
                ModuleSummary s = createCustomers(grouped.get("customer"), customerNameToId);
                summaries.add(s);
                totalGenerated += s.getSuccessCount();
            }
            if (grouped.containsKey("supplier")) {
                ModuleSummary s = createSuppliers(grouped.get("supplier"), supplierNameToId);
                summaries.add(s);
                totalGenerated += s.getSuccessCount();
            }
            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);
                summaries.add(s);
                totalGenerated += s.getSuccessCount();
            }
            if (grouped.containsKey("purchaseLedger")) {
                ModuleSummary s = createPurchaseLedgers(grouped.get("purchaseLedger"), supplierNameToId);
                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<JSONObject> 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();
        }
    }
    // ---- Tier 0: åŸºç¡€æ•°æ® ----
    private ModuleSummary createCustomers(List<JSONObject> items, Map<String, Long> 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.setTaxpayerIdentificationNumber(item.getString("taxpayerIdentificationNumber"));
                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<JSONObject> items, Map<String, Long> 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.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<JSONObject> 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<JSONObject> items, Map<String, Long> customerNameToId) {
        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"));
                if (item.containsKey("entryDate")) {
                    dto.setEntryDate(java.sql.Date.valueOf(item.getString("entryDate")));
                }
                if (item.containsKey("executionDate")) {
                    dto.setExecutionDate(LocalDate.parse(item.getString("executionDate")));
                }
                if (item.containsKey("deliveryDate")) {
                    dto.setDeliveryDate(LocalDate.parse(item.getString("deliveryDate")));
                }
                JSONArray productData = item.getJSONArray("productData");
                if (productData != null) {
                    List<SalesLedgerProduct> 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"));
                        products.add(slp);
                    }
                    dto.setProductData(products);
                }
                salesLedgerService.addOrUpdateSalesLedger(dto);
                success++;
            } catch (Exception e) {
                log.warn("创建销售台账失败: {}", e.getMessage());
                fail++;
            }
        }
        return summary("sales", "销售台账", items.size(), success, fail);
    }
    private ModuleSummary createPurchaseLedgers(List<JSONObject> items, Map<String, Long> supplierNameToId) {
        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.setProjectName(item.getString("projectName"));
                dto.setContractAmount(item.getBigDecimal("contractAmount"));
                dto.setPaymentMethod(item.getString("paymentMethod"));
                if (item.containsKey("entryDate")) {
                    dto.setEntryDate(java.sql.Date.valueOf(item.getString("entryDate")));
                }
                if (item.containsKey("executionDate")) {
                    dto.setExecutionDate(java.sql.Date.valueOf(item.getString("executionDate")));
                }
                JSONArray productData = item.getJSONArray("productData");
                if (productData != null) {
                    List<SalesLedgerProduct> 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);
                        products.add(slp);
                    }
                    dto.setProductData(products);
                }
                purchaseLedgerService.addOrEditPurchase(dto);
                success++;
            } catch (Exception e) {
                log.warn("创建采购台账失败: {}", e.getMessage());
                fail++;
            }
        }
        return summary("purchase", "采购台账", items.size(), success, fail);
    }
    private ModuleSummary createProductionPlans(List<JSONObject> items) {
        int success = 0, fail = 0;
        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")) {
                    plan.setRequiredDate(LocalDate.parse(item.getString("requiredDate")));
                }
                if (item.containsKey("promisedDeliveryDate")) {
                    plan.setPromisedDeliveryDate(LocalDate.parse(item.getString("promisedDeliveryDate")));
                }
                productionPlanService.save(plan);
                success++;
            } catch (Exception e) {
                log.warn("创建生产计划失败: {}", e.getMessage());
                fail++;
            }
        }
        return summary("production", "生产计划", items.size(), success, fail);
    }
    // ---- Tier 2: åŽç»­å•据 ----
    private ModuleSummary createProductionOrders(List<JSONObject> 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")) {
                    order.setPlanCompleteTime(LocalDate.parse(item.getString("planCompleteTime")));
                }
                productionOrderService.saveProductionOrder(order);
                success++;
            } catch (Exception e) {
                log.warn("创建生产订单失败: {}", e.getMessage());
                fail++;
            }
        }
        return summary("production", "生产订单", items.size(), success, fail);
    }
    private ModuleSummary createQualityBindings(List<JSONObject> 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<JSONObject> 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;
    }
}
src/main/java/com/ruoyi/mock/vo/DataCheckResult.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,36 @@
package com.ruoyi.mock.vo;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
 * åŸºç¡€æ•°æ®æ£€æµ‹ç»“æžœ
 */
@Data
public class DataCheckResult {
    /** æ£€æµ‹é¡¹æ€»æ•° */
    private int totalItems;
    /** å·²é€šè¿‡çš„æ£€æµ‹é¡¹æ•°é‡ */
    private int passedItems;
    /** æ£€æµ‹æ˜Žç»† */
    private List<CheckItem> items = new ArrayList<>();
    @Data
    public static class CheckItem {
        /** æ¨¡å—:sales / purchase / quality / common */
        private String module;
        /** æ£€æµ‹é¡¹åç§° */
        private String itemName;
        /** æ‰€éœ€æœ€ä½Žæ•°é‡ */
        private int minRequired;
        /** å½“前实际数量 */
        private int currentCount;
        /** æ˜¯å¦é€šè¿‡ */
        private boolean passed;
        /** æç¤ºä¿¡æ¯ */
        private String message;
    }
}
src/main/java/com/ruoyi/mock/vo/DataGenerateResult.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,37 @@
package com.ruoyi.mock.vo;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
 * AI数据生成结果
 */
@Data
public class DataGenerateResult {
    /** ç”ŸæˆçŠ¶æ€: SUCCESS / PARTIAL / FAILED */
    private String status;
    /** æ€»è®¡ç”Ÿæˆæ¡æ•° */
    private int totalGenerated;
    /** å„模块生成摘要 */
    private List<ModuleSummary> moduleSummaries = new ArrayList<>();
    /** é”™è¯¯ä¿¡æ¯ï¼ˆå¦‚有) */
    private List<String> errors = new ArrayList<>();
    /** åŸºç¡€æ•°æ®æ£€æµ‹ç»“果(前置校验未通过时返回) */
    private DataCheckResult checkResult;
    @Data
    public static class ModuleSummary {
        private String module;
        private String entityName;
        private int generatedCount;
        private int successCount;
        private int failCount;
    }
}