---
template: flow-doc@1
doc_id: 01-admin-console
title: 통합 관리자 콘솔 절차군
config: ../_meta/template-config.yaml
last_verified: fb001e1d
flows:
  - id: admin-console-session-entry
    actor: 관리자
    status: active-prod
    status_source: ./00-ia.md#admin-manage-login
    evidence: [S-route, S-component, S-guard]
    limitations:
      - 절차의 단계 순서는 라우트 등록과 컴포넌트 실존과 서버 guard 정독에서 역산했다. 런타임 통과 기록은 대조하지 않았다
      - 사이드바에 실제로 어떤 메뉴가 뜨는지는 tn_menu_auth_map 행에 달려 있고 그 스냅샷은 이 창에 없다
    refs:
      - ./00-ia.md#admin-manage-login
      - ./00-ia.md#admin-login
      - ./00-ia.md#admin-root
      - ./00-ia.md#admin-403
    anchors:
      - insuniverse-admin/src/app/router/routes/page.routes.tsx:46
      - insuniverse-server/apps/api/src/modules/auth/admin/jwt.guard.ts:26
      - insuniverse-server/apps/api/src/modules/menu/menu.repository.ts:32
      - insuniverse-server/apps/api/src/modules/auth/admin/roles.guard.ts:22
      - insuniverse-server/apps/api/src/modules/auth/admin/corp-readonly.guard.ts:21
      - insuniverse-server/apps/api/src/modules/auth/admin/corp-analysis-view.guard.ts:34
  - id: admin-console-member-lifecycle
    actor: 관리자
    status: active-prod
    status_source: ./00-ia.md#admin-user-user
    evidence: [S-route, S-component, S-roles]
    limitations:
      - 계정 정지가 진행 중 분석·구독·결제에 각각 무엇을 하는지는 이 창의 코드 정독으로 전건 확정하지 못했다. 확정한 것만 절차에 적고 나머지는 아래 실패·이탈 표의 미상 행으로 남긴다
      - 해제 시 원상 복구 범위는 관측하지 못했다. 그 사실 자체를 적는다
    refs:
      - ./00-ia.md#admin-user-user
      - ./00-ia.md#admin-subscription-subscription
    anchors:
      - insuniverse-admin/src/app/router/routes/domains/b2c.routes.tsx:24
      - insuniverse-server/apps/api/src/modules/user/user-admin.controller.ts:21
      - insuniverse-server/apps/api/src/modules/auth/admin/roles.guard.ts:27
  - id: admin-console-settlement-adjust
    actor: 관리자
    status: active-prod
    status_source: ./00-ia.md#admin-client-settlement-client-settlement
    evidence: [S-route, S-component, S-roles]
    limitations:
      - 정산 확정 취소가 남기는 변경 이력의 소재를 이 창에서 찾지 못했다. 검색어는 audit · admin_log · tnAdminLog 셋이며 그 밖은 보지 못한다
    refs:
      - ./00-ia.md#admin-client-settlement-client-settlement
      - ./00-ia.md#admin-corp-subscription-corp-settlement
    anchors:
      - insuniverse-server/apps/api/src/modules/order/order/order-admin.controller.ts:1
      - insuniverse-admin/src/app/router/routes/domains/b2c.routes.tsx:36
      - insuniverse-server/apps/api/src/modules/auth/admin/corp-readonly.guard.ts:33
      - insuniverse-server/apps/api/src/modules/auth/admin/corp-readonly.guard.ts:24
  - id: admin-console-payment-reconcile
    actor: 관리자
    status: active-prod
    status_source: ./00-ia.md#admin-money-payment
    evidence: [S-route, S-component, S-roles]
    limitations:
      - 환불 컨트롤러가 통째로 주석 처리돼 있다. 화면은 등록돼 있고 서버 경로는 열려 있지 않다는 비대칭을 절차에 적었다
    refs:
      - ./00-ia.md#admin-money-payment
      - ./00-ia.md#admin-money-refund
      - ./00-ia.md#admin-subscription-order
    anchors:
      - insuniverse-server/apps/api/src/modules/refund/refund-admin.controller.ts:3
      - insuniverse-server/apps/api/src/modules/money/money.repository.ts:1859
  - id: admin-console-bulk-operation
    actor: 관리자
    status: active-prod
    status_source: ./00-ia.md#admin-subscription-analysis
    evidence: [S-route, S-component, S-constant]
    limitations:
      - 대량 작업의 부분 실패를 화면이 어떻게 표시하는지는 라우트와 컴포넌트 실존까지만 확인했다. 실패분 재실행 수단의 유무는 이 창에서 확정하지 못했다
    refs:
      - ./00-ia.md#admin-subscription-analysis
      - ./00-ia.md#admin-statistics-analysis
    anchors:
      - insuniverse-server/apps/api/src/modules/treat/monitor/treat-monitor.controller.ts:52
      - insuniverse-admin/src/app/router/routes/domains/b2c.routes.tsx:24
  - id: admin-console-data-correction
    actor: 관리자
    status: active-prod
    status_source: ./00-ia.md#admin-search-codes
    evidence: [S-route, S-component, S-roles]
    limitations:
      - 기준 데이터 수정이 정산·통계에 각각 어떻게 반영되는지는 코드 경로를 따라가지 못했다. 반영 시점의 미상을 실패·이탈 표에 적었다
    refs:
      - ./00-ia.md#admin-search-codes
      - ./00-ia.md#admin-site-criteria
    anchors:
      - insuniverse-server/apps/api/src/modules/search/search-admin.controller.ts:1
      - insuniverse-admin/src/app/router/routes/domains/data.routes.tsx:1
  - id: admin-console-concurrent-write
    actor: 관리자
    status: active-prod
    status_source: ./00-ia.md#admin-money-general
    evidence: [S-route, S-schema, S-constant]
    limitations:
      - 잠금 관측은 검색어 네 개(version · optimistic · forUpdate · FOR UPDATE)로만 훑었다. 그 밖의 구현은 보지 못했고 부재 증명이 아니다
    refs:
      - ./00-ia.md#admin-money-general
      - ./00-ia.md#admin-money-corp-money
    anchors:
      - insuniverse-server/apps/api/src/modules/money/money.repository.ts:1859
      - insuniverse-server/apps/api/src/modules/money/money.repository.ts:2073
