'integer', 'checkpoint_index' => 'integer', 'is_checked' => 'boolean', 'checked_at' => 'datetime', ]; /** * 테넌트 관계 */ public function tenant(): BelongsTo { return $this->belongsTo(Tenant::class); } /** * 가망고객 관계 */ public function tenantProspect(): BelongsTo { return $this->belongsTo(TenantProspect::class, 'tenant_prospect_id'); } /** * 체크한 사용자 관계 */ public function checkedByUser(): BelongsTo { return $this->belongsTo(User::class, 'checked_by'); } /** * 사용자 관계 (하위 호환성) */ public function user(): BelongsTo { return $this->belongsTo(User::class, 'user_id'); } /** * 체크포인트 토글 (tenant_id 기반) */ public static function toggle(int $tenantId, string $scenarioType, int $stepId, string $checkpointId, bool $checked, ?int $userId = null): self { $currentUserId = $userId ?? auth()->id(); $checklist = self::firstOrNew([ 'tenant_id' => $tenantId, 'scenario_type' => $scenarioType, 'step_id' => $stepId, 'checkpoint_id' => $checkpointId, ]); // 새 레코드인 경우 필수 필드 설정 if (!$checklist->exists) { $checklist->user_id = $currentUserId; $checklist->checkpoint_index = 0; // 기본값 } $checklist->is_checked = $checked; $checklist->checked_at = $checked ? now() : null; $checklist->checked_by = $checked ? $currentUserId : null; $checklist->save(); return $checklist; } /** * 체크포인트 토글 (prospect_id 기반) */ public static function toggleByProspect(int $prospectId, string $scenarioType, int $stepId, string $checkpointId, bool $checked, ?int $userId = null): self { $currentUserId = $userId ?? auth()->id(); $checklist = self::firstOrNew([ 'tenant_prospect_id' => $prospectId, 'scenario_type' => $scenarioType, 'step_id' => $stepId, 'checkpoint_id' => $checkpointId, ]); // 새 레코드인 경우 필수 필드 설정 if (!$checklist->exists) { $checklist->user_id = $currentUserId; $checklist->checkpoint_index = 0; } $checklist->is_checked = $checked; $checklist->checked_at = $checked ? now() : null; $checklist->checked_by = $checked ? $currentUserId : null; $checklist->save(); // 체크 완료 시 진행률 100% 확인 후 상태 자동 전환 if ($checked) { self::checkAndConvertProspectStatus($prospectId, $currentUserId); } return $checklist; } /** * 진행률 100% 시 가망고객 상태를 '계약완료'로 자동 전환 */ public static function checkAndConvertProspectStatus(int $prospectId, ?int $userId = null): bool { $progress = self::getProspectProgress($prospectId); // 영업 시나리오와 매니저 시나리오 모두 100%인 경우에만 전환 if ($progress['sales']['percentage'] === 100 && $progress['manager']['percentage'] === 100) { $prospect = TenantProspect::find($prospectId); if ($prospect && $prospect->status !== TenantProspect::STATUS_CONVERTED) { $prospect->update([ 'status' => TenantProspect::STATUS_CONVERTED, 'converted_at' => now(), 'converted_by' => $userId ?? auth()->id(), ]); return true; } } return false; } /** * 특정 테넌트/시나리오의 체크리스트 조회 */ public static function getChecklist(int $tenantId, string $scenarioType): array { $items = self::where('tenant_id', $tenantId) ->where('scenario_type', $scenarioType) ->where('is_checked', true) ->get(); $result = []; foreach ($items as $item) { $key = "{$item->step_id}_{$item->checkpoint_id}"; $result[$key] = [ 'checked_at' => $item->checked_at?->toDateTimeString(), 'checked_by' => $item->checked_by, ]; } return $result; } /** * 특정 가망고객/시나리오의 체크리스트 조회 */ public static function getChecklistByProspect(int $prospectId, string $scenarioType): array { $items = self::where('tenant_prospect_id', $prospectId) ->where('scenario_type', $scenarioType) ->where('is_checked', true) ->get(); $result = []; foreach ($items as $item) { $key = "{$item->step_id}_{$item->checkpoint_id}"; $result[$key] = [ 'checked_at' => $item->checked_at?->toDateTimeString(), 'checked_by' => $item->checked_by, ]; } return $result; } /** * 진행률 계산 */ public static function calculateProgress(int $tenantId, string $scenarioType, array $steps): array { $checklist = self::getChecklist($tenantId, $scenarioType); $totalCheckpoints = 0; $completedCheckpoints = 0; $stepProgress = []; foreach ($steps as $step) { $stepCompleted = 0; $stepTotal = count($step['checkpoints'] ?? []); $totalCheckpoints += $stepTotal; foreach ($step['checkpoints'] as $checkpoint) { $key = "{$step['id']}_{$checkpoint['id']}"; if (isset($checklist[$key])) { $completedCheckpoints++; $stepCompleted++; } } $stepProgress[$step['id']] = [ 'total' => $stepTotal, 'completed' => $stepCompleted, 'percentage' => $stepTotal > 0 ? round(($stepCompleted / $stepTotal) * 100) : 0, ]; } return [ 'total' => $totalCheckpoints, 'completed' => $completedCheckpoints, 'percentage' => $totalCheckpoints > 0 ? round(($completedCheckpoints / $totalCheckpoints) * 100) : 0, 'steps' => $stepProgress, ]; } /** * 가망고객 기반 진행률 계산 */ public static function calculateProgressByProspect(int $prospectId, string $scenarioType, array $steps): array { $checklist = self::getChecklistByProspect($prospectId, $scenarioType); $totalCheckpoints = 0; $completedCheckpoints = 0; $stepProgress = []; foreach ($steps as $step) { $stepCompleted = 0; $stepTotal = count($step['checkpoints'] ?? []); $totalCheckpoints += $stepTotal; foreach ($step['checkpoints'] as $checkpoint) { $key = "{$step['id']}_{$checkpoint['id']}"; if (isset($checklist[$key])) { $completedCheckpoints++; $stepCompleted++; } } $stepProgress[$step['id']] = [ 'total' => $stepTotal, 'completed' => $stepCompleted, 'percentage' => $stepTotal > 0 ? round(($stepCompleted / $stepTotal) * 100) : 0, ]; } return [ 'total' => $totalCheckpoints, 'completed' => $completedCheckpoints, 'percentage' => $totalCheckpoints > 0 ? round(($completedCheckpoints / $totalCheckpoints) * 100) : 0, 'steps' => $stepProgress, ]; } /** * 시나리오 타입 스코프 */ public function scopeByScenarioType($query, string $type) { return $query->where('scenario_type', $type); } /** * 체크된 항목만 스코프 */ public function scopeChecked($query) { return $query->where('is_checked', true); } /** * 간단한 진행률 계산 (전체 체크포인트 수 기준) * * @param int $tenantId * @param string $scenarioType 'sales' 또는 'manager' * @return array ['completed' => 완료 수, 'total' => 전체 수, 'percentage' => 백분율] */ public static function getSimpleProgress(int $tenantId, string $scenarioType): array { // 전체 체크포인트 수 (config에서 계산) $configKey = $scenarioType === 'sales' ? 'sales_scenario.sales_steps' : 'sales_scenario.manager_steps'; $steps = config($configKey, []); $total = 0; $validCheckpointKeys = []; // config에 정의된 유효한 체크포인트만 수집 foreach ($steps as $step) { foreach ($step['checkpoints'] ?? [] as $checkpoint) { $total++; $validCheckpointKeys[] = "{$step['id']}_{$checkpoint['id']}"; } } // 완료된 체크포인트 수 (config에 존재하는 것만 카운트) $checkedItems = self::where('tenant_id', $tenantId) ->where('scenario_type', $scenarioType) ->where('is_checked', true) ->get(); $completed = 0; foreach ($checkedItems as $item) { $key = "{$item->step_id}_{$item->checkpoint_id}"; if (in_array($key, $validCheckpointKeys)) { $completed++; } } $percentage = $total > 0 ? round(($completed / $total) * 100) : 0; return [ 'completed' => $completed, 'total' => $total, 'percentage' => $percentage, ]; } /** * 테넌트별 영업/매니저 진행률 한번에 조회 */ public static function getTenantProgress(int $tenantId): array { return [ 'sales' => self::getSimpleProgress($tenantId, 'sales'), 'manager' => self::getSimpleProgress($tenantId, 'manager'), ]; } /** * 가망고객별 간단한 진행률 계산 */ public static function getSimpleProgressByProspect(int $prospectId, string $scenarioType): array { $configKey = $scenarioType === 'sales' ? 'sales_scenario.sales_steps' : 'sales_scenario.manager_steps'; $steps = config($configKey, []); $total = 0; $validCheckpointKeys = []; foreach ($steps as $step) { foreach ($step['checkpoints'] ?? [] as $checkpoint) { $total++; $validCheckpointKeys[] = "{$step['id']}_{$checkpoint['id']}"; } } $checkedItems = self::where('tenant_prospect_id', $prospectId) ->where('scenario_type', $scenarioType) ->where('is_checked', true) ->get(); $completed = 0; foreach ($checkedItems as $item) { $key = "{$item->step_id}_{$item->checkpoint_id}"; if (in_array($key, $validCheckpointKeys)) { $completed++; } } $percentage = $total > 0 ? round(($completed / $total) * 100) : 0; return [ 'completed' => $completed, 'total' => $total, 'percentage' => $percentage, ]; } /** * 가망고객별 영업/매니저 진행률 한번에 조회 */ public static function getProspectProgress(int $prospectId): array { return [ 'sales' => self::getSimpleProgressByProspect($prospectId, 'sales'), 'manager' => self::getSimpleProgressByProspect($prospectId, 'manager'), ]; } }