315 lines
18 KiB
PHP
315 lines
18 KiB
PHP
<style>
|
||
.accounting-dashboard-structures>.card>.table-responsive {
|
||
padding: .75rem 1rem 1rem
|
||
}
|
||
|
||
</style>
|
||
<?php $sum = (int)($accountingTotals['billed'] ?? array_sum(array_column($invoices, 'total_ttc_cents')));
|
||
$due = (int)($accountingTotals['due'] ?? array_sum(array_column($invoices, 'amount_due_cents')));
|
||
$paid = $sum - $due;
|
||
$money = static fn (int$c) => number_format($c / 100, 2, ',', ' ') . ' €';
|
||
$statusLabels = ['paid' => 'Payée','to_pay' => 'À payer','partial' => 'Partiellement payée','disputed' => 'Contestée','cancelled' => 'Annulée','credit' => 'Avoir'];
|
||
$methodLabels = ['transfer' => 'Virement','direct_debit' => 'Prélèvement','card' => 'Carte','check' => 'Chèque','cash' => 'Espèces','other' => 'Autre','unknown' => 'Non renseigné'];?>
|
||
<?php $accountingFilters = $accountingFilters ?? ['q' => '','vendor_id' => 0,'status' => '','type' => '','payment_method' => ''];
|
||
$filterQuery = array_merge(['year' => (int)$year], $accountingFilters);?>
|
||
<div class="d-flex flex-wrap justify-content-between align-items-center gap-2 mb-3 d-print-none">
|
||
<div class="text-muted">Pilotage des factures, paiements et partenaires</div>
|
||
<div class="btn-list"><a class="btn btn-outline-primary"
|
||
href="/accounting/export.csv?<?= h(http_build_query($filterQuery)) ?>">⬇ Export CSV</a><button
|
||
class="btn btn-outline-secondary" type="button" onclick="window.print()">🖨 Imprimer / PDF</button></div>
|
||
</div>
|
||
<form method="get" action="/accounting" class="card mb-3 d-print-none">
|
||
<div class="card-body">
|
||
<div class="row g-2 align-items-end">
|
||
<div class="col-md-4"><label class="form-label">Rechercher</label><input class="form-control" name="q"
|
||
value="<?= h($accountingFilters['q']) ?>" placeholder="Référence, structure ou notes…"></div>
|
||
<div class="col-md-2"><label class="form-label">Année</label><select class="form-select" name="year">
|
||
<option value="0">Toutes les années</option><?php foreach ($years as $y):?><option
|
||
value="<?= h($y) ?>" <?= (int)$y === $year ? 'selected' : ''?>><?= h($y) ?></option>
|
||
<?php endforeach?>
|
||
</select></div>
|
||
<div class="col-md-3"><label class="form-label">Structure</label><select class="form-select"
|
||
name="vendor_id">
|
||
<option value="0">Toutes les structures</option><?php foreach ($vendors as $v):?><option
|
||
value="<?= (int)$v['id'] ?>"
|
||
<?= (int)$v['id'] === (int)$accountingFilters['vendor_id'] ? 'selected' : ''?>>
|
||
<?= h($v['name']) ?></option><?php endforeach?>
|
||
</select></div>
|
||
<div class="col-md-3"><label class="form-label">Statut</label><select class="form-select" name="status">
|
||
<option value="">Tous les statuts</option><?php foreach ($statusLabels as $k => $label):?><option
|
||
value="<?= h($k) ?>" <?= $accountingFilters['status'] === $k ? 'selected' : ''?>>
|
||
<?= h($label) ?></option><?php endforeach?>
|
||
</select></div>
|
||
<div class="col-md-3"><label class="form-label">Type de pièce</label><select class="form-select"
|
||
name="type">
|
||
<option value="">Tous les types</option>
|
||
<?php foreach (['invoice' => 'Facture','credit' => 'Avoir','adjustment' => 'Régularisation'] as $k => $label):?>
|
||
<option value="<?= h($k) ?>" <?= $accountingFilters['type'] === $k ? 'selected' : ''?>>
|
||
<?= h($label) ?></option><?php endforeach?>
|
||
</select></div>
|
||
<div class="col-md-3"><label class="form-label">Règlement</label><select class="form-select"
|
||
name="payment_method">
|
||
<option value="">Tous les règlements</option><?php foreach ($methodLabels as $k => $label):?><option
|
||
value="<?= h($k) ?>" <?= $accountingFilters['payment_method'] === $k ? 'selected' : ''?>>
|
||
<?= h($label) ?></option><?php endforeach?>
|
||
</select></div>
|
||
<div class="col-md-6">
|
||
<div class="btn-list justify-content-end"><a class="btn btn-outline-secondary"
|
||
href="/accounting?year=0">Réinitialiser</a><button class="btn btn-primary">Filtrer</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
<div class="row g-3 mb-3">
|
||
<div class="col-sm-6 col-xl-3">
|
||
<div class="card card-sm h-100">
|
||
<div class="card-body">
|
||
<div class="text-muted">Payé net</div>
|
||
<div class="h2 mb-0 text-green"><?= $money($paid) ?></div>
|
||
<div class="small text-muted">
|
||
<?= $sum ? number_format(max(0, $paid / $sum * 100), 1, ',', ' ') . ' % du facturé' : '—' ?></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-sm-6 col-xl-3">
|
||
<div class="card card-sm h-100">
|
||
<div class="card-body">
|
||
<div class="text-muted">À régler</div>
|
||
<div class="h2 mb-0 text-orange"><?= $money($due) ?></div>
|
||
<div class="small text-muted"><?= (int)($accountingTotals['to_pay'] ?? 0) ?> pièce(s)</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-sm-6 col-xl-3">
|
||
<div class="card card-sm h-100">
|
||
<div class="card-body">
|
||
<div class="text-muted">Échéances dépassées</div>
|
||
<div class="h2 mb-0 <?= !empty($accountingTotals['overdue']) ? 'text-red' : '' ?>">
|
||
<?= (int)($accountingTotals['overdue'] ?? 0) ?></div>
|
||
<div class="small text-muted">Avec une date d’échéance connue</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-sm-6 col-xl-3">
|
||
<div class="card card-sm h-100">
|
||
<div class="card-body">
|
||
<div class="text-muted">Taux restant</div>
|
||
<div class="h2 mb-0"><?= $sum ? number_format(max(0, $due / $sum * 100), 1, ',', ' ') . ' %' : '—' ?>
|
||
</div>
|
||
<div class="small text-muted">Sur la période choisie</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="row g-3 mb-3">
|
||
<div class="col-xl-7">
|
||
<div class="card h-100">
|
||
<div class="card-header">
|
||
<h3 class="card-title">Coût et paiements par structure</h3>
|
||
</div>
|
||
<div class="table-responsive">
|
||
<table class="table table-sm table-vcenter mb-0">
|
||
<thead>
|
||
<tr>
|
||
<th>Structure</th>
|
||
<th class="text-end">Facturé</th>
|
||
<th class="text-end">Payé</th>
|
||
<th class="text-end">Reste dû</th>
|
||
<th class="text-end">Animaux</th>
|
||
<th class="text-end">Coût/animal</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody><?php foreach (($vendorStats ?? []) as $v):$vendorPaid = $v['billed'] - $v['due'];?><tr>
|
||
<td><?php if ($v['id']):?><a class="fw-semibold"
|
||
href="/directory/view?id=<?= (int)$v['id'] ?>&year=<?= $year ?: 'all' ?>"><?= h($v['label']) ?></a><?php else:?><?= h($v['label']) ?><?php endif?>
|
||
<div class="small text-muted">
|
||
<?= (int)$v['paid_documents'] ?>/<?= (int)$v['documents'] ?> pièce(s) soldée(s)
|
||
</div>
|
||
</td>
|
||
<td class="text-end"><?= $money((int)$v['billed']) ?></td>
|
||
<td class="text-end text-green"><?= $money((int)$vendorPaid) ?></td>
|
||
<td class="text-end <?= $v['due'] ? 'text-orange fw-bold' : '' ?>">
|
||
<?= $money((int)$v['due']) ?></td>
|
||
<td class="text-end"><?= (int)$v['animals'] ?: '—' ?></td>
|
||
<td class="text-end">
|
||
<?= $v['animals'] ? $money((int)round($v['animal_cost'] / $v['animals'])) : '—' ?></td>
|
||
</tr><?php endforeach?></tbody>
|
||
</table>
|
||
</div>
|
||
<div class="card-footer small text-muted">Le coût par animal utilise les actes déjà rapprochés des fiches
|
||
animales ; il peut différer du total des factures.</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-xl-5">
|
||
<div class="card h-100">
|
||
<div class="card-header">
|
||
<h3 class="card-title">Évolution annuelle des paiements</h3>
|
||
</div>
|
||
<div class="card-body">
|
||
<div style="height:250px"><canvas id="accountingAnnualChart"
|
||
data-series="<?= h(json_encode($annualStats ?? [], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)) ?>"></canvas>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="row g-3 mb-3">
|
||
<div class="col-md-6">
|
||
<div class="card h-100">
|
||
<div class="card-header">
|
||
<h3 class="card-title">État des pièces</h3>
|
||
</div>
|
||
<div class="card-body d-flex flex-wrap gap-2"><?php foreach (($statusStats ?? []) as $key => $count):?><span
|
||
class="badge <?= $key === 'paid' ? 'bg-green-lt' : ($key === 'to_pay' ? 'bg-orange-lt' : 'bg-secondary-lt') ?> p-2"><?= h($statusLabels[$key] ?? $key) ?>
|
||
· <?= (int)$count ?></span><?php endforeach?></div>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-6">
|
||
<div class="card h-100">
|
||
<div class="card-header">
|
||
<h3 class="card-title">Modes de règlement</h3>
|
||
</div>
|
||
<div class="card-body d-flex flex-wrap gap-2"><?php foreach (($methodStats ?? []) as $key => $count):?><span
|
||
class="badge bg-blue-lt p-2"><?= h($methodLabels[$key] ?? $key) ?> ·
|
||
<?= (int)$count ?></span><?php endforeach?></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<?php $credits = array_sum(array_map(static fn ($r) => $r['document_type'] === 'credit' ? (int)$r['total_ttc_cents'] : 0, $invoices));?>
|
||
<div class="row g-3 mb-3">
|
||
<div class="col-md-4">
|
||
<div class="card card-sm">
|
||
<div class="card-body">
|
||
<div class="text-muted">Facturé TTC net</div>
|
||
<div class="h2 mb-0"><?= $money($sum) ?></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-4">
|
||
<div class="card card-sm">
|
||
<div class="card-body">
|
||
<div class="text-muted">Avoirs inclus</div>
|
||
<div class="h2 mb-0 text-green"><?= $money($credits) ?></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-4">
|
||
<div class="card card-sm">
|
||
<div class="card-body">
|
||
<div class="text-muted">Pièces comptables</div>
|
||
<div class="h2 mb-0"><?= count($invoices) ?></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="card mb-3">
|
||
<div class="card-header">
|
||
<h3 class="card-title">🧾 Nouvelle facture ou avoir</h3>
|
||
</div>
|
||
<form method="post" action="/accounting/save" enctype="multipart/form-data"><input type="hidden" name="csrf"
|
||
value="<?= h(Auth::csrf()) ?>">
|
||
<div class="card-body">
|
||
<div class="row g-3">
|
||
<div class="col-md-3"><label class="form-label">Référence *</label><input class="form-control"
|
||
name="reference" required></div>
|
||
<div class="col-md-3"><label class="form-label">Fournisseur / structure</label><select
|
||
class="form-select" name="vendor_contact_id">
|
||
<option value="">—</option><?php foreach ($vendors as $v):?><option
|
||
value="<?= (int)$v['id'] ?>"><?= h($v['name']) ?></option><?php endforeach?>
|
||
</select></div>
|
||
<div class="col-md-3"><label class="form-label">Date *</label><input class="form-control" type="date"
|
||
name="invoice_date" value="<?= h(date('Y-m-d')) ?>" required></div>
|
||
<div class="col-md-3"><label class="form-label">Échéance</label><input class="form-control" type="date"
|
||
name="due_date"></div>
|
||
<div class="col-md-2"><label class="form-label">Type</label><select class="form-select"
|
||
name="document_type">
|
||
<option value="invoice">Facture</option>
|
||
<option value="credit">Avoir</option>
|
||
<option value="adjustment">Régularisation</option>
|
||
</select></div>
|
||
<div class="col-md-2"><label class="form-label">Montant HT</label><input class="form-control"
|
||
name="total_ht" inputmode="decimal"></div>
|
||
<div class="col-md-2"><label class="form-label">Montant TTC *</label><input class="form-control"
|
||
name="total_ttc" inputmode="decimal" required></div>
|
||
<div class="col-md-2"><label class="form-label">Statut</label><select class="form-select" name="status">
|
||
<option value="to_pay">À payer</option>
|
||
<option value="paid">Payée</option>
|
||
<option value="partial">Partiellement payée</option>
|
||
<option value="disputed">Contestée</option>
|
||
<option value="credit">Avoir</option>
|
||
<option value="cancelled">Annulée</option>
|
||
</select></div>
|
||
<div class="col-md-2"><label class="form-label">Règlement</label><select class="form-select"
|
||
name="payment_method">
|
||
<option value="">—</option>
|
||
<option value="transfer">Virement</option>
|
||
<option value="direct_debit">Prélèvement</option>
|
||
<option value="card">Carte</option>
|
||
<option value="check">Chèque</option>
|
||
<option value="cash">Espèces</option>
|
||
<option value="other">Autre</option>
|
||
</select></div>
|
||
<div class="col-md-2"><label class="form-label">Reste dû</label><input class="form-control"
|
||
name="amount_due" inputmode="decimal"></div>
|
||
<div class="col-md-6"><label class="form-label">Facture (PDF ou image, 20 Mo max.)</label><input
|
||
class="form-control" type="file" name="document"
|
||
accept="application/pdf,image/jpeg,image/png,image/webp"></div>
|
||
<div class="col-md-6"><label class="form-label">Notes</label><input class="form-control" name="notes">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="card-footer text-end"><button class="btn btn-primary">Ajouter</button></div>
|
||
</form>
|
||
</div>
|
||
<div class="card">
|
||
<div class="card-header d-flex align-items-center">
|
||
<div>
|
||
<h3 class="card-title mb-0">Journal des factures</h3>
|
||
<div class="text-muted small">
|
||
<?= $year ? 'Période : ' . (int)$year : 'Toutes les années · historique complet' ?></div>
|
||
</div>
|
||
<form class="ms-auto"><select class="form-select" name="year" onchange="this.form.submit()">
|
||
<option value="0">Toutes les années</option><?php foreach ($years as $y):?><option value="<?= h($y) ?>"
|
||
<?= (int)$y === $year ? 'selected' : ''?>><?= h($y) ?></option><?php endforeach?>
|
||
</select></form>
|
||
</div>
|
||
<div class="table-responsive">
|
||
<table class="table table-vcenter">
|
||
<thead>
|
||
<tr>
|
||
<th>Date</th>
|
||
<th>Référence</th>
|
||
<th>Type</th>
|
||
<th>Structure</th>
|
||
<th>HT</th>
|
||
<th>TTC</th>
|
||
<th>Statut</th>
|
||
<th>Règlement</th>
|
||
<th></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody><?php foreach ($invoices as $r):?><tr>
|
||
<td><?= h(date('d/m/Y', strtotime($r['invoice_date']))) ?></td>
|
||
<td><?= h($r['reference']) ?></td>
|
||
<td><span
|
||
class="badge <?= $r['document_type'] === 'credit' ? 'bg-green-lt' : 'bg-blue-lt' ?>"><?= h(['invoice' => 'Facture','credit' => 'Avoir','adjustment' => 'Régularisation'][$r['document_type']] ?? $r['document_type']) ?></span>
|
||
</td>
|
||
<td><?= h($r['vendor_name'] ?? '—') ?></td>
|
||
<td><?= $r['total_ht_cents'] === null ? '—' : number_format($r['total_ht_cents'] / 100, 2, ',', ' ') . ' €' ?>
|
||
</td>
|
||
<td class="fw-bold <?= $r['total_ttc_cents'] < 0 ? 'text-green' : '' ?>">
|
||
<?= number_format($r['total_ttc_cents'] / 100, 2, ',', ' ') ?> €</td>
|
||
<td><span
|
||
class="badge <?= $r['status'] === 'paid' ? 'bg-green-lt' : ($r['status'] === 'to_pay' ? 'bg-orange-lt' : 'bg-secondary-lt') ?>"><?= h(['paid' => 'Payée','to_pay' => 'À payer','partial' => 'Partielle','disputed' => 'Contestée','cancelled' => 'Annulée','credit' => 'Avoir'][$r['status']] ?? $r['status']) ?></span>
|
||
</td>
|
||
<td><?= h(['transfer' => 'Virement','direct_debit' => 'Prélèvement','card' => 'Carte','check' => 'Chèque','cash' => 'Espèces','other' => 'Autre'][$r['payment_method']] ?? '—') ?>
|
||
</td>
|
||
<td><?php if ($r['stored_filename']):?><a class="btn btn-sm btn-outline-primary" target="_blank"
|
||
href="/accounting/document?id=<?= (int)$r['id'] ?>">Voir</a><?php endif?></td>
|
||
</tr><?php endforeach?><?php if (!$invoices):?><tr>
|
||
<td colspan="9" class="text-center text-muted py-4">Aucune facture pour cette période.</td>
|
||
</tr><?php endif?></tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|