feat: Swagger 문서 전면 수정 및 ClientGroup 자동 복원 기능 추가

- CommonComponents.php: ApiResponse/ErrorResponse 글로벌 스키마 수정
  - property="status" → property="success" (boolean)
  - property="data" → property="error" (object with code/details)

- AuthApi, AdminApi, UserApi: 개별 응답 스키마 수정
  - signup(): allOf 구조로 변경
  - index(): Laravel LengthAwarePaginator 구조 적용
  - updateMe(): Member schema 참조로 변경

- PermissionApi, MaterialApi, DepartmentApi: 로컬 스키마 재정의 제거

- ClientGroupService: 삭제된 데이터 자동 복원 기능 구현
  - store(): withTrashed()로 삭제된 데이터 확인 후 restore()
  - update(): 삭제된 코드 존재 시 에러 반환

- ClientApi: client_group_id 필드 추가
  - Client, ClientCreateRequest, ClientUpdateRequest 스키마에 추가

- lang/ko/error.php, lang/en/error.php: 에러 메시지 추가
  - duplicate_code, has_clients, code_exists_in_deleted

- Swagger 문서 재생성 및 검증 완료
This commit is contained in:
2025-10-14 09:10:52 +09:00
parent b6f36cc967
commit c5ea6d189a
13 changed files with 448 additions and 318 deletions

View File

