diff --git a/src/main/java/com/be/sportizebe/domain/admin/controller/AdminController.java b/src/main/java/com/be/sportizebe/domain/admin/controller/AdminController.java index 8f34b0f..2b56553 100644 --- a/src/main/java/com/be/sportizebe/domain/admin/controller/AdminController.java +++ b/src/main/java/com/be/sportizebe/domain/admin/controller/AdminController.java @@ -66,6 +66,15 @@ public ResponseEntity> createMatch( .body(BaseResponse.success("매칭 생성 성공", response)); } + @Operation(summary = "매칭 취소 (관리자 전용)", description = "매칭을 취소 상태로 변경합니다. 이미 시작/종료된 매칭은 취소할 수 없습니다.") + @PatchMapping("/matches/{matchId}/cancel") + public ResponseEntity> cancelMatch( + @PathVariable Long matchId + ) { + adminService.cancelMatch(matchId); + return ResponseEntity.ok(BaseResponse.success("매칭 취소 성공", null)); + } + @Operation(summary = "매칭 삭제 (관리자 전용)", description = "매칭을 강제 삭제합니다.") @DeleteMapping("/matches/{matchId}") public ResponseEntity> deleteMatch( diff --git a/src/main/java/com/be/sportizebe/domain/admin/service/AdminService.java b/src/main/java/com/be/sportizebe/domain/admin/service/AdminService.java index 39d048c..482284c 100644 --- a/src/main/java/com/be/sportizebe/domain/admin/service/AdminService.java +++ b/src/main/java/com/be/sportizebe/domain/admin/service/AdminService.java @@ -16,5 +16,7 @@ public interface AdminService { MatchResponse createMatch(Long adminId, MatchCreateRequest request); // 매칭 생성 + void cancelMatch(Long matchId); // 매칭 취소 + void deleteMatch(Long matchId); // 매칭 삭제 } diff --git a/src/main/java/com/be/sportizebe/domain/admin/service/AdminServiceImpl.java b/src/main/java/com/be/sportizebe/domain/admin/service/AdminServiceImpl.java index 3b5eaaf..280ec51 100644 --- a/src/main/java/com/be/sportizebe/domain/admin/service/AdminServiceImpl.java +++ b/src/main/java/com/be/sportizebe/domain/admin/service/AdminServiceImpl.java @@ -43,6 +43,12 @@ public MatchResponse createMatch(Long adminId, MatchCreateRequest request) { return matchService.createMatch(adminId, request); } + @Override + @Transactional + public void cancelMatch(Long matchId) { + matchService.cancelMatch(matchId); + } + @Override @Transactional public void deleteMatch(Long matchId) { diff --git a/src/main/java/com/be/sportizebe/domain/match/entity/MatchRoom.java b/src/main/java/com/be/sportizebe/domain/match/entity/MatchRoom.java index 142afd4..987017a 100644 --- a/src/main/java/com/be/sportizebe/domain/match/entity/MatchRoom.java +++ b/src/main/java/com/be/sportizebe/domain/match/entity/MatchRoom.java @@ -79,5 +79,9 @@ public void leave() { if (this.status == MatchStatus.FULL) this.status = MatchStatus.OPEN; } + public void cancel() { + this.status = MatchStatus.CANCELLED; + } + } \ No newline at end of file diff --git a/src/main/java/com/be/sportizebe/domain/match/entity/MatchStatus.java b/src/main/java/com/be/sportizebe/domain/match/entity/MatchStatus.java index 794cace..2a50bdf 100644 --- a/src/main/java/com/be/sportizebe/domain/match/entity/MatchStatus.java +++ b/src/main/java/com/be/sportizebe/domain/match/entity/MatchStatus.java @@ -1,7 +1,9 @@ package com.be.sportizebe.domain.match.entity; public enum MatchStatus { - OPEN, // 참여 가능 - FULL, // 정원 마감 - CLOSED // 운영상 종료(옵션) + OPEN, // 참여 가능 + FULL, // 정원 마감 + CLOSED, // 매칭 시작됨 (scheduledAt 이후, 참여 불가) -> "지금 진행중인 매칭" 탭 보여줄 때 + COMPLETED, // 매칭 종료 (scheduledAt + durationMinutes 이후) -> "매칭 완료 후 리뷰/평점. 포인트 지금 등 사이드 이펙트 필요 시COM + CANCELLED // Admin 취소 } diff --git a/src/main/java/com/be/sportizebe/domain/match/exception/MatchErrorCode.java b/src/main/java/com/be/sportizebe/domain/match/exception/MatchErrorCode.java index d02e750..74b13d1 100644 --- a/src/main/java/com/be/sportizebe/domain/match/exception/MatchErrorCode.java +++ b/src/main/java/com/be/sportizebe/domain/match/exception/MatchErrorCode.java @@ -29,6 +29,12 @@ public enum MatchErrorCode implements BaseErrorCode { HttpStatus.BAD_REQUEST, "MATCH_400_NOT_JOINED", "해당 매칭에 참가 중이지 않습니다." + ), + + MATCH_CANNOT_CANCEL( + HttpStatus.BAD_REQUEST, + "MATCH_400_CANNOT_CANCEL", + "이미 취소되었거나 시작/종료된 매칭은 취소할 수 없습니다." ); private final HttpStatus status; diff --git a/src/main/java/com/be/sportizebe/domain/match/service/MatchService.java b/src/main/java/com/be/sportizebe/domain/match/service/MatchService.java index c225293..388d3a0 100644 --- a/src/main/java/com/be/sportizebe/domain/match/service/MatchService.java +++ b/src/main/java/com/be/sportizebe/domain/match/service/MatchService.java @@ -21,6 +21,8 @@ public interface MatchService { List getNearMatches(MatchNearRequest request); // 내 주변 매칭 목록 조회 + void cancelMatch(Long matchId); // 매칭 취소 (관리자 전용) + void deleteMatch(Long matchId); // 매칭 삭제 (관리자 전용) } diff --git a/src/main/java/com/be/sportizebe/domain/match/service/MatchServiceImpl.java b/src/main/java/com/be/sportizebe/domain/match/service/MatchServiceImpl.java index 721c39e..81c13b3 100644 --- a/src/main/java/com/be/sportizebe/domain/match/service/MatchServiceImpl.java +++ b/src/main/java/com/be/sportizebe/domain/match/service/MatchServiceImpl.java @@ -7,6 +7,7 @@ import com.be.sportizebe.domain.match.entity.MatchParticipant; import com.be.sportizebe.domain.match.entity.MatchParticipantStatus; import com.be.sportizebe.domain.match.entity.MatchRoom; +import com.be.sportizebe.domain.match.entity.MatchStatus; import com.be.sportizebe.domain.match.exception.MatchErrorCode; import com.be.sportizebe.domain.match.repository.MatchParticipantRepository; import com.be.sportizebe.domain.match.repository.MatchRoomRepository; @@ -126,6 +127,20 @@ public MatchDetailResponse getMatchDetail(Long matchId, Long userId) { return MatchDetailResponse.of(matchRoom, user); } + @Override + public void cancelMatch(Long matchId) { + MatchRoom matchRoom = matchRoomRepository.findById(matchId) + .orElseThrow(() -> new CustomException(MatchErrorCode.MATCH_NOT_FOUND)); + + if (matchRoom.getStatus() == MatchStatus.CANCELLED + || matchRoom.getStatus() == MatchStatus.CLOSED + || matchRoom.getStatus() == MatchStatus.COMPLETED) { + throw new CustomException(MatchErrorCode.MATCH_CANNOT_CANCEL); + } + + matchRoom.cancel(); + } + @Override public void deleteMatch(Long matchId) { if (!matchRoomRepository.existsById(matchId)) {