@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') }}
{{ __('Run analysis') }}
@else
{{ __('Attach source data') }}
@endunless
@if($isRunning)
{{ __('Abort analysis') }}
@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
@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)
@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
@if($done)
@else
{{ $i + 1 }}
@endif
{{ $step['label'] }}
{{ $step['at'] ? \Illuminate\Support\Carbon::parse($step['at'])->diffForHumans() : 'β' }}
@unless($isLast)
@endunless
@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 --}}
@csrf
{{ __('Are you sure you want to abort this analysis?') }}
{{ __('This will stop the running analysis and mark the submission as abandoned.') }}
{{ __('Cancel') }}
{{ __('Yes, abort') }}
@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 a file') }}
{{ __('Pull from integration') }}
@if($uploaderIntegrations->isNotEmpty())
{{ $uploaderIntegrations->count() }}
@endif
{{-- Upload tab --}}
{{-- 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
{{ __('Source integration') }}
@foreach($uploaderIntegrations as $itg)
{{ $itg->providerLabel() }}@if($itg->label && $itg->label !== $itg->providerLabel()) β {{ $itg->label }}@endif
@endforeach
@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.') }}
{{ __('Run analysis') }}
@endif
@if($analyses->count() > 1)
@endif
@endsection