Initial commit: 교육 프로젝트 배포
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
require __DIR__ . '/../../bbs/db_conn.php';
|
||||
|
||||
// PDO 연결 생성
|
||||
$pdo = db_conn();
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
$type = isset($_GET['type']) ? $_GET['type'] : '';
|
||||
|
||||
/* 콘텐츠 상세(수정 모달용) */
|
||||
if ($type === 'content_detail') {
|
||||
$content_id = isset($_GET['content_id']) ? trim($_GET['content_id']) : '';
|
||||
if ($content_id === '') {
|
||||
echo json_encode(['success' => false, 'message' => 'content_id 가 필요합니다.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("SELECT * FROM edu_contents WHERE content_id = :c LIMIT 1");
|
||||
$stmt->execute([':c' => $content_id]);
|
||||
$item = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$item) {
|
||||
echo json_encode(['success' => false, 'message' => '콘텐츠를 찾을 수 없습니다.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$json = json_encode(['success' => true, 'item' => $item], JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE);
|
||||
if ($json === false) {
|
||||
echo json_encode(['success' => false, 'message' => 'JSON encoding failed: ' . json_last_error_msg()]);
|
||||
} else {
|
||||
echo $json;
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
/* 콘텐츠 포스트잇(메모) 목록 조회 */
|
||||
if ($type === 'content_memos') {
|
||||
$content_id = isset($_GET['content_id']) ? trim($_GET['content_id']) : '';
|
||||
if ($content_id === '') {
|
||||
echo json_encode(['success' => false, 'message' => 'content_id 가 필요합니다.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("SELECT content_id, seq, title, is_active, created_at, updated_at
|
||||
FROM edu_content_memos
|
||||
WHERE content_id = :c
|
||||
ORDER BY seq ASC
|
||||
LIMIT 3");
|
||||
$stmt->execute([':c' => $content_id]);
|
||||
$items = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
echo json_encode(['success' => true, 'items' => $items]);
|
||||
exit;
|
||||
}
|
||||
|
||||
/* 학습목표 추천이유 목록 조회 */
|
||||
if ($type === 'goal_recommends') {
|
||||
$goal_code = isset($_GET['goal_code']) ? trim($_GET['goal_code']) : '';
|
||||
if ($goal_code === '') {
|
||||
echo json_encode(['success' => false, 'message' => 'goal_code 가 필요합니다.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("SELECT goal_code, seq, title, title2, is_active, created_at, updated_at
|
||||
FROM edu_recommended_goals
|
||||
WHERE goal_code = :g
|
||||
ORDER BY seq ASC");
|
||||
$stmt->execute([':g' => $goal_code]);
|
||||
$items = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
echo json_encode(['success' => true, 'items' => $items]);
|
||||
exit;
|
||||
}
|
||||
|
||||
/* 추천 키워드 목록 조회 */
|
||||
if ($type === 'recommend_keywords') {
|
||||
$stmt = $pdo->prepare("SELECT keyword_code FROM edu_recommend_keywords WHERE is_active='1'");
|
||||
$stmt->execute();
|
||||
$items = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||
echo json_encode(['items' => $items]);
|
||||
exit;
|
||||
}
|
||||
|
||||
/* 카테고리 */
|
||||
if ($type === 'category') {
|
||||
$stmt = $pdo->prepare("CALL proc_get_code_list('CA100')");
|
||||
$stmt->execute();
|
||||
echo json_encode(['items' => $stmt->fetchAll()]);
|
||||
exit;
|
||||
}
|
||||
/* 카테고리구분 */
|
||||
if ($type === 'category_group') {
|
||||
$desc01 = isset($_GET['desc01']) ? $_GET['desc01'] : '';
|
||||
$stmt = $pdo->prepare("CALL proc_get_sub_codes('CA200', :d)");
|
||||
$stmt->execute([':d' => $desc01]);
|
||||
echo json_encode(['items' => $stmt->fetchAll()]);
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
/* 새 콘텐츠 등록 학습목표 코드 */
|
||||
if ($type === 'learning_goal') {
|
||||
$year = isset($_GET['year']) ? $_GET['year'] : date('Y');
|
||||
$categoryGroup = isset($_GET['category_group']) ? $_GET['category_group'] : '';
|
||||
|
||||
// 기준년도 + 카테고리구분(중분류)을 함께 사용하는 조회
|
||||
// 카테고리구분(category_group)을 변수 :p 로 받아 edu_learning_goals.quarter 에 매핑합니다.
|
||||
|
||||
$sql = "SELECT goal_code AS code, title AS name
|
||||
FROM edu_learning_goals
|
||||
WHERE base_year = :y
|
||||
AND is_active = '1'";
|
||||
|
||||
$params = [':y' => $year];
|
||||
|
||||
// 카테고리 그룹이 넘어온 경우에만 조건 추가
|
||||
if ($categoryGroup !== '') {
|
||||
$sql .= " AND quarter = :p";
|
||||
$params[':p'] = $categoryGroup;
|
||||
}
|
||||
|
||||
$sql .= " ORDER BY quarter , sort_order";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
echo json_encode(['items' => $stmt->fetchAll()]);
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
/* 학습목표 목록 */
|
||||
if ($type === 'learning_goal_all') {
|
||||
$year = isset($_GET['year']) ? $_GET['year'] : date('Y');
|
||||
$quarter = isset($_GET['quarter']) ? $_GET['quarter'] : '';
|
||||
|
||||
$sql = "SELECT goal_code, title, quarter, fn_get_code_name(quarter) AS quarter_name, is_active, remarks, sort_order, goal_no, fn_get_code_name(goal_no) AS goal_no_name FROM edu_learning_goals WHERE base_year=:y AND is_active='1' ";
|
||||
$params = [':y' => $year];
|
||||
|
||||
if ($quarter !== '') {
|
||||
$sql .= " AND quarter=:q";
|
||||
$params[':q'] = $quarter;
|
||||
}
|
||||
|
||||
$sql .= " ORDER BY quarter DESC";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$items = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
echo json_encode(['items' => $items]);
|
||||
exit;
|
||||
}
|
||||
/* 키워드 */
|
||||
if ($type === 'content_keywords') {
|
||||
$content_id = isset($_GET['content_id']) ? $_GET['content_id'] : '';
|
||||
if ($content_id === '') {
|
||||
echo json_encode(['items' => []]);
|
||||
exit;
|
||||
}
|
||||
$stmt = $pdo->prepare("SELECT keyword_code FROM edu_content_keywords WHERE content_id=:c AND is_active='1'");
|
||||
$stmt->execute([':c' => $content_id]);
|
||||
$items = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||
echo json_encode(['items' => $items]);
|
||||
exit;
|
||||
}
|
||||
/*인사이트 이슈구분 코드*/
|
||||
if ($type === 'issue_type_code') {
|
||||
$stmt = $pdo->prepare("CALL proc_get_code_list('IS100')");
|
||||
$stmt->execute();
|
||||
echo json_encode(['items' => $stmt->fetchAll()]);
|
||||
exit;
|
||||
}
|
||||
echo json_encode(['items' => []]);
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
require __DIR__ . '/../../bbs/db_conn.php';
|
||||
|
||||
// PDO 연결 생성
|
||||
$pdo = db_conn();
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
echo json_encode(['success' => false, 'message' => '잘못된 요청입니다.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$content_id = isset($_POST['content_id']) ? trim($_POST['content_id']) : '';
|
||||
|
||||
if ($content_id === '') {
|
||||
echo json_encode(['success' => false, 'message' => '콘텐츠 ID가 필요합니다.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. 제약사항 검사: 학습이력(edu_learning_histories) 또는 찜목록(edu_content_wishlist) 확인
|
||||
$check_stmt = $pdo->prepare("
|
||||
SELECT
|
||||
(SELECT COUNT(*) FROM edu_learning_histories WHERE content_id = :content_id1) as history_count,
|
||||
(SELECT COUNT(*) FROM edu_content_wishlist WHERE content_id = :content_id2) as wishlist_count
|
||||
");
|
||||
$check_stmt->execute([
|
||||
':content_id1' => $content_id,
|
||||
':content_id2' => $content_id
|
||||
]);
|
||||
$check_result = $check_stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($check_result['history_count'] > 0 || $check_result['wishlist_count'] > 0) {
|
||||
echo json_encode(['success' => false, 'message' => '학습이력이나 찜 목록에 존재하는 콘텐츠는 삭제할 수 없습니다.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 2. 연관 데이터 삭제를 위한 트랜잭션 시작
|
||||
$pdo->beginTransaction();
|
||||
|
||||
// 키워드 삭제
|
||||
$stmt_keywords = $pdo->prepare("DELETE FROM edu_content_keywords WHERE content_id = :content_id");
|
||||
$stmt_keywords->execute([':content_id' => $content_id]);
|
||||
|
||||
// 포스트잇(메모) 삭제
|
||||
$stmt_memos = $pdo->prepare("DELETE FROM edu_content_memos WHERE content_id = :content_id");
|
||||
$stmt_memos->execute([':content_id' => $content_id]);
|
||||
|
||||
// 본 콘텐츠 삭제
|
||||
$stmt_content = $pdo->prepare("DELETE FROM edu_contents WHERE content_id = :content_id");
|
||||
$stmt_content->execute([':content_id' => $content_id]);
|
||||
|
||||
// 3. 트랜잭션 커밋
|
||||
$pdo->commit();
|
||||
|
||||
echo json_encode(['success' => true]);
|
||||
} catch (PDOException $e) {
|
||||
// 오류 발생 시 롤백
|
||||
if ($pdo->inTransaction()) {
|
||||
$pdo->rollBack();
|
||||
}
|
||||
echo json_encode(['success' => false, 'message' => '삭제 실패: ' . $e->getMessage()]);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
<?php
|
||||
require __DIR__ . '/../../bbs/db_conn.php';
|
||||
require_once __DIR__ . '/../../bbs/auth.php';
|
||||
edu_start_session();
|
||||
|
||||
// 로그인 사용자 member_id 추출
|
||||
$login_member_id = $_SESSION['member_id'] ?? null;
|
||||
|
||||
// PDO 연결 생성
|
||||
$pdo = db_conn();
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
header('Location: ../skin/content_upload.php');
|
||||
exit;
|
||||
}
|
||||
function generate_content_id(PDO $pdo): string
|
||||
{
|
||||
$prefix = date('Ym') . '-';
|
||||
$stmt = $pdo->prepare("SELECT MAX(content_id) AS max_id FROM edu_contents WHERE content_id LIKE :pfx");
|
||||
$stmt->execute([':pfx' => $prefix . '%']);
|
||||
$max = $stmt->fetchColumn();
|
||||
$next = 1;
|
||||
if ($max) {
|
||||
$seq = (int) substr($max, -3);
|
||||
$next = $seq + 1;
|
||||
}
|
||||
return $prefix . str_pad((string) $next, 3, '0', STR_PAD_LEFT);
|
||||
}
|
||||
$content_id = isset($_POST['content_id']) ? trim($_POST['content_id']) : '';
|
||||
$category_code = isset($_POST['category_code']) ? trim($_POST['category_code']) : '';
|
||||
if ($category_code === '' && isset($_POST['category_code_hidden'])) {
|
||||
$category_code = trim($_POST['category_code_hidden']);
|
||||
}
|
||||
$title = isset($_POST['title']) ? trim($_POST['title']) : '';
|
||||
$description = isset($_POST['description']) ? trim($_POST['description']) : '';
|
||||
$description1 = isset($_POST['description1']) ? trim($_POST['description1']) : '';
|
||||
$description2 = isset($_POST['description2']) ? trim($_POST['description2']) : '';
|
||||
$description3 = isset($_POST['description3']) ? trim($_POST['description3']) : '';
|
||||
$content_url = isset($_POST['content_url']) ? trim($_POST['content_url']) : '';
|
||||
$thumbnail_url = isset($_POST['thumbnail_url']) ? trim($_POST['thumbnail_url']) : '';
|
||||
$sort_order = isset($_POST['sort_order']) && $_POST['sort_order'] !== '' ? (int) $_POST['sort_order'] : null;
|
||||
$keywords = isset($_POST['keywords']) ? trim($_POST['keywords']) : '';
|
||||
$is_active = isset($_POST['is_active']) ? '1' : '0';
|
||||
$content_tm = isset($_POST['content_tm']) && $_POST['content_tm'] !== '' ? (int) $_POST['content_tm'] : null;
|
||||
// 카테고리별 추가 필드
|
||||
$base_year = null;
|
||||
$goal_code = null;
|
||||
$category_group = null;
|
||||
$issue_type_code = null;
|
||||
$start_date = null;
|
||||
$end_date = null;
|
||||
$is_offer = '0';
|
||||
$offer_id = null;
|
||||
$book_yn = '0'; // 사내도서여부 기본값
|
||||
// 카테고리 이름 조회
|
||||
$catName = null;
|
||||
if ($category_code !== '') {
|
||||
$nstmt = $pdo->prepare("SELECT code_name FROM edu_codes WHERE group_code='CA100' AND base_code=:c LIMIT 1");
|
||||
$nstmt->execute([':c' => $category_code]);
|
||||
$catName = $nstmt->fetchColumn();
|
||||
}
|
||||
if ($catName === '마이클래스') {
|
||||
$base_year = isset($_POST['base_year']) ? trim($_POST['base_year']) : null;
|
||||
$goal_code = isset($_POST['goal_code']) ? trim($_POST['goal_code']) : null;
|
||||
}
|
||||
// 공통 카테고리구분
|
||||
$category_group = isset($_POST['category_group']) ? trim($_POST['category_group']) : null;
|
||||
if ($catName === '법정교육') {
|
||||
$base_year = isset($_POST['base_year_law']) ? trim($_POST['base_year_law']) : null;
|
||||
$start_date = isset($_POST['start_date']) ? $_POST['start_date'] : null;
|
||||
$end_date = isset($_POST['end_date']) ? $_POST['end_date'] : null;
|
||||
}
|
||||
if ($catName === '인사이트' || $catName === '리더십') {
|
||||
$issue_type_code = isset($_POST['issue_type_code']) ? trim($_POST['issue_type_code']) : null;
|
||||
$offer_id = isset($_POST['offer_id']) ? trim($_POST['offer_id']) : null;
|
||||
$is_offer = isset($_POST['is_offer']) ? '1' : '0';
|
||||
if ($is_offer === '1') {
|
||||
// 기존 추천콘텐츠 초기화 인사이트/리더십카테고리 , 카테고리 구분별 (단일 적용)
|
||||
$stmt = $pdo->prepare("UPDATE edu_contents SET is_offer='0' WHERE is_offer='1' AND category_code = :category_code and issue_type_code = :issue_type_code ");
|
||||
$stmt->execute([':category_code' => $category_code, ':issue_type_code' => $issue_type_code]);
|
||||
}
|
||||
}
|
||||
if ($catName === '비즈트렌드') {
|
||||
$start_date = isset($_POST['start_date_bzt']) ? $_POST['start_date_bzt'] : null;
|
||||
// 사내도서여부: 체크 시 '1', 미체크 시 '0'
|
||||
$book_yn = isset($_POST['book_yn']) && $_POST['book_yn'] === '1' ? '1' : '0';
|
||||
}
|
||||
if ($category_code === '' || $title === '') {
|
||||
echo json_encode(['success' => false, 'message' => '카테고리와 콘텐츠명은 필수입니다.']);
|
||||
exit;
|
||||
}
|
||||
$image_name = null;
|
||||
if ($content_id !== '') {
|
||||
$stmtImg = $pdo->prepare("SELECT image_name FROM edu_contents WHERE content_id = :id");
|
||||
$stmtImg->execute([':id' => $content_id]);
|
||||
$image_name = $stmtImg->fetchColumn();
|
||||
}
|
||||
|
||||
if (isset($_FILES['image_name']) && $_FILES['image_name']['error'] === UPLOAD_ERR_OK) {
|
||||
if ($_FILES['image_name']['size'] > 4 * 1024 * 1024) {
|
||||
echo json_encode(['success' => false, 'message' => '이미지 파일은 4MB 이하만 등록 가능합니다.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$finfo = @finfo_open(FILEINFO_MIME_TYPE);
|
||||
if ($finfo) {
|
||||
$mime = @finfo_file($finfo, $_FILES['image_name']['tmp_name']);
|
||||
@finfo_close($finfo);
|
||||
if ($mime && strpos($mime, 'image/') !== 0) {
|
||||
echo json_encode(['success' => false, 'message' => '이미지 파일만 등록 가능합니다.']);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
$upload_dir = __DIR__ . '/../../uploads/biztrend/';
|
||||
if (!is_dir($upload_dir)) {
|
||||
@mkdir($upload_dir, 0777, true);
|
||||
}
|
||||
|
||||
$ext = strtolower(pathinfo($_FILES['image_name']['name'], PATHINFO_EXTENSION));
|
||||
if (!$ext)
|
||||
$ext = 'jpg';
|
||||
$new_filename = 'img_' . time() . '_' . mt_rand(1000, 9999) . '.' . $ext;
|
||||
$target_file = $upload_dir . $new_filename;
|
||||
|
||||
if (move_uploaded_file($_FILES['image_name']['tmp_name'], $target_file)) {
|
||||
if ($image_name && file_exists($upload_dir . $image_name)) {
|
||||
@unlink($upload_dir . $image_name);
|
||||
}
|
||||
$image_name = $new_filename;
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => '이미지 업로드에 실패했습니다.']);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
if ($content_id !== '') {
|
||||
$sql = "UPDATE edu_contents SET
|
||||
category_code = :category_code,
|
||||
category_group = :category_group,
|
||||
title = :title,
|
||||
description = :description,
|
||||
description1 = :description1,
|
||||
description2 = :description2,
|
||||
description3 = :description3,
|
||||
content_url = :content_url,
|
||||
thumbnail_url = :thumbnail_url,
|
||||
content_tm = :content_tm,
|
||||
base_year = :base_year,
|
||||
start_date = :start_date,
|
||||
end_date = :end_date,
|
||||
sort_order = :sort_order,
|
||||
goal_code = :goal_code,
|
||||
issue_type_code = :issue_type_code,
|
||||
is_offer = :is_offer,
|
||||
offer_id = :offer_id,
|
||||
is_active = :is_active,
|
||||
book_yn = :book_yn,
|
||||
image_name = :image_name,
|
||||
updated_by = :updated_by,
|
||||
updated_at = NOW()
|
||||
WHERE content_id = :content_id";
|
||||
} else {
|
||||
$content_id = generate_content_id($pdo);
|
||||
$sql = "INSERT INTO edu_contents (
|
||||
content_id, category_code, category_group, title, description,description1, description2, description3,
|
||||
content_url, thumbnail_url, content_tm, base_year, start_date, end_date, sort_order,
|
||||
goal_code, issue_type_code, is_offer, offer_id, is_active, book_yn, image_name, created_by , created_at , updated_by , updated_at
|
||||
) VALUES (
|
||||
:content_id, :category_code, :category_group, :title, :description, :description1, :description2, :description3,
|
||||
:content_url, :thumbnail_url, :content_tm, :base_year, :start_date, :end_date, :sort_order,
|
||||
:goal_code, :issue_type_code, :is_offer, :offer_id, :is_active, :book_yn, :image_name, :created_by, NOW(), :updated_by, NOW()
|
||||
)";
|
||||
}
|
||||
try {
|
||||
$stmt = $pdo->prepare($sql);
|
||||
|
||||
$stmt->bindValue(':image_name', $image_name !== '' && $image_name !== null ? $image_name : null, $image_name !== '' && $image_name !== null ? PDO::PARAM_STR : PDO::PARAM_NULL);
|
||||
$stmt->bindValue(':content_id', $content_id);
|
||||
$stmt->bindValue(':category_code', $category_code !== '' ? $category_code : null, $category_code !== '' ? PDO::PARAM_STR : PDO::PARAM_NULL);
|
||||
$stmt->bindValue(':category_group', $category_group !== '' ? $category_group : null, $category_group !== '' ? PDO::PARAM_STR : PDO::PARAM_NULL);
|
||||
$stmt->bindValue(':title', $title);
|
||||
$stmt->bindValue(':description', $description !== '' ? $description : null, $description !== '' ? PDO::PARAM_STR : PDO::PARAM_NULL);
|
||||
$stmt->bindValue(':description1', $description1 !== '' ? $description1 : null, $description1 !== '' ? PDO::PARAM_STR : PDO::PARAM_NULL);
|
||||
$stmt->bindValue(':description2', $description2 !== '' ? $description2 : null, $description2 !== '' ? PDO::PARAM_STR : PDO::PARAM_NULL);
|
||||
$stmt->bindValue(':description3', $description3 !== '' ? $description3 : null, $description3 !== '' ? PDO::PARAM_STR : PDO::PARAM_NULL);
|
||||
$stmt->bindValue(':content_url', $content_url !== '' ? $content_url : null, $content_url !== '' ? PDO::PARAM_STR : PDO::PARAM_NULL);
|
||||
$stmt->bindValue(':thumbnail_url', $thumbnail_url !== '' ? $thumbnail_url : null, $thumbnail_url !== '' ? PDO::PARAM_STR : PDO::PARAM_NULL);
|
||||
if ($content_tm === null) {
|
||||
$stmt->bindValue(':content_tm', null, PDO::PARAM_NULL);
|
||||
} else {
|
||||
$stmt->bindValue(':content_tm', $content_tm, PDO::PARAM_INT);
|
||||
}
|
||||
$stmt->bindValue(':base_year', $base_year !== '' ? $base_year : null, $base_year !== '' ? PDO::PARAM_STR : PDO::PARAM_NULL);
|
||||
$stmt->bindValue(':start_date', $start_date !== '' ? $start_date : null, $start_date !== '' ? PDO::PARAM_STR : PDO::PARAM_NULL);
|
||||
$stmt->bindValue(':end_date', $end_date !== '' ? $end_date : null, $end_date !== '' ? PDO::PARAM_STR : PDO::PARAM_NULL);
|
||||
if ($sort_order === null || $sort_order === '') {
|
||||
$stmt->bindValue(':sort_order', null, PDO::PARAM_NULL);
|
||||
} else {
|
||||
$stmt->bindValue(':sort_order', (int) $sort_order, PDO::PARAM_INT);
|
||||
}
|
||||
$stmt->bindValue(':goal_code', $goal_code !== '' ? $goal_code : null, $goal_code !== '' ? PDO::PARAM_STR : PDO::PARAM_NULL);
|
||||
$stmt->bindValue(':issue_type_code', $issue_type_code !== '' ? $issue_type_code : null, $issue_type_code !== '' ? PDO::PARAM_STR : PDO::PARAM_NULL);
|
||||
$stmt->bindValue(':is_offer', $is_offer);
|
||||
$stmt->bindValue(':offer_id', $offer_id !== '' ? $offer_id : null, $offer_id !== '' ? PDO::PARAM_STR : PDO::PARAM_NULL);
|
||||
$stmt->bindValue(':is_active', $is_active);
|
||||
$stmt->bindValue(':book_yn', $book_yn);
|
||||
|
||||
// created_by는 INSERT 모드에서만 필요함
|
||||
if (!isset($_POST['content_id']) || $_POST['content_id'] === '') {
|
||||
$stmt->bindValue(':created_by', $login_member_id);
|
||||
}
|
||||
|
||||
$stmt->bindValue(':updated_by', $login_member_id);
|
||||
|
||||
$stmt->execute();
|
||||
echo json_encode(['success' => true]);
|
||||
} catch (Exception $e) {
|
||||
// 에러 내용을 JSON 으로 반환하여 화면에서 확인할 수 있게 합니다.
|
||||
echo json_encode(['success' => false, 'message' => '에러 발생: ' . $e->getMessage()]);
|
||||
}
|
||||
exit;
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
$host = getenv('EDU_DB_HOST') ?: 'edu_db';
|
||||
$db = getenv('EDU_DB_NAME') ?: 'edu';
|
||||
$user = getenv('EDU_DB_USER') ?: 'edu1234';
|
||||
$pass = getenv('EDU_DB_PASS') ?: 'edu1234';
|
||||
$charset = 'utf8mb4';
|
||||
$dsn = "mysql:host=$host;dbname=$db;charset=$charset";
|
||||
$options = [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
];
|
||||
$pdo = new PDO($dsn, $user, $pass, $options);
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
require_once '../../bbs/db_conn.php';
|
||||
|
||||
// PDO 연결 생성
|
||||
$pdo = db_conn();
|
||||
|
||||
try {
|
||||
// JSON ?먮뒗 POST ?뚮씪誘명꽣 ?쎄린
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
if (!$input) {
|
||||
$input = $_POST;
|
||||
}
|
||||
|
||||
$group_code = $input['group_code'] ?? '';
|
||||
$code = $input['code'] ?? '';
|
||||
|
||||
if (empty($group_code) || empty($code)) {
|
||||
echo json_encode(['success' => false, 'message' => 'parameters required']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("DELETE FROM edu_codes WHERE group_code = ? AND code = ?");
|
||||
$result = $stmt->execute([$group_code, $code]);
|
||||
echo json_encode(['success' => (bool)$result]);
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
require_once '../../bbs/db_conn.php';
|
||||
|
||||
// PDO 연결 생성
|
||||
$pdo = db_conn();
|
||||
|
||||
try {
|
||||
$group_code = $_POST['group_code'] ?? '';
|
||||
if (empty($group_code)) {
|
||||
echo json_encode(['success' => false, 'message' => 'group_code required']);
|
||||
exit;
|
||||
}
|
||||
$stmt = $pdo->prepare("DELETE FROM edu_code_group WHERE group_code = ?");
|
||||
$result = $stmt->execute([$group_code]);
|
||||
echo json_encode(['success' => (bool)$result]);
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../bbs/auth.php';
|
||||
edu_require_login();
|
||||
|
||||
include_once __DIR__ . '/../../bbs/db_conn.php';
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$year = $_GET['year'] ?? date('Y');
|
||||
$month = $_GET['month'] ?? '';
|
||||
$comp = $_GET['access_comp'] ?? '';
|
||||
|
||||
if (!$month && !isset($_GET['fr_date'])) {
|
||||
echo json_encode(['success' => false, 'message' => '조회 정보가 없습니다.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
|
||||
$fr_date = $_GET['fr_date'] ?? '';
|
||||
$to_date = $_GET['to_date'] ?? '';
|
||||
|
||||
// 법인별 접속자 리스트 조회
|
||||
$sql = "
|
||||
SELECT
|
||||
fn_get_code_name(CONCAT('CO100', u.sys_comp_code)) as comp_name,
|
||||
u.sys_comp_code,
|
||||
u.name,
|
||||
u.dept_name,
|
||||
u.rank_name,
|
||||
a.accessed_at
|
||||
FROM edu_access_logs a
|
||||
JOIN edu_users u ON a.member_id = u.member_id and a.sys_comp_code = u.sys_comp_code
|
||||
WHERE 1=1
|
||||
";
|
||||
$params = [];
|
||||
|
||||
if ($fr_date && $to_date) {
|
||||
$sql .= " AND a.accessed_at BETWEEN ? AND ?";
|
||||
$params[] = "$fr_date 00:00:00";
|
||||
$params[] = "$to_date 23:59:59";
|
||||
} elseif ($month) {
|
||||
$sql .= " AND YEAR(a.accessed_at) = ? AND MONTH(a.accessed_at) = ?";
|
||||
$params[] = $year;
|
||||
$params[] = $month;
|
||||
} else {
|
||||
$sql .= " AND YEAR(a.accessed_at) = ?";
|
||||
$params[] = $year;
|
||||
}
|
||||
|
||||
if ($comp !== '') {
|
||||
$sql .= " AND u.sys_comp_code = ?";
|
||||
$params[] = $comp;
|
||||
}
|
||||
|
||||
$sql .= " ORDER BY a.accessed_at DESC, u.sys_comp_code ASC, u.member_id ASC";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$logs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
echo json_encode(['success' => true, 'data' => $logs]);
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(['success' => false, 'message' => '데이터 조회 중 오류가 발생했습니다.', 'error' => $e->getMessage()]);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
require_once '../../bbs/db_conn.php';
|
||||
|
||||
// PDO 연결 생성
|
||||
$pdo = db_conn();
|
||||
|
||||
try {
|
||||
$stmt = $pdo->query("SELECT * FROM edu_code_group ORDER BY group_code");
|
||||
$groups = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
echo json_encode(['groups' => $groups], JSON_UNESCAPED_UNICODE);
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(['error' => $e->getMessage()], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
require_once '../../bbs/db_conn.php';
|
||||
$pdo = db_conn();
|
||||
$group = $_GET['group_code'] ?? '';
|
||||
try {
|
||||
if ($group !== '') {
|
||||
$stmt = $pdo->prepare("SELECT * FROM edu_codes WHERE group_code = ? ORDER BY code");
|
||||
$stmt->execute([$group]);
|
||||
} else {
|
||||
$stmt = $pdo->query("SELECT * FROM edu_codes ORDER BY group_code, code");
|
||||
}
|
||||
$codes = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
echo json_encode(['codes' => $codes], JSON_UNESCAPED_UNICODE);
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(['error' => $e->getMessage()], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../bbs/auth.php';
|
||||
edu_require_login();
|
||||
|
||||
include_once __DIR__ . '/../../bbs/db_conn.php';
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$corp_code = $_GET['corp_code'] ?? '';
|
||||
$fr_date = $_GET['fr_date'] ?? '';
|
||||
$to_date = $_GET['to_date'] ?? '';
|
||||
$type = $_GET['type'] ?? 'avg';
|
||||
|
||||
if (!$fr_date || !$to_date) {
|
||||
echo json_encode(['success' => false, 'message' => '필수 정보가 누락되었습니다.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
u.member_id,
|
||||
u.name,
|
||||
u.dept_name,
|
||||
c.title as content_title,
|
||||
CONCAT(
|
||||
LPAD(FLOOR(h.watch_tm / 3600), 2, '0'), '시간 ',
|
||||
LPAD(FLOOR((h.watch_tm % 3600) / 60), 2, '0'), '분'
|
||||
) as formatted_watch_tm,
|
||||
h.last_viewed_at
|
||||
FROM edu_learning_histories h
|
||||
JOIN edu_users u ON h.member_id = u.member_id AND h.sys_comp_code = u.sys_comp_code
|
||||
JOIN edu_contents c ON h.content_id = c.content_id
|
||||
WHERE (? = '' OR u.sys_comp_code = ?) AND h.last_viewed_at BETWEEN ? AND ?
|
||||
ORDER BY u.name ASC , h.last_viewed_at DESC
|
||||
";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$corp_code, $corp_code, "$fr_date 00:00:00", "$to_date 23:59:59"]);
|
||||
|
||||
$data = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
echo json_encode(['success' => true, 'data' => $data]);
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(['success' => false, 'message' => '데이터 조회 중 오류가 발생했습니다.', 'error' => $e->getMessage()]);
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
<?php
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
require_once __DIR__ . '/../../bbs/db_conn.php';
|
||||
require_once __DIR__ . '/../../bbs/auth.php';
|
||||
edu_require_login();
|
||||
|
||||
$pdo = db_conn();
|
||||
|
||||
$year = $_GET['year'] ?? date('Y');
|
||||
$quarter_raw = $_GET['quarter'] ?? ceil(date('n') / 3);
|
||||
|
||||
// Convert quarter code (e.g., CA200Q01, CA200001) to integer 1~4
|
||||
if (is_string($quarter_raw) && strpos($quarter_raw, 'CA200') === 0) {
|
||||
$quarter = (int) substr($quarter_raw, -1);
|
||||
} else {
|
||||
$quarter = (int) $quarter_raw;
|
||||
}
|
||||
if ($quarter < 1 || $quarter > 4)
|
||||
$quarter = 1;
|
||||
|
||||
// Calculate start and end dates for the quarter
|
||||
$p_fr_dt = $year . '-' . sprintf('%02d', ($quarter - 1) * 3 + 1) . '-01';
|
||||
$p_to_dt = date('Y-m-t', strtotime($year . '-' . sprintf('%02d', $quarter * 3) . '-01'));
|
||||
$p_fr_dt_full = $p_fr_dt . ' 00:00:00';
|
||||
$p_to_dt_full = $p_to_dt . ' 23:59:59';
|
||||
|
||||
$response = [
|
||||
'success' => true,
|
||||
'data' => []
|
||||
];
|
||||
|
||||
try {
|
||||
// 1. KPI
|
||||
$stmt_kpi = $pdo->prepare("CALL proc_get_dashboard_kpi(?, ?)");
|
||||
$stmt_kpi->execute([$year, $quarter]);
|
||||
$kpi = $stmt_kpi->fetch(PDO::FETCH_ASSOC);
|
||||
while ($stmt_kpi->nextRowset()) {
|
||||
} // Clear extra rowsets
|
||||
$stmt_kpi->closeCursor();
|
||||
|
||||
// Ensure all variables exist, with defaults
|
||||
$kpi = $kpi ?: [];
|
||||
// DEBUG: Write kpi to file
|
||||
file_put_contents(__DIR__ . '/kpi_debug.json', json_encode($kpi, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
|
||||
|
||||
$kpi = array_change_key_case($kpi, CASE_LOWER);
|
||||
|
||||
// Add computed metrics for KPI based on User's input
|
||||
$access_qty = isset($kpi['access_qty']) ? (float) $kpi['access_qty'] : 0;
|
||||
$completed_qty = isset($kpi['completed_qty']) ? (float) $kpi['completed_qty'] : 0;
|
||||
$access_qty_u = isset($kpi['access_qty_u']) ? (float) $kpi['access_qty_u'] : 0;
|
||||
$completed_qty_u = isset($kpi['completed_qty_u']) ? (float) $kpi['completed_qty_u'] : 0;
|
||||
|
||||
$kpi['computed_content_per_user'] = $access_qty > 0 ? round($completed_qty / $access_qty, 1) : 0;
|
||||
$prev_content_per_user = $access_qty_u > 0 ? round($completed_qty_u / $access_qty_u, 1) : 0;
|
||||
$kpi['computed_content_per_user_diff'] = round($kpi['computed_content_per_user'] - $prev_content_per_user, 1);
|
||||
|
||||
$legal_qty_all = isset($kpi['legal_qty_all']) ? (int) $kpi['legal_qty_all'] : 0;
|
||||
$legal_qty_y = isset($kpi['legal_qty_y']) ? (int) $kpi['legal_qty_y'] : 0;
|
||||
$kpi['computed_legal_uncompleted'] = $legal_qty_all - $legal_qty_y;
|
||||
$kpi['computed_legal_rate'] = $legal_qty_all > 0 ? round(($legal_qty_y / $legal_qty_all) * 100) : 0;
|
||||
|
||||
$quarter_qty = isset($kpi['quarter_qty']) ? (int) $kpi['quarter_qty'] : 0;
|
||||
$goals_qty = isset($kpi['goals_qty']) ? (int) $kpi['goals_qty'] : (isset($kpi['goal_qty']) ? (int) $kpi['goal_qty'] : 0);
|
||||
$goals_completed_qty = isset($kpi['goals_completed_qty']) ? (int) $kpi['goals_completed_qty'] : (isset($kpi['goal_completed_qty']) ? (int) $kpi['goal_completed_qty'] : 0);
|
||||
|
||||
$kpi['myclass_target_qty'] = $goals_qty;
|
||||
$kpi['myclass_target_rate'] = $quarter_qty > 0 ? round(($goals_qty / $quarter_qty) * 100) : 0;
|
||||
|
||||
$kpi['myclass_achieve_qty'] = $goals_completed_qty;
|
||||
$kpi['myclass_achieve_rate'] = $goals_qty > 0 ? round(($goals_completed_qty / $goals_qty) * 100) : 0;
|
||||
|
||||
$response['data']['kpi'] = $kpi;
|
||||
|
||||
// 2. 법인별 접속률
|
||||
$stmt_corp = $pdo->prepare("
|
||||
SELECT c.code_name
|
||||
, COUNT(DISTINCT b.member_id) as all_qty
|
||||
, IFNULL(d.access_qty, 0) AS access_qty
|
||||
, IFNULL(ROUND(IFNULL(d.access_qty, 0) / NULLIF(COUNT(DISTINCT b.member_id), 0) * 100,0),0) as access_rate
|
||||
, IFNULL(COUNT(DISTINCT b.member_id) - IFNULL(d.access_qty, 0),0) AS no_access_qty
|
||||
FROM edu_codes c
|
||||
LEFT JOIN edu_users b ON c.code = b.sys_comp_code AND (b.end_date IS NULL OR b.end_date > CURDATE())
|
||||
LEFT JOIN (SELECT COUNT(0) AS access_qty , sys_comp_code
|
||||
FROM (SELECT a.member_id, a.sys_comp_code
|
||||
FROM edu_access_logs a
|
||||
WHERE 1=1
|
||||
AND accessed_at >= ?
|
||||
AND accessed_at <= ?
|
||||
GROUP BY a.member_id, a.sys_comp_code) x
|
||||
GROUP BY sys_comp_code ) d ON c.code = d.sys_comp_code
|
||||
WHERE c.group_code = 'CO100' AND c.is_active = '1'
|
||||
GROUP BY c.code, c.code_name
|
||||
ORDER BY c.code ASC
|
||||
");
|
||||
$stmt_corp->execute([$p_fr_dt, $p_to_dt]);
|
||||
$response['data']['corp_access_rate'] = $stmt_corp->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// 3. 학습자 접속 빈도
|
||||
$stmt_freq = $pdo->prepare("
|
||||
SELECT
|
||||
SUM(CASE WHEN IFNULL(a.qty, 0) >= 12 THEN 1 ELSE 0 END) AS active_lv01,
|
||||
SUM(CASE WHEN IFNULL(a.qty, 0) BETWEEN 5 AND 11 THEN 1 ELSE 0 END) AS normal_lv02,
|
||||
SUM(CASE WHEN IFNULL(a.qty, 0) BETWEEN 1 AND 4 THEN 1 ELSE 0 END) AS low_lv03,
|
||||
SUM(CASE WHEN IFNULL(a.qty, 0) = 0 THEN 1 ELSE 0 END) AS none_lv04
|
||||
FROM edu_users u
|
||||
LEFT JOIN (
|
||||
SELECT member_id, sys_comp_code, COUNT(DISTINCT DATE(accessed_at)) AS qty
|
||||
FROM edu_access_logs
|
||||
WHERE accessed_at BETWEEN ? AND ?
|
||||
GROUP BY member_id, sys_comp_code
|
||||
) a ON u.member_id = a.member_id AND u.sys_comp_code = a.sys_comp_code
|
||||
WHERE 1=1
|
||||
AND (end_date IS NULL OR end_date > CURDATE())
|
||||
");
|
||||
$stmt_freq->execute([$p_fr_dt, $p_to_dt]);
|
||||
$response['data']['access_freq'] = $stmt_freq->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
// 4. 접속 시간대
|
||||
$stmt_time = $pdo->prepare("
|
||||
SELECT
|
||||
SUM(CASE WHEN substring(accessed_at,12,5)>= '09:00' AND substring(accessed_at,12,5)<= '17:00' THEN 1 ELSE 0 END)-
|
||||
SUM(CASE WHEN substring(accessed_at,12,5)>= '11:30' AND substring(accessed_at,12,5)<= '13:30' THEN 1 ELSE 0 END) AS worktime
|
||||
, SUM(CASE WHEN substring(accessed_at,12,5)>= '11:30' AND substring(accessed_at,12,5)<= '13:30' THEN 1 ELSE 0 END) AS lunchtime
|
||||
, SUM(CASE WHEN substring(accessed_at,12,5) < '09:00' or substring(accessed_at,12,5) > '17:00' THEN 1 ELSE 0 END) AS outtime
|
||||
, SUM(CASE WHEN 1=1 THEN 1 ELSE 0 END) AS alltime
|
||||
FROM edu_access_logs
|
||||
WHERE 1=1
|
||||
AND accessed_at BETWEEN ? AND ?
|
||||
");
|
||||
$stmt_time->execute([$p_fr_dt_full, $p_to_dt_full]);
|
||||
$response['data']['access_time'] = $stmt_time->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
// 5. 접속 방법
|
||||
$stmt_device = $pdo->prepare("
|
||||
SELECT
|
||||
SUM(CASE WHEN device_type = 'PC' THEN 1 ELSE 0 END) AS pc
|
||||
, SUM(CASE WHEN device_type = 'Mobile' THEN 1 ELSE 0 END) AS mobile
|
||||
, SUM(CASE WHEN 1=1 THEN 1 ELSE 0 END) AS alldevice
|
||||
FROM edu_access_logs
|
||||
WHERE 1=1
|
||||
AND accessed_at BETWEEN ? AND ?
|
||||
");
|
||||
$stmt_device->execute([$p_fr_dt_full, $p_to_dt_full]);
|
||||
$response['data']['access_device'] = $stmt_device->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
// 6. 카테고리별 이용현황
|
||||
$stmt_usage = $pdo->prepare("
|
||||
SELECT
|
||||
SUM(CASE WHEN 1=1 THEN 1 ELSE 0 END) AS tot_qty
|
||||
, SUM(CASE WHEN a.category_code = 'CA10001' THEN 1 ELSE 0 END) AS myclass_use_qty
|
||||
, SUM(CASE WHEN a.category_code = 'CA10004' THEN 1 ELSE 0 END) AS leader_use_qty
|
||||
, SUM(CASE WHEN a.category_code = 'CA10005' THEN 1 ELSE 0 END) AS insight_use_qty
|
||||
, SUM(CASE WHEN a.category_code = 'CA10006' THEN 1 ELSE 0 END) AS biz_use_qty
|
||||
FROM edu_contents a
|
||||
JOIN edu_learning_histories b ON a.content_id = b.content_id
|
||||
WHERE 1=1
|
||||
AND b.last_viewed_at BETWEEN ? AND ?
|
||||
AND a.category_code IN('CA10001','CA10004','CA10005','CA10006')
|
||||
");
|
||||
$stmt_usage->execute([$p_fr_dt_full, $p_to_dt_full]);
|
||||
$response['data']['content_usage'] = $stmt_usage->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
// 7. 이번분기 신규콘텐츠 등록수량
|
||||
$stmt_new = $pdo->prepare("
|
||||
SELECT
|
||||
IFNULL(SUM(category_code = 'CA10001'), 0) AS myclass_new_qty,
|
||||
IFNULL(SUM(category_code = 'CA10004'), 0) AS leader_new_qty,
|
||||
IFNULL(SUM(category_code = 'CA10005'), 0) AS insight_new_qty,
|
||||
IFNULL(SUM(category_code = 'CA10006'), 0) AS biz_new_qty
|
||||
FROM edu_contents
|
||||
WHERE created_at BETWEEN ? AND ?
|
||||
");
|
||||
$stmt_new->execute([$p_fr_dt_full, $p_to_dt_full]);
|
||||
$response['data']['new_content_qty'] = $stmt_new->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
// 8. 가장 많이 본 콘텐츠
|
||||
$stmt_popular = $pdo->prepare("
|
||||
SELECT
|
||||
b.category_code
|
||||
, fn_get_code_name(b.category_code) AS category_name
|
||||
, b.title AS content_title
|
||||
, COUNT(a.content_id) as view_count
|
||||
, SUM(CASE WHEN a.completed_at IS NOT NULL OR a.watch_tm/a.content_tm >= 0.7 THEN 1 ELSE 0 END)/COUNT(a.content_id)*100 AS completed_rate
|
||||
, c.comment_cnt
|
||||
FROM edu_learning_histories a
|
||||
JOIN edu_contents b ON a.content_id = b.content_id
|
||||
LEFT JOIN ( SELECT y.category_code , x.content_id , COUNT(0) AS comment_cnt
|
||||
FROM edu_comments x
|
||||
JOIN edu_contents y ON x.content_id = y.content_id
|
||||
WHERE x.created_at BETWEEN ? AND ?
|
||||
GROUP BY y.category_code , x.content_id) c ON a.content_id = c.content_id
|
||||
WHERE 1=1
|
||||
AND b.category_code IN('CA10001','CA10004','CA10005','CA10006')
|
||||
AND a.last_viewed_at BETWEEN ? AND ?
|
||||
GROUP BY b.category_code, b.content_id, b.title
|
||||
ORDER BY COUNT(a.content_id) DESC
|
||||
LIMIT 50
|
||||
");
|
||||
$stmt_popular->execute([$p_fr_dt_full, $p_to_dt_full, $p_fr_dt_full, $p_to_dt_full]);
|
||||
$response['data']['popular_contents'] = $stmt_popular->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
} catch (Throwable $e) {
|
||||
$response['success'] = false;
|
||||
$response['message'] = $e->getMessage();
|
||||
$response['trace'] = $e->getTraceAsString();
|
||||
}
|
||||
|
||||
echo json_encode($response);
|
||||
@@ -0,0 +1,214 @@
|
||||
<?php
|
||||
/**
|
||||
* get_legal_cert_print.php - 수료증 정보 조회 API
|
||||
*/
|
||||
ini_set('display_errors', '1');
|
||||
ini_set('display_startup_errors', '1');
|
||||
error_reporting(E_ALL);
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
require_once __DIR__ . '/../../bbs/db_conn.php';
|
||||
require_once __DIR__ . '/../../bbs/auth.php';
|
||||
edu_require_login();
|
||||
|
||||
$year = $_GET['year'] ?? $_GET['_YEAR'] ?? '';
|
||||
$comp = $_GET['comp'] ?? $_GET['_COMP'] ?? '';
|
||||
$member_id = $_GET['member_id'] ?? $_GET['_MEMBER_ID'] ?? '';
|
||||
$category_group = $_GET['category_group'] ?? $_GET['_CATEGORY_GROUP'] ?? '';
|
||||
|
||||
if (empty($year) || empty($comp) || empty($member_id) || empty($category_group)) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '필수 파라미터가 부족합니다. (year, comp, member_id, category_group)'
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
|
||||
$category_groups = explode(',', $category_group);
|
||||
$decodedItems = [];
|
||||
$calls_log = [];
|
||||
|
||||
foreach ($category_groups as $cat) {
|
||||
$cat = trim($cat);
|
||||
if (empty($cat))
|
||||
continue;
|
||||
|
||||
$member_ids = [];
|
||||
if ($member_id === 'ALL') {
|
||||
// 1. Get total active legal contents count for the category group
|
||||
$stmt_cnt = $pdo->prepare("
|
||||
SELECT COUNT(0) AS cnt
|
||||
FROM edu_contents
|
||||
WHERE base_year = ?
|
||||
AND category_code = 'CA10003'
|
||||
AND category_group = ?
|
||||
AND is_active = '1';
|
||||
");
|
||||
$stmt_cnt->execute([$year, $cat]);
|
||||
$total_legal_cnt = (int)$stmt_cnt->fetchColumn();
|
||||
|
||||
if ($total_legal_cnt > 0) {
|
||||
// 2. Query completed users using the SQL logic from 개발.md
|
||||
$stmt_users = $pdo->prepare("
|
||||
SELECT b.member_id
|
||||
FROM edu_learning_histories b
|
||||
INNER JOIN edu_contents a ON a.content_id = b.content_id
|
||||
INNER JOIN edu_users c ON b.member_id = c.member_id AND b.sys_comp_code = c.sys_comp_code
|
||||
WHERE a.base_year = ?
|
||||
AND a.category_code = 'CA10003'
|
||||
AND a.is_active = '1'
|
||||
AND b.completed_at IS NOT NULL
|
||||
AND a.category_group = ?
|
||||
AND (? = '' OR c.belong_comp = ?)
|
||||
GROUP BY b.member_id, c.belong_comp
|
||||
HAVING COUNT(b.content_id) = ?;
|
||||
");
|
||||
$stmt_users->execute([$year, $cat, $comp, $comp, $total_legal_cnt]);
|
||||
$member_ids = $stmt_users->fetchAll(PDO::FETCH_COLUMN);
|
||||
}
|
||||
} else {
|
||||
$member_ids = [$member_id];
|
||||
}
|
||||
|
||||
foreach ($member_ids as $m_id) {
|
||||
$calls_log[] = [
|
||||
'proc' => 'proc_get_legal_education_certificate',
|
||||
'params' => [
|
||||
'year' => $year,
|
||||
'comp' => $comp,
|
||||
'member_id' => $m_id,
|
||||
'category_group' => $cat
|
||||
]
|
||||
];
|
||||
|
||||
$stmt = $pdo->prepare("CALL proc_get_legal_education_certificate(?, ?, ?, ?);");
|
||||
$stmt->execute([
|
||||
$year,
|
||||
$comp,
|
||||
$m_id,
|
||||
$cat
|
||||
]);
|
||||
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
while ($stmt->nextRowset()) {
|
||||
} // 소진
|
||||
|
||||
if ($row) {
|
||||
$decodedRow = [];
|
||||
foreach ($row as $key => $val) {
|
||||
$decodedKey = strtolower($key);
|
||||
if (is_object($val) && isset($val->type) && $val->type === 'Buffer') {
|
||||
$decodedRow[$decodedKey] = pack('C*', ...$val->data);
|
||||
} else {
|
||||
$decodedRow[$decodedKey] = $val;
|
||||
}
|
||||
}
|
||||
|
||||
// 각 아이템별 동적 스탬프 및 로고 워터마크 이미지 검증 (admin/file 경로 우선, 기존 img 경로 차선 Fallback)
|
||||
$belong_comp_code = $decodedRow['belong_comp_code'] ?? $decodedRow['sys_comp_code'] ?? $comp;
|
||||
$logo_url = "";
|
||||
$stamp_url = "";
|
||||
|
||||
if (!empty($belong_comp_code)) {
|
||||
// /www/admin/file/logo 및 /www/admin/file/stamp 컨테이너 내 절대 파일 경로
|
||||
$abs_logo_svg = '/www/admin/file/logo/' . $belong_comp_code . '_logo.svg';
|
||||
$abs_logo_png = '/www/admin/file/logo/' . $belong_comp_code . '_logo.png';
|
||||
$abs_stamp_svg = '/www/admin/file/stamp/' . $belong_comp_code . '_stamp.svg';
|
||||
$abs_stamp_png = '/www/admin/file/stamp/' . $belong_comp_code . '_stamp.png';
|
||||
|
||||
// 로컬 상대 파일 경로 (__DIR__ 기준 fallback)
|
||||
$admin_logo_svg = __DIR__ . '/../file/logo/' . $belong_comp_code . '_logo.svg';
|
||||
$admin_logo_png = __DIR__ . '/../file/logo/' . $belong_comp_code . '_logo.png';
|
||||
$admin_stamp_svg = __DIR__ . '/../file/stamp/' . $belong_comp_code . '_stamp.svg';
|
||||
$admin_stamp_png = __DIR__ . '/../file/stamp/' . $belong_comp_code . '_stamp.png';
|
||||
|
||||
// 기존 img/ 절대 파일 경로
|
||||
$img_logo_svg = __DIR__ . '/../../img/' . $belong_comp_code . '_logo.svg';
|
||||
$img_logo_png = __DIR__ . '/../../img/' . $belong_comp_code . '_logo.png';
|
||||
$img_stamp_svg = __DIR__ . '/../../img/' . $belong_comp_code . '_stamp.svg';
|
||||
$img_stamp_png = __DIR__ . '/../../img/' . $belong_comp_code . '_stamp.png';
|
||||
|
||||
// 로고 매핑 (절대경로 및 로컬경로 존재 확인 후 웹 URL 설정 - /admin/file/logo/ 우선)
|
||||
if (file_exists($abs_logo_svg)) {
|
||||
$logo_url = "/admin/file/logo/" . $belong_comp_code . "_logo.svg";
|
||||
} elseif (file_exists($admin_logo_svg)) {
|
||||
$logo_url = "/admin/file/logo/" . $belong_comp_code . "_logo.svg";
|
||||
} elseif (file_exists($abs_logo_png)) {
|
||||
$logo_url = "/admin/file/logo/" . $belong_comp_code . "_logo.png";
|
||||
} elseif (file_exists($admin_logo_png)) {
|
||||
$logo_url = "/admin/file/logo/" . $belong_comp_code . "_logo.png";
|
||||
} elseif (file_exists($img_logo_svg)) {
|
||||
$logo_url = "/img/" . $belong_comp_code . "_logo.svg";
|
||||
} elseif (file_exists($img_logo_png)) {
|
||||
$logo_url = "/img/" . $belong_comp_code . "_logo.png";
|
||||
}
|
||||
|
||||
// 직인 매핑 (절대경로 및 로컬경로 존재 확인 후 웹 URL 설정 - /admin/file/stamp/ 우선)
|
||||
if (file_exists($abs_stamp_svg)) {
|
||||
$stamp_url = "/admin/file/stamp/" . $belong_comp_code . "_stamp.svg";
|
||||
} elseif (file_exists($admin_stamp_svg)) {
|
||||
$stamp_url = "/admin/file/stamp/" . $belong_comp_code . "_stamp.svg";
|
||||
} elseif (file_exists($abs_stamp_png)) {
|
||||
$stamp_url = "/admin/file/stamp/" . $belong_comp_code . "_stamp.png";
|
||||
} elseif (file_exists($admin_stamp_png)) {
|
||||
$stamp_url = "/admin/file/stamp/" . $belong_comp_code . "_stamp.png";
|
||||
} elseif (file_exists($img_stamp_svg)) {
|
||||
$stamp_url = "/img/" . $belong_comp_code . "_stamp.svg";
|
||||
} elseif (file_exists($img_stamp_png)) {
|
||||
$stamp_url = "/img/" . $belong_comp_code . "_stamp.png";
|
||||
}
|
||||
}
|
||||
|
||||
$decodedRow['logo_url'] = $logo_url;
|
||||
$decodedRow['stamp_url'] = $stamp_url;
|
||||
|
||||
$decodedItems[] = $decodedRow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($decodedItems)) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '해당 조건의 수료 정보가 존재하지 않습니다.',
|
||||
'debug' => [
|
||||
'api_params' => [
|
||||
'year' => $year,
|
||||
'comp' => $comp,
|
||||
'member_id' => $member_id,
|
||||
'category_group' => $category_group
|
||||
],
|
||||
'processed_categories' => $category_groups,
|
||||
'total_legal_cnt' => $total_legal_cnt ?? null,
|
||||
'matched_member_ids' => $member_ids ?? [],
|
||||
'procedure_calls' => $calls_log,
|
||||
'step_info' => 'No certificates could be loaded or formatted.'
|
||||
]
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'data' => $decodedItems,
|
||||
'debug' => [
|
||||
'procedure_calls' => $calls_log
|
||||
]
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log('[get_legal_cert_print] Error: ' . $e->getMessage());
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '서버 내부 오류가 발생했습니다.',
|
||||
'detail' => $e->getMessage(),
|
||||
'debug' => [
|
||||
'procedure_calls' => $calls_log ?? []
|
||||
]
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
ini_set('display_errors', '1');
|
||||
ini_set('display_startup_errors', '1');
|
||||
error_reporting(E_ALL);
|
||||
|
||||
require_once __DIR__ . '/../../bbs/db_conn.php';
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
$sys_comp_code = $_GET['sys_comp_code'] ?? '';
|
||||
$member_id = $_GET['member_id'] ?? '';
|
||||
$year = $_GET['year'] ?? date('Y');
|
||||
|
||||
if (!$sys_comp_code || !$member_id) {
|
||||
echo json_encode(['success' => false, 'message' => '파라미터가 부족합니다.'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
|
||||
$stmt = $pdo->prepare("CALL proc_get_member_learning_status(:sys_comp_code, :year, :member_id, 'CA10003')");
|
||||
$stmt->execute([
|
||||
':sys_comp_code' => $sys_comp_code,
|
||||
':year' => $year,
|
||||
':member_id' => $member_id
|
||||
]);
|
||||
|
||||
$items = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
while ($stmt->nextRowset()) {}
|
||||
|
||||
echo json_encode(['success' => true, 'items' => $items], JSON_UNESCAPED_UNICODE);
|
||||
} catch (Exception $e) {
|
||||
error_log('[get_legal_edu_detail] ' . $e->getMessage());
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '서버 에러',
|
||||
'detail' => $e->getMessage()
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
require_once __DIR__ . '/../../bbs/db_conn.php';
|
||||
require_once __DIR__ . '/../../bbs/auth.php';
|
||||
edu_require_login();
|
||||
|
||||
$pdo = db_conn();
|
||||
|
||||
$year = $_GET['year'] ?? date('Y');
|
||||
$quarter_raw = $_GET['quarter'] ?? ceil(date('n') / 3);
|
||||
|
||||
// Convert quarter code (e.g., CA200Q01, CA200001) to integer 1~4
|
||||
if (is_string($quarter_raw) && strpos($quarter_raw, 'CA200') === 0) {
|
||||
$quarter = (int) substr($quarter_raw, -1);
|
||||
} else {
|
||||
$quarter = (int) $quarter_raw;
|
||||
}
|
||||
if ($quarter < 1 || $quarter > 4)
|
||||
$quarter = 1;
|
||||
|
||||
// Calculate start and end dates for the quarter
|
||||
$p_fr_dt = $year . '-' . sprintf('%02d', ($quarter - 1) * 3 + 1) . '-01';
|
||||
$p_to_dt = date('Y-m-t', strtotime($year . '-' . sprintf('%02d', $quarter * 3) . '-01'));
|
||||
$p_fr_dt_full = $p_fr_dt . ' 00:00:00';
|
||||
$p_to_dt_full = $p_to_dt . ' 23:59:59';
|
||||
|
||||
$response = [
|
||||
'success' => true,
|
||||
'data' => []
|
||||
];
|
||||
|
||||
try {
|
||||
// 1. 전체학습자(quarter_qty)
|
||||
$stmt_q1 = $pdo->prepare("
|
||||
SELECT COUNT(*) as quarter_qty FROM edu_users
|
||||
WHERE join_date <= ?
|
||||
AND (end_date >= ? OR end_date IS NULL)
|
||||
");
|
||||
$stmt_q1->execute([$p_to_dt_full, $p_fr_dt_full]);
|
||||
$quarter_qty = (int) $stmt_q1->fetchColumn();
|
||||
|
||||
// 2. 목표선택자(goals_qty)
|
||||
$stmt_q2 = $pdo->prepare("
|
||||
SELECT COUNT(b.member_id) as goals_qty
|
||||
FROM edu_learning_goals a
|
||||
JOIN edu_user_learning_goals b ON a.goal_code = b.goal_code
|
||||
WHERE 1=1
|
||||
AND b.is_active = '1'
|
||||
AND a.base_year = ? AND RIGHT(a.quarter, 1) = ?
|
||||
");
|
||||
$stmt_q2->execute([$year, $quarter]);
|
||||
$goals_qty = (int) $stmt_q2->fetchColumn();
|
||||
|
||||
// 3. 목표달성자(goals_completed_qty)
|
||||
$stmt_q3 = $pdo->prepare("
|
||||
SELECT COUNT( b.member_id) as goals_completed_qty
|
||||
FROM edu_learning_goals a
|
||||
JOIN edu_user_learning_goals b ON a.goal_code = b.goal_code
|
||||
WHERE a.base_year = ? AND RIGHT(a.quarter, 1) = ? AND b.completed_date IS NOT NULL
|
||||
");
|
||||
$stmt_q3->execute([$year, $quarter]);
|
||||
$goals_completed_qty = (int) $stmt_q3->fetchColumn();
|
||||
|
||||
// 4. 목표별선택률
|
||||
$stmt_q4 = $pdo->prepare("
|
||||
SELECT a.sort_order
|
||||
, a.title
|
||||
, IFNULL(b.goal_qty, 0) as goal_qty
|
||||
FROM edu_learning_goals a
|
||||
LEFT JOIN ( SELECT goal_code , COUNT(0) AS goal_qty
|
||||
FROM edu_user_learning_goals a
|
||||
WHERE 1=1
|
||||
AND is_active = '1'
|
||||
AND RIGHT(a.quarter, 1) = ?
|
||||
GROUP BY goal_code) b ON a.goal_code = b.goal_code
|
||||
WHERE 1=1
|
||||
AND a.is_active = '1'
|
||||
AND RIGHT(a.quarter, 1) = ?
|
||||
ORDER BY a.sort_order
|
||||
");
|
||||
$stmt_q4->execute([$quarter, $quarter]);
|
||||
$goal_selection_rate = $stmt_q4->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// 5. 법인별 비교
|
||||
$stmt_q5 = $pdo->prepare("
|
||||
SELECT fn_get_code_name(CONCAT('CO100', a.code)) AS comp_name
|
||||
, IFNULL(b.comp_qty,0) AS comp_qty
|
||||
, COUNT(DISTINCT u.member_id) as all_qty
|
||||
FROM edu_codes a
|
||||
LEFT JOIN edu_users u ON a.code = u.sys_comp_code AND (u.end_date >= ? OR u.end_date IS NULL)
|
||||
LEFT JOIN (
|
||||
SELECT sys_comp_code
|
||||
, COUNT(0) AS comp_qty
|
||||
FROM edu_user_learning_goals x
|
||||
WHERE 1=1
|
||||
AND is_active = '1'
|
||||
AND RIGHT(x.quarter, 1) = ?
|
||||
GROUP BY sys_comp_code ) b ON a.code = b.sys_comp_code
|
||||
WHERE 1=1
|
||||
AND a.group_code = 'CO100'
|
||||
GROUP BY a.code, fn_get_code_name(CONCAT('CO100', a.code)), b.comp_qty
|
||||
ORDER BY a.code
|
||||
");
|
||||
$stmt_q5->execute([$p_fr_dt_full, $quarter]);
|
||||
$comp_selection_rate = $stmt_q5->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Calculate derived metrics based on User's explicit formulas
|
||||
$target_rate = $quarter_qty > 0 ? round(($goals_qty / $quarter_qty) * 100) : 0;
|
||||
$achieve_rate = $goals_qty > 0 ? round(($goals_completed_qty / $goals_qty) * 100) : 0;
|
||||
$unselected_qty = $quarter_qty - $goals_qty;
|
||||
$unselected_rate = $quarter_qty > 0 ? round(($unselected_qty / $quarter_qty) * 100, 1) : 0;
|
||||
|
||||
$response['data'] = [
|
||||
'kpi' => [
|
||||
'quarter_qty' => $quarter_qty,
|
||||
'goals_qty' => $goals_qty,
|
||||
'goals_completed_qty' => $goals_completed_qty,
|
||||
'target_rate' => $target_rate,
|
||||
'achieve_rate' => $achieve_rate,
|
||||
'unselected_qty' => $unselected_qty,
|
||||
'unselected_rate' => $unselected_rate
|
||||
],
|
||||
'goals' => $goal_selection_rate,
|
||||
'comps' => $comp_selection_rate
|
||||
];
|
||||
|
||||
} catch (PDOException $e) {
|
||||
$response['success'] = false;
|
||||
$response['error'] = 'DB Query Error: ' . $e->getMessage();
|
||||
} catch (Exception $e) {
|
||||
$response['success'] = false;
|
||||
$response['error'] = 'Error: ' . $e->getMessage();
|
||||
}
|
||||
|
||||
echo json_encode($response, JSON_UNESCAPED_UNICODE);
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../bbs/db_conn.php';
|
||||
|
||||
ini_set('display_errors', '0');
|
||||
ini_set('display_startup_errors', '0');
|
||||
error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
|
||||
// 파라미터 받기
|
||||
$offer_date_fr = $_GET['offer_date_fr'] ?? '';
|
||||
$offer_date_to = $_GET['offer_date_to'] ?? '';
|
||||
$member_id = $_GET['member_id'] ?? '';
|
||||
$reason = $_GET['reason'] ?? '';
|
||||
$status_code = $_GET['status_code'] ?? '';
|
||||
|
||||
// 쿼리 빌드
|
||||
$sql = "
|
||||
SELECT
|
||||
o.offer_id,
|
||||
o.reference_url,
|
||||
o.reason,
|
||||
o.status_code,
|
||||
COALESCE(c.code_name, o.status_code) AS status_name,
|
||||
o.reason_return,
|
||||
o.member_id,
|
||||
u.name,
|
||||
DATE(o.created_at) AS offer_date
|
||||
FROM edu_content_offer o
|
||||
LEFT JOIN edu_users u ON o.member_id = u.member_id AND o.sys_comp_code = u.sys_comp_code
|
||||
LEFT JOIN edu_codes c ON c.group_code = 'OF100' AND c.base_code = o.status_code
|
||||
WHERE 1=1
|
||||
";
|
||||
$params = [];
|
||||
|
||||
if ($offer_date_fr) {
|
||||
$sql .= " AND DATE(o.created_at) >= ?";
|
||||
$params[] = $offer_date_fr;
|
||||
}
|
||||
if ($offer_date_to) {
|
||||
$sql .= " AND DATE(o.created_at) <= ?";
|
||||
$params[] = $offer_date_to;
|
||||
}
|
||||
if ($member_id) {
|
||||
$sql .= " AND (o.member_id LIKE ? OR u.name LIKE ?)";
|
||||
$params[] = '%' . $member_id . '%';
|
||||
$params[] = '%' . $member_id . '%';
|
||||
}
|
||||
if ($reason) {
|
||||
$sql .= " AND o.reason LIKE ?";
|
||||
$params[] = '%' . $reason . '%';
|
||||
}
|
||||
if ($status_code) {
|
||||
$sql .= " AND o.status_code = ?";
|
||||
$params[] = $status_code;
|
||||
}
|
||||
|
||||
$sql .= " ORDER BY o.created_at DESC";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$offers = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
echo json_encode(['success' => true, 'offers' => $offers], JSON_UNESCAPED_UNICODE);
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log('[GET_OFFERS ERROR] ' . $e->getMessage());
|
||||
echo json_encode(['success' => false, 'message' => '서버 오류가 발생했습니다.'], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
require_once '../../bbs/db_conn.php';
|
||||
|
||||
// PDO 연결 생성
|
||||
$pdo = db_conn();
|
||||
|
||||
try {
|
||||
$base_year = $_GET['base_year'] ?? '';
|
||||
|
||||
if (empty($base_year)) {
|
||||
echo json_encode(['start_date' => '', 'end_date' => '']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT start_date, end_date
|
||||
FROM edu_contents
|
||||
WHERE category_code = 'CA10003'
|
||||
AND base_year = ?
|
||||
GROUP BY start_date, end_date
|
||||
ORDER BY start_date DESC, end_date DESC
|
||||
LIMIT 1
|
||||
");
|
||||
|
||||
$stmt->execute([$base_year]);
|
||||
$result = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($result) {
|
||||
echo json_encode([
|
||||
'start_date' => $result['start_date'],
|
||||
'end_date' => $result['end_date']
|
||||
]);
|
||||
} else {
|
||||
echo json_encode(['start_date' => '', 'end_date' => '']);
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(['error' => $e->getMessage()]);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
require_once '../../bbs/db_conn.php';
|
||||
|
||||
// PDO 연결 생성
|
||||
$pdo = db_conn();
|
||||
|
||||
try {
|
||||
// 寃???뚮씪誘명꽣 諛쏄린
|
||||
$belong_comp = $_GET['belong_comp'] ?? '';
|
||||
$working_comp = $_GET['working_comp'] ?? '';
|
||||
$member_id = $_GET['member_id'] ?? '';
|
||||
$name = $_GET['name'] ?? '';
|
||||
$dept_name = $_GET['dept_name'] ?? '';
|
||||
$auth_level = $_GET['auth_level'] ?? '';
|
||||
|
||||
// SQL 荑쇰━ 以鍮?
|
||||
$sql = "
|
||||
SELECT
|
||||
u.member_id,
|
||||
u.name,
|
||||
u.dept_name,
|
||||
u.rank_name,
|
||||
u.auth_level,
|
||||
COALESCE(c1.code_name, u.belong_comp) AS belong_comp_name,
|
||||
COALESCE(c2.code_name, u.working_comp) AS working_comp_name,
|
||||
fn_get_code_name(u.auth_level) AS auth_level_name
|
||||
FROM edu_users u
|
||||
LEFT JOIN edu_codes c1 ON c1.group_code = 'CO100' AND c1.code = u.belong_comp
|
||||
LEFT JOIN edu_codes c2 ON c2.group_code = 'CO100' AND c2.code = u.working_comp
|
||||
WHERE 1=1
|
||||
";
|
||||
|
||||
$params = [];
|
||||
$types = '';
|
||||
|
||||
if (!empty($belong_comp)) {
|
||||
$sql .= " AND u.belong_comp = ?";
|
||||
$params[] = $belong_comp;
|
||||
$types .= 's';
|
||||
}
|
||||
if (!empty($working_comp)) {
|
||||
$sql .= " AND u.working_comp = ?";
|
||||
$params[] = $working_comp;
|
||||
$types .= 's';
|
||||
}
|
||||
if (!empty($member_id)) {
|
||||
$sql .= " AND u.member_id LIKE ?";
|
||||
$params[] = '%' . $member_id . '%';
|
||||
$types .= 's';
|
||||
}
|
||||
if (!empty($name)) {
|
||||
$sql .= " AND u.name LIKE ?";
|
||||
$params[] = '%' . $name . '%';
|
||||
$types .= 's';
|
||||
}
|
||||
if (!empty($dept_name)) {
|
||||
$sql .= " AND u.dept_name LIKE ?";
|
||||
$params[] = '%' . $dept_name . '%';
|
||||
$types .= 's';
|
||||
}
|
||||
if (!empty($auth_level)) {
|
||||
$sql .= " AND u.auth_level = ?";
|
||||
$params[] = $auth_level;
|
||||
$types .= 's';
|
||||
}
|
||||
|
||||
$sql .= " ORDER BY u.name ASC";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
if (!empty($params)) {
|
||||
$stmt->execute($params);
|
||||
} else {
|
||||
$stmt->execute();
|
||||
}
|
||||
|
||||
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
echo json_encode(['success' => true, 'users' => $users], JSON_UNESCAPED_UNICODE);
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(['success' => false, 'message' => $e->getMessage()], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
// 데이터베이스 연결 설정 파일을 불러옵니다.
|
||||
require __DIR__ . '/../../bbs/db_conn.php';
|
||||
|
||||
// PDO 연결 생성
|
||||
$pdo = db_conn();
|
||||
|
||||
// POST 요청이 아닌 경우 접근을 차단하고 목록 페이지로 돌려보냅니다.
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
header('Location: ../skin/content_upload.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
// POST로 전달받은 학습목표코드(goal_code)가 있으면 공백을 제거하여 변수에 저장하고, 없으면 빈 문자열을 할당합니다.
|
||||
$goal_code = isset($_POST['goal_code']) ? trim($_POST['goal_code']) : '';
|
||||
|
||||
// 학습목표코드가 정상적으로 전달되었는지 확인합니다.
|
||||
if ($goal_code !== '') {
|
||||
try {
|
||||
// 해당 학습목표코드를 삭제하는 DELETE 쿼리를 준비합니다.
|
||||
$stmt = $pdo->prepare("DELETE FROM edu_learning_goals WHERE goal_code = :goal_code");
|
||||
// 전달받은 학습목표코드를 바인딩하여 쿼리를 실행합니다.
|
||||
$stmt->execute([':goal_code' => $goal_code]);
|
||||
|
||||
// Ajax 요청에 맞춰 JSON 응답을 반환합니다.
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
echo json_encode(['success' => true]);
|
||||
exit;
|
||||
} catch (PDOException $e) {
|
||||
// DB 쿼리 실행 중 에러가 발생하면 JSON 에러 응답을 반환합니다.
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
echo json_encode(['success' => false, 'message' => 'DB 오류: ' . $e->getMessage()]);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// 전달된 코드가 없는 경우 실패 응답을 반환합니다.
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
echo json_encode(['success' => false, 'message' => 'goal_code 가 없습니다.']);
|
||||
exit;
|
||||
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
// DB 접속 설정을 포함합니다.
|
||||
require __DIR__ . '/../../bbs/db_conn.php';
|
||||
|
||||
// PDO 연결 생성
|
||||
$pdo = db_conn();
|
||||
|
||||
// POST 요청이 아닐 경우 비정상 접근으로 간주하고 업로드 페이지로 돌려보냅니다.
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
header('Location: ../skin/content_upload.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* 신규 학습목표 코드를 생성하는 함수입니다.
|
||||
* 예) 2026년에 첫 등록이면 2026-001, 이후 2026-002 등으로 자동 번호를 부여합니다.
|
||||
*/
|
||||
function generate_goal_code(PDO $pdo, string $year): string
|
||||
{
|
||||
$prefix = $year . '-'; // 연도- 문자열 접두사 생성 (예: 2026-)
|
||||
// 해당 연도의 접두사로 시작하는 코드 중 가장 큰 값(가장 최근 번호)을 찾습니다.
|
||||
$stmt = $pdo->prepare("SELECT MAX(goal_code) AS max_code FROM edu_learning_goals WHERE goal_code LIKE :pfx");
|
||||
$stmt->execute([':pfx' => $prefix . '%']);
|
||||
$max = $stmt->fetchColumn();
|
||||
|
||||
$next = 1; // 기본 순번은 1로 설정합니다.
|
||||
// 이전에 등록된 코드가 있다면, 마지막 세 자리 숫자를 추출하여 1을 더해줍니다.
|
||||
if ($max) {
|
||||
$seq = (int) substr($max, -3);
|
||||
$next = $seq + 1;
|
||||
}
|
||||
// 생성된 번호를 3자리 형식(001, 002 등)으로 포맷팅한 후 반환합니다.
|
||||
return $prefix . str_pad((string) $next, 3, '0', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
// POST로 전달된 값들을 변수에 대입합니다. 빈 값 처리와 trim을 수행합니다.
|
||||
$goal_code = isset($_POST['goal_code']) ? trim($_POST['goal_code']) : '';
|
||||
$title = isset($_POST['title']) ? trim($_POST['title']) : '';
|
||||
$base_year = isset($_POST['base_year']) ? trim($_POST['base_year']) : date('Y');
|
||||
$quarter = isset($_POST['quarter']) ? trim($_POST['quarter']) : null;
|
||||
$is_active = isset($_POST['is_active']) ? '1' : '0';
|
||||
$sort_order = isset($_POST['sort_order']) && $_POST['sort_order'] !== '' ? (int) $_POST['sort_order'] : null;
|
||||
$remarks = isset($_POST['remarks']) ? trim($_POST['remarks']) : null;
|
||||
|
||||
// 필수 입력 항목인 제목과 기준년도가 누락되었다면 에러 파라미터와 함께 되돌아갑니다.
|
||||
if ($title === '' || $base_year === '') {
|
||||
header('Location: ../skin/content_upload.php?goal_error=required');
|
||||
exit;
|
||||
}
|
||||
|
||||
$goal_no = isset($_POST['goal_no']) ? trim($_POST['goal_no']) : null;
|
||||
$qParam = $quarter !== '' ? $quarter : null;
|
||||
$gnParam = $goal_no !== '' ? $goal_no : null;
|
||||
|
||||
// Validate uniqueness of year, quarter and goal_no
|
||||
if ($gnParam !== null && $qParam !== null) {
|
||||
$dupSql = "SELECT COUNT(*) FROM edu_learning_goals WHERE base_year = :by AND quarter = :q AND goal_no = :gn";
|
||||
$dupParams = [':by' => $base_year, ':q' => $qParam, ':gn' => $gnParam];
|
||||
if ($goal_code !== '') {
|
||||
$dupSql .= " AND goal_code != :gc";
|
||||
$dupParams[':gc'] = $goal_code;
|
||||
}
|
||||
$chk = $pdo->prepare($dupSql);
|
||||
$chk->execute($dupParams);
|
||||
if ($chk->fetchColumn() > 0) {
|
||||
echo json_encode(['success' => false, 'message' => '해당 년도/분기에 이미 같은 책장번호가 사용되었습니다.']);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// 기존에 존재하는 학습목표 코드(goal_code)가 넘겨져왔다면, 수정(UPDATE) 동작을 수행합니다.
|
||||
if ($goal_code !== '') {
|
||||
// 업데이트할 필드들을 지정하는 쿼리입니다.
|
||||
$sql = "UPDATE edu_learning_goals SET title=:title, quarter=:quarter, base_year=:base_year, is_active=:is_active, sort_order=:sort_order, remarks=:remarks, goal_no=:goal_no WHERE goal_code=:goal_code";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
|
||||
// 파라미터를 바인딩합니다.
|
||||
$stmt->bindValue(':title', $title);
|
||||
$stmt->bindValue(':quarter', $quarter !== '' ? $quarter : null, $quarter !== '' ? PDO::PARAM_STR : PDO::PARAM_NULL);
|
||||
$stmt->bindValue(':base_year', $base_year);
|
||||
$stmt->bindValue(':is_active', $is_active);
|
||||
// 정렬 순서 파라미터는 null 허용이므로 분기 처리하여 바인딩합니다.
|
||||
if ($sort_order === null) {
|
||||
$stmt->bindValue(':sort_order', null, PDO::PARAM_NULL);
|
||||
} else {
|
||||
$stmt->bindValue(':sort_order', $sort_order, PDO::PARAM_INT);
|
||||
}
|
||||
$stmt->bindValue(':remarks', $remarks !== '' ? $remarks : null, $remarks !== '' ? PDO::PARAM_STR : PDO::PARAM_NULL);
|
||||
$stmt->bindValue(':goal_no', $goal_no !== '' ? $goal_no : null, $goal_no !== '' ? PDO::PARAM_STR : PDO::PARAM_NULL);
|
||||
$stmt->bindValue(':goal_code', $goal_code);
|
||||
|
||||
// 수정 쿼리를 실행합니다.
|
||||
$stmt->execute();
|
||||
}
|
||||
// 전송된 코드가 없다면, 신규 등록(INSERT) 동작을 수행합니다.
|
||||
else {
|
||||
// 위의 생성 함수를 통해 새로운 코드를 채번합니다.
|
||||
$goal_code = generate_goal_code($pdo, $base_year);
|
||||
$sql = "INSERT INTO edu_learning_goals (goal_code, title, quarter, base_year, is_active, sort_order, remarks, goal_no, created_at) VALUES (:goal_code, :title, :quarter, :base_year, :is_active, :sort_order, :remarks, :goal_no, NOW())";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
|
||||
$stmt->bindValue(':goal_code', $goal_code);
|
||||
$stmt->bindValue(':title', $title);
|
||||
$stmt->bindValue(':quarter', $quarter !== '' ? $quarter : null, $quarter !== '' ? PDO::PARAM_STR : PDO::PARAM_NULL);
|
||||
$stmt->bindValue(':base_year', $base_year);
|
||||
$stmt->bindValue(':is_active', $is_active);
|
||||
if ($sort_order === null) {
|
||||
$stmt->bindValue(':sort_order', null, PDO::PARAM_NULL);
|
||||
} else {
|
||||
$stmt->bindValue(':sort_order', $sort_order, PDO::PARAM_INT);
|
||||
}
|
||||
$stmt->bindValue(':remarks', $remarks !== '' ? $remarks : null, $remarks !== '' ? PDO::PARAM_STR : PDO::PARAM_NULL);
|
||||
$stmt->bindValue(':goal_no', $goal_no !== '' ? $goal_no : null, $goal_no !== '' ? PDO::PARAM_STR : PDO::PARAM_NULL);
|
||||
|
||||
// 등록 쿼리를 실행합니다.
|
||||
$stmt->execute();
|
||||
}
|
||||
|
||||
// AJAX JSON response
|
||||
echo json_encode(['success' => true]);
|
||||
exit;
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
require __DIR__ . '/../../bbs/db_conn.php';
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// PDO 연결 생성
|
||||
$pdo = db_conn();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
echo json_encode(['success' => false, 'message' => 'Invalid request']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$content_id = isset($_POST['content_id']) ? trim($_POST['content_id']) : '';
|
||||
$keywords_csv = isset($_POST['keywords']) ? trim($_POST['keywords']) : '';
|
||||
$admin_id = 'admin'; // User login ID not provided in context, defaulting to 'admin'
|
||||
|
||||
if ($content_id === '') {
|
||||
echo json_encode(['success' => false, 'message' => 'missing content_id']);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo->beginTransaction();
|
||||
|
||||
// Set all existing active keywords to inactive first
|
||||
$updateStmt = $pdo->prepare("UPDATE edu_content_keywords SET is_active='0', updated_at=NOW(), updated_by=:admin WHERE content_id=:c AND is_active='1'");
|
||||
$updateStmt->execute([':c' => $content_id, ':admin' => $admin_id]);
|
||||
|
||||
if ($keywords_csv !== '') {
|
||||
$keywords = explode(',', $keywords_csv);
|
||||
$insertStmt = $pdo->prepare("INSERT INTO edu_content_keywords (content_id, keyword_code, is_active, created_by, created_at, updated_by, updated_at)
|
||||
VALUES (:c, :k, '1', :admin1, NOW(), :admin2, NOW())
|
||||
ON DUPLICATE KEY UPDATE is_active='1', updated_by=:admin3, updated_at=NOW()");
|
||||
foreach ($keywords as $k) {
|
||||
$k = trim($k);
|
||||
if ($k !== '') {
|
||||
$insertStmt->execute([
|
||||
':c' => $content_id,
|
||||
':k' => $k,
|
||||
':admin1' => $admin_id,
|
||||
':admin2' => $admin_id,
|
||||
':admin3' => $admin_id
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$pdo->commit();
|
||||
echo json_encode(['success' => true]);
|
||||
} catch (Exception $e) {
|
||||
$pdo->rollBack();
|
||||
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"quarter_qty": 3085,
|
||||
"access_qty": 85,
|
||||
"quarter_qty_u": 3207,
|
||||
"access_qty_u": 1192,
|
||||
"legal_qty_all": 2805,
|
||||
"legal_qty_y": 476,
|
||||
"goals_qty": 41,
|
||||
"goals_completed_qty": 2,
|
||||
"completed_qty": 4,
|
||||
"completed_qty_u": 541
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../bbs/db_conn.php';
|
||||
|
||||
$search_comp = $_GET['comp'] ?? '';
|
||||
$search_year = $_GET['year'] ?? date('Y');
|
||||
$search_dept = $_GET['dept'] ?? '';
|
||||
$search_name = $_GET['name'] ?? '';
|
||||
$search_comp_status = $_GET['comp_status'] ?? '';
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
|
||||
// G1 메인 쿼리와 동일한 쿼리 사용
|
||||
// $sql_inner = "SELECT a.sys_comp_code, a.belong_comp
|
||||
// , (SELECT code_name FROM edu_codes c WHERE c.group_code = 'CO100' AND c.code = a.belong_comp LIMIT 1) AS comp_name
|
||||
// , a.name, a.member_id
|
||||
// , a.dept_name
|
||||
// , IFNULL(b.formatted_tm, '00시간 00분') AS all_tm
|
||||
// , CASE WHEN a.member_id IN (
|
||||
// SELECT u2.member_id FROM edu_users u2 WHERE NOT EXISTS (
|
||||
// SELECT 1 FROM edu_contents c2 WHERE c2.category_code = 'CA10003' AND c2.base_year = ? AND c2.is_active = '1'
|
||||
// AND NOT EXISTS (SELECT 1 FROM edu_learning_histories h2 WHERE h2.content_id = c2.content_id AND h2.member_id = u2.member_id AND h2.sys_comp_code = u2.sys_comp_code AND h2.completed_at IS NOT NULL AND h2.completed_at != '')
|
||||
// )
|
||||
// ) THEN '수료' ELSE '미수료' END AS completion_status
|
||||
// , fn_get_progress_rate(a.sys_comp_code, ?, a.member_id, 'CA10003', '') AS progress_rate
|
||||
// , fn_get_completion_date(a.sys_comp_code, ?, a.member_id, 'CA10003', '') AS completion_date
|
||||
// FROM edu_users a
|
||||
// LEFT JOIN (
|
||||
// SELECT x.sys_comp_code, x.member_id,
|
||||
// CONCAT(LPAD(FLOOR(SUM(CASE WHEN x.completed_at IS NOT NULL AND x.completed_at <> '' THEN x.content_tm ELSE x.watch_tm END)/3600),2,'0'),'시간 ',
|
||||
// LPAD(FLOOR((SUM(CASE WHEN x.completed_at IS NOT NULL AND x.completed_at <> '' THEN x.content_tm ELSE x.watch_tm END)%3600)/60),2,'0'),'분') AS formatted_tm
|
||||
// FROM edu_learning_histories x JOIN edu_contents y ON x.content_id = y.content_id
|
||||
// WHERE y.category_code = 'CA10003' AND YEAR(x.first_viewed_at) = ?
|
||||
// GROUP BY x.sys_comp_code, x.member_id
|
||||
// ) b ON a.member_id = b.member_id AND a.sys_comp_code = b.sys_comp_code
|
||||
// WHERE (a.end_date IS NULL OR a.end_date = '' OR (a.end_date > '1000-01-01' AND YEAR(a.end_date) >= ?))
|
||||
// AND (? = '' OR a.belong_comp = ?)
|
||||
// AND a.dept_name LIKE CONCAT('%', ?, '%')
|
||||
// AND a.name LIKE CONCAT('%', ?, '%')";
|
||||
|
||||
$sql_inner = "SELECT a.sys_comp_code, a.belong_comp
|
||||
, (SELECT code_name FROM edu_codes c WHERE c.group_code = 'CO100' AND c.code = a.belong_comp LIMIT 1) AS comp_name
|
||||
, a.name, a.member_id
|
||||
, a.dept_name
|
||||
, IFNULL(b.formatted_tm, '00시간 00분') AS all_tm
|
||||
, CASE WHEN a.member_id IN (
|
||||
SELECT u2.member_id FROM edu_users u2 WHERE NOT EXISTS (
|
||||
SELECT 1 FROM edu_contents c2 WHERE c2.category_code = 'CA10003' AND c2.base_year = ? AND c2.is_active = '1'
|
||||
AND NOT EXISTS (SELECT 1 FROM edu_learning_histories h2 WHERE h2.content_id = c2.content_id AND h2.member_id = u2.member_id AND h2.sys_comp_code = u2.sys_comp_code AND h2.completed_at IS NOT NULL AND h2.completed_at != '')
|
||||
)
|
||||
) THEN '수료' ELSE '미수료' END AS completion_status
|
||||
, fn_get_progress_rate(a.sys_comp_code, ?, a.member_id, 'CA10003', '') AS progress_rate -- 진행율
|
||||
, fn_get_completion_date(a.sys_comp_code, ?, a.member_id, 'CA10003', '') AS completion_date -- 학습완료일
|
||||
FROM edu_users a
|
||||
LEFT JOIN (
|
||||
SELECT z.sys_comp_code, x.member_id,
|
||||
CONCAT(LPAD(FLOOR(SUM(CASE WHEN x.completed_at IS NOT NULL AND x.completed_at <> '' THEN x.content_tm ELSE x.watch_tm END)/3600),2,'0'),'시간 ',
|
||||
LPAD(FLOOR((SUM(CASE WHEN x.completed_at IS NOT NULL AND x.completed_at <> '' THEN x.content_tm ELSE x.watch_tm END)%3600)/60),2,'0'),'분') AS formatted_tm
|
||||
FROM edu_learning_histories x JOIN edu_contents y ON x.content_id = y.content_id
|
||||
JOIN edu_users z ON x.sys_comp_code = z.working_comp AND x.member_id = z.member_id
|
||||
WHERE y.category_code = 'CA10003' AND YEAR(x.first_viewed_at) = ?
|
||||
GROUP BY x.sys_comp_code, x.member_id
|
||||
) b ON a.member_id = b.member_id AND a.sys_comp_code = b.sys_comp_code
|
||||
WHERE (a.end_date IS NULL OR a.end_date = '' OR (a.end_date > '1000-01-01' AND YEAR(a.end_date) >= ?))
|
||||
and a.sys_comp_code = a.belong_comp
|
||||
AND (? = '' OR a.belong_comp = ?)
|
||||
AND a.dept_name LIKE CONCAT('%', ?, '%')
|
||||
AND a.name LIKE CONCAT('%', ?, '%')";
|
||||
|
||||
|
||||
if ($search_comp_status === 'Y') {
|
||||
$sql = "SELECT * FROM ($sql_inner) t WHERE completion_status = '수료'";
|
||||
} elseif ($search_comp_status === 'N') {
|
||||
$sql = "SELECT * FROM ($sql_inner) t WHERE completion_status = '미수료'";
|
||||
} else {
|
||||
$sql = "SELECT * FROM ($sql_inner) t";
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$search_year, $search_year, $search_year, $search_year, $search_year, $search_comp, $search_comp, $search_dept, $search_name]);
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
|
||||
} catch (Exception $e) {
|
||||
die("DB Error");
|
||||
}
|
||||
|
||||
header("Content-Type: application/vnd.ms-excel; charset=utf-8");
|
||||
header("Content-Disposition: attachment; filename=legal_edu_result_" . date('Ymd') . ".xls");
|
||||
header("Expires: 0");
|
||||
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
|
||||
header("Pragma: public");
|
||||
|
||||
echo '<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"></head><body>';
|
||||
echo '<table border="1">';
|
||||
echo '<thead><tr>';
|
||||
echo '<th>NO</th>';
|
||||
echo '<th>소속법인</th>';
|
||||
echo '<th>성명</th>';
|
||||
echo '<th>사번</th>';
|
||||
echo '<th>부서</th>';
|
||||
echo '<th>학습시간</th>';
|
||||
echo '<th>진도율</th>';
|
||||
echo '<th>교육이수일</th>';
|
||||
echo '<th>수료구분</th>';
|
||||
echo '</tr></thead>';
|
||||
echo '<tbody>';
|
||||
|
||||
$idx = 1;
|
||||
foreach ($rows as $row) {
|
||||
echo '<tr>';
|
||||
echo '<td>' . $idx++ . '</td>';
|
||||
echo '<td>' . htmlspecialchars($row['comp_name'] ?? '') . '</td>';
|
||||
echo '<td>' . htmlspecialchars($row['name'] ?? '') . '</td>';
|
||||
echo '<td>' . htmlspecialchars($row['member_id'] ?? '') . '</td>';
|
||||
echo '<td>' . htmlspecialchars($row['dept_name'] ?? '') . '</td>';
|
||||
echo '<td>' . htmlspecialchars($row['all_tm'] ?? '') . '</td>';
|
||||
|
||||
// 진행율 처리
|
||||
$progress = $row['progress_rate'] ?? '0%';
|
||||
$progress_value = is_numeric($progress) ? (int) $progress : (int) preg_replace('/[^0-9]/', '', $progress);
|
||||
echo '<td>' . $progress_value . '%</td>';
|
||||
|
||||
echo '<td>' . htmlspecialchars($row['completion_date'] ?? '-') . '</td>';
|
||||
echo '<td>' . htmlspecialchars($row['completion_status']) . '</td>';
|
||||
echo '</tr>';
|
||||
}
|
||||
|
||||
echo '</tbody></table></body></html>';
|
||||
@@ -0,0 +1,614 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../bbs/auth.php';
|
||||
edu_require_login();
|
||||
require_once __DIR__ . '/../../bbs/db_conn.php';
|
||||
|
||||
$search_comp = $_GET['comp'] ?? '';
|
||||
if (empty($search_comp) && !isset($_GET['comp'])) {
|
||||
$search_comp = $sys_comp_code;
|
||||
}
|
||||
$search_year = $_GET['year'] ?? date('Y');
|
||||
|
||||
// 권한에 따른 법인 제한
|
||||
if ($auth_level === 'LE10002') {
|
||||
$search_comp = $sys_comp_code;
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
|
||||
// 1) proc_get_legal_total_report 호출
|
||||
$stmt_total = $pdo->prepare("CALL proc_get_legal_total_report(?, '', ?)");
|
||||
$stmt_total->execute([$search_comp, $search_year]);
|
||||
$total_data = $stmt_total->fetch(PDO::FETCH_ASSOC);
|
||||
while ($stmt_total->nextRowset()) {}
|
||||
unset($stmt_total);
|
||||
|
||||
// 만약 전체조회 등 데이터가 없을 경우 기본값 세팅
|
||||
if (!$total_data) {
|
||||
$total_data = [
|
||||
'title01' => $search_year . '년 법정의무교육',
|
||||
'title02' => '교육결과보고서',
|
||||
'edu_purpose' => '',
|
||||
'corp_name' => '',
|
||||
'edu_term' => '',
|
||||
'edu_category' => '',
|
||||
'all_user_qty' => 0,
|
||||
'all_completed_qty' => 0,
|
||||
'rmks' => ''
|
||||
];
|
||||
}
|
||||
|
||||
// 2) proc_get_legal_report_by_year_category_sum 호출
|
||||
$stmt_sum = $pdo->prepare("CALL proc_get_legal_report_by_year_category_sum(?, '', ?)");
|
||||
$stmt_sum->execute([$search_comp, $search_year]);
|
||||
$sum_rows = $stmt_sum->fetchAll(PDO::FETCH_ASSOC);
|
||||
while ($stmt_sum->nextRowset()) {}
|
||||
unset($stmt_sum);
|
||||
|
||||
// 3) proc_get_legal_report 호출 (과정별 시트 데이터)
|
||||
$stmt_detail = $pdo->prepare("CALL proc_get_legal_report(?, '', ?)");
|
||||
$stmt_detail->execute([$search_comp, $search_year]);
|
||||
$detail_rows = $stmt_detail->fetchAll(PDO::FETCH_ASSOC);
|
||||
while ($stmt_detail->nextRowset()) {}
|
||||
unset($stmt_detail);
|
||||
|
||||
} catch (Exception $e) {
|
||||
die("DB Error: " . $e->getMessage());
|
||||
}
|
||||
|
||||
// HTTP Header 설정 - Excel 파일 다운로드 유도
|
||||
$safe_corp_name = preg_replace('/[\/\\:*?"<>|]/', '', $total_data['corp_name'] ?? '');
|
||||
$safe_corp_name = trim($safe_corp_name);
|
||||
if (empty($safe_corp_name)) {
|
||||
$safe_corp_name = '법정의무교육';
|
||||
}
|
||||
$download_filename = $safe_corp_name . "_교육결과보고서_" . date('Ymd') . ".xls";
|
||||
|
||||
header("Content-Type: application/vnd.ms-excel; charset=utf-8");
|
||||
header("Content-Disposition: attachment; filename=\"" . $download_filename . "\"; filename*=UTF-8''" . rawurlencode($download_filename));
|
||||
header("Expires: 0");
|
||||
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
|
||||
header("Pragma: public");
|
||||
|
||||
// XML Spreadsheet 2003 양식 작성 시작
|
||||
echo '<?xml version="1.0" encoding="utf-8"?>' . "\n";
|
||||
echo '<?mso-application progid="Excel.Sheet"?>' . "\n";
|
||||
?>
|
||||
<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet"
|
||||
xmlns:o="urn:schemas-microsoft-com:office:office"
|
||||
xmlns:x="urn:schemas-microsoft-com:office:excel"
|
||||
xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"
|
||||
xmlns:html="http://www.w3.org/TR/REC-html40">
|
||||
<DocumentProperties xmlns="urn:schemas-microsoft-com:office:office">
|
||||
<Author>Baron Consultant</Author>
|
||||
<LastAuthor>Baron Consultant</LastAuthor>
|
||||
<Created><?php echo date('Y-m-d\TH:i:s\Z'); ?></Created>
|
||||
<Version>16.00</Version>
|
||||
</DocumentProperties>
|
||||
<OfficeDocumentSettings xmlns="urn:schemas-microsoft-com:office:office">
|
||||
<AllowPNG/>
|
||||
</OfficeDocumentSettings>
|
||||
<ExcelWorkbook xmlns="urn:schemas-microsoft-com:office:excel">
|
||||
<WindowHeight>9000</WindowHeight>
|
||||
<WindowWidth>13600</WindowWidth>
|
||||
<WindowTopX>0</WindowTopX>
|
||||
<WindowTopY>0</WindowTopY>
|
||||
<ProtectStructure>False</ProtectStructure>
|
||||
<ProtectWindows>False</ProtectWindows>
|
||||
</ExcelWorkbook>
|
||||
<Styles>
|
||||
<!-- 기본 폰트 및 정렬 -->
|
||||
<Style ss:ID="Default" ss:Name="Normal">
|
||||
<Alignment ss:Vertical="Center"/>
|
||||
<Borders/>
|
||||
<Font ss:FontName="맑은 고딕" x:CharSet="129" ss:Size="10" ss:Color="#000000"/>
|
||||
<Interior/>
|
||||
<NumberFormat/>
|
||||
<Protection/>
|
||||
</Style>
|
||||
<!-- 메인 대제목 -->
|
||||
<Style ss:ID="sMainTitle">
|
||||
<Alignment ss:Horizontal="Center" ss:Vertical="Center" ss:WrapText="1"/>
|
||||
<Font ss:FontName="맑은 고딕" ss:Size="18" ss:Bold="1" ss:Color="#111111"/>
|
||||
</Style>
|
||||
<!-- 소제목 (1. 교육목적 등) -->
|
||||
<Style ss:ID="sSecTitle">
|
||||
<Alignment ss:Horizontal="Left" ss:Vertical="Center"/>
|
||||
<Font ss:FontName="맑은 고딕" ss:Size="11" ss:Bold="1" ss:Color="#114b3d"/>
|
||||
</Style>
|
||||
<!-- 교육목적 단락용 스타일 (자동 줄바꿈, 옅은 회색 배경) -->
|
||||
<Style ss:ID="sPurposeText">
|
||||
<Alignment ss:Horizontal="Left" ss:Vertical="Top" ss:WrapText="1"/>
|
||||
<Borders>
|
||||
<Border ss:Position="Top" ss:LineStyle="Continuous" ss:Weight="1" ss:Color="#cccccc"/>
|
||||
<Border ss:Position="Bottom" ss:LineStyle="Continuous" ss:Weight="1" ss:Color="#cccccc"/>
|
||||
<Border ss:Position="Left" ss:LineStyle="Continuous" ss:Weight="1" ss:Color="#cccccc"/>
|
||||
<Border ss:Position="Right" ss:LineStyle="Continuous" ss:Weight="1" ss:Color="#cccccc"/>
|
||||
</Borders>
|
||||
<Font ss:FontName="맑은 고딕" ss:Size="9.5" ss:Color="#333333"/>
|
||||
<Interior ss:Color="#fafafa" ss:Pattern="Solid"/>
|
||||
</Style>
|
||||
<!-- 테이블 헤더 (진회색 배경) -->
|
||||
<Style ss:ID="sTblHeader">
|
||||
<Alignment ss:Horizontal="Center" ss:Vertical="Center" ss:WrapText="1"/>
|
||||
<Borders>
|
||||
<Border ss:Position="Top" ss:LineStyle="Continuous" ss:Weight="1" ss:Color="#b2b2b2"/>
|
||||
<Border ss:Position="Bottom" ss:LineStyle="Continuous" ss:Weight="1" ss:Color="#b2b2b2"/>
|
||||
<Border ss:Position="Left" ss:LineStyle="Continuous" ss:Weight="1" ss:Color="#b2b2b2"/>
|
||||
<Border ss:Position="Right" ss:LineStyle="Continuous" ss:Weight="1" ss:Color="#b2b2b2"/>
|
||||
</Borders>
|
||||
<Font ss:FontName="맑은 고딕" ss:Size="10" ss:Bold="1" ss:Color="#333333"/>
|
||||
<Interior ss:Color="#e8ecef" ss:Pattern="Solid"/>
|
||||
</Style>
|
||||
<!-- 테이블 헤더 (종합시트용 민트/그린 계열) -->
|
||||
<Style ss:ID="sTblHeaderTeal">
|
||||
<Alignment ss:Horizontal="Center" ss:Vertical="Center" ss:WrapText="1"/>
|
||||
<Borders>
|
||||
<Border ss:Position="Top" ss:LineStyle="Continuous" ss:Weight="1" ss:Color="#cccccc"/>
|
||||
<Border ss:Position="Bottom" ss:LineStyle="Continuous" ss:Weight="2" ss:Color="#114b3d"/>
|
||||
<Border ss:Position="Left" ss:LineStyle="Continuous" ss:Weight="1" ss:Color="#cccccc"/>
|
||||
<Border ss:Position="Right" ss:LineStyle="Continuous" ss:Weight="1" ss:Color="#cccccc"/>
|
||||
</Borders>
|
||||
<Font ss:FontName="맑은 고딕" ss:Size="10" ss:Bold="1" ss:Color="#333333"/>
|
||||
<Interior ss:Color="#f0f5f4" ss:Pattern="Solid"/>
|
||||
</Style>
|
||||
<!-- 테이블 셀 (가운데 정렬, 얇은 테두리) -->
|
||||
<Style ss:ID="sCellCenter">
|
||||
<Alignment ss:Horizontal="Center" ss:Vertical="Center" ss:WrapText="1"/>
|
||||
<Borders>
|
||||
<Border ss:Position="Top" ss:LineStyle="Continuous" ss:Weight="1" ss:Color="#dddddd"/>
|
||||
<Border ss:Position="Bottom" ss:LineStyle="Continuous" ss:Weight="1" ss:Color="#dddddd"/>
|
||||
<Border ss:Position="Left" ss:LineStyle="Continuous" ss:Weight="1" ss:Color="#dddddd"/>
|
||||
<Border ss:Position="Right" ss:LineStyle="Continuous" ss:Weight="1" ss:Color="#dddddd"/>
|
||||
</Borders>
|
||||
<Font ss:FontName="맑은 고딕" ss:Size="10" ss:Color="#333333"/>
|
||||
</Style>
|
||||
<!-- 테이블 셀 (좌측 정렬, 얇은 테두리) -->
|
||||
<Style ss:ID="sCellLeft">
|
||||
<Alignment ss:Horizontal="Left" ss:Vertical="Center" ss:WrapText="1"/>
|
||||
<Borders>
|
||||
<Border ss:Position="Top" ss:LineStyle="Continuous" ss:Weight="1" ss:Color="#dddddd"/>
|
||||
<Border ss:Position="Bottom" ss:LineStyle="Continuous" ss:Weight="1" ss:Color="#dddddd"/>
|
||||
<Border ss:Position="Left" ss:LineStyle="Continuous" ss:Weight="1" ss:Color="#dddddd"/>
|
||||
<Border ss:Position="Right" ss:LineStyle="Continuous" ss:Weight="1" ss:Color="#dddddd"/>
|
||||
</Borders>
|
||||
<Font ss:FontName="맑은 고딕" ss:Size="10" ss:Color="#333333"/>
|
||||
</Style>
|
||||
<!-- 상세 시트 - 교육내용 박스용 스타일 -->
|
||||
<Style ss:ID="sDescContent">
|
||||
<Alignment ss:Horizontal="Left" ss:Vertical="Top" ss:WrapText="1"/>
|
||||
<Borders>
|
||||
<Border ss:Position="Top" ss:LineStyle="Continuous" ss:Weight="1" ss:Color="#b2b2b2"/>
|
||||
<Border ss:Position="Bottom" ss:LineStyle="Continuous" ss:Weight="1" ss:Color="#b2b2b2"/>
|
||||
<Border ss:Position="Left" ss:LineStyle="Continuous" ss:Weight="1" ss:Color="#b2b2b2"/>
|
||||
<Border ss:Position="Right" ss:LineStyle="Continuous" ss:Weight="1" ss:Color="#b2b2b2"/>
|
||||
</Borders>
|
||||
<Font ss:FontName="맑은 고딕" ss:Size="9.5" ss:Color="#444444"/>
|
||||
<Interior ss:Color="#fafafa" ss:Pattern="Solid"/>
|
||||
</Style>
|
||||
<!-- 하단 팩스/주소 표시용 소형 폰트 -->
|
||||
<Style ss:ID="sFooter">
|
||||
<Alignment ss:Horizontal="Center" ss:Vertical="Center"/>
|
||||
<Font ss:FontName="맑은 고딕" ss:Size="8.5" ss:Color="#666666"/>
|
||||
</Style>
|
||||
<!-- "위와 같이 제출합니다" 문장용 -->
|
||||
<Style ss:ID="sSubmitText">
|
||||
<Alignment ss:Horizontal="Center" ss:Vertical="Center"/>
|
||||
<Font ss:FontName="맑은 고딕" ss:Size="11" ss:Bold="1" ss:Color="#111111"/>
|
||||
</Style>
|
||||
<!-- 회사명 하단 푸터 -->
|
||||
<Style ss:ID="sCompanyFooter">
|
||||
<Alignment ss:Horizontal="Center" ss:Vertical="Center"/>
|
||||
<Font ss:FontName="맑은 고딕" ss:Size="14" ss:Bold="1" ss:Color="#114b3d"/>
|
||||
</Style>
|
||||
<!-- 결재박스 헤더 스타일 -->
|
||||
<Style ss:ID="sApprHeader">
|
||||
<Alignment ss:Horizontal="Center" ss:Vertical="Center" ss:WrapText="1"/>
|
||||
<Borders>
|
||||
<Border ss:Position="Top" ss:LineStyle="Continuous" ss:Weight="1" ss:Color="#b2b2b2"/>
|
||||
<Border ss:Position="Bottom" ss:LineStyle="Continuous" ss:Weight="1" ss:Color="#b2b2b2"/>
|
||||
<Border ss:Position="Left" ss:LineStyle="Continuous" ss:Weight="1" ss:Color="#b2b2b2"/>
|
||||
<Border ss:Position="Right" ss:LineStyle="Continuous" ss:Weight="1" ss:Color="#b2b2b2"/>
|
||||
</Borders>
|
||||
<Font ss:FontName="맑은 고딕" ss:Size="9.5" ss:Color="#333333"/>
|
||||
<Interior ss:Color="#f5f7f8" ss:Pattern="Solid"/>
|
||||
</Style>
|
||||
<!-- 결재박스 세로 병합 "결재" 스타일 -->
|
||||
<Style ss:ID="sApprVertical">
|
||||
<Alignment ss:Horizontal="Center" ss:Vertical="Center" ss:WrapText="1"/>
|
||||
<Borders>
|
||||
<Border ss:Position="Top" ss:LineStyle="Continuous" ss:Weight="1" ss:Color="#b2b2b2"/>
|
||||
<Border ss:Position="Bottom" ss:LineStyle="Continuous" ss:Weight="1" ss:Color="#b2b2b2"/>
|
||||
<Border ss:Position="Left" ss:LineStyle="Continuous" ss:Weight="1" ss:Color="#b2b2b2"/>
|
||||
<Border ss:Position="Right" ss:LineStyle="Continuous" ss:Weight="1" ss:Color="#b2b2b2"/>
|
||||
</Borders>
|
||||
<Font ss:FontName="맑은 고딕" ss:Size="9.5" ss:Bold="1" ss:Color="#333333"/>
|
||||
<Interior ss:Color="#f5f7f8" ss:Pattern="Solid"/>
|
||||
</Style>
|
||||
<!-- 상세시트 결재 및 제목 박스용 세부 스타일 -->
|
||||
<Style ss:ID="sCourseTitleBlock">
|
||||
<Alignment ss:Horizontal="Center" ss:Vertical="Center" ss:WrapText="1"/>
|
||||
<Borders>
|
||||
<Border ss:Position="Top" ss:LineStyle="Continuous" ss:Weight="1" ss:Color="#b2b2b2"/>
|
||||
<Border ss:Position="Bottom" ss:LineStyle="Continuous" ss:Weight="1" ss:Color="#b2b2b2"/>
|
||||
<Border ss:Position="Left" ss:LineStyle="Continuous" ss:Weight="1" ss:Color="#b2b2b2"/>
|
||||
<Border ss:Position="Right" ss:LineStyle="Continuous" ss:Weight="1" ss:Color="#b2b2b2"/>
|
||||
</Borders>
|
||||
<Font ss:FontName="맑은 고딕" ss:Size="14" ss:Bold="1" ss:Color="#111111"/>
|
||||
<Interior ss:Color="#f5f7f8" ss:Pattern="Solid"/>
|
||||
</Style>
|
||||
</Styles>
|
||||
|
||||
<!-- ========================================================================= -->
|
||||
<!-- [시트 1 : 교육결과보고서(종합)] 시작 -->
|
||||
<!-- ========================================================================= -->
|
||||
<Worksheet ss:Name="교육결과보고서(종합)">
|
||||
<Table ss:ExpandedColumnCount="9" ss:ExpandedRowCount="40" x:FullColumns="1" x:FullRows="1" ss:DefaultColumnWidth="54" ss:DefaultRowHeight="20">
|
||||
<!-- 열 너비 설정 (A: Spacer, B: 헤더/과정명, C-H: 데이터 및 요약) -->
|
||||
<Column ss:Width="20"/> <!-- A: Spacer -->
|
||||
<Column ss:Width="130"/> <!-- B -->
|
||||
<Column ss:Width="100"/> <!-- C -->
|
||||
<Column ss:Width="80"/> <!-- D -->
|
||||
<Column ss:Width="80"/> <!-- E -->
|
||||
<Column ss:Width="80"/> <!-- F -->
|
||||
<Column ss:Width="80"/> <!-- G -->
|
||||
<Column ss:Width="80"/> <!-- H -->
|
||||
|
||||
<Row ss:Height="10"/> <!-- spacer row -->
|
||||
|
||||
<!-- 대제목 -->
|
||||
<Row ss:Height="25">
|
||||
<Cell ss:Index="2" ss:MergeAcross="6" ss:StyleID="sMainTitle">
|
||||
<Data ss:Type="String"><?php echo htmlspecialchars($total_data['title01'] ?? '', ENT_QUOTES, 'UTF-8'); ?></Data>
|
||||
</Cell>
|
||||
</Row>
|
||||
<Row ss:Height="25">
|
||||
<Cell ss:Index="2" ss:MergeAcross="6" ss:StyleID="sMainTitle">
|
||||
<Data ss:Type="String"><?php echo htmlspecialchars($total_data['title02'] ?? '', ENT_QUOTES, 'UTF-8'); ?></Data>
|
||||
</Cell>
|
||||
</Row>
|
||||
|
||||
<Row ss:Height="15"/> <!-- spacer row -->
|
||||
|
||||
<!-- 1. 교육목적 -->
|
||||
<Row ss:Height="20">
|
||||
<Cell ss:Index="2" ss:MergeAcross="6" ss:StyleID="sSecTitle">
|
||||
<Data ss:Type="String">1. 교육목적</Data>
|
||||
</Cell>
|
||||
</Row>
|
||||
<Row ss:Height="70">
|
||||
<Cell ss:Index="2" ss:MergeAcross="6" ss:StyleID="sPurposeText">
|
||||
<Data ss:Type="String"><?php echo str_replace(["\r\n", "\r", "\n"], " ", htmlspecialchars($total_data['edu_purpose'] ?? '', ENT_QUOTES, 'UTF-8')); ?></Data>
|
||||
</Cell>
|
||||
</Row>
|
||||
|
||||
<Row ss:Height="15"/> <!-- spacer row -->
|
||||
|
||||
<!-- 2. 교육 운영 개요 -->
|
||||
<Row ss:Height="20">
|
||||
<Cell ss:Index="2" ss:MergeAcross="6" ss:StyleID="sSecTitle">
|
||||
<Data ss:Type="String">2. 교육 운영 개요</Data>
|
||||
</Cell>
|
||||
</Row>
|
||||
<Row ss:Height="22">
|
||||
<Cell ss:Index="2" ss:StyleID="sTblHeader"><Data ss:Type="String">사업장명</Data></Cell>
|
||||
<Cell ss:MergeAcross="5" ss:StyleID="sCellCenter">
|
||||
<Data ss:Type="String"><?php echo htmlspecialchars($total_data['corp_name'] ?? '', ENT_QUOTES, 'UTF-8'); ?></Data>
|
||||
</Cell>
|
||||
</Row>
|
||||
<Row ss:Height="22">
|
||||
<Cell ss:Index="2" ss:StyleID="sTblHeader"><Data ss:Type="String">교육일시</Data></Cell>
|
||||
<Cell ss:MergeAcross="5" ss:StyleID="sCellCenter">
|
||||
<Data ss:Type="String"><?php echo htmlspecialchars($total_data['edu_term'] ?? '', ENT_QUOTES, 'UTF-8'); ?></Data>
|
||||
</Cell>
|
||||
</Row>
|
||||
<Row ss:Height="22">
|
||||
<Cell ss:Index="2" ss:StyleID="sTblHeader"><Data ss:Type="String">교육구분</Data></Cell>
|
||||
<Cell ss:MergeAcross="5" ss:StyleID="sCellCenter">
|
||||
<Data ss:Type="String"><?php echo htmlspecialchars($total_data['edu_category'] ?? '', ENT_QUOTES, 'UTF-8'); ?></Data>
|
||||
</Cell>
|
||||
</Row>
|
||||
<Row ss:Height="22">
|
||||
<Cell ss:Index="2" ss:MergeDown="1" ss:StyleID="sTblHeader"><Data ss:Type="String">참석인원</Data></Cell>
|
||||
<Cell ss:Index="3" ss:MergeAcross="1" ss:StyleID="sTblHeader"><Data ss:Type="String">대상인원</Data></Cell>
|
||||
<Cell ss:Index="5" ss:MergeAcross="1" ss:StyleID="sTblHeader"><Data ss:Type="String">실시인원</Data></Cell>
|
||||
<Cell ss:Index="7" ss:MergeAcross="1" ss:StyleID="sTblHeader"><Data ss:Type="String">미실시인원</Data></Cell>
|
||||
</Row>
|
||||
<Row ss:Height="22">
|
||||
<?php
|
||||
$all_qty = (int)($total_data['all_user_qty'] ?? 0);
|
||||
$comp_qty = (int)($total_data['all_completed_qty'] ?? 0);
|
||||
$incomp_qty = max(0, $all_qty - $comp_qty);
|
||||
?>
|
||||
<Cell ss:Index="3" ss:MergeAcross="1" ss:StyleID="sCellCenter"><Data ss:Type="String"><?php echo $all_qty; ?>명</Data></Cell>
|
||||
<Cell ss:Index="5" ss:MergeAcross="1" ss:StyleID="sCellCenter"><Data ss:Type="String"><?php echo $comp_qty; ?>명</Data></Cell>
|
||||
<Cell ss:Index="7" ss:MergeAcross="1" ss:StyleID="sCellCenter"><Data ss:Type="String"><?php echo $incomp_qty; ?>명</Data></Cell>
|
||||
</Row>
|
||||
<Row ss:Height="22">
|
||||
<Cell ss:Index="2" ss:StyleID="sTblHeader"><Data ss:Type="String">비고</Data></Cell>
|
||||
<Cell ss:MergeAcross="5" ss:StyleID="sCellCenter">
|
||||
<Data ss:Type="String"><?php echo htmlspecialchars($total_data['rmks'] ?? '', ENT_QUOTES, 'UTF-8'); ?></Data>
|
||||
</Cell>
|
||||
</Row>
|
||||
|
||||
<Row ss:Height="15"/> <!-- spacer row -->
|
||||
|
||||
<!-- 3. 과정별 교육 이수 현황 -->
|
||||
<Row ss:Height="20">
|
||||
<Cell ss:Index="2" ss:MergeAcross="6" ss:StyleID="sSecTitle">
|
||||
<Data ss:Type="String">3. 과정별 교육 이수 현황</Data>
|
||||
</Cell>
|
||||
</Row>
|
||||
<Row ss:Height="22">
|
||||
<Cell ss:Index="2" ss:MergeAcross="1" ss:StyleID="sTblHeaderTeal"><Data ss:Type="String">과정별</Data></Cell>
|
||||
<Cell ss:Index="4" ss:MergeAcross="1" ss:StyleID="sTblHeaderTeal"><Data ss:Type="String">교육인원</Data></Cell>
|
||||
<Cell ss:Index="6" ss:StyleID="sTblHeaderTeal"><Data ss:Type="String">수료인원</Data></Cell>
|
||||
<Cell ss:Index="7" ss:MergeAcross="1" ss:StyleID="sTblHeaderTeal"><Data ss:Type="String">수료율</Data></Cell>
|
||||
</Row>
|
||||
|
||||
<?php if (empty($sum_rows)): ?>
|
||||
<Row ss:Height="22">
|
||||
<Cell ss:Index="2" ss:MergeAcross="6" ss:StyleID="sCellCenter"><Data ss:Type="String">데이터가 없습니다.</Data></Cell>
|
||||
</Row>
|
||||
<?php else: ?>
|
||||
<?php foreach ($sum_rows as $row): ?>
|
||||
<Row ss:Height="22">
|
||||
<Cell ss:Index="2" ss:MergeAcross="1" ss:StyleID="sCellLeft">
|
||||
<Data ss:Type="String"><?php echo htmlspecialchars($row['edu_name'] ?? '', ENT_QUOTES, 'UTF-8'); ?></Data>
|
||||
</Cell>
|
||||
<Cell ss:Index="4" ss:MergeAcross="1" ss:StyleID="sCellCenter">
|
||||
<Data ss:Type="String"><?php echo (int)($row['all_user_qty'] ?? 0); ?>명</Data>
|
||||
</Cell>
|
||||
<Cell ss:Index="6" ss:StyleID="sCellCenter">
|
||||
<Data ss:Type="String"><?php echo (int)($row['completed_qty'] ?? 0); ?>명</Data>
|
||||
</Cell>
|
||||
<Cell ss:Index="7" ss:MergeAcross="1" ss:StyleID="sCellCenter">
|
||||
<?php
|
||||
$rate = $row['completed_rate'] ?? '0';
|
||||
if (is_numeric($rate)) {
|
||||
$rate = number_format((float)$rate, 1);
|
||||
}
|
||||
?>
|
||||
<Data ss:Type="String"><?php echo $rate; ?>%</Data>
|
||||
</Cell>
|
||||
</Row>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<Row ss:Height="30"/> <!-- spacer row -->
|
||||
|
||||
<!-- 최하단 주소 및 연락처 푸터 -->
|
||||
<Row ss:Height="20">
|
||||
<Cell ss:Index="2" ss:MergeAcross="6" ss:StyleID="sFooter">
|
||||
<Data ss:Type="String">서울시 송파구 오금로 554 한맥B/D TEL | 02-1234-5600 FAX | 02-1234-5600</Data>
|
||||
</Cell>
|
||||
</Row>
|
||||
</Table>
|
||||
<WorksheetOptions xmlns="urn:schemas-microsoft-com:office:excel">
|
||||
<PageSetup>
|
||||
<Layout x:Orientation="Portrait"/>
|
||||
<Header x:Margin="0.3"/>
|
||||
<Footer x:Margin="0.3"/>
|
||||
<PageMargins x:Bottom="0.75" x:Left="0.7" x:Right="0.7" x:Top="0.75"/>
|
||||
</PageSetup>
|
||||
<FitToPage/>
|
||||
<Print>
|
||||
<FitWidth>1</FitWidth>
|
||||
<FitHeight>1</FitHeight>
|
||||
<PaperSizeIndex>9</PaperSizeIndex>
|
||||
</Print>
|
||||
</WorksheetOptions>
|
||||
</Worksheet>
|
||||
|
||||
<!-- ========================================================================= -->
|
||||
<!-- [시트 2~ : 교육과정별 결과보고서] 시작 -->
|
||||
<!-- ========================================================================= -->
|
||||
<?php foreach ($detail_rows as $dIdx => $dRow): ?>
|
||||
<?php
|
||||
$rawName = $dRow['edu_name'] ?? '교육과정';
|
||||
// 특수문자 제거
|
||||
$cleanName = str_replace([':', '\\', '/', '?', '*', '[', ']'], '', $rawName);
|
||||
// 인덱스를 붙여 유일성 보장 및 31글자 제한 내 절단
|
||||
$sheetName = ($dIdx + 1) . '. ' . $cleanName;
|
||||
$sheetName = mb_strimwidth($sheetName, 0, 31, "");
|
||||
?>
|
||||
<Worksheet ss:Name="<?php echo htmlspecialchars($sheetName, ENT_QUOTES, 'UTF-8'); ?>">
|
||||
<Table ss:ExpandedColumnCount="11" ss:ExpandedRowCount="40" x:FullColumns="1" x:FullRows="1" ss:DefaultColumnWidth="54" ss:DefaultRowHeight="20">
|
||||
<!-- 열 너비 설정 (상세 시트 최적화) -->
|
||||
<Column ss:Width="20"/> <!-- A: Spacer -->
|
||||
<Column ss:Width="50"/> <!-- B -->
|
||||
<Column ss:Width="50"/> <!-- C -->
|
||||
<Column ss:Width="50"/> <!-- D -->
|
||||
<Column ss:Width="50"/> <!-- E -->
|
||||
<Column ss:Width="70"/> <!-- F -->
|
||||
<Column ss:Width="70"/> <!-- G -->
|
||||
<Column ss:Width="70"/> <!-- H -->
|
||||
<Column ss:Width="70"/> <!-- I -->
|
||||
<Column ss:Width="100"/> <!-- J -->
|
||||
|
||||
<Row ss:Height="10"/> <!-- spacer row -->
|
||||
|
||||
<!-- 타이틀 블록 & 결재란 병합 영역 -->
|
||||
<Row ss:Height="22">
|
||||
<!-- 타이틀 (B2~D4 세로 가로 병합) -->
|
||||
<?php
|
||||
$titleText = $search_year . "년\n" . ($dRow['edu_name'] ?? '') . "\n결과보고서";
|
||||
?>
|
||||
<Cell ss:Index="2" ss:MergeAcross="2" ss:MergeDown="2" ss:StyleID="sCourseTitleBlock">
|
||||
<Data ss:Type="String"><?php echo str_replace("\n", " ", htmlspecialchars($titleText, ENT_QUOTES, 'UTF-8')); ?></Data>
|
||||
</Cell>
|
||||
<!-- 결재 세로 병합 (E2~E4) -->
|
||||
<Cell ss:Index="5" ss:MergeDown="2" ss:StyleID="sApprVertical">
|
||||
<Data ss:Type="String">결 재</Data>
|
||||
</Cell>
|
||||
<!-- 작성, 검토, 승인 헤더 -->
|
||||
<Cell ss:Index="6" ss:StyleID="sApprHeader"><Data ss:Type="String">작성</Data></Cell>
|
||||
<Cell ss:Index="7" ss:StyleID="sApprHeader"><Data ss:Type="String">검토</Data></Cell>
|
||||
<Cell ss:Index="8" ss:StyleID="sApprHeader"><Data ss:Type="String">승인</Data></Cell>
|
||||
|
||||
<!-- 담당자 및 정성호 -->
|
||||
<Cell ss:Index="9" ss:StyleID="sApprHeader"><Data ss:Type="String">담당자</Data></Cell>
|
||||
<Cell ss:Index="10" ss:StyleID="sCellCenter"><Data ss:Type="String">정성호</Data></Cell>
|
||||
</Row>
|
||||
|
||||
<Row ss:Height="25">
|
||||
<!-- F3, G3, H3 결재란 사인/도장 공간 (MergeDown으로 세로 2개 셀 병합 영역) -->
|
||||
<Cell ss:Index="6" ss:MergeDown="1" ss:StyleID="sCellCenter"/>
|
||||
<Cell ss:Index="7" ss:MergeDown="1" ss:StyleID="sCellCenter"/>
|
||||
<Cell ss:Index="8" ss:MergeDown="1" ss:StyleID="sCellCenter"/>
|
||||
|
||||
<!-- 작성일자 -->
|
||||
<Cell ss:Index="9" ss:StyleID="sApprHeader"><Data ss:Type="String">작성일자</Data></Cell>
|
||||
<Cell ss:Index="10" ss:StyleID="sCellCenter">
|
||||
<Data ss:Type="String"><?php echo date('Y. m. d'); ?></Data>
|
||||
</Cell>
|
||||
</Row>
|
||||
|
||||
<Row ss:Height="25">
|
||||
<!-- 보존기간 -->
|
||||
<Cell ss:Index="9" ss:StyleID="sApprHeader"><Data ss:Type="String">보존기간</Data></Cell>
|
||||
<Cell ss:Index="10" ss:StyleID="sCellCenter"><Data ss:Type="String">3년</Data></Cell>
|
||||
</Row>
|
||||
|
||||
<Row ss:Height="15"/> <!-- spacer row -->
|
||||
|
||||
<!-- 1. 과정개요 -->
|
||||
<Row ss:Height="20">
|
||||
<Cell ss:Index="2" ss:MergeAcross="8" ss:StyleID="sSecTitle">
|
||||
<Data ss:Type="String">1. 과정개요</Data>
|
||||
</Cell>
|
||||
</Row>
|
||||
<Row ss:Height="22">
|
||||
<Cell ss:Index="2" ss:MergeAcross="2" ss:StyleID="sTblHeader"><Data ss:Type="String">교 육 명</Data></Cell>
|
||||
<Cell ss:Index="5" ss:MergeAcross="5" ss:StyleID="sCellCenter">
|
||||
<Data ss:Type="String"><?php echo htmlspecialchars($dRow['edu_name'] ?? '', ENT_QUOTES, 'UTF-8'); ?></Data>
|
||||
</Cell>
|
||||
</Row>
|
||||
<Row ss:Height="22">
|
||||
<Cell ss:Index="2" ss:MergeAcross="2" ss:StyleID="sTblHeader"><Data ss:Type="String">교육기간</Data></Cell>
|
||||
<Cell ss:Index="5" ss:MergeAcross="5" ss:StyleID="sCellCenter">
|
||||
<Data ss:Type="String"><?php echo htmlspecialchars($dRow['edu_term'] ?? '', ENT_QUOTES, 'UTF-8'); ?></Data>
|
||||
</Cell>
|
||||
</Row>
|
||||
<Row ss:Height="22">
|
||||
<Cell ss:Index="2" ss:MergeAcross="2" ss:StyleID="sTblHeader"><Data ss:Type="String">교육방법</Data></Cell>
|
||||
<Cell ss:Index="5" ss:MergeAcross="5" ss:StyleID="sCellCenter">
|
||||
<Data ss:Type="String"><?php echo htmlspecialchars($dRow['edu_category'] ?? '', ENT_QUOTES, 'UTF-8'); ?></Data>
|
||||
</Cell>
|
||||
</Row>
|
||||
<Row ss:Height="22">
|
||||
<Cell ss:Index="2" ss:MergeAcross="2" ss:StyleID="sTblHeader"><Data ss:Type="String">교육인원</Data></Cell>
|
||||
<Cell ss:Index="5" ss:MergeAcross="5" ss:StyleID="sCellCenter">
|
||||
<Data ss:Type="String"><?php echo (int)($dRow['all_user_qty'] ?? 0); ?> 명</Data>
|
||||
</Cell>
|
||||
</Row>
|
||||
<Row ss:Height="22">
|
||||
<Cell ss:Index="2" ss:MergeAcross="2" ss:StyleID="sTblHeader"><Data ss:Type="String">이수기준</Data></Cell>
|
||||
<Cell ss:Index="5" ss:MergeAcross="5" ss:StyleID="sCellCenter">
|
||||
<Data ss:Type="String">진도율 100% 달성 및 교육기간 내 수강 완료</Data>
|
||||
</Cell>
|
||||
</Row>
|
||||
|
||||
<Row ss:Height="15"/> <!-- spacer row -->
|
||||
|
||||
<!-- 2. 교육결과 -->
|
||||
<Row ss:Height="20">
|
||||
<Cell ss:Index="2" ss:MergeAcross="8" ss:StyleID="sSecTitle">
|
||||
<Data ss:Type="String">2. 교육결과</Data>
|
||||
</Cell>
|
||||
</Row>
|
||||
<Row ss:Height="22">
|
||||
<Cell ss:Index="2" ss:MergeAcross="2" ss:StyleID="sTblHeader"><Data ss:Type="String">교육기간</Data></Cell>
|
||||
<Cell ss:Index="5" ss:MergeAcross="5" ss:StyleID="sCellCenter">
|
||||
<Data ss:Type="String"><?php echo htmlspecialchars($dRow['edu_term'] ?? '', ENT_QUOTES, 'UTF-8'); ?></Data>
|
||||
</Cell>
|
||||
</Row>
|
||||
<Row ss:Height="22">
|
||||
<Cell ss:Index="2" ss:MergeAcross="2" ss:StyleID="sTblHeader"><Data ss:Type="String">교육대상자</Data></Cell>
|
||||
<Cell ss:Index="5" ss:MergeAcross="5" ss:StyleID="sCellCenter">
|
||||
<Data ss:Type="String"><?php echo (int)($dRow['all_user_qty'] ?? 0); ?>명</Data>
|
||||
</Cell>
|
||||
</Row>
|
||||
<Row ss:Height="22">
|
||||
<Cell ss:Index="2" ss:MergeAcross="2" ss:StyleID="sTblHeader"><Data ss:Type="String">수료자</Data></Cell>
|
||||
<Cell ss:Index="5" ss:MergeAcross="5" ss:StyleID="sCellCenter">
|
||||
<Data ss:Type="String"><?php echo (int)($dRow['completed_qty'] ?? 0); ?>명</Data>
|
||||
</Cell>
|
||||
</Row>
|
||||
<Row ss:Height="22">
|
||||
<Cell ss:Index="2" ss:MergeAcross="2" ss:StyleID="sTblHeader"><Data ss:Type="String">미수료자</Data></Cell>
|
||||
<Cell ss:Index="5" ss:MergeAcross="5" ss:StyleID="sCellCenter">
|
||||
<Data ss:Type="String"><?php echo (int)($dRow['incomplete_qty'] ?? 0); ?>명</Data>
|
||||
</Cell>
|
||||
</Row>
|
||||
<Row ss:Height="22">
|
||||
<Cell ss:Index="2" ss:MergeAcross="2" ss:StyleID="sTblHeader"><Data ss:Type="String">수료율</Data></Cell>
|
||||
<Cell ss:Index="5" ss:MergeAcross="5" ss:StyleID="sCellCenter">
|
||||
<?php
|
||||
$cRate = $dRow['completed_rate'] ?? '0';
|
||||
if (is_numeric($cRate)) {
|
||||
$cRate = number_format((float)$cRate, 1);
|
||||
}
|
||||
?>
|
||||
<Data ss:Type="String"><?php echo $cRate; ?>%</Data>
|
||||
</Cell>
|
||||
</Row>
|
||||
|
||||
<Row ss:Height="15"/> <!-- spacer row -->
|
||||
|
||||
<!-- 3. 교육내용 -->
|
||||
<Row ss:Height="20">
|
||||
<Cell ss:Index="2" ss:MergeAcross="8" ss:StyleID="sSecTitle">
|
||||
<Data ss:Type="String">3. 교육내용</Data>
|
||||
</Cell>
|
||||
</Row>
|
||||
<Row ss:Height="22">
|
||||
<Cell ss:Index="2" ss:MergeAcross="8" ss:StyleID="sTblHeader"><Data ss:Type="String">교육내용</Data></Cell>
|
||||
</Row>
|
||||
<Row ss:Height="120">
|
||||
<Cell ss:Index="2" ss:MergeAcross="8" ss:MergeDown="4" ss:StyleID="sDescContent">
|
||||
<Data ss:Type="String"><?php echo str_replace(["\r\n", "\r", "\n"], " ", htmlspecialchars($dRow['edu_desc'] ?? '', ENT_QUOTES, 'UTF-8')); ?></Data>
|
||||
</Cell>
|
||||
</Row>
|
||||
<!-- MergedDown rows (height spacers) -->
|
||||
<Row ss:Height="22"/>
|
||||
<Row ss:Height="22"/>
|
||||
<Row ss:Height="22"/>
|
||||
<Row ss:Height="22"/>
|
||||
|
||||
<Row ss:Height="25"/> <!-- spacer row -->
|
||||
|
||||
<!-- 제출 문구 -->
|
||||
<Row ss:Height="25">
|
||||
<Cell ss:Index="2" ss:MergeAcross="8" ss:StyleID="sSubmitText">
|
||||
<Data ss:Type="String">위와 같이 <?php echo $search_year; ?>년 <?php echo htmlspecialchars($dRow['edu_name'] ?? '', ENT_QUOTES, 'UTF-8'); ?> 결과보고서를 제출합니다.</Data>
|
||||
</Cell>
|
||||
</Row>
|
||||
|
||||
<Row ss:Height="20"/> <!-- spacer row -->
|
||||
|
||||
<!-- 회사 푸터 -->
|
||||
<Row ss:Height="30">
|
||||
<Cell ss:Index="2" ss:MergeAcross="8" ss:StyleID="sCompanyFooter">
|
||||
<Data ss:Type="String"><?php echo htmlspecialchars($total_data['corp_name'] ?? '', ENT_QUOTES, 'UTF-8'); ?></Data>
|
||||
</Cell>
|
||||
</Row>
|
||||
</Table>
|
||||
<WorksheetOptions xmlns="urn:schemas-microsoft-com:office:excel">
|
||||
<PageSetup>
|
||||
<Layout x:Orientation="Portrait"/>
|
||||
<Header x:Margin="0.3"/>
|
||||
<Footer x:Margin="0.3"/>
|
||||
<PageMargins x:Bottom="0.75" x:Left="0.7" x:Right="0.7" x:Top="0.75"/>
|
||||
</PageSetup>
|
||||
<FitToPage/>
|
||||
<Print>
|
||||
<FitWidth>1</FitWidth>
|
||||
<FitHeight>1</FitHeight>
|
||||
<PaperSizeIndex>9</PaperSizeIndex>
|
||||
</Print>
|
||||
</WorksheetOptions>
|
||||
</Worksheet>
|
||||
<?php endforeach; ?>
|
||||
</Workbook>
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
require __DIR__ . '/../../bbs/db_conn.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// PDO 연결 생성
|
||||
$pdo = db_conn();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
echo json_encode(['success' => false, 'message' => '잘못된 요청입니다.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$content_id = isset($_POST['content_id']) ? trim($_POST['content_id']) : '';
|
||||
$seqRaw = isset($_POST['seq']) ? trim((string)$_POST['seq']) : '';
|
||||
$seq = ($seqRaw !== '' ? (int)$seqRaw : null);
|
||||
|
||||
if ($content_id === '' || $seq === null) {
|
||||
echo json_encode(['success' => false, 'message' => '콘텐츠 ID가 필요합니다.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$stmt = $pdo->prepare("DELETE FROM edu_content_memos WHERE content_id = :c AND seq = :s");
|
||||
$stmt->execute([':c' => $content_id, ':s' => $seq]);
|
||||
echo json_encode(['success' => true]);
|
||||
} catch (PDOException $e) {
|
||||
echo json_encode(['success' => false, 'message' => 'DB 에러: ' . $e->getMessage()]);
|
||||
}
|
||||
exit;
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
require __DIR__ . '/../../bbs/db_conn.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// PDO 연결 생성
|
||||
$pdo = db_conn();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
echo json_encode(['success' => false, 'message' => '잘못된 요청입니다.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$content_id = isset($_POST['content_id']) ? trim($_POST['content_id']) : '';
|
||||
$seqRaw = isset($_POST['seq']) ? trim((string)$_POST['seq']) : '';
|
||||
$seq = ($seqRaw !== '' ? (int)$seqRaw : null);
|
||||
$title = isset($_POST['title']) ? trim($_POST['title']) : '';
|
||||
$is_active = isset($_POST['is_active']) ? '1' : '0';
|
||||
|
||||
if ($content_id === '') {
|
||||
echo json_encode(['success' => false, 'message' => '콘텐츠 ID가 필요합니다.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
if ($seq === null) {
|
||||
// 신규 등록: seq를 순차증감(최대 3개)
|
||||
$stmt = $pdo->prepare("SELECT COALESCE(MAX(seq), 0) FROM edu_content_memos WHERE content_id = :c");
|
||||
$stmt->execute([':c' => $content_id]);
|
||||
$nextSeq = (int)$stmt->fetchColumn() + 1;
|
||||
if ($nextSeq > 3) {
|
||||
echo json_encode(['success' => false, 'message' => '포스트잇은 최대 3개까지만 등록할 수 있습니다.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$ins = $pdo->prepare("INSERT INTO edu_content_memos (content_id, seq, title, is_active, created_by, created_at, updated_by, updated_at)
|
||||
VALUES (:c, :s, :t, :a, 'admin', NOW(), 'admin', NOW())");
|
||||
$ins->execute([':c' => $content_id, ':s' => $nextSeq, ':t' => $title, ':a' => $is_active]);
|
||||
echo json_encode(['success' => true, 'seq' => $nextSeq]);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($seq < 1 || $seq > 3) {
|
||||
echo json_encode(['success' => false, 'message' => 'seq는 1~3 범위만 가능합니다.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 수정: 존재하면 UPDATE, 없으면 INSERT(지정 seq)
|
||||
$sql = "INSERT INTO edu_content_memos (content_id, seq, title, is_active, created_by, created_at, updated_by, updated_at)
|
||||
VALUES (:c, :s, :t, :a, 'admin', NOW(), 'admin', NOW())
|
||||
ON DUPLICATE KEY UPDATE
|
||||
title = VALUES(title),
|
||||
is_active = VALUES(is_active),
|
||||
updated_by = 'admin',
|
||||
updated_at = NOW()";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([':c' => $content_id, ':s' => $seq, ':t' => $title, ':a' => $is_active]);
|
||||
|
||||
echo json_encode(['success' => true, 'seq' => $seq]);
|
||||
} catch (PDOException $e) {
|
||||
echo json_encode(['success' => false, 'message' => 'DB 에러: ' . $e->getMessage()]);
|
||||
}
|
||||
exit;
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
require __DIR__ . '/../../bbs/db_conn.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// PDO 연결 생성
|
||||
$pdo = db_conn();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
echo json_encode(['success' => false, 'message' => '잘못된 요청입니다.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$code = isset($_POST['code']) ? trim($_POST['code']) : '';
|
||||
$content = isset($_POST['content']) ? trim($_POST['content']) : '';
|
||||
|
||||
if ($code === '' || !in_array($code, ['100', '200', '900'])) {
|
||||
echo json_encode(['success' => false, 'message' => '잘못된 코드입니다.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$stmt = $pdo->prepare("UPDATE edu_codes SET desc01 = :content, updated_by = 'admin', updated_at = NOW() WHERE group_code = 'AL100' AND code = :code");
|
||||
$stmt->execute([':content' => $content, ':code' => $code]);
|
||||
echo json_encode(['success' => true]);
|
||||
} catch (PDOException $e) {
|
||||
echo json_encode(['success' => false, 'message' => 'DB 에러: ' . $e->getMessage()]);
|
||||
}
|
||||
exit;
|
||||
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
require __DIR__ . '/../../bbs/db_conn.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// PDO 연결 생성
|
||||
$pdo = db_conn();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
echo json_encode(['success' => false, 'message' => '잘못된 요청입니다.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$code = isset($_POST['code']) ? trim($_POST['code']) : '';
|
||||
$endDate = isset($_POST['end_date']) ? trim($_POST['end_date']) : '';
|
||||
$action = isset($_POST['action']) ? trim($_POST['action']) : 'count'; // 'count' 또는 'send'
|
||||
|
||||
if ($code === '' || !in_array($code, ['100', '200', '900'])) {
|
||||
echo json_encode(['success' => false, 'message' => '잘못된 코드입니다.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
//$typeCode = 'AL100' . str_pad($code, 3, '0', STR_PAD_LEFT) . '0';
|
||||
$typeCode = 'AL100' . $code;
|
||||
try {
|
||||
// 대상 사용자 조회
|
||||
if ($code === '100') {
|
||||
// 미수료자: 법정교육 콘텐츠를 수강해야 하지만 학습 이력이 없는 사용자
|
||||
$userStmt = $pdo->prepare("
|
||||
SELECT u.member_id, u.name, u.sys_comp_code
|
||||
FROM edu_contents a
|
||||
CROSS JOIN edu_users u
|
||||
WHERE a.category_code = 'CA10003' -- 법정교육 카테고리
|
||||
AND a.base_year = YEAR(NOW()) -- 기준년도
|
||||
AND a.is_active = '1' -- 콘텐츠 활성 여부
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM edu_learning_histories b
|
||||
WHERE b.content_id = a.content_id -- 콘텐츠ID
|
||||
AND b.member_id = u.member_id -- 사용자ID
|
||||
AND b.sys_comp_code = u.sys_comp_code -- 사영여부
|
||||
AND b.completed_at is not null -- 학습완료일시
|
||||
)
|
||||
GROUP BY u.member_id, u.name, u.sys_comp_code
|
||||
");
|
||||
} else {
|
||||
// 법정교육 안내 및 전체 공지: 활성 사용자
|
||||
$userStmt = $pdo->prepare("
|
||||
SELECT member_id, name, sys_comp_code
|
||||
FROM edu_users
|
||||
WHERE (end_date IS NULL OR end_date > CURDATE())
|
||||
");
|
||||
}
|
||||
$userStmt->execute();
|
||||
$users = $userStmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
$userCount = count($users);
|
||||
|
||||
// 대상자 수만 반환하는 경우
|
||||
if ($action === 'count') {
|
||||
echo json_encode(['success' => true, 'count' => $userCount]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 실제 알람 발송
|
||||
$pdo->beginTransaction();
|
||||
|
||||
// 법정교육 기간 정보 조회 (메시지 치환용)
|
||||
$periodStmt = $pdo->prepare("SELECT start_date, end_date FROM edu_contents WHERE category_code = 'CA10003' AND base_year = YEAR(NOW()) LIMIT 1");
|
||||
$periodStmt->execute();
|
||||
$period = $periodStmt->fetch(PDO::FETCH_ASSOC);
|
||||
$startDate = $period['start_date'] ?? '';
|
||||
$periodEndDate = $period['end_date'] ?? '';
|
||||
|
||||
// 기본 메시지 조회
|
||||
$msgStmt = $pdo->prepare("SELECT desc01 FROM edu_codes WHERE group_code = 'AL100' AND code = :code");
|
||||
$msgStmt->execute([':code' => $code]);
|
||||
$baseMessage = $msgStmt->fetchColumn();
|
||||
|
||||
foreach ($users as $user) {
|
||||
// seq 최대값 +1
|
||||
$seqStmt = $pdo->prepare("SELECT COALESCE(MAX(seq), 0) + 1 FROM edu_notifications WHERE member_id = :mid");
|
||||
$seqStmt->execute([':mid' => $user['member_id']]);
|
||||
$seq = $seqStmt->fetchColumn();
|
||||
|
||||
// 메시지 치환
|
||||
$message = str_replace(
|
||||
['{학습자명}', '{시작일}', '{종료일}'],
|
||||
[$user['name'], $startDate, $periodEndDate],
|
||||
$baseMessage
|
||||
);
|
||||
|
||||
// 알람 저장
|
||||
$insStmt = $pdo->prepare("INSERT INTO edu_notifications (member_id, corp_code, seq, type_code, message, sent_at, end_date) VALUES (:mid, :corp, :seq, :type, :msg, NOW(), :end)");
|
||||
$insStmt->execute([
|
||||
':mid' => $user['member_id'],
|
||||
':corp' => $user['sys_comp_code'],
|
||||
':seq' => $seq,
|
||||
':type' => $typeCode,
|
||||
':msg' => $message,
|
||||
':end' => $endDate
|
||||
]);
|
||||
}
|
||||
|
||||
$pdo->commit();
|
||||
echo json_encode(['success' => true, 'count' => $userCount]);
|
||||
} catch (PDOException $e) {
|
||||
$pdo->rollBack();
|
||||
echo json_encode(['success' => false, 'message' => 'DB 에러: ' . $e->getMessage()]);
|
||||
}
|
||||
exit;
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
require __DIR__ . '/../../bbs/db_conn.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// PDO 연결 생성
|
||||
$pdo = db_conn();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
echo json_encode(['success' => false, 'message' => '잘못된 요청입니다.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$goal_code = isset($_POST['goal_code']) ? trim($_POST['goal_code']) : '';
|
||||
$seqRaw = isset($_POST['seq']) ? trim((string)$_POST['seq']) : '';
|
||||
$seq = ($seqRaw !== '' ? (int)$seqRaw : null);
|
||||
|
||||
if ($goal_code === '' || $seq === null) {
|
||||
echo json_encode(['success' => false, 'message' => '학습목표 코드가 필요합니다.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$stmt = $pdo->prepare("DELETE FROM edu_recommended_goals WHERE goal_code = :g AND seq = :s");
|
||||
$stmt->execute([':g' => $goal_code, ':s' => $seq]);
|
||||
echo json_encode(['success' => true]);
|
||||
} catch (PDOException $e) {
|
||||
echo json_encode(['success' => false, 'message' => 'DB 에러: ' . $e->getMessage()]);
|
||||
}
|
||||
exit;
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
require __DIR__ . '/../../bbs/db_conn.php';
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// PDO 연결 생성
|
||||
$pdo = db_conn();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
echo json_encode(['success' => false, 'message' => 'Invalid request']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$keywords_csv = isset($_POST['keywords']) ? trim($_POST['keywords']) : '';
|
||||
$admin_id = 'admin'; // User login ID not provided in context, defaulting to 'admin'
|
||||
|
||||
try {
|
||||
$pdo->beginTransaction();
|
||||
|
||||
// Set all existing active keywords to inactive first
|
||||
$updateStmt = $pdo->prepare("UPDATE edu_recommend_keywords SET is_active='0', updated_at=NOW(), updated_by=:admin WHERE is_active='1'");
|
||||
$updateStmt->execute([ ':admin' => $admin_id]);
|
||||
|
||||
if ($keywords_csv !== '') {
|
||||
$keywords = explode(',', $keywords_csv);
|
||||
if (count($keywords) > 2) {
|
||||
echo json_encode(['success' => false, 'message' => '최대 2개의 키워드만 선택 가능합니다.']);
|
||||
exit;
|
||||
}
|
||||
$insertStmt = $pdo->prepare("INSERT INTO edu_recommend_keywords ( keyword_code, is_active, created_by, created_at, updated_by, updated_at)
|
||||
VALUES ( :k, '1', :admin1, NOW(), :admin2, NOW())
|
||||
ON DUPLICATE KEY UPDATE is_active='1', updated_by=:admin3, updated_at=NOW()");
|
||||
foreach ($keywords as $k) {
|
||||
$k = trim($k);
|
||||
if ($k !== '') {
|
||||
$insertStmt->execute([
|
||||
':k' => $k,
|
||||
':admin1' => $admin_id,
|
||||
':admin2' => $admin_id,
|
||||
':admin3' => $admin_id
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$pdo->commit();
|
||||
echo json_encode(['success' => true]);
|
||||
} catch (Exception $e) {
|
||||
$pdo->rollBack();
|
||||
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
require __DIR__ . '/../../bbs/db_conn.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// PDO 연결 생성
|
||||
$pdo = db_conn();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
echo json_encode(['success' => false, 'message' => '잘못된 요청입니다.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$goal_code = isset($_POST['goal_code']) ? trim($_POST['goal_code']) : '';
|
||||
$seqRaw = isset($_POST['seq']) ? trim((string)$_POST['seq']) : '';
|
||||
$seq = ($seqRaw !== '' ? (int)$seqRaw : null);
|
||||
$title = isset($_POST['title']) ? trim($_POST['title']) : '';
|
||||
$title2 = isset($_POST['title2']) ? trim($_POST['title2']) : '';
|
||||
$is_active = isset($_POST['is_active']) ? '1' : '0';
|
||||
|
||||
if ($goal_code === '') {
|
||||
echo json_encode(['success' => false, 'message' => '학습목표 코드가 필요합니다.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
if ($seq === null) {
|
||||
if ($seq === null) {
|
||||
// 신규 등록: seq를 순차증감(최대 3개)
|
||||
$stmt = $pdo->prepare("SELECT COALESCE(MAX(seq), 0) FROM edu_recommended_goals WHERE goal_code = :g");
|
||||
$stmt->execute([':g' => $goal_code]);
|
||||
$nextSeq = (int)$stmt->fetchColumn() + 1;
|
||||
if ($nextSeq > 3) {
|
||||
echo json_encode(['success' => false, 'message' => '추천이유는 최대 3개까지만 등록할 수 있습니다.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$ins = $pdo->prepare("INSERT INTO edu_recommended_goals (goal_code, seq, title, title2, is_active, created_by, created_at, updated_by, updated_at)
|
||||
VALUES (:g, :s, :t, :t2, :a, 'admin', NOW(), 'admin', NOW())");
|
||||
$ins->execute([':g' => $goal_code, ':s' => $nextSeq, ':t' => $title, ':t2' => $title2, ':a' => $is_active]);
|
||||
echo json_encode(['success' => true, 'seq' => $nextSeq]);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// 수정: 존재하면 UPDATE, 없으면 INSERT(지정 seq)
|
||||
$sql = "INSERT INTO edu_recommended_goals (goal_code, seq, title, title2, is_active, created_by, created_at, updated_by, updated_at)
|
||||
VALUES (:g, :s, :t, :t2, :a, 'admin', NOW(), 'admin', NOW())
|
||||
ON DUPLICATE KEY UPDATE
|
||||
title = VALUES(title),
|
||||
title2 = VALUES(title2),
|
||||
is_active = VALUES(is_active),
|
||||
updated_by = 'admin',
|
||||
updated_at = NOW()";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([':g' => $goal_code, ':s' => $seq, ':t' => $title, ':t2' => $title2, ':a' => $is_active]);
|
||||
|
||||
echo json_encode(['success' => true, 'seq' => $seq]);
|
||||
} catch (PDOException $e) {
|
||||
echo json_encode(['success' => false, 'message' => 'DB 에러: ' . $e->getMessage()]);
|
||||
}
|
||||
exit;
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
require_once '../../bbs/db_conn.php';
|
||||
|
||||
// PDO 연결 생성
|
||||
$pdo = db_conn();
|
||||
|
||||
try {
|
||||
$group_code = $_POST['group_code'] ?? '';
|
||||
$code = $_POST['code'] ?? '';
|
||||
$base_code = $_POST['base_code'] ?? '';
|
||||
$code_name = $_POST['code_name'] ?? '';
|
||||
// ?ъ슜?щ?: ?대씪?댁뼵?몃뒗 1 ?먮뒗 0??蹂대궦??
|
||||
$is_active = ($_POST['is_active'] ?? '') === '1' ? '1' : '0';
|
||||
$desc01 = $_POST['desc01'] ?? '';
|
||||
|
||||
if (empty($group_code) || empty($code)) {
|
||||
echo json_encode(['success' => false, 'message' => '?꾩닔 ?꾨뱶媛 ?꾨씫?섏뿀?듬땲??']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// code_name 而щ읆 異붽?
|
||||
$stmt = $pdo->prepare("REPLACE INTO edu_codes (group_code, code, base_code, code_name, is_active, desc01, updated_at) VALUES (?, ?, ?, ?, ?, ?, NOW())");
|
||||
$result = $stmt->execute([$group_code, $code, $base_code, $code_name, $is_active, $desc01]);
|
||||
|
||||
echo json_encode(['success' => (bool)$result]);
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
require_once '../../bbs/db_conn.php';
|
||||
|
||||
// PDO 연결 생성
|
||||
$pdo = db_conn();
|
||||
|
||||
try {
|
||||
$group_code = $_POST['group_code'] ?? '';
|
||||
$group_name = $_POST['group_name'] ?? '';
|
||||
$is_active = ($_POST['is_active'] ?? '') === '1' ? '1' : '0';
|
||||
$comment = $_POST['comment'] ?? '';
|
||||
$desc01 = $_POST['desc01'] ?? '';
|
||||
|
||||
if (empty($group_code) || empty($group_name)) {
|
||||
echo json_encode(['success' => false, 'message' => '?꾩닔 ?꾨뱶媛 ?꾨씫?섏뿀?듬땲??']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("REPLACE INTO edu_code_group (group_code, group_name, is_active, comment, desc01, updated_at) VALUES (?, ?, ?, ?, ?, NOW())");
|
||||
$result = $stmt->execute([$group_code, $group_name, $is_active, $comment, $desc01]);
|
||||
|
||||
echo json_encode(['success' => (bool)$result]);
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
require_once '../../bbs/db_conn.php';
|
||||
|
||||
// PDO 연결 생성
|
||||
$pdo = db_conn();
|
||||
|
||||
try {
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
$base_year = $input['base_year'] ?? '';
|
||||
$start_date = $input['start_date'] ?? '';
|
||||
$end_date = $input['end_date'] ?? '';
|
||||
|
||||
if (empty($base_year) || empty($start_date) || empty($end_date)) {
|
||||
echo json_encode(['success' => false, 'message' => '?꾩닔 ?뚮씪誘명꽣媛 ?꾨씫?섏뿀?듬땲??']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// edu_contents ?뚯씠釉붿뿉??踰뺤젙援먯쑁(CA10003) 移댄뀒怨좊━ ??ぉ ?낅뜲?댄듃
|
||||
$stmt = $pdo->prepare("
|
||||
UPDATE edu_contents
|
||||
SET
|
||||
start_date = ?,
|
||||
end_date = ?,
|
||||
updated_at = NOW()
|
||||
WHERE 1=1
|
||||
AND category_code = 'CA10003'
|
||||
AND base_year = ?
|
||||
");
|
||||
|
||||
$result = $stmt->execute([$start_date, $end_date, $base_year]);
|
||||
|
||||
if ($result) {
|
||||
echo json_encode(['success' => true]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => '????ㅽ뙣']);
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
require __DIR__ . '/../../bbs/db_conn.php';
|
||||
$pdo = db_conn();
|
||||
$stmt = $pdo->prepare("CALL proc_get_learner_status('2026', '', '', '', '', '')");
|
||||
$stmt->execute();
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
echo '<pre>';
|
||||
print_r($row);
|
||||
echo '</pre>';
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
$ch = curl_init('http://localhost/html/admin/bbs/content_save.php');
|
||||
curl_setopt($ch, CURLOPT_POST, 1);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, [
|
||||
'title' => 'Test title',
|
||||
'category_code' => 'CA10001'
|
||||
]);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
echo "Response: " . curl_exec($ch);
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../bbs/db_conn.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
$offer_id = $input['offer_id'] ?? '';
|
||||
$status_code = $input['status_code'] ?? '';
|
||||
$reason_return = $input['reason_return'] ?? '';
|
||||
|
||||
if (!$offer_id || !$status_code) {
|
||||
echo json_encode(['success' => false, 'message' => '필수 파라미터가 누락되었습니다.'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
$sql = "UPDATE edu_content_offer SET status_code = ?, reason_return = ? WHERE offer_id = ?";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$status_code, $reason_return, $offer_id]);
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
echo json_encode(['success' => true], JSON_UNESCAPED_UNICODE);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => '업데이트된 행이 없습니다.'], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log('[UPDATE_OFFER ERROR] ' . $e->getMessage());
|
||||
echo json_encode(['success' => false, 'message' => '서버 오류가 발생했습니다.'], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
require_once '../../bbs/db_conn.php';
|
||||
|
||||
// PDO 연결 생성
|
||||
$pdo = db_conn();
|
||||
|
||||
try {
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
$member_id = $input['member_id'] ?? '';
|
||||
$auth_level = $input['auth_level'] ?? '';
|
||||
|
||||
if (empty($member_id) || empty($auth_level)) {
|
||||
echo json_encode(['success' => false, 'message' => '필수 파라미터가 누락되었습니다.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("UPDATE edu_users SET auth_level = ? WHERE member_id = ? ");
|
||||
$result = $stmt->execute([$auth_level, $member_id]);
|
||||
|
||||
if ($result) {
|
||||
echo json_encode(['success' => true]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => '업데이트 실패']);
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
/**
|
||||
* YouTube Data API v3 프록시 + Gemini AI 무료 요약 (최종 완성본)
|
||||
*/
|
||||
require __DIR__ . '/../../bbs/auth.php';
|
||||
edu_require_login();
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// ★ 서비스 키 설정
|
||||
define('YOUTUBE_API_KEY', 'AIzaSyDfj_8oN6cbkmxn2zS77_CG-OB6xF8ywpI');
|
||||
define('GEMINI_API_KEY', 'AIzaSyAlQXuhXroeoWmh0u_N3Qq9tgUMLYuucWI');
|
||||
|
||||
$video_id = isset($_GET['video_id']) ? trim($_GET['video_id']) : '';
|
||||
$video_id = extract_yt_id($video_id);
|
||||
|
||||
if (!$video_id) {
|
||||
echo json_encode(['success' => false, 'message' => '유효하지 않은 ID']);
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* YouTube ID 추출 함수
|
||||
*/
|
||||
function extract_yt_id($input)
|
||||
{
|
||||
if (preg_match('/^[a-zA-Z0-9_-]{11}$/', $input))
|
||||
return $input;
|
||||
if (preg_match('#youtu\.be/([a-zA-Z0-9_-]{11})#', $input, $m))
|
||||
return $m[1];
|
||||
if (preg_match('#[?&]v=([a-zA-Z0-9_-]{11})#', $input, $m))
|
||||
return $m[1];
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gemini AI 요약 함수 (보안 및 통신 강화)
|
||||
*/
|
||||
function get_gemini_summary($text)
|
||||
{
|
||||
if (empty(GEMINI_API_KEY) || GEMINI_API_KEY === 'YOUR_GEMINI_API_KEY_HERE')
|
||||
return null;
|
||||
|
||||
// $url = 'https://generativelanguage.googleapis.com/v1/models/gemini-1.5-flash:generateContent?key=' . GEMINI_API_KEY;
|
||||
$url = 'https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key=' . GEMINI_API_KEY;
|
||||
$data = [
|
||||
'contents' => [
|
||||
[
|
||||
'parts' => [['text' => "너는 교육 콘텐츠 에디터야. 아래 유튜브 설명에서 타임라인(00:00)과 구독/좋아요 요청은 완전히 제외하고, 핵심 내용만 직장인을 위해 3문장 이내로 요약해줘:\n\n" . mb_substr($text, 0, 3000, 'UTF-8')]]
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 15);
|
||||
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0 Safari/537.36');
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($http_code === 200 && $response) {
|
||||
$res = json_decode($response, true);
|
||||
$result = $res['candidates'][0]['content']['parts'][0]['text'] ?? null;
|
||||
return $result ? trim($result) : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 1. YouTube API 호출 (데이터 가져오기 로직 복구)
|
||||
$api_url = "https://www.googleapis.com/youtube/v3/videos?id={$video_id}&part=snippet,contentDetails&key=" . YOUTUBE_API_KEY;
|
||||
$yt_res = @file_get_contents($api_url);
|
||||
$yt_data = json_decode($yt_res, true);
|
||||
|
||||
if (empty($yt_data['items'])) {
|
||||
echo json_encode(['success' => false, 'message' => '영상을 찾을 수 없습니다.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$item = $yt_data['items'][0];
|
||||
$yt_title = $item['snippet']['title'] ?? '';
|
||||
$yt_desc_raw = $item['snippet']['description'] ?? '';
|
||||
$duration = $item['contentDetails']['duration'] ?? 'PT0S';
|
||||
|
||||
// 2. 요약 처리 (AI 시도)
|
||||
$summary = get_gemini_summary($yt_desc_raw);
|
||||
|
||||
// [핵심] AI가 실패하거나, 결과에 여전히 타임라인(00:00)이 포함된 경우 강력한 수동 필터 작동
|
||||
if (!$summary || preg_match('/[0-9]{1,2}:[0-9]{2}/', $summary)) {
|
||||
$text = $yt_desc_raw;
|
||||
|
||||
// (1) 타임라인 제거 (줄 시작이 숫자:숫자인 모든 줄 삭제)
|
||||
$text = preg_replace('/^[ ]*[0-9]{1,2}:[0-9]{2}.*$/m', '', $text);
|
||||
|
||||
// (2) 구독/좋아요/광고 문구가 포함된 문장 삭제
|
||||
$patterns = ['구독', '좋아요', '알림설정', '인스타그램', '페이스북', '문의'];
|
||||
foreach ($patterns as $p) {
|
||||
$text = preg_replace('/[^.!?\n]*?' . $p . '[^.!?\n]*?[.!?\n]?/u', '', $text);
|
||||
}
|
||||
|
||||
// (3) 해시태그 및 이모지 제거
|
||||
$text = preg_replace('/#[^\s#]+/u', '', $text);
|
||||
$text = preg_replace('/[\x{10000}-\x{10FFFF}]/u', '', $text);
|
||||
|
||||
// (4) 공백 정리 및 200자 절삭
|
||||
$text = preg_replace('/\s+/', ' ', trim($text));
|
||||
$summary = mb_substr($text, 0, 200, 'UTF-8');
|
||||
if (mb_strlen($text, 'UTF-8') > 200)
|
||||
$summary .= '...';
|
||||
}
|
||||
|
||||
// 3. 재생 시간 계산 (ISO 8601 -> 초)
|
||||
preg_match('/PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?/', $duration, $m);
|
||||
$content_tm = ((int) ($m[1] ?? 0) * 3600) + ((int) ($m[2] ?? 0) * 60) + (int) ($m[3] ?? 0);
|
||||
|
||||
// 최종 결과 출력
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'yt_title' => $yt_title,
|
||||
'description' => $summary,
|
||||
'content_tm' => $content_tm,
|
||||
'duration_fmt' => sprintf('%d:%02d', intdiv($content_tm, 60), $content_tm % 60),
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
Reference in New Issue
Block a user