@extends('layouts.app') @section('title', $submission->title) @push('head') @endpush @section('content') @php // Allow ?analysis=ID to view any past analysis for this submission. // Falls back to the most recent run. $requestedAnalysisId = request()->integer('analysis'); $latest = $requestedAnalysisId ? ($submission->analyses->firstWhere('id', $requestedAnalysisId) ?? $submission->analyses->first()) : $submission->analyses->first(); $isViewingLatest = ! $latest || $latest->id === $submission->analyses->first()?->id; $grouped = collect($types)->groupBy(fn ($t) => $t['group'] ?? 'general', true); // Build a status timeline $timeline = collect([ ['key' => 'received', 'label' => __('Received'), 'icon' => 'inbox', 'at' => $submission->created_at], ['key' => 'seen', 'label' => __('Seen'), 'icon' => 'eye', 'at' => $submission->seen_at], ['key' => 'analysing', 'label' => __('Analysing'), 'icon' => 'sparkles', 'at' => $latest?->started_at], ['key' => 'ready', 'label' => __('Analysis ready'), 'icon' => 'check', 'at' => $latest?->finished_at && in_array($latest->status, [\App\Models\FileAnalysis::STATUS_DONE, \App\Models\FileAnalysis::STATUS_AWAITING_REVISION], true) ? $latest->finished_at : null], ['key' => 'sent', 'label' => __('Sent to client'), 'icon' => 'send', 'at' => $submission->sent_at], ['key' => 'opened', 'label' => __('Opened by client'),'icon' => 'open', 'at' => $submission->opened_at], ['key' => 'evaluated', 'label' => __('Evaluated'), 'icon' => 'star', 'at' => $submission->evaluated_at], ]); $extIconColor = match (strtolower($submission->extension ?? '')) { 'pdf' => 'bg-rose-100 text-rose-700 dark:bg-rose-900/30 dark:text-rose-300', 'xlsx', 'xls' => 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-300', 'csv', 'tsv' => 'bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-300', 'doc', 'docx' => 'bg-sky-100 text-sky-700 dark:bg-sky-900/30 dark:text-sky-300', 'json' => 'bg-violet-100 text-violet-700 dark:bg-violet-900/30 dark:text-violet-300', default => 'bg-slate-100 text-slate-700 dark:bg-slate-800 dark:text-slate-300', }; $isRunning = $latest && in_array($latest->status, [\App\Models\FileAnalysis::STATUS_PENDING, \App\Models\FileAnalysis::STATUS_RUNNING], true); // "Request only" submissions arrive with no file attached β€” the uploader // asked the firm to source the data themselves. The admin must attach a // file (or import from an integration) before any analysis can run. $isFileless = empty($submission->path); $uploaderIntegrations = $uploaderIntegrations ?? collect(); // Pre-select the analysis type the client requested when they uploaded: // - 'forecast' / 'valuation' submissions default to that high-level type // - 'analysis' submissions default to the first requested sub-type, if any $initialAnalysisType = match ($submission->analysis_type) { \App\Models\FileSubmission::TYPE_FORECAST => 'forecast', \App\Models\FileSubmission::TYPE_VALUATION => 'valuation', default => (is_array($submission->requested_analysis_types ?? null) && count($submission->requested_analysis_types)) ? (string) $submission->requested_analysis_types[0] : '', }; @endphp @if($isRunning) @endif {{-- HERO --}}
{{ strtoupper($submission->extension ?? '?') }}
{{ __('Inbox') }}

{{ $submission->title }}

{{ $submission->original_name }} Β· {{ $submission->humanSize() }}

@if($isRunning) @endif {{ $submission->statusLabel() }}
{{-- Quick action bar --}}
@unless($isFileless) {{ __('Download') }} @else {{ __('Attach source data') }} @endunless @if($isRunning) @endif @if($latest && $latest->status === \App\Models\FileAnalysis::STATUS_AWAITING_REVISION) {{ __('Review & send') }} @endif @if($latest && in_array($latest->status, [\App\Models\FileAnalysis::STATUS_DONE, \App\Models\FileAnalysis::STATUS_AWAITING_REVISION], true)) {{ __('Compare') }} @endif
@csrf
@if($submission->client_id) {{ optional($submission->client)->name ?: __('Client') }} @endif @if($latest && $latest->status === \App\Models\FileAnalysis::STATUS_DONE && $submission->status === \App\Models\FileSubmission::STATUS_READY)
@csrf
@endif @if($submission->evaluation) @if($submission->evaluation === 'good') πŸ‘ @elseif($submission->evaluation === 'bad') πŸ‘Ž @else ✏️ @endif {{ ucfirst(str_replace('_',' ', $submission->evaluation)) }} @endif
{{-- Timeline --}}
    @foreach($timeline as $i => $step) @php $done = (bool) $step['at']; $isLast = $i === $timeline->count() - 1; @endphp
  1. @if($done) @else {{ $i + 1 }} @endif
    {{ $step['label'] }}
    {{ $step['at'] ? \Illuminate\Support\Carbon::parse($step['at'])->diffForHumans() : 'β€”' }}
    @unless($isLast)
    @endunless
  2. @endforeach
