Files
sam-docs/dev/dev_plans/fqc-document-system-plan.md
권혁성 5bfc89afa7 docs: [제품검사] FQC 문서 시스템 계획 + 스냅샷 Lazy Snapshot 반영
- fqc-document-system-plan.md: FormRequest 상태 수정, Phase 2.4 Lazy Snapshot 확정, 참고 파일 추가
- document-snapshot-architecture-plan.md: Lazy Snapshot 캡처 원칙 추가
- server-access-management.md 신규
- README.md 수정

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 21:09:57 +09:00

44 KiB

제품검사 문서 시스템 구축 계획

작성일: 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 스냅샷 저장 적용 (Lazy Snapshot) 요청서 최초 조회 시 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 스냅샷 (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

16번은 section_item의 읽기 전용 필드를 React에서 직접 렌더링. 78번만 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 섹션):

// 섹션: 검사 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.tsxbuildCoverageMap() (라인 42-57) 패턴을 template 데이터 기반으로 자동화.

주의: methodfrequency복합 키(method+frequency)로 병합해야 함. 단일 필드 비교 시 frequency='전수검사'가 7+4=11행 연속으로 잘못 병합됨. 실제로는 method가 다르면 (육안검사 7행 vs 체크검사 4행) 별도 그룹이어야 함.

// category는 단일 필드 병합 (같은 category 연속이면 병합)
function buildFieldRowSpan(items: SectionItem[], field: 'category') {
  const spans: Map<number, number> = new Map();
  const covered: Set<number> = 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<number, number> = new Map();
  const covered: Set<number> = 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) && (
  <td rowSpan={categoryCoverage.spans.get(idx) || 1}>{item.category}</td>
)}
{!methodFreqCoverage.covered.has(idx) && (
  <td rowSpan={methodFreqCoverage.spans.get(idx) || 1}>{item.method}</td>
)}
{!methodFreqCoverage.covered.has(idx) && (
  <td rowSpan={methodFreqCoverage.spans.get(idx) || 1}>{item.frequency}</td>
)}

병합 결과 예시 (시더 데이터 기준):

  • 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)

// 1. ref 정의
const contentWrapperRef = useRef<HTMLDivElement>(null);

// 2. 저장 시 캡처
const handleSave = async () => {
  const renderedHtml = contentWrapperRef.current?.innerHTML || undefined;
  const result = await saveInspectionDocument({
    ...data,
    rendered_html: renderedHtml,
  });
};

// 3. JSX에서 ref 연결
<div ref={contentWrapperRef}>
  <TemplateInspectionContent ... />
</div>

패턴 2: 오프스크린 렌더링 (참조: 수입검사 ImportInspectionInputModal.tsx)

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.tssaveFqcDocument()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):

// 현재: rendered_html 미포함
const body: Record<string, unknown> = {
  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):

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의 TemplateItemApiFqcTemplateItem 타입에 이 필드들이 누락되어 있음.

현재 타입 (fqcActions.ts:20-29):

interface TemplateItemApi {
  id: number;
  section_id: number;
  item: string;
  standard: string;
  sort_order: number;
  // ❌ category, method, frequency, measurement_type 누락
}

수정 필요:

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)에서 매핑 추가:

// 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.tsxInspectionRow):

// measurement_type에 따른 렌더링 분기
if (item.measurement_type === 'none') {
  // 측정값: 빈 셀
  // 판정: 빈 셀 (토글 버튼 미표시)
  return <><td>-</td><td>-</td></>;
}

// 종합판정 계산 (라인 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 유지
$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-templatessaveRelations() 트랜잭션

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)

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 스킬로 생성되었습니다.