Skip to content

Commit 3f8b754

Browse files
authored
Merge pull request #38 from Nexters/dev
Dev
2 parents c3251bc + feeb6d3 commit 3f8b754

File tree

12 files changed

+259
-14
lines changed

12 files changed

+259
-14
lines changed

src/main/java/donmani/donmani_server/expense/controller/ExpenseController.java

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package donmani.donmani_server.expense.controller;
22

3+
import donmani.donmani_server.expense.dto.*;
34
import org.springframework.http.HttpStatus;
45
import org.springframework.http.ResponseEntity;
56
import org.springframework.web.bind.annotation.GetMapping;
@@ -11,12 +12,12 @@
1112
import org.springframework.web.bind.annotation.RestController;
1213

1314
import donmani.donmani_server.common.httpStatus.HttpStatusDTO;
14-
import donmani.donmani_server.expense.dto.ExpenseRequestDTO;
15-
import donmani.donmani_server.expense.dto.ExpenseResponseDTO;
1615
import donmani.donmani_server.expense.service.ExpenseService;
1716
import jakarta.persistence.EntityNotFoundException;
1817
import lombok.RequiredArgsConstructor;
1918

19+
import java.util.Map;
20+
2021
@RestController
2122
@RequiredArgsConstructor
2223
public class ExpenseController {
@@ -82,9 +83,27 @@ public ResponseEntity<HttpStatusDTO<Void>> addExpenseV2(@RequestBody ExpenseRequ
8283
HttpStatusDTO.response(HttpStatus.CREATED.value(), "성공", null));
8384
}
8485

86+
// 연도별 소비 기록 요약 조회
87+
@GetMapping("api/v1/expenses/summary/{userKey}")
88+
public ResponseEntity<ExpenseSummaryDTO> getYearlyExpenseSummary(
89+
@PathVariable String userKey, @RequestParam int year) {
90+
ExpenseSummaryDTO summary = expenseService.getYearlyExpenseSummary(userKey, year);
91+
return ResponseEntity.ok(summary);
92+
}
8593

94+
// 월별 소비 기록 통계 조회
95+
@GetMapping("api/v1/expenses/statistics/{userKey}")
96+
public ResponseEntity<ExpenseStatisticsDTO> getMonthlyExpenseStatistics(
97+
@PathVariable String userKey, @RequestParam int year, @RequestParam int month) {
98+
ExpenseStatisticsDTO statistics = expenseService.getMonthlyExpenseStatistics(userKey, year, month);
99+
return ResponseEntity.ok(statistics);
100+
}
86101

87-
88-
89-
102+
// 월별 카테고리 통계 조회
103+
@GetMapping("api/v1/expenses/category-statistics/{userKey}")
104+
public ResponseEntity<CategoryStatisticsDTO> getCategoryStatistics(
105+
@PathVariable String userKey, @RequestParam int year, @RequestParam int month) {
106+
CategoryStatisticsDTO categoryStatistics = expenseService.getCategoryStatistics(userKey, year, month);
107+
return ResponseEntity.ok(categoryStatistics);
108+
}
90109
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package donmani.donmani_server.expense.dto;
2+
3+
import com.fasterxml.jackson.annotation.JsonInclude;
4+
import donmani.donmani_server.expense.entity.CategoryType;
5+
import io.swagger.v3.oas.annotations.media.Schema;
6+
import lombok.*;
7+
8+
import java.util.Map;
9+
10+
@Getter
11+
@Setter
12+
@NoArgsConstructor
13+
@AllArgsConstructor
14+
@Builder
15+
@JsonInclude(JsonInclude.Include.NON_NULL)
16+
@Schema(description = "사용자별 월별 소비 카테고리 통계 DTO")
17+
public class CategoryStatisticsDTO {
18+
@Schema(description = "연도", example = "2025")
19+
private int year;
20+
21+
@Schema(description = "월", example = "2")
22+
private int month;
23+
24+
@Schema(description = "각 카테고리별 소비 기록 수", example = "{FOOD: 5, TRANSPORT: 3}")
25+
private Map<CategoryType, Integer> categoryCounts;
26+
}

