diff --git a/src/main/java/com/flytrap/venusplanner/api/plan/business/service/EndOptionCalculate.java b/src/main/java/com/flytrap/venusplanner/api/plan/business/service/EndOptionCalculate.java new file mode 100644 index 0000000..a21a6d8 --- /dev/null +++ b/src/main/java/com/flytrap/venusplanner/api/plan/business/service/EndOptionCalculate.java @@ -0,0 +1,8 @@ +package com.flytrap.venusplanner.api.plan.business.service; + +import java.time.Instant; + +public interface EndOptionCalculate { + Instant calculateEndDate(Instant startDate, int count); + int calculateCount(Instant startDate, Instant endDate); +} diff --git a/src/main/java/com/flytrap/venusplanner/api/plan/business/service/PlanService.java b/src/main/java/com/flytrap/venusplanner/api/plan/business/service/PlanService.java index b951b7f..d3ab494 100644 --- a/src/main/java/com/flytrap/venusplanner/api/plan/business/service/PlanService.java +++ b/src/main/java/com/flytrap/venusplanner/api/plan/business/service/PlanService.java @@ -1,10 +1,17 @@ package com.flytrap.venusplanner.api.plan.business.service; import com.flytrap.venusplanner.api.plan.domain.Plan; +import com.flytrap.venusplanner.api.plan.domain.RecurringOption; import com.flytrap.venusplanner.api.plan.infrastructure.repository.PlanRepository; +import com.flytrap.venusplanner.api.plan.infrastructure.repository.RecurringOptionRepository; +import com.flytrap.venusplanner.api.plan.presentation.dto.request.PlanCreateRequest; +import com.flytrap.venusplanner.api.plan.util.DateTimeUtils; import com.flytrap.venusplanner.api.plan_category.domain.PlanCategory; import com.flytrap.venusplanner.api.study.domain.Study; -import com.flytrap.venusplanner.api.study_plan.presentation.dto.request.StudyPlanCreateRequest; +import java.time.Instant; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -16,15 +23,48 @@ public class PlanService { private final PlanRepository planRepository; + private final RecurringOptionRepository recurringOptionRepository; - public Long savePlan(Study study, PlanCategory planCategory, StudyPlanCreateRequest request) { - //TODO: 반복 옵션 설정시 DB에 여러 plan 저장 로직 추가 + public Long savePlan(Study study, PlanCategory planCategory, PlanCreateRequest request) { Plan plan = request.toEntity(study, planCategory); planRepository.save(plan); + if (plan.getRecurringOption() != null) { + List recurringPlan = createPlansWithRecurringPlan(plan); + planRepository.saveAll(recurringPlan); + } + return plan.getId(); } + private List createPlansWithRecurringPlan(Plan planTemplate) { + int recurrenceCount = planTemplate.getRecurringOption().getRecurrenceCount(); + ZonedDateTime startTimeZDT = DateTimeUtils.toZonedDateTime(planTemplate.getStartTime()); + ZonedDateTime endTimeZDT = DateTimeUtils.toZonedDateTime(planTemplate.getEndTime()); + ChronoUnit chronoUnit = planTemplate.getRecurringOption().getFrequency().getChronoUnit(); + + List createdPlans = new ArrayList<>(); + for (int i = 1; i < recurrenceCount; i++) { + Instant startTime = DateTimeUtils.toInstant(startTimeZDT.plus(i, chronoUnit)); + Instant endTime = DateTimeUtils.toInstant(endTimeZDT.plus(i, chronoUnit)); + + Plan newPlan = Plan.builder() + .study(planTemplate.getStudy()) + .planCategory(planTemplate.getPlanCategory()) + .recurringOption(planTemplate.getRecurringOption()) + .title(planTemplate.getTitle()) + .description(planTemplate.getDescription()) + .startTime(startTime) + .endTime(endTime) + .notificationTime(planTemplate.getNotificationTime()) + .build(); + + createdPlans.add(planRepository.save(newPlan)); + } + + return createdPlans; + } + public List findAllByStudyIdAndYearAndMonth(Long studyId, int year, int month) { return planRepository.findAllByStudyIdAndYearAndMonth(studyId, year, month); } @@ -32,8 +72,19 @@ public List findAllByStudyIdAndYearAndMonth(Long studyId, int year, int mo @Transactional public void deleteById(Long planId) { //TODO: 멤버의 권한 검증 로직 - //TODO: 반복옵션 전체 삭제 여부 //TODO: plan이 없을 때 예외처리 planRepository.deleteById(planId); } + + public void deleteAllByRecurringId(Long planId) { + //Todo: plan 없을 때 예외처리 + Plan plan = planRepository.findById(planId).get(); + RecurringOption recurringOption = plan.getRecurringOption(); + if (recurringOption != null) { + planRepository.deleteAllByRecurringOptionId(recurringOption.getId()); + recurringOptionRepository.deleteById(recurringOption.getId()); + } else { + planRepository.deleteById(planId); + } + } } diff --git a/src/main/java/com/flytrap/venusplanner/api/study_plan/business/service/StudyPlanFacadeService.java b/src/main/java/com/flytrap/venusplanner/api/plan/business/service/StudyPlanFacadeService.java similarity index 63% rename from src/main/java/com/flytrap/venusplanner/api/study_plan/business/service/StudyPlanFacadeService.java rename to src/main/java/com/flytrap/venusplanner/api/plan/business/service/StudyPlanFacadeService.java index c42e80d..5c60ee0 100644 --- a/src/main/java/com/flytrap/venusplanner/api/study_plan/business/service/StudyPlanFacadeService.java +++ b/src/main/java/com/flytrap/venusplanner/api/plan/business/service/StudyPlanFacadeService.java @@ -1,13 +1,11 @@ -package com.flytrap.venusplanner.api.study_plan.business.service; +package com.flytrap.venusplanner.api.plan.business.service; -import com.flytrap.venusplanner.api.plan.business.service.PlanService; import com.flytrap.venusplanner.api.plan.domain.Plan; +import com.flytrap.venusplanner.api.plan.presentation.dto.request.PlanCreateRequest; import com.flytrap.venusplanner.api.plan_category.business.service.PlanCategoryService; import com.flytrap.venusplanner.api.plan_category.domain.PlanCategory; -import com.flytrap.venusplanner.api.study.business.service.StudyService; +import com.flytrap.venusplanner.api.study.business.service.StudyValid; import com.flytrap.venusplanner.api.study.domain.Study; -import com.flytrap.venusplanner.api.study_plan.presentation.dto.request.StudyPlanCreateRequest; -import com.flytrap.venusplanner.api.study_plan.presentation.dto.response.StudyPlanReadResponse; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -18,26 +16,31 @@ @Transactional(readOnly = true) public class StudyPlanFacadeService { - private final StudyService studyService; + private final StudyValid studyValid; private final PlanCategoryService planCategoryService; private final PlanService planService; @Transactional - public Long savePlan(Long studyId, StudyPlanCreateRequest request) { - Study study = studyService.findById(studyId); + public Long savePlan(Long studyId, PlanCreateRequest request) { + Study study = studyValid.findById(studyId); PlanCategory planCategory = planCategoryService.findById(request.categoryId()); return planService.savePlan(study, planCategory, request); } @Transactional public List findAllBy(Long studyId, int year, int month) { - Study study = studyService.findById(studyId); + Study study = studyValid.findById(studyId); List plans = planService.findAllByStudyIdAndYearAndMonth(studyId, year, month); return plans; } - public void deleteById(Long planId) { - planService.deleteById(planId); + @Transactional + public void delete(Long planId, boolean applyAll) { + if (applyAll) { + planService.deleteAllByRecurringId(planId); + } else { + planService.deleteById(planId); + } } } diff --git a/src/main/java/com/flytrap/venusplanner/api/plan/domain/Frequency.java b/src/main/java/com/flytrap/venusplanner/api/plan/domain/Frequency.java index 89cefb1..a6aaa88 100644 --- a/src/main/java/com/flytrap/venusplanner/api/plan/domain/Frequency.java +++ b/src/main/java/com/flytrap/venusplanner/api/plan/domain/Frequency.java @@ -1,5 +1,67 @@ package com.flytrap.venusplanner.api.plan.domain; -public enum Frequency { - WEEKLY, MONTHLY, YEARLY +import com.flytrap.venusplanner.api.plan.business.service.EndOptionCalculate; +import com.flytrap.venusplanner.api.plan.util.DateTimeUtils; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; + +public enum Frequency implements EndOptionCalculate { + WEEKLY(ChronoUnit.WEEKS) { + @Override + public Instant calculateEndDate(Instant startDate, int count) { + ZonedDateTime zdtStart = DateTimeUtils.toZonedDateTime(startDate); + return DateTimeUtils.toInstant(zdtStart.plusWeeks(count - 1)); + } + + @Override + public int calculateCount(Instant startDate, Instant endDate) { + ZonedDateTime zdtStart = DateTimeUtils.toZonedDateTime(startDate); + ZonedDateTime zdtEnd = DateTimeUtils.toZonedDateTime(endDate); + return (int) ChronoUnit.WEEKS.between(zdtStart, zdtEnd) + 1; + } + }, + MONTHLY(ChronoUnit.MONTHS) { + @Override + public Instant calculateEndDate(Instant startDate, int count) { + ZonedDateTime zdtStart = startDate.atZone(ZoneId.systemDefault()); + ZonedDateTime zdtEnd = zdtStart.plusMonths(count - 1); + return zdtEnd.toInstant(); + } + + @Override + public int calculateCount(Instant startDate, Instant endDate) { + ZonedDateTime zdtStart = startDate.atZone(ZoneId.systemDefault()); + ZonedDateTime zdtEnd = endDate.atZone(ZoneId.systemDefault()); + long monthsBetween = ChronoUnit.MONTHS.between(zdtStart, zdtEnd); + return (int) monthsBetween + 1; + } + }, + YEARLY(ChronoUnit.YEARS) { + @Override + public Instant calculateEndDate(Instant startDate, int count) { + ZonedDateTime zdtStart = startDate.atZone(ZoneId.systemDefault()); + ZonedDateTime zdtEnd = zdtStart.plusYears(count - 1); + return zdtEnd.toInstant(); + } + + @Override + public int calculateCount(Instant startDate, Instant endDate) { + ZonedDateTime zdtStart = startDate.atZone(ZoneId.systemDefault()); + ZonedDateTime zdtEnd = endDate.atZone(ZoneId.systemDefault()); + long yearsBetween = ChronoUnit.YEARS.between(zdtStart, zdtEnd); + return (int) yearsBetween + 1; + } + }; + + private final ChronoUnit chronoUnit; + + Frequency(ChronoUnit chronoUnit) { + this.chronoUnit = chronoUnit; + } + + public ChronoUnit getChronoUnit() { + return chronoUnit; + } } diff --git a/src/main/java/com/flytrap/venusplanner/api/plan/domain/Plan.java b/src/main/java/com/flytrap/venusplanner/api/plan/domain/Plan.java index 428b2da..4e68383 100644 --- a/src/main/java/com/flytrap/venusplanner/api/plan/domain/Plan.java +++ b/src/main/java/com/flytrap/venusplanner/api/plan/domain/Plan.java @@ -67,7 +67,6 @@ private Plan(Study study, PlanCategory planCategory, RecurringOption recurringOp this.notificationTime = notificationTime; } - @PrePersist @PreUpdate public void validateTitle() { final String DEFAULT_TILE = "새로운 일정"; @@ -76,4 +75,12 @@ public void validateTitle() { title = DEFAULT_TILE; } } + + @PrePersist + private void beforeSave() { + if (recurringOption != null) { + recurringOption.calculate(startTime); + } + validateTitle(); + } } diff --git a/src/main/java/com/flytrap/venusplanner/api/plan/domain/RecurringOption.java b/src/main/java/com/flytrap/venusplanner/api/plan/domain/RecurringOption.java index 8996d54..de462b7 100644 --- a/src/main/java/com/flytrap/venusplanner/api/plan/domain/RecurringOption.java +++ b/src/main/java/com/flytrap/venusplanner/api/plan/domain/RecurringOption.java @@ -40,4 +40,17 @@ public RecurringOption(Frequency frequency, EndOption endOption, this.recurrenceCount = recurrenceCount; this.endDate = endDate; } + + public void calculate(Instant startDate) { + //TODO:(의견1) endOption에서 계산 로직 실행 + if (recurrenceCount != null && endDate != null) { + return; + } + + if (endOption == EndOption.COUNT && recurrenceCount != null) { + this.endDate = frequency.calculateEndDate(startDate, recurrenceCount); + } else if (endOption == EndOption.DATE && endDate != null) { + this.recurrenceCount = frequency.calculateCount(startDate, endDate); + } + } } diff --git a/src/main/java/com/flytrap/venusplanner/api/plan/infrastructure/repository/PlanRepository.java b/src/main/java/com/flytrap/venusplanner/api/plan/infrastructure/repository/PlanRepository.java index 99b5665..f4891f6 100644 --- a/src/main/java/com/flytrap/venusplanner/api/plan/infrastructure/repository/PlanRepository.java +++ b/src/main/java/com/flytrap/venusplanner/api/plan/infrastructure/repository/PlanRepository.java @@ -13,4 +13,6 @@ public interface PlanRepository extends JpaRepository { "OR (YEAR(p.end_time) = :year AND MONTH(p.end_time) = :month)", nativeQuery = true) List findAllByStudyIdAndYearAndMonth(Long studyId, int year, int month); + + void deleteAllByRecurringOptionId(Long recurringOptionId); } diff --git a/src/main/java/com/flytrap/venusplanner/api/study_plan/presentation/controller/StudyPlanController.java b/src/main/java/com/flytrap/venusplanner/api/plan/presentation/controller/PlanController.java similarity index 65% rename from src/main/java/com/flytrap/venusplanner/api/study_plan/presentation/controller/StudyPlanController.java rename to src/main/java/com/flytrap/venusplanner/api/plan/presentation/controller/PlanController.java index 9f07830..898dced 100644 --- a/src/main/java/com/flytrap/venusplanner/api/study_plan/presentation/controller/StudyPlanController.java +++ b/src/main/java/com/flytrap/venusplanner/api/plan/presentation/controller/PlanController.java @@ -1,10 +1,10 @@ -package com.flytrap.venusplanner.api.study_plan.presentation.controller; +package com.flytrap.venusplanner.api.plan.presentation.controller; import com.flytrap.venusplanner.api.plan.domain.Plan; -import com.flytrap.venusplanner.api.study_plan.business.service.StudyPlanFacadeService; -import com.flytrap.venusplanner.api.study_plan.presentation.dto.request.PlanReadConditionRequest; -import com.flytrap.venusplanner.api.study_plan.presentation.dto.request.StudyPlanCreateRequest; -import com.flytrap.venusplanner.api.study_plan.presentation.dto.response.StudyPlanReadResponse; +import com.flytrap.venusplanner.api.plan.presentation.dto.request.PlanReadConditionRequest; +import com.flytrap.venusplanner.api.plan.presentation.dto.request.PlanCreateRequest; +import com.flytrap.venusplanner.api.plan.presentation.dto.response.PlanReadResponse; +import com.flytrap.venusplanner.api.plan.business.service.StudyPlanFacadeService; import jakarta.validation.Valid; import java.util.List; import lombok.RequiredArgsConstructor; @@ -16,17 +16,18 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController @RequiredArgsConstructor -public class StudyPlanController { +public class PlanController { private final StudyPlanFacadeService studyPlanFacadeService; @PostMapping("/api/v1/studies/{studyId}/plans") public ResponseEntity createPlan(@PathVariable Long studyId, - @Valid @RequestBody StudyPlanCreateRequest request) { + @Valid @RequestBody PlanCreateRequest request) { Long memberId = 1L; Long planId = studyPlanFacadeService.savePlan(studyId, request); @@ -35,20 +36,21 @@ public ResponseEntity createPlan(@PathVariable Long studyId, } @GetMapping("/api/v1/studies/{studyId}/plans") - public ResponseEntity> readPlans( + public ResponseEntity> readPlans( @PathVariable Long studyId, @Valid @ModelAttribute PlanReadConditionRequest params) { List studyPlans = studyPlanFacadeService.findAllBy(studyId, params.year(), params.month()); return ResponseEntity.ok() - .body(StudyPlanReadResponse.from(studyPlans)); + .body(PlanReadResponse.from(studyPlans)); } @DeleteMapping("/api/v1/studies/{studyId}/plans/{planId}") public ResponseEntity deletePlan( - @PathVariable Long planId + @PathVariable Long planId, + @RequestParam boolean applyAll ) { - studyPlanFacadeService.deleteById(planId); + studyPlanFacadeService.delete(planId, applyAll); return ResponseEntity.ok() .build(); diff --git a/src/main/java/com/flytrap/venusplanner/api/study_plan/presentation/dto/request/StudyPlanCreateRequest.java b/src/main/java/com/flytrap/venusplanner/api/plan/presentation/dto/request/PlanCreateRequest.java similarity index 92% rename from src/main/java/com/flytrap/venusplanner/api/study_plan/presentation/dto/request/StudyPlanCreateRequest.java rename to src/main/java/com/flytrap/venusplanner/api/plan/presentation/dto/request/PlanCreateRequest.java index 8fdd45c..f0c4a62 100644 --- a/src/main/java/com/flytrap/venusplanner/api/study_plan/presentation/dto/request/StudyPlanCreateRequest.java +++ b/src/main/java/com/flytrap/venusplanner/api/plan/presentation/dto/request/PlanCreateRequest.java @@ -1,4 +1,4 @@ -package com.flytrap.venusplanner.api.study_plan.presentation.dto.request; +package com.flytrap.venusplanner.api.plan.presentation.dto.request; import com.flytrap.venusplanner.api.plan.domain.Plan; import com.flytrap.venusplanner.api.plan.domain.RecurringOption; @@ -10,7 +10,7 @@ import java.time.Instant; import java.util.Optional; -public record StudyPlanCreateRequest( +public record PlanCreateRequest( @NotNull Long categoryId, diff --git a/src/main/java/com/flytrap/venusplanner/api/study_plan/presentation/dto/request/PlanReadConditionRequest.java b/src/main/java/com/flytrap/venusplanner/api/plan/presentation/dto/request/PlanReadConditionRequest.java similarity index 76% rename from src/main/java/com/flytrap/venusplanner/api/study_plan/presentation/dto/request/PlanReadConditionRequest.java rename to src/main/java/com/flytrap/venusplanner/api/plan/presentation/dto/request/PlanReadConditionRequest.java index 162c3a3..416ae8c 100644 --- a/src/main/java/com/flytrap/venusplanner/api/study_plan/presentation/dto/request/PlanReadConditionRequest.java +++ b/src/main/java/com/flytrap/venusplanner/api/plan/presentation/dto/request/PlanReadConditionRequest.java @@ -1,4 +1,4 @@ -package com.flytrap.venusplanner.api.study_plan.presentation.dto.request; +package com.flytrap.venusplanner.api.plan.presentation.dto.request; import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; diff --git a/src/main/java/com/flytrap/venusplanner/api/study_plan/presentation/dto/request/RecurringOptionRequest.java b/src/main/java/com/flytrap/venusplanner/api/plan/presentation/dto/request/RecurringOptionRequest.java similarity index 90% rename from src/main/java/com/flytrap/venusplanner/api/study_plan/presentation/dto/request/RecurringOptionRequest.java rename to src/main/java/com/flytrap/venusplanner/api/plan/presentation/dto/request/RecurringOptionRequest.java index e704ee3..90ad8ce 100644 --- a/src/main/java/com/flytrap/venusplanner/api/study_plan/presentation/dto/request/RecurringOptionRequest.java +++ b/src/main/java/com/flytrap/venusplanner/api/plan/presentation/dto/request/RecurringOptionRequest.java @@ -1,4 +1,4 @@ -package com.flytrap.venusplanner.api.study_plan.presentation.dto.request; +package com.flytrap.venusplanner.api.plan.presentation.dto.request; import com.flytrap.venusplanner.api.plan.domain.EndOption; import com.flytrap.venusplanner.api.plan.domain.Frequency; diff --git a/src/main/java/com/flytrap/venusplanner/api/plan/presentation/dto/response/PlanReadResponse.java b/src/main/java/com/flytrap/venusplanner/api/plan/presentation/dto/response/PlanReadResponse.java new file mode 100644 index 0000000..2f840d5 --- /dev/null +++ b/src/main/java/com/flytrap/venusplanner/api/plan/presentation/dto/response/PlanReadResponse.java @@ -0,0 +1,23 @@ +package com.flytrap.venusplanner.api.plan.presentation.dto.response; + +import com.flytrap.venusplanner.api.plan.domain.Plan; +import java.time.Instant; +import java.util.List; + +public record PlanReadResponse( + Long planId, + Long categoryId, + String title, + Instant startTime, + Instant endTime +) { + public static List from(List plans) { + return plans.stream() + .map(PlanReadResponse::from) + .toList(); + } + + private static PlanReadResponse from(Plan plan) { + return new PlanReadResponse(plan.getId(), plan.getPlanCategory().getId(), plan.getTitle(), plan.getStartTime(), plan.getEndTime()); + } +} diff --git a/src/main/java/com/flytrap/venusplanner/api/plan/util/DateTimeUtils.java b/src/main/java/com/flytrap/venusplanner/api/plan/util/DateTimeUtils.java new file mode 100644 index 0000000..5762779 --- /dev/null +++ b/src/main/java/com/flytrap/venusplanner/api/plan/util/DateTimeUtils.java @@ -0,0 +1,15 @@ +package com.flytrap.venusplanner.api.plan.util; + +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; + +public class DateTimeUtils { + public static ZonedDateTime toZonedDateTime(Instant instant) { + return instant.atZone(ZoneId.systemDefault()); + } + + public static Instant toInstant(ZonedDateTime zonedDateTime) { + return zonedDateTime.toInstant(); + } +} diff --git a/src/main/java/com/flytrap/venusplanner/api/study/business/service/StudyService.java b/src/main/java/com/flytrap/venusplanner/api/study/business/service/StudyService.java index 50ae1eb..c6850c5 100644 --- a/src/main/java/com/flytrap/venusplanner/api/study/business/service/StudyService.java +++ b/src/main/java/com/flytrap/venusplanner/api/study/business/service/StudyService.java @@ -9,7 +9,7 @@ @Service @RequiredArgsConstructor @Transactional(readOnly = true) -public class StudyService { +public class StudyService implements StudyValid { private final StudyRepository studyRepository; @@ -18,6 +18,7 @@ public Study saveStudy(Study study) { return studyRepository.save(study); } + @Override public Study findById(Long studyId) { //TODO: optional null 처리 return studyRepository.findById(studyId).get(); diff --git a/src/main/java/com/flytrap/venusplanner/api/study/business/service/StudyValid.java b/src/main/java/com/flytrap/venusplanner/api/study/business/service/StudyValid.java new file mode 100644 index 0000000..1aca55c --- /dev/null +++ b/src/main/java/com/flytrap/venusplanner/api/study/business/service/StudyValid.java @@ -0,0 +1,7 @@ +package com.flytrap.venusplanner.api.study.business.service; + +import com.flytrap.venusplanner.api.study.domain.Study; + +public interface StudyValid { + Study findById(Long id); +} diff --git a/src/main/java/com/flytrap/venusplanner/api/study_plan/presentation/dto/response/StudyPlanReadResponse.java b/src/main/java/com/flytrap/venusplanner/api/study_plan/presentation/dto/response/StudyPlanReadResponse.java deleted file mode 100644 index c3e7eb3..0000000 --- a/src/main/java/com/flytrap/venusplanner/api/study_plan/presentation/dto/response/StudyPlanReadResponse.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.flytrap.venusplanner.api.study_plan.presentation.dto.response; - -import com.flytrap.venusplanner.api.plan.domain.Plan; -import java.time.Instant; -import java.util.List; - -public record StudyPlanReadResponse( - Long planId, - Long categoryId, - String title, - Instant startTime, - Instant endTime -) { - public static List from(List plans) { - return plans.stream() - .map(StudyPlanReadResponse::from) - .toList(); - } - - private static StudyPlanReadResponse from(Plan plan) { - return new StudyPlanReadResponse(plan.getId(), plan.getPlanCategory().getId(), plan.getTitle(), plan.getStartTime(), plan.getEndTime()); - } -}