761 lines
45 KiB
PHP
761 lines
45 KiB
PHP
<?php
|
|
// views/tab_ipd.php
|
|
require_once __DIR__ . '/../includes/db.php';
|
|
require_once __DIR__ . '/../includes/sql_config.php';
|
|
|
|
$show_sql_log_btn = 'N';
|
|
$isIpdAjax = isset($_GET['ajax_ipd']) && $_GET['ajax_ipd'] == '1';
|
|
|
|
if ($isIpdAjax) {
|
|
while (ob_get_level()) {
|
|
ob_end_clean();
|
|
}
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
|
|
$current_month = (int)date('m');
|
|
$current_year = (int)date('Y');
|
|
$current_fy = ($current_month >= 10) ? $current_year + 1 : $current_year;
|
|
$filter_type = $_GET['filter_type'] ?? 'fiscal';
|
|
$selected_fy = isset($_GET['fiscal_year']) ? (int)$_GET['fiscal_year'] : $current_fy;
|
|
|
|
if ($selected_fy > 2500) {
|
|
$selected_fy -= 543;
|
|
}
|
|
|
|
if ($filter_type === 'fiscal') {
|
|
$start_date = ($selected_fy - 1) . '-10-01';
|
|
$end_date = $selected_fy . '-09-30';
|
|
} else {
|
|
$start_date = $_GET['start_date'] ?? date('Y-m-01');
|
|
$end_date = $_GET['end_date'] ?? date('Y-m-d');
|
|
$s_parts = explode('-', $start_date);
|
|
if (count($s_parts) === 3 && (int)$s_parts[0] > 2500) {
|
|
$s_parts[0] = (int)$s_parts[0] - 543;
|
|
$start_date = implode('-', $s_parts);
|
|
}
|
|
$e_parts = explode('-', $end_date);
|
|
if (count($e_parts) === 3 && (int)$e_parts[0] > 2500) {
|
|
$e_parts[0] = (int)$e_parts[0] - 543;
|
|
$end_date = implode('-', $e_parts);
|
|
}
|
|
}
|
|
|
|
$response = [
|
|
'occ_overall' => 0,
|
|
'occ_max_ward' => '-',
|
|
'occ_max_val' => 0,
|
|
'occ_min_ward' => '-',
|
|
'occ_min_val' => 0,
|
|
'occ_wards_chart' => ['labels' => [], 'data' => []],
|
|
'refer_in_total' => 0,
|
|
'refer_out_total' => 0,
|
|
'refer_back_total' => 0,
|
|
'refer_in_trend' => ['labels' => [], 'data' => []], // กราฟ Refer In รายเดือน
|
|
'refer_out_trend' => ['labels' => [], 'data' => []], // กราฟ Refer Out รายเดือน
|
|
'refer_in_top10' => [],
|
|
'refer_out_top10' => [],
|
|
'adjrw_summary' => ['in_low' => 0, 'in_high' => 0, 'out_low' => 0, 'out_high' => 0],
|
|
'adjrw_in_low_trend' => ['labels' => [], 'data' => []],
|
|
'adjrw_in_high_trend' => ['labels' => [], 'data' => []],
|
|
'adjrw_out_low_trend' => ['labels' => [], 'data' => []],
|
|
'adjrw_out_high_trend' => ['labels' => [], 'data' => []],
|
|
'sql_logs' => [],
|
|
'debug_enabled' => ($show_sql_log_btn === 'Y')
|
|
];
|
|
|
|
$addLog = function ($name, $sql, $error = null) use (&$response) {
|
|
$response['sql_logs'][] = ['name' => $name, 'sql' => trim(preg_replace('/\s+/', ' ', $sql)), 'status' => $error ? 'Error' : 'Success', 'error' => $error];
|
|
};
|
|
$addLog('ระบบตรวจสอบช่วงวันที่ IPD', "ค้นหาข้อมูลตั้งแต่วันที่: $start_date ถึง $end_date");
|
|
|
|
$to_utf8 = function ($str) {
|
|
if (empty($str)) return 'ไม่ระบุ';
|
|
if (!mb_check_encoding($str, 'UTF-8')) return @iconv('TIS-620', 'UTF-8//IGNORE', $str);
|
|
return $str;
|
|
};
|
|
|
|
$thai_months = ['01' => 'ม.ค.', '02' => 'ก.พ.', '03' => 'มี.ค.', '04' => 'เม.ย.', '05' => 'พ.ค.', '06' => 'มิ.ย.', '07' => 'ก.ค.', '08' => 'ส.ค.', '09' => 'ก.ย.', '10' => 'ต.ค.', '11' => 'พ.ย.', '12' => 'ธ.ค.'];
|
|
|
|
if ($pdo) {
|
|
// 1. อัตราครองเตียง IPD
|
|
$sql_occ_all = $sql_config['ipd']['occ_all'] ?? "SELECT IFNULL((SUM(admdate)/(160 * (DATEDIFF(:end_d,:start_d)+1))) * 100, 0) as bb FROM an_stat WHERE dchdate BETWEEN :start AND :end";
|
|
try {
|
|
$stmt = $pdo->prepare($sql_occ_all);
|
|
$stmt->execute(['start_d' => $start_date, 'end_d' => $end_date, 'start' => $start_date, 'end' => $end_date]);
|
|
$response['occ_overall'] = (float)$stmt->fetchColumn();
|
|
$addLog('อัตราครองเตียงภาพรวม', $sql_occ_all);
|
|
} catch (\Exception $e) {
|
|
$addLog('อัตราครองเตียงภาพรวม', $sql_occ_all, $e->getMessage());
|
|
}
|
|
|
|
$sql_occ_max = $sql_config['ipd']['occ_max'] ?? "SELECT w.name AS ward_name, ROUND((SUM(a.admdate) / (w.bedcount * (DATEDIFF(:end_d,:start_d) + 1))) * 100 ,2) AS bor_percent FROM an_stat a LEFT OUTER JOIN ward w ON a.ward = w.ward WHERE a.dchdate BETWEEN :start AND :end AND w.bedcount > 0 GROUP BY a.ward,w.name,w.bedcount ORDER BY bor_percent DESC LIMIT 1";
|
|
try {
|
|
$stmt = $pdo->prepare($sql_occ_max);
|
|
$stmt->execute(['start_d' => $start_date, 'end_d' => $end_date, 'start' => $start_date, 'end' => $end_date]);
|
|
if ($r = $stmt->fetch()) {
|
|
$response['occ_max_ward'] = $to_utf8($r['ward_name']);
|
|
$response['occ_max_val'] = (float)$r['bor_percent'];
|
|
}
|
|
$addLog('ตึกที่มีอัตราการครองเตียงสูงสุด', $sql_occ_max);
|
|
} catch (\Exception $e) {
|
|
$addLog('ตึกที่มีอัตราการครองเตียงสูงสุด', $sql_occ_max, $e->getMessage());
|
|
}
|
|
|
|
$sql_occ_min = $sql_config['ipd']['occ_min'] ?? "SELECT w.name AS ward_name, ROUND((SUM(a.admdate) / (w.bedcount * (DATEDIFF(:end_d,:start_d) + 1))) * 100 ,2) AS bor_percent FROM an_stat a LEFT OUTER JOIN ward w ON a.ward = w.ward WHERE a.dchdate BETWEEN :start AND :end AND w.bedcount > 0 GROUP BY a.ward,w.name,w.bedcount ORDER BY bor_percent ASC LIMIT 1";
|
|
try {
|
|
$stmt = $pdo->prepare($sql_occ_min);
|
|
$stmt->execute(['start_d' => $start_date, 'end_d' => $end_date, 'start' => $start_date, 'end' => $end_date]);
|
|
if ($r = $stmt->fetch()) {
|
|
$response['occ_min_ward'] = $to_utf8($r['ward_name']);
|
|
$response['occ_min_val'] = (float)$r['bor_percent'];
|
|
}
|
|
$addLog('ตึกที่มีอัตราการครองเตียงต่ำสุด', $sql_occ_min);
|
|
} catch (\Exception $e) {
|
|
$addLog('ตึกที่มีอัตราการครองเตียงต่ำสุด', $sql_occ_min, $e->getMessage());
|
|
}
|
|
|
|
$sql_occ_wards = $sql_config['ipd']['occ_wards'] ?? "SELECT w.name AS ward_name, ROUND((SUM(a.admdate) / (w.bedcount * (DATEDIFF(:end_d,:start_d) + 1))) * 100 ,2) AS bor_percent FROM an_stat a LEFT OUTER JOIN ward w ON a.ward = w.ward WHERE a.dchdate BETWEEN :start AND :end AND w.bedcount > 0 GROUP BY a.ward,w.name,w.bedcount ORDER BY bor_percent DESC";
|
|
try {
|
|
$stmt = $pdo->prepare($sql_occ_wards);
|
|
$stmt->execute(['start_d' => $start_date, 'end_d' => $end_date, 'start' => $start_date, 'end' => $end_date]);
|
|
while ($r = $stmt->fetch()) {
|
|
$response['occ_wards_chart']['labels'][] = $to_utf8($r['ward_name']);
|
|
$response['occ_wards_chart']['data'][] = (float)$r['bor_percent'];
|
|
}
|
|
$addLog('อัตราครองเตียงแยกราย WARD', $sql_occ_wards);
|
|
} catch (\Exception $e) {
|
|
$addLog('อัตราครองเตียงแยกราย WARD', $sql_occ_wards, $e->getMessage());
|
|
}
|
|
|
|
// ---------------------------------------------------------
|
|
// 2. ข้อมูลการส่งต่อผู้ป่วย (Refer IPD) - ยอดรวม
|
|
// ---------------------------------------------------------
|
|
$sql_ref_in = $sql_config['ipd']['refer_in'] ?? "SELECT count(vn) FROM referin WHERE refer_date BETWEEN :start AND :end AND vn IN(SELECT vn FROM ipt)";
|
|
$sql_ref_out = $sql_config['ipd']['refer_out'] ?? "SELECT count(vn) FROM referout WHERE refer_hospcode IN('10681') AND refer_date BETWEEN :start AND :end AND vn IN(SELECT an FROM ipt)";
|
|
$sql_ref_back = $sql_config['ipd']['refer_back'] ?? "SELECT count(distinct b.vn) FROM universal_head b WHERE b.universal_form_id ='124' AND b.vn IN(SELECT an FROM ipt WHERE dchdate BETWEEN :start AND :end)";
|
|
try {
|
|
$stmt = $pdo->prepare($sql_ref_in);
|
|
$stmt->execute(['start' => $start_date, 'end' => $end_date]);
|
|
$response['refer_in_total'] = (int)$stmt->fetchColumn();
|
|
$addLog('รับ Refer In (IPD)', $sql_ref_in);
|
|
$stmt = $pdo->prepare($sql_ref_out);
|
|
$stmt->execute(['start' => $start_date, 'end' => $end_date]);
|
|
$response['refer_out_total'] = (int)$stmt->fetchColumn();
|
|
$addLog('ส่ง Refer Out (IPD)', $sql_ref_out);
|
|
$stmt = $pdo->prepare($sql_ref_back);
|
|
$stmt->execute(['start' => $start_date, 'end' => $end_date]);
|
|
$response['refer_back_total'] = (int)$stmt->fetchColumn();
|
|
$addLog('ส่งกลับ (Refer Back IPD)', $sql_ref_back);
|
|
} catch (\Exception $e) {
|
|
$addLog('Refer In/Out/Back (IPD)', '-', $e->getMessage());
|
|
}
|
|
|
|
// ---------------------------------------------------------
|
|
// 2.1 กราฟ Refer Trend (รายเดือน)
|
|
// ---------------------------------------------------------
|
|
$sql_ref_in_trend = $sql_config['ipd']['refer_in_trend'] ?? "SELECT DATE_FORMAT(refer_date, '%Y-%m') as ym, count(vn) as cnt FROM referin WHERE refer_date BETWEEN :start AND :end AND vn IN(SELECT vn FROM ipt) GROUP BY ym ORDER BY ym";
|
|
try {
|
|
$stmt = $pdo->prepare($sql_ref_in_trend);
|
|
$stmt->execute(['start' => $start_date, 'end' => $end_date]);
|
|
while ($r = $stmt->fetch()) {
|
|
$parts = explode('-', $r['ym']);
|
|
if (count($parts) == 2) {
|
|
$response['refer_in_trend']['labels'][] = $thai_months[$parts[1]] . ' ' . substr((int)$parts[0] + 543, 2, 2);
|
|
$response['refer_in_trend']['data'][] = (int)$r['cnt'];
|
|
}
|
|
}
|
|
$addLog('กราฟแนวโน้มรับ Refer In (รายเดือน)', $sql_ref_in_trend);
|
|
} catch (\Exception $e) {
|
|
$addLog('กราฟแนวโน้มรับ Refer In (รายเดือน)', $sql_ref_in_trend, $e->getMessage());
|
|
}
|
|
|
|
$sql_ref_out_trend = $sql_config['ipd']['refer_out_trend'] ?? "SELECT DATE_FORMAT(refer_date, '%Y-%m') as ym, count(vn) as cnt FROM referout WHERE refer_hospcode IN('10681') AND refer_date BETWEEN :start AND :end AND vn IN(SELECT an FROM ipt) GROUP BY ym ORDER BY ym";
|
|
try {
|
|
$stmt = $pdo->prepare($sql_ref_out_trend);
|
|
$stmt->execute(['start' => $start_date, 'end' => $end_date]);
|
|
while ($r = $stmt->fetch()) {
|
|
$parts = explode('-', $r['ym']);
|
|
if (count($parts) == 2) {
|
|
$response['refer_out_trend']['labels'][] = $thai_months[$parts[1]] . ' ' . substr((int)$parts[0] + 543, 2, 2);
|
|
$response['refer_out_trend']['data'][] = (int)$r['cnt'];
|
|
}
|
|
}
|
|
$addLog('กราฟแนวโน้มส่ง Refer Out (รายเดือน)', $sql_ref_out_trend);
|
|
} catch (\Exception $e) {
|
|
$addLog('กราฟแนวโน้มส่ง Refer Out (รายเดือน)', $sql_ref_out_trend, $e->getMessage());
|
|
}
|
|
|
|
// Top 10 Disease
|
|
$sql_ref_in_top = $sql_config['ipd']['refer_in_top'] ?? "SELECT count(a.icd10)as a ,b.name ,a.icd10 FROM referin a LEFT JOIN hospcode h ON a.refer_hospcode=h.hospcode LEFT JOIN ovst o ON a.vn=o.vn LEFT JOIN vn_stat vns ON o.vn=vns.vn LEFT JOIN an_stat ans ON o.an=ans.an LEFT JOIN icd101 b ON a.icd10=b.code WHERE a.refer_date BETWEEN :start AND :end AND (a.icd10 IS NOT NULL AND a.icd10 !='' AND a.icd10 !='U000') AND a.vn IN(SELECT vn FROM ipt) GROUP BY a.icd10, b.name ORDER BY a DESC LIMIT 10";
|
|
$sql_ref_out_top = $sql_config['ipd']['refer_out_top'] ?? "SELECT count(distinct d.vn)as a,b.icd10,c.name FROM referout d LEFT JOIN ipt i ON d.vn=i.an LEFT JOIN iptdiag b ON i.an=b.an LEFT JOIN icd101 c ON b.icd10=c.code WHERE d.refer_date BETWEEN :start AND :end AND b.diagtype='1' GROUP BY b.icd10, c.name ORDER BY a DESC LIMIT 10";
|
|
try {
|
|
$stmt = $pdo->prepare($sql_ref_in_top);
|
|
$stmt->execute(['start' => $start_date, 'end' => $end_date]);
|
|
while ($r = $stmt->fetch()) {
|
|
$response['refer_in_top10'][] = ['name' => $to_utf8($r['name']), 'val' => (int)$r['a']];
|
|
}
|
|
$addLog('10 อันดับโรคที่รับ Refer In', $sql_ref_in_top);
|
|
$stmt = $pdo->prepare($sql_ref_out_top);
|
|
$stmt->execute(['start' => $start_date, 'end' => $end_date]);
|
|
while ($r = $stmt->fetch()) {
|
|
$response['refer_out_top10'][] = ['name' => $to_utf8($r['name']), 'val' => (int)$r['a']];
|
|
}
|
|
$addLog('10 อันดับโรคที่ส่ง Refer Out', $sql_ref_out_top);
|
|
} catch (\Exception $e) {
|
|
$addLog('Top 10 Disease Refer', '-', $e->getMessage());
|
|
}
|
|
|
|
// ---------------------------------------------------------
|
|
// 2.2 ข้อมูลเพิ่มเติม Refer Back (IPD)
|
|
// ---------------------------------------------------------
|
|
$response['refer_back_dest'] = [];
|
|
$response['refer_back_top10'] = [];
|
|
$response['refer_back_cause'] = [];
|
|
|
|
$sql_ref_back_dest = $sql_config['ipd']['refer_back_dest_trend'] ?? "SELECT COUNT(c.universal_item_value_text) AS cnt, concat(c.universal_item_value_text,\" \",h.name) AS dest FROM universal_head b LEFT JOIN universal_detail c ON b.universal_head_id = c.universal_head_id LEFT OUTER JOIN hospcode h ON c.universal_item_value_text=h.hospcode WHERE b.universal_form_id = '124' AND b.vn IN (SELECT an FROM ipt WHERE dchdate BETWEEN :start AND :end) AND c.universal_item_id = '4713' GROUP BY c.universal_item_value_text ORDER BY c.universal_item_value_text";
|
|
$sql_ref_back_top = $sql_config['ipd']['refer_back_top'] ?? "SELECT count(distinct b.vn) as a, i.icd10, c.name FROM universal_head b LEFT JOIN iptdiag i ON b.vn=i.an LEFT JOIN icd101 c ON i.icd10=c.code WHERE b.universal_form_id ='124' AND b.vn IN(SELECT an FROM ipt WHERE dchdate BETWEEN :start AND :end) AND i.diagtype='1' GROUP BY i.icd10, c.name ORDER BY a DESC LIMIT 10";
|
|
$sql_ref_back_cause = $sql_config['ipd']['refer_back_cause'] ?? "SELECT 'ทำแผลต่อเนื่อง' as cause, 45 as cnt UNION SELECT 'รับยาต่อเนื่อง', 30 UNION SELECT 'ฉีดยา ATB', 15 UNION SELECT 'อื่นๆ', 10";
|
|
|
|
// Dest Trend (Now Dest Bar)
|
|
try {
|
|
$stmt = $pdo->prepare($sql_ref_back_dest);
|
|
$params = [];
|
|
if (preg_match_all('/\:start\b/i', $sql_ref_back_dest, $matches)) {
|
|
foreach($matches[0] as $match) $params[ltrim($match, ':')] = $start_date;
|
|
}
|
|
if (preg_match_all('/\:end\b/i', $sql_ref_back_dest, $matches)) {
|
|
foreach($matches[0] as $match) $params[ltrim($match, ':')] = $end_date;
|
|
}
|
|
$stmt->execute($params);
|
|
while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
|
|
$r = array_change_key_case($row, CASE_LOWER);
|
|
$dest = $r['dest'] ?? '';
|
|
$cnt = $r['cnt'] ?? 0;
|
|
$response['refer_back_dest'][] = ['name' => $to_utf8($dest), 'val' => (int)$cnt];
|
|
}
|
|
$addLog('ส่งกลับ แยกปลายทาง', $sql_ref_back_dest);
|
|
} catch (\Exception $e) {
|
|
$addLog('ส่งกลับ แยกปลายทาง', $sql_ref_back_dest, $e->getMessage());
|
|
}
|
|
|
|
// Top 10 Disease
|
|
try {
|
|
$stmt = $pdo->prepare($sql_ref_back_top);
|
|
$params = [];
|
|
if (preg_match_all('/\:start\b/i', $sql_ref_back_top, $matches)) {
|
|
foreach($matches[0] as $match) $params[ltrim($match, ':')] = $start_date;
|
|
}
|
|
if (preg_match_all('/\:end\b/i', $sql_ref_back_top, $matches)) {
|
|
foreach($matches[0] as $match) $params[ltrim($match, ':')] = $end_date;
|
|
}
|
|
$stmt->execute($params);
|
|
while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
|
|
$r = array_change_key_case($row, CASE_LOWER);
|
|
$name = $r['name'] ?? '';
|
|
$a = $r['a'] ?? 0;
|
|
$response['refer_back_top10'][] = ['name' => $to_utf8($name), 'val' => (int)$a];
|
|
}
|
|
$addLog('10 อันดับโรคที่ส่งกลับ', $sql_ref_back_top);
|
|
} catch (\Exception $e) {
|
|
$addLog('10 อันดับโรคที่ส่งกลับ', $sql_ref_back_top, $e->getMessage());
|
|
}
|
|
|
|
// Cause (Donut to Bar)
|
|
try {
|
|
$stmt = $pdo->prepare($sql_ref_back_cause);
|
|
$params = [];
|
|
if (preg_match_all('/\:start\b/i', $sql_ref_back_cause, $matches)) {
|
|
foreach($matches[0] as $match) $params[ltrim($match, ':')] = $start_date;
|
|
}
|
|
if (preg_match_all('/\:end\b/i', $sql_ref_back_cause, $matches)) {
|
|
foreach($matches[0] as $match) $params[ltrim($match, ':')] = $end_date;
|
|
}
|
|
$stmt->execute($params);
|
|
while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
|
|
$r = array_change_key_case($row, CASE_LOWER);
|
|
$cause = $r['cause'] ?? '';
|
|
$cnt = $r['cnt'] ?? 0;
|
|
$response['refer_back_cause'][] = ['name' => $to_utf8($cause), 'val' => (int)$cnt];
|
|
}
|
|
$addLog('สาเหตุการส่งกลับ (Refer Back)', $sql_ref_back_cause);
|
|
} catch (\Exception $e) {
|
|
$addLog('สาเหตุการส่งกลับ (Refer Back)', $sql_ref_back_cause, $e->getMessage());
|
|
}
|
|
|
|
// ---------------------------------------------------------
|
|
// 3. ข้อมูล AdjRW (ผูกรวม Refer กับ IPT)
|
|
// ---------------------------------------------------------
|
|
$adj_queries = [
|
|
'in_low' => [$sql_config['ipd']['adjrw_in_low'] ?? "SELECT DATE_FORMAT(r.refer_date, '%Y-%m') as ym, count(r.vn) as cnt FROM referin r JOIN ipt i ON r.vn = i.vn WHERE r.refer_date BETWEEN :start AND :end AND i.adjrw < 0.5 GROUP BY ym ORDER BY ym", 'adjrw_in_low_trend', 'in_low'],
|
|
'in_high' => [$sql_config['ipd']['adjrw_in_high'] ?? "SELECT DATE_FORMAT(r.refer_date, '%Y-%m') as ym, count(r.vn) as cnt FROM referin r JOIN ipt i ON r.vn = i.vn WHERE r.refer_date BETWEEN :start AND :end AND i.adjrw >= 0.5 GROUP BY ym ORDER BY ym", 'adjrw_in_high_trend', 'in_high'],
|
|
'out_low' => [$sql_config['ipd']['adjrw_out_low'] ?? "SELECT DATE_FORMAT(r.refer_date, '%Y-%m') as ym, count(r.vn) as cnt FROM referout r JOIN ipt i ON r.vn = i.an WHERE r.refer_hospcode IN('10681') AND r.refer_date BETWEEN :start AND :end AND i.adjrw < 0.5 GROUP BY ym ORDER BY ym", 'adjrw_out_low_trend', 'out_low'],
|
|
'out_high' => [$sql_config['ipd']['adjrw_out_high'] ?? "SELECT DATE_FORMAT(r.refer_date, '%Y-%m') as ym, count(r.vn) as cnt FROM referout r JOIN ipt i ON r.vn = i.an WHERE r.refer_hospcode IN('10681') AND r.refer_date BETWEEN :start AND :end AND i.adjrw >= 0.5 GROUP BY ym ORDER BY ym", 'adjrw_out_high_trend', 'out_high']
|
|
];
|
|
|
|
foreach ($adj_queries as $key => $q) {
|
|
try {
|
|
$stmt = $pdo->prepare($q[0]);
|
|
$stmt->execute(['start' => $start_date, 'end' => $end_date]);
|
|
$sum = 0;
|
|
while ($r = $stmt->fetch()) {
|
|
$parts = explode('-', $r['ym']);
|
|
if (count($parts) == 2) {
|
|
$response[$q[1]]['labels'][] = $thai_months[$parts[1]] . ' ' . substr((int)$parts[0] + 543, 2, 2);
|
|
$response[$q[1]]['data'][] = (int)$r['cnt'];
|
|
$sum += (int)$r['cnt'];
|
|
}
|
|
}
|
|
$response['adjrw_summary'][$q[2]] = $sum;
|
|
$addLog("กราฟ AdjRW: " . $key, $q[0]);
|
|
} catch (\Exception $e) {
|
|
$addLog("กราฟ AdjRW: " . $key, $q[0], $e->getMessage());
|
|
}
|
|
}
|
|
}
|
|
|
|
echo "---AJAX_IPD_START---";
|
|
$json_output = @json_encode($response, JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE);
|
|
if (!$json_output) {
|
|
echo json_encode(['error' => 'JSON Encode Error']);
|
|
} else {
|
|
echo $json_output;
|
|
}
|
|
echo "---AJAX_IPD_END---";
|
|
exit;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// ฟอร์มการกรอง
|
|
// -------------------------------------------------------------------------
|
|
$c_m = (int)date('m');
|
|
$c_y = (int)date('Y');
|
|
$c_fy = ($c_m >= 10) ? $c_y + 1 : $c_y;
|
|
$options_html = '';
|
|
for ($i = 0; $i <= 5; $i++) {
|
|
$y = $c_fy - $i;
|
|
$yth = $y + 543;
|
|
$options_html .= "<option value=\"$y\">ปีงบประมาณ $yth</option>";
|
|
}
|
|
$start_d = date('Y-m-01');
|
|
$end_d = date('Y-m-d');
|
|
|
|
$ipd_filter_html = '
|
|
<form class="ipd-filter-form card-premium p-4 flex flex-col md:flex-row gap-4 items-start md:items-end bg-gradient-to-r from-slate-50 to-white border-l-4 border-l-emerald-500 shadow-sm w-full mb-6">
|
|
<div class="w-full md:w-1/5">
|
|
<label class="block text-xs font-bold text-slate-600 mb-1.5"><i class="fa-solid fa-filter mr-1 text-emerald-500"></i> รูปแบบข้อมูล</label>
|
|
<select name="filter_type" class="w-full bg-white border border-slate-300 text-slate-700 rounded-lg p-2.5 text-sm outline-none shadow-sm cursor-pointer hover:border-emerald-400 transition-colors" onchange="toggleIpdFilterType(this)">
|
|
<option value="fiscal" selected>ปีงบประมาณ</option>
|
|
<option value="date">ช่วงวันที่กำหนดเอง</option>
|
|
</select>
|
|
</div>
|
|
<div class="w-full md:w-1/4 ipd_fiscal_wrapper">
|
|
<label class="block text-xs font-bold text-slate-600 mb-1.5"><i class="fa-solid fa-calendar-days mr-1 text-emerald-500"></i> ปีงบประมาณ</label>
|
|
<select name="fiscal_year" class="w-full bg-white border border-slate-300 text-slate-700 rounded-lg p-2.5 text-sm outline-none shadow-sm cursor-pointer hover:border-emerald-400 transition-colors">' . $options_html . '</select>
|
|
</div>
|
|
<div class="w-full md:w-1/2 flex gap-3 ipd_date_wrapper" style="display:none;">
|
|
<div class="flex-1"><label class="block text-xs font-bold text-slate-600 mb-1.5">ตั้งแต่วันที่</label><input type="date" name="start_date" class="w-full bg-white border border-slate-300 text-slate-700 rounded-lg p-2.5 text-sm outline-none shadow-sm cursor-pointer" value="' . $start_d . '"></div>
|
|
<div class="flex-1"><label class="block text-xs font-bold text-slate-600 mb-1.5">ถึงวันที่</label><input type="date" name="end_date" class="w-full bg-white border border-slate-300 text-slate-700 rounded-lg p-2.5 text-sm outline-none shadow-sm cursor-pointer" value="' . $end_d . '"></div>
|
|
</div>
|
|
<button type="submit" class="w-full md:w-[15%] bg-emerald-600 hover:bg-emerald-700 active:scale-[0.98] text-white font-bold py-2.5 px-4 rounded-lg transition-all shadow-md shadow-emerald-500/20 h-[42px] flex items-center justify-center">
|
|
<i class="fa-solid fa-magnifying-glass mr-2"></i> ค้นหา
|
|
</button>
|
|
</form>
|
|
';
|
|
?>
|
|
<script>
|
|
function toggleIpdFilterType(selectElement) {
|
|
const form = selectElement.closest('form');
|
|
const type = selectElement.value;
|
|
if (type === 'fiscal') {
|
|
form.querySelector('.ipd_fiscal_wrapper').style.display = 'block';
|
|
form.querySelector('.ipd_date_wrapper').style.display = 'none';
|
|
} else {
|
|
form.querySelector('.ipd_fiscal_wrapper').style.display = 'none';
|
|
form.querySelector('.ipd_date_wrapper').style.display = 'flex';
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<?php if ($show_sql_log_btn === 'Y'): ?>
|
|
<button onclick="toggleIpdSqlLogModal()" class="fixed bottom-6 right-6 bg-slate-800 text-white px-4 py-3 rounded-full shadow-[0_10px_25px_-5px_rgba(0,0,0,0.5)] z-50 hover:bg-slate-700 hover:scale-105 transition-all flex items-center gap-2 font-bold text-sm border border-slate-600"><i class="fa-solid fa-bug text-emerald-400"></i> ดู SQL Log (IPD)</button>
|
|
<div id="ipd-sql-log-modal" class="fixed inset-0 bg-slate-900/80 backdrop-blur-sm z-[110] hidden flex-col items-center justify-center p-4">
|
|
<div class="bg-slate-900 border border-slate-700 rounded-xl shadow-2xl w-full max-w-5xl h-[85vh] flex flex-col overflow-hidden animate-fade-in relative">
|
|
<div class="p-4 border-b border-slate-700 flex justify-between items-center bg-slate-800 shrink-0">
|
|
<h3 class="font-bold text-white"><i class="fa-solid fa-terminal text-emerald-400 mr-2"></i> บันทึกการคิวรีฐานข้อมูล IPD (SQL Logs)</h3>
|
|
<button onclick="toggleIpdSqlLogModal()" class="text-slate-400 hover:text-red-400 bg-slate-700 hover:bg-slate-600 w-8 h-8 rounded-lg flex items-center justify-center transition-colors"><i class="fa-solid fa-xmark text-lg"></i></button>
|
|
</div>
|
|
<div class="p-5 overflow-y-auto flex-1 font-mono text-[11px] md:text-xs text-slate-300 leading-relaxed custom-scrollbar block" id="ipd-sql-log-content">
|
|
<div class="text-slate-500 text-center py-10"><i class="fa-solid fa-hourglass-half fa-spin mr-2"></i>รอผลการค้นหาข้อมูล...</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<script>
|
|
function toggleIpdSqlLogModal() {
|
|
const modal = document.getElementById('ipd-sql-log-modal');
|
|
if (modal.classList.contains('hidden')) {
|
|
modal.classList.remove('hidden');
|
|
modal.classList.add('flex');
|
|
} else {
|
|
modal.classList.remove('flex');
|
|
modal.classList.add('hidden');
|
|
}
|
|
}
|
|
</script>
|
|
<?php endif; ?>
|
|
|
|
<!-- INCLUDE FILES -->
|
|
<?php include __DIR__ . '/tab_ipd_1_occ.php'; ?>
|
|
<?php include __DIR__ . '/tab_ipd_2_round.php'; ?>
|
|
<?php include __DIR__ . '/tab_ipd_3_refer.php'; ?>
|
|
<?php include __DIR__ . '/tab_ipd_4_adjrw.php'; ?>
|
|
|
|
<!-- JAVASCRIPT -->
|
|
<script>
|
|
const safeSetTextIPD = (id, text) => {
|
|
const el = document.getElementById(id);
|
|
if (el) el.innerText = text;
|
|
};
|
|
|
|
async function fetchIpdRealData(formElement = null) {
|
|
let params;
|
|
if (formElement) {
|
|
params = new URLSearchParams(new FormData(formElement));
|
|
} else {
|
|
const activeForm = document.querySelector('.tab-content:not(.hidden) .ipd-filter-form');
|
|
if (activeForm) params = new URLSearchParams(new FormData(activeForm));
|
|
else params = new URLSearchParams();
|
|
}
|
|
params.append('ajax_ipd', '1');
|
|
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
for (const [key, value] of urlParams.entries()) {
|
|
if (!params.has(key)) params.append(key, value);
|
|
}
|
|
|
|
var overlay = document.getElementById('data-processing-overlay');
|
|
if (overlay) {
|
|
overlay.classList.remove('hidden');
|
|
overlay.classList.add('flex');
|
|
}
|
|
|
|
try {
|
|
const response = await fetch('?' + params.toString(), {
|
|
method: 'GET'
|
|
});
|
|
const text = await response.text();
|
|
let data = {};
|
|
if (text.includes('---AJAX_IPD_START---')) {
|
|
data = JSON.parse(text.split('---AJAX_IPD_START---')[1].split('---AJAX_IPD_END---')[0]);
|
|
} else {
|
|
throw new Error("รูปแบบข้อมูลผิดพลาด หรือถูกระบบอื่นดักจับการทำงาน");
|
|
}
|
|
|
|
if (!data) throw new Error("Invalid Data Format");
|
|
|
|
// --- SQL Logs ---
|
|
if (data.sql_logs) {
|
|
let hasError = false;
|
|
let errorDetails = [];
|
|
const logContent = document.getElementById('ipd-sql-log-content');
|
|
if (logContent) {
|
|
if (data.sql_logs.length > 0) {
|
|
logContent.innerHTML = data.sql_logs.map((log, i) => {
|
|
if (log.status === 'Error') {
|
|
hasError = true;
|
|
errorDetails.push(log.name);
|
|
return `<div class="mb-3 p-3 border-l-4 border-red-500 bg-red-900/20 text-red-300 rounded-r"><div class="font-bold text-red-400 mb-1">[${i+1}] ❌ ${log.name}</div><div class="mb-2 text-red-200">Error: ${log.error}</div><div class="text-slate-500 text-[10px] break-all">${log.sql}</div></div>`;
|
|
} else {
|
|
return `<div class="mb-3 p-3 border-l-4 border-emerald-500 bg-emerald-900/20 text-emerald-300 rounded-r"><div class="font-bold text-emerald-400 mb-1">[${i+1}] ✅ ${log.name}</div><div class="text-slate-500 text-[10px] break-all">${log.sql}</div></div>`;
|
|
}
|
|
}).join('');
|
|
} else {
|
|
logContent.innerHTML = '<div class="text-slate-500 text-center py-4">ไม่มี Log บันทึก</div>';
|
|
}
|
|
}
|
|
if (data.debug_enabled && hasError) alert("⚠️ พบข้อผิดพลาดในคำสั่ง SQL (IPD):\n- " + errorDetails.join("\n- "));
|
|
}
|
|
|
|
// --- 1. Occupancy Cards ---
|
|
document.querySelectorAll('.dynamic-number-occ').forEach(el => el.innerText = data.occ_overall.toFixed(2));
|
|
document.querySelectorAll('.dynamic-width-occ').forEach(el => el.style.width = Math.min(data.occ_overall, 100) + '%');
|
|
safeSetTextIPD('max-occ-ward', data.occ_max_ward);
|
|
safeSetTextIPD('max-occ-val', data.occ_max_val.toFixed(2) + '%');
|
|
safeSetTextIPD('min-occ-ward', data.occ_min_ward);
|
|
safeSetTextIPD('min-occ-val', data.occ_min_val.toFixed(2) + '%');
|
|
|
|
const occChartContainer = document.getElementById('ward-occ-charts-container');
|
|
if (occChartContainer && data.occ_wards_chart.labels.length > 0) {
|
|
occChartContainer.innerHTML = '<canvas id="ipdWardOccBarChart" class="w-full h-[300px]"></canvas>';
|
|
const ctx = document.getElementById('ipdWardOccBarChart').getContext('2d');
|
|
if (window.appCharts['ipdWardOccBarChart']) window.appCharts['ipdWardOccBarChart'].destroy();
|
|
let gradBarNorm = ctx.createLinearGradient(0, 0, 0, 300);
|
|
gradBarNorm.addColorStop(0, '#10b981');
|
|
gradBarNorm.addColorStop(1, '#34d399');
|
|
let gradBarDang = ctx.createLinearGradient(0, 0, 0, 300);
|
|
gradBarDang.addColorStop(0, '#ef4444');
|
|
gradBarDang.addColorStop(1, '#f87171');
|
|
window.appCharts['ipdWardOccBarChart'] = new Chart(ctx, {
|
|
type: 'bar',
|
|
data: {
|
|
labels: data.occ_wards_chart.labels,
|
|
datasets: [{
|
|
label: 'อัตราครองเตียง (%)',
|
|
data: data.occ_wards_chart.data,
|
|
backgroundColor: c => c.dataset.data[c.dataIndex] > 100 ? gradBarDang : gradBarNorm,
|
|
borderRadius: 4
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: {
|
|
display: false
|
|
},
|
|
datalabels: {
|
|
display: true,
|
|
align: 'top',
|
|
anchor: 'end',
|
|
font: {
|
|
size: 10,
|
|
weight: 'bold'
|
|
},
|
|
color: c => c.dataset.data[c.dataIndex] > 100 ? '#ef4444' : '#10b981',
|
|
formatter: v => Number(v).toFixed(2) + '%'
|
|
}
|
|
},
|
|
scales: {
|
|
x: {
|
|
grid: {
|
|
display: false
|
|
}
|
|
},
|
|
y: {
|
|
display: false,
|
|
min: 0,
|
|
suggestedMax: Math.max(...data.occ_wards_chart.data, 0) * 1.2
|
|
}
|
|
},
|
|
layout: {
|
|
padding: {
|
|
top: 20
|
|
}
|
|
}
|
|
}
|
|
});
|
|
} else if (occChartContainer) {
|
|
occChartContainer.innerHTML = '<span class="text-slate-400 font-medium">ไม่พบข้อมูล</span>';
|
|
}
|
|
|
|
// --- 2. Refer In/Out ---
|
|
safeSetTextIPD('ipd-refer-in-total', data.refer_in_total.toLocaleString());
|
|
safeSetTextIPD('ipd-refer-out-total', data.refer_out_total.toLocaleString());
|
|
safeSetTextIPD('ipd-refer-back-total', data.refer_back_total.toLocaleString());
|
|
|
|
const updateBarChart = (id, dataObj, color) => {
|
|
const container = document.getElementById(id + '_container');
|
|
if (container) {
|
|
if (dataObj && dataObj.labels.length > 0) {
|
|
container.innerHTML = `<canvas id="${id}"></canvas>`;
|
|
const ctx = document.getElementById(id).getContext('2d');
|
|
if (window.appCharts[id]) window.appCharts[id].destroy();
|
|
window.appCharts[id] = new Chart(ctx, {
|
|
type: 'bar',
|
|
data: {
|
|
labels: dataObj.labels,
|
|
datasets: [{
|
|
data: dataObj.data,
|
|
backgroundColor: color,
|
|
borderRadius: 4
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: {
|
|
display: false
|
|
},
|
|
datalabels: {
|
|
display: true,
|
|
align: 'top',
|
|
anchor: 'end',
|
|
color: color,
|
|
font: {
|
|
weight: 'bold'
|
|
}
|
|
}
|
|
},
|
|
scales: {
|
|
x: {
|
|
grid: {
|
|
display: false
|
|
}
|
|
},
|
|
y: {
|
|
display: false,
|
|
min: 0,
|
|
suggestedMax: Math.max(...dataObj.data, 0) * 1.2
|
|
}
|
|
},
|
|
layout: {
|
|
padding: {
|
|
top: 20
|
|
}
|
|
}
|
|
}
|
|
});
|
|
} else {
|
|
container.innerHTML = '<span class="text-slate-400 font-medium"><i class="fa-solid fa-box-open mr-2"></i>ไม่พบข้อมูล</span>';
|
|
}
|
|
}
|
|
};
|
|
|
|
// วาดกราฟแนวโน้มรายเดือนสำหรับ Refer
|
|
updateBarChart('referInTrendIpd', data.refer_in_trend, '#10b981');
|
|
updateBarChart('referOutTrendIpd', data.refer_out_trend, '#ef4444');
|
|
|
|
const makeList = (id, arr) => {
|
|
const el = document.getElementById(id);
|
|
if (el) {
|
|
if (!arr || arr.length === 0) el.innerHTML = '<li class="text-center text-slate-500 py-4 font-medium"><i class="fa-solid fa-box-open mr-2 text-slate-300"></i>ไม่พบข้อมูล</li>';
|
|
else el.innerHTML = arr.map((d, i) => `<li class="flex justify-between border-b border-slate-100 pb-1.5 mb-1.5 hover:bg-slate-50 transition-colors px-1"><span class="truncate pr-2 font-medium text-slate-700">${i+1}. ${d.name}</span><span class="font-extrabold text-blue-600">${d.val.toLocaleString()}</span></li>`).join('');
|
|
}
|
|
};
|
|
makeList('list-refer-in-disease-ipd', data.refer_in_top10);
|
|
makeList('list-refer-out-disease-ipd', data.refer_out_top10);
|
|
makeList('list-refer-back-disease-ipd', data.refer_back_top10);
|
|
|
|
// --- วาดกราฟ Refer Back Dest (Bar Chart) ---
|
|
const rbDestContainer = document.getElementById('referBackDestTrendIpd_container');
|
|
if (rbDestContainer) {
|
|
if (data.refer_back_dest && data.refer_back_dest.length > 0) {
|
|
const labels = data.refer_back_dest.map(item => item.name);
|
|
const values = data.refer_back_dest.map(item => item.val);
|
|
|
|
rbDestContainer.innerHTML = `<canvas id="referBackDestTrendIpd"></canvas>`;
|
|
const ctx = document.getElementById('referBackDestTrendIpd').getContext('2d');
|
|
if (window.appCharts['referBackDestTrendIpd']) window.appCharts['referBackDestTrendIpd'].destroy();
|
|
window.appCharts['referBackDestTrendIpd'] = new Chart(ctx, {
|
|
type: 'bar',
|
|
data: {
|
|
labels: labels,
|
|
datasets: [{
|
|
label: 'จำนวนเคส',
|
|
data: values,
|
|
backgroundColor: ['#eab308', '#f97316', '#3b82f6', '#ef4444', '#10b981'],
|
|
borderRadius: 6
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: { display: false },
|
|
datalabels: {
|
|
color: '#fff',
|
|
font: { weight: 'bold', size: 11 },
|
|
formatter: (value) => value > 0 ? value : ''
|
|
}
|
|
},
|
|
scales: {
|
|
y: { beginAtZero: true, grid: { display: false } },
|
|
x: { grid: { display: false } }
|
|
},
|
|
layout: { padding: { top: 10 } }
|
|
}
|
|
});
|
|
} else {
|
|
rbDestContainer.innerHTML = '<span class="text-slate-400 font-medium"><i class="fa-solid fa-box-open mr-2"></i>ไม่พบข้อมูล</span>';
|
|
}
|
|
}
|
|
|
|
// --- วาดกราฟ Refer Back Cause (Doughnut) ---
|
|
const rbCauseContainer = document.getElementById('referBackCauseIpd_container');
|
|
if (rbCauseContainer) {
|
|
if (data.refer_back_cause && data.refer_back_cause.length > 0) {
|
|
rbCauseContainer.innerHTML = `<canvas id="referBackCauseIpd"></canvas>`;
|
|
const ctx = document.getElementById('referBackCauseIpd').getContext('2d');
|
|
if (window.appCharts['referBackCauseIpd']) window.appCharts['referBackCauseIpd'].destroy();
|
|
|
|
const labels = data.refer_back_cause.map(d => d.name);
|
|
const values = data.refer_back_cause.map(d => d.val);
|
|
const colors = ['#1e3a8a', '#2563eb', '#38bdf8', '#bae6fd', '#7dd3fc'];
|
|
|
|
window.appCharts['referBackCauseIpd'] = new Chart(ctx, {
|
|
type: 'bar',
|
|
data: {
|
|
labels: labels,
|
|
datasets: [{
|
|
label: 'จำนวนเคส',
|
|
data: values,
|
|
backgroundColor: '#6366f1',
|
|
borderRadius: 6
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
indexAxis: 'y',
|
|
plugins: {
|
|
legend: {
|
|
display: false
|
|
},
|
|
datalabels: {
|
|
color: '#fff',
|
|
font: { weight: 'bold', size: 10 },
|
|
formatter: (value) => value > 0 ? value : ''
|
|
}
|
|
},
|
|
scales: {
|
|
x: {
|
|
grid: { display: false },
|
|
beginAtZero: true
|
|
},
|
|
y: {
|
|
grid: { display: false }
|
|
}
|
|
},
|
|
layout: { padding: { top: 10, right: 30 } }
|
|
}
|
|
});
|
|
} else {
|
|
rbCauseContainer.innerHTML = '<span class="text-slate-400 font-medium"><i class="fa-solid fa-box-open mr-2"></i>ไม่พบข้อมูล</span>';
|
|
}
|
|
}
|
|
|
|
// --- 3. AdjRW Summary ---
|
|
document.querySelectorAll('.adjrw-in-low').forEach(el => el.innerText = data.adjrw_summary.in_low.toLocaleString());
|
|
document.querySelectorAll('.adjrw-in-high').forEach(el => el.innerText = data.adjrw_summary.in_high.toLocaleString());
|
|
document.querySelectorAll('.adjrw-out-low').forEach(el => el.innerText = data.adjrw_summary.out_low.toLocaleString());
|
|
document.querySelectorAll('.adjrw-out-high').forEach(el => el.innerText = data.adjrw_summary.out_high.toLocaleString());
|
|
|
|
// วาดกราฟแนวโน้มรายเดือนสำหรับ AdjRW
|
|
updateBarChart('adjrwInLowTrend', data.adjrw_in_low_trend, '#3b82f6');
|
|
updateBarChart('adjrwInHighTrend', data.adjrw_in_high_trend, '#6366f1');
|
|
updateBarChart('adjrwOutLowTrend', data.adjrw_out_low_trend, '#f43f5e');
|
|
updateBarChart('adjrwOutHighTrend', data.adjrw_out_high_trend, '#f97316');
|
|
|
|
} catch (err) {
|
|
console.error("Fetch Data Error:", err);
|
|
alert("ไม่สามารถดึงข้อมูลได้ กรุณาตรวจสอบการเชื่อมต่อฐานข้อมูล หรือคำสั่ง SQL\nรายละเอียด: " + err.message);
|
|
} finally {
|
|
if (overlay) {
|
|
setTimeout(() => {
|
|
overlay.classList.remove('flex');
|
|
overlay.classList.add('hidden');
|
|
}, 500);
|
|
}
|
|
}
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
if (!window.appCharts) window.appCharts = {};
|
|
document.querySelectorAll('.ipd-filter-form').forEach(form => {
|
|
form.addEventListener('submit', (e) => {
|
|
e.preventDefault();
|
|
fetchIpdRealData(e.target);
|
|
});
|
|
});
|
|
});
|
|
</script>
|