chore: 불필요한 claudedocs 문서 정리

- 오래된 분석 문서 삭제
- Swagger 감사 문서 삭제
- MES 관련 임시 문서 삭제
This commit is contained in:
2025-11-30 21:06:11 +09:00
parent 7c40c2395a
commit 3c9ba94784
8 changed files with 0 additions and 5748 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,201 +0,0 @@
# SAM API Swagger 문서 점검 현황
## 📋 점검 개요
**목적:** SAM API의 Swagger 문서 품질을 체계적으로 점검하고 개선
**범위:** 총 30개 Swagger API 파일 (app/Swagger/v1/)
**진행 방식:** Phase별 순차 점검 (세션 독립적)
## 🎯 Phase 구성
### Phase 1: 기본 설정 및 보안 (완료 ✅)
**완료일:** 2025-11-06
#### 수정 내용:
1.**SAMInfo.php - Auth 태그 개선**
- 상세한 인증 흐름 설명 추가
- API Key 및 Bearer Token 사용 예시 추가
- IP 기반 접근 제어 안내 추가
2.**RegisterApi.php - 보안 어노테이션 추가**
- `security={{"ApiKeyAuth": {}}}` 추가
- "Authentication: Not Required" 오류 해결
3. **서버 URL 설정**
- .env 파일의 L5_SWAGGER_CONST_HOST 변수로 관리
- 사용자가 직접 수정 예정 (http://api.sam.kr/ → https://api.codebridge-x.com)
### Phase 2: Auth API 상세 점검 (완료 ✅)
**완료일:** 2025-11-06
**대상 파일:** AuthApi.php
#### 수정 내용:
1.**debug-apikey API 개선**
- description 추가 (API Key 유효성 확인 설명)
- 응답 형식 명시 (`{message: "API Key 인증 성공"}`)
2.**logout API 응답 형식 수정**
- Swagger: `{success, message, data}` → 실제: `{message}`
- 실제 코드와 일치하도록 수정
3.**login API 검증**
- 요청/응답 스키마와 실제 코드 일치 확인
- user, tenant, menus 구조 정확성 확인
4.**signup API 중복 확인**
- AuthApi.php와 RegisterApi.php가 동일 엔드포인트
- RegisterApi.php가 더 상세 (테넌트 생성 포함)
- 두 파일 모두 유지 (태그 및 구조 차이)
### Phase 3: 리소스별 순차 점검 (예정)
**총 30개 파일 중 28개 남음**
**우선순위 높음 (핵심 기능):**
- [ ] ProductApi.php
- [ ] MaterialApi.php
- [ ] ClientApi.php
- [ ] UserApi.php
- [ ] TenantApi.php
- [ ] CategoryApi.php
**우선순위 중간 (관리 기능):**
- [ ] RoleApi.php
- [ ] PermissionApi.php
- [ ] DepartmentApi.php
- [ ] MenuApi.php
- [ ] FieldProfileApi.php
- [ ] FileApi.php
**우선순위 낮음 (부가 기능):**
- [ ] ModelApi.php
- [ ] BomCalculationApi.php
- [ ] PricingApi.php
- [ ] ClassificationApi.php
- [ ] AuditLogApi.php
- [ ] CommonApi.php
**스키마 정의 파일:**
- [ ] CommonComponents.php
- [ ] ProductExtraSchemas.php
- [ ] CategoryExtras.php
- [ ] DesignBomTemplateExtras.php
## 📝 표준 점검 체크리스트
각 API 파일 점검 시 다음 항목을 확인합니다:
### 보안 및 인증
- [ ] security 어노테이션 정확성 (ApiKeyAuth, BearerAuth)
- [ ] 인증 불필요 API의 명시적 표시 (security={})
### 요청 스키마
- [ ] RequestBody 정의 완성도
- [ ] required 필드 정확성
- [ ] 타입 및 format 정확성
- [ ] example 값의 실제 동작 일치성
- [ ] nullable 속성 정확성
### 응답 스키마
- [ ] Response 정의 완성도 (200, 400, 401, 403, 404, 422, 500)
- [ ] 성공 응답의 data 구조 정확성
- [ ] 에러 응답의 message/errors 구조 일치성
- [ ] example 값과 실제 응답 일치성
- [ ] nullable/oneOf 구분 정확성
### 문서 품질
- [ ] summary 명확성
- [ ] description 상세성
- [ ] 파라미터 설명 충실도
- [ ] 예시 값의 실용성
- [ ] tags 분류 적절성
### 스키마 재사용
- [ ] 중복 스키마 존재 여부
- [ ] 공통 스키마 활용 여부
- [ ] ref 참조 정확성
## 🐛 발견된 이슈
### 해결됨 (✅)
1. **RegisterApi.php - 인증 필수 미표시**
- 문제: "Authentication: Not Required"로 표시됨
- 원인: security 어노테이션 누락
- 해결: `security={{"ApiKeyAuth": {}}}` 추가
- 완료일: 2025-11-06
2. **SAMInfo.php - Auth 태그 설명 부족**
- 문제: 인증 흐름 및 사용 예시 부족
- 해결: 상세한 설명 및 예시 추가
- 완료일: 2025-11-06
3. **AuthApi.php - logout 응답 형식 불일치**
- 문제: Swagger `{success, message, data}` vs 실제 `{message}`
- 원인: Swagger 문서가 표준 응답 형식으로 작성됨
- 해결: 실제 코드에 맞춰 `{message}` 형식으로 수정
- 완료일: 2025-11-06
4. **AuthApi.php - debug-apikey 설명 부족**
- 문제: 응답 형식 미명시
- 해결: description 및 응답 형식 추가
- 완료일: 2025-11-06
### 진행 중 (🔄)
없음
### 대기 중 (⏳)
1. **서버 URL 변경**
- 현재: http://api.sam.kr/
- 목표: https://api.codebridge-x.com
- 방법: .env 파일의 L5_SWAGGER_CONST_HOST 수정
- 담당: 사용자 직접 수정
## 📊 진행 상황
### 전체 진도
- **Phase 1:** ✅ 완료 (3/3)
- **Phase 2:** ✅ 완료 (4/4)
- **Phase 3:** ⏳ 대기 중 (0/28)
### 파일별 상태
| 파일명 | 상태 | 점검일 | 비고 |
|--------|------|--------|------|
| SAMInfo.php | ✅ 완료 | 2025-11-06 | Auth 태그 개선 |
| RegisterApi.php | ✅ 완료 | 2025-11-06 | 보안 어노테이션 추가 |
| AuthApi.php | ✅ 완료 | 2025-11-06 | logout/debug-apikey 수정 |
| ProductApi.php | ⏳ 대기 | - | Phase 3 (우선순위 높음) |
| MaterialApi.php | ⏳ 대기 | - | Phase 3 (우선순위 높음) |
| ... | ... | ... | ... |
## 🔄 다음 단계
### 즉시 실행 가능
1. Phase 1 변경사항 검증
- Swagger 재생성: `php artisan l5-swagger:generate`
- Swagger UI에서 Auth 태그 및 Register API 확인
- 실제 API 호출 테스트
2. Phase 2 시작 준비
- AuthApi.php 파일 분석
- 실제 Controller 및 Service 코드 확인
- 요청/응답 검증 계획 수립
### 사용자 조치 필요
- .env 파일의 L5_SWAGGER_CONST_HOST 수정 (운영 도메인 반영)
## 📌 참고 사항
### 세션 독립성 유지 방법
- 이 문서를 통해 작업 진행 상황 추적
- Phase별 독립 실행 가능
- 각 Phase 완료 후 Git 커밋으로 체크포인트 생성
### 품질 기준
- SAM API Development Rules 준수
- 실제 Controller/Service 코드와 100% 일치
- 사용자가 직접 테스트 가능한 예시 값
- i18n 메시지 키 사용 확인
### 관련 문서
- `CLAUDE.md` - SAM 프로젝트 전체 가이드
- `SAM API Development Rules` - API 개발 규칙
- `l5-swagger` 문서 - Swagger 어노테이션 가이드

View File

@@ -1,201 +0,0 @@
# Product API Swagger 점검 및 개선 (Phase 3-1)
**날짜:** 2025-11-07
**작업자:** Claude Code
**이슈:** Phase 3-1: ProductApi.php Swagger 점검 및 개선
## 📋 변경 개요
ProductApi.php Swagger 문서 점검 후, SAM API Development Rules에 따라 FormRequest 적용 및 i18n 메시지 키 적용
## 🔧 사용된 도구
### MCP 서버
- Sequential Thinking: 복잡한 분석 및 검증 로직 수행
- Native Tools: Read, Write, Edit, Bash, Glob 등 파일 작업
### SuperClaude 페르소나
- backend-architect: API 구조 분석 및 설계 검증
- code-workflow: 체계적 코드 수정 프로세스 적용
### 네이티브 도구
- Read: 9회 (파일 내용 확인)
- Write: 2회 (FormRequest 파일 생성)
- Edit: 2회 (Controller, message.php 수정)
- Bash: 7회 (파일 검색, 문법 체크)
- Glob: 3회 (패턴 기반 파일 검색)
## 📁 수정된 파일
### 1. `app/Http/Requests/Product/ProductStoreRequest.php` (신규 생성)
**목적:** 제품 생성 시 입력 검증을 FormRequest로 분리
**주요 내용:**
- required: code, name, category_id, product_type
- nullable: attributes, description, is_sellable, is_purchasable, is_producible, is_active
- 검증 규칙: Service에서 Controller로 이동 (SAM 규칙 준수)
### 2. `app/Http/Requests/Product/ProductUpdateRequest.php` (신규 생성)
**목적:** 제품 수정 시 입력 검증을 FormRequest로 분리
**주요 내용:**
- sometimes 규칙 적용 (부분 업데이트 지원)
- nullable 필드 동일하게 유지
### 3. `app/Http/Controllers/Api/V1/ProductController.php` (수정)
**변경 전:**
```php
public function store(Request $request)
{
return ApiResponse::handle(function () use ($request) {
return $this->service->store($request->all());
}, '제품 생성');
}
```
**변경 후:**
```php
public function store(ProductStoreRequest $request)
{
return ApiResponse::handle(function () use ($request) {
return $this->service->store($request->validated());
}, __('message.product.created'));
}
```
**변경 이유:**
- FormRequest 적용으로 검증 로직 분리 (SAM 규칙)
- `$request->all()``$request->validated()` (보안 강화)
- 하드코딩된 한글 메시지 → i18n 키 사용
**적용된 메서드:**
- getCategory(): `__('message.product.category_fetched')`
- index(): `__('message.product.fetched')`
- store(): `__('message.product.created')`
- show(): `__('message.product.fetched')`
- update(): `__('message.product.updated')`
- destroy(): `__('message.product.deleted')`
- search(): `__('message.product.searched')`
- toggle(): `__('message.product.toggled')`
### 4. `lang/ko/message.php` (수정)
**변경 전:**
```php
'product' => [
'created' => '제품이 등록되었습니다.',
'updated' => '제품이 수정되었습니다.',
'deleted' => '제품이 삭제되었습니다.',
'toggled' => '제품 상태가 변경되었습니다.',
],
```
**변경 후:**
```php
'product' => [
'fetched' => '제품을 조회했습니다.',
'category_fetched' => '제품 카테고리를 조회했습니다.',
'created' => '제품이 등록되었습니다.',
'updated' => '제품이 수정되었습니다.',
'deleted' => '제품이 삭제되었습니다.',
'toggled' => '제품 상태가 변경되었습니다.',
'searched' => '제품을 검색했습니다.',
],
```
**변경 이유:** Controller의 모든 메서드에 대응하는 i18n 키 추가
## 🔍 분석 결과
### BOM API 확인
- ✅ ProductBomItemController 존재 확인
- ✅ Route 정의 확인 (/api/v1/products/{id}/bom/*)
- ✅ ProductBomService 존재
- ✅ Swagger 정의 (ProductApi.php)와 실제 구현 일치
### 스키마 확인
- ✅ ProductExtraSchemas.php 존재
- ✅ Product, ProductPagination, ProductCreateRequest, ProductUpdateRequest 스키마 정의됨
- ✅ BomItem, BomItemBulkUpsertRequest, BomItemUpdateRequest, BomReorderRequest 스키마 정의됨
- ✅ BomTreeNode, BomCategoryStat, BomReplaceRequest 스키마 정의됨 (ProductApi.php 내부)
### SAM API Rules 준수 확인
#### ✅ 준수 항목
1. **FormRequest 사용**
- ProductStoreRequest, ProductUpdateRequest 생성
- Controller에서 타입 힌트 적용
- `$request->validated()` 사용
2. **i18n 메시지 키 사용**
- 모든 하드코딩된 한글 메시지 제거
- `__('message.product.xxx')` 형식 적용
3. **Service-First 패턴**
- 비즈니스 로직은 Service에 유지
- Controller는 DI + ApiResponse::handle()만 사용
4. **Multi-tenancy**
- ProductService에서 BelongsToTenant 적용 확인
- tenant_id 필터링 확인
#### ⚠️ 개선 여부 결정 필요
1. **검증 로직 중복**
- ProductService에 Validator::make() 로직 존재
- FormRequest에서 기본 검증, Service에서 비즈니스 검증 (code 중복 체크 등)
- **현재 상태:** 유지 (비즈니스 검증은 Service에서 처리하는 것이 적절)
## ✅ 테스트 체크리스트
- [x] PHP 문법 체크 (php -l)
- [x] ProductStoreRequest 문법 확인
- [x] ProductUpdateRequest 문법 확인
- [x] ProductController 문법 확인
- [x] message.php 문법 확인
- [ ] Swagger 재생성 (`php artisan l5-swagger:generate`)
- [ ] Swagger UI 확인 (http://api.sam.kr/api-docs/index.html)
- [ ] 실제 API 호출 테스트
- [ ] GET /api/v1/product/category
- [ ] GET /api/v1/products
- [ ] POST /api/v1/products (FormRequest 검증 확인)
- [ ] GET /api/v1/products/{id}
- [ ] PATCH /api/v1/products/{id} (FormRequest 검증 확인)
- [ ] DELETE /api/v1/products/{id}
- [ ] GET /api/v1/products/search
- [ ] POST /api/v1/products/{id}/toggle
## ⚠️ 배포 시 주의사항
1. **FormRequest 적용으로 검증 로직 변경**
- 기존: Service에서 모든 검증
- 변경 후: FormRequest(기본 검증) + Service(비즈니스 검증)
- 영향: 검증 에러 응답 형식 동일 (422 Unprocessable Entity)
2. **i18n 메시지 변경**
- 기존: 하드코딩된 한글 메시지
- 변경 후: i18n 키 사용
- 영향: 응답 메시지 내용 약간 변경 (의미는 동일)
3. **BOM API 미수정**
- ProductBomItemController는 별도 Controller
- 현재 작업에서는 제외
- Phase 3-1 완료 후 별도 점검 필요
## 🔗 관련 문서
- `CLAUDE.md` - SAM 프로젝트 가이드
- `SWAGGER_AUDIT.md` - Swagger 전체 점검 현황
- SAM API Development Rules (CLAUDE.md 내 섹션)
## 📊 변경 통계
- **신규 파일:** 2개 (FormRequest)
- **수정 파일:** 2개 (Controller, message.php)
- **삭제 파일:** 0개
- **총 변경 라인:** ~50줄
- **SAM 규칙 준수:** 100%
## 🎯 다음 작업
1. Swagger 재생성 및 검증
2. 실제 API 테스트
3. Phase 3-2: MaterialApi.php Swagger 점검

View File

@@ -1,335 +0,0 @@
# Material API Swagger 점검 및 개선 (Phase 3-2)
**날짜:** 2025-11-07
**작업자:** Claude Code
**이슈:** Phase 3-2: MaterialApi.php Swagger 점검 및 개선
## 📋 변경 개요
MaterialApi.php Swagger 문서 점검 후, SAM API Development Rules에 따라:
- **경로 불일치 해결**: `/api/v1/materials``/api/v1/products/materials`
- **Swagger 주석 분리**: Controller에서 MaterialApi.php로 완전 이전
- **FormRequest 적용**: MaterialStoreRequest, MaterialUpdateRequest 생성
- **i18n 메시지 키 적용**: 하드코딩된 한글 메시지 제거
## 🔍 분석 결과
### 1. 경로 불일치 문제 발견
**문제점:**
- MaterialApi.php: `/api/v1/materials` (잘못된 경로)
- MaterialController.php: `/api/v1/products/materials` (실제 경로)
- Route 파일: `/api/v1/products/materials` (실제 정의)
**선택지:**
1. ~~MaterialApi.php 삭제, Controller 주석 유지~~
2. **MaterialApi.php 경로 수정, Controller 주석 삭제**
3. ~~둘 다 유지, 경로만 일치시키기~~
**사용자 결정:** 옵션 2 선택 (MaterialApi.php를 표준으로 사용)
### 2. Swagger 주석 중복
- MaterialController.php에 327줄의 Swagger 주석 존재
- SAM API Development Rules: Swagger 주석은 별도 파일에 작성
- **해결:** Controller의 모든 Swagger 주석 제거 (327줄 → 50줄)
### 3. FormRequest 누락
- Controller에서 `Request $request` 사용 (검증 로직 없음)
- MaterialService에 Validator::make() 로직 존재 (추정)
- **해결:** MaterialStoreRequest, MaterialUpdateRequest 생성
### 4. i18n 메시지 하드코딩
- Controller에서 `__('message.materials.xxx')` 사용
- lang/ko/message.php에 'materials' 키 존재 (복수형)
- **해결:** 'material' (단수형)로 통일
## 📁 수정된 파일
### 1. `app/Http/Requests/Material/MaterialStoreRequest.php` (신규 생성)
**목적:** 자재 생성 시 입력 검증을 FormRequest로 분리
**주요 내용:**
```php
public function rules(): array
{
return [
'category_id' => 'nullable|integer',
'name' => 'required|string|max:100',
'unit' => 'required|string|max:20',
'is_inspection' => 'nullable|in:Y,N',
'search_tag' => 'nullable|string|max:255',
'remarks' => 'nullable|string|max:500',
'attributes' => 'nullable|array',
'attributes.*.label' => 'required|string|max:50',
'attributes.*.value' => 'required|string|max:100',
'attributes.*.unit' => 'nullable|string|max:20',
'options' => 'nullable|array',
'material_code' => 'nullable|string|max:30',
'specification' => 'nullable|string|max:255',
];
}
```
**검증 규칙:**
- **필수 필드**: name, unit
- **중첩 배열 검증**: attributes 배열 내부 label, value 필수
- **제약 조건**: is_inspection은 Y/N만 허용
### 2. `app/Http/Requests/Material/MaterialUpdateRequest.php` (신규 생성)
**목적:** 자재 수정 시 입력 검증을 FormRequest로 분리
**주요 내용:**
- StoreRequest와 동일한 필드 구조
- 모든 필드에 'sometimes' 규칙 적용 (부분 업데이트 지원)
- name, unit은 'sometimes' + 'string' (필수 아님)
### 3. `app/Swagger/v1/MaterialApi.php` (수정)
**변경 전:**
```php
/**
* @OA\Get(
* path="/api/v1/materials",
* ...
* )
*/
```
**변경 후:**
```php
/**
* @OA\Get(
* path="/api/v1/products/materials",
* ...
* )
*/
```
**적용된 엔드포인트:**
- GET `/api/v1/products/materials` (목록 조회)
- POST `/api/v1/products/materials` (자재 등록)
- GET `/api/v1/products/materials/{id}` (단건 조회)
- PUT `/api/v1/products/materials/{id}` (전체 수정)
- PATCH `/api/v1/products/materials/{id}` (부분 수정)
- DELETE `/api/v1/products/materials/{id}` (삭제)
**변경 이유:**
- Route 정의와 경로 일치 (`/api/v1/products/materials`)
- Products 그룹 내 Materials 서브 리소스로 구조화
### 4. `app/Http/Controllers/Api/V1/MaterialController.php` (수정)
**변경 전:** 327줄 (Swagger 주석 포함)
```php
/**
* @OA\Tag(
* name="Products & Materials - Materials",
* description="자재 관리 API (Products 그룹 내 통합)"
* )
*/
class MaterialController extends Controller
{
/**
* @OA\Get(
* path="/api/v1/products/materials",
* summary="자재 목록 조회",
* ...
* )
*/
public function index(Request $request)
{
return ApiResponse::handle(function () use ($request) {
return $this->service->getMaterials($request->all());
}, __('message.materials.fetched'));
}
// ... 300줄의 Swagger 주석
}
```
**변경 후:** 50줄 (비즈니스 로직만 유지)
```php
class MaterialController extends Controller
{
public function __construct(private MaterialService $service) {}
public function index(Request $request)
{
return ApiResponse::handle(function () use ($request) {
return $this->service->getMaterials($request->all());
}, __('message.material.fetched'));
}
public function store(MaterialStoreRequest $request)
{
return ApiResponse::handle(function () use ($request) {
return $this->service->setMaterial($request->validated());
}, __('message.material.created'));
}
public function show(int $id)
{
return ApiResponse::handle(function () use ($id) {
return $this->service->getMaterial($id);
}, __('message.material.fetched'));
}
public function update(MaterialUpdateRequest $request, int $id)
{
return ApiResponse::handle(function () use ($request, $id) {
return $this->service->updateMaterial($id, $request->validated());
}, __('message.material.updated'));
}
public function destroy(int $id)
{
return ApiResponse::handle(function () use ($id) {
return $this->service->destroyMaterial($id);
}, __('message.material.deleted'));
}
}
```
**변경 이유:**
1. **Swagger 주석 분리**: Controller는 비즈니스 로직만 담당 (SAM 규칙)
2. **FormRequest 적용**: `Request``MaterialStoreRequest`, `MaterialUpdateRequest`
3. **validated() 사용**: `$request->all()``$request->validated()` (보안 강화)
4. **i18n 키 사용**: `materials.xxx``material.xxx` (단수형 통일)
5. **불필요한 파라미터 제거**: show()와 destroy()에서 `Request $request` 제거
**적용된 메서드:**
- index(): `__('message.material.fetched')`
- store(): `__('message.material.created')`
- show(): `__('message.material.fetched')`
- update(): `__('message.material.updated')`
- destroy(): `__('message.material.deleted')`
### 5. `lang/ko/message.php` (수정)
**변경 전:**
```php
'materials' => [
'created' => '자재가 등록되었습니다.',
'updated' => '자재가 수정되었습니다.',
'deleted' => '자재가 삭제되었습니다.',
'fetched' => '자재 목록을 조회했습니다.',
],
```
**변경 후:**
```php
'material' => [
'fetched' => '자재를 조회했습니다.',
'created' => '자재가 등록되었습니다.',
'updated' => '자재가 수정되었습니다.',
'deleted' => '자재가 삭제되었습니다.',
],
```
**변경 이유:**
1. **단수형 통일**: 'materials' → 'material' (product, category와 일관성)
2. **순서 정렬**: CRUD 순서로 재배치 (fetched, created, updated, deleted)
3. **메시지 개선**: "자재 목록을 조회했습니다." → "자재를 조회했습니다." (단건/목록 공통 사용)
## 🔍 SAM API Rules 준수 확인
### ✅ 준수 항목
1. **Swagger 주석 분리**
- MaterialApi.php에 모든 Swagger 주석 집중
- Controller는 비즈니스 로직만 유지
2. **FormRequest 사용**
- MaterialStoreRequest, MaterialUpdateRequest 생성
- Controller에서 타입 힌트 적용
- `$request->validated()` 사용
3. **i18n 메시지 키 사용**
- 모든 하드코딩된 한글 메시지 제거
- `__('message.material.xxx')` 형식 적용
4. **경로 일치성**
- Swagger 문서와 실제 Route 경로 일치
- `/api/v1/products/materials` 통일
5. **Service-First 패턴**
- 비즈니스 로직은 MaterialService에 유지
- Controller는 DI + ApiResponse::handle()만 사용
6. **Multi-tenancy**
- MaterialService에서 BelongsToTenant 적용 (추정)
- tenant_id 필터링 유지
## ✅ 테스트 체크리스트
- [x] PHP 문법 체크 (php -l)
- [x] MaterialStoreRequest 문법 확인
- [x] MaterialUpdateRequest 문법 확인
- [x] MaterialApi.php 문법 확인
- [x] MaterialController 문법 확인
- [x] message.php 문법 확인
- [ ] Swagger 재생성 (`php artisan l5-swagger:generate`)
- [ ] Swagger UI 확인 (http://api.sam.kr/api-docs/index.html)
- [ ] 실제 API 호출 테스트
- [ ] GET /api/v1/products/materials
- [ ] POST /api/v1/products/materials (FormRequest 검증 확인)
- [ ] GET /api/v1/products/materials/{id}
- [ ] PATCH /api/v1/products/materials/{id} (FormRequest 검증 확인)
- [ ] DELETE /api/v1/products/materials/{id}
## ⚠️ 배포 시 주의사항
1. **경로 변경 없음**
- 실제 Route는 변경되지 않음 (이미 `/api/v1/products/materials` 사용 중)
- Swagger 문서만 실제 경로와 일치하도록 수정
- **영향:** 기존 API 클라이언트는 영향 없음
2. **FormRequest 적용으로 검증 로직 변경**
- 기존: Service에서 모든 검증 (추정)
- 변경 후: FormRequest(기본 검증) + Service(비즈니스 검증)
- **영향:** 검증 에러 응답 형식 동일 (422 Unprocessable Entity)
3. **i18n 메시지 변경**
- 기존: `__('message.materials.xxx')`
- 변경 후: `__('message.material.xxx')`
- **영향:** 응답 메시지 내용 약간 변경 (의미는 동일)
4. **Controller 코드 간소화**
- 327줄 → 50줄 (Swagger 주석 제거)
- **영향:** 유지보수성 향상, 기능은 동일
## 📊 변경 통계
- **신규 파일:** 2개 (FormRequest)
- **수정 파일:** 3개 (MaterialApi.php, MaterialController.php, message.php)
- **삭제 파일:** 0개
- **코드 감소:** -212줄 (Controller Swagger 주석 제거)
- **실질 추가:** +88줄 (FormRequest + i18n)
- **SAM 규칙 준수:** 100%
## 🎯 다음 작업
1. Swagger 재생성 및 검증
2. 실제 API 테스트
3. Phase 3-3: ClientApi.php Swagger 점검
## 🔗 관련 문서
- `CLAUDE.md` - SAM 프로젝트 가이드
- `SWAGGER_AUDIT.md` - Swagger 전체 점검 현황
- `SWAGGER_PHASE3_1_PRODUCT.md` - Phase 3-1 작업 문서
- SAM API Development Rules (CLAUDE.md 내 섹션)
## 📝 커밋 정보
**커밋 해시:** f4d663a
**커밋 메시지:**
```
feat: MaterialApi.php Swagger 점검 및 개선 (Phase 3-2)
- MaterialStoreRequest.php 생성 (검증 로직 분리)
- MaterialUpdateRequest.php 생성 (검증 로직 분리)
- MaterialApi.php 경로 수정 (/api/v1/products/materials)
- MaterialController.php Swagger 주석 제거, FormRequest 적용
- lang/ko/message.php material 메시지 키 추가
- SAM API Development Rules 준수 완료
```
---
**Phase 3-2 완료 ✅**

View File

@@ -1,284 +0,0 @@
# Client API Swagger 점검 및 개선 (Phase 3-3)
**날짜:** 2025-11-07
**작업자:** Claude Code
**이슈:** Phase 3-3: ClientApi.php Swagger 점검 및 개선
## 📋 변경 개요
ClientApi.php Swagger 문서 점검 후, SAM API Development Rules에 따라:
- **FormRequest 적용**: ClientStoreRequest, ClientUpdateRequest 생성
- **i18n 메시지 키 적용**: 리소스별 키 사용 (`message.client.xxx`)
- **Controller 패턴 통일**: ApiResponse::handle 두 번째 인자로 메시지 전달
- **코드 간소화**: 불필요한 배열 래핑 제거
## 🔍 분석 결과
### ✅ 좋은 점
1. **Swagger 구조**: ClientApi.php가 별도 파일로 잘 분리되어 있음
2. **스키마 완성도**: Client, ClientPagination, ClientCreateRequest, ClientUpdateRequest 모두 정의됨
3. **Controller 간결함**: Swagger 주석이 없어서 깔끔함 (Phase 3-1, 3-2와 동일)
4. **경로 일치성**: Swagger와 실제 Route 경로가 일치 (`/api/v1/clients`)
### ⚠️ 개선이 필요한 점
1. **FormRequest 누락**: ClientStoreRequest, ClientUpdateRequest 없음
2. **i18n 메시지**: 공통 키만 사용 (`message.fetched`, `message.created`) - 리소스별 키 필요
3. **Controller 패턴 불일치**:
- 기존: `return ['data' => $data, 'message' => __('message.xxx')]` (배열 래핑)
- Product/Material: `return $this->service->xxx()` + ApiResponse::handle 두 번째 인자
4. **Constructor 스타일**: `protected` + 수동 할당 (PHP 8.2+ constructor property promotion 미사용)
## 📁 수정된 파일
### 1. `app/Http/Requests/Client/ClientStoreRequest.php` (신규 생성)
**목적:** 거래처 생성 시 입력 검증을 FormRequest로 분리
**주요 내용:**
```php
public function rules(): array
{
return [
'client_group_id' => 'nullable|integer',
'client_code' => 'required|string|max:50',
'name' => 'required|string|max:100',
'contact_person' => 'nullable|string|max:100',
'phone' => 'nullable|string|max:20',
'email' => 'nullable|email|max:100',
'address' => 'nullable|string|max:255',
'is_active' => 'nullable|in:Y,N',
];
}
```
**검증 규칙:**
- **필수 필드**: client_code, name
- **이메일 검증**: email 규칙 적용
- **제약 조건**: is_active는 Y/N만 허용
### 2. `app/Http/Requests/Client/ClientUpdateRequest.php` (신규 생성)
**목적:** 거래처 수정 시 입력 검증을 FormRequest로 분리
**주요 내용:**
- StoreRequest와 동일한 필드 구조
- client_code, name에 'sometimes' 규칙 적용 (부분 업데이트 지원)
- 나머지 필드는 nullable (선택적 업데이트)
### 3. `app/Http/Controllers/Api/V1/ClientController.php` (수정)
**변경 전:** 73줄
```php
class ClientController extends Controller
{
protected ClientService $service;
public function __construct(ClientService $service)
{
$this->service = $service;
}
public function index(Request $request)
{
return ApiResponse::handle(function () use ($request) {
$data = $this->service->index($request->all());
return ['data' => $data, 'message' => __('message.fetched')];
});
}
public function store(Request $request)
{
return ApiResponse::handle(function () use ($request) {
$data = $this->service->store($request->all());
return ['data' => $data, 'message' => __('message.created')];
});
}
// ... 나머지 메서드들도 동일한 패턴
}
```
**변경 후:** 58줄
```php
class ClientController extends Controller
{
public function __construct(private ClientService $service) {}
public function index(Request $request)
{
return ApiResponse::handle(function () use ($request) {
return $this->service->index($request->all());
}, __('message.client.fetched'));
}
public function store(ClientStoreRequest $request)
{
return ApiResponse::handle(function () use ($request) {
return $this->service->store($request->validated());
}, __('message.client.created'));
}
public function update(ClientUpdateRequest $request, int $id)
{
return ApiResponse::handle(function () use ($request, $id) {
return $this->service->update($id, $request->validated());
}, __('message.client.updated'));
}
public function destroy(int $id)
{
return ApiResponse::handle(function () use ($id) {
$this->service->destroy($id);
return 'success';
}, __('message.client.deleted'));
}
public function toggle(int $id)
{
return ApiResponse::handle(function () use ($id) {
return $this->service->toggle($id);
}, __('message.client.toggled'));
}
}
```
**주요 변경사항:**
1. **Constructor Property Promotion**: `protected` + 수동 할당 → `private` + 자동 할당
2. **FormRequest 적용**: `Request``ClientStoreRequest`, `ClientUpdateRequest`
3. **validated() 사용**: `$request->all()``$request->validated()` (보안 강화)
4. **패턴 통일**: 배열 래핑 제거, ApiResponse::handle 두 번째 인자로 메시지 전달
5. **i18n 키 사용**: `message.xxx``message.client.xxx` (리소스별 키)
**적용된 메서드:**
- index(): `__('message.client.fetched')`
- show(): `__('message.client.fetched')`
- store(): `__('message.client.created')`
- update(): `__('message.client.updated')`
- destroy(): `__('message.client.deleted')`
- toggle(): `__('message.client.toggled')`
### 4. `lang/ko/message.php` (수정)
**추가된 내용:**
```php
// 거래처 관리
'client' => [
'fetched' => '거래처를 조회했습니다.',
'created' => '거래처가 등록되었습니다.',
'updated' => '거래처가 수정되었습니다.',
'deleted' => '거래처가 삭제되었습니다.',
'toggled' => '거래처 상태가 변경되었습니다.',
],
```
**추가 이유:**
- Product, Material과 일관성 유지
- 리소스별 명확한 메시지 제공
- toggle() 메서드용 메시지 추가 (상태 변경 전용)
## 🔍 SAM API Rules 준수 확인
### ✅ 준수 항목
1. **Swagger 주석 분리**
- ClientApi.php에 모든 Swagger 주석 집중 (이미 준수됨)
- Controller는 비즈니스 로직만 유지
2. **FormRequest 사용**
- ClientStoreRequest, ClientUpdateRequest 생성
- Controller에서 타입 힌트 적용
- `$request->validated()` 사용
3. **i18n 메시지 키 사용**
- 모든 공통 키를 리소스별 키로 변경
- `__('message.client.xxx')` 형식 적용
4. **Controller 패턴 통일**
- Product, Material과 동일한 패턴 적용
- ApiResponse::handle 두 번째 인자로 메시지 전달
- 불필요한 배열 래핑 제거
5. **Service-First 패턴**
- 비즈니스 로직은 ClientService에 유지
- Controller는 DI + ApiResponse::handle()만 사용
6. **Modern PHP 문법**
- Constructor Property Promotion 사용 (PHP 8.0+)
- Private property로 캡슐화 강화
## ✅ 테스트 체크리스트
- [x] PHP 문법 체크 (php -l)
- [x] ClientStoreRequest 문법 확인
- [x] ClientUpdateRequest 문법 확인
- [x] ClientController 문법 확인
- [x] message.php 문법 확인
- [ ] Swagger 재생성 (`php artisan l5-swagger:generate`)
- [ ] Swagger UI 확인 (http://api.sam.kr/api-docs/index.html)
- [ ] 실제 API 호출 테스트
- [ ] GET /api/v1/clients (목록 조회)
- [ ] POST /api/v1/clients (FormRequest 검증 확인)
- [ ] GET /api/v1/clients/{id} (단건 조회)
- [ ] PUT /api/v1/clients/{id} (FormRequest 검증 확인)
- [ ] DELETE /api/v1/clients/{id} (삭제)
- [ ] PATCH /api/v1/clients/{id}/toggle (상태 토글)
## ⚠️ 배포 시 주의사항
1. **FormRequest 적용으로 검증 로직 변경**
- 기존: Service에서 모든 검증 (추정)
- 변경 후: FormRequest(기본 검증) + Service(비즈니스 검증)
- **영향:** 검증 에러 응답 형식 동일 (422 Unprocessable Entity)
2. **i18n 메시지 변경**
- 기존: `__('message.fetched')`, `__('message.created')` (공통 키)
- 변경 후: `__('message.client.xxx')` (리소스별 키)
- **영향:** 응답 메시지 내용 약간 변경 (의미는 동일)
3. **Controller 응답 패턴 변경**
- 기존: `return ['data' => $data, 'message' => __('message.xxx')]`
- 변경 후: `return $data` + ApiResponse::handle 두 번째 인자
- **영향:** 응답 JSON 구조는 동일 (ApiResponse::handle이 래핑 처리)
4. **코드 간소화**
- 73줄 → 58줄 (15줄 감소)
- **영향:** 유지보수성 향상, 기능은 동일
## 📊 변경 통계
- **신규 파일:** 2개 (FormRequest)
- **수정 파일:** 2개 (ClientController.php, message.php)
- **삭제 파일:** 0개
- **코드 감소:** -15줄 (Controller 간소화)
- **실질 추가:** +60줄 (FormRequest)
- **SAM 규칙 준수:** 100%
## 🎯 다음 작업
1. Swagger 재생성 및 검증
2. 실제 API 테스트
3. Phase 3-4: UserApi.php Swagger 점검
## 🔗 관련 문서
- `CLAUDE.md` - SAM 프로젝트 가이드
- `SWAGGER_AUDIT.md` - Swagger 전체 점검 현황
- `SWAGGER_PHASE3_1_PRODUCT.md` - Phase 3-1 작업 문서
- `SWAGGER_PHASE3_2_MATERIAL.md` - Phase 3-2 작업 문서
- SAM API Development Rules (CLAUDE.md 내 섹션)
## 📝 커밋 정보
**커밋 해시:** c87aadc
**커밋 메시지:**
```
feat: ClientApi.php Swagger 점검 및 개선 (Phase 3-3)
- ClientStoreRequest.php 생성 (검증 로직 분리)
- ClientUpdateRequest.php 생성 (검증 로직 분리)
- ClientController.php FormRequest 적용 및 패턴 통일
- lang/ko/message.php client 메시지 키 추가
- ApiResponse::handle 패턴 통일 (메시지 두 번째 인자)
- SAM API Development Rules 준수 완료
```
---
**Phase 3-3 완료 ✅**

View File

@@ -1,411 +0,0 @@
# BP-MES 하이브리드 구조 사용 가이드
## 📖 개요
BP-MES Phase 1에서 도입한 하이브리드 구조는 **멀티테넌트 유연성**과 **통계 성능**을 동시에 달성하기 위한 설계입니다.
### 핵심 개념
- **고정 필드**: 자주 조회하고 통계를 내는 필드 (인덱싱)
- **동적 필드**: 테넌트별로 다를 수 있는 필드 (JSON attributes)
---
## 🗂️ 데이터베이스 구조
### 1. products 테이블
```sql
-- 고정 필드 (6개)
safety_stock INT NULL -- 안전 재고 수량
lead_time INT NULL -- 리드타임 (일)
is_variable_size BOOLEAN -- 가변 치수 여부
product_category VARCHAR(20) -- 통계용 분류 (SCREEN, STEEL 등)
part_type VARCHAR(20) -- 통계용 분류 (ASSEMBLY, BENDING, PURCHASED)
attributes_archive JSON NULL -- 카테고리 변경 시 백업
-- 동적 필드
attributes JSON NULL -- 테넌트별 커스텀 필드
-- 인덱스
INDEX idx_products_product_category (tenant_id, product_category)
INDEX idx_products_part_type (tenant_id, part_type)
INDEX idx_products_is_variable_size (tenant_id, is_variable_size)
```
### 2. product_components 테이블
```sql
-- 고정 필드 (2개)
quantity_formula VARCHAR(500) -- BOM 수식 계산
condition TEXT -- 조건부 BOM
-- 동적 필드
attributes JSON -- 테넌트별 커스텀 필드
```
### 3. 메타 정보 테이블
```sql
-- category_fields: 동적 필드 스키마 정의
category_id, field_key, field_name, field_type, is_required, options...
-- tenant_stat_fields: 통계 필드 설정
tenant_id, target_table, field_key, aggregation_types, is_critical...
```
---
## 💻 코드 예제
### Product 생성 (완제품 - FG)
```php
use App\Models\Products\Product;
$product = Product::create([
'tenant_id' => 1,
'code' => 'FG-001',
'name' => '방화셔터 KSS01',
'unit' => 'EA',
'category_id' => $fgCategoryId, // 완제품 카테고리
// 고정 필드
'safety_stock' => 10,
'lead_time' => 5,
'is_variable_size' => false,
'product_category' => 'SCREEN',
// 동적 필드 (attributes JSON)
'attributes' => [
// 비용 관리
'margin_rate' => 25.5,
'processing_cost' => 50000,
'labor_cost' => 30000,
'install_cost' => 20000,
// LOT 관리
'lot_abbreviation' => 'KSS',
'note' => '스크린 제품',
// 인증 정보
'certification_number' => 'CERT-2025-001',
'certification_start_date' => '2025-01-01',
'certification_end_date' => '2027-12-31',
// 파일
'specification_file' => '/files/spec_001.pdf',
'specification_file_name' => '규격서.pdf',
],
]);
```
### Product 생성 (부품 - PT)
```php
$part = Product::create([
'tenant_id' => 1,
'code' => 'PT-001',
'name' => '가이드레일 A형',
'unit' => 'M',
'category_id' => $ptCategoryId, // 부품 카테고리
// 고정 필드
'safety_stock' => 100,
'lead_time' => 3,
'is_variable_size' => false,
'part_type' => 'PURCHASED',
// 동적 필드
'attributes' => [
'part_usage' => '셔터 가이드',
'installation_type' => 'WALL',
'assembly_type' => 'BOLT',
'side_spec_width' => 70,
'side_spec_height' => 50,
'assembly_length' => 3000,
'guide_rail_model_type' => 'TYPE_A',
'guide_rail_model' => 'GR-A-2025',
],
]);
```
### Product 생성 (절곡품)
```php
$bending = Product::create([
'tenant_id' => 1,
'code' => 'BD-001',
'name' => '절곡 브라켓 L형',
'unit' => 'EA',
'category_id' => $bendingCategoryId, // 절곡품 카테고리
// 고정 필드
'safety_stock' => 50,
'lead_time' => 2,
'is_variable_size' => true, // 맞춤형 절곡
// 동적 필드
'attributes' => [
'bending_diagram' => '/files/bending_001.dwg',
'bending_details' => [
['angle' => 90, 'position' => 100],
['angle' => 45, 'position' => 250],
],
'material' => 'STEEL',
'length' => 500,
'bending_length' => 350,
],
]);
```
### ProductComponent 생성 (BOM)
```php
use App\Models\Products\ProductComponent;
// 일반 BOM
$component = ProductComponent::create([
'tenant_id' => 1,
'parent_product_id' => $productId,
'ref_type' => 'MATERIAL',
'ref_id' => $materialId,
'quantity' => 5.0,
'sort_order' => 1,
]);
// 수식 계산 BOM
$formulaComponent = ProductComponent::create([
'tenant_id' => 1,
'parent_product_id' => $productId,
'ref_type' => 'PRODUCT',
'ref_id' => $partId,
'quantity' => 1.0, // 기본값
'quantity_formula' => 'W0 * H0 / 1000000', // 면적 기반 계산
'sort_order' => 2,
]);
// 조건부 BOM
$conditionalComponent = ProductComponent::create([
'tenant_id' => 1,
'parent_product_id' => $productId,
'ref_type' => 'PRODUCT',
'ref_id' => $partId,
'quantity' => 2.0,
'condition' => 'installation_type=CEILING', // 천장형일 때만
'sort_order' => 3,
]);
// 절곡품 BOM
$bendingComponent = ProductComponent::create([
'tenant_id' => 1,
'parent_product_id' => $productId,
'ref_type' => 'PRODUCT',
'ref_id' => $bendingProductId,
'quantity' => 4.0,
'attributes' => [
'is_bending' => true,
'bending_diagram' => '/files/bending_diagram_001.dwg',
'bending_details' => [
['angle' => 90, 'position' => 100],
],
],
'sort_order' => 4,
]);
```
### attributes 조회
```php
// 단순 조회
$product = Product::find($id);
$marginRate = $product->attributes['margin_rate'] ?? null;
// 배열 접근
$certNumber = $product->attributes['certification_number'] ?? '없음';
// JSON 중첩 구조
$bendingDetails = $product->attributes['bending_details'] ?? [];
```
### 통계 필드 조회
```php
use App\Models\Tenants\TenantStatField;
// 특정 테넌트의 products 통계 필드
$statFields = TenantStatField::where('tenant_id', $tenantId)
->forTable('products')
->critical() // 중요 필드만
->ordered()
->get();
// 평균 집계가 필요한 필드
$avgFields = TenantStatField::where('tenant_id', $tenantId)
->withAggregation('avg')
->get();
// 통계 계산 예시
foreach ($statFields as $field) {
$fieldKey = $field->field_key;
$aggregations = $field->aggregation_types;
foreach ($aggregations as $agg) {
// DB::raw("JSON_EXTRACT(attributes, '$.{$fieldKey}')")
// 를 사용하여 통계 계산
}
}
```
---
## 🔍 통계 쿼리 예제
### JSON 필드 통계
```php
use Illuminate\Support\Facades\DB;
// 평균 마진율 (SCREEN 카테고리)
$avgMargin = DB::table('products')
->where('tenant_id', $tenantId)
->where('product_category', 'SCREEN')
->whereNotNull('attributes')
->selectRaw("AVG(JSON_UNQUOTE(JSON_EXTRACT(attributes, '$.margin_rate'))) as avg_margin")
->value('avg_margin');
// 가공비 합계 (부품 타입별)
$costByType = DB::table('products')
->where('tenant_id', $tenantId)
->whereNotNull('part_type')
->selectRaw("
part_type,
SUM(JSON_UNQUOTE(JSON_EXTRACT(attributes, '$.processing_cost'))) as total_cost
")
->groupBy('part_type')
->get();
```
---
## 📝 카테고리 필드 스키마 관리
### CategoryField 조회
```php
use App\Models\Commons\CategoryField;
// 특정 카테고리의 필드 스키마
$fields = CategoryField::where('category_id', $categoryId)
->orderBy('sort_order')
->get();
// 동적 폼 생성
foreach ($fields as $field) {
echo "Field: {$field->field_name} ({$field->field_key})\n";
echo "Type: {$field->field_type}\n";
echo "Required: {$field->is_required}\n";
if ($field->field_type === 'select') {
$options = $field->options; // JSON 자동 파싱
echo "Options: " . implode(', ', $options) . "\n";
}
}
```
---
## ⚠️ 주의사항
### 1. attributes JSON 구조
```php
// ✅ 올바른 사용
$product->attributes = [
'margin_rate' => 25.5,
'processing_cost' => 50000,
];
// ❌ 잘못된 사용 (중첩 깊이 제한)
$product->attributes = [
'level1' => [
'level2' => [
'level3' => [
'level4' => 'too deep' // 피하세요
]
]
]
];
```
### 2. 고정 필드 vs 동적 필드 선택 기준
```
고정 필드로 만들어야 하는 경우:
✅ 모든 테넌트가 공통으로 사용
✅ 인덱스가 필요한 필드 (통계, 검색)
✅ WHERE 절에서 자주 사용
동적 필드로 만들어야 하는 경우:
✅ 테넌트마다 다를 수 있는 필드
✅ 선택적으로 사용하는 필드
✅ 자주 변경되는 비즈니스 로직
```
### 3. 성능 고려사항
```php
// ✅ 고정 필드로 필터링 (빠름)
Product::where('product_category', 'SCREEN')
->where('is_variable_size', false)
->get();
// ⚠️ JSON 필드로 필터링 (느림 - 필요시에만)
Product::whereRaw("JSON_EXTRACT(attributes, '$.margin_rate') > ?", [20])
->get();
```
---
## 🚀 향후 확장
### 통계 시스템 연동
```php
// tenant_stat_fields를 기반으로 통계 생성
$statFields = TenantStatField::where('tenant_id', $tenantId)
->critical()
->get();
foreach ($statFields as $field) {
// 통계 시스템에서 처리
// - aggregation_types에 따라 avg, sum, min, max 계산
// - 리포트에 표시
}
```
### Virtual Column 최적화 (MySQL 8.0+)
자주 조회하는 JSON 필드를 Virtual Column으로 변환하여 성능 향상:
```sql
-- 예시: margin_rate를 Virtual Column으로
ALTER TABLE products
ADD COLUMN margin_rate_virtual DECIMAL(5,2)
AS (JSON_UNQUOTE(JSON_EXTRACT(attributes, '$.margin_rate'))) VIRTUAL;
CREATE INDEX idx_margin_rate ON products(margin_rate_virtual);
```
---
## 📚 참고 문서
- `database/migrations/2025_11_14_000001_add_hybrid_fields_to_products_table.php`
- `database/migrations/2025_11_14_000002_add_attributes_to_product_components_table.php`
- `database/migrations/2025_11_14_000003_create_tenant_stat_fields_table.php`
- `database/seeders/BpMesCategoryFieldsSeeder.php`
- `database/seeders/BpMesTenantStatFieldsSeeder.php`
- `app/Models/Products/Product.php`
- `app/Models/Products/ProductComponent.php`
- `app/Models/Tenants/TenantStatField.php`