Files
webstatement/app/Jobs/ExportStatementJob.php
Daeng Deni Mardaeni 29ed72ad8b feat(webstatement): tambahkan atribut client untuk pemisahan data per klien
- Mengubah struktur listAccount menjadi array multidimensional untuk mendukung penamaan klien.
- Menambahkan parameter `client_name` pada data yang dikirimkan ke job ExportStatementJob.
- Memperbaiki penulisan nama file export dengan menambahkan informasi nama klien.
- Mengimplementasikan pembuatan direktori berdasarkan nama klien dan nomor akun untuk pengelompokan file secara terstruktur.
- Menambahkan atribut baru `client` pada ExportStatementJob untuk mengelola data terkait klien secara lebih spesifik.
- Melakukan perubahan pada proses export CSV:
  - Menentukan struktur direktori berdasarkan klien dan akun.
  - Menambahkan langkah untuk membuat direktori klien dan akun jika belum ada.
  - Menyesuaikan log informasi dan path untuk setiap file yang di-export.
- Perubahan ini bertujuan untuk mempermudah pengelolaan statement per klien dengan struktur file yang lebih terorganisir.

Signed-off-by: Daeng Deni Mardaeni <ddeni05@gmail.com>
2025-05-24 14:55:36 +07:00

361 lines
14 KiB
PHP