---

# 통합 관리자 콘솔 절차군

> **admin 은 자동화된 테스트 근거가 없다.** `insuniverse-admin` 동결 HEAD `fb001e1d` 에 테스트 파일이 한 건도 없다. 이 디렉터리의 모든 문서는 라우트 등록과 컴포넌트 실존과 소스 읽기만을 근거로 하며 화면 축에 `S-test` 등급을 단 항목이 하나도 없다. 절차의 단계 순서가 런타임에서 실제로 그 순서로 도는지는 이 문서가 판정하지 않았다.

이 문서는 운영사 관리자(`admin.role === 'admin'`)가 통합 콘솔에서 수행하는 절차군을 담는다. 화면 구조의 정본은 [화면 인벤토리](./00-ia.md)이며 이 문서는 그 화면들을 지나는 **순서**와 각 지점에서 걸리는 **권한 층**을 쓴다.

**총론은 이 문서가 재기술하지 않는다.** 권한 규칙의 정본은 `docs/02-access/` 소유이고, 계약·한도는 `docs/03-contracts/` 와 `docs/04-limits/` 소유이며, 이 창에는 그 디렉터리가 한 파일도 없다. **부재 문서로는 링크를 걸지 않는다** — 경로를 산문으로 적고 실재하게 되는 창에서 링크로 바꾼다. 스케줄 정책은 이미 서 있으므로 [보존 주기](../05-schedules/01-retention.md)와 [정산 주기](../05-schedules/02-billing-cycles.md)를 그대로 참조한다.

