feat(webstatement): tambahkan fitur monitoring dan peningkatan pengiriman email statement
- **Perbaikan dan Penambahan Komando:**
- Memberikan komando baru `webstatement:check-progress` untuk memantau progres pengiriman email statement.
- Menampilkan informasi seperti `Log ID`, `Batch ID`, `Request Type`, status, hingga persentase progress.
- Menangani secara detail jumlah akun yang diproses, sukses, gagal, dan kalkulasi tingkat keberhasilan.
- Menyediakan penanganan error jika log tidak ditemukan atau terjadi kegagalan lainnya.
- Memperluas komando `webstatement:send-email`:
- Mendukung pengiriman berdasarkan `single account`, `branch`, atau `all branches`.
- Menambahkan validasi parameter `type` (`single`, `branch`, `all`) dan input spesifik seperti `--account` atau `--branch` untuk mode tertentu.
- Melakukan pencatatan log awal dengan metadata lengkap seperti `request_type`, `batch_id`, dan status.
- **Peningkatan Logika Proses Backend:**
- Menambahkan fungsi `createLogEntry` untuk mencatat log pengiriman email statement secara dinamis berdasarkan tipe request.
- Menyediakan reusable method seperti `validateParameters` dan `determineRequestTypeAndTarget` untuk mempermudah pengelolaan parameter pengiriman.
- Memberikan feedback dan panduan kepada pengguna mengenai ID log dan komando monitoring (`webstatement:check-progress`).
- **Penambahan Controller dan Fitur UI:**
- Menambahkan controller baru `EmailStatementLogController`:
- Mendukung pengelolaan log seperti list, detail, dan retry untuk pengiriman ulang email statement.
- Menyediakan fitur pencarian, filter, dan halaman data log yang responsif menggunakan datatable.
- Menambahkan kemampuan resend email untuk log dengan status `completed` atau `failed`.
- Mengimplementasikan UI untuk log pengiriman:
- Halaman daftar monitoring dengan filter berdasarkan branch, account number, request type, status, dan tanggal.
- Menampilkan kemajuan, tingkat keberhasilan, serta tombol aksi seperti detail dan pengiriman ulang.
- **Peningkatan Model dan Validasi:**
- Menyesuaikan model `PrintStatementLog` untuk mendukung lebih banyak atribut seperti `processed_accounts`, `success_count`, `failed_count`, `request_type`, serta metode utilitas seperti `getProgressPercentage()` dan `getSuccessRate()`.
- Memvalidasi parameter input lebih mendalam agar kesalahan dapat diminimalisasi di awal proses.
- **Peningkatan pada View dan Feedback Pengguna:**
- Menambah daftar command berguna untuk user di interface log:
- Status antrian dengan `php artisan queue:work`.
- Monitoring menggunakan komando custom yang baru ditambahkan.
- **Perbaikan Logging dan Error Handling:**
- Menambahkan logging komprehensif pada semua proses, termasuk batch pengiriman ulang.
- Memastikan rollback pada database jika terjadi error melalui transaksi pada critical path.
Signed-off-by: Daeng Deni Mardaeni <ddeni05@gmail.com>
This commit is contained in:
@@ -1,186 +1,288 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Webstatement\Models;
|
||||
namespace Modules\Webstatement\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Modules\Basicdata\Models\Branch;
|
||||
use Modules\Usermanagement\Models\User;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Modules\Basicdata\Models\Branch;
|
||||
use Modules\Usermanagement\Models\User;
|
||||
|
||||
class PrintStatementLog extends Model
|
||||
class PrintStatementLog extends Model
|
||||
{
|
||||
use HasFactory, SoftDeletes;
|
||||
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'branch_code',
|
||||
'account_number',
|
||||
'request_type',
|
||||
'batch_id',
|
||||
'target_accounts',
|
||||
'total_accounts',
|
||||
'processed_accounts',
|
||||
'success_count',
|
||||
'failed_count',
|
||||
'status',
|
||||
'started_at',
|
||||
'completed_at',
|
||||
'error_message',
|
||||
'period_from',
|
||||
'period_to',
|
||||
'is_period_range',
|
||||
'is_available',
|
||||
'is_downloaded',
|
||||
'ip_address',
|
||||
'user_agent',
|
||||
'downloaded_at',
|
||||
'authorization_status',
|
||||
'created_by',
|
||||
'updated_by',
|
||||
'deleted_by',
|
||||
'authorized_by',
|
||||
'authorized_at',
|
||||
'remarks',
|
||||
'email',
|
||||
'email_sent_at',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'is_period_range' => 'boolean',
|
||||
'is_available' => 'boolean',
|
||||
'is_downloaded' => 'boolean',
|
||||
'downloaded_at' => 'datetime',
|
||||
'authorized_at' => 'datetime',
|
||||
'started_at' => 'datetime',
|
||||
'completed_at' => 'datetime',
|
||||
'target_accounts' => 'array',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the formatted period display
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPeriodDisplayAttribute()
|
||||
{
|
||||
use HasFactory, SoftDeletes;
|
||||
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'branch_code',
|
||||
'account_number',
|
||||
'period_from',
|
||||
'period_to',
|
||||
'is_period_range',
|
||||
'is_available',
|
||||
'is_downloaded',
|
||||
'ip_address',
|
||||
'user_agent',
|
||||
'downloaded_at',
|
||||
'authorization_status',
|
||||
'created_by',
|
||||
'updated_by',
|
||||
'deleted_by',
|
||||
'authorized_by',
|
||||
'authorized_at',
|
||||
'remarks',
|
||||
'email',
|
||||
'email_sent_at',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'is_period_range' => 'boolean',
|
||||
'is_available' => 'boolean',
|
||||
'is_downloaded' => 'boolean',
|
||||
'downloaded_at' => 'datetime',
|
||||
'authorized_at' => 'datetime',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the formatted period display
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPeriodDisplayAttribute()
|
||||
{
|
||||
if ($this->is_period_range) {
|
||||
return $this->formatPeriod($this->period_from) . ' - ' . $this->formatPeriod($this->period_to);
|
||||
}
|
||||
|
||||
return $this->formatPeriod($this->period_from);
|
||||
if ($this->is_period_range) {
|
||||
return $this->formatPeriod($this->period_from) . ' - ' . $this->formatPeriod($this->period_to);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format period from YYYYMM to Month Year
|
||||
*
|
||||
* @param string $period
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function formatPeriod($period)
|
||||
{
|
||||
if (strlen($period) !== 6) {
|
||||
return $period;
|
||||
}
|
||||
|
||||
$year = substr($period, 0, 4);
|
||||
$month = substr($period, 4, 2);
|
||||
|
||||
return date('F Y', mktime(0, 0, 0, (int) $month, 1, (int) $year));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user who requested the statement
|
||||
*/
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user who created the record
|
||||
*/
|
||||
public function creator()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'created_by');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user who updated the record
|
||||
*/
|
||||
public function updater()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'updated_by');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user who authorized the record
|
||||
*/
|
||||
public function authorizer()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'authorized_by');
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope a query to only include pending authorization records
|
||||
*/
|
||||
public function scopePending($query)
|
||||
{
|
||||
return $query->where('authorization_status', 'pending');
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope a query to only include approved records
|
||||
*/
|
||||
public function scopeApproved($query)
|
||||
{
|
||||
return $query->where('authorization_status', 'approved');
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope a query to only include rejected records
|
||||
*/
|
||||
public function scopeRejected($query)
|
||||
{
|
||||
return $query->where('authorization_status', 'rejected');
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope a query to only include downloaded records
|
||||
*/
|
||||
public function scopeDownloaded($query)
|
||||
{
|
||||
return $query->where('is_downloaded', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope a query to only include available records
|
||||
*/
|
||||
public function scopeAvailable($query)
|
||||
{
|
||||
return $query->where('is_available', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the statement is for a single period
|
||||
*/
|
||||
public function isSinglePeriod()
|
||||
{
|
||||
return !$this->is_period_range;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the statement is authorized
|
||||
*/
|
||||
public function isAuthorized()
|
||||
{
|
||||
return $this->authorization_status === 'approved';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the statement is rejected
|
||||
*/
|
||||
public function isRejected()
|
||||
{
|
||||
return $this->authorization_status === 'rejected';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the statement is pending authorization
|
||||
*/
|
||||
public function isPending()
|
||||
{
|
||||
return $this->authorization_status === 'pending';
|
||||
}
|
||||
|
||||
public function branch(){
|
||||
return $this->belongsTo(Branch::class, 'branch_code','code');
|
||||
}
|
||||
return $this->formatPeriod($this->period_from);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format period from YYYYMM to Month Year
|
||||
*
|
||||
* @param string $period
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function formatPeriod($period)
|
||||
{
|
||||
if (strlen($period) !== 6) {
|
||||
return $period;
|
||||
}
|
||||
|
||||
$year = substr($period, 0, 4);
|
||||
$month = substr($period, 4, 2);
|
||||
|
||||
return date('F Y', mktime(0, 0, 0, (int) $month, 1, (int) $year));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user who requested the statement
|
||||
*/
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user who created the record
|
||||
*/
|
||||
public function creator()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'created_by');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user who updated the record
|
||||
*/
|
||||
public function updater()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'updated_by');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user who authorized the record
|
||||
*/
|
||||
public function authorizer()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'authorized_by');
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope a query to only include pending authorization records
|
||||
*/
|
||||
public function scopePending($query)
|
||||
{
|
||||
return $query->where('authorization_status', 'pending');
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope a query to only include approved records
|
||||
*/
|
||||
public function scopeApproved($query)
|
||||
{
|
||||
return $query->where('authorization_status', 'approved');
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope a query to only include rejected records
|
||||
*/
|
||||
public function scopeRejected($query)
|
||||
{
|
||||
return $query->where('authorization_status', 'rejected');
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope a query to only include downloaded records
|
||||
*/
|
||||
public function scopeDownloaded($query)
|
||||
{
|
||||
return $query->where('is_downloaded', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope a query to only include available records
|
||||
*/
|
||||
public function scopeAvailable($query)
|
||||
{
|
||||
return $query->where('is_available', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the statement is for a single period
|
||||
*/
|
||||
public function isSinglePeriod()
|
||||
{
|
||||
return !$this->is_period_range;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the statement is authorized
|
||||
*/
|
||||
public function isAuthorized()
|
||||
{
|
||||
return $this->authorization_status === 'approved';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the statement is rejected
|
||||
*/
|
||||
public function isRejected()
|
||||
{
|
||||
return $this->authorization_status === 'rejected';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the statement is pending authorization
|
||||
*/
|
||||
public function isPending()
|
||||
{
|
||||
return $this->authorization_status === 'pending';
|
||||
}
|
||||
|
||||
public function branch(){
|
||||
return $this->belongsTo(Branch::class, 'branch_code','code');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this is a single account request
|
||||
*/
|
||||
public function isSingleAccountRequest()
|
||||
{
|
||||
return $this->request_type === 'single_account';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this is a branch request
|
||||
*/
|
||||
public function isBranchRequest()
|
||||
{
|
||||
return $this->request_type === 'branch';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this is an all branches request
|
||||
*/
|
||||
public function isAllBranchesRequest()
|
||||
{
|
||||
return $this->request_type === 'all_branches';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if processing is completed
|
||||
*/
|
||||
public function isCompleted()
|
||||
{
|
||||
return $this->status === 'completed';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if processing is in progress
|
||||
*/
|
||||
public function isProcessing()
|
||||
{
|
||||
return $this->status === 'processing';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if processing failed
|
||||
*/
|
||||
public function isFailed()
|
||||
{
|
||||
return $this->status === 'failed';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get progress percentage
|
||||
*/
|
||||
public function getProgressPercentage()
|
||||
{
|
||||
if (!$this->total_accounts || $this->total_accounts == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return round(($this->processed_accounts / $this->total_accounts) * 100, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get success rate percentage
|
||||
*/
|
||||
public function getSuccessRate()
|
||||
{
|
||||
if (!$this->processed_accounts || $this->processed_accounts == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return round(($this->success_count / $this->processed_accounts) * 100, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope for batch requests
|
||||
*/
|
||||
public function scopeBatch($query)
|
||||
{
|
||||
return $query->whereIn('request_type', ['branch', 'all_branches']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope for single account requests
|
||||
*/
|
||||
public function scopeSingleAccount($query)
|
||||
{
|
||||
return $query->where('request_type', 'single_account');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user