Files
webstatement/resources/views/statements/stmt.blade.php
Daeng Deni Mardaeni 9373325399 feat(webstatement): dukung endPeriod dan format folder baru untuk statement
- Ubah konstruksi path SFTP dan storage lokal agar konsisten dengan format folder baru `YYYYMMDD.YYYYMMDD`
- Tambahkan dukungan periode akhir (`endPeriod`) pada alur cetak dan ekspor statement, lengkap dengan propagasi ke view dan log
- Perkuat logging di controller dan job untuk audit proses, serta sesuaikan penamaan file pada jalur PRINT

Rincian Perubahan
- PrintStatementController.php
  - Ganti path SFTP awal dari `{$period_from}/{$branch_code}/{$account_number}_{$period_from}.pdf` menjadi:
    - `{$periodPath}/PRINT/{$branch_code}/{$account_number}.1.pdf` pada cek file awal
    - Gunakan `$periodPath = formatPeriodForFolder($statement->period_from)` untuk semua referensi path
  - Iterasi ketersediaan periode:
    - Gunakan `formatPeriodForFolder($periodFormatted)` saat membentuk `periodPath` dalam loop bulan
  - Generate atau fetch statement:
    - Ubah path menjadi `{$periodPath}/{$branch_code}/{$account_number}_{$period}.pdf` untuk konsistensi
  - ZIP multi-periode:
    - Cari file ZIP pada `statements/{$periodPath}/multi_account/{$statementId}` sesuai format folder baru
  - Variabel periode:
    - Tambahkan `$endPeriod = $statement->period_to ?? $period` dan propagasikan ke:
      - Pemanggilan `generateStatementPdf($norek, $period, $endPeriod, ...)`
      - View `statements.stmt` melalui `compact(..., 'endPeriod')`
    - Perbarui logging untuk menampilkan `endPeriod`
  - Generate PDF:
    - Tandai storage path menjadi `statements/{$periodPath}/{$norek}`
    - Ubah signature: `generateStatementPdf($norek, $period, $endPeriod, ...)`
  - Akses file lokal/SFTP:
    - Ubah path storage menjadi `statements/{$periodPath}/{$account->branch_code}/{$filename}`
    - Penyesuaian delete path: `statements/{$periodPath}/{$norek}/{$filename}`

- ExportStatementPeriodJob.php
  - Tambah properti dan parameter konstruktor: `$endPeriod`
  - Ubah inisialisasi periode:
    - Ganti `calculatePeriodDates()` menjadi `formatPeriodForFolder()` (metode internal yang menetapkan `startDate` dan `endDate`)
    - Jika `$endPeriod` diisi, jadikan akhir bulan dari `endPeriod` sebagai `endDate` pemrosesan
  - Render view:
    - Tambahkan `endPeriod` ke `compact(...)` agar view mengetahui batas periode akhir
  - Storage path:
    - Gunakan `formatPeriodForFolder($this->period)` untuk path `statements/{$periodPath}/{$account->branch_code}`
  - Controller dispatch:
    - Ubah pemanggilan job menjadi `ExportStatementPeriodJob::dispatch($statementId, $accountNumber, $period, $endPeriod, $balance, $clientName)`

- resources/views/statements/stmt.blade.php
  - Periode:
    - Hitung `periodDates` via `calculatePeriodDates($period)`
    - Jika `endPeriod` ada, gunakan `calculatePeriodDates($endPeriod)` sebagai referensi `endDate`
  - Data customer:
    - Gunakan `$customer` langsung, bukan `$account->customer`
    - Kondisional alamat berdasarkan `stmt_sent_type == 'BY.MAIL.TO.DOM.ADDR'`:
      - Utamakan `l_dom_street` jika tersedia, fallback ke `address`
      - Susun RT/RW/kelurahan/kota/provinsi/kode pos sesuai preferensi pengiriman
  - Format angka:
    - Penyesuaian spasi dan casting `(float)` untuk konsistensi number_format
  - Logging:
    - Tambahkan informasi hasil perhitungan period dates untuk audit
2025-11-27 18:15:33 +07:00

596 lines
22 KiB
PHP

