# 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 완성 (오후) ### 주요 작업 - **ClientGroup API 전체 구현**: 고객 그룹 관리를 위한 완전한 REST API 구축 - **Pricing API 전체 구현**: 가격 이력 관리 및 가격 조회 API 구축 - **Swagger 문서 작성**: ClientGroup, Pricing API의 완전한 OpenAPI 3.0 문서 생성 - **l5-swagger 재생성**: 모든 API 문서를 Swagger UI에서 확인 가능하도록 재생성 ### 추가된 파일: - `app/Services/ClientGroupService.php` - 고객 그룹 관리 서비스 (CRUD + toggle) - `app/Http/Controllers/Api/V1/ClientGroupController.php` - 고객 그룹 컨트롤러 - `app/Http/Controllers/Api/V1/PricingController.php` - 가격 이력 컨트롤러 - `app/Swagger/v1/ClientGroupApi.php` - ClientGroup Swagger 문서 - `app/Swagger/v1/PricingApi.php` - Pricing Swagger 문서 ### 수정된 파일: - `routes/api.php` - ClientGroup 및 Pricing 라우트 등록 ### 작업 내용: #### 1. ClientGroupService 구현 **핵심 기능:** - `index()` - 페이지네이션 목록 조회 (검색, 활성 여부 필터링) - `show($id)` - 단건 조회 - `store($params)` - 생성 (group_code 중복 검사) - `update($id, $params)` - 수정 (중복 검사) - `destroy($id)` - Soft Delete - `toggle($id)` - 활성/비활성 상태 토글 **검증 규칙:** ```php - group_code: required|string|max:30 - group_name: required|string|max:100 - price_rate: required|numeric|min:0|max:99.9999 - is_active: nullable|boolean ``` **에러 처리:** - 중복 코드: `__('error.duplicate_code')` - 데이터 없음: `NotFoundHttpException` - 검증 실패: `BadRequestHttpException` #### 2. ClientGroupController 구현 **표준 RESTful 패턴:** ```php - GET /api/v1/client-groups → index() - POST /api/v1/client-groups → store() - GET /api/v1/client-groups/{id} → show() - PUT /api/v1/client-groups/{id} → update() - DELETE /api/v1/client-groups/{id} → destroy() - PATCH /api/v1/client-groups/{id}/toggle → toggle() ``` **공통 특징:** - ApiResponse::handle() 래퍼 사용 - i18n 메시지 키 사용 (__('message.xxx')) - Service DI를 통한 비즈니스 로직 분리 #### 3. PricingController 구현 **특화된 엔드포인트:** ```php - GET /api/v1/pricing → index() (가격 이력 목록) - GET /api/v1/pricing/show → show() (단일 항목 가격 조회) - POST /api/v1/pricing/bulk → bulk() (여러 항목 일괄 조회) - POST /api/v1/pricing/upsert → upsert() (등록/수정) - DELETE /api/v1/pricing/{id} → destroy() (삭제) ``` **가격 조회 파라미터:** - `item_type`: PRODUCT | MATERIAL (필수) - `item_id`: 항목 ID (필수) - `client_id`: 고객 ID (선택, 그룹별 가격 조회) - `date`: 기준일 (선택, 미지정 시 오늘) **일괄 조회 요청:** ```json { "items": [ {"item_type": "PRODUCT", "item_id": 1}, {"item_type": "MATERIAL", "item_id": 5} ], "client_id": 10, "date": "2025-10-13" } ``` #### 4. ClientGroupApi.php Swagger 문서 **스키마 구성:** - `ClientGroup` - 모델 스키마 (전체 필드) - `ClientGroupPagination` - 페이지네이션 응답 - `ClientGroupCreateRequest` - 생성 요청 (required 필드) - `ClientGroupUpdateRequest` - 수정 요청 (optional 필드) **엔드포인트 문서:** - 각 엔드포인트별 파라미터, 요청/응답 예시 - 에러 응답 (401, 404, 400) 정의 - Security: ApiKeyAuth + BearerAuth #### 5. PricingApi.php Swagger 문서 **스키마 구성:** - `PriceHistory` - 가격 이력 모델 - `PriceHistoryPagination` - 목록 응답 - `PriceUpsertRequest` - 등록/수정 요청 - `PriceQueryResult` - 단일 조회 결과 (price, price_history_id, client_group_id, warning) - `BulkPriceQueryRequest` - 일괄 조회 요청 - `BulkPriceQueryResult` - 일괄 조회 결과 (prices[], warnings[]) **특수 스키마:** ```php // 단일 항목 가격 조회 결과 PriceQueryResult { price: 50000.00, price_history_id: 1, client_group_id: 1, warning: "가격을 찾을 수 없습니다" // nullable } // 일괄 조회 결과 BulkPriceQueryResult { prices: [ {item_type: "PRODUCT", item_id: 1, price: 50000, ...}, {item_type: "MATERIAL", item_id: 5, price: null, ...} ], warnings: ["MATERIAL(5): 가격을 찾을 수 없습니다"] } ``` #### 6. Routes 등록 **ClientGroups 라우트:** ```php Route::prefix('client-groups')->group(function () { Route::get ('', [ClientGroupController::class, 'index']); Route::post ('', [ClientGroupController::class, 'store']); Route::get ('/{id}', [ClientGroupController::class, 'show'])->whereNumber('id'); Route::put ('/{id}', [ClientGroupController::class, 'update'])->whereNumber('id'); Route::delete('/{id}', [ClientGroupController::class, 'destroy'])->whereNumber('id'); Route::patch ('/{id}/toggle', [ClientGroupController::class, 'toggle'])->whereNumber('id'); }); ``` **Pricing 라우트:** ```php Route::prefix('pricing')->group(function () { Route::get ('', [PricingController::class, 'index']); Route::get ('/show', [PricingController::class, 'show']); Route::post ('/bulk', [PricingController::class, 'bulk']); Route::post ('/upsert', [PricingController::class, 'upsert']); Route::delete('/{id}', [PricingController::class, 'destroy'])->whereNumber('id'); }); ``` #### 7. Swagger 재생성 및 검증 ```bash # Swagger JSON 재생성 php artisan l5-swagger:generate # 검증 결과 ✅ ClientGroup 태그 확인됨 ✅ Pricing 태그 확인됨 ✅ 11개 엔드포인트 모두 포함: - /api/v1/client-groups (6개) - /api/v1/pricing (5개) ``` ### 사용한 도구: - **기본 Claude 도구**: Read, Write, Edit, Bash, TodoWrite - **MCP 서버**: 사용하지 않음 (표준 CRUD 구현) - **SuperClaude 페르소나**: 사용하지 않음 (기존 패턴 따름) ### 아키텍처 준수 사항: ✅ **SAM API Development Rules 준수:** - Service-First 아키텍처 (비즈니스 로직은 Service에) - Controller는 DI + ApiResponse::handle()만 사용 - i18n 메시지 키 사용 (__('message.xxx')) - Validator 사용 (Service 내에서) - BelongsToTenant 멀티테넌트 스코프 - SoftDeletes 적용 - 감사 컬럼 (created_by, updated_by) 포함 ✅ **Swagger 문서 표준:** - Controller와 분리된 별도 파일 - 파일 위치: `app/Swagger/v1/` - 파일명: `{Resource}Api.php` - 빈 메서드에 @OA 어노테이션 - ApiResponse, ErrorResponse 재사용 ### API 엔드포인트 요약: #### ClientGroup API (6개) | 메서드 | 경로 | 설명 | |--------|------|------| | GET | /api/v1/client-groups | 목록 조회 (페이지네이션, 검색) | | POST | /api/v1/client-groups | 고객 그룹 생성 | | GET | /api/v1/client-groups/{id} | 단건 조회 | | PUT | /api/v1/client-groups/{id} | 수정 | | DELETE | /api/v1/client-groups/{id} | 삭제 (soft) | | PATCH | /api/v1/client-groups/{id}/toggle | 활성/비활성 토글 | #### Pricing API (5개) | 메서드 | 경로 | 설명 | |--------|------|------| | GET | /api/v1/pricing | 가격 이력 목록 (필터링) | | GET | /api/v1/pricing/show | 단일 항목 가격 조회 | | POST | /api/v1/pricing/bulk | 여러 항목 일괄 조회 | | POST | /api/v1/pricing/upsert | 가격 등록/수정 | | DELETE | /api/v1/pricing/{id} | 가격 이력 삭제 | ### Swagger UI 접근: - URL: `http://localhost:8000/api-docs/index.html` - ClientGroup 섹션: 6개 엔드포인트 - Pricing 섹션: 5개 엔드포인트 - Try it out 기능으로 즉시 테스트 가능 ### 완료된 향후 작업 (오전 작업 기준): - [x] 가격 관리 API 엔드포인트 추가 (CRUD) ✅ - [x] Swagger 문서 작성 (가격 관련 API) ✅ - [x] 고객 그룹 관리 API 엔드포인트 추가 ✅ ### 신규 향후 작업: - [ ] API 엔드포인트 실제 테스트 (Postman/Swagger UI) - [ ] Frontend 가격 관리 화면 구현 - [ ] Frontend 고객 그룹 관리 화면 구현 - [ ] 가격 일괄 업로드 기능 추가 - [ ] 단위 테스트 작성 ### Git 커밋 준비: - 다음 커밋 예정: `feat: ClientGroup 및 Pricing API 완성 및 Swagger 문서 작성` --- ## 2025-10-13 (일) - 고객그룹별 차등 가격 시스템 구축 ### 주요 작업 - **고객 그룹 관리 시스템 구축**: 고객별 차등 가격 관리를 위한 client_groups 테이블 및 모델 구현 - **가격 이력 시스템 확장**: price_histories 테이블에 고객그룹별 가격 지원 추가 - **PricingService 신규 구축**: 우선순위 기반 가격 조회 로직 구현 - **EstimateService 통합**: 견적 생성 시 자동 가격 계산 기능 추가 ### 추가된 파일: - `database/migrations/2025_10_13_213549_create_client_groups_table.php` - 고객 그룹 테이블 생성 - `database/migrations/2025_10_13_213556_add_client_group_id_to_clients_table.php` - clients 테이블에 그룹 ID 추가 - `database/migrations/2025_10_13_213602_add_client_group_id_to_price_histories_table.php` - price_histories 테이블에 그룹 ID 추가 - `app/Models/Orders/ClientGroup.php` - 고객 그룹 모델 - `app/Services/Pricing/PricingService.php` - 가격 조회/관리 서비스 ### 수정된 파일: - `app/Models/Orders/Client.php` - ClientGroup 관계 추가 - `app/Models/Products/PriceHistory.php` - ClientGroup 관계 추가, 다양한 스코프 메서드 추가 - `app/Services/Estimate/EstimateService.php` - PricingService 의존성 주입 및 가격 계산 로직 통합 - `lang/ko/error.php` - price_not_found 에러 메시지 추가 ### 작업 내용: #### 1. 데이터베이스 스키마 설계 **client_groups 테이블:** ```sql - id, tenant_id - group_code (그룹 코드) - group_name (그룹명) - price_rate (가격 배율: 1.0 = 기준가, 0.9 = 90%, 1.1 = 110%) - is_active (활성 여부) - created_by, updated_by, deleted_by (감사 컬럼) - created_at, updated_at, deleted_at - UNIQUE(tenant_id, group_code) ``` **clients 테이블 확장:** - `client_group_id` 컬럼 추가 (NULL 허용 = 기본 그룹) **price_histories 테이블 확장:** - `client_group_id` 컬럼 추가 (NULL = 기본 가격, 값 있으면 그룹별 차등 가격) - 인덱스 재구성: (tenant_id, item_type_code, item_id, client_group_id, started_at) #### 2. 모델 관계 설정 **ClientGroup 모델:** - `clients()` → hasMany 관계 - `scopeActive()` → 활성 그룹만 조회 - `scopeCode()` → 코드로 검색 **Client 모델:** - `clientGroup()` → belongsTo 관계 **PriceHistory 모델:** - `clientGroup()` → belongsTo 관계 - `item()` → Polymorphic 관계 (Product/Material) - 다양한 스코프 메서드: - `scopeForItem()` → 특정 항목 필터링 - `scopeForClientGroup()` → 고객 그룹 필터링 - `scopeValidAt()` → 기준일 기준 유효한 가격 - `scopeSalePrice()` → 매출단가만 - `scopePurchasePrice()` → 매입단가만 #### 3. PricingService 핵심 로직 **가격 조회 우선순위:** ```php 1순위: 고객 그룹별 매출단가 (client_group_id 있음) 2순위: 기본 매출단가 (client_group_id = NULL) 3순위: NULL (경고 발생) // ❌ 제거: 매입단가는 견적에서 사용하지 않음 (순수 참고용) ``` **주요 메서드:** - `getItemPrice()` → 단일 항목 가격 조회 - `getBulkItemPrices()` → 여러 항목 일괄 조회 - `upsertPrice()` → 가격 등록/수정 - `listPrices()` → 가격 이력 조회 (페이지네이션) - `deletePrice()` → 가격 삭제 (Soft Delete) #### 4. EstimateService 통합 **견적 생성 프로세스:** ```php 1. BOM 계산 (수량만) 2. 각 BOM 항목의 가격 조회 (PricingService) 3. unit_price × quantity = total_price 계산 4. 전체 항목의 total_price 합산 = total_amount 5. 가격 없는 항목 경고 로그 기록 ``` **수정된 메서드:** - `createEstimate()` → client_id 전달, total_amount 재계산 - `updateEstimate()` → 파라미터 변경 시 가격 재계산 - `createEstimateItems()` → 가격 조회 로직 추가, float 반환 #### 5. 에러 처리 및 로깅 **가격 없는 항목 처리:** - 경고 메시지 반환: `__('error.price_not_found', [...])` - Laravel Log에 경고 기록 - 견적 생성은 계속 진행 (unit_price = 0) - 프론트엔드에서 경고 표시 가능 ### 비즈니스 규칙 정리: #### 매입단가 vs 매출단가 - **매입단가 (PURCHASE)**: 순수 참고용, 견적 계산에 미사용 - **매출단가 (SALE)**: 실제 견적 계산에 사용 - **STANDARD 가격**: 경동 비즈니스에서는 불필요 (사용하지 않음) #### 고객 그룹별 차등 가격 ``` 예시 데이터: - 기본 가격: 100,000원 (client_group_id = NULL) - A그룹 가격: 90,000원 (client_group_id = 1, price_rate = 0.9) - B그룹 가격: 110,000원 (client_group_id = 2, price_rate = 1.1) 조회 로직: - A그룹 고객 → 90,000원 (1순위) - B그룹 고객 → 110,000원 (1순위) - 일반 고객 → 100,000원 (2순위) - 가격 없음 → NULL + 경고 (3순위) ``` ### 마이그레이션 실행 결과: ```bash ✅ 2025_10_13_213549_create_client_groups_table (46.85ms) ✅ 2025_10_13_213556_add_client_group_id_to_clients_table (38.75ms) ✅ 2025_10_13_213602_add_client_group_id_to_price_histories_table (38.46ms) ``` ### 코드 품질: - Laravel Pint 포맷팅 완료 (5 files, 4 style issues fixed) - SAM API Development Rules 준수 - Service-First 아키텍처 유지 - BelongsToTenant 멀티테넌트 스코프 적용 - SoftDeletes 적용 - 감사 컬럼 (created_by, updated_by, deleted_by) 포함 ### 예상 효과: 1. **유연한 가격 관리**: 고객 그룹별 차등 가격 설정 가능 2. **자동 가격 계산**: 견적 생성 시 수동 입력 불필요 3. **이력 관리**: 기간별 가격 변동 이력 추적 4. **확장성**: 향후 복잡한 가격 정책 적용 가능 5. **투명성**: 가격 출처 추적 가능 (price_history_id, client_group_id) ### 향후 작업: - [x] PricingService 구현 - [x] EstimateService 통합 - [ ] 가격 관리 API 엔드포인트 추가 (CRUD) - [ ] Swagger 문서 작성 (가격 관련 API) - [ ] 고객 그룹 관리 API 엔드포인트 추가 - [ ] Frontend 가격 관리 화면 구현 - [ ] 가격 일괄 업로드 기능 추가 ### Git 커밋 준비: - 다음 커밋 예정: `feat: 고객그룹별 차등 가격 시스템 구축` ---