canAccessFattura($user, $fattura)) { abort(403); } $filename = $fattura->nome_file_xml ?: ('fattura-' . $fattura->id . '.xml'); $filename = str_replace(["\r", "\n", '"'], '', $filename); if (is_string($fattura->xml_path) && $fattura->xml_path !== '' && Storage::disk('local')->exists($fattura->xml_path)) { $fullPath = Storage::disk('local')->path($fattura->xml_path); return response()->download($fullPath, $filename, [ 'Content-Type' => 'application/xml; charset=utf-8', ]); } if (is_string($fattura->xml_content) && $fattura->xml_content !== '') { return response($fattura->xml_content, 200, [ 'Content-Type' => 'application/xml; charset=utf-8', 'Content-Disposition' => 'attachment; filename="' . $filename . '"', ]); } abort(404, 'XML non disponibile'); } public function downloadPdf(FatturaElettronica $fattura): Response { $user = Auth::user(); if (! $user instanceof User) { abort(403); } if (! $this->canAccessFattura($user, $fattura)) { abort(403); } $path = is_string($fattura->allegato_pdf_path) ? $fattura->allegato_pdf_path : ''; if ($path === '' || ! Storage::disk('local')->exists($path)) { // Best-effort: ripara record e protocollazione. try { app(FatturaElettronicaProtocolloService::class)->ensureDocumentoProtocollato($fattura, (int) $user->id); $fattura->refresh(); } catch (\Throwable) { // ignore } // Best-effort: se i file sono stati puliti dallo storage ma abbiamo xml_content, rigeneriamo allegati. try { $xml = is_string($fattura->xml_content) ? trim($fattura->xml_content) : ''; if ($xml !== '') { $importer = new FatturaElettronicaImporter(new FatturaElettronicaXmlParser()); $importer->importXml($xml, (int) $fattura->stabile_id, is_string($fattura->nome_file_xml) ? $fattura->nome_file_xml : null, [ 'force_update' => true, 'auto_repair_duplicate' => true, 'allow_fallback_stabile' => true, 'force_stabile_id' => (int) $fattura->stabile_id, 'user_id' => (int) $user->id, ]); try { app(FatturaElettronicaProtocolloService::class)->ensureDocumentoProtocollato($fattura, (int) $user->id); $fattura->refresh(); } catch (\Throwable) { // ignore } } } catch (\Throwable) { // ignore } $path = is_string($fattura->allegato_pdf_path) ? $fattura->allegato_pdf_path : ''; if ($path === '' || ! Storage::disk('local')->exists($path)) { $doc = Documento::query() ->where('documentable_type', FatturaElettronica::class) ->where('documentable_id', (int) $fattura->id) ->first(); $docPath = $doc ? (string) ($doc->percorso_file ?: ($doc->path_file ?: '')): ''; if ($docPath !== '' && Storage::disk('local')->exists($docPath) && str_ends_with(strtolower($docPath), '.pdf')) { $path = $docPath; } } if ($path === '' || ! Storage::disk('local')->exists($path)) { abort(404, 'PDF non disponibile'); } } $filename = $fattura->allegato_pdf_nome ?: ('fattura-' . $fattura->id . '.pdf'); $filename = str_replace(["\r", "\n", '"'], '', $filename); $fullPath = Storage::disk('local')->path($path); if (request()->boolean('inline')) { return response()->file($fullPath, [ 'Content-Type' => 'application/pdf', 'Content-Disposition' => 'inline; filename="' . $filename . '"', ]); } return response()->download($fullPath, $filename, [ 'Content-Type' => 'application/pdf', ]); } private function canAccessFattura(User $user, FatturaElettronica $fattura): bool { $allowedStabili = StabileContext::accessibleStabili($user)->pluck('id')->map(fn($value) => (int) $value)->all(); if (in_array((int) $fattura->stabile_id, $allowedStabili, true)) { return true; } if (! $user->hasRole('fornitore')) { return false; } $supplier = $this->resolveSupplierForUser($user); if (! $supplier instanceof Fornitore) { return false; } $allowedSupplierIds = $this->resolveAccessibleSupplierIds($supplier); if (in_array((int) ($fattura->fornitore_id ?? 0), $allowedSupplierIds, true)) { return true; } $supplierIds = array_values(array_unique(array_filter([ $this->normalizeTaxId((string) ($supplier->partita_iva ?? '')), $this->normalizeTaxId((string) ($supplier->codice_fiscale ?? '')), ]))); foreach ($allowedSupplierIds as $allowedSupplierId) { $linkedSupplier = Fornitore::query()->find($allowedSupplierId); if (! $linkedSupplier instanceof Fornitore) { continue; } $supplierIds = array_values(array_unique(array_filter(array_merge($supplierIds, [ $this->normalizeTaxId((string) ($linkedSupplier->partita_iva ?? '')), $this->normalizeTaxId((string) ($linkedSupplier->codice_fiscale ?? '')), ])))); } if ($supplierIds === []) { return false; } $invoiceIds = array_values(array_unique(array_filter([ $this->normalizeTaxId((string) ($fattura->destinatario_piva ?? '')), $this->normalizeTaxId((string) ($fattura->destinatario_cf ?? '')), $this->normalizeTaxId((string) ($fattura->fornitore_piva ?? '')), $this->normalizeTaxId((string) ($fattura->fornitore_cf ?? '')), ]))); foreach ($invoiceIds as $invoiceId) { if (in_array($invoiceId, $supplierIds, true)) { return true; } } return false; } /** * @return array */ private function resolveAccessibleSupplierIds(Fornitore $supplier): array { $configuredIds = collect(data_get($supplier->operational_config ?? [], 'catalog_import.fe_supplier_ids', [])) ->map(fn($value): int => (int) $value) ->filter(fn(int $value): bool => $value > 0) ->unique() ->values() ->all(); return array_values(array_unique(array_merge([(int) $supplier->id], $configuredIds))); } private function resolveSupplierForUser(User $user): ?Fornitore { $email = mb_strtolower(trim((string) $user->email)); if ($email === '') { return null; } return Fornitore::query() ->whereRaw('LOWER(email) = ?', [$email]) ->orderByDesc('id') ->first(); } private function normalizeTaxId(string $value): string { $normalized = strtoupper(str_replace(' ', '', trim($value))); if (str_starts_with($normalized, 'IT') && strlen($normalized) > 11) { $normalized = substr($normalized, 2); } return preg_replace('/[^A-Z0-9]/', '', $normalized) ?? ''; } }