# 설비관리 (Equipment Management)

> **작성일**: 2026-02-27
> **상태**: MNG R&D 완료 / API Phase 1 완료 / React 미구현
> **최종 갱신**: 2026-03-12 (설비 등록 시 사진 업로드 기능 반영)

---

## 1. 개요

### 1.1 목적

생산 설비의 등록, 점검, 수리이력을 체계적으로 관리한다. MNG 관리자 패널에서 Blade/HTMX 기반으로 R&D가 완료되었으며, API 백엔드 구현이 완료되어 React 프론트엔드 연동을 진행할 수 있다.

### 1.2 핵심 기능

| 기능 | 설명 | MNG | API |
|------|------|:---:|:---:|
| 설비 대시보드 | 현황 카드(총/가동/유휴/폐기), 유형별 통계 | ✅ | ✅ |
| 설비 대장 (CRUD) | 등록/수정/삭제/복원, SoftDeletes | ✅ | ✅ |
| 설비 점검 | 6주기(일일~반년), 그리드 셀 토글, 종합판정 | ✅ | ✅ |
| 점검 템플릿 | 설비별 점검항목 정의, 주기간 복사 | ✅ | ✅ |
| 수리이력 | 사내/외주, 비용, 시간, 업체 관리 | ✅ | ✅ |
| 설비 사진 | 파일 업로드/삭제 | ✅ | ✅ |
| 엑셀 Import | 미리보기 + 일괄등록, 이미지 포함 | ✅ | — |
| 모바일 점검 | QR 스캔 → 모바일 점검 화면 | ✅ | — |
| 설비-공정 매핑 | N:N 관계, 주 설비 여부 | ✅ | ✅ |

---

## 2. DB 테이블

> 마이그레이션: `api/database/migrations/` (MNG에서 생성 금지)

### 2.1 테이블 목록

| 테이블 | 설명 | 마이그레이션 |
|--------|------|-------------|
| `equipments` | 설비 마스터 | `2026_02_25_100000` |
| `equipment_inspection_templates` | 점검항목 템플릿 | `2026_02_25_100100` |
| `equipment_inspections` | 점검 헤더 (설비+주기+기간) | `2026_02_25_100200` |
| `equipment_inspection_details` | 점검 상세 (셀 단위 결과) | `2026_02_25_100300` |
| `equipment_repairs` | 수리이력 | `2026_02_25_100400` |
| `equipment_process` | 설비-공정 매핑 (N:N pivot) | `2026_02_25_100500` |

추가 마이그레이션:

| 마이그레이션 | 설명 |
|-------------|------|
| `2026_02_28_100000` | `sub_manager_id` 컬럼 추가 |
| `2026_02_28_100100` | templates에 `inspection_cycle` 추가 + 유니크 변경 |
| `2026_02_28_100200` | inspections에 `inspection_cycle` 추가 + 유니크 변경 |
| `2026_03_12_100000` | `equipments`, `equipment_repairs`에 `options` JSON 추가 |
| `2026_03_12_100001` | 기본 DB에 테이블 존재 보장 (`hasTable` 체크) |

> **DB 연결 참고**: 기존 MNG 마이그레이션이 `codebridge` DB에 테이블을 생성했으나, API에서는 기본 DB(`sam`/`sam_prod`)에 테이블이 필요하다. `2026_03_12_100001` 마이그레이션이 기본 DB에 테이블이 없으면 최신 스키마로 생성한다.

### 2.2 ERD 개요

```
equipments (설비 마스터)
├── 1:N → equipment_inspection_templates (점검항목 정의)
├── 1:N → equipment_inspections (점검 헤더)
│         └── 1:N → equipment_inspection_details (점검 상세)
├── 1:N → equipment_repairs (수리이력)
├── N:N → processes (via equipment_process)
└── 1:N → files (사진, document_type='equipment')
```

### 2.3 주요 컬럼

#### `equipments`

