608 lines
21 KiB
PHP
608 lines
21 KiB
PHP
<?php
|
|
namespace App\Filament\Pages\Supporto;
|
|
|
|
use App\Models\CategoriaTicket;
|
|
use App\Models\RubricaUniversale;
|
|
use App\Models\Stabile;
|
|
use App\Models\Ticket;
|
|
use App\Models\TicketAttachment;
|
|
use App\Models\User;
|
|
use App\Support\StabileContext;
|
|
use BackedEnum;
|
|
use Filament\Notifications\Notification;
|
|
use Filament\Pages\Page;
|
|
use Illuminate\Support\Collection;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Illuminate\Support\Facades\Schema;
|
|
use Illuminate\Support\Str;
|
|
use Livewire\WithFileUploads;
|
|
use Throwable;
|
|
use UnitEnum;
|
|
|
|
class TicketMobile extends Page
|
|
{
|
|
use WithFileUploads;
|
|
|
|
protected static ?string $navigationLabel = 'Ticket Mobile';
|
|
|
|
protected static ?string $title = 'Ticket Mobile';
|
|
|
|
protected static BackedEnum|string|null $navigationIcon = 'heroicon-o-device-phone-mobile';
|
|
|
|
protected static UnitEnum|string|null $navigationGroup = 'Supporto';
|
|
|
|
protected static ?int $navigationSort = 25;
|
|
|
|
protected static ?string $slug = 'supporto/ticket-mobile';
|
|
|
|
protected string $view = 'filament.pages.supporto.ticket-mobile';
|
|
|
|
public string $status = 'open';
|
|
|
|
public string $callerSearch = '';
|
|
|
|
public ?int $selectedCallerId = null;
|
|
|
|
public ?string $newTicketTitolo = null;
|
|
|
|
public ?string $newTicketDescrizione = null;
|
|
|
|
public ?string $newTicketLuogo = null;
|
|
|
|
public string $newTicketPriorita = 'Media';
|
|
|
|
public ?int $newTicketCategoriaId = null;
|
|
|
|
public ?string $newTicketTipoIntervento = null;
|
|
|
|
/** @var array<int, mixed> */
|
|
public array $newTicketAttachments = [];
|
|
|
|
/** @var array<int, mixed> */
|
|
public array $newTicketCameraShots = [];
|
|
|
|
/** @var array<int, string> */
|
|
public array $newTicketAttachmentDescriptions = [];
|
|
|
|
public ?string $newTicketFotoNote = null;
|
|
|
|
/** @var \Illuminate\Support\Collection<int, Ticket> */
|
|
public $tickets;
|
|
|
|
/** @var \Illuminate\Support\Collection<int, RubricaUniversale> */
|
|
public $callerMatches;
|
|
|
|
/** @var array<string,int> */
|
|
public array $ticketCounters = [
|
|
'open' => 0,
|
|
'urgent' => 0,
|
|
'closed' => 0,
|
|
'all' => 0,
|
|
];
|
|
|
|
/** @var array<int, array{id:int,nome:string}> */
|
|
public array $categorieTicketOptions = [];
|
|
|
|
/** @var array<string,string> */
|
|
public array $interventiPreimpostati = [
|
|
'idraulico_perdita' => 'Idraulico - Perdita acqua',
|
|
'elettrico_luci' => 'Elettrico - Luci/Quadro',
|
|
'ascensore_bloccato' => 'Ascensore - Bloccato',
|
|
'citofono_guasto' => 'Citofono - Guasto',
|
|
'pulizia_straordinaria' => 'Pulizia - Straordinaria',
|
|
'infissi_serrature' => 'Infissi/Serrature',
|
|
'fognatura' => 'Fognatura/Odori',
|
|
'verde_potatura' => 'Verde - Potatura',
|
|
'altro' => 'Altro',
|
|
];
|
|
|
|
public static function canAccess(): bool
|
|
{
|
|
$user = Auth::user();
|
|
|
|
return $user instanceof User
|
|
&& $user->hasAnyRole(['super-admin', 'admin', 'amministratore', 'collaboratore']);
|
|
}
|
|
|
|
public function mount(): void
|
|
{
|
|
$this->tickets = collect();
|
|
$this->callerMatches = collect();
|
|
$this->newTicketPriorita = 'Media';
|
|
$this->newTicketTipoIntervento = 'altro';
|
|
$this->loadCategorieTicketOptions();
|
|
|
|
if (request()->query('status') === 'all') {
|
|
$this->status = 'all';
|
|
}
|
|
|
|
$this->refreshData();
|
|
}
|
|
|
|
public function updatedStatus(): void
|
|
{
|
|
$this->refreshData();
|
|
}
|
|
|
|
public function updatedCallerSearch(): void
|
|
{
|
|
$this->searchCaller();
|
|
}
|
|
|
|
public function refreshData(): void
|
|
{
|
|
$this->loadCounters();
|
|
$this->loadTickets();
|
|
$this->searchCaller();
|
|
}
|
|
|
|
public function getRubricaFilamentUrl(): string
|
|
{
|
|
return route('filament.admin-filament.pages.gescon.anagrafica.rubrica-universale');
|
|
}
|
|
|
|
public function getDashboardFilamentUrl(): string
|
|
{
|
|
return route('filament.admin-filament.pages.dashboard');
|
|
}
|
|
|
|
public function getTicketArchivioUrl(): string
|
|
{
|
|
return TicketGestione::getUrl(panel: 'admin-filament', parameters: ['status' => 'all']);
|
|
}
|
|
|
|
public function getTicketDettaglioUrl(int $ticketId): string
|
|
{
|
|
return TicketScheda::getUrl(panel: 'admin-filament') . '?ticket=' . $ticketId;
|
|
}
|
|
|
|
public function prendiInCarico(int $ticketId): void
|
|
{
|
|
$this->aggiornaStatoTicket($ticketId, 'Preso in Carico', true);
|
|
}
|
|
|
|
public function avviaLavorazione(int $ticketId): void
|
|
{
|
|
$this->aggiornaStatoTicket($ticketId, 'In Lavorazione');
|
|
}
|
|
|
|
public function risolviTicket(int $ticketId): void
|
|
{
|
|
$this->aggiornaStatoTicket($ticketId, 'Risolto');
|
|
}
|
|
|
|
public function chiudiTicket(int $ticketId): void
|
|
{
|
|
$this->aggiornaStatoTicket($ticketId, 'Chiuso');
|
|
}
|
|
|
|
private function loadTickets(): void
|
|
{
|
|
$user = Auth::user();
|
|
if (! $user instanceof User) {
|
|
$this->tickets = collect();
|
|
return;
|
|
}
|
|
|
|
$stabileId = StabileContext::resolveActiveStabileId($user);
|
|
if (! $stabileId) {
|
|
$this->tickets = collect();
|
|
return;
|
|
}
|
|
|
|
$query = Ticket::query()
|
|
->with(['categoriaTicket'])
|
|
->where('stabile_id', $stabileId);
|
|
|
|
if ($this->status === 'open') {
|
|
$query->whereIn('stato', ['Aperto', 'Preso in Carico', 'In Lavorazione']);
|
|
} elseif ($this->status === 'urgent') {
|
|
$query->where('priorita', 'Urgente')
|
|
->whereIn('stato', ['Aperto', 'Preso in Carico', 'In Lavorazione']);
|
|
} elseif ($this->status === 'closed') {
|
|
$query->whereIn('stato', ['Risolto', 'Chiuso']);
|
|
}
|
|
|
|
$this->tickets = $query->latest('data_apertura')->limit(30)->get();
|
|
}
|
|
|
|
private function loadCounters(): void
|
|
{
|
|
$user = Auth::user();
|
|
if (! $user instanceof User) {
|
|
$this->ticketCounters = ['open' => 0, 'urgent' => 0, 'closed' => 0, 'all' => 0];
|
|
return;
|
|
}
|
|
|
|
$stabileId = StabileContext::resolveActiveStabileId($user);
|
|
if (! $stabileId) {
|
|
$this->ticketCounters = ['open' => 0, 'urgent' => 0, 'closed' => 0, 'all' => 0];
|
|
return;
|
|
}
|
|
|
|
$base = Ticket::query()->where('stabile_id', $stabileId);
|
|
|
|
$this->ticketCounters = [
|
|
'open' => (clone $base)->whereIn('stato', ['Aperto', 'Preso in Carico', 'In Lavorazione'])->count(),
|
|
'urgent' => (clone $base)->where('priorita', 'Urgente')->whereIn('stato', ['Aperto', 'Preso in Carico', 'In Lavorazione'])->count(),
|
|
'closed' => (clone $base)->whereIn('stato', ['Risolto', 'Chiuso'])->count(),
|
|
'all' => (clone $base)->count(),
|
|
];
|
|
}
|
|
|
|
private function searchCaller(): void
|
|
{
|
|
$raw = trim($this->callerSearch);
|
|
if ($raw === '') {
|
|
$this->callerMatches = collect();
|
|
$this->selectedCallerId = null;
|
|
return;
|
|
}
|
|
|
|
$digits = preg_replace('/\D+/', '', $raw) ?: '';
|
|
$needleText = '%' . mb_strtolower($raw) . '%';
|
|
$needle = '%' . $digits . '%';
|
|
|
|
$this->callerMatches = RubricaUniversale::query()
|
|
->where(function ($q) use ($needle, $needleText, $digits): void {
|
|
$q->orWhereRaw("LOWER(COALESCE(nome, '')) LIKE ?", [$needleText])
|
|
->orWhereRaw("LOWER(COALESCE(cognome, '')) LIKE ?", [$needleText])
|
|
->orWhereRaw("LOWER(COALESCE(ragione_sociale, '')) LIKE ?", [$needleText])
|
|
->orWhereRaw("LOWER(COALESCE(email, '')) LIKE ?", [$needleText]);
|
|
|
|
if ($digits !== '') {
|
|
$q->orWhereRaw("REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(telefono_cellulare, ''), ' ', ''), '+', ''), '-', ''), '(', ''), ')', '') LIKE ?", [$needle])
|
|
->orWhereRaw("REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(telefono_ufficio, ''), ' ', ''), '+', ''), '-', ''), '(', ''), ')', '') LIKE ?", [$needle])
|
|
->orWhereRaw("REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(telefono_casa, ''), ' ', ''), '+', ''), '-', ''), '(', ''), ')', '') LIKE ?", [$needle]);
|
|
}
|
|
})
|
|
->orderByRaw('CASE WHEN id = ? THEN 0 ELSE 1 END', [(int) ($this->selectedCallerId ?? 0)])
|
|
->orderBy('nome')
|
|
->orderBy('cognome')
|
|
->limit(12)
|
|
->get();
|
|
|
|
if ($this->selectedCallerId && ! $this->callerMatches->contains('id', $this->selectedCallerId)) {
|
|
$this->selectedCallerId = null;
|
|
}
|
|
}
|
|
|
|
public function selezionaCaller(int $rubricaId): void
|
|
{
|
|
$match = $this->callerMatches->firstWhere('id', $rubricaId);
|
|
if (! $match) {
|
|
$match = RubricaUniversale::query()->find($rubricaId);
|
|
}
|
|
|
|
if (! $match) {
|
|
return;
|
|
}
|
|
|
|
$this->selectedCallerId = (int) $match->id;
|
|
$this->callerSearch = trim((string) ($match->nome_completo ?: $match->ragione_sociale ?: ''));
|
|
if (blank($this->newTicketTitolo)) {
|
|
$this->newTicketTitolo = 'Segnalazione da ' . ($match->nome_completo ?: $match->ragione_sociale ?: 'contatto');
|
|
}
|
|
$this->searchCaller();
|
|
}
|
|
|
|
public function creaTicketRapido(): void
|
|
{
|
|
$this->validate([
|
|
'newTicketTitolo' => ['required', 'string', 'max:255'],
|
|
'newTicketDescrizione' => ['required', 'string'],
|
|
'newTicketLuogo' => ['nullable', 'string', 'max:255'],
|
|
'newTicketPriorita' => ['required', 'in:Bassa,Media,Alta,Urgente'],
|
|
'newTicketCategoriaId' => ['nullable', 'integer', 'exists:categorie_ticket,id'],
|
|
'newTicketTipoIntervento' => ['nullable', 'string', 'max:80'],
|
|
'newTicketFotoNote' => ['nullable', 'string', 'max:2000'],
|
|
'newTicketCameraShots' => ['nullable', 'array', 'max:10'],
|
|
'newTicketCameraShots.*' => ['file', 'max:10240', 'mimes:jpg,jpeg,png,webp'],
|
|
'newTicketAttachments' => ['nullable', 'array', 'max:10'],
|
|
'newTicketAttachments.*' => ['file', 'max:10240', 'mimes:jpg,jpeg,png,webp,pdf,doc,docx,xls,xlsx,txt'],
|
|
'newTicketAttachmentDescriptions' => ['nullable', 'array'],
|
|
'newTicketAttachmentDescriptions.*' => ['nullable', 'string', 'max:255'],
|
|
]);
|
|
|
|
$totalUploads = count($this->newTicketCameraShots) + count($this->newTicketAttachments);
|
|
if ($totalUploads > 10) {
|
|
Notification::make()
|
|
->title('Troppi allegati selezionati')
|
|
->body('Puoi caricare al massimo 10 file totali (foto + documenti) per ticket.')
|
|
->warning()
|
|
->send();
|
|
|
|
return;
|
|
}
|
|
|
|
$user = Auth::user();
|
|
if (! $user instanceof User) {
|
|
return;
|
|
}
|
|
|
|
$stabileId = StabileContext::resolveActiveStabileId($user);
|
|
if (! $stabileId) {
|
|
Notification::make()
|
|
->title('Seleziona prima uno stabile attivo')
|
|
->danger()
|
|
->send();
|
|
return;
|
|
}
|
|
|
|
$caller = null;
|
|
if ($this->selectedCallerId) {
|
|
$caller = RubricaUniversale::query()->find($this->selectedCallerId);
|
|
}
|
|
|
|
$descrizione = trim((string) $this->newTicketDescrizione);
|
|
if ($caller) {
|
|
$telefono = $caller->telefono_cellulare ?: ($caller->telefono_ufficio ?: $caller->telefono_casa);
|
|
$descrizione .= "\n\nChiamante selezionato: " . ($caller->nome_completo ?: $caller->ragione_sociale ?: ('Rubrica #' . $caller->id));
|
|
if ($telefono) {
|
|
$descrizione .= "\nTelefono: {$telefono}";
|
|
}
|
|
if ($caller->tipo_utenza_call) {
|
|
$descrizione .= "\nProfilo: {$caller->tipo_utenza_call}";
|
|
}
|
|
if ($caller->riferimento_stabile) {
|
|
$descrizione .= "\nRif. stabile: {$caller->riferimento_stabile}";
|
|
}
|
|
if ($caller->riferimento_unita) {
|
|
$descrizione .= "\nRif. unità: {$caller->riferimento_unita}";
|
|
}
|
|
}
|
|
|
|
if (filled($this->newTicketFotoNote)) {
|
|
$descrizione .= "\n\nNota fotografica: " . trim((string) $this->newTicketFotoNote);
|
|
}
|
|
|
|
if (filled($this->newTicketTipoIntervento)) {
|
|
$label = $this->interventiPreimpostati[(string) $this->newTicketTipoIntervento] ?? (string) $this->newTicketTipoIntervento;
|
|
$descrizione .= "\nTipo intervento: " . $label;
|
|
}
|
|
|
|
$ticket = Ticket::query()->create([
|
|
'stabile_id' => $stabileId,
|
|
'aperto_da_user_id' => $user->id,
|
|
'titolo' => (string) $this->newTicketTitolo,
|
|
'descrizione' => $descrizione,
|
|
'categoria_ticket_id' => $this->newTicketCategoriaId,
|
|
'luogo_intervento' => $this->newTicketLuogo,
|
|
'data_apertura' => now(),
|
|
'stato' => 'Aperto',
|
|
'priorita' => $this->newTicketPriorita,
|
|
]);
|
|
|
|
$savedAttachments = $this->salvaAllegatiTicket($ticket, $user->id);
|
|
|
|
Notification::make()
|
|
->title('Ticket creato')
|
|
->body(
|
|
$savedAttachments > 0
|
|
? 'Ticket #' . $ticket->id . ' creato con successo con ' . $savedAttachments . ' allegati.'
|
|
: 'Ticket #' . $ticket->id . ' creato con successo'
|
|
)
|
|
->success()
|
|
->send();
|
|
|
|
$this->newTicketTitolo = null;
|
|
$this->newTicketDescrizione = null;
|
|
$this->newTicketLuogo = null;
|
|
$this->newTicketPriorita = 'Media';
|
|
$this->newTicketCategoriaId = null;
|
|
$this->newTicketTipoIntervento = 'altro';
|
|
$this->newTicketCameraShots = [];
|
|
$this->newTicketAttachments = [];
|
|
$this->newTicketAttachmentDescriptions = [];
|
|
$this->newTicketFotoNote = null;
|
|
$this->refreshData();
|
|
}
|
|
|
|
public function updatedNewTicketCameraShots(): void
|
|
{
|
|
$this->syncAttachmentDescriptionSlots();
|
|
}
|
|
|
|
public function updatedNewTicketAttachments(): void
|
|
{
|
|
$this->syncAttachmentDescriptionSlots();
|
|
}
|
|
|
|
public function removeNewTicketFile(int $index): void
|
|
{
|
|
$cameraCount = count($this->newTicketCameraShots);
|
|
|
|
if ($index < $cameraCount) {
|
|
unset($this->newTicketCameraShots[$index]);
|
|
$this->newTicketCameraShots = array_values($this->newTicketCameraShots);
|
|
} else {
|
|
$docIndex = $index - $cameraCount;
|
|
unset($this->newTicketAttachments[$docIndex]);
|
|
$this->newTicketAttachments = array_values($this->newTicketAttachments);
|
|
}
|
|
|
|
$this->syncAttachmentDescriptionSlots();
|
|
}
|
|
|
|
/**
|
|
* @return array<int, array{index:int,name:string,mime:string,is_image:bool,preview_url:?string,description:string}>
|
|
*/
|
|
public function getSelectedUploadsProperty(): array
|
|
{
|
|
$rows = [];
|
|
$files = array_merge($this->newTicketCameraShots, $this->newTicketAttachments);
|
|
|
|
foreach ($files as $index => $file) {
|
|
if (! is_object($file)) {
|
|
continue;
|
|
}
|
|
|
|
$name = (string) (method_exists($file, 'getClientOriginalName') ? $file->getClientOriginalName() : ('file-' . $index));
|
|
$mime = (string) (method_exists($file, 'getClientMimeType') ? $file->getClientMimeType() : 'application/octet-stream');
|
|
$isImage = str_starts_with($mime, 'image/');
|
|
|
|
$previewUrl = null;
|
|
if ($isImage && method_exists($file, 'temporaryUrl')) {
|
|
try {
|
|
$previewUrl = (string) $file->temporaryUrl();
|
|
} catch (Throwable) {
|
|
$previewUrl = null;
|
|
}
|
|
}
|
|
|
|
$rows[] = [
|
|
'index' => (int) $index,
|
|
'name' => $name,
|
|
'mime' => $mime,
|
|
'is_image' => $isImage,
|
|
'preview_url' => $previewUrl,
|
|
'description' => (string) ($this->newTicketAttachmentDescriptions[$index] ?? ''),
|
|
];
|
|
}
|
|
|
|
return $rows;
|
|
}
|
|
|
|
private function loadCategorieTicketOptions(): void
|
|
{
|
|
$this->categorieTicketOptions = CategoriaTicket::query()
|
|
->orderBy('nome')
|
|
->limit(200)
|
|
->get(['id', 'nome'])
|
|
->map(fn(CategoriaTicket $cat): array=> [
|
|
'id' => (int) $cat->id,
|
|
'nome' => (string) $cat->nome,
|
|
])
|
|
->all();
|
|
}
|
|
|
|
private function salvaAllegatiTicket(Ticket $ticket, int $userId): int
|
|
{
|
|
if (! Schema::hasTable('ticket_attachments')) {
|
|
return 0;
|
|
}
|
|
|
|
$saved = 0;
|
|
|
|
$files = array_merge($this->newTicketCameraShots, $this->newTicketAttachments);
|
|
|
|
foreach ($files as $index => $file) {
|
|
if (! is_object($file) || ! method_exists($file, 'store')) {
|
|
continue;
|
|
}
|
|
|
|
try {
|
|
$path = $file->store('ticket-mobile/' . $ticket->id, 'public');
|
|
|
|
TicketAttachment::query()->create([
|
|
'ticket_id' => (int) $ticket->id,
|
|
'ticket_update_id' => null,
|
|
'user_id' => $userId,
|
|
'file_path' => $path,
|
|
'original_file_name' => (string) (method_exists($file, 'getClientOriginalName') ? $file->getClientOriginalName() : basename($path)),
|
|
'mime_type' => (string) (method_exists($file, 'getClientMimeType') ? $file->getClientMimeType() : 'application/octet-stream'),
|
|
'size' => (int) (method_exists($file, 'getSize') ? $file->getSize() : 0),
|
|
'description' => $this->resolveAttachmentDescription((int) $index),
|
|
]);
|
|
|
|
$saved++;
|
|
} catch (Throwable $e) {
|
|
Log::warning('TicketMobile attachment save failed', [
|
|
'ticket_id' => $ticket->id,
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
}
|
|
}
|
|
|
|
return $saved;
|
|
}
|
|
|
|
private function syncAttachmentDescriptionSlots(): void
|
|
{
|
|
$files = array_merge($this->newTicketCameraShots, $this->newTicketAttachments);
|
|
$next = [];
|
|
|
|
foreach ($files as $index => $_file) {
|
|
$next[$index] = (string) ($this->newTicketAttachmentDescriptions[$index] ?? '');
|
|
}
|
|
|
|
$this->newTicketAttachmentDescriptions = $next;
|
|
}
|
|
|
|
private function resolveAttachmentDescription(int $index): string
|
|
{
|
|
$specific = trim((string) ($this->newTicketAttachmentDescriptions[$index] ?? ''));
|
|
if ($specific !== '') {
|
|
return $specific;
|
|
}
|
|
|
|
if (filled($this->newTicketFotoNote)) {
|
|
return 'Nota foto: ' . trim((string) $this->newTicketFotoNote);
|
|
}
|
|
|
|
return 'Allegato da Ticket Mobile';
|
|
}
|
|
|
|
public function excerpt(?string $text, int $limit = 160): string
|
|
{
|
|
return Str::limit((string) $text, $limit);
|
|
}
|
|
|
|
public function getCallerStabileLabel(int $rubricaId): ?string
|
|
{
|
|
$stabile = Stabile::query()->where('rubrica_id', $rubricaId)->first();
|
|
if (! $stabile) {
|
|
return null;
|
|
}
|
|
|
|
return $stabile->denominazione ?: ('Stabile #' . $stabile->id);
|
|
}
|
|
|
|
private function aggiornaStatoTicket(int $ticketId, string $nuovoStato, bool $assegnaAUtente = false): void
|
|
{
|
|
$user = Auth::user();
|
|
if (! $user instanceof User) {
|
|
return;
|
|
}
|
|
|
|
$stabileId = StabileContext::resolveActiveStabileId($user);
|
|
if (! $stabileId) {
|
|
return;
|
|
}
|
|
|
|
$ticket = Ticket::query()
|
|
->where('id', $ticketId)
|
|
->where('stabile_id', $stabileId)
|
|
->first();
|
|
|
|
if (! $ticket) {
|
|
Notification::make()
|
|
->title('Ticket non trovato o non accessibile')
|
|
->danger()
|
|
->send();
|
|
return;
|
|
}
|
|
|
|
$payload = ['stato' => $nuovoStato];
|
|
if ($assegnaAUtente) {
|
|
$payload['assegnato_a_user_id'] = $user->id;
|
|
}
|
|
if ($nuovoStato === 'Risolto' && ! $ticket->data_risoluzione_effettiva) {
|
|
$payload['data_risoluzione_effettiva'] = now()->toDateString();
|
|
}
|
|
if ($nuovoStato === 'Chiuso' && ! $ticket->data_chiusura_effettiva) {
|
|
$payload['data_chiusura_effettiva'] = now()->toDateString();
|
|
}
|
|
|
|
$ticket->update($payload);
|
|
|
|
Notification::make()
|
|
->title('Ticket #' . $ticket->id . ' aggiornato a ' . $nuovoStato)
|
|
->success()
|
|
->send();
|
|
|
|
$this->refreshData();
|
|
}
|
|
}
|