PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_TIMEOUT => 5, PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci" ] ); } catch (PDOException $e) { // Trả về lỗi chi tiết để debug $code = $e->getCode(); $msg = $e->getMessage(); // SQLSTATE[HY000] [1045] = Access denied // SQLSTATE[HY000] [1049] = Unknown database // SQLSTATE[HY000] [2002] = Connection refused http_response_code(500); echo json_encode([ 'ok' => false, 'msg' => "Lỗi DB [$code]: $msg", 'data' => null, 'debug' => [ 'host' => DB_HOST, 'user' => DB_USER, 'db' => DB_NAME, 'php' => PHP_VERSION, ] ], JSON_UNESCAPED_UNICODE); exit; } } return $p; } function ok($d = [], $m = 'OK') { echo json_encode(['ok' => true, 'msg' => $m, 'data' => $d], JSON_UNESCAPED_UNICODE); exit; } function err($m, $c = 400) { http_response_code($c); echo json_encode(['ok' => false, 'msg' => $m, 'data' => null], JSON_UNESCAPED_UNICODE); exit; } function inp() { $r = file_get_contents('php://input'); $d = json_decode($r, true); return $d ?? $_POST; } // ── INIT TABLES ─────────────────────────────────────────────── function nl_init() { $db = nl_db(); $db->exec("CREATE TABLE IF NOT EXISTS nl_sc_phieu ( id INT AUTO_INCREMENT PRIMARY KEY, ma_phieu VARCHAR(30) NOT NULL UNIQUE, ho_ten VARCHAR(100) DEFAULT '', doi VARCHAR(50) DEFAULT '', bien_so VARCHAR(20) DEFAULT '', loai_xe VARCHAR(100) DEFAULT '', sdt VARCHAR(20) DEFAULT '', ngay_phat_hien DATE NULL, so_km INT DEFAULT 0, tuyen_duong VARCHAR(100) DEFAULT '', mo_ta_hong TEXT, nguoi_lap VARCHAR(100) DEFAULT '', ngay_lap DATE NULL, ngay_dua_xe DATE NULL, ngay_nhan_xe DATE NULL, so_hoa_don VARCHAR(50) DEFAULT '', don_vi_sc VARCHAR(200) DEFAULT '', ket_qua_sau_sc TEXT, da_hoan_thanh TINYINT(1) DEFAULT 0, ghi_chu_ton_dong TEXT, tong_chi_phi DECIMAL(15,0) DEFAULT 0, trang_thai VARCHAR(20) DEFAULT 'cho_duyet', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX idx_bs (bien_so), INDEX idx_tt (trang_thai), INDEX idx_nl (ngay_lap) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"); $db->exec("CREATE TABLE IF NOT EXISTS nl_sc_hang_muc ( id INT AUTO_INCREMENT PRIMARY KEY, phieu_id INT NOT NULL, stt INT DEFAULT 1, ten VARCHAR(255) DEFAULT '', uu_tien VARCHAR(20) DEFAULT 'binh_thuong', du_kien DECIMAL(15,0) DEFAULT 0, ghi_chu VARCHAR(500) DEFAULT '', INDEX idx_pid (phieu_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"); $db->exec("CREATE TABLE IF NOT EXISTS nl_sc_chi_phi ( id INT AUTO_INCREMENT PRIMARY KEY, phieu_id INT NOT NULL, stt INT DEFAULT 1, ten VARCHAR(255) DEFAULT '', dvt VARCHAR(50) DEFAULT '', so_luong DECIMAL(10,3) DEFAULT 1, don_gia DECIMAL(15,0) DEFAULT 0, thanh_tien DECIMAL(15,0) DEFAULT 0, ghi_chu VARCHAR(255) DEFAULT '', INDEX idx_pid (phieu_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"); $db->exec("CREATE TABLE IF NOT EXISTS nl_sc_hinh ( id INT AUTO_INCREMENT PRIMARY KEY, phieu_id INT NOT NULL, ten_file VARCHAR(255) DEFAULT '', mime VARCHAR(50) DEFAULT 'image/jpeg', data_b64 MEDIUMTEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_pid (phieu_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"); } // Chạy init — nếu lỗi trả về JSON rõ ràng try { nl_init(); } catch (Exception $e) { http_response_code(500); echo json_encode(['ok' => false, 'msg' => 'Init lỗi: ' . $e->getMessage(), 'data' => null], JSON_UNESCAPED_UNICODE); exit; } // ── ROUTER ──────────────────────────────────────────────────── $a = isset($_GET['a']) ? trim($_GET['a']) : ''; switch ($a) { case 'tao': sc_tao(); break; case 'sua': sc_sua(); break; case 'ds': sc_ds(); break; case 'ct': sc_ct(); break; case 'xoa': sc_xoa(); break; case 'nt_luu': sc_nt_luu(); break; case 'hinh_up': sc_hinh_up(); break; case 'hinh_xoa': sc_hinh_xoa(); break; case 'hinh_lay': sc_hinh_lay(); break; case 'tk': sc_tk(); break; case 'find': sc_find(); break; case 'ping': ok(['pong' => true, 'db' => DB_NAME, 'time' => date('Y-m-d H:i:s')], 'OK'); break; default: err("Action '$a' không hợp lệ"); } // ── HELPERS ─────────────────────────────────────────────────── function ma_moi() { $db = nl_db(); $p = 'SC' . date('Ymd'); $s = $db->prepare("SELECT COUNT(*) FROM nl_sc_phieu WHERE ma_phieu LIKE ?"); $s->execute([$p . '%']); return $p . '-' . str_pad((int)$s->fetchColumn() + 1, 3, '0', STR_PAD_LEFT); } function dv($d, $k, $def = '') { return isset($d[$k]) ? $d[$k] : $def; } function dn($d, $k) { return (isset($d[$k]) && $d[$k] !== '' && $d[$k] !== null) ? $d[$k] : null; } function save_hm($pid, $arr) { $db = nl_db(); $db->prepare("DELETE FROM nl_sc_hang_muc WHERE phieu_id=?")->execute([$pid]); $s = $db->prepare("INSERT INTO nl_sc_hang_muc (phieu_id,stt,ten,uu_tien,du_kien,ghi_chu) VALUES(?,?,?,?,?,?)"); foreach ((array)$arr as $i => $h) { if (empty(trim($h['ten'] ?? ''))) continue; $s->execute([$pid, $i+1, $h['ten'], $h['uu_tien'] ?? 'binh_thuong', (float)($h['du_kien'] ?? 0), $h['ghi_chu'] ?? '']); } } function save_cp($pid, $arr) { $db = nl_db(); $db->prepare("DELETE FROM nl_sc_chi_phi WHERE phieu_id=?")->execute([$pid]); $s = $db->prepare("INSERT INTO nl_sc_chi_phi (phieu_id,stt,ten,dvt,so_luong,don_gia,thanh_tien,ghi_chu) VALUES(?,?,?,?,?,?,?,?)"); foreach ((array)$arr as $i => $c) { if (empty(trim($c['ten'] ?? ''))) continue; $s->execute([$pid, $i+1, $c['ten'], $c['dvt'] ?? '', (float)($c['sl'] ?? 1), (float)($c['dg'] ?? 0), (float)($c['tt'] ?? 0), $c['ghi_chu'] ?? '']); } } // ── CRUD ────────────────────────────────────────────────────── function sc_tao() { $d = inp(); $db = nl_db(); $ma = ma_moi(); $db->prepare("INSERT INTO nl_sc_phieu (ma_phieu,ho_ten,doi,bien_so,loai_xe,sdt,ngay_phat_hien,so_km,tuyen_duong,mo_ta_hong,nguoi_lap,ngay_lap,trang_thai) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?)")->execute([ $ma, dv($d,'ho_ten'), dv($d,'doi'), dv($d,'bien_so'), dv($d,'loai_xe'), dv($d,'sdt'), dn($d,'ngay_phat_hien'), (int)dv($d,'so_km',0), dv($d,'tuyen_duong'), dv($d,'mo_ta_hong'), dv($d,'nguoi_lap'), dn($d,'ngay_lap') ?? date('Y-m-d'), 'cho_duyet' ]); $pid = $db->lastInsertId(); if (!empty($d['hang_muc'])) save_hm($pid, $d['hang_muc']); ok(['id' => $pid, 'ma' => $ma], 'Tạo phiếu thành công'); } function sc_sua() { $d = inp(); $id = (int)dv($d,'id'); if (!$id) err('Thiếu ID'); $db = nl_db(); $db->prepare("UPDATE nl_sc_phieu SET ho_ten=?,doi=?,bien_so=?,loai_xe=?,sdt=?,ngay_phat_hien=?,so_km=?, tuyen_duong=?,mo_ta_hong=?,nguoi_lap=?,ngay_lap=?,trang_thai=? WHERE id=?")->execute([ dv($d,'ho_ten'), dv($d,'doi'), dv($d,'bien_so'), dv($d,'loai_xe'), dv($d,'sdt'), dn($d,'ngay_phat_hien'), (int)dv($d,'so_km',0), dv($d,'tuyen_duong'), dv($d,'mo_ta_hong'), dv($d,'nguoi_lap'), dn($d,'ngay_lap') ?? date('Y-m-d'), dv($d,'trang_thai','cho_duyet'), $id ]); if (isset($d['hang_muc'])) save_hm($id, $d['hang_muc']); ok([], 'Cập nhật thành công'); } function sc_ds() { $db = nl_db(); $w = ['1=1']; $p = []; $bs = trim($_GET['bs'] ?? ''); $tt = $_GET['tt'] ?? ''; $tu = $_GET['tu'] ?? ''; $den = $_GET['den'] ?? ''; $pg = max(1, (int)($_GET['pg'] ?? 1)); $lmt = min(100, (int)($_GET['lmt'] ?? 30)); $off = ($pg - 1) * $lmt; if ($bs) { $w[] = 'bien_so LIKE ?'; $p[] = "%$bs%"; } if ($tt) { $w[] = 'trang_thai=?'; $p[] = $tt; } if ($tu) { $w[] = 'ngay_lap>=?'; $p[] = $tu; } if ($den) { $w[] = 'ngay_lap<=?'; $p[] = $den; } $wh = implode(' AND ', $w); $s = $db->prepare("SELECT id,ma_phieu,ngay_lap,bien_so,loai_xe,ho_ten,doi,trang_thai,tong_chi_phi FROM nl_sc_phieu WHERE $wh ORDER BY created_at DESC LIMIT $lmt OFFSET $off"); $s->execute($p); $cs = $db->prepare("SELECT COUNT(*) FROM nl_sc_phieu WHERE $wh"); $cs->execute($p); $tot = (int)$cs->fetchColumn(); ok(['rows' => $s->fetchAll(), 'tot' => $tot, 'pg' => $pg, 'pages' => (int)ceil($tot / $lmt)]); } function sc_ct() { $id = (int)($_GET['id'] ?? 0); if (!$id) err('Thiếu ID'); $db = nl_db(); $s = $db->prepare("SELECT * FROM nl_sc_phieu WHERE id=?"); $s->execute([$id]); $row = $s->fetch(); if (!$row) err('Không tìm thấy', 404); $h = $db->prepare("SELECT * FROM nl_sc_hang_muc WHERE phieu_id=? ORDER BY stt"); $h->execute([$id]); $row['hang_muc'] = $h->fetchAll(); $c = $db->prepare("SELECT * FROM nl_sc_chi_phi WHERE phieu_id=? ORDER BY stt"); $c->execute([$id]); $row['chi_phi'] = $c->fetchAll(); $hc = $db->prepare("SELECT COUNT(*) FROM nl_sc_hinh WHERE phieu_id=?"); $hc->execute([$id]); $row['so_hinh'] = (int)$hc->fetchColumn(); ok($row); } function sc_xoa() { $d = inp(); $id = (int)($d['id'] ?? 0); if (!$id) err('Thiếu ID'); $db = nl_db(); foreach (['nl_sc_hang_muc','nl_sc_chi_phi','nl_sc_hinh'] as $t) $db->prepare("DELETE FROM $t WHERE phieu_id=?")->execute([$id]); $db->prepare("DELETE FROM nl_sc_phieu WHERE id=?")->execute([$id]); ok([], 'Đã xóa'); } function sc_nt_luu() { $d = inp(); $id = (int)dv($d,'id'); if (!$id) err('Thiếu ID'); $db = nl_db(); $cp = (array)($d['chi_phi'] ?? []); $tot = 0; foreach ($cp as $c) $tot += (float)($c['tt'] ?? 0); $vat = (float)($d['vat_pct'] ?? 0); $grand = round($tot + $tot * $vat / 100); $db->prepare("UPDATE nl_sc_phieu SET ngay_dua_xe=?,ngay_nhan_xe=?,so_hoa_don=?,don_vi_sc=?, ket_qua_sau_sc=?,da_hoan_thanh=?,ghi_chu_ton_dong=?, tong_chi_phi=?,trang_thai='hoan_thanh' WHERE id=?")->execute([ dn($d,'ngay_dua_xe'), dn($d,'ngay_nhan_xe'), dv($d,'so_hoa_don'), dv($d,'don_vi_sc'), dv($d,'ket_qua_sau_sc'), (int)($d['da_hoan_thanh'] ?? 0), dv($d,'ghi_chu_ton_dong'), $grand, $id ]); save_cp($id, $cp); ok(['tong' => $grand], 'Lưu nghiệm thu thành công'); } function sc_hinh_up() { $d = inp(); if (!isset($d['phieu_id']) || !isset($d['data_b64'])) err('Thiếu dữ liệu'); $db = nl_db(); $db->prepare("INSERT INTO nl_sc_hinh (phieu_id,ten_file,mime,data_b64) VALUES(?,?,?,?)") ->execute([(int)$d['phieu_id'], $d['ten_file'] ?? 'img.jpg', $d['mime'] ?? 'image/jpeg', $d['data_b64']]); ok(['hid' => $db->lastInsertId()]); } function sc_hinh_xoa() { $d = inp(); $id = (int)($d['id'] ?? 0); if (!$id) err('Thiếu ID'); nl_db()->prepare("DELETE FROM nl_sc_hinh WHERE id=?")->execute([$id]); ok([], 'Đã xóa hình'); } function sc_hinh_lay() { $pid = (int)($_GET['pid'] ?? 0); if (!$pid) err('Thiếu pid'); $s = nl_db()->prepare("SELECT id,ten_file,mime,data_b64,created_at FROM nl_sc_hinh WHERE phieu_id=? ORDER BY id"); $s->execute([$pid]); ok($s->fetchAll()); } function sc_tk() { $db = nl_db(); $thang = ($_GET['thang'] ?? date('Y-m')); $r = $db->query("SELECT COUNT(*) t, SUM(trang_thai='cho_duyet') cho, SUM(trang_thai='dang_xu_ly') dang, SUM(trang_thai='hoan_thanh') xong FROM nl_sc_phieu")->fetch(); $s = $db->prepare("SELECT SUM(tong_chi_phi) FROM nl_sc_phieu WHERE DATE_FORMAT(ngay_lap,'%Y-%m')=?"); $s->execute([$thang]); ok(['tong' => (int)$r['t'], 'cho' => (int)$r['cho'], 'dang' => (int)$r['dang'], 'xong' => (int)$r['xong'], 'cp' => (float)($s->fetchColumn() ?? 0)]); } function sc_find() { $q = trim($_GET['q'] ?? ''); if (!$q) ok(['rows' => []]); $l = "%$q%"; $s = nl_db()->prepare("SELECT id,ma_phieu,bien_so,ho_ten,loai_xe,trang_thai FROM nl_sc_phieu WHERE bien_so LIKE ? OR ho_ten LIKE ? OR ma_phieu LIKE ? ORDER BY created_at DESC LIMIT 30"); $s->execute([$l, $l, $l]); ok(['rows' => $s->fetchAll()]); }