Compare commits
10 Commits
db63fcff85
...
03850fefdd
| Author | SHA1 | Date | |
|---|---|---|---|
| 03850fefdd | |||
| cf189fd453 | |||
| b1472f3c35 | |||
| 5bfc89afa7 | |||
| f1683f753e | |||
| 614e90066f | |||
| 8efe0ac477 | |||
| 303de36e1c | |||
| 334c8f3918 | |||
| 0223c33fd9 |
4
INDEX.md
4
INDEX.md
@@ -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) | 품목 마스터 통합 설계 |
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# SAM 프로젝트 문서
|
||||
# SAM 프로젝트 문서
|
||||
|
||||
SAM ERP 시스템의 기술 문서, 비즈니스 규칙, 기능 명세를 관리하는 저장소입니다.
|
||||
|
||||
|
||||
385
dev/dev_plans/document-snapshot-architecture-plan.md
Normal file
385
dev/dev_plans/document-snapshot-architecture-plan.md
Normal 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 스킬로 생성되었습니다.*
|
||||
873
dev/dev_plans/fqc-document-system-plan.md
Normal file
873
dev/dev_plans/fqc-document-system-plan.md
Normal 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 스킬로 생성되었습니다.*
|
||||
245
dev/dev_plans/mng-bending-document-matching-plan.md
Normal file
245
dev/dev_plans/mng-bending-document-matching-plan.md
Normal 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개 카테고리, 생산량합계 포함 |
|
||||
| 기존 다른 문서 렌더링에 영향 없음 | ⏳ | 조건 분기로 절곡 전용만 |
|
||||
1053
dev/dev_plans/quality/quality-management-plan.md
Normal file
1053
dev/dev_plans/quality/quality-management-plan.md
Normal file
File diff suppressed because it is too large
Load Diff
282
system/server-access-management.md
Normal file
282
system/server-access-management.md
Normal 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
|
||||
Reference in New Issue
Block a user