route('perbandingan')
->with('error', 'Selesaikan perhitungan bobot kriteria terlebih dahulu.');
}
$komponens = Komponen::all();
$waktuMakans = WaktuMakan::all();
// Get filter parameters
$waktuMakanId = $request->waktu_makan_id;
$search = $request->q;
// Get expert recommendations and available foods
$rekomendasiAhli = collect();
$makanans = collect();
if ($waktuMakanId) {
// Get expert recommendations for all components with eager loading
$rekomendasiAhli = RekomendasiAhli::with(['makanan', 'komponen', 'waktuMakan'])
->where('waktu_makan_id', $waktuMakanId)
->get();
// Get all foods from pivot table makanan_komponen_waktu
$query = MakananKomponenWaktu::with(['makanan', 'komponen'])
->where('waktu_makan_id', $waktuMakanId);
// Try with status filter first
$makananKomponenWaktu = $query->where('status', true)->get();
// If no data with status true, try without status filter
if ($makananKomponenWaktu->isEmpty()) {
$makananKomponenWaktu = $query->get();
\Log::info('No data with status=true, using all data without status filter');
}
if ($search) {
$makananKomponenWaktu = $makananKomponenWaktu->filter(function($item) use ($search) {
return stripos($item->makanan->nama, $search) !== false;
});
}
// Debug: Check all data without status filter
$allData = MakananKomponenWaktu::with(['makanan', 'komponen'])
->where('waktu_makan_id', $waktuMakanId)
->get();
\Log::info('Debug - All data without status filter:', [
'waktu_makan_id' => $waktuMakanId,
'total_all_data' => $allData->count(),
'status_counts' => $allData->groupBy('status')->map->count(),
'all_data' => $allData->toArray()
]);
// Log untuk debugging
\Log::info('Data makanan dari pivot table:', [
'waktu_makan_id' => $waktuMakanId,
'total_makanan' => $makananKomponenWaktu->count(),
'makanan_data' => $makananKomponenWaktu->toArray()
]);
// Transform data to match expected format
$makanans = $makananKomponenWaktu->map(function($item) {
$makanan = $item->makanan;
$makanan->komponen_id = $item->komponen_id;
return $makanan;
});
}
return view('admin.alternatif.pilih', compact(
'rekomendasiAhli',
'makanans',
'komponens',
'waktuMakans',
'waktuMakanId',
'search'
));
}
// 2. Simpan pilihan alternatif dan hitung bobot
public function pilihAlternatif(Request $request)
{
try {
$alternatifIds = $request->input('alternatifs');
$waktuMakanId = $request->input('waktu_makan_id');
$komponenIds = $request->input('komponen_ids');
$tanggalRekomendasi = now()->toDateString();
if (!$alternatifIds || !$waktuMakanId || !$komponenIds) {
return redirect()->back()
->with('error', 'Pilih minimal 4 alternatif untuk setiap komponen.');
}
// Validasi minimal 4 alternatif per komponen
$makananKomponenWaktu = MakananKomponenWaktu::with(['makanan', 'komponen'])
->whereIn('makanan_id', $alternatifIds)
->where('waktu_makan_id', $waktuMakanId)
->get();
// Group by komponen dan cek jumlah
$alternatifPerKomponen = $makananKomponenWaktu->groupBy('komponen_id');
$errorMessages = [];
foreach ($alternatifPerKomponen as $komponenId => $items) {
if ($items->count() < 4) {
$komponen = $items->first()->komponen;
$errorMessages[] = "Komponen {$komponen->nama}: hanya {$items->count()} alternatif (minimal 4)";
}
}
if (!empty($errorMessages)) {
return redirect()->back()
->with('error', 'Validasi gagal:
' . implode('
', $errorMessages));
}
// Log request data untuk debugging
\Log::info('Request data:', [
'alternatifIds' => $alternatifIds,
'waktuMakanId' => $waktuMakanId,
'komponenIds' => $komponenIds,
'all_request' => $request->all()
]);
// Ambil data makanan yang dipilih dari pivot table
$makananKomponenWaktu = MakananKomponenWaktu::with(['makanan'])
->whereIn('makanan_id', $alternatifIds)
->where('waktu_makan_id', $waktuMakanId)
->where('status', true)
->get();
// If no data with status true, try without status filter
if ($makananKomponenWaktu->isEmpty()) {
$makananKomponenWaktu = MakananKomponenWaktu::with(['makanan'])
->whereIn('makanan_id', $alternatifIds)
->where('waktu_makan_id', $waktuMakanId)
->get();
\Log::info('No data with status=true in pilihAlternatif, using all data without status filter');
}
// Group makanan berdasarkan komponen
$alternatifsByKomponen = [];
foreach ($makananKomponenWaktu as $item) {
$makanan = $item->makanan;
$komponenId = $item->komponen_id;
if (!isset($alternatifsByKomponen[$komponenId])) {
$komponen = Komponen::find($komponenId);
$alternatifsByKomponen[$komponenId] = [
'nama_komponen' => $komponen->nama,
'alternatifs' => []
];
}
// Simpan data makanan dalam format array
$alternatifsByKomponen[$komponenId]['alternatifs'][] = [
'id' => $makanan->id,
'nama' => $makanan->nama,
'energi' => $makanan->energi,
'lemak' => $makanan->lemak,
'karbohidrat' => $makanan->karbohidrat,
'natrium' => $makanan->natrium,
'lemak_invers' => $makanan->lemak > 0 ? 1 / $makanan->lemak : 0,
'natrium_invers' => $makanan->natrium > 0 ? 1 / $makanan->natrium : 0
];
}
// Log data untuk debugging
\Log::info('Data alternatif yang akan disimpan:', [
'alternatifs_by_komponen' => $alternatifsByKomponen,
'waktu_makan_id' => $waktuMakanId,
'tanggal_rekomendasi' => $tanggalRekomendasi
]);
// Simpan ke session
session([
'alternatifs_by_komponen' => $alternatifsByKomponen,
'waktu_makan_id' => $waktuMakanId,
'hasil_rekomendasi' => [
'waktu_makan_id' => $waktuMakanId,
'tanggal_rekomendasi' => $tanggalRekomendasi,
'waktu_makan_nama' => WaktuMakan::find($waktuMakanId)->nama,
'alternatifs_dipilih' => $alternatifIds
]
]);
return redirect()->route('alternatif.view')
->with('success', 'Alternatif berhasil dipilih.');
} catch (\Exception $e) {
\Log::error('Error in simpanPilihan:', [
'message' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
return redirect()->back()
->with('error', 'Terjadi kesalahan: ' . $e->getMessage());
}
}
public function viewAlternatif()
{
try {
// Ambil data dari session
$alternatifsByKomponen = session('alternatifs_by_komponen');
$hasilRekomendasi = session('hasil_rekomendasi');
if (!$hasilRekomendasi || !$alternatifsByKomponen) {
return redirect()->route('alternatif.pilih')
->with('error', 'Silakan pilih alternatif terlebih dahulu');
}
// Log data untuk debugging
\Log::info('Data untuk view:', [
'alternatifs_by_komponen' => $alternatifsByKomponen,
'hasil_rekomendasi' => $hasilRekomendasi
]);
return view('admin.alternatif.view', compact('alternatifsByKomponen', 'hasilRekomendasi'));
} catch (\Exception $e) {
\Log::error('Error in viewAlternatif:', [
'message' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
return redirect()->route('alternatif.pilih')
->with('error', 'Terjadi kesalahan saat memproses data: ' . $e->getMessage());
}
}
// 3. Hitung bobot alternatif
private function hitungBobotAlternatif($alternativesByComponent, $kriterias, $waktuMakanId)
{
try {
// Random Index values
$RI = [
1 => 0.00, 2 => 0.00, 3 => 0.58, 4 => 0.90, 5 => 1.12,
6 => 1.24, 7 => 1.32, 8 => 1.41, 9 => 1.45, 10 => 1.49,
11 => 1.51, 12 => 1.48, 13 => 1.56, 14 => 1.57, 15 => 1.59
];
// Ambil bobot kriteria berdasarkan waktu makan
$bobotKriteria = [];
foreach ($kriterias as $kriteria) {
$bobot = BobotKriteria::where([
'kriteria_id' => $kriteria->id,
'waktu_makan_id' => $waktuMakanId
])->first();
if (!$bobot) {
throw new \Exception("Bobot kriteria untuk {$kriteria->nama} pada waktu makan ini belum dihitung.");
}
$bobotKriteria[$kriteria->id] = $bobot->bobot;
}
foreach ($alternativesByComponent as $currentKomponenId => $komponenAlternatifs) {
// Untuk setiap kriteria
foreach ($kriterias as $kriteria) {
// 1. Buat matriks perbandingan berpasangan untuk komponen ini
$matriksPerbandingan = [];
// Isi matriks perbandingan
foreach ($komponenAlternatifs as $alt1) {
foreach ($komponenAlternatifs as $alt2) {
$nilai1 = $alt1->{strtolower($kriteria->nama)};
$nilai2 = $alt2->{strtolower($kriteria->nama)};
// Untuk kriteria cost (lemak dan natrium), gunakan nilai inverse
if ($this->isCostCriteria($kriteria->nama)) {
$nilai1 = $nilai1 > 0 ? 1 / $nilai1 : 0;
$nilai2 = $nilai2 > 0 ? 1 / $nilai2 : 0;
}
if ($nilai2 == 0) {
$matriksPerbandingan[$alt1->id][$alt2->id] = 0;
} else {
$matriksPerbandingan[$alt1->id][$alt2->id] = $nilai1 / $nilai2;
}
// Simpan perbandingan ke database
PerbandinganAlternatif::updateOrCreate(
[
'alternatif_id_1' => $alt1->id,
'alternatif_id_2' => $alt2->id,
'kriteria_id' => $kriteria->id,
'waktu_makan_id' => $waktuMakanId,
'komponen_id' => $currentKomponenId
],
['nilai' => $matriksPerbandingan[$alt1->id][$alt2->id]]
);
}
}
// 2. Hitung jumlah kolom
$jumlahKolom = [];
foreach ($komponenAlternatifs as $alt2) {
$jumlahKolom[$alt2->id] = 0;
foreach ($komponenAlternatifs as $alt1) {
$jumlahKolom[$alt2->id] += $matriksPerbandingan[$alt1->id][$alt2->id];
}
}
// 3. Normalisasi matriks
$matriksNormal = [];
foreach ($komponenAlternatifs as $alt1) {
foreach ($komponenAlternatifs as $alt2) {
if ($jumlahKolom[$alt2->id] > 0) {
$matriksNormal[$alt1->id][$alt2->id] =
$matriksPerbandingan[$alt1->id][$alt2->id] / $jumlahKolom[$alt2->id];
} else {
$matriksNormal[$alt1->id][$alt2->id] = 0;
}
}
}
// 4. Hitung prioritas lokal
$priorityVector = [];
foreach ($komponenAlternatifs as $alt) {
$priorityVector[$alt->id] = array_sum($matriksNormal[$alt->id]) / count($komponenAlternatifs);
}
// 5. Hitung CI dan CR
$lambdaMax = 0;
foreach ($komponenAlternatifs as $alt) {
$sum = 0;
foreach ($komponenAlternatifs as $alt2) {
$sum += $matriksPerbandingan[$alt->id][$alt2->id] * $priorityVector[$alt2->id];
}
if ($priorityVector[$alt->id] > 0) {
$lambdaMax += $sum / $priorityVector[$alt->id];
}
}
$lambdaMax = $lambdaMax / count($komponenAlternatifs);
$CI = ($lambdaMax - count($komponenAlternatifs)) / (count($komponenAlternatifs) - 1);
$CR = $CI / ($RI[count($komponenAlternatifs)] ?? 1.59);
// Simpan CR ke database
ConsistencyRatioAlternatif::updateOrCreate(
[
'kriteria_id' => $kriteria->id,
'waktu_makan_id' => $waktuMakanId,
'komponen_id' => $currentKomponenId
],
[
'ci' => $CI,
'cr' => $CR,
'is_consistent' => $CR <= 0.1
]
);
// Simpan skor akhir ke database
foreach ($komponenAlternatifs as $alt) {
SkorMakanan::updateOrCreate(
[
'makanan_id' => $alt->id,
'kriteria_id' => $kriteria->id,
'waktu_makan_id' => $waktuMakanId,
'komponen_id' => $currentKomponenId
],
['nilai' => $priorityVector[$alt->id] * $bobotKriteria[$kriteria->id]]
);
}
}
}
return true;
} catch (\Exception $e) {
\Log::error('Error in hitungBobotAlternatif: ' . $e->getMessage());
throw $e;
}
}
// 4. Tampilkan form perbandingan alternatif
public function tampilPerbandingan()
{
try {
// Ambil data dari session
$alternatifsByKomponen = session('alternatifs_by_komponen');
if (!$alternatifsByKomponen) {
return redirect()->route('alternatif.view')
->with('error', 'Silakan lihat data alternatif terlebih dahulu');
}
// Ambil semua kriteria
$kriterias = \App\Models\Kriteria::all();
// Data sudah siap di session, tampilkan view dengan data kriteria
return view('admin.alternatif.perbandingan', [
'alternatifsByKomponen' => $alternatifsByKomponen,
'kriterias' => $kriterias
]);
} catch (\Exception $e) {
\Log::error('Error in tampilPerbandingan: ' . $e->getMessage());
return redirect()->route('alternatif.view')
->with('error', 'Terjadi kesalahan: ' . $e->getMessage());
}
}
public function simpanNormalisasi(Request $request)
{
try {
// Ambil data normalisasi dari request dalam format JSON
$normalisasiDataJson = $request->input('normalisasi_data_json');
if (!$normalisasiDataJson) {
return redirect()->back()->with('error', 'Data normalisasi tidak ditemukan');
}
// Decode JSON data
$normalisasiData = json_decode($normalisasiDataJson, true);
if (!$normalisasiData || !isset($normalisasiData['data'])) {
return redirect()->back()->with('error', 'Format data normalisasi tidak valid');
}
// Simpan ke session
session(['alternatifs_by_komponen' => $normalisasiData['data']]);
// Log untuk debugging
\Log::info('Data normalisasi disimpan:', [
'normalisasi_data' => $normalisasiData['data']
]);
return redirect()->route('rekomendasi.hitung.otomatis')
->with('success', 'Data normalisasi berhasil disimpan');
} catch (\Exception $e) {
\Log::error('Error in simpanNormalisasi:', [
'message' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
return redirect()->back()
->with('error', 'Terjadi kesalahan: ' . $e->getMessage());
}
}
private function hitungNilaiPerbandingan($alt1, $alt2, $kriteria)
{
switch ($kriteria->kode) {
case 'C1': // Lemak (cost)
return $alt2['lemak'] > 0 ? $alt1['lemak_invers'] / $alt2['lemak_invers'] : 1;
case 'C2': // Natrium (cost)
return $alt2['natrium'] > 0 ? $alt1['natrium_invers'] / $alt2['natrium_invers'] : 1;
case 'C3': // Energi (benefit)
return $alt2['energi'] > 0 ? $alt1['energi'] / $alt2['energi'] : 1;
case 'C4': // Karbohidrat (benefit)
return $alt2['karbohidrat'] > 0 ? $alt1['karbohidrat'] / $alt2['karbohidrat'] : 1;
default:
return 1;
}
}
private function getNilaiKriteria($makanan, $kriteria)
{
switch (strtolower($kriteria->nama)) {
case 'energi':
return $makanan->energi;
case 'lemak':
return $makanan->lemak;
case 'karbohidrat':
return $makanan->karbohidrat;
case 'natrium':
return $makanan->natrium;
default:
return 0;
}
}
private function normalisasiNilai($nilai, $kriteria, $makanans)
{
// Hitung jumlah total untuk kriteria ini
$total = 0;
foreach ($makanans as $makanan) {
$nilaiKriteria = $this->getNilaiKriteria($makanan, $kriteria);
$total += $nilaiKriteria;
}
// Normalisasi sesuai perhitungan manual (nilai/jumlah)
// Hindari pembagian dengan 0
return $total > 0 ? $nilai / $total : 0;
}
}