/SimpleHR project
SimpleHR project

플랜별 메뉴 접근 제한 설계

개념편집: Cairni · 방금 · AI 생성v2

개요

요금제 시스템 구현 설계의 일환으로, 가입 플랜에 따라 특정 메뉴 접근을 제한하는 메커니즘을 정의한다. 전체 이용요금 및 과금 정책과 연계되며, 플랜 코드(PLAN_FREE, PLAN_BASIC, PLAN_STD, PLAN_SPC)를 기준으로 동작한다. 설계 원문


권위 소스: Menu.visible_tiers

기존 LicenseMenuMapping 별도 테이블 대신 Menu.visible_tiers JSONB 필드를 단일 권위 소스로 사용한다. 설계 원문

방식설명
기존 (LicenseMenuMapping)별도 테이블 JOIN 필요, 동기화 이슈 발생 가능
신규 (Menu.visible_tiers)메뉴와 허용 플랜 정보가 한 곳에 집중 → 동기화 이슈 없음, JOIN 불필요

기본값: ["PLAN_FREE", "PLAN_BASIC", "PLAN_STD", "PLAN_SPC"]


플랜별 메뉴 허용 규칙

AI · 출처 클릭
PLAN_FREE1
PLAN_BASIC1
PLAN_STD2
PLAN_SPC2
메뉴 그룹PLAN_FREEPLAN_BASICPLAN_STDPLAN_SPC
급여관리 (4개)
이러닝센터 (5개)
전자근로계약서
나머지 전체
  • 이러닝/전자근로계약서 메뉴의 visible_tiers["PLAN_STD", "PLAN_SPC"]
  • 그 외 전체 메뉴 → ["PLAN_FREE", "PLAN_BASIC", "PLAN_STD", "PLAN_SPC"]

이용요금(과금) 정책 스펙과 함께 참고할 것. 설계 원문


미들웨어 연동 구조

기존 menu_permission_guard 미들웨어(main.py L672)에 플랜 체크 단계를 추가한다.

두 가지 조건을 모두 통과해야만 접근이 허용된다. 설계 원문


메모리 캐시 전략

매 요청마다 DB를 조회하는 오버헤드를 피하기 위해 앱 시작 시 Menu.visible_tiers 데이터를 메모리에 로드한다.

python
# {menu_url: set(plan_codes)} — 해당 메뉴에 접근 가능한 플랜 목록
_MENU_PLAN_CACHE: dict[str, set[str]] = {}
시점동작
앱 시작Menu 테이블 전체 조회 → 캐시 1회 로드
라이센스 메뉴 수정 시캐시 즉시 갱신
일반 API 요청DB 조회 없이 딕셔너리 lookup

설계 원문


라이센스 메뉴 관리 페이지 전환

기존 license-menus/page.tsxLicenseMenuMapping 테이블을 참조하고 있으나, Menu.visible_tiers를 직접 읽고 쓰도록 전환한다.

작업변경 내용
GETMenu 테이블에서 visible_tiers 조회
PUTMenu.visible_tiers 업데이트 + 메모리 캐시 갱신
UI플랜 열을 4종(FREE/BASIC/STD/SPC)으로 표시 — PLAN_PRM 제거

설계 원문


프론트엔드 반영

로그인 응답의 menu_keys에 플랜 제한을 반영하여 사이드바가 자동으로 비허용 메뉴를 숨기도록 한다.

  • resolve_menu_keys() 함수 내에 플랜 기반 필터링 로직 추가
  • 기존 사이드바 menu_keys 기반 렌더링 로직 그대로 활용 (추가 UI 변경 최소화)

설계 원문


마이그레이션 연동

마이그레이션(billing_plan_migration.py) 실행 시 Menu.visible_tiers 일괄 업데이트가 포함된다.

  • 이러닝/전자근로계약서 메뉴 → ["PLAN_STD", "PLAN_SPC"]
  • 그 외 전체 메뉴 → ["PLAN_FREE", "PLAN_BASIC", "PLAN_STD", "PLAN_SPC"]

마이그레이션 전략 전체는 요금제 시스템 구현 설계 참고. 설계 원문


관련 스펙


백엔드 메뉴 게이팅 버그 수정 (2026-08-03)

menu_allowed/admin(대시보드)을 모든 /admin/* 경로의 상위경로로 처리하여, 대시보드 권한만 있는 관리자도 API 직접 호출로 전체 admin API에 접근 가능하던 보안 버그를 수정했다. fix-task 0803 — 수정완료 항목

  • /admin 단독 키는 정확 일치만 허용(k != "/admin" 가드 추가), 다른 키의 하위 상속(/admin/payroll → /base 등)은 유지.
  • _MENU_PREFIXES/admin/billing 추가 → 정산 API도 게이팅 대상에 포함(menu_enforcement.py).

회귀 검증 결과:

케이스결과
대시보드-only 역할 → 전체 admin API403
하위 상속 경로200 유지
is_system / ["*"] 역할전부 허용
정산 API — 최고관리자200
정산 API — 미부여(조직관리자 등)403
커스텀 "전메뉴" 역할은 concrete 리스트라 billing 미포함 → 역할관리에서 별도 부여 필요(설계상 정상). fix-task 0803 — 수정완료 항목

정산 메뉴 최고관리자 전용 노출

/admin/billing을 관리자 메뉴 카탈로그(admin_menu.py)에 추가했다. 최고관리자는 기본 노출되며, 역할관리에서 타 관리자에게 부여·회수 가능. 레거시 ["*"] 관리자는 여전히 노출(커스텀 역할 지정으로 제한 가능). fix-task 0803 — 수정완료 항목

대시보드 역할별 위젯 게이팅

게이팅 적용 후 권한 없는 도메인 위젯이 403 오류와 함께 노출되던 문제를 대응했다. 대시보드 전 위젯 및 HERO 근태 viz를 menu_keys로 게이팅(권한 없으면 fetch·렌더 안 함, 숨김 방식·사이드바와 일관). HERO는 헤드카운트·제목은 유지. 프론트 전용(admin/page.tsx). fix-task 0803 — 수정완료 항목

후속(리디자인): role-adaptive HERO — 역할별 다른 핵심 지표 표시.