feat:가망고객(prospect) 상담 기록 및 첨부파일 기능 추가

- SalesConsultation 모델에 prospect 관련 메서드 추가
  - createTextByProspect(), createAudioByProspect(), createFileByProspect()
  - getByProspectAndType() 조회 메서드
- ConsultationController에 prospect 라우트 추가
  - prospectIndex(), prospectStore(), prospectUploadAudio(), prospectUploadFile()
- scenario-modal.blade.php에서 @if(!$isProspectMode) 조건 제거
  - 가망고객 모드에서도 상담 기록 섹션 표시
- voice-recorder, file-uploader, consultation-log에 prospect 모드 지원
- routes/web.php에 prospect 상담 기록 라우트 추가

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
김보곤
2026-01-31 19:50:46 +09:00
parent 49c437f796
commit d96cdc1975
7 changed files with 368 additions and 42 deletions

View File

@@ -2,6 +2,7 @@
namespace App\Models\Sales;
use App\Models\Sales\TenantProspect;
use App\Models\Tenants\Tenant;
use App\Models\User;
use Illuminate\Database\Eloquent\Model;
@@ -13,7 +14,8 @@
* 영업 상담 기록 모델 (텍스트, 음성, 첨부파일)
*
* @property int $id
* @property int $tenant_id
* @property int|null $tenant_id
* @property int|null $tenant_prospect_id
* @property string $scenario_type (sales/manager)
* @property int|null $step_id
* @property string $consultation_type (text/audio/file)
@@ -34,6 +36,7 @@ class SalesConsultation extends Model
protected $fillable = [
'tenant_id',
'tenant_prospect_id',
'scenario_type',
'step_id',
'consultation_type',
@@ -69,6 +72,14 @@ public function tenant(): BelongsTo
return $this->belongsTo(Tenant::class);
}
/**
* 가망고객 관계
*/
public function prospect(): BelongsTo
{
return $this->belongsTo(TenantProspect::class, 'tenant_prospect_id');
}
/**
* 작성자 관계
*/
@@ -258,4 +269,95 @@ public function scopeFileOnly($query)
{
return $query->where('consultation_type', self::TYPE_FILE);
}
// ========================================
// Prospect(가망고객) 관련 메서드
// ========================================
/**
* 가망고객 텍스트 상담 기록 생성
*/
public static function createTextByProspect(int $prospectId, string $scenarioType, ?int $stepId, string $content): self
{
return self::create([
'tenant_prospect_id' => $prospectId,
'scenario_type' => $scenarioType,
'step_id' => $stepId,
'consultation_type' => self::TYPE_TEXT,
'content' => $content,
'created_by' => auth()->id(),
]);
}
/**
* 가망고객 음성 상담 기록 생성
*/
public static function createAudioByProspect(
int $prospectId,
string $scenarioType,
?int $stepId,
string $filePath,
string $fileName,
int $fileSize,
?string $transcript = null,
?int $duration = null,
?string $gcsUri = null
): self {
return self::create([
'tenant_prospect_id' => $prospectId,
'scenario_type' => $scenarioType,
'step_id' => $stepId,
'consultation_type' => self::TYPE_AUDIO,
'file_path' => $filePath,
'file_name' => $fileName,
'file_size' => $fileSize,
'file_type' => 'audio/webm',
'transcript' => $transcript,
'duration' => $duration,
'gcs_uri' => $gcsUri,
'created_by' => auth()->id(),
]);
}
/**
* 가망고객 파일 상담 기록 생성
*/
public static function createFileByProspect(
int $prospectId,
string $scenarioType,
?int $stepId,
string $filePath,
string $fileName,
int $fileSize,
string $fileType
): self {
return self::create([
'tenant_prospect_id' => $prospectId,
'scenario_type' => $scenarioType,
'step_id' => $stepId,
'consultation_type' => self::TYPE_FILE,
'file_path' => $filePath,
'file_name' => $fileName,
'file_size' => $fileSize,
'file_type' => $fileType,
'created_by' => auth()->id(),
]);
}
/**
* 가망고객 + 시나리오 타입으로 조회
*/
public static function getByProspectAndType(int $prospectId, string $scenarioType, ?int $stepId = null)
{
$query = self::where('tenant_prospect_id', $prospectId)
->where('scenario_type', $scenarioType)
->with('creator')
->orderBy('created_at', 'desc');
if ($stepId !== null) {
$query->where('step_id', $stepId);
}
return $query->get();
}
}