**권한 층 5종의 판정은 흐름마다 다르다.** 다섯 층을 전부 적는 것은 규약이고, 걸리지 않는 층에 `해당 없음` 과 이유를 적는 것이 정보다 — 층을 빠뜨린 것과 그 층이 이 흐름에서 무력하다고 판정한 것은 다르다.

**인증 층에는 이 문서가 검증하지 않은 우회 분기가 둘 있다.** `admin/jwt.guard.ts:33` 은 핸들러 메타데이터 `isPublic` 이 참이면 토큰을 읽기 전에 통과시키고, `admin/jwt.guard.ts:42` 는 토큰 검증이 실패해도 `isDevSwaggerRequest` 가 참이면 `DEV_ADMIN`(`admId 19`)을 주입하고 통과시킨다. **아래 모든 권한 표의 인증 층 `차단` 판정과 모든 이탈 표의 `HTTP 401` 은 그 두 분기가 거짓일 때의 값이다.** 이 창의 관측으로는 `isPublic` 을 세팅하는 데코레이터가 저장소 전체에 0건이고 `isDevSwaggerRequest` 는 `NODE_ENV === 'dev'` 와 `referer` 와 `x-forwarded-for` 셋을 함께 요구한다 — 그래서 지금 도달 가능한 분기는 없다고 본다. **부재 증명이 아니다** — 검색어는 `isPublic` 하나이고 배포 환경변수는 대조하지 않았다. 코드 수정은 이 창 범위 밖이며 backlog `50m3-admin-jwt-guard-bypass-branches` 로 이월했다.

## 흐름 목록

| flow id | 목적 | actor | status |
|---|---|---|---|
| admin-console-session-entry | 통합 콘솔에 들어가 자기 권한 범위의 메뉴를 받는다 | 관리자 | active-prod |
| admin-console-member-lifecycle | 회원 계정을 정지하고 해제한다 | 관리자 | active-prod |
| admin-console-settlement-adjust | 확정된 정산을 수정하거나 취소한다 | 관리자 | active-prod |
| admin-console-payment-reconcile | 결제는 됐는데 후속 처리가 어긋난 건을 바로잡는다 | 관리자 | active-prod |
| admin-console-bulk-operation | 대량 조회·엑셀 내보내기를 돌리고 결과를 확인한다 | 관리자 | active-prod |
| admin-console-data-correction | 운영 중 발견한 기준 데이터 오류를 화면에서 고친다 | 관리자 | active-prod |
| admin-console-concurrent-write | 두 관리자가 같은 건을 동시에 저장한다 | 관리자 | active-prod |

## 흐름

### admin-console-session-entry

**전제**

- 운영사 관리자 계정을 보유하고 있고 그 계정에 권한 그룹(`tn_auth`)이 배정되어 있다
- 로그인 화면은 둘이다 — `admin-manage-login` 이 통합 진입이고 `admin-login` 은 기업·조직 전용이다. 경로 분리는 `48e52b5d` 가 넣었다
- 사이드바 구성의 입력은 `tn_menu_auth_map` 배분이며 그 스냅샷은 이 창에 없다

**절차**

1. `admin-manage-login` 에서 아이디·비밀번호를 제출한다. 성공하면 서버가 `_aaT` 쿠키를 내린다 (`admin/jwt.guard.ts:26` 이 이후 모든 요청에서 그 쿠키를 읽는다).
2. 클라이언트가 `GET /admin/menu/my` 로 자기 메뉴 트리를 받는다. 서버는 `tnMenuAuthMap` 을 `tnAdmin.athId` 로 조인해 배분된 메뉴와 그 부모를 낸다 (`menu.repository.ts:32-39`).
3. 배분된 메뉴가 0건이면 `menu.repository.ts:41-43` 이 빈 배열을 낸다. 사이드바가 비고 진입 지점이 사라진다.
4. `admin-root` 로 들어가면 각 도메인 화면으로 이동할 수 있다. 권한이 맞지 않는 요청은 서버가 거부하고 클라이언트가 `admin-403` 으로 보낸다.

