Skip to content

Conversation

@ByunDohwi
Copy link
Member

@ByunDohwi ByunDohwi commented Jan 21, 2026

Summary by CodeRabbit

  • Bug Fixes

    • 신청서 저장 전 시간 유효성 검증 강화로 잘못된 시간 입력 차단
    • 조기귀가 신청 시 별도 시간 검증 추가
  • New Features

    • 신청 유형별 시작/종료 시간 매칭 및 번역 로직 개선으로 자동 매핑 정확도 향상
    • 4교시 시간대 조정(시작 시점 확장)

✏️ Tip: You can customize this high-level summary in your review settings.

@ByunDohwi ByunDohwi self-assigned this Jan 21, 2026
@ByunDohwi ByunDohwi added the bug Something isn't working label Jan 21, 2026
@coderabbitai
Copy link

coderabbitai bot commented Jan 21, 2026

Walkthrough

AttendanceService에 기간/시간 검증 및 변환 로직이 강화되고, ApplicationService와 CreateEarlyReturnService에서 해당 검증을 호출하도록 통합했습니다. translateApplication/getMatchPeriods 반환 타입이 List→Pair로 변경되었고, 새로운 예외 InvalidPeriodException 기반의 검증 진입점들이 추가되었습니다.

Changes

Cohort / File(s) 변경 요약
검증 통합 및 호출 추가
src/main/kotlin/dsm/pick2024/domain/application/service/ApplicationService.kt, src/main/kotlin/dsm/pick2024/domain/earlyreturn/service/CreateEarlyReturnService.kt
AttendanceService 의존성 추가(생성자), 저장 전 시간 검증 호출 추가(checkApplicationTime, checkEarlyReturnTime)
출결 서비스 리팩토링
src/main/kotlin/dsm/pick2024/domain/attendance/domain/service/AttendanceService.kt
translateApplication 반환 타입 List→Pair, getMatchPeriods 반환 타입 List→Pair 및 private화, translateEarlyReturn 공개화(문자열 반환), checkApplicationTime/checkEarlyReturnTime 추가, 4교시 시간 범위 조정(12:40→11:40 시작 확대), InvalidPeriodException 도입 및 예외 기반 검증
조합/호출 처리 조정
src/main/kotlin/dsm/pick2024/domain/earlyreturn/service/processor/EarlyReturnApprovalProcessor.kt
translateEarlyReturn의 Pair/문자열 변경에 맞춰 호출부 수정(기존 .first() 제거 등)

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant AppService as ApplicationService
    participant AttendService as AttendanceService
    participant Repo as Repository

    Client->>AppService: createApplication(request)
    AppService->>AttendService: checkApplicationTime(type, start, end)
    alt Valid
        AttendService-->>AppService: ok
        AppService->>AttendService: translateApplication(start, end, type)
        AttendService-->>AppService: Pair(startPeriod, endPeriod)
        AppService->>Repo: save(application)
        Repo-->>AppService: Application
        AppService-->>Client: Application
    else Invalid
        AttendService-->>AppService: InvalidPeriodException
        AppService-->>Client: Error
    end
