feat(webstatement): tambah fitur pengiriman statement ke email

- Menambahkan kolom `email` dan `email_sent_at` pada tabel `print_statement_logs`.
- Menambah endpoint baru untuk mengirim statement via email (`/send-email`).
- Mengupdate form request untuk validasi email pada `PrintStatementRequest`.
- Menambah logika pengiriman email dengan menggunakan `Mailable` (`StatementEmail`).
- Memvalidasi ketersediaan file statement sebelum dikirimkan via email.
- Menambahkan tombol baru pada tampilan frontend untuk opsi `Send to Email`.
- Mengupdate file zip untuk pengiriman email ketika ada statement dalam rentang waktu.
- Refaktor dan perbaikan minor pada kode terkait check statement availability.
- Menyesuaikan title menu pada `module.json` dari "Periode Statement" ke "Create Periode" dan "Print Statement" ke "Statement".

Signed-off-by: Daeng Deni Mardaeni <ddeni05@gmail.com>
This commit is contained in:
Daeng Deni Mardaeni
2025-05-13 16:44:22 +07:00
parent 8e5c2ce79e
commit 427d1291f8
10 changed files with 540 additions and 163 deletions

View File

@@ -3,13 +3,18 @@
namespace Modules\Webstatement\Http\Controllers;
use App\Http\Controllers\Controller;
use Carbon\Carbon;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Storage;
use Modules\Webstatement\Http\Requests\PrintStatementRequest;
use Modules\Webstatement\Models\PrintStatementLog;
use Log;
use Modules\Basicdata\Models\Branch;
use phpseclib3\Net\SFTP;
use Modules\Webstatement\Http\Requests\PrintStatementRequest;
use Modules\Webstatement\Mail\StatementEmail;
use Modules\Webstatement\Models\PrintStatementLog;
use ZipArchive;
class PrintStatementController extends Controller
{
@@ -23,15 +28,6 @@
return view('webstatement::statements.index', compact('branches'));
}
/**
* Show the form for creating a new statement request.
*/
public function create()
{
$branches = Branch::orderBy('name')->get();
return view('webstatement::statements.create', compact('branches',));
}
/**
* Store a newly created statement request.
*/
@@ -40,7 +36,7 @@
$validated = $request->validated();
// Add user tracking data
$validated['user_id'] = Auth::id();
$validated['user_id'] = Auth::id();
$validated['created_by'] = Auth::id();
$validated['ip_address'] = $request->ip();
$validated['user_agent'] = $request->userAgent();
@@ -55,6 +51,81 @@
->with('success', 'Statement request has been created successfully.');
}
/**
* 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.
* This is a placeholder method - implement according to your system.
*/
protected function checkStatementAvailability(PrintStatementLog $statement)
{
// This would be implemented based on your system's logic
// For example, checking an API or database for statement availability
$disk = Storage::disk('sftpStatement');
//format folder /periode/Print/branch_code/account_number.pdf
$filePath = "{$statement->period_from}/Print/{$statement->branch_code}/{$statement->account_number}.pdf";
// Check if the statement exists in the storage
if ($statement->is_period_range && $statement->period_to) {
$periodFrom = Carbon::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 . "/Print/{$statement->branch_code}/{$statement->account_number}.pdf";
if ($disk->exists($periodPath)) {
$availablePeriods[] = $periodFormatted;
} else {
$missingPeriods[] = $periodFormatted;
}
}
// If any period is missing, the statement is not available
if (count($missingPeriods) > 0) {
$notes = "Missing periods: " . implode(', ', $missingPeriods);
$statement->update([
'is_available' => false,
'remarks' => $notes,
'updated_by' => Auth::id()
]);
return;
} else {
// All periods are available
$statement->update([
'is_available' => true,
'updated_by' => Auth::id()
]);
return;
}
} else if ($disk->exists($filePath)) {
$statement->update([
'is_available' => true,
'updated_by' => Auth::id()
]);
return;
}
$statement->update([
'is_available' => false,
'updated_by' => Auth::id()
]);
return;
}
/**
* Display the specified statement.
*/
@@ -74,32 +145,32 @@
return back()->with('error', 'Statement is not available for download.');
}
/* if ($statement->authorization_status !== 'approved') {
return back()->with('error', 'Statement download requires authorization.');
}*/
/* if ($statement->authorization_status !== 'approved') {
return back()->with('error', 'Statement download requires authorization.');
}*/
// Update download status
$statement->update([
'is_downloaded' => true,
'downloaded_at' => now(),
'updated_by' => Auth::id()
'updated_by' => Auth::id()
]);
// Generate or fetch the statement file (implementation depends on your system)
$disk = Storage::disk('sftpStatement');
$disk = Storage::disk('sftpStatement');
$filePath = "{$statement->period_from}/Print/{$statement->branch_code}/{$statement->account_number}.pdf";
if ($statement->is_period_range && $statement->period_to) {
$periodFrom = \Carbon\Carbon::createFromFormat('Ym', $statement->period_from);
$periodTo = \Carbon\Carbon::createFromFormat('Ym', $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 = [];
$missingPeriods = [];
$availablePeriods = [];
for ($period = clone $periodFrom; $period->lte($periodTo); $period->addMonth()) {
$periodFormatted = $period->format('Ym');
$periodPath = $periodFormatted . "/Print/{$statement->branch_code}/{$statement->account_number}.pdf";
$periodPath = $periodFormatted . "/Print/{$statement->branch_code}/{$statement->account_number}.pdf";
if ($disk->exists($periodPath)) {
$availablePeriods[] = $periodFormatted;
@@ -120,11 +191,11 @@
}
// Create a new zip archive
$zip = new \ZipArchive();
if ($zip->open($zipFilePath, \ZipArchive::CREATE | \ZipArchive::OVERWRITE) === TRUE) {
$zip = new ZipArchive();
if ($zip->open($zipFilePath, ZipArchive::CREATE | ZipArchive::OVERWRITE) === true) {
// Add each available statement to the zip
foreach ($availablePeriods as $period) {
$filePath = "{$period}/Print/{$statement->branch_code}/{$statement->account_number}.pdf";
$filePath = "{$period}/Print/{$statement->branch_code}/{$statement->account_number}.pdf";
$localFilePath = storage_path("app/temp/{$statement->account_number}_{$period}.pdf");
// Download the file from SFTP to local storage temporarily
@@ -152,7 +223,7 @@
} else {
return back()->with('error', 'No statements available for download.');
}
} else if($disk->exists($filePath)) {
} else if ($disk->exists($filePath)) {
return $disk->download($filePath, "{$statement->account_number}_{$statement->period_from}.pdf");
}
}
@@ -164,16 +235,16 @@
{
$request->validate([
'authorization_status' => ['required', Rule::in(['approved', 'rejected'])],
'remarks' => ['nullable', 'string', 'max:255'],
'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()
'authorized_by' => Auth::id(),
'authorized_at' => now(),
'remarks' => $request->remarks,
'updated_by' => Auth::id()
]);
$statusText = $request->authorization_status === 'approved' ? 'approved' : 'rejected';
@@ -183,99 +254,6 @@
}
/**
* Check if the statement is available in the system.
* This is a placeholder method - implement according to your system.
*/
protected function checkStatementAvailability(PrintStatementLog $statement)
{
// This would be implemented based on your system's logic
// For example, checking an API or database for statement availability
$disk = Storage::disk('sftpStatement');
//format folder /periode/Print/branch_code/account_number.pdf
$filePath = "{$statement->period_from}/Print/{$statement->branch_code}/{$statement->account_number}.pdf";
// Check if the statement exists in the storage
if ($statement->is_period_range && $statement->period_to) {
$periodFrom = \Carbon\Carbon::createFromFormat('Ym', $statement->period_from);
$periodTo = \Carbon\Carbon::createFromFormat('Ym', $statement->period_to);
// Loop through each month in the range
$missingPeriods = [];
$availablePeriods = [];
for ($period = clone $periodFrom; $period->lte($periodTo); $period->addMonth()) {
$periodFormatted = $period->format('Ym');
$periodPath = $periodFormatted . "/Print/{$statement->branch_code}/{$statement->account_number}.pdf";
if ($disk->exists($periodPath)) {
$availablePeriods[] = $periodFormatted;
} else {
$missingPeriods[] = $periodFormatted;
}
}
// If any period is missing, the statement is not available
if (count($missingPeriods) > 0) {
$notes = "Missing periods: " . implode(', ', $missingPeriods);
$statement->update([
'is_available' => false,
'remarks' => $notes,
'updated_by' => Auth::id()
]);
return;
} else {
// All periods are available
$statement->update([
'is_available' => true,
'updated_by' => Auth::id()
]);
}
} else if($disk->exists($filePath)) {
$statement->update([
'is_available' => true,
'updated_by' => Auth::id()
]);
return;
}
$statement->update([
'is_available' => false,
'updated_by' => Auth::id()
]);
return;
}
/**
* 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;
}
/**
* 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";
}
/**
* Provide data for datatables.
*/
@@ -309,9 +287,9 @@
if (!empty($filter['value'])) {
if ($filter['column'] === 'branch_code') {
$query->where('branch_code', $filter['value']);
} elseif ($filter['column'] === 'authorization_status') {
} else if ($filter['column'] === 'authorization_status') {
$query->where('authorization_status', $filter['value']);
} elseif ($filter['column'] === 'is_downloaded') {
} else if ($filter['column'] === 'is_downloaded') {
$query->where('is_downloaded', filter_var($filter['value'], FILTER_VALIDATE_BOOLEAN));
}
}
@@ -320,16 +298,16 @@
// Apply sorting if provided
if ($request->has('sortOrder') && !empty($request->get('sortOrder'))) {
$order = $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',
'branch' => 'branch_code',
'account' => 'account_number',
'period' => 'period_from',
'status' => 'authorization_status',
'remarks' =>'remarks',
'period' => 'period_from',
'status' => 'authorization_status',
'remarks' => 'remarks',
// Add more mappings as needed
];
@@ -345,8 +323,8 @@
// Apply pagination if provided
if ($request->has('page') && $request->has('size')) {
$page = $request->get('page');
$size = $request->get('size');
$page = $request->get('page');
$size = $request->get('size');
$offset = ($page - 1) * $size; // Calculate the offset
$query->skip($offset)->take($size);
@@ -362,20 +340,20 @@
$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,
'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_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,
'is_available' => $item->is_available,
'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,
];
});
@@ -387,22 +365,184 @@
// Return the response data as a JSON object
return response()->json([
'draw' => $request->get('draw'),
'recordsTotal' => $totalRecords,
'draw' => $request->get('draw'),
'recordsTotal' => $totalRecords,
'recordsFiltered' => $filteredRecords,
'pageCount' => $pageCount,
'page' => $currentPage,
'totalCount' => $totalRecords,
'data' => $data,
'pageCount' => $pageCount,
'page' => $currentPage,
'totalCount' => $totalRecords,
'data' => $data,
]);
}
public function destroy(PrintStatementLog $statement){
public function destroy(PrintStatementLog $statement)
{
// Delete the statement
$statement->delete();
return response()->json([
'message' => 'Statement deleted successfully.',
'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}/Print/{$statement->branch_code}/{$statement->account_number}.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 . "/Print/{$statement->branch_code}/{$statement->account_number}.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}/Print/{$statement->branch_code}/{$statement->account_number}.pdf";
$localFilePath = storage_path("app/temp/{$statement->account_number}_{$period}.pdf");
// Download the file from SFTP to local storage temporarily
file_put_contents($localFilePath, $disk->get($filePath));
// Add the file to the zip
$zip->addFile($localFilePath, "{$statement->account_number}_{$period}.pdf");
}
$zip->close();
// 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()
]);
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";
}
}

