-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[BE] 참여자 개별 지출 금액 수정 및 조회 기능 구현 #378
Changes from all commits
9807d1c
d0260f8
627aebf
77f09ae
ded858c
3fa60f0
b116d7c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
== 지출 상세 | ||
|
||
=== 지출 상세 조회 | ||
|
||
operation::findBillActionDetailsTest[snippets="path-parameters,http-request,http-response,request-cookies"] | ||
|
||
==== [.red]#Exceptions# | ||
|
||
[source,json,options="nowrap"] | ||
---- | ||
[ | ||
{ | ||
"code": "EVENT_NOT_FOUND", | ||
"message": "존재하지 않는 행사입니다." | ||
}, | ||
{ | ||
"code": "BILL_ACTION_NOT_FOUND", | ||
"message": "존재하지 않는 지출 액션입니다." | ||
}, | ||
{ | ||
"code": "BILL_ACTION_DETAIL_NOT_FOUND", | ||
"message": "존재하지 않는 참여자 지출입니다." | ||
}, | ||
{ | ||
"code": "TOKEN_NOT_FOUND", | ||
"message": "토큰이 존재하지 않습니다." | ||
}, | ||
{ | ||
"code": "TOKEN_EXPIRED", | ||
"message": "만료된 토큰입니다." | ||
}, | ||
{ | ||
"code": "TOKEN_INVALID", | ||
"message": "유효하지 않은 토큰입니다." | ||
}, | ||
{ | ||
"code": "FORBIDDEN", | ||
"message": "접근할 수 없는 행사입니다." | ||
} | ||
] | ||
---- | ||
|
||
=== 지출 상세 수정 | ||
|
||
operation::updateBillActionDetailsTest[snippets="path-parameters,http-request,request-body,request-fields,http-response,request-cookies"] | ||
|
||
==== [.red]#Exceptions# | ||
|
||
[source,json,options="nowrap"] | ||
---- | ||
[ | ||
{ | ||
"code": "REQUEST_EMPTY", | ||
"message": "멤버 이름은 공백일 수 없습니다." | ||
}, | ||
{ | ||
"code": "REQUEST_EMPTY", | ||
"message": "지출 금액은 공백일 수 없습니다." | ||
}, | ||
{ | ||
"code": "EVENT_NOT_FOUND", | ||
"message": "존재하지 않는 행사입니다." | ||
}, | ||
{ | ||
"code": "BILL_ACTION_NOT_FOUND", | ||
"message": "존재하지 않는 지출 액션입니다." | ||
}, | ||
{ | ||
"code": "BILL_ACTION_DETAIL_NOT_FOUND", | ||
"message": "존재하지 않는 참여자 지출입니다." | ||
}, | ||
{ | ||
"code": "BILL_ACTION_PRICE_NOT_MATCHED", | ||
"message": "지출 총액이 일치하지 않습니다." | ||
}, | ||
{ | ||
"code": "TOKEN_NOT_FOUND", | ||
"message": "토큰이 존재하지 않습니다." | ||
}, | ||
{ | ||
"code": "TOKEN_EXPIRED", | ||
"message": "만료된 토큰입니다." | ||
}, | ||
{ | ||
"code": "TOKEN_INVALID", | ||
"message": "유효하지 않은 토큰입니다." | ||
}, | ||
{ | ||
"code": "FORBIDDEN", | ||
"message": "접근할 수 없는 행사입니다." | ||
} | ||
] | ||
---- |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,15 @@ | ||
:source-highlighter: highlightjs | ||
:hardbreaks: | ||
:toc: left | ||
:doctype: book | ||
:icons: font | ||
:toc-title: 전체 API 목록 | ||
:toclevels: 2 | ||
:sectlinks: | ||
ifndef::snippets[] | ||
:snippets: ../../build/generated-snippets | ||
endif::[] | ||
= 행동대장 | ||
:source-highlighter: highlightjs :hardbreaks: | ||
:toc: left :doctype: book :icons: font :toc-title: 전체 API 목록 :toclevels: 2 :sectlinks: | ||
:sectnums: | ||
:sectnumlevels: 2 | ||
|
||
= 행동대장 | ||
|
||
include::{docdir}/event.adoc[] | ||
include::{docdir}/memberBillReport.adoc[] | ||
include::{docdir}/memberAction.adoc[] | ||
include::{docdir}/billAction.adoc[] | ||
|
||
include::{docdir}/billActionDetail.adoc[] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,78 @@ | ||
package server.haengdong.application; | ||
|
||
import java.util.List; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
import server.haengdong.application.request.BillActionDetailUpdateAppRequest; | ||
import server.haengdong.application.request.BillActionDetailsUpdateAppRequest; | ||
import server.haengdong.application.response.BillActionDetailsAppResponse; | ||
import server.haengdong.domain.action.BillAction; | ||
import server.haengdong.domain.action.BillActionDetail; | ||
import server.haengdong.domain.action.BillActionDetailRepository; | ||
import server.haengdong.domain.action.BillActionRepository; | ||
import server.haengdong.domain.event.Event; | ||
import server.haengdong.exception.HaengdongErrorCode; | ||
import server.haengdong.exception.HaengdongException; | ||
|
||
@RequiredArgsConstructor | ||
@Transactional(readOnly = true) | ||
@Service | ||
public class BillActionDetailService { | ||
|
||
private final BillActionDetailRepository billActionDetailRepository; | ||
private final BillActionRepository billActionRepository; | ||
|
||
public BillActionDetailsAppResponse findBillActionDetails(String token, Long actionId) { | ||
BillAction billAction = billActionRepository.findByAction_Id(actionId) | ||
.orElseThrow(() -> new HaengdongException(HaengdongErrorCode.BILL_ACTION_NOT_FOUND)); | ||
validateToken(token, billAction); | ||
|
||
List<BillActionDetail> billActionDetails = billActionDetailRepository.findAllByBillAction(billAction); | ||
|
||
return BillActionDetailsAppResponse.of(billActionDetails); | ||
} | ||
|
||
@Transactional | ||
public void updateBillActionDetails(String token, Long actionId, BillActionDetailsUpdateAppRequest request) { | ||
BillAction billAction = billActionRepository.findByAction_Id(actionId) | ||
.orElseThrow(() -> new HaengdongException(HaengdongErrorCode.BILL_ACTION_NOT_FOUND)); | ||
|
||
List<BillActionDetailUpdateAppRequest> billActionDetailUpdateAppRequests = request.billActionDetailUpdateAppRequests(); | ||
|
||
validateToken(token, billAction); | ||
validateTotalPrice(billActionDetailUpdateAppRequests, billAction); | ||
|
||
List<BillActionDetail> billActionDetails = billActionDetailRepository.findAllByBillAction(billAction); | ||
|
||
for (BillActionDetailUpdateAppRequest updateRequest : billActionDetailUpdateAppRequests) { | ||
BillActionDetail detailToUpdate = billActionDetails.stream() | ||
.filter(detail -> detail.isSameName(updateRequest.name())) | ||
.findFirst() | ||
.orElseThrow(() -> new HaengdongException(HaengdongErrorCode.BILL_ACTION_DETAIL_NOT_FOUND)); | ||
|
||
detailToUpdate.updatePrice(updateRequest.price()); | ||
} | ||
} | ||
|
||
private void validateToken(String token, BillAction billAction) { | ||
Event event = billAction.getEvent(); | ||
if (event.isTokenMismatch(token)) { | ||
throw new HaengdongException(HaengdongErrorCode.BILL_ACTION_NOT_FOUND); | ||
} | ||
} | ||
|
||
private void validateTotalPrice(List<BillActionDetailUpdateAppRequest> billActionDetailUpdateAppRequests, | ||
BillAction billAction) { | ||
Long requestsPriceSum = calculateUpdatePriceSum(billActionDetailUpdateAppRequests); | ||
if (!billAction.isSamePrice(requestsPriceSum)) { | ||
throw new HaengdongException(HaengdongErrorCode.BILL_ACTION_PRICE_NOT_MATCHED); | ||
} | ||
} | ||
|
||
private Long calculateUpdatePriceSum(List<BillActionDetailUpdateAppRequest> billActionDetailUpdateAppRequests) { | ||
return billActionDetailUpdateAppRequests.stream() | ||
.map(BillActionDetailUpdateAppRequest::price) | ||
.reduce(0L, Long::sum); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package server.haengdong.application.request; | ||
|
||
public record BillActionDetailUpdateAppRequest( | ||
String name, | ||
Long price | ||
) { | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package server.haengdong.application.request; | ||
|
||
import java.util.List; | ||
|
||
public record BillActionDetailsUpdateAppRequest( | ||
List<BillActionDetailUpdateAppRequest> billActionDetailUpdateAppRequests | ||
) { | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package server.haengdong.application.response; | ||
|
||
import server.haengdong.domain.action.BillActionDetail; | ||
|
||
public record BillActionDetailAppResponse( | ||
String name, | ||
Long price | ||
) { | ||
|
||
public static BillActionDetailAppResponse of(BillActionDetail billActionDetail) { | ||
return new BillActionDetailAppResponse(billActionDetail.getMemberName(), billActionDetail.getPrice()); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package server.haengdong.application.response; | ||
|
||
import java.util.List; | ||
import java.util.stream.Collectors; | ||
import server.haengdong.domain.action.BillActionDetail; | ||
|
||
public record BillActionDetailsAppResponse(List<BillActionDetailAppResponse> billActionDetailAppResponses) { | ||
|
||
public static BillActionDetailsAppResponse of(List<BillActionDetail> billActionDetails) { | ||
return billActionDetails.stream() | ||
.map(BillActionDetailAppResponse::of) | ||
.collect(Collectors.collectingAndThen(Collectors.toList(), BillActionDetailsAppResponse::new)); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,13 +2,19 @@ | |
|
||
import java.util.List; | ||
import org.springframework.data.jpa.repository.JpaRepository; | ||
import org.springframework.data.jpa.repository.Query; | ||
import org.springframework.stereotype.Repository; | ||
import server.haengdong.domain.event.Event; | ||
|
||
@Repository | ||
public interface BillActionDetailRepository extends JpaRepository<BillActionDetail, Long> { | ||
|
||
List<BillActionDetail> findByBillAction(BillAction billAction); | ||
@Query(""" | ||
select bd | ||
from BillActionDetail bd | ||
where bd.billAction = :billAction | ||
""") | ||
Comment on lines
+12
to
+16
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. query creation이 편하긴 해도 작성한 메서드의 쿼리가 어떻게 나가는지 직관적으로 알 수 있어서 좋다고 생각합니다. |
||
List<BillActionDetail> findAllByBillAction(BillAction billAction); | ||
|
||
void deleteAllByBillAction(BillAction billAction); | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,6 +35,8 @@ public enum HaengdongErrorCode { | |
BillAction.MIN_TITLE_LENGTH, | ||
BillAction.MAX_TITLE_LENGTH)), | ||
BILL_ACTION_PRICE_INVALID(String.format("지출 금액은 %,d 이하의 자연수여야 합니다.", BillAction.MAX_PRICE)), | ||
BILL_ACTION_DETAIL_NOT_FOUND("존재하지 않는 참여자 지출입니다."), | ||
BILL_ACTION_PRICE_NOT_MATCHED("지출 총액이 일치하지 않습니다."), | ||
Comment on lines
+38
to
+39
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 노션 문서도 최신화 해주셨네요 👍 |
||
|
||
/* Authentication */ | ||
|
||
|
@@ -55,7 +57,8 @@ public enum HaengdongErrorCode { | |
MESSAGE_NOT_READABLE("읽을 수 없는 요청입니다."), | ||
REQUEST_METHOD_NOT_SUPPORTED("지원하지 않는 요청 메서드입니다."), | ||
NO_RESOURCE_REQUEST("존재하지 않는 자원입니다."), | ||
INTERNAL_SERVER_ERROR("서버 내부에서 에러가 발생했습니다."); | ||
INTERNAL_SERVER_ERROR("서버 내부에서 에러가 발생했습니다."), | ||
; | ||
|
||
private final String message; | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,42 @@ | ||
package server.haengdong.presentation; | ||
|
||
import jakarta.validation.Valid; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.web.bind.annotation.GetMapping; | ||
import org.springframework.web.bind.annotation.PathVariable; | ||
import org.springframework.web.bind.annotation.PutMapping; | ||
import org.springframework.web.bind.annotation.RequestBody; | ||
import org.springframework.web.bind.annotation.RestController; | ||
import server.haengdong.application.BillActionDetailService; | ||
import server.haengdong.application.response.BillActionDetailsAppResponse; | ||
import server.haengdong.presentation.request.BillActionDetailsUpdateRequest; | ||
import server.haengdong.presentation.response.BillActionDetailsResponse; | ||
|
||
@RequiredArgsConstructor | ||
@RestController | ||
public class BillActionDetailController { | ||
|
||
private final BillActionDetailService billActionDetailService; | ||
|
||
@GetMapping("/api/events/{eventId}/bill-actions/{actionId}/fixed") | ||
public ResponseEntity<BillActionDetailsResponse> findBillActionDetails( | ||
@PathVariable("eventId") String token, | ||
@PathVariable("actionId") Long actionId | ||
) { | ||
BillActionDetailsAppResponse appResponse = billActionDetailService.findBillActionDetails(token, actionId); | ||
|
||
return ResponseEntity.ok(BillActionDetailsResponse.of(appResponse)); | ||
} | ||
|
||
@PutMapping("/api/events/{eventId}/bill-actions/{actionId}/fixed") | ||
public ResponseEntity<Void> updateBillActionDetails( | ||
@PathVariable("eventId") String token, | ||
@PathVariable("actionId") Long actionId, | ||
@Valid @RequestBody BillActionDetailsUpdateRequest request | ||
) { | ||
billActionDetailService.updateBillActionDetails(token, actionId, request.toAppRequest()); | ||
|
||
return ResponseEntity.ok().build(); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
c: 프론트에서 주는 값을 Long으로 받고 있어서, 데이터 주고받는 과정에서 발생한 오차로도 예외가 발생할 텐데 어떻게 처리하면 좋을지 같이 얘기해 보면 좋을 것 같아요.