2025-12-19 06:18:21 +00:00
< ? php
namespace console\controllers ;
2026-01-06 06:45:01 +00:00
2025-12-19 06:18:21 +00:00
use console\models\ApiToken ;
use console\models\HisRequest ;
2026-01-06 06:45:01 +00:00
use console\models\LisResult ;
2025-12-19 06:18:21 +00:00
use console\models\PatientCaseApprove ;
use yii\console\Controller ;
use Yii ;
class LisSendPathoController extends Controller
{
/**
* Console action : รันกระบวนการส่งผลไปยัง external API
* เรียกจาก CLI : php / path / to / yii lis - api - send / create
*/
public function actionCreate ()
{
error_log ( " === Hanuman Result Sikarin Patho START === " );
2026-01-06 06:45:01 +00:00
$resultPatho = PatientCaseApprove :: find ()
-> 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' , '2025-11-01 00:00:00' ])
-> andWhere ([ 'in' , 'patient_case_approve.hospital_id' , [ 703 , 366 , 367 , 169 ]])
2025-12-19 06:18:21 +00:00
-> asArray ()
-> all ();
2026-01-06 06:45:01 +00:00
foreach ( $resultPatho as $case ) {
2025-12-19 06:18:21 +00:00
2026-01-06 06:45:01 +00:00
$ln = ( string ) $case [ 'ln' ];
$requestLn = substr ( $ln , 0 , - 2 );
2025-12-19 06:18:21 +00:00
2026-01-06 06:45:01 +00:00
$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 ) {
2025-12-19 06:18:21 +00:00
2026-01-06 06:45:01 +00:00
$hospital_code = Yii :: $app -> patho -> createCommand ( " select code from const_hospital where id = " . $case [ 'hospital_id' ] . " " ) -> queryOne ();
$filesData = [];
$timestamp = date ( 'dmYHis' );
2025-12-19 06:18:21 +00:00
2026-01-06 06:45:01 +00:00
$LisResultUrl = " https://pathology.prolab.co.th/reports/H " . $hospital_code [ 'code' ] . " / " . $case [ 'ln' ] . " _ " . $case [ 'h_n' ] . " _Prolab- " . $case [ 'id_case' ] . " .pdf " ;
//$url = 'https://example.com/report.pdf';
/* var_dump ( stream_get_wrappers ());
die (); */
var_dump ( fopen ( 'https://www.google.com' , 'rb' ));
die ();
$path = Yii :: getAlias ( '@runtime' ) . '/report.pdf' ;
2025-12-19 06:18:21 +00:00
2026-01-06 06:45:01 +00:00
if ( $this -> downloadFileStream ( $LisResultUrl , $path )) {
echo 'Download completed' ;
} else {
echo 'Download failed' ;
2025-12-19 06:18:21 +00:00
}
2026-01-06 06:45:01 +00:00
die ();
$pdfContent = $this -> fetchUrl ( $LisResultUrl , 6000 );
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 );
2025-12-19 06:18:21 +00:00
}
2026-01-06 06:45:01 +00:00
}
2025-12-19 06:18:21 +00:00
}
2026-01-06 06:45:01 +00:00
}
2025-12-19 06:18:21 +00:00
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 ),
];
}
2026-01-06 06:45:01 +00:00
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 ;
}
2025-12-19 06:18:21 +00:00
/**
* helper : fetch URL via cURL ( robust )
*/
2026-01-06 06:45:01 +00:00
protected function fetchUrl ( $url )
2025-12-19 06:18:21 +00:00
{
$ch = curl_init ( $url );
2026-01-06 06:45:01 +00:00
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 ,
// ลดปัญหา IPv6 hang
CURLOPT_IPRESOLVE => CURL_IPRESOLVE_V4 ,
]);
2025-12-19 06:18:21 +00:00
$data = curl_exec ( $ch );
2026-01-06 06:45:01 +00:00
$errno = curl_errno ( $ch );
$error = curl_error ( $ch );
$http = curl_getinfo ( $ch , CURLINFO_HTTP_CODE );
2025-12-19 06:18:21 +00:00
curl_close ( $ch );
2026-01-06 06:45:01 +00:00
if ( $data === false ) {
error_log ( " fetchUrl timeout or error: { $url } errno= { $errno } err= { $error } " );
2025-12-19 06:18:21 +00:00
return false ;
}
2026-01-06 06:45:01 +00:00
if ( $http >= 400 ) {
error_log ( " fetchUrl http error: { $url } http= { $http } " );
return false ;
}
2025-12-19 06:18:21 +00:00
return $data ;
}
2026-01-06 06:45:01 +00:00
2025-12-19 06:18:21 +00:00
/**
* 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 ;
}
}