fix(aihr): close pre-merge auth and pilot metric gaps
Allow sys_user admin console to use unscoped mobile review APIs, restrict org snapshot/sync to superadmin/hr_operator, count completed pilot people with the 10-session rule, and let employees self-read assignments without org team membership.
This commit is contained in:
+8
@@ -128,7 +128,15 @@ public class AihrMobileController {
|
||||
return ownMobileExtPartyId(requested);
|
||||
}
|
||||
|
||||
/**
|
||||
* APP supervisor tokens are org-scoped; management console sys_user keeps unscoped access
|
||||
* so admin review/growth pages continue to work against the same mobile APIs.
|
||||
*/
|
||||
private String supervisorScopeExtPartyId() {
|
||||
LoginUser loginUser = LoginHelper.getLoginUser();
|
||||
if (loginUser != null && UserType.SYS_USER.getUserType().equals(loginUser.getUserType())) {
|
||||
return "";
|
||||
}
|
||||
return mobileSeedService.requireSupervisorIdentity(currentAppUsername());
|
||||
}
|
||||
|
||||
|
||||
+7
@@ -1,10 +1,13 @@
|
||||
package org.dromara.aihr.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckRole;
|
||||
import cn.dev33.satoken.annotation.SaMode;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.aihr.domain.AihrOrgSyncDto.OrgSnapshotResponse;
|
||||
import org.dromara.aihr.domain.AihrOrgSyncDto.SyncRequest;
|
||||
import org.dromara.aihr.domain.AihrOrgSyncDto.SyncResponse;
|
||||
import org.dromara.aihr.service.AihrOrgSyncService;
|
||||
import org.dromara.common.core.constant.TenantConstants;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
@@ -18,13 +21,17 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
@RequestMapping("/api/aihr/org")
|
||||
public class AihrOrgSyncController {
|
||||
|
||||
private static final String HR_OPERATOR_ROLE = "hr_operator";
|
||||
|
||||
private final AihrOrgSyncService orgSyncService;
|
||||
|
||||
@SaCheckRole(value = {TenantConstants.SUPER_ADMIN_ROLE_KEY, HR_OPERATOR_ROLE}, mode = SaMode.OR)
|
||||
@PostMapping("/sync")
|
||||
public R<SyncResponse> sync(@RequestBody(required = false) SyncRequest request) {
|
||||
return R.ok(orgSyncService.sync(request));
|
||||
}
|
||||
|
||||
@SaCheckRole(value = {TenantConstants.SUPER_ADMIN_ROLE_KEY, HR_OPERATOR_ROLE}, mode = SaMode.OR)
|
||||
@GetMapping("/snapshot")
|
||||
public R<OrgSnapshotResponse> snapshot(@RequestParam(required = false) String keyword,
|
||||
@RequestParam(required = false) String projectCode,
|
||||
|
||||
+8
-1
@@ -952,9 +952,11 @@ public class AihrPracticeSeedService {
|
||||
return count("""
|
||||
SELECT COUNT(*)
|
||||
FROM (
|
||||
SELECT DISTINCT ext_party_id
|
||||
SELECT ext_party_id
|
||||
FROM aihr_practice_session
|
||||
WHERE tenant_id = ? AND mode = 'mobile'
|
||||
GROUP BY ext_party_id
|
||||
HAVING COUNT(*) >= 10
|
||||
) completed_people
|
||||
""", TENANT_ID);
|
||||
}
|
||||
@@ -1338,7 +1340,12 @@ public class AihrPracticeSeedService {
|
||||
|
||||
public List<PracticeAssignmentResponse> assignments(String extPartyId, String supervisorExtPartyId, int limit) {
|
||||
String party = isBlank(extPartyId) ? "" : extPartyId.trim();
|
||||
String supervisor = isBlank(supervisorExtPartyId) ? "" : supervisorExtPartyId.trim();
|
||||
if (!isBlank(party)) {
|
||||
// Self-read must not depend on org team membership (SMS users may lack org snapshot rows).
|
||||
if (!supervisor.isEmpty() && supervisor.equals(party)) {
|
||||
return assignments(party, limit);
|
||||
}
|
||||
return inTeamScope(supervisorExtPartyId, party) ? assignments(party, limit) : List.of();
|
||||
}
|
||||
TeamScope scope = teamScope(supervisorExtPartyId);
|
||||
|
||||
+46
-1
@@ -354,11 +354,33 @@ public class AihrPracticeSeedServiceTest {
|
||||
assertTrue(controllerSource.contains("mobileSeedService.createPracticeAssignment(request, supervisorScopeExtPartyId())"));
|
||||
assertTrue(controllerSource.contains("private String supervisorScopeExtPartyId()"));
|
||||
assertTrue(controllerSource.contains("mobileSeedService.requireSupervisorIdentity(currentAppUsername())"));
|
||||
assertTrue(controllerSource.contains("UserType.SYS_USER.getUserType().equals(loginUser.getUserType())"));
|
||||
assertFalse(controllerSource.contains("private static String supervisorScopeExtPartyId()"));
|
||||
assertFalse(controllerSource.contains("supervisorScopeExtPartyId() {\n return \"\";"));
|
||||
assertFalse(controllerSource.contains("supervisor H5 keeps team-scope"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void assignmentsSelfReadBypassesTeamScope() {
|
||||
AssignmentJdbcTemplate jdbcTemplate = new AssignmentJdbcTemplate(null, List.of());
|
||||
AihrPracticeSeedService service = new AihrPracticeSeedService(new ObjectMapper(), jdbcTemplate, null, null);
|
||||
|
||||
service.assignments("13900009999", "13900009999", 10);
|
||||
|
||||
assertTrue(jdbcTemplate.assignmentSql.contains("SELECT id, ext_party_id"));
|
||||
assertTrue(jdbcTemplate.assignmentSql.contains("ext_party_id = ?"));
|
||||
assertFalse(jdbcTemplate.assignmentSql.contains("ext_party_id IN"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void completedPilotPeopleCountRequiresTenSessions() {
|
||||
CountingJdbcTemplate jdbcTemplate = new CountingJdbcTemplate();
|
||||
AihrPracticeSeedService service = new AihrPracticeSeedService(new ObjectMapper(), jdbcTemplate, null, null);
|
||||
|
||||
assertEquals(2, service.mobileCompletedPeopleCount());
|
||||
assertTrue(jdbcTemplate.lastCountSql.contains("HAVING COUNT(*) >= 10"));
|
||||
assertTrue(jdbcTemplate.lastCountSql.contains("GROUP BY ext_party_id"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void practiceTeamSnapshotUsesActivityMembersWhenUnscopedWithOrgSnapshot() throws Exception {
|
||||
TeamSnapshotJdbcTemplate jdbcTemplate = new TeamSnapshotJdbcTemplate(true);
|
||||
@@ -971,4 +993,27 @@ public class AihrPracticeSeedServiceTest {
|
||||
}
|
||||
}
|
||||
|
||||
private static final class CountingJdbcTemplate extends JdbcTemplate {
|
||||
private String lastCountSql = "";
|
||||
|
||||
@Override
|
||||
public void execute(String sql) {
|
||||
// Schema setup is irrelevant for count query tests.
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T queryForObject(String sql, Class<T> requiredType) {
|
||||
return requiredType.cast(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T queryForObject(String sql, Class<T> requiredType, Object... args) {
|
||||
if (requiredType == Integer.class) {
|
||||
lastCountSql = sql;
|
||||
return requiredType.cast(2);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user