isAuthorized($request)) { return response()->json(['ok' => false, 'message' => 'Unauthorized'], 401); } $payload = $request->validate([ 'phone' => ['required', 'string', 'max:64'], 'name' => ['nullable', 'string', 'max:255'], 'direction' => ['nullable', 'in:in_arrivo,in_uscita,persa'], 'outcome' => ['nullable', 'string', 'max:255'], 'event_id' => ['nullable', 'string', 'max:100'], 'called_extension' => ['nullable', 'string', 'max:30'], 'note' => ['nullable', 'string'], 'called_at' => ['nullable', 'date'], ]); $normalized = PhoneNumber::normalizeForMatch((string) $payload['phone']); $rubrica = $this->findRubricaByPhone($normalized); $postIt = null; if (Schema::hasTable('chiamate_post_it')) { $postIt = ChiamataPostIt::query()->create([ 'stabile_id' => null, 'rubrica_id' => $rubrica?->id, 'ticket_id' => null, 'creato_da_user_id' => null, 'telefono' => $normalized !== '' ? $normalized : (string) $payload['phone'], 'nome_chiamante' => $rubrica?->nome_completo ?: ($payload['name'] ?? null), 'direzione' => $payload['direction'] ?? 'in_arrivo', 'esito' => $payload['outcome'] ?? null, 'origine' => 'panasonic_ns1000', 'origine_id' => $payload['event_id'] ?? null, 'oggetto' => 'Chiamata ' . (($payload['direction'] ?? 'in_arrivo') === 'in_arrivo' ? 'in arrivo' : 'telefonica'), 'nota' => $this->buildNote($payload), 'priorita' => 'Media', 'stato' => 'post_it', 'chiamata_il' => $payload['called_at'] ?? now(), ]); } return response()->json([ 'ok' => true, 'source' => 'panasonic_ns1000', 'phone_normalized' => $normalized, 'matched' => (bool) $rubrica, 'rubrica' => $rubrica ? [ 'id' => $rubrica->id, 'nome_completo' => $rubrica->nome_completo, 'categoria' => $rubrica->categoria, ] : null, 'post_it_id' => $postIt?->id, 'called_extension' => $payload['called_extension'] ?? null, ]); } public function lookup(Request $request): JsonResponse { if (! $this->isAuthorized($request)) { return response()->json(['ok' => false, 'message' => 'Unauthorized'], 401); } $data = $request->validate([ 'phone' => ['required', 'string', 'max:64'], ]); $normalized = PhoneNumber::normalizeForMatch((string) $data['phone']); $rubrica = $this->findRubricaByPhone($normalized); return response()->json([ 'ok' => true, 'phone_normalized' => $normalized, 'matched' => (bool) $rubrica, 'rubrica' => $rubrica ? [ 'id' => $rubrica->id, 'nome_completo' => $rubrica->nome_completo, 'telefono_ufficio' => $rubrica->telefono_ufficio, 'telefono_cellulare' => $rubrica->telefono_cellulare, 'telefono_casa' => $rubrica->telefono_casa, 'categoria' => $rubrica->categoria, ] : null, ]); } private function isAuthorized(Request $request): bool { $configured = (string) env('NETGESCON_CTI_SHARED_TOKEN', ''); if ($configured === '') { return false; } $token = (string) ($request->header('X-CTI-Token') ?: $request->input('token', '')); return hash_equals($configured, $token); } private function findRubricaByPhone(string $phone): ?RubricaUniversale { $digits = PhoneNumber::normalizeDigits($phone); if ($digits === '') { return null; } $tail = substr($digits, -7); if ($tail === false || $tail === '') { return null; } return RubricaUniversale::query() ->where(function ($q) use ($tail) { $q->orWhere('telefono_cellulare', 'like', '%' . $tail . '%') ->orWhere('telefono_ufficio', 'like', '%' . $tail . '%') ->orWhere('telefono_casa', 'like', '%' . $tail . '%') ->orWhere('telefono_cellulare_nazionale', 'like', '%' . $tail . '%'); }) ->orderByDesc('updated_at') ->first(); } private function buildNote(array $payload): string { $lines = [ 'Origine: Panasonic NS1000 (CSTA)', ]; if (! empty($payload['called_extension'])) { $lines[] = 'Interno chiamato: ' . $payload['called_extension']; } if (! empty($payload['event_id'])) { $lines[] = 'Event ID: ' . $payload['event_id']; } if (! empty($payload['note'])) { $lines[] = 'Note PBX: ' . $payload['note']; } return implode("\n", $lines); } }