Compare commits

...

10 Commits

Author SHA1 Message Date
03850fefdd docs: 서버 접근 권한 관리 문서 업데이트
- 운영 관리자 정보 추가 (hskwon/권혁성, pro/김보곤)
- 서버 시간대 설정 정리 (OS, PHP CLI/FPM, Laravel)
- develop 그룹 + setgid 공동 관리 구조 추가
- DB 리플리케이션 현황 (sam, sam_stat, codebridge)
- DB 백업 설정 (/data/ 경로, codebridge 포함)
- sam-dev 서버 정보 추가
- INDEX.md에 서버 접근/백업 문서 등록

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 01:28:08 +09:00
cf189fd453 docs: FQC 문서 시스템 계획 Phase 3 완료 (100%)
- Phase 3 통합 테스트 전체 통과
- 검증 결과 및 테스트 시나리오 업데이트

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 22:02:51 +09:00
b1472f3c35 docs: FQC 문서 시스템 계획 Phase 2 완료 (67%)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 21:43:07 +09:00
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
f1683f753e docs: [문서스냅샷] 계획 문서 보정 - API 수정, 오프스크린 렌더링, 변경이력 반영
- Phase 2 보정 내용 변경이력 3건 추가
- 참고 파일에 UpsertRequest.php, capture-rendered-html.tsx 추가
- 자기완결성 점검 작업 수 업데이트

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 20:35:39 +09:00
614e90066f docs: [제품검사] FQC 문서 시스템 구축 계획 작성 + 방법론 수정 6건
- 제품검사 성적서(template 65 보완) + 요청서(신규) 개발 계획
- 방안 C 확정(template items 완전 이관), 시더 방식, 자동 생성 확정
- 방법론 수정: 컬럼 아키텍처(template 2개+시각 8컬럼), rowSpan 복합키,
  프론트 타입 갭, measurement_type=none, Template ID 안정성, 시더 위치 명확화

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 20:10:50 +09:00
8efe0ac477 docs: [문서스냅샷] Phase 3.3 완료 - 코드 작업 100% 완료
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 17:54:07 +09:00
303de36e1c docs: [문서스냅샷] Phase 2 완료 - 진행률 90% 업데이트
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 17:47:13 +09:00
334c8f3918 docs: 문서 스냅샷 아키텍처 계획 + 절곡 문서 매칭 계획
- document-snapshot-architecture-plan.md: B안(HTML 스냅샷) + 구조화 데이터 병행 계획
- mng-bending-document-matching-plan.md: 절곡 중간검사/작업일지 MNG 매칭 계획

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 15:52:27 +09:00
0223c33fd9 docs: [품질관리] 개발 계획 Phase 4 프론트엔드 API 연동 완료 업데이트
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 19:45:27 +09:00
7 changed files with 2842 additions and 2 deletions

View File

@@ -1,7 +1,7 @@
# SAM 문서 인덱스 (Claude Code용)
> 작업 유형에 맞는 문서를 먼저 읽고 시작하세요.
> 최종 갱신: 2026-03-05
> 최종 갱신: 2026-03-07
---
@@ -21,6 +21,7 @@
| 견적관리 | `features/quotes/README.md` | 견적 시스템, BOM 계산 |
| 운영 배포 | `dev/dev_plans/production-deployment-plan.md` | 배포 계획 |
| 서버 운영 | `dev/deploys/ops-manual/README.md` | 서버 운영 매뉴얼 |
| 서버 접근/백업 | `system/server-access-management.md` | 계정, 권한, 백업, 리플리케이션 |
| MES | `projects/mes/README.md` | MES 프로젝트 |
---
@@ -72,6 +73,7 @@ docs/
| [docker-setup.md](system/docker-setup.md) | Docker 환경 + CI/CD |
| [database/README.md](system/database/README.md) | DB 스키마 인덱스 |
| [security-policy.md](system/security-policy.md) | 보안 정책 |
| [server-access-management.md](system/server-access-management.md) | 서버 접근 권한, 백업, 리플리케이션 |
| [scaling-roadmap.md](system/scaling-roadmap.md) | 스케일링 로드맵 |
| [board-system-spec.md](system/board-system-spec.md) | 게시판 시스템 설계 |
| [item-master-integration.md](system/item-master-integration.md) | 품목 마스터 통합 설계 |

View File

@@ -1,4 +1,4 @@
# SAM 프로젝트 문서
# SAM 프로젝트 문서
SAM ERP 시스템의 기술 문서, 비즈니스 규칙, 기능 명세를 관리하는 저장소입니다.

View File

