diff --git a/console/controllers/LisSendBackUpController.php b/console/controllers/LisSendBackUpController.php index ec2ff6f3..926dd141 100755 --- a/console/controllers/LisSendBackUpController.php +++ b/console/controllers/LisSendBackUpController.php @@ -18,7 +18,6 @@ class LisSendBackUpController extends Controller { error_log("=== Hanuman Result Sikarin START ==="); - $sendMapping = []; // config diff --git a/console/controllers/LisSendPathoAllLnController.php b/console/controllers/LisSendPathoAllLnController.php new file mode 100644 index 00000000..80ac7425 --- /dev/null +++ b/console/controllers/LisSendPathoAllLnController.php @@ -0,0 +1,437 @@ +joinWith(['approveBy']) + ->andWhere(['<>', 'ln', '']) + ->andWhere(['not like', 'ln', '-%', false]) + ->andWhere(['not', ['report_at' => null]]) + ->andWhere(['not', ['approve_at' => null]]) + ->andWhere(['approve_status' => 3]) + ->andWhere(['>', 'register_at', '2026-02-01 00:00:00']) + ->andWhere(['in', 'patient_case_approve.hospital_id', [703, 366, 367, 169]]) + ->all(); + + foreach ($resultPatho as $case) { + + $lnList = preg_split('/\s*,\s*/', (string)$case->ln, -1, PREG_SPLIT_NO_EMPTY); + + foreach ($lnList as $ln) { + + // ต้องยาวมากกว่า 2 ถึงจะตัดได้ + if (strlen($ln) <= 2) { + continue; + } + + // ✅ ตัด 2 ตัวท้าย (เช่น 76) + $requestLn = substr($ln, 0, -2); + + $hisRequestCheck = HisRequest::find()->where(['HN' => (string)$case['h_n']])->andFilterWhere(['like', 'LabNo', (string)$requestLn])->one(); + if ($hisRequestCheck && isset($hisRequestCheck->testCode)) { + $lisResultCheck = LisResult::find()->where(['lab_no' => $hisRequestCheck->LabNo])->one(); + if (!$lisResultCheck) { + + $hospital_code = Yii::$app->patho->createCommand("select code from const_hospital where id = " . $case['hospital_id'] . "")->queryOne(); + $filesData = []; + $timestamp = date('dmYHis'); + + $LisResultUrl = "https://pathology.prolab.co.th/reports/H" . $hospital_code['code'] . "/" . $ln . "_" . $case['h_n'] . "_Prolab-" . $case['id_case'] . ".pdf"; + //echo $LisResultUrl . "
"; + + $path = Yii::getAlias('@runtime') . '/report.pdf'; + + /*if ($this->downloadFileStream($LisResultUrl, $path)) { + echo 'Download completed'; + } else { + echo 'Download failed'; + }*/ + + $pdfContent = $this->fetchUrl($LisResultUrl); + if ($pdfContent !== false && strlen($pdfContent) > 0) { + $filesData[] = [ + 'FileName' => "{$hisRequestCheck->LabNo}_{$timestamp}.pdf", + 'Filebase64' => base64_encode($pdfContent), + ]; + + $testList = [ + 'LabNo' => $hisRequestCheck->LabNo ?? '', + 'RequestRemark' => '', + 'ResultStatus' => '', + 'TestList' => [ + [ + 'TestCode' => (string)($hisRequestCheck->testCode->TestCode ?? ''), + 'TestName' => (string)($hisRequestCheck->testCode->TestName ?? ''), + 'TestRemark' => '', + 'InformCriticalByCode' => '', + 'InformCriticalBy' => '', + 'InformCriticalTo' => '', + 'InformCriticalDateTime' => '', + 'CriticalFlag' => '', + 'ReportResultByCode' => '', + 'ReportResultBy' => (string)($case->pathologist ?? ''), + 'ReportResultDateTime' => $this->dateTimeFormat($case->report_at ?? ''), + 'ApproveResultByCode' => '', + 'ApproveResultBy' => (string)($case->approveBy->realname ?? ''), + 'ApproveResultDateTime' => $this->dateTimeFormat($case->approve_at ?? ''), + 'Filedata' => $filesData, + 'ResultList' => [ + [ + 'ResultCode' => (string)($hisRequestCheck->testCode->resultCode->ResultCode ?? ''), + 'ResultName' => (string)($hisRequestCheck->testCode->resultCode->ResultName ?? ''), + 'ResultValue' => '', + 'ResultUnit' => '', + 'ResultFlag' => '', + 'ReferenceRange' => '', + 'ResultRemark' => '', + ] + ], + ] + ] + ]; + var_dump($testList); + } + } + } + } + } + + error_log("=== Hanuman Result Sikarin Patho END ==="); + } + + /** helper: ส่ง JSON ผ่าน cURL (คืนข้อมูล debug เต็ม) */ + protected function postJsonCurl($url, $payload, $headers = []) + { + $ch = curl_init($url); + $jsonPayload = json_encode($payload, JSON_UNESCAPED_UNICODE); + + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonPayload); + + // security: allow config to control SSL verify + $verifySsl = Yii::$app->params['external_api']['verify_ssl'] ?? true; + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $verifySsl); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, $verifySsl ? 2 : 0); + + curl_setopt($ch, CURLOPT_TIMEOUT, 60); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + + $curlHeaders = ['Content-Type: application/json', 'Accept: application/json', 'Expect:', 'User-Agent: MyLabSender/1.0']; + foreach ($headers as $k => $v) { + if (is_int($k)) { + $curlHeaders[] = $v; + } else { + $curlHeaders[] = "{$k}: {$v}"; + } + } + curl_setopt($ch, CURLOPT_HTTPHEADER, $curlHeaders); + + $response = curl_exec($ch); + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $curlErrno = curl_errno($ch); + $curlError = $curlErrno ? curl_error($ch) : null; + curl_close($ch); + + return [ + 'http_code' => $httpCode, + 'response' => $response, + 'curl_error' => $curlError, + 'curl_errno' => $curlErrno, + 'url' => $url, + 'payload_size' => strlen($jsonPayload), + ]; + } + + protected function downloadFileStream($url, $savePath) + { + $context = stream_context_create([ + 'http' => [ + 'method' => 'GET', + 'timeout' => 30, // วินาที + 'header' => "User-Agent: FileFetcher/1.0\r\n", + ], + 'ssl' => [ + 'verify_peer' => false, + 'verify_peer_name' => false, + ], + ]); + + $read = @fopen($url, 'rb', false, $context); + if ($read === false) { + error_log("Stream open failed: {$url}"); + return false; + } + + $write = fopen($savePath, 'w+b'); + if ($write === false) { + fclose($read); + error_log("Cannot open file for writing: {$savePath}"); + return false; + } + + stream_set_timeout($read, 30); + + stream_copy_to_stream($read, $write); + + $meta = stream_get_meta_data($read); + + fclose($read); + fclose($write); + + if ($meta['timed_out']) { + @unlink($savePath); + error_log("Stream timeout while downloading: {$url}"); + return false; + } + + return $savePath; + } + + /** + * helper: fetch URL via cURL (robust) + */ + protected function fetchUrl($url) + { + $ch = curl_init($url); + + curl_setopt_array($ch, [ + CURLOPT_RETURNTRANSFER => true, + CURLOPT_FOLLOWLOCATION => true, + + CURLOPT_CONNECTTIMEOUT => 600, // 10 วินาที + CURLOPT_TIMEOUT => 600, // รวมทั้งหมด 20 วินาที + + CURLOPT_SSL_VERIFYPEER => false, + CURLOPT_SSL_VERIFYHOST => false, + CURLOPT_HEADER => false, + CURLOPT_USERAGENT => 'Mozilla/5.0 (compatible; PDFFetcher/1.0)', + CURLOPT_BUFFERSIZE => 128 * 1024, + CURLOPT_HTTPHEADER => [ + 'Accept: application/pdf', + 'Cache-Control: no-cache' + ], + + // ลดปัญหา IPv6 hang + CURLOPT_IPRESOLVE => CURL_IPRESOLVE_V4, + ]); + + $data = curl_exec($ch); + $errno = curl_errno($ch); + $error = curl_error($ch); + $http = curl_getinfo($ch, CURLINFO_HTTP_CODE); + + curl_close($ch); + + if ($data === false) { + error_log("fetchUrl timeout or error: {$url} errno={$errno} err={$error}"); + return false; + } + + if ($http >= 400) { + error_log("fetchUrl http error: {$url} http={$http}"); + return false; + } + + return $data; + } + + + /** + * Log helper — writes to STDOUT with timestamp and also to Yii log. + * $level: 'info'|'warning'|'error' + */ + protected function logConsole($message, $level = 'info') + { + $time = date('Y-m-d H:i:s'); + $line = "[{$time}] {$message}" . PHP_EOL; + // write to console + $this->stdout($line); + // write to Yii logger with appropriate level + switch (strtolower($level)) { + case 'error': + Yii::error($message, __METHOD__); + break; + case 'warning': + Yii::warning($message, __METHOD__); + break; + default: + Yii::info($message, __METHOD__); + } + } + + /** + * Truncate long strings for logging. $maxBytes = null => no truncation. + */ + protected function truncateForLog($text, $maxBytes = 4096) + { + if ($text === null) return ''; + if ($maxBytes === null) return $text; + $len = strlen($text); + if ($len <= $maxBytes) return $text; + $prefix = substr($text, 0, $maxBytes); + return $prefix . "\n...TRUNCATED... (original_length={$len})"; + } + + /** helper: แปลง datetime เป็น dd/mm/YYYY H:i:s */ + public function dateTimeFormat($value) + { + if (empty($value)) return ''; + try { + $dt = new \DateTime($value); + return $dt->format('d/m/Y H:i:s'); + } catch (\Throwable $e) { + return ''; + } + } + + /** + * ขอ token จาก API (คืนค่า associative array แบบ {ok, token, expires_at, http_code, raw, error}) + */ + protected function getApiToken($cfg) + { + $tokenUrl = $cfg['tokenUrl'] ?? Yii::$app->params['external_api']['tokenUrl'] ?? null; + $username = $cfg['username'] ?? Yii::$app->params['external_api']['username'] ?? null; + $password = $cfg['password'] ?? Yii::$app->params['external_api']['password'] ?? null; + + $payload = ['username' => $username, 'password' => $password]; + + $ch = curl_init($tokenUrl); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload)); + curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json', 'Accept: application/json']); + curl_setopt($ch, CURLOPT_TIMEOUT, 15); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); + + $raw = curl_exec($ch); + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $curlErr = curl_errno($ch) ? curl_error($ch) : null; + curl_close($ch); + + $result = ['ok' => false, 'token' => null, 'expires_at' => null, 'http_code' => $httpCode, 'raw' => $raw, 'error' => null]; + + if ($raw === false || $curlErr) { + $result['error'] = 'cURL error: ' . ($curlErr ?? 'unknown'); + return $result; + } + + $decoded = json_decode($raw, true); + if (json_last_error() !== JSON_ERROR_NONE) { + $result['error'] = 'Invalid JSON response from token endpoint'; + return $result; + } + + if (!empty($decoded['access_token'])) { + $result['ok'] = true; + $result['token'] = $decoded['access_token']; + $expiresIn = intval($decoded['expires_in'] ?? 3600); + $result['expires_at'] = date('Y-m-d H:i:s', time() + $expiresIn); + } elseif (!empty($decoded['token'])) { + $result['ok'] = true; + $result['token'] = $decoded['token']; + $expiresIn = intval($decoded['expires_in'] ?? 3600); + $result['expires_at'] = date('Y-m-d H:i:s', time() + $expiresIn); + } else { + $result['error'] = $decoded['error'] ?? ($decoded['message'] ?? 'No token in response'); + } + + return $result; + } + + /** + * ดึง token ที่ valid (คืน token string หรือ null และตั้ง $tokenError หากมีปัญหา) + * พร้อม debug log วันที่หมดอายุของ Token + */ + protected function getValidApiToken($cfg, &$tokenError = null) + { + $apiName = $cfg['apiName'] ?? 'gems_api'; + $tokenModel = ApiToken::find()->where(['api_name' => $apiName])->one(); + $token = null; + $nowTs = time(); + $refreshMargin = $cfg['refreshMargin'] ?? 300; // 5 นาที + + $needRequest = !$tokenModel || (strtotime($tokenModel->expires_at) <= ($nowTs + $refreshMargin)); + + if ($tokenModel) { + //$this->logConsole("Token in DB found for {$apiName}: expires_at={$tokenModel->expires_at}, token_prefix=" . substr($tokenModel->token ?? '', 0, 15) . "..."); + error_log("Token in DB found for {$apiName}: expires_at={$tokenModel->expires_at}, token_prefix=" . substr($tokenModel->token ?? '', 0, 15) . "..."); + } else { + error_log("No token found in DB for {$apiName}, will request new token"); + //$this->logConsole("No token found in DB for {$apiName}, will request new token"); + } + + if ($needRequest) { + $tokenResp = $this->getApiToken($cfg); + + if (empty($tokenResp) || empty($tokenResp['ok'])) { + $tokenError = [ + 'message' => 'Failed to get token from API', + 'tokenUrl' => $cfg['tokenUrl'] ?? null, + 'error' => $tokenResp['error'] ?? 'Unknown error', + 'http_code' => $tokenResp['http_code'] ?? 0, + 'raw' => $tokenResp['raw'] ?? null, + ]; + + if ($tokenModel && !empty($tokenModel->token)) { + error_log("Using old token as fallback (expires_at={$tokenModel->expires_at})"); + //$this->logConsole("Using old token as fallback (expires_at={$tokenModel->expires_at})"); + return $tokenModel->token; // fallback + } + + return null; + } + + $token = $tokenResp['token']; + $expiresAt = $tokenResp['expires_at'] ?? date('Y-m-d H:i:s', time() + 3600); + + if (!$tokenModel) { + $tokenModel = new ApiToken(); + $tokenModel->api_name = $apiName; + } + + $tokenModel->token = $token; + $tokenModel->expires_at = $expiresAt; + + try { + $tokenModel->save(false); + error_log("New token saved: api_name={$apiName}, expires_at={$expiresAt}, token_prefix=" . substr($token ?? '', 0, 15) . "..."); + //$this->logConsole("New token saved: api_name={$apiName}, expires_at={$expiresAt}, token_prefix=" . substr($token, 0, 15) . "..."); + } catch (\Throwable $e) { + //$this->logConsole("Error saving new token: " . $e->getMessage(), 'error'); + error_log("Error saving new token: " . $e->getMessage(), 'error'); + if ($tokenModel && !empty($tokenModel->token)) { + return $tokenModel->token; // fallback + } + $tokenError = $e->getMessage(); + return null; + } + } else { + $token = $tokenModel->token; + //$this->logConsole("Reusing valid token: expires_at={$tokenModel->expires_at}, token_prefix=" . substr($token, 0, 15) . "..."); + error_log("Reusing valid token: expires_at={$tokenModel->expires_at}, token_prefix=" . substr($token, 0, 15) . "..."); + } + return $token; + } +} diff --git a/console/controllers/LisSendPathoController.php b/console/controllers/LisSendPathoController.php index 31d2e838..6ee888a1 100755 --- a/console/controllers/LisSendPathoController.php +++ b/console/controllers/LisSendPathoController.php @@ -36,7 +36,6 @@ class LisSendPathoController extends Controller foreach ($resultPatho as $case) { - $ln = (string)$case['ln']; $requestLn = substr($ln, 0, -2); @@ -107,7 +106,7 @@ class LisSendPathoController extends Controller ] ] ]; - var_dump($testList); + //var_dump($testList); } } }