역할 관리
- 대상 사용자 : 최고 관리자(admin), 인사관리자
- 화면의 목적
- 직원에게 부여할 역할(권한)을 관리 및 설정을 위한 페이지
1. 화면
- 상태 필터링
- default : 전체
- 조건 : 전체, 활성, 비활성
- 부서 필터링
- default : 전체 부서
- 조건 : 전체 부서, 부서 미지정, 각 부서(이름순)
- 팀(병동)만 지정한 역할도 그 팀이 속한 부서를 고르면 보인다
- 부서 미지정 : 부서도 팀(병동)도 지정하지 않은 역할
- 부서를 바꾸면 팀(병동) 필터는 「전체 팀(병동)」으로 돌아간다
- 팀(병동) 필터링
- default : 전체 팀(병동)
- 조건 : 전체 팀(병동), 팀(병동) 미지정, 각 팀 — 부서별로 묶어 보여 주고, 부서 필터를 고르면 그 부서의 팀만 보여 준다
- 하위 팀을 지정한 역할도 상위 팀을 고르면 보인다
- 팀(병동) 미지정 : 팀(병동)을 지정하지 않은 역할
- 역할 검색란
- placeholder : 역할
- 검색한 역할이 없을 때
- 리스트 뷰에 안내 문구 제공
- 문구 : 검색 결과가 없어요
- 리스트 뷰에 안내 문구 제공
- 팀/커스텀/시스템 라벨 삭제
- 상태
- 활성 : 변화 없음
- 비활성 : 역할 박스 전체 회색으로 표시
- 역할 권한 보여주기
- default : 닫힌 상태
- 펼치면 해당 역할이 보유한 권한 리스트 제공
- 권한 정리는 7월말해서 8월 초에 적용 예정
- 리스트 순서 : 역할명, 설명, 상태, 기능(관리) 버튼
최고 관리자(admin)인 경우
- [삭제] 버튼 비활성화
- 툴팁 : 최고 관리자는 삭제할 수 없어요.
2. 역할 추가 팝업
- 역할명
- 필수 조건 표시
- placeholder: 역할
- 설명
- placeholder : 설명
- 부서
- 선택 입력(필수 아님), 드롭다운으로 제공
- 제공 : 선택 안 함, 각 부서(이름순)
- 드롭다운 아래 「중복 부서」 체크박스 제공
- 체크 해제(default) : 부서 하나만 선택
- 체크 : 여러 부서 선택
- 드롭다운 첫 항목이 「부서 추가」로 바뀌고, 이미 고른 부서는 드롭다운에서 선택할 수 없음
- 고른 부서는 드롭다운 아래 라벨로 표시
- 라벨을 누르면 그 부서가 빠짐
- 체크를 끄면 먼저 고른 부서 하나만 남음
- 팀(병동)
- 선택 입력(필수 아님), 드롭다운으로 제공
- 제공 : 선택 안 함, 부서별로 묶은 팀 목록(하위 팀은 「— 」로 들여쓰기)
- 부서를 골랐으면 고른 부서의 팀만 제공
- 드롭다운 아래 「중복 팀(병동)」 체크박스 제공 — 동작은 「중복 부서」와 같음(첫 항목 「팀(병동) 추가」)
- 같은 이름의 팀이 여럿이면 라벨에 「상위 팀 > 팀명」으로 표시
- 부서를 바꾸거나 빼면, 선택한 팀 중 그 부서 밖의 팀은 빠짐
- 상태
- default : 활성
- 제공 : 활성, 비활성
- 드롭다운으로 제공
- 권한 선택
- 전체 선택/전체 해제
- 하나의 버튼 제공
- 선택 시 : 전체 선택
- 해제 시 : 전체 선택 해제
- 하나의 버튼 제공
- 전체 선택/전체 해제
- [취소] 버튼
- [취소] 버튼 클릭 시 해당 팝업 닫힘
- [저장] 버튼
- 역할명 입력해야 [저장] 버튼 활성화
- [저장] 버튼 클릭 시, 역할 저장되고 해당 팝업 닫힘
3. 역할 수정 팝업
- [편집] 버튼을 누르면 제공되는 팝업
- 역할명
- 역할 추가 팝업의 역할명과 공통
- 설명
- 역할 추가 팝업의 설명과 공통
- 부서 · 팀(병동)
- 역할 추가 팝업의 부서 · 팀(병동)과 공통
- 저장된 값이 2개 이상이면 「중복 부서」 · 「중복 팀(병동)」 체크가 켜진 상태로 열림
- 삭제된 팀은 열 때 선택에서 빠짐
- 체크박스만 바꾸고 선택 값이 그대로면 수정된 내역으로 보지 않음
- 상태
- 역할 추가 팝업의 상태와 공통
- 권한 선택
- 역할 추가 팝업의 권한 선택과 공통
- [취소] 버튼
- [취소] 버튼 클릭 시 해당 팝업 닫힘
- [저장] 버튼
- [저장] 버튼 활성화 조건
- 역할명 입력
- 수정된 내역 있을 때
- [저장] 버튼 클릭 시, 수정 내역 저장되고 해당 팝업 닫힘
- [저장] 버튼 활성화 조건
최고 관리자(admin)인 경우
- 역할명
- 역할 추가 팝업의 역할명과 공통
- 설명
- 역할 추가 팝업의 설명과 공통
- 상태
- default : 활성
- 수정 불가능
- 권한 선택
- 수정 불가능
- 그외 역할 추가 팝업의 권한 선택과 공통
- [취소] 버튼
- [취소] 버튼 클릭 시 해당 팝업 닫힘
- [저장] 버튼
- [저장] 버튼 활성화 조건
- 역할명 입력
- 수정된 내역 있을 때
- [저장] 버튼 클릭 시, 수정 내역 저장되고 해당 팝업 닫힘
- [저장] 버튼 활성화 조건
- 역할명
4. 정책
- 비활성 역할은 [[common-policy]] 비활성화 규칙을 따른다.
- 역할 배정 (2026-09-14)
- 역할은 팀 소속마다 따로 붙는다. 한 직원이 여러 팀에 속하면 팀마다 다른 역할을 가질 수 있고, 팀에 넣을 때 역할이 자동으로 따라붙지 않는다.
- 비활성 역할은 배정할 수 없다 — 「「{역할명}」 역할은 비활성이라 배정할 수 없어요.」. 비활성 역할을 붙이면 권한이 0이 되기 때문이다. 다른 조직의 역할도 배정할 수 없다.
- 역할을 고르거나 비우려면 역할 관리 권한(
role:update, 범위 무관)이 필요하다 — 「역할을 배정하거나 바꾸려면 역할 관리 권한이 필요해요.」- 팀 수정 권한 · 직원 관리 권한만 가진 사람이 자기나 남에게 더 높은 역할을 붙여 권한을 올리지 못하게 하는 규칙이다. 팀 추가 · 팀원 추가 · 일괄 역할 변경 · 엑셀 대량 업로드 모두 같다
- 예외 : 기본 역할(일반 직원)로 붙이는 것은 권한 없이 통과한다 — 직원 추가 패널이 등록 직후 붙이는 경로. 본인 범위 권한뿐이라 권한이 올라가지 않는다
- 역할을 비우는 것(null)은 예외가 아니다 — 역할 관리 권한이 있어야 한다
- 서버가 기본 역할을 자동으로 채우는 경로(직원 등록 · 대량 등록의 빈 역할 칸 · 직원 마스터 시트)는 이 검사를 거치지 않는다
- 엑셀 대량 업로드에서 역할 관리 권한이 없으면 역할 열에 기본 역할만 적을 수 있다.
- 역할은 팀 소속에 붙으므로 팀 없이 역할만 배정할 수 없다 — 「역할은 팀 소속에 붙어요. 역할을 배정하려면 팀도 함께 입력해 주세요.」 / 「소속 팀이 없어 역할을 배정할 수 없어요. 팀을 함께 입력해 주세요.」
- 부서 · 팀(병동)
- 부서 = 팀 관리의 최상위 팀, 팀(병동) = 그 아래 모든 단계의 팀. 별도의 부서 데이터는 없다
- 역할 목록을 거르기 위한 분류이며, 권한 범위 · 결재선과는 관계없다
- 둘 다 선택 입력이고, 여러 병원에서 한 역할을 여러 부서 · 팀이 함께 쓰므로 여러 개를 지정할 수 있다
- 저장할 때 같은 항목은 한 번만, 삭제된 팀 · 다른 조직의 팀은 빼고, 부서를 지정했으면 그 부서 밖의 팀은 뺀다
- 역할 삭제 시, 해당 역할을 가지고 있는 경우 삭제 불가
- [삭제] 버튼을 클릭하면 팝업 발생
- 문구 : 해당 역할을 가진 직원이 있어서 삭제할 수 없어요.
- [확인] 버튼 : 해당 팝업만 닫힘
⚠️ 구현 현황 · 하드닝 후보 (2026-08-13)
CODE가 진실이에요. 정책과 코드가 어긋나는 지점을 기록해요.
role.updatePermissions 에 최고 관리자(SUPER_ADMIN) 가드 부재 [기능 수정 후보 — 인증 무결성]
- 문서 기술: §3 최고 관리자 팝업 — 권한 선택 "수정 불가능"(
roles.md:72-73). SUPER_ADMIN 권한 세트는 불변이어야 함. - 실제 구현:
role.updatePermissions(packages/api/src/routers/role.router.ts:244-275)에는role.code === "SUPER_ADMIN"체크가 없어요. 반면 역할 수정/삭제 경로에는 가드가 있어요(role.router.ts:167,:194).ROLE:UPDATE권한만으로 게이트되고 HR_ADMIN 이 role 리소스 전체를 보유하므로(default-permissions.ts), owner 가 아닌 관리자가 SUPER_ADMIN 역할의 권한 행을 비우거나 교체할 수 있어요. - 영향: 최초 owner 는
isSystemAdmin바이패스로 보호되지만(trpc.ts),isSystemAdmin=false인 두 번째 SUPER_ADMIN 보유자는 role→permission 조인으로만 권한을 얻으므로 권한을 스트립당하면 잠겨요. 백엔드 가드가 없고 프론트 비활성화에만 의존해요. (추가로 이 findOne 은organizationId필터 없이id로만 조회해요 — 다중 조직 스코프 공백도 함께 검토 필요.)
삭제 관련 문구가 코드와 다름 / 영문 throw 메시지 [문서 수정]
- 삭제 차단 문구: 문서(
roles.md:88) "해당 역할을 가진 직원이 있어서 삭제할 수 없어요." ↔ 실제(role.router.ts) "이 역할에 배정된 인원이 {n}명 있어 삭제할 수 없습니다. 먼저 배정을 해제하세요."(합쇼체, 문구 다름). role.router.ts:197message: "Cannot delete the SUPER_ADMIN role",updatePermissions의"Role not found"등 영문 throw 메시지 가 남아 있어요 — 프로젝트 규칙(사용자 노출 메시지는 한글 해요체)과 어긋나고, 문서(roles.md:26)는 "최고 관리자는 삭제할 수 없어요." 로 명시해요.