- integrated-phase-3.md: 3.1~3.5 전체 ✅, 변경이력 추가 - integrated-master-plan.md: 상태 5/7, Phase 3 → ✅ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
363 lines
12 KiB
Markdown
363 lines
12 KiB
Markdown
# Phase 3: 절곡 검사 동적 구현
|
|
|
|
> **통합 계획**: [`integrated-master-plan.md`](./integrated-master-plan.md)
|
|
> **원본**: [`document-system-improvement-plan.md`](./document-system-improvement-plan.md) Phase 2
|
|
> **상태**: ✅ 구현 완료
|
|
> **의존성**: Phase 1 (product_code 전파) + Phase 2A (API 설계) 완료 필수
|
|
> **리뷰 문서**: [`document-system-improvement-review.md`](./document-system-improvement-review.md)
|
|
|
|
---
|
|
|
|
## 1. 개요
|
|
|
|
### 1.1 목표
|
|
|
|
API 기반 동적 구성품 로딩으로 `buildBendingProducts()` 고정 로직을 대체한다.
|
|
|
|
Phase 1에서 `product_code` 전파 버그를 수정하고, Phase 2A에서 API 설계를 완료한 뒤, 이 Phase에서 실제 구현을 진행한다.
|
|
|
|
### 1.2 핵심 구현 항목
|
|
|
|
- `inspection-config` API 구현 (공정 자동 판별)
|
|
- `TemplateInspectionContent` API 연동 (`buildBendingProducts` 대체)
|
|
- `document_data` EAV 저장/복원 검증
|
|
- 트랜잭션 보강 (`lockForUpdate` + `DB::transaction`)
|
|
|
|
### 1.3 선행 완료 커밋 (react/)
|
|
|
|
| 커밋 | 내용 |
|
|
|------|------|
|
|
| `7b8b5cf5` | feat: TemplateInspectionContent 절곡 bending save/restore 지원 |
|
|
| `54716e63` | feat: InspectionReportModal에서 documentRecords prop 전달 |
|
|
| `36052f3e` | fix: bending 개소별 저장 fallback 조건 수정 |
|
|
|
|
---
|
|
|
|
## 2. 작업 항목
|
|
|
|
| # | 작업 항목 | 상태 | 비고 |
|
|
|---|----------|:----:|------|
|
|
| 3.1 | `inspection-config` API 구현 (공정 자동 판별) | ✅ | `BENDING_GAP_PROFILES` 상수 + `resolveInspectionProcessType` |
|
|
| 3.2 | `TemplateInspectionContent` API 연동 (`buildBendingProducts` 대체) | ✅ | API 우선 → `buildBendingProducts` fallback |
|
|
| 3.3 | `document_data` EAV 저장/복원 검증 | ✅ | productIdx 순서 일치 확인 (벽면/측면 모두) |
|
|
| 3.4 | 기존 절곡 검사 데이터 하위 호환 확인 | ✅ | Path A 미수정, Path B fallback 유지 |
|
|
| 3.5 | `createInspectionDocument` 트랜잭션 보강 | ✅ | `DB::transaction` + `lockForUpdate` 적용 |
|
|
|
|
---
|
|
|
|
## 3. 상세 구현 내용
|
|
|
|
### 3.1 inspection-config API 구현
|
|
|
|
Phase 2A 설계 기반. 엔드포인트: `GET /api/v1/work-orders/{id}/inspection-config`
|
|
|
|
#### 3.1.1 구현 방향
|
|
|
|
```php
|
|
// WorkOrderController 또는 신규 InspectionConfigController
|
|
public function inspectionConfig(WorkOrder $workOrder)
|
|
{
|
|
// 1. 공정 타입 자동 판별
|
|
$processType = $workOrder->process->type; // screen, slat, bending, etc.
|
|
|
|
// 2. product_code 추출 (Phase 1에서 수정됨)
|
|
$productCode = $workOrder->items->first()?->options['product_code'] ?? null;
|
|
|
|
// 3. BOM 기반 구성품 조회 (절곡만)
|
|
$items = [];
|
|
if ($processType === 'bending') {
|
|
$items = $this->getBendingComponents($productCode, $workOrder);
|
|
}
|
|
|
|
// 4. 템플릿 ID 조회
|
|
$templateId = $workOrder->process->steps()
|
|
->where('needs_inspection', true)
|
|
->first()?->document_template_id;
|
|
|
|
return ApiResponse::handle(fn() => [
|
|
'work_order_id' => $workOrder->id,
|
|
'process_type' => $processType,
|
|
'product_code' => $productCode,
|
|
'template_id' => $templateId,
|
|
'items' => $items,
|
|
]);
|
|
}
|
|
```
|
|
|
|
#### 3.1.2 핵심 요구사항
|
|
|
|
| 항목 | 요구사항 | 정책 근거 |
|
|
|------|----------|----------|
|
|
| 멀티테넌시 | `BelongsToTenant` 스코프 필수 적용 | M1 |
|
|
| 응답 시간 | < 200ms | M2 |
|
|
| Fallback | BOM 미등록 시 빈 `items` 배열 반환 | C5 |
|
|
| 공정 판별 | 프론트가 공정 타입을 하드코딩하지 않음 | I5 |
|
|
|
|
#### 3.1.3 응답 구조 (예시)
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"work_order_id": 123,
|
|
"process_type": "bending",
|
|
"product_code": "KWE01",
|
|
"template_id": 45,
|
|
"items": [
|
|
{
|
|
"id": "guide_rail_wall",
|
|
"name": "가이드레일 벽면",
|
|
"gap_points": [30, 78, 25, 45],
|
|
"labels": ["상", "중상", "중하", "하"]
|
|
},
|
|
{
|
|
"id": "guide_rail_front",
|
|
"name": "가이드레일 정면",
|
|
"gap_points": [30, 78, 25, 45],
|
|
"labels": ["상", "중상", "중하", "하"]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
```
|
|
|
|
> BOM 미등록이거나 `product_code`가 없는 경우 `items: []`를 반환하고, 프론트에서 `DEFAULT_GAP_PROFILES`를 사용한다.
|
|
|
|
---
|
|
|
|
### 3.2 TemplateInspectionContent API 연동
|
|
|
|
#### 3.2.1 현재 코드 구조 (AS-IS)
|
|
|
|
```typescript
|
|
// TemplateInspectionContent.tsx
|
|
const DEFAULT_GAP_PROFILES = { /* L176-206 */ };
|
|
|
|
function buildBendingProducts(order): BendingProduct[] { /* L209-274 */ }
|
|
|
|
const isBending =
|
|
order.processType === 'bending' || columns에 point sub_labels 존재;
|
|
```
|
|
|
|
`buildBendingProducts()`가 `order.bendingInfo`에서 동적 구성품을 생성하지만, 프론트 데이터에 의존하며 BOM 기반이 아니다.
|
|
|
|
#### 3.2.2 변경 내용 (TO-BE)
|
|
|
|
```typescript
|
|
// TemplateInspectionContent.tsx (Phase 3 변경)
|
|
|
|
interface InspectionConfig {
|
|
process_type: string;
|
|
product_code: string;
|
|
items: BendingInspectionItem[];
|
|
}
|
|
|
|
// API 호출 (React Query 또는 Server Action)
|
|
const { data: config, isLoading } = useInspectionConfig(workOrderId);
|
|
|
|
// fallback: API 실패/미응답 → buildBendingProducts 기본값
|
|
const bendingProducts = config?.items?.length
|
|
? mapConfigToProducts(config.items)
|
|
: buildBendingProducts(order);
|
|
```
|
|
|
|
#### 3.2.3 변경 범위
|
|
|
|
| 변경 대상 | 내용 | 비고 |
|
|
|----------|------|------|
|
|
| API 호출 추가 | `useInspectionConfig(workOrderId)` 훅 또는 Server Action | 신규 |
|
|
| `buildBendingProducts` | 유지 (fallback 용도) | 기존 코드 수정 없음 |
|
|
| `DEFAULT_GAP_PROFILES` | API 응답의 `gap_points`로 대체, 미응답 시 기존값 사용 | I1 기준 통일 |
|
|
| `bendingExpandedRows` | API 기반 구성품으로 행 확장 | 기존 로직 활용 |
|
|
|
|
---
|
|
|
|
### 3.3 document_data EAV 저장 구조 (C1 정책)
|
|
|
|
#### 3.3.1 저장 구조
|
|
|
|
```
|
|
row_index 의미: 모든 공정에서 "개소(WorkOrderItem)" 통일
|
|
|
|
절곡 field_key 패턴:
|
|
├─ b{productIdx}_ok → 구성품 OK/NG 판정
|
|
├─ b{productIdx}_ng → NG 상세
|
|
├─ b{productIdx}_value → 길이/너비 측정값
|
|
├─ b{productIdx}_judgment → 종합 판정
|
|
├─ b{productIdx}_p{pointIdx}_n1 → 간격 포인트 측정값 1
|
|
├─ b{productIdx}_p{pointIdx}_n2 → 간격 포인트 측정값 2
|
|
└─ b{productIdx}_n{n} → 추가 측정값
|
|
```
|
|
|
|
> 이미 save/restore 구현 완료 (커밋 `7b8b5cf5`)
|
|
> `productIdx`는 `buildBendingProducts()` 반환 배열의 인덱스
|
|
|
|
#### 3.3.2 BOM 동적화 시 향후 전환 (C4)
|
|
|
|
| 구분 | 현행 (Phase 3) | 향후 (BOM 동적화) |
|
|
|------|---------------|------------------|
|
|
| `field_key` 형식 | `b{productIdx}_...` | `b{productId}_...` |
|
|
| 매핑 기준 | 배열 인덱스 | 구성품 식별자 |
|
|
| BOM 스냅샷 | 없음 | `document.options.bom_snapshot` 저장 |
|
|
|
|
---
|
|
|
|
### 3.4 하위호환 (C2)
|
|
|
|
두 개의 독립적인 데이터 경로가 존재한다.
|
|
|
|
```
|
|
Path A: InspectionInputModal
|
|
→ work_order_items.options.inspection_data
|
|
→ 개소별 빠른 입력 (기존 유지)
|
|
|
|
Path B: TemplateInspectionContent
|
|
→ document_data EAV
|
|
→ 검사 성적서 (신규 방식)
|
|
|
|
→ 두 경로 독립 동작, 마이그레이션 불필요
|
|
→ 레거시 데이터(Path A)는 그대로 유지
|
|
→ 신규 데이터(Path B)는 EAV에 저장
|
|
```
|
|
|
|
#### 3.4.1 하위호환 보장 조건
|
|
|
|
| 조건 | 설명 | 검증 방법 |
|
|
|------|------|----------|
|
|
| Path A 무변경 | `InspectionInputModal`의 절곡 입력/저장 로직 건드리지 않음 | 기존 데이터 정상 표시 확인 |
|
|
| Path B 독립 | `TemplateInspectionContent`는 `document_data` EAV만 사용 | 신규 저장→조회 사이클 검증 |
|
|
| 롤백 가능 | `document_template_id` NULL 설정 시 레거시 컴포넌트 자동 복귀 | I4 정책 결정 |
|
|
|
|
---
|
|
|
|
### 3.5 createInspectionDocument 트랜잭션 보강 (I2)
|
|
|
|
#### 3.5.1 현재 문제
|
|
|
|
`WorkOrderService::createInspectionDocument`에 `DB::transaction()` 없이 조회→분기→create/update를 실행한다. 절곡 동적화로 API 호출이 추가되면 race window가 확대된다.
|
|
|
|
#### 3.5.2 수정 내용
|
|
|
|
```php
|
|
// WorkOrderService::createInspectionDocument
|
|
public function createInspectionDocument(WorkOrder $workOrder, ...)
|
|
{
|
|
return DB::transaction(function () use ($workOrder, ...) {
|
|
// lockForUpdate로 동시 생성 방지
|
|
$workOrder->lockForUpdate();
|
|
|
|
// 기존 document 중복 체크
|
|
$existing = $workOrder->documents()
|
|
->where('template_id', $templateId)
|
|
->first();
|
|
|
|
if ($existing) {
|
|
return $existing; // 이미 존재하면 반환
|
|
}
|
|
|
|
// 신규 생성
|
|
return $this->documentService->create(...);
|
|
});
|
|
}
|
|
```
|
|
|
|
#### 3.5.3 적용 범위
|
|
|
|
| 항목 | 설명 |
|
|
|------|------|
|
|
| `lockForUpdate()` | 동일 `WorkOrder`에 대한 동시 문서 생성 방지 |
|
|
| `DB::transaction()` | 조회→분기→생성 전체를 원자적으로 처리 |
|
|
| 중복 체크 | `template_id` 기준 기존 문서 존재 시 생성 대신 반환 |
|
|
| 별도 작업 | 절곡 동적화와 무관하게 기존 코드 결함 수정 (I2) |
|
|
|
|
---
|
|
|
|
## 4. 데이터 경로 다이어그램
|
|
|
|
```
|
|
작업지시(WorkOrder)
|
|
│
|
|
├─ inspection-config API ← Phase 3 구현
|
|
│ ├─ process_type 자동 판별
|
|
│ ├─ product_code 추출 (Phase 1에서 수정됨)
|
|
│ └─ BOM 기반 구성품 목록 반환
|
|
│
|
|
├─ TemplateInspectionContent (React)
|
|
│ ├─ AS-IS: buildBendingProducts() 고정 로직
|
|
│ └─ TO-BE: inspection-config API → 동적 구성품
|
|
│ └─ fallback: buildBendingProducts() 유지
|
|
│
|
|
├─ Path A: InspectionInputModal → options.inspection_data
|
|
│ └─ 개소별 빠른 입력 (기존 유지)
|
|
│
|
|
└─ Path B: TemplateInspectionContent → document_data EAV
|
|
├─ row_index = 개소(WorkOrderItem)
|
|
├─ field_key = b{idx}_ok, b{idx}_p{pt}_n1 등
|
|
└─ save/restore 구현 완료 (7b8b5cf5)
|
|
```
|
|
|
|
---
|
|
|
|
## 5. 검증 계획
|
|
|
|
| # | 테스트 | 예상 결과 | 실제 결과 | 상태 |
|
|
|---|--------|----------|----------|:----:|
|
|
| 1 | KWE01 → 구성품 표시 | `buildBendingProducts` 결과와 동일 | | ⏳ |
|
|
| 2 | KSS01 → 다른 구성품 | KSS01 전용 구성품 | | ⏳ |
|
|
| 3 | KSS02 → 다른 구성품 | KSS02 전용 구성품 | | ⏳ |
|
|
| 4 | 마감유형 S1/S2/S3 | 유형별 차이 반영 | | ⏳ |
|
|
| 5 | 구성품 수 7개 미만/초과 | 정상 렌더링 | | ⏳ |
|
|
| 6 | API 미응답 시 fallback | `buildBendingProducts` 기본값 | | ⏳ |
|
|
| 7 | BOM 미등록 시 | `DEFAULT_GAP_PROFILES` 사용 | | ⏳ |
|
|
| 8 | 저장→조회→재저장 사이클 | 데이터 무손실 | | ⏳ |
|
|
| 9 | 기존 절곡 데이터 (Path A) | 정상 표시 | | ⏳ |
|
|
| 10 | 신규 절곡 데이터 (Path B) | EAV 정상 동작 | | ⏳ |
|
|
| 11 | mng `show.blade.php` 렌더링 | 성적서 정상 표시 | | ⏳ |
|
|
| 12 | `inspection-config` API 응답 | < 200ms | | ⏳ |
|
|
| 13 | 스크린/슬랫 회귀 | 변화 없음 | | ⏳ |
|
|
| 14 | 트랜잭션 동시 접근 (I2) | race condition 없음 | | ⏳ |
|
|
|
|
---
|
|
|
|
## 6. 참고 파일
|
|
|
|
### 6.1 React
|
|
|
|
| 파일 | 역할 | 비고 |
|
|
|------|------|------|
|
|
| `react/.../documents/TemplateInspectionContent.tsx` | 통합 방향 (C3) | L176-274 |
|
|
| `react/.../documents/BendingInspectionContent.tsx` | 레거시 동결 (C3) | L71-135 |
|
|
| `react/.../documents/InspectionReportModal.tsx` | 모달 래퍼 | `documentRecords` 전달 완료 |
|
|
| `react/.../documents/inspection-shared.tsx` | 공유 유틸 | |
|
|
| `react/.../WorkerScreen/InspectionInputModal.tsx` | Path A 유지 | ~950행 |
|
|
|
|
### 6.2 API
|
|
|
|
| 파일 | 역할 |
|
|
|------|------|
|
|
| `api/app/Services/WorkOrderService.php` | `createInspectionDocument` (I2 보강) |
|
|
| `api/app/Services/DocumentService.php` | 문서 CRUD |
|
|
| `api/app/Http/Controllers/V1/DocumentController.php` | 문서 API |
|
|
|
|
### 6.3 5130 레거시
|
|
|
|
| 파일 | 역할 |
|
|
|------|------|
|
|
| `5130/output/view_inspection_bending.php` | 절곡 중간검사 |
|
|
| `5130/estimate/common/common_addrowJS.php` | 구성품 정의 |
|
|
|
|
---
|
|
|
|
## 7. 변경 이력
|
|
|
|
| 날짜 | 항목 | 변경 내용 |
|
|
|------|------|----------|
|
|
| 2026-02-27 | 문서 작성 | 통합 계획 Phase 3 상세 문서 작성 |
|
|
| 2026-02-27 | 3.5 완료 | `createInspectionDocument` DB::transaction + lockForUpdate 적용 |
|
|
| 2026-02-27 | 3.1 완료 | `inspection-config` API 구현 (Service + Controller + Route) |
|
|
| 2026-02-27 | 3.2 완료 | `TemplateInspectionContent` API 연동 (inspectionConfig state + fallback) |
|
|
| 2026-02-27 | 3.3+3.4 완료 | EAV productIdx 순서 호환 확인, Path A/B 독립 동작 확인 |
|
|
|
|
---
|
|
|
|
*이 문서는 [`integrated-master-plan.md`](./integrated-master-plan.md)의 Phase 3 상세입니다.* |