chore: incar 14도메인 데모 샘플데이터(V108) + 외부연동 활성화 가이드

- 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>
This commit is contained in:
GA Pro
2026-05-31 21:09:12 +09:00
parent 204e3b6975
commit 51f340e36e
2 changed files with 193 additions and 0 deletions
+74
View File
@@ -0,0 +1,74 @@
# 외부연동 활성화 가이드 (KFTC 펌뱅킹 / NHN Toast / FCM 푸시)
세 외부연동 어댑터는 **코드 구현 완료** 상태이며, 기본은 모두 **Mock** 으로 동작한다.
운영 활성화는 아래 환경변수(또는 application.yml)만 주입하면 된다. 키가 비어 있으면 어댑터는 **fail-fast**(IllegalStateException) 하여 잘못된 적용을 즉시 알린다.
> 공통 전환 스위치: `app.adapter.{bank|message|push}` = `mock`(기본) / `kftc` / `toast` / `fcm`
> 어댑터별 구현체는 `ga-external-adapter` 모듈, 모든 외부호출은 `external_call_log` 에 AOP 로 자동 감사된다.
---
## 1. KFTC 오픈뱅킹 입금이체 (펌뱅킹)
- 구현체: `KftcBankTransferAdapter` (입금이체 `POST /v2.0/transfer/deposit/acnt_num` + 이체결과조회)
- 활성화: `ADAPTER_BANK=kftc`
| 환경변수 | 설명 | 예시 |
|---|---|---|
| `KFTC_BASE_URL` | API 베이스 (운영/테스트베드) | `https://openapi.openbanking.or.kr` |
| `KFTC_ACCESS_TOKEN` | 이용기관 2-legged Access Token (주기 재발급) | (발급 토큰) |
| `KFTC_CNTR_ACCOUNT_NUM` | 이용기관 약정 출금계좌 번호 | (약정계좌) |
| `KFTC_CNTR_ACCOUNT_TYPE` | 약정계좌 구분 | `N` |
| `KFTC_WD_PRINT` | 출금계좌 인자내역 | `수수료정산` |
| `KFTC_NAME_CHECK` | 수취인 실명확인 옵션 | `off` |
**사전 조건(비개발)**: 금융결제원 이용기관 계약, 약정 출금계좌 등록, Access Token 발급 체계.
**검증**: 테스트베드에서 출금 1건 → `withdraw_request.transfer_status` 전이 + `external_call_log` 확인.
---
## 2. NHN Cloud(Toast) SMS / 카카오 알림톡
- 구현체: `ToastMessageAdapter` (SMS v3.0 + 알림톡 v2.3, `MessageChannel` 분기)
- 활성화: `ADAPTER_MESSAGE=toast`
| 환경변수 | 설명 |
|---|---|
| `TOAST_SMS_APP_KEY` / `TOAST_SMS_SECRET_KEY` | SMS 프로젝트 AppKey / Secret Key |
| `TOAST_SENDER_NO` | 사전 등록 발신번호 |
| `TOAST_ALIMTALK_APP_KEY` / `TOAST_ALIMTALK_SECRET_KEY` | 알림톡 AppKey / Secret Key |
| `TOAST_SENDER_KEY` | 카카오 비즈메시지 발신프로필 SenderKey |
**사전 조건(비개발)**: NHN Cloud 프로젝트 + SMS/알림톡 상품 활성화, 발신번호 등록, 알림톡 **템플릿 등록**(발송 시 `templateCode` 필수).
**검증**: 본인 번호로 SMS 1건 발송 → `header.isSuccessful=true` + `external_call_log` 확인.
---
## 3. FCM 푸시 (모바일 PWA)
- 서버 구현체: `FcmPushAdapter` (HTTP v1 `POST /v1/projects/{projectId}/messages:send`)
- 활성화(서버): `ADAPTER_PUSH=fcm`
| 환경변수(서버) | 설명 |
|---|---|
| `FCM_PROJECT_ID` | Firebase 프로젝트 ID |
| `FCM_ACCESS_TOKEN` | 서비스계정 OAuth2 토큰(scope `firebase.messaging`, 주기 재발급) |
**PWA(클라이언트) 활성화**`ga-mobile-pwa`:
1. Firebase 콘솔 → 웹 앱 등록 → 설정값을 `.env``VITE_FCM_*` 로 주입
(`VITE_FCM_API_KEY`, `VITE_FCM_PROJECT_ID`, `VITE_FCM_APP_ID`, `VITE_FCM_SENDER_ID`, `VITE_FCM_VAPID_KEY`)
2. `npm i firebase``src/push/initPush.ts` 의 TODO 블록(firebase/messaging `getToken`) 활성화
3. `public/firebase-messaging-sw.js` 서비스워커 배치
4. 로그인 시 `initPush()` 가 토큰 발급 → `POST /api/mobile/me/device-tokens` 자동 등록
**동작 흐름(구현 완료)**: 알림 발생(예: 출금 SETTLED) → `PushDispatchService``user_device_token` 의 활성 토큰 조회 → `PushAdapter.send`. 토큰 미등록/Mock 이면 no-op.
**검증**: PWA 로그인 → device-token 등록 확인(`user_device_token`) → 출금 SETTLED 트리거 → 푸시 수신 + `external_call_log(call_type=PUSH)` 확인.
---
## 요약 — 남은 것은 "키 주입"뿐
| 연동 | 코드 | 활성화에 필요한 것(비개발) |
|---|---|---|
| KFTC 입금이체 | ✅ 완료 | 계약 + 약정계좌 + Access Token |
| Toast SMS/알림톡 | ✅ 완료 | AppKey/SecretKey + 발신번호 + 알림톡 템플릿 |
| FCM 푸시 | ✅ 완료(서버) / 스캐폴드(PWA) | Firebase 프로젝트 + `firebase` SDK + VAPID 키 |
@@ -0,0 +1,119 @@
-- V108: incar 갭 14개 도메인(P7/P8) 데모용 샘플 데이터.
-- 신규 화면이 빈 화면으로 보이지 않도록 대표 샘플을 시드한다(기존 V65/V69/V80 패턴).
-- 모든 블록 멱등(NOT EXISTS 가드). 기준 정산월 = 202605(화면 기본 조회월).
-- 참조 시드: agent 1~5, product 1~5, contract 1~5, company 1~3, org 1001~1003, grade 1~5.
-- ===== P7-1 수입수수료 + 수지차 =====
INSERT INTO income_commission_ledger (contract_id, carrier_id, settle_month, premium_amount, company_rate, income_amount, commission_type, status)
SELECT 1, 1, '202605', 1000000, 0.6000, 600000, 'RECRUIT', 'CALCULATED'
WHERE NOT EXISTS (SELECT 1 FROM income_commission_ledger WHERE contract_id=1 AND settle_month='202605' AND commission_type='RECRUIT');
INSERT INTO income_commission_ledger (contract_id, carrier_id, settle_month, premium_amount, company_rate, income_amount, commission_type, status)
SELECT 2, 2, '202605', 2000000, 0.5500, 1100000, 'MAINTAIN', 'CALCULATED'
WHERE NOT EXISTS (SELECT 1 FROM income_commission_ledger WHERE contract_id=2 AND settle_month='202605' AND commission_type='MAINTAIN');
INSERT INTO margin_summary (settle_month, agent_id, income_total, payout_total, margin_amount, margin_rate)
SELECT '202605', NULL, 1700000, 1200000, 500000, 0.2941
WHERE NOT EXISTS (SELECT 1 FROM margin_summary WHERE settle_month='202605' AND agent_id IS NULL);
-- ===== P7-2 소개·이관수수료 =====
INSERT INTO referral_commission_rule (referral_type, calc_type, commission_rate, fixed_amount, effective_from)
SELECT 'REFERRAL', 'RATE', 0.0200, NULL, DATE '2026-01-01'
WHERE NOT EXISTS (SELECT 1 FROM referral_commission_rule WHERE referral_type='REFERRAL' AND effective_from=DATE '2026-01-01');
INSERT INTO referral_commission_rule (referral_type, calc_type, commission_rate, fixed_amount, effective_from)
SELECT 'TRANSFER', 'FIXED', NULL, 50000, DATE '2026-01-01'
WHERE NOT EXISTS (SELECT 1 FROM referral_commission_rule WHERE referral_type='TRANSFER' AND effective_from=DATE '2026-01-01');
INSERT INTO referral_commission_ledger (contract_id, referrer_agent_id, settle_month, referral_type, base_amount, commission_rate, commission_amount, status)
SELECT 3, 2, '202605', 'REFERRAL', 1000000, 0.0200, 20000, 'CALCULATED'
WHERE NOT EXISTS (SELECT 1 FROM referral_commission_ledger WHERE contract_id=3 AND referrer_agent_id=2 AND settle_month='202605');
-- ===== P7-3 정착지원금 =====
INSERT INTO settling_support_rule (agent_grade, support_amount, duration_months, clawback_months, effective_from)
SELECT 'NEW', 500000, 12, 12, DATE '2026-01-01'
WHERE NOT EXISTS (SELECT 1 FROM settling_support_rule WHERE agent_grade='NEW' AND effective_from=DATE '2026-01-01');
INSERT INTO settling_support_ledger (agent_id, settle_month, support_amount, clawback_amount, net_amount, status)
SELECT 3, '202605', 500000, 0, 500000, 'PAID'
WHERE NOT EXISTS (SELECT 1 FROM settling_support_ledger WHERE agent_id=3 AND settle_month='202605');
-- ===== P7-4 인정실적 + 환산율 =====
INSERT INTO product_conversion_rate (product_id, conversion_rate, effective_from)
SELECT 1, 0.5000, DATE '2026-01-01'
WHERE NOT EXISTS (SELECT 1 FROM product_conversion_rate WHERE product_id=1 AND effective_from=DATE '2026-01-01');
INSERT INTO product_conversion_rate (product_id, conversion_rate, effective_from)
SELECT 2, 0.6000, DATE '2026-01-01'
WHERE NOT EXISTS (SELECT 1 FROM product_conversion_rate WHERE product_id=2 AND effective_from=DATE '2026-01-01');
INSERT INTO recognized_performance_ledger (agent_id, contract_id, settle_month, premium_amount, conversion_rate, converted_premium, recognition_rate, recognized_amount, status)
SELECT 1, 1, '202605', 1000000, 0.5000, 500000, 0.8000, 400000, 'CALCULATED'
WHERE NOT EXISTS (SELECT 1 FROM recognized_performance_ledger WHERE agent_id=1 AND contract_id=1 AND settle_month='202605');
-- ===== P7-5 시상 =====
INSERT INTO award_rule (award_type, calc_type, threshold_amount, award_rate, award_fixed, effective_from)
SELECT 'NEW_RECRUIT', 'FIXED', 5000000, NULL, 200000, DATE '2026-01-01'
WHERE NOT EXISTS (SELECT 1 FROM award_rule WHERE award_type='NEW_RECRUIT' AND effective_from=DATE '2026-01-01');
INSERT INTO award_rule (award_type, calc_type, threshold_amount, award_rate, award_fixed, effective_from)
SELECT 'RETENTION', 'RATE', 0, 0.0100, NULL, DATE '2026-01-01'
WHERE NOT EXISTS (SELECT 1 FROM award_rule WHERE award_type='RETENTION' AND effective_from=DATE '2026-01-01');
INSERT INTO award_ledger (agent_id, settle_month, award_type, base_amount, award_amount, status)
SELECT 1, '202605', 'NEW_RECRUIT', 0, 200000, 'CALCULATED'
WHERE NOT EXISTS (SELECT 1 FROM award_ledger WHERE agent_id=1 AND settle_month='202605' AND award_type='NEW_RECRUIT');
-- ===== P7-6 생보운영지원수수료 =====
INSERT INTO life_support_rule (agent_grade, support_rate, effective_from)
SELECT 'A', 0.0300, DATE '2026-01-01'
WHERE NOT EXISTS (SELECT 1 FROM life_support_rule WHERE agent_grade='A' AND effective_from=DATE '2026-01-01');
INSERT INTO life_support_ledger (agent_id, settle_month, base_amount, support_rate, support_amount, clawback_amount, net_amount, status)
SELECT 2, '202605', 3000000, 0.0300, 90000, 0, 90000, 'CALCULATED'
WHERE NOT EXISTS (SELECT 1 FROM life_support_ledger WHERE agent_id=2 AND settle_month='202605');
-- ===== P7-7 지점장수당 =====
INSERT INTO branch_mgr_allowance_rule (position_code, allowance_rate, cap_amount, effective_from)
SELECT 'BRANCH_MGR', 0.0200, 1000000, DATE '2026-01-01'
WHERE NOT EXISTS (SELECT 1 FROM branch_mgr_allowance_rule WHERE position_code='BRANCH_MGR' AND effective_from=DATE '2026-01-01');
INSERT INTO branch_mgr_allowance_ledger (manager_agent_id, org_id, settle_month, base_amount, allowance_rate, allowance_amount, clawback_amount, status)
SELECT 1, 1001, '202605', 10000000, 0.0200, 200000, 0, 'CALCULATED'
WHERE NOT EXISTS (SELECT 1 FROM branch_mgr_allowance_ledger WHERE manager_agent_id=1 AND org_id=1001 AND settle_month='202605');
-- ===== P8-1 단계마감 =====
INSERT INTO settlement_step_close (settle_month, step_code, step_seq, status)
SELECT '202605', x.code, x.seq, 'OPEN'
FROM (VALUES ('RECEIVE',1),('CALC',2),('VERIFY',3),('PAY',4)) AS x(code, seq)
WHERE NOT EXISTS (SELECT 1 FROM settlement_step_close s WHERE s.settle_month='202605' AND s.step_code=x.code);
-- ===== P8-2 본사정산 =====
INSERT INTO hq_settle_item (settle_month, item_type, item_name, amount, memo)
SELECT '202605', 'INCOME', '보험사 수입수수료', 50000000, '5월 수입'
WHERE NOT EXISTS (SELECT 1 FROM hq_settle_item WHERE settle_month='202605' AND item_name='보험사 수입수수료');
INSERT INTO hq_settle_item (settle_month, item_type, item_name, amount, memo)
SELECT '202605', 'EXPENSE', '운영비', 12000000, '인건비/임대료'
WHERE NOT EXISTS (SELECT 1 FROM hq_settle_item WHERE settle_month='202605' AND item_name='운영비');
-- ===== P8-3 등급평가 =====
INSERT INTO grade_eval_rule (grade_id, min_score, effective_from)
SELECT g.gid, g.score, DATE '2026-01-01'
FROM (VALUES (1, 90.00), (2, 80.00), (3, 70.00)) AS g(gid, score)
WHERE NOT EXISTS (SELECT 1 FROM grade_eval_rule r WHERE r.grade_id=g.gid AND r.effective_from=DATE '2026-01-01');
INSERT INTO agent_grade_evaluation (agent_id, eval_period, performance_score, prev_grade_id, evaluated_grade_id, status)
SELECT 1, '202605', 92.50, 2, 1, 'EVALUATED'
WHERE NOT EXISTS (SELECT 1 FROM agent_grade_evaluation WHERE agent_id=1 AND eval_period='202605');
-- ===== P8-4 공동계약 (지분율 합 1.0) =====
INSERT INTO joint_contract (contract_id, agent_id, role, share_rate)
SELECT 4, 1, 'LEAD', 0.6000
WHERE NOT EXISTS (SELECT 1 FROM joint_contract WHERE contract_id=4 AND agent_id=1);
INSERT INTO joint_contract (contract_id, agent_id, role, share_rate)
SELECT 4, 2, 'SUB', 0.4000
WHERE NOT EXISTS (SELECT 1 FROM joint_contract WHERE contract_id=4 AND agent_id=2);
-- ===== P8-5 보증보험 =====
INSERT INTO agent_guarantee_insurance (agent_id, policy_no, carrier_id, guarantee_amount, start_date, end_date, status)
SELECT 1, 'GIP-2026-0001', 1, 50000000, DATE '2026-01-01', DATE '2026-12-31', 'ACTIVE'
WHERE NOT EXISTS (SELECT 1 FROM agent_guarantee_insurance WHERE policy_no='GIP-2026-0001');
-- ===== P8-6 적립금 =====
INSERT INTO agent_reserve_fund (agent_id, settle_month, reserve_in, reserve_out, balance)
SELECT 1, '202605', 100000, 0, 100000
WHERE NOT EXISTS (SELECT 1 FROM agent_reserve_fund WHERE agent_id=1 AND settle_month='202605');
-- ===== P8-7 수수료 시뮬레이터 =====
INSERT INTO commission_simulation (sim_name, premium_amount, company_rate, payout_rate, commission_year, company_payment, agent_commission, withholding_tax, net_amount, expected_chargeback)
SELECT '표준 1차년 예시', 1000000, 0.6000, 0.7500, 1, 600000, 450000, 14850, 435150, 225000
WHERE NOT EXISTS (SELECT 1 FROM commission_simulation WHERE sim_name='표준 1차년 예시');