From 6df1da9e42b91b0b1d5c0b7c1d7f1a007d175889 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=B3=B4=EA=B3=A4?= Date: Sat, 28 Feb 2026 20:02:33 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20[interview]=20=EC=9D=B8=ED=84=B0?= =?UTF-8?q?=EB=B7=B0=20=EC=8B=9C=EB=82=98=EB=A6=AC=EC=98=A4=20=EA=B3=A0?= =?UTF-8?q?=EB=8F=84=ED=99=94=20=EB=A7=88=EC=9D=B4=EA=B7=B8=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=EC=85=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - interview_projects 테이블 신규 (회사별 프로젝트) - interview_attachments 테이블 신규 (첨부파일 + AI 분석) - interview_knowledge 테이블 신규 (AI 추출 지식) - interview_categories에 project_id, domain 컬럼 추가 - interview_questions에 ai_hint, expected_format, depends_on, domain 추가 - interview_answers에 answer_data, attachments JSON 추가 - interview_sessions에 project_id, session_type, voice_recording_id 추가 --- ...100000_create_interview_projects_table.php | 39 ++++++++++++ ...001_create_interview_attachments_table.php | 38 ++++++++++++ ...00002_create_interview_knowledge_table.php | 46 ++++++++++++++ ...t_domain_to_interview_categories_table.php | 28 +++++++++ ...rview_questions_answers_sessions_table.php | 62 +++++++++++++++++++ 5 files changed, 213 insertions(+) create mode 100644 database/migrations/2026_02_28_100000_create_interview_projects_table.php create mode 100644 database/migrations/2026_02_28_100001_create_interview_attachments_table.php create mode 100644 database/migrations/2026_02_28_100002_create_interview_knowledge_table.php create mode 100644 database/migrations/2026_02_28_100003_add_project_domain_to_interview_categories_table.php create mode 100644 database/migrations/2026_02_28_100004_add_fields_to_interview_questions_answers_sessions_table.php diff --git a/database/migrations/2026_02_28_100000_create_interview_projects_table.php b/database/migrations/2026_02_28_100000_create_interview_projects_table.php new file mode 100644 index 0000000..5e604ea --- /dev/null +++ b/database/migrations/2026_02_28_100000_create_interview_projects_table.php @@ -0,0 +1,39 @@ +id(); + $table->unsignedBigInteger('tenant_id')->comment('테넌트 ID'); + $table->string('company_name', 200)->comment('대상 회사명'); + $table->string('company_type', 100)->nullable()->comment('업종 (방화셔터, 블라인드 등)'); + $table->string('contact_person', 100)->nullable()->comment('담당자'); + $table->string('contact_info', 200)->nullable()->comment('연락처'); + $table->enum('status', ['draft', 'interviewing', 'analyzing', 'code_generated', 'deployed']) + ->default('draft')->comment('상태'); + $table->unsignedBigInteger('target_tenant_id')->nullable()->comment('생성될 테넌트 ID'); + $table->json('product_categories')->nullable()->comment('제품 카테고리 목록'); + $table->text('summary')->nullable()->comment('AI 생성 요약'); + $table->unsignedTinyInteger('progress_percent')->default(0)->comment('전체 진행률'); + $table->unsignedBigInteger('created_by')->nullable()->comment('등록자'); + $table->unsignedBigInteger('updated_by')->nullable()->comment('수정자'); + $table->unsignedBigInteger('deleted_by')->nullable()->comment('삭제자'); + $table->timestamps(); + $table->softDeletes(); + + $table->index('tenant_id', 'idx_interview_projects_tenant'); + $table->index('status', 'idx_interview_projects_status'); + }); + } + + public function down(): void + { + Schema::dropIfExists('interview_projects'); + } +}; diff --git a/database/migrations/2026_02_28_100001_create_interview_attachments_table.php b/database/migrations/2026_02_28_100001_create_interview_attachments_table.php new file mode 100644 index 0000000..0772652 --- /dev/null +++ b/database/migrations/2026_02_28_100001_create_interview_attachments_table.php @@ -0,0 +1,38 @@ +id(); + $table->unsignedBigInteger('tenant_id')->comment('테넌트 ID'); + $table->unsignedBigInteger('interview_project_id')->comment('인터뷰 프로젝트 ID'); + $table->enum('file_type', ['excel_template', 'pdf_quote', 'sample_bom', 'price_list', 'photo', 'voice', 'other']) + ->default('other')->comment('파일 유형'); + $table->string('file_name', 255)->comment('파일명'); + $table->string('file_path', 500)->comment('저장 경로'); + $table->unsignedInteger('file_size')->default(0)->comment('파일 크기 (bytes)'); + $table->string('mime_type', 100)->nullable()->comment('MIME 타입'); + $table->json('ai_analysis')->nullable()->comment('AI 분석 결과'); + $table->enum('ai_analysis_status', ['pending', 'processing', 'completed', 'failed']) + ->default('pending')->comment('AI 분석 상태'); + $table->text('description')->nullable()->comment('설명'); + $table->unsignedBigInteger('created_by')->nullable()->comment('등록자'); + $table->timestamps(); + + $table->index('tenant_id', 'idx_interview_attachments_tenant'); + $table->index('interview_project_id', 'idx_interview_attachments_project'); + $table->index('file_type', 'idx_interview_attachments_type'); + }); + } + + public function down(): void + { + Schema::dropIfExists('interview_attachments'); + } +}; diff --git a/database/migrations/2026_02_28_100002_create_interview_knowledge_table.php b/database/migrations/2026_02_28_100002_create_interview_knowledge_table.php new file mode 100644 index 0000000..47529b7 --- /dev/null +++ b/database/migrations/2026_02_28_100002_create_interview_knowledge_table.php @@ -0,0 +1,46 @@ +id(); + $table->unsignedBigInteger('tenant_id')->comment('테넌트 ID'); + $table->unsignedBigInteger('interview_project_id')->comment('인터뷰 프로젝트 ID'); + $table->enum('domain', [ + 'product_classification', 'bom_structure', 'dimension_formula', + 'component_config', 'pricing_structure', 'quantity_formula', + 'conditional_logic', 'quote_format', + ])->comment('도메인 영역'); + $table->enum('knowledge_type', ['fact', 'rule', 'formula', 'mapping', 'range', 'table']) + ->comment('지식 유형'); + $table->string('title', 300)->comment('제목'); + $table->json('content')->comment('구조화된 지식 데이터'); + $table->enum('source_type', ['interview_answer', 'voice_recording', 'document', 'manual']) + ->comment('출처 유형'); + $table->unsignedBigInteger('source_id')->nullable()->comment('출처 레코드 ID'); + $table->decimal('confidence', 3, 2)->default(0.00)->comment('AI 신뢰도 (0.00~1.00)'); + $table->boolean('is_verified')->default(false)->comment('사용자 검증 여부'); + $table->unsignedBigInteger('verified_by')->nullable()->comment('검증자'); + $table->timestamp('verified_at')->nullable()->comment('검증 일시'); + $table->unsignedBigInteger('created_by')->nullable()->comment('등록자'); + $table->timestamps(); + $table->softDeletes(); + + $table->index('tenant_id', 'idx_interview_knowledge_tenant'); + $table->index('interview_project_id', 'idx_interview_knowledge_project'); + $table->index('domain', 'idx_interview_knowledge_domain'); + $table->index('is_verified', 'idx_interview_knowledge_verified'); + }); + } + + public function down(): void + { + Schema::dropIfExists('interview_knowledge'); + } +}; diff --git a/database/migrations/2026_02_28_100003_add_project_domain_to_interview_categories_table.php b/database/migrations/2026_02_28_100003_add_project_domain_to_interview_categories_table.php new file mode 100644 index 0000000..8ea37ac --- /dev/null +++ b/database/migrations/2026_02_28_100003_add_project_domain_to_interview_categories_table.php @@ -0,0 +1,28 @@ +unsignedBigInteger('interview_project_id')->nullable() + ->after('tenant_id')->comment('프로젝트 연결'); + $table->string('domain', 50)->nullable() + ->after('description')->comment('도메인 영역 태그'); + + $table->index('interview_project_id', 'idx_interview_categories_project'); + }); + } + + public function down(): void + { + Schema::table('interview_categories', function (Blueprint $table) { + $table->dropIndex('idx_interview_categories_project'); + $table->dropColumn(['interview_project_id', 'domain']); + }); + } +}; diff --git a/database/migrations/2026_02_28_100004_add_fields_to_interview_questions_answers_sessions_table.php b/database/migrations/2026_02_28_100004_add_fields_to_interview_questions_answers_sessions_table.php new file mode 100644 index 0000000..e10a663 --- /dev/null +++ b/database/migrations/2026_02_28_100004_add_fields_to_interview_questions_answers_sessions_table.php @@ -0,0 +1,62 @@ +text('ai_hint')->nullable()->after('options')->comment('AI 분석 힌트/가이드'); + $table->string('expected_format', 100)->nullable()->after('ai_hint')->comment('예상 답변 형식 (mm, 원/kg 등)'); + $table->json('depends_on')->nullable()->after('expected_format')->comment('조건부 표시 조건'); + $table->string('domain', 50)->nullable()->after('depends_on')->comment('도메인 영역'); + }); + + // question_type 컬럼 확장 (varchar(20) → varchar(50)) + Schema::table('interview_questions', function (Blueprint $table) { + $table->string('question_type', 50)->default('checkbox') + ->comment('질문유형: checkbox|text|number|select|multi_select|file_upload|formula_input|table_input|bom_tree|price_table|dimension_diagram') + ->change(); + }); + + // interview_answers 확장 + Schema::table('interview_answers', function (Blueprint $table) { + $table->json('answer_data')->nullable()->after('answer_text')->comment('구조화 답변 (테이블, 수식, BOM 등)'); + $table->json('attachments')->nullable()->after('answer_data')->comment('첨부 파일 경로 목록'); + }); + + // interview_sessions 확장 + Schema::table('interview_sessions', function (Blueprint $table) { + $table->unsignedBigInteger('interview_project_id')->nullable() + ->after('tenant_id')->comment('프로젝트 ID'); + $table->string('session_type', 20)->default('checklist') + ->after('status')->comment('세션 유형: checklist|structured|voice|review'); + $table->unsignedBigInteger('voice_recording_id')->nullable() + ->after('session_type')->comment('음성 녹음 ID'); + + $table->index('interview_project_id', 'idx_interview_sessions_project'); + }); + } + + public function down(): void + { + Schema::table('interview_sessions', function (Blueprint $table) { + $table->dropIndex('idx_interview_sessions_project'); + $table->dropColumn(['interview_project_id', 'session_type', 'voice_recording_id']); + }); + + Schema::table('interview_answers', function (Blueprint $table) { + $table->dropColumn(['answer_data', 'attachments']); + }); + + Schema::table('interview_questions', function (Blueprint $table) { + $table->string('question_type', 20)->default('checkbox') + ->comment('질문유형: checkbox/text')->change(); + $table->dropColumn(['ai_hint', 'expected_format', 'depends_on', 'domain']); + }); + } +};