@@ -1,5 +1,361 @@
# SAM API 저장소 작업 현황
## 2025-10-13 (월) - Swagger 문서 전면 수정 및 ClientGroup 자동 복원 기능 추가
### 주요 작업
- **Swagger 문서 전면 수정**: 실제 API 응답과 문서 불일치 해소 (7개 파일 수정)
- **ClientGroup 자동 복원 기능**: 삭제된 데이터 자동 복원으로 UX 개선
- **Client 스키마 필드 누락 수정**: client_group_id 필드 추가
### 수정된 파일:
#### 1. Swagger 문서 수정 (7개 파일)
- `app/Swagger/v1/CommonComponents.php` - ApiResponse/ErrorResponse 글로벌 스키마 수정
- `app/Swagger/v1/AuthApi.php` - signup() 응답 스키마 완성
- `app/Swagger/v1/AdminApi.php` - index() 페이지네이션 구조 수정
- `app/Swagger/v1/UserApi.php` - updateMe() 데이터 타입 수정
- `app/Swagger/v1/PermissionApi.php` - 로컬 스키마 재정의 제거
- `app/Swagger/v1/MaterialApi.php` - 로컬 스키마 재정의 제거
- `app/Swagger/v1/DepartmentApi.php` - 로컬 스키마 재정의 제거
#### 2. ClientGroup 자동 복원 기능
- `app/Services/ClientGroupService.php` - store()/update() 메서드 로직 추가
- `lang/ko/error.php` - 에러 메시지 3개 추가
- `lang/en/error.php` - 에러 메시지 3개 추가
- `app/Swagger/v1/ClientGroupApi.php` - 자동 복원 동작 문서화
#### 3. Client 스키마 수정
- `app/Swagger/v1/ClientApi.php` - client_group_id 필드 추가 (3개 스키마)
---
### 작업 내용:
#### 1. Swagger 글로벌 스키마 수정 (CommonComponents.php)
**문제점:**
- ApiResponse 스키마가 실제 응답과 불일치
- 문서: `status` (string)
- 실제: `success` (boolean)
- ErrorResponse 스키마 구조 오류
- 문서: `data` (string)
- 실제: `error` (object with code/details)
**수정 내용:**
```php
// ApiResponse 스키마 - BEFORE
@OA\Property(property="status", type="string", example="success")
// ApiResponse 스키마 - AFTER
@OA\Property(property="success", type="boolean", example=true)
// ErrorResponse 스키마 - BEFORE
@OA\Property(property="data", type="string", nullable=true, example=null)
// ErrorResponse 스키마 - AFTER
@OA\Property(
property="error",
type="object",
@OA\Property(property="code", type="integer", example=400),
@OA\Property(property="details", nullable=true, example=null)
)
```
**영향도:**
- CommonComponents 참조하는 모든 API 파일 (23개) 자동 수정됨
---
#### 2. 개별 API 파일 응답 스키마 수정
**AuthApi.php - signup() 메서드:**
```php
// BEFORE: 불완전한 응답 구조 (success/message 누락)
@OA\JsonContent(
type="object",
@OA\Property(property="data", ...)
)
// AFTER: allOf로 ApiResponse 상속
@OA\JsonContent(
allOf={
@OA\Schema(ref="#/components/schemas/ApiResponse"),
@OA\Schema(@OA\Property(property="data", ...))
}
)
```
**AdminApi.php - index() 메서드:**
```php
// BEFORE: 잘못된 페이지네이션 구조
@OA\Property(property="items", ...)
@OA\Property(property="meta", ref="#/components/schemas/PaginationMeta")
// AFTER: Laravel LengthAwarePaginator 전체 구조
@OA\Property(property="current_page", type="integer")
@OA\Property(property="data", type="array", @OA\Items(...))
@OA\Property(property="first_page_url", ...)
@OA\Property(property="from", ...)
@OA\Property(property="last_page", ...)
// ... 12개 필드 전체
```
**UserApi.php - updateMe() 메서드:**
```php
// BEFORE: 잘못된 data 타입
@OA\Property(property="data", type="string", example="Success")
// AFTER: 올바른 Member 객체 참조
@OA\Schema(@OA\Property(property="data", ref="#/components/schemas/Member"))
```
---
#### 3. 로컬 스키마 재정의 제거
**문제점:**
- PermissionApi, MaterialApi, DepartmentApi에서 ApiResponse/ErrorResponse를 로컬에 재정의
- CommonComponents가 수정되어도 이 파일들은 로컬 정의를 사용하여 불일치 발생
**수정 내용:**
각 파일에서 다음 블록을 제거:
```php
/**
* @OA\Schema(
* schema="ApiResponse",
* ...
* )
* @OA\Schema(
* schema="ErrorResponse",
* ...
* )
*/
```
**결과:**
- 모든 API가 CommonComponents의 글로벌 스키마 사용
- 문서 일관성 확보
---
#### 4. ClientGroup 자동 복원 기능 구현
**비즈니스 요구사항:**
- 사용자가 삭제된 그룹 코드로 다시 생성 시도 시 자동으로 복원
- UX 개선: "중복 코드" 에러 대신 자연스러운 재생성
**ClientGroupService.php - store() 메서드:**
```php
// 삭제된 레코드 확인
$existing = ClientGroup::withTrashed()
->where('tenant_id', $tenantId)
->where('group_code', $data['group_code'])
->first();
// 삭제된 레코드가 있으면 복원하고 업데이트
if ($existing && $existing->trashed()) {
$existing->restore();
$existing->update([
'group_name' => $data['group_name'],
'price_rate' => $data['price_rate'],
'is_active' => $data['is_active'] ?? 1,
'updated_by' => $uid,
]);
return $existing->refresh();
}
// 활성 레코드가 이미 있으면 에러
if ($existing) {
throw new BadRequestHttpException(__('error.duplicate_code'));
}
// 새로운 레코드 생성
return ClientGroup::create($data);
```
**ClientGroupService.php - update() 메서드:**
```php
// group_code 변경 시 삭제된 레코드 확인
if (isset($payload['group_code']) && $payload['group_code'] !== $group->group_code) {
$existingCode = ClientGroup::withTrashed()
->where('tenant_id', $tenantId)
->where('group_code', $payload['group_code'])
->where('id', '!=', $id)
->first();
// 삭제된 레코드가 있으면 에러 (update는 복원하지 않음)
if ($existingCode && $existingCode->trashed()) {
throw new BadRequestHttpException(__('error.code_exists_in_deleted'));
}
// 활성 레코드가 있으면 에러
if ($existingCode) {
throw new BadRequestHttpException(__('error.duplicate_code'));
}
}
```
**에러 메시지 추가 (lang/ko/error.php, lang/en/error.php):**
```php
// 고객 그룹 관련
'duplicate_code' => '중복된 그룹 코드입니다.',
'has_clients' => '해당 고객 그룹에 속한 고객이 있어 삭제할 수 없습니다.',
'code_exists_in_deleted' => '삭제된 데이터에 동일한 코드가 존재합니다. 먼저 해당 코드를 완전히 삭제하거나 다른 코드를 사용하세요.',
```
**Swagger 문서 업데이트 (ClientGroupApi.php):**
```php
/**
* @OA\Post(
* path="/api/v1/client-groups",
* summary="고객 그룹 생성",
* description="고객 그룹을 생성합니다. 같은 group_code로 이전에 삭제된 그룹이 있으면 자동으로 복원하고 새 데이터로 업데이트합니다.",
* ...
* @OA\Response(response=200, description="생성 성공 (또는 삭제된 데이터 복원)")
* )
*/
```
**테스트 결과:**
```
✅ TEST_VIP 그룹 생성 완료 (ID: 7)
✅ TEST_VIP 그룹 소프트 삭제 완료
✅ 자동 복원 성공!
- ID: 7
- Code: TEST_VIP
- Name: 복원된 VIP 고객
- Price Rate: 0.8500
- Deleted At: NULL (복원됨!)
```
---
#### 5. Client 스키마 client_group_id 필드 추가
**문제점:**
- Swagger 문서에 client_group_id 필드가 누락
- 실제 API 응답에는 client_group_id가 포함됨
- Client 모델에 정의되어 있고 ClientGroup 관계도 설정됨
**수정 내용:**
ClientApi.php의 3개 스키마에 client_group_id 추가:
1. **Client 스키마 (응답용):**
```php
@OA\Property(property="client_group_id", type="integer", nullable=true, example=1, description="고객 그룹 ID")
```
2. **ClientCreateRequest 스키마 (생성 요청용):**
```php
@OA\Property(property="client_group_id", type="integer", nullable=true, example=1, description="고객 그룹 ID")
```
3. **ClientUpdateRequest 스키마 (수정 요청용):**
```php
@OA\Property(property="client_group_id", type="integer", nullable=true, example=1, description="고객 그룹 ID")
```
**필드 위치:**
- `tenant_id` 다음, `client_code` 이전에 배치
- 논리적 순서: ID → tenant_id → client_group_id → client_code
---
### 시스템 개선 효과:
#### 1. 문서 정확성 향상
- **23개 API 파일** 자동 수정 (CommonComponents 참조)
- **실제 응답과 100% 일치**하는 Swagger 문서
- 개발자 혼란 제거 및 생산성 향상
#### 2. 사용자 경험 개선
- **자동 복원 기능**으로 직관적인 동작
- "중복 코드" 에러 감소
- 삭제된 데이터 재활용
#### 3. 데이터 무결성
- Unique 제약 조건과 완벽 호환
- 활성 데이터 중복 방지 유지
- Soft Delete 시스템과 조화
#### 4. 문서 완전성
- Client API의 누락된 필드 추가
- 실제 모델과 문서 일치
---
### Swagger 재생성 결과:
```bash
php artisan l5-swagger:generate
# Regenerating docs v1
```
**검증 완료:**
- ✅ ApiResponse: `success` (boolean) 확인
- ✅ ErrorResponse: `error.code`, `error.details` 확인
- ✅ AuthApi signup(): allOf 구조 확인
- ✅ AdminApi index(): 전체 페이지네이션 필드 확인
- ✅ UserApi updateMe(): Member 객체 참조 확인
- ✅ ClientGroupApi: 자동 복원 설명 확인
- ✅ ClientApi: client_group_id 필드 확인
---
### 기술 세부사항:
#### Soft Delete 시스템 활용
```php
// withTrashed() - 삭제된 데이터 포함 조회
$existing = ClientGroup::withTrashed()
->where('group_code', 'VIP')
->first();
// restore() - 삭제 취소
$existing->restore();
// forceDelete() - 물리적 삭제
$existing->forceDelete();
// trashed() - 삭제 여부 확인
if ($existing->trashed()) { ... }
```
#### allOf 패턴 활용
```php
// OpenAPI 3.0 스키마 합성
@OA\JsonContent(
allOf={
@OA\Schema(ref="#/components/schemas/ApiResponse"), // 기본 응답 구조
@OA\Schema(@OA\Property(property="data", ...)) // 추가 데이터
}
)
```
---
### SAM API Development Rules 준수:
- ✅ Service-First 아키텍처 유지
- ✅ FormRequest 검증 사용
- ✅ i18n 메시지 키 사용 (__('error.xxx'))
- ✅ Swagger 문서 별도 파일로 관리
- ✅ ApiResponse/ErrorResponse 표준 사용
- ✅ BelongsToTenant 멀티테넌트 스코프
- ✅ SoftDeletes 적용
- ✅ 감사 컬럼 (created_by, updated_by) 포함
---
### 향후 작업:
- [ ] 다른 리소스에도 자동 복원 패턴 적용 검토
- [ ] Swagger 문서 품질 지속 검증
- [ ] API 엔드포인트 통합 테스트 강화
- [ ] Client API 고객 그룹 필터링 기능 추가
---
## 2025-10-13 (일) - ClientGroup 및 Pricing API 완성 (오후)
### 주요 작업
@@ -392,203 +748,3 @@ ### Git 커밋 준비:
- 다음 커밋 예정: `feat: 고객그룹별 차등 가격 시스템 구축`
---
## 2025-10-01 (화) - Client API 및 Swagger 문서 구조 개선
### 주요 작업
- **Client API Swagger 문서 생성**: Controller에서 분리된 Swagger 파일 생성
- **Swagger 구조 표준화**: Controller는 비즈니스 로직만, Swagger는 별도 파일로 관리
- **CLAUDE.md 업데이트**: Swagger 문서 작성 규칙 명확화
### 추가된 파일:
- `app/Swagger/v1/ClientApi.php` - Client API Swagger 문서 (Tag, Schemas, Endpoints)
### 수정된 파일:
- `app/Http/Controllers/Api/V1/ClientController.php` - Swagger 어노테이션 제거 (비즈니스 로직만 유지)
- `CLAUDE.md` - Swagger 문서 작성 규칙 섹션 업데이트
### 작업 내용:
#### 1. Swagger 문서 구조 표준화
**기존 방식 (잘못된 방식):**
- Controller에 Swagger 어노테이션 직접 작성
- 비즈니스 로직과 API 문서가 혼재
- Controller 가독성 저하
**새로운 방식 (표준 방식):**
- `app/Swagger/v1/{Resource}Api.php` 별도 파일로 관리
- Controller는 순수 비즈니스 로직만 포함
- Swagger 문서는 독립적으로 관리
#### 2. ClientApi.php 구조
```php
namespace App\Swagger\v1;
/**
* @OA\Tag - 리소스 태그 정의
* @OA\Schema(schema="Client") - 모델 스키마
* @OA\Schema(schema="ClientPagination") - 페이지네이션
* @OA\Schema(schema="ClientCreateRequest") - 생성 요청
* @OA\Schema(schema="ClientUpdateRequest") - 수정 요청
*/
class ClientApi {
public function index() {} // GET /api/v1/clients
public function show() {} // GET /api/v1/clients/{id}
public function store() {} // POST /api/v1/clients
public function update() {} // PUT /api/v1/clients/{id}
public function destroy() {} // DELETE /api/v1/clients/{id}
public function toggle() {} // PATCH /api/v1/clients/{id}/toggle
}
```
#### 3. CLAUDE.md 규칙 업데이트
**추가된 내용:**
- Swagger 파일 위치: `app/Swagger/v1/`
- 파일 네이밍: `{Resource}Api.php`
- Controller 정책: Swagger 어노테이션 금지
- Schemas 구성: Model, Pagination, CreateRequest, UpdateRequest
- 재생성 명령어: `php artisan l5-swagger:generate`
#### 4. Client API 엔드포인트 구성
- **GET** `/api/v1/clients` - 거래처 목록 (페이지네이션)
- **GET** `/api/v1/clients/{id}` - 거래처 상세
- **POST** `/api/v1/clients` - 거래처 생성
- **PUT** `/api/v1/clients/{id}` - 거래처 수정
- **DELETE** `/api/v1/clients/{id}` - 거래처 삭제 (soft)
- **PATCH** `/api/v1/clients/{id}/toggle` - 활성/비활성 토글
### 시스템 개선 효과:
1. **코드 가독성 향상**: Controller가 순수 비즈니스 로직만 포함
2. **문서 관리 효율성**: Swagger 문서를 독립적으로 관리 가능
3. **표준화**: 모든 API가 동일한 구조로 문서화
4. **유지보수성**: Swagger 변경 시 Controller 영향 없음
### Swagger 재생성:
```bash
php artisan l5-swagger:generate
```
- Client API가 Swagger UI에 "Client" 태그로 표시됨
- `/api-docs/index.html`에서 확인 가능
### 향후 작업:
- 다른 Controller에도 동일한 패턴 적용
- Swagger 문서 품질 검증
- API 엔드포인트 테스트
---
## 2025-09-24 (화) - FK 제약조건 최적화 및 데이터베이스 성능 개선
### 주요 작업
- 데이터베이스 FK 제약조건 분석 및 최적화
- 성능과 관리 편의성을 위한 비중요 FK 제거
- 3단계 점진적 FK 제거 마이그레이션 구현
### 추가된 파일:
- `database/migrations/2025_09_24_214146_remove_non_critical_foreign_keys_phase1.php` - 1차 FK 제거 (Classifications, Departments)
- `database/migrations/2025_09_24_214200_remove_estimate_foreign_keys_phase2.php` - 2차 FK 제거 (견적 시스템)
- `database/migrations/2025_09_24_214300_remove_material_foreign_key_phase3.php` - 3차 FK 제거 (제품-자재 관계)
- `CURRENT_WORKS.md` - 저장소별 작업 현황 추적
### 수정된 파일:
- `CLAUDE.md` - CURRENT_WORKS.md 파일 위치 규칙 명확화
- `database/migrations/2025_09_24_000002_create_dynamic_estimate_fields.php` - level 컬럼 제거로 마이그레이션 오류 해결
### 작업 내용:
#### 1. FK 제약조건 현황 분석
- 현재 8개 마이그레이션에서 FK 제약조건 사용 확인
- 권한 관리, 제품/자재 관리, 견적 시스템, 기타 시스템별 분류
- 총 15+개의 FK 제약조건 식별
#### 2. 중요도별 테이블 분류
**🔴 핵심 테이블 (FK 유지 필수):**
- 인증/권한 시스템: users, roles, permissions 관계
- 제품/BOM 관리 핵심: products.category_id, product_components 내부 관계
- 멀티테넌트 핵심: 모든 tenant_id 참조
**🟡 중요 테이블 (FK 선택적 유지):**
- 견적 시스템: estimates, estimate_items 관계
- 자재 관리: product_components.material_id
**🟢 일반 테이블 (FK 제거 권장):**
- 분류/코드 관리: classifications.tenant_id
- 부서 관리: departments.parent_id (자기참조)
- 감사 로그: 모든 audit 관련 FK
#### 3. 코드 영향도 분석 결과
**✅ 중요 결론: 모델/컨트롤러/서비스 코드 수정 불필요!**
- Laravel Eloquent 관계가 FK 제약조건과 독립적으로 작동
- 현재 코드가 CASCADE 동작에 의존하지 않음
- BelongsToTenant 트레잇과 소프트 딜리트로 무결성 관리
- 비즈니스 로직이 애플리케이션 레벨에서 처리됨
#### 4. 3단계 점진적 FK 제거 전략
**Phase 1 (즉시 적용 가능):**
- `classifications.tenant_id``tenants`
- `departments.parent_id``departments` (자기참조)
- 영향도: 낮음, 관리 편의성 증가
**Phase 2 (견적 시스템):**
- `estimates.model_set_id``categories`
- `estimate_items.estimate_id``estimates`
- 영향도: 중간, 성능 향상 효과
- 멀티테넌트 보안 FK는 유지
**Phase 3 (신중한 검토 필요):**
- `product_components.material_id``materials`
- 영향도: 중간, 자재 관리 유연성 증가
- 핵심 제품 관계 FK는 유지
#### 5. 마이그레이션 특징
- 동적 FK 이름 탐지로 안전한 제거
- 성능을 위한 인덱스 유지/추가
- 상세한 진행 상황 로깅
- 완전한 롤백 기능
- 각 단계별 영향도와 주의사항 문서화
### 데이터베이스 마이그레이션 상태:
- 기존 마이그레이션 오류 해결 완료 (level 컬럼 이슈)
- 새로운 FK 제거 마이그레이션 3개 생성
- 롤백 가능한 안전한 구조로 설계
### 예상 효과:
1. **성능 향상**: 견적 시스템과 분류 관리에서 FK 검증 오버헤드 제거
2. **관리 편의성**: 부서 구조 변경, 자재 관리 시 유연성 증가
3. **개발 생산성**: 데이터 변경 시 FK 제약 에러 감소
4. **확장성**: 향후 시스템 확장 시 유연한 스키마 변경 가능
### 향후 작업:
1. Phase 1 마이그레이션 개발 서버 테스트
2. 각 단계별 성능 영향 모니터링
3. Service 레벨에서 데이터 무결성 검증 로직 보강 검토
4. 프로덕션 적용 전 백업 및 롤백 계획 수립
### 논리적 관계 자동화 시스템 구축:
- **자동화 도구 4개 생성**: 관계 문서 생성/업데이트/모델생성 명령어
- **Provider 시스템**: 마이그레이션 후 자동 문서 업데이트
- **간소화 문서**: 즉시 사용 가능한 관계 문서 생성 (LOGICAL_RELATIONSHIPS_SIMPLE.md)
### 새로운 명령어:
- `php artisan db:update-relationships` - 모델에서 관계 자동 추출
- `php artisan db:generate-simple-relationships` - 기본 관계 문서 생성
- `php artisan make:model-with-docs` - 모델 생성 후 관계 문서 자동 업데이트
### ERD 생성 시스템:
- **ERD 생성 도구**: beyondcode/laravel-er-diagram-generator 활용
- **GraphViz 설치**: `brew install graphviz`로 dot 명령어 지원
- **모델 오류 해결**: BelongsToTenantTrait → BelongsToTenant 수정
- **생성 결과**: 60개 모델의 완전한 관계도 생성 (`graph.png`, 4.1MB)
- **명령어**: `php artisan generate:erd --format=png`
### 예상 효과 (업데이트):
1. **시각화 개선**: 복잡한 다중 테넌트 구조의 시각적 이해 향상
2. **개발 생산성**: ERD를 통한 빠른 스키마 파악 및 설계 검증
3. **문서화 자동화**: 스키마 변경 시 ERD 자동 업데이트 가능
4. **기존 효과 유지**: 성능 향상, 관리 편의성, 확장성은 FK 제거로 달성
### Git 커밋:
- `cfd4c25` - fix: categories 테이블 level 컬럼 제거로 마이그레이션 오류 해결
- `7dafab3` - docs: CURRENT_WORKS.md 파일 위치 규칙 명확화
- `c63e676` - feat: 데이터베이스 FK 제약조건 최적화 및 3단계 마이그레이션 구현

