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); } }