From 0527431df61fc67f730dcddc9d32863eb8a3c56a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=93=80=ED=9E=88?= <149302959+duehee@users.noreply.github.com> Date: Sat, 25 Jan 2025 16:53:43 +0900 Subject: [PATCH] =?UTF-8?q?feat=20:=20=ED=95=99=EA=B8=B0=EC=99=80=20?= =?UTF-8?q?=EC=9D=B4=EC=88=98=EA=B5=AC=EB=B6=84=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EA=B0=95=EC=9D=98=20=EC=B0=BE=EA=B8=B0=20(#1184)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix : flyway 116번, 117번 수정 * feat : Repository 정리 및 작성 * feat : Response 추가 * feat : exception 추가 * chore : Repo 정리 * feat : Service 및 Controller 작성 * feat : graduation 도메인 swagger 추가 * feat : 리뷰 반영 * chore : 리뷰 반영 --- .../graduation/controller/GraduationApi.java | 20 +++++++++ .../controller/GraduationController.java | 19 ++++++-- .../dto/CourseTypeLectureResponse.java | 21 +++++++++ .../dto/LecturePortionResponse.java | 31 +++++++++++++ .../repository/CatalogRepository.java | 10 ++++- .../repository/CourseTypeRepository.java | 5 +++ ...DetectGraduationCalculationRepository.java | 4 +- .../StudentCourseCalculationRepository.java | 8 ++-- .../graduation/service/GraduationService.java | 44 ++++++++++++++++--- ...otFoundSemesterAndCourseTypeException.java | 19 ++++++++ .../repository/LectureRepositoryV2.java | 7 ++- .../config/swagger/SwaggerGroupConfig.java | 3 +- .../db/migration/V116__add_catalog.sql | 2 +- ...117__add_detect_graduation_calculation.sql | 2 +- 14 files changed, 176 insertions(+), 19 deletions(-) create mode 100644 src/main/java/in/koreatech/koin/domain/graduation/dto/CourseTypeLectureResponse.java create mode 100644 src/main/java/in/koreatech/koin/domain/graduation/dto/LecturePortionResponse.java create mode 100644 src/main/java/in/koreatech/koin/domain/timetableV2/exception/NotFoundSemesterAndCourseTypeException.java diff --git a/src/main/java/in/koreatech/koin/domain/graduation/controller/GraduationApi.java b/src/main/java/in/koreatech/koin/domain/graduation/controller/GraduationApi.java index 4d9068cac..9adb2406d 100644 --- a/src/main/java/in/koreatech/koin/domain/graduation/controller/GraduationApi.java +++ b/src/main/java/in/koreatech/koin/domain/graduation/controller/GraduationApi.java @@ -3,10 +3,12 @@ import static in.koreatech.koin.domain.user.model.UserType.STUDENT; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.multipart.MultipartFile; +import in.koreatech.koin.domain.graduation.dto.CourseTypeLectureResponse; import in.koreatech.koin.global.auth.Auth; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; @@ -49,4 +51,22 @@ ResponseEntity uploadStudentGradeExcelFile( @RequestParam(value = "file") MultipartFile file, @Auth(permit = {STUDENT}) Integer userId ); + + @ApiResponses( + value = { + @ApiResponse(responseCode = "200"), + @ApiResponse(responseCode = "401", content = @Content(schema = @Schema(hidden = true))), + @ApiResponse(responseCode = "403", content = @Content(schema = @Schema(hidden = true))), + @ApiResponse(responseCode = "404", content = @Content(schema = @Schema(hidden = true))) + } + ) + @Operation(summary = "학기에 따른 이수구분 강의 출력") + @SecurityRequirement(name = "Jwt Authentication") + @GetMapping("/graduation/course-type") + ResponseEntity getCourseTypeLecture( + @RequestParam(name = "year") Integer year, + @RequestParam(name = "term") String term, + @RequestParam(name = "name") String courseTypeName, + @Auth(permit = {STUDENT}) Integer userId + ); } diff --git a/src/main/java/in/koreatech/koin/domain/graduation/controller/GraduationController.java b/src/main/java/in/koreatech/koin/domain/graduation/controller/GraduationController.java index ae552b4d4..de485f9f7 100644 --- a/src/main/java/in/koreatech/koin/domain/graduation/controller/GraduationController.java +++ b/src/main/java/in/koreatech/koin/domain/graduation/controller/GraduationController.java @@ -1,6 +1,7 @@ package in.koreatech.koin.domain.graduation.controller; import java.io.IOException; + import static in.koreatech.koin.domain.user.model.UserType.STUDENT; import org.springframework.http.ResponseEntity; @@ -10,7 +11,7 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; -import in.koreatech.koin.domain.graduation.dto.GraduationCourseCalculationResponse; +import in.koreatech.koin.domain.graduation.dto.CourseTypeLectureResponse; import in.koreatech.koin.domain.graduation.service.GraduationService; import in.koreatech.koin.domain.user.model.UserType; import in.koreatech.koin.global.auth.Auth; @@ -24,8 +25,7 @@ public class GraduationController implements GraduationApi { @PostMapping("/graduation/agree") public ResponseEntity createStudentCourseCalculation( - @Auth(permit = {STUDENT}) Integer userId) - { + @Auth(permit = {STUDENT}) Integer userId) { graduationService.createStudentCourseCalculation(userId); return ResponseEntity.ok().build(); } @@ -43,10 +43,23 @@ public ResponseEntity uploadStudentGradeExcelFile( } } + @GetMapping("/graduation/course-type") + public ResponseEntity getCourseTypeLecture( + @RequestParam(name = "year") Integer year, + @RequestParam(name = "term") String term, + @RequestParam(name = "name") String courseTypeName, + @Auth(permit = {STUDENT}) Integer userId + ) { + CourseTypeLectureResponse response = graduationService.getLectureByCourseType(year, term, courseTypeName); + return ResponseEntity.ok(response); + } + + /* @GetMapping("/graduation/course/calculation") public ResponseEntity getGraduationCourseCalculation( @Auth(permit = {STUDENT}) Integer userId) { GraduationCourseCalculationResponse response = graduationService.getGraduationCourseCalculationResponse(userId); return ResponseEntity.ok(response); } + */ } diff --git a/src/main/java/in/koreatech/koin/domain/graduation/dto/CourseTypeLectureResponse.java b/src/main/java/in/koreatech/koin/domain/graduation/dto/CourseTypeLectureResponse.java new file mode 100644 index 000000000..835c21d5d --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/graduation/dto/CourseTypeLectureResponse.java @@ -0,0 +1,21 @@ +package in.koreatech.koin.domain.graduation.dto; + +import java.util.List; + +import in.koreatech.koin.domain.timetable.model.Lecture; +import io.swagger.v3.oas.annotations.media.Schema; + +public record CourseTypeLectureResponse( + @Schema(description = "학기", example = "20192") + String semester, + + @Schema(description = "이수구분 충족강의") + List lectures +) { + public static CourseTypeLectureResponse of(String semester, List lectures) { + List lectureList = lectures.stream() + .map(LecturePortionResponse::from) + .toList(); + return new CourseTypeLectureResponse(semester, lectureList); + } +} diff --git a/src/main/java/in/koreatech/koin/domain/graduation/dto/LecturePortionResponse.java b/src/main/java/in/koreatech/koin/domain/graduation/dto/LecturePortionResponse.java new file mode 100644 index 000000000..c46b7e600 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/graduation/dto/LecturePortionResponse.java @@ -0,0 +1,31 @@ +package in.koreatech.koin.domain.graduation.dto; + +import in.koreatech.koin.domain.timetable.model.Lecture; +import io.swagger.v3.oas.annotations.media.Schema; + +public record LecturePortionResponse( + @Schema(description = "강의 ID", example = "1") + Integer id, + + @Schema(description = "강의 코드", example = "ABC123") + String code, + + @Schema(description = "강의 이름", example = "컴퓨터구조") + String name, + + @Schema(description = "학점", example = "3") + String grades, + + @Schema(description = "학과", example = "컴퓨터공학부") + String department +) { + public static LecturePortionResponse from(Lecture lecture) { + return new LecturePortionResponse( + lecture.getId(), + lecture.getCode(), + lecture.getName(), + lecture.getGrades(), + lecture.getDepartment() + ); + } +} diff --git a/src/main/java/in/koreatech/koin/domain/graduation/repository/CatalogRepository.java b/src/main/java/in/koreatech/koin/domain/graduation/repository/CatalogRepository.java index 53fa3bb52..af32f7beb 100644 --- a/src/main/java/in/koreatech/koin/domain/graduation/repository/CatalogRepository.java +++ b/src/main/java/in/koreatech/koin/domain/graduation/repository/CatalogRepository.java @@ -13,10 +13,18 @@ public interface CatalogRepository extends Repository { Optional findByDepartmentAndCode(Department department, String code); + // 이거 오류나요.. + // List findByLectureNameAndYearAndDepartment(String lectureName, String studentYear, Department department); + + Optional> findAllByCourseTypeId(Integer courseTypeId); + default Catalog getByDepartmentAndCode(Department department, String code) { return findByDepartmentAndCode(department, code) .orElseThrow(() -> CatalogNotFoundException.withDetail("department: " + department + ", code: " + code)); } - List findByLectureNameAndYearAndDepartment(String lectureName, String studentYear, Department department); + default List getAllByCourseTypeId(Integer courseTypeId) { + return findAllByCourseTypeId(courseTypeId) + .orElseThrow(() -> CatalogNotFoundException.withDetail("course_type_id" + courseTypeId)); + } } diff --git a/src/main/java/in/koreatech/koin/domain/graduation/repository/CourseTypeRepository.java b/src/main/java/in/koreatech/koin/domain/graduation/repository/CourseTypeRepository.java index 9cfc7a369..9dbe48b1c 100644 --- a/src/main/java/in/koreatech/koin/domain/graduation/repository/CourseTypeRepository.java +++ b/src/main/java/in/koreatech/koin/domain/graduation/repository/CourseTypeRepository.java @@ -19,4 +19,9 @@ default CourseType getCourseTypeById(Integer id) { return findById(id) .orElseThrow(() -> CourseTypeNotFoundException.withDetail("course_type_id: " + id)); } + + default CourseType getByName(String name) { + return findByName(name) + .orElseThrow(() -> CourseTypeNotFoundException.withDetail("course_type_name: " + name)); + } } diff --git a/src/main/java/in/koreatech/koin/domain/graduation/repository/DetectGraduationCalculationRepository.java b/src/main/java/in/koreatech/koin/domain/graduation/repository/DetectGraduationCalculationRepository.java index 538a72114..4fae8e833 100644 --- a/src/main/java/in/koreatech/koin/domain/graduation/repository/DetectGraduationCalculationRepository.java +++ b/src/main/java/in/koreatech/koin/domain/graduation/repository/DetectGraduationCalculationRepository.java @@ -8,8 +8,8 @@ public interface DetectGraduationCalculationRepository extends Repository { - Optional findByUserId(Integer userId); - void save(DetectGraduationCalculation detectGraduationCalculation); + + Optional findByUserId(Integer userId); } diff --git a/src/main/java/in/koreatech/koin/domain/graduation/repository/StudentCourseCalculationRepository.java b/src/main/java/in/koreatech/koin/domain/graduation/repository/StudentCourseCalculationRepository.java index 875fbbf4f..353eda3b3 100644 --- a/src/main/java/in/koreatech/koin/domain/graduation/repository/StudentCourseCalculationRepository.java +++ b/src/main/java/in/koreatech/koin/domain/graduation/repository/StudentCourseCalculationRepository.java @@ -8,13 +8,13 @@ public interface StudentCourseCalculationRepository extends Repository { - Optional findByUserId(Integer userId); - - void deleteAllByUserId(Integer userId); - void save(StudentCourseCalculation studentCourseCalculation); + Optional findByUserId(Integer userId); + StudentCourseCalculation findByUserIdAndStandardGraduationRequirementsId(Integer userId, Integer id); void delete(StudentCourseCalculation existingCalculation); + + void deleteAllByUserId(Integer userId); } diff --git a/src/main/java/in/koreatech/koin/domain/graduation/service/GraduationService.java b/src/main/java/in/koreatech/koin/domain/graduation/service/GraduationService.java index a286b6dfd..0f8e227df 100644 --- a/src/main/java/in/koreatech/koin/domain/graduation/service/GraduationService.java +++ b/src/main/java/in/koreatech/koin/domain/graduation/service/GraduationService.java @@ -2,7 +2,6 @@ import java.io.IOException; import java.io.InputStream; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -16,6 +15,7 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; +import in.koreatech.koin.domain.graduation.dto.CourseTypeLectureResponse; import in.koreatech.koin.domain.graduation.dto.GraduationCourseCalculationResponse; import in.koreatech.koin.domain.graduation.model.Catalog; import in.koreatech.koin.domain.graduation.model.DetectGraduationCalculation; @@ -30,7 +30,7 @@ import in.koreatech.koin.domain.student.model.Department; import in.koreatech.koin.domain.student.model.Student; import in.koreatech.koin.domain.student.repository.StudentRepository; -import in.koreatech.koin.domain.student.util.StudentUtil; +import in.koreatech.koin.domain.timetableV2.exception.NotFoundSemesterAndCourseTypeException; import in.koreatech.koin.domain.timetableV2.model.TimetableLecture; import in.koreatech.koin.domain.timetableV2.repository.TimetableFrameRepositoryV2; @@ -45,6 +45,9 @@ import in.koreatech.koin.domain.timetableV2.repository.LectureRepositoryV2; import in.koreatech.koin.domain.timetableV2.repository.SemesterRepositoryV2; import in.koreatech.koin.domain.timetableV2.repository.TimetableLectureRepositoryV2; +import in.koreatech.koin.domain.timetableV3.model.Term; +import in.koreatech.koin.domain.timetableV3.repository.SemesterRepositoryV3; +import in.koreatech.koin.domain.timetableV3.service.SemesterServiceV3; import in.koreatech.koin.domain.user.model.User; import in.koreatech.koin.domain.user.repository.UserRepository; import lombok.RequiredArgsConstructor; @@ -64,12 +67,14 @@ public class GraduationService { private final SemesterRepositoryV2 semesterRepositoryV2; private final LectureRepositoryV2 lectureRepositoryV2; private final TimetableLectureRepositoryV2 timetableLectureRepositoryV2; + private final SemesterRepositoryV3 semesterRepositoryV3; private final CatalogRepository catalogRepository; private static final String MIDDLE_TOTAL = "소 계"; private static final String TOTAL = "합 계"; private static final String RETAKE = "Y"; private static final String UNSATISFACTORY = "U"; + private final SemesterServiceV3 semesterServiceV3; @Transactional public void createStudentCourseCalculation(Integer userId) { @@ -103,6 +108,7 @@ public void resetStudentCourseCalculation(Student student, Department newDepartm }); } + /* @Transactional public GraduationCourseCalculationResponse getGraduationCourseCalculationResponse(Integer userId) { DetectGraduationCalculation detectGraduationCalculation = detectGraduationCalculationRepository.findByUserId(userId) @@ -135,6 +141,7 @@ public GraduationCourseCalculationResponse getGraduationCourseCalculationRespons return GraduationCourseCalculationResponse.of(courseTypes); } + */ @Transactional public void readStudentGradeExcelFile(MultipartFile file, Integer userId) throws IOException { @@ -158,7 +165,7 @@ public void readStudentGradeExcelFile(MultipartFile file, Integer userId) throws } String semester = getKoinSemester(data.semester(), data.year()); - CourseType courseType = courseTypeRepository.findByName(data.courseType()).orElse(null); + CourseType courseType = mappingCourseType(data.courseType()); Lecture lecture = lectureRepositoryV2.findBySemesterAndCodeAndLectureClass(semester, data.code(), data.lectureClass()).orElse(null); @@ -229,8 +236,8 @@ private void checkFiletype(MultipartFile file) { private boolean skipRow(GradeExcelData gradeExcelData) { return gradeExcelData.classTitle().equals(MIDDLE_TOTAL) || - gradeExcelData.retakeStatus().equals(RETAKE) || - gradeExcelData.grade().equals(UNSATISFACTORY); + gradeExcelData.retakeStatus().equals(RETAKE) || + gradeExcelData.grade().equals(UNSATISFACTORY); } private String getKoinSemester(String semester, String year) { @@ -248,6 +255,16 @@ private void validateStudentField(Object field, String message) { } } + public CourseType mappingCourseType(String courseTypeName) { + if ("전필".equals(courseTypeName)) { + return courseTypeRepository.getByName("학과(전공)필수"); + } else if ("전선".equals(courseTypeName)) { + return courseTypeRepository.getByName("학과(전공)선택"); + } else { + return courseTypeRepository.findByName(courseTypeName).orElse(null); + } + } + private void initializeStudentCourseCalculation(Student student, Department department) { // 학번에 맞는 이수요건 정보 조회 List requirementsList = @@ -278,6 +295,7 @@ private Student getValidatedStudent(Integer userId) { return student; } + /* private List getCatalogListForStudent(Student student, String studentYear) { List timetableLectures = timetableFrameRepositoryV2.getAllByUserId(student.getId()).stream() .flatMap(frame -> frame.getTimetableLectures().stream()) @@ -297,6 +315,7 @@ private List getCatalogListForStudent(Student student, String studentYe }); return catalogList; } + */ private Map calculateCourseTypeCredits(List catalogList) { Map courseTypeCreditsMap = new HashMap<>(); @@ -361,4 +380,19 @@ private int updateStudentCourseCalculation(Integer userId, Student student, return completedGrades; } + + public CourseTypeLectureResponse getLectureByCourseType(Integer year, String term, String courseTypeName) { + CourseType courseType = courseTypeRepository.getByName(courseTypeName); + List catalogs = catalogRepository.getAllByCourseTypeId(courseType.getId()); + List codes = catalogs.stream().map(Catalog::getCode).toList(); + + Term parsedTerm = Term.fromDescription(term); + Semester foundSemester = semesterRepositoryV3.getByYearAndTerm(year, parsedTerm); + String semester = foundSemester.getSemester(); + + List lectures = lectureRepositoryV2.findAllByCodesAndSemester(codes, semester) + .orElseThrow(() -> new NotFoundSemesterAndCourseTypeException("학기나 이수구분을 찾을 수 없습니다.")); + + return CourseTypeLectureResponse.of(semester, lectures); + } } diff --git a/src/main/java/in/koreatech/koin/domain/timetableV2/exception/NotFoundSemesterAndCourseTypeException.java b/src/main/java/in/koreatech/koin/domain/timetableV2/exception/NotFoundSemesterAndCourseTypeException.java new file mode 100644 index 000000000..981f650c7 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/timetableV2/exception/NotFoundSemesterAndCourseTypeException.java @@ -0,0 +1,19 @@ +package in.koreatech.koin.domain.timetableV2.exception; + +import in.koreatech.koin.global.exception.DataNotFoundException; + +public class NotFoundSemesterAndCourseTypeException extends DataNotFoundException { + private static final String DEFAULT_MESSAGE = "학기나 이수구분을 찾을 수 없습니다."; + + public NotFoundSemesterAndCourseTypeException(String message) { + super(message); + } + + public NotFoundSemesterAndCourseTypeException(String message, String detail) { + super(message, detail); + } + + public static NotFoundSemesterAndCourseTypeException withDetail(String detail) { + return new NotFoundSemesterAndCourseTypeException(DEFAULT_MESSAGE, detail); + } +} diff --git a/src/main/java/in/koreatech/koin/domain/timetableV2/repository/LectureRepositoryV2.java b/src/main/java/in/koreatech/koin/domain/timetableV2/repository/LectureRepositoryV2.java index 0bcf1fb76..817e7f8cc 100644 --- a/src/main/java/in/koreatech/koin/domain/timetableV2/repository/LectureRepositoryV2.java +++ b/src/main/java/in/koreatech/koin/domain/timetableV2/repository/LectureRepositoryV2.java @@ -3,12 +3,13 @@ import java.util.List; import java.util.Optional; +import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.Repository; import in.koreatech.koin.domain.timetable.exception.LectureNotFoundException; import in.koreatech.koin.domain.timetable.exception.SemesterNotFoundException; import in.koreatech.koin.domain.timetable.model.Lecture; -import in.koreatech.koin.domain.timetable.model.Semester; +import io.lettuce.core.dynamic.annotation.Param; public interface LectureRepositoryV2 extends Repository { @@ -20,6 +21,9 @@ public interface LectureRepositoryV2 extends Repository { Optional findBySemesterAndCodeAndLectureClass(String semesterDate, String code, String classLecture); + @Query("SELECT l FROM Lecture l WHERE l.code IN :codes AND l.semester = :semesterDate") + Optional> findAllByCodesAndSemester(@Param("codes") List codes, @Param("semesterDate") String semesterDate); + default Lecture getBySemesterAndCodeAndLectureClass(String semesterDate, String code, String classLecture) { return findBySemesterAndCodeAndLectureClass(semesterDate, code, classLecture) .orElseThrow(() -> SemesterNotFoundException.withDetail( @@ -30,4 +34,5 @@ default Lecture getLectureById(Integer id) { return findById(id) .orElseThrow(() -> LectureNotFoundException.withDetail("lecture_id: " + id)); } + } diff --git a/src/main/java/in/koreatech/koin/global/config/swagger/SwaggerGroupConfig.java b/src/main/java/in/koreatech/koin/global/config/swagger/SwaggerGroupConfig.java index 117d18626..87ad2ed55 100644 --- a/src/main/java/in/koreatech/koin/global/config/swagger/SwaggerGroupConfig.java +++ b/src/main/java/in/koreatech/koin/global/config/swagger/SwaggerGroupConfig.java @@ -61,7 +61,8 @@ public GroupedOpenApi userApi() { "in.koreatech.koin.domain.student", "in.koreatech.koin.domain.timetable", "in.koreatech.koin.domain.timetableV2", - "in.koreatech.koin.domain.timetableV3" + "in.koreatech.koin.domain.timetableV3", + "in.koreatech.koin.domain.graduation" }; return createGroupedOpenApi("4. User API", packagesPath); diff --git a/src/main/resources/db/migration/V116__add_catalog.sql b/src/main/resources/db/migration/V116__add_catalog.sql index 82e64aa3d..7743b1205 100644 --- a/src/main/resources/db/migration/V116__add_catalog.sql +++ b/src/main/resources/db/migration/V116__add_catalog.sql @@ -11,5 +11,5 @@ CREATE TABLE if not exists `koin`.`catalog` FOREIGN KEY (`course_type_id`) REFERENCES `course_type` (`id`), FOREIGN KEY (`department_id`) REFERENCES `department` (`id`), created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP comment '생성 일자', - updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP comment '수정 일자', + updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP comment '수정 일자' ); diff --git a/src/main/resources/db/migration/V117__add_detect_graduation_calculation.sql b/src/main/resources/db/migration/V117__add_detect_graduation_calculation.sql index 248909739..9a948393a 100644 --- a/src/main/resources/db/migration/V117__add_detect_graduation_calculation.sql +++ b/src/main/resources/db/migration/V117__add_detect_graduation_calculation.sql @@ -1,5 +1,5 @@ -- 졸업학점 계산 감지 테이블 -CREATE TABLE `detect_graduation_calculation` +CREATE TABLE if not exists `koin`.`detect_graduation_calculation` ( id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT comment '고유 id', user_id INT UNSIGNED NULL comment '유저 id',