**사이드바는 인가가 아니라 표시다.** 2단계가 만드는 것은 메뉴 목록이고 API 차단이 아니다 — 그 귀결은 `authority-api-exposure` 가 다룬다 ([권한 관리](./04-authority-management.md)).

**권한**

| 층 | 차단성 | 이 흐름에서의 강제 지점 | 실패 시 |
|---|---|---|---|
| 인증 | 차단 | `admin/jwt.guard.ts:26` — `_aaT` 쿠키를 검증한다 | HTTP 401 |
| 계정 종류 | 차단 | `roles.guard.ts:22` — 이 흐름의 `GET /admin/menu/my` 에는 `@Roles` 선언이 없어 통과한다 | 해당 없음 — 선언이 없으면 통과가 정상 경로다 |
| 쓰기 스코프 | 차단 | 해당 없음 — `CorpReadonlyGuard` 는 `admin.corpScope === 'ORG'` 에서만 걸리고 운영사 관리자는 그 값이 아니다 (`corp-readonly.guard.ts:21`) | 해당 없음 |
| 기업 노출 등급 | 차단 | 해당 없음 — `CorpAnalysisViewGuard` 는 `admin.role === 'corp'` 에서만 걸린다 (`corp-analysis-view.guard.ts:34`) | 해당 없음 |
| 메뉴 노출 | 비차단 | `menu.repository.ts:32-39` — 배분된 메뉴만 사이드바에 실린다 | 사이드바에서 사라짐. URL 을 직접 치면 위 네 층만 통과하면 열린다 |

**실패·이탈**

| 지점 | 조건 | 결과 |
|---|---|---|
| 로그인 제출 | 자격 증명 불일치 | 막다름. 로그인 화면에 머문다 |
| 이후 모든 요청 | `_aaT` 쿠키 부재·만료 | HTTP 401 (`admin/jwt.guard.ts:47`) — 위 우회 분기 둘이 거짓일 때 |
| 메뉴 조회 | 배분된 메뉴 0건 | 계속 빈 사이드바로 진입한다. 절차는 성립하나 이동 지점이 없다 |
| 도메인 화면 진입 | 계정 종류가 맞지 않는 API 응답 403 | `./00-ia.md#admin-403` 으로 이탈 |

### admin-console-member-lifecycle

**전제**

- 통합 콘솔에 로그인되어 있다 (`admin-console-session-entry` 완료)
- 대상 회원이 `admin-user-user` 목록에 있다
- 그 회원의 진행 중 구독은 `admin-subscription-subscription` 에서 따로 본다

**절차**

1. `admin-user-user` 에서 대상 회원을 찾는다. 목록 조회는 `GET /admin/user` 이고 `@Roles('admin','sales','corp')` 아래 있다 (`user-admin.controller.ts:21-23`).
2. 상세에서 계정 상태를 정지로 바꾼다.
3. 진행 중이던 구독의 상태는 `admin-subscription-subscription` 에서 별도로 확인한다. **정지가 구독을 자동으로 무엇으로 바꾸는지는 이 창에서 확정하지 못했다.**
4. 해제는 같은 화면에서 상태를 되돌리는 것이다.

**모르는 것을 모른다고 적는다.** 3단계의 연쇄(분석·구독·결제 각각의 귀결)와 4단계의 원상 복구 범위는 코드 정독으로 확정하지 못했다. `막다름` 을 모름의 대체로 쓰지 않으므로 아래 표에 미상 행으로 남긴다.

**권한**

