
안녕하세요, 베테랑 보험 IT 컨설턴트입니다. 보험 회사의 시스템, 즉 보험 ERP는 단순히 여러 기능을 모아놓은 창고가 아닙니다. 그것은 하나의 생명체처럼, 데이터가 끊임없이 흐르고 서로 영향을 주고받는 거대한 강과 같습니다.
이 문서에서는 보험 상품이라는 씨앗이 뿌려져 고객과의 계약이라는 열매를 맺고, 사고 처리와 회계 결산이라는 과정을 거쳐 한 해의 농사를 마무리하기까지의 전 과정을 하나의 이야기처럼 따라가려 합니다. 보험 ERP의 진짜 핵심은 이처럼 '데이터의 연쇄 작용을 자동화'하는 데 있습니다. 각 단계가 어떻게 꼬리에 꼬리를 물고 이어지는지, 그 데이터의 여정을 함께 따라가 보겠습니다.
--------------------------------------------------------------------------------
1단계: 모든 이야기의 시작, '상품 출시'
모든 보험은 '상품'에서 시작합니다. 보험사는 고객에게 어떤 위험을, 얼마 동안, 얼마의 가격으로 보장해 줄지 결정해야 합니다. 이 단계는 마치 건물의 설계도를 그리는 것과 같습니다.
- 핵심 활동: 상품의 보장 내용, 보험 기간, 가입 연령 등 수많은 규칙(Rule)을 시스템에 정의합니다. 이때 중요한 것은 규칙을 코드에 직접 박아 넣는 '하드코딩' 방식이 아니라, 유연하게 규칙을 관리할 수 있는 **'룰 엔진(Rule Engine)'**을 기반으로 설계하는 것입니다. 이를 통해 고객의 성별, 연령, 직업 등에 따른 복잡한 보험료 산출 로직을 시스템 수정 없이 신속하게 반영할 수 있습니다.
- 핵심 데이터: 이 과정을 통해 상품 코드라는 고유 식별자와 보험료 산출 로직(Pricing)이라는 핵심 데이터가 생성됩니다. 이 데이터는 앞으로 체결될 모든 계약의 절대적인 기준이 됩니다.
- 중요성: 이 단계에서 상품의 뼈대가 잘못 설계되면, 이후에 발생하는 모든 계약, 수수료, 보험금 지급, 회계 처리에 연쇄적인 문제를 일으키게 됩니다.
모든 데이터 여정의 첫 단추입니다. 여기서 정의된 규칙이 앞으로 맺어질 수백만 계약의 기준이 됩니다.
이렇게 탄생한 상품은 이제 고객을 만나러 갈 준비를 마쳤습니다. 다음 단계에서는 이 상품이 어떻게 고객에게 전달되는지 살펴보겠습니다.
--------------------------------------------------------------------------------
2단계: 고객과의 첫 만남, '설계사 판매'
잘 만들어진 상품도 고객에게 전달되지 않으면 의미가 없습니다. 여기서 보험 설계사가 중요한 역할을 합니다. 설계사는 고객과 회사를 잇는 다리이며, 이들의 활동은 시스템에 중요한 데이터 흔적을 남깁니다.
- 핵심 활동: 설계사는 고객의 필요에 맞는 상품을 제안하고, 고객이 가입을 결정하면 청약 절차를 진행합니다. 이들의 채용, 교육, 위촉 상태 등 조직 정보 역시 시스템에서 관리됩니다.
- 핵심 데이터: 판매가 이루어지는 순간, 설계사에게 지급될 수수료(수당) 데이터가 발생합니다. 이 수수료는 상품의 종류, 납입 회차, 판매 실적 등에 따라 매우 복잡한 체계로 계산되며, 시스템은 이 계산을 자동화해야 합니다.
- 중요성: 정확하고 시기적절한 수수료 지급은 설계사의 동기부여와 직결됩니다. 동시에, 고객이 계약을 너무 빨리 해지할 경우 이미 지급된 수수료를 다시 회수('환수')하는 리스크 관리 로직 또한 필수적입니다.
설계사를 통해 청약서가 접수되었습니다. 하지만 이것이 바로 계약 성사를 의미하는 것은 아닙니다. 회사는 이제 고객이 정말 보험에 가입할 자격이 있는지 꼼꼼히 심사해야 합니다.
--------------------------------------------------------------------------------
3단계: 약속의 증표, '계약 체결'
고객의 청약은 회사에 두 가지 중요한 과제를 던져줍니다. 하나는 '이 계약을 받아들일 것인가?'라는 심사이고, 다른 하나는 '계약을 받아들인다면 미래의 책임을 어떻게 준비할 것인가?'라는 회계적 문제입니다.
- 가입 심사 (언더라이팅)
- 무엇을 하나요? 고객의 가입 요청(청약)에 대해 회사가 승인, 조건부 승인, 또는 거절을 결정하는 과정입니다. 고객의 건강 상태, 직업 등을 평가하여 보험사가 감당할 위험 수준을 판단합니다.
- 왜 중요한가요? 위험이 너무 큰 고객을 무분별하게 가입시키면 향후 막대한 보험금 지출로 이어져 회사의 손실을 초래할 수 있습니다. 따라서 언더라이팅은 보험사의 재무 건전성을 지키는 첫 번째 관문입니다.
- 부채의 시작 (준비금 적립)
- 무엇을 하나요? 계약이 최종적으로 성사되는 순간, 보험사는 미래에 고객에게 지급해야 할 보험금을 위해 '책임준비금'이라는 돈을 쌓기 시작합니다. 이는 회계 장부상 자산이 아닌 **'부채'**로 기록됩니다.
- 핵심 데이터: 언더라이팅을 통과하면 정상 계약 데이터가 생성되고, 동시에 미래의 약속 이행을 위한 책임준비금 데이터가 발생하며 회계 시스템과 연결됩니다.
이제 회사는 고객으로부터 매달 보험료를 받게 되었습니다. 이 소중한 돈을 금고에 쌓아두기만 할까요? 당연히 아닙니다. 이 돈을 '운용'하여 더 큰 가치를 만들어내야 합니다.
--------------------------------------------------------------------------------
4단계: 돈이 일하게 하라, '자금 운용'
고객에게 받은 보험료는 미래에 지급할 보험금의 재원이지만, 동시에 회사가 수익을 창출할 수 있는 중요한 '투자 자산'입니다. 이 자금을 효율적으로 운용하는 것은 보험사의 핵심 역량 중 하나입니다.
- 수익 창출 고객의 보험료를 주식, 채권, 부동산, 대출 등 다양한 금융 상품에 투자하여 이자나 배당, 시세 차익과 같은 추가 수익을 만들어내는 것이 목표입니다.
- 미래 지급 재원 마련 자산 운용을 통해 불어난 자금은 훗날 고객에게 약속한 보험금을 지급할 수 있는 든든한 기반이 됩니다. 안정적인 자금 운용은 회사의 지급 능력을 보장하는 핵심 요소입니다.
- 핵심 데이터 이 과정에서 회사가 어떤 자산에 얼마나 투자하고 있는지를 보여주는 투자 자산 포트폴리오와 그 성과를 나타내는 운용 수익률 같은 데이터가 체계적으로 관리됩니다. 이 수익률 데이터는 마지막 단계인 회계 결산에서 회사의 최종 손익을 계산하는 데 결정적인 역할을 합니다.
평화롭게 자금이 운용되던 어느 날, 고객으로부터 다급한 연락이 옵니다. 바로 '사고'가 발생한 것입니다. 지금부터 보험의 존재 이유가 빛을 발하는 순간이 시작됩니다.
--------------------------------------------------------------------------------
5단계: 위험의 순간, '사고 접수'
고객이 사고를 당했을 때, 보험사는 신속하고 정확하게 대응해야 합니다. 사고 접수는 그 첫 단계이며, 이때부터 보이지 않는 곳에서 또 다른 시스템이 바쁘게 움직이기 시작합니다.
- 핵심 활동: 고객은 콜센터, 홈페이지, 모바일 앱 등 다양한 온/오프라인 채널을 통해 사고 발생 사실을 알리고, 진단서나 수리 견적서 같은 증빙 서류를 제출합니다.
- FDS의 역할: 사고가 접수되는 즉시, **보험사기방지시스템(FDS, Fraud Detection System)**이 가동됩니다. FDS는 과거의 수많은 사고 데이터 패턴을 학습하여, 이번 사고가 혹시 비정상적이거나 의심스러운 점은 없는지 분석하는 '감시자' 역할을 수행합니다. 예를 들어, 특정 병원에서 특정 사고가 너무 자주 접수되는 등의 이상 징후를 포착합니다.
- 핵심 데이터: 접수가 완료되면 고유한 사고 접수 번호가 부여되고, 시스템은 자동으로 FDS 분석 결과 데이터를 생성하여 담당자에게 전달합니다.
사고 접수와 초기 분석이 끝났습니다. 이제 회사는 조사를 통해 실제로 얼마의 보험금을 지급해야 할지 결정해야 합니다.
--------------------------------------------------------------------------------
6단계: 약속의 이행, '보험금 지급'
사고 조사를 통해 지급할 금액을 확정하고 고객에게 전달하는, 보험의 가장 본질적인 단계입니다. 이 과정은 투명성과 속도가 생명이며, 동시에 회사의 리스크를 관리하는 절차도 함께 진행됩니다.
보험금 지급
손해 사정(사고의 원인과 손해액을 조사하고 과실 비율을 산정하는 과정)을 거쳐 최종적으로 지급할 보험금이 결정됩니다. 이후 심사팀의 승인을 받아 고객에게 보험금을 송금합니다. 이 모든 과정은 고객에게 투명하게 공유되어야 합니다.
재보험금 청구
만약 이번 사고가 대형 화재나 재해처럼 한 회사가 감당하기 어려운 매우 큰 규모의 사고라면, 미리 보험을 들어놓은 다른 보험사, 즉 '재보험사'에 보험금을 청구하는 절차가 함께 진행됩니다. 이를 통해 회사가 입게 될 거대한 손실을 분산시킬 수 있습니다.
- 핵심 데이터: 이 단계의 최종 결과물은 고객에게 나간 최종 지급 보험금 데이터와, 회사의 손실을 보전하기 위해 재보험사로부터 받게 될 재보험금 데이터입니다.
고객과의 약속을 지키고, 회사의 위험 부담까지 관리했습니다. 이제 1년 동안 일어난 이 모든 활동을 정리하고, 회사의 최종 성적표를 작성할 시간이 되었습니다.
--------------------------------------------------------------------------------
7단계: 모든 여정의 마무리, '회계 결산'
회계 결산은 1년간의 모든 경영 활동을 숫자로 정리하여 회사의 최종 성적표를 만드는 과정입니다. 단순히 돈이 얼마나 들어오고 나갔는지를 계산하는 것을 넘어, 회사의 미래 가치를 평가하는 중요한 작업입니다.
- 무엇을 하나요?: 1년 동안 벌어들인 돈(수입 보험료, 4단계 자금 운용에서 발생한 투자 수익 등)과 쓴 돈(지급 보험료, 사업비, 수수료 등)을 모두 종합하여 회사의 최종 이익과 손실을 계산합니다.
- 왜 중요한가요?: 특히 IFRS17이라는 새로운 국제회계기준에 따라, 회사가 미래에 고객에게 갚아야 할 빚(보험 부채)을 현재 시점의 가치로 정확하게 평가하는 것이 핵심입니다. 이를 통해 투자자나 감독기관은 이 회사가 얼마나 재무적으로 건전한지 투명하게 파악할 수 있습니다.
- 핵심 결과물: 이 모든 복잡한 과정을 거쳐 재무제표라는 회사의 최종 성적표가 만들어집니다.
--------------------------------------------------------------------------------
결론: 데이터로 흐르는 보험의 강

