feat: 자주 쓰는 내역(빠른등록) + 문자/푸시 텍스트 파싱 API
- quick_entry 테이블 + 도메인/매퍼/서비스/컨트롤러 (/account/quick-entries CRUD) - /account/entries/parse: 문자·푸시 텍스트를 파서로 분석해 결과만 반환(저장X) → iPhone 등 알림 자동수집 불가 시 복사→붙여넣기 자동채움용 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
63b3ffea24
commit
a1697a7a22
@@ -8,6 +8,7 @@ import com.sb.web.account.dto.AccountEntryResponse;
|
||||
import com.sb.web.account.dto.AccountSummary;
|
||||
import com.sb.web.account.dto.ConfirmRequest;
|
||||
import com.sb.web.account.dto.NotificationRequest;
|
||||
import com.sb.web.account.dto.ParsedEntryResponse;
|
||||
import com.sb.web.account.dto.AccountTagRequest;
|
||||
import com.sb.web.account.dto.AccountTagResponse;
|
||||
import com.sb.web.account.dto.CategoryStat;
|
||||
@@ -277,6 +278,19 @@ public class AccountService {
|
||||
* 카드 결제 푸시 알림 → 미확인(pending) 지출 내역 생성.
|
||||
* 결제 알림이 아니거나 중복이면 null 반환(생성 안 함). 취소 알림도 일단 무시(v1).
|
||||
*/
|
||||
/** 문자/푸시 텍스트만 파싱해 폼 자동채움용 결과 반환 (저장 안 함). iPhone 등 알림 자동수집 불가 대비. */
|
||||
public ParsedEntryResponse parseText(NotificationRequest req) {
|
||||
CardNotificationParser.Parsed p =
|
||||
notificationParser.parse(req.getTitle(), req.getText(), java.time.LocalDate.now());
|
||||
return ParsedEntryResponse.builder()
|
||||
.recognized(p.isRecognized())
|
||||
.type(p.isIncome() ? "INCOME" : "EXPENSE")
|
||||
.amount(p.getAmount())
|
||||
.merchant(p.getMerchant())
|
||||
.date(p.getDate())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public AccountEntryResponse createFromNotification(NotificationRequest req, Long memberId) {
|
||||
CardNotificationParser.Parsed p =
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.sb.web.account.service;
|
||||
|
||||
import com.sb.web.account.domain.QuickEntry;
|
||||
import com.sb.web.account.dto.QuickEntryRequest;
|
||||
import com.sb.web.account.dto.QuickEntryResponse;
|
||||
import com.sb.web.account.mapper.QuickEntryMapper;
|
||||
import com.sb.web.common.exception.ApiException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 자주 쓰는 내역(빠른 등록 템플릿) 서비스 — 사용자별.
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class QuickEntryService {
|
||||
|
||||
private final QuickEntryMapper mapper;
|
||||
|
||||
public List<QuickEntryResponse> list(Long memberId) {
|
||||
return mapper.findByMember(memberId).stream().map(QuickEntryResponse::from).toList();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public QuickEntryResponse create(QuickEntryRequest req, Long memberId) {
|
||||
Integer max = mapper.maxSortOrder(memberId);
|
||||
QuickEntry e = QuickEntry.builder()
|
||||
.memberId(memberId)
|
||||
.label(blankToNull(req.getLabel()))
|
||||
.type(req.getType())
|
||||
.category("EXPENSE".equals(req.getType()) || "INCOME".equals(req.getType()) ? blankToNull(req.getCategory()) : null)
|
||||
.amount(req.getAmount())
|
||||
.memo(blankToNull(req.getMemo()))
|
||||
.walletId(req.getWalletId())
|
||||
.sortOrder(max == null ? 0 : max + 1)
|
||||
.build();
|
||||
mapper.insert(e);
|
||||
return QuickEntryResponse.from(mapper.findByIdAndMember(e.getId(), memberId));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void delete(Long id, Long memberId) {
|
||||
if (mapper.findByIdAndMember(id, memberId) == null) {
|
||||
throw new ApiException(HttpStatus.NOT_FOUND, "자주 쓰는 내역을 찾을 수 없습니다.");
|
||||
}
|
||||
mapper.delete(id, memberId);
|
||||
}
|
||||
|
||||
private String blankToNull(String s) {
|
||||
return (s == null || s.isBlank()) ? null : s.trim();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user