본사관리자 데이터 흐름 v1
1. 목적
본 문서는 본사관리자가 부동산 사무소를 등록하고, 오너 관리자 계정을 만들고, 인원/과금/기능 권한을 설정하는 관리자 데이터 흐름을 정의한다.
기준 문서:
docs/db-schema-v1.mddocs/auth-permission-v1.mddocs/billing-model-v1.md
핵심 원칙:
- 본사관리자 계정은
headquarters_users, 부동산 사용자는office_users로 분리한다. - 사무소 등록은
offices를 루트로 하며, 오너 관리자/과금 정책/기능 권한을 같은 업무 흐름에서 생성한다. - 과금 정책과 기능 권한은 덮어쓰지 않고 기간 이력 row로 관리한다.
- 권한/과금/기능 변경은
audit_logs에 남긴다. - 저장 작업은 가능한 한 하나의 DB 트랜잭션으로 처리한다.
2. 본사관리자 권한 체크
2.1 공통 체크
본사관리자 전용 API는 아래 순서로 검사한다.
- 세션의
actorType = HEADQUARTERS_USER인지 확인한다. headquartersUserId로headquarters_users를 조회한다.headquarters_users.status = ACTIVE인지 확인한다.- 요청 액션에 필요한 역할을 확인한다.
- 과금/기능/권한 변경이면 저장 성공 후
audit_logs를 기록한다.
권한 실패 응답:
| 상황 | 응답 |
|---|---|
| 로그인 없음 | 401 |
| 본사관리자 세션 아님 | 403 |
| 본사관리자 상태가 비활성 | 403 |
| 역할 권한 없음 | 403 |
2.2 역할별 허용 액션
| 액션 | SUPER_ADMIN | ADMIN | BILLING_MANAGER | SUPPORT |
|---|---|---|---|---|
| 사무소 등록 | Y | Y | N | N |
| 오너 관리자 계정 생성 | Y | Y | N | N |
| 사무소 기본 정보 수정 | Y | Y | N | N |
| 과금 정책 생성/변경 | Y | Y | Y | N |
| 기능 권한 생성/변경 | Y | Y | Y | N |
| 사무소/사용자 조회 | Y | Y | Y | Y |
| 감사 로그 조회 | Y | Y | Y | Y |
SUPPORT는 운영 지원 조회 전용을 기본으로 한다. 임시 조치가 필요하면 SUPER_ADMIN 또는 ADMIN이 수행한다.
3. 부동산 등록 폼
3.1 사무소 기본 정보
| 필드 | 저장 대상 | 필수 | 검증 |
|---|---|---|---|
| 사무소명 | offices.name |
Y | 1~150자 |
| 사업자등록번호 | offices.businessNumber |
N | 숫자/하이픈 정규화 후 중복 확인 |
| 중개사무소 등록번호 | offices.brokerageRegistrationNo |
N | 80자 이하 |
| 대표자명 | offices.ownerName |
N | 100자 이하 |
| 대표 전화 | offices.phone |
N | 전화번호 형식 정규화 |
| 우편번호 | offices.postalCode |
N | 20자 이하 |
| 주소 | offices.address1 |
N | 255자 이하 |
| 상세 주소 | offices.address2 |
N | 255자 이하 |
| 상태 | offices.status |
Y | 기본 ACTIVE |
| 내부 메모 | offices.memo |
N | text |
3.2 오너 관리자 계정
| 필드 | 저장 대상 | 필수 | 검증 |
|---|---|---|---|
| 이름 | office_users.name |
Y | 1~100자 |
| 이메일 | office_users.email |
Y | 이메일 형식, 같은 사무소 내 unique |
| 연락처 | office_users.phone |
N | 전화번호 형식 정규화 |
| 임시 비밀번호 | office_users.passwordHash |
Y | 평문 저장 금지, 서버에서 해시 |
| 계정 상태 | office_users.status |
Y | 기본 ACTIVE 또는 INVITED |
| 과금 포함 여부 | office_users.billable |
Y | 기본 true |
| 과금 제외 사유 | office_users.billingExemptReason |
조건부 | billable=false이면 필수 |
| 권한 그룹 | office_users.permissionGroupId |
N | 기본 오너 권한 그룹 연결 |
오너 관리자는 role = OWNER_ADMIN, invitedByHeadquartersUserId = session.headquartersUserId로 생성한다.
3.3 인원/과금 정책
| 필드 | 저장 대상 | 필수 | 검증 |
|---|---|---|---|
| 등록 가능 총 인원 | office_billing_policies.maxUserLimit |
Y | 1 이상 |
| 무료/기본 포함 인원 | office_billing_policies.includedUserLimit |
Y | 0 이상 |
| 기본 월 이용료 | office_billing_policies.baseMonthlyFee |
Y | 0 이상 |
| 초과 인원 월 단가 | office_billing_policies.extraUserFee |
Y | 0 이상 |
| 실제 과금 여부 | office_billing_policies.billingEnabled |
Y | 무료 운영 기본 false |
| 적용 시작일 | office_billing_policies.effectiveFrom |
Y | 등록일 또는 미래일 |
| 정책 메모 | office_billing_policies.memo |
N | 할인/무료/제휴 사유 |
추가 검증:
maxUserLimit >= includedUserLimit- 최초 오너 관리자 1명은 등록 가능 총 인원에 포함한다.
billingEnabled = true이고 모든 금액이 0원이면 본사관리자에게 확인 경고를 표시한다.
3.4 기능 권한 설정
기능 목록은 features.status = ACTIVE인 row를 기준으로 표시한다.
| 필드 | 저장 대상 | 필수 | 검증 |
|---|---|---|---|
| 기능 코드 | office_features.featureId |
Y | 활성 기능만 선택 가능 |
| 사용 여부 | office_features.enabled |
Y | boolean |
| 월 과금액 | office_features.monthlyFee |
Y | 0 이상 |
| 적용 시작일 | office_features.effectiveFrom |
Y | 과금 정책 시작일과 같거나 별도 지정 |
| 기능 메모 | office_features.memo |
N | 상담/예외 사유 |
기본 제공 기능은 features.defaultEnabled, features.defaultMonthlyFee를 초기값으로 사용한다. 기본 미제공 기능은 enabled=false, monthlyFee=0을 기본값으로 한다.
4. 신규 등록 저장 순서
신규 사무소 등록은 하나의 트랜잭션에서 처리한다.
권한 체크
→ 입력값 정규화
→ 중복/제약 검증
→ offices 생성
→ 기본 permission_groups 확인 또는 사무소별 오너 권한 그룹 생성
→ OWNER_ADMIN office_users 생성
→ office_billing_policies 현재 정책 생성
→ office_features 초기 row 생성
→ audit_logs 생성
→ 커밋
4.1 생성되는 레코드
| 순서 | 테이블 | action | 주요 값 |
|---|---|---|---|
| 1 | offices |
CREATE | status=ACTIVE, createdByHeadquartersUserId=session.headquartersUserId |
| 2 | permission_groups |
CREATE 또는 SELECT | 사무소별 OWNER_ADMIN_DEFAULT 또는 시스템 기본 그룹 |
| 3 | office_users |
CREATE | role=OWNER_ADMIN, status=ACTIVE/INVITED, billable=true |
| 4 | office_billing_policies |
CREATE | effectiveTo=null, changedByHeadquartersUserId=session.headquartersUserId |
| 5 | office_features |
CREATE | 기능별 초기 사용 여부/금액, effectiveTo=null |
| 6 | audit_logs |
CREATE | 사무소 등록, 오너 생성, 과금/기능 설정 이력 |
4.2 감사 로그
등록 성공 시 최소 아래 로그를 남긴다.
| action | resourceType | resourceId | metadataJson |
|---|---|---|---|
| CREATE | OFFICE |
offices.id |
사무소명, 사업자등록번호 존재 여부, 상태 |
| CREATE | OFFICE_USER |
office_users.id |
역할, 상태, 과금 포함 여부 |
| BILLING_CHANGE | OFFICE_BILLING_POLICY |
office_billing_policies.id |
기본료, 포함 인원, 초과 단가, 과금 여부, 적용일 |
| PERMISSION_CHANGE | OFFICE_FEATURE |
null 또는 대표 feature id | 기능별 enabled/monthlyFee 요약 |
공통 값:
actorType = HEADQUARTERS_USERheadquartersUserId = session.headquartersUserIdofficeId = offices.idipAddress,userAgent는 요청 컨텍스트에서 저장한다.
5. 유효성 검증 상세
5.1 사무소 검증
name은 공백 제거 후 빈 문자열이면 저장하지 않는다.businessNumber는 숫자만 남긴 정규화 값을 기준으로 중복 확인한다.businessNumber가 null이 아니면offices_business_number_key충돌을 사전에 확인한다.status는ACTIVE,SUSPENDED,CLOSED중 하나만 허용하되 신규 등록 기본값은ACTIVE이다.
5.2 오너 관리자 검증
- 이메일은 소문자 정규화 후 저장한다.
- 같은
officeId + email중복을 허용하지 않는다. - 최초 등록에서는 반드시
OWNER_ADMIN1명을 생성한다. billable=false이면billingExemptReason을 필수로 받는다.status=ACTIVE이면 비밀번호 해시가 반드시 있어야 한다.status=INVITED정책을 쓰는 경우에도 임시 비밀번호 또는 초대 토큰 정책 중 하나를 선택해 로그인 가능 상태를 명확히 한다.
5.3 인원/과금 검증
maxUserLimit,includedUserLimit,baseMonthlyFee,extraUserFee는 정수만 허용한다.baseMonthlyFee,extraUserFee는 0 이상이어야 한다.maxUserLimit < includedUserLimit이면 저장하지 않는다.maxUserLimit는 현재 활성/초대 사용자 수보다 작게 설정할 수 없다.- 신규 등록 시 현재 사용자 수는 최초 오너 관리자 1명으로 계산한다.
- 무료 운영 중에도
billingEnabled=false와 금액 설정을 함께 저장해 예상 과금 계산이 가능해야 한다.
5.4 기능 권한 검증
featureId는features.status = ACTIVE인 기능만 허용한다.- 같은
officeId + featureId에서effectiveTo is null인 row는 1개만 유지한다. enabled=false인 기능의monthlyFee는 0을 권장한다. 예외적으로 예약 과금 금액을 저장할 경우memo에 사유를 남긴다.- 기능 사용이 업무 메뉴 노출에 영향을 주면 저장 직후 권한 캐시를 무효화한다.
6. 기존 사무소 변경 흐름
6.1 과금 정책 변경
과금 정책은 현재 row를 수정하지 않고 기존 row를 닫은 뒤 새 row를 생성한다.
권한 체크
→ 대상 officeId 확인
→ 현재 office_billing_policies 조회
→ 변경 입력값 검증
→ 기존 현재 row의 effectiveTo = 새 effectiveFrom - 1일
→ 새 office_billing_policies 생성
→ BILLING_CHANGE audit_logs 생성
→ 커밋
변경 시 생성/수정되는 레코드:
| 테이블 | 처리 |
|---|---|
office_billing_policies |
기존 현재 row effectiveTo 설정 |
office_billing_policies |
새 현재 row 생성 |
audit_logs |
BILLING_CHANGE 기록 |
metadataJson에는 변경 전/후 값을 함께 저장한다.
{
"before": {
"baseMonthlyFee": 0,
"maxUserLimit": 5,
"includedUserLimit": 2,
"extraUserFee": 0,
"billingEnabled": false
},
"after": {
"baseMonthlyFee": 10000,
"maxUserLimit": 10,
"includedUserLimit": 2,
"extraUserFee": 5000,
"billingEnabled": true
},
"effectiveFrom": "2026-06-01",
"memo": "유료 전환"
}
6.2 기능 권한 변경
기능 권한도 현재 row를 직접 덮어쓰지 않는다.
권한 체크
→ 대상 officeId와 featureId 확인
→ 현재 office_features 조회
→ 변경 입력값 검증
→ 기존 현재 row의 effectiveTo = 새 effectiveFrom - 1일
→ 새 office_features 생성
→ PERMISSION_CHANGE audit_logs 생성
→ 권한/메뉴 캐시 무효화
→ 커밋
변경 시 생성/수정되는 레코드:
| 테이블 | 처리 |
|---|---|
office_features |
기존 현재 row effectiveTo 설정 |
office_features |
새 현재 row 생성 |
audit_logs |
PERMISSION_CHANGE 기록 |
metadataJson에는 기능 코드, 변경 전/후 enabled, 금액, 적용일, 메모를 저장한다.
6.3 오너 관리자 추가/변경
본사관리자가 오너 관리자를 추가할 때는 다음을 확인한다.
- 대상 사무소가 존재하고
deletedAt is null인지 확인한다. offices.status가ACTIVE또는 운영상 허용된 상태인지 확인한다.- 현재 과금 정책의
maxUserLimit를 초과하지 않는지 확인한다. - 초과 허용 정책을 쓰는 경우 저장은 허용하되 예상 과금에서 초과 인원으로 계산한다.
OWNER_ADMIN을 비활성화하거나 역할 변경할 때는 사무소별 활성 오너 관리자 최소 1명을 유지한다.
오너 관리자 추가는 office_users row를 생성하고 audit_logs에 CREATE 또는 PERMISSION_CHANGE를 남긴다.
7. 월 과금 스냅샷과 연결
본사관리자가 등록/변경한 정책은 월별 과금 계산에서 다음처럼 사용한다.
대상 월
→ 해당 월에 유효한 office_billing_policies 조회
→ 해당 월에 유효한 office_features 조회
→ billable=true and status=ACTIVE인 office_users 계산
→ 초과 인원/기능 금액 계산
→ billing_monthly_usages 생성 또는 갱신
계산식:
billableUserCount = ACTIVE office_users where billable = true
extraUserCount = max(0, billableUserCount - includedUserLimit)
extraUserAmount = extraUserCount * extraUserFee
featureAmount = sum(enabled office_features.monthlyFee)
totalAmount = baseMonthlyFee + extraUserAmount + featureAmount
billingEnabled=false이면 totalAmount는 예상 금액으로 저장하되 실제 청구 대상은 아니다. 월별 확정 row는 billing_monthly_usages.status = CONFIRMED와 confirmedByHeadquartersUserId로 본사관리자 확정자를 남긴다.
8. 데이터 흐름 예시
8.1 무료 운영 신규 등록
입력:
| 항목 | 값 |
|---|---|
| 사무소명 | 제주수공인중개사사무소 |
| 오너 관리자 | owner@example.com |
| 등록 가능 총 인원 | 5 |
| 무료 인원 | 2 |
| 기본 월 이용료 | 0 |
| 초과 인원 월 단가 | 0 |
| 실제 과금 여부 | false |
| 기능 | 고객 예약 팝업 off, 공동중개 off |
생성 결과:
offices1건permission_groups사무소 오너 기본 그룹 1건 또는 시스템 기본 그룹 연결office_users오너 관리자 1건office_billing_policies현재 정책 1건office_features활성 기능 수만큼 초기 rowaudit_logs최소 4건
8.2 유료 전환
입력:
| 항목 | 변경 전 | 변경 후 |
|---|---|---|
| 기본 월 이용료 | 0 | 10000 |
| 무료 인원 | 2 | 2 |
| 초과 인원 월 단가 | 0 | 5000 |
| 실제 과금 여부 | false | true |
| 적용 시작일 | 등록일 | 2026-06-01 |
처리 결과:
- 기존
office_billing_policies.effectiveTo = 2026-05-31 - 새
office_billing_policies.effectiveFrom = 2026-06-01,effectiveTo = null audit_logs.action = BILLING_CHANGE
9. API 단위 권장 트랜잭션
| API | 트랜잭션 포함 테이블 |
|---|---|
| 사무소 신규 등록 | offices, permission_groups, office_users, office_billing_policies, office_features, audit_logs |
| 과금 정책 변경 | office_billing_policies, audit_logs |
| 기능 권한 변경 | office_features, audit_logs |
| 오너 관리자 추가 | office_users, audit_logs |
| 월 과금 확정 | billing_monthly_usages, audit_logs |
트랜잭션 실패 시 일부 row만 남지 않아야 한다. 특히 offices만 생성되고 오너 관리자 또는 과금 정책이 없는 상태는 허용하지 않는다.
10. 운영 주의사항
- 본사관리자는 사무소 업무 사용자가 아니므로
office_users에 본사 계정을 만들지 않는다. office_billing_policies와office_features에서effectiveTo is null인 row는 가장 마지막에 열린 정책으로 판단한다.- 실제 적용 정책은 기준일이
effectiveFrom <= 기준일이고effectiveTo is null또는effectiveTo >= 기준일인 row로 조회한다. - 정책 적용일이 미래인 경우 기존 row의
effectiveTo를 미래 적용일 전날로 닫고 새 row를 열어두므로, 조회 함수는 반드시 기준일을 인자로 받아야 한다. - 과거 월의
billing_monthly_usages가 확정된 뒤 정책 이력을 수정해야 하면 자동 변경 대신 별도 보정 또는 재확정 절차를 둔다. - 개인정보를 포함한 오너 관리자 상세 조회/수정 화면 접근은 운영 정책에 따라
audit_logs.READ또는UPDATE대상으로 확장할 수 있다.