Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.flytrap.venusplanner.api.plan.business.service;

import java.time.Instant;

public interface EndOptionCalculate {
Copy link
Contributor

Choose a reason for hiding this comment

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

Calculate가 동사라서 Calculator나 Calculatable? 어떠세요?

Instant calculateEndDate(Instant startDate, int count);
int calculateCount(Instant startDate, Instant endDate);
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -16,24 +23,68 @@
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<Plan> recurringPlan = createPlansWithRecurringPlan(plan);
planRepository.saveAll(recurringPlan);
}

return plan.getId();
}

private List<Plan> 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<Plan> 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();
Comment on lines +51 to +60
Copy link
Member

Choose a reason for hiding this comment

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

서비스 로직 코드 읽는데 빌더가 너무 길어서 Plan에 static으로 메서드 하나 만드는건 어떨까요


createdPlans.add(planRepository.save(newPlan));
}

return createdPlans;
}

public List<Plan> findAllByStudyIdAndYearAndMonth(Long studyId, int year, int month) {
return planRepository.findAllByStudyIdAndYearAndMonth(studyId, year, month);
}

@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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<Plan> findAllBy(Long studyId, int year, int month) {
Study study = studyService.findById(studyId);
Study study = studyValid.findById(studyId);
List<Plan> 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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
},
Comment on lines +11 to +24
Copy link
Contributor

Choose a reason for hiding this comment

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

이 방식 보기 좋아요!

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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ private Plan(Study study, PlanCategory planCategory, RecurringOption recurringOp
this.notificationTime = notificationTime;
}

@PrePersist
@PreUpdate
public void validateTitle() {
final String DEFAULT_TILE = "새로운 일정";
Expand All @@ -76,4 +75,12 @@ public void validateTitle() {
title = DEFAULT_TILE;
}
}

@PrePersist
private void beforeSave() {
if (recurringOption != null) {
recurringOption.calculate(startTime);
}
validateTitle();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,17 @@ public RecurringOption(Frequency frequency, EndOption endOption,
this.recurrenceCount = recurrenceCount;
this.endDate = endDate;
}

public void calculate(Instant startDate) {
Comment on lines +43 to +44
Copy link
Member

Choose a reason for hiding this comment

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

calculate 메서드명이 좀 더 구체적이면 좋을 것 같아요

Copy link
Member

Choose a reason for hiding this comment

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

반복 옵션이 생성될 때 생성자에서 계산해서 넣어주면 안되나요?? calculate가 있어야 하는 이유가 궁금합니다

//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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ public interface PlanRepository extends JpaRepository<Plan, Long> {
"OR (YEAR(p.end_time) = :year AND MONTH(p.end_time) = :month)",
nativeQuery = true)
List<Plan> findAllByStudyIdAndYearAndMonth(Long studyId, int year, int month);

void deleteAllByRecurringOptionId(Long recurringOptionId);
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<Long> createPlan(@PathVariable Long studyId,
@Valid @RequestBody StudyPlanCreateRequest request) {
@Valid @RequestBody PlanCreateRequest request) {
Long memberId = 1L;
Long planId = studyPlanFacadeService.savePlan(studyId, request);

Expand All @@ -35,20 +36,21 @@ public ResponseEntity<Long> createPlan(@PathVariable Long studyId,
}

@GetMapping("/api/v1/studies/{studyId}/plans")
public ResponseEntity<List<StudyPlanReadResponse>> readPlans(
public ResponseEntity<List<PlanReadResponse>> readPlans(
@PathVariable Long studyId,
@Valid @ModelAttribute PlanReadConditionRequest params) {
List<Plan> 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<Void> deletePlan(
@PathVariable Long planId
@PathVariable Long planId,
@RequestParam boolean applyAll
) {
studyPlanFacadeService.deleteById(planId);
studyPlanFacadeService.delete(planId, applyAll);

return ResponseEntity.ok()
.build();
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -10,7 +10,7 @@
import java.time.Instant;
import java.util.Optional;

public record StudyPlanCreateRequest(
public record PlanCreateRequest(
@NotNull
Long categoryId,

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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<PlanReadResponse> from(List<Plan> 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());
}
}
Loading