지금까지 우리는 보험 상품의 탄생부터 소멸까지, 7단계에 걸친 데이터의 여정을 함께했습니다. 각 단계는 독립적으로 보이지만, 사실은 서로 긴밀하게 연결되어 있습니다. 아래 표는 전체 흐름을 한눈에 보여줍니다.
| 단계 | 핵심 활동 | 생성되는 주요 데이터 |
| 1. 상품 출시 | 보험 상품의 규칙과 가격 정의 | 상품 코드, 보험료 산출 로직 |
| 2. 설계사 판매 | 고객에게 상품을 판매하고 청약 진행 | 발생 수수료 데이터 |
| 3. 계약 체결 | 가입 심사 및 계약 확정 | 정상 계약 데이터, 책임준비금 |
| 4. 자금 운용 | 받은 보험료를 투자하여 수익 창출 | 투자 포트폴리오, 운용 수익률 |
| 5. 사고 접수 | 고객의 사고 신고 접수 및 사기 분석 | 사고 접수 번호, FDS 분석 결과 |
| 6. 보험금 지급 | 손해 사정 후 보험금 지급, 재보험 처리 | 최종 지급 보험금, 재보험금 |
| 7. 회계 결산 | 1년간의 손익을 최종 계산 | 재무제표 |
결국 보험 ERP는 단순히 개별 업무를 처리하는 도구가 아닙니다. '상품 출시'에서 발생한 데이터가 '설계사 판매'를 거쳐 '계약'으로 이어지고, 그 돈이 '운용'되다가 '사고'를 만나 '보험금'으로 지급된 후, 최종적으로 '회계 결산'이라는 종착지에 도달하기까지, 모든 데이터가 꼬리에 꼬리를 물고 이어지는 거대한 흐름을 관장하는 '중추 신경계'와 같습니다. 이 흐름을 이해하는 것이 바로 보험 비즈니스의 핵심을 꿰뚫어 보는 첫걸음입니다.
보험 ERP 데이터베이스 설계 (Part 1: 상품, 판매 및 계약)
보험 시스템은 데이터 간의 참조 무결성이 매우 중요합니다. 모든 테이블은 created_at, updated_at 등의 감사(Audit) 컬럼을 공통으로 가진다고 가정합니다.
1단계: 상품 출시 (Product & Rule Engine)
상품은 모든 데이터의 기준(Master)이 됩니다.
-- 1. 보험 상품 마스터
CREATE TABLE products (
product_code VARCHAR(20) PRIMARY KEY, -- 상품코드
product_name VARCHAR(100) NOT NULL, -- 상품명
product_type ENUM('LIFE', 'NON_LIFE'), -- 생보/손보 구분
sale_start_date DATE, -- 판매시작일
sale_end_date DATE, -- 판매종료일
status ENUM('ACTIVE', 'INACTIVE') -- 상태
);
-- 2. 상품별 룰 정의 (Rule Engine 기초 데이터)
CREATE TABLE product_rules (
rule_id INT AUTO_INCREMENT PRIMARY KEY,
product_code VARCHAR(20),
rule_type ENUM('AGE_LIMIT', 'PREMIUM_CALC', 'COVERAGE_LIMIT'), -- 가입연령, 보험료산출, 보장한도 등
rule_value JSON, -- 복잡한 로직을 JSON 형태로 저장하여 유연성 확보
FOREIGN KEY (product_code) REFERENCES products(product_code)
);
2단계: 설계사 판매 (Sales & Channel)
누가 판매했는지와 그에 따른 수당 계산을 위한 기초 정보를 저장합니다.
-- 3. 설계사(채널) 정보
CREATE TABLE agents (
agent_id VARCHAR(20) PRIMARY KEY,
agent_name VARCHAR(50),
branch_name VARCHAR(50), -- 소속 지점
commission_rate DECIMAL(5,2), -- 기본 수수료율
status ENUM('ACTIVE', 'RETIRED')
);
-- 4. 수수료 발생 내역 (데이터 연쇄의 시작)
CREATE TABLE commissions (
comm_id BIGINT AUTO_INCREMENT PRIMARY KEY,
contract_id VARCHAR(30), -- 3단계의 계약 ID와 연결
agent_id VARCHAR(20),
amount DECIMAL(15, 2), -- 계산된 수수료
payment_date DATE, -- 지급 예정일
status ENUM('PENDING', 'PAID', 'RECALLED'), -- 지급대기, 지급완료, 환수
FOREIGN KEY (agent_id) REFERENCES agents(agent_id)
);
3단계: 계약 체결 (Contract & Underwriting)
심사 결과와 함께 부채(준비금)의 시작을 기록합니다.
-- 5. 고객 정보 (피보험자/계약자)
CREATE TABLE customers (
customer_id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50),
rrn_hash VARCHAR(255), -- 주민번호 해시값 (보안)
job_code VARCHAR(10),
health_status TEXT
);
-- 6. 보험 계약 마스터 (핵심 테이블)
CREATE TABLE contracts (
contract_id VARCHAR(30) PRIMARY KEY, -- 계약번호
product_code VARCHAR(20),
customer_id INT,
agent_id VARCHAR(20),
contract_date DATE,
premium_amount DECIMAL(15, 2), -- 보험료
policy_status ENUM('PENDING', 'ACTIVE', 'CANCELED', 'EXPIRED'),
uw_result ENUM('PASS', 'CONDITION', 'REJECT'), -- 언더라이팅 결과
FOREIGN KEY (product_code) REFERENCES products(product_code),
FOREIGN KEY (customer_id) REFERENCES customers(customer_id),
FOREIGN KEY (agent_id) REFERENCES agents(agent_id)
);
-- 7. 책임준비금 (회계 부채 기록)
CREATE TABLE liability_reserves (
reserve_id BIGINT AUTO_INCREMENT PRIMARY KEY,
contract_id VARCHAR(30),
base_date DATE, -- 산출 기준일
reserve_amount DECIMAL(18, 2), -- 적립금액 (부채)
FOREIGN KEY (contract_id) REFERENCES contracts(contract_id)
);
설계의 특징
- JSON 활용: product_rules 테이블에 JSON 타입을 사용하여 하드코딩 없이 다양한 상품 로직을 수용할 수 있게 설계했습니다.
- 상태값 관리: 계약의 생애주기(policy_status)와 수수료 상태(status)를 구분하여 추후 환수(Clawback) 로직에 대응할 수 있습니다.
- 회계 연동: liability_reserves를 통해 계약 체결 시점부터 부채를 데이터화하여 IFRS17 결산의 기초를 마련했습니다.
보험 ERP 데이터베이스 설계 (Part 2: 운용, 사고 및 지급)
4단계: 자금 운용 (Investment Asset Management)
고객으로부터 받은 보험료를 자산별로 나누어 관리하고 수익률을 추적합니다.
-- 8. 투자 자산 마스터
CREATE TABLE investment_assets (
asset_id VARCHAR(20) PRIMARY KEY,
asset_name VARCHAR(100),
asset_type ENUM('STOCK', 'BOND', 'REAL_ESTATE', 'LOAN'), -- 주식, 채권, 부동산, 대출 등
purchase_date DATE,
purchase_price DECIMAL(18, 2),
current_value DECIMAL(18, 2) -- 현재 평가액
);
-- 9. 자산별 수익 기록
CREATE TABLE asset_returns (
return_id BIGINT AUTO_INCREMENT PRIMARY KEY,
asset_id VARCHAR(20),
record_date DATE,
return_amount DECIMAL(18, 2), -- 배당금, 이자, 매각차익 등
return_rate DECIMAL(5, 2), -- 수익률
FOREIGN KEY (asset_id) REFERENCES investment_assets(asset_id)
);
5단계: 사고 접수 및 FDS (Claim Intake & Fraud Detection)
사고가 접수됨과 동시에 사기 여부를 판단하는 데이터가 생성됩니다.
-- 10. 사고 접수 마스터
CREATE TABLE claim_receptions (
reception_id VARCHAR(30) PRIMARY KEY, -- 접수번호
contract_id VARCHAR(30),
incident_date DATETIME, -- 사고발생일시
report_date DATETIME, -- 신고일시
incident_location VARCHAR(255),
incident_description TEXT,
status EN TABLE_UM('RECEIVED', 'INVESTIGATING', 'APPROVED', 'REJECTED'),
FOREIGN KEY (contract_id) REFERENCES contracts(contract_id)
);
-- 11. FDS(보험사기방지) 분석 결과
CREATE TABLE fds_analysis (
fds_id BIGINT AUTO_INCREMENT PRIMARY KEY,
reception_id VARCHAR(30),
risk_score INT, -- 사기 위험 점수 (0-100)
fraud_type_tags VARCHAR(100), -- 의심 패턴 태그 (예: 다수지급, 단기집중)
analysis_report JSON, -- 상세 분석 결과
is_suspicious BOOLEAN DEFAULT FALSE,
FOREIGN KEY (reception_id) REFERENCES claim_receptions(reception_id)
);
6단계: 보험금 지급 및 재보험 (Claim Payout & Reinsurance)
최종 지급액을 확정하고, 대형 사고 시 재보험사로부터 받을 돈을 계산합니다.
-- 12. 보험금 지급 상세
CREATE TABLE claim_payouts (
payout_id BIGINT AUTO_INCREMENT PRIMARY KEY,
reception_id VARCHAR(30),
payout_amount DECIMAL(18, 2), -- 실제 지급액
loss_adjustment_fee DECIMAL(15, 2), -- 손해사정비용
payment_date DATE,
recipient_name VARCHAR(50), -- 수령인
FOREIGN KEY (reception_id) REFERENCES claim_receptions(reception_id)
);
-- 13. 재보험 처리 (Reinsurance)
CREATE TABLE reinsurance_claims (
ri_claim_id BIGINT AUTO_INCREMENT PRIMARY KEY,
payout_id BIGINT,
reinsurer_name VARCHAR(50), -- 재보험사명 (예: 코리안리)
recovery_amount DECIMAL(18, 2), -- 재보험사로부터 회수할 금액
status ENUM('PENDING', 'RECOVERED'),
FOREIGN KEY (payout_id) REFERENCES claim_payouts(payout_id)
);
설계의 핵심 포인트 (Part 2)
- 리스크 스코어링: fds_analysis 테이블을 통해 사고 접수 시점의 데이터가 수치화되어 심사자에게 즉시 전달됩니다.
- 비용 분산: reinsurance_claims 테이블은 대형 손실 데이터가 회사의 재무제표에 미치는 충격을 완화하는 과정을 기록합니다.
- 자산-부채 매칭: 4단계의 자산 수익 데이터와 3단계의 준비금(부채) 데이터를 비교함으로써 회사의 안정성을 평가할 수 있는 구조입니다.
보험 ERP 데이터베이스 설계 (Part 3: 회계 결산 및 통합)
7단계: 회계 결산 (Accounting & IFRS17)
단순한 입출금 기록을 넘어, 미래의 가치를 현재로 환산하고 통합적인 손익을 산출합니다.
-- 14. 계정 과목 마스터 (Chart of Accounts)
CREATE TABLE account_subjects (
account_code VARCHAR(10) PRIMARY KEY, -- 계정코드 (예: 1100-현금, 2100-보험계약부채)
account_name VARCHAR(50), -- 계정명
account_type ENUM('ASSET', 'LIABILITY', 'EQUITY', 'REVENUE', 'EXPENSE'), -- 자산, 부채, 자본, 수익, 비용
is_ifrs17_relevant BOOLEAN -- IFRS17 관련 계정 여부
);
-- 15. 통합 전표 (General Ledger) - 모든 단계의 데이터가 모이는 곳
CREATE TABLE general_ledger (
journal_id BIGINT AUTO_INCREMENT PRIMARY KEY,
transaction_date DATE, -- 거래 발생일
account_code VARCHAR(10), -- 계정코드
debit_amount DECIMAL(18, 2) DEFAULT 0, -- 차변 (들어온 돈/자산 증가)
credit_amount DECIMAL(18, 2) DEFAULT 0, -- 대변 (나간 돈/부채 증가)
reference_id VARCHAR(50), -- 참조 ID (계약ID, 사고ID, 투자ID 등)
description TEXT, -- 적요
FOREIGN KEY (account_code) REFERENCES account_subjects(account_code)
);
-- 16. 월별/연도별 결산 요약 (Financial Closing)
CREATE TABLE financial_statements (
statement_id BIGINT AUTO_INCREMENT PRIMARY KEY,
closing_period VARCHAR(7), -- 결산월 (예: 2024-12)
total_assets DECIMAL(20, 2), -- 총 자산
total_liabilities DECIMAL(20, 2), -- 총 부채 (준비금 포함)
net_income DECIMAL(20, 2), -- 당기순이익
csm_amount DECIMAL(20, 2), -- 보험계약마진 (CSM, IFRS17 핵심 지표)
status ENUM('OPEN', 'CLOSED') -- 결산 확정 여부
);
🏁 보험 ERP 데이터 여정의 완성 (데이터 연쇄 작용 요약)
이제 1단계부터 7단계까지 구축된 테이블들이 어떻게 '꼬리에 꼬리를 무는지' 흐름을 정리해 보겠습니다.
| 단계 | 데이터 발생원 (Source) | 회계 처리 (General Ledger) | 비고 |
| 1~3단계 | contracts, commissions | (차) 미수보험료 / (대) 보험계약수익, (차) 수당비용 | 매출의 발생과 비용 예약 |
| 4단계 | asset_returns | (차) 현금 / (대) 투자수익 | 자금 운용 성과 반영 |
| 5~6단계 | claim_payouts | (차) 보험금비용 / (대) 현금 | 약속 이행 및 현금 유출 |
| 7단계 | 위 모든 테이블 합산 | 재무제표(BS, PL) 생성 | IFRS17 기준 최종 결산 |
설계의 최종 포인트
- 데이터 통합: general_ledger 테이블의 reference_id를 통해 이 전표가 어느 계약에서 왔는지, 어떤 사고 때문인지 역추적(Drill-down)이 가능해야 합니다.
- IFRS17 대응: 단순히 들어온 돈을 수익으로 잡지 않고, financial_statements의 csm_amount(미래에 실현될 이익)처럼 복잡한 지표를 산출할 수 있는 기초 데이터를 보존합니다.
- 무결성: 모든 자금 흐름은 차변(Debit)과 대변(Credit)이 일치해야 하며, 이는 시스템적으로 자동 검증되어야 합니다.
보험 ERP의 핵심 지능이라고 할 수 있는 [설계사 수수료 자동 계산] 로직과 [FDS 위험 점수 산출] 로직을 구체적인 SQL과 알고리즘 관점에서 설계해 드리겠습니다.
1. 설계사 수수료 자동 계산 로직
수수료는 상품별 요율, 납입 회차, 계약 상태에 따라 연쇄적으로 계산되어야 합니다.
[수수료 계산 프로시저 로직 생성 예시]
계약이 '정상'이고 보험료가 납입되었을 때, 상품 마스터의 룰을 참조하여 수수료를 생성하는 로직입니다.
-- 수수료 자동 산출 쿼리 예시
INSERT INTO commissions (contract_id, agent_id, amount, payment_date, status)
SELECT
c.contract_id,
c.agent_id,
-- 보험료 * 상품별 수수료율 * 설계사 개별 수수료율 (계단식 계산)
(c.premium_amount * (CAST(JSON_EXTRACT(pr.rule_value, '$.comm_rate') AS DECIMAL(5,2)) / 100) * a.commission_rate) AS calculated_comm,
DATE_ADD(CURDATE(), INTERVAL 1 MONTH) AS next_pay_date, -- 다음 달 지급
'PENDING'
FROM contracts c
JOIN agents a ON c.agent_id = a.agent_id
JOIN product_rules pr ON c.product_code = pr.product_code
WHERE c.policy_status = 'ACTIVE'
AND pr.rule_type = 'COMMISSION_RULE' -- 수수료 관련 룰만 참조
AND NOT EXISTS (SELECT 1 FROM commissions WHERE contract_id = c.contract_id); -- 중복 생성 방지
핵심 포인트: JSON_EXTRACT를 사용하여 상품 출시 단계에서 정의한 유연한 룰(rule_value)을 실시간으로 계산에 반영합니다.
2. FDS 위험 점수 산출 로직
FDS는 사고 접수 즉시 과거 패턴을 분석하여 점수를 매깁니다. 여기서는 3가지 대표적인 의심 패턴을 기준으로 점수를 합산하는 방식을 제안합니다.
[FDS 스코어링 가중치 모델]
- 단기 사고 (가중치 40점): 계약 후 1개월 이내 사고 발생
- 다수 사고 (가중치 30점): 해당 고객이 최근 1년 내 3회 이상 사고 접수
- 특이 병원/업체 (가중치 30점): 블랙리스트에 등록된 병원/정비소 이용
[FDS 위험 점수 업데이트 쿼리]
UPDATE fds_analysis f
JOIN claim_receptions cr ON f.reception_id = cr.reception_id
JOIN contracts c ON cr.contract_id = c.contract_id
SET f.risk_score = (
-- 1. 단기 사고 체크
CASE WHEN DATEDIFF(cr.incident_date, c.contract_date) <= 30 THEN 40 ELSE 0 END +
-- 2. 과거 사고 이력 체크 (서브쿼리 활용)
CASE WHEN (SELECT COUNT(*) FROM claim_receptions WHERE contract_id = c.contract_id) >= 3 THEN 30 ELSE 0 END +
-- 3. 특정 키워드(블랙리스트) 체크
CASE WHEN cr.incident_description LIKE '%의심병원%' THEN 30 ELSE 0 END
),
f.is_suspicious = CASE WHEN f.risk_score >= 70 THEN TRUE ELSE FALSE END,
f.fraud_type_tags = 'CRITICAL_TIMING, REPETITIVE_CLAIM'
WHERE f.reception_id = 'TARGET_RECEPTION_ID';
💡 데이터 연쇄 작용의 최종 시나리오
- Batch 로직: 매일 새벽, contracts와 product_rules를 Join 하여 지급할 수수료를 commissions 테이블에 쌓습니다.
- Trigger 로직: claim_receptions에 새로운 행이 삽입되는 순간, FDS 스코어링 프로시저가 실행되어 fds_analysis에 점수를 기록합니다.
- 결산 연동: is_suspicious가 TRUE인 건은 보험금 지급(claim_payouts) 프로세스에서 '승인 대기' 상태로 묶여, 최종 회계 전표로 넘어가지 않도록 방어합니다.
요약 및 다음 제언
이로써 상품 → 계약 → 수수료 → 사고 → FDS → 지급 → 회계로 이어지는 ERP의 핵심 엔진 설계가 마무리되었습니다.













