Files
sam-docs/dev/dev_plans/nonconforming-management-plan.md
김보곤 32e761773d docs: [material] 부적합관리 기획서 API/FE 분리 단계별 계획 재구성
- Phase별 API 선행 → FE 후행 구조로 분리
- 각 Phase마다 API 산출물, FE 전달자료 명시
- API 엔드포인트를 Phase별로 분류
- 병행 개발 일정 다이어그램 추가
2026-03-19 08:29:51 +09:00

24 KiB

자재관리 - 부적합관리 기획서

작성일: 2026-03-19 상태: 기획 중 담당: R&D실 라우트: /material/nonconforming-management


1. 개요

1.1 목적

제조 공정 및 자재 입고 과정에서 발생하는 품질 부적합(불량)을 체계적으로 기록하고, 원인 분석 → 시정 조치 → 비용 산정까지 일괄 관리하는 기능을 제공한다.

1.2 핵심 컨셉

SAM 수주서의 화면 구조(FormSection 기반 섹션 레이아웃 + 하단 품목 테이블)를 그대로 따르되, 3가지 핵심 데이터에 집중한다:

┌─────────────────────────────────────────────┐
│  1. 불량 내역 — 무엇이, 언제, 어디서 발생   │
│  2. 처리 방법 — 원인 분석 + 시정/개선 조치   │
│  3. 자재 내역과 비용 — 손실 비용 산정         │
└─────────────────────────────────────────────┘

1.3 참고

  • 타사 양식: "품질불량(부적합) 원인분석 및 개선 대책 보고서"
  • SAM 내부: 수주 등록(OrderRegistration.tsx) 구조 준용

2. 화면 구성

SAM 수주서처럼 IntegratedDetailTemplate + FormSection 패턴을 따른다. 목록 → 등록/상세 → 수정 모드를 ?mode=new|edit 쿼리파라미터로 분기한다.

2.1 목록 페이지

/material/nonconforming-management

필터 타입 설명
기간 DateRangePicker 발생일 기준
상태 Select 전체/접수/분석중/조치완료/종결
부적합 유형 Select 자재불량/공정불량/시공불량/기타
검색 Input 부적합번호, 현장명, 품목명
순서 컬럼 설명
1 체크박스 행 선택
2 No. 자동 번호
3 부적합번호 NC-YYYYMMDD-NNN
4 부적합 유형 자재불량/공정불량/시공불량/기타
5 현장명 발생 현장
6 품목명 관련 품목
7 발생일 불량 발생 날짜
8 비용 합계 자재+운송+시공+기타
9 상태 Badge (접수/분석중/조치완료/종결)
10 등록자 작성자명

2.2 등록/상세 페이지 — 섹션 구성

/material/nonconforming-management?mode=new /material/nonconforming-management/{id} /material/nonconforming-management/{id}?mode=edit

수주서와 동일한 패턴으로 5개 FormSection을 배치한다:

