Repository navigation
fix: 학교버스 시간표 저장과 교통편 조회 오류 수정 #2440
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
4b715c9
fix: 셔틀 시간표 동명 회차 및 일괄 수정 오류 해결
taejinn 3d02081
fix: 학교버스 시간표 교체 및 교통편 조회 개선
taejinn d24be2e
test: 시간표 PR 추가 테스트 및 샘플 데이터 제거
taejinn 94c8e9c
refactor: 학교버스 저장 분기와 중복 검증 정리
taejinn 27f458b
style: 위키 코드 컨벤션에 맞춰 버스 코드 포맷 정리
taejinn cf23033
refactor: 버스 엑셀 처리와 방향 판정 중복 제거
taejinn 2be4495
fix: 시간표 부분 수정 시 정류장 상세정보 보존
taejinn 3a51777
Merge branch 'develop' into fix/2439-shuttle-timetable-update
taejinn File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
54 changes: 54 additions & 0 deletions
54
src/main/java/in/koreatech/koin/admin/bus/TimetableValidator.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| package in.koreatech.koin.admin.bus; | ||
|
|
||
| import static in.koreatech.koin.global.code.ApiResponseCode.INVALID_REQUEST_BODY; | ||
| import static in.koreatech.koin.global.code.ApiResponseCode.REQUIRED_SHUTTLE_RUNNING_DAYS; | ||
|
|
||
| import java.util.List; | ||
| import java.util.Set; | ||
|
|
||
| import org.springframework.util.CollectionUtils; | ||
| import org.springframework.util.StringUtils; | ||
|
|
||
| import in.koreatech.koin.domain.bus.service.shuttle.model.ShuttleBusRoute; | ||
| import in.koreatech.koin.domain.bus.service.shuttle.model.ShuttleBusRoute.NodeInfo; | ||
| import in.koreatech.koin.domain.bus.service.shuttle.model.ShuttleBusRoute.RouteInfo; | ||
| import in.koreatech.koin.global.exception.CustomException; | ||
|
|
||
| public final class TimetableValidator { | ||
|
|
||
| private static final Set<String> VALID_RUNNING_DAYS = Set.of( | ||
| "MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN" | ||
| ); | ||
|
|
||
| private TimetableValidator() { | ||
| } | ||
|
|
||
| public static void validateTimetable( | ||
| String region, String routeType, String routeName, List<?> nodes, List<?> routes | ||
| ) { | ||
| if (!StringUtils.hasText(region) || !StringUtils.hasText(routeType) || !StringUtils.hasText(routeName) | ||
| || CollectionUtils.isEmpty(nodes) || CollectionUtils.isEmpty(routes)) { | ||
| throw CustomException.of(INVALID_REQUEST_BODY, "노선과 정류장, 회차 정보는 필수입니다."); | ||
| } | ||
| if (nodes.stream().anyMatch(node -> node == null)) { | ||
| throw CustomException.of(INVALID_REQUEST_BODY, "정류장 정보는 비어 있을 수 없습니다."); | ||
| } | ||
| if (routes.stream().anyMatch(route -> route == null)) { | ||
| throw CustomException.of(INVALID_REQUEST_BODY, "회차 정보는 비어 있을 수 없습니다."); | ||
| } | ||
| } | ||
|
|
||
| public static void validateRoutes(List<NodeInfo> nodes, List<RouteInfo> routes, boolean requireRunningDays) { | ||
| ShuttleBusRoute.validateRouteShape(nodes, routes); | ||
| for (RouteInfo route : routes) { | ||
| List<String> days = route.getRunningDays(); | ||
| if (CollectionUtils.isEmpty(days)) { | ||
| if (requireRunningDays) { | ||
| throw CustomException.of(REQUIRED_SHUTTLE_RUNNING_DAYS); | ||
| } | ||
| } else if (days.stream().anyMatch(day -> day == null || !VALID_RUNNING_DAYS.contains(day))) { | ||
| throw CustomException.of(INVALID_REQUEST_BODY, "운행 요일 코드가 올바르지 않습니다."); | ||
| } | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
101 changes: 69 additions & 32 deletions
101
src/main/java/in/koreatech/koin/admin/bus/commuting/service/AdminCommutingBusService.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,68 +1,105 @@ | ||
| package in.koreatech.koin.admin.bus.commuting.service; | ||
|
|
||
| import static in.koreatech.koin.admin.bus.commuting.dto.AdminCommutingBusUpdateRequest.InnerAdminCommutingBusUpdateRequest; | ||
| import static in.koreatech.koin.global.code.ApiResponseCode.INVALID_REQUEST_BODY; | ||
|
|
||
| import java.util.LinkedHashMap; | ||
| import java.util.List; | ||
| import java.util.Optional; | ||
| import java.util.Map; | ||
|
|
||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
| import org.springframework.util.CollectionUtils; | ||
|
|
||
| import in.koreatech.koin.admin.bus.TimetableValidator; | ||
| import in.koreatech.koin.admin.bus.commuting.dto.AdminCommutingBusUpdateRequest; | ||
| import in.koreatech.koin.admin.bus.commuting.enums.SemesterType; | ||
| import in.koreatech.koin.admin.bus.commuting.repository.AdminCommutingBusRepository; | ||
| import in.koreatech.koin.admin.bus.shuttle.enums.RunningDays; | ||
| import in.koreatech.koin.domain.bus.enums.ShuttleBusRegion; | ||
| import in.koreatech.koin.domain.bus.enums.ShuttleRouteType; | ||
| import in.koreatech.koin.domain.bus.service.shuttle.model.ShuttleBusRoute; | ||
| import in.koreatech.koin.domain.bus.service.shuttle.model.ShuttleBusRoute.NodeInfo; | ||
| import in.koreatech.koin.domain.bus.service.shuttle.model.ShuttleBusRoute.RouteInfo; | ||
| import in.koreatech.koin.global.exception.CustomException; | ||
| import lombok.RequiredArgsConstructor; | ||
|
|
||
| @Service | ||
| @RequiredArgsConstructor | ||
| public class AdminCommutingBusService { | ||
|
|
||
| private static final List<String> WEEKDAYS = List.of("MON", "TUE", "WED", "THU", "FRI"); | ||
|
|
||
| private final AdminCommutingBusRepository adminCommutingBusRepository; | ||
|
|
||
| @Transactional | ||
| public void updateCommutingBusTimetable( | ||
| SemesterType semesterType, | ||
| AdminCommutingBusUpdateRequest request | ||
| ) { | ||
| for (InnerAdminCommutingBusUpdateRequest commutingBusUpdateRequest : request.commutingBusTimetables()) { | ||
| ShuttleBusRegion region = ShuttleBusRegion.convertFrom(commutingBusUpdateRequest.region()); | ||
| ShuttleRouteType routeType = ShuttleRouteType.convertFrom(commutingBusUpdateRequest.routeType()); | ||
| routeType.validateCommuting(); | ||
|
|
||
| // 등하교 버스는 validateCommuting() 으로 주중 노선만 허용되므로 운행 요일이 주중으로 결정된다. | ||
| List<String> runningDays = RunningDays.WEEKDAYS.getDays(); | ||
| validateRequestBatch(request); | ||
| Map<TimetableKey, ShuttleBusRoute> preparedTimetables = new LinkedHashMap<>(); | ||
|
|
||
| Optional<ShuttleBusRoute> shuttleBusRoute = adminCommutingBusRepository | ||
| .findBySemesterTypeAndRegionAndRouteTypeAndRouteNameAndSubName( | ||
| semesterType.getDescription(), | ||
| region, | ||
| routeType, | ||
| commutingBusUpdateRequest.routeName(), | ||
| commutingBusUpdateRequest.subName() | ||
| ); | ||
|
|
||
| if (shuttleBusRoute.isPresent()) { | ||
| ShuttleBusRoute route = shuttleBusRoute.get(); | ||
| route.updateCommutingBusRoute( | ||
| commutingBusUpdateRequest.toNodeInfoEntity(), | ||
| commutingBusUpdateRequest.toRouteInfoEntity(runningDays) | ||
| ); | ||
| adminCommutingBusRepository.save(route); | ||
| } else { | ||
| adminCommutingBusRepository.save(ShuttleBusRoute.builder() | ||
| for (InnerAdminCommutingBusUpdateRequest timetableRequest : request.commutingBusTimetables()) { | ||
| ShuttleBusRegion region = ShuttleBusRegion.convertFrom(timetableRequest.region()); | ||
| ShuttleRouteType routeType = ShuttleRouteType.convertFrom(timetableRequest.routeType()); | ||
| routeType.validateCommuting(); | ||
| TimetableKey key = new TimetableKey( | ||
| region, | ||
| routeType, | ||
| timetableRequest.routeName(), | ||
| timetableRequest.subName() | ||
| ); | ||
| List<NodeInfo> nodeInfos = timetableRequest.toNodeInfoEntity(); | ||
| ShuttleBusRoute prepared = preparedTimetables.get(key); | ||
| if (prepared == null) { | ||
| prepared = adminCommutingBusRepository.findBySemesterTypeAndRegionAndRouteTypeAndRouteNameAndSubName( | ||
| semesterType.getDescription(), region, routeType, key.routeName(), key.subName()) | ||
| .map(ShuttleBusRoute::copy).orElse(null); | ||
| } | ||
| List<RouteInfo> routeInfos = timetableRequest.toRouteInfoEntity(prepared == null ? WEEKDAYS : null); | ||
| TimetableValidator.validateRoutes(nodeInfos, routeInfos, false); | ||
| if (prepared == null) { | ||
| prepared = ShuttleBusRoute.builder() | ||
| .semesterType(semesterType.getDescription()) | ||
| .region(region) | ||
| .routeType(routeType) | ||
| .routeName(commutingBusUpdateRequest.routeName()) | ||
| .subName(commutingBusUpdateRequest.subName()) | ||
| .nodeInfo(commutingBusUpdateRequest.toNodeInfoEntity()) | ||
| .routeInfo(commutingBusUpdateRequest.toRouteInfoEntity(runningDays)) | ||
| .build()); | ||
| .routeName(key.routeName()) | ||
| .subName(key.subName()) | ||
| .nodeInfo(nodeInfos) | ||
| .routeInfo(routeInfos) | ||
| .build(); | ||
| } else { | ||
| prepared.updateCommutingBusRoute(nodeInfos, routeInfos); | ||
| } | ||
| preparedTimetables.put(key, prepared); | ||
| } | ||
|
|
||
| preparedTimetables.values().forEach(adminCommutingBusRepository::save); | ||
| } | ||
|
|
||
| private void validateRequestBatch(AdminCommutingBusUpdateRequest request) { | ||
| if (request == null || CollectionUtils.isEmpty(request.commutingBusTimetables())) { | ||
| throw invalidRequest("버스 노선 정보 목록은 비어 있을 수 없습니다."); | ||
| } | ||
| for (InnerAdminCommutingBusUpdateRequest timetableRequest : request.commutingBusTimetables()) { | ||
| if (timetableRequest == null) { | ||
| throw invalidRequest("노선과 정류장, 회차 정보는 필수입니다."); | ||
| } | ||
| TimetableValidator.validateTimetable( | ||
| timetableRequest.region(), timetableRequest.routeType(), timetableRequest.routeName(), | ||
| timetableRequest.nodeInfo(), timetableRequest.routeInfo()); | ||
| } | ||
| } | ||
|
|
||
| private static CustomException invalidRequest(String detail) { | ||
| return CustomException.of(INVALID_REQUEST_BODY, detail); | ||
| } | ||
|
|
||
| private record TimetableKey( | ||
| ShuttleBusRegion region, | ||
| ShuttleRouteType routeType, | ||
| String routeName, | ||
| String subName | ||
| ) { | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
6 changes: 6 additions & 0 deletions
6
src/main/java/in/koreatech/koin/admin/bus/shuttle/enums/UpdateMode.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| package in.koreatech.koin.admin.bus.shuttle.enums; | ||
|
|
||
| public enum UpdateMode { | ||
| PARTIAL, | ||
| REPLACE | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
Repository: BCSDLab/KOIN_API_V2
Length of output: 7622
🏁 Script executed:
Repository: BCSDLab/KOIN_API_V2
Length of output: 41887
🏁 Script executed:
Repository: BCSDLab/KOIN_API_V2
Length of output: 32846
🏁 Script executed:
Repository: BCSDLab/KOIN_API_V2
Length of output: 9649
Classify conflicting running days as an Excel validation error.
월요일 목금matchesTHURSDAY_FRIDAY_PATTERN.findExplicitDays()also findsMONDAY, soRunningDays.from()throwsINVALID_REQUEST_BODY.RunningDays.from()is called by the Excel route extractor, and the preview service rethrows the exception. The preview API therefore exposes the request-body code, whileINVALID_REQUEST_BODYis documented for the separate timetable update endpoint. Map this parser error toINVALID_EXCEL_FILE_FORMATat the preview boundary. Both responses are HTTP 400, but clients that distinguish error codes receive the wrong category.🤖 Prompt for AI Agents