<!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;
word-wrap: break-word;
word-break: break-word;
white-space: normal;
overflow-wrap: break-word;
hyphens: auto;
}
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;
}
/* Menghilangkan padding dan margin untuk baris narrative tambahan */
tbody tr td {
padding: 5px;
}
/* Khusus untuk baris narrative tambahan - tanpa padding/margin */
tbody tr.narrative-line td {
padding: 2px 5px;
margin: 0;
line-height: 1;
}
/* Column width classes */
.col-date {
width: 10%;
text-align: center;
}
.col-desc {
width: 25%;
min-width: 150px;
max-width: 150px;
}
.col-valuta {
width: 10%;
text-align: center;
}
.col-referensi {
width: 15%;
}
.col-debet,
.col-kredit {
width: 12.5%;
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;
$linePerPage = 23;
@endphp
@php
// Hitung tanggal periode berdasarkan $period
$periodDates = calculatePeriodDates($period);
// Jika endPeriod ada, gunakan endPeriod sebagai batas akhir, jika tidak, gunakan period
$endPeriodDate = $endPeriod ? calculatePeriodDates($endPeriod) : $periodDates;
$startDate = $periodDates['start'];
$endDate = $endPeriodDate['end'] ?? $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) > 30) {
$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 ($linePerPage lines per page)
$totalPages = ceil($totalLines / $linePerPage);
$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&amp;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>{{ $customer->name }}</p>
@if ($account->stmt_sent_type == 'BY.MAIL.TO.DOM.ADDR')
<p>{{ $customer->l_dom_street ?? $customer->address }}</p>
<p>{{ $customer->district }}
{{ ($customer->ktp_rt ?: $customer->home_rt) ? 'RT ' . ($customer->ktp_rt ?: $customer->home_rt) : '' }}
{{ ($customer->ktp_rw ?: $customer->home_rw) ? 'RW ' . ($customer->ktp_rw ?: $customer->home_rw) : '' }}
</p>
<p>{{ trim($customer->city . ' ' . ($customer->province ? getProvinceCoreName($customer->province) . ' ' : '') . ($customer->postal_code ?? '')) }}
@else
<p>{{ $customer->address }}</p>
<p>{{ $customer->district }}
{{ ($customer->ktp_rt ?: $customer->home_rt) ? 'RT ' . ($customer->ktp_rt ?: $customer->home_rt) : '' }}
{{ ($customer->ktp_rw ?: $customer->home_rw) ? 'RW ' . ($customer->ktp_rw ?: $customer->home_rw) : '' }}
</p>
<p>{{ trim($customer->city . ' ' . ($customer->province ? getProvinceCoreName($customer->province) . ' ' : '') . ($customer->postal_code ?? '')) }}
@endif
</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="text-left 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">&nbsp;</td>
<td><strong>Saldo Awal Bulan</strong></td>
<td class="text-center">&nbsp;</td>
<td class="text-right">&nbsp;</td>
<td class="text-right">&nbsp;</td>
<td class="text-right">
<strong>{{ number_format((float) $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) > 30) {
$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">{{ substr($row->actual_date, 0, 10) }}</td>
<td>{{ str_replace(['[', ']'], ' ', $narrativeLines[0] ?? '') }}</td>
<td>{{ $row->reference_number }}</td>
<td class="text-right">
{{ $debit > 0 ? number_format((float) $debit, 2, ',', '.') : '' }}</td>
<td class="text-right">
{{ $kredit > 0 ? number_format((float) $kredit, 2, ',', '.') : '' }}
</td>
<td class="text-right">{{ number_format((float) $saldo, 2, ',', '.') }}</td>
</tr>
@for ($i = 1; $i < count($narrativeLines); $i++)
<tr class="narrative-line">
<td class="text-center"></td>
<td class="text-center"></td>
<td>{{ str_replace(['[', ']'], ' ', $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 >= $linePerPage && !$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">&nbsp;</td>
<td class="text-right">&nbsp;</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 }}
{{ ($account->customer->ktp_rt ?: $account->customer->home_rt) ? 'RT ' . ($account->customer->ktp_rt ?: $account->customer->home_rt) : '' }}
{{ ($account->customer->ktp_rw ?: $account->customer->home_rw) ? 'RW ' . ($account->customer->ktp_rw ?: $account->customer->home_rw) : '' }}
</p>
<p>{{ trim($account->customer->city . ' ' . ($account->customer->province ? getProvinceCoreName($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 < $linePerPage - $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>