CalcRecruitStep(Step4)이 1200%룰 clip 초과분을 installment_plan(7년)으로 생성할 때,
재계산을 위해 recruit_ledger는 deleteBySettleMonth로 지우면서 분급은 정리하지 않아
같은 정산월을 재실행하면 동일 계약이 또 clip되어 동일 분급키(contract_id,plan_year,
plan_month)로 INSERT → UNIQUE 충돌로 Job 실패하던 버그.
수정: insertBatch 직전에 clip된 계약들의 SCHEDULED(미지급) 분급을 먼저 삭제
(InstallmentPlanMapper.deleteScheduledByContractIds). PAID 분급 이력은 보존하여
PayInstallmentStep 지급분과 충돌하지 않음.
검증: CalcRecruitStepTest 멱등 테스트(clip 시 delete→insert 순서/대상계약 검증) 추가,
전체 build SUCCESS. 라이브 E2E: 202605 정산을 연속 2회 실행 → 둘 다 COMPLETED,
분급 196건 불변(중복 0), settle_master gross 동일, 무결성 유지.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- fix(HIGH): CalcOverrideStep base 가 override_rule.insurance_type_cond 를 무시하고
하위 설계사의 전 보험종류 실적 합계에 율을 곱해 과지급. 룰에 보험종류 조건이
지정되면(LIFE 등) 해당 종류 실적만 base 로 집계하도록 수정.
(현 시드는 insurance_type_cond NULL 이라 잠복상태였으나 종류한정 룰 도입 시 과지급)
- add: RecruitLedgerMapper/MaintainLedgerMapper.sumByAgentMonthAndType + XML
- test: CalcOverrideStepTest — LIFE 한정 룰 base 필터 검증
- 전체 build+test GREEN: 153건 0실패
잔여 백로그(정책결정 필요 — 자동수정 보류): 1200%룰 초과분 clip/이연(지급정책),
계약승계 from검증(다단계 체인+contract.agent_id 갱신정책), 정착지원금 환수공식(입력
스키마), 회계 원천세 예수금 분개(회계정책), net음수 클램프(이월저장), 세금 3중경로 통일.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- V108__incar도메인_샘플데이터.sql: P7/P8 14개 도메인 대표 샘플 시드(멱등 NOT EXISTS,
기준월 202605). 신규 화면이 빈 화면으로 보이지 않도록 — 기존 V65/V69/V80 패턴.
ROLLBACK 트랜잭션 사전검증(오류 0) 후 Flyway V108 success.
라이브 확인: 12개 신규 엔드포인트 전부 200 + 샘플 rows 반환.
- docs/외부연동_활성화_가이드.md: KFTC/Toast/FCM 어댑터 운영 활성화 절차
(전환 스위치 app.adapter.*, 환경변수 매트릭스, 사전조건, 검증법).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
공개 문서화된 실제 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>
- 목차(TOC) 추가
- §3 보험 기초(보험을 처음 접한다면) — 보험료/보험종류/유지율/위촉해촉 등 입문 용어
- §7 수수료 계산 공식과 숫자 예시 — 기본 3단계 공식(보험료×보험사요율×지급률)
+ 모집/분급(7년)/차등환수/오버라이드/세금상세 worked example
(CommissionCalculator·TaxCalculator·CalcOverrideStep 실제 코드 기준)
- §13 용어 사전 확장(보험 기초 용어 + 요율 용어 추가)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- GlobalExceptionHandler: MissingServletRequestParameterException 핸들러 추가.
기존엔 미처리로 generic Exception→E500. 이제 INVALID_PARAMETER(400) 반환.
(전수 점검 중 /api/settle/summary 가 settleMonth 누락 시 500 반환하던 결함 발견)
- smoke_allmenus.py: 전체 메뉴 화면 1차 GET 엔드포인트 67개 전수 점검 스크립트.
admin 전권 로그인 후 5xx 만 FAIL 분류. 검증: 66 PASS + 1 정상400, 5xx 0건.
- DOMAIN_KNOWLEDGE.md: 보험 GA 수수료 정산 도메인 입문 문서
(수수료 종류/규제룰/세무공제/12단계 정산 파이프라인/상태흐름/용어사전).
검증: 5모듈 compileJava + ga-frontend vite build + PWA tsc PASS,
전체 테스트 112건 PASS, 모바일 me/* 5개 200.
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>
AccountingEntryGenerateStep(Step8b)이 selectBySettleMonth 결과를 무조건 insert 하여
동일 정산월 재정산 시 분개가 누적 중복되던 버그. payment_id 가 항상 NULL(원장 기반)이라
기존 주석의 existsByPaymentId 중복방어는 불가능했음.
- V87: contract_accounting_entry.settle_month 컬럼 추가 + 기존데이터 백필(description 6자리) + 미전기 부분 인덱스
- core: VO settleMonth 필드, Mapper deleteUnpostedBySettleMonth, insertBatch/selectBySettleMonth 에 settle_month 반영
- batch: 재생성 전 deleteUnpostedBySettleMonth 호출(마감 채번분 journal_no IS NOT NULL 보존)
검증: ga-core/ga-batch/ga-api compileJava SUCCESS, Flyway v87 적용(백필 433건 NULL 0),
202605 정산 2회 실행 → 193건 유지(중복 0, 수정 전이라면 386). 멱등성 PASS. 테스트데이터 롤백 완료.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
AggregateStep(Step8) 이 차감 APPLIED 일괄 전이로 호출하는 updateStatusBatch 가
인터페이스에만 선언되고 XML 구문이 없어, PENDING 차감이 있는 정산월 집계 시
'Invalid bound statement (not found)' 로 aggregate 전체가 FAILED 되던 잠복 버그.
updateStatus 동일 패턴 + foreach IN 으로 구현. (계약승계 e2e 중 발견, 승계와 무관)
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>
이전 커밋(5ee8321)에 검증 중 만든 login.json/token.txt 가 실수로 포함되어
JWT 토큰이 히스토리에 노출됨. 토큰은 이미 expired 되었지만(7200s) 즉시 제거.
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>
정산/지급 화면이 전부 비어있던 원인: 운영 DB에서 정산 배치가 실행된 적 없음.
MONTHLY_SETTLEMENT 배치를 돌리는 과정에서 발견한 버그 수정 + 데이터 구성.
매퍼 버그 (PG: could not determine data type of parameter):
- RegulatoryLimitMapper/ChargebackGradeMapper/InstallmentRatioMapper:
#{productCategory} 가 IS NULL 비교에 쓰여 타입 추론 실패 -> jdbcType=VARCHAR 명시
- ForecastKpiMonthlyMapper: #{targetOrgId} 동일 -> jdbcType=BIGINT 명시
스키마 (V79):
- override_settle.settle_id NOT NULL 완화 — calcOverride(Step7)는 settle_master
생성(Step8) 전 단계라 settle_id 를 채울 수 없음
데이터 (V80):
- 배치 실행 결과: settle_master 150 / 원장 433 / 명세서 112 / 분개 433 (2026-02~04)
- payment 는 settle_master 파생 생성, 운영성 도메인(공제/마감/세금계산서/이의신청/
시책/정정)은 대표 샘플 시드 (멱등 NOT EXISTS)
검증: 배치 12스텝 COMPLETED, 정산/지급 10개 엔드포인트 200 OK.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
비개발자도 더블클릭 한 번으로 백엔드(ga-api 8082)+프론트(ga-frontend 3000)를
동시에 띄우도록 실행.bat 추가. 실행/접속/종료/문제해결을 실행방법.md에 정리.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>