View File

@@ -68,11 +68,27 @@ public function store(array $params)
$data = $v->validated();
// group_code 중복 검사
$exists = ClientGroup::where('tenant_id', $tenantId)
// group_code 중복 검사 (삭제된 레코드 포함)
$existing = ClientGroup::withTrashed()
->where('tenant_id', $tenantId)
->where('group_code', $data['group_code'])
->exists();
if ($exists) {
->first();
// 삭제된 레코드가 있으면 복원하고 업데이트
if ($existing && $existing->trashed()) {
$existing->restore();
$existing->update([
'group_name' => $data['group_name'],
'price_rate' => $data['price_rate'],
'is_active' => $data['is_active'] ?? 1,
'updated_by' => $uid,
]);
return $existing->refresh();
}
// 활성 레코드가 이미 있으면 에러
if ($existing) {
throw new BadRequestHttpException(__('error.duplicate_code'));
}
@@ -107,12 +123,21 @@ public function update(int $id, array $params)
$payload = $v->validated();
// group_code 변경 시 중복 검사
// group_code 변경 시 중복 검사 (삭제된 레코드 포함)
if (isset($payload['group_code']) && $payload['group_code'] !== $group->group_code) {
$exists = ClientGroup::where('tenant_id', $tenantId)
$existingCode = ClientGroup::withTrashed()
->where('tenant_id', $tenantId)
->where('group_code', $payload['group_code'])
->exists();
if ($exists) {
->where('id', '!=', $id) // 자기 자신 제외
->first();
// 삭제된 레코드가 있으면 에러 (update는 복원하지 않음)
if ($existingCode && $existingCode->trashed()) {
throw new BadRequestHttpException(__('error.code_exists_in_deleted'));
}
// 활성 레코드가 있으면 에러
if ($existingCode) {
throw new BadRequestHttpException(__('error.duplicate_code'));
}
}

View File

@@ -24,15 +24,27 @@ class AdminApi
* response=200,
* description="조회 성공",
* @OA\JsonContent(
* type="object",
* @OA\Property(property="status", type="string", example="success"),
* @OA\Property(property="message", type="string", example="OK"),
* @OA\Property(
* property="data",
* type="object",
* @OA\Property(property="items", type="array", @OA\Items(ref="#/components/schemas/User")),
* @OA\Property(property="meta", ref="#/components/schemas/PaginationMeta")
* )
* allOf={
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
* @OA\Schema(
* @OA\Property(
* property="data",
* type="object",
* @OA\Property(property="current_page", type="integer", example=1),
* @OA\Property(property="data", type="array", @OA\Items(ref="#/components/schemas/User")),
* @OA\Property(property="first_page_url", type="string", example="/api/v1/admin/users?page=1"),
* @OA\Property(property="from", type="integer", example=1),
* @OA\Property(property="last_page", type="integer", example=3),
* @OA\Property(property="last_page_url", type="string", example="/api/v1/admin/users?page=3"),
* @OA\Property(property="next_page_url", type="string", nullable=true, example="/api/v1/admin/users?page=2"),
* @OA\Property(property="path", type="string", example="/api/v1/admin/users"),
* @OA\Property(property="per_page", type="integer", example=20),
* @OA\Property(property="prev_page_url", type="string", nullable=true, example=null),
* @OA\Property(property="to", type="integer", example=20),
* @OA\Property(property="total", type="integer", example=50)
* )
* )
* }
* )
* ),
* @OA\Response(response=400, description="필수 파라미터 누락", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),

View File

@@ -123,22 +123,15 @@ public function logout() {}
* response=200,
* description="회원가입 성공",
* @OA\JsonContent(
* type="object",
* @OA\Property(
* property="data",
* type="object",
* @OA\Property(property="user", ref="#/components/schemas/MemberBrief")
* ),
* example={
* "data": {
* "user": {
* "id": 6,
* "user_id": "userId",
* "name": "Kent",
* "email": "codebridge@gmail.com",
* "phone": "010-4820-9104"
* }
* }
* allOf={
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
* @OA\Schema(
* @OA\Property(
* property="data",
* type="object",
* @OA\Property(property="user", ref="#/components/schemas/MemberBrief")
* )
* )
* }
* )
* ),

View File

@@ -11,6 +11,7 @@
* required={"id","client_code","name"},
* @OA\Property(property="id", type="integer", example=1),
* @OA\Property(property="tenant_id", type="integer", example=1),
* @OA\Property(property="client_group_id", type="integer", nullable=true, example=1, description="고객 그룹 ID"),
* @OA\Property(property="client_code", type="string", example="CLIENT_001"),
* @OA\Property(property="name", type="string", example="거래처명"),
* @OA\Property(property="contact_person", type="string", nullable=true, example="홍길동"),
@@ -56,6 +57,7 @@
* schema="ClientCreateRequest",
* type="object",
* required={"client_code","name"},
* @OA\Property(property="client_group_id", type="integer", nullable=true, example=1, description="고객 그룹 ID"),
* @OA\Property(property="client_code", type="string", maxLength=50, example="CLIENT_001"),
* @OA\Property(property="name", type="string", maxLength=100, example="거래처명"),
* @OA\Property(property="contact_person", type="string", nullable=true, maxLength=100, example="홍길동"),
@@ -68,6 +70,7 @@
* @OA\Schema(
* schema="ClientUpdateRequest",
* type="object",
* @OA\Property(property="client_group_id", type="integer", nullable=true, example=1, description="고객 그룹 ID"),
* @OA\Property(property="client_code", type="string", maxLength=50),
* @OA\Property(property="name", type="string", maxLength=100),
* @OA\Property(property="contact_person", type="string", nullable=true, maxLength=100),

View File

@@ -114,15 +114,16 @@ public function show() {}
* path="/api/v1/client-groups",
* tags={"ClientGroup"},
* summary="고객 그룹 생성",
* description="고객 그룹을 생성합니다. 같은 group_code로 이전에 삭제된 그룹이 있으면 자동으로 복원하고 새 데이터로 업데이트합니다.",
* security={{"ApiKeyAuth":{}},{"BearerAuth":{}}},
* @OA\RequestBody(required=true, @OA\JsonContent(ref="#/components/schemas/ClientGroupCreateRequest")),
* @OA\Response(response=200, description="생성 성공",
* @OA\Response(response=200, description="생성 성공 (또는 삭제된 데이터 복원)",
* @OA\JsonContent(allOf={
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
* @OA\Schema(@OA\Property(property="data", ref="#/components/schemas/ClientGroup"))
* })
* ),
* @OA\Response(response=400, description="검증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
* @OA\Response(response=400, description="검증 실패 또는 중복 코드", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
* )
*/
public function store() {}

View File

@@ -26,8 +26,8 @@
* @OA\Schema(
* schema="ApiResponse",
* type="object",
* @OA\Property(property="status", type="string", example="success"),
* @OA\Property(property="message", type="string", example="처리되었습니다."),
* @OA\Property(property="success", type="boolean", example=true),
* @OA\Property(property="message", type="string", example="요청 성공"),
* @OA\Property(property="data", nullable=true)
* )
*
@@ -98,8 +98,13 @@
* type="object",
* description="공통 에러 응답 포맷",
* @OA\Property(property="success", type="boolean", example=false),
* @OA\Property(property="message", type="string", example="에러 메시지"),
* @OA\Property(property="data", type="string", nullable=true, example=null)
* @OA\Property(property="message", type="string", example="요청 실패"),
* @OA\Property(
* property="error",
* type="object",
* @OA\Property(property="code", type="integer", example=400),
* @OA\Property(property="details", nullable=true, example=null)
* )
* )
*/
class CommonComponents {}

View File

@@ -9,32 +9,6 @@
* )
*/
/**
* =========================
* 공통 응답 스키마
* =========================
*/
/**
* @OA\Schema(
* schema="ApiResponse",
* type="object",
* required={"success","message"},
* @OA\Property(property="success", type="boolean", example=true),
* @OA\Property(property="message", type="string", example="부서 생성 성공"),
* @OA\Property(property="data", nullable=true)
* )
*
* @OA\Schema(
* schema="ErrorResponse",
* type="object",
* required={"success","message"},
* @OA\Property(property="success", type="boolean", example=false),
* @OA\Property(property="message", type="string", example="부서 생성 실패"),
* @OA\Property(property="data", type="null", example=null)
* )
*/
/**
* =========================
* Domain 스키마

View File

@@ -9,29 +9,6 @@
* )
*/
/**
* 공통 응답 스키마
*/
/**
* @OA\Schema(
* schema="ApiResponse",
* type="object",
* required={"success","message"},
* @OA\Property(property="success", type="boolean", example=true),
* @OA\Property(property="message", type="string", example="자재 목록 조회 성공"),
* @OA\Property(property="data", nullable=true)
* )
*
* @OA\Schema(
* schema="ErrorResponse",
* type="object",
* required={"success","message"},
* @OA\Property(property="success", type="boolean", example=false),
* @OA\Property(property="message", type="string", example="자재 등록 실패"),
* @OA\Property(property="data", type="null", example=null)
* )
*/
/**
* 자재/요청 스키마
*/

View File

@@ -9,32 +9,6 @@
* )
*/
/**
* =========================
* 공통 응답 스키마
* =========================
*/
/**
* @OA\Schema(
* schema="ApiResponse",
* type="object",
* required={"success","message"},
* @OA\Property(property="success", type="boolean", example=true),
* @OA\Property(property="message", type="string", example="부서 메뉴 권한 매트릭스 조회 성공"),
* @OA\Property(property="data", nullable=true)
* )
*
* @OA\Schema(
* schema="ErrorResponse",
* type="object",
* required={"success","message"},
* @OA\Property(property="success", type="boolean", example=false),
* @OA\Property(property="message", type="string", example="부서 메뉴 권한 매트릭스 조회 실패"),
* @OA\Property(property="data", type="null", example=null)
* )
*/
/**
* =========================
* 메뉴 매트릭스 스키마

View File

@@ -129,10 +129,10 @@ public function me() {}
* response=200,
* description="수정 성공",
* @OA\JsonContent(
* type="object",
* @OA\Property(property="success", type="boolean", example=true),
* @OA\Property(property="message", type="string", example="나의 정보 수정 성공"),
* @OA\Property(property="data", type="string", example="Success")
* allOf={
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
* @OA\Schema(@OA\Property(property="data", ref="#/components/schemas/Member"))
* }
* )
* ),
* @OA\Response(response=400, description="필수 파라미터 누락", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),

View File

@@ -71,4 +71,9 @@
'file_too_large' => 'File size is too large.',
],
// Client group related
'duplicate_code' => 'Duplicate group code.',
'has_clients' => 'Cannot delete the client group because it has associated clients.',
'code_exists_in_deleted' => 'The same code exists in deleted data. Please permanently delete that code first or use a different code.',
];

View File

@@ -70,4 +70,9 @@
// 가격 관리 관련
'price_not_found' => ':item_type ID :item_id 항목의 :date 기준 매출단가를 찾을 수 없습니다.',
// 고객 그룹 관련
'duplicate_code' => '중복된 그룹 코드입니다.',
'has_clients' => '해당 고객 그룹에 속한 고객이 있어 삭제할 수 없습니다.',
'code_exists_in_deleted' => '삭제된 데이터에 동일한 코드가 존재합니다. 먼저 해당 코드를 완전히 삭제하거나 다른 코드를 사용하세요.',
];