feat: add PDF batch generation page for superuser
- Add /superuser/pdf-batch page with textarea input for access numbers
- Create API endpoint /api/superuser/pdf-batch to process batch PDF generation
- Use report/{id}/pdf endpoint for authenticated PDF generation
- Show results table with success/failed status, language, and type (new/regen)
- Remove deprecated /batch/pdf endpoint
- Add PDF Batch menu item to superuser navigation
This commit is contained in:
parent
d8dc4e95f2
commit
0fad3baab7
@ -71,6 +71,11 @@ $routes->group('superuser', ['filter' => 'role:0'], function ($routes) {
|
||||
$routes->get('', 'Pages\SuperuserController::index');
|
||||
$routes->get('users', 'Pages\SuperuserController::users');
|
||||
$routes->get('validate', 'Pages\SuperuserController::validatePage');
|
||||
$routes->get('pdf-batch', 'Pages\SuperuserController::pdfBatch');
|
||||
});
|
||||
|
||||
$routes->group('api', ['filter' => 'role:0'], function ($routes) {
|
||||
$routes->post('superuser/pdf-batch', 'Pages\SuperuserController::processPdfBatch');
|
||||
});
|
||||
|
||||
$routes->group('admin', ['filter' => 'role:1'], function ($routes) {
|
||||
@ -108,9 +113,6 @@ $routes->group('report', ['filter' => 'role:0,1,2,4'], function ($routes) {
|
||||
|
||||
$routes->get('report/status/(:any)', 'ReportController::checkPdfStatus/$1');
|
||||
|
||||
// Batch PDF endpoint - no auth required (internal use only)
|
||||
$routes->get('batch/pdf/(:num)', 'ReportController::generatePdfNoAuth/$1');
|
||||
|
||||
// External PDF generator endpoint - no auth required
|
||||
$routes->post('api/requests/(:any)/pdf', 'RequestsController::setPdfFlag/$1');
|
||||
|
||||
|
||||
@ -30,4 +30,127 @@ class SuperuserController extends BaseController
|
||||
return view('superuser/validate', ['roleConfig' => $config['superuser']]);
|
||||
}
|
||||
|
||||
public function pdfBatch()
|
||||
{
|
||||
$config = require APPPATH . 'Views/shared/config.php';
|
||||
return view('superuser/pdf_batch', ['roleConfig' => $config['superuser']]);
|
||||
}
|
||||
|
||||
public function processPdfBatch()
|
||||
{
|
||||
$input = $this->request->getJSON(true);
|
||||
$accessNumbers = $input['accessNumbers'] ?? [];
|
||||
|
||||
if (empty($accessNumbers)) {
|
||||
return $this->response->setStatusCode(400)->setJSON([
|
||||
'success' => false,
|
||||
'message' => 'No access numbers provided'
|
||||
]);
|
||||
}
|
||||
|
||||
$results = [];
|
||||
$db = \Config\Database::connect();
|
||||
|
||||
foreach ($accessNumbers as $accessnumber) {
|
||||
$accessnumber = trim($accessnumber);
|
||||
if (empty($accessnumber)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
// Check if request exists
|
||||
$sql = "SELECT ACCESSNUMBER, REPORT_LANG FROM GDC_CMOD.dbo.CM_REQUESTS WHERE ACCESSNUMBER = ?";
|
||||
$row = $db->query($sql, [$accessnumber])->getRowArray();
|
||||
|
||||
if (!$row) {
|
||||
$results[] = [
|
||||
'accessnumber' => $accessnumber,
|
||||
'success' => false,
|
||||
'error' => 'Access number not found',
|
||||
'lang' => null,
|
||||
'isRegen' => false
|
||||
];
|
||||
continue;
|
||||
}
|
||||
|
||||
// Call report/{accessnumber}/pdf endpoint internally
|
||||
$response = $this->callReportPdfEndpoint($accessnumber);
|
||||
|
||||
if ($response['success']) {
|
||||
$results[] = [
|
||||
'accessnumber' => $accessnumber,
|
||||
'success' => true,
|
||||
'error' => null,
|
||||
'lang' => $response['lang'] ?? 'Unknown',
|
||||
'isRegen' => $response['isRegen'] ?? false
|
||||
];
|
||||
} else {
|
||||
$results[] = [
|
||||
'accessnumber' => $accessnumber,
|
||||
'success' => false,
|
||||
'error' => $response['error'] ?? 'PDF generation failed',
|
||||
'lang' => null,
|
||||
'isRegen' => false
|
||||
];
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$results[] = [
|
||||
'accessnumber' => $accessnumber,
|
||||
'success' => false,
|
||||
'error' => $e->getMessage(),
|
||||
'lang' => null,
|
||||
'isRegen' => false
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $this->response->setJSON([
|
||||
'success' => true,
|
||||
'results' => $results,
|
||||
'total' => count($results),
|
||||
'successful' => count(array_filter($results, fn($r) => $r['success'])),
|
||||
'failed' => count(array_filter($results, fn($r) => !$r['success']))
|
||||
]);
|
||||
}
|
||||
|
||||
private function callReportPdfEndpoint($accessnumber)
|
||||
{
|
||||
$db = \Config\Database::connect();
|
||||
|
||||
// Get language preference
|
||||
$sql = "SELECT REPORT_LANG FROM GDC_CMOD.dbo.CM_REQUESTS WHERE ACCESSNUMBER = ?";
|
||||
$row = $db->query($sql, [$accessnumber])->getRowArray();
|
||||
$eng = (int) ($row['REPORT_LANG'] ?? 0);
|
||||
|
||||
// Load ReportController and call generatePdf
|
||||
$reportController = new \App\Controllers\ReportController();
|
||||
|
||||
try {
|
||||
// Temporarily override the response to capture it
|
||||
$response = $reportController->generatePdf($accessnumber);
|
||||
|
||||
// Parse the response
|
||||
$body = $response->getBody();
|
||||
$data = json_decode($body, true);
|
||||
|
||||
if ($data && isset($data['success']) && $data['success']) {
|
||||
return [
|
||||
'success' => true,
|
||||
'lang' => $data['lang'] ?? ($eng == 1 ? 'English' : 'Indonesian'),
|
||||
'isRegen' => $data['isRegen'] ?? false
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => $data['error'] ?? 'Unknown error'
|
||||
];
|
||||
} catch (\Throwable $e) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => $e->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -82,6 +82,7 @@ return [
|
||||
['label' => 'Dashboard', 'href' => 'superuser', 'icon' => 'chart-bar'],
|
||||
['label' => 'Validate', 'href' => 'superuser/validate', 'icon' => 'check-circle'],
|
||||
['label' => 'Users', 'href' => 'superuser/users', 'icon' => 'users'],
|
||||
['label' => 'PDF Batch', 'href' => 'superuser/pdf-batch', 'icon' => 'file-pdf'],
|
||||
['label' => 'Reports', 'href' => 'http://glenlis/report2/', 'icon' => 'book'],
|
||||
],
|
||||
],
|
||||
|
||||
170
app/Views/superuser/pdf_batch.php
Normal file
170
app/Views/superuser/pdf_batch.php
Normal file
@ -0,0 +1,170 @@
|
||||
<?php $config = require APPPATH . 'Views/shared/config.php'; ?>
|
||||
<?= $this->extend('shared/layout'); ?>
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
<div x-data="pdfBatch" class="contents">
|
||||
<main class="p-4 flex-1 flex flex-col gap-4 max-w-4xl w-full mx-auto overflow-hidden min-h-0">
|
||||
<div class="card bg-base-100 shadow-xl border border-base-200 h-full">
|
||||
<div class="card-body p-4 overflow-y-auto">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h2 class="text-2xl font-bold flex items-center gap-2 text-base-content">
|
||||
<i class="fa fa-file-pdf text-primary"></i> PDF Batch Generation
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div class="form-control w-full mb-4">
|
||||
<label class="label">
|
||||
<span class="label-text font-medium">Access Numbers</span>
|
||||
<span class="label-text-alt">One per line or comma-separated</span>
|
||||
</label>
|
||||
<textarea
|
||||
x-model="accessNumbersInput"
|
||||
class="textarea textarea-bordered w-full font-mono text-sm h-48"
|
||||
placeholder="24000001 24000002 24000003"
|
||||
:disabled="isProcessing"
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<div class="text-sm text-base-content/60">
|
||||
<span x-show="!isProcessing" x-text="`${getAccessNumberCount()} access numbers`"></span>
|
||||
<span x-show="isProcessing" x-text="`Processing ${currentIndex} of ${totalCount}...`"></span>
|
||||
</div>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
@click="processBatch()"
|
||||
:disabled="isProcessing || getAccessNumberCount() === 0"
|
||||
>
|
||||
<span x-show="isProcessing" class="loading loading-spinner loading-sm"></span>
|
||||
<i x-show="!isProcessing" class="fa fa-play mr-2"></i>
|
||||
<span x-text="isProcessing ? 'Processing...' : 'Generate PDFs'"></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div x-show="results.length > 0" class="mt-4">
|
||||
<div class="flex justify-between items-center mb-2">
|
||||
<h3 class="text-lg font-semibold">Results</h3>
|
||||
<div class="flex gap-4 text-sm">
|
||||
<span class="text-success">
|
||||
<i class="fa fa-check-circle mr-1"></i>
|
||||
<span x-text="successfulCount"></span> Success
|
||||
</span>
|
||||
<span class="text-error">
|
||||
<i class="fa fa-times-circle mr-1"></i>
|
||||
<span x-text="failedCount"></span> Failed
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto border rounded-lg">
|
||||
<table class="table table-zebra w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-16">Status</th>
|
||||
<th>Access Number</th>
|
||||
<th>Language</th>
|
||||
<th>Type</th>
|
||||
<th>Error</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template x-for="(result, index) in results" :key="index">
|
||||
<tr :class="result.success ? 'bg-success/5' : 'bg-error/5'">
|
||||
<td>
|
||||
<i
|
||||
:class="result.success ? 'fa fa-check-circle text-success' : 'fa fa-times-circle text-error'"
|
||||
class="text-lg"
|
||||
></i>
|
||||
</td>
|
||||
<td class="font-mono font-bold" x-text="result.accessnumber"></td>
|
||||
<td x-text="result.lang || '-'" :class="result.success ? 'text-success' : ''"></td>
|
||||
<td>
|
||||
<span
|
||||
x-show="result.success"
|
||||
class="badge badge-sm"
|
||||
:class="result.isRegen ? 'badge-warning' : 'badge-success'"
|
||||
x-text="result.isRegen ? 'Regenerated' : 'New'"
|
||||
></span>
|
||||
<span x-show="!result.success" class="text-error">-</span>
|
||||
</td>
|
||||
<td class="text-error text-sm" x-text="result.error || '-'" :class="result.error ? '' : 'text-base-content/30'"></td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
<?= $this->endSection(); ?>
|
||||
|
||||
<?= $this->section('script') ?>
|
||||
<script type="module">
|
||||
import Alpine from '<?= base_url("js/app.js"); ?>';
|
||||
|
||||
document.addEventListener('alpine:init', () => {
|
||||
Alpine.data("pdfBatch", () => ({
|
||||
accessNumbersInput: '',
|
||||
isProcessing: false,
|
||||
results: [],
|
||||
currentIndex: 0,
|
||||
totalCount: 0,
|
||||
successfulCount: 0,
|
||||
failedCount: 0,
|
||||
|
||||
getAccessNumberCount() {
|
||||
const numbers = this.parseAccessNumbers();
|
||||
return numbers.length;
|
||||
},
|
||||
|
||||
parseAccessNumbers() {
|
||||
return this.accessNumbersInput
|
||||
.split(/[\n,]+/)
|
||||
.map(n => n.trim())
|
||||
.filter(n => n.length > 0);
|
||||
},
|
||||
|
||||
async processBatch() {
|
||||
const accessNumbers = this.parseAccessNumbers();
|
||||
if (accessNumbers.length === 0) {
|
||||
alert('Please enter at least one access number');
|
||||
return;
|
||||
}
|
||||
|
||||
this.isProcessing = true;
|
||||
this.results = [];
|
||||
this.totalCount = accessNumbers.length;
|
||||
this.currentIndex = 0;
|
||||
this.successfulCount = 0;
|
||||
this.failedCount = 0;
|
||||
|
||||
try {
|
||||
const res = await fetch(`${BASEURL}/api/superuser/pdf-batch`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ accessNumbers })
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
if (data.success) {
|
||||
this.results = data.results;
|
||||
this.successfulCount = data.successful;
|
||||
this.failedCount = data.failed;
|
||||
} else {
|
||||
alert(data.message || 'Failed to process batch');
|
||||
}
|
||||
} catch (err) {
|
||||
alert('Error processing batch: ' + err.message);
|
||||
} finally {
|
||||
this.isProcessing = false;
|
||||
}
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
Alpine.start();
|
||||
</script>
|
||||
<?= $this->endSection(); ?>
|
||||
Loading…
x
Reference in New Issue
Block a user