{% extends 'base.html.twig' %}
{% block title %}Charges{% endblock %}
{% block stylesheets %}
{{ parent() }}
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.11.5/css/dataTables.bootstrap5.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
{% endblock %}
{% block body %}
{{ include('_header.html.twig') }}
<style>
.action-link {
color: #006288;
text-decoration: none;
padding: 4px 8px;
border: 1px solid #006288;
border-radius: 4px;
margin: 0 2px;
display: inline-block;
font-size: 14px;
}
.action-link:hover {
background-color: #006288;
color: white;
}
.action-link.delete {
color: #dc3545;
border-color: #dc3545;
}
.action-link.delete:hover {
background-color: #dc3545;
color: white;
}
.action-link.duplicate {
color: #198754;
border-color: #198754;
}
.action-link.duplicate:hover {
background-color: #198754;
color: white;
}
.dataTables_wrapper .dataTables_filter {
margin-bottom: 1rem;
}
.dataTables_wrapper .dataTables_filter input {
border: 1px solid #ced4da;
border-radius: 4px;
padding: 0.375rem 0.75rem;
}
.dataTables_wrapper .dataTables_length select {
border: 1px solid #ced4da;
border-radius: 4px;
padding: 0.375rem 2rem 0.375rem 0.75rem;
}
.dataTables_wrapper .dataTables_paginate .paginate_button {
padding: 0.375rem 0.75rem;
margin-left: -1px;
border: 1px solid #dee2e6;
background-color: #fff;
color: #006288 !important;
}
.dataTables_wrapper .dataTables_paginate .paginate_button:hover {
background-color: #e9ecef !important;
border-color: #dee2e6;
color: #006288 !important;
}
.dataTables_wrapper .dataTables_paginate .paginate_button.current {
background-color: #006288 !important;
border-color: #006288;
color: #fff !important;
}
.dataTables_wrapper .dataTables_paginate .paginate_button.disabled {
color: #6c757d !important;
pointer-events: none;
background-color: #fff;
border-color: #dee2e6;
}
.toggle-icon {
transition: transform 0.3s ease;
display: inline-block;
}
.toggle-icon.collapsed {
transform: rotate(-90deg);
}
.missing-charges-content {
max-height: 1000px;
overflow: hidden;
transition: max-height 0.3s ease-out;
}
.missing-charges-content.hidden {
max-height: 0;
}
</style>
<div class="container mt-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1>Charges</h1>
<div>
<a href="{{ path('app_compta') }}" class="btn btn-secondary me-2">
Retour à la comptabilité
</a>
<a href="{{ path('app_compta_charge_new') }}" class="btn btn-primary">
Ajouter une charge
</a>
</div>
</div>
{% if missingCharges is not empty %}
<div class="alert alert-warning mb-4">
<div class="d-flex align-items-center" style="cursor: pointer;" id="toggleMissingCharges">
<i class="fa-solid fa-chevron-down toggle-icon me-2 collapsed"></i>
<h5 class="alert-heading mb-0">Charges mensuelles manquantes</h5>
</div>
<div class="missing-charges-content mt-3 hidden" id="missingChargesContent">
<p>Les charges mensuelles suivantes n'ont pas été enregistrées :</p>
<ul class="mb-0">
{% for charge in missingCharges %}
<li>
{{ charge.designation }} ({{ charge.agent }}) -
{{ charge.montantHT|number_format(2, ',', ' ') }} € HT -
Attendue le {{ charge.date_attendue|date('d/m/Y') }}
<a href="{{ path('app_compta_charge_new', {
'monthly_charge_id': charge.charge_originale_id,
'expected_date': charge.date_attendue|date('Y-m-d')
}) }}" class="btn btn-sm btn-warning ms-2">
Ajouter
</a>
</li>
{% endfor %}
</ul>
</div>
</div>
{% endif %}
<div class="card shadow-sm mb-4">
<div class="card-header bg-light">
<h5 class="card-title mb-0">Filtres avancés</h5>
</div>
<div class="card-body">
<div class="row g-3">
<div class="col-md-6">
<h6 class="mb-3">Période</h6>
<div class="row">
<div class="col-md-6">
<label class="form-label">Date début</label>
<input type="date" class="form-control" id="date-debut">
</div>
<div class="col-md-6">
<label class="form-label">Date fin</label>
<input type="date" class="form-control" id="date-fin">
</div>
</div>
</div>
<div class="col-md-6">
<h6 class="mb-3">Montant HT</h6>
<div class="row">
<div class="col-md-6">
<label class="form-label">Minimum</label>
<input type="number" class="form-control" id="montant-min" step="0.01">
</div>
<div class="col-md-6">
<label class="form-label">Maximum</label>
<input type="number" class="form-control" id="montant-max" step="0.01">
</div>
</div>
</div>
<div class="col-md-6">
<h6 class="mb-3">Catégorie</h6>
<select id="category-filter" class="form-select">
<option value="">Toutes les catégories</option>
<option value="employes">Employés</option>
<option value="administratif">Administratif</option>
<option value="locaux">Locaux</option>
<option value="fonctionnement">Fonctionnement</option>
<option value="deplacement">Déplacement</option>
<option value="restauration">Restauration</option>
<option value="fourniture">Fourniture</option>
<option value="autres">Autres</option>
{% for category in categories %}
<option value="{{ category }}">{{ category }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-6">
<h6 class="mb-2">Agent</h6>
<select id="agent-filter" class="form-select">
<option value="">Tous les agents</option>
{% for agent in agents %}
<option value="{{ agent.username }}">{{ agent.username }}</option>
{% endfor %}
</select>
</div>
</div>
</div>
</div>
<div class="card shadow-sm">
<div class="card-body">
<div class="table-responsive">
<table id="charges-table" class="table table-hover align-middle">
<thead class="table-light">
<tr>
<th>Date</th>
<th>Agent</th>
<th>Désignation</th>
<th class="text-end">Montant HT</th>
<th class="text-end">Taux TVA</th>
<th class="text-end">Montant TVA</th>
<th class="text-end">Montant TTC</th>
<th>Date Paiement</th>
<th class="text-center" style="min-width: 200px;">Actions</th>
</tr>
</thead>
<tbody>
{% for charge in charges %}
<tr data-category="{{ charge.categorieDesignation|lower }}"
data-agent="{{ charge.agent.username }}"
data-montant="{{ charge.montantHT }}"
data-date="{{ charge.date|date('Y-m-d') }}">
<td>{{ charge.date|date('d/m/Y') }}</td>
<td>{{ charge.agent.username }}</td>
<td>
<span class="fw-bold">{{ charge.designation }}</span><br>
<small class="text-muted">
{{ charge.categorieDesignation|title }}
{% if charge.categoriePersonnalisee %}
({{ charge.categoriePersonnalisee }})
{% endif %}
{% if charge.isMensuel %}
<span class="badge bg-info ms-1">Mensuel</span>
{% endif %}
{% if charge.isEntrepriseCharge %}
<span class="badge bg-primary ms-1">Entreprise</span>
{% endif %}
</small>
</td>
<td class="text-end">{{ charge.montantHT|number_format(2, ',', ' ') }} €</td>
<td class="text-end">{{ charge.tauxTVA }}%</td>
<td class="text-end">{{ charge.montantTVA|number_format(2, ',', ' ') }} €</td>
<td class="text-end">{{ charge.montantTTC|number_format(2, ',', ' ') }} €</td>
<td>{{ charge.datePaiement ? charge.datePaiement|date('d/m/Y') : '-' }}</td>
<td class="text-center">
<a href="{{ path('app_compta_charge_edit', {'id': charge.id}) }}"
class="action-link">
Modifier
</a>
<a href="{{ path('app_compta_charge_new', {'monthly_charge_id': charge.id}) }}"
class="action-link duplicate">
Dupliquer
</a>
<button type="submit"
form="delete-form-{{ charge.id }}"
class="action-link delete">
Supprimer
</button>
<form id="delete-form-{{ charge.id }}"
action="{{ path('app_compta_charge_delete', {'id': charge.id}) }}"
method="post"
style="display: none;"
onsubmit="return confirm('Êtes-vous sûr de vouloir supprimer cette charge ?');">
<input type="hidden" name="_token" value="{{ csrf_token('delete' ~ charge.id) }}">
</form>
</td>
</tr>
{% endfor %}
</tbody>
<tfoot class="table-light">
<tr class="fw-bold">
<td colspan="3" class="text-end">Totaux</td>
<td class="text-end" id="total-ht">{{ totalHT|number_format(2, ',', ' ') }} €</td>
<td></td>
<td class="text-end" id="total-tva">{{ totalTVA|number_format(2, ',', ' ') }} €</td>
<td class="text-end" id="total-ttc">{{ totalTTC|number_format(2, ',', ' ') }} €</td>
<td colspan="2"></td>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
</div>
{{ include('_footer.html.twig') }}
{% endblock %}
{% block javascripts %}
{{ parent() }}
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script type="text/javascript" src="https://cdn.datatables.net/1.11.5/js/jquery.dataTables.min.js"></script>
<script type="text/javascript" src="https://cdn.datatables.net/1.11.5/js/dataTables.bootstrap5.min.js"></script>
<script>
$(document).ready(function() {
// Gestion du toggle pour les charges manquantes
$('#toggleMissingCharges').on('click', function() {
const content = $('#missingChargesContent');
const icon = $(this).find('.toggle-icon');
content.toggleClass('hidden');
icon.toggleClass('collapsed');
});
// Configuration des traductions
const frenchTranslation = {
"lengthMenu": "Afficher _MENU_ entrées",
"zeroRecords": "Aucune charge trouvée",
"info": "Page _PAGE_ sur _PAGES_",
"infoEmpty": "Aucune charge disponible",
"infoFiltered": "(filtré parmi _MAX_ charges au total)",
"search": "Rechercher :",
"paginate": {
"first": "Premier",
"last": "Dernier",
"next": "Suivant",
"previous": "Précédent"
}
};
// Fonction de mise à jour des totaux
function updateTotals(dt) {
let totalHT = 0;
let totalTVA = 0;
let totalTTC = 0;
dt.rows({search: 'applied'}).every(function() {
const data = this.data();
const montantHT = parseFloat($(data[3]).text().replace('€', '').replace(/\s/g, '').replace(',', '.'));
const montantTVA = parseFloat(data[5].replace('€', '').replace(/\s/g, '').replace(',', '.'));
const montantTTC = parseFloat(data[6].replace('€', '').replace(/\s/g, '').replace(',', '.'));
totalHT += montantHT;
totalTVA += montantTVA;
totalTTC += montantTTC;
});
$('#total-ht').text(totalHT.toLocaleString('fr-FR', {minimumFractionDigits: 2, maximumFractionDigits: 2}) + ' €');
$('#total-tva').text(totalTVA.toLocaleString('fr-FR', {minimumFractionDigits: 2, maximumFractionDigits: 2}) + ' €');
$('#total-ttc').text(totalTTC.toLocaleString('fr-FR', {minimumFractionDigits: 2, maximumFractionDigits: 2}) + ' €');
}
// Initialisation de DataTables
const table = $('#charges-table').DataTable({
language: frenchTranslation,
order: [[0, 'desc']], // Tri par date décroissante par défaut
pageLength: 10, // Nombre d'éléments par page
lengthMenu: [[10, 25, 50, 100, -1], [10, 25, 50, 100, "Tout"]], // Options du nombre d'éléments par page
columnDefs: [
{ orderable: false, targets: 8 } // Désactive le tri sur la colonne Actions
],
drawCallback: function(settings) {
updateTotals(this.api());
}
});
// Fonction de filtrage personnalisé
$.fn.dataTable.ext.search.push(function(settings, searchData, index, rowData, counter) {
const $row = $(table.row(index).node());
// Filtre par date
const dateDebut = $('#date-debut').val();
const dateFin = $('#date-fin').val();
const date = $row.data('date');
if ((dateDebut && date < dateDebut) || (dateFin && date > dateFin)) {
return false;
}
// Filtre par montant
const montantMin = parseFloat($('#montant-min').val()) || 0;
const montantMax = parseFloat($('#montant-max').val()) || Infinity;
const montant = parseFloat($row.data('montant'));
if (isNaN(montant) || montant < montantMin || montant > montantMax) {
return false;
}
// Filtre par catégorie
const categorie = $('#category-filter').val().toLowerCase();
if (categorie && $row.data('category') !== categorie) {
return false;
}
// Filtre par agent
const agent = $('#agent-filter').val();
if (agent && $row.data('agent') !== agent) {
return false;
}
return true;
});
// Event listeners pour les filtres personnalisés
$('#date-debut, #date-fin, #montant-min, #montant-max, #category-filter, #agent-filter').on('change', function() {
table.draw();
});
});
</script>
{% endblock %}