<?php
namespace Modules\Webstatement\Jobs;
use Carbon\Carbon;
use Exception;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Modules\Webstatement\Models\ProcessedStatement;
use Modules\Webstatement\Models\StmtEntry;
use Modules\Webstatement\Models\TempFundsTransfer;
use Modules\Webstatement\Models\TempStmtNarrFormat;
use Modules\Webstatement\Models\TempStmtNarrParam;
class ExportStatementJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $account_number;
protected $period;
protected $saldo;
protected $disk;
protected $client;
protected $fileName;
protected $chunkSize = 1000; // Proses data dalam chunk untuk mengurangi penggunaan memori
/**
* Create a new job instance.
*
* @param string $account_number
* @param string $period
* @param string $saldo
* @param string $disk
*/
public function __construct(string $account_number, string $period, string $saldo, string $client = '', string $disk = 'local')
{
$this->account_number = $account_number;
$this->period = $period;
$this->saldo = $saldo;
$this->disk = $disk;
$this->client = $client;
$this->fileName = "{$account_number}_{$period}.csv";
}
/**
* Execute the job.
*/
public function handle()
: void
{
try {
Log::info("Starting export statement job for account: {$this->account_number}, period: {$this->period}");
$this->processStatementData();
// Export data yang sudah diproses ke CSV
$this->exportToCsv();
Log::info("Export statement job completed successfully for account: {$this->account_number}, period: {$this->period}");
} catch (Exception $e) {
Log::error("Error in ExportStatementJob: " . $e->getMessage());
throw $e;
}
}
private function processStatementData()
: void
{
$accountQuery = [
'account_number' => $this->account_number,
'period' => $this->period
];
$totalCount = $this->getTotalEntryCount($accountQuery);
$existingDataCount = $this->getExistingProcessedCount($accountQuery);
// Hanya proses jika data belum lengkap diproses
if ($existingDataCount !== $totalCount) {
$this->deleteExistingProcessedData($accountQuery);
$this->processAndSaveStatementEntries($totalCount);
}
}
private function getTotalEntryCount(array $criteria)
: int
{
return StmtEntry::where('account_number', $criteria['account_number'])
->where('booking_date', $criteria['period'])
->count();
}
private function getExistingProcessedCount(array $criteria)
: int
{
return ProcessedStatement::where('account_number', $criteria['account_number'])
->where('period', $criteria['period'])
->count();
}
private function deleteExistingProcessedData(array $criteria)
: void
{
ProcessedStatement::where('account_number', $criteria['account_number'])
->where('period', $criteria['period'])
->delete();
}
private function processAndSaveStatementEntries(int $totalCount)
: void
{
$runningBalance = (float) $this->saldo;
$globalSequence = 0;
Log::info("Processing {$totalCount} statement entries for account: {$this->account_number}");
StmtEntry::with(['ft', 'transaction'])
->where('account_number', $this->account_number)
->where('booking_date', $this->period)
->orderBy('date_time', 'ASC')
->orderBy('trans_reference', 'ASC')
->chunk($this->chunkSize, function ($entries) use (&$runningBalance, &$globalSequence) {
$processedData = $this->prepareProcessedData($entries, $runningBalance, $globalSequence);
if (!empty($processedData)) {
DB::table('processed_statements')->insert($processedData);
}
});
}
private function prepareProcessedData($entries, &$runningBalance, &$globalSequence)
: array
{
$processedData = [];
foreach ($entries as $item) {
$globalSequence++;
$runningBalance += (float) $item->amount_lcy;
$transactionDate = $this->formatTransactionDate($item);
$actualDate = $this->formatActualDate($item);
$processedData[] = [
'account_number' => $this->account_number,
'period' => $this->period,
'sequence_no' => $globalSequence,
'transaction_date' => $transactionDate,
'reference_number' => $item->trans_reference,
'transaction_amount' => $item->amount_lcy,
'transaction_type' => $item->amount_lcy < 0 ? 'D' : 'C',
'description' => $this->generateNarrative($item),
'end_balance' => $runningBalance,
'actual_date' => $actualDate,
'created_at' => now(),
'updated_at' => now(),
];
}
return $processedData;
}
private function formatTransactionDate($item)
: string
{
try {
return Carbon::createFromFormat(
'YmdHi',
$item->booking_date . substr($item->ft?->date_time ?? '0000000000', 6, 4)
)->format('d/m/Y H:i');
} catch (Exception $e) {
Log::warning("Error formatting transaction date: " . $e->getMessage());
return Carbon::now()->format('d/m/Y H:i');
}
}
private function formatActualDate($item)
: string
{
try {
return Carbon::createFromFormat(
'ymdHi',
$item->ft?->date_time ?? '2505120000'
)->format('d/m/Y H:i');
} catch (Exception $e) {
Log::warning("Error formatting actual date: " . $e->getMessage());
return Carbon::now()->format('d/m/Y H:i');
}
}
/**
* Generate narrative for a statement entry
*/
private function generateNarrative($item)
{
$narr = '';
if ($item->transaction->narr_type) {
$narr .= $item->transaction->stmt_narr . ' ';
$narr .= $this->getFormatNarrative($item->transaction->narr_type, $item);
} else {
$narr .= $item->transaction->stmt_narr . ' ';
}
if ($item->ft?->recipt_no) {
$narr .= 'Receipt No: ' . $item->ft->recipt_no;
}
return trim($narr);
}
/**
* Get formatted narrative based on narrative type
*/
private function getFormatNarrative($narr, $item)
{
$narrParam = TempStmtNarrParam::where('_id', $narr)->first();
if (!$narrParam) {
return '';
}
$fmt = '';
if ($narrParam->_id == 'FTIN') {
$fmt = 'FT.IN';
} else if ($narrParam->_id == 'FTOUT') {
$fmt = 'FT.OUT';
} else {
$fmt = $narrParam->_id;
}
$narrFormat = TempStmtNarrFormat::where('_id', $fmt)->first();
if (!$narrFormat) {
return '';
}
// Get the format string from the database
$formatString = $narrFormat->text_data ?? '';
// Parse the format string
// Split by the separator ']'
$parts = explode(']', $formatString);
$result = '';
foreach ($parts as $index => $part) {
if (empty($part)) {
continue;
}
if ($index === 0) {
// For the first part, take only what's before the '!'
$splitPart = explode('!', $part);
if (count($splitPart) > 0) {
// Remove quotes, backslashes, and other escape characters
$cleanPart = trim($splitPart[0]);
// Remove quotes at the beginning and end
$cleanPart = preg_replace('/^["\'\\\\]+|["\'\\\\]+$/', '', $cleanPart);
// Remove any remaining backslashes
$cleanPart = str_replace('\\', '', $cleanPart);
// Remove any remaining quotes
$cleanPart = str_replace('"', '', $cleanPart);
$result .= $cleanPart;
}
} else {
// For other parts, these are field placeholders
$fieldName = strtolower(str_replace('.', '_', $part));
// Get the corresponding parameter value from narrParam
$paramValue = null;
// Check if the field exists as a property in narrParam
if (property_exists($narrParam, $fieldName)) {
$paramValue = $narrParam->$fieldName;
} else if (isset($narrParam->$fieldName)) {
$paramValue = $narrParam->$fieldName;
}
// If we found a value, add it to the result
if ($paramValue !== null) {
$result .= $paramValue;
} else {
// If no value found, try to use the original field name as a fallback
if ($fieldName !== 'recipt_no') {
$result .= ($item->ft?->$fieldName ?? '') . ' ';
}
}
}
}
return str_replace('<NL>', '', $result);
}
/**
* Export processed data to CSV file
*/
private function exportToCsv()
: void
{
// Determine the base path based on client
$basePath = !empty($this->client)
? "statements/{$this->client}"
: "statements";
// Create client directory if it doesn't exist
if (!empty($this->client)) {
Storage::disk($this->disk)->makeDirectory($basePath);
}
// Create account directory
$accountPath = "{$basePath}/{$this->account_number}";
Storage::disk($this->disk)->makeDirectory($accountPath);
$filePath = "{$accountPath}/{$this->fileName}";
// Delete existing file if it exists
if (Storage::disk($this->disk)->exists($filePath)) {
Storage::disk($this->disk)->delete($filePath);
}
$csvContent = "NO|TRANSACTION.DATE|REFERENCE.NUMBER|TRANSACTION.AMOUNT|TRANSACTION.TYPE|DESCRIPTION|END.BALANCE|ACTUAL.DATE\n";
// Ambil data yang sudah diproses dalam chunk untuk mengurangi penggunaan memori
ProcessedStatement::where('account_number', $this->account_number)
->where('period', $this->period)
->orderBy('sequence_no')
->chunk($this->chunkSize, function ($statements) use (&$csvContent, $filePath) {
foreach ($statements as $statement) {
$csvContent .= implode('|', [
$statement->sequence_no,
$statement->transaction_date,
$statement->reference_number,
$statement->transaction_amount,
$statement->transaction_type,
$statement->description,
$statement->end_balance,
$statement->actual_date
]) . "\n";
}
// Tulis ke file secara bertahap untuk mengurangi penggunaan memori
Storage::disk($this->disk)->append($filePath, $csvContent);
$csvContent = ''; // Reset content setelah ditulis
});
Log::info("Statement exported to {$this->disk} disk: {$filePath}");
}
/**
* Get transaction data by reference and field
*/
private function getTransaction($ref, $field)
{
$trans = TempFundsTransfer::where('ref_no', $ref)->first();
return $trans->$field ?? "";
}
}