MIF_E31222658/app/Services/SawCalculationService.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,
];
}
}