---
template: flow-doc@1
doc_id: 03-sales-console
title: 영업대리점 콘솔 절차군
config: ../_meta/template-config.yaml
last_verified: fb001e1d
flows:
  - id: sales-console-session-entry
    actor: 영업대리점
    status: active-prod
    status_source: ./00-ia.md#sales-login
    evidence: [S-route, S-component, S-roles]
    limitations:
      - 절차의 단계 순서는 라우트 등록과 서버 데코레이터 정독에서 역산했다. 런타임 통과 기록은 대조하지 않았다
      - 대리점 계정에 어떤 메뉴가 배분되는지는 tn_menu_auth_map 행에 달려 있고 그 스냅샷은 이 창에 없다
    refs:
      - ./00-ia.md#sales-login
      - ./00-ia.md#sales-manage-login
      - ./00-ia.md#sales-root
      - ./00-ia.md#sales-403
    anchors:
      - insuniverse-admin/src/app/router/routes/page.routes.tsx:46
      - insuniverse-server/apps/api/src/modules/auth/admin/roles.guard.ts:27
      - insuniverse-server/apps/api/src/modules/menu/menu.repository.ts:32
      - insuniverse-server/apps/api/src/modules/auth/admin/jwt.guard.ts:47
      - insuniverse-server/apps/api/src/modules/auth/admin/jwt.guard.ts:42
      - insuniverse-server/apps/api/src/modules/auth/admin/jwt.guard.ts:33
      - insuniverse-server/apps/api/src/modules/auth/admin/jwt.guard.ts:26
      - 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: sales-console-agency-scope
    actor: 영업대리점
    status: active-prod
    status_source: ./00-ia.md#sales-sales-sales-agency
    evidence: [S-route, S-component, S-roles]
    limitations:
      - 대리점이 자기 채널 밖 데이터를 볼 수 없게 하는 스코프 검증의 소재를 이 창에서 전건 확정하지 못했다. 확정한 자리만 절차에 적었다
    refs:
      - ./00-ia.md#sales-sales-sales-agency
      - ./00-ia.md#sales-sales-sales-agency-channel
      - ./00-ia.md#sales-user-fc
    anchors:
      - insuniverse-admin/src/app/router/routes/domains/sales.routes.tsx:1
      - insuniverse-server/apps/api/src/modules/sales-agency/sales-agency-admin.controller.ts:1
---

# 영업대리점 콘솔 절차군

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

이 문서는 영업대리점(`admin.role === 'sales'`)이 대리점 콘솔에서 수행하는 절차군을 담는다. 화면 구조의 정본은 [화면 인벤토리](./00-ia.md)다.

**대리점 콘솔이 통합 콘솔과 다른 점은 인가 층의 형태다.** 기업 콘솔이 층을 **더** 받는 것과 달리(`쓰기 스코프` · `기업 노출 등급` 두 층이 `corp` 에서만 발화한다 — [기업 콘솔](./02-corp-console.md) 참조), 대리점은 그 두 층이 전부 `해당 없음` 이고 실질 차단이 `계정 종류` 한 층으로 좁아진다. `corp-readonly.guard.ts:21` 이 `corpScope !== 'ORG'` 를 통과시키고 `corp-analysis-view.guard.ts:34` 가 `role !== 'corp'` 를 통과시키기 때문이다.

**그 결과 대리점의 실질 경계는 인가가 아니라 조회 스코프다.** `@Roles('admin','sales','corp')` 가 붙은 핸들러에서 대리점은 통합 관리자와 같은 층을 지나며, 자기 채널 밖 데이터를 보지 못하게 하는 것은 그 아래 조회 조건이다. 그 조건의 소재를 이 창에서 전건 확정하지 못했고, 확정하지 못한 것을 확정한 것처럼 적지 않는다.

**총론이 아직 없다.** 역할 매트릭스의 정본은 `docs/02-access/` 소유이며 이 창에는 그 디렉터리가 없다. 부재 문서로 링크를 걸지 않고 경로만 산문으로 적는다.

