# 청구서 발급 내역 — 인앱 미리보기와 다운로드

## Problem

청구서 발급 내역 상세에서 '보기'를 누르면 서비스가 PDF를 **브라우저 내장 뷰어에 넘겨버린다.** 앱(WebView)에서는 `bridge_openExternalBrowser`로 외부 브라우저가 열려 **사용자가 앱을 벗어나고**, 돌아오려면 앱을 다시 찾아야 한다. PC 웹에서도 새 탭의 탭 제목이 `merged-pdf-view`로 떠서 어떤 청구서인지 화면상 알 수 없다.

받아두는 수단도 사실상 없다. 브라우저 뷰어에 다운로드 아이콘이 있긴 하지만 서비스가 제공한 기능이 아니라 발견되지 않고, 앱 외부 브라우저로 넘어간 경우 저장 위치도 사용자 통제 밖이다. 청구서는 보험사에 제출하는 서류이므로 **보관·재제출 수요가 상시로 있다.**

방치 비용: 청구서를 다시 받으려면 발급 플로우를 처음부터 다시 타야 한다고 오인하게 되고(실제로는 발급 내역에 남아 있다), 이는 고객센터 문의와 중복 발급으로 이어진다.

## Evidence

근거 정본: `.claude/_meta/2026-08-26-invoice-detail-pdf-preview-download.md` (2026-08-26, 코드 sha `06ddbe69`/`92ab9697` 기준 실측)

- **E1** — 로컬에서 청구서를 실제 발급(ivhId=690)해 '보기'를 눌러 관찰: 새 탭이 열리고 브라우저 내장 PDF 뷰어가 3페이지 문서를 표시한다. 서비스 UI는 전혀 개입하지 않는다.
- **E2** — 앱 분기는 `bridge_openExternalBrowser`로 URL을 외부 브라우저에 넘긴다. 앱을 벗어나는 동작이 코드에 명시돼 있다.
- **E5** — 기존 출력 플로우의 미리보기를 390x844 모바일 뷰포트에서 실측: 청구서 한 장이 화면에 온전히 들어온다. **모바일 미리보기는 새 반응형 작업 없이 성립한다.**
- **E6** — 개발 DB 실측: 청구서 발급 내역 437건 중 파일이 남아 있는 것은 206건(47.1%). 나머지는 '보기' 버튼 자체가 뜨지 않고 `-`만 보이며 **사용자에게 이유가 전달되지 않는다.**
- **P1** — 인앱 미리보기는 2026-08-25에 도입됐다가 같은 날 철거됐다. 사용자 확인 결과 **철거 사유는 "당일에 처리하기엔 시간 소요가 클 것 같아 심층 분석 후 진행하기 위함"** — 구조적 장애가 아니라 일정상 연기였다. 이 PRD가 그 "심층 분석 후 진행"에 해당한다.

## Users

- **Primary**: 보험금 청구를 대행·지원하는 **보험 설계사**. 고객의 청구서를 발급한 뒤, 보험사 제출 전에 내용을 확인하고 파일로 보관하거나 고객에게 전달해야 하는 시점에 이 기능이 필요하다. 앱과 PC 웹을 모두 쓴다.
- **Not for**: 보험사 담당자 및 관리자. 관리자 화면은 별도 저장소의 자체 뷰어를 쓰며 이 변경의 대상이 아니다.

## Hypothesis

We believe **앱을 벗어나지 않는 청구서 미리보기 화면과 명시적인 다운로드 버튼**이 **"확인하고 보관한다"는 동작을 서비스 안에서 끝나게 함**으로써 **청구서를 발급한 보험 설계사**의 문제를 해결한다.

We'll know we're right when **'보기' 진입 후 외부 브라우저 이탈이 0이 되고, 발급 내역 상세에서 다운로드가 실제로 발생한다** — 즉 지금은 측정조차 불가능한(서비스 밖에서 일어나므로) 동작이 서비스 안의 관측 가능한 이벤트가 된다.

## Success Metrics

| Metric | Target | How measured |
|---|---|---|
| '보기' 진입 시 외부 브라우저 이탈 | 0건 (앱·웹 모두) | 미리보기 라우트 진입이 곧 이탈 없음 — 코드 경로상 보장, 실기기 1회 확인 |
| 다운로드 성공률 (파일이 존재하는 건 기준) | 100% | 웹·앱 각 경로 수동 검증. 실패는 사용자에게 보이는 오류로 처리 |
| 미리보기 표시 성공률 | 100% (파일 존재 건) | PC·모바일 뷰포트 실측 |
| 파일 없음 상태의 설명 도달 | `-` 대신 사유 문구 노출 | 현재 52.9%가 무설명 `-` (E6) |

> 제품 분석 이벤트(GA 등) 계측은 이 저장소에 확립된 관행이 없어 `TBD — 계측 도입 시 별도 검증`으로 둔다. 위 지표는 수동 검증 기준이다.

## Scope

**MVP** — 발급 내역 상세의 '보기'가 **서비스 안의 전체 화면 미리보기**로 진입하고, 그 화면과 상세 표 양쪽에서 **다운로드**할 수 있다.

- 미리보기는 PC와 모바일 모두에서 청구서를 읽을 수 있게 표시한다.
- 청구서가 여러 보험사로 발급된 경우 **미리보기 안에서 이전/다음으로 보험사 간 이동**이 가능하다.
- 다운로드는 **화면당 버튼 하나**로, **그 보험사에 실제 발송된 파일(청구서 + 첨부)** 을 내려준다. 상세 표는 행마다 `다운로드`, 미리보기는 액션 바에 `청구서 다운로드` 하나다.
- **화면 문구와 파일명에 '통합본'을 쓰지 않는다.** 서버는 병합본을 그 이름으로 부르지만(`invoice.service.ts`), 사용자에게 '통합본'은 "모든 보험사 청구서를 묶은 한 파일"을 뜻한다. 보험사 한 곳의 발송본에 그 말을 붙이면 뜻이 어긋난다 — 내부 용어로만 남긴다 (사용자 결정 2026-08-26).
- 앱(WebView)에서도 앱을 벗어나지 않으며, 저장은 앱의 기존 파일 저장 경로를 쓴다.
- 파일이 없는 건은 미리보기·다운로드 모두 제공되지 않으며, **왜 안 되는지 사용자에게 알린다.**

