From 3e1b7461d1ec2d41f4b5461b062afc69e7a12424 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9B=90=EC=A7=80=EC=9C=A4?= Date: Sun, 31 May 2026 18:05:11 +0900 Subject: [PATCH 1/2] =?UTF-8?q?fix:=20=EB=A9=A4=EB=B2=84=EB=B3=84=20?= =?UTF-8?q?=EC=A0=95=EC=82=B0=EB=82=B4=EC=97=AD=20=EC=9E=85=EA=B8=88=20?= =?UTF-8?q?=ED=99=95=EC=9D=B8=20=EC=9A=94=EC=B2=AD=20ID=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../query/QueryMemberExpenseService.java | 24 ++++++++--- .../presentation/MemberExpenseController.java | 9 ++-- .../response/MemberExpenseItemResponse.java | 4 +- .../MemberExpenseControllerTest.java | 16 ++++--- .../QueryMemberExpenseServiceTest.java | 43 ++++++++++++++++++- 5 files changed, 81 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/dnd/moddo/event/application/query/QueryMemberExpenseService.java b/src/main/java/com/dnd/moddo/event/application/query/QueryMemberExpenseService.java index b18f113d..f083b678 100644 --- a/src/main/java/com/dnd/moddo/event/application/query/QueryMemberExpenseService.java +++ b/src/main/java/com/dnd/moddo/event/application/query/QueryMemberExpenseService.java @@ -12,9 +12,12 @@ import com.dnd.moddo.event.application.impl.ExpenseReader; import com.dnd.moddo.event.application.impl.MemberExpenseReader; import com.dnd.moddo.event.application.impl.MemberReader; +import com.dnd.moddo.event.application.impl.PaymentRequestReader; +import com.dnd.moddo.event.application.impl.SettlementReader; import com.dnd.moddo.event.domain.expense.Expense; import com.dnd.moddo.event.domain.member.Member; import com.dnd.moddo.event.domain.memberExpense.MemberExpense; +import com.dnd.moddo.event.domain.settlement.Settlement; import com.dnd.moddo.event.presentation.response.MemberExpenseDetailResponse; import com.dnd.moddo.event.presentation.response.MemberExpenseItemResponse; import com.dnd.moddo.event.presentation.response.MemberExpenseResponse; @@ -28,6 +31,8 @@ public class QueryMemberExpenseService { private final MemberExpenseReader memberExpenseReader; private final MemberReader memberReader; private final ExpenseReader expenseReader; + private final SettlementReader settlementReader; + private final PaymentRequestReader paymentRequestReader; public List findAllByExpenseId( Long expenseId) { @@ -37,7 +42,12 @@ public List findAllByExpenseId( .toList(); } - public MembersExpenseResponse findMemberExpenseDetailsBySettlementId(Long settlementId) { + public MembersExpenseResponse findMemberExpenseDetailsBySettlementId(Long settlementId, Long userId) { + Settlement settlement = settlementReader.read(settlementId); + Map paymentRequestIdByMemberId = settlement.isWriter(userId) + ? paymentRequestReader.findPendingRequestIdByMemberId(settlementId) + : Map.of(); + List members = memberReader.findAllBySettlementId(settlementId); Map appointmentMemberById = convertAppointmentMembersToMap(members); @@ -55,7 +65,8 @@ public MembersExpenseResponse findMemberExpenseDetailsBySettlementId(Long settle .map( key -> findMemberExpenseDetailByAppointmentMember(appointmentMemberById.get(key), memberExpenses.get(key), - expenses) + expenses, + paymentRequestIdByMemberId) ) .filter(Objects::nonNull) .toList(); @@ -72,16 +83,19 @@ private Map convertAppointmentMembersToMap(List members) { } private MemberExpenseItemResponse findMemberExpenseDetailByAppointmentMember( - Member member, List memberExpenses, List expenses) { + Member member, List memberExpenses, List expenses, + Map paymentRequestIdByMemberId) { + + Long paymentRequestId = paymentRequestIdByMemberId.get(member.getId()); if (memberExpenses == null) { - return MemberExpenseItemResponse.of(member, 0L, new ArrayList<>()); + return MemberExpenseItemResponse.of(member, 0L, new ArrayList<>(), paymentRequestId); } List memberExpenseDetails = mapToMemberExpenseDetails(memberExpenses, expenses); Long totalAmount = calculateTotalAmount(memberExpenses); - return MemberExpenseItemResponse.of(member, totalAmount, memberExpenseDetails); + return MemberExpenseItemResponse.of(member, totalAmount, memberExpenseDetails, paymentRequestId); } private Long calculateTotalAmount(List memberExpenses) { diff --git a/src/main/java/com/dnd/moddo/event/presentation/MemberExpenseController.java b/src/main/java/com/dnd/moddo/event/presentation/MemberExpenseController.java index 2e3fffe0..a37624f8 100644 --- a/src/main/java/com/dnd/moddo/event/presentation/MemberExpenseController.java +++ b/src/main/java/com/dnd/moddo/event/presentation/MemberExpenseController.java @@ -6,6 +6,8 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import com.dnd.moddo.auth.infrastructure.security.LoginUser; +import com.dnd.moddo.auth.presentation.response.LoginUserInfo; import com.dnd.moddo.event.application.query.QueryMemberExpenseService; import com.dnd.moddo.event.application.query.QuerySettlementService; import com.dnd.moddo.event.presentation.response.MembersExpenseResponse; @@ -22,13 +24,14 @@ public class MemberExpenseController { @GetMapping public ResponseEntity getMemberExpensesDetails( - @PathVariable String code + @PathVariable String code, + @LoginUser LoginUserInfo loginUser ) { Long settlementId = querySettlementService.findIdByCode(code); MembersExpenseResponse response = - queryMemberExpenseService.findMemberExpenseDetailsBySettlementId(settlementId); + queryMemberExpenseService.findMemberExpenseDetailsBySettlementId(settlementId, loginUser.userId()); return ResponseEntity.ok(response); } -} \ No newline at end of file +} diff --git a/src/main/java/com/dnd/moddo/event/presentation/response/MemberExpenseItemResponse.java b/src/main/java/com/dnd/moddo/event/presentation/response/MemberExpenseItemResponse.java index 95903849..c6f1be96 100644 --- a/src/main/java/com/dnd/moddo/event/presentation/response/MemberExpenseItemResponse.java +++ b/src/main/java/com/dnd/moddo/event/presentation/response/MemberExpenseItemResponse.java @@ -17,10 +17,11 @@ public record MemberExpenseItemResponse( String profile, boolean isPaid, LocalDateTime paidAt, + Long paymentRequestId, List expenses ) { public static MemberExpenseItemResponse of(Member member, Long totalAmount, - List expenses) { + List expenses, Long paymentRequestId) { return MemberExpenseItemResponse.builder() .id(member.getId()) .role(member.getRole()) @@ -29,6 +30,7 @@ public static MemberExpenseItemResponse of(Member member, Long totalAmount, .profile(member.getProfileUrl()) .isPaid(member.isPaid()) .paidAt(member.getPaidAt()) + .paymentRequestId(paymentRequestId) .expenses(expenses).build(); } } diff --git a/src/test/java/com/dnd/moddo/domain/memberExpense/controller/MemberExpenseControllerTest.java b/src/test/java/com/dnd/moddo/domain/memberExpense/controller/MemberExpenseControllerTest.java index 36a75b5e..89215333 100644 --- a/src/test/java/com/dnd/moddo/domain/memberExpense/controller/MemberExpenseControllerTest.java +++ b/src/test/java/com/dnd/moddo/domain/memberExpense/controller/MemberExpenseControllerTest.java @@ -12,6 +12,7 @@ import org.junit.jupiter.api.Test; import org.springframework.http.MediaType; +import com.dnd.moddo.auth.presentation.response.LoginUserInfo; import com.dnd.moddo.event.presentation.response.MemberExpenseDetailResponse; import com.dnd.moddo.event.presentation.response.MemberExpenseItemResponse; import com.dnd.moddo.event.presentation.response.MembersExpenseResponse; @@ -30,26 +31,31 @@ void getMemberExpensesDetailsSuccess() throws Exception { List.of( new MemberExpenseItemResponse(1L, MANAGER, "김모또", 10000L, "https://moddo-s3.s3.amazonaws.com/profile/MODDO.png", true, LocalDateTime.now(), - List.of(new MemberExpenseDetailResponse("카페", 10000L)) + null, List.of(new MemberExpenseDetailResponse("카페", 10000L)) ), new MemberExpenseItemResponse(2L, PARTICIPANT, "군계란", 10000L, "https://moddo-s3.s3.amazonaws.com/profile/1.png", false, null, - List.of(new MemberExpenseDetailResponse("카페", 10000L)) + 100L, List.of(new MemberExpenseDetailResponse("카페", 10000L)) ) ) ); + when(loginUserArgumentResolver.supportsParameter(any())) + .thenReturn(true); + when(loginUserArgumentResolver.resolveArgument(any(), any(), any(), any())) + .thenReturn(new LoginUserInfo(1L, "USER")); when(querySettlementService.findIdByCode(code)).thenReturn(groupId); - when(queryMemberExpenseService.findMemberExpenseDetailsBySettlementId(groupId)).thenReturn( + when(queryMemberExpenseService.findMemberExpenseDetailsBySettlementId(groupId, 1L)).thenReturn( membersExpenseResponse); // when & then mockMvc.perform(get("/api/v1/groups/{code}/member-expenses", code) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) - .andExpect(jsonPath("$.memberExpenses").isArray()); + .andExpect(jsonPath("$.memberExpenses").isArray()) + .andExpect(jsonPath("$.memberExpenses[1].paymentRequestId").value(100L)); verify(querySettlementService, times(1)).findIdByCode(code); - verify(queryMemberExpenseService, times(1)).findMemberExpenseDetailsBySettlementId(groupId); + verify(queryMemberExpenseService, times(1)).findMemberExpenseDetailsBySettlementId(groupId, 1L); } } diff --git a/src/test/java/com/dnd/moddo/domain/memberExpense/service/QueryMemberExpenseServiceTest.java b/src/test/java/com/dnd/moddo/domain/memberExpense/service/QueryMemberExpenseServiceTest.java index b9bb4e13..d78511a5 100644 --- a/src/test/java/com/dnd/moddo/domain/memberExpense/service/QueryMemberExpenseServiceTest.java +++ b/src/test/java/com/dnd/moddo/domain/memberExpense/service/QueryMemberExpenseServiceTest.java @@ -17,6 +17,8 @@ import com.dnd.moddo.event.application.impl.ExpenseReader; import com.dnd.moddo.event.application.impl.MemberExpenseReader; import com.dnd.moddo.event.application.impl.MemberReader; +import com.dnd.moddo.event.application.impl.PaymentRequestReader; +import com.dnd.moddo.event.application.impl.SettlementReader; import com.dnd.moddo.event.application.query.QueryMemberExpenseService; import com.dnd.moddo.event.domain.expense.Expense; import com.dnd.moddo.event.domain.member.ExpenseRole; @@ -35,6 +37,10 @@ class QueryMemberExpenseServiceTest { private ExpenseReader expenseReader; @Mock private MemberReader memberReader; + @Mock + private SettlementReader settlementReader; + @Mock + private PaymentRequestReader paymentRequestReader; @InjectMocks private QueryMemberExpenseService queryMemberExpenseService; @@ -91,6 +97,7 @@ void findAllByExpenseId() { void findMemberExpenseDetailsBySettlementId_Success() { //given Long groupId = 1L; + Long userId = 1L; Member member1 = mock(Member.class); Member member2 = mock(Member.class); @@ -114,6 +121,8 @@ void findMemberExpenseDetailsBySettlementId_Success() { when(expense1.getId()).thenReturn(1L); when(expense2.getId()).thenReturn(2L); + when(settlementReader.read(groupId)).thenReturn(mockSettlement); + when(paymentRequestReader.findPendingRequestIdByMemberId(groupId)).thenReturn(Map.of(2L, 100L)); when(memberReader.findAllBySettlementId(eq(groupId))).thenReturn(members); when(memberExpenseReader.findAllByAppointMemberIds(List.of(1L, 2L))) .thenReturn(List.of(memberExpense1, memberExpense2)); @@ -121,17 +130,49 @@ void findMemberExpenseDetailsBySettlementId_Success() { // when MembersExpenseResponse response = queryMemberExpenseService.findMemberExpenseDetailsBySettlementId( - groupId); + groupId, userId); // then assertThat(response).isNotNull(); assertThat(response.memberExpenses()).hasSize(2); + assertThat(response.memberExpenses().get(0).paymentRequestId()).isNull(); + assertThat(response.memberExpenses().get(1).paymentRequestId()).isEqualTo(100L); + verify(settlementReader, times(1)).read(groupId); + verify(paymentRequestReader, times(1)).findPendingRequestIdByMemberId(groupId); verify(memberReader, times(1)).findAllBySettlementId(groupId); verify(memberExpenseReader, times(1)).findAllByAppointMemberIds(anyList()); verify(expenseReader, times(1)).findAllBySettlementId(groupId); } + @DisplayName("총무가 아닐 때 참여자별 정산내역 조회 시 입금 확인 요청 ID를 조회하지 않는다.") + @Test + void findMemberExpenseDetailsBySettlementId_Success_WhenNotManager() { + //given + Long groupId = 1L; + Long userId = 2L; + Member member = mock(Member.class); + + when(member.getId()).thenReturn(1L); + when(settlementReader.read(groupId)).thenReturn(mockSettlement); + when(memberReader.findAllBySettlementId(eq(groupId))).thenReturn(List.of(member)); + when(memberExpenseReader.findAllByAppointMemberIds(List.of(1L))).thenReturn(List.of()); + when(expenseReader.findAllBySettlementId(groupId)).thenReturn(List.of()); + + // when + MembersExpenseResponse response = queryMemberExpenseService.findMemberExpenseDetailsBySettlementId( + groupId, userId); + + // then + assertThat(response).isNotNull(); + assertThat(response.memberExpenses()).hasSize(1); + assertThat(response.memberExpenses().get(0).paymentRequestId()).isNull(); + + verify(settlementReader, times(1)).read(groupId); + verify(paymentRequestReader, never()).findPendingRequestIdByMemberId(anyLong()); + verify(memberReader, times(1)).findAllBySettlementId(groupId); + } + @DisplayName("지출내역 id를 통해 참여자 지출내역을 찾아 지출내역 id에 해당하는 참여자 이름들을 map으로 조회할 수 있다.") @Test void getMemberNamesByExpenseIds_Success() { From 141276c28cc5dd035eff708171ac5de20a1dbbb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9B=90=EC=A7=80=EC=9C=A4?= Date: Sun, 31 May 2026 18:19:40 +0900 Subject: [PATCH 2/2] =?UTF-8?q?test:=20=EC=A0=95=EC=82=B0=EB=82=B4?= =?UTF-8?q?=EC=97=AD=20=EC=A1=B0=ED=9A=8C=20=EC=B4=9D=EB=AC=B4=20=EB=B6=84?= =?UTF-8?q?=EA=B8=B0=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=AA=85=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../QueryMemberExpenseServiceTest.java | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/test/java/com/dnd/moddo/domain/memberExpense/service/QueryMemberExpenseServiceTest.java b/src/test/java/com/dnd/moddo/domain/memberExpense/service/QueryMemberExpenseServiceTest.java index d78511a5..e3e97feb 100644 --- a/src/test/java/com/dnd/moddo/domain/memberExpense/service/QueryMemberExpenseServiceTest.java +++ b/src/test/java/com/dnd/moddo/domain/memberExpense/service/QueryMemberExpenseServiceTest.java @@ -3,6 +3,7 @@ import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; +import java.time.LocalDateTime; import java.util.List; import java.util.Map; @@ -27,10 +28,12 @@ import com.dnd.moddo.event.domain.settlement.Settlement; import com.dnd.moddo.event.presentation.response.MemberExpenseResponse; import com.dnd.moddo.event.presentation.response.MembersExpenseResponse; -import com.dnd.moddo.global.support.GroupTestFactory; @ExtendWith(MockitoExtension.class) class QueryMemberExpenseServiceTest { + private static final Long MANAGER_USER_ID = 1L; + private static final Long PARTICIPANT_USER_ID = 2L; + @Mock private MemberExpenseReader memberExpenseReader; @Mock @@ -50,7 +53,7 @@ class QueryMemberExpenseServiceTest { @BeforeEach void setUp() { - mockSettlement = GroupTestFactory.createDefault(); + mockSettlement = createSettlementWithWriter(MANAGER_USER_ID); mockMember1 = Member.builder() .name("김모또") @@ -97,7 +100,7 @@ void findAllByExpenseId() { void findMemberExpenseDetailsBySettlementId_Success() { //given Long groupId = 1L; - Long userId = 1L; + Long userId = MANAGER_USER_ID; Member member1 = mock(Member.class); Member member2 = mock(Member.class); @@ -150,7 +153,7 @@ void findMemberExpenseDetailsBySettlementId_Success() { void findMemberExpenseDetailsBySettlementId_Success_WhenNotManager() { //given Long groupId = 1L; - Long userId = 2L; + Long userId = PARTICIPANT_USER_ID; Member member = mock(Member.class); when(member.getId()).thenReturn(1L); @@ -209,4 +212,16 @@ void getMemberNamesByExpenseIds_Success() { verify(memberExpenseReader, times(1)).findAllByExpenseIds(eq(expenseIds)); } + + private Settlement createSettlementWithWriter(Long writerId) { + return new Settlement( + "group 1", + writerId, + LocalDateTime.now().plusMinutes(1), + "은행", + "계좌", + "code", + LocalDateTime.now().plusDays(1) + ); + } }