From 3341a77da8ef7cc549d3afc2a1b5fad9ad662939 Mon Sep 17 00:00:00 2001 From: mins1031 Date: Tue, 16 Dec 2025 19:45:39 +0900 Subject: [PATCH 01/14] =?UTF-8?q?feat=20:=20=EC=88=98=EA=B0=95=EC=8B=A0?= =?UTF-8?q?=EC=B2=AD=202=EB=8B=A8=EA=B3=84=20(=EB=8F=84=EB=A9=94=EC=9D=B8?= =?UTF-8?q?=20=EB=AA=A8=EB=8D=B8)=20-=20=ED=8C=A8=ED=82=A4=EC=A7=80=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/nextstep/courses/{ => course}/domain/Course.java | 2 +- .../{ => course}/infrastructure/JdbcCourseRepository.java | 6 +++--- .../java/nextstep/courses/course/service/CourseService.java | 5 +++++ .../service/repository}/CourseRepository.java | 4 +++- .../courses/infrastructure/CourseRepositoryTest.java | 5 +++-- 5 files changed, 15 insertions(+), 7 deletions(-) rename src/main/java/nextstep/courses/{ => course}/domain/Course.java (96%) rename src/main/java/nextstep/courses/{ => course}/infrastructure/JdbcCourseRepository.java (89%) create mode 100644 src/main/java/nextstep/courses/course/service/CourseService.java rename src/main/java/nextstep/courses/{domain => course/service/repository}/CourseRepository.java (50%) diff --git a/src/main/java/nextstep/courses/domain/Course.java b/src/main/java/nextstep/courses/course/domain/Course.java similarity index 96% rename from src/main/java/nextstep/courses/domain/Course.java rename to src/main/java/nextstep/courses/course/domain/Course.java index 0f6971604..818d208b7 100644 --- a/src/main/java/nextstep/courses/domain/Course.java +++ b/src/main/java/nextstep/courses/course/domain/Course.java @@ -1,4 +1,4 @@ -package nextstep.courses.domain; +package nextstep.courses.course.domain; import java.time.LocalDateTime; diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java b/src/main/java/nextstep/courses/course/infrastructure/JdbcCourseRepository.java similarity index 89% rename from src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java rename to src/main/java/nextstep/courses/course/infrastructure/JdbcCourseRepository.java index f9122cbe3..ca90cecd8 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java +++ b/src/main/java/nextstep/courses/course/infrastructure/JdbcCourseRepository.java @@ -1,7 +1,7 @@ -package nextstep.courses.infrastructure; +package nextstep.courses.course.infrastructure; -import nextstep.courses.domain.Course; -import nextstep.courses.domain.CourseRepository; +import nextstep.courses.course.domain.Course; +import nextstep.courses.course.service.repository.CourseRepository; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Repository; diff --git a/src/main/java/nextstep/courses/course/service/CourseService.java b/src/main/java/nextstep/courses/course/service/CourseService.java new file mode 100644 index 000000000..352134893 --- /dev/null +++ b/src/main/java/nextstep/courses/course/service/CourseService.java @@ -0,0 +1,5 @@ +package nextstep.courses.course.service; + +public class CourseService { + +} diff --git a/src/main/java/nextstep/courses/domain/CourseRepository.java b/src/main/java/nextstep/courses/course/service/repository/CourseRepository.java similarity index 50% rename from src/main/java/nextstep/courses/domain/CourseRepository.java rename to src/main/java/nextstep/courses/course/service/repository/CourseRepository.java index 6aaeb638d..30606d35f 100644 --- a/src/main/java/nextstep/courses/domain/CourseRepository.java +++ b/src/main/java/nextstep/courses/course/service/repository/CourseRepository.java @@ -1,4 +1,6 @@ -package nextstep.courses.domain; +package nextstep.courses.course.service.repository; + +import nextstep.courses.course.domain.Course; public interface CourseRepository { int save(Course course); diff --git a/src/test/java/nextstep/courses/infrastructure/CourseRepositoryTest.java b/src/test/java/nextstep/courses/infrastructure/CourseRepositoryTest.java index f087fc0ad..0eb156ef7 100644 --- a/src/test/java/nextstep/courses/infrastructure/CourseRepositoryTest.java +++ b/src/test/java/nextstep/courses/infrastructure/CourseRepositoryTest.java @@ -1,7 +1,8 @@ package nextstep.courses.infrastructure; -import nextstep.courses.domain.Course; -import nextstep.courses.domain.CourseRepository; +import nextstep.courses.course.domain.Course; +import nextstep.courses.course.service.repository.CourseRepository; +import nextstep.courses.course.infrastructure.JdbcCourseRepository; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.slf4j.Logger; From 0f5b3d644c33ee181ce32e168b29479c99697aef Mon Sep 17 00:00:00 2001 From: mins1031 Date: Tue, 16 Dec 2025 19:55:56 +0900 Subject: [PATCH 02/14] =?UTF-8?q?feat=20:=20=EC=88=98=EA=B0=95=EC=8B=A0?= =?UTF-8?q?=EC=B2=AD=202=EB=8B=A8=EA=B3=84=20(=EB=8F=84=EB=A9=94=EC=9D=B8?= =?UTF-8?q?=20=EB=AA=A8=EB=8D=B8)=20-=20Course=20TDD=EA=B8=B0=EB=B0=98=20?= =?UTF-8?q?=EC=9C=A0/=EB=AC=B4=EB=A3=8C=20=EA=B5=AC=EB=B6=84=20=ED=96=89?= =?UTF-8?q?=EC=9C=84=EA=B0=9C=EB=B0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../courses/course/domain/Course.java | 42 ++++++++++++++++++- .../domain/enumaration/CourseChargeType.java | 13 ++++++ .../courses/course/domain/CourseTest.java | 31 ++++++++++++++ 3 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 src/main/java/nextstep/courses/course/domain/enumaration/CourseChargeType.java create mode 100644 src/test/java/nextstep/courses/course/domain/CourseTest.java diff --git a/src/main/java/nextstep/courses/course/domain/Course.java b/src/main/java/nextstep/courses/course/domain/Course.java index 818d208b7..4fdfc89d5 100644 --- a/src/main/java/nextstep/courses/course/domain/Course.java +++ b/src/main/java/nextstep/courses/course/domain/Course.java @@ -1,6 +1,7 @@ package nextstep.courses.course.domain; import java.time.LocalDateTime; +import nextstep.courses.course.domain.enumaration.CourseChargeType; public class Course { private Long id; @@ -13,19 +14,56 @@ public class Course { private LocalDateTime updatedAt; + private CourseChargeType courseChargeType; + public Course() { } public Course(String title, Long creatorId) { - this(0L, title, creatorId, LocalDateTime.now(), null); + this(0L, title, creatorId, LocalDateTime.now(), null, CourseChargeType.PAID); + } + + public Course(String title, Long creatorId, CourseChargeType courseChargeType) { + this(0L, title, creatorId, LocalDateTime.now(), null, courseChargeType); + } + + public Course( + Long id, + String title, + Long creatorId, + LocalDateTime createdAt, + LocalDateTime updatedAt + ) { + this.id = id; + this.title = title; + this.creatorId = creatorId; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + this.courseChargeType = CourseChargeType.PAID; } - public Course(Long id, String title, Long creatorId, LocalDateTime createdAt, LocalDateTime updatedAt) { + public Course( + Long id, + String title, + Long creatorId, + LocalDateTime createdAt, + LocalDateTime updatedAt, + CourseChargeType courseChargeType + ) { this.id = id; this.title = title; this.creatorId = creatorId; this.createdAt = createdAt; this.updatedAt = updatedAt; + this.courseChargeType = courseChargeType; + } + + public boolean isPaid() { + return this.courseChargeType.equals(CourseChargeType.PAID); + } + + public boolean isFree() { + return this.courseChargeType.equals(CourseChargeType.FREE); } public String getTitle() { diff --git a/src/main/java/nextstep/courses/course/domain/enumaration/CourseChargeType.java b/src/main/java/nextstep/courses/course/domain/enumaration/CourseChargeType.java new file mode 100644 index 000000000..90353a16e --- /dev/null +++ b/src/main/java/nextstep/courses/course/domain/enumaration/CourseChargeType.java @@ -0,0 +1,13 @@ +package nextstep.courses.course.domain.enumaration; + +public enum CourseChargeType { + FREE("무료강의"), + PAID("유료강의"), + ; + + private final String desc; + + CourseChargeType(String desc) { + this.desc = desc; + } +} diff --git a/src/test/java/nextstep/courses/course/domain/CourseTest.java b/src/test/java/nextstep/courses/course/domain/CourseTest.java new file mode 100644 index 000000000..68268db7e --- /dev/null +++ b/src/test/java/nextstep/courses/course/domain/CourseTest.java @@ -0,0 +1,31 @@ +package nextstep.courses.course.domain; + +import static org.assertj.core.api.Assertions.assertThat; + +import nextstep.courses.course.domain.enumaration.CourseChargeType; +import org.junit.jupiter.api.Test; + +class CourseTest { + + @Test + void 과정의_수강가능여부를_확인할_수_있다() { + } + + @Test + void 유료강의인지_확인할_수_있다() { + Course course = new Course("TDD, 객체지향 과정", 1L, CourseChargeType.PAID); + + assertThat( + course.isPaid() + ).isTrue(); + } + + @Test + void 무료강의인지_확인할_수_있다() { + Course course = new Course("TDD, 객체지향 과정", 1L, CourseChargeType.FREE); + + assertThat( + course.isFree() + ).isTrue(); + } +} \ No newline at end of file From a732a306eab07853d16ecaf30e5bdc8aba408ce4 Mon Sep 17 00:00:00 2001 From: mins1031 Date: Tue, 16 Dec 2025 20:11:43 +0900 Subject: [PATCH 03/14] =?UTF-8?q?feat=20:=20=EC=88=98=EA=B0=95=EC=8B=A0?= =?UTF-8?q?=EC=B2=AD=202=EB=8B=A8=EA=B3=84=20(=EB=8F=84=EB=A9=94=EC=9D=B8?= =?UTF-8?q?=20=EB=AA=A8=EB=8D=B8)=20-=20Course=EC=97=90=20BaseEntity=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9=20-=20BaseEntity=20protected=20=ED=83=80?= =?UTF-8?q?=EC=9E=85=20=EA=B2=8C=ED=84=B0=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nextstep/common/domain/BaseEntity.java | 16 +++++---- .../courses/course/domain/Course.java | 36 +++++-------------- .../infrastructure/JdbcCourseRepository.java | 2 +- 3 files changed, 20 insertions(+), 34 deletions(-) diff --git a/src/main/java/nextstep/common/domain/BaseEntity.java b/src/main/java/nextstep/common/domain/BaseEntity.java index ef5afe1e5..0a4d20543 100644 --- a/src/main/java/nextstep/common/domain/BaseEntity.java +++ b/src/main/java/nextstep/common/domain/BaseEntity.java @@ -8,20 +8,24 @@ public abstract class BaseEntity { private LocalDateTime createdDate; private LocalDateTime updatedDate; - protected BaseEntity(Long id) { - this(id, LocalDateTime.now(), LocalDateTime.now()); - } - - public BaseEntity(Long id, LocalDateTime createdDate, LocalDateTime updatedDate) { + protected BaseEntity(Long id, LocalDateTime createdDate, LocalDateTime updatedDate) { this.id = id; this.createdDate = createdDate; this.updatedDate = updatedDate; } - public Long getId() { + protected Long getId() { return id; } + protected LocalDateTime getCreatedDate() { + return createdDate; + } + + protected LocalDateTime getUpdatedDate() { + return updatedDate; + } + @Override public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) { diff --git a/src/main/java/nextstep/courses/course/domain/Course.java b/src/main/java/nextstep/courses/course/domain/Course.java index 4fdfc89d5..e46906d73 100644 --- a/src/main/java/nextstep/courses/course/domain/Course.java +++ b/src/main/java/nextstep/courses/course/domain/Course.java @@ -1,28 +1,21 @@ package nextstep.courses.course.domain; import java.time.LocalDateTime; +import nextstep.common.domain.BaseEntity; import nextstep.courses.course.domain.enumaration.CourseChargeType; -public class Course { - private Long id; - +public class Course extends BaseEntity { private String title; - private Long creatorId; - - private LocalDateTime createdAt; - - private LocalDateTime updatedAt; - private CourseChargeType courseChargeType; - public Course() { - } public Course(String title, Long creatorId) { this(0L, title, creatorId, LocalDateTime.now(), null, CourseChargeType.PAID); } + // TODO 중요한 도메인 객체에서 여러가지 생성자 버전을 외부에 제공할때 식별자를 외부에 열어놓는다는것 자체가 아직은 좀 불안하네요. + // 일단 주생성자에만 식별자 파라미터를 열어놓고 그 외엔 모두 닫아놓는 방식으로 구현해보려고 하는데 어떻게 생각하시나요? public Course(String title, Long creatorId, CourseChargeType courseChargeType) { this(0L, title, creatorId, LocalDateTime.now(), null, courseChargeType); } @@ -34,12 +27,7 @@ public Course( LocalDateTime createdAt, LocalDateTime updatedAt ) { - this.id = id; - this.title = title; - this.creatorId = creatorId; - this.createdAt = createdAt; - this.updatedAt = updatedAt; - this.courseChargeType = CourseChargeType.PAID; + this(id, title, creatorId, createdAt, updatedAt, CourseChargeType.PAID); } public Course( @@ -50,11 +38,9 @@ public Course( LocalDateTime updatedAt, CourseChargeType courseChargeType ) { - this.id = id; + super(id, createdAt, updatedAt); this.title = title; this.creatorId = creatorId; - this.createdAt = createdAt; - this.updatedAt = updatedAt; this.courseChargeType = courseChargeType; } @@ -74,18 +60,14 @@ public Long getCreatorId() { return creatorId; } - public LocalDateTime getCreatedAt() { - return createdAt; - } @Override public String toString() { return "Course{" + - "id=" + id + - ", title='" + title + '\'' + + "id='" + super.getId() + '\'' + + "title='" + title + '\'' + ", creatorId=" + creatorId + - ", createdAt=" + createdAt + - ", updatedAt=" + updatedAt + + ", courseChargeType=" + courseChargeType + '}'; } } diff --git a/src/main/java/nextstep/courses/course/infrastructure/JdbcCourseRepository.java b/src/main/java/nextstep/courses/course/infrastructure/JdbcCourseRepository.java index ca90cecd8..2defdddc9 100644 --- a/src/main/java/nextstep/courses/course/infrastructure/JdbcCourseRepository.java +++ b/src/main/java/nextstep/courses/course/infrastructure/JdbcCourseRepository.java @@ -20,7 +20,7 @@ public JdbcCourseRepository(JdbcOperations jdbcTemplate) { @Override public int save(Course course) { String sql = "insert into course (title, creator_id, created_at) values(?, ?, ?)"; - return jdbcTemplate.update(sql, course.getTitle(), course.getCreatorId(), course.getCreatedAt()); + return jdbcTemplate.update(sql, course.getTitle(), course.getCreatorId(), LocalDateTime.now()); } @Override From 073ded441c3e2a5f868aaf9afa8a4c3363ba43b9 Mon Sep 17 00:00:00 2001 From: mins1031 Date: Wed, 17 Dec 2025 14:40:06 +0900 Subject: [PATCH 04/14] =?UTF-8?q?feat=20:=20=EC=88=98=EA=B0=95=EC=8B=A0?= =?UTF-8?q?=EC=B2=AD=202=EB=8B=A8=EA=B3=84=20(=EB=8F=84=EB=A9=94=EC=9D=B8?= =?UTF-8?q?=20=EB=AA=A8=EB=8D=B8)=20-=20Course,=20Cohort=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=EC=9E=90=20=EB=B0=8F=20=EC=98=88=EC=99=B8=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20TDD=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../courses/cohort/domain/Cohort.java | 45 ++++++++++++++++++ .../courses/cohort/domain/Period.java | 23 +++++++++ .../domain/enumeration/CohortStateType.java | 11 +++++ .../courses/course/domain/Course.java | 20 +++++++- .../courses/cohort/domain/CohortTest.java | 47 +++++++++++++++++++ .../courses/cohort/domain/PeriodTest.java | 33 +++++++++++++ .../courses/course/domain/CourseTest.java | 28 ++++++++++- 7 files changed, 205 insertions(+), 2 deletions(-) create mode 100644 src/main/java/nextstep/courses/cohort/domain/Cohort.java create mode 100644 src/main/java/nextstep/courses/cohort/domain/Period.java create mode 100644 src/main/java/nextstep/courses/cohort/domain/enumeration/CohortStateType.java create mode 100644 src/test/java/nextstep/courses/cohort/domain/CohortTest.java create mode 100644 src/test/java/nextstep/courses/cohort/domain/PeriodTest.java diff --git a/src/main/java/nextstep/courses/cohort/domain/Cohort.java b/src/main/java/nextstep/courses/cohort/domain/Cohort.java new file mode 100644 index 000000000..6e32b72cc --- /dev/null +++ b/src/main/java/nextstep/courses/cohort/domain/Cohort.java @@ -0,0 +1,45 @@ +package nextstep.courses.cohort.domain; + +import java.time.LocalDateTime; +import nextstep.common.domain.BaseEntity; +import nextstep.courses.cohort.domain.enumeration.CohortStateType; + +public class Cohort extends BaseEntity { + + private final Long courseId; + private CohortStateType cohortStateType; + private Period registerPeriod; + private Period cohortPeriod; + + public Cohort( + Long courseId, + LocalDateTime registerStartDate, + LocalDateTime registerEndDate, + LocalDateTime cohortStartDate, + LocalDateTime cohortEndDate + ) { + this(0L, courseId, CohortStateType.PREPARE, registerStartDate, registerEndDate, cohortStartDate, cohortEndDate, null, null); + } + + public Cohort( + Long id, + Long courseId, + CohortStateType cohortStateType, + LocalDateTime registerStartDate, + LocalDateTime registerEndDate, + LocalDateTime cohortStartDate, + LocalDateTime cohortEndDate, + LocalDateTime createdDate, + LocalDateTime updatedDate + ) { + super(id, createdDate, updatedDate); + if (courseId <= 0L) { + throw new IllegalArgumentException("기수는 관련 코스정보가 필수 입니다."); + } + + this.courseId = courseId; + this.cohortStateType = cohortStateType; + this.registerPeriod = new Period(registerStartDate, registerEndDate); + this.cohortPeriod = new Period(cohortStartDate, cohortEndDate); + } +} diff --git a/src/main/java/nextstep/courses/cohort/domain/Period.java b/src/main/java/nextstep/courses/cohort/domain/Period.java new file mode 100644 index 000000000..e1902c6d8 --- /dev/null +++ b/src/main/java/nextstep/courses/cohort/domain/Period.java @@ -0,0 +1,23 @@ +package nextstep.courses.cohort.domain; + +import static java.util.Objects.isNull; + +import java.time.LocalDateTime; + +public class Period { + private final LocalDateTime registerStartDate; + private final LocalDateTime registerEndDate; + + public Period(LocalDateTime registerStartDate, LocalDateTime registerEndDate) { + if (isNull(registerStartDate) || isNull(registerEndDate)) { + throw new IllegalArgumentException("수강신청 시작일과 종료일은 필수값 입니다."); + } + + if (registerStartDate.isAfter(registerEndDate)) { + throw new IllegalArgumentException("수강신청 시작일이 종료일보다 미래일수는 없습니다"); + } + + this.registerStartDate = registerStartDate; + this.registerEndDate = registerEndDate; + } +} diff --git a/src/main/java/nextstep/courses/cohort/domain/enumeration/CohortStateType.java b/src/main/java/nextstep/courses/cohort/domain/enumeration/CohortStateType.java new file mode 100644 index 000000000..7964a6a27 --- /dev/null +++ b/src/main/java/nextstep/courses/cohort/domain/enumeration/CohortStateType.java @@ -0,0 +1,11 @@ +package nextstep.courses.cohort.domain.enumeration; + +public enum CohortStateType { + PREPARE, + ACTIVE, + END, + + ; + + +} diff --git a/src/main/java/nextstep/courses/course/domain/Course.java b/src/main/java/nextstep/courses/course/domain/Course.java index e46906d73..bb12ebef8 100644 --- a/src/main/java/nextstep/courses/course/domain/Course.java +++ b/src/main/java/nextstep/courses/course/domain/Course.java @@ -1,14 +1,19 @@ package nextstep.courses.course.domain; +import static java.util.Objects.isNull; +import static org.springframework.util.StringUtils.hasText; + import java.time.LocalDateTime; import nextstep.common.domain.BaseEntity; +import nextstep.courses.cohort.domain.enumeration.CohortStateType; import nextstep.courses.course.domain.enumaration.CourseChargeType; public class Course extends BaseEntity { + private String title; private Long creatorId; private CourseChargeType courseChargeType; - + private CohortStateType cohortStateType; public Course(String title, Long creatorId) { this(0L, title, creatorId, LocalDateTime.now(), null, CourseChargeType.PAID); @@ -39,6 +44,19 @@ public Course( CourseChargeType courseChargeType ) { super(id, createdAt, updatedAt); + + if (!hasText(title)) { + throw new IllegalArgumentException("강의제목은 필수값 입니다."); + } + + if (isNull(creatorId) || creatorId <= 0L) { + throw new IllegalArgumentException("강의 생성자 정보는 필수 값 입니다."); + } + + if (isNull(courseChargeType)) { + throw new IllegalArgumentException("강의 결제타입은 필수 값 입니다."); + } + this.title = title; this.creatorId = creatorId; this.courseChargeType = courseChargeType; diff --git a/src/test/java/nextstep/courses/cohort/domain/CohortTest.java b/src/test/java/nextstep/courses/cohort/domain/CohortTest.java new file mode 100644 index 000000000..bf01a3109 --- /dev/null +++ b/src/test/java/nextstep/courses/cohort/domain/CohortTest.java @@ -0,0 +1,47 @@ +package nextstep.courses.cohort.domain; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.time.LocalDateTime; +import org.junit.jupiter.api.Test; + +class CohortTest { + + @Test + void 기수생성시_코스식별자가_비정상이면_예외처리_할_수_있다() { + assertThatThrownBy(() -> new Cohort(0L, LocalDateTime.now(), LocalDateTime.now(), + LocalDateTime.now(), LocalDateTime.now()) + ).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void 기수생성시_수강신청기간이_수강기간보다_미래면_예외처리_할_수_있다() { + // 조금 생각이 필요한 기능. 시작 시점을 기준으로 처리할지, 종료시점을 기준으로 할지, 각각의 기간을 기준으로 할지 + } + + @Test + void 현재_수강인원을_더_받을수_있는지_확인할_수_있다() { + // 최대 수강인원, 현재 수강인원 전부 필요할듯. + } + + @Test + void 지금이_수강신청_기간인지_확인할_수_있다() { + // 기간 객체엔 특정 날짜 값이 시작-종료 중간값인지 확인하는 메서드 + 기수에선 수강신청기간 확인 메서드로 표현성 극대화 + } + + @Test + void 지금이_수강_기간인지_확인할_수_있다() { + // 기간 객체엔 특정 날짜 값이 시작-종료 중간값인지 확인하는 메서드 + 기수에선 수강기간 확인 메서드로 표현성 극대화 + } + + @Test + void 기수의_상태를_수강신청_기간으로_변경할_수_있다() { + // 특정 날짜값 받고 + 수강신청 기간인지 확인 + 신청상태로 상태변경 + } + + @Test + void 기수의_상태를_수강_기간으로_변경할_수_있다() { + // 특정 날짜값 받고 + 수강기간인지 확인 + 수강기간으로 상태변경 + } + +} \ No newline at end of file diff --git a/src/test/java/nextstep/courses/cohort/domain/PeriodTest.java b/src/test/java/nextstep/courses/cohort/domain/PeriodTest.java new file mode 100644 index 000000000..da1727795 --- /dev/null +++ b/src/test/java/nextstep/courses/cohort/domain/PeriodTest.java @@ -0,0 +1,33 @@ +package nextstep.courses.cohort.domain; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.time.LocalDateTime; +import org.junit.jupiter.api.Test; + +class PeriodTest { + + @Test + void 수강신청_시작일이_NULL이면_예외처리_할_수_있다() { + assertThatThrownBy( + () -> new Period(null, LocalDateTime.now()) + ).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void 수강신청_종료일이_NULL이면_예외처리_할_수_있다() { + assertThatThrownBy( + () -> new Period(LocalDateTime.now(), null) + ).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void 수강신청_시작일이_종료일보다_미래이면_예외처리_할_수_있다() { + LocalDateTime endDate = LocalDateTime.of(2025, 1, 1, 0, 0, 0); + LocalDateTime startDate = endDate.plusSeconds(1); + + assertThatThrownBy( + () -> new Period(startDate, endDate) + ).isInstanceOf(IllegalArgumentException.class); + } +} \ No newline at end of file diff --git a/src/test/java/nextstep/courses/course/domain/CourseTest.java b/src/test/java/nextstep/courses/course/domain/CourseTest.java index 68268db7e..26172ab8e 100644 --- a/src/test/java/nextstep/courses/course/domain/CourseTest.java +++ b/src/test/java/nextstep/courses/course/domain/CourseTest.java @@ -1,14 +1,40 @@ package nextstep.courses.course.domain; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import nextstep.courses.course.domain.enumaration.CourseChargeType; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; +import org.junit.jupiter.params.provider.NullSource; +import org.junit.jupiter.params.provider.ValueSource; class CourseTest { + @ParameterizedTest + @NullAndEmptySource + @ValueSource(strings = {" "}) + void 강의제목이_없이_강의를_생성하면_예외처리_할_수_있다(String title) { + assertThatThrownBy( + () -> new Course(title, 1L) + ).isInstanceOf(IllegalArgumentException.class); + } + + @ParameterizedTest + @NullSource + @ValueSource(longs = {0L}) + void 강의생성자_정보없이_강의를_생성하면_예외처리_할_수_있다(Long creatorId) { + assertThatThrownBy( + () -> new Course("TDD, 객체지향 과정", creatorId) + ).isInstanceOf(IllegalArgumentException.class); + } + @Test - void 과정의_수강가능여부를_확인할_수_있다() { + void 강의_결제타입_없이_강의를_생성하면_예외처리_할_수_있다() { + assertThatThrownBy( + () -> new Course("TDD, 객체지향 과정", 1L, null) + ).isInstanceOf(IllegalArgumentException.class); } @Test From ef496fe0fb4425672ca34ce481474e1d3768ec32 Mon Sep 17 00:00:00 2001 From: mins1031 Date: Wed, 17 Dec 2025 16:26:02 +0900 Subject: [PATCH 05/14] =?UTF-8?q?feat=20:=20=EC=88=98=EA=B0=95=EC=8B=A0?= =?UTF-8?q?=EC=B2=AD=202=EB=8B=A8=EA=B3=84=20(=EB=8F=84=EB=A9=94=EC=9D=B8?= =?UTF-8?q?=20=EB=AA=A8=EB=8D=B8)=20-=20Cohort=20=EC=84=A4=EA=B3=84=20?= =?UTF-8?q?=ED=96=89=EC=9C=84=20=EC=9D=BC=EB=B6=80=EB=B6=84=20TDD=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../courses/cohort/domain/Cohort.java | 48 ++++++++++++- .../courses/cohort/domain/Period.java | 21 ++++-- .../domain/enumeration/CohortStateType.java | 12 +++- .../courses/cohort/domain/CohortTest.java | 71 ++++++++++++++++--- .../courses/cohort/domain/PeriodTest.java | 19 ++++- 5 files changed, 149 insertions(+), 22 deletions(-) diff --git a/src/main/java/nextstep/courses/cohort/domain/Cohort.java b/src/main/java/nextstep/courses/cohort/domain/Cohort.java index 6e32b72cc..4f9bc0aef 100644 --- a/src/main/java/nextstep/courses/cohort/domain/Cohort.java +++ b/src/main/java/nextstep/courses/cohort/domain/Cohort.java @@ -7,23 +7,29 @@ public class Cohort extends BaseEntity { private final Long courseId; + private int maxStudentCount; + private int presentStudentCount; private CohortStateType cohortStateType; private Period registerPeriod; private Period cohortPeriod; public Cohort( Long courseId, + int maxStudentCount, + int presentStudentCount, LocalDateTime registerStartDate, LocalDateTime registerEndDate, LocalDateTime cohortStartDate, LocalDateTime cohortEndDate ) { - this(0L, courseId, CohortStateType.PREPARE, registerStartDate, registerEndDate, cohortStartDate, cohortEndDate, null, null); + this(0L, courseId, maxStudentCount, presentStudentCount, CohortStateType.PREPARE, registerStartDate, registerEndDate, cohortStartDate, cohortEndDate, null, null); } public Cohort( Long id, Long courseId, + int maxStudentCount, + int presentStudentCount, CohortStateType cohortStateType, LocalDateTime registerStartDate, LocalDateTime registerEndDate, @@ -38,8 +44,48 @@ public Cohort( } this.courseId = courseId; + this.maxStudentCount = maxStudentCount; + this.presentStudentCount = presentStudentCount; this.cohortStateType = cohortStateType; this.registerPeriod = new Period(registerStartDate, registerEndDate); this.cohortPeriod = new Period(cohortStartDate, cohortEndDate); } + + public boolean isCanResist() { +// if (this.cohortStateType.equals(CohortStateType.PREPARE)) { +// // 고민.. 위에서 말했던 대로 약간... 수강신청가능 같은 어떤 상태가 필요할듯 +// } + + return this.maxStudentCount > this.presentStudentCount; + } + + public boolean isInRecruitBy(LocalDateTime now) { + // 이것도 수강신청 정책 관련 로직일듯 + return this.registerPeriod.isPeriodIn(now); + } + + public boolean isInActiveBy(LocalDateTime now) { + // 이것도 수강신청 정책 관련 로직일듯 + return this.cohortPeriod.isPeriodIn(now); + } + + public void putOnRecruit(LocalDateTime now) { + if (!isInRecruitBy(now)) { + throw new IllegalArgumentException("현재는 수강신청 기간이 아닙니다"); + } + + this.cohortStateType = CohortStateType.RECRUIT; + } + + public void putOnActive(LocalDateTime now) { + if (!isInActiveBy(now)) { + throw new IllegalArgumentException("현재는 수강중 기간이 아닙니다"); + } + + this.cohortStateType = CohortStateType.ACTIVE; + } + + public CohortStateType cohortStateType() { + return this.cohortStateType; + } } diff --git a/src/main/java/nextstep/courses/cohort/domain/Period.java b/src/main/java/nextstep/courses/cohort/domain/Period.java index e1902c6d8..af78d68a6 100644 --- a/src/main/java/nextstep/courses/cohort/domain/Period.java +++ b/src/main/java/nextstep/courses/cohort/domain/Period.java @@ -5,19 +5,26 @@ import java.time.LocalDateTime; public class Period { - private final LocalDateTime registerStartDate; - private final LocalDateTime registerEndDate; + private final LocalDateTime startDate; + private final LocalDateTime endDate; - public Period(LocalDateTime registerStartDate, LocalDateTime registerEndDate) { - if (isNull(registerStartDate) || isNull(registerEndDate)) { + public Period(LocalDateTime startDate, LocalDateTime endDate) { + if (isNull(startDate) || isNull(endDate)) { throw new IllegalArgumentException("수강신청 시작일과 종료일은 필수값 입니다."); } - if (registerStartDate.isAfter(registerEndDate)) { + if (startDate.isAfter(endDate)) { throw new IllegalArgumentException("수강신청 시작일이 종료일보다 미래일수는 없습니다"); } - this.registerStartDate = registerStartDate; - this.registerEndDate = registerEndDate; + this.startDate = startDate; + this.endDate = endDate; + } + + public boolean isPeriodIn(LocalDateTime targetDate) { + boolean startDateAfter = this.startDate.isBefore(targetDate); + boolean endDateBefore = this.endDate.isAfter(targetDate); + + return startDateAfter && endDateBefore; } } diff --git a/src/main/java/nextstep/courses/cohort/domain/enumeration/CohortStateType.java b/src/main/java/nextstep/courses/cohort/domain/enumeration/CohortStateType.java index 7964a6a27..057aebfc7 100644 --- a/src/main/java/nextstep/courses/cohort/domain/enumeration/CohortStateType.java +++ b/src/main/java/nextstep/courses/cohort/domain/enumeration/CohortStateType.java @@ -1,11 +1,17 @@ package nextstep.courses.cohort.domain.enumeration; public enum CohortStateType { - PREPARE, - ACTIVE, - END, + PREPARE("준비중"), + RECRUIT("모집중"), + ACTIVE("진행중"), + END("종료"), ; + private final String desc; + + CohortStateType(String desc) { + this.desc = desc; + } } diff --git a/src/test/java/nextstep/courses/cohort/domain/CohortTest.java b/src/test/java/nextstep/courses/cohort/domain/CohortTest.java index bf01a3109..2ed2ee9be 100644 --- a/src/test/java/nextstep/courses/cohort/domain/CohortTest.java +++ b/src/test/java/nextstep/courses/cohort/domain/CohortTest.java @@ -1,15 +1,19 @@ package nextstep.courses.cohort.domain; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.time.LocalDateTime; +import nextstep.courses.cohort.domain.enumeration.CohortStateType; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; class CohortTest { @Test void 기수생성시_코스식별자가_비정상이면_예외처리_할_수_있다() { - assertThatThrownBy(() -> new Cohort(0L, LocalDateTime.now(), LocalDateTime.now(), + assertThatThrownBy(() -> new Cohort(0L, 1, 0, LocalDateTime.now(), LocalDateTime.now(), LocalDateTime.now(), LocalDateTime.now()) ).isInstanceOf(IllegalArgumentException.class); } @@ -17,16 +21,18 @@ class CohortTest { @Test void 기수생성시_수강신청기간이_수강기간보다_미래면_예외처리_할_수_있다() { // 조금 생각이 필요한 기능. 시작 시점을 기준으로 처리할지, 종료시점을 기준으로 할지, 각각의 기간을 기준으로 할지 + // 이건 요구사항 기준으로 달라질거 같긴한데... 시작시간을 기준으로하자. + // 신청종료시점이 수강시작기간보다 뒤에있어도 된다 = ot기간까지 수강신청 받는경우도 있으니 ㅇㅇ ==> 이건 인터페이스로 빼도 되겠다. } - @Test - void 현재_수강인원을_더_받을수_있는지_확인할_수_있다() { + @ParameterizedTest + @CsvSource({"19, true", "20, false"}) + void 현재_수강인원을_더_받을수_있는지_확인할_수_있다(int presentStudentCount, boolean result) { // 최대 수강인원, 현재 수강인원 전부 필요할듯. - } + Cohort cohort = new Cohort(1L, 20, presentStudentCount, LocalDateTime.now(), LocalDateTime.now(), + LocalDateTime.now(), LocalDateTime.now()); - @Test - void 지금이_수강신청_기간인지_확인할_수_있다() { - // 기간 객체엔 특정 날짜 값이 시작-종료 중간값인지 확인하는 메서드 + 기수에선 수강신청기간 확인 메서드로 표현성 극대화 + assertThat(cohort.isCanResist()).isEqualTo(result); } @Test @@ -37,11 +43,60 @@ class CohortTest { @Test void 기수의_상태를_수강신청_기간으로_변경할_수_있다() { // 특정 날짜값 받고 + 수강신청 기간인지 확인 + 신청상태로 상태변경 + Cohort cohort = new Cohort(1L, 20, 0, + LocalDateTime.of(2025, 1, 1, 0, 0, 0), + LocalDateTime.of(2025, 1, 10, 23, 59, 59), + LocalDateTime.of(2025, 1, 17, 0, 0, 0), + LocalDateTime.of(2025, 2, 25, 23, 59, 59) + ); + + cohort.putOnRecruit(LocalDateTime.of(2025, 1, 1, 0, 0, 1)); + + assertThat(cohort.cohortStateType()).isEqualTo(CohortStateType.RECRUIT); + } + + @Test + void 수강신청기간이_아닌데_기수의_상태를_수강신청_기간으로_변경하면_예외처리_할_수_있다() { + // 특정 날짜값 받고 + 수강신청 기간인지 확인 + 신청상태로 상태변경 + Cohort cohort = new Cohort(1L, 20, 0, + LocalDateTime.of(2025, 1, 1, 0, 0, 0), + LocalDateTime.of(2025, 1, 10, 23, 59, 59), + LocalDateTime.of(2025, 1, 17, 0, 0, 0), + LocalDateTime.of(2025, 2, 25, 23, 59, 59) + ); + + assertThatThrownBy( + () -> cohort.putOnRecruit(LocalDateTime.of(2025, 1, 11, 0, 0, 1)) + ).isInstanceOf(IllegalArgumentException.class); } @Test void 기수의_상태를_수강_기간으로_변경할_수_있다() { - // 특정 날짜값 받고 + 수강기간인지 확인 + 수강기간으로 상태변경 + // 특정 날짜값 받고 + 수강신청 기간인지 확인 + 신청상태로 상태변경 + Cohort cohort = new Cohort(1L, 20, 0, + LocalDateTime.of(2025, 1, 1, 0, 0, 0), + LocalDateTime.of(2025, 1, 10, 23, 59, 59), + LocalDateTime.of(2025, 1, 17, 0, 0, 0), + LocalDateTime.of(2025, 2, 25, 23, 59, 59) + ); + + cohort.putOnActive(LocalDateTime.of(2025, 1, 17, 0, 0, 1)); + + assertThat(cohort.cohortStateType()).isEqualTo(CohortStateType.ACTIVE); } + @Test + void 수강기간이_아닌데_기수의_상태를_수강중으로_변경하면_예외처리_할_수_있다() { + // 특정 날짜값 받고 + 수강신청 기간인지 확인 + 신청상태로 상태변경 + Cohort cohort = new Cohort(1L, 20, 0, + LocalDateTime.of(2025, 1, 1, 0, 0, 0), + LocalDateTime.of(2025, 1, 10, 23, 59, 59), + LocalDateTime.of(2025, 1, 17, 0, 0, 0), + LocalDateTime.of(2025, 2, 25, 23, 59, 59) + ); + + assertThatThrownBy( + () -> cohort.putOnActive(LocalDateTime.of(2025, 2, 26, 0, 0, 0)) + ).isInstanceOf(IllegalArgumentException.class); + } } \ No newline at end of file diff --git a/src/test/java/nextstep/courses/cohort/domain/PeriodTest.java b/src/test/java/nextstep/courses/cohort/domain/PeriodTest.java index da1727795..7d8c8bec7 100644 --- a/src/test/java/nextstep/courses/cohort/domain/PeriodTest.java +++ b/src/test/java/nextstep/courses/cohort/domain/PeriodTest.java @@ -1,5 +1,6 @@ package nextstep.courses.cohort.domain; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.time.LocalDateTime; @@ -8,21 +9,21 @@ class PeriodTest { @Test - void 수강신청_시작일이_NULL이면_예외처리_할_수_있다() { + void 시작일이_NULL이면_예외처리_할_수_있다() { assertThatThrownBy( () -> new Period(null, LocalDateTime.now()) ).isInstanceOf(IllegalArgumentException.class); } @Test - void 수강신청_종료일이_NULL이면_예외처리_할_수_있다() { + void 종료일이_NULL이면_예외처리_할_수_있다() { assertThatThrownBy( () -> new Period(LocalDateTime.now(), null) ).isInstanceOf(IllegalArgumentException.class); } @Test - void 수강신청_시작일이_종료일보다_미래이면_예외처리_할_수_있다() { + void 시작일이_종료일보다_미래이면_예외처리_할_수_있다() { LocalDateTime endDate = LocalDateTime.of(2025, 1, 1, 0, 0, 0); LocalDateTime startDate = endDate.plusSeconds(1); @@ -30,4 +31,16 @@ class PeriodTest { () -> new Period(startDate, endDate) ).isInstanceOf(IllegalArgumentException.class); } + + @Test + void 특정시점이_기간에_포함되는지_확인_할_수_있다() { + LocalDateTime startDate = LocalDateTime.of(2025, 1, 1, 0, 29, 28); + LocalDateTime endDate = LocalDateTime.of(2025, 1, 1, 0, 29, 30); + LocalDateTime target = LocalDateTime.of(2025, 1, 1, 0, 29, 29); + + assertThat( + new Period(startDate, endDate).isPeriodIn(target) + ).isTrue(); + } + } \ No newline at end of file From d369569e4ae91dba14629cb7ea995455bfd584fe Mon Sep 17 00:00:00 2001 From: mins1031 Date: Fri, 19 Dec 2025 13:18:16 +0900 Subject: [PATCH 06/14] =?UTF-8?q?feat=20:=20=EC=88=98=EA=B0=95=EC=8B=A0?= =?UTF-8?q?=EC=B2=AD=202=EB=8B=A8=EA=B3=84=20(=EB=8F=84=EB=A9=94=EC=9D=B8?= =?UTF-8?q?=20=EB=AA=A8=EB=8D=B8)=20-=20Cohorts=20=EB=8F=84=EB=A7=A4?= =?UTF-8?q?=EC=9D=B8=20=EA=B0=9D=EC=B2=B4=20=EA=B5=AC=ED=98=84=20-=20?= =?UTF-8?q?=ED=8A=B9=EC=A0=95=20=EA=B8=B0=EC=88=98=20=EC=8B=9D=EB=B3=84?= =?UTF-8?q?=EC=9E=90=EB=A5=BC=20=EA=B8=B0=EB=B0=98=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=ED=8A=B9=EC=A0=95=20=EA=B8=B0=EC=88=98=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EB=B0=8F=20=EC=88=98=EA=B0=95=EC=8B=A0=EC=B2=AD=20=EA=B0=80?= =?UTF-8?q?=EB=8A=A5=EC=97=AC=EB=B6=80=20=ED=8C=90=EB=8B=A8=ED=96=89?= =?UTF-8?q?=EC=9C=84=20Course=EC=97=90=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../courses/cohort/domain/Cohort.java | 50 +++++++++++++++++-- .../courses/cohort/domain/Cohorts.java | 18 +++++++ .../service/repository/CohortRepository.java | 9 ++++ .../courses/course/domain/Course.java | 36 ++++++++++--- .../infrastructure/JdbcCourseRepository.java | 5 +- .../service/repository/CourseRepository.java | 3 +- .../enrollment/service/EnrollmentService.java | 43 ++++++++++++++++ .../service/dto/EnrollmentSaveRequest.java | 38 ++++++++++++++ .../nextstep/payments/domain/Payment.java | 6 +-- .../payments/service/PaymentService.java | 4 ++ .../courses/cohort/domain/CohortTest.java | 21 +++++--- .../courses/cohort/domain/CohortsTest.java | 23 +++++++++ .../cohort/domain/fixture/CohortFixture.java | 48 ++++++++++++++++++ .../courses/course/domain/CourseTest.java | 33 ++++++++++++ .../infrastructure/CourseRepositoryTest.java | 2 +- 15 files changed, 316 insertions(+), 23 deletions(-) create mode 100644 src/main/java/nextstep/courses/cohort/domain/Cohorts.java create mode 100644 src/main/java/nextstep/courses/cohort/service/repository/CohortRepository.java create mode 100644 src/main/java/nextstep/courses/enrollment/service/EnrollmentService.java create mode 100644 src/main/java/nextstep/courses/enrollment/service/dto/EnrollmentSaveRequest.java create mode 100644 src/test/java/nextstep/courses/cohort/domain/CohortsTest.java create mode 100644 src/test/java/nextstep/courses/cohort/domain/fixture/CohortFixture.java diff --git a/src/main/java/nextstep/courses/cohort/domain/Cohort.java b/src/main/java/nextstep/courses/cohort/domain/Cohort.java index 4f9bc0aef..1185f3746 100644 --- a/src/main/java/nextstep/courses/cohort/domain/Cohort.java +++ b/src/main/java/nextstep/courses/cohort/domain/Cohort.java @@ -1,12 +1,16 @@ package nextstep.courses.cohort.domain; +import static java.util.Objects.isNull; + import java.time.LocalDateTime; +import java.util.Objects; import nextstep.common.domain.BaseEntity; import nextstep.courses.cohort.domain.enumeration.CohortStateType; public class Cohort extends BaseEntity { private final Long courseId; + private int cohortCount; private int maxStudentCount; private int presentStudentCount; private CohortStateType cohortStateType; @@ -15,6 +19,7 @@ public class Cohort extends BaseEntity { public Cohort( Long courseId, + int cohortCount, int maxStudentCount, int presentStudentCount, LocalDateTime registerStartDate, @@ -22,12 +27,13 @@ public Cohort( LocalDateTime cohortStartDate, LocalDateTime cohortEndDate ) { - this(0L, courseId, maxStudentCount, presentStudentCount, CohortStateType.PREPARE, registerStartDate, registerEndDate, cohortStartDate, cohortEndDate, null, null); + this(0L, courseId, cohortCount, maxStudentCount, presentStudentCount, CohortStateType.PREPARE, registerStartDate, registerEndDate, cohortStartDate, cohortEndDate, null, null); } public Cohort( Long id, Long courseId, + int cohortCount, int maxStudentCount, int presentStudentCount, CohortStateType cohortStateType, @@ -43,7 +49,12 @@ public Cohort( throw new IllegalArgumentException("기수는 관련 코스정보가 필수 입니다."); } + if (cohortCount <= 0) { + throw new IllegalArgumentException("기수는 회차정보가 필수 입니다."); + } + this.courseId = courseId; + this.cohortCount = cohortCount; this.maxStudentCount = maxStudentCount; this.presentStudentCount = presentStudentCount; this.cohortStateType = cohortStateType; @@ -52,9 +63,9 @@ public Cohort( } public boolean isCanResist() { -// if (this.cohortStateType.equals(CohortStateType.PREPARE)) { -// // 고민.. 위에서 말했던 대로 약간... 수강신청가능 같은 어떤 상태가 필요할듯 -// } + if (!this.cohortStateType.equals(CohortStateType.PREPARE)) { + return false; + } return this.maxStudentCount > this.presentStudentCount; } @@ -85,7 +96,38 @@ public void putOnActive(LocalDateTime now) { this.cohortStateType = CohortStateType.ACTIVE; } + public boolean isSameCohortId(Long cohortId) { + if (isNull(cohortId)) { + return false; + } + + return super.getId().equals(cohortId); + } + public CohortStateType cohortStateType() { return this.cohortStateType; } + + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + Cohort cohort = (Cohort) o; + return cohortCount == cohort.cohortCount && maxStudentCount == cohort.maxStudentCount + && presentStudentCount == cohort.presentStudentCount && Objects.equals( + courseId, cohort.courseId) && cohortStateType == cohort.cohortStateType + && Objects.equals(registerPeriod, cohort.registerPeriod) + && Objects.equals(cohortPeriod, cohort.cohortPeriod); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), courseId, cohortCount, maxStudentCount, + presentStudentCount, cohortStateType, registerPeriod, cohortPeriod); + } } diff --git a/src/main/java/nextstep/courses/cohort/domain/Cohorts.java b/src/main/java/nextstep/courses/cohort/domain/Cohorts.java new file mode 100644 index 000000000..52a8e5e91 --- /dev/null +++ b/src/main/java/nextstep/courses/cohort/domain/Cohorts.java @@ -0,0 +1,18 @@ +package nextstep.courses.cohort.domain; + +import java.util.List; +import java.util.Optional; + +public class Cohorts { + private List cohorts; + + public Cohorts(List cohorts) { + this.cohorts = cohorts; + } + + public Optional findCohortById(Long cohortId) { + return cohorts.stream() + .filter(cohort -> cohort.isSameCohortId(cohortId)) + .findFirst(); + } +} diff --git a/src/main/java/nextstep/courses/cohort/service/repository/CohortRepository.java b/src/main/java/nextstep/courses/cohort/service/repository/CohortRepository.java new file mode 100644 index 000000000..149fbd3ac --- /dev/null +++ b/src/main/java/nextstep/courses/cohort/service/repository/CohortRepository.java @@ -0,0 +1,9 @@ +package nextstep.courses.cohort.service.repository; + +import java.util.Optional; +import nextstep.courses.cohort.domain.Cohort; + +public interface CohortRepository { + + Optional findById(Long id); +} diff --git a/src/main/java/nextstep/courses/course/domain/Course.java b/src/main/java/nextstep/courses/course/domain/Course.java index bb12ebef8..5d0557d9a 100644 --- a/src/main/java/nextstep/courses/course/domain/Course.java +++ b/src/main/java/nextstep/courses/course/domain/Course.java @@ -4,8 +4,11 @@ import static org.springframework.util.StringUtils.hasText; import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Optional; import nextstep.common.domain.BaseEntity; -import nextstep.courses.cohort.domain.enumeration.CohortStateType; +import nextstep.courses.cohort.domain.Cohort; +import nextstep.courses.cohort.domain.Cohorts; import nextstep.courses.course.domain.enumaration.CourseChargeType; public class Course extends BaseEntity { @@ -13,16 +16,20 @@ public class Course extends BaseEntity { private String title; private Long creatorId; private CourseChargeType courseChargeType; - private CohortStateType cohortStateType; + private Cohorts cohorts; public Course(String title, Long creatorId) { - this(0L, title, creatorId, LocalDateTime.now(), null, CourseChargeType.PAID); + this(0L, title, creatorId, LocalDateTime.now(), null, CourseChargeType.PAID, new Cohorts(new ArrayList<>())); } // TODO 중요한 도메인 객체에서 여러가지 생성자 버전을 외부에 제공할때 식별자를 외부에 열어놓는다는것 자체가 아직은 좀 불안하네요. // 일단 주생성자에만 식별자 파라미터를 열어놓고 그 외엔 모두 닫아놓는 방식으로 구현해보려고 하는데 어떻게 생각하시나요? public Course(String title, Long creatorId, CourseChargeType courseChargeType) { - this(0L, title, creatorId, LocalDateTime.now(), null, courseChargeType); + this(0L, title, creatorId, LocalDateTime.now(), null, courseChargeType, new Cohorts(new ArrayList<>())); + } + + public Course(String title, Long creatorId, CourseChargeType courseChargeType, Cohorts cohorts) { + this(0L, title, creatorId, LocalDateTime.now(), null, courseChargeType, cohorts); } public Course( @@ -32,7 +39,7 @@ public Course( LocalDateTime createdAt, LocalDateTime updatedAt ) { - this(id, title, creatorId, createdAt, updatedAt, CourseChargeType.PAID); + this(id, title, creatorId, createdAt, updatedAt, CourseChargeType.PAID, new Cohorts(new ArrayList<>())); } public Course( @@ -41,7 +48,8 @@ public Course( Long creatorId, LocalDateTime createdAt, LocalDateTime updatedAt, - CourseChargeType courseChargeType + CourseChargeType courseChargeType, + Cohorts cohorts ) { super(id, createdAt, updatedAt); @@ -60,6 +68,7 @@ public Course( this.title = title; this.creatorId = creatorId; this.courseChargeType = courseChargeType; + this.cohorts = cohorts; } public boolean isPaid() { @@ -70,6 +79,21 @@ public boolean isFree() { return this.courseChargeType.equals(CourseChargeType.FREE); } + public boolean isCanEnrollBy(Long cohortId) { + if (isNull(cohortId)) { + return false; + } + + if (isNull(this.cohorts)) { + return false; + } + + Optional cohort = this.cohorts.findCohortById(cohortId); + + return cohort.map(Cohort::isCanResist).orElse(false); + + } + public String getTitle() { return title; } diff --git a/src/main/java/nextstep/courses/course/infrastructure/JdbcCourseRepository.java b/src/main/java/nextstep/courses/course/infrastructure/JdbcCourseRepository.java index 2defdddc9..c3a6ebfd1 100644 --- a/src/main/java/nextstep/courses/course/infrastructure/JdbcCourseRepository.java +++ b/src/main/java/nextstep/courses/course/infrastructure/JdbcCourseRepository.java @@ -1,5 +1,6 @@ package nextstep.courses.course.infrastructure; +import java.util.Optional; import nextstep.courses.course.domain.Course; import nextstep.courses.course.service.repository.CourseRepository; import org.springframework.jdbc.core.JdbcOperations; @@ -24,7 +25,7 @@ public int save(Course course) { } @Override - public Course findById(Long id) { + public Optional findById(Long id) { String sql = "select id, title, creator_id, created_at, updated_at from course where id = ?"; RowMapper rowMapper = (rs, rowNum) -> new Course( rs.getLong(1), @@ -32,7 +33,7 @@ public Course findById(Long id) { rs.getLong(3), toLocalDateTime(rs.getTimestamp(4)), toLocalDateTime(rs.getTimestamp(5))); - return jdbcTemplate.queryForObject(sql, rowMapper, id); + return Optional.of(jdbcTemplate.queryForObject(sql, rowMapper, id)); } private LocalDateTime toLocalDateTime(Timestamp timestamp) { diff --git a/src/main/java/nextstep/courses/course/service/repository/CourseRepository.java b/src/main/java/nextstep/courses/course/service/repository/CourseRepository.java index 30606d35f..591138ee3 100644 --- a/src/main/java/nextstep/courses/course/service/repository/CourseRepository.java +++ b/src/main/java/nextstep/courses/course/service/repository/CourseRepository.java @@ -1,9 +1,10 @@ package nextstep.courses.course.service.repository; +import java.util.Optional; import nextstep.courses.course.domain.Course; public interface CourseRepository { int save(Course course); - Course findById(Long id); + Optional findById(Long id); } diff --git a/src/main/java/nextstep/courses/enrollment/service/EnrollmentService.java b/src/main/java/nextstep/courses/enrollment/service/EnrollmentService.java new file mode 100644 index 000000000..bbcbc7dee --- /dev/null +++ b/src/main/java/nextstep/courses/enrollment/service/EnrollmentService.java @@ -0,0 +1,43 @@ +package nextstep.courses.enrollment.service; + +import nextstep.courses.cohort.service.repository.CohortRepository; +import nextstep.courses.course.domain.Course; +import nextstep.courses.course.service.repository.CourseRepository; +import nextstep.courses.enrollment.service.dto.EnrollmentSaveRequest; +import nextstep.qna.exception.unchecked.NotFoundException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class EnrollmentService { + private final CourseRepository courseRepository; + private final CohortRepository cohortRepository; + + @Autowired + public EnrollmentService(CourseRepository courseRepository, CohortRepository cohortRepository) { + this.courseRepository = courseRepository; + this.cohortRepository = cohortRepository; + } + + @Transactional + public void saveEnrollment(EnrollmentSaveRequest request) { + // req 기반 결제정보 확인 + // 결제모듈에 동기 이벤트 발송해서 확인필요 + + Course course = courseRepository.findById(request.getCourseId()) + .orElseThrow(NotFoundException::new); + + // course에 수강가능한지 질문 + + // Course에 cohorts 넣어놓고 조회시에 한번에 땡겨와서 처리해야함. + // course 유/무료상태 확인후 유료인 경우 결제확인 과정 처리 + // cohort 조회 및 모집중 상태확인 + // cohort 수강신청 인원 남았는지 확인 + // + // enrollment 객체 리턴 + + // enrollment 저장. + + } +} diff --git a/src/main/java/nextstep/courses/enrollment/service/dto/EnrollmentSaveRequest.java b/src/main/java/nextstep/courses/enrollment/service/dto/EnrollmentSaveRequest.java new file mode 100644 index 000000000..674cf838c --- /dev/null +++ b/src/main/java/nextstep/courses/enrollment/service/dto/EnrollmentSaveRequest.java @@ -0,0 +1,38 @@ +package nextstep.courses.enrollment.service.dto; + +public class EnrollmentSaveRequest { + private final String paymentId; + private final Long courseId; + private final Long cohortId; + private final Long studentId; + private final Long sessionId; + + public EnrollmentSaveRequest(String paymentId, Long courseId, Long cohortId, Long studentId, + Long sessionId) { + this.paymentId = paymentId; + this.courseId = courseId; + this.cohortId = cohortId; + this.studentId = studentId; + this.sessionId = sessionId; + } + + public String getPaymentId() { + return paymentId; + } + + public Long getCourseId() { + return courseId; + } + + public Long getCohortId() { + return cohortId; + } + + public Long getStudentId() { + return studentId; + } + + public Long getSessionId() { + return sessionId; + } +} diff --git a/src/main/java/nextstep/payments/domain/Payment.java b/src/main/java/nextstep/payments/domain/Payment.java index 57d833f85..3a437be0b 100644 --- a/src/main/java/nextstep/payments/domain/Payment.java +++ b/src/main/java/nextstep/payments/domain/Payment.java @@ -6,7 +6,7 @@ public class Payment { private String id; // 결제한 강의 아이디 - private Long sessionId; + private Long cohortId; // 결제한 사용자 아이디 private Long nsUserId; @@ -19,9 +19,9 @@ public class Payment { public Payment() { } - public Payment(String id, Long sessionId, Long nsUserId, Long amount) { + public Payment(String id, Long cohortId, Long nsUserId, Long amount) { this.id = id; - this.sessionId = sessionId; + this.cohortId = cohortId; this.nsUserId = nsUserId; this.amount = amount; this.createdAt = LocalDateTime.now(); diff --git a/src/main/java/nextstep/payments/service/PaymentService.java b/src/main/java/nextstep/payments/service/PaymentService.java index 372019abb..7c700078c 100644 --- a/src/main/java/nextstep/payments/service/PaymentService.java +++ b/src/main/java/nextstep/payments/service/PaymentService.java @@ -1,10 +1,14 @@ package nextstep.payments.service; import nextstep.payments.domain.Payment; +import org.springframework.stereotype.Service; +@Service public class PaymentService { public Payment payment(String id) { // PG사 API를 통해 id에 해당하는 결제 정보를 반환 return new Payment(); } + + } diff --git a/src/test/java/nextstep/courses/cohort/domain/CohortTest.java b/src/test/java/nextstep/courses/cohort/domain/CohortTest.java index 2ed2ee9be..87d39dfce 100644 --- a/src/test/java/nextstep/courses/cohort/domain/CohortTest.java +++ b/src/test/java/nextstep/courses/cohort/domain/CohortTest.java @@ -1,10 +1,12 @@ package nextstep.courses.cohort.domain; +import static nextstep.courses.cohort.domain.fixture.CohortFixture.식별자를_전달받아_기수픽스처를_생성한다; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.time.LocalDateTime; import nextstep.courses.cohort.domain.enumeration.CohortStateType; +import nextstep.courses.cohort.domain.fixture.CohortFixture; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; @@ -13,7 +15,7 @@ class CohortTest { @Test void 기수생성시_코스식별자가_비정상이면_예외처리_할_수_있다() { - assertThatThrownBy(() -> new Cohort(0L, 1, 0, LocalDateTime.now(), LocalDateTime.now(), + assertThatThrownBy(() -> new Cohort(0L, 5, 1, 0, LocalDateTime.now(), LocalDateTime.now(), LocalDateTime.now(), LocalDateTime.now()) ).isInstanceOf(IllegalArgumentException.class); } @@ -29,7 +31,7 @@ class CohortTest { @CsvSource({"19, true", "20, false"}) void 현재_수강인원을_더_받을수_있는지_확인할_수_있다(int presentStudentCount, boolean result) { // 최대 수강인원, 현재 수강인원 전부 필요할듯. - Cohort cohort = new Cohort(1L, 20, presentStudentCount, LocalDateTime.now(), LocalDateTime.now(), + Cohort cohort = new Cohort(1L, 5, 20, presentStudentCount, LocalDateTime.now(), LocalDateTime.now(), LocalDateTime.now(), LocalDateTime.now()); assertThat(cohort.isCanResist()).isEqualTo(result); @@ -43,7 +45,7 @@ class CohortTest { @Test void 기수의_상태를_수강신청_기간으로_변경할_수_있다() { // 특정 날짜값 받고 + 수강신청 기간인지 확인 + 신청상태로 상태변경 - Cohort cohort = new Cohort(1L, 20, 0, + Cohort cohort = new Cohort(1L, 5, 20, 0, LocalDateTime.of(2025, 1, 1, 0, 0, 0), LocalDateTime.of(2025, 1, 10, 23, 59, 59), LocalDateTime.of(2025, 1, 17, 0, 0, 0), @@ -58,7 +60,7 @@ class CohortTest { @Test void 수강신청기간이_아닌데_기수의_상태를_수강신청_기간으로_변경하면_예외처리_할_수_있다() { // 특정 날짜값 받고 + 수강신청 기간인지 확인 + 신청상태로 상태변경 - Cohort cohort = new Cohort(1L, 20, 0, + Cohort cohort = new Cohort(1L, 5, 20, 0, LocalDateTime.of(2025, 1, 1, 0, 0, 0), LocalDateTime.of(2025, 1, 10, 23, 59, 59), LocalDateTime.of(2025, 1, 17, 0, 0, 0), @@ -73,7 +75,7 @@ class CohortTest { @Test void 기수의_상태를_수강_기간으로_변경할_수_있다() { // 특정 날짜값 받고 + 수강신청 기간인지 확인 + 신청상태로 상태변경 - Cohort cohort = new Cohort(1L, 20, 0, + Cohort cohort = new Cohort(1L, 5, 20, 0, LocalDateTime.of(2025, 1, 1, 0, 0, 0), LocalDateTime.of(2025, 1, 10, 23, 59, 59), LocalDateTime.of(2025, 1, 17, 0, 0, 0), @@ -88,7 +90,7 @@ class CohortTest { @Test void 수강기간이_아닌데_기수의_상태를_수강중으로_변경하면_예외처리_할_수_있다() { // 특정 날짜값 받고 + 수강신청 기간인지 확인 + 신청상태로 상태변경 - Cohort cohort = new Cohort(1L, 20, 0, + Cohort cohort = new Cohort(1L, 5, 20, 0, LocalDateTime.of(2025, 1, 1, 0, 0, 0), LocalDateTime.of(2025, 1, 10, 23, 59, 59), LocalDateTime.of(2025, 1, 17, 0, 0, 0), @@ -99,4 +101,11 @@ class CohortTest { () -> cohort.putOnActive(LocalDateTime.of(2025, 2, 26, 0, 0, 0)) ).isInstanceOf(IllegalArgumentException.class); } + + @Test + void 기수의_식별자가_같은지_식별할_수_있다() { + Cohort cohort = 식별자를_전달받아_기수픽스처를_생성한다(1L); + + assertThat(cohort.isSameCohortId(1L)).isTrue(); + } } \ No newline at end of file diff --git a/src/test/java/nextstep/courses/cohort/domain/CohortsTest.java b/src/test/java/nextstep/courses/cohort/domain/CohortsTest.java new file mode 100644 index 000000000..282751dce --- /dev/null +++ b/src/test/java/nextstep/courses/cohort/domain/CohortsTest.java @@ -0,0 +1,23 @@ +package nextstep.courses.cohort.domain; + +import static nextstep.courses.cohort.domain.fixture.CohortFixture.식별자를_전달받아_기수픽스처를_생성한다; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import org.junit.jupiter.api.Test; + +class CohortsTest { + + @Test + void 기수목록에서_기수식별자로_기수를_찾을수_있다() { + Cohort target = 식별자를_전달받아_기수픽스처를_생성한다(1L); + Cohorts cohorts = new Cohorts(List.of( + target, + 식별자를_전달받아_기수픽스처를_생성한다(2L), + 식별자를_전달받아_기수픽스처를_생성한다(3L) + )); + + assertThat(cohorts.findCohortById(1L).get()).isEqualTo(target); + } + +} \ No newline at end of file diff --git a/src/test/java/nextstep/courses/cohort/domain/fixture/CohortFixture.java b/src/test/java/nextstep/courses/cohort/domain/fixture/CohortFixture.java new file mode 100644 index 000000000..cca0aee86 --- /dev/null +++ b/src/test/java/nextstep/courses/cohort/domain/fixture/CohortFixture.java @@ -0,0 +1,48 @@ +package nextstep.courses.cohort.domain.fixture; + +import java.time.LocalDateTime; +import nextstep.courses.cohort.domain.Cohort; +import nextstep.courses.cohort.domain.enumeration.CohortStateType; + +public class CohortFixture { + + public static Cohort 식별자를_전달받아_기수픽스처를_생성한다(Long id) { + LocalDateTime registerStartDate = LocalDateTime.of(2025, 1, 1, 0, 0, 0); + LocalDateTime registerEndDate = LocalDateTime.of(2025, 1, 2, 0, 0, 0); + + LocalDateTime cohortStartDate = LocalDateTime.of(2025, 1, 3, 0, 0, 0); + LocalDateTime cohortEndDate = LocalDateTime.of(2025, 1, 4, 0, 0, 0); + + LocalDateTime fixDateTime = LocalDateTime.of(2025, 1, 5, 0, 0, 0); + + return new Cohort( + id, 2L, 5, 1, 0, CohortStateType.PREPARE, + registerStartDate, + registerEndDate, + cohortStartDate, + cohortEndDate, + fixDateTime, + fixDateTime + ); + } + + public static Cohort 식별자와_상태를_전달받아_기수픽스처를_생성한다(Long id, CohortStateType cohortStateType) { + LocalDateTime registerStartDate = LocalDateTime.of(2025, 1, 1, 0, 0, 0); + LocalDateTime registerEndDate = LocalDateTime.of(2025, 1, 2, 0, 0, 0); + + LocalDateTime cohortStartDate = LocalDateTime.of(2025, 1, 3, 0, 0, 0); + LocalDateTime cohortEndDate = LocalDateTime.of(2025, 1, 4, 0, 0, 0); + + LocalDateTime fixDateTime = LocalDateTime.of(2025, 1, 5, 0, 0, 0); + + return new Cohort( + id, 2L, 5, 1, 0, cohortStateType, + registerStartDate, + registerEndDate, + cohortStartDate, + cohortEndDate, + fixDateTime, + fixDateTime + ); + } +} diff --git a/src/test/java/nextstep/courses/course/domain/CourseTest.java b/src/test/java/nextstep/courses/course/domain/CourseTest.java index 26172ab8e..ff142cb0a 100644 --- a/src/test/java/nextstep/courses/course/domain/CourseTest.java +++ b/src/test/java/nextstep/courses/course/domain/CourseTest.java @@ -1,8 +1,12 @@ package nextstep.courses.course.domain; +import static nextstep.courses.cohort.domain.fixture.CohortFixture.식별자와_상태를_전달받아_기수픽스처를_생성한다; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import java.util.List; +import nextstep.courses.cohort.domain.Cohorts; +import nextstep.courses.cohort.domain.enumeration.CohortStateType; import nextstep.courses.course.domain.enumaration.CourseChargeType; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -54,4 +58,33 @@ class CourseTest { course.isFree() ).isTrue(); } + + @Test + void 기수식별자를_통해_해당기수가_신청가능한_상태인지_확인할_수_있다() { + Cohorts cohorts = new Cohorts(List.of( + 식별자와_상태를_전달받아_기수픽스처를_생성한다(1L, CohortStateType.END), + 식별자와_상태를_전달받아_기수픽스처를_생성한다(2L, CohortStateType.ACTIVE), + 식별자와_상태를_전달받아_기수픽스처를_생성한다(3L, CohortStateType.PREPARE) + )); + Course course = new Course("TDD, 객체지향 과정", 1L, CourseChargeType.FREE, cohorts); + + assertThat( + course.isCanEnrollBy(3L) + ).isTrue(); + } + + @Test + void 잘못된_기수식별자는_신청가능한_상태인지_확인할_수_없다() { + Cohorts cohorts = new Cohorts(List.of( + 식별자와_상태를_전달받아_기수픽스처를_생성한다(1L, CohortStateType.END), + 식별자와_상태를_전달받아_기수픽스처를_생성한다(2L, CohortStateType.ACTIVE), + 식별자와_상태를_전달받아_기수픽스처를_생성한다(3L, CohortStateType.PREPARE) + )); + Course course = new Course("TDD, 객체지향 과정", 1L, CourseChargeType.FREE, cohorts); + + assertThat(course.isCanEnrollBy(4L)).isFalse(); + assertThat(course.isCanEnrollBy(2L)).isFalse(); + } + + } \ No newline at end of file diff --git a/src/test/java/nextstep/courses/infrastructure/CourseRepositoryTest.java b/src/test/java/nextstep/courses/infrastructure/CourseRepositoryTest.java index 0eb156ef7..e12dea1fb 100644 --- a/src/test/java/nextstep/courses/infrastructure/CourseRepositoryTest.java +++ b/src/test/java/nextstep/courses/infrastructure/CourseRepositoryTest.java @@ -32,7 +32,7 @@ void crud() { Course course = new Course("TDD, 클린 코드 with Java", 1L); int count = courseRepository.save(course); assertThat(count).isEqualTo(1); - Course savedCourse = courseRepository.findById(1L); + Course savedCourse = courseRepository.findById(1L).get(); assertThat(course.getTitle()).isEqualTo(savedCourse.getTitle()); LOGGER.debug("Course: {}", savedCourse); } From 1223af4dca65875fa8c31eb92d313fd8f580c102 Mon Sep 17 00:00:00 2001 From: mins1031 Date: Fri, 19 Dec 2025 20:35:49 +0900 Subject: [PATCH 07/14] =?UTF-8?q?feat=20:=20=EC=88=98=EA=B0=95=EC=8B=A0?= =?UTF-8?q?=EC=B2=AD=202=EB=8B=A8=EA=B3=84=20(=EB=8F=84=EB=A9=94=EC=9D=B8?= =?UTF-8?q?=20=EB=AA=A8=EB=8D=B8)=20-=20=EC=88=98=EA=B0=95=EC=8B=A0?= =?UTF-8?q?=EC=B2=AD=20=EB=B9=84=EC=A6=88=EB=8B=88=EC=8A=A4=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=A1=B0=ED=95=A9=20=EB=B0=8F=20=EB=B6=80=EC=A1=B1?= =?UTF-8?q?=ED=95=9C=20=EB=8F=84=EB=A9=94=EC=9D=B8=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../courses/cohort/domain/Cohort.java | 8 ++++ .../courses/cohort/domain/Cohorts.java | 4 ++ .../service/repository/CohortRepository.java | 2 + .../courses/course/domain/Course.java | 13 ++++++ .../domain/service/CourseDomainService.java | 18 ++++++++ .../courses/course/service/CourseService.java | 5 -- .../service/repository/CourseRepository.java | 4 ++ .../courses/enrollment/domain/Enrollment.java | 29 ++++++++++++ .../enrollment/service/EnrollmentService.java | 46 +++++++++++-------- .../repository/EnrollmentRepository.java | 10 ++++ .../nextstep/payments/domain/Payment.java | 4 ++ .../service/repository/PaymentRepository.java | 10 ++++ .../courses/cohort/domain/CohortTest.java | 11 +++++ 13 files changed, 141 insertions(+), 23 deletions(-) create mode 100644 src/main/java/nextstep/courses/course/domain/service/CourseDomainService.java delete mode 100644 src/main/java/nextstep/courses/course/service/CourseService.java create mode 100644 src/main/java/nextstep/courses/enrollment/domain/Enrollment.java create mode 100644 src/main/java/nextstep/courses/enrollment/service/repository/EnrollmentRepository.java create mode 100644 src/main/java/nextstep/payments/service/repository/PaymentRepository.java diff --git a/src/main/java/nextstep/courses/cohort/domain/Cohort.java b/src/main/java/nextstep/courses/cohort/domain/Cohort.java index 1185f3746..ecfc88675 100644 --- a/src/main/java/nextstep/courses/cohort/domain/Cohort.java +++ b/src/main/java/nextstep/courses/cohort/domain/Cohort.java @@ -104,6 +104,14 @@ public boolean isSameCohortId(Long cohortId) { return super.getId().equals(cohortId); } + public void plusOnePresent() { + if(!isCanResist()) { + return; + } + + this.presentStudentCount++; + } + public CohortStateType cohortStateType() { return this.cohortStateType; } diff --git a/src/main/java/nextstep/courses/cohort/domain/Cohorts.java b/src/main/java/nextstep/courses/cohort/domain/Cohorts.java index 52a8e5e91..c01461df1 100644 --- a/src/main/java/nextstep/courses/cohort/domain/Cohorts.java +++ b/src/main/java/nextstep/courses/cohort/domain/Cohorts.java @@ -15,4 +15,8 @@ public Optional findCohortById(Long cohortId) { .filter(cohort -> cohort.isSameCohortId(cohortId)) .findFirst(); } + + public void plusOnePresent(Long cohortId) { + findCohortById(cohortId).ifPresent(Cohort::plusOnePresent); + } } diff --git a/src/main/java/nextstep/courses/cohort/service/repository/CohortRepository.java b/src/main/java/nextstep/courses/cohort/service/repository/CohortRepository.java index 149fbd3ac..1709fbd4d 100644 --- a/src/main/java/nextstep/courses/cohort/service/repository/CohortRepository.java +++ b/src/main/java/nextstep/courses/cohort/service/repository/CohortRepository.java @@ -2,7 +2,9 @@ import java.util.Optional; import nextstep.courses.cohort.domain.Cohort; +import org.springframework.stereotype.Repository; +@Repository public interface CohortRepository { Optional findById(Long id); diff --git a/src/main/java/nextstep/courses/course/domain/Course.java b/src/main/java/nextstep/courses/course/domain/Course.java index 5d0557d9a..2587be968 100644 --- a/src/main/java/nextstep/courses/course/domain/Course.java +++ b/src/main/java/nextstep/courses/course/domain/Course.java @@ -79,6 +79,7 @@ public boolean isFree() { return this.courseChargeType.equals(CourseChargeType.FREE); } + public boolean isCanEnrollBy(Long cohortId) { if (isNull(cohortId)) { return false; @@ -94,6 +95,18 @@ public boolean isCanEnrollBy(Long cohortId) { } + public void plusOnePresentStudent(Long cohortId) { + if (isNull(cohortId)) { + return; + } + + if (!isCanEnrollBy(cohortId)) { + return; + } + + this.cohorts.plusOnePresent(cohortId); + } + public String getTitle() { return title; } diff --git a/src/main/java/nextstep/courses/course/domain/service/CourseDomainService.java b/src/main/java/nextstep/courses/course/domain/service/CourseDomainService.java new file mode 100644 index 000000000..e6f900c2e --- /dev/null +++ b/src/main/java/nextstep/courses/course/domain/service/CourseDomainService.java @@ -0,0 +1,18 @@ +package nextstep.courses.course.domain.service; + +import nextstep.courses.course.domain.Course; +import nextstep.courses.enrollment.domain.Enrollment; + +public class CourseDomainService { + + public Enrollment registerEnrollment(Course course, Long cohortId, Long studentId) { + // 결제정보도 적용필요 + + if (!course.isCanEnrollBy(cohortId)) { + throw new IllegalArgumentException("해당기수는 수강신청 할 수 없는 상태입니다"); + } + course.plusOnePresentStudent(cohortId); + + return new Enrollment(studentId, cohortId); + } +} diff --git a/src/main/java/nextstep/courses/course/service/CourseService.java b/src/main/java/nextstep/courses/course/service/CourseService.java deleted file mode 100644 index 352134893..000000000 --- a/src/main/java/nextstep/courses/course/service/CourseService.java +++ /dev/null @@ -1,5 +0,0 @@ -package nextstep.courses.course.service; - -public class CourseService { - -} diff --git a/src/main/java/nextstep/courses/course/service/repository/CourseRepository.java b/src/main/java/nextstep/courses/course/service/repository/CourseRepository.java index 591138ee3..705a3b323 100644 --- a/src/main/java/nextstep/courses/course/service/repository/CourseRepository.java +++ b/src/main/java/nextstep/courses/course/service/repository/CourseRepository.java @@ -2,9 +2,13 @@ import java.util.Optional; import nextstep.courses.course.domain.Course; +import org.springframework.stereotype.Repository; +@Repository public interface CourseRepository { int save(Course course); Optional findById(Long id); + + void plusOnePresentCount(); } diff --git a/src/main/java/nextstep/courses/enrollment/domain/Enrollment.java b/src/main/java/nextstep/courses/enrollment/domain/Enrollment.java new file mode 100644 index 000000000..57b672936 --- /dev/null +++ b/src/main/java/nextstep/courses/enrollment/domain/Enrollment.java @@ -0,0 +1,29 @@ +package nextstep.courses.enrollment.domain; + +import java.time.LocalDateTime; +import nextstep.common.domain.BaseEntity; + +public class Enrollment extends BaseEntity { + + private Long studentId; + private Long cohortId; + + public Enrollment( + Long studentId, + Long cohortId + ) { + this(0L, LocalDateTime.now(), LocalDateTime.now(), studentId, cohortId); + } + + public Enrollment( + Long id, + LocalDateTime createdDate, + LocalDateTime updatedDate, + Long studentId, + Long cohortId + ) { + super(id, createdDate, updatedDate); + this.studentId = studentId; + this.cohortId = cohortId; + } +} diff --git a/src/main/java/nextstep/courses/enrollment/service/EnrollmentService.java b/src/main/java/nextstep/courses/enrollment/service/EnrollmentService.java index bbcbc7dee..1401c4010 100644 --- a/src/main/java/nextstep/courses/enrollment/service/EnrollmentService.java +++ b/src/main/java/nextstep/courses/enrollment/service/EnrollmentService.java @@ -2,8 +2,13 @@ import nextstep.courses.cohort.service.repository.CohortRepository; import nextstep.courses.course.domain.Course; +import nextstep.courses.course.domain.service.CourseDomainService; import nextstep.courses.course.service.repository.CourseRepository; +import nextstep.courses.enrollment.domain.Enrollment; import nextstep.courses.enrollment.service.dto.EnrollmentSaveRequest; +import nextstep.courses.enrollment.service.repository.EnrollmentRepository; +import nextstep.payments.domain.Payment; +import nextstep.payments.service.repository.PaymentRepository; import nextstep.qna.exception.unchecked.NotFoundException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -12,32 +17,37 @@ @Service public class EnrollmentService { private final CourseRepository courseRepository; - private final CohortRepository cohortRepository; + private final EnrollmentRepository enrollmentRepository; + private final PaymentRepository paymentRepository; @Autowired - public EnrollmentService(CourseRepository courseRepository, CohortRepository cohortRepository) { + public EnrollmentService( + CourseRepository courseRepository, + EnrollmentRepository enrollmentRepository, + PaymentRepository paymentRepository + ) { this.courseRepository = courseRepository; - this.cohortRepository = cohortRepository; + this.enrollmentRepository = enrollmentRepository; + this.paymentRepository = paymentRepository; } + @Transactional public void saveEnrollment(EnrollmentSaveRequest request) { - // req 기반 결제정보 확인 - // 결제모듈에 동기 이벤트 발송해서 확인필요 - Course course = courseRepository.findById(request.getCourseId()) .orElseThrow(NotFoundException::new); - - // course에 수강가능한지 질문 - - // Course에 cohorts 넣어놓고 조회시에 한번에 땡겨와서 처리해야함. - // course 유/무료상태 확인후 유료인 경우 결제확인 과정 처리 - // cohort 조회 및 모집중 상태확인 - // cohort 수강신청 인원 남았는지 확인 - // - // enrollment 객체 리턴 - - // enrollment 저장. - + if (course.isPaid()) { + Payment payment = paymentRepository.findById(request.getPaymentId()) + .orElseThrow(NotFoundException::new); + if (!payment.isPayedCohort(request.getCohortId())) { + throw new IllegalArgumentException("결제정보와 강의정보가 상이합니다."); + } + } + + Enrollment enrollment = new CourseDomainService().registerEnrollment( + course, request.getCohortId(), request.getStudentId()); + + enrollmentRepository.save(enrollment); + courseRepository.plusOnePresentCount(); } } diff --git a/src/main/java/nextstep/courses/enrollment/service/repository/EnrollmentRepository.java b/src/main/java/nextstep/courses/enrollment/service/repository/EnrollmentRepository.java new file mode 100644 index 000000000..3df1d3f4c --- /dev/null +++ b/src/main/java/nextstep/courses/enrollment/service/repository/EnrollmentRepository.java @@ -0,0 +1,10 @@ +package nextstep.courses.enrollment.service.repository; + +import nextstep.courses.enrollment.domain.Enrollment; +import org.springframework.stereotype.Repository; + +@Repository +public interface EnrollmentRepository { + + Enrollment save(Enrollment enrollment); +} diff --git a/src/main/java/nextstep/payments/domain/Payment.java b/src/main/java/nextstep/payments/domain/Payment.java index 3a437be0b..fd18a0a32 100644 --- a/src/main/java/nextstep/payments/domain/Payment.java +++ b/src/main/java/nextstep/payments/domain/Payment.java @@ -26,4 +26,8 @@ public Payment(String id, Long cohortId, Long nsUserId, Long amount) { this.amount = amount; this.createdAt = LocalDateTime.now(); } + + public boolean isPayedCohort(Long cohortId) { + return this.cohortId.equals(cohortId); + } } diff --git a/src/main/java/nextstep/payments/service/repository/PaymentRepository.java b/src/main/java/nextstep/payments/service/repository/PaymentRepository.java new file mode 100644 index 000000000..00dd06664 --- /dev/null +++ b/src/main/java/nextstep/payments/service/repository/PaymentRepository.java @@ -0,0 +1,10 @@ +package nextstep.payments.service.repository; + +import java.util.Optional; +import nextstep.payments.domain.Payment; +import org.springframework.stereotype.Repository; + +@Repository +public interface PaymentRepository { + Optional findById(String id); +} diff --git a/src/test/java/nextstep/courses/cohort/domain/CohortTest.java b/src/test/java/nextstep/courses/cohort/domain/CohortTest.java index 87d39dfce..9208dd316 100644 --- a/src/test/java/nextstep/courses/cohort/domain/CohortTest.java +++ b/src/test/java/nextstep/courses/cohort/domain/CohortTest.java @@ -108,4 +108,15 @@ class CohortTest { assertThat(cohort.isSameCohortId(1L)).isTrue(); } + + @Test + void 기수의_현재수강인원을_1만큼_증가시킬_수_있다() { + Cohort cohort = new Cohort(1L, 5, 20, 19, LocalDateTime.now(), LocalDateTime.now(), + LocalDateTime.now(), LocalDateTime.now()); + assertThat(cohort.isCanResist()).isTrue(); + + cohort.plusOnePresent(); + + assertThat(cohort.isCanResist()).isFalse(); + } } \ No newline at end of file From 95f2b825bcc8ac0f075d2cbc20753e9da30cbe99 Mon Sep 17 00:00:00 2001 From: mins1031 Date: Fri, 19 Dec 2025 20:37:45 +0900 Subject: [PATCH 08/14] =?UTF-8?q?feat=20:=20=EC=88=98=EA=B0=95=EC=8B=A0?= =?UTF-8?q?=EC=B2=AD=202=EB=8B=A8=EA=B3=84=20(=EB=8F=84=EB=A9=94=EC=9D=B8?= =?UTF-8?q?=20=EB=AA=A8=EB=8D=B8)=20-=20=EB=B0=A9=ED=96=A5=EC=84=B1=20?= =?UTF-8?q?=EC=A3=BC=EC=84=9D=20=EB=A9=94=EB=AA=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../courses/course/domain/service/CourseDomainService.java | 2 -- .../nextstep/courses/enrollment/service/EnrollmentService.java | 3 ++- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/nextstep/courses/course/domain/service/CourseDomainService.java b/src/main/java/nextstep/courses/course/domain/service/CourseDomainService.java index e6f900c2e..4b815f7d2 100644 --- a/src/main/java/nextstep/courses/course/domain/service/CourseDomainService.java +++ b/src/main/java/nextstep/courses/course/domain/service/CourseDomainService.java @@ -6,8 +6,6 @@ public class CourseDomainService { public Enrollment registerEnrollment(Course course, Long cohortId, Long studentId) { - // 결제정보도 적용필요 - if (!course.isCanEnrollBy(cohortId)) { throw new IllegalArgumentException("해당기수는 수강신청 할 수 없는 상태입니다"); } diff --git a/src/main/java/nextstep/courses/enrollment/service/EnrollmentService.java b/src/main/java/nextstep/courses/enrollment/service/EnrollmentService.java index 1401c4010..d63ec996c 100644 --- a/src/main/java/nextstep/courses/enrollment/service/EnrollmentService.java +++ b/src/main/java/nextstep/courses/enrollment/service/EnrollmentService.java @@ -1,6 +1,5 @@ package nextstep.courses.enrollment.service; -import nextstep.courses.cohort.service.repository.CohortRepository; import nextstep.courses.course.domain.Course; import nextstep.courses.course.domain.service.CourseDomainService; import nextstep.courses.course.service.repository.CourseRepository; @@ -16,6 +15,7 @@ @Service public class EnrollmentService { + private final CourseRepository courseRepository; private final EnrollmentRepository enrollmentRepository; private final PaymentRepository paymentRepository; @@ -37,6 +37,7 @@ public void saveEnrollment(EnrollmentSaveRequest request) { Course course = courseRepository.findById(request.getCourseId()) .orElseThrow(NotFoundException::new); if (course.isPaid()) { + // TODO 결제 모듈 이벤트 처리로 리팩토링 예정. Payment payment = paymentRepository.findById(request.getPaymentId()) .orElseThrow(NotFoundException::new); if (!payment.isPayedCohort(request.getCohortId())) { From 4d1f6d3b7811b6bef2b440dee6a0feb79b10a338 Mon Sep 17 00:00:00 2001 From: mins1031 Date: Sat, 20 Dec 2025 16:31:16 +0900 Subject: [PATCH 09/14] =?UTF-8?q?refactor=20:=20=EC=88=98=EA=B0=95?= =?UTF-8?q?=EC=8B=A0=EC=B2=AD=202=EB=8B=A8=EA=B3=84=20(=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EB=AA=A8=EB=8D=B8)=20-=20=EC=97=90=EA=B7=B8?= =?UTF-8?q?=EB=A6=AC=EC=BB=A4=ED=8A=B8=20=EA=B0=9C=EB=85=90=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0=EB=A1=9C=20=EC=88=98=EA=B0=95=EC=8B=A0=EC=B2=AD=20Coh?= =?UTF-8?q?ort=20=EA=B8=B0=EB=B0=98=EC=9C=BC=EB=A1=9C=20=EB=8F=99=EC=9E=91?= =?UTF-8?q?=20-=20Course=EC=9D=98=20=EB=B6=88=ED=95=84=EC=9A=94=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../courses/cohort/domain/Cohort.java | 12 +++-- .../courses/cohort/domain/Cohorts.java | 10 ----- .../domain/service/CohortDomainService.java | 22 +++++++++ .../courses/course/domain/Course.java | 45 +++---------------- .../domain/service/CourseDomainService.java | 16 ------- .../infrastructure/JdbcCourseRepository.java | 5 +++ .../enrollment/service/EnrollmentService.java | 17 ++++--- .../courses/cohort/domain/CohortTest.java | 7 ++- .../courses/cohort/domain/CohortsTest.java | 23 ---------- .../courses/course/domain/CourseTest.java | 28 ------------ 10 files changed, 55 insertions(+), 130 deletions(-) create mode 100644 src/main/java/nextstep/courses/cohort/domain/service/CohortDomainService.java delete mode 100644 src/main/java/nextstep/courses/course/domain/service/CourseDomainService.java delete mode 100644 src/test/java/nextstep/courses/cohort/domain/CohortsTest.java diff --git a/src/main/java/nextstep/courses/cohort/domain/Cohort.java b/src/main/java/nextstep/courses/cohort/domain/Cohort.java index ecfc88675..ad43e16bc 100644 --- a/src/main/java/nextstep/courses/cohort/domain/Cohort.java +++ b/src/main/java/nextstep/courses/cohort/domain/Cohort.java @@ -96,15 +96,15 @@ public void putOnActive(LocalDateTime now) { this.cohortStateType = CohortStateType.ACTIVE; } - public boolean isSameCohortId(Long cohortId) { - if (isNull(cohortId)) { + public boolean isSameCourseId(Long courseId) { + if (isNull(courseId)) { return false; } - return super.getId().equals(cohortId); + return this.courseId.equals(courseId); } - public void plusOnePresent() { + public void registerStudent() { if(!isCanResist()) { return; } @@ -112,10 +112,14 @@ public void plusOnePresent() { this.presentStudentCount++; } + public CohortStateType cohortStateType() { return this.cohortStateType; } + public Long getId() { + return super.getId(); + } @Override public boolean equals(Object o) { diff --git a/src/main/java/nextstep/courses/cohort/domain/Cohorts.java b/src/main/java/nextstep/courses/cohort/domain/Cohorts.java index c01461df1..d507d1582 100644 --- a/src/main/java/nextstep/courses/cohort/domain/Cohorts.java +++ b/src/main/java/nextstep/courses/cohort/domain/Cohorts.java @@ -9,14 +9,4 @@ public class Cohorts { public Cohorts(List cohorts) { this.cohorts = cohorts; } - - public Optional findCohortById(Long cohortId) { - return cohorts.stream() - .filter(cohort -> cohort.isSameCohortId(cohortId)) - .findFirst(); - } - - public void plusOnePresent(Long cohortId) { - findCohortById(cohortId).ifPresent(Cohort::plusOnePresent); - } } diff --git a/src/main/java/nextstep/courses/cohort/domain/service/CohortDomainService.java b/src/main/java/nextstep/courses/cohort/domain/service/CohortDomainService.java new file mode 100644 index 000000000..201ae1887 --- /dev/null +++ b/src/main/java/nextstep/courses/cohort/domain/service/CohortDomainService.java @@ -0,0 +1,22 @@ +package nextstep.courses.cohort.domain.service; + +import nextstep.courses.cohort.domain.Cohort; +import nextstep.courses.enrollment.domain.Enrollment; + +public class CohortDomainService { + + public Enrollment registerEnrollment(Cohort cohort, Long studentId, Long courseId) { + if (!cohort.isCanResist()) { + throw new IllegalArgumentException("해당기수는 수강신청 할 수 없는 상태입니다"); + } + if (!cohort.isSameCourseId(courseId)) { + throw new IllegalArgumentException("결제정보와 강의정보가 상이합니다."); + } + + cohort.registerStudent(); + + // Cohort에서 studentId를 전달받아 Enrollment 반환 vs 외부에서 조합해서 Enrollment 반환하는 대신 id getter 오픈 + // Cohort에서 Enrollment를 생성하는 동작을 가지고 있는것 보다 외부에서 하는며 두객체의 결합도를 낮추는게 더 좋다고 생각합니다 + return new Enrollment(studentId, cohort.getId()); + } +} diff --git a/src/main/java/nextstep/courses/course/domain/Course.java b/src/main/java/nextstep/courses/course/domain/Course.java index 2587be968..af5df1097 100644 --- a/src/main/java/nextstep/courses/course/domain/Course.java +++ b/src/main/java/nextstep/courses/course/domain/Course.java @@ -5,9 +5,7 @@ import java.time.LocalDateTime; import java.util.ArrayList; -import java.util.Optional; import nextstep.common.domain.BaseEntity; -import nextstep.courses.cohort.domain.Cohort; import nextstep.courses.cohort.domain.Cohorts; import nextstep.courses.course.domain.enumaration.CourseChargeType; @@ -19,17 +17,13 @@ public class Course extends BaseEntity { private Cohorts cohorts; public Course(String title, Long creatorId) { - this(0L, title, creatorId, LocalDateTime.now(), null, CourseChargeType.PAID, new Cohorts(new ArrayList<>())); + this(0L, title, creatorId, LocalDateTime.now(), null, CourseChargeType.PAID, + new Cohorts(new ArrayList<>())); } - // TODO 중요한 도메인 객체에서 여러가지 생성자 버전을 외부에 제공할때 식별자를 외부에 열어놓는다는것 자체가 아직은 좀 불안하네요. - // 일단 주생성자에만 식별자 파라미터를 열어놓고 그 외엔 모두 닫아놓는 방식으로 구현해보려고 하는데 어떻게 생각하시나요? public Course(String title, Long creatorId, CourseChargeType courseChargeType) { - this(0L, title, creatorId, LocalDateTime.now(), null, courseChargeType, new Cohorts(new ArrayList<>())); - } - - public Course(String title, Long creatorId, CourseChargeType courseChargeType, Cohorts cohorts) { - this(0L, title, creatorId, LocalDateTime.now(), null, courseChargeType, cohorts); + this(0L, title, creatorId, LocalDateTime.now(), null, courseChargeType, + new Cohorts(new ArrayList<>())); } public Course( @@ -39,7 +33,8 @@ public Course( LocalDateTime createdAt, LocalDateTime updatedAt ) { - this(id, title, creatorId, createdAt, updatedAt, CourseChargeType.PAID, new Cohorts(new ArrayList<>())); + this(id, title, creatorId, createdAt, updatedAt, CourseChargeType.PAID, + new Cohorts(new ArrayList<>())); } public Course( @@ -79,34 +74,6 @@ public boolean isFree() { return this.courseChargeType.equals(CourseChargeType.FREE); } - - public boolean isCanEnrollBy(Long cohortId) { - if (isNull(cohortId)) { - return false; - } - - if (isNull(this.cohorts)) { - return false; - } - - Optional cohort = this.cohorts.findCohortById(cohortId); - - return cohort.map(Cohort::isCanResist).orElse(false); - - } - - public void plusOnePresentStudent(Long cohortId) { - if (isNull(cohortId)) { - return; - } - - if (!isCanEnrollBy(cohortId)) { - return; - } - - this.cohorts.plusOnePresent(cohortId); - } - public String getTitle() { return title; } diff --git a/src/main/java/nextstep/courses/course/domain/service/CourseDomainService.java b/src/main/java/nextstep/courses/course/domain/service/CourseDomainService.java deleted file mode 100644 index 4b815f7d2..000000000 --- a/src/main/java/nextstep/courses/course/domain/service/CourseDomainService.java +++ /dev/null @@ -1,16 +0,0 @@ -package nextstep.courses.course.domain.service; - -import nextstep.courses.course.domain.Course; -import nextstep.courses.enrollment.domain.Enrollment; - -public class CourseDomainService { - - public Enrollment registerEnrollment(Course course, Long cohortId, Long studentId) { - if (!course.isCanEnrollBy(cohortId)) { - throw new IllegalArgumentException("해당기수는 수강신청 할 수 없는 상태입니다"); - } - course.plusOnePresentStudent(cohortId); - - return new Enrollment(studentId, cohortId); - } -} diff --git a/src/main/java/nextstep/courses/course/infrastructure/JdbcCourseRepository.java b/src/main/java/nextstep/courses/course/infrastructure/JdbcCourseRepository.java index c3a6ebfd1..98a082ab6 100644 --- a/src/main/java/nextstep/courses/course/infrastructure/JdbcCourseRepository.java +++ b/src/main/java/nextstep/courses/course/infrastructure/JdbcCourseRepository.java @@ -36,6 +36,11 @@ public Optional findById(Long id) { return Optional.of(jdbcTemplate.queryForObject(sql, rowMapper, id)); } + @Override + public void plusOnePresentCount() { + + } + private LocalDateTime toLocalDateTime(Timestamp timestamp) { if (timestamp == null) { return null; diff --git a/src/main/java/nextstep/courses/enrollment/service/EnrollmentService.java b/src/main/java/nextstep/courses/enrollment/service/EnrollmentService.java index d63ec996c..84ccda22e 100644 --- a/src/main/java/nextstep/courses/enrollment/service/EnrollmentService.java +++ b/src/main/java/nextstep/courses/enrollment/service/EnrollmentService.java @@ -1,7 +1,9 @@ package nextstep.courses.enrollment.service; +import nextstep.courses.cohort.domain.Cohort; +import nextstep.courses.cohort.domain.service.CohortDomainService; +import nextstep.courses.cohort.service.repository.CohortRepository; import nextstep.courses.course.domain.Course; -import nextstep.courses.course.domain.service.CourseDomainService; import nextstep.courses.course.service.repository.CourseRepository; import nextstep.courses.enrollment.domain.Enrollment; import nextstep.courses.enrollment.service.dto.EnrollmentSaveRequest; @@ -19,25 +21,26 @@ public class EnrollmentService { private final CourseRepository courseRepository; private final EnrollmentRepository enrollmentRepository; private final PaymentRepository paymentRepository; + private final CohortRepository cohortRepository; @Autowired public EnrollmentService( CourseRepository courseRepository, EnrollmentRepository enrollmentRepository, - PaymentRepository paymentRepository + PaymentRepository paymentRepository, + CohortRepository cohortRepository ) { this.courseRepository = courseRepository; this.enrollmentRepository = enrollmentRepository; this.paymentRepository = paymentRepository; + this.cohortRepository = cohortRepository; } - @Transactional public void saveEnrollment(EnrollmentSaveRequest request) { Course course = courseRepository.findById(request.getCourseId()) .orElseThrow(NotFoundException::new); if (course.isPaid()) { - // TODO 결제 모듈 이벤트 처리로 리팩토링 예정. Payment payment = paymentRepository.findById(request.getPaymentId()) .orElseThrow(NotFoundException::new); if (!payment.isPayedCohort(request.getCohortId())) { @@ -45,8 +48,10 @@ public void saveEnrollment(EnrollmentSaveRequest request) { } } - Enrollment enrollment = new CourseDomainService().registerEnrollment( - course, request.getCohortId(), request.getStudentId()); + Cohort cohort = cohortRepository.findById(request.getCohortId()) + .orElseThrow(NotFoundException::new); + Enrollment enrollment = new CohortDomainService().registerEnrollment( + cohort, request.getStudentId(), request.getCourseId()); enrollmentRepository.save(enrollment); courseRepository.plusOnePresentCount(); diff --git a/src/test/java/nextstep/courses/cohort/domain/CohortTest.java b/src/test/java/nextstep/courses/cohort/domain/CohortTest.java index 9208dd316..bf54f0973 100644 --- a/src/test/java/nextstep/courses/cohort/domain/CohortTest.java +++ b/src/test/java/nextstep/courses/cohort/domain/CohortTest.java @@ -6,7 +6,6 @@ import java.time.LocalDateTime; import nextstep.courses.cohort.domain.enumeration.CohortStateType; -import nextstep.courses.cohort.domain.fixture.CohortFixture; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; @@ -103,10 +102,10 @@ class CohortTest { } @Test - void 기수의_식별자가_같은지_식별할_수_있다() { + void 코스의_식별자가_같은지_식별할_수_있다() { Cohort cohort = 식별자를_전달받아_기수픽스처를_생성한다(1L); - assertThat(cohort.isSameCohortId(1L)).isTrue(); + assertThat(cohort.isSameCourseId(2L)).isTrue(); } @Test @@ -115,7 +114,7 @@ class CohortTest { LocalDateTime.now(), LocalDateTime.now()); assertThat(cohort.isCanResist()).isTrue(); - cohort.plusOnePresent(); + cohort.registerStudent(); assertThat(cohort.isCanResist()).isFalse(); } diff --git a/src/test/java/nextstep/courses/cohort/domain/CohortsTest.java b/src/test/java/nextstep/courses/cohort/domain/CohortsTest.java deleted file mode 100644 index 282751dce..000000000 --- a/src/test/java/nextstep/courses/cohort/domain/CohortsTest.java +++ /dev/null @@ -1,23 +0,0 @@ -package nextstep.courses.cohort.domain; - -import static nextstep.courses.cohort.domain.fixture.CohortFixture.식별자를_전달받아_기수픽스처를_생성한다; -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.List; -import org.junit.jupiter.api.Test; - -class CohortsTest { - - @Test - void 기수목록에서_기수식별자로_기수를_찾을수_있다() { - Cohort target = 식별자를_전달받아_기수픽스처를_생성한다(1L); - Cohorts cohorts = new Cohorts(List.of( - target, - 식별자를_전달받아_기수픽스처를_생성한다(2L), - 식별자를_전달받아_기수픽스처를_생성한다(3L) - )); - - assertThat(cohorts.findCohortById(1L).get()).isEqualTo(target); - } - -} \ No newline at end of file diff --git a/src/test/java/nextstep/courses/course/domain/CourseTest.java b/src/test/java/nextstep/courses/course/domain/CourseTest.java index ff142cb0a..dadfa3d37 100644 --- a/src/test/java/nextstep/courses/course/domain/CourseTest.java +++ b/src/test/java/nextstep/courses/course/domain/CourseTest.java @@ -59,32 +59,4 @@ class CourseTest { ).isTrue(); } - @Test - void 기수식별자를_통해_해당기수가_신청가능한_상태인지_확인할_수_있다() { - Cohorts cohorts = new Cohorts(List.of( - 식별자와_상태를_전달받아_기수픽스처를_생성한다(1L, CohortStateType.END), - 식별자와_상태를_전달받아_기수픽스처를_생성한다(2L, CohortStateType.ACTIVE), - 식별자와_상태를_전달받아_기수픽스처를_생성한다(3L, CohortStateType.PREPARE) - )); - Course course = new Course("TDD, 객체지향 과정", 1L, CourseChargeType.FREE, cohorts); - - assertThat( - course.isCanEnrollBy(3L) - ).isTrue(); - } - - @Test - void 잘못된_기수식별자는_신청가능한_상태인지_확인할_수_없다() { - Cohorts cohorts = new Cohorts(List.of( - 식별자와_상태를_전달받아_기수픽스처를_생성한다(1L, CohortStateType.END), - 식별자와_상태를_전달받아_기수픽스처를_생성한다(2L, CohortStateType.ACTIVE), - 식별자와_상태를_전달받아_기수픽스처를_생성한다(3L, CohortStateType.PREPARE) - )); - Course course = new Course("TDD, 객체지향 과정", 1L, CourseChargeType.FREE, cohorts); - - assertThat(course.isCanEnrollBy(4L)).isFalse(); - assertThat(course.isCanEnrollBy(2L)).isFalse(); - } - - } \ No newline at end of file From 963a010c03d1072910e5c7ce99eff4b8b5de1778 Mon Sep 17 00:00:00 2001 From: mins1031 Date: Tue, 23 Dec 2025 14:52:16 +0900 Subject: [PATCH 10/14] =?UTF-8?q?feat=20:=20=EC=88=98=EA=B0=95=EC=8B=A0?= =?UTF-8?q?=EC=B2=AD=202=EB=8B=A8=EA=B3=84=20(=EB=8F=84=EB=A9=94=EC=9D=B8?= =?UTF-8?q?=20=EB=AA=A8=EB=8D=B8)=20-=20=EA=B0=95=EC=9D=98=20=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=EC=A7=80=20=EA=B4=80=EB=A0=A8=20=EC=9C=A0=ED=9A=A8?= =?UTF-8?q?=EC=84=B1=EA=B2=80=EC=82=AC=20TDD=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../courses/session/domain/Session.java | 6 ++ .../courses/session/domain/SessionImage.java | 57 ++++++++++++++++ .../session/domain/SessionImageTest.java | 67 +++++++++++++++++++ 3 files changed, 130 insertions(+) create mode 100644 src/main/java/nextstep/courses/session/domain/Session.java create mode 100644 src/main/java/nextstep/courses/session/domain/SessionImage.java create mode 100644 src/test/java/nextstep/courses/session/domain/SessionImageTest.java diff --git a/src/main/java/nextstep/courses/session/domain/Session.java b/src/main/java/nextstep/courses/session/domain/Session.java new file mode 100644 index 000000000..faec4f280 --- /dev/null +++ b/src/main/java/nextstep/courses/session/domain/Session.java @@ -0,0 +1,6 @@ +package nextstep.courses.session.domain; + +public class Session { + private String title; + +} diff --git a/src/main/java/nextstep/courses/session/domain/SessionImage.java b/src/main/java/nextstep/courses/session/domain/SessionImage.java new file mode 100644 index 000000000..bfa4a576d --- /dev/null +++ b/src/main/java/nextstep/courses/session/domain/SessionImage.java @@ -0,0 +1,57 @@ +package nextstep.courses.session.domain; + +import static org.springframework.util.StringUtils.hasText; + +public class SessionImage { + + public static final long MB = 1024 * 1024; + public static final int MIN_WIDTH = 300; + public static final int MIN_HEIGHT = 200; + private static final String EXTENSION_REGEX = ".*\\.(jpg|jpeg|png|gif|svg)$";; + + + private String url; + private String name; + private long size; + private int width; + private int height; + + public SessionImage() { + } + + public SessionImage(String url, long size, String name, int width, int height) { + if (!hasText(url)) { + throw new IllegalArgumentException("이미지 주소값은 필수 입니다"); + } + + if (size <= 0 || size > MB) { + throw new IllegalArgumentException("강의 이미지 크기는 1MB 이하여야 합니다"); + } + + if (!hasText(name) || !name.matches(EXTENSION_REGEX)) { + throw new IllegalArgumentException("강의 이미지 크기는 1MB 이하여야 합니다"); + } + + if (width < MIN_WIDTH || height < MIN_HEIGHT) { + throw new IllegalArgumentException("강의 이미지 너비는 300, 높이는 200 픽셀 이상여야 합니다"); + } + + if (isRatioThreeToTwo(width, height)) { + throw new IllegalArgumentException("강의 이미지 너비, 높이 비율은 3:2 여야 합니다"); + } + + this.url = url; + this.size = size; + this.name = name; + this.width = width; + this.height = height; + } + + + public boolean isRatioThreeToTwo(int width, int height) { + if (width <= 0 || height <= 0) { + return false; + } + return width * 2 == height * 3; + } +} diff --git a/src/test/java/nextstep/courses/session/domain/SessionImageTest.java b/src/test/java/nextstep/courses/session/domain/SessionImageTest.java new file mode 100644 index 000000000..e2def3acc --- /dev/null +++ b/src/test/java/nextstep/courses/session/domain/SessionImageTest.java @@ -0,0 +1,67 @@ +package nextstep.courses.session.domain; + + +import static nextstep.courses.session.domain.SessionImage.MB; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; +import org.junit.jupiter.params.provider.ValueSource; + +class SessionImageTest { + + @ParameterizedTest + @NullAndEmptySource + void 강의이미지_저장소주소가_없으면_예외처리_할_수_있다(String wrongUrl) { + + assertThatThrownBy( + () -> new SessionImage(wrongUrl, 1024, "name.jpg", 300, 200) + ).isInstanceOf(IllegalArgumentException.class); + } + + @ParameterizedTest + @ValueSource(longs = {-1, MB + 1}) + void 강의이미지가_1MB_이상이면_예외처리_할_수_있다(long wrongSize) { + assertThatThrownBy( + () -> new SessionImage("url", wrongSize, "name.jpg", 300, 200) + ).isInstanceOf(IllegalArgumentException.class); + } + + @ParameterizedTest + @ValueSource(strings = {"", " ", "name.", "name.txt", "name.pdf", "name.mp3", "name.smi"}) + void 강의이미지_확장자가_정해진_확장자가_아니면_예외처리_할_수_있다(String name) { + assertThatThrownBy( + () -> new SessionImage("url", 1024, name, 300, 200) + ).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void 강의이미지의_너비가_300픽셀_이하면_예외처리_할_수_있다() { + int wrongWidth = 299; + + assertThatThrownBy( + () -> new SessionImage("url", 1024, "name.jpg", wrongWidth, 200) + ).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void 강의이미지의_높이가_200픽셀_이하면_예외처리_할_수_있다() { + int wrongHeight = 199; + + assertThatThrownBy( + () -> new SessionImage("url", 1024, "name.jpg", 300, wrongHeight) + ).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void 강의이미지의_너비와_높이의_비율이_3대2가_아니면_예외처리_할_수_있다() { + int wrongWidth = 200; + int wrongHeight = 100; + + assertThatThrownBy( + () -> new SessionImage("url", 1024, "name.jpg", wrongWidth, wrongHeight) + ).isInstanceOf(IllegalArgumentException.class); + } + +} \ No newline at end of file From 4cd6da183674da38d2a3780e197defc4cc12a6ce Mon Sep 17 00:00:00 2001 From: mins1031 Date: Tue, 23 Dec 2025 20:50:59 +0900 Subject: [PATCH 11/14] =?UTF-8?q?feat=20:=20=EC=88=98=EA=B0=95=EC=8B=A0?= =?UTF-8?q?=EC=B2=AD=202=EB=8B=A8=EA=B3=84=20(=EB=8F=84=EB=A9=94=EC=9D=B8?= =?UTF-8?q?=20=EB=AA=A8=EB=8D=B8)=20-=20=EA=B8=B0=EC=88=98=20=EA=B0=9D?= =?UTF-8?q?=EC=B2=B4=EC=9D=98=20=EC=9D=B8=EC=8A=A4=ED=84=B4=EC=8A=A4=20?= =?UTF-8?q?=EB=B3=80=EC=88=98=20=EC=B5=9C=EC=86=8C=ED=99=94=EB=A5=BC=20?= =?UTF-8?q?=EC=9C=84=ED=95=B4=20=EA=B8=B0=EC=88=98=ED=95=99=EC=83=9D?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=20=EA=B0=9D=EC=B2=B4=20=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?=EB=B0=8F=20=EA=B5=AC=ED=98=84=20-=20=EA=B0=95=EC=9D=98=20?= =?UTF-8?q?=EA=B0=9D=EC=B2=B4=20=EA=B8=B0=EB=B3=B8=20=EB=82=B4=EC=9A=A9=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../courses/cohort/domain/Cohort.java | 27 +++++++------ .../cohort/domain/CohortStudentCount.java | 32 +++++++++++++++ .../courses/session/domain/Session.java | 24 +++++++++++ .../courses/session/domain/SessionImage.java | 7 +--- .../cohort/domain/CohortStudentCountTest.java | 40 +++++++++++++++++++ 5 files changed, 112 insertions(+), 18 deletions(-) create mode 100644 src/main/java/nextstep/courses/cohort/domain/CohortStudentCount.java create mode 100644 src/test/java/nextstep/courses/cohort/domain/CohortStudentCountTest.java diff --git a/src/main/java/nextstep/courses/cohort/domain/Cohort.java b/src/main/java/nextstep/courses/cohort/domain/Cohort.java index ad43e16bc..4df2673cd 100644 --- a/src/main/java/nextstep/courses/cohort/domain/Cohort.java +++ b/src/main/java/nextstep/courses/cohort/domain/Cohort.java @@ -11,8 +11,7 @@ public class Cohort extends BaseEntity { private final Long courseId; private int cohortCount; - private int maxStudentCount; - private int presentStudentCount; + private CohortStudentCount cohortStudentCount; private CohortStateType cohortStateType; private Period registerPeriod; private Period cohortPeriod; @@ -27,7 +26,9 @@ public Cohort( LocalDateTime cohortStartDate, LocalDateTime cohortEndDate ) { - this(0L, courseId, cohortCount, maxStudentCount, presentStudentCount, CohortStateType.PREPARE, registerStartDate, registerEndDate, cohortStartDate, cohortEndDate, null, null); + this(0L, courseId, cohortCount, maxStudentCount, presentStudentCount, + CohortStateType.PREPARE, registerStartDate, registerEndDate, cohortStartDate, + cohortEndDate, null, null); } public Cohort( @@ -55,8 +56,7 @@ public Cohort( this.courseId = courseId; this.cohortCount = cohortCount; - this.maxStudentCount = maxStudentCount; - this.presentStudentCount = presentStudentCount; + this.cohortStudentCount = new CohortStudentCount(maxStudentCount, presentStudentCount); this.cohortStateType = cohortStateType; this.registerPeriod = new Period(registerStartDate, registerEndDate); this.cohortPeriod = new Period(cohortStartDate, cohortEndDate); @@ -67,7 +67,7 @@ public boolean isCanResist() { return false; } - return this.maxStudentCount > this.presentStudentCount; + return this.cohortStudentCount.isNotOverMax(); } public boolean isInRecruitBy(LocalDateTime now) { @@ -105,11 +105,11 @@ public boolean isSameCourseId(Long courseId) { } public void registerStudent() { - if(!isCanResist()) { + if (!isCanResist()) { return; } - this.presentStudentCount++; + this.cohortStudentCount.plusOneCountAtPresent(); } @@ -130,16 +130,17 @@ public boolean equals(Object o) { return false; } Cohort cohort = (Cohort) o; - return cohortCount == cohort.cohortCount && maxStudentCount == cohort.maxStudentCount - && presentStudentCount == cohort.presentStudentCount && Objects.equals( - courseId, cohort.courseId) && cohortStateType == cohort.cohortStateType + return cohortCount == cohort.cohortCount + && Objects.equals(courseId, cohort.courseId) + && Objects.equals(cohortStudentCount, cohort.cohortStudentCount) + && cohortStateType == cohort.cohortStateType && Objects.equals(registerPeriod, cohort.registerPeriod) && Objects.equals(cohortPeriod, cohort.cohortPeriod); } @Override public int hashCode() { - return Objects.hash(super.hashCode(), courseId, cohortCount, maxStudentCount, - presentStudentCount, cohortStateType, registerPeriod, cohortPeriod); + return Objects.hash(super.hashCode(), courseId, cohortCount, + cohortStudentCount, cohortStateType, registerPeriod, cohortPeriod); } } diff --git a/src/main/java/nextstep/courses/cohort/domain/CohortStudentCount.java b/src/main/java/nextstep/courses/cohort/domain/CohortStudentCount.java new file mode 100644 index 000000000..656de36dc --- /dev/null +++ b/src/main/java/nextstep/courses/cohort/domain/CohortStudentCount.java @@ -0,0 +1,32 @@ +package nextstep.courses.cohort.domain; + +public class CohortStudentCount { + + private int maxStudentCount; + private int presentStudentCount; + + public CohortStudentCount(int maxStudentCount, int presentStudentCount) { + if (maxStudentCount < 1) { + throw new IllegalArgumentException("수강가능 총인원은 1이상이어야 합니다"); + } + + if (presentStudentCount < 0) { + throw new IllegalArgumentException("현재 신청인원은 음수일수 없습니다"); + } + + this.maxStudentCount = maxStudentCount; + this.presentStudentCount = presentStudentCount; + } + + public boolean isNotOverMax() { + return this.maxStudentCount > this.presentStudentCount; + } + + public void plusOneCountAtPresent() { + if (!isNotOverMax()) { + return; + } + + this.presentStudentCount++; + } +} diff --git a/src/main/java/nextstep/courses/session/domain/Session.java b/src/main/java/nextstep/courses/session/domain/Session.java index faec4f280..6eec25d7f 100644 --- a/src/main/java/nextstep/courses/session/domain/Session.java +++ b/src/main/java/nextstep/courses/session/domain/Session.java @@ -1,6 +1,30 @@ package nextstep.courses.session.domain; public class Session { + + private Long id; private String title; + private String url; + private SessionImage image; + + + public Session(String title, String url, SessionImage sessionImage) { + this(0L, title, url, sessionImage); + } + + public Session( + String title, + String url, + String imageUrl, String imageName, + long size, int width, int height + ) { + this(0L, title, url, new SessionImage(imageUrl, size, imageName, width, height)); + } + public Session(Long id, String title, String url, SessionImage image) { + this.id = id; + this.title = title; + this.url = url; + this.image = image; + } } diff --git a/src/main/java/nextstep/courses/session/domain/SessionImage.java b/src/main/java/nextstep/courses/session/domain/SessionImage.java index bfa4a576d..bf1055d3d 100644 --- a/src/main/java/nextstep/courses/session/domain/SessionImage.java +++ b/src/main/java/nextstep/courses/session/domain/SessionImage.java @@ -3,21 +3,17 @@ import static org.springframework.util.StringUtils.hasText; public class SessionImage { - public static final long MB = 1024 * 1024; public static final int MIN_WIDTH = 300; public static final int MIN_HEIGHT = 200; private static final String EXTENSION_REGEX = ".*\\.(jpg|jpeg|png|gif|svg)$";; - private String url; private String name; private long size; private int width; private int height; - public SessionImage() { - } public SessionImage(String url, long size, String name, int width, int height) { if (!hasText(url)) { @@ -48,10 +44,11 @@ public SessionImage(String url, long size, String name, int width, int height) { } - public boolean isRatioThreeToTwo(int width, int height) { + private boolean isRatioThreeToTwo(int width, int height) { if (width <= 0 || height <= 0) { return false; } + return width * 2 == height * 3; } } diff --git a/src/test/java/nextstep/courses/cohort/domain/CohortStudentCountTest.java b/src/test/java/nextstep/courses/cohort/domain/CohortStudentCountTest.java new file mode 100644 index 000000000..f31517f65 --- /dev/null +++ b/src/test/java/nextstep/courses/cohort/domain/CohortStudentCountTest.java @@ -0,0 +1,40 @@ +package nextstep.courses.cohort.domain; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.Test; + +class CohortStudentCountTest { + + @Test + void 기수의_수강가능인원이_1명미만이면_예외처리_할_수_있다() { + assertThatThrownBy( + () -> new CohortStudentCount(0, 1) + ).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void 기수의_현재수강인원이_0명미만이면_예외처리_할_수_있다() { + assertThatThrownBy( + () -> new CohortStudentCount(1, -1) + ).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void 현재수강인원이_수강가능인원보다_적은상태인것을_확인_할_수_있다() { + CohortStudentCount cohortStudentCount = new CohortStudentCount(10, 9); + + assertThat(cohortStudentCount.isNotOverMax()).isTrue(); + } + + @Test + void 현재수강인원을_1명_늘릴수_있다() { + CohortStudentCount cohortStudentCount = new CohortStudentCount(10, 9); + + assertThat(cohortStudentCount.isNotOverMax()).isTrue(); + cohortStudentCount.plusOneCountAtPresent(); + + assertThat(cohortStudentCount.isNotOverMax()).isFalse(); + } +} \ No newline at end of file From cf9933d96f97ece9a58764741d60076a840973e3 Mon Sep 17 00:00:00 2001 From: mins1031 Date: Wed, 24 Dec 2025 15:39:57 +0900 Subject: [PATCH 12/14] =?UTF-8?q?feat=20:=20=EC=88=98=EA=B0=95=EC=8B=A0?= =?UTF-8?q?=EC=B2=AD=202=EB=8B=A8=EA=B3=84=20(=EB=8F=84=EB=A9=94=EC=9D=B8?= =?UTF-8?q?=20=EB=AA=A8=EB=8D=B8)=20-=20=EA=B8=B0=EC=88=98=EC=83=81?= =?UTF-8?q?=ED=83=9C=20=EA=B0=9D=EC=B2=B4=EB=A5=BC=20=ED=86=B5=ED=95=B4=20?= =?UTF-8?q?=EA=B8=B0=EC=88=98=20=EA=B0=9D=EC=B2=B4=20=EC=9D=B8=EC=8A=A4?= =?UTF-8?q?=ED=84=B4=EC=8A=A4=20=EB=B3=80=EC=88=98=20=EC=B5=9C=EC=86=8C?= =?UTF-8?q?=ED=99=94=20-=20=EA=B8=B0=EC=88=98=EC=83=81=ED=83=9C=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EC=9A=94=EA=B5=AC=EC=82=AC=ED=95=AD=20TDD?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84=20=EB=B0=8F=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=20=EC=82=AD=EC=A0=9C=20-=20=EB=AA=A8?= =?UTF-8?q?=EC=A7=91=EB=A7=88=EA=B0=90=20=EC=83=81=ED=83=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../courses/cohort/domain/Cohort.java | 74 +++++++++-------- .../courses/cohort/domain/CohortState.java | 38 +++++++++ .../courses/cohort/domain/Cohorts.java | 1 - .../courses/cohort/domain/Period.java | 8 ++ .../domain/enumeration/CohortStateType.java | 1 + .../domain/service/CohortDomainService.java | 6 +- .../cohort/domain/CohortStateTest.java | 59 ++++++++++++++ .../courses/cohort/domain/CohortTest.java | 80 +++++++++++-------- .../courses/cohort/domain/PeriodTest.java | 22 +++++ 9 files changed, 217 insertions(+), 72 deletions(-) create mode 100644 src/main/java/nextstep/courses/cohort/domain/CohortState.java create mode 100644 src/test/java/nextstep/courses/cohort/domain/CohortStateTest.java diff --git a/src/main/java/nextstep/courses/cohort/domain/Cohort.java b/src/main/java/nextstep/courses/cohort/domain/Cohort.java index 4df2673cd..9be2019a3 100644 --- a/src/main/java/nextstep/courses/cohort/domain/Cohort.java +++ b/src/main/java/nextstep/courses/cohort/domain/Cohort.java @@ -12,9 +12,23 @@ public class Cohort extends BaseEntity { private final Long courseId; private int cohortCount; private CohortStudentCount cohortStudentCount; - private CohortStateType cohortStateType; - private Period registerPeriod; - private Period cohortPeriod; + private CohortState cohortState2; + + public Cohort( + Long courseId, + int cohortCount, + int maxStudentCount, + int presentStudentCount, + CohortStateType cohortStateType, + LocalDateTime registerStartDate, + LocalDateTime registerEndDate, + LocalDateTime cohortStartDate, + LocalDateTime cohortEndDate + ) { + this(0L, courseId, cohortCount, maxStudentCount, presentStudentCount, + cohortStateType, registerStartDate, registerEndDate, cohortStartDate, + cohortEndDate, null, null); + } public Cohort( Long courseId, @@ -57,43 +71,37 @@ public Cohort( this.courseId = courseId; this.cohortCount = cohortCount; this.cohortStudentCount = new CohortStudentCount(maxStudentCount, presentStudentCount); - this.cohortStateType = cohortStateType; - this.registerPeriod = new Period(registerStartDate, registerEndDate); - this.cohortPeriod = new Period(cohortStartDate, cohortEndDate); + this.cohortState2 = new CohortState(cohortStateType, + new Period(registerStartDate, registerEndDate), + new Period(cohortStartDate, cohortEndDate)); } public boolean isCanResist() { - if (!this.cohortStateType.equals(CohortStateType.PREPARE)) { + if (!this.cohortState2.isSameState(CohortStateType.PREPARE)) { return false; } return this.cohortStudentCount.isNotOverMax(); } - public boolean isInRecruitBy(LocalDateTime now) { - // 이것도 수강신청 정책 관련 로직일듯 - return this.registerPeriod.isPeriodIn(now); - } - - public boolean isInActiveBy(LocalDateTime now) { - // 이것도 수강신청 정책 관련 로직일듯 - return this.cohortPeriod.isPeriodIn(now); - } + public void putOnRecruitEnd(LocalDateTime now) { + if (isNull(now)) { + throw new IllegalArgumentException("현재시점은 필수 값 입니다."); + } - public void putOnRecruit(LocalDateTime now) { - if (!isInRecruitBy(now)) { - throw new IllegalArgumentException("현재는 수강신청 기간이 아닙니다"); + if (this.cohortState2.isBeforeRecruitPeriod(now)) { + throw new IllegalArgumentException("현재는 수강신청 기간전 입니다"); } - this.cohortStateType = CohortStateType.RECRUIT; - } + if (!this.cohortState2.isSameState(CohortStateType.RECRUIT)) { + throw new IllegalArgumentException("해당 기수의 상태가 수강신청이 아니기 때문에 신청마감상태로 변경할 수 없습니다"); + } - public void putOnActive(LocalDateTime now) { - if (!isInActiveBy(now)) { - throw new IllegalArgumentException("현재는 수강중 기간이 아닙니다"); + if (this.cohortState2.isInRecruitPeriod(now) && this.cohortStudentCount.isNotOverMax()) { + throw new IllegalArgumentException("현재는 수강신청 기간입니다"); } - this.cohortStateType = CohortStateType.ACTIVE; + this.cohortState2.changeRecruitEnd(); } public boolean isSameCourseId(Long courseId) { @@ -112,9 +120,8 @@ public void registerStudent() { this.cohortStudentCount.plusOneCountAtPresent(); } - - public CohortStateType cohortStateType() { - return this.cohortStateType; + public boolean isCohortStateType(CohortStateType cohortStateType) { + return this.cohortState2.isSameState(cohortStateType); } public Long getId() { @@ -130,17 +137,14 @@ public boolean equals(Object o) { return false; } Cohort cohort = (Cohort) o; - return cohortCount == cohort.cohortCount - && Objects.equals(courseId, cohort.courseId) + return cohortCount == cohort.cohortCount && Objects.equals(courseId, cohort.courseId) && Objects.equals(cohortStudentCount, cohort.cohortStudentCount) - && cohortStateType == cohort.cohortStateType - && Objects.equals(registerPeriod, cohort.registerPeriod) - && Objects.equals(cohortPeriod, cohort.cohortPeriod); + && Objects.equals(cohortState2, cohort.cohortState2); } @Override public int hashCode() { - return Objects.hash(super.hashCode(), courseId, cohortCount, - cohortStudentCount, cohortStateType, registerPeriod, cohortPeriod); + return Objects.hash(super.hashCode(), courseId, cohortCount, cohortStudentCount, + cohortState2); } } diff --git a/src/main/java/nextstep/courses/cohort/domain/CohortState.java b/src/main/java/nextstep/courses/cohort/domain/CohortState.java new file mode 100644 index 000000000..e2a3cf98d --- /dev/null +++ b/src/main/java/nextstep/courses/cohort/domain/CohortState.java @@ -0,0 +1,38 @@ +package nextstep.courses.cohort.domain; + +import static java.util.Objects.isNull; + +import java.time.LocalDateTime; +import nextstep.courses.cohort.domain.enumeration.CohortStateType; + +public class CohortState { + private CohortStateType cohortStateType; + private Period registerPeriod; + private Period cohortPeriod; + + public CohortState(CohortStateType cohortStateType, Period registerPeriod, Period cohortPeriod) { + this.cohortStateType = cohortStateType; + this.registerPeriod = registerPeriod; + this.cohortPeriod = cohortPeriod; + } + + public boolean isSameState(CohortStateType cohortStateType) { + if (isNull(cohortStateType)) { + return false; + } + + return this.cohortStateType.equals(cohortStateType); + } + + public boolean isInRecruitPeriod(LocalDateTime now) { + return this.registerPeriod.isPeriodIn(now); + } + + public boolean isBeforeRecruitPeriod(LocalDateTime now) { + return this.registerPeriod.isBeforeStartDate(now); + } + + public void changeRecruitEnd() { + this.cohortStateType = CohortStateType.RECRUIT_END; + } +} diff --git a/src/main/java/nextstep/courses/cohort/domain/Cohorts.java b/src/main/java/nextstep/courses/cohort/domain/Cohorts.java index d507d1582..8ed597e3c 100644 --- a/src/main/java/nextstep/courses/cohort/domain/Cohorts.java +++ b/src/main/java/nextstep/courses/cohort/domain/Cohorts.java @@ -1,7 +1,6 @@ package nextstep.courses.cohort.domain; import java.util.List; -import java.util.Optional; public class Cohorts { private List cohorts; diff --git a/src/main/java/nextstep/courses/cohort/domain/Period.java b/src/main/java/nextstep/courses/cohort/domain/Period.java index af78d68a6..e9a811442 100644 --- a/src/main/java/nextstep/courses/cohort/domain/Period.java +++ b/src/main/java/nextstep/courses/cohort/domain/Period.java @@ -27,4 +27,12 @@ public boolean isPeriodIn(LocalDateTime targetDate) { return startDateAfter && endDateBefore; } + + public boolean isOverEndDate(LocalDateTime targetDate) { + return this.endDate.isBefore(targetDate); + } + + public boolean isBeforeStartDate(LocalDateTime targetDate) { + return this.startDate.isAfter(targetDate); + } } diff --git a/src/main/java/nextstep/courses/cohort/domain/enumeration/CohortStateType.java b/src/main/java/nextstep/courses/cohort/domain/enumeration/CohortStateType.java index 057aebfc7..c58ea58b2 100644 --- a/src/main/java/nextstep/courses/cohort/domain/enumeration/CohortStateType.java +++ b/src/main/java/nextstep/courses/cohort/domain/enumeration/CohortStateType.java @@ -3,6 +3,7 @@ public enum CohortStateType { PREPARE("준비중"), RECRUIT("모집중"), + RECRUIT_END("모집마감"), ACTIVE("진행중"), END("종료"), diff --git a/src/main/java/nextstep/courses/cohort/domain/service/CohortDomainService.java b/src/main/java/nextstep/courses/cohort/domain/service/CohortDomainService.java index 201ae1887..7164b635f 100644 --- a/src/main/java/nextstep/courses/cohort/domain/service/CohortDomainService.java +++ b/src/main/java/nextstep/courses/cohort/domain/service/CohortDomainService.java @@ -2,12 +2,13 @@ import nextstep.courses.cohort.domain.Cohort; import nextstep.courses.enrollment.domain.Enrollment; +import nextstep.qna.exception.unchecked.WrongRequestException; public class CohortDomainService { public Enrollment registerEnrollment(Cohort cohort, Long studentId, Long courseId) { if (!cohort.isCanResist()) { - throw new IllegalArgumentException("해당기수는 수강신청 할 수 없는 상태입니다"); + throw new WrongRequestException("해당기수는 수강신청 할 수 없는 상태입니다"); } if (!cohort.isSameCourseId(courseId)) { throw new IllegalArgumentException("결제정보와 강의정보가 상이합니다."); @@ -19,4 +20,7 @@ public Enrollment registerEnrollment(Cohort cohort, Long studentId, Long courseI // Cohort에서 Enrollment를 생성하는 동작을 가지고 있는것 보다 외부에서 하는며 두객체의 결합도를 낮추는게 더 좋다고 생각합니다 return new Enrollment(studentId, cohort.getId()); } + + // 여기에 상태체크 한번 더 하는 로직 적용할 예정. 인원상태 보고 인원 다 찼으면 인원풀필 상태로 변경하는 로직도 구현 예정 + // 일단 상태가 인원마감 상태는 확인 및 적용하는 로직 필요 } diff --git a/src/test/java/nextstep/courses/cohort/domain/CohortStateTest.java b/src/test/java/nextstep/courses/cohort/domain/CohortStateTest.java new file mode 100644 index 000000000..6eb171844 --- /dev/null +++ b/src/test/java/nextstep/courses/cohort/domain/CohortStateTest.java @@ -0,0 +1,59 @@ +package nextstep.courses.cohort.domain; + +import static java.time.LocalDateTime.now; +import static java.time.LocalDateTime.of; +import static nextstep.courses.cohort.domain.enumeration.CohortStateType.RECRUIT; +import static org.assertj.core.api.Assertions.assertThat; + +import nextstep.courses.cohort.domain.enumeration.CohortStateType; +import org.junit.jupiter.api.Test; + +class CohortStateTest { + + @Test + void 주어진상태와_기수의상태가_동일한지_확인_할_수_있다() { + CohortState cohortState = new CohortState(RECRUIT, new Period(now(), now()), + new Period(now(), now())); + assertThat(cohortState.isSameState(RECRUIT)).isTrue(); + } + + @Test + void 현재시점이_기수의_모집중_기간인지_확인_할_수_있다() { + CohortState cohortState = new CohortState( + RECRUIT, + new Period( + of(2025, 1, 1, 0, 0, 0), + of(2025, 1, 10, 23, 59, 59) + ), + new Period( + of(2025, 1, 17, 0, 0, 0), + of(2025, 2, 25, 23, 59, 59) + ) + ); + + assertThat( + cohortState.isInRecruitPeriod(of(2025, 1, 1, 0, 0, 1)) + ).isTrue(); + } + + @Test + void 기수의_상태를_모집마감으로_변경할_수_있다() { + CohortState cohortState = new CohortState( + RECRUIT, + new Period( + of(2025, 1, 1, 0, 0, 0), + of(2025, 1, 10, 23, 59, 59) + ), + new Period( + of(2025, 1, 17, 0, 0, 0), + of(2025, 2, 25, 23, 59, 59) + ) + ); + + cohortState.changeRecruitEnd(); + + assertThat(cohortState.isSameState(CohortStateType.RECRUIT_END)).isTrue(); + } + + +} \ No newline at end of file diff --git a/src/test/java/nextstep/courses/cohort/domain/CohortTest.java b/src/test/java/nextstep/courses/cohort/domain/CohortTest.java index bf54f0973..2927f9e1b 100644 --- a/src/test/java/nextstep/courses/cohort/domain/CohortTest.java +++ b/src/test/java/nextstep/courses/cohort/domain/CohortTest.java @@ -1,5 +1,7 @@ package nextstep.courses.cohort.domain; +import static nextstep.courses.cohort.domain.enumeration.CohortStateType.RECRUIT; +import static nextstep.courses.cohort.domain.enumeration.CohortStateType.RECRUIT_END; import static nextstep.courses.cohort.domain.fixture.CohortFixture.식별자를_전달받아_기수픽스처를_생성한다; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -37,58 +39,55 @@ class CohortTest { } @Test - void 지금이_수강_기간인지_확인할_수_있다() { - // 기간 객체엔 특정 날짜 값이 시작-종료 중간값인지 확인하는 메서드 + 기수에선 수강기간 확인 메서드로 표현성 극대화 + void 코스의_식별자가_같은지_식별할_수_있다() { + Cohort cohort = 식별자를_전달받아_기수픽스처를_생성한다(1L); + + assertThat(cohort.isSameCourseId(2L)).isTrue(); } @Test - void 기수의_상태를_수강신청_기간으로_변경할_수_있다() { - // 특정 날짜값 받고 + 수강신청 기간인지 확인 + 신청상태로 상태변경 - Cohort cohort = new Cohort(1L, 5, 20, 0, - LocalDateTime.of(2025, 1, 1, 0, 0, 0), - LocalDateTime.of(2025, 1, 10, 23, 59, 59), - LocalDateTime.of(2025, 1, 17, 0, 0, 0), - LocalDateTime.of(2025, 2, 25, 23, 59, 59) - ); + void 기수의_현재수강인원을_1만큼_증가시킬_수_있다() { + Cohort cohort = new Cohort(1L, 5, 20, 19, LocalDateTime.now(), LocalDateTime.now(), + LocalDateTime.now(), LocalDateTime.now()); + assertThat(cohort.isCanResist()).isTrue(); - cohort.putOnRecruit(LocalDateTime.of(2025, 1, 1, 0, 0, 1)); + cohort.registerStudent(); - assertThat(cohort.cohortStateType()).isEqualTo(CohortStateType.RECRUIT); + assertThat(cohort.isCanResist()).isFalse(); } @Test - void 수강신청기간이_아닌데_기수의_상태를_수강신청_기간으로_변경하면_예외처리_할_수_있다() { - // 특정 날짜값 받고 + 수강신청 기간인지 확인 + 신청상태로 상태변경 + void 현시점이_수강신청기간보다_미래면_기수의_상태를_수강신청종료로_변경할_수_있다() { Cohort cohort = new Cohort(1L, 5, 20, 0, + RECRUIT, LocalDateTime.of(2025, 1, 1, 0, 0, 0), LocalDateTime.of(2025, 1, 10, 23, 59, 59), LocalDateTime.of(2025, 1, 17, 0, 0, 0), LocalDateTime.of(2025, 2, 25, 23, 59, 59) ); - assertThatThrownBy( - () -> cohort.putOnRecruit(LocalDateTime.of(2025, 1, 11, 0, 0, 1)) - ).isInstanceOf(IllegalArgumentException.class); + cohort.putOnRecruitEnd(LocalDateTime.of(2025, 1, 11, 0, 0, 0)); + + assertThat(cohort.isCohortStateType(RECRUIT_END)).isTrue(); } @Test - void 기수의_상태를_수강_기간으로_변경할_수_있다() { - // 특정 날짜값 받고 + 수강신청 기간인지 확인 + 신청상태로 상태변경 - Cohort cohort = new Cohort(1L, 5, 20, 0, + void 현시점이_수강신청기간이라도_기수의_최대인원이_모집됐으면_수강신청종료로_변경할_수_있다() { + Cohort cohort = new Cohort(1L, 5, 20, 20, + RECRUIT, LocalDateTime.of(2025, 1, 1, 0, 0, 0), LocalDateTime.of(2025, 1, 10, 23, 59, 59), LocalDateTime.of(2025, 1, 17, 0, 0, 0), LocalDateTime.of(2025, 2, 25, 23, 59, 59) ); - cohort.putOnActive(LocalDateTime.of(2025, 1, 17, 0, 0, 1)); + cohort.putOnRecruitEnd(LocalDateTime.of(2025, 1, 9, 0, 0, 0)); - assertThat(cohort.cohortStateType()).isEqualTo(CohortStateType.ACTIVE); + assertThat(cohort.isCohortStateType(RECRUIT_END)).isTrue(); } @Test - void 수강기간이_아닌데_기수의_상태를_수강중으로_변경하면_예외처리_할_수_있다() { - // 특정 날짜값 받고 + 수강신청 기간인지 확인 + 신청상태로 상태변경 + void 현시점이_수강신청기간이고_모집인원의_정원이_초과되지_않았을떄_수강신청종료로_변경할_수_없다() { Cohort cohort = new Cohort(1L, 5, 20, 0, LocalDateTime.of(2025, 1, 1, 0, 0, 0), LocalDateTime.of(2025, 1, 10, 23, 59, 59), @@ -97,25 +96,36 @@ class CohortTest { ); assertThatThrownBy( - () -> cohort.putOnActive(LocalDateTime.of(2025, 2, 26, 0, 0, 0)) + () -> cohort.putOnRecruitEnd(LocalDateTime.of(2025, 1, 9, 0, 0, 0)) ).isInstanceOf(IllegalArgumentException.class); } @Test - void 코스의_식별자가_같은지_식별할_수_있다() { - Cohort cohort = 식별자를_전달받아_기수픽스처를_생성한다(1L); + void 현시점이_수강신청이전이면_수강신청종료로_변경할_수_없다() { + Cohort cohort = new Cohort(1L, 5, 20, 0, + LocalDateTime.of(2025, 1, 1, 0, 0, 1), + LocalDateTime.of(2025, 1, 10, 23, 59, 59), + LocalDateTime.of(2025, 1, 17, 0, 0, 0), + LocalDateTime.of(2025, 2, 25, 23, 59, 59) + ); - assertThat(cohort.isSameCourseId(2L)).isTrue(); + assertThatThrownBy( + () -> cohort.putOnRecruitEnd(LocalDateTime.of(2025, 1, 1, 0, 0, 0)) + ).isInstanceOf(IllegalArgumentException.class); } @Test - void 기수의_현재수강인원을_1만큼_증가시킬_수_있다() { - Cohort cohort = new Cohort(1L, 5, 20, 19, LocalDateTime.now(), LocalDateTime.now(), - LocalDateTime.now(), LocalDateTime.now()); - assertThat(cohort.isCanResist()).isTrue(); - - cohort.registerStudent(); + void 현시점파라미터가_NULL인경우_예외처리_할_수_있다() { + Cohort cohort = new Cohort(1L, 5, 20, 0, + LocalDateTime.of(2025, 1, 1, 0, 0, 1), + LocalDateTime.of(2025, 1, 10, 23, 59, 59), + LocalDateTime.of(2025, 1, 17, 0, 0, 0), + LocalDateTime.of(2025, 2, 25, 23, 59, 59) + ); - assertThat(cohort.isCanResist()).isFalse(); + assertThatThrownBy( + () -> cohort.putOnRecruitEnd(null) + ).isInstanceOf(IllegalArgumentException.class); } + } \ No newline at end of file diff --git a/src/test/java/nextstep/courses/cohort/domain/PeriodTest.java b/src/test/java/nextstep/courses/cohort/domain/PeriodTest.java index 7d8c8bec7..dbc35368c 100644 --- a/src/test/java/nextstep/courses/cohort/domain/PeriodTest.java +++ b/src/test/java/nextstep/courses/cohort/domain/PeriodTest.java @@ -43,4 +43,26 @@ class PeriodTest { ).isTrue(); } + @Test + void 특정시점이_종료일보다_미래인지_확인_할_수_있다() { + LocalDateTime startDate = LocalDateTime.of(2025, 1, 1, 0, 29, 28); + LocalDateTime endDate = LocalDateTime.of(2025, 1, 1, 0, 29, 30); + LocalDateTime target = LocalDateTime.of(2025, 1, 1, 0, 29, 31); + + assertThat( + new Period(startDate, endDate).isOverEndDate(target) + ).isTrue(); + } + + @Test + void 특정시점이_시작일보다_과거인지_확인_할_수_있다() { + LocalDateTime startDate = LocalDateTime.of(2025, 1, 1, 0, 29, 28); + LocalDateTime endDate = LocalDateTime.of(2025, 1, 1, 0, 29, 30); + LocalDateTime target = LocalDateTime.of(2025, 1, 1, 0, 29, 27); + + assertThat( + new Period(startDate, endDate).isBeforeStartDate(target) + ).isTrue(); + } + } \ No newline at end of file From 6ae60f1db6f3741d8180bae78239629046f87ed7 Mon Sep 17 00:00:00 2001 From: mins1031 Date: Wed, 24 Dec 2025 15:57:05 +0900 Subject: [PATCH 13/14] =?UTF-8?q?feat=20:=20=EC=88=98=EA=B0=95=EC=8B=A0?= =?UTF-8?q?=EC=B2=AD=202=EB=8B=A8=EA=B3=84=20(=EB=8F=84=EB=A9=94=EC=9D=B8?= =?UTF-8?q?=20=EB=AA=A8=EB=8D=B8)=20-=20=EA=B8=B0=EC=88=98=EC=83=81?= =?UTF-8?q?=ED=83=9C=EB=A5=BC=20=EC=B2=B4=ED=81=AC=ED=95=B4=20=EB=AA=A8?= =?UTF-8?q?=EC=A7=91=EB=A7=88=EA=B0=90=20=EC=83=81=ED=83=9C=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=ED=95=98=EB=8A=94=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80.=20-=20service=EC=9D=98=20udpate=20=EC=BF=BC?= =?UTF-8?q?=EB=A6=AC=20=EB=B6=80=EB=B6=84=20course=20->=20cohort=EB=A1=9C?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../courses/cohort/domain/Cohort.java | 13 +++--- .../domain/service/CohortDomainService.java | 12 +++++- .../service/repository/CohortRepository.java | 2 + .../service/repository/CourseRepository.java | 2 - .../enrollment/service/EnrollmentService.java | 8 ++-- .../courses/cohort/domain/CohortTest.java | 40 ++++++++++++------- 6 files changed, 49 insertions(+), 28 deletions(-) diff --git a/src/main/java/nextstep/courses/cohort/domain/Cohort.java b/src/main/java/nextstep/courses/cohort/domain/Cohort.java index 9be2019a3..2890ca32b 100644 --- a/src/main/java/nextstep/courses/cohort/domain/Cohort.java +++ b/src/main/java/nextstep/courses/cohort/domain/Cohort.java @@ -77,31 +77,32 @@ public Cohort( } public boolean isCanResist() { - if (!this.cohortState2.isSameState(CohortStateType.PREPARE)) { + if (!this.cohortState2.isSameState(CohortStateType.RECRUIT)) { return false; } return this.cohortStudentCount.isNotOverMax(); } - public void putOnRecruitEnd(LocalDateTime now) { + public boolean putOnRecruitEnd(LocalDateTime now) { if (isNull(now)) { - throw new IllegalArgumentException("현재시점은 필수 값 입니다."); + return false; } if (this.cohortState2.isBeforeRecruitPeriod(now)) { - throw new IllegalArgumentException("현재는 수강신청 기간전 입니다"); + return false; } if (!this.cohortState2.isSameState(CohortStateType.RECRUIT)) { - throw new IllegalArgumentException("해당 기수의 상태가 수강신청이 아니기 때문에 신청마감상태로 변경할 수 없습니다"); + return false; } if (this.cohortState2.isInRecruitPeriod(now) && this.cohortStudentCount.isNotOverMax()) { - throw new IllegalArgumentException("현재는 수강신청 기간입니다"); + return false; } this.cohortState2.changeRecruitEnd(); + return true; } public boolean isSameCourseId(Long courseId) { diff --git a/src/main/java/nextstep/courses/cohort/domain/service/CohortDomainService.java b/src/main/java/nextstep/courses/cohort/domain/service/CohortDomainService.java index 7164b635f..274ae867a 100644 --- a/src/main/java/nextstep/courses/cohort/domain/service/CohortDomainService.java +++ b/src/main/java/nextstep/courses/cohort/domain/service/CohortDomainService.java @@ -1,5 +1,8 @@ package nextstep.courses.cohort.domain.service; +import static java.util.Objects.isNull; + +import java.time.LocalDateTime; import nextstep.courses.cohort.domain.Cohort; import nextstep.courses.enrollment.domain.Enrollment; import nextstep.qna.exception.unchecked.WrongRequestException; @@ -21,6 +24,11 @@ public Enrollment registerEnrollment(Cohort cohort, Long studentId, Long courseI return new Enrollment(studentId, cohort.getId()); } - // 여기에 상태체크 한번 더 하는 로직 적용할 예정. 인원상태 보고 인원 다 찼으면 인원풀필 상태로 변경하는 로직도 구현 예정 - // 일단 상태가 인원마감 상태는 확인 및 적용하는 로직 필요 + public void updateStateToRecruitEnd(Cohort cohort) { + if (isNull(cohort)) { + return; + } + + cohort.putOnRecruitEnd(LocalDateTime.now()); + } } diff --git a/src/main/java/nextstep/courses/cohort/service/repository/CohortRepository.java b/src/main/java/nextstep/courses/cohort/service/repository/CohortRepository.java index 1709fbd4d..aec383612 100644 --- a/src/main/java/nextstep/courses/cohort/service/repository/CohortRepository.java +++ b/src/main/java/nextstep/courses/cohort/service/repository/CohortRepository.java @@ -8,4 +8,6 @@ public interface CohortRepository { Optional findById(Long id); + + void update(Cohort cohort); } diff --git a/src/main/java/nextstep/courses/course/service/repository/CourseRepository.java b/src/main/java/nextstep/courses/course/service/repository/CourseRepository.java index 705a3b323..209602b42 100644 --- a/src/main/java/nextstep/courses/course/service/repository/CourseRepository.java +++ b/src/main/java/nextstep/courses/course/service/repository/CourseRepository.java @@ -9,6 +9,4 @@ public interface CourseRepository { int save(Course course); Optional findById(Long id); - - void plusOnePresentCount(); } diff --git a/src/main/java/nextstep/courses/enrollment/service/EnrollmentService.java b/src/main/java/nextstep/courses/enrollment/service/EnrollmentService.java index 84ccda22e..0dc761ea1 100644 --- a/src/main/java/nextstep/courses/enrollment/service/EnrollmentService.java +++ b/src/main/java/nextstep/courses/enrollment/service/EnrollmentService.java @@ -50,10 +50,12 @@ public void saveEnrollment(EnrollmentSaveRequest request) { Cohort cohort = cohortRepository.findById(request.getCohortId()) .orElseThrow(NotFoundException::new); - Enrollment enrollment = new CohortDomainService().registerEnrollment( - cohort, request.getStudentId(), request.getCourseId()); + + CohortDomainService cohortDomainService = new CohortDomainService(); + Enrollment enrollment = cohortDomainService.registerEnrollment(cohort, request.getStudentId(), request.getCourseId()); + cohortDomainService.updateStateToRecruitEnd(cohort); enrollmentRepository.save(enrollment); - courseRepository.plusOnePresentCount(); + cohortRepository.update(cohort); } } diff --git a/src/test/java/nextstep/courses/cohort/domain/CohortTest.java b/src/test/java/nextstep/courses/cohort/domain/CohortTest.java index 2927f9e1b..e5a51a7e5 100644 --- a/src/test/java/nextstep/courses/cohort/domain/CohortTest.java +++ b/src/test/java/nextstep/courses/cohort/domain/CohortTest.java @@ -32,7 +32,9 @@ class CohortTest { @CsvSource({"19, true", "20, false"}) void 현재_수강인원을_더_받을수_있는지_확인할_수_있다(int presentStudentCount, boolean result) { // 최대 수강인원, 현재 수강인원 전부 필요할듯. - Cohort cohort = new Cohort(1L, 5, 20, presentStudentCount, LocalDateTime.now(), LocalDateTime.now(), + Cohort cohort = new Cohort(1L, 5, 20, presentStudentCount, + RECRUIT, + LocalDateTime.now(), LocalDateTime.now(), LocalDateTime.now(), LocalDateTime.now()); assertThat(cohort.isCanResist()).isEqualTo(result); @@ -47,7 +49,9 @@ class CohortTest { @Test void 기수의_현재수강인원을_1만큼_증가시킬_수_있다() { - Cohort cohort = new Cohort(1L, 5, 20, 19, LocalDateTime.now(), LocalDateTime.now(), + Cohort cohort = new Cohort(1L, 5, 20, 19, + CohortStateType.RECRUIT, + LocalDateTime.now(), LocalDateTime.now(), LocalDateTime.now(), LocalDateTime.now()); assertThat(cohort.isCanResist()).isTrue(); @@ -66,8 +70,9 @@ class CohortTest { LocalDateTime.of(2025, 2, 25, 23, 59, 59) ); - cohort.putOnRecruitEnd(LocalDateTime.of(2025, 1, 11, 0, 0, 0)); - + assertThat( + cohort.putOnRecruitEnd(LocalDateTime.of(2025, 1, 11, 0, 0, 0)) + ).isTrue(); assertThat(cohort.isCohortStateType(RECRUIT_END)).isTrue(); } @@ -81,23 +86,26 @@ class CohortTest { LocalDateTime.of(2025, 2, 25, 23, 59, 59) ); - cohort.putOnRecruitEnd(LocalDateTime.of(2025, 1, 9, 0, 0, 0)); - + assertThat( + cohort.putOnRecruitEnd(LocalDateTime.of(2025, 1, 9, 0, 0, 0)) + ).isTrue(); assertThat(cohort.isCohortStateType(RECRUIT_END)).isTrue(); } @Test void 현시점이_수강신청기간이고_모집인원의_정원이_초과되지_않았을떄_수강신청종료로_변경할_수_없다() { Cohort cohort = new Cohort(1L, 5, 20, 0, + CohortStateType.RECRUIT, LocalDateTime.of(2025, 1, 1, 0, 0, 0), LocalDateTime.of(2025, 1, 10, 23, 59, 59), LocalDateTime.of(2025, 1, 17, 0, 0, 0), LocalDateTime.of(2025, 2, 25, 23, 59, 59) ); - assertThatThrownBy( - () -> cohort.putOnRecruitEnd(LocalDateTime.of(2025, 1, 9, 0, 0, 0)) - ).isInstanceOf(IllegalArgumentException.class); + assertThat( + cohort.putOnRecruitEnd(LocalDateTime.of(2025, 1, 9, 0, 0, 0)) + ).isFalse(); + assertThat(cohort.isCohortStateType(RECRUIT_END)).isFalse(); } @Test @@ -109,9 +117,10 @@ class CohortTest { LocalDateTime.of(2025, 2, 25, 23, 59, 59) ); - assertThatThrownBy( - () -> cohort.putOnRecruitEnd(LocalDateTime.of(2025, 1, 1, 0, 0, 0)) - ).isInstanceOf(IllegalArgumentException.class); + assertThat( + cohort.putOnRecruitEnd(LocalDateTime.of(2025, 1, 1, 0, 0, 0)) + ).isFalse(); + assertThat(cohort.isCohortStateType(RECRUIT_END)).isFalse(); } @Test @@ -123,9 +132,10 @@ class CohortTest { LocalDateTime.of(2025, 2, 25, 23, 59, 59) ); - assertThatThrownBy( - () -> cohort.putOnRecruitEnd(null) - ).isInstanceOf(IllegalArgumentException.class); + assertThat( + cohort.putOnRecruitEnd(null) + ).isFalse(); + assertThat(cohort.isCohortStateType(RECRUIT_END)).isFalse(); } } \ No newline at end of file From 49fe706991a92120ddacc928c9f2c077d1706314 Mon Sep 17 00:00:00 2001 From: mins1031 Date: Wed, 24 Dec 2025 15:57:32 +0900 Subject: [PATCH 14/14] =?UTF-8?q?refactor=20:=20=EC=88=98=EA=B0=95?= =?UTF-8?q?=EC=8B=A0=EC=B2=AD=202=EB=8B=A8=EA=B3=84=20(=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EB=AA=A8=EB=8D=B8)=20-=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=20=EB=A1=9C=EC=A7=81=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../courses/course/infrastructure/JdbcCourseRepository.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/main/java/nextstep/courses/course/infrastructure/JdbcCourseRepository.java b/src/main/java/nextstep/courses/course/infrastructure/JdbcCourseRepository.java index 98a082ab6..c3a6ebfd1 100644 --- a/src/main/java/nextstep/courses/course/infrastructure/JdbcCourseRepository.java +++ b/src/main/java/nextstep/courses/course/infrastructure/JdbcCourseRepository.java @@ -36,11 +36,6 @@ public Optional findById(Long id) { return Optional.of(jdbcTemplate.queryForObject(sql, rowMapper, id)); } - @Override - public void plusOnePresentCount() { - - } - private LocalDateTime toLocalDateTime(Timestamp timestamp) { if (timestamp == null) { return null;