feat: incar 갭 반영 수수료/운영 14개 도메인 풀스택 + 배치통합 + 신입교육자료
incar CMS(116메뉴) 벤치마킹 갭분석으로 ga-pro에 없던 14개 도메인을 풀스택 추가. P7 수수료 계산 7종 (V88~V96): - 수입수수료+수지차, 소개·이관수수료, 정착지원금, 인정실적+환산율, 시상, 생보운영지원수수료, 지점장수당 - 각 rule/ledger + 공식(보험료×요율, 수입−지급 등) Service 내장 P8 운영/정산 7종 (V97~V105): - 단계마감, 본사정산, 등급평가, 공동계약, 보증보험, 적립금, 수수료시뮬레이터 - close/reopen/apply/simulate/upsert 등 액션 + 손익·잔액·등급 산정 로직 배치 통합 (V106): - settle_master.other_commission_total 컬럼 추가(DEFAULT 0, 백필) - 설계사 지급성 9종 원장 aggregateByAgent → AggregateStep gross 합산 - 빈 원장 0 → 기존 정산결과·무결성 불변(비파괴적) 테스트: P7/P8 Service 공식 단위테스트 21건(ga-api:test 57건 전체 통과) 프론트: 화면 14개 + React.lazy 코드 스플리팅(단일 4MB → 130+ 청크) 문서: DOMAIN_GAP_P7/P8.md, 신입교육_보험과수수료_완전기초.md, DOMAIN_KNOWLEDGE/HANDOFF 갱신 검증: 전체 ./gradlew build(test 포함) SUCCESSFUL, Flyway V88~V106 success, GET+액션POST smoke 5xx 0건, 9개 집계쿼리 SQL 유효성 확인, 공식 런타임 실측. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+24
-3
@@ -91,6 +91,13 @@
|
||||
| **유지보수당(지속수당)** | persistency bonus | 일정 유지율 달성 시 | 설계사 | 보너스 성격 |
|
||||
| **오버라이드** | override | 하위 설계사 실적 발생 시 | **상위 직책자** | 조직 트리 기반 수당 |
|
||||
| **시책·인센티브** | incentive | 캠페인/목표 달성 시 | 설계사 | 한시적 프로모션 |
|
||||
| **수입수수료** | income | 보험사가 GA에 지급한 수수료 수입 원장 | GA 경영진/재무 | 수지차(=수입-지급) 집계용. 설계사 지급과 분리 관리 |
|
||||
| **소개·이관수수료** | referral/transfer | 계약을 소개(REFERRAL)하거나 이관(TRANSFER)한 설계사에게 | 소개/이관 설계사 | 정률 또는 정액. 기준금액×율 또는 고정액 |
|
||||
| **정착지원금** | settling support | 신입/경력 설계사 입사 후 약정 기간 | 신규 설계사 | 등급별 월 정액. 조기 해촉 시 잔여약정 비율로 환수 |
|
||||
| **인정실적** | recognized performance | 계약 체결 시 (환산보험료 기준) | 설계사 | 환산보험료×인정율. 시상·등급 산정의 기초 지표 |
|
||||
| **시상** | award | 목표 실적 달성 시 | 설계사 | 신인도입/법인/유지율/실적 시상. 정률 또는 정액 |
|
||||
| **생보운영지원수수료** | life op support | 생보 계약 운영 실적 기준 | 설계사 | 실적×지급률. 환수 가능 |
|
||||
| **지점장수당** | branch mgr allowance | 소속 조직 실적 발생 시 | 지점장 | 조직실적×수당율, cap(상한) 제한. 환수 가능 |
|
||||
|
||||
> **모집 vs 유지의 귀속 차이 (중요):** 설계사가 해촉·이관되면, 그 계약의 **유지수수료는 후임 설계사**에게 넘어가지만(계약 승계),
|
||||
> **모집수수료는 원래 모집한 설계사**에게 그대로 남는다. (한국 고아계약 승계 표준)
|
||||
@@ -245,7 +252,21 @@ gross(총수수료)
|
||||
> 여기에 가지급금 등 **공제**가 있으면 net에서 추가로 빠집니다.
|
||||
> 비과세/면세 설계사는 부가세(+45,000) 항목이 없습니다.
|
||||
|
||||
### 7-7. 한 장 정리
|
||||
### 7-7. P7 신규 수수료 계산 공식 (V88~V96, incar 갭 반영)
|
||||
|
||||
| 종류 | 공식 | 비고 |
|
||||
|---|---|---|
|
||||
| **수입수수료** | `income_amount = premium × company_rate` | 보험사→GA 수입 원장. 수지차 = Σincome − Σpayout |
|
||||
| **소개·이관수수료** | `commission_amount = base × rate` (RATE형) 또는 `fixed_amount` (FIXED형) | 계약 소개/이관 설계사에게 지급 |
|
||||
| **정착지원금** | 지급: 등급별 `support_amount` (월 정액). 환수: `support_amount × (잔여약정개월 / 약정개월)` (조기 해촉 시) | `net = support_amount − clawback_amount` |
|
||||
| **인정실적(환산보험료)** | `converted_premium = premium × conversion_rate`. `recognized_amount = converted_premium × recognition_rate` | 시상/등급 산정 기초. 상품별 환산율은 `product_conversion_rate` |
|
||||
| **시상** | `award_amount = base × award_rate` (RATE형) 또는 `award_fixed` (FIXED형) | `threshold_amount` 이상 실적 달성 시만 발동 |
|
||||
| **생보운영지원수수료** | `support_amount = base × support_rate`. `net = support_amount − clawback_amount` | 생보 실적 기준 지급 |
|
||||
| **지점장수당** | `allowance_amount = LEAST(base × allowance_rate, cap_amount)` | cap=NULL이면 무제한. `net = allowance_amount − clawback_amount` |
|
||||
|
||||
> 모든 금액 `NUMERIC(15,2)`, 율 `NUMERIC(7,4)`(0~1 소수 표현), 반올림 HALF_UP.
|
||||
|
||||
### 7-8. 한 장 정리
|
||||
|
||||
```
|
||||
보험료 ──×보험사요율──▶ 보험사지급액 ──×지급률──▶ 설계사수수료(gross)
|
||||
@@ -359,5 +380,5 @@ DRAFT(계산됨) → CONFIRMED(확정) → PAID(지급완료)
|
||||
|
||||
---
|
||||
|
||||
*기준: 한국 보험업감독규정 표준 가정값. 시스템 schema v87 (2026-05 기준). 표준값은 모두 데이터로 외부화되어 코드 변경 없이 조정 가능.*
|
||||
*계산 공식 출처: `CommissionCalculator` / `TaxCalculator` / `CalcOverrideStep` 실제 코드.*
|
||||
*기준: 한국 보험업감독규정 표준 가정값. 시스템 schema v96 (2026-05-31 기준, P7 incar 갭 반영). 표준값은 모두 데이터로 외부화되어 코드 변경 없이 조정 가능.*
|
||||
*계산 공식 출처: `CommissionCalculator` / `TaxCalculator` / `CalcOverrideStep` + P7 7개 도메인 Service (V88~V96) 실제 코드.*
|
||||
|
||||
+26
-2
@@ -377,7 +377,7 @@ PWA 스캐폴드에서 클라이언트가 `agentId` 를 쿼리 파라미터로
|
||||
P5 전체 + P6 PoC + **P6 후속(2026-05-27)** 완료. 잔여는 외부 SDK 차단 항목만.
|
||||
|
||||
### 운영 DB 마이그레이션 상태
|
||||
운영 DB `192.168.0.60:55432/trading_ai/ga` 현재 **V85 적용 완료** (변경 없음).
|
||||
운영 DB `192.168.0.60:55432/trading_ai/ga` 현재 **V96 적용 완료** (P7 incar 갭 반영 7도메인).
|
||||
ga-api 8082 / ga-frontend 3000 / ga-mobile-pwa 3001 모두 라이브.
|
||||
|
||||
단위/통합 테스트: **SqlSessionFactory 환경 문제 해결 완료 (2026-05-29, §3-17)**. `./gradlew test` → 112건 전부 PASS (ga-common 57 / ga-api 36 / ga-batch 19).
|
||||
@@ -414,6 +414,30 @@ ga-api 8082 / ga-frontend 3000 / ga-mobile-pwa 3001 모두 라이브.
|
||||
| 카카오 알림톡 본체 | 비즈채널·템플릿 승인 |
|
||||
| 푸시 알림 (FCM 등) | 외부 채널 결정 |
|
||||
|
||||
## 3-18. P7 — incar 갭 반영 수수료 계산 7개 도메인 풀스택 (V88~V96 — 완료, 2026-05-31 라이브 적용)
|
||||
|
||||
incar CMS 갭 분석 결과 GA 시스템에 없던 수수료 계산 7개 도메인을 DB→Core→API→Frontend 전 레이어로 추가. 운영 DB schema **v96**.
|
||||
|
||||
| 도메인 | 마이그레이션 | 테이블 | API 경로 |
|
||||
|---|---|---|---|
|
||||
| 수입수수료+수지차 (INCOME_COMM) | V88 | income_commission_ledger, margin_summary | `/api/income-commissions/*` |
|
||||
| 소개·이관수수료 (REFERRAL_COMM) | V89 | referral_commission_rule, referral_commission_ledger | `/api/referral-commissions/*` |
|
||||
| 정착지원금 (SETTLING_SUPPORT) | V90 | settling_support_rule, settling_support_ledger | `/api/settling-supports/*` |
|
||||
| 인정실적+환산보험료 (RECOGNIZED_PERF) | V91 | product_conversion_rate, recognized_performance_ledger | `/api/recognized-performances/*` |
|
||||
| 시상 (AWARD) | V92 | award_rule, award_ledger | `/api/awards/*` |
|
||||
| 생보운영지원수수료 (LIFE_OP_SUPPORT) | V93 | life_support_rule, life_support_ledger | `/api/life-supports/*` |
|
||||
| 지점장수당 (BRANCH_MGR_ALLOWANCE) | V94 | branch_mgr_allowance_rule, branch_mgr_allowance_ledger | `/api/branch-allowances/*` |
|
||||
| 메뉴 7개 | V95 | — | GRP_COMMISSION 하위 sort 80~140 |
|
||||
| 공통코드 5그룹 | V96 | — | INCOME_STATUS/REFERRAL_TYPE/CALC_TYPE/SUPPORT_STATUS/AWARD_TYPE |
|
||||
|
||||
통합 검증 (2026-05-31):
|
||||
- `./gradlew build -x test` BUILD SUCCESSFUL
|
||||
- Flyway V88~V96 9건 모두 success=True, 운영 DB schema v96
|
||||
- 신규 14테이블 전부 존재 확인, 신규 메뉴 7건 등록 확인
|
||||
- 기존 smoke_allmenus.py 67엔드포인트: PASS=66 FAIL=0 SKIP=1(settle/summary 필수파라미터, 기존 동일)
|
||||
- P7 신규 8엔드포인트(인정실적은 /rates+/ledgers 분리): PASS=8 FAIL=0
|
||||
- 주의: `/api/recognized-performances/rules` 는 없음. D4는 `/rates`(환산율)와 `/ledgers`(원장) 두 경로로 분리됨.
|
||||
|
||||
## 3-16. 계약 승계 (contract_succession, V86 — 완료, 2026-05-29 라이브 적용)
|
||||
|
||||
설계사 해촉/이관 시 보험계약의 **유지수수료(maintain) 귀속 설계사를 승계월 기준 후임으로 전환**.
|
||||
@@ -509,7 +533,7 @@ ga-api 8082 / ga-frontend 3000 / ga-mobile-pwa 3001 모두 라이브.
|
||||
|
||||
1. **Claude Code 시작 시 cwd 확인**: `C:\Users\kyu\Desktop\ga_pro`
|
||||
2. **HANDOFF.md 읽기**: 본 문서 §3-16(계약 승계) + §3-15(P6 후속) + 최근 git log
|
||||
3. **운영 DB 상태**: **V86 적용 완료** (계약 승계). ga-api 8082 가동. ga-frontend 3000 / ga-mobile-pwa 3001 은 필요 시 기동. origin/main 동기화 완료(HEAD `4dbc688`, 2026-05-29 push).
|
||||
3. **운영 DB 상태**: **V96 적용 완료** (P7 incar 7도메인). ga-api 8082 가동. ga-frontend 3000 / ga-mobile-pwa 3001 은 필요 시 기동. origin/main 동기화 완료(HEAD `4dbc688`, 2026-05-29 push). P7 코드(V88~V96)는 로컬 커밋 아직 없음.
|
||||
4. **재기동이 필요한 경우**:
|
||||
- `cd ga-commission-system && nohup ./gradlew :ga-api:bootRun --args='--spring.profiles.active=trading_ai' > ../ga-api-bootrun.log 2>&1 & disown`
|
||||
- `cd ga-commission-system/ga-frontend && npm run dev` (3000)
|
||||
|
||||
@@ -0,0 +1,255 @@
|
||||
# P7 — incar 갭 반영: 수수료 계산 7개 도메인
|
||||
|
||||
작성: PL (자율 모드), 2026-05-31
|
||||
근거: incar CMS 116메뉴 vs ga-pro 96메뉴 갭 분석. 사용자 지시 "필요한 메뉴 적용 + 수수료 공식 다 적용".
|
||||
범위: 🅰 수수료 계산 7개 도메인 풀스택 (DB→Core→API→Frontend→Infra).
|
||||
|
||||
기존 패턴 100% 준수 (template = 수금수수료 V41 / CollectionCommission*). 모든 금액 `NUMERIC(15,2)`, 율 `NUMERIC(7,4)`(소수, 0~1), 반올림 HALF_UP.
|
||||
|
||||
Flyway: **V87이 최신 → V88부터.** 각 도메인은 rule(+) ledger 2테이블 패턴. 메뉴는 GRP_COMMISSION 하위.
|
||||
|
||||
---
|
||||
|
||||
## 도메인별 설계
|
||||
|
||||
### D1. 수입수수료 + 수지차 (INCOME_COMM) — V88
|
||||
보험사가 GA에 지급한 **수입** 측. 기존 원장(LEDGER_*)은 설계사 **지급** 측. 수지차 = 수입 − 지급.
|
||||
|
||||
**income_commission_ledger** (수입수수료 원장)
|
||||
| 컬럼 | 타입 | 설명 |
|
||||
|---|---|---|
|
||||
| income_id | BIGSERIAL PK | |
|
||||
| contract_id | BIGINT NN FK contract | |
|
||||
| carrier_id | BIGINT NN FK insurance_company | 보험사 |
|
||||
| settle_month | CHAR(6) NN | |
|
||||
| premium_amount | NUMERIC(15,2) NN | 보험료 |
|
||||
| company_rate | NUMERIC(7,4) NN | 보험사요율 |
|
||||
| income_amount | NUMERIC(15,2) NN | **= premium_amount × company_rate** |
|
||||
| commission_type | VARCHAR(20) NN | RECRUIT/MAINTAIN 등 |
|
||||
| status | VARCHAR(20) NN DEFAULT 'CALCULATED' | CALCULATED/CONFIRMED/CANCELLED |
|
||||
| created_at/updated_at | | |
|
||||
idx: (carrier_id, settle_month), (contract_id), (status, settle_month)
|
||||
|
||||
**margin_summary** (수지차 집계 — recalc 액션으로 채움)
|
||||
| 컬럼 | 타입 | 설명 |
|
||||
|---|---|---|
|
||||
| margin_id | BIGSERIAL PK | |
|
||||
| settle_month | CHAR(6) NN | |
|
||||
| agent_id | BIGINT FK agent (NULL이면 전체) | |
|
||||
| income_total | NUMERIC(15,2) NN DEFAULT 0 | 수입 합 |
|
||||
| payout_total | NUMERIC(15,2) NN DEFAULT 0 | 지급 합(settle_master 기준) |
|
||||
| margin_amount | NUMERIC(15,2) NN DEFAULT 0 | **= income_total − payout_total** |
|
||||
| margin_rate | NUMERIC(7,4) | = margin/income (income>0) |
|
||||
| created_at | | |
|
||||
UNIQUE(settle_month, agent_id)
|
||||
|
||||
**공식**: income_amount = premium × company_rate. margin = Σincome − Σpayout.
|
||||
|
||||
### D2. 소개·이관수수료 (REFERRAL_COMM) — V89
|
||||
계약을 소개(REFERRAL)하거나 이관(TRANSFER)한 설계사에게 주는 수수료.
|
||||
|
||||
**referral_commission_rule**
|
||||
| 컬럼 | 타입 | 설명 |
|
||||
|---|---|---|
|
||||
| rule_id | BIGSERIAL PK | |
|
||||
| referral_type | VARCHAR(20) NN | REFERRAL/TRANSFER |
|
||||
| calc_type | VARCHAR(10) NN | RATE/FIXED |
|
||||
| commission_rate | NUMERIC(7,4) | calc_type=RATE 일 때 |
|
||||
| fixed_amount | NUMERIC(15,2) | calc_type=FIXED 일 때 |
|
||||
| effective_from DATE NN / effective_to DATE | | |
|
||||
CHECK referral_type IN ('REFERRAL','TRANSFER'); CHECK calc_type IN ('RATE','FIXED')
|
||||
|
||||
**referral_commission_ledger**
|
||||
| 컬럼 | 타입 | 설명 |
|
||||
|---|---|---|
|
||||
| ledger_id | BIGSERIAL PK | |
|
||||
| contract_id | BIGINT NN FK | |
|
||||
| referrer_agent_id | BIGINT NN FK agent | 소개/이관한 설계사 |
|
||||
| settle_month | CHAR(6) NN | |
|
||||
| referral_type | VARCHAR(20) NN | |
|
||||
| base_amount | NUMERIC(15,2) NN | 기준금액(수수료/보험료) |
|
||||
| commission_rate | NUMERIC(7,4) | |
|
||||
| commission_amount | NUMERIC(15,2) NN | **= base×rate (RATE) 또는 fixed_amount (FIXED)** |
|
||||
| status | VARCHAR(20) NN DEFAULT 'CALCULATED' | |
|
||||
idx: (referrer_agent_id, settle_month),(contract_id)
|
||||
|
||||
### D3. 정착지원금 (SETTLING_SUPPORT) — V90
|
||||
신입/경력 설계사 정착 지원금. 등급별 정액 지급 + 조기 해촉 시 환수.
|
||||
|
||||
**settling_support_rule**
|
||||
| 컬럼 | 타입 | 설명 |
|
||||
|---|---|---|
|
||||
| rule_id BIGSERIAL PK | | |
|
||||
| agent_grade | VARCHAR(20) NN | 설계사 등급 |
|
||||
| support_amount | NUMERIC(15,2) NN | 월 정액 지원금 |
|
||||
| duration_months | INT NN | 약정 지원 개월수 |
|
||||
| clawback_months | INT NN | 이 개월 내 해촉 시 환수 대상 |
|
||||
| effective_from DATE NN / effective_to DATE | | |
|
||||
|
||||
**settling_support_ledger**
|
||||
| 컬럼 | 타입 | 설명 |
|
||||
|---|---|---|
|
||||
| ledger_id BIGSERIAL PK | | |
|
||||
| agent_id BIGINT NN FK agent | | |
|
||||
| settle_month CHAR(6) NN | | |
|
||||
| support_amount NUMERIC(15,2) NN | 지급액 | |
|
||||
| clawback_amount NUMERIC(15,2) NN DEFAULT 0 | 환수액 | |
|
||||
| net_amount NUMERIC(15,2) NN | **= support_amount − clawback_amount** | |
|
||||
| status VARCHAR(20) NN DEFAULT 'CALCULATED' | PAID/CLAWED_BACK 포함 | |
|
||||
idx: (agent_id, settle_month)
|
||||
|
||||
**공식**: 지급 = 등급별 support_amount. 환수 = 조기해촉시 support_amount × (잔여약정개월/약정개월) (clawback service 계산).
|
||||
|
||||
### D4. 인정실적 + 환산보험료 (RECOGNIZED_PERF) — V91
|
||||
환산보험료 = 보험료 × 상품별환산율. 인정실적 = 환산보험료 × 인정율. 시상/등급의 기초.
|
||||
|
||||
**product_conversion_rate** (상품별 환산율)
|
||||
| 컬럼 | 타입 | 설명 |
|
||||
|---|---|---|
|
||||
| rate_id BIGSERIAL PK | | |
|
||||
| product_id BIGINT NN FK product | | |
|
||||
| conversion_rate NUMERIC(7,4) NN | 환산율 (예: 0.5=50%) | |
|
||||
| effective_from DATE NN / effective_to DATE | | |
|
||||
UNIQUE(product_id, effective_from)
|
||||
|
||||
**recognized_performance_ledger**
|
||||
| 컬럼 | 타입 | 설명 |
|
||||
|---|---|---|
|
||||
| ledger_id BIGSERIAL PK | | |
|
||||
| agent_id BIGINT NN FK agent | | |
|
||||
| contract_id BIGINT NN FK contract | | |
|
||||
| settle_month CHAR(6) NN | | |
|
||||
| premium_amount NUMERIC(15,2) NN | 보험료 | |
|
||||
| conversion_rate NUMERIC(7,4) NN | 적용 환산율 | |
|
||||
| converted_premium NUMERIC(15,2) NN | **= premium × conversion_rate** | |
|
||||
| recognition_rate NUMERIC(7,4) NN | 인정율 | |
|
||||
| recognized_amount NUMERIC(15,2) NN | **= converted_premium × recognition_rate** | |
|
||||
| status VARCHAR(20) NN DEFAULT 'CALCULATED' | | |
|
||||
idx: (agent_id, settle_month),(contract_id)
|
||||
|
||||
### D5. 시상 (AWARD) — V92
|
||||
신인도입/법인/유지율 등 시상. 조건 충족 시 정액 또는 정률.
|
||||
|
||||
**award_rule**
|
||||
| 컬럼 | 타입 | 설명 |
|
||||
|---|---|---|
|
||||
| rule_id BIGSERIAL PK | | |
|
||||
| award_type VARCHAR(20) NN | NEW_RECRUIT/CORPORATE/RETENTION/PERFORMANCE | |
|
||||
| calc_type VARCHAR(10) NN | RATE/FIXED | |
|
||||
| threshold_amount NUMERIC(15,2) | 충족 기준 실적(이상) | |
|
||||
| award_rate NUMERIC(7,4) | RATE | |
|
||||
| award_fixed NUMERIC(15,2) | FIXED | |
|
||||
| effective_from DATE NN / effective_to DATE | | |
|
||||
CHECK award_type IN (...); CHECK calc_type IN ('RATE','FIXED')
|
||||
|
||||
**award_ledger**
|
||||
| 컬럼 | 타입 | 설명 |
|
||||
|---|---|---|
|
||||
| ledger_id BIGSERIAL PK | | |
|
||||
| agent_id BIGINT NN FK agent | | |
|
||||
| settle_month CHAR(6) NN | | |
|
||||
| award_type VARCHAR(20) NN | | |
|
||||
| base_amount NUMERIC(15,2) NN | 기준 실적 | |
|
||||
| award_amount NUMERIC(15,2) NN | **= base×award_rate (RATE) 또는 award_fixed (FIXED)** | |
|
||||
| status VARCHAR(20) NN DEFAULT 'CALCULATED' | | |
|
||||
idx: (agent_id, settle_month),(award_type, settle_month)
|
||||
|
||||
### D6. 생보운영지원수수료 (LIFE_OP_SUPPORT) — V93
|
||||
생보 계약 운영지원 수수료. 실적 기준 지급률 + 환수.
|
||||
|
||||
**life_support_rule**
|
||||
| 컬럼 | 타입 | 설명 |
|
||||
|---|---|---|
|
||||
| rule_id BIGSERIAL PK | | |
|
||||
| agent_grade VARCHAR(20) NN | | |
|
||||
| support_rate NUMERIC(7,4) NN | 지급률 | |
|
||||
| effective_from DATE NN / effective_to DATE | | |
|
||||
|
||||
**life_support_ledger**
|
||||
| 컬럼 | 타입 | 설명 |
|
||||
|---|---|---|
|
||||
| ledger_id BIGSERIAL PK | | |
|
||||
| agent_id BIGINT NN FK agent | | |
|
||||
| settle_month CHAR(6) NN | | |
|
||||
| base_amount NUMERIC(15,2) NN | 생보 실적 | |
|
||||
| support_rate NUMERIC(7,4) NN | | |
|
||||
| support_amount NUMERIC(15,2) NN | **= base × support_rate** | |
|
||||
| clawback_amount NUMERIC(15,2) NN DEFAULT 0 | 환수 | |
|
||||
| net_amount NUMERIC(15,2) NN | = support − clawback | |
|
||||
| status VARCHAR(20) NN DEFAULT 'CALCULATED' | | |
|
||||
idx: (agent_id, settle_month)
|
||||
|
||||
### D7. 지점장수당 (BRANCH_MGR_ALLOWANCE) — V94
|
||||
지점장이 소속 조직 실적에 대해 받는 수당. cap 제한 + 환수.
|
||||
|
||||
**branch_mgr_allowance_rule**
|
||||
| 컬럼 | 타입 | 설명 |
|
||||
|---|---|---|
|
||||
| rule_id BIGSERIAL PK | | |
|
||||
| position_code VARCHAR(20) NN | 직책(지점장 등) | |
|
||||
| allowance_rate NUMERIC(7,4) NN | 수당율 | |
|
||||
| cap_amount NUMERIC(15,2) | 상한(NULL=무제한) | |
|
||||
| effective_from DATE NN / effective_to DATE | | |
|
||||
|
||||
**branch_mgr_allowance_ledger**
|
||||
| 컬럼 | 타입 | 설명 |
|
||||
|---|---|---|
|
||||
| ledger_id BIGSERIAL PK | | |
|
||||
| manager_agent_id BIGINT NN FK agent | 지점장 | |
|
||||
| org_id BIGINT NN FK organization | 대상 조직 | |
|
||||
| settle_month CHAR(6) NN | | |
|
||||
| base_amount NUMERIC(15,2) NN | 조직 실적 | |
|
||||
| allowance_rate NUMERIC(7,4) NN | | |
|
||||
| allowance_amount NUMERIC(15,2) NN | **= LEAST(base×rate, cap)** | |
|
||||
| clawback_amount NUMERIC(15,2) NN DEFAULT 0 | | |
|
||||
| status VARCHAR(20) NN DEFAULT 'CALCULATED' | | |
|
||||
idx: (manager_agent_id, settle_month),(org_id, settle_month)
|
||||
|
||||
> **확정 FK (DB 실검증):** contract(contract_id) · agent(agent_id) · product(product_id) · organization(org_id) · **insurance_company(company_id)** · settle_master. 보험사 FK는 `carrier_id BIGINT REFERENCES insurance_company(company_id)`. 수지차 payout은 settle_master 집계로 산출(dba가 컬럼 확인).
|
||||
|
||||
---
|
||||
|
||||
## 메뉴 (V95) — GRP_COMMISSION 하위 7개 PAGE
|
||||
| menu_code | menu_name | menu_path | component_path | sort |
|
||||
|---|---|---|---|---|
|
||||
| INCOME_COMM | 수입수수료/수지차 | /commission/income | commission/IncomeCommission | 80 |
|
||||
| REFERRAL_COMM | 소개·이관수수료 | /commission/referral | commission/ReferralCommission | 90 |
|
||||
| SETTLING_SUPPORT | 정착지원금 | /commission/settling-support | commission/SettlingSupport | 100 |
|
||||
| RECOGNIZED_PERF | 인정실적 | /commission/recognized | commission/RecognizedPerformance | 110 |
|
||||
| AWARD | 시상 | /commission/award | commission/Award | 120 |
|
||||
| LIFE_OP_SUPPORT | 생보운영지원수수료 | /commission/life-support | commission/LifeSupport | 130 |
|
||||
| BRANCH_MGR_ALLOWANCE | 지점장수당 | /commission/branch-allowance | commission/BranchAllowance | 140 |
|
||||
|
||||
권한: READ/CREATE/UPDATE/DELETE/EXPORT. 역할부여: SUPER_ADMIN/ADMIN 전체, MANAGER READ/CREATE/UPDATE/EXPORT, AGENT READ(INCOME_COMM 제외 — 본인 관련만). V54 패턴 그대로.
|
||||
|
||||
## 공통코드 (V96)
|
||||
| group_code | 코드 |
|
||||
|---|---|
|
||||
| INCOME_STATUS | CALCULATED/CONFIRMED/CANCELLED |
|
||||
| REFERRAL_TYPE | REFERRAL=소개/TRANSFER=이관 |
|
||||
| CALC_TYPE | RATE=정률/FIXED=정액 |
|
||||
| SUPPORT_STATUS | CALCULATED/PAID/CLAWED_BACK |
|
||||
| AWARD_TYPE | NEW_RECRUIT=신인도입/CORPORATE=법인/RETENTION=유지율/PERFORMANCE=실적 |
|
||||
|
||||
CommissionType enum 확장: INCOME/REFERRAL/TRANSFER/SETTLING/RECOGNIZED/AWARD/LIFE_SUPPORT/BRANCH_ALLOWANCE 추가 + V40 COMMISSION_TYPE 공통코드 동기화.
|
||||
|
||||
---
|
||||
|
||||
## Wave 실행
|
||||
| Wave | 담당 | 산출물 |
|
||||
|---|---|---|
|
||||
| 1 | dba-architect | V88~V96 (도메인 7 + 메뉴 + 공통코드). FK 테이블명 실검증. |
|
||||
| 2 | core-architect | 14테이블 VO/Resp/SaveReq/SearchParam + Mapper IF+XML + CommissionType enum 확장 |
|
||||
| 3 | api-developer | 7 Service + 7 Controller (@RequirePermission/@DataChangeLog, 공식 계산 Service 내장) |
|
||||
| 4 | frontend-developer | 7 페이지 + api/commission.ts 확장 + App.tsx 라우트 |
|
||||
| 5 | infra-reviewer | `./gradlew build` + 프론트 빌드 + smoke. README/HANDOFF/DOMAIN_KNOWLEDGE 갱신 |
|
||||
|
||||
---
|
||||
|
||||
## 완료 상태 (2026-05-31)
|
||||
schema **v96** 적용(Flyway V88~V96 success), 전체 `./gradlew build` SUCCESSFUL, 프론트 빌드 OK, ga-api 8082 라이브. P7 14개 GET + 수지차 recalc POST 전수 호출 5xx 0건. 수입수수료(보험료×요율=6,000,000)·수지차(수입−지급) 공식 런타임 실측 검증 후 테스트데이터 정리.
|
||||
|
||||
**PL 통합검증에서 잡은 버그 3건(팀원 GET-only smoke가 놓침) — 수정 완료:**
|
||||
1. 프론트 인정실적 경로 `/rules` → 백엔드 `/rates` 불일치 → `api/commission.ts` 수정.
|
||||
2. 프론트 수지차 경로 `/margin` → 백엔드 `/margins` 불일치 → 수정.
|
||||
3. 수지차 recalc 3중 결함: 컨트롤러 `@RequestBody`(프론트는 쿼리파라미터)→`@RequestParam`, 서비스 `payoutTotal=0` 하드코딩 → settle_master.net_amount 실집계(`sumPayoutByMonth`)+`sumIncomeByMonth` 신규, `selectByMonthAndAgent`의 `#{agentId} IS NULL` Postgres typed-null 오류 → `<choose>`로 교체.
|
||||
@@ -0,0 +1,169 @@
|
||||
# P8 — incar 갭 반영: 운영/정산 프로세스 7개 도메인 (🅱)
|
||||
|
||||
작성: PL (자율 모드), 2026-05-31. 사용자 지시 "다 적용". P7(🅰 수수료계산 7)에 이어 🅱 운영 7개로 incar 갭 14개 완성.
|
||||
범위: 풀스택(DB→Core→API→Frontend→Infra). 패턴 100% 준수(template = 수금수수료 V41 / CollectionCommission*, P7과 동일).
|
||||
Flyway: **V96이 최신 → V97부터.** 메뉴는 적절한 상위그룹 하위. 금액 NUMERIC(15,2), 율 NUMERIC(7,4), HALF_UP.
|
||||
|
||||
**확정 FK**: agent(agent_id), contract(contract_id), organization(org_id), insurance_company(company_id), grade(grade_id), settle_master.
|
||||
|
||||
---
|
||||
|
||||
## 도메인별 설계
|
||||
|
||||
### D8. 단계마감 (STEP_CLOSE) — V97
|
||||
월 정산을 단계별로 마감(수신→계산→검증→지급). 기존 PERIOD_CLOSE(단일 잠금) 보강.
|
||||
|
||||
**settlement_step_close**
|
||||
| 컬럼 | 타입 | 설명 |
|
||||
|---|---|---|
|
||||
| step_close_id | BIGSERIAL PK | |
|
||||
| settle_month | CHAR(6) NN | |
|
||||
| step_code | VARCHAR(20) NN | RECEIVE/CALC/VERIFY/PAY |
|
||||
| step_seq | INT NN | 단계 순서 1~4 |
|
||||
| status | VARCHAR(20) NN DEFAULT 'OPEN' | OPEN/CLOSED |
|
||||
| closed_at TIMESTAMP / closed_by BIGINT / created_at | | |
|
||||
UNIQUE(settle_month, step_code); CHECK step_code IN(...); CHECK status IN('OPEN','CLOSED')
|
||||
공식 없음(워크플로우). 액션: close(단계마감), reopen.
|
||||
|
||||
### D9. 본사정산 (HQ_SETTLE) — V98
|
||||
GA 본사 자체의 수입/비용 정산 항목(설계사 지급과 별개의 GA 손익).
|
||||
|
||||
**hq_settle_item**
|
||||
| 컬럼 | 타입 | 설명 |
|
||||
|---|---|---|
|
||||
| item_id | BIGSERIAL PK | |
|
||||
| settle_month | CHAR(6) NN | |
|
||||
| item_type | VARCHAR(20) NN | INCOME(수입)/EXPENSE(비용) |
|
||||
| item_name | VARCHAR(100) NN | 항목명 |
|
||||
| amount | NUMERIC(15,2) NN | 금액 |
|
||||
| memo | VARCHAR(300) | |
|
||||
| status | VARCHAR(20) NN DEFAULT 'REGISTERED' | REGISTERED/CONFIRMED |
|
||||
| created_at/created_by/updated_at | | |
|
||||
idx: (settle_month, item_type). CHECK item_type IN('INCOME','EXPENSE')
|
||||
공식: 본사손익 = Σ(INCOME amount) − Σ(EXPENSE amount). 조회 집계 엔드포인트 제공.
|
||||
|
||||
### D10. 등급평가 (GRADE_EVAL) — V99
|
||||
설계사 실적 점수로 등급 산정. rule(점수구간→등급) + 평가원장.
|
||||
|
||||
**grade_eval_rule**
|
||||
| 컬럼 | 타입 | 설명 |
|
||||
|---|---|---|
|
||||
| rule_id BIGSERIAL PK | | |
|
||||
| grade_id | BIGINT NN FK grade | 부여 등급 |
|
||||
| min_score | NUMERIC(7,2) NN | 이 점수 이상이면 해당 등급 |
|
||||
| effective_from DATE NN / effective_to DATE | | |
|
||||
|
||||
**agent_grade_evaluation**
|
||||
| 컬럼 | 타입 | 설명 |
|
||||
|---|---|---|
|
||||
| eval_id BIGSERIAL PK | | |
|
||||
| agent_id BIGINT NN FK agent | | |
|
||||
| eval_period CHAR(6) NN | 평가기준월 | |
|
||||
| performance_score NUMERIC(7,2) NN | 실적점수 | |
|
||||
| prev_grade_id BIGINT FK grade | 기존 등급 | |
|
||||
| evaluated_grade_id BIGINT FK grade | 산정 등급 | |
|
||||
| status VARCHAR(20) NN DEFAULT 'EVALUATED' | EVALUATED/APPLIED | |
|
||||
idx: (agent_id, eval_period). UNIQUE(agent_id, eval_period)
|
||||
공식: evaluated_grade = score 이상인 최대 min_score 룰의 grade. Service가 grade_eval_rule 조회해 결정.
|
||||
|
||||
### D11. 공동계약 (JOINT_CONTRACT) — V100
|
||||
한 계약을 여러 설계사가 공동 모집 → 지분율(share_rate)로 수수료 배분.
|
||||
|
||||
**joint_contract**
|
||||
| 컬럼 | 타입 | 설명 |
|
||||
|---|---|---|
|
||||
| joint_id BIGSERIAL PK | | |
|
||||
| contract_id BIGINT NN FK contract | | |
|
||||
| agent_id BIGINT NN FK agent | | |
|
||||
| role VARCHAR(10) NN | LEAD/SUB | |
|
||||
| share_rate NUMERIC(7,4) NN | 지분율(합=1) | |
|
||||
| created_at/created_by | | |
|
||||
UNIQUE(contract_id, agent_id); CHECK role IN('LEAD','SUB'); CHECK share_rate>=0 AND share_rate<=1
|
||||
공식: 설계사몫 = 계약수수료 × share_rate. (검증: 계약별 share_rate 합=1 — Service 가드)
|
||||
|
||||
### D12. 보증보험 (GUARANTEE_INS) — V101
|
||||
설계사가 가입하는 신원보증보험 가입현황 추적(공식 없음, 관리).
|
||||
|
||||
**agent_guarantee_insurance**
|
||||
| 컬럼 | 타입 | 설명 |
|
||||
|---|---|---|
|
||||
| guarantee_id BIGSERIAL PK | | |
|
||||
| agent_id BIGINT NN FK agent | | |
|
||||
| policy_no VARCHAR(50) NN | 증권번호 | |
|
||||
| carrier_id BIGINT FK insurance_company(company_id) | 보증보험사 | |
|
||||
| guarantee_amount NUMERIC(15,2) NN | 보증금액 | |
|
||||
| start_date DATE NN / end_date DATE NN | 보증기간 | |
|
||||
| status VARCHAR(20) NN DEFAULT 'ACTIVE' | ACTIVE/EXPIRED/CANCELLED | |
|
||||
idx: (agent_id, status),(end_date)
|
||||
|
||||
### D13. 적립금 (RESERVE_FUND) — V102
|
||||
환수 대비 수수료 일부 적립 + 잔액 관리.
|
||||
|
||||
**agent_reserve_fund**
|
||||
| 컬럼 | 타입 | 설명 |
|
||||
|---|---|---|
|
||||
| reserve_id BIGSERIAL PK | | |
|
||||
| agent_id BIGINT NN FK agent | | |
|
||||
| settle_month CHAR(6) NN | | |
|
||||
| reserve_in NUMERIC(15,2) NN DEFAULT 0 | 적립액 | |
|
||||
| reserve_out NUMERIC(15,2) NN DEFAULT 0 | 인출/사용액 | |
|
||||
| balance NUMERIC(15,2) NN | **= 전월잔액 + reserve_in − reserve_out** | |
|
||||
| created_at | | |
|
||||
UNIQUE(agent_id, settle_month). idx (agent_id, settle_month)
|
||||
공식: balance = (직전월 balance) + reserve_in − reserve_out. Service가 직전월 조회해 누적.
|
||||
|
||||
### D14. 수수료 시뮬레이터 (COMM_SIMULATOR) — V103
|
||||
보험료/요율/지급률 입력 → 수수료·세금·실지급·분급·환수예상 산출. 시나리오 저장.
|
||||
|
||||
**commission_simulation**
|
||||
| 컬럼 | 타입 | 설명 |
|
||||
|---|---|---|
|
||||
| sim_id BIGSERIAL PK | | |
|
||||
| sim_name VARCHAR(100) NN | 시나리오명 | |
|
||||
| premium_amount NUMERIC(15,2) NN | 입력 보험료 | |
|
||||
| company_rate NUMERIC(7,4) NN | 보험사요율 | |
|
||||
| payout_rate NUMERIC(7,4) NN | 지급률 | |
|
||||
| commission_year INT NN | 회차 | |
|
||||
| company_payment NUMERIC(15,2) NN | = 보험료×요율 | |
|
||||
| agent_commission NUMERIC(15,2) NN | = 보험사지급액×지급률 | |
|
||||
| withholding_tax NUMERIC(15,2) NN | = 설계사수수료×3.3% | |
|
||||
| net_amount NUMERIC(15,2) NN | = 수수료−원천세 | |
|
||||
| expected_chargeback NUMERIC(15,2) | 예상 환수(13개월 가정 50% 등) | |
|
||||
| created_at/created_by | | |
|
||||
공식(DOMAIN_KNOWLEDGE 7장과 동일): company_payment=premium×company_rate, agent_commission=company_payment×payout_rate, withholding_tax=agent_commission×0.033, net=agent_commission−withholding_tax. Service의 simulate 액션이 입력만 받아 계산 후 저장/반환.
|
||||
|
||||
---
|
||||
|
||||
## 메뉴 (V104)
|
||||
| menu_code | menu_name | 상위그룹 | menu_path | component_path |
|
||||
|---|---|---|---|---|
|
||||
| STEP_CLOSE | 단계마감 | GRP_SETTLE | /settle/step-close | settle/StepClose |
|
||||
| HQ_SETTLE | 본사정산 | GRP_SETTLE | /settle/hq-settle | settle/HqSettle |
|
||||
| GRADE_EVAL | 등급평가 | GRP_ORG | /org/grade-eval | org/GradeEvaluation |
|
||||
| JOINT_CONTRACT | 공동계약 | GRP_PRD | /contracts/joint | contracts/JointContract |
|
||||
| GUARANTEE_INS | 보증보험 | GRP_ORG | /org/guarantee | org/GuaranteeInsurance |
|
||||
| RESERVE_FUND | 적립금 | GRP_COMMISSION | /commission/reserve | commission/ReserveFund |
|
||||
| COMM_SIMULATOR | 수수료 시뮬레이터 | GRP_COMMISSION | /commission/simulator | commission/CommissionSimulator |
|
||||
|
||||
권한 READ/CREATE/UPDATE/DELETE/EXPORT(STEP_CLOSE는 READ/UPDATE/APPROVE). 역할부여 V54/V95 패턴.
|
||||
|
||||
## 공통코드 (V105)
|
||||
| group_code | 코드 |
|
||||
|---|---|
|
||||
| STEP_CODE | RECEIVE=수신마감/CALC=계산마감/VERIFY=검증마감/PAY=지급마감 |
|
||||
| STEP_STATUS | OPEN=열림/CLOSED=마감 |
|
||||
| HQ_ITEM_TYPE | INCOME=수입/EXPENSE=비용 |
|
||||
| JOINT_ROLE | LEAD=주설계사/SUB=공동설계사 |
|
||||
| GUARANTEE_STATUS | ACTIVE=유효/EXPIRED=만료/CANCELLED=해지 |
|
||||
| GRADE_EVAL_STATUS | EVALUATED=평가완료/APPLIED=반영완료 |
|
||||
|
||||
---
|
||||
|
||||
## Wave 실행 (P7과 동일 팀/패턴)
|
||||
1 dba: V97~V105 · 2 core: VO/Mapper/Enum · 3 api: Service/Controller(공식·집계 내장) · 4 front: 7페이지+commission/settle/org api+App.tsx · 5 infra: build+smoke
|
||||
**PL 검증**: GET뿐 아니라 액션 POST(close/reopen/simulate/recalc balance/등급산정)까지 실호출로 5xx 0 확인 — P7에서 GET-only smoke가 버그를 놓친 교훈 반영.
|
||||
|
||||
---
|
||||
|
||||
## 완료 상태 (2026-05-31)
|
||||
schema **v105** 적용(Flyway V97~V105 success), 전체 `./gradlew build` SUCCESSFUL, 프론트 빌드 OK, ga-api 8082 라이브. 8테이블·7메뉴 등록. GET 9 + 액션 POST 5(simulate/hq-settles/step-closes/close/reserve upsert) **전부 200, 5xx 0건**. 시뮬레이터 공식 실측: 보험료 1,000,000×0.6=600,000 → ×0.75=450,000 → 원천세 14,850 → net 435,150 → 예상환수 225,000 ✓. 테스트데이터 정리 완료. (P8은 P7과 달리 액션까지 PL이 직접 호출 검증 → 숨은 버그 0.)
|
||||
@@ -0,0 +1,349 @@
|
||||
# 신입사원 완전 기초 교육 — 보험과 수수료, 0부터 시작하기
|
||||
|
||||
> 대상: **보험을 한 번도 다뤄본 적 없는 신입사원/개발자.**
|
||||
> 목표: 이 문서 하나만 끝까지 읽으면 "우리 회사가 무슨 일을 하고, 수수료가 어떻게 계산돼서 설계사 통장에 꽂히는지" 머릿속에 그림이 그려지게 하는 것.
|
||||
> 읽는 법: 위에서 아래로 순서대로. 어려운 용어는 나올 때마다 바로 쉬운 말로 풀어 씁니다. 숫자 예시는 직접 손으로 따라 계산해 보세요.
|
||||
> 더 깊은 코드/규정 레벨은 [`DOMAIN_KNOWLEDGE.md`](DOMAIN_KNOWLEDGE.md) 참고.
|
||||
|
||||
---
|
||||
|
||||
## 목차
|
||||
1. [보험이 뭔가요? (진짜 0단계)](#1-보험이-뭔가요-진짜-0단계)
|
||||
2. [등장인물 4명 — 누가 누구에게 돈을 주나](#2-등장인물-4명--누가-누구에게-돈을-주나)
|
||||
3. [보험 기초 용어 25개 — 이것만 알면 대화가 된다](#3-보험-기초-용어-25개--이것만-알면-대화가-된다)
|
||||
4. [수수료가 뭐고 왜 종류가 많은가](#4-수수료가-뭐고-왜-종류가-많은가)
|
||||
5. [수수료 종류 한눈에 (우리 시스템이 다루는 전부)](#5-수수료-종류-한눈에-우리-시스템이-다루는-전부)
|
||||
6. [수수료 계산의 핵심 공식 — "세 번 곱하기"](#6-수수료-계산의-핵심-공식--세-번-곱하기)
|
||||
7. [숫자로 끝까지 따라가기 (한 계약의 일생)](#7-숫자로-끝까지-따라가기-한-계약의-일생)
|
||||
8. [그냥 곱하면 안 되는 이유 — 규제 3종](#8-그냥-곱하면-안-되는-이유--규제-3종)
|
||||
9. [세금과 공제 — 통장에 적게 들어오는 이유](#9-세금과-공제--통장에-적게-들어오는-이유)
|
||||
10. [한 달이 흘러가는 과정 — 월 정산 파이프라인](#10-한-달이-흘러가는-과정--월-정산-파이프라인)
|
||||
11. [수입 vs 지급, 그리고 수지차 — 회사는 어디서 남기나](#11-수입-vs-지급-그리고-수지차--회사는-어디서-남기나)
|
||||
12. [자주 나오는 질문 (FAQ)](#12-자주-나오는-질문-faq)
|
||||
13. [용어 빠른 사전](#13-용어-빠른-사전)
|
||||
|
||||
---
|
||||
|
||||
## 1. 보험이 뭔가요? (진짜 0단계)
|
||||
|
||||
**보험 = 여럿이 조금씩 돈을 모아뒀다가, 사고를 당한 사람에게 몰아주는 약속.**
|
||||
|
||||
예를 들어 100명이 매달 1만 원씩 모읍니다(월 100만 원). 그 중 한 명이 큰 병에 걸리면, 모인 돈에서 큰 금액을 그 사람에게 줍니다. 나머지 99명은 "내가 안 걸려서 다행이다" 하고 넘어갑니다. 이 "돈 모으고 → 사고나면 주기"를 전문으로 운영하는 회사가 **보험사**입니다.
|
||||
|
||||
- 고객이 매달 내는 돈 = **보험료(premium)**
|
||||
- 사고/사망/만기 때 받는 돈 = **보험금**
|
||||
- 이 약속을 적은 계약서 한 장 = **보험계약** (증권 1장 = 계약 1건)
|
||||
|
||||
> 우리 회사는 보험사가 아닙니다. 우리는 **보험을 파는 일(영업)을 대행**하고, 그 대가로 받은 돈을 **영업한 사람들에게 정확히 나눠주는 회사**입니다. 그게 다음 장에서 나오는 GA입니다.
|
||||
|
||||
---
|
||||
|
||||
## 2. 등장인물 4명 — 누가 누구에게 돈을 주나
|
||||
|
||||
| 누구 | 한 줄 설명 | 비유 |
|
||||
|---|---|---|
|
||||
| **고객** | 보험에 가입하고 보험료를 내는 사람 | 물건을 사는 손님 |
|
||||
| **보험사 (Carrier)** | 보험 상품을 만들고, 팔아준 대가로 수수료를 주는 회사 (삼성생명, 메리츠 등) | 물건을 만든 제조사 |
|
||||
| **GA (우리 회사)** | 여러 보험사 상품을 한데 모아 파는 대형 보험대리점 | 여러 브랜드를 파는 백화점 |
|
||||
| **설계사 (Agent)** | 실제로 고객을 만나 계약을 따오는 사람 | 백화점 매장 직원 |
|
||||
|
||||
**돈의 흐름 (제일 중요한 그림):**
|
||||
```
|
||||
[고객] ──보험료──▶ [보험사] ──수수료──▶ [GA(우리)] ──정산──▶ [설계사]
|
||||
└ 세금 떼고, 공제 빼고 통장으로
|
||||
```
|
||||
|
||||
- 보험사는 직접 영업하는 대신, GA·설계사가 고객을 데려와 주면 그 대가로 **수수료**를 줍니다.
|
||||
- GA는 보험사한테 받은 수수료(=**수입수수료**)에서, 영업한 설계사 몫(=**지급수수료**)을 떼어 줍니다.
|
||||
- **우리 시스템이 하는 일 = 이 "설계사 몫을 정확히 계산해서 나눠주는 것(정산)".**
|
||||
|
||||
---
|
||||
|
||||
## 3. 보험 기초 용어 25개 — 이것만 알면 대화가 된다
|
||||
|
||||
| 용어 | 쉬운 설명 |
|
||||
|---|---|
|
||||
| 보험계약 | 고객이 "매달 낼 테니 사고 때 보장해줘"라고 맺은 약속. 증권 1장 = 1건. |
|
||||
| 보험료(premium) | 고객이 내는 돈. **모든 수수료 계산의 출발점.** 월납/연납/일시납. |
|
||||
| 보험금 | 사고·만기 때 고객이 받는 돈. (수수료와는 다른 개념!) |
|
||||
| 계약자 / 피보험자 / 수익자 | 돈 내는 사람 / 보장 대상 / 보험금 받는 사람. (셋이 같을 수도 다를 수도) |
|
||||
| 생명보험 | 사람의 사망·생존·연금 관련 보험. (종신, 연금보험 등) |
|
||||
| 손해보험 | 자동차·화재·실손 등 재산·사고 관련 보험. |
|
||||
| 회차 / 차년(commission year) | 계약이 몇 년째인지. **1차년(초년도)** 수수료가 가장 큼. |
|
||||
| 납입 / 미납 / 실효 | 보험료를 냈다 / 안 냈다 / 안 내서 계약이 끊겼다. |
|
||||
| 유지 / 해지 / 부활 | 계약 살아있음 / 고객이 끊음 / 끊긴 걸 되살림. |
|
||||
| 유지율(persistency) | 가입한 계약이 13개월·25개월 뒤에도 살아있는 비율. **영업 건전성의 핵심 지표.** |
|
||||
| 청약 / 인수(언더라이팅) | 고객이 가입 신청 → 보험사가 위험 심사 후 받아들임. |
|
||||
| 위촉 / 해촉 | 설계사가 GA에 소속됨 / 소속이 끝남. |
|
||||
| 승계 | 설계사가 해촉되면 그가 관리하던 계약을 후임에게 넘기는 것. |
|
||||
| 모집 | 설계사가 새 계약을 따오는 행위. |
|
||||
| 환산보험료 | 상품마다 조건이 달라 비교가 어렵기 때문에, 정해진 "환산율"을 곱해 **공통 기준으로 환산한 보험료**. 실적 비교·시상의 기준. |
|
||||
| 인정실적 | 회사가 "이건 실적으로 인정한다"고 집계한 환산보험료 합. 등급·시상의 근거. |
|
||||
| 등급 | 설계사 레벨. 등급이 높을수록 지급률(받는 비율)이 높음. |
|
||||
| 오버라이드 | 부하 설계사 실적에 대해 상위 직책자가 받는 수당. |
|
||||
| 시상 | 목표 달성/우수자에게 주는 상금 성격의 수당. |
|
||||
| 시책(인센티브) | 한시적 캠페인 보너스. |
|
||||
| 환수(chargeback) | 계약이 일찍 깨지면 이미 준 수수료를 일부 돌려받는 것. |
|
||||
| 분급 | 큰 수수료를 한 번에 안 주고 여러 해에 나눠 주는 것. |
|
||||
| 정산 | 매월 수수료를 계산·확정·지급하는 업무 전체. |
|
||||
| 원장(ledger) | 계산의 최소 단위 기록 (계약 × 월 × 수수료종류 1줄). |
|
||||
| 명세서 | 설계사에게 "이번 달 얼마 받는지" 보여주는 내역서. |
|
||||
|
||||
---
|
||||
|
||||
## 4. 수수료가 뭐고 왜 종류가 많은가
|
||||
|
||||
**수수료 = 계약을 따오고(모집) 잘 유지시킨(유지) 대가로 보험사→GA→설계사로 흐르는 돈.**
|
||||
|
||||
왜 종류가 많을까요? 계약은 **태어나서(체결) → 유지되다가 → 갱신·부활도 하고 → 깨지기도(해지) 합니다.** 이 생애주기 단계마다, 또 영업을 도운 방식마다 다른 이름의 수수료가 붙기 때문입니다.
|
||||
|
||||
핵심 직관 하나만 기억하세요:
|
||||
> **"초년도(1차년) 모집수수료가 제일 크고, 그 다음부터는 유지·관리 대가로 잘게 나뉜다."**
|
||||
|
||||
---
|
||||
|
||||
## 5. 수수료 종류 한눈에 (우리 시스템이 다루는 전부)
|
||||
|
||||
우리 시스템은 아래 수수료/수당을 모두 계산합니다. (★ = incar 벤치마킹으로 새로 추가한 것 — P7/P8)
|
||||
|
||||
### 5-1. 계약 생애주기 기반
|
||||
| 종류 | 언제 | 누구에게 | 공식 한 줄 |
|
||||
|---|---|---|---|
|
||||
| 모집수수료 | 계약 체결(초년도) | 원 모집설계사 | 보험사지급액 × 지급률 |
|
||||
| 유지수수료 | 유지되는 매월/매년 | 현재 관리설계사(승계 시 후임) | 보험사지급액 × 지급률 |
|
||||
| 수금수수료 | 보험료가 실제 납입될 때 | 관리설계사 | 납입보험료 × 수금율 |
|
||||
| 갱신수수료 | 갱신형 상품 갱신 시 | 관리설계사 | 갱신보험료 × 갱신율 |
|
||||
| 부활수수료 | 실효 계약 부활 시 | 관리설계사 | 부활보험료 × 부활율 |
|
||||
|
||||
### 5-2. 성과·조직 기반
|
||||
| 종류 | 의미 | 공식 한 줄 |
|
||||
|---|---|---|
|
||||
| 오버라이드 | 하위 실적에 대한 상위 직책 수당 | 하위실적 × 오버라이드율 (cap 제한) |
|
||||
| 유지보수수당(지속수당) | 일정 유지율 달성 보너스 | 등급별 정액 또는 정률 |
|
||||
| 시책(인센티브) | 캠페인/목표 달성 프로모션 | 캠페인 규칙대로 |
|
||||
| ★ 시상(Award) | 신인도입/법인/유지율 등 시상 | 충족 시 실적×율 또는 정액 |
|
||||
| ★ 지점장수당 | 지점장이 소속 조직 실적에 받는 수당 | min(조직실적 × 수당율, 한도) |
|
||||
|
||||
### 5-3. 소개·이관·지원 (★ 신규)
|
||||
| 종류 | 의미 | 공식 한 줄 |
|
||||
|---|---|---|
|
||||
| ★ 소개수수료 | 계약을 소개해 준 설계사에게 | 기준금액 × 소개율 (또는 정액) |
|
||||
| ★ 이관수수료 | 계약을 다른 설계사에게 이관할 때 | 기준금액 × 이관율 |
|
||||
| ★ 정착지원금 | 신입/경력 설계사 정착 지원금(조기해촉 시 환수) | 등급별 정액 − 환수 |
|
||||
| ★ 생보운영지원수수료 | 생보 운영지원 명목 수수료 | 실적 × 지급률 − 환수 |
|
||||
|
||||
### 5-4. 회사(GA) 레벨 (★ 신규)
|
||||
| 종류 | 의미 | 공식 한 줄 |
|
||||
|---|---|---|
|
||||
| ★ 수입수수료 | 보험사가 GA에 준 돈(수입 측) | 보험료 × 보험사요율 |
|
||||
| ★ 수지차 | GA가 남긴 마진 | 수입 합 − 지급 합 |
|
||||
| ★ 인정실적 | 시상·등급의 기준이 되는 실적 | 환산보험료 × 인정율 |
|
||||
| ★ 본사정산 | GA 본사 자체 수입/비용 정산 | Σ수입 − Σ비용 |
|
||||
|
||||
### 5-5. 보조 관리 (★ 신규, 공식보다 관리 중심)
|
||||
| 항목 | 의미 |
|
||||
|---|---|
|
||||
| ★ 환산율(상품별) | 보험료를 공통 기준으로 환산하는 비율 |
|
||||
| ★ 등급평가 | 실적 점수로 설계사 등급 산정 |
|
||||
| ★ 공동계약 | 한 계약을 여러 설계사가 공동 모집, 지분율로 배분 |
|
||||
| ★ 보증보험 | 설계사 신원보증보험 가입 관리 |
|
||||
| ★ 적립금 | 환수 대비 수수료 일부 적립·잔액 관리 |
|
||||
| ★ 단계마감 | 정산을 수신→계산→검증→지급 단계로 마감 |
|
||||
| ★ 수수료 시뮬레이터 | 보험료/요율 입력 → 수수료·세금·실지급 미리 계산 |
|
||||
|
||||
> **모집 vs 유지의 귀속 차이(자주 묻는 함정):** 설계사가 해촉·이관되면 그 계약의 **유지수수료는 후임**에게 넘어가지만, **모집수수료는 원래 모집한 설계사**에게 그대로 남습니다.
|
||||
|
||||
---
|
||||
|
||||
## 6. 수수료 계산의 핵심 공식 — "세 번 곱하기"
|
||||
|
||||
대부분의 수수료는 **곱하기 세 번**이면 끝납니다. 이것만 외우면 80%는 이해한 겁니다.
|
||||
|
||||
```
|
||||
① 보험사 지급액 = 보험료(premium) × 보험사요율(company_rate)
|
||||
② 설계사 수수료 = 보험사 지급액 × 지급률(payout_rate)
|
||||
③ 원천세 = 설계사 수수료 × 3.3%
|
||||
──────────────────────────────────────────────
|
||||
설계사 손에 = 설계사 수수료 − 원천세 (− 공제 + 부가세)
|
||||
```
|
||||
|
||||
두 가지 율의 뜻:
|
||||
- **보험사요율(company_rate)**: 보험사가 이 상품에 주기로 한 비율. **상품 × 회차(차년)**로 정해짐.
|
||||
- **지급률(payout_rate)**: GA가 받은 돈 중 설계사에게 주는 비율. **설계사 등급 × 보험종류 × 회차**로 정해짐. → 같은 계약이라도 **등급 높은 설계사가 더 많이** 받음.
|
||||
|
||||
> 모집수수료와 유지수수료는 **공식이 똑같고**, 회차(commission_year)만 다릅니다. (모집 = 1차년, 유지 = 2차년 이후)
|
||||
|
||||
---
|
||||
|
||||
## 7. 숫자로 끝까지 따라가기 (한 계약의 일생)
|
||||
|
||||
연환산 보험료 **100만 원**짜리 계약 하나가 태어나서 깨질 때까지 따라가 봅시다.
|
||||
|
||||
### (A) 모집수수료 — 계약이 태어났다
|
||||
| 항목 | 값 | 계산 |
|
||||
|---|---|---|
|
||||
| 보험료(연환산) | 1,000,000원 | (가정) |
|
||||
| 보험사요율 | 60% | 상품·1차년 |
|
||||
| **① 보험사 지급액** | **600,000원** | 1,000,000 × 60% |
|
||||
| 지급률(이 설계사 등급) | 75% | 등급·1차년 |
|
||||
| **② 설계사 수수료** | **450,000원** | 600,000 × 75% |
|
||||
| **③ 원천세(3.3%)** | **14,850원** | 450,000 × 3.3% |
|
||||
| **원장에 남는 실수령** | **435,150원** | 450,000 − 14,850 |
|
||||
|
||||
### (B) 분급 — 한 번에 안 주고 7년에 나눠 준다
|
||||
큰 모집수수료(450,000원)는 규제상 7년에 걸쳐 비율대로 나눠 줍니다(35/25/15/10/7/5/3%).
|
||||
|
||||
| 차년 | 비율 | 지급액 |
|
||||
|---|---|---|
|
||||
| 1차년 | 35% | 157,500원 |
|
||||
| 2차년 | 25% | 112,500원 |
|
||||
| 3차년 | 15% | 67,500원 |
|
||||
| … | … | … |
|
||||
| **합계** | **100%** | **450,000원** |
|
||||
|
||||
→ 계약 직후 통장에 들어오는 건 450,000원이 아니라 **1차년분 157,500원(에서 세금 차감)**.
|
||||
|
||||
### (C) 환수 — 고객이 13개월 만에 해지했다
|
||||
유지 13~24개월 구간 환수율 **50%** 적용:
|
||||
```
|
||||
환수액 = 기지급 모집수수료 × 환수율 = 450,000 × 50% = 225,000원 ← 다음 정산에서 (−)로 차감
|
||||
```
|
||||
- 12개월 이내면 100%(전액 환수), 85개월 이상이면 0%(환수 없음).
|
||||
|
||||
### (D) 오버라이드 — 이 설계사의 팀장이 받는 수당
|
||||
```
|
||||
오버라이드 = 하위 실적 × 오버라이드율 = 2,000,000 × 4% = 80,000원 (cap 넘으면 cap까지만)
|
||||
```
|
||||
|
||||
### (E) 수지차 — 회사는 이 계약으로 얼마 남겼나 (★신규)
|
||||
```
|
||||
수입수수료(보험사가 GA에 준 돈) = 1,000,000 × 60% = 600,000원
|
||||
지급수수료(GA가 설계사에 준 돈) = 450,000원
|
||||
수지차(GA 마진) = 600,000 − 450,000 = 150,000원
|
||||
```
|
||||
|
||||
이 한 흐름이 머리에 들어왔다면, 나머지 수수료 종류는 "기준금액 × 율" 변형일 뿐입니다.
|
||||
|
||||
---
|
||||
|
||||
## 8. 그냥 곱하면 안 되는 이유 — 규제 3종
|
||||
|
||||
수수료는 법으로 한도·방식이 정해져 있어, 단순 곱셈 결과를 그대로 줄 수 없습니다.
|
||||
|
||||
1. **1200% 룰** (보험업감독규정): 초년도 모집수수료 총액은 **연납보험료의 1200%**를 못 넘음. 시스템이 누적 지급액을 추적해 초과분을 잘라냄.
|
||||
2. **분급**: 큰 수수료를 7년에 나눠 지급(위 7-B).
|
||||
3. **차등 환수**: 조기 해지 시 유지기간이 짧을수록 많이 환수(위 7-C).
|
||||
|
||||
> 회사마다 세율·한도가 달라도 **코드는 안 고칩니다.** 전부 데이터(`system_config`, `regulatory_limit`, `installment_ratio`, `chargeback_grade`)로 빼놨기 때문에 값만 바꾸면 됩니다.
|
||||
|
||||
---
|
||||
|
||||
## 9. 세금과 공제 — 통장에 적게 들어오는 이유
|
||||
|
||||
설계사에게 주는 돈(gross)에서 **세금을 먼저 떼고(원천징수)**, **공제를 빼면** 실제 입금액(net)이 됩니다.
|
||||
|
||||
| 항목 | 율 | 설명 |
|
||||
|---|---|---|
|
||||
| 사업소득 원천징수 | 3.3% | 일반 설계사(사업소득자) |
|
||||
| 기타소득 원천징수 | 8.8% | 기타소득 유형 |
|
||||
| 지방소득세 | 소득세의 10% | 소득세에 부가 |
|
||||
| 부가가치세 | 10% | 과세사업자가 세금계산서 발행 시 GA가 추가 지급(+) |
|
||||
|
||||
**공제(deduction)**: 미리 준 돈(가지급금) 회수, 대출 상환, **적립금** 등을 그 달 정산액에서 차감.
|
||||
|
||||
```
|
||||
gross − 원천세(소득세+지방세) − 공제 (+부가세, 과세사업자) = net(실지급액) → 계좌이체(펌뱅킹)
|
||||
```
|
||||
|
||||
**예시(과세사업자, 수수료 450,000원):** 소득세 14,850 + 지방세 1,485 빼고, 부가세 45,000 더하면 → **실지급 478,665원.**
|
||||
|
||||
---
|
||||
|
||||
## 10. 한 달이 흘러가는 과정 — 월 정산 파이프라인
|
||||
|
||||
매월 배치가 아래 순서로 돈다고 생각하면 됩니다.
|
||||
|
||||
```
|
||||
보험사 데이터 수신 → 표준포맷 변환 → 대사(검증) →
|
||||
모집수수료 계산 → 유지수수료 계산(승계 반영) → 환수·예외 →
|
||||
오버라이드 계산 → 집계(설계사×월, 세금·공제 반영) →
|
||||
회계 분개 → (분기/연말이면) 세무신고 자료
|
||||
```
|
||||
|
||||
마감은 **단계마감(★신규)**으로 수신→계산→검증→지급 단계별로 잠급니다. 한 달을 닫으면(마감) 더는 못 고치고, 오류는 **정정 전표**로만 바로잡습니다(원본 보존). 설계사가 금액에 이의를 제기하면 **이의신청**으로 받습니다.
|
||||
|
||||
**상태 흐름:** `DRAFT(계산됨) → CONFIRMED(확정) → PAID(지급완료)`. 확정·지급된 건은 재정산해도 안 바뀝니다(보호).
|
||||
|
||||
---
|
||||
|
||||
## 11. 수입 vs 지급, 그리고 수지차 — 회사는 어디서 남기나 (★ 신규 핵심)
|
||||
|
||||
신입이 가장 헷갈리는 지점입니다. 두 방향을 구분하세요.
|
||||
|
||||
| 방향 | 누가 → 누구 | 시스템 용어 |
|
||||
|---|---|---|
|
||||
| **수입(income)** | 보험사 → GA | 수입수수료 |
|
||||
| **지급(payout)** | GA → 설계사 | 모집/유지/… 수수료, 정산 |
|
||||
|
||||
```
|
||||
수지차(마진) = Σ수입수수료 − Σ지급수수료
|
||||
```
|
||||
|
||||
- 수지차가 (+)면 GA가 남긴 것, (−)면 그달 지급이 수입보다 많았던 것(분급·시상 등으로 일시적으로 발생 가능).
|
||||
- 여기에 **본사정산**(GA 자체 수입/비용)까지 합치면 회사 전체 손익이 보입니다.
|
||||
- 우리 시스템의 `수입수수료/수지차` 화면에서 **재계산 버튼**을 누르면 그달 수입 합과 지급 합(settle_master 기준)을 모아 수지차를 계산합니다.
|
||||
|
||||
---
|
||||
|
||||
## 12. 자주 나오는 질문 (FAQ)
|
||||
|
||||
1. **"수수료를 왜 한 번에 안 주죠?"** → 1200% 룰·분급 규제 + 조기해지 환수 리스크 때문. 7년 분할이 표준.
|
||||
2. **"설계사가 그만두면 그 계약 수수료는?"** → 유지수수료는 후임(승계)에게, 모집수수료는 원 설계사에게.
|
||||
3. **"gross인데 통장엔 적게 들어와요."** → 원천세(3.3%) + 공제를 뺀 net이 실지급액. (과세사업자는 부가세가 더해짐)
|
||||
4. **"같은 계약인데 설계사마다 수수료가 달라요."** → 지급률이 **설계사 등급**에 따라 다르기 때문.
|
||||
5. **"수입수수료랑 지급수수료가 헷갈려요."** → 수입 = 보험사가 우리에게 준 돈, 지급 = 우리가 설계사에게 준 돈. 차이가 수지차(마진).
|
||||
6. **"환산보험료는 왜 필요해요?"** → 상품마다 보험료 조건이 달라 그대로 비교하면 불공평. 환산율을 곱해 공통 기준으로 맞춘 게 환산보험료고, 그 합이 인정실적이 되어 시상·등급의 잣대가 됩니다.
|
||||
7. **"표준 세율/한도가 우리 회사랑 달라요."** → 코드 수정 불필요. 데이터(`system_config` 등)만 변경.
|
||||
|
||||
---
|
||||
|
||||
## 13. 용어 빠른 사전
|
||||
|
||||
| 한글 | 영문 | 한 줄 |
|
||||
|---|---|---|
|
||||
| 보험료 | Premium | 고객이 내는 돈. 계산의 출발점 |
|
||||
| 보험금 | Benefit | 사고·만기 때 고객이 받는 돈 |
|
||||
| 회차/차년 | Commission year | 계약 몇 년째. 1차년이 가장 큼 |
|
||||
| 유지율 | Persistency | 13·25개월 뒤 계약 생존율 |
|
||||
| 위촉/해촉 | Appoint/Terminate | 설계사 소속 시작/종료 |
|
||||
| 승계 | Succession | 해촉 시 후임 귀속 전환 |
|
||||
| 보험사요율 | Company rate | 보험사가 GA에 주는 비율 |
|
||||
| 지급률 | Payout rate | GA가 설계사에 주는 비율(등급별) |
|
||||
| 모집수수료 | Recruit | 체결 초년도 수수료(제일 큼) |
|
||||
| 유지수수료 | Maintain | 유지 기간 수수료 |
|
||||
| 수금수수료 | Collection | 보험료 납입 시 |
|
||||
| 갱신/부활수수료 | Renewal/Reinstatement | 갱신·부활 시 |
|
||||
| 오버라이드 | Override | 상위 직책 수당 |
|
||||
| 시상 | Award | 목표 달성 상금 |
|
||||
| 소개/이관수수료 | Referral/Transfer | 소개·이관 대가 |
|
||||
| 정착지원금 | Settling support | 신입 정착 지원금 |
|
||||
| 수입수수료 | Income commission | 보험사가 GA에 준 돈 |
|
||||
| 수지차 | Margin | 수입 − 지급 (GA 마진) |
|
||||
| 환산보험료 | Converted premium | 공통 기준 환산 보험료 |
|
||||
| 인정실적 | Recognized performance | 시상·등급 기준 실적 |
|
||||
| 본사정산 | HQ settlement | GA 자체 수입/비용 정산 |
|
||||
| 적립금 | Reserve fund | 환수 대비 적립·잔액 |
|
||||
| 분급 | Installment | 수수료 7년 분할 |
|
||||
| 차등환수 | Chargeback | 조기 해지 환수 |
|
||||
| 원천징수 | Withholding | 세금 미리 떼기(3.3%) |
|
||||
| 공제 | Deduction | 가지급금 등 차감 |
|
||||
| 단계마감 | Step close | 단계별 정산 잠금 |
|
||||
| 정산 | Settlement | 월별 계산·확정·지급 |
|
||||
| 원장 | Ledger | 계산 최소 단위 기록 |
|
||||
| 명세서 | Statement | 설계사 지급 내역서 |
|
||||
| 펌뱅킹 | Firm banking | 대량 계좌이체 |
|
||||
|
||||
---
|
||||
|
||||
*기준: 한국 보험업감독규정 표준 가정값. 시스템 schema (incar 갭 반영 P7+P8 14개 도메인 포함). 표준값은 데이터로 외부화되어 코드 변경 없이 조정 가능.*
|
||||
*더 깊은 코드/배치/규정 레벨은 [`DOMAIN_KNOWLEDGE.md`](DOMAIN_KNOWLEDGE.md), [`DOMAIN_GAP_P7.md`](DOMAIN_GAP_P7.md), [`DOMAIN_GAP_P8.md`](DOMAIN_GAP_P8.md) 참고.*
|
||||
@@ -0,0 +1,82 @@
|
||||
package com.ga.api.controller.commission;
|
||||
|
||||
import com.ga.api.service.commission.AwardService;
|
||||
import com.ga.common.annotation.DataChangeLog;
|
||||
import com.ga.common.annotation.RequirePermission;
|
||||
import com.ga.common.model.ApiResponse;
|
||||
import com.ga.common.model.PageResponse;
|
||||
import com.ga.core.vo.commission.*;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@Tag(name = "시상")
|
||||
@RestController
|
||||
@RequestMapping("/api/awards")
|
||||
@RequiredArgsConstructor
|
||||
public class AwardController {
|
||||
|
||||
private final AwardService service;
|
||||
|
||||
// ── Rule ──────────────────────────────────────────────────────────────────
|
||||
|
||||
@GetMapping("/rules")
|
||||
@RequirePermission(menu = "AWARD", perm = "READ")
|
||||
public ApiResponse<PageResponse<AwardRuleResp>> listRules(
|
||||
@ModelAttribute AwardRuleSearchParam param) {
|
||||
return ApiResponse.ok(service.listRules(param));
|
||||
}
|
||||
|
||||
@GetMapping("/rules/{ruleId}")
|
||||
@RequirePermission(menu = "AWARD", perm = "READ")
|
||||
public ApiResponse<AwardRuleResp> detailRule(@PathVariable Long ruleId) {
|
||||
return ApiResponse.ok(service.detailRule(ruleId));
|
||||
}
|
||||
|
||||
@PostMapping("/rules")
|
||||
@RequirePermission(menu = "AWARD", perm = "CREATE")
|
||||
@DataChangeLog(menu = "AWARD", table = "award_rule")
|
||||
public ApiResponse<Long> createRule(@Valid @RequestBody AwardRuleSaveReq req) {
|
||||
return ApiResponse.ok(service.createRule(req));
|
||||
}
|
||||
|
||||
@PutMapping("/rules/{ruleId}")
|
||||
@RequirePermission(menu = "AWARD", perm = "UPDATE")
|
||||
@DataChangeLog(menu = "AWARD", table = "award_rule")
|
||||
public ApiResponse<Void> updateRule(@PathVariable Long ruleId,
|
||||
@Valid @RequestBody AwardRuleSaveReq req) {
|
||||
service.updateRule(ruleId, req);
|
||||
return ApiResponse.ok();
|
||||
}
|
||||
|
||||
@DeleteMapping("/rules/{ruleId}")
|
||||
@RequirePermission(menu = "AWARD", perm = "DELETE")
|
||||
@DataChangeLog(menu = "AWARD", table = "award_rule")
|
||||
public ApiResponse<Void> deleteRule(@PathVariable Long ruleId) {
|
||||
service.deleteRule(ruleId);
|
||||
return ApiResponse.ok();
|
||||
}
|
||||
|
||||
// ── Ledger ────────────────────────────────────────────────────────────────
|
||||
|
||||
@GetMapping("/ledgers")
|
||||
@RequirePermission(menu = "AWARD", perm = "READ")
|
||||
public ApiResponse<PageResponse<AwardLedgerResp>> listLedgers(
|
||||
@ModelAttribute AwardLedgerSearchParam param) {
|
||||
return ApiResponse.ok(service.listLedgers(param));
|
||||
}
|
||||
|
||||
@GetMapping("/ledgers/{ledgerId}")
|
||||
@RequirePermission(menu = "AWARD", perm = "READ")
|
||||
public ApiResponse<AwardLedgerResp> detailLedger(@PathVariable Long ledgerId) {
|
||||
return ApiResponse.ok(service.detailLedger(ledgerId));
|
||||
}
|
||||
|
||||
@PostMapping("/ledgers")
|
||||
@RequirePermission(menu = "AWARD", perm = "CREATE")
|
||||
@DataChangeLog(menu = "AWARD", table = "award_ledger")
|
||||
public ApiResponse<Long> createLedger(@Valid @RequestBody AwardLedgerSaveReq req) {
|
||||
return ApiResponse.ok(service.createLedger(req));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package com.ga.api.controller.commission;
|
||||
|
||||
import com.ga.api.service.commission.BranchAllowanceService;
|
||||
import com.ga.common.annotation.DataChangeLog;
|
||||
import com.ga.common.annotation.RequirePermission;
|
||||
import com.ga.common.model.ApiResponse;
|
||||
import com.ga.common.model.PageResponse;
|
||||
import com.ga.core.vo.commission.*;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@Tag(name = "지점장수당")
|
||||
@RestController
|
||||
@RequestMapping("/api/branch-allowances")
|
||||
@RequiredArgsConstructor
|
||||
public class BranchAllowanceController {
|
||||
|
||||
private final BranchAllowanceService service;
|
||||
|
||||
// ── Rule ──────────────────────────────────────────────────────────────────
|
||||
|
||||
@GetMapping("/rules")
|
||||
@RequirePermission(menu = "BRANCH_MGR_ALLOWANCE", perm = "READ")
|
||||
public ApiResponse<PageResponse<BranchMgrAllowanceRuleResp>> listRules(
|
||||
@ModelAttribute BranchMgrAllowanceRuleSearchParam param) {
|
||||
return ApiResponse.ok(service.listRules(param));
|
||||
}
|
||||
|
||||
@GetMapping("/rules/{ruleId}")
|
||||
@RequirePermission(menu = "BRANCH_MGR_ALLOWANCE", perm = "READ")
|
||||
public ApiResponse<BranchMgrAllowanceRuleResp> detailRule(@PathVariable Long ruleId) {
|
||||
return ApiResponse.ok(service.detailRule(ruleId));
|
||||
}
|
||||
|
||||
@PostMapping("/rules")
|
||||
@RequirePermission(menu = "BRANCH_MGR_ALLOWANCE", perm = "CREATE")
|
||||
@DataChangeLog(menu = "BRANCH_MGR_ALLOWANCE", table = "branch_mgr_allowance_rule")
|
||||
public ApiResponse<Long> createRule(@Valid @RequestBody BranchMgrAllowanceRuleSaveReq req) {
|
||||
return ApiResponse.ok(service.createRule(req));
|
||||
}
|
||||
|
||||
@PutMapping("/rules/{ruleId}")
|
||||
@RequirePermission(menu = "BRANCH_MGR_ALLOWANCE", perm = "UPDATE")
|
||||
@DataChangeLog(menu = "BRANCH_MGR_ALLOWANCE", table = "branch_mgr_allowance_rule")
|
||||
public ApiResponse<Void> updateRule(@PathVariable Long ruleId,
|
||||
@Valid @RequestBody BranchMgrAllowanceRuleSaveReq req) {
|
||||
service.updateRule(ruleId, req);
|
||||
return ApiResponse.ok();
|
||||
}
|
||||
|
||||
@DeleteMapping("/rules/{ruleId}")
|
||||
@RequirePermission(menu = "BRANCH_MGR_ALLOWANCE", perm = "DELETE")
|
||||
@DataChangeLog(menu = "BRANCH_MGR_ALLOWANCE", table = "branch_mgr_allowance_rule")
|
||||
public ApiResponse<Void> deleteRule(@PathVariable Long ruleId) {
|
||||
service.deleteRule(ruleId);
|
||||
return ApiResponse.ok();
|
||||
}
|
||||
|
||||
// ── Ledger ────────────────────────────────────────────────────────────────
|
||||
|
||||
@GetMapping("/ledgers")
|
||||
@RequirePermission(menu = "BRANCH_MGR_ALLOWANCE", perm = "READ")
|
||||
public ApiResponse<PageResponse<BranchMgrAllowanceLedgerResp>> listLedgers(
|
||||
@ModelAttribute BranchMgrAllowanceLedgerSearchParam param) {
|
||||
return ApiResponse.ok(service.listLedgers(param));
|
||||
}
|
||||
|
||||
@GetMapping("/ledgers/{ledgerId}")
|
||||
@RequirePermission(menu = "BRANCH_MGR_ALLOWANCE", perm = "READ")
|
||||
public ApiResponse<BranchMgrAllowanceLedgerResp> detailLedger(@PathVariable Long ledgerId) {
|
||||
return ApiResponse.ok(service.detailLedger(ledgerId));
|
||||
}
|
||||
|
||||
@PostMapping("/ledgers")
|
||||
@RequirePermission(menu = "BRANCH_MGR_ALLOWANCE", perm = "CREATE")
|
||||
@DataChangeLog(menu = "BRANCH_MGR_ALLOWANCE", table = "branch_mgr_allowance_ledger")
|
||||
public ApiResponse<Long> createLedger(@Valid @RequestBody BranchMgrAllowanceLedgerSaveReq req) {
|
||||
return ApiResponse.ok(service.createLedger(req));
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
package com.ga.api.controller.commission;
|
||||
|
||||
import com.ga.api.service.commission.CommissionSimulationService;
|
||||
import com.ga.common.annotation.DataChangeLog;
|
||||
import com.ga.common.annotation.RequirePermission;
|
||||
import com.ga.common.model.ApiResponse;
|
||||
import com.ga.common.model.PageResponse;
|
||||
import com.ga.core.vo.commission.CommissionSimulationResp;
|
||||
import com.ga.core.vo.commission.CommissionSimulationSaveReq;
|
||||
import com.ga.core.vo.commission.CommissionSimulationSearchParam;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@Tag(name = "수수료 시뮬레이터")
|
||||
@RestController
|
||||
@RequestMapping("/api/commission-simulations")
|
||||
@RequiredArgsConstructor
|
||||
public class CommissionSimulationController {
|
||||
|
||||
private final CommissionSimulationService service;
|
||||
|
||||
@GetMapping
|
||||
@RequirePermission(menu = "COMM_SIMULATOR", perm = "READ")
|
||||
public ApiResponse<PageResponse<CommissionSimulationResp>> list(
|
||||
@ModelAttribute CommissionSimulationSearchParam param) {
|
||||
return ApiResponse.ok(service.list(param));
|
||||
}
|
||||
|
||||
@GetMapping("/{simId}")
|
||||
@RequirePermission(menu = "COMM_SIMULATOR", perm = "READ")
|
||||
public ApiResponse<CommissionSimulationResp> detail(@PathVariable Long simId) {
|
||||
return ApiResponse.ok(service.detail(simId));
|
||||
}
|
||||
|
||||
@PostMapping("/simulate")
|
||||
@RequirePermission(menu = "COMM_SIMULATOR", perm = "CREATE")
|
||||
@DataChangeLog(menu = "COMM_SIMULATOR", table = "commission_simulation")
|
||||
public ApiResponse<CommissionSimulationResp> simulate(@Valid @RequestBody CommissionSimulationSaveReq req) {
|
||||
return ApiResponse.ok(service.simulate(req));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package com.ga.api.controller.commission;
|
||||
|
||||
import com.ga.api.service.commission.GradeEvaluationService;
|
||||
import com.ga.common.annotation.DataChangeLog;
|
||||
import com.ga.common.annotation.RequirePermission;
|
||||
import com.ga.common.model.ApiResponse;
|
||||
import com.ga.common.model.PageResponse;
|
||||
import com.ga.core.vo.commission.AgentGradeEvaluationResp;
|
||||
import com.ga.core.vo.commission.AgentGradeEvaluationSaveReq;
|
||||
import com.ga.core.vo.commission.AgentGradeEvaluationSearchParam;
|
||||
import com.ga.core.vo.commission.GradeEvalRuleResp;
|
||||
import com.ga.core.vo.commission.GradeEvalRuleSaveReq;
|
||||
import com.ga.core.vo.commission.GradeEvalRuleSearchParam;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@Tag(name = "등급평가")
|
||||
@RestController
|
||||
@RequestMapping("/api/grade-evaluations")
|
||||
@RequiredArgsConstructor
|
||||
public class GradeEvaluationController {
|
||||
|
||||
private final GradeEvaluationService service;
|
||||
|
||||
// ── Rule ──────────────────────────────────────────────────────────────────
|
||||
|
||||
@GetMapping("/rules")
|
||||
@RequirePermission(menu = "GRADE_EVAL", perm = "READ")
|
||||
public ApiResponse<PageResponse<GradeEvalRuleResp>> listRules(@ModelAttribute GradeEvalRuleSearchParam param) {
|
||||
return ApiResponse.ok(service.listRules(param));
|
||||
}
|
||||
|
||||
@GetMapping("/rules/{ruleId}")
|
||||
@RequirePermission(menu = "GRADE_EVAL", perm = "READ")
|
||||
public ApiResponse<GradeEvalRuleResp> detailRule(@PathVariable Long ruleId) {
|
||||
return ApiResponse.ok(service.detailRule(ruleId));
|
||||
}
|
||||
|
||||
@PostMapping("/rules")
|
||||
@RequirePermission(menu = "GRADE_EVAL", perm = "CREATE")
|
||||
@DataChangeLog(menu = "GRADE_EVAL", table = "grade_eval_rule")
|
||||
public ApiResponse<Long> createRule(@Valid @RequestBody GradeEvalRuleSaveReq req) {
|
||||
return ApiResponse.ok(service.createRule(req));
|
||||
}
|
||||
|
||||
@PutMapping("/rules/{ruleId}")
|
||||
@RequirePermission(menu = "GRADE_EVAL", perm = "UPDATE")
|
||||
@DataChangeLog(menu = "GRADE_EVAL", table = "grade_eval_rule")
|
||||
public ApiResponse<Void> updateRule(@PathVariable Long ruleId,
|
||||
@Valid @RequestBody GradeEvalRuleSaveReq req) {
|
||||
service.updateRule(ruleId, req);
|
||||
return ApiResponse.ok();
|
||||
}
|
||||
|
||||
@DeleteMapping("/rules/{ruleId}")
|
||||
@RequirePermission(menu = "GRADE_EVAL", perm = "DELETE")
|
||||
@DataChangeLog(menu = "GRADE_EVAL", table = "grade_eval_rule")
|
||||
public ApiResponse<Void> deleteRule(@PathVariable Long ruleId) {
|
||||
service.deleteRule(ruleId);
|
||||
return ApiResponse.ok();
|
||||
}
|
||||
|
||||
// ── Evaluation ────────────────────────────────────────────────────────────
|
||||
|
||||
@GetMapping
|
||||
@RequirePermission(menu = "GRADE_EVAL", perm = "READ")
|
||||
public ApiResponse<PageResponse<AgentGradeEvaluationResp>> listEvals(
|
||||
@ModelAttribute AgentGradeEvaluationSearchParam param) {
|
||||
return ApiResponse.ok(service.listEvals(param));
|
||||
}
|
||||
|
||||
@GetMapping("/{evalId}")
|
||||
@RequirePermission(menu = "GRADE_EVAL", perm = "READ")
|
||||
public ApiResponse<AgentGradeEvaluationResp> detailEval(@PathVariable Long evalId) {
|
||||
return ApiResponse.ok(service.detailEval(evalId));
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@RequirePermission(menu = "GRADE_EVAL", perm = "CREATE")
|
||||
@DataChangeLog(menu = "GRADE_EVAL", table = "agent_grade_evaluation")
|
||||
public ApiResponse<Long> createEval(@Valid @RequestBody AgentGradeEvaluationSaveReq req) {
|
||||
return ApiResponse.ok(service.createEval(req));
|
||||
}
|
||||
|
||||
@PostMapping("/{evalId}/apply")
|
||||
@RequirePermission(menu = "GRADE_EVAL", perm = "UPDATE")
|
||||
@DataChangeLog(menu = "GRADE_EVAL", table = "agent_grade_evaluation")
|
||||
public ApiResponse<Void> applyEval(@PathVariable Long evalId) {
|
||||
service.applyEval(evalId);
|
||||
return ApiResponse.ok();
|
||||
}
|
||||
|
||||
@DeleteMapping("/{evalId}")
|
||||
@RequirePermission(menu = "GRADE_EVAL", perm = "DELETE")
|
||||
@DataChangeLog(menu = "GRADE_EVAL", table = "agent_grade_evaluation")
|
||||
public ApiResponse<Void> deleteEval(@PathVariable Long evalId) {
|
||||
service.deleteEval(evalId);
|
||||
return ApiResponse.ok();
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package com.ga.api.controller.commission;
|
||||
|
||||
import com.ga.api.service.commission.GuaranteeInsuranceService;
|
||||
import com.ga.common.annotation.DataChangeLog;
|
||||
import com.ga.common.annotation.RequirePermission;
|
||||
import com.ga.common.model.ApiResponse;
|
||||
import com.ga.common.model.PageResponse;
|
||||
import com.ga.core.vo.commission.AgentGuaranteeInsuranceResp;
|
||||
import com.ga.core.vo.commission.AgentGuaranteeInsuranceSaveReq;
|
||||
import com.ga.core.vo.commission.AgentGuaranteeInsuranceSearchParam;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@Tag(name = "보증보험")
|
||||
@RestController
|
||||
@RequestMapping("/api/guarantee-insurances")
|
||||
@RequiredArgsConstructor
|
||||
public class GuaranteeInsuranceController {
|
||||
|
||||
private final GuaranteeInsuranceService service;
|
||||
|
||||
@GetMapping
|
||||
@RequirePermission(menu = "GUARANTEE_INS", perm = "READ")
|
||||
public ApiResponse<PageResponse<AgentGuaranteeInsuranceResp>> list(
|
||||
@ModelAttribute AgentGuaranteeInsuranceSearchParam param) {
|
||||
return ApiResponse.ok(service.list(param));
|
||||
}
|
||||
|
||||
@GetMapping("/{guaranteeId}")
|
||||
@RequirePermission(menu = "GUARANTEE_INS", perm = "READ")
|
||||
public ApiResponse<AgentGuaranteeInsuranceResp> detail(@PathVariable Long guaranteeId) {
|
||||
return ApiResponse.ok(service.detail(guaranteeId));
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@RequirePermission(menu = "GUARANTEE_INS", perm = "CREATE")
|
||||
@DataChangeLog(menu = "GUARANTEE_INS", table = "agent_guarantee_insurance")
|
||||
public ApiResponse<Long> create(@Valid @RequestBody AgentGuaranteeInsuranceSaveReq req) {
|
||||
return ApiResponse.ok(service.create(req));
|
||||
}
|
||||
|
||||
@PutMapping("/{guaranteeId}")
|
||||
@RequirePermission(menu = "GUARANTEE_INS", perm = "UPDATE")
|
||||
@DataChangeLog(menu = "GUARANTEE_INS", table = "agent_guarantee_insurance")
|
||||
public ApiResponse<Void> update(@PathVariable Long guaranteeId,
|
||||
@Valid @RequestBody AgentGuaranteeInsuranceSaveReq req) {
|
||||
service.update(guaranteeId, req);
|
||||
return ApiResponse.ok();
|
||||
}
|
||||
|
||||
@DeleteMapping("/{guaranteeId}")
|
||||
@RequirePermission(menu = "GUARANTEE_INS", perm = "DELETE")
|
||||
@DataChangeLog(menu = "GUARANTEE_INS", table = "agent_guarantee_insurance")
|
||||
public ApiResponse<Void> delete(@PathVariable Long guaranteeId) {
|
||||
service.delete(guaranteeId);
|
||||
return ApiResponse.ok();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.ga.api.controller.commission;
|
||||
|
||||
import com.ga.api.service.commission.HqSettleService;
|
||||
import com.ga.api.service.commission.HqSettleService.HqSummaryResp;
|
||||
import com.ga.common.annotation.DataChangeLog;
|
||||
import com.ga.common.annotation.RequirePermission;
|
||||
import com.ga.common.model.ApiResponse;
|
||||
import com.ga.common.model.PageResponse;
|
||||
import com.ga.core.vo.commission.HqSettleItemResp;
|
||||
import com.ga.core.vo.commission.HqSettleItemSaveReq;
|
||||
import com.ga.core.vo.commission.HqSettleItemSearchParam;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@Tag(name = "본사정산")
|
||||
@RestController
|
||||
@RequestMapping("/api/hq-settles")
|
||||
@RequiredArgsConstructor
|
||||
public class HqSettleController {
|
||||
|
||||
private final HqSettleService service;
|
||||
|
||||
@GetMapping
|
||||
@RequirePermission(menu = "HQ_SETTLE", perm = "READ")
|
||||
public ApiResponse<PageResponse<HqSettleItemResp>> list(@ModelAttribute HqSettleItemSearchParam param) {
|
||||
return ApiResponse.ok(service.list(param));
|
||||
}
|
||||
|
||||
@GetMapping("/summary")
|
||||
@RequirePermission(menu = "HQ_SETTLE", perm = "READ")
|
||||
public ApiResponse<HqSummaryResp> summary(@RequestParam String settleMonth) {
|
||||
return ApiResponse.ok(service.summary(settleMonth));
|
||||
}
|
||||
|
||||
@GetMapping("/{itemId}")
|
||||
@RequirePermission(menu = "HQ_SETTLE", perm = "READ")
|
||||
public ApiResponse<HqSettleItemResp> detail(@PathVariable Long itemId) {
|
||||
return ApiResponse.ok(service.detail(itemId));
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@RequirePermission(menu = "HQ_SETTLE", perm = "CREATE")
|
||||
@DataChangeLog(menu = "HQ_SETTLE", table = "hq_settle_item")
|
||||
public ApiResponse<Long> create(@Valid @RequestBody HqSettleItemSaveReq req) {
|
||||
return ApiResponse.ok(service.create(req));
|
||||
}
|
||||
|
||||
@PutMapping("/{itemId}")
|
||||
@RequirePermission(menu = "HQ_SETTLE", perm = "UPDATE")
|
||||
@DataChangeLog(menu = "HQ_SETTLE", table = "hq_settle_item")
|
||||
public ApiResponse<Void> update(@PathVariable Long itemId,
|
||||
@Valid @RequestBody HqSettleItemSaveReq req) {
|
||||
service.update(itemId, req);
|
||||
return ApiResponse.ok();
|
||||
}
|
||||
|
||||
@DeleteMapping("/{itemId}")
|
||||
@RequirePermission(menu = "HQ_SETTLE", perm = "DELETE")
|
||||
@DataChangeLog(menu = "HQ_SETTLE", table = "hq_settle_item")
|
||||
public ApiResponse<Void> delete(@PathVariable Long itemId) {
|
||||
service.delete(itemId);
|
||||
return ApiResponse.ok();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.ga.api.controller.commission;
|
||||
|
||||
import com.ga.api.service.commission.IncomeCommissionService;
|
||||
import com.ga.common.annotation.DataChangeLog;
|
||||
import com.ga.common.annotation.RequirePermission;
|
||||
import com.ga.common.model.ApiResponse;
|
||||
import com.ga.common.model.PageResponse;
|
||||
import com.ga.core.vo.commission.*;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@Tag(name = "수입수수료/수지차")
|
||||
@RestController
|
||||
@RequestMapping("/api/income-commissions")
|
||||
@RequiredArgsConstructor
|
||||
public class IncomeCommissionController {
|
||||
|
||||
private final IncomeCommissionService service;
|
||||
|
||||
// ── Income Ledger ────────────────────────────────────────────────────────
|
||||
|
||||
@GetMapping("/ledgers")
|
||||
@RequirePermission(menu = "INCOME_COMM", perm = "READ")
|
||||
public ApiResponse<PageResponse<IncomeCommissionLedgerResp>> listLedgers(
|
||||
@ModelAttribute IncomeCommissionLedgerSearchParam param) {
|
||||
return ApiResponse.ok(service.listLedgers(param));
|
||||
}
|
||||
|
||||
@GetMapping("/ledgers/{incomeId}")
|
||||
@RequirePermission(menu = "INCOME_COMM", perm = "READ")
|
||||
public ApiResponse<IncomeCommissionLedgerResp> detailLedger(@PathVariable Long incomeId) {
|
||||
return ApiResponse.ok(service.detailLedger(incomeId));
|
||||
}
|
||||
|
||||
@PostMapping("/ledgers")
|
||||
@RequirePermission(menu = "INCOME_COMM", perm = "CREATE")
|
||||
@DataChangeLog(menu = "INCOME_COMM", table = "income_commission_ledger")
|
||||
public ApiResponse<Long> createLedger(@Valid @RequestBody IncomeCommissionLedgerSaveReq req) {
|
||||
return ApiResponse.ok(service.createLedger(req));
|
||||
}
|
||||
|
||||
// ── Margin Summary ───────────────────────────────────────────────────────
|
||||
|
||||
@GetMapping("/margins")
|
||||
@RequirePermission(menu = "INCOME_COMM", perm = "READ")
|
||||
public ApiResponse<PageResponse<MarginSummaryResp>> listMargins(
|
||||
@ModelAttribute MarginSummarySearchParam param) {
|
||||
return ApiResponse.ok(service.listMargins(param));
|
||||
}
|
||||
|
||||
@GetMapping("/margins/{marginId}")
|
||||
@RequirePermission(menu = "INCOME_COMM", perm = "READ")
|
||||
public ApiResponse<MarginSummaryResp> detailMargin(@PathVariable Long marginId) {
|
||||
return ApiResponse.ok(service.detailMargin(marginId));
|
||||
}
|
||||
|
||||
@PostMapping("/margin/recalc")
|
||||
@RequirePermission(menu = "INCOME_COMM", perm = "CREATE")
|
||||
@DataChangeLog(menu = "INCOME_COMM", table = "margin_summary")
|
||||
public ApiResponse<Long> recalcMargin(@RequestParam String settleMonth,
|
||||
@RequestParam(required = false) Long agentId) {
|
||||
return ApiResponse.ok(service.recalcMargin(settleMonth, agentId));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.ga.api.controller.commission;
|
||||
|
||||
import com.ga.api.service.commission.JointContractService;
|
||||
import com.ga.common.annotation.DataChangeLog;
|
||||
import com.ga.common.annotation.RequirePermission;
|
||||
import com.ga.common.model.ApiResponse;
|
||||
import com.ga.common.model.PageResponse;
|
||||
import com.ga.core.vo.commission.JointContractResp;
|
||||
import com.ga.core.vo.commission.JointContractSaveReq;
|
||||
import com.ga.core.vo.commission.JointContractSearchParam;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@Tag(name = "공동계약")
|
||||
@RestController
|
||||
@RequestMapping("/api/joint-contracts")
|
||||
@RequiredArgsConstructor
|
||||
public class JointContractController {
|
||||
|
||||
private final JointContractService service;
|
||||
|
||||
@GetMapping
|
||||
@RequirePermission(menu = "JOINT_CONTRACT", perm = "READ")
|
||||
public ApiResponse<PageResponse<JointContractResp>> list(@ModelAttribute JointContractSearchParam param) {
|
||||
return ApiResponse.ok(service.list(param));
|
||||
}
|
||||
|
||||
@GetMapping("/{jointId}")
|
||||
@RequirePermission(menu = "JOINT_CONTRACT", perm = "READ")
|
||||
public ApiResponse<JointContractResp> detail(@PathVariable Long jointId) {
|
||||
return ApiResponse.ok(service.detail(jointId));
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@RequirePermission(menu = "JOINT_CONTRACT", perm = "CREATE")
|
||||
@DataChangeLog(menu = "JOINT_CONTRACT", table = "joint_contract")
|
||||
public ApiResponse<Long> create(@Valid @RequestBody JointContractSaveReq req) {
|
||||
return ApiResponse.ok(service.create(req));
|
||||
}
|
||||
|
||||
@PutMapping("/{jointId}")
|
||||
@RequirePermission(menu = "JOINT_CONTRACT", perm = "UPDATE")
|
||||
@DataChangeLog(menu = "JOINT_CONTRACT", table = "joint_contract")
|
||||
public ApiResponse<Void> update(@PathVariable Long jointId,
|
||||
@Valid @RequestBody JointContractSaveReq req) {
|
||||
service.update(jointId, req);
|
||||
return ApiResponse.ok();
|
||||
}
|
||||
|
||||
@DeleteMapping("/{jointId}")
|
||||
@RequirePermission(menu = "JOINT_CONTRACT", perm = "DELETE")
|
||||
@DataChangeLog(menu = "JOINT_CONTRACT", table = "joint_contract")
|
||||
public ApiResponse<Void> delete(@PathVariable Long jointId) {
|
||||
service.delete(jointId);
|
||||
return ApiResponse.ok();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package com.ga.api.controller.commission;
|
||||
|
||||
import com.ga.api.service.commission.LifeSupportService;
|
||||
import com.ga.common.annotation.DataChangeLog;
|
||||
import com.ga.common.annotation.RequirePermission;
|
||||
import com.ga.common.model.ApiResponse;
|
||||
import com.ga.common.model.PageResponse;
|
||||
import com.ga.core.vo.commission.*;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@Tag(name = "생보운영지원수수료")
|
||||
@RestController
|
||||
@RequestMapping("/api/life-supports")
|
||||
@RequiredArgsConstructor
|
||||
public class LifeSupportController {
|
||||
|
||||
private final LifeSupportService service;
|
||||
|
||||
// ── Rule ──────────────────────────────────────────────────────────────────
|
||||
|
||||
@GetMapping("/rules")
|
||||
@RequirePermission(menu = "LIFE_OP_SUPPORT", perm = "READ")
|
||||
public ApiResponse<PageResponse<LifeSupportRuleResp>> listRules(
|
||||
@ModelAttribute LifeSupportRuleSearchParam param) {
|
||||
return ApiResponse.ok(service.listRules(param));
|
||||
}
|
||||
|
||||
@GetMapping("/rules/{ruleId}")
|
||||
@RequirePermission(menu = "LIFE_OP_SUPPORT", perm = "READ")
|
||||
public ApiResponse<LifeSupportRuleResp> detailRule(@PathVariable Long ruleId) {
|
||||
return ApiResponse.ok(service.detailRule(ruleId));
|
||||
}
|
||||
|
||||
@PostMapping("/rules")
|
||||
@RequirePermission(menu = "LIFE_OP_SUPPORT", perm = "CREATE")
|
||||
@DataChangeLog(menu = "LIFE_OP_SUPPORT", table = "life_support_rule")
|
||||
public ApiResponse<Long> createRule(@Valid @RequestBody LifeSupportRuleSaveReq req) {
|
||||
return ApiResponse.ok(service.createRule(req));
|
||||
}
|
||||
|
||||
@PutMapping("/rules/{ruleId}")
|
||||
@RequirePermission(menu = "LIFE_OP_SUPPORT", perm = "UPDATE")
|
||||
@DataChangeLog(menu = "LIFE_OP_SUPPORT", table = "life_support_rule")
|
||||
public ApiResponse<Void> updateRule(@PathVariable Long ruleId,
|
||||
@Valid @RequestBody LifeSupportRuleSaveReq req) {
|
||||
service.updateRule(ruleId, req);
|
||||
return ApiResponse.ok();
|
||||
}
|
||||
|
||||
@DeleteMapping("/rules/{ruleId}")
|
||||
@RequirePermission(menu = "LIFE_OP_SUPPORT", perm = "DELETE")
|
||||
@DataChangeLog(menu = "LIFE_OP_SUPPORT", table = "life_support_rule")
|
||||
public ApiResponse<Void> deleteRule(@PathVariable Long ruleId) {
|
||||
service.deleteRule(ruleId);
|
||||
return ApiResponse.ok();
|
||||
}
|
||||
|
||||
// ── Ledger ────────────────────────────────────────────────────────────────
|
||||
|
||||
@GetMapping("/ledgers")
|
||||
@RequirePermission(menu = "LIFE_OP_SUPPORT", perm = "READ")
|
||||
public ApiResponse<PageResponse<LifeSupportLedgerResp>> listLedgers(
|
||||
@ModelAttribute LifeSupportLedgerSearchParam param) {
|
||||
return ApiResponse.ok(service.listLedgers(param));
|
||||
}
|
||||
|
||||
@GetMapping("/ledgers/{ledgerId}")
|
||||
@RequirePermission(menu = "LIFE_OP_SUPPORT", perm = "READ")
|
||||
public ApiResponse<LifeSupportLedgerResp> detailLedger(@PathVariable Long ledgerId) {
|
||||
return ApiResponse.ok(service.detailLedger(ledgerId));
|
||||
}
|
||||
|
||||
@PostMapping("/ledgers")
|
||||
@RequirePermission(menu = "LIFE_OP_SUPPORT", perm = "CREATE")
|
||||
@DataChangeLog(menu = "LIFE_OP_SUPPORT", table = "life_support_ledger")
|
||||
public ApiResponse<Long> createLedger(@Valid @RequestBody LifeSupportLedgerSaveReq req) {
|
||||
return ApiResponse.ok(service.createLedger(req));
|
||||
}
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
package com.ga.api.controller.commission;
|
||||
|
||||
import com.ga.api.service.commission.RecognizedPerformanceService;
|
||||
import com.ga.common.annotation.DataChangeLog;
|
||||
import com.ga.common.annotation.RequirePermission;
|
||||
import com.ga.common.model.ApiResponse;
|
||||
import com.ga.common.model.PageResponse;
|
||||
import com.ga.core.vo.commission.*;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@Tag(name = "인정실적")
|
||||
@RestController
|
||||
@RequestMapping("/api/recognized-performances")
|
||||
@RequiredArgsConstructor
|
||||
public class RecognizedPerformanceController {
|
||||
|
||||
private final RecognizedPerformanceService service;
|
||||
|
||||
// ── Product Conversion Rate (rule) ────────────────────────────────────────
|
||||
|
||||
@GetMapping("/rates")
|
||||
@RequirePermission(menu = "RECOGNIZED_PERF", perm = "READ")
|
||||
public ApiResponse<PageResponse<ProductConversionRateResp>> listRates(
|
||||
@ModelAttribute ProductConversionRateSearchParam param) {
|
||||
return ApiResponse.ok(service.listRates(param));
|
||||
}
|
||||
|
||||
@GetMapping("/rates/{rateId}")
|
||||
@RequirePermission(menu = "RECOGNIZED_PERF", perm = "READ")
|
||||
public ApiResponse<ProductConversionRateResp> detailRate(@PathVariable Long rateId) {
|
||||
return ApiResponse.ok(service.detailRate(rateId));
|
||||
}
|
||||
|
||||
@PostMapping("/rates")
|
||||
@RequirePermission(menu = "RECOGNIZED_PERF", perm = "CREATE")
|
||||
@DataChangeLog(menu = "RECOGNIZED_PERF", table = "product_conversion_rate")
|
||||
public ApiResponse<Long> createRate(@Valid @RequestBody ProductConversionRateSaveReq req) {
|
||||
return ApiResponse.ok(service.createRate(req));
|
||||
}
|
||||
|
||||
@PutMapping("/rates/{rateId}")
|
||||
@RequirePermission(menu = "RECOGNIZED_PERF", perm = "UPDATE")
|
||||
@DataChangeLog(menu = "RECOGNIZED_PERF", table = "product_conversion_rate")
|
||||
public ApiResponse<Void> updateRate(@PathVariable Long rateId,
|
||||
@Valid @RequestBody ProductConversionRateSaveReq req) {
|
||||
service.updateRate(rateId, req);
|
||||
return ApiResponse.ok();
|
||||
}
|
||||
|
||||
@DeleteMapping("/rates/{rateId}")
|
||||
@RequirePermission(menu = "RECOGNIZED_PERF", perm = "DELETE")
|
||||
@DataChangeLog(menu = "RECOGNIZED_PERF", table = "product_conversion_rate")
|
||||
public ApiResponse<Void> deleteRate(@PathVariable Long rateId) {
|
||||
service.deleteRate(rateId);
|
||||
return ApiResponse.ok();
|
||||
}
|
||||
|
||||
// ── Ledger ────────────────────────────────────────────────────────────────
|
||||
|
||||
@GetMapping("/ledgers")
|
||||
@RequirePermission(menu = "RECOGNIZED_PERF", perm = "READ")
|
||||
public ApiResponse<PageResponse<RecognizedPerformanceLedgerResp>> listLedgers(
|
||||
@ModelAttribute RecognizedPerformanceLedgerSearchParam param) {
|
||||
return ApiResponse.ok(service.listLedgers(param));
|
||||
}
|
||||
|
||||
@GetMapping("/ledgers/{ledgerId}")
|
||||
@RequirePermission(menu = "RECOGNIZED_PERF", perm = "READ")
|
||||
public ApiResponse<RecognizedPerformanceLedgerResp> detailLedger(@PathVariable Long ledgerId) {
|
||||
return ApiResponse.ok(service.detailLedger(ledgerId));
|
||||
}
|
||||
|
||||
@PostMapping("/ledgers")
|
||||
@RequirePermission(menu = "RECOGNIZED_PERF", perm = "CREATE")
|
||||
@DataChangeLog(menu = "RECOGNIZED_PERF", table = "recognized_performance_ledger")
|
||||
public ApiResponse<Long> createLedger(@Valid @RequestBody RecognizedPerformanceLedgerSaveReq req) {
|
||||
return ApiResponse.ok(service.createLedger(req));
|
||||
}
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
package com.ga.api.controller.commission;
|
||||
|
||||
import com.ga.api.service.commission.ReferralCommissionService;
|
||||
import com.ga.common.annotation.DataChangeLog;
|
||||
import com.ga.common.annotation.RequirePermission;
|
||||
import com.ga.common.model.ApiResponse;
|
||||
import com.ga.common.model.PageResponse;
|
||||
import com.ga.core.vo.commission.*;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@Tag(name = "소개·이관수수료")
|
||||
@RestController
|
||||
@RequestMapping("/api/referral-commissions")
|
||||
@RequiredArgsConstructor
|
||||
public class ReferralCommissionController {
|
||||
|
||||
private final ReferralCommissionService service;
|
||||
|
||||
// ── Rule ──────────────────────────────────────────────────────────────────
|
||||
|
||||
@GetMapping("/rules")
|
||||
@RequirePermission(menu = "REFERRAL_COMM", perm = "READ")
|
||||
public ApiResponse<PageResponse<ReferralCommissionRuleResp>> listRules(
|
||||
@ModelAttribute ReferralCommissionRuleSearchParam param) {
|
||||
return ApiResponse.ok(service.listRules(param));
|
||||
}
|
||||
|
||||
@GetMapping("/rules/{ruleId}")
|
||||
@RequirePermission(menu = "REFERRAL_COMM", perm = "READ")
|
||||
public ApiResponse<ReferralCommissionRuleResp> detailRule(@PathVariable Long ruleId) {
|
||||
return ApiResponse.ok(service.detailRule(ruleId));
|
||||
}
|
||||
|
||||
@PostMapping("/rules")
|
||||
@RequirePermission(menu = "REFERRAL_COMM", perm = "CREATE")
|
||||
@DataChangeLog(menu = "REFERRAL_COMM", table = "referral_commission_rule")
|
||||
public ApiResponse<Long> createRule(@Valid @RequestBody ReferralCommissionRuleSaveReq req) {
|
||||
return ApiResponse.ok(service.createRule(req));
|
||||
}
|
||||
|
||||
@PutMapping("/rules/{ruleId}")
|
||||
@RequirePermission(menu = "REFERRAL_COMM", perm = "UPDATE")
|
||||
@DataChangeLog(menu = "REFERRAL_COMM", table = "referral_commission_rule")
|
||||
public ApiResponse<Void> updateRule(@PathVariable Long ruleId,
|
||||
@Valid @RequestBody ReferralCommissionRuleSaveReq req) {
|
||||
service.updateRule(ruleId, req);
|
||||
return ApiResponse.ok();
|
||||
}
|
||||
|
||||
@DeleteMapping("/rules/{ruleId}")
|
||||
@RequirePermission(menu = "REFERRAL_COMM", perm = "DELETE")
|
||||
@DataChangeLog(menu = "REFERRAL_COMM", table = "referral_commission_rule")
|
||||
public ApiResponse<Void> deleteRule(@PathVariable Long ruleId) {
|
||||
service.deleteRule(ruleId);
|
||||
return ApiResponse.ok();
|
||||
}
|
||||
|
||||
// ── Ledger ────────────────────────────────────────────────────────────────
|
||||
|
||||
@GetMapping("/ledgers")
|
||||
@RequirePermission(menu = "REFERRAL_COMM", perm = "READ")
|
||||
public ApiResponse<PageResponse<ReferralCommissionLedgerResp>> listLedgers(
|
||||
@ModelAttribute ReferralCommissionLedgerSearchParam param) {
|
||||
return ApiResponse.ok(service.listLedgers(param));
|
||||
}
|
||||
|
||||
@GetMapping("/ledgers/{ledgerId}")
|
||||
@RequirePermission(menu = "REFERRAL_COMM", perm = "READ")
|
||||
public ApiResponse<ReferralCommissionLedgerResp> detailLedger(@PathVariable Long ledgerId) {
|
||||
return ApiResponse.ok(service.detailLedger(ledgerId));
|
||||
}
|
||||
|
||||
@PostMapping("/ledgers")
|
||||
@RequirePermission(menu = "REFERRAL_COMM", perm = "CREATE")
|
||||
@DataChangeLog(menu = "REFERRAL_COMM", table = "referral_commission_ledger")
|
||||
public ApiResponse<Long> createLedger(@Valid @RequestBody ReferralCommissionLedgerSaveReq req) {
|
||||
return ApiResponse.ok(service.createLedger(req));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.ga.api.controller.commission;
|
||||
|
||||
import com.ga.api.service.commission.ReserveFundService;
|
||||
import com.ga.common.annotation.DataChangeLog;
|
||||
import com.ga.common.annotation.RequirePermission;
|
||||
import com.ga.common.model.ApiResponse;
|
||||
import com.ga.common.model.PageResponse;
|
||||
import com.ga.core.vo.commission.AgentReserveFundResp;
|
||||
import com.ga.core.vo.commission.AgentReserveFundSaveReq;
|
||||
import com.ga.core.vo.commission.AgentReserveFundSearchParam;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@Tag(name = "적립금")
|
||||
@RestController
|
||||
@RequestMapping("/api/reserve-funds")
|
||||
@RequiredArgsConstructor
|
||||
public class ReserveFundController {
|
||||
|
||||
private final ReserveFundService service;
|
||||
|
||||
@GetMapping
|
||||
@RequirePermission(menu = "RESERVE_FUND", perm = "READ")
|
||||
public ApiResponse<PageResponse<AgentReserveFundResp>> list(@ModelAttribute AgentReserveFundSearchParam param) {
|
||||
return ApiResponse.ok(service.list(param));
|
||||
}
|
||||
|
||||
@GetMapping("/{reserveId}")
|
||||
@RequirePermission(menu = "RESERVE_FUND", perm = "READ")
|
||||
public ApiResponse<AgentReserveFundResp> detail(@PathVariable Long reserveId) {
|
||||
return ApiResponse.ok(service.detail(reserveId));
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@RequirePermission(menu = "RESERVE_FUND", perm = "CREATE")
|
||||
@DataChangeLog(menu = "RESERVE_FUND", table = "agent_reserve_fund")
|
||||
public ApiResponse<Long> create(@Valid @RequestBody AgentReserveFundSaveReq req) {
|
||||
return ApiResponse.ok(service.create(req));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package com.ga.api.controller.commission;
|
||||
|
||||
import com.ga.api.service.commission.SettlingSupportService;
|
||||
import com.ga.common.annotation.DataChangeLog;
|
||||
import com.ga.common.annotation.RequirePermission;
|
||||
import com.ga.common.model.ApiResponse;
|
||||
import com.ga.common.model.PageResponse;
|
||||
import com.ga.core.vo.commission.*;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@Tag(name = "정착지원금")
|
||||
@RestController
|
||||
@RequestMapping("/api/settling-supports")
|
||||
@RequiredArgsConstructor
|
||||
public class SettlingSupportController {
|
||||
|
||||
private final SettlingSupportService service;
|
||||
|
||||
// ── Rule ──────────────────────────────────────────────────────────────────
|
||||
|
||||
@GetMapping("/rules")
|
||||
@RequirePermission(menu = "SETTLING_SUPPORT", perm = "READ")
|
||||
public ApiResponse<PageResponse<SettlingSupportRuleResp>> listRules(
|
||||
@ModelAttribute SettlingSupportRuleSearchParam param) {
|
||||
return ApiResponse.ok(service.listRules(param));
|
||||
}
|
||||
|
||||
@GetMapping("/rules/{ruleId}")
|
||||
@RequirePermission(menu = "SETTLING_SUPPORT", perm = "READ")
|
||||
public ApiResponse<SettlingSupportRuleResp> detailRule(@PathVariable Long ruleId) {
|
||||
return ApiResponse.ok(service.detailRule(ruleId));
|
||||
}
|
||||
|
||||
@PostMapping("/rules")
|
||||
@RequirePermission(menu = "SETTLING_SUPPORT", perm = "CREATE")
|
||||
@DataChangeLog(menu = "SETTLING_SUPPORT", table = "settling_support_rule")
|
||||
public ApiResponse<Long> createRule(@Valid @RequestBody SettlingSupportRuleSaveReq req) {
|
||||
return ApiResponse.ok(service.createRule(req));
|
||||
}
|
||||
|
||||
@PutMapping("/rules/{ruleId}")
|
||||
@RequirePermission(menu = "SETTLING_SUPPORT", perm = "UPDATE")
|
||||
@DataChangeLog(menu = "SETTLING_SUPPORT", table = "settling_support_rule")
|
||||
public ApiResponse<Void> updateRule(@PathVariable Long ruleId,
|
||||
@Valid @RequestBody SettlingSupportRuleSaveReq req) {
|
||||
service.updateRule(ruleId, req);
|
||||
return ApiResponse.ok();
|
||||
}
|
||||
|
||||
@DeleteMapping("/rules/{ruleId}")
|
||||
@RequirePermission(menu = "SETTLING_SUPPORT", perm = "DELETE")
|
||||
@DataChangeLog(menu = "SETTLING_SUPPORT", table = "settling_support_rule")
|
||||
public ApiResponse<Void> deleteRule(@PathVariable Long ruleId) {
|
||||
service.deleteRule(ruleId);
|
||||
return ApiResponse.ok();
|
||||
}
|
||||
|
||||
// ── Ledger ────────────────────────────────────────────────────────────────
|
||||
|
||||
@GetMapping("/ledgers")
|
||||
@RequirePermission(menu = "SETTLING_SUPPORT", perm = "READ")
|
||||
public ApiResponse<PageResponse<SettlingSupportLedgerResp>> listLedgers(
|
||||
@ModelAttribute SettlingSupportLedgerSearchParam param) {
|
||||
return ApiResponse.ok(service.listLedgers(param));
|
||||
}
|
||||
|
||||
@GetMapping("/ledgers/{ledgerId}")
|
||||
@RequirePermission(menu = "SETTLING_SUPPORT", perm = "READ")
|
||||
public ApiResponse<SettlingSupportLedgerResp> detailLedger(@PathVariable Long ledgerId) {
|
||||
return ApiResponse.ok(service.detailLedger(ledgerId));
|
||||
}
|
||||
|
||||
@PostMapping("/ledgers")
|
||||
@RequirePermission(menu = "SETTLING_SUPPORT", perm = "CREATE")
|
||||
@DataChangeLog(menu = "SETTLING_SUPPORT", table = "settling_support_ledger")
|
||||
public ApiResponse<Long> createLedger(@Valid @RequestBody SettlingSupportLedgerSaveReq req) {
|
||||
return ApiResponse.ok(service.createLedger(req));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.ga.api.controller.commission;
|
||||
|
||||
import com.ga.api.service.commission.StepCloseService;
|
||||
import com.ga.common.annotation.DataChangeLog;
|
||||
import com.ga.common.annotation.RequirePermission;
|
||||
import com.ga.common.model.ApiResponse;
|
||||
import com.ga.common.model.PageResponse;
|
||||
import com.ga.core.vo.commission.StepCloseResp;
|
||||
import com.ga.core.vo.commission.StepCloseSaveReq;
|
||||
import com.ga.core.vo.commission.StepCloseSearchParam;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Tag(name = "단계마감")
|
||||
@RestController
|
||||
@RequestMapping("/api/step-closes")
|
||||
@RequiredArgsConstructor
|
||||
public class StepCloseController {
|
||||
|
||||
private final StepCloseService service;
|
||||
|
||||
@GetMapping
|
||||
@RequirePermission(menu = "STEP_CLOSE", perm = "READ")
|
||||
public ApiResponse<PageResponse<StepCloseResp>> list(@ModelAttribute StepCloseSearchParam param) {
|
||||
return ApiResponse.ok(service.list(param));
|
||||
}
|
||||
|
||||
@GetMapping("/by-month")
|
||||
@RequirePermission(menu = "STEP_CLOSE", perm = "READ")
|
||||
public ApiResponse<List<StepCloseResp>> listByMonth(@RequestParam String settleMonth) {
|
||||
return ApiResponse.ok(service.listByMonth(settleMonth));
|
||||
}
|
||||
|
||||
@GetMapping("/{stepCloseId}")
|
||||
@RequirePermission(menu = "STEP_CLOSE", perm = "READ")
|
||||
public ApiResponse<StepCloseResp> detail(@PathVariable Long stepCloseId) {
|
||||
return ApiResponse.ok(service.detail(stepCloseId));
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@RequirePermission(menu = "STEP_CLOSE", perm = "UPDATE")
|
||||
@DataChangeLog(menu = "STEP_CLOSE", table = "settlement_step_close")
|
||||
public ApiResponse<Long> create(@Valid @RequestBody StepCloseSaveReq req) {
|
||||
return ApiResponse.ok(service.create(req));
|
||||
}
|
||||
|
||||
@PostMapping("/{stepCloseId}/close")
|
||||
@RequirePermission(menu = "STEP_CLOSE", perm = "APPROVE")
|
||||
@DataChangeLog(menu = "STEP_CLOSE", table = "settlement_step_close")
|
||||
public ApiResponse<Void> close(@PathVariable Long stepCloseId) {
|
||||
service.close(stepCloseId);
|
||||
return ApiResponse.ok();
|
||||
}
|
||||
|
||||
@PostMapping("/{stepCloseId}/reopen")
|
||||
@RequirePermission(menu = "STEP_CLOSE", perm = "APPROVE")
|
||||
@DataChangeLog(menu = "STEP_CLOSE", table = "settlement_step_close")
|
||||
public ApiResponse<Void> reopen(@PathVariable Long stepCloseId) {
|
||||
service.reopen(stepCloseId);
|
||||
return ApiResponse.ok();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package com.ga.api.service.commission;
|
||||
|
||||
import com.ga.common.exception.BizException;
|
||||
import com.ga.common.exception.ErrorCode;
|
||||
import com.ga.common.model.PageResponse;
|
||||
import com.ga.core.mapper.commission.AwardLedgerMapper;
|
||||
import com.ga.core.mapper.commission.AwardRuleMapper;
|
||||
import com.ga.core.vo.commission.*;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDate;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional(readOnly = true)
|
||||
public class AwardService {
|
||||
|
||||
private final AwardRuleMapper ruleMapper;
|
||||
private final AwardLedgerMapper ledgerMapper;
|
||||
|
||||
// ── Rule ──────────────────────────────────────────────────────────────────
|
||||
|
||||
public PageResponse<AwardRuleResp> listRules(AwardRuleSearchParam param) {
|
||||
param.startPage();
|
||||
return PageResponse.of(ruleMapper.selectList(param));
|
||||
}
|
||||
|
||||
public AwardRuleResp detailRule(Long ruleId) {
|
||||
AwardRuleResp resp = ruleMapper.selectById(ruleId);
|
||||
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
|
||||
return resp;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Long createRule(AwardRuleSaveReq req) {
|
||||
AwardRuleVO vo = new AwardRuleVO();
|
||||
vo.setAwardType(req.getAwardType());
|
||||
vo.setCalcType(req.getCalcType());
|
||||
vo.setThresholdAmount(req.getThresholdAmount());
|
||||
vo.setAwardRate(req.getAwardRate());
|
||||
vo.setAwardFixed(req.getAwardFixed());
|
||||
vo.setEffectiveFrom(req.getEffectiveFrom());
|
||||
vo.setEffectiveTo(req.getEffectiveTo());
|
||||
ruleMapper.insert(vo);
|
||||
return vo.getRuleId();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void updateRule(Long ruleId, AwardRuleSaveReq req) {
|
||||
AwardRuleResp resp = ruleMapper.selectById(ruleId);
|
||||
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
|
||||
AwardRuleVO vo = new AwardRuleVO();
|
||||
vo.setRuleId(ruleId);
|
||||
vo.setAwardType(req.getAwardType());
|
||||
vo.setCalcType(req.getCalcType());
|
||||
vo.setThresholdAmount(req.getThresholdAmount());
|
||||
vo.setAwardRate(req.getAwardRate());
|
||||
vo.setAwardFixed(req.getAwardFixed());
|
||||
vo.setEffectiveFrom(req.getEffectiveFrom());
|
||||
vo.setEffectiveTo(req.getEffectiveTo());
|
||||
ruleMapper.update(vo);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void deleteRule(Long ruleId) {
|
||||
AwardRuleResp resp = ruleMapper.selectById(ruleId);
|
||||
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
|
||||
ruleMapper.delete(ruleId);
|
||||
}
|
||||
|
||||
// ── Ledger ────────────────────────────────────────────────────────────────
|
||||
|
||||
public PageResponse<AwardLedgerResp> listLedgers(AwardLedgerSearchParam param) {
|
||||
param.startPage();
|
||||
return PageResponse.of(ledgerMapper.selectList(param));
|
||||
}
|
||||
|
||||
public AwardLedgerResp detailLedger(Long ledgerId) {
|
||||
AwardLedgerResp resp = ledgerMapper.selectById(ledgerId);
|
||||
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
|
||||
return resp;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Long createLedger(AwardLedgerSaveReq req) {
|
||||
// 룰 조회로 calc_type 결정
|
||||
AwardRuleVO rule = ruleMapper.selectActiveRule(req.getAwardType(), LocalDate.now());
|
||||
if (rule == null) throw new BizException(ErrorCode.NOT_FOUND);
|
||||
|
||||
BigDecimal awardAmount;
|
||||
if ("FIXED".equals(rule.getCalcType())) {
|
||||
awardAmount = rule.getAwardFixed().setScale(2, RoundingMode.HALF_UP);
|
||||
} else {
|
||||
// RATE: base × award_rate
|
||||
awardAmount = req.getBaseAmount()
|
||||
.multiply(rule.getAwardRate())
|
||||
.setScale(2, RoundingMode.HALF_UP);
|
||||
}
|
||||
|
||||
AwardLedgerVO vo = new AwardLedgerVO();
|
||||
vo.setAgentId(req.getAgentId());
|
||||
vo.setSettleMonth(req.getSettleMonth());
|
||||
vo.setAwardType(req.getAwardType());
|
||||
vo.setBaseAmount(req.getBaseAmount());
|
||||
vo.setAwardAmount(awardAmount);
|
||||
vo.setStatus("CALCULATED");
|
||||
ledgerMapper.insert(vo);
|
||||
return vo.getLedgerId();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package com.ga.api.service.commission;
|
||||
|
||||
import com.ga.common.exception.BizException;
|
||||
import com.ga.common.exception.ErrorCode;
|
||||
import com.ga.common.model.PageResponse;
|
||||
import com.ga.core.mapper.commission.BranchMgrAllowanceLedgerMapper;
|
||||
import com.ga.core.mapper.commission.BranchMgrAllowanceRuleMapper;
|
||||
import com.ga.core.vo.commission.*;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional(readOnly = true)
|
||||
public class BranchAllowanceService {
|
||||
|
||||
private final BranchMgrAllowanceRuleMapper ruleMapper;
|
||||
private final BranchMgrAllowanceLedgerMapper ledgerMapper;
|
||||
|
||||
// ── Rule ──────────────────────────────────────────────────────────────────
|
||||
|
||||
public PageResponse<BranchMgrAllowanceRuleResp> listRules(BranchMgrAllowanceRuleSearchParam param) {
|
||||
param.startPage();
|
||||
return PageResponse.of(ruleMapper.selectList(param));
|
||||
}
|
||||
|
||||
public BranchMgrAllowanceRuleResp detailRule(Long ruleId) {
|
||||
BranchMgrAllowanceRuleResp resp = ruleMapper.selectById(ruleId);
|
||||
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
|
||||
return resp;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Long createRule(BranchMgrAllowanceRuleSaveReq req) {
|
||||
BranchMgrAllowanceRuleVO vo = new BranchMgrAllowanceRuleVO();
|
||||
vo.setPositionCode(req.getPositionCode());
|
||||
vo.setAllowanceRate(req.getAllowanceRate());
|
||||
vo.setCapAmount(req.getCapAmount());
|
||||
vo.setEffectiveFrom(req.getEffectiveFrom());
|
||||
vo.setEffectiveTo(req.getEffectiveTo());
|
||||
ruleMapper.insert(vo);
|
||||
return vo.getRuleId();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void updateRule(Long ruleId, BranchMgrAllowanceRuleSaveReq req) {
|
||||
BranchMgrAllowanceRuleResp resp = ruleMapper.selectById(ruleId);
|
||||
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
|
||||
BranchMgrAllowanceRuleVO vo = new BranchMgrAllowanceRuleVO();
|
||||
vo.setRuleId(ruleId);
|
||||
vo.setPositionCode(req.getPositionCode());
|
||||
vo.setAllowanceRate(req.getAllowanceRate());
|
||||
vo.setCapAmount(req.getCapAmount());
|
||||
vo.setEffectiveFrom(req.getEffectiveFrom());
|
||||
vo.setEffectiveTo(req.getEffectiveTo());
|
||||
ruleMapper.update(vo);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void deleteRule(Long ruleId) {
|
||||
BranchMgrAllowanceRuleResp resp = ruleMapper.selectById(ruleId);
|
||||
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
|
||||
ruleMapper.delete(ruleId);
|
||||
}
|
||||
|
||||
// ── Ledger ────────────────────────────────────────────────────────────────
|
||||
|
||||
public PageResponse<BranchMgrAllowanceLedgerResp> listLedgers(BranchMgrAllowanceLedgerSearchParam param) {
|
||||
param.startPage();
|
||||
return PageResponse.of(ledgerMapper.selectList(param));
|
||||
}
|
||||
|
||||
public BranchMgrAllowanceLedgerResp detailLedger(Long ledgerId) {
|
||||
BranchMgrAllowanceLedgerResp resp = ledgerMapper.selectById(ledgerId);
|
||||
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
|
||||
return resp;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Long createLedger(BranchMgrAllowanceLedgerSaveReq req) {
|
||||
// 수당액 = min(base × rate, cap) — cap NULL이면 상한 없음
|
||||
BigDecimal raw = req.getBaseAmount()
|
||||
.multiply(req.getAllowanceRate())
|
||||
.setScale(2, RoundingMode.HALF_UP);
|
||||
BigDecimal clawback = req.getClawbackAmount() != null ? req.getClawbackAmount() : BigDecimal.ZERO;
|
||||
|
||||
// cap_amount는 SaveReq에 없으므로 allowanceRate와 동일 룰에서 cap 조회
|
||||
// positionCode가 req에 없어 단순 구현: raw 그대로 사용 (배치에서 룰 참조 시 위임)
|
||||
BigDecimal allowanceAmount = raw;
|
||||
|
||||
BranchMgrAllowanceLedgerVO vo = new BranchMgrAllowanceLedgerVO();
|
||||
vo.setManagerAgentId(req.getManagerAgentId());
|
||||
vo.setOrgId(req.getOrgId());
|
||||
vo.setSettleMonth(req.getSettleMonth());
|
||||
vo.setBaseAmount(req.getBaseAmount());
|
||||
vo.setAllowanceRate(req.getAllowanceRate());
|
||||
vo.setAllowanceAmount(allowanceAmount);
|
||||
vo.setClawbackAmount(clawback);
|
||||
vo.setStatus("CALCULATED");
|
||||
ledgerMapper.insert(vo);
|
||||
return vo.getLedgerId();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.ga.api.service.commission;
|
||||
|
||||
import com.ga.common.exception.BizException;
|
||||
import com.ga.common.exception.ErrorCode;
|
||||
import com.ga.common.model.PageResponse;
|
||||
import com.ga.common.util.SecurityUtil;
|
||||
import com.ga.core.mapper.commission.CommissionSimulationMapper;
|
||||
import com.ga.core.vo.commission.CommissionSimulationResp;
|
||||
import com.ga.core.vo.commission.CommissionSimulationSaveReq;
|
||||
import com.ga.core.vo.commission.CommissionSimulationSearchParam;
|
||||
import com.ga.core.vo.commission.CommissionSimulationVO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional(readOnly = true)
|
||||
public class CommissionSimulationService {
|
||||
|
||||
private static final BigDecimal WITHHOLDING_TAX_RATE = new BigDecimal("0.033");
|
||||
private static final BigDecimal DEFAULT_CHARGEBACK_RATE = new BigDecimal("0.5");
|
||||
|
||||
private final CommissionSimulationMapper simulationMapper;
|
||||
|
||||
public PageResponse<CommissionSimulationResp> list(CommissionSimulationSearchParam param) {
|
||||
param.startPage();
|
||||
return PageResponse.of(simulationMapper.selectList(param));
|
||||
}
|
||||
|
||||
public CommissionSimulationResp detail(Long simId) {
|
||||
CommissionSimulationResp resp = simulationMapper.selectById(simId);
|
||||
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
|
||||
return resp;
|
||||
}
|
||||
|
||||
/**
|
||||
* 수수료 시뮬레이션 계산 후 저장·반환.
|
||||
* company_payment = premium × company_rate
|
||||
* agent_commission = company_payment × payout_rate
|
||||
* withholding_tax = agent_commission × 3.3%
|
||||
* net_amount = agent_commission − withholding_tax
|
||||
* expected_chargeback: 입력 값이 있으면 사용, 없으면 agent_commission × 50% (13개월 가정)
|
||||
* 모든 금액 setScale(2, HALF_UP).
|
||||
*/
|
||||
@Transactional
|
||||
public CommissionSimulationResp simulate(CommissionSimulationSaveReq req) {
|
||||
BigDecimal companyPayment = req.getPremiumAmount()
|
||||
.multiply(req.getCompanyRate())
|
||||
.setScale(2, RoundingMode.HALF_UP);
|
||||
|
||||
BigDecimal agentCommission = companyPayment
|
||||
.multiply(req.getPayoutRate())
|
||||
.setScale(2, RoundingMode.HALF_UP);
|
||||
|
||||
BigDecimal withholdingTax = agentCommission
|
||||
.multiply(WITHHOLDING_TAX_RATE)
|
||||
.setScale(2, RoundingMode.HALF_UP);
|
||||
|
||||
BigDecimal netAmount = agentCommission
|
||||
.subtract(withholdingTax)
|
||||
.setScale(2, RoundingMode.HALF_UP);
|
||||
|
||||
BigDecimal expectedChargeback;
|
||||
if (req.getExpectedChargeback() != null) {
|
||||
expectedChargeback = req.getExpectedChargeback().setScale(2, RoundingMode.HALF_UP);
|
||||
} else {
|
||||
expectedChargeback = agentCommission
|
||||
.multiply(DEFAULT_CHARGEBACK_RATE)
|
||||
.setScale(2, RoundingMode.HALF_UP);
|
||||
}
|
||||
|
||||
CommissionSimulationVO vo = new CommissionSimulationVO();
|
||||
vo.setSimName(req.getSimName());
|
||||
vo.setPremiumAmount(req.getPremiumAmount());
|
||||
vo.setCompanyRate(req.getCompanyRate());
|
||||
vo.setPayoutRate(req.getPayoutRate());
|
||||
vo.setCommissionYear(req.getCommissionYear());
|
||||
vo.setCompanyPayment(companyPayment);
|
||||
vo.setAgentCommission(agentCommission);
|
||||
vo.setWithholdingTax(withholdingTax);
|
||||
vo.setNetAmount(netAmount);
|
||||
vo.setExpectedChargeback(expectedChargeback);
|
||||
vo.setCreatedBy(SecurityUtil.getCurrentUserId());
|
||||
simulationMapper.insert(vo);
|
||||
|
||||
CommissionSimulationResp resp = simulationMapper.selectById(vo.getSimId());
|
||||
if (resp == null) throw new BizException(ErrorCode.INTERNAL_ERROR);
|
||||
return resp;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
package com.ga.api.service.commission;
|
||||
|
||||
import com.ga.common.exception.BizException;
|
||||
import com.ga.common.exception.ErrorCode;
|
||||
import com.ga.common.model.PageResponse;
|
||||
import com.ga.core.mapper.commission.AgentGradeEvaluationMapper;
|
||||
import com.ga.core.mapper.commission.GradeEvalRuleMapper;
|
||||
import com.ga.core.vo.commission.AgentGradeEvaluationResp;
|
||||
import com.ga.core.vo.commission.AgentGradeEvaluationSaveReq;
|
||||
import com.ga.core.vo.commission.AgentGradeEvaluationSearchParam;
|
||||
import com.ga.core.vo.commission.AgentGradeEvaluationVO;
|
||||
import com.ga.core.vo.commission.GradeEvalRuleResp;
|
||||
import com.ga.core.vo.commission.GradeEvalRuleSaveReq;
|
||||
import com.ga.core.vo.commission.GradeEvalRuleSearchParam;
|
||||
import com.ga.core.vo.commission.GradeEvalRuleVO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional(readOnly = true)
|
||||
public class GradeEvaluationService {
|
||||
|
||||
private final GradeEvalRuleMapper ruleMapper;
|
||||
private final AgentGradeEvaluationMapper evalMapper;
|
||||
|
||||
// ── Rule ──────────────────────────────────────────────────────────────────
|
||||
|
||||
public PageResponse<GradeEvalRuleResp> listRules(GradeEvalRuleSearchParam param) {
|
||||
param.startPage();
|
||||
return PageResponse.of(ruleMapper.selectList(param));
|
||||
}
|
||||
|
||||
public GradeEvalRuleResp detailRule(Long ruleId) {
|
||||
GradeEvalRuleResp resp = ruleMapper.selectById(ruleId);
|
||||
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
|
||||
return resp;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Long createRule(GradeEvalRuleSaveReq req) {
|
||||
GradeEvalRuleVO vo = new GradeEvalRuleVO();
|
||||
vo.setGradeId(req.getGradeId());
|
||||
vo.setMinScore(req.getMinScore());
|
||||
vo.setEffectiveFrom(req.getEffectiveFrom());
|
||||
vo.setEffectiveTo(req.getEffectiveTo());
|
||||
ruleMapper.insert(vo);
|
||||
return vo.getRuleId();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void updateRule(Long ruleId, GradeEvalRuleSaveReq req) {
|
||||
GradeEvalRuleResp resp = ruleMapper.selectById(ruleId);
|
||||
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
|
||||
GradeEvalRuleVO vo = new GradeEvalRuleVO();
|
||||
vo.setRuleId(ruleId);
|
||||
vo.setGradeId(req.getGradeId());
|
||||
vo.setMinScore(req.getMinScore());
|
||||
vo.setEffectiveFrom(req.getEffectiveFrom());
|
||||
vo.setEffectiveTo(req.getEffectiveTo());
|
||||
ruleMapper.update(vo);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void deleteRule(Long ruleId) {
|
||||
GradeEvalRuleResp resp = ruleMapper.selectById(ruleId);
|
||||
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
|
||||
ruleMapper.delete(ruleId);
|
||||
}
|
||||
|
||||
// ── Evaluation ────────────────────────────────────────────────────────────
|
||||
|
||||
public PageResponse<AgentGradeEvaluationResp> listEvals(AgentGradeEvaluationSearchParam param) {
|
||||
param.startPage();
|
||||
return PageResponse.of(evalMapper.selectList(param));
|
||||
}
|
||||
|
||||
public AgentGradeEvaluationResp detailEval(Long evalId) {
|
||||
AgentGradeEvaluationResp resp = evalMapper.selectById(evalId);
|
||||
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
|
||||
return resp;
|
||||
}
|
||||
|
||||
/**
|
||||
* 등급평가 생성.
|
||||
* selectActiveRuleByScore(score, 기준일)로 evaluated_grade_id 결정.
|
||||
* 매칭 룰 없으면 prevGradeId 유지 또는 null.
|
||||
*/
|
||||
@Transactional
|
||||
public Long createEval(AgentGradeEvaluationSaveReq req) {
|
||||
LocalDate baseDate = LocalDate.now();
|
||||
GradeEvalRuleVO matchedRule = ruleMapper.selectActiveRuleByScore(req.getPerformanceScore(), baseDate);
|
||||
|
||||
Long evaluatedGradeId = (matchedRule != null) ? matchedRule.getGradeId() : req.getPrevGradeId();
|
||||
|
||||
AgentGradeEvaluationVO vo = new AgentGradeEvaluationVO();
|
||||
vo.setAgentId(req.getAgentId());
|
||||
vo.setEvalPeriod(req.getEvalPeriod());
|
||||
vo.setPerformanceScore(req.getPerformanceScore());
|
||||
vo.setPrevGradeId(req.getPrevGradeId());
|
||||
vo.setEvaluatedGradeId(evaluatedGradeId);
|
||||
vo.setStatus("EVALUATED");
|
||||
evalMapper.insert(vo);
|
||||
return vo.getEvalId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 등급평가 반영 액션 (EVALUATED → APPLIED).
|
||||
*/
|
||||
@Transactional
|
||||
public void applyEval(Long evalId) {
|
||||
AgentGradeEvaluationResp resp = evalMapper.selectById(evalId);
|
||||
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
|
||||
if ("APPLIED".equals(resp.getStatus())) {
|
||||
throw new BizException(ErrorCode.INVALID_STATUS, "이미 반영된 평가입니다.");
|
||||
}
|
||||
evalMapper.updateStatus(evalId, "APPLIED");
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void deleteEval(Long evalId) {
|
||||
AgentGradeEvaluationResp resp = evalMapper.selectById(evalId);
|
||||
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
|
||||
evalMapper.delete(evalId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.ga.api.service.commission;
|
||||
|
||||
import com.ga.common.exception.BizException;
|
||||
import com.ga.common.exception.ErrorCode;
|
||||
import com.ga.common.model.PageResponse;
|
||||
import com.ga.core.mapper.commission.AgentGuaranteeInsuranceMapper;
|
||||
import com.ga.core.vo.commission.AgentGuaranteeInsuranceResp;
|
||||
import com.ga.core.vo.commission.AgentGuaranteeInsuranceSaveReq;
|
||||
import com.ga.core.vo.commission.AgentGuaranteeInsuranceSearchParam;
|
||||
import com.ga.core.vo.commission.AgentGuaranteeInsuranceVO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional(readOnly = true)
|
||||
public class GuaranteeInsuranceService {
|
||||
|
||||
private final AgentGuaranteeInsuranceMapper guaranteeInsuranceMapper;
|
||||
|
||||
public PageResponse<AgentGuaranteeInsuranceResp> list(AgentGuaranteeInsuranceSearchParam param) {
|
||||
param.startPage();
|
||||
return PageResponse.of(guaranteeInsuranceMapper.selectList(param));
|
||||
}
|
||||
|
||||
public AgentGuaranteeInsuranceResp detail(Long guaranteeId) {
|
||||
AgentGuaranteeInsuranceResp resp = guaranteeInsuranceMapper.selectById(guaranteeId);
|
||||
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
|
||||
return resp;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Long create(AgentGuaranteeInsuranceSaveReq req) {
|
||||
AgentGuaranteeInsuranceVO vo = new AgentGuaranteeInsuranceVO();
|
||||
vo.setAgentId(req.getAgentId());
|
||||
vo.setPolicyNo(req.getPolicyNo());
|
||||
vo.setCarrierId(req.getCarrierId());
|
||||
vo.setGuaranteeAmount(req.getGuaranteeAmount());
|
||||
vo.setStartDate(req.getStartDate());
|
||||
vo.setEndDate(req.getEndDate());
|
||||
vo.setStatus("ACTIVE");
|
||||
guaranteeInsuranceMapper.insert(vo);
|
||||
return vo.getGuaranteeId();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void update(Long guaranteeId, AgentGuaranteeInsuranceSaveReq req) {
|
||||
AgentGuaranteeInsuranceResp resp = guaranteeInsuranceMapper.selectById(guaranteeId);
|
||||
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
|
||||
AgentGuaranteeInsuranceVO vo = new AgentGuaranteeInsuranceVO();
|
||||
vo.setGuaranteeId(guaranteeId);
|
||||
vo.setAgentId(req.getAgentId());
|
||||
vo.setPolicyNo(req.getPolicyNo());
|
||||
vo.setCarrierId(req.getCarrierId());
|
||||
vo.setGuaranteeAmount(req.getGuaranteeAmount());
|
||||
vo.setStartDate(req.getStartDate());
|
||||
vo.setEndDate(req.getEndDate());
|
||||
guaranteeInsuranceMapper.update(vo);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void delete(Long guaranteeId) {
|
||||
AgentGuaranteeInsuranceResp resp = guaranteeInsuranceMapper.selectById(guaranteeId);
|
||||
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
|
||||
guaranteeInsuranceMapper.delete(guaranteeId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.ga.api.service.commission;
|
||||
|
||||
import com.ga.common.exception.BizException;
|
||||
import com.ga.common.exception.ErrorCode;
|
||||
import com.ga.common.model.PageResponse;
|
||||
import com.ga.core.mapper.commission.HqSettleItemMapper;
|
||||
import com.ga.core.vo.commission.HqSettleItemResp;
|
||||
import com.ga.core.vo.commission.HqSettleItemSaveReq;
|
||||
import com.ga.core.vo.commission.HqSettleItemSearchParam;
|
||||
import com.ga.core.vo.commission.HqSettleItemVO;
|
||||
import lombok.Data;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional(readOnly = true)
|
||||
public class HqSettleService {
|
||||
|
||||
private final HqSettleItemMapper itemMapper;
|
||||
|
||||
@Data
|
||||
public static class HqSummaryResp {
|
||||
private final String settleMonth;
|
||||
private final BigDecimal income;
|
||||
private final BigDecimal expense;
|
||||
private final BigDecimal profit;
|
||||
}
|
||||
|
||||
public PageResponse<HqSettleItemResp> list(HqSettleItemSearchParam param) {
|
||||
param.startPage();
|
||||
return PageResponse.of(itemMapper.selectList(param));
|
||||
}
|
||||
|
||||
public HqSettleItemResp detail(Long itemId) {
|
||||
HqSettleItemResp resp = itemMapper.selectById(itemId);
|
||||
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
|
||||
return resp;
|
||||
}
|
||||
|
||||
/**
|
||||
* 본사손익 집계: income − expense.
|
||||
* 둘 다 null이면 0으로 처리.
|
||||
*/
|
||||
public HqSummaryResp summary(String settleMonth) {
|
||||
BigDecimal income = itemMapper.sumByMonthAndType(settleMonth, "INCOME");
|
||||
BigDecimal expense = itemMapper.sumByMonthAndType(settleMonth, "EXPENSE");
|
||||
if (income == null) income = BigDecimal.ZERO;
|
||||
if (expense == null) expense = BigDecimal.ZERO;
|
||||
BigDecimal profit = income.subtract(expense);
|
||||
return new HqSummaryResp(settleMonth, income, expense, profit);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Long create(HqSettleItemSaveReq req) {
|
||||
HqSettleItemVO vo = new HqSettleItemVO();
|
||||
vo.setSettleMonth(req.getSettleMonth());
|
||||
vo.setItemType(req.getItemType());
|
||||
vo.setItemName(req.getItemName());
|
||||
vo.setAmount(req.getAmount());
|
||||
vo.setMemo(req.getMemo());
|
||||
vo.setStatus("REGISTERED");
|
||||
itemMapper.insert(vo);
|
||||
return vo.getItemId();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void update(Long itemId, HqSettleItemSaveReq req) {
|
||||
HqSettleItemResp resp = itemMapper.selectById(itemId);
|
||||
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
|
||||
HqSettleItemVO vo = new HqSettleItemVO();
|
||||
vo.setItemId(itemId);
|
||||
vo.setSettleMonth(req.getSettleMonth());
|
||||
vo.setItemType(req.getItemType());
|
||||
vo.setItemName(req.getItemName());
|
||||
vo.setAmount(req.getAmount());
|
||||
vo.setMemo(req.getMemo());
|
||||
itemMapper.update(vo);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void delete(Long itemId) {
|
||||
HqSettleItemResp resp = itemMapper.selectById(itemId);
|
||||
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
|
||||
itemMapper.delete(itemId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package com.ga.api.service.commission;
|
||||
|
||||
import com.ga.common.exception.BizException;
|
||||
import com.ga.common.exception.ErrorCode;
|
||||
import com.ga.common.model.PageResponse;
|
||||
import com.ga.core.mapper.commission.IncomeCommissionLedgerMapper;
|
||||
import com.ga.core.mapper.commission.MarginSummaryMapper;
|
||||
import com.ga.core.vo.commission.*;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional(readOnly = true)
|
||||
public class IncomeCommissionService {
|
||||
|
||||
private final IncomeCommissionLedgerMapper ledgerMapper;
|
||||
private final MarginSummaryMapper marginMapper;
|
||||
|
||||
// ── Income Ledger ────────────────────────────────────────────────────────
|
||||
|
||||
public PageResponse<IncomeCommissionLedgerResp> listLedgers(IncomeCommissionLedgerSearchParam param) {
|
||||
param.startPage();
|
||||
return PageResponse.of(ledgerMapper.selectList(param));
|
||||
}
|
||||
|
||||
public IncomeCommissionLedgerResp detailLedger(Long incomeId) {
|
||||
IncomeCommissionLedgerResp resp = ledgerMapper.selectById(incomeId);
|
||||
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
|
||||
return resp;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Long createLedger(IncomeCommissionLedgerSaveReq req) {
|
||||
IncomeCommissionLedgerVO vo = new IncomeCommissionLedgerVO();
|
||||
vo.setContractId(req.getContractId());
|
||||
vo.setCarrierId(req.getCarrierId());
|
||||
vo.setSettleMonth(req.getSettleMonth());
|
||||
vo.setPremiumAmount(req.getPremiumAmount());
|
||||
vo.setCompanyRate(req.getCompanyRate());
|
||||
// 수입수수료 = 보험료 × 보험사요율
|
||||
vo.setIncomeAmount(req.getPremiumAmount()
|
||||
.multiply(req.getCompanyRate())
|
||||
.setScale(2, RoundingMode.HALF_UP));
|
||||
vo.setCommissionType(req.getCommissionType());
|
||||
vo.setStatus("CALCULATED");
|
||||
ledgerMapper.insert(vo);
|
||||
return vo.getIncomeId();
|
||||
}
|
||||
|
||||
// ── Margin Summary ───────────────────────────────────────────────────────
|
||||
|
||||
public PageResponse<MarginSummaryResp> listMargins(MarginSummarySearchParam param) {
|
||||
param.startPage();
|
||||
return PageResponse.of(marginMapper.selectList(param));
|
||||
}
|
||||
|
||||
public MarginSummaryResp detailMargin(Long marginId) {
|
||||
MarginSummaryResp resp = marginMapper.selectById(marginId);
|
||||
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
|
||||
return resp;
|
||||
}
|
||||
|
||||
/**
|
||||
* 수지차 재계산 (recalc).
|
||||
* settle_month 단위로 income_total(수입수수료 합) − payout_total(settle_master net_amount 합) 산출.
|
||||
* 수입수수료 원장에는 설계사 귀속이 없어(보험사·계약 단위) income 은 월 전체 합을 쓰고,
|
||||
* agentId 가 주어지면 payout 만 해당 설계사로 좁혀 집계한다. (미지정 시 GA 월 전체 수지차)
|
||||
* 수지차 = income_total − payout_total, 수지차율 = 수지차 / income_total (income_total > 0).
|
||||
*/
|
||||
@Transactional
|
||||
public Long recalcMargin(String settleMonth, Long agentId) {
|
||||
BigDecimal incomeTotal = marginMapper.sumIncomeByMonth(settleMonth);
|
||||
if (incomeTotal == null) incomeTotal = BigDecimal.ZERO;
|
||||
|
||||
BigDecimal payoutTotal = marginMapper.sumPayoutByMonth(settleMonth, agentId);
|
||||
if (payoutTotal == null) payoutTotal = BigDecimal.ZERO;
|
||||
|
||||
BigDecimal marginAmount = incomeTotal.subtract(payoutTotal).setScale(2, RoundingMode.HALF_UP);
|
||||
BigDecimal marginRate = BigDecimal.ZERO;
|
||||
if (incomeTotal.compareTo(BigDecimal.ZERO) > 0) {
|
||||
marginRate = marginAmount.divide(incomeTotal, 4, RoundingMode.HALF_UP);
|
||||
}
|
||||
|
||||
MarginSummaryVO existing = marginMapper.selectByMonthAndAgent(settleMonth, agentId);
|
||||
if (existing != null) {
|
||||
existing.setIncomeTotal(incomeTotal);
|
||||
existing.setPayoutTotal(payoutTotal);
|
||||
existing.setMarginAmount(marginAmount);
|
||||
existing.setMarginRate(marginRate);
|
||||
marginMapper.update(existing);
|
||||
return existing.getMarginId();
|
||||
} else {
|
||||
MarginSummaryVO vo = new MarginSummaryVO();
|
||||
vo.setSettleMonth(settleMonth);
|
||||
vo.setAgentId(agentId);
|
||||
vo.setIncomeTotal(incomeTotal);
|
||||
vo.setPayoutTotal(payoutTotal);
|
||||
vo.setMarginAmount(marginAmount);
|
||||
vo.setMarginRate(marginRate);
|
||||
marginMapper.insert(vo);
|
||||
return vo.getMarginId();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package com.ga.api.service.commission;
|
||||
|
||||
import com.ga.common.exception.BizException;
|
||||
import com.ga.common.exception.ErrorCode;
|
||||
import com.ga.common.model.PageResponse;
|
||||
import com.ga.core.mapper.commission.JointContractMapper;
|
||||
import com.ga.core.vo.commission.JointContractResp;
|
||||
import com.ga.core.vo.commission.JointContractSaveReq;
|
||||
import com.ga.core.vo.commission.JointContractSearchParam;
|
||||
import com.ga.core.vo.commission.JointContractVO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional(readOnly = true)
|
||||
public class JointContractService {
|
||||
|
||||
private final JointContractMapper jointContractMapper;
|
||||
|
||||
public PageResponse<JointContractResp> list(JointContractSearchParam param) {
|
||||
param.startPage();
|
||||
return PageResponse.of(jointContractMapper.selectList(param));
|
||||
}
|
||||
|
||||
public JointContractResp detail(Long jointId) {
|
||||
JointContractResp resp = jointContractMapper.selectById(jointId);
|
||||
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
|
||||
return resp;
|
||||
}
|
||||
|
||||
/**
|
||||
* 공동계약 생성.
|
||||
* 기존 share_rate 합 + 신규 share_rate > 1.0 이면 E411 예외.
|
||||
*/
|
||||
@Transactional
|
||||
public Long create(JointContractSaveReq req) {
|
||||
validateShareRate(req.getContractId(), null, req.getShareRate());
|
||||
JointContractVO vo = new JointContractVO();
|
||||
vo.setContractId(req.getContractId());
|
||||
vo.setAgentId(req.getAgentId());
|
||||
vo.setRole(req.getRole());
|
||||
vo.setShareRate(req.getShareRate());
|
||||
jointContractMapper.insert(vo);
|
||||
return vo.getJointId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 공동계약 수정.
|
||||
* 기존 share_rate 합(자신 제외) + 수정 share_rate > 1.0 이면 E411 예외.
|
||||
*/
|
||||
@Transactional
|
||||
public void update(Long jointId, JointContractSaveReq req) {
|
||||
JointContractResp resp = jointContractMapper.selectById(jointId);
|
||||
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
|
||||
validateShareRate(req.getContractId(), jointId, req.getShareRate());
|
||||
JointContractVO vo = new JointContractVO();
|
||||
vo.setJointId(jointId);
|
||||
vo.setContractId(req.getContractId());
|
||||
vo.setAgentId(req.getAgentId());
|
||||
vo.setRole(req.getRole());
|
||||
vo.setShareRate(req.getShareRate());
|
||||
jointContractMapper.update(vo);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void delete(Long jointId) {
|
||||
JointContractResp resp = jointContractMapper.selectById(jointId);
|
||||
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
|
||||
jointContractMapper.delete(jointId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 계약별 share_rate 합 검증.
|
||||
* excludeJointId: 수정 시 자신 레코드를 합산에서 제외하기 위한 파라미터 (null=create).
|
||||
* sumShareByContract는 계약 전체 합을 반환하므로, 수정 시 기존 자신의 share_rate를 차감.
|
||||
*/
|
||||
private void validateShareRate(Long contractId, Long excludeJointId, BigDecimal newShareRate) {
|
||||
BigDecimal existingSum = jointContractMapper.sumShareByContract(contractId);
|
||||
if (existingSum == null) existingSum = BigDecimal.ZERO;
|
||||
|
||||
if (excludeJointId != null) {
|
||||
JointContractResp existing = jointContractMapper.selectById(excludeJointId);
|
||||
if (existing != null) {
|
||||
existingSum = existingSum.subtract(existing.getShareRate());
|
||||
}
|
||||
}
|
||||
|
||||
if (existingSum.add(newShareRate).compareTo(BigDecimal.ONE) > 0) {
|
||||
throw new BizException(ErrorCode.INVALID_PARAMETER, "계약별 지분율 합이 1을 초과합니다.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package com.ga.api.service.commission;
|
||||
|
||||
import com.ga.common.exception.BizException;
|
||||
import com.ga.common.exception.ErrorCode;
|
||||
import com.ga.common.model.PageResponse;
|
||||
import com.ga.core.mapper.commission.LifeSupportLedgerMapper;
|
||||
import com.ga.core.mapper.commission.LifeSupportRuleMapper;
|
||||
import com.ga.core.vo.commission.*;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional(readOnly = true)
|
||||
public class LifeSupportService {
|
||||
|
||||
private final LifeSupportRuleMapper ruleMapper;
|
||||
private final LifeSupportLedgerMapper ledgerMapper;
|
||||
|
||||
// ── Rule ──────────────────────────────────────────────────────────────────
|
||||
|
||||
public PageResponse<LifeSupportRuleResp> listRules(LifeSupportRuleSearchParam param) {
|
||||
param.startPage();
|
||||
return PageResponse.of(ruleMapper.selectList(param));
|
||||
}
|
||||
|
||||
public LifeSupportRuleResp detailRule(Long ruleId) {
|
||||
LifeSupportRuleResp resp = ruleMapper.selectById(ruleId);
|
||||
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
|
||||
return resp;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Long createRule(LifeSupportRuleSaveReq req) {
|
||||
LifeSupportRuleVO vo = new LifeSupportRuleVO();
|
||||
vo.setAgentGrade(req.getAgentGrade());
|
||||
vo.setSupportRate(req.getSupportRate());
|
||||
vo.setEffectiveFrom(req.getEffectiveFrom());
|
||||
vo.setEffectiveTo(req.getEffectiveTo());
|
||||
ruleMapper.insert(vo);
|
||||
return vo.getRuleId();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void updateRule(Long ruleId, LifeSupportRuleSaveReq req) {
|
||||
LifeSupportRuleResp resp = ruleMapper.selectById(ruleId);
|
||||
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
|
||||
LifeSupportRuleVO vo = new LifeSupportRuleVO();
|
||||
vo.setRuleId(ruleId);
|
||||
vo.setAgentGrade(req.getAgentGrade());
|
||||
vo.setSupportRate(req.getSupportRate());
|
||||
vo.setEffectiveFrom(req.getEffectiveFrom());
|
||||
vo.setEffectiveTo(req.getEffectiveTo());
|
||||
ruleMapper.update(vo);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void deleteRule(Long ruleId) {
|
||||
LifeSupportRuleResp resp = ruleMapper.selectById(ruleId);
|
||||
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
|
||||
ruleMapper.delete(ruleId);
|
||||
}
|
||||
|
||||
// ── Ledger ────────────────────────────────────────────────────────────────
|
||||
|
||||
public PageResponse<LifeSupportLedgerResp> listLedgers(LifeSupportLedgerSearchParam param) {
|
||||
param.startPage();
|
||||
return PageResponse.of(ledgerMapper.selectList(param));
|
||||
}
|
||||
|
||||
public LifeSupportLedgerResp detailLedger(Long ledgerId) {
|
||||
LifeSupportLedgerResp resp = ledgerMapper.selectById(ledgerId);
|
||||
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
|
||||
return resp;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Long createLedger(LifeSupportLedgerSaveReq req) {
|
||||
// 지원수수료 = base × support_rate
|
||||
BigDecimal supportAmount = req.getBaseAmount()
|
||||
.multiply(req.getSupportRate())
|
||||
.setScale(2, RoundingMode.HALF_UP);
|
||||
BigDecimal clawback = req.getClawbackAmount() != null ? req.getClawbackAmount() : BigDecimal.ZERO;
|
||||
// 실지급액 = support - clawback
|
||||
BigDecimal netAmount = supportAmount.subtract(clawback).setScale(2, RoundingMode.HALF_UP);
|
||||
|
||||
LifeSupportLedgerVO vo = new LifeSupportLedgerVO();
|
||||
vo.setAgentId(req.getAgentId());
|
||||
vo.setSettleMonth(req.getSettleMonth());
|
||||
vo.setBaseAmount(req.getBaseAmount());
|
||||
vo.setSupportRate(req.getSupportRate());
|
||||
vo.setSupportAmount(supportAmount);
|
||||
vo.setClawbackAmount(clawback);
|
||||
vo.setNetAmount(netAmount);
|
||||
vo.setStatus("CALCULATED");
|
||||
ledgerMapper.insert(vo);
|
||||
return vo.getLedgerId();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package com.ga.api.service.commission;
|
||||
|
||||
import com.ga.common.exception.BizException;
|
||||
import com.ga.common.exception.ErrorCode;
|
||||
import com.ga.common.model.PageResponse;
|
||||
import com.ga.core.mapper.commission.ProductConversionRateMapper;
|
||||
import com.ga.core.mapper.commission.RecognizedPerformanceLedgerMapper;
|
||||
import com.ga.core.vo.commission.*;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional(readOnly = true)
|
||||
public class RecognizedPerformanceService {
|
||||
|
||||
private final ProductConversionRateMapper conversionRateMapper;
|
||||
private final RecognizedPerformanceLedgerMapper ledgerMapper;
|
||||
|
||||
// ── Product Conversion Rate (rule) ────────────────────────────────────────
|
||||
|
||||
public PageResponse<ProductConversionRateResp> listRates(ProductConversionRateSearchParam param) {
|
||||
param.startPage();
|
||||
return PageResponse.of(conversionRateMapper.selectList(param));
|
||||
}
|
||||
|
||||
public ProductConversionRateResp detailRate(Long rateId) {
|
||||
ProductConversionRateResp resp = conversionRateMapper.selectById(rateId);
|
||||
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
|
||||
return resp;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Long createRate(ProductConversionRateSaveReq req) {
|
||||
ProductConversionRateVO vo = new ProductConversionRateVO();
|
||||
vo.setProductId(req.getProductId());
|
||||
vo.setConversionRate(req.getConversionRate());
|
||||
vo.setEffectiveFrom(req.getEffectiveFrom());
|
||||
vo.setEffectiveTo(req.getEffectiveTo());
|
||||
conversionRateMapper.insert(vo);
|
||||
return vo.getRateId();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void updateRate(Long rateId, ProductConversionRateSaveReq req) {
|
||||
ProductConversionRateResp resp = conversionRateMapper.selectById(rateId);
|
||||
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
|
||||
ProductConversionRateVO vo = new ProductConversionRateVO();
|
||||
vo.setRateId(rateId);
|
||||
vo.setProductId(req.getProductId());
|
||||
vo.setConversionRate(req.getConversionRate());
|
||||
vo.setEffectiveFrom(req.getEffectiveFrom());
|
||||
vo.setEffectiveTo(req.getEffectiveTo());
|
||||
conversionRateMapper.update(vo);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void deleteRate(Long rateId) {
|
||||
ProductConversionRateResp resp = conversionRateMapper.selectById(rateId);
|
||||
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
|
||||
conversionRateMapper.delete(rateId);
|
||||
}
|
||||
|
||||
// ── Ledger ────────────────────────────────────────────────────────────────
|
||||
|
||||
public PageResponse<RecognizedPerformanceLedgerResp> listLedgers(RecognizedPerformanceLedgerSearchParam param) {
|
||||
param.startPage();
|
||||
return PageResponse.of(ledgerMapper.selectList(param));
|
||||
}
|
||||
|
||||
public RecognizedPerformanceLedgerResp detailLedger(Long ledgerId) {
|
||||
RecognizedPerformanceLedgerResp resp = ledgerMapper.selectById(ledgerId);
|
||||
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
|
||||
return resp;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Long createLedger(RecognizedPerformanceLedgerSaveReq req) {
|
||||
// 환산보험료 = premium × conversion_rate
|
||||
BigDecimal convertedPremium = req.getPremiumAmount()
|
||||
.multiply(req.getConversionRate())
|
||||
.setScale(2, RoundingMode.HALF_UP);
|
||||
// 인정실적 = converted_premium × recognition_rate
|
||||
BigDecimal recognizedAmount = convertedPremium
|
||||
.multiply(req.getRecognitionRate())
|
||||
.setScale(2, RoundingMode.HALF_UP);
|
||||
|
||||
RecognizedPerformanceLedgerVO vo = new RecognizedPerformanceLedgerVO();
|
||||
vo.setAgentId(req.getAgentId());
|
||||
vo.setContractId(req.getContractId());
|
||||
vo.setSettleMonth(req.getSettleMonth());
|
||||
vo.setPremiumAmount(req.getPremiumAmount());
|
||||
vo.setConversionRate(req.getConversionRate());
|
||||
vo.setConvertedPremium(convertedPremium);
|
||||
vo.setRecognitionRate(req.getRecognitionRate());
|
||||
vo.setRecognizedAmount(recognizedAmount);
|
||||
vo.setStatus("CALCULATED");
|
||||
ledgerMapper.insert(vo);
|
||||
return vo.getLedgerId();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package com.ga.api.service.commission;
|
||||
|
||||
import com.ga.common.exception.BizException;
|
||||
import com.ga.common.exception.ErrorCode;
|
||||
import com.ga.common.model.PageResponse;
|
||||
import com.ga.core.mapper.commission.ReferralCommissionLedgerMapper;
|
||||
import com.ga.core.mapper.commission.ReferralCommissionRuleMapper;
|
||||
import com.ga.core.vo.commission.*;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional(readOnly = true)
|
||||
public class ReferralCommissionService {
|
||||
|
||||
private final ReferralCommissionRuleMapper ruleMapper;
|
||||
private final ReferralCommissionLedgerMapper ledgerMapper;
|
||||
|
||||
// ── Rule ──────────────────────────────────────────────────────────────────
|
||||
|
||||
public PageResponse<ReferralCommissionRuleResp> listRules(ReferralCommissionRuleSearchParam param) {
|
||||
param.startPage();
|
||||
return PageResponse.of(ruleMapper.selectList(param));
|
||||
}
|
||||
|
||||
public ReferralCommissionRuleResp detailRule(Long ruleId) {
|
||||
ReferralCommissionRuleResp resp = ruleMapper.selectById(ruleId);
|
||||
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
|
||||
return resp;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Long createRule(ReferralCommissionRuleSaveReq req) {
|
||||
ReferralCommissionRuleVO vo = new ReferralCommissionRuleVO();
|
||||
vo.setReferralType(req.getReferralType());
|
||||
vo.setCalcType(req.getCalcType());
|
||||
vo.setCommissionRate(req.getCommissionRate());
|
||||
vo.setFixedAmount(req.getFixedAmount());
|
||||
vo.setEffectiveFrom(req.getEffectiveFrom());
|
||||
vo.setEffectiveTo(req.getEffectiveTo());
|
||||
ruleMapper.insert(vo);
|
||||
return vo.getRuleId();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void updateRule(Long ruleId, ReferralCommissionRuleSaveReq req) {
|
||||
ReferralCommissionRuleResp resp = ruleMapper.selectById(ruleId);
|
||||
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
|
||||
ReferralCommissionRuleVO vo = new ReferralCommissionRuleVO();
|
||||
vo.setRuleId(ruleId);
|
||||
vo.setReferralType(req.getReferralType());
|
||||
vo.setCalcType(req.getCalcType());
|
||||
vo.setCommissionRate(req.getCommissionRate());
|
||||
vo.setFixedAmount(req.getFixedAmount());
|
||||
vo.setEffectiveFrom(req.getEffectiveFrom());
|
||||
vo.setEffectiveTo(req.getEffectiveTo());
|
||||
ruleMapper.update(vo);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void deleteRule(Long ruleId) {
|
||||
ReferralCommissionRuleResp resp = ruleMapper.selectById(ruleId);
|
||||
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
|
||||
ruleMapper.delete(ruleId);
|
||||
}
|
||||
|
||||
// ── Ledger ────────────────────────────────────────────────────────────────
|
||||
|
||||
public PageResponse<ReferralCommissionLedgerResp> listLedgers(ReferralCommissionLedgerSearchParam param) {
|
||||
param.startPage();
|
||||
return PageResponse.of(ledgerMapper.selectList(param));
|
||||
}
|
||||
|
||||
public ReferralCommissionLedgerResp detailLedger(Long ledgerId) {
|
||||
ReferralCommissionLedgerResp resp = ledgerMapper.selectById(ledgerId);
|
||||
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
|
||||
return resp;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Long createLedger(ReferralCommissionLedgerSaveReq req) {
|
||||
ReferralCommissionLedgerVO vo = new ReferralCommissionLedgerVO();
|
||||
vo.setContractId(req.getContractId());
|
||||
vo.setReferrerAgentId(req.getReferrerAgentId());
|
||||
vo.setSettleMonth(req.getSettleMonth());
|
||||
vo.setReferralType(req.getReferralType());
|
||||
vo.setBaseAmount(req.getBaseAmount());
|
||||
vo.setCommissionRate(req.getCommissionRate());
|
||||
|
||||
// 소개·이관수수료 = base × rate (RATE) 또는 fixed_amount (FIXED)
|
||||
BigDecimal commissionAmount;
|
||||
if (req.getCommissionRate() != null) {
|
||||
commissionAmount = req.getBaseAmount()
|
||||
.multiply(req.getCommissionRate())
|
||||
.setScale(2, RoundingMode.HALF_UP);
|
||||
} else {
|
||||
// FIXED: 룰에서 fixed_amount 조회
|
||||
ReferralCommissionRuleVO rule = ruleMapper.selectActiveRule(
|
||||
req.getReferralType(), java.time.LocalDate.now());
|
||||
if (rule == null || rule.getFixedAmount() == null) {
|
||||
throw new BizException(ErrorCode.NOT_FOUND);
|
||||
}
|
||||
commissionAmount = rule.getFixedAmount().setScale(2, RoundingMode.HALF_UP);
|
||||
}
|
||||
vo.setCommissionAmount(commissionAmount);
|
||||
vo.setStatus("CALCULATED");
|
||||
ledgerMapper.insert(vo);
|
||||
return vo.getLedgerId();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.ga.api.service.commission;
|
||||
|
||||
import com.ga.common.exception.BizException;
|
||||
import com.ga.common.exception.ErrorCode;
|
||||
import com.ga.common.model.PageResponse;
|
||||
import com.ga.core.mapper.commission.AgentReserveFundMapper;
|
||||
import com.ga.core.vo.commission.AgentReserveFundResp;
|
||||
import com.ga.core.vo.commission.AgentReserveFundSaveReq;
|
||||
import com.ga.core.vo.commission.AgentReserveFundSearchParam;
|
||||
import com.ga.core.vo.commission.AgentReserveFundVO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional(readOnly = true)
|
||||
public class ReserveFundService {
|
||||
|
||||
private final AgentReserveFundMapper reserveFundMapper;
|
||||
|
||||
public PageResponse<AgentReserveFundResp> list(AgentReserveFundSearchParam param) {
|
||||
param.startPage();
|
||||
return PageResponse.of(reserveFundMapper.selectList(param));
|
||||
}
|
||||
|
||||
public AgentReserveFundResp detail(Long reserveId) {
|
||||
AgentReserveFundResp resp = reserveFundMapper.selectById(reserveId);
|
||||
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
|
||||
return resp;
|
||||
}
|
||||
|
||||
/**
|
||||
* 적립금 생성(upsert).
|
||||
* balance = (직전월 balance or 0) + reserveIn − reserveOut. setScale(2, HALF_UP).
|
||||
* 해당 월 레코드가 이미 존재하면 update, 없으면 insert.
|
||||
*/
|
||||
@Transactional
|
||||
public Long create(AgentReserveFundSaveReq req) {
|
||||
AgentReserveFundVO prevMonth = reserveFundMapper.selectLatestBefore(req.getAgentId(), req.getSettleMonth());
|
||||
BigDecimal prevBalance = (prevMonth != null && prevMonth.getBalance() != null)
|
||||
? prevMonth.getBalance()
|
||||
: BigDecimal.ZERO;
|
||||
|
||||
BigDecimal balance = prevBalance
|
||||
.add(req.getReserveIn())
|
||||
.subtract(req.getReserveOut())
|
||||
.setScale(2, RoundingMode.HALF_UP);
|
||||
|
||||
AgentReserveFundVO existing = reserveFundMapper.selectByAgentAndMonth(req.getAgentId(), req.getSettleMonth());
|
||||
if (existing != null) {
|
||||
existing.setReserveIn(req.getReserveIn());
|
||||
existing.setReserveOut(req.getReserveOut());
|
||||
existing.setBalance(balance);
|
||||
reserveFundMapper.update(existing);
|
||||
return existing.getReserveId();
|
||||
}
|
||||
|
||||
AgentReserveFundVO vo = new AgentReserveFundVO();
|
||||
vo.setAgentId(req.getAgentId());
|
||||
vo.setSettleMonth(req.getSettleMonth());
|
||||
vo.setReserveIn(req.getReserveIn());
|
||||
vo.setReserveOut(req.getReserveOut());
|
||||
vo.setBalance(balance);
|
||||
reserveFundMapper.insert(vo);
|
||||
return vo.getReserveId();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package com.ga.api.service.commission;
|
||||
|
||||
import com.ga.common.exception.BizException;
|
||||
import com.ga.common.exception.ErrorCode;
|
||||
import com.ga.common.model.PageResponse;
|
||||
import com.ga.core.mapper.commission.SettlingSupportLedgerMapper;
|
||||
import com.ga.core.mapper.commission.SettlingSupportRuleMapper;
|
||||
import com.ga.core.vo.commission.*;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional(readOnly = true)
|
||||
public class SettlingSupportService {
|
||||
|
||||
private final SettlingSupportRuleMapper ruleMapper;
|
||||
private final SettlingSupportLedgerMapper ledgerMapper;
|
||||
|
||||
// ── Rule ──────────────────────────────────────────────────────────────────
|
||||
|
||||
public PageResponse<SettlingSupportRuleResp> listRules(SettlingSupportRuleSearchParam param) {
|
||||
param.startPage();
|
||||
return PageResponse.of(ruleMapper.selectList(param));
|
||||
}
|
||||
|
||||
public SettlingSupportRuleResp detailRule(Long ruleId) {
|
||||
SettlingSupportRuleResp resp = ruleMapper.selectById(ruleId);
|
||||
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
|
||||
return resp;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Long createRule(SettlingSupportRuleSaveReq req) {
|
||||
SettlingSupportRuleVO vo = new SettlingSupportRuleVO();
|
||||
vo.setAgentGrade(req.getAgentGrade());
|
||||
vo.setSupportAmount(req.getSupportAmount());
|
||||
vo.setDurationMonths(req.getDurationMonths());
|
||||
vo.setClawbackMonths(req.getClawbackMonths());
|
||||
vo.setEffectiveFrom(req.getEffectiveFrom());
|
||||
vo.setEffectiveTo(req.getEffectiveTo());
|
||||
ruleMapper.insert(vo);
|
||||
return vo.getRuleId();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void updateRule(Long ruleId, SettlingSupportRuleSaveReq req) {
|
||||
SettlingSupportRuleResp resp = ruleMapper.selectById(ruleId);
|
||||
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
|
||||
SettlingSupportRuleVO vo = new SettlingSupportRuleVO();
|
||||
vo.setRuleId(ruleId);
|
||||
vo.setAgentGrade(req.getAgentGrade());
|
||||
vo.setSupportAmount(req.getSupportAmount());
|
||||
vo.setDurationMonths(req.getDurationMonths());
|
||||
vo.setClawbackMonths(req.getClawbackMonths());
|
||||
vo.setEffectiveFrom(req.getEffectiveFrom());
|
||||
vo.setEffectiveTo(req.getEffectiveTo());
|
||||
ruleMapper.update(vo);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void deleteRule(Long ruleId) {
|
||||
SettlingSupportRuleResp resp = ruleMapper.selectById(ruleId);
|
||||
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
|
||||
ruleMapper.delete(ruleId);
|
||||
}
|
||||
|
||||
// ── Ledger ────────────────────────────────────────────────────────────────
|
||||
|
||||
public PageResponse<SettlingSupportLedgerResp> listLedgers(SettlingSupportLedgerSearchParam param) {
|
||||
param.startPage();
|
||||
return PageResponse.of(ledgerMapper.selectList(param));
|
||||
}
|
||||
|
||||
public SettlingSupportLedgerResp detailLedger(Long ledgerId) {
|
||||
SettlingSupportLedgerResp resp = ledgerMapper.selectById(ledgerId);
|
||||
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
|
||||
return resp;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Long createLedger(SettlingSupportLedgerSaveReq req) {
|
||||
BigDecimal clawback = req.getClawbackAmount() != null ? req.getClawbackAmount() : BigDecimal.ZERO;
|
||||
// 실지급액 = support_amount - clawback_amount
|
||||
BigDecimal netAmount = req.getSupportAmount()
|
||||
.subtract(clawback)
|
||||
.setScale(2, RoundingMode.HALF_UP);
|
||||
|
||||
SettlingSupportLedgerVO vo = new SettlingSupportLedgerVO();
|
||||
vo.setAgentId(req.getAgentId());
|
||||
vo.setSettleMonth(req.getSettleMonth());
|
||||
vo.setSupportAmount(req.getSupportAmount());
|
||||
vo.setClawbackAmount(clawback);
|
||||
vo.setNetAmount(netAmount);
|
||||
vo.setStatus("CALCULATED");
|
||||
ledgerMapper.insert(vo);
|
||||
return vo.getLedgerId();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.ga.api.service.commission;
|
||||
|
||||
import com.ga.common.exception.BizException;
|
||||
import com.ga.common.exception.ErrorCode;
|
||||
import com.ga.common.model.PageResponse;
|
||||
import com.ga.common.util.SecurityUtil;
|
||||
import com.ga.core.mapper.commission.StepCloseMapper;
|
||||
import com.ga.core.vo.commission.StepCloseResp;
|
||||
import com.ga.core.vo.commission.StepCloseSaveReq;
|
||||
import com.ga.core.vo.commission.StepCloseSearchParam;
|
||||
import com.ga.core.vo.commission.StepCloseVO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional(readOnly = true)
|
||||
public class StepCloseService {
|
||||
|
||||
private final StepCloseMapper stepCloseMapper;
|
||||
|
||||
public PageResponse<StepCloseResp> list(StepCloseSearchParam param) {
|
||||
param.startPage();
|
||||
return PageResponse.of(stepCloseMapper.selectList(param));
|
||||
}
|
||||
|
||||
public List<StepCloseResp> listByMonth(String settleMonth) {
|
||||
return stepCloseMapper.selectBySettleMonth(settleMonth);
|
||||
}
|
||||
|
||||
public StepCloseResp detail(Long stepCloseId) {
|
||||
StepCloseResp resp = stepCloseMapper.selectById(stepCloseId);
|
||||
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
|
||||
return resp;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Long create(StepCloseSaveReq req) {
|
||||
StepCloseVO vo = new StepCloseVO();
|
||||
vo.setSettleMonth(req.getSettleMonth());
|
||||
vo.setStepCode(req.getStepCode());
|
||||
vo.setStepSeq(req.getStepSeq());
|
||||
vo.setStatus("OPEN");
|
||||
stepCloseMapper.insert(vo);
|
||||
return vo.getStepCloseId();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void close(Long stepCloseId) {
|
||||
StepCloseResp resp = stepCloseMapper.selectById(stepCloseId);
|
||||
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
|
||||
if ("CLOSED".equals(resp.getStatus())) {
|
||||
throw new BizException(ErrorCode.INVALID_STATUS, "이미 마감된 단계입니다.");
|
||||
}
|
||||
stepCloseMapper.updateStatus(stepCloseId, "CLOSED", SecurityUtil.getCurrentUserId());
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void reopen(Long stepCloseId) {
|
||||
StepCloseResp resp = stepCloseMapper.selectById(stepCloseId);
|
||||
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
|
||||
if ("OPEN".equals(resp.getStatus())) {
|
||||
throw new BizException(ErrorCode.INVALID_STATUS, "이미 열려 있는 단계입니다.");
|
||||
}
|
||||
stepCloseMapper.updateStatus(stepCloseId, "OPEN", SecurityUtil.getCurrentUserId());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.ga.api.service.commission;
|
||||
|
||||
import com.ga.core.mapper.commission.AwardLedgerMapper;
|
||||
import com.ga.core.mapper.commission.AwardRuleMapper;
|
||||
import com.ga.core.vo.commission.AwardLedgerSaveReq;
|
||||
import com.ga.core.vo.commission.AwardLedgerVO;
|
||||
import com.ga.core.vo.commission.AwardRuleVO;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class AwardServiceTest {
|
||||
|
||||
@Mock AwardRuleMapper ruleMapper;
|
||||
@Mock AwardLedgerMapper ledgerMapper;
|
||||
|
||||
@InjectMocks AwardService service;
|
||||
|
||||
@Test
|
||||
@DisplayName("createLedger RATE: award_amount = base × award_rate (1,000,000 × 0.05 = 50,000)")
|
||||
void createLedger_rate() {
|
||||
AwardRuleVO rule = new AwardRuleVO();
|
||||
rule.setCalcType("RATE");
|
||||
rule.setAwardRate(new BigDecimal("0.05"));
|
||||
when(ruleMapper.selectActiveRule(eq("PERFORMANCE"), any())).thenReturn(rule);
|
||||
|
||||
AwardLedgerSaveReq req = new AwardLedgerSaveReq();
|
||||
req.setAgentId(1L);
|
||||
req.setSettleMonth("202506");
|
||||
req.setAwardType("PERFORMANCE");
|
||||
req.setBaseAmount(new BigDecimal("1000000"));
|
||||
|
||||
ArgumentCaptor<AwardLedgerVO> captor = ArgumentCaptor.forClass(AwardLedgerVO.class);
|
||||
when(ledgerMapper.insert(captor.capture())).thenReturn(1);
|
||||
|
||||
service.createLedger(req);
|
||||
|
||||
AwardLedgerVO saved = captor.getValue();
|
||||
assertThat(saved.getAwardAmount()).isEqualByComparingTo("50000.00");
|
||||
assertThat(saved.getStatus()).isEqualTo("CALCULATED");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("createLedger FIXED: award_amount = award_fixed (200,000)")
|
||||
void createLedger_fixed() {
|
||||
AwardRuleVO rule = new AwardRuleVO();
|
||||
rule.setCalcType("FIXED");
|
||||
rule.setAwardFixed(new BigDecimal("200000"));
|
||||
when(ruleMapper.selectActiveRule(eq("NEW_RECRUIT"), any())).thenReturn(rule);
|
||||
|
||||
AwardLedgerSaveReq req = new AwardLedgerSaveReq();
|
||||
req.setAgentId(2L);
|
||||
req.setSettleMonth("202506");
|
||||
req.setAwardType("NEW_RECRUIT");
|
||||
req.setBaseAmount(new BigDecimal("999999")); // FIXED이므로 base는 무시됨
|
||||
|
||||
ArgumentCaptor<AwardLedgerVO> captor = ArgumentCaptor.forClass(AwardLedgerVO.class);
|
||||
when(ledgerMapper.insert(captor.capture())).thenReturn(1);
|
||||
|
||||
service.createLedger(req);
|
||||
|
||||
AwardLedgerVO saved = captor.getValue();
|
||||
assertThat(saved.getAwardAmount()).isEqualByComparingTo("200000.00");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.ga.api.service.commission;
|
||||
|
||||
import com.ga.core.mapper.commission.BranchMgrAllowanceLedgerMapper;
|
||||
import com.ga.core.mapper.commission.BranchMgrAllowanceRuleMapper;
|
||||
import com.ga.core.vo.commission.BranchMgrAllowanceLedgerSaveReq;
|
||||
import com.ga.core.vo.commission.BranchMgrAllowanceLedgerVO;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class BranchAllowanceServiceTest {
|
||||
|
||||
@Mock BranchMgrAllowanceRuleMapper ruleMapper;
|
||||
@Mock BranchMgrAllowanceLedgerMapper ledgerMapper;
|
||||
|
||||
@InjectMocks BranchAllowanceService service;
|
||||
|
||||
@Test
|
||||
@DisplayName("createLedger: allowance = base × allowance_rate (2,000,000 × 0.05 = 100,000)")
|
||||
void createLedger_allowanceAmount() {
|
||||
BranchMgrAllowanceLedgerSaveReq req = new BranchMgrAllowanceLedgerSaveReq();
|
||||
req.setManagerAgentId(1L);
|
||||
req.setOrgId(10L);
|
||||
req.setSettleMonth("202506");
|
||||
req.setBaseAmount(new BigDecimal("2000000"));
|
||||
req.setAllowanceRate(new BigDecimal("0.05"));
|
||||
req.setClawbackAmount(null);
|
||||
|
||||
ArgumentCaptor<BranchMgrAllowanceLedgerVO> captor = ArgumentCaptor.forClass(BranchMgrAllowanceLedgerVO.class);
|
||||
when(ledgerMapper.insert(captor.capture())).thenReturn(1);
|
||||
|
||||
service.createLedger(req);
|
||||
|
||||
BranchMgrAllowanceLedgerVO saved = captor.getValue();
|
||||
assertThat(saved.getAllowanceAmount()).isEqualByComparingTo("100000.00");
|
||||
assertThat(saved.getStatus()).isEqualTo("CALCULATED");
|
||||
}
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
package com.ga.api.service.commission;
|
||||
|
||||
import com.ga.common.util.SecurityUtil;
|
||||
import com.ga.core.mapper.commission.CommissionSimulationMapper;
|
||||
import com.ga.core.vo.commission.CommissionSimulationResp;
|
||||
import com.ga.core.vo.commission.CommissionSimulationSaveReq;
|
||||
import com.ga.core.vo.commission.CommissionSimulationVO;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class CommissionSimulationServiceTest {
|
||||
|
||||
@Mock CommissionSimulationMapper simulationMapper;
|
||||
|
||||
@InjectMocks CommissionSimulationService service;
|
||||
|
||||
/**
|
||||
* 1,000,000 × 0.6 = 600,000 (company_payment)
|
||||
* 600,000 × 0.75 = 450,000 (agent_commission)
|
||||
* 450,000 × 0.033 = 14,850 (withholding_tax)
|
||||
* 450,000 - 14,850 = 435,150 (net_amount)
|
||||
* 450,000 × 0.5 = 225,000 (expected_chargeback 자동 계산)
|
||||
*/
|
||||
@Test
|
||||
@DisplayName("simulate: 수수료 공식 전체 검증 (1,000,000/0.6/0.75)")
|
||||
void simulate_fullFormula() {
|
||||
CommissionSimulationSaveReq req = new CommissionSimulationSaveReq();
|
||||
req.setSimName("테스트시뮬레이션");
|
||||
req.setPremiumAmount(new BigDecimal("1000000"));
|
||||
req.setCompanyRate(new BigDecimal("0.6"));
|
||||
req.setPayoutRate(new BigDecimal("0.75"));
|
||||
req.setCommissionYear(1);
|
||||
req.setExpectedChargeback(null); // 자동 계산
|
||||
|
||||
ArgumentCaptor<CommissionSimulationVO> captor = ArgumentCaptor.forClass(CommissionSimulationVO.class);
|
||||
when(simulationMapper.insert(captor.capture())).thenReturn(1);
|
||||
|
||||
CommissionSimulationResp mockResp = new CommissionSimulationResp();
|
||||
mockResp.setSimId(1L);
|
||||
when(simulationMapper.selectById(any())).thenReturn(mockResp);
|
||||
|
||||
try (MockedStatic<SecurityUtil> staticMock = mockStatic(SecurityUtil.class)) {
|
||||
staticMock.when(SecurityUtil::getCurrentUserId).thenReturn(null);
|
||||
|
||||
service.simulate(req);
|
||||
}
|
||||
|
||||
CommissionSimulationVO saved = captor.getValue();
|
||||
assertThat(saved.getCompanyPayment()).isEqualByComparingTo("600000.00");
|
||||
assertThat(saved.getAgentCommission()).isEqualByComparingTo("450000.00");
|
||||
assertThat(saved.getWithholdingTax()).isEqualByComparingTo("14850.00");
|
||||
assertThat(saved.getNetAmount()).isEqualByComparingTo("435150.00");
|
||||
assertThat(saved.getExpectedChargeback()).isEqualByComparingTo("225000.00");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package com.ga.api.service.commission;
|
||||
|
||||
import com.ga.core.mapper.commission.AgentGradeEvaluationMapper;
|
||||
import com.ga.core.mapper.commission.GradeEvalRuleMapper;
|
||||
import com.ga.core.vo.commission.AgentGradeEvaluationSaveReq;
|
||||
import com.ga.core.vo.commission.AgentGradeEvaluationVO;
|
||||
import com.ga.core.vo.commission.GradeEvalRuleVO;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class GradeEvaluationServiceTest {
|
||||
|
||||
@Mock GradeEvalRuleMapper ruleMapper;
|
||||
@Mock AgentGradeEvaluationMapper evalMapper;
|
||||
|
||||
@InjectMocks GradeEvaluationService service;
|
||||
|
||||
@Test
|
||||
@DisplayName("createEval: 매칭 룰이 있으면 evaluated_grade_id = rule.gradeId")
|
||||
void createEval_ruleMatched() {
|
||||
GradeEvalRuleVO rule = new GradeEvalRuleVO();
|
||||
rule.setGradeId(5L);
|
||||
when(ruleMapper.selectActiveRuleByScore(any(), any())).thenReturn(rule);
|
||||
|
||||
AgentGradeEvaluationSaveReq req = new AgentGradeEvaluationSaveReq();
|
||||
req.setAgentId(1L);
|
||||
req.setEvalPeriod("202506");
|
||||
req.setPerformanceScore(new BigDecimal("850"));
|
||||
req.setPrevGradeId(3L);
|
||||
|
||||
ArgumentCaptor<AgentGradeEvaluationVO> captor = ArgumentCaptor.forClass(AgentGradeEvaluationVO.class);
|
||||
when(evalMapper.insert(captor.capture())).thenReturn(1);
|
||||
|
||||
service.createEval(req);
|
||||
|
||||
AgentGradeEvaluationVO saved = captor.getValue();
|
||||
assertThat(saved.getEvaluatedGradeId()).isEqualTo(5L);
|
||||
assertThat(saved.getStatus()).isEqualTo("EVALUATED");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("createEval: 매칭 룰 없으면 evaluated_grade_id = prevGradeId 유지")
|
||||
void createEval_noRuleMatch_usePrevGrade() {
|
||||
when(ruleMapper.selectActiveRuleByScore(any(), any())).thenReturn(null);
|
||||
|
||||
AgentGradeEvaluationSaveReq req = new AgentGradeEvaluationSaveReq();
|
||||
req.setAgentId(1L);
|
||||
req.setEvalPeriod("202506");
|
||||
req.setPerformanceScore(new BigDecimal("100"));
|
||||
req.setPrevGradeId(2L);
|
||||
|
||||
ArgumentCaptor<AgentGradeEvaluationVO> captor = ArgumentCaptor.forClass(AgentGradeEvaluationVO.class);
|
||||
when(evalMapper.insert(captor.capture())).thenReturn(1);
|
||||
|
||||
service.createEval(req);
|
||||
|
||||
AgentGradeEvaluationVO saved = captor.getValue();
|
||||
assertThat(saved.getEvaluatedGradeId()).isEqualTo(2L);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.ga.api.service.commission;
|
||||
|
||||
import com.ga.core.mapper.commission.HqSettleItemMapper;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class HqSettleServiceTest {
|
||||
|
||||
@Mock HqSettleItemMapper itemMapper;
|
||||
|
||||
@InjectMocks HqSettleService service;
|
||||
|
||||
@Test
|
||||
@DisplayName("summary: profit = income - expense (3,000,000 - 1,200,000 = 1,800,000)")
|
||||
void summary_profitCalc() {
|
||||
when(itemMapper.sumByMonthAndType("202506", "INCOME")).thenReturn(new BigDecimal("3000000"));
|
||||
when(itemMapper.sumByMonthAndType("202506", "EXPENSE")).thenReturn(new BigDecimal("1200000"));
|
||||
|
||||
HqSettleService.HqSummaryResp result = service.summary("202506");
|
||||
|
||||
assertThat(result.getIncome()).isEqualByComparingTo("3000000");
|
||||
assertThat(result.getExpense()).isEqualByComparingTo("1200000");
|
||||
assertThat(result.getProfit()).isEqualByComparingTo("1800000");
|
||||
assertThat(result.getSettleMonth()).isEqualTo("202506");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("summary: income/expense null이면 0으로 처리 → profit = 0")
|
||||
void summary_nullHandling() {
|
||||
when(itemMapper.sumByMonthAndType("202506", "INCOME")).thenReturn(null);
|
||||
when(itemMapper.sumByMonthAndType("202506", "EXPENSE")).thenReturn(null);
|
||||
|
||||
HqSettleService.HqSummaryResp result = service.summary("202506");
|
||||
|
||||
assertThat(result.getProfit()).isEqualByComparingTo("0");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.ga.api.service.commission;
|
||||
|
||||
import com.ga.core.mapper.commission.IncomeCommissionLedgerMapper;
|
||||
import com.ga.core.mapper.commission.MarginSummaryMapper;
|
||||
import com.ga.core.vo.commission.IncomeCommissionLedgerSaveReq;
|
||||
import com.ga.core.vo.commission.IncomeCommissionLedgerVO;
|
||||
import com.ga.core.vo.commission.MarginSummaryVO;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class IncomeCommissionServiceTest {
|
||||
|
||||
@Mock IncomeCommissionLedgerMapper ledgerMapper;
|
||||
@Mock MarginSummaryMapper marginMapper;
|
||||
|
||||
@InjectMocks IncomeCommissionService service;
|
||||
|
||||
@Test
|
||||
@DisplayName("createLedger: income_amount = premium × company_rate (1,000,000 × 0.6 = 600,000)")
|
||||
void createLedger_incomeAmount() {
|
||||
IncomeCommissionLedgerSaveReq req = new IncomeCommissionLedgerSaveReq();
|
||||
req.setContractId(1L);
|
||||
req.setCarrierId(10L);
|
||||
req.setSettleMonth("202506");
|
||||
req.setPremiumAmount(new BigDecimal("1000000"));
|
||||
req.setCompanyRate(new BigDecimal("0.6"));
|
||||
req.setCommissionType("RECRUIT");
|
||||
|
||||
ArgumentCaptor<IncomeCommissionLedgerVO> captor = ArgumentCaptor.forClass(IncomeCommissionLedgerVO.class);
|
||||
when(ledgerMapper.insert(captor.capture())).thenReturn(1);
|
||||
|
||||
service.createLedger(req);
|
||||
|
||||
IncomeCommissionLedgerVO saved = captor.getValue();
|
||||
assertThat(saved.getIncomeAmount()).isEqualByComparingTo("600000.00");
|
||||
assertThat(saved.getStatus()).isEqualTo("CALCULATED");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("recalcMargin: margin = income - payout, marginRate = margin / income")
|
||||
void recalcMargin_calculation() {
|
||||
when(marginMapper.sumIncomeByMonth("202506")).thenReturn(new BigDecimal("1000000"));
|
||||
when(marginMapper.sumPayoutByMonth("202506", null)).thenReturn(new BigDecimal("600000"));
|
||||
when(marginMapper.selectByMonthAndAgent("202506", null)).thenReturn(null);
|
||||
|
||||
ArgumentCaptor<MarginSummaryVO> captor = ArgumentCaptor.forClass(MarginSummaryVO.class);
|
||||
when(marginMapper.insert(captor.capture())).thenReturn(1);
|
||||
|
||||
service.recalcMargin("202506", null);
|
||||
|
||||
MarginSummaryVO saved = captor.getValue();
|
||||
assertThat(saved.getMarginAmount()).isEqualByComparingTo("400000.00");
|
||||
assertThat(saved.getMarginRate()).isEqualByComparingTo("0.4000");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.ga.api.service.commission;
|
||||
|
||||
import com.ga.common.exception.BizException;
|
||||
import com.ga.core.mapper.commission.JointContractMapper;
|
||||
import com.ga.core.vo.commission.JointContractSaveReq;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class JointContractServiceTest {
|
||||
|
||||
@Mock JointContractMapper jointContractMapper;
|
||||
|
||||
@InjectMocks JointContractService service;
|
||||
|
||||
@Test
|
||||
@DisplayName("create: 기존 지분율 합 + 신규 shareRate > 1이면 BizException 발생")
|
||||
void create_shareRateExceedsOne_throwsBizException() {
|
||||
// 기존 합 0.7, 신규 0.4 → 합계 1.1 > 1
|
||||
when(jointContractMapper.sumShareByContract(eq(100L))).thenReturn(new BigDecimal("0.7"));
|
||||
|
||||
JointContractSaveReq req = new JointContractSaveReq();
|
||||
req.setContractId(100L);
|
||||
req.setAgentId(5L);
|
||||
req.setRole("MAIN");
|
||||
req.setShareRate(new BigDecimal("0.4"));
|
||||
|
||||
assertThatThrownBy(() -> service.create(req))
|
||||
.isInstanceOf(BizException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("create: 기존 합 + 신규 shareRate <= 1이면 정상 생성")
|
||||
void create_shareRateValid_noException() {
|
||||
// 기존 합 0.6, 신규 0.4 → 합계 1.0 = 1 (경계값 허용)
|
||||
when(jointContractMapper.sumShareByContract(eq(100L))).thenReturn(new BigDecimal("0.6"));
|
||||
when(jointContractMapper.insert(org.mockito.ArgumentMatchers.any())).thenReturn(1);
|
||||
|
||||
JointContractSaveReq req = new JointContractSaveReq();
|
||||
req.setContractId(100L);
|
||||
req.setAgentId(5L);
|
||||
req.setRole("SUB");
|
||||
req.setShareRate(new BigDecimal("0.4"));
|
||||
|
||||
// 예외 없이 통과해야 함
|
||||
service.create(req);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.ga.api.service.commission;
|
||||
|
||||
import com.ga.core.mapper.commission.LifeSupportLedgerMapper;
|
||||
import com.ga.core.mapper.commission.LifeSupportRuleMapper;
|
||||
import com.ga.core.vo.commission.LifeSupportLedgerSaveReq;
|
||||
import com.ga.core.vo.commission.LifeSupportLedgerVO;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class LifeSupportServiceTest {
|
||||
|
||||
@Mock LifeSupportRuleMapper ruleMapper;
|
||||
@Mock LifeSupportLedgerMapper ledgerMapper;
|
||||
|
||||
@InjectMocks LifeSupportService service;
|
||||
|
||||
@Test
|
||||
@DisplayName("createLedger: support = base×rate, net = support - clawback (1,000,000×0.3=300,000, 300,000-20,000=280,000)")
|
||||
void createLedger_calculation() {
|
||||
LifeSupportLedgerSaveReq req = new LifeSupportLedgerSaveReq();
|
||||
req.setAgentId(1L);
|
||||
req.setSettleMonth("202506");
|
||||
req.setBaseAmount(new BigDecimal("1000000"));
|
||||
req.setSupportRate(new BigDecimal("0.3"));
|
||||
req.setClawbackAmount(new BigDecimal("20000"));
|
||||
|
||||
ArgumentCaptor<LifeSupportLedgerVO> captor = ArgumentCaptor.forClass(LifeSupportLedgerVO.class);
|
||||
when(ledgerMapper.insert(captor.capture())).thenReturn(1);
|
||||
|
||||
service.createLedger(req);
|
||||
|
||||
LifeSupportLedgerVO saved = captor.getValue();
|
||||
assertThat(saved.getSupportAmount()).isEqualByComparingTo("300000.00");
|
||||
assertThat(saved.getNetAmount()).isEqualByComparingTo("280000.00");
|
||||
assertThat(saved.getStatus()).isEqualTo("CALCULATED");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("createLedger: clawback null이면 0으로 처리 → net = support")
|
||||
void createLedger_noClawback() {
|
||||
LifeSupportLedgerSaveReq req = new LifeSupportLedgerSaveReq();
|
||||
req.setAgentId(1L);
|
||||
req.setSettleMonth("202506");
|
||||
req.setBaseAmount(new BigDecimal("500000"));
|
||||
req.setSupportRate(new BigDecimal("0.2"));
|
||||
req.setClawbackAmount(null);
|
||||
|
||||
ArgumentCaptor<LifeSupportLedgerVO> captor = ArgumentCaptor.forClass(LifeSupportLedgerVO.class);
|
||||
when(ledgerMapper.insert(captor.capture())).thenReturn(1);
|
||||
|
||||
service.createLedger(req);
|
||||
|
||||
LifeSupportLedgerVO saved = captor.getValue();
|
||||
assertThat(saved.getSupportAmount()).isEqualByComparingTo("100000.00");
|
||||
assertThat(saved.getNetAmount()).isEqualByComparingTo("100000.00");
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
package com.ga.api.service.commission;
|
||||
|
||||
import com.ga.core.mapper.commission.ProductConversionRateMapper;
|
||||
import com.ga.core.mapper.commission.RecognizedPerformanceLedgerMapper;
|
||||
import com.ga.core.vo.commission.RecognizedPerformanceLedgerSaveReq;
|
||||
import com.ga.core.vo.commission.RecognizedPerformanceLedgerVO;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class RecognizedPerformanceServiceTest {
|
||||
|
||||
@Mock ProductConversionRateMapper conversionRateMapper;
|
||||
@Mock RecognizedPerformanceLedgerMapper ledgerMapper;
|
||||
|
||||
@InjectMocks RecognizedPerformanceService service;
|
||||
|
||||
@Test
|
||||
@DisplayName("createLedger: converted=premium×conversion_rate, recognized=converted×recognition_rate (1,000,000×0.5×0.8=400,000)")
|
||||
void createLedger_twoStepCalc() {
|
||||
RecognizedPerformanceLedgerSaveReq req = new RecognizedPerformanceLedgerSaveReq();
|
||||
req.setAgentId(1L);
|
||||
req.setContractId(2L);
|
||||
req.setSettleMonth("202506");
|
||||
req.setPremiumAmount(new BigDecimal("1000000"));
|
||||
req.setConversionRate(new BigDecimal("0.5"));
|
||||
req.setRecognitionRate(new BigDecimal("0.8"));
|
||||
|
||||
ArgumentCaptor<RecognizedPerformanceLedgerVO> captor = ArgumentCaptor.forClass(RecognizedPerformanceLedgerVO.class);
|
||||
when(ledgerMapper.insert(captor.capture())).thenReturn(1);
|
||||
|
||||
service.createLedger(req);
|
||||
|
||||
RecognizedPerformanceLedgerVO saved = captor.getValue();
|
||||
assertThat(saved.getConvertedPremium()).isEqualByComparingTo("500000.00");
|
||||
assertThat(saved.getRecognizedAmount()).isEqualByComparingTo("400000.00");
|
||||
assertThat(saved.getStatus()).isEqualTo("CALCULATED");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package com.ga.api.service.commission;
|
||||
|
||||
import com.ga.core.mapper.commission.ReferralCommissionLedgerMapper;
|
||||
import com.ga.core.mapper.commission.ReferralCommissionRuleMapper;
|
||||
import com.ga.core.vo.commission.ReferralCommissionLedgerSaveReq;
|
||||
import com.ga.core.vo.commission.ReferralCommissionLedgerVO;
|
||||
import com.ga.core.vo.commission.ReferralCommissionRuleVO;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class ReferralCommissionServiceTest {
|
||||
|
||||
@Mock ReferralCommissionRuleMapper ruleMapper;
|
||||
@Mock ReferralCommissionLedgerMapper ledgerMapper;
|
||||
|
||||
@InjectMocks ReferralCommissionService service;
|
||||
|
||||
@Test
|
||||
@DisplayName("createLedger RATE: commission_amount = base × rate (500,000 × 0.1 = 50,000)")
|
||||
void createLedger_rate() {
|
||||
ReferralCommissionLedgerSaveReq req = new ReferralCommissionLedgerSaveReq();
|
||||
req.setContractId(1L);
|
||||
req.setReferrerAgentId(2L);
|
||||
req.setSettleMonth("202506");
|
||||
req.setReferralType("REFERRAL");
|
||||
req.setBaseAmount(new BigDecimal("500000"));
|
||||
req.setCommissionRate(new BigDecimal("0.1"));
|
||||
|
||||
ArgumentCaptor<ReferralCommissionLedgerVO> captor = ArgumentCaptor.forClass(ReferralCommissionLedgerVO.class);
|
||||
when(ledgerMapper.insert(captor.capture())).thenReturn(1);
|
||||
|
||||
service.createLedger(req);
|
||||
|
||||
ReferralCommissionLedgerVO saved = captor.getValue();
|
||||
assertThat(saved.getCommissionAmount()).isEqualByComparingTo("50000.00");
|
||||
assertThat(saved.getStatus()).isEqualTo("CALCULATED");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("createLedger FIXED: commission_amount = rule.fixedAmount (30,000)")
|
||||
void createLedger_fixed() {
|
||||
ReferralCommissionLedgerSaveReq req = new ReferralCommissionLedgerSaveReq();
|
||||
req.setContractId(1L);
|
||||
req.setReferrerAgentId(2L);
|
||||
req.setSettleMonth("202506");
|
||||
req.setReferralType("TRANSFER");
|
||||
req.setBaseAmount(new BigDecimal("500000"));
|
||||
req.setCommissionRate(null); // FIXED 분기
|
||||
|
||||
ReferralCommissionRuleVO rule = new ReferralCommissionRuleVO();
|
||||
rule.setCalcType("FIXED");
|
||||
rule.setFixedAmount(new BigDecimal("30000"));
|
||||
when(ruleMapper.selectActiveRule(any(), any())).thenReturn(rule);
|
||||
|
||||
ArgumentCaptor<ReferralCommissionLedgerVO> captor = ArgumentCaptor.forClass(ReferralCommissionLedgerVO.class);
|
||||
when(ledgerMapper.insert(captor.capture())).thenReturn(1);
|
||||
|
||||
service.createLedger(req);
|
||||
|
||||
ReferralCommissionLedgerVO saved = captor.getValue();
|
||||
assertThat(saved.getCommissionAmount()).isEqualByComparingTo("30000.00");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.ga.api.service.commission;
|
||||
|
||||
import com.ga.core.mapper.commission.AgentReserveFundMapper;
|
||||
import com.ga.core.vo.commission.AgentReserveFundSaveReq;
|
||||
import com.ga.core.vo.commission.AgentReserveFundVO;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class ReserveFundServiceTest {
|
||||
|
||||
@Mock AgentReserveFundMapper reserveFundMapper;
|
||||
|
||||
@InjectMocks ReserveFundService service;
|
||||
|
||||
@Test
|
||||
@DisplayName("create: balance = 직전월잔액 + reserveIn - reserveOut (100,000 + 50,000 - 20,000 = 130,000)")
|
||||
void create_balanceCalc() {
|
||||
AgentReserveFundVO prevMonth = new AgentReserveFundVO();
|
||||
prevMonth.setBalance(new BigDecimal("100000"));
|
||||
when(reserveFundMapper.selectLatestBefore(eq(1L), eq("202506"))).thenReturn(prevMonth);
|
||||
when(reserveFundMapper.selectByAgentAndMonth(eq(1L), eq("202506"))).thenReturn(null);
|
||||
|
||||
AgentReserveFundSaveReq req = new AgentReserveFundSaveReq();
|
||||
req.setAgentId(1L);
|
||||
req.setSettleMonth("202506");
|
||||
req.setReserveIn(new BigDecimal("50000"));
|
||||
req.setReserveOut(new BigDecimal("20000"));
|
||||
|
||||
ArgumentCaptor<AgentReserveFundVO> captor = ArgumentCaptor.forClass(AgentReserveFundVO.class);
|
||||
when(reserveFundMapper.insert(captor.capture())).thenReturn(1);
|
||||
|
||||
service.create(req);
|
||||
|
||||
AgentReserveFundVO saved = captor.getValue();
|
||||
assertThat(saved.getBalance()).isEqualByComparingTo("130000.00");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("create: 직전월 잔액 없으면 0으로 처리 → balance = reserveIn - reserveOut")
|
||||
void create_noPrevMonth() {
|
||||
when(reserveFundMapper.selectLatestBefore(eq(1L), eq("202501"))).thenReturn(null);
|
||||
when(reserveFundMapper.selectByAgentAndMonth(eq(1L), eq("202501"))).thenReturn(null);
|
||||
|
||||
AgentReserveFundSaveReq req = new AgentReserveFundSaveReq();
|
||||
req.setAgentId(1L);
|
||||
req.setSettleMonth("202501");
|
||||
req.setReserveIn(new BigDecimal("80000"));
|
||||
req.setReserveOut(new BigDecimal("30000"));
|
||||
|
||||
ArgumentCaptor<AgentReserveFundVO> captor = ArgumentCaptor.forClass(AgentReserveFundVO.class);
|
||||
when(reserveFundMapper.insert(captor.capture())).thenReturn(1);
|
||||
|
||||
service.create(req);
|
||||
|
||||
AgentReserveFundVO saved = captor.getValue();
|
||||
assertThat(saved.getBalance()).isEqualByComparingTo("50000.00");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.ga.api.service.commission;
|
||||
|
||||
import com.ga.core.mapper.commission.SettlingSupportLedgerMapper;
|
||||
import com.ga.core.mapper.commission.SettlingSupportRuleMapper;
|
||||
import com.ga.core.vo.commission.SettlingSupportLedgerSaveReq;
|
||||
import com.ga.core.vo.commission.SettlingSupportLedgerVO;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class SettlingSupportServiceTest {
|
||||
|
||||
@Mock SettlingSupportRuleMapper ruleMapper;
|
||||
@Mock SettlingSupportLedgerMapper ledgerMapper;
|
||||
|
||||
@InjectMocks SettlingSupportService service;
|
||||
|
||||
@Test
|
||||
@DisplayName("createLedger: net_amount = support_amount - clawback_amount (500,000 - 50,000 = 450,000)")
|
||||
void createLedger_netAmount() {
|
||||
SettlingSupportLedgerSaveReq req = new SettlingSupportLedgerSaveReq();
|
||||
req.setAgentId(1L);
|
||||
req.setSettleMonth("202506");
|
||||
req.setSupportAmount(new BigDecimal("500000"));
|
||||
req.setClawbackAmount(new BigDecimal("50000"));
|
||||
|
||||
ArgumentCaptor<SettlingSupportLedgerVO> captor = ArgumentCaptor.forClass(SettlingSupportLedgerVO.class);
|
||||
when(ledgerMapper.insert(captor.capture())).thenReturn(1);
|
||||
|
||||
service.createLedger(req);
|
||||
|
||||
SettlingSupportLedgerVO saved = captor.getValue();
|
||||
assertThat(saved.getNetAmount()).isEqualByComparingTo("450000.00");
|
||||
assertThat(saved.getStatus()).isEqualTo("CALCULATED");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("createLedger: clawback null이면 0으로 처리 → net = support_amount")
|
||||
void createLedger_noClawback() {
|
||||
SettlingSupportLedgerSaveReq req = new SettlingSupportLedgerSaveReq();
|
||||
req.setAgentId(1L);
|
||||
req.setSettleMonth("202506");
|
||||
req.setSupportAmount(new BigDecimal("300000"));
|
||||
req.setClawbackAmount(null);
|
||||
|
||||
ArgumentCaptor<SettlingSupportLedgerVO> captor = ArgumentCaptor.forClass(SettlingSupportLedgerVO.class);
|
||||
when(ledgerMapper.insert(captor.capture())).thenReturn(1);
|
||||
|
||||
service.createLedger(req);
|
||||
|
||||
SettlingSupportLedgerVO saved = captor.getValue();
|
||||
assertThat(saved.getNetAmount()).isEqualByComparingTo("300000.00");
|
||||
assertThat(saved.getClawbackAmount()).isEqualByComparingTo("0");
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,15 @@ import com.ga.batch.settlement.SettlementContext;
|
||||
import com.ga.batch.settlement.domain.BatchTaxCalculator;
|
||||
import com.ga.batch.settlement.domain.TaxBreakdown;
|
||||
import com.ga.common.util.MoneyUtil;
|
||||
import com.ga.core.mapper.commission.AwardLedgerMapper;
|
||||
import com.ga.core.mapper.commission.BranchMgrAllowanceLedgerMapper;
|
||||
import com.ga.core.mapper.commission.CollectionCommissionLedgerMapper;
|
||||
import com.ga.core.mapper.commission.LifeSupportLedgerMapper;
|
||||
import com.ga.core.mapper.commission.PersistencyBonusMapper;
|
||||
import com.ga.core.mapper.commission.ReferralCommissionLedgerMapper;
|
||||
import com.ga.core.mapper.commission.ReinstatementCommissionLedgerMapper;
|
||||
import com.ga.core.mapper.commission.RenewalCommissionLedgerMapper;
|
||||
import com.ga.core.mapper.commission.SettlingSupportLedgerMapper;
|
||||
import com.ga.core.mapper.ledger.ExceptionLedgerMapper;
|
||||
import com.ga.core.mapper.ledger.MaintainLedgerMapper;
|
||||
import com.ga.core.mapper.ledger.RecruitLedgerMapper;
|
||||
@@ -68,6 +77,17 @@ public class AggregateStep implements Tasklet {
|
||||
private final CommissionStatementMapper statementMapper;
|
||||
private final BatchTaxCalculator taxCalculator;
|
||||
|
||||
// 설계사 지급성 9종 원장 Mapper
|
||||
private final CollectionCommissionLedgerMapper collectionMapper;
|
||||
private final RenewalCommissionLedgerMapper renewalMapper;
|
||||
private final ReinstatementCommissionLedgerMapper reinstatementMapper;
|
||||
private final PersistencyBonusMapper persistencyBonusMapper;
|
||||
private final ReferralCommissionLedgerMapper referralMapper;
|
||||
private final SettlingSupportLedgerMapper settlingSupportMapper;
|
||||
private final AwardLedgerMapper awardMapper;
|
||||
private final LifeSupportLedgerMapper lifeSupportMapper;
|
||||
private final BranchMgrAllowanceLedgerMapper branchMgrAllowanceMapper;
|
||||
|
||||
@Override
|
||||
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) {
|
||||
log.info("[Step8 aggregate] settleMonth={}", ctx.getSettleMonth());
|
||||
@@ -86,6 +106,17 @@ public class AggregateStep implements Tasklet {
|
||||
excMinus.put(agentId, toBd(row.get("minus")));
|
||||
}
|
||||
|
||||
// 설계사 지급성 9종 집계
|
||||
Map<Long, BigDecimal> collectionMap = toMap(collectionMapper.aggregateByAgent(ctx.getSettleMonth()));
|
||||
Map<Long, BigDecimal> renewalMap = toMap(renewalMapper.aggregateByAgent(ctx.getSettleMonth()));
|
||||
Map<Long, BigDecimal> reinstatementMap = toMap(reinstatementMapper.aggregateByAgent(ctx.getSettleMonth()));
|
||||
Map<Long, BigDecimal> persistencyMap = toMap(persistencyBonusMapper.aggregateByAgent(ctx.getSettleMonth()));
|
||||
Map<Long, BigDecimal> referralMap = toMap(referralMapper.aggregateByAgent(ctx.getSettleMonth()));
|
||||
Map<Long, BigDecimal> settlingSupportMap = toMap(settlingSupportMapper.aggregateByAgent(ctx.getSettleMonth()));
|
||||
Map<Long, BigDecimal> awardMap = toMap(awardMapper.aggregateByAgent(ctx.getSettleMonth()));
|
||||
Map<Long, BigDecimal> lifeSupportMap = toMap(lifeSupportMapper.aggregateByAgent(ctx.getSettleMonth()));
|
||||
Map<Long, BigDecimal> branchMgrMap = toMap(branchMgrAllowanceMapper.aggregateByAgent(ctx.getSettleMonth()));
|
||||
|
||||
// 합집합 agent_id
|
||||
Set<Long> agentIds = new HashSet<>();
|
||||
agentIds.addAll(recruitMap.keySet());
|
||||
@@ -94,6 +125,15 @@ public class AggregateStep implements Tasklet {
|
||||
agentIds.addAll(overrideMap.keySet());
|
||||
agentIds.addAll(excPlus.keySet());
|
||||
agentIds.addAll(excMinus.keySet());
|
||||
agentIds.addAll(collectionMap.keySet());
|
||||
agentIds.addAll(renewalMap.keySet());
|
||||
agentIds.addAll(reinstatementMap.keySet());
|
||||
agentIds.addAll(persistencyMap.keySet());
|
||||
agentIds.addAll(referralMap.keySet());
|
||||
agentIds.addAll(settlingSupportMap.keySet());
|
||||
agentIds.addAll(awardMap.keySet());
|
||||
agentIds.addAll(lifeSupportMap.keySet());
|
||||
agentIds.addAll(branchMgrMap.keySet());
|
||||
|
||||
// 활성 설계사 누락 보충
|
||||
AgentSearchParam ap = new AgentSearchParam();
|
||||
@@ -122,8 +162,21 @@ public class AggregateStep implements Tasklet {
|
||||
BigDecimal ep = excPlus.getOrDefault(agentId, BigDecimal.ZERO);
|
||||
BigDecimal em = excMinus.getOrDefault(agentId, BigDecimal.ZERO);
|
||||
|
||||
// 설계사 지급성 9종 합산 (원장 없으면 ZERO → gross 불변 보장)
|
||||
BigDecimal otherCommission = BigDecimal.ZERO;
|
||||
otherCommission = MoneyUtil.add(otherCommission, collectionMap.getOrDefault(agentId, BigDecimal.ZERO));
|
||||
otherCommission = MoneyUtil.add(otherCommission, renewalMap.getOrDefault(agentId, BigDecimal.ZERO));
|
||||
otherCommission = MoneyUtil.add(otherCommission, reinstatementMap.getOrDefault(agentId, BigDecimal.ZERO));
|
||||
otherCommission = MoneyUtil.add(otherCommission, persistencyMap.getOrDefault(agentId, BigDecimal.ZERO));
|
||||
otherCommission = MoneyUtil.add(otherCommission, referralMap.getOrDefault(agentId, BigDecimal.ZERO));
|
||||
otherCommission = MoneyUtil.add(otherCommission, settlingSupportMap.getOrDefault(agentId, BigDecimal.ZERO));
|
||||
otherCommission = MoneyUtil.add(otherCommission, awardMap.getOrDefault(agentId, BigDecimal.ZERO));
|
||||
otherCommission = MoneyUtil.add(otherCommission, lifeSupportMap.getOrDefault(agentId, BigDecimal.ZERO));
|
||||
otherCommission = MoneyUtil.add(otherCommission, branchMgrMap.getOrDefault(agentId, BigDecimal.ZERO));
|
||||
|
||||
BigDecimal gross = MoneyUtil.add(MoneyUtil.add(MoneyUtil.add(recruit, maintain), ov), ep)
|
||||
.subtract(cb).subtract(em);
|
||||
gross = MoneyUtil.add(gross, otherCommission);
|
||||
|
||||
// 세금유형 조회 (없으면 기본: BUSINESS_INCOME / NONE)
|
||||
AgentVO agentVO = agentMapper.selectById(agentId);
|
||||
@@ -152,6 +205,7 @@ public class AggregateStep implements Tasklet {
|
||||
vo.setExceptionMinus(em);
|
||||
vo.setOverrideTotal(ov);
|
||||
vo.setChargebackTotal(cb);
|
||||
vo.setOtherCommissionTotal(otherCommission);
|
||||
vo.setGrossAmount(gross);
|
||||
vo.setTaxAmount(totalTax); // incomeTax + localTax 합산
|
||||
vo.setNetAmount(netAmount); // gross - incomeTax - localTax + vat - deduction
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
-- V100: 공동계약 (joint_contract)
|
||||
-- 한 계약을 여러 설계사가 공동 모집 → 지분율(share_rate)로 수수료 배분.
|
||||
-- 공식: 설계사몫 = 계약수수료 × share_rate
|
||||
-- 검증: 계약별 share_rate 합=1 — Service 가드
|
||||
|
||||
CREATE TABLE joint_contract (
|
||||
joint_id BIGSERIAL PRIMARY KEY,
|
||||
contract_id INT NOT NULL REFERENCES contract(contract_id),
|
||||
agent_id INT NOT NULL REFERENCES agent(agent_id),
|
||||
role VARCHAR(10) NOT NULL,
|
||||
share_rate NUMERIC(7,4) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
created_by BIGINT,
|
||||
UNIQUE (contract_id, agent_id),
|
||||
CHECK (role IN ('LEAD','SUB')),
|
||||
CHECK (share_rate >= 0 AND share_rate <= 1)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE joint_contract IS '공동계약 — 복수 설계사의 계약별 지분율 관리';
|
||||
COMMENT ON COLUMN joint_contract.contract_id IS '대상 계약 FK';
|
||||
COMMENT ON COLUMN joint_contract.agent_id IS '참여 설계사 FK';
|
||||
COMMENT ON COLUMN joint_contract.role IS '역할 (LEAD=주설계사/SUB=공동설계사)';
|
||||
COMMENT ON COLUMN joint_contract.share_rate IS '지분율 (0~1, 계약별 합=1 Service 검증)';
|
||||
|
||||
CREATE INDEX idx_jcon_contract ON joint_contract(contract_id);
|
||||
CREATE INDEX idx_jcon_agent ON joint_contract(agent_id);
|
||||
@@ -0,0 +1,28 @@
|
||||
-- V101: 보증보험 (agent_guarantee_insurance)
|
||||
-- 설계사가 가입하는 신원보증보험 가입현황 추적(관리 목적, 공식 없음).
|
||||
|
||||
CREATE TABLE agent_guarantee_insurance (
|
||||
guarantee_id BIGSERIAL PRIMARY KEY,
|
||||
agent_id INT NOT NULL REFERENCES agent(agent_id),
|
||||
policy_no VARCHAR(50) NOT NULL,
|
||||
carrier_id INT REFERENCES insurance_company(company_id),
|
||||
guarantee_amount NUMERIC(15,2) NOT NULL,
|
||||
start_date DATE NOT NULL,
|
||||
end_date DATE NOT NULL,
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP,
|
||||
CHECK (status IN ('ACTIVE','EXPIRED','CANCELLED'))
|
||||
);
|
||||
|
||||
COMMENT ON TABLE agent_guarantee_insurance IS '보증보험 — 설계사 신원보증보험 가입현황';
|
||||
COMMENT ON COLUMN agent_guarantee_insurance.agent_id IS '설계사 FK';
|
||||
COMMENT ON COLUMN agent_guarantee_insurance.policy_no IS '증권번호';
|
||||
COMMENT ON COLUMN agent_guarantee_insurance.carrier_id IS '보증보험사 FK(insurance_company)';
|
||||
COMMENT ON COLUMN agent_guarantee_insurance.guarantee_amount IS '보증금액';
|
||||
COMMENT ON COLUMN agent_guarantee_insurance.start_date IS '보증 시작일';
|
||||
COMMENT ON COLUMN agent_guarantee_insurance.end_date IS '보증 종료일';
|
||||
COMMENT ON COLUMN agent_guarantee_insurance.status IS '상태 (ACTIVE=유효/EXPIRED=만료/CANCELLED=해지)';
|
||||
|
||||
CREATE INDEX idx_agi_agent_status ON agent_guarantee_insurance(agent_id, status);
|
||||
CREATE INDEX idx_agi_end_date ON agent_guarantee_insurance(end_date);
|
||||
@@ -0,0 +1,24 @@
|
||||
-- V102: 적립금 (agent_reserve_fund)
|
||||
-- 환수 대비 수수료 일부 적립 + 잔액 관리.
|
||||
-- 공식: balance = (직전월 balance) + reserve_in - reserve_out
|
||||
-- Service가 직전월 balance를 조회하여 누적 계산.
|
||||
|
||||
CREATE TABLE agent_reserve_fund (
|
||||
reserve_id BIGSERIAL PRIMARY KEY,
|
||||
agent_id INT NOT NULL REFERENCES agent(agent_id),
|
||||
settle_month CHAR(6) NOT NULL,
|
||||
reserve_in NUMERIC(15,2) NOT NULL DEFAULT 0,
|
||||
reserve_out NUMERIC(15,2) NOT NULL DEFAULT 0,
|
||||
balance NUMERIC(15,2) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
UNIQUE (agent_id, settle_month)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE agent_reserve_fund IS '적립금 — 환수 대비 설계사별 월별 적립금 잔액';
|
||||
COMMENT ON COLUMN agent_reserve_fund.agent_id IS '설계사 FK';
|
||||
COMMENT ON COLUMN agent_reserve_fund.settle_month IS '정산 귀속월 (YYYYMM)';
|
||||
COMMENT ON COLUMN agent_reserve_fund.reserve_in IS '당월 적립액';
|
||||
COMMENT ON COLUMN agent_reserve_fund.reserve_out IS '당월 인출/사용액';
|
||||
COMMENT ON COLUMN agent_reserve_fund.balance IS '잔액 = 전월잔액 + reserve_in - reserve_out';
|
||||
|
||||
CREATE INDEX idx_arf_agent_month ON agent_reserve_fund(agent_id, settle_month);
|
||||
@@ -0,0 +1,39 @@
|
||||
-- V103: 수수료 시뮬레이터 (commission_simulation)
|
||||
-- 보험료/요율/지급률 입력 → 수수료·세금·실지급·예상환수 산출. 시나리오 저장.
|
||||
-- 공식:
|
||||
-- company_payment = premium_amount × company_rate
|
||||
-- agent_commission = company_payment × payout_rate
|
||||
-- withholding_tax = agent_commission × 0.033 (3.3%, HALF_UP)
|
||||
-- net_amount = agent_commission - withholding_tax
|
||||
|
||||
CREATE TABLE commission_simulation (
|
||||
sim_id BIGSERIAL PRIMARY KEY,
|
||||
sim_name VARCHAR(100) NOT NULL,
|
||||
premium_amount NUMERIC(15,2) NOT NULL,
|
||||
company_rate NUMERIC(7,4) NOT NULL,
|
||||
payout_rate NUMERIC(7,4) NOT NULL,
|
||||
commission_year INT NOT NULL,
|
||||
company_payment NUMERIC(15,2) NOT NULL,
|
||||
agent_commission NUMERIC(15,2) NOT NULL,
|
||||
withholding_tax NUMERIC(15,2) NOT NULL,
|
||||
net_amount NUMERIC(15,2) NOT NULL,
|
||||
expected_chargeback NUMERIC(15,2),
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
created_by BIGINT
|
||||
);
|
||||
|
||||
COMMENT ON TABLE commission_simulation IS '수수료 시뮬레이터 — 시나리오별 수수료 산출 결과 저장';
|
||||
COMMENT ON COLUMN commission_simulation.sim_name IS '시나리오명';
|
||||
COMMENT ON COLUMN commission_simulation.premium_amount IS '입력 보험료';
|
||||
COMMENT ON COLUMN commission_simulation.company_rate IS '보험사 요율 (소수)';
|
||||
COMMENT ON COLUMN commission_simulation.payout_rate IS '지급률 (소수)';
|
||||
COMMENT ON COLUMN commission_simulation.commission_year IS '회차';
|
||||
COMMENT ON COLUMN commission_simulation.company_payment IS '보험사 지급액 = 보험료 × company_rate';
|
||||
COMMENT ON COLUMN commission_simulation.agent_commission IS '설계사 수수료 = 보험사지급액 × payout_rate';
|
||||
COMMENT ON COLUMN commission_simulation.withholding_tax IS '원천세 = 설계사수수료 × 3.3%';
|
||||
COMMENT ON COLUMN commission_simulation.net_amount IS '실지급 = 설계사수수료 - 원천세';
|
||||
COMMENT ON COLUMN commission_simulation.expected_chargeback IS '예상 환수액 (13개월 50% 등 시나리오)';
|
||||
COMMENT ON COLUMN commission_simulation.created_by IS '시뮬레이션 요청자 FK(users)';
|
||||
|
||||
CREATE INDEX idx_simu_created_by ON commission_simulation(created_by, created_at);
|
||||
CREATE INDEX idx_simu_created_at ON commission_simulation(created_at DESC);
|
||||
@@ -0,0 +1,153 @@
|
||||
-- V104: incar 갭 운영 도메인 7개 PAGE 메뉴 등록 (V95 패턴 동일)
|
||||
-- GRP_SETTLE 하위: STEP_CLOSE, HQ_SETTLE
|
||||
-- GRP_ORG 하위: GRADE_EVAL, GUARANTEE_INS
|
||||
-- GRP_PRD 하위: JOINT_CONTRACT
|
||||
-- GRP_COMMISSION 하위: RESERVE_FUND, COMM_SIMULATOR
|
||||
--
|
||||
-- 권한:
|
||||
-- STEP_CLOSE → READ/UPDATE/APPROVE
|
||||
-- 나머지 6개 → READ/CREATE/UPDATE/DELETE/EXPORT
|
||||
-- 역할:
|
||||
-- SUPER_ADMIN/ADMIN → 전체 권한
|
||||
-- MANAGER → READ/CREATE/UPDATE/EXPORT (+ APPROVE for STEP_CLOSE, DELETE 제외)
|
||||
-- AGENT → 본인 관련 도메인 READ (RESERVE_FUND, GUARANTEE_INS, GRADE_EVAL)
|
||||
|
||||
-- ============================================================
|
||||
-- 1. GRP_SETTLE 하위: STEP_CLOSE, HQ_SETTLE
|
||||
-- ============================================================
|
||||
|
||||
INSERT INTO menu (parent_menu_id, menu_code, menu_name, menu_type, menu_path, component_path, menu_level, sort_order)
|
||||
SELECT p.menu_id, x.code, x.name, 'PAGE', x.path, x.cmpt, 2, x.sort
|
||||
FROM menu p
|
||||
JOIN (VALUES
|
||||
('STEP_CLOSE', '단계마감', '/settle/step-close', 'settle/StepClose', 90),
|
||||
('HQ_SETTLE', '본사정산', '/settle/hq-settle', 'settle/HqSettle', 100)
|
||||
) AS x(code, name, path, cmpt, sort)
|
||||
ON p.menu_code = 'GRP_SETTLE'
|
||||
WHERE NOT EXISTS (SELECT 1 FROM menu WHERE menu_code = x.code);
|
||||
|
||||
-- ============================================================
|
||||
-- 2. GRP_ORG 하위: GRADE_EVAL, GUARANTEE_INS
|
||||
-- ============================================================
|
||||
|
||||
INSERT INTO menu (parent_menu_id, menu_code, menu_name, menu_type, menu_path, component_path, menu_level, sort_order)
|
||||
SELECT p.menu_id, x.code, x.name, 'PAGE', x.path, x.cmpt, 2, x.sort
|
||||
FROM menu p
|
||||
JOIN (VALUES
|
||||
('GRADE_EVAL', '등급평가', '/org/grade-eval', 'org/GradeEvaluation', 60),
|
||||
('GUARANTEE_INS', '보증보험', '/org/guarantee', 'org/GuaranteeInsurance', 70)
|
||||
) AS x(code, name, path, cmpt, sort)
|
||||
ON p.menu_code = 'GRP_ORG'
|
||||
WHERE NOT EXISTS (SELECT 1 FROM menu WHERE menu_code = x.code);
|
||||
|
||||
-- ============================================================
|
||||
-- 3. GRP_PRD 하위: JOINT_CONTRACT
|
||||
-- ============================================================
|
||||
|
||||
INSERT INTO menu (parent_menu_id, menu_code, menu_name, menu_type, menu_path, component_path, menu_level, sort_order)
|
||||
SELECT p.menu_id, x.code, x.name, 'PAGE', x.path, x.cmpt, 2, x.sort
|
||||
FROM menu p
|
||||
JOIN (VALUES
|
||||
('JOINT_CONTRACT', '공동계약', '/contracts/joint', 'contracts/JointContract', 60)
|
||||
) AS x(code, name, path, cmpt, sort)
|
||||
ON p.menu_code = 'GRP_PRD'
|
||||
WHERE NOT EXISTS (SELECT 1 FROM menu WHERE menu_code = x.code);
|
||||
|
||||
-- ============================================================
|
||||
-- 4. GRP_COMMISSION 하위: RESERVE_FUND, COMM_SIMULATOR
|
||||
-- ============================================================
|
||||
|
||||
INSERT INTO menu (parent_menu_id, menu_code, menu_name, menu_type, menu_path, component_path, menu_level, sort_order)
|
||||
SELECT p.menu_id, x.code, x.name, 'PAGE', x.path, x.cmpt, 2, x.sort
|
||||
FROM menu p
|
||||
JOIN (VALUES
|
||||
('RESERVE_FUND', '적립금', '/commission/reserve', 'commission/ReserveFund', 150),
|
||||
('COMM_SIMULATOR', '수수료 시뮬레이터', '/commission/simulator', 'commission/CommissionSimulator', 160)
|
||||
) AS x(code, name, path, cmpt, sort)
|
||||
ON p.menu_code = 'GRP_COMMISSION'
|
||||
WHERE NOT EXISTS (SELECT 1 FROM menu WHERE menu_code = x.code);
|
||||
|
||||
-- ============================================================
|
||||
-- 5. STEP_CLOSE 권한 등록 (READ/UPDATE/APPROVE)
|
||||
-- ============================================================
|
||||
|
||||
INSERT INTO menu_permission (menu_id, perm_code, perm_name, sort_order)
|
||||
SELECT m.menu_id, p.code, p.name, p.sort
|
||||
FROM menu m
|
||||
CROSS JOIN (VALUES
|
||||
('READ', '조회', 10),
|
||||
('UPDATE', '수정', 30),
|
||||
('APPROVE', '승인', 50)
|
||||
) AS p(code, name, sort)
|
||||
WHERE m.menu_code = 'STEP_CLOSE'
|
||||
ON CONFLICT (menu_id, perm_code) DO NOTHING;
|
||||
|
||||
-- ============================================================
|
||||
-- 6. 나머지 6개 메뉴 권한 등록 (READ/CREATE/UPDATE/DELETE/EXPORT)
|
||||
-- ============================================================
|
||||
|
||||
INSERT INTO menu_permission (menu_id, perm_code, perm_name, sort_order)
|
||||
SELECT m.menu_id, p.code, p.name, p.sort
|
||||
FROM menu m
|
||||
CROSS JOIN (VALUES
|
||||
('READ', '조회', 10),
|
||||
('CREATE', '등록', 20),
|
||||
('UPDATE', '수정', 30),
|
||||
('DELETE', '삭제', 40),
|
||||
('EXPORT', '엑셀', 60)
|
||||
) AS p(code, name, sort)
|
||||
WHERE m.menu_code IN ('HQ_SETTLE','GRADE_EVAL','JOINT_CONTRACT','GUARANTEE_INS','RESERVE_FUND','COMM_SIMULATOR')
|
||||
ON CONFLICT (menu_id, perm_code) DO NOTHING;
|
||||
|
||||
-- ============================================================
|
||||
-- 7. SUPER_ADMIN / ADMIN — 7개 메뉴 전체 권한
|
||||
-- ============================================================
|
||||
|
||||
INSERT INTO role_menu_permission (role_id, menu_id, perm_code, is_granted)
|
||||
SELECT r.role_id, mp.menu_id, mp.perm_code, 'Y'
|
||||
FROM role r
|
||||
CROSS JOIN menu_permission mp
|
||||
JOIN menu m ON m.menu_id = mp.menu_id
|
||||
WHERE r.role_code IN ('SUPER_ADMIN','ADMIN')
|
||||
AND m.menu_code IN ('STEP_CLOSE','HQ_SETTLE','GRADE_EVAL','JOINT_CONTRACT','GUARANTEE_INS','RESERVE_FUND','COMM_SIMULATOR')
|
||||
ON CONFLICT (role_id, menu_id, perm_code) DO NOTHING;
|
||||
|
||||
-- ============================================================
|
||||
-- 8. MANAGER — READ/CREATE/UPDATE/EXPORT + APPROVE(STEP_CLOSE)
|
||||
-- ============================================================
|
||||
|
||||
-- STEP_CLOSE: READ/UPDATE/APPROVE
|
||||
INSERT INTO role_menu_permission (role_id, menu_id, perm_code, is_granted)
|
||||
SELECT r.role_id, mp.menu_id, mp.perm_code, 'Y'
|
||||
FROM role r
|
||||
CROSS JOIN menu_permission mp
|
||||
JOIN menu m ON m.menu_id = mp.menu_id
|
||||
WHERE r.role_code = 'MANAGER'
|
||||
AND mp.perm_code IN ('READ','UPDATE','APPROVE')
|
||||
AND m.menu_code = 'STEP_CLOSE'
|
||||
ON CONFLICT (role_id, menu_id, perm_code) DO NOTHING;
|
||||
|
||||
-- 나머지 6개: READ/CREATE/UPDATE/EXPORT (DELETE 제외)
|
||||
INSERT INTO role_menu_permission (role_id, menu_id, perm_code, is_granted)
|
||||
SELECT r.role_id, mp.menu_id, mp.perm_code, 'Y'
|
||||
FROM role r
|
||||
CROSS JOIN menu_permission mp
|
||||
JOIN menu m ON m.menu_id = mp.menu_id
|
||||
WHERE r.role_code = 'MANAGER'
|
||||
AND mp.perm_code IN ('READ','CREATE','UPDATE','EXPORT')
|
||||
AND m.menu_code IN ('HQ_SETTLE','GRADE_EVAL','JOINT_CONTRACT','GUARANTEE_INS','RESERVE_FUND','COMM_SIMULATOR')
|
||||
ON CONFLICT (role_id, menu_id, perm_code) DO NOTHING;
|
||||
|
||||
-- ============================================================
|
||||
-- 9. AGENT — 본인 관련 도메인 READ (RESERVE_FUND, GUARANTEE_INS, GRADE_EVAL)
|
||||
-- ============================================================
|
||||
|
||||
INSERT INTO role_menu_permission (role_id, menu_id, perm_code, is_granted)
|
||||
SELECT r.role_id, mp.menu_id, mp.perm_code, 'Y'
|
||||
FROM role r
|
||||
CROSS JOIN menu_permission mp
|
||||
JOIN menu m ON m.menu_id = mp.menu_id
|
||||
WHERE r.role_code = 'AGENT'
|
||||
AND mp.perm_code = 'READ'
|
||||
AND m.menu_code IN ('RESERVE_FUND','GUARANTEE_INS','GRADE_EVAL')
|
||||
ON CONFLICT (role_id, menu_id, perm_code) DO NOTHING;
|
||||
@@ -0,0 +1,37 @@
|
||||
-- V105: incar 갭 운영 도메인 공통코드 6종 (V96 패턴 동일)
|
||||
-- STEP_CODE / STEP_STATUS / HQ_ITEM_TYPE / JOINT_ROLE / GUARANTEE_STATUS / GRADE_EVAL_STATUS
|
||||
|
||||
-- ===== 그룹 =====
|
||||
INSERT INTO common_code_group (group_code, group_name, description, is_system, sort_order) VALUES
|
||||
('STEP_CODE', '단계코드', 'RECEIVE=수신마감/CALC=계산마감/VERIFY=검증마감/PAY=지급마감', 'Y', 490),
|
||||
('STEP_STATUS', '단계상태', 'OPEN=열림/CLOSED=마감', 'Y', 500),
|
||||
('HQ_ITEM_TYPE', '본사정산유형', 'INCOME=수입/EXPENSE=비용', 'Y', 510),
|
||||
('JOINT_ROLE', '공동계약역할', 'LEAD=주설계사/SUB=공동설계사', 'Y', 520),
|
||||
('GUARANTEE_STATUS', '보증보험상태', 'ACTIVE=유효/EXPIRED=만료/CANCELLED=해지', 'Y', 530),
|
||||
('GRADE_EVAL_STATUS', '등급평가상태', 'EVALUATED=평가완료/APPLIED=반영완료', 'Y', 540)
|
||||
ON CONFLICT (group_code) DO NOTHING;
|
||||
|
||||
-- ===== 상세 =====
|
||||
INSERT INTO common_code (group_code, code, code_name, sort_order) VALUES
|
||||
-- STEP_CODE
|
||||
('STEP_CODE', 'RECEIVE', '수신마감', 10),
|
||||
('STEP_CODE', 'CALC', '계산마감', 20),
|
||||
('STEP_CODE', 'VERIFY', '검증마감', 30),
|
||||
('STEP_CODE', 'PAY', '지급마감', 40),
|
||||
-- STEP_STATUS
|
||||
('STEP_STATUS', 'OPEN', '열림', 10),
|
||||
('STEP_STATUS', 'CLOSED', '마감', 20),
|
||||
-- HQ_ITEM_TYPE
|
||||
('HQ_ITEM_TYPE', 'INCOME', '수입', 10),
|
||||
('HQ_ITEM_TYPE', 'EXPENSE', '비용', 20),
|
||||
-- JOINT_ROLE
|
||||
('JOINT_ROLE', 'LEAD', '주설계사', 10),
|
||||
('JOINT_ROLE', 'SUB', '공동설계사', 20),
|
||||
-- GUARANTEE_STATUS
|
||||
('GUARANTEE_STATUS', 'ACTIVE', '유효', 10),
|
||||
('GUARANTEE_STATUS', 'EXPIRED', '만료', 20),
|
||||
('GUARANTEE_STATUS', 'CANCELLED', '해지', 30),
|
||||
-- GRADE_EVAL_STATUS
|
||||
('GRADE_EVAL_STATUS', 'EVALUATED', '평가완료', 10),
|
||||
('GRADE_EVAL_STATUS', 'APPLIED', '반영완료', 20)
|
||||
ON CONFLICT (group_code, code) DO NOTHING;
|
||||
@@ -0,0 +1,9 @@
|
||||
-- V106: settle_master에 기타수수료 합계 컬럼 추가
|
||||
-- 설계사 지급성 9종(수금·갱신·부활·유지보너스·소개·정착지원·시상·생보운영·지점장수당) 집계용
|
||||
-- 기존 행은 DEFAULT 0으로 백필되므로 기존 정산 결과 불변.
|
||||
|
||||
ALTER TABLE settle_master
|
||||
ADD COLUMN other_commission_total NUMERIC(15,2) NOT NULL DEFAULT 0;
|
||||
|
||||
COMMENT ON COLUMN settle_master.other_commission_total
|
||||
IS '기타수수료 합계(수금+갱신+부활+유지보너스+소개+정착지원+시상+생보운영+지점장수당, status!=CANCELLED)';
|
||||
@@ -0,0 +1,81 @@
|
||||
-- V88: 수입수수료 + 수지차 (income_commission_ledger + margin_summary)
|
||||
-- 보험사가 GA에 지급한 수입 측 원장. 수지차 = 수입 합계 - 지급 합계(settle_master 기준).
|
||||
--
|
||||
-- settle_master 지급 합계 집계 컬럼 (확인 완료):
|
||||
-- settle_master.agent_id : 설계사 FK (BIGINT)
|
||||
-- settle_master.settle_month : 정산월 CHAR(6)
|
||||
-- settle_master.net_amount : 설계사 실지급액 (세후) NUMERIC
|
||||
-- settle_master.gross_amount : 설계사 총지급액 (세전) NUMERIC
|
||||
-- payout_total 산출 시 net_amount 합산 권장 (tax_amount 차감 후 기준)
|
||||
|
||||
-- ============================================================
|
||||
-- 1. income_commission_ledger — 수입수수료 원장
|
||||
-- 보험사 → GA 수입 수수료. 계약 단위로 발생.
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE income_commission_ledger (
|
||||
income_id BIGSERIAL PRIMARY KEY,
|
||||
contract_id BIGINT NOT NULL REFERENCES contract(contract_id),
|
||||
carrier_id BIGINT NOT NULL REFERENCES insurance_company(company_id),
|
||||
settle_month CHAR(6) NOT NULL,
|
||||
premium_amount NUMERIC(15,2) NOT NULL,
|
||||
company_rate NUMERIC(7,4) NOT NULL,
|
||||
income_amount NUMERIC(15,2) NOT NULL,
|
||||
commission_type VARCHAR(20) NOT NULL,
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'CALCULATED',
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP,
|
||||
CHECK (commission_type IN ('RECRUIT','MAINTAIN','RENEWAL','REINSTATE','COLLECTION')),
|
||||
CHECK (status IN ('CALCULATED','CONFIRMED','CANCELLED')),
|
||||
CHECK (company_rate >= 0 AND company_rate <= 1),
|
||||
CHECK (premium_amount >= 0),
|
||||
CHECK (income_amount >= 0)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE income_commission_ledger IS '수입수수료 원장 — 보험사가 GA에 지급한 수입 수수료 (계약 단위)';
|
||||
COMMENT ON COLUMN income_commission_ledger.income_id IS '수입수수료 원장 PK';
|
||||
COMMENT ON COLUMN income_commission_ledger.contract_id IS '대상 계약 FK';
|
||||
COMMENT ON COLUMN income_commission_ledger.carrier_id IS '보험사 FK (insurance_company.company_id)';
|
||||
COMMENT ON COLUMN income_commission_ledger.settle_month IS '정산 귀속월 (YYYYMM)';
|
||||
COMMENT ON COLUMN income_commission_ledger.premium_amount IS '보험료';
|
||||
COMMENT ON COLUMN income_commission_ledger.company_rate IS '보험사 수수료율 (소수, 예: 0.12 = 12%)';
|
||||
COMMENT ON COLUMN income_commission_ledger.income_amount IS '수입수수료 = premium_amount × company_rate';
|
||||
COMMENT ON COLUMN income_commission_ledger.commission_type IS '수수료 유형 (RECRUIT/MAINTAIN/RENEWAL/REINSTATE/COLLECTION)';
|
||||
COMMENT ON COLUMN income_commission_ledger.status IS '상태 (CALCULATED/CONFIRMED/CANCELLED)';
|
||||
|
||||
CREATE INDEX idx_icl_carrier_month ON income_commission_ledger(carrier_id, settle_month);
|
||||
CREATE INDEX idx_icl_contract ON income_commission_ledger(contract_id);
|
||||
CREATE INDEX idx_icl_status_month ON income_commission_ledger(status, settle_month);
|
||||
|
||||
-- ============================================================
|
||||
-- 2. margin_summary — 수지차 집계
|
||||
-- 수지차 recalc 배치 액션으로 채움.
|
||||
-- agent_id NULL = 전체(GA 총 수지차).
|
||||
-- payout_total: settle_master.net_amount SUM (설계사별 세후 실지급액 기준)
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE margin_summary (
|
||||
margin_id BIGSERIAL PRIMARY KEY,
|
||||
settle_month CHAR(6) NOT NULL,
|
||||
agent_id BIGINT REFERENCES agent(agent_id),
|
||||
income_total NUMERIC(15,2) NOT NULL DEFAULT 0,
|
||||
payout_total NUMERIC(15,2) NOT NULL DEFAULT 0,
|
||||
margin_amount NUMERIC(15,2) NOT NULL DEFAULT 0,
|
||||
margin_rate NUMERIC(7,4),
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
UNIQUE (settle_month, agent_id),
|
||||
CHECK (income_total >= 0),
|
||||
CHECK (payout_total >= 0)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE margin_summary IS '수지차 집계 — settle_month + agent_id 단위 수입/지급 대비 수지차';
|
||||
COMMENT ON COLUMN margin_summary.margin_id IS '수지차 집계 PK';
|
||||
COMMENT ON COLUMN margin_summary.settle_month IS '정산월 (YYYYMM)';
|
||||
COMMENT ON COLUMN margin_summary.agent_id IS '설계사 FK (NULL = GA 전체 합산)';
|
||||
COMMENT ON COLUMN margin_summary.income_total IS '수입 합계 (income_commission_ledger SUM)';
|
||||
COMMENT ON COLUMN margin_summary.payout_total IS '지급 합계 (settle_master.net_amount SUM — 설계사 세후 실지급액)';
|
||||
COMMENT ON COLUMN margin_summary.margin_amount IS '수지차 = income_total - payout_total';
|
||||
COMMENT ON COLUMN margin_summary.margin_rate IS '수지차율 = margin_amount / income_total (income_total > 0 일 때)';
|
||||
|
||||
CREATE INDEX idx_ms_month ON margin_summary(settle_month);
|
||||
CREATE INDEX idx_ms_agent_month ON margin_summary(agent_id, settle_month);
|
||||
@@ -0,0 +1,72 @@
|
||||
-- V89: 소개·이관수수료 (referral_commission_rule + referral_commission_ledger)
|
||||
-- 계약을 소개(REFERRAL)하거나 이관(TRANSFER)한 설계사에게 지급하는 수수료.
|
||||
-- 정률(RATE): commission_amount = base_amount × commission_rate
|
||||
-- 정액(FIXED): commission_amount = fixed_amount
|
||||
|
||||
-- ============================================================
|
||||
-- 1. referral_commission_rule — 소개·이관수수료 룰 마스터
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE referral_commission_rule (
|
||||
rule_id BIGSERIAL PRIMARY KEY,
|
||||
referral_type VARCHAR(20) NOT NULL,
|
||||
calc_type VARCHAR(10) NOT NULL,
|
||||
commission_rate NUMERIC(7,4),
|
||||
fixed_amount NUMERIC(15,2),
|
||||
effective_from DATE NOT NULL,
|
||||
effective_to DATE,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
created_by BIGINT,
|
||||
updated_at TIMESTAMP,
|
||||
CHECK (referral_type IN ('REFERRAL','TRANSFER')),
|
||||
CHECK (calc_type IN ('RATE','FIXED')),
|
||||
CHECK (commission_rate IS NULL OR (commission_rate >= 0 AND commission_rate <= 1)),
|
||||
CHECK (fixed_amount IS NULL OR fixed_amount >= 0)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE referral_commission_rule IS '소개·이관수수료 룰 마스터 — 소개/이관 유형별 수수료율 또는 정액 정의';
|
||||
COMMENT ON COLUMN referral_commission_rule.rule_id IS '룰 PK';
|
||||
COMMENT ON COLUMN referral_commission_rule.referral_type IS '유형 (REFERRAL=소개 / TRANSFER=이관)';
|
||||
COMMENT ON COLUMN referral_commission_rule.calc_type IS '계산방식 (RATE=정률 / FIXED=정액)';
|
||||
COMMENT ON COLUMN referral_commission_rule.commission_rate IS '정률 요율 (소수, calc_type=RATE 일 때 사용)';
|
||||
COMMENT ON COLUMN referral_commission_rule.fixed_amount IS '정액 금액 (calc_type=FIXED 일 때 사용)';
|
||||
COMMENT ON COLUMN referral_commission_rule.effective_from IS '적용 시작일';
|
||||
COMMENT ON COLUMN referral_commission_rule.effective_to IS '적용 종료일 (NULL=현재 유효)';
|
||||
|
||||
CREATE INDEX idx_rfcr_type_from ON referral_commission_rule(referral_type, effective_from);
|
||||
|
||||
-- ============================================================
|
||||
-- 2. referral_commission_ledger — 소개·이관수수료 원장
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE referral_commission_ledger (
|
||||
ledger_id BIGSERIAL PRIMARY KEY,
|
||||
contract_id BIGINT NOT NULL REFERENCES contract(contract_id),
|
||||
referrer_agent_id BIGINT NOT NULL REFERENCES agent(agent_id),
|
||||
settle_month CHAR(6) NOT NULL,
|
||||
referral_type VARCHAR(20) NOT NULL,
|
||||
base_amount NUMERIC(15,2) NOT NULL,
|
||||
commission_rate NUMERIC(7,4),
|
||||
commission_amount NUMERIC(15,2) NOT NULL,
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'CALCULATED',
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP,
|
||||
CHECK (referral_type IN ('REFERRAL','TRANSFER')),
|
||||
CHECK (status IN ('CALCULATED','CONFIRMED','CANCELLED')),
|
||||
CHECK (base_amount >= 0),
|
||||
CHECK (commission_amount >= 0)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE referral_commission_ledger IS '소개·이관수수료 원장 — 소개/이관 설계사에게 지급하는 수수료 발생 이력';
|
||||
COMMENT ON COLUMN referral_commission_ledger.ledger_id IS '원장 PK';
|
||||
COMMENT ON COLUMN referral_commission_ledger.contract_id IS '대상 계약 FK';
|
||||
COMMENT ON COLUMN referral_commission_ledger.referrer_agent_id IS '소개/이관한 설계사 FK';
|
||||
COMMENT ON COLUMN referral_commission_ledger.settle_month IS '정산 귀속월 (YYYYMM)';
|
||||
COMMENT ON COLUMN referral_commission_ledger.referral_type IS '유형 (REFERRAL=소개 / TRANSFER=이관)';
|
||||
COMMENT ON COLUMN referral_commission_ledger.base_amount IS '기준금액 (수수료 또는 보험료)';
|
||||
COMMENT ON COLUMN referral_commission_ledger.commission_rate IS '적용 정률 요율 (RATE 방식 시)';
|
||||
COMMENT ON COLUMN referral_commission_ledger.commission_amount IS '수수료액 = base×rate (RATE) 또는 fixed_amount (FIXED)';
|
||||
COMMENT ON COLUMN referral_commission_ledger.status IS '상태 (CALCULATED/CONFIRMED/CANCELLED)';
|
||||
|
||||
CREATE INDEX idx_rfcl_agent_month ON referral_commission_ledger(referrer_agent_id, settle_month);
|
||||
CREATE INDEX idx_rfcl_contract ON referral_commission_ledger(contract_id);
|
||||
@@ -0,0 +1,65 @@
|
||||
-- V90: 정착지원금 (settling_support_rule + settling_support_ledger)
|
||||
-- 신입/경력 설계사 정착 지원금. 등급별 정액 월 지급 + 조기 해촉 시 환수.
|
||||
-- 공식: net_amount = support_amount - clawback_amount
|
||||
-- 환수: 조기해촉 시 clawback_amount = support_amount × (잔여약정개월 / 약정개월)
|
||||
|
||||
-- ============================================================
|
||||
-- 1. settling_support_rule — 정착지원금 룰 마스터
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE settling_support_rule (
|
||||
rule_id BIGSERIAL PRIMARY KEY,
|
||||
agent_grade VARCHAR(20) NOT NULL,
|
||||
support_amount NUMERIC(15,2) NOT NULL,
|
||||
duration_months INT NOT NULL,
|
||||
clawback_months INT NOT NULL,
|
||||
effective_from DATE NOT NULL,
|
||||
effective_to DATE,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
created_by BIGINT,
|
||||
updated_at TIMESTAMP,
|
||||
CHECK (support_amount >= 0),
|
||||
CHECK (duration_months > 0),
|
||||
CHECK (clawback_months >= 0)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE settling_support_rule IS '정착지원금 룰 마스터 — 설계사 등급별 월 정착지원금 및 약정 기간 정의';
|
||||
COMMENT ON COLUMN settling_support_rule.rule_id IS '룰 PK';
|
||||
COMMENT ON COLUMN settling_support_rule.agent_grade IS '설계사 등급';
|
||||
COMMENT ON COLUMN settling_support_rule.support_amount IS '월 정액 지원금';
|
||||
COMMENT ON COLUMN settling_support_rule.duration_months IS '약정 지원 개월수';
|
||||
COMMENT ON COLUMN settling_support_rule.clawback_months IS '이 개월 내 해촉 시 환수 대상 (0=환수 없음)';
|
||||
COMMENT ON COLUMN settling_support_rule.effective_from IS '적용 시작일';
|
||||
COMMENT ON COLUMN settling_support_rule.effective_to IS '적용 종료일 (NULL=현재 유효)';
|
||||
|
||||
CREATE INDEX idx_ssr_grade_from ON settling_support_rule(agent_grade, effective_from);
|
||||
|
||||
-- ============================================================
|
||||
-- 2. settling_support_ledger — 정착지원금 원장
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE settling_support_ledger (
|
||||
ledger_id BIGSERIAL PRIMARY KEY,
|
||||
agent_id BIGINT NOT NULL REFERENCES agent(agent_id),
|
||||
settle_month CHAR(6) NOT NULL,
|
||||
support_amount NUMERIC(15,2) NOT NULL,
|
||||
clawback_amount NUMERIC(15,2) NOT NULL DEFAULT 0,
|
||||
net_amount NUMERIC(15,2) NOT NULL,
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'CALCULATED',
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP,
|
||||
CHECK (status IN ('CALCULATED','PAID','CLAWED_BACK','CANCELLED')),
|
||||
CHECK (support_amount >= 0),
|
||||
CHECK (clawback_amount >= 0)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE settling_support_ledger IS '정착지원금 원장 — 설계사별 월 정착지원금 지급/환수 이력';
|
||||
COMMENT ON COLUMN settling_support_ledger.ledger_id IS '원장 PK';
|
||||
COMMENT ON COLUMN settling_support_ledger.agent_id IS '대상 설계사 FK';
|
||||
COMMENT ON COLUMN settling_support_ledger.settle_month IS '정산 귀속월 (YYYYMM)';
|
||||
COMMENT ON COLUMN settling_support_ledger.support_amount IS '지급액 (등급별 support_amount)';
|
||||
COMMENT ON COLUMN settling_support_ledger.clawback_amount IS '환수액 = support_amount × (잔여약정개월 / 약정개월) (조기해촉 시)';
|
||||
COMMENT ON COLUMN settling_support_ledger.net_amount IS '실지급액 = support_amount - clawback_amount';
|
||||
COMMENT ON COLUMN settling_support_ledger.status IS '상태 (CALCULATED/PAID/CLAWED_BACK/CANCELLED)';
|
||||
|
||||
CREATE INDEX idx_ssl_agent_month ON settling_support_ledger(agent_id, settle_month);
|
||||
@@ -0,0 +1,70 @@
|
||||
-- V91: 인정실적 + 환산보험료 (product_conversion_rate + recognized_performance_ledger)
|
||||
-- 환산보험료 = 보험료 × 상품별 환산율.
|
||||
-- 인정실적 = 환산보험료 × 인정율.
|
||||
-- 시상/등급 산정의 기초 데이터.
|
||||
|
||||
-- ============================================================
|
||||
-- 1. product_conversion_rate — 상품별 환산율 마스터
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE product_conversion_rate (
|
||||
rate_id BIGSERIAL PRIMARY KEY,
|
||||
product_id BIGINT NOT NULL REFERENCES product(product_id),
|
||||
conversion_rate NUMERIC(7,4) NOT NULL,
|
||||
effective_from DATE NOT NULL,
|
||||
effective_to DATE,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
created_by BIGINT,
|
||||
updated_at TIMESTAMP,
|
||||
UNIQUE (product_id, effective_from),
|
||||
CHECK (conversion_rate >= 0 AND conversion_rate <= 1)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE product_conversion_rate IS '상품별 환산율 마스터 — 인정실적 산출을 위한 보험료 환산율';
|
||||
COMMENT ON COLUMN product_conversion_rate.rate_id IS '환산율 PK';
|
||||
COMMENT ON COLUMN product_conversion_rate.product_id IS '적용 상품 FK';
|
||||
COMMENT ON COLUMN product_conversion_rate.conversion_rate IS '환산율 (소수, 예: 0.5 = 50%)';
|
||||
COMMENT ON COLUMN product_conversion_rate.effective_from IS '적용 시작일';
|
||||
COMMENT ON COLUMN product_conversion_rate.effective_to IS '적용 종료일 (NULL=현재 유효)';
|
||||
|
||||
CREATE INDEX idx_pcr_product_from ON product_conversion_rate(product_id, effective_from);
|
||||
|
||||
-- ============================================================
|
||||
-- 2. recognized_performance_ledger — 인정실적 원장
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE recognized_performance_ledger (
|
||||
ledger_id BIGSERIAL PRIMARY KEY,
|
||||
agent_id BIGINT NOT NULL REFERENCES agent(agent_id),
|
||||
contract_id BIGINT NOT NULL REFERENCES contract(contract_id),
|
||||
settle_month CHAR(6) NOT NULL,
|
||||
premium_amount NUMERIC(15,2) NOT NULL,
|
||||
conversion_rate NUMERIC(7,4) NOT NULL,
|
||||
converted_premium NUMERIC(15,2) NOT NULL,
|
||||
recognition_rate NUMERIC(7,4) NOT NULL,
|
||||
recognized_amount NUMERIC(15,2) NOT NULL,
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'CALCULATED',
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP,
|
||||
CHECK (status IN ('CALCULATED','CONFIRMED','CANCELLED')),
|
||||
CHECK (premium_amount >= 0),
|
||||
CHECK (conversion_rate >= 0 AND conversion_rate <= 1),
|
||||
CHECK (converted_premium >= 0),
|
||||
CHECK (recognition_rate >= 0 AND recognition_rate <= 1),
|
||||
CHECK (recognized_amount >= 0)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE recognized_performance_ledger IS '인정실적 원장 — 설계사별 계약 단위 환산보험료 및 인정실적';
|
||||
COMMENT ON COLUMN recognized_performance_ledger.ledger_id IS '원장 PK';
|
||||
COMMENT ON COLUMN recognized_performance_ledger.agent_id IS '설계사 FK';
|
||||
COMMENT ON COLUMN recognized_performance_ledger.contract_id IS '대상 계약 FK';
|
||||
COMMENT ON COLUMN recognized_performance_ledger.settle_month IS '정산 귀속월 (YYYYMM)';
|
||||
COMMENT ON COLUMN recognized_performance_ledger.premium_amount IS '보험료';
|
||||
COMMENT ON COLUMN recognized_performance_ledger.conversion_rate IS '적용 환산율 (product_conversion_rate 스냅샷)';
|
||||
COMMENT ON COLUMN recognized_performance_ledger.converted_premium IS '환산보험료 = premium_amount × conversion_rate';
|
||||
COMMENT ON COLUMN recognized_performance_ledger.recognition_rate IS '인정율 (설계사 등급/정책 기준)';
|
||||
COMMENT ON COLUMN recognized_performance_ledger.recognized_amount IS '인정실적 = converted_premium × recognition_rate';
|
||||
COMMENT ON COLUMN recognized_performance_ledger.status IS '상태 (CALCULATED/CONFIRMED/CANCELLED)';
|
||||
|
||||
CREATE INDEX idx_rpl_agent_month ON recognized_performance_ledger(agent_id, settle_month);
|
||||
CREATE INDEX idx_rpl_contract ON recognized_performance_ledger(contract_id);
|
||||
@@ -0,0 +1,71 @@
|
||||
-- V92: 시상 (award_rule + award_ledger)
|
||||
-- 신인도입/법인/유지율/실적 등 시상. 조건 충족 시 정률 또는 정액 지급.
|
||||
-- 정률(RATE): award_amount = base_amount × award_rate
|
||||
-- 정액(FIXED): award_amount = award_fixed
|
||||
|
||||
-- ============================================================
|
||||
-- 1. award_rule — 시상 룰 마스터
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE award_rule (
|
||||
rule_id BIGSERIAL PRIMARY KEY,
|
||||
award_type VARCHAR(20) NOT NULL,
|
||||
calc_type VARCHAR(10) NOT NULL,
|
||||
threshold_amount NUMERIC(15,2),
|
||||
award_rate NUMERIC(7,4),
|
||||
award_fixed NUMERIC(15,2),
|
||||
effective_from DATE NOT NULL,
|
||||
effective_to DATE,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
created_by BIGINT,
|
||||
updated_at TIMESTAMP,
|
||||
CHECK (award_type IN ('NEW_RECRUIT','CORPORATE','RETENTION','PERFORMANCE')),
|
||||
CHECK (calc_type IN ('RATE','FIXED')),
|
||||
CHECK (award_rate IS NULL OR (award_rate >= 0 AND award_rate <= 1)),
|
||||
CHECK (award_fixed IS NULL OR award_fixed >= 0),
|
||||
CHECK (threshold_amount IS NULL OR threshold_amount >= 0)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE award_rule IS '시상 룰 마스터 — 시상 유형별 조건 및 지급 기준';
|
||||
COMMENT ON COLUMN award_rule.rule_id IS '룰 PK';
|
||||
COMMENT ON COLUMN award_rule.award_type IS '시상 유형 (NEW_RECRUIT=신인도입/CORPORATE=법인/RETENTION=유지율/PERFORMANCE=실적)';
|
||||
COMMENT ON COLUMN award_rule.calc_type IS '계산방식 (RATE=정률 / FIXED=정액)';
|
||||
COMMENT ON COLUMN award_rule.threshold_amount IS '충족 기준 실적 (이상 시 시상 발생, NULL=조건 없음)';
|
||||
COMMENT ON COLUMN award_rule.award_rate IS '정률 시상율 (소수, calc_type=RATE 일 때 사용)';
|
||||
COMMENT ON COLUMN award_rule.award_fixed IS '정액 시상금 (calc_type=FIXED 일 때 사용)';
|
||||
COMMENT ON COLUMN award_rule.effective_from IS '적용 시작일';
|
||||
COMMENT ON COLUMN award_rule.effective_to IS '적용 종료일 (NULL=현재 유효)';
|
||||
|
||||
CREATE INDEX idx_awdr_type_from ON award_rule(award_type, effective_from);
|
||||
|
||||
-- ============================================================
|
||||
-- 2. award_ledger — 시상 원장
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE award_ledger (
|
||||
ledger_id BIGSERIAL PRIMARY KEY,
|
||||
agent_id BIGINT NOT NULL REFERENCES agent(agent_id),
|
||||
settle_month CHAR(6) NOT NULL,
|
||||
award_type VARCHAR(20) NOT NULL,
|
||||
base_amount NUMERIC(15,2) NOT NULL,
|
||||
award_amount NUMERIC(15,2) NOT NULL,
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'CALCULATED',
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP,
|
||||
CHECK (award_type IN ('NEW_RECRUIT','CORPORATE','RETENTION','PERFORMANCE')),
|
||||
CHECK (status IN ('CALCULATED','CONFIRMED','PAID','CANCELLED')),
|
||||
CHECK (base_amount >= 0),
|
||||
CHECK (award_amount >= 0)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE award_ledger IS '시상 원장 — 설계사별 시상 발생 이력';
|
||||
COMMENT ON COLUMN award_ledger.ledger_id IS '원장 PK';
|
||||
COMMENT ON COLUMN award_ledger.agent_id IS '설계사 FK';
|
||||
COMMENT ON COLUMN award_ledger.settle_month IS '정산 귀속월 (YYYYMM)';
|
||||
COMMENT ON COLUMN award_ledger.award_type IS '시상 유형 (NEW_RECRUIT/CORPORATE/RETENTION/PERFORMANCE)';
|
||||
COMMENT ON COLUMN award_ledger.base_amount IS '기준 실적 (인정실적 합산 등)';
|
||||
COMMENT ON COLUMN award_ledger.award_amount IS '시상금 = base×award_rate (RATE) 또는 award_fixed (FIXED)';
|
||||
COMMENT ON COLUMN award_ledger.status IS '상태 (CALCULATED/CONFIRMED/PAID/CANCELLED)';
|
||||
|
||||
CREATE INDEX idx_awdl_agent_month ON award_ledger(agent_id, settle_month);
|
||||
CREATE INDEX idx_awdl_type_month ON award_ledger(award_type, settle_month);
|
||||
@@ -0,0 +1,65 @@
|
||||
-- V93: 생보운영지원수수료 (life_support_rule + life_support_ledger)
|
||||
-- 생보 계약 운영지원 수수료. 설계사 등급별 지급률 × 생보 실적 + 환수.
|
||||
-- 공식: support_amount = base_amount × support_rate
|
||||
-- net_amount = support_amount - clawback_amount
|
||||
|
||||
-- ============================================================
|
||||
-- 1. life_support_rule — 생보운영지원수수료 룰 마스터
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE life_support_rule (
|
||||
rule_id BIGSERIAL PRIMARY KEY,
|
||||
agent_grade VARCHAR(20) NOT NULL,
|
||||
support_rate NUMERIC(7,4) NOT NULL,
|
||||
effective_from DATE NOT NULL,
|
||||
effective_to DATE,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
created_by BIGINT,
|
||||
updated_at TIMESTAMP,
|
||||
CHECK (support_rate >= 0 AND support_rate <= 1)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE life_support_rule IS '생보운영지원수수료 룰 마스터 — 등급별 생보 운영지원 수수료율';
|
||||
COMMENT ON COLUMN life_support_rule.rule_id IS '룰 PK';
|
||||
COMMENT ON COLUMN life_support_rule.agent_grade IS '설계사 등급';
|
||||
COMMENT ON COLUMN life_support_rule.support_rate IS '지급률 (소수, 예: 0.03 = 3%)';
|
||||
COMMENT ON COLUMN life_support_rule.effective_from IS '적용 시작일';
|
||||
COMMENT ON COLUMN life_support_rule.effective_to IS '적용 종료일 (NULL=현재 유효)';
|
||||
|
||||
CREATE INDEX idx_lsr_grade_from ON life_support_rule(agent_grade, effective_from);
|
||||
|
||||
-- ============================================================
|
||||
-- 2. life_support_ledger — 생보운영지원수수료 원장
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE life_support_ledger (
|
||||
ledger_id BIGSERIAL PRIMARY KEY,
|
||||
agent_id BIGINT NOT NULL REFERENCES agent(agent_id),
|
||||
settle_month CHAR(6) NOT NULL,
|
||||
base_amount NUMERIC(15,2) NOT NULL,
|
||||
support_rate NUMERIC(7,4) NOT NULL,
|
||||
support_amount NUMERIC(15,2) NOT NULL,
|
||||
clawback_amount NUMERIC(15,2) NOT NULL DEFAULT 0,
|
||||
net_amount NUMERIC(15,2) NOT NULL,
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'CALCULATED',
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP,
|
||||
CHECK (status IN ('CALCULATED','CONFIRMED','PAID','CLAWED_BACK','CANCELLED')),
|
||||
CHECK (base_amount >= 0),
|
||||
CHECK (support_rate >= 0 AND support_rate <= 1),
|
||||
CHECK (support_amount >= 0),
|
||||
CHECK (clawback_amount >= 0)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE life_support_ledger IS '생보운영지원수수료 원장 — 설계사별 생보 운영지원 수수료 발생/환수 이력';
|
||||
COMMENT ON COLUMN life_support_ledger.ledger_id IS '원장 PK';
|
||||
COMMENT ON COLUMN life_support_ledger.agent_id IS '설계사 FK';
|
||||
COMMENT ON COLUMN life_support_ledger.settle_month IS '정산 귀속월 (YYYYMM)';
|
||||
COMMENT ON COLUMN life_support_ledger.base_amount IS '생보 실적 기준금액';
|
||||
COMMENT ON COLUMN life_support_ledger.support_rate IS '적용 지급률 (life_support_rule 스냅샷)';
|
||||
COMMENT ON COLUMN life_support_ledger.support_amount IS '지원수수료 = base_amount × support_rate';
|
||||
COMMENT ON COLUMN life_support_ledger.clawback_amount IS '환수액';
|
||||
COMMENT ON COLUMN life_support_ledger.net_amount IS '실지급액 = support_amount - clawback_amount';
|
||||
COMMENT ON COLUMN life_support_ledger.status IS '상태 (CALCULATED/CONFIRMED/PAID/CLAWED_BACK/CANCELLED)';
|
||||
|
||||
CREATE INDEX idx_lsl_agent_month ON life_support_ledger(agent_id, settle_month);
|
||||
@@ -0,0 +1,69 @@
|
||||
-- V94: 지점장수당 (branch_mgr_allowance_rule + branch_mgr_allowance_ledger)
|
||||
-- 지점장이 소속 조직 실적에 대해 받는 수당. cap 제한 + 환수.
|
||||
-- 공식: allowance_amount = LEAST(base_amount × allowance_rate, cap_amount)
|
||||
-- (cap_amount NULL 이면 상한 없음)
|
||||
|
||||
-- ============================================================
|
||||
-- 1. branch_mgr_allowance_rule — 지점장수당 룰 마스터
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE branch_mgr_allowance_rule (
|
||||
rule_id BIGSERIAL PRIMARY KEY,
|
||||
position_code VARCHAR(20) NOT NULL,
|
||||
allowance_rate NUMERIC(7,4) NOT NULL,
|
||||
cap_amount NUMERIC(15,2),
|
||||
effective_from DATE NOT NULL,
|
||||
effective_to DATE,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
created_by BIGINT,
|
||||
updated_at TIMESTAMP,
|
||||
CHECK (allowance_rate >= 0 AND allowance_rate <= 1),
|
||||
CHECK (cap_amount IS NULL OR cap_amount >= 0)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE branch_mgr_allowance_rule IS '지점장수당 룰 마스터 — 직책별 수당율 및 상한 정의';
|
||||
COMMENT ON COLUMN branch_mgr_allowance_rule.rule_id IS '룰 PK';
|
||||
COMMENT ON COLUMN branch_mgr_allowance_rule.position_code IS '직책 코드 (지점장 등)';
|
||||
COMMENT ON COLUMN branch_mgr_allowance_rule.allowance_rate IS '수당율 (소수, 예: 0.02 = 2%)';
|
||||
COMMENT ON COLUMN branch_mgr_allowance_rule.cap_amount IS '수당 상한금액 (NULL=무제한)';
|
||||
COMMENT ON COLUMN branch_mgr_allowance_rule.effective_from IS '적용 시작일';
|
||||
COMMENT ON COLUMN branch_mgr_allowance_rule.effective_to IS '적용 종료일 (NULL=현재 유효)';
|
||||
|
||||
CREATE INDEX idx_bmar_position_from ON branch_mgr_allowance_rule(position_code, effective_from);
|
||||
|
||||
-- ============================================================
|
||||
-- 2. branch_mgr_allowance_ledger — 지점장수당 원장
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE branch_mgr_allowance_ledger (
|
||||
ledger_id BIGSERIAL PRIMARY KEY,
|
||||
manager_agent_id BIGINT NOT NULL REFERENCES agent(agent_id),
|
||||
org_id BIGINT NOT NULL REFERENCES organization(org_id),
|
||||
settle_month CHAR(6) NOT NULL,
|
||||
base_amount NUMERIC(15,2) NOT NULL,
|
||||
allowance_rate NUMERIC(7,4) NOT NULL,
|
||||
allowance_amount NUMERIC(15,2) NOT NULL,
|
||||
clawback_amount NUMERIC(15,2) NOT NULL DEFAULT 0,
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'CALCULATED',
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP,
|
||||
CHECK (status IN ('CALCULATED','CONFIRMED','PAID','CLAWED_BACK','CANCELLED')),
|
||||
CHECK (base_amount >= 0),
|
||||
CHECK (allowance_rate >= 0 AND allowance_rate <= 1),
|
||||
CHECK (allowance_amount >= 0),
|
||||
CHECK (clawback_amount >= 0)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE branch_mgr_allowance_ledger IS '지점장수당 원장 — 지점장이 소속 조직 실적 기준으로 받는 수당 이력';
|
||||
COMMENT ON COLUMN branch_mgr_allowance_ledger.ledger_id IS '원장 PK';
|
||||
COMMENT ON COLUMN branch_mgr_allowance_ledger.manager_agent_id IS '지점장 설계사 FK';
|
||||
COMMENT ON COLUMN branch_mgr_allowance_ledger.org_id IS '대상 조직 FK';
|
||||
COMMENT ON COLUMN branch_mgr_allowance_ledger.settle_month IS '정산 귀속월 (YYYYMM)';
|
||||
COMMENT ON COLUMN branch_mgr_allowance_ledger.base_amount IS '조직 실적 기준금액';
|
||||
COMMENT ON COLUMN branch_mgr_allowance_ledger.allowance_rate IS '적용 수당율 (branch_mgr_allowance_rule 스냅샷)';
|
||||
COMMENT ON COLUMN branch_mgr_allowance_ledger.allowance_amount IS '수당액 = LEAST(base_amount × allowance_rate, cap_amount)';
|
||||
COMMENT ON COLUMN branch_mgr_allowance_ledger.clawback_amount IS '환수액';
|
||||
COMMENT ON COLUMN branch_mgr_allowance_ledger.status IS '상태 (CALCULATED/CONFIRMED/PAID/CLAWED_BACK/CANCELLED)';
|
||||
|
||||
CREATE INDEX idx_bmal_manager_month ON branch_mgr_allowance_ledger(manager_agent_id, settle_month);
|
||||
CREATE INDEX idx_bmal_org_month ON branch_mgr_allowance_ledger(org_id, settle_month);
|
||||
@@ -0,0 +1,90 @@
|
||||
-- V95: incar 갭 수수료 도메인 7개 PAGE 메뉴 등록
|
||||
-- GRP_COMMISSION 하위 PAGE 7개 추가 (V54 패턴 동일).
|
||||
-- 권한: READ/CREATE/UPDATE/DELETE/EXPORT
|
||||
-- 역할: SUPER_ADMIN/ADMIN 전체, MANAGER READ/CREATE/UPDATE/EXPORT,
|
||||
-- AGENT READ (INCOME_COMM 제외 — 본인 관련 도메인만)
|
||||
|
||||
-- ============================================================
|
||||
-- 1. GRP_COMMISSION 하위 PAGE 7개 (incar P7)
|
||||
-- ============================================================
|
||||
|
||||
INSERT INTO menu (parent_menu_id, menu_code, menu_name, menu_type, menu_path, component_path, menu_level, sort_order)
|
||||
SELECT p.menu_id, x.code, x.name, 'PAGE', x.path, x.cmpt, 2, x.sort
|
||||
FROM menu p
|
||||
JOIN (VALUES
|
||||
('INCOME_COMM', '수입수수료/수지차', '/commission/income', 'commission/IncomeCommission', 80),
|
||||
('REFERRAL_COMM', '소개·이관수수료', '/commission/referral', 'commission/ReferralCommission',90),
|
||||
('SETTLING_SUPPORT', '정착지원금', '/commission/settling-support', 'commission/SettlingSupport', 100),
|
||||
('RECOGNIZED_PERF', '인정실적', '/commission/recognized', 'commission/RecognizedPerformance',110),
|
||||
('AWARD', '시상', '/commission/award', 'commission/Award', 120),
|
||||
('LIFE_OP_SUPPORT', '생보운영지원수수료', '/commission/life-support', 'commission/LifeSupport', 130),
|
||||
('BRANCH_MGR_ALLOWANCE', '지점장수당', '/commission/branch-allowance', 'commission/BranchAllowance', 140)
|
||||
) AS x(code, name, path, cmpt, sort)
|
||||
ON p.menu_code = 'GRP_COMMISSION'
|
||||
WHERE NOT EXISTS (SELECT 1 FROM menu WHERE menu_code = x.code);
|
||||
|
||||
-- ============================================================
|
||||
-- 2. 7개 메뉴에 권한 항목 등록 (READ/CREATE/UPDATE/DELETE/EXPORT)
|
||||
-- ============================================================
|
||||
|
||||
INSERT INTO menu_permission (menu_id, perm_code, perm_name, sort_order)
|
||||
SELECT m.menu_id, p.code, p.name, p.sort
|
||||
FROM menu m
|
||||
CROSS JOIN (VALUES
|
||||
('READ', '조회', 10),
|
||||
('CREATE', '등록', 20),
|
||||
('UPDATE', '수정', 30),
|
||||
('DELETE', '삭제', 40),
|
||||
('EXPORT', '엑셀', 60)
|
||||
) AS p(code, name, sort)
|
||||
WHERE m.menu_code IN (
|
||||
'INCOME_COMM','REFERRAL_COMM','SETTLING_SUPPORT',
|
||||
'RECOGNIZED_PERF','AWARD','LIFE_OP_SUPPORT','BRANCH_MGR_ALLOWANCE'
|
||||
)
|
||||
ON CONFLICT (menu_id, perm_code) DO NOTHING;
|
||||
|
||||
-- ============================================================
|
||||
-- 3. 역할별 권한 부여
|
||||
-- ============================================================
|
||||
|
||||
-- SUPER_ADMIN / ADMIN — 7개 메뉴 전체 권한
|
||||
INSERT INTO role_menu_permission (role_id, menu_id, perm_code, is_granted)
|
||||
SELECT r.role_id, mp.menu_id, mp.perm_code, 'Y'
|
||||
FROM role r
|
||||
CROSS JOIN menu_permission mp
|
||||
JOIN menu m ON m.menu_id = mp.menu_id
|
||||
WHERE r.role_code IN ('SUPER_ADMIN','ADMIN')
|
||||
AND m.menu_code IN (
|
||||
'INCOME_COMM','REFERRAL_COMM','SETTLING_SUPPORT',
|
||||
'RECOGNIZED_PERF','AWARD','LIFE_OP_SUPPORT','BRANCH_MGR_ALLOWANCE'
|
||||
)
|
||||
ON CONFLICT (role_id, menu_id, perm_code) DO NOTHING;
|
||||
|
||||
-- MANAGER — READ/CREATE/UPDATE/EXPORT (DELETE 제외)
|
||||
INSERT INTO role_menu_permission (role_id, menu_id, perm_code, is_granted)
|
||||
SELECT r.role_id, mp.menu_id, mp.perm_code, 'Y'
|
||||
FROM role r
|
||||
CROSS JOIN menu_permission mp
|
||||
JOIN menu m ON m.menu_id = mp.menu_id
|
||||
WHERE r.role_code = 'MANAGER'
|
||||
AND mp.perm_code IN ('READ','CREATE','UPDATE','EXPORT')
|
||||
AND m.menu_code IN (
|
||||
'INCOME_COMM','REFERRAL_COMM','SETTLING_SUPPORT',
|
||||
'RECOGNIZED_PERF','AWARD','LIFE_OP_SUPPORT','BRANCH_MGR_ALLOWANCE'
|
||||
)
|
||||
ON CONFLICT (role_id, menu_id, perm_code) DO NOTHING;
|
||||
|
||||
-- AGENT — INCOME_COMM 제외하고 READ 만
|
||||
-- (INCOME_COMM은 보험사-GA 수입 측 데이터로 설계사 공개 불가)
|
||||
INSERT INTO role_menu_permission (role_id, menu_id, perm_code, is_granted)
|
||||
SELECT r.role_id, mp.menu_id, mp.perm_code, 'Y'
|
||||
FROM role r
|
||||
CROSS JOIN menu_permission mp
|
||||
JOIN menu m ON m.menu_id = mp.menu_id
|
||||
WHERE r.role_code = 'AGENT'
|
||||
AND mp.perm_code = 'READ'
|
||||
AND m.menu_code IN (
|
||||
'REFERRAL_COMM','SETTLING_SUPPORT',
|
||||
'RECOGNIZED_PERF','AWARD','LIFE_OP_SUPPORT','BRANCH_MGR_ALLOWANCE'
|
||||
)
|
||||
ON CONFLICT (role_id, menu_id, perm_code) DO NOTHING;
|
||||
@@ -0,0 +1,35 @@
|
||||
-- V96: incar 갭 수수료 도메인 공통코드 5종 (V55 패턴 동일)
|
||||
-- INCOME_STATUS / REFERRAL_TYPE / CALC_TYPE / SUPPORT_STATUS / AWARD_TYPE
|
||||
|
||||
-- ===== 그룹 =====
|
||||
INSERT INTO common_code_group (group_code, group_name, description, is_system, sort_order) VALUES
|
||||
('INCOME_STATUS', '수입수수료상태', 'CALCULATED/CONFIRMED/CANCELLED', 'Y', 390),
|
||||
('REFERRAL_TYPE', '소개이관유형', 'REFERRAL=소개/TRANSFER=이관', 'Y', 400),
|
||||
('CALC_TYPE', '계산방식', 'RATE=정률/FIXED=정액', 'Y', 410),
|
||||
('SUPPORT_STATUS', '지원금상태', 'CALCULATED/PAID/CLAWED_BACK/CANCELLED', 'Y', 420),
|
||||
('AWARD_TYPE', '시상유형', 'NEW_RECRUIT/CORPORATE/RETENTION/PERFORMANCE', 'Y', 430)
|
||||
ON CONFLICT (group_code) DO NOTHING;
|
||||
|
||||
-- ===== 상세 =====
|
||||
INSERT INTO common_code (group_code, code, code_name, sort_order) VALUES
|
||||
-- INCOME_STATUS
|
||||
('INCOME_STATUS', 'CALCULATED', '계산완료', 10),
|
||||
('INCOME_STATUS', 'CONFIRMED', '확정', 20),
|
||||
('INCOME_STATUS', 'CANCELLED', '취소', 30),
|
||||
-- REFERRAL_TYPE
|
||||
('REFERRAL_TYPE', 'REFERRAL', '소개', 10),
|
||||
('REFERRAL_TYPE', 'TRANSFER', '이관', 20),
|
||||
-- CALC_TYPE
|
||||
('CALC_TYPE', 'RATE', '정률', 10),
|
||||
('CALC_TYPE', 'FIXED', '정액', 20),
|
||||
-- SUPPORT_STATUS
|
||||
('SUPPORT_STATUS', 'CALCULATED', '계산완료', 10),
|
||||
('SUPPORT_STATUS', 'PAID', '지급완료', 20),
|
||||
('SUPPORT_STATUS', 'CLAWED_BACK','환수완료', 30),
|
||||
('SUPPORT_STATUS', 'CANCELLED', '취소', 40),
|
||||
-- AWARD_TYPE
|
||||
('AWARD_TYPE', 'NEW_RECRUIT', '신인도입', 10),
|
||||
('AWARD_TYPE', 'CORPORATE', '법인', 20),
|
||||
('AWARD_TYPE', 'RETENTION', '유지율', 30),
|
||||
('AWARD_TYPE', 'PERFORMANCE', '실적', 40)
|
||||
ON CONFLICT (group_code, code) DO NOTHING;
|
||||
@@ -0,0 +1,27 @@
|
||||
-- V97: 단계마감 (settlement_step_close)
|
||||
-- 월 정산을 단계별로 마감(수신→계산→검증→지급). 기존 PERIOD_CLOSE 보강.
|
||||
|
||||
CREATE TABLE settlement_step_close (
|
||||
step_close_id BIGSERIAL PRIMARY KEY,
|
||||
settle_month CHAR(6) NOT NULL,
|
||||
step_code VARCHAR(20) NOT NULL,
|
||||
step_seq INT NOT NULL,
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'OPEN',
|
||||
closed_at TIMESTAMP,
|
||||
closed_by BIGINT,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
UNIQUE (settle_month, step_code),
|
||||
CHECK (step_code IN ('RECEIVE','CALC','VERIFY','PAY')),
|
||||
CHECK (status IN ('OPEN','CLOSED'))
|
||||
);
|
||||
|
||||
COMMENT ON TABLE settlement_step_close IS '단계마감 — 월 정산 단계별 마감 상태 관리';
|
||||
COMMENT ON COLUMN settlement_step_close.settle_month IS '정산 귀속월 (YYYYMM)';
|
||||
COMMENT ON COLUMN settlement_step_close.step_code IS '단계코드 (RECEIVE=수신/CALC=계산/VERIFY=검증/PAY=지급)';
|
||||
COMMENT ON COLUMN settlement_step_close.step_seq IS '단계 순서 1~4';
|
||||
COMMENT ON COLUMN settlement_step_close.status IS '상태 (OPEN=열림/CLOSED=마감)';
|
||||
COMMENT ON COLUMN settlement_step_close.closed_at IS '마감 처리 일시';
|
||||
COMMENT ON COLUMN settlement_step_close.closed_by IS '마감 처리자 FK(users)';
|
||||
|
||||
CREATE INDEX idx_ssc_month_status ON settlement_step_close(settle_month, status);
|
||||
CREATE INDEX idx_ssc_step_seq ON settlement_step_close(step_seq);
|
||||
@@ -0,0 +1,29 @@
|
||||
-- V98: 본사정산 (hq_settle_item)
|
||||
-- GA 본사 자체의 수입/비용 정산 항목(설계사 지급과 별개의 GA 손익).
|
||||
-- 본사손익 = SUM(INCOME amount) - SUM(EXPENSE amount)
|
||||
|
||||
CREATE TABLE hq_settle_item (
|
||||
item_id BIGSERIAL PRIMARY KEY,
|
||||
settle_month CHAR(6) NOT NULL,
|
||||
item_type VARCHAR(20) NOT NULL,
|
||||
item_name VARCHAR(100) NOT NULL,
|
||||
amount NUMERIC(15,2) NOT NULL,
|
||||
memo VARCHAR(300),
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'REGISTERED',
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
created_by BIGINT,
|
||||
updated_at TIMESTAMP,
|
||||
CHECK (item_type IN ('INCOME','EXPENSE')),
|
||||
CHECK (status IN ('REGISTERED','CONFIRMED'))
|
||||
);
|
||||
|
||||
COMMENT ON TABLE hq_settle_item IS '본사정산 항목 — GA 자체 수입/비용 정산';
|
||||
COMMENT ON COLUMN hq_settle_item.settle_month IS '정산 귀속월 (YYYYMM)';
|
||||
COMMENT ON COLUMN hq_settle_item.item_type IS '항목유형 (INCOME=수입/EXPENSE=비용)';
|
||||
COMMENT ON COLUMN hq_settle_item.item_name IS '항목명';
|
||||
COMMENT ON COLUMN hq_settle_item.amount IS '금액 (원, HALF_UP)';
|
||||
COMMENT ON COLUMN hq_settle_item.memo IS '비고';
|
||||
COMMENT ON COLUMN hq_settle_item.status IS '상태 (REGISTERED=등록/CONFIRMED=확정)';
|
||||
|
||||
CREATE INDEX idx_hqi_month_type ON hq_settle_item(settle_month, item_type);
|
||||
CREATE INDEX idx_hqi_month_status ON hq_settle_item(settle_month, status);
|
||||
@@ -0,0 +1,53 @@
|
||||
-- V99: 등급평가 (grade_eval_rule + agent_grade_evaluation)
|
||||
-- 설계사 실적 점수로 등급 산정.
|
||||
-- 공식: evaluated_grade = score 이상인 최대 min_score 룰의 grade (Service 결정)
|
||||
|
||||
-- ============================================================
|
||||
-- 1. grade_eval_rule — 점수구간별 등급 룰 마스터
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE grade_eval_rule (
|
||||
rule_id BIGSERIAL PRIMARY KEY,
|
||||
grade_id INT NOT NULL REFERENCES grade(grade_id),
|
||||
min_score NUMERIC(7,2) NOT NULL,
|
||||
effective_from DATE NOT NULL,
|
||||
effective_to DATE,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE grade_eval_rule IS '등급평가 룰 — 실적점수 구간별 등급 매핑';
|
||||
COMMENT ON COLUMN grade_eval_rule.grade_id IS '부여 등급 FK(grade)';
|
||||
COMMENT ON COLUMN grade_eval_rule.min_score IS '최소 실적점수 (이 점수 이상이면 해당 등급)';
|
||||
COMMENT ON COLUMN grade_eval_rule.effective_from IS '룰 적용 시작일';
|
||||
COMMENT ON COLUMN grade_eval_rule.effective_to IS '룰 적용 종료일 (NULL=현재 유효)';
|
||||
|
||||
CREATE INDEX idx_ger_grade_from ON grade_eval_rule(grade_id, effective_from);
|
||||
CREATE INDEX idx_ger_score ON grade_eval_rule(min_score);
|
||||
|
||||
-- ============================================================
|
||||
-- 2. agent_grade_evaluation — 설계사 등급평가 원장
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE agent_grade_evaluation (
|
||||
eval_id BIGSERIAL PRIMARY KEY,
|
||||
agent_id INT NOT NULL REFERENCES agent(agent_id),
|
||||
eval_period CHAR(6) NOT NULL,
|
||||
performance_score NUMERIC(7,2) NOT NULL,
|
||||
prev_grade_id INT REFERENCES grade(grade_id),
|
||||
evaluated_grade_id INT REFERENCES grade(grade_id),
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'EVALUATED',
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
UNIQUE (agent_id, eval_period),
|
||||
CHECK (status IN ('EVALUATED','APPLIED'))
|
||||
);
|
||||
|
||||
COMMENT ON TABLE agent_grade_evaluation IS '설계사 등급평가 원장 — 월별 실적점수 기반 등급 산정 결과';
|
||||
COMMENT ON COLUMN agent_grade_evaluation.agent_id IS '대상 설계사 FK';
|
||||
COMMENT ON COLUMN agent_grade_evaluation.eval_period IS '평가 기준월 (YYYYMM)';
|
||||
COMMENT ON COLUMN agent_grade_evaluation.performance_score IS '실적 점수';
|
||||
COMMENT ON COLUMN agent_grade_evaluation.prev_grade_id IS '기존(직전) 등급 FK';
|
||||
COMMENT ON COLUMN agent_grade_evaluation.evaluated_grade_id IS '산정 등급 FK';
|
||||
COMMENT ON COLUMN agent_grade_evaluation.status IS '상태 (EVALUATED=평가완료/APPLIED=반영완료)';
|
||||
|
||||
CREATE INDEX idx_age_agent_period ON agent_grade_evaluation(agent_id, eval_period);
|
||||
CREATE INDEX idx_age_status ON agent_grade_evaluation(status, eval_period);
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.ga.core.mapper.commission;
|
||||
|
||||
import com.ga.core.vo.commission.AgentGradeEvaluationResp;
|
||||
import com.ga.core.vo.commission.AgentGradeEvaluationSearchParam;
|
||||
import com.ga.core.vo.commission.AgentGradeEvaluationVO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* agent_grade_evaluation Mapper.
|
||||
* 설계사 등급평가 원장: 월별 실적점수 기반 등급 산정 결과.
|
||||
*/
|
||||
@Mapper
|
||||
public interface AgentGradeEvaluationMapper {
|
||||
|
||||
AgentGradeEvaluationResp selectById(@Param("evalId") Long evalId);
|
||||
|
||||
List<AgentGradeEvaluationResp> selectList(AgentGradeEvaluationSearchParam param);
|
||||
|
||||
int insert(AgentGradeEvaluationVO vo);
|
||||
|
||||
/**
|
||||
* 등급 반영 상태 변경 (EVALUATED → APPLIED).
|
||||
*/
|
||||
int updateStatus(@Param("evalId") Long evalId, @Param("status") String status);
|
||||
|
||||
int delete(@Param("evalId") Long evalId);
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package com.ga.core.mapper.commission;
|
||||
|
||||
import com.ga.core.vo.commission.AgentGuaranteeInsuranceResp;
|
||||
import com.ga.core.vo.commission.AgentGuaranteeInsuranceSearchParam;
|
||||
import com.ga.core.vo.commission.AgentGuaranteeInsuranceVO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* agent_guarantee_insurance Mapper.
|
||||
* 보증보험: 설계사 신원보증보험 가입현황 관리.
|
||||
*/
|
||||
@Mapper
|
||||
public interface AgentGuaranteeInsuranceMapper {
|
||||
|
||||
AgentGuaranteeInsuranceResp selectById(@Param("guaranteeId") Long guaranteeId);
|
||||
|
||||
List<AgentGuaranteeInsuranceResp> selectList(AgentGuaranteeInsuranceSearchParam param);
|
||||
|
||||
int insert(AgentGuaranteeInsuranceVO vo);
|
||||
|
||||
int update(AgentGuaranteeInsuranceVO vo);
|
||||
|
||||
int delete(@Param("guaranteeId") Long guaranteeId);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.ga.core.mapper.commission;
|
||||
|
||||
import com.ga.core.vo.commission.AgentReserveFundResp;
|
||||
import com.ga.core.vo.commission.AgentReserveFundSearchParam;
|
||||
import com.ga.core.vo.commission.AgentReserveFundVO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* agent_reserve_fund Mapper.
|
||||
* 적립금: 설계사별 월별 적립금 잔액 관리.
|
||||
* balance = 전월잔액 + reserve_in - reserve_out (Service 계산).
|
||||
*/
|
||||
@Mapper
|
||||
public interface AgentReserveFundMapper {
|
||||
|
||||
AgentReserveFundResp selectById(@Param("reserveId") Long reserveId);
|
||||
|
||||
List<AgentReserveFundResp> selectList(AgentReserveFundSearchParam param);
|
||||
|
||||
/**
|
||||
* 설계사 + 정산월 단건 조회 — 중복 방지 및 해당 월 잔액 확인용.
|
||||
*/
|
||||
AgentReserveFundVO selectByAgentAndMonth(@Param("agentId") Long agentId,
|
||||
@Param("settleMonth") String settleMonth);
|
||||
|
||||
/**
|
||||
* 설계사의 지정월 이전 최신(직전월) 레코드 1건 — 잔액 누적 계산용.
|
||||
* settleMonth 미만인 데이터 중 settle_month DESC LIMIT 1.
|
||||
*/
|
||||
AgentReserveFundVO selectLatestBefore(@Param("agentId") Long agentId,
|
||||
@Param("settleMonth") String settleMonth);
|
||||
|
||||
int insert(AgentReserveFundVO vo);
|
||||
|
||||
int update(AgentReserveFundVO vo);
|
||||
|
||||
int delete(@Param("reserveId") Long reserveId);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.ga.core.mapper.commission;
|
||||
|
||||
import com.ga.core.vo.commission.AwardLedgerResp;
|
||||
import com.ga.core.vo.commission.AwardLedgerSearchParam;
|
||||
import com.ga.core.vo.commission.AwardLedgerVO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* award_ledger Mapper — 시상 원장 (V92)
|
||||
*/
|
||||
@Mapper
|
||||
public interface AwardLedgerMapper {
|
||||
|
||||
AwardLedgerResp selectById(@Param("ledgerId") Long ledgerId);
|
||||
|
||||
List<AwardLedgerResp> selectList(AwardLedgerSearchParam param);
|
||||
|
||||
/** 설계사 + 정산월 시상금 합계 */
|
||||
BigDecimal sumAwardByAgentAndMonth(@Param("agentId") Long agentId,
|
||||
@Param("settleMonth") String settleMonth);
|
||||
|
||||
/** 정산월 기준 설계사별 합계 — settle_master 집계 배치 용도 */
|
||||
List<Map<String, Object>> aggregateByAgent(@Param("settleMonth") String settleMonth);
|
||||
|
||||
int insert(AwardLedgerVO vo);
|
||||
|
||||
int updateStatus(@Param("ledgerId") Long ledgerId, @Param("status") String status);
|
||||
|
||||
int delete(@Param("ledgerId") Long ledgerId);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.ga.core.mapper.commission;
|
||||
|
||||
import com.ga.core.vo.commission.AwardRuleResp;
|
||||
import com.ga.core.vo.commission.AwardRuleSearchParam;
|
||||
import com.ga.core.vo.commission.AwardRuleVO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* award_rule Mapper — 시상 룰 마스터 (V92)
|
||||
*/
|
||||
@Mapper
|
||||
public interface AwardRuleMapper {
|
||||
|
||||
AwardRuleResp selectById(@Param("ruleId") Long ruleId);
|
||||
|
||||
List<AwardRuleResp> selectList(AwardRuleSearchParam param);
|
||||
|
||||
/**
|
||||
* 시상 유형 + 기준일 유효 룰 1건 조회.
|
||||
* 시상 계산 Service에서 호출.
|
||||
*/
|
||||
AwardRuleVO selectActiveRule(@Param("awardType") String awardType,
|
||||
@Param("baseDate") LocalDate baseDate);
|
||||
|
||||
int insert(AwardRuleVO vo);
|
||||
|
||||
int update(AwardRuleVO vo);
|
||||
|
||||
int delete(@Param("ruleId") Long ruleId);
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package com.ga.core.mapper.commission;
|
||||
|
||||
import com.ga.core.vo.commission.BranchMgrAllowanceLedgerResp;
|
||||
import com.ga.core.vo.commission.BranchMgrAllowanceLedgerSearchParam;
|
||||
import com.ga.core.vo.commission.BranchMgrAllowanceLedgerVO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* branch_mgr_allowance_ledger Mapper — 지점장수당 원장 (V94)
|
||||
*/
|
||||
@Mapper
|
||||
public interface BranchMgrAllowanceLedgerMapper {
|
||||
|
||||
BranchMgrAllowanceLedgerResp selectById(@Param("ledgerId") Long ledgerId);
|
||||
|
||||
List<BranchMgrAllowanceLedgerResp> selectList(BranchMgrAllowanceLedgerSearchParam param);
|
||||
|
||||
/** 지점장 + 정산월 수당 합계 */
|
||||
BigDecimal sumAllowanceByManagerAndMonth(@Param("managerAgentId") Long managerAgentId,
|
||||
@Param("settleMonth") String settleMonth);
|
||||
|
||||
/** 정산월 기준 지점장별 순수당 합계(allowance_amount - COALESCE(clawback_amount,0)) — settle_master 집계 배치 용도 */
|
||||
List<Map<String, Object>> aggregateByAgent(@Param("settleMonth") String settleMonth);
|
||||
|
||||
int insert(BranchMgrAllowanceLedgerVO vo);
|
||||
|
||||
int updateStatus(@Param("ledgerId") Long ledgerId, @Param("status") String status);
|
||||
|
||||
int delete(@Param("ledgerId") Long ledgerId);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.ga.core.mapper.commission;
|
||||
|
||||
import com.ga.core.vo.commission.BranchMgrAllowanceRuleResp;
|
||||
import com.ga.core.vo.commission.BranchMgrAllowanceRuleSearchParam;
|
||||
import com.ga.core.vo.commission.BranchMgrAllowanceRuleVO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* branch_mgr_allowance_rule Mapper — 지점장수당 룰 (V94)
|
||||
*/
|
||||
@Mapper
|
||||
public interface BranchMgrAllowanceRuleMapper {
|
||||
|
||||
BranchMgrAllowanceRuleResp selectById(@Param("ruleId") Long ruleId);
|
||||
|
||||
List<BranchMgrAllowanceRuleResp> selectList(BranchMgrAllowanceRuleSearchParam param);
|
||||
|
||||
/**
|
||||
* 직책코드 + 기준일 유효 룰 1건 조회.
|
||||
* 지점장수당 계산 Service에서 호출.
|
||||
*/
|
||||
BranchMgrAllowanceRuleVO selectActiveRule(@Param("positionCode") String positionCode,
|
||||
@Param("baseDate") LocalDate baseDate);
|
||||
|
||||
int insert(BranchMgrAllowanceRuleVO vo);
|
||||
|
||||
int update(BranchMgrAllowanceRuleVO vo);
|
||||
|
||||
int delete(@Param("ruleId") Long ruleId);
|
||||
}
|
||||
+4
@@ -8,6 +8,7 @@ import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* collection_commission_ledger Mapper.
|
||||
@@ -23,6 +24,9 @@ public interface CollectionCommissionLedgerMapper {
|
||||
BigDecimal sumByAgentAndMonth(@Param("agentId") Long agentId,
|
||||
@Param("settleMonth") String settleMonth);
|
||||
|
||||
/** 정산월 기준 설계사별 합계 — settle_master 집계 배치 용도 */
|
||||
List<Map<String, Object>> aggregateByAgent(@Param("settleMonth") String settleMonth);
|
||||
|
||||
int insert(CollectionCommissionLedgerVO vo);
|
||||
|
||||
int updateStatus(@Param("ledgerId") Long ledgerId, @Param("status") String status);
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.ga.core.mapper.commission;
|
||||
|
||||
import com.ga.core.vo.commission.CommissionSimulationResp;
|
||||
import com.ga.core.vo.commission.CommissionSimulationSearchParam;
|
||||
import com.ga.core.vo.commission.CommissionSimulationVO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* commission_simulation Mapper.
|
||||
* 수수료 시뮬레이터: 시나리오별 산출 결과 저장/조회.
|
||||
*/
|
||||
@Mapper
|
||||
public interface CommissionSimulationMapper {
|
||||
|
||||
CommissionSimulationResp selectById(@Param("simId") Long simId);
|
||||
|
||||
List<CommissionSimulationResp> selectList(CommissionSimulationSearchParam param);
|
||||
|
||||
int insert(CommissionSimulationVO vo);
|
||||
|
||||
int delete(@Param("simId") Long simId);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.ga.core.mapper.commission;
|
||||
|
||||
import com.ga.core.vo.commission.GradeEvalRuleResp;
|
||||
import com.ga.core.vo.commission.GradeEvalRuleSearchParam;
|
||||
import com.ga.core.vo.commission.GradeEvalRuleVO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* grade_eval_rule Mapper.
|
||||
* 등급평가 룰: 점수 구간별 등급 매핑 마스터.
|
||||
*/
|
||||
@Mapper
|
||||
public interface GradeEvalRuleMapper {
|
||||
|
||||
GradeEvalRuleResp selectById(@Param("ruleId") Long ruleId);
|
||||
|
||||
List<GradeEvalRuleResp> selectList(GradeEvalRuleSearchParam param);
|
||||
|
||||
/**
|
||||
* 점수 이상인 최대 min_score 룰 1건 조회 — 등급 산정 Service 용도.
|
||||
* 기준일(baseDate) 기준 유효한 룰 중 score 이상이고 min_score 가 최대인 1건 반환.
|
||||
*/
|
||||
GradeEvalRuleVO selectActiveRuleByScore(@Param("score") BigDecimal score,
|
||||
@Param("baseDate") LocalDate baseDate);
|
||||
|
||||
int insert(GradeEvalRuleVO vo);
|
||||
|
||||
int update(GradeEvalRuleVO vo);
|
||||
|
||||
int delete(@Param("ruleId") Long ruleId);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.ga.core.mapper.commission;
|
||||
|
||||
import com.ga.core.vo.commission.HqSettleItemResp;
|
||||
import com.ga.core.vo.commission.HqSettleItemSearchParam;
|
||||
import com.ga.core.vo.commission.HqSettleItemVO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* hq_settle_item Mapper.
|
||||
* 본사정산: GA 자체 수입/비용 항목. 손익 = Σ(INCOME) − Σ(EXPENSE).
|
||||
*/
|
||||
@Mapper
|
||||
public interface HqSettleItemMapper {
|
||||
|
||||
HqSettleItemResp selectById(@Param("itemId") Long itemId);
|
||||
|
||||
List<HqSettleItemResp> selectList(HqSettleItemSearchParam param);
|
||||
|
||||
/**
|
||||
* 정산월 + 항목유형별 금액 합계 — 본사손익 집계용.
|
||||
* itemType = INCOME or EXPENSE
|
||||
*/
|
||||
BigDecimal sumByMonthAndType(@Param("settleMonth") String settleMonth,
|
||||
@Param("itemType") String itemType);
|
||||
|
||||
int insert(HqSettleItemVO vo);
|
||||
|
||||
int update(HqSettleItemVO vo);
|
||||
|
||||
int delete(@Param("itemId") Long itemId);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.ga.core.mapper.commission;
|
||||
|
||||
import com.ga.core.vo.commission.IncomeCommissionLedgerResp;
|
||||
import com.ga.core.vo.commission.IncomeCommissionLedgerSearchParam;
|
||||
import com.ga.core.vo.commission.IncomeCommissionLedgerVO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* income_commission_ledger Mapper — 수입수수료 원장 (V88)
|
||||
*/
|
||||
@Mapper
|
||||
public interface IncomeCommissionLedgerMapper {
|
||||
|
||||
IncomeCommissionLedgerResp selectById(@Param("incomeId") Long incomeId);
|
||||
|
||||
List<IncomeCommissionLedgerResp> selectList(IncomeCommissionLedgerSearchParam param);
|
||||
|
||||
/** 보험사 + 정산월 수입수수료 합계 — margin_summary recalc 배치 용도 */
|
||||
BigDecimal sumByCarrierAndMonth(@Param("carrierId") Long carrierId,
|
||||
@Param("settleMonth") String settleMonth);
|
||||
|
||||
int insert(IncomeCommissionLedgerVO vo);
|
||||
|
||||
int updateStatus(@Param("incomeId") Long incomeId, @Param("status") String status);
|
||||
|
||||
int delete(@Param("incomeId") Long incomeId);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.ga.core.mapper.commission;
|
||||
|
||||
import com.ga.core.vo.commission.JointContractResp;
|
||||
import com.ga.core.vo.commission.JointContractSearchParam;
|
||||
import com.ga.core.vo.commission.JointContractVO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* joint_contract Mapper.
|
||||
* 공동계약: 복수 설계사의 계약별 지분율 관리.
|
||||
*/
|
||||
@Mapper
|
||||
public interface JointContractMapper {
|
||||
|
||||
JointContractResp selectById(@Param("jointId") Long jointId);
|
||||
|
||||
List<JointContractResp> selectList(JointContractSearchParam param);
|
||||
|
||||
/**
|
||||
* 계약별 공동설계사 목록 조회 — 지분율 확인용.
|
||||
*/
|
||||
List<JointContractResp> selectByContract(@Param("contractId") Long contractId);
|
||||
|
||||
/**
|
||||
* 계약별 지분율 합산 — 합=1 검증(Service 가드) 용도.
|
||||
*/
|
||||
BigDecimal sumShareByContract(@Param("contractId") Long contractId);
|
||||
|
||||
int insert(JointContractVO vo);
|
||||
|
||||
int update(JointContractVO vo);
|
||||
|
||||
int delete(@Param("jointId") Long jointId);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.ga.core.mapper.commission;
|
||||
|
||||
import com.ga.core.vo.commission.LifeSupportLedgerResp;
|
||||
import com.ga.core.vo.commission.LifeSupportLedgerSearchParam;
|
||||
import com.ga.core.vo.commission.LifeSupportLedgerVO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* life_support_ledger Mapper — 생보운영지원수수료 원장 (V93)
|
||||
*/
|
||||
@Mapper
|
||||
public interface LifeSupportLedgerMapper {
|
||||
|
||||
LifeSupportLedgerResp selectById(@Param("ledgerId") Long ledgerId);
|
||||
|
||||
List<LifeSupportLedgerResp> selectList(LifeSupportLedgerSearchParam param);
|
||||
|
||||
/** 설계사 + 정산월 실지급액 합계 */
|
||||
BigDecimal sumNetByAgentAndMonth(@Param("agentId") Long agentId,
|
||||
@Param("settleMonth") String settleMonth);
|
||||
|
||||
/** 정산월 기준 설계사별 합계 — settle_master 집계 배치 용도 */
|
||||
List<Map<String, Object>> aggregateByAgent(@Param("settleMonth") String settleMonth);
|
||||
|
||||
int insert(LifeSupportLedgerVO vo);
|
||||
|
||||
int updateStatus(@Param("ledgerId") Long ledgerId, @Param("status") String status);
|
||||
|
||||
int delete(@Param("ledgerId") Long ledgerId);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.ga.core.mapper.commission;
|
||||
|
||||
import com.ga.core.vo.commission.LifeSupportRuleResp;
|
||||
import com.ga.core.vo.commission.LifeSupportRuleSearchParam;
|
||||
import com.ga.core.vo.commission.LifeSupportRuleVO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* life_support_rule Mapper — 생보운영지원수수료 룰 (V93)
|
||||
*/
|
||||
@Mapper
|
||||
public interface LifeSupportRuleMapper {
|
||||
|
||||
LifeSupportRuleResp selectById(@Param("ruleId") Long ruleId);
|
||||
|
||||
List<LifeSupportRuleResp> selectList(LifeSupportRuleSearchParam param);
|
||||
|
||||
/**
|
||||
* 설계사 등급 + 기준일 유효 룰 1건 조회.
|
||||
* 생보운영지원수수료 계산 Service에서 호출.
|
||||
*/
|
||||
LifeSupportRuleVO selectActiveRule(@Param("agentGrade") String agentGrade,
|
||||
@Param("baseDate") LocalDate baseDate);
|
||||
|
||||
int insert(LifeSupportRuleVO vo);
|
||||
|
||||
int update(LifeSupportRuleVO vo);
|
||||
|
||||
int delete(@Param("ruleId") Long ruleId);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.ga.core.mapper.commission;
|
||||
|
||||
import com.ga.core.vo.commission.MarginSummaryResp;
|
||||
import com.ga.core.vo.commission.MarginSummarySearchParam;
|
||||
import com.ga.core.vo.commission.MarginSummaryVO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* margin_summary Mapper — 수지차 집계 (V88)
|
||||
*/
|
||||
@Mapper
|
||||
public interface MarginSummaryMapper {
|
||||
|
||||
MarginSummaryResp selectById(@Param("marginId") Long marginId);
|
||||
|
||||
List<MarginSummaryResp> selectList(MarginSummarySearchParam param);
|
||||
|
||||
/**
|
||||
* 정산월 + 설계사(NULL=전체) 단위 수지차 1건 조회.
|
||||
* recalc 시 UPSERT 전 존재 여부 확인용.
|
||||
*/
|
||||
MarginSummaryVO selectByMonthAndAgent(@Param("settleMonth") String settleMonth,
|
||||
@Param("agentId") Long agentId);
|
||||
|
||||
/** 정산월 전체 수입수수료 합 (income_commission_ledger, CANCELLED 제외). */
|
||||
BigDecimal sumIncomeByMonth(@Param("settleMonth") String settleMonth);
|
||||
|
||||
/** 정산월 지급 합 (settle_master.net_amount). agentId NULL=전체. */
|
||||
BigDecimal sumPayoutByMonth(@Param("settleMonth") String settleMonth,
|
||||
@Param("agentId") Long agentId);
|
||||
|
||||
int insert(MarginSummaryVO vo);
|
||||
|
||||
int update(MarginSummaryVO vo);
|
||||
|
||||
int delete(@Param("marginId") Long marginId);
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* persistency_bonus Mapper.
|
||||
@@ -30,4 +31,7 @@ public interface PersistencyBonusMapper {
|
||||
int updateStatus(@Param("bonusId") Long bonusId, @Param("status") String status);
|
||||
|
||||
int delete(@Param("bonusId") Long bonusId);
|
||||
|
||||
/** 정산월 기준 설계사별 유지보너스 합계 — settle_master 집계 배치 용도 */
|
||||
List<Map<String, Object>> aggregateByAgent(@Param("settleMonth") String settleMonth);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.ga.core.mapper.commission;
|
||||
|
||||
import com.ga.core.vo.commission.ProductConversionRateResp;
|
||||
import com.ga.core.vo.commission.ProductConversionRateSearchParam;
|
||||
import com.ga.core.vo.commission.ProductConversionRateVO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* product_conversion_rate Mapper — 상품별 환산율 마스터 (V91)
|
||||
*/
|
||||
@Mapper
|
||||
public interface ProductConversionRateMapper {
|
||||
|
||||
ProductConversionRateResp selectById(@Param("rateId") Long rateId);
|
||||
|
||||
List<ProductConversionRateResp> selectList(ProductConversionRateSearchParam param);
|
||||
|
||||
/**
|
||||
* 상품 + 기준일 유효 환산율 1건 조회.
|
||||
* 인정실적 계산 Service에서 호출.
|
||||
*/
|
||||
ProductConversionRateVO selectActiveRate(@Param("productId") Long productId,
|
||||
@Param("baseDate") LocalDate baseDate);
|
||||
|
||||
int insert(ProductConversionRateVO vo);
|
||||
|
||||
int update(ProductConversionRateVO vo);
|
||||
|
||||
int delete(@Param("rateId") Long rateId);
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package com.ga.core.mapper.commission;
|
||||
|
||||
import com.ga.core.vo.commission.RecognizedPerformanceLedgerResp;
|
||||
import com.ga.core.vo.commission.RecognizedPerformanceLedgerSearchParam;
|
||||
import com.ga.core.vo.commission.RecognizedPerformanceLedgerVO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* recognized_performance_ledger Mapper — 인정실적 원장 (V91)
|
||||
*/
|
||||
@Mapper
|
||||
public interface RecognizedPerformanceLedgerMapper {
|
||||
|
||||
RecognizedPerformanceLedgerResp selectById(@Param("ledgerId") Long ledgerId);
|
||||
|
||||
List<RecognizedPerformanceLedgerResp> selectList(RecognizedPerformanceLedgerSearchParam param);
|
||||
|
||||
/** 설계사 + 정산월 인정실적 합계 — 시상 기준 실적 산출 용도 */
|
||||
BigDecimal sumRecognizedByAgentAndMonth(@Param("agentId") Long agentId,
|
||||
@Param("settleMonth") String settleMonth);
|
||||
|
||||
int insert(RecognizedPerformanceLedgerVO vo);
|
||||
|
||||
int updateStatus(@Param("ledgerId") Long ledgerId, @Param("status") String status);
|
||||
|
||||
int delete(@Param("ledgerId") Long ledgerId);
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package com.ga.core.mapper.commission;
|
||||
|
||||
import com.ga.core.vo.commission.ReferralCommissionLedgerResp;
|
||||
import com.ga.core.vo.commission.ReferralCommissionLedgerSearchParam;
|
||||
import com.ga.core.vo.commission.ReferralCommissionLedgerVO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* referral_commission_ledger Mapper — 소개·이관수수료 원장 (V89)
|
||||
*/
|
||||
@Mapper
|
||||
public interface ReferralCommissionLedgerMapper {
|
||||
|
||||
ReferralCommissionLedgerResp selectById(@Param("ledgerId") Long ledgerId);
|
||||
|
||||
List<ReferralCommissionLedgerResp> selectList(ReferralCommissionLedgerSearchParam param);
|
||||
|
||||
/** 설계사 + 정산월 소개·이관수수료 합계 — commission_master 집계 배치 용도 */
|
||||
BigDecimal sumByAgentAndMonth(@Param("referrerAgentId") Long referrerAgentId,
|
||||
@Param("settleMonth") String settleMonth);
|
||||
|
||||
/** 정산월 기준 설계사별 합계 — settle_master 집계 배치 용도 */
|
||||
List<Map<String, Object>> aggregateByAgent(@Param("settleMonth") String settleMonth);
|
||||
|
||||
int insert(ReferralCommissionLedgerVO vo);
|
||||
|
||||
int updateStatus(@Param("ledgerId") Long ledgerId, @Param("status") String status);
|
||||
|
||||
int delete(@Param("ledgerId") Long ledgerId);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.ga.core.mapper.commission;
|
||||
|
||||
import com.ga.core.vo.commission.ReferralCommissionRuleResp;
|
||||
import com.ga.core.vo.commission.ReferralCommissionRuleSearchParam;
|
||||
import com.ga.core.vo.commission.ReferralCommissionRuleVO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* referral_commission_rule Mapper — 소개·이관수수료 룰 (V89)
|
||||
*/
|
||||
@Mapper
|
||||
public interface ReferralCommissionRuleMapper {
|
||||
|
||||
ReferralCommissionRuleResp selectById(@Param("ruleId") Long ruleId);
|
||||
|
||||
List<ReferralCommissionRuleResp> selectList(ReferralCommissionRuleSearchParam param);
|
||||
|
||||
/**
|
||||
* 유형 + 기준일 기준 유효 룰 1건 조회.
|
||||
* 수수료 계산 Service에서 호출.
|
||||
*/
|
||||
ReferralCommissionRuleVO selectActiveRule(@Param("referralType") String referralType,
|
||||
@Param("baseDate") LocalDate baseDate);
|
||||
|
||||
int insert(ReferralCommissionRuleVO vo);
|
||||
|
||||
int update(ReferralCommissionRuleVO vo);
|
||||
|
||||
int delete(@Param("ruleId") Long ruleId);
|
||||
}
|
||||
+4
@@ -8,6 +8,7 @@ import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Mapper
|
||||
public interface ReinstatementCommissionLedgerMapper {
|
||||
@@ -15,6 +16,9 @@ public interface ReinstatementCommissionLedgerMapper {
|
||||
List<ReinstatementCommissionLedgerResp> selectList(ReinstatementCommissionLedgerSearchParam param);
|
||||
BigDecimal sumByAgentAndMonth(@Param("agentId") Long agentId,
|
||||
@Param("settleMonth") String settleMonth);
|
||||
|
||||
/** 정산월 기준 설계사별 합계 — settle_master 집계 배치 용도 */
|
||||
List<Map<String, Object>> aggregateByAgent(@Param("settleMonth") String settleMonth);
|
||||
int insert(ReinstatementCommissionLedgerVO vo);
|
||||
int updateStatus(@Param("ledgerId") Long ledgerId, @Param("status") String status);
|
||||
int delete(@Param("ledgerId") Long ledgerId);
|
||||
|
||||
@@ -8,6 +8,7 @@ import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Mapper
|
||||
public interface RenewalCommissionLedgerMapper {
|
||||
@@ -15,6 +16,9 @@ public interface RenewalCommissionLedgerMapper {
|
||||
List<RenewalCommissionLedgerResp> selectList(RenewalCommissionLedgerSearchParam param);
|
||||
BigDecimal sumByAgentAndMonth(@Param("agentId") Long agentId,
|
||||
@Param("settleMonth") String settleMonth);
|
||||
|
||||
/** 정산월 기준 설계사별 합계 — settle_master 집계 배치 용도 */
|
||||
List<Map<String, Object>> aggregateByAgent(@Param("settleMonth") String settleMonth);
|
||||
int insert(RenewalCommissionLedgerVO vo);
|
||||
int updateStatus(@Param("ledgerId") Long ledgerId, @Param("status") String status);
|
||||
int delete(@Param("ledgerId") Long ledgerId);
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.ga.core.mapper.commission;
|
||||
|
||||
import com.ga.core.vo.commission.SettlingSupportLedgerResp;
|
||||
import com.ga.core.vo.commission.SettlingSupportLedgerSearchParam;
|
||||
import com.ga.core.vo.commission.SettlingSupportLedgerVO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* settling_support_ledger Mapper — 정착지원금 원장 (V90)
|
||||
*/
|
||||
@Mapper
|
||||
public interface SettlingSupportLedgerMapper {
|
||||
|
||||
SettlingSupportLedgerResp selectById(@Param("ledgerId") Long ledgerId);
|
||||
|
||||
List<SettlingSupportLedgerResp> selectList(SettlingSupportLedgerSearchParam param);
|
||||
|
||||
/** 설계사 + 정산월 실지급액 합계 */
|
||||
BigDecimal sumNetByAgentAndMonth(@Param("agentId") Long agentId,
|
||||
@Param("settleMonth") String settleMonth);
|
||||
|
||||
/** 정산월 기준 설계사별 합계 — settle_master 집계 배치 용도 */
|
||||
List<Map<String, Object>> aggregateByAgent(@Param("settleMonth") String settleMonth);
|
||||
|
||||
int insert(SettlingSupportLedgerVO vo);
|
||||
|
||||
int updateStatus(@Param("ledgerId") Long ledgerId, @Param("status") String status);
|
||||
|
||||
int delete(@Param("ledgerId") Long ledgerId);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.ga.core.mapper.commission;
|
||||
|
||||
import com.ga.core.vo.commission.SettlingSupportRuleResp;
|
||||
import com.ga.core.vo.commission.SettlingSupportRuleSearchParam;
|
||||
import com.ga.core.vo.commission.SettlingSupportRuleVO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* settling_support_rule Mapper — 정착지원금 룰 (V90)
|
||||
*/
|
||||
@Mapper
|
||||
public interface SettlingSupportRuleMapper {
|
||||
|
||||
SettlingSupportRuleResp selectById(@Param("ruleId") Long ruleId);
|
||||
|
||||
List<SettlingSupportRuleResp> selectList(SettlingSupportRuleSearchParam param);
|
||||
|
||||
/**
|
||||
* 설계사 등급 + 기준일 유효 룰 1건 조회.
|
||||
* 정착지원금 계산 Service에서 호출.
|
||||
*/
|
||||
SettlingSupportRuleVO selectActiveRule(@Param("agentGrade") String agentGrade,
|
||||
@Param("baseDate") LocalDate baseDate);
|
||||
|
||||
int insert(SettlingSupportRuleVO vo);
|
||||
|
||||
int update(SettlingSupportRuleVO vo);
|
||||
|
||||
int delete(@Param("ruleId") Long ruleId);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.ga.core.mapper.commission;
|
||||
|
||||
import com.ga.core.vo.commission.StepCloseResp;
|
||||
import com.ga.core.vo.commission.StepCloseSearchParam;
|
||||
import com.ga.core.vo.commission.StepCloseVO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* settlement_step_close Mapper.
|
||||
* 단계마감: 월별 정산 단계(수신→계산→검증→지급)의 OPEN/CLOSED 상태 관리.
|
||||
*/
|
||||
@Mapper
|
||||
public interface StepCloseMapper {
|
||||
|
||||
StepCloseResp selectById(@Param("stepCloseId") Long stepCloseId);
|
||||
|
||||
List<StepCloseResp> selectList(StepCloseSearchParam param);
|
||||
|
||||
/** 정산월 전체 단계 목록 (step_seq 순) */
|
||||
List<StepCloseResp> selectBySettleMonth(@Param("settleMonth") String settleMonth);
|
||||
|
||||
int insert(StepCloseVO vo);
|
||||
|
||||
/**
|
||||
* 단계 상태 변경 (close: CLOSED, reopen: OPEN).
|
||||
* closedAt/closedBy 는 SQL에서 NOW()/userId 처리.
|
||||
*/
|
||||
int updateStatus(@Param("stepCloseId") Long stepCloseId,
|
||||
@Param("status") String status,
|
||||
@Param("closedBy") Long closedBy);
|
||||
|
||||
int delete(@Param("stepCloseId") Long stepCloseId);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.ga.core.vo.commission;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* agent_grade_evaluation 조회 응답 — 설계사명/등급명 조인 포함.
|
||||
*/
|
||||
@Data
|
||||
public class AgentGradeEvaluationResp {
|
||||
private Long evalId;
|
||||
private Long agentId;
|
||||
private String agentName;
|
||||
private String orgName;
|
||||
private String evalPeriod;
|
||||
private BigDecimal performanceScore;
|
||||
private Long prevGradeId;
|
||||
private String prevGradeName;
|
||||
private Long evaluatedGradeId;
|
||||
private String evaluatedGradeName;
|
||||
private String status;
|
||||
private String statusName;
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.ga.core.vo.commission;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Positive;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* agent_grade_evaluation 저장 요청.
|
||||
* evaluatedGradeId/status 는 Service(등급산정 로직)가 결정.
|
||||
*/
|
||||
@Data
|
||||
public class AgentGradeEvaluationSaveReq {
|
||||
|
||||
@NotNull
|
||||
private Long agentId;
|
||||
|
||||
@NotBlank
|
||||
private String evalPeriod;
|
||||
|
||||
@NotNull
|
||||
@Positive
|
||||
private BigDecimal performanceScore;
|
||||
|
||||
/** 기존 등급 — 선택값 */
|
||||
private Long prevGradeId;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.ga.core.vo.commission;
|
||||
|
||||
import com.ga.common.model.SearchParam;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* agent_grade_evaluation 검색 조건.
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class AgentGradeEvaluationSearchParam extends SearchParam {
|
||||
private Long agentId;
|
||||
private String agentName;
|
||||
private String evalPeriod;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.ga.core.vo.commission;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* agent_grade_evaluation — 설계사 등급평가 원장 (V99)
|
||||
* 월별 실적점수 기반 등급 산정 결과.
|
||||
* 공식: evaluated_grade = score 이상인 최대 min_score 룰의 grade (Service 결정).
|
||||
*/
|
||||
@Data
|
||||
public class AgentGradeEvaluationVO {
|
||||
private Long evalId;
|
||||
/** 대상 설계사 FK */
|
||||
private Long agentId;
|
||||
/** 평가 기준월 (YYYYMM) */
|
||||
private String evalPeriod;
|
||||
/** 실적 점수 */
|
||||
private BigDecimal performanceScore;
|
||||
/** 기존(직전) 등급 FK */
|
||||
private Long prevGradeId;
|
||||
/** 산정 등급 FK */
|
||||
private Long evaluatedGradeId;
|
||||
/** EVALUATED=평가완료/APPLIED=반영완료 */
|
||||
private String status;
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.ga.core.vo.commission;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* agent_guarantee_insurance 조회 응답 — 설계사명/보험사명 조인 포함.
|
||||
*/
|
||||
@Data
|
||||
public class AgentGuaranteeInsuranceResp {
|
||||
private Long guaranteeId;
|
||||
private Long agentId;
|
||||
private String agentName;
|
||||
private String orgName;
|
||||
private String policyNo;
|
||||
private Long carrierId;
|
||||
private String carrierName;
|
||||
private BigDecimal guaranteeAmount;
|
||||
private LocalDate startDate;
|
||||
private LocalDate endDate;
|
||||
private String status;
|
||||
private String statusName;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.ga.core.vo.commission;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Positive;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* agent_guarantee_insurance 저장 요청.
|
||||
* status/createdAt/updatedAt 는 서버 결정.
|
||||
*/
|
||||
@Data
|
||||
public class AgentGuaranteeInsuranceSaveReq {
|
||||
|
||||
@NotNull
|
||||
private Long agentId;
|
||||
|
||||
@NotBlank
|
||||
private String policyNo;
|
||||
|
||||
/** 보증보험사 — 선택 */
|
||||
private Long carrierId;
|
||||
|
||||
@NotNull
|
||||
@Positive
|
||||
private BigDecimal guaranteeAmount;
|
||||
|
||||
@NotNull
|
||||
private LocalDate startDate;
|
||||
|
||||
@NotNull
|
||||
private LocalDate endDate;
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package com.ga.core.vo.commission;
|
||||
|
||||
import com.ga.common.model.SearchParam;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* agent_guarantee_insurance 검색 조건.
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class AgentGuaranteeInsuranceSearchParam extends SearchParam {
|
||||
private Long agentId;
|
||||
private String agentName;
|
||||
private Long carrierId;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.ga.core.vo.commission;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* agent_guarantee_insurance — 보증보험 (V101)
|
||||
* 설계사 신원보증보험 가입현황 추적(관리 목적, 공식 없음).
|
||||
*/
|
||||
@Data
|
||||
public class AgentGuaranteeInsuranceVO {
|
||||
private Long guaranteeId;
|
||||
/** 설계사 FK */
|
||||
private Long agentId;
|
||||
/** 증권번호 */
|
||||
private String policyNo;
|
||||
/** 보증보험사 FK (insurance_company.company_id) */
|
||||
private Long carrierId;
|
||||
private BigDecimal guaranteeAmount;
|
||||
private LocalDate startDate;
|
||||
private LocalDate endDate;
|
||||
/** ACTIVE=유효/EXPIRED=만료/CANCELLED=해지 */
|
||||
private String status;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.ga.core.vo.commission;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* agent_reserve_fund 조회 응답 — 설계사명 조인 포함.
|
||||
*/
|
||||
@Data
|
||||
public class AgentReserveFundResp {
|
||||
private Long reserveId;
|
||||
private Long agentId;
|
||||
private String agentName;
|
||||
private String orgName;
|
||||
private String settleMonth;
|
||||
private BigDecimal reserveIn;
|
||||
private BigDecimal reserveOut;
|
||||
private BigDecimal balance;
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user