┌─ IntegratedDetailTemplate ──────────────────────────────────┐
│                                                             │
│  [섹션 1] 기본 정보                                         │
│  ┌─────────────────────────────────────────────────────┐    │
│  │ 부적합번호  │ 상태(Badge)                            │    │
│  │ 발생일      │ 확인일       │ 부적합 유형             │    │
│  │ 현장명 (🔍 수주 연결)      │ 부서                    │    │
│  │ 등록자      │ 관련 직원    │ 관련 수주번호           │    │
│  └─────────────────────────────────────────────────────┘    │
│                                                             │
│  [섹션 2] 불량 내역 ★핵심                                   │
│  ┌─────────────────────────────────────────────────────┐    │
│  │ 관련 품목 (🔍 품목 검색)    │ 불량 수량  │ 단위      │    │
│  │ 불량 상세 설명 (Textarea)                            │    │
│  │ 첨부파일 (이미지/문서 다중 업로드)                   │    │
│  └─────────────────────────────────────────────────────┘    │
│                                                             │
│  [섹션 3] 원인 분석 및 처리 방안 ★핵심                      │
│  ┌─────────────────────────────────────────────────────┐    │
│  │ 불량 발생 원인 및 분석 (Textarea)                    │    │
│  │ 처리 방안 및 개선 사항 (Textarea)                    │    │
│  │ 시정 조치 완료일  │ 조치 담당자                      │    │
│  └─────────────────────────────────────────────────────┘    │
│                                                             │
│  [섹션 4] 자재 내역 및 비용 ★핵심                           │
│  ┌─────────────────────────────────────────────────────┐    │
│  │ 원자재 및 자재 소요량 설명 (Textarea)                │    │
│  │ ┌─ 자재 상세 테이블 ───────────────────────────┐    │    │
│  │ │ No │ 품목명 │ 규격 │ 수량│단가│금액│비고    │    │    │
│  │ │ 1  │ ...    │ ...  │    │    │    │        │    │    │
│  │ │    │        │      │    │    │    │ [+추가] │    │    │
│  │ └──────────────────────────────────────────────┘    │    │
│  │ ┌─ 비용 요약 ─────────────────────────────────┐    │    │
│  │ │ 자재비용 │ 운송비용 │ 시공비용 │ 기타 │ 합계 │    │    │
│  │ └──────────────────────────────────────────────┘    │    │
│  └─────────────────────────────────────────────────────┘    │
│                                                             │
│  [섹션 5] 비고                                              │
│  ┌─────────────────────────────────────────────────────┐    │
│  │ 비고 (Textarea)                                      │    │
│  │ 도면 저장 위치 (Input)                                │    │
│  └─────────────────────────────────────────────────────┘    │
│                                                             │
│  ── sticky bottom bar ──────────────────────────────────    │
│  │ [취소]                        [저장] / [결재상신]    │    │
│  ────────────────────────────────────────────────────────   │
└─────────────────────────────────────────────────────────────┘

2.3 상태 워크플로우

접수(RECEIVED) → 분석중(ANALYZING) → 조치완료(RESOLVED) → 종결(CLOSED)
                                                          ↗
                                  → 결재상신 → 승인 시 종결
상태 설명 액션
RECEIVED 부적합 접수 (초기 상태) 등록 시 자동
ANALYZING 원인 분석 중 분석 시작 클릭
RESOLVED 시정 조치 완료 조치 완료 클릭
CLOSED 종결 (결재 승인 후) 결재 승인 시 자동

3. 데이터 모델

3.1 nonconforming_reports (부적합 보고서)

api 프로젝트에서 마이그레이션 관리 (공용 테이블)

컬럼 타입 설명
id bigint PK
tenant_id bigint FK 테넌트
nc_number varchar(30) 부적합번호 NC-YYYYMMDD-NNN
status varchar(20) RECEIVED/ANALYZING/RESOLVED/CLOSED
nc_type varchar(20) 부적합 유형 (material/process/construction/other)
occurred_at date 발생일
confirmed_at date 불량 확인일
site_name varchar(100) 현장명
department_id bigint FK nullable 부서
order_id bigint FK nullable 관련 수주
item_id bigint FK nullable 관련 품목
defect_quantity decimal(10,2) nullable 불량 수량
unit varchar(20) nullable 단위
defect_description text nullable 불량 상세 설명
cause_analysis text nullable 원인 분석
corrective_action text nullable 처리 방안 및 개선 사항
action_completed_at date nullable 조치 완료일
action_manager_id bigint FK nullable 조치 담당자
related_employee_id bigint FK nullable 관련 직원
material_cost decimal(12,0) default 0 자재 비용
shipping_cost decimal(12,0) default 0 운송 비용
construction_cost decimal(12,0) default 0 시공 비용
other_cost decimal(12,0) default 0 기타 비용
total_cost decimal(12,0) default 0 비용 합계 (자동 계산)
remarks text nullable 비고
drawing_location varchar(255) nullable 도면 저장 위치
options json nullable 확장 속성
created_by bigint FK 등록자
updated_by bigint FK nullable
deleted_by bigint FK nullable
created_at timestamp
updated_at timestamp
deleted_at timestamp nullable 소프트 삭제

3.2 nonconforming_report_items (부적합 자재 상세)

수주의 order_items처럼 부적합 보고서에 연결된 자재 내역