| 컬럼 | 타입 | 설명 |
|------|------|------|
| `tenant_id` | bigint | 테넌트 ID |
| `equipment_code` | varchar(20) | 설비코드 (UNIQUE per tenant) |
| `name` | varchar(100) | 설비명 |
| `equipment_type` | varchar(50) | 유형: 포밍기/미싱기/샤링기/V컷팅기/절곡기/프레스/드릴/기타 |
| `production_line` | varchar(50) | 라인: 스라트/스크린/절곡/기타 |
| `status` | varchar(20) | active/idle/disposed |
| `manager_id` | FK → users | 정 담당자 |
| `sub_manager_id` | FK → users | 부 담당자 |
| `purchase_date` | date | 구입일 |
| `install_date` | date | 설치일 |
| `purchase_price` | decimal(15,2) | 구입가격 |
| `useful_life` | int | 내용연수 |
| `options` | json | 확장 속성 JSON |
| `sort_order` | int | 정렬순서 |

#### `equipment_inspection_templates`

| 컬럼 | 타입 | 설명 |
|------|------|------|
| `equipment_id` | FK → equipments | 설비 |
| `inspection_cycle` | varchar | 점검주기 (daily/weekly/monthly/bimonthly/quarterly/semiannual) |
| `item_no` | int | 항목번호 |
| `check_point` | varchar(50) | 점검개소 |
| `check_item` | varchar(100) | 점검항목 |
| `check_timing` | varchar(20) | 시기: operating/stopped |
| `check_method` | text | 점검방법 및 기준 |

#### `equipment_inspections`

| 컬럼 | 타입 | 설명 |
|------|------|------|
| `equipment_id` | FK → equipments | 설비 |
| `inspection_cycle` | varchar | 점검주기 |
| `year_month` | varchar(7) | 기간 (daily: `2026-03`, 그 외: `2026`) |
| `overall_judgment` | varchar(10) | 종합판정: OK/NG |
| `inspector_id` | FK → users | 점검자 |
| `repair_note` | text | 수리내역 |
| `issue_note` | text | 이상내용 |

#### `equipment_inspection_details`

| 컬럼 | 타입 | 설명 |
|------|------|------|
| `inspection_id` | FK → inspections | 점검 헤더 |
| `template_item_id` | FK → templates | 점검항목 |
| `check_date` | date | 점검일 |
| `result` | varchar(10) | good(○)/bad(X)/repaired(△) |

#### `equipment_repairs`

| 컬럼 | 타입 | 설명 |
|------|------|------|
| `equipment_id` | FK → equipments | 설비 |
| `repair_date` | date | 수리일 |
| `repair_type` | varchar(20) | internal(사내)/external(외주) |
| `repair_hours` | decimal(5,1) | 수리시간 |
| `cost` | decimal(15,2) | 수리비용 |
| `vendor` | varchar(100) | 외주업체 |
| `repaired_by` | FK → users | 수리자 |
| `options` | json | 확장 속성 JSON |

---

## 3. API 구현 현황 (Phase 1 완료)

### 3.1 모델 (`api/app/Models/Equipment/`)

| 모델 | Traits | 특이사항 |
|------|--------|---------|
| `Equipment` | Auditable, BelongsToTenant, ModelTrait, SoftDeletes | `options` cast, `getOption()`/`setOption()` |
| `EquipmentInspection` | Auditable, BelongsToTenant, ModelTrait | — |
| `EquipmentInspectionDetail` | ModelTrait | `getNextResult()`, `getResultSymbol()` static |
| `EquipmentInspectionTemplate` | Auditable, BelongsToTenant, ModelTrait | — |
| `EquipmentRepair` | Auditable, BelongsToTenant, ModelTrait, SoftDeletes | `options` cast, `getOption()`/`setOption()` |
| `EquipmentProcess` | — | pivot 모델 |

### 3.2 Enum (`api/app/Enums/InspectionCycle.php`)

6개 점검주기를 관리하는 헬퍼 클래스:

| 주기 | 코드 | period 형식 | 그리드 열 |
|------|------|------------|----------|
| 일일 | `daily` | `2026-03` (년-월) | 1~31일 |
| 주간 | `weekly` | `2026` (년) | 1~52주 |
| 월간 | `monthly` | `2026` (년) | 1~12월 |
| 2개월 | `bimonthly` | `2026` (년) | 1~6 |
| 분기 | `quarterly` | `2026` (년) | 1~4분기 |
| 반년 | `semiannual` | `2026` (년) | 상/하반기 |

