Files
webstatement/app/Http/Controllers/AtmTransactionReportController.php
Daeng Deni Mardaeni 55313fb0b0 feat(webstatement): tambahkan fitur retry dengan handling timeout pada laporan transaksi ATM
- Memperbarui logika retry pada `AtmTransactionReportController`:
  - Memperbolehkan retry jika status laporan adalah `failed`, `pending`, atau laporan dengan status `processing` yang telah melebihi batas waktu (1 jam).
  - Menambahkan atribut baru seperti `processing_hours` dan `is_processing_timeout` pada data untuk menampilkan informasi durasi proses dan flag timeout.
  - Mengubah status laporan menjadi `failed` jika melebihi batas waktu sebelum dilakukan retry.
  - Memperbarui error message untuk mencatat alasan timeout.

- Menambahkan metode baru `canRetry` pada controller:
  - Mengembalikan boolean jika laporan dapat di-retry berdasarkan status dan kondisi laporan.

- Memperbarui tampilan untuk bagian daftar laporan (`atm-reports/index.blade.php`):
  - Menambahkan tombol retry dengan warna yang disesuaikan (kuning/oranye untuk `failed`/`pending`, merah untuk timeout).
  - Memperbarui tampilan status laporan menjadi lebih informatif, termasuk keterangan durasi proses jika timeout.

- Memperbarui tampilan detail laporan (`atm-reports/show.blade.php`):
  - Menambahkan tombol retry dengan label tambahan "Retry (Timeout)" jika melebihi batas waktu proses.
  - Menampilkan informasi tambahan seperti waktu proses jika laporan dalam status `processing`.

- Menyediakan fungsi JavaScript baru `retryReport` untuk handling retry via AJAX:
  - Menyertakan konfirmasi sebelum retry.
  - Memperbarui tombol retry agar lebih reaktif terhadap perubahan status laporan.

Signed-off-by: Daeng Deni Mardaeni <ddeni05@gmail.com>
2025-06-10 11:17:17 +07:00

367 lines
13 KiB
PHP

