- weeklyWalletFlow(월요일 기준 주별 순현금흐름) + netWorthTrendWeekly(기본 13주) - 컨트롤러에 unit=WEEK&weeks=N 지원(기존 월별은 그대로) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -167,11 +167,16 @@ public class AccountController {
|
|||||||
return accountService.categoryStats(current.getId(), type, year, month);
|
return accountService.categoryStats(current.getId(), type, year, month);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 월별 순자산 추이 */
|
/** 순자산 추이 (unit=WEEK 면 주별, weeks 개수 / 그 외 월별, months 개수) */
|
||||||
@GetMapping("/networth/trend")
|
@GetMapping("/networth/trend")
|
||||||
public List<NetWorthPoint> netWorthTrend(
|
public List<NetWorthPoint> netWorthTrend(
|
||||||
@RequestParam(required = false) Integer months,
|
@RequestParam(required = false) Integer months,
|
||||||
|
@RequestParam(required = false) Integer weeks,
|
||||||
|
@RequestParam(defaultValue = "MONTH") String unit,
|
||||||
@RequestAttribute(AuthInterceptor.CURRENT_MEMBER) SessionUser current) {
|
@RequestAttribute(AuthInterceptor.CURRENT_MEMBER) SessionUser current) {
|
||||||
|
if ("WEEK".equalsIgnoreCase(unit)) {
|
||||||
|
return accountService.netWorthTrendWeekly(current.getId(), weeks);
|
||||||
|
}
|
||||||
return accountService.netWorthTrend(current.getId(), months);
|
return accountService.netWorthTrend(current.getId(), months);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,9 @@ public interface AccountEntryMapper {
|
|||||||
/** 월별 순현금흐름(수입−지출, 계좌 지정분) — {ym, net} 행 반환 (순자산 추이용) */
|
/** 월별 순현금흐름(수입−지출, 계좌 지정분) — {ym, net} 행 반환 (순자산 추이용) */
|
||||||
List<Map<String, Object>> monthlyWalletFlow(@Param("memberId") Long memberId);
|
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} (이체 제외) */
|
/** 기간 버킷(일/주/월/년)별 수입·지출 — {bucket, income, expense} (이체 제외) */
|
||||||
List<Map<String, Object>> statsByPeriod(@Param("memberId") Long memberId,
|
List<Map<String, Object>> statsByPeriod(@Param("memberId") Long memberId,
|
||||||
@Param("unit") String unit,
|
@Param("unit") String unit,
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import org.springframework.http.HttpStatus;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
import java.time.YearMonth;
|
import java.time.YearMonth;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@@ -128,6 +129,66 @@ public class AccountService {
|
|||||||
return result;
|
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) {
|
public List<PeriodStat> stats(Long memberId, String unit, Integer year, Integer month) {
|
||||||
String u = switch (unit == null ? "" : unit.toUpperCase()) {
|
String u = switch (unit == null ? "" : unit.toUpperCase()) {
|
||||||
|
|||||||
@@ -108,6 +108,17 @@
|
|||||||
ORDER BY ym
|
ORDER BY ym
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<select id="weeklyWalletFlow" resultType="map">
|
||||||
|
SELECT DATE_FORMAT(DATE_SUB(entry_date, INTERVAL WEEKDAY(entry_date) DAY), '%Y-%m-%d') AS wk,
|
||||||
|
COALESCE(SUM(CASE WHEN type = 'INCOME' THEN amount
|
||||||
|
WHEN type = 'EXPENSE' THEN -amount
|
||||||
|
ELSE 0 END), 0) AS net
|
||||||
|
FROM account_entry
|
||||||
|
WHERE member_id = #{memberId} AND wallet_id IS NOT NULL AND type IN ('INCOME', 'EXPENSE')
|
||||||
|
GROUP BY wk
|
||||||
|
ORDER BY wk
|
||||||
|
</select>
|
||||||
|
|
||||||
<select id="statsByPeriod" resultType="map">
|
<select id="statsByPeriod" resultType="map">
|
||||||
SELECT
|
SELECT
|
||||||
<choose>
|
<choose>
|
||||||
|
|||||||
Reference in New Issue
Block a user