# Code Template Bundles Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Add selectable code template bundles so `qing` remains the default template while future templates can be added through data and metadata instead of hardcoded IDs. **Architecture:** Introduce a `sys_template_bundle` aggregate that owns the enabled target types for a code template bundle. Persist the selected bundle on front projects as `codeTemplate`, carry it into `GenProject`, and update generation, preview, download, and one-click flows to resolve templates by `bundleCode + templateType`. **Tech Stack:** Java 8, Spring Boot 2.5, MyBatis XML mappers, JUnit 4, Mockito, Vue 3, Element Plus, Node `node:test`, MySQL SQL seed scripts. --- ## File Map - Create `ruoyi-generator/src/main/java/com/ruoyi/generator/domain/TemplateBundle.java`: domain object for `sys_template_bundle`. - Create `ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/TemplateBundleMapper.java`: MyBatis mapper contract. - Create `ruoyi-generator/src/main/resources/mapper/generator/TemplateBundleMapper.xml`: SQL queries for bundles. - Create `ruoyi-generator/src/main/java/com/ruoyi/generator/service/ITemplateBundleService.java`: service contract for bundle lookup and supported target types. - Create `ruoyi-generator/src/main/java/com/ruoyi/generator/service/TemplateBundleServiceImpl.java`: default bundle fallback and enabled type logic. - Create `ruoyi-generator/src/test/java/com/ruoyi/generator/service/TemplateBundleServiceImplTest.java`: bundle service unit coverage. - Modify `ruoyi-generator/src/main/java/com/ruoyi/generator/domain/Template.java`: add `bundleCode`. - Modify `ruoyi-generator/src/main/resources/mapper/generator/TemplateMapper.xml`: select, filter, insert, and update `bundle_code`. - Modify `ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/FrontProject.java`: add `codeTemplate`. - Modify `ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/dto/FrontProjectCreateRequest.java`: add `codeTemplate`. - Modify `ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/dto/FrontProjectUpdateRequest.java`: add `codeTemplate`. - Modify `ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/dto/AiGenerationTaskCreateRequest.java`: add `codeTemplate`. - Modify `ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenProject.java`: add transient `codeTemplate`. - Modify `ruoyi-generator/src/main/resources/mapper/front/FrontProjectMapper.xml`: map and persist `code_template`. - Modify `ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/FrontProjectServiceImpl.java`: validate/default selected code template. - Modify `ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/FrontProjectConverter.java`: copy `codeTemplate` into `GenProject`. - Modify `ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenProjectServiceImpl.java`: resolve active template by bundle code and supported type. - Modify `ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/IFrontProjectPreviewService.java`: expose supported template types. - Modify `ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/FrontProjectPreviewServiceImpl.java`: use supported types for validation and download-all. - Modify `ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/OneClickProjectGenerationServiceImpl.java`: preview only supported types. - Modify `ruoyi-admin/src/main/java/com/ruoyi/web/controller/front/FrontProjectController.java`: expose bundle options and project template types. - Modify `sql/code_template_bundles.sql`: create template bundle migration and qing seed. - Modify `sql/db.sql`: fold migration into full initialization. - Modify `sql/qing_templates.sql`: set `bundle_code = 'qing'` for qing templates. - Modify `sql/front_project_upgrade.sql`: add front project column for older databases. - Modify `ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/FrontProjectServiceImplTest.java`: project default and persistence tests. - Modify `ruoyi-generator/src/test/java/com/ruoyi/generator/service/GenProjectServiceImplTest.java`: bundle selection tests. - Modify `ruoyi-generator/src/test/java/com/ruoyi/generator/util/QingTemplateSupportTest.java`: SQL seed assertions. - Modify `easycode-web/src/api/project.js`: add bundle and supported-type APIs. - Modify `easycode-web/src/views/GenerateView.vue`: show code template select and submit `codeTemplate`. - Modify `easycode-web/src/views/PreviewView.vue`: fetch and render supported template types dynamically. - Modify `easycode-web/src/views/generateView.test.mjs`: source tests for create form payload. - Modify `easycode-web/src/views/previewView.test.mjs`: source tests for dynamic preview tabs. --- ### Task 1: Add Template Bundle Domain And Service **Files:** - Create: `ruoyi-generator/src/main/java/com/ruoyi/generator/domain/TemplateBundle.java` - Create: `ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/TemplateBundleMapper.java` - Create: `ruoyi-generator/src/main/resources/mapper/generator/TemplateBundleMapper.xml` - Create: `ruoyi-generator/src/main/java/com/ruoyi/generator/service/ITemplateBundleService.java` - Create: `ruoyi-generator/src/main/java/com/ruoyi/generator/service/TemplateBundleServiceImpl.java` - Create: `ruoyi-generator/src/test/java/com/ruoyi/generator/service/TemplateBundleServiceImplTest.java` - [ ] **Step 1: Write failing service tests** Create `ruoyi-generator/src/test/java/com/ruoyi/generator/service/TemplateBundleServiceImplTest.java`: ```java package com.ruoyi.generator.service; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Mockito.when; import java.lang.reflect.Field; import java.util.Arrays; import java.util.List; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.generator.domain.TemplateBundle; import com.ruoyi.generator.mapper.TemplateBundleMapper; public class TemplateBundleServiceImplTest { private TemplateBundleServiceImpl service; @Mock private TemplateBundleMapper templateBundleMapper; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); service = new TemplateBundleServiceImpl(); Field field = TemplateBundleServiceImpl.class.getDeclaredField("templateBundleMapper"); field.setAccessible(true); field.set(service, templateBundleMapper); } @Test public void getDefaultBundleReturnsEnabledDefault() { TemplateBundle qing = bundle("qing", "0", "1", true, true, true, true); when(templateBundleMapper.selectDefaultTemplateBundle()).thenReturn(qing); assertEquals("qing", service.getDefaultBundle().getBundleCode()); } @Test public void getDefaultBundleFallsBackToQingWhenDatabaseIsEmpty() { when(templateBundleMapper.selectDefaultTemplateBundle()).thenReturn(null); TemplateBundle fallback = service.getDefaultBundle(); assertEquals("qing", fallback.getBundleCode()); assertEquals("Qing Default Template", fallback.getBundleName()); assertEquals("0", fallback.getStatus()); } @Test public void getRequiredEnabledBundleRejectsDisabledBundle() { when(templateBundleMapper.selectTemplateBundleByCode("vue3-plus")) .thenReturn(bundle("vue3-plus", "1", "0", true, true, true, true)); try { service.getRequiredEnabledBundle("vue3-plus"); fail("Expected disabled bundle to fail"); } catch (ServiceException e) { assertEquals("Code template bundle is disabled: vue3-plus", e.getMessage()); } } @Test public void getSupportedTemplateTypesHonorsFrontendSwitch() { TemplateBundle qing = bundle("qing", "0", "1", true, true, true, true); List enabled = service.getSupportedTemplateTypes(qing, "1"); List frontendOff = service.getSupportedTemplateTypes(qing, "0"); assertEquals(Arrays.asList("backend", "frontend", "admin_frontend", "sql"), enabled); assertEquals(Arrays.asList("backend", "admin_frontend", "sql"), frontendOff); } @Test public void assertTemplateTypeSupportedRejectsUnsupportedType() { TemplateBundle backendOnly = bundle("backend-only", "0", "0", true, false, false, true); try { service.assertTemplateTypeSupported(backendOnly, "frontend", "1"); fail("Expected unsupported type to fail"); } catch (ServiceException e) { assertEquals("Code template bundle backend-only does not support template type: frontend", e.getMessage()); } } private TemplateBundle bundle(String code, String status, String isDefault, boolean backend, boolean frontend, boolean adminFrontend, boolean sql) { TemplateBundle bundle = new TemplateBundle(); bundle.setBundleCode(code); bundle.setBundleName("Qing Default Template"); bundle.setStatus(status); bundle.setIsDefault(isDefault); bundle.setSupportBackend(backend ? "1" : "0"); bundle.setSupportFrontend(frontend ? "1" : "0"); bundle.setSupportAdminFrontend(adminFrontend ? "1" : "0"); bundle.setSupportSql(sql ? "1" : "0"); return bundle; } } ``` - [ ] **Step 2: Run test to verify it fails** Run: `mvn -pl ruoyi-generator -Dtest=TemplateBundleServiceImplTest test` Expected: compilation fails because `TemplateBundle`, `TemplateBundleMapper`, and `TemplateBundleServiceImpl` do not exist. - [ ] **Step 3: Add domain object** Create `ruoyi-generator/src/main/java/com/ruoyi/generator/domain/TemplateBundle.java`: ```java package com.ruoyi.generator.domain; import com.ruoyi.common.core.domain.BaseEntity; public class TemplateBundle extends BaseEntity { private static final long serialVersionUID = 1L; private Long bundleId; private String bundleCode; private String bundleName; private String description; private String status; private String isDefault; private String supportBackend; private String supportFrontend; private String supportAdminFrontend; private String supportSql; private Integer sortOrder; public Long getBundleId() { return bundleId; } public void setBundleId(Long bundleId) { this.bundleId = bundleId; } public String getBundleCode() { return bundleCode; } public void setBundleCode(String bundleCode) { this.bundleCode = bundleCode; } public String getBundleName() { return bundleName; } public void setBundleName(String bundleName) { this.bundleName = bundleName; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } public String getIsDefault() { return isDefault; } public void setIsDefault(String isDefault) { this.isDefault = isDefault; } public String getSupportBackend() { return supportBackend; } public void setSupportBackend(String supportBackend) { this.supportBackend = supportBackend; } public String getSupportFrontend() { return supportFrontend; } public void setSupportFrontend(String supportFrontend) { this.supportFrontend = supportFrontend; } public String getSupportAdminFrontend() { return supportAdminFrontend; } public void setSupportAdminFrontend(String supportAdminFrontend) { this.supportAdminFrontend = supportAdminFrontend; } public String getSupportSql() { return supportSql; } public void setSupportSql(String supportSql) { this.supportSql = supportSql; } public Integer getSortOrder() { return sortOrder; } public void setSortOrder(Integer sortOrder) { this.sortOrder = sortOrder; } } ``` - [ ] **Step 4: Add mapper contract and XML** Create `ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/TemplateBundleMapper.java`: ```java package com.ruoyi.generator.mapper; import java.util.List; import com.ruoyi.generator.domain.TemplateBundle; public interface TemplateBundleMapper { TemplateBundle selectTemplateBundleByCode(String bundleCode); TemplateBundle selectDefaultTemplateBundle(); List selectTemplateBundleList(TemplateBundle templateBundle); } ``` Create `ruoyi-generator/src/main/resources/mapper/generator/TemplateBundleMapper.xml`: ```xml select bundle_id, bundle_code, bundle_name, description, status, is_default, support_backend, support_frontend, support_admin_frontend, support_sql, sort_order, create_by, create_time, update_by, update_time, remark from sys_template_bundle ``` - [ ] **Step 5: Add service contract and implementation** Create `ruoyi-generator/src/main/java/com/ruoyi/generator/service/ITemplateBundleService.java`: ```java package com.ruoyi.generator.service; import java.util.List; import com.ruoyi.generator.domain.TemplateBundle; public interface ITemplateBundleService { String DEFAULT_BUNDLE_CODE = "qing"; List listEnabledBundles(); TemplateBundle getDefaultBundle(); TemplateBundle getRequiredEnabledBundle(String bundleCode); List getSupportedTemplateTypes(TemplateBundle bundle, String frontendEnabled); List getSupportedTemplateTypes(String bundleCode, String frontendEnabled); void assertTemplateTypeSupported(TemplateBundle bundle, String templateType, String frontendEnabled); } ``` Create `ruoyi-generator/src/main/java/com/ruoyi/generator/service/TemplateBundleServiceImpl.java`: ```java package com.ruoyi.generator.service; import java.util.ArrayList; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.generator.domain.TemplateBundle; import com.ruoyi.generator.mapper.TemplateBundleMapper; @Service public class TemplateBundleServiceImpl implements ITemplateBundleService { @Autowired private TemplateBundleMapper templateBundleMapper; @Override public List listEnabledBundles() { TemplateBundle query = new TemplateBundle(); query.setStatus("0"); return templateBundleMapper.selectTemplateBundleList(query); } @Override public TemplateBundle getDefaultBundle() { TemplateBundle bundle = templateBundleMapper.selectDefaultTemplateBundle(); return bundle == null ? qingFallbackBundle() : bundle; } @Override public TemplateBundle getRequiredEnabledBundle(String bundleCode) { String code = StringUtils.defaultIfEmpty(bundleCode, getDefaultBundle().getBundleCode()); TemplateBundle bundle = templateBundleMapper.selectTemplateBundleByCode(code); if (bundle == null && DEFAULT_BUNDLE_CODE.equals(code)) { bundle = qingFallbackBundle(); } if (bundle == null) { throw new ServiceException("Code template bundle does not exist: " + code); } if (!"0".equals(bundle.getStatus())) { throw new ServiceException("Code template bundle is disabled: " + code); } return bundle; } @Override public List getSupportedTemplateTypes(String bundleCode, String frontendEnabled) { return getSupportedTemplateTypes(getRequiredEnabledBundle(bundleCode), frontendEnabled); } @Override public List getSupportedTemplateTypes(TemplateBundle bundle, String frontendEnabled) { List types = new ArrayList(); if (isSupported(bundle == null ? null : bundle.getSupportBackend())) { types.add("backend"); } if (!"0".equals(frontendEnabled) && isSupported(bundle == null ? null : bundle.getSupportFrontend())) { types.add("frontend"); } if (isSupported(bundle == null ? null : bundle.getSupportAdminFrontend())) { types.add("admin_frontend"); } if (isSupported(bundle == null ? null : bundle.getSupportSql())) { types.add("sql"); } return types; } @Override public void assertTemplateTypeSupported(TemplateBundle bundle, String templateType, String frontendEnabled) { if (!getSupportedTemplateTypes(bundle, frontendEnabled).contains(templateType)) { String code = bundle == null ? DEFAULT_BUNDLE_CODE : bundle.getBundleCode(); throw new ServiceException("Code template bundle " + code + " does not support template type: " + templateType); } } private boolean isSupported(String value) { return !"0".equals(value); } private TemplateBundle qingFallbackBundle() { TemplateBundle bundle = new TemplateBundle(); bundle.setBundleCode(DEFAULT_BUNDLE_CODE); bundle.setBundleName("Qing Default Template"); bundle.setStatus("0"); bundle.setIsDefault("1"); bundle.setSupportBackend("1"); bundle.setSupportFrontend("1"); bundle.setSupportAdminFrontend("1"); bundle.setSupportSql("1"); bundle.setSortOrder(0); return bundle; } } ``` - [ ] **Step 6: Run test to verify it passes** Run: `mvn -pl ruoyi-generator -Dtest=TemplateBundleServiceImplTest test` Expected: PASS with all 5 tests green. - [ ] **Step 7: Commit** ```bash git add ruoyi-generator/src/main/java/com/ruoyi/generator/domain/TemplateBundle.java \ ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/TemplateBundleMapper.java \ ruoyi-generator/src/main/resources/mapper/generator/TemplateBundleMapper.xml \ ruoyi-generator/src/main/java/com/ruoyi/generator/service/ITemplateBundleService.java \ ruoyi-generator/src/main/java/com/ruoyi/generator/service/TemplateBundleServiceImpl.java \ ruoyi-generator/src/test/java/com/ruoyi/generator/service/TemplateBundleServiceImplTest.java git commit -m "feat: add template bundle service" ``` --- ### Task 2: Persist Bundle Codes On Templates And Projects **Files:** - Modify: `ruoyi-generator/src/main/java/com/ruoyi/generator/domain/Template.java` - Modify: `ruoyi-generator/src/main/resources/mapper/generator/TemplateMapper.xml` - Modify: `ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/FrontProject.java` - Modify: `ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/dto/FrontProjectCreateRequest.java` - Modify: `ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/dto/FrontProjectUpdateRequest.java` - Modify: `ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/dto/AiGenerationTaskCreateRequest.java` - Modify: `ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenProject.java` - Modify: `ruoyi-generator/src/main/resources/mapper/front/FrontProjectMapper.xml` - Modify: `ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/FrontProjectServiceImplTest.java` - [ ] **Step 1: Write failing project persistence tests** Add these tests to `FrontProjectServiceImplTest`: ```java @Mock private com.ruoyi.generator.service.ITemplateBundleService templateBundleService; ``` In `setUp`, add: ```java setField("templateBundleService", templateBundleService); when(templateBundleService.getDefaultBundle()).thenReturn(templateBundle("qing")); when(templateBundleService.getRequiredEnabledBundle("qing")).thenReturn(templateBundle("qing")); when(templateBundleService.getRequiredEnabledBundle("vue3-plus")).thenReturn(templateBundle("vue3-plus")); ``` Add tests: ```java @Test public void createProjectDefaultsToQingCodeTemplate() { FrontProjectCreateRequest request = new FrontProjectCreateRequest(); request.setProjectName("Demo"); FrontProject project = service.createProject(7L, request); assertEquals("qing", project.getCodeTemplate()); verify(frontProjectMapper).insertFrontProject(project); } @Test public void createProjectPersistsSelectedCodeTemplate() { FrontProjectCreateRequest request = new FrontProjectCreateRequest(); request.setProjectName("Demo"); request.setCodeTemplate("vue3-plus"); FrontProject project = service.createProject(7L, request); assertEquals("vue3-plus", project.getCodeTemplate()); verify(templateBundleService).getRequiredEnabledBundle("vue3-plus"); } @Test public void updateProjectPersistsSelectedCodeTemplate() { when(frontProjectMapper.selectFrontProjectByUserAndId(7L, 10L)).thenReturn(project()); FrontProjectUpdateRequest request = new FrontProjectUpdateRequest(); request.setProjectName("Demo"); request.setCodeTemplate("vue3-plus"); service.updateProject(7L, 10L, request); ArgumentCaptor projectCaptor = ArgumentCaptor.forClass(FrontProject.class); verify(frontProjectMapper).updateFrontProject(projectCaptor.capture()); assertEquals("vue3-plus", projectCaptor.getValue().getCodeTemplate()); } private com.ruoyi.generator.domain.TemplateBundle templateBundle(String code) { com.ruoyi.generator.domain.TemplateBundle bundle = new com.ruoyi.generator.domain.TemplateBundle(); bundle.setBundleCode(code); bundle.setBundleName(code); bundle.setStatus("0"); bundle.setSupportBackend("1"); bundle.setSupportFrontend("1"); bundle.setSupportAdminFrontend("1"); bundle.setSupportSql("1"); return bundle; } ``` - [ ] **Step 2: Run tests to verify they fail** Run: `mvn -pl ruoyi-generator -Dtest=FrontProjectServiceImplTest test` Expected: compilation fails because `codeTemplate` accessors and `templateBundleService` field do not exist. - [ ] **Step 3: Add fields and accessors** In `Template.java`, add after `templateType`: ```java private String bundleCode; public String getBundleCode() { return bundleCode; } public void setBundleCode(String bundleCode) { this.bundleCode = bundleCode; } ``` Also add to `toString()`: ```java .append("bundleCode", getBundleCode()) ``` In `FrontProject.java`, add after `industryTemplate`: ```java private String codeTemplate; public String getCodeTemplate() { return codeTemplate; } public void setCodeTemplate(String codeTemplate) { this.codeTemplate = codeTemplate; } ``` In `FrontProjectCreateRequest.java`, `FrontProjectUpdateRequest.java`, and `AiGenerationTaskCreateRequest.java`, add: ```java private String codeTemplate; public String getCodeTemplate() { return codeTemplate; } public void setCodeTemplate(String codeTemplate) { this.codeTemplate = codeTemplate; } ``` In `GenProject.java`, add near transient generation fields: ```java private transient String codeTemplate; public String getCodeTemplate() { return codeTemplate; } public void setCodeTemplate(String codeTemplate) { this.codeTemplate = codeTemplate; } ``` - [ ] **Step 4: Map `bundle_code` and `code_template`** In `TemplateMapper.xml`, update `TemplateResult`: ```xml ``` Update `selectTemplateVo`: ```xml select t.template_id, t.template_name, t.template_path, t.template_desc, t.template_type, t.bundle_code, t.template_status from sys_template t ``` Add filter: ```xml AND t.bundle_code = #{bundleCode} ``` Update `selectTemplateByTemplateId` selected columns: ```xml select t.template_id, t.template_name, t.template_path, t.template_desc, t.template_type, t.bundle_code, t.template_status, tf.template_file_id, tf.file_name, tf.file_path, tf.file_content, tf.create_by, tf.create_time, tf.update_by, tf.update_time ``` Add `bundle_code` to insert and update trims: ```xml bundle_code, ``` ```xml #{bundleCode}, ``` ```xml bundle_code = #{bundleCode}, ``` In `FrontProjectMapper.xml`, add result: ```xml ``` Update `selectFrontProjectVo` to include `code_template` after `industry_template`: ```xml select project_id, user_id, project_name, project_file_name, package_name, version, project_desc, requirement_keyword, industry_template, code_template, style_preset, front_framework, back_framework, frontend_enabled, generate_status, preview_status, app_blueprint, business_loop_plan, er_diagram, business_blueprint, status, create_time, update_time, remark from front_project ``` Add insert column/value: ```xml code_template, ``` ```xml #{codeTemplate}, ``` Add update: ```xml code_template = #{codeTemplate}, ``` - [ ] **Step 5: Default and validate project `codeTemplate`** In `FrontProjectServiceImpl`, add import: ```java import com.ruoyi.generator.service.ITemplateBundleService; ``` Add field: ```java @Autowired private ITemplateBundleService templateBundleService; ``` In `createProject`, after `project.setIndustryTemplate(request.getIndustryTemplate());`, add: ```java project.setCodeTemplate(resolveCodeTemplate(request.getCodeTemplate())); ``` In `updateProject`, after `project.setIndustryTemplate(request.getIndustryTemplate());`, add: ```java project.setCodeTemplate(resolveNullableCodeTemplate(request.getCodeTemplate())); ``` Add helper methods: ```java private String resolveCodeTemplate(String codeTemplate) { String code = StringUtils.defaultIfEmpty(codeTemplate, templateBundleService.getDefaultBundle().getBundleCode()); return templateBundleService.getRequiredEnabledBundle(code).getBundleCode(); } private String resolveNullableCodeTemplate(String codeTemplate) { if (codeTemplate == null) { return null; } return resolveCodeTemplate(codeTemplate); } ``` In `FrontProjectConverter.toGenProject`, after `genProject.setStylePreset(project.getStylePreset());`, add: ```java genProject.setCodeTemplate(project.getCodeTemplate()); ``` - [ ] **Step 6: Run tests to verify they pass** Run: `mvn -pl ruoyi-generator -Dtest=FrontProjectServiceImplTest test` Expected: PASS. - [ ] **Step 7: Commit** ```bash git add ruoyi-generator/src/main/java/com/ruoyi/generator/domain/Template.java \ ruoyi-generator/src/main/resources/mapper/generator/TemplateMapper.xml \ ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/FrontProject.java \ ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/dto/FrontProjectCreateRequest.java \ ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/dto/FrontProjectUpdateRequest.java \ ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/dto/AiGenerationTaskCreateRequest.java \ ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenProject.java \ ruoyi-generator/src/main/resources/mapper/front/FrontProjectMapper.xml \ ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/FrontProjectServiceImpl.java \ ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/FrontProjectConverter.java \ ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/FrontProjectServiceImplTest.java git commit -m "feat: persist selected code template bundle" ``` --- ### Task 3: Resolve Generation Templates By Bundle **Files:** - Modify: `ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenProjectServiceImpl.java` - Modify: `ruoyi-generator/src/test/java/com/ruoyi/generator/service/GenProjectServiceImplTest.java` - [ ] **Step 1: Write failing bundle selection tests** In `GenProjectServiceImplTest`, add mock field: ```java @Mock private ITemplateBundleService templateBundleService; ``` In `setUp`, add: ```java setField("templateBundleService", templateBundleService); when(templateBundleService.getDefaultBundle()).thenReturn(bundle("qing")); when(templateBundleService.getRequiredEnabledBundle("qing")).thenReturn(bundle("qing")); when(templateBundleService.getRequiredEnabledBundle("vue3-plus")).thenReturn(bundle("vue3-plus")); ``` Add tests: ```java @Test public void frontDraftTemplateSelectionUsesProjectCodeTemplate() { GenProject project = project(); project.setFrontDraft(true); project.setCodeTemplate("vue3-plus"); Template vue3Backend = template(9301L, 0L); vue3Backend.setBundleCode("vue3-plus"); Template qingBackend = template(9201L, 0L); qingBackend.setBundleCode("qing"); when(templateService.selectTemplateList(any(Template.class))).thenReturn(Arrays.asList(qingBackend, vue3Backend)); when(sysProjectStructureService.selectSysProjectStructureList(any(SysProjectStructure.class))).thenReturn(Arrays.asList( structure(1L, 0L, "{projectName}", "folder", null, null) )); service.getProjectStructure(project, "backend"); ArgumentCaptor