Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
import kr.allcll.backend.domain.graduation.certification.GraduationCertRuleRepository;
import kr.allcll.backend.domain.graduation.certification.GraduationCertRuleType;
import kr.allcll.backend.domain.graduation.credit.CategoryType;
import kr.allcll.backend.domain.graduation.credit.CourseReplacement;
import kr.allcll.backend.domain.graduation.credit.CourseReplacementRepository;
import kr.allcll.backend.domain.graduation.credit.CourseEquivalence;
import kr.allcll.backend.domain.graduation.credit.CourseEquivalenceRepository;
import kr.allcll.backend.domain.graduation.credit.CreditCriterion;
import kr.allcll.backend.domain.graduation.credit.CreditCriterionRepository;
import kr.allcll.backend.domain.graduation.credit.DoubleCreditCriterion;
Expand All @@ -42,7 +42,7 @@
import kr.allcll.backend.support.sheet.validation.BalanceRequiredRulesSheetValidator;
import kr.allcll.backend.support.sheet.validation.ClassicCertCriteriaSheetValidator;
import kr.allcll.backend.support.sheet.validation.CodingCertCriteriaSheetValidator;
import kr.allcll.backend.support.sheet.validation.CourseReplacementsSheetValidator;
import kr.allcll.backend.support.sheet.validation.CourseEquivalencesSheetValidator;
import kr.allcll.backend.support.sheet.validation.CreditCriteriaSheetValidator;
import kr.allcll.backend.support.sheet.validation.DoubleCreditCriteriaSheetValidator;
import kr.allcll.backend.support.sheet.validation.EnglishCertCriteriaSheetValidator;
Expand All @@ -64,7 +64,7 @@ public class AdminGraduationSyncService {
private final RequiredCourseRepository requiredCourseRepository;
private final CreditCriterionRepository creditCriterionRepository;
private final GraduationSheetProperties graduationSheetProperties;
private final CourseReplacementRepository courseReplacementRepository;
private final CourseEquivalenceRepository courseEquivalenceRepository;
private final GraduationCertRuleRepository graduationCertRuleRepository;
private final BalanceRequiredRuleRepository balanceRequiredRuleRepository;
private final CodingCertCriterionRepository codingCertCriterionRepository;
Expand All @@ -82,7 +82,7 @@ public void syncGraduationRules() {
syncCreditCriteria();
syncDoubleCreditCriteria();
syncRequiredCourses();
syncCourseReplacements();
syncCourseEquivalences();

syncBalanceRequiredRule();
syncBalanceRequiredCourseAreaMap();
Expand Down Expand Up @@ -179,6 +179,7 @@ private void syncRequiredCourses() {
graduationSheetTable.getString(row, "curi_no"),
graduationSheetTable.getString(row, "curi_nm"),
graduationSheetTable.getString(row, "alt_group"),
graduationSheetTable.getString(row, "group_code"),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reject required-course rows missing group_code

This sync now persists group_code and the new findRequiredCourseInGroup logic depends on it, but required-courses validation still does not require that column/value; if the sheet is not updated (or cells are blank), rows are saved with null group codes and group-based matching always misses, causing equivalent/replacement academic-basic courses to be incorrectly treated as not required instead of failing fast during sync.

Useful? React with 👍 / 👎.

graduationSheetTable.getBoolean(row, "required"),
graduationSheetTable.getString(row, "note")
);
Expand All @@ -191,31 +192,26 @@ private void syncRequiredCourses() {
log.info("[졸업요건 데이터 동기화] 탭 이름={}, {}개 저장 완료", tabKey, requiredCourseList.size());
}

private void syncCourseReplacements() {
String tabKey = CourseReplacementsSheetValidator.TAB_KEY;
private void syncCourseEquivalences() {
String tabKey = CourseEquivalencesSheetValidator.TAB_KEY;
GraduationSheetTable graduationSheetTable = fetchAndValidate(tabKey);

List<CourseReplacement> courseReplacementList = new ArrayList<>();
List<CourseEquivalence> courseEquivalenceList = new ArrayList<>();
for (List<Object> row : graduationSheetTable.getDataRows()) {
CourseReplacement courseReplacement = new CourseReplacement(
graduationSheetTable.getInt(row, "admission_year"),
graduationSheetTable.getInt(row, "admission_year_short"),
graduationSheetTable.getString(row, "legacy_curi_nm"),
graduationSheetTable.getString(row, "current_curi_no"),
graduationSheetTable.getString(row, "current_curi_nm"),
graduationSheetTable.getBoolean(row, "enabled"),
graduationSheetTable.getString(row, "note")
CourseEquivalence courseEquivalence = new CourseEquivalence(
graduationSheetTable.getString(row, "group_code"),
graduationSheetTable.getString(row, "curi_no"),
graduationSheetTable.getString(row, "curi_nm")
);
courseReplacementList.add(courseReplacement);
courseEquivalenceList.add(courseEquivalence);
}

courseReplacementRepository.deleteAllInBatch();
courseReplacementRepository.saveAll(courseReplacementList);
courseEquivalenceRepository.deleteAllInBatch();
courseEquivalenceRepository.saveAll(courseEquivalenceList);

log.info("[졸업요건 데이터 동기화] 탭 이름={}, {}개 저장 완료", tabKey, courseReplacementList.size());
log.info("[졸업요건 데이터 동기화] 탭 이름={}, {}개 저장 완료", tabKey, courseEquivalenceList.size());
}


private void syncBalanceRequiredRule() {
String tabKey = BalanceRequiredRulesSheetValidator.TAB_KEY;
GraduationSheetTable graduationSheetTable = fetchAndValidate(tabKey);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@
public class AcademicBasicPolicy {

private final RequiredCourseResolver requiredCourseResolver;
private final CourseReplacementRepository courseReplacementRepository;
private final CourseEquivalenceRepository courseEquivalenceRepository;

public boolean isRecentMajorAcademicBasic(CompletedCourse course, CreditCriterion criterion) {
if (isNotAcademicBasic(course)) {
return true;
}
String courseName = course.getCuriNm();
String curiNm = course.getCuriNm();
String curiNo = course.getCuriNo();
Integer admissionYear = criterion.getAdmissionYear();
String departmentName = criterion.getDeptNm();
List<String> academicBasicRequiredCourseNames = requiredCourseResolver.findRequiredCourseNames(
Expand All @@ -25,44 +26,33 @@ public boolean isRecentMajorAcademicBasic(CompletedCourse course, CreditCriterio
CategoryType.ACADEMIC_BASIC
);

if (isExistAcademicBasicCourse(academicBasicRequiredCourseNames, courseName)) {
if (isExistAcademicBasicCourse(academicBasicRequiredCourseNames, curiNm)) {
return true;
}

return isHaveReplaceCourse(academicBasicRequiredCourseNames, admissionYear, courseName);
return isHaveReplaceOrEquivalenceCourse(admissionYear, departmentName, curiNo);
}

private boolean isExistAcademicBasicCourse(List<String> academicBasicRequiredCourseNames, String courseName) {
return academicBasicRequiredCourseNames.contains(courseName);
}

/*
대체된 최신 과목을 들었을 경우를 판별한다.
대체된 최신 과목이 없는 경우 false를 반환한다.
대체 과목의 예전 과목 명이, 학생의 이수 요건에 없으면 false를 반환한다.
*/
private boolean isHaveReplaceCourse(
List<String> academicBasicRequiredCourseNames,
Integer admissionYear,
String courseName
) {
List<CourseReplacement> recentCourse = courseReplacementRepository.findRecentCourse(admissionYear, courseName);
if (recentCourse.isEmpty()) {
return false;
}
for (CourseReplacement courseReplacement : recentCourse) {
if (academicBasicRequiredCourseNames.contains(courseReplacement.getLegacyCuriNm())) {
return true;
}
}
return false;
}

private boolean isNotAcademicBasic(CompletedCourse course) {
return !isAcademicBasic(course);
return !CategoryType.ACADEMIC_BASIC.equals(course.getCategoryType());
}

private boolean isAcademicBasic(CompletedCourse course) {
return CategoryType.ACADEMIC_BASIC.equals(course.getCategoryType());
private boolean isHaveReplaceOrEquivalenceCourse(
Integer admissionYear,
String departmentName,
String curiNo
) {
return courseEquivalenceRepository.findGroupCodeByCuriNo(curiNo)
.map(groupCode -> requiredCourseResolver.findRequiredCourseInGroup(
departmentName,
admissionYear,
CategoryType.ACADEMIC_BASIC,
groupCode
))
.orElse(false);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package kr.allcll.backend.domain.graduation.credit;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import kr.allcll.backend.support.entity.BaseEntity;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Table(name = "course_equivalences")
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class CourseEquivalence extends BaseEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(name = "group_code", nullable = false)
private String groupCode; // 동일과목 그룹 번호

@Column(name = "curi_no", nullable = false)
private String curiNo; // 학수번호

@Column(name = "curi_nm", nullable = false, length = 255)
private String curiNm; // 과목명

public CourseEquivalence(String groupCode, String curiNo, String curiNm) {
this.groupCode = groupCode;
this.curiNo = curiNo;
this.curiNm = curiNm;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package kr.allcll.backend.domain.graduation.credit;

import java.util.List;
import java.util.Optional;
import java.util.Set;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

public interface CourseEquivalenceRepository extends JpaRepository<CourseEquivalence, Long> {

@Query("""
select e.curiNo from CourseEquivalence e
join CourseEquivalence e2 on e.groupCode = e2.groupCode
where e2.curiNo in :curiNos
""")
List<String> findSameGroupCuriNos(Set<String> curiNos);

@Query("""
select e.groupCode from CourseEquivalence e
where e.curiNo = :curiNo
""")
Optional<String> findGroupCodeByCuriNo(String curiNo);
Comment on lines +19 to +22
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Enforce unique curi_no when reading equivalence group

findGroupCodeByCuriNo assumes a single row, but course_equivalences is populated from a sheet that is only string-validated (no uniqueness check on curi_no), so duplicate rows for the same course code will make this query return multiple results and throw IncorrectResultSizeDataAccessException at runtime during academic-basic evaluation. This should either be made tolerant (distinct/first) or guarded by validation/constraints so one bad sheet row does not break graduation checks.

Useful? React with 👍 / 👎.

}

This file was deleted.

This file was deleted.

This file was deleted.

Loading
Loading