Loading
sequenceDiagram
    participant Client
    participant EarlyRetService as CreateEarlyReturnService
    participant AttendService as AttendanceService
    participant Repo as Repository

    Client->>EarlyRetService: createEarlyReturn(request)
    EarlyRetService->>AttendService: checkEarlyReturnTime(type, start)
    alt Valid
        AttendService-->>EarlyRetService: ok
        EarlyRetService->>AttendService: translateEarlyReturn(start, type)
        AttendService-->>EarlyRetService: periodLabel
        EarlyRetService->>Repo: save(earlyReturn)
        Repo-->>EarlyRetService: EarlyReturn
        EarlyRetService-->>Client: EarlyReturn
    else Invalid
        AttendService-->>EarlyRetService: InvalidPeriodException
        EarlyRetService-->>Client: Error
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 시간표 밟고 깡충 뛰어,
쌍으로 맞춘 시작과 끝을 안아,
검증 한 번 더 하고 길을 열면,
신청들은 조용히 줄을 서고,
토끼는 기쁘게 코드를 튕긴다 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목이 변경 사항의 주요 내용을 정확하게 반영하고 있습니다. 시간-교시 변환 알고리즘 버그 수정이라는 핵심 변경 사항을 명확하게 설명합니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In
`@src/main/kotlin/dsm/pick2024/domain/attendance/domain/service/AttendanceService.kt`:
- Around line 66-81: The checkEarlyReturnTime function currently only validates
lower bounds and allows out-of-range values (e.g., "11교시" or times after the
last period) which leads updateAttendanceToEarlyReturn to compute startIndex=0
and mark wrong periods; update checkEarlyReturnTime (handling
ApplicationType.PERIOD and ApplicationType.TIME) to enforce both lower and upper
bounds by validating PERIOD against the actual number of school periods (reject
if parsed startPeriod < 1 or > maxPeriod) and validating TIME against the
scheduled period boundaries (parse start as LocalTime and ensure it falls within
the start times of an actual period or not after the last period start); throw
InvalidPeriodException when out of range; reference the period schedule/constant
used elsewhere (e.g., the period list or maxPeriod constant used by
updateAttendanceToEarlyReturn) to determine valid upper bounds.
- Around line 170-181: getMatchPeriods currently finds startIndex/endIndex via
indexOfFirst with endTime < period.endAt, so when endTime equals the last
period's end (e.g., 20:30) endIndex becomes -1 and throws
InvalidPeriodException; update getMatchPeriods to handle that boundary by
treating an endTime equal to the final period's end as matching the last period:
compute endIndex as before, but if endIndex == -1 and endTime ==
periods.last().second then set endIndex = periods.lastIndex (use the existing
periods and periodNames variables), then keep the existing
InvalidPeriodException throw only if startIndex == -1 || endIndex == -1.
- Around line 44-63: In checkApplicationTime, guard against parse failures by
wrapping LocalTime.parse(...) and the toInt() conversions in try/catch blocks
(or a single try surrounding each ApplicationType branch) and convert
DateTimeParseException and NumberFormatException into the unified
InvalidPeriodException; specifically update the ApplicationType.TIME branch to
catch/paraphrase exceptions from LocalTime.parse and the ApplicationType.PERIOD
branch to catch exceptions from start.replace(...).toInt() /
end.replace(...).toInt(), rethrowing InvalidPeriodException when parsing fails
or when the existing logical checks fail.

