diff --git a/backend/src/main/java/com/xly/erp/module/usr/mapper/PermissionCategoryMapper.java b/backend/src/main/java/com/xly/erp/module/usr/mapper/PermissionCategoryMapper.java
new file mode 100644
index 0000000..171b13c
--- /dev/null
+++ b/backend/src/main/java/com/xly/erp/module/usr/mapper/PermissionCategoryMapper.java
@@ -0,0 +1,18 @@
+package com.xly.erp.module.usr.mapper;
+
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.List;
+
+@Mapper
+public interface PermissionCategoryMapper {
+
+ @Select("")
+ int countActiveByIds(@Param("ids") List ids);
+}
diff --git a/backend/src/main/java/com/xly/erp/module/usr/mapper/StaffMapper.java b/backend/src/main/java/com/xly/erp/module/usr/mapper/StaffMapper.java
new file mode 100644
index 0000000..e06946f
--- /dev/null
+++ b/backend/src/main/java/com/xly/erp/module/usr/mapper/StaffMapper.java
@@ -0,0 +1,16 @@
+package com.xly.erp.module.usr.mapper;
+
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+@Mapper
+public interface StaffMapper {
+
+ @Select("SELECT 1 FROM tStaff WHERE iIncrement = #{id} AND bDeleted = 0 LIMIT 1")
+ Integer findActiveStaffFlag(@Param("id") Integer id);
+
+ default boolean existsActiveById(Integer id) {
+ return findActiveStaffFlag(id) != null;
+ }
+}
diff --git a/backend/src/test/java/com/xly/erp/module/usr/mapper/PermissionCategoryMapperIT.java b/backend/src/test/java/com/xly/erp/module/usr/mapper/PermissionCategoryMapperIT.java
new file mode 100644
index 0000000..104d053
--- /dev/null
+++ b/backend/src/test/java/com/xly/erp/module/usr/mapper/PermissionCategoryMapperIT.java
@@ -0,0 +1,51 @@
+package com.xly.erp.module.usr.mapper;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.test.context.ActiveProfiles;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
+@ActiveProfiles("test")
+class PermissionCategoryMapperIT {
+
+ @Autowired
+ private PermissionCategoryMapper permissionCategoryMapper;
+
+ @Autowired
+ private JdbcTemplate jdbcTemplate;
+
+ @BeforeEach
+ @AfterEach
+ void cleanup() {
+ jdbcTemplate.update("DELETE FROM tPermissionCategory WHERE sCategoryCode LIKE 'sp_test_%'");
+ }
+
+ @Test
+ void countActiveByIds_returnsCorrectCount() {
+ Integer cat1 = insertCategory("sp_test_c1", "权限1", false);
+ Integer cat2 = insertCategory("sp_test_c2", "权限2", false);
+ Integer cat3 = insertCategory("sp_test_c3", "权限3", true);
+
+ assertThat(permissionCategoryMapper.countActiveByIds(List.of(cat1, cat2, cat3))).isEqualTo(2);
+ assertThat(permissionCategoryMapper.countActiveByIds(List.of(cat1, 99999991))).isEqualTo(1);
+ assertThat(permissionCategoryMapper.countActiveByIds(List.of(99999991))).isZero();
+ }
+
+ private Integer insertCategory(String code, String name, boolean deleted) {
+ jdbcTemplate.update(
+ "INSERT INTO tPermissionCategory (sBrandsId, sSubsidiaryId, tCreateDate, sCategoryCode, sCategoryName, "
+ + "iSortOrder, sCreatedBy, bDeleted) "
+ + "VALUES ('XLY','XLY', NOW(), ?, ?, 0, 'STUB_ADMIN', ?)",
+ code, name, deleted ? 1 : 0);
+ return jdbcTemplate.queryForObject(
+ "SELECT iIncrement FROM tPermissionCategory WHERE sCategoryCode = ?", Integer.class, code);
+ }
+}
diff --git a/backend/src/test/java/com/xly/erp/module/usr/mapper/StaffMapperIT.java b/backend/src/test/java/com/xly/erp/module/usr/mapper/StaffMapperIT.java
new file mode 100644
index 0000000..52c74c4
--- /dev/null
+++ b/backend/src/test/java/com/xly/erp/module/usr/mapper/StaffMapperIT.java
@@ -0,0 +1,47 @@
+package com.xly.erp.module.usr.mapper;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.test.context.ActiveProfiles;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
+@ActiveProfiles("test")
+class StaffMapperIT {
+
+ @Autowired
+ private StaffMapper staffMapper;
+
+ @Autowired
+ private JdbcTemplate jdbcTemplate;
+
+ @BeforeEach
+ @AfterEach
+ void cleanup() {
+ jdbcTemplate.update("DELETE FROM tStaff WHERE sStaffNo LIKE 'sp_test_%'");
+ }
+
+ @Test
+ void existsActiveById_handlesAliveDeletedMissing() {
+ Integer aliveId = insertStaff("sp_test_st_alive", "活的", false);
+ Integer deletedId = insertStaff("sp_test_st_dead", "死的", true);
+
+ assertThat(staffMapper.existsActiveById(aliveId)).isTrue();
+ assertThat(staffMapper.existsActiveById(deletedId)).isFalse();
+ assertThat(staffMapper.existsActiveById(99999990)).isFalse();
+ }
+
+ private Integer insertStaff(String staffNo, String staffName, boolean deleted) {
+ jdbcTemplate.update(
+ "INSERT INTO tStaff (sBrandsId, sSubsidiaryId, tCreateDate, sStaffNo, sStaffName, sCreatedBy, bDeleted) "
+ + "VALUES ('XLY','XLY', NOW(), ?, ?, 'STUB_ADMIN', ?)",
+ staffNo, staffName, deleted ? 1 : 0);
+ return jdbcTemplate.queryForObject(
+ "SELECT iIncrement FROM tStaff WHERE sStaffNo = ?", Integer.class, staffNo);
+ }
+}