이호영 이호영 07-27
docs: README를 현재 시스템 기준으로 다시 씀
기능이 대폭 늘었는데 README는 기관 명부·채널 생성까지만 설명하고 있어 실제와 어긋났다.
대시보드·사업관리·시스템관리·권리확인/처리·도움말까지 반영하고, 읽는 순서대로 다시 배치한다.

- 화면 구성, 12단계 흐름, 최초 사용 순서를 표와 도식으로 정리
- 환경변수를 필수 8개와 기본값 있는 선택 2개로 나눠 표기
- 수정할 때 깨뜨리면 안 되는 약속(채널 2종, 재실행 안전, 채널⇔단계 불변식,
  엑셀 재업로드 병합 규칙, 제3자 권리처리 정책, 연번 문자열)을 한 표로 모음
- 패키지·화면 구조와 API 목록, 배포(deploy/README.md 연결) 추가

Yona 렌더러에서 깨지지 않도록 GitHub 전용 문법(경고 블록, HTML 표) 대신
표준 마크다운만 쓴다.

Co-Authored-By: Claude Opus 5 (1M context) 
@41ea29b1bcf6e196454294f1b06909b6553f533b
README.md
--- README.md
+++ README.md
@@ -1,66 +1,91 @@
 # ITN-HUB
 
-47개 신유형 사업 신청기관의 명부·담당자 정보를 관리하고, 기관별 Mattermost 채널(문정원/법률검토)
-생성을 자동화하는 웹 애플리케이션이다. 기존 Excel VBA 매크로(`M06_Mattermost_기관목록`,
-`M07_Mattermost_채널일괄생성`) 워크플로를 웹으로 옮긴 후속 단계로, 신청목록 시트 업로드로
-기관 정보를 시드하고, 부서/담당자 정보를 입력한 뒤, 기관당 Mattermost 비공개 채널 2개
-(`문정원`, `법률검토`)를 생성·복구한다.
+> **2026 신유형 공공저작물 개방사업 · 3단계 법률검토 관리 시스템**
+>
+> 신청기관 명부·담당자 관리부터 Mattermost 채널 자동 생성, 권리확인·권리처리 판정,
+> 사업 현황 대시보드까지 한 곳에서 처리한다.
 
-## ⚠️ 실행 전 반드시 읽을 것
+`Spring Boot 3.3.5` `Java 21` `React 18` `PostgreSQL 16` `MyBatis` `Flyway` `Mattermost`
 
-- **이 애플리케이션은 실제 운영 Mattermost 서버(`https://hub.iten.co.kr`)에 실제 채널을 생성한다.**
-  테스트 목적으로 함부로 실행하면 운영 팀에 실제 채널이 생긴다.
-- Mattermost `MATTERMOST_TOKEN`(Personal Access Token)은 **시스템 관리자(system-admin) 권한**을
-  가진 계정의 토큰이어야 한다. 비공개(private) 채널을 팀 전체 범위에서 조회(레거시 채널
-  복구용 표시명 검색, `GET /api/v4/teams/{teamId}/channels`)하려면 이 권한이 필요하다.
-  일반 권한 토큰을 쓰면 레거시 채널을 못 찾아 매번 중복 채널이 생성될 수 있다.
-- **최초 사용 전에는 실제 신청기관이 아니라 테스트용 기관(예: 연번 `999`, 임시 기관명)으로
-  먼저 스모크 테스트를 해볼 것을 권장한다.** 시드 업로드 → 담당자 입력 → 채널 생성까지
-  한 번 돌려보고, Mattermost 서버에서 실제로 의도한 이름의 비공개 채널 2개가 만들어지는지
-  확인한 뒤 실제 기관 데이터를 올린다.
+---
 
-## 환경변수
+## 목차
 
