feat(webstatement): tambah validasi cabang rekening dan update logika penyimpanan statement

### Perubahan Utama
- Tambah validasi untuk memverifikasi bahwa nomor rekening sesuai dengan cabang pengguna.
- Cegah transaksi untuk rekening yang terdaftar di cabang khusus (`ID0019999`).
- Perbaikan sistem untuk menangani kasus rekening yang tidak ditemukan di database.

### Detail Perubahan
1. **Validasi Cabang Rekening**:
   - Tambah pengecekan untuk memastikan rekening yang dimasukkan adalah milik cabang pengguna (non-multi-branch).
   - Blokir transaksi jika rekening terdaftar pada cabang khusus (`ID0019999`) dengan menampilkan pesan error yang relevan.
   - Tambahkan pesan error jika nomor rekening tidak ditemukan dalam sistem.

2. **Update Logika Penyimpanan**:
   - Tambahkan validasi untuk mengisi kolom `branch_code` secara otomatis berdasarkan informasi rekening terkait.
   - Otomatis atur nilai awal `authorization_status` menjadi `approved`.

3. **Penghapusan Atribut Tidak Digunakan**:
   - Hapus form `branch_code` dari view terkait (`index.blade.php`) karena sekarang diisi secara otomatis berdasarkan data rekening.

4. **Perbaikan View dan Logika Terkait Status Otorisasi**:
   - Hapus logic dan elemen UI terkait `authorization_status` di halaman statement (`index.blade.php` dan `show.blade.php`).
   - Simplifikasi tampilan untuk hanya menampilkan informasi yang tersedia dan relevan.

5. **Optimasi Query Data Cabang**:
   - Update query untuk memfilter cabang berdasarkan kondisi `customer_company` dan mengecualikan kode cabang khusus.

6. **Penyesuaian Struktur Request**:
   - Hapus validasi terkait `branch_code` di `PrintStatementRequest` karena tidak lagi relevan.

7. **Log Aktivitas dan Kesalahan**:
   - Tambahkan log untuk mencatat aktivitas seperti validasi rekening dan penyimpanan batch data.
   - Penanganan lebih baik untuk logging jika terjadi error saat validasi nomor rekening atau penyimpanan statement.

### Manfaat Perubahan
- Meningkatkan akurasi data cabang dan validasi rekening sebelum penyimpanan.
- Menyederhanakan antarmuka pengguna dengan menghapus field input redundant.
- Memastikan proses menjadi lebih transparan dengan penanganan error yang lebih baik.

Langkah ini diterapkan untuk meningkatkan keamanan dan keandalan sistem dalam memverifikasi dan memproses pemintaan statement.
This commit is contained in:
daengdeni
2025-06-20 13:59:58 +07:00
parent 6035c61cc4
commit 8fb16028d9
5 changed files with 98 additions and 110 deletions

View File

