format('Y'); $currentMonth = (int) $today->format('m'); $currentQuarterNum = (int) ceil($currentMonth / 3); $currentQuarterCode = sprintf('CA200Q%02d', $currentQuarterNum); $modalQuarterLabel = substr((string)$currentYear, 2) . '년 ' . $currentQuarterNum . '분기)'; // DB에서 추가 정보 보충 (member_id + sys_comp_code 복합키 기준) if (isset($memberId) && $memberId !== null && $memberId !== '' && $userCompany !== '' && ($userPosition === '' || $userName === '' || $userName === '사용자')) { try { $stmt = db_conn()->prepare( 'SELECT name, rank_name, sys_comp_code FROM edu_users WHERE member_id = ? AND sys_comp_code = ? LIMIT 1' ); $stmt->execute([(string)$memberId, (string)$userCompany]); $row = $stmt->fetch(PDO::FETCH_ASSOC); if ($row) { if ($userName === '' || $userName === '사용자') { $userName = $row['name'] ?? '사용자'; } if ($userPosition === '') { $userPosition = $row['rank_name'] ?? ''; } } } catch (Throwable $e) { error_log('[myclass.php] DB fetch error: ' . $e->getMessage()); } } // ========== 목표 설정 여부 확인 → 이미 설정한 사용자는 myclass_list.php로 바로 이동 ========== // mode=initial(기본 진입)일 때만 체크. completed/additional 모드는 목표 완료 플로우이므로 통과. if ($mode === 'initial' && isset($memberId) && !$debugRecs) { try { $sysCompCodeCheck = (string)($userCompany ?? ''); $hasGoal = false; if ($memberId !== null && $memberId !== '' && $sysCompCodeCheck !== '') { $stmtGoalCheck = db_conn()->prepare( "SELECT COUNT(*) FROM edu_user_learning_goals WHERE member_id = ? AND sys_comp_code = ? AND quarter = ? AND is_active = '1' LIMIT 1" ); $stmtGoalCheck->execute([(string)$memberId, $sysCompCodeCheck, $currentQuarterCode]); $hasGoal = (int)$stmtGoalCheck->fetchColumn() > 0; } if ($hasGoal) { header('Location: /skin/myclass_list.php'); exit; } } catch (Throwable $e) { error_log('[myclass.php] goal check error: ' . $e->getMessage()); // 오류 시 그냥 myclass.php 렌더링 계속 } } // 분기 마지막 날짜 $quarterEndDates = [ 1 => '03-31', 2 => '06-30', 3 => '09-30', 4 => '12-31' ]; $todayDateOnly = (clone $today)->setTime(0, 0, 0); $targetDateStr = $currentYear . '-' . $quarterEndDates[$currentQuarterNum]; $targetDate = new DateTime($targetDateStr, new DateTimeZone('Asia/Seoul')); $targetDate->setTime(0, 0, 0); $daysInterval = $todayDateOnly->diff($targetDate); $daysRemaining = max(0, (int) $daysInterval->format('%a')); $daysDisplay = 'D-' . $daysRemaining; // 분기 마지막 날짜 (한글) $endDateParts = explode('-', $quarterEndDates[$currentQuarterNum]); $endMonth = (int) $endDateParts[0]; $endDay = (int) $endDateParts[1]; $targetDateKr = $endMonth . '월 ' . $endDay . '일'; $completedGoalId = max(1, min(6, $completedGoalId)); $defaultGoals = [ ['id' => 1, 'title' => '자기이해와 강점 찾기', 'desc' => '나의 성향, 가치관, 강점 등을 탐색하고 이해하며 자기인식을 높이는 과정', 'icon' => '01', 'gif' => 'ico_study_01.png', 'json' => 'ico_study_01.json'], ['id' => 2, 'title' => '효과적인\n의사소통 배우기', 'desc' => '상황에 맞는 말하기와 경청을 통해 타인과 원활하게 소통하는 방법을 익히는 과정', 'icon' => '02', 'gif' => 'ico_study_02.png', 'json' => 'ico_study_02.json'], ['id' => 3, 'title' => '감정 조절과 자기관리', 'desc' => '다양한 감정을 인식하고 조절하여 안정적인 삶을 유지하는 자기관리 훈련 과정', 'icon' => '03', 'gif' => 'ico_study_03.png', 'json' => 'ico_study_03.json'], ['id' => 4, 'title' => '비판적 사고와 문제 해결', 'desc' => '다양한 관점에서 생각하고 문제 상황을 논리적으로 해결하는 능력을 기르는 과정', 'icon' => '04', 'gif' => 'ico_study_04.png', 'json' => 'ico_study_04.json'], ['id' => 5, 'title' => '협업과 팀워크 기르기', 'desc' => '타인과 협력하며 공동의 목표를 위해 함께 노력하는 태도와 기술을 기르는 과정', 'icon' => '05', 'gif' => 'ico_study_05.png', 'json' => 'ico_study_05.json'], ['id' => 6, 'title' => '커리어 탐색과 역량 개발', 'desc' => '직무와 산업의 흐름을 이해하고, 자신의 커리어 방향성과 필요한 역량을 점검·계획하는 과정', 'icon' => '06', 'gif' => 'ico_study_06.png', 'json' => 'ico_study_06.json'], ]; $defaultModalBooks = [ ['id' => 1, 'youtube' => 'KE_MeQZgnPM', 'title' => '조직도가 리셋된다!', 'sub' => '위계 중심 조직은\n더 이상 통하지 않습니다.', 'main' => '더 유연하게\n일할 방법', 'tag' => 'IT 테크', 'img' => 'img_book_01'], ['id' => 2, 'youtube' => 'a2l1uZfsRi0', 'title' => '혈당 스파이크', 'sub' => '식후 졸림은 의지 문제가\n아니라 혈당의 문제!', 'main' => '혈당을 안정시켜줄\n실생활 관리법', 'tag' => '웰니스', 'img' => 'img_book_02'], ['id' => 3, 'youtube' => 'IeF8r0ycgVg', 'title' => '제대로 쉬는 방법', 'sub' => '아무리 자도\n피곤한가요?', 'main' => '진짜 회복되는 쉼이\n무엇인지 알게됩니다.', 'tag' => '마인드셋', 'img' => 'img_book_03'], ['id' => 4, 'youtube' => 'KMZXMI0QPoA', 'title' => "'AI 도파민'의 바다에 빠진 이유", 'sub' => 'AI는 선택이 아닌\n생존의 도구', 'main' => 'AI는 도구가 아닌\n업무 파트너', 'tag' => 'IT 테크', 'img' => 'img_book_04'], ['id' => 5, 'youtube' => 'CRKwszz6l2M', 'title' => '살찌고 망가진몸 되살리는 방법', 'sub' => '몸을 망쳤다면?\n다이어트 아닌 리셋이 답!', 'main' => '건강한 몸 되찾기', 'tag' => '웰니스', 'img' => 'img_book_05'], ['id' => 6, 'youtube' => 'Gf5WoZ3BmgI', 'title' => '자기 관점의 힘', 'sub' => '당신의 삶을 바꾸는\n가장 강력한 무기?', 'main' => '자신만의 관점으로\n경쟁력을 키우는 방법', 'tag' => '리더십', 'img' => 'img_book_06'], ]; $goals = $defaultGoals; $goalCodeById = []; $defaultRecommendedItems = [ ['title' => '업무에 익숙해졌지만 성장 정체를 느끼는 구성원', 'description' => "창의적 사고를 '아이디어가 아니라 업무를 바라보는 방식으로 재정렬 할 수 있어요!"], ['title' => '문제 해결을 요구받기 시작한 주니어, 중급 구성원', 'description' => "창의적 사고를 '아이디어가 아니라 업무를 바라보는 방식으로 재정렬 할 수 있어요!"], ['title' => '팀 또는 프로젝트 단위로 사고의 확장이 필요한 구성원', 'description' => "창의적 사고를 '아이디어가 아니라 업무를 바라보는 방식으로 재정렬 할 수 있어요!"], ]; $goalRecommendations = []; for ($i = 1; $i <= 6; $i++) { $goalRecommendations[$i] = $defaultRecommendedItems; } $goalBooksById = []; for ($i = 1; $i <= 6; $i++) { $goalBooksById[$i] = []; } function myclass_extract_video_id($value) { $value = trim((string)$value); if ($value === '') { return ''; } if (preg_match('~(?:v=|\.be/)([A-Za-z0-9_-]{11})~', $value, $m)) { return $m[1]; } if (preg_match('/^[A-Za-z0-9_-]{11}$/', $value)) { return $value; } return ''; } // 목표 카드 6개: edu_learning_goals.title / edu_learning_goals.remarks 연동 try { $stmtGoals = db_conn()->prepare( "SELECT goal_code, title AS goal_title, remarks AS goal_remarks FROM edu_learning_goals WHERE is_active = '1' AND base_year = ? AND quarter = ? ORDER BY goal_code ASC" ); $stmtGoals->execute([(string)$currentYear, $currentQuarterCode]); $goalRows = $stmtGoals->fetchAll(PDO::FETCH_ASSOC); // 현재 연도/분기 데이터가 부족하면 동일 분기의 최신 연도로 fallback if (count($goalRows) < 6) { $stmtGoals = db_conn()->query( "SELECT goal_code, title AS goal_title, remarks AS goal_remarks FROM edu_learning_goals WHERE is_active = '1' AND quarter = " . db_conn()->quote($currentQuarterCode) . " ORDER BY base_year DESC, goal_code ASC LIMIT 6" ); $goalRows = $stmtGoals->fetchAll(PDO::FETCH_ASSOC); } $goalSlot = 1; foreach ($goalRows as $goalRow) { if ($goalSlot > 6) { break; } $goalCode = (string)($goalRow['goal_code'] ?? ''); $title = trim((string)($goalRow['goal_title'] ?? '')); $desc = trim((string)($goalRow['goal_remarks'] ?? '')); if ($goalCode === '') { continue; } $goalId = $goalSlot; $goalCodeById[$goalId] = $goalCode; if ($title !== '') { $goals[$goalId - 1]['title'] = $title; } if ($desc !== '') { $goals[$goalId - 1]['desc'] = $desc; } $goalSlot++; } } catch (Throwable $e) { error_log('[myclass.php] edu_learning_goals fetch error: ' . $e->getMessage()); } // 목표별 추천 3개: goal_code + seq 기준으로 연동 (rec-text는 title2 우선 사용) try { $goalCodes = array_values(array_unique(array_filter($goalCodeById))); if (count($goalCodes) > 0) { $normalizeGoalCode = static function ($value) { return strtoupper(trim((string)$value)); }; $goalIdByCode = []; foreach ($goalCodeById as $goalId => $goalCodeRaw) { $normalizedCode = $normalizeGoalCode($goalCodeRaw); if ($normalizedCode === '') { continue; } $goalIdByCode[$normalizedCode] = (int)$goalId; } $normalizedGoalCodes = []; foreach ($goalCodes as $goalCodeRaw) { $normalizedCode = $normalizeGoalCode($goalCodeRaw); if ($normalizedCode !== '') { $normalizedGoalCodes[] = $normalizedCode; } } $normalizedGoalCodes = array_values(array_unique($normalizedGoalCodes)); if (count($normalizedGoalCodes) === 0) { throw new RuntimeException('goal codes are empty after normalization'); } $goalSuffixes = []; foreach (array_keys($goalIdByCode) as $normalizedCode) { if (preg_match('/-(\d{3})$/', $normalizedCode, $m)) { $goalSuffixes[] = '-' . $m[1]; } } $goalSuffixes = array_values(array_unique($goalSuffixes)); $placeholders = implode(',', array_fill(0, count($normalizedGoalCodes), '?')); $suffixPlaceholders = count($goalSuffixes) > 0 ? implode(',', array_fill(0, count($goalSuffixes), '?')) : ''; $pdo = db_conn(); $hasColumn = static function (PDO $conn, $tableName, $columnName) { $stmt = $conn->prepare( "SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = ? AND column_name = ?" ); $stmt->execute([(string)$tableName, (string)$columnName]); return ((int)$stmt->fetchColumn()) > 0; }; $hasTable = static function (PDO $conn, $tableName) { $stmt = $conn->prepare( "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = ?" ); $stmt->execute([(string)$tableName]); return ((int)$stmt->fetchColumn()) > 0; }; // 운영 환경별 오탈자 대응: edu_recommended_goals / edu_recommended_gaols // 둘 다 존재할 수 있으므로, 실제 현재 목표코드와 매칭되는 row 수가 많은 테이블을 선택 $availableRecTables = []; foreach (['edu_recommended_goals', 'edu_recommended_gaols'] as $candTable) { if ($hasTable($pdo, $candTable)) { $availableRecTables[] = $candTable; } } if (count($availableRecTables) === 0) { throw new RuntimeException('recommended goals table not found'); } $tableScores = []; $recTable = $availableRecTables[0]; $bestScore = -1; foreach ($availableRecTables as $candTable) { $candHasQuarter = $hasColumn($pdo, $candTable, 'quarter'); $candHasBaseYear = $hasColumn($pdo, $candTable, 'base_year'); $candHasIsActive = $hasColumn($pdo, $candTable, 'is_active'); $candCodeCondition = "UPPER(TRIM(goal_code)) IN ($placeholders)"; if ($suffixPlaceholders !== '') { $candCodeCondition .= " OR RIGHT(UPPER(TRIM(goal_code)), 4) IN ($suffixPlaceholders)"; } $candWhere = []; if ($candHasIsActive) { $candWhere[] = "is_active = '1'"; } $candWhere[] = "({$candCodeCondition})"; if ($candHasQuarter) { $candWhere[] = 'quarter = ?'; } if ($candHasBaseYear) { $candWhere[] = 'base_year = ?'; } $candSql = "SELECT COUNT(*) FROM {$candTable} WHERE " . implode(' AND ', $candWhere); $candParams = $normalizedGoalCodes; if (count($goalSuffixes) > 0) { $candParams = array_merge($candParams, $goalSuffixes); } if ($candHasQuarter) { $candParams[] = $currentQuarterCode; } if ($candHasBaseYear) { $candParams[] = (string)$currentYear; } $stmtScore = $pdo->prepare($candSql); $stmtScore->execute($candParams); $score = (int)$stmtScore->fetchColumn(); $tableScores[$candTable] = $score; if ($score > $bestScore) { $bestScore = $score; $recTable = $candTable; } } $hasTitle2Col = $hasColumn($pdo, $recTable, 'title2'); $hasQuarterCol = $hasColumn($pdo, $recTable, 'quarter'); $hasBaseYearCol = $hasColumn($pdo, $recTable, 'base_year'); $hasIsActiveCol = $hasColumn($pdo, $recTable, 'is_active'); $hasSeqCol = $hasColumn($pdo, $recTable, 'seq'); $hasSortOrderCol = $hasColumn($pdo, $recTable, 'sort_order'); $hasDescCol = $hasColumn($pdo, $recTable, 'description'); $title2Expr = $hasTitle2Col ? 'rg.title2' : "''"; $recDescExpr = $hasDescCol ? 'rg.description' : "''"; $seqExpr = $hasSeqCol ? 'rg.seq' : ($hasSortOrderCol ? 'rg.sort_order' : '0'); $joinSeqCondition = ''; if ($hasSeqCol) { $joinSeqCondition = ' AND c.sort_order = rg.seq'; } elseif ($hasSortOrderCol) { $joinSeqCondition = ' AND c.sort_order = rg.sort_order'; } $codeCondition = "UPPER(TRIM(rg.goal_code)) IN ($placeholders)"; if ($suffixPlaceholders !== '') { $codeCondition .= " OR RIGHT(UPPER(TRIM(rg.goal_code)), 4) IN ($suffixPlaceholders)"; } $whereConditions = []; if ($hasIsActiveCol) { $whereConditions[] = "rg.is_active = '1'"; } $whereConditions[] = "({$codeCondition})"; if ($hasQuarterCol) { $whereConditions[] = 'rg.quarter = ?'; } if ($hasBaseYearCol) { $whereConditions[] = 'rg.base_year = ?'; } $whereSql = implode("\n AND ", $whereConditions); $stmtRec = $pdo->prepare( "SELECT rg.goal_code, {$seqExpr} AS seq, rg.title AS rec_title, {$recDescExpr} AS rec_description, {$title2Expr} AS rec_title2, c.title AS content_title, c.description AS content_description FROM {$recTable} rg LEFT JOIN edu_contents c ON c.goal_code = rg.goal_code {$joinSeqCondition} AND c.is_active = '1' WHERE {$whereSql} ORDER BY rg.goal_code ASC, seq ASC" ); $stmtRecParams = $normalizedGoalCodes; if (count($goalSuffixes) > 0) { $stmtRecParams = array_merge($stmtRecParams, $goalSuffixes); } if ($hasQuarterCol) { $stmtRecParams[] = $currentQuarterCode; } if ($hasBaseYearCol) { $stmtRecParams[] = (string)$currentYear; } $stmtRec->execute($stmtRecParams); $recRows = $stmtRec->fetchAll(PDO::FETCH_ASSOC); if ($debugRecs) { $debugRecsPayload['table'] = $recTable; $debugRecsPayload['tableScores'] = $tableScores; $debugRecsPayload['schema'] = [ 'hasTitle2Col' => $hasTitle2Col, 'hasQuarterCol' => $hasQuarterCol, 'hasBaseYearCol' => $hasBaseYearCol, 'hasIsActiveCol' => $hasIsActiveCol, 'hasSeqCol' => $hasSeqCol, 'hasSortOrderCol' => $hasSortOrderCol, 'hasDescCol' => $hasDescCol, ]; $debugRecsPayload['normalizedGoalCodes'] = $normalizedGoalCodes; $debugRecsPayload['stmtRecParams'] = $stmtRecParams; $debugRecsPayload['recRows'] = $recRows; error_log('[myclass.php][debug_recs] table=' . $recTable . ' normalizedGoalCodes=' . json_encode($normalizedGoalCodes, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); error_log('[myclass.php][debug_recs] recRows=' . json_encode($recRows, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); } $fallbackSeqByGoalId = []; foreach ($recRows as $recRow) { $goalCode = $normalizeGoalCode($recRow['goal_code'] ?? ''); $seq = (int)($recRow['seq'] ?? 0); $recTitle = trim((string)($recRow['rec_title'] ?? '')); $recTitle2 = trim((string)($recRow['rec_title2'] ?? '')); $recDesc = trim((string)($recRow['rec_description'] ?? '')); $contentTitle = trim((string)($recRow['content_title'] ?? '')); $goalId = 0; if ($goalCode !== '' && isset($goalIdByCode[$goalCode])) { $goalId = (int)$goalIdByCode[$goalCode]; } elseif (preg_match('/-(\d{3})$/', $goalCode, $m)) { $suffixGoalId = (int)$m[1]; if ($suffixGoalId >= 1 && $suffixGoalId <= 6) { $goalId = $suffixGoalId; } } if ($goalId < 1 || $goalId > 6) { continue; } if ($seq < 1 || $seq > 3) { $fallbackSeqByGoalId[$goalId] = (int)($fallbackSeqByGoalId[$goalId] ?? 0) + 1; $seq = $fallbackSeqByGoalId[$goalId]; if ($seq < 1 || $seq > 3) { continue; } } if ($recTitle === '') { $recTitle = $contentTitle; } if ($recTitle2 !== '') { $recDesc = $recTitle2; } if ($recTitle !== '') { $goalRecommendations[$goalId][$seq - 1]['title'] = $recTitle; } if ($recDesc !== '') { $goalRecommendations[$goalId][$seq - 1]['description'] = $recDesc; } if ($debugRecs) { error_log('[myclass.php][debug_recs] mapped goalCode=' . $goalCode . ' seq=' . $seq . ' title=' . $recTitle . ' title2=' . $recTitle2 . ' desc=' . $recDesc); } } } } catch (Throwable $e) { if ($debugRecs) { $debugRecsPayload['recsError'] = $e->getMessage(); } error_log('[myclass.php] edu_recommended_goals fetch error: ' . $e->getMessage()); } // 목표별 영상 리스트: edu_contents.title/description1/description2/content_url/thumbnail 연동 try { $goalCodes = array_values(array_unique(array_filter($goalCodeById))); if (count($goalCodes) > 0) { $goalIdByCode = array_flip($goalCodeById); $placeholders = implode(',', array_fill(0, count($goalCodes), '?')); $stmtContents = db_conn()->prepare( "SELECT content_id, goal_code, sort_order, title, description, description1, description2, content_url, thumbnail_url FROM edu_contents WHERE is_active = '1' AND category_code = 'CA10001' AND category_group = ? AND goal_code IN ($placeholders) ORDER BY goal_code ASC, sort_order ASC, content_id ASC" ); $stmtContents->execute(array_merge([$currentQuarterCode], $goalCodes)); $contentRows = $stmtContents->fetchAll(PDO::FETCH_ASSOC); foreach ($contentRows as $contentRow) { $goalCode = (string)($contentRow['goal_code'] ?? ''); if (!isset($goalIdByCode[$goalCode])) { continue; } $goalId = (int)$goalIdByCode[$goalCode]; if (!isset($goalBooksById[$goalId])) { $goalBooksById[$goalId] = []; } $contentUrl = trim((string)($contentRow['content_url'] ?? '')); $thumbnailUrl = trim((string)($contentRow['thumbnail_url'] ?? '')); $sortOrder = (int)($contentRow['sort_order'] ?? 0); if ($sortOrder < 1 || $sortOrder > 6) { continue; } $templateBook = $defaultModalBooks[$sortOrder - 1] ?? null; if ($templateBook === null) { continue; } if (count($goalBooksById[$goalId]) === 0) { $goalBooksById[$goalId] = $defaultModalBooks; } $description1 = trim((string)($contentRow['description1'] ?? '')); $description2 = trim((string)($contentRow['description2'] ?? '')); $description = trim((string)($contentRow['description'] ?? '')); if ($description1 === '') { $description1 = $description; } $goalBooksById[$goalId][$sortOrder - 1] = [ 'id' => (int)($templateBook['id'] ?? $sortOrder), 'order' => $sortOrder, 'youtube' => ($tmpVideoId = myclass_extract_video_id($contentUrl)) !== '' ? $tmpVideoId : (string)($templateBook['youtube'] ?? ''), 'content_url' => $contentUrl, 'title' => trim((string)($contentRow['title'] ?? (string)($templateBook['title'] ?? ''))), 'sub' => $description1 !== '' ? $description1 : (string)($templateBook['sub'] ?? ''), 'main' => $description2 !== '' ? $description2 : (string)($templateBook['main'] ?? ''), 'tag' => (string)($templateBook['tag'] ?? ''), 'img' => (string)($templateBook['img'] ?? 'img_book_01'), 'thumbnail' => $thumbnailUrl, ]; } foreach ($goalBooksById as $goalId => $goalBooks) { if (count($goalBooks) > 0) { ksort($goalBooks); $goalBooksById[$goalId] = array_values($goalBooks); } else { $goalBooksById[$goalId] = $defaultModalBooks; } } } } catch (Throwable $e) { error_log('[myclass.php] edu_contents fetch error: ' . $e->getMessage()); } foreach ($goalBooksById as $goalId => $goalBooks) { if (count($goalBooks) === 0) { $goalBooksById[$goalId] = $defaultModalBooks; } } $modalBooks = $goalBooksById[1] ?? $defaultModalBooks; $modalRecItems = $goalRecommendations[1] ?? $defaultRecommendedItems; if ($debugRecs) { $debugRecsPayload['goalCodeById'] = $goalCodeById; $debugRecsPayload['goalRecommendations1'] = $goalRecommendations[1] ?? []; $debugRecsPayload['modalRecItems'] = $modalRecItems; error_log('[myclass.php][debug_recs] goalCodeById=' . json_encode($goalCodeById, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); error_log('[myclass.php][debug_recs] goalRecommendations[1]=' . json_encode($goalRecommendations[1] ?? [], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); error_log('[myclass.php][debug_recs] modalRecItems=' . json_encode($modalRecItems, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); } // 실제 완료된 목표 목록 조회 (DB 기준) $completedGoalIds = []; if (isset($memberId) && $userCompany !== '') { try { $stmtCompleted = db_conn()->prepare( "SELECT g.goal_code, g.title, g.remarks, u.completed_date, u.goal_code AS user_goal_code FROM edu_user_learning_goals u JOIN edu_learning_goals g ON u.goal_code = g.goal_code AND g.is_active = '1' WHERE u.member_id = ? AND u.sys_comp_code = ? AND u.is_active = '1' AND g.quarter = ? AND u.completed_date IS NOT NULL" ); $stmtCompleted->execute([(string)$memberId, (string)$userCompany, $currentQuarterCode]); $rowsCompleted = $stmtCompleted->fetchAll(PDO::FETCH_ASSOC); foreach ($rowsCompleted as $row) { // goalCodeById: [1=>goal_code1, 2=>goal_code2, ...] $goalCode = (string)($row['user_goal_code'] ?? $row['goal_code'] ?? ''); $goalId = array_search($goalCode, $goalCodeById, true); if ($goalId !== false) { $completedGoalIds[] = (int)$goalId; } } } catch (Throwable $e) { error_log('[myclass.php] fetch completed goals error: ' . $e->getMessage()); } } // completed/additional 화면은 DB completed_date(트리거 결과)가 확인될 때만 허용 $isTriggeredCompletion = in_array($completedGoalId, $completedGoalIds, true); if (in_array($mode, ['completed', 'additional'], true) && !$isTriggeredCompletion) { $mode = 'initial'; } $completedGoal = $goals[$completedGoalId - 1]; $isCompletionFlow = in_array($mode, ['completed', 'additional'], true); ?>