| 층 | 차단성 | 이 흐름에서의 강제 지점 | 실패 시 |
|---|---|---|---|
| 인증 | 차단 | `admin/jwt.guard.ts:26` — 컨트롤러 전체에 `AdminJwtAuthGuard` (`user-admin.controller.ts:21`) | HTTP 401 |
| 계정 종류 | 차단 | `roles.guard.ts:27` — `@Roles('admin','sales','corp')` (`user-admin.controller.ts:23`) | HTTP 403 접근 권한이 없습니다 |
| 쓰기 스코프 | 차단 | 해당 없음 — 이 컨트롤러에 `CorpReadonlyGuard` 가 걸려 있지 않다. 운영사 관리자는 걸렸어도 통과한다 | 해당 없음 |
| 기업 노출 등급 | 차단 | 해당 없음 — `CorpAnalysisViewGuard` 미적용 컨트롤러다 | 해당 없음 |
| 메뉴 노출 | 비차단 | `menu.repository.ts:32-39` — 회원 관리 메뉴 미배분이면 사이드바에서 사라진다 | 사이드바에서 사라짐. `@Roles` 셋 중 하나면 API 는 응답한다 |

**실패·이탈**

| 지점 | 조건 | 결과 |
|---|---|---|
| 목록 조회 | 세 role 밖 계정 | HTTP 403. 콘솔 진입 자체가 다른 라우터다 |
| 정지 저장 | 진행 중 분석·결제의 귀결 | 계속 이 문서가 판정하지 않았다. 코드 정독으로 확정하지 못한 자리다 |
| 해제 저장 | 정지 전 상태로 돌아오는가 | 계속 원상 복구 범위를 관측하지 못했다 |
| 상태 변경 | 변경 이력이 남는가 | 계속 감사 로그 어휘 3종으로 훑어 파일 0건이다. 부재 증명은 아니다 |

### admin-console-settlement-adjust

**전제**

- 통합 콘솔에 로그인되어 있다
- 대상 정산이 확정 상태이며 `admin-client-settlement-client-settlement` 또는 `admin-corp-subscription-corp-settlement` 목록에 있다
- 정산 주기의 정본은 [정산 주기](../05-schedules/02-billing-cycles.md)이며 이 흐름은 그 주기가 낸 결과를 사후에 손대는 절차다

**절차**

1. `admin-client-settlement-client-settlement` 에서 대상 정산 건을 연다. 기업 축은 `admin-corp-subscription-corp-settlement` 다.
2. 확정 취소를 수행한다. 정산 상태 개편과 확정 취소는 `89bc923a` 가 넣었다.
3. 값을 고쳐 다시 확정한다.

**누가 언제 무엇을 바꿨는지는 이 문서가 답하지 못한다.** `audit` · `admin_log` · `tnAdminLog` 세 어휘로 서버 `modules/` 전체를 훑어 파일 0건이다. 세 어휘 밖의 구현은 이 검색이 보지 못하므로 **부재 증명이 아니다.** 그 사실을 그대로 적는 것이 이 문단의 목적이다.

**권한**

| 층 | 차단성 | 이 흐름에서의 강제 지점 | 실패 시 |
|---|---|---|---|
| 인증 | 차단 | `admin/jwt.guard.ts:26` — `_aaT` 쿠키 | HTTP 401 |
| 계정 종류 | 차단 | `roles.guard.ts:27` — 정산 쓰기는 운영사 관리자 축이다 | HTTP 403 접근 권한이 없습니다 |
| 쓰기 스코프 | 차단 | `corp-readonly.guard.ts:24` — 비-GET 이므로 조직관리자였다면 여기서 잘린다. 운영사 관리자는 21행에서 먼저 통과한다 | HTTP 403 조직관리자는 허용된 작업만 수행할 수 있습니다 |
| 기업 노출 등급 | 차단 | 해당 없음 — `role` 이 `corp` 가 아니면 통과한다 (`corp-analysis-view.guard.ts:34`) | 해당 없음 |
| 메뉴 노출 | 비차단 | `menu.repository.ts:32-39` — 정산 메뉴 미배분이면 사이드바에서 사라진다 | 사이드바에서 사라짐. API 는 계정 종류만 맞으면 응답한다 |

**실패·이탈**