@php $kpiCatalogue = [ __('Profitability') => ['Gross margin', 'Operating margin', 'Net margin', 'EBITDA', 'EBITDA margin', 'ROA', 'ROE', 'ROIC'], __('Liquidity & solvency') => ['Current ratio', 'Quick ratio', 'Cash ratio', 'Working capital', 'Debt-to-equity', 'Debt-to-assets', 'Interest coverage', 'Gearing'], __('Efficiency & activity') => ['DSO', 'DPO', 'DIO', 'Cash conversion cycle', 'Asset turnover', 'Inventory turnover'], __('Cash-flow analysis') => ['Operating cash flow', 'Free cash flow', 'FCF margin', 'Cash conversion ratio'], __('Growth & valuation') => ['Revenue growth', 'CAGR', 'Net income growth', 'EBITDA growth'], __('Risk & default models') => ['Altman Z-score', 'Beneish M-score', 'Piotroski F-score'], ]; $allKpis = collect($kpiCatalogue)->flatten()->values()->all(); $typeLabels = array_merge( collect($types)->mapWithKeys(fn ($t, $k) => [$k => __($t['label'])])->all(), [ 'forecast' => __('Financial forecast'), 'valuation' => __('Enterprise valuation'), ], ); @endphp {{-- ANALYSE MODAL β€” 2-step wizard --}} @php $forwardItems = [ 'forecast' => ['label' => __('Financial forecast'), 'description' => __('Multi-year P&L, cash and KPIs with scenario branches.')], 'valuation' => ['label' => __('Enterprise valuation'), 'description' => __('Triangulated value (DCF, multiples, comparables).')], ]; $typeSections = [ ['key' => 'forward', 'title' => __('Forward-looking'), 'items' => $forwardItems], ]; foreach ($grouped as $groupKey => $items) { $typeSections[] = [ 'key' => $groupKey, 'title' => __($groups[$groupKey] ?? $groupKey), 'items' => collect($items)->map(fn ($t) => ['label' => __($t['label']), 'description' => __($t['description'] ?? '')])->all(), ]; } $languages = ['en' => 'EN', 'fr' => 'FR', 'nl' => 'NL', 'de' => 'DE', 'ar' => 'AR', 'es' => 'ES']; @endphp
{{-- ABORT CONFIRMATION MODAL --}}
@if($isFileless)

{{ __('No file attached') }}

{{ __('The client requested an analysis without uploading a file. Attach a document or pull the data from one of their connected accounting integrations to unblock the analysis.') }}

{{-- Upload tab --}}
@csrf

{{ __('Accepted: PDF, Excel, CSV, JSON, Word, images. Max 50 MB.') }}

@error('file')

{{ $message }}

@enderror
{{-- Integration tab --}}
@if($uploaderIntegrations->isEmpty())

{{ __(':name has not connected an accounting integration yet.', ['name' => $submission->uploader?->name ?? __('This client')]) }}

{{ __('Ask them to connect Billit, Odoo or Exact Online from their workspace, or upload the file manually here.') }}

@else
@csrf
@error('integration_id')

{{ $message }}

@enderror @error('period_from')

{{ $message }}

@enderror @error('period_to')

{{ $message }}

@enderror
@endif
@endif @if(!empty($submission->requested_analysis_types))

{{ __('Client asked for') }}

@foreach($submission->requested_analysis_types as $rt) {{ isset($types[$rt]) ? __($types[$rt]['label']) : $rt }} @endforeach
@endif @php $methodCatalog = config('analysis_methods', []); $valuationLabels = $methodCatalog['valuation_methods'] ?? []; $forecastLabels = $methodCatalog['forecast_methods'] ?? []; $methodLabel = function (string $code) use ($submission, $valuationLabels, $forecastLabels) { if ($submission->analysis_type === \App\Models\FileSubmission::TYPE_VALUATION && isset($valuationLabels[$code]['label'])) return __($valuationLabels[$code]['label']); if ($submission->analysis_type === \App\Models\FileSubmission::TYPE_FORECAST && isset($forecastLabels[$code]['label'])) return __($forecastLabels[$code]['label']); return $code; }; @endphp @if(in_array($submission->analysis_type, [\App\Models\FileSubmission::TYPE_FORECAST, \App\Models\FileSubmission::TYPE_VALUATION], true) && !empty($submission->methods))

