feat(aihr): filter supervisor reviews by org scope
This commit is contained in:
+4
-2
@@ -53,8 +53,10 @@ public class AihrMobileController {
|
||||
}
|
||||
|
||||
@GetMapping("/practice/reviews")
|
||||
public R<List<RecordResponse>> practiceReviews() {
|
||||
return R.ok(mobileSeedService.practiceReviews(currentAppUsername()));
|
||||
public R<List<RecordResponse>> practiceReviews(@RequestParam(required = false) String projectCode,
|
||||
@RequestParam(required = false) String extPartyId,
|
||||
@RequestParam(required = false) Integer limit) {
|
||||
return R.ok(mobileSeedService.practiceReviews(currentAppUsername(), projectCode, extPartyId, limit));
|
||||
}
|
||||
|
||||
@GetMapping("/practice/reviews/{id}")
|
||||
|
||||
+4
@@ -57,6 +57,10 @@ public class AihrMobileSeedService {
|
||||
return practiceSeedService.pendingReviewRecords(supervisorExtPartyId, 5);
|
||||
}
|
||||
|
||||
public List<RecordResponse> practiceReviews(String supervisorExtPartyId, String projectCode, String extPartyId, Integer limit) {
|
||||
return practiceSeedService.pendingReviewRecords(supervisorExtPartyId, projectCode, extPartyId, limit == null ? 20 : limit);
|
||||
}
|
||||
|
||||
public ReviewDetailResponse practiceReview(Long id) {
|
||||
return practiceSeedService.reviewDetail(id);
|
||||
}
|
||||
|
||||
+34
-16
@@ -478,37 +478,55 @@ public class AihrPracticeSeedService {
|
||||
}
|
||||
|
||||
public List<RecordResponse> pendingReviewRecords(int limit) {
|
||||
ensurePracticeTable();
|
||||
return jdbcTemplate.query("""
|
||||
SELECT id, session_id, finished_time, trainee_name, scenario_name, total_score, status, summary, ext_party_id
|
||||
FROM aihr_practice_session
|
||||
WHERE tenant_id = ? AND mode = 'mobile' AND status = '待复盘'
|
||||
ORDER BY CASE WHEN total_score < 80 THEN 0 ELSE 1 END, total_score ASC, finished_time DESC, id DESC
|
||||
LIMIT ?
|
||||
""", this::mapRecord, TENANT_ID, normalizeLimit(limit));
|
||||
return pendingReviewRecords("", "", "", limit);
|
||||
}
|
||||
|
||||
public List<RecordResponse> pendingReviewRecords(String supervisorExtPartyId, int limit) {
|
||||
return pendingReviewRecords(supervisorExtPartyId, "", "", limit);
|
||||
}
|
||||
|
||||
public List<RecordResponse> pendingReviewRecords(String supervisorExtPartyId, String projectCode, String extPartyId, int limit) {
|
||||
TeamScope scope = teamScope(supervisorExtPartyId);
|
||||
if (!scope.scoped()) {
|
||||
return pendingReviewRecords(limit);
|
||||
if (scope.scoped() && scope.extPartyIds().isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
if (scope.extPartyIds().isEmpty()) {
|
||||
String project = isBlank(projectCode) ? "" : projectCode.trim();
|
||||
if (!project.isEmpty() && !orgSnapshotTableExists()) {
|
||||
return List.of();
|
||||
}
|
||||
ensurePracticeTable();
|
||||
List<Object> args = new ArrayList<>();
|
||||
args.add(TENANT_ID);
|
||||
args.addAll(scope.extPartyIds());
|
||||
StringBuilder where = new StringBuilder("WHERE tenant_id = ? AND mode = 'mobile' AND status = '待复盘'");
|
||||
String party = isBlank(extPartyId) ? "" : extPartyId.trim();
|
||||
if (!party.isEmpty()) {
|
||||
where.append(" AND ext_party_id = ?");
|
||||
args.add(party);
|
||||
}
|
||||
if (!project.isEmpty()) {
|
||||
where.append("""
|
||||
AND ext_party_id IN (
|
||||
SELECT ext_party_id
|
||||
FROM aihr_org_snapshot
|
||||
WHERE tenant_id = ? AND project_code = ? AND employment_status = 'active'
|
||||
)
|
||||
""");
|
||||
args.add(TENANT_ID);
|
||||
args.add(project);
|
||||
}
|
||||
if (scope.scoped()) {
|
||||
where.append(" AND ext_party_id IN (").append(inClause(scope.extPartyIds().size())).append(")");
|
||||
args.addAll(scope.extPartyIds());
|
||||
}
|
||||
args.add(normalizeLimit(limit));
|
||||
return jdbcTemplate.query("""
|
||||
String sql = """
|
||||
SELECT id, session_id, finished_time, trainee_name, scenario_name, total_score, status, summary, ext_party_id
|
||||
FROM aihr_practice_session
|
||||
WHERE tenant_id = ? AND mode = 'mobile' AND status = '待复盘'
|
||||
AND ext_party_id IN (%s)
|
||||
""" + where + """
|
||||
ORDER BY CASE WHEN total_score < 80 THEN 0 ELSE 1 END, total_score ASC, finished_time DESC, id DESC
|
||||
LIMIT ?
|
||||
""".formatted(inClause(scope.extPartyIds().size())), this::mapRecord, args.toArray());
|
||||
""";
|
||||
return jdbcTemplate.query(sql, this::mapRecord, args.toArray());
|
||||
}
|
||||
|
||||
public String exportPilotCsv() {
|
||||
|
||||
+97
-5
@@ -3,6 +3,7 @@ package org.dromara.aihr.service;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.dromara.aihr.domain.AihrPracticeDto.PracticeAssignmentRequest;
|
||||
import org.dromara.common.core.exception.ServiceException;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentMatchers;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
@@ -11,17 +12,20 @@ import org.springframework.jdbc.core.RowMapper;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.contains;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@Tag("dev")
|
||||
public class AihrPracticeSeedServiceTest {
|
||||
|
||||
@Test
|
||||
@@ -64,13 +68,101 @@ public class AihrPracticeSeedServiceTest {
|
||||
assertEquals("只能派发给当前项目范围内员工", error.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pendingReviewRecordsAppliesProjectAndAssigneeFilters() {
|
||||
PracticeReviewJdbcTemplate jdbcTemplate = new PracticeReviewJdbcTemplate("主管", List.of("SUP-1", "EMP-1"));
|
||||
AihrPracticeSeedService service = new AihrPracticeSeedService(new ObjectMapper(), jdbcTemplate, null, null);
|
||||
|
||||
service.pendingReviewRecords("SUP-1", "P1", "EMP-1", 50);
|
||||
|
||||
assertTrue(jdbcTemplate.reviewSql.contains("status = '待复盘'"));
|
||||
assertTrue(jdbcTemplate.reviewSql.contains("ext_party_id = ?"));
|
||||
assertTrue(jdbcTemplate.reviewSql.contains("project_code = ?"));
|
||||
assertTrue(jdbcTemplate.reviewSql.contains("ext_party_id IN (?,?)"));
|
||||
assertEquals(List.of("000000", "EMP-1", "000000", "P1", "SUP-1", "EMP-1", 20), jdbcTemplate.reviewArgs);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pendingReviewRecordsLimitsNonSupervisorToSelf() {
|
||||
PracticeReviewJdbcTemplate jdbcTemplate = new PracticeReviewJdbcTemplate("员工", List.of("SUP-1", "EMP-1"));
|
||||
AihrPracticeSeedService service = new AihrPracticeSeedService(new ObjectMapper(), jdbcTemplate, null, null);
|
||||
|
||||
service.pendingReviewRecords("SUP-1", "", "", 0);
|
||||
|
||||
assertTrue(jdbcTemplate.reviewSql.contains("ext_party_id IN (?)"));
|
||||
assertEquals(List.of("000000", "SUP-1", 5), jdbcTemplate.reviewArgs);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
private static <T> List<T> mapOne(InvocationOnMock invocation, Map<String, String> values) throws SQLException {
|
||||
RowMapper mapper = invocation.getArgument(1);
|
||||
ResultSet resultSet = mock(ResultSet.class);
|
||||
for (Map.Entry<String, String> entry : values.entrySet()) {
|
||||
when(resultSet.getString(entry.getKey())).thenReturn(entry.getValue());
|
||||
}
|
||||
return List.of((T) mapper.mapRow(resultSet, 0));
|
||||
return mapRows(mapper, List.of(values));
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
private static <T> List<T> mapRows(RowMapper mapper, List<Map<String, String>> rows) throws SQLException {
|
||||
List<T> mapped = new ArrayList<>();
|
||||
int index = 0;
|
||||
for (Map<String, String> values : rows) {
|
||||
ResultSet resultSet = mock(ResultSet.class);
|
||||
for (Map.Entry<String, String> entry : values.entrySet()) {
|
||||
when(resultSet.getString(entry.getKey())).thenReturn(entry.getValue());
|
||||
}
|
||||
mapped.add((T) mapper.mapRow(resultSet, index++));
|
||||
}
|
||||
return mapped;
|
||||
}
|
||||
|
||||
private static final class PracticeReviewJdbcTemplate extends JdbcTemplate {
|
||||
private final String ownerLevel;
|
||||
private final List<String> scopeIds;
|
||||
private String reviewSql = "";
|
||||
private List<Object> reviewArgs = List.of();
|
||||
|
||||
private PracticeReviewJdbcTemplate(String ownerLevel, List<String> scopeIds) {
|
||||
this.ownerLevel = ownerLevel;
|
||||
this.scopeIds = scopeIds;
|
||||
}
|
||||
|
||||
@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) {
|
||||
return requiredType.cast(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(String sql) {
|
||||
// Table creation is irrelevant for query assembly tests.
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> List<T> query(String sql, RowMapper<T> rowMapper, Object... args) {
|
||||
try {
|
||||
if (sql.contains("FROM aihr_practice_session")) {
|
||||
reviewSql = sql;
|
||||
reviewArgs = List.of(args);
|
||||
return List.of();
|
||||
}
|
||||
if (sql.contains("SELECT project_code") && sql.contains("LIMIT 1")) {
|
||||
return mapRows(rowMapper, List.of(Map.of(
|
||||
"project_code", "P1",
|
||||
"position_level", ownerLevel
|
||||
)));
|
||||
}
|
||||
if (sql.contains("SELECT ext_party_id") && sql.contains("ORDER BY position_level")) {
|
||||
return mapRows(rowMapper, scopeIds.stream()
|
||||
.map(id -> Map.of("ext_party_id", id))
|
||||
.toList());
|
||||
}
|
||||
return List.of();
|
||||
} catch (SQLException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user