/SimpleHR project
SimpleHR project

요금제 시스템 구현 설계 (DB·API·서비스·프론트엔드·마이그레이션)

conceptedited by Cairni · 방금 · AIv1

개요

본 페이지는 설계-요금제시스템.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원전체 메뉴 + 페이롤 연동

설계-요금제시스템-md

구간별 과금 공식

재직인원 ≤ 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 (신규)

sql
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_codename_koprice_per_userflat_fee_amount
PLAN_FREE무료00
PLAN_BASIC베이직2,00020,000
PLAN_STD스탠다드3,00020,000
PLAN_SPC스페셜10,00020,000

설계-요금제시스템-md

3-2. BillingSnapshot (신규)

sql
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)
);

설계-요금제시스템-md

3-3. TenantBillingOverride (구조만, 후순위)

업체별 커스텀 단가를 위한 테이블. 현재 구현 범위 이며 DB 구조만 준비합니다. 설계-요금제시스템-md

sql
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}/trial

PATCH /v2/system/tenants/{id}/trialaction 파라미터: "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

python
# 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) -> Tenant

6. 플랜별 메뉴 접근 제한

권위 소스: Menu.visible_tiers

LicenseMenuMapping 대신 Menu.visible_tiers JSONB 필드를 권위 소스로 사용합니다. 설계-요금제시스템-md

  • 메뉴 데이터와 허용 플랜 정보가 한 곳에 있어 동기화 이슈 없음
  • 별도 테이블 JOIN 불필요
  • 마이그레이션 시 이러닝/계약서 메뉴의 visible_tiers만 수정하면 끝

요청 처리 흐름

설계-요금제시스템-md

캐시 전략

python
# {menu_url: set(plan_codes)} — 앱 시작 시 메모리 로드
_MENU_PLAN_CACHE: dict[str, set[str]] = {}
  • 앱 시작 시 1회 로드 (Menu 테이블 전체 조회)
  • 라이센스 메뉴 수정 시 캐시 갱신
  • 매 요청마다 DB 조회 없이 딕셔너리 lookup 설계-요금제시스템-md

메뉴별 플랜 허용 규칙

메뉴 그룹PLAN_FREEPLAN_BASICPLAN_STDPLAN_SPC
급여관리 (4개)OOOO
이러닝센터 (5개)XXOO
전자근로계약서XXOO
나머지 전체OOOO

마이그레이션에서 이러닝/계약서 메뉴의 visible_tiers["PLAN_STD", "PLAN_SPC"]로 설정. 근로계약서 스펙 참고. 설계-요금제시스템-md

프론트엔드 연동

로그인 응답의 menu_keys에 플랜 제한을 반영 — resolve_menu_keys()에서 plan 기반 필터링 추가. 사이드바가 자동으로 해당 메뉴를 숨깁니다. 설계-요금제시스템-md


7. 프론트엔드 변경

프론트엔드 변경 항목AI · 출처 클릭
신규 생성1
admin/billing/page.tsx — 회사 최고관리자 정산 페이지
수정5
system-admin/plans/page.tsx — MOCK_PLANS 제거, API 연동, 플랜 카드 4종
system-admin/companies/page.tsx — 플랜 드롭다운 4종, 필터 추가
system-admin/license-menus/page.tsx — 4개 플랜 열(PRM 제거)
admin/Sidebar.tsx — '정산' 메뉴 추가 (is_system 관리자만)
lib/api.ts — 신규 billing API 엔드포인트 추가

사용량 페이지 (신규): GET /v2/system/usage 연동, 테넌트별 과금 테이블, 요약 카드 (총 테넌트·총 인원·예상 월매출), 스냅샷 생성 버튼 포함. 설계-요금제시스템-md


8. 마이그레이션 전략


9. 파일 목록

신규 생성

파일 경로설명
backend/app/services/billing_service.py과금 계산·스냅샷·체험판 관리 서비스
backend/app/schemas/billing.pyPydantic 스키마
backend/app/api/v2/billing.pybilling 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회사 최고관리자 정산 페이지

설계-요금제시스템-md

수정 대상

  • backend/app/db/enums.py, models.py, main.py
  • backend/app/api/v2/system.py, sysadmin.py
  • backend/app/schemas/system.py, sysadmin.py
  • backend/app/services/sysadmin_service.py
  • frontend/src/app/system-admin/plans/page.tsx
  • frontend/src/app/system-admin/companies/page.tsx
  • frontend/src/app/system-admin/license-menus/page.tsx
  • frontend/src/components/admin/Sidebar.tsx
  • frontend/src/lib/api.ts 설계-요금제시스템-md

관련 문서

Made with CairniExplore public wikis →