diff --git a/dev/dev_plans/fqc-document-system-plan.md b/dev/dev_plans/fqc-document-system-plan.md new file mode 100644 index 0000000..22a0e48 --- /dev/null +++ b/dev/dev_plans/fqc-document-system-plan.md @@ -0,0 +1,847 @@ +# 제품검사 문서 시스템 구축 계획 + +> **작성일**: 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 1.1: 제품검사 성적서 template ID 65 시더 보완 | +| **진행률** | 0/12 (0%) | +| **마지막 업데이트** | 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 스냅샷 저장 적용 | ⏳ | FQC 문서 저장 시 | +| 1.4 | InspectionReportModal에서 양식 모드 기본값 전환 | ⏳ | FQC 모드 우선 | + +### Phase 2: 제품검사 요청서 (신규 template) + +| # | 작업 항목 | 상태 | 비고 | +|---|----------|:----:|------| +| 2.1 | mng에 제품검사 요청서 template 신규 등록 (시더) | ⏳ | 시더 방식 확정 | +| 2.2 | React 동적 렌더링 컴포넌트 구현 | ⏳ | 하드코딩 → 양식 기반 전환 | +| 2.3 | 요청서 문서 생성 API 연동 | ⏳ | quality_document 생성 시 자동 생성 확정 | +| 2.4 | rendered_html 스냅샷 저장 적용 | ⏳ | | + +### Phase 3: 통합 및 정리 + +| # | 작업 항목 | 상태 | 비고 | +|---|----------|:----:|------| +| 3.1 | InspectionDetail.tsx 모달 연동 통합 테스트 | ⏳ | | +| 3.2 | mng 문서 보기에서 스냅샷 출력 확인 | ⏳ | | +| 3.3 | 하드코딩 컴포넌트 fallback 유지 확인 | ⏳ | | +| 3.4 | 기존 FQC 데이터 호환성 확인 | ⏳ | | + +--- + +## 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 스냅샷 + ├── 요청서 저장/표시 시 HTML 캡처 + └── API 전송 및 저장 + +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 캡처 패턴 (참조: 중간검사) + +**`react/src/components/production/WorkOrders/documents/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 연결 +
+ +
+``` + +**FQC에 적용할 위치**: `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 | ✅ 확정 | + +--- + +## 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 검증 | ⚠️ | rendered_html 검증 규칙 확인 필요 | + +### 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) | - | - | + +--- + +## 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 | + +### 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 | + +### 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에서 요청서 양식 기반 동적 렌더링 | ⏳ | | +| 5 | 저장 시 rendered_html 스냅샷 저장됨 | ⏳ | | +| 6 | mng 문서 보기에서 스냅샷 정상 출력 | ⏳ | | +| 7 | 기존 하드코딩 fallback 정상 동작 | ⏳ | | +| 8 | 기존 FQC 데이터 호환성 유지 | ⏳ | | + +### 12.2 테스트 시나리오 + +| 시나리오 | 예상 결과 | 실제 결과 | 상태 | +|---------|----------|----------|------| +| `/quality/inspections/1?mode=view` → 검사제품요청서 클릭 | 양식 기반 요청서 표시 | | ⏳ | +| `/quality/inspections/1?mode=view` → 제품검사하기 클릭 | 양식 기반 성적서 표시 (편집 모드, 8컬럼) | | ⏳ | +| 성적서 검사 완료 후 저장 | document_data + rendered_html 저장 | | ⏳ | +| `mng.sam.kr/documents/{id}` | rendered_html 스냅샷 출력 | | ⏳ | +| template ID 65 없는 환경 | 하드코딩 fallback 동작 | | ⏳ | +| 치수 검사항목에 측정값 입력 | numeric input → document_data에 저장 | | ⏳ | + +--- + +## 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 스킬로 생성되었습니다.*