From 9c84d557b149776684726859080a3507d24bf3f7 Mon Sep 17 00:00:00 2001 From: hyunseok Date: Wed, 25 Feb 2026 16:18:16 +0900 Subject: [PATCH 1/5] =?UTF-8?q?=F0=9F=90=9Bfix:=20=EC=9D=BC=EC=A0=95=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=20=EC=A1=B0=ED=9A=8C=20=EC=8B=9C,=20?= =?UTF-8?q?=EB=B6=80=EB=AA=A8=20=EC=9D=BC=EC=A0=95=EC=97=90=20=EB=8C=80?= =?UTF-8?q?=ED=95=9C=20=EB=B0=98=ED=99=98=20=EC=8B=9C,=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EC=82=AC=ED=95=AD=EC=97=90=20=EB=8C=80=ED=95=9C=20?= =?UTF-8?q?=EB=AF=B8=EB=B0=98=EC=98=81=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/event/service/query/EventQueryServiceImpl.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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..fea751a 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); } From 1e1b5dcc98f248a9fc21c64352a11fa6a70ef574 Mon Sep 17 00:00:00 2001 From: hyunseok Date: Wed, 25 Feb 2026 16:47:14 +0900 Subject: [PATCH 2/5] =?UTF-8?q?=F0=9F=90=9Bfix:=20THIS=5FEVENT=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=EC=8B=9C,=20startTime=EC=9D=84=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=ED=95=9C=20=EA=B2=BD=EC=9A=B0,=20endTime=EB=8F=84=20duration?= =?UTF-8?q?=EC=97=90=20=EB=A7=9E=EA=B2=8C=20=EC=88=98=EC=A0=95=EB=90=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../event/converter/RecurrenceGroupConverter.java | 11 ++++++++--- .../service/command/EventCommandServiceImpl.java | 3 ++- 2 files changed, 10 insertions(+), 4 deletions(-) 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..e804706 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) 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..919c80b 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 @@ -466,7 +466,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가 속했던 반복 객체에 예외 날짜 추가 From df1d37201258b60684cf3263282c56ad1a7d5986 Mon Sep 17 00:00:00 2001 From: Gyueon Lee Date: Wed, 25 Feb 2026 17:23:12 +0900 Subject: [PATCH 3/5] =?UTF-8?q?=F0=9F=90=9Bfix:=20=EC=9D=B4=EB=B2=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BA=98=EB=A6=B0=EB=8D=94=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EC=8B=9C=20=EB=B6=80=EB=AA=A8=EA=B0=80=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=EB=98=90=EB=8A=94=20=EC=82=AD=EC=A0=9C=EB=90=98=EC=97=88?= =?UTF-8?q?=EC=9D=84=20=EB=95=8C,=20=EC=BA=98=EB=A6=B0=EB=8D=94=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=EC=97=90=EC=84=9C=20=EB=B0=98=EC=98=81?= =?UTF-8?q?=EB=90=98=EC=A7=80=20=EC=95=8A=EB=8D=98=20=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/query/EventQueryServiceImpl.java | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) 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..bfb3046 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 @@ -335,9 +335,27 @@ private List expandEvents( recurrenceExceptions = recurrenceExceptionRepository.findAllByRecurrenceGroupId(event.getRecurrenceGroup().getId()); } + LocalDateTime tempStartTime = event.getStartTime(); + LocalDateTime tempEndTime = event.getEndTime(); + boolean isSkip = false; + for (RecurrenceException ex : recurrenceExceptions) { + // 만약 부모 익셉션이 존재한다면 + if (ex.getExceptionDate().isEqual(event.getStartTime())) { + if (ex.getExceptionType() == OVERRIDE) { + tempStartTime = ex.getStartTime() != null ? ex.getStartTime() : event.getStartTime(); + tempEndTime = ex.getEndTime() != null ? ex.getEndTime() : event.getEndTime(); + break; + } + else if (ex.getExceptionType() == SKIP) { + isSkip = true; + break; + } + } + } + // 부모가 검색 범위에 포함되어 있지 않다면 시간만 추출하고 폐기 - if (!event.getEndTime().isBefore(startRange) && !event.getStartTime().isAfter(endRange)) { - expandedEvents.add(EventConverter.toDetailRes(event)); + if (!isSkip && !tempStartTime.isBefore(startRange) && !tempEndTime.isAfter(endRange)) { + expandedEvents.add(EventConverter.toDetailRes(event, tempStartTime, tempEndTime)); } // 부모 이벤트 포함 int count = 1; From ddfff315d6267e7477f6ce2f7777617adb329f16 Mon Sep 17 00:00:00 2001 From: Gyueon Lee Date: Wed, 25 Feb 2026 17:39:57 +0900 Subject: [PATCH 4/5] =?UTF-8?q?=F0=9F=90=9Bfix:=20=EB=B6=80=EB=AA=A8?= =?UTF-8?q?=EC=9D=98=20=EC=9D=B5=EC=85=89=EC=85=98=EC=9D=B4=20=EC=8B=9C?= =?UTF-8?q?=EC=9E=91=20=EC=8B=9C=EA=B0=84=EA=B3=BC=20=EC=A2=85=EB=A3=8C=20?= =?UTF-8?q?=EC=8B=9C=EA=B0=84=EB=A7=8C=20=EB=B0=98=EC=98=81=EB=90=98?= =?UTF-8?q?=EB=A9=B4=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/query/EventQueryServiceImpl.java | 37 ++++++++++++------- 1 file changed, 24 insertions(+), 13 deletions(-) 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 bfb3046..ef3322e 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 @@ -337,26 +337,37 @@ private List expandEvents( } LocalDateTime tempStartTime = event.getStartTime(); LocalDateTime tempEndTime = event.getEndTime(); + RecurrenceException tempEx = null; boolean isSkip = false; for (RecurrenceException ex : recurrenceExceptions) { // 만약 부모 익셉션이 존재한다면 if (ex.getExceptionDate().isEqual(event.getStartTime())) { - if (ex.getExceptionType() == OVERRIDE) { - tempStartTime = ex.getStartTime() != null ? ex.getStartTime() : event.getStartTime(); - tempEndTime = ex.getEndTime() != null ? ex.getEndTime() : event.getEndTime(); - break; - } - else if (ex.getExceptionType() == SKIP) { - isSkip = true; - break; - } + log.debug("부모 익셉션 존재"); + tempEx = ex; } } - - // 부모가 검색 범위에 포함되어 있지 않다면 시간만 추출하고 폐기 - if (!isSkip && !tempStartTime.isBefore(startRange) && !tempEndTime.isAfter(endRange)) { - expandedEvents.add(EventConverter.toDetailRes(event, tempStartTime, tempEndTime)); + 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; // 생성기에 최초로 들어갈 기준 시간 From 5112614c4b66c8d44979270bf8d1befa8c473ef1 Mon Sep 17 00:00:00 2001 From: hyunseok Date: Wed, 25 Feb 2026 18:57:49 +0900 Subject: [PATCH 5/5] =?UTF-8?q?=F0=9F=90=9Bfix:=20=EB=A7=A4=EC=9B=94=20N?= =?UTF-8?q?=EB=B2=88=EC=A7=B8=20N=EC=9A=94=EC=9D=BC=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=20=EC=8B=9C,=20=EC=9B=90=EB=B3=B8=EC=9D=BC=EC=A0=95=EC=9D=98?= =?UTF-8?q?=20startTime=EC=9D=B4=20=EC=84=A4=EC=A0=95=ED=95=9C=20n?= =?UTF-8?q?=EB=B2=88=EC=A7=B8=20n=EC=9A=94=EC=9D=BC=EC=9D=B4=20=ED=98=84?= =?UTF-8?q?=EC=9E=AC=20=EC=9B=94=20=EA=B8=B0=EC=A4=80=20=EB=B3=B4=EB=8B=A4?= =?UTF-8?q?=20=EC=9D=B4=EC=A0=84=EC=9D=B4=EB=A9=B4=20=EB=8B=A4=EC=9D=8C=20?= =?UTF-8?q?=EB=8B=AC=EB=A1=9C=20=EB=84=98=EA=B8=B0=EA=B3=A0,=20=EC=9D=B4?= =?UTF-8?q?=ED=9B=84=EB=A9=B4=20=ED=95=B4=EB=8B=B9=20=EB=8B=AC=EB=A1=9C=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../converter/RecurrenceGroupConverter.java | 6 ++- .../backend/domain/event/entity/Event.java | 5 +++ .../event/service/RecurrenceTimeAdjuster.java | 41 +++++++++++++------ .../command/EventCommandServiceImpl.java | 9 +++- 4 files changed, 46 insertions(+), 15 deletions(-) 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 e804706..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 @@ -350,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 919c80b..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); }