| 지점 | 조건 | 결과 |
|---|---|---|
| 확정 취소 | 조직관리자 계정 | HTTP 403 (`corp-readonly.guard.ts:33`) |
| 확정 취소 | 후속 정산 주기가 이미 지난 건 | 계속 이 문서가 판정하지 않았다 |
| 재확정 | 변경 이력 조회 | 계속 감사 로그 어휘 3종 검색에서 파일 0건. 이력의 소재가 이 문서에 없다 |

### admin-console-payment-reconcile

**전제**

- 통합 콘솔에 로그인되어 있다
- 결제는 승인됐고 그 뒤 주문·구독 반영이 어긋난 건이 있다
- 결제 축 화면은 `admin-money-payment` 이고 환불 축은 `admin-money-refund` 이며 주문 축은 `admin-subscription-order` 다

**절차**

1. `admin-money-payment` 에서 결제 승인 기록을 확인한다.
2. `admin-subscription-order` 에서 대응 주문의 상태를 확인한다.
3. 어긋난 건을 화면에서 바로잡는다.
4. 환불이 필요하면 `admin-money-refund` 로 이동한다.

**4단계에는 서버 경로가 없다.** `refund-admin.controller.ts` 는 파일 전체가 주석이다 — 2행의 `@Controller('admin/refund')` 부터 `@Post('delete')` 까지 전부 주석 처리돼 있다. 화면 라우트는 등록돼 있고 서버 엔드포인트는 열려 있지 않다. **이 비대칭이 개발자 개입이 필요한 지점이며 연락 경로는 이 문서가 알지 못한다** — 그 사실을 아래 표에 적는다.

**권한**

| 층 | 차단성 | 이 흐름에서의 강제 지점 | 실패 시 |
|---|---|---|---|
| 인증 | 차단 | `admin/jwt.guard.ts:26` — `_aaT` 쿠키 | HTTP 401 |
| 계정 종류 | 차단 | `roles.guard.ts:27` — 결제·주문 쓰기 경로는 운영사 관리자 축이다 | HTTP 403 접근 권한이 없습니다 |
| 쓰기 스코프 | 차단 | `corp-readonly.guard.ts:24` — 비-GET 이라 조직관리자면 잘린다 | HTTP 403 조직관리자는 허용된 작업만 수행할 수 있습니다 |
| 기업 노출 등급 | 차단 | 해당 없음 — `role` 이 `corp` 가 아니면 통과한다 | 해당 없음 |
| 메뉴 노출 | 비차단 | `menu.repository.ts:32-39` — 결제 메뉴 미배분이면 사이드바에서 사라진다 | 사이드바에서 사라짐. API 는 응답한다 |

**실패·이탈**

| 지점 | 조건 | 결과 |
|---|---|---|
| 주문 상태 확인 | 결제는 있고 주문이 없다 | 계속 화면에서 대응 건을 찾지 못한다 |
| 환불 실행 | 서버 엔드포인트가 주석 처리돼 있다 | 막다름. 화면에서 끝나지 않고 개발자 개입이 필요하다 (`refund-admin.controller.ts:2-40`) |
| 정정 저장 | 머니 잔액이 함께 움직이는 건 | `admin-console-concurrent-write` 의 잠금 관측이 그대로 적용된다 |

### admin-console-bulk-operation

**전제**

- 통합 콘솔에 로그인되어 있다
- 대량 조회 대상이 `admin-subscription-analysis` 목록 또는 `admin-statistics-analysis` 집계에 있다

**절차**

1. `admin-subscription-analysis` 에서 조건을 걸어 목록을 조회한다.
2. 엑셀 내보내기를 실행한다. 진료집계 축의 대응 엔드포인트는 `treat-monitor.controller.ts:52` 의 `GET admin/treat-monitor/daily-excel` 이고 `ef4908eb` 가 넣었다.
3. 집계 결과는 `admin-statistics-analysis` 에서 확인한다.

**부분 실패의 표시 지점을 이 문서가 확정하지 못했다.** 라우트와 컴포넌트 실존까지는 확인했고, 실패분만 골라 다시 돌리는 수단이 있는지는 코드 경로로 답하지 못했다. 진료 조회 축의 재처리는 별개 절차이며 [감시](./05-treat-monitoring.md)가 진다.

