Files
webstatement/app/Http/Controllers/PrintStatementController.php
Daeng Deni Mardaeni 34571483eb feat(webstatement): optimalkan proses PDF statement dan hapus fitur tidak terpakai
- **Optimalisasi Pembuatan dan Pengunduhan PDF:**
  - Tambahkan validasi untuk mengecek keberadaan file sebelum pembuatan ulang PDF.
  - Tambahkan mekanisme pengunduhan langsung file PDF jika sudah tersedia di storage.
  - Mengelompokkan path penyimpanan dan nama file PDF secara dinamis berdasarkan periode dan nomor rekening.
  - Integrasi update status `is_available` pada tabel `PrintStatementLog` setelah file berhasil dibuat.

- **Refaktor dan Perbaikan Logika Pembuatan PDF:**
  - Perbarui fungsi `generateStatementPdf` untuk mendukung parameter tambahan terkait penyimpanan file.
  - Hapus duplikasi logika terkait pembuatan path storage dan file PDF.
  - Tambahkan handling error untuk memastikan file PDF berhasil dibuat.

- **Hapus Fitur Preview PDF:**
  - Menghapus fungsi `previewPdf()` dan rute terkait karena sudah tidak digunakan.
  - Membersihkan bagian UI yang merujuk pada fitur ini.

- **Peningkatan UI dan Tampilan:**
  - Menghapus kolom `authorization_status` pada tabel karena tidak relevan.
  - Penyesuaian styling pada kolom `is_available` untuk menampilkan status ketersediaan file.

- **Perubahan pada Rute:**
  - Membersihkan rute tidak terpakai terkait preview dan generate PDF langsung.

- **Refaktor Umum:**
  - Menghapus kode redundan yang berhubungan dengan penyimpanan file sementara.
  - Penyesuaian struktur fungsi untuk meningkatkan keterbacaan dan efisiensi.

Perubahan ini memastikan proses PDF statement lebih efisien dengan validasi ketat, pengelolaan file yang lebih baik, serta penyederhanaan sistem dengan menghapus fitur yang tidak relevan.

Signed-off-by: Daeng Deni Mardaeni <ddeni05@gmail.com>
2025-07-09 20:23:52 +07:00

