diff --git a/src/main/java/ceos/backend/domain/application/ApplicationController.java b/src/main/java/ceos/backend/domain/application/ApplicationController.java index f7a0f9c..cf32828 100644 --- a/src/main/java/ceos/backend/domain/application/ApplicationController.java +++ b/src/main/java/ceos/backend/domain/application/ApplicationController.java @@ -1,3 +1,4 @@ + package ceos.backend.domain.application; @@ -7,12 +8,7 @@ import ceos.backend.domain.application.dto.request.UpdateAttendanceRequest; import ceos.backend.domain.application.dto.request.UpdateInterviewTime; import ceos.backend.domain.application.dto.request.UpdatePassStatus; -import ceos.backend.domain.application.dto.response.GetApplication; -import ceos.backend.domain.application.dto.response.GetApplicationQuestion; -import ceos.backend.domain.application.dto.response.GetApplications; -import ceos.backend.domain.application.dto.response.GetCreationTime; -import ceos.backend.domain.application.dto.response.GetInterviewTime; -import ceos.backend.domain.application.dto.response.GetResultResponse; +import ceos.backend.domain.application.dto.response.*; import ceos.backend.domain.application.service.ApplicationExcelService; import ceos.backend.domain.application.service.ApplicationService; import ceos.backend.global.common.entity.Part; @@ -147,6 +143,13 @@ public void updateDocumentPassStatus( applicationService.updateDocumentPassStatus(applicationId, updatePassStatus); } + @Operation(summary = "면접 참여 가능 여부 확인", description = "resultDateDoc ~ resultDateFinal 전날") + @GetMapping(value = "/{applicationId}/interview/availability") + public GetInterviewAvailability getInterviewAvailability(@PathVariable("applicationId") Long applicationId) { + log.info("면접 참여 가능 여부 확인"); + return applicationService.getInterviewAvailability(applicationId); + } + @Operation(summary = "최종 합격 여부 변경", description = "resultDateDoc ~ ResultDateFinal 전날") @PatchMapping(value = "/{applicationId}/final") public void updateFinalPassStatus( @@ -156,6 +159,13 @@ public void updateFinalPassStatus( applicationService.updateFinalPassStatus(applicationId, updatePassStatus); } + @Operation(summary = "활동 가능 여부 확인", description = "resultDateDoc ~ resultDateFinal 전날") + @GetMapping(value = "/{applicationId}/final/availability") + public GetFinalAvailability getFinalPass(@PathVariable("applicationId") Long applicationId) { + log.info("활동 가능 여부 확인"); + return applicationService.getFinalAvailability(applicationId); + } + @Operation(summary = "지원서 엑셀 파일 생성") @GetMapping(value = "/file/create") public GetCreationTime createApplicationExcel() { diff --git a/src/main/java/ceos/backend/domain/application/domain/Application.java b/src/main/java/ceos/backend/domain/application/domain/Application.java index a08fc62..3f60165 100644 --- a/src/main/java/ceos/backend/domain/application/domain/Application.java +++ b/src/main/java/ceos/backend/domain/application/domain/Application.java @@ -8,6 +8,8 @@ import jakarta.validation.constraints.NotNull; import java.util.ArrayList; import java.util.List; + +import jakarta.validation.constraints.Size; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; @@ -36,17 +38,20 @@ public class Application extends BaseEntity { @ColumnDefault("false") private boolean interviewCheck; + @Size(max = 100) + private String unableReason; + @NotNull @Enumerated(EnumType.STRING) private Pass documentPass; @NotNull @ColumnDefault("false") - private boolean finalCheck; + private boolean finalCheck; // 활동 가능 여부 @NotNull @Enumerated(EnumType.STRING) - private Pass finalPass; + private Pass finalPass; // 최종 합격 결과 @OneToMany(mappedBy = "application", cascade = CascadeType.ALL) private List applicationAnswers = new ArrayList<>(); @@ -85,6 +90,8 @@ public void updateInterviewCheck(boolean check) { this.interviewCheck = check; } + public void updateUnableReason(String reason) { this.unableReason = reason; } + public void updateFinalCheck(boolean check) { this.finalCheck = check; } diff --git a/src/main/java/ceos/backend/domain/application/dto/response/GetFinalAvailability.java b/src/main/java/ceos/backend/domain/application/dto/response/GetFinalAvailability.java new file mode 100644 index 0000000..aec81cd --- /dev/null +++ b/src/main/java/ceos/backend/domain/application/dto/response/GetFinalAvailability.java @@ -0,0 +1,26 @@ +package ceos.backend.domain.application.dto.response; + +import ceos.backend.domain.application.domain.Application; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class GetFinalAvailability { + private boolean finalAvailability; // 활동 가능 여부 + private String reason; // 활동 불가능 사유 + + @Builder + public GetFinalAvailability(boolean finalCheck, String reason) { + this.finalAvailability = finalCheck; + this.reason = reason; + } + + public static GetFinalAvailability of(Application application) { + return GetFinalAvailability.builder() + .finalAvailability(application.isFinalCheck()) + .reason(application.getUnableReason()) + .build(); + } + +} diff --git a/src/main/java/ceos/backend/domain/application/dto/response/GetInterviewAvailability.java b/src/main/java/ceos/backend/domain/application/dto/response/GetInterviewAvailability.java new file mode 100644 index 0000000..8ff1610 --- /dev/null +++ b/src/main/java/ceos/backend/domain/application/dto/response/GetInterviewAvailability.java @@ -0,0 +1,25 @@ +package ceos.backend.domain.application.dto.response; + +import ceos.backend.domain.application.domain.Application; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class GetInterviewAvailability { + private boolean interviewAvailability; // 참여 가능 여부 + private String reason; // 참여 불가능 사유 + + @Builder + public GetInterviewAvailability(boolean interviewCheck, String reason) { + this.interviewAvailability = interviewCheck; + this.reason = reason; + } + + public static GetInterviewAvailability of(Application application) { + return GetInterviewAvailability.builder() + .interviewAvailability(application.isInterviewCheck()) + .reason(application.getUnableReason()) + .build(); + } +} diff --git a/src/main/java/ceos/backend/domain/application/service/ApplicationService.java b/src/main/java/ceos/backend/domain/application/service/ApplicationService.java index ac9d66a..5d421d7 100644 --- a/src/main/java/ceos/backend/domain/application/service/ApplicationService.java +++ b/src/main/java/ceos/backend/domain/application/service/ApplicationService.java @@ -13,11 +13,7 @@ import ceos.backend.domain.application.dto.request.UpdateAttendanceRequest; import ceos.backend.domain.application.dto.request.UpdateInterviewTime; import ceos.backend.domain.application.dto.request.UpdatePassStatus; -import ceos.backend.domain.application.dto.response.GetApplication; -import ceos.backend.domain.application.dto.response.GetApplicationQuestion; -import ceos.backend.domain.application.dto.response.GetApplications; -import ceos.backend.domain.application.dto.response.GetInterviewTime; -import ceos.backend.domain.application.dto.response.GetResultResponse; +import ceos.backend.domain.application.dto.response.*; import ceos.backend.domain.application.exception.exceptions.NotDeletableDuringRecruitment; import ceos.backend.domain.application.helper.ApplicationHelper; import ceos.backend.domain.application.mapper.ApplicationMapper; @@ -168,6 +164,7 @@ public void updateInterviewAttendance( application.updateInterviewCheck(true); applicationRepository.save(application); } else { + application.updateUnableReason(request.getReason()); applicationHelper.sendSlackUnableReasonMessage(application, request, false); } } @@ -196,6 +193,7 @@ public void updateParticipationAvailability( application.updateFinalCheck(true); applicationRepository.save(application); } else { + application.updateUnableReason(request.getReason()); applicationHelper.sendSlackUnableReasonMessage(application, request, true); } } @@ -249,6 +247,16 @@ public void updateInterviewTime(Long applicationId, UpdateInterviewTime updateIn application.updateInterviewTime(duration); } + @Transactional(readOnly = true) + public GetInterviewAvailability getInterviewAvailability(Long applicationId) { + applicationValidator.validateExistingApplicant(applicationId); // 유저 검증 + final Application application = applicationHelper.getApplicationById(applicationId); + applicationValidator.validateDocumentPassStatus(application); // 서류 통과 검증 + + return GetInterviewAvailability.of(application); + } + + @Transactional public void updateDocumentPassStatus(Long applicationId, UpdatePassStatus updatePassStatus) { recruitmentValidator.validateBetweenStartDateDocAndResultDateDoc(); // 기간 검증 @@ -268,6 +276,16 @@ public void updateFinalPassStatus(Long applicationId, UpdatePassStatus updatePas application.updateFinalPass(updatePassStatus.getPass()); } + @Transactional(readOnly = true) + public GetFinalAvailability getFinalAvailability(Long applicationId) { + applicationValidator.validateExistingApplicant(applicationId); // 유저 검증 + final Application application = applicationHelper.getApplicationById(applicationId); + applicationValidator.validateFinalPassStatus(application); // 최종 합격 검증 + + return GetFinalAvailability.of(application); + } + + @Transactional public void deleteAllApplications() { Recruitment recruitment = recruitmentHelper.takeRecruitment(); diff --git a/src/main/java/ceos/backend/domain/application/validator/ApplicationValidator.java b/src/main/java/ceos/backend/domain/application/validator/ApplicationValidator.java index 1590793..efa6e67 100644 --- a/src/main/java/ceos/backend/domain/application/validator/ApplicationValidator.java +++ b/src/main/java/ceos/backend/domain/application/validator/ApplicationValidator.java @@ -70,6 +70,10 @@ public void validateDocumentPassStatus(Application application) { application.validateDocumentPass(); } + public void validateFinalPassStatus(Application application) { + application.validateFinalPass(); + } + public void validateInterviewTime(List interviews, String interviewTime) { if (interviews.stream() .noneMatch( diff --git a/src/main/java/ceos/backend/domain/startup/StartupController.java b/src/main/java/ceos/backend/domain/startup/StartupController.java new file mode 100644 index 0000000..126bbd0 --- /dev/null +++ b/src/main/java/ceos/backend/domain/startup/StartupController.java @@ -0,0 +1,68 @@ +package ceos.backend.domain.startup; + +import ceos.backend.domain.startup.dto.request.StartupRequest; +import ceos.backend.domain.startup.dto.response.StartupResponse; +import ceos.backend.domain.startup.dto.response.StartupsResponse; +import ceos.backend.domain.startup.service.StartupService; +import ceos.backend.global.common.dto.AwsS3Url; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping(value = "/start-ups") +@Tag(name = "Start-Up") +public class StartupController { + + private final StartupService startupService; + + @Operation(summary = "창업 리스트 추가") + @PostMapping + public void createStartup(@RequestBody @Valid StartupRequest startupRequest) { + log.info("창업 리스트 추가"); + startupService.createStartup(startupRequest); + } + + @Operation(summary = "창업 리스트 보기") + @GetMapping + public StartupsResponse getStartups( + @RequestParam("pageNum") Integer pageNum, @RequestParam("limit") Integer limit) { + log.info("창업 리스트 보기"); + return startupService.getStartups(pageNum, limit); + } + + @Operation(summary = "창업 리스트 상세 보기") + @GetMapping(value = "/{startupId}") + public StartupResponse getStartup(@PathVariable("startupId") Long startupId) { + log.info("창업 리스트 상세 보기"); + return startupService.getStartup(startupId); + } + + @Operation(summary = "창업 리스트 수정") + @PatchMapping(value = "/{startupId}") + public StartupResponse updateStartup(@PathVariable("startupId") Long startupId, + @RequestBody @Valid StartupRequest startupRequest) { + log.info("창업 리스트 수정"); + return startupService.updateStartup(startupId, startupRequest); + } + + @Operation(summary = "창업 리스트 삭제") + @DeleteMapping("/{startupId}") + public void deleteManagement(@PathVariable(name = "startupId") Long startupId) { + log.info("창업 리스트 삭제"); + startupService.deleteStartup(startupId); + } + + @Operation(summary = "서비스 이미지 url 생성하기") + @GetMapping("/image") + public AwsS3Url getImageUrl() { + log.info("서비스 이미지 url 생성하기"); + return startupService.getImageUrl(); + } + +} diff --git a/src/main/java/ceos/backend/domain/startup/domain/Startup.java b/src/main/java/ceos/backend/domain/startup/domain/Startup.java new file mode 100644 index 0000000..73e16c1 --- /dev/null +++ b/src/main/java/ceos/backend/domain/startup/domain/Startup.java @@ -0,0 +1,58 @@ +package ceos.backend.domain.startup.domain; + +import ceos.backend.domain.startup.dto.request.StartupRequest; +import jakarta.persistence.*; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Entity +public class Startup { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "startup_id") + private Long id; + + @NotBlank + private String serviceName; + + private String companyName; + + @NotBlank + private String imageUrl; + + @NotBlank + private String serviceUrl; + + @NotNull + private Integer generation; + + @NotBlank + private String founder; + + @Builder + public Startup(String serviceName, String companyName, String imageUrl, String serviceUrl, Integer generation, String founder) { + this.serviceName = serviceName; + this.companyName = companyName; + this.imageUrl = imageUrl; + this.serviceUrl = serviceUrl; + this.generation = generation; + this.founder = founder; + } + + public void update(StartupRequest startupRequest) { + this.serviceName = startupRequest.getServiceName(); + this.companyName = startupRequest.getCompanyName(); + this.imageUrl = startupRequest.getImageUrl(); + this.serviceUrl = startupRequest.getServiceUrl(); + this.generation = startupRequest.getGeneration(); + this.founder = startupRequest.getFounder(); + } + +} diff --git a/src/main/java/ceos/backend/domain/startup/dto/request/StartupRequest.java b/src/main/java/ceos/backend/domain/startup/dto/request/StartupRequest.java new file mode 100644 index 0000000..54779c2 --- /dev/null +++ b/src/main/java/ceos/backend/domain/startup/dto/request/StartupRequest.java @@ -0,0 +1,46 @@ +package ceos.backend.domain.startup.dto.request; + +import ceos.backend.domain.startup.domain.Startup; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; + +@Getter +public class StartupRequest { + + @NotBlank + @Schema(defaultValue = "Repick",description = "서비스명") + private String serviceName; + + @Schema(defaultValue = "(주)Repick",description = "회사명(생략가능)") + private String companyName; + + @NotBlank + @Schema(description = "서비스 이미지 url") + private String imageUrl; + + @NotBlank + @Schema(description = "서비스 url") + private String serviceUrl; + + @NotNull + @Schema(defaultValue = "17", description = "창업자 활동 기수") + private Integer generation; + + @NotBlank + @Schema(defaultValue = "이도현", description = "창업자 이름") + private String founder; + + public Startup toEntity() { + return Startup.builder() + .serviceName(serviceName) + .companyName(companyName) + .imageUrl(imageUrl) + .serviceUrl(serviceUrl) + .generation(generation) + .founder(founder) + .build(); + } + +} diff --git a/src/main/java/ceos/backend/domain/startup/dto/response/StartupResponse.java b/src/main/java/ceos/backend/domain/startup/dto/response/StartupResponse.java new file mode 100644 index 0000000..51bf04b --- /dev/null +++ b/src/main/java/ceos/backend/domain/startup/dto/response/StartupResponse.java @@ -0,0 +1,47 @@ +package ceos.backend.domain.startup.dto.response; + +import ceos.backend.domain.startup.domain.Startup; +import lombok.Builder; +import lombok.Getter; + +@Getter +public class StartupResponse { + + private Long startupId; + + private String serviceName; + + private String companyName; + + private String imageUrl; + + private String serviceUrl; + + private Integer generation; + + private String founder; + + @Builder + public StartupResponse(Long startupId, String serviceName, String companyName, String imageUrl, String serviceUrl, Integer generation, String founder) { + this.startupId = startupId; + this.serviceName = serviceName; + this.companyName = companyName; + this.imageUrl = imageUrl; + this.serviceUrl = serviceUrl; + this.generation = generation; + this.founder = founder; + } + + public static StartupResponse fromEntity(Startup startup) { + return StartupResponse.builder() + .startupId(startup.getId()) + .serviceName(startup.getServiceName()) + .companyName(startup.getCompanyName()) + .imageUrl(startup.getImageUrl()) + .serviceUrl(startup.getServiceUrl()) + .generation(startup.getGeneration()) + .founder(startup.getFounder()) + .build(); + } + +} diff --git a/src/main/java/ceos/backend/domain/startup/dto/response/StartupsResponse.java b/src/main/java/ceos/backend/domain/startup/dto/response/StartupsResponse.java new file mode 100644 index 0000000..c2ad7e4 --- /dev/null +++ b/src/main/java/ceos/backend/domain/startup/dto/response/StartupsResponse.java @@ -0,0 +1,30 @@ +package ceos.backend.domain.startup.dto.response; + +import ceos.backend.domain.startup.domain.Startup; +import ceos.backend.global.common.dto.PageInfo; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.data.domain.Page; + +import java.util.List; + +@Getter +@AllArgsConstructor +public class StartupsResponse { + + public List startups; + public PageInfo pageInfo; + + public static StartupsResponse fromPageable(Page startups) { + return new StartupsResponse( + startups.map(StartupResponse::fromEntity).toList(), + PageInfo.of( + startups.getNumber(), + startups.getSize(), + startups.getTotalPages(), + startups.getTotalElements() + ) + ); + } + +} diff --git a/src/main/java/ceos/backend/domain/startup/exception/StartupErrorCode.java b/src/main/java/ceos/backend/domain/startup/exception/StartupErrorCode.java new file mode 100644 index 0000000..c439363 --- /dev/null +++ b/src/main/java/ceos/backend/domain/startup/exception/StartupErrorCode.java @@ -0,0 +1,26 @@ +package ceos.backend.domain.startup.exception; + +import ceos.backend.global.common.dto.ErrorReason; +import ceos.backend.global.error.BaseErrorCode; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +import static org.springframework.http.HttpStatus.BAD_REQUEST; + +@Getter +@AllArgsConstructor +public enum StartupErrorCode implements BaseErrorCode { + + STARTUP_NOT_FOUND(BAD_REQUEST, "STARTUP_404_1", "존재하지 않는 창업 서비스입니다."); + + private HttpStatus status; + private String code; + private String reason; + + @Override + public ErrorReason getErrorReason() { + return ErrorReason.of(status.value(), code, reason); + } + +} diff --git a/src/main/java/ceos/backend/domain/startup/exception/StartupNotFound.java b/src/main/java/ceos/backend/domain/startup/exception/StartupNotFound.java new file mode 100644 index 0000000..33ff73f --- /dev/null +++ b/src/main/java/ceos/backend/domain/startup/exception/StartupNotFound.java @@ -0,0 +1,14 @@ +package ceos.backend.domain.startup.exception; + + +import ceos.backend.global.error.BaseErrorException; + +public class StartupNotFound extends BaseErrorException { + + public static final StartupNotFound EXCEPTION = new StartupNotFound(); + + public StartupNotFound() { + super(StartupErrorCode.STARTUP_NOT_FOUND); + } + +} diff --git a/src/main/java/ceos/backend/domain/startup/repository/StartupRepository.java b/src/main/java/ceos/backend/domain/startup/repository/StartupRepository.java new file mode 100644 index 0000000..3e3e9e4 --- /dev/null +++ b/src/main/java/ceos/backend/domain/startup/repository/StartupRepository.java @@ -0,0 +1,8 @@ +package ceos.backend.domain.startup.repository; + +import ceos.backend.domain.startup.domain.Startup; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface StartupRepository extends JpaRepository { + +} diff --git a/src/main/java/ceos/backend/domain/startup/service/StartupService.java b/src/main/java/ceos/backend/domain/startup/service/StartupService.java new file mode 100644 index 0000000..30434dc --- /dev/null +++ b/src/main/java/ceos/backend/domain/startup/service/StartupService.java @@ -0,0 +1,72 @@ +package ceos.backend.domain.startup.service; + +import ceos.backend.domain.startup.domain.Startup; +import ceos.backend.domain.startup.dto.request.StartupRequest; +import ceos.backend.domain.startup.dto.response.StartupResponse; +import ceos.backend.domain.startup.dto.response.StartupsResponse; +import ceos.backend.domain.startup.exception.StartupNotFound; +import ceos.backend.domain.startup.repository.StartupRepository; +import ceos.backend.global.common.dto.AwsS3Url; +import ceos.backend.infra.s3.AwsS3UrlHandler; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class StartupService { + + private final StartupRepository startupRepository; + private final AwsS3UrlHandler awsS3UrlHandler; + + @Transactional + public void createStartup(StartupRequest startupRequest) { + startupRepository.save(startupRequest.toEntity()); + } + + @Transactional(readOnly = true) + public StartupsResponse getStartups(Integer pageNum, Integer limit) { + PageRequest pageRequest = PageRequest.of(pageNum, limit, Sort.by("id").descending()); + + Page startups = startupRepository.findAll(pageRequest); + + return StartupsResponse.fromPageable(startups); + } + + @Transactional(readOnly = true) + public StartupResponse getStartup(Long startupId) { + Startup startup = startupRepository.findById(startupId).orElseThrow( + () -> StartupNotFound.EXCEPTION + ); + + return StartupResponse.fromEntity(startup); + } + + @Transactional + public StartupResponse updateStartup(Long startupId, StartupRequest startupRequest) { + Startup startup = startupRepository.findById(startupId).orElseThrow( + () -> StartupNotFound.EXCEPTION + ); + + startup.update(startupRequest); + return StartupResponse.fromEntity(startup); + } + + @Transactional + public void deleteStartup(Long startupId) { + Startup startup = startupRepository.findById(startupId).orElseThrow( + () -> StartupNotFound.EXCEPTION + ); + + startupRepository.delete(startup); + } + + @Transactional(readOnly = true) + public AwsS3Url getImageUrl() { + return awsS3UrlHandler.handle("startups"); + } + +} diff --git a/src/main/java/ceos/backend/global/config/WebSecurityConfig.java b/src/main/java/ceos/backend/global/config/WebSecurityConfig.java index f9948be..c68a0e1 100644 --- a/src/main/java/ceos/backend/global/config/WebSecurityConfig.java +++ b/src/main/java/ceos/backend/global/config/WebSecurityConfig.java @@ -80,7 +80,8 @@ public class WebSecurityConfig { "/awards/**", "/managements/**", "/faq/**", - "/sponsors/**" + "/sponsors/**", + "/start-ups/**" }; private final String[] GetPermittedPatterns = { @@ -94,7 +95,8 @@ public class WebSecurityConfig { "/applications/question", "/applications/document", "/applications/final", - "/admin/reissue" + "/admin/reissue", + "/start-ups/**" }; private final String[] PostPermittedPatterns = {"/applications"};