Skip to content
2 changes: 2 additions & 0 deletions src/docs/asciidoc/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ include::settlement.adoc[]

include::member.adoc[]

include::payment.adoc[]

include::image.adoc[]

include::memberExpenses.adoc[]
105 changes: 105 additions & 0 deletions src/docs/asciidoc/payment.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
= 입금 확인 요청 (PaymentRequest)
:toc: left
:toclevels: 2

== 입금 확인 요청 목록 조회

로그인한 사용자를 대상으로 들어온 입금 확인 요청 목록을 조회할 수 있습니다.

=== Example

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

=== HTTP

==== 요청

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

==== 응답

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

=== Body

==== 응답

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

== 입금 확인 요청 생성

정산 참여자가 총무에게 입금 확인 요청을 보낼 수 있습니다.

=== Example

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

=== HTTP

==== 요청

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

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

==== 응답

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

=== Body

==== 응답

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

== 입금 확인 요청 승인

총무가 입금 확인 요청을 승인할 수 있습니다.

=== Example

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

=== HTTP

==== 요청

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

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

==== 응답

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

=== Body

==== 응답

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

== 입금 확인 요청 거절

총무가 입금 확인 요청을 거절할 수 있습니다.

=== Example

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

=== HTTP

==== 요청

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

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

==== 응답

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

=== Body

==== 응답

