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);
|
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() {
|
private String supervisorScopeExtPartyId() {
|
||||||
|
LoginUser loginUser = LoginHelper.getLoginUser();
|
||||||
|
if (loginUser != null && UserType.SYS_USER.getUserType().equals(loginUser.getUserType())) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
return mobileSeedService.requireSupervisorIdentity(currentAppUsername());
|
return mobileSeedService.requireSupervisorIdentity(currentAppUsername());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+7
@@ -1,10 +1,13 @@
|
|||||||
package org.dromara.aihr.controller;
|
package org.dromara.aihr.controller;
|
||||||
|
|
||||||
|
import cn.dev33.satoken.annotation.SaCheckRole;
|
||||||
|
import cn.dev33.satoken.annotation.SaMode;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.dromara.aihr.domain.AihrOrgSyncDto.OrgSnapshotResponse;
|
import org.dromara.aihr.domain.AihrOrgSyncDto.OrgSnapshotResponse;
|
||||||
import org.dromara.aihr.domain.AihrOrgSyncDto.SyncRequest;
|
import org.dromara.aihr.domain.AihrOrgSyncDto.SyncRequest;
|
||||||
import org.dromara.aihr.domain.AihrOrgSyncDto.SyncResponse;
|
import org.dromara.aihr.domain.AihrOrgSyncDto.SyncResponse;
|
||||||
import org.dromara.aihr.service.AihrOrgSyncService;
|
import org.dromara.aihr.service.AihrOrgSyncService;
|
||||||
|
import org.dromara.common.core.constant.TenantConstants;
|
||||||
import org.dromara.common.core.domain.R;
|
import org.dromara.common.core.domain.R;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
@@ -18,13 +21,17 @@ import org.springframework.web.bind.annotation.RestController;
|
|||||||
@RequestMapping("/api/aihr/org")
|
@RequestMapping("/api/aihr/org")
|
||||||
public class AihrOrgSyncController {
|
public class AihrOrgSyncController {
|
||||||
|
|
||||||
|
private static final String HR_OPERATOR_ROLE = "hr_operator";
|
||||||
|
|
||||||
private final AihrOrgSyncService orgSyncService;
|
private final AihrOrgSyncService orgSyncService;
|
||||||
|
|
||||||
|
@SaCheckRole(value = {TenantConstants.SUPER_ADMIN_ROLE_KEY, HR_OPERATOR_ROLE}, mode = SaMode.OR)
|
||||||
@PostMapping("/sync")
|
@PostMapping("/sync")
|
||||||
public R<SyncResponse> sync(@RequestBody(required = false) SyncRequest request) {
|
public R<SyncResponse> sync(@RequestBody(required = false) SyncRequest request) {
|
||||||
return R.ok(orgSyncService.sync(request));
|
return R.ok(orgSyncService.sync(request));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SaCheckRole(value = {TenantConstants.SUPER_ADMIN_ROLE_KEY, HR_OPERATOR_ROLE}, mode = SaMode.OR)
|
||||||
@GetMapping("/snapshot")
|
@GetMapping("/snapshot")
|
||||||
public R<OrgSnapshotResponse> snapshot(@RequestParam(required = false) String keyword,
|
public R<OrgSnapshotResponse> snapshot(@RequestParam(required = false) String keyword,
|
||||||
@RequestParam(required = false) String projectCode,
|
@RequestParam(required = false) String projectCode,
|
||||||
|
|||||||
+8
-1
@@ -952,9 +952,11 @@ public class AihrPracticeSeedService {
|
|||||||
return count("""
|
return count("""
|
||||||
SELECT COUNT(*)
|
SELECT COUNT(*)
|
||||||
FROM (
|
FROM (
|
||||||
SELECT DISTINCT ext_party_id
|
SELECT ext_party_id
|
||||||
FROM aihr_practice_session
|
FROM aihr_practice_session
|
||||||
WHERE tenant_id = ? AND mode = 'mobile'
|
WHERE tenant_id = ? AND mode = 'mobile'
|
||||||
|
GROUP BY ext_party_id
|
||||||
|
HAVING COUNT(*) >= 10
|
||||||
) completed_people
|
) completed_people
|
||||||
""", TENANT_ID);
|
""", TENANT_ID);
|
||||||
}
|
}
|
||||||
@@ -1338,7 +1340,12 @@ public class AihrPracticeSeedService {
|
|||||||
|
|
||||||
public List<PracticeAssignmentResponse> assignments(String extPartyId, String supervisorExtPartyId, int limit) {
|
public List<PracticeAssignmentResponse> assignments(String extPartyId, String supervisorExtPartyId, int limit) {
|
||||||
String party = isBlank(extPartyId) ? "" : extPartyId.trim();
|
String party = isBlank(extPartyId) ? "" : extPartyId.trim();
|
||||||
|
String supervisor = isBlank(supervisorExtPartyId) ? "" : supervisorExtPartyId.trim();
|
||||||
if (!isBlank(party)) {
|
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();
|
return inTeamScope(supervisorExtPartyId, party) ? assignments(party, limit) : List.of();
|
||||||
}
|
}
|
||||||
TeamScope scope = teamScope(supervisorExtPartyId);
|
TeamScope scope = teamScope(supervisorExtPartyId);
|
||||||
|
|||||||
+46
-1
@@ -354,11 +354,33 @@ public class AihrPracticeSeedServiceTest {
|
|||||||
assertTrue(controllerSource.contains("mobileSeedService.createPracticeAssignment(request, supervisorScopeExtPartyId())"));
|
assertTrue(controllerSource.contains("mobileSeedService.createPracticeAssignment(request, supervisorScopeExtPartyId())"));
|
||||||
assertTrue(controllerSource.contains("private String supervisorScopeExtPartyId()"));
|
assertTrue(controllerSource.contains("private String supervisorScopeExtPartyId()"));
|
||||||
assertTrue(controllerSource.contains("mobileSeedService.requireSupervisorIdentity(currentAppUsername())"));
|
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("private static String supervisorScopeExtPartyId()"));
|
||||||
assertFalse(controllerSource.contains("supervisorScopeExtPartyId() {\n return \"\";"));
|
|
||||||
assertFalse(controllerSource.contains("supervisor H5 keeps team-scope"));
|
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
|
@Test
|
||||||
public void practiceTeamSnapshotUsesActivityMembersWhenUnscopedWithOrgSnapshot() throws Exception {
|
public void practiceTeamSnapshotUsesActivityMembersWhenUnscopedWithOrgSnapshot() throws Exception {
|
||||||
TeamSnapshotJdbcTemplate jdbcTemplate = new TeamSnapshotJdbcTemplate(true);
|
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