시리즈 1: 백엔드(NestJS + Prisma) 기초 및 상품/계약 설계
1. Prisma 스키마 설정 (schema.prisma)
가장 먼저 MySQL 데이터베이스 구조를 Prisma 스키마로 정의합니다. 앞서 설계한 MySQL 구조를 NestJS에서 사용할 수 있게 변환합니다.
// schema.prisma
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
// 1단계: 상품 마스터
model Product {
productCode String @id @map("product_code")
productName String @map("product_name")
productType ProductType @map("product_type")
rules ProductRule[]
contracts Contract[]
@@map("products")
}
enum ProductType {
LIFE
NON_LIFE
}
// 상품 룰 엔진 데이터
model ProductRule {
id Int @id @default(autoincrement())
productCode String @map("product_code")
ruleType String @map("rule_type")
ruleValue Json @map("rule_value")
product Product @relation(fields: [productCode], references: [productCode])
@@map("product_rules")
}
// 3단계: 계약 마스터
model Contract {
contractId String @id @map("contract_id")
productCode String @map("product_code")
customerId Int @map("customer_id")
premiumAmount Decimal @map("premium_amount") @db.Decimal(15, 2)
status String @default("PENDING")
product Product @relation(fields: [productCode], references: [productCode])
// ... 설계사, 고객 관계 생략
@@map("contracts")
}
2. NestJS 백엔드: 상품 생성 서비스 (products.service.ts)
상품 마스터와 룰을 동시에 생성하는 핵심 로직입니다.
import { Injectable } from '@nestjs/common';
import { PrismaService } from './prisma.service';
@Injectable()
export class ProductsService {
constructor(private prisma: PrismaService) {}
async createProduct(data: { code: string; name: string; type: any; rules: any[] }) {
return this.prisma.product.create({
data: {
productCode: data.code,
productName: data.name,
productType: data.type,
rules: {
create: data.rules.map(rule => ({
ruleType: rule.type,
ruleValue: rule.value,
})),
},
},
});
}
}
3. NuxtJS 프론트엔드: 상품 등록 화면 (pages/products/create.vue)
상품 코드와 룰을 입력받아 백엔드로 전송하는 UI 모듈입니다.
<template>
<div class="p-8">
<h1 class="text-2xl font-bold mb-4">새 보험 상품 등록</h1>
<form @submit.prevent="submitProduct">
<div class="space-y-4">
<input v-model="form.code" placeholder="상품코드 (예: LIFE001)" class="border p-2 w-full" />
<input v-model="form.name" placeholder="상품명" class="border p-2 w-full" />
<select v-model="form.type" class="border p-2 w-full">
<option value="LIFE">생명보험</option>
<option value="NON_LIFE">손해보험</option>
</select>
<div class="bg-gray-100 p-4">
<h3 class="font-bold mb-2">수수료 룰 설정 (JSON)</h3>
<textarea v-model="form.commissionRule" class="w-full h-24 border p-2" placeholder='{"comm_rate": 10}'></textarea>
</div>
<button type="submit" class="bg-blue-600 text-white px-4 py-2 rounded">상품 출시</button>
</div>
</form>
</div>
</template>
<script setup>
const form = ref({
code: '',
name: '',
type: 'LIFE',
commissionRule: '{"comm_rate": 10}'
});
const submitProduct = async () => {
const payload = {
code: form.value.code,
name: form.value.name,
type: form.value.type,
rules: [{ type: 'COMMISSION_RULE', value: JSON.parse(form.value.commissionRule) }]
};
await $fetch('/api/products', { method: 'POST', body: payload });
alert('상품이 성공적으로 출시되었습니다!');
};
</script>
💡 이번 단계 요약
- NestJS: Prisma를 통해 DB 스키마를 동기화하고, 상품-룰 간의 관계를 한 번에 생성하는 트랜잭션을 처리합니다.
- NuxtJS: 사용자로부터 상품 정의를 받아 API로 전달합니다.
이번 단계에서는 계약이 체결되었을 때 수수료를 계산하는 Service 로직과, 매달 정기적으로 수수료를 정산하는 Task Scheduling(Batch) 기능을 NestJS에서 어떻게 구현하는지 중점적으로 다룹니다.
1. Prisma 스키마 확장 (schema.prisma)
수수료와 설계사 정보를 관리하기 위한 모델을 추가합니다.
// 설계사 마스터
model Agent {
agentId String @id @map("agent_id")
name String
commissionRate Decimal @map("commission_rate") @db.Decimal(5, 2) // 개인 수수료율
commissions Commission[]
@@map("agents")
}
// 수수료 내역
model Commission {
id Int @id @default(autoincrement())
contractId String @map("contract_id")
agentId String @map("agent_id")
amount Decimal @db.Decimal(15, 2)
status String @default("PENDING") // PENDING, PAID
paymentDate DateTime @map("payment_date")
agent Agent @relation(fields: [agentId], references: [agentId])
@@map("commissions")
}
2. NestJS 백엔드: 수수료 엔진 서비스 (commissions.service.ts)
계약 체결 시 상품 룰을 읽어와 즉시 예상 수수료를 산출하는 로직입니다.
import { Injectable } from '@nestjs/common';
import { PrismaService } from './prisma.service';
import { Cron, CronExpression } from '@nestjs/schedule';
@Injectable()
export class CommissionsService {
constructor(private prisma: PrismaService) {}
// [로직 A] 계약 체결 시 수수료 데이터 생성
async calculateInitialCommission(contractId: string) {
const contract = await this.prisma.contract.findUnique({
where: { contractId },
include: { product: { include: { rules: true } } },
});
const agent = await this.prisma.agent.findFirst(); // 실제론 계약에 귀속된 설계사 조회
// 상품 룰에서 수수료율 추출 (JSON 파싱)
const commRule = contract.product.rules.find(r => r.ruleType === 'COMMISSION_RULE');
const baseRate = (commRule.ruleValue as any).comm_rate || 0;
// 수수료 = 보험료 * 상품기본율 * 설계사등급율
const amount = Number(contract.premiumAmount) * (baseRate / 100) * Number(agent.commissionRate);
return this.prisma.commission.create({
data: {
contractId: contract.contractId,
agentId: agent.agentId,
amount: amount,
paymentDate: new Date(), // 실제론 익월 지급일 계산 로직 필요
},
});
}
// [로직 B] 배치 작업: 매달 1일 미지급 수수료 정산 처리
@Cron(CronExpression.EVERY_1ST_DAY_OF_MONTH_AT_MIDNIGHT)
async handleMonthlySettlement() {
console.log('월간 수수료 정산 배치 시작...');
await this.prisma.commission.updateMany({
where: { status: 'PENDING' },
data: { status: 'PAID' },
});
}
}
3. NuxtJS 프론트엔드: 수수료 대시보드 (pages/commissions/index.vue)
설계사가 자신의 예상 수수료를 확인하는 화면입니다.
<template>
<div class="p-8">
<h1 class="text-2xl font-bold mb-6">수수료 정산 현황</h1>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-8">
<div class="bg-blue-100 p-6 rounded-lg">
<p class="text-sm text-blue-600">이번 달 지급 예정</p>
<p class="text-3xl font-bold">{{ formatCurrency(totalPending) }}원</p>
</div>
</div>
<table class="w-full border-collapse border text-left">
<thead>
<tr class="bg-gray-100">
<th class="p-3 border">계약번호</th>
<th class="p-3 border">수수료</th>
<th class="p-3 border">상태</th>
<th class="p-3 border">지급일</th>
</tr>
</thead>
<tbody>
<tr v-for="item in commissions" :key="item.id">
<td class="p-3 border">{{ item.contractId }}</td>
<td class="p-3 border font-semibold">{{ formatCurrency(item.amount) }}</td>
<td class="p-3 border">
<span :class="item.status === 'PAID' ? 'text-green-600' : 'text-orange-600'">
{{ item.status }}
</span>
</td>
<td class="p-3 border">{{ formatDate(item.paymentDate) }}</td>
</tr>
</tbody>
</table>
</div>
</template>
<script setup>
// 백엔드에서 수수료 목록 조회
const { data: commissions } = await useFetch('/api/commissions');
const totalPending = computed(() => {
return commissions.value
?.filter(c => c.status === 'PENDING')
.reduce((acc, cur) => acc + Number(cur.amount), 0) || 0;
});
const formatCurrency = (val) => new Intl.NumberFormat('ko-KR').format(val);
const formatDate = (date) => new Date(date).toLocaleDateString();
</script>
💡 이번 단계 요약
- NestJS @Schedule: 별도의 외부 배치 툴 없이도 정기적인 정산 업무를 자동화합니다.
- 비즈니스 로직: JSON으로 저장된 유연한 상품 룰을 런타임에 해석하여 복잡한 수수료를 실시간 계산합니다.
- Nuxt Data Fetching: 설계사별 맞춤형 데이터를 실시간으로 시각화합니다.
이번 단계에서는 사고가 접수되는 즉시 백엔드에서 위험 점수를 계산하고, 높은 점수의 사고가 발생하면 프론트엔드(관리자 화면)에 실시간으로 경고를 띄우는 로직을 구현합니다.
1. Prisma 스키마 확장 (schema.prisma)
사고 접수 및 FDS 분석 결과를 위한 모델을 추가합니다.
// 사고 접수 모델
model Claim {
id String @id @default(cuid()) // 접수번호
contractId String @map("contract_id")
incidentDate DateTime @map("incident_date")
description String @db.Text
status ClaimStatus @default(RECEIVED)
fdsAnalysis FdsAnalysis?
contract Contract @relation(fields: [contractId], references: [contractId])
@@map("claims")
}
enum ClaimStatus {
RECEIVED
UNDER_REVIEW
APPROVED
REJECTED
}
// FDS 분석 결과 모델
model FdsAnalysis {
id Int @id @default(autoincrement())
claimId String @unique @map("claim_id")
riskScore Int @map("risk_score")
isSuspicious Boolean @default(false) @map("is_suspicious")
fraudTags String? @map("fraud_tags") // 예: "SHORT_TERM,REPETITIVE"
claim Claim @relation(fields: [claimId], references: [id])
@@map("fds_analysis")
}
2. NestJS 백엔드: FDS 엔진 서비스 (claims.service.ts)
사고가 접수될 때 자동으로 실행되는 스코어링 로직입니다.
import { Injectable } from '@nestjs/common';
import { PrismaService } from './prisma.service';
@Injectable()
export class ClaimsService {
constructor(private prisma: PrismaService) {}
async createClaim(data: { contractId: string; incidentDate: Date; description: string }) {
// 1. 사고 접수 저장
const claim = await this.prisma.claim.create({
data,
include: { contract: true }
});
// 2. FDS 스코어링 실행 (비즈니스 로직)
let score = 0;
const tags = [];
// 가중치 A: 계약 후 30일 이내 사고 (단기 사고)
const diffDays = Math.abs(
(claim.incidentDate.getTime() - claim.contract.createdAt.getTime()) / (1000 * 60 * 60 * 24)
);
if (diffDays <= 30) {
score += 40;
tags.push('SHORT_TERM');
}
// 가중치 B: 과거 사고 이력 조회
const pastClaimsCount = await this.prisma.claim.count({
where: { contractId: data.contractId }
});
if (pastClaimsCount >= 3) {
score += 30;
tags.push('REPETITIVE');
}
// 3. FDS 결과 저장
await this.prisma.fdsAnalysis.create({
data: {
claimId: claim.id,
riskScore: score,
isSuspicious: score >= 70,
fraudTags: tags.join(','),
}
});
return claim;
}
}
3. NuxtJS 프론트엔드: 사고 심사 대시보드 (pages/admin/claims.vue)
관리자가 실시간으로 위험도가 높은 사고를 감지하는 화면입니다.
<template>
<div class="p-8">
<h1 class="text-2xl font-bold mb-6">사고 심사 및 FDS 모니터링</h1>
<div class="space-y-4">
<div v-for="claim in claims" :key="claim.id"
:class="['p-4 border rounded-lg flex justify-between items-center',
claim.fdsAnalysis?.isSuspicious ? 'bg-red-50 border-red-200' : 'bg-white']">
<div>
<div class="flex items-center gap-2">
<span class="font-mono text-sm text-gray-500">{{ claim.id }}</span>
<span v-if="claim.fdsAnalysis?.isSuspicious"
class="bg-red-600 text-white text-xs px-2 py-1 rounded-full animate-pulse">
위험군 감지
</span>
</div>
<p class="font-bold text-lg">{{ claim.description }}</p>
<p class="text-sm text-gray-600">계약번호: {{ claim.contractId }}</p>
</div>
<div class="text-right">
<div class="text-sm text-gray-500 mb-1">FDS 위험 점수</div>
<div :class="['text-2xl font-black',
claim.fdsAnalysis?.riskScore >= 70 ? 'text-red-600' : 'text-green-600']">
{{ claim.fdsAnalysis?.riskScore || 0 }}점
</div>
<button class="mt-2 bg-gray-800 text-white px-3 py-1 text-sm rounded">상세조사</button>
</div>
</div>
</div>
</div>
</template>
<script setup>
// 실시간성을 위해 필요시 웹소켓이나 주기적 fetch 사용
const { data: claims } = await useFetch('/api/admin/claims');
</script>
마지막 단계에서는 한 해(또는 한 달) 동안 발생한 보험료 수입, 수수료 지출, 보험금 지급 데이터를 통합 전표(General Ledger)로 모으고, 이를 바탕으로 IFRS17의 핵심 지표가 포함된 재무제표를 산출합니다.
1. Prisma 스키마 확장 (schema.prisma)
회계 결산을 위한 통합 전표와 재무제표 모델을 추가합니다.
// 15. 통합 전표 (General Ledger)
model GeneralLedger {
id Int @id @default(autoincrement())
transactionDate DateTime @default(now()) @map("transaction_date")
accountCode String @map("account_code") // 계정코드 (예: REV_PREM, EXP_COMM)
debit Decimal @default(0) @db.Decimal(18, 2) // 차변
credit Decimal @default(0) @db.Decimal(18, 2) // 대변
referenceId String? @map("reference_id") // 계약ID 또는 사고ID 역추적용
description String?
@@map("general_ledger")
}
// 16. 월별 결산 요약 (IFRS17 리포트용)
model FinancialClosing {
period String @id // 예: "2024-12"
totalRevenue Decimal @map("total_revenue") @db.Decimal(20, 2)
totalExpense Decimal @map("total_expense") @db.Decimal(20, 2)
netIncome Decimal @map("net_income") @db.Decimal(20, 2)
csm Decimal @map("csm") @db.Decimal(20, 2) // 서비스마진 (IFRS17 핵심)
isClosed Boolean @default(false) @map("is_closed")
@@map("financial_closing")
}
2. NestJS 백엔드: 결산 서비스 (accounting.service.ts)
흩어진 데이터를 수집하여 회계 장부를 마감하는 로직입니다.
import { Injectable } from '@nestjs/common';
import { PrismaService } from './prisma.service';
@Injectable()
export class AccountingService {
constructor(private prisma: PrismaService) {}
// [결산 로직] 특정 기간의 데이터를 전표로 통합하고 마감
async performClosing(period: string) {
return await this.prisma.$transaction(async (tx) => {
// 1. 해당 기간 보험료 수입 합산
const revenue = await tx.contract.aggregate({
_sum: { premiumAmount: true },
where: { createdAt: { /* 기간 필터 */ } }
});
// 2. 해당 기간 지출(수수료 + 보험금) 합산
const commExpense = await tx.commission.aggregate({
_sum: { amount: true },
where: { status: 'PAID' }
});
const claimExpense = await tx.claimPayout.aggregate({
_sum: { payoutAmount: true }
});
const totalRev = revenue._sum.premiumAmount || 0;
const totalExp = (commExpense._sum.amount || 0).add(claimExpense._sum.payoutAmount || 0);
// 3. 재무제표 요약 생성 (IFRS17 CSM은 간략히 수익의 10%로 가정)
return await tx.financialClosing.upsert({
where: { period },
update: {},
create: {
period,
totalRevenue: totalRev,
totalExpense: totalExp,
netIncome: totalRev.minus(totalExp),
csm: totalRev.mul(0.1), // 실제론 복잡한 계리 모델 적용
isClosed: true
}
});
});
}
}
3. NuxtJS 프론트엔드: 경영진 대시보드 (pages/admin/dashboard.vue)
회사의 재무 상태를 한눈에 확인하는 최종 리포트 화면입니다.
<template>
<div class="p-8">
<h1 class="text-3xl font-black mb-8">Executive Financial Report</h1>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-12">
<div class="bg-white p-6 rounded-xl shadow-sm border border-gray-100">
<p class="text-sm text-gray-500 mb-1">당기순이익 (Net Income)</p>
<p class="text-2xl font-bold text-blue-600">{{ formatCurrency(report?.netIncome) }}</p>
</div>
<div class="bg-white p-6 rounded-xl shadow-sm border border-gray-100">
<p class="text-sm text-gray-500 mb-1">보험서비스마진 (CSM)</p>
<p class="text-2xl font-bold text-purple-600">{{ formatCurrency(report?.csm) }}</p>
</div>
<div class="bg-white p-6 rounded-xl shadow-sm border border-gray-100">
<p class="text-sm text-gray-500 mb-1">손해율 (Loss Ratio)</p>
<p class="text-2xl font-bold text-red-600">{{ lossRatio }}%</p>
</div>
</div>
<div class="bg-gray-900 text-white p-8 rounded-2xl">
<h3 class="text-lg font-bold mb-4">전표 데이터 역추적 (Audit Trail)</h3>
<div class="text-sm opacity-70">
<div v-for="log in logs" :key="log.id" class="flex justify-between py-2 border-b border-gray-800">
<span>{{ log.accountCode }} - {{ log.description }}</span>
<span class="font-mono">{{ formatCurrency(log.debit || log.credit) }}</span>
</div>
</div>
</div>
</div>
</template>
<script setup>
const { data: report } = await useFetch('/api/accounting/closing/latest');
const { data: logs } = await useFetch('/api/accounting/logs');
const lossRatio = computed(() => {
if (!report.value) return 0;
return ((report.value.totalExpense / report.value.totalRevenue) * 100).toFixed(1);
});
const formatCurrency = (val) => new Intl.NumberFormat('ko-KR', { style: 'currency', currency: 'KRW' }).format(val || 0);
</script>
'IT 개발,관리,연동,자동화' 카테고리의 다른 글
| '무인점포는 무인이 아니다'...월 300 수익의 꿈이 '리모컨 감옥'으로 변하는 이유 (1) | 2026.01.04 |
|---|---|
| 인공지능 코딩의 진화 oh my opencode 이해하기 (1) | 2026.01.04 |
| 스프링 부트 환경에서 JWT(JSON Web Token)를 사용한 인증 및 인가 시스템을 구축 (0) | 2025.12.27 |
| Protobuf, gRPC 데이터 직렬화 (1) | 2025.12.26 |
| 영업용 보세화물 관리 시스템 (0) | 2025.12.26 |