include::{snippets}/payment-request-controller-test/reject-payment-request/response-body.adoc[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.dnd.moddo.event.application.command;

import org.springframework.stereotype.Service;

import com.dnd.moddo.event.application.impl.PaymentRequestCreator;
import com.dnd.moddo.event.application.impl.PaymentRequestUpdater;
import com.dnd.moddo.event.domain.paymentRequest.PaymentRequest;
import com.dnd.moddo.event.presentation.response.PaymentRequestResponse;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Service
public class CommandPaymentRequest {
private final PaymentRequestCreator paymentRequestCreator;
private final PaymentRequestUpdater paymentRequestUpdater;

public PaymentRequestResponse createPaymentRequest(Long settlementId, Long userId) {
PaymentRequest paymentRequest = paymentRequestCreator.createPaymentRequest(settlementId, userId);
return PaymentRequestResponse.of(paymentRequest);
}

public PaymentRequestResponse approvePaymentRequest(Long paymentRequestId, Long userId) {
PaymentRequest paymentRequest = paymentRequestUpdater.approvePaymentRequest(paymentRequestId, userId);
return PaymentRequestResponse.of(paymentRequest);
}

public PaymentRequestResponse rejectPaymentRequest(Long paymentRequestId, Long userId) {
PaymentRequest paymentRequest = paymentRequestUpdater.rejectPaymentRequest(paymentRequestId, userId);
return PaymentRequestResponse.of(paymentRequest);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,22 @@ public List<MemberExpense> findAllByExpenseId(Long expenseId) {
return memberExpenseRepository.findByExpenseId(expenseId);
}

public List<MemberExpense> findAllByAppointMemberIds(List<Long> appointMemberIds) {
return memberExpenseRepository.findAllByAppointmentMemberIds(appointMemberIds);
public List<MemberExpense> findAllByAppointMemberIds(List<Long> memberIds) {
return memberExpenseRepository.findAllByAppointmentMemberIds(memberIds);
}

public List<MemberExpense> findAllByExpenseIds(List<Long> expenseIds) {
return memberExpenseRepository.findAllByExpenseIds(expenseIds);
}

public List<MemberExpense> findAllByMemberId(Long memberId) {
return memberExpenseRepository.findByMemberId(memberId);
}

public List<MemberExpense> findAllByMemberIds(List<Long> memberIds) {
if (memberIds == null || memberIds.isEmpty()) {
return List.of();
}
return memberExpenseRepository.findAllByMemberIds(memberIds);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.springframework.transaction.annotation.Transactional;

import com.dnd.moddo.event.domain.member.Member;
import com.dnd.moddo.event.domain.member.exception.MemberNotFoundException;
import com.dnd.moddo.event.domain.member.type.MemberSortType;
import com.dnd.moddo.event.infrastructure.MemberQueryRepository;
import com.dnd.moddo.event.infrastructure.MemberRepository;
Expand Down Expand Up @@ -35,4 +36,9 @@ public List<Long> findIdsBySettlementId(Long settlementId) {
return memberRepository.findMemberIdsBySettlementId(settlementId);
}

public Member findBySettlementIdAndUserId(Long settlementId, Long userId) {
return memberRepository.findBySettlementIdAndUserId(settlementId, userId)
.orElseThrow(() -> new MemberNotFoundException(userId));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import com.dnd.moddo.event.domain.member.exception.MemberSelectionNotAllowedException;
import com.dnd.moddo.event.domain.member.exception.MemberSelectionUnauthorizedException;
import com.dnd.moddo.event.domain.member.exception.PaymentConcurrencyException;
import com.dnd.moddo.event.domain.member.exception.UserAlreadyAssignedException;
import com.dnd.moddo.event.domain.settlement.Settlement;
import com.dnd.moddo.event.infrastructure.MemberRepository;
import com.dnd.moddo.event.presentation.request.MemberSaveRequest;
Expand Down Expand Up @@ -61,10 +60,15 @@ public Member addToSettlement(Long settlementId, MemberSaveRequest request) {

@Transactional(isolation = Isolation.READ_COMMITTED)
public Member updatePaymentStatus(Long appointmentMemberId, PaymentStatusUpdateRequest request) {
return updatePaymentStatus(appointmentMemberId, request.isPaid());
}

@Transactional(isolation = Isolation.READ_COMMITTED)
public Member updatePaymentStatus(Long appointmentMemberId, boolean isPaid) {
try {
Member member = memberRepository.getById(appointmentMemberId);
if (member.isPaid() != request.isPaid()) {
member.updatePaymentStatus(request.isPaid());
if (member.isPaid() != isPaid) {
member.updatePaymentStatus(isPaid);
memberRepository.save(member);
}
return member;
Expand All @@ -75,20 +79,29 @@ public Member updatePaymentStatus(Long appointmentMemberId, PaymentStatusUpdateR

@Transactional
public Member assignMember(Long settlementId, Long memberId, Long userId) {
Member member = memberRepository.getById(memberId);
validateMemberBelongsToSettlement(member, settlementId);
validateSelectable(member);
Member targetMember = memberRepository.getById(memberId);
validateMemberBelongsToSettlement(targetMember, settlementId);
validateSelectable(targetMember);

if (memberRepository.existsBySettlementIdAndUserId(settlementId, userId)) {
throw new UserAlreadyAssignedException(userId);
}
if (member.isAssigned()) {
throw new MemberAlreadyAssignedException(memberId);
if (targetMember.isAssigned()) {
if (targetMember.isAssignedTo(userId)) {
return targetMember;
}
throw new MemberAlreadyAssignedException();
}

memberRepository.findBySettlementIdAndUserId(settlementId, userId)
.ifPresent(member -> {
if (member.isManager()) {
throw new MemberSelectionNotAllowedException(member.getId());
}
member.unassignUser(userId);
});

User user = userRepository.getById(userId);
member.assignUser(user);
return memberRepository.save(member);
targetMember.assignUser(user);

return memberRepository.save(targetMember);
}

@Transactional
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.dnd.moddo.event.application.impl;

import org.springframework.stereotype.Service;

import com.dnd.moddo.event.domain.member.Member;
import com.dnd.moddo.event.domain.paymentRequest.PaymentRequest;
import com.dnd.moddo.event.domain.settlement.Settlement;
import com.dnd.moddo.event.infrastructure.PaymentRequestRepository;
import com.dnd.moddo.user.application.impl.UserReader;
import com.dnd.moddo.user.domain.User;

import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class PaymentRequestCreator {
private final PaymentRequestRepository paymentRequestRepository;
private final MemberReader memberReader;
private final SettlementReader settlementReader;
private final UserReader userReader;
private final PaymentRequestValidator paymentRequestValidator;

public PaymentRequest createPaymentRequest(Long settlementId, Long userId) {
Member requestMember = memberReader.findBySettlementIdAndUserId(settlementId, userId);
Settlement settlement = settlementReader.read(settlementId);

paymentRequestValidator.validateCreateRequest(settlementId, requestMember);

User targetUser = userReader.read(settlement.getWriter());

PaymentRequest paymentRequest = PaymentRequest.builder()
.settlement(settlement)
.requestMember(requestMember)
.targetUser(targetUser)
.build();

return paymentRequestRepository.save(paymentRequest);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package com.dnd.moddo.event.application.impl;

import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

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

import com.dnd.moddo.event.domain.member.Member;
import com.dnd.moddo.event.domain.memberExpense.MemberExpense;
import com.dnd.moddo.event.domain.paymentRequest.PaymentRequest;
import com.dnd.moddo.event.domain.paymentRequest.PaymentRequestStatus;
import com.dnd.moddo.event.infrastructure.PaymentRequestRepository;
import com.dnd.moddo.event.presentation.response.PaymentRequestItemResponse;
import com.dnd.moddo.event.presentation.response.PaymentRequestsResponse;

import lombok.RequiredArgsConstructor;

@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class PaymentRequestReader {
private final PaymentRequestRepository paymentRequestRepository;
private final MemberExpenseReader memberExpenseReader;

public PaymentRequestsResponse findByTargetUserId(Long targetUserId) {
List<PaymentRequest> paymentRequests = paymentRequestRepository.findByTargetUserId(targetUserId)
.stream().filter(paymentRequest -> paymentRequest.getStatus() == PaymentRequestStatus.PENDING)
.toList();

List<Long> memberIds = paymentRequests.stream()
.map(PaymentRequest::getRequestMemberId)
.distinct()
.toList();

Map<Long, Long> amountByMemberId = memberExpenseReader.findAllByMemberIds(memberIds).stream()
.collect(Collectors.groupingBy(
MemberExpense::getMemberId,
Collectors.summingLong(me -> me.getAmount() != null ? me.getAmount() : 0L)
));

List<PaymentRequestItemResponse> responses = paymentRequests.stream()
.map(paymentRequest -> {
Member member = paymentRequest.getRequestMember();
Long memberId = member.getId();

return new PaymentRequestItemResponse(
paymentRequest.getRequestedAt(),
paymentRequest.getId(),
memberId,
member.getName(),
member.getProfileUrl(),
amountByMemberId.getOrDefault(memberId, 0L)
);
})
.sorted(Comparator.comparing(PaymentRequestItemResponse::requestedAt).reversed())
.toList();

return PaymentRequestsResponse.of(responses);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.dnd.moddo.event.application.impl;

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

import com.dnd.moddo.event.domain.paymentRequest.PaymentRequest;
import com.dnd.moddo.event.infrastructure.PaymentRequestRepository;

import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class PaymentRequestUpdater {
private final PaymentRequestRepository paymentRequestRepository;
private final PaymentRequestValidator paymentRequestValidator;
private final MemberUpdater memberUpdater;

@Transactional
public PaymentRequest approvePaymentRequest(Long paymentRequestId, Long userId) {
PaymentRequest paymentRequest = paymentRequestRepository.getById(paymentRequestId);
paymentRequestValidator.validateProcessRequest(paymentRequest, userId);
memberUpdater.updatePaymentStatus(paymentRequest.getRequestMemberId(), true);
paymentRequest.approve();
return paymentRequest;
}

@Transactional
public PaymentRequest rejectPaymentRequest(Long paymentRequestId, Long userId) {
PaymentRequest paymentRequest = paymentRequestRepository.getById(paymentRequestId);
paymentRequestValidator.validateProcessRequest(paymentRequest, userId);
paymentRequest.reject();
return paymentRequest;
}
}
Loading
Loading