214 lines
6.6 KiB
PHP
214 lines
6.6 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\PengajuanUkt;
|
|
use App\Models\Kriteria;
|
|
use App\Models\HasilPenilaian;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Exception;
|
|
|
|
class SawCalculationService
|
|
{
|
|
protected $kriteria;
|
|
|
|
public function __construct()
|
|
{
|
|
$this->kriteria = Kriteria::with('subKriteria')->orderBy('id')->get();
|
|
|
|
if ($this->kriteria->isEmpty()) {
|
|
throw new Exception('Tidak ada kriteria untuk perhitungan');
|
|
}
|
|
}
|
|
|
|
public function calculate(PengajuanUkt $pengajuan): array
|
|
{
|
|
// Cek apakah total bobot valid (== 1)
|
|
$totalBobot = $this->kriteria->sum('bobot');
|
|
if (abs($totalBobot - 1) > 0.0001) {
|
|
throw new Exception("Total bobot saat ini adalah ...");
|
|
}
|
|
|
|
DB::beginTransaction();
|
|
|
|
try {
|
|
if ($pengajuan->status_validasi !== 'valid') {
|
|
throw new Exception("Pengajuan belum divalidasi");
|
|
}
|
|
|
|
if ($pengajuan->details->isEmpty()) {
|
|
throw new Exception("Data detail pengajuan tidak lengkap");
|
|
}
|
|
|
|
// 1. Decision Matrix
|
|
$decisionMatrix = $this->buildDecisionMatrix($pengajuan);
|
|
|
|
// 2. Normalized Matrix
|
|
$normalizedMatrix = $this->normalizeMatrix($decisionMatrix);
|
|
|
|
// 3. Preference Value
|
|
$preferenceValue = $this->calculatePreferenceValue($normalizedMatrix);
|
|
|
|
// 4. Recommendation
|
|
$recommendation = $this->determineRecommendation($pengajuan->jenis_pengajuan, $preferenceValue);
|
|
|
|
// 5. Save Result
|
|
$this->saveResult($pengajuan, $preferenceValue, $recommendation);
|
|
|
|
DB::commit();
|
|
|
|
return [
|
|
'decision_matrix' => $decisionMatrix,
|
|
'normalized_matrix' => $normalizedMatrix,
|
|
'preference_value' => $preferenceValue,
|
|
'recommendation' => $recommendation,
|
|
];
|
|
|
|
} catch (Exception $e) {
|
|
DB::rollBack();
|
|
Log::error("Error pada perhitungan SAW: " . $e->getMessage());
|
|
throw $e;
|
|
}
|
|
}
|
|
|
|
protected function determineRecommendation(string $jenisPengajuan, float $preferenceValue): string
|
|
{
|
|
if ($jenisPengajuan === 'penurunan') {
|
|
return ($preferenceValue >= 0.65) ? 'Dapat Penurunan UKT' : 'UKT Tetap';
|
|
}
|
|
elseif ($jenisPengajuan === 'pengangsuran') {
|
|
return ($preferenceValue >= 0.75) ? 'Dapat Pengangsuran UKT' : 'Tidak Dapat Mengangsur';
|
|
}
|
|
return 'UKT Tetap';
|
|
}
|
|
|
|
public function processBatch(array $pengajuanIds): array
|
|
{
|
|
$successCount = 0;
|
|
$errorCount = 0;
|
|
$errors = [];
|
|
|
|
foreach ($pengajuanIds as $id) {
|
|
try {
|
|
$pengajuan = PengajuanUkt::findOrFail($id);
|
|
$this->calculate($pengajuan);
|
|
$successCount++;
|
|
} catch (Exception $e) {
|
|
$errorCount++;
|
|
$errors[$id] = $e->getMessage();
|
|
Log::error("Gagal memproses pengajuan ID {$id}: " . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
return [
|
|
'status' => $errorCount === 0 ? 'success' : ($successCount > 0 ? 'partial' : 'failed'),
|
|
'message' => "Berhasil memproses {$successCount} dari " . count($pengajuanIds) . " pengajuan",
|
|
'success_count' => $successCount,
|
|
'error_count' => $errorCount,
|
|
'errors' => $errors
|
|
];
|
|
}
|
|
|
|
protected function buildDecisionMatrix(PengajuanUkt $pengajuan): array
|
|
{
|
|
$decisionMatrix = [];
|
|
|
|
foreach ($this->kriteria as $kriteria) {
|
|
$detail = $pengajuan->details->firstWhere('kriteria_id', $kriteria->id);
|
|
if (!$detail) {
|
|
Log::error("Missing detail for kriteria: {$kriteria->nama_kriteria}");
|
|
continue;
|
|
}
|
|
|
|
$sub = $kriteria->subKriteria->firstWhere('id', $detail->sub_kriteria_id);
|
|
if (!$sub) {
|
|
Log::error("Missing subkriteria for detail: {$detail->id}");
|
|
continue;
|
|
}
|
|
|
|
$decisionMatrix[$kriteria->id] = $sub->nilai;
|
|
}
|
|
|
|
if (count($decisionMatrix) !== count($this->kriteria)) {
|
|
throw new Exception("Data kriteria tidak lengkap");
|
|
}
|
|
|
|
return $decisionMatrix;
|
|
}
|
|
|
|
protected function normalizeMatrix(array $decisionMatrix): array
|
|
{
|
|
$normalizedMatrix = [];
|
|
|
|
foreach ($this->kriteria as $kriteria) {
|
|
$nilai = $decisionMatrix[$kriteria->id];
|
|
$allNilai = $kriteria->subKriteria->pluck('nilai')->toArray();
|
|
|
|
if (empty($allNilai)) {
|
|
throw new Exception("Subkriteria untuk kriteria ID {$kriteria->id} kosong");
|
|
}
|
|
|
|
if (strtolower($kriteria->attribut) === 'benefit') {
|
|
$max = max($allNilai);
|
|
$value = $max != 0 ? ($nilai / $max) : 0;
|
|
} else { // COST
|
|
$min = min($allNilai);
|
|
$value = $nilai != 0 ? ($min / $nilai) : 0;
|
|
}
|
|
|
|
$normalizedMatrix[$kriteria->id] = $value;
|
|
}
|
|
|
|
return $normalizedMatrix;
|
|
}
|
|
|
|
|
|
protected function calculatePreferenceValue(array $normalizedMatrix): float
|
|
{
|
|
$preferenceValue = 0;
|
|
|
|
foreach ($this->kriteria as $kriteria) {
|
|
$bobot = $kriteria->bobot;
|
|
$nilaiNormalisasi = $normalizedMatrix[$kriteria->id];
|
|
|
|
$preferenceValue += $bobot * $nilaiNormalisasi;
|
|
}
|
|
|
|
return round($preferenceValue, 5, PHP_ROUND_HALF_EVEN);
|
|
}
|
|
protected function saveResult(PengajuanUkt $pengajuan, float $preferenceValue, string $recommendation): void
|
|
{
|
|
HasilPenilaian::updateOrCreate(
|
|
['pengajuan_id' => $pengajuan->id],
|
|
[
|
|
'nilai_preferensi' => $preferenceValue,
|
|
'rekomendasi_ukt' => $recommendation,
|
|
'keterangan' => null,
|
|
'processed_by' => auth()->id(),
|
|
]
|
|
);
|
|
}
|
|
|
|
public function prosesPerhitunganLengkap(array $pengajuanIds): array
|
|
{
|
|
return $this->processBatch($pengajuanIds);
|
|
}
|
|
|
|
public function getCalculationDetails($pengajuanId)
|
|
{
|
|
$pengajuan = PengajuanUkt::with('details.subKriteria')->findOrFail($pengajuanId);
|
|
|
|
// Rebuild the calculation to ensure consistency
|
|
$decisionMatrix = $this->buildDecisionMatrix($pengajuan);
|
|
$normalizedMatrix = $this->normalizeMatrix($decisionMatrix);
|
|
$preferenceValue = $this->calculatePreferenceValue($normalizedMatrix);
|
|
|
|
return [
|
|
'decision_matrix' => $decisionMatrix,
|
|
'normalized_matrix' => $normalizedMatrix,
|
|
'preference_value' => $pengajuan->hasilPenilaian->nilai_preferensi,
|
|
'recommendation' => $pengajuan->hasilPenilaian->rekomendasi_ukt,
|
|
];
|
|
}
|
|
} |