Compare commits
2 Commits
3535c2e220
..
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| 947c7a1f68 | |||
| 24e235404b |
@@ -50,8 +50,22 @@ public class BudgetController {
|
|||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
public List<BudgetResponse> list(
|
public List<BudgetResponse> list(
|
||||||
|
@RequestParam int year,
|
||||||
|
@RequestParam int month,
|
||||||
@RequestAttribute(AuthInterceptor.CURRENT_MEMBER) SessionUser current) {
|
@RequestAttribute(AuthInterceptor.CURRENT_MEMBER) SessionUser current) {
|
||||||
return budgetService.list(current.getId());
|
return budgetService.list(current.getId(), year, month);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 다른 월의 예산을 이 달로 복사(전월 복사 등). 같은 분류는 덮어씀 */
|
||||||
|
@PostMapping("/copy")
|
||||||
|
public List<BudgetResponse> copy(
|
||||||
|
@RequestParam int fromYear,
|
||||||
|
@RequestParam int fromMonth,
|
||||||
|
@RequestParam int toYear,
|
||||||
|
@RequestParam int toMonth,
|
||||||
|
@RequestAttribute(AuthInterceptor.CURRENT_MEMBER) SessionUser current) {
|
||||||
|
budgetService.copy(current.getId(), fromYear, fromMonth, toYear, toMonth);
|
||||||
|
return budgetService.list(current.getId(), toYear, toMonth);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/status")
|
@GetMapping("/status")
|
||||||
@@ -74,9 +88,11 @@ public class BudgetController {
|
|||||||
|
|
||||||
@PostMapping
|
@PostMapping
|
||||||
public ResponseEntity<BudgetResponse> create(
|
public ResponseEntity<BudgetResponse> create(
|
||||||
|
@RequestParam int year,
|
||||||
|
@RequestParam int month,
|
||||||
@Valid @RequestBody BudgetRequest req,
|
@Valid @RequestBody BudgetRequest req,
|
||||||
@RequestAttribute(AuthInterceptor.CURRENT_MEMBER) SessionUser current) {
|
@RequestAttribute(AuthInterceptor.CURRENT_MEMBER) SessionUser current) {
|
||||||
return ResponseEntity.status(HttpStatus.CREATED).body(budgetService.create(req, current.getId()));
|
return ResponseEntity.status(HttpStatus.CREATED).body(budgetService.create(req, current.getId(), year, month));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PutMapping("/{id}")
|
@PutMapping("/{id}")
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ public class Budget implements Serializable {
|
|||||||
private Long weekly;
|
private Long weekly;
|
||||||
private Long monthly;
|
private Long monthly;
|
||||||
private Long yearly;
|
private Long yearly;
|
||||||
|
private Integer year; // 예산 연도(월별 예산)
|
||||||
|
private Integer month; // 예산 월(1~12)
|
||||||
private LocalDateTime createdAt;
|
private LocalDateTime createdAt;
|
||||||
private LocalDateTime updatedAt;
|
private LocalDateTime updatedAt;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import lombok.Data;
|
|||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.math.BigDecimal;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
@@ -29,6 +30,11 @@ public class Wallet implements Serializable {
|
|||||||
private Long openingBalance; // 초기 잔액(부호있음: 자산+, 부채-)
|
private Long openingBalance; // 초기 잔액(부호있음: 자산+, 부채-)
|
||||||
private LocalDate openingDate;
|
private LocalDate openingDate;
|
||||||
private Long currentValue; // 현재 평가금액 (INVEST 전용, 수동 갱신)
|
private Long currentValue; // 현재 평가금액 (INVEST 전용, 수동 갱신)
|
||||||
|
private Long loanAmount; // 대출 실행 금액(원금, 처음 빌린 금액) (LOAN 전용)
|
||||||
|
private BigDecimal loanRate; // 연이자율(%) e.g. 5.25 (LOAN 전용)
|
||||||
|
private String loanMethod; // 상환방식: EQUAL_PAYMENT / EQUAL_PRINCIPAL / BULLET (LOAN 전용)
|
||||||
|
private Integer loanMonths; // 대출기간(개월) (LOAN 전용)
|
||||||
|
private LocalDate loanStart; // 대출시작일 (LOAN 전용)
|
||||||
private Integer sortOrder; // 표시 순서 (타입 내 정렬)
|
private Integer sortOrder; // 표시 순서 (타입 내 정렬)
|
||||||
private LocalDateTime createdAt;
|
private LocalDateTime createdAt;
|
||||||
private LocalDateTime updatedAt;
|
private LocalDateTime updatedAt;
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
package com.sb.web.account.dto;
|
package com.sb.web.account.dto;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.DecimalMax;
|
||||||
|
import jakarta.validation.constraints.DecimalMin;
|
||||||
import jakarta.validation.constraints.NotBlank;
|
import jakarta.validation.constraints.NotBlank;
|
||||||
import jakarta.validation.constraints.Pattern;
|
import jakarta.validation.constraints.Pattern;
|
||||||
import jakarta.validation.constraints.Size;
|
import jakarta.validation.constraints.Size;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -37,4 +40,17 @@ public class WalletRequest {
|
|||||||
|
|
||||||
/** 현재 평가금액 (INVEST 전용, 수동 갱신) */
|
/** 현재 평가금액 (INVEST 전용, 수동 갱신) */
|
||||||
private Long currentValue;
|
private Long currentValue;
|
||||||
|
|
||||||
|
// ===== 대출(LOAN) 전용 =====
|
||||||
|
private Long loanAmount; // 대출 실행 금액(원금)
|
||||||
|
|
||||||
|
@DecimalMin(value = "0.0", inclusive = true)
|
||||||
|
@DecimalMax(value = "100.0", message = "이자율은 100% 이하여야 합니다.")
|
||||||
|
private BigDecimal loanRate; // 연이자율(%)
|
||||||
|
|
||||||
|
@Pattern(regexp = "EQUAL_PAYMENT|EQUAL_PRINCIPAL|BULLET|", message = "상환방식이 올바르지 않습니다.")
|
||||||
|
private String loanMethod; // 상환방식
|
||||||
|
|
||||||
|
private Integer loanMonths; // 대출기간(개월)
|
||||||
|
private LocalDate loanStart; // 대출시작일
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import com.sb.web.account.domain.Wallet;
|
|||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -31,6 +32,13 @@ public class WalletResponse {
|
|||||||
private Long currentValue; // 평가액 직접입력값(퇴직연금·연금 등 수동 갱신). 있으면 총평가로 사용
|
private Long currentValue; // 평가액 직접입력값(퇴직연금·연금 등 수동 갱신). 있으면 총평가로 사용
|
||||||
private Boolean manualValuation; // true면 평가액 직접입력형(종목 자동계산 대신)
|
private Boolean manualValuation; // true면 평가액 직접입력형(종목 자동계산 대신)
|
||||||
|
|
||||||
|
// 대출(LOAN) 전용
|
||||||
|
private Long loanAmount; // 대출 실행 금액(원금)
|
||||||
|
private BigDecimal loanRate; // 연이자율(%) e.g. 5.25
|
||||||
|
private String loanMethod; // EQUAL_PAYMENT / EQUAL_PRINCIPAL / BULLET
|
||||||
|
private Integer loanMonths; // 대출기간(개월)
|
||||||
|
private LocalDate loanStart; // 대출시작일
|
||||||
|
|
||||||
public static WalletResponse from(Wallet w, long balance) {
|
public static WalletResponse from(Wallet w, long balance) {
|
||||||
return WalletResponse.builder()
|
return WalletResponse.builder()
|
||||||
.id(w.getId())
|
.id(w.getId())
|
||||||
@@ -43,6 +51,11 @@ public class WalletResponse {
|
|||||||
.openingDate(w.getOpeningDate())
|
.openingDate(w.getOpeningDate())
|
||||||
.balance(balance)
|
.balance(balance)
|
||||||
.currentValue(w.getCurrentValue())
|
.currentValue(w.getCurrentValue())
|
||||||
|
.loanAmount(w.getLoanAmount())
|
||||||
|
.loanRate(w.getLoanRate())
|
||||||
|
.loanMethod(w.getLoanMethod())
|
||||||
|
.loanMonths(w.getLoanMonths())
|
||||||
|
.loanStart(w.getLoanStart())
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,13 +12,24 @@ import java.util.List;
|
|||||||
@Mapper
|
@Mapper
|
||||||
public interface BudgetMapper {
|
public interface BudgetMapper {
|
||||||
|
|
||||||
List<Budget> findByMember(@Param("memberId") Long memberId);
|
List<Budget> findByMember(@Param("memberId") Long memberId,
|
||||||
|
@Param("year") Integer year,
|
||||||
|
@Param("month") Integer month);
|
||||||
|
|
||||||
Budget findByIdAndMember(@Param("id") Long id, @Param("memberId") Long memberId);
|
Budget findByIdAndMember(@Param("id") Long id, @Param("memberId") Long memberId);
|
||||||
|
|
||||||
int countByCategory(@Param("memberId") Long memberId,
|
int countByCategory(@Param("memberId") Long memberId,
|
||||||
@Param("category") String category,
|
@Param("category") String category,
|
||||||
@Param("excludeId") Long excludeId);
|
@Param("excludeId") Long excludeId,
|
||||||
|
@Param("year") Integer year,
|
||||||
|
@Param("month") Integer month);
|
||||||
|
|
||||||
|
/** fromYear/fromMonth 예산을 toYear/toMonth 로 복사(같은 분류는 덮어씀). 복사된 건수 반환 */
|
||||||
|
int copyMonth(@Param("memberId") Long memberId,
|
||||||
|
@Param("fromYear") Integer fromYear,
|
||||||
|
@Param("fromMonth") Integer fromMonth,
|
||||||
|
@Param("toYear") Integer toYear,
|
||||||
|
@Param("toMonth") Integer toMonth);
|
||||||
|
|
||||||
int insert(Budget budget);
|
int insert(Budget budget);
|
||||||
|
|
||||||
|
|||||||
@@ -535,6 +535,12 @@ public class AccountService {
|
|||||||
wallet.setOpeningBalance(req.getOpeningBalance() != null ? req.getOpeningBalance() : 0L);
|
wallet.setOpeningBalance(req.getOpeningBalance() != null ? req.getOpeningBalance() : 0L);
|
||||||
wallet.setOpeningDate(req.getOpeningDate());
|
wallet.setOpeningDate(req.getOpeningDate());
|
||||||
wallet.setCurrentValue("INVEST".equals(req.getType()) ? req.getCurrentValue() : null);
|
wallet.setCurrentValue("INVEST".equals(req.getType()) ? req.getCurrentValue() : null);
|
||||||
|
boolean isLoan = "LOAN".equals(req.getType());
|
||||||
|
wallet.setLoanAmount(isLoan ? req.getLoanAmount() : null);
|
||||||
|
wallet.setLoanRate(isLoan ? req.getLoanRate() : null);
|
||||||
|
wallet.setLoanMethod(isLoan ? blankToNull(req.getLoanMethod()) : null);
|
||||||
|
wallet.setLoanMonths(isLoan ? req.getLoanMonths() : null);
|
||||||
|
wallet.setLoanStart(isLoan ? req.getLoanStart() : null);
|
||||||
walletMapper.update(wallet);
|
walletMapper.update(wallet);
|
||||||
return WalletResponse.from(wallet, balanceMap(memberId).getOrDefault(id, wallet.getOpeningBalance()));
|
return WalletResponse.from(wallet, balanceMap(memberId).getOrDefault(id, wallet.getOpeningBalance()));
|
||||||
}
|
}
|
||||||
@@ -564,6 +570,7 @@ public class AccountService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Wallet toWallet(WalletRequest req) {
|
private Wallet toWallet(WalletRequest req) {
|
||||||
|
boolean isLoan = "LOAN".equals(req.getType());
|
||||||
return Wallet.builder()
|
return Wallet.builder()
|
||||||
.type(req.getType())
|
.type(req.getType())
|
||||||
.name(req.getName().trim())
|
.name(req.getName().trim())
|
||||||
@@ -573,6 +580,11 @@ public class AccountService {
|
|||||||
.openingBalance(req.getOpeningBalance() != null ? req.getOpeningBalance() : 0L)
|
.openingBalance(req.getOpeningBalance() != null ? req.getOpeningBalance() : 0L)
|
||||||
.openingDate(req.getOpeningDate())
|
.openingDate(req.getOpeningDate())
|
||||||
.currentValue("INVEST".equals(req.getType()) ? req.getCurrentValue() : null)
|
.currentValue("INVEST".equals(req.getType()) ? req.getCurrentValue() : null)
|
||||||
|
.loanAmount(isLoan ? req.getLoanAmount() : null)
|
||||||
|
.loanRate(isLoan ? req.getLoanRate() : null)
|
||||||
|
.loanMethod(isLoan ? blankToNull(req.getLoanMethod()) : null)
|
||||||
|
.loanMonths(isLoan ? req.getLoanMonths() : null)
|
||||||
|
.loanStart(isLoan ? req.getLoanStart() : null)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -156,11 +156,12 @@ public class BackupService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 7) 예산
|
// 7) 예산 — 복구 시 현재 월 예산으로 등록(백업엔 월 정보 없음)
|
||||||
if (req.getBudgets() != null) {
|
if (req.getBudgets() != null) {
|
||||||
|
java.time.LocalDate today = java.time.LocalDate.now();
|
||||||
for (BudgetRequest b : req.getBudgets()) {
|
for (BudgetRequest b : req.getBudgets()) {
|
||||||
if (b == null || b.getCategory() == null) continue;
|
if (b == null || b.getCategory() == null) continue;
|
||||||
budgetService.create(b, memberId);
|
budgetService.create(b, memberId, today.getYear(), today.getMonthValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,36 +31,46 @@ public class BudgetService {
|
|||||||
private final BudgetMapper budgetMapper;
|
private final BudgetMapper budgetMapper;
|
||||||
private final AccountEntryMapper entryMapper;
|
private final AccountEntryMapper entryMapper;
|
||||||
|
|
||||||
public List<BudgetResponse> list(Long memberId) {
|
public List<BudgetResponse> list(Long memberId, int year, int month) {
|
||||||
return budgetMapper.findByMember(memberId).stream().map(BudgetResponse::from).toList();
|
return budgetMapper.findByMember(memberId, year, month).stream().map(BudgetResponse::from).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public BudgetResponse create(BudgetRequest req, Long memberId) {
|
public BudgetResponse create(BudgetRequest req, Long memberId, int year, int month) {
|
||||||
String category = req.getCategory().trim();
|
String category = req.getCategory().trim();
|
||||||
if (budgetMapper.countByCategory(memberId, category, null) > 0) {
|
if (budgetMapper.countByCategory(memberId, category, null, year, month) > 0) {
|
||||||
throw new ApiException(HttpStatus.CONFLICT, "이미 예산이 설정된 카테고리입니다.");
|
throw new ApiException(HttpStatus.CONFLICT, "이미 예산이 설정된 카테고리입니다.");
|
||||||
}
|
}
|
||||||
Budget budget = toBudget(req, category);
|
Budget budget = toBudget(req, category);
|
||||||
budget.setMemberId(memberId);
|
budget.setMemberId(memberId);
|
||||||
|
budget.setYear(year);
|
||||||
|
budget.setMonth(month);
|
||||||
budgetMapper.insert(budget);
|
budgetMapper.insert(budget);
|
||||||
return BudgetResponse.from(budget);
|
return BudgetResponse.from(budget);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public BudgetResponse update(Long id, BudgetRequest req, Long memberId) {
|
public BudgetResponse update(Long id, BudgetRequest req, Long memberId) {
|
||||||
mustFind(id, memberId);
|
Budget existing = mustFind(id, memberId);
|
||||||
String category = req.getCategory().trim();
|
String category = req.getCategory().trim();
|
||||||
if (budgetMapper.countByCategory(memberId, category, id) > 0) {
|
if (budgetMapper.countByCategory(memberId, category, id, existing.getYear(), existing.getMonth()) > 0) {
|
||||||
throw new ApiException(HttpStatus.CONFLICT, "이미 예산이 설정된 카테고리입니다.");
|
throw new ApiException(HttpStatus.CONFLICT, "이미 예산이 설정된 카테고리입니다.");
|
||||||
}
|
}
|
||||||
Budget budget = toBudget(req, category);
|
Budget budget = toBudget(req, category);
|
||||||
budget.setId(id);
|
budget.setId(id);
|
||||||
budget.setMemberId(memberId);
|
budget.setMemberId(memberId);
|
||||||
|
budget.setYear(existing.getYear());
|
||||||
|
budget.setMonth(existing.getMonth());
|
||||||
budgetMapper.update(budget);
|
budgetMapper.update(budget);
|
||||||
return BudgetResponse.from(budget);
|
return BudgetResponse.from(budget);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** fromYear/fromMonth 예산을 toYear/toMonth 로 복사(같은 분류 덮어씀). 복사된 건수 반환 */
|
||||||
|
@Transactional
|
||||||
|
public int copy(Long memberId, int fromYear, int fromMonth, int toYear, int toMonth) {
|
||||||
|
return budgetMapper.copyMonth(memberId, fromYear, fromMonth, toYear, toMonth);
|
||||||
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public void delete(Long id, Long memberId) {
|
public void delete(Long id, Long memberId) {
|
||||||
mustFind(id, memberId);
|
mustFind(id, memberId);
|
||||||
@@ -79,7 +89,7 @@ public class BudgetService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<BudgetStatus> result = new ArrayList<>();
|
List<BudgetStatus> result = new ArrayList<>();
|
||||||
for (Budget b : budgetMapper.findByMember(memberId)) {
|
for (Budget b : budgetMapper.findByMember(memberId, year, month)) {
|
||||||
long monthlyBudget = monthlyBudget(b, daysInMonth);
|
long monthlyBudget = monthlyBudget(b, daysInMonth);
|
||||||
long spent = spentByCategory.getOrDefault(b.getCategory(), 0L);
|
long spent = spentByCategory.getOrDefault(b.getCategory(), 0L);
|
||||||
result.add(BudgetStatus.builder()
|
result.add(BudgetStatus.builder()
|
||||||
@@ -103,7 +113,8 @@ public class BudgetService {
|
|||||||
int y = year != null ? year : Year.now().getValue();
|
int y = year != null ? year : Year.now().getValue();
|
||||||
int m = month != null ? month : 1;
|
int m = month != null ? month : 1;
|
||||||
|
|
||||||
List<Budget> budgets = budgetMapper.findByMember(memberId);
|
// 기간 차트는 선택 월의 예산을 기준으로 각 버킷에 적용(월별 예산 도입 전 동작과 동일)
|
||||||
|
List<Budget> budgets = budgetMapper.findByMember(memberId, y, m);
|
||||||
Map<String, Long> expense = new HashMap<>();
|
Map<String, Long> expense = new HashMap<>();
|
||||||
for (Map<String, Object> row : entryMapper.statsByPeriod(memberId, unit, year, month)) {
|
for (Map<String, Object> row : entryMapper.statsByPeriod(memberId, unit, year, month)) {
|
||||||
expense.put(String.valueOf(((Number) row.get("bucket")).longValue()),
|
expense.put(String.valueOf(((Number) row.get("bucket")).longValue()),
|
||||||
|
|||||||
@@ -28,6 +28,11 @@ ALTER TABLE wallet ADD COLUMN IF NOT EXISTS opening_balance BIGINT NOT NULL DEFA
|
|||||||
ALTER TABLE wallet ADD COLUMN IF NOT EXISTS opening_date DATE NULL;
|
ALTER TABLE wallet ADD COLUMN IF NOT EXISTS opening_date DATE NULL;
|
||||||
ALTER TABLE wallet ADD COLUMN IF NOT EXISTS current_value BIGINT NULL;
|
ALTER TABLE wallet ADD COLUMN IF NOT EXISTS current_value BIGINT NULL;
|
||||||
ALTER TABLE wallet ADD COLUMN IF NOT EXISTS sort_order INT NOT NULL DEFAULT 0;
|
ALTER TABLE wallet ADD COLUMN IF NOT EXISTS sort_order INT NOT NULL DEFAULT 0;
|
||||||
|
ALTER TABLE wallet ADD COLUMN IF NOT EXISTS loan_rate DECIMAL(7,4) NULL COMMENT '연이자율(%) 예: 5.2500';
|
||||||
|
ALTER TABLE wallet ADD COLUMN IF NOT EXISTS loan_method VARCHAR(20) NULL COMMENT '상환방식: EQUAL_PAYMENT/EQUAL_PRINCIPAL/BULLET';
|
||||||
|
ALTER TABLE wallet ADD COLUMN IF NOT EXISTS loan_months INT NULL COMMENT '대출기간(개월)';
|
||||||
|
ALTER TABLE wallet ADD COLUMN IF NOT EXISTS loan_start DATE NULL COMMENT '대출시작일';
|
||||||
|
ALTER TABLE wallet ADD COLUMN IF NOT EXISTS loan_amount BIGINT NULL COMMENT '대출 실행 금액(원금, 처음 빌린 금액)';
|
||||||
-- 계좌번호 암호화 저장(AES-GCM)으로 평문보다 길어 VARCHAR(255) 필요 — 운영 적용 완료(CREATE TABLE 정의에 반영).
|
-- 계좌번호 암호화 저장(AES-GCM)으로 평문보다 길어 VARCHAR(255) 필요 — 운영 적용 완료(CREATE TABLE 정의에 반영).
|
||||||
-- 매 기동 MODIFY 는 라이브 테이블 락 위험이 있어 제거. 기존 환경 확장이 필요하면 1회 수동 적용:
|
-- 매 기동 MODIFY 는 라이브 테이블 락 위험이 있어 제거. 기존 환경 확장이 필요하면 1회 수동 적용:
|
||||||
-- ALTER TABLE wallet MODIFY account_number VARCHAR(255) NULL;
|
-- ALTER TABLE wallet MODIFY account_number VARCHAR(255) NULL;
|
||||||
@@ -165,6 +170,15 @@ CREATE TABLE IF NOT EXISTS budget (
|
|||||||
UNIQUE KEY uk_budget_member_category (member_id, category)
|
UNIQUE KEY uk_budget_member_category (member_id, category)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- 월별 예산: 예산을 연/월별로 분리(전월 복사로 다음 달에 동일 예산 저장 가능).
|
||||||
|
ALTER TABLE budget ADD COLUMN IF NOT EXISTS `year` INT NULL COMMENT '예산 연도';
|
||||||
|
ALTER TABLE budget ADD COLUMN IF NOT EXISTS `month` INT NULL COMMENT '예산 월(1~12)';
|
||||||
|
-- 기존 상시 예산을 현재 연/월로 1회 이관
|
||||||
|
UPDATE budget SET `year` = YEAR(NOW()), `month` = MONTH(NOW()) WHERE `year` IS NULL OR `month` IS NULL;
|
||||||
|
-- 분류 유니크를 (member,category) → (member,category,year,month) 로 교체
|
||||||
|
ALTER TABLE budget DROP INDEX IF EXISTS uk_budget_member_category;
|
||||||
|
ALTER TABLE budget ADD UNIQUE KEY IF NOT EXISTS uk_budget_member_cat_ym (member_id, category, `year`, `month`);
|
||||||
|
|
||||||
-- 가계부 항목 - 태그 (다대다, tag_id 는 account_tag.id 참조)
|
-- 가계부 항목 - 태그 (다대다, tag_id 는 account_tag.id 참조)
|
||||||
CREATE TABLE IF NOT EXISTS account_entry_tag (
|
CREATE TABLE IF NOT EXISTS account_entry_tag (
|
||||||
entry_id BIGINT NOT NULL,
|
entry_id BIGINT NOT NULL,
|
||||||
|
|||||||
@@ -14,15 +14,19 @@
|
|||||||
<result property="weekly" column="weekly"/>
|
<result property="weekly" column="weekly"/>
|
||||||
<result property="monthly" column="monthly"/>
|
<result property="monthly" column="monthly"/>
|
||||||
<result property="yearly" column="yearly"/>
|
<result property="yearly" column="yearly"/>
|
||||||
|
<result property="year" column="year"/>
|
||||||
|
<result property="month" column="month"/>
|
||||||
<result property="createdAt" column="created_at"/>
|
<result property="createdAt" column="created_at"/>
|
||||||
<result property="updatedAt" column="updated_at"/>
|
<result property="updatedAt" column="updated_at"/>
|
||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
<sql id="cols">id, member_id, category, fixed, base_unit, base_amount,
|
<sql id="cols">id, member_id, category, fixed, base_unit, base_amount,
|
||||||
daily, weekly, monthly, yearly, created_at, updated_at</sql>
|
daily, weekly, monthly, yearly, `year`, `month`, created_at, updated_at</sql>
|
||||||
|
|
||||||
<select id="findByMember" parameterType="long" resultMap="budgetResultMap">
|
<select id="findByMember" resultMap="budgetResultMap">
|
||||||
SELECT <include refid="cols"/> FROM budget WHERE member_id = #{memberId} ORDER BY category
|
SELECT <include refid="cols"/> FROM budget
|
||||||
|
WHERE member_id = #{memberId} AND `year` = #{year} AND `month` = #{month}
|
||||||
|
ORDER BY category
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select id="findByIdAndMember" resultMap="budgetResultMap">
|
<select id="findByIdAndMember" resultMap="budgetResultMap">
|
||||||
@@ -32,15 +36,30 @@
|
|||||||
<select id="countByCategory" resultType="int">
|
<select id="countByCategory" resultType="int">
|
||||||
SELECT COUNT(*) FROM budget
|
SELECT COUNT(*) FROM budget
|
||||||
WHERE member_id = #{memberId} AND category = #{category}
|
WHERE member_id = #{memberId} AND category = #{category}
|
||||||
|
AND `year` = #{year} AND `month` = #{month}
|
||||||
<if test="excludeId != null">AND id != #{excludeId}</if>
|
<if test="excludeId != null">AND id != #{excludeId}</if>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<insert id="insert" parameterType="com.sb.web.account.domain.Budget"
|
<insert id="insert" parameterType="com.sb.web.account.domain.Budget"
|
||||||
useGeneratedKeys="true" keyProperty="id">
|
useGeneratedKeys="true" keyProperty="id">
|
||||||
INSERT INTO budget (member_id, category, fixed, base_unit, base_amount,
|
INSERT INTO budget (member_id, category, fixed, base_unit, base_amount,
|
||||||
daily, weekly, monthly, yearly, created_at, updated_at)
|
daily, weekly, monthly, yearly, `year`, `month`, created_at, updated_at)
|
||||||
VALUES (#{memberId}, #{category}, #{fixed}, #{baseUnit}, #{baseAmount},
|
VALUES (#{memberId}, #{category}, #{fixed}, #{baseUnit}, #{baseAmount},
|
||||||
#{daily}, #{weekly}, #{monthly}, #{yearly}, NOW(), NOW())
|
#{daily}, #{weekly}, #{monthly}, #{yearly}, #{year}, #{month}, NOW(), NOW())
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
<!-- 전월(또는 임의 월) 예산을 다른 월로 복사. 같은 분류는 덮어씀(ON DUPLICATE) -->
|
||||||
|
<insert id="copyMonth">
|
||||||
|
INSERT INTO budget (member_id, category, fixed, base_unit, base_amount,
|
||||||
|
daily, weekly, monthly, yearly, `year`, `month`, created_at, updated_at)
|
||||||
|
SELECT member_id, category, fixed, base_unit, base_amount,
|
||||||
|
daily, weekly, monthly, yearly, #{toYear}, #{toMonth}, NOW(), NOW()
|
||||||
|
FROM budget
|
||||||
|
WHERE member_id = #{memberId} AND `year` = #{fromYear} AND `month` = #{fromMonth}
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
fixed = VALUES(fixed), base_unit = VALUES(base_unit), base_amount = VALUES(base_amount),
|
||||||
|
daily = VALUES(daily), weekly = VALUES(weekly), monthly = VALUES(monthly),
|
||||||
|
yearly = VALUES(yearly), updated_at = NOW()
|
||||||
</insert>
|
</insert>
|
||||||
|
|
||||||
<update id="update" parameterType="com.sb.web.account.domain.Budget">
|
<update id="update" parameterType="com.sb.web.account.domain.Budget">
|
||||||
|
|||||||
@@ -15,13 +15,20 @@
|
|||||||
<result property="openingBalance" column="opening_balance"/>
|
<result property="openingBalance" column="opening_balance"/>
|
||||||
<result property="openingDate" column="opening_date"/>
|
<result property="openingDate" column="opening_date"/>
|
||||||
<result property="currentValue" column="current_value"/>
|
<result property="currentValue" column="current_value"/>
|
||||||
|
<result property="loanAmount" column="loan_amount"/>
|
||||||
|
<result property="loanRate" column="loan_rate"/>
|
||||||
|
<result property="loanMethod" column="loan_method"/>
|
||||||
|
<result property="loanMonths" column="loan_months"/>
|
||||||
|
<result property="loanStart" column="loan_start"/>
|
||||||
<result property="sortOrder" column="sort_order"/>
|
<result property="sortOrder" column="sort_order"/>
|
||||||
<result property="createdAt" column="created_at"/>
|
<result property="createdAt" column="created_at"/>
|
||||||
<result property="updatedAt" column="updated_at"/>
|
<result property="updatedAt" column="updated_at"/>
|
||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
<sql id="cols">id, member_id, type, name, issuer, account_number, card_type,
|
<sql id="cols">id, member_id, type, name, issuer, account_number, card_type,
|
||||||
opening_balance, opening_date, current_value, sort_order, created_at, updated_at</sql>
|
opening_balance, opening_date, current_value,
|
||||||
|
loan_amount, loan_rate, loan_method, loan_months, loan_start,
|
||||||
|
sort_order, created_at, updated_at</sql>
|
||||||
|
|
||||||
<select id="findByMember" parameterType="long" resultMap="walletResultMap">
|
<select id="findByMember" parameterType="long" resultMap="walletResultMap">
|
||||||
SELECT <include refid="cols"/> FROM wallet
|
SELECT <include refid="cols"/> FROM wallet
|
||||||
@@ -37,10 +44,14 @@
|
|||||||
<insert id="insert" parameterType="com.sb.web.account.domain.Wallet"
|
<insert id="insert" parameterType="com.sb.web.account.domain.Wallet"
|
||||||
useGeneratedKeys="true" keyProperty="id">
|
useGeneratedKeys="true" keyProperty="id">
|
||||||
INSERT INTO wallet (member_id, type, name, issuer, account_number, card_type,
|
INSERT INTO wallet (member_id, type, name, issuer, account_number, card_type,
|
||||||
opening_balance, opening_date, current_value, sort_order, created_at, updated_at)
|
opening_balance, opening_date, current_value,
|
||||||
|
loan_amount, loan_rate, loan_method, loan_months, loan_start,
|
||||||
|
sort_order, created_at, updated_at)
|
||||||
VALUES (#{memberId}, #{type}, #{name}, #{issuer},
|
VALUES (#{memberId}, #{type}, #{name}, #{issuer},
|
||||||
#{accountNumber, typeHandler=com.sb.web.common.crypto.EncryptedStringTypeHandler}, #{cardType},
|
#{accountNumber, typeHandler=com.sb.web.common.crypto.EncryptedStringTypeHandler}, #{cardType},
|
||||||
#{openingBalance}, #{openingDate}, #{currentValue}, #{sortOrder}, NOW(), NOW())
|
#{openingBalance}, #{openingDate}, #{currentValue},
|
||||||
|
#{loanAmount}, #{loanRate}, #{loanMethod}, #{loanMonths}, #{loanStart},
|
||||||
|
#{sortOrder}, NOW(), NOW())
|
||||||
</insert>
|
</insert>
|
||||||
|
|
||||||
<update id="update" parameterType="com.sb.web.account.domain.Wallet">
|
<update id="update" parameterType="com.sb.web.account.domain.Wallet">
|
||||||
@@ -49,7 +60,10 @@
|
|||||||
account_number = #{accountNumber, typeHandler=com.sb.web.common.crypto.EncryptedStringTypeHandler},
|
account_number = #{accountNumber, typeHandler=com.sb.web.common.crypto.EncryptedStringTypeHandler},
|
||||||
card_type = #{cardType},
|
card_type = #{cardType},
|
||||||
opening_balance = #{openingBalance}, opening_date = #{openingDate},
|
opening_balance = #{openingBalance}, opening_date = #{openingDate},
|
||||||
current_value = #{currentValue}
|
current_value = #{currentValue},
|
||||||
|
loan_amount = #{loanAmount},
|
||||||
|
loan_rate = #{loanRate}, loan_method = #{loanMethod},
|
||||||
|
loan_months = #{loanMonths}, loan_start = #{loanStart}
|
||||||
WHERE id = #{id} AND member_id = #{memberId}
|
WHERE id = #{id} AND member_id = #{memberId}
|
||||||
</update>
|
</update>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user