1546 lines
64 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, DB, Log, Mail, Storage};
use Illuminate\Validation\Rule;
use Modules\Basicdata\Models\Branch;
use Modules\Webstatement\Http\Requests\PrintStatementRequest;
use Modules\Webstatement\Jobs\{ExportStatementPeriodJob, GenerateMultiAccountPdfJob};
use Modules\Webstatement\Mail\StatementEmail;
use Modules\Webstatement\Models\{Account, AccountBalance, PrintStatementLog, ProcessedStatement};
use Spatie\Browsershot\Browsershot;
use ZipArchive;
ini_set('memory_limit', '2G'); // Atau '1G' untuk data yang sangat besar
ini_set('max_execution_time', 300000);
class PrintStatementController extends Controller
{
/**
* Display a listing of the statements.
*/
public function index(Request $request)
{
$branches = Branch::whereNotNull('customer_company')
->where('code', '!=', 'ID0019999')
->orderBy('name')
->get();
$branch = Branch::find(Auth::user()->branch_id);
$multiBranch = session('MULTI_BRANCH') ?? false;
return view('webstatement::statements.index', compact('branches', 'branch', 'multiBranch'));
}
/**
* Store a newly created statement request.
* Menangani pembuatan request statement baru dengan logging dan transaksi database
*/
public function store(PrintStatementRequest $request)
{
// Add account verification before storing
$accountNumber = $request->input('account_number'); // Assuming this is the field name for account number
// First, check if the account exists and get branch information
$account = Account::where('account_number', $accountNumber)->first();
if ($account) {
$branch_code = $account->branch_code;
$userBranchId = session('branch_id'); // Assuming branch ID is stored in session
$multiBranch = session('MULTI_BRANCH');
if (!$multiBranch) {
// Check if account branch matches user's branch
if ($account->branch_id !== $userBranchId) {
return redirect()->route('statements.index')
->with('error', 'Nomor rekening tidak sesuai dengan cabang Anda. Transaksi tidak dapat dilanjutkan.');
}
}
// Check if account belongs to restricted branch ID0019999
if ($account->branch_id === 'ID0019999') {
return redirect()->route('statements.index')
->with('error', 'Nomor rekening terdaftar pada cabang khusus. Silakan hubungi bagian HC untuk informasi lebih lanjut.');
}
// If all checks pass, proceed with storing data
// Your existing store logic here
} else {
// Account not found
return redirect()->route('statements.index')
->with('error', 'Nomor rekening tidak ditemukan dalam sistem.');
}
DB::beginTransaction();
try {
$validated = $request->validated();
$validated['request_type'] = 'single_account'; // Default untuk request manual
if($validated['branch_code'] && !empty($validated['stmt_sent_type'])){
$validated['request_type'] = 'multi_account'; // Default untuk request manual
}
// Add user tracking data dan field baru untuk single account request
$validated['user_id'] = Auth::id();
$validated['created_by'] = Auth::id();
$validated['ip_address'] = $request->ip();
$validated['user_agent'] = $request->userAgent();
$validated['status'] = 'pending'; // Status awal
$validated['authorization_status'] = 'approved'; // Status otorisasi awal
$validated['total_accounts'] = 1; // Untuk single account
$validated['processed_accounts'] = 0;
$validated['success_count'] = 0;
$validated['failed_count'] = 0;
$validated['stmt_sent_type'] = $request->input('stmt_sent_type') ? json_encode($request->input('stmt_sent_type')) : '';
$validated['branch_code'] = $validated['branch_code'] ?? $branch_code; // Awal tidak tersedia
// Create the statement log
$statement = PrintStatementLog::create($validated);
// Log aktivitas
Log::info('Statement request created', [
'statement_id' => $statement->id,
'user_id' => Auth::id(),
'account_number' => $statement->account_number,
'request_type' => $statement->request_type
]);
// Process statement availability check
$this->checkStatementAvailability($statement);
if(!$statement->is_available){
$this->printStatementRekening($statement);
}
$statement = PrintStatementLog::find($statement->id);
if($statement->email){
$this->sendEmail($statement->id);
}
DB::commit();
return redirect()->route('statements.index')
->with('success', 'Statement request has been created successfully.');
} catch (Exception $e) {
DB::rollBack();
Log::error('Failed to create statement request', [
'error' => $e->getMessage(),
'user_id' => Auth::id()
]);
return redirect()->back()
->withInput()
->with('error', 'Failed to create statement request: ' . $e->getMessage());
}
}
/**
* Show the form for creating a new statement request.
*/
public function create()
{
$branches = Branch::orderBy('name')->get();
return view('webstatement::statements.create', compact('branches'));
}
/**
* Check if the statement is available in the system.
* Memperbarui status availability dengan logging
*/
protected function checkStatementAvailability(PrintStatementLog $statement)
{
DB::beginTransaction();
try {
$disk = Storage::disk('sftpStatement');
$filePath = "{$statement->period_from}/{$statement->branch_code}/{$statement->account_number}_{$statement->period_from}.pdf";
// Log untuk debugging
Log::info('Checking SFTP file path', [
'file_path' => $filePath,
'sftp_root' => config('filesystems.disks.sftpStatement.root'),
'full_path' => config('filesystems.disks.sftpStatement.root') . '/' . $filePath
]);
if ($statement->is_period_range && $statement->period_to) {
$periodFrom = Carbon::createFromFormat('Ym', $statement->period_from);
$periodTo = Carbon::createFromFormat('Ym', $statement->period_to);
$missingPeriods = [];
$availablePeriods = [];
for ($period = clone $periodFrom; $period->lte($periodTo); $period->addMonth()) {
$periodFormatted = $period->format('Ym');
$periodPath = $periodFormatted . "/{$statement->branch_code}/{$statement->account_number}_{$periodFormatted}.pdf";
if ($disk->exists($periodPath)) {
$availablePeriods[] = $periodFormatted;
} else {
$missingPeriods[] = $periodFormatted;
}
}
if (count($missingPeriods) > 0) {
$notes = "Missing periods: " . implode(', ', $missingPeriods);
$statement->update([
'is_available' => false,
'remarks' => $notes,
'updated_by' => Auth::id(),
'status' => 'failed'
]);
Log::warning('Statement not available - missing periods', [
'statement_id' => $statement->id,
'missing_periods' => $missingPeriods
]);
} else {
$statement->update([
'is_available' => true,
'updated_by' => Auth::id(),
'status' => 'completed',
'processed_accounts' => 1,
'success_count' => 1
]);
Log::info('Statement available - all periods found', [
'statement_id' => $statement->id,
'available_periods' => $availablePeriods
]);
}
} else if ($disk->exists($filePath)) {
$statement->update([
'is_available' => true,
'updated_by' => Auth::id(),
'status' => 'completed',
'processed_accounts' => 1,
'success_count' => 1
]);
Log::info('Statement available', [
'statement_id' => $statement->id,
'file_path' => $filePath
]);
} else {
$statement->update([
'is_available' => false,
'updated_by' => Auth::id(),
'status' => 'failed',
'processed_accounts' => 1,
'failed_count' => 1,
'error_message' => 'Statement file not found'
]);
Log::warning('Statement not available', [
'statement_id' => $statement->id,
'file_path' => $filePath
]);
}
DB::commit();
} catch (Exception $e) {
DB::rollBack();
Log::error('Error checking statement availability', [
'statement_id' => $statement->id,
'error' => $e->getMessage()
]);
$statement->update([
'is_available' => false,
'status' => 'failed',
'error_message' => $e->getMessage(),
'updated_by' => Auth::id()
]);
}
}
/**
* Display the specified statement.
*/
public function show(PrintStatementLog $statement)
{
$statement->load(['user', 'branch', 'creator', 'authorizer']);
return view('webstatement::statements.show', compact('statement'));
}
/**
* Download the statement if available and authorized.
* Memperbarui status download dengan logging dan transaksi
*/
public function download(PrintStatementLog $statement)
{
if (!$statement->is_available) {
return back()->with('error', 'Statement is not available for download.');
}
if($statement->is_generated){
return $this->generated($statement->id);
}
DB::beginTransaction();
try {
// Update download status
$statement->update([
'is_downloaded' => true,
'downloaded_at' => now(),
'updated_by' => Auth::id()
]);
Log::info('Statement downloaded', [
'statement_id' => $statement->id,
'user_id' => Auth::id(),
'account_number' => $statement->account_number
]);
DB::commit();
// Generate or fetch the statement file
$disk = Storage::disk('sftpStatement');
$filePath = "{$statement->period_from}/{$statement->branch_code}/{$statement->account_number}_{$statement->period_from}.pdf";
if ($statement->is_period_range && $statement->period_to) {
// Log: Memulai proses download period range
Log::info('Starting period range download', [
'statement_id' => $statement->id,
'period_from' => $statement->period_from,
'period_to' => $statement->period_to
]);
/**
* Handle period range download dengan membuat zip file
* yang berisi semua statement dalam rentang periode
*/
$periodFrom = Carbon::createFromFormat('Ym', $statement->period_from);
$periodTo = 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 . "/{$statement->branch_code}/{$statement->account_number}_{$periodFormatted}.pdf";
if ($disk->exists($periodPath)) {
$availablePeriods[] = $periodFormatted;
Log::info('Period available for download', [
'period' => $periodFormatted,
'path' => $periodPath
]);
} else {
$missingPeriods[] = $periodFormatted;
Log::warning('Period not available for download', [
'period' => $periodFormatted,
'path' => $periodPath
]);
}
}
// If any period is available, create a zip and download it
if (count($availablePeriods) > 0) {
/**
* Membuat zip file temporary untuk download
* dengan semua statement yang tersedia dalam periode
*/
$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);
Log::info('Created temp directory for zip files');
}
// Create a new zip archive
$zip = new ZipArchive();
if ($zip->open($zipFilePath, ZipArchive::CREATE | ZipArchive::OVERWRITE) === true) {
Log::info('Zip archive created successfully', ['zip_path' => $zipFilePath]);
// Add each available statement to the zip
foreach ($availablePeriods as $period) {
$periodFilePath = "{$period}/{$statement->branch_code}/{$statement->account_number}_{$period}.pdf";
$localFilePath = storage_path("app/temp/{$statement->account_number}_{$period}.pdf");
try {
// Download the file from SFTP to local storage temporarily
file_put_contents($localFilePath, $disk->get($periodFilePath));
// Add the file to the zip
$zip->addFile($localFilePath, "{$statement->account_number}_{$period}.pdf");
Log::info('Added file to zip', [
'period' => $period,
'local_path' => $localFilePath
]);
} catch (Exception $e) {
Log::error('Failed to add file to zip', [
'period' => $period,
'error' => $e->getMessage()
]);
}
}
$zip->close();
Log::info('Zip archive closed successfully');
// Return the zip file for download
$response = response()->download($zipFilePath, $zipFileName)->deleteFileAfterSend(true);
// Clean up temporary PDF files
foreach ($availablePeriods as $period) {
$localFilePath = storage_path("app/temp/{$statement->account_number}_{$period}.pdf");
if (file_exists($localFilePath)) {
unlink($localFilePath);
Log::info('Cleaned up temporary file', ['file' => $localFilePath]);
}
}
Log::info('Period range download completed successfully', [
'statement_id' => $statement->id,
'available_periods' => count($availablePeriods),
'missing_periods' => count($missingPeriods)
]);
return $response;
} else {
Log::error('Failed to create zip archive', ['zip_path' => $zipFilePath]);
return back()->with('error', 'Failed to create zip archive for download.');
}
} else {
Log::warning('No statements available for download in period range', [
'statement_id' => $statement->id,
'missing_periods' => $missingPeriods
]);
return back()->with('error', 'No statements available for download in the specified period range.');
}
} else if ($disk->exists($filePath)) {
/**
* Handle single period download
* Download file PDF tunggal untuk periode tertentu
*/
Log::info('Single period download', [
'statement_id' => $statement->id,
'file_path' => $filePath
]);
return $disk->download($filePath, "{$statement->account_number}_{$statement->period_from}.pdf");
} else {
Log::warning('Statement file not found', [
'statement_id' => $statement->id,
'file_path' => $filePath
]);
return back()->with('error', 'Statement file not found.');
}
} catch (Exception $e) {
DB::rollBack();
Log::error('Failed to download statement', [
'statement_id' => $statement->id,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
return back()->with('error', 'Failed to download statement: ' . $e->getMessage());
}
}
/**
* Authorize a statement request.
*/
public function authorize(Request $request, PrintStatementLog $statement)
{
$request->validate([
'authorization_status' => ['required', Rule::in(['approved', 'rejected'])],
'remarks' => ['nullable', 'string', 'max:255'],
]);
// Update authorization status
$statement->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('statements.show', $statement->id)
->with('success', "Statement request has been {$statusText} successfully.");
}
/**
* Provide data for datatables.
*/
public function dataForDatatables(Request $request)
{
// Check permissions if needed
// if (!auth()->user()->can('view_statements')) {
// abort(403, 'Sorry! You are not allowed to view statements.');
// }
// Retrieve data from the database
$query = PrintStatementLog::query();
$query->whereNotNull('user_id');
if (!auth()->user()->hasRole('administrator')) {
$query->where(function($q) {
$q->where('user_id', Auth::id())
->orWhere('branch_code', Auth::user()->branch->code);
});
}
// 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('account_number', 'LIKE', "%$search%")
->orWhere('branch_code', 'LIKE', "%$search%")
->orWhere('period_from', 'LIKE', "%$search%")
->orWhere('period_to', 'LIKE', "%$search%")
->orWhere('authorization_status', 'LIKE', "%$search%")
->orWhere('request_type', 'LIKE', "%$search%")
->orWhere('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'] === 'branch_code') {
$query->where('branch_code', $filter['value']);
} else if ($filter['column'] === 'authorization_status') {
$query->where('authorization_status', $filter['value']);
} else if ($filter['column'] === 'request_type') {
$query->where('request_type', $filter['value']);
} else if ($filter['column'] === 'status') {
$query->where('status', $filter['value']);
} else if ($filter['column'] === 'is_downloaded') {
$query->where('is_downloaded', filter_var($filter['value'], FILTER_VALIDATE_BOOLEAN));
}
}
}
}
// 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 = [
'branch' => 'branch_code',
'account' => 'account_number',
'period' => 'period_from',
'auth_status' => 'authorization_status',
'request_type' => 'request_type',
'status' => 'status',
'remarks' => 'remarks',
];
$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; // Calculate the offset
$query->skip($offset)->take($size);
}
// Get the filtered count of records
$filteredRecords = $query->count();
// Eager load relationships to avoid N+1 query problems
$query->with(['user', 'branch', 'authorizer']);
// Get the data for the current page
$data = $query->get()->map(function ($item) {
// Transform data for frontend if needed
return [
'id' => $item->id,
'branch_code' => $item->branch_code,
'branch_name' => $item->branch->name ?? 'N/A',
'account_number' => $item->account_number,
'period_from' => $item->period_from,
'period_to' => $item->is_period_range ? $item->period_to : null,
'authorization_status' => $item->authorization_status,
'is_available' => $item->is_available,
'is_generated' => $item->is_generated,
'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,
'remarks' => $item->remarks,
];
});
// Calculate the page count
$pageCount = ceil($filteredRecords / ($request->get('size') ?: 1));
// Calculate the current page number
$currentPage = $request->get('page') ?: 1;
// Return the response data as a JSON object
return response()->json([
'draw' => $request->get('draw'),
'recordsTotal' => $totalRecords,
'recordsFiltered' => $filteredRecords,
'pageCount' => $pageCount,
'page' => $currentPage,
'totalCount' => $totalRecords,
'data' => $data,
]);
}
public function destroy(PrintStatementLog $statement)
{
// Delete the statement
$statement->delete();
return response()->json([
'message' => 'Statement deleted successfully.',
]);
}
/**
* Send statement to email
*/
public function sendEmail($id)
{
$statement = PrintStatementLog::findOrFail($id);
// Check if statement has email
if (empty($statement->email)) {
return redirect()->back()->with('error', 'No email address provided for this statement.');
}
// Check if statement is available
if (!$statement->is_available) {
return redirect()->back()->with('error', 'Statement is not available for sending.');
}
try {
$disk = Storage::disk('sftpStatement');
$filePath = "{$statement->period_from}/{$statement->branch_code}/{$statement->account_number}_{$statement->period_from}.pdf";
if ($statement->is_period_range && $statement->period_to) {
$periodFrom = Carbon::createFromFormat('Ym', $statement->period_from);
$periodTo = 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 . "/{$statement->branch_code}/{$statement->account_number}_{$periodFormatted}.pdf";
if ($disk->exists($periodPath)) {
$availablePeriods[] = $periodFormatted;
} else {
$missingPeriods[] = $periodFormatted;
}
}
// If any period is available, create a zip and send it
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}/{$statement->branch_code}/{$statement->account_number}_{$statement->period_from}.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();
// Send email with zip attachment
Mail::to($statement->email)
->send(new StatementEmail($statement, $zipFilePath, true));
// Clean up temporary files
foreach ($availablePeriods as $period) {
$localFilePath = storage_path("app/temp/{$statement->account_number}_{$period}.pdf");
if (file_exists($localFilePath)) {
unlink($localFilePath);
}
}
// Delete the zip file after sending
if (file_exists($zipFilePath)) {
unlink($zipFilePath);
}
} else {
return redirect()->back()->with('error', 'Failed to create zip archive for email.');
}
} else {
return redirect()->back()->with('error', 'No statements available for sending.');
}
} else if ($disk->exists($filePath)) {
// For single period statements
$localFilePath = storage_path("app/temp/{$statement->account_number}_{$statement->period_from}.pdf");
// Ensure the temp directory exists
if (!file_exists(storage_path('app/temp'))) {
mkdir(storage_path('app/temp'), 0755, true);
}
// Download the file from SFTP to local storage temporarily
file_put_contents($localFilePath, $disk->get($filePath));
// Send email with PDF attachment
Mail::to($statement->email)
->send(new StatementEmail($statement, $localFilePath, false));
// Delete the temporary file
if (file_exists($localFilePath)) {
unlink($localFilePath);
}
} else {
return redirect()->back()->with('error', 'Statement file not found.');
}
// Update statement record to mark as emailed
$statement->update([
'email_sent_at' => now(),
'updated_by' => Auth::id()
]);
Log::info('Statement email sent successfully', [
'statement_id' => $statement->id,
'email' => $statement->email,
'user_id' => Auth::id()
]);
DB::commit();
return redirect()->back()->with('success', 'Statement has been sent to ' . $statement->email);
} catch (Exception $e) {
// Log the error
Log::error('Failed to send statement email: ' . $e->getMessage());
return redirect()->back()->with('error', 'Failed to send email: ' . $e->getMessage());
}
}
/**
* Generate or fetch the statement file.
* This is a placeholder method - implement according to your system.
*/
protected function generateStatementFile(PrintStatementLog $statement)
{
// This would be implemented based on your system's logic
// For example, calling an API to generate a PDF or fetching from storage
// Placeholder implementation - return a dummy path
$tempFile = tempnam(sys_get_temp_dir(), 'statement_');
file_put_contents($tempFile, 'Statement content would go here');
return $tempFile;
}
/**
* Send statement to email
*/
/**
* Generate a filename for the statement download.
*/
protected function generateFileName(PrintStatementLog $statement)
{
$accountNumber = $statement->account_number;
if ($statement->is_period_range) {
return "statement_{$accountNumber}_{$statement->period_from}_to_{$statement->period_to}.pdf";
}
return "statement_{$accountNumber}_{$statement->period_from}.pdf";
}
/**
* Generate statement view atau PDF berdasarkan parameter
*
* @param string $norek Nomor rekening
* @param string $period Periode dalam format YYYYMM
* @param string|null $format Format output: 'html' atau 'pdf'
* @return \Illuminate\View\View|\Illuminate\Http\Response
*/
public function generated($id){
try {
$statement = PrintStatementLog::find($id);
DB::beginTransaction();
$norek = $statement->account_number;
$period = $statement->period_from;
$format='pdf';
// Generate nama file PDF
$filename = $this->generatePdfFileName($norek, $period);
// Tentukan path storage
$storagePath = "statements/{$period}/{$norek}";
$fullStoragePath = "{$storagePath}/{$filename}";
// Pastikan direktori storage ada
Storage::disk('local')->makeDirectory($storagePath);
// Path temporary untuk Browsershot
$tempPath = storage_path("app/{$fullStoragePath}");
if(file_exists($tempPath)){
return response()->download($tempPath, $filename, [
'Content-Type' => 'application/pdf',
'Content-Disposition' => 'attachment; filename="' . $filename . '"'
])->deleteFileAfterSend(false); // Keep file in storage
}
$stmtEntries = ProcessedStatement::where(['account_number' => $norek, 'period' => $period])->orderBy('sequence_no')->get();
$account = Account::with('customer')->where('account_number', $norek)->first();
if (!$account) {
throw new Exception("Account not found: {$norek}");
}
$customer = $account->customer;
$branch = Branch::where('code', $account->branch_code)->first();
// Cek apakah file gambar ada
$headerImagePath = public_path('assets/media/images/bg-header-table.png');
$headerTableBg = file_exists($headerImagePath)
? base64_encode(file_get_contents($headerImagePath))
: null;
// Logika untuk menentukan period saldo berdasarkan aturan baru
$saldoPeriod = $this->calculateSaldoPeriod($period);
Log::info('Calculated saldo period', [
'original_period' => $period,
'saldo_period' => $saldoPeriod
]);
$saldoAwalBulan = AccountBalance::where(['account_number' => $norek, 'period' => $saldoPeriod])->first();
if (!$saldoAwalBulan) {
Log::warning('Saldo awal bulan not found', [
'account_number' => $norek,
'saldo_period' => $saldoPeriod
]);
$saldoAwalBulan = (object) ['actual_balance' => 0];
}
DB::commit();
Log::info('Statement data prepared successfully', [
'account_number' => $norek,
'period' => $period,
'saldo_period' => $saldoPeriod,
'saldo_awal' => $saldoAwalBulan->actual_balance ?? 0,
'entries_count' => $stmtEntries->count()
]);
$periodDates = calculatePeriodDates($period);
// Jika format adalah PDF, generate PDF
if ($format === 'pdf') {
return $this->generateStatementPdf($norek, $period, $stmtEntries, $account, $customer, $headerTableBg, $branch, $saldoAwalBulan, $statement->id, $tempPath, $filename);
}
// Default return HTML view
return view('webstatement::statements.stmt', compact('stmtEntries', 'account', 'customer', 'headerTableBg', 'branch', 'period', 'saldoAwalBulan'));
} catch (Exception $e) {
DB::rollBack();
Log::error('Failed to generate statement', [
'error' => $e->getMessage(),
'account_number' => $norek,
'period' => $period,
'format' => $format,
'trace' => $e->getTraceAsString()
]);
if ($format === 'pdf') {
return response()->json([
'success' => false,
'message' => 'Failed to generate PDF statement',
'error' => $e->getMessage()
], 500);
}
throw $e;
}
}
/**
* Generate PDF dari statement HTML dan simpan ke storage
*
* @param string $norek Nomor rekening
* @param string $period Periode
* @param \Illuminate\Database\Eloquent\Collection $stmtEntries Data transaksi
* @param object $account Data akun
* @param object $customer Data customer
* @param string|null $headerTableBg Base64 encoded header image
* @param object $branch Data cabang
* @param object $saldoAwalBulan Data saldo awal
* @return \Illuminate\Http\Response
*/
protected function generateStatementPdf($norek, $period, $stmtEntries, $account, $customer, $headerTableBg, $branch, $saldoAwalBulan, $statementId, $tempPath, $filename)
{
try {
DB::beginTransaction();
Log::info('Starting PDF generation with storage', [
'account_number' => $norek,
'period' => $period,
'user_id' => Auth::id()
]);
// Render HTML view
$html = view('webstatement::statements.stmt', compact(
'stmtEntries',
'account',
'customer',
'headerTableBg',
'branch',
'period',
'saldoAwalBulan'
))->render();
// Tentukan path storage
$storagePath = "statements/{$period}/{$norek}";
$fullStoragePath = "{$storagePath}/{$filename}";
// Generate PDF menggunakan Browsershot dan simpan langsung ke storage
Browsershot::html($html)
->showBackground()
->setOption('addStyleTag', json_encode(['content' => '@page { margin: 0; }']))
->format('A4')
->margins(0, 0, 0, 0)
->waitUntilNetworkIdle()
->timeout(6000)
->save($tempPath);
// Verifikasi file berhasil dibuat
if (!file_exists($tempPath)) {
throw new Exception('PDF file was not created successfully');
} else {
$printLog = PrintStatementLog::find($statementId);
if($printLog){
$printLog->update(['is_available' => true]);
}
}
$fileSize = filesize($tempPath);
Log::info('PDF generated and saved to storage', [
'account_number' => $norek,
'period' => $period,
'storage_path' => $fullStoragePath,
'file_size' => $fileSize,
'temp_path' => $tempPath
]);
DB::commit();
// Return download response
return response()->download($tempPath, $filename, [
'Content-Type' => 'application/pdf',
'Content-Disposition' => 'attachment; filename="' . $filename . '"'
])->deleteFileAfterSend(false); // Keep file in storage
} catch (Exception $e) {
DB::rollBack();
Log::error('Failed to generate PDF with storage', [
'error' => $e->getMessage(),
'account_number' => $norek,
'period' => $period,
'trace' => $e->getTraceAsString()
]);
throw new Exception('Failed to generate PDF: ' . $e->getMessage());
}
}
/**
* Generate nama file PDF berdasarkan nomor rekening dan periode
*
* @param string $norek Nomor rekening
* @param string $period Periode
* @return string
*/
protected function generatePdfFileName($norek, $period)
{
try {
$filename = "statement_{$norek}_{$period}.pdf";
Log::info('Generated PDF filename', [
'account_number' => $norek,
'period' => $period,
'filename' => $filename
]);
return $filename;
} catch (Exception $e) {
Log::error('Error generating PDF filename', [
'error' => $e->getMessage(),
'account_number' => $norek,
'period' => $period
]);
// Fallback filename
return "statement_{$norek}_{$period}.pdf";
}
}
/**
* Simpan PDF ke storage dengan validasi dan logging
*
* @param string $tempPath Path temporary file
* @param string $storagePath Path di storage
* @param string $norek Nomor rekening
* @param string $period Periode
* @return string|null Path file yang disimpan
*/
protected function savePdfToStorage($tempPath, $storagePath, $norek, $period)
{
try {
// Validasi file temporary ada
if (!file_exists($tempPath)) {
throw new Exception('Temporary PDF file not found');
}
// Validasi ukuran file
$fileSize = filesize($tempPath);
if ($fileSize === 0) {
throw new Exception('PDF file is empty');
}
// Baca konten file
$pdfContent = file_get_contents($tempPath);
if ($pdfContent === false) {
throw new Exception('Failed to read PDF content');
}
// Simpan ke storage
$saved = Storage::disk('local')->put($storagePath, $pdfContent);
if (!$saved) {
throw new Exception('Failed to save PDF to storage');
}
// Verifikasi file tersimpan
if (!Storage::disk('local')->exists($storagePath)) {
throw new Exception('PDF file not found in storage after save');
}
$savedSize = Storage::disk('local')->size($storagePath);
Log::info('PDF successfully saved to storage', [
'account_number' => $norek,
'period' => $period,
'storage_path' => $storagePath,
'original_size' => $fileSize,
'saved_size' => $savedSize,
'temp_path' => $tempPath
]);
return $storagePath;
} catch (Exception $e) {
Log::error('Failed to save PDF to storage', [
'error' => $e->getMessage(),
'account_number' => $norek,
'period' => $period,
'storage_path' => $storagePath,
'temp_path' => $tempPath
]);
return null;
}
}
/**
* Ambil PDF dari storage untuk download
*
* @param string $norek Nomor rekening
* @param string $period Periode
* @param string $filename Nama file (optional)
* @return \Illuminate\Http\Response
*/
public function downloadFromStorage($norek, $period, $filename = null)
{
try {
// Generate filename jika tidak disediakan
if (!$filename) {
$filename = $this->generatePdfFileName($norek, $period);
}
$storagePath = "statements/{$period}/{$norek}/{$filename}";
// Cek apakah file ada di storage
if (!Storage::disk('local')->exists($storagePath)) {
Log::warning('PDF not found in storage', [
'account_number' => $norek,
'period' => $period,
'storage_path' => $storagePath
]);
return response()->json([
'success' => false,
'message' => 'PDF file not found in storage'
], 404);
}
$fullPath = Storage::disk('local')->path($storagePath);
Log::info('PDF downloaded from storage', [
'account_number' => $norek,
'period' => $period,
'storage_path' => $storagePath
]);
return response()->download($fullPath, $filename, [
'Content-Type' => 'application/pdf'
]);
} catch (Exception $e) {
Log::error('Failed to download PDF from storage', [
'error' => $e->getMessage(),
'account_number' => $norek,
'period' => $period,
'filename' => $filename
]);
return response()->json([
'success' => false,
'message' => 'Failed to download PDF',
'error' => $e->getMessage()
], 500);
}
}
/**
* Hapus PDF dari storage
*
* @param string $norek Nomor rekening
* @param string $period Periode
* @param string $filename Nama file (optional)
* @return bool
*/
public function deleteFromStorage($norek, $period, $filename = null)
{
try {
if (!$filename) {
$filename = $this->generatePdfFileName($norek, $period);
}
$storagePath = "statements/{$period}/{$norek}/{$filename}";
if (Storage::disk('local')->exists($storagePath)) {
$deleted = Storage::disk('local')->delete($storagePath);
Log::info('PDF deleted from storage', [
'account_number' => $norek,
'period' => $period,
'storage_path' => $storagePath,
'success' => $deleted
]);
return $deleted;
}
Log::warning('PDF not found for deletion', [
'account_number' => $norek,
'period' => $period,
'storage_path' => $storagePath
]);
return false;
} catch (Exception $e) {
Log::error('Failed to delete PDF from storage', [
'error' => $e->getMessage(),
'account_number' => $norek,
'period' => $period,
'filename' => $filename
]);
return false;
}
}
/**
* Menghitung period untuk pengambilan saldo berdasarkan aturan bisnis
* - Jika period = 202505, gunakan 20250510
* - Jika period > 202505, ambil tanggal akhir bulan sebelumnya
*
* @param string $period Format YYYYMM
* @return string Format YYYYMMDD
*/
private function calculateSaldoPeriod($period)
{
try {
// Jika period adalah 202505, gunakan tanggal 10 Mei 2025
if ($period === '202505') {
return '20250510';
}
// Jika period lebih dari 202505, ambil tanggal akhir bulan sebelumnya
if ($period > '202505') {
$year = substr($period, 0, 4);
$month = substr($period, 4, 2);
// Buat tanggal pertama bulan ini
$firstDayOfMonth = Carbon::createFromDate($year, $month, 1);
// Ambil tanggal terakhir bulan sebelumnya
$lastDayPrevMonth = $firstDayOfMonth->subDay();
return $lastDayPrevMonth->format('Ymd');
}
// Untuk period sebelum 202505, gunakan logika default (tanggal 10)
$year = substr($period, 0, 4);
$month = substr($period, 4, 2);
return $year . $month . '10';
} catch (Exception $e) {
Log::error('Error calculating saldo period', [
'period' => $period,
'error' => $e->getMessage()
]);
// Fallback ke format default
return $period . '10';
}
}
/**
* Process statement untuk multi account berdasarkan stmt_sent_type
*
* @param PrintStatementLog $statement
* @return \Illuminate\Http\JsonResponse
*/
function printStatementRekening($statement) {
try {
// DB::beginTransaction();
Log::info('Starting statement processing', [
'statement_id' => $statement->id,
'request_type' => $statement->request_type,
'stmt_sent_type' => $statement->stmt_sent_type,
'branch_code' => $statement->branch_code
]);
if ($statement->request_type === 'multi_account') {
return $this->processMultiAccountStatement($statement);
} else {
return $this->processSingleAccountStatement($statement);
}
} catch (\Exception $e) {
//DB::rollBack();
Log::error('Failed to process statement', [
'error' => $e->getMessage(),
'statement_id' => $statement->id,
'trace' => $e->getTraceAsString()
]);
return response()->json([
'success' => false,
'message' => 'Failed to process statement',
'error' => $e->getMessage()
], 500);
}
}
/**
* Process multi account statement berdasarkan stmt_sent_type
*
* @param PrintStatementLog $statement
* @return \Illuminate\Http\JsonResponse
*/
protected function processMultiAccountStatement($statement)
{
try {
$period = $statement->period_from ?? date('Ym');
$clientName = 'client1';
// Validasi stmt_sent_type
if (empty($statement->stmt_sent_type)) {
throw new \Exception('stmt_sent_type is required for multi account processing');
}
// Decode stmt_sent_type jika dalam format JSON array
$stmtSentTypes = is_string($statement->stmt_sent_type)
? json_decode($statement->stmt_sent_type, true)
: $statement->stmt_sent_type;
if (!is_array($stmtSentTypes)) {
$stmtSentTypes = [$stmtSentTypes];
}
Log::info('Processing multi account statement', [
'statement_id' => $statement->id,
'branch_code' => $statement->branch_code,
'stmt_sent_types' => $stmtSentTypes,
'period' => $period
]);
// Ambil accounts berdasarkan branch_code dan stmt_sent_type
$accounts = Account::where('branch_code', $statement->branch_code)
->whereIn('stmt_sent_type', $stmtSentTypes)
->with('customer')
->get();
if ($accounts->isEmpty()) {
throw new \Exception('No accounts found for the specified criteria');
}
Log::info('Found accounts for processing', [
'total_accounts' => $accounts->count(),
'branch_code' => $statement->branch_code,
'stmt_sent_types' => $stmtSentTypes
]);
// Update statement log dengan informasi accounts
$accountNumbers = $accounts->pluck('account_number')->toArray();
$statement->update([
'target_accounts' => $accountNumbers,
'total_accounts' => $accounts->count(),
'status' => 'processing',
'started_at' => now()
]);
// Dispatch job untuk generate PDF multi account
$job = GenerateMultiAccountPdfJob::dispatch(
$statement,
$accounts,
$period,
$clientName
);
DB::commit();
Log::info('Multi account PDF generation job dispatched', [
'job_id' => $job->job_id ?? null,
'statement_id' => $statement->id,
'total_accounts' => $accounts->count(),
'period' => $period
]);
return response()->json([
'success' => true,
'message' => 'Multi account statement processing queued successfully',
'data' => [
'job_id' => $job->job_id ?? null,
'statement_id' => $statement->id,
'total_accounts' => $accounts->count(),
'account_numbers' => $accountNumbers,
'period' => $period,
'client_name' => $clientName
]
]);
} catch (\Exception $e) {
DB::rollBack();
Log::error('Failed to process multi account statement', [
'error' => $e->getMessage(),
'statement_id' => $statement->id,
'trace' => $e->getTraceAsString()
]);
throw $e;
}
}
/**
* Process single account statement (existing logic)
*
* @param PrintStatementLog $statement
* @return \Illuminate\Http\JsonResponse
*/
protected function processSingleAccountStatement($statement)
{
$accountNumber = $statement->account_number;
$period = $statement->period_from ?? date('Ym');
$balance = AccountBalance::where('account_number', $accountNumber)
->when($period === '202505', function($query) {
return $query->where('period', '>=', '20250512')
->orderBy('period', 'asc');
}, function($query) use ($period) {
// Get balance from last day of previous month
$firstDayOfMonth = Carbon::createFromFormat('Ym', $period)->startOfMonth();
$lastDayPrevMonth = $firstDayOfMonth->copy()->subDay()->format('Ymd');
return $query->where('period', $lastDayPrevMonth);
})
->first()
->actual_balance ?? '0.00';
$clientName = 'client1';
try {
Log::info("Starting statement export for account: {$accountNumber}, period: {$period}, client: {$clientName}");
// Validate inputs
if (empty($accountNumber) || empty($period) || empty($clientName)) {
throw new \Exception('Required parameters missing');
}
// Dispatch the job
$job = ExportStatementPeriodJob::dispatch($statement->id, $accountNumber, $period, $balance, $clientName);
Log::info("Statement export job dispatched successfully", [
'job_id' => $job->job_id ?? null,
'account' => $accountNumber,
'period' => $period,
'client' => $clientName
]);
return response()->json([
'success' => true,
'message' => 'Statement export job queued successfully',
'data' => [
'job_id' => $job->job_id ?? null,
'account_number' => $accountNumber,
'period' => $period,
'client_name' => $clientName
]
]);
} catch (\Exception $e) {
Log::error("Failed to export statement", [
'error' => $e->getMessage(),
'account' => $accountNumber,
'period' => $period
]);
return response()->json([
'success' => false,
'message' => 'Failed to queue statement export job',
'error' => $e->getMessage()
]);
}
}
/**
* Download ZIP file untuk multi account statement
*
* @param int $statementId
* @return \Illuminate\Http\Response
*/
public function downloadMultiAccountZip($statementId)
{
try {
$statement = PrintStatementLog::findOrFail($statementId);
if ($statement->request_type !== 'multi_account') {
return response()->json([
'success' => false,
'message' => 'This statement is not a multi account request'
], 400);
}
if (!$statement->is_available) {
return response()->json([
'success' => false,
'message' => 'Statement files are not available for download'
], 404);
}
// Find ZIP file
$zipFiles = Storage::disk('local')->files("statements/{$statement->period_from}/multi_account/{$statementId}");
$zipFile = null;
foreach ($zipFiles as $file) {
if (pathinfo($file, PATHINFO_EXTENSION) === 'zip') {
$zipFile = $file;
break;
}
}
if (!$zipFile || !Storage::disk('local')->exists($zipFile)) {
return response()->json([
'success' => false,
'message' => 'ZIP file not found'
], 404);
}
$zipPath = Storage::disk('local')->path($zipFile);
$filename = basename($zipFile);
// Update download status
$statement->update([
'is_downloaded' => true,
'downloaded_at' => now()
]);
Log::info('Multi account ZIP downloaded', [
'statement_id' => $statementId,
'zip_file' => $zipFile,
'user_id' => auth()->id()
]);
return response()->download($zipPath, $filename, [
'Content-Type' => 'application/zip'
]);
} catch (Exception $e) {
Log::error('Failed to download multi account ZIP', [
'statement_id' => $statementId,
'error' => $e->getMessage()
]);
return response()->json([
'success' => false,
'message' => 'Failed to download ZIP file',
'error' => $e->getMessage()
], 500);
}
}
}