대시보드는 단계를 안 바꾸고는 코드표에 연결할 수 없는 구조였다. 칸반 컬럼 수가 단계
배열 길이에서 나오고 완료 판정이 11로 박혀 있었는데 코드표는 13을 말하기 때문이다.
프론트만 바꾸면 화면과 서버 숫자가 어긋나므로 백엔드까지 한 번에 옮긴다.
[11] 칸반 컬럼이 단계 미지정 + 13개가 된다. 단계 상한도 코드표 개수를 따른다.
[2] 완료 = ⑬보고서 제출 완료. 보고서가 실제로 올라온 기관만 센다.
[7] 진행상태에 '신청'이 생긴다. 신청은 ①만, 완료는 ⑬만, 나머지가 진행중이다.
채널이 없어 단계가 아예 없는 기관은 '단계 미지정'으로 그대로 둔다.
원문자 배열(①~⑫)과 구간 색 맵을 지웠다. 원문자는 번호에서 계산하고 색은 코드표의
tone에서 온다 - 단계가 늘 때마다 같이 늘려야 했던 자리다.
stages.ts를 지웠다. 남아 있던 소비자(기관상세·타임라인·시스템관리)도 모두 옮겼다.
동작이 바뀐 테스트 3건은 새 기준으로 다시 썼다(11단계 완료 집계, 13단계 거부,
마지막 단계 12). 도움말 문구도 13단계 기준으로 고쳤다.
백엔드 263건, 프론트 208건 전부 통과.
Co-Authored-By: Claude Opus 5 (1M context)
담당자관리는 구분 5종이 한 파일 안에서만 네 번 따로 적혀 있었다(표시명·뱃지색·필터칩·
드롭다운). 전부 코드표에서 가져오게 바꿨다.
사업관리의 단계 필터는 Array.from({length: 12})로 12가 박혀 있어서, 13단계를 늘려도
이 필터만 12개로 남을 자리였다. 연락 방법 4종도 코드표로 옮겼다.
동작은 그대로다. 프론트 208건 전부 통과.
Co-Authored-By: Claude Opus 5 (1M context)
로그인 직후 /api/meta/codes를 한 번 불러 앱 전체가 나눠 쓴다. 화면마다 따로 부르면
선택지를 기다리느라 폼이 깜빡인다.
선택지를 못 받으면 화면을 아예 안 그리고 오류를 보여준다. 빈 드롭다운을 그려두면
거기서 저장할 때 값이 지워지기 때문이다.
색은 DB에 tone(의미 토큰)만 두고 Tailwind 클래스는 프론트가 붙인다. 조립식 클래스명은
빌드 시점에 지워지므로 맵을 풀어 썼다.
useStages()가 단계 수·라벨·원문자·구간·진행상태·KPI 임계값을 코드표에서 가져온다.
원문자는 저장하지 않고 번호에서 계산한다.
테스트는 CodeProvider에 codes를 직접 넘겨 서버 없이 돈다. 픽스처는 시드와 같은 값이라
화면 테스트가 실제 선택지로 검증된다.
아직 각 화면은 기존 하드코딩 배열을 쓴다. 화면 전환은 다음 커밋부터.
Co-Authored-By: Claude Opus 5 (1M context)
시드를 쓸 때 화면 소스를 안 보고 의미만으로 색을 골랐던 탓에 두 가지가 어긋나 있었다.
Phase 2에서 화면이 코드테이블을 보게 되면 조용히 바뀔 값들이다.
업무구간 색 assign violet->indigo, check sky->emerald, closing emerald->violet
담당자 구분 MJ 표시명 주관기관->문정원 (주관기관은 엑셀 표기라 importAlias로)
뱃지색 5개 전부 화면 기준으로 교정
이미 적용된 V13을 고치면 체크섬이 어긋나 기동이 실패하므로 V15로 앞으로 나아가며 고친다.
어긋남이 다시 생기지 않도록 화면 기준값을 테스트로 잠갔다.
STAGE_GROUP의 cardBody 속성은 뺐다. 칸반 카드 본문은 구간이 아니라 단계 번호로 갈리는
자리가 있어(1단계는 신청일, 2·3단계는 목록 건수) 화면 로직으로 두는 편이 정확하다.
Co-Authored-By: Claude Opus 5 (1M context)
배포 전 정합성 점검을 실제 DB에 돌려보니 코드테이블에 없는 값이 1,926건 있었다.
review_item.kogl_type = 유형 확인 필요 1,858건
review_item.kogl_type = 제4유형 68건
둘 다 기관 조사표에서 온 값이라 prior(기존 부착 유형) 자리에만 넣고, 변호사가 고르는
판정값(review/process)에는 넣지 않는다. surveyOnly 속성으로 구분하고 그 구분이 새는지를
테스트로 막는다.
등록하지 않았다면 Phase 2에서 이 1,926건이 상세 화면에 선택 안 됨으로 보였을 것이다.
Co-Authored-By: Claude Opus 5 (1M context)
운영 DB에는 정책 변경 전 값이나 공백만 다른 값이 남아 있을 수 있다. Phase 2에서
화면이 코드테이블만 보게 되면 그런 값은 선택지에서 조용히 사라지므로, 배포 전에
찾아내 active=false 코드로 보존해야 한다.
Phase 1 회귀 확인: 백엔드 255건(기존 221 + 신규 34), 프론트 191건 전부 통과.
기존 동작은 바뀌지 않았다.
Co-Authored-By: Claude Opus 5 (1M context)
CodeService가 기동 후 첫 조회에 코드를 한 번 읽어 메모리에 들고 있는다. 지연 로딩인
이유는 Flyway 마이그레이션보다 빈 초기화가 먼저 돌 수 있기 때문이다.
활성 목록과 전체 목록을 나눠 들고 있는다. 화면 선택지에는 활성만 나가지만, 이미
저장된 과거 값의 라벨을 그리려면 비활성도 조회돼야 한다.
GET /api/meta/codes는 아직 소비자가 없다. 화면 전환은 Phase 2다.
Co-Authored-By: Claude Opus 5 (1M context)
진행단계는 요구사항 [7][11]대로 13단계로 넣고, 진행상태(신청/진행중/완료)와
KPI 임계값을 attrs로 데이터화했다. 요구사항 [2](보고서가 올라와야 최종완료)는
completed 임계값을 13번에 두는 것으로 해결된다.
공공누리 유형은 scopes로 사용처(review/process/prior)를 구분한다. 세 화면의
목록이 7/5/6개로 달랐던 것이 실수가 아니라 자리 차이였기 때문이고, 이 구분 없이
합치면 권리처리 화면에 보류가 잘못 노출된다.
Co-Authored-By: Claude Opus 5 (1M context)
코드값을 소스가 아니라 데이터로 관리하기 위한 기반. 저장값은 지금 업무 테이블에
들어 있는 한글 문자열을 그대로 쓴다 - 영문 키로 바꾸면 기존 데이터 변환과 엑셀
양식 수정이 딸려와 위험 대비 실익이 없다.
설계서와 Phase 1 구현계획도 함께 넣는다.
Co-Authored-By: Claude Opus 5 (1M context)
서버에서 itnhub.env를 Windows에서 만들어 올리면 source 할 때 줄 끝 캐리지 리턴이
값에 그대로 붙는다. DB 비밀번호나 SERVER_PORT가 조용히 틀어지고 빈 줄에서는
command not found 가 뜬다. 캐리지 리턴을 걷어낸 사본을 만들어 읽도록 바꾼다.
원본 파일은 그대로 둔다.
alpine에서 bash -n, shellcheck, CRLF env 주입까지 확인했다.
Co-Authored-By: Claude Opus 5 (1M context)
Windows에서 작업하고 리눅스 서버에서 실행하는 파일인데, git이 체크아웃할 때 CRLF로
바꿔놓아 bash가 syntax error로 죽었다(shellcheck SC1017). .gitattributes로 *.sh 와
Dockerfile, compose 파일의 줄바꿈을 LF로 고정하고 기존 파일을 정규화한다.
ps1은 Windows 전용이라 CRLF로 둔다.
start.sh는 포트를 파일 위쪽 설정 블록의 APP_PORT=8877 로 끌어올려, 기동과 확인이
같은 값을 쓰도록 정리했다. env 파일에 SERVER_PORT가 있으면 그 값이 우선한다.
alpine 컨테이너에서 bash -n 과 shellcheck로 확인했다.
Co-Authored-By: Claude Opus 5 (1M context)
application.yml에 server.port가 없어 기본 8080으로 뜨고, 실행 스크립트마다 --server.port를
따로 넘기고 있어서 환경별로 포트가 달랐다. 앱 자체 기본값을 8877로 두어 어디서 띄우든
같은 포트가 되게 한다. SERVER_PORT 환경변수로는 여전히 덮어쓸 수 있다.
함께 맞춘 곳:
- Dockerfile EXPOSE / HEALTHCHECK, docker-compose 포트 매핑
- deploy/start.sh 기본값, itnhub.env.example (SERVER_PORT·TZ 항목 추가)
- vite 개발 프록시 대상(:8080 -> :8877). 이걸 안 바꾸면 npm run dev 에서 /api 가 죽는다
- run-local.ps1 은 --server.port 인자를 떼고 앱 기본값을 따르게 함
- README / deploy/README 의 포트 표기
로컬에서 재빌드 후 확인: 8877 /login 200, 8080 닫힘.
Co-Authored-By: Claude Opus 5 (1M context)
기능이 대폭 늘었는데 README는 기관 명부·채널 생성까지만 설명하고 있어 실제와 어긋났다.
대시보드·사업관리·시스템관리·권리확인/처리·도움말까지 반영하고, 읽는 순서대로 다시 배치한다.
- 화면 구성, 12단계 흐름, 최초 사용 순서를 표와 도식으로 정리
- 환경변수를 필수 8개와 기본값 있는 선택 2개로 나눠 표기
- 수정할 때 깨뜨리면 안 되는 약속(채널 2종, 재실행 안전, 채널⇔단계 불변식,
엑셀 재업로드 병합 규칙, 제3자 권리처리 정책, 연번 문자열)을 한 표로 모음
- 패키지·화면 구조와 API 목록, 배포(deploy/README.md 연결) 추가
Yona 렌더러에서 깨지지 않도록 GitHub 전용 문법(경고 블록, HTML 표) 대신
표준 마크다운만 쓴다.
Co-Authored-By: Claude Opus 5 (1M context)
멀티스테이지로 프런트(React)까지 이미지 안에서 빌드해 단일 jar로 만든다.
빌드 머신에만 인터넷이 필요하고(Node v20.17.0·npm·Maven 내려받음) 실행 서버에는
JDK도 Node도 깔 필요가 없다.
- 컨테이너 시간대를 Asia/Seoul로 고정한다. UTC로 두면 업무단계 도달 일시,
칸반 카드의 D+n, 연락 이력 날짜가 9시간 어긋난다.
- 비루트 사용자로 실행하고, actuator가 없으므로 헬스체크는 /login 200으로 한다.
- 이미지 빌드 중에는 테스트를 돌리지 않는다(-DskipTests). Testcontainers가 Docker를
다시 요구해 Docker-in-Docker가 되기 때문이며, 테스트는 CI에서 별도 단계로 돌린다.
- .dockerignore로 run-local.ps1을 제외한다(DB 비밀번호가 들어 있다).
deploy/는 192.168.0.176 운영 정의다. DB는 기존 192.168.0.60을 그대로 쓰므로 컨테이너로
띄우지 않고, 앱이 로컬 디스크에 쓰는 것이 없어(업로드는 DB와 Mattermost로 간다)
영속 볼륨도 두지 않는다. 실제 값은 서버의 /etc/itnhub/itnhub.env 에만 두고
저장소에는 .example만 남긴다.
로컬에서 이미지 빌드·기동까지 확인했다: jar 안에 static/index.html 포함, 컨테이너
시간대 KST, /login 200, HEALTHCHECK healthy, Flyway "schema itnhub: 12, up to date".
Co-Authored-By: Claude Opus 5 (1M context)
요청자 피드백 PPT 2건(기능확인 24쪽, 문정원 검토내용 17쪽)에서 확인된
요구를 다음과 같이 반영한다.
- 대시보드: KPI 7타일 + "단계 미지정" 포함 13컬럼 칸반(카드 드래그로 단계 이동)
+ 최근 진행 기관. 집계는 /api/dashboard 한 번으로 끝낸다 - 기관별로 건수를
반복 조회하면 화면 한 번에 150회 조회가 되기 때문이다.
- 사업관리: 3단계 대상 기관 현황, 신청 확인 연락 이력, 기관별 최종 보고서.
기관 현황 숫자는 대시보드와 같은 집계를 재사용해 두 화면이 어긋나지 않게 한다.
- 시스템관리: 채널 초기화(대상 미리보기 → 확인문구 입력 → 서버 재검사).
삭제가 아니라 보관(아카이브)이며 DB가 아는 채널만 대상으로 한다.
- 권리확인: 제3자 권리처리 정책변경 반영. "권리처리 추진 미희망"과 공공누리
"보류"를 신설하고, 처리결과에 따라 공공누리 유형·비고를 규칙대로 자동 채운다.
- 권리확인/권리처리: 일괄 등록·선택 삭제와 열별 조건 복합검색(AND).
- 업무메모: 작성자를 화면에서 고르지 않고 로그인 사용자를 서버가 기록하며,
수정 기능을 추가한다(작성자·작성시각은 유지).
- 기관관리: 채널에 고정된 최초 안내글을 확인하는 [최초 게시글 보기].
- 도움말: 주요 버튼 옆 (?) 모달 15개 항목. 실제 화면 캡처를 함께 담는다.
마이그레이션 두 건:
- V11 "채널 있음 <=> 단계 있음" 불변식을 맞추는 1회성 백필. 단계 기능(V8)
이전에 채널을 만든 기관이 칸반에서 사라지는 것을 막는다.
- V12 사업관리용 contact_log / org_report.
테스트: 백엔드 221건, 프런트 191건 전부 통과.
Co-Authored-By: Claude Opus 5 (1M context)
권리확인 탭과 같은 목록↔상세 구조로 권리처리 보드를 만든다. 상태 칩(전체/미처리/
처리완료)과 검색어로 목록을 필터링하고, 상세에서 계약서 유무를 체크박스 조합(+기타
직접입력)으로 관리하며 권리처리상태를 고르기 전에는 저장을 막는다. 기관 상세 헤더의
권리확인/권리처리 통계 카드도 각 탭의 완료·전체 건수를 실데이터로 보여주도록 연결한다.
권리확인 모듈을 참고해 권리처리 탭 전용 process_item 테이블과 CRUD API를 구성한다.
조사원+권리확인 영역은 재업로드마다 항상 최신 파일값으로 갱신하고, 계약서 유무와
권리처리 판정(공공누리유형/최종의견/판단근거/처리상태)은 웹에서 한 번이라도 수정하면
재업로드가 덮어쓰지 않는다. 처리완료 최초 도달 시각은 processed_at에 남기고 이후
재저장에도 값이 바뀌지 않는다.
기관별 권리확인 엑셀(Sheet0, ~1000행)을 업로드해 review_item 테이블에 반영하고,
조사원 항목은 항상 최신화하되 변호사 판정은 웹에서 한번 수정되면(web_edited_at)
재업로드가 덮어쓰지 않는 병합 정책을 적용한다. 목록/단건조회/판정저장/삭제/원본
서식 그대로의 엑셀 다운로드까지 REST로 제공한다.
기관의 신청기관/문정원/아이티앤/변호사 담당자가 채널 생성(또는 재확인) 시
이메일 기준으로 Mattermost 계정을 찾거나 새로 만들고, 문정원/법률검토 채널에
알맞게 추가하도록 ChannelProvisionService.ensureMembers 훅을 추가했다.
계정 생성 실패(아이디 중복 등)와 채널 추가 실패는 담당자별로 격리되어
provision 결과를 뒤집지 않고 메시지에만 남는다.
Co-Authored-By: ITN Dev