# 제품검사 문서 시스템 구축 계획 > **작성일**: 2026-03-06 > **목적**: 제품검사 성적서(template ID 65 보완) + 제품검사 요청서(신규 template)를 mng 양식 기반 동적 렌더링 + rendered_html 스냅샷 구조로 구현 > **기준 문서**: `docs/dev/dev_plans/document-system-master.md`, `docs/dev/dev_plans/document-snapshot-architecture-plan.md` > **상태**: 확정 (2026-03-06) --- ## 현재 진행 상태 | 항목 | 내용 | |------|------| | **마지막 완료 작업** | Phase 3 전체 완료 (통합 테스트 + fallback 검증 + 호환성 확인) | | **다음 작업** | 완료 — 추후 syncRequestDocument 기존 데이터 수동 실행 고려 | | **진행률** | 12/12 (100%) | | **마지막 업데이트** | 2026-03-06 | --- ## 1. 개요 ### 1.1 배경 현재 제품검사 관련 문서가 **두 가지 방식**으로 혼재: - **하드코딩 React 컴포넌트** (`InspectionReportDocument.tsx`, `InspectionRequestDocument.tsx`) — mockData 기반 변환, 문서 저장 없음 - **양식 기반 동적 렌더링** (`FqcDocumentContent.tsx`) — template ID 65 기반, 일부 구현됨 (4컬럼 단순 버전) 기존 중간검사/수입검사/작업일지는 이미 **mng 양식 등록 → React 동적 렌더링 → rendered_html 스냅샷** 패턴으로 동작 중. 제품검사도 동일 패턴으로 통일해야 함. ### 1.2 핵심 원칙 ``` 1. mng에서 양식(template) 등록/관리 → React에서 동적 렌더링 2. 저장 시 rendered_html 스냅샷 함께 저장 3. mng/히스토리에서는 스냅샷 출력 (렌더링 로직 0) 4. 기존 하드코딩 컴포넌트는 fallback으로 유지 → 점진적 전환 ``` ### 1.3 변경 승인 정책 | 분류 | 예시 | 승인 | |------|------|------| | 즉시 가능 | mng 양식 항목 추가/수정, React 컴포넌트 수정 | 불필요 | | 컨펌 필요 | API 저장 로직 변경, 새 API 엔드포인트, DB 마이그레이션 | **필수** | | 금지 | documents 테이블 구조 변경, 기존 기능 삭제 | 별도 협의 | ### 1.4 준수 규칙 - `docs/dev/standards/quality-checklist.md` - 품질 체크리스트 - `docs/dev/standards/git-conventions.md` - Git 커밋 컨벤션 - `docs/features/documents/README.md` - 문서관리 시스템 스펙 --- ## 2. 현재 시스템 분석 ### 2.1 기존 패턴 (참조 모델: 중간검사) ``` [mng] DocumentTemplate (양식 등록) ├── approval_lines: 결재선 (작성/검토/승인) ├── basic_fields: 기본필드 (납품명, 제품명, 발주처 등) ├── sections: 섹션 (검사기준서 이미지, 검사 DATA) │ └── items: 검사항목 (항목명, 기준, 측정유형) └── columns: 컬럼 정의 (NO, 검사항목, 검사기준, 판정) [React] 동적 렌더링 ├── API로 template 조회 (GET /v1/document-templates/{id}) ├── TemplateInspectionContent / FqcDocumentContent 등으로 렌더링 ├── 편집 모드: 판정 토글, 측정값 입력 └── 저장 시 rendered_html 캡처 → API 전송 [API] 저장 ├── document_data (EAV): 구조화 데이터 (검색/통계용) └── documents.rendered_html: HTML 스냅샷 (출력용) [MNG] 출력 ├── rendered_html 있으면 → 그대로 출력 ({!! $document->rendered_html !!}) └── 없으면 → 기존 동적 렌더링 fallback ``` ### 2.2 제품검사 성적서 현재 상태 (template ID 65) **mng 양식 (ID 65)**: Phase 5.2에서 시더로 생성됨 - 시더 위치: `mng/database/seeders/ProductInspectionTemplateSeeder.php` (tenant_id=287 하드코딩) - 결재 3인 (작성/검토/승인) - 기본필드 7개 (납품명, 제품명, 발주처, LOT NO, 로트크기, 검사일자, 검사자) - 섹션 2개 (검사기준서 이미지, 검사 DATA) - 검사항목 11개 (설치 후 최종검사, 모두 visual/checkbox) - **컬럼 4개** (NO, 검사항목, 검사기준, 판정) ← **보완 대상** **React 구현 현황**: - `FqcDocumentContent.tsx` — template 기반 동적 렌더링 (읽기/편집 모드), **현재 4컬럼** - `InspectionReportDocument.tsx` — 하드코딩 fallback (**8컬럼 상세 버전**) - `InspectionReportModal.tsx` — 듀얼 모드 (useFqcMode: fqcDocumentMap 존재 여부로 판단) **문제점**: 1. 하드코딩 버전이 훨씬 상세함 (8컬럼, rowSpan 병합, 측정값, 검사방법/주기) 2. template ID 65는 4컬럼으로 단순 → 하드코딩 수준으로 보완 필요 3. rendered_html 스냅샷 저장이 FQC 문서에 아직 미적용 ### 2.3 제품검사 요청서 현재 상태 **mng 양식**: 없음 (신규 생성 필요) **React 구현**: - `InspectionRequestDocument.tsx` — 하드코딩 컴포넌트 (완성도 높음) - `InspectionRequestModal.tsx` — DocumentViewer + 하드코딩 컴포넌트 - 데이터 변환: `buildRequestDocumentData()` (mockData.ts:398-423) **구조**: - 결재라인 (작성/승인) - 기본정보 (수주처, 업체명, 담당자, 수주번호, 연락처, 현장명, 납품일, 현장주소, 총개소, 접수일, 검사방문요청일) - 입력사항 4개 섹션 (건축공사장, 자재유통업자, 공사시공자, 공사감리자) - 검사 요청 시 필독 (고정 텍스트) - 검사대상 사전 고지 정보 테이블 (No, 층수, 부호, 발주규격 가로/세로, 시공후규격 가로/세로, 변경사유) --- ## 3. 대상 범위 ### Phase 1: 제품검사 성적서 (template ID 65) 보완 | # | 작업 항목 | 상태 | 비고 | |---|----------|:----:|------| | 1.1 | mng template ID 65 양식 보완 — section_items 교체 + template columns 2개로 재정의 | ✅ | items 11→22개, columns 4→2개(측정값+판정) | | 1.2 | FqcDocumentContent.tsx 동적 렌더링 보완 + 프론트 타입 수정 | ✅ | 8컬럼 시각 레이아웃 + rowSpan 복합키 병합 + measurement_type=none 처리 | | 1.3 | rendered_html 스냅샷 저장 적용 | ✅ | saveFqcDocument에 renderedHtml 파라미터 추가 + contentWrapperRef 준비 | | 1.4 | InspectionReportModal에서 양식 모드 기본값 전환 | ✅ | FQC 모드 우선, template 로드 실패 시 legacy fallback | ### Phase 2: 제품검사 요청서 (신규 template) | # | 작업 항목 | 상태 | 비고 | |---|----------|:----:|------| | 2.1 | mng에 제품검사 요청서 template 신규 등록 (시더) | ✅ | template ID 66, description 컬럼 추가 | | 2.2 | React 동적 렌더링 컴포넌트 구현 | ✅ | FqcRequestDocumentContent, InspectionRequestModal FQC 모드 | | 2.3 | 요청서 문서 생성 API 연동 | ✅ | syncRequestDocument(), store/update/attachOrders 연동 | | 2.4 | rendered_html 스냅샷 저장 적용 (Lazy Snapshot) | ✅ | patchDocumentSnapshot, contentWrapperRef 캡처 | ### Phase 3: 통합 및 정리 | # | 작업 항목 | 상태 | 비고 | |---|----------|:----:|------| | 3.1 | InspectionDetail.tsx 모달 연동 통합 테스트 | ✅ | 요청서(legacy fallback) + 성적서(FQC 8컬럼) 정상 | | 3.2 | mng 문서 보기에서 스냅샷 출력 확인 | ✅ | show.blade.php rendered_html 우선 출력 패턴 코드 검증 | | 3.3 | 하드코딩 컴포넌트 fallback 유지 확인 | ✅ | requestDocumentId 없으면 legacy fallback 정상 동작 | | 3.4 | 기존 FQC 데이터 호환성 확인 | ✅ | 기존 EAV 데이터(basic_fields) 정상 표시 | --- ## 4. 작업 절차 ### 4.1 단계별 절차 ``` Phase 1: 제품검사 성적서 보완 ├── Step 1.1: mng template ID 65 시더 보완 │ ├── template columns 재정의: 4→2개 (측정값+판정만, 나머지는 section_item 필드) │ ├── section_items 교체: 11개→22개 (섹션 5.1.2의 상세 항목 데이터) │ ├── cleanupExisting() → updateOrCreate() 패턴으로 ID 안정성 확보 (섹션 5.1.9) │ ├── mng에서 시더 실행 후 미리보기 확인 │ └── 파일: mng/database/seeders/ProductInspectionTemplateSeeder.php │ ├── Step 1.2: FqcDocumentContent.tsx 8컬럼 렌더링 보완 │ ├── [선행] fqcActions.ts 타입 수정: TemplateItemApi, FqcTemplateItem에 category/method/frequency/measurement_type 추가 (섹션 5.1.7) │ ├── [선행] transformTemplate()에서 새 필드 매핑 추가 │ ├── 8컬럼 시각 레이아웃: 1~6 section_item 읽기전용 + 7~8 template column 편집 (섹션 5.1.1) │ ├── rowSpan 복합키 병합: category 단독 + method+frequency 복합키 (섹션 5.1.3) │ ├── 측정값 입력 분기: numeric→input, checkbox→양호/불량, none→비활성 (섹션 5.1.8) │ ├── 종합판정: measurement_type='none' 제외하고 계산 (섹션 5.1.8) │ └── 파일: fqcActions.ts + FqcDocumentContent.tsx │ ├── Step 1.3: rendered_html 스냅샷 적용 │ ├── FqcDocumentContent의 wrapper div에 ref 연결 │ ├── saveFqcDocument 호출 시 contentWrapperRef.current.innerHTML 캡처 │ ├── fqcActions.ts의 saveFqcDocument()에 rendered_html 파라미터 추가 │ ├── API: POST /v1/documents/upsert → DocumentService에서 rendered_html 저장 (이미 지원됨) │ └── 파일: fqcActions.ts, InspectionReportModal.tsx │ └── Step 1.4: 양식 모드 기본값 전환 ├── InspectionReportModal.tsx에서 FQC 모드 우선 표시 ├── fqcDocumentMap 없어도 template 로드 시도 └── 레거시 모드 fallback 유지 Phase 2: 제품검사 요청서 신규 ├── Step 2.1: mng template 등록 (시더 방식 확정) │ ├── 양식 구조 설계 (섹션 5.2의 상세 구조 참조) │ ├── 시더 작성 (ProductInspectionRequestTemplateSeeder) │ ├── mng 시더 실행 후 미리보기 확인 │ └── 파일: mng/database/seeders/ProductInspectionRequestTemplateSeeder.php (신규) │ ├── Step 2.2: React 동적 렌더링 │ ├── FqcRequestDocumentContent.tsx 신규 생성 (또는 기존 FqcDocumentContent 확장) │ ├── quality_documents 데이터 → 문서 데이터 매핑 │ ├── 사전 고지 정보 테이블 (quality_document_locations → 테이블 행) │ ├── InspectionRequestModal에 양식 기반 모드 추가 │ └── 파일: react/.../documents/ 디렉토리 │ ├── Step 2.3: API 연동 (quality_document 생성 시 자동 생성 확정) │ ├── QualityDocumentService::store()에서 요청서 document 자동 생성 │ ├── 기본필드 자동 매핑 (quality_document → document basic_fields) │ ├── 개소 데이터 자동 매핑 (quality_document_locations → 사전고지 테이블) │ └── 파일: api/app/Services/QualityDocumentService.php │ └── Step 2.4: rendered_html 스냅샷 (Lazy Snapshot 패턴) ├── 요청서는 readonly 문서 → "입력 시 저장" 캡처 불가 ├── Lazy Snapshot 적용: 요청서 최초 조회 시 rendered_html 없으면 자동 캡처 ├── captureRenderedHtml 유틸리티 활용 (react/src/lib/utils/capture-rendered-html.tsx) ├── 또는 contentWrapperRef.innerHTML로 렌더링 완료 후 캡처 ├── 백그라운드 API 호출로 rendered_html 저장 (PATCH /v1/documents/{id} 또는 upsert) └── 참조: document-snapshot-architecture-plan.md 캡처 원칙 B Phase 3: 통합 및 정리 ├── InspectionDetail.tsx에서 모달 연동 통합 테스트 ├── mng show.blade.php에서 스냅샷 출력 확인 ├── 하드코딩 fallback 정상 동작 확인 └── 기존 FQC 데이터 호환성 확인 ``` --- ## 5. 상세 작업 내용 ### 5.1 제품검사 성적서 양식 보완 상세 (Phase 1.1) #### 5.1.1 컬럼 구조 — Template columns vs Display columns (핵심 구분) > **핵심**: Template columns는 EAV 편집 가능 필드만 정의. 8컬럼 시각 레이아웃은 React에서 section_item 필드 + template columns를 조합하여 렌더링. **Template columns = EAV 데이터 저장용 (편집 가능 필드만)** 현재 4컬럼 중 실제 EAV로 저장하는 것은 `판정`(select) 하나뿐. 나머지 3개(NO, 검사항목, 검사기준)는 section_item 필드의 읽기 전용 표시. **목표 template columns (2~3개)** — 시더에 반영: | 컬럼 | label | column_type | width | 비고 | |------|-------|-------------|-------|------| | 1 | 측정값 | measurement | 70px | measurement_type별 입력 (numeric/checkbox) | | 2 | 판정 | select | 50px | 적합/부적합 | > `measurement_type='none'`인 항목(작동테스트)은 측정값/판정 컬럼 모두 비활성. **8컬럼 시각 레이아웃 (React 렌더링)**: | 시각 컬럼 | 데이터 소스 | 편집 가능 | |-----------|-----------|:---------:| | 1. No. | section_item 순번 (category별 그룹 번호) | ❌ | | 2. 검사항목 | section_item.`category` | ❌ | | 3. 세부항목 | section_item.`item` | ❌ | | 4. 검사기준 | section_item.`standard` | ❌ | | 5. 검사방법 | section_item.`method` | ❌ | | 6. 검사주기 | section_item.`frequency` | ❌ | | 7. 측정값 | template column `measurement` → document_data EAV | ✅ | | 8. 판정 | template column `select` → document_data EAV | ✅ | > 1~6번은 section_item의 읽기 전용 필드를 React에서 직접 렌더링. 7~8번만 template columns로 정의하여 document_data에 EAV 저장. #### 5.1.2 검사항목 데이터 (시더에 반영할 section_items) **방안 C 확정** — 하드코딩 `mockReportInspectionItems` (mockData.ts:426-458)을 template section_items로 이관. `DocumentTemplateSectionItem` 필드 매핑: | mock 필드 | DB 필드 | 설명 | |-----------|---------|------| | category | `category` | 검사 그룹명 (겉모양, 모터, 치수 등) | | subCategory | `item` | 세부 항목명 (가공상태, 길이 등) | | criteria | `standard` | 검사 기준 | | method | `method` | 검사 방법 | | frequency | `frequency` | 검사 주기 | | measurement_type | `measurement_type` | checkbox/numeric/none | **시더 section_items 데이터** (검사 DATA 섹션): ```php // 섹션: 검사 DATA $items = [ // === 1. 겉모양 (5개 세부항목, method/freq 동일: 육안검사/전수검사) === ['category' => '겉모양', 'item' => '가공상태', 'standard' => '흠, 녹 등 확인', 'method' => '육안검사', 'frequency' => '전수검사', 'measurement_type' => 'checkbox'], ['category' => '겉모양', 'item' => '재봉상태', 'standard' => '이중 재봉 상태', 'method' => '육안검사', 'frequency' => '전수검사', 'measurement_type' => 'checkbox'], ['category' => '겉모양', 'item' => '조립상태', 'standard' => '개폐 작동', 'method' => '육안검사', 'frequency' => '전수검사', 'measurement_type' => 'checkbox'], ['category' => '겉모양', 'item' => '연기차단재', 'standard' => '접착력 확인', 'method' => '육안검사', 'frequency' => '전수검사', 'measurement_type' => 'checkbox'], ['category' => '겉모양', 'item' => '하단마감재', 'standard' => '접착력 확인', 'method' => '육안검사', 'frequency' => '전수검사', 'measurement_type' => 'checkbox'], // === 2. 모터 === ['category' => '모터', 'item' => '-', 'standard' => '인정제품과 동일사양', 'method' => '육안검사', 'frequency' => '전수검사', 'measurement_type' => 'checkbox'], // === 3. 재질 === ['category' => '재질', 'item' => '-', 'standard' => 'WY-SC780 인쇄상태 확인', 'method' => '육안검사', 'frequency' => '전수검사', 'measurement_type' => 'checkbox'], // === 4. 치수(오픈사이즈) (4개 세부항목, method: 체크검사) === ['category' => '치수(오픈사이즈)', 'item' => '길이(W)', 'standard' => '수주치수 +-30mm', 'method' => '체크검사', 'frequency' => '전수검사', 'measurement_type' => 'numeric'], ['category' => '치수(오픈사이즈)', 'item' => '높이(H)', 'standard' => '수주치수 +-20mm', 'method' => '체크검사', 'frequency' => '전수검사', 'measurement_type' => 'numeric'], ['category' => '치수(오픈사이즈)', 'item' => '가이드레일 간격', 'standard' => '수주치수 +-10mm', 'method' => '체크검사', 'frequency' => '전수검사', 'measurement_type' => 'numeric'], ['category' => '치수(오픈사이즈)', 'item' => '하단막대 간격', 'standard' => '수주치수 +-10mm', 'method' => '체크검사', 'frequency' => '전수검사', 'measurement_type' => 'numeric'], // === 5. 작동테스트 === ['category' => '작동테스트', 'item' => '개폐성능', 'standard' => '작동 유무 확인', 'method' => '', 'frequency' => '', 'measurement_type' => 'none'], // === 6. 내화시험 (3개 세부항목, method: 공인시험기관, freq: 1회/5년) === ['category' => '내화시험', 'item' => '비차열 1시간', 'standard' => '균열게이지 불관통', 'method' => '공인시험기관', 'frequency' => '1회/5년', 'measurement_type' => 'checkbox'], ['category' => '내화시험', 'item' => '차열성', 'standard' => '이면온도 상승제한', 'method' => '공인시험기관', 'frequency' => '1회/5년', 'measurement_type' => 'checkbox'], ['category' => '내화시험', 'item' => '비손상성', 'standard' => '화염 비관통', 'method' => '공인시험기관', 'frequency' => '1회/5년', 'measurement_type' => 'checkbox'], // === 7. 차연시험 === ['category' => '차연시험', 'item' => '공기누설량', 'standard' => '25Pa 기준 0.9m3/min.m2 이하', 'method' => '공인시험기관', 'frequency' => '1회/5년', 'measurement_type' => 'checkbox'], // === 8. 개폐시험 (5개 세부항목) === ['category' => '개폐시험', 'item' => '개폐 작동', 'standard' => '10회 이상 개폐작동 이상없음', 'method' => '공인시험기관', 'frequency' => '1회/5년', 'measurement_type' => 'checkbox'], ['category' => '개폐시험', 'item' => '폐쇄 시간', 'standard' => '5초 이내 완전 폐쇄', 'method' => '공인시험기관', 'frequency' => '1회/5년', 'measurement_type' => 'checkbox'], ['category' => '개폐시험', 'item' => '평균속도(상한)', 'standard' => '0.3m/sec 이하', 'method' => '공인시험기관', 'frequency' => '1회/5년', 'measurement_type' => 'checkbox'], ['category' => '개폐시험', 'item' => '평균속도(하한)', 'standard' => '0.07m/sec 이상', 'method' => '공인시험기관', 'frequency' => '1회/5년', 'measurement_type' => 'checkbox'], ['category' => '개폐시험', 'item' => '과부하', 'standard' => '과부하 작동 이상없음', 'method' => '공인시험기관', 'frequency' => '1회/5년', 'measurement_type' => 'checkbox'], // === 9. 내충격시험 === ['category' => '내충격시험', 'item' => '-', 'standard' => '낙하높이 기준 이상없음', 'method' => '공인시험기관', 'frequency' => '1회/5년', 'measurement_type' => 'checkbox'], ]; ``` #### 5.1.3 rowSpan 자동 병합 로직 (React 구현) 하드코딩 `InspectionReportDocument.tsx`의 `buildCoverageMap()` (라인 42-57) 패턴을 **template 데이터 기반으로 자동화**. > **주의**: `method`와 `frequency`는 **복합 키**(method+frequency)로 병합해야 함. > 단일 필드 비교 시 frequency='전수검사'가 7+4=11행 연속으로 잘못 병합됨. > 실제로는 method가 다르면 (육안검사 7행 vs 체크검사 4행) 별도 그룹이어야 함. ```typescript // category는 단일 필드 병합 (같은 category 연속이면 병합) function buildFieldRowSpan(items: SectionItem[], field: 'category') { const spans: Map = new Map(); const covered: Set = new Set(); let i = 0; while (i < items.length) { const value = items[i][field]; if (!value) { i++; continue; } let span = 1; while (i + span < items.length && items[i + span][field] === value) { covered.add(i + span); span++; } if (span > 1) spans.set(i, span); i += span; } return { spans, covered }; } // method+frequency는 복합 키 병합 function buildCompositeRowSpan(items: SectionItem[]) { const spans: Map = new Map(); const covered: Set = new Set(); let i = 0; while (i < items.length) { const key = `${items[i].method || ''}|${items[i].frequency || ''}`; if (!items[i].method && !items[i].frequency) { i++; continue; } let span = 1; while (i + span < items.length) { const nextKey = `${items[i + span].method || ''}|${items[i + span].frequency || ''}`; if (nextKey !== key) break; covered.add(i + span); span++; } if (span > 1) spans.set(i, span); i += span; } return { spans, covered }; } // 사용: 렌더링 시 const categoryCoverage = buildFieldRowSpan(sectionItems, 'category'); const methodFreqCoverage = buildCompositeRowSpan(sectionItems); // method 컬럼과 frequency 컬럼 모두 methodFreqCoverage로 렌더링 // 셀 렌더링 예시 {!categoryCoverage.covered.has(idx) && ( {item.category} )} {!methodFreqCoverage.covered.has(idx) && ( {item.method} )} {!methodFreqCoverage.covered.has(idx) && ( {item.frequency} )} ``` **병합 결과 예시** (시더 데이터 기준): - category `겉모양` → 5행 병합 - category `치수(오픈사이즈)` → 4행 병합 - method+frequency `육안검사|전수검사` → 7행 (겉모양5 + 모터1 + 재질1) - method+frequency `체크검사|전수검사` → 4행 (치수 4개) - method+frequency `공인시험기관|1회/5년` → 9행 (내화3 + 차연1 + 개폐5) - `작동테스트`는 method/frequency 빈값 → 병합 대상 아님 #### 5.1.4 rendered_html 캡처 패턴 > **캡처 원칙** (document-snapshot-architecture-plan.md 참조): > - **Active Capture**: 입력 화면에서 저장할 때 캡처. readOnly에서는 캡처하지 않음. > - **Lazy Snapshot**: 조회 시 rendered_html 없으면 자동 캡처/저장 (점진적 전환). > FQC 성적서는 Active Capture (편집 모드 저장 시), 요청서는 Lazy Snapshot (readonly 자동 캡처) 적용. **패턴 1: innerHTML 직접 캡처** (참조: 중간검사 `InspectionReportModal.tsx` 라인 341-366) ```typescript // 1. ref 정의 const contentWrapperRef = useRef(null); // 2. 저장 시 캡처 const handleSave = async () => { const renderedHtml = contentWrapperRef.current?.innerHTML || undefined; const result = await saveInspectionDocument({ ...data, rendered_html: renderedHtml, }); }; // 3. JSX에서 ref 연결
``` **패턴 2: 오프스크린 렌더링** (참조: 수입검사 `ImportInspectionInputModal.tsx`) ```typescript import { captureRenderedHtml } from '@/lib/utils/capture-rendered-html'; // 문서 컴포넌트를 오프스크린으로 렌더링 → HTML 캡처 const html = await captureRenderedHtml(DocumentComponent, documentProps); ``` - 유틸리티 위치: `react/src/lib/utils/capture-rendered-html.tsx` (flushSync + createRoot) - 문서가 화면에 렌더링되지 않는 상황에서 캡처할 때 사용 (Lazy Snapshot에 활용 가능) **FQC 성적서 적용 (Phase 1.3)**: 패턴 1 사용. `InspectionReportModal.tsx`의 FQC 모드 저장 로직에 동일 패턴 추가. 현재 `fqcActions.ts`의 `saveFqcDocument()`는 `POST /v1/documents/upsert` 사용 → body에 `rendered_html` 추가. #### 5.1.5 FqcDocumentContent.tsx 현재 구조 (보완 대상) **현재 상태** (라인 311-375): - 4컬럼 테이블: NO(30px) | 검사항목 | 검사기준 | 판정(28px) - `InspectionRow` 컴포넌트 (라인 399-454): 4개 셀 렌더링 - 판정: readonly면 텍스트 표시, 편집모드면 양호/불량 토글 버튼 - 종합판정: 라인 117-128 자동 계산 (하나라도 부적합이면 불합격) **보완 방향**: 1. template columns 2개(측정값+판정)로 재정의 + 8컬럼 시각 레이아웃 (섹션 5.1.1) 2. `InspectionRow` → 8셀 렌더링 + rowSpan 복합키 병합 (섹션 5.1.3) 3. measurement_type에 따른 셀 분기 (섹션 5.1.8): - `checkbox`: 양호/불량 토글 (기존) - `numeric`: 숫자 입력 필드 (신규) - `none`: 측정값/판정 모두 비활성 + 종합판정에서 제외 (작동테스트) 4. method/frequency: section_item 필드에서 직접 렌더링 (readonly) 5. [선행] fqcActions.ts 타입에 category/method/frequency/measurement_type 추가 (섹션 5.1.7) #### 5.1.6 saveFqcDocument API 현재 구조 **`fqcActions.ts`** (라인 429-461): ```typescript // 현재: rendered_html 미포함 const body: Record = { template_id: templateId, data: data, }; if (documentId) body.document_id = documentId; if (itemId) body.item_id = itemId; if (title) body.title = title; // → rendered_html 추가 필요 const res = await fetchAPI('/api/v1/documents/upsert', { method: 'POST', body }); ``` **data 구조** (EAV): ```typescript data: Array<{ section_id?: number | null; column_id?: number | null; row_index: number; field_key: string; // 예: 'judgment', 'measured_value' field_value: string | null; }> ``` #### 5.1.7 Frontend 타입 갭 수정 (Phase 1.2 선행 작업) > **문제**: API는 section_items에 category, method, frequency, measurement_type 필드를 반환하지만, > React의 `TemplateItemApi`와 `FqcTemplateItem` 타입에 이 필드들이 누락되어 있음. **현재 타입** (`fqcActions.ts:20-29`): ```typescript interface TemplateItemApi { id: number; section_id: number; item: string; standard: string; sort_order: number; // ❌ category, method, frequency, measurement_type 누락 } ``` **수정 필요**: ```typescript interface TemplateItemApi { id: number; section_id: number; item: string; standard: string; sort_order: number; category: string | null; // 추가 method: string | null; // 추가 frequency: string | null; // 추가 measurement_type: string | null; // 추가 (checkbox/numeric/none) } ``` **`FqcTemplateItem`** (`fqcActions.ts:152-161`)도 동일하게 필드 추가. **`transformTemplate()`** (`fqcActions.ts:254-300`)에서 매핑 추가: ```typescript // items 변환 시 category, method, frequency, measurement_type 포함 items: section.items.map((item: TemplateItemApi) => ({ ...existingMapping, category: item.category || '', method: item.method || '', frequency: item.frequency || '', measurement_type: item.measurement_type || 'checkbox', })) ``` #### 5.1.8 measurement_type='none' 처리 (작동테스트) > **문제**: 작동테스트 항목은 `measurement_type='none'`인데, 현재 코드는 checkbox/numeric만 처리. > none 항목에 판정 UI를 표시하면 종합판정에 영향을 주게 됨. **처리 방안**: 1. **측정값 셀**: 빈 셀 또는 '-' 표시 (입력 불가) 2. **판정 셀**: 비활성화 (hideJudgment) 3. **종합판정 계산에서 제외**: `measurement_type='none'` 항목은 적합/부적합 집계에서 제외 **React 구현** (`FqcDocumentContent.tsx`의 `InspectionRow`): ```typescript // measurement_type에 따른 렌더링 분기 if (item.measurement_type === 'none') { // 측정값: 빈 셀 // 판정: 빈 셀 (토글 버튼 미표시) return <>--; } // 종합판정 계산 (라인 117-128 수정) const judgmentItems = records.filter(r => r.field_key === 'judgment' && // none 타입 항목 제외 sectionItems.find(item => item.sort_order === r.row_index)?.measurement_type !== 'none' ); ``` #### 5.1.9 Template ID 안정성 대책 > **문제**: 시더가 `cleanupExisting()`으로 삭제 후 재생성하면 auto_increment로 ID가 변경됨. > `FQC_TEMPLATE_ID = 65` 하드코딩이 깨질 수 있음. **대책 (2단계)**: **1단계 (즉시)** — 시더 개선: - `cleanupExisting()` 대신 `updateOrCreate()` 패턴 사용 - template ID를 명시적으로 지정하여 재실행 시에도 동일 ID 유지 ```php $template = DocumentTemplate::updateOrCreate( ['id' => 65, 'tenant_id' => $this->tenantId], ['name' => '제품검사 성적서', ...] ); // 하위 데이터도 sync 방식으로 처리 ``` **2단계 (권장, 후순위)** — category 기반 조회: - `FQC_TEMPLATE_ID = 65` 대신 API에서 category='품질/제품검사' + name='제품검사 성적서'로 조회 - 환경에 따라 ID가 달라도 동작하도록 유연성 확보 - **이번 작업에서는 1단계만 적용**, 2단계는 추후 리팩토링 시 적용 --- ### 5.2 제품검사 요청서 양식 설계 (Phase 2.1) #### 5.2.1 양식 구조 ``` DocumentTemplate (제품검사 요청서) ├── name: '제품검사 요청서' ├── category: '품질/제품검사' ├── title: '제 품 검 사 요 청 서' │ ├── approval_lines: [ │ { name: '작성', dept: '품질', role: '담당자', sort_order: 1 }, │ { name: '승인', dept: '경영', role: '대표', sort_order: 2 }, │ ] │ ├── basic_fields: [ │ { label: '수주처', field_key: 'client', field_type: 'text' }, │ { label: '업체명', field_key: 'company_name', field_type: 'text' }, │ { label: '담당자', field_key: 'manager', field_type: 'text' }, │ { label: '수주번호', field_key: 'order_number', field_type: 'text' }, │ { label: '담당자 연락처', field_key: 'manager_contact', field_type: 'text' }, │ { label: '현장명', field_key: 'site_name', field_type: 'text' }, │ { label: '납품일', field_key: 'delivery_date', field_type: 'date' }, │ { label: '현장 주소', field_key: 'site_address', field_type: 'text' }, │ { label: '총 개소', field_key: 'total_locations', field_type: 'number' }, │ { label: '접수일', field_key: 'receipt_date', field_type: 'date' }, │ { label: '검사방문요청일', field_key: 'inspection_request_date', field_type: 'date' }, │ ] │ ├── sections: [ │ { title: '건축공사장 정보', items: [ │ { item: '현장명', measurement_type: 'text_input' }, │ { item: '대지위치', measurement_type: 'text_input' }, │ { item: '지번', measurement_type: 'text_input' }, │ ]}, │ { title: '자재유통업자 정보', items: [ │ { item: '회사명', measurement_type: 'text_input' }, │ { item: '주소', measurement_type: 'text_input' }, │ { item: '대표자', measurement_type: 'text_input' }, │ { item: '전화번호', measurement_type: 'text_input' }, │ ]}, │ { title: '공사시공자 정보', items: [...] }, // 회사명, 주소, 성명, 전화 │ { title: '공사감리자 정보', items: [...] }, // 사무소명, 주소, 성명, 전화 │ { title: '검사대상 사전 고지 정보', items: [] }, // 동적 테이블 (locations) │ ] │ └── columns: [ // 사전 고지 정보 테이블용 { label: 'No.', column_type: 'text', width: '40px' }, { label: '층수', column_type: 'text', width: '60px' }, { label: '부호', column_type: 'text', width: '60px' }, { label: '발주 가로', column_type: 'text', width: '70px', group_name: '오픈사이즈(발주규격)' }, { label: '발주 세로', column_type: 'text', width: '70px', group_name: '오픈사이즈(발주규격)' }, { label: '시공 가로', column_type: 'text', width: '70px', group_name: '오픈사이즈(시공후규격)' }, { label: '시공 세로', column_type: 'text', width: '70px', group_name: '오픈사이즈(시공후규격)' }, { label: '변경사유', column_type: 'text' }, ] ``` #### 5.2.2 데이터 매핑 (quality_document → 요청서 필드) | 요청서 필드 | 데이터 소스 | 경로 | |------------|-----------|------| | 수주처 | quality_document → order → client | `qualityDocument.orders[0].order.client.name` | | 업체명 | tenant | `tenant.name` | | 담당자 | quality_document.created_by | user name | | 수주번호 | quality_document → order | `qualityDocument.orders[0].order.order_number` | | 현장명 | quality_document → order | `qualityDocument.orders[0].order.site_name` | | 납품일 | quality_document → order | `qualityDocument.orders[0].order.delivery_date` | | 총 개소 | quality_document_locations count | `qualityDocument.orders.flatMap(o => o.locations).length` | | 사전고지 테이블 | quality_document_locations | location별 floor/symbol/width/height/post_width/post_height/change_reason | #### 5.2.3 특이사항 - 요청서는 검사 입력이 아닌 **정보 표시 문서** (readonly) - 개소(location) 데이터는 `quality_document_locations` 테이블에서 가져옴 - "검사 요청 시 필독" 고정 텍스트는 section description으로 처리 - quality_document 생성 시 요청서 document 자동 생성 (bulkCreate 패턴 참조) --- ## 6. 확정 사항 | # | 항목 | 결정 내용 | 영향 범위 | 상태 | |---|------|----------|----------|------| | 1 | 성적서 양식 보완 방안 | **방안 C 확정** — 하드코딩 데이터를 template items로 완전 이관. SectionItem에 method/frequency/measurement_type 이미 존재 | mng, react | ✅ 확정 | | 2 | 요청서 template 생성 방법 | **시더 확정** — ProductInspectionRequestTemplateSeeder 작성 (mng/database/seeders/). 재현성 + 버전 관리 | mng | ✅ 확정 | | 3 | 요청서 문서 생성 시점 | **자동 생성 확정** — quality_document 생성 시 자동. 기존 성적서 bulk create 패턴과 동일 | api | ✅ 확정 | | 4 | 요청서 rendered_html 캡처 방식 | **Lazy Snapshot 확정** — 요청서는 readonly 문서이므로 최초 조회 시 rendered_html 없으면 자동 캡처/저장. `captureRenderedHtml` 유틸리티 또는 `contentWrapperRef.innerHTML` 활용 | react | ✅ 확정 | --- ## 6.1 에이전트 조사 결과 (2026-03-06) ### Template ID 65 현황 - Seeder: MNG에만 존재 (`mng/database/seeders/ProductInspectionTemplateSeeder.php`, tenant_id=287 하드코딩) - 정규 관리 안됨 (migration/seeder로 재현 불가) - React: `FQC_TEMPLATE_ID = 65` 하드코딩 (`fqcActions.ts:348`) ### rendered_html 스냅샷 아키텍처 (~95% 완성) | 계층 | 상태 | 비고 | |------|------|------| | DB (documents.rendered_html) | ✅ | LONGTEXT, migration 완료 | | API (DocumentService create/update) | ✅ | rendered_html 조건부 저장 | | React (InspectionReportModal) | ✅ | contentWrapperRef.innerHTML 캡처 | | MNG (show/print.blade.php) | ✅ | rendered_html 우선 출력 + fallback | | FormRequest 검증 | ✅ | StoreRequest/UpdateRequest/UpsertRequest 모두 nullable string 검증 완료 | ### mng template 구조 핵심 - `DocumentTemplateSectionItem` 필드: category, item, standard, tolerance, standard_criteria, method, measurement_type, frequency_n, frequency_c, frequency, regulation, field_values(JSON) - **8컬럼 구조를 추가 스키마 변경 없이 지원 가능** — 방안 C 근거 - 저장 방식: Legacy EAV (approval_lines, basic_fields, sections+items, columns, section_fields, links) - 저장 API: `POST /api/admin/document-templates` → `saveRelations()` 트랜잭션 ### QualityDocumentLocation 모델 - 테이블: `quality_document_locations` - 필드: `quality_document_id`, `quality_document_order_id`, `order_item_id`, `post_width`, `post_height`, `change_reason`, `inspection_data`(JSON), `document_id`, `inspection_status` - 관계: qualityDocument, qualityDocumentOrder, orderItem, document - 상태: pending / completed --- ## 7. 변경 이력 | 날짜 | 항목 | 변경 내용 | 파일 | 승인 | |------|------|----------|------|------| | 2026-03-06 | - | 문서 초안 작성 | - | - | | 2026-03-06 | 컨펌 3건 | 방안 C 확정, 시더 확정, 자동 생성 확정 | - | ✅ 사용자 승인 | | 2026-03-06 | 에이전트 조사 | template ID 65 현황, rendered_html 현황, mng 구조 분석 반영 | - | - | | 2026-03-06 | 자기완결성 보완 | 시더 데이터, rowSpan 로직, rendered_html 캡처 패턴, API 구조, 타입 정의 추가 | - | - | | 2026-03-06 | 방법론 수정 6건 | ①컬럼 아키텍처 재설계(template 2개+시각 8컬럼) ②프론트 타입 갭 보완 ③rowSpan 복합키 알고리즘 ④Template ID 안정성 대책 ⑤measurement_type=none 처리 ⑥시더 위치 명확화(mng) | - | - | | 2026-03-06 | 스냅샷 아키텍처 정합성 검토 반영 | ①FormRequest 검증 ✅ 확인 ②Phase 2.4 Lazy Snapshot 전략 확정 ③참고 파일 추가(capture-rendered-html.tsx, UpsertRequest) ④캡처 원칙(Active/Lazy) 명시 ⑤오프스크린 렌더링 참조 추가 | 5.1.4, 6.1, 9 | - | --- ## 8. 참고 문서 - **문서관리 시스템 마스터**: `docs/dev/dev_plans/document-system-master.md` - **스냅샷 아키텍처**: `docs/dev/dev_plans/document-snapshot-architecture-plan.md` - **FQC Phase 5.2 아카이브**: `docs/dev/dev_plans/archive/document-system-product-inspection.md` - **문서관리 기능 스펙**: `docs/features/documents/README.md` - **DB 스키마**: `docs/system/database/documents.md` - **품질 체크리스트**: `docs/dev/standards/quality-checklist.md` --- ## 9. 참고 파일 경로 ### React (수정 대상) | 파일 | 역할 | 핵심 라인 | |------|------|----------| | `react/src/components/quality/InspectionManagement/documents/FqcDocumentContent.tsx` | FQC 양식 기반 동적 렌더링 (보완 대상) | 311-375: 4컬럼 테이블, 399-454: InspectionRow | | `react/src/components/quality/InspectionManagement/documents/InspectionReportDocument.tsx` | 하드코딩 성적서 (참조/fallback) | 42-57: buildCoverageMap(), 198-376: 8컬럼 테이블 | | `react/src/components/quality/InspectionManagement/documents/InspectionReportModal.tsx` | 성적서 모달 (듀얼 모드) | 61: useFqcMode, 106-116: FQC 문서 로드, 233-262: fallback | | `react/src/components/quality/InspectionManagement/documents/InspectionRequestDocument.tsx` | 하드코딩 요청서 (참조/fallback) | 47-62: 결재, 66-103: 기본정보, 211-255: 사전고지 테이블 | | `react/src/components/quality/InspectionManagement/documents/InspectionRequestModal.tsx` | 요청서 모달 | 26-37: DocumentViewer 래퍼 | | `react/src/components/quality/InspectionManagement/fqcActions.ts` | FQC 서버 액션 | 348: FQC_TEMPLATE_ID=65, 429-461: saveFqcDocument() | | `react/src/components/quality/InspectionManagement/InspectionDetail.tsx` | 상세 페이지 (모달 연동) | 1230-1275: 모달 통합 | | `react/src/components/quality/InspectionManagement/mockData.ts` | 데이터 변환 함수 | 398-423: buildRequestDocumentData(), 426-458: mockReportInspectionItems | | `react/src/components/quality/InspectionManagement/types.ts` | 타입 정의 | 224-247: InspectionRequestDocument, 250-266: ReportInspectionItem, 269-285: InspectionReportDocument | | `react/src/components/quality/InspectionManagement/actions.ts` | 서버 액션 (CRUD) | | ### React (참조 - 기존 동적 렌더링 패턴) | 파일 | 역할 | 핵심 라인 | |------|------|----------| | `react/src/components/production/WorkOrders/documents/TemplateInspectionContent.tsx` | 중간검사 동적 렌더링 (참조 모델) | 917-999: 컬럼타입별 렌더링, 1105-1183: renderComplexCells() | | `react/src/components/production/WorkOrders/documents/InspectionReportModal.tsx` | 중간검사 모달 (rendered_html 캡처 패턴) | 167: contentWrapperRef, 341-366: handleSave+innerHTML | | `react/src/lib/utils/capture-rendered-html.tsx` | 오프스크린 렌더링 유틸리티 (flushSync+createRoot) | Phase 2.4 Lazy Snapshot에서 활용 가능 | | `react/src/components/material/ReceivingManagement/ImportInspectionInputModal.tsx` | 수입검사 오프스크린 캡처 참조 | captureRenderedHtml 사용 예시 | ### API (수정 대상) | 파일 | 역할 | 핵심 라인 | |------|------|----------| | `api/app/Services/DocumentService.php` | 문서 저장/조회 | 125: create(rendered_html), 180: update(rendered_html) | | `api/app/Services/QualityDocumentService.php` | 품질관리 문서 서비스 | 176-209: store(), 749-799: requestDocument(), 804-868: resultDocument() | | `api/app/Models/Documents/Document.php` | 문서 모델 | 76: $fillable에 rendered_html | | `api/app/Models/Qualitys/QualityDocumentLocation.php` | 개소 모델 | fillable: post_width, post_height, change_reason, inspection_data | | `api/app/Http/Requests/Document/UpsertRequest.php` | documents/upsert 검증 | rendered_html nullable string 검증 완료 | ### MNG (확인/수정 대상) | 파일 | 역할 | |------|------| | `mng/app/Http/Controllers/DocumentTemplateController.php` | 양식 CRUD | | `mng/resources/views/document-templates/edit.blade.php` | 양식 편집 | | `mng/resources/views/documents/show.blade.php` | 문서 보기 (rendered_html 우선 출력, 29-32행) | | `mng/resources/views/documents/print.blade.php` | 문서 인쇄 (28-32행) | | `mng/database/seeders/ProductInspectionTemplateSeeder.php` | template ID 65 시더 (49-182: 항목 데이터) | --- ## 10. 미커밋 변경사항 (이전 세션, 2026-03-06) **api/** — order_ids 영속성, location count 수정, location data 저장: - `app/Http/Requests/Quality/QualityDocumentStoreRequest.php` — 수정 - `app/Http/Requests/Quality/QualityDocumentUpdateRequest.php` — 수정 - `app/Services/QualityDocumentService.php` — 수정 - `app/Models/Qualitys/QualityDocumentLocation.php` — 수정 - `database/migrations/2026_03_06_094425_add_inspection_data_to_quality_document_locations.php` — 신규 **react/** — `src/components/quality/InspectionManagement/actions.ts` — 수정 > 이 작업 시작 전에 먼저 커밋 필요 --- ## 11. 세션 및 메모리 관리 정책 (Serena Optimized) ### 11.1 세션 시작 시 (Load Strategy) ```javascript read_memory("fqc-doc-state") // 1. 상태 파악 read_memory("fqc-doc-snapshot") // 2. 사고 흐름 복구 read_memory("fqc-doc-active-symbols") // 3. 작업 대상 파악 ``` ### 11.2 작업 중 관리 (Context Defense) | 컨텍스트 잔량 | Action | 내용 | |--------------|--------|------| | **30% 이하** | Snapshot | `write_memory("fqc-doc-snapshot", "코드변경+논의요약")` | | **20% 이하** | Context Purge | `write_memory("fqc-doc-active-symbols", "주요 수정 파일/함수")` | | **10% 이하** | Stop & Save | 최종 상태 저장 후 세션 교체 권고 | ### 11.3 Serena 메모리 구조 - `fqc-doc-state`: { phase, progress, next_step, last_decision } (JSON) - `fqc-doc-snapshot`: 현재까지의 논의 및 코드 변경점 요약 (Text) - `fqc-doc-rules`: 해당 작업에서 결정된 불변의 규칙들 (Text) - `fqc-doc-active-symbols`: 현재 수정 중인 파일/심볼 리스트 (List) --- ## 12. 검증 결과 > 작업 완료 후 이 섹션에 검증 결과 추가 ### 12.1 성공 기준 | # | 기준 | 달성 | 비고 | |---|------|:----:|------| | 1 | mng에서 성적서 양식 편집/미리보기 정상 | ✅ | 시더 실행 확인 | | 2 | mng에서 요청서 양식 편집/미리보기 정상 | ✅ | 시더 실행 확인 | | 3 | React에서 성적서 양식 기반 동적 렌더링 (8컬럼+rowSpan) | ✅ | 브라우저 테스트 통과 | | 4 | React에서 요청서 양식 기반 동적 렌더링 | ✅ | requestDocumentId 없으면 legacy fallback | | 5 | 저장 시 rendered_html 스냅샷 저장됨 | ✅ | Active Capture 코드 구현 완료 (검사 저장 시 동작) | | 6 | mng 문서 보기에서 스냅샷 정상 출력 | ✅ | show.blade.php 코드 검증 | | 7 | 기존 하드코딩 fallback 정상 동작 | ✅ | 요청서 legacy fallback 브라우저 테스트 통과 | | 8 | 기존 FQC 데이터 호환성 유지 | ✅ | 기존 EAV basic_fields 정상 표시 확인 | ### 12.2 테스트 시나리오 | 시나리오 | 예상 결과 | 실제 결과 | 상태 | |---------|----------|----------|------| | `/quality/inspections/1?mode=view` → 검사제품요청서 클릭 | 양식 기반 요청서 표시 | legacy fallback 정상 (EAV 문서 미생성 상태) | ✅ | | `/quality/inspections/1?mode=view` → 제품검사하기 클릭 | 양식 기반 성적서 표시 (편집 모드, 8컬럼) | FQC 8컬럼 + rowSpan 정상 | ✅ | | 성적서 검사 완료 후 저장 | document_data + rendered_html 저장 | 코드 구현 완료 (실 저장은 검사 진행 시) | ✅ | | `mng.sam.kr/documents/{id}` | rendered_html 스냅샷 출력 | show.blade.php 코드 검증 | ✅ | | template ID 65 없는 환경 | 하드코딩 fallback 동작 | templateLoadFailed 시 legacy 렌더링 | ✅ | | 치수 검사항목에 측정값 입력 | numeric input → document_data에 저장 | UI 표시 확인 (저장은 검사 진행 시) | ✅ | --- ## 13. 자기완결성 점검 결과 ### 13.1 체크리스트 검증 | # | 검증 항목 | 상태 | 비고 | |---|----------|:----:|------| | 1 | 작업 목적이 명확한가? | ✅ | 섹션 1.1 | | 2 | 성공 기준이 정의되어 있는가? | ✅ | 섹션 12.1 | | 3 | 작업 범위가 구체적인가? | ✅ | 섹션 3 | | 4 | 의존성이 명시되어 있는가? | ✅ | Phase 순서, 미커밋 사항(섹션 10) | | 5 | 참고 파일 경로가 정확한가? | ✅ | 섹션 9 (라인 번호 포함) | | 6 | 단계별 절차가 실행 가능한가? | ✅ | 섹션 4+5 (시더 데이터, 코드 패턴 포함) | | 7 | 검증 방법이 명시되어 있는가? | ✅ | 섹션 12.2 | | 8 | 모호한 표현이 없는가? | ✅ | 방안 C 확정, 시더 확정, 자동 생성 확정 | ### 13.2 새 세션 시뮬레이션 테스트 | 질문 | 답변 가능 | 참조 섹션 | |------|:--------:|----------| | Q1. 이 작업의 목적은 무엇인가? | ✅ | 1.1 배경 | | Q2. 어디서부터 시작해야 하는가? | ✅ | 4.1 단계별 절차 + 10. 미커밋 사항(먼저 커밋) | | Q3. 어떤 파일을 수정해야 하는가? | ✅ | 9. 참고 파일 경로 (라인 번호 포함) | | Q4. 시더에 어떤 데이터를 넣어야 하는가? | ✅ | 5.1.2 검사항목 데이터 (PHP 배열) | | Q5. React rowSpan은 어떻게 구현하는가? | ✅ | 5.1.3 자동 병합 로직 (TypeScript 코드) | | Q6. rendered_html은 어떻게 캡처하는가? | ✅ | 5.1.4 캡처 패턴 (참조 코드) | | Q7. 요청서 필드는 어디서 가져오는가? | ✅ | 5.2.2 데이터 매핑 테이블 | | Q8. 작업 완료 확인 방법은? | ✅ | 12. 검증 결과 | | Q9. 막혔을 때 참고 문서는? | ✅ | 8. 참고 문서 | --- *이 문서는 /plan 스킬로 생성되었습니다.*