View File

@@ -3,7 +3,6 @@
namespace Modules\Webstatement\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
use Modules\Webstatement\Models\PrintStatementLog as Statement;
class PrintStatementRequest extends FormRequest
@@ -29,6 +28,8 @@
'branch_code' => ['required', 'string', 'exists:branches,code'],
'account_number' => ['required', 'string'],
'is_period_range' => ['sometimes', 'boolean'],
'email' => ['nullable', 'email'],
'email_sent_at' => ['nullable', 'timestamp'],
'period_from' => [
'required',
'string',
@@ -36,8 +37,8 @@
// Prevent duplicate requests with same account number and period
function ($attribute, $value, $fail) {
$query = Statement::where('account_number', $this->input('account_number'))
->where('authorization_status', '!=', 'rejected')
->where('period_from', $value);
->where('authorization_status', '!=', 'rejected')
->where('period_from', $value);
// If this is an update request, exclude the current record
if ($this->route('statement')) {
@@ -100,17 +101,17 @@
protected function prepareForValidation()
: void
{
if($this->has('period_from')){
if ($this->has('period_from')) {
//conver to YYYYMM format
$this->merge([
'period_from' => substr($this->period_from, 0, 4).substr($this->period_from, 5, 2),
'period_from' => substr($this->period_from, 0, 4) . substr($this->period_from, 5, 2),
]);
}
if($this->has('period_to')){
if ($this->has('period_to')) {
//conver to YYYYMM format
$this->merge([
'period_to' => substr($this->period_to, 0, 4).substr($this->period_to, 5, 2),
'period_to' => substr($this->period_to, 0, 4) . substr($this->period_to, 5, 2),
]);
}

View File

@@ -0,0 +1,73 @@
<?php
namespace Modules\Webstatement\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Modules\Webstatement\Models\PrintStatementLog;
class StatementEmail extends Mailable
{
use Queueable, SerializesModels;
protected $statement;
protected $filePath;
protected $isZip;
/**
* Create a new message instance.
*
* @param PrintStatementLog $statement
* @param string $filePath
* @param bool $isZip
*/
public function __construct(PrintStatementLog $statement, $filePath, $isZip = false)
{
$this->statement = $statement;
$this->filePath = $filePath;
$this->isZip = $isZip;
}
/**
* Build the message.
*
* @return $this
*/
public function build()
{
$subject = 'Your Account Statement';
if ($this->statement->is_period_range) {
$subject .= " - {$this->statement->period_from} to {$this->statement->period_to}";
} else {
$subject .= " - {$this->statement->period_from}";
}
$email = $this->subject($subject)
->view('webstatement::statements.email')
->with([
'statement' => $this->statement,
'accountNumber' => $this->statement->account_number,
'periodFrom' => $this->statement->period_from,
'periodTo' => $this->statement->period_to,
'isRange' => $this->statement->is_period_range,
]);
if ($this->isZip) {
$fileName = "{$this->statement->account_number}_{$this->statement->period_from}_to_{$this->statement->period_to}.zip";
$email->attach($this->filePath, [
'as' => $fileName,
'mime' => 'application/zip',
]);
} else {
$fileName = "{$this->statement->account_number}_{$this->statement->period_from}.pdf";
$email->attach($this->filePath, [
'as' => $fileName,
'mime' => 'application/pdf',
]);
}
return $email;
}
}

View File

@@ -31,6 +31,8 @@
'authorized_by',
'authorized_at',
'remarks',
'email',
'email_sent_at',
];
protected $casts = [

View File

@@ -25,6 +25,8 @@
$table->string('user_agent')->nullable()->comment('User agent of requester');
$table->timestamp('downloaded_at')->nullable()->comment('When the statement was downloaded');
$table->string('remarks')->nullable()->comment('Remarks for the statement');
$table->string('email')->nullable()->comment('Email address of customer');
$table->timestamp('email_sent_at')->nullable()->comment('When the email was sent');
$table->enum('authorization_status', ['pending', 'approved', 'rejected'])->default('pending');

View File

@@ -12,7 +12,7 @@
"menu": {
"main": [
{
"title": "Periode Statement",
"title": "Create Periode",
"path": "periode-statements",
"icon": "ki-filled ki-calendar text-lg text-primary",
"classes": "",
@@ -23,7 +23,7 @@
]
},
{
"title": "Print Statement",
"title": "Statement",
"path": "statements",
"icon": "ki-filled ki-printer text-lg text-primary",
"classes": "",

View File

@@ -0,0 +1,143 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body {
font-family: 'Arial', sans-serif;
line-height: 1.6;
color: #333;
margin: 0;
padding: 0;
background-color: #f4f4f4;
}
.container {
max-width: 90%;
margin: 20px auto;
background-color: #ffffff;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
.header {
background-color: #35c1d080;
color: #ffffff;
padding: 20px;
text-align: center;
}
.header h1 {
margin: 0;
font-size: 24px;
}
.content {
padding: 30px;
font-size: 14px;
}
.footer {
background-color: #35c1d080;
padding: 15px;
text-align: center;
font-size: 12px;
color: #666;
}
.btn {
display: inline-block;
padding: 10px 20px;
background-color: #0056b3;
color: #ffffff;
text-decoration: none;
border-radius: 5px;
margin-top: 20px;
}
ul.dashed-list {
list-style-type: none; /* Remove default bullet */
padding-left: 1em; /* Add some left padding for spacing */
}
ul.dashed-list li::before {
content: " "; /* Use an en dash (U+2013) or a hyphen "-" */
display: inline-block; /* Ensure proper spacing */
width: 1em; /* Adjust width as needed */
margin-left: 0.5em; /* Align the dash properly */
}
</style>
</head>
<body>
<div class="container">
<div class="content">
<div>
Yang Terhormat <strong>Bapak/Ibu Daeng Deni Mardaeni</strong>,<br><br>
Terlampir adalah Electronic Statement Rekening Anda.<br>
Silahkan gunakan password Electronic Statement Anda untuk membukanya.<br><br>
Password standar Elektronic Statement ini adalah <strong>ddMonyyyyxx</strong> (contoh: 01Aug1970xx) dimana :
<ul class="dashed-list">
<li>dd : <strong>2 digit</strong> tanggal lahir anda, contoh: 01</li>
<li>Mon :
<strong>3 huruf pertama</strong> bulan lahir anda dalam bahasa Ingris. Huruf pertama adalah huruf besar dan selanjutnya huruf kecil, contoh : Aug
</li>
<li>yyyy : <strong>4 digit</strong> tahun kelahiran anda, contoh : 1970</li>
<li>xx : <strong>2 digit terakhir</strong> dari nomer rekening anda, contoh : 12</li>
</ul>
<br>
Terima Kasih,<br><br>
<strong>Bank Artha Graha Internasional</strong><br>
------------------------------
<wbr>
------------------------------
<wbr>
--------<br>
Kami sangat menghargai masukan dan saran Anda untuk meningkatkan layanan dan produk kami.<br>
Untuk memberikan masukan, silakan hubungi <strong>GrahaCall 24 Jam</strong> kami di
<strong>0-800-191-8880</strong>.<br><br><br>
Dear <strong>Mr/Mrs/Ms Daeng Deni Mardaeni</strong>,<br><br>
Attached is your Electronic Account Statement.<br>
Please use your Electronic Statement password to open it.<br><br>
The Electronic Statement standard password is <strong>ddMonyyyyxx</strong> (example: 01Aug1970xx) where:
<ul class="dashed-list">
<li>dd : <strong>The first 2 digits</strong> of your birthdate, example: 01</li>
<li>Mon :
<strong>The first 3 letters</strong> of your birth month in English. The first letter is uppercase and the rest are lowercase, example: Aug
</li>
<li>yyyy : <strong>4 digit</strong> of your birth year, example: 1970</li>
<li>xx : <strong>The last 2 digits</strong> of your account number, example: 12.</li>
</ul>
<br>
Regards,<br><br>
<strong>Bank Artha Graha Internasional</strong><br>
------------------------------
<wbr>
------------------------------
<wbr>
--------<br>
We welcome any feedback or suggestions to improve our product and services.<br>
If you have any feedback, please contact our <strong>GrahaCall 24 Hours</strong> at
<strong>0-800-191-8880</strong>.
<div class="yj6qo"></div>
<div class="adL"><br>
</div>
</div>
</div>
<div class="footer">
<p>© 2023 Bank Artha Graha Internasional. All rights reserved.</p>
<p>Jika Anda memiliki pertanyaan, silakan hubungi customer service kami.</p>
</div>
</div>
</body>
</html>

View File

@@ -41,6 +41,14 @@
@enderror
</div>
<div class="form-group">
<label class="form-label" for="email">Email</label>
<input type="email" class="input form-control @error('email') is-invalid @enderror" id="email" name="email" value="{{ old('email', $statement->email ?? '') }}" placeholder="Optional email for send statement">
@error('email')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="form-group">
<label class="form-label required" for="start_date">Start Date</label>
@@ -284,6 +292,13 @@
</a>`;
}
// Show send email button if email is not empty and statement is available
if (data.is_available) {
buttons += `<a class="btn btn-sm btn-icon btn-clear btn-primary" href="statements/${data.id}/send-email" title="Send to Email">
<i class="ki-outline ki-paper-plane"></i>
</a>`;
}
// Only show delete button if status is pending
if (data.authorization_status === 'pending') {
buttons += `<a onclick="deleteData(${data.id})" class="delete btn btn-sm btn-icon btn-clear btn-danger">

View File

@@ -83,6 +83,7 @@ Route::middleware(['auth'])->group(function () {
Route::get('/datatables', [PrintStatementController::class, 'dataForDatatables'])->name('datatables');
Route::get('/{statement}/download', [PrintStatementController::class, 'download'])->name('download');
Route::post('/{statement}/authorize', [PrintStatementController::class, 'authorize'])->name('authorize');
Route::get('/{statement}/send-email', [PrintStatementController::class, 'sendEmail'])->name('send-email');
});
Route::resource('statements', PrintStatementController::class);