Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.back.domain.study.plan.controller;

import com.back.domain.study.plan.dto.StudyPlanDeleteResponse;
import com.back.domain.study.plan.dto.StudyPlanRequest;
import com.back.domain.study.plan.dto.StudyPlanListResponse;
import com.back.domain.study.plan.dto.StudyPlanResponse;
Expand Down Expand Up @@ -111,15 +112,15 @@ public ResponseEntity<RsData<StudyPlanResponse>> updateStudyPlan(
description = "기존 학습 계획을 삭제합니다. 반복 계획의 경우 적용 범위를 applyScope로 설정 할 수 있으며" +
"클라이언트에서는 paln에 repeat_rule이 있으면 반복 계획으로 간주하고 반드시 apply_scope를 쿼리 파라미터로 넘겨야 합니다." +
"repeat_rule이 없으면 단발성 계획으로 간주하여 삭제 범위를 설정 할 필요가 없으므로 apply_scope를 넘기지 않아도 됩니다.")
public ResponseEntity<RsData<Void>> deleteStudyPlan(
public ResponseEntity<RsData<StudyPlanDeleteResponse>> deleteStudyPlan(
@AuthenticationPrincipal CustomUserDetails user,
@PathVariable Long planId,
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate selectedDate,
@RequestParam(name = "applyScope", required = true) ApplyScope applyScope) {
Long userId = user.getUserId();

studyPlanService.deleteStudyPlan(userId, planId, selectedDate, applyScope);
return ResponseEntity.ok(RsData.success("학습 계획이 성공적으로 삭제되었습니다."));
StudyPlanDeleteResponse response = studyPlanService.deleteStudyPlan(userId, planId, selectedDate, applyScope);
return ResponseEntity.ok(RsData.success("학습 계획이 성공적으로 삭제되었습니다.",response));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.back.domain.study.plan.dto;

import com.back.domain.study.plan.entity.ApplyScope;
import com.back.domain.study.plan.entity.Color;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.time.LocalDate;
import java.time.LocalDateTime;

@Getter
@NoArgsConstructor
@AllArgsConstructor
public class StudyPlanDeleteResponse {
private Long id;
private String subject;
private LocalDateTime startDate;
private LocalDateTime endDate;
private Color color;
private LocalDate deletedDate; // 삭제된 날짜
private ApplyScope applyScope; // 삭제 범위

public StudyPlanDeleteResponse(StudyPlanResponse plan, ApplyScope applyScope) {
this.id = plan.getId();
this.subject = plan.getSubject();
this.startDate = plan.getStartDate();
this.endDate = plan.getEndDate();
this.color = plan.getColor();
this.deletedDate = plan.getStartDate().toLocalDate();
this.applyScope = applyScope;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.back.domain.study.plan.dto;

import com.back.domain.study.plan.entity.Color;
import com.back.domain.study.plan.entity.DayOfWeek;
import com.back.domain.study.plan.entity.Frequency;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
Expand Down Expand Up @@ -37,7 +38,7 @@ public class StudyPlanRequest {
public static class RepeatRuleRequest {
private Frequency frequency;
private Integer intervalValue;
private List<String> byDay = new ArrayList<>(); // 문자열 리스트
private List<DayOfWeek> byDay = new ArrayList<>();

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
private String untilDate; // "2025-12-31" 형태
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,7 @@ public class StudyPlanResponse {
public static class RepeatRuleResponse {
private Frequency frequency;
private Integer repeatInterval;
// byDay 필드는 이미 List<String>으로 선언되어 있음.
private List<String> byDay = new ArrayList<>(); // "MON" 형태의 문자열 리스트
private List<DayOfWeek> byDay = new ArrayList<>(); // "MON" 형태의 enum 문자열 리스트

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
private LocalDate untilDate;
Expand All @@ -60,16 +59,6 @@ public RepeatRuleResponse(com.back.domain.study.plan.entity.RepeatRule repeatRul
}
}

public List<DayOfWeek> getByDaysList() {
if (byDay == null || byDay.isEmpty()) {
return List.of();
}

// List<String>의 각 요소를 DayOfWeek enum으로 변환하여 반환
return byDay.stream()
.map(com.back.domain.study.plan.entity.DayOfWeek::valueOf)
.collect(Collectors.toList());
}
}
//엔티티를 DTO로 변환하는 생성자
public StudyPlanResponse(StudyPlan studyPlan) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public class RepeatRule extends BaseEntity {

//요일은 응답에 들어있는 요일을 그대로 저장 (예: "WED")
@Column(name = "by_day")
private List<String> byDay = new ArrayList<>();
private List<DayOfWeek> byDay = new ArrayList<>();

private LocalDate untilDate;
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@ public class RepeatRuleEmbeddable {
private Frequency frequency;

private Integer intervalValue;
private List<String> byDay = new ArrayList<>();
private List<DayOfWeek> byDay = new ArrayList<>();
private LocalDate untilDate; // LocalDateTime → LocalDate 변경
}

This file was deleted.

132 changes: 75 additions & 57 deletions src/main/java/com/back/domain/study/plan/service/StudyPlanService.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.back.domain.study.plan.service;

import com.back.domain.study.plan.dto.StudyPlanDeleteRequest;
import com.back.domain.study.plan.dto.StudyPlanDeleteResponse;
import com.back.domain.study.plan.dto.StudyPlanRequest;
import com.back.domain.study.plan.dto.StudyPlanResponse;
import com.back.domain.study.plan.entity.*;
Expand Down Expand Up @@ -69,19 +69,9 @@ private RepeatRule createRepeatRule(StudyPlanRequest.RepeatRuleRequest request,
repeatRule.setStudyPlan(studyPlan);
repeatRule.setFrequency(request.getFrequency());
repeatRule.setRepeatInterval(request.getIntervalValue() != null ? request.getIntervalValue() : 1);
// byDay 설정 (WEEKLY인 경우에만 의미 있음)
getByDayInWeekly(request, studyPlan, repeatRule);

// byDay 설정 (WEEKLY 인 경우에만)
if (request.getFrequency() == Frequency.WEEKLY) {
// 1. byDay가 없으면 시작일 요일을 자동으로 설정 (현재 구현 의도 반영)
if(request.getByDay() == null || request.getByDay().isEmpty()) {
String startDayOfWeek = studyPlan.getStartDate().getDayOfWeek().name().substring(0, 3);
// *가정: RepeatRule.byDay는 List<String> 타입으로 가정
repeatRule.setByDay(List.of(startDayOfWeek));
} else {
// 2. byDay가 있다면 요청 값을 사용 (List<String> to List<String> 매핑 확인)
repeatRule.setByDay(request.getByDay());
}
}
// untilDate 설정 및 검증
LocalDate untilDate;

Expand Down Expand Up @@ -335,6 +325,10 @@ public StudyPlanResponse updateStudyPlan(Long userId, Long planId, StudyPlanRequ

switch (updateType) {
case ORIGINAL_PLAN_UPDATE:
// 요청에 반복 규칙이 있으면 반복 규칙 수정 후 원본 계획 수정
if (request.getRepeatRule() != null) {
updateRepeatRule(originalPlan.getRepeatRule(), request.getRepeatRule(), originalPlan);
}
return updateOriginalPlan(originalPlan, request);

case REPEAT_INSTANCE_CREATE:
Expand Down Expand Up @@ -390,11 +384,6 @@ private StudyPlanResponse updateOriginalPlan(StudyPlan originalPlan, StudyPlanRe
if (request.getEndDate() != null) originalPlan.setEndDate(request.getEndDate());
if (request.getColor() != null) originalPlan.setColor(request.getColor());

// 요청에 반복 규칙이 있고 원본 반복성 계획인 경우에만 반복 규칙 수정
if (request.getRepeatRule() != null && originalPlan.getRepeatRule() != null) {
updateRepeatRule(originalPlan.getRepeatRule(), request.getRepeatRule());
}

StudyPlan savedPlan = studyPlanRepository.save(originalPlan);
return new StudyPlanResponse(savedPlan);
}
Expand All @@ -412,7 +401,7 @@ private StudyPlanResponse createRepeatException(StudyPlan originalPlan, StudyPla
exception.setStudyPlan(originalPlan);
exception.setExceptionDate(exceptionDate);
exception.setExceptionType(StudyPlanException.ExceptionType.MODIFIED);
exception.setApplyScope(applyScope); // 파라미터로 받은 applyScope
exception.setApplyScope(applyScope);

// 수정된 내용 설정
if (request.getSubject() != null) exception.setModifiedSubject(request.getSubject());
Expand All @@ -422,24 +411,10 @@ private StudyPlanResponse createRepeatException(StudyPlan originalPlan, StudyPla

// 반복 규칙 수정. 요청에 반복 규칙이 있으면 설정
if (request.getRepeatRule() != null) {
RepeatRuleEmbeddable embeddable = new RepeatRuleEmbeddable();
embeddable.setFrequency(request.getRepeatRule().getFrequency());
embeddable.setIntervalValue(request.getRepeatRule().getIntervalValue());
embeddable.setByDay(request.getRepeatRule().getByDay());

if (request.getRepeatRule().getUntilDate() != null && !request.getRepeatRule().getUntilDate().isEmpty()) {
try {
LocalDate untilDate = LocalDate.parse(request.getRepeatRule().getUntilDate());
embeddable.setUntilDate(untilDate);
} catch (Exception e) {
throw new CustomException(ErrorCode.INVALID_DATE_FORMAT);
}
}

RepeatRuleEmbeddable embeddable = createRepeatRuleEmbeddable(request.getRepeatRule(), request.getStartDate());
exception.setModifiedRepeatRule(embeddable);
}


studyPlanExceptionRepository.save(exception);
return createVirtualPlanForDate(originalPlan, exceptionDate);
}
Expand All @@ -458,38 +433,26 @@ private StudyPlanResponse updateExistingException(StudyPlan originalPlan, StudyP
if (request.getEndDate() != null) existingException.setModifiedEndDate(request.getEndDate());
if (request.getColor() != null) existingException.setModifiedColor(request.getColor());

// ApplyScope도 업데이트 (사용자가 범위를 변경할 수 있음)
// ApplyScope도 업데이트
existingException.setApplyScope(applyScope);

// 반복 규칙 수정사항 있으면 예외 안에 추가 (embeddable)
if (request.getRepeatRule() != null) {
RepeatRuleEmbeddable embeddable = new RepeatRuleEmbeddable();
embeddable.setFrequency(request.getRepeatRule().getFrequency());
embeddable.setIntervalValue(request.getRepeatRule().getIntervalValue());
embeddable.setByDay(request.getRepeatRule().getByDay());

if (request.getRepeatRule().getUntilDate() != null && !request.getRepeatRule().getUntilDate().isEmpty()) {
try {
LocalDate untilDate = LocalDate.parse(request.getRepeatRule().getUntilDate());
embeddable.setUntilDate(untilDate);
} catch (Exception e) {
throw new CustomException(ErrorCode.INVALID_DATE_FORMAT);
}
}

RepeatRuleEmbeddable embeddable = createRepeatRuleEmbeddable(request.getRepeatRule(), request.getStartDate());
existingException.setModifiedRepeatRule(embeddable);
}

studyPlanExceptionRepository.save(existingException);
return createVirtualPlanForDate(originalPlan, exceptionDate);
}


// 원본의 반복 룰 수정 (엔티티)
private void updateRepeatRule(RepeatRule repeatRule, StudyPlanRequest.RepeatRuleRequest request) {
private void updateRepeatRule(RepeatRule repeatRule, StudyPlanRequest.RepeatRuleRequest request, StudyPlan studyPlan) {
if (request.getFrequency() != null) repeatRule.setFrequency(request.getFrequency());
if (request.getIntervalValue() != null) repeatRule.setRepeatInterval(request.getIntervalValue());
if (request.getByDay() != null) repeatRule.setByDay(request.getByDay());

// byDay 자동 설정 (기존 메서드 재사용)
getByDayInWeekly(request, studyPlan, repeatRule);

if (request.getUntilDate() != null && !request.getUntilDate().isEmpty()) {
try {
Expand All @@ -501,22 +464,52 @@ private void updateRepeatRule(RepeatRule repeatRule, StudyPlanRequest.RepeatRule
}
}

// RepeatRuleEmbeddable 생성 헬퍼 메서드 (중복 코드 제거)
private RepeatRuleEmbeddable createRepeatRuleEmbeddable(StudyPlanRequest.RepeatRuleRequest request, LocalDateTime startDate) {
RepeatRuleEmbeddable embeddable = new RepeatRuleEmbeddable();
embeddable.setFrequency(request.getFrequency());
embeddable.setIntervalValue(request.getIntervalValue());

// byDay 자동 설정 (오버로딩된 메서드 사용)
getByDayInWeekly(request, startDate, embeddable);

if (request.getUntilDate() != null && !request.getUntilDate().isEmpty()) {
try {
LocalDate untilDate = LocalDate.parse(request.getUntilDate());
embeddable.setUntilDate(untilDate);
} catch (Exception e) {
throw new CustomException(ErrorCode.INVALID_DATE_FORMAT);
}
}

return embeddable;
}

// ==================== 삭제 ===================
@Transactional
public void deleteStudyPlan(Long userId, Long planId, LocalDate selectedDate, ApplyScope applyScope) {
public StudyPlanDeleteResponse deleteStudyPlan(Long userId, Long planId, LocalDate selectedDate, ApplyScope applyScope) {
StudyPlan studyPlan = studyPlanRepository.findById(planId)
.orElseThrow(() -> new CustomException(ErrorCode.PLAN_NOT_FOUND));

validateUserAccess(studyPlan, userId);

// 단발성 계획 삭제 (반복 룰이 null이거나 applyScope가 null인 경우)
// 삭제 전 정보 조회
StudyPlanResponse deletedPlan;

if (studyPlan.getRepeatRule() == null || applyScope == null) {
// 단발성 계획
deletedPlan = new StudyPlanResponse(studyPlan);
studyPlanRepository.delete(studyPlan);
return;
} else {
// 반복성 계획 - 가상 계획 조회
deletedPlan = createVirtualPlanForDate(studyPlan, selectedDate);
if (deletedPlan == null) {
throw new CustomException(ErrorCode.PLAN_NOT_FOUND);
}
deleteRepeatPlan(studyPlan, selectedDate, applyScope);
}

// 반복성 계획 삭제 - applyScope에 따른 처리
deleteRepeatPlan(studyPlan, selectedDate, applyScope);
return new StudyPlanDeleteResponse(deletedPlan, applyScope);
}

private void deleteRepeatPlan(StudyPlan studyPlan, LocalDate selectedDate, ApplyScope applyScope) {
Expand Down Expand Up @@ -617,5 +610,30 @@ private void validateRepeatRuleDate(StudyPlan studyPlan, LocalDate untilDate) {
throw new CustomException(ErrorCode.REPEAT_INVALID_UNTIL_DATE);
}
}
// WEEKLY인 경우 빈 byDay 처리 메서드 (RepeatRule용)
private void getByDayInWeekly(StudyPlanRequest.RepeatRuleRequest request, StudyPlan studyPlan, RepeatRule repeatRule) {
// byDay 설정 (WEEKLY 인 경우에만)
if (request.getFrequency() == Frequency.WEEKLY) {
// 1. byDay가 없으면 시작일 요일을 자동으로 설정
if(request.getByDay() == null || request.getByDay().isEmpty()) {
DayOfWeek startDay = DayOfWeek.valueOf(studyPlan.getStartDate().getDayOfWeek().name().substring(0,3));
repeatRule.setByDay(List.of(startDay));
} else {
// 2. byDay가 있다면 요청 값을 사용
repeatRule.setByDay(request.getByDay());
}
}
}
// WEEKLY인 경우 빈 byDay 처리 메서드 (RepeatRuleEmbeddable용 - 오버로딩)
private void getByDayInWeekly(StudyPlanRequest.RepeatRuleRequest request, LocalDateTime startDate, RepeatRuleEmbeddable embeddable) {
if (request.getFrequency() == Frequency.WEEKLY) {
if (request.getByDay() == null || request.getByDay().isEmpty()) {
DayOfWeek startDay = DayOfWeek.valueOf(startDate.getDayOfWeek().name().substring(0, 3));
embeddable.setByDay(List.of(startDay));
} else {
embeddable.setByDay(request.getByDay());
}
}
}

}
Loading