|
|
|
|
|
# 学校默认实训任务配置 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:** 为每所学校异步创建可由管理员教师维护的默认实训任务,并使教学班任务按“教学班 → 学校默认 → 平台基线”回退。
|
|
|
|
|
|
|
|
|
|
|
|
**Architecture:** 复用 `task_allocation` 保存三层任务配置,以服务端生成的 `SCHOOL_DEFAULT:{schoolId}` 作为学校默认层 ID。新建学校时先写初始化状态,再投递应用内异步复制;读取与保存通过专用服务和登录态鉴权,不再信任客户端提供的操作者 ID。
|
|
|
|
|
|
|
|
|
|
|
|
**Tech Stack:** Spring Boot、MyBatis、Spring `@Async` / `AsyncTaskExecutor`、Vue 3、Element Plus、Vite、JUnit 5、Node 静态断言。
|
|
|
|
|
|
|
|
|
|
|
|
## Global Constraints
|
|
|
|
|
|
|
|
|
|
|
|
- 平台基线固定为 `task_allocation.class_id = school_id = '999999999'`,只能读取。
|
|
|
|
|
|
- 学校默认固定为 `class_id = 'SCHOOL_DEFAULT:' + schoolId`,不得由前端传入或编辑其他学校。
|
|
|
|
|
|
- 教学班已有配置优先级最高;保留“仅教学班创建教师可编辑教学班任务”的现有逻辑。
|
|
|
|
|
|
- 新建学校 HTTP 请求不得等待默认任务复制完成。
|
|
|
|
|
|
- 前端以 `teacherAdmin=true` 显示菜单;后端用登录 `userId` 回查 `userinfo`,确认 `teacher_admin=true`、`role=3` 和本校归属后才可查看、保存、重试默认任务。
|
|
|
|
|
|
- 新增生产代码前必须先新增对应失败测试并实际运行确认失败。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
### Task 1: 建立学校默认任务状态与配置服务
|
|
|
|
|
|
|
|
|
|
|
|
**Files:**
|
|
|
|
|
|
- Create: `docs/sql/2026-07-30-school-default-task-initialization.sql`
|
|
|
|
|
|
- Create: `src/main/java/com/sztzjy/linkCommerce/entity/SchoolDefaultTaskInitialization.java`
|
|
|
|
|
|
- Create: `src/main/java/com/sztzjy/linkCommerce/entity/SchoolDefaultTaskInitializationExample.java`
|
|
|
|
|
|
- Create: `src/main/java/com/sztzjy/linkCommerce/mapper/SchoolDefaultTaskInitializationMapper.java`
|
|
|
|
|
|
- Create: `src/main/resources/mappers/SchoolDefaultTaskInitializationMapper.xml`
|
|
|
|
|
|
- Create: `src/main/java/com/sztzjy/linkCommerce/service/SchoolDefaultTaskService.java`
|
|
|
|
|
|
- Create: `src/main/java/com/sztzjy/linkCommerce/service/impl/SchoolDefaultTaskServiceImpl.java`
|
|
|
|
|
|
- Test: `src/test/java/com/sztzjy/linkCommerce/service/impl/SchoolDefaultTaskServiceImplTest.java`
|
|
|
|
|
|
|
|
|
|
|
|
**Interfaces:**
|
|
|
|
|
|
- Consumes: `TaskAllocationMapper`, `SchoolClassMapper`, `SchoolDefaultTaskInitializationMapper`.
|
|
|
|
|
|
- Produces: `String schoolDefaultClassId(String schoolId)`, `List<TaskAllocation> resolveForTeachingClass(String classId)`, `List<TaskAllocation> getSchoolDefault(String schoolId)`, `void replaceSchoolDefault(String schoolId, List<TaskAllocation> tasks)`, `SchoolDefaultTaskInitialization getInitialization(String schoolId)`.
|
|
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 1: Write failing fallback tests**
|
|
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
|
@Test
|
|
|
|
|
|
void resolvesSchoolDefaultWhenTeachingClassHasNoConfiguration() {
|
|
|
|
|
|
when(taskMapper.selectByExample(forClass("class-1"))).thenReturn(Collections.emptyList());
|
|
|
|
|
|
when(classMapper.selectByPrimaryKey("class-1")).thenReturn(teachingClass("class-1", "school-1"));
|
|
|
|
|
|
when(taskMapper.selectByExample(forClass("SCHOOL_DEFAULT:school-1"))).thenReturn(List.of(task("任务A", (byte) 0)));
|
|
|
|
|
|
|
|
|
|
|
|
assertThat(service.resolveForTeachingClass("class-1")).extracting(TaskAllocation::getModule).containsExactly("任务A");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
|
void neverOverwritesExistingSchoolDefaultDuringInitialization() {
|
|
|
|
|
|
when(taskMapper.countByExample(forClass("SCHOOL_DEFAULT:school-1"))).thenReturn(1L);
|
|
|
|
|
|
service.initializeSchoolDefault("school-1");
|
|
|
|
|
|
verify(taskMapper, never()).insert(any());
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 2: Run the new test to verify it fails**
|
|
|
|
|
|
|
|
|
|
|
|
Run: `mvn -q -Dtest=SchoolDefaultTaskServiceImplTest test`
|
|
|
|
|
|
|
|
|
|
|
|
Expected: FAIL because `SchoolDefaultTaskService` and its implementation do not exist.
|
|
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 3: Add schema and minimal service implementation**
|
|
|
|
|
|
|
|
|
|
|
|
```sql
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS school_default_task_initialization (
|
|
|
|
|
|
school_id varchar(64) NOT NULL,
|
|
|
|
|
|
status varchar(16) NOT NULL,
|
|
|
|
|
|
last_error varchar(1000) NULL,
|
|
|
|
|
|
attempt_count int NOT NULL DEFAULT 0,
|
|
|
|
|
|
create_time datetime NOT NULL,
|
|
|
|
|
|
update_time datetime NOT NULL,
|
|
|
|
|
|
PRIMARY KEY (school_id)
|
|
|
|
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
|
public static String schoolDefaultClassId(String schoolId) {
|
|
|
|
|
|
return "SCHOOL_DEFAULT:" + schoolId;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public List<TaskAllocation> resolveForTeachingClass(String classId) {
|
|
|
|
|
|
List<TaskAllocation> classTasks = listByClassId(classId);
|
|
|
|
|
|
if (!classTasks.isEmpty()) return classTasks;
|
|
|
|
|
|
SchoolClass schoolClass = requireTeachingClass(classId);
|
|
|
|
|
|
List<TaskAllocation> schoolTasks = listByClassId(schoolDefaultClassId(schoolClass.getSchoolId()));
|
|
|
|
|
|
return schoolTasks.isEmpty() ? listByClassId(PLATFORM_DEFAULT_CLASS_ID) : schoolTasks;
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 4: Run the focused test to verify it passes**
|
|
|
|
|
|
|
|
|
|
|
|
Run: `mvn -q -Dtest=SchoolDefaultTaskServiceImplTest test`
|
|
|
|
|
|
|
|
|
|
|
|
Expected: PASS, including teaching-class, school-default, and platform fallback cases.
|
|
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 5: Commit**
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
git add docs/sql/2026-07-30-school-default-task-initialization.sql src/main/java/com/sztzjy/linkCommerce/entity/SchoolDefaultTaskInitialization.java src/main/java/com/sztzjy/linkCommerce/entity/SchoolDefaultTaskInitializationExample.java src/main/java/com/sztzjy/linkCommerce/mapper/SchoolDefaultTaskInitializationMapper.java src/main/resources/mappers/SchoolDefaultTaskInitializationMapper.xml src/main/java/com/sztzjy/linkCommerce/service/SchoolDefaultTaskService.java src/main/java/com/sztzjy/linkCommerce/service/impl/SchoolDefaultTaskServiceImpl.java src/test/java/com/sztzjy/linkCommerce/service/impl/SchoolDefaultTaskServiceImplTest.java
|
|
|
|
|
|
git commit -m "feat: add school default task service"
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### Task 2: 异步初始化并接入创建学校流程
|
|
|
|
|
|
|
|
|
|
|
|
**Files:**
|
|
|
|
|
|
- Modify: `src/main/java/com/sztzjy/linkCommerce/controller/platformadmin/PlatformAdminController.java:73-88`
|
|
|
|
|
|
- Modify: `src/main/java/com/sztzjy/linkCommerce/service/SchoolDefaultTaskService.java`
|
|
|
|
|
|
- Modify: `src/main/java/com/sztzjy/linkCommerce/service/impl/SchoolDefaultTaskServiceImpl.java`
|
|
|
|
|
|
- Test: `src/test/java/com/sztzjy/linkCommerce/controller/platformadmin/PlatformAdminControllerTest.java`
|
|
|
|
|
|
- Test: `src/test/java/com/sztzjy/linkCommerce/service/impl/SchoolDefaultTaskServiceImplTest.java`
|
|
|
|
|
|
|
|
|
|
|
|
**Interfaces:**
|
|
|
|
|
|
- Consumes: Task 1 `SchoolDefaultTaskService.enqueueInitialization(String schoolId)`.
|
|
|
|
|
|
- Produces: non-blocking school creation and an idempotent `@Async initializeSchoolDefault(String schoolId)` task.
|
|
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 1: Write failing controller and service tests**
|
|
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
|
@Test
|
|
|
|
|
|
void addSchoolQueuesDefaultTaskInitializationAfterSuccessfulInsert() {
|
|
|
|
|
|
when(schoolMapper.insertSelective(any())).thenReturn(1);
|
|
|
|
|
|
controller.addSchool(new School(), platformAdminRequest());
|
|
|
|
|
|
verify(defaultTaskService).enqueueInitialization(anyString());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
|
void initializationCopiesPlatformTasksAndMarksReady() {
|
|
|
|
|
|
when(taskMapper.selectByExample(forClass("999999999"))).thenReturn(List.of(task("任务A", (byte) 0)));
|
|
|
|
|
|
service.initializeSchoolDefault("school-1");
|
|
|
|
|
|
verify(taskMapper).insert(argThat(row -> row.getClassId().equals("SCHOOL_DEFAULT:school-1")));
|
|
|
|
|
|
verify(statusMapper).updateByPrimaryKeySelective(argThat(row -> "READY".equals(row.getStatus())));
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 2: Run the tests to verify they fail**
|
|
|
|
|
|
|
|
|
|
|
|
Run: `mvn -q -Dtest=PlatformAdminControllerTest,SchoolDefaultTaskServiceImplTest test`
|
|
|
|
|
|
|
|
|
|
|
|
Expected: FAIL because school creation does not queue default task initialization and no asynchronous state transition exists.
|
|
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 3: Implement status transitions and asynchronous submission**
|
|
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
|
public void enqueueInitialization(String schoolId) {
|
|
|
|
|
|
createPendingIfAbsent(schoolId);
|
|
|
|
|
|
asyncSelf.initializeSchoolDefault(schoolId);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Async
|
|
|
|
|
|
public void initializeSchoolDefault(String schoolId) {
|
|
|
|
|
|
markRunning(schoolId);
|
|
|
|
|
|
try {
|
|
|
|
|
|
copyPlatformTasksOnlyWhenSchoolDefaultIsEmpty(schoolId);
|
|
|
|
|
|
markReady(schoolId);
|
|
|
|
|
|
} catch (RuntimeException ex) {
|
|
|
|
|
|
markFailed(schoolId, ex.getMessage());
|
|
|
|
|
|
throw ex;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Add `defaultTaskService.enqueueInitialization(school.getSchoolId())` after `createDefaultConfig(...)` succeeds. Invoke the async method through a Spring proxy or a dedicated async bean so `@Async` is effective.
|
|
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 4: Run focused tests to verify they pass**
|
|
|
|
|
|
|
|
|
|
|
|
Run: `mvn -q -Dtest=PlatformAdminControllerTest,SchoolDefaultTaskServiceImplTest test`
|
|
|
|
|
|
|
|
|
|
|
|
Expected: PASS; duplicate initialization preserves a school administrator’s prior default-task edits.
|
|
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 5: Commit**
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
git add src/main/java/com/sztzjy/linkCommerce/controller/platformadmin/PlatformAdminController.java src/main/java/com/sztzjy/linkCommerce/service/SchoolDefaultTaskService.java src/main/java/com/sztzjy/linkCommerce/service/impl/SchoolDefaultTaskServiceImpl.java src/test/java/com/sztzjy/linkCommerce/controller/platformadmin/PlatformAdminControllerTest.java src/test/java/com/sztzjy/linkCommerce/service/impl/SchoolDefaultTaskServiceImplTest.java
|
|
|
|
|
|
git commit -m "feat: initialize school default tasks asynchronously"
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### Task 3: 加入管理员教师接口并替换任务读取回退
|
|
|
|
|
|
|
|
|
|
|
|
**Files:**
|
|
|
|
|
|
- Create: `src/main/java/com/sztzjy/linkCommerce/controller/stu/SchoolDefaultTaskController.java`
|
|
|
|
|
|
- Modify: `src/main/java/com/sztzjy/linkCommerce/controller/stu/TaskAllocationController.java:31-61`
|
|
|
|
|
|
- Modify: `src/main/java/com/sztzjy/linkCommerce/service/SchoolDefaultTaskService.java`
|
|
|
|
|
|
- Test: `src/test/java/com/sztzjy/linkCommerce/controller/stu/SchoolDefaultTaskControllerTest.java`
|
|
|
|
|
|
- Test: `src/test/java/com/sztzjy/linkCommerce/controller/stu/TaskAllocationControllerTest.java`
|
|
|
|
|
|
|
|
|
|
|
|
**Interfaces:**
|
|
|
|
|
|
- Consumes: authenticated `JwtUser` for identity, `UserinfoMapper` for authoritative manager-teacher and school checks, Task 1 services.
|
|
|
|
|
|
- Produces:
|
|
|
|
|
|
- `GET /api/school-default-tasks` → `{ status, tasks }`
|
|
|
|
|
|
- `PUT /api/school-default-tasks` → replaces this school’s full default task list
|
|
|
|
|
|
- `POST /api/school-default-tasks/retry` → queues retry and returns `PENDING`
|
|
|
|
|
|
- student list endpoint applies the three-level fallback.
|
|
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 1: Write failing authorization and fallback tests**
|
|
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
|
@Test
|
|
|
|
|
|
void managerTeacherCanSaveOnlyOwnSchoolDefaults() {
|
|
|
|
|
|
ResultEntity result = controller.replace(List.of(task("任务A", (byte) 0)), requestFor(teacher("teacher-1", "school-1", true)));
|
|
|
|
|
|
assertEquals(HttpStatus.OK, result.getStatusCode());
|
|
|
|
|
|
verify(service).replaceSchoolDefault("school-1", List.of(task("任务A", (byte) 0)));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
|
void ordinaryTeacherCannotSaveSchoolDefaults() {
|
|
|
|
|
|
assertThrows(UnAuthorizedException.class,
|
|
|
|
|
|
() -> controller.replace(List.of(task("任务A", (byte) 0)), requestFor(teacher("teacher-2", "school-1", false))));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
|
void studentTaskLookupUsesSchoolDefaultBeforePlatformDefault() {
|
|
|
|
|
|
when(defaultTaskService.resolveForTeachingClass("class-1")).thenReturn(List.of(task("学校任务", (byte) 0)));
|
|
|
|
|
|
assertThat(controller.selectTaskAllocationByStudentUserId("student-1").getData())
|
|
|
|
|
|
.extracting(TaskAllocation::getModule).containsExactly("学校任务");
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 2: Run tests to verify they fail**
|
|
|
|
|
|
|
|
|
|
|
|
Run: `mvn -q -Dtest=SchoolDefaultTaskControllerTest,TaskAllocationControllerTest test`
|
|
|
|
|
|
|
|
|
|
|
|
Expected: FAIL because default-task endpoints and school-level fallback are absent.
|
|
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 3: Implement authenticated endpoints and service delegation**
|
|
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
|
private Userinfo requireManagerTeacher(HttpServletRequest request) {
|
|
|
|
|
|
JwtUser user = TokenProvider.getJWTUser(request);
|
|
|
|
|
|
Userinfo operator = userinfoMapper.selectByPrimaryKey(user.getUserId());
|
|
|
|
|
|
if (operator == null || !Integer.valueOf(3).equals(operator.getRole())
|
|
|
|
|
|
|| !Boolean.TRUE.equals(operator.getTeacherAdmin())) {
|
|
|
|
|
|
throw new UnAuthorizedException("仅管理员教师可维护学校默认实训任务");
|
|
|
|
|
|
}
|
|
|
|
|
|
return operator;
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
The controller derives the target `schoolId` exclusively from the authoritative `Userinfo` record; it accepts only the full task list in the body. `TaskAllocationController` delegates both teaching-class and student lookup to `SchoolDefaultTaskService.resolveForTeachingClass` without changing the existing teaching-class write endpoint’s creator check.
|
|
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 4: Run focused tests to verify they pass**
|
|
|
|
|
|
|
|
|
|
|
|
Run: `mvn -q -Dtest=SchoolDefaultTaskControllerTest,TaskAllocationControllerTest test`
|
|
|
|
|
|
|
|
|
|
|
|
Expected: PASS for own-school manager, ordinary teacher, cross-school attempt, retry, and all three fallback layers.
|
|
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 5: Commit**
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
git add src/main/java/com/sztzjy/linkCommerce/controller/stu/SchoolDefaultTaskController.java src/main/java/com/sztzjy/linkCommerce/controller/stu/TaskAllocationController.java src/main/java/com/sztzjy/linkCommerce/service/SchoolDefaultTaskService.java src/test/java/com/sztzjy/linkCommerce/controller/stu/SchoolDefaultTaskControllerTest.java src/test/java/com/sztzjy/linkCommerce/controller/stu/TaskAllocationControllerTest.java
|
|
|
|
|
|
git commit -m "feat: manage school default task allocations"
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### Task 4: 添加管理员教师菜单、配置页和前端验证
|
|
|
|
|
|
|
|
|
|
|
|
**Files:**
|
|
|
|
|
|
- Modify: `E:/workspace/dianshang/e-commerce-internet/.worktrees/optional-administrative-class/src/api/teacher.js:519-548`
|
|
|
|
|
|
- Create: `E:/workspace/dianshang/e-commerce-internet/.worktrees/optional-administrative-class/src/views/teacherEnd/defaultTask/index.vue`
|
|
|
|
|
|
- Modify: `E:/workspace/dianshang/e-commerce-internet/.worktrees/optional-administrative-class/src/router/index.js:450-590`
|
|
|
|
|
|
- Modify: `E:/workspace/dianshang/e-commerce-internet/.worktrees/optional-administrative-class/src/layout/components/Sidebar/index.vue:566-604`
|
|
|
|
|
|
- Create: `E:/workspace/dianshang/e-commerce-internet/.worktrees/optional-administrative-class/tests/school-default-task.static.test.cjs`
|
|
|
|
|
|
|
|
|
|
|
|
**Interfaces:**
|
|
|
|
|
|
- Consumes: Task 3 default-task REST endpoints and `userStore.userInfo.teacherAdmin`.
|
|
|
|
|
|
- Produces: “默认实训任务配置” menu/page for manager teachers only; read-only initializing state with retry; full-list save in ready state.
|
|
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 1: Write a failing static front-end contract test**
|
|
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
|
assert.match(routerSource, /requiresManagerTeacher:\s*true/);
|
|
|
|
|
|
assert.match(sidebarSource, /route\.meta\?\.requiresManagerTeacher/);
|
|
|
|
|
|
assert.match(apiSource, /\/api\/school-default-tasks/);
|
|
|
|
|
|
assert.match(pageSource, /正在初始化,稍后刷新/);
|
|
|
|
|
|
assert.match(pageSource, /立即重试/);
|
|
|
|
|
|
assert.match(pageSource, /:disabled="!isReady"/);
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 2: Run the static test to verify it fails**
|
|
|
|
|
|
|
|
|
|
|
|
Run: `node tests/school-default-task.static.test.cjs`
|
|
|
|
|
|
|
|
|
|
|
|
Expected: FAIL because no manager-only route, API client, or initialization-state page exists.
|
|
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 3: Implement API client, route, filtering, and page**
|
|
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
|
export function getSchoolDefaultTasks() {
|
|
|
|
|
|
return request({ url: "/api/school-default-tasks", method: "GET" });
|
|
|
|
|
|
}
|
|
|
|
|
|
export function saveSchoolDefaultTasks(data) {
|
|
|
|
|
|
return request({ url: "/api/school-default-tasks", method: "PUT", data });
|
|
|
|
|
|
}
|
|
|
|
|
|
export function retrySchoolDefaultTaskInitialization() {
|
|
|
|
|
|
return request({ url: "/api/school-default-tasks/retry", method: "POST" });
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Add the route with `meta.requiresManagerTeacher = true`, filter it when `userStore.userInfo.teacherAdmin !== true`, and block direct rendering in the page before API calls. The new page copies only the task toggle/list behavior from `teacherEnd/task/index.vue`; it has no class selector and never passes `schoolId` or `userId` to the backend. It disables save unless response status equals `READY`.
|
|
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 4: Run front-end test and production build**
|
|
|
|
|
|
|
|
|
|
|
|
Run: `node tests/school-default-task.static.test.cjs; npm run build:prod`
|
|
|
|
|
|
|
|
|
|
|
|
Expected: static contract PASS and production build succeeds; existing non-failing bundle-size/deprecation warnings may remain.
|
|
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 5: Commit**
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
git -C E:/workspace/dianshang/e-commerce-internet/.worktrees/optional-administrative-class add src/api/teacher.js src/views/teacherEnd/defaultTask/index.vue src/router/index.js src/layout/components/Sidebar/index.vue tests/school-default-task.static.test.cjs
|
|
|
|
|
|
git -C E:/workspace/dianshang/e-commerce-internet/.worktrees/optional-administrative-class commit -m "feat: add school default task configuration page"
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### Task 5: 执行完整回归并补齐既有学校初始化
|
|
|
|
|
|
|
|
|
|
|
|
**Files:**
|
|
|
|
|
|
- Create: `docs/sql/2026-07-30-backfill-school-default-tasks.sql`
|
|
|
|
|
|
- Modify: `src/test/java/com/sztzjy/linkCommerce/controller/stu/TeaScoreControllerProgressTest.java` (only if progress service requires explicit expectation for school-default fallback)
|
|
|
|
|
|
|
|
|
|
|
|
**Interfaces:**
|
|
|
|
|
|
- Consumes: completed backend and frontend feature.
|
|
|
|
|
|
- Produces: safe backfill for schools missing defaults and verification evidence that the new fallback does not break existing teacher class ownership or score progress behavior.
|
|
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 1: Write failing regression test for score progress fallback**
|
|
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
|
@Test
|
|
|
|
|
|
void scoreProgressUsesSchoolDefaultWhenTeachingClassHasNoOwnTasks() {
|
|
|
|
|
|
when(taskMapper.selectByExample(forClass("class-1"))).thenReturn(Collections.emptyList());
|
|
|
|
|
|
when(taskMapper.selectByExample(forClass("SCHOOL_DEFAULT:school-1"))).thenReturn(List.of(task("任务A", (byte) 0)));
|
|
|
|
|
|
assertThat(controller.selectStuRankAndScore("class-1", 1, 10).getList().get(0).getTotalTaskCount()).isEqualTo(1);
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 2: Run it to verify it fails**
|
|
|
|
|
|
|
|
|
|
|
|
Run: `mvn -q -Dtest=TeaScoreControllerProgressTest test`
|
|
|
|
|
|
|
|
|
|
|
|
Expected: FAIL if score progress still independently falls back directly from teaching class to platform baseline.
|
|
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 3: Make score progress use the shared fallback resolver and add backfill SQL**
|
|
|
|
|
|
|
|
|
|
|
|
```sql
|
|
|
|
|
|
INSERT INTO task_allocation (id, class_id, school_id, module, disabled_status, sort)
|
|
|
|
|
|
SELECT UUID(), CONCAT('SCHOOL_DEFAULT:', s.school_id), s.school_id, p.module, p.disabled_status, p.sort
|
|
|
|
|
|
FROM school s
|
|
|
|
|
|
JOIN task_allocation p ON p.class_id = '999999999'
|
|
|
|
|
|
WHERE NOT EXISTS (
|
|
|
|
|
|
SELECT 1 FROM task_allocation d WHERE d.class_id = CONCAT('SCHOOL_DEFAULT:', s.school_id)
|
|
|
|
|
|
);
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
The SQL must also insert a `READY` status row for schools where the default rows now exist, using `INSERT ... ON DUPLICATE KEY UPDATE` without overwriting existing status/error metadata.
|
|
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 4: Run full verification**
|
|
|
|
|
|
|
|
|
|
|
|
Run: `mvn -q -DforkCount=0 test`
|
|
|
|
|
|
|
|
|
|
|
|
Run: `node tests/school-default-task.static.test.cjs; node tests/school-product-config.static.test.cjs; node tests/score-reference-progress.static.test.cjs; npm run build:prod`
|
|
|
|
|
|
|
|
|
|
|
|
Expected: backend tests and all stated front-end checks pass.
|
|
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 5: Commit**
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
git add docs/sql/2026-07-30-backfill-school-default-tasks.sql src/test/java/com/sztzjy/linkCommerce/controller/stu/TeaScoreControllerProgressTest.java src/main/java/com/sztzjy/linkCommerce/controller/stu/TeaScoreController.java
|
|
|
|
|
|
git commit -m "test: cover school default task fallback"
|
|
|
|
|
|
```
|