diff --git a/app/Http/Controllers/PrintStatementController.php b/app/Http/Controllers/PrintStatementController.php index 3e82f55..1b90d12 100644 --- a/app/Http/Controllers/PrintStatementController.php +++ b/app/Http/Controllers/PrintStatementController.php @@ -5,9 +5,11 @@ use App\Http\Controllers\Controller; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; + use Illuminate\Support\Facades\Storage; use Modules\Webstatement\Http\Requests\PrintStatementRequest; use Modules\Webstatement\Models\PrintStatementLog; use Modules\Basicdata\Models\Branch; + use phpseclib3\Net\SFTP; class PrintStatementController extends Controller { @@ -49,7 +51,7 @@ // Process statement availability check (this would be implemented based on your system) $this->checkStatementAvailability($statement); - return redirect()->route('webstatement.statements.show', $statement->id) + return redirect()->route('statements.index') ->with('success', 'Statement request has been created successfully.'); } @@ -72,9 +74,9 @@ return back()->with('error', 'Statement is not available for download.'); } - if ($statement->authorization_status !== 'approved') { + /* if ($statement->authorization_status !== 'approved') { return back()->with('error', 'Statement download requires authorization.'); - } + }*/ // Update download status $statement->update([ @@ -84,10 +86,75 @@ ]); // Generate or fetch the statement file (implementation depends on your system) - $filePath = $this->generateStatementFile($statement); + $disk = Storage::disk('sftpStatement'); + $filePath = "{$statement->period_from}/Print/{$statement->branch_code}/{$statement->account_number}.pdf"; - // Return file download response - return response()->download($filePath, $this->generateFileName($statement)); + if ($statement->is_period_range && $statement->period_to) { + $periodFrom = \Carbon\Carbon::createFromFormat('Ym', $statement->period_from); + $periodTo = \Carbon\Carbon::createFromFormat('Ym', $statement->period_to); + + // Loop through each month in the range + $missingPeriods = []; + $availablePeriods = []; + + for ($period = clone $periodFrom; $period->lte($periodTo); $period->addMonth()) { + $periodFormatted = $period->format('Ym'); + $periodPath = $periodFormatted . "/Print/{$statement->branch_code}/{$statement->account_number}.pdf"; + + if ($disk->exists($periodPath)) { + $availablePeriods[] = $periodFormatted; + } else { + $missingPeriods[] = $periodFormatted; + } + } + + // If any period is missing, the statement is not available + if (count($availablePeriods) > 0) { + // Create a temporary zip file + $zipFileName = "{$statement->account_number}_{$statement->period_from}_to_{$statement->period_to}.zip"; + $zipFilePath = storage_path("app/temp/{$zipFileName}"); + + // Ensure the temp directory exists + if (!file_exists(storage_path('app/temp'))) { + mkdir(storage_path('app/temp'), 0755, true); + } + + // Create a new zip archive + $zip = new \ZipArchive(); + if ($zip->open($zipFilePath, \ZipArchive::CREATE | \ZipArchive::OVERWRITE) === TRUE) { + // Add each available statement to the zip + foreach ($availablePeriods as $period) { + $filePath = "{$period}/Print/{$statement->branch_code}/{$statement->account_number}.pdf"; + $localFilePath = storage_path("app/temp/{$statement->account_number}_{$period}.pdf"); + + // Download the file from SFTP to local storage temporarily + file_put_contents($localFilePath, $disk->get($filePath)); + + // Add the file to the zip + $zip->addFile($localFilePath, "{$statement->account_number}_{$period}.pdf"); + } + + $zip->close(); + + // Clean up temporary files + foreach ($availablePeriods as $period) { + $localFilePath = storage_path("app/temp/{$statement->account_number}_{$period}.pdf"); + if (file_exists($localFilePath)) { + unlink($localFilePath); + } + } + + // Return the zip file for download + return response()->download($zipFilePath, $zipFileName)->deleteFileAfterSend(true); + } else { + return back()->with('error', 'Failed to create zip archive.'); + } + } else { + return back()->with('error', 'No statements available for download.'); + } + } else if($disk->exists($filePath)) { + return $disk->download($filePath, "{$statement->account_number}_{$statement->period_from}.pdf"); + } } /** @@ -111,8 +178,9 @@ $statusText = $request->authorization_status === 'approved' ? 'approved' : 'rejected'; - return redirect()->route('webstatement.statements.show', $statement->id) + return redirect()->route('statements.show', $statement->id) ->with('success', "Statement request has been {$statusText} successfully."); + } /** @@ -123,12 +191,60 @@ { // This would be implemented based on your system's logic // For example, checking an API or database for statement availability + $disk = Storage::disk('sftpStatement'); + + //format folder /periode/Print/branch_code/account_number.pdf + $filePath = "{$statement->period_from}/Print/{$statement->branch_code}/{$statement->account_number}.pdf"; + + // Check if the statement exists in the storage + if ($statement->is_period_range && $statement->period_to) { + $periodFrom = \Carbon\Carbon::createFromFormat('Ym', $statement->period_from); + $periodTo = \Carbon\Carbon::createFromFormat('Ym', $statement->period_to); + + // Loop through each month in the range + $missingPeriods = []; + $availablePeriods = []; + + for ($period = clone $periodFrom; $period->lte($periodTo); $period->addMonth()) { + $periodFormatted = $period->format('Ym'); + $periodPath = $periodFormatted . "/Print/{$statement->branch_code}/{$statement->account_number}.pdf"; + + if ($disk->exists($periodPath)) { + $availablePeriods[] = $periodFormatted; + } else { + $missingPeriods[] = $periodFormatted; + } + } + + // If any period is missing, the statement is not available + if (count($missingPeriods) > 0) { + $notes = "Missing periods: " . implode(', ', $missingPeriods); + $statement->update([ + 'is_available' => false, + 'remarks' => $notes, + 'updated_by' => Auth::id() + ]); + return; + } else { + // All periods are available + $statement->update([ + 'is_available' => true, + 'updated_by' => Auth::id() + ]); + } + } else if($disk->exists($filePath)) { + $statement->update([ + 'is_available' => true, + 'updated_by' => Auth::id() + ]); + return; + } - // Placeholder implementation - set to available for demo $statement->update([ - 'is_available' => true, + 'is_available' => false, 'updated_by' => Auth::id() ]); + return; } /** @@ -213,6 +329,7 @@ 'account' => 'account_number', 'period' => 'period_from', 'status' => 'authorization_status', + 'remarks' =>'remarks', // Add more mappings as needed ]; @@ -254,10 +371,11 @@ 'authorization_status' => $item->authorization_status, 'is_available' => $item->is_available, 'is_downloaded' => $item->is_downloaded, - 'created_at' => $item->created_at->format('Y-m-d H:i:s'), + '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, + 'remarks' => $item->remarks, ]; }); @@ -278,4 +396,13 @@ 'data' => $data, ]); } + + public function destroy(PrintStatementLog $statement){ + // Delete the statement + $statement->delete(); + + return response()->json([ + 'message' => 'Statement deleted successfully.', + ]); + } } diff --git a/app/Http/Requests/PrintStatementRequest.php b/app/Http/Requests/PrintStatementRequest.php index 67701b9..0537fc1 100644 --- a/app/Http/Requests/PrintStatementRequest.php +++ b/app/Http/Requests/PrintStatementRequest.php @@ -3,6 +3,8 @@ namespace Modules\Webstatement\Http\Requests; use Illuminate\Foundation\Http\FormRequest; + use Illuminate\Validation\Rule; + use Modules\Webstatement\Models\PrintStatementLog as Statement; class PrintStatementRequest extends FormRequest { @@ -27,11 +29,38 @@ 'branch_code' => ['required', 'string', 'exists:branches,code'], 'account_number' => ['required', 'string'], 'is_period_range' => ['sometimes', 'boolean'], - 'period_from' => ['required', 'string', 'regex:/^\d{6}$/'], // YYYYMM format + 'period_from' => [ + 'required', + 'string', + 'regex:/^\d{6}$/', // YYYYMM format + // Prevent duplicate requests with same account number and period + function ($attribute, $value, $fail) { + $query = Statement::where('account_number', $this->input('account_number')) + ->where('authorization_status', '!=', 'rejected') + ->where('period_from', $value); + + // If this is an update request, exclude the current record + if ($this->route('statement')) { + $query->where('id', '!=', $this->route('statement')); + } + + // If period_to is provided, check for overlapping periods + if ($this->input('period_to')) { + $query->where(function ($q) use ($value) { + $q->where('period_from', '<=', $this->input('period_to')) + ->where('period_to', '>=', $value); + }); + } + + if ($query->exists()) { + $fail('A statement request with this account number and period already exists.'); + } + } + ], ]; // If it's a period range, require period_to - if ($this->input('is_period_range')) { + if ($this->input('period_to')) { $rules['period_to'] = [ 'required', 'string', @@ -71,10 +100,24 @@ protected function prepareForValidation() : void { - // Convert is_period_range to boolean if it exists - if ($this->has('is_period_range')) { + if($this->has('period_from')){ + //conver to YYYYMM format $this->merge([ - 'is_period_range' => filter_var($this->is_period_range, FILTER_VALIDATE_BOOLEAN), + 'period_from' => substr($this->period_from, 0, 4).substr($this->period_from, 5, 2), + ]); + } + + if($this->has('period_to')){ + //conver to YYYYMM format + $this->merge([ + 'period_to' => substr($this->period_to, 0, 4).substr($this->period_to, 5, 2), + ]); + } + + // Convert is_period_range to boolean if it exists + if ($this->has('period_to')) { + $this->merge([ + 'is_period_range' => true, ]); } } diff --git a/app/Models/PrintStatementLog.php b/app/Models/PrintStatementLog.php index 2206a08..ec1630a 100644 --- a/app/Models/PrintStatementLog.php +++ b/app/Models/PrintStatementLog.php @@ -30,6 +30,7 @@ 'deleted_by', 'authorized_by', 'authorized_at', + 'remarks', ]; protected $casts = [ diff --git a/database/migrations/2025_05_11_172635_create_print_statement_logs_table.php b/database/migrations/2025_05_11_172635_create_print_statement_logs_table.php index a78ede6..33a60d1 100644 --- a/database/migrations/2025_05_11_172635_create_print_statement_logs_table.php +++ b/database/migrations/2025_05_11_172635_create_print_statement_logs_table.php @@ -24,6 +24,7 @@ $table->string('ip_address')->nullable()->comment('IP address of requester'); $table->string('user_agent')->nullable()->comment('User agent of requester'); $table->timestamp('downloaded_at')->nullable()->comment('When the statement was downloaded'); + $table->string('remarks')->nullable()->comment('Remarks for the statement'); $table->enum('authorization_status', ['pending', 'approved', 'rejected'])->default('pending'); diff --git a/module.json b/module.json index 664d81a..1c375d3 100644 --- a/module.json +++ b/module.json @@ -25,7 +25,7 @@ { "title": "Print Statement", "path": "statements", - "icon": "ki-filled ki-calendar text-lg text-primary", + "icon": "ki-filled ki-printer text-lg text-primary", "classes": "", "attributes": [], "permission": "", diff --git a/resources/views/statements/create.blade.php b/resources/views/statements/create.blade.php deleted file mode 100644 index f648007..0000000 --- a/resources/views/statements/create.blade.php +++ /dev/null @@ -1,38 +0,0 @@ -@extends('layouts.main') - -@section('breadcrumbs') - -@endsection - -@section('content') - -@endsection - -@push('scripts') - -@endpush diff --git a/resources/views/statements/index.blade.php b/resources/views/statements/index.blade.php index 6fd2358..afe577a 100644 --- a/resources/views/statements/index.blade.php +++ b/resources/views/statements/index.blade.php @@ -5,15 +5,10 @@ @endsection @section('content') -
-
+
+

Request Print Stetement

-
@@ -22,7 +17,7 @@ @method('PUT') @endif -
+
- +
+
+
+

+ Daftar Statement Request +

+
+
+ +
-
-
-
- - - - - + + + +
- - +
+
+ + + + + - + - + - + - + - + - + - + - - - -
+ + ID - + Branch - + Account Number - + Period - + Status - + Available - - Downloaded + + Notes - + Created At - Action
-
-
Action
-
- - @@ -177,7 +171,7 @@ } }); - $.ajax(`webstatement/statements/${data}`, { + $.ajax(`statements/${data}`, { type: 'DELETE' }).then((response) => { swal.fire('Deleted!', 'Statement request has been deleted.', 'success').then(() => { @@ -222,6 +216,24 @@ }, period: { title: 'Period', + render: (item, data) => { + const monthNames = [ + 'January', 'February', 'March', 'April', 'May', 'June', + 'July', 'August', 'September', 'October', 'November', 'December' + ]; + + const formatPeriod = (period) => { + if (!period) return ''; + const year = period.substring(0, 4); + const month = parseInt(period.substring(4, 6)); + return `${monthNames[month - 1]} ${year}`; + }; + + const fromPeriod = formatPeriod(data.period_from); + const toPeriod = data.period_to ? ` - ${formatPeriod(data.period_to)}` : ''; + + return fromPeriod + toPeriod; + }, }, authorization_status: { title: 'Status', @@ -247,20 +259,13 @@ return `${statusText}`; }, }, - is_downloaded: { - title: 'Downloaded', - render: (item, data) => { - let statusClass = data.is_downloaded ? 'badge badge-light-success' : 'badge badge-light-danger'; - let statusText = data.is_downloaded ? 'Yes' : 'No'; - let downloadInfo = data.is_downloaded && data.downloaded_at ? - `
${new Date(data.downloaded_at).toLocaleString()}
` : ''; - return `${statusText}${downloadInfo}`; - }, + remarks : { + title: 'Notes', }, created_at: { title: 'Created At', render: (item, data) => { - return data.created_at ? new Date(data.created_at).toLocaleString() : ''; + return data.created_at ?? ''; }, }, actions: { @@ -271,17 +276,11 @@ `; - // Only show edit button if status is pending - if (data.authorization_status === 'pending') { - buttons += ` - - `; - } - // Show download button if statement is approved and available but not downloaded - if (data.authorization_status === 'approved' && data.is_available && !data.is_downloaded) { + //if (data.authorization_status === 'approved' && data.is_available && !data.is_downloaded) { + if (data.is_available) { buttons += ` - + `; } diff --git a/resources/views/statements/show.blade.php b/resources/views/statements/show.blade.php new file mode 100644 index 0000000..76c333b --- /dev/null +++ b/resources/views/statements/show.blade.php @@ -0,0 +1,219 @@ +@extends('layouts.main') + +@section('content') +
+
+

Statement Request Details

+
+ + Back to List + + + @if($statement->is_available && $statement->authorization_status === 'approved') + + Download Statement + + @endif +
+
+ +
+ @if(session('success')) +
+ {{ session('success') }} +
+ @endif + + @if(session('error')) +
+ {{ session('error') }} +
+ @endif + +
+ +
+
+
+

Statement Information

+
+
+
+
+
+
Branch
+
{{ $statement->branch->name ?? 'N/A' }} ({{ $statement->branch_code }})
+
+ +
+
Account Number
+
{{ $statement->account_number }}
+
+ +
+
Period
+
+ @php + // Convert format YYYYMM to Month Year + $fromYear = substr($statement->period_from, 0, 4); + $fromMonth = substr($statement->period_from, 4, 2); + $fromMonthName = date('F', mktime(0, 0, 0, $fromMonth, 1)); + + $periodText = $fromMonthName . ' ' . $fromYear; + + if($statement->is_period_range && $statement->period_to) { + $toYear = substr($statement->period_to, 0, 4); + $toMonth = substr($statement->period_to, 4, 2); + $toMonthName = date('F', mktime(0, 0, 0, $toMonth, 1)); + + $periodText .= ' - ' . $toMonthName . ' ' . $toYear; + } + @endphp + {{ $periodText }} +
+
+ +
+
Status
+
+ @if($statement->authorization_status === 'pending') + Pending Authorization + @elseif($statement->authorization_status === 'approved') + Approved + @elseif($statement->authorization_status === 'rejected') + Rejected + @endif +
+
+ +
+
Availability
+
+ @if($statement->is_available) + Available + @else + Not Available + @endif +
+
+ +
+
Downloaded
+
+ @if($statement->is_downloaded) + Yes +
+ Downloaded at: {{ $statement->downloaded_at ? $statement->downloaded_at->format('d M Y H:i:s') : 'N/A' }} +
+ @else + No + @endif +
+
+
+
+
+ + +
+
+
+

Request Information

+
+
+
+
+
+
Requested By
+
{{ $statement->user->name ?? 'N/A' }}
+
+ +
+
Requested At
+
{{ dateFormat($statement->created_at,1,1) }}
+
+ +
+
IP Address
+
{{ $statement->ip_address }}
+
+ +
+
User Agent
+
{{ $statement->user_agent }}
+
+ + @if($statement->authorization_status !== 'pending') +
+
Authorized By
+
{{ $statement->authorizer->name ?? 'N/A' }}
+
+ +
+
Authorized At
+
{{ $statement->authorized_at ? $statement->authorized_at->format('d M Y H:i:s') : 'N/A' }}
+
+ + @if($statement->remarks) +
+
Remarks
+
{{ $statement->remarks }}
+
+ @endif + @endif +
+
+
+
+ + @if($statement->authorization_status === 'pending' && auth()->user()->can('authorize_statements')) +
+
+

Authorization

+
+
+ + @csrf + +
+ +
+
+ + +
+
+ + +
+
+
+ +
+ + +
+ +
+ +
+ +
+
+ @endif +
+
+@endsection + +@push('scripts') + +@endpush