diff --git a/build.gradle b/build.gradle index 14bfbb3f..a7af60bd 100644 --- a/build.gradle +++ b/build.gradle @@ -75,6 +75,9 @@ dependencies { // H2 (테스트 환경) testImplementation 'com.h2database:h2' + + // Excel + implementation 'org.apache.poi:poi-ooxml:5.2.3' } test { diff --git a/src/main/java/KUSITMS/WITHUS/domain/application/applicantAvailability/repository/ApplicantAvailabilityRepository.java b/src/main/java/KUSITMS/WITHUS/domain/application/applicantAvailability/repository/ApplicantAvailabilityRepository.java index bc7f6d0f..06ded9b3 100644 --- a/src/main/java/KUSITMS/WITHUS/domain/application/applicantAvailability/repository/ApplicantAvailabilityRepository.java +++ b/src/main/java/KUSITMS/WITHUS/domain/application/applicantAvailability/repository/ApplicantAvailabilityRepository.java @@ -9,4 +9,5 @@ public interface ApplicantAvailabilityRepository extends JpaRepository { List findByApplicationIn(List applications); List findByApplicationId(Long id); + List findAllByApplicationIdIn(List appIds); } diff --git a/src/main/java/KUSITMS/WITHUS/domain/application/application/controller/AdminApplicationController.java b/src/main/java/KUSITMS/WITHUS/domain/application/application/controller/AdminApplicationController.java index e38d6b79..351e875b 100644 --- a/src/main/java/KUSITMS/WITHUS/domain/application/application/controller/AdminApplicationController.java +++ b/src/main/java/KUSITMS/WITHUS/domain/application/application/controller/AdminApplicationController.java @@ -9,20 +9,26 @@ import KUSITMS.WITHUS.domain.application.application.service.ApplicationSmsService; import KUSITMS.WITHUS.domain.application.applicationEvaluator.dto.ApplicationEvaluatorRequestDTO; import KUSITMS.WITHUS.domain.application.distributionRequest.dto.DistributionRequestResponseDTO; -import KUSITMS.WITHUS.domain.application.distributionRequest.entity.DistributionRequest; +import KUSITMS.WITHUS.domain.application.enumerate.ApplicationStatus; +import KUSITMS.WITHUS.domain.user.user.entity.User; +import KUSITMS.WITHUS.global.common.annotation.CurrentUser; import KUSITMS.WITHUS.global.response.PagedResponse; import KUSITMS.WITHUS.global.response.SuccessResponse; +import KUSITMS.WITHUS.global.util.excel.ExcelExporter; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.mail.MessagingException; +import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import org.apache.poi.ss.usermodel.Workbook; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.web.PageableDefault; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; +import java.io.IOException; import java.util.List; @RestController @@ -41,15 +47,53 @@ public class AdminApplicationController { public SuccessResponse> getList( @PathVariable Long recruitmentId, @RequestParam(defaultValue = "DOCUMENT") AdminStageFilter stage, - @RequestParam(defaultValue = "NAME") AdminApplicationSortField sortBy, - @RequestParam(defaultValue = "ASC") Sort.Direction direction, + @RequestParam(defaultValue = "LATEST") AdminApplicationSortField sortBy, + @RequestParam(defaultValue = "DESC") Sort.Direction direction, + @RequestParam(required = false) List organizationRoleIds, + @RequestParam(required = false) List statuses, + @RequestParam(required = false) String keyword, @PageableDefault(size = 7) Pageable pageable ) { - ApplicationResponseDTO.AdminPageWithStageCounts result = applicationService.getByRecruitmentIdForAdmin(recruitmentId, stage, pageable, sortBy, direction); + ApplicationResponseDTO.AdminPageWithStageCounts result = applicationService.getByRecruitmentIdForAdmin(recruitmentId, stage, pageable, sortBy, direction, organizationRoleIds, statuses, keyword); PagedResponse paged = PagedResponse.from(result.page(), result.counts()); return SuccessResponse.ok(paged); } + @GetMapping("/recruitment/{recruitmentId}/excel") + public void downloadExcel( + @PathVariable Long recruitmentId, + @RequestParam(defaultValue = "DOCUMENT") AdminStageFilter stage, + @RequestParam(defaultValue = "LATEST") AdminApplicationSortField sortBy, + @RequestParam(defaultValue = "DESC") Sort.Direction direction, + @RequestParam(required = false) List organizationRoleIds, + @RequestParam(required = false) List statuses, + @RequestParam(required = false) String keyword, + @CurrentUser User user, + HttpServletResponse response + ) throws IOException { + + List list = + applicationService.getAllDetailForExcel( + recruitmentId, + stage, + sortBy, + direction, + organizationRoleIds, + statuses, + keyword, + user.getId() + ); + + String fileName = "applications-detail.xlsx"; + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setHeader("Content-Disposition", "attachment; filename=" + fileName); + + try (Workbook workbook = ExcelExporter.createExcel(list)) { + workbook.write(response.getOutputStream()); + } + } + + @GetMapping("/recruitments/{recruitmentId}/timeslots/{timeslotId}/candidates") @Operation(summary = "타임테이블 후보자 조회") public SuccessResponse> getTimetableCandidates( diff --git a/src/main/java/KUSITMS/WITHUS/domain/application/application/enumerate/AdminApplicationSortField.java b/src/main/java/KUSITMS/WITHUS/domain/application/application/enumerate/AdminApplicationSortField.java index 0bbd91c3..07365ea0 100644 --- a/src/main/java/KUSITMS/WITHUS/domain/application/application/enumerate/AdminApplicationSortField.java +++ b/src/main/java/KUSITMS/WITHUS/domain/application/application/enumerate/AdminApplicationSortField.java @@ -1,6 +1,7 @@ package KUSITMS.WITHUS.domain.application.application.enumerate; public enum AdminApplicationSortField { + LATEST, NAME, POSITION_NAME, DOCUMENT_EVALUATION_STATUS, diff --git a/src/main/java/KUSITMS/WITHUS/domain/application/application/service/ApplicationService.java b/src/main/java/KUSITMS/WITHUS/domain/application/application/service/ApplicationService.java index 23159fa2..9c63749a 100644 --- a/src/main/java/KUSITMS/WITHUS/domain/application/application/service/ApplicationService.java +++ b/src/main/java/KUSITMS/WITHUS/domain/application/application/service/ApplicationService.java @@ -7,6 +7,7 @@ import KUSITMS.WITHUS.domain.application.application.enumerate.EvaluationStatus; import KUSITMS.WITHUS.domain.application.applicationEvaluator.dto.ApplicationEvaluatorRequestDTO; import KUSITMS.WITHUS.domain.application.distributionRequest.dto.DistributionRequestResponseDTO; +import KUSITMS.WITHUS.domain.application.enumerate.ApplicationStatus; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; @@ -19,11 +20,12 @@ public interface ApplicationService { void delete(Long id); ApplicationResponseDTO.Detail getById(Long id, Long currentUserId); Page getByRecruitmentId(Long recruitmentId, Long currentUserId, EvaluationStatus evaluationStatus, String keyword, Pageable pageable); - ApplicationResponseDTO.AdminPageWithStageCounts getByRecruitmentIdForAdmin(Long recruitmentId, AdminStageFilter stage, Pageable pageable, AdminApplicationSortField sortBy, Sort.Direction direction); + ApplicationResponseDTO.AdminPageWithStageCounts getByRecruitmentIdForAdmin(Long recruitmentId, AdminStageFilter stage, Pageable pageable, AdminApplicationSortField sortBy, Sort.Direction direction, List organizationRoleIds, List statuses, String keyword); List updateStatus(ApplicationRequestDTO.UpdateStatus request); void distributeEvaluators(ApplicationEvaluatorRequestDTO.Distribute request); DistributionRequestResponseDTO.Detail distributeEvaluatorsLatestRequest(Long recruitmentId); void updateEvaluators(ApplicationEvaluatorRequestDTO.Update request); boolean toggleAcquaintance(Long applicationId, Long userId); List findTimeslotCandidates(Long recruitmentId, Long timeslotId, String query, boolean excludeCurrent); + List getAllDetailForExcel(Long recruitmentId, AdminStageFilter stage, AdminApplicationSortField sortBy, Sort.Direction direction, List organizationRoleIds, List statuses, String keyword, Long id); } diff --git a/src/main/java/KUSITMS/WITHUS/domain/application/application/service/ApplicationServiceImpl.java b/src/main/java/KUSITMS/WITHUS/domain/application/application/service/ApplicationServiceImpl.java index e089a146..cbcfdcbc 100644 --- a/src/main/java/KUSITMS/WITHUS/domain/application/application/service/ApplicationServiceImpl.java +++ b/src/main/java/KUSITMS/WITHUS/domain/application/application/service/ApplicationServiceImpl.java @@ -46,6 +46,7 @@ import KUSITMS.WITHUS.global.infra.upload.dto.FileResponseDTO; import KUSITMS.WITHUS.global.infra.upload.service.FileUploadService; import lombok.RequiredArgsConstructor; +import org.jetbrains.annotations.NotNull; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; @@ -254,11 +255,75 @@ public ApplicationResponseDTO.AdminPageWithStageCounts getByRecruitmentIdForAdmi AdminStageFilter stage, Pageable pageable, AdminApplicationSortField sortBy, - Sort.Direction direction + Sort.Direction direction, + List organizationRoleIds, + List statuses, + String keyword ) { List allApps = applicationRepository .findByRecruitmentIdAndStatusIn(recruitmentId, stage.toStatusList()); + // POSITION_NAME 필터 + if (organizationRoleIds != null && !organizationRoleIds.isEmpty()) { + allApps = allApps.stream() + .filter(app -> + app.getOrganizationRole() != null && + organizationRoleIds.contains(app.getOrganizationRole().getId()) + ) + .collect(Collectors.toList()); + } + + // STATUS 필터 + if (statuses != null && !statuses.isEmpty()) { + allApps = allApps.stream() + .filter(app -> statuses.contains(app.getStatus())) + .collect(Collectors.toList()); + } + + // NAME KEYWORD 필터 + if (keyword != null && !keyword.trim().isEmpty()) { + String lower = keyword.trim().toLowerCase(Locale.ROOT); + allApps = allApps.stream() + .filter(app -> + app.getName() != null && + app.getName().toLowerCase().contains(lower) + ) + .collect(Collectors.toList()); + } + + List sortedApps = getSortedApps(sortBy, direction, allApps); + + List allDtos = IntStream.range(0, sortedApps.size()) + .mapToObj(i -> ApplicationResponseDTO.SummaryForAdmin.from(sortedApps.get(i), i + 1L)) + .toList(); + + int start = (int) pageable.getOffset(); + int end = Math.min(start + pageable.getPageSize(), allDtos.size()); + List content = start > end + ? List.of() + : allDtos.subList(start, end); + + Page page = new PageImpl<>(content, pageable, allDtos.size()); + + long documentCnt = applicationRepository.countByRecruitmentIdAndStatusIn( + recruitmentId, AdminStageFilter.DOCUMENT.toStatusList()); + long interviewCnt = applicationRepository.countByRecruitmentIdAndStatusIn( + recruitmentId, AdminStageFilter.INTERVIEW.toStatusList()); + long finalPassCnt = applicationRepository.countByRecruitmentIdAndStatusIn( + recruitmentId, AdminStageFilter.FINAL_PASS.toStatusList()); + long failCnt = applicationRepository.countByRecruitmentIdAndStatusIn( + recruitmentId, AdminStageFilter.FAIL.toStatusList()); + + ApplicationResponseDTO.StageCount counts = ApplicationResponseDTO.StageCount.from( + documentCnt, interviewCnt, finalPassCnt, failCnt + ); + + return ApplicationResponseDTO.AdminPageWithStageCounts.from(page, counts); + } + + @NotNull + private static List getSortedApps(AdminApplicationSortField sortBy, Sort.Direction direction, List allApps) { + allApps.sort((a, b) -> { var sa = ApplicationResponseDTO.SummaryForAdmin.from(a, 0L); var sb = ApplicationResponseDTO.SummaryForAdmin.from(b, 0L); @@ -300,6 +365,9 @@ public ApplicationResponseDTO.AdminPageWithStageCounts getByRecruitmentIdForAdmi boolean smsB = Boolean.TRUE.equals(sb.isSmsSent()); cmp = Boolean.compare(smsA, smsB); break; + case LATEST: + cmp = a.getCreatedAt().compareTo(b.getCreatedAt()); + break; case NAME: default: cmp = sa.name().compareToIgnoreCase(sb.name()); @@ -307,34 +375,7 @@ public ApplicationResponseDTO.AdminPageWithStageCounts getByRecruitmentIdForAdmi } return direction.isDescending() ? -cmp : cmp; }); - - - List allDtos = IntStream.range(0, allApps.size()) - .mapToObj(i -> ApplicationResponseDTO.SummaryForAdmin.from(allApps.get(i), i + 1L)) - .toList(); - - int start = (int) pageable.getOffset(); - int end = Math.min(start + pageable.getPageSize(), allDtos.size()); - List content = start > end - ? List.of() - : allDtos.subList(start, end); - - Page page = new PageImpl<>(content, pageable, allDtos.size()); - - long documentCnt = applicationRepository.countByRecruitmentIdAndStatusIn( - recruitmentId, AdminStageFilter.DOCUMENT.toStatusList()); - long interviewCnt = applicationRepository.countByRecruitmentIdAndStatusIn( - recruitmentId, AdminStageFilter.INTERVIEW.toStatusList()); - long finalPassCnt = applicationRepository.countByRecruitmentIdAndStatusIn( - recruitmentId, AdminStageFilter.FINAL_PASS.toStatusList()); - long failCnt = applicationRepository.countByRecruitmentIdAndStatusIn( - recruitmentId, AdminStageFilter.FAIL.toStatusList()); - - ApplicationResponseDTO.StageCount counts = ApplicationResponseDTO.StageCount.from( - documentCnt, interviewCnt, finalPassCnt, failCnt - ); - - return ApplicationResponseDTO.AdminPageWithStageCounts.from(page, counts); + return allApps; } @@ -425,6 +466,99 @@ public List findTimeslotCandidates( return applicationRepository.findEligibleCandidates(recruitmentId, timeslotId, q, excludeCurrent); } + @Override + public List getAllDetailForExcel( + Long recruitmentId, + AdminStageFilter stage, + AdminApplicationSortField sortBy, + Sort.Direction direction, + List organizationRoleIds, + List statuses, + String keyword, + Long currentUserId + ) { + + List apps = applicationRepository.findByRecruitmentIdAndStatusIn( + recruitmentId, stage.toStatusList() + ); + + // POSITION(OrganizationRole) 필터 + if (organizationRoleIds != null && !organizationRoleIds.isEmpty()) { + apps = apps.stream() + .filter(a -> a.getOrganizationRole() != null && + organizationRoleIds.contains(a.getOrganizationRole().getId())) + .collect(Collectors.toList()); + } + + // STATUS 필터 + if (statuses != null && !statuses.isEmpty()) { + apps = apps.stream() + .filter(a -> statuses.contains(a.getStatus())) + .collect(Collectors.toList()); + } + + // KEYWORD (name 검색) + if (keyword != null && !keyword.isBlank()) { + String kw = keyword.trim().toLowerCase(Locale.ROOT); + apps = apps.stream() + .filter(a -> a.getName() != null && a.getName().toLowerCase().contains(kw)) + .collect(Collectors.toList()); + } + + // SORT + apps = getSortedApps(sortBy, direction, apps); + + if (apps.isEmpty()) { + return List.of(); + } + + // 성능 최적화 - bulk 조회 + List appIds = apps.stream() + .map(Application::getId) + .toList(); + + // 면접 가능 시간 전체 조회 + List allAvail = + applicantAvailabilityRepository.findAllByApplicationIdIn(appIds); + + // 평가 전체 조회 + List allEvaluations = + evaluationRepository.findAllByApplicationIdIn(appIds); + + // 평가 기준 (기존 단건 상세 방식과 동일) + // 단건에서는 DOCUMENT 기준만 가져오지만, Detail.from() 내부에서 인터뷰/서류 모두 사용하므로 recruitment.getEvaluationCriteriaList() 그대로 써도 됨. + Recruitment recruitment = apps.isEmpty() ? null : apps.get(0).getRecruitment(); + List criteriaList = + recruitment != null ? recruitment.getEvaluationCriteriaList() : List.of(); + + + // previous, next → 엑셀에서는 불필요하므로 null + Long previous = null; + Long next = null; + + + // Detail DTO 변환 + Map> availMap = + allAvail.stream().collect(Collectors.groupingBy(a -> a.getApplication().getId())); + + Map> evalMap = + allEvaluations.stream().collect(Collectors.groupingBy(e -> e.getApplication().getId())); + + + return apps.stream() + .map(app -> assembler.toDetail( + app, + availMap.getOrDefault(app.getId(), List.of()), + evalMap.getOrDefault(app.getId(), List.of()), + criteriaList, + currentUserId, + previous, + next + )) + .toList(); + } + + /** * PASS/FAIL/HOLD의 간단 상태를 단계와 현재 상태에 맞춰 ApplicationStatus으로 매핑 * @param stage 변경할 단계 (DOCUMENT, INTERVIEW, FINAL_PASS, FAIL) diff --git a/src/main/java/KUSITMS/WITHUS/domain/evaluation/evaluation/repository/EvaluationJpaRepository.java b/src/main/java/KUSITMS/WITHUS/domain/evaluation/evaluation/repository/EvaluationJpaRepository.java index ccdf6266..c6e2b66f 100644 --- a/src/main/java/KUSITMS/WITHUS/domain/evaluation/evaluation/repository/EvaluationJpaRepository.java +++ b/src/main/java/KUSITMS/WITHUS/domain/evaluation/evaluation/repository/EvaluationJpaRepository.java @@ -14,5 +14,6 @@ public interface EvaluationJpaRepository extends JpaRepository List findByApplicationAndUserAndCriteriaIn(Application application, User user, List criterias); long countByApplication_IdAndUser_IdAndCriteria_IdIn(Long applicationId, Long userId, List criteriaIds); long countByApplication_IdAndUser_IdAndCriteria_EvaluationType(Long id, Long userId, EvaluationType evaluationType); + List findAllByApplicationIdIn(List appIds); } diff --git a/src/main/java/KUSITMS/WITHUS/domain/evaluation/evaluation/repository/EvaluationRepository.java b/src/main/java/KUSITMS/WITHUS/domain/evaluation/evaluation/repository/EvaluationRepository.java index b14d94f8..b9d6f360 100644 --- a/src/main/java/KUSITMS/WITHUS/domain/evaluation/evaluation/repository/EvaluationRepository.java +++ b/src/main/java/KUSITMS/WITHUS/domain/evaluation/evaluation/repository/EvaluationRepository.java @@ -18,4 +18,5 @@ public interface EvaluationRepository { long countByApplication_IdAndUser_IdAndCriteria_IdIn(Long applicationId, Long userId, List criteriaIds); long countByApplication_IdAndUser_IdAndCriteria_EvaluationType(Long id, Long userId, EvaluationType evaluationType); void deleteAll(List existingEvaluations); + List findAllByApplicationIdIn(List appIds); } diff --git a/src/main/java/KUSITMS/WITHUS/domain/evaluation/evaluation/repository/EvaluationRepositoryImpl.java b/src/main/java/KUSITMS/WITHUS/domain/evaluation/evaluation/repository/EvaluationRepositoryImpl.java index 45a4f36f..70893322 100644 --- a/src/main/java/KUSITMS/WITHUS/domain/evaluation/evaluation/repository/EvaluationRepositoryImpl.java +++ b/src/main/java/KUSITMS/WITHUS/domain/evaluation/evaluation/repository/EvaluationRepositoryImpl.java @@ -85,4 +85,9 @@ public long countByApplication_IdAndUser_IdAndCriteria_EvaluationType(Long id, L public void deleteAll(List existingEvaluations) { evaluationJpaRepository.deleteAll(existingEvaluations); } + + @Override + public List findAllByApplicationIdIn(List appIds) { + return evaluationJpaRepository.findAllByApplicationIdIn(appIds); + } } diff --git a/src/main/java/KUSITMS/WITHUS/domain/template/controller/TemplateController.java b/src/main/java/KUSITMS/WITHUS/domain/template/controller/TemplateController.java index e98cec0d..bb83ab71 100644 --- a/src/main/java/KUSITMS/WITHUS/domain/template/controller/TemplateController.java +++ b/src/main/java/KUSITMS/WITHUS/domain/template/controller/TemplateController.java @@ -51,5 +51,15 @@ public SuccessResponse create( return SuccessResponse.ok(created); } + @PutMapping("/{templateId}") + @Operation(summary = "문자/메일 템플릿 수정", description = "기존에 등록된 템플릿의 정보를 수정합니다.") + public SuccessResponse update( + @PathVariable Long templateId, + @RequestBody @Valid TemplateRequestDTO.Update dto + ) { + TemplateResponseDTO.Detail updated = templateService.update(templateId, dto); + return SuccessResponse.ok(updated); + } + } diff --git a/src/main/java/KUSITMS/WITHUS/domain/template/dto/TemplateRequestDTO.java b/src/main/java/KUSITMS/WITHUS/domain/template/dto/TemplateRequestDTO.java index baca8a77..6d710f73 100644 --- a/src/main/java/KUSITMS/WITHUS/domain/template/dto/TemplateRequestDTO.java +++ b/src/main/java/KUSITMS/WITHUS/domain/template/dto/TemplateRequestDTO.java @@ -25,4 +25,19 @@ public record Create( @Schema(description = "템플릿 타입", example = "SMS | MAIL") @NotNull Medium medium ) {} + + @Schema(description = "문자/메일 템플릿 수정 요청 DTO") + public record Update( + @Schema(description = "템플릿 이름", example = "면접 일정 안내") + @NotBlank String name, + + @Schema(description = "메일 제목", example = "[WITHUS] 면접 일정 안내") + String subject, + + @Schema(description = "메일 본문", example = "

안녕하세요, {{applicantName}}님!

") + @NotBlank String body, + + @Schema(description = "템플릿 타입", example = "SMS | MAIL") + @NotNull Medium medium + ) {} } diff --git a/src/main/java/KUSITMS/WITHUS/domain/template/entity/Template.java b/src/main/java/KUSITMS/WITHUS/domain/template/entity/Template.java index bf38f9fc..3ad57ca5 100644 --- a/src/main/java/KUSITMS/WITHUS/domain/template/entity/Template.java +++ b/src/main/java/KUSITMS/WITHUS/domain/template/entity/Template.java @@ -45,5 +45,15 @@ public Template(String name, String subject, String body, Medium medium, Organiz this.medium = medium; this.organization = organization; } + + public void update(String name, String subject, String body, Medium medium) { + if(medium == Medium.MAIL && (subject == null || subject.isBlank())) { + throw new CustomException(ErrorCode.EMAIL_SUBJECT_REQUIRED); + } + this.name = name; + this.subject = subject; + this.body = body; + this.medium = medium; + } } diff --git a/src/main/java/KUSITMS/WITHUS/domain/template/service/TemplateService.java b/src/main/java/KUSITMS/WITHUS/domain/template/service/TemplateService.java index a96a8621..3f34c1ff 100644 --- a/src/main/java/KUSITMS/WITHUS/domain/template/service/TemplateService.java +++ b/src/main/java/KUSITMS/WITHUS/domain/template/service/TemplateService.java @@ -11,4 +11,5 @@ public interface TemplateService { TemplateResponseDTO.Detail getById(Long templateId); List listAll(Medium medium, User user); TemplateResponseDTO.Detail create(TemplateRequestDTO.Create dto); -} \ No newline at end of file + TemplateResponseDTO.Detail update(Long templateId, TemplateRequestDTO.Update dto); +} diff --git a/src/main/java/KUSITMS/WITHUS/domain/template/service/TemplateServiceImpl.java b/src/main/java/KUSITMS/WITHUS/domain/template/service/TemplateServiceImpl.java index 5798d719..68c2df41 100644 --- a/src/main/java/KUSITMS/WITHUS/domain/template/service/TemplateServiceImpl.java +++ b/src/main/java/KUSITMS/WITHUS/domain/template/service/TemplateServiceImpl.java @@ -62,4 +62,21 @@ public TemplateResponseDTO.Detail create(TemplateRequestDTO.Create dto) { return TemplateResponseDTO.Detail.from(saved); } + @Override + @Transactional + public TemplateResponseDTO.Detail update(Long templateId, TemplateRequestDTO.Update dto) { + Template template = templateRepository.getById(templateId); + + // TODO: 사용자가 속한 조직의 템플릿만 수정하도록 검증 추가 필요 + + template.update( + dto.name(), + dto.subject(), + dto.body(), + dto.medium() + ); + + return TemplateResponseDTO.Detail.from(template); + } + } diff --git a/src/main/java/KUSITMS/WITHUS/global/util/excel/ExcelExporter.java b/src/main/java/KUSITMS/WITHUS/global/util/excel/ExcelExporter.java new file mode 100644 index 00000000..6ee12416 --- /dev/null +++ b/src/main/java/KUSITMS/WITHUS/global/util/excel/ExcelExporter.java @@ -0,0 +1,51 @@ +package KUSITMS.WITHUS.global.util.excel; + +import KUSITMS.WITHUS.domain.application.application.dto.ApplicationResponseDTO; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; + +import java.util.List; + +public class ExcelExporter { + + public static Workbook createExcel(List list) { + Workbook workbook = new XSSFWorkbook(); + Sheet sheet = workbook.createSheet("지원서 상세"); + + Row header = sheet.createRow(0); + String[] columns = { + "ID", "이름", "이메일", "전화번호", "성별", "전공", + "지원 분야", "합불 상태", + "서류 평균", "면접 평균", + "주소", "생년월일", "학교", + "지인 수" + }; + + for (int i = 0; i < columns.length; i++) { + header.createCell(i).setCellValue(columns[i]); + } + + int rowIdx = 1; + for (var dto : list) { + Row row = sheet.createRow(rowIdx++); + + row.createCell(0).setCellValue(dto.id()); + row.createCell(1).setCellValue(dto.name()); + row.createCell(2).setCellValue(dto.email()); + row.createCell(3).setCellValue(dto.phoneNumber()); + row.createCell(4).setCellValue(dto.gender() != null ? dto.gender().name() : ""); + row.createCell(5).setCellValue(dto.major()); + row.createCell(6).setCellValue(dto.appliedPosition()); + row.createCell(7).setCellValue(dto.status().name()); + row.createCell(8).setCellValue(dto.documentAverageScore()); + row.createCell(9).setCellValue(dto.interviewAverageScore()); + row.createCell(10).setCellValue(dto.address()); + row.createCell(11).setCellValue(dto.birthDate() != null ? dto.birthDate().toString() : ""); + row.createCell(12).setCellValue(dto.university()); + row.createCell(13).setCellValue(dto.acquaintanceCount()); + } + + return workbook; + } + +}