From aa547ef365827fc2ee6eadd4a21ba5362d67ea4b Mon Sep 17 00:00:00 2001 From: let5sne Date: Thu, 9 Jul 2026 15:15:14 +0800 Subject: [PATCH] fix(mobile): avoid seeded empty competency --- .../aihr/service/AihrPracticeSeedService.java | 20 ++++++++--- .../service/AihrPracticeSeedServiceTest.java | 36 +++++++++++++++++++ docs/20260708/uni-app用户侧前端重建计划.md | 1 + 3 files changed, 52 insertions(+), 5 deletions(-) diff --git a/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java b/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java index 9d2f60b0..9180e5f4 100644 --- a/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java +++ b/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java @@ -765,11 +765,11 @@ public class AihrPracticeSeedService { List rows = jdbcTemplate.query(""" SELECT COUNT(*) completed, - COALESCE(ROUND(AVG(total_score)), 82) score, + COALESCE(ROUND(AVG(total_score)), 0) score, SUM(CASE WHEN status = '待复盘' THEN 1 ELSE 0 END) pending_review, - COALESCE(ROUND(AVG(dim_compliance)), 82) compliance, - COALESCE(ROUND(AVG(dim_communication)), 82) communication, - COALESCE(ROUND(AVG(dim_emotion)), 82) emotion, + COALESCE(ROUND(AVG(dim_compliance)), 0) compliance, + COALESCE(ROUND(AVG(dim_communication)), 0) communication, + COALESCE(ROUND(AVG(dim_emotion)), 0) emotion, COALESCE(SUM(incentive_point), 0) incentive_points FROM aihr_practice_session WHERE tenant_id = ? AND mode = 'mobile' AND (? = '' OR ext_party_id = ?) @@ -782,7 +782,7 @@ public class AihrPracticeSeedService { rs.getInt("emotion"), rs.getInt("incentive_points") ), TENANT_ID, party, party); - CompetencySnapshot snapshot = rows.isEmpty() ? new CompetencySnapshot(0, 82, 0, 82, 82, 82, 0) : rows.get(0); + CompetencySnapshot snapshot = rows.isEmpty() ? new CompetencySnapshot(0, 0, 0, 0, 0, 0, 0) : rows.get(0); int trainingMinutes = snapshot.completed() * 8; int contribution = snapshot.completed() == 0 && snapshot.incentivePoints() == 0 ? 0 @@ -835,6 +835,9 @@ public class AihrPracticeSeedService { } private String aiLevel(int completed, int score) { + if (completed <= 0) { + return "未评级"; + } if (completed >= 20 && score >= 90) { return "高级"; } @@ -845,6 +848,13 @@ public class AihrPracticeSeedService { } private List growthPath(int completed, int score) { + if (completed <= 0) { + return List.of( + new GrowthStageResponse("初级", 0, "待训练", "完成3次基础场景并保持80分以上"), + new GrowthStageResponse("中级", 0, "待积累", "完成10次训练且测评分达到85"), + new GrowthStageResponse("高级", 0, "待积累", "完成20次训练、主管复盘稳定通过") + ); + } return List.of( new GrowthStageResponse("初级", Math.min(100, Math.max(30, completed * 10)), completed >= 3 ? "已达成" : "进行中", "完成3次基础场景并保持80分以上"), new GrowthStageResponse("中级", Math.min(100, Math.max(0, completed * 6 + Math.max(0, score - 75))), completed >= 10 && score >= 85 ? "已达成" : "待积累", "完成10次训练且测评分达到85"), diff --git a/backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrPracticeSeedServiceTest.java b/backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrPracticeSeedServiceTest.java index 0a48abcc..1aee70b9 100644 --- a/backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrPracticeSeedServiceTest.java +++ b/backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrPracticeSeedServiceTest.java @@ -1,6 +1,7 @@ package org.dromara.aihr.service; import com.fasterxml.jackson.databind.ObjectMapper; +import org.dromara.aihr.domain.AihrPracticeDto.CompetencyResponse; import org.dromara.aihr.domain.AihrPracticeDto.PracticeAssignmentRequest; import org.dromara.common.core.exception.ServiceException; import org.junit.jupiter.api.Tag; @@ -106,6 +107,35 @@ public class AihrPracticeSeedServiceTest { assertEquals(List.of("000000", "SUP-1", "EMP-1", 5), jdbcTemplate.reviewArgs); } + @Test + public void competencyDoesNotSeedScoreForEmptyEmployee() { + JdbcTemplate jdbcTemplate = mock(JdbcTemplate.class); + when(jdbcTemplate.queryForObject(ArgumentMatchers.anyString(), eq(Integer.class), ArgumentMatchers.any(), ArgumentMatchers.any())) + .thenReturn(1); + when(jdbcTemplate.query(contains("COUNT(*) completed"), any(RowMapper.class), eq("000000"), eq("13900000999"), eq("13900000999"))) + .thenAnswer(invocation -> mapOne(invocation, Map.of( + "completed", "0", + "score", "0", + "pending_review", "0", + "compliance", "0", + "communication", "0", + "emotion", "0", + "incentive_points", "0" + ))); + AihrPracticeSeedService service = new AihrPracticeSeedService(new ObjectMapper(), jdbcTemplate, null, null); + + CompetencyResponse profile = service.competency("13900000999"); + + assertEquals(0, profile.score()); + assertEquals(0, profile.assessmentScore()); + assertEquals(0, profile.completed()); + assertEquals(0, profile.trainingMinutes()); + assertEquals(0, profile.contribution()); + assertEquals("未评级", profile.aiLevel()); + assertTrue(profile.dimensions().stream().allMatch(item -> Integer.valueOf(0).equals(item.value()))); + assertTrue(profile.growthPath().stream().allMatch(item -> Integer.valueOf(0).equals(item.progress()))); + } + @SuppressWarnings({"unchecked", "rawtypes"}) private static List mapOne(InvocationOnMock invocation, Map values) throws SQLException { RowMapper mapper = invocation.getArgument(1); @@ -120,6 +150,12 @@ public class AihrPracticeSeedServiceTest { ResultSet resultSet = mock(ResultSet.class); for (Map.Entry entry : values.entrySet()) { when(resultSet.getString(entry.getKey())).thenReturn(entry.getValue()); + try { + int intValue = Integer.parseInt(entry.getValue()); + when(resultSet.getInt(entry.getKey())).thenReturn(intValue); + } catch (NumberFormatException ignored) { + // String-only test column. + } } mapped.add((T) mapper.mapRow(resultSet, index++)); } diff --git a/docs/20260708/uni-app用户侧前端重建计划.md b/docs/20260708/uni-app用户侧前端重建计划.md index bfbd3d36..50e3aa40 100644 --- a/docs/20260708/uni-app用户侧前端重建计划.md +++ b/docs/20260708/uni-app用户侧前端重建计划.md @@ -149,6 +149,7 @@ mobile-uni/ - 员工端每日三题排序已改为按题目生成顺序展示: 后端 assignments 查询对 `source=daily` 按 ID 升序返回,不再把第 3 题排在最前。 - 员工端专项训练已补完成回写: 主管指派返回真实专项 ID,员工从专项入口完成对练后对应 `aihr_practice_assignment` 会置为已完成,不再长期卡在“待训练”。 - 员工端成长证据贡献度已收紧: 无训练记录且无主管人工加分时贡献度为 0,不再给空用户默认 20 分。 +- 员工端空画像已收紧: 无移动端对练记录时综合能力、维度分、测评分和成长路径均为 0,AI 等级为“未评级”,不再沿用演示 seed 的 82 分/初级/30%。 - 员工端训练前预习卡已接入: 新 uni-app 对练页复用 `/api/train/practice/scenarios/{id}/prep-card`,开始前展示要点、红线和可说话术。 - 员工端训练前预习卡与页头已收敛展示: 隐藏内部 promptVersion,训练卡标题跟随当前专项场景,页头不再固定写“投诉接待”。 - 员工端训练中已接 SOP 求助: 新 uni-app 对练页复用 `/api/knowledge/search`,展示正式检索答案和引用,失败明确报错不兜假答案。