|
| 1 | +<?php |
| 2 | + |
| 3 | +namespace App\Controllers; |
| 4 | + |
| 5 | +use App\Models\MasjidFinanceTransactionModel; |
| 6 | +use App\Models\MasjidFinanceCategoryModel; |
| 7 | +use App\Models\MasjidProgramModel; |
| 8 | +use App\Libraries\SumoPodAI; |
| 9 | + |
| 10 | +class FinanceAI extends BaseController |
| 11 | +{ |
| 12 | + public function importCSV() |
| 13 | + { |
| 14 | + $masjidId = session()->get('masjid_id'); |
| 15 | + return view('dashboard/keuangan/import_csv', [ |
| 16 | + 'title' => 'Import Mutasi Bank (AI) - Masj.id' |
| 17 | + ]); |
| 18 | + } |
| 19 | + |
| 20 | + public function processCSV() |
| 21 | + { |
| 22 | + $masjidId = session()->get('masjid_id'); |
| 23 | + $file = $this->request->getFile('csv_file'); |
| 24 | + |
| 25 | + if (!$file || !$file->isValid() || $file->getExtension() !== 'csv') { |
| 26 | + return redirect()->back()->with('error', 'Silakan unggah file CSV yang valid.'); |
| 27 | + } |
| 28 | + |
| 29 | + $csvData = array_map('str_getcsv', file($file->getTempName())); |
| 30 | + if (count($csvData) < 2) { |
| 31 | + return redirect()->back()->with('error', 'File CSV kosong atau tidak valid.'); |
| 32 | + } |
| 33 | + |
| 34 | + // Asumsi format CSV: Tanggal, Deskripsi, Jumlah, Tipe (Masuk/Keluar) |
| 35 | + // Kita hanya butuh list transaksi untuk dianalisis AI |
| 36 | + $header = array_shift($csvData); |
| 37 | + |
| 38 | + $transactions = []; |
| 39 | + foreach ($csvData as $idx => $row) { |
| 40 | + if (count($row) >= 4) { |
| 41 | + $transactions[] = [ |
| 42 | + 'id' => $idx, |
| 43 | + 'date' => trim($row[0]), |
| 44 | + 'description' => trim($row[1]), |
| 45 | + 'amount' => (float) str_replace(['Rp', '.', ','], '', trim($row[2])), |
| 46 | + 'type' => strtolower(trim($row[3])) === 'keluar' ? 'pengeluaran' : 'pemasukan' |
| 47 | + ]; |
| 48 | + } |
| 49 | + } |
| 50 | + |
| 51 | + if (empty($transactions)) { |
| 52 | + return redirect()->back()->with('error', 'Tidak ada data transaksi yang dapat diproses.'); |
| 53 | + } |
| 54 | + |
| 55 | + // Get Categories |
| 56 | + $categoryModel = new MasjidFinanceCategoryModel(); |
| 57 | + $categories = $categoryModel->where('masjid_id', $masjidId)->findAll(); |
| 58 | + $catMap = []; |
| 59 | + foreach ($categories as $cat) { |
| 60 | + $catMap[$cat['type']][] = ['id' => $cat['id'], 'name' => $cat['name']]; |
| 61 | + } |
| 62 | + |
| 63 | + // Call AI |
| 64 | + $sumoPod = new SumoPodAI(); |
| 65 | + $prompt = "Anda adalah AI Akuntan Masjid. Tugas Anda adalah mengkategorikan transaksi bank berikut ke dalam kategori yang tepat.\n\n"; |
| 66 | + $prompt .= "Kategori Pemasukan Tersedia: " . json_encode($catMap['pemasukan'] ?? []) . "\n"; |
| 67 | + $prompt .= "Kategori Pengeluaran Tersedia: " . json_encode($catMap['pengeluaran'] ?? []) . "\n\n"; |
| 68 | + $prompt .= "Data Transaksi:\n" . json_encode($transactions) . "\n\n"; |
| 69 | + $prompt .= "Output HANYA dalam format array JSON valid dengan key:\n"; |
| 70 | + $prompt .= "- 'id' (id dari input)\n"; |
| 71 | + $prompt .= "- 'category_id' (id kategori yang paling cocok, jika tidak ada berikan null)\n"; |
| 72 | + $prompt .= "- 'suggested_category_name' (nama kategori yang dipilih atau saran kategori baru)\n"; |
| 73 | + $prompt .= "TIDAK ADA teks lain selain JSON."; |
| 74 | + |
| 75 | + $response = $sumoPod->chatCompletion($prompt, [ |
| 76 | + 'temperature' => 0.1, |
| 77 | + 'max_tokens' => 1500 |
| 78 | + ]); |
| 79 | + |
| 80 | + $aiResults = []; |
| 81 | + if ($response) { |
| 82 | + $response = str_replace(['```json', '```'], '', trim($response)); |
| 83 | + $aiResults = json_decode($response, true) ?? []; |
| 84 | + } |
| 85 | + |
| 86 | + // Merge AI Results with Transactions |
| 87 | + $mergedTransactions = []; |
| 88 | + foreach ($transactions as $t) { |
| 89 | + $aiMatch = array_filter($aiResults, fn($r) => $r['id'] === $t['id']); |
| 90 | + $aiMatch = reset($aiMatch); |
| 91 | + |
| 92 | + $t['category_id'] = $aiMatch['category_id'] ?? null; |
| 93 | + $t['suggested_category_name'] = $aiMatch['suggested_category_name'] ?? 'Lainnya'; |
| 94 | + $mergedTransactions[] = $t; |
| 95 | + } |
| 96 | + |
| 97 | + // Store temporarily in session for review page |
| 98 | + session()->set('temp_csv_transactions', $mergedTransactions); |
| 99 | + |
| 100 | + return redirect()->to('/dashboard/keuangan/review-csv'); |
| 101 | + } |
| 102 | + |
| 103 | + public function reviewCSV() |
| 104 | + { |
| 105 | + $transactions = session()->get('temp_csv_transactions'); |
| 106 | + if (!$transactions) { |
| 107 | + return redirect()->to('/dashboard/keuangan')->with('error', 'Tidak ada data CSV yang sedang direview.'); |
| 108 | + } |
| 109 | + |
| 110 | + $masjidId = session()->get('masjid_id'); |
| 111 | + $categoryModel = new MasjidFinanceCategoryModel(); |
| 112 | + $categories = $categoryModel->where('masjid_id', $masjidId)->findAll(); |
| 113 | + |
| 114 | + return view('dashboard/keuangan/review_csv', [ |
| 115 | + 'title' => 'Review Mutasi Bank - Masj.id', |
| 116 | + 'transactions' => $transactions, |
| 117 | + 'categories' => $categories |
| 118 | + ]); |
| 119 | + } |
| 120 | + |
| 121 | + public function saveCSV() |
| 122 | + { |
| 123 | + $masjidId = session()->get('masjid_id'); |
| 124 | + $data = $this->request->getPost('transactions'); |
| 125 | + |
| 126 | + if (empty($data)) { |
| 127 | + return redirect()->to('/dashboard/keuangan')->with('error', 'Tidak ada data yang disimpan.'); |
| 128 | + } |
| 129 | + |
| 130 | + $transactionModel = new MasjidFinanceTransactionModel(); |
| 131 | + $categoryModel = new MasjidFinanceCategoryModel(); |
| 132 | + |
| 133 | + $insertData = []; |
| 134 | + foreach ($data as $t) { |
| 135 | + if (!empty($t['date']) && !empty($t['amount'])) { |
| 136 | + // If AI suggested a new category or missing category |
| 137 | + $catId = $t['category_id']; |
| 138 | + if (empty($catId) && !empty($t['suggested_category_name'])) { |
| 139 | + $catType = $t['type']; |
| 140 | + $slug = url_title($t['suggested_category_name'], '-', true); |
| 141 | + |
| 142 | + // Check if exists |
| 143 | + $exist = $categoryModel->where('masjid_id', $masjidId)->where('slug', $slug)->first(); |
| 144 | + if ($exist) { |
| 145 | + $catId = $exist['id']; |
| 146 | + } else { |
| 147 | + // Create new category |
| 148 | + $catId = $categoryModel->insert([ |
| 149 | + 'masjid_id' => $masjidId, |
| 150 | + 'name' => $t['suggested_category_name'], |
| 151 | + 'slug' => $slug, |
| 152 | + 'type' => $catType |
| 153 | + ]); |
| 154 | + } |
| 155 | + } |
| 156 | + |
| 157 | + // Format date from dd/mm/yyyy or yyyy-mm-dd to yyyy-mm-dd |
| 158 | + $dateRaw = $t['date']; |
| 159 | + if (strpos($dateRaw, '/') !== false) { |
| 160 | + $parts = explode('/', $dateRaw); |
| 161 | + if (count($parts) === 3) { |
| 162 | + $dateRaw = $parts[2] . '-' . $parts[1] . '-' . $parts[0]; |
| 163 | + } |
| 164 | + } |
| 165 | + |
| 166 | + $insertData[] = [ |
| 167 | + 'masjid_id' => $masjidId, |
| 168 | + 'category_id' => $catId, |
| 169 | + 'date' => $dateRaw, |
| 170 | + 'amount' => $t['amount'], |
| 171 | + 'type' => $t['type'], |
| 172 | + 'description' => $t['description'] |
| 173 | + ]; |
| 174 | + } |
| 175 | + } |
| 176 | + |
| 177 | + if (!empty($insertData)) { |
| 178 | + $transactionModel->insertBatch($insertData); |
| 179 | + session()->remove('temp_csv_transactions'); |
| 180 | + return redirect()->to('/dashboard/keuangan')->with('success', count($insertData) . ' Transaksi berhasil disimpan.'); |
| 181 | + } |
| 182 | + |
| 183 | + return redirect()->back()->with('error', 'Gagal menyimpan transaksi.'); |
| 184 | + } |
| 185 | + |
| 186 | + public function generateReport() |
| 187 | + { |
| 188 | + $masjidId = session()->get('masjid_id'); |
| 189 | + $transactionModel = new MasjidFinanceTransactionModel(); |
| 190 | + $programModel = new MasjidProgramModel(); |
| 191 | + |
| 192 | + // Get this month's data |
| 193 | + $currentMonth = date('Y-m'); |
| 194 | + $transactions = $transactionModel->where('masjid_id', $masjidId)->like('date', $currentMonth)->findAll(); |
| 195 | + |
| 196 | + $totalPemasukan = 0; |
| 197 | + $totalPengeluaran = 0; |
| 198 | + foreach ($transactions as $t) { |
| 199 | + if ($t['type'] === 'pemasukan') $totalPemasukan += $t['amount']; |
| 200 | + if ($t['type'] === 'pengeluaran') $totalPengeluaran += $t['amount']; |
| 201 | + } |
| 202 | + |
| 203 | + $activePrograms = $programModel->where('masjid_id', $masjidId) |
| 204 | + ->where('date_end >=', date('Y-m-d')) |
| 205 | + ->countAllResults(); |
| 206 | + |
| 207 | + if ($this->request->getMethod() === 'POST' || $this->request->getMethod() === 'post') { |
| 208 | + $sumoPod = new SumoPodAI(); |
| 209 | + |
| 210 | + $prompt = "Kamu adalah Sekretaris Masjid yang profesional, hangat, dan komunikatif.\n"; |
| 211 | + $prompt .= "Buat draf narasi/copywriting Laporan Keuangan dan Kegiatan Bulanan yang cocok dikirim melalui WhatsApp Broadcast ke jamaah.\n"; |
| 212 | + $prompt .= "Data Bulan Ini (" . date('F Y') . "):\n"; |
| 213 | + $prompt .= "- Total Pemasukan: Rp " . number_format($totalPemasukan, 0, ',', '.') . "\n"; |
| 214 | + $prompt .= "- Total Pengeluaran: Rp " . number_format($totalPengeluaran, 0, ',', '.') . "\n"; |
| 215 | + $prompt .= "- Saldo Tersisa: Rp " . number_format($totalPemasukan - $totalPengeluaran, 0, ',', '.') . "\n"; |
| 216 | + $prompt .= "- Jumlah Program/Kegiatan Aktif: $activePrograms\n\n"; |
| 217 | + $prompt .= "Instruksi:\n"; |
| 218 | + $prompt .= "1. Gunakan gaya bahasa yang sopan, bersyukur (Alhamdulillah), dan mengapresiasi donatur.\n"; |
| 219 | + $prompt .= "2. Jangan terlalu kaku, gunakan sedikit emoji.\n"; |
| 220 | + $prompt .= "3. Output HARUS BERUPA TEKS LANGSUNG yang siap di-copy-paste, JANGAN format JSON."; |
| 221 | + |
| 222 | + $response = $sumoPod->chatCompletion($prompt, [ |
| 223 | + 'temperature' => 0.7, |
| 224 | + 'max_tokens' => 800 |
| 225 | + ]); |
| 226 | + |
| 227 | + return $this->response->setJSON(['status' => 'success', 'data' => $response]); |
| 228 | + } |
| 229 | + |
| 230 | + return view('dashboard/keuangan/report_generator', [ |
| 231 | + 'title' => 'Generate Laporan (AI) - Masj.id', |
| 232 | + 'totalPemasukan' => $totalPemasukan, |
| 233 | + 'totalPengeluaran' => $totalPengeluaran, |
| 234 | + 'activePrograms' => $activePrograms |
| 235 | + ]); |
| 236 | + } |
| 237 | +} |
0 commit comments