diff --git a/INDEX.md b/INDEX.md index 8addef8..3dfb7d7 100644 --- a/INDEX.md +++ b/INDEX.md @@ -307,6 +307,7 @@ DB 도메인별: | [receiving-create-ux-20260318.md](frontend/api-specs/receiving-create-ux-20260318.md) | 입고등록 UX 개선 요청 (초기값, 상세이동, 413 수정, Dialog 접근성) | | [ai-token-usage-api.md](frontend/api-specs/ai-token-usage-api.md) | AI 토큰사용량 API 명세 (2개 엔드포인트 + 화면 가이드 + 타입 정의) | | [usage-subscription-api.md](frontend/api-specs/usage-subscription-api.md) | 이용현황(구독관리 통합) API 명세 (API 완료, 화면 가이드, 타입 정의, 피드백 반영) | +| [nonconforming-api.md](frontend/api-specs/nonconforming-api.md) | 부적합관리 API 명세 (7개 엔드포인트 + 화면 가이드 + Server Actions 예시) | | [vehicle-react-implementation.md](plans/vehicle-react-implementation.md) | 차량관리 React 구현 요청서 (3개 메뉴, 컴포넌트 구조, 타입 정의) | | [stock-production-react-request.md](plans/stock-production-react-request.md) | 재고생산관리 React 구현 요청서 (수주 화면 단순화, API 스펙 포함) | | [bending-lot-react-request.md](plans/bending-lot-react-request.md) | 절곡품 LOT 재고생산 React 구현 요청서 (캐스케이딩 드롭다운, LOT 자동생성, 취소 복원) | diff --git a/frontend/api-specs/nonconforming-api.md b/frontend/api-specs/nonconforming-api.md new file mode 100644 index 0000000..1dae404 --- /dev/null +++ b/frontend/api-specs/nonconforming-api.md @@ -0,0 +1,501 @@ +# 부적합관리 API 명세 + +> **작성일**: 2026-03-19 +> **상태**: Phase 1 API 완료 +> **대상**: React 프론트엔드 개발자 +> **라우트**: `/material/nonconforming-management` + +--- + +## 1. 개요 + +### 1.1 목적 + +제조/자재 입고 과정에서 발생하는 품질 부적합(불량)을 등록하고, 원인 분석 → 시정 조치 → 비용 산정까지 관리하는 기능이다. + +### 1.2 화면 구성 + +수주서(`OrderRegistration`)와 동일한 `IntegratedDetailTemplate` + `FormSection` 패턴을 따른다. + +- **목록**: `/material/nonconforming-management` +- **등록**: `/material/nonconforming-management?mode=new` +- **상세**: `/material/nonconforming-management/{id}` +- **수정**: `/material/nonconforming-management/{id}?mode=edit` + +### 1.3 핵심 3가지 + +1. **불량 내역** — 무엇이, 언제, 어디서 발생했는지 +2. **처리 방법** — 원인 분석 + 시정/개선 조치 +3. **자재 내역과 비용** — 자재 상세 테이블 + 비용 요약 (4항목 + 합계) + +--- + +## 2. 코드 상수 + +### 2.1 부적합 유형 (`nc_type`) + +| 값 | 라벨 | +|------|------| +| `material` | 자재불량 | +| `process` | 공정불량 | +| `construction` | 시공불량 | +| `other` | 기타 | + +### 2.2 상태 (`status`) + +| 값 | 라벨 | Badge 색상 (권장) | +|------|------|------| +| `RECEIVED` | 접수 | default (회색) | +| `ANALYZING` | 분석중 | warning (노랑) | +| `RESOLVED` | 조치완료 | info (파랑) | +| `CLOSED` | 종결 | success (녹색) | + +### 2.3 상태 전이 규칙 + +``` +RECEIVED → ANALYZING (조건 없음) +ANALYZING → RESOLVED (cause_analysis + corrective_action 필수) +RESOLVED → CLOSED (결재 승인 시 — Phase 2) +``` + +> `CLOSED` 상태에서는 수정/삭제 불가 (API가 403 반환) + +--- + +## 3. API 엔드포인트 + +> **기본 URL**: `/api/v1/material/nonconforming-reports` +> **인증**: `Bearer Token` + `X-API-KEY` + `X-TENANT-ID` + +--- + +### 3.1 목록 조회 + +``` +GET /api/v1/material/nonconforming-reports +``` + +| 파라미터 | 타입 | 필수 | 설명 | +|----------|------|:----:|------| +| `page` | int | - | 페이지 (기본: 1) | +| `per_page` | int | - | 페이지 크기 (기본: 20) | +| `search` | string | - | 검색어 (부적합번호, 현장명, 품목명) | +| `status` | string | - | 상태 필터 (`RECEIVED`, `ANALYZING`, `RESOLVED`, `CLOSED`) | +| `nc_type` | string | - | 유형 필터 (`material`, `process`, `construction`, `other`) | +| `from_date` | date | - | 발생일 시작 (`YYYY-MM-DD`) | +| `to_date` | date | - | 발생일 종료 (`YYYY-MM-DD`) | + +**응답**: + +```json +{ + "success": true, + "message": "데이터를 조회했습니다.", + "data": { + "data": [ + { + "id": 1, + "nc_number": "NC-20260319-001", + "status": "RECEIVED", + "nc_type": "material", + "occurred_at": "2026-03-19", + "confirmed_at": "2026-03-19", + "site_name": "강남 현장", + "total_cost": 180000, + "creator": { "id": 5, "name": "김보곤" }, + "item": { "id": 45, "name": "알루미늄 프레임 50T" }, + "order": { "id": 152, "order_number": "ORD-20260315-001" } + } + ], + "current_page": 1, + "last_page": 1, + "per_page": 20, + "total": 1 + } +} +``` + +--- + +### 3.2 상태별 통계 + +``` +GET /api/v1/material/nonconforming-reports/stats +``` + +**응답**: + +```json +{ + "success": true, + "data": { + "by_status": { + "RECEIVED": 3, + "ANALYZING": 2, + "RESOLVED": 5, + "CLOSED": 10 + }, + "total_count": 20, + "total_cost": 2500000 + } +} +``` + +--- + +### 3.3 단건 조회 + +``` +GET /api/v1/material/nonconforming-reports/{id} +``` + +**응답**: + +```json +{ + "success": true, + "data": { + "id": 1, + "nc_number": "NC-20260319-001", + "status": "RECEIVED", + "nc_type": "material", + "occurred_at": "2026-03-19", + "confirmed_at": "2026-03-19", + "site_name": "강남 현장", + "department": { "id": 3, "name": "제조파트" }, + "order": { "id": 152, "order_number": "ORD-20260315-001", "site_name": "강남 현장" }, + "item": { "id": 45, "name": "알루미늄 프레임 50T" }, + "defect_quantity": 10.00, + "unit": "EA", + "defect_description": "V-CUT 불량 - 절단면 틀어짐", + "cause_analysis": "절단기 날 마모로 인한 정밀도 저하", + "corrective_action": "절단기 날 교체 및 정밀도 재검증", + "action_completed_at": null, + "action_manager": { "id": 8, "name": "이철수" }, + "related_employee": { "id": 12, "name": "박영희" }, + "material_cost": 150000, + "shipping_cost": 30000, + "construction_cost": 0, + "other_cost": 0, + "total_cost": 180000, + "remarks": "", + "drawing_location": "nas2dual/도면/2026/03", + "items": [ + { + "id": 1, + "item_id": 45, + "item_name": "알루미늄 프레임 50T", + "specification": "50 x 3000mm", + "quantity": 10.00, + "unit_price": 15000, + "amount": 150000, + "sort_order": 0, + "remarks": "전량 폐기" + } + ], + "files": [], + "creator": { "id": 5, "name": "김보곤" }, + "created_at": "2026-03-19", + "updated_at": "2026-03-19" + } +} +``` + +--- + +### 3.4 등록 + +``` +POST /api/v1/material/nonconforming-reports +``` + +**요청 Body**: + +```json +{ + "nc_type": "material", + "occurred_at": "2026-03-19", + "confirmed_at": "2026-03-19", + "site_name": "강남 현장", + "department_id": 3, + "order_id": 152, + "item_id": 45, + "defect_quantity": 10, + "unit": "EA", + "defect_description": "V-CUT 불량 - 절단면 틀어짐", + "cause_analysis": "", + "corrective_action": "", + "action_manager_id": 8, + "related_employee_id": 12, + "shipping_cost": 30000, + "construction_cost": 0, + "other_cost": 0, + "drawing_location": "nas2dual/도면/2026/03", + "remarks": "", + "items": [ + { + "item_id": 45, + "item_name": "알루미늄 프레임 50T", + "specification": "50 x 3000mm", + "quantity": 10, + "unit_price": 15000, + "remarks": "전량 폐기" + } + ] +} +``` + +| 필드 | 타입 | 필수 | 설명 | +|------|------|:----:|------| +| `nc_type` | string | ✅ | 부적합유형 (2.1 참고) | +| `occurred_at` | date | ✅ | 발생일 | +| `confirmed_at` | date | - | 불량확인일 | +| `site_name` | string | - | 현장명 | +| `department_id` | int | - | 부서 ID | +| `order_id` | int | - | 관련 수주 ID | +| `item_id` | int | - | 관련 품목 ID | +| `defect_quantity` | number | - | 불량 수량 | +| `unit` | string | - | 단위 (EA, M, KG 등) | +| `defect_description` | string | - | 불량 상세 설명 | +| `cause_analysis` | string | - | 원인 분석 | +| `corrective_action` | string | - | 처리 방안 | +| `action_manager_id` | int | - | 조치 담당자 ID | +| `related_employee_id` | int | - | 관련 직원 ID | +| `shipping_cost` | int | - | 운송 비용 | +| `construction_cost` | int | - | 시공 비용 | +| `other_cost` | int | - | 기타 비용 | +| `drawing_location` | string | - | 도면 저장 위치 | +| `remarks` | string | - | 비고 | +| `items` | array | - | 자재 상세 내역 (아래 참고) | + +**items 배열 항목**: + +| 필드 | 타입 | 필수 | 설명 | +|------|------|:----:|------| +| `item_id` | int | - | 품목 마스터 ID (선택적) | +| `item_name` | string | ✅ | 품목명 | +| `specification` | string | - | 규격/사양 | +| `quantity` | number | - | 수량 | +| `unit_price` | int | - | 단가 | +| `remarks` | string | - | 비고 | + +> `material_cost`는 전송하지 않아도 됨 — items의 `quantity × unit_price` 합계로 자동 계산 +> `total_cost`도 자동 계산: `material_cost + shipping_cost + construction_cost + other_cost` + +**응답**: 단건 조회와 동일한 구조 + +--- + +### 3.5 수정 + +``` +PUT /api/v1/material/nonconforming-reports/{id} +``` + +등록과 동일한 Body. 부분 수정 지원 (변경된 필드만 전송 가능). + +> `items` 배열이 포함되면 기존 items를 **전체 교체** (삭제 후 재생성) +> `items`를 빈 배열 `[]`로 보내면 기존 자재 내역 전부 삭제 + +--- + +### 3.6 삭제 + +``` +DELETE /api/v1/material/nonconforming-reports/{id} +``` + +> `CLOSED` 상태에서는 삭제 불가 (403) + +--- + +### 3.7 상태 변경 + +``` +PATCH /api/v1/material/nonconforming-reports/{id}/status +``` + +**요청 Body**: + +```json +{ + "status": "ANALYZING" +} +``` + +> 전이 규칙 위반 시 422 에러 반환 (섹션 2.3 참고) + +--- + +## 4. 화면별 구현 가이드 + +### 4.1 목록 페이지 + +``` +파일: react/src/components/material/nonconforming/NonconformingList.tsx +``` + +| 영역 | 내용 | +|------|------| +| **상단 통계 카드** | stats API로 상태별 건수 표시 (4개 카드) | +| **필터** | 기간(DateRangePicker), 상태(Select), 유형(Select), 검색(Input) | +| **테이블 컬럼** | No, 부적합번호, 유형, 현장명, 품목명, 발생일, 비용합계, 상태(Badge), 등록자 | + +### 4.2 등록/수정 폼 — 5개 FormSection + +``` +파일: react/src/components/material/nonconforming/NonconformingForm.tsx +``` + +| 섹션 | 필드 | +|------|------| +| **1. 기본 정보** | 부적합번호(자동/읽기전용), 상태(Badge), 발생일, 확인일, 부적합유형(Select), 현장명, 부서, 관련 수주(검색), 관련 직원 | +| **2. 불량 내역** | 관련 품목(검색), 불량 수량, 단위, 불량 상세 설명(Textarea), 첨부파일 | +| **3. 원인 분석 및 처리 방안** | 원인 분석(Textarea), 처리 방안(Textarea), 조치 완료일, 조치 담당자 | +| **4. 자재 내역 및 비용** | 자재 상세 테이블(행 추가/삭제) + 비용 요약(4항목+합계) | +| **5. 비고** | 비고(Textarea), 도면 저장 위치(Input) | + +### 4.3 자재 상세 테이블 + +``` +파일: react/src/components/material/nonconforming/NonconformingItemTable.tsx +``` + +| 컬럼 | 타입 | 설명 | +|------|------|------| +| No | 자동 | 1부터 순번 | +| 품목명 | Input (필수) | `item_name` | +| 규격 | Input | `specification` | +| 수량 | NumberInput | `quantity` | +| 단가 | NumberInput | `unit_price` | +| 금액 | 읽기전용 | `quantity × unit_price` (자동 계산) | +| 비고 | Input | `remarks` | +| 작업 | Button | 삭제 버튼 | + +- 하단에 "행 추가" 버튼 +- 금액 합계 = `material_cost` (비용 요약에 자동 반영) + +### 4.4 비용 요약 + +``` +파일: react/src/components/material/nonconforming/CostSummary.tsx +``` + +| 항목 | 입력 방식 | 설명 | +|------|----------|------| +| 자재 비용 | **읽기 전용** | 자재 상세 테이블 금액 합계 | +| 운송 비용 | NumberInput | 수동 입력 | +| 시공 비용 | NumberInput | 수동 입력 | +| 기타 비용 | NumberInput | 수동 입력 | +| **비용 합계** | **읽기 전용** | 4개 항목 합계 | + +### 4.5 상태별 UI 제어 + +| 상태 | 수정 가능 | 액션 버튼 | +|------|:---------:|----------| +| `RECEIVED` | ✅ | "분석 시작" → `ANALYZING` | +| `ANALYZING` | ✅ | "조치 완료" → `RESOLVED` (원인분석+처리방안 필수) | +| `RESOLVED` | ✅ | "결재상신" (Phase 2) | +| `CLOSED` | ❌ 읽기전용 | 없음 | + +--- + +## 5. React 파일 구조 + +``` +react/src/ +├── app/[locale]/(protected)/material/ +│ └── nonconforming-management/ +│ ├── page.tsx ← mode 분기 (목록/등록) +│ └── [id]/page.tsx ← 상세/수정 +├── components/material/nonconforming/ +│ ├── NonconformingList.tsx ← 목록 + 통계 카드 +│ ├── NonconformingForm.tsx ← 등록/수정 (5개 FormSection) +│ ├── NonconformingDetail.tsx ← 상세 뷰 (읽기전용) +│ ├── NonconformingItemTable.tsx ← 자재 상세 테이블 +│ ├── CostSummary.tsx ← 비용 요약 (4항목+합계) +│ └── actions.ts ← Server Actions +``` + +--- + +## 6. Server Actions 예시 + +```typescript +// actions.ts +'use server'; + +import { buildApiUrl } from '@/lib/api/query-params'; +import { executeServerAction, executePaginatedAction } from '@/lib/api/server-action'; + +// 목록 +export async function getNonconformingReports(params: Record) { + return executePaginatedAction({ + url: buildApiUrl('/api/v1/material/nonconforming-reports', params), + method: 'GET', + }); +} + +// 통계 +export async function getNonconformingStats() { + return executeServerAction({ + url: buildApiUrl('/api/v1/material/nonconforming-reports/stats'), + method: 'GET', + }); +} + +// 단건 +export async function getNonconformingReport(id: number) { + return executeServerAction({ + url: buildApiUrl(`/api/v1/material/nonconforming-reports/${id}`), + method: 'GET', + }); +} + +// 등록 +export async function createNonconformingReport(data: Record) { + return executeServerAction({ + url: buildApiUrl('/api/v1/material/nonconforming-reports'), + method: 'POST', + body: data, + }); +} + +// 수정 +export async function updateNonconformingReport(id: number, data: Record) { + return executeServerAction({ + url: buildApiUrl(`/api/v1/material/nonconforming-reports/${id}`), + method: 'PUT', + body: data, + }); +} + +// 삭제 +export async function deleteNonconformingReport(id: number) { + return executeServerAction({ + url: buildApiUrl(`/api/v1/material/nonconforming-reports/${id}`), + method: 'DELETE', + }); +} + +// 상태 변경 +export async function changeNonconformingStatus(id: number, status: string) { + return executeServerAction({ + url: buildApiUrl(`/api/v1/material/nonconforming-reports/${id}/status`), + method: 'PATCH', + body: { status }, + }); +} +``` + +--- + +## 관련 문서 + +- `dev/dev_plans/nonconforming-management-plan.md` — 기획서 (전체 Phase 계획) +- `rules/item-policy.md` — 품목 정책 +- `features/quotes/README.md` — 수주 시스템 (폼 구조 참고) + +--- + +**최종 업데이트**: 2026-03-19