Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
30 changes: 30 additions & 0 deletions docs/step4요구사항.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# 변경된 기능 요구사항 정리

## 1 강의 상태 분리
### 기존
- 강의 진행 상태와 모집 상태가 결합되어 있음
- 모집중 상태일 때만 수강신청 가능

### 변경점
- [ ] 강의 진행 상태(ProgressStatus): 준비중, 진행중, 종료
- [ ] 모집 상태(RecruitmentStatus): 비모집중, 모집중
- [ ] 강의가 진행중 이여도 모집중이라면 수강신청 가능

## 2 강의 커버 이미지 여러개
### 기존
- Session은 하나의 SessionImage만 가짐
- session테이블에 image_id가 FK 존재

### 변경점
- [ ] Session은 하나 이상의 커버 이미지를 가질 수 있음
- [ ] DB에 데이터가 존재한다는 가정하에 진행해야 하므로 session테이블의 image_id는 대표이미지로 변경
- [ ] 등록되는 여러개의 이미지는 별도 테이블로 추가로 관리해야할듯

## 3 수강 승인기능 추가
### 기존
- 모집중이고 결제금액과 수강료가 일치하고 최대 수강인원을 넘지 않으면 수강 가능

### 변경점
- [ ] 수강신청을 받으면 대기 상태가 되고
- [ ] 강사가 선발된 인원에 대해 승인 (승인된 인원만 수강 가능)
- [ ] 강사가 선발되지 않은 인원 수강 취소
40 changes: 29 additions & 11 deletions src/main/java/nextstep/courses/domain/session/Enrollment.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,28 @@
import nextstep.payments.domain.Payment;

