035175e895
00_PROMPTS.md — Claude Code 실행 프롬프트 1~6 01_DB_SCHEMA.md — V1~V17 Flyway 스키마 + 프로젝트 뼈대 02_COMMON_SPEC.md — ga-common 공통 프레임워크 (모델/예외/MyBatis/AOP/엑셀/공통컴포넌트) 03_CORE_SPEC.md — ga-core VO 설계 원칙 + Mapper 패턴 04_BATCH_SPEC.md — ga-batch 정산 8 Step + DataReceiver/MappingEngine 05_API_SPEC.md — ga-api Controller/Service 표준 패턴 + 엔드포인트 목록 06_FRONTEND_SPEC.md — ga-frontend 13개 화면 + 공통 컴포넌트 사용법 07_INFRA_SPEC.md — Docker/Nginx + README + DEVELOPER_GUIDE 08_DBA_SPEC.md — V13~V15 인덱스/파티셔닝/뷰 실제 구현된 ga-commission-system 기준으로 작성. 코드는 변경 없음. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
314 lines
15 KiB
Markdown
314 lines
15 KiB
Markdown
# 01_DB_SCHEMA — DB 스키마 + 프로젝트 뼈대
|
|
|
|
## 1. 기술 스택
|
|
|
|
| 영역 | 기술 |
|
|
|---|---|
|
|
| Backend | Java 17, Spring Boot 3.2.5, Spring Security, Spring Batch |
|
|
| ORM | **MyBatis 3** + mybatis-spring-boot-starter:3.0.3 (JPA 아님) |
|
|
| 페이징 | pagehelper-spring-boot-starter:2.1.0 |
|
|
| DB | PostgreSQL 16 |
|
|
| Cache | Redis 7 (선택) |
|
|
| Frontend | React 18 + Vite + TS + Ant Design 5 + AG Grid Community + React Query + Zustand |
|
|
| Build | Gradle 멀티모듈 (Java 17 toolchain) |
|
|
| 기타 | Lombok, MapStruct 1.5.5, Flyway, Springdoc OpenAPI 2.3 |
|
|
|
|
## 2. 프로젝트 구조 (멀티모듈)
|
|
|
|
```
|
|
ga-commission-system/
|
|
├── settings.gradle # 모듈 7개 (ga-common/core/api/batch/admin/frontend)
|
|
├── build.gradle # subprojects { Lombok, MapStruct, JUnit5 }
|
|
├── gradle.properties
|
|
├── ga-common/ # 공통 프레임워크 (라이브러리, bootJar disabled)
|
|
├── ga-core/ # 도메인 코어 (VO/Mapper/XML, ga-common 의존)
|
|
├── ga-api/ # REST API (executable jar, 8080)
|
|
├── ga-batch/ # Spring Batch (executable jar, 8081)
|
|
├── ga-admin/ # (현재 빈 모듈, 향후 관리자 전용)
|
|
├── ga-frontend/ # Vite 빌드, dist/ → nginx
|
|
├── docker-compose.yml
|
|
├── Dockerfile
|
|
└── docs/ # 본 폴더
|
|
```
|
|
|
|
### 모듈 의존성
|
|
- `ga-core` → `ga-common`
|
|
- `ga-api` → `ga-core` (transitively `ga-common`)
|
|
- `ga-batch`→ `ga-core`
|
|
- `ga-admin`→ `ga-core` (현재 미사용)
|
|
|
|
### 핵심 build.gradle 설정
|
|
```groovy
|
|
// 루트 build.gradle subprojects
|
|
plugins {
|
|
id 'java'
|
|
id 'org.springframework.boot' version '3.2.5' apply false
|
|
id 'io.spring.dependency-management' version '1.1.4' apply false
|
|
}
|
|
java { toolchain { languageVersion = JavaLanguageVersion.of(17) } }
|
|
compileJava { options.compilerArgs << '-parameters' } // MyBatis 파라미터 이름
|
|
|
|
// ga-common: 라이브러리 (bootJar disabled, jar enabled)
|
|
// ga-api / ga-batch: bootJar enabled, jar disabled
|
|
```
|
|
|
|
### MyBatis 설정 (`ga-common/src/main/resources/mybatis/mybatis-config.xml`)
|
|
```xml
|
|
<configuration>
|
|
<settings>
|
|
<setting name="mapUnderscoreToCamelCase" value="true"/>
|
|
<setting name="cacheEnabled" value="false"/>
|
|
<setting name="callSettersOnNulls" value="true"/>
|
|
<setting name="defaultStatementTimeout" value="60"/>
|
|
<setting name="jdbcTypeForNull" value="NULL"/>
|
|
</settings>
|
|
<typeHandlers>
|
|
<!-- JsonTypeHandler 만 글로벌. EncryptTypeHandler 는 컬럼별 명시 -->
|
|
<typeHandler handler="com.ga.common.mybatis.JsonTypeHandler"/>
|
|
</typeHandlers>
|
|
</configuration>
|
|
```
|
|
|
|
### application.yml (ga-api)
|
|
```yaml
|
|
spring:
|
|
datasource:
|
|
url: jdbc:postgresql://localhost:5432/ga
|
|
username: ga
|
|
password: ga
|
|
hikari: { maximum-pool-size: 20 }
|
|
flyway:
|
|
locations: classpath:db/migration # ga-common 의 V1~V17
|
|
baseline-on-migrate: true
|
|
mybatis:
|
|
config-location: classpath:mybatis/mybatis-config.xml
|
|
mapper-locations: classpath*:mapper/**/*.xml
|
|
ga:
|
|
jwt:
|
|
secret: ${JWT_SECRET:replace-me-256-bit-or-longer-secret-key}
|
|
access-ttl-min: 120
|
|
refresh-ttl-day: 7
|
|
encrypt:
|
|
key: ${ENCRYPT_KEY:32byte-key-for-aes-256-cbc-aaaa} # 32 byte
|
|
```
|
|
|
|
---
|
|
|
|
## 3. Flyway 마이그레이션 V1~V17
|
|
|
|
위치: `ga-common/src/main/resources/db/migration/`
|
|
|
|
**모든 금액 `DECIMAL(15,2)`, 모든 수수료율 `DECIMAL(7,4)`. 모든 테이블에 인덱스 부여.**
|
|
|
|
### V1__조직_인사.sql (4 테이블)
|
|
```sql
|
|
grade (grade_id PK, grade_name, grade_level, grade_group, description, is_active, sort_order)
|
|
organization (org_id PK, parent_org_id FK self, org_name, org_type[HQ/BR/TM], org_level, region,
|
|
effective_from, effective_to, is_active, created_at, updated_at, created_by)
|
|
agent (agent_id PK, org_id FK, grade_id FK, agent_name, resident_no(AES), license_no,
|
|
phone, email, bank_code, account_no(AES), status[ACTIVE/LEAVE/SUSPEND],
|
|
join_date, leave_date, created_at, updated_at)
|
|
agent_org_history(history_id PK, agent_id FK, from_org_id, to_org_id, from_grade_id, to_grade_id,
|
|
change_type[TRANSFER/PROMOTE/DEMOTE/JOIN/LEAVE], change_date, change_reason)
|
|
```
|
|
인덱스: `idx_org_parent`, `idx_org_type(org_type,is_active)`, `idx_agent_org(org_id,status)`,
|
|
`idx_agent_lic(license_no)`, `idx_agent_status`.
|
|
|
|
### V2__상품_계약.sql (3 테이블)
|
|
```sql
|
|
insurance_company(company_id PK, company_code UQ, company_name, company_type, biz_no,
|
|
contact_name, contact_phone, contact_email, is_active)
|
|
product (product_id PK, company_id FK, product_code, product_name, insurance_type,
|
|
product_group, pay_period_type, is_active, launch_date, end_date)
|
|
contract (contract_id PK, agent_id FK, product_id FK, policy_no UQ, contractor_name,
|
|
insured_name, premium DECIMAL(15,2), pay_cycle, pay_period, coverage_period,
|
|
status, contract_date, effective_date, lapse_date, revival_date, created_at, updated_at)
|
|
```
|
|
|
|
### V3__수수료규정.sql (5 테이블)
|
|
```sql
|
|
commission_rate (rate_id PK, product_id FK, commission_year, rate_pct DECIMAL(7,4),
|
|
pay_method_cond, min_premium, effective_from, effective_to, version)
|
|
payout_rule (rule_id PK, grade_id FK, insurance_type, commission_year, payout_pct DECIMAL(7,4),
|
|
pay_method_cond, performance_grade, effective_from, effective_to, version)
|
|
override_rule (override_id PK, from_grade, to_grade, override_pct DECIMAL(7,4),
|
|
calc_type, insurance_type_cond, cap_amount, effective_from, effective_to)
|
|
chargeback_rule (cb_rule_id PK, company_code, insurance_type, lapse_month_from, lapse_month_to,
|
|
cb_rate DECIMAL(7,4), include_override, installment_allowed, max_installments,
|
|
effective_from, effective_to)
|
|
exception_type_code (exception_code PK, exception_name, category, direction[PLUS/MINUS],
|
|
auto_calc_yn, approval_required, tax_included, description, is_active)
|
|
```
|
|
|
|
### V4__데이터수신.sql (5 테이블)
|
|
```sql
|
|
company_profile (company_code PK FK, data_format, receive_method, file_encoding, delimiter,
|
|
header_row, data_start_row, sheet_name, date_format, amount_format,
|
|
api_url, ftp_host, ftp_path, receive_schedule, is_active)
|
|
company_field_mapping (mapping_id PK, company_code FK, source_field, source_col_index,
|
|
target_table, target_field, data_type, transform_rule, default_value,
|
|
is_required, sort_order, version, effective_from)
|
|
code_mapping (code_id PK, company_code, code_type, source_code, source_name,
|
|
target_code, target_name, is_active)
|
|
raw_commission_data (raw_id PK, company_code, batch_id, settle_month, data_type,
|
|
raw_content TEXT, parse_status[PENDING/OK/ERROR], mapped_ledger_id,
|
|
mapped_table, file_name, row_number, received_at, parsed_at)
|
|
parse_error_log (error_id PK, raw_id FK, company_code, error_type, error_field,
|
|
error_value, error_message, resolve_status[OPEN/RESOLVED/IGNORED],
|
|
resolved_by, resolve_note, resolved_at, created_at)
|
|
```
|
|
|
|
### V5__원장관리.sql (3 테이블, 동일 컬럼)
|
|
```sql
|
|
recruit_ledger / maintain_ledger / exception_ledger
|
|
공통 컬럼:
|
|
ledger_id PK, contract_id FK, agent_id FK, policy_no, company_code, product_code,
|
|
insurance_type, commission_year, premium DECIMAL(15,2), pay_method,
|
|
company_rate DECIMAL(7,4), company_amount DECIMAL(15,2),
|
|
payout_rate DECIMAL(7,4), agent_amount DECIMAL(15,2),
|
|
tax_amount DECIMAL(15,2),
|
|
reconcile_status[PENDING/MATCHED/DIFF], reconcile_diff,
|
|
advance_yn, advance_amount, chargeback_yn, settle_month, status[CALCULATED/CONFIRMED/HOLD],
|
|
version, created_at, updated_at
|
|
|
|
exception_ledger 추가:
|
|
exception_code, approve_status, approver_id, approve_date,
|
|
recurring_yn, recurring_until, recurring_parent_id
|
|
```
|
|
|
|
### V6__정산_지급.sql (4 테이블)
|
|
```sql
|
|
settle_master (settle_id PK, agent_id FK, settle_month,
|
|
recruit_total, maintain_total, exception_plus, exception_minus,
|
|
override_total, chargeback_total, gross_amount, tax_amount, net_amount,
|
|
status[DRAFT/CONFIRMED/HOLD/PAID], calc_date, confirmed_by, confirmed_at,
|
|
hold_reason, UNIQUE(agent_id, settle_month))
|
|
override_settle(os_id PK, settle_id FK, from_agent_id, to_agent_id, base_amount,
|
|
override_pct, override_amount, calc_basis)
|
|
chargeback (cb_id PK, contract_id FK, agent_id FK, lapse_month_diff, base_amount,
|
|
cb_rate, cb_amount, installment_seq, installment_total, status, created_at)
|
|
payment (payment_id PK, settle_id FK, agent_id FK, bank_code, account_no(AES),
|
|
amount, transfer_date, status[PENDING/SENT/SUCCESS/FAIL], result_code, result_msg)
|
|
```
|
|
|
|
### V7__배치_감사.sql (4 테이블)
|
|
```sql
|
|
batch_job_log (job_log_id PK, job_name, job_params, settle_month, status[REQUESTED/RUNNING/
|
|
COMPLETED/FAILED], step_name, total_count, success_count, error_count,
|
|
error_message, started_at, ended_at, duration_ms)
|
|
reconciliation (recon_id PK, company_code, settle_month, expected_amount, actual_amount,
|
|
diff_amount, recon_status, resolved_by, resolved_at)
|
|
audit_log (audit_id PK, user_id, action_type, target_table, target_id,
|
|
before_data JSONB, after_data JSONB, ip_address, user_agent, created_at)
|
|
users (user_id PK, login_id UQ, password(BCrypt), user_name, agent_id FK nullable,
|
|
email, phone, status[ACTIVE/LOCKED/EXPIRED], last_login_at,
|
|
fail_count, password_changed_at, created_at, updated_at)
|
|
```
|
|
|
|
### V8__초기데이터.sql
|
|
- grade 6 건 (FC/SFC/SMGR/DM/GM/SVP)
|
|
- exception_type_code 10 건 (ADJUST_PLUS / ADJUST_MINUS / BONUS / PENALTY / ...)
|
|
|
|
### V9__공통코드.sql (2 테이블)
|
|
```sql
|
|
common_code_group (group_code VARCHAR(20) PK, group_name, description, is_system CHAR(1),
|
|
is_active, sort_order, created_at, created_by, updated_at, updated_by)
|
|
common_code (code_id PK, group_code FK, code VARCHAR(30), code_name, code_name_en,
|
|
code_desc, attr1, attr2, attr3, ref_code, sort_order, is_active,
|
|
created_at, created_by, updated_at, updated_by,
|
|
UNIQUE(group_code, code))
|
|
```
|
|
|
|
### V10__메뉴관리.sql (5 테이블)
|
|
```sql
|
|
menu (menu_id PK, parent_menu_id FK self, menu_code UQ, menu_name,
|
|
menu_type[DIRECTORY/PAGE/LINK], menu_path, menu_icon, component_path,
|
|
menu_level, sort_order, is_visible, is_active, description)
|
|
menu_permission (perm_id PK, menu_id FK, perm_code[READ/CREATE/UPDATE/DELETE/APPROVE/
|
|
EXPORT/PRINT], perm_name, sort_order, UNIQUE(menu_id, perm_code))
|
|
role (role_id PK, role_code UQ, role_name, role_desc, role_level,
|
|
is_system, is_active)
|
|
role_menu_permission (rmp_id PK, role_id FK, menu_id FK, perm_code, is_granted,
|
|
UNIQUE(role_id, menu_id, perm_code))
|
|
user_role (ur_id PK, user_id FK, role_id FK, UNIQUE(user_id, role_id))
|
|
```
|
|
|
|
### V11__시스템관리.sql (6 테이블)
|
|
```sql
|
|
login_log (log_id PK, user_id, login_id, login_type[PWD/SSO], result[SUCCESS/FAIL],
|
|
fail_reason, ip_address, user_agent, login_at)
|
|
api_access_log (access_id PK, user_id, request_uri, http_method, params, response_code,
|
|
response_time_ms, ip_address, created_at) -- 비동기 저장
|
|
data_change_log (change_id PK, user_id, menu_code, table_name, record_id,
|
|
before_data JSONB, after_data JSONB, action[CREATE/UPDATE/DELETE], created_at)
|
|
file_storage (file_id PK, group_id, original_name, stored_name, file_path, file_size,
|
|
content_type, download_count, is_deleted, created_by, created_at)
|
|
notification (noti_id PK, user_id, noti_type, title, content, link_url, is_read, read_at, created_at)
|
|
system_config (config_key VARCHAR(50) PK, config_value, config_group, description,
|
|
is_encrypted, updated_by, updated_at)
|
|
```
|
|
|
|
### V12__공통_초기데이터.sql
|
|
- 공통코드 18 그룹 + 상세 42 건
|
|
(AGENT_STATUS / CONTRACT_STATUS / SETTLE_STATUS / PAY_CYCLE / INSURANCE_TYPE /
|
|
ORG_TYPE / GRADE_GROUP / RECONCILE_STATUS / PARSE_STATUS / EXCEPTION_DIRECTION /
|
|
APPROVE_STATUS / PAYMENT_STATUS / NOTI_TYPE / FILE_TYPE / DATA_FORMAT /
|
|
RECEIVE_METHOD / TRANSFORM_RULE / CHANGE_TYPE)
|
|
- 역할 4 건 (`SUPER_ADMIN` / `ADMIN` / `MANAGER` / `AGENT`)
|
|
- 대메뉴 10 건 + 시스템설정 13 건
|
|
|
|
### V13__인덱스최적화.sql
|
|
- `recruit_ledger(settle_month, agent_id)`, `recruit_ledger(policy_no)`
|
|
- `maintain_ledger(settle_month, agent_id)`, `maintain_ledger(policy_no)`
|
|
- `exception_ledger(settle_month, approve_status)`
|
|
- `settle_master(settle_month, status)`
|
|
- `contract(agent_id, status)`, `contract(policy_no)`
|
|
- `raw_commission_data(company_code, settle_month, parse_status)`
|
|
- `api_access_log(created_at)`, `data_change_log(created_at, table_name)`
|
|
|
|
### V14__파티셔닝.sql
|
|
- 헬퍼 함수 `create_monthly_partition(table_name, year_month)` 정의
|
|
- `recruit_ledger / maintain_ledger / api_access_log / data_change_log` 를
|
|
`settle_month` 또는 `created_at` 기준 RANGE 파티션으로 변환 (PG 13+)
|
|
|
|
### V15__뷰.sql
|
|
```sql
|
|
v_agent_monthly_summary (agent_id, agent_name, org_name, settle_month,
|
|
recruit_amt, maintain_amt, exception_plus, exception_minus,
|
|
override_amt, chargeback_amt, gross, tax, net, status)
|
|
v_org_settle_summary (org_id, org_name, settle_month, agent_count, total_gross, total_net)
|
|
v_chargeback_risk (contract_id, policy_no, agent_name, lapse_month_diff,
|
|
predicted_cb_amount, risk_grade)
|
|
```
|
|
|
|
### V16__관리자_초기데이터.sql
|
|
- `users` 에 `admin / admin1234!` (BCrypt) + `SUPER_ADMIN` 역할 매핑
|
|
- 메뉴 33건 + 메뉴별 권한 150건 + 역할-메뉴-권한 매트릭스 323건
|
|
|
|
### V17__테스트데이터.sql
|
|
- 보험사 5 (SAMSUNG / HANWHA / KYOBO / DB / KB)
|
|
- 상품 20 (보험사당 4)
|
|
- 조직 7 (본부 1 / 지점 3 / 팀 3) + 설계사 50
|
|
- 계약 200 (ACTIVE 190 / LAPSE 10)
|
|
- 수수료 규정: commission_rate 100 / payout_rule 120 / override_rule 10 / chargeback_rule 4
|
|
|
|
---
|
|
|
|
## 4. 환경변수 / 시크릿
|
|
|
|
| 키 | 기본값 | 설명 |
|
|
|---|---|---|
|
|
| `JWT_SECRET` | `replace-me-...` | HS256 256bit+ |
|
|
| `ENCRYPT_KEY` | `32byte-key-...` | AES-256 키 (32 byte) |
|
|
| `DB_HOST` / `DB_PORT` / `DB_NAME` / `DB_USERNAME` / `DB_PASSWORD` | localhost/5432/ga/ga/ga | DataSource |
|
|
| `REDIS_HOST` / `REDIS_PORT` | localhost / 6379 | 선택 (캐시) |
|
|
|
|
운영 DB 프로파일 (`application-trading_ai.yml`):
|
|
```yaml
|
|
spring.datasource:
|
|
url: jdbc:postgresql://192.168.0.60:55432/trading_ai?currentSchema=ga
|
|
username: kyu
|
|
password: ${DB_PASSWORD}
|
|
spring.flyway.schemas: ga
|
|
```
|