**권한**

| 층 | 차단성 | 이 흐름에서의 강제 지점 | 실패 시 |
|---|---|---|---|
| 인증 | 차단 | `admin/jwt.guard.ts:26` — 진료집계 컨트롤러 전체에 `AdminJwtAuthGuard` (`treat-monitor.controller.ts:9`) | HTTP 401 |
| 계정 종류 | 차단 | `roles.guard.ts:27` — `RolesGuard` 가 컨트롤러에 걸려 있다 (`treat-monitor.controller.ts:9`) | HTTP 403 접근 권한이 없습니다 |
| 쓰기 스코프 | 차단 | 해당 없음 — 이 흐름은 전 구간 GET 이고 `corp-readonly.guard.ts:24` 는 GET 을 항상 허용한다 | 해당 없음 |
| 기업 노출 등급 | 차단 | 해당 없음 — `role` 이 `corp` 가 아니면 통과한다 | 해당 없음 |
| 메뉴 노출 | 비차단 | `menu.repository.ts:32-39` — 통계 메뉴 미배분이면 사이드바에서 사라진다 | 사이드바에서 사라짐. API 는 응답한다 |

**실패·이탈**

| 지점 | 조건 | 결과 |
|---|---|---|
| 엑셀 내보내기 | 조회 범위가 커 타임아웃 | 계속 이 문서가 상한을 판정하지 않았다 |
| 엑셀 내보내기 | 일부 행만 실패 | 계속 실패분 확인 지점을 이 창에서 확정하지 못했다 |
| 집계 확인 | 집계 시점과 목록 시점이 다르다 | 계속 두 화면의 기준 시점 정합은 이 문서가 판정하지 않았다 |

### admin-console-data-correction

**전제**

- 통합 콘솔에 로그인되어 있다
- 고칠 대상이 기준 데이터다 — 코드 마스터는 `admin-search-codes` 이고 노출 기준은 `admin-site-criteria` 다

**절차**

1. `admin-search-codes` 에서 코드 값을 찾는다.
2. 값을 고쳐 저장한다.
3. 노출 기준을 함께 바꿔야 하면 `admin-site-criteria` 에서 이어서 고친다.

**수정이 사용자 노출값과 정산·통계에 각각 언제 반영되는지는 이 문서가 답하지 못한다.** 코드 경로를 끝까지 따라가지 못했고, 그것을 추측으로 메우지 않는다.

**권한**

| 층 | 차단성 | 이 흐름에서의 강제 지점 | 실패 시 |
|---|---|---|---|
| 인증 | 차단 | `admin/jwt.guard.ts:26` — `_aaT` 쿠키 | HTTP 401 |
| 계정 종류 | 차단 | `roles.guard.ts:27` — 기준 데이터 쓰기는 운영사 관리자 축이다 | HTTP 403 접근 권한이 없습니다 |
| 쓰기 스코프 | 차단 | `corp-readonly.guard.ts:24` — 비-GET 이라 조직관리자면 잘린다 | HTTP 403 조직관리자는 허용된 작업만 수행할 수 있습니다 |
| 기업 노출 등급 | 차단 | 해당 없음 — `role` 이 `corp` 가 아니면 통과한다 | 해당 없음 |
| 메뉴 노출 | 비차단 | `menu.repository.ts:32-39` — 기준 데이터 메뉴 미배분이면 사이드바에서 사라진다 | 사이드바에서 사라짐. API 는 응답한다 |

**실패·이탈**

| 지점 | 조건 | 결과 |
|---|---|---|
| 코드 저장 | 이미 참조 중인 코드 값 | 계속 참조 무결성 처리를 이 문서가 판정하지 않았다 |
| 저장 후 | 사용자 노출값 반영 시점 | 계속 코드 경로를 따라가지 못했다 |
| 저장 후 | 정산·통계 반영 시점 | 계속 같은 이유로 미상이다 |

