From 553855b31c30199482046745076c1dcfe85778a7 Mon Sep 17 00:00:00 2001 From: ByungCheol Date: Fri, 12 Jun 2026 22:52:23 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=83=81=ED=99=98/=EB=82=A9=EB=B6=80?= =?UTF-8?q?=EC=97=90=20=EC=97=B0=ED=9A=8C=EB=B9=84(=EC=B9=B4=EB=93=9C)=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=E2=80=94=20=EC=A7=80=EC=B6=9C(=EB=B6=84?= =?UTF-8?q?=EB=A5=98=20=EC=97=B0=ED=9A=8C=EB=B9=84)=EB=A1=9C=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC=20=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - RepaymentRequest.annualFee 추가, repay()에서 연회비>0이면 EXPENSE(분류=연회비) 생성 - 연회비는 카드 대상에만 허용(대출 입력 시 400) - 원금/이자/연회비 중 하나는 필수로 완화 Co-Authored-By: Claude Opus 4.8 --- .../sb/web/account/dto/RepaymentRequest.java | 5 ++++- .../web/account/service/AccountService.java | 22 ++++++++++++++++--- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/sb/web/account/dto/RepaymentRequest.java b/src/main/java/com/sb/web/account/dto/RepaymentRequest.java index bd984e9..4f9d971 100644 --- a/src/main/java/com/sb/web/account/dto/RepaymentRequest.java +++ b/src/main/java/com/sb/web/account/dto/RepaymentRequest.java @@ -8,7 +8,7 @@ import lombok.Data; import java.time.LocalDate; /** - * 대출/카드 상환·납부 요청. 원금은 이체, 이자는 지출로 분리 생성된다. + * 대출/카드 상환·납부 요청. 원금은 이체, 이자·연회비는 지출로 분리 생성된다. */ @Data public class RepaymentRequest { @@ -28,6 +28,9 @@ public class RepaymentRequest { @PositiveOrZero(message = "이자는 0 이상이어야 합니다.") private Long interest; + @PositiveOrZero(message = "연회비는 0 이상이어야 합니다.") + private Long annualFee; + @Size(max = 255, message = "메모는 255자 이내여야 합니다.") private String memo; } diff --git a/src/main/java/com/sb/web/account/service/AccountService.java b/src/main/java/com/sb/web/account/service/AccountService.java index 6763adf..90fcd09 100644 --- a/src/main/java/com/sb/web/account/service/AccountService.java +++ b/src/main/java/com/sb/web/account/service/AccountService.java @@ -292,13 +292,14 @@ public class AccountService { /* ===================== 상환/납부 ===================== */ - /** 대출/카드 상환·납부: 원금은 이체(부채↓), 이자는 지출로 분리해 2건 생성 */ + /** 대출/카드 상환·납부: 원금은 이체(부채↓), 이자·연회비는 지출로 분리 생성 */ @Transactional public void repay(RepaymentRequest req, Long memberId) { long principal = req.getPrincipal() != null ? req.getPrincipal() : 0L; long interest = req.getInterest() != null ? req.getInterest() : 0L; - if (principal <= 0 && interest <= 0) { - throw new ApiException(HttpStatus.BAD_REQUEST, "원금 또는 이자를 입력하세요."); + long annualFee = req.getAnnualFee() != null ? req.getAnnualFee() : 0L; + if (principal <= 0 && interest <= 0 && annualFee <= 0) { + throw new ApiException(HttpStatus.BAD_REQUEST, "원금·이자·연회비 중 하나는 입력하세요."); } requireOwnedWallet(req.getFromWalletId(), memberId); Wallet target = walletMapper.findByIdAndMember(req.getTargetWalletId(), memberId); @@ -308,6 +309,9 @@ public class AccountService { if (!"LOAN".equals(target.getType()) && !"CARD".equals(target.getType())) { throw new ApiException(HttpStatus.BAD_REQUEST, "상환 대상은 대출/카드만 가능합니다."); } + if (annualFee > 0 && !"CARD".equals(target.getType())) { + throw new ApiException(HttpStatus.BAD_REQUEST, "연회비는 카드 상환에만 입력할 수 있습니다."); + } if (req.getFromWalletId().equals(req.getTargetWalletId())) { throw new ApiException(HttpStatus.BAD_REQUEST, "출금/대상 계좌가 같을 수 없습니다."); } @@ -336,6 +340,18 @@ public class AccountService { .memo(req.getMemo() != null ? req.getMemo() : "이자") .build()); } + // 연회비: 지출 (카드, 출금 계좌에서, 분류=연회비) + if (annualFee > 0) { + mapper.insert(AccountEntry.builder() + .memberId(memberId) + .entryDate(req.getEntryDate()) + .type("EXPENSE") + .category("연회비") + .amount(annualFee) + .walletId(req.getFromWalletId()) + .memo(req.getMemo() != null ? req.getMemo() : "연회비") + .build()); + } } /* ===================== 계좌/카드 (사용자별) ===================== */