- 1<?php
- 2session_start();
- 3ini_set('display_errors', 1);
- 4ini_set('display_startup_errors', 1);
- 5error_reporting(E_ALL);
- 6
- 7require_once __DIR__ . '/../app/Support/helpers.php';
- 8require_once __DIR__ . '/../app/Core/Database.php';
- 9require_once __DIR__ . '/../app/Core/View.php';
- 10require_once __DIR__ . '/../app/Repositories/InvoiceRepository.php';
- 11require_once __DIR__ . '/../app/Services/VatCalculator.php';
- 12require_once __DIR__ . '/../app/Services/InvoiceBuilder.php';
- 13require_once __DIR__ . '/../app/Services/PdfService.php';
- 14require_once __DIR__ . '/../app/Services/ExcelGenerator.php';
- 15require_once __DIR__ . '/../app/Controllers/DashboardController.php';
- 16require_once __DIR__ . '/../app/Controllers/InvoiceController.php';
- 17
- 18use App\Core\Database;
- 19use App\Repositories\InvoiceRepository;
- 20use App\Services\VatCalculator;
- 21use App\Services\InvoiceBuilder;
- 22use App\Services\PdfService;
- 23use App\Services\ExcelGenerator;
- 24use App\Controllers\DashboardController;
- 25use App\Controllers\InvoiceController;
- 26$config = require __DIR__ . '/../config/database.php';
- 27$pdo = Database::connect($config);
- 28
- 29$repository = new InvoiceRepository($pdo);
- 30$calculator = new VatCalculator(0.20);
- 31$builder = new InvoiceBuilder($calculator);
- 32$pdfService = new PdfService();
- 33$excelService = new ExcelGenerator();
- 34$dashboardController = new DashboardController($repository);
- 35$invoiceController = new InvoiceController($repository, $calculator, $builder, $pdfService, $excelService);
- 36
- 37$action = $_GET['action'] ?? 'dashboard';
- 38var_dump($action);
- 39$dashboardYear = isset($_GET['year']) ? (int)$_GET['year'] : (int)date('Y');
- 40
- 41if ($_SERVER['REQUEST_METHOD'] === 'POST') {
- 42 if ($action === 'store') $invoiceController->store();
- 43 if ($action === 'update') $invoiceController->update();
- 44 if ($action === 'form') {
- 45 echo "TEST: inclusion form.php";
- 46 include __DIR__ . '/../app/Views/invoices/form.php';
- 47 exit;
- 48}
- 49
- 50
- 51if ($action === 'edit') {
- 52 $edit = $repository->find((int)($_GET['id'] ?? 0));
- 53 var_dump($edit); // ← test
- 54 include __DIR__ . '/../app/Views/invoices/form.php';
- 55 exit;
- 56}
- 57
- 58
- 59
- 60 if ($action === 'delete') $invoiceController->delete();
- 61 if ($action === 'tva') $invoiceController->selectedVat();
- 62}
- 63
- 64if ($action === 'pdf') {
- 65 $invoiceController->pdf();
- 66 exit;
- 67}
- 68
- 69if ($action === 'csv') {
- 70 // Filtres liste
- 71$listYear = isset($_GET['year']) ? (int)$_GET['year'] : (int)date('Y');
- 72$listPage = isset($_GET['page']) ? max(1,(int)$_GET['page']) : 1;
- 73$listSort = isset($_GET['sort']) ? $_GET['sort'] : 'date_desc';
- 74$listQuarter = isset($_GET['quarter']) ? trim($_GET['quarter']) : '';
- 75$listQ = isset($_GET['q']) ? trim($_GET['q']) : '';
- 76$perPage = 20;
- 77$totalInv = $repository->countSearch($listYear, $listQuarter, $listQ);
- 78$pages = max(1, (int)ceil($totalInv / $perPage));
- 79if ($listPage > $pages) $listPage = $pages;
- 80$invoices = $repository->search($listYear, $listPage, $perPage, $listSort, $listQuarter, $listQ);
- 81$availYears = $repository->availableYears();
- 82 header('Content-Type: text/csv; charset=UTF-8');
- 83 header('Content-Disposition: attachment; filename="factures.csv"');
- 84 $out = fopen('php://output', 'w');
- 85 fputcsv($out, ['FACTURE', 'NUMERO', 'DATE', 'TRIMESTRE', 'ANNEE'], ';');
- 86 fputcsv($out, ['LIGNE', 'DESIGNATION', 'QTE', 'PU HT', 'TOTAL HT'], ';');
- 87 fputcsv($out, [], ';');
- 88 foreach ($invoices as $inv) {
- 89 fputcsv($out, ['FACTURE', $inv['invoice_number'], $inv['invoice_date'], $inv['quarter'], $inv['year']], ';');
- 90 $it = $pdo->prepare('SELECT * FROM invoice_items WHERE invoice_id=:id ORDER BY id ASC');
- 91 $it->execute(['id' => $inv['id']]);
- 92 foreach ($it->fetchAll() as $item) {
- 93 fputcsv($out, ['LIGNE', $item['description'], $item['quantity'], $item['unit_price_ht'], $item['line_total_ht']], ';');
- 94 }
- 95 fputcsv($out, ['TOTAL HT', $inv['total_ht']], ';');
- 96 fputcsv($out, ['TVA 20%', $inv['total_vat']], ';');
- 97 fputcsv($out, ['TOTAL TTC', $inv['total_ttc']], ';');
- 98 fputcsv($out, [], ';');
- 99 }
- 100 fclose($out);
- 101 exit;
- 102}
- 103
- 104if ($action === 'excel') {
- 105 $xid = isset($_GET['id']) ? (int)$_GET['id'] : 0;
- 106 if ($xid > 0) {
- 107 ob_end_clean();
- 108 header('Location: /TV@/dlxls.php?id=' . $xid);
- 109 exit;
- 110 }
- 111 if (!empty($_POST['invoice_ids'])) {
- 112 ob_end_clean();
- 113 require_once dirname(__DIR__) . '/dlxls.php';
- 114 exit;
- 115 }
- 116 echo '<div class="container py-4"><div class="alert alert-warning">Selectionnez au moins une facture.</div></div>';
- 117 exit;
- 118}
- 119
- 120$invoices = $repository->all();
- 121$selectedTotals = $_SESSION['selected_totals'] ?? null;
- 122unset($_SESSION['selected_totals']);
- 123
- 124$edit = null;
- 125if ($action === 'edit') {
- 126 $edit = $repository->find((int)($_GET['id'] ?? 0));
- 127}
- 128
- 129$yearInvoices = array_values(array_filter($invoices, fn($inv) => (int)$inv['year'] === $dashboardYear));
- 130$yearTotals = ['c' => count($yearInvoices), 'ht' => 0, 'vat' => 0, 'ttc' => 0];
- 131foreach ($yearInvoices as $inv) {
- 132 $yearTotals['ht'] += (float)$inv['total_ht'];
- 133 $yearTotals['vat'] += (float)$inv['total_vat'];
- 134 $yearTotals['ttc'] += (float)$inv['total_ttc'];
- 135}
- 136$quarters = $repository->quarterSummary($dashboardYear);
- 137$latest = array_slice($yearInvoices, 0, 10);
- 138$nextNumber = $repository->nextNumber();
- 139?>
- 140<!doctype html>
- 141<html lang="fr">
- 142<head>
- 143<meta charset="utf-8">
- 144<meta name="viewport" content="width=device-width, initial-scale=1">
- 145<title>Facturation TVA</title>
- 146<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
- 147<link rel="stylesheet" href="assets/css/style.css">
- 148</head>
- 149<body class="bg-light">
- 150<div class="container py-4">
- 151 <div class="card card-soft mb-4">
- 152 <div class="card-body d-flex justify-content-between align-items-center flex-wrap gap-2">
- 153 <div>
- 154 <h1 class="h4 mb-1">Facturation TVA</h1>
- 155 <div class="text-muted">Dashboard, factures, TVA, PDF, CSV, Excel</div>
- 156 </div>
- 157 <div class="d-flex gap-2 flex-wrap">
- 158 <a href="?action=dashboard&year=<?= (int)$dashboardYear ?>" class="btn btn-outline-primary btn-sm">Dashboard</a>
- 159 <a href="?action=list" class="btn btn-outline-primary btn-sm">Factures</a>
- 160 <a href="?action=form" class="btn btn-primary btn-sm">Nouvelle facture</a>
- 161 <a href="?action=csv" class="btn btn-outline-success btn-sm">Export CSV</a>
- 162 <a href="?action=excel" class="btn btn-success btn-sm">Export Excel</a>
- 163 </div>
- 164 </div>
- 165 </div>
- 166
- 167 <?php if (!empty($_SESSION['success'])): ?>
- 168 <div class="alert alert-success"><?= e($_SESSION['success']); unset($_SESSION['success']); ?></div>
- 169 <?php endif; ?>
- 170
- 171 <?php if (!empty($_SESSION['error'])): ?>
- 172 <div class="alert alert-danger"><?= e($_SESSION['error']); unset($_SESSION['error']); ?></div>
- 173 <?php endif; ?>
- 174
- 175 <?php if ($action === 'dashboard'): ?>
- 176 <div class="card card-soft mb-4">
- 177 <div class="card-body">
- 178 <div class="d-flex justify-content-between align-items-center flex-wrap gap-2 mb-3">
- 179 <h2 class="h5 mb-0">Dashboard <?= (int)$dashboardYear ?></h2>
- 180 <form method="get" class="d-flex gap-2 align-items-center">
- 181 <input type="hidden" name="action" value="dashboard">
- 182 <select name="year" class="form-select form-select-sm" style="width:auto">
- 183 <?php for ($y = date('Y'); $y >= date('Y') - 5; $y--): ?>
- 184 <option value="<?= (int)$y ?>" <?= $dashboardYear == $y ? 'selected' : '' ?>><?= (int)$y ?></option>
- 185 <?php endfor; ?>
- 186 </select>
- 187 <button class="btn btn-sm btn-primary" type="submit">Voir</button>
- 188 </form>
- 189 </div>
- 190
- 191 <div class="row g-3 mb-4">
- 192 <div class="col-md-3"><div class="card card-soft"><div class="card-body"><div class="text-muted">Factures <?= (int)$dashboardYear ?></div><div class="fs-4 fw-bold"><?= (int)$yearTotals['c'] ?></div></div></div></div>
- 193 <div class="col-md-3"><div class="card card-soft"><div class="card-body"><div class="text-muted">Total HT</div><div class="fs-4 fw-bold"><?= money($yearTotals['ht']) ?></div></div></div></div>
- 194 <div class="col-md-3"><div class="card card-soft"><div class="card-body"><div class="text-muted">TVA</div><div class="fs-4 fw-bold"><?= money($yearTotals['vat']) ?></div></div></div></div>
- 195 <div class="col-md-3"><div class="card card-soft"><div class="card-body"><div class="text-muted">TTC</div><div class="fs-4 fw-bold"><?= money($yearTotals['ttc']) ?></div></div></div></div>
- 196 </div>
- 197
- 198 <div class="table-responsive">
- 199 <table class="table table-sm align-middle mb-0">
- 200 <thead><tr><th>Trimestre</th><th>Factures</th><th>HT</th><th>TVA</th><th>TTC</th></tr></thead>
- 201 <tbody>
- 202 <?php foreach($quarters as $q): ?>
- 203 <tr>
- 204 <td><?= e($q['quarter']) ?></td>
- 205 <td><?= (int)$q['c'] ?></td>
- 206 <td><?= money($q['ht']) ?></td>
- 207 <td><?= money($q['vat']) ?></td>
- 208 <td><?= money($q['ttc']) ?></td>
- 209 </tr>
- 210 <?php endforeach; ?>
- 211 </tbody>
- 212 </table>
- 213 </div>
- 214 </div>
- 215 </div>
- 216
- 217 <div class="card card-soft">
- 218 <div class="card-body">
- 219 <h2 class="h5 mb-3">Dernières factures de l’année <?= (int)$dashboardYear ?></h2>
- 220 <div class="table-responsive">
- 221 <table class="table table-sm align-middle">
- 222 <thead><tr><th>N°</th><th>Date</th><th>Trimestre</th><th>TVA</th><th>TTC</th></tr></thead>
- 223 <tbody>
- 224 <?php foreach($latest as $inv): ?>
- 225 <tr>
- 226 <td><?= e($inv['invoice_number']) ?></td>
- 227 <td><?= e($inv['invoice_date']) ?></td>
- 228 <td><?= e($inv['quarter']) ?></td>
- 229 <td><?= money($inv['total_vat']) ?></td>
- 230 <td><?= money($inv['total_ttc']) ?></td>
- 231 </tr>
- 232 <?php endforeach; ?>
- 233 </tbody>
- 234 </table>
- 235 </div>
- 236 </div>
- 237 </div>
- 238
- 239<?php elseif ($action === 'list'): ?>
- 240<?php
- 241$listYear = isset($_GET['year']) ? (int)$_GET['year'] : (int)date('Y');
- 242$listPage = isset($_GET['page']) ? max(1,(int)$_GET['page']) : 1;
- 243$listSort = isset($_GET['sort']) ? $_GET['sort'] : 'date_desc';
- 244$listQuarter = isset($_GET['quarter']) ? trim($_GET['quarter']) : '';
- 245$listQ = isset($_GET['q']) ? trim($_GET['q']) : '';
- 246$perPage = 20;
- 247$totalInv = $repository->countSearch($listYear, $listQuarter, $listQ);
- 248$pages = max(1,(int)ceil($totalInv / $perPage));
- 249if ($listPage > $pages) $listPage = $pages;
- 250$invoices = $repository->search($listYear, $listPage, $perPage, $listSort, $listQuarter, $listQ);
- 251$availYears = $repository->availableYears();
- 252$baseParams = http_build_query(array_filter(["action"=>"list","year"=>$listYear,"sort"=>$listSort,"quarter"=>$listQuarter,"q"=>$listQ],fn($v)=>$v!==" "&&$v!==null&&$v!==0));
- 253?>
- 254
- 255<div class="card card-soft mb-3">
- 256 <div class="card-body py-2">
- 257 <form method="get" id="filterForm" class="row g-2 align-items-end">
- 258 <input type="hidden" name="action" value="list">
- 259 <div class="col-auto">
- 260 <label class="form-label mb-1 small text-muted">Année</label>
- 261 <select name="year" class="form-select form-select-sm" style="width:100px" onchange="document.getElementById('filterForm').submit()">
- 262 <?php foreach ($availYears as $y): ?>
- 263 <option value="<?= (int)$y ?>" <?= (int)$y===$listYear?"selected":"" ?>><?= (int)$y ?></option>
- 264 <?php endforeach; ?>
- 265 </select>
- 266 </div>
- 267 <div class="col-auto">
- 268 <label class="form-label mb-1 small text-muted">Trimestre</label>
- 269 <select name="quarter" class="form-select form-select-sm" style="width:120px" onchange="document.getElementById('filterForm').submit()">
- 270 <option value="" <?= $listQuarter===""?"selected":"" ?>>Tous</option>
- 271 <option value="Q1" <?= $listQuarter==="Q1"?"selected":"" ?>>T1 (Jan-Mar)</option>
- 272 <option value="Q2" <?= $listQuarter==="Q2"?"selected":"" ?>>T2 (Avr-Jun)</option>
- 273 <option value="Q3" <?= $listQuarter==="Q3"?"selected":"" ?>>T3 (Jul-Sep)</option>
- 274 <option value="Q4" <?= $listQuarter==="Q4"?"selected":"" ?>>T4 (Oct-Dec)</option>
- 275 </select>
- 276 </div>
- 277 <div class="col-auto">
- 278 <label class="form-label mb-1 small text-muted">Trier</label>
- 279 <select name="sort" class="form-select form-select-sm" style="width:150px" onchange="document.getElementById('filterForm').submit()">
- 280 <option value="date_desc" <?= $listSort==="date_desc"?"selected":"" ?>>Date décroissante</option>
- 281 <option value="date_asc" <?= $listSort==="date_asc"?"selected":"" ?>>Date croissante</option>
- 282 </select>
- 283 </div>
- 284 <div class="col-auto">
- 285 <label class="form-label mb-1 small text-muted">Recherche article</label>
- 286 <div class="input-group input-group-sm">
- 287 <input type="text" name="q" class="form-control" placeholder="Ex: béton, dalle..." value="<?= htmlspecialchars($listQ) ?>" style="width:180px">
- 288 <button type="submit" class="btn btn-primary btn-sm">OK</button>
- 289 </div>
- 290 </div>
- 291 <div class="col-auto">
- 292 <label class="form-label mb-1 d-block"> </label>
- 293 <?php if ($listQuarter!==" "||$listQ!==" "||$listSort!=="date_desc"): ?>
- 294 <a href="?action=list&year=<?= $listYear ?>" class="btn btn-sm btn-outline-danger">Effacer</a>
- 295 <?php endif; ?>
- 296 </div>
- 297 <div class="col-auto ms-auto">
- 298 <label class="form-label mb-1 d-block"> </label>
- 299 <span class="text-muted small"><?= $totalInv ?> facture(s)</span>
- 300 </div>
- 301 </form>
- 302 </div>
- 303</div>
- 304
- 305<div class="card card-soft">
- 306 <div class="card-body p-0">
- 307 <form method="post" action="?action=tva" id="invForm">
- 308 <div class="d-flex align-items-center gap-2 px-3 py-2 border-bottom bg-light rounded-top">
- 309 <div class="form-check mb-0">
- 310 <input class="form-check-input" type="checkbox" id="chkAll" onchange="toggleAll(this)">
- 311 <label class="form-check-label small" for="chkAll">Tout sélectionner</label>
- 312 </div>
- 313 <button type="button" class="btn btn-sm btn-outline-secondary" onclick="deselectAll()">Désélectionner tout</button>
- 314 <span class="badge bg-secondary" id="selCount">0 sélectionné(s)</span>
- 315 <div class="ms-auto d-flex gap-2">
- 316 <button type="submit" formaction="?action=tva" class="btn btn-sm btn-outline-info">TVA</button>
- 317 <button type="submit" formaction="/TV@/dlxls.php" class="btn btn-sm btn-success">Export Excel</button>
- 318 </div>
- 319 </div>
- 320 <div class="table-responsive">
- 321 <table class="table table-sm align-middle mb-0">
- 322 <thead class="table-light">
- 323 <tr>
- 324 <th style="width:36px"></th>
- 325 <th>N°</th>
- 326 <th>Client</th>
- 327 <th>Date</th>
- 328 <th>Trimestre</th>
- 329 <th class="text-end">HT</th>
- 330 <th class="text-end">TVA</th>
- 331 <th class="text-end">TTC</th>
- 332 <th class="text-center">Actions</th>
- 333 </tr>
- 334 </thead>
- 335 <tbody>
- 336 <?php if (empty($invoices)): ?>
- 337 <tr><td colspan="9" class="text-center text-muted py-4">Aucune facture trouvée</td></tr>
- 338 <?php else: ?>
- 339 <?php foreach ($invoices as $inv): ?>
- 340 <tr>
- 341 <td><input type="checkbox" name="invoice_ids[]" value="<?= $inv["id"] ?>" class="form-check-input chk" onchange="saveSelection();updateCount()"></td>
- 342 <td><strong><?= e($inv["invoice_number"]) ?></strong></td>
- 343 <td class="text-truncate" style="max-width:150px"><?= e($inv["client_name"]??"") ?></td>
- 344 <td><?= e($inv["invoice_date"]) ?></td>
- 345 <td><span class="badge bg-light text-dark border"><?= e($inv["quarter"]) ?></span></td>
- 346 <td class="text-end"><?= money($inv["total_ht"]) ?></td>
- 347 <td class="text-end"><?= money($inv["total_vat"]) ?></td>
- 348 <td class="text-end fw-semibold"><?= money($inv["total_ttc"]) ?></td>
- 349 <td class="text-center">
- 350 <a href="?action=edit&id=<?= $inv["id"] ?>" class="btn btn-sm btn-outline-primary py-0 px-1">Modifier</a>
- 351 <a href="/TV@/genpdf.php?id=<?= $inv["id"] ?>" target="_blank" class="btn btn-sm btn-outline-danger py-0 px-1">PDF</a>
- 352 <a href="/TV@/dlxls.php?id=<?= $inv["id"] ?>" class="btn btn-sm btn-outline-success py-0 px-1">XLS</a>
- 353 <a href="?action=delete&id=<?= $inv["id"] ?>" class="btn btn-sm btn-outline-secondary py-0 px-1" onclick="return confirm('Supprimer ?')">Suppr.</a>
- 354 </td>
- 355 </tr>
- 356 <?php endforeach; ?>
- 357 <?php endif; ?>
- 358 </tbody>
- 359 </table>
- 360 </div>
- 361 <?php if ($pages>1): ?>
- 362 <nav class="d-flex justify-content-center align-items-center py-3 gap-1 flex-wrap">
- 363 <?php if ($listPage>1): ?><a href="?<?= $baseParams ?>&page=<?= $listPage-1 ?>" class="btn btn-sm btn-outline-secondary">«</a><?php endif; ?>
- 364 <?php $ps=max(1,$listPage-3);$pe=min($pages,$listPage+3); ?>
- 365 <?php if($ps>1): ?><a href="?<?= $baseParams ?>&page=1" class="btn btn-sm btn-outline-secondary">1</a><?php if($ps>2): ?><span class="px-1 text-muted">...</span><?php endif; endif; ?>
- 366 <?php for($p=$ps;$p<=$pe;$p++): ?>
- 367 <a href="?<?= $baseParams ?>&page=<?= $p ?>" class="btn btn-sm <?= $p===$listPage?"btn-primary":"btn-outline-secondary" ?>"><?= $p ?></a>
- 368 <?php endfor; ?>
- 369 <?php if($pe<$pages): ?><?php if($pe<$pages-1): ?><span class="px-1 text-muted">...</span><?php endif; ?><a href="?<?= $baseParams ?>&page=<?= $pages ?>" class="btn btn-sm btn-outline-secondary"><?= $pages ?></a><?php endif; ?>
- 370 <?php if($listPage<$pages): ?><a href="?<?= $baseParams ?>&page=<?= $listPage+1 ?>" class="btn btn-sm btn-outline-secondary">»</a><?php endif; ?>
- 371 <small class="text-muted ms-2">Page <?= $listPage ?>/<?= $pages ?></small>
- 372 </nav>
- 373 <?php endif; ?>
- 374 </form>
- 375 </div>
- 376</div>
- 377<script>
- 378const _SK="inv_sel";
- 379function _getS(){try{return JSON.parse(sessionStorage.getItem(_SK)||"[]")}catch(e){return[]}}
- 380function saveSelection(){const s=_getS();document.querySelectorAll(".chk").forEach(c=>{const id=c.value;if(c.checked&&!s.includes(id))s.push(id);else if(!c.checked){const i=s.indexOf(id);if(i>-1)s.splice(i,1);}});sessionStorage.setItem(_SK,JSON.stringify(s));updateCount();}
- 381function restoreSelection(){const s=_getS();document.querySelectorAll(".chk").forEach(c=>c.checked=s.includes(c.value));updateCount();syncChkAll();}
- 382function updateCount(){document.getElementById("selCount").textContent=_getS().length+" sélectionné(s)";}
- 383function toggleAll(m){document.querySelectorAll(".chk").forEach(c=>c.checked=m.checked);saveSelection();}
- 384function deselectAll(){sessionStorage.removeItem(_SK);document.querySelectorAll(".chk").forEach(c=>c.checked=false);document.getElementById("chkAll").checked=false;updateCount();}
- 385function syncChkAll(){const a=document.querySelectorAll(".chk").length,c=document.querySelectorAll(".chk:checked").length;const el=document.getElementById("chkAll");el.checked=a>0&&c===a;el.indeterminate=c>0&&c<a;}
- 386document.getElementById("invForm").addEventListener("submit",function(e){const btn=e.submitter;const s=_getS();this.querySelectorAll("input[name=\"invoice_ids[]\"]").forEach(e=>e.remove());s.forEach(id=>{const i=document.createElement("input");i.type="hidden";i.name="invoice_ids[]";i.value=id;this.appendChild(i);});if(btn&&btn.getAttribute("formaction")){this.action=btn.getAttribute("formaction");}});
- 387document.addEventListener("DOMContentLoaded",()=>restoreSelection());
- 388</script>
- 389<?php endif; ?>
- 390</div>
- 391</body>
- 392</html>
Paste brut