Ajoute géolocalisation des sites, duplication d'actifs et intégration GLPI

- Coordonnées GPS sur les sites (formulaire + carte Leaflet en direct sur
  la fiche et sur la liste des sites d'une organisation).
- Duplication d'un actif depuis la liste.
- Intégration GLPI en lecture seule : panneau "Tickets" sur le tableau de
  bord de l'organisation (tickets non clos, priorité, catégorie, extrait),
  organisations liées à leur entité GLPI via glpi_entity_id.
- Corrige la mise en cache des permissions Spatie lors du seed (silencée
  par WithoutModelEvents) et rend le seeder admin idempotent.
- Ajoute busy_timeout/WAL à la config SQLite pour éviter les erreurs de
  verrou sous écritures concurrentes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
mrKamoo
2026-08-18 08:45:49 +02:00
co-authored by Claude Sonnet 5
parent 2d12d3c2b2
commit 01233928cc
15 changed files with 617 additions and 17 deletions
@@ -1,6 +1,7 @@
<?php
use App\Models\Organization;
use App\Services\Glpi\GlpiClient;
use Livewire\Component;
new class extends Component
@@ -127,7 +128,20 @@ new class extends Component
->limit(8)
->get();
$glpi = ['tickets' => [], 'total' => 0, 'error' => null];
$glpiNewTicketUrl = null;
if ($this->organization->glpi_entity_id !== null) {
$glpiClient = app(GlpiClient::class);
$glpi = $glpiClient->recentTicketsForEntity($this->organization->glpi_entity_id);
$glpiNewTicketUrl = $glpiClient->frontUrl().'/front/ticket.form.php';
}
return [
'glpiTickets' => $glpi['tickets'],
'glpiTicketsTotal' => $glpi['total'],
'glpiError' => $glpi['error'],
'glpiNewTicketUrl' => $glpiNewTicketUrl,
'counts' => [
'contacts' => $this->organization->contacts()->count(),
'locations' => $this->organization->locations()->count(),
@@ -341,4 +355,70 @@ new class extends Component
</div>
</div>
</div>
@if ($organization->glpi_entity_id !== null)
<div class="mt-6 rounded border border-slate-200 bg-white p-4">
<div class="mb-3 flex items-center justify-between">
<h2 class="text-sm font-semibold uppercase tracking-wide text-slate-500">Tickets GLPI</h2>
<a href="{{ $glpiNewTicketUrl }}" target="_blank" rel="noopener"
class="text-xs text-slate-500 hover:text-slate-900">
Nouveau ticket &rarr;
</a>
</div>
@if ($glpiError)
<p class="text-sm text-amber-600">{{ $glpiError }}</p>
@elseif (empty($glpiTickets))
<p class="text-sm text-slate-400">Aucun ticket non clos pour le moment.</p>
@else
<ul class="divide-y divide-slate-100">
@foreach ($glpiTickets as $ticket)
<li class="py-2.5 text-sm">
<div class="flex items-start justify-between gap-4">
<a href="{{ $ticket['url'] }}" target="_blank" rel="noopener"
class="min-w-0 font-medium text-slate-900 hover:underline">
#{{ $ticket['id'] }} — {{ $ticket['title'] }}
</a>
<div class="flex shrink-0 items-center gap-1.5">
<span class="rounded-full px-2 py-0.5 text-xs font-medium
{{ match(true) {
$ticket['priority'] >= 5 => 'bg-red-100 text-red-700',
$ticket['priority'] === 4 => 'bg-amber-100 text-amber-700',
default => 'bg-slate-100 text-slate-600',
} }}">
{{ $ticket['priority_label'] }}
</span>
<span class="rounded-full px-2 py-0.5 text-xs font-medium
{{ match(true) {
$ticket['status'] === 5 => 'bg-emerald-100 text-emerald-700',
$ticket['status'] === 4 => 'bg-amber-100 text-amber-700',
default => 'bg-blue-100 text-blue-700',
} }}">
{{ $ticket['status_label'] }}
</span>
</div>
</div>
@if ($ticket['excerpt'])
<p class="mt-1 text-xs text-slate-600">{{ $ticket['excerpt'] }}</p>
@endif
<p class="mt-1 text-xs text-slate-400">
{{ \Illuminate\Support\Carbon::parse($ticket['date_mod'])->diffForHumans() }}
@if ($ticket['category'])
&middot; {{ $ticket['category'] }}
@endif
</p>
</li>
@endforeach
</ul>
@if ($glpiTicketsTotal > count($glpiTickets))
<p class="mt-3 border-t border-slate-100 pt-3 text-xs text-slate-500">
{{ $glpiTicketsTotal }} tickets non clos au total pour cette entité.
</p>
@endif
@endif
</div>
@endif
</div>