-`.env.example`을 참고해 실제 값을 채운 `.env`(또는 배포 환경의 환경변수)를 준비한다.
-**`.env`나 실제 값이 든 파일은 절대 커밋하지 않는다.**
+- [무엇을 하는 시스템인가](#무엇을-하는-시스템인가)
+- [화면 구성](#화면-구성)
+- [시작하기](#시작하기)
+- [환경변수](#환경변수)
+- [업무 흐름](#업무-흐름)
+- [핵심 규칙](#핵심-규칙)
+- [프로젝트 구조](#프로젝트-구조)
+- [API](#api)
+- [테스트](#테스트)
+- [배포](#배포)
 
-| 변수 | 설명 | 예시 값 |
-|---|---|---|
-| `DB_URL` | PostgreSQL 16 JDBC URL | `jdbc:postgresql://localhost:5432/itnhub` |
-| `DB_USERNAME` | DB 접속 계정 | `itnhub_app` |
-| `DB_PASSWORD` | DB 접속 비밀번호 | `changeme-please` |
-| `MATTERMOST_URL` | Mattermost 서버 베이스 URL | `https://hub.iten.co.kr` |
-| `MATTERMOST_TOKEN` | system-admin 권한 Personal Access Token | `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` |
-| `MATTERMOST_TEAM_ID` | 채널을 생성할 Mattermost 팀 ID | `abcdefghijklmnopqrstuvwxyz` |
-| `APP_ADMIN_USERNAME` | 앱 로그인 관리자 계정 | `admin` |
-| `APP_ADMIN_PASSWORD` | 앱 로그인 관리자 비밀번호(충분히 긴 무작위 문자열 권장) | `use-a-long-random-value` |
+---
 
-이 8개는 전부 `${...}` 플레이스홀더로 `application.yml`에 선언되어 있어 값이 없으면
-애플리케이션이 기동하지 않는다.
+## 무엇을 하는 시스템인가
 
-### 왜 `spring.datasource.hikari.schema: itnhub`가 있는가
+기존 Excel VBA 매크로(`M06_Mattermost_기관목록`, `M07_Mattermost_채널일괄생성`)로 하던 작업을
+웹으로 옮긴 것이다. 47개 신청기관을 대상으로 다음 흐름을 처리한다.
 
-`application.yml`의 `spring.datasource.hikari.schema: itnhub` 설정은 지우면 안 된다.
-`spring.flyway.schemas`는 Flyway 전용 커넥션에만 적용되는 설정이라, 애플리케이션이 실제
-쿼리를 실행하는 HikariCP 커넥션 풀은 이 설정이 없으면 기본 `search_path`(보통 `public`)를
-써서 `relation "organization" does not exist` 오류가 난다. Flyway가 만드는 스키마와
-런타임 커넥션이 바라보는 스키마를 반드시 일치시켜야 한다.
+```
+ 신청목록 엑셀        담당자            채널 2종         권리확인 / 권리처리       보고서
+    업로드      →     배정      →     자동 생성    →      변호사 판정       →     제출
+   기관 명부      신청기관·문정원     문정원·법률검토      엑셀 업로드 후          사업관리
+                 아이티앤·변호사     + 안내글 핀 고정        건별 판정
+```
 
-## 빌드
+> **⚠ 주의**
+>
+> 이 애플리케이션은 **실제 운영 Mattermost 서버(`https://hub.iten.co.kr`)에 실제 채널을 만든다.**
+> 처음 쓸 때는 실제 기관이 아니라 테스트 기관(예: 연번 `999`)으로 한 바퀴 돌려보고 시작할 것.
+>
+> `MATTERMOST_TOKEN`은 **시스템 관리자(system-admin) 권한** 계정의 Personal Access Token이어야 한다.
+> 팀 전체 범위에서 비공개 채널을 조회해야(레거시 채널 복구) 중복 생성을 막을 수 있기 때문이다.
+
+---
+
+## 화면 구성
+
+| 메뉴 | 하는 일 |
+| :-- | :-- |
+| **Dashboard** | KPI 7타일 · 단계별 칸반(카드 드래그로 단계 이동) · 최근 진행 기관 |
+| **채널관리** | 신청목록 시트 업로드 · 담당자 4역할 배정 · Mattermost 채널 2종 생성 |
+| **기관관리** | 기관 상세. 업무단계 스트립 + 7개 탭(채팅·자료·권리확인·권리처리·RE·타임라인·업무메모) |
+| **담당자관리** | 담당자 등록·수정·삭제, 회원명단 엑셀 일괄 업로드 |
+| **사업관리** | 3단계 대상 기관 현황 · 신청 확인 연락 이력 · 기관별 최종 보고서 |
+| **시스템관리** | 채널 초기화(미리보기 → 확인문구 → 실행) |
+
+화면 곳곳의 버튼 옆 **`?` 아이콘**을 누르면 실제 화면 캡처가 담긴 설명서가 열린다(19개 항목).
+
+---
+
+## 시작하기
+
+### 요구사항
+
+| 항목 | 버전 / 비고 |
+| :-- | :-- |
+| JDK | **21** |
+| PostgreSQL | **16** |
+| Node.js | 불필요 — `mvn package`가 v20.17.0을 내려받아 프런트를 함께 빌드한다 |
+| Docker | 백엔드 테스트에만 필요(Testcontainers) |
+
+### 빌드
 
 ```bash
 mvn -DskipTests package
 ```
 
-`pom.xml`의 `frontend-maven-plugin`이 `mvn package` 과정에서 `frontend/`의 React 앱을
-함께 빌드해 `src/main/resources/static/`에 넣고, 그 결과를 Spring Boot의 단일 jar에
-패키징한다. 별도로 프론트엔드를 먼저 빌드할 필요는 없다.
+`frontend-maven-plugin`이 React 앱을 빌드해 `src/main/resources/static/`에 넣고,
+그 결과가 **단일 jar 하나**로 패키징된다. 프런트를 따로 빌드할 필요가 없다.
 
-## 실행
+### 실행
 
 ```bash
 export DB_URL=jdbc:postgresql://localhost:5432/itnhub
-export DB_USERNAME=itnhub_app
+export DB_USERNAME=itnhub
 export DB_PASSWORD=...
 export MATTERMOST_URL=https://hub.iten.co.kr
 export MATTERMOST_TOKEN=...
@@ -68,30 +93,169 @@
 export APP_ADMIN_USERNAME=admin
 export APP_ADMIN_PASSWORD=...
 
-java -jar target/itnhub-0.1.0.jar
+java -jar target/itnhub-0.1.0.jar --server.port=8091
 ```
 
-## 최초 사용 순서
+기동하면 Flyway가 `itnhub` 스키마와 테이블(V1~V12)을 **자동으로 만든다.** 수동 DDL은 없다.
 
-1. PostgreSQL 16 인스턴스를 준비하고 위 환경변수로 접속 정보를 지정한다. 기동 시 Flyway가
-   `itnhub` 스키마와 테이블을 자동으로 만든다(수동 DDL 불필요).
-2. 브라우저로 접속해 `APP_ADMIN_USERNAME` / `APP_ADMIN_PASSWORD`로 로그인한다.
-3. 좌측 하단 "신청목록 시트 올리기"로 기관 신청목록 엑셀(`.xlsx`/`.xlsm`)을 업로드해
-   기관 명부를 시드한다.
-4. 기관을 선택해 부서명/담당자명/연락처/이메일(직급/직함은 선택)을 입력하고 저장한다.
-5. "채널 생성"을 눌러 Mattermost에 문정원/법률검토 채널 2개를 생성한다. 이미 생성된
-   채널은 재실행해도 다시 만들지 않는다(idempotent) - 실패한 채널만 다시 시도된다.
-
-## 개발 워크플로
+### 프런트만 반복 개발할 때
 
 ```bash
-cd frontend
-npm install
-npm run dev
+cd frontend && npm install && npm run dev   # Vite가 /api 를 :8080 으로 프록시
+mvn spring-boot:run                          # 백엔드는 따로 띄워 둔다
 ```
 
-Vite 개발 서버가 `/api` 요청을 `:8080`(백엔드)으로 프록시한다. 백엔드는 별도로
-`mvn spring-boot:run` 등으로 띄워둔 상태에서 프론트엔드만 `npm run dev`로 반복 개발하면 된다.
+---
 
-프론트엔드 테스트: `cd frontend && npm test`
-백엔드 테스트(Testcontainers로 PostgreSQL을 띄우므로 Docker 필요): `mvn test`
+## 환경변수
+
+`.env.example`을 복사해 값을 채운다. **실제 값이 든 파일은 커밋하지 않는다.**
+
+### 필수 — 하나라도 비면 기동하지 않는다
+
+| 변수 | 설명 | 예시 |
+| :-- | :-- | :-- |
+| `DB_URL` | PostgreSQL JDBC URL | `jdbc:postgresql://192.168.0.60:5432/itnhub` |
+| `DB_USERNAME` | DB 계정 | `itnhub` |
+| `DB_PASSWORD` | DB 비밀번호 | |
+| `MATTERMOST_URL` | Mattermost 베이스 URL | `https://hub.iten.co.kr` |
+| `MATTERMOST_TOKEN` | **system-admin 권한** Personal Access Token | |
+| `MATTERMOST_TEAM_ID` | 채널을 만들 팀 ID | |
+| `APP_ADMIN_USERNAME` | 앱 로그인 계정 | `admin` |
+| `APP_ADMIN_PASSWORD` | 앱 로그인 비밀번호(충분히 긴 무작위 값) | |
+
+### 선택 — 기본값 있음
+
+| 변수 | 기본값 | 설명 |
+| :-- | :-- | :-- |
+| `MATTERMOST_TEAM_NAME` | `itn-hub` | 채널 URL을 만들 때 쓰는 팀 이름 |
+| `MATTERMOST_DEFAULT_USER_PASSWORD` | `test1234!` | 자동 생성되는 담당자 계정의 초기 비밀번호 |
+
+> **참고 —** `application.yml`의 `spring.datasource.hikari.schema: itnhub` 는 **지우면 안 된다.**
+> `spring.flyway.schemas`는 Flyway 전용 커넥션에만 적용돼서, 이 설정이 없으면 런타임 커넥션이
+> `public` 스키마를 보고 `relation "organization" does not exist` 로 죽는다.
+
+---
+
+## 업무 흐름
+
+### 진행단계 12단계
+
+```
+ ① 신청  →  ② 목록접수  →  ③ 예비검토  →  ④ 변호사 배당
+       →  ⑤ 법률검토(권리확인)  →  ⑥ RE:확인  →  ⑦ 법률검토(권리확인) 완료
+       →  ⑧ 법률검토(권리처리)  →  ⑨ RE:처리  →  ⑩ 법률검토(권리처리) 완료
+       →  ⑪ 법률검토 최종완료   →  ⑫ 보고서 작성
+```
+
+- 채널을 만들면 **①신청이 자동으로 시작**된다. 그 뒤로는 **전부 수동**이다 — 자료를 올리거나
+  게시글을 써도 단계가 저절로 넘어가지 않는다(요청자 확정 사항).
+- 단계 변경 이력은 전부 **타임라인 탭**에 남는다.
+
+### 최초 사용 순서
+
+1. PostgreSQL을 준비하고 환경변수를 채운다 → 기동하면 스키마가 만들어진다
+2. 브라우저로 접속해 관리자 계정으로 로그인
+3. **담당자관리** 에서 담당자를 등록한다(회원명단 엑셀 일괄 업로드 가능)
+4. **채널관리** 에서 신청목록 시트를 올려 기관 명부를 만들고, 기관마다 담당자 4역할을 배정한다
+5. **채널 생성** — 채널 2종 + 담당자 계정 + 안내글이 한 번에 준비된다
+6. **기관관리 › 권리확인/권리처리** 탭에서 조사 엑셀을 올리고 건별로 판정한다
+7. **사업관리** 에서 연락 이력을 남기고 최종 보고서를 올린다
+
+---
+
+## 핵심 규칙
+
+수정할 때 깨뜨리면 안 되는 약속들이다.
+
+| 규칙 | 내용 |
+| :-- | :-- |
+| **채널은 항상 2개** | 기관마다 `NNN_기관명 (문정원)` · `NNN_기관명 (법률검토)` 가 쌍으로 만들어진다. 게시글·자료도 늘 양쪽에 함께 반영된다. 한쪽만 처리하는 코드는 버그다. |
+| **재실행 안전** | 채널 생성·시드 업로드·채널 초기화 모두 여러 번 실행해도 안전하다. 이미 처리된 것은 건너뛰고 없는 것만 만든다. 새 로직에서도 이 원칙을 지킨다. |
+| **채널 ⇔ 단계** | 채널이 있으면 진행단계가 반드시 있고, 단계가 없으면 채널도 없다(V11이 이 불변식을 맞춘다). 대시보드 칸반의 **단계 미지정** 컬럼은 **채널 미생성 기관만** 담는다. |
+| **엑셀 재업로드** | 같은 파일을 다시 올려도 **웹에서 고친 값(`web_edited_at`)은 보존**되고 조사원 영역만 덮어쓴다. 병합 기준은 순번이 아니라 **파일의 행 순서**다(원본에 순번 중복이 있다). |
+| **제3자 권리처리** | 권리 일부 보유·권리 미보유는 `권리처리 추진` / `권리처리 추진 미희망` 택일, 초상권 포함은 `개방불가`까지 3택. 추진이면 공공누리 유형이 `보류`로, 미희망이면 기존 유형이 자동 선택되고 비고에 사유가 적힌다. |
+| **작성자 자동 기록** | 업무메모·연락 이력의 작성자는 화면에서 고르지 않는다. 로그인 사용자를 서버가 기록하고, 수정해도 최초 작성자·작성시각은 유지된다. |
+| **연번은 문자열** | 기관 코드는 `연번1_연번2`(`001_00`) 형식이다. 숫자로 바꾸면 앞자리 0이 사라져 매칭이 전부 깨진다. |
+
+---
+
+## 프로젝트 구조
+
+```
+itnhub/
+├─ src/main/java/kr/itn/itnhub/
+│  ├─ org/          기관 명부·배정              ├─ review/     권리확인
+│  ├─ contact/      담당자                      ├─ process/    권리처리
+│  ├─ provision/    채널 생성(멱등)             ├─ stage/      진행단계·이력
+│  ├─ mattermost/   Mattermost API 클라이언트   ├─ timeline/   타임라인 집계
+│  ├─ feed/         채널 게시글·파일            ├─ memo/       업무메모
+│  ├─ dashboard/    대시보드 집계               ├─ project/    사업관리
+│  ├─ seed/         신청목록 시트 시드          └─ system/     채널 초기화
+│  └─ config/       보안·설정
+│
+├─ src/main/resources/
+│  ├─ db/migration/     Flyway V1~V12
+│  ├─ mapper/           MyBatis XML
+│  └─ application.yml
+│
+├─ frontend/src/
+│  ├─ components/       화면 19개 (Dashboard, OrgOverview, ReviewBoard …)
+│  ├─ help/             버튼 옆 「?」 도움말 — 본문과 캡처가 여기 한 곳에 있다
+│  ├─ api/client.ts     모든 서버 호출이 지나는 단일 창구
+│  └─ stages.ts         12단계 라벨 정본
+│
+├─ deploy/               176 서버 배포 정의 (compose · env 템플릿 · 절차서)
+└─ Dockerfile            프런트까지 함께 빌드하는 멀티스테이지
+```
+
+---
+
+## API
+
+| 영역 | 엔드포인트 |
+| :-- | :-- |
+| 기관 | `GET /api/orgs` · `PUT /api/orgs/{id}/assignments` · `POST /api/orgs/{id}/channels` |
+| 진행단계 | `PUT /api/orgs/{id}/stage` · `GET /api/orgs/{id}/stage-history` |
+| 채팅·자료 | `/api/orgs/{id}/posts` `messages` `notice` `files` · `GET /api/files/{fileId}` |
+| 권리확인 | `/api/orgs/{id}/review` `import` `download` · `PUT …/review/bulk` |
+| 권리처리 | `/api/orgs/{id}/process` `import` `download` · `PUT …/process/bulk` |
+| 대시보드 | `GET /api/dashboard` — KPI·칸반·최근 진행 기관을 한 번에 |
+| 사업관리 | `GET /api/projects` · `/api/orgs/{id}/contact-logs` · `/api/orgs/{id}/reports` |
+| 업무메모 | `GET` `POST` `PUT` `DELETE /api/orgs/{id}/memos` |
+| 타임라인 | `GET /api/orgs/{id}/timeline` |
+| 시스템 | `GET /api/system/reset/preview` · `POST /api/system/reset/channels` |
+
+전 구간 로그인이 필요하고(`/api/auth/login` 제외), 변경 요청은 CSRF 토큰을 함께 보낸다.
+
+---
+
+## 테스트
+
+```bash
+mvn test                    # 백엔드 221건 — Testcontainers로 PostgreSQL을 띄우므로 Docker 필요
+cd frontend && npm test     # 프런트 191건
+```
+
+---
+
+## 배포
+
+Docker 기준 상세 절차는 **[`deploy/README.md`](deploy/README.md)** 에 있다.
+
+```bash
+docker build -t itnhub:latest .
+sudo cp deploy/itnhub.env.example /etc/itnhub/itnhub.env   # 값 채우기
+cd /opt/itnhub && sudo docker compose up -d
+```
+
+| | |
+| :-- | :-- |
+| 산출물 | **단일 jar** — React가 그 안에 있어 서버에 Node도 웹서버도 필요 없다 |
+| 영속 볼륨 | **불필요** — 업로드 파일은 DB(`org_report`)와 Mattermost에 저장된다 |
+| DB | 컨테이너로 띄우지 않고 기존 PostgreSQL을 그대로 쓴다 |
+| 시간대 | 컨테이너를 `Asia/Seoul`로 고정 — UTC면 도달 일시·D+n이 9시간 어긋난다 |
+| 헬스체크 | `GET /login` 200 (actuator 미포함) |
+
+> **⚠ 빌드 머신에는 인터넷이 필요하다**(Node·npm·Maven을 내려받는다). 실행 서버는 필요 없다.
+> 사내망이 막혀 있으면 Maven 미러와 `nodeDownloadRoot`를 사내 저장소로 돌려야 한다.
Add a comment
List