Agregar reporte de Conceptos Especiales con filtros y exportación PDF/CSV

This commit is contained in:
Administrador Ibiza
2025-12-30 23:25:16 -06:00
parent 6c5baacc87
commit d629526485
4 changed files with 418 additions and 3 deletions

View File

@@ -649,10 +649,14 @@ switch ($page) {
'balance' => 'Balance_General',
'expenses' => 'Gastos_por_Categoria',
'water-debtors' => 'Deudores_de_Agua',
'concept-debtors' => 'Deudores_de_Conceptos'
'concept-debtors' => 'Deudores_de_Conceptos',
'concepts' => 'Conceptos_Especiales'
];
$reportName = $reportNames[$reportType] ?? ucfirst($reportType);
// Para conceptos, definir nombre personalizado del archivo PDF
$pdfFilename = $reportName . '_IBIZA_' . $year . '.pdf';
// Incluir TCPDF
require_once __DIR__ . '/vendor/autoload.php';
require_once __DIR__ . '/vendor/tecnickcom/tcpdf/tcpdf.php';
@@ -709,6 +713,42 @@ switch ($page) {
$expensesByCategory = Report::getExpensesByCategory();
include __DIR__ . '/views/reports/pdf_expenses.php';
break;
case 'concepts':
// Requerimos los modelos necesarios
require_once __DIR__ . '/models/CollectionConcept.php';
require_once __DIR__ . '/models/Expense.php';
$filterConceptId = $_GET['filter_concept'] ?? null;
$conceptsToExport = [];
if ($filterConceptId) {
$concept = CollectionConcept::findById($filterConceptId);
if ($concept) {
$status = CollectionConcept::getCollectionStatus($filterConceptId);
$payments = CollectionConcept::getPaymentsByConcept($filterConceptId);
$conceptsToExport[] = [
'concept' => $concept,
'status' => $status,
'payments' => $payments
];
$conceptName = preg_replace('/[^a-zA-Z0-9_]/', '_', $concept['name']);
$pdfFilename = 'Concepto_' . $conceptName . '_IBIZA.pdf';
}
} else {
$allConcepts = CollectionConcept::all(true);
foreach ($allConcepts as $c) {
$status = CollectionConcept::getCollectionStatus($c['id']);
$payments = CollectionConcept::getPaymentsByConcept($c['id']);
$conceptsToExport[] = [
'concept' => $c,
'status' => $status,
'payments' => $payments
];
}
}
include __DIR__ . '/views/reports/pdf_concepts.php';
break;
case 'balance':
default:
// Requerimos el modelo Report
@@ -719,7 +759,7 @@ switch ($page) {
}
$html = ob_get_clean();
$pdf->writeHTML($html, true, false, true, false, '');
$pdf->Output($reportName . '_IBIZA_' . $year . '.pdf', 'D');
$pdf->Output($pdfFilename, 'D');
exit;
case 'export_csv_balance':
@@ -771,6 +811,86 @@ switch ($page) {
fclose($output);
exit;
case 'export_csv_concepts':
Auth::requireAdmin();
require_once __DIR__ . '/models/CollectionConcept.php';
$filterConceptId = $_GET['filter_concept'] ?? null;
$conceptsToExport = [];
if ($filterConceptId) {
$concept = CollectionConcept::findById($filterConceptId);
if ($concept) {
$status = CollectionConcept::getCollectionStatus($filterConceptId);
$payments = CollectionConcept::getPaymentsByConcept($filterConceptId);
$conceptsToExport[] = [
'concept' => $concept,
'status' => $status,
'payments' => $payments
];
}
} else {
$allConcepts = CollectionConcept::all(true);
foreach ($allConcepts as $c) {
$status = CollectionConcept::getCollectionStatus($c['id']);
$payments = CollectionConcept::getPaymentsByConcept($c['id']);
$conceptsToExport[] = [
'concept' => $c,
'status' => $status,
'payments' => $payments
];
}
}
header('Content-Type: text/csv');
if ($filterConceptId && !empty($conceptsToExport)) {
$conceptName = $conceptsToExport[0]['concept']['name'];
$conceptName = preg_replace('/[^a-zA-Z0-9_]/', '_', $conceptName);
$filename = 'Concepto_' . $conceptName . '_IBIZA';
} else {
$filename = 'Conceptos_Especiales_IBIZA';
}
header('Content-Disposition: attachment; filename="' . $filename . '.csv"');
fputs(fopen('php://output', 'w'), $bom = (chr(0xEF) . chr(0xBB) . chr(0xBF)));
$output = fopen('php://output', 'w');
foreach ($conceptsToExport as $conceptData) {
$concept = $conceptData['concept'];
$status = $conceptData['status'];
$payments = $conceptData['payments'];
fputcsv($output, ['Condominio IBIZA-Cto Sierra Morena 152 - Concepto: ' . htmlspecialchars($concept['name'])]);
fputcsv($output, ['Fecha: ' . date('d/m/Y', strtotime($concept['concept_date']))]);
if (!empty($concept['description'])) {
fputcsv($output, ['Descripción: ' . htmlspecialchars($concept['description'])]);
}
fputcsv($output, ['']); // Blank line
fputcsv($output, ['Monto por Casa:', $concept['amount_per_house']]);
fputcsv($output, ['Total Recaudado:', $status['total_collected']]);
fputcsv($output, ['Total Esperado:', $status['total_expected']]);
fputcsv($output, ['Balance:', $status['total_collected']]);
fputcsv($output, ['']); // Blank line
fputcsv($output, ['Casa', 'Propietario', 'Monto Pagado', 'Fecha de Pago']);
foreach ($payments as $payment) {
fputcsv($output, [
$payment['house_number'],
$payment['owner_name'] ?? '-',
$payment['amount'] > 0 ? $payment['amount'] : 0,
$payment['payment_date'] ? date('d/m/Y', strtotime($payment['payment_date'])) : '-'
]);
}
fputcsv($output, ['', '', 'Total:', $status['total_collected']]);
fputcsv($output, ['']); // Blank line para separar conceptos
}
fclose($output);
exit;
default:
// Si no es una acción de exportación conocida, continuar al default
break;

View File

@@ -185,7 +185,7 @@ class CollectionPayment {
public static function initializePayments($conceptId, $userId) {
require_once __DIR__ . '/House.php'; // Incluir House model
$houses = House::allActive(); // Obtener solo casas activas
$houses = House::getActive(); // Obtener solo casas activas
$db = Database::getInstance();
$db->beginTransaction();

View File

@@ -16,6 +16,9 @@
<a href="/dashboard.php?page=reportes&type=concept-debtors" class="btn btn-outline-warning <?= ($_GET['type'] ?? '') == 'concept-debtors' ? 'active' : '' ?>">
<i class="bi bi-cash-coin"></i> Deudores de Conceptos
</a>
<a href="/dashboard.php?page=reportes&type=concepts" class="btn btn-outline-info <?= ($_GET['type'] ?? '') == 'concepts' ? 'active' : '' ?>">
<i class="bi bi-collection"></i> Conceptos Especiales
</a>
</div>
</div>
@@ -307,6 +310,216 @@ function exportConceptDebtorsPDF() {
}
</script>
<?php elseif ($reportType == 'concepts'): ?>
<div class="card mb-4">
<div class="card-header d-flex justify-content-between align-items-center">
<div class="d-flex align-items-center">
<h5 class="card-title mb-0">
<i class="bi bi-funnel"></i> Filtros de Conceptos Especiales
</h5>
<button type="button" class="btn btn-sm btn-outline-secondary ms-3" data-bs-toggle="collapse" data-bs-target="#conceptsFilterCollapse">
<i class="bi bi-chevron-down"></i>
</button>
</div>
<div class="d-flex gap-2">
<button onclick="exportConceptsPDF()" class="btn btn-outline-primary btn-sm">
<i class="bi bi-file-earmark-pdf"></i> PDF
</button>
<?php if (Auth::isAdmin()): ?>
<button onclick="exportConceptsCSV()" class="btn btn-outline-success btn-sm">
<i class="bi bi-file-earmark-csv"></i> CSV
</button>
<?php endif; ?>
</div>
</div>
<div class="collapse show" id="conceptsFilterCollapse">
<div class="card-body">
<form id="conceptsFilter">
<div class="row g-3">
<div class="col-md-12">
<label class="form-label">Concepto</label>
<select name="filter_concept" class="form-select">
<option value="">Todos los conceptos</option>
<?php
require_once __DIR__ . '/../../models/CollectionConcept.php';
$allConcepts = CollectionConcept::all(true);
foreach ($allConcepts as $c): ?>
<option value="<?= $c['id'] ?>" <?= ($_GET['filter_concept'] ?? '') == $c['id'] ? 'selected' : '' ?>>
<?= htmlspecialchars($c['name']) ?> - <?= date('d/m/Y', strtotime($c['concept_date'])) ?>
</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 Filtros
</button>
<a href="/dashboard.php?page=reportes&type=concepts" class="btn btn-outline-secondary">
<i class="bi bi-x-circle"></i> Limpiar Filtros
</a>
</div>
</div>
</form>
</div>
</div>
</div>
<?php
$filterConceptId = $_GET['filter_concept'] ?? null;
$conceptsToDisplay = [];
if ($filterConceptId) {
$concept = CollectionConcept::findById($filterConceptId);
if ($concept) {
$status = CollectionConcept::getCollectionStatus($filterConceptId);
$payments = CollectionConcept::getPaymentsByConcept($filterConceptId);
$conceptsToDisplay[] = [
'concept' => $concept,
'status' => $status,
'payments' => $payments
];
}
} else {
$allConcepts = CollectionConcept::all(true);
foreach ($allConcepts as $c) {
$status = CollectionConcept::getCollectionStatus($c['id']);
$payments = CollectionConcept::getPaymentsByConcept($c['id']);
$conceptsToDisplay[] = [
'concept' => $c,
'status' => $status,
'payments' => $payments
];
}
}
?>
<?php foreach ($conceptsToDisplay as $conceptData): ?>
<?php $concept = $conceptData['concept']; $status = $conceptData['status']; $payments = $conceptData['payments']; ?>
<div class="card mb-4">
<div class="card-header">
<h5 class="card-title mb-0">
<i class="bi bi-collection"></i> <?= htmlspecialchars($concept['name']) ?>
<small class="text-muted">(<?= date('d/m/Y', strtotime($concept['concept_date'])) ?>)</small>
</h5>
</div>
<div class="card-body">
<div class="row g-4 mb-4">
<div class="col-md-3">
<div class="card border-primary">
<div class="card-body text-center">
<h6 class="text-muted">Monto por Casa</h6>
<h5 class="text-primary">$<?= number_format($concept['amount_per_house'], 2) ?></h5>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card border-success">
<div class="card-body text-center">
<h6 class="text-muted">Recaudado</h6>
<h5 class="text-success">$<?= number_format($status['total_collected'], 2) ?></h5>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card border-info">
<div class="card-body text-center">
<h6 class="text-muted">Esperado</h6>
<h5 class="text-info">$<?= number_format($status['total_expected'], 2) ?></h5>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card <?= $status['balance'] >= 0 ? 'border-success' : 'border-danger' ?>">
<div class="card-body text-center">
<h6 class="text-muted">Balance</h6>
<h5 class="<?= $status['balance'] >= 0 ? 'text-success' : 'text-danger' ?>">
$<?= number_format($status['total_collected'], 2) ?>
</h5>
</div>
</div>
</div>
</div>
<h6 class="mb-3">Pagos por Casa</h6>
<?php if (empty($payments)): ?>
<div class="alert alert-info text-center">
No hay pagos registrados para este concepto.
</div>
<?php else: ?>
<div class="table-responsive">
<table class="table table-sm table-bordered">
<thead class="table-primary">
<tr>
<th>Casa</th>
<th>Propietario</th>
<th>Monto Pagado</th>
<th>Fecha de Pago</th>
</tr>
</thead>
<tbody>
<?php foreach ($payments as $payment):
$rowClass = $payment['amount'] > 0 ? 'table-success' : 'table-light';
?>
<tr class="<?= $rowClass ?>">
<td><strong><?= $payment['house_number'] ?></strong></td>
<td><?= htmlspecialchars($payment['owner_name'] ?? '-') ?></td>
<td class="text-end">
<?= $payment['amount'] > 0 ? '$' . number_format($payment['amount'], 2) : '-' ?>
</td>
<td>
<?= $payment['payment_date'] ? date('d/m/Y', strtotime($payment['payment_date'])) : '-' ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
<tfoot class="table-dark">
<tr>
<th colspan="2" class="text-end">Total Recaudado:</th>
<th class="text-end">$<?= number_format($status['total_collected'], 2) ?></th>
<th></th>
</tr>
</tfoot>
</table>
</div>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
<script>
document.getElementById('conceptsFilter').addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(this);
const params = new URLSearchParams();
if (formData.get('filter_concept')) {
params.append('filter_concept', formData.get('filter_concept'));
}
window.location.href = '/dashboard.php?page=reportes&type=concepts&' + params.toString();
});
function exportConceptsPDF() {
const conceptId = '<?= $filterConceptId ?? '' ?>';
let url = '/dashboard.php?page=reportes_actions&action=export_pdf_report&type=concepts';
if (conceptId) {
url += '&filter_concept=' + conceptId;
}
window.open(url, '_blank');
}
function exportConceptsCSV() {
const conceptId = '<?= $filterConceptId ?? '' ?>';
let url = '/dashboard.php?page=reportes_actions&action=export_csv_concepts';
if (conceptId) {
url += '&filter_concept=' + conceptId;
}
window.open(url, '_blank');
}
</script>
<?php else: ?>
<div class="row g-4 mb-4">

View File

@@ -0,0 +1,82 @@
<style>
table {
width: 100%;
border-collapse: collapse;
font-size: 9pt;
}
th, td {
border: 1px solid #000;
padding: 4px;
}
th {
background-color: #eee;
}
</style>
<?php
$conceptCount = count($conceptsToExport);
foreach ($conceptsToExport as $index => $conceptData):
$concept = $conceptData['concept'];
$status = $conceptData['status'];
$payments = $conceptData['payments'];
?>
<h2><?= htmlspecialchars($concept['name']) ?></h2>
<p><strong>Fecha:</strong> <?= date('d/m/Y', strtotime($concept['concept_date'])) ?></p>
<?php if (!empty($concept['description'])): ?>
<p><strong>Descripción:</strong> <?= htmlspecialchars($concept['description']) ?></p>
<?php endif; ?>
<table border="1" cellpadding="5">
<tr>
<td style="background-color: #e3f2fd;"><strong>Monto por Casa:</strong></td>
<td style="background-color: #e3f2fd; text-align: right; font-weight: bold;">$<?= number_format($concept['amount_per_house'], 2) ?></td>
<td style="background-color: #d4edda;"><strong>Recaudado:</strong></td>
<td style="background-color: #d4edda; text-align: right; font-weight: bold;">$<?= number_format($status['total_collected'], 2) ?></td>
</tr>
<tr>
<td style="background-color: #e3f2fd;"><strong>Esperado:</strong></td>
<td style="background-color: #e3f2fd; text-align: right; font-weight: bold;">$<?= number_format($status['total_expected'], 2) ?></td>
<td style="background-color: #d4edda;"><strong>Balance:</strong></td>
<td style="background-color: #d4edda; text-align: right; font-weight: bold;">$<?= number_format($status['total_collected'], 2) ?></td>
</tr>
</table>
<h3>Pagos por Casa</h3>
<table border="1" cellpadding="5">
<thead style="background-color: #0d6efd; color: white;">
<tr>
<th>Casa</th>
<th>Propietario</th>
<th style="text-align: right;">Monto Pagado</th>
<th>Fecha de Pago</th>
</tr>
</thead>
<tbody>
<?php foreach ($payments as $payment):
$bgColor = $payment['amount'] > 0 ? '#d4edda' : '#f8f9fa';
?>
<tr style="background-color: <?= $bgColor ?>;">
<td><strong><?= $payment['house_number'] ?></strong></td>
<td><?= htmlspecialchars($payment['owner_name'] ?? '-') ?></td>
<td style="text-align: right;">
<?= $payment['amount'] > 0 ? '$' . number_format($payment['amount'], 2) : '-' ?>
</td>
<td>
<?= $payment['payment_date'] ? date('d/m/Y', strtotime($payment['payment_date'])) : '-' ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
<tfoot style="background-color: #343a40; color: white; font-weight: bold;">
<tr>
<td colspan="2" style="text-align: right;">Total Recaudado:</td>
<td style="text-align: right;">$<?= number_format($status['total_collected'], 2) ?></td>
<td></td>
</tr>
</tfoot>
</table>
<?php if ($index < $conceptCount - 1): ?>
<div style="page-break-after: always;"></div>
<?php endif; ?>
<?php endforeach; ?>