주요 메서드: `columnLabels()`, `resolveCheckDate()`, `resolvePeriod()`, `isNonWorkingDay()`, `getHolidayDates(int $tenantId)`

> **MNG→API 변환**: `getHolidayDates()`가 `session('selected_tenant_id')` 대신 명시적 `$tenantId` 매개변수를 받는다.

### 3.3 서비스 (`api/app/Services/Equipment/`)

모든 서비스는 `extends Service` (base class)를 사용하며, 쓰기 작업은 `DB::transaction()`으로 감싼다.

| 서비스 | 핵심 메서드 |
|--------|-----------|
| `EquipmentService` | `index()`, `show()`, `store()`, `update()`, `destroy()`, `restore()`, `toggleActive()`, `stats()`, `options()` |
| `EquipmentInspectionService` | `getInspections()`, `toggleDetail()`, `setResult()`, `updateNotes()`, `resetInspection()`, `saveTemplate()`, `updateTemplate()`, `deleteTemplate()`, `copyTemplates()`, `getActiveCycles()` |
| `EquipmentRepairService` | `index()`, `store()`, `update()`, `destroy()` |
| `EquipmentPhotoService` | `index()`, `store()`, `destroy()` |

### 3.4 컨트롤러 (`api/app/Http/Controllers/V1/Equipment/`)

| 컨트롤러 | DI | 메서드 |
|---------|-----|-------|
| `EquipmentController` | `EquipmentService` | index, show, store, update, destroy, restore, toggleActive, stats, options |
| `EquipmentInspectionController` | `EquipmentInspectionService` | index, toggleDetail, setResult, updateNotes, resetInspection, templates, storeTemplate, updateTemplate, deleteTemplate, copyTemplates |
| `EquipmentRepairController` | `EquipmentRepairService` | index, store, update, destroy |
| `EquipmentPhotoController` | `EquipmentPhotoService` | index, store, destroy |

### 3.5 FormRequest (`api/app/Http/Requests/V1/Equipment/`)

| 클래스 | 용도 |
|--------|------|
| `StoreEquipmentRequest` | 설비 등록 (코드 unique per tenant) |
| `UpdateEquipmentRequest` | 설비 수정 (`sometimes` 규칙) |
| `StoreEquipmentRepairRequest` | 수리이력 등록/수정 |
| `StoreInspectionTemplateRequest` | 점검항목 등록 (cycle enum 검증) |
| `ToggleInspectionDetailRequest` | 점검 셀 토글 |
| `UpdateInspectionNotesRequest` | 점검 메모/판정 수정 |

### 3.6 API 라우트 (`api/routes/api/v1/equipment.php`)

26개 엔드포인트, prefix: `/v1/equipment`

```
설비 CRUD (9):
  GET    /v1/equipment                    → index (목록+검색+필터)
  GET    /v1/equipment/options            → options (드롭다운 데이터)
  GET    /v1/equipment/stats              → stats (대시보드 통계)
  POST   /v1/equipment                    → store
  GET    /v1/equipment/{id}               → show
  PUT    /v1/equipment/{id}               → update
  DELETE /v1/equipment/{id}               → destroy (soft delete)
  POST   /v1/equipment/{id}/restore       → restore
  PATCH  /v1/equipment/{id}/toggle        → toggleActive

점검 템플릿 (5):
  GET    /v1/equipment/{id}/templates          → templates (활성 주기 목록)
  POST   /v1/equipment/{id}/templates          → storeTemplate
  PUT    /v1/equipment/templates/{templateId}  → updateTemplate
  DELETE /v1/equipment/templates/{templateId}  → deleteTemplate
  POST   /v1/equipment/{id}/templates/copy     → copyTemplates

점검 (5):
  GET    /v1/equipment/inspections             → index (그리드 데이터)
  PATCH  /v1/equipment/inspections/toggle      → toggleDetail (셀 토글)
  PATCH  /v1/equipment/inspections/set-result  → setResult (결과 직접 설정)
  PATCH  /v1/equipment/inspections/notes       → updateNotes (메모/판정)
  DELETE /v1/equipment/inspections/reset       → resetInspection (초기화)

수리이력 (4):
  GET    /v1/equipment/repairs            → index
  POST   /v1/equipment/repairs            → store
  PUT    /v1/equipment/repairs/{id}       → update
  DELETE /v1/equipment/repairs/{id}       → destroy

사진 (3):
  GET    /v1/equipment/{id}/photos             → index
  POST   /v1/equipment/{id}/photos             → store
  DELETE /v1/equipment/{id}/photos/{fileId}    → destroy
```

