Status Lifecycle (HqStatus · AccountStatus · StoreStatus)
SPEC #002·#003 정합 + 후속 SPEC 예고.
Overview
도메인 entity 의 lifecycle. v1 도입 분 + 후속 SPEC 분을 모두 명시.
AccountStatus
| 전이 | 트리거 | 효과 | v1 구현 |
|---|---|---|---|
| → ACTIVE | 가입 API | default | ✅ |
| ACTIVE → SUSPENDED | 운영사 액션 | 즉시 세션 무효화 + 로그인 차단 | ✅ STORE_MANAGER (#029) · OPERATOR (#032) · HQ_MANAGER 는 HqStatus 경유(#018/#024) |
| SUSPENDED → ACTIVE | 운영사 액션 | 로그인 가능 | ✅ STORE_MANAGER (#029) · OPERATOR (#032) |
| ACTIVE → WITHDRAWN | 운영사 회수 / 탈퇴 | soft-delete · 재로그인 차단 · 이메일 재사용 불가 (terminal) | ✅ STORE_MANAGER 회수 (#029) · OPERATOR 회수 (#032) |
| SUSPENDED → WITHDRAWN | 운영사 회수 / 탈퇴 | 위와 동일 (terminal) | ✅ STORE_MANAGER 회수 (#029) · OPERATOR 회수 (#032) |
STORE_MANAGER 점장 계정 라이프사이클은 SPEC #029 로 도입 — ACTIVE ↔ SUSPENDED,
ACTIVE/SUSPENDED → WITHDRAWN(terminal). 운영자 백오피스 /stores 관리 다이얼로그(비밀번호
재설정[ACTIVE]·정지·복구·회수)에서 수행하며, 잘못된 전이는 409 ACCOUNT_INVALID_STATUS_TRANSITION.
OPERATOR 운영자 계정 라이프사이클은 SPEC #032 로 도입 — 점장과 동일한 ACTIVE ↔ SUSPENDED,
ACTIVE/SUSPENDED → WITHDRAWN(terminal) + 초대(생성, passwordMustChange=true)·비밀번호 재설정
[ACTIVE]. 운영자 백오피스 /users 에서 수행한다. 점장과 다른 두 가지 가드: ① self-guard —
본인 계정 정지·재설정·회수 금지(403 OPERATOR_SELF_ACTION_FORBIDDEN, FE 도 isSelf 행 액션 비활성).
② 마지막 활성 운영자 보호 — 시스템에 남은 마지막 ACTIVE 운영자는 정지·회수 불가
(403 OPERATOR_LAST_ACTIVE_FORBIDDEN). 정지·재설정·회수는 대상 활성 세션을 즉시 무효화한다.
모든 전이는 공통 감사 로그(OperatorAuditLog)에 기록(#026/#029/#032 — OperatorAuditAction 에
OPERATOR_* 5종 추가). HQ_MANAGER 계정 상태는 본사 HqStatus 전이(#018)를 통해 간접 제어된다.
일반 탈퇴(self-service)·90일 보관 정책은 후속.
StoreStatus (정지/복구 전이 도입 완료)
폐점(closedAt)은 status 와 독립된 별도 terminal 축이다 —
StoreStatusenum 에CLOSED를 추가하지 않고,store.closed_at타임스탬프로 표현한다(폐점 판정 =closedAt != null). status 분기·CHECK 제약·FE STATUS_META 파급을 회피하기 위함(SPEC #039 결정).
| 전이 | 트리거 | v1 구현 |
|---|---|---|
| → ACTIVE | INSERT (Flyway 시드 / 등록 API) | ✅ |
ACTIVE | INACTIVE → SUSPENDED | 운영사 액션 | ✅ SPEC #037 |
| SUSPENDED → ACTIVE | 운영사 복구 액션 | ✅ SPEC #037 |
| ACTIVE → INACTIVE | 90일 자동 (스케줄러) | ❌ 후속 (FR-11.10) |
| 폐점 (terminal) | closedAt 채움 (운영사 액션) | ✅ SPEC #039 |
정지/복구 전이 — 도입 완료 (SPEC #037):
| 동작 | endpoint | 허용 전이 | 부수 효과 |
|---|---|---|---|
| 정지 | POST /api/v1/admin/stores/{storeId}/suspend | ACTIVE | INACTIVE → SUSPENDED | 소속 STORE_MANAGER active RefreshToken 즉시 revoke(세션 무효화) + 이후 로그인/refresh 차단(AUTH_STORE_SUSPENDED) |
| 복구 | POST /api/v1/admin/stores/{storeId}/reactivate | SUSPENDED → ACTIVE | 소속 점장 로그인 재허용 |
- 그 외(같은 상태·역행) → 409
STORE_INVALID_STATUS_TRANSITION, 미존재 → 404STORE_NOT_FOUND. INACTIVE(휴면)도 정지 가능 — 운영 차단은 status 무관.- 정지 사유(
reason, NotBlank·max255)는store.suspension_reason컬럼에 영속(복구 시 null clear)되고 감사 로그에 기록된다. 가상 본사 산하 매장도 전이 허용. OPERATOR-only. - 운영자 백오피스
/stores/{id}매장 상세 헤더의 status 기반 정지/복구 2-step 확인 다이얼로그에서 수행한다.
폐점(closedAt) — 도입 완료 (SPEC #039):
| 동작 | endpoint | 가드 | 부수 효과 |
|---|---|---|---|
| 폐점 | POST /api/v1/admin/stores/{storeId}/close | closed_at IS NULL 인 매장만 (원자적 UPDATE WHERE) | 소속 STORE_MANAGER active RefreshToken 즉시 revoke + 이후 로그인/refresh 영구 차단(AUTH_STORE_CLOSED) |
- 폐점은 비가역(terminal) — 재폐점 → 409
STORE_ALREADY_CLOSED, 미존재 → 404STORE_NOT_FOUND. 폐점 복구(reopen)는 비목표(후속). - 폐점 사유(
reason, NotBlank·max255) 필수 → 감사 로그(detail)에 기록(closed_at은 시각만 보관, 사유는 감사가 단일 보존처). - status 는 변경하지 않는다(
closed_at만 채움).suspend/reactivateWHERE 에AND closed_at IS NULL가드가 추가돼 폐점 매장 정지/복구는 DB 레벨에서 차단(affected 0 → 409)된다. - 운영자 백오피스
/stores/{id}매장 상세 헤더의 폐점 2-step 확인 다이얼로그(비가역 경고 + 사유 입력 필수)에서 수행한다. 폐점 매장은 헤더에 “폐점됨” 배지를 노출하고 정지/복구/폐점 액션을 모두 숨긴다.
HqStatus (enum 도입 + 정지/복구 전이 도입 완료)
Hq entity 에 status 컬럼 도입됨 (SPEC #013, BE v0.7.0, Flyway V7) — 4 종 enum
(ACTIVE / ONBOARDING / UNPAID / SUSPENDED), 신규 본사 default ONBOARDING, 가상 본사
(IndependentHqIds.SINGLETON) 는 ACTIVE 고정. API 레벨에서 HqAdminListItem.status 로 노출 +
/hq 화면 탭 5종 (전체·활성·온보딩 중·미납·정지) 가 client-side 필터링.
정지/복구 전이 — 도입 완료 (SPEC #018, BE v0.11.0):
| 전이 | endpoint | 규칙 | 효과 |
|---|---|---|---|
| 정지 | POST /api/v1/admin/hq/{id}/suspend | ACTIVE | ONBOARDING | UNPAID → SUSPENDED | 해당 본사 관리자(HQ_MANAGER) active RefreshToken 즉시 revoke(세션 무효화) + 이후 로그인/refresh 차단 |
| 복구 | POST /api/v1/admin/hq/{id}/reactivate | SUSPENDED → ACTIVE | 로그인 재허용 |
- 원자적 UPDATE(
WHERE id=:id AND status IN(...)) + affected-row 검증으로 race/TOCTOU 방지. 전이 불가(이미 같은 상태 등) → 409HQ_INVALID_STATUS_TRANSITION. - INDEPENDENT 가상 본사는 정지 불가 → 403
INDEPENDENT_HQ_SUSPENSION_FORBIDDEN(시스템 단일·항시 ACTIVE). - OPERATOR-only. 정지 사유(reason) 저장은 v1 미포함(후속).
아직 미구현 — 후속 SPEC:
- 전이 규칙 자동화 (ONBOARDING→ACTIVE 트리거·UNPAID 결제 실패 자동 전환)
- WITHDRAWN(탈퇴/soft-delete) 전이·정지 사유 저장
lifecycle (handoff 10-surface §3 참고):
영향:
SUSPENDED본사 — 임퍼소네이션 confirm dialog 비활성 + 사유 표시 (handoff §3). 본사 관리자 로그인 차단·진행 중 세션 무효화.INDEPENDENT가상 본사 — type 분기로만 차단 (status 무관). 정지/복구 액션 자체를 숨김.
TicketStatus (#027 — CS 티켓 v1, 전이 도입 완료)
CS 티켓의 처리 상태. 생성 직후 OPEN. 전이는 PATCH /api/v1/admin/tickets/{id}/status.
| 전이 | 규칙 |
|---|---|
| 착수 | OPEN → IN_PROGRESS |
| 해결 | IN_PROGRESS → RESOLVED |
| 종결 | RESOLVED → CLOSED |
| 재오픈 | RESOLVED → IN_PROGRESS · CLOSED → IN_PROGRESS |
- 그 외 전이는 backend 가 409
TICKET_INVALID_STATUS_TRANSITION으로 거부(서버 가드). FE 는 현재 status 의 유효 전이만 컨트롤로 노출(ticket-meta.tsvalidTransitions— 클라 가드, 이중 방어). - 409 는 토스트/inline 에러로 처리하고 목록 새로고침을 유도한다.
우선순위(
TicketPriority)는 생성 시점 고정 — v1 변경 endpoint 없음(후속 SPEC). 담당자는PATCH /tickets/{id}/assignee로 배정/해제(상태머신과 무관).
ContractPlan lifecycle (후속 SPEC)
DRAFT → ACTIVE (계약 발효) → EXPIRED (만료) | TERMINATED (조기 해지)활성 협약 중복 차단 — 같은 본사·매장 활성 협약 2개 차단 (FR-20.7).
TermsDocument · PrivacyPolicy lifecycle (#033 — effectiveAt + status)
게시(즉시: effectiveAt<=now) → status=ACTIVE (직전 유효본 → SUPERSEDED)
게시(예약: effectiveAt>now) → status=SCHEDULED ──(발효 시각 도달)──▶ 읽기 계산으로 자동 유효본LegalDocumentStatus enum: SCHEDULED → ACTIVE → SUPERSEDED.
- stale 주의: 스케줄러가 없어
status는 게시 시점 스냅샷이다. 예약본(SCHEDULED)이 발효 시각을 지나도 컬럼은SCHEDULED로 남을 수 있다. “현재 유효본” 판정은 항상effective_at <= now중 최신(effectiveAt DESC)으로 한다 —status는 이력 표시·필터 보조값./active응답의status는 항상ACTIVE로 보정. - soft-delete 안 함. 과거 버전 영구 보존(별도 history 테이블 없음).
is_active는 레거시 컬럼(점진 마이그레이션 — 완전 제거는 follow-up). - partial-unique(
idx_*_active)는 예약본 공존을 위해 #033 에서 드롭. version 유일(uq_*_version) 유지.
Decisions
- HqStatus 도입을 미룬 이유 — SPEC #002 는 schema 단계. status 사용처 (UI · 임퍼소네이션 차단 · 정산 차단) 가 명확해진 시점에 add. lifecycle 변경은 PR 한 번에 모든 호출부 함께 갱신해야 안전.
Roadmap
HqStatus 도입 (SPEC #013)✅ ·정지/복구 전이 + 세션 무효화 (SPEC #018)✅StoreStatus 정지/복구 전이 + 세션 무효화 + 정지 사유 영속 (SPEC #037)✅매장 폐점(closedAt) 액션 + 세션 무효화 + 인증 차단 (SPEC #039)✅- HqStatus 자동 전환 (ONBOARDING→ACTIVE·UNPAID 결제 실패) · 정지 사유 저장
- StoreStatus 자동 전환 스케줄러 (90일 INACTIVE) · 폐점 복구(reopen)
- AccountStatus.SUSPENDED 즉시 세션 무효화
- ContractPlan · BillingKey · Invoice lifecycle
References
- SPEC #002 §2-3·§2-9
- SPEC #003 §2-1 (AccountStatus)
- handoff
10-surface-ops-backoffice.md§3·§6