### admin-console-concurrent-write

**전제**

- 두 관리자가 같은 건을 동시에 연다
- 대상은 머니 축이다 — 회원 머니는 `admin-money-general` 이고 기업 머니는 `admin-money-corp-money` 다

**절차**

1. 관리자 A 가 `admin-money-general` 에서 대상 건을 연다.
2. 관리자 B 가 같은 건을 연다.
3. A 가 저장한다.
4. B 가 저장한다.

**머니 잠금은 두 자리에만 있다.** `SELECT … FOR UPDATE` 는 서버 저장소 전체에서 2건이고 둘 다 `money.repository.ts` 다 — 회원 머니가 1859행, 대리점 머니가 2073행이다. 그 두 경로에서는 4단계가 A 의 트랜잭션을 기다린다.

**그 밖의 쓰기 경로에서는 잠금 관측이 없다.** 낙관적 잠금이나 버전 열은 `version` · `optimistic` · `forUpdate` · `FOR UPDATE` 네 검색어로 훑어 나오지 않았다. **네 어휘 밖의 구현은 이 검색이 보지 못하므로 부재 증명이 아니다.** 관측되지 않았다는 사실만 적는다.

**권한**

| 층 | 차단성 | 이 흐름에서의 강제 지점 | 실패 시 |
|---|---|---|---|
| 인증 | 차단 | `admin/jwt.guard.ts:26` — 두 관리자 모두 `_aaT` 쿠키를 갖는다 | HTTP 401 |
| 계정 종류 | 차단 | `roles.guard.ts:27` — 머니 쓰기는 운영사 관리자 축이다 | HTTP 403 접근 권한이 없습니다 |
| 쓰기 스코프 | 차단 | `corp-readonly.guard.ts:24` — 비-GET 이라 조직관리자면 잘린다 | HTTP 403 조직관리자는 허용된 작업만 수행할 수 있습니다 |
| 기업 노출 등급 | 차단 | 해당 없음 — `role` 이 `corp` 가 아니면 통과한다 | 해당 없음 |
| 메뉴 노출 | 비차단 | `menu.repository.ts:32-39` — 머니 메뉴 미배분이면 사이드바에서 사라진다 | 사이드바에서 사라짐. API 는 응답한다 |

**실패·이탈**

| 지점 | 조건 | 결과 |
|---|---|---|
| B 저장 | 대상이 회원 머니 잔액 | 계속 `money.repository.ts:1859` 의 행 잠금이 A 의 트랜잭션을 기다린다 |
| B 저장 | 대상이 대리점 머니 잔액 | 계속 `money.repository.ts:2073` 의 행 잠금이 같은 일을 한다 |
| B 저장 | 그 밖의 관리자 쓰기 경로 | 계속 잠금 관측이 없다. 충돌 통지 여부를 이 문서가 판정하지 않았다 |

## 연혁

릴리스 태그가 존재하지 않는다. 릴리스 열은 `—` 로 두고 커밋 SHA 로 추적한다. 절차의 단계·권한 층·이탈 경로가 바뀐 시점만 적는다. 화면 문구 변경은 연혁이 아니다.

| 릴리스 | 날짜 | SHA | 변경 요약 |
|---|---|---|---|
| — | 2026-07-16 | `89bc923a` | 기업주문 정산상태 개편과 정산확정 취소 — 정산 사후 정정 절차가 여기서 생겼다 |
| — | 2026-07-23 | `ef4908eb` | 진료집계 엑셀 다운로드 엔드포인트 — 대량 내보내기 절차의 서버 자리 |
| — | 2026-07-30 | `48e52b5d` | 로그인 경로 분리 — 통합 진입이 `/manage/login`, 기업·조직이 `/login` 으로 갈렸다 |
| — | 2026-07-31 | `d05f5d91` | 로그아웃 시 본인 포털 로그인 화면 복귀 — 세션 종료 이탈 대상이 바뀌었다 |