Comment on lines 44 to 63
fun checkApplicationTime(applicationType: ApplicationType, start: String, end: String) {
when (applicationType) {
ApplicationType.TIME -> {
val startTime = LocalTime.parse(start)
val endTime = LocalTime.parse(end)
if (startTime > endTime ||
endTime > LocalTime.of(20, 30) ||
startTime < LocalTime.of(8, 30)
) {
throw InvalidPeriodException
}
}
ApplicationType.PERIOD -> {
val startPeriod = start.replace("교시", "").toInt()
val endPeriod = end.replace("교시", "").toInt()
if (startPeriod > endPeriod || startPeriod < 1 || endPeriod > 10) {
throw InvalidPeriodException
}
}
}
Copy link

@coderabbitai coderabbitai bot Jan 21, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

잘못된 시간/교시 포맷이 그대로 런타임 예외로 노출될 수 있습니다.

LocalTime.parse(...)toInt()가 실패하면 InvalidPeriodException이 아닌 DateTimeParseException/NumberFormatException으로 터져 500이 될 수 있습니다. 입력 검증으로 예외를 통일하는 편이 안전합니다.

🛠️ 제안 수정
 fun checkApplicationTime(applicationType: ApplicationType, start: String, end: String) {
     when (applicationType) {
         ApplicationType.TIME -> {
-            val startTime = LocalTime.parse(start)
-            val endTime = LocalTime.parse(end)
+            val startTime = runCatching { LocalTime.parse(start) }
+                .getOrElse { throw InvalidPeriodException }
+            val endTime = runCatching { LocalTime.parse(end) }
+                .getOrElse { throw InvalidPeriodException }
             if (startTime > endTime ||
                 endTime > LocalTime.of(20, 30) ||
                 startTime < LocalTime.of(8, 30)
             ) {
                 throw InvalidPeriodException
             }
         }
         ApplicationType.PERIOD -> {
-            val startPeriod = start.replace("교시", "").toInt()
-            val endPeriod = end.replace("교시", "").toInt()
+            val startPeriod = start.replace("교시", "").toIntOrNull()
+                ?: throw InvalidPeriodException
+            val endPeriod = end.replace("교시", "").toIntOrNull()
+                ?: throw InvalidPeriodException
             if (startPeriod > endPeriod || startPeriod < 1 || endPeriod > 10) {
                 throw InvalidPeriodException
             }
         }
     }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fun checkApplicationTime(applicationType: ApplicationType, start: String, end: String) {
when (applicationType) {
ApplicationType.TIME -> {
val startTime = LocalTime.parse(start)
val endTime = LocalTime.parse(end)
if (startTime > endTime ||
endTime > LocalTime.of(20, 30) ||
startTime < LocalTime.of(8, 30)
) {
throw InvalidPeriodException
}
}
ApplicationType.PERIOD -> {
val startPeriod = start.replace("교시", "").toInt()
val endPeriod = end.replace("교시", "").toInt()
if (startPeriod > endPeriod || startPeriod < 1 || endPeriod > 10) {
throw InvalidPeriodException
}
}
}
fun checkApplicationTime(applicationType: ApplicationType, start: String, end: String) {
when (applicationType) {
ApplicationType.TIME -> {
val startTime = runCatching { LocalTime.parse(start) }
.getOrElse { throw InvalidPeriodException }
val endTime = runCatching { LocalTime.parse(end) }
.getOrElse { throw InvalidPeriodException }
if (startTime > endTime ||
endTime > LocalTime.of(20, 30) ||
startTime < LocalTime.of(8, 30)
) {
throw InvalidPeriodException
}
}
ApplicationType.PERIOD -> {
val startPeriod = start.replace("교시", "").toIntOrNull()
?: throw InvalidPeriodException
val endPeriod = end.replace("교시", "").toIntOrNull()
?: throw InvalidPeriodException
if (startPeriod > endPeriod || startPeriod < 1 || endPeriod > 10) {
throw InvalidPeriodException
}
}
}
}
🤖 Prompt for AI Agents
In
`@src/main/kotlin/dsm/pick2024/domain/attendance/domain/service/AttendanceService.kt`
around lines 44 - 63, In checkApplicationTime, guard against parse failures by
wrapping LocalTime.parse(...) and the toInt() conversions in try/catch blocks
(or a single try surrounding each ApplicationType branch) and convert
DateTimeParseException and NumberFormatException into the unified
InvalidPeriodException; specifically update the ApplicationType.TIME branch to
catch/paraphrase exceptions from LocalTime.parse and the ApplicationType.PERIOD
branch to catch exceptions from start.replace(...).toInt() /
end.replace(...).toInt(), rethrowing InvalidPeriodException when parsing fails
or when the existing logical checks fail.

Copy link
Member Author

Choose a reason for hiding this comment

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

abf2875
time 변환은 기존에도 저런 형식으로 진행하였기 때문에 괜찮습니다

Copy link

Choose a reason for hiding this comment

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

@ByunDohwi 알겠습니다! 기존 코드베이스와의 일관성을 유지하는 것도 중요하니 이해했습니다. 입력 검증은 현재 방식대로 진행하시면 되겠습니다. 👍

imhanseong
imhanseong previously approved these changes Jan 21, 2026
1210thatman
1210thatman previously approved these changes Jan 21, 2026
@ByunDohwi ByunDohwi dismissed stale reviews from 1210thatman and imhanseong via abf2875 January 21, 2026 06:34
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In
`@src/main/kotlin/dsm/pick2024/domain/attendance/domain/service/AttendanceService.kt`:
- Around line 83-95: translateEarlyReturn currently treats the period end as
exclusive (startTime < endAt) which mismatches getMatchPeriods; change the
matching to be inclusive of the end boundary so both functions behave the same.
In translateEarlyReturn (and the lambda over periods) use an inclusive check
(e.g., startTime in start..endAt or startTime >= start && startTime <= endAt)
when finding startIndex, and keep throwing InvalidPeriodException and returning
periodNames[startIndex] as before so periods, periodNames, translateEarlyReturn
and getMatchPeriods use the same boundary logic.
♻️ Duplicate comments (1)
src/main/kotlin/dsm/pick2024/domain/attendance/domain/service/AttendanceService.kt (1)

74-79: PERIOD 분기에서 .toInt() 대신 .toIntOrNull()을 사용하세요.

checkApplicationTime에서는 .toIntOrNull()을 사용하지만 여기서는 .toInt()를 사용하여 일관성이 없습니다. 잘못된 입력 시 NumberFormatException 대신 InvalidPeriodException이 발생하도록 통일하는 것이 좋습니다.

🛠️ 제안 수정
 ApplicationType.PERIOD -> {
-    val startPeriod = start.replace("교시", "").toInt()
+    val startPeriod = start.replace("교시", "").toIntOrNull()
+        ?: throw InvalidPeriodException
     if (startPeriod < 1 || startPeriod > 10) {
         throw InvalidPeriodException
     }
 }

Comment on lines +83 to +95
fun translateEarlyReturn(start: String, applicationType: ApplicationType): String {
return when (applicationType) {
ApplicationType.PERIOD -> start
ApplicationType.TIME -> {
val startTime = LocalTime.parse(start)
val startIndex = periods.indexOfFirst { (start, endAt) ->
startTime >= start && startTime < endAt
}
if (startIndex == -1) throw InvalidPeriodException
periodNames[startIndex]
}
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

경계 시간 처리 로직이 getMatchPeriods와 불일치합니다.

translateEarlyReturnstartTime < endAt (종료 시간 제외)을 사용하지만, getMatchPeriodsin start..endAt (종료 시간 포함)을 사용합니다.

예를 들어 9:40 (1교시 종료 시간)의 경우:

  • translateEarlyReturn: 2교시로 매칭
  • getMatchPeriods: 1교시로 매칭

두 함수의 경계 처리 로직을 통일하는 것이 좋습니다.

🛠️ 제안 수정 (inclusive로 통일)
 fun translateEarlyReturn(start: String, applicationType: ApplicationType): String {
     return when (applicationType) {
         ApplicationType.PERIOD -> start
         ApplicationType.TIME -> {
             val startTime = LocalTime.parse(start)
             val startIndex = periods.indexOfFirst { (start, endAt) ->
-                startTime >= start && startTime < endAt
+                startTime in start..endAt
             }
             if (startIndex == -1) throw InvalidPeriodException
             periodNames[startIndex]
         }
     }
 }
🤖 Prompt for AI Agents
In
`@src/main/kotlin/dsm/pick2024/domain/attendance/domain/service/AttendanceService.kt`
around lines 83 - 95, translateEarlyReturn currently treats the period end as
exclusive (startTime < endAt) which mismatches getMatchPeriods; change the
matching to be inclusive of the end boundary so both functions behave the same.
In translateEarlyReturn (and the lambda over periods) use an inclusive check
(e.g., startTime in start..endAt or startTime >= start && startTime <= endAt)
when finding startIndex, and keep throwing InvalidPeriodException and returning
periodNames[startIndex] as before so periods, periodNames, translateEarlyReturn
and getMatchPeriods use the same boundary logic.

@imhanseong imhanseong self-requested a review January 21, 2026 06:45
@ByunDohwi ByunDohwi merged commit 05b73f7 into develop Jan 21, 2026
2 checks passed
@ByunDohwi ByunDohwi deleted the 640-시간에서-교시-변환-시-동작하는-알고리즘-버그-수정 branch January 21, 2026 06:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

시간에서 교시 변환 시 동작하는 알고리즘 버그 수정

4 participants