diff --git a/src/main/java/com/project/backend/domain/event/converter/RecurrenceGroupConverter.java b/src/main/java/com/project/backend/domain/event/converter/RecurrenceGroupConverter.java index 4d55621..d793843 100644 --- a/src/main/java/com/project/backend/domain/event/converter/RecurrenceGroupConverter.java +++ b/src/main/java/com/project/backend/domain/event/converter/RecurrenceGroupConverter.java @@ -142,14 +142,19 @@ public static RecurrenceException toRecurrenceExceptionForDelete( public static RecurrenceException toRecurrenceExceptionForUpdate( EventReqDTO.UpdateReq req, RecurrenceGroup recurrenceGroup, - LocalDateTime occurrenceDate + LocalDateTime occurrenceDate, + Integer duration ) { + LocalDateTime startTime = req.startTime() != null ? req.startTime() : null; + LocalDateTime endTime = req.endTime() != null + ? req.endTime() + : startTime != null ? startTime.plusMinutes(duration) : null; return RecurrenceException.builder() .exceptionDate(occurrenceDate) .title(req.title() != null ? req.title() : null) .content(req.content() != null ? req.content() : null) - .startTime(req.startTime() != null ? req.startTime() : null) - .endTime(req.endTime() != null ? req.endTime() : null) + .startTime(startTime) + .endTime(endTime) .exceptionType(ExceptionType.OVERRIDE) .location(req.location() != null ? req.location() : null) .color(req.color() != null ? req.color() : null) @@ -345,13 +350,15 @@ private static void normalizeFrequencyForUpdate( b.frequency(frequency); - Integer interval; + int interval; if (frequency == RecurrenceFrequency.WEEKLY) { // WEEKLY는 무조건 1 interval = 1; } else { // WEEKLY가 아닌 경우만 req → rg 순으로 선택 - interval = req.intervalValue() != null ? req.intervalValue() : rg.getIntervalValue(); + interval = req.intervalValue() != null + ? req.intervalValue() + : rg != null ? rg.getIntervalValue() : 1; } b.interval(interval); diff --git a/src/main/java/com/project/backend/domain/event/entity/Event.java b/src/main/java/com/project/backend/domain/event/entity/Event.java index 2818407..dd87960 100644 --- a/src/main/java/com/project/backend/domain/event/entity/Event.java +++ b/src/main/java/com/project/backend/domain/event/entity/Event.java @@ -161,4 +161,9 @@ public void updateRecurrenceGroup(RecurrenceGroup recurrenceGroup) { this.recurrenceGroup = recurrenceGroup; this.recurrenceFrequency = recurrenceGroup.getFrequency(); } + + public void updateTime(LocalDateTime startTime, LocalDateTime endTime) { + this.startTime = startTime; + this.endTime = endTime; + } } diff --git a/src/main/java/com/project/backend/domain/event/service/RecurrenceTimeAdjuster.java b/src/main/java/com/project/backend/domain/event/service/RecurrenceTimeAdjuster.java index 0430714..5361f44 100644 --- a/src/main/java/com/project/backend/domain/event/service/RecurrenceTimeAdjuster.java +++ b/src/main/java/com/project/backend/domain/event/service/RecurrenceTimeAdjuster.java @@ -178,27 +178,44 @@ private static LocalDateTime adjustMonthlyByNthWeekday( return base; } + // 이번 달 먼저, 후보가 base보다 이전이면 다음 달로 1번 더 시도 YearMonth month = YearMonth.from(base); - // 해당 월의 n번째 주 시작 (월요일 기준) - LocalDate startOfNthWeek = getNthWeekday(month, week, DayOfWeek.MONDAY); - if (startOfNthWeek == null) { - throw new IllegalStateException("Invalid weekOfMonth"); + LocalDate candidateDate = findNthWeekCandidate(month, week, targetDays); + if (candidateDate != null) { + LocalDateTime candidate = LocalDateTime.of(candidateDate, base.toLocalTime()); + if (!candidate.isBefore(base)) { + return candidate; // candidate >= base 이면 이번 달 확정 + } } - LocalDate endOfNthWeek = startOfNthWeek.plusDays(6); + // 이번 달 후보가 base보다 이전이거나(= 과거), 아예 없으면 다음 달 + YearMonth nextMonth = month.plusMonths(1); + LocalDate nextCandidateDate = findNthWeekCandidate(nextMonth, week, targetDays); + if (nextCandidateDate != null) { + return LocalDateTime.of(nextCandidateDate, base.toLocalTime()); + } + + throw new RecurrenceGroupException(RecurrenceGroupErrorCode.FAIL_ADJUSTMENT_DAY_OF_WEEK); + } - // 그 주 안에서 rule에 맞는 첫 번째 요일 선택 - for (LocalDate d = startOfNthWeek; - !d.isAfter(endOfNthWeek); - d = d.plusDays(1)) { + private static LocalDate findNthWeekCandidate( + YearMonth month, + int week, + List targetDays + ) { + // 해당 월의 n번째 주 시작(월요일 기준) + LocalDate startOfNthWeek = getNthWeekday(month, week, DayOfWeek.MONDAY); + if (startOfNthWeek == null) return null; + LocalDate endOfNthWeek = startOfNthWeek.plusDays(6); + + for (LocalDate d = startOfNthWeek; !d.isAfter(endOfNthWeek); d = d.plusDays(1)) { if (targetDays.contains(d.getDayOfWeek())) { - return LocalDateTime.of(d, base.toLocalTime()); + return d; // 그 주 안에서 rule에 맞는 첫 날짜 } } - - throw new RecurrenceGroupException(RecurrenceGroupErrorCode.FAIL_ADJUSTMENT_DAY_OF_WEEK); + return null; } private static LocalDate getNthWeekday( diff --git a/src/main/java/com/project/backend/domain/event/service/command/EventCommandServiceImpl.java b/src/main/java/com/project/backend/domain/event/service/command/EventCommandServiceImpl.java index 67e3520..97fa1c9 100644 --- a/src/main/java/com/project/backend/domain/event/service/command/EventCommandServiceImpl.java +++ b/src/main/java/com/project/backend/domain/event/service/command/EventCommandServiceImpl.java @@ -161,7 +161,7 @@ public void updateEvent( RecurrenceGroupReqDTO.CreateReq createReq = RecurrenceGroupConverter.toCreateReq(req.recurrenceGroup()); rgValidator.validateCreate(createReq, start); - RecurrenceGroup rg = updateToRecurrenceEvent(req, req.recurrenceGroup(), member, start); + RecurrenceGroup rg = updateToRecurrenceEvent(req, req.recurrenceGroup(), event, member, start); event.updateRecurrenceGroup(rg); rg.updateEvent(event); // 이벤트 + 반복 생성에 따른 리스너 수정 로직 실행 @@ -424,9 +424,16 @@ private void updateRecurrenceException(EventReqDTO.UpdateReq req, RecurrenceExce private RecurrenceGroup updateToRecurrenceEvent( EventReqDTO.UpdateReq eventReq, RecurrenceGroupReqDTO.UpdateReq rgReq, + Event event, Member member, LocalDateTime start) { RecurrenceGroupSpec rgSpec = RecurrenceGroupConverter.from(eventReq, rgReq, null, start); + + AdjustedTime adjusted = RecurrenceTimeAdjuster.adjust(event.getStartTime(), event.getEndTime(), rgSpec); + + // 생성된 반복에 따른 일정 start,endTime 업데이트 + event.updateTime(adjusted.start(), adjusted.end()); + return createRecurrenceGroup(rgSpec, member); } @@ -466,7 +473,8 @@ private void updateThisEventOnly( return; } - RecurrenceException ex = RecurrenceGroupConverter.toRecurrenceExceptionForUpdate(req, rg, occurrenceDate); + RecurrenceException ex = RecurrenceGroupConverter.toRecurrenceExceptionForUpdate( + req, rg, occurrenceDate, event.getDurationMinutes()); recurrenceExRepository.save(ex); rg.addExceptionDate(ex); // 해당 event가 속했던 반복 객체에 예외 날짜 추가 diff --git a/src/main/java/com/project/backend/domain/event/service/query/EventQueryServiceImpl.java b/src/main/java/com/project/backend/domain/event/service/query/EventQueryServiceImpl.java index 2cf83d4..06b80a6 100644 --- a/src/main/java/com/project/backend/domain/event/service/query/EventQueryServiceImpl.java +++ b/src/main/java/com/project/backend/domain/event/service/query/EventQueryServiceImpl.java @@ -63,10 +63,10 @@ public EventResDTO.DetailRes getEventDetail( eventValidator.validateRead(event, occurrenceDate); - // 찾고자 하는 것이 부모 이벤트인 경우 - if (event.getStartTime().isEqual(occurrenceDate)) { - return EventConverter.toDetailRes(event); - } +// // 찾고자 하는 것이 부모 이벤트인 경우 +// if (event.getStartTime().isEqual(occurrenceDate)) { +// return EventConverter.toDetailRes(event); +// } return eventOccurrenceResolver.resolveForRead(event, occurrenceDate); } @@ -335,10 +335,39 @@ private List expandEvents( recurrenceExceptions = recurrenceExceptionRepository.findAllByRecurrenceGroupId(event.getRecurrenceGroup().getId()); } - // 부모가 검색 범위에 포함되어 있지 않다면 시간만 추출하고 폐기 - if (!event.getEndTime().isBefore(startRange) && !event.getStartTime().isAfter(endRange)) { - expandedEvents.add(EventConverter.toDetailRes(event)); + LocalDateTime tempStartTime = event.getStartTime(); + LocalDateTime tempEndTime = event.getEndTime(); + RecurrenceException tempEx = null; + boolean isSkip = false; + for (RecurrenceException ex : recurrenceExceptions) { + // 만약 부모 익셉션이 존재한다면 + if (ex.getExceptionDate().isEqual(event.getStartTime())) { + log.debug("부모 익셉션 존재"); + tempEx = ex; + } + } + if (tempEx != null) { + if (tempEx.getExceptionType() == OVERRIDE) { + log.debug("오버라이드 존재"); + tempStartTime = tempEx.getStartTime() != null ? tempEx.getStartTime() : event.getStartTime(); + tempEndTime = tempEx.getEndTime() != null ? tempEx.getEndTime() : event.getEndTime(); + } + else if (tempEx.getExceptionType() == SKIP) { + log.debug("스킵 존재"); + isSkip = true; + } + // 부모가 검색 범위에 포함되어 있지 않다면 시간만 추출하고 폐기 + if (!isSkip && !tempStartTime.isBefore(startRange) && !tempEndTime.isAfter(endRange)) { + log.debug("예외 부모가 범위에 포함되었습니다"); + expandedEvents.add(EventConverter.toDetailRes(tempEx, event)); + } + } else { + if (!tempStartTime.isBefore(startRange) && !tempEndTime.isAfter(endRange)) { + log.debug("원본 부모가 범위에 포함되었습니다"); + expandedEvents.add(EventConverter.toDetailRes(event)); + } } + // 부모 이벤트 포함 int count = 1; // 생성기에 최초로 들어갈 기준 시간