{{ $submission->analysis_type === \App\Models\FileSubmission::TYPE_VALUATION ? __('Valuation methods requested by the client') : __('Forecast methods requested by the client') }}

@foreach($submission->methods as $code) @php $w = data_get($submission->method_weights, $code); @endphp {{ $methodLabel($code) }} @if($w !== null) {{ rtrim(rtrim(number_format((float) $w, 2, '.', ''), '0'), '.') }}% @endif @endforeach
@if(!empty($submission->method_weights)) @php $sum = array_sum(array_map('floatval', (array) $submission->method_weights)); @endphp

{{ __('Total weight: :p%', ['p' => rtrim(rtrim(number_format($sum, 2, '.', ''), '0'), '.')]) }}

@endif
@endif @if($submission->note)

{{ __('Client note') }}

{{ $submission->note }}

@endif @if($latest)

{{ __($latest->typeLabel()) }}

{{ __('Status') }}: {{ __($latest->status) }} @if($latest->finished_at) Β· {{ $latest->finished_at->diffForHumans() }} @endif

@if($latest->tokens_used) {{ number_format($latest->tokens_used) }} {{ __('tokens') }} @endif @if($latest->language) {{ $latest->language }} @endif
@if(!empty($latest->requested_kpis) || $latest->instructions)
@if(!empty($latest->requested_kpis))
{{ __('Requested KPIs') }}: @foreach($latest->requested_kpis as $k) {{ __($k) }} @endforeach
@endif @if($latest->instructions)
{{ __('Instructions') }}: {{ $latest->instructions }}
@endif
@endif @if($latest->status === \App\Models\FileAnalysis::STATUS_FAILED)
{{ __('Analysis failed') }}
{{ $latest->error }}
@elseif(in_array($latest->status, [\App\Models\FileAnalysis::STATUS_DONE, \App\Models\FileAnalysis::STATUS_AWAITING_REVISION], true)) @if($latest->status === \App\Models\FileAnalysis::STATUS_AWAITING_REVISION)
{{ __('Ready for your review') }}
{{ __('The analysis is ready. Edit the summary, KPIs and charts before sending β€” or send as-is.') }}
{{ __('Open review') }}
@endif @if($latest->summary)
{{ __('Executive summary') }}

{{ $latest->summary }}

@endif @if(!empty($latest->kpis))
{{ __('Key indicators') }}
{{ count($latest->kpis) }}
@foreach($latest->kpis as $kpi) @php $trend = $kpi['trend'] ?? null; $trendCls = $trend === 'up' ? 'text-emerald-700 bg-emerald-50 dark:bg-emerald-900/30 dark:text-emerald-300' : ($trend === 'down' ? 'text-rose-700 bg-rose-50 dark:bg-rose-900/30 dark:text-rose-300' : ($trend === 'flat' ? 'text-slate-600 bg-slate-100 dark:bg-slate-800 dark:text-slate-300' : '')); $trendLabel = $trend === 'up' ? __('up') : ($trend === 'down' ? __('down') : ($trend === 'flat' ? __('flat') : '')); @endphp
{{ $kpi['label'] ?? '' }}
@if($trend) @if($trend === 'up') ↑ @elseif($trend === 'down') ↓ @else β†’ @endif {{ $trendLabel }} @endif
{{ $kpi['value'] ?? 'β€”' }}{{ $kpi['unit'] ?? '' }}
@if(!empty($kpi['comment']))
{{ $kpi['comment'] }}
@endif @if(!empty($kpi['chart']) && is_array($kpi['chart']))
{!! \App\Services\Charting\ChartRenderer::render( (string) ($kpi['chart']['type'] ?? ''), (array) ($kpi['chart']['series'] ?? []), [ 'categories' => array_values((array) ($kpi['chart']['categories'] ?? [])), 'width' => 280, 'height' => 110, ], ) !!}
@endif
@endforeach
@endif @if($latest->explanation_html)
{{ __('Detailed explanation') }}
{!! $latest->explanation_html !!}
@endif @else
{{ __('Analysis is running…') }}
{{ __('This page refreshes automatically. You can leave it.') }}
@endif
@elseif(! $isFileless)

{{ __('No analysis yet') }}

{{ __('Pick an analysis type and the KPIs you want β€” the system takes it from there.') }}

@endif @if($analyses->count() > 1) @endif
@endsection