410 lines
16 KiB
PHP
410 lines
16 KiB
PHP
<?php
|
|
// =============================================
|
|
// GOI Dashboard API - Order List (Full Version)
|
|
// GET /api/orders.php
|
|
//
|
|
// Parameters:
|
|
// date_from YYYYMMDD (default: today)
|
|
// date_to YYYYMMDD (default: today)
|
|
// driver d_drinitial e.g. M.K (optional)
|
|
//
|
|
// Header:
|
|
// X-API-Key: GOI_DASHBOARD_2026_SECRET
|
|
//
|
|
// Returns per driver per day:
|
|
// - Forecast / Actual / Achievement%
|
|
// - Total stops / Completed stops
|
|
// - Departure / Arrival time
|
|
// - Mileage start / finish
|
|
// - Opening Balance / Cash In / Cash Out
|
|
// - Cash Return / Gas / Extra / Total Balance
|
|
// - Driver comment
|
|
// =============================================
|
|
|
|
require_once 'config.php';
|
|
validateApiKey();
|
|
|
|
$pdo = getDB();
|
|
|
|
// ── Forecast Cache 분기 ───────────────────────
|
|
if (($_GET['type'] ?? '') === 'forecast') {
|
|
|
|
header('Content-Type: application/json');
|
|
header('Access-Control-Allow-Origin: *');
|
|
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
|
http_response_code(204); exit;
|
|
}
|
|
|
|
$pdo = getDB();
|
|
$date = $_GET['date'] ?? date('Ymd');
|
|
|
|
$pdo->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
|
|
]);
|