templates/compta/charges.html.twig line 1

Open in your IDE?
  1. {% extends 'base.html.twig' %}
  2. {% block title %}Charges{% endblock %}
  3. {% block stylesheets %}
  4.     {{ parent() }}
  5. {% endblock %}
  6. {% block body %}
  7. <style>
  8. .action-link {
  9.     color: #006288;
  10.     text-decoration: none;
  11.     padding: 4px 8px;
  12.     border: 1px solid #006288;
  13.     border-radius: 4px;
  14.     margin: 0 2px;
  15.     display: inline-block;
  16.     font-size: 14px;
  17. }
  18. .action-link:hover {
  19.     background-color: #006288;
  20.     color: white;
  21. }
  22. .action-link.delete {
  23.     color: #dc3545;
  24.     border-color: #dc3545;
  25. }
  26. .action-link.delete:hover {
  27.     background-color: #dc3545;
  28.     color: white;
  29. }
  30. .action-link.duplicate {
  31.     color: #198754;
  32.     border-color: #198754;
  33. }
  34. .action-link.duplicate:hover {
  35.     background-color: #198754;
  36.     color: white;
  37. }
  38. .dataTables_wrapper .dataTables_filter {
  39.     margin-bottom: 1rem;
  40. }
  41. .dataTables_wrapper .dataTables_filter input {
  42.     border: 1px solid #ced4da;
  43.     border-radius: 4px;
  44.     padding: 0.375rem 0.75rem;
  45. }
  46. .dataTables_wrapper .dataTables_length select {
  47.     border: 1px solid #ced4da;
  48.     border-radius: 4px;
  49.     padding: 0.375rem 2rem 0.375rem 0.75rem;
  50. }
  51. .dataTables_wrapper .dataTables_paginate .paginate_button {
  52.     padding: 0.375rem 0.75rem;
  53.     margin-left: -1px;
  54.     border: 1px solid #dee2e6;
  55.     background-color: #fff;
  56.     color: #006288 !important;
  57. }
  58. .dataTables_wrapper .dataTables_paginate .paginate_button:hover {
  59.     background-color: #e9ecef !important;
  60.     border-color: #dee2e6;
  61.     color: #006288 !important;
  62. }
  63. .dataTables_wrapper .dataTables_paginate .paginate_button.current {
  64.     background-color: #006288 !important;
  65.     border-color: #006288;
  66.     color: #fff !important;
  67. }
  68. .dataTables_wrapper .dataTables_paginate .paginate_button.disabled {
  69.     color: #6c757d !important;
  70.     pointer-events: none;
  71.     background-color: #fff;
  72.     border-color: #dee2e6;
  73. }
  74. .toggle-icon {
  75.     transition: transform 0.3s ease;
  76.     display: inline-block;
  77. }
  78. .toggle-icon.collapsed {
  79.     transform: rotate(-90deg);
  80. }
  81. .missing-charges-content {
  82.     max-height: 1000px;
  83.     overflow: hidden;
  84.     transition: max-height 0.3s ease-out;
  85. }
  86. .missing-charges-content.hidden {
  87.     max-height: 0;
  88. }
  89. </style>
  90. <div class="container mt-4">
  91.     <div class="d-flex justify-content-between align-items-center mb-4">
  92.         <h1>Charges</h1>
  93.         <div>
  94.             <a href="{{ path('app_compta') }}" class="btn btn-secondary me-2">
  95.                 Retour à la comptabilité
  96.             </a>
  97.             <a href="{{ path('app_compta_charge_new') }}" class="btn btn-primary">
  98.                 Ajouter une charge
  99.             </a>
  100.         </div>
  101.     </div>
  102.     {% if missingCharges is not empty %}
  103.         <div class="alert alert-warning mb-4">
  104.             <div class="d-flex align-items-center" style="cursor: pointer;" id="toggleMissingCharges">
  105.                 <i class="fa-solid fa-chevron-down toggle-icon me-2 collapsed"></i>
  106.                 <h5 class="alert-heading mb-0">Charges mensuelles manquantes</h5>
  107.             </div>
  108.             <div class="missing-charges-content mt-3 hidden" id="missingChargesContent">
  109.                 <p>Les charges mensuelles suivantes n'ont pas été enregistrées :</p>
  110.                 <ul class="mb-0">
  111.                     {% for charge in missingCharges %}
  112.                         <li>
  113.                             {{ charge.designation }} ({{ charge.agent }}) - 
  114.                             {{ charge.montantHT|number_format(2, ',', ' ') }} € HT - 
  115.                             Attendue le {{ charge.date_attendue|date('d/m/Y') }}
  116.                             <a href="{{ path('app_compta_charge_new', {
  117.                                 'monthly_charge_id': charge.charge_originale_id,
  118.                                 'expected_date': charge.date_attendue|date('Y-m-d')
  119.                             }) }}" class="btn btn-sm btn-warning ms-2">
  120.                                 Ajouter
  121.                             </a>
  122.                         </li>
  123.                     {% endfor %}
  124.                 </ul>
  125.             </div>
  126.         </div>
  127.     {% endif %}
  128.     <div class="card shadow-sm mb-4">
  129.         <div class="card-header bg-light">
  130.             <h5 class="card-title mb-0">Filtres avancés</h5>
  131.         </div>
  132.         <div class="card-body">
  133.             <div class="row g-3">
  134.                 <div class="col-md-6">
  135.                     <h6 class="mb-3">Période</h6>
  136.                     <div class="row">
  137.                         <div class="col-md-6">
  138.                             <label class="form-label">Date début</label>
  139.                             <input type="date" class="form-control" id="date-debut" value="{{ app.session.get('charges_date_debut') }}">
  140.                         </div>
  141.                         <div class="col-md-6">
  142.                             <label class="form-label">Date fin</label>
  143.                             <input type="date" class="form-control" id="date-fin" value="{{ app.session.get('charges_date_fin') }}">
  144.                         </div>
  145.                     </div>
  146.                 </div>
  147.                 <div class="col-md-6">
  148.                     <h6 class="mb-3">Montant HT</h6>
  149.                     <div class="row">
  150.                         <div class="col-md-6">
  151.                             <label class="form-label">Minimum</label>
  152.                             <input type="number" class="form-control" id="montant-min" step="0.01">
  153.                         </div>
  154.                         <div class="col-md-6">
  155.                             <label class="form-label">Maximum</label>
  156.                             <input type="number" class="form-control" id="montant-max" step="0.01">
  157.                         </div>
  158.                     </div>
  159.                 </div>
  160.                 <div class="col-md-6">
  161.                     <h6 class="mb-3">Catégorie</h6>
  162.                     <select id="category-filter" class="form-select" multiple size="6" style="height: auto;">
  163.                         <option value="employes">Employés</option>
  164.                         <option value="administratif">Administratif</option>
  165.                         <option value="locaux">Locaux</option>
  166.                         <option value="fonctionnement">Fonctionnement</option>
  167.                         <option value="deplacement">Déplacement</option>
  168.                         <option value="restauration">Restauration</option>
  169.                         <option value="fourniture">Fourniture</option>
  170.                         <option value="autres">Autres</option>
  171.                         {% for category in categories %}
  172.                             <option value="{{ category }}">{{ category }}</option>
  173.                         {% endfor %}
  174.                     </select>
  175.                     <div class="form-text">Maintenez Ctrl (ou Cmd sur Mac) pour sélectionner plusieurs catégories</div>
  176.                 </div>
  177.                 <div class="col-md-6">
  178.                     <h6 class="mb-2">Agent</h6>
  179.                     <select id="agent-filter" class="form-select">
  180.                         <option value="">Tous les agents</option>
  181.                         {% for agent in agents %}
  182.                             <option value="{{ agent.username }}">{{ agent.username }}</option>
  183.                         {% endfor %}
  184.                     </select>
  185.                 </div>
  186.             </div>
  187.         </div>
  188.     </div>
  189.     <div class="card shadow-sm">
  190.         <div class="card-body">
  191.             <div class="table-responsive">
  192.                 <table id="charges-table" class="table table-hover align-middle">
  193.                     <thead class="table-light">
  194.                         <tr>
  195.                             <th>Date</th>
  196.                             <th>Agent</th>
  197.                             <th>Désignation</th>
  198.                             <th class="text-end">Montant HT</th>
  199.                             <th class="text-end">Taux TVA</th>
  200.                             <th class="text-end">Montant TVA</th>
  201.                             <th class="text-end">Montant TTC</th>
  202.                             <th>Date Paiement</th>
  203.                             <th class="text-center" style="min-width: 200px;">Actions</th>
  204.                         </tr>
  205.                     </thead>
  206.                     <tbody>
  207.                         {% for charge in charges %}
  208.                             <tr data-category="{{ charge.categorieDesignation|lower }}"
  209.                                 data-agent="{{ charge.agent.username }}"
  210.                                 data-montant="{{ charge.montantHT }}"
  211.                                 data-date="{{ charge.date|date('Y-m-d') }}">
  212.                                 <td>{{ charge.date|date('d/m/Y') }}</td>
  213.                                 <td>{{ charge.agent.username }}</td>
  214.                                 <td>
  215.                                     <span class="fw-bold">{{ charge.designation }}</span><br>
  216.                                     <small class="text-muted">
  217.                                         {{ charge.categorieDesignation|title }}
  218.                                         {% if charge.categoriePersonnalisee %}
  219.                                             ({{ charge.categoriePersonnalisee }})
  220.                                         {% endif %}
  221.                                         {% if charge.isMensuel %}
  222.                                             <span class="badge bg-info ms-1">Mensuel</span>
  223.                                         {% endif %}
  224.                                         {% if charge.isEntrepriseCharge %}
  225.                                             <span class="badge bg-primary ms-1">Entreprise</span>
  226.                                         {% endif %}
  227.                                     </small>
  228.                                 </td>
  229.                                 <td class="text-end">{{ charge.montantHT|number_format(2, ',', ' ') }} €</td>
  230.                                 <td class="text-end">{{ charge.tauxTVA }}%</td>
  231.                                 <td class="text-end">{{ charge.montantTVA|number_format(2, ',', ' ') }} €</td>
  232.                                 <td class="text-end">{{ charge.montantTTC|number_format(2, ',', ' ') }} €</td>
  233.                                 <td>{{ charge.datePaiement ? charge.datePaiement|date('d/m/Y') : '-' }}</td>
  234.                                 <td class="text-center">
  235.                                     <a href="{{ path('app_compta_charge_edit', {'id': charge.id}) }}" 
  236.                                        class="action-link">
  237.                                         Modifier
  238.                                     </a>
  239.                                     <a href="{{ path('app_compta_charge_new', {'monthly_charge_id': charge.id}) }}"
  240.                                        class="action-link duplicate">
  241.                                         Dupliquer
  242.                                     </a>
  243.                                     <button type="submit" 
  244.                                             form="delete-form-{{ charge.id }}"
  245.                                             class="action-link delete">
  246.                                         Supprimer
  247.                                     </button>
  248.                                     <form id="delete-form-{{ charge.id }}"
  249.                                           action="{{ path('app_compta_charge_delete', {'id': charge.id}) }}" 
  250.                                           method="post" 
  251.                                           style="display: none;"
  252.                                           onsubmit="return confirm('Êtes-vous sûr de vouloir supprimer cette charge ?');">
  253.                                         <input type="hidden" name="_token" value="{{ csrf_token('delete' ~ charge.id) }}">
  254.                                     </form>
  255.                                 </td>
  256.                             </tr>
  257.                         {% endfor %}
  258.                     </tbody>
  259.                     <tfoot class="table-light">
  260.                         <tr class="fw-bold">
  261.                             <td colspan="3" class="text-end">Totaux</td>
  262.                             <td class="text-end" id="total-ht">{{ totalHT|number_format(2, ',', ' ') }} €</td>
  263.                             <td></td>
  264.                             <td class="text-end" id="total-tva">{{ totalTVA|number_format(2, ',', ' ') }} €</td>
  265.                             <td class="text-end" id="total-ttc">{{ totalTTC|number_format(2, ',', ' ') }} €</td>
  266.                             <td colspan="2"></td>
  267.                         </tr>
  268.                     </tfoot>
  269.                 </table>
  270.             </div>
  271.         </div>
  272.     </div>
  273. </div>
  274. {{ include('_footer.html.twig') }}
  275. {% endblock %}
  276. {% block javascripts %}
  277. {{ parent() }}
  278. <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
  279. <script type="text/javascript" src="https://cdn.datatables.net/1.11.5/js/jquery.dataTables.min.js"></script>
  280. <script type="text/javascript" src="https://cdn.datatables.net/1.11.5/js/dataTables.bootstrap5.min.js"></script>
  281. <script>
  282.     $(document).ready(function() {
  283.         // Gestion du toggle pour les charges manquantes
  284.         $('#toggleMissingCharges').on('click', function() {
  285.             const content = $('#missingChargesContent');
  286.             const icon = $(this).find('.toggle-icon');
  287.             
  288.             content.toggleClass('hidden');
  289.             icon.toggleClass('collapsed');
  290.         });
  291.         // Configuration des traductions
  292.         const frenchTranslation = {
  293.             "lengthMenu": "Afficher _MENU_ entrées",
  294.             "zeroRecords": "Aucune charge trouvée",
  295.             "info": "Page _PAGE_ sur _PAGES_",
  296.             "infoEmpty": "Aucune charge disponible",
  297.             "infoFiltered": "(filtré parmi _MAX_ charges au total)",
  298.             "search": "Rechercher :",
  299.             "paginate": {
  300.                 "first": "Premier",
  301.                 "last": "Dernier",
  302.                 "next": "Suivant",
  303.                 "previous": "Précédent"
  304.             }
  305.         };
  306.         // Fonction de mise à jour des totaux
  307.         function updateTotals(dt) {
  308.             let totalHT = 0;
  309.             let totalTVA = 0;
  310.             let totalTTC = 0;
  311.             dt.rows({search: 'applied'}).every(function() {
  312.                 const data = this.data();
  313.                 const montantHT = parseFloat($(data[3]).text().replace('€', '').replace(/\s/g, '').replace(',', '.'));
  314.                 const montantTVA = parseFloat(data[5].replace('€', '').replace(/\s/g, '').replace(',', '.'));
  315.                 const montantTTC = parseFloat(data[6].replace('€', '').replace(/\s/g, '').replace(',', '.'));
  316.                 
  317.                 totalHT += montantHT;
  318.                 totalTVA += montantTVA;
  319.                 totalTTC += montantTTC;
  320.             });
  321.             $('#total-ht').text(totalHT.toLocaleString('fr-FR', {minimumFractionDigits: 2, maximumFractionDigits: 2}) + ' €');
  322.             $('#total-tva').text(totalTVA.toLocaleString('fr-FR', {minimumFractionDigits: 2, maximumFractionDigits: 2}) + ' €');
  323.             $('#total-ttc').text(totalTTC.toLocaleString('fr-FR', {minimumFractionDigits: 2, maximumFractionDigits: 2}) + ' €');
  324.         }
  325.         // Initialisation de DataTables
  326.         const table = $('#charges-table').DataTable({
  327.             language: frenchTranslation,
  328.             order: [[0, 'desc']], // Tri par date décroissante par défaut
  329.             pageLength: 10, // Nombre d'éléments par page
  330.             lengthMenu: [[10, 25, 50, 100, -1], [10, 25, 50, 100, "Tout"]], // Options du nombre d'éléments par page
  331.             columnDefs: [
  332.                 { orderable: false, targets: 8 } // Désactive le tri sur la colonne Actions
  333.             ],
  334.             drawCallback: function(settings) {
  335.                 updateTotals(this.api());
  336.             }
  337.         });
  338.         // Fonction de filtrage personnalisé
  339.         $.fn.dataTable.ext.search.push(function(settings, searchData, index, rowData, counter) {
  340.             const $row = $(table.row(index).node());
  341.             
  342.             // Filtre par date
  343.             const dateDebut = $('#date-debut').val();
  344.             const dateFin = $('#date-fin').val();
  345.             const date = $row.data('date');
  346.             if ((dateDebut && date < dateDebut) || (dateFin && date > dateFin)) {
  347.                 return false;
  348.             }
  349.             // Filtre par montant
  350.             const montantMin = parseFloat($('#montant-min').val()) || 0;
  351.             const montantMax = parseFloat($('#montant-max').val()) || Infinity;
  352.             const montant = parseFloat($row.data('montant'));
  353.             if (isNaN(montant) || montant < montantMin || montant > montantMax) {
  354.                 return false;
  355.             }
  356.             // Filtre par catégories (sélection multiple)
  357.             const categories = $('#category-filter').val() || [];
  358.             if (categories.length > 0) {
  359.                 const rowCategory = $row.data('category').toLowerCase();
  360.                 const rowCustomCategory = $row.find('small').text().toLowerCase();
  361.                 if (!categories.some(cat => 
  362.                     rowCategory === cat.toLowerCase() || 
  363.                     rowCustomCategory.includes(cat.toLowerCase())
  364.                 )) {
  365.                     return false;
  366.                 }
  367.             }
  368.             // Filtre par agent
  369.             const agent = $('#agent-filter').val();
  370.             if (agent && $row.data('agent') !== agent) {
  371.                 return false;
  372.             }
  373.             return true;
  374.         });
  375.         // Event listeners pour les filtres personnalisés
  376.         $('#date-debut, #date-fin, #montant-min, #montant-max, #category-filter, #agent-filter').on('change', function() {
  377.             table.draw();
  378.             
  379.             // Sauvegarder les dates dans la session via une requête AJAX
  380.             if (this.id === 'date-debut' || this.id === 'date-fin') {
  381.                 $.post('{{ path('app_compta_save_dates') }}', {
  382.                     dateDebut: $('#date-debut').val(),
  383.                     dateFin: $('#date-fin').val()
  384.                 });
  385.             }
  386.         });
  387.     });
  388. </script>
  389. {% endblock %}