공개 문서화된 실제 API 스펙에 맞춰 스켈레톤을 RestClient 기반 실구현으로 교체.
모두 @ConditionalOnProperty 로 config-gated, 기본은 Mock 유지 → 운영 영향 0.
- ToastMessageAdapter: SMS v3.0(/sms/v3.0/appKeys/{k}/sender/sms) + 알림톡 v2.3
(/alimtalk/v2.3/appkeys/{k}/messages). X-Secret-Key 인증, MessageChannel 분기.
응답 header.isSuccessful/resultCode + requestId 매핑. ToastProperties 외부화.
- KftcBankTransferAdapter: 입금이체(/v2.0/transfer/deposit/acnt_num) + 이체결과조회.
Bearer 토큰, 문서화된 KFTC 필드(bank_tran_id/req_list/rsp_code) 매핑. KftcProperties 외부화.
- 키/토큰 누락 시 fail-fast(IllegalStateException) — 잘못된 운영 적용 즉시 감지.
- application.yml app.adapter.{kftc,toast}.* 설정(env placeholder, 빈 기본값) 추가.
- 의존성 추가 없음(ga-common 의 spring-web RestClient + Jackson 재사용).
- AdapterFailFastTest 3건(설정누락 차단/알림톡 templateCode 필수) 추가.
제외: FCM 푸시 — MessageAdapter 인터페이스/PWA 연동 계약 미정으로 추측구현 회피.
실거래 검증은 운영 키(계약·AppKey·약정계좌·토큰) 주입 후 테스트베드에서 수행 필요.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@SpringBootTest 컨텍스트가 @MapperScan(com.ga) 으로 모든 매퍼 MapperFactoryBean 을 만드는데
test 프로파일이 MybatisAutoConfiguration 을 제외해 SqlSessionFactory 가 없어 깨지던 문제.
기존엔 매퍼를 하나씩 @MockBean 으로 막았으나 신규 매퍼 누락 시마다 컨텍스트 로딩 실패.
- TestMyBatisConfig: 미접속 PGSimpleDataSource + SqlSessionFactory 제공 → 전 매퍼 빈 생성, 목록 갱신 불필요
- AbstractControllerTest @Import 에 TestMyBatisConfig 추가
- SettleControllerTest hold stub 2인자→3인자 정정(testCompile 실패 해소)
- HANDOFF §3-17 추가 + 테스트 상태 서술 정정
검증: ./gradlew test --rerun-tasks → 112건 PASS (ga-common 57 / ga-api 36 / ga-batch 19, 실패 0). DB 무관.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- 백엔드 GET /api/mobile/me/settle-masters/{id}/withdrawable-balance
· 응답 {settleId, netAmount, usedAmount, remainingAmount}
· 본인 settle_master 검증 후 net - sumActiveAmountByMaster 반환
· createWithdrawRequest 가 동일 조회 로직 재사용 (중복 제거)
- PWA WithdrawCreate: settleMaster 선택 시 잔액 카드 표시 + amount 클라이언트 사전 검증
검증: ga-api compileJava BUILD SUCCESSFUL / ga-mobile-pwa tsc --noEmit 0 오류
(라이브 e2e 는 DB/docker 미가동으로 차단 — 환경 복구 후 스모크 예정)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PWA 본인 프로필:
- Profile.tsx 신규 (phone 자기수정 Dialog + 비밀번호 변경 + 로그아웃)
- TabBar 에 "내정보" 탭 추가 (총 5탭)
- 백엔드 PUT /api/mobile/me/profile/phone (MobileMeService.updateMyPhone, 토큰에서 userId 강제)
- 비밀번호 변경은 기존 /api/auth/password 재사용
PWA 출금 신청 취소:
- 백엔드 POST /api/mobile/me/withdraw-requests/{id}/cancel
· 본인 agentId 검증 후 기존 WithdrawRequestService.cancel 호출
· REQUESTED 상태에서만 가능 (기존 검증 재사용)
- WithdrawList 행을 SwipeAction 으로 감싸 "취소" 액션 노출
· REQUESTED 상태 행만 SwipeAction 활성
출금 SETTLED/FAILED 본인 알림 자동 발행:
- WithdrawBankSettleService 가 SETTLED/FAILED 전이 시 user_notification INSERT
- 신청자(requestedBy) 에게 RefType=WITHDRAW, refId=requestId 로 발행
- 알림 발행 실패해도 동기화 흐름 비차단 (warn 로그만)
- PWA NoticeList 의 "내 알림" 탭에서 즉시 확인 가능
검증:
- ga-api compileJava + ga-frontend/ga-mobile-pwa tsc PASS
- PUT /api/mobile/me/profile/phone (admin) → 200, GET /api/auth/me 의 phone 변경 확인
- POST /api/mobile/me/withdraw-requests/1/cancel (admin) → E403 (agentId null 보안 강제)
- POST /api/internal/bank-settle → 200 data=0 (이번엔 ACCEPTED 잔여 없음, 알림 발행 경로 코드상 검증)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
외부 SDK 차단 항목 외 P6 잔여 운영성 작업을 일괄 완료. DB 변경 없음(v85 유지).
PWA:
- 출금 신청 작성 폼 (WithdrawCreate.tsx + POST /api/mobile/me/withdraw-requests + GET /settle-masters)
· 클라이언트가 보낸 agentId/userId 무시, 토큰에서 강제 결정
- 명세서 상세 + 엑셀 다운로드 (StatementDetail.tsx + GET /api/mobile/me/statements/{id}[/download])
· 본인 agentId 검증 후 기존 CommissionStatementService.download() 재사용
- 토큰 자동 refresh (request.ts interceptor + authStore.refreshToken)
· 401 시 단일 refresh 호출로 합치고 broadcast, login/refresh 자체 401 은 무한루프 차단
펌뱅킹 SETTLED 동기화:
- WithdrawBankSettleService + POST /api/internal/bank-settle (BankSettleController)
- Mapper: selectAcceptedTransfers / markSettled / markSettleFailed
- 어댑터 queryStatus 폴링 결과로 SETTLED→COMPLETED, FAILED→APPROVED(재시도풀) 전이
- app.bank-settle.cron 옵션 (기본 비활성)
사용자 phone 등록 UI:
- PUT /api/system/users/{id}/phone — NotBlank 검증 없는 단일 패치 endpoint
- UserList 에 phone 컬럼 + "전화번호" 액션 → Antd Modal 인라인 편집
- 결재 SMS 발신 전제 충족
검증 (9/9 PASS, 라이브):
- ga-common/ga-core/ga-external-adapter/ga-api compileJava BUILD SUCCESSFUL
- ga-frontend / ga-mobile-pwa tsc --noEmit PASS
- POST /api/internal/bank-settle → data=3 (3건 COMPLETED 전이 확인)
- PUT /api/system/users/1/phone → 200, 이후 GET 으로 phone 노출 확인
- 보안 강제: admin(agentId=null)으로 /api/mobile/me/{statements,settle-masters,withdraw-requests} → E403
- POST /api/auth/refresh → 200, 새 accessToken 발급
- 3개 서비스 라이브: ga-api :8082 / ga-frontend :3000 / ga-mobile-pwa :3001
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
신규 모듈 ga-external-adapter — 외부 SDK 의존성을 격리.
실제 SDK 통합 시 이 모듈의 구현체만 교체하면 ga-api/ga-common 무변경.
- ga-common.adapter.bank: BankTransferAdapter 인터페이스 + Request/Response/Status
- ga-common.adapter.message: MessageAdapter 인터페이스 + Channel(SMS/KAKAO)/Request/Response
- ga-external-adapter: MockBankTransferAdapter / MockMessageAdapter (로그 + 마스킹)
- V82: withdraw_request.transfer_tx_id/transfer_status/transfer_at + 부분 인덱스
- ApprovalService.handleApprovalCallback(WITHDRAW) — 최종 승인 시 펌뱅킹 어댑터 호출
- 성공: status APPROVED → SENT 전이, transfer_tx_id 영속화
- 실패: APPROVED 유지 + fail_reason 기록 (재시도 배치는 추후)
- ApprovalService.sendStepNotification — DB 알림 외 SMS 어댑터 호출 (non-critical)
- WithdrawRequestMapper.updateTransferResult 추가
- installment_ratio.plan_month INT NULL 컬럼 추가
- NULL = 연 단위(연차 합계, 기존 동작)
- 1~12 = 해당 회차(월) 단위 비율
- UNIQUE 재정의: NULLS NOT DISTINCT (product_category, plan_year, plan_month, effective_from)
- InstallmentPlanGenerator: planMonth=NULL이면 1, NOT NULL이면 그대로 사용해 settle_month 계산
- 분급비율 화면: 회차(월) 컬럼·입력 추가, "(연차-1)*12+월" 통합 회차 표기
전체 화면 API 점검(85종) 중 발견한 오류 수정:
- ContractAccountingEntryMapper: ac.contract_no 미존재 -> ac.policy_no AS contract_no
(contract 테이블 컬럼은 policy_no. 데이터 적재 후 표면화된 버그)
- KpiMapper: mv_*.refreshed_at 가 now() 기반 timestamptz 인데 VO는 LocalDateTime
-> refreshed_at::timestamp 캐스팅 (monthly/org/cumulative/retention 4종)
- ApprovalController/Service: /requests/mine, /requests/pending 엔드포인트 부재로
/requests/{requestId} 에 매칭돼 400 -> 두 엔드포인트 신규 추가
검증: 전체 엔드포인트 ERROR 0건. 정산 배치 데이터 + KPI MV 새로고침으로
KPI 대시보드 4화면(월별/조직별/누적/유지율) 정상 표시.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
코드 품질 자동 체인 마지막 단계. ga-api에 5a 산출물 적용 + 잔여 Controller
Mapper 직접 주입 제거.
MapStruct 적용 (수동 매핑 제거):
- ContractService.create/update — ContractMapStruct.toVO/updateVO
- LedgerService — ExceptionLedgerMapStruct.toVO
- UserService.create/update — UserMapStruct.toVO/updateVO
AgentOrgHistoryResp 적용:
- AgentService.history() 반환 List<AgentOrgHistoryVO> → List<AgentOrgHistoryResp>
- AgentMapper.selectHistoryResp(agentId) 사용
- AgentController history 시그니처 동반 변경 — 감사 필드 누설 + FK ID 노출 문제 해소
잔여 Controller Mapper 직접 주입 제거:
- AgentController/LedgerController — AgentService.exportCursor /
LedgerService.exportRecruitCursor/exportMaintainCursor 위임
- PaymentService 신설 — PaymentController에서 Mapper/TransferFileService
직접 주입 제거, PageHelper.startPage Service로 이전
- UploadTemplateService 신설 — 3개 Mapper(template/column/history) Service로 이전
PageHelper 위치 통일:
- grep -rn "startPage" controller/ → 결과 없음 (모두 Service로 이전)
남은 예외:
- BatchTriggerController.BatchJobLogMapper — 배치 트리거 특수 패턴이라 보류
- PaymentStatus Enum — 도메인 단계에서 처리 예정
리뷰에서 P0로 지적된 동시성/롱 트랜잭션 이슈 일괄 해소.
LookupCache (P0 데이터 정합성):
- 싱글톤 인스턴스 필드 캐시 제거 → preload()가 새 Map 반환, 호출자 보유
- TransformEngine.transform/applyTransform/applyLookup/applyCodeMap 시그니처에
lookupData 파라미터 추가하여 명시적 전달
- 스레드 간 공유 상태 자체가 없어져 동기화 불필요
UploadService (P0 가용성):
- 신규 UploadTxHelper로 saveHistory/insertBatch/updateHistory를 REQUIRES_NEW로 분리
- UploadService에서 @Transactional 완전 제거 — 파일 파싱은 트랜잭션 밖
- 청크(1000행) 단위 커밋 → 커넥션 점유 시간 감소
TransferFileService (P0 정합성):
- 파일 생성을 트랜잭션 밖으로 분리
- markPaymentsSent() 별도 @Transactional로 payment UPDATE만 처리
- 파일 저장 실패 시 payment 상태 미변경으로 재시도 안전성 확보
조회 메서드 readOnly=true 일괄 적용 (Hibernate dirty check 비활성화 / 읽기 복제본 라우팅 준비):
- AgentService, ContractService, LedgerService, SettleService,
OrganizationService, UserService 조회 메서드 전체
엑셀 컬럼 매핑이 더 직관적이도록:
Backend:
- TargetFieldMetaService 신규: 9개 target_table 의 사용 가능 필드 메타데이터
(recruit_ledger 15 / maintain_ledger 11 / contract 10 / agent 10 /
commission_rate 7 / payout_rule 7 / chargeback_rule 6 / exception_ledger 6 /
payment 6) — field/label/type/required
- UploadService.extractHeaders(file, headerRow, sheetIndex, previewRows):
StreamingReader 로 첫 행 헤더 + N행 미리보기 추출, A/B/C 컬럼 라벨 생성
- UploadController 에 3개 엔드포인트 추가:
· POST /api/upload-templates/excel-headers (multipart) → ExcelHeaderResp
· GET /api/upload-templates/target-fields/{targetTable}
· GET /api/upload-templates/target-tables
Frontend:
- api/uploadTemplate.ts: extractHeaders / targetFields / targetTables 함수 추가
+ ExcelHeaderResp / TargetFieldMeta 타입
- ColumnEditor 보강:
1) "샘플 엑셀로 자동 채우기" 버튼 — 사용자가 샘플 파일 올리면 헤더 추출 후
sourceColumn(A,B,C) + sourceHeader(헤더명) 자동 채움 (기존 매핑 있으면
Modal.confirm 으로 덮어쓰기 확인)
2) 대상 필드 Input → Select (target_table 별 메타 옵션) — showSearch +
선택 시 targetType 자동 설정 (필드 메타의 type 으로)
3) 헤더에 target_table tag 표시 (→ recruit_ledger)
4) 자동 채우기 후 첫 5행 샘플 데이터 테이블이 매핑 편집기 위에 표시됨
검증: tsc -b 통과, ga-api 재기동 후 /target-fields 200 (15필드 응답)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>