feat(aihr): add employee mistake book audit

This commit is contained in:
2026-07-14 03:23:49 +08:00
parent 5c0f792f37
commit d98ff0ff8b
12 changed files with 317 additions and 10 deletions
@@ -13,6 +13,7 @@ import org.dromara.aihr.domain.AihrPracticeDto.PracticeAssignmentResponse;
import org.dromara.aihr.domain.AihrPracticeDto.PracticeTeamResponse;
import org.dromara.aihr.domain.AihrPracticeDto.PromotionEvidenceResponse;
import org.dromara.aihr.domain.AihrPracticeDto.RecordResponse;
import org.dromara.aihr.domain.AihrPracticeDto.MistakeResponse;
import org.dromara.aihr.domain.AihrPracticeDto.ReviewAdviceRequest;
import org.dromara.aihr.domain.AihrPracticeDto.ReviewDetailResponse;
import org.dromara.aihr.domain.vo.AihrMobileDto.HomeResponse;
@@ -91,6 +92,11 @@ public class AihrMobileController {
return detail == null ? R.fail("训练记录不存在") : R.ok(reviewDetailPayload(detail));
}
@GetMapping("/practice/mistakes")
public R<List<MistakeResponse>> practiceMistakes(@RequestParam(required = false) String extPartyId) {
return R.ok(mobileSeedService.practiceMistakes(ownMobileExtPartyId(extPartyId)));
}
@GetMapping("/practice/reviews")
public R<List<RecordResponse>> practiceReviews(@RequestParam(required = false) String projectCode,
@RequestParam(required = false) String extPartyId,
@@ -142,6 +142,20 @@ public final class AihrPracticeDto {
public record RecordResponse(Long id, String sessionId, String time, String trainee, String scene, Integer score, String status, String summary, String extPartyId) {
}
public record MistakeResponse(
Long id,
String sessionId,
String scenarioId,
String scenarioName,
Integer score,
String cause,
Long retryAssignmentId,
String retryStatus,
String time,
String summary
) {
}
public record DialogueResponse(String role, String label, String text, String audioUrl, Long audioOssId, Integer emotion, Integer trust, Boolean redFlag, String coachHint) {
public DialogueResponse(String role, String label, String text) {
this(role, label, text, "", null, null, null, null, "");
@@ -9,6 +9,7 @@ import org.dromara.aihr.domain.AihrPracticeDto.PracticeAssignmentResponse;
import org.dromara.aihr.domain.AihrPracticeDto.PracticeTeamResponse;
import org.dromara.aihr.domain.AihrPracticeDto.PromotionEvidenceResponse;
import org.dromara.aihr.domain.AihrPracticeDto.RecordResponse;
import org.dromara.aihr.domain.AihrPracticeDto.MistakeResponse;
import org.dromara.aihr.domain.AihrPracticeDto.ReviewAdviceRequest;
import org.dromara.aihr.domain.AihrPracticeDto.ReviewDetailResponse;
import org.dromara.aihr.domain.vo.AihrMobileDto.HomeResponse;
@@ -49,6 +50,10 @@ public class AihrMobileSeedService {
return practiceSeedService.mobileHistory(extPartyId, 5);
}
public List<MistakeResponse> practiceMistakes(String extPartyId) {
return practiceSeedService.mobileMistakes(extPartyId, 20);
}
public List<RecordResponse> practiceReviews() {
return practiceSeedService.pendingReviewRecords(5);
}
@@ -25,6 +25,7 @@ import org.dromara.aihr.domain.AihrPracticeDto.PromptTemplateRequest;
import org.dromara.aihr.domain.AihrPracticeDto.PromptTemplateResponse;
import org.dromara.aihr.domain.AihrPracticeDto.PromotionEvidenceResponse;
import org.dromara.aihr.domain.AihrPracticeDto.RecordResponse;
import org.dromara.aihr.domain.AihrPracticeDto.MistakeResponse;
import org.dromara.aihr.domain.AihrPracticeDto.ReviewAdviceRequest;
import org.dromara.aihr.domain.AihrPracticeDto.ReviewDetailResponse;
import org.dromara.aihr.domain.AihrPracticeDto.RubricDimensionResponse;
@@ -722,6 +723,65 @@ public class AihrPracticeSeedService {
""", this::mapRecord, TENANT_ID, extPartyId.trim(), safeLimit);
}
public List<MistakeResponse> mobileMistakes(String extPartyId, int limit) {
ensurePracticeTable();
ensureAssignmentTable();
if (isBlank(extPartyId)) {
return List.of();
}
int safeLimit = normalizeLimit(limit);
return jdbcTemplate.query("""
SELECT s.id,
s.session_id,
s.scenario_id,
s.scenario_name,
s.total_score,
CASE
WHEN s.dialogue_json LIKE '%"redFlag":true%' THEN '红线话术'
WHEN COALESCE(s.dim_emotion, 100) < 60 THEN '情绪承接'
WHEN COALESCE(s.dim_compliance, 100) < 60 THEN '流程合规'
ELSE '综合得分不足'
END AS cause,
(SELECT a.id
FROM aihr_practice_assignment a
WHERE a.tenant_id = s.tenant_id
AND a.ext_party_id = s.ext_party_id
AND a.scenario_id = s.scenario_id
AND a.source = 'retry'
ORDER BY a.id DESC
LIMIT 1) AS retry_assignment_id,
COALESCE((SELECT a.status
FROM aihr_practice_assignment a
WHERE a.tenant_id = s.tenant_id
AND a.ext_party_id = s.ext_party_id
AND a.scenario_id = s.scenario_id
AND a.source = 'retry'
ORDER BY a.id DESC
LIMIT 1), '未生成') AS retry_status,
s.finished_time,
s.summary
FROM aihr_practice_session s
WHERE s.tenant_id = ?
AND s.mode = 'mobile'
AND s.ext_party_id = ?
AND s.finished_time IS NOT NULL
AND (s.total_score < 80 OR s.dialogue_json LIKE '%"redFlag":true%')
ORDER BY s.finished_time DESC, s.id DESC
LIMIT ?
""", (rs, rowNum) -> new MistakeResponse(
rs.getLong("id"),
rs.getString("session_id"),
rs.getString("scenario_id"),
firstNonBlank(rs.getString("scenario_name"), "场景训练"),
rs.getInt("total_score"),
firstNonBlank(rs.getString("cause"), "综合得分不足"),
rs.getObject("retry_assignment_id") == null ? null : rs.getLong("retry_assignment_id"),
firstNonBlank(rs.getString("retry_status"), "未生成"),
rs.getTimestamp("finished_time") == null ? "" : rs.getTimestamp("finished_time").toLocalDateTime().format(TIME_FORMATTER),
firstNonBlank(rs.getString("summary"), "建议回看训练对话和导师改写")
), TENANT_ID, extPartyId.trim(), safeLimit);
}
public PracticeTeamResponse practiceTeamSnapshot(String supervisorExtPartyId, int limit) {
TeamScope scope = teamScope(supervisorExtPartyId);
int safeLimit = normalizeTeamLimit(limit);