From f130d53f62effd4c062554975b91d0f0d26de226 Mon Sep 17 00:00:00 2001 From: ByungCheol Date: Sun, 28 Jun 2026 09:03:11 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=EB=B3=B5=EA=B5=AC=20-=20=EA=B3=84?= =?UTF-8?q?=EC=A2=8C=EB=A5=BC=20=EC=9D=B4=EB=A6=84=20=EB=8C=80=EC=8B=A0=20?= =?UTF-8?q?=EC=9B=90=EB=B3=B8=20ID=20=EA=B8=B0=EC=A4=80=20=EB=A7=A4?= =?UTF-8?q?=ED=95=91=20+=20=EC=9E=98=EB=AA=BB=EB=90=9C=20=EC=9D=B4?= =?UTF-8?q?=EC=B2=B4=20=EC=8A=A4=ED=82=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 같은 이름 계좌가 있어도 정확히 매핑(이름 충돌로 '출금=입금' 오류 해결) - 이체인데 계좌 없거나 같으면 해당 항목만 건너뜀(전체 롤백 방지) Co-Authored-By: Claude Opus 4.8 --- .../sb/web/account/dto/RestoreRequest.java | 41 ++++++++++++------- .../sb/web/account/service/BackupService.java | 39 ++++++++++++++---- 2 files changed, 57 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/sb/web/account/dto/RestoreRequest.java b/src/main/java/com/sb/web/account/dto/RestoreRequest.java index 8ab5225..9c4c38f 100644 --- a/src/main/java/com/sb/web/account/dto/RestoreRequest.java +++ b/src/main/java/com/sb/web/account/dto/RestoreRequest.java @@ -7,18 +7,31 @@ import java.util.List; /** * 데이터 복구(가져오기) 요청 — 엑셀 백업을 파싱한 결과. - * 참조는 이름 기준(계좌명·분류 대분류명·태그명). 서버에서 새 ID 로 재매핑한다. + * 계좌는 원본 ID(oldId) 기준으로 매핑(같은 이름 계좌가 있어도 정확). 분류·태그는 이름 기준(유니크). */ @Data public class RestoreRequest { - private List wallets; // 계좌 (이름 참조 없음 → 요청 DTO 재사용) - private List categories; // 분류 (parent = 대분류명) - private List tags; // 태그명 - private List entries; // 내역 - private List recurrings; // 고정지출 - private List budgets; // 예산 (분류명 참조) - private List quickEntries; // 자주 쓰는 내역 + private List wallets; + private List categories; + private List tags; + private List entries; + private List recurrings; + private List budgets; + private List quickEntries; + + @Data + public static class Wal { + private Long oldId; // 원본 계좌 id (매핑 키) + private String type; + private String name; + private String issuer; + private String accountNumber; + private String cardType; + private Long openingBalance; + private LocalDate openingDate; + private Long currentValue; + } @Data public static class Cat { @@ -34,10 +47,10 @@ public class RestoreRequest { private String category; private Long amount; private String memo; - private String wallet; // 계좌명 - private String toWallet; // 입금 계좌명(이체) + private Long walletId; // 원본 계좌 id + private Long toWalletId; // 원본 입금 계좌 id(이체) private Integer installmentMonths; - private List tags; // 태그명 + private List tags; // 태그명 } @Data @@ -47,8 +60,8 @@ public class RestoreRequest { private Long amount; private String category; private String memo; - private String wallet; - private String toWallet; + private Long walletId; + private Long toWalletId; private String frequency; private Integer dayOfMonth; private Integer dayOfWeek; @@ -65,6 +78,6 @@ public class RestoreRequest { private String category; private Long amount; private String memo; - private String wallet; + private Long walletId; } } diff --git a/src/main/java/com/sb/web/account/service/BackupService.java b/src/main/java/com/sb/web/account/service/BackupService.java index b6b5e0e..7bfaf00 100644 --- a/src/main/java/com/sb/web/account/service/BackupService.java +++ b/src/main/java/com/sb/web/account/service/BackupService.java @@ -57,12 +57,22 @@ public class BackupService { backupMapper.deleteTags(memberId); backupMapper.deleteWallets(memberId); - // 2) 계좌 → 이름→새 ID - Map walletMap = new HashMap<>(); + // 2) 계좌 → 원본 id(oldId) → 새 id (같은 이름 계좌가 있어도 정확) + Map walletMap = new HashMap<>(); if (req.getWallets() != null) { - for (WalletRequest w : req.getWallets()) { + for (RestoreRequest.Wal w : req.getWallets()) { if (w == null || w.getName() == null) continue; - walletMap.put(w.getName(), accountService.createWallet(w, memberId).getId()); + WalletRequest wr = new WalletRequest(); + wr.setType(w.getType()); + wr.setName(w.getName()); + wr.setIssuer(w.getIssuer()); + wr.setAccountNumber(w.getAccountNumber()); + wr.setCardType(w.getCardType()); + wr.setOpeningBalance(w.getOpeningBalance()); + wr.setOpeningDate(w.getOpeningDate()); + wr.setCurrentValue(w.getCurrentValue()); + Long newId = accountService.createWallet(wr, memberId).getId(); + if (w.getOldId() != null) walletMap.put(w.getOldId(), newId); } } @@ -101,14 +111,17 @@ public class BackupService { if (req.getEntries() != null) { for (RestoreRequest.Entry e : req.getEntries()) { if (e.getEntryDate() == null || e.getType() == null) continue; + Long wid = walletMap.get(e.getWalletId()); + Long twid = walletMap.get(e.getToWalletId()); + if (badTransfer(e.getType(), wid, twid)) continue; // 이체 계좌가 없거나 같으면 건너뜀 AccountEntryRequest er = new AccountEntryRequest(); er.setEntryDate(e.getEntryDate()); er.setType(e.getType()); er.setCategory(e.getCategory()); er.setAmount(e.getAmount()); er.setMemo(e.getMemo()); - er.setWalletId(walletMap.get(e.getWallet())); - er.setToWalletId(walletMap.get(e.getToWallet())); + er.setWalletId(wid); + er.setToWalletId(twid); er.setInstallmentMonths(e.getInstallmentMonths()); if (e.getTags() != null) { er.setTagIds(e.getTags().stream().map(tagMap::get).filter(Objects::nonNull).toList()); @@ -121,14 +134,17 @@ public class BackupService { if (req.getRecurrings() != null) { for (RestoreRequest.Recur r : req.getRecurrings()) { if (r.getTitle() == null) continue; + Long wid = walletMap.get(r.getWalletId()); + Long twid = walletMap.get(r.getToWalletId()); + if (badTransfer(r.getType(), wid, twid)) continue; RecurringRequest rr = new RecurringRequest(); rr.setTitle(r.getTitle()); rr.setType(r.getType()); rr.setAmount(r.getAmount()); rr.setCategory(r.getCategory()); rr.setMemo(r.getMemo()); - rr.setWalletId(walletMap.get(r.getWallet())); - rr.setToWalletId(walletMap.get(r.getToWallet())); + rr.setWalletId(wid); + rr.setToWalletId(twid); rr.setFrequency(r.getFrequency()); rr.setDayOfMonth(r.getDayOfMonth()); rr.setDayOfWeek(r.getDayOfWeek()); @@ -158,7 +174,7 @@ public class BackupService { qr.setCategory(q.getCategory()); qr.setAmount(q.getAmount()); qr.setMemo(q.getMemo()); - qr.setWalletId(walletMap.get(q.getWallet())); + qr.setWalletId(walletMap.get(q.getWalletId())); quickEntryService.create(qr, memberId); } } @@ -171,6 +187,11 @@ public class BackupService { return type + "|" + name; } + /** 이체인데 출금/입금 계좌가 없거나 같으면 복구에서 제외(검증 오류로 전체 롤백 방지) */ + private static boolean badTransfer(String type, Long walletId, Long toWalletId) { + return "TRANSFER".equals(type) && (walletId == null || toWalletId == null || walletId.equals(toWalletId)); + } + private boolean isEmpty(RestoreRequest r) { return empty(r.getWallets()) && empty(r.getCategories()) && empty(r.getTags()) && empty(r.getEntries()) && empty(r.getRecurrings()) && empty(r.getBudgets())