-
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] 참여자별 정산 현황 조회 기능 구현 #77
Changes from all commits
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,38 @@ | ||
package server.haengdong.application; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.stereotype.Service; | ||
import server.haengdong.application.response.MemberBillReportAppResponse; | ||
import server.haengdong.domain.action.BillAction; | ||
import server.haengdong.domain.action.BillActionRepository; | ||
import server.haengdong.domain.action.MemberAction; | ||
import server.haengdong.domain.action.MemberActionRepository; | ||
import server.haengdong.domain.action.MemberBillReports; | ||
import server.haengdong.domain.event.Event; | ||
import server.haengdong.domain.event.EventRepository; | ||
|
||
@RequiredArgsConstructor | ||
@Service | ||
public class ActionService { | ||
|
||
private final BillActionRepository billActionRepository; | ||
private final MemberActionRepository memberActionRepository; | ||
private final EventRepository eventRepository; | ||
|
||
public List<MemberBillReportAppResponse> getMemberBillReports(String token) { | ||
Event event = eventRepository.findByToken(token) | ||
.orElseThrow(() -> new IllegalArgumentException("event not found")); | ||
List<BillAction> billActions = billActionRepository.findByAction_Event(event); | ||
List<MemberAction> memberActions = memberActionRepository.findAllByEvent(event); | ||
|
||
MemberBillReports memberBillReports = MemberBillReports.createByActions(billActions, memberActions); | ||
|
||
List<MemberBillReportAppResponse> memberBillReportResponses = new ArrayList<>(); | ||
memberBillReports.getReports().forEach( | ||
(member, price) -> memberBillReportResponses.add(new MemberBillReportAppResponse(member, price)) | ||
); | ||
return memberBillReportResponses; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
package server.haengdong.application.response; | ||
|
||
public record MemberBillReportAppResponse(String name, Long price) { | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,83 @@ | ||||||||||||||||||||||||||
package server.haengdong.domain.action; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
import static java.util.stream.Collectors.toMap; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
import java.util.HashSet; | ||||||||||||||||||||||||||
import java.util.List; | ||||||||||||||||||||||||||
import java.util.Map; | ||||||||||||||||||||||||||
import java.util.PriorityQueue; | ||||||||||||||||||||||||||
import java.util.Set; | ||||||||||||||||||||||||||
import java.util.function.Function; | ||||||||||||||||||||||||||
import lombok.Getter; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
@Getter | ||||||||||||||||||||||||||
public class MemberBillReports { | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
private final Map<String, Long> reports; | ||||||||||||||||||||||||||
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. R: Long을 vo로 분리하면 좋을 것 같아요. Money, MemberName 같이요 :) |
||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
private MemberBillReports(Map<String, Long> reports) { | ||||||||||||||||||||||||||
this.reports = reports; | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
Comment on lines
+18
to
+20
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. r: #70 pr에 있는 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. 저도 순수 자바 객체에는 롬복을 쓸 필요가 없어보입니다. POJO를 지향하는 것이 더 읽기 쉽고 유지보수에 좋을 것 같아요. |
||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
public static MemberBillReports createByActions(List<BillAction> billActions, List<MemberAction> memberActions) { | ||||||||||||||||||||||||||
PriorityQueue<BillAction> sortedBillActions = new PriorityQueue<>(billActions); | ||||||||||||||||||||||||||
PriorityQueue<MemberAction> sortedMemberActions = new PriorityQueue<>(memberActions); | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
Map<String, Long> memberBillReports = initReports(memberActions); | ||||||||||||||||||||||||||
Set<String> currentMembers = new HashSet<>(); | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
while (!sortedBillActions.isEmpty() && !sortedMemberActions.isEmpty()) { | ||||||||||||||||||||||||||
if (isMemberActionTurn(sortedMemberActions, sortedBillActions)) { | ||||||||||||||||||||||||||
addMemberAction(sortedMemberActions, currentMembers); | ||||||||||||||||||||||||||
continue; | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
addBillAction(sortedBillActions, currentMembers, memberBillReports); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
while (!sortedBillActions.isEmpty()) { | ||||||||||||||||||||||||||
addBillAction(sortedBillActions, currentMembers, memberBillReports); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
return new MemberBillReports(memberBillReports); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
private static Map<String, Long> initReports(List<MemberAction> memberActions) { | ||||||||||||||||||||||||||
return memberActions.stream() | ||||||||||||||||||||||||||
.map(MemberAction::getMemberName) | ||||||||||||||||||||||||||
.distinct() | ||||||||||||||||||||||||||
.collect(toMap(Function.identity(), i -> 0L)); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
private static boolean isMemberActionTurn( | ||||||||||||||||||||||||||
PriorityQueue<MemberAction> memberActions, | ||||||||||||||||||||||||||
PriorityQueue<BillAction> billActions | ||||||||||||||||||||||||||
) { | ||||||||||||||||||||||||||
MemberAction memberAction = memberActions.peek(); | ||||||||||||||||||||||||||
BillAction billAction = billActions.peek(); | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
return memberAction.getSequence() < billAction.getSequence(); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
private static void addMemberAction(PriorityQueue<MemberAction> sortedMemberActions, Set<String> currentMembers) { | ||||||||||||||||||||||||||
MemberAction memberAction = sortedMemberActions.poll(); | ||||||||||||||||||||||||||
String memberName = memberAction.getMemberName(); | ||||||||||||||||||||||||||
if (memberAction.isSameStatus(MemberActionStatus.IN)) { | ||||||||||||||||||||||||||
currentMembers.add(memberName); | ||||||||||||||||||||||||||
return; | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
currentMembers.remove(memberAction.getMemberName()); | ||||||||||||||||||||||||||
Comment on lines
+63
to
+68
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.
Suggested change
r: 만들어 놓은 변수 사용하면 될 것 같아요. |
||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
private static void addBillAction( | ||||||||||||||||||||||||||
PriorityQueue<BillAction> sortedBillActions, | ||||||||||||||||||||||||||
Set<String> currentMembers, | ||||||||||||||||||||||||||
Map<String, Long> memberBillReports | ||||||||||||||||||||||||||
) { | ||||||||||||||||||||||||||
BillAction billAction = sortedBillActions.poll(); | ||||||||||||||||||||||||||
Long pricePerMember = billAction.getPrice() / currentMembers.size(); | ||||||||||||||||||||||||||
for (String currentMember : currentMembers) { | ||||||||||||||||||||||||||
Long price = memberBillReports.getOrDefault(currentMember, 0L) + pricePerMember; | ||||||||||||||||||||||||||
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. r: 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.
|
||||||||||||||||||||||||||
memberBillReports.put(currentMember, price); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package server.haengdong.presentation; | ||
|
||
import java.util.List; | ||
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.RestController; | ||
import server.haengdong.application.ActionService; | ||
import server.haengdong.application.response.MemberBillReportAppResponse; | ||
import server.haengdong.presentation.response.MemberBillReportsResponse; | ||
|
||
@RequiredArgsConstructor | ||
@RestController | ||
public class ActionController { | ||
|
||
private final ActionService actionService; | ||
|
||
@GetMapping("/api/events/{token}/actions/reports") | ||
public ResponseEntity<MemberBillReportsResponse> getMemberBillReports(@PathVariable("token") String token) { | ||
List<MemberBillReportAppResponse> memberBillReports = actionService.getMemberBillReports(token); | ||
|
||
return ResponseEntity.ok() | ||
.body(MemberBillReportsResponse.of(memberBillReports)); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package server.haengdong.presentation.response; | ||
|
||
import server.haengdong.application.response.MemberBillReportAppResponse; | ||
|
||
public record MemberBillReportResponse(String name, Long price) { | ||
|
||
public static MemberBillReportResponse of(MemberBillReportAppResponse memberBillReportAppResponse) { | ||
return new MemberBillReportResponse(memberBillReportAppResponse.name(), memberBillReportAppResponse.price()); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package server.haengdong.presentation.response; | ||
|
||
import java.util.List; | ||
import server.haengdong.application.response.MemberBillReportAppResponse; | ||
|
||
public record MemberBillReportsResponse(List<MemberBillReportResponse> reports) { | ||
|
||
public static MemberBillReportsResponse of(List<MemberBillReportAppResponse> memberBillReports) { | ||
List<MemberBillReportResponse> reports = memberBillReports.stream() | ||
.map(MemberBillReportResponse::of) | ||
.toList(); | ||
|
||
return new MemberBillReportsResponse(reports); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
package server.haengdong.application; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
import static org.assertj.core.api.Assertions.tuple; | ||
import static server.haengdong.domain.action.MemberActionStatus.IN; | ||
import static server.haengdong.domain.action.MemberActionStatus.OUT; | ||
|
||
import java.util.List; | ||
import org.junit.jupiter.api.DisplayName; | ||
import org.junit.jupiter.api.Test; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.boot.test.context.SpringBootTest; | ||
import server.haengdong.application.response.MemberBillReportAppResponse; | ||
import server.haengdong.domain.action.Action; | ||
import server.haengdong.domain.action.BillAction; | ||
import server.haengdong.domain.action.BillActionRepository; | ||
import server.haengdong.domain.action.MemberAction; | ||
import server.haengdong.domain.action.MemberActionRepository; | ||
import server.haengdong.domain.event.Event; | ||
import server.haengdong.domain.event.EventRepository; | ||
|
||
@SpringBootTest | ||
class ActionServiceTest { | ||
|
||
@Autowired | ||
private ActionService actionService; | ||
|
||
@Autowired | ||
private EventRepository eventRepository; | ||
|
||
@Autowired | ||
private BillActionRepository billActionRepository; | ||
|
||
@Autowired | ||
private MemberActionRepository memberActionRepository; | ||
|
||
@DisplayName("참여자별 정산 현황을 조회한다.") | ||
@Test | ||
void getMemberBillReports() { | ||
String token = "tOkEn1"; | ||
Event event = new Event("행동대장", token); | ||
Event savedEvent = eventRepository.save(event); | ||
List<MemberAction> memberActions = List.of( | ||
new MemberAction(new Action(savedEvent, 1L), "소하", IN, 1L), | ||
new MemberAction(new Action(savedEvent, 2L), "감자", IN, 1L), | ||
new MemberAction(new Action(savedEvent, 3L), "쿠키", IN, 1L), | ||
new MemberAction(new Action(savedEvent, 5L), "감자", OUT, 2L) | ||
); | ||
List<BillAction> billActions = List.of( | ||
new BillAction(new Action(savedEvent, 4L), "뽕족", 60_000L), | ||
new BillAction(new Action(savedEvent, 6L), "인생맥주", 40_000L), | ||
new BillAction(new Action(savedEvent, 7L), "인생네컷", 20_000L) | ||
); | ||
memberActionRepository.saveAll(memberActions); | ||
billActionRepository.saveAll(billActions); | ||
|
||
List<MemberBillReportAppResponse> responses = actionService.getMemberBillReports(token); | ||
|
||
assertThat(responses) | ||
.hasSize(3) | ||
.extracting(MemberBillReportAppResponse::name, MemberBillReportAppResponse::price) | ||
.containsExactlyInAnyOrder( | ||
tuple("감자", 20_000L), | ||
tuple("쿠키", 50_000L), | ||
tuple("소하", 50_000L) | ||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package server.haengdong.domain.action; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
import static server.haengdong.domain.action.MemberActionStatus.IN; | ||
import static server.haengdong.domain.action.MemberActionStatus.OUT; | ||
|
||
import java.util.List; | ||
import java.util.Map; | ||
import org.junit.jupiter.api.DisplayName; | ||
import org.junit.jupiter.api.Test; | ||
import server.haengdong.domain.event.Event; | ||
|
||
class MemberBillReportsTest { | ||
|
||
@DisplayName("액션 목록으로 참가자 정산 리포트를 생성한다.") | ||
@Test | ||
void createByActions() { | ||
String token = "TOK2N"; | ||
Event event = new Event("행동대장", token); | ||
List<BillAction> billActions = List.of( | ||
new BillAction(new Action(event, 4L), "뽕족", 60_000L), | ||
new BillAction(new Action(event, 6L), "인생맥주", 40_000L), | ||
new BillAction(new Action(event, 7L), "인생네컷", 20_000L) | ||
); | ||
List<MemberAction> memberActions = List.of( | ||
new MemberAction(new Action(event, 1L), "소하", IN, 1L), | ||
new MemberAction(new Action(event, 2L), "감자", IN, 1L), | ||
new MemberAction(new Action(event, 3L), "쿠키", IN, 1L), | ||
new MemberAction(new Action(event, 5L), "감자", OUT, 2L) | ||
); | ||
|
||
MemberBillReports memberBillReports = MemberBillReports.createByActions(billActions, memberActions); | ||
|
||
assertThat(memberBillReports.getReports()) | ||
.containsAllEntriesOf( | ||
Map.of( | ||
"감자", 20_000L, | ||
"쿠키", 50_000L, | ||
"소하", 50_000L | ||
) | ||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package server.haengdong.presentation; | ||
|
||
import static org.hamcrest.Matchers.equalTo; | ||
import static org.mockito.ArgumentMatchers.any; | ||
import static org.mockito.BDDMockito.given; | ||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; | ||
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; | ||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; | ||
|
||
import java.util.List; | ||
import org.junit.jupiter.api.DisplayName; | ||
import org.junit.jupiter.api.Test; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; | ||
import org.springframework.boot.test.mock.mockito.MockBean; | ||
import org.springframework.http.MediaType; | ||
import org.springframework.test.web.servlet.MockMvc; | ||
import org.springframework.test.web.servlet.result.MockMvcResultMatchers; | ||
import server.haengdong.application.ActionService; | ||
import server.haengdong.application.response.MemberBillReportAppResponse; | ||
|
||
@WebMvcTest(ActionController.class) | ||
class ActionControllerTest { | ||
|
||
@Autowired | ||
private MockMvc mockMvc; | ||
|
||
@MockBean | ||
private ActionService actionService; | ||
|
||
@DisplayName("참여자별 정산 현황을 조회한다.") | ||
@Test | ||
void getMemberBillReports() throws Exception { | ||
List<MemberBillReportAppResponse> memberBillReportAppResponses = List.of( | ||
new MemberBillReportAppResponse("소하", 20_000L), new MemberBillReportAppResponse("토다리", 200_000L)); | ||
|
||
given(actionService.getMemberBillReports(any())).willReturn(memberBillReportAppResponses); | ||
|
||
mockMvc.perform(get("/api/events/{token}/actions/reports", "token") | ||
.accept(MediaType.APPLICATION_JSON)) | ||
.andDo(print()) | ||
.andExpect(status().isOk()) | ||
.andExpect(MockMvcResultMatchers.jsonPath("$.reports[0].name").value(equalTo("소하"))) | ||
.andExpect(MockMvcResultMatchers.jsonPath("$.reports[0].price").value(equalTo(20_000))) | ||
.andExpect(MockMvcResultMatchers.jsonPath("$.reports[1].name").value(equalTo("토다리"))) | ||
.andExpect(MockMvcResultMatchers.jsonPath("$.reports[1].price").value(equalTo(200_000))); | ||
|
||
} | ||
} |
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.
A: 단수형은 어떤가요?