feat: 순자산 추이 주간 집계 추가 (/networth/trend?unit=WEEK)
CI / build (push) Failing after 14m43s

- weeklyWalletFlow(월요일 기준 주별 순현금흐름) + netWorthTrendWeekly(기본 13주)
- 컨트롤러에 unit=WEEK&weeks=N 지원(기존 월별은 그대로)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
ByungCheol
2026-06-22 22:38:06 +09:00
parent 7de643022b
commit 3fc233d697
4 changed files with 81 additions and 1 deletions
@@ -167,11 +167,16 @@ public class AccountController {
return accountService.categoryStats(current.getId(), type, year, month);
}
/** 월별 순자산 추이 */
/** 순자산 추이 (unit=WEEK 면 주별, weeks 개수 / 그 외 월별, months 개수) */
@GetMapping("/networth/trend")
public List<NetWorthPoint> netWorthTrend(
@RequestParam(required = false) Integer months,
@RequestParam(required = false) Integer weeks,
@RequestParam(defaultValue = "MONTH") String unit,
@RequestAttribute(AuthInterceptor.CURRENT_MEMBER) SessionUser current) {
if ("WEEK".equalsIgnoreCase(unit)) {
return accountService.netWorthTrendWeekly(current.getId(), weeks);
}
return accountService.netWorthTrend(current.getId(), months);
}
@@ -47,6 +47,9 @@ public interface AccountEntryMapper {
/** 월별 순현금흐름(수입−지출, 계좌 지정분) — {ym, net} 행 반환 (순자산 추이용) */
List<Map<String, Object>> monthlyWalletFlow(@Param("memberId") Long memberId);
/** 주별 순현금흐름(수입−지출, 계좌 지정분) — {wk(=주 월요일 yyyy-MM-dd), net} (주간 순자산 추이용) */
List<Map<String, Object>> weeklyWalletFlow(@Param("memberId") Long memberId);
/** 기간 버킷(일/주/월/년)별 수입·지출 — {bucket, income, expense} (이체 제외) */
List<Map<String, Object>> statsByPeriod(@Param("memberId") Long memberId,
@Param("unit") String unit,
@@ -26,6 +26,7 @@ import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDate;
import java.time.YearMonth;
import java.util.ArrayList;
import java.util.HashMap;
@@ -128,6 +129,66 @@ public class AccountService {
return result;
}
/** 주별 순자산 추이 (월요일 기준 버킷). 기본 13주(최근 약 3개월). */
public List<NetWorthPoint> netWorthTrendWeekly(Long memberId, Integer weeks) {
int n = (weeks == null || weeks <= 0 || weeks > 53) ? 13 : weeks;
LocalDate today = LocalDate.now();
LocalDate currentMon = today.minusDays(today.getDayOfWeek().getValue() - 1L); // 이번 주 월요일
LocalDate startMon = currentMon.minusWeeks(n - 1L);
Map<String, Long> flow = new HashMap<>();
for (Map<String, Object> row : mapper.weeklyWalletFlow(memberId)) {
Object net = row.get("net");
flow.put((String) row.get("wk"), net == null ? 0L : ((Number) net).longValue());
}
// 개시잔액: 개시주가 시작주 이전(또는 미상)이면 기준선, 범위 내면 해당 주 버킷
List<Wallet> ws = walletMapper.findByMember(memberId);
Map<String, Long> openingByWeek = new HashMap<>();
long baseline = 0;
for (Wallet w : ws) {
long ob = w.getOpeningBalance() == null ? 0L : w.getOpeningBalance();
if (ob == 0) {
continue;
}
LocalDate od = w.getOpeningDate();
LocalDate om = od == null ? null : od.minusDays(od.getDayOfWeek().getValue() - 1L);
if (om == null || om.isBefore(startMon)) {
baseline += ob;
} else if (!om.isAfter(currentMon)) {
openingByWeek.merge(om.toString(), ob, Long::sum);
}
}
String startKey = startMon.toString();
for (Map.Entry<String, Long> e : flow.entrySet()) {
if (e.getKey().compareTo(startKey) < 0) {
baseline += e.getValue();
}
}
List<NetWorthPoint> result = new ArrayList<>();
long running = baseline;
for (LocalDate m = startMon; !m.isAfter(currentMon); m = m.plusWeeks(1)) {
String key = m.toString();
running += flow.getOrDefault(key, 0L);
running += openingByWeek.getOrDefault(key, 0L);
result.add(NetWorthPoint.builder().month(key).netWorth(running).build());
}
// 투자 손익(실현+평가)은 과거 주별 이력이 없으므로 마지막 점에만 가산 (월간과 동일)
if (!result.isEmpty()) {
long investAdjust = 0;
for (InvestService.WalletInvest wi : investService.valuationByWallet(memberId).values()) {
investAdjust += wi.cashDelta + wi.stockEval;
}
if (investAdjust != 0) {
NetWorthPoint last = result.get(result.size() - 1);
last.setNetWorth(last.getNetWorth() + investAdjust);
}
}
return result;
}
/** 기간 버킷(일/주/월/년)별 수입·지출 통계 */
public List<PeriodStat> stats(Long memberId, String unit, Integer year, Integer month) {
String u = switch (unit == null ? "" : unit.toUpperCase()) {