diff --git a/.claude/memory/MEMORY.md b/.claude/memory/MEMORY.md new file mode 100644 index 00000000..90c34ad0 --- /dev/null +++ b/.claude/memory/MEMORY.md @@ -0,0 +1,7 @@ +- [Project Tech Stack](project_stack.md) — Spring Boot 3.3 + Java 17 + MyBatis + MSSQL, solar panel system (QCAST) +- [Middle server DTOs — act, don't notify](feedback_proactive_middle_server.md) — qcast-api가 중간 서버이므로 통보문만 주지 말고 DTO를 같은 세션에서 바로 수정할 것 +- [작업 완료 = 코드+인수인계서+커밋 메시지 한번에](feedback_commit_message.md) — 코드 변경 시 doc/ 인수인계서 동시 갱신, 한국어 커밋 메시지 제시(직접 커밋 X), 해시는 post-commit 훅 자동 기록 +- [로컬 애플리케이션 로그 경로](reference_local_log_path.md) — 로컬 실행 시 `logs/api/local.qcast.api.log` (logback FILE appender, local 프로필) +- [커밋 로그 파일 위치](reference_commit_log.md) — qcast-api는 post-commit 훅으로 `memory/commit_log.txt`에 매 커밋 자동 append +- [코드 주석 작성 시 날짜 포함](feedback_comment_date.md) — 파일 생성/수정 시 새로 다는 주석에 항상 작성 날짜(YYYY-MM-DD) 포함 +- [Claude 메모리 저장 위치](reference_memory_location.md) — 2026-05-06부터 메모리는 `/.claude/memory/`에 git 관리, 외부 경로는 symlink. 새 PC는 `.claude/setup-memory-symlink.sh` 실행 diff --git a/.claude/memory/feedback_comment_date.md b/.claude/memory/feedback_comment_date.md new file mode 100644 index 00000000..a004688d --- /dev/null +++ b/.claude/memory/feedback_comment_date.md @@ -0,0 +1,20 @@ +--- +name: 코드 주석 작성 시 날짜 포함 +description: 파일 생성/수정 중 주석을 달 때는 그날의 날짜(YYYY-MM-DD)를 함께 적을 것 +type: feedback +--- + +파일을 생성하거나 수정할 때 **주석을 추가하는 경우, 작성 날짜(YYYY-MM-DD)를 반드시 포함**할 것. + +**Why:** 사용자가 변경 이력 추적과 컨텍스트 파악을 쉽게 하기 위함. 주석만 보고도 언제 적힌 것인지 알 수 있어야 git blame을 거치지 않고도 시간 흐름을 파악 가능. + +**How to apply:** +- 새로 다는 주석 모두 적용 대상 (한 줄 주석, 블록 주석, JavaDoc, XML 주석 등 형식 무관) +- 형식 예시: + - Java/JS 한 줄: `// 2026-05-06 cha: 발전 시뮬레이션 정렬 로직 추가` + - Java JavaDoc: `* @since 2026-05-06` + - XML/HTML: `` + - SQL: `-- 2026-05-06: 인덱스 힌트 추가` +- 날짜는 항상 시스템 컨텍스트의 `currentDate`(오늘) 기준 절대 날짜로 적을 것 ("어제", "오늘" 같은 상대 표현 금지) +- 기존 주석을 단순히 읽거나 보존하는 경우는 해당 없음 — **새로 작성하는 주석에만** 적용 +- 코드 자체에 주석 추가가 불필요한 경우(자명한 코드)에는 주석 자체를 달지 않는 기존 원칙 유지 (이 규칙은 "주석을 달 때"의 형식 규약) diff --git a/.claude/memory/feedback_commit_message.md b/.claude/memory/feedback_commit_message.md new file mode 100644 index 00000000..f643695d --- /dev/null +++ b/.claude/memory/feedback_commit_message.md @@ -0,0 +1,49 @@ +--- +name: 작업 완료 시 커밋 메시지 제안 + 인수인계서 자동 갱신 + 커밋 해시는 자동 로그 참조 +description: 작업이 끝났을 때 한국어 커밋 메시지 한 줄과 함께 관련 인수인계서까지 자동으로 갱신하고 제시한다. 커밋 해시는 post-commit 훅이 누적하는 commit_log.txt에서 참조한다. +type: feedback +--- + +## 1. 커밋 메시지 제안 + +작업이 완료되면 항상 git 커밋에 사용할 메시지를 함께 제시한다. + +**Why:** 사용자가 직접 git에 올리며, 매번 커밋 메시지 작성을 요청하는 번거로움을 줄이기 위함. 또한 커밋 본문에 변경 항목 타이틀이 정리돼 있어야 나중에 이력 추적이 쉬움. + +**How to apply:** +- 작업 완료 응답 마지막에 별도 섹션으로 커밋 메시지 제시 +- 한국어로 **타이틀 한 줄**만 작성 (예: "로컬 프로필 파일 로깅 활성화 + 로그 경로 프로젝트 내부로 이동") + - 사용자는 명령 → 작업 완료 → 즉시 커밋 흐름이므로, 한 작업 단위가 곧 한 커밋. 본문 불릿은 불필요. + - 예외적으로 한 작업에 정말 이질적인 변경이 섞인 경우에만 본문 불릿을 추가 (드문 경우) +- **티켓번호(`[1487]`, `[2133]` 등) prefix는 절대 임의로 붙이지 말 것** — 사용자가 직접 추가하는 영역. Claude는 타이틀 본문만 작성. +- 변경 핵심을 간결하게 (무엇을/어디서/어떻게의 압축형) +- 사용자가 직접 커밋하므로 Claude가 임의로 `git commit` 실행하지 말 것 + +## 2. 작업 완료 시 인수인계서 동시 갱신 (커밋 신호 기다리지 말 것) + +작업이 끝나면 **별도 지시 없어도 관련 인수인계서까지 같은 응답에서 함께 갱신**한다. 사용자는 작업 완료 → 즉시 커밋 흐름이라, "인수인계서도 했지?"라고 한 번 더 물어보게 만들면 안 된다. + +**Why:** 사용자 피드백(2026-05-06): "내가 커밋을 하면 자동으로 여기까지 해줘야 한다." 코드 변경만 하고 인수인계서를 빼먹으면 사용자가 커밋 직전에 다시 갱신을 요청해야 하므로 흐름이 끊김. 인수인계서는 후임/리뷰의 1차 자료라 코드 변경과 동시 반영이 필수. + +**How to apply:** +- 작업 완료 응답에서: ① 코드 변경 요약 → ② 인수인계서 갱신 → ③ 커밋 메시지 제안 순서로 구성. +- **갱신 대상 인수인계서 매핑** (qcast-api `doc/`): + - 견적서·발전시뮬·Excel/PDF 출력 → `OUTPUT_HANDOVER.md` + - QSP↔프론트 DTO 변경, 중간서버 스키마 → `DTO_MIDDLE_SERVER_HANDOVER.md` + - 인증·로그인 추적·errSave류 → `AUTH_ERROR_HANDOVER.md` + - 그 외 도메인 (BOM/권한/우편번호/배치 등) → `DOMAIN_HANDOVER.md` + - 트랙이 새로 생기면 `HANDOVER_INDEX.md`에도 항목 추가. +- 갱신 방식: **기존 번호 시리즈를 가능한 보존**, 새 항목은 **마지막 번호 다음에 신규 섹션으로 append** (기존 §번호를 한 칸씩 미루지 말 것 — diff 부담↑). +- 새 섹션에는 **작성일(YYYY-MM-DD)**, 배경(무슨 일이 있었나), 한 일(파일·라인), 후속 확인 포인트를 넣음. 사용자 메모리 정책 "코드 주석에 날짜 포함"과 동일 원칙. +- 변경이 사소하거나(오타, 포맷팅 등) 인수인계서 정책에 영향 없는 경우는 갱신 생략 가능. 단 의심되면 갱신 쪽으로 기울 것. +- 인수인계서 갱신 자체도 같은 커밋에 포함되도록 사용자에게 안내 (별도 커밋 분리 옵션도 같이 제시). + +## 3. 커밋 해시는 자동 로그에서 참조 (수동 기록 X) + +2026-05-06부터 qcast-api 저장소에 git `post-commit` 훅이 설치되어, 커밋 직후 자동으로 `~/.claude/projects/-Users-cha-Works-Projects-qcast3-qcast-api/memory/commit_log.txt`에 한 줄 append 된다. + +**How to apply:** +- 인수인계서/리뷰/리포트에 해시를 인용해야 할 때는 그 로그 파일을 읽어 참조 (또는 `git log -1 --format='%h %s'`) +- Claude가 메모리 파일에 해시를 수동으로 누적할 필요 **없음** (이중 기록 금지) +- 푸시 여부는 로그에 기록되지 않으므로, 푸시 확인이 필요하면 `git log origin/..HEAD` 등으로 별도 확인 +- 자세한 위치/형식은 `reference_commit_log.md` 참조 diff --git a/.claude/memory/feedback_proactive_middle_server.md b/.claude/memory/feedback_proactive_middle_server.md new file mode 100644 index 00000000..1c723667 --- /dev/null +++ b/.claude/memory/feedback_proactive_middle_server.md @@ -0,0 +1,15 @@ +--- +name: Middle server DTO updates — act, don't just notify +description: qcast-api is the middle server (QSP API → qcast-api → qcast-front). When QSP or front contract changes, modify qcast-api DTOs directly in the same session +type: feedback +--- + +아키텍처: **QSP API → qcast-api(중간 서버) → qcast-front**. QSP 응답이나 프론트 요청 스키마가 바뀌면 qcast-api DTO를 통과해야 하므로 DTO 수정이 반드시 필요하다. "통보 템플릿"만 만들고 DTO 수정을 미루지 말 것. + +**Why:** qcast-api 자체가 중간 서버다. 외부 통보할 "다른 팀의 서버"가 따로 있지 않다. 사용자가 한 번 물어봤을 때 통보문만 주고 DTO 수정을 미루면, 다음 날 사용자가 "DTO에 추가해야 하는 거 아닌가?"라고 다시 물어 같은 작업을 두 번 하게 된다. 사용자 피드백(2026-04-22): "어제 명령했을 때는 추가 안 하고, 내가 오늘 다시 물으니깐 추가하면 일을 두번 하게 만들어." + +**How to apply:** +- QSP 응답 변경 → `dto/` 하위 Response DTO 필드 추가 +- 프론트 요청 변경 → `dto/` 하위 Request DTO 필드 추가 +- 분석 후 바로 Edit으로 반영, "반영할까요?" 재확인 없이 진행 +- 통보 템플릿은 QSP 백엔드 팀 쪽에만 해당 (qcast-api 내부는 직접 수정) diff --git a/.claude/memory/project_stack.md b/.claude/memory/project_stack.md new file mode 100644 index 00000000..9865e581 --- /dev/null +++ b/.claude/memory/project_stack.md @@ -0,0 +1,21 @@ +--- +name: qcast-api tech stack +description: Spring Boot 3.3 + Java 17 + MyBatis + MSSQL Server project for qcast solar panel system +type: project +--- + +qcast-api is a Spring Boot 3.3.0 REST API for a solar panel (PV) system called QCAST. + +**Tech stack:** +- Java 17, Spring Boot 3.3.0, Maven +- MyBatis 3.0.3 with XML mappers (src/main/resources/mappers/) +- MSSQL Server (database: NEWPVCAD) +- Lombok, Jasypt (password encryption), Spring Security, OpenFeign +- Apache POI / jxls (Excel export), iText html2pdf (PDF generation) +- Springdoc OpenAPI (Swagger UI) +- Spring Batch + Quartz (scheduler) + +**Package structure:** com.interplug.qcast.biz.{domain} with Controller/Service/Mapper/DTO pattern + +**Why:** Understanding the stack helps provide accurate code suggestions matching existing patterns. +**How to apply:** Use MyBatis XML mapper style for DB queries, follow existing Controller-Service-Mapper layering, write SQL for MSSQL Server syntax. diff --git a/.claude/memory/reference_commit_log.md b/.claude/memory/reference_commit_log.md new file mode 100644 index 00000000..5e963a21 --- /dev/null +++ b/.claude/memory/reference_commit_log.md @@ -0,0 +1,23 @@ +--- +name: 커밋 로그 파일 위치 (post-commit hook 자동 누적) +description: qcast-api 저장소의 .git/hooks/post-commit 훅이 매 커밋 직후 누적 기록하는 plain text 로그 위치와 형식 +type: reference +--- + +qcast-api 저장소는 로컬 git `post-commit` 훅으로 매 커밋 직후 다음 파일에 한 줄을 append 한다. + +- **훅이 쓰는 경로**: `~/.claude/projects/-Users-cha-Works-Projects-qcast3-qcast-api/memory/commit_log.txt` +- **실제 저장 위치**: 위 경로는 symlink → `/.claude/memory/commit_log.txt` 로 해소됨 (2026-05-06부터 메모리를 프로젝트 내부로 이동) +- **gitignore**: `commit_log.txt`는 머신마다 다르므로 git 추적 제외 (`.gitignore`의 `### Claude Code ###` 섹션 참조) +- **형식**: `YYYY-MM-DD HH:MM:SS [브랜치] short(fullhash) 커밋제목` +- **훅 위치**: `/.git/hooks/post-commit` (qcast-api 로컬 전용, 팀 공유 X) +- **작성일**: 2026-05-06 + +## 사용 시점 +- 사용자가 "최근 커밋 뭐 있었지?", "어제 푸시한 해시 알려줘" 같은 질문을 할 때 이 로그 파일을 먼저 확인한다. +- `git log`와 병행 활용: 로그 파일은 누적 타임스탬프(작업 시각) 기준, `git log`는 커밋 메타데이터 기준이라 둘이 보완적이다. +- 푸시 여부는 기록되지 않음(post-commit만 trigger). push 성공 여부가 필요하면 별도 확인 필요. + +## 주의 +- 로그 파일이 없을 수도 있음 (아직 커밋이 한 번도 발생하지 않은 경우). 이 경우 `git log`로 대체. +- 다른 저장소에서는 동작하지 않음. 이 hook은 qcast-api 로컬에만 설치됨. diff --git a/.claude/memory/reference_local_log_path.md b/.claude/memory/reference_local_log_path.md new file mode 100644 index 00000000..d46203e0 --- /dev/null +++ b/.claude/memory/reference_local_log_path.md @@ -0,0 +1,25 @@ +--- +name: 로컬 애플리케이션 로그 경로 +description: qcast-api 로컬 실행 시 logback이 기록하는 로그 파일 위치 — 디버깅/에러 추적 시 직접 읽을 수 있음 +type: reference +--- + +로컬 프로필(`local`)로 qcast-api 실행 시 로그 파일 위치 (프로젝트 상대경로): + +- **현재 로그**: `logs/api/local.qcast.api.log` (절대경로: `/Users/cha/Works/Projects/qcast3/qcast-api/logs/api/local.qcast.api.log`) +- **롤링 백업**: `logs/api/local.qcast.api.YYYY-MM-DD.log` (TimeBasedRollingPolicy, 일자별) + +설정 위치: `src/main/resources/logback/logback-local.xml` +- `LOG_HOME=./logs/api/` (프로젝트 작업 디렉터리 기준 상대경로) +- 로그 레벨: `com.interplug.qcast` = DEBUG, root = WARN, Spring Batch/JDBC = DEBUG +- `.gitignore`에 `logs/` 추가됨 (커밋 안 됨) + +활용 방법: 사용자가 에러나 동작을 물을 때 로그 파일을 직접 읽어 원인 추적. +- `Read /Users/cha/Works/Projects/qcast3/qcast-api/logs/api/local.qcast.api.log` +- `Bash: tail -n 300 logs/api/local.qcast.api.log` +- `Grep "ERROR\|Exception" logs/api/local.qcast.api.log` + +주의: +- 상대경로 `./logs/api/`는 앱 실행 시 working directory 기준. IntelliJ/터미널 모두 보통 프로젝트 루트가 기준이므로 정상 동작. +- 만약 working dir이 달라지면 다른 위치에 생성될 수 있음 — 파일이 없으면 사용자에게 실행 위치 확인 요청. +- dev/prd 프로필은 다른 경로 사용 (이 메모리는 로컬 전용) diff --git a/.claude/memory/reference_memory_location.md b/.claude/memory/reference_memory_location.md new file mode 100644 index 00000000..cc151532 --- /dev/null +++ b/.claude/memory/reference_memory_location.md @@ -0,0 +1,28 @@ +--- +name: Claude 메모리 저장 위치 (프로젝트 내부) +description: qcast-api의 Claude 자동 메모리 파일이 저장되는 실제 위치와 다른 PC에서 셋업하는 방법 +type: reference +--- + +2026-05-06부터 qcast-api의 Claude 자동 메모리는 **프로젝트 내부**에 저장하고 git으로 관리한다. + +## 구조 +- **실제 저장 위치**: `/.claude/memory/` (git 추적 대상) +- **Claude Code가 접근하는 경로**: `~/.claude/projects/-Users-cha-Works-Projects-qcast3-qcast-api/memory/` + - 위 경로는 `/.claude/memory/` 로 향하는 **symlink** +- **commit_log.txt** 만 예외적으로 머신 로컬 (gitignore 처리됨) + +## 다른 PC에서 처음 셋업할 때 +저장소 클론 직후 한 번만 실행: + +```sh +sh .claude/setup-memory-symlink.sh +``` + +스크립트는 멱등성이 보장되며, Claude Code 인코딩 규칙(working dir 절대경로의 `/`를 `-`로 치환)에 맞춰 +`~/.claude/projects//memory` symlink를 자동 생성한다. 클론 위치가 다르면 인코딩도 자동으로 달라진다. + +## 활용 +- 메모리 파일을 추가/수정하면 git diff에 잡히므로 다른 변경과 함께 커밋 가능 +- 새 메모리 작성 시에도 기존 자동 메모리 흐름 그대로 (`Write` 후 `MEMORY.md` 인덱스 추가) — 어느 경로로 쓰든 symlink를 통해 같은 파일을 가리킴 +- `MEMORY.md`도 git에 함께 커밋되어 다른 PC에서 즉시 동일한 인덱스 사용 가능 diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 00000000..3a3b3492 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,14 @@ +{ + "hooks": { + "UserPromptSubmit": [ + { + "hooks": [ + { + "type": "command", + "command": "DEBUG_FILE=/tmp/claude-debug.log; LOG_FILE=/logs/api/local.qcast.api.log; if [ -f \"$DEBUG_FILE\" ] && [ -s \"$DEBUG_FILE\" ]; then echo '=== [ClaudeDebug 덤프] ==='; cat \"$DEBUG_FILE\"; echo '=== [덤프 END] ==='; fi; if [ -f \"$LOG_FILE\" ]; then ERRORS=$(tail -n 200 \"$LOG_FILE\" | grep -E 'ERROR|WARN|Exception' | tail -n 20); if [ -n \"$ERRORS\" ]; then echo '=== [APP 에러/경고 로그] ==='; echo \"$ERRORS\"; echo '=== [LOG END] ==='; fi; fi" + } + ] + } + ] + } +} diff --git a/.claude/setup-memory-symlink.sh b/.claude/setup-memory-symlink.sh new file mode 100755 index 00000000..f0ad7b82 --- /dev/null +++ b/.claude/setup-memory-symlink.sh @@ -0,0 +1,49 @@ +#!/bin/sh +# 2026-05-06: Claude Code auto-memory 시스템이 사용하는 외부 디렉터리(~/.claude/projects//memory)를 +# 이 저장소의 .claude/memory 로 향하는 symlink로 만들어 주는 스크립트. +# +# 배경: +# - Claude Code는 working directory 경로를 인코딩(슬래시→하이픈)해서 ~/.claude/projects//memory 에 +# 메모리 파일을 읽고 쓴다. 이 경로는 머신/체크아웃 위치마다 달라진다. +# - 메모리 .md 파일들은 git에 커밋되어 있고, 다른 PC에서는 symlink만 새로 걸어 주면 +# 기존 메모리를 그대로 활용할 수 있다. +# +# 사용: +# sh .claude/setup-memory-symlink.sh +# +# 멱등성: 이미 올바른 symlink가 존재하면 아무 동작도 하지 않는다. + +set -eu + +REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" +MEMORY_SRC="$REPO_ROOT/.claude/memory" + +# Claude Code 인코딩 규칙: 절대경로 슬래시(/)를 하이픈(-)으로 치환한 디렉터리 이름 +ENCODED="$(printf '%s' "$REPO_ROOT" | sed 's|/|-|g')" +TARGET_DIR="$HOME/.claude/projects/$ENCODED" +TARGET_LINK="$TARGET_DIR/memory" + +if [ ! -d "$MEMORY_SRC" ]; then + echo "[setup-memory-symlink] ERROR: $MEMORY_SRC 가 없습니다. 먼저 git pull 하세요." + exit 1 +fi + +mkdir -p "$TARGET_DIR" + +# 이미 같은 곳을 가리키는 symlink면 종료 +if [ -L "$TARGET_LINK" ]; then + CURRENT="$(readlink "$TARGET_LINK")" + if [ "$CURRENT" = "$MEMORY_SRC" ]; then + echo "[setup-memory-symlink] OK: 이미 연결됨 ($TARGET_LINK -> $MEMORY_SRC)" + exit 0 + fi + echo "[setup-memory-symlink] 기존 symlink 갱신: $CURRENT -> $MEMORY_SRC" + rm "$TARGET_LINK" +elif [ -e "$TARGET_LINK" ]; then + echo "[setup-memory-symlink] ERROR: $TARGET_LINK 가 일반 파일/디렉터리로 존재합니다." + echo " 내용 백업 후 직접 정리하세요. (이 스크립트는 안전을 위해 강제 삭제하지 않습니다.)" + exit 1 +fi + +ln -s "$MEMORY_SRC" "$TARGET_LINK" +echo "[setup-memory-symlink] 연결 완료: $TARGET_LINK -> $MEMORY_SRC" diff --git a/.gitignore b/.gitignore index 32b5ccdb..955da1c4 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,8 @@ build/ qcast3/ src/test logs/ + +### Claude Code ### +# 2026-05-06: 머신 로컬 전용 파일 — settings.local.json, post-commit 훅 누적 로그 +.claude/settings.local.json +.claude/memory/commit_log.txt diff --git a/doc/AUTH_ERROR_HANDOVER.md b/doc/AUTH_ERROR_HANDOVER.md new file mode 100644 index 00000000..31af7dfe --- /dev/null +++ b/doc/AUTH_ERROR_HANDOVER.md @@ -0,0 +1,110 @@ +# 인증 / 로그인 추적 / 에러 저장 인수인계서 + +> 작성일: 2026-05-06 +> 대상: 후임 AI 엔지니어 또는 개발 관리자 +> 작업 브랜치: `dev_ysCha` + +> **이 문서의 범위**: 로그인·로그아웃 추적, 클라이언트 에러 저장 API 트랙만 다룹니다. +> 다른 트랙은 `HANDOVER_INDEX.md` 참조. + +--- + +## 0. 한 줄 요약 + +운영 중 **자동 로그인/로그아웃이 의도대로 안 되는 케이스**와 **클라이언트에서 발생한 예외를 잡지 못하는 문제**를 추적하기 위해, 추적 로그와 에러 저장 API를 단계적으로 보강한 트랙입니다. + +--- + +## 1. 로그인 / 로그아웃 추적 + +### 1.1 한 일 + +| 단계 | 내용 | 커밋 | +|------|------|------| +| 1차 | 로그인·로그아웃 추적 | `60b84191` | +| 2차 | 자동 로그인 추적 | `1bf777f8` | + +### 1.2 추적 로그의 목적 + +- 어디서 토큰이 사라지는지 +- 어디서 자동 로그인 트리거가 안 잡히는지 +- 어디서 로그아웃이 누락되는지 + +> 프론트(qcast-front) 에도 같은 목적의 추적 트랙이 있습니다 (`API_STABILITY_HANDOVER.md` 5절). 프론트와 백엔드 양쪽 로그를 **같은 시점 기준으로 매칭**해서 봐야 원인이 보입니다. + +### 1.3 주의 + +- 추적 로그는 디버깅 목적으로 추가된 것이라, **운영에서 떼어낼지 유지할지** 사용자에게 확인이 필요합니다. +- 토큰/세션 정보를 로그에 그대로 남기면 보안 이슈가 생기므로, 로그 내용에 민감 정보가 안 들어가는지 다시 확인하세요. + +--- + +## 2. 에러 저장 API — `errSave` / `quotationErrSave` + +### 2.1 한 일 + +| 단계 | 내용 | 커밋 | +|------|------|------| +| 1차 | `errSave` API 추가 | `63969fa2` | +| 2차 | `errSave` API 추가 (보강) | `23f70b21` | +| 3차 | `errSave` API 추가 2 | `d22a7508` | +| 별건 | `quotationErrSave` API 추가 (견적서 전용) | `0b9eeb99` | + +### 2.2 무엇을 하나 + +- 클라이언트(프론트)에서 발생한 예외 / 에러를 **백엔드로 전송해 저장**. +- 운영 중 사용자가 알아채지 못하는 에러 추적용. + +### 2.3 사용처 (프론트 측) + +- 글로벌 에러 핸들러 +- 주요 fetch 실패 catch 블록 +- 견적서 출력 실패는 `quotationErrSave` 로 분리 + +### 2.4 주의 + +- 에러 저장 자체가 또 실패하면 **무한 루프** 가 될 수 있습니다. 저장 실패 시에는 추가 호출을 하지 않도록 막아야 합니다. +- 로그에 사용자 입력값을 통째로 담으면 PII 가 들어갈 수 있어 위험. 필요한 식별자만 남기세요. + +--- + +## 3. 안정성 보강 + +### 3.1 `itemList` null 체크 +- 커밋: `b84e1fd0` +- 견적서 항목 리스트가 null 인 경우 NPE 가 나던 문제. 호출부에서 null 체크 추가. + +### 3.2 `HttpMediaTypeNotAcceptableException` 에러 +- 커밋: `50d8a9e3` +- 특정 요청에서 Accept 헤더 불일치로 예외 발생 → 컨버터/Producer 설정 보강. + +--- + +## 4. 핵심 파일 + +| 파일/위치 | 역할 | +|----------|------| +| `com.interplug.qcast.biz.auth.*` | 로그인/세션 관련 | +| `com.interplug.qcast.biz.{도메인}.*ErrSaveController` 또는 Service | errSave 엔드포인트 | +| `com.interplug.qcast.biz.quotation.*ErrSave*` | quotationErrSave 엔드포인트 | +| 글로벌 ExceptionHandler | 알 수 없는 예외 처리 | + +> 정확한 클래스명은 변경되었을 수 있으니 IDE 검색으로 `errSave` 키워드 찾는 게 빠릅니다. + +--- + +## 5. 인수 직후 권장 절차 + +1. 이 문서 통독. +2. `errSave` 키워드로 코드 베이스 검색해서 엔드포인트 위치 확인. +3. 로그인 컨트롤러에서 토큰 발급/검증 흐름 한 번 따라가 보기. +4. 운영 로그에서 `자동 로그인` / `로그아웃` 키워드로 grep 해 어떤 패턴이 잡히는지 확인. + +--- + +## 6. 미해결 / 추적 중 + +- **자동 로그인 실패 케이스 완전 재현 안 됨**. 추적 로그를 보면 단서가 보이는데, 환경별로 다르게 나타남. +- 인증 추적 로그를 **운영에서 유지/제거**할지 사용자 결정 필요. + +끝. diff --git a/doc/DOMAIN_HANDOVER.md b/doc/DOMAIN_HANDOVER.md new file mode 100644 index 00000000..df542713 --- /dev/null +++ b/doc/DOMAIN_HANDOVER.md @@ -0,0 +1,153 @@ +# 도메인 기능 인수인계서 (BOM / 권한 / API / 우편번호 / 배치) + +> 작성일: 2026-05-06 +> 대상: 후임 AI 엔지니어 또는 개발 관리자 +> 작업 브랜치: `dev_ysCha` + +> **이 문서의 범위**: 출력·DTO·인증 외 나머지 도메인 작업. +> 다른 트랙은 `HANDOVER_INDEX.md` 참조. + +--- + +## 0. 한 줄 요약 + +[1xxx] 티켓 시리즈와 새로 추가된 API들이 모이는 곳. **BOM 부모/자식 의미**, **오브젝트 복사 권한**, **발전시뮬레이션 API 신규 개발**, **우편번호 하이픈 자동 처리**, **배치(Quartz) 포트/시간 조정** 등 다양한 도메인 작업이 들어 있습니다. + +--- + +## 1. BOM (자재 명세서) + +### 1.1 `[1487]` 자식 BOM 조회 쿼리 컬럼 의미 수정 +- 커밋: `0c979df6` +- 가장 헷갈렸던 부분: **컬럼명과 의미가 직관과 반대**. + - `PACKAGE_ITEM_ID` = **자식** + - `ITEM_ID` = **부모** +- BOM 조회/저장 쿼리를 새로 손댈 때 항상 이 의미를 다시 확인하세요. **반대로 알면 부모-자식 관계가 통째로 뒤집힙니다.** + +### 1.2 권장 +- 새 매퍼 쿼리에 컬럼 alias를 줄 때 `PARENT_ITEM_ID` / `CHILD_ITEM_ID` 같이 의미 명시한 별칭을 써서 후임자가 헷갈리지 않게 하세요. + +--- + +## 2. 권한 + +### 2.1 `[1485]` 오브젝트 복사 기능 권한 설정 +- 커밋: `491f9304`, `31e3c77a` +- 오브젝트 복사 액션은 모두에게 열려 있지 않고 **권한이 있는 사용자만** 수행 가능하게 제한. +- 권한 체크는 Controller/Service 레이어에서. 프론트에서도 버튼이 노출/비노출되는지 양쪽 정합성 확인 필요. + +--- + +## 3. 발전시뮬레이션 관련 신규 API + +### 3.1 `[?]` 발전시뮬레이션 관련 견적서 조회 API +- 커밋: `7211101a` +- 발전시뮬레이션이 참조하는 견적서를 조회하는 신규 엔드포인트. + +### 3.2 `[?]` 발전시뮬레이션 물건번호 목록 조회 API +- 커밋: `c7253388` +- 물건번호(objectNo) 목록을 발전시뮬레이션 화면에 노출. + +### 3.3 `[1516]` 발전량 수정 — 중복 모듈 처리 +- 커밋: `3b090f7e` +- 같은 모듈이 중복으로 잡혀 발전량 계산이 중복되던 문제 수정. + +### 3.4 `[1375]` 인증용량 쿼리 수정 +- 커밋: `2c0a8a35`, `0a0e248b` +- 인증용량 산출 쿼리 보정. (수정이 두 번 나뉜 걸 보면 한 번에 안 잡힘 — 새 케이스가 발견되면 또 분기 추가될 가능성.) + +--- + +## 4. 모듈 / PCS / 거치대 + +### 4.1 `[1479]` 물건 정보 화면에 일부 PCS 명칭이 표시되지 않는다 — 스토리지뱃더리 추가 +- 커밋: `3a4dab2f` +- "스토리지뱃더리"(스토리지 배터리, 일본어 표기)를 PCS 종류에 추가. + +### 4.2 `[1763]` 특정 지붕재 사용 시 Plan 복사 오류 +- 커밋: `1b4e64b5` +- 특정 지붕재가 들어간 Plan을 복사하면 지붕재 정보가 올바르게 전달되지 않음 → 복사 시 매핑 보정. + +### 4.3 `[1764]` 북쪽 면 설치 사양 변경 +- 커밋: `408cc75a` +- 북쪽 면 설치 사양 처리 분기 추가. + +### 4.4 `[1423]` 설계 가져오기 기능 +- 커밋: `2b43fad2` +- 다른 설계를 현재 화면으로 가져오는 기능 보강. + +--- + +## 5. 우편번호 + +### 5.1 한 일 + +| 내용 | 커밋 | +|------|------| +| 판매점 우편번호 등록 시 하이픈이 없으면 **셋째 자리 뒤에 자동 하이픈 조합**으로 등록 처리 | `e05ef305` | +| 우편번호 변경 부분 추가 | `4e502c42` | + +### 5.2 정책 + +- 일본 우편번호는 `XXX-XXXX` 7자리 + 하이픈 형식. 사용자가 하이픈 없이 7자리만 넣어도 받아주도록 자동 보정. + +--- + +## 6. 배치 (Spring Batch + Quartz) + +### 6.1 한 일 + +| 내용 | 커밋 | +|------|------| +| 배치 잡이 8080, 8081 두 곳에서 실행되던 것을 8080만 실행하도록 수정 | `711c8fcd` | +| 개발에 `batch: scheduler-port: 8090` 추가 | `4bcc2fe9` | +| `materialJob`: `02:30 → 04:50`, `bomJob`: `02:40 → 05:20` 시간 조정 | `19250727` | +| job 수정 / 제목 한 줄에 나타내기 | `6b072e42`, `4759c572` | + +### 6.2 주의 + +- 배치 잡 시간을 옮길 때는 **다른 잡과 겹치지 않는지** 반드시 확인하세요. 같은 시간에 여러 잡이 돌면 DB 부하/락이 발생합니다. +- 잡이 두 인스턴스(8080/8081)에서 동시에 도는 사고가 있었던 영역이라 **포트 분리 정책**이 들어가 있습니다. + +--- + +## 7. Java 버전 / 빌드 환경 + +### 7.1 한 일 +- 커밋 `73da60a0`, `9b762063`, `d68b1270`, `037e3201` (java 버전 맞춤 1~3) +- 로컬·CI 자바 버전 불일치로 빌드 실패가 반복돼 **Java 17** 로 정렬. + +### 7.2 주의 +- 새 환경 세팅 시 `JAVA_HOME` 이 17 인지 먼저 확인. +- `pom.xml` 의 `` 도 17 로 고정. + +--- + +## 8. 핵심 파일 / 위치 + +| 위치 | 역할 | +|------|------| +| `com.interplug.qcast.biz.bom.*` | BOM 도메인 | +| `com.interplug.qcast.biz.simulation.*` | 발전시뮬레이션 | +| `com.interplug.qcast.biz.permission.*` 또는 Controller 단의 권한 어노테이션 | 권한 | +| `com.interplug.qcast.batch.*` | 배치 잡 / Quartz 설정 | +| `application*.yml` | 포트/스케줄러 포트/잡 시간 등 설정 | + +--- + +## 9. 인수 직후 권장 절차 + +1. 이 문서 통독. +2. `application.yml` 와 `application-{profile}.yml` 차이를 확인 (특히 배치 포트). +3. BOM 자식 조회 쿼리 한 번 직접 실행 → 부모/자식 컬럼 의미 체득. +4. 발전시뮬레이션 신규 API 두 개의 endpoint URL을 Swagger UI에서 확인. + +--- + +## 10. 미해결 / 추적 중 + +- `[1375]` 인증용량 쿼리는 **케이스가 더 있을 수 있음** (이미 두 차례 수정됨). +- 배치 잡 시간 정책은 운영 데이터 양에 따라 또 옮길 가능성 있음. +- 권한 체크가 Controller/Service 두 레이어에 산재되어 있어 **표준화 여지**가 있음 (시간 되면). + +끝. diff --git a/doc/DTO_MIDDLE_SERVER_HANDOVER.md b/doc/DTO_MIDDLE_SERVER_HANDOVER.md new file mode 100644 index 00000000..fcd685b5 --- /dev/null +++ b/doc/DTO_MIDDLE_SERVER_HANDOVER.md @@ -0,0 +1,114 @@ +# 중간 서버 DTO 변경 인수인계서 + +> 작성일: 2026-05-06 +> 대상: 후임 AI 엔지니어 또는 개발 관리자 +> 작업 브랜치: `dev_ysCha` + +> **이 문서의 범위**: qcast-api가 중간 서버로서 통과시키는 DTO 변경 작업만 다룹니다. +> 다른 트랙은 `HANDOVER_INDEX.md` 참조. + +--- + +## 0. 한 줄 요약 + +`QSP API → qcast-api → qcast-front` 의 가운데 다리 역할이라, **양쪽 스키마가 바뀌면 중간 DTO를 반드시 통과시켜야** 합니다. "외부에 통보하면 끝"이 아니라 **여기서 직접 코드를 고칩니다**. + +--- + +## 1. 가장 중요한 원칙 — 통보 X, 직접 수정 O + +### 1.1 무엇을 잘못하기 쉬운가 + +QSP 응답이나 프론트 요청 스키마가 바뀌었다는 얘기를 들었을 때, AI가 자주 하는 실수: + +> "QSP 응답에 X 필드가 추가되었습니다. DTO에 추가가 필요합니다." (← 이렇게 끝내고 코드는 안 고침) + +이 프로젝트에서는 **틀린 행동**입니다. + +### 1.2 사용자 피드백 (2026-04-22) + +> "어제 명령했을 때는 추가 안 하고, 내가 오늘 다시 물으니깐 추가하면 일을 두번 하게 만들어." + +### 1.3 올바른 행동 + +- 분석 → **그 자리에서 Edit으로 DTO 필드 추가** +- "반영할까요?" 재확인 없이 진행 +- 통보 템플릿은 QSP 백엔드 팀 쪽에만 해당 (qcast-api 내부는 직접 수정) + +--- + +## 2. 작업 위치 + +### 2.1 DTO 폴더 + +| 종류 | 위치 | +|------|------| +| Request DTO (프론트 → qcast-api) | `com.interplug.qcast.biz.{도메인}.dto.*Request` | +| Response DTO (QSP → qcast-api → 프론트) | `com.interplug.qcast.biz.{도메인}.dto.*Response` | + +> 도메인별로 패키지가 다르므로 IDE의 "Find in Files" 로 필드명 검색이 빠릅니다. + +### 2.2 수정 시 점검 포인트 + +1. **Request 인 경우**: 프론트가 보내는 필드를 받을 수 있는지 (필드명/타입 일치). +2. **Response 인 경우**: QSP 응답을 그대로 통과시키는지 (또는 변환 로직이 있는지). +3. **Lombok `@Data` 또는 `@Getter/@Setter`** 가 붙어 있는지 확인. +4. **Swagger 문서**에도 자동 반영되는지(어노테이션 사용 여부). + +--- + +## 3. 한 일 (대표 사례) + +### 3.1 PCS 접속함 `moduleTpCd` +- 커밋: `475733d2` +- PCS 접속함 **응답/요청 DTO 모두**에 `moduleTpCd` 필드 추가. +- 양방향이므로 두 군데를 동시에 수정해야 함. + +### 3.2 `trestles[].subModuleTpCd` +- 커밋: `45f8bbd4` +- `trestles[]` 배열 요소에 `subModuleTpCd` 추가. +- **Cross Mix 식별용**. 단일/동계열은 빈 값. + +### 3.3 `addresseeCompanyName` 50자 제한 +- 커밋: `b2305295` +- 수신자 회사명 50자 제한. (DTO/검증/저장 어느 단에서 자르는지 확인하고 일관되게) + +### 3.4 `selectParentAgentInfo` 관련 +- 커밋: `fd2703ba` +- 부모 에이전트 정보 조회 — DTO + Mapper + Service 한 세트. + +### 3.5 `CATEGORY` 20자 제한 +- 커밋: `4db2ddfd` +- `CATEGORY, LEN(CATEGORY) AS M_USER` 20자 제한. + +--- + +## 4. 검증 / 길이 제한 정책 + +길이 제한이 들어가는 필드들은 보통 다음 위치 **모두**에 같은 값을 둬야 합니다. + +| 위치 | 역할 | +|------|------| +| DTO 필드 | `@Size(max = N)` 등으로 검증 | +| Mapper XML | DB 컬럼 길이 일치 | +| 프론트 입력 폼 | 입력 단에서 자르기 / 안내 | + +> 한 곳만 50자, 다른 곳 100자 이렇게 어긋나면 운영에서 끊어집니다. 변경 시 전부 같이. + +--- + +## 5. 인수 직후 권장 절차 + +1. 이 문서 통독. +2. `dto/` 하위 패키지 한 번 훑어 어떤 도메인이 있는지 확인. +3. 가장 최근 DTO 변경 커밋(`475733d2`, `45f8bbd4`) 의 diff 한 번 따라가 보기. +4. QSP 응답 ↔ 프론트 요청 매핑 흐름을 견적서 도메인 하나로 따라가 보기. + +--- + +## 6. 미해결 / 추적 중 + +- 중간 서버 특성상 **상시 변경 영역**입니다. QSP 또는 프론트 변경 보고가 들어오면 즉시 DTO부터 확인하세요. +- 현재까지 별도의 미해결 DTO 이슈는 없으나, **변경 누락 = 운영 장애** 직결이므로 보수적으로. + +끝. diff --git a/doc/HANDOVER_INDEX.md b/doc/HANDOVER_INDEX.md new file mode 100644 index 00000000..d1106db1 --- /dev/null +++ b/doc/HANDOVER_INDEX.md @@ -0,0 +1,122 @@ +# 인수인계 문서 인덱스 (qcast-api) + +> 작성일: 2026-05-06 +> 대상: 후임 AI 엔지니어 또는 개발 관리자 +> 작업 브랜치: `dev_ysCha` + +--- + +## 0. 이 폴더는 뭔가요? + +**qcast-api** 프로젝트(태양광 시스템 QCAST의 백엔드 API)의 **트랙별 인수인계 문서**가 모여 있습니다. 한 파일에 다 넣으면 너무 길고 영역이 섞여서 보기 어렵기 때문에 4개 트랙으로 분리했습니다. + +**읽는 순서**: 위에서부터 차례로. 출력(Excel/PDF) 트랙이 가장 양이 많고 손댈 일도 많으니 먼저 읽으세요. + +> **참고**: 이 프로젝트는 **중간 서버**입니다. 구조는 `QSP API → qcast-api → qcast-front` 입니다. "다른 팀 서버에 알려야 한다" 가 아니라 **여기서 직접 DTO/로직을 고치면 됩니다**. + +--- + +## 1. 트랙별 문서 + +### [OUTPUT_HANDOVER.md](./OUTPUT_HANDOVER.md) ★ 가장 자주 손댐 +**견적서 / 발전시뮬레이션 / Excel·PDF 출력 트랙.** +견적서·중량·구조도·계통도 등의 Excel/PDF 출력 관련 작업이 가장 많은 영역입니다. 정렬·파일명·이미지·전각문자·도면 비율 등 출력물에서 보이는 거의 모든 것이 여기에 모입니다. + +핵심 키워드: 견적서 / 발전시뮬레이션 / Excel(jxls+POI) / PDF(iText html2pdf) / 정렬 / 파일명 포맷 / 전각문자 / 도면 비율 / ®→Ⓡ→(R) / 폰트 + +--- + +### [DTO_MIDDLE_SERVER_HANDOVER.md](./DTO_MIDDLE_SERVER_HANDOVER.md) +**중간 서버 DTO 변경 트랙.** +QSP API 응답 또는 프론트 요청 스키마가 바뀌면 qcast-api DTO를 통과해야 하므로 DTO 수정이 필수입니다. **통보문만 주고 미루면 다음 날 같은 일을 또 하게 됩니다.** + +핵심 키워드: 중간 서버 / DTO 수정 / moduleTpCd / subModuleTpCd / Cross Mix / addresseeCompanyName 50자 / selectParentAgentInfo + +--- + +### [AUTH_ERROR_HANDOVER.md](./AUTH_ERROR_HANDOVER.md) +**인증 / 로그인 추적 / 에러 저장 API 트랙.** +자동 로그인·로그아웃 동작 추적, 클라이언트 에러를 백엔드에 저장하는 `errSave`/`quotationErrSave` API. + +핵심 키워드: 로그인·로그아웃 추적 / 자동 로그인 추적 / errSave / quotationErrSave / itemList null 체크 + +--- + +### [DOMAIN_HANDOVER.md](./DOMAIN_HANDOVER.md) +**도메인 기능 트랙 (BOM / 권한 / 발전시뮬레이션 API / 우편번호 / 배치 등).** +출력·DTO·인증 외 나머지 도메인 작업 전반. [1xxx] 티켓 시리즈와 새로 추가된 API들. + +핵심 키워드: BOM 부모/자식 / 오브젝트 복사 권한 / 발전시뮬레이션 물건번호 API / 우편번호 하이픈 / 배치(Batch) / Quartz / 인증용량 쿼리 / [1xxx] 티켓 + +--- + +## 2. 같이 봐야 하는 자료 + +### 메모리 인덱스 +- 위치: `~/.claude/projects/-Users-cha-Works-Projects-qcast3-qcast-api/memory/MEMORY.md` +- 작업 시 지켜야 할 사용자(Cha) 작업 스타일이 들어 있습니다. +- **작업 시작 전에 반드시 통독하세요.** + +### 커밋 로그 (자동 누적, 2026-05-06~) +- 위치: `~/.claude/projects/-Users-cha-Works-Projects-qcast3-qcast-api/memory/commit_log.txt` +- 본 저장소의 `.git/hooks/post-commit` 훅이 매 커밋 직후 한 줄 append 합니다. +- 형식: `YYYY-MM-DD HH:MM:SS [브랜치] short(fullhash) 커밋제목` +- 인수인계서/리뷰에 해시를 인용해야 할 때 우선 참조. 푸시 여부는 기록되지 않으므로 별도 확인. + +### 사내 자료 (참고) +- 자바 코드 스타일: `doc/eclipse-java-google-style.xml` +- 자매 프로젝트 인수인계: `qcast-front/docs/HANDOVER_INDEX.md` (프론트 영역의 결정 사항이 백엔드 동작 이해에도 필요할 때 참조) + +--- + +## 3. 인수받은 직후 추천 절차 + +1. **이 인덱스 통독** (5분). +2. **메모리 `MEMORY.md` 통독** — 사용자 스타일 / 중간 서버 원칙 / 커밋 메시지 규칙 (5~10분). +3. **`OUTPUT_HANDOVER.md` 통독** — 가장 자주 손대는 영역 (20~30분). +4. 나머지 3개 트랙 문서는 **필요할 때 펼쳐 보는 레퍼런스** 로 사용. +5. `pom.xml` / `application.yml` / 매퍼 폴더 구조를 한 번 훑어 패키지 구성을 머리에 넣기. + +--- + +## 4. 핵심 원칙 3줄 요약 + +이 프로젝트에서 일을 한다면 적어도 이 3개는 머리에 두세요. + +1. **중간 서버다.** QSP/프론트 스키마가 바뀌었다는 정보를 받으면, 통보만 하지 말고 **그 자리에서 DTO를 직접 수정**. +2. **출력물(Excel/PDF)은 양쪽을 따로 본다.** Excel은 jxls/POI, PDF는 iText html2pdf — 같은 견적서라도 두 곳을 동시에 고쳐야 함. +3. **작업 끝나면 한국어 한 줄 커밋 메시지를 함께 제시**한다. (단, **AI가 직접 `git commit` 하지 말 것**, 사용자가 직접 커밋함.) + +--- + +## 5. 기술 스택 한눈에 + +| 항목 | 내용 | +|------|------| +| 언어 | Java 17 | +| 프레임워크 | Spring Boot 3.3.0, Maven | +| DB 접근 | MyBatis 3.0.3 (XML 매퍼: `src/main/resources/mappers/`) | +| 데이터베이스 | MSSQL Server (DB명: `NEWPVCAD`) | +| 보안/암호화 | Spring Security, Jasypt | +| 외부 API 호출 | OpenFeign | +| Excel | Apache POI / jxls | +| PDF | iText html2pdf | +| API 문서 | Springdoc OpenAPI (Swagger UI) | +| 스케줄러 | Spring Batch + Quartz | + +**패키지 구조**: `com.interplug.qcast.biz.{도메인}` 안에 `Controller / Service / Mapper / DTO`. + +--- + +## 6. 브랜치 전략 + +- `main` — 운영 (PR 대상은 보통 여기) +- `dev` — 개발 통합 +- `dev-deploy` — 개발 서버 배포용 +- `dev_ysCha` — 담당자 개인 작업 브랜치 (현재 체크아웃) +- `hotfix/9999` — 긴급 수정용 +- `feature/{이름이나 번호}` — 기능별 브랜치 + +--- + +끝. diff --git a/doc/OUTPUT_HANDOVER.md b/doc/OUTPUT_HANDOVER.md new file mode 100644 index 00000000..3e2cd42a --- /dev/null +++ b/doc/OUTPUT_HANDOVER.md @@ -0,0 +1,230 @@ +# 견적서 / 발전시뮬레이션 / Excel·PDF 출력 인수인계서 + +> 작성일: 2026-05-06 +> 대상: 후임 AI 엔지니어 또는 개발 관리자 +> 작업 브랜치: `dev_ysCha` + +> **이 문서의 범위**: Excel/PDF 출력물 관련 작업만 다룹니다. +> 다른 트랙은 `HANDOVER_INDEX.md` 참조. + +--- + +## 0. 한 줄 요약 + +견적서·중량 시트·구조도·계통도 등 **출력물에서 보이는 거의 모든 이슈**가 이 트랙으로 들어옵니다. 정렬·파일명·이미지·전각문자·도면 비율·폰트 등이 반복적으로 나오는 영역이며, **Excel(jxls + POI)** 과 **PDF(iText html2pdf)** 두 출력 경로를 동시에 봐야 합니다. + +--- + +## 1. 출력 시스템 개요 + +### 1.1 두 가지 출력 경로 + +| 출력 종류 | 사용 라이브러리 | 템플릿 위치 | +|----------|----------------|-------------| +| **Excel** | Apache POI + jxls | `src/main/resources/template/excel/` | +| **PDF** | iText html2pdf | (HTML 템플릿 → PDF 변환) | + +> **중요**: 같은 견적서라도 Excel과 PDF는 **별도 경로**입니다. 한쪽만 고치면 다른 쪽이 어긋납니다. "정렬을 왼쪽으로" 같은 표시 변경은 양쪽을 함께 손대야 합니다. + +### 1.2 자주 나오는 출력 종류 + +- 견적서 (Quotation) +- 발전시뮬레이션 결과 +- 중량 계산 시트 +- 배치도 / 계통도 / 구조도 + +--- + +## 2. 정렬 (Alignment) 변경 작업 + +### 2.1 한 일 + +| 티켓 | 내용 | 커밋 | +|------|------|------| +| `[1508]` | 견적서 [품명][형번] 가운데 → **왼쪽 정렬** (Excel + PDF 둘 다) | `11d2acb2` | +| `[2133]` | 중량 계산 시트 [품명][모델명] **왼쪽 정렬** (Excel) | `1c22880c` | + +### 2.2 주의 + +- 정렬은 사용자 가독성에 직접 영향. 변경 전 항상 **현 상태 스크린샷**을 떠 두세요. +- Excel만 바꾸고 PDF를 안 바꿔서 보고가 들어오는 경우가 잦습니다. + +--- + +## 3. 파일명 / 다운로드명 포맷 + +### 3.1 한 일 + +| 티켓 | 내용 | 커밋 | +|------|------|------| +| `[2133]` | 다운로드 파일명 포맷에 `planNo` 추가 → `objectNo_planNo_날짜` (견적서/발전시뮬레이션 통일) | `fc6655f9` | + +### 3.2 정책 + +- 파일명 포맷은 견적서·발전시뮬레이션이 **동일 규칙**으로 가야 함 (사용자 요청). +- 새 다운로드를 추가할 때도 같은 패턴을 따르세요. + +--- + +## 4. 전각문자 / unknown 처리 + +### 4.1 한 일 + +| 티켓 | 내용 | 커밋 | +|------|------|------| +| `[1827]` | 엑셀/PDF 다운로드 시 파일명이 `unknown` 으로 나오는 문제 — **전각문자 처리** | `dd2b3e4d` | + +### 4.2 배경 + +- 일본어 전각문자(예: `ABC`)가 파일명/HTTP 헤더에 들어가면 인코딩 문제로 `unknown` 으로 떨어집니다. +- 파일명 인코딩 단계에서 전각 → 반각 변환 또는 URL 인코딩 강화로 처리. + +### 4.3 일반화 가능한 교훈 + +- **이 시스템은 일본어 환경**입니다. 새 출력에서 파일명을 만들 때 항상 전각문자 가능성을 의심하세요. + +--- + +## 5. 등록상표 ® 처리 (카카오 시리즈) + +### 5.1 한 일 + +PDF 출력 시 등록상표 기호 `®` 가 `?` 로 깨지는 문제. 단계별로 변경: + +| 단계 | 내용 | 커밋 | +|------|------|------| +| 1차 | `®` → `Ⓡ` 로 치환 | `47f18b50` | +| 2차 | `®` → `(R)` + PDF 폰트 변경 | `57d52c7c` | +| 3차 | PDF 폰트 복구 | `33555619` | + +### 5.2 교훈 + +- PDF 폰트는 **글리프 지원 범위**를 항상 확인. 안 되는 글리프는 텍스트 치환으로 우회하는 게 안전. +- 폰트를 바꾸면 다른 글자(특히 한자/일본어)가 깨지는 부작용이 생기므로, **폰트 변경은 최후의 수단**. + +--- + +## 6. 도면 비율 / 화질 개선 + +### 6.1 한 일 + +| 티켓 | 내용 | 커밋 | +|------|------|------| +| `[1427]` | 출력한 견적서 도면의 가로/세로 비율이 화면과 다른 문제 (+ 로컬 실행 가능 조정) | `3048076d` | +| `[1470]` | 배치도·계통도·구조도 화질 개선 (cell 이미지 추가) | `4468d3f9`, `1a4becd1`, `512cdd5c` 등 다수 | +| `[1268][1295]` | PDF 출력 세로 → 가로 혼합 | `abf1f91a`, `3612c50d` | + +### 6.2 주의 + +- 화질 개선 작업은 같은 티켓에 여러 커밋이 따라옵니다(이미지 교체 반복). 한 번에 안 되는 게 정상. +- 도면 비율은 **DPI / 픽셀 단위 / mm 단위** 가 섞여 있어 한 곳만 고치면 다른 출력이 어긋납니다. + +--- + +## 7. 견적서 주소 (가장 험난했던 시리즈) — `[1379]` + +### 7.1 무슨 일이 있었나 + +견적서 주소 표시 조건을 바꿨다가 → 원복 → 다시 수정 → 또 원복 → 재수정 의 반복이 있었습니다. + +| 단계 | 커밋 | +|------|------| +| 견적서 주소 수정 | `a430ce64` | +| 견적서 출력 쿼리 수정 | `c28eeee2` | +| 1차 원복 | `1fcd2dac` | +| 2차 원복 | `32e6237f` | +| 3/9 이전 원복 | `35584228` | +| 최초 원복 | `c735263d` | +| 최초 원시 원복 | `150b64b0` | +| 재수정 | `b5e3012d` | + +### 7.2 경고 + +- **이 영역은 손대기 전에 반드시 사용자에게 현재 정책을 재확인**하세요. +- 견적서 주소는 일본 거래처별로 표시 요건이 다를 수 있어 케이스가 많습니다. + +--- + +## 8. 지붕/사 각도 출력 관련 + +| 티켓 | 내용 | 커밋 | +|------|------|------| +| `[1765]` | 출력한 견적서 지붕 면이 이중 표시 | `9dce727f`, `dc7eb154` | +| `[1480]` | 사(斜)의 각도 설정을 "각도"로 했을 때 출력물 문제 | `709b1f1f`, `f407079f`, `3e64d8a2` | + +지붕·각도 관련 출력은 프론트(qcast-front)의 SK(Straight Skeleton) 트랙과 맞물립니다. 프론트의 `SK_MOVEMENT_HANDOVER.md` 도 같이 보세요. + +--- + +## 9. 핵심 파일 / 위치 + +| 위치 | 역할 | +|------|------| +| `src/main/resources/template/excel/` | Excel 템플릿(jxls) | +| `src/main/resources/mappers/` | 출력에 쓰이는 SQL 매퍼 | +| `com.interplug.qcast.biz.quotation.*` | 견적서 도메인 (Controller/Service/Mapper/DTO) | +| `com.interplug.qcast.biz.simulation.*` | 발전시뮬레이션 도메인 | +| (PDF 관련) Service/Util 클래스 | iText html2pdf 변환 로직 | + +> 파일 경로는 패키지 변경 가능성이 있으므로 항상 현재 트리에서 다시 확인하세요. + +--- + +## 10. 인수 직후 권장 절차 + +1. 이 문서 통독. +2. `template/excel/` 폴더 안 템플릿 한두 개 열어 jxls 태그 사용법 확인. +3. 견적서 도메인 Controller → Service → Mapper → 템플릿 한 번 따라가 보기. +4. 로컬에서 견적서 Excel/PDF 한 번 출력해보고 결과 확인. + +--- + +## 11. 미해결 / 추적 중 + +- `[1379]` 견적서 주소 정책은 **여전히 변동 가능성** 있음. 추가 요청 들어오면 신중히. +- 화질 개선(`[1470]`) 은 일단락됐지만 같은 종류의 이미지 깨짐 보고가 또 들어올 수 있음. + +--- + +## 12. 旧見積書書式(EXCEL2) 출력 시트 정책 — 2026-05-06 + +### 12.1 무슨 일이 있었나 +일본에서 다음 요청이 옴. +> 旧見積書書式 선택 시에도 [図面/シミュレーションファイルを含む] 옵션이면 `割付図・系統図`/`架台図`/`発電シミュレーション` 시트를, [架台重量表付き] 옵션이면 `重量算出シート` 시트를 함께 출력해 달라. 결국 **신(통상) 견적서와 동일한 시트 출력 사양**으로 통일 요구. + +기존 코드(`EstimateService.java` `excelDownload()` 내 `EXCEL2` 분기)는 旧見積書書式 선택 시 **schDrawingFlg/schWeightFlg 옵션과 무관하게** 부속 시트를 모두 강제 제거하고 `見積書.` 한 장만 남기고 있었음. + +### 12.2 한 일 +`EstimateService.java` 1915~1925 (수정 후) 영역을 다음 정책으로 단순화. + +- `schDrawingFlg`/`schWeightFlg`로 시트를 골라내는 1단계 분기는 그대로 유지 (= 신 양식과 동일하게 작동). +- 양식 분기는 **견적서 시트만 교체** 책임으로 축소: + - `EXCEL2` (旧書式) → `見積書` 제거 + `見積書.`을 첫 번째 시트로 이동 + - 그 외 (신 양식) → `見積書.` 제거 +- `特異事項` 시트는 두 양식 모두 항상 제거. + +결과적으로 旧書式 출력 순서: +``` +見積書. → 発電シミュレーション → 割付図・系統図 → 架台図 → 重量算出シート → P1 → P2 +``` +(옵션에 따라 일부 시트 빠짐) + +### 12.3 견적서 시트 두 종류 차이 (혼동 주의) +| 항목 | `見積書` (신/통상) | `見積書.` (구) | +|---|---|---| +| 템플릿 sheet 번호 | sheet1.xml | sheet7.xml | +| 영역 | A1:AP46 (가로) | A1:V104 (세로 길쭉) | +| 병합셀 | 61 | 343 | +| 아이템 출력 | 인덱스 고정 (`list[0]~list[26]`) | JXLS each 반복 (`item.itemName`) | + +→ "이름이 거의 같으니 같은 시트 아니냐"라는 인상과 달리 **레이아웃·바인딩 키가 완전히 다른 별개 양식**. 한쪽 손볼 때 다른 쪽도 자동 적용된다고 착각하지 말 것. + +### 12.4 후속 확인 포인트 (다음 작업 시 체크) +1. **프론트(qcast-front)** 가 旧書式 선택 시 `schDrawingFlg`/`schWeightFlg` 값을 함께 전송하는지 확인. 안 보내면 백엔드만 고쳐도 효과 없음. +2. `EstimateRequest.schWeightFlg` 필드는 정의되어 있으나 분기에서는 `schDrawingFlg.indexOf("4")`만 보고 있음. 프론트가 별도 키로 보내는 경우 백엔드 분기 조건도 함께 손봐야 함. +3. 旧書式 템플릿(`excel_download_quotation_detail_template2.xlsx`) 시트 구성은 신 양식 템플릿(`excel_download_quotation_detail_template.xlsx`)과 동일. 旧書式 전용으로 부속 시트 디자인을 다르게 가져갈 일이 생기면 템플릿 자체를 분리해야 함. + +### 12.5 활성 시트 +`setSheetOrder("見積書.", 0)`로 옮겨도 POI는 active sheet 인덱스를 자동 보정하지 않을 수 있음. 열었을 때 다른 시트가 먼저 보이면 `workbook.setActiveSheet(0)` 한 줄을 추가. + +끝. diff --git a/src/main/java/com/interplug/qcast/biz/estimate/EstimateService.java b/src/main/java/com/interplug/qcast/biz/estimate/EstimateService.java index 457d6167..02de114a 100644 --- a/src/main/java/com/interplug/qcast/biz/estimate/EstimateService.java +++ b/src/main/java/com/interplug/qcast/biz/estimate/EstimateService.java @@ -1912,19 +1912,20 @@ public class EstimateService { safeRemoveSheet(workbook, "特異事項"); } + // 2026-05-06 신/구 견적서 양식에 따른 견적서 시트만 교체. + // schDrawingFlg / schWeightFlg(위쪽 분기)로 선택된 부속 시트 + // (割付図・系統図 / 架台図 / 発電シミュレーション / 重量算出シート 등)는 그대로 유지한다. if ("EXCEL2".equals(estimateRequest.getSchDownload())) { + // 旧見積書書式: 신 양식 견적서 시트(見積書) 제거 후, 구 양식(見積書.)을 맨 앞으로 이동 safeRemoveSheet(workbook, "見積書"); - safeRemoveSheet(workbook, "特異事項"); - safeRemoveSheet(workbook, "発電シミュレーション"); - safeRemoveSheet(workbook, "割付図・系統図"); - safeRemoveSheet(workbook, "架台図"); - safeRemoveSheet(workbook, "重量算出シート"); - safeRemoveSheet(workbook, "P1"); - safeRemoveSheet(workbook, "P2"); - }else{ - safeRemoveSheet(workbook,"見積書."); - safeRemoveSheet(workbook, "特異事項"); + if (workbook.getSheetIndex("見積書.") > 0) { + workbook.setSheetOrder("見積書.", 0); + } + } else { + // 신(통상) 견적서 양식: 구 양식 견적서 시트(見積書.)만 제거 + safeRemoveSheet(workbook, "見積書."); } + safeRemoveSheet(workbook, "特異事項"); applyExcelNumberFormat(workbook); // applyExcelFixedNumberFormat(workbook, Arrays.asList("H25", "C12"), "#,##0.00");