199 lines
6.6 KiB
PHP
Executable File
199 lines
6.6 KiB
PHP
Executable File
<?php
|
|
|
|
class Expense {
|
|
public static function all($startDate = null, $endDate = null) {
|
|
$db = Database::getInstance();
|
|
|
|
$sql = "SELECT e.*, u.username as created_by_name
|
|
FROM expenses e
|
|
LEFT JOIN users u ON e.created_by = u.id";
|
|
|
|
$params = [];
|
|
|
|
if ($startDate && $endDate) {
|
|
$sql .= " WHERE expense_date BETWEEN ? AND ?";
|
|
$params[] = $startDate;
|
|
$params[] = $endDate;
|
|
}
|
|
|
|
$sql .= " ORDER BY expense_date DESC";
|
|
|
|
return $db->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;
|
|
}
|
|
}
|