상세 설계 문서

본사관리자 데이터 흐름

본사관리자 데이터 흐름 v1

1. 목적

본 문서는 본사관리자가 부동산 사무소를 등록하고, 오너 관리자 계정을 만들고, 인원/과금/기능 권한을 설정하는 관리자 데이터 흐름을 정의한다.

기준 문서:

  • docs/db-schema-v1.md
  • docs/auth-permission-v1.md
  • docs/billing-model-v1.md

핵심 원칙:

  • 본사관리자 계정은 headquarters_users, 부동산 사용자는 office_users로 분리한다.
  • 사무소 등록은 offices를 루트로 하며, 오너 관리자/과금 정책/기능 권한을 같은 업무 흐름에서 생성한다.
  • 과금 정책과 기능 권한은 덮어쓰지 않고 기간 이력 row로 관리한다.
  • 권한/과금/기능 변경은 audit_logs에 남긴다.
  • 저장 작업은 가능한 한 하나의 DB 트랜잭션으로 처리한다.

2. 본사관리자 권한 체크

2.1 공통 체크

본사관리자 전용 API는 아래 순서로 검사한다.

  1. 세션의 actorType = HEADQUARTERS_USER인지 확인한다.
  2. headquartersUserIdheadquarters_users를 조회한다.
  3. headquarters_users.status = ACTIVE인지 확인한다.
  4. 요청 액션에 필요한 역할을 확인한다.
  5. 과금/기능/권한 변경이면 저장 성공 후 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_USER
  • headquartersUserId = session.headquartersUserId
  • officeId = offices.id
  • ipAddress, userAgent는 요청 컨텍스트에서 저장한다.

5. 유효성 검증 상세

5.1 사무소 검증

  • name은 공백 제거 후 빈 문자열이면 저장하지 않는다.
  • businessNumber는 숫자만 남긴 정규화 값을 기준으로 중복 확인한다.
  • businessNumber가 null이 아니면 offices_business_number_key 충돌을 사전에 확인한다.
  • statusACTIVE, SUSPENDED, CLOSED 중 하나만 허용하되 신규 등록 기본값은 ACTIVE이다.

5.2 오너 관리자 검증

  • 이메일은 소문자 정규화 후 저장한다.
  • 같은 officeId + email 중복을 허용하지 않는다.
  • 최초 등록에서는 반드시 OWNER_ADMIN 1명을 생성한다.
  • billable=false이면 billingExemptReason을 필수로 받는다.
  • status=ACTIVE이면 비밀번호 해시가 반드시 있어야 한다.
  • status=INVITED 정책을 쓰는 경우에도 임시 비밀번호 또는 초대 토큰 정책 중 하나를 선택해 로그인 가능 상태를 명확히 한다.

5.3 인원/과금 검증

  • maxUserLimit, includedUserLimit, baseMonthlyFee, extraUserFee는 정수만 허용한다.
  • baseMonthlyFee, extraUserFee는 0 이상이어야 한다.
  • maxUserLimit < includedUserLimit이면 저장하지 않는다.
  • maxUserLimit는 현재 활성/초대 사용자 수보다 작게 설정할 수 없다.
  • 신규 등록 시 현재 사용자 수는 최초 오너 관리자 1명으로 계산한다.
  • 무료 운영 중에도 billingEnabled=false와 금액 설정을 함께 저장해 예상 과금 계산이 가능해야 한다.

5.4 기능 권한 검증

  • featureIdfeatures.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.statusACTIVE 또는 운영상 허용된 상태인지 확인한다.
  • 현재 과금 정책의 maxUserLimit를 초과하지 않는지 확인한다.
  • 초과 허용 정책을 쓰는 경우 저장은 허용하되 예상 과금에서 초과 인원으로 계산한다.
  • OWNER_ADMIN을 비활성화하거나 역할 변경할 때는 사무소별 활성 오너 관리자 최소 1명을 유지한다.

오너 관리자 추가는 office_users row를 생성하고 audit_logsCREATE 또는 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 = CONFIRMEDconfirmedByHeadquartersUserId로 본사관리자 확정자를 남긴다.

8. 데이터 흐름 예시

8.1 무료 운영 신규 등록

입력:

항목
사무소명 제주수공인중개사사무소
오너 관리자 owner@example.com
등록 가능 총 인원 5
무료 인원 2
기본 월 이용료 0
초과 인원 월 단가 0
실제 과금 여부 false
기능 고객 예약 팝업 off, 공동중개 off

생성 결과:

  • offices 1건
  • permission_groups 사무소 오너 기본 그룹 1건 또는 시스템 기본 그룹 연결
  • office_users 오너 관리자 1건
  • office_billing_policies 현재 정책 1건
  • office_features 활성 기능 수만큼 초기 row
  • audit_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_policiesoffice_features에서 effectiveTo is null인 row는 가장 마지막에 열린 정책으로 판단한다.
  • 실제 적용 정책은 기준일이 effectiveFrom <= 기준일이고 effectiveTo is null 또는 effectiveTo >= 기준일인 row로 조회한다.
  • 정책 적용일이 미래인 경우 기존 row의 effectiveTo를 미래 적용일 전날로 닫고 새 row를 열어두므로, 조회 함수는 반드시 기준일을 인자로 받아야 한다.
  • 과거 월의 billing_monthly_usages가 확정된 뒤 정책 이력을 수정해야 하면 자동 변경 대신 별도 보정 또는 재확정 절차를 둔다.
  • 개인정보를 포함한 오너 관리자 상세 조회/수정 화면 접근은 운영 정책에 따라 audit_logs.READ 또는 UPDATE 대상으로 확장할 수 있다.