@@ -14,6 +14,7 @@
use Modules\Basicdata\Models\Branch; use Modules\Basicdata\Models\Branch;
use Modules\Webstatement\Http\Requests\PrintStatementRequest; use Modules\Webstatement\Http\Requests\PrintStatementRequest;
use Modules\Webstatement\Mail\StatementEmail; use Modules\Webstatement\Mail\StatementEmail;
use Modules\Webstatement\Models\Account;
use Modules\Webstatement\Models\PrintStatementLog; use Modules\Webstatement\Models\PrintStatementLog;
use ZipArchive; use ZipArchive;
@@ -24,7 +25,10 @@
*/ */
public function index(Request $request) public function index(Request $request)
{ {
$branches = Branch::orderBy('name')->get(); $branches = Branch::whereNotNull('customer_company')
->where('code', '!=', 'ID0019999')
->orderBy('name')
->get();
return view('webstatement::statements.index', compact('branches')); return view('webstatement::statements.index', compact('branches'));
} }
@@ -35,32 +39,66 @@
*/ */
public function store(PrintStatementRequest $request) public function store(PrintStatementRequest $request)
{ {
// Add account verification before storing
$accountNumber = $request->input('account_number'); // Assuming this is the field name for account number
// First, check if the account exists and get branch information
$account = Account::where('account_number', $accountNumber)->first();
if ($account) {
$branch_code = $account->branch_code;
$userBranchId = session('branch_id'); // Assuming branch ID is stored in session
$multiBranch = session('MULTI_BRANCH');
if (!$multiBranch) {
// Check if account branch matches user's branch
if ($account->branch_id !== $userBranchId) {
return redirect()->route('statements.index')
->with('error', 'Nomor rekening tidak sesuai dengan cabang Anda. Transaksi tidak dapat dilanjutkan.');
}
}
// Check if account belongs to restricted branch ID0019999
if ($account->branch_id === 'ID0019999') {
return redirect()->route('statements.index')
->with('error', 'Nomor rekening terdaftar pada cabang khusus. Silakan hubungi bagian HC untuk informasi lebih lanjut.');
}
// If all checks pass, proceed with storing data
// Your existing store logic here
} else {
// Account not found
return redirect()->route('statements.index')
->with('error', 'Nomor rekening tidak ditemukan dalam sistem.');
}
DB::beginTransaction(); DB::beginTransaction();
try { try {
$validated = $request->validated(); $validated = $request->validated();
// 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['request_type'] = 'single_account'; // Default untuk request manual
$validated['status'] = 'pending'; // Status awal $validated['status'] = 'pending'; // Status awal
$validated['total_accounts'] = 1; // Untuk single account $validated['authorization_status'] = 'approved'; // Status otorisasi awal
$validated['total_accounts'] = 1; // Untuk single account
$validated['processed_accounts'] = 0; $validated['processed_accounts'] = 0;
$validated['success_count'] = 0; $validated['success_count'] = 0;
$validated['failed_count'] = 0; $validated['failed_count'] = 0;
$validated['branch_code'] = $branch_code; // Awal tidak tersedia
// Create the statement log // Create the statement log
$statement = PrintStatementLog::create($validated); $statement = PrintStatementLog::create($validated);
// Log aktivitas // Log aktivitas
Log::info('Statement request created', [ Log::info('Statement request created', [
'statement_id' => $statement->id, 'statement_id' => $statement->id,
'user_id' => Auth::id(), 'user_id' => Auth::id(),
'account_number' => $statement->account_number, 'account_number' => $statement->account_number,
'request_type' => $statement->request_type 'request_type' => $statement->request_type
]); ]);
// Process statement availability check // Process statement availability check
@@ -69,18 +107,18 @@
DB::commit(); DB::commit();
return redirect()->route('statements.index') return redirect()->route('statements.index')
->with('success', 'Statement request has been created successfully.'); ->with('success', 'Statement request has been created successfully.');
} catch (Exception $e) { } catch (Exception $e) {
DB::rollBack(); DB::rollBack();
Log::error('Failed to create statement request', [ Log::error('Failed to create statement request', [
'error' => $e->getMessage(), 'error' => $e->getMessage(),
'user_id' => Auth::id() 'user_id' => Auth::id()
]); ]);
return redirect()->back() return redirect()->back()
->withInput() ->withInput()
->with('error', 'Failed to create statement request: ' . $e->getMessage()); ->with('error', 'Failed to create statement request: ' . $e->getMessage());
} }
} }
@@ -102,19 +140,19 @@
DB::beginTransaction(); DB::beginTransaction();
try { try {
$disk = Storage::disk('sftpStatement'); $disk = Storage::disk('sftpStatement');
$filePath = "{$statement->period_from}/Print/{$statement->branch_code}/{$statement->account_number}.pdf"; $filePath = "{$statement->period_from}/Print/{$statement->branch_code}/{$statement->account_number}.pdf";
if ($statement->is_period_range && $statement->period_to) { if ($statement->is_period_range && $statement->period_to) {
$periodFrom = Carbon::createFromFormat('Ym', $statement->period_from); $periodFrom = Carbon::createFromFormat('Ym', $statement->period_from);
$periodTo = Carbon::createFromFormat('Ym', $statement->period_to); $periodTo = Carbon::createFromFormat('Ym', $statement->period_to);
$missingPeriods = []; $missingPeriods = [];
$availablePeriods = []; $availablePeriods = [];
for ($period = clone $periodFrom; $period->lte($periodTo); $period->addMonth()) { for ($period = clone $periodFrom; $period->lte($periodTo); $period->addMonth()) {
$periodFormatted = $period->format('Ym'); $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)) { if ($disk->exists($periodPath)) {
$availablePeriods[] = $periodFormatted; $availablePeriods[] = $periodFormatted;
@@ -127,55 +165,55 @@
$notes = "Missing periods: " . implode(', ', $missingPeriods); $notes = "Missing periods: " . implode(', ', $missingPeriods);
$statement->update([ $statement->update([
'is_available' => false, 'is_available' => false,
'remarks' => $notes, 'remarks' => $notes,
'updated_by' => Auth::id(), 'updated_by' => Auth::id(),
'status' => 'failed' 'status' => 'failed'
]); ]);
Log::warning('Statement not available - missing periods', [ Log::warning('Statement not available - missing periods', [
'statement_id' => $statement->id, 'statement_id' => $statement->id,
'missing_periods' => $missingPeriods 'missing_periods' => $missingPeriods
]); ]);
} else { } else {
$statement->update([ $statement->update([
'is_available' => true, 'is_available' => true,
'updated_by' => Auth::id(), 'updated_by' => Auth::id(),
'status' => 'completed', 'status' => 'completed',
'processed_accounts' => 1, 'processed_accounts' => 1,
'success_count' => 1 'success_count' => 1
]); ]);
Log::info('Statement available - all periods found', [ Log::info('Statement available - all periods found', [
'statement_id' => $statement->id, 'statement_id' => $statement->id,
'available_periods' => $availablePeriods 'available_periods' => $availablePeriods
]); ]);
} }
} else if ($disk->exists($filePath)) { } else if ($disk->exists($filePath)) {
$statement->update([ $statement->update([
'is_available' => true, 'is_available' => true,
'updated_by' => Auth::id(), 'updated_by' => Auth::id(),
'status' => 'completed', 'status' => 'completed',
'processed_accounts' => 1, 'processed_accounts' => 1,
'success_count' => 1 'success_count' => 1
]); ]);
Log::info('Statement available', [ Log::info('Statement available', [
'statement_id' => $statement->id, 'statement_id' => $statement->id,
'file_path' => $filePath 'file_path' => $filePath
]); ]);
} else { } else {
$statement->update([ $statement->update([
'is_available' => false, 'is_available' => false,
'updated_by' => Auth::id(), 'updated_by' => Auth::id(),
'status' => 'failed', 'status' => 'failed',
'processed_accounts' => 1, 'processed_accounts' => 1,
'failed_count' => 1, 'failed_count' => 1,
'error_message' => 'Statement file not found' 'error_message' => 'Statement file not found'
]); ]);
Log::warning('Statement not available', [ Log::warning('Statement not available', [
'statement_id' => $statement->id, 'statement_id' => $statement->id,
'file_path' => $filePath 'file_path' => $filePath
]); ]);
} }
@@ -185,14 +223,14 @@
Log::error('Error checking statement availability', [ Log::error('Error checking statement availability', [
'statement_id' => $statement->id, 'statement_id' => $statement->id,
'error' => $e->getMessage() 'error' => $e->getMessage()
]); ]);
$statement->update([ $statement->update([
'is_available' => false, 'is_available' => false,
'status' => 'failed', 'status' => 'failed',
'error_message' => $e->getMessage(), 'error_message' => $e->getMessage(),
'updated_by' => Auth::id() 'updated_by' => Auth::id()
]); ]);
} }
} }
@@ -223,19 +261,19 @@
$statement->update([ $statement->update([
'is_downloaded' => true, 'is_downloaded' => true,
'downloaded_at' => now(), 'downloaded_at' => now(),
'updated_by' => Auth::id() 'updated_by' => Auth::id()
]); ]);
Log::info('Statement downloaded', [ Log::info('Statement downloaded', [
'statement_id' => $statement->id, 'statement_id' => $statement->id,
'user_id' => Auth::id(), 'user_id' => Auth::id(),
'account_number' => $statement->account_number 'account_number' => $statement->account_number
]); ]);
DB::commit(); DB::commit();
// Generate or fetch the statement file // Generate or fetch the statement file
$disk = Storage::disk('sftpStatement'); $disk = Storage::disk('sftpStatement');
$filePath = "{$statement->period_from}/Print/{$statement->branch_code}/{$statement->account_number}.pdf"; $filePath = "{$statement->period_from}/Print/{$statement->branch_code}/{$statement->account_number}.pdf";
if ($statement->is_period_range && $statement->period_to) { if ($statement->is_period_range && $statement->period_to) {
@@ -249,7 +287,7 @@
Log::error('Failed to download statement', [ Log::error('Failed to download statement', [
'statement_id' => $statement->id, 'statement_id' => $statement->id,
'error' => $e->getMessage() 'error' => $e->getMessage()
]); ]);
return back()->with('error', 'Failed to download statement: ' . $e->getMessage()); return back()->with('error', 'Failed to download statement: ' . $e->getMessage());
@@ -337,13 +375,13 @@
// Map frontend column names to database column names if needed // Map frontend column names to database column names if needed
$columnMap = [ $columnMap = [
'branch' => 'branch_code', 'branch' => 'branch_code',
'account' => 'account_number', 'account' => 'account_number',
'period' => 'period_from', 'period' => 'period_from',
'auth_status' => 'authorization_status', 'auth_status' => 'authorization_status',
'request_type' => 'request_type', 'request_type' => 'request_type',
'status' => 'status', 'status' => 'status',
'remarks' => 'remarks', 'remarks' => 'remarks',
]; ];
$dbColumn = $columnMap[$column] ?? $column; $dbColumn = $columnMap[$column] ?? $column;
@@ -541,8 +579,8 @@
Log::info('Statement email sent successfully', [ Log::info('Statement email sent successfully', [
'statement_id' => $statement->id, 'statement_id' => $statement->id,
'email' => $statement->email, 'email' => $statement->email,
'user_id' => Auth::id() 'user_id' => Auth::id()
]); ]);
DB::commit(); DB::commit();

View File

@@ -21,7 +21,6 @@ class PrintStatementRequest extends FormRequest
public function rules(): array public function rules(): array
{ {
$rules = [ $rules = [
'branch_code' => ['required', 'string', 'exists:branches,code'],
'account_number' => ['required', 'string'], 'account_number' => ['required', 'string'],
'is_period_range' => ['sometimes', 'boolean'], 'is_period_range' => ['sometimes', 'boolean'],
'email' => ['nullable', 'email'], 'email' => ['nullable', 'email'],
@@ -77,8 +76,6 @@ class PrintStatementRequest extends FormRequest
public function messages(): array public function messages(): array
{ {
return [ return [
'branch_code.required' => 'Branch code is required',
'branch_code.exists' => 'Selected branch does not exist',
'account_number.required' => 'Account number is required', 'account_number.required' => 'Account number is required',
'period_from.required' => 'Period is required', 'period_from.required' => 'Period is required',
'period_from.regex' => 'Period must be in YYYYMM format', 'period_from.regex' => 'Period must be in YYYYMM format',

View File

@@ -30,7 +30,8 @@
"attributes": [], "attributes": [],
"permission": "", "permission": "",
"roles": [ "roles": [
"administrator" "administrator",
"customer_service"
] ]
}, },
{ {

View File

@@ -18,21 +18,6 @@
@endif @endif
<div class="grid grid-cols-1 gap-5"> <div class="grid grid-cols-1 gap-5">
<div class="form-group">
<label class="form-label required" for="branch_code">Branch</label>
<select class="select tomselect @error('branch_code') is-invalid @enderror" id="branch_code" name="branch_code" required>
<option value="">Select Branch</option>
@foreach($branches as $branch)
<option value="{{ $branch->code }}" {{ (old('branch_code', $statement->branch_code ?? '') == $branch->code) ? 'selected' : '' }}>
{{ $branch->name }}
</option>
@endforeach
</select>
@error('branch_code')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="form-group"> <div class="form-group">
<label class="form-label required" for="account_number">Account Number</label> <label class="form-label required" for="account_number">Account Number</label>
<input type="text" class="input form-control @error('account_number') is-invalid @enderror" id="account_number" name="account_number" value="{{ old('account_number', $statement->account_number ?? '') }}" required> <input type="text" class="input form-control @error('account_number') is-invalid @enderror" id="account_number" name="account_number" value="{{ old('account_number', $statement->account_number ?? '') }}" required>
@@ -122,10 +107,6 @@
<span class="sort"> <span class="sort-label"> Period </span> <span class="sort"> <span class="sort-label"> Period </span>
<span class="sort-icon"> </span> </span> <span class="sort-icon"> </span> </span>
</th> </th>
<th class="min-w-[150px]" data-datatable-column="authorization_status">
<span class="sort"> <span class="sort-label"> Status </span>
<span class="sort-icon"> </span> </span>
</th>
<th class="min-w-[150px]" data-datatable-column="is_available"> <th class="min-w-[150px]" data-datatable-column="is_available">
<span class="sort"> <span class="sort-label"> Available </span> <span class="sort"> <span class="sort-label"> Available </span>
<span class="sort-icon"> </span> </span> <span class="sort-icon"> </span> </span>
@@ -243,22 +224,6 @@
return fromPeriod + toPeriod; return fromPeriod + toPeriod;
}, },
}, },
authorization_status: {
title: 'Status',
render: (item, data) => {
let statusClass = 'badge badge-light-primary';
if (data.authorization_status === 'approved') {
statusClass = 'badge badge-light-success';
} else if (data.authorization_status === 'rejected') {
statusClass = 'badge badge-light-danger';
} else if (data.authorization_status === 'pending') {
statusClass = 'badge badge-light-warning';
}
return `<span class="${statusClass}">${data.authorization_status}</span>`;
},
},
is_available: { is_available: {
title: 'Available', title: 'Available',
render: (item, data) => { render: (item, data) => {

View File

@@ -73,19 +73,6 @@
</div> </div>
</div> </div>
<div class="d-flex flex-column">
<div class="text-gray-500 fw-semibold">Status</div>
<div>
@if($statement->authorization_status === 'pending')
<span class="badge badge-warning">Pending Authorization</span>
@elseif($statement->authorization_status === 'approved')
<span class="badge badge-success">Approved</span>
@elseif($statement->authorization_status === 'rejected')
<span class="badge badge-danger">Rejected</span>
@endif
</div>
</div>
<div class="d-flex flex-column"> <div class="d-flex flex-column">
<div class="text-gray-500 fw-semibold">Availability</div> <div class="text-gray-500 fw-semibold">Availability</div>
<div> <div>