src/main/java/donmani/donmani_server/expense/dto/ContentDTO.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import donmani.donmani_server.expense.entity.CategoryType;
66
import donmani.donmani_server.expense.entity.FlagType;
77

8+
import io.swagger.v3.oas.annotations.media.Schema;
89
import lombok.AllArgsConstructor;
910
import lombok.Builder;
1011
import lombok.Getter;
@@ -17,9 +18,16 @@
1718
@AllArgsConstructor
1819
@Builder
1920
@JsonInclude(JsonInclude.Include.NON_NULL) // null 값 제외
21+
@Schema(description = "소비 기록의 상세 항목 DTO")
2022
public class ContentDTO {
23+
24+
@Schema(description = "소비 유형", example = "GOOD")
2125
private FlagType flag;
26+
27+
@Schema(description = "소비 카테고리", example = "FOOD")
2228
private CategoryType category;
29+
30+
@Schema(description = "소비 메모", example = "Lunch at restaurant")
2331
private String memo;
2432
}
2533

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package donmani.donmani_server.expense.dto;
2+
3+
import com.fasterxml.jackson.annotation.JsonInclude;
4+
import io.swagger.v3.oas.annotations.media.Schema;
5+
import lombok.*;
6+
7+
import java.util.List;
8+
9+
@Getter
10+
@Setter
11+
@NoArgsConstructor
12+
@AllArgsConstructor
13+
@Builder
14+
@JsonInclude(JsonInclude.Include.NON_NULL)
15+
@Schema(description = "사용자별 월별 소비 기록 통계 DTO")
16+
public class ExpenseStatisticsDTO {
17+
@Schema(description = "연도", example = "2025")
18+
private int year;
19+
20+
@Schema(description = "월", example = "2")
21+
private int month;
22+
23+
@Schema(description = "행복한 소비 기록 수", example = "8")
24+
private int goodCount;
25+
26+
@Schema(description = "후회한 소비 기록 수", example = "2")
27+
private int badCount;
28+
29+
@Schema(description = "소비 기록이 존재하는지 여부", example = "true")
30+
private boolean hasRecords;
31+
32+
@Schema(description = "소비 기록 리스트")
33+
private List<RecordDTO> records;
34+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package donmani.donmani_server.expense.dto;
2+
3+
import io.swagger.v3.oas.annotations.media.Schema;
4+
import lombok.*;
5+
6+
import java.util.Map;
7+
8+
@Getter
9+
@Setter
10+
@NoArgsConstructor
11+
@AllArgsConstructor
12+
@Builder
13+
@Schema(description = "사용자별 연도별 소비 기록 요약 DTO")
14+
public class ExpenseSummaryDTO {
15+
@Schema(description = "연도", example = "2025")
16+
private int year;
17+
18+
@Schema(description = "각 월별 소비 기록 정보", example = "{1: {recordCount: 10, totalDaysInMonth: 28}, 2: {recordCount: 15, totalDaysInMonth: 28}}")
19+
private Map<Integer, RecordInfoDTO> monthlyRecords;
20+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package donmani.donmani_server.expense.dto;
2+
3+
import com.fasterxml.jackson.annotation.JsonInclude;
4+
import io.swagger.v3.oas.annotations.media.Schema;
5+
import lombok.*;
6+
7+
@Getter
8+
@Setter
9+
@NoArgsConstructor
10+
@AllArgsConstructor
11+
@Builder
12+
@JsonInclude(JsonInclude.Include.NON_NULL)
13+
@Schema(description = "사용자별 공지사항 읽음 여부 DTO")
14+
public class NoticeReadDTO {
15+
@Schema(description = "공지사항 읽음 여부", example = "true")
16+
private boolean read;
17+
}

src/main/java/donmani/donmani_server/expense/dto/RecordDTO.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import com.fasterxml.jackson.annotation.JsonInclude;
77

8+
import io.swagger.v3.oas.annotations.media.Schema;
89
import lombok.AllArgsConstructor;
910
import lombok.Builder;
1011
import lombok.Getter;
@@ -16,9 +17,14 @@
1617
@NoArgsConstructor
1718
@AllArgsConstructor
1819
@Builder
20+
@Schema(description = "소비 기록 DTO")
1921
public class RecordDTO {
22+
23+
@Schema(description = "소비 기록 날짜", example = "2025-02-15")
2024
private LocalDate date;
21-
private List<ContentDTO> contents; // null 가능
25+
26+
@Schema(description = "소비 기록의 상세 항목 리스트", nullable = true)
27+
private List<ContentDTO> contents;
2228

2329
public static RecordDTO of(LocalDate localDate, List<ContentDTO> contents) {
2430
RecordDTO recordDTO = new RecordDTO();
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package donmani.donmani_server.expense.dto;
2+
3+
import com.fasterxml.jackson.annotation.JsonInclude;
4+
import io.swagger.v3.oas.annotations.media.Schema;
5+
import lombok.*;
6+
7+
@Getter
8+
@Setter
9+
@NoArgsConstructor
10+
@AllArgsConstructor
11+
@Builder
12+
@JsonInclude(JsonInclude.Include.NON_NULL)
13+
@Schema(description = "월별 소비 기록 정보 DTO")
14+
public class RecordInfoDTO {
15+
@Schema(description = "해당 월의 소비 기록 개수", example = "10")
16+
private long recordCount;
17+
18+
@Schema(description = "해당 월의 일수", example = "28")
19+
private int totalDaysInMonth;
20+
}

src/main/java/donmani/donmani_server/expense/service/ExpenseService.java

Lines changed: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,20 @@
22

33
import java.time.LocalDate;
44
import java.time.LocalDateTime;
5+
import java.time.YearMonth;
56
import java.util.*;
67
import java.util.stream.Collectors;
78
import java.util.stream.Stream;
89

10+
import donmani.donmani_server.expense.dto.*;
11+
import donmani.donmani_server.expense.entity.CategoryType;
12+
import donmani.donmani_server.expense.entity.FlagType;
913
import org.springframework.data.domain.Page;
1014
import org.springframework.data.domain.PageRequest;
1115
import org.springframework.data.domain.Pageable;
1216
import org.springframework.stereotype.Service;
1317
import org.springframework.transaction.annotation.Transactional;
1418

15-
import donmani.donmani_server.expense.dto.ContentDTO;
16-
import donmani.donmani_server.expense.dto.ExpenseRequestDTO;
17-
import donmani.donmani_server.expense.dto.ExpenseResponseDTO;
18-
import donmani.donmani_server.expense.dto.RecordDTO;
1919
import donmani.donmani_server.expense.entity.Expense;
2020
import donmani.donmani_server.expense.repository.ExpenseRepository;
2121
import donmani.donmani_server.user.service.UserService;
@@ -136,4 +136,69 @@ public ExpenseResponseDTO getAllExpenses(String userKey, int page) {
136136
.build();
137137
}
138138

139+
@Transactional(readOnly = true)
140+
public ExpenseSummaryDTO getYearlyExpenseSummary(String userKey, int year) {
141+
Long userId = userService.getUserIdByUserKey(userKey);
142+
List<Expense> expenses = expenseRepository.findByUserId(userId);
143+
144+
Map<Integer, RecordInfoDTO> monthlyRecords = expenses.stream()
145+
.filter(exp -> exp.getCreatedAt().getYear() == year)
146+
.collect(Collectors.groupingBy(
147+
exp -> exp.getCreatedAt().getMonthValue(),
148+
Collectors.collectingAndThen(
149+
Collectors.toList(),
150+
list -> {
151+
long recordCount = list.size();
152+
int totalDaysInMonth = YearMonth.of(year, list.get(0).getCreatedAt().getMonthValue()).lengthOfMonth();
153+
return new RecordInfoDTO(recordCount, totalDaysInMonth);
154+
}
155+
)
156+
));
157+
158+
return ExpenseSummaryDTO.builder()
159+
.year(year)
160+
.monthlyRecords(monthlyRecords)
161+
.build();
162+
}
163+
164+
@Transactional(readOnly = true)
165+
public ExpenseStatisticsDTO getMonthlyExpenseStatistics(String userKey, int year, int month) {
166+
Long userId = userService.getUserIdByUserKey(userKey);
167+
List<Expense> expenses = expenseRepository.findByUserIdAndMonth(userId, year, month);
168+
169+
int goodCount = (int) expenses.stream().filter(e -> e.getFlag() == FlagType.GOOD).count();
170+
int badCount = (int) expenses.stream().filter(e -> e.getFlag() == FlagType.BAD).count();
171+
boolean hasRecords = !expenses.isEmpty();
172+
173+
return ExpenseStatisticsDTO.builder()
174+
.year(year)
175+
.month(month)
176+
.goodCount(goodCount)
177+
.badCount(badCount)
178+
.hasRecords(hasRecords)
179+
.records(expenses.stream().map(e -> RecordDTO.of(e.getCreatedAt().toLocalDate(), List.of(ContentDTO.builder()
180+
.flag(e.getFlag())
181+
.category(e.getCategory())
182+
.memo(e.getMemo())
183+
.build()))).collect(Collectors.toList()))
184+
.build();
185+
}
186+
187+
@Transactional(readOnly = true)
188+
public CategoryStatisticsDTO getCategoryStatistics(String userKey, int year, int month) {
189+
Long userId = userService.getUserIdByUserKey(userKey);
190+
List<Expense> expenses = expenseRepository.findByUserIdAndMonth(userId, year, month);
191+
192+
Map<CategoryType, Integer> categoryCounts = Arrays.stream(CategoryType.values())
193+
.collect(Collectors.toMap(category -> category, category -> 0));
194+
195+
expenses.forEach(expense -> categoryCounts.put(expense.getCategory(), categoryCounts.get(expense.getCategory()) + 1));
196+
197+
return CategoryStatisticsDTO.builder()
198+
.year(year)
199+
.month(month)
200+
.categoryCounts(categoryCounts)
201+
.build();
202+
}
203+
139204
}

src/main/java/donmani/donmani_server/user/controller/UserController.java

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package donmani.donmani_server.user.controller;
22

33
import donmani.donmani_server.common.httpStatus.HttpStatusDTO;
4+
import donmani.donmani_server.expense.dto.NoticeReadDTO;
45
import donmani.donmani_server.user.dto.UpdateUsernameRequestDTO;
56
import donmani.donmani_server.user.dto.UpdateUsernameResponseDTO;
67
import donmani.donmani_server.user.dto.UserRegisterRequestDTO;
@@ -10,10 +11,7 @@
1011

1112
import org.springframework.http.HttpStatus;
1213
import org.springframework.http.ResponseEntity;
13-
import org.springframework.web.bind.annotation.PostMapping;
14-
import org.springframework.web.bind.annotation.PutMapping;
15-
import org.springframework.web.bind.annotation.RequestBody;
16-
import org.springframework.web.bind.annotation.RestController;
14+
import org.springframework.web.bind.annotation.*;
1715

1816
import jakarta.validation.Valid;
1917
import lombok.RequiredArgsConstructor;
@@ -79,4 +77,16 @@ public ResponseEntity<HttpStatusDTO<UpdateUsernameResponseDTO>> updateNicknameV2
7977
HttpStatusDTO.response(HttpStatus.INTERNAL_SERVER_ERROR.value(), "유저 정보 없음", null));
8078
}
8179
}
80+
81+
@GetMapping("api/v1/notice/status/{userKey}")
82+
public ResponseEntity<NoticeReadDTO> getNoticeReadStatus(@PathVariable String userKey) {
83+
NoticeReadDTO noticeStatus = userService.getNoticeReadStatus(userKey);
84+
return ResponseEntity.ok(noticeStatus);
85+
}
86+
87+
@PutMapping("api/v1/notice/status/{userKey}")
88+
public ResponseEntity<Void> markNoticeAsRead(@PathVariable String userKey) {
89+
userService.markNoticeAsRead(userKey);
90+
return ResponseEntity.ok().build();
91+
}
8292
}

0 commit comments

Comments
 (0)