Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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 @@ -2,6 +2,7 @@

import dmu.dasom.api.domain.applicant.entity.Applicant;
import dmu.dasom.api.domain.applicant.enums.ApplicantStatus;
import io.lettuce.core.dynamic.annotation.Param;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
Expand All @@ -20,4 +21,8 @@ public interface ApplicantRepository extends JpaRepository<Applicant, Long> {

Optional<Applicant> findByStudentNo(final String studentNo);

@Query("SELECT a FROM Applicant a WHERE a.studentNo = :studentNo AND a.contact LIKE %:contactLastDigits")
Optional<Applicant> findByStudentNoAndContactEndsWith(@Param("studentNo") String studentNo,
@Param("contactLastDigits") String contactLastDigits);

}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,14 @@ public enum ErrorCode {
INVALID_TIME_FORMAT(400, "C017", "시간 형식이 올바르지 않습니다."),
INVALID_INQUIRY_PERIOD(400, "C018", "조회 기간이 아닙니다."),
SHEET_WRITE_FAIL(400, "C019", "시트에 데이터를 쓰는데 실패하였습니다."),
SHEET_READ_FAIL(400, "C200", "시트에 데이터를 쓰는데 실패하였습니다."),
SHEET_READ_FAIL(400, "C200", "시트에 데이터를 읽는데 실패하였습니다."),
SLOT_NOT_FOUND(400, "C021", "슬롯을 찾을 수 없습니다."),
APPLICANT_NOT_FOUND(400, "C022", "지원자를 찾을 수 없습니다."),
ALREADY_RESERVED(400, "C023", "이미 예약된 지원자입니다."),
RESERVED_SLOT_CANNOT_BE_DELETED(400, "C024", "예약된 슬롯은 삭제할 수 없습니다."),
SLOT_FULL(400, "C025", "해당 슬롯이 가득 찼습니다."),
RESERVATION_NOT_FOUND(400, "C026", "예약을 찾을 수 없습니다."),
SLOT_NOT_ACTIVE(400, "C027", "해당 슬롯이 비활성화 되었습니다."),
FILE_ENCODE_FAIL(400, "C028", "파일 인코딩에 실패하였습니다.")
;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package dmu.dasom.api.domain.interview.dto;

import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import lombok.*;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class InterviewReservationRequestDto {

@NotNull(message = "슬롯 ID는 필수 값입니다.")
private Long slotId; // 예약할 슬롯 ID

@NotNull(message = "예약 코드는 필수 값입니다.")
@Pattern(regexp = "^[0-9]{8}[0-9]{4}$", message = "예약 코드는 학번 전체와 전화번호 뒤 4자리로 구성되어야 합니다.")
private String reservationCode; // 학번 전체 + 전화번호 뒤 4자리 조합 코드
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package dmu.dasom.api.domain.interview.dto;

import jakarta.validation.constraints.NotNull;
import lombok.*;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class InterviewReservationResponseDto {

@NotNull(message = "예약 ID는 필수 값입니다.")
private Long reservationId; // 예약 ID

@NotNull(message = "슬롯 ID는 필수 값입니다.")
private Long slotId; // 슬롯 ID

@NotNull(message = "지원자 ID는 필수 값입니다.")
private Long applicantId; // 지원자 ID

@NotNull(message = "예약 코드는 필수 값입니다.")
private String reservationCode; // 예약 코드 (학번+전화번호 조합)

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package dmu.dasom.api.domain.interview.dto;

import jakarta.validation.constraints.NotNull;
import lombok.*;

import java.time.LocalDate;
import java.time.LocalTime;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class InterviewSlotCreateRequestDto {

@NotNull(message = "시작 날짜는 필수 값입니다.")
private LocalDate startDate; // 면접 시작 날짜

@NotNull(message = "종료 날짜는 필수 값입니다.")
private LocalDate endDate; // 면접 종료 날짜

@NotNull(message = "시작 시간은 필수 값입니다.")
private LocalTime startTime; // 하루의 시작 시간

@NotNull(message = "종료 시간은 필수 값입니다.")
private LocalTime endTime; // 하루의 종료 시간
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package dmu.dasom.api.domain.interview.dto;

import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import lombok.*;

import java.time.LocalDate;
import java.time.LocalTime;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class InterviewSlotRequestDto {

@NotNull(message = "면접 날짜는 필수 입력 값입니다.")
private LocalDate interviewDate; // 면접 날짜

@NotNull(message = "시작 시간은 필수 입력 값입니다.")
private LocalTime startTime; // 시작 시간

@NotNull(message = "종료 시간은 필수 입력 값입니다.")
private LocalTime endTime; // 종료 시간

@NotNull(message = "최대 지원자 수는 필수 입력 값입니다.")
@Min(value = 1, message = "최대 지원자 수는 최소 1명 이상이어야 합니다.")
@Max(value = 100, message = "최대 지원자 수는 최대 100명까지 가능합니다.") // 필요에 따라 수정 가능
private Integer maxCandidates; // 최대 지원자 수
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package dmu.dasom.api.domain.interview.dto;

import dmu.dasom.api.domain.interview.entity.InterviewSlot;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import lombok.*;

import java.time.LocalDate;
import java.time.LocalTime;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class InterviewSlotResponseDto {

@NotNull(message = "슬롯 ID는 필수 입력 값입니다.")
private Long id; // 슬롯 ID

@NotNull(message = "면접 날짜는 필수 입력 값입니다.")
private LocalDate interviewDate; // 면접 날짜

@NotNull(message = "시작 시간은 필수 입력 값입니다.")
private LocalTime startTime; // 시작 시간

@NotNull(message = "종료 시간은 필수 입력 값입니다.")
private LocalTime endTime; // 종료 시간

@NotNull(message = "최대 지원자 수는 필수 입력 값입니다.")
@Min(value = 1, message = "최대 지원자 수는 최소 1명 이상이어야 합니다.")
@Max(value = 100, message = "최대 지원자 수는 최대 100명까지 가능합니다.")
private Integer maxCandidates; // 최대 지원자 수

@NotNull(message = "현재 예약된 지원자 수는 필수 입력 값입니다.")
@Min(value = 0, message = "현재 예약된 지원자 수는 0명 이상이어야 합니다.")
private Integer currentCandidates; // 현재 예약된 지원자 수

public InterviewSlotResponseDto(InterviewSlot slot){
this.id = slot.getId();
this.interviewDate = slot.getInterviewDate();
this.startTime = slot.getStartTime();
this.endTime = slot.getEndTime();
this.maxCandidates = slot.getMaxCandidates();
this.currentCandidates = slot.getCurrentCandidates();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package dmu.dasom.api.domain.interview.entity;

import dmu.dasom.api.domain.applicant.entity.Applicant;
import jakarta.persistence.*;
import lombok.*;

@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class InterviewReservation {

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

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "slot_id", nullable = false)
private InterviewSlot slot; // 연관된 면접 슬롯

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "applicant_id", nullable = false)
private Applicant applicant; // 지원자

@Column(nullable = false, unique = true, length = 12)
private String reservationCode; // 학번 전체 + 전화번호 뒤 4자리 조합 코드
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package dmu.dasom.api.domain.interview.entity;

import dmu.dasom.api.domain.interview.enums.InterviewStatus;
import jakarta.persistence.*;
import lombok.*;

import java.time.LocalDate;
import java.time.LocalTime;

@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class InterviewSlot {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false)
private LocalDate interviewDate;

@Column(nullable = false)
private LocalTime startTime;

@Column(nullable = false)
private LocalTime endTime; // 종료 시간

@Column(nullable = false)
private Integer maxCandidates; // 최대 지원자 수

@Column(nullable = false)
private Integer currentCandidates; // 현재 예약된 지원자 수

@Enumerated(EnumType.STRING)
@Column(nullable = false, length = 16)
private InterviewStatus interviewStatus; // 면접 슬롯 상태 (ACTIVE, INACTIVE, CLOSED)

public void incrementCurrentCandidates() {
this.currentCandidates++;
if (this.currentCandidates >= this.maxCandidates) {
this.interviewStatus = InterviewStatus.CLOSED; // 최대 지원자 수에 도달하면 상태 변경
}
}

public void decrementCurrentCandidates() {
this.currentCandidates--;
if (interviewStatus == InterviewStatus.CLOSED && this.currentCandidates < this.maxCandidates) {
this.interviewStatus = InterviewStatus.ACTIVE; // 지원자 수가 줄어들면 다시 활성화
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package dmu.dasom.api.domain.interview.enums;

public enum InterviewStatus {
ACTIVE, // 활성화된 슬롯
INACTIVE, // 비활성화된 슬롯
CLOSED // 예약 마감된 슬롯
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package dmu.dasom.api.domain.interview.repositoty;

import dmu.dasom.api.domain.interview.entity.InterviewReservation;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface InterviewReservationRepository extends JpaRepository<InterviewReservation, Long> {
boolean existsByReservationCode(String reservationCode);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package dmu.dasom.api.domain.interview.repositoty;

import dmu.dasom.api.domain.interview.entity.InterviewSlot;
import dmu.dasom.api.domain.interview.enums.InterviewStatus;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;

import java.util.Collection;
import java.util.List;

@Repository
public interface InterviewSlotRepository extends JpaRepository<InterviewSlot, Long> {
// 현재 인원이 최대 인원보다 작은 슬롯 조회
// 현재 예약된 인원이 최대 지원자 수보다 적은 슬롯 조회
@Query("SELECT s FROM InterviewSlot s WHERE s.currentCandidates < s.maxCandidates")
Collection<InterviewSlot> findAllByCurrentCandidatesLessThanMaxCandidates();

// 상태에 따른 슬롯 조회
@Query("SELECT s FROM InterviewSlot s WHERE s.interviewStatus = :status AND s.currentCandidates < s.maxCandidates")
List<InterviewSlot> findAllByStatusAndCurrentCandidatesLessThanMaxCandidates(InterviewStatus interviewStatus);

// 슬롯이 하나라도 존재하는지 확인
@Query("SELECT COUNT(s) > 0 FROM InterviewSlot s")
boolean existsAny();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package dmu.dasom.api.domain.interview.service;

import dmu.dasom.api.domain.interview.dto.InterviewReservationRequestDto;
import dmu.dasom.api.domain.interview.dto.InterviewSlotResponseDto;

import java.time.LocalDate;
import java.time.LocalTime;
import java.util.List;


public interface InterviewService {

// 면접 슬롯 생성
List<InterviewSlotResponseDto> createInterviewSlots(LocalDate newStartDate, LocalDate newEndDate, LocalTime newStartTime, LocalTime newEndTime);

// 예약 가능한 면접 슬롯 조회
List<InterviewSlotResponseDto> getAvailableSlots();

// 면접 예약
void reserveInterviewSlot(InterviewReservationRequestDto request);

// 면접 예약 취소
void cancelReservation(Long reservationId, Long applicantId);


}
Loading