From f5e9b78a14735d39aee488c6c389eb416cbceb07 Mon Sep 17 00:00:00 2001 From: ByungCheol Date: Mon, 1 Jun 2026 22:54:23 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=98=88=EC=83=81=20=EC=88=98=EC=9E=85?= =?UTF-8?q?=20=EC=9B=94=EB=B3=84=20=EC=A0=80=EC=9E=A5=20+=20=EA=B3=84?= =?UTF-8?q?=EC=A2=8C=20=EC=88=9C=EC=84=9C=EB=B3=80=EA=B2=BD=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 예상 수입: budget_income(연·월별) 로 변경, GET/PUT 에 year·month - 계좌: wallet.sort_order + reorder 엔드포인트, 생성 시 맨 뒤 배치 Co-Authored-By: Claude Opus 4.8 --- .../account/controller/AccountController.java | 9 +++++++++ .../account/controller/BudgetController.java | 12 ++++++++---- .../java/com/sb/web/account/domain/Wallet.java | 1 + .../web/account/dto/WalletReorderRequest.java | 15 +++++++++++++++ .../account/mapper/AccountSettingMapper.java | 15 ++++++++++----- .../sb/web/account/mapper/WalletMapper.java | 6 ++++++ .../sb/web/account/service/AccountService.java | 15 +++++++++++++++ .../account/service/AccountSettingService.java | 10 +++++----- src/main/resources/db/account.sql | 9 ++++++--- .../resources/mapper/AccountSettingMapper.xml | 7 ++++--- src/main/resources/mapper/WalletMapper.xml | 18 ++++++++++++++---- 11 files changed, 93 insertions(+), 24 deletions(-) create mode 100644 src/main/java/com/sb/web/account/dto/WalletReorderRequest.java diff --git a/src/main/java/com/sb/web/account/controller/AccountController.java b/src/main/java/com/sb/web/account/controller/AccountController.java index 260e072..174970d 100644 --- a/src/main/java/com/sb/web/account/controller/AccountController.java +++ b/src/main/java/com/sb/web/account/controller/AccountController.java @@ -10,6 +10,7 @@ import com.sb.web.account.dto.NetWorthPoint; import com.sb.web.account.dto.NetWorthResponse; import com.sb.web.account.dto.PeriodStat; import com.sb.web.account.dto.RepaymentRequest; +import com.sb.web.account.dto.WalletReorderRequest; import com.sb.web.account.dto.WalletRequest; import com.sb.web.account.dto.WalletResponse; import com.sb.web.account.service.AccountService; @@ -69,6 +70,14 @@ public class AccountController { return ResponseEntity.noContent().build(); } + /** 계좌/카드 순서 변경 (드래그앤드랍) */ + @PutMapping("/wallets/reorder") + public List reorderWallets( + @RequestBody WalletReorderRequest req, + @RequestAttribute(AuthInterceptor.CURRENT_MEMBER) SessionUser current) { + return accountService.reorderWallets(current.getId(), req.getType(), req.getIds()); + } + @GetMapping("/networth") public NetWorthResponse netWorth( @RequestAttribute(AuthInterceptor.CURRENT_MEMBER) SessionUser current) { diff --git a/src/main/java/com/sb/web/account/controller/BudgetController.java b/src/main/java/com/sb/web/account/controller/BudgetController.java index a9c56e9..1522d33 100644 --- a/src/main/java/com/sb/web/account/controller/BudgetController.java +++ b/src/main/java/com/sb/web/account/controller/BudgetController.java @@ -28,19 +28,23 @@ public class BudgetController { private final BudgetService budgetService; private final AccountSettingService settingService; - /** 월 예상 수입 조회 */ + /** 월 예상 수입 조회 (연·월별) */ @GetMapping("/income") public ExpectedIncomeRequest getIncome( + @RequestParam int year, + @RequestParam int month, @RequestAttribute(AuthInterceptor.CURRENT_MEMBER) SessionUser current) { - return new ExpectedIncomeRequest(settingService.getExpectedIncome(current.getId())); + return new ExpectedIncomeRequest(settingService.getExpectedIncome(current.getId(), year, month)); } - /** 월 예상 수입 설정 */ + /** 월 예상 수입 설정 (연·월별) */ @PutMapping("/income") public ExpectedIncomeRequest setIncome( + @RequestParam int year, + @RequestParam int month, @Valid @RequestBody ExpectedIncomeRequest req, @RequestAttribute(AuthInterceptor.CURRENT_MEMBER) SessionUser current) { - settingService.setExpectedIncome(current.getId(), req.getExpectedIncome()); + settingService.setExpectedIncome(current.getId(), year, month, req.getExpectedIncome()); return req; } diff --git a/src/main/java/com/sb/web/account/domain/Wallet.java b/src/main/java/com/sb/web/account/domain/Wallet.java index a7a1ff5..8bd3b03 100644 --- a/src/main/java/com/sb/web/account/domain/Wallet.java +++ b/src/main/java/com/sb/web/account/domain/Wallet.java @@ -29,6 +29,7 @@ public class Wallet implements Serializable { private Long openingBalance; // 초기 잔액(부호있음: 자산+, 부채-) private LocalDate openingDate; private Long currentValue; // 현재 평가금액 (INVEST 전용, 수동 갱신) + private Integer sortOrder; // 표시 순서 (타입 내 정렬) private LocalDateTime createdAt; private LocalDateTime updatedAt; } diff --git a/src/main/java/com/sb/web/account/dto/WalletReorderRequest.java b/src/main/java/com/sb/web/account/dto/WalletReorderRequest.java new file mode 100644 index 0000000..cd2d3ca --- /dev/null +++ b/src/main/java/com/sb/web/account/dto/WalletReorderRequest.java @@ -0,0 +1,15 @@ +package com.sb.web.account.dto; + +import lombok.Data; + +import java.util.List; + +/** + * 계좌/카드 순서 변경 요청. ids 순서대로 sort_order 를 재설정한다. + */ +@Data +public class WalletReorderRequest { + + private String type; // BANK / CARD / LOAN / INVEST + private List ids; // 새 순서의 wallet id 목록 +} diff --git a/src/main/java/com/sb/web/account/mapper/AccountSettingMapper.java b/src/main/java/com/sb/web/account/mapper/AccountSettingMapper.java index b3f7094..2629dc4 100644 --- a/src/main/java/com/sb/web/account/mapper/AccountSettingMapper.java +++ b/src/main/java/com/sb/web/account/mapper/AccountSettingMapper.java @@ -4,14 +4,19 @@ import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; /** - * 사용자별 가계부 설정 매퍼 (account_setting). + * 월 예상 수입 매퍼 (budget_income, 월별). */ @Mapper public interface AccountSettingMapper { - /** 월 예상 수입 (없으면 null) */ - Long findExpectedIncome(@Param("memberId") Long memberId); + /** 해당 연·월의 예상 수입 (없으면 null) */ + Long findExpectedIncome(@Param("memberId") Long memberId, + @Param("year") int year, + @Param("month") int month); - /** 월 예상 수입 upsert */ - int upsertExpectedIncome(@Param("memberId") Long memberId, @Param("expectedIncome") Long expectedIncome); + /** 해당 연·월 예상 수입 upsert */ + int upsertExpectedIncome(@Param("memberId") Long memberId, + @Param("year") int year, + @Param("month") int month, + @Param("expectedIncome") Long expectedIncome); } diff --git a/src/main/java/com/sb/web/account/mapper/WalletMapper.java b/src/main/java/com/sb/web/account/mapper/WalletMapper.java index bf6025d..ba12c0a 100644 --- a/src/main/java/com/sb/web/account/mapper/WalletMapper.java +++ b/src/main/java/com/sb/web/account/mapper/WalletMapper.java @@ -25,4 +25,10 @@ public interface WalletMapper { int update(Wallet wallet); int delete(@Param("id") Long id, @Param("memberId") Long memberId); + + /** 순서(sort_order) 변경 */ + int updateSortOrder(@Param("id") Long id, @Param("memberId") Long memberId, @Param("sortOrder") int sortOrder); + + /** 해당 타입의 현재 최대 sort_order (신규 계좌를 맨 뒤에 배치) */ + Integer maxSortOrder(@Param("memberId") Long memberId, @Param("type") String type); } 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 e7c3d27..21de589 100644 --- a/src/main/java/com/sb/web/account/service/AccountService.java +++ b/src/main/java/com/sb/web/account/service/AccountService.java @@ -301,10 +301,25 @@ public class AccountService { public WalletResponse createWallet(WalletRequest req, Long memberId) { Wallet wallet = toWallet(req); wallet.setMemberId(memberId); + Integer max = walletMapper.maxSortOrder(memberId, req.getType()); + wallet.setSortOrder(max == null ? 0 : max + 1); // 같은 타입의 맨 뒤 walletMapper.insert(wallet); return WalletResponse.from(wallet, wallet.getOpeningBalance()); // 신규 → 잔액=초기잔액 } + /** 드래그앤드랍 순서 변경: 전달된 id 순서대로 sort_order 재설정 (본인·동일 타입만) */ + @Transactional + public List reorderWallets(Long memberId, String type, List ids) { + int i = 0; + for (Long id : ids) { + Wallet w = walletMapper.findByIdAndMember(id, memberId); + if (w != null && w.getType().equals(type)) { + walletMapper.updateSortOrder(id, memberId, i++); + } + } + return listWallets(memberId); + } + @Transactional public WalletResponse updateWallet(Long id, WalletRequest req, Long memberId) { Wallet wallet = mustFindWallet(id, memberId); diff --git a/src/main/java/com/sb/web/account/service/AccountSettingService.java b/src/main/java/com/sb/web/account/service/AccountSettingService.java index 5a3ac97..5112053 100644 --- a/src/main/java/com/sb/web/account/service/AccountSettingService.java +++ b/src/main/java/com/sb/web/account/service/AccountSettingService.java @@ -6,7 +6,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; /** - * 사용자별 가계부 설정 서비스 (월 예상 수입 등). + * 월 예상 수입 설정 서비스 (월별). */ @Service @RequiredArgsConstructor @@ -14,12 +14,12 @@ public class AccountSettingService { private final AccountSettingMapper settingMapper; - public Long getExpectedIncome(Long memberId) { - return settingMapper.findExpectedIncome(memberId); + public Long getExpectedIncome(Long memberId, int year, int month) { + return settingMapper.findExpectedIncome(memberId, year, month); } @Transactional - public void setExpectedIncome(Long memberId, Long expectedIncome) { - settingMapper.upsertExpectedIncome(memberId, expectedIncome); + public void setExpectedIncome(Long memberId, int year, int month, Long expectedIncome) { + settingMapper.upsertExpectedIncome(memberId, year, month, expectedIncome); } } diff --git a/src/main/resources/db/account.sql b/src/main/resources/db/account.sql index a8bde3a..104f3f5 100644 --- a/src/main/resources/db/account.sql +++ b/src/main/resources/db/account.sql @@ -27,6 +27,7 @@ CREATE TABLE IF NOT EXISTS wallet ( ALTER TABLE wallet ADD COLUMN IF NOT EXISTS opening_balance BIGINT NOT NULL DEFAULT 0; 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 sort_order INT NOT NULL DEFAULT 0; CREATE TABLE IF NOT EXISTS account_entry ( id BIGINT NOT NULL AUTO_INCREMENT, @@ -123,12 +124,14 @@ CREATE TABLE IF NOT EXISTS account_entry_tag ( KEY idx_aet_tag (tag_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; --- 사용자별 가계부 설정 (예: 월 예상 수입) -CREATE TABLE IF NOT EXISTS account_setting ( +-- 사용자별 월 예상 수입 (월별 저장) +CREATE TABLE IF NOT EXISTS budget_income ( member_id BIGINT NOT NULL COMMENT '소유자 member.id', + `year` INT NOT NULL, + `month` INT NOT NULL, expected_income BIGINT NULL COMMENT '월 예상 수입', updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (member_id) + PRIMARY KEY (member_id, `year`, `month`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- 투자 보유 종목 (INVEST 지갑별 · 사용자별) diff --git a/src/main/resources/mapper/AccountSettingMapper.xml b/src/main/resources/mapper/AccountSettingMapper.xml index 9534ee0..0fb3bc8 100644 --- a/src/main/resources/mapper/AccountSettingMapper.xml +++ b/src/main/resources/mapper/AccountSettingMapper.xml @@ -4,12 +4,13 @@ - INSERT INTO account_setting (member_id, expected_income, updated_at) - VALUES (#{memberId}, #{expectedIncome}, NOW()) + INSERT INTO budget_income (member_id, `year`, `month`, expected_income, updated_at) + VALUES (#{memberId}, #{year}, #{month}, #{expectedIncome}, NOW()) ON DUPLICATE KEY UPDATE expected_income = #{expectedIncome}, updated_at = NOW() diff --git a/src/main/resources/mapper/WalletMapper.xml b/src/main/resources/mapper/WalletMapper.xml index 2f8d4b7..7d738f9 100644 --- a/src/main/resources/mapper/WalletMapper.xml +++ b/src/main/resources/mapper/WalletMapper.xml @@ -14,17 +14,18 @@ + id, member_id, type, name, issuer, account_number, card_type, - opening_balance, opening_date, current_value, created_at, updated_at + opening_balance, opening_date, current_value, sort_order, created_at, updated_at + SELECT MAX(sort_order) FROM wallet WHERE member_id = #{memberId} AND type = #{type} + +