load->database(); // ONLY CLI ACCESS if (!$this->input->is_cli_request()) { exit('No direct access allowed'); } } public function generate_insight() { $period = date('Y-m'); // ===================================================== // 🔥 REPLACE MODE (hapus data lama dulu) // ===================================================== $this->db->where('period', $period); $this->db->delete('ai_insights'); // ===================================================== // 1. FINANCE - OVERDUE INVOICE // ===================================================== $overdue = $this->db->query(" SELECT COUNT(*) as total FROM invoices WHERE status != 'paid' AND jatuh_tempo < CURDATE() AND deleted_at IS NULL ")->row()->total; if ($overdue > 0) { $this->db->insert('ai_insights', [ 'type' => 'finance', 'severity' => 'warning', 'message' => "$overdue invoice sudah melewati jatuh tempo", 'meta' => json_encode([ 'overdue_count' => (int)$overdue ]), 'period' => $period ]); } // ===================================================== // 2. FINANCE - CASHFLOW BULAN INI // ===================================================== $cash = $this->db->query(" SELECT COALESCE(SUM(jd.debit),0) as cash_in FROM journal_details jd JOIN journals j ON j.id = jd.journal_id WHERE MONTH(j.tanggal) = MONTH(CURDATE()) AND YEAR(j.tanggal) = YEAR(CURDATE()) ")->row()->cash_in; if ($cash > 0) { $this->db->insert('ai_insights', [ 'type' => 'finance', 'severity' => ($cash > 10000000) ? 'info' : 'warning', 'message' => "Cashflow bulan ini Rp " . number_format($cash), 'meta' => json_encode([ 'cash_in' => (float)$cash ]), 'period' => $period ]); } // ===================================================== // 3. STOCK - KRITIS // ===================================================== $stocks = $this->db->query(" SELECT i.kode_id, k.nama_barang, SUM(i.stok) as total_stok, k.limit_stock FROM items i JOIN kode_barang k ON k.id = i.kode_id GROUP BY i.kode_id, k.nama_barang, k.limit_stock HAVING total_stok <= k.limit_stock ")->result(); foreach ($stocks as $s) { $this->db->insert('ai_insights', [ 'type' => 'stock', 'severity' => 'critical', 'message' => "Stok {$s->nama_barang} kritis ({$s->total_stok})", 'meta' => json_encode([ 'kode_id' => $s->kode_id, 'item' => $s->nama_barang, 'stok' => (int)$s->total_stok, 'limit' => (int)$s->limit_stock ]), 'period' => $period ]); } // ===================================================== // 4. CUSTOMER - PAYMENT RISK // ===================================================== $lateCustomers = $this->db->query(" SELECT c.nama, COUNT(i.id) as total_invoice FROM invoices i JOIN customers c ON c.id = i.customer_id WHERE i.status != 'paid' AND i.jatuh_tempo < CURDATE() GROUP BY i.customer_id HAVING total_invoice > 2 ")->result(); foreach ($lateCustomers as $c) { $this->db->insert('ai_insights', [ 'type' => 'customer', 'severity' => 'warning', 'message' => "Customer {$c->nama} memiliki {$c->total_invoice} invoice overdue", 'meta' => json_encode([ 'customer' => $c->nama, 'overdue_invoice' => (int)$c->total_invoice ]), 'period' => $period ]); } // ===================================================== // RESPONSE // ===================================================== echo json_encode([ 'status' => true, 'message' => 'AI Insight generated (REPLACE MODE)', 'period' => $period ]); } // ===================================================== // CRONJOB UNTUK LIHAT KEHADIRAN SELAMA 1 BULAN // ===================================================== // | Kriteria | Pengurangan | // | ---------------------- | ------------------: | // | Alpha | -20 poin / hari | // | Terlambat | -2 poin / kejadian | // | Izin (permit) | -1 poin / hari | // | Akumulasi telat | -1 poin / 60 menit | // | Akumulasi pulang cepat | -1 poin / 120 menit | public function generateMonthlyAttendance() { $month = date('Y-m-01'); $startDate = date('Y-m-01', strtotime($month)); $endDate = date('Y-m-t', strtotime($month)); $employees = $this->db ->where('is_active', 1) ->get('k_employees') ->result(); $processed = 0; $this->db->trans_begin(); try { foreach ($employees as $employee) { // Total Hari Kerja $schedule = $this->db->query(" SELECT COUNT(*) AS total_hari_kerja FROM k_employee_shift_schedules WHERE employee_id = ? AND status IN ('published','done') AND schedule_date BETWEEN ? AND ? ", array( $employee->id, $startDate, $endDate ))->row(); $total_hari_kerja = (int) $schedule->total_hari_kerja; // Rekap Absensi $attendance = $this->db->query(" SELECT COALESCE(SUM(attendance_status='present'),0) AS total_present, COALESCE(SUM(attendance_status='late'),0) AS total_late, COALESCE(SUM(attendance_status='alpha'),0) AS total_alpha, COALESCE(SUM(attendance_status='sick'),0) AS total_sick, COALESCE(SUM(attendance_status='permit'),0) AS total_permit, COALESCE(SUM(attendance_status='annual'),0) AS total_annual, COALESCE(SUM(late_minutes),0) AS total_late_minutes, COALESCE(SUM(early_leave_minutes),0) AS total_early_leave_minutes, COALESCE(SUM(work_hours),0) AS total_work_hours, COALESCE(SUM(overtime_hours),0) AS total_overtime_hours FROM k_attendances WHERE employee_id = ? AND attendance_date BETWEEN ? AND ? ", array( $employee->id, $startDate, $endDate ))->row(); $total_present = (int) $attendance->total_present; $total_late = (int) $attendance->total_late; $total_alpha = (int) $attendance->total_alpha; $total_sick = (int) $attendance->total_sick; $total_permit = (int) $attendance->total_permit; $total_annual = (int) $attendance->total_annual; // Kehadiran $total_hadir = $total_present + $total_late; $attendance_percentage = 0; if ($total_hari_kerja > 0) { $attendance_percentage = round( ($total_hadir / $total_hari_kerja) * 100, 2 ); } // SCOR $score = 100; // Alpha $score -= ($total_alpha * 20); // Telat $score -= ($total_late * 2); // Izin $score -= ($total_permit * 1); // Total menit telat $score -= floor( ((int) $attendance->total_late_minutes) / 60 ); // Total menit pulang cepat $score -= floor( ((int) $attendance->total_early_leave_minutes) / 120 ); if ($score < 0) { $score = 0; } // LOGIKA STATUS if ($score >= 90) { $status = 'baik sekali'; } elseif ($score >= 75) { $status = 'baik'; } elseif ($score >= 60) { $status = 'kurang baik'; } else { $status = 'buruk'; } // INSERT OR UPDATE TO k_attendance_monthly $sql = " INSERT INTO k_attendance_monthly ( employee_id, month, total_present, total_late, total_alpha, total_sick, total_permit, total_annual, total_late_minutes, total_early_leave_minutes, total_work_hours, total_overtime_hours, total_hari_kerja, total_hadir, attendance_percentage, score, status ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ) ON DUPLICATE KEY UPDATE total_present = VALUES(total_present), total_late = VALUES(total_late), total_alpha = VALUES(total_alpha), total_sick = VALUES(total_sick), total_permit = VALUES(total_permit), total_annual = VALUES(total_annual), total_late_minutes = VALUES(total_late_minutes), total_early_leave_minutes = VALUES(total_early_leave_minutes), total_work_hours = VALUES(total_work_hours), total_overtime_hours = VALUES(total_overtime_hours), total_hari_kerja = VALUES(total_hari_kerja), total_hadir = VALUES(total_hadir), attendance_percentage = VALUES(attendance_percentage), score = VALUES(score), status = VALUES(status), updated_at = CURRENT_TIMESTAMP "; $this->db->query($sql, array( $employee->id, $month, $total_present, $total_late, $total_alpha, $total_sick, $total_permit, $total_annual, (int)$attendance->total_late_minutes, (int)$attendance->total_early_leave_minutes, (float)$attendance->total_work_hours, (float)$attendance->total_overtime_hours, $total_hari_kerja, $total_hadir, $attendance_percentage, $score, $status )); $processed++; } if ($this->db->trans_status() === FALSE) { throw new Exception('Transaction failed'); } $this->db->trans_commit(); echo json_encode(array( 'status' => true, 'month' => $month, 'processed' => $processed, 'message' => 'Monthly attendance generated successfully' )); } catch (Exception $e) { $this->db->trans_rollback(); echo json_encode(array( 'status' => false, 'month' => $month, 'message' => $e->getMessage() )); } } }