diff --git a/server/src/main/java/server/haengdong/application/MemberActionService.java b/server/src/main/java/server/haengdong/application/MemberActionService.java index f893bd08d..64858d8d4 100644 --- a/server/src/main/java/server/haengdong/application/MemberActionService.java +++ b/server/src/main/java/server/haengdong/application/MemberActionService.java @@ -58,4 +58,17 @@ private Event findEvent(String token) { return eventRepository.findByToken(token) .orElseThrow(() -> new HaengdongException(HaengdongErrorCode.NOT_FOUND_EVENT)); } + + @Transactional + public void deleteMemberAction(String token, Long actionId) { + Event event = eventRepository.findByToken(token) + .orElseThrow(() -> new HaengdongException(HaengdongErrorCode.NOT_FOUND_EVENT)); + Action action = actionRepository.findByIdAndEvent(actionId, event) + .orElseThrow(() -> new HaengdongException(HaengdongErrorCode.NOT_FOUND_ACTION)); + MemberAction memberAction = memberActionRepository.findByAction(action) + .orElseThrow(() -> new HaengdongException(HaengdongErrorCode.NOT_FOUND_MEMBER_ACTION)); + + memberActionRepository.deleteAllByMemberNameAndMinSequence(memberAction.getMemberName(), + memberAction.getSequence()); + } } diff --git a/server/src/main/java/server/haengdong/domain/action/ActionRepository.java b/server/src/main/java/server/haengdong/domain/action/ActionRepository.java index c2138e42f..2fde0ffa6 100644 --- a/server/src/main/java/server/haengdong/domain/action/ActionRepository.java +++ b/server/src/main/java/server/haengdong/domain/action/ActionRepository.java @@ -18,4 +18,6 @@ public interface ActionRepository extends JpaRepository { LIMIT 1 """) Optional findLastByEvent(@Param("event") Event event); + + Optional findByIdAndEvent(Long id, Event event); } diff --git a/server/src/main/java/server/haengdong/domain/action/BillActionRepository.java b/server/src/main/java/server/haengdong/domain/action/BillActionRepository.java index 1ad7416aa..468d03061 100644 --- a/server/src/main/java/server/haengdong/domain/action/BillActionRepository.java +++ b/server/src/main/java/server/haengdong/domain/action/BillActionRepository.java @@ -1,6 +1,8 @@ package server.haengdong.domain.action; import java.util.List; +import java.util.Optional; + import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @@ -11,4 +13,6 @@ public interface BillActionRepository extends JpaRepository { @EntityGraph(attributePaths = {"action"}) List findByAction_Event(Event event); + + Optional findByAction(Action action); } diff --git a/server/src/main/java/server/haengdong/domain/action/MemberActionRepository.java b/server/src/main/java/server/haengdong/domain/action/MemberActionRepository.java index 6c4769e61..51248f305 100644 --- a/server/src/main/java/server/haengdong/domain/action/MemberActionRepository.java +++ b/server/src/main/java/server/haengdong/domain/action/MemberActionRepository.java @@ -1,7 +1,9 @@ package server.haengdong.domain.action; import java.util.List; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; @@ -12,4 +14,14 @@ public interface MemberActionRepository extends JpaRepository findAllByEvent(@Param("event") Event event); + + Optional findByAction(Action action); + + @Modifying + @Query(""" + delete + from MemberAction m + where m.memberName = :memberName and m.action.sequence >= :sequence + """) + void deleteAllByMemberNameAndMinSequence(String memberName, Long sequence); } diff --git a/server/src/main/java/server/haengdong/exception/HaengdongErrorCode.java b/server/src/main/java/server/haengdong/exception/HaengdongErrorCode.java index 052b97fe2..55e48661a 100644 --- a/server/src/main/java/server/haengdong/exception/HaengdongErrorCode.java +++ b/server/src/main/java/server/haengdong/exception/HaengdongErrorCode.java @@ -10,9 +10,10 @@ public enum HaengdongErrorCode { DUPLICATED_MEMBER_ACTION("MA_001", "중복된 인원이 존재합니다."), INVALID_MEMBER_IN_ACTION("MA_002", "현재 참여하고 있는 인원이 존재합니다."), INVALID_MEMBER_OUT_ACTION("MA_003", "현재 참여하고 있지 않는 인원이 존재합니다."), + NOT_FOUND_MEMBER_ACTION("MA_400", "존재하지 않는 멤버 액션입니다."), NOT_FOUND_EVENT("EV_400", "존재하지 않는 행사입니다."), - INTERNAL_SERVER_ERROR("S_001", "서버 내부에서 에러가 발생했습니다."), - ; + NOT_FOUND_ACTION("AC_400", "존재하지 않는 액션입니다."), + INTERNAL_SERVER_ERROR("S_001", "서버 내부에서 에러가 발생했습니다."); private final String code; private final String message; diff --git a/server/src/main/java/server/haengdong/presentation/MemberActionController.java b/server/src/main/java/server/haengdong/presentation/MemberActionController.java index c12370d86..e2c61b33f 100644 --- a/server/src/main/java/server/haengdong/presentation/MemberActionController.java +++ b/server/src/main/java/server/haengdong/presentation/MemberActionController.java @@ -3,6 +3,7 @@ import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -36,4 +37,14 @@ public ResponseEntity getCurrentMembers(@PathVariable("e return ResponseEntity.ok() .body(CurrentMembersResponse.of(currentMembers)); } + + @DeleteMapping("/api/events/{eventId}/actions/{actionId}/members") + public ResponseEntity deleteMemberAction( + @PathVariable("eventId") String token, + @PathVariable("actionId") Long actionId + ) { + memberActionService.deleteMemberAction(token, actionId); + + return ResponseEntity.ok().build(); + } } diff --git a/server/src/test/java/server/haengdong/application/ActionServiceTest.java b/server/src/test/java/server/haengdong/application/ActionServiceTest.java index ac2dc4f4f..a05141b9b 100644 --- a/server/src/test/java/server/haengdong/application/ActionServiceTest.java +++ b/server/src/test/java/server/haengdong/application/ActionServiceTest.java @@ -7,12 +7,14 @@ import static server.haengdong.domain.action.MemberActionStatus.OUT; import java.util.List; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import server.haengdong.application.response.MemberBillReportAppResponse; import server.haengdong.domain.action.Action; +import server.haengdong.domain.action.ActionRepository; import server.haengdong.domain.action.BillAction; import server.haengdong.domain.action.BillActionRepository; import server.haengdong.domain.action.MemberAction; @@ -31,12 +33,23 @@ class ActionServiceTest { @Autowired private EventRepository eventRepository; + @Autowired + private ActionRepository actionRepository; + @Autowired private BillActionRepository billActionRepository; @Autowired private MemberActionRepository memberActionRepository; + @AfterEach + void tearDown() { + billActionRepository.deleteAllInBatch(); + memberActionRepository.deleteAllInBatch(); + actionRepository.deleteAllInBatch(); + eventRepository.deleteAllInBatch(); + } + @DisplayName("참여자별 정산 현황을 조회한다.") @Test void getMemberBillReports() { diff --git a/server/src/test/java/server/haengdong/application/BillActionServiceTest.java b/server/src/test/java/server/haengdong/application/BillActionServiceTest.java index 195d4bdd9..e519eb5ac 100644 --- a/server/src/test/java/server/haengdong/application/BillActionServiceTest.java +++ b/server/src/test/java/server/haengdong/application/BillActionServiceTest.java @@ -5,14 +5,16 @@ import static org.assertj.core.api.Assertions.tuple; import java.util.List; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import server.haengdong.application.request.BillActionAppRequest; +import server.haengdong.domain.action.ActionRepository; import server.haengdong.domain.action.BillAction; -import server.haengdong.domain.event.Event; import server.haengdong.domain.action.BillActionRepository; +import server.haengdong.domain.event.Event; import server.haengdong.domain.event.EventRepository; import server.haengdong.exception.HaengdongException; @@ -22,12 +24,22 @@ class BillActionServiceTest { @Autowired private BillActionService billActionService; + @Autowired + private ActionRepository actionRepository; + @Autowired private EventRepository eventRepository; @Autowired private BillActionRepository billActionRepository; + @AfterEach + void tearDown() { + billActionRepository.deleteAllInBatch(); + actionRepository.deleteAllInBatch(); + eventRepository.deleteAllInBatch(); + } + @DisplayName("지출 내역을 생성한다.") @Test void saveAllBillAction() { diff --git a/server/src/test/java/server/haengdong/application/MemberActionServiceTest.java b/server/src/test/java/server/haengdong/application/MemberActionServiceTest.java index 7212e300d..9639f9570 100644 --- a/server/src/test/java/server/haengdong/application/MemberActionServiceTest.java +++ b/server/src/test/java/server/haengdong/application/MemberActionServiceTest.java @@ -1,7 +1,9 @@ package server.haengdong.application; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.tuple; import static server.haengdong.domain.action.MemberActionStatus.IN; import static server.haengdong.domain.action.MemberActionStatus.OUT; @@ -17,6 +19,7 @@ import server.haengdong.domain.action.ActionRepository; import server.haengdong.domain.action.MemberAction; import server.haengdong.domain.action.MemberActionRepository; +import server.haengdong.domain.action.MemberActionStatus; import server.haengdong.domain.event.Event; import server.haengdong.domain.event.EventRepository; import server.haengdong.exception.HaengdongException; @@ -48,7 +51,7 @@ void tearDown() { void saveMemberActionTest() { Event event = eventRepository.save(new Event("test", "TOKEN")); Action action = new Action(event, 1L); - MemberAction memberAction = new MemberAction(action, "망쵸", IN, 1L); + MemberAction memberAction = createMemberAction(action, "망쵸", IN, 1L); memberActionRepository.save(memberAction); assertThatCode(() -> memberActionService.saveMemberAction("TOKEN", new MemberActionsSaveAppRequest( @@ -61,11 +64,11 @@ void saveMemberActionTest() { void saveMemberActionTest1() { Event event = eventRepository.save(new Event("test", "TOKEN")); Action actionOne = new Action(event, 1L); - MemberAction memberActionOne = new MemberAction(actionOne, "망쵸", IN, 1L); + MemberAction memberActionOne = createMemberAction(actionOne, "망쵸", IN, 1L); memberActionRepository.save(memberActionOne); Action actionTwo = new Action(event, 2L); - MemberAction memberActionTwo = new MemberAction(actionTwo, "망쵸", OUT, 1L); + MemberAction memberActionTwo = createMemberAction(actionTwo, "망쵸", OUT, 1L); memberActionRepository.save(memberActionTwo); assertThatCode(() -> memberActionService.saveMemberAction("TOKEN", new MemberActionsSaveAppRequest( @@ -89,4 +92,50 @@ void getCurrentMembers() { assertThatThrownBy(() -> memberActionService.getCurrentMembers("token")) .isInstanceOf(HaengdongException.class); } + + @DisplayName("이벤트에 속한 멤버 액션을 삭제하면 이후에 기록된 해당 참여자의 모든 멤버 액션을 삭제한다.") + @Test + void deleteMemberAction() { + String token = "TOKEN"; + Event event = new Event("행동대장 회식", token); + eventRepository.save(event); + MemberAction memberAction1 = createMemberAction(new Action(event, 1L), "토다리", IN, 1L); + Action targetAction = new Action(event, 2L); + MemberAction memberAction2 = createMemberAction(targetAction, "토다리", OUT, 2L); + MemberAction memberAction3 = createMemberAction(new Action(event, 3L), "쿠키", IN, 3L); + MemberAction memberAction4 = createMemberAction(new Action(event, 4L), "웨디", IN, 4L); + MemberAction memberAction5 = createMemberAction(new Action(event, 5L), "토다리", IN, 5L); + MemberAction memberAction6 = createMemberAction(new Action(event, 6L), "토다리", OUT, 6L); + MemberAction memberAction7 = createMemberAction(new Action(event, 7L), "쿠키", OUT, 7L); + memberActionRepository.saveAll( + List.of(memberAction1, + memberAction2, + memberAction3, + memberAction4, + memberAction5, + memberAction6, + memberAction7) + ); + + memberActionService.deleteMemberAction(token, targetAction.getId()); + List memberActions = memberActionRepository.findAll(); + + assertThat(memberActions).hasSize(4) + .extracting("id", "memberName", "status") + .containsExactly( + tuple(memberAction1.getId(), "토다리", IN), + tuple(memberAction3.getId(), "쿠키", IN), + tuple(memberAction4.getId(), "웨디", IN), + tuple(memberAction7.getId(), "쿠키", OUT) + ); + } + + private MemberAction createMemberAction( + Action action, + String memberName, + MemberActionStatus memberActionStatus, + long memberGroupId + ) { + return new MemberAction(action, memberName, memberActionStatus, memberGroupId); + } } diff --git a/server/src/test/java/server/haengdong/presentation/MemberActionControllerTest.java b/server/src/test/java/server/haengdong/presentation/MemberActionControllerTest.java index ad8787cc6..22840f091 100644 --- a/server/src/test/java/server/haengdong/presentation/MemberActionControllerTest.java +++ b/server/src/test/java/server/haengdong/presentation/MemberActionControllerTest.java @@ -3,9 +3,11 @@ import static org.hamcrest.Matchers.equalTo; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.fasterxml.jackson.databind.ObjectMapper; @@ -17,7 +19,6 @@ import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.result.MockMvcResultMatchers; import server.haengdong.application.MemberActionService; import server.haengdong.application.response.CurrentMemberAppResponse; import server.haengdong.presentation.request.MemberActionsSaveRequest; @@ -61,7 +62,18 @@ void getCurrentMembers() throws Exception { .accept(MediaType.APPLICATION_JSON)) .andDo(print()) .andExpect(status().isOk()) - .andExpect(MockMvcResultMatchers.jsonPath("$.members[0].name").value(equalTo("소하"))) - .andExpect(MockMvcResultMatchers.jsonPath("$.members[1].name").value(equalTo("토다리"))); + .andExpect(jsonPath("$.members[0].name").value(equalTo("소하"))) + .andExpect(jsonPath("$.members[1].name").value(equalTo("토다리"))); + } + + @DisplayName("이벤트에 속한 멤버 액션을 삭제하면 이후에 기록된 해당 참여자의 모든 멤버 액션을 삭제한다.") + @Test + void deleteMemberAction() throws Exception { + String token = "TOKEN"; + Long actionId = 2L; + + mockMvc.perform(delete("/api/events/{token}/actions/{actionId}/members", token, actionId)) + .andDo(print()) + .andExpect(status().isOk()); } }