2026-01-28 21:45:11 +09:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace App\Http\Controllers\Sales;
|
|
|
|
|
|
|
|
|
|
use App\Http\Controllers\Controller;
|
2026-01-29 06:42:32 +09:00
|
|
|
use App\Models\Sales\SalesConsultation;
|
2026-01-28 21:45:11 +09:00
|
|
|
use App\Models\Tenants\Tenant;
|
|
|
|
|
use Illuminate\Http\Request;
|
|
|
|
|
use Illuminate\Http\JsonResponse;
|
|
|
|
|
use Illuminate\Support\Facades\Storage;
|
|
|
|
|
use Illuminate\View\View;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 상담 기록 관리 컨트롤러
|
|
|
|
|
*
|
|
|
|
|
* 테넌트별 상담 기록(텍스트, 음성, 파일)을 관리합니다.
|
2026-01-29 06:42:32 +09:00
|
|
|
* 데이터는 sales_consultations 테이블에 저장됩니다.
|
2026-01-28 21:45:11 +09:00
|
|
|
*/
|
|
|
|
|
class ConsultationController extends Controller
|
|
|
|
|
{
|
|
|
|
|
/**
|
|
|
|
|
* 상담 기록 목록 (HTMX 부분 뷰)
|
|
|
|
|
*/
|
|
|
|
|
public function index(int $tenantId, Request $request): View
|
|
|
|
|
{
|
|
|
|
|
$tenant = Tenant::findOrFail($tenantId);
|
|
|
|
|
$scenarioType = $request->input('scenario_type', 'sales');
|
|
|
|
|
$stepId = $request->input('step_id');
|
|
|
|
|
|
2026-01-29 06:42:32 +09:00
|
|
|
// DB에서 상담 기록 조회
|
|
|
|
|
$consultations = SalesConsultation::getByTenantAndType($tenantId, $scenarioType, $stepId);
|
2026-01-28 21:45:11 +09:00
|
|
|
|
|
|
|
|
return view('sales.modals.consultation-log', [
|
|
|
|
|
'tenant' => $tenant,
|
|
|
|
|
'consultations' => $consultations,
|
|
|
|
|
'scenarioType' => $scenarioType,
|
|
|
|
|
'stepId' => $stepId,
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 텍스트 상담 기록 저장
|
|
|
|
|
*/
|
|
|
|
|
public function store(Request $request): JsonResponse
|
|
|
|
|
{
|
|
|
|
|
$request->validate([
|
|
|
|
|
'tenant_id' => 'required|integer|exists:tenants,id',
|
|
|
|
|
'scenario_type' => 'required|in:sales,manager',
|
|
|
|
|
'step_id' => 'nullable|integer',
|
|
|
|
|
'content' => 'required|string|max:5000',
|
|
|
|
|
]);
|
|
|
|
|
|
2026-01-29 06:42:32 +09:00
|
|
|
$consultation = SalesConsultation::createText(
|
|
|
|
|
$request->input('tenant_id'),
|
|
|
|
|
$request->input('scenario_type'),
|
|
|
|
|
$request->input('step_id'),
|
|
|
|
|
$request->input('content')
|
|
|
|
|
);
|
2026-01-28 21:45:11 +09:00
|
|
|
|
2026-01-29 06:42:32 +09:00
|
|
|
$consultation->load('creator');
|
2026-01-28 21:45:11 +09:00
|
|
|
|
|
|
|
|
return response()->json([
|
|
|
|
|
'success' => true,
|
2026-01-29 06:42:32 +09:00
|
|
|
'consultation' => [
|
|
|
|
|
'id' => $consultation->id,
|
|
|
|
|
'type' => $consultation->consultation_type,
|
|
|
|
|
'content' => $consultation->content,
|
|
|
|
|
'created_by_name' => $consultation->creator->name,
|
|
|
|
|
'created_at' => $consultation->created_at->format('Y-m-d H:i'),
|
|
|
|
|
],
|
2026-01-28 21:45:11 +09:00
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 상담 기록 삭제
|
|
|
|
|
*/
|
2026-01-29 06:42:32 +09:00
|
|
|
public function destroy(int $consultationId, Request $request): JsonResponse
|
2026-01-28 21:45:11 +09:00
|
|
|
{
|
2026-01-29 06:42:32 +09:00
|
|
|
$consultation = SalesConsultation::findOrFail($consultationId);
|
2026-01-28 21:45:11 +09:00
|
|
|
|
2026-01-29 06:42:32 +09:00
|
|
|
// 파일이 있으면 함께 삭제
|
|
|
|
|
$consultation->deleteWithFile();
|
2026-01-28 21:45:11 +09:00
|
|
|
|
|
|
|
|
return response()->json([
|
|
|
|
|
'success' => true,
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 음성 파일 업로드
|
|
|
|
|
*/
|
|
|
|
|
public function uploadAudio(Request $request): JsonResponse
|
|
|
|
|
{
|
|
|
|
|
$request->validate([
|
|
|
|
|
'tenant_id' => 'required|integer|exists:tenants,id',
|
|
|
|
|
'scenario_type' => 'required|in:sales,manager',
|
|
|
|
|
'step_id' => 'nullable|integer',
|
|
|
|
|
'audio' => 'required|file|mimes:webm,mp3,wav,ogg|max:51200', // 50MB
|
|
|
|
|
'transcript' => 'nullable|string|max:10000',
|
|
|
|
|
'duration' => 'nullable|integer',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$tenantId = $request->input('tenant_id');
|
|
|
|
|
$scenarioType = $request->input('scenario_type');
|
|
|
|
|
$stepId = $request->input('step_id');
|
|
|
|
|
$transcript = $request->input('transcript');
|
|
|
|
|
$duration = $request->input('duration');
|
|
|
|
|
|
|
|
|
|
// 파일 저장
|
|
|
|
|
$file = $request->file('audio');
|
|
|
|
|
$fileName = 'audio_' . now()->format('Ymd_His') . '_' . uniqid() . '.' . $file->getClientOriginalExtension();
|
|
|
|
|
$path = $file->storeAs("tenant/consultations/{$tenantId}", $fileName, 'local');
|
|
|
|
|
|
2026-01-29 06:42:32 +09:00
|
|
|
// DB에 저장
|
|
|
|
|
$consultation = SalesConsultation::createAudio(
|
|
|
|
|
$tenantId,
|
|
|
|
|
$scenarioType,
|
|
|
|
|
$stepId,
|
|
|
|
|
$path,
|
|
|
|
|
$fileName,
|
|
|
|
|
$file->getSize(),
|
|
|
|
|
$transcript,
|
|
|
|
|
$duration
|
|
|
|
|
);
|
2026-01-28 21:45:11 +09:00
|
|
|
|
2026-01-29 06:42:32 +09:00
|
|
|
$consultation->load('creator');
|
2026-01-28 21:45:11 +09:00
|
|
|
|
|
|
|
|
return response()->json([
|
|
|
|
|
'success' => true,
|
2026-01-29 06:42:32 +09:00
|
|
|
'consultation' => [
|
|
|
|
|
'id' => $consultation->id,
|
|
|
|
|
'type' => $consultation->consultation_type,
|
|
|
|
|
'file_name' => $consultation->file_name,
|
|
|
|
|
'transcript' => $consultation->transcript,
|
|
|
|
|
'duration' => $consultation->duration,
|
|
|
|
|
'formatted_duration' => $consultation->formatted_duration,
|
|
|
|
|
'created_by_name' => $consultation->creator->name,
|
|
|
|
|
'created_at' => $consultation->created_at->format('Y-m-d H:i'),
|
|
|
|
|
],
|
2026-01-28 21:45:11 +09:00
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 첨부파일 업로드
|
|
|
|
|
*/
|
|
|
|
|
public function uploadFile(Request $request): JsonResponse
|
|
|
|
|
{
|
|
|
|
|
$request->validate([
|
|
|
|
|
'tenant_id' => 'required|integer|exists:tenants,id',
|
|
|
|
|
'scenario_type' => 'required|in:sales,manager',
|
|
|
|
|
'step_id' => 'nullable|integer',
|
|
|
|
|
'file' => 'required|file|max:20480', // 20MB
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$tenantId = $request->input('tenant_id');
|
|
|
|
|
$scenarioType = $request->input('scenario_type');
|
|
|
|
|
$stepId = $request->input('step_id');
|
|
|
|
|
|
|
|
|
|
// 파일 저장
|
|
|
|
|
$file = $request->file('file');
|
|
|
|
|
$originalName = $file->getClientOriginalName();
|
|
|
|
|
$fileName = now()->format('Ymd_His') . '_' . uniqid() . '_' . $originalName;
|
|
|
|
|
$path = $file->storeAs("tenant/attachments/{$tenantId}", $fileName, 'local');
|
|
|
|
|
|
2026-01-29 06:42:32 +09:00
|
|
|
// DB에 저장
|
|
|
|
|
$consultation = SalesConsultation::createFile(
|
|
|
|
|
$tenantId,
|
|
|
|
|
$scenarioType,
|
|
|
|
|
$stepId,
|
|
|
|
|
$path,
|
|
|
|
|
$originalName,
|
|
|
|
|
$file->getSize(),
|
|
|
|
|
$file->getMimeType()
|
|
|
|
|
);
|
2026-01-28 21:45:11 +09:00
|
|
|
|
2026-01-29 06:42:32 +09:00
|
|
|
$consultation->load('creator');
|
2026-01-28 21:45:11 +09:00
|
|
|
|
|
|
|
|
return response()->json([
|
|
|
|
|
'success' => true,
|
2026-01-29 06:42:32 +09:00
|
|
|
'consultation' => [
|
|
|
|
|
'id' => $consultation->id,
|
|
|
|
|
'type' => $consultation->consultation_type,
|
|
|
|
|
'file_name' => $consultation->file_name,
|
|
|
|
|
'file_size' => $consultation->file_size,
|
|
|
|
|
'formatted_file_size' => $consultation->formatted_file_size,
|
|
|
|
|
'created_by_name' => $consultation->creator->name,
|
|
|
|
|
'created_at' => $consultation->created_at->format('Y-m-d H:i'),
|
|
|
|
|
],
|
2026-01-28 21:45:11 +09:00
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 파일 삭제
|
|
|
|
|
*/
|
2026-01-29 06:42:32 +09:00
|
|
|
public function deleteFile(int $fileId, Request $request): JsonResponse
|
2026-01-28 21:45:11 +09:00
|
|
|
{
|
|
|
|
|
return $this->destroy($fileId, $request);
|
|
|
|
|
}
|
|
|
|
|
}
|