diff --git a/server/src/main/java/server/haengdong/application/ActionService.java b/server/src/main/java/server/haengdong/application/ActionService.java index 7fae107ba..bda638756 100644 --- a/server/src/main/java/server/haengdong/application/ActionService.java +++ b/server/src/main/java/server/haengdong/application/ActionService.java @@ -1,12 +1,9 @@ package server.haengdong.application; import java.util.List; -import java.util.Optional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; 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; @@ -24,7 +21,6 @@ public class ActionService { private final BillActionRepository billActionRepository; private final MemberActionRepository memberActionRepository; private final EventRepository eventRepository; - private final ActionRepository actionRepository; public List getMemberBillReports(String token) { Event event = eventRepository.findByToken(token) @@ -38,20 +34,4 @@ public List getMemberBillReports(String token) { .map(entry -> new MemberBillReportAppResponse(entry.getKey(), entry.getValue())) .toList(); } - - public void deleteAction(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)); - Optional billAction = billActionRepository.findByAction(action); - Optional memberAction = memberActionRepository.findByAction(action); - - if (billAction.isPresent()) { - billActionRepository.delete(billAction.get()); - return; - } - - memberAction.ifPresent(memberActionRepository::delete); - } } diff --git a/server/src/main/java/server/haengdong/application/MemberActionService.java b/server/src/main/java/server/haengdong/application/MemberActionService.java index f893bd08d..c53c5174a 100644 --- a/server/src/main/java/server/haengdong/application/MemberActionService.java +++ b/server/src/main/java/server/haengdong/application/MemberActionService.java @@ -58,4 +58,16 @@ 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.deleteAllByMemberAction(memberAction.getMemberName(), memberAction.getSequence()); + } } 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 134eddf0f..9e69e5e67 100644 --- a/server/src/main/java/server/haengdong/domain/action/MemberActionRepository.java +++ b/server/src/main/java/server/haengdong/domain/action/MemberActionRepository.java @@ -3,6 +3,7 @@ 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; @@ -15,4 +16,12 @@ 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 deleteAllByMemberAction(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 289009170..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", "존재하지 않는 액션입니다."); + 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/ActionController.java b/server/src/main/java/server/haengdong/presentation/ActionController.java index 533560280..657cb567e 100644 --- a/server/src/main/java/server/haengdong/presentation/ActionController.java +++ b/server/src/main/java/server/haengdong/presentation/ActionController.java @@ -3,7 +3,6 @@ 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.RestController; @@ -24,14 +23,4 @@ public ResponseEntity getMemberBillReports(@PathVaria return ResponseEntity.ok() .body(MemberBillReportsResponse.of(memberBillReports)); } - - @DeleteMapping("/api/events/{eventId}/actions/{actionId}") - public ResponseEntity deleteAction( - @PathVariable("eventId") String token, - @PathVariable("actionId") Long actionId - ) { - actionService.deleteAction(token, actionId); - - return ResponseEntity.noContent().build(); - } } 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 0b2e21f60..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,18 @@ 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.*; +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; +import server.haengdong.domain.action.MemberActionRepository; import server.haengdong.domain.event.Event; import server.haengdong.domain.event.EventRepository; import server.haengdong.exception.HaengdongErrorCode; @@ -27,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() { @@ -72,20 +89,4 @@ void getMemberBillReports1() { .isInstanceOf(HaengdongException.class) .hasMessage(HaengdongErrorCode.NOT_FOUND_EVENT.getMessage()); } - - @DisplayName("액션을 삭제합니다.") - @Test - void deleteAction() { - Event event = new Event("potato", "potatoHi"); - eventRepository.save(event); - - Action action = new Action(event, 1L); - MemberAction memberAction = new MemberAction(action, "웨디", MemberActionStatus.IN, 1L); - MemberAction savedMemberAction = memberActionRepository.save(memberAction); - Long memberActionId = savedMemberAction.getId(); - - actionService.deleteAction("potatoHi", memberActionId); - - assertThat(memberActionRepository.findById(memberActionId)).isEmpty(); - } } 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..fc00e2262 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,49 @@ void getCurrentMembers() { assertThatThrownBy(() -> memberActionService.getCurrentMembers("token")) .isInstanceOf(HaengdongException.class); } + + @DisplayName("이벤트에 속한 멤버 액션을 삭제하면 이후에 기록된 해당 참여자의 모든 멤버 액션을 삭제한다.") + @Test + void deleteMemberAction() { + String token = "TOKEN"; + Event event = new Event("행동대장 회식", token); + eventRepository.save(event); + Action action = Action.createFirst(event); + MemberAction memberAction1 = createMemberAction(action, "토다리", IN, 1L); + Action targetAction = action.next(); + MemberAction memberAction2 = createMemberAction(targetAction, "토다리", OUT, 2L); + MemberAction memberAction3 = createMemberAction(action.next(), "쿠키", IN, 3L); + MemberAction memberAction4 = createMemberAction(action.next(), "웨디", IN, 4L); + MemberAction memberAction5 = createMemberAction(action.next(), "토다리", IN, 5L); + MemberAction memberAction6 = createMemberAction(action.next(), "토다리", OUT, 6L); + MemberAction memberAction7 = createMemberAction(action.next(), "쿠키", 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 static 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/ActionControllerTest.java b/server/src/test/java/server/haengdong/presentation/ActionControllerTest.java index 0ae3cc2e2..739ac4962 100644 --- a/server/src/test/java/server/haengdong/presentation/ActionControllerTest.java +++ b/server/src/test/java/server/haengdong/presentation/ActionControllerTest.java @@ -3,7 +3,6 @@ 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.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -47,13 +46,4 @@ void getMemberBillReports() throws Exception { .andExpect(MockMvcResultMatchers.jsonPath("$.reports[1].price").value(equalTo(200_000))); } - - @DisplayName("액션을 삭제한다.") - @Test - void deleteAction() throws Exception { - mockMvc.perform(delete("/api/events/{eventId}/actions/{actionId}", "token", "1") - .accept(MediaType.APPLICATION_JSON)) - .andDo(print()) - .andExpect(status().isNoContent()); - } } diff --git a/server/src/test/java/server/haengdong/presentation/MemberActionControllerTest.java b/server/src/test/java/server/haengdong/presentation/MemberActionControllerTest.java index ad8787cc6..acab2f6cc 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 memberActionId = 2L; + + mockMvc.perform(delete(String.format("/api/events/%s/actions/%d/members", token, memberActionId))) + .andDo(print()) + .andExpect(status().isOk()); } }