feat(webstatement): tambahkan fitur pembuatan, penyimpanan, dan pengunduhan PDF statement
- **Fitur Baru:**
- Menambahkan kemampuan untuk membuat PDF statement secara dinamis menggunakan Browsershot.
- Fitur penyimpanan otomatis PDF ke dalam local storage dengan struktur direktori berdasarkan periode dan account number.
- Menyediakan fitur unduhan langsung dari storage atau melalui preview di browser.
- Mendukung penghapusan PDF dari storage dengan log terintegrasi.
- **Perubahan pada Controller:**
- Ditambah method baru `generated` untuk membangun PDF atau tampilan HTML statement.
- Integrasi penghitungan periode saldo (`calculateSaldoPeriod`) untuk menghasilkan data laporan yang lebih akurat.
- Perubahan pada `printStatementRekening` untuk mendukung pengiriman objek statement secara penuh.
- Menambahkan method tambahan untuk preview, download, dan delete PDF langsung dari storage.
- **Validasi Permintaan:**
- Menambah validasi wajib pada `branch_code` untuk memastikan data cabang sesuai.
- Menyesuaikan logika validasi di `PrintStatementRequest` untuk mendukung input cabang dan parameter lain.
- **Cakupan Logging:**
- Meningkatkan logging pada setiap proses penting:
- Mulai dari validasi data, proses pembuatan PDF, hingga penyimpanan.
- Deteksi error secara spesifik pada setiap tahapan proses.
- Menambah log debugging untuk nama file, ukuran file, dan path penyimpanan.
- **Peningkatan pada UI:**
- Penyesuaian field `branch_code` pada form UI untuk menggantikan `branch_id` dengan validasi error yang lebih eksplisit.
- Membuat halaman baru untuk tampilan preview PDF di interface.
- **Optimisasi dan Refaktor:**
- Grouping import library untuk meningkatkan keterbacaan.
- Manajemen direktori penyimpanan PDF dipastikan berjalan dinamis dan fleksibel.
- Penghilangan redundansi logika pada proses pencarian saldo awal bulan.
Signed-off-by: Daeng Deni Mardaeni <ddeni05@gmail.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Modules\Webstatement\Http\Controllers;
|
namespace Modules\Webstatement\Http\Controllers;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
@@ -17,7 +17,9 @@ use Modules\Webstatement\{
|
|||||||
Models\AccountBalance,
|
Models\AccountBalance,
|
||||||
Jobs\ExportStatementPeriodJob
|
Jobs\ExportStatementPeriodJob
|
||||||
};
|
};
|
||||||
|
use Modules\Webstatement\Models\ProcessedStatement;
|
||||||
use ZipArchive;
|
use ZipArchive;
|
||||||
|
use Spatie\Browsershot\Browsershot;
|
||||||
|
|
||||||
class PrintStatementController extends Controller
|
class PrintStatementController extends Controller
|
||||||
{
|
{
|
||||||
@@ -80,12 +82,17 @@ use ZipArchive;
|
|||||||
try {
|
try {
|
||||||
$validated = $request->validated();
|
$validated = $request->validated();
|
||||||
|
|
||||||
|
$validated['request_type'] = 'single_account'; // Default untuk request manual
|
||||||
|
if($validated['branch_code'] && $validated['stmt_sent_type']){
|
||||||
|
$validated['request_type'] = 'multi_account'; // Default untuk request manual
|
||||||
|
}
|
||||||
|
|
||||||
// Add user tracking data dan field baru untuk single account request
|
// Add user tracking data dan field baru untuk single account request
|
||||||
$validated['user_id'] = Auth::id();
|
$validated['user_id'] = Auth::id();
|
||||||
$validated['created_by'] = Auth::id();
|
$validated['created_by'] = Auth::id();
|
||||||
$validated['ip_address'] = $request->ip();
|
$validated['ip_address'] = $request->ip();
|
||||||
$validated['user_agent'] = $request->userAgent();
|
$validated['user_agent'] = $request->userAgent();
|
||||||
$validated['request_type'] = 'single_account'; // Default untuk request manual
|
|
||||||
$validated['status'] = 'pending'; // Status awal
|
$validated['status'] = 'pending'; // Status awal
|
||||||
$validated['authorization_status'] = 'approved'; // Status otorisasi awal
|
$validated['authorization_status'] = 'approved'; // Status otorisasi awal
|
||||||
$validated['total_accounts'] = 1; // Untuk single account
|
$validated['total_accounts'] = 1; // Untuk single account
|
||||||
@@ -93,7 +100,7 @@ use ZipArchive;
|
|||||||
$validated['success_count'] = 0;
|
$validated['success_count'] = 0;
|
||||||
$validated['failed_count'] = 0;
|
$validated['failed_count'] = 0;
|
||||||
$validated['stmt_sent_type'] = $request->input('stmt_sent_type') ? implode(',', $request->input('stmt_sent_type')) : '';
|
$validated['stmt_sent_type'] = $request->input('stmt_sent_type') ? implode(',', $request->input('stmt_sent_type')) : '';
|
||||||
$validated['branch_code'] = $branch_code; // Awal tidak tersedia
|
$validated['branch_code'] = $validated['branch_code'] ?? $branch_code; // Awal tidak tersedia
|
||||||
|
|
||||||
// Create the statement log
|
// Create the statement log
|
||||||
$statement = PrintStatementLog::create($validated);
|
$statement = PrintStatementLog::create($validated);
|
||||||
@@ -109,7 +116,7 @@ use ZipArchive;
|
|||||||
// Process statement availability check
|
// Process statement availability check
|
||||||
$this->checkStatementAvailability($statement);
|
$this->checkStatementAvailability($statement);
|
||||||
if(!$statement->is_available){
|
if(!$statement->is_available){
|
||||||
$this->printStatementRekening($statement->account_number,$statement->period_from,$statement->period_to,$statement->stmt_sent_type);
|
$this->printStatementRekening($statement);
|
||||||
}
|
}
|
||||||
|
|
||||||
$statement = PrintStatementLog::find($statement->id);
|
$statement = PrintStatementLog::find($statement->id);
|
||||||
@@ -784,9 +791,493 @@ use ZipArchive;
|
|||||||
return "statement_{$accountNumber}_{$statement->period_from}.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($norek, $period='202505', $format = 'html'){
|
||||||
|
try {
|
||||||
|
DB::beginTransaction();
|
||||||
|
|
||||||
function printStatementRekening($accountNumber, $period, $periodTo = null, $stmtSentType = null) {
|
Log::info('Generating statement', [
|
||||||
$period = $period ?? date('Ym');
|
'account_number' => $norek,
|
||||||
|
'period' => $period,
|
||||||
|
'format' => $format,
|
||||||
|
'user_id' => Auth::id()
|
||||||
|
]);
|
||||||
|
|
||||||
|
$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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
|
||||||
|
// 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}");
|
||||||
|
|
||||||
|
// 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(60)
|
||||||
|
->save($tempPath);
|
||||||
|
|
||||||
|
// Verifikasi file berhasil dibuat
|
||||||
|
if (!file_exists($tempPath)) {
|
||||||
|
throw new Exception('PDF file was not created successfully');
|
||||||
|
}
|
||||||
|
|
||||||
|
$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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate PDF dan return sebagai stream untuk preview
|
||||||
|
*
|
||||||
|
* @param string $norek Nomor rekening
|
||||||
|
* @param string $period Periode
|
||||||
|
* @return \Illuminate\Http\Response
|
||||||
|
*/
|
||||||
|
public function previewPdf($norek, $period = '202505')
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
Log::info('Generating PDF preview', [
|
||||||
|
'account_number' => $norek,
|
||||||
|
'period' => $period,
|
||||||
|
'user_id' => Auth::id()
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Generate PDF dengan format parameter
|
||||||
|
$response = $this->generated($norek, $period, 'pdf');
|
||||||
|
|
||||||
|
// Ubah header untuk preview di browser
|
||||||
|
return $response->header('Content-Disposition', 'inline; filename="statement_preview.pdf"');
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
Log::error('Failed to generate PDF preview', [
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
'account_number' => $norek,
|
||||||
|
'period' => $period
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Failed to generate PDF preview',
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function printStatementRekening($statement) {
|
||||||
|
$accountNumber = $statement->account_number;
|
||||||
|
$period = $statement->period_from ?? date('Ym');
|
||||||
$balance = AccountBalance::where('account_number', $accountNumber)
|
$balance = AccountBalance::where('account_number', $accountNumber)
|
||||||
->when($period === '202505', function($query) {
|
->when($period === '202505', function($query) {
|
||||||
return $query->where('period', '>=', '20250512')
|
return $query->where('period', '>=', '20250512')
|
||||||
@@ -810,7 +1301,7 @@ use ZipArchive;
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Dispatch the job
|
// Dispatch the job
|
||||||
$job = ExportStatementPeriodJob::dispatch($accountNumber, $period, $balance, $clientName);
|
$job = ExportStatementPeriodJob::dispatch($statement, $accountNumber, $period, $balance, $clientName);
|
||||||
|
|
||||||
Log::info("Statement export job dispatched successfully", [
|
Log::info("Statement export job dispatched successfully", [
|
||||||
'job_id' => $job->job_id ?? null,
|
'job_id' => $job->job_id ?? null,
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ class PrintStatementRequest extends FormRequest
|
|||||||
public function rules(): array
|
public function rules(): array
|
||||||
{
|
{
|
||||||
$rules = [
|
$rules = [
|
||||||
|
'branch_code' => ['required', 'string'],
|
||||||
// account_number required jika stmt_sent_type tidak diisi atau kosong
|
// account_number required jika stmt_sent_type tidak diisi atau kosong
|
||||||
'account_number' => [
|
'account_number' => [
|
||||||
function ($attribute, $value, $fail) {
|
function ($attribute, $value, $fail) {
|
||||||
@@ -99,6 +100,8 @@ class PrintStatementRequest extends FormRequest
|
|||||||
public function messages(): array
|
public function messages(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
|
'branch_code.required' => 'Branch code is required',
|
||||||
|
'branch_code.string' => 'Branch code must be a string',
|
||||||
'account_number.required' => 'Account number is required when statement type is not specified',
|
'account_number.required' => 'Account number is required when statement type is not specified',
|
||||||
'stmt_sent_type.*.in' => 'Invalid statement type selected',
|
'stmt_sent_type.*.in' => 'Invalid statement type selected',
|
||||||
'period_from.required' => 'Period is required',
|
'period_from.required' => 'Period is required',
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ class ExportStatementPeriodJob implements ShouldQueue
|
|||||||
protected $startDate;
|
protected $startDate;
|
||||||
protected $endDate;
|
protected $endDate;
|
||||||
protected $toCsv;
|
protected $toCsv;
|
||||||
|
protected $statement;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new job instance.
|
* Create a new job instance.
|
||||||
@@ -43,8 +44,9 @@ class ExportStatementPeriodJob implements ShouldQueue
|
|||||||
* @param string $client
|
* @param string $client
|
||||||
* @param string $disk
|
* @param string $disk
|
||||||
*/
|
*/
|
||||||
public function __construct(string $account_number, string $period, string $saldo, string $client = '', string $disk = 'local', bool $toCsv = true)
|
public function __construct($statement, string $account_number, string $period, string $saldo, string $client = '', string $disk = 'local', bool $toCsv = true)
|
||||||
{
|
{
|
||||||
|
$this->statement = $statement;
|
||||||
$this->account_number = $account_number;
|
$this->account_number = $account_number;
|
||||||
$this->period = $period;
|
$this->period = $period;
|
||||||
$this->saldo = $saldo;
|
$this->saldo = $saldo;
|
||||||
@@ -170,10 +172,7 @@ class ExportStatementPeriodJob implements ShouldQueue
|
|||||||
});
|
});
|
||||||
|
|
||||||
if($entry){
|
if($entry){
|
||||||
$printLog = PrintStatementLog::where('account_number', $this->account_number)
|
$printLog = PrintStatementLog::find($this->statement->id);
|
||||||
->where('period_from', $this->period)
|
|
||||||
->latest()
|
|
||||||
->first();
|
|
||||||
if($printLog){
|
if($printLog){
|
||||||
$printLog->update(['is_generated' => true]);
|
$printLog->update(['is_generated' => true]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,19 +22,19 @@
|
|||||||
<div class="grid grid-cols-1 gap-5">
|
<div class="grid grid-cols-1 gap-5">
|
||||||
@if ($multiBranch)
|
@if ($multiBranch)
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label required" for="branch_id">Branch/Cabang</label>
|
<label class="form-label required" for="branch_code">Branch/Cabang</label>
|
||||||
<select
|
<select
|
||||||
class="input form-control tomselect @error('branch_id') border-danger bg-danger-light @enderror"
|
class="input form-control tomselect @error('branch_code') border-danger bg-danger-light @enderror"
|
||||||
id="branch_id" name="branch_id" required>
|
id="branch_code" name="branch_code" required>
|
||||||
<option value="">Pilih Branch/Cabang</option>
|
<option value="">Pilih Branch/Cabang</option>
|
||||||
@foreach ($branches as $branchOption)
|
@foreach ($branches as $branchOption)
|
||||||
<option value="{{ $branchOption->code }}"
|
<option value="{{ $branchOption->code }}"
|
||||||
{{ old('branch_id', $statement->branch_id ?? ($branch->code ?? '')) == $branchOption->code ? 'selected' : '' }}>
|
{{ old('branch_code', $statement->branch_code ?? ($branch->code ?? '')) == $branchOption->code ? 'selected' : '' }}>
|
||||||
{{ $branchOption->code }} - {{ $branchOption->name }}
|
{{ $branchOption->code }} - {{ $branchOption->name }}
|
||||||
</option>
|
</option>
|
||||||
@endforeach
|
@endforeach
|
||||||
</select>
|
</select>
|
||||||
@error('branch_id')
|
@error('branch_code')
|
||||||
<div class="text-sm alert text-danger">{{ $message }}</div>
|
<div class="text-sm alert text-danger">{{ $message }}</div>
|
||||||
@enderror
|
@enderror
|
||||||
</div>
|
</div>
|
||||||
@@ -43,7 +43,10 @@
|
|||||||
<label class="form-label" for="branch_display">Branch/Cabang</label>
|
<label class="form-label" for="branch_display">Branch/Cabang</label>
|
||||||
<input type="text" class="input form-control" id="branch_display"
|
<input type="text" class="input form-control" id="branch_display"
|
||||||
value="{{ $branch->code ?? '' }} - {{ $branch->name ?? '' }}" readonly>
|
value="{{ $branch->code ?? '' }} - {{ $branch->name ?? '' }}" readonly>
|
||||||
<input type="hidden" name="branch_id" value="{{ $branch->code ?? '' }}">
|
<input type="hidden" name="branch_code" value="{{ $branch->code ?? '' }}">
|
||||||
|
@error('branch_code')
|
||||||
|
<div class="text-sm alert text-danger">{{ $message }}</div>
|
||||||
|
@enderror
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
|
|||||||
555
resources/views/statements/stmt.blade.php
Normal file
555
resources/views/statements/stmt.blade.php
Normal file
@@ -0,0 +1,555 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Rekening Tabungan</title>
|
||||||
|
<style>
|
||||||
|
@page {
|
||||||
|
size: A4;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background-color: #fff;
|
||||||
|
width: 210mm;
|
||||||
|
height: 297mm;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
width: 190mm;
|
||||||
|
min-height: 277mm;
|
||||||
|
margin: 10mm auto;
|
||||||
|
background: #ffffff;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
padding: 10mm;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.watermark {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
opacity: 1;
|
||||||
|
z-index: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.watermark img {
|
||||||
|
margin: 0px 50px;
|
||||||
|
width: 85%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-wrapper {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
/* Ensure content is above watermark */
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-end;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
height: 45px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header .title {
|
||||||
|
text-align: left;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #0056b3;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header .title h1 {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header .logo {
|
||||||
|
text-align: center;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header .logo img {
|
||||||
|
max-height: 50px;
|
||||||
|
margin-right: 10px
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-section {
|
||||||
|
text-transform: uppercase;
|
||||||
|
padding: 10px 0;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-section .column {
|
||||||
|
width: 48%;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-section .column p {
|
||||||
|
margin: 5px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-section {
|
||||||
|
padding-top: 15px;
|
||||||
|
flex: 1;
|
||||||
|
/* Allow table section to grow */
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table th,
|
||||||
|
table td {
|
||||||
|
padding: 5px;
|
||||||
|
text-align: left;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table th {
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-weight: bold;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
table td.text-right {
|
||||||
|
text-align: right !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
table td.text-center {
|
||||||
|
text-align: center !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
table td.text-left {
|
||||||
|
text-align: left !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
font-size: 10px;
|
||||||
|
color: #666;
|
||||||
|
margin-top: auto;
|
||||||
|
position: relative;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 1;
|
||||||
|
/* Ensure footer is above watermark */
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer p {
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer .highlight {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-25 {
|
||||||
|
margin-left: 25px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.same-size {
|
||||||
|
display: inline-block;
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sponsor {
|
||||||
|
border-top: 1.5px solid black;
|
||||||
|
padding: 10px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight {
|
||||||
|
margin: 10px 0px 0px 0px;
|
||||||
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody td {
|
||||||
|
border-bottom: none;
|
||||||
|
border-top: none;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Column width classes */
|
||||||
|
.col-date {
|
||||||
|
width: 10%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col-desc {
|
||||||
|
width: 25%;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.col-valuta {
|
||||||
|
width: 10%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col-referensi {
|
||||||
|
width: 20%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col-debet,
|
||||||
|
.col-kredit {
|
||||||
|
width: 10%;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col-saldo {
|
||||||
|
width: 15%;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-number {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 5mm;
|
||||||
|
right: 10mm;
|
||||||
|
font-size: 10px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
body {
|
||||||
|
width: 210mm;
|
||||||
|
height: 297mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
margin: 0;
|
||||||
|
border: initial;
|
||||||
|
border-radius: initial;
|
||||||
|
width: initial;
|
||||||
|
min-height: initial;
|
||||||
|
box-shadow: initial;
|
||||||
|
background: initial;
|
||||||
|
page-break-after: always;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container:last-of-type {
|
||||||
|
page-break-after: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
@php
|
||||||
|
$saldo = $saldoAwalBulan->actual_balance ?? 0;
|
||||||
|
$totalDebit = 0;
|
||||||
|
$totalKredit = 0;
|
||||||
|
$line = 1;
|
||||||
|
@endphp
|
||||||
|
@php
|
||||||
|
// Hitung tanggal periode berdasarkan $period
|
||||||
|
$periodDates = calculatePeriodDates($period);
|
||||||
|
$startDate = $periodDates['start'];
|
||||||
|
$endDate = $periodDates['end'];
|
||||||
|
|
||||||
|
// Log hasil perhitungan
|
||||||
|
\Log::info('Period dates calculated', [
|
||||||
|
'period' => $period,
|
||||||
|
'start_date' => $startDate->format('d/m/Y'),
|
||||||
|
'end_date' => $endDate->format('d/m/Y'),
|
||||||
|
]);
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
@php
|
||||||
|
// Calculate total pages based on actual line count
|
||||||
|
$totalLines = 0;
|
||||||
|
foreach ($stmtEntries as $entry) {
|
||||||
|
// Split narrative into multiple lines of approximately 35 characters, breaking at word boundaries
|
||||||
|
$narrative = $entry->description ?? '';
|
||||||
|
$words = explode(' ', $narrative);
|
||||||
|
|
||||||
|
$narrativeLineCount = 0;
|
||||||
|
$currentLine = '';
|
||||||
|
|
||||||
|
foreach ($words as $word) {
|
||||||
|
if (strlen($currentLine . ' ' . $word) > 35) {
|
||||||
|
$narrativeLineCount++;
|
||||||
|
$currentLine = $word;
|
||||||
|
} else {
|
||||||
|
$currentLine .= ($currentLine ? ' ' : '') . $word;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($currentLine) {
|
||||||
|
$narrativeLineCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each entry takes at least one line for the main data + narrative lines + gap row
|
||||||
|
$totalLines += $narrativeLineCount; // +1 for gap row
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add 1 for the "Saldo Awal Bulan" row
|
||||||
|
$totalLines += 1;
|
||||||
|
|
||||||
|
// Calculate total pages (19 lines per page)
|
||||||
|
$totalPages = ceil($totalLines / 19);
|
||||||
|
$pageNumber = 0;
|
||||||
|
|
||||||
|
$footerContent =
|
||||||
|
'
|
||||||
|
<div class="footer">
|
||||||
|
<p class="sponsor">Belanja puas di Electronic City! Dapatkan cashback hingga 250 ribu dan nikmati makan enak dengan cashback hingga 25 ribu. Bayar pakai QRIS AGI. S&K berlaku. Info lengkap: www.arthagraha.com</p>
|
||||||
|
|
||||||
|
<p class="sponsor">Waspada dalam bertransaksi QRIS! Periksa kembali identitas penjual & nominal pembayaran sebelum melanjutkan transaksi. Info terkait Bank Artha Graha Internasional, kunjungi website www.arthagraha.com</p>
|
||||||
|
|
||||||
|
<div class="highlight">
|
||||||
|
<img src="' .
|
||||||
|
public_path('assets/media/images/banner-footer.png') .
|
||||||
|
'" alt="Logo Bank" style="width: 100%; height: auto;">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
';
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="watermark">
|
||||||
|
<img src="{{ public_path('assets/media/images/watermark.png') }}" alt="Watermark">
|
||||||
|
</div>
|
||||||
|
<div class="content-wrapper">
|
||||||
|
<!-- Header Section -->
|
||||||
|
<div class="header">
|
||||||
|
<div class="logo">
|
||||||
|
<img src="{{ public_path('assets/media/images/logo-arthagraha.png') }}" alt="Logo Bank">
|
||||||
|
<img src="{{ public_path('assets/media/images/logo-agi.png') }}" alt="Logo Bank">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Bank Information Section -->
|
||||||
|
<div class="info-section">
|
||||||
|
<div class="column">
|
||||||
|
<p>{{ $branch->name }}</p>
|
||||||
|
<p style="text-transform: capitalize">Kepada</p>
|
||||||
|
<p>{{ $account->customer->name }}</p>
|
||||||
|
<p>{{ $account->customer->address }}</p>
|
||||||
|
<p>{{ $account->customer->district }}</p>
|
||||||
|
<p>{{ ($account->customer->city ? $account->customer->city . ' ' : '') . ($account->customer->province ? $account->customer->province . ' ' : '') . ($account->customer->postal_code ?? '') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div style="text-transform: capitalize;" class="column">
|
||||||
|
<p style="padding-left:50px"><span class="same-size">Periode Statement </span>:
|
||||||
|
{{ dateFormat($startDate) }} <span style="text-transform:lowercase !important">s/d</span>
|
||||||
|
{{ dateFormat($endDate) }}</p>
|
||||||
|
<p style="padding-left:50px"><span class="same-size">Nomor Rekening</span>:
|
||||||
|
{{ $account->account_number }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Table Section -->
|
||||||
|
<div class="table-section">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr
|
||||||
|
style="@if ($headerTableBg) background-image: url('data:image/png;base64,{{ $headerTableBg }}'); background-repeat: no-repeat; background-size: cover; background-position: center; @else background-color: #0056b3; @endif height: 30px;">
|
||||||
|
<th class="col-date">Tanggal</th>
|
||||||
|
<th class="col-valuta">Tanggal<br>Valuta</th>
|
||||||
|
<th class="text-left col-desc">Keterangan</th>
|
||||||
|
<th class="col-referensi">Referensi</th>
|
||||||
|
<th class="col-debet">Debet</th>
|
||||||
|
<th class="col-kredit">Kredit</th>
|
||||||
|
<th class="col-saldo">Saldo</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="text-center"></td>
|
||||||
|
<td class="text-center"> </td>
|
||||||
|
<td><strong>Saldo Awal Bulan</strong></td>
|
||||||
|
<td class="text-center"> </td>
|
||||||
|
<td class="text-right"> </td>
|
||||||
|
<td class="text-right"> </td>
|
||||||
|
<td class="text-right">
|
||||||
|
<strong>{{ number_format($saldoAwalBulan->actual_balance, 2, ',', '.') }}</strong>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
@foreach ($stmtEntries as $row)
|
||||||
|
@php
|
||||||
|
$debit = $row->transaction_amount < 0 ? abs($row->transaction_amount) : 0;
|
||||||
|
$kredit = $row->transaction_amount > 0 ? $row->transaction_amount : 0;
|
||||||
|
$saldo += $kredit - $debit;
|
||||||
|
$totalDebit += $debit;
|
||||||
|
$totalKredit += $kredit;
|
||||||
|
|
||||||
|
// Split narrative into multiple lines of approximately 35 characters, breaking at word boundaries
|
||||||
|
$narrative = $row->description ?? '';
|
||||||
|
$words = explode(' ', $narrative);
|
||||||
|
$narrativeLines = [];
|
||||||
|
$currentLine = '';
|
||||||
|
foreach ($words as $word) {
|
||||||
|
if (strlen($currentLine . ' ' . $word) > 35) {
|
||||||
|
$narrativeLines[] = trim($currentLine);
|
||||||
|
$currentLine = $word;
|
||||||
|
} else {
|
||||||
|
$currentLine .= ($currentLine ? ' ' : '') . $word;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($currentLine) {
|
||||||
|
$narrativeLines[] = trim($currentLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
@endphp
|
||||||
|
<tr>
|
||||||
|
<td class="text-center">{{ date('d/m/Y', strtotime($row->transaction_date)) }}</td>
|
||||||
|
<td class="text-center">{{ date('d/m/Y', strtotime($row->actual_date)) }}</td>
|
||||||
|
<td>{{ $narrativeLines[0] ?? '' }}</td>
|
||||||
|
<td>{{ $row->reference_number }}</td>
|
||||||
|
<td class="text-right">{{ $debit > 0 ? number_format($debit, 2, ',', '.') : '' }}</td>
|
||||||
|
<td class="text-right">{{ $kredit > 0 ? number_format($kredit, 2, ',', '.') : '' }}
|
||||||
|
</td>
|
||||||
|
<td class="text-right">{{ number_format($saldo, 2, ',', '.') }}</td>
|
||||||
|
</tr>
|
||||||
|
@for ($i = 1; $i < count($narrativeLines); $i++)
|
||||||
|
<tr>
|
||||||
|
<td class="text-center"></td>
|
||||||
|
<td class="text-center"></td>
|
||||||
|
<td>{{ $narrativeLines[$i] }}</td>
|
||||||
|
<td class="text-center"></td>
|
||||||
|
<td class="text-right"></td>
|
||||||
|
<td class="text-right"></td>
|
||||||
|
<td class="text-right"></td>
|
||||||
|
</tr>
|
||||||
|
@endfor
|
||||||
|
@php $line += count($narrativeLines); @endphp
|
||||||
|
<!-- Add a gap row -->
|
||||||
|
<tr class="gap-row">
|
||||||
|
<td class="text-center"></td>
|
||||||
|
<td class="text-center"></td>
|
||||||
|
<td class="text-center"></td>
|
||||||
|
<td></td>
|
||||||
|
<td class="text-right"></td>
|
||||||
|
<td class="text-right"></td>
|
||||||
|
<td class="text-right"></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
@if ($line >= 19 && !$loop->last)
|
||||||
|
@php
|
||||||
|
$line = 0;
|
||||||
|
$pageNumber++;
|
||||||
|
@endphp
|
||||||
|
<tr>
|
||||||
|
<td class="text-center"></td>
|
||||||
|
<td class="text-center"></td>
|
||||||
|
<td><strong>Pindah ke Halaman Berikutnya</strong></td>
|
||||||
|
<td class="text-center"></td>
|
||||||
|
<td class="text-right"> </td>
|
||||||
|
<td class="text-right"> </td>
|
||||||
|
<td class="text-right"></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{!! $footerContent !!}
|
||||||
|
</div>
|
||||||
|
<div class="page-number">Halaman {{ $pageNumber }} dari {{ $totalPages }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="container">
|
||||||
|
<div class="watermark">
|
||||||
|
<img src="{{ public_path('assets/media/images/watermark.png') }}" alt="Watermark">
|
||||||
|
</div>
|
||||||
|
<div class="content-wrapper">
|
||||||
|
<!-- Header Section for continuation page -->
|
||||||
|
<div class="header">
|
||||||
|
<div class="logo">
|
||||||
|
<img src="{{ public_path('assets/media/images/logo-arthagraha.png') }}" alt="Logo Bank">
|
||||||
|
<img src="{{ public_path('assets/media/images/logo-agi.png') }}" alt="Logo Bank">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Bank Information Section for continuation page -->
|
||||||
|
<div class="info-section">
|
||||||
|
<div class="column">
|
||||||
|
<p>{{ $branch->name }}</p>
|
||||||
|
<p style="text-transform: capitalize">Kepada</p>
|
||||||
|
<p>{{ $account->customer->name }}</p>
|
||||||
|
<p>{{ $account->customer->address }}</p>
|
||||||
|
<p>{{ $account->customer->district }}</p>
|
||||||
|
<p>{{ ($account->customer->city ? $account->customer->city . ' ' : '') . ($account->customer->province ? $account->customer->province . ' ' : '') . ($account->customer->postal_code ?? '') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div style="text-transform: capitalize;" class="column">
|
||||||
|
<p style="padding-left:50px"><span class="same-size">Periode Statement </span>:
|
||||||
|
{{ dateFormat($startDate) }} <span style="text-transform:lowercase !important">s/d</span>
|
||||||
|
{{ dateFormat($endDate) }}</p>
|
||||||
|
<p style="padding-left:50px"><span class="same-size">Nomor Rekening</span>:
|
||||||
|
{{ $account->account_number }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-section">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr
|
||||||
|
style="@if ($headerTableBg) background-image: url('data:image/png;base64,{{ $headerTableBg }}'); background-repeat: no-repeat; background-size: cover; background-position: center; @else background-color: #0056b3; @endif height: 30px;">
|
||||||
|
<th class="col-date">Tanggal</th>
|
||||||
|
<th class="col-valuta">Tanggal<br>Valuta</th>
|
||||||
|
<th class="text-left col-desc">Keterangan</th>
|
||||||
|
<th class="col-referensi">Referensi</th>
|
||||||
|
<th class="col-debet">Debet</th>
|
||||||
|
<th class="col-kredit">Kredit</th>
|
||||||
|
<th class="col-saldo">Saldo</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@endif
|
||||||
|
@endforeach
|
||||||
|
@for ($i = 0; $i < 19 - $line; $i++)
|
||||||
|
<tr>
|
||||||
|
<td class="text-center"></td>
|
||||||
|
<td class="text-center"></td>
|
||||||
|
<td></td>
|
||||||
|
<td class="text-center"></td>
|
||||||
|
<td class="text-right"></td>
|
||||||
|
<td class="text-right"></td>
|
||||||
|
<td class="text-right"></td>
|
||||||
|
</tr>
|
||||||
|
@endfor
|
||||||
|
<tr>
|
||||||
|
<td class="text-center"></td>
|
||||||
|
<td class="text-center"></td>
|
||||||
|
<td><strong>Total Akhir</strong></td>
|
||||||
|
<td class="text-center"></td>
|
||||||
|
<td class="text-right"><strong>{{ number_format($totalDebit, 2, ',', '.') }}</strong></td>
|
||||||
|
<td class="text-right"><strong>{{ number_format($totalKredit, 2, ',', '.') }}</strong>
|
||||||
|
</td>
|
||||||
|
<td class="text-right"><strong>{{ number_format($saldo, 2, ',', '.') }}</strong></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer Section -->
|
||||||
|
{!! $footerContent !!}
|
||||||
|
</div>
|
||||||
|
<div class="page-number">Halaman {{ $pageNumber + 1 }} dari {{ $totalPages }}</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user