<?php
namespace Modules\Webstatement\Http\Controllers;
use App\Http\Controllers\Controller;
use Carbon\Carbon;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
use Illuminate\Validation\Rule;
use Log;
use Modules\Webstatement\Jobs\GenerateAtmTransactionReportJob;
use Modules\Webstatement\Models\AtmTransactionReportLog;
class AtmTransactionReportController extends Controller
{
/**
* Display a listing of the ATM transaction reports.
*/
public function index(Request $request)
{
return view('webstatement::atm-reports.index');
}
/**
* Store a newly created ATM transaction report request.
*/
public function store(Request $request)
{
$validated = $request->validate([
'report_date' => ['required', 'date_format:Y-m-d'],
]);
// Convert date to Ymd format for period
$period = Carbon::createFromFormat('Y-m-d', $validated['report_date'])->format('Ymd');
// Add user tracking data
$reportData = [
'period' => $period,
'report_date' => $validated['report_date'],
'user_id' => Auth::id(),
'created_by' => Auth::id(),
'ip_address' => $request->ip(),
'user_agent' => $request->userAgent(),
'status' => 'pending',
];
// Create the report request log
$reportRequest = AtmTransactionReportLog::create($reportData);
// Dispatch the job to generate the report
try {
GenerateAtmTransactionReportJob::dispatch($period, $reportRequest->id);
$reportRequest->update([
'status' => 'processing',
'updated_by' => Auth::id()
]);
} catch (Exception $e) {
$reportRequest->update([
'status' => 'failed',
'error_message' => $e->getMessage(),
'updated_by' => Auth::id()
]);
}
return redirect()->route('atm-reports.index')
->with('success', 'ATM Transaction report request has been created successfully.');
}
/**
* Show the form for creating a new report request.
*/
public function create()
{
return view('webstatement::atm-reports.create', compact('branches'));
}
/**
* Display the specified report request.
*/
public function show(AtmTransactionReportLog $atmReport)
{
$atmReport->load(['user', 'creator', 'authorizer']);
return view('webstatement::atm-reports.show', compact('atmReport'));
}
/**
* Download the report if available.
*/
public function download(AtmTransactionReportLog $atmReport)
{
// Check if report is available
if ($atmReport->status !== 'completed' || !$atmReport->file_path) {
return back()->with('error', 'Report is not available for download.');
}
// Update download status
$atmReport->update([
'is_downloaded' => true,
'downloaded_at' => now(),
'updated_by' => Auth::id()
]);
// Download the file
$filePath = $atmReport->file_path;
if (Storage::exists($filePath)) {
$fileName = "atm_transaction_report_{$atmReport->period}.csv";
return Storage::download($filePath, $fileName);
}
return back()->with('error', 'Report file not found.');
}
/**
* Authorize a report request.
*/
public function authorize(Request $request, AtmTransactionReportLog $atmReport)
{
$request->validate([
'authorization_status' => ['required', Rule::in(['approved', 'rejected'])],
'remarks' => ['nullable', 'string', 'max:255'],
]);
// Update authorization status
$atmReport->update([
'authorization_status' => $request->authorization_status,
'authorized_by' => Auth::id(),
'authorized_at' => now(),
'remarks' => $request->remarks,
'updated_by' => Auth::id()
]);
$statusText = $request->authorization_status === 'approved' ? 'approved' : 'rejected';
return redirect()->route('atm-reports.show', $atmReport->id)
->with('success', "ATM Transaction report request has been {$statusText} successfully.");
}
/**
* Provide data for datatables.
*/
public function dataForDatatables(Request $request)
{
// Retrieve data from the database
$query = AtmTransactionReportLog::query();
// Apply search filter if provided
if ($request->has('search') && !empty($request->get('search'))) {
$search = $request->get('search');
$query->where(function ($q) use ($search) {
$q->where('period', 'LIKE', "%$search%")
->orWhere('status', 'LIKE', "%$search%")
->orWhere('authorization_status', 'LIKE', "%$search%");
});
}
// Apply column filters if provided
if ($request->has('filters') && !empty($request->get('filters'))) {
$filters = json_decode($request->get('filters'), true);
foreach ($filters as $filter) {
if (!empty($filter['value'])) {
if ($filter['column'] === 'status') {
$query->where('status', $filter['value']);
} else if ($filter['column'] === 'authorization_status') {
$query->where('authorization_status', $filter['value']);
}
}
}
}
// Apply sorting if provided
if ($request->has('sortOrder') && !empty($request->get('sortOrder'))) {
$order = $request->get('sortOrder');
$column = $request->get('sortField');
// Map frontend column names to database column names if needed
$columnMap = [
'period' => 'period',
'status' => 'status',
];
$dbColumn = $columnMap[$column] ?? $column;
$query->orderBy($dbColumn, $order);
} else {
// Default sorting
$query->latest('created_at');
}
// Get the total count of records
$totalRecords = $query->count();
// Apply pagination if provided
if ($request->has('page') && $request->has('size')) {
$page = $request->get('page');
$size = $request->get('size');
$offset = ($page - 1) * $size;
$query->skip($offset)->take($size);
}
// Get the filtered count of records
$filteredRecords = $query->count();
// Eager load relationships (remove branch since it's not used anymore)
$query->with(['user', 'authorizer']);
// Get the data for the current page
$data = $query->get()->map(function ($item) {
$processingHours = $item->status === 'processing' ? $item->updated_at->diffInHours(now()) : 0;
$isProcessingTimeout = $item->status === 'processing' && $processingHours >= 1;
return [
'id' => $item->id,
'period' => $item->period,
'report_date' => Carbon::createFromFormat('Ymd', $item->period)->format('Y-m-d'),
'status' => $item->status,
'status_display' => $item->status . ($isProcessingTimeout ? ' (Timeout)' : ''),
'processing_hours' => $processingHours,
'is_processing_timeout' => $isProcessingTimeout,
'authorization_status' => $item->authorization_status,
'is_downloaded' => $item->is_downloaded,
'created_at' => dateFormat($item->created_at, 1, 1),
'created_by' => $item->user->name ?? 'N/A',
'authorized_by' => $item->authorizer ? $item->authorizer->name : null,
'authorized_at' => $item->authorized_at ? $item->authorized_at->format('Y-m-d H:i:s') : null,
'file_path' => $item->file_path,
'can_retry' => in_array($item->status, ['failed', 'pending']) || $isProcessingTimeout || ($item->status === 'completed' && !$item->file_path),
];
});
// Calculate the page count
$pageCount = ceil($filteredRecords / ($request->get('size') ?: 1));
$currentPage = $request->get('page') ?: 1;
return response()->json([
'draw' => $request->get('draw'),
'recordsTotal' => $totalRecords,
'recordsFiltered' => $filteredRecords,
'pageCount' => $pageCount,
'page' => $currentPage,
'totalCount' => $totalRecords,
'data' => $data,
]);
}
/**
* Delete a report request.
*/
public function destroy(AtmTransactionReportLog $atmReport)
{
// Delete the file if exists
if ($atmReport->file_path && Storage::exists($atmReport->file_path)) {
Storage::delete($atmReport->file_path);
}
// Delete the report request
$atmReport->delete();
return response()->json([
'message' => 'ATM Transaction report deleted successfully.',
]);
}
/**
* Send report to email
*/
public function sendEmail($id)
{
$atmReport = AtmTransactionReportLog::findOrFail($id);
// Check if report has email
if (empty($atmReport->email)) {
return redirect()->back()->with('error', 'No email address provided for this report.');
}
// Check if report is available
if ($atmReport->status !== 'completed' || !$atmReport->file_path) {
return redirect()->back()->with('error', 'Report is not available for sending.');
}
try {
// Send email with report attachment
// Implementation depends on your email system
// Mail::to($atmReport->email)->send(new AtmTransactionReportEmail($atmReport));
$atmReport->update([
'email_sent' => true,
'email_sent_at' => now(),
'updated_by' => Auth::id()
]);
return redirect()->back()->with('success', 'ATM Transaction report sent to email successfully.');
} catch (Exception $e) {
Log::error('Failed to send ATM Transaction report email: ' . $e->getMessage());
return redirect()->back()->with('error', 'Failed to send email: ' . $e->getMessage());
}
}
/**
* Retry generating the ATM transaction report
*/
public function retry(AtmTransactionReportLog $atmReport)
{
// Check if retry is allowed (failed, pending, or processing for more than 1 hour)
$allowedStatuses = ['failed', 'pending'];
$isProcessingTooLong = $atmReport->status === 'processing' &&
$atmReport->updated_at->diffInHours(now()) >= 1;
if (!in_array($atmReport->status, $allowedStatuses) && !$isProcessingTooLong) {
return back()->with('error', 'Report can only be retried if status is failed, pending, or processing for more than 1 hour.');
}
try {
// If it was processing for too long, mark it as failed first
if ($isProcessingTooLong) {
$atmReport->update([
'status' => 'failed',
'error_message' => 'Processing timeout - exceeded 1 hour limit',
'updated_by' => Auth::id()
]);
}
// Reset the report status and clear previous data
$atmReport->update([
'status' => 'processing',
'error_message' => null,
'file_path' => null,
'file_size' => null,
'record_count' => null,
'updated_by' => Auth::id()
]);
// Dispatch the job again
GenerateAtmTransactionReportJob::dispatch($atmReport->period, $atmReport->id);
return back()->with('success', 'ATM Transaction report job has been retried successfully.');
} catch (Exception $e) {
$atmReport->update([
'status' => 'failed',
'error_message' => $e->getMessage(),
'updated_by' => Auth::id()
]);
return back()->with('error', 'Failed to retry report generation: ' . $e->getMessage());
}
}
/**
* Check if report can be retried
*/
public function canRetry(AtmTransactionReportLog $atmReport)
{
$allowedStatuses = ['failed', 'pending'];
$isProcessingTooLong = $atmReport->status === 'processing' &&
$atmReport->updated_at->diffInHours(now()) >= 1;
return in_array($atmReport->status, $allowedStatuses) ||
$isProcessingTooLong ||
($atmReport->status === 'completed' && !$atmReport->file_path);
}
}