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