Files
BE_IBL/application/libraries/Generateqrreport.php
2026-05-24 18:14:52 +07:00

666 lines
26 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
defined("BASEPATH") or exit("No direct script access allowed");
/*
SQL DDL untuk tabel qr_printout:
CREATE TABLE IF NOT EXISTS `qr_printout` (
`QR_PrintOutID` int(11) NOT NULL AUTO_INCREMENT,
`QR_PrintOutT_OrderHeaderID` int(11) NOT NULL DEFAULT 0
COMMENT 'FK ke t_orderheader',
`QR_PrintOutGroup_ResultID` int(11) NOT NULL DEFAULT 0
COMMENT 'FK ke m_groupresult (opsional)',
`QR_PrintOutT_TestID` int(11) NOT NULL DEFAULT 0
COMMENT 'FK ke m_test (opsional)',
`QR_PrintOutGroup_ResultName` varchar(250) NOT NULL DEFAULT ''
COMMENT 'Label group hasil cetak (snapshot)',
`QR_PrintOutUUID` varchar(36) NOT NULL DEFAULT ''
COMMENT 'UUID v4 unik per sesi cetak',
`QR_PrintOutVerifyURL` varchar(500) NOT NULL DEFAULT ''
COMMENT 'URL QR Code & tujuan upload: https://ds.com/files/{uuid}.pdf (Golang upload ke sini, pasien scan QR buka ini)',
`QR_PrintOutReportURL` varchar(500) NOT NULL DEFAULT ''
COMMENT 'URL sumber PDF di PHP app server (Golang HTTP-fetch dari sini untuk diupload ke dedicated server)',
`QR_PrintOutTempFilePath` varchar(500) NOT NULL DEFAULT ''
COMMENT 'Path absolut file PDF sementara di PHP server (untuk diambil Golang)',
`QR_PrintOutUploadStatus` enum('pending','uploaded','failed','failed_permanent') NOT NULL DEFAULT 'pending'
COMMENT 'Status upload: pending|uploaded|failed (retry<3)|failed_permanent (retry>=3)',
`QR_PrintOutRetryCount` int(11) NOT NULL DEFAULT 0
COMMENT 'Berapa kali sudah dicoba upload, maksimal 3',
`QR_PrintOutLastRetryAt` datetime DEFAULT NULL
COMMENT 'Waktu percobaan upload terakhir yang gagal',
`QR_PrintOutUploadedAt` datetime DEFAULT NULL
COMMENT 'Waktu upload PDF ke dedicated server berhasil',
`QR_PrintOutCreatedAt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP
COMMENT 'Waktu token QR dibuat',
`QR_PrintOutCreatedByUserID` int(11) NOT NULL DEFAULT 0
COMMENT 'UserID yang mencetak laporan',
`QR_PrintOutIsActive` tinyint(1) NOT NULL DEFAULT 1
COMMENT '1=aktif, 0=dinonaktifkan (mis. hasil direvisi)',
PRIMARY KEY (`QR_PrintOutID`),
UNIQUE KEY `uq_qr_uuid` (`QR_PrintOutUUID`),
KEY `idx_order_header` (`QR_PrintOutT_OrderHeaderID`),
KEY `idx_upload_status` (`QR_PrintOutUploadStatus`),
KEY `idx_retry` (`QR_PrintOutUploadStatus`, `QR_PrintOutRetryCount`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
COMMENT='Token QR Code untuk verifikasi keaslian laporan hasil laboratorium';
*/
/**
* Generateqrreport Library
*
* Library untuk generate QR Code pada laporan hasil laboratorium.
* QR Code berisi URL langsung ke file PDF di dedicated server.
* Saat QR di-scan, browser langsung membuka file PDF tersebut.
*
* Arsitektur:
*
* ┌─ PHP App (ini) ──────────────────────────────────────────────┐
* │ 1. saveQRPrintout(verifyBaseURL='https://ds.com/files') │
* │ → UUID + verifyURL = https://ds.com/files/{uuid}.pdf │
* │ verifyURL = URL final PDF = yang di-encode ke QR Code │
* │ 2. generateQRImageBase64(verifyURL) → embed QR ke PDF cetak │
* │ 3. saveTempPDF() → simpan PDF sementara di /tmp/ │
* └──────────────────────────────────────────────────────────────┘
* │
* ▼
* ┌─ Golang Upload Tool ─────────────────────────────────────────┐
* │ - Poll PHP API: getPendingUploads() │
* │ - Ambil TempFilePath dari row │
* │ - Upload PDF ke dedicated server pada path sesuai verifyURL │
* │ - Berhasil → callback confirmUpload($uuid) │
* │ - Gagal → callback incrementRetry($uuid) │
* └──────────────────────────────────────────────────────────────┘
*
* ┌─ Dedicated Server ───────────────────────────────────────────┐
* │ - Serve file PDF statis │
* │ - URL: https://ds.com/files/{uuid}.pdf │
* │ - Saat QR di-scan → browser langsung buka PDF ini │
* └──────────────────────────────────────────────────────────────┘
*
* Tabel yang digunakan: qr_printout
* (lihat: sql/qr_printout_updated.sql untuk DDL lengkap)
*/
class Generateqrreport
{
/** @var CI_DB_driver */
protected $db_smartone;
/** @var CI_DB_driver */
protected $db_onedev;
function __construct()
{
$CI = &get_instance();
$this->db_smartone = $CI->load->database("default", true);
$this->db_onedev = $CI->load->database("default", true);
$this->_loadQRLib();
}
// =========================================================================
// PRIVATE HELPERS
// =========================================================================
/**
* Load phpqrcode library jika belum di-include.
*/
private function _loadQRLib()
{
if (!class_exists('QRcode', false)) {
$libPath = APPPATH . 'libraries/qrcode/';
if (!defined('QR_CACHEABLE'))
define('QR_CACHEABLE', false);
if (!defined('QR_CACHE_DIR'))
define('QR_CACHE_DIR', APPPATH . 'cache/');
if (!defined('QR_LOG_DIR'))
define('QR_LOG_DIR', APPPATH . 'logs/');
if (!defined('QR_FIND_BEST_MASK'))
define('QR_FIND_BEST_MASK', true);
if (!defined('QR_FIND_FROM_RANDOM'))
define('QR_FIND_FROM_RANDOM', false);
if (!defined('QR_PNG_MAXIMUM_SIZE'))
define('QR_PNG_MAXIMUM_SIZE', 1024);
include_once $libPath . 'phpqrcode.php';
}
}
/**
* Generate UUID v4.
*
* @param bool $withHyphens true → 36 char 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'
* false → 32 char hex tanpa tanda hubung
* @return string
*/
private function _generateUUID($withHyphens = true)
{
$data = random_bytes(16);
$data[6] = chr(ord($data[6]) & 0x0f | 0x40); // version 4
$data[8] = chr(ord($data[8]) & 0x3f | 0x80); // variant bits
$uuid = vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
return $withHyphens ? $uuid : str_replace('-', '', $uuid);
}
/**
* Normalisasi UUID: konversi 32-char (tanpa hyphen) ke format 36-char standar.
*
* @param string $uuid
* @return string lowercase 36-char UUID
*/
private function _normalizeUUID($uuid)
{
$uuid = trim($uuid);
if (strlen($uuid) === 32 && strpos($uuid, '-') === false) {
$uuid = substr($uuid, 0, 8) . '-'
. substr($uuid, 8, 4) . '-'
. substr($uuid, 12, 4) . '-'
. substr($uuid, 16, 4) . '-'
. substr($uuid, 20);
}
return strtolower($uuid);
}
/**
* Validasi format UUID v4 (8-4-4-4-12).
*
* @param string $uuid
* @return bool
*/
private function _validateUUIDFormat($uuid)
{
return (bool)preg_match(
'/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/',
strtolower($uuid)
);
}
// =========================================================================
// PUBLIC METHODS — QR TOKEN
// =========================================================================
/**
* Buat record QR baru di tabel qr_printout.
*
* verifyURL = URL LANGSUNG ke file PDF di dedicated server.
* URL ini yang di-encode ke gambar QR Code.
* Golang tool akan upload PDF ke path/URL ini.
*
* Contoh:
* verifyBaseURL = 'https://files.lab.rs.com/reports'
* verifyURL = 'https://files.lab.rs.com/reports/{uuid}.pdf'
*
* @param array $params Wajib:
* - orderHeaderID (int) FK ke t_orderheader
* - groupResultName (string) Label group, mis: 'HEMATOLOGI'
* - verifyBaseURL (string) Base URL folder PDF di dedicated server,
* contoh: 'https://files.lab.rs.com/reports'
* Hasilnya: {verifyBaseURL}/{uuid}.pdf
* Opsional:
* - groupResultID (int) default 0
* - testID (int) default 0
* - createdByUserID (int) default 0
* @return array [
* 'success' => bool,
* 'uuid' => string|null, // 36-char dengan hyphen
* 'uuid_short' => string|null, // 32-char tanpa hyphen
* 'verifyURL' => string|null, // URL PDF final = yang di-encode ke QR Code
* 'qr_printout_id' => int|null,
* 'message' => string
* ]
*/
public function saveQRPrintout(array $params)
{
$required = ['orderHeaderID', 'groupResultName', 'verifyBaseURL', 'QR_PrintOutReportURL'];
foreach ($required as $field) {
if (empty($params[$field])) {
return [
'success' => false,
'uuid' => null,
'uuid_short' => null,
'verifyURL' => null,
'qr_printout_id' => null,
'message' => "Parameter '{$field}' wajib diisi.",
];
}
}
$uuid = $this->_generateUUID(true);
$uuidShort = str_replace('-', '', $uuid);
// verifyURL = URL langsung ke PDF: {base}/{uuid}.pdf
// Golang akan upload PDF ke path ini di dedicated server
$verifyURL = rtrim($params['verifyBaseURL'], '/') . '/' . $uuid;
$groupResultID = (int)($params['groupResultID'] ?? 0);
$testID = (int)($params['testID'] ?? 0);
$groupResultName = $params['groupResultName'];
$this->db_smartone
->where('QR_PrintOutT_OrderHeaderID', (int)$params['orderHeaderID'])
->where('QR_PrintOutGroup_ResultID', $groupResultID)
->where('QR_PrintOutT_TestID', $testID)
->where('QR_PrintOutIsActive', 1)
->update('qr_printout', ['QR_PrintOutIsActive' => 0]);
$data = [
'QR_PrintOutT_OrderHeaderID' => (int)$params['orderHeaderID'],
'QR_PrintOutGroup_ResultID' => $groupResultID,
'QR_PrintOutT_TestID' => $testID,
'QR_PrintOutGroup_ResultName' => $groupResultName,
'QR_PrintOutUUID' => $uuid,
'QR_PrintOutVerifyURL' => $verifyURL, // URL final PDF = yang di-encode ke QR Code
'QR_PrintOutReportURL' => $params['QR_PrintOutReportURL'], // URL sumber PDF di PHP server (Golang fetch dari sini)
'QR_PrintOutReportURLElectronic' => (string)($params['QR_PrintOutReportURLElectronic'] ?? ''),
'QR_PrintOutTempFilePath' => '',
'QR_PrintOutUploadStatus' => 'pending',
'QR_PrintOutRetryCount' => 0,
'QR_PrintOutLastRetryAt' => null,
'QR_PrintOutUploadedAt' => null,
'QR_PrintOutCreatedAt' => date('Y-m-d H:i:s'),
'QR_PrintOutCreatedByUserID' => (int)($params['createdByUserID'] ?? 0),
'QR_PrintOutIsActive' => 1,
];
$this->db_smartone->insert('qr_printout', $data);
if ($this->db_smartone->affected_rows() === 0) {
return [
'success' => false,
'uuid' => null,
'uuid_short' => null,
'verifyURL' => null,
'qr_printout_id' => null,
'message' => 'Gagal menyimpan data QR Printout ke database.',
];
}
return [
'success' => true,
'uuid' => $uuid,
'uuid_short' => $uuidShort,
'verifyURL' => $verifyURL, // ini yang di-encode ke gambar QR Code
'qr_printout_id' => $this->db_smartone->insert_id(),
'message' => 'OK',
];
}
/**
* Nonaktifkan QR Code (misal karena hasil direvisi dan QR lama tidak boleh diakses).
*
* @param string $uuid
* @return bool
*/
public function deactivateQR($uuid)
{
$uuid = $this->_normalizeUUID($uuid);
$this->db_smartone->where('QR_PrintOutUUID', $uuid)
->update('qr_printout', ['QR_PrintOutIsActive' => 0]);
return $this->db_smartone->affected_rows() > 0;
}
// =========================================================================
// PUBLIC METHODS — QR IMAGE
// =========================================================================
/**
* Generate QR Code sebagai Base64 PNG string.
* Hasil bisa langsung di-embed ke HTML/PDF: <img src="{hasil}">
*
* @param string $url URL yang akan di-encode (verifyURL dari saveQRPrintout)
* @param int $pixelSize Ukuran pixel per modul (110), default 5
* @param string $errorLevel Koreksi error: L, M, Q, H (default H)
* @return string "data:image/png;base64,..." atau '' jika gagal
*/
public function generateQRImageBase64($url, $pixelSize = 5, $errorLevel = 'H')
{
$errorLevel = in_array($errorLevel, ['L', 'M', 'Q', 'H']) ? $errorLevel : 'H';
$pixelSize = min(max((int)$pixelSize, 1), 10);
ob_start();
QRcode::png($url, false, $errorLevel, $pixelSize, 2);
$imgData = ob_get_clean();
return empty($imgData) ? '' : 'data:image/png;base64,' . base64_encode($imgData);
}
/**
* Generate QR Code dan simpan sebagai file PNG di disk.
*
* @param string $url URL yang akan di-encode
* @param string $savePath Path lengkap tujuan, misal: FCPATH.'qrcodes/abc.png'
* @param int $pixelSize Ukuran pixel per modul (110), default 5
* @param string $errorLevel Koreksi error: L, M, Q, H (default H)
* @return bool true jika berhasil disimpan
*/
public function generateQRImageFile($url, $savePath, $pixelSize = 5, $errorLevel = 'H')
{
$errorLevel = in_array($errorLevel, ['L', 'M', 'Q', 'H']) ? $errorLevel : 'H';
$pixelSize = min(max((int)$pixelSize, 1), 10);
$dir = dirname($savePath);
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
QRcode::png($url, $savePath, $errorLevel, $pixelSize, 2);
return file_exists($savePath);
}
// =========================================================================
// PUBLIC METHODS — PDF TEMP & GOLANG CALLBACK
// =========================================================================
// =========================================================================
// PUBLIC METHODS — GOLANG UPLOAD TOOL SUPPORT
// =========================================================================
/**
* Ambil daftar record yang BELUM diupload ke dedicated server.
* Dipakai oleh Golang upload tool sebagai polling.
*
* Hanya mengambil record dengan:
* - UploadStatus = 'pending' ATAU 'failed' (masih bisa retry)
* - RetryCount < 3
* - IsActive = 1
* - QR_PrintOutReportURL tidak kosong (PDF sudah tersedia di PHP server)
*
* Field penting untuk Golang dari setiap row:
* - QR_PrintOutUUID : identifier
* - QR_PrintOutVerifyURL : URL TUJUAN upload di dedicated server
* - QR_PrintOutReportURL : URL SUMBER PDF di PHP server (Golang fetch dari sini)
* - QR_PrintOutTempFilePath: path lokal file PDF (opsional, jika Golang akses lokal)
*
* @param int $limit Jumlah maksimal record (default 10)
* @return array
*/
public function getPendingUploads($limit = 10)
{
return $this->db_smartone
->where_in('QR_PrintOutUploadStatus', ['pending', 'failed'])
->where('QR_PrintOutRetryCount <', 3)
->where('QR_PrintOutIsActive', 1)
->where('QR_PrintOutReportURL !=', '') // pastikan PDF sudah siap
->order_by('QR_PrintOutCreatedAt', 'ASC')
->limit((int)$limit)
->get('qr_printout')
->result_array();
}
/**
* Catat bahwa satu percobaan upload gagal dan tambah retry count.
*
* - Jika RetryCount setelah increment < 3 → status tetap 'failed' (akan dicoba lagi)
* - Jika RetryCount setelah increment >= 3 → status menjadi 'failed_permanent'
* (tidak akan diambil oleh getPendingUploads() lagi)
*
* Dipanggil oleh Golang tool saat upload ke dedicated server gagal.
*
* @param string $uuid
* @return array [
* 'success' => bool,
* 'retry_count' => int, // retry count setelah increment
* 'is_permanent' => bool, // true = sudah melebihi batas, tidak akan di-retry lagi
* 'message' => string
* ]
*/
public function incrementRetry($uuid)
{
$uuid = $this->_normalizeUUID($uuid);
if (!$this->_validateUUIDFormat($uuid)) {
return [
'success' => false,
'retry_count' => 0,
'is_permanent' => false,
'message' => 'Format UUID tidak valid.'
];
}
$qrRow = $this->db_smartone
->select('QR_PrintOutRetryCount, QR_PrintOutUploadStatus')
->where('QR_PrintOutUUID', $uuid)
->get('qr_printout')
->row_array();
if (!$qrRow) {
return [
'success' => false,
'retry_count' => 0,
'is_permanent' => false,
'message' => 'UUID tidak ditemukan.'
];
}
$newRetryCount = (int)$qrRow['QR_PrintOutRetryCount'] + 1;
$isPermanent = $newRetryCount >= 3;
$newStatus = $isPermanent ? 'failed_permanent' : 'failed';
$this->db_smartone->where('QR_PrintOutUUID', $uuid)
->update('qr_printout', [
'QR_PrintOutRetryCount' => $newRetryCount,
'QR_PrintOutLastRetryAt' => date('Y-m-d H:i:s'),
'QR_PrintOutUploadStatus' => $newStatus,
]);
return [
'success' => true,
'retry_count' => $newRetryCount,
'is_permanent' => $isPermanent,
'message' => $isPermanent
? "Retry count mencapai {$newRetryCount}. Status menjadi 'failed_permanent'. Upload otomatis dihentikan."
: "Retry count: {$newRetryCount}/3. Akan dicoba kembali.",
];
}
/**
* Minta upload ulang (re-upload) untuk satu record.
*
* Gunakan fungsi ini ketika:
* (a) File PDF di dedicated server sudah dihapus / kadaluarsa, ATAU
* (b) Record sebelumnya 'failed_permanent' dan ingin dicoba ulang secara manual
*
* Fungsi ini akan:
* - Reset UploadStatus → 'pending'
* - Reset RetryCount → 0
* - Kosongkan ReportURL (file lama di dedicated server sudah tidak valid)
* - Wajib diikuti dengan saveTempPDF() untuk menyiapkan file PDF baru
*
* @param string $uuid
* @return array ['success' => bool, 'message' => string]
*/
public function requestReUpload($uuid)
{
$uuid = $this->_normalizeUUID($uuid);
if (!$this->_validateUUIDFormat($uuid)) {
return ['success' => false, 'message' => 'Format UUID tidak valid.'];
}
$exists = $this->db_smartone
->where('QR_PrintOutUUID', $uuid)
->count_all_results('qr_printout');
if ($exists === 0) {
return ['success' => false, 'message' => 'UUID tidak ditemukan.'];
}
$this->db_smartone->where('QR_PrintOutUUID', $uuid)
->update('qr_printout', [
'QR_PrintOutUploadStatus' => 'pending',
'QR_PrintOutRetryCount' => 0,
'QR_PrintOutLastRetryAt' => null,
'QR_PrintOutUploadedAt' => null,
'QR_PrintOutTempFilePath' => '', // harus diisi ulang via saveTempPDF()
]);
if ($this->db_smartone->affected_rows() === 0) {
return ['success' => false, 'message' => 'Gagal update database.'];
}
return [
'success' => true,
'message' => 'Record di-reset ke pending. Panggil saveTempPDF() untuk menyiapkan PDF baru.',
];
}
/**
* Konfirmasi bahwa Golang berhasil upload PDF ke dedicated server.
*
* URL QR_PrintOutVerifyURL tidak berubah (sudah fix sejak saveQRPrintout).
* Fungsi ini hanya mengubah status → 'uploaded'.
*
* Alur Golang:
* 1. Poll getPendingUploads() → dapat row dengan:
* QR_PrintOutReportURL (fetch PDF dari PHP server via HTTP)
* QR_PrintOutVerifyURL (upload PDF ke dedicated server di path ini)
* 2. Download PDF dari QR_PrintOutReportURL
* 3. Upload ke dedicated server sesuai QR_PrintOutVerifyURL
* 4. Callback PHP: confirmUpload($uuid)
*
* @param string $uuid UUID record qr_printout
* @return array ['success' => bool, 'message' => string]
*/
public function confirmUpload($uuid)
{
$uuid = $this->_normalizeUUID($uuid);
if (!$this->_validateUUIDFormat($uuid)) {
return ['success' => false, 'message' => 'Format UUID tidak valid.'];
}
$exists = $this->db_smartone
->where('QR_PrintOutUUID', $uuid)
->count_all_results('qr_printout');
if ($exists === 0) {
return ['success' => false, 'message' => 'UUID tidak ditemukan di database.'];
}
$this->db_smartone->where('QR_PrintOutUUID', $uuid)
->update('qr_printout', [
'QR_PrintOutUploadStatus' => 'uploaded',
'QR_PrintOutUploadedAt' => date('Y-m-d H:i:s'),
]);
if ($this->db_smartone->affected_rows() === 0) {
return ['success' => false, 'message' => 'Gagal update database.'];
}
return ['success' => true, 'message' => 'OK'];
}
/**
* Tandai upload gagal permanen tanpa menambah retry count.
* Gunakan ini hanya untuk override manual (mis. admin force-fail).
* Untuk kasus normal, gunakan incrementRetry() agar retry count terlacak.
*
* @param string $uuid
* @return bool
*/
public function markUploadFailed($uuid)
{
$uuid = $this->_normalizeUUID($uuid);
$this->db_smartone->where('QR_PrintOutUUID', $uuid)
->update('qr_printout', [
'QR_PrintOutUploadStatus' => 'failed_permanent',
'QR_PrintOutRetryCount' => 3,
]);
return $this->db_smartone->affected_rows() > 0;
}
/**
* Reset URL report (untuk upload ulang jika laporan direvisi).
* @deprecated Gunakan requestReUpload() yang lebih lengkap.
*
* @param string $uuid
* @return array ['success' => bool, 'message' => string]
*/
public function resetReportURL($uuid)
{
return $this->requestReUpload($uuid);
}
// =========================================================================
// PUBLIC METHODS — VERIFIKASI (dipakai dedicated server / controller publik)
// =========================================================================
/**
* Ambil URL PDF report berdasarkan UUID.
*
* Dipakai jika PHP app perlu tahu URL PDF (mis. untuk redirect).
* Scan tracking dihandle oleh dedicated server.
*
* @param string $uuid UUID (36 atau 32 char)
* @return array [
* 'success' => bool,
* 'report_url' => string|null,
* 'qr_info' => array|null,
* 'message' => string
* ]
*/
public function getReportURL($uuid)
{
$uuid = $this->_normalizeUUID($uuid);
if (!$this->_validateUUIDFormat($uuid)) {
return [
'success' => false,
'report_url' => null,
'qr_info' => null,
'message' => 'Format UUID tidak valid.'
];
}
$qrRow = $this->db_smartone
->where('QR_PrintOutUUID', $uuid)
->where('QR_PrintOutIsActive', 1)
->get('qr_printout')
->row_array();
if (!$qrRow) {
return [
'success' => false,
'report_url' => null,
'qr_info' => null,
'message' => 'QR Code tidak ditemukan atau sudah tidak aktif.'
];
}
// verifyURL = URL langsung ke PDF, hanya valid jika sudah ter-upload
if ($qrRow['QR_PrintOutUploadStatus'] !== 'uploaded') {
$status = $qrRow['QR_PrintOutUploadStatus'];
$msg = $status === 'failed_permanent'
? 'Upload PDF gagal permanen. Hubungi admin laboratorium.'
: 'Report PDF sedang diproses. Coba beberapa saat lagi.';
return ['success' => false, 'report_url' => null, 'qr_info' => $qrRow, 'message' => $msg];
}
return [
'success' => true,
'report_url' => $qrRow['QR_PrintOutVerifyURL'], // URL langsung ke PDF
'qr_info' => $qrRow,
'message' => 'OK',
];
}
// =========================================================================
// LEGACY / UTILITY
// =========================================================================
function clean_mysqli_connection($dbc)
{
while (mysqli_more_results($dbc)) {
if (mysqli_next_result($dbc)) {
$result = mysqli_use_result($dbc);
if (get_class($result) == 'mysqli_stmt') {
mysqli_stmt_free_result($result);
} else {
unset($result);
}
}
}
}
}