diff --git a/src/main/java/com/dnd/moddo/event/application/impl/PaymentRequestReader.java b/src/main/java/com/dnd/moddo/event/application/impl/PaymentRequestReader.java index c9035e55..a69a0f86 100644 --- a/src/main/java/com/dnd/moddo/event/application/impl/PaymentRequestReader.java +++ b/src/main/java/com/dnd/moddo/event/application/impl/PaymentRequestReader.java @@ -15,6 +15,7 @@ import com.dnd.moddo.event.infrastructure.PaymentRequestRepository; import com.dnd.moddo.event.presentation.response.PaymentRequestItemResponse; import com.dnd.moddo.event.presentation.response.PaymentRequestsResponse; +import com.dnd.moddo.event.presentation.response.PaymentRequestSummaryResponse; import lombok.RequiredArgsConstructor; @@ -65,15 +66,13 @@ public boolean existsBySettlementId(Long settlementId) { return paymentRequestRepository.existsBySettlementId(settlementId); } - public Map findPendingRequestIdByMemberId(Long settlementId) { - return paymentRequestRepository.findBySettlementIdAndStatus(settlementId, PaymentRequestStatus.PENDING) + public Map findLatestRequestByMemberId(Long settlementId) { + return paymentRequestRepository.findBySettlementIdOrderByRequestedAtDesc(settlementId) .stream() .collect(Collectors.toMap( PaymentRequest::getRequestMemberId, - PaymentRequest::getId, - (first, duplicate) -> { - throw new IllegalStateException("중복된 PENDING 입금 확인 요청이 존재합니다."); - } + PaymentRequestSummaryResponse::of, + (first, duplicate) -> first )); } 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 f083b678..9f3eea6d 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 @@ -22,6 +22,7 @@ import com.dnd.moddo.event.presentation.response.MemberExpenseItemResponse; import com.dnd.moddo.event.presentation.response.MemberExpenseResponse; import com.dnd.moddo.event.presentation.response.MembersExpenseResponse; +import com.dnd.moddo.event.presentation.response.PaymentRequestSummaryResponse; import lombok.RequiredArgsConstructor; @@ -44,8 +45,8 @@ public List findAllByExpenseId( public MembersExpenseResponse findMemberExpenseDetailsBySettlementId(Long settlementId, Long userId) { Settlement settlement = settlementReader.read(settlementId); - Map paymentRequestIdByMemberId = settlement.isWriter(userId) - ? paymentRequestReader.findPendingRequestIdByMemberId(settlementId) + Map paymentRequestByMemberId = settlement.isWriter(userId) + ? paymentRequestReader.findLatestRequestByMemberId(settlementId) : Map.of(); List members = memberReader.findAllBySettlementId(settlementId); @@ -66,7 +67,7 @@ public MembersExpenseResponse findMemberExpenseDetailsBySettlementId(Long settle key -> findMemberExpenseDetailByAppointmentMember(appointmentMemberById.get(key), memberExpenses.get(key), expenses, - paymentRequestIdByMemberId) + paymentRequestByMemberId) ) .filter(Objects::nonNull) .toList(); @@ -84,18 +85,18 @@ private Map convertAppointmentMembersToMap(List members) { private MemberExpenseItemResponse findMemberExpenseDetailByAppointmentMember( Member member, List memberExpenses, List expenses, - Map paymentRequestIdByMemberId) { + Map paymentRequestByMemberId) { - Long paymentRequestId = paymentRequestIdByMemberId.get(member.getId()); + PaymentRequestSummaryResponse paymentRequest = paymentRequestByMemberId.get(member.getId()); if (memberExpenses == null) { - return MemberExpenseItemResponse.of(member, 0L, new ArrayList<>(), paymentRequestId); + return MemberExpenseItemResponse.of(member, 0L, new ArrayList<>(), paymentRequest); } List memberExpenseDetails = mapToMemberExpenseDetails(memberExpenses, expenses); Long totalAmount = calculateTotalAmount(memberExpenses); - return MemberExpenseItemResponse.of(member, totalAmount, memberExpenseDetails, paymentRequestId); + return MemberExpenseItemResponse.of(member, totalAmount, memberExpenseDetails, paymentRequest); } private Long calculateTotalAmount(List memberExpenses) { diff --git a/src/main/java/com/dnd/moddo/event/application/query/QuerySettlementService.java b/src/main/java/com/dnd/moddo/event/application/query/QuerySettlementService.java index 71b472e4..decb7955 100644 --- a/src/main/java/com/dnd/moddo/event/application/query/QuerySettlementService.java +++ b/src/main/java/com/dnd/moddo/event/application/query/QuerySettlementService.java @@ -20,6 +20,7 @@ import com.dnd.moddo.event.presentation.response.SettlementHeaderResponse; import com.dnd.moddo.event.presentation.response.SettlementListResponse; import com.dnd.moddo.event.presentation.response.SettlementShareResponse; +import com.dnd.moddo.event.presentation.response.PaymentRequestSummaryResponse; import lombok.RequiredArgsConstructor; @@ -39,8 +40,9 @@ public SettlementDetailResponse findOne(Long settlementId, Long userId) { Settlement settlement = settlementReader.read(settlementId); settlementValidator.checkSettlementAuthor(settlement, userId); List members = settlementReader.findBySettlement(settlementId); - Map paymentRequestIdByMemberId = paymentRequestReader.findPendingRequestIdByMemberId(settlementId); - return SettlementDetailResponse.of(settlement, members, paymentRequestIdByMemberId); + Map paymentRequestByMemberId = + paymentRequestReader.findLatestRequestByMemberId(settlementId); + return SettlementDetailResponse.of(settlement, members, paymentRequestByMemberId); } public SettlementHeaderResponse findBySettlementHeader(Long settlementId) { diff --git a/src/main/java/com/dnd/moddo/event/domain/paymentRequest/PaymentRequestStatus.java b/src/main/java/com/dnd/moddo/event/domain/paymentRequest/PaymentRequestStatus.java index 647fe9e2..7fc663e6 100644 --- a/src/main/java/com/dnd/moddo/event/domain/paymentRequest/PaymentRequestStatus.java +++ b/src/main/java/com/dnd/moddo/event/domain/paymentRequest/PaymentRequestStatus.java @@ -1,5 +1,16 @@ package com.dnd.moddo.event.domain.paymentRequest; +import lombok.Getter; + +@Getter public enum PaymentRequestStatus { - PENDING, APPROVED, REJECTED + PENDING("확인중"), + APPROVED("승인완료"), + REJECTED("거절"); + + private final String label; + + PaymentRequestStatus(String label) { + this.label = label; + } } diff --git a/src/main/java/com/dnd/moddo/event/infrastructure/PaymentRequestRepository.java b/src/main/java/com/dnd/moddo/event/infrastructure/PaymentRequestRepository.java index 11c56975..30d6f205 100644 --- a/src/main/java/com/dnd/moddo/event/infrastructure/PaymentRequestRepository.java +++ b/src/main/java/com/dnd/moddo/event/infrastructure/PaymentRequestRepository.java @@ -44,10 +44,7 @@ select count(pr) > 0 select pr from PaymentRequest pr where pr.settlement.id = :settlementId - and pr.status = :status - """) - List findBySettlementIdAndStatus( - @Param("settlementId") Long settlementId, - @Param("status") PaymentRequestStatus status - ); + order by pr.requestedAt desc, pr.id desc + """) + List findBySettlementIdOrderByRequestedAtDesc(@Param("settlementId") Long settlementId); } 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 c6f1be96..8903de4b 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 @@ -5,6 +5,7 @@ import com.dnd.moddo.event.domain.member.ExpenseRole; import com.dnd.moddo.event.domain.member.Member; +import com.dnd.moddo.event.domain.paymentRequest.PaymentRequestStatus; import lombok.Builder; @@ -18,10 +19,12 @@ public record MemberExpenseItemResponse( boolean isPaid, LocalDateTime paidAt, Long paymentRequestId, + PaymentRequestStatus paymentRequestStatus, + String paymentRequestStatusLabel, List expenses ) { public static MemberExpenseItemResponse of(Member member, Long totalAmount, - List expenses, Long paymentRequestId) { + List expenses, PaymentRequestSummaryResponse paymentRequest) { return MemberExpenseItemResponse.builder() .id(member.getId()) .role(member.getRole()) @@ -30,7 +33,9 @@ public static MemberExpenseItemResponse of(Member member, Long totalAmount, .profile(member.getProfileUrl()) .isPaid(member.isPaid()) .paidAt(member.getPaidAt()) - .paymentRequestId(paymentRequestId) + .paymentRequestId(paymentRequest == null ? null : paymentRequest.id()) + .paymentRequestStatus(paymentRequest == null ? null : paymentRequest.status()) + .paymentRequestStatusLabel(paymentRequest == null ? null : paymentRequest.statusLabel()) .expenses(expenses).build(); } } diff --git a/src/main/java/com/dnd/moddo/event/presentation/response/PaymentRequestResponse.java b/src/main/java/com/dnd/moddo/event/presentation/response/PaymentRequestResponse.java index f861b567..9eac6431 100644 --- a/src/main/java/com/dnd/moddo/event/presentation/response/PaymentRequestResponse.java +++ b/src/main/java/com/dnd/moddo/event/presentation/response/PaymentRequestResponse.java @@ -12,7 +12,8 @@ public record PaymentRequestResponse( Long targetUserId, LocalDateTime requestedAt, LocalDateTime processedAt, - PaymentRequestStatus status + PaymentRequestStatus status, + String statusLabel ) { public static PaymentRequestResponse of(PaymentRequest paymentRequest) { return new PaymentRequestResponse( @@ -22,7 +23,8 @@ public static PaymentRequestResponse of(PaymentRequest paymentRequest) { paymentRequest.getTargetUserId(), paymentRequest.getRequestedAt(), paymentRequest.getProcessedAt(), - paymentRequest.getStatus() + paymentRequest.getStatus(), + paymentRequest.getStatus().getLabel() ); } } diff --git a/src/main/java/com/dnd/moddo/event/presentation/response/PaymentRequestSummaryResponse.java b/src/main/java/com/dnd/moddo/event/presentation/response/PaymentRequestSummaryResponse.java new file mode 100644 index 00000000..ac65ef84 --- /dev/null +++ b/src/main/java/com/dnd/moddo/event/presentation/response/PaymentRequestSummaryResponse.java @@ -0,0 +1,18 @@ +package com.dnd.moddo.event.presentation.response; + +import com.dnd.moddo.event.domain.paymentRequest.PaymentRequest; +import com.dnd.moddo.event.domain.paymentRequest.PaymentRequestStatus; + +public record PaymentRequestSummaryResponse( + Long id, + PaymentRequestStatus status, + String statusLabel +) { + public static PaymentRequestSummaryResponse of(PaymentRequest paymentRequest) { + return new PaymentRequestSummaryResponse( + paymentRequest.getId(), + paymentRequest.getStatus(), + paymentRequest.getStatus().getLabel() + ); + } +} diff --git a/src/main/java/com/dnd/moddo/event/presentation/response/SettlementDetailResponse.java b/src/main/java/com/dnd/moddo/event/presentation/response/SettlementDetailResponse.java index 21477e91..ef7a7f2e 100644 --- a/src/main/java/com/dnd/moddo/event/presentation/response/SettlementDetailResponse.java +++ b/src/main/java/com/dnd/moddo/event/presentation/response/SettlementDetailResponse.java @@ -19,10 +19,10 @@ public static SettlementDetailResponse of(Settlement settlement, List me public static SettlementDetailResponse of( Settlement settlement, List members, - Map paymentRequestIdByMemberId + Map paymentRequestByMemberId ) { List memberResponses = members.stream() - .map(member -> SettlementMemberResponse.of(member, paymentRequestIdByMemberId.get(member.getId()))) + .map(member -> SettlementMemberResponse.of(member, paymentRequestByMemberId.get(member.getId()))) .collect(Collectors.toList()); return new SettlementDetailResponse( settlement.getId(), diff --git a/src/main/java/com/dnd/moddo/event/presentation/response/SettlementMemberResponse.java b/src/main/java/com/dnd/moddo/event/presentation/response/SettlementMemberResponse.java index 63460ef4..36215266 100644 --- a/src/main/java/com/dnd/moddo/event/presentation/response/SettlementMemberResponse.java +++ b/src/main/java/com/dnd/moddo/event/presentation/response/SettlementMemberResponse.java @@ -4,6 +4,7 @@ import com.dnd.moddo.event.domain.member.ExpenseRole; import com.dnd.moddo.event.domain.member.Member; +import com.dnd.moddo.event.domain.paymentRequest.PaymentRequestStatus; import lombok.Builder; @@ -16,10 +17,12 @@ public record SettlementMemberResponse( Long userId, Boolean isPaid, LocalDateTime paidAt, - Long paymentRequestId + Long paymentRequestId, + PaymentRequestStatus paymentRequestStatus, + String paymentRequestStatusLabel ) { - public static SettlementMemberResponse of(Member member, Long paymentRequestId) { + public static SettlementMemberResponse of(Member member, PaymentRequestSummaryResponse paymentRequest) { return SettlementMemberResponse.builder() .id(member.getId()) .name(member.getName()) @@ -27,7 +30,9 @@ public static SettlementMemberResponse of(Member member, Long paymentRequestId) .userId(member.getUserId()) .isPaid(member.isPaid()) .paidAt(member.getPaidAt()) - .paymentRequestId(paymentRequestId) + .paymentRequestId(paymentRequest == null ? null : paymentRequest.id()) + .paymentRequestStatus(paymentRequest == null ? null : paymentRequest.status()) + .paymentRequestStatusLabel(paymentRequest == null ? null : paymentRequest.statusLabel()) .profile(member.getProfileUrl()) .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 89215333..ddd47b43 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 @@ -13,6 +13,7 @@ import org.springframework.http.MediaType; import com.dnd.moddo.auth.presentation.response.LoginUserInfo; +import com.dnd.moddo.event.domain.paymentRequest.PaymentRequestStatus; import com.dnd.moddo.event.presentation.response.MemberExpenseDetailResponse; import com.dnd.moddo.event.presentation.response.MemberExpenseItemResponse; import com.dnd.moddo.event.presentation.response.MembersExpenseResponse; @@ -31,11 +32,12 @@ void getMemberExpensesDetailsSuccess() throws Exception { List.of( new MemberExpenseItemResponse(1L, MANAGER, "김모또", 10000L, "https://moddo-s3.s3.amazonaws.com/profile/MODDO.png", true, LocalDateTime.now(), - null, List.of(new MemberExpenseDetailResponse("카페", 10000L)) + null, null, null, List.of(new MemberExpenseDetailResponse("카페", 10000L)) ), new MemberExpenseItemResponse(2L, PARTICIPANT, "군계란", 10000L, "https://moddo-s3.s3.amazonaws.com/profile/1.png", false, null, - 100L, List.of(new MemberExpenseDetailResponse("카페", 10000L)) + 100L, PaymentRequestStatus.APPROVED, "승인완료", + List.of(new MemberExpenseDetailResponse("카페", 10000L)) ) ) ); @@ -53,7 +55,9 @@ void getMemberExpensesDetailsSuccess() throws Exception { .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$.memberExpenses").isArray()) - .andExpect(jsonPath("$.memberExpenses[1].paymentRequestId").value(100L)); + .andExpect(jsonPath("$.memberExpenses[1].paymentRequestId").value(100L)) + .andExpect(jsonPath("$.memberExpenses[1].paymentRequestStatus").value("APPROVED")) + .andExpect(jsonPath("$.memberExpenses[1].paymentRequestStatusLabel").value("승인완료")); verify(querySettlementService, times(1)).findIdByCode(code); 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 e3e97feb..b3915563 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 @@ -25,9 +25,11 @@ import com.dnd.moddo.event.domain.member.ExpenseRole; import com.dnd.moddo.event.domain.member.Member; import com.dnd.moddo.event.domain.memberExpense.MemberExpense; +import com.dnd.moddo.event.domain.paymentRequest.PaymentRequestStatus; 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.event.presentation.response.PaymentRequestSummaryResponse; @ExtendWith(MockitoExtension.class) class QueryMemberExpenseServiceTest { @@ -125,7 +127,9 @@ void findMemberExpenseDetailsBySettlementId_Success() { when(expense2.getId()).thenReturn(2L); when(settlementReader.read(groupId)).thenReturn(mockSettlement); - when(paymentRequestReader.findPendingRequestIdByMemberId(groupId)).thenReturn(Map.of(2L, 100L)); + when(paymentRequestReader.findLatestRequestByMemberId(groupId)) + .thenReturn(Map.of(2L, + new PaymentRequestSummaryResponse(100L, PaymentRequestStatus.APPROVED, "승인완료"))); when(memberReader.findAllBySettlementId(eq(groupId))).thenReturn(members); when(memberExpenseReader.findAllByAppointMemberIds(List.of(1L, 2L))) .thenReturn(List.of(memberExpense1, memberExpense2)); @@ -139,10 +143,14 @@ void findMemberExpenseDetailsBySettlementId_Success() { assertThat(response).isNotNull(); assertThat(response.memberExpenses()).hasSize(2); assertThat(response.memberExpenses().get(0).paymentRequestId()).isNull(); + assertThat(response.memberExpenses().get(0).paymentRequestStatus()).isNull(); + assertThat(response.memberExpenses().get(0).paymentRequestStatusLabel()).isNull(); assertThat(response.memberExpenses().get(1).paymentRequestId()).isEqualTo(100L); + assertThat(response.memberExpenses().get(1).paymentRequestStatus()).isEqualTo(PaymentRequestStatus.APPROVED); + assertThat(response.memberExpenses().get(1).paymentRequestStatusLabel()).isEqualTo("승인완료"); verify(settlementReader, times(1)).read(groupId); - verify(paymentRequestReader, times(1)).findPendingRequestIdByMemberId(groupId); + verify(paymentRequestReader, times(1)).findLatestRequestByMemberId(groupId); verify(memberReader, times(1)).findAllBySettlementId(groupId); verify(memberExpenseReader, times(1)).findAllByAppointMemberIds(anyList()); verify(expenseReader, times(1)).findAllBySettlementId(groupId); @@ -170,9 +178,11 @@ void findMemberExpenseDetailsBySettlementId_Success_WhenNotManager() { assertThat(response).isNotNull(); assertThat(response.memberExpenses()).hasSize(1); assertThat(response.memberExpenses().get(0).paymentRequestId()).isNull(); + assertThat(response.memberExpenses().get(0).paymentRequestStatus()).isNull(); + assertThat(response.memberExpenses().get(0).paymentRequestStatusLabel()).isNull(); verify(settlementReader, times(1)).read(groupId); - verify(paymentRequestReader, never()).findPendingRequestIdByMemberId(anyLong()); + verify(paymentRequestReader, never()).findLatestRequestByMemberId(anyLong()); verify(memberReader, times(1)).findAllBySettlementId(groupId); } diff --git a/src/test/java/com/dnd/moddo/domain/paymentRequest/controller/PaymentRequestControllerTest.java b/src/test/java/com/dnd/moddo/domain/paymentRequest/controller/PaymentRequestControllerTest.java index fef3927d..ec7d98a4 100644 --- a/src/test/java/com/dnd/moddo/domain/paymentRequest/controller/PaymentRequestControllerTest.java +++ b/src/test/java/com/dnd/moddo/domain/paymentRequest/controller/PaymentRequestControllerTest.java @@ -101,7 +101,8 @@ void createPaymentRequest() throws Exception { String code = "code"; Long settlementId = 1L; PaymentRequestResponse response = new PaymentRequestResponse( - 1L, settlementId, 2L, 3L, LocalDateTime.of(2026, 3, 13, 22, 0), null, PaymentRequestStatus.PENDING + 1L, settlementId, 2L, 3L, LocalDateTime.of(2026, 3, 13, 22, 0), null, + PaymentRequestStatus.PENDING, "확인중" ); when(querySettlementService.findIdByCode(code)).thenReturn(settlementId); @@ -112,6 +113,7 @@ void createPaymentRequest() throws Exception { .andExpect(status().isOk()) .andExpect(jsonPath("$.id").value(1L)) .andExpect(jsonPath("$.settlementId").value(settlementId)) + .andExpect(jsonPath("$.statusLabel").value("확인중")) .andDo(restDocs.document( pathParameters( parameterWithName("code").description("정산 코드") @@ -123,7 +125,8 @@ void createPaymentRequest() throws Exception { fieldWithPath("targetUserId").type(JsonFieldType.NUMBER).description("처리 대상 사용자 ID"), fieldWithPath("requestedAt").type(JsonFieldType.STRING).description("요청 시각"), fieldWithPath("processedAt").type(JsonFieldType.NULL).description("처리 시각").optional(), - fieldWithPath("status").type(JsonFieldType.STRING).description("요청 상태") + fieldWithPath("status").type(JsonFieldType.STRING).description("요청 상태"), + fieldWithPath("statusLabel").type(JsonFieldType.STRING).description("요청 상태 표시명") ) )); } @@ -135,7 +138,8 @@ void approvePaymentRequest() throws Exception { 1L, 1L, 2L, 1L, LocalDateTime.of(2026, 3, 13, 22, 0), LocalDateTime.of(2026, 3, 13, 22, 5), - com.dnd.moddo.event.domain.paymentRequest.PaymentRequestStatus.APPROVED + com.dnd.moddo.event.domain.paymentRequest.PaymentRequestStatus.APPROVED, + "승인완료" ); when(commandPaymentRequest.approvePaymentRequest(1L, 1L)).thenReturn(response); @@ -143,6 +147,7 @@ void approvePaymentRequest() throws Exception { mockMvc.perform(patch("/api/v1/payments/{paymentRequestId}/approve", 1L)) .andExpect(status().isOk()) .andExpect(jsonPath("$.status").value("APPROVED")) + .andExpect(jsonPath("$.statusLabel").value("승인완료")) .andDo(restDocs.document( pathParameters( parameterWithName("paymentRequestId").description("입금 확인 요청 ID") @@ -154,7 +159,8 @@ void approvePaymentRequest() throws Exception { fieldWithPath("targetUserId").type(JsonFieldType.NUMBER).description("처리 대상 사용자 ID"), fieldWithPath("requestedAt").type(JsonFieldType.STRING).description("요청 시각"), fieldWithPath("processedAt").type(JsonFieldType.STRING).description("처리 시각"), - fieldWithPath("status").type(JsonFieldType.STRING).description("요청 상태") + fieldWithPath("status").type(JsonFieldType.STRING).description("요청 상태"), + fieldWithPath("statusLabel").type(JsonFieldType.STRING).description("요청 상태 표시명") ) )); } @@ -166,7 +172,8 @@ void rejectPaymentRequest() throws Exception { 1L, 1L, 2L, 1L, LocalDateTime.of(2026, 3, 13, 22, 0), LocalDateTime.of(2026, 3, 13, 22, 5), - com.dnd.moddo.event.domain.paymentRequest.PaymentRequestStatus.REJECTED + com.dnd.moddo.event.domain.paymentRequest.PaymentRequestStatus.REJECTED, + "거절" ); when(commandPaymentRequest.rejectPaymentRequest(1L, 1L)).thenReturn(response); @@ -174,6 +181,7 @@ void rejectPaymentRequest() throws Exception { mockMvc.perform(patch("/api/v1/payments/{paymentRequestId}/reject", 1L)) .andExpect(status().isOk()) .andExpect(jsonPath("$.status").value("REJECTED")) + .andExpect(jsonPath("$.statusLabel").value("거절")) .andDo(restDocs.document( pathParameters( parameterWithName("paymentRequestId").description("입금 확인 요청 ID") @@ -185,7 +193,8 @@ void rejectPaymentRequest() throws Exception { fieldWithPath("targetUserId").type(JsonFieldType.NUMBER).description("처리 대상 사용자 ID"), fieldWithPath("requestedAt").type(JsonFieldType.STRING).description("요청 시각"), fieldWithPath("processedAt").type(JsonFieldType.STRING).description("처리 시각"), - fieldWithPath("status").type(JsonFieldType.STRING).description("요청 상태") + fieldWithPath("status").type(JsonFieldType.STRING).description("요청 상태"), + fieldWithPath("statusLabel").type(JsonFieldType.STRING).description("요청 상태 표시명") ) )); } diff --git a/src/test/java/com/dnd/moddo/domain/paymentRequest/service/implementation/PaymentRequestReaderTest.java b/src/test/java/com/dnd/moddo/domain/paymentRequest/service/implementation/PaymentRequestReaderTest.java index daf136bc..e47fbd03 100644 --- a/src/test/java/com/dnd/moddo/domain/paymentRequest/service/implementation/PaymentRequestReaderTest.java +++ b/src/test/java/com/dnd/moddo/domain/paymentRequest/service/implementation/PaymentRequestReaderTest.java @@ -24,6 +24,7 @@ import com.dnd.moddo.event.infrastructure.PaymentRequestRepository; import com.dnd.moddo.event.presentation.response.PaymentRequestItemResponse; import com.dnd.moddo.event.presentation.response.PaymentRequestsResponse; +import com.dnd.moddo.event.presentation.response.PaymentRequestSummaryResponse; @ExtendWith(MockitoExtension.class) class PaymentRequestReaderTest { @@ -101,8 +102,8 @@ void findByTargetUserId() { } @Test - @DisplayName("정산의 대기 중인 입금 확인 요청 ID를 멤버 ID 기준으로 조회할 수 있다.") - void findPendingRequestIdByMemberId() { + @DisplayName("정산의 최신 입금 확인 요청 정보를 멤버 ID 기준으로 조회할 수 있다.") + void findLatestRequestByMemberId() { Settlement settlement = mock(Settlement.class); Member member = Member.builder() .name("김반숙") @@ -115,16 +116,19 @@ void findPendingRequestIdByMemberId() { .requestMember(member) .targetUser(mock(com.dnd.moddo.user.domain.User.class)) .build(); + paymentRequest.approve(); setField(member, "id", 11L); setField(paymentRequest, "id", 100L); - when(paymentRequestRepository.findBySettlementIdAndStatus(1L, PaymentRequestStatus.PENDING)) + when(paymentRequestRepository.findBySettlementIdOrderByRequestedAtDesc(1L)) .thenReturn(List.of(paymentRequest)); - java.util.Map result = paymentRequestReader.findPendingRequestIdByMemberId(1L); + java.util.Map result = paymentRequestReader.findLatestRequestByMemberId(1L); - assertThat(result).containsEntry(11L, 100L); + assertThat(result.get(11L).id()).isEqualTo(100L); + assertThat(result.get(11L).status()).isEqualTo(PaymentRequestStatus.APPROVED); + assertThat(result.get(11L).statusLabel()).isEqualTo("승인완료"); } @Test @@ -139,8 +143,8 @@ void existsBySettlementId() { } @Test - @DisplayName("같은 멤버의 대기 중인 입금 확인 요청이 중복되면 예외가 발생한다.") - void findPendingRequestIdByMemberIdFailWhenDuplicatePendingRequest() { + @DisplayName("같은 멤버의 입금 확인 요청이 여러 개면 최신 요청 정보를 조회한다.") + void findLatestRequestByMemberIdWhenMultipleRequests() { Settlement settlement = mock(Settlement.class); Member member = Member.builder() .name("김반숙") @@ -153,22 +157,26 @@ void findPendingRequestIdByMemberIdFailWhenDuplicatePendingRequest() { .requestMember(member) .targetUser(mock(com.dnd.moddo.user.domain.User.class)) .build(); + first.reject(); PaymentRequest second = PaymentRequest.builder() .settlement(settlement) .requestMember(member) .targetUser(mock(com.dnd.moddo.user.domain.User.class)) .build(); + second.approve(); setField(member, "id", 11L); setField(first, "id", 100L); setField(second, "id", 101L); - when(paymentRequestRepository.findBySettlementIdAndStatus(1L, PaymentRequestStatus.PENDING)) - .thenReturn(List.of(first, second)); + when(paymentRequestRepository.findBySettlementIdOrderByRequestedAtDesc(1L)) + .thenReturn(List.of(second, first)); - assertThatThrownBy(() -> paymentRequestReader.findPendingRequestIdByMemberId(1L)) - .isInstanceOf(IllegalStateException.class) - .hasMessage("중복된 PENDING 입금 확인 요청이 존재합니다."); + java.util.Map result = paymentRequestReader.findLatestRequestByMemberId(1L); + + assertThat(result.get(11L).id()).isEqualTo(101L); + assertThat(result.get(11L).status()).isEqualTo(PaymentRequestStatus.APPROVED); + assertThat(result.get(11L).statusLabel()).isEqualTo("승인완료"); } private void setField(Object target, String fieldName, Object value) { diff --git a/src/test/java/com/dnd/moddo/domain/settlement/controller/SettlementControllerTest.java b/src/test/java/com/dnd/moddo/domain/settlement/controller/SettlementControllerTest.java index b8ab82d3..70b31941 100644 --- a/src/test/java/com/dnd/moddo/domain/settlement/controller/SettlementControllerTest.java +++ b/src/test/java/com/dnd/moddo/domain/settlement/controller/SettlementControllerTest.java @@ -118,6 +118,8 @@ void getSettlement() throws Exception { 1L, true, LocalDateTime.now(), + null, + null, null) )); @@ -134,6 +136,8 @@ void getSettlement() throws Exception { mockMvc.perform(get("/api/v1/groups/{code}", "code")) .andExpect(status().isOk()) .andExpect(jsonPath("$.members[0].paymentRequestId").value(Matchers.nullValue())) + .andExpect(jsonPath("$.members[0].paymentRequestStatus").value(Matchers.nullValue())) + .andExpect(jsonPath("$.members[0].paymentRequestStatusLabel").value(Matchers.nullValue())) .andDo(restDocs.document( pathParameters( parameterWithName("code").description("정산 코드") @@ -149,7 +153,9 @@ void getSettlement() throws Exception { fieldWithPath("members[].userId").description("사용자 ID").optional(), fieldWithPath("members[].isPaid").description("정산 완료 여부"), fieldWithPath("members[].paidAt").description("정산 완료 시각").optional(), - fieldWithPath("members[].paymentRequestId").description("대기 중인 입금 확인 요청 ID").optional() + fieldWithPath("members[].paymentRequestId").description("입금 확인 요청 ID").optional(), + fieldWithPath("members[].paymentRequestStatus").description("입금 확인 요청 상태").optional(), + fieldWithPath("members[].paymentRequestStatusLabel").description("입금 확인 요청 상태 표시명").optional() ) )); } diff --git a/src/test/java/com/dnd/moddo/domain/settlement/service/QuerySettlementServiceTest.java b/src/test/java/com/dnd/moddo/domain/settlement/service/QuerySettlementServiceTest.java index 21477e32..93af36a6 100644 --- a/src/test/java/com/dnd/moddo/domain/settlement/service/QuerySettlementServiceTest.java +++ b/src/test/java/com/dnd/moddo/domain/settlement/service/QuerySettlementServiceTest.java @@ -23,11 +23,13 @@ import com.dnd.moddo.event.application.query.QuerySettlementService; import com.dnd.moddo.event.domain.member.ExpenseRole; import com.dnd.moddo.event.domain.member.Member; +import com.dnd.moddo.event.domain.paymentRequest.PaymentRequestStatus; import com.dnd.moddo.event.domain.settlement.Settlement; import com.dnd.moddo.event.domain.settlement.exception.GroupNotFoundException; import com.dnd.moddo.event.domain.settlement.type.SettlementSortType; import com.dnd.moddo.event.domain.settlement.type.SettlementStatus; import com.dnd.moddo.event.presentation.request.SearchSettlementListRequest; +import com.dnd.moddo.event.presentation.response.PaymentRequestSummaryResponse; import com.dnd.moddo.event.presentation.response.SettlementDetailResponse; import com.dnd.moddo.event.presentation.response.SettlementHeaderResponse; import com.dnd.moddo.event.presentation.response.SettlementListResponse; @@ -76,7 +78,9 @@ void FindOne_Success() { // Given when(settlementReader.read(anyLong())).thenReturn(settlement); when(settlementReader.findBySettlement(settlement.getId())).thenReturn(List.of(member)); - when(paymentRequestReader.findPendingRequestIdByMemberId(settlement.getId())).thenReturn(java.util.Map.of(10L, 100L)); + when(paymentRequestReader.findLatestRequestByMemberId(settlement.getId())) + .thenReturn(java.util.Map.of(10L, + new PaymentRequestSummaryResponse(100L, PaymentRequestStatus.APPROVED, "승인완료"))); doNothing().when(settlementValidator).checkSettlementAuthor(settlement, 1L); // When @@ -89,10 +93,12 @@ void FindOne_Success() { assertThat(response.members()).hasSize(1); assertThat(response.members().get(0).name()).isEqualTo(member.getName()); assertThat(response.members().get(0).paymentRequestId()).isEqualTo(100L); + assertThat(response.members().get(0).paymentRequestStatus()).isEqualTo(PaymentRequestStatus.APPROVED); + assertThat(response.members().get(0).paymentRequestStatusLabel()).isEqualTo("승인완료"); verify(settlementReader, times(1)).read(1L); verify(settlementReader, times(1)).findBySettlement(settlement.getId()); - verify(paymentRequestReader, times(1)).findPendingRequestIdByMemberId(settlement.getId()); + verify(paymentRequestReader, times(1)).findLatestRequestByMemberId(settlement.getId()); verify(settlementValidator, times(1)).checkSettlementAuthor(settlement, 1L); }