Files
sam-docs/frontend/api-specs/nonconforming-api.md
김보곤 a1a13c1e70 docs: [material] 부적합관리 프론트엔드 API 연동 명세 작성
- 7개 엔드포인트 상세 (요청/응답 JSON 예시)
- 코드 상수 (부적합유형 4종, 상태 4종, 전이 규칙)
- 화면별 구현 가이드 (5개 FormSection, 자재 테이블, 비용 요약)
- React 파일 구조 + Server Actions 예시 코드
2026-03-19 08:42:30 +09:00

14 KiB
Raw Blame History

부적합관리 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)

응답:

{
  "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

응답:

{
  "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}

응답:

{
  "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:

{
  "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:

{
  "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 예시

// 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<string, unknown>) {
  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<string, unknown>) {
  return executeServerAction({
    url: buildApiUrl('/api/v1/material/nonconforming-reports'),
    method: 'POST',
    body: data,
  });
}

// 수정
export async function updateNonconformingReport(id: number, data: Record<string, unknown>) {
  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