@@ -0,0 +1,385 @@
# 문서 스냅샷 아키텍처 계획
> **작성일**: 2026-03-06
> **목적**: 문서 보기/인쇄 시 HTML 스냅샷 기반 출력으로 전환 (B안 + 구조화 데이터 병행)
> **상태**: ✅ 코드 완료 (검증 대기)
> **영향 범위**: API(저장), React(캡처/전송), MNG(출력)
---
## 현재 진행 상태
| 항목 | 내용 |
|------|------|
| **마지막 완료 작업** | Phase 2 전면 보정: API 누락 수정, 오프스크린 렌더링 적용, readOnly 자동 캡처 제거 |
| **다음 작업** | Phase 4: 브라우저 검증 + 기존 partial 정리 |
| **진행률** | 13/13 (100% 코드 완료, 검증 대기) |
| **마지막 업데이트** | 2026-03-06 |
---
## 1. 개요
### 1.1 배경
현재 MNG 문서 보기(`show.blade.php`)는 문서 양식별로 전용 blade partial을 만들어 렌더링한다:
- `bending-inspection-data.blade.php` (절곡 중간검사)
- `bending-worklog.blade.php` (절곡 작업일지)
이 방식의 문제:
1. **확장 불가**: 회사마다 다양한 양식이 존재 → 양식마다 blade 파일 생성 불가
2. **스냅샷 미보장**: 하드코딩된 제품 목록/도면치수가 정책 변경 시 과거 문서를 깨뜨림
3. **이중 렌더링**: React와 MNG에서 동일 문서를 각각 렌더링 → 불일치 발생
### 1.2 목표 아키텍처
```
[React] 문서 저장 시
├── 구조화 데이터 저장 (기존 유지)
│ ├── document_data (EAV 플랫)
│ └── work_order_items.options.inspection_data (JSON 스냅샷)
└── rendered_html 저장 (신규)
└── React가 렌더링한 HTML을 캡처 → documents.rendered_html에 저장
[MNG] 문서 보기 시
├── rendered_html 있으면 → 그대로 출력 (렌더링 로직 0)
└── rendered_html 없으면 → 기존 동적 렌더링 fallback
```
### 1.3 핵심 원칙
```
1. 하나의 view 파일로 모든 문서를 보기 (문서 양식별 blade 파일 금지)
2. rendered_html이 있으면 무조건 그것을 사용 (완전한 스냅샷)
3. 구조화 데이터는 편집/검색/통계용으로 병행 유지
4. React에서만 문서 렌더링 책임 → MNG는 출력만 담당
5. Lazy Snapshot: 조회 시 rendered_html 없으면 자동 캡처 → 저장 (점진적 스냅샷 전환)
```
### 1.4 변경 승인 정책
| 분류 | 예시 | 승인 |
|------|------|------|
| 즉시 가능 | blade 템플릿 수정, 기존 partial 정리 | 불필요 |
| 컨펌 필요 | API 저장 로직 변경, React 저장 흐름 변경 | **필수** |
| 금지 | documents 테이블 구조 변경 (이미 rendered_html 존재) | 불필요 |
---
## 2. 현황 분석
### 2.1 DB 현황
`documents` 테이블에 이미 `rendered_html` (LONGTEXT, nullable) 컬럼이 존재:
- 마이그레이션: `api/database/migrations/2026_02_28_100001_add_block_data_to_documents.php`
- 현재 값: 모든 문서에서 NULL (사용 안 됨)
- **DB 변경 불필요**
### 2.2 React 현황 (구현 완료)
#### 캡처 원칙 A: 입력 시 저장 (Active Capture)
입력 화면에서 저장할 때 해당 데이터의 "문서 뷰"를 캡처. 보기(readOnly)에서는 캡처하지 않음.
| Save Path | 파일 | 방식 | 캡처 대상 |
|-----------|------|------|----------|
| 작업일지 저장 | `WorkLogModal.tsx` | contentWrapperRef.innerHTML | 작업일지 문서 뷰 |
| 검사성적서 저장 (edit) | `InspectionReportModal.tsx` | contentWrapperRef.innerHTML | 검사 성적서 문서 뷰 |
| 수입검사 저장 | `ImportInspectionInputModal.tsx` | 오프스크린 렌더링 (`captureRenderedHtml`) | 수입검사 성적서 문서 (`ImportInspectionDocument`) |
| WorkerScreen 인라인 검사 저장 | `index.tsx` | 미캡처 (데이터만 저장) | 성적서 모달에서 저장 시 캡처 |
> **WorkerScreen 인라인 저장**: 검사 입력 시점에 성적서 문서가 렌더링되지 않으므로 rendered_html 미포함.
> 이후 InspectionReportModal을 edit 모드로 열어 저장하면 캡처됨.
> 향후 오프스크린 렌더링으로 확장 가능 (템플릿 로딩 등 async 의존성 해결 필요).
#### 캡처 원칙 B: 조회 시 자동 캡처 (Lazy Snapshot)
문서 조회(view/readOnly) 시 `rendered_html`이 없으면 자동 캡처하여 백그라운드 저장.
```
문서 View 시
├── rendered_html 있음 → 그대로 표시 (기존)
└── rendered_html 없음 → 동적 렌더링 완료 후 캡처 → API로 rendered_html 저장
(다음 조회부터는 스냅샷 사용)
```
**적용 대상**:
- readonly 문서 (제품검사 요청서 등 — 입력 없이 자동 생성되는 문서)
- 마이그레이션 이전 기존 데이터 (rendered_html이 NULL인 과거 문서)
- WorkerScreen 인라인 저장 후 아직 모달에서 저장하지 않은 문서
**구현 방식**:
```typescript
// 문서 표시 컴포넌트에서 (DocumentViewer, Modal 등)
useEffect(() => {
if (document && !document.rendered_html && isContentRendered) {
const html = contentWrapperRef.current?.innerHTML
|| await captureRenderedHtml(DocumentComponent, props);
patchDocumentRenderedHtml(document.id, html); // 백그라운드 저장
}
}, [document, isContentRendered]);
```
**고려사항**:
- 사용자 UX 영향 없음 (백그라운드 비동기 저장)
- 조회 권한만 있는 사용자도 트리거 가능해야 함
- 동시 접속 시 중복 저장 가능 → 같은 HTML이므로 실질적 문제 없음
- 캡처 타이밍: template 로드 + 데이터 바인딩 완료 후 (isContentRendered 판단 필요)
### 2.3 API 현황 (구현 완료)
- Document 모델 `$fillable``rendered_html` 포함 ✅
- `DocumentService` store/update에서 `rendered_html` 저장 ✅
- `DocumentService` upsert에서 `rendered_html` 전달 ✅ (수입검사 경로)
- `StoreRequest`/`UpdateRequest``rendered_html` nullable string 검증 ✅
- `UpsertRequest``rendered_html` nullable string 검증 ✅
### 2.4 MNG 현황 (구현 완료)
- `show.blade.php`: rendered_html 우선 출력, 없으면 기존 동적 렌더링 fallback ✅
- `print.blade.php`: 동일 패턴 적용 ✅
- 전용 partial 파일 (삭제 대기):
- `partials/bending-inspection-data.blade.php`
- `partials/bending-worklog.blade.php`
---
## 3. 작업 범위
### Phase 0: 사전 정리
| # | 작업 항목 | 상태 | 비고 |
|---|----------|:----:|------|
| 0.1 | API Document 모델 $fillable 확인 및 rendered_html 추가 | ✅ | |
| 0.2 | 기존 절곡 전용 partial 파일 정리 방침 결정 | ✅ | rendered_html 전환 후 삭제 |
### Phase 1: API - rendered_html 저장 지원
| # | 작업 항목 | 상태 | 비고 |
|---|----------|:----:|------|
| 1.1 | Document 모델 $fillable에 rendered_html 추가 | ✅ | |
| 1.2 | DocumentService store/update에서 rendered_html 저장 | ✅ | |
| 1.3 | StoreRequest/UpdateRequest에 rendered_html 검증 추가 | ✅ | nullable, string |
| 1.4 | WorkOrderService inspection/worklog에 rendered_html 전달 | ✅ | create + update 모두 |
### Phase 2: React - HTML 캡처 및 전송
| # | 작업 항목 | 상태 | 비고 |
|---|----------|:----:|------|
| 2.1 | 오프스크린 렌더링 유틸리티 생성 | ✅ | `captureRenderedHtml()``flushSync` + `createRoot` |
| 2.2 | InspectionReportModal 저장 시 rendered_html 포함 전송 | ✅ | contentWrapperRef.innerHTML 캡처 |
| 2.3 | 작업일지 저장 시 rendered_html 포함 전송 | ✅ | contentWrapperRef.innerHTML 캡처 |
| 2.4 | ImportInspectionInputModal 수입검사 저장 시 rendered_html | ✅ | 오프스크린 성적서 문서 렌더링 |
| 2.5 | ReceivingManagement/actions saveInspectionData 파라미터 추가 | ✅ | rendered_html → /documents/upsert 전달 |
| 2.6 | API UpsertRequest에 rendered_html 검증 추가 | ✅ | nullable string |
### Phase 3: MNG - 스냅샷 출력
| # | 작업 항목 | 상태 | 비고 |
|---|----------|:----:|------|
| 3.1 | show.blade.php에 rendered_html 우선 출력 로직 추가 | ✅ | |
| 3.2 | 기존 전용 partial 파일 fallback으로 유지 (과도기) | ✅ | |
| 3.3 | print.blade.php에도 rendered_html 출력 적용 | ✅ | 스냅샷 우선, 레거시 fallback |
### Phase 4: 검증 및 정리
| # | 작업 항목 | 상태 | 비고 |
|---|----------|:----:|------|
| 4.1 | 브라우저 검증 (MNG 보기/인쇄) | ⏳ | |
| 4.2 | 기존 전용 partial 파일 삭제 | ⏳ | rendered_html 전환 완료 후 |
---
## 4. 상세 작업 내용
### 4.1 HTML 캡처 방식 (Phase 2.1)
React에서 문서 컨텐츠 영역의 DOM을 캡처할 때 고려사항:
**방법 A: innerHTML 직접 추출 + CSS 인라인화**
```typescript
// 문서 컨텐츠 영역에 ref 부여
const contentRef = useRef<HTMLDivElement>(null);
// 저장 시 HTML 추출
const captureHtml = () => {
const el = contentRef.current;
if (!el) return '';
// Tailwind 클래스 → 인라인 스타일 변환 (또는 스타일시트 포함)
// 방법 1: 계산된 스타일을 인라인으로
// 방법 2: 필요한 Tailwind CSS를 <style> 태그로 포함
return el.innerHTML;
};
```
**방법 B: 자체 CSS 포함 완전한 HTML 조각**
```html
<div class="document-snapshot">
<style>/* 필요한 스타일만 추출 */</style>
<!-- 문서 컨텐츠 -->
</div>
```
**권장: 방법 B** — MNG에서 Tailwind가 로드되어 있으므로, Tailwind 클래스를 그대로 사용하되 MNG에 없는 커스텀 스타일만 `<style>` 태그로 포함. MNG도 Tailwind를 사용하므로 대부분의 클래스가 호환됨.
### 4.2 MNG 출력 구조 (Phase 3.1)
```blade
{{-- show.blade.php --}}
@if($document->rendered_html)
{{-- 스냅샷 모드: React가 생성한 HTML 그대로 출력 --}}
<div class="document-snapshot-container">
{!! $document->rendered_html !!}
</div>
@else
{{-- 동적 모드: 기존 템플릿 기반 렌더링 (fallback) --}}
@include('documents.partials.dynamic-render')
@endif
```
### 4.3 스타일 호환성 전략
| 항목 | React (Tailwind) | MNG (Tailwind) | 호환성 |
|------|------------------|----------------|--------|
| 기본 클래스 | px-2, py-1, border 등 | 동일 | 완전 호환 |
| 반응형 | sm:, md:, lg: | inline style 정책 | 주의 필요 |
| 커스텀 컴포넌트 | shadcn/ui | 없음 | `<style>` 포함 필요 |
**결론**: React에서 문서 영역은 순수 HTML+Tailwind로 렌더링 (shadcn 컴포넌트 미사용) → MNG 호환성 높음.
단, `@media` 쿼리나 MNG에서 빌드되지 않은 Tailwind 클래스가 있을 수 있으므로 필요 시 인라인 스타일 변환.
### 4.4 XSS 보안 고려
`{!! !!}` (unescaped output) 사용 시 XSS 위험:
- `rendered_html`**자체 시스템(React)에서만 생성** → 외부 입력 아님
- API 저장 시 **sanitize** 처리 권장 (script 태그, on* 이벤트 제거)
- 또는 CSP(Content Security Policy)로 인라인 스크립트 차단
---
## 5. 참고 파일
### React (수정 대상)
- `react/src/components/production/WorkOrders/documents/InspectionReportModal.tsx` — 저장 흐름 (contentWrapperRef)
- `react/src/components/production/WorkOrders/documents/BendingInspectionContent.tsx` — 검사 문서 렌더링
- `react/src/components/production/WorkOrders/documents/BendingWorkLogContent.tsx` — 작업일지 렌더링
- `react/src/components/production/WorkOrders/documents/bending/` — 절곡 섹션 컴포넌트들
- `react/src/components/production/WorkerScreen/WorkLogModal.tsx` — 작업일지 저장 시 캡처
- `react/src/components/material/ReceivingManagement/ImportInspectionInputModal.tsx` — 수입검사 저장 시 오프스크린 캡처
- `react/src/components/material/ReceivingManagement/actions.ts` — saveInspectionData rendered_html 전달
- `react/src/lib/utils/capture-rendered-html.tsx` — 오프스크린 렌더링 유틸리티 (신규)
### API (수정 대상)
- `api/app/Models/Documents/Document.php` — $fillable
- `api/app/Services/DocumentService.php` — store/update/upsert
- `api/app/Http/Requests/Documents/StoreRequest.php` — 검증
- `api/app/Http/Requests/Documents/UpdateRequest.php` — 검증
- `api/app/Http/Requests/Document/UpsertRequest.php` — 검증 (수입검사 경로)
### MNG (수정 대상)
- `mng/resources/views/documents/show.blade.php` — 메인 보기
- `mng/resources/views/documents/print.blade.php` — 인쇄
- `mng/resources/views/documents/partials/bending-inspection-data.blade.php` — 삭제 예정
- `mng/resources/views/documents/partials/bending-worklog.blade.php` — 삭제 예정
- `mng/app/Http/Controllers/DocumentController.php` — show()
### DB
- `api/database/migrations/2026_02_28_100001_add_block_data_to_documents.php` — rendered_html 컬럼 (이미 존재)
### 문서
- `docs/features/documents/README.md` — 문서관리 시스템
- `docs/system/database/documents.md` — DB 스키마
---
## 6. 의존성 및 순서
```
Phase 0 (사전 정리)
Phase 1 (API: rendered_html 저장)
Phase 2 (React: HTML 캡처 + 전송) ← Phase 1 완료 필요
Phase 3 (MNG: 스냅샷 출력) ← Phase 2 완료 후 데이터 존재
Phase 4 (검증 + 정리) ← 모든 Phase 완료 후
```
Phase 1과 Phase 3의 MNG 코드 수정은 병렬 가능 (fallback 유지).
단, 실제 데이터가 있어야 검증 가능하므로 Phase 2 완료 후 통합 검증.
---
## 7. 리스크 및 대응
| 리스크 | 영향 | 대응 |
|--------|------|------|
| HTML 용량 | 문서당 50-200KB, 수만 건 시 GB | LONGTEXT 이미 사용, 필요 시 gzip 압축 |
| Tailwind 클래스 불일치 | MNG에서 일부 스타일 깨짐 | 인라인 스타일 변환 또는 MNG Tailwind 빌드에 포함 |
| XSS | rendered_html에 악성 코드 | API에서 sanitize, CSP 정책 |
| 과도기 fallback | rendered_html 없는 기존 문서 | 기존 동적 렌더링 유지 |
| React 미경유 문서 | MNG에서만 생성된 문서 | MNG 저장 시에도 rendered_html 생성 검토 |
---
## 8. 변경 이력
| 날짜 | 항목 | 변경 내용 | 파일 | 승인 |
|------|------|----------|------|------|
| 2026-03-06 | - | 계획 문서 작성 | - | - |
| 2026-03-06 | Phase 0~3 | Phase 0~3 전체 구현 완료 | API/React/MNG 다수 | ✅ |
| 2026-03-06 | Phase 2.4~2.6 | 모든 저장 경로에 rendered_html 추가 | InspectionReportModal, ImportInspectionInputModal, actions.ts | ✅ |
| 2026-03-06 | Phase 2 보정 | API UpsertRequest rendered_html 누락 수정, DocumentService upsert() rendered_html 전달 추가 | UpsertRequest.php, DocumentService.php | ✅ |
| 2026-03-06 | Phase 2 보정 | ImportInspection: 입력폼 캡처 → 오프스크린 성적서 렌더링으로 변경 | ImportInspectionInputModal.tsx, capture-rendered-html.tsx | ✅ |
| 2026-03-06 | Phase 2 보정 | InspectionReportModal readOnly 자동 캡처 useEffect 제거 | InspectionReportModal.tsx | ✅ |
| 2026-03-06 | 원칙 확장 | Lazy Snapshot 패턴 추가 — 조회 시 rendered_html 없으면 자동 캡처/저장 | 아키텍처 원칙 | - |
---
## 9. 검증 결과
> 작업 완료 후 이 섹션에 검증 결과 추가
### 9.1 성공 기준
| 기준 | 달성 | 비고 |
|------|------|------|
| React 저장 시 rendered_html이 documents 테이블에 저장 | ⏳ | |
| mng.sam.kr/documents/36 에서 rendered_html로 문서 출력 | ⏳ | |
| mng.sam.kr/documents/39 에서 rendered_html로 문서 출력 | ⏳ | |
| rendered_html 없는 기존 문서가 기존대로 렌더링 | ⏳ | |
| 인쇄 시에도 스냅샷 기반 출력 | ⏳ | |
| 양식별 전용 blade 파일 없이 동작 | ⏳ | |
---
## 10. 자기완결성 점검 결과
### 10.1 체크리스트 검증
| # | 검증 항목 | 상태 | 비고 |
|---|----------|:----:|------|
| 1 | 작업 목적이 명확한가? | ✅ | 스냅샷 기반 문서 출력 |
| 2 | 성공 기준이 정의되어 있는가? | ✅ | 9.1 참조 |
| 3 | 작업 범위가 구체적인가? | ✅ | 5 Phase, 15개 작업 |
| 4 | 의존성이 명시되어 있는가? | ✅ | 6. 의존성 참조 |
| 5 | 참고 파일 경로가 정확한가? | ✅ | 5. 참고 파일 |
| 6 | 단계별 절차가 실행 가능한가? | ✅ | 3. 작업 범위 |
| 7 | 검증 방법이 명시되어 있는가? | ✅ | 9.1 성공 기준 |
| 8 | 모호한 표현이 없는가? | ✅ | |
### 10.2 새 세션 시뮬레이션 테스트
| 질문 | 답변 가능 | 참조 섹션 |
|------|:--------:|----------|
| Q1. 이 작업의 목적은 무엇인가? | ✅ | 1.1 배경 |
| Q2. 어디서부터 시작해야 하는가? | ✅ | Phase 0 → 1 → 2 → 3 |
| Q3. 어떤 파일을 수정해야 하는가? | ✅ | 5. 참고 파일 |
| Q4. 작업 완료 확인 방법은? | ✅ | 9.1 성공 기준 |
| Q5. 막혔을 때 참고 문서는? | ✅ | 5. 참고 파일, docs/ |
---
*이 문서는 /plan 스킬로 생성되었습니다.*

