MIF_E31222307/app/Http/Controllers/RekomendasiController.php

555 lines
22 KiB
PHP

<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Kriteria;
use App\Models\Makanan;
use App\Models\BobotKriteria;
use App\Models\SkorMakanan;
use App\Models\Rekomendasi;
use App\Models\RekomendasiAhli;
use App\Models\Komponen;
use App\Models\WaktuMakan;
use App\Models\PreferensiWaktuKriteria;
use Illuminate\Support\Facades\Auth;
use Carbon\Carbon;
use App\Models\PerbandinganKriteria;
use Illuminate\Support\Facades\DB;
use App\Traits\KriteriaTrait;
use App\Models\PerbandinganAlternatif;
use App\Models\ConsistencyRatioCriteria;
use App\Models\ConsistencyRatioAlternatif;
class RekomendasiController extends Controller
{
use KriteriaTrait;
public function hitungDanSimpanOtomatis()
{
try {
// Ambil data dari session
$sessionData = session('hasil_rekomendasi');
$alternatifsByKomponen = session('alternatifs_by_komponen');
$waktuMakanId = session('waktu_makan_id');
if (!$sessionData || !$alternatifsByKomponen || !$waktuMakanId) {
\Log::error('Data session tidak lengkap:', [
'hasil_rekomendasi' => $sessionData,
'alternatifs_by_komponen' => $alternatifsByKomponen,
'waktu_makan_id' => $waktuMakanId
]);
return redirect()->route('alternatif.pilih')
->with('error', 'Data session tidak lengkap. Silakan pilih alternatif terlebih dahulu.');
}
// Ambil semua kriteria
$kriterias = Kriteria::all();
$bobotKriteria = $this->getBobotKriteria($kriterias);
// Proses untuk setiap komponen
foreach ($alternatifsByKomponen as $komponenId => $komponenData) {
$alternatifs = $komponenData['alternatifs'];
// Log untuk debugging
\Log::info('Memproses komponen:', [
'komponen_id' => $komponenId,
'jumlah_alternatif' => count($alternatifs)
]);
// Inisialisasi matriks perbandingan
$matriksPerbandingan = [];
foreach ($kriterias as $kriteria) {
$matriksPerbandingan[$kriteria->id] = [];
foreach ($alternatifs as $alt1) {
$matriksPerbandingan[$kriteria->id][$alt1['id']] = [];
foreach ($alternatifs as $alt2) {
// Menggunakan nilai yang sudah dinormalisasi dari session
$nilai1 = $alt1[strtolower($kriteria->nama) . '_normalized'] ?? 0;
$nilai2 = $alt2[strtolower($kriteria->nama) . '_normalized'] ?? 0;
if ($nilai2 == 0) {
$matriksPerbandingan[$kriteria->id][$alt1['id']][$alt2['id']] = 0;
} else {
$matriksPerbandingan[$kriteria->id][$alt1['id']][$alt2['id']] = $nilai1 / $nilai2;
}
}
}
}
// Hitung prioritas lokal untuk setiap kriteria
$prioritasLokal = [];
foreach ($kriterias as $kriteria) {
$prioritasLokal[$kriteria->id] = [];
// Hitung jumlah kolom
$jumlahKolom = [];
foreach ($alternatifs as $alt2) {
$jumlahKolom[$alt2['id']] = 0;
foreach ($alternatifs as $alt1) {
$jumlahKolom[$alt2['id']] += $matriksPerbandingan[$kriteria->id][$alt1['id']][$alt2['id']];
}
}
// Normalisasi matriks
$matriksNormal = [];
foreach ($alternatifs as $alt1) {
$matriksNormal[$alt1['id']] = [];
foreach ($alternatifs as $alt2) {
if ($jumlahKolom[$alt2['id']] > 0) {
$matriksNormal[$alt1['id']][$alt2['id']] =
$matriksPerbandingan[$kriteria->id][$alt1['id']][$alt2['id']] / $jumlahKolom[$alt2['id']];
} else {
$matriksNormal[$alt1['id']][$alt2['id']] = 0;
}
}
}
// Hitung prioritas lokal (rata-rata baris)
foreach ($alternatifs as $alt) {
$jumlahBaris = array_sum($matriksNormal[$alt['id']]);
$prioritasLokal[$kriteria->id][$alt['id']] = $jumlahBaris / count($alternatifs);
}
// Simpan skor ke database
foreach ($alternatifs as $alt) {
SkorMakanan::updateOrCreate(
[
'makanan_id' => $alt['id'],
'kriteria_id' => $kriteria->id,
'waktu_makan_id' => $waktuMakanId,
'komponen_id' => $komponenId
],
['nilai' => $prioritasLokal[$kriteria->id][$alt['id']]]
);
}
}
// Hitung skor akhir
foreach ($alternatifs as $alt) {
$skorAkhir = 0;
foreach ($kriterias as $kriteria) {
$skorAkhir += $prioritasLokal[$kriteria->id][$alt['id']] * $bobotKriteria[$kriteria->id];
}
// Simpan rekomendasi
Rekomendasi::updateOrCreate(
[
'makanan_id' => $alt['id'],
'komponen_id' => $komponenId,
'waktu_makan_id' => $waktuMakanId,
'user_id' => Auth::id(),
'tanggal_rekomendasi' => $sessionData['tanggal_rekomendasi']
],
['nilai_akhir' => $skorAkhir]
);
}
}
return redirect()->route('rekomendasi.index')
->with('success', 'Perhitungan AHP berhasil dilakukan.');
} catch (\Exception $e) {
\Log::error('Error in hitungDanSimpanOtomatis:', [
'message' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
'session_data' => session()->all()
]);
return redirect()->back()
->with('error', 'Terjadi kesalahan saat melakukan perhitungan: ' . $e->getMessage());
}
}
private function getBobotKriteria($kriterias)
{
try {
$waktuMakanId = session('waktu_makan_id');
if (!$waktuMakanId) {
throw new \Exception('Waktu makan ID tidak ditemukan di session');
}
$bobotKriteria = [];
foreach ($kriterias as $kriteria) {
$bobot = BobotKriteria::where([
'kriteria_id' => $kriteria->id,
'waktu_makan_id' => $waktuMakanId
])->first();
if (!$bobot) {
// Jika tidak ada bobot, coba ambil dari perbandingan kriteria
$perbandinganKriteria = PerbandinganKriteria::where([
'kriteria_id_1' => $kriteria->id,
'waktu_makan_id' => $waktuMakanId
])->first();
if ($perbandinganKriteria) {
$total = PerbandinganKriteria::where([
'kriteria_id_1' => $kriteria->id,
'waktu_makan_id' => $waktuMakanId
])->sum('nilai');
$bobotKriteria[$kriteria->id] = $total > 0 ? $perbandinganKriteria->nilai / $total : 0;
} else {
\Log::warning("Bobot kriteria tidak ditemukan untuk kriteria {$kriteria->nama} pada waktu makan ID: {$waktuMakanId}");
$bobotKriteria[$kriteria->id] = 0;
}
} else {
$bobotKriteria[$kriteria->id] = $bobot->bobot;
}
}
// Normalisasi bobot kriteria
$totalBobot = array_sum($bobotKriteria);
if ($totalBobot > 0) {
foreach ($bobotKriteria as $key => $value) {
$bobotKriteria[$key] = $value / $totalBobot;
}
}
// Log untuk debugging
\Log::info('Bobot kriteria yang diambil:', [
'waktu_makan_id' => $waktuMakanId,
'bobot' => $bobotKriteria
]);
return $bobotKriteria;
} catch (\Exception $e) {
\Log::error('Error in getBobotKriteria: ' . $e->getMessage());
throw $e;
}
}
public function tampilHasil()
{
try {
// Ambil data dari session
$hasilRekomendasi = session('hasil_rekomendasi');
if (!$hasilRekomendasi) {
return redirect()->route('alternatif.pilih')
->with('error', 'Data rekomendasi tidak ditemukan.');
}
// Ambil data yang diperlukan dengan select spesifik
$kriterias = Kriteria::select('id', 'nama')->get();
$komponens = Komponen::select('id', 'nama')->get();
$waktuMakan = WaktuMakan::select('id', 'nama')->findOrFail($hasilRekomendasi['waktu_makan_id']);
// Inisialisasi array untuk menyimpan hasil per komponen
$hasilPerKomponen = [];
// Proses setiap komponen
foreach ($komponens as $komponen) {
// Ambil rekomendasi untuk komponen ini
$rekomendasis = Rekomendasi::with('makanan')
->where('waktu_makan_id', $hasilRekomendasi['waktu_makan_id'])
->where('komponen_id', $komponen->id)
->orderByDesc('nilai_akhir')
->get();
if ($rekomendasis->isNotEmpty()) {
// Ambil detail skor hanya untuk makanan yang ada di rekomendasi
$detailSkor = [];
foreach ($rekomendasis as $rekomendasi) {
$detailSkor[$rekomendasi->makanan_id] = [];
foreach ($kriterias as $kriteria) {
$skor = SkorMakanan::select('nilai')
->where('makanan_id', $rekomendasi->makanan_id)
->where('kriteria_id', $kriteria->id)
->first();
$detailSkor[$rekomendasi->makanan_id][$kriteria->nama] = $skor ? $skor->nilai : 0;
}
}
// Simpan hasil untuk komponen ini
$hasilPerKomponen[$komponen->id] = [
'komponen' => $komponen,
'rekomendasis' => $rekomendasis,
'detailSkor' => $detailSkor
];
}
}
// Ambil bobot kriteria
$bobotKriteria = [];
foreach ($kriterias as $kriteria) {
$bobot = BobotKriteria::select('bobot')
->where('kriteria_id', $kriteria->id)
->first();
$bobotKriteria[$kriteria->id] = $bobot ? $bobot->bobot : 0;
}
// Normalisasi bobot
$totalBobot = array_sum($bobotKriteria);
if ($totalBobot > 0) {
foreach ($bobotKriteria as $key => $value) {
$bobotKriteria[$key] = $value / $totalBobot;
}
}
// Set CR to null since it's temporarily disabled
$consistencyRatio = null;
return view('admin.rekomendasi', compact(
'hasilPerKomponen',
'kriterias',
'waktuMakan',
'consistencyRatio',
'bobotKriteria'
));
} catch (\Exception $e) {
\Log::error('Error in tampilHasil:', [
'message' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
return redirect()->route('alternatif.pilih')
->with('error', 'Terjadi kesalahan saat menampilkan hasil: ' . $e->getMessage());
}
}
private function hitungConsistencyRatio()
{
$waktuMakanId = session('waktu_makan_id');
if (!$waktuMakanId) {
return 0; // Return 0 if no waktu_makan_id in session
}
$perbandinganKriterias = PerbandinganKriteria::where('waktu_makan_id', $waktuMakanId)->get();
$n = Kriteria::count();
if ($perbandinganKriterias->isEmpty()) {
return 0; // Return 0 if no perbandingan data
}
// Buat matriks perbandingan
$matrix = array_fill(0, $n, array_fill(0, $n, 1));
foreach ($perbandinganKriterias as $perbandingan) {
$matrix[$perbandingan->kriteria_id_1 - 1][$perbandingan->kriteria_id_2 - 1] = $perbandingan->nilai;
$matrix[$perbandingan->kriteria_id_2 - 1][$perbandingan->kriteria_id_1 - 1] = 1 / $perbandingan->nilai;
}
// Hitung eigenvalue maksimum
$rowSums = array_map(function($row) {
return array_sum($row);
}, $matrix);
$totalSum = array_sum($rowSums);
$normalizedMatrix = array_map(function($row) use ($totalSum) {
return array_map(function($val) use ($totalSum) {
return $val / $totalSum;
}, $row);
}, $matrix);
$eigenvalue = 0;
for ($i = 0; $i < $n; $i++) {
$sum = 0;
for ($j = 0; $j < $n; $j++) {
$sum += $matrix[$i][$j] * array_sum($normalizedMatrix[$j]);
}
$eigenvalue += $sum / array_sum($normalizedMatrix[$i]);
}
$eigenvalue /= $n;
// Random Index values for n = 1 to 10
$RI = [0, 0, 0.58, 0.90, 1.12, 1.24, 1.32, 1.41, 1.45, 1.49];
// Hitung Consistency Index
$CI = ($eigenvalue - $n) / ($n - 1);
// Hitung Consistency Ratio
return $n <= 2 ? 0 : $CI / $RI[$n - 1];
}
private function getNilaiKriteria($makanan, $kriteria)
{
switch (strtolower($kriteria->nama)) {
case 'energi':
return is_array($makanan) ? $makanan['energi'] : $makanan->energi;
case 'protein':
return is_array($makanan) ? $makanan['protein'] : $makanan->protein;
case 'lemak':
return is_array($makanan) ? $makanan['lemak'] : $makanan->lemak;
case 'karbohidrat':
return is_array($makanan) ? $makanan['karbohidrat'] : $makanan->karbohidrat;
case 'natrium':
return is_array($makanan) ? $makanan['natrium'] : $makanan->natrium;
default:
return 0;
}
}
private function isCostCriteria($kriteriaNama)
{
return in_array(strtolower($kriteriaNama), ['lemak', 'natrium']);
}
private function normalisasiNilai($nilai, $kriteria, $makanans)
{
// Kumpulkan semua nilai untuk kriteria ini
$nilaiKriteria = [];
foreach ($makanans as $makanan) {
$nilaiKriteria[] = $this->getNilaiKriteria($makanan, $kriteria);
}
// Cek jenis kriteria (benefit atau cost)
if ($this->isCostCriteria($kriteria->nama)) {
// Untuk kriteria cost (lemak dan natrium), nilai lebih kecil lebih baik
$nilaiInverse = $nilai > 0 ? 1 / $nilai : 0;
$totalInverse = 0;
foreach ($nilaiKriteria as $n) {
$totalInverse += ($n > 0 ? 1 / $n : 0);
}
return $totalInverse > 0 ? $nilaiInverse / $totalInverse : 0;
} else {
// Untuk kriteria benefit (energi dan karbohidrat), nilai lebih besar lebih baik
$total = array_sum($nilaiKriteria);
return $total > 0 ? $nilai / $total : 0;
}
}
public function index()
{
$waktuMakans = WaktuMakan::with(['komponens', 'consistencyRatios' => function($query) {
$query->where('user_id', Auth::id())
->latest('tanggal_perhitungan');
}])
->select('waktu_makans.*')
->selectRaw('(
SELECT COUNT(*) > 0
FROM rekomendasis r
WHERE r.waktu_makan_id = waktu_makans.id
AND r.user_id = ?
) as has_recommendation', [Auth::id()])
->selectRaw('(
SELECT MAX(tanggal_rekomendasi)
FROM rekomendasis r
WHERE r.waktu_makan_id = waktu_makans.id
AND r.user_id = ?
) as latest_calculation', [Auth::id()])
->get();
return view('admin.rekomendasi-list', compact('waktuMakans'));
}
public function detail($waktuMakanId)
{
try {
$waktuMakan = WaktuMakan::findOrFail($waktuMakanId);
// Ambil rekomendasi terbaru untuk waktu makan ini
$latestRekomendasi = Rekomendasi::where('waktu_makan_id', $waktuMakanId)
->where('user_id', Auth::id())
->orderBy('tanggal_rekomendasi', 'desc')
->first();
if (!$latestRekomendasi) {
return redirect()->route('rekomendasi.index')
->with('error', 'Belum ada rekomendasi untuk waktu makan ini.');
}
// Set data untuk tampilan detail
$hasilRekomendasi = [
'tanggal_rekomendasi' => $latestRekomendasi->tanggal_rekomendasi,
'waktu_makan_id' => $waktuMakanId
];
// Ambil semua kriteria
$kriterias = Kriteria::all();
// Ambil bobot kriteria dari database
$bobotKriteria = [];
foreach ($kriterias as $kriteria) {
$bobot = BobotKriteria::where('kriteria_id', $kriteria->id)->first();
$bobotKriteria[$kriteria->id] = $bobot ? $bobot->bobot : 0;
}
// Normalisasi bobot
$totalBobot = array_sum($bobotKriteria);
if ($totalBobot > 0) {
foreach ($bobotKriteria as $key => $value) {
$bobotKriteria[$key] = $value / $totalBobot;
}
}
// Ambil komponen yang memiliki rekomendasi
$komponens = Komponen::whereExists(function ($query) use ($waktuMakanId, $latestRekomendasi) {
$query->select(DB::raw(1))
->from('rekomendasis')
->whereColumn('rekomendasis.komponen_id', 'komponens.id')
->where('rekomendasis.waktu_makan_id', $waktuMakanId)
->where('rekomendasis.tanggal_rekomendasi', $latestRekomendasi->tanggal_rekomendasi);
})->get();
$hasilPerKomponen = [];
foreach ($komponens as $komponen) {
// Ambil rekomendasi untuk komponen ini dengan eager loading
$rekomendasis = Rekomendasi::with(['makanan' => function($query) {
$query->select('makanans.id', 'makanans.nama', 'makanans.energi', 'makanans.lemak',
'makanans.karbohidrat', 'makanans.natrium');
}])
->where('waktu_makan_id', $waktuMakanId)
->where('komponen_id', $komponen->id)
->where('user_id', Auth::id())
->where('tanggal_rekomendasi', $latestRekomendasi->tanggal_rekomendasi)
->orderByDesc('nilai_akhir')
->get();
// Ambil detail skor untuk setiap makanan
$detailSkor = [];
foreach ($rekomendasis as $rekomendasi) {
$detailSkor[$rekomendasi->makanan_id] = [];
foreach ($kriterias as $kriteria) {
$skor = SkorMakanan::where('makanan_id', $rekomendasi->makanan_id)
->where('kriteria_id', $kriteria->id)
->where('waktu_makan_id', $waktuMakanId)
->where('komponen_id', $komponen->id)
->first();
$detailSkor[$rekomendasi->makanan_id][$kriteria->nama] = $skor ? $skor->nilai : 0;
}
// Ambil data perbandingan untuk makanan ini
$perbandinganData = PerbandinganAlternatif::where('waktu_makan_id', $waktuMakanId)
->where('komponen_id', $komponen->id)
->where(function($query) use ($rekomendasi) {
$query->where('alternatif_id_1', $rekomendasi->makanan_id)
->orWhere('alternatif_id_2', $rekomendasi->makanan_id);
})
->get();
if ($perbandinganData->isNotEmpty()) {
$detailSkor[$rekomendasi->makanan_id]['perbandingan'] = $perbandinganData;
}
}
$hasilPerKomponen[$komponen->id] = [
'komponen' => $komponen,
'rekomendasis' => $rekomendasis,
'detailSkor' => $detailSkor
];
}
// Ambil CR terbaru dari ConsistencyRatioCriteria untuk waktu makan ini
$latestCR = ConsistencyRatioCriteria::where('waktu_makan_id', $waktuMakanId)
->latest()
->first();
return view('admin.rekomendasi', compact(
'waktuMakan',
'hasilRekomendasi',
'kriterias',
'bobotKriteria',
'hasilPerKomponen',
'latestCR'
));
} catch (\Exception $e) {
return redirect()->route('rekomendasi.index')
->with('error', 'Terjadi kesalahan saat menampilkan detail: ' . $e->getMessage());
}
}
}