fetchAll($sql, $params); } public static function findById($id) { $db = Database::getInstance(); return $db->fetchOne( "SELECT * FROM expenses WHERE id = ?", [$id] ); } public static function create($data, $userId) { $db = Database::getInstance(); $db->execute( "INSERT INTO expenses (description, amount, expense_date, category, receipt_path, notes, created_by) VALUES (?, ?, ?, ?, ?, ?, ?)", [ $data['description'], $data['amount'], $data['expense_date'], $data['category'] ?? null, $data['receipt_path'] ?? null, $data['notes'] ?? null, $userId ] ); return $db->lastInsertId(); } public static function update($id, $data) { $db = Database::getInstance(); $db->execute( "UPDATE expenses SET description = ?, amount = ?, expense_date = ?, category = ?, receipt_path = ?, notes = ? WHERE id = ?", [ $data['description'], $data['amount'], $data['expense_date'], $data['category'] ?? null, $data['receipt_path'] ?? null, $data['notes'] ?? null, $id ] ); return $id; } public static function save($data, $userId, $receiptFile = null, $allocations = []) { $db = Database::getInstance(); $isUpdate = isset($data['id']) && !empty($data['id']); $expenseId = $data['id'] ?? null; // Handle receipt upload $receipt_path = $data['receipt_path'] ?? null; // Keep existing path if not new file if ($receiptFile && $receiptFile['error'] === UPLOAD_ERR_OK) { $uploadDir = __DIR__ . '/../uploads/expenses/'; if (!is_dir($uploadDir)) { mkdir($uploadDir, 0777, true); } $filename = uniqid('receipt_') . '_' . basename($receiptFile['name']); $targetPath = $uploadDir . $filename; if (move_uploaded_file($receiptFile['tmp_name'], $targetPath)) { $receipt_path = '/uploads/expenses/' . $filename; } else { // Handle upload error, maybe return false or throw exception error_log("Failed to move uploaded file: " . $receiptFile['tmp_name'] . " to " . $targetPath); return false; } } elseif ($isUpdate && empty($data['existing_receipt'])) { // If it's an update and no new file, and existing_receipt is empty, clear the path $receipt_path = null; } $data['receipt_path'] = $receipt_path; if ($isUpdate) { self::update($expenseId, $data); } else { $expenseId = self::create($data, $userId); } // Save allocations if ($expenseId && !empty($allocations)) { self::saveAllocations($expenseId, $allocations); } else if ($expenseId && $isUpdate) { // If update and no allocations, clear existing ones $db->execute("DELETE FROM expense_concept_allocations WHERE expense_id = ?", [$expenseId]); } return $expenseId; } public static function saveAllocations($expenseId, $allocations) { $db = Database::getInstance(); // Delete existing allocations for this expense $db->execute("DELETE FROM expense_concept_allocations WHERE expense_id = ?", [$expenseId]); foreach ($allocations as $allocation) { $db->execute( "INSERT INTO expense_concept_allocations (expense_id, concept_id, amount) VALUES (?, ?, ?)", [$expenseId, $allocation['concept_id'], $allocation['amount']] ); } } public static function delete($id) { $db = Database::getInstance(); // Optionally delete associated receipt file $expense = self::findById($id); if ($expense && $expense['receipt_path']) { $filePath = __DIR__ . '/..' . $expense['receipt_path']; if (file_exists($filePath)) { unlink($filePath); } } // Delete allocations first $db->execute("DELETE FROM expense_concept_allocations WHERE expense_id = ?", [$id]); return $db->execute( "DELETE FROM expenses WHERE id = ?", [$id] ); } public static function getTotalByDateRange($startDate, $endDate) { $db = Database::getInstance(); $result = $db->fetchOne( "SELECT COALESCE(SUM(amount), 0) as total FROM expenses WHERE expense_date BETWEEN ? AND ?", [$startDate, $endDate] ); return $result['total'] ?? 0; } public static function getTotalByCategory($category, $startDate = null, $endDate = null) { $db = Database::getInstance(); $sql = "SELECT COALESCE(SUM(amount), 0) as total FROM expenses WHERE category = ?"; $params = [$category]; if ($startDate && $endDate) { $sql .= " AND expense_date BETWEEN ? AND ?"; $params[] = $startDate; $params[] = $endDate; } $result = $db->fetchOne($sql, $params); return $result['total'] ?? 0; } public static function getConcepts($expenseId) { $db = Database::getInstance(); return $db->fetchAll( "SELECT ec.*, c.name as concept_name FROM expense_concept_allocations ec JOIN finance_collection_concepts c ON ec.concept_id = c.id WHERE ec.expense_id = ?", [$expenseId] ); } public static function getTotalByConcept($conceptId) { $db = Database::getInstance(); $result = $db->fetchOne( "SELECT COALESCE(SUM(amount), 0) as total FROM expense_concept_allocations WHERE concept_id = ?", [$conceptId] ); return $result['total'] ?? 0; } }