Files
sam-manage/app/Models/Sales/SalesConsultation.php
김보곤 8291cdc39b feat: [database] codebridge DB 분리 - 118개 MNG 전용 테이블 connection 설정
- config/database.php에 codebridge connection 추가
- 78개 MNG 전용 모델에 $connection = 'codebridge' 설정
  - Admin (15): PM, 로드맵, API Explorer
  - Sales (16): 영업파트너, 수수료, 가망고객
  - Finance (9): 법인카드, 자금관리, 홈택스
  - Barobill (12): 은행/카드 동기화 관리
  - Interview (1), ESign (6), Equipment (2)
  - AI (3), Audit (3), 기타 (11)
2026-03-07 11:31:27 +09:00

366 lines
9.7 KiB
PHP

<?php
namespace App\Models\Sales;
use App\Models\Tenants\Tenant;
use App\Models\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Facades\Storage;
/**
* 영업 상담 기록 모델 (텍스트, 음성, 첨부파일)
*
* @property int $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)
* @property string|null $content
* @property string|null $file_path
* @property string|null $file_name
* @property int|null $file_size
* @property string|null $file_type
* @property string|null $transcript
* @property int|null $duration
* @property int $created_by
*/
class SalesConsultation extends Model
{
use SoftDeletes;
protected $connection = 'codebridge';
protected $table = 'sales_consultations';
protected $fillable = [
'tenant_id',
'tenant_prospect_id',
'scenario_type',
'step_id',
'consultation_type',
'content',
'file_path',
'file_name',
'file_size',
'file_type',
'transcript',
'duration',
'gcs_uri',
'created_by',
];
protected $casts = [
'step_id' => 'integer',
'file_size' => 'integer',
'duration' => 'integer',
];
/**
* 상담 유형 상수
*/
const TYPE_TEXT = 'text';
const TYPE_AUDIO = 'audio';
const TYPE_FILE = 'file';
/**
* 테넌트 관계
*/
public function tenant(): BelongsTo
{
return $this->belongsTo(Tenant::class);
}
/**
* 가망고객 관계
*/
public function prospect(): BelongsTo
{
return $this->belongsTo(TenantProspect::class, 'tenant_prospect_id');
}
/**
* 작성자 관계
*/
public function creator(): BelongsTo
{
return $this->belongsTo(User::class, 'created_by');
}
/**
* 텍스트 상담 기록 생성
*/
public static function createText(int $tenantId, string $scenarioType, ?int $stepId, string $content): self
{
return self::create([
'tenant_id' => $tenantId,
'scenario_type' => $scenarioType,
'step_id' => $stepId,
'consultation_type' => self::TYPE_TEXT,
'content' => $content,
'created_by' => auth()->id(),
]);
}
/**
* 음성 상담 기록 생성
*
* @param int $tenantId 테넌트 ID
* @param string $scenarioType 시나리오 타입 (sales/manager)
* @param int|null $stepId 단계 ID
* @param string $filePath 로컬 파일 경로
* @param string $fileName 파일명
* @param int $fileSize 파일 크기
* @param string|null $transcript 음성 텍스트 변환 결과
* @param int|null $duration 녹음 시간 (초)
* @param string|null $gcsUri GCS URI (본사 연구용 백업)
*/
public static function createAudio(
int $tenantId,
string $scenarioType,
?int $stepId,
string $filePath,
string $fileName,
int $fileSize,
?string $transcript = null,
?int $duration = null,
?string $gcsUri = null
): self {
return self::create([
'tenant_id' => $tenantId,
'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 createFile(
int $tenantId,
string $scenarioType,
?int $stepId,
string $filePath,
string $fileName,
int $fileSize,
string $fileType
): self {
return self::create([
'tenant_id' => $tenantId,
'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(),
]);
}
/**
* 파일 삭제 (storage 포함)
*/
public function deleteWithFile(): bool
{
if ($this->file_path && Storage::disk('local')->exists($this->file_path)) {
Storage::disk('local')->delete($this->file_path);
}
return $this->delete();
}
/**
* 포맷된 duration Accessor
*/
public function getFormattedDurationAttribute(): ?string
{
if (! $this->duration) {
return null;
}
$minutes = floor($this->duration / 60);
$seconds = $this->duration % 60;
return sprintf('%02d:%02d', $minutes, $seconds);
}
/**
* 포맷된 file size Accessor
*/
public function getFormattedFileSizeAttribute(): ?string
{
if (! $this->file_size) {
return null;
}
if ($this->file_size < 1024) {
return $this->file_size.' B';
} elseif ($this->file_size < 1024 * 1024) {
return round($this->file_size / 1024, 1).' KB';
} else {
return round($this->file_size / (1024 * 1024), 1).' MB';
}
}
/**
* 테넌트 + 시나리오 타입으로 조회
*/
public static function getByTenantAndType(int $tenantId, string $scenarioType, ?int $stepId = null)
{
$query = self::where('tenant_id', $tenantId)
->where('scenario_type', $scenarioType)
->with('creator')
->orderBy('created_at', 'desc');
if ($stepId !== null) {
$query->where('step_id', $stepId);
}
return $query->get();
}
/**
* 시나리오 타입 스코프
*/
public function scopeByScenarioType($query, string $type)
{
return $query->where('scenario_type', $type);
}
/**
* 상담 유형 스코프
*/
public function scopeByType($query, string $type)
{
return $query->where('consultation_type', $type);
}
/**
* 텍스트만 스코프
*/
public function scopeTextOnly($query)
{
return $query->where('consultation_type', self::TYPE_TEXT);
}
/**
* 오디오만 스코프
*/
public function scopeAudioOnly($query)
{
return $query->where('consultation_type', self::TYPE_AUDIO);
}
/**
* 파일만 스코프
*/
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();
}
}