**Out of scope**

- **기존 '새 창 + 브라우저 뷰어' 서버 경로 제거** — 사용자 결정에 따라 남겨둔다. 문제 발생 시 프런트 한 줄로 되돌릴 수 있는 상태를 유지하는 것이 목적.
- **청구서 출력 플로우(발급 5단계)의 미리보기 교체** — 그 화면은 재출력 경고와 팩스 발송이 얽혀 있어 회귀 위험이 크다. 이번 변경은 발급 내역 쪽만 건드린다.
- **첨부파일 개별 다운로드** — 발송본 하나로 충분한지 먼저 확인한 뒤 판단한다.
- **청구서 원본만(첨부 제외) 받는 경로** — 초안에는 있었으나 **사용자 결정으로 제거**했다(2026-08-26). 행마다 버튼이 둘이면 그중 하나를 '통합본'이라 부르게 되고, 그 말이 사용자가 아는 뜻과 어긋난다. 필요해지면 되살린다.
- **전체 일괄 다운로드(모든 보험사 청구서를 한 파일로)** — 용어 정정 과정에서 후보로 검토했고 **사용자가 불필요하다고 판정**했다(2026-08-26). 구현하지 않는다.
- **파일 업로드 실패가 조용히 넘어가는 서버 동작 수정** — 청구서 생성이 파일 저장에 실패해도 성공 응답을 준다(E6). 별도 저장소의 서버 변경이며, 여기서 같이 고치면 이번 변경의 회귀 판정이 흐려진다.
- **서버가 내려주는 파일명 결함 수정** — 통합본 파일명에 확장자가 두 번 붙는다. 서버 저장소 변경이라 분리하고, 이번 변경은 파일명을 화면에서 직접 구성한다.
- **관리자 화면과의 뷰어 공통화** — 별개 저장소, 공유 패키지 없음.

## Delivery Milestones

<!-- Business outcomes, not engineering tasks. /mccp:plan turns each into a plan. -->
<!-- Status: pending | in-progress | complete -->

| # | Milestone | Outcome | Status | Plan |
|---|---|---|---|---|
| 1 | 인앱 미리보기 + 다운로드 | 발급 내역 상세에서 '보기'가 서비스 안 전체 화면으로 열리고, 보험사 간 이동이 되며, 그 보험사에 발송된 파일을 버튼 하나로 받을 수 있다. 파일이 없으면 이유가 보인다 | in-progress | `.claude/plans/invoice-pdf-preview-download.plan.md` |

## Open Questions

- [x] ~~인앱 미리보기가 2026-08-25에 철거된 사유~~ — **해소.** 시간 부족에 의한 연기였으며 구조적 장애가 아니다 (사용자 확인 2026-08-26)
- [ ] 앱 WebView 실기기에서 인앱 PDF 렌더가 문제없이 도는가 — 로컬 브라우저로는 판정 불가. **MVP 완료 조건에 실기기 확인 1회를 포함한다**
- [x] ~~통합본과 청구서 원본 중 실제로 어느 쪽이 쓰이는가~~ — **관찰 전에 해소.** 두 버튼 구성 자체가 '통합본'이라는 말을 보험사 단위에 붙이게 만들어 뜻이 어긋났다. **발송본 하나로 정리**하고 원본 경로는 제거했다 (사용자 결정 2026-08-26)
- [ ] 첨부를 뺀 청구서만 필요한 상황이 실제로 있는가 — 발송본 하나로 운영한 뒤 문의가 들어오면 되살린다
- [ ] 파일이 없는 52.9%(개발 DB 기준)에 어떤 문구를 보여줄 것인가 — "파일 없음"으로 충분한지, 재발급 유도가 필요한지

## Risks

| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
| 앱 WebView에서 PDF 렌더가 느리거나 실패 | 중 | 높음 — 앱 사용자에게 기능이 무의미 | 기존 브라우저 뷰어 경로를 남겨둔다(사용자 결정). 실기기 확인을 완료 조건에 포함 |
| 모바일에서 다중 페이지 문서의 메모리 부담 | 중 | 중 — 저사양 기기에서 렌더 지연 | 화면 폭에 맞춰 렌더한다. 기존 화면은 화면 폭과 무관하게 크게 그려 모바일에서 3.3배 과다 렌더하고 있다(E5) — 이번 화면은 그 비용을 지지 않는다 |
| 파일이 없는 건이 절반에 달해 기능 체감이 낮음 | 높음 | 중 | 원인(서버 저장 실패의 무음 처리)은 범위 밖이지만, **사용자에게 이유를 알리는 것**은 MVP에 포함한다 |
| 같은 이유로 다시 철거 | 낮음 | 중 | 철거 사유가 시간 부족으로 확인됐고(P1), 이번에는 조사·계획을 선행한다. 기존 경로 유지로 롤백 비용도 낮다 |
| 다운로드 파일명이 서버 결함으로 어색함 | 확실 | 낮음 | 화면에서 파일명을 직접 구성한다. 서버 수정은 별도 과제로 분리 |

---
*Status: DRAFT — requirements only. Implementation planning pending via /mccp:plan.*
*Co-created with user on 2026-08-26.*
