Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions src/docs/asciidoc/payment.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,36 @@ include::{snippets}/payment-request-controller-test/get-payment-requests/http-re

include::{snippets}/payment-request-controller-test/get-payment-requests/response-body.adoc[]

== 모임 입금 확인 요청 존재 여부 조회

모임에 생성된 입금 확인 요청이 하나라도 있는지 조회할 수 있습니다.

=== Example

include::{snippets}/payment-request-controller-test/exists-payment-request/curl-request.adoc[]

=== HTTP

==== 요청

include::{snippets}/payment-request-controller-test/exists-payment-request/http-request.adoc[]

include::{snippets}/payment-request-controller-test/exists-payment-request/path-parameters.adoc[]

==== 응답

include::{snippets}/payment-request-controller-test/exists-payment-request/http-response.adoc[]

=== Body

==== 응답

include::{snippets}/payment-request-controller-test/exists-payment-request/response-body.adoc[]

==== 응답 필드

include::{snippets}/payment-request-controller-test/exists-payment-request/response-fields.adoc[]

== 입금 확인 요청 생성

정산 참여자가 총무에게 입금 확인 요청을 보낼 수 있습니다.
Expand Down
24 changes: 24 additions & 0 deletions src/docs/asciidoc/settlement.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,26 @@ include::{snippets}/settlement-controller-test/update-account/request-body.adoc[

include::{snippets}/settlement-controller-test/update-account/response-body.adoc[]

== 정산 수동 완료

총무가 정산을 수동으로 완료할 수 있습니다.

=== Example

include::{snippets}/settlement-controller-test/complete-settlement/curl-request.adoc[]

=== HTTP

==== 요청

include::{snippets}/settlement-controller-test/complete-settlement/http-request.adoc[]

include::{snippets}/settlement-controller-test/complete-settlement/path-parameters.adoc[]

==== 응답

include::{snippets}/settlement-controller-test/complete-settlement/http-response.adoc[]

== 모임 조회

모임 정보와 참여자 목록을 조회할 수 있습니다.
Expand All @@ -91,6 +111,10 @@ include::{snippets}/settlement-controller-test/get-settlement/http-response.adoc

include::{snippets}/settlement-controller-test/get-settlement/response-body.adoc[]

==== 응답 필드

include::{snippets}/settlement-controller-test/get-settlement/response-fields.adoc[]

== 모임 상단 조회

지출 내역 화면의 상단 정보를 조회할 수 있습니다.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import com.dnd.moddo.common.cache.CacheEvictor;
import com.dnd.moddo.event.application.impl.MemberCreator;
import com.dnd.moddo.event.application.impl.SettlementCompletionProcessor;
import com.dnd.moddo.event.application.impl.SettlementCreator;
import com.dnd.moddo.event.application.impl.SettlementReader;
import com.dnd.moddo.event.application.impl.SettlementUpdater;
Expand All @@ -28,6 +29,7 @@ public class CommandSettlementService {
private final SettlementValidator settlementValidator;
private final SettlementReader settlementReader;
private final MemberCreator memberCreator;
private final SettlementCompletionProcessor settlementCompletionProcessor;

public SettlementSaveResponse createSettlement(SettlementRequest request, Long userId) {
Settlement settlement = settlementCreator.createSettlement(request, userId);
Expand All @@ -43,4 +45,12 @@ public SettlementResponse updateAccount(SettlementAccountRequest request, Long u
cacheEvictor.evictSettlementHeader(settlementId);
return SettlementResponse.of(settlement);
}

public void completeSettlement(Long userId, Long settlementId) {
Settlement settlement = settlementReader.read(settlementId);
settlementValidator.checkSettlementAuthor(settlement, userId);
settlementCompletionProcessor.complete(settlementId);
cacheEvictor.evictSettlementHeader(settlementId);
cacheEvictor.evictSettlementListsBySettlement(settlementId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ public PaymentRequestsResponse findByTargetUserId(Long targetUserId) {
return PaymentRequestsResponse.of(responses);
}

public boolean existsBySettlementId(Long settlementId) {
return paymentRequestRepository.existsBySettlementId(settlementId);
}

public Map<Long, Long> findPendingRequestIdByMemberId(Long settlementId) {
return paymentRequestRepository.findBySettlementIdAndStatus(settlementId, PaymentRequestStatus.PENDING)
.stream()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package com.dnd.moddo.event.application.impl;

import java.time.LocalDateTime;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.dnd.moddo.event.domain.settlement.Settlement;
import com.dnd.moddo.outbox.application.command.CommandOutboxEventService;
import com.dnd.moddo.outbox.domain.event.type.AggregateType;
import com.dnd.moddo.outbox.domain.event.type.OutboxEventType;
Expand All @@ -13,6 +16,7 @@
@RequiredArgsConstructor
public class SettlementCompletionProcessor {
private final MemberReader memberReader;
private final SettlementReader settlementReader;
private final SettlementUpdater settlementUpdater;
private final CommandOutboxEventService commandOutboxEventService;

Expand All @@ -22,8 +26,15 @@ public boolean completeIfAllPaid(Long settlementId) {
return false;
}

boolean completed = settlementUpdater.complete(settlementId);
if (completed) {
return complete(settlementId);
}

@Transactional
public boolean complete(Long settlementId) {
Settlement settlement = settlementReader.read(settlementId);
LocalDateTime completedAt = LocalDateTime.now();
boolean completed = settlementUpdater.complete(settlementId, completedAt);
if (completed && settlement.isCompletedWithinDeadline(completedAt)) {
commandOutboxEventService.create(OutboxEventType.SETTLEMENT_COMPLETED, AggregateType.SETTLEMENT,
Comment thread
coderabbitai[bot] marked this conversation as resolved.
settlementId);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.dnd.moddo.event.application.impl;

import java.time.LocalDateTime;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -21,7 +23,7 @@ public Settlement updateAccount(SettlementAccountRequest request, Long settlemen
return settlement;
}

public boolean complete(Long settlementId) {
return settlementRepository.markCompletedIfNotCompleted(settlementId) == 1;
public boolean complete(Long settlementId, LocalDateTime completedAt) {
return settlementRepository.markCompletedIfNotCompleted(settlementId, completedAt) == 1;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import org.springframework.stereotype.Service;

import com.dnd.moddo.event.application.impl.PaymentRequestReader;
import com.dnd.moddo.event.presentation.response.PaymentRequestExistenceResponse;
import com.dnd.moddo.event.presentation.response.PaymentRequestsResponse;

import lombok.RequiredArgsConstructor;
Expand All @@ -15,4 +16,8 @@ public class QueryPaymentRequestService {
public PaymentRequestsResponse findByTargetUserId(Long targetUserId) {
return paymentRequestReader.findByTargetUserId(targetUserId);
}

public PaymentRequestExistenceResponse existsBySettlementId(Long settlementId) {
return PaymentRequestExistenceResponse.of(paymentRequestReader.existsBySettlementId(settlementId));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,12 @@ public Settlement(String name, Long writer, LocalDateTime createdAt,
String bank, String accountNumber, String code, LocalDateTime deadline) {
this.name = name;
this.writer = writer;
this.createdAt = createdAt;
this.createdAt = createdAt != null ? createdAt : LocalDateTime.now();
this.expiredAt = LocalDateTime.now().plusMonths(1);
this.bank = bank;
this.accountNumber = accountNumber;
this.code = code;
this.deadline = deadline;
this.deadline = deadline != null ? deadline : this.createdAt.plusDays(1);
}

public void updateAccount(SettlementAccountRequest request) {
Expand All @@ -79,4 +79,8 @@ public boolean isWriter(Long userId) {
public void complete() {
this.completedAt = LocalDateTime.now();
}

public boolean isCompletedWithinDeadline(LocalDateTime completedAt) {
return deadline != null && !completedAt.isAfter(deadline);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ default PaymentRequest getById(Long paymentRequestId) {
@Query("select pr from PaymentRequest pr where pr.targetUser.id = :targetUserId")
List<PaymentRequest> findByTargetUserId(@Param("targetUserId") Long targetUserId);

@Query("""
select count(pr) > 0
from PaymentRequest pr
where pr.settlement.id = :settlementId
""")
boolean existsBySettlementId(@Param("settlementId") Long settlementId);

@Query("""
select pr
from PaymentRequest pr
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,14 @@ SELECT COUNT(s)
@Modifying
@Query("""
update Settlement s
set s.completedAt = CURRENT_TIMESTAMP
set s.completedAt = :completedAt
where s.id = :settlementId
and s.completedAt is null
""")
int markCompletedIfNotCompleted(@Param("settlementId") Long settlementId);
""")
int markCompletedIfNotCompleted(
@Param("settlementId") Long settlementId,
@Param("completedAt") LocalDateTime completedAt
);

default Long getIdByCode(String code) {
return findIdByCode(code).orElseThrow(() -> new GroupNotFoundException(code));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import com.dnd.moddo.event.application.command.CommandPaymentRequest;
import com.dnd.moddo.event.application.query.QueryPaymentRequestService;
import com.dnd.moddo.event.application.query.QuerySettlementService;
import com.dnd.moddo.event.presentation.response.PaymentRequestExistenceResponse;
import com.dnd.moddo.event.presentation.response.PaymentRequestResponse;
import com.dnd.moddo.event.presentation.response.PaymentRequestsResponse;

Expand Down Expand Up @@ -44,6 +45,15 @@ public ResponseEntity<PaymentRequestResponse> createPaymentRequest(
return ResponseEntity.ok(response);
}

@GetMapping("/groups/{code}/payments/exists")
public ResponseEntity<PaymentRequestExistenceResponse> existsPaymentRequest(
@PathVariable String code
) {
Long settlementId = querySettlementService.findIdByCode(code);
PaymentRequestExistenceResponse response = queryPaymentRequestService.existsBySettlementId(settlementId);
return ResponseEntity.ok(response);
}

@PatchMapping("/payments/{paymentRequestId}/approve")
public ResponseEntity<PaymentRequestResponse> approvePaymentRequest(
@PathVariable Long paymentRequestId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
Expand Down Expand Up @@ -62,6 +63,16 @@ public ResponseEntity<SettlementResponse> updateAccount(
return ResponseEntity.ok(response);
}

@PatchMapping("/{code}/complete")
public ResponseEntity<Void> completeSettlement(
@PathVariable("code") String code,
@LoginUser LoginUserInfo loginUser
) {
Long settlementId = querySettlementService.findIdByCode(code);
commandSettlementService.completeSettlement(loginUser.userId(), settlementId);
return ResponseEntity.ok().build();
}

@GetMapping("/{code}")
public ResponseEntity<SettlementDetailResponse> getSettlement(
HttpServletRequest request,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.dnd.moddo.event.presentation.response;

public record PaymentRequestExistenceResponse(
boolean exists
) {
public static PaymentRequestExistenceResponse of(boolean exists) {
return new PaymentRequestExistenceResponse(exists);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.dnd.moddo.auth.infrastructure.security.LoginUserArgumentResolver;
import com.dnd.moddo.auth.presentation.response.LoginUserInfo;
import com.dnd.moddo.event.domain.paymentRequest.PaymentRequestStatus;
import com.dnd.moddo.event.presentation.response.PaymentRequestExistenceResponse;
import com.dnd.moddo.event.presentation.response.PaymentRequestResponse;
import com.dnd.moddo.event.presentation.response.PaymentRequestsResponse;
import com.dnd.moddo.global.util.RestDocsTestSupport;
Expand Down Expand Up @@ -71,6 +72,29 @@ void getPaymentRequests() throws Exception {
));
}

@Test
@DisplayName("모임에 생성된 입금 확인 요청이 있는지 확인한다.")
void existsPaymentRequest() throws Exception {
String code = "code";
Long settlementId = 1L;
PaymentRequestExistenceResponse response = new PaymentRequestExistenceResponse(true);

when(querySettlementService.findIdByCode(code)).thenReturn(settlementId);
when(queryPaymentRequestService.existsBySettlementId(settlementId)).thenReturn(response);

mockMvc.perform(get("/api/v1/groups/{code}/payments/exists", code))
.andExpect(status().isOk())
.andExpect(jsonPath("$.exists").value(true))
.andDo(restDocs.document(
pathParameters(
parameterWithName("code").description("정산 코드")
),
responseFields(
fieldWithPath("exists").type(JsonFieldType.BOOLEAN).description("모임에 생성된 입금 확인 요청 존재 여부")
)
));
}

@Test
@DisplayName("입금 확인 요청을 생성한다.")
void createPaymentRequest() throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

import com.dnd.moddo.event.application.impl.PaymentRequestReader;
import com.dnd.moddo.event.application.query.QueryPaymentRequestService;
import com.dnd.moddo.event.presentation.response.PaymentRequestExistenceResponse;
import com.dnd.moddo.event.presentation.response.PaymentRequestItemResponse;
import com.dnd.moddo.event.presentation.response.PaymentRequestsResponse;

Expand Down Expand Up @@ -47,4 +48,15 @@ void findByTargetUserId() {
assertThat(result).isEqualTo(expected);
verify(paymentRequestReader).findByTargetUserId(1L);
}

@Test
@DisplayName("정산에 생성된 입금 확인 요청이 있는지 확인할 수 있다.")
void existsBySettlementId() {
when(paymentRequestReader.existsBySettlementId(1L)).thenReturn(true);

PaymentRequestExistenceResponse result = queryPaymentRequestService.existsBySettlementId(1L);

assertThat(result.exists()).isTrue();
verify(paymentRequestReader).existsBySettlementId(1L);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,17 @@ void findPendingRequestIdByMemberId() {
assertThat(result).containsEntry(11L, 100L);
}

@Test
@DisplayName("정산에 생성된 입금 확인 요청이 있는지 확인할 수 있다.")
void existsBySettlementId() {
when(paymentRequestRepository.existsBySettlementId(1L)).thenReturn(true);

boolean result = paymentRequestReader.existsBySettlementId(1L);

assertThat(result).isTrue();
verify(paymentRequestRepository).existsBySettlementId(1L);
}

@Test
@DisplayName("같은 멤버의 대기 중인 입금 확인 요청이 중복되면 예외가 발생한다.")
void findPendingRequestIdByMemberIdFailWhenDuplicatePendingRequest() {
Expand Down
Loading
Loading