View File

@@ -0,0 +1,873 @@
# 제품검사 문서 시스템 구축 계획
> **작성일**: 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<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)
```typescript
// 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`)
```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<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):
```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 <><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 유지
```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 스킬로 생성되었습니다.*

View File

@@ -0,0 +1,245 @@
# MNG 절곡 문서 React 매칭 계획
> **작성일**: 2026-03-05
> **목적**: MNG 절곡 문서(중간검사성적서 #36, 작업일지 #39)의 상세보기를 React와 동일하게 수정
> **상태**: 🔄 진행중
> **영향 범위**: mng만 (React/API 변경 없음)
---
## 현재 진행 상태
| 항목 | 내용 |
|------|------|
| **마지막 완료 작업** | Task 2.2: 절곡 작업일지 전용 렌더링 블록 추가 |
| **다음 작업** | 브라우저 검증 (사용자 확인 필요) |
| **진행률** | 5/6 (83%) |
| **마지막 업데이트** | 2026-03-05 |
---
## 1. 개요
### 1.1 배경
MNG(`mng.sam.kr/documents/36`, `/documents/39`)에서 절곡 문서의 상세보기가 React(`dev.sam.kr/production/worker-screen` > 절곡공정)와 다르다.
- **MNG**: DB 템플릿(`document_templates`, `document_template_columns`)을 동적으로 읽어 범용 렌더링
- **React**: 전용 하드코딩 컴포넌트(`BendingInspectionContent.tsx`, `BendingWorkLogContent.tsx`)로 렌더링
React가 더 상세하고 정확하므로, MNG를 React에 맞춰 수정한다.
### 1.2 핵심 원칙
- MNG only 수정 (React/API 변경 없음)
- DB 템플릿 컬럼 수정 + show.blade.php 전용 렌더링 블록 추가
- 기존 범용 렌더링 로직은 유지 (다른 문서에 영향 없도록)
### 1.3 변경 승인 정책
| 분류 | 예시 | 승인 |
|------|------|------|
| 즉시 가능 | blade 템플릿 수정, DB 시더/컬럼 업데이트 | 불필요 |
| 컨펌 필요 | DocumentController 로직 변경, 새 쿼리 추가 | **필수** |
| 금지 | 테이블 구조 변경, API 엔드포인트 변경 | 별도 협의 |
---
## 2. 차이점 분석
### 2.1 중간검사성적서 (#36) - 중간검사 DATA 테이블
**React 컬럼 구조** (`BendingInspectionContent.tsx`):
| 컬럼 | 타입 | 비고 |
|------|------|------|
| 분류 | text | KWE01 등 제품코드 |
| 제품명 | text | 가이드레일, 케이스 등 |
| 타입 | text | 벽면형, 측면형 등 |
| 겉모양/절곡상태 | check | 양호/불량 체크 |
| 길이 - 도면치수 | text | 설계값 |
| 길이 - 측정값 | input | 입력값 |
| 너비 - 도면치수 | text | 설계값 |
| 너비 - 측정값 | input | 입력값 |
| 간격 - 포인트 | text | 1~5 등 |
| 간격 - 도면치수 | text | 설계값 |
| 간격 - 측정값 | input | 입력값 |
| 판정 | auto | 적/부 자동판정 |
**핵심 차이**:
- React는 **7개 제품** x **다중 gapPoints**(간격 측정점)로 rowSpan 사용
- MNG 현재 DB에는 "간격" 컬럼, "판정" 컬럼 없음
- MNG "분류/제품명"이 하나로 합쳐져 있을 수 있음
**React 제품 목록** (INITIAL_PRODUCTS):
1. 가이드레일 벽면형 (gapPoints: 5개)
2. 가이드레일 측면형 (gapPoints: 5개)
3. 케이스 (gapPoints: 2개)
4. 하단마감재 (gapPoints: 2개)
5. 하단L-BAR (gapPoints: 1개)
6. 연기차단재 W50 (gapPoints: 1개)
7. 연기차단재 W80 (gapPoints: 2개)
### 2.2 작업일지 (#39) - 절곡 전용 구조
**React 구조** (`BendingWorkLogContent.tsx`):
```
헤더: "작업일지 (절곡)" + 문서번호/작성일자 + 결재란
신청업체/신청내용 테이블 (수주일, 현장명, 수주처, 작업일자, 담당자, LOT NO, 연락처, 생산담당자, 출고예정일)
제품 정보 테이블 (제품명, 재질, 마감, 유형)
4개 카테고리 섹션:
1. GuideRailSection (가이드레일 벽면형/측면형)
2. BottomBarSection (하단마감재)
3. ShutterBoxSection (셔터박스)
4. SmokeBarrierSection (연기차단재 W50/W80)
생산량 합계 [kg] (SUS/EGI)
비고
```
**핵심 차이**:
- MNG 현재: 범용 work_order_items 플랫 테이블 (line 137, 섹션 없는 문서)
- React: 절곡 전용 4개 카테고리 섹션 + 제품정보 + 생산량합계
- React는 `bendingInfo` 데이터(work_order.options.bending_info)를 사용
- MNG Controller에서 bending_info를 아직 view에 전달하지 않음
---
## 3. 작업 범위
### Phase 1: 중간검사성적서 (#36)
| # | 작업 항목 | 상태 | 비고 |
|---|----------|:----:|------|
| 1.1 | 현재 DB 템플릿 컬럼 조회 (tinker) | ✅ | template_id=60, 7컬럼 (간격/판정 포함) |
| 1.2 | show.blade.php 절곡 검사 전용 렌더링 블록 추가 | ✅ | partials/bending-inspection-data.blade.php |
| 1.3 | 브라우저 검증 | ⏳ | mng.sam.kr/documents/36 |
### Phase 2: 작업일지 (#39)
| # | 작업 항목 | 상태 | 비고 |
|---|----------|:----:|------|
| 2.1 | DocumentController에 bending_info 전달 추가 | ✅ | work_order.options에서 추출 |
| 2.2 | show.blade.php 절곡 작업일지 전용 렌더링 블록 추가 | ✅ | partials/bending-worklog.blade.php |
| 2.3 | 브라우저 검증 | ⏳ | mng.sam.kr/documents/39 |
---
## 4. 상세 작업 내용
### 4.1 Task 1.2: show.blade.php 절곡 검사 전용 블록
**위치**: `show.blade.php` 섹션 렌더링 영역 (line ~400)
**조건**: template category가 '절곡' 또는 template name에 '절곡' 포함
**렌더링 구조**:
```html
<table>
<thead>
<tr>
<th rowSpan=2>분류</th>
<th rowSpan=2>제품명</th>
<th rowSpan=2>타입</th>
<th rowSpan=2>겉모양/절곡상태</th>
<th colSpan=2>길이</th>
<th colSpan=2>너비</th>
<th colSpan=3>간격</th>
<th rowSpan=2>판정</th>
</tr>
<tr>
<th>도면치수</th><th>측정값</th>
<th>도면치수</th><th>측정값</th>
<th>포인트</th><th>도면치수</th><th>측정값</th>
</tr>
</thead>
<tbody>
<!-- 각 제품별 gapPoints 수만큼 행 (rowSpan 사용) -->
</tbody>
</table>
```
**데이터 소스**: `document_data` EAV + INITIAL_PRODUCTS 정적 구조
- section_id, column_id, row_index, field_key로 조회
- 간격 데이터: field_key `gap_point_{n}`, `gap_design_{n}`, `gap_measured_{n}`
### 4.2 Task 2.1: Controller bending_info 전달
**파일**: `mng/app/Http/Controllers/DocumentController.php` show()
**추가 로직**:
```php
// 절곡 작업일지용: bending_info 추출
$bendingInfo = null;
if ($workOrder) {
$woOptions = json_decode($workOrder->options, true) ?? [];
$bendingInfo = $woOptions['bending_info'] ?? null;
}
```
**view 전달**: `'bendingInfo' => $bendingInfo`
### 4.3 Task 2.2: show.blade.php 절곡 작업일지 전용 블록
**위치**: `show.blade.php` line ~137 (작업일지 전용 영역)
**조건**: 절곡 공정인 경우 (template name에 '절곡' 포함 또는 공정 확인)
**렌더링 구조**:
1. 신청업체/신청내용 테이블
2. 제품 정보 테이블 (제품명, 재질, 마감, 유형)
3. 4개 카테고리 섹션 (가이드레일, 하단마감재, 셔터박스, 연기차단재)
4. 생산량 합계 [kg] (SUS/EGI)
5. 비고
**PHP 유틸 함수 필요**:
- `getMaterialMapping($productCode, $finishMaterial)` - React utils.ts 포팅
- `calculateProductionSummary($bendingInfo, $mapping)` - React utils.ts 포팅
---
## 5. 참고 파일
### React (타겟 - 읽기 전용)
- `react/src/components/production/WorkOrders/documents/BendingInspectionContent.tsx` (547행)
- `react/src/components/production/WorkOrders/documents/BendingWorkLogContent.tsx` (207행)
- `react/src/components/production/WorkOrders/documents/bending/types.ts`
- `react/src/components/production/WorkOrders/documents/bending/utils.ts`
- `react/src/components/production/WorkOrders/documents/bending/GuideRailSection.tsx`
- `react/src/components/production/WorkOrders/documents/bending/BottomBarSection.tsx`
- `react/src/components/production/WorkOrders/documents/bending/ShutterBoxSection.tsx`
- `react/src/components/production/WorkOrders/documents/bending/SmokeBarrierSection.tsx`
- `react/src/components/production/WorkOrders/documents/bending/ProductionSummarySection.tsx`
### MNG (수정 대상)
- `mng/resources/views/documents/show.blade.php` (메인 렌더링)
- `mng/app/Http/Controllers/DocumentController.php` (show 메서드)
### DB 참고
- `mng/database/seeders/MidInspectionTemplateSeeder.php` (절곡 검사 템플릿)
- `mng/database/seeders/WorkLogTemplateSeeder.php` (절곡 작업일지 템플릿)
### 문서
- `docs/features/documents/README.md` - 문서관리 시스템
- `docs/system/database/documents.md` - DB 스키마
- `docs/dev/dev_plans/document-system-mid-inspection.md` - 중간검사 계획
- `docs/dev/dev_plans/document-system-work-log.md` - 작업일지 계획
---
## 6. 변경 이력
| 날짜 | 항목 | 변경 내용 | 파일 | 승인 |
|------|------|----------|------|------|
| 2026-03-05 | - | 계획 문서 작성 | - | - |
---
## 7. 검증 결과
> 작업 완료 후 이 섹션에 검증 결과 추가
### 7.1 성공 기준
| 기준 | 달성 | 비고 |
|------|------|------|
| mng.sam.kr/documents/36 검사 DATA 테이블이 React와 동일 구조 | ⏳ | rowSpan, 간격, 판정 포함 |
| mng.sam.kr/documents/39 작업일지가 React와 동일 구조 | ⏳ | 4개 카테고리, 생산량합계 포함 |
| 기존 다른 문서 렌더링에 영향 없음 | ⏳ | 조건 분기로 절곡 전용만 |

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,282 @@
# SAM 서버 접근 권한 관리
## 개요
SAM 시스템의 서버 및 데이터베이스 접근 권한을 관리합니다.
서버 관리자는 1명이며, 외부 인원에게는 임시로 접근을 허용하고 작업 완료 후 차단합니다.
**최종 업데이트:** 2026-03-07
---
## 운영 관리자
| 계정 | 이름 | 역할 |
|------|------|------|
| `hskwon` | 권혁성 | 서버 관리자 (배포, 운영, 인프라) |
| `pro` | 김보곤 | 서브 관리자 (임시 접근, 평상시 잠금) |
---
## 서버 정보
| 구분 | 호스트 | SSH alias | 용도 |
|------|--------|-----------|------|
| 운영 (prod) | 211.117.60.189 | `sam-prod` | API + React 운영 배포 |
| 개발 (dev) | - | `sam-dev` | 개발 환경 |
| CI/CD | 114.203.209.83 | `sam-cicd` | Jenkins + React 개발 배포 + DB 백업 |
### 서버 시간대
| 설정 | prod | cicd |
|------|------|------|
| OS timezone | Asia/Seoul (KST, +0900) | Asia/Seoul (KST, +0900) |
| NTP 동기화 | 활성 | 활성 |
| PHP CLI (`/etc/php/8.4/cli/php.ini`) | Asia/Seoul | PHP 미설치 |
| PHP FPM (`/etc/php/8.4/fpm/php.ini`) | Asia/Seoul | - |
| Laravel `app.timezone` | Asia/Seoul | - |
> **주의:** PHP timezone이 UTC면 스케줄러 실행 시간이 9시간 어긋남. 반드시 Asia/Seoul로 설정할 것.
---
## OS 계정 현황
### sam-prod (운영서버)
| 계정 | 이름 | 용도 | 상태 |
|------|------|------|------|
| `hskwon` | 권혁성 | 서버 관리자 (배포, 운영) | 활성 |
| `pro` | 김보곤 | 서브 관리자 임시 접근용 | 평상시 **잠금** |
### sam-cicd (CI/CD 서버)
| 계정 | 이름 | 용도 | 상태 |
|------|------|------|------|
| `hskwon` | 권혁성 | 서버 관리자 | 활성 |
| `pro` | 김보곤 | 서브 관리자 임시 접근용 | 평상시 **잠금** |
### sam-dev (개발서버)
| 계정 | 이름 | 용도 | 상태 |
|------|------|------|------|
| `hskwon` | 권혁성 | 서버 관리자 | 활성 |
| `pro` | 김보곤 | 서브 관리자 | 활성 |
---
## OS 계정 잠금/해제
서브 관리자에게 서버 접근이 필요한 경우, 작업 시간 동안만 해제하고 완료 후 다시 잠금합니다.
```bash
# 계정 잠금
sudo usermod -L pro
# 계정 해제
sudo usermod -U pro
# 상태 확인 (비밀번호 필드 앞에 ! 있으면 잠금 상태)
sudo passwd -S pro
```
### 임시 접근 절차
1. 작업 요청 접수 및 범위 확인
2. `sudo usermod -U pro` 로 해제
3. 작업 완료 확인
4. `sudo usermod -L pro` 로 잠금
5. 작업 내용 기록
---
## 공동 관리 구조 (cicd 서버)
인력 변동에 대비하여 cicd 서버는 공동 관리 구조로 운영합니다.
### develop 그룹
```bash
# 그룹 구성원
$ getent group develop
develop:x:1004:hskwon,pro
# 디렉토리 구조
/data/
├── backups/mysql/ # DB 백업 파일 (14일 보관)
└── scripts/ # 운영 스크립트 + .sam_backup.cnf
```
### 권한 설정
- `/data/` 전체: `develop` 그룹 소유, **setgid** 적용
- setgid(`chmod g+s`)로 하위에 생성되는 파일/폴더도 자동으로 `develop` 그룹 상속
- 그룹 권한 = 소유자 권한 (`chmod g=u`)
- 누가 작업하든 다른 관리자가 접근/수정 가능
```bash
# 권한 재설정 필요 시
sudo chown -R hskwon:develop /data
sudo chmod -R g=u /data
sudo chmod g+s /data /data/backups /data/backups/mysql /data/scripts
```
---
## DB 계정 현황
### sam-prod (운영서버 MySQL 8.4)
| 계정 | 인증 플러그인 | 접근 DB | 용도 |
|------|-------------|---------|------|
| `codebridge@localhost` | caching_sha2_password | sam, sam_stage, sam_stat, codebridge | 애플리케이션 (.env) |
| `hskwon@localhost` | caching_sha2_password | 전역 관리자 + sam | 서버 관리자 |
| `pro@localhost` | caching_sha2_password | sam, sam_stage, sam_stat, codebridge | 서브 관리자용 |
### sam-cicd (CI/CD 서버 MySQL 8.4)
| 계정 | 인증 플러그인 | 접근 DB | 용도 |
|------|-------------|---------|------|
| `hskwon@localhost` | caching_sha2_password | 전역 관리자 | 서버 관리자 |
| `pro@localhost` | caching_sha2_password | sam, sam_stage, sam_stat, codebridge, gitea | 서브 관리자용 |
> **참고:** cicd 서버에는 `codebridge` DB 계정이 없음 (애플리케이션이 다른 계정 사용)
### sam-dev (개발서버 MySQL 8.4)
| 계정 | 용도 |
|------|------|
| `pro@localhost` | 서브 관리자용 |
> **참고:** dev 서버는 `sudo mysql` 불가, root 비밀번호 인증 방식
---
## DB 계정 관리
### 계정 잠금/해제 (MySQL)
```sql
-- 계정 잠금
ALTER USER 'pro'@'localhost' ACCOUNT LOCK;
-- 계정 해제
ALTER USER 'pro'@'localhost' ACCOUNT UNLOCK;
-- 상태 확인
SELECT user, host, account_locked FROM mysql.user WHERE user = 'pro';
```
### 인증 플러그인 변경
MySQL 8.4에서는 `mysql_native_password`가 제거되었으므로 `caching_sha2_password`를 사용합니다.
```sql
ALTER USER 'username'@'localhost' IDENTIFIED WITH caching_sha2_password BY '비밀번호';
FLUSH PRIVILEGES;
```
### PhpStorm 접속 시 주의사항
- SSH 터널 필수 (MySQL이 localhost만 허용)
- Host: `localhost` (127.0.0.1 아님)
- `caching_sha2_password` 사용 시 Advanced 탭에서 `allowPublicKeyRetrieval = true` 설정 필요
---
## DB 리플리케이션
운영 DB(prod)에서 cicd 서버로 실시간 리플리케이션이 구성되어 있습니다.
| 항목 | 값 |
|------|---|
| Source (마스터) | sam-prod (211.117.60.189) |
| Replica (슬레이브) | sam-cicd |
| 리플리케이션 사용자 | `sam_backup` |
| 대상 DB | **sam, sam_stat, codebridge** |
| 지연 | 0초 (실시간) |
```sql
-- cicd에서 리플리케이션 상태 확인
SHOW REPLICA STATUS\G
-- 확인 항목
-- Replica_IO_Running: Yes
-- Replica_SQL_Running: Yes
-- Seconds_Behind_Source: 0
```
---
## DB 백업
리플리케이션과 별도로, cicd 서버에서 매일 03:00에 mysqldump 백업을 수행합니다.
### 백업 설정
| 항목 | 값 |
|------|---|
| 스크립트 | `/data/scripts/backup-db.sh` |
| 인증 파일 | `/data/scripts/.sam_backup.cnf` |
| 백업 경로 | `/data/backups/mysql/` |
| 실행 시간 | 매일 03:00 (cron, hskwon 사용자) |
| 보관 기간 | 14일 |
### 백업 대상
| DB | 방식 | 파일명 패턴 |
|----|------|------------|
| sam | 원격 (prod) | `sam_production_YYYYMMDD_HHMMSS.sql.gz` |
| sam_stat | 원격 (prod) | `sam_stat_production_YYYYMMDD_HHMMSS.sql.gz` |
| codebridge | 원격 (prod) | `codebridge_production_YYYYMMDD_HHMMSS.sql.gz` |
| gitea | 로컬 (cicd) | `gitea_YYYYMMDD_HHMMSS.sql.gz` |
### 백업 확인
```bash
# 최근 백업 파일 확인
ls -lt /data/backups/mysql/ | head -10
# 백업 로그 확인
tail /data/backups/mysql/backup.log
```
---
## 배포 계정 관리
### Jenkins 배포 흐름
| 프로젝트 | main | develop |
|----------|------|---------|
| **api** | Jenkins (Stage + Production) | post-update hook (Jenkins 미관여) |
| **react** | Jenkins (Stage + Production) | Jenkins (Development) |
- 배포 SSH 계정: `hskwon` (deploy-ssh-key 크레덴셜)
- Slack 알림: `#deploy_api`, `#deploy_react`
---
## 관리 정책
1. **서버 관리자 1명 원칙** -- 여러 명이 동시에 관리하지 않음
2. **임시 접근만 허용** -- 서브 관리자는 필요 시에만 OS/DB 계정 해제
3. **작업 완료 후 즉시 잠금** -- 해제 상태로 방치하지 않음
4. **DB 접근은 SSH 터널 경유** -- 외부에서 MySQL 직접 접근 불가 (localhost 바인딩)
5. **공동 관리 대비** -- cicd `/data/`는 develop 그룹으로 관리, 인력 변동 대비
6. **이중 백업** -- 리플리케이션(실시간) + mysqldump(매일 03:00) 병행
---
## 참고
- API 보안 가이드: [security-policy.md](./security-policy.md)
- Docker 설정: [docker-setup.md](./docker-setup.md)
- 원격 근무 설정: [remote-work-setup.md](./remote-work-setup.md)
- Jenkins 설정: [../dev/guides/jenkins-setup-guide.md](../dev/guides/jenkins-setup-guide.md)
---
**작성일:** 2026-03-05
**버전:** 1.1
**담당자:** SAM Infrastructure Team