diff --git a/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrOrgSyncService.java b/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrOrgSyncService.java index 9db5e6b0..28bb789c 100644 --- a/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrOrgSyncService.java +++ b/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrOrgSyncService.java @@ -187,10 +187,10 @@ public class AihrOrgSyncService { select count(*) from aihr_org_snapshot """ + where + """ and ( - person_name like '%?%' - or project_name like '%?%' - or dept_name like '%?%' - or position_name like '%?%' + instr(coalesce(person_name, ''), '?') > 0 + or instr(coalesce(project_name, ''), '?') > 0 + or instr(coalesce(dept_name, ''), '?') > 0 + or instr(coalesce(position_name, ''), '?') > 0 ) """, args); List rowArgs = new ArrayList<>(args); diff --git a/backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrOrgSyncServiceTest.java b/backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrOrgSyncServiceTest.java index d0b66441..1f2c0e41 100644 --- a/backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrOrgSyncServiceTest.java +++ b/backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrOrgSyncServiceTest.java @@ -6,11 +6,13 @@ import org.dromara.aihr.domain.AihrOrgSyncDto.SyncRequest; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Tag; import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; import org.springframework.test.util.ReflectionTestUtils; import org.springframework.transaction.support.TransactionTemplate; import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -54,6 +56,21 @@ public class AihrOrgSyncServiceTest { assertFalse(AihrOrgSyncService.hasUnusablePhoneCandidate(mapper.readTree("{\"name\":\"张三\"}"))); } + @Test + public void snapshotSuspectTextQuerySearchesLiteralQuestionMarks() { + SnapshotJdbcTemplate jdbcTemplate = new SnapshotJdbcTemplate(); + AihrOrgSyncService service = new AihrOrgSyncService(new ObjectMapper(), jdbcTemplate, mock(TransactionTemplate.class)); + + service.snapshot(null, null, null, null, 1, 20, null); + + String suspectQuery = jdbcTemplate.queries.stream() + .filter(sql -> sql.contains("coalesce(person_name")) + .findFirst() + .orElseThrow(); + assertTrue(suspectQuery.contains("instr(coalesce(person_name, ''), '?') > 0")); + assertFalse(suspectQuery.contains("like '%?%'")); + } + @Test public void dryRunNeverTouchesSnapshotSchemaWhenExternalFetchFails() { JdbcTemplate jdbcTemplate = mock(JdbcTemplate.class); @@ -110,4 +127,31 @@ public class AihrOrgSyncServiceTest { server.stop(0); } } + + private static final class SnapshotJdbcTemplate extends JdbcTemplate { + private final List queries = new ArrayList<>(); + + @Override + public T queryForObject(String sql, Class requiredType) { + queries.add(sql); + return requiredType.cast(1); + } + + @Override + public T queryForObject(String sql, Class requiredType, Object... args) { + queries.add(sql); + return requiredType.cast(0); + } + + @Override + public List query(String sql, RowMapper rowMapper, Object... args) { + queries.add(sql); + return List.of(); + } + + @Override + public void execute(String sql) { + // Schema setup is irrelevant for this query contract test. + } + } } diff --git a/docs/BRD_IMPLEMENTATION_AUDIT.md b/docs/BRD_IMPLEMENTATION_AUDIT.md index 7de0e62b..f0abd8d9 100644 --- a/docs/BRD_IMPLEMENTATION_AUDIT.md +++ b/docs/BRD_IMPLEMENTATION_AUDIT.md @@ -80,6 +80,12 @@ 4. 在业务提供一期视频样片及字段口径后,继续扩案例详情数据契约;当前原始音频回放已完成,但不替代视频、学习点和主管点评。 5. 用正式试点窗口跑 BRD L1/M5,而不是继续增加视觉页面。 +## 2026-07-14 增量:组织快照异常统计修复 + +- 发现 `AihrOrgSyncService.snapshot()` 的疑似乱码统计把问号写成 `like '%?%'`,问号位于 SQL 字符串字面量内,未形成有效的参数或字符匹配,导致管理端组织快照的异常计数可能恒为 0。 +- 已改为 MySQL `instr(coalesce(field, ''), '?') > 0`,继续沿用当前租户和筛选条件,并补回归测试与 `demo-check` marker;本地 `ry-vue` 直接查询得到 `3473` 条快照、`8` 条疑似异常文本,与修复后的统计口径一致。 +- 该修复只恢复异常计数的真实性,不代表外部组织快照已经具备完整手机号映射;正式主管身份和项目范围仍需真实组织同步数据。 + ## 5. 每批完成定义 - 有明确 BRD 条款、代码路径和数据证据。 diff --git a/scripts/demo-check.sh b/scripts/demo-check.sh index a1f24b90..b7e784d1 100755 --- a/scripts/demo-check.sh +++ b/scripts/demo-check.sh @@ -455,6 +455,7 @@ contains mobile-uni/src/pages/supervisor/review/index.vue "人工贡献分" contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrMobileController.java "/practice/reviews/{id}/reviewed" contains mobile-uni/src/pages/supervisor/review/index.vue "标记已复盘" contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java "source = 'retry'" +contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrOrgSyncService.java "instr(coalesce(person_name, '')" contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java "maskSensitiveText" contains mobile-uni/src/services/practice.ts "/api/aihr/mobile/practice/assignments" contains frontend/src/api/aihr/case.ts "/api/knowledge/case/upload"