### 3.7 i18n 메시지

**성공 메시지** (`lang/ko/message.php` → `equipment.*`):

| 키 | 메시지 |
|----|--------|
| `created` | 설비가 등록되었습니다. |
| `updated` | 설비 정보가 수정되었습니다. |
| `deleted` | 설비가 삭제되었습니다. |
| `restored` | 설비가 복원되었습니다. |
| `inspection_saved` | 점검 정보가 저장되었습니다. |
| `inspection_reset` | 점검 데이터가 초기화되었습니다. |
| `template_created` | 점검항목이 추가되었습니다. |
| `template_copied` | 점검항목이 복사되었습니다. |
| `repair_created` | 수리이력이 등록되었습니다. |
| `photo_uploaded` | 사진이 업로드되었습니다. |

**에러 메시지** (`lang/ko/error.php` → `equipment.*`):

| 키 | 메시지 |
|----|--------|
| `not_found` | 설비 정보를 찾을 수 없습니다. |
| `template_not_found` | 점검항목을 찾을 수 없습니다. |
| `inspection_not_found` | 점검 데이터를 찾을 수 없습니다. |
| `repair_not_found` | 수리이력을 찾을 수 없습니다. |
| `photo_not_found` | 사진을 찾을 수 없습니다. |
| `invalid_cycle` | 유효하지 않은 점검주기입니다. |
| `no_source_templates` | 복사할 점검항목이 없습니다. |

### 3.8 MNG→API 변환 규칙 적용

| MNG 패턴 | API 변환 |
|----------|---------|
| `$connection = 'codebridge'` | 제거 (기본 connection 사용) |
| `session('selected_tenant_id')` | `$this->tenantId()` |
| `auth()->id()` | `$this->apiUserId()` |
| `DB::connection('codebridge')` | `DB::` (기본 connection) |
| MNG 직접 validate | FormRequest 분리 |
| 직접 문자열 에러 메시지 | `__('error.equipment.*')` i18n |
| — | `Auditable` trait 추가 |
| — | `DB::transaction()` 쓰기 래핑 |

---

## 4. MNG 구현 현황

### 4.1 서비스 (`mng/app/Services/`)

| 서비스 | 핵심 메서드 |
|--------|-----------|
| `EquipmentService` | `getEquipments()`, `createEquipment()`, `updateEquipment()`, `deleteEquipment()`, `restoreEquipment()`, `getDashboardStats()`, `getTypeStats()` |
| `EquipmentInspectionService` | `getInspections()`, `toggleDetail()`, `setResult()`, `updateInspectionNotes()`, `resetEquipmentInspection()`, `resetAllInspections()`, `saveTemplate()`, `copyTemplatesToCycles()` |
| `EquipmentRepairService` | `getRepairs()`, `createRepair()`, `updateRepair()`, `deleteRepair()`, `getRecentRepairs()` |
| `EquipmentPhotoService` | `uploadPhotos()`, `uploadPhotoFromPath()`, `deletePhoto()`, `getPhotoUrls()`, `compressImage()` |
| `EquipmentImportService` | `preview()`, `import()` — 엑셀 헤더 매핑, 이미지 추출, 중복 처리 |

### 4.2 뷰 파일 (`mng/resources/views/equipment/`)

