From 46dc493af11cde0e0cd836852cc76cf199d24820 Mon Sep 17 00:00:00 2001 From: mahdahar <89adham@gmail.com> Date: Thu, 5 Feb 2026 14:12:17 +0700 Subject: [PATCH] feat: Add PDF generation audit tracking and simplify result dialog - Add PDF generation events (GEN_PDF, REGEN_PDF) to AUDIT_REQUESTS table - Track PDF print/generate/regenerate with timestamp and language - Fix language parameter handling in ReportController (engQuery vs engQuery typo) - Simplify result dialog to show report in iframe instead of async PDF loading - Add PDF tab to audit dialog showing generation history --- TODO.md | 8 +- .../ApiRequestsAuditController.php | 18 ++++- app/Controllers/ReportController.php | 35 +++++--- app/Views/shared/dialog_audit.php | 12 ++- app/Views/shared/dialog_results_generate.php | 77 +++--------------- app/Views/shared/script_requests.php | 81 +++++-------------- 6 files changed, 88 insertions(+), 143 deletions(-) diff --git a/TODO.md b/TODO.md index 0249883..9f3d6ac 100644 --- a/TODO.md +++ b/TODO.md @@ -1,11 +1,9 @@ # Project Checklist: Glen RME & Lab Management System -**Last Updated:** 20260204 +**Last Updated:** 20260205 Pending: - Test and fix Reprint label -- Test and fix PDF Generation -- Print Result Audit (Track when result reports are printed/exported, log user and timestamp) Completed: - Update User Role levels (Standardize roles: Superuser, Admin, Lab, Phlebo, CS) @@ -35,4 +33,6 @@ Completed: - Auto generate PDF on second val - Validate delay when loading result - Reprint Label (Add functionality to reprint labels) -- Create Eng Result UI UX on request dashboard \ No newline at end of file +- Create Eng Result UI UX on request dashboard +- Test and fix PDF Generation +- Print Result Audit (Track when result reports are printed/exported, log user and timestamp) \ No newline at end of file diff --git a/app/Controllers/ApiRequestsAuditController.php b/app/Controllers/ApiRequestsAuditController.php index a2cb81b..1622a16 100644 --- a/app/Controllers/ApiRequestsAuditController.php +++ b/app/Controllers/ApiRequestsAuditController.php @@ -13,7 +13,8 @@ class ApiRequestsAuditController extends BaseController { 'accessnumber' => $accessnumber, 'validation' => [], 'sample_collection' => [], - 'tube_received' => [] + 'tube_received' => [], + 'pdf_generation' => [] ]; $sqlAudit = "SELECT EVENT_TYPE, USERID, EVENT_AT, REASON @@ -63,6 +64,21 @@ class ApiRequestsAuditController extends BaseController { ]; } + $sqlRequests = "SELECT STEPTYPE, STEPDATE, STEPSTATUS + FROM GDC_CMOD.dbo.AUDIT_REQUESTS + WHERE ACCESSNUMBER = ? AND STEPTYPE IN ('PRINT', 'GEN_PDF', 'REGEN_PDF') + ORDER BY STEPDATE ASC"; + $requestRows = $db->query($sqlRequests, [$accessnumber])->getResultArray(); + + foreach ($requestRows as $row) { + $result['pdf_generation'][] = [ + 'type' => $row['STEPTYPE'], + 'status' => trim($row['STEPSTATUS']), + 'datetime' => $row['STEPDATE'] ? date('Y-m-d H:i:s', strtotime($row['STEPDATE'])) : null, + 'user' => session()->get('userid') + ]; + } + return $this->respond(['status' => 'success', 'data' => $result]); } } diff --git a/app/Controllers/ReportController.php b/app/Controllers/ReportController.php index 90e08bd..882d641 100644 --- a/app/Controllers/ReportController.php +++ b/app/Controllers/ReportController.php @@ -20,14 +20,16 @@ class ReportController extends BaseController if ($ispdf == 0) { $ispdf = $this->request->getVar('ispdf') ?? 0; } - - if ($eng == 0) { - // Read REPORT_LANG from CM_REQUESTS if not provided + + $engQuery = $this->request->getVar('eng'); + if ($engQuery !== null) { + $eng = (int) $engQuery; + } elseif ($eng == 0) { $sql = "SELECT REPORT_LANG FROM GDC_CMOD.dbo.CM_REQUESTS WHERE ACCESSNUMBER=?"; $row = $this->db->query($sql, [$accessnumber])->getRowArray(); $eng = $row['REPORT_LANG'] ?? 0; } - + return $this->renderReport($accessnumber, $eng, $ispdf, false); } @@ -36,14 +38,16 @@ class ReportController extends BaseController if ($ispdf == 0) { $ispdf = $this->request->getVar('ispdf') ?? 0; } - - if ($eng == 0) { - // Read REPORT_LANG from CM_REQUESTS if not provided + + $engQuery = $this->request->getVar('eng'); + if ($engQuery !== null) { + $eng = (int) $engQuery; + } elseif ($eng == 0) { $sql = "SELECT REPORT_LANG FROM GDC_CMOD.dbo.CM_REQUESTS WHERE ACCESSNUMBER=?"; $row = $this->db->query($sql, [$accessnumber])->getRowArray(); $eng = $row['REPORT_LANG'] ?? 0; } - + return $this->renderReport($accessnumber, $eng, $ispdf, true); } @@ -116,12 +120,25 @@ class ReportController extends BaseController try { $jobId = $this->postToSpooler($html, $filename, $collectionDate); + + $sqlCheck = "SELECT COUNT(*) as cnt FROM GDC_CMOD.dbo.AUDIT_REQUESTS + WHERE ACCESSNUMBER = ? AND STEPTYPE IN ('GEN_PDF', 'REGEN_PDF')"; + $result = $this->db->query($sqlCheck, [$accessnumber])->getRowArray(); + + $stepType = ($result['cnt'] > 0) ? 'REGEN_PDF' : 'GEN_PDF'; + $stepStatus = $eng == 1 ? 'English' : 'Indonesian'; + + $sqlLog = "INSERT INTO GDC_CMOD.dbo.AUDIT_REQUESTS(ACCESSNUMBER, STEPDATE, STEPTYPE, STEPSTATUS) + VALUES (?, GETDATE(), ?, ?)"; + $this->db->query($sqlLog, [$accessnumber, $stepType, $stepStatus]); + return $this->response->setJSON([ 'success' => true, 'jobId' => $jobId, 'message' => 'PDF queued for generation', 'status' => 'queued', - 'lang' => $eng == 1 ? 'English' : 'Indonesian' + 'lang' => $eng == 1 ? 'English' : 'Indonesian', + 'isRegen' => ($stepType === 'REGEN_PDF') ]); } catch (\Exception $e) { log_message('error', "PDF generation failed: " . $e->getMessage()); diff --git a/app/Views/shared/dialog_audit.php b/app/Views/shared/dialog_audit.php index e899c0e..b9a0a0f 100644 --- a/app/Views/shared/dialog_audit.php +++ b/app/Views/shared/dialog_audit.php @@ -26,6 +26,9 @@ +
@@ -52,14 +55,16 @@ 'bg-success': event.category === 'validation' && event.type !== 'UNVAL', 'bg-info': event.category === 'sample', 'bg-warning': event.category === 'receive', - 'bg-error': event.category === 'validation' && event.type === 'UNVAL' + 'bg-error': event.category === 'validation' && event.type === 'UNVAL', + 'bg-secondary': event.category === 'pdf' }">
@@ -70,7 +75,8 @@ 'badge-success': event.category === 'validation' && event.type !== 'UNVAL', 'badge-info': event.category === 'sample', 'badge-warning': event.category === 'receive', - 'badge-error': event.category === 'validation' && event.type === 'UNVAL' + 'badge-error': event.category === 'validation' && event.type === 'UNVAL', + 'badge-secondary': event.category === 'pdf' }" x-text="event.type">

