diff --git a/CURRENT_WORKS.md b/CURRENT_WORKS.md index e44e78c..f5c41dc 100644 --- a/CURRENT_WORKS.md +++ b/CURRENT_WORKS.md @@ -1,175 +1,93 @@ -# SAM 프로젝트 작업 현황 +# SAM API 저장소 작업 현황 -## 2025-09-19 (목요일) - 프로젝트 리셋 및 환경 정리 +## 2025-09-24 (화) - FK 제약조건 최적화 및 데이터베이스 성능 개선 ### 주요 작업 -- **프로젝트 리셋**: `785e367` 커밋으로 전체 프로젝트 롤백 -- **환경 정리**: 불필요한 시스템 파일 및 로그 파일 삭제 (52MB 절약) -- **프로젝트 구조 개선**: CLAUDE.md 파일 관리 체계 구축 +- 데이터베이스 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` - 저장소별 작업 현황 추적 -#### API 저장소 (`/api`) -**수정된 파일:** -- `CLAUDE.md` - 프로젝트 가이드 파일 추가 (최상단에서 복사) +### 수정된 파일: +- `CLAUDE.md` - CURRENT_WORKS.md 파일 위치 규칙 명확화 +- `database/migrations/2025_09_24_000002_create_dynamic_estimate_fields.php` - level 컬럼 제거로 마이그레이션 오류 해결 -**작업 내용:** -- Git 리셋: `785e367` (감사 로그 도입) 커밋으로 hard reset -- 데이터베이스 마이그레이션 롤백 후 재실행 (batch 10 → 감사 로그까지) -- 불필요한 Estimate 관련 임시 파일들 삭제 -- 시스템 파일 정리: .DS_Store, PHPUnit 캐시, Laravel 로그 (52MB) 삭제 +### 작업 내용: -**마이그레이션 상태:** -- 최종 실행된 마이그레이션: `2025_09_11_000100_create_audit_logs_table` -- 현재 batch: 14 +#### 1. FK 제약조건 현황 분석 +- 현재 8개 마이그레이션에서 FK 제약조건 사용 확인 +- 권한 관리, 제품/자재 관리, 견적 시스템, 기타 시스템별 분류 +- 총 15+개의 FK 제약조건 식별 -#### Frontend 저장소 (`/front/www`) -**삭제된 파일:** -- `application/views/kdcom/estimates/` (디렉토리) -- `application/views/kdcom/estimates_list.php` -- `assets/` (디렉토리) +#### 2. 중요도별 테이블 분류 +**🔴 핵심 테이블 (FK 유지 필수):** +- 인증/권한 시스템: users, roles, permissions 관계 +- 제품/BOM 관리 핵심: products.category_id, product_components 내부 관계 +- 멀티테넌트 핵심: 모든 tenant_id 참조 -**작업 내용:** -- 작업 중이던 estimates 관련 뷰 파일들 정리 -- 브랜치 상태: develop (최신 상태 유지) +**🟡 중요 테이블 (FK 선택적 유지):** +- 견적 시스템: estimates, estimate_items 관계 +- 자재 관리: product_components.material_id -#### Admin 저장소 (`/admin`) -**삭제된 파일:** -- `config/filament.php` +**🟢 일반 테이블 (FK 제거 권장):** +- 분류/코드 관리: classifications.tenant_id +- 부서 관리: departments.parent_id (자기참조) +- 감사 로그: 모든 audit 관련 FK -**작업 내용:** -- 추가된 Filament 설정 파일 정리 -- 브랜치 상태: develop (최신 상태 유지) +#### 3. 코드 영향도 분석 결과 +**✅ 중요 결론: 모델/컨트롤러/서비스 코드 수정 불필요!** +- Laravel Eloquent 관계가 FK 제약조건과 독립적으로 작동 +- 현재 코드가 CASCADE 동작에 의존하지 않음 +- BelongsToTenant 트레잇과 소프트 딜리트로 무결성 관리 +- 비즈니스 로직이 애플리케이션 레벨에서 처리됨 -#### Shared 저장소 (`/shared`) -**작업 내용:** -- 변경사항 없음 (깨끗한 상태 유지) -- 브랜치 상태: develop (최신 상태) +#### 4. 3단계 점진적 FK 제거 전략 -### 환경 개선 사항 -1. **파일 정리**: 총 52MB의 불필요한 파일 삭제 - - .DS_Store 파일들 (macOS 시스템 파일) - - PHPUnit 캐시 파일 (3KB) - - Laravel 로그 파일 (52MB) +**Phase 1 (즉시 적용 가능):** +- `classifications.tenant_id` → `tenants` +- `departments.parent_id` → `departments` (자기참조) +- 영향도: 낮음, 관리 편의성 증가 -2. **프로젝트 관리 체계 구축**: - - CLAUDE.md 파일을 API 서버에도 복사 - - CURRENT_WORKS.md 파일 생성으로 작업 추적 체계 마련 +**Phase 2 (견적 시스템):** +- `estimates.model_set_id` → `categories` +- `estimate_items.estimate_id` → `estimates` +- 영향도: 중간, 성능 향상 효과 +- 멀티테넌트 보안 FK는 유지 -### 추가 완료된 작업 -3. **프로젝트 관리 체계 구축**: - - CLAUDE.md 워크플로우 가이드 완성 - - Git 커밋 컨벤션 및 관리 체계 정립 - - 체크포인트 시스템 구축 +**Phase 3 (신중한 검토 필요):** +- `product_components.material_id` → `materials` +- 영향도: 중간, 자재 관리 유연성 증가 +- 핵심 제품 관계 FK는 유지 -4. **데이터베이스 스키마 점검**: - - samdb 전체 테이블 구조 분석 (70개 테이블, 3.42MB) - - 핵심 테이블 스키마 상세 문서화 - - Multi-tenant 아키텍처 및 감사 시스템 확인 +#### 5. 마이그레이션 특징 +- 동적 FK 이름 탐지로 안전한 제거 +- 성능을 위한 인덱스 유지/추가 +- 상세한 진행 상황 로깅 +- 완전한 롤백 기능 +- 각 단계별 영향도와 주의사항 문서화 -5. **문서화 시스템**: - - `CHECKPOINT_2025-09-19.md`: 완전한 복원 지점 생성 - - `DATABASE_SCHEMA_2025-09-19.md`: DB 스키마 상세 보고서 - - `CLAUDE.md`: 프로젝트 워크플로우 가이드 포함 +### 데이터베이스 마이그레이션 상태: +- 기존 마이그레이션 오류 해결 완료 (level 컬럼 이슈) +- 새로운 FK 제거 마이그레이션 3개 생성 +- 롤백 가능한 안전한 구조로 설계 -### 최종 커밋 상태 -- **API 저장소**: `08f775e` - 프로젝트 워크플로우 가이드 포함 -- **Frontend 저장소**: `ec18d70` - 수주관리 화면 개발 완료 -- **Admin/Shared**: 깨끗한 상태 유지 +### 예상 효과: +1. **성능 향상**: 견적 시스템과 분류 관리에서 FK 검증 오버헤드 제거 +2. **관리 편의성**: 부서 구조 변경, 자재 관리 시 유연성 증가 +3. **개발 생산성**: 데이터 변경 시 FK 제약 에러 감소 +4. **확장성**: 향후 시스템 확장 시 유연한 스키마 변경 가능 -### 최종 상태 -- **모든 저장소**: 깨끗한 상태로 정리 완료 -- **문서화**: 완전한 프로젝트 가이드 및 체크포인트 생성 -- **환경**: 다른 장소에서 동일한 작업 환경 재현 가능 -- **백업**: 안전한 복원 지점 확보 +### 향후 작업: +1. Phase 1 마이그레이션 개발 서버 테스트 +2. 각 단계별 성능 영향 모니터링 +3. Service 레벨에서 데이터 무결성 검증 로직 보강 검토 +4. 프로덕션 적용 전 백업 및 롤백 계획 수립 -### 다른 장소에서 작업 시작 방법 -1. `git pull origin develop` (모든 저장소) -2. Docker 서비스 실행 확인 -3. CLAUDE.md 파일 동기화 확인 -4. `CHECKPOINT_2025-09-19.md` 참조하여 환경 검증 -5. `php artisan migrate:status`로 DB 상태 확인 - ---- - -## 2025-09-22 (일요일) - 업체별 동적 BOM 계산 시스템 구현 - -### 주요 작업 -- **BOM 계산 시스템 구현**: 경동기업 하드코딩 산출식을 업체별 동적 시스템으로 전환 -- **1~3단계 완료**: 데이터베이스 스키마 확장 → 계산 엔진 개발 → API 구현 -- **견적 시스템 개선**: BOM에서 필요 조건 추출하여 동적 견적 화면 구현 기반 마련 - -### 추가된 파일 -- `database/migrations/2025_09_22_215127_add_calculation_fields_to_bom_tables.php` - BOM 테이블 계산 필드 확장 -- `database/migrations/2025_09_22_215217_create_calculation_configs_table.php` - 업체별 산출식 설정 테이블 -- `app/Services/Calculation/CalculationEngine.php` - 계산 프로세스 오케스트레이션 -- `app/Services/Calculation/FormulaParser.php` - 업체별 산출식 파싱/실행 -- `app/Services/Calculation/ParameterValidator.php` - 파라미터 검증 엔진 -- `app/Models/Calculation/CalculationConfig.php` - 업체별 산출식 설정 모델 -- `app/Services/Design/BomCalculationService.php` - BOM 계산 비즈니스 로직 -- `app/Http/Controllers/Api/V1/Design/BomCalculationController.php` - BOM 계산 API 컨트롤러 -- `app/Http/Requests/Design/GetEstimateParametersRequest.php` - 견적 파라미터 조회 검증 -- `app/Http/Requests/Design/CalculateBomRequest.php` - BOM 계산 요청 검증 -- `app/Http/Requests/Design/SaveCompanyFormulaRequest.php` - 업체 산출식 저장 검증 - -### 수정된 파일 -- `routes/api.php` - BOM 계산 API 라우트 5개 추가 - -### 작업 내용 - -#### 1단계: 데이터베이스 스키마 확장 -- **bom_templates 테이블 확장**: - - `calculation_schema` (JSON) - 견적 파라미터 스키마 - - `company_type` (VARCHAR 50) - 업체 타입 (경동기업, 삼성물산 등) - - `formula_version` (VARCHAR 20) - 산출식 버전 - -- **bom_template_items 테이블 확장**: - - `is_calculated` (BOOLEAN) - 계산식 적용 여부 - - `calculation_formula` (TEXT) - 계산식 표현식 - - `depends_on` (JSON) - 의존성 파라미터 - - `calculation_config` (JSON) - 계산 설정 - -- **calculation_configs 테이블 신규**: - - 업체별 산출식 버전 관리, 멀티테넌트 지원, JSON 기반 파라미터 정의 - -#### 2단계: 계산 엔진 개발 -- **CalculationEngine**: BOM 파라미터 추출 → 중간값 계산 → 아이템별 수량 산출 -- **FormulaParser**: 경동기업/삼성물산 산출식 지원, 안전한 수식 파싱 -- **ParameterValidator**: JSON 스키마 기반 검증, 한국어 에러 메시지 - -#### 3단계: API 구현 -- **API 엔드포인트 5개**: - - `GET /design/models/{id}/estimate-parameters` - BOM별 필요 파라미터 동적 추출 - - `POST /design/bom-templates/{id}/calculate-bom` - 실시간 BOM 수량 계산 - - `GET /design/companies/{name}/formulas` - 업체별 산출식 목록 조회 - - `POST /design/companies/{name}/formulas/{type}` - 업체별 산출식 등록/수정 - - `POST /design/formulas/test` - 산출식 배포 전 테스트 - -### 검증 완료 사항 -- ✅ **FormulaParser**: 경동기업 스크린 사이즈 계산 정상 작동 -- ✅ **ParameterValidator**: 파라미터 검증 및 한국어 에러 메시지 정상 -- ✅ **CalculationConfig**: 데이터베이스 CRUD 및 버전 관리 정상 -- ✅ **API Routes**: 5개 엔드포인트 정상 등록 및 라우트 캐시 성공 - -### 마이그레이션 상태 변경 -- **이전**: batch 11까지 실행 완료 -- **현재**: batch 12로 마이그레이션 2개 추가 실행 - -### Git 커밋 정보 -- **커밋 해시**: bd678df -- **브랜치**: develop -- **파일 변경**: 15개 파일 (11개 신규 생성, 4개 수정) -- **라인 변경**: +2039 추가, -3 삭제 - -### 시스템 개선 효과 -- **개발 효율성**: 하드코딩 → 설정 기반 동적 계산으로 전환 -- **운영 편의성**: 코드 수정 없이 관리자 화면에서 업체별 산출식 등록 가능 -- **확장 가능성**: 새로운 업체 추가 시 코드 변경 없이 설정만으로 지원 - -### 다음 단계 (4단계) -- front/www 저장소: 견적 화면 BOM별 동적 파라미터 입력 폼 구현 -- admin 저장소: 업체별 산출식 관리 인터페이스 구현 - ---- -**최종 업데이트**: 2025-09-22 23:15 KST -**상태**: BOM 계산 시스템 구현 완료 \ No newline at end of file +### Git 커밋: +- `cfd4c25` - fix: categories 테이블 level 컬럼 제거로 마이그레이션 오류 해결 +- `7dafab3` - docs: CURRENT_WORKS.md 파일 위치 규칙 명확화 \ No newline at end of file diff --git a/database/migrations/2025_09_24_214146_remove_non_critical_foreign_keys_phase1.php b/database/migrations/2025_09_24_214146_remove_non_critical_foreign_keys_phase1.php new file mode 100644 index 0000000..4266127 --- /dev/null +++ b/database/migrations/2025_09_24_214146_remove_non_critical_foreign_keys_phase1.php @@ -0,0 +1,129 @@ +CONSTRAINT_NAME : null; + } + + /** + * FK 제약조건을 안전하게 제거하는 헬퍼 함수 + */ + private function dropForeignKeyIfExists(string $table, string $column): void + { + $fkName = $this->findForeignKeyName($table, $column); + if ($fkName) { + DB::statement("ALTER TABLE `{$table}` DROP FOREIGN KEY `{$fkName}`"); + echo "✅ Dropped FK: {$table}.{$column} ({$fkName})\n"; + } else { + echo "ℹ️ No FK found: {$table}.{$column}\n"; + } + } + + public function up(): void + { + echo "🚀 Phase 1: 중요하지 않은 FK 제약조건 제거 시작\n\n"; + + // 1. classifications.tenant_id → tenants 제거 + echo "1️⃣ Classifications 테이블 FK 제거...\n"; + $this->dropForeignKeyIfExists('classifications', 'tenant_id'); + + // 2. departments.parent_id → departments (자기참조) 제거 + echo "\n2️⃣ Departments 자기참조 FK 제거...\n"; + $this->dropForeignKeyIfExists('departments', 'parent_id'); + + // 3. 인덱스는 유지 (쿼리 성능을 위해) + echo "\n3️⃣ 인덱스 상태 확인 및 유지...\n"; + + // classifications.tenant_id 인덱스 확인 + $classificationIndexExists = DB::selectOne(" + SHOW INDEX FROM classifications WHERE Column_name = 'tenant_id' + "); + if (!$classificationIndexExists) { + DB::statement("CREATE INDEX idx_classifications_tenant_id ON classifications (tenant_id)"); + echo "✅ Added index: classifications.tenant_id\n"; + } else { + echo "ℹ️ Index exists: classifications.tenant_id\n"; + } + + // departments.parent_id 인덱스 확인 + $departmentIndexExists = DB::selectOne(" + SHOW INDEX FROM departments WHERE Column_name = 'parent_id' + "); + if (!$departmentIndexExists) { + DB::statement("CREATE INDEX idx_departments_parent_id ON departments (parent_id)"); + echo "✅ Added index: departments.parent_id\n"; + } else { + echo "ℹ️ Index exists: departments.parent_id\n"; + } + + echo "\n🎉 Phase 1 FK 제거 완료!\n"; + echo "📋 제거된 FK:\n"; + echo " - classifications.tenant_id → tenants\n"; + echo " - departments.parent_id → departments\n"; + echo "📈 예상 효과: 분류 코드 관리 및 부서 구조 변경 시 유연성 증가\n"; + } + + public function down(): void + { + echo "🔄 Phase 1 FK 제거 롤백 시작...\n\n"; + + // 1. departments.parent_id → departments FK 복구 + echo "1️⃣ Departments 자기참조 FK 복구...\n"; + try { + Schema::table('departments', function (Blueprint $table) { + $table->foreign('parent_id') + ->references('id') + ->on('departments') + ->nullOnDelete(); + }); + echo "✅ Restored FK: departments.parent_id → departments\n"; + } catch (\Throwable $e) { + echo "⚠️ Could not restore FK: departments.parent_id - " . $e->getMessage() . "\n"; + } + + // 2. classifications.tenant_id → tenants FK 복구 + echo "\n2️⃣ Classifications 테넌트 FK 복구...\n"; + try { + Schema::table('classifications', function (Blueprint $table) { + $table->foreign('tenant_id') + ->references('id') + ->on('tenants') + ->cascadeOnUpdate() + ->restrictOnDelete(); + }); + echo "✅ Restored FK: classifications.tenant_id → tenants\n"; + } catch (\Throwable $e) { + echo "⚠️ Could not restore FK: classifications.tenant_id - " . $e->getMessage() . "\n"; + } + + echo "\n🔄 Phase 1 FK 복구 완료!\n"; + } +}; \ No newline at end of file diff --git a/database/migrations/2025_09_24_214200_remove_estimate_foreign_keys_phase2.php b/database/migrations/2025_09_24_214200_remove_estimate_foreign_keys_phase2.php new file mode 100644 index 0000000..62910c1 --- /dev/null +++ b/database/migrations/2025_09_24_214200_remove_estimate_foreign_keys_phase2.php @@ -0,0 +1,153 @@ +CONSTRAINT_NAME : null; + } + + /** + * FK 제약조건을 안전하게 제거하는 헬퍼 함수 + */ + private function dropForeignKeyIfExists(string $table, string $column): void + { + $fkName = $this->findForeignKeyName($table, $column); + if ($fkName) { + DB::statement("ALTER TABLE `{$table}` DROP FOREIGN KEY `{$fkName}`"); + echo "✅ Dropped FK: {$table}.{$column} ({$fkName})\n"; + } else { + echo "ℹ️ No FK found: {$table}.{$column}\n"; + } + } + + public function up(): void + { + echo "🚀 Phase 2: 견적 시스템 FK 제약조건 제거 시작\n\n"; + + // 견적 시스템 테이블 존재 여부 확인 + $estimatesExists = Schema::hasTable('estimates'); + $estimateItemsExists = Schema::hasTable('estimate_items'); + + if (!$estimatesExists && !$estimateItemsExists) { + echo "ℹ️ 견적 시스템 테이블이 존재하지 않습니다. 건너뜁니다.\n"; + return; + } + + if ($estimatesExists) { + echo "1️⃣ Estimates 테이블 FK 검토...\n"; + + // estimates.model_set_id → categories 제거 (견적 스냅샷 특성상 불필요) + echo " - model_set_id FK 제거 (견적 스냅샷 특성)\n"; + $this->dropForeignKeyIfExists('estimates', 'model_set_id'); + + // estimates.tenant_id → tenants 유지 (멀티테넌트 핵심) + echo " - tenant_id FK 유지 (멀티테넌트 보안)\n"; + + // 성능을 위한 인덱스 확인/추가 + $indexExists = DB::selectOne(" + SHOW INDEX FROM estimates WHERE Key_name = 'idx_estimates_tenant_model_set' + "); + if (!$indexExists) { + DB::statement("CREATE INDEX idx_estimates_tenant_model_set ON estimates (tenant_id, model_set_id)"); + echo "✅ Added performance index: estimates(tenant_id, model_set_id)\n"; + } + } + + if ($estimateItemsExists) { + echo "\n2️⃣ Estimate_items 테이블 FK 검토...\n"; + + // estimate_items.estimate_id → estimates 제거 (성능 우선) + echo " - estimate_id FK 제거 (성능 우선)\n"; + $this->dropForeignKeyIfExists('estimate_items', 'estimate_id'); + + // estimate_items.tenant_id → tenants 유지 (멀티테넌트 핵심) + echo " - tenant_id FK 유지 (멀티테넌트 보안)\n"; + + // 성능을 위한 인덱스 확인/추가 + $indexExists = DB::selectOne(" + SHOW INDEX FROM estimate_items WHERE Key_name = 'idx_estimate_items_tenant_estimate' + "); + if (!$indexExists) { + DB::statement("CREATE INDEX idx_estimate_items_tenant_estimate ON estimate_items (tenant_id, estimate_id)"); + echo "✅ Added performance index: estimate_items(tenant_id, estimate_id)\n"; + } + } + + echo "\n🎉 Phase 2 견적 시스템 FK 제거 완료!\n"; + echo "📋 제거된 FK:\n"; + if ($estimatesExists) { + echo " - estimates.model_set_id → categories\n"; + } + if ($estimateItemsExists) { + echo " - estimate_items.estimate_id → estimates\n"; + } + echo "📈 예상 효과: 견적 생성/수정 성능 향상, 카테고리 변경 시 유연성 증가\n"; + echo "🔒 유지된 FK: 멀티테넌트 보안을 위한 tenant_id 관계\n"; + } + + public function down(): void + { + echo "🔄 Phase 2 견적 시스템 FK 복구 시작...\n\n"; + + if (Schema::hasTable('estimate_items')) { + echo "1️⃣ Estimate_items FK 복구...\n"; + try { + Schema::table('estimate_items', function (Blueprint $table) { + $table->foreign('estimate_id') + ->references('id') + ->on('estimates') + ->cascadeOnDelete(); + }); + echo "✅ Restored FK: estimate_items.estimate_id → estimates\n"; + } catch (\Throwable $e) { + echo "⚠️ Could not restore FK: estimate_items.estimate_id - " . $e->getMessage() . "\n"; + } + } + + if (Schema::hasTable('estimates')) { + echo "\n2️⃣ Estimates FK 복구...\n"; + try { + Schema::table('estimates', function (Blueprint $table) { + $table->foreign('model_set_id') + ->references('id') + ->on('categories') + ->restrictOnDelete(); + }); + echo "✅ Restored FK: estimates.model_set_id → categories\n"; + } catch (\Throwable $e) { + echo "⚠️ Could not restore FK: estimates.model_set_id - " . $e->getMessage() . "\n"; + } + } + + echo "\n🔄 Phase 2 FK 복구 완료!\n"; + } +}; \ No newline at end of file diff --git a/database/migrations/2025_09_24_214300_remove_material_foreign_key_phase3.php b/database/migrations/2025_09_24_214300_remove_material_foreign_key_phase3.php new file mode 100644 index 0000000..5ee5bae --- /dev/null +++ b/database/migrations/2025_09_24_214300_remove_material_foreign_key_phase3.php @@ -0,0 +1,138 @@ +CONSTRAINT_NAME : null; + } + + /** + * FK 제약조건을 안전하게 제거하는 헬퍼 함수 + */ + private function dropForeignKeyIfExists(string $table, string $column): void + { + $fkName = $this->findForeignKeyName($table, $column); + if ($fkName) { + DB::statement("ALTER TABLE `{$table}` DROP FOREIGN KEY `{$fkName}`"); + echo "✅ Dropped FK: {$table}.{$column} ({$fkName})\n"; + } else { + echo "ℹ️ No FK found: {$table}.{$column}\n"; + } + } + + public function up(): void + { + echo "🚀 Phase 3: 제품-자재 관계 FK 제약조건 제거 시작\n\n"; + echo "⚠️ 주의: 이 작업은 신중한 검토가 필요합니다!\n"; + echo "📋 영향 범위: BOM 시스템의 자재 참조 관계\n\n"; + + // product_components 테이블 존재 여부 확인 + if (!Schema::hasTable('product_components')) { + echo "ℹ️ product_components 테이블이 존재하지 않습니다. 건너뜁니다.\n"; + return; + } + + echo "1️⃣ Product_components 테이블 FK 분석...\n"; + + // 현재 FK 상태 확인 + $materialFk = $this->findForeignKeyName('product_components', 'material_id'); + $parentProductFk = $this->findForeignKeyName('product_components', 'parent_product_id'); + $childProductFk = $this->findForeignKeyName('product_components', 'child_product_id'); + + echo " 현재 FK 상태:\n"; + echo " - material_id FK: " . ($materialFk ? "존재 ({$materialFk})" : "없음") . "\n"; + echo " - parent_product_id FK: " . ($parentProductFk ? "존재 ({$parentProductFk})" : "없음") . "\n"; + echo " - child_product_id FK: " . ($childProductFk ? "존재 ({$childProductFk})" : "없음") . "\n\n"; + + // material_id FK만 제거 (핵심 제품 관계는 유지) + echo "2️⃣ Material_id FK 제거 (자재 관리 유연성)...\n"; + $this->dropForeignKeyIfExists('product_components', 'material_id'); + + echo "3️⃣ 핵심 제품 관계 FK 유지 확인...\n"; + if ($parentProductFk) { + echo "✅ 유지: parent_product_id → products (BOM 구조 핵심)\n"; + } + if ($childProductFk) { + echo "✅ 유지: child_product_id → products (BOM 구조 핵심)\n"; + } + + // 성능을 위한 인덱스 확인/추가 + echo "\n4️⃣ 성능 인덱스 확인...\n"; + $materialIndexExists = DB::selectOne(" + SHOW INDEX FROM product_components WHERE Key_name = 'idx_components_material_id' + "); + if (!$materialIndexExists) { + DB::statement("CREATE INDEX idx_components_material_id ON product_components (material_id)"); + echo "✅ Added performance index: product_components.material_id\n"; + } else { + echo "ℹ️ Index exists: product_components.material_id\n"; + } + + echo "\n🎉 Phase 3 제품-자재 FK 제거 완료!\n"; + echo "📋 제거된 FK:\n"; + echo " - product_components.material_id → materials\n"; + echo "🔒 유지된 핵심 FK:\n"; + echo " - product_components.parent_product_id → products\n"; + echo " - product_components.child_product_id → products\n"; + echo "📈 예상 효과: 자재 변경/삭제 시 BOM 유연성 증가\n"; + echo "⚠️ 주의사항: Service 레이어에서 자재 무결성 검증 필요\n"; + } + + public function down(): void + { + echo "🔄 Phase 3 제품-자재 FK 복구 시작...\n\n"; + + if (Schema::hasTable('product_components')) { + echo "1️⃣ Material_id FK 복구...\n"; + try { + Schema::table('product_components', function (Blueprint $table) { + $table->foreign('material_id') + ->references('id') + ->on('materials') + ->nullOnDelete(); + }); + echo "✅ Restored FK: product_components.material_id → materials\n"; + } catch (\Throwable $e) { + echo "⚠️ Could not restore FK: product_components.material_id\n"; + echo " Error: " . $e->getMessage() . "\n"; + echo " 이유: 데이터 무결성 위반이나 참조되지 않는 material_id가 있을 수 있습니다.\n"; + } + } + + echo "\n🔄 Phase 3 FK 복구 완료!\n"; + echo "📝 참고: FK 복구 실패 시 데이터 정합성을 먼저 확인하세요.\n"; + } +}; \ No newline at end of file