diff --git a/app/Models/Interview/InterviewAnswer.php b/app/Models/Interview/InterviewAnswer.php new file mode 100644 index 0000000..cd48438 --- /dev/null +++ b/app/Models/Interview/InterviewAnswer.php @@ -0,0 +1,40 @@ + 'boolean', + ]; + + public function session() + { + return $this->belongsTo(InterviewSession::class, 'interview_session_id'); + } + + public function question() + { + return $this->belongsTo(InterviewQuestion::class, 'interview_question_id'); + } + + public function template() + { + return $this->belongsTo(InterviewTemplate::class, 'interview_template_id'); + } +} diff --git a/app/Models/Interview/InterviewCategory.php b/app/Models/Interview/InterviewCategory.php new file mode 100644 index 0000000..c647cd0 --- /dev/null +++ b/app/Models/Interview/InterviewCategory.php @@ -0,0 +1,44 @@ + 'boolean', + 'sort_order' => 'integer', + ]; + + protected $hidden = [ + 'deleted_at', + ]; + + public function templates() + { + return $this->hasMany(InterviewTemplate::class, 'interview_category_id'); + } + + public function sessions() + { + return $this->hasMany(InterviewSession::class, 'interview_category_id'); + } +} diff --git a/app/Models/Interview/InterviewQuestion.php b/app/Models/Interview/InterviewQuestion.php new file mode 100644 index 0000000..7ae519e --- /dev/null +++ b/app/Models/Interview/InterviewQuestion.php @@ -0,0 +1,44 @@ + 'array', + 'is_required' => 'boolean', + 'is_active' => 'boolean', + 'sort_order' => 'integer', + ]; + + protected $hidden = [ + 'deleted_at', + ]; + + public function template() + { + return $this->belongsTo(InterviewTemplate::class, 'interview_template_id'); + } +} diff --git a/app/Models/Interview/InterviewSession.php b/app/Models/Interview/InterviewSession.php new file mode 100644 index 0000000..63e94ad --- /dev/null +++ b/app/Models/Interview/InterviewSession.php @@ -0,0 +1,57 @@ + 'date', + 'completed_at' => 'datetime', + 'total_questions' => 'integer', + 'answered_questions' => 'integer', + ]; + + protected $hidden = [ + 'deleted_at', + ]; + + public function category() + { + return $this->belongsTo(InterviewCategory::class, 'interview_category_id'); + } + + public function interviewer() + { + return $this->belongsTo(\App\Models\User::class, 'interviewer_id'); + } + + public function answers() + { + return $this->hasMany(InterviewAnswer::class, 'interview_session_id'); + } +} diff --git a/app/Models/Interview/InterviewTemplate.php b/app/Models/Interview/InterviewTemplate.php new file mode 100644 index 0000000..787baf4 --- /dev/null +++ b/app/Models/Interview/InterviewTemplate.php @@ -0,0 +1,45 @@ + 'boolean', + 'sort_order' => 'integer', + ]; + + protected $hidden = [ + 'deleted_at', + ]; + + public function category() + { + return $this->belongsTo(InterviewCategory::class, 'interview_category_id'); + } + + public function questions() + { + return $this->hasMany(InterviewQuestion::class, 'interview_template_id'); + } +} diff --git a/database/migrations/2026_02_06_100000_create_interview_categories_table.php b/database/migrations/2026_02_06_100000_create_interview_categories_table.php new file mode 100644 index 0000000..43ce572 --- /dev/null +++ b/database/migrations/2026_02_06_100000_create_interview_categories_table.php @@ -0,0 +1,33 @@ +id(); + $table->unsignedBigInteger('tenant_id')->comment('테넌트 ID'); + $table->string('name', 100)->comment('카테고리명 (예: 제조-방화셔터)'); + $table->text('description')->nullable()->comment('설명'); + $table->unsignedInteger('sort_order')->default(0)->comment('정렬순서'); + $table->boolean('is_active')->default(true)->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_categories_tenant'); + $table->index('sort_order', 'idx_interview_categories_sort'); + }); + } + + public function down(): void + { + Schema::dropIfExists('interview_categories'); + } +}; diff --git a/database/migrations/2026_02_06_100001_create_interview_templates_table.php b/database/migrations/2026_02_06_100001_create_interview_templates_table.php new file mode 100644 index 0000000..c1d8e5a --- /dev/null +++ b/database/migrations/2026_02_06_100001_create_interview_templates_table.php @@ -0,0 +1,35 @@ +id(); + $table->unsignedBigInteger('tenant_id')->comment('테넌트 ID'); + $table->unsignedBigInteger('interview_category_id')->comment('카테고리 ID'); + $table->string('name', 200)->comment('항목명 (예: 견적서 제작)'); + $table->text('description')->nullable()->comment('설명'); + $table->unsignedInteger('sort_order')->default(0)->comment('정렬순서'); + $table->boolean('is_active')->default(true)->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_templates_tenant'); + $table->index('interview_category_id', 'idx_interview_templates_category'); + $table->index('sort_order', 'idx_interview_templates_sort'); + }); + } + + public function down(): void + { + Schema::dropIfExists('interview_templates'); + } +}; diff --git a/database/migrations/2026_02_06_100002_create_interview_questions_table.php b/database/migrations/2026_02_06_100002_create_interview_questions_table.php new file mode 100644 index 0000000..9ce06cf --- /dev/null +++ b/database/migrations/2026_02_06_100002_create_interview_questions_table.php @@ -0,0 +1,37 @@ +id(); + $table->unsignedBigInteger('tenant_id')->comment('테넌트 ID'); + $table->unsignedBigInteger('interview_template_id')->comment('템플릿(항목) ID'); + $table->string('question_text', 500)->comment('질문 내용'); + $table->string('question_type', 20)->default('checkbox')->comment('질문유형: checkbox/text'); + $table->json('options')->nullable()->comment('확장용 옵션'); + $table->boolean('is_required')->default(false)->comment('필수여부'); + $table->unsignedInteger('sort_order')->default(0)->comment('정렬순서'); + $table->boolean('is_active')->default(true)->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_questions_tenant'); + $table->index('interview_template_id', 'idx_interview_questions_template'); + $table->index('sort_order', 'idx_interview_questions_sort'); + }); + } + + public function down(): void + { + Schema::dropIfExists('interview_questions'); + } +}; diff --git a/database/migrations/2026_02_06_100003_create_interview_sessions_table.php b/database/migrations/2026_02_06_100003_create_interview_sessions_table.php new file mode 100644 index 0000000..636fc84 --- /dev/null +++ b/database/migrations/2026_02_06_100003_create_interview_sessions_table.php @@ -0,0 +1,41 @@ +id(); + $table->unsignedBigInteger('tenant_id')->comment('테넌트 ID'); + $table->unsignedBigInteger('interview_category_id')->comment('사용한 카테고리 ID'); + $table->unsignedBigInteger('interviewer_id')->comment('면담자(매니저) ID'); + $table->string('interviewee_name', 100)->nullable()->comment('면담 상대 이름'); + $table->string('interviewee_company', 200)->nullable()->comment('면담 상대 회사'); + $table->date('interview_date')->comment('면담 일자'); + $table->string('status', 20)->default('in_progress')->comment('상태: in_progress/completed'); + $table->unsignedInteger('total_questions')->default(0)->comment('총 질문 수'); + $table->unsignedInteger('answered_questions')->default(0)->comment('답변 완료 수'); + $table->text('memo')->nullable()->comment('메모'); + $table->timestamp('completed_at')->nullable()->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', 'interviewer_id'], 'idx_interview_sessions_tenant_interviewer'); + $table->index('interview_category_id', 'idx_interview_sessions_category'); + $table->index('interview_date', 'idx_interview_sessions_date'); + $table->index('status', 'idx_interview_sessions_status'); + }); + } + + public function down(): void + { + Schema::dropIfExists('interview_sessions'); + } +}; diff --git a/database/migrations/2026_02_06_100004_create_interview_answers_table.php b/database/migrations/2026_02_06_100004_create_interview_answers_table.php new file mode 100644 index 0000000..b262ab8 --- /dev/null +++ b/database/migrations/2026_02_06_100004_create_interview_answers_table.php @@ -0,0 +1,32 @@ +id(); + $table->unsignedBigInteger('tenant_id')->comment('테넌트 ID'); + $table->unsignedBigInteger('interview_session_id')->comment('세션 ID'); + $table->unsignedBigInteger('interview_question_id')->comment('질문 ID'); + $table->unsignedBigInteger('interview_template_id')->comment('템플릿 ID (비정규화)'); + $table->boolean('is_checked')->default(false)->comment('체크 여부'); + $table->text('answer_text')->nullable()->comment('텍스트 답변'); + $table->text('memo')->nullable()->comment('개별 메모'); + $table->timestamps(); + + $table->unique(['interview_session_id', 'interview_question_id'], 'uniq_session_question'); + $table->index('tenant_id', 'idx_interview_answers_tenant'); + $table->index('interview_template_id', 'idx_interview_answers_template'); + }); + } + + public function down(): void + { + Schema::dropIfExists('interview_answers'); + } +};