# 인터뷰 시나리오 ## 개요 인터뷰 시나리오는 영업 상담 시 사용할 질문 템플릿을 관리하고, 인터뷰 세션을 진행/기록하는 기능입니다. 카테고리별 질문 구조, 체크리스트/텍스트 답변, MD 일괄 가져오기를 지원합니다. - **라우트**: `GET /sales/interviews` - **미들웨어**: `auth`, `hq.member` - **UI 기술**: Blade + React/JavaScript (API 기반, 1,076줄) ## 파일 구조 ``` mng/ ├── app/Http/Controllers/Sales/ │ └── InterviewScenarioController.php # 메인 컨트롤러 (16개 메서드) ├── app/Services/Sales/ │ └── InterviewScenarioService.php # 비즈니스 로직 서비스 ├── app/Models/Interview/ │ ├── InterviewCategory.php # 카테고리 │ ├── InterviewTemplate.php # 템플릿 (항목) │ ├── InterviewQuestion.php # 질문 │ ├── InterviewSession.php # 인터뷰 세션 │ └── InterviewAnswer.php # 답변 └── resources/views/sales/interviews/ └── index.blade.php # 메인 페이지 (1,076줄) api/ └── database/migrations/ ├── 2026_02_06_100000_create_interview_categories_table.php ├── 2026_02_06_100001_create_interview_templates_table.php ├── 2026_02_06_100002_create_interview_questions_table.php ├── 2026_02_06_100003_create_interview_sessions_table.php └── 2026_02_06_100004_create_interview_answers_table.php ``` ## 라우트 ```php // routes/web.php (sales/interviews prefix 그룹 내) // 페이지 GET /interviews → index() 메인 페이지 // 카테고리 API GET /interviews/api/categories → categories() 카테고리 목록 POST /interviews/api/categories → storeCategory() 카테고리 생성 PUT /interviews/api/categories/{id} → updateCategory() 카테고리 수정 DELETE /interviews/api/categories/{id} → destroyCategory() 카테고리 삭제 // 트리 API GET /interviews/api/tree → tree() 전체 계층 구조 // 템플릿(항목) API POST /interviews/api/templates → storeTemplate() 템플릿 생성 PUT /interviews/api/templates/{id} → updateTemplate() 템플릿 수정 DELETE /interviews/api/templates/{id} → destroyTemplate() 템플릿 삭제 // 질문 API POST /interviews/api/questions → storeQuestion() 질문 생성 PUT /interviews/api/questions/{id} → updateQuestion() 질문 수정 DELETE /interviews/api/questions/{id} → destroyQuestion() 질문 삭제 // 일괄 가져오기 POST /interviews/api/bulk-import → bulkImport() MD 파일에서 가져오기 // 세션(인터뷰) API GET /interviews/api/sessions → sessions() 세션 목록 (필터) POST /interviews/api/sessions → storeSession() 인터뷰 시작 GET /interviews/api/sessions/{id} → showSession() 세션 상세 POST /interviews/api/sessions/toggle-answer → toggleAnswer() 답변 토글 POST /interviews/api/sessions/{id}/complete → completeSession() 인터뷰 완료 ``` ## 컨트롤러 ### InterviewScenarioController | 메서드 | HTTP | 설명 | |--------|------|------| | `index()` | GET | 메인 페이지 | | `categories()` | GET | 카테고리 목록 | | `storeCategory()` | POST | 카테고리 생성 | | `updateCategory()` | PUT | 카테고리 수정 | | `destroyCategory()` | DELETE | 카테고리 삭제 | | `tree()` | GET | 전체 계층 구조 (카테고리→템플릿→질문) | | `storeTemplate()` | POST | 템플릿 생성 | | `updateTemplate()` | PUT | 템플릿 수정 | | `destroyTemplate()` | DELETE | 템플릿 삭제 | | `storeQuestion()` | POST | 질문 생성 | | `updateQuestion()` | PUT | 질문 수정 | | `destroyQuestion()` | DELETE | 질문 삭제 | | `bulkImport()` | POST | MD 파일에서 일괄 가져오기 | | `sessions()` | GET | 인터뷰 세션 목록 | | `storeSession()` | POST | 인터뷰 시작 | | `showSession()` | GET | 세션 상세 | | `toggleAnswer()` | POST | 답변 토글/기록 | | `completeSession()` | POST | 인터뷰 완료 | ### InterviewScenarioService | 메서드 | 설명 | |--------|------| | `getCategories()` | 카테고리 목록 | | `createCategory()` | 카테고리 생성 | | `updateCategory()` | 카테고리 수정 | | `deleteCategory()` | 카테고리 삭제 | | `getTree()` | 전체 계층 구조 조회 | | `createTemplate()` | 템플릿 생성 | | `updateTemplate()` | 템플릿 수정 | | `deleteTemplate()` | 템플릿 삭제 | | `createQuestion()` | 질문 생성 | | `updateQuestion()` | 질문 수정 | | `deleteQuestion()` | 질문 삭제 | | `bulkImport()` | MD에서 일괄 가져오기 | | `getSessions()` | 세션 목록 (필터) | | `startSession()` | 인터뷰 시작 | | `getSessionDetail()` | 세션 상세 | | `toggleAnswer()` | 답변 토글 | | `completeSession()` | 인터뷰 완료 | ## 모델 ### 데이터 계층 구조 ``` InterviewCategory (카테고리) └── InterviewTemplate (템플릿/항목) └── InterviewQuestion (질문) InterviewSession (인터뷰 세션) └── InterviewAnswer (답변) ``` ### InterviewCategory **테이블**: `interview_categories` | 필드 | 타입 | 설명 | |------|------|------| | `tenant_id` | bigint | 테넌트 ID | | `name` | string | 카테고리명 (예: 제조-방화셔터) | | `description` | text | 설명 | | `sort_order` | int | 정렬 순서 | | `is_active` | boolean | 활성 여부 | | `created_by` | bigint | 등록자 | - SoftDeletes 적용 - 관계: `templates()`, `sessions()` ### InterviewTemplate **테이블**: `interview_templates` | 필드 | 타입 | 설명 | |------|------|------| | `tenant_id` | bigint | 테넌트 ID | | `interview_category_id` | bigint (FK) | 카테고리 ID | | `name` | string | 항목명 (예: 견적서 제작) | | `description` | text | 설명 | | `sort_order` | int | 정렬 순서 | | `is_active` | boolean | 활성 여부 | - SoftDeletes 적용 - 관계: `category()`, `questions()` ### InterviewQuestion **테이블**: `interview_questions` | 필드 | 타입 | 설명 | |------|------|------| | `tenant_id` | bigint | 테넌트 ID | | `interview_template_id` | bigint (FK) | 템플릿 ID | | `question_text` | string(500) | 질문 텍스트 | | `question_type` | string | **checkbox** / **text** | | `options` | json | 선택지 (배열) | | `is_required` | boolean | 필수 여부 | | `sort_order` | int | 정렬 순서 | | `is_active` | boolean | 활성 여부 | - SoftDeletes 적용 - 관계: `template()` #### 질문 타입 | 타입 | 설명 | 답변 방식 | |------|------|----------| | `checkbox` | 체크 질문 | is_checked 토글 | | `text` | 서술형 질문 | answer_text 입력 | ### InterviewSession **테이블**: `interview_sessions` | 필드 | 타입 | 설명 | |------|------|------| | `tenant_id` | bigint | 테넌트 ID | | `interview_category_id` | bigint (FK) | 사용한 카테고리 ID | | `interviewer_id` | bigint (FK) | 면담자(매니저) ID | | `interviewee_name` | string(100) | 면담 상대 이름 | | `interviewee_company` | string(200) | 면담 상대 회사 | | `interview_date` | date | 면담 일자 | | `status` | string | **in_progress** / **completed** | | `total_questions` | int | 총 질문 수 | | `answered_questions` | int | 답변 완료 수 | | `memo` | text | 메모 | | `completed_at` | timestamp | 완료 일시 | - SoftDeletes 적용 - 관계: `category()`, `interviewer()`, `answers()` ### InterviewAnswer **테이블**: `interview_answers` | 필드 | 타입 | 설명 | |------|------|------| | `tenant_id` | bigint | 테넌트 ID | | `interview_session_id` | bigint (FK) | 세션 ID | | `interview_question_id` | bigint (FK) | 질문 ID | | `interview_template_id` | bigint (FK) | 템플릿 ID | | `is_checked` | boolean | 체크 여부 (checkbox 유형) | | `answer_text` | text | 답변 텍스트 (text 유형) | | `memo` | text | 메모 | ### 인터뷰 진행 흐름 ``` 1. 카테고리 선택 + 면담 상대 정보 입력 → storeSession() → 세션 생성 (status: in_progress) → 해당 카테고리의 모든 질문에 대해 빈 Answer 생성 2. 질문별 답변 기록 → toggleAnswer() → is_checked 토글 / answer_text 저장 → answered_questions 카운트 갱신 3. 인터뷰 완료 → completeSession() → status: completed, completed_at 기록 ``` ## 뷰 구성 ### index.blade.php ``` ┌─ 페이지 헤더 ────────────────────── │ 제목: "인터뷰 시나리오" │ [카테고리 추가] [일괄 가져오기] 버튼 │ ├─ 좌측 사이드바 ─────────────────── │ 카테고리 트리 │ ├── 카테고리 A │ │ ├── 템플릿 1 (질문 3개) │ │ └── 템플릿 2 (질문 5개) │ └── 카테고리 B │ └── 템플릿 3 (질문 2개) │ ├─ 우측 메인 영역 ────────────────── │ ├─ 템플릿 편집 모드 │ │ 질문 목록 + 추가/수정/삭제 │ │ 질문 타입(checkbox/text) + 옵션 │ │ │ └─ 인터뷰 세션 모드 │ 면담 상대 정보 + 날짜 │ 질문별 체크/답변 기록 │ 진행률 표시 │ [완료] 버튼 │ └─ 세션 목록 ─────────────────────── 과거 인터뷰 기록 (필터: 카테고리, 날짜) 면담자 | 상대방 | 회사 | 날짜 | 완료율 | 상태 ``` ## HTMX 호환성 - JavaScript/React 기반 페이지이므로 **HX-Redirect 필요** - API 호출로 동적 CRUD 관리 - `@push('scripts')` 블록에 스크립트 포함