exec("CREATE TABLE IF NOT EXISTS tbl_forecast_cache ( fc_date VARCHAR(8) NOT NULL PRIMARY KEY, fc_data TEXT NOT NULL, fc_updated DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP )"); if ($_SERVER['REQUEST_METHOD'] === 'POST') { $body = json_decode(file_get_contents('php://input'), true); if (!$body) { echo json_encode(['success' => false, 'error' => 'Invalid JSON']); exit; } $st = $pdo->prepare(" INSERT INTO tbl_forecast_cache (fc_date, fc_data) VALUES (:d, :data) ON DUPLICATE KEY UPDATE fc_data = VALUES(fc_data), fc_updated = NOW() "); $st->execute([':d' => $date, ':data' => json_encode($body)]); echo json_encode(['success' => true, 'date' => $date]); } else { $st = $pdo->prepare("SELECT fc_data, fc_updated FROM tbl_forecast_cache WHERE fc_date = :d"); $st->execute([':d' => $date]); $row = $st->fetch(PDO::FETCH_ASSOC); echo json_encode([ 'success' => true, 'date' => $date, 'saved_at' => $row ? $row['fc_updated'] : null, 'data' => $row ? json_decode($row['fc_data'], true) : null, ]); } exit; } // ── CS Dashboard 분기 ───────────────────────── if (($_GET['type'] ?? '') === 'cs') { $dateFrom = $_GET['date_from'] ?? date('Ymd', strtotime('-30 days')); $dateTo = $_GET['date_to'] ?? date('Ymd'); $status = $_GET['status'] ?? 'all'; $today = date('Ymd'); // 1. SUMMARY KPIs $summary = []; $st = $pdo->prepare("SELECT COUNT(*) FROM tbl_request WHERE r_status='A' AND SUBSTR(r_createddate,1,8) BETWEEN :f AND :t"); $st->execute([':f' => $dateFrom, ':t' => $dateTo]); $summary['total_pending'] = (int)$st->fetchColumn(); $st = $pdo->prepare("SELECT COUNT(*) FROM tbl_request WHERE SUBSTR(r_createddate,1,8) = :d"); $st->execute([':d' => $today]); $summary['today_new'] = (int)$st->fetchColumn(); $st = $pdo->prepare("SELECT COUNT(*) FROM tbl_request WHERE r_status='F' AND SUBSTR(r_createddate,1,8) = :d"); $st->execute([':d' => $today]); $summary['today_completed'] = (int)$st->fetchColumn(); $st = $pdo->prepare("SELECT MIN(r_requestdate) FROM tbl_request WHERE r_status='A' AND SUBSTR(r_createddate,1,8) BETWEEN :f AND :t"); $st->execute([':f' => $dateFrom, ':t' => $dateTo]); $oldest = $st->fetchColumn(); $summary['oldest_pending_date'] = $oldest ?: null; if ($oldest && strlen($oldest) === 8) { $oDate = mktime(0,0,0,(int)substr($oldest,4,2),(int)substr($oldest,6,2),(int)substr($oldest,0,4)); $summary['oldest_pending_days'] = (int)floor((time() - $oDate) / 86400); } else { $summary['oldest_pending_days'] = 0; } $st = $pdo->prepare("SELECT COUNT(*) FROM tbl_request WHERE SUBSTR(r_createddate,1,8) BETWEEN :f AND :t"); $st->execute([':f' => $dateFrom, ':t' => $dateTo]); $summary['period_total'] = (int)$st->fetchColumn(); $st = $pdo->prepare("SELECT COUNT(*) FROM tbl_request WHERE r_status='F' AND SUBSTR(r_createddate,1,8) BETWEEN :f AND :t"); $st->execute([':f' => $dateFrom, ':t' => $dateTo]); $summary['period_completed'] = (int)$st->fetchColumn(); // 2a. ALL PENDING (날짜 필터 없음 — 전체 미처리) $st = $pdo->query(" SELECT r.r_uid AS id, r.r_requestdate AS request_date, r.r_accountno AS account, r.r_name AS customer_name, r.r_status AS status, r.r_note AS note, r.r_createddate AS created_at, COALESCE(c.c_area, '') AS area, COALESCE(c.c_city, '') AS city, COALESCE(c.c_phone, '') AS phone, COALESCE(m.m_initial, '') AS driver, COALESCE(NULLIF(m2.m_initial,''), m2.m_firstname, '?') AS staff FROM tbl_request r LEFT JOIN tbl_customer c ON r.r_customeruid = c.c_uid LEFT JOIN tbl_member m ON r.r_driveruid = m.m_uid LEFT JOIN tbl_member m2 ON r.r_memberuid = m2.m_uid WHERE r.r_status = 'A' ORDER BY r.r_requestdate DESC, r.r_uid DESC LIMIT 20000 "); $allPending = $st->fetchAll(PDO::FETCH_ASSOC); foreach ($allPending as &$row) { $d = $row['request_date']; $row['request_date_fmt'] = strlen($d) === 8 ? substr($d,0,4).'-'.substr($d,4,2).'-'.substr($d,6,2) : $d; $oDate = mktime(0,0,0,(int)substr($d,4,2),(int)substr($d,6,2),(int)substr($d,0,4)); $row['days_pending'] = (int)floor((time() - $oDate) / 86400); } unset($row); // 2b. PERIOD REQUESTS (기간 필터 — 전체 요청 탭용) $where = ["SUBSTR(r.r_createddate,1,8) BETWEEN :date_from AND :date_to"]; $params = [':date_from' => $dateFrom, ':date_to' => $dateTo]; $sql = " SELECT r.r_uid AS id, r.r_requestdate AS request_date, r.r_accountno AS account, r.r_name AS customer_name, r.r_status AS status, r.r_note AS note, r.r_createddate AS created_at, COALESCE(c.c_area, '') AS area, COALESCE(c.c_city, '') AS city, COALESCE(c.c_phone, '') AS phone, COALESCE(m.m_initial, '') AS driver, COALESCE(NULLIF(m2.m_initial,''), m2.m_firstname, '?') AS staff FROM tbl_request r LEFT JOIN tbl_customer c ON r.r_customeruid = c.c_uid LEFT JOIN tbl_member m ON r.r_driveruid = m.m_uid LEFT JOIN tbl_member m2 ON r.r_memberuid = m2.m_uid WHERE " . implode(" AND ", $where) . " ORDER BY r.r_requestdate DESC, r.r_uid DESC LIMIT 500 "; $st = $pdo->prepare($sql); $st->execute($params); $requests = $st->fetchAll(PDO::FETCH_ASSOC); foreach ($requests as &$row) { $d = $row['request_date']; $row['request_date_fmt'] = strlen($d) === 8 ? substr($d,0,4).'-'.substr($d,4,2).'-'.substr($d,6,2) : $d; if ($row['status'] === 'A' && strlen($d) === 8) { $oDate = mktime(0,0,0,(int)substr($d,4,2),(int)substr($d,6,2),(int)substr($d,0,4)); $row['days_pending'] = (int)floor((time() - $oDate) / 86400); } else { $row['days_pending'] = 0; } } unset($row); // 3. STAFF BREAKDOWN (접수자별 통계) $st = $pdo->prepare(" SELECT COALESCE(NULLIF(m2.m_initial,''), m2.m_firstname, '?') AS staff, COUNT(*) AS total, SUM(CASE WHEN r.r_status='A' THEN 1 ELSE 0 END) AS pending, SUM(CASE WHEN r.r_status='F' THEN 1 ELSE 0 END) AS completed FROM tbl_request r LEFT JOIN tbl_member m2 ON r.r_memberuid = m2.m_uid WHERE SUBSTR(r.r_createddate,1,8) BETWEEN :f AND :t GROUP BY r.r_memberuid, m2.m_initial ORDER BY total DESC "); $st->execute([':f' => $dateFrom, ':t' => $dateTo]); $byStaff = $st->fetchAll(PDO::FETCH_ASSOC); // 4. DRIVER BREAKDOWN $st = $pdo->prepare(" SELECT COALESCE(m.m_initial, '?') AS driver, COUNT(*) AS total, SUM(CASE WHEN r.r_status='A' THEN 1 ELSE 0 END) AS pending, SUM(CASE WHEN r.r_status='F' THEN 1 ELSE 0 END) AS completed FROM tbl_request r LEFT JOIN tbl_member m ON r.r_driveruid = m.m_uid WHERE r.r_requestdate BETWEEN :f AND :t GROUP BY r.r_driveruid, m.m_initial ORDER BY pending DESC, total DESC "); $st->execute([':f' => $dateFrom, ':t' => $dateTo]); $byDriver = $st->fetchAll(PDO::FETCH_ASSOC); // 4. NOTES (type A + D) $st = $pdo->prepare(" SELECT n.n_uid AS id, n.n_type AS type, n.n_note AS note, n.n_createddate AS created_at, COALESCE(c.c_name, '') AS customer_name, COALESCE(c.c_accountno, '') AS account, COALESCE(c.c_area, '') AS area, COALESCE(c.c_city, '') AS city, COALESCE(CONCAT(m.m_firstname,' ',m.m_lastname), '') AS author FROM tbl_note n LEFT JOIN tbl_customer c ON n.n_customeruid = c.c_uid LEFT JOIN tbl_member m ON n.n_memberuid = m.m_uid WHERE n.n_status = 'A' AND n.n_type IN ('A','D') AND SUBSTR(n.n_createddate,1,8) BETWEEN :f AND :t ORDER BY n.n_createddate DESC LIMIT 300 "); $st->execute([':f' => $dateFrom, ':t' => $dateTo]); $notes = $st->fetchAll(PDO::FETCH_ASSOC); foreach ($notes as &$n) { $d = substr($n['created_at'], 0, 8); $n['date_fmt'] = strlen($d) === 8 ? substr($d,0,4).'-'.substr($d,4,2).'-'.substr($d,6,2) : $d; } unset($n); // 5. AREA BREAKDOWN $st = $pdo->prepare(" SELECT COALESCE(NULLIF(c.c_area,''), c.c_city, '기타') AS area, COUNT(*) AS total FROM tbl_request r LEFT JOIN tbl_customer c ON r.r_customeruid = c.c_uid WHERE SUBSTR(r.r_createddate,1,8) BETWEEN :f AND :t GROUP BY area ORDER BY total DESC "); $st->execute([':f' => $dateFrom, ':t' => $dateTo]); $byArea = $st->fetchAll(PDO::FETCH_ASSOC); // 6. DAILY BREAKDOWN (접수일 기준) $st = $pdo->prepare(" SELECT SUBSTR(r_createddate,1,8) AS date, COUNT(*) AS total, SUM(CASE WHEN r_status='F' THEN 1 ELSE 0 END) AS completed, SUM(CASE WHEN r_status='A' THEN 1 ELSE 0 END) AS pending FROM tbl_request WHERE SUBSTR(r_createddate,1,8) BETWEEN :f AND :t GROUP BY SUBSTR(r_createddate,1,8) ORDER BY SUBSTR(r_createddate,1,8) ASC "); $st->execute([':f' => $dateFrom, ':t' => $dateTo]); $byDay = $st->fetchAll(PDO::FETCH_ASSOC); jsonResponse([ 'success' => true, 'date_from' => $dateFrom, 'date_to' => $dateTo, 'summary' => $summary, 'all_pending' => $allPending, 'requests' => $requests, 'by_driver' => $byDriver, 'by_staff' => $byStaff, 'by_area' => $byArea, 'by_day' => $byDay, 'notes' => $notes, ]); exit; } // ───────────────────────────────────────────── // 날짜 파라미터 (DB 형식: YYYYMMDD varchar) $dateFrom = $_GET['date_from'] ?? date('Ymd'); $dateTo = $_GET['date_to'] ?? date('Ymd'); $driver = $_GET['driver'] ?? null; // 날짜 형식 검증 (8자리 숫자) if (!preg_match('/^\d{8}$/', $dateFrom) || !preg_match('/^\d{8}$/', $dateTo)) { http_response_code(400); jsonResponse(['success' => false, 'error' => 'Invalid date format. Use YYYYMMDD (e.g. 20260521)']); } // ============================================= // 메인 쿼리 // tbl_daily → 수거 데이터 (Forecast, Actual, Stops) // tbl_member → 드라이버 조인 조건 // tbl_memberhis → 출퇴근/Cash 데이터 (LEFT JOIN: 없어도 수거 데이터는 나옴) // ============================================= $sql = " SELECT d.d_orderdate AS order_date, d.d_druid AS driver_uid, d.d_drinitial AS driver, -- 수거 데이터 COUNT(*) AS total_stops, COUNT(CASE WHEN d.d_status = 'F' THEN 1 END) AS completed_stops, COALESCE(SUM(d.d_estquantity), 0) AS forecast, COALESCE(SUM(d.d_quantity), 0) AS actual, -- 출퇴근 시간 (tbl_memberhis) h.h_departuretime AS departure, h.h_arrivaltime AS arrival, -- 마일리지 (tbl_memberhis) h.h_mileage_s AS mileage_start, h.h_mileage_f AS mileage_finish, -- Cash 데이터 (tbl_memberhis) COALESCE(h.h_balance_o, 0) AS opening_balance, COALESCE(h.h_balance_in, 0) AS cash_in, COALESCE(h.h_balance_out, 0) AS cash_out, COALESCE(h.h_balance_r, 0) AS cash_return, COALESCE(h.h_balance_g, 0) AS gas, COALESCE(h.h_balance_e, 0) AS extra, COALESCE(h.h_balance_t, 0) AS balance_total, -- 드라이버 코멘트 h.h_comment AS comment FROM tbl_daily d INNER JOIN ( SELECT DISTINCT m_currentdriver FROM tbl_member WHERE m_currentdriver IS NOT NULL ) m ON (d.d_druid = m.m_currentdriver) LEFT JOIN tbl_memberhis h ON (d.d_druid = h.h_druid AND d.d_orderdate = h.h_date) WHERE d.d_orderdate BETWEEN :date_from AND :date_to "; if ($driver) { $sql .= " AND d.d_drinitial = :driver "; } $sql .= " GROUP BY d.d_orderdate, d.d_drinitial, d.d_druid, h.h_departuretime, h.h_arrivaltime, h.h_mileage_s, h.h_mileage_f, h.h_balance_o, h.h_balance_in, h.h_balance_out, h.h_balance_r, h.h_balance_g, h.h_balance_e, h.h_balance_t, h.h_comment ORDER BY d.d_orderdate DESC, d.d_drinitial ASC "; $stmt = $pdo->prepare($sql); $stmt->bindParam(':date_from', $dateFrom); $stmt->bindParam(':date_to', $dateTo); if ($driver) { $stmt->bindParam(':driver', $driver); } $stmt->execute(); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); // ============================================= // 계산 컬럼 추가 // ============================================= foreach ($rows as &$row) { // 날짜 포맷 변환 YYYYMMDD → YYYY-MM-DD $d = $row['order_date']; if (strlen($d) === 8) { $row['order_date_formatted'] = substr($d,0,4).'-'.substr($d,4,2).'-'.substr($d,6,2); } // 달성률 (%) $fc = floatval($row['forecast']); $ac = floatval($row['actual']); $row['achievement_pct'] = $fc > 0 ? round($ac / $fc * 100, 1) : 0; // 잔여 수거량 $row['remaining'] = max($fc - $ac, 0); // 진행 상태 if ($ac <= 0) $row['status'] = 'Pending'; elseif ($ac >= $fc) $row['status'] = 'Done'; else $row['status'] = 'In Progress'; // 마일리지 주행거리 $ms = intval($row['mileage_start']); $mf = intval($row['mileage_finish']); $row['mileage_driven'] = ($ms > 0 && $mf > 0) ? max($mf - $ms, 0) : null; } unset($row); jsonResponse([ 'success' => true, 'date_from' => $dateFrom, 'date_to' => $dateTo, 'count' => count($rows), 'data' => $rows ]);