fix(aihr): scope formal sop topics by app projects
This commit is contained in:
+25
-1
@@ -25,7 +25,9 @@ import org.dromara.aihr.domain.AihrSopDto.UploadItemResponse;
|
||||
import org.dromara.aihr.domain.AihrSopDto.UploadResponse;
|
||||
import org.dromara.aihr.domain.AihrSopDto.VectorIndexStatusResponse;
|
||||
import org.dromara.aihr.domain.AihrSopDto.VectorizeResponse;
|
||||
import org.dromara.aihr.domain.AihrOrgSyncDto.OrgPersonRow;
|
||||
import org.dromara.aihr.service.AihrSopSeedService;
|
||||
import org.dromara.aihr.service.AihrOrgSyncService;
|
||||
import org.dromara.aihr.service.AihrUploadQueueService;
|
||||
import org.dromara.common.core.constant.TenantConstants;
|
||||
import org.dromara.common.core.domain.R;
|
||||
@@ -42,7 +44,9 @@ import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* SOP 知识库 API。
|
||||
@@ -56,6 +60,7 @@ public class AihrSopController {
|
||||
private static final String DEMO_VERIFY_SOURCE = "verify_demo_questions";
|
||||
|
||||
private final AihrSopSeedService sopSeedService;
|
||||
private final AihrOrgSyncService orgSyncService;
|
||||
private final AihrUploadQueueService uploadQueueService;
|
||||
|
||||
@SaCheckRole(value = {TenantConstants.SUPER_ADMIN_ROLE_KEY, HR_OPERATOR_ROLE}, mode = SaMode.OR)
|
||||
@@ -105,7 +110,26 @@ public class AihrSopController {
|
||||
@GetMapping("/position-sop")
|
||||
public R<PositionSopResponse> positionSop(@RequestParam(required = false) String position,
|
||||
@RequestParam(required = false) String projectType) {
|
||||
return R.ok(sopSeedService.positionSop(position, projectType));
|
||||
return R.ok(sopSeedService.positionSop(position, projectType, currentAppProjectCodes()));
|
||||
}
|
||||
|
||||
private Set<String> currentAppProjectCodes() {
|
||||
LoginUser loginUser = LoginHelper.getLoginUser();
|
||||
if (loginUser == null || !UserType.APP_USER.getUserType().equals(loginUser.getUserType())) {
|
||||
return null;
|
||||
}
|
||||
String identity = currentRequesterExtPartyId();
|
||||
if (identity.isBlank()) {
|
||||
return Set.of();
|
||||
}
|
||||
try {
|
||||
return orgSyncService.snapshot(identity, null, null, "active", 1, 500, 500).rows().stream()
|
||||
.map(OrgPersonRow::projectCode)
|
||||
.filter(code -> code != null && !code.isBlank())
|
||||
.collect(java.util.stream.Collectors.toCollection(LinkedHashSet::new));
|
||||
} catch (RuntimeException ex) {
|
||||
return Set.of();
|
||||
}
|
||||
}
|
||||
|
||||
@SaCheckLogin
|
||||
|
||||
+24
-9
@@ -168,13 +168,17 @@ public class AihrSopSeedService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 一期只提供岗位学习导航摘要,不把它冒充成正式的 SOP 适用范围或上岗资格判断。
|
||||
* 正式匹配需要 position_responsibility/sop_applicability 和组织项目数据落库后再开启。
|
||||
* 提供岗位学习导航摘要,不把它冒充成正式的上岗资格判断。
|
||||
* 有正式岗位/SOP映射时按项目范围读取;没有映射或旧库尚未迁移时回退一期 seed。
|
||||
*/
|
||||
public PositionSopResponse positionSop(String requestedPosition, String projectType) {
|
||||
return positionSop(requestedPosition, projectType, null);
|
||||
}
|
||||
|
||||
public PositionSopResponse positionSop(String requestedPosition, String projectType, Set<String> projectCodes) {
|
||||
String position = firstNonBlank(requestedPosition, "生活顾问");
|
||||
String effectiveProjectType = firstNonBlank(projectType, "住宅");
|
||||
List<PositionSopTopicResponse> formalTopics = formalPositionSopTopics(position, effectiveProjectType);
|
||||
List<PositionSopTopicResponse> formalTopics = formalPositionSopTopics(position, effectiveProjectType, projectCodes);
|
||||
if (!formalTopics.isEmpty()) {
|
||||
return new PositionSopResponse(
|
||||
position,
|
||||
@@ -211,12 +215,12 @@ public class AihrSopSeedService {
|
||||
);
|
||||
}
|
||||
|
||||
private List<PositionSopTopicResponse> formalPositionSopTopics(String position, String projectType) {
|
||||
private List<PositionSopTopicResponse> formalPositionSopTopics(String position, String projectType, Set<String> projectCodes) {
|
||||
if (jdbcTemplate == null) {
|
||||
return List.of();
|
||||
}
|
||||
try {
|
||||
return jdbcTemplate.query("""
|
||||
StringBuilder sql = new StringBuilder("""
|
||||
SELECT r.position_code, r.responsibility_text, r.sop_code, r.sop_id
|
||||
FROM aihr_position_responsibility r
|
||||
JOIN aihr_sop_applicability a
|
||||
@@ -232,9 +236,20 @@ public class AihrSopSeedService {
|
||||
AND (r.effective_thru IS NULL OR r.effective_thru >= CURRENT_DATE)
|
||||
AND (a.effective_from IS NULL OR a.effective_from <= CURRENT_DATE)
|
||||
AND (a.effective_thru IS NULL OR a.effective_thru >= CURRENT_DATE)
|
||||
ORDER BY r.id
|
||||
LIMIT 50
|
||||
""", (rs, rowNum) -> {
|
||||
""");
|
||||
List<Object> args = new ArrayList<>(List.of(tenantId(), position, projectType));
|
||||
if (projectCodes != null) {
|
||||
sql.append(" AND (a.org_scope IS NULL OR a.org_scope = ''");
|
||||
for (String projectCode : projectCodes) {
|
||||
if (projectCode != null && !projectCode.isBlank()) {
|
||||
sql.append(" OR FIND_IN_SET(?, REPLACE(a.org_scope, ' ', '')) > 0");
|
||||
args.add(projectCode.trim());
|
||||
}
|
||||
}
|
||||
sql.append(")\n");
|
||||
}
|
||||
sql.append(" ORDER BY r.id LIMIT 50");
|
||||
return jdbcTemplate.query(sql.toString(), (rs, rowNum) -> {
|
||||
String sopCode = rs.getString("sop_code");
|
||||
Object sopId = rs.getObject("sop_id");
|
||||
String sopReference = firstNonBlank(sopCode, sopId == null ? null : "sop#" + sopId);
|
||||
@@ -244,7 +259,7 @@ public class AihrSopSeedService {
|
||||
firstNonBlank(sopReference, "待绑定SOP"),
|
||||
"正式适用"
|
||||
);
|
||||
}, tenantId(), position, projectType);
|
||||
}, args.toArray());
|
||||
} catch (DataAccessException ex) {
|
||||
log.debug("formal position SOP mapping unavailable, use pilot seed navigation", ex);
|
||||
return List.of();
|
||||
|
||||
+16
-1
@@ -253,13 +253,28 @@ public class AihrSopSeedServiceTest {
|
||||
}
|
||||
String code = Files.readString(source);
|
||||
|
||||
assertTrue(code.contains("formalPositionSopTopics(position, effectiveProjectType)"));
|
||||
assertTrue(code.contains("formalPositionSopTopics(position, effectiveProjectType, projectCodes)"));
|
||||
assertTrue(code.contains("FROM aihr_position_responsibility r"));
|
||||
assertTrue(code.contains("JOIN aihr_sop_applicability a"));
|
||||
assertTrue(code.contains("FIND_IN_SET(?, REPLACE(a.org_scope, ' ', '')) > 0"));
|
||||
assertTrue(code.contains("\"formal-applicability\""));
|
||||
assertTrue(code.contains("\"NOT_CONFIGURED\""));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag("dev")
|
||||
public void positionSopBindsFormalMappingsToAuthenticatedAppProjects() throws Exception {
|
||||
Path source = Path.of("src/main/java/org/dromara/aihr/controller/AihrSopController.java");
|
||||
if (!Files.exists(source)) {
|
||||
source = Path.of("ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrSopController.java");
|
||||
}
|
||||
String code = Files.readString(source);
|
||||
|
||||
assertTrue(code.contains("sopSeedService.positionSop(position, projectType, currentAppProjectCodes())"));
|
||||
assertTrue(code.contains("orgSyncService.snapshot(identity, null, null, \"active\", 1, 500, 500)"));
|
||||
assertTrue(code.contains("OrgPersonRow::projectCode"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag("dev")
|
||||
public void answerFeedbackReviewUsesAuthenticatedOperatorForAuditTrail() throws Exception {
|
||||
|
||||
Reference in New Issue
Block a user