**인증 층에는 이 문서가 검증하지 않은 우회 분기가 둘 있다.** `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 |
|---|---|---|---|
| sales-console-session-entry | 대리점 콘솔에 들어가 자기 권한 범위의 메뉴를 받는다 | 영업대리점 | active-prod |
| sales-console-agency-scope | 자기 대리점과 채널의 데이터를 조회한다 | 영업대리점 | active-prod |

## 흐름

### sales-console-session-entry

**전제**

- 영업대리점 계정을 보유하고 있다 (`admin.role === 'sales'`)
- 로그인 화면은 `sales-login` 이고 `sales-manage-login` 은 같은 라우터의 통합 진입 경로다
- 그 계정에 권한 그룹이 배정되어 있다

**절차**

1. `sales-login` 에서 자격 증명을 제출한다. 성공하면 `_aaT` 쿠키를 받는다.
2. 메뉴 트리를 받아 사이드바를 그린다. 배분이 0건이면 사이드바가 빈다.
3. `sales-root` 에서 각 도메인 화면으로 이동한다.
4. 계정 종류가 맞지 않는 요청은 서버가 403 을 내고 클라이언트가 `sales-403` 으로 보낸다.

**세 콘솔의 로그인 절차는 같은 서버 경로를 쓴다.** 갈리는 것은 착지 화면과 사이드바 구성이며, 인증 층 자체는 `admin/jwt.guard.ts:26` 하나로 동일하다.

**권한**

| 층 | 차단성 | 이 흐름에서의 강제 지점 | 실패 시 |
|---|---|---|---|
| 인증 | 차단 | `admin/jwt.guard.ts:26` — `_aaT` 쿠키를 검증한다 | HTTP 401 |
| 계정 종류 | 차단 | `roles.guard.ts:27` — `sales` 가 허용 목록에 있어야 한다 | HTTP 403 접근 권한이 없습니다 |
| 쓰기 스코프 | 차단 | 해당 없음 — `corpScope` 가 `ORG` 가 아니므로 21행에서 통과한다 (`corp-readonly.guard.ts:21`) | 해당 없음 |
| 기업 노출 등급 | 차단 | 해당 없음 — `role` 이 `corp` 가 아니므로 34행에서 통과한다 (`corp-analysis-view.guard.ts:34`) | 해당 없음 |
| 메뉴 노출 | 비차단 | `menu.repository.ts:32-39` — 배분된 메뉴만 사이드바에 실린다 | 사이드바에서 사라짐. URL 을 직접 치면 위 네 층만 통과하면 열린다 |

**실패·이탈**

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

### sales-console-agency-scope

**전제**

- 대리점 콘솔에 로그인되어 있다 (`sales-console-session-entry` 완료)
- 조회 대상이 자기 대리점(`sales-sales-sales-agency`) 또는 그 채널(`sales-sales-sales-agency-channel`)이다
- 소속 설계사 목록은 `sales-user-fc` 다

**절차**

1. `sales-sales-sales-agency` 에서 대리점 정보를 확인한다.
2. `sales-sales-sales-agency-channel` 에서 채널 목록을 본다.
3. `sales-user-fc` 에서 소속 설계사를 조회한다.

**이 절차의 경계는 인가 층이 아니다.** 위 권한 표대로 대리점은 `계정 종류` 한 층만 지나며, 자기 대리점 밖 데이터를 보지 못하게 하는 것은 조회 조건이다. **그 조건이 모든 조회 경로에 붙어 있는지를 이 문서가 확정하지 못했다** — 확정한 것은 소유자 검증이 분석·청구서 조회에 `47d90c30` 로 들어갔다는 사실 하나이고, 대리점 축 전 경로의 스코프 검증은 이 창의 정독으로 전수를 세지 못했다.

**권한**

| 층 | 차단성 | 이 흐름에서의 강제 지점 | 실패 시 |
|---|---|---|---|
| 인증 | 차단 | `admin/jwt.guard.ts:26` — `_aaT` 쿠키를 검증한다 | HTTP 401 |
| 계정 종류 | 차단 | `roles.guard.ts:27` — `@Roles` 에 `sales` 가 있어야 한다 | HTTP 403 접근 권한이 없습니다 |
| 쓰기 스코프 | 차단 | 해당 없음 — `corpScope` 가 `ORG` 가 아니다 (`corp-readonly.guard.ts:21`) | 해당 없음 |
| 기업 노출 등급 | 차단 | 해당 없음 — `role` 이 `corp` 가 아니다 (`corp-analysis-view.guard.ts:34`) | 해당 없음 |
| 메뉴 노출 | 비차단 | `menu.repository.ts:32-39` — 대리점 메뉴 미배분이면 사이드바에서 사라진다 | 사이드바에서 사라짐. API 는 `@Roles` 만 맞으면 응답한다 |

**실패·이탈**

| 지점 | 조건 | 결과 |
|---|---|---|
| 대리점 조회 | `@Roles` 에 `sales` 가 없는 핸들러 | HTTP 403 접근 권한이 없습니다 |
| 채널 조회 | 자기 대리점 밖 식별자를 직접 넣는다 | 계속 스코프 검증의 전수를 이 문서가 확정하지 못했다 |
| 설계사 조회 | 소유자 검증이 붙은 경로 | 막다름. 소유자 검증은 `47d90c30` 가 넣었다 |

## 연혁

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

| 릴리스 | 날짜 | SHA | 변경 요약 |
|---|---|---|---|
| — | 2026-07-06 | `397f7453` | 대리점 청구·머니 제어 갱신 — 대리점 축 절차의 서버 자리가 바뀌었다 |
| — | 2026-07-28 | `47d90c30` | 분석·청구서 조회에 소유자 검증 추가 — 조회 이탈 경로가 하나 늘었다 |
| — | 2026-07-30 | `48e52b5d` | 로그인 경로 분리 — 통합 진입과 기업·조직 진입이 갈렸다 |
| — | 2026-07-31 | `d05f5d91` | 로그아웃 시 본인 포털 로그인 화면 복귀 — 세션 종료 이탈 대상이 바뀌었다 |
