diff --git a/app/Http/Controllers/Sales/InterviewScenarioController.php b/app/Http/Controllers/Sales/InterviewScenarioController.php new file mode 100644 index 00000000..d169ff55 --- /dev/null +++ b/app/Http/Controllers/Sales/InterviewScenarioController.php @@ -0,0 +1,227 @@ +header('HX-Request')) { + return response('', 200)->header('HX-Redirect', route('sales.interviews.index')); + } + + return view('sales.interviews.index'); + } + + // ============================================================ + // 카테고리 API + // ============================================================ + + public function categories(): JsonResponse + { + return response()->json($this->service->getCategories()); + } + + public function storeCategory(Request $request): JsonResponse + { + $validated = $request->validate([ + 'name' => 'required|string|max:100', + 'description' => 'nullable|string', + ]); + + $category = $this->service->createCategory($validated); + + return response()->json($category, 201); + } + + public function updateCategory(Request $request, int $id): JsonResponse + { + $validated = $request->validate([ + 'name' => 'required|string|max:100', + 'description' => 'nullable|string', + ]); + + $category = $this->service->updateCategory($id, $validated); + + return response()->json($category); + } + + public function destroyCategory(int $id): JsonResponse + { + $this->service->deleteCategory($id); + + return response()->json(['message' => '삭제되었습니다.']); + } + + // ============================================================ + // 트리 API + // ============================================================ + + public function tree(): JsonResponse + { + return response()->json($this->service->getTree()); + } + + // ============================================================ + // 템플릿(항목) API + // ============================================================ + + public function storeTemplate(Request $request): JsonResponse + { + $validated = $request->validate([ + 'interview_category_id' => 'required|integer|exists:interview_categories,id', + 'name' => 'required|string|max:200', + 'description' => 'nullable|string', + ]); + + $template = $this->service->createTemplate($validated); + + return response()->json($template, 201); + } + + public function updateTemplate(Request $request, int $id): JsonResponse + { + $validated = $request->validate([ + 'name' => 'required|string|max:200', + 'description' => 'nullable|string', + ]); + + $template = $this->service->updateTemplate($id, $validated); + + return response()->json($template); + } + + public function destroyTemplate(int $id): JsonResponse + { + $this->service->deleteTemplate($id); + + return response()->json(['message' => '삭제되었습니다.']); + } + + // ============================================================ + // 질문 API + // ============================================================ + + public function storeQuestion(Request $request): JsonResponse + { + $validated = $request->validate([ + 'interview_template_id' => 'required|integer|exists:interview_templates,id', + 'question_text' => 'required|string|max:500', + 'question_type' => 'nullable|string|in:checkbox,text', + 'is_required' => 'nullable|boolean', + ]); + + $question = $this->service->createQuestion($validated); + + return response()->json($question, 201); + } + + public function updateQuestion(Request $request, int $id): JsonResponse + { + $validated = $request->validate([ + 'question_text' => 'required|string|max:500', + 'question_type' => 'nullable|string|in:checkbox,text', + 'is_required' => 'nullable|boolean', + ]); + + $question = $this->service->updateQuestion($id, $validated); + + return response()->json($question); + } + + public function destroyQuestion(int $id): JsonResponse + { + $this->service->deleteQuestion($id); + + return response()->json(['message' => '삭제되었습니다.']); + } + + // ============================================================ + // MD 파일 일괄 가져오기 + // ============================================================ + + public function bulkImport(Request $request): JsonResponse + { + $validated = $request->validate([ + 'category_id' => 'required|integer|exists:interview_categories,id', + 'templates' => 'required|array|min:1', + 'templates.*.name' => 'required|string|max:200', + 'templates.*.questions' => 'required|array|min:1', + 'templates.*.questions.*' => 'required|string|max:500', + ]); + + $result = $this->service->bulkImport($validated['category_id'], $validated['templates']); + + return response()->json($result, 201); + } + + // ============================================================ + // 세션 API + // ============================================================ + + public function sessions(Request $request): JsonResponse + { + $filters = $request->only(['status', 'category_id']); + $sessions = $this->service->getSessions($filters); + + return response()->json($sessions); + } + + public function storeSession(Request $request): JsonResponse + { + $validated = $request->validate([ + 'interview_category_id' => 'required|integer|exists:interview_categories,id', + 'interviewee_name' => 'nullable|string|max:100', + 'interviewee_company' => 'nullable|string|max:200', + 'interview_date' => 'nullable|date', + 'memo' => 'nullable|string', + ]); + + $session = $this->service->startSession($validated); + + return response()->json($session, 201); + } + + public function showSession(int $id): JsonResponse + { + $session = $this->service->getSessionDetail($id); + + return response()->json($session); + } + + public function toggleAnswer(Request $request): JsonResponse + { + $validated = $request->validate([ + 'session_id' => 'required|integer', + 'question_id' => 'required|integer', + 'answer_text' => 'nullable|string', + 'memo' => 'nullable|string', + ]); + + $answer = $this->service->toggleAnswer($validated); + + return response()->json($answer); + } + + public function completeSession(int $id): JsonResponse + { + $session = $this->service->completeSession($id); + + return response()->json($session); + } +} diff --git a/app/Models/Interview/InterviewAnswer.php b/app/Models/Interview/InterviewAnswer.php new file mode 100644 index 00000000..cd484388 --- /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 00000000..dac8c0af --- /dev/null +++ b/app/Models/Interview/InterviewCategory.php @@ -0,0 +1,39 @@ + 'boolean', + 'sort_order' => 'integer', + ]; + + 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 00000000..896f6c56 --- /dev/null +++ b/app/Models/Interview/InterviewQuestion.php @@ -0,0 +1,39 @@ + 'array', + 'is_required' => 'boolean', + 'is_active' => 'boolean', + 'sort_order' => 'integer', + ]; + + 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 00000000..c0733bff --- /dev/null +++ b/app/Models/Interview/InterviewSession.php @@ -0,0 +1,52 @@ + 'date', + 'completed_at' => 'datetime', + 'total_questions' => 'integer', + 'answered_questions' => 'integer', + ]; + + 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 00000000..3b851806 --- /dev/null +++ b/app/Models/Interview/InterviewTemplate.php @@ -0,0 +1,40 @@ + 'boolean', + 'sort_order' => 'integer', + ]; + + 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/app/Services/Sales/InterviewScenarioService.php b/app/Services/Sales/InterviewScenarioService.php new file mode 100644 index 00000000..6a35afb2 --- /dev/null +++ b/app/Services/Sales/InterviewScenarioService.php @@ -0,0 +1,340 @@ +orderBy('id') + ->get(); + } + + public function createCategory(array $data): InterviewCategory + { + $tenantId = session('selected_tenant_id', 1); + $maxSort = InterviewCategory::max('sort_order') ?? 0; + + return InterviewCategory::create([ + 'tenant_id' => $tenantId, + 'name' => $data['name'], + 'description' => $data['description'] ?? null, + 'sort_order' => $maxSort + 1, + 'is_active' => true, + 'created_by' => auth()->id(), + 'updated_by' => auth()->id(), + ]); + } + + public function updateCategory(int $id, array $data): InterviewCategory + { + $category = InterviewCategory::findOrFail($id); + $category->update([ + 'name' => $data['name'], + 'description' => $data['description'] ?? null, + 'updated_by' => auth()->id(), + ]); + + return $category->fresh(); + } + + public function deleteCategory(int $id): void + { + $category = InterviewCategory::findOrFail($id); + $category->update(['deleted_by' => auth()->id()]); + $category->delete(); + } + + // ============================================================ + // 템플릿(항목) CRUD + // ============================================================ + + public function createTemplate(array $data): InterviewTemplate + { + $tenantId = session('selected_tenant_id', 1); + $maxSort = InterviewTemplate::where('interview_category_id', $data['interview_category_id']) + ->max('sort_order') ?? 0; + + return InterviewTemplate::create([ + 'tenant_id' => $tenantId, + 'interview_category_id' => $data['interview_category_id'], + 'name' => $data['name'], + 'description' => $data['description'] ?? null, + 'sort_order' => $maxSort + 1, + 'is_active' => true, + 'created_by' => auth()->id(), + 'updated_by' => auth()->id(), + ]); + } + + public function updateTemplate(int $id, array $data): InterviewTemplate + { + $template = InterviewTemplate::findOrFail($id); + $template->update([ + 'name' => $data['name'], + 'description' => $data['description'] ?? null, + 'updated_by' => auth()->id(), + ]); + + return $template->fresh(); + } + + public function deleteTemplate(int $id): void + { + $template = InterviewTemplate::findOrFail($id); + $template->update(['deleted_by' => auth()->id()]); + $template->delete(); + } + + // ============================================================ + // 질문 CRUD + // ============================================================ + + public function createQuestion(array $data): InterviewQuestion + { + $tenantId = session('selected_tenant_id', 1); + $maxSort = InterviewQuestion::where('interview_template_id', $data['interview_template_id']) + ->max('sort_order') ?? 0; + + return InterviewQuestion::create([ + 'tenant_id' => $tenantId, + 'interview_template_id' => $data['interview_template_id'], + 'question_text' => $data['question_text'], + 'question_type' => $data['question_type'] ?? 'checkbox', + 'is_required' => $data['is_required'] ?? false, + 'sort_order' => $maxSort + 1, + 'is_active' => true, + 'created_by' => auth()->id(), + 'updated_by' => auth()->id(), + ]); + } + + public function updateQuestion(int $id, array $data): InterviewQuestion + { + $question = InterviewQuestion::findOrFail($id); + $question->update([ + 'question_text' => $data['question_text'], + 'question_type' => $data['question_type'] ?? $question->question_type, + 'is_required' => $data['is_required'] ?? $question->is_required, + 'updated_by' => auth()->id(), + ]); + + return $question->fresh(); + } + + public function deleteQuestion(int $id): void + { + $question = InterviewQuestion::findOrFail($id); + $question->update(['deleted_by' => auth()->id()]); + $question->delete(); + } + + // ============================================================ + // MD 파일 일괄 가져오기 + // ============================================================ + + public function bulkImport(int $categoryId, array $templates): array + { + return DB::transaction(function () use ($categoryId, $templates) { + $tenantId = session('selected_tenant_id', 1); + $userId = auth()->id(); + $maxTemplateSort = InterviewTemplate::where('interview_category_id', $categoryId) + ->max('sort_order') ?? 0; + + $createdTemplates = 0; + $createdQuestions = 0; + + foreach ($templates as $tpl) { + $maxTemplateSort++; + $template = InterviewTemplate::create([ + 'tenant_id' => $tenantId, + 'interview_category_id' => $categoryId, + 'name' => $tpl['name'], + 'sort_order' => $maxTemplateSort, + 'is_active' => true, + 'created_by' => $userId, + 'updated_by' => $userId, + ]); + $createdTemplates++; + + $questionSort = 0; + foreach ($tpl['questions'] as $questionText) { + $questionSort++; + InterviewQuestion::create([ + 'tenant_id' => $tenantId, + 'interview_template_id' => $template->id, + 'question_text' => $questionText, + 'question_type' => 'checkbox', + 'is_required' => false, + 'sort_order' => $questionSort, + 'is_active' => true, + 'created_by' => $userId, + 'updated_by' => $userId, + ]); + $createdQuestions++; + } + } + + return [ + 'templates_created' => $createdTemplates, + 'questions_created' => $createdQuestions, + ]; + }); + } + + // ============================================================ + // 전체 트리 조회 + // ============================================================ + + public function getTree() + { + return InterviewCategory::with([ + 'templates' => function ($q) { + $q->orderBy('sort_order')->orderBy('id'); + $q->with(['questions' => function ($q2) { + $q2->orderBy('sort_order')->orderBy('id'); + }]); + }, + ]) + ->orderBy('sort_order') + ->orderBy('id') + ->get(); + } + + // ============================================================ + // 세션 관리 + // ============================================================ + + public function getSessions(array $filters = []) + { + $query = InterviewSession::with(['category', 'interviewer']) + ->orderByDesc('interview_date') + ->orderByDesc('id'); + + if (!empty($filters['status'])) { + $query->where('status', $filters['status']); + } + + if (!empty($filters['category_id'])) { + $query->where('interview_category_id', $filters['category_id']); + } + + return $query->paginate(20); + } + + public function startSession(array $data): InterviewSession + { + return DB::transaction(function () use ($data) { + $tenantId = session('selected_tenant_id', 1); + $categoryId = $data['interview_category_id']; + + // 카테고리의 모든 활성 템플릿과 질문 가져오기 + $templates = InterviewTemplate::where('interview_category_id', $categoryId) + ->where('is_active', true) + ->with(['questions' => function ($q) { + $q->where('is_active', true)->orderBy('sort_order'); + }]) + ->orderBy('sort_order') + ->get(); + + $totalQuestions = $templates->sum(fn($t) => $t->questions->count()); + + // 세션 생성 + $session = InterviewSession::create([ + 'tenant_id' => $tenantId, + 'interview_category_id' => $categoryId, + 'interviewer_id' => auth()->id(), + 'interviewee_name' => $data['interviewee_name'] ?? null, + 'interviewee_company' => $data['interviewee_company'] ?? null, + 'interview_date' => $data['interview_date'] ?? now()->toDateString(), + 'status' => 'in_progress', + 'total_questions' => $totalQuestions, + 'answered_questions' => 0, + 'memo' => $data['memo'] ?? null, + 'created_by' => auth()->id(), + 'updated_by' => auth()->id(), + ]); + + // 모든 질문에 대해 빈 답변 레코드 생성 + foreach ($templates as $template) { + foreach ($template->questions as $question) { + InterviewAnswer::create([ + 'tenant_id' => $tenantId, + 'interview_session_id' => $session->id, + 'interview_question_id' => $question->id, + 'interview_template_id' => $template->id, + 'is_checked' => false, + ]); + } + } + + return $session; + }); + } + + public function getSessionDetail(int $id) + { + return InterviewSession::with([ + 'category', + 'interviewer', + 'answers' => function ($q) { + $q->with(['question', 'template']); + }, + ])->findOrFail($id); + } + + public function toggleAnswer(array $data): InterviewAnswer + { + $answer = InterviewAnswer::where('interview_session_id', $data['session_id']) + ->where('interview_question_id', $data['question_id']) + ->firstOrFail(); + + $answer->update([ + 'is_checked' => !$answer->is_checked, + 'answer_text' => $data['answer_text'] ?? $answer->answer_text, + 'memo' => $data['memo'] ?? $answer->memo, + ]); + + // answered_questions 갱신 + $session = InterviewSession::findOrFail($data['session_id']); + $answeredCount = InterviewAnswer::where('interview_session_id', $session->id) + ->where('is_checked', true) + ->count(); + $session->update([ + 'answered_questions' => $answeredCount, + 'updated_by' => auth()->id(), + ]); + + return $answer->fresh(); + } + + public function completeSession(int $id): InterviewSession + { + $session = InterviewSession::findOrFail($id); + + $answeredCount = InterviewAnswer::where('interview_session_id', $session->id) + ->where('is_checked', true) + ->count(); + + $session->update([ + 'status' => 'completed', + 'answered_questions' => $answeredCount, + 'completed_at' => now(), + 'updated_by' => auth()->id(), + ]); + + return $session->fresh(); + } +} diff --git a/database/seeders/InterviewMenuSeeder.php b/database/seeders/InterviewMenuSeeder.php new file mode 100644 index 00000000..c468144e --- /dev/null +++ b/database/seeders/InterviewMenuSeeder.php @@ -0,0 +1,59 @@ +where('name', '영업관리') + ->whereNull('parent_id') + ->whereNull('deleted_at') + ->get(); + + if ($salesMenus->isEmpty()) { + $this->command->error('영업관리 메뉴를 찾을 수 없습니다.'); + return; + } + + foreach ($salesMenus as $salesMenu) { + $tenantId = $salesMenu->tenant_id; + $label = $tenantId ? "tenant_id={$tenantId}" : 'global(tenant_id=null)'; + + // 이미 존재하는지 확인 + $exists = Menu::withoutGlobalScopes() + ->where('parent_id', $salesMenu->id) + ->where('name', '인터뷰 시나리오') + ->whereNull('deleted_at') + ->exists(); + + if ($exists) { + $this->command->info("[{$label}] 인터뷰 시나리오 메뉴가 이미 존재합니다."); + continue; + } + + // 마지막 sort_order 확인 + $maxSort = Menu::withoutGlobalScopes() + ->where('parent_id', $salesMenu->id) + ->whereNull('deleted_at') + ->max('sort_order') ?? 0; + + Menu::withoutGlobalScopes()->create([ + 'tenant_id' => $tenantId, + 'parent_id' => $salesMenu->id, + 'name' => '인터뷰 시나리오', + 'url' => '/sales/interviews', + 'icon' => 'clipboard-check', + 'sort_order' => $maxSort + 1, + 'is_active' => true, + ]); + + $this->command->info("[{$label}] 인터뷰 시나리오 메뉴 생성 완료."); + } + } +} diff --git a/docs/samples/방화셔터_견적구조_인터뷰.md b/docs/samples/방화셔터_견적구조_인터뷰.md new file mode 100644 index 00000000..45a6f9f7 --- /dev/null +++ b/docs/samples/방화셔터_견적구조_인터뷰.md @@ -0,0 +1,87 @@ +# 견적 산출 엑셀 구조 검토 +- 견적 산출 엑셀 파일을 분석했는가? +- 엑셀 구조를 자세히 검토했는가? +- 이미 완성된 엑셀을 그대로 활용할 수 있는가? +- 견적 구조를 문서화하여 설명할 수 있는가? + +# 셔터 카테고리 및 제품 분류 +- 통합 견적(견적 산출 + 발주서 작성) 범위를 확인했는가? +- 스크린 셔터와 철제 셔터의 분류 기준을 확인했는가? +- 스크린 본체/철제 슬랫 본체에 따른 제품 구분을 이해했는가? +- 산업용 등 카테고리별 구분 체계를 확인했는가? + +# 셔터 제품명 및 구분 구조 +- KSS01, KSS02 등 제품명 체계를 파악했는가? +- KWW WS 등 대형 제품 포함 여부를 확인했는가? +- KQTS, KT201 등 전체 제품 목록을 확보했는가? +- 벽면형, 측면형, 혼합형 등 설치 유형별 분류를 확인했는가? + +# 셔터 본체 구성 +- 본체가 스크린 또는 철제 슬랫으로 구성됨을 확인했는가? +- 본체와 모터가 공통 구성 요소임을 확인했는가? +- 스크린 본체의 원단 종류(실리카, 와이어, 하이바)를 확인했는가? +- 스크린의 절단, 미싱, 포장 공정을 파악했는가? +- 환봉이 부자재 창고에서 별도 관리됨을 확인했는가? + +# 전동 개폐기(모터) 구성 +- 전동 개폐기(모터)의 품질심사 용어와 현장 용어 차이를 인지했는가? +- 모터 내부 구성품(절연 거품, 부자재)을 확인했는가? +- 모터 용량 범위(150kg~1,500kg)를 파악했는가? +- 인증 제품의 경우 모터 구성이 고정됨을 확인했는가? +- 모터를 별도 구매하여 부품으로만 관리하는 현재 방식을 확인했는가? + +# 인정제품 vs 비인정제품 구분 +- 인정제품은 모든 구성 요소가 고정된 완전 세트임을 확인했는가? +- 비인정제품은 본체만/모터만/부자재만 개별 주문 가능함을 확인했는가? +- 인증 제품의 고정 구성 목록을 확보했는가? + +# 연동 제어기 및 부자재 +- 연동 제어기의 노출형/매립형 구분을 확인했는가? +- 매립형 연동 제어기에 뒷박스 부자재가 추가됨을 확인했는가? +- 공임과 부자재가 견적의 핵심 항목임을 인지했는가? + +# 가이드레일 및 케이스 구성 +- 가이드레일이 제품마다 모양과 치수가 다름을 확인했는가? +- 가이드레일 표준 길이(2,438mm, 3,305mm, 4,430mm) 조합을 파악했는가? +- 케이스 크기별(1500×380, 500×380) 부속품 차이를 확인했는가? +- 가이드레일은 항상 2개씩 필요함을 확인했는가? +- 판재 절곡 방식에 따른 모양 차이를 이해했는가? + +# 오픈 사이즈와 제작 사이즈 +- 오픈 사이즈 입력에 따른 제작 사이즈 산출 방식을 확인했는가? +- 케이스/가이드레일 길이가 오픈 사이즈 확정 후 산출됨을 이해했는가? +- 현장 조합 및 용접 방식을 파악했는가? +- 표준 길이 판재의 현장 절단 방식을 확인했는가? + +# BOM 구조 현황 및 문제점 +- 현재 상향식(최하위 부품→상위 조립품) BOM 구조를 파악했는가? +- 제품별 부품 매칭 정보가 미구현 상태임을 확인했는가? +- KSS02 선택 시 부품 목록 자동 나열이 안 되는 문제를 인지했는가? +- 오픈 사이즈 기반 동적 부품 계산이 미구현임을 확인했는가? +- 케이스 내 부속품 연결이 표시되지 않는 문제를 확인했는가? + +# BOM 구조 개선 방안 +- 하향식 BOM 구조(상위 제품→하위 구성품) 도입을 검토했는가? +- KSS02 등록 시 하위 항목 자동 등록 플로우를 설계했는가? +- 카테고리별 품목 분류(본체, 절곡품, 부자재) 체계를 수립했는가? +- 제품-부품 매핑 테이블(기본 구성품/옵션 구성품)을 정의했는가? +- 오픈 사이즈 입력 시 동적 계산 부품 구분을 설계했는가? + +# 품목 등록 시스템 개선 +- 품목 등록 시 상위 품목 선택 기능을 구현할 수 있는가? +- 카테고리 선택 시 해당 목록만 표시하는 기능을 설계했는가? +- 제품별 부품 설정 등록 기능을 기획했는가? +- 상위 부품 선택 시 하위 부품 자동 표시를 구현할 수 있는가? + +# 제품 목록 및 데이터 확보 +- 5130 시스템의 제품 수(20개 미만)를 확인했는가? +- 미등록 제품이 더 있을 가능성을 검토했는가? +- 전체 제품(약 11개 + 스모크 관련) 목록을 확보했는가? +- 참고 자료 기반 부품 정리가 완료되었는가? + +# 견적 구조 확장 계획 +- 기본 복에서 제품 단위로 견적 확장 계획을 수립했는가? +- 제품별 원단 확정(예: KSS02→실리카) 정보를 정리했는가? +- 케이스 부품 종류 확정 및 사이즈/길이의 동적 입력 방식을 설계했는가? +- 공정 관리와 품목 연동 구조를 검토했는가? +- 개발팀과의 추가 협의 일정을 확인했는가? diff --git a/resources/views/barobill/hometax/index.blade.php b/resources/views/barobill/hometax/index.blade.php index 098c354a..c85820f6 100644 --- a/resources/views/barobill/hometax/index.blade.php +++ b/resources/views/barobill/hometax/index.blade.php @@ -61,7 +61,7 @@ + + + +@verbatim + +@endverbatim +@endpush diff --git a/routes/web.php b/routes/web.php index 665ce111..d9b165cd 100644 --- a/routes/web.php +++ b/routes/web.php @@ -1222,4 +1222,32 @@ Route::post('/products', [\App\Http\Controllers\Sales\SalesContractController::class, 'saveProducts'])->name('products.save'); Route::get('/products/{tenant}', [\App\Http\Controllers\Sales\SalesContractController::class, 'getProducts'])->name('products.get'); }); + + // 인터뷰 시나리오 관리 + Route::prefix('interviews')->name('interviews.')->group(function () { + Route::get('/', [\App\Http\Controllers\Sales\InterviewScenarioController::class, 'index'])->name('index'); + // 카테고리 API + Route::get('/api/categories', [\App\Http\Controllers\Sales\InterviewScenarioController::class, 'categories'])->name('api.categories'); + Route::post('/api/categories', [\App\Http\Controllers\Sales\InterviewScenarioController::class, 'storeCategory'])->name('api.categories.store'); + Route::put('/api/categories/{id}', [\App\Http\Controllers\Sales\InterviewScenarioController::class, 'updateCategory'])->name('api.categories.update'); + Route::delete('/api/categories/{id}', [\App\Http\Controllers\Sales\InterviewScenarioController::class, 'destroyCategory'])->name('api.categories.destroy'); + // 트리 API + Route::get('/api/tree', [\App\Http\Controllers\Sales\InterviewScenarioController::class, 'tree'])->name('api.tree'); + // 템플릿(항목) API + Route::post('/api/templates', [\App\Http\Controllers\Sales\InterviewScenarioController::class, 'storeTemplate'])->name('api.templates.store'); + Route::put('/api/templates/{id}', [\App\Http\Controllers\Sales\InterviewScenarioController::class, 'updateTemplate'])->name('api.templates.update'); + Route::delete('/api/templates/{id}', [\App\Http\Controllers\Sales\InterviewScenarioController::class, 'destroyTemplate'])->name('api.templates.destroy'); + // 질문 API + Route::post('/api/questions', [\App\Http\Controllers\Sales\InterviewScenarioController::class, 'storeQuestion'])->name('api.questions.store'); + Route::put('/api/questions/{id}', [\App\Http\Controllers\Sales\InterviewScenarioController::class, 'updateQuestion'])->name('api.questions.update'); + Route::delete('/api/questions/{id}', [\App\Http\Controllers\Sales\InterviewScenarioController::class, 'destroyQuestion'])->name('api.questions.destroy'); + // 일괄 가져오기 API + Route::post('/api/bulk-import', [\App\Http\Controllers\Sales\InterviewScenarioController::class, 'bulkImport'])->name('api.bulk-import'); + // 세션 API + Route::get('/api/sessions', [\App\Http\Controllers\Sales\InterviewScenarioController::class, 'sessions'])->name('api.sessions'); + Route::post('/api/sessions', [\App\Http\Controllers\Sales\InterviewScenarioController::class, 'storeSession'])->name('api.sessions.store'); + Route::get('/api/sessions/{id}', [\App\Http\Controllers\Sales\InterviewScenarioController::class, 'showSession'])->name('api.sessions.show'); + Route::post('/api/sessions/toggle-answer', [\App\Http\Controllers\Sales\InterviewScenarioController::class, 'toggleAnswer'])->name('api.sessions.toggle-answer'); + Route::post('/api/sessions/{id}/complete', [\App\Http\Controllers\Sales\InterviewScenarioController::class, 'completeSession'])->name('api.sessions.complete'); + }); });