컬럼 타입 설명
id bigint PK
tenant_id bigint FK 테넌트
nonconforming_report_id bigint FK 부적합 보고서
item_id bigint FK nullable 품목 마스터 연결
item_name varchar(100) 품목명 (수동 입력 허용)
specification varchar(100) nullable 규격/사양
quantity decimal(10,2) default 0 수량
unit_price decimal(12,0) default 0 단가
amount decimal(12,0) default 0 금액 (수량 x 단가)
sort_order int default 0 정렬 순서
remarks varchar(255) nullable 비고
options json nullable 확장 속성
created_at timestamp
updated_at timestamp

3.3 첨부파일

기존 SAM 파일 시스템(files 테이블 polymorphic) 활용:

// NonconformingReport 모델
public function files(): MorphMany
{
    return $this->morphMany(File::class, 'fileable');
}

4. API 엔드포인트

prefix: /api/v1/material/nonconforming-reports

Method Path 설명
GET / 목록 조회 (필터/검색/페이지네이션)
GET /stats 상태별 통계
GET /{id} 단건 조회 (items, files 포함)
POST / 등록 (items 포함 일괄 저장)
PUT /{id} 수정 (items sync)
DELETE /{id} 소프트 삭제
PATCH /{id}/status 상태 변경
POST /{id}/files 첨부파일 업로드
DELETE /{id}/files/{fileId} 첨부파일 삭제

4.1 등록 요청 예시

{
  "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": "절단기 날 교체 및 정밀도 재검증",
  "material_cost": 150000,
  "shipping_cost": 30000,
  "construction_cost": 0,
  "other_cost": 0,
  "drawing_location": "nas2dual/도면/2026/03",
  "related_employee_id": 12,
  "remarks": "",
  "items": [
    {
      "item_id": 45,
      "item_name": "알루미늄 프레임 50T",
      "specification": "50 x 3000mm",
      "quantity": 10,
      "unit_price": 15000,
      "remarks": "전량 폐기"
    }
  ]
}

4.2 목록 응답 예시

{
  "data": [
    {
      "id": 1,
      "nc_number": "NC-20260319-001",
      "nc_type": "material",
      "nc_type_label": "자재불량",
      "site_name": "강남 현장",
      "item_name": "알루미늄 프레임 50T",
      "occurred_at": "2026-03-19",
      "total_cost": 180000,
      "status": "RECEIVED",
      "status_label": "접수",
      "creator_name": "김보곤"
    }
  ],
  "meta": { "current_page": 1, "total": 15 }
}

5. 비즈니스 규칙

5.1 채번 규칙

항목 규칙
형식 NC-YYYYMMDD-NNN
예시 NC-20260319-001
리셋 일자별 순번 리셋
테넌트 테넌트별 독립 채번

5.2 비용 자동 계산

total_cost = material_cost + shipping_cost + construction_cost + other_cost
material_cost = SUM(items[].amount) (자재 상세 테이블 합계로 자동 산정)
  • 자재 상세 테이블의 금액 합계가 변경되면 material_cost 자동 갱신
  • total_cost는 4개 비용 항목의 합계로 자동 계산 (프론트/백엔드 양쪽)

5.3 상태 전이 규칙

현재 상태 가능한 전이 조건
RECEIVED → ANALYZING
ANALYZING → RESOLVED cause_analysis + corrective_action 필수
RESOLVED → CLOSED 결재 승인 시
CLOSED (변경 불가) 종결 후 수정 불가 (열람만)

5.4 수주 연결

  • 현장명 입력 시 기존 수주를 검색하여 연결 가능 (선택적)
  • 수주 연결 시 현장명, 거래처 등 기본 정보 자동 채움
  • 연결 없이도 수동 입력 허용

5.5 결재 연동

  • SAM 기존 결재 시스템(approval_requests 테이블)과 연동
  • "결재상신" 버튼으로 결재 요청 생성
  • 결재 승인 시 상태가 CLOSED로 자동 전환

6. 단계별 구현 계획

원칙: 각 Phase에서 API를 먼저 완성한 후 프론트엔드 개발을 시작한다. API 완료 시점에 프론트엔드 개발자에게 API 명세를 전달한다.

Phase 1: API 완성 → FE 개발 (병행 가능)
Phase 2: API 완성 → FE 개발 (병행 가능)
Phase 3: API 완성 → FE 개발 (병행 가능)

Phase 1 — 기본 CRUD + 비용 산정

