diff --git a/docs/plans/backend/2026-03-29-project-import-history-backend-implementation.md b/docs/plans/backend/2026-03-29-project-import-history-backend-implementation.md new file mode 100644 index 00000000..c9e0d9eb --- /dev/null +++ b/docs/plans/backend/2026-03-29-project-import-history-backend-implementation.md @@ -0,0 +1,437 @@ +# Project Import History Backend Implementation Plan + +> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** 为历史项目导入能力补齐后端链路,支持创建新项目后异步复制历史流水、复制成功文件记录、禁止删除历史导入记录,并自动触发现有打标与结果刷新流程。 + +**Architecture:** 后端采用最短路径实现,在现有 `CcdiProjectController -> ICcdiProjectService -> CcdiProjectServiceImpl` 创建项目主链路外新增“历史导入”专用接口与异步编排服务。流水复制不复用文件上传接口语义,而是直接在项目维度重建批次号、批量写入 `ccdi_bank_statement` 和 `ccdi_file_upload_record`,完成后复用 `bankTagService.submitAutoRebuild(...)` 与现有详情页状态轮询。 + +**Tech Stack:** Java 21, Spring Boot 3, MyBatis Plus, MyBatis XML, MySQL 8, JUnit 5, Mockito, Maven + +## 后端验收清单 + +- `GET /ccdi/project/history` 仅返回已完成、已归档项目,支持项目名搜索 +- `POST /ccdi/project/import` 能先创建新项目,再异步提交历史导入任务 +- 历史导入按 `trxDate` 过滤流水,并按现有唯一性口径去重 +- 新项目中的历史导入文件记录带来源标识,且不允许删除 +- 历史导入完成后自动触发打标、结果表刷新和统计刷新 + +--- + +### Task 1: 先用 SQL/Mapper 测试锁定历史导入所需数据结构 + +**Files:** +- Create: `sql/migration/2026-03-29-add-file-upload-history-import-source-fields.sql` +- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/entity/CcdiFileUploadRecord.java` +- Modify: `ccdi-project/src/main/resources/mapper/ccdi/project/CcdiFileUploadRecordMapper.xml` +- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiFileUploadRecordMapper.java` +- Modify: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/mapper/CcdiBankStatementMapperXmlTest.java` + +- [ ] **Step 1: Write the failing mapper/XML assertions** + +在 `CcdiBankStatementMapperXmlTest.java` 或新增紧邻的 XML 测试中,先锁定以下点: + +```java +assertTrue(xml.contains("source_type")); +assertTrue(xml.contains("source_project_id")); +assertTrue(xml.contains("source_project_name")); +assertTrue(xml.contains("fur.source_project_name")); +``` + +同时确认 `CcdiFileUploadRecord` 实体新增字段后有对应 getter/setter。 + +- [ ] **Step 2: Run test to verify it fails** + +Run: + +```bash +mvn -pl ccdi-project -Dtest=CcdiBankStatementMapperXmlTest test +``` + +Expected: + +- `FAIL` +- 原因是当前文件记录表和 Mapper 还没有历史导入来源字段 + +- [ ] **Step 3: Add the minimal schema and mapper support** + +在 SQL 脚本中新增: + +```sql +ALTER TABLE ccdi_file_upload_record + ADD COLUMN source_type varchar(32) DEFAULT 'UPLOAD' COMMENT '来源类型', + ADD COLUMN source_project_id bigint NULL COMMENT '来源项目ID', + ADD COLUMN source_project_name varchar(100) NULL COMMENT '来源项目名称'; +``` + +在 `CcdiFileUploadRecord.java` 增加: + +```java +private String sourceType; +private Long sourceProjectId; +private String sourceProjectName; +``` + +在 `CcdiFileUploadRecordMapper.xml` 的 `resultMap`、`select`、`insertBatch` 中补齐字段映射。 + +- [ ] **Step 4: Run test to verify it passes** + +Run: + +```bash +mvn -pl ccdi-project -Dtest=CcdiBankStatementMapperXmlTest test +``` + +Expected: + +- `PASS` + +- [ ] **Step 5: Commit** + +```bash +git add sql/migration/2026-03-29-add-file-upload-history-import-source-fields.sql ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/entity/CcdiFileUploadRecord.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiFileUploadRecordMapper.java ccdi-project/src/main/resources/mapper/ccdi/project/CcdiFileUploadRecordMapper.xml ccdi-project/src/test/java/com/ruoyi/ccdi/project/mapper/CcdiBankStatementMapperXmlTest.java +git commit -m "补充历史导入文件记录字段" +``` + +### Task 2: 锁定项目历史列表接口与导入接口契约 + +**Files:** +- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/dto/CcdiProjectImportHistoryDTO.java` +- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectHistoryListItemVO.java` +- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiProjectService.java` +- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiProjectController.java` +- Modify: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiProjectControllerContractTest.java` +- Modify: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiProjectControllerTest.java` + +- [ ] **Step 1: Write the failing controller contract test** + +在 `CcdiProjectControllerContractTest.java` 中新增断言: + +```java +Method history = CcdiProjectController.class.getMethod("listHistoryProjects", CcdiProjectQueryDTO.class); +assertEquals("/history", history.getAnnotation(GetMapping.class).value()[0]); + +Method importing = CcdiProjectController.class.getMethod("importFromHistory", CcdiProjectImportHistoryDTO.class); +assertEquals("/import", importing.getAnnotation(PostMapping.class).value()[0]); +``` + +并在控制器测试中锁定返回结构至少包含“项目创建成功后返回新项目数据”。 + +- [ ] **Step 2: Run test to verify it fails** + +Run: + +```bash +mvn -pl ccdi-project -Dtest=CcdiProjectControllerContractTest,CcdiProjectControllerTest test +``` + +Expected: + +- `FAIL` +- 原因是当前控制器没有历史项目列表和导入提交两个真实接口 + +- [ ] **Step 3: Add DTO/VO and minimal controller signatures** + +在 DTO 中定义: + +```java +private String projectName; +private String description; +private List sourceProjectIds; +private String startDate; +private String endDate; +``` + +在 `ICcdiProjectService.java` 中补充: + +```java +List listHistoryProjects(CcdiProjectQueryDTO queryDTO); +CcdiProjectVO importFromHistory(CcdiProjectImportHistoryDTO dto, String operator); +``` + +在 `CcdiProjectController.java` 中增加: + +```java +@GetMapping("/history") +public AjaxResult listHistoryProjects(CcdiProjectQueryDTO queryDTO) { ... } + +@PostMapping("/import") +public AjaxResult importFromHistory(@Validated @RequestBody CcdiProjectImportHistoryDTO dto) { ... } +``` + +- [ ] **Step 4: Run tests to verify the contract passes** + +Run: + +```bash +mvn -pl ccdi-project -Dtest=CcdiProjectControllerContractTest,CcdiProjectControllerTest test +``` + +Expected: + +- `PASS` + +- [ ] **Step 5: Commit** + +```bash +git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/dto/CcdiProjectImportHistoryDTO.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectHistoryListItemVO.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiProjectService.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiProjectController.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiProjectControllerContractTest.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiProjectControllerTest.java +git commit -m "补充历史项目导入接口契约" +``` + +### Task 3: 实现历史项目列表查询与项目创建后任务提交流程 + +**Files:** +- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiProjectMapper.java` +- Modify: `ccdi-project/src/main/resources/mapper/ccdi/project/CcdiProjectMapper.xml` +- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectServiceImpl.java` +- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiProjectHistoryImportService.java` +- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectHistoryImportServiceImpl.java` +- Modify: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectServiceImplTest.java` + +- [ ] **Step 1: Write the failing service tests** + +在 `CcdiProjectServiceImplTest.java` 中新增至少两个场景: + +```java +@Test +void shouldOnlyReturnCompletedAndArchivedHistoryProjects() { ... } + +@Test +void shouldCreateProjectThenSubmitHistoryImportAfterCommit() { ... } +``` + +第二个测试至少锁定: + +- 先调用 `createProject` 生成本地项目 +- 仅在事务提交后委托 `historyImportService.submitImport(...)` + +- [ ] **Step 2: Run test to verify it fails** + +Run: + +```bash +mvn -pl ccdi-project -Dtest=CcdiProjectServiceImplTest test +``` + +Expected: + +- `FAIL` + +- [ ] **Step 3: Implement the minimal query and submit flow** + +在 `CcdiProjectMapper.xml` 中新增历史项目查询 SQL,条件固定: + +```sql +where p.status in ('1', '2') +``` + +并支持项目名模糊搜索。 + +在 `CcdiProjectServiceImpl.java` 中实现: + +```java +public List listHistoryProjects(CcdiProjectQueryDTO queryDTO) { ... } + +public CcdiProjectVO importFromHistory(CcdiProjectImportHistoryDTO dto, String operator) { + CcdiProjectSaveDTO saveDTO = new CcdiProjectSaveDTO(); + saveDTO.setProjectName(dto.getProjectName()); + saveDTO.setDescription(dto.getDescription()); + saveDTO.setConfigType("default"); + CcdiProjectVO project = createProject(saveDTO); + TransactionSynchronizationManager.registerSynchronization(... historyImportService.submitImport(...)); + return project; +} +``` + +约束: + +- 不新增导入专用项目状态 +- 不在接口内同步执行大批量复制 +- 只在事务提交后启动异步导入 + +- [ ] **Step 4: Run tests to verify it passes** + +Run: + +```bash +mvn -pl ccdi-project -Dtest=CcdiProjectServiceImplTest,CcdiProjectControllerTest test +``` + +Expected: + +- `PASS` + +- [ ] **Step 5: Commit** + +```bash +git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiProjectMapper.java ccdi-project/src/main/resources/mapper/ccdi/project/CcdiProjectMapper.xml ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectServiceImpl.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiProjectHistoryImportService.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectHistoryImportServiceImpl.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectServiceImplTest.java +git commit -m "实现历史项目导入任务提交流程" +``` + +### Task 4: 实现流水复制、批次号重映射与来源文件记录生成 + +**Files:** +- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiBankStatementMapper.java` +- Modify: `ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankStatementMapper.xml` +- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiFileUploadRecordMapper.java` +- Modify: `ccdi-project/src/main/resources/mapper/ccdi/project/CcdiFileUploadRecordMapper.xml` +- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectHistoryImportServiceImpl.java` +- Modify: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java` +- Create: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectHistoryImportServiceImplTest.java` + +- [ ] **Step 1: Write the failing history import service tests** + +新增 `CcdiProjectHistoryImportServiceImplTest.java`,锁定以下行为: + +```java +@Test +void shouldFilterStatementsByTrxDateAndDeduplicateAcrossSourceProjects() { ... } + +@Test +void shouldGenerateNewBatchIdsAndHistoryImportFileRecordsOnlyForSuccessfulSourceBatches() { ... } +``` + +断言重点: + +- 仅复制命中时间范围的流水 +- 新项目 `batchId/logId` 不等于来源批次号 +- 文件记录 `sourceType = HISTORY_IMPORT` +- 文件记录携带 `sourceProjectId/sourceProjectName` + +- [ ] **Step 2: Run test to verify it fails** + +Run: + +```bash +mvn -pl ccdi-project -Dtest=CcdiProjectHistoryImportServiceImplTest,CcdiFileUploadServiceImplTest test +``` + +Expected: + +- `FAIL` + +- [ ] **Step 3: Implement the minimal copy pipeline** + +在 Mapper 中新增只读查询: + +- 查询来源项目成功文件记录 +- 按来源批次和日期区间查询流水 + +在 `CcdiProjectHistoryImportServiceImpl.java` 中实现: + +```java +Map newBatchIdMapping = new HashMap<>(); +for (SourceBatch batch : matchedBatches) { + int newBatchId = nextBatchId(); + newBatchIdMapping.put(batch.getSourceLogId(), newBatchId); +} +``` + +并在复制流水时写入: + +```java +statement.setProjectId(targetProjectId); +statement.setGroupId(targetLsfxProjectId); +statement.setBatchId(newBatchId); +``` + +在生成文件记录时写入: + +```java +record.setSourceType("HISTORY_IMPORT"); +record.setSourceProjectId(sourceProjectId); +record.setSourceProjectName(sourceProjectName); +record.setFileStatus("parsed_success"); +``` + +- [ ] **Step 4: Run tests to verify it passes** + +Run: + +```bash +mvn -pl ccdi-project -Dtest=CcdiProjectHistoryImportServiceImplTest,CcdiFileUploadServiceImplTest,CcdiBankStatementMapperXmlTest test +``` + +Expected: + +- `PASS` + +- [ ] **Step 5: Commit** + +```bash +git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiBankStatementMapper.java ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankStatementMapper.xml ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiFileUploadRecordMapper.java ccdi-project/src/main/resources/mapper/ccdi/project/CcdiFileUploadRecordMapper.xml ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectHistoryImportServiceImpl.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectHistoryImportServiceImplTest.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java +git commit -m "实现历史项目流水复制后端逻辑" +``` + +### Task 5: 接入自动打标、删除拦截与回归验证 + +**Files:** +- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectHistoryImportServiceImpl.java` +- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java` +- Modify: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectHistoryImportServiceImplTest.java` +- Modify: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java` +- Modify: `docs/reports/implementation/2026-03-29-project-import-history-plan-record.md` + +- [ ] **Step 1: Write the failing tests for auto rebuild and delete guard** + +在 `CcdiProjectHistoryImportServiceImplTest.java` 中新增: + +```java +verify(bankTagService).submitAutoRebuild(targetProjectId, TriggerType.AUTO_BATCH_UPLOAD); +``` + +在 `CcdiFileUploadServiceImplTest.java` 中新增: + +```java +assertThrows(ServiceException.class, () -> service.deleteFileUploadRecord(historyRecordId, 1L)); +``` + +并锁定错误信息包含“历史导入文件不支持删除”。 + +- [ ] **Step 2: Run test to verify it fails** + +Run: + +```bash +mvn -pl ccdi-project -Dtest=CcdiProjectHistoryImportServiceImplTest,CcdiFileUploadServiceImplTest test +``` + +Expected: + +- `FAIL` + +- [ ] **Step 3: Implement the minimal rebuild and delete guard** + +在历史导入服务复制完成后补充: + +```java +refreshProjectTargetCount(targetProjectId); +bankTagService.submitAutoRebuild(targetProjectId, TriggerType.AUTO_BATCH_UPLOAD); +``` + +在 `deleteFileUploadRecord(...)` 中补充: + +```java +if ("HISTORY_IMPORT".equals(record.getSourceType())) { + throw new ServiceException("历史导入文件不支持删除"); +} +``` + +- [ ] **Step 4: Run the backend regression suite** + +Run: + +```bash +mvn -pl ccdi-project -Dtest=CcdiProjectControllerTest,CcdiProjectControllerContractTest,CcdiProjectServiceImplTest,CcdiProjectHistoryImportServiceImplTest,CcdiFileUploadServiceImplTest,CcdiBankStatementMapperXmlTest test +``` + +Expected: + +- `PASS` + +- [ ] **Step 5: Commit** + +```bash +git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectHistoryImportServiceImpl.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectHistoryImportServiceImplTest.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java docs/reports/implementation/2026-03-29-project-import-history-plan-record.md +git commit -m "接通历史项目导入后端闭环" +``` diff --git a/docs/plans/frontend/2026-03-29-project-import-history-frontend-implementation.md b/docs/plans/frontend/2026-03-29-project-import-history-frontend-implementation.md new file mode 100644 index 00000000..f4abcd48 --- /dev/null +++ b/docs/plans/frontend/2026-03-29-project-import-history-frontend-implementation.md @@ -0,0 +1,356 @@ +# Project Import History Frontend Implementation Plan + +> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** 为项目管理页补齐真实“导入历史项目”交互,支持上方新项目配置、下方多选历史项目、提交后跳转详情页,并在上传记录列表中正确展示且锁定历史导入文件记录。 + +**Architecture:** 前端在现有 `ccdiProject` 列表与详情页结构上最小化改造,重点落在 `ImportHistoryDialog.vue`、`index.vue`、`ccdiProject.js`、`UploadData.vue` 和 `uploadFileActionRules.js`。弹窗负责表单校验、历史项目查询、多选和真实提交;列表页负责成功后跳转;详情页上传记录区域负责展示历史导入来源并收敛删除操作。 + +**Tech Stack:** Vue 2, Element UI, Axios request wrapper, Node, npm + +## 前端验收清单 + +- 导入历史项目弹窗采用“新项目配置在上、历史项目列表在下”的纵向布局 +- 历史项目支持多选,且只展示后端返回的真实历史项目数据 +- 提交成功后关闭弹窗、提示任务已开始并跳转到新项目详情页 +- 上传记录列表能展示“历史导入 · 来源项目名” +- 历史导入文件记录不显示删除入口 + +--- + +### Task 1: 先锁定 API 层与列表页提交后的跳转契约 + +**Files:** +- Modify: `ruoyi-ui/src/api/ccdiProject.js` +- Modify: `ruoyi-ui/src/views/ccdiProject/index.vue` +- Modify: `ruoyi-ui/tests/unit/project-list-reanalyze-api.test.js` +- Create: `ruoyi-ui/tests/unit/project-import-history-submit-flow.test.js` + +- [ ] **Step 1: Write the failing source-based test** + +新增 `project-import-history-submit-flow.test.js`,至少锁定以下代码痕迹: + +```javascript +assert(apiSource.includes("url: '/ccdi/project/history'")); +assert(apiSource.includes("url: '/ccdi/project/import'")); +assert(indexSource.includes("this.$router.push({")); +assert(indexSource.includes("path: `/ccdiProject/detail/${project.projectId}`") || indexSource.includes("path: `/ccdiProject/detail/${data.projectId}`")); +``` + +并锁定成功提示文案包含“历史项目导入任务已开始”。 + +- [ ] **Step 2: Run test to verify it fails** + +Run: + +```bash +cd ruoyi-ui +node tests/unit/project-import-history-submit-flow.test.js +``` + +Expected: + +- `FAIL` +- 原因是当前列表页提交导入后只本地提示成功,没有跳转详情页 + +- [ ] **Step 3: Implement the minimal API and jump logic** + +在 `ruoyi-ui/src/api/ccdiProject.js` 保留并改用真实接口: + +```javascript +export function listHistoryProjects(query) { ... } +export function importFromHistory(data) { ... } +``` + +在 `index.vue` 中将 `handleSubmitImport(data)` 收敛为: + +```javascript +handleSubmitImport(project) { + this.$modal.msgSuccess("历史项目导入任务已开始"); + this.importDialogVisible = false; + this.getList(); + this.$router.push({ path: `/ccdiProject/detail/${project.projectId}` }); +} +``` + +- [ ] **Step 4: Run test to verify it passes** + +Run: + +```bash +cd ruoyi-ui +node tests/unit/project-import-history-submit-flow.test.js +``` + +Expected: + +- `PASS` + +- [ ] **Step 5: Commit** + +```bash +git add ruoyi-ui/src/api/ccdiProject.js ruoyi-ui/src/views/ccdiProject/index.vue ruoyi-ui/tests/unit/project-import-history-submit-flow.test.js +git commit -m "接通历史项目导入前端提交链路" +``` + +### Task 2: 重写导入弹窗为真实纵向表单与多选列表 + +**Files:** +- Modify: `ruoyi-ui/src/views/ccdiProject/components/ImportHistoryDialog.vue` +- Create: `ruoyi-ui/tests/unit/project-import-history-dialog-layout.test.js` +- Create: `ruoyi-ui/tests/unit/project-import-history-dialog-behavior.test.js` + +- [ ] **Step 1: Write the failing layout/behavior tests** + +在布局测试中锁定: + +```javascript +assert(source.includes('label="新项目名称"')); +assert(source.includes('label="备注"')); +assert(source.includes("type=\"daterange\"")); +assert(source.includes("data-multiselect") === false); // Vue 模板里应改成 checkbox/multiple 语义,而不是单选 radio +assert(source.includes("selectedProjectIds")); +``` + +在行为测试中锁定: + +```javascript +assert(source.includes("listHistoryProjects(")); +assert(source.includes("importFromHistory(")); +assert(source.includes("this.$emit('submit', response.data)")); +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: + +```bash +cd ruoyi-ui +node tests/unit/project-import-history-dialog-layout.test.js +node tests/unit/project-import-history-dialog-behavior.test.js +``` + +Expected: + +- `FAIL` +- 原因是当前弹窗还是单选、Mock 数据、上下字段不对 + +- [ ] **Step 3: Implement the minimal vertical dialog** + +在 `ImportHistoryDialog.vue` 中完成以下最小改造: + +- 新项目配置区放上方: + - `newProjectName` + - `description` + - `dateRange` +- 历史项目列表区放下方: + - 搜索框 + - 多选项目列表,状态只读展示 + - 固定高度滚动 +- 数据改为: + +```javascript +selectedProjectIds: [], +configForm: { + newProjectName: "", + description: "", + dateRange: [] +} +``` + +提交时组装: + +```javascript +const payload = { + projectName: this.configForm.newProjectName, + description: this.configForm.description, + sourceProjectIds: this.selectedProjectIds, + startDate: this.configForm.dateRange[0] || "", + endDate: this.configForm.dateRange[1] || "" +}; +``` + +- [ ] **Step 4: Run tests to verify they pass** + +Run: + +```bash +cd ruoyi-ui +node tests/unit/project-import-history-dialog-layout.test.js +node tests/unit/project-import-history-dialog-behavior.test.js +``` + +Expected: + +- `PASS` + +- [ ] **Step 5: Commit** + +```bash +git add ruoyi-ui/src/views/ccdiProject/components/ImportHistoryDialog.vue ruoyi-ui/tests/unit/project-import-history-dialog-layout.test.js ruoyi-ui/tests/unit/project-import-history-dialog-behavior.test.js +git commit -m "重构历史项目导入弹窗" +``` + +### Task 3: 接入上传记录来源展示与历史导入只读操作 + +**Files:** +- Modify: `ruoyi-ui/src/views/ccdiProject/components/detail/UploadData.vue` +- Modify: `ruoyi-ui/src/views/ccdiProject/components/detail/uploadFileActionRules.js` +- Modify: `ruoyi-ui/tests/unit/upload-data-file-list-table.test.js` +- Modify: `ruoyi-ui/tests/unit/upload-data-delete-retag-copy.test.js` +- Create: `ruoyi-ui/tests/unit/upload-data-history-import-readonly.test.js` + +- [ ] **Step 1: Write the failing file-list tests** + +新增或补充断言: + +```javascript +assert(uploadSource.includes("sourceProjectName")); +assert(uploadSource.includes("历史导入")); +assert(actionRuleSource.includes("HISTORY_IMPORT")); +assert(!deleteBehaviorAllowsHistoryImport); +``` + +其中 `upload-data-history-import-readonly.test.js` 至少锁定: + +```javascript +assert(/if\s*\(row\.sourceType\s*===\s*["']HISTORY_IMPORT["']\)\s*\{\s*return null/.test(actionRuleSource)); +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: + +```bash +cd ruoyi-ui +node tests/unit/upload-data-file-list-table.test.js +node tests/unit/upload-data-delete-retag-copy.test.js +node tests/unit/upload-data-history-import-readonly.test.js +``` + +Expected: + +- `FAIL` + +- [ ] **Step 3: Implement the minimal readonly display** + +在 `UploadData.vue` 的表格中增加来源展示,例如: + +```vue + + + +``` + +在 `uploadFileActionRules.js` 中收敛删除规则: + +```javascript +if (row.sourceType === 'HISTORY_IMPORT') { + return null +} +``` + +- [ ] **Step 4: Run tests to verify they pass** + +Run: + +```bash +cd ruoyi-ui +node tests/unit/upload-data-file-list-table.test.js +node tests/unit/upload-data-delete-retag-copy.test.js +node tests/unit/upload-data-history-import-readonly.test.js +``` + +Expected: + +- `PASS` + +- [ ] **Step 5: Commit** + +```bash +git add ruoyi-ui/src/views/ccdiProject/components/detail/UploadData.vue ruoyi-ui/src/views/ccdiProject/components/detail/uploadFileActionRules.js ruoyi-ui/tests/unit/upload-data-file-list-table.test.js ruoyi-ui/tests/unit/upload-data-delete-retag-copy.test.js ruoyi-ui/tests/unit/upload-data-history-import-readonly.test.js +git commit -m "收敛历史导入文件前端只读展示" +``` + +### Task 4: 收口弹窗校验、详情页衔接与回归验证 + +**Files:** +- Modify: `ruoyi-ui/src/views/ccdiProject/components/ImportHistoryDialog.vue` +- Modify: `ruoyi-ui/src/views/ccdiProject/detail.vue` +- Modify: `ruoyi-ui/tests/unit/project-detail-tagging-polling.test.js` +- Modify: `docs/reports/implementation/2026-03-29-project-import-history-plan-record.md` + +- [ ] **Step 1: Write the failing validation/regression assertions** + +在弹窗行为测试里补充: + +```javascript +assert(source.includes("请选择历史项目")); +assert(source.includes("请输入新项目名称")); +``` + +在 `project-detail-tagging-polling.test.js` 中锁定: + +```javascript +assert(detailSource.includes("this.$route.params.projectId")); +assert(detailSource.includes("projectStatusPolling")); +``` + +目的是保证本次跳转详情页不会破坏现有轮询结构。 + +- [ ] **Step 2: Run tests to verify they fail or cover the gap** + +Run: + +```bash +cd ruoyi-ui +node tests/unit/project-import-history-dialog-behavior.test.js +node tests/unit/project-detail-tagging-polling.test.js +``` + +Expected: + +- 历史导入弹窗测试先 `FAIL` +- 轮询测试保持 `PASS` 或在改动后回归通过 + +- [ ] **Step 3: Implement the minimal validation and handoff polish** + +在 `ImportHistoryDialog.vue` 中补充: + +- 新项目名称必填 +- 至少选择一个历史项目 +- 提交态 loading +- 成功后 reset 表单 + +在 `detail.vue` 中不新增新逻辑,只确保项目详情页跳转入口沿用现有 `projectId` 路由参数与状态轮询。 + +- [ ] **Step 4: Run the frontend regression set** + +Run: + +```bash +cd ruoyi-ui +node tests/unit/project-import-history-submit-flow.test.js +node tests/unit/project-import-history-dialog-layout.test.js +node tests/unit/project-import-history-dialog-behavior.test.js +node tests/unit/upload-data-file-list-table.test.js +node tests/unit/upload-data-delete-retag-copy.test.js +node tests/unit/upload-data-history-import-readonly.test.js +node tests/unit/project-detail-tagging-polling.test.js +``` + +Expected: + +- 全部 `PASS` + +- [ ] **Step 5: Commit** + +```bash +git add ruoyi-ui/src/views/ccdiProject/components/ImportHistoryDialog.vue ruoyi-ui/src/views/ccdiProject/detail.vue ruoyi-ui/tests/unit/project-import-history-submit-flow.test.js ruoyi-ui/tests/unit/project-import-history-dialog-layout.test.js ruoyi-ui/tests/unit/project-import-history-dialog-behavior.test.js ruoyi-ui/tests/unit/upload-data-history-import-readonly.test.js docs/reports/implementation/2026-03-29-project-import-history-plan-record.md +git commit -m "完成历史项目导入前端闭环" +``` diff --git a/docs/reports/implementation/2026-03-29-project-import-history-plan-record.md b/docs/reports/implementation/2026-03-29-project-import-history-plan-record.md new file mode 100644 index 00000000..fab900b5 --- /dev/null +++ b/docs/reports/implementation/2026-03-29-project-import-history-plan-record.md @@ -0,0 +1,19 @@ +# 历史项目导入新项目实施计划记录 + +## 本次改动 + +- 新增后端实施计划 `docs/plans/backend/2026-03-29-project-import-history-backend-implementation.md` +- 新增前端实施计划 `docs/plans/frontend/2026-03-29-project-import-history-frontend-implementation.md` +- 后端计划拆分为表结构与 Mapper、接口契约、任务提交流程、流水复制、删除拦截与回归验证五个任务 +- 前端计划拆分为 API 与跳转、弹窗重构、上传记录只读展示、校验与回归验证四个任务 + +## 计划结论 + +- 后端按“同步创建项目 + 异步历史导入任务”主线推进 +- 前端按“弹窗真实提交 + 详情页承接状态 + 历史导入记录只读展示”主线推进 +- 执行阶段优先遵循最短路径实现,不引入额外任务中心或兼容性分支 + +## 审阅说明 + +- 按仓库约束,本次未启用 subagent +- 已人工检查计划与设计文档的一致性,确认未引入未被批准的新方案