요금제 시스템 구현 설계 (DB·API·서비스·프론트엔드·마이그레이션)
개요
본 페이지는 설계-요금제시스템.md를 기반으로 작성된 요금제 시스템 구현 설계 문서입니다. 이용요금 및 과금 정책 및 이용요금(과금) 정책 스펙과 함께 참고하세요. 설계-요금제시스템-md
1. 구현 목표
| # | 목표 | 대상 |
|---|---|---|
| 1 | 시스템관리자가 플랜 단가를 설정/수정 | SYS_ADMIN |
| 2 | 테넌트별 사용량·과금 실시간 조회 | SYS_ADMIN |
| 3 | 체험판 연장/단축/정식 전환 | SYS_ADMIN |
| 4 | 월별 과금 스냅샷 생성 및 이력 관리 | SYS_ADMIN |
| 5 | 플랜별 메뉴 접근 제한 실동작 | 전체 |
| 6 | 회사 최고관리자 정산 페이지 (읽기 전용) | COMP_ADMIN (is_system) |
범위 제외: PG 연동, 자동 청구, 업체별 커스텀 단가 UI (DB 구조만 준비) 설계-요금제시스템-md
2. 과금 정책
플랜별 단가
| 플랜 코드 | 이름 | 1인당 단가 | 메뉴 제한 |
|---|---|---|---|
PLAN_FREE | 무료 | 0원 | 이러닝·전자근로계약서 제외 |
PLAN_BASIC | 베이직 | 2,000원 | 이러닝·전자근로계약서 제외 |
PLAN_STD | 스탠다드 | 3,000원 | 전체 메뉴 |
PLAN_SPC | 스페셜 | 10,000원 | 전체 메뉴 + 페이롤 연동 |
구간별 과금 공식
재직인원 ≤ 5명 → 0원
5 < 재직인원 ≤ 10명 → 정액 20,000원 (PLAN_FREE는 0원)
재직인원 > 10명 → 정액 + (재직인원 - 10) × 1인당 단가- 예시 1: 스탠다드 11명 → 20,000 + 1 × 3,000 = 23,000원
- 예시 2: 무료 15명 → 0원 (단가·정액 모두 0)
재직인원 기준
Employee.employment_status != "EMP_RETIRED" AND Employee.tenant_id == tenant_id
직원 재직 상태 변경 워크플로에서 EMP_RETIRED 전환 시점이 과금 인원에 영향을 미칩니다. 설계-요금제시스템-md
3. DB 스키마 설계
3-1. BillingPlan (신규)
CREATE TABLE billing_plans (
id SERIAL PRIMARY KEY,
plan_code VARCHAR(30) UNIQUE NOT NULL,
name_ko VARCHAR(100) NOT NULL,
name_en VARCHAR(100),
price_per_user INTEGER NOT NULL DEFAULT 0,
base_free_seats INTEGER NOT NULL DEFAULT 5,
flat_fee_seats INTEGER NOT NULL DEFAULT 10,
flat_fee_amount INTEGER NOT NULL DEFAULT 20000,
sort_order INTEGER NOT NULL DEFAULT 0,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
description TEXT,
created_by UUID,
updated_by UUID,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);시드 데이터
| plan_code | name_ko | price_per_user | flat_fee_amount |
|---|---|---|---|
| PLAN_FREE | 무료 | 0 | 0 |
| PLAN_BASIC | 베이직 | 2,000 | 20,000 |
| PLAN_STD | 스탠다드 | 3,000 | 20,000 |
| PLAN_SPC | 스페셜 | 10,000 | 20,000 |
3-2. BillingSnapshot (신규)
CREATE TABLE billing_snapshots (
id UUID PRIMARY KEY,
tenant_id UUID NOT NULL REFERENCES tenants(id),
billing_month VARCHAR(7) NOT NULL, -- "2026-06"
plan_code VARCHAR(30) NOT NULL,
active_employee_count INTEGER NOT NULL,
price_per_user INTEGER NOT NULL,
base_amount INTEGER NOT NULL DEFAULT 0,
extra_amount INTEGER NOT NULL DEFAULT 0,
total_amount INTEGER NOT NULL,
memo TEXT,
created_by UUID,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
UNIQUE (tenant_id, billing_month)
);3-3. TenantBillingOverride (구조만, 후순위)
업체별 커스텀 단가를 위한 테이블. 현재 구현 범위 외이며 DB 구조만 준비합니다. 설계-요금제시스템-md
CREATE TABLE tenant_billing_overrides (
id UUID PRIMARY KEY,
tenant_id UUID UNIQUE NOT NULL REFERENCES tenants(id),
custom_price_per_user INTEGER,
custom_flat_fee INTEGER,
discount_reason VARCHAR(500),
valid_from TIMESTAMP,
valid_until TIMESTAMP,
created_by UUID,
updated_by UUID,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);3-4. 기존 모델 변경
- PlanCode enum (
enums.py):PLAN_FREE,PLAN_BASIC추가 /PLAN_PRM제거 (마이그레이션 후) Menu.visible_tiers(models.py): 기본값["PLAN_FREE", "PLAN_BASIC", "PLAN_STD", "PLAN_SPC"]설계-요금제시스템-md
4. API 설계
4-1. 시스템관리자 API
GET /v2/system/billing-plans
PUT /v2/system/billing-plans/{plan_code}
GET /v2/system/usage
POST /v2/system/billing-snapshots/generate
GET /v2/system/billing-snapshots?month=2026-06
PATCH /v2/system/tenants/{id}/trialPATCH /v2/system/tenants/{id}/trial의 action 파라미터: "extend" | "shorten" | "convert_to_active" 설계-요금제시스템-md
4-2. 회사관리자 API (최고관리자 전용, 읽기 전용)
GET /v2/admin/billing/current
GET /v2/admin/billing/history권한: require_company_admin + employee.admin_role.is_system == True 체크. 관리자 권한 구조는 관리자 등급 및 권한 관리 스펙 참고. 설계-요금제시스템-md
5. 서비스 레이어
파일: backend/app/services/billing_service.py 설계-요금제시스템-md
# 1. 순수 계산 함수 (단위 테스트 대상)
def calc_monthly_bill(active_count, price_per_user,
base_free_seats=5, flat_fee_seats=10,
flat_fee_amount=20000) -> dict: ...
# 2. DB 의존 함수
async def count_active_employees(db, tenant_id) -> int
async def get_billing_plan(db, plan_code) -> BillingPlan | None
async def get_all_billing_plans(db) -> list[BillingPlan]
async def update_billing_plan(db, plan_code, data, admin_id) -> BillingPlan
# 3. 사용량 조회
async def get_all_tenants_usage(db) -> list[dict]
async def get_tenant_current_billing(db, tenant_id) -> dict
# 4. 스냅샷
async def generate_monthly_snapshots(db, billing_month, admin_id) -> dict
async def get_billing_snapshots(db, tenant_id=None, month=None) -> list[dict]
# 5. 체험판 관리
async def update_tenant_trial(db, tenant_id, action, days, admin_id) -> Tenant6. 플랜별 메뉴 접근 제한
권위 소스: Menu.visible_tiers
LicenseMenuMapping 대신 Menu.visible_tiers JSONB 필드를 권위 소스로 사용합니다. 설계-요금제시스템-md
- 메뉴 데이터와 허용 플랜 정보가 한 곳에 있어 동기화 이슈 없음
- 별도 테이블 JOIN 불필요
- 마이그레이션 시 이러닝/계약서 메뉴의
visible_tiers만 수정하면 끝
요청 처리 흐름
캐시 전략
# {menu_url: set(plan_codes)} — 앱 시작 시 메모리 로드
_MENU_PLAN_CACHE: dict[str, set[str]] = {}- 앱 시작 시 1회 로드 (Menu 테이블 전체 조회)
- 라이센스 메뉴 수정 시 캐시 갱신
- 매 요청마다 DB 조회 없이 딕셔너리 lookup 설계-요금제시스템-md
메뉴별 플랜 허용 규칙
| 메뉴 그룹 | PLAN_FREE | PLAN_BASIC | PLAN_STD | PLAN_SPC |
|---|---|---|---|---|
| 급여관리 (4개) | O | O | O | O |
| 이러닝센터 (5개) | X | X | O | O |
| 전자근로계약서 | X | X | O | O |
| 나머지 전체 | O | O | O | O |
마이그레이션에서 이러닝/계약서 메뉴의 visible_tiers를 ["PLAN_STD", "PLAN_SPC"]로 설정. 근로계약서 스펙 참고. 설계-요금제시스템-md
프론트엔드 연동
로그인 응답의 menu_keys에 플랜 제한을 반영 — resolve_menu_keys()에서 plan 기반 필터링 추가. 사이드바가 자동으로 해당 메뉴를 숨깁니다. 설계-요금제시스템-md
7. 프론트엔드 변경
사용량 페이지 (신규): GET /v2/system/usage 연동, 테넌트별 과금 테이블, 요약 카드 (총 테넌트·총 인원·예상 월매출), 스냅샷 생성 버튼 포함. 설계-요금제시스템-md
8. 마이그레이션 전략
9. 파일 목록
신규 생성
| 파일 경로 | 설명 |
|---|---|
backend/app/services/billing_service.py | 과금 계산·스냅샷·체험판 관리 서비스 |
backend/app/schemas/billing.py | Pydantic 스키마 |
backend/app/api/v2/billing.py | billing API 라우터 |
backend/app/db/seed/billing_plan_migration.py | 마이그레이션 스크립트 |
backend/tests/unit/test_billing_service.py | 단위 테스트 |
backend/tests/integration/test_billing_api.py | 통합 테스트 |
frontend/src/app/admin/billing/page.tsx | 회사 최고관리자 정산 페이지 |
수정 대상
backend/app/db/enums.py,models.py,main.pybackend/app/api/v2/system.py,sysadmin.pybackend/app/schemas/system.py,sysadmin.pybackend/app/services/sysadmin_service.pyfrontend/src/app/system-admin/plans/page.tsxfrontend/src/app/system-admin/companies/page.tsxfrontend/src/app/system-admin/license-menus/page.tsxfrontend/src/components/admin/Sidebar.tsxfrontend/src/lib/api.ts설계-요금제시스템-md
관련 문서
- 이용요금 및 과금 정책 — 정책 레벨 요금 규정
- 이용요금(과금) 정책 스펙 — 요금 정책 상세 스펙
- 관리자 등급 및 권한 관리 스펙 — is_system 관리자 권한
- 직원 재직 상태 변경 워크플로 — EMP_RETIRED 전환과 과금 인원 산정
- 근로계약서 스펙 — 플랜별 메뉴 제한 대상 (전자근로계약서)
- Simple HR 프로젝트 개요 — 시스템 전체 맥락