목표: 부적합 보고서 등록/조회/수정/삭제 + 자재 내역 + 비용 계산

Phase 1-A: API (sam/api)

순서 작업 산출물
1 DB 마이그레이션 nonconforming_reports, nonconforming_report_items 테이블
2 Model 생성 NonconformingReport, NonconformingReportItem (관계, cast, scope)
3 FormRequest 생성 StoreNonconformingReportRequest, UpdateNonconformingReportRequest
4 Service 생성 NonconformingReportService — CRUD + 채번 + 비용 자동 계산
5 Controller 생성 NonconformingReportController — REST 9개 엔드포인트
6 Route 등록 routes/api/v1/material.php
7 첨부파일 연동 기존 File polymorphic 연결
8 채번 로직 NC-YYYYMMDD-NNN (테넌트별, 일자별 리셋)

완료 기준: Postman/Swagger로 전체 CRUD 동작 확인

API 엔드포인트 (Phase 1 범위):

Method Path 설명
GET /api/v1/material/nonconforming-reports 목록 (필터/검색/페이지네이션)
GET /api/v1/material/nonconforming-reports/{id} 단건 (items, files 포함)
POST /api/v1/material/nonconforming-reports 등록 (items 일괄 저장)
PUT /api/v1/material/nonconforming-reports/{id} 수정 (items sync)
DELETE /api/v1/material/nonconforming-reports/{id} 소프트 삭제
POST /api/v1/material/nonconforming-reports/{id}/files 첨부파일 업로드
DELETE /api/v1/material/nonconforming-reports/{id}/files/{fileId} 첨부파일 삭제

Phase 1-B: 프론트엔드 (sam/react)

선행 조건: Phase 1-A API 완료 + API 명세 전달

순서 작업 산출물
1 Server Actions 작성 actions.ts — API 호출 함수 (목록/상세/등록/수정/삭제)
2 목록 페이지 NonconformingList.tsx — 필터, 검색, 페이지네이션, 상태 Badge
3 등록/수정 폼 NonconformingForm.tsx — 5개 FormSection (수주서 구조 준용)
4 자재 상세 테이블 NonconformingItemTable.tsx — 행 추가/삭제, 금액 자동 계산
5 비용 요약 CostSummary.tsx — 자재/운송/시공/기타/합계, 자재비 자동 산정
6 상세 뷰 NonconformingDetail.tsx — 읽기 전용 상세 화면
7 첨부파일 기존 파일 업로드 컴포넌트 재사용
8 라우트 등록 page.tsx + [id]/page.tsx (mode=new/edit/view 분기)

전달 자료 (API → FE):

  • API 엔드포인트 목록 (이 문서 4장)
  • 요청/응답 JSON 예시 (이 문서 4.1, 4.2)
  • 부적합 유형 코드 목록: material, process, construction, other
  • 상태 코드 목록: RECEIVED, ANALYZING, RESOLVED, CLOSED

Phase 2 — 상태 워크플로우 + 결재

목표: 상태 전이 로직 + 결재 시스템 연동 + 수정 제한

Phase 2-A: API (sam/api)

순서 작업 산출물
1 상태 전이 로직 Service에 changeStatus() — 전이 규칙 검증 포함
2 상태 변경 API PATCH /{id}/status — body: { "status": "ANALYZING" }
3 통계 API GET /stats — 상태별 건수, 월별 비용 합계
4 수정 제한 CLOSED 상태에서 update/delete 요청 시 403 반환
5 결재 연동 기존 approval_requests 테이블과 연결, 승인 시 자동 CLOSED

추가 API 엔드포인트 (Phase 2):

Method Path 설명
PATCH /{id}/status 상태 변경 (전이 규칙 검증)
GET /stats 상태별 통계

상태 전이 검증 규칙:

RECEIVED → ANALYZING     : 조건 없음
ANALYZING → RESOLVED     : cause_analysis + corrective_action 필수
RESOLVED → CLOSED        : 결재 승인 시 자동 (수동 전이 불가)

Phase 2-B: 프론트엔드 (sam/react)

선행 조건: Phase 2-A API 완료

