fix(aihr): scope mobile home metrics by identity
This commit is contained in:
+21
-1
@@ -63,7 +63,17 @@ public class AihrMobileController {
|
||||
@SaIgnore
|
||||
@GetMapping("/home/{role}")
|
||||
public R<HomeResponse> home(@PathVariable String role) {
|
||||
return R.ok(mobileSeedService.home(role));
|
||||
if (!LoginHelper.isLogin()) {
|
||||
return R.ok(mobileSeedService.publicHome(role));
|
||||
}
|
||||
LoginUser loginUser = LoginHelper.getLoginUser();
|
||||
String identity = currentAppUsername();
|
||||
if (loginUser != null
|
||||
&& UserType.APP_USER.getUserType().equals(loginUser.getUserType())
|
||||
&& supervisorRoleRequested(role)) {
|
||||
mobileSeedService.requireSupervisorIdentity(identity);
|
||||
}
|
||||
return R.ok(mobileSeedService.home(role, identity));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -226,6 +236,16 @@ public class AihrMobileController {
|
||||
return username == null ? "" : username.trim();
|
||||
}
|
||||
|
||||
private static boolean supervisorRoleRequested(String role) {
|
||||
if (role == null) {
|
||||
return false;
|
||||
}
|
||||
return switch (role.toLowerCase()) {
|
||||
case "supervisor", "manager", "admin" -> true;
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
|
||||
private static Map<String, Object> reviewDetailPayload(ReviewDetailResponse detail) {
|
||||
Map<String, Object> data = new LinkedHashMap<>();
|
||||
data.put("id", detail.id());
|
||||
|
||||
+60
-15
@@ -41,13 +41,43 @@ public class AihrMobileSeedService {
|
||||
}
|
||||
|
||||
public HomeResponse home(String role) {
|
||||
return home(role, "");
|
||||
}
|
||||
|
||||
public HomeResponse home(String role, String mobileIdentity) {
|
||||
return switch (normalize(role)) {
|
||||
case "candidate" -> candidateHome();
|
||||
case "supervisor" -> supervisorHome();
|
||||
default -> userHome();
|
||||
case "supervisor" -> supervisorHomeForIdentity(mobileIdentity);
|
||||
default -> userHome(mobileIdentity);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a public first-screen seed without reading tenant business metrics.
|
||||
* The authenticated home path remains available through {@link #home(String)}.
|
||||
*/
|
||||
public HomeResponse publicHome(String role) {
|
||||
return switch (normalize(role)) {
|
||||
case "candidate" -> candidateHome();
|
||||
case "supervisor" -> supervisorHome(false, "");
|
||||
default -> workerHome("user", "员工端", "avatar-woman", "早上好,王敏", "星河湾一期 · 客服管家", false, "");
|
||||
};
|
||||
}
|
||||
|
||||
private HomeResponse supervisorHomeForIdentity(String supervisorIdentity) {
|
||||
if (supervisorIdentity == null || supervisorIdentity.isBlank()) {
|
||||
return supervisorHome(true, new AihrPracticeSeedService.MobileTeamMetrics(
|
||||
practiceSeedService.mobileCompletedPeopleCount(),
|
||||
practiceSeedService.mobilePilotPeopleCount(),
|
||||
practiceSeedService.mobilePendingReviewCount(),
|
||||
practiceSeedService.mobilePendingAssignmentCount(),
|
||||
practiceSeedService.mobileLowScoreCount(),
|
||||
practiceSeedService.mobileAverageScore()
|
||||
));
|
||||
}
|
||||
return supervisorHome(true, practiceSeedService.mobileTeamMetrics(supervisorIdentity));
|
||||
}
|
||||
|
||||
public List<RecordResponse> practiceHistory(String extPartyId) {
|
||||
return practiceSeedService.mobileHistory(extPartyId, 5);
|
||||
}
|
||||
@@ -159,8 +189,8 @@ public class AihrMobileSeedService {
|
||||
};
|
||||
}
|
||||
|
||||
private HomeResponse userHome() {
|
||||
return workerHome("user", "员工端", "avatar-woman", "早上好,王敏", "星河湾一期 · 客服管家");
|
||||
private HomeResponse userHome(String mobileIdentity) {
|
||||
return workerHome("user", "员工端", "avatar-woman", "早上好,王敏", "星河湾一期 · 客服管家", true, mobileIdentity);
|
||||
}
|
||||
|
||||
private static HomeResponse candidateHome() {
|
||||
@@ -208,10 +238,18 @@ public class AihrMobileSeedService {
|
||||
);
|
||||
}
|
||||
|
||||
private HomeResponse workerHome(String role, String roleLabel, String avatarTone, String greeting, String location) {
|
||||
int completed = practiceSeedService.mobileCompletedCount();
|
||||
int pendingReview = practiceSeedService.mobilePendingReviewCount();
|
||||
int averageScore = practiceSeedService.mobileAverageScore();
|
||||
private HomeResponse workerHome(String role, String roleLabel, String avatarTone, String greeting, String location,
|
||||
boolean includeLiveMetrics, String mobileIdentity) {
|
||||
boolean scoped = mobileIdentity != null && !mobileIdentity.isBlank();
|
||||
int completed = includeLiveMetrics
|
||||
? scoped ? practiceSeedService.mobileCompletedCount(mobileIdentity) : practiceSeedService.mobileCompletedCount()
|
||||
: 0;
|
||||
int pendingReview = includeLiveMetrics
|
||||
? scoped ? practiceSeedService.mobilePendingReviewCount(mobileIdentity) : practiceSeedService.mobilePendingReviewCount()
|
||||
: 0;
|
||||
int averageScore = includeLiveMetrics
|
||||
? scoped ? practiceSeedService.mobileAverageScore(mobileIdentity) : practiceSeedService.mobileAverageScore()
|
||||
: 0;
|
||||
return new HomeResponse(
|
||||
role,
|
||||
roleLabel,
|
||||
@@ -256,14 +294,21 @@ public class AihrMobileSeedService {
|
||||
);
|
||||
}
|
||||
|
||||
private HomeResponse supervisorHome() {
|
||||
int completed = practiceSeedService.mobileCompletedPeopleCount();
|
||||
int total = practiceSeedService.mobilePilotPeopleCount();
|
||||
private HomeResponse supervisorHome(boolean includeLiveMetrics, String supervisorIdentity) {
|
||||
AihrPracticeSeedService.MobileTeamMetrics metrics = includeLiveMetrics
|
||||
? practiceSeedService.mobileTeamMetrics(supervisorIdentity)
|
||||
: AihrPracticeSeedService.MobileTeamMetrics.zero();
|
||||
return supervisorHome(includeLiveMetrics, metrics);
|
||||
}
|
||||
|
||||
private HomeResponse supervisorHome(boolean includeLiveMetrics, AihrPracticeSeedService.MobileTeamMetrics metrics) {
|
||||
int completed = metrics.completedPeople();
|
||||
int total = metrics.pilotPeople();
|
||||
int pending = Math.max(0, total - completed);
|
||||
int pendingReview = practiceSeedService.mobilePendingReviewCount();
|
||||
int pendingAssignment = practiceSeedService.mobilePendingAssignmentCount();
|
||||
int lowScore = practiceSeedService.mobileLowScoreCount();
|
||||
int averageScore = practiceSeedService.mobileAverageScore();
|
||||
int pendingReview = metrics.pendingReview();
|
||||
int pendingAssignment = metrics.pendingAssignment();
|
||||
int lowScore = metrics.lowScore();
|
||||
int averageScore = metrics.averageScore();
|
||||
int completionRate = total == 0 ? 0 : Math.round(completed * 100F / total);
|
||||
return new HomeResponse(
|
||||
"supervisor",
|
||||
|
||||
+133
@@ -694,10 +694,18 @@ public class AihrPracticeSeedService {
|
||||
return countMobileRecords("");
|
||||
}
|
||||
|
||||
public int mobileCompletedCount(String extPartyId) {
|
||||
return countMobileRecords(extPartyId);
|
||||
}
|
||||
|
||||
public int mobilePendingReviewCount() {
|
||||
return countPendingReview("");
|
||||
}
|
||||
|
||||
public int mobilePendingReviewCount(String extPartyId) {
|
||||
return countPendingReview(extPartyId);
|
||||
}
|
||||
|
||||
public int mobileCompletedPeopleCount() {
|
||||
return countCompletedPilotPeople();
|
||||
}
|
||||
@@ -735,6 +743,59 @@ public class AihrPracticeSeedService {
|
||||
return value == null ? 0 : Math.round(value.floatValue());
|
||||
}
|
||||
|
||||
public int mobileAverageScore(String extPartyId) {
|
||||
if (isBlank(extPartyId)) {
|
||||
return mobileAverageScore();
|
||||
}
|
||||
ensurePracticeTable();
|
||||
Double value = jdbcTemplate.queryForObject("""
|
||||
SELECT COALESCE(AVG(total_score), 0)
|
||||
FROM aihr_practice_session
|
||||
WHERE tenant_id = ? AND mode = 'mobile' AND ext_party_id = ?
|
||||
""", Double.class, tenantId(), extPartyId.trim());
|
||||
return value == null ? 0 : Math.round(value.floatValue());
|
||||
}
|
||||
|
||||
public MobileTeamMetrics mobileTeamMetrics(String supervisorExtPartyId) {
|
||||
TeamScope scope = teamScope(supervisorExtPartyId);
|
||||
if (!scope.scoped()) {
|
||||
return new MobileTeamMetrics(
|
||||
mobileCompletedPeopleCount(),
|
||||
mobilePilotPeopleCount(),
|
||||
mobilePendingReviewCount(),
|
||||
mobilePendingAssignmentCount(),
|
||||
mobileLowScoreCount(),
|
||||
mobileAverageScore()
|
||||
);
|
||||
}
|
||||
if (scope.extPartyIds().isEmpty()) {
|
||||
return MobileTeamMetrics.zero();
|
||||
}
|
||||
ensurePracticeTable();
|
||||
ensureAssignmentTable();
|
||||
return new MobileTeamMetrics(
|
||||
countCompletedPilotPeople(scope),
|
||||
countPilotPeople(scope),
|
||||
countPendingReview(scope),
|
||||
countPendingAssignment(scope),
|
||||
countLowScore(scope),
|
||||
averageScore(scope)
|
||||
);
|
||||
}
|
||||
|
||||
public record MobileTeamMetrics(
|
||||
int completedPeople,
|
||||
int pilotPeople,
|
||||
int pendingReview,
|
||||
int pendingAssignment,
|
||||
int lowScore,
|
||||
int averageScore
|
||||
) {
|
||||
public static MobileTeamMetrics zero() {
|
||||
return new MobileTeamMetrics(0, 0, 0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public List<RecordResponse> mobileHistory(String extPartyId, int limit) {
|
||||
ensurePracticeTable();
|
||||
int safeLimit = normalizeLimit(limit);
|
||||
@@ -1197,6 +1258,23 @@ public class AihrPracticeSeedService {
|
||||
""", tenantId());
|
||||
}
|
||||
|
||||
private int countCompletedPilotPeople(TeamScope scope) {
|
||||
List<Object> args = new ArrayList<>();
|
||||
args.add(tenantId());
|
||||
args.addAll(scope.extPartyIds());
|
||||
return count("""
|
||||
SELECT COUNT(*)
|
||||
FROM (
|
||||
SELECT ext_party_id
|
||||
FROM aihr_practice_session
|
||||
WHERE tenant_id = ? AND mode = 'mobile'
|
||||
AND ext_party_id IN (%s)
|
||||
GROUP BY ext_party_id
|
||||
HAVING COUNT(*) >= 10
|
||||
) completed_people
|
||||
""".formatted(inClause(scope.extPartyIds().size())), args.toArray());
|
||||
}
|
||||
|
||||
private int countPilotPeople() {
|
||||
ensurePracticeTable();
|
||||
ensureAssignmentTable();
|
||||
@@ -1216,6 +1294,29 @@ public class AihrPracticeSeedService {
|
||||
""", tenantId(), tenantId());
|
||||
}
|
||||
|
||||
private int countPilotPeople(TeamScope scope) {
|
||||
List<Object> args = new ArrayList<>();
|
||||
args.add(tenantId());
|
||||
args.addAll(scope.extPartyIds());
|
||||
args.add(tenantId());
|
||||
args.addAll(scope.extPartyIds());
|
||||
return count("""
|
||||
SELECT COUNT(*)
|
||||
FROM (
|
||||
SELECT ext_party_id
|
||||
FROM aihr_practice_session
|
||||
WHERE tenant_id = ? AND mode = 'mobile'
|
||||
AND ext_party_id IN (%s)
|
||||
UNION
|
||||
SELECT ext_party_id
|
||||
FROM aihr_practice_assignment
|
||||
WHERE tenant_id = ? AND ext_party_id IN (%s)
|
||||
AND status = '待训练'
|
||||
AND (source <> 'daily' OR DATE(create_time) = CURRENT_DATE())
|
||||
) pilot_people
|
||||
""".formatted(inClause(scope.extPartyIds().size()), inClause(scope.extPartyIds().size())), args.toArray());
|
||||
}
|
||||
|
||||
public ReviewDetailResponse reviewDetail(Long id) {
|
||||
ensurePracticeTable();
|
||||
if (id == null) {
|
||||
@@ -2072,6 +2173,22 @@ public class AihrPracticeSeedService {
|
||||
""".formatted(inClause(scope.extPartyIds().size())), args.toArray());
|
||||
}
|
||||
|
||||
private int countPendingAssignment(TeamScope scope) {
|
||||
if (scope.extPartyIds().isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
List<Object> args = new ArrayList<>();
|
||||
args.add(tenantId());
|
||||
args.addAll(scope.extPartyIds());
|
||||
return count("""
|
||||
SELECT COUNT(*)
|
||||
FROM aihr_practice_assignment
|
||||
WHERE tenant_id = ? AND status = '待训练'
|
||||
AND (source <> 'daily' OR DATE(create_time) = CURRENT_DATE())
|
||||
AND ext_party_id IN (%s)
|
||||
""".formatted(inClause(scope.extPartyIds().size())), args.toArray());
|
||||
}
|
||||
|
||||
private int countLowScore(TeamScope scope) {
|
||||
if (scope.extPartyIds().isEmpty()) {
|
||||
return 0;
|
||||
@@ -2087,6 +2204,22 @@ public class AihrPracticeSeedService {
|
||||
""".formatted(inClause(scope.extPartyIds().size())), args.toArray());
|
||||
}
|
||||
|
||||
private int averageScore(TeamScope scope) {
|
||||
if (scope.extPartyIds().isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
List<Object> args = new ArrayList<>();
|
||||
args.add(tenantId());
|
||||
args.addAll(scope.extPartyIds());
|
||||
Double value = jdbcTemplate.queryForObject("""
|
||||
SELECT COALESCE(AVG(total_score), 0)
|
||||
FROM aihr_practice_session
|
||||
WHERE tenant_id = ? AND mode = 'mobile'
|
||||
AND ext_party_id IN (%s)
|
||||
""".formatted(inClause(scope.extPartyIds().size())), Double.class, args.toArray());
|
||||
return value == null ? 0 : Math.round(value.floatValue());
|
||||
}
|
||||
|
||||
private boolean inTeamScope(String supervisorExtPartyId, String extPartyId) {
|
||||
if (isBlank(extPartyId)) {
|
||||
return false;
|
||||
|
||||
+24
@@ -8,6 +8,7 @@ import java.util.stream.Collectors;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@Tag("dev")
|
||||
@@ -38,5 +39,28 @@ public class AihrMobileSeedServiceTest {
|
||||
assertEquals(3, supervisor.todos().get(0).count());
|
||||
assertEquals(2, supervisor.todos().get(1).count());
|
||||
assertEquals("1", supervisor.team().stats().get(2).value());
|
||||
|
||||
AihrPracticeSeedService publicPractice = mock(AihrPracticeSeedService.class);
|
||||
AihrMobileSeedService publicService = new AihrMobileSeedService(publicPractice);
|
||||
HomeResponse publicUser = publicService.publicHome("user");
|
||||
HomeResponse publicSupervisor = publicService.publicHome("supervisor");
|
||||
assertEquals("0", publicUser.metrics().get(0).value());
|
||||
assertEquals("0人已完成 · 0人待跟进", publicSupervisor.team().progressText());
|
||||
assertEquals(0, publicSupervisor.todos().get(0).count());
|
||||
verifyNoInteractions(publicPractice);
|
||||
|
||||
when(practice.mobileCompletedCount("employee-1")).thenReturn(2);
|
||||
when(practice.mobilePendingReviewCount("employee-1")).thenReturn(1);
|
||||
when(practice.mobileAverageScore("employee-1")).thenReturn(88);
|
||||
when(practice.mobileTeamMetrics("supervisor-1"))
|
||||
.thenReturn(new AihrPracticeSeedService.MobileTeamMetrics(3, 4, 2, 1, 1, 79));
|
||||
HomeResponse scopedUser = service.home("user", "employee-1");
|
||||
HomeResponse scopedSupervisor = service.home("supervisor", "supervisor-1");
|
||||
assertEquals("2", scopedUser.metrics().get(0).value());
|
||||
assertEquals("88", scopedUser.metrics().get(1).value());
|
||||
assertEquals("1", scopedUser.metrics().get(2).value());
|
||||
assertEquals("3人已完成 · 1人待跟进", scopedSupervisor.team().progressText());
|
||||
assertEquals(2, scopedSupervisor.todos().get(0).count());
|
||||
assertEquals(1, scopedSupervisor.todos().get(1).count());
|
||||
}
|
||||
}
|
||||
|
||||
+5
@@ -401,6 +401,11 @@ public class AihrPracticeSeedServiceTest {
|
||||
assertTrue(controllerSource.contains("mobileSeedService.requireSupervisorIdentity(currentAppUsername())"));
|
||||
assertTrue(controllerSource.contains("@SaCheckLogin\npublic class AihrMobileController"));
|
||||
assertTrue(controllerSource.contains("@SaIgnore\n @GetMapping(\"/home/{role}\")"));
|
||||
assertTrue(controllerSource.contains("if (!LoginHelper.isLogin())"));
|
||||
assertTrue(controllerSource.contains("return R.ok(mobileSeedService.publicHome(role))"));
|
||||
assertTrue(controllerSource.contains("return R.ok(mobileSeedService.home(role, identity))"));
|
||||
assertTrue(controllerSource.contains("mobileSeedService.requireSupervisorIdentity(identity)"));
|
||||
assertTrue(controllerSource.contains("private static boolean supervisorRoleRequested(String role)"));
|
||||
assertTrue(controllerSource.contains("UserType.SYS_USER.getUserType().equals(loginUser.getUserType())"));
|
||||
assertTrue(controllerSource.contains("StpUtil.hasRoleOr(TenantConstants.SUPER_ADMIN_ROLE_KEY, \"hr_operator\")"));
|
||||
assertFalse(controllerSource.contains("private static String supervisorScopeExtPartyId()"));
|
||||
|
||||
Reference in New Issue
Block a user