Skip to content

Commit

Permalink
feat : 엑셀 파일로 과거 시간표 추가하기 (#1153)
Browse files Browse the repository at this point in the history
* feat : 엑셀 파일 최신화

* feat : 리팩토링 진행 및 패키지 이동

* chore : 안 쓰는 import 제거 및 정리

* chore : 관규님 리뷰 반영

* chore : 개행 추가

* feat: lecture 컬럼 크기 수정 flyway 추가 (#1142)

* fix: 식단 이미지 알림 롤백  (#1145)

* fix: 스케줄러 cron식 수정

* fix: 식단 이미지 업로드 알림 롤백

* fix : 동계방학 생협 운영시간 업데이트 (#1144)

* fix : 생협 동계학기 운영 시간 수정

* fix : 생협 동계학기 운영 시간 수정

* chore : 공백 추가

* fix: 세탁소 전화번호 수정

* feat: 스웨거 그룹화 (#1138)

* chore: swagger 패키지 생성 및 클래스 이동

* feat: 비즈니스팀 API 그룹화

* chore: 비즈니스 API 추가

* feat: 캠퍼스팀 API 그룹화

* feat: 유저팀 API 그룹화

* feat: ABTEST API 그룹화

* feat: BCSD API 그룹화

* chore: 비즈니스팀 API 추가

* feat: 어드민 API 그룹화

* chore: 유저팀 API 추가

* feat: 로그인 API 그룹화 및 그룹 이름 변경

* refactor: 패키지 경로 enum화

* refactor: 중복 코드 메소드화

* refactor: 그룹 스웨거 파일 분할

* chore: 미사용 import 삭제

* chore: 메소드 명 변경

* chore: 리뷰 반영

* fix: 안경점, 우편취급국 운영시간 추가, 대즐 운영시간 오타 수정 (#1151)

* feat: flyway추가 & model 관련 추가 및 수정

* feat: 시간표 조회 API 이수 구분 반환 추가 (#1150)

* feat: repository 추가

* feat: exception 추가

* feat: 이수 구분 반환값 추가

* feat: coursetype 반환값 추가

* test: 테스트 코드 수정

* chore: 리뷰 반영

* fix: department 중복 수정

* fix: department 조회 로직 변경

* rebase : 충돌 해결

* chore : 현수님 리뷰 반영

---------

Co-authored-by: 신관규 <[email protected]>
Co-authored-by: 허준기 <[email protected]>
Co-authored-by: Hwang HyeonSik <[email protected]>
Co-authored-by: 김원경 <[email protected]>
  • Loading branch information
5 people authored Dec 30, 2024
1 parent 862775f commit 03ad206
Show file tree
Hide file tree
Showing 11 changed files with 349 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package in.koreatech.koin.domain.graduation.controller;

import static in.koreatech.koin.domain.user.model.UserType.STUDENT;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

import in.koreatech.koin.global.auth.Auth;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;

@Tag(name = "(Normal) Graduation: 졸업학점 계산기", description = "졸업학점 계산기 정보를 관리한다")
public interface GraduationApi {
@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")
@PostMapping("/graduation/excel/upload")
ResponseEntity<String> uploadStudentGradeExcelFile(
@RequestParam(value = "file") MultipartFile file,
@Auth(permit = {STUDENT}) Integer userId
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package in.koreatech.koin.domain.graduation.controller;

import java.io.IOException;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import in.koreatech.koin.domain.graduation.service.GraduationService;
import in.koreatech.koin.domain.user.model.UserType;
import in.koreatech.koin.global.auth.Auth;
import lombok.RequiredArgsConstructor;

@RestController
@RequiredArgsConstructor
public class GraduationController implements GraduationApi {

private final GraduationService graduationService;

@PostMapping("/graduation/excel/upload")
public ResponseEntity<String> uploadStudentGradeExcelFile(
@RequestParam(value = "file") MultipartFile file,
@Auth(permit = {UserType.STUDENT}) Integer userId
) {
try {
graduationService.readStudentGradeExcelFile(file, userId);
return ResponseEntity.ok("파일이 성공적으로 업로드되었습니다.");
} catch (IOException e) {
return ResponseEntity.badRequest().build();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package in.koreatech.koin.domain.graduation.exception;

import in.koreatech.koin.global.exception.DataNotFoundException;

public class ExcelFileCheckException extends DataNotFoundException {
private static final String DEFAULT_MESSAGE = "엑셀 파일 형식이 아닙니다.";

public ExcelFileCheckException(String message) {
super(message);
}

public ExcelFileCheckException(String message, String detail) {
super(message, detail);
}

public static ExcelFileCheckException withDetail(String detail) {
return new ExcelFileCheckException(DEFAULT_MESSAGE, detail);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package in.koreatech.koin.domain.graduation.exception;

import in.koreatech.koin.global.exception.DataNotFoundException;

public class ExcelFileNotFoundException extends DataNotFoundException {
private static final String DEFAULT_MESSAGE = "엑셀 파일을 찾을 수 없습니다.";

public ExcelFileNotFoundException(String message) {
super(message);
}

public ExcelFileNotFoundException(String message, String detail) {
super(message, detail);
}

public static ExcelFileNotFoundException withDetail(String detail) {
return new ExcelFileNotFoundException(DEFAULT_MESSAGE, detail);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package in.koreatech.koin.domain.graduation.model;

import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;

public record GradeExcelData(
String year,
String semester,
String code,
String classTitle,
String lectureClass,
String professor,
String courseType,
String credit,
String grade,
String retakeStatus
) {
public static GradeExcelData fromRow(Row row) {
return new GradeExcelData(
getCellValueAsString(row.getCell(1)),
getCellValueAsString(row.getCell(2)),
getCellValueAsString(row.getCell(4)),
getCellValueAsString(row.getCell(5)),
getCellValueAsString(row.getCell(6)),
getCellValueAsString(row.getCell(7)),
getCellValueAsString(row.getCell(8)),
getCellValueAsString(row.getCell(9)),
getCellValueAsString(row.getCell(10)),
getCellValueAsString(row.getCell(11))
);
}

private static String getCellValueAsString(Cell cell) {
if (cell == null) {
return "";
}
return switch (cell.getCellType()) {
case STRING -> cell.getStringCellValue();
case NUMERIC -> String.valueOf((int)cell.getNumericCellValue());
default -> "";
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ public interface CourseTypeRepository extends Repository<CourseType, Integer> {

Optional<CourseType> findById(Integer id);

Optional<CourseType> findByName(String name);

default CourseType getCourseTypeById(Integer id) {
return findById(id)
.orElseThrow(() -> CourseTypeNotFoundException.withDetail("course_type_id: " + id));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package in.koreatech.koin.domain.graduation.service;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import in.koreatech.koin.domain.graduation.model.GradeExcelData;
import in.koreatech.koin.domain.graduation.model.CourseType;
import in.koreatech.koin.domain.graduation.repository.CourseTypeRepository;
import in.koreatech.koin.domain.graduation.exception.ExcelFileCheckException;
import in.koreatech.koin.domain.graduation.exception.ExcelFileNotFoundException;
import in.koreatech.koin.domain.timetable.model.Lecture;
import in.koreatech.koin.domain.timetable.model.Semester;
import in.koreatech.koin.domain.timetableV2.model.TimetableFrame;
import in.koreatech.koin.domain.timetableV2.model.TimetableLecture;
import in.koreatech.koin.domain.timetableV2.repository.LectureRepositoryV2;
import in.koreatech.koin.domain.timetableV2.repository.SemesterRepositoryV2;
import in.koreatech.koin.domain.timetableV2.repository.TimetableFrameRepositoryV2;
import in.koreatech.koin.domain.timetableV2.repository.TimetableLectureRepositoryV2;
import in.koreatech.koin.domain.user.model.User;
import in.koreatech.koin.domain.user.repository.UserRepository;
import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class GraduationService {

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 CourseTypeRepository courseTypeRepository;
private final UserRepository userRepository;
private final SemesterRepositoryV2 semesterRepositoryV2;
private final LectureRepositoryV2 lectureRepositoryV2;
private final TimetableLectureRepositoryV2 timetableLectureRepositoryV2;
private final TimetableFrameRepositoryV2 timetableFrameRepositoryV2;

@Transactional
public void readStudentGradeExcelFile(MultipartFile file, Integer userId) throws IOException {
checkFiletype(file);

try (InputStream inputStream = file.getInputStream();
Workbook workbook = new HSSFWorkbook(inputStream)
) {
Sheet sheet = workbook.getSheetAt(0);
TimetableFrame graduationFrame = null;
String currentSemester = "default";

for (Row row : sheet) {
GradeExcelData data = extractExcelData(row);
if (row.getRowNum() == 0 || skipRow(data)) {
continue;
}

if (data.classTitle().equals(TOTAL)) {
break;
}

String semester = getKoinSemester(data.semester(), data.year());
CourseType courseType = courseTypeRepository.findByName(data.courseType()).orElse(null);
Lecture lecture = lectureRepositoryV2.findBySemesterAndCodeAndLectureClass(semester,
data.code(), data.lectureClass()).orElse(null);

if (!currentSemester.equals(semester)) {
currentSemester = semester;
graduationFrame = createFrameAboutExcel(userId, currentSemester);
}

TimetableLecture timetableLecture = TimetableLecture.builder()
.classTitle(data.classTitle())
.classTime(lecture != null ? lecture.getClassTime() : null)
.professor(data.professor())
.grades(data.credit())
.isDeleted(false)
.lecture(lecture)
.timetableFrame(graduationFrame)
.courseType(courseType)
.build();

timetableLectureRepositoryV2.save(timetableLecture);
}
}
}

private TimetableFrame createFrameAboutExcel(Integer userId, String semester) {
User user = userRepository.getById(userId);
Semester saveSemester = semesterRepositoryV2.getBySemester(semester);

List<TimetableFrame> timetableFrameList = timetableFrameRepositoryV2.findAllByUserIdAndSemesterId
(userId, saveSemester.getId());
for (TimetableFrame timetableFrame : timetableFrameList) {
if (timetableFrame.isMain()) {
timetableFrame.cancelMain();
}
}
TimetableFrame graduationFrame = TimetableFrame.builder()
.user(user)
.semester(saveSemester)
.name("Graduation Frame")
.isDeleted(false)
.isMain(true)
.build();
timetableFrameRepositoryV2.save(graduationFrame);

return graduationFrame;
}

private GradeExcelData extractExcelData(Row row) {
return GradeExcelData.fromRow(row);
}

private void checkFiletype(MultipartFile file) {
if (file == null) {
throw new ExcelFileNotFoundException("파일이 있는지 확인 해주세요.");
}

String fileName = file.getOriginalFilename();
int findDot = fileName.lastIndexOf(".");
if (findDot == -1) {
throw new ExcelFileNotFoundException("파일의 형식이 맞는지 확인 해주세요.");
}

String extension = fileName.substring(findDot + 1);
if (!extension.equals("xls") && !extension.equals("xlsx")) {
throw new ExcelFileCheckException("엑셀 파일인지 확인 해주세요.");
}
}

private boolean skipRow(GradeExcelData gradeExcelData) {
return gradeExcelData.classTitle().equals(MIDDLE_TOTAL) ||
gradeExcelData.retakeStatus().equals(RETAKE) ||
gradeExcelData.grade().equals(UNSATISFACTORY);
}

private String getKoinSemester(String semester, String year) {
if (semester.equals("1") || semester.equals("2")) {
return year + semester;
} else if (semester.equals("동계")) {
return year + "-" + "겨울";
} else
return year + "-" + "여름";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package in.koreatech.koin.domain.timetableV2.exception;

import in.koreatech.koin.global.exception.DataNotFoundException;

public class ExcelFileCheckException extends DataNotFoundException
{
private static final String DEFAULT_MESSAGE = "엑셀 파일 형식이 아닙니다.";

public ExcelFileCheckException(String message) {
super(message);
}

public ExcelFileCheckException(String message, String detail) {
super(message, detail);
}

public static ExcelFileCheckException withDetail(String detail) {
return new ExcelFileCheckException(DEFAULT_MESSAGE, detail);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package in.koreatech.koin.domain.timetableV2.exception;

import in.koreatech.koin.global.exception.DataNotFoundException;

public class ExcelFileNotFoundException extends DataNotFoundException {
private static final String DEFAULT_MESSAGE = "엑셀 파일을 찾을 수 없습니다.";

public ExcelFileNotFoundException(String message) {
super(message);
}

public ExcelFileNotFoundException(String message, String detail) {
super(message, detail);
}

public static ExcelFileNotFoundException withDetail(String detail) {
return new ExcelFileNotFoundException(DEFAULT_MESSAGE, detail);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -112,4 +112,6 @@ public void delete() {
public void undelete() {
this.isDeleted = false;
}

public void cancelMain() { this.isMain = false; }
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
package in.koreatech.koin.domain.timetableV2.model;

import static jakarta.persistence.GenerationType.IDENTITY;
import static lombok.AccessLevel.PROTECTED;

Expand Down

0 comments on commit 03ad206

Please sign in to comment.