순서 작업 산출물
1 상태 변경 버튼 상세 화면 상단에 상태별 액션 버튼 표시
2 수정 제한 UI CLOSED 상태 시 폼 전체 disabled + 수정 버튼 숨김
3 결재상신 버튼 RESOLVED 상태에서 "결재상신" 버튼 → 결재 다이얼로그
4 목록 통계 카드 상단에 상태별 건수 카드 (접수/분석중/조치완료/종결)

전달 자료 (API → FE):

  • 상태 전이 규칙 (이 문서 5.3)
  • 상태별 허용 액션 매핑
  • 결재 API 연동 방식 (기존 결재 시스템 패턴 참고)

Phase 3 — 통계 + 연동 + 출력

목표: 대시보드, 수주 연결 강화, 엑셀/PDF 출력

Phase 3-A: API (sam/api)

순서 작업 산출물
1 대시보드 통계 API 월별 부적합 건수/비용 추이, 유형별 분포
2 수주 연동 API GET /api/v1/orders/{id}/nonconforming-reports — 수주별 부적합 이력
3 엑셀 Export GET /{id}/export/excel — 부적합 보고서 엑셀 다운로드
4 PDF 출력 GET /{id}/export/pdf — 인쇄용 보고서 (DomPDF, Pretendard 폰트)
5 일괄 삭제 PATCH /bulk-delete — 체크박스 선택 일괄 삭제

추가 API 엔드포인트 (Phase 3):

Method Path 설명
GET /dashboard 대시보드 통계 (월별 건수/비용, 유형별 분포)
GET /{id}/export/excel 엑셀 다운로드
GET /{id}/export/pdf PDF 다운로드
PATCH /bulk-delete 일괄 삭제

Phase 3-B: 프론트엔드 (sam/react)

선행 조건: Phase 3-A API 완료

순서 작업 산출물
1 대시보드 차트 월별 부적합 건수/비용 추이 차트, 유형별 도넛 차트
2 수주 상세 연동 수주 상세 화면에 "부적합 이력" 탭 추가
3 엑셀/PDF 버튼 상세 화면 + 목록 화면에 다운로드 버튼
4 일괄 삭제 목록 체크박스 → "선택 삭제" 버튼

전체 일정 요약

Phase 1-A (API)  ■■■■□□□□□□□□  → Phase 1-B (FE)  □□□□■■■■□□□□
Phase 2-A (API)  □□□□□□■■□□□□  → Phase 2-B (FE)  □□□□□□□□■■□□
Phase 3-A (API)  □□□□□□□□□■■□  → Phase 3-B (FE)  □□□□□□□□□□■■

Phase 1-A 완료 후 FE 개발이 시작되면, API 개발자는 Phase 2-A를 병행할 수 있다.


7. 기술 설계 요약

7.1 API 파일 구조

api/app/
├── Http/Controllers/Api/V1/Material/
│   └── NonconformingReportController.php
├── Services/Material/
│   └── NonconformingReportService.php
├── Models/Material/
│   ├── NonconformingReport.php
│   └── NonconformingReportItem.php
├── Http/Requests/Material/
│   ├── StoreNonconformingReportRequest.php
│   └── UpdateNonconformingReportRequest.php
└── database/migrations/
    ├── xxxx_create_nonconforming_reports_table.php
    └── xxxx_create_nonconforming_report_items_table.php

7.2 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

7.3 모델 주요 설정

// NonconformingReport.php
class NonconformingReport extends Model
{
    use Auditable, BelongsToTenant, ModelTrait, SoftDeletes;

    protected $casts = [
        'options' => 'array',
        'occurred_at' => 'date',
        'confirmed_at' => 'date',
        'action_completed_at' => 'date',
        'material_cost' => 'integer',
        'shipping_cost' => 'integer',
        'construction_cost' => 'integer',
        'other_cost' => 'integer',
        'total_cost' => 'integer',
    ];

    public function items(): HasMany { ... }
    public function order(): BelongsTo { ... }
    public function item(): BelongsTo { ... }
    public function files(): MorphMany { ... }
    public function department(): BelongsTo { ... }
}

관련 문서

  • rules/item-policy.md — 품목 정책
  • features/quotes/README.md — 견적/수주 시스템
  • system/database/production.md — 생산/자재/품질 DB

최종 업데이트: 2026-03-19