import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class Enrollment {
private final Long sessionId;
private final SessionStatus status;
private final RecruitmentStatus recruitmentStatus;
private final SessionType sessionType;
private final List<EnrolledStudent> enrolledStudents;

public Enrollment(SessionStatus status, SessionType sessionType) {
this(null, status, sessionType, Collections.emptyList());
public Enrollment(RecruitmentStatus recruitmentStatus, SessionType sessionType) {
this(null, recruitmentStatus, sessionType, Collections.emptyList());
}

public Enrollment(Long sessionId, SessionStatus status, SessionType sessionType, List<EnrolledStudent> enrolledStudents) {

public Enrollment(Long sessionId, RecruitmentStatus recruitmentStatus, SessionType sessionType, List<EnrolledStudent> enrolledStudents) {
this.sessionId = sessionId;
this.status = status;
this.recruitmentStatus = recruitmentStatus;
this.sessionType = sessionType;
this.enrolledStudents = enrolledStudents;
}

public EnrolledStudent enroll(Long nsUserId, Payment payment) {
if (!status.canEnroll()) {
if (!recruitmentStatus.canEnroll()) {
throw new IllegalStateException("모집중인 강의만 수강 신청할 수 있다");
}
if (!sessionType.isValidPayment(payment)) {
Expand All @@ -35,14 +34,33 @@ public EnrolledStudent enroll(Long nsUserId, Payment payment) {
if (sessionType.isOverCapacity(enrolledStudents.size())) {
throw new IllegalStateException("최대 수강 인원을 초과했습니다.");
}
return new EnrolledStudent(sessionId,nsUserId);
return new EnrolledStudent(sessionId, nsUserId);
}

public SessionStatus getStatus() {
return status;
public EnrollmentApplication apply(Long nsUserId, Payment payment) {
if (!recruitmentStatus.canEnroll()) {
throw new IllegalStateException("모집중인 강의만 수강 신청할 수 있습니다.");
}
if (!sessionType.isValidPayment(payment)) {
throw new IllegalArgumentException("결제 금액이 수강료와 일치하지 않습니다.");
}
if (sessionType.isOverCapacity(enrolledStudents.size())) {
throw new IllegalStateException("최대 수강 인원을 초과했습니다.");
}
return new EnrollmentApplication(sessionId, nsUserId, payment);
}

public EnrolledStudent approve(EnrollmentApplication application, Long adminId) {
application.approve(adminId);
return new EnrolledStudent(application.getSessionId(), application.getNsUserId());
}


public SessionType getSessionType() {
return sessionType;
}

public RecruitmentStatus getRecruitmentStatus() {
return recruitmentStatus;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package nextstep.courses.domain.session;

import nextstep.payments.domain.Payment;

import java.time.LocalDateTime;

public class EnrollmentApplication {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EnrollmentApplication 이름만으로 어떤 역할과 책임을 담당하고 있는지 파악하기 어려움
수강 신청 중인 수강생 정보를 담고 있는 것으로 보이는데 Student와 같은 이름으로 좀 더 명확히 하면 어떨까?
AI에게 이 객체가 담당하는 역할과 책임을 설명하고 적합한 이름을 찾아볼 것을 추천

private final Long sessionId;
private final Long nsUserId;
private final Payment payment;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

굳이 금액 정보를 저장할 필요가 있을까?

private EnrollmentStatus status;
private LocalDateTime approvedAt;
private Long approvedBy;

public EnrollmentApplication(Long sessionId, Long nsUserId, Payment payment) {
this(sessionId, nsUserId, payment, EnrollmentStatus.PENDING, LocalDateTime.now(), null);
}

public EnrollmentApplication(Long sessionId, Long nsUserId, Payment payment, EnrollmentStatus status, LocalDateTime approvedAt, Long approvedBy) {
this.sessionId = sessionId;
this.nsUserId = nsUserId;
this.payment = payment;
this.status = status;
this.approvedAt = approvedAt;
this.approvedBy = approvedBy;
}

public void approve(Long adminId) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

if (!status.isPending()) {
throw new IllegalStateException("대기 중인 신청만 승인 가능합니다.");
}
this.status = EnrollmentStatus.APPROVED;
this.approvedAt = LocalDateTime.now();
this.approvedBy = adminId;
}

public void cancel() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍
단, 승인 날짜와 승인한 사람의 정보를 기록하고 있는데 승인 취소에 대한 기록은 관리하지 않고 있다.
승인과 취소에 대한 정보를 관리하는 것이 중요하다면 이 테이블에 관리하기 보다 별도의 History 테이블을 통해 관리하는 것은 어떨까?

if (!status.isPending()) {
throw new IllegalStateException("대기 중인 신청만 취소 가능합니다.");
}
this.status = EnrollmentStatus.CANCELLED;
}

public Long getSessionId() {
return sessionId;
}

public Long getNsUserId() {
return nsUserId;
}

public Payment getPayment() {
return payment;
}

public EnrollmentStatus getStatus() {
return status;
}

public LocalDateTime getApprovedAt() {
return approvedAt;
}

public Long getApprovedBy() {
return approvedBy;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
import java.util.List;

public interface EnrollmentRepository {
void save (EnrolledStudent enrolledStudent);
void save(EnrolledStudent enrolledStudent);

List<EnrolledStudent> findBySessionId(Long sessionId);

void saveApplication(EnrollmentApplication application);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이미 EnrolledStudent가 있다면 EnrollmentApplication을 추가하는 대신 EnrolledStudent 객체를 리팩터링하는 것이 낫지 않을까?


void updateApplication(EnrollmentApplication application);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package nextstep.courses.domain.session;

import java.util.Arrays;

public enum EnrollmentStatus {
PENDING("대기중"),
APPROVED("승인됨"),
CANCELLED("취소됨");

private final String value;

EnrollmentStatus(String value) {
this.value = value;
}

public static EnrollmentStatus from(String description) {
return Arrays.stream(values())
.filter(status -> status.value.equals(description))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("잘못된 수강신청 상태입니다: " + description));
}

public boolean isApproved() {
return this == APPROVED;
}

public boolean isPending() {
return this == PENDING;
}

public String getValue() {
return value;
}
}
26 changes: 26 additions & 0 deletions src/main/java/nextstep/courses/domain/session/ProgressStatus.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package nextstep.courses.domain.session;

import java.util.Arrays;

public enum ProgressStatus {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

status 분리 👍

PREPARING("준비중"),
IN_PROGRESS("진행중"),
CLOSED("종료");

private final String value;

ProgressStatus(String value) {
this.value = value;
}

public static ProgressStatus from(String description) {
return Arrays.stream(values())
.filter(status -> status.value.equals(description))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("잘못된 진행 상태입니다: " + description));
}

public String getValue() {
return value;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package nextstep.courses.domain.session;

import java.util.Arrays;

public enum RecruitmentStatus {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

status 분리 👍

NOT_RECRUITING("비모집중"),
RECRUITING("모집중");

private final String value;

RecruitmentStatus(String value) {
this.value = value;
}

public static RecruitmentStatus from(String description) {
return Arrays.stream(values())
.filter(status -> status.value.equals(description))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("잘못된 모집 상태입니다: " + description));
}

public boolean canEnroll() {
return this == RECRUITING;
}

public String getValue() {
return value;
}
}
Loading