'integer', 'checkpoint_index' => 'integer', 'is_checked' => 'boolean', 'checked_at' => 'datetime', ]; /** * 테넌트 관계 */ public function tenant(): BelongsTo { return $this->belongsTo(Tenant::class); } /** * 체크한 사용자 관계 */ public function checkedByUser(): BelongsTo { return $this->belongsTo(User::class, 'checked_by'); } /** * 사용자 관계 (하위 호환성) */ public function user(): BelongsTo { return $this->belongsTo(User::class, 'user_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; } /** * 특정 테넌트/시나리오의 체크리스트 조회 */ 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 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 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; foreach ($steps as $step) { $total += count($step['checkpoints'] ?? []); } // 완료된 체크포인트 수 (DB에서 조회) $completed = self::where('tenant_id', $tenantId) ->where('scenario_type', $scenarioType) ->where('is_checked', true) ->count(); $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'), ]; } }