diff --git a/app/Views/shared/dialog_results_generate.php b/app/Views/shared/dialog_results_generate.php index 07f53f9..1227628 100644 --- a/app/Views/shared/dialog_results_generate.php +++ b/app/Views/shared/dialog_results_generate.php @@ -30,79 +30,24 @@
- +
- - - - - - - - - - - +
-
-
- -
-
- - -
+
+
- + \ No newline at end of file diff --git a/app/Views/shared/script_requests.php b/app/Views/shared/script_requests.php index 790c627..b4a386a 100644 --- a/app/Views/shared/script_requests.php +++ b/app/Views/shared/script_requests.php @@ -12,10 +12,6 @@ document.addEventListener('alpine:init', () => { isGenerateDialogOpen: false, generateAccessnumber: null, generateLang: 0, - pdfStatus: 'idle', - pdfJobId: null, - pdfUrl: '', - pdfPollInterval: null, statusColor: { Pend: 'bg-white text-black font-bold', PartColl: 'bg-[#ff99aa] text-black font-bold', @@ -315,6 +311,26 @@ document.addEventListener('alpine:init', () => { }); }); + this.auditData.pdf_generation?.forEach(p => { + let desc = ''; + if (p.type === 'PRINT') { + desc = `Printed report (${p.status})`; + } else if (p.type === 'GEN_PDF') { + desc = `PDF Generated (${p.status})`; + } else if (p.type === 'REGEN_PDF') { + desc = `PDF Regenerated (${p.status})`; + } + events.push({ + id: id++, + category: 'pdf', + type: p.type, + description: desc, + datetime: p.datetime, + user: p.user, + reason: null + }); + }); + return events.sort((a, b) => { if (!a.datetime) return 1; if (!b.datetime) return -1; @@ -333,34 +349,15 @@ document.addEventListener('alpine:init', () => { openGenerateDialog(accessnumber) { this.generateAccessnumber = accessnumber; this.generateLang = 0; - this.pdfStatus = 'idle'; - this.pdfJobId = null; - this.pdfUrl = ''; this.isGenerateDialogOpen = true; - - // Try to show auto-generated Indo PDF immediately after VAL2 - // Since it's generated immediately, assume it's ready - // The spooler will serve the latest PDF - this.pdfUrl = `http://glenlis:3000/api/pdf/view/${accessnumber}.pdf`; - this.pdfStatus = 'completed'; }, closeGenerateDialog() { this.isGenerateDialogOpen = false; this.generateAccessnumber = null; - if (this.pdfPollInterval) { - clearInterval(this.pdfPollInterval); - this.pdfPollInterval = null; - } }, async generatePdfFromDialog() { - if (this.pdfStatus === 'queued' || this.pdfStatus === 'processing') return; - - this.pdfStatus = 'queued'; - this.pdfJobId = null; - this.pdfUrl = ''; - const eng = this.generateLang === 1 ? '?eng=1' : ''; try { @@ -368,51 +365,15 @@ document.addEventListener('alpine:init', () => { const data = await res.json(); if (data.success) { - this.pdfJobId = data.jobId; - this.pdfStatus = 'queued'; - this.showToast(`${data.lang} PDF queued for generation`, 'success'); - this.startPdfStatusPolling(); + this.showToast(`${data.lang} PDF queued for download`, 'success'); } else { - this.pdfStatus = 'failed'; this.showToast('PDF generation failed', 'error'); } } catch (e) { - this.pdfStatus = 'failed'; this.showToast('PDF generation failed - try again', 'error'); } }, - startPdfStatusPolling() { - if (this.pdfPollInterval) clearInterval(this.pdfPollInterval); - this.pdfPollInterval = setInterval(() => { - this.checkPdfStatus(); - }, 2000); - }, - - async checkPdfStatus() { - if (!this.pdfJobId || this.pdfStatus === 'completed' || this.pdfStatus === 'failed') return; - - try { - const res = await fetch(`${BASEURL}/report/status/${this.pdfJobId}`); - const data = await res.json(); - - this.pdfStatus = data.status; - - if (this.pdfStatus === 'processing') { - // Continue polling - } else if (this.pdfStatus === 'completed' && data.pdfUrl) { - this.pdfUrl = data.pdfUrl; - clearInterval(this.pdfPollInterval); - this.showToast('PDF generated successfully', 'success'); - } else if (this.pdfStatus === 'failed') { - clearInterval(this.pdfPollInterval); - this.showToast('PDF generation failed', 'error'); - } - } catch (e) { - console.error('PDF status check failed:', e); - } - }, - selectedPrinter: 'lab',