Mejorar Balance General con detalle de conceptos especiales y filtro por año
This commit is contained in:
@@ -533,6 +533,8 @@ switch ($page) {
|
||||
$balance = Report::getGeneralBalance();
|
||||
$expensesByCategory = Report::getExpensesByCategory();
|
||||
$accessibleHouseIds = Auth::getAccessibleHouseIds();
|
||||
$filterYear = $_GET['filter_year'] ?? null;
|
||||
$conceptDetails = Report::getConceptDetailsByYear($filterYear);
|
||||
|
||||
if ($reportType == 'water-debtors') {
|
||||
$filters = [
|
||||
@@ -754,6 +756,8 @@ switch ($page) {
|
||||
// Requerimos el modelo Report
|
||||
require_once __DIR__ . '/models/Report.php';
|
||||
$balance = Report::getGeneralBalance();
|
||||
$filterYear = $_GET['filter_year'] ?? null;
|
||||
$conceptDetails = Report::getConceptDetailsByYear($filterYear);
|
||||
include __DIR__ . '/views/reports/pdf_balance.php';
|
||||
break;
|
||||
}
|
||||
@@ -763,25 +767,63 @@ switch ($page) {
|
||||
exit;
|
||||
|
||||
case 'export_csv_balance':
|
||||
// Lógica para exportar CSV de balance
|
||||
header('Content-Type: text/csv');
|
||||
header('Content-Disposition: attachment; filename="Balance_General_IBIZA_' . date('Y') . '.csv"');
|
||||
fputs(fopen('php://output', 'w'), $bom = (chr(0xEF) . chr(0xBB) . chr(0xBF))); // BOM for UTF-8 in Excel
|
||||
|
||||
// Requerimos el modelo Report
|
||||
Auth::requireAdmin();
|
||||
require_once __DIR__ . '/models/Report.php';
|
||||
|
||||
$filterYear = $_GET['filter_year'] ?? null;
|
||||
$balance = Report::getGeneralBalance();
|
||||
$conceptDetails = Report::getConceptDetailsByYear($filterYear);
|
||||
|
||||
header('Content-Type: text/csv');
|
||||
$filename = 'Balance_General_IBIZA';
|
||||
if ($filterYear) {
|
||||
$filename .= '_' . $filterYear;
|
||||
}
|
||||
header('Content-Disposition: attachment; filename="' . $filename . '.csv"');
|
||||
fputs(fopen('php://output', 'w'), $bom = (chr(0xEF) . chr(0xBB) . chr(0xBF)));
|
||||
|
||||
$output = fopen('php://output', 'w');
|
||||
fputcsv($output, ['Condominio IBIZA-Cto Sierra Morena 152 - Balance General ' . date('Y')]);
|
||||
fputcsv($output, ['Condominio IBIZA-Cto Sierra Morena 152 - Balance General']);
|
||||
if ($filterYear) {
|
||||
fputcsv($output, ['Año: ' . $filterYear]);
|
||||
}
|
||||
fputcsv($output, ['']); // Blank line
|
||||
|
||||
fputcsv($output, ['Descripción', 'Monto']);
|
||||
fputcsv($output, ['Total Ingresos (Conceptos)', $balance['total_incomes']]);
|
||||
if (!Auth::isLector()) {
|
||||
fputcsv($output, ['Total Egresos', $balance['total_expenses']]);
|
||||
fputcsv($output, ['Balance Neto', $balance['balance']]);
|
||||
fputcsv($output, ['Total Egresos', $balance['total_expenses']]);
|
||||
fputcsv($output, ['Balance Neto', $balance['balance']]);
|
||||
fputcsv($output, ['']); // Blank line
|
||||
|
||||
if (!empty($conceptDetails['concepts'])) {
|
||||
fputcsv($output, ['Detalle de Conceptos Especiales']);
|
||||
fputcsv($output, ['']); // Blank line
|
||||
fputcsv($output, ['Concepto', 'Descripción', 'Esperado', 'Recaudado', 'Pendiente', 'Gastos', 'Balance']);
|
||||
|
||||
foreach ($conceptDetails['concepts'] as $cd) {
|
||||
fputcsv($output, [
|
||||
$cd['concept']['name'],
|
||||
$cd['concept']['description'] ?? '-',
|
||||
$cd['expected'],
|
||||
$cd['collected'],
|
||||
$cd['pending'],
|
||||
$cd['expenses'],
|
||||
$cd['balance']
|
||||
]);
|
||||
}
|
||||
|
||||
fputcsv($output, ['']); // Blank line
|
||||
fputcsv($output, [
|
||||
'TOTALES',
|
||||
'',
|
||||
$conceptDetails['totals']['expected'],
|
||||
$conceptDetails['totals']['collected'],
|
||||
$conceptDetails['totals']['pending'],
|
||||
$conceptDetails['totals']['expenses'],
|
||||
$conceptDetails['totals']['balance']
|
||||
]);
|
||||
}
|
||||
|
||||
fclose($output);
|
||||
exit;
|
||||
|
||||
|
||||
@@ -66,6 +66,92 @@ class Report {
|
||||
];
|
||||
}
|
||||
|
||||
public static function getConceptDetailsByYear($year = null) {
|
||||
$db = Database::getInstance();
|
||||
$concepts = $db->fetchAll(
|
||||
"SELECT c.id, c.name, c.amount_per_house, c.concept_date, c.description
|
||||
FROM finance_collection_concepts c
|
||||
WHERE c.is_active = 1
|
||||
ORDER BY c.concept_date DESC"
|
||||
);
|
||||
|
||||
$conceptsData = [];
|
||||
$totalExpected = 0;
|
||||
$totalCollected = 0;
|
||||
$totalExpenses = 0;
|
||||
|
||||
foreach ($concepts as $concept) {
|
||||
$conceptYear = date('Y', strtotime($concept['concept_date']));
|
||||
|
||||
if ($year && $conceptYear != $year) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$activeHousesCount = $db->fetchOne(
|
||||
"SELECT COUNT(*) as count FROM houses WHERE status = 'activa'"
|
||||
);
|
||||
$activeHousesCount = $activeHousesCount['count'] ?? 0;
|
||||
|
||||
$expected = $concept['amount_per_house'] * $activeHousesCount;
|
||||
|
||||
$whereClause = "WHERE cp.concept_id = ?";
|
||||
$params = [$concept['id']];
|
||||
|
||||
if ($year) {
|
||||
$whereClause .= " AND YEAR(cp.payment_date) = ?";
|
||||
$params[] = $year;
|
||||
}
|
||||
|
||||
$collected = $db->fetchOne(
|
||||
"SELECT COALESCE(SUM(cp.amount), 0) as total
|
||||
FROM finance_collection_payments cp
|
||||
$whereClause",
|
||||
$params
|
||||
);
|
||||
$collectedAmount = $collected['total'] ?? 0;
|
||||
|
||||
$expenses = $db->fetchOne(
|
||||
"SELECT COALESCE(SUM(ec.amount), 0) as total
|
||||
FROM expense_concept_allocations ec
|
||||
JOIN expenses e ON ec.expense_id = e.id
|
||||
WHERE ec.concept_id = ?
|
||||
" . ($year ? "AND YEAR(e.expense_date) = ?" : ""),
|
||||
$year ? [$concept['id'], $year] : [$concept['id']]
|
||||
);
|
||||
$expensesAmount = $expenses['total'] ?? 0;
|
||||
|
||||
$balance = $collectedAmount - $expensesAmount;
|
||||
|
||||
$conceptsData[] = [
|
||||
'concept' => $concept,
|
||||
'year' => $conceptYear,
|
||||
'expected' => $expected,
|
||||
'collected' => $collectedAmount,
|
||||
'expenses' => $expensesAmount,
|
||||
'balance' => $balance,
|
||||
'pending' => max(0, $expected - $collectedAmount)
|
||||
];
|
||||
|
||||
$totalExpected += $expected;
|
||||
$totalCollected += $collectedAmount;
|
||||
$totalExpenses += $expensesAmount;
|
||||
}
|
||||
|
||||
$totalBalance = $totalCollected - $totalExpenses;
|
||||
|
||||
return [
|
||||
'concepts' => $conceptsData,
|
||||
'totals' => [
|
||||
'expected' => $totalExpected,
|
||||
'collected' => $totalCollected,
|
||||
'expenses' => $totalExpenses,
|
||||
'balance' => $totalBalance,
|
||||
'pending' => max(0, $totalExpected - $totalCollected)
|
||||
],
|
||||
'year' => $year
|
||||
];
|
||||
}
|
||||
|
||||
public static function getHouseStatement($houseId, $year = null) {
|
||||
$db = Database::getInstance();
|
||||
$house = House::findById($houseId);
|
||||
|
||||
@@ -521,6 +521,60 @@ function exportConceptsCSV() {
|
||||
</script>
|
||||
|
||||
<?php else: ?>
|
||||
<div class="card mb-4">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="bi bi-funnel"></i> Filtro por Año
|
||||
</h5>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" data-bs-toggle="collapse" data-bs-target="#yearFilterCollapse">
|
||||
<i class="bi bi-chevron-down"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="collapse show" id="yearFilterCollapse">
|
||||
<div class="card-body">
|
||||
<form id="yearFilter">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Año</label>
|
||||
<select name="filter_year" class="form-select">
|
||||
<option value="">Todos los años</option>
|
||||
<?php
|
||||
$years = range(2024, date('Y') + 1);
|
||||
foreach ($years as $y): ?>
|
||||
<option value="<?= $y ?>" <?= ($_GET['filter_year'] ?? '') == $y ? 'selected' : '' ?>><?= $y ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-12">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="bi bi-search"></i> Aplicar Filtro
|
||||
</button>
|
||||
<a href="/dashboard.php?page=reportes" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-x-circle"></i> Limpiar Filtro
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.getElementById('yearFilter').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(this);
|
||||
const params = new URLSearchParams();
|
||||
|
||||
if (formData.get('filter_year')) {
|
||||
params.append('filter_year', formData.get('filter_year'));
|
||||
}
|
||||
|
||||
window.location.href = '/dashboard.php?page=reportes&' + params.toString();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<div class="row g-4 mb-4">
|
||||
<div class="col-md-3">
|
||||
@@ -596,33 +650,75 @@ function exportConceptsCSV() {
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="col-md-<?= Auth::isLector() ? '12' : '6' ?>">
|
||||
<div class="col-md-<?= Auth::isLector() ? '12' : '12' ?>">
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5 class="card-title mb-0">Resumen Financiero</h5>
|
||||
<h5 class="card-title mb-0">
|
||||
Resumen Financiero
|
||||
<?php if (!empty($conceptDetails['year'])): ?>
|
||||
<small class="text-muted">(<?= $conceptDetails['year'] ?>)</small>
|
||||
<?php endif; ?>
|
||||
</h5>
|
||||
<button onclick="exportBalancePDF()" class="btn btn-outline-primary btn-sm">
|
||||
<i class="bi bi-file-earmark-pdf"></i> PDF
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<table class="table table-sm">
|
||||
<tr>
|
||||
<td>Total Ingresos (Conceptos):</td>
|
||||
<td class="text-end text-success">$<?= number_format($balance['total_incomes'], 2) ?></td>
|
||||
</tr>
|
||||
<?php if (!Auth::isLector()): ?>
|
||||
<tr class="table-light">
|
||||
<td>Total Egresos:</td>
|
||||
<td class="text-end text-danger">$<?= number_format($balance['total_expenses'], 2) ?></td>
|
||||
</tr>
|
||||
<tr class="table-dark">
|
||||
<td><strong>Balance:</strong></td>
|
||||
<td class="text-end fw-bold <?= $balance['balance'] >= 0 ? 'text-success' : 'text-danger' ?>">
|
||||
$<?= number_format($balance['balance'], 2) ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</table>
|
||||
<?php if (empty($conceptDetails['concepts'])): ?>
|
||||
<p class="text-muted">No hay conceptos especiales registrados para el año seleccionado.</p>
|
||||
<?php else: ?>
|
||||
<table class="table table-sm table-bordered">
|
||||
<thead class="table-primary">
|
||||
<tr>
|
||||
<th>Concepto</th>
|
||||
<th class="text-end">Esperado</th>
|
||||
<th class="text-end">Recaudado</th>
|
||||
<th class="text-end">Pendiente</th>
|
||||
<th class="text-end">Gastos Asociados</th>
|
||||
<th class="text-end">Balance</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($conceptDetails['concepts'] as $cd): ?>
|
||||
<tr>
|
||||
<td>
|
||||
<strong><?= htmlspecialchars($cd['concept']['name']) ?></strong>
|
||||
<?php if (!empty($cd['concept']['description'])): ?>
|
||||
<small class="text-muted d-block"><?= htmlspecialchars($cd['concept']['description']) ?></small>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td class="text-end">$<?= number_format($cd['expected'], 2) ?></td>
|
||||
<td class="text-end text-success">$<?= number_format($cd['collected'], 2) ?></td>
|
||||
<td class="text-end text-warning">$<?= number_format($cd['pending'], 2) ?></td>
|
||||
<?php if (!Auth::isLector()): ?>
|
||||
<td class="text-end text-danger">$<?= number_format($cd['expenses'], 2) ?></td>
|
||||
<td class="text-end fw-bold <?= $cd['balance'] >= 0 ? 'text-success' : 'text-danger' ?>">
|
||||
$<?= number_format($cd['balance'], 2) ?>
|
||||
</td>
|
||||
<?php else: ?>
|
||||
<td class="text-end" colspan="2">-</td>
|
||||
<?php endif; ?>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
<tfoot class="table-dark">
|
||||
<tr>
|
||||
<th><strong>TOTALES:</strong></th>
|
||||
<th class="text-end">$<?= number_format($conceptDetails['totals']['expected'], 2) ?></th>
|
||||
<th class="text-end">$<?= number_format($conceptDetails['totals']['collected'], 2) ?></th>
|
||||
<th class="text-end">$<?= number_format($conceptDetails['totals']['pending'], 2) ?></th>
|
||||
<?php if (!Auth::isLector()): ?>
|
||||
<th class="text-end">$<?= number_format($conceptDetails['totals']['expenses'], 2) ?></th>
|
||||
<th class="text-end fw-bold <?= $conceptDetails['totals']['balance'] >= 0 ? 'text-success' : 'text-danger' ?>">
|
||||
$<?= number_format($conceptDetails['totals']['balance'], 2) ?>
|
||||
</th>
|
||||
<?php else: ?>
|
||||
<th class="text-end" colspan="2">-</th>
|
||||
<?php endif; ?>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -666,11 +762,21 @@ function exportConceptsCSV() {
|
||||
|
||||
<script>
|
||||
function exportBalancePDF() {
|
||||
window.open('/dashboard.php?page=reportes_actions&action=export_pdf_report&type=balance', '_blank');
|
||||
const filterYear = '<?= $_GET['filter_year'] ?? '' ?>';
|
||||
let url = '/dashboard.php?page=reportes_actions&action=export_pdf_report&type=balance';
|
||||
if (filterYear) {
|
||||
url += '&filter_year=' + filterYear;
|
||||
}
|
||||
window.open(url, '_blank');
|
||||
}
|
||||
|
||||
function exportBalanceCSV() {
|
||||
window.open('/dashboard.php?page=reportes_actions&action=export_csv_balance', '_blank');
|
||||
const filterYear = '<?= $_GET['filter_year'] ?? '' ?>';
|
||||
let url = '/dashboard.php?page=reportes_actions&action=export_csv_balance';
|
||||
if (filterYear) {
|
||||
url += '&filter_year=' + filterYear;
|
||||
}
|
||||
window.open(url, '_blank');
|
||||
}
|
||||
|
||||
function exportExpensesPDF() {
|
||||
|
||||
@@ -58,3 +58,64 @@
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<br>
|
||||
|
||||
<?php if (!empty($conceptDetails['concepts'])): ?>
|
||||
<h3 style="text-align: center; margin-bottom: 10px;">Detalle de Conceptos Especiales</h3>
|
||||
<?php if (!empty($conceptDetails['year'])): ?>
|
||||
<p style="text-align: center; font-size: 9pt; margin-bottom: 15px;">Año: <?= $conceptDetails['year'] ?></p>
|
||||
<?php endif; ?>
|
||||
|
||||
<table style="margin-bottom: 10px;">
|
||||
<thead style="background-color: #0d6efd; color: white;">
|
||||
<tr>
|
||||
<th>Concepto</th>
|
||||
<th>Esperado</th>
|
||||
<th>Recaudado</th>
|
||||
<th>Pendiente</th>
|
||||
<th>Gastos</th>
|
||||
<th>Balance</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($conceptDetails['concepts'] as $cd): ?>
|
||||
<tr>
|
||||
<td style="text-align: left;">
|
||||
<strong><?= htmlspecialchars($cd['concept']['name']) ?></strong>
|
||||
<?php if (!empty($cd['concept']['description'])): ?>
|
||||
<br><small style="color: #666;"><?= htmlspecialchars($cd['concept']['description']) ?></small>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>$<?= number_format($cd['expected'], 2) ?></td>
|
||||
<td class="text-success">$<?= number_format($cd['collected'], 2) ?></td>
|
||||
<td class="text-warning">$<?= number_format($cd['pending'], 2) ?></td>
|
||||
<?php if (!Auth::isLector()): ?>
|
||||
<td class="text-danger">$<?= number_format($cd['expenses'], 2) ?></td>
|
||||
<td class="text-end fw-bold <?= $cd['balance'] >= 0 ? 'text-success' : 'text-danger' ?>">
|
||||
$<?= number_format($cd['balance'], 2) ?>
|
||||
</td>
|
||||
<?php else: ?>
|
||||
<td colspan="2">-</td>
|
||||
<?php endif; ?>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
<tfoot style="background-color: #343a40; color: white; font-weight: bold;">
|
||||
<tr>
|
||||
<td style="text-align: left;">TOTALES:</td>
|
||||
<td>$<?= number_format($conceptDetails['totals']['expected'], 2) ?></td>
|
||||
<td>$<?= number_format($conceptDetails['totals']['collected'], 2) ?></td>
|
||||
<td>$<?= number_format($conceptDetails['totals']['pending'], 2) ?></td>
|
||||
<?php if (!Auth::isLector()): ?>
|
||||
<td>$<?= number_format($conceptDetails['totals']['expenses'], 2) ?></td>
|
||||
<td class="text-end <?= $conceptDetails['totals']['balance'] >= 0 ? 'text-success' : 'text-danger' ?>">
|
||||
$<?= number_format($conceptDetails['totals']['balance'], 2) ?>
|
||||
</td>
|
||||
<?php else: ?>
|
||||
<td colspan="2">-</td>
|
||||
<?php endif; ?>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
<?php endif; ?>
|
||||
|
||||
Reference in New Issue
Block a user