```
equipment/
├── dashboard.blade.php              # 설비 현황 대시보드
├── index.blade.php                  # 설비 대장 목록
├── create.blade.php                 # 설비 등록
├── show.blade.php                   # 설비 상세
├── edit.blade.php                   # 설비 수정
├── import.blade.php                 # 엑셀 Import
├── guide.blade.php                  # 설비관리 가이드
├── inspections/
│   └── index.blade.php              # 점검 그리드 (6주기)
├── repairs/
│   ├── index.blade.php              # 수리이력 목록
│   └── create.blade.php             # 수리이력 등록
└── partials/
    ├── table.blade.php              # 설비 목록 테이블 (HTMX)
    ├── repair-table.blade.php       # 수리이력 테이블 (HTMX)
    ├── inspection-grid.blade.php    # 점검 그리드 (HTMX)
    ├── qr-code.blade.php            # QR 코드
    └── tabs/
        ├── basic-info.blade.php     # 설비 기본정보 탭
        ├── inspection-items.blade.php # 점검항목 탭
        └── repair-history.blade.php  # 수리이력 탭
```

---

## 5. 비즈니스 규칙

### 5.1 설비 상태

| 상태 | 코드 | 설명 |
|------|------|------|
| 가동 | `active` | 정상 운영 중 |
| 유휴 | `idle` | 일시 정지 |
| 폐기 | `disposed` | 사용 중단 |

### 5.2 점검 권한

- 관리자(`isAdmin`): 모든 설비 점검 가능
- 일반 사용자: `manager_id` 또는 `sub_manager_id`와 일치하는 설비만 점검 가능
- 권한 없는 사용자가 점검 시도 시 403 응답

### 5.3 점검 결과 순환

셀 클릭 시 결과가 순환한다:

```
(빈칸) → good(○) → bad(X) → repaired(△) → (빈칸)
```

### 5.4 휴일/주말 점검 제한

- 일일 점검(`daily`)에서 주말(토/일) 및 `holidays` 테이블 등록 휴일에는 점검 기록 불가
- 다른 주기(weekly/monthly 등)에는 제한 없음

### 5.5 설비 사진

- 최대 10장 (설비당)
- Cloudflare R2에 저장 (`Storage::disk('r2')`)
- 저장 경로: `{tenantId}/equipment/{year}/{month}/{storedName}`
- 다중 업로드 지원 (`files[]` multipart/form-data)
- 허용 확장자: jpg, jpeg, png, gif, bmp, webp (최대 10MB/파일)
- 삭제 시 soft delete (`softDeleteFile()`) 적용
- MNG는 GCS + 자동 압축, API는 R2 직접 저장
- **등록 시 사진 업로드**: 등록 폼에서 사진을 미리 선택하면 대기열에 추가되고 미리보기가 표시된다. 설비 저장 완료 후 대기 중인 사진이 자동으로 업로드된다.
- **수정 시 사진 관리**: 기존 사진 조회/삭제 + 새 사진 추가 가능

### 5.6 엑셀 Import (MNG 전용)

- 한글/영문 헤더 자동 매핑
- 중복 처리: skip 또는 overwrite 선택
- 엑셀 내 이미지(Drawing) 자동 추출 후 저장 (MNG: GCS, API: R2 예정)
- API에서는 미구현 (Phase 2+ 검토)

---

## 6. 향후 계획

| 단계 | 내용 | 상태 |
|------|------|------|
| Phase 1 | API 백엔드 (모델, 서비스, 컨트롤러, 라우트) | ✅ 완료 |
| Phase 2 | React 프론트엔드 | 미착수 |
| Phase 3 | 테스트 + Swagger 문서 | 미착수 |
| 추가 | 엑셀 Import API | 검토 중 |
| 추가 | 모바일 점검 (React) | 검토 중 |

---

## 관련 문서

- [서비스 구축 계획](../../dev/dev_plans/equipment-service-build-plan.md)
- [MNG 구조](../../system/mng-structure.md)
- [DB 스키마 — 공통](../../system/database/commons.md)
- [options 컬럼 정책](../../dev/standards/options-column-policy.md)

---

**최종 업데이트**: 2026-03-12
