@php $money = fn ($v) => number_format((float) $v, 2); $signed = fn ($v) => (float) $v >= 0 ? 'text-emerald-600' : 'text-rose-600'; // Status pill palette (On Track / At Risk / Behind). $toneChip = [ 'good' => 'bg-emerald-50 text-emerald-700 ring-emerald-600/15', 'warn' => 'bg-amber-50 text-amber-700 ring-amber-600/15', 'bad' => 'bg-rose-50 text-rose-700 ring-rose-600/15', 'neutral' => 'bg-slate-100 text-slate-600 ring-slate-500/15', ]; // Heatmap cell palette by band. $heat = [ 'high' => 'bg-emerald-100 text-emerald-800', 'med' => 'bg-amber-100 text-amber-800', 'low' => 'bg-rose-100 text-rose-800', ]; // Build an SVG polyline from a value series (auto-scaled to the box). $spark = function (array $vals, int $w = 120, int $h = 34): string { $vals = array_values(array_map('floatval', $vals)); if (count($vals) < 2) { return ''; } $min = min($vals); $max = max($vals); $range = ($max - $min) ?: 1; $n = count($vals) - 1; $pts = []; foreach ($vals as $i => $v) { $x = $i / $n * $w; $y = $h - (($v - $min) / $range) * ($h - 4) - 2; $pts[] = round($x, 1) . ',' . round($y, 1); } return implode(' ', $pts); }; // Donut-gauge arc path (r chosen so circumference ≈ 100 → dasharray = pct). $arc = 'M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831'; $payColors = ['paid' => 'emerald', 'partial' => 'sky', 'overdue' => 'rose', 'unpaid' => 'slate', 'void' => 'slate']; $commColors = ['fully_paid' => 'emerald', 'deposit_paid' => 'sky', 'not_funded' => 'slate']; // The three page sections. $tabMeta = [ 'summary' => ['label' => __('Profit Summary'), 'icon' => 'chart-bar'], 'jobs' => ['label' => __('Job Profitability'), 'icon' => 'briefcase'], 'costs' => ['label' => __('Cost & Margin'), 'icon' => 'calculator'], ]; // Month-by-month table: how each line of the statement is drawn. $rowStyle = [ 'plain' => ['label' => 'text-slate-600', 'value' => 'text-slate-600', 'row' => '', 'bg' => 'bg-white'], 'deduct' => ['label' => 'pl-5 text-slate-500', 'value' => 'text-slate-500', 'row' => '', 'bg' => 'bg-white'], 'subtotal' => ['label' => 'font-semibold text-slate-800', 'value' => 'font-semibold text-slate-900', 'row' => 'border-t border-slate-200', 'bg' => 'bg-white'], 'total' => ['label' => 'font-bold text-slate-900', 'value' => 'font-bold', 'row' => 'border-t-2 border-slate-300 bg-slate-50/70', 'bg' => 'bg-slate-50'], 'memo' => ['label' => 'text-slate-400', 'value' => 'text-slate-400', 'row' => '', 'bg' => 'bg-white'], ]; @endphp
{{ $card['label'] }}
@if ($card['sub']){{ $card['sub'] }}
@endif{{ $card['value'] }}
@if ($card['target']){{ $card['target'] }} · {{ $card['pct'] }}% {{ $card['pct_caption'] }}
@endif @if ($card['delta'] !== null){{ $card['label'] }}
{{ $card['delta_pp'] >= 0 ? '+' : '−' }}{{ number_format(abs($card['delta_pp']), 1) }}pp {{ __('vs last month') }}
@endif @endif{{ __('What was billed, what was earned, what was banked and what is still owed.') }}
{{ __('Gross and net profit on this page are recognised revenue less costs.') }} {{ __('Cash collected measures collections, not trading, so it sits on the right with the other receivables figures. Amount invoiced is what was billed and is not revenue until credits and refunds come off it.') }} {{ __('The GP hurdle and commission pool below deliberately stay on collected cash, which is the basis the workspace pays commission on.') }}
{{ __('Cash & Receivables') }}
@foreach ($cashCards as $c){{ $c['label'] }}
{{ $c['sub'] }}
{{ $c['value'] }}
@if ($c['delta'] !== null){{ $c['delta'] >= 0 ? '+' : '−' }}{{ number_format(abs($c['delta']), 1) }}%
@endif{{ __('The vs Target columns are empty because no monthly target is set for these metrics.') }} {{ __('Set one on Targets') }} {{ __('and they fill in here automatically.') }}
@endunless{{ __('Every line of the statement, one column per month, on the recognised-revenue basis.') }}
| {{ __('Line') }} | @foreach ($monthlyCompare['months'] as $m){{ $m['label'] }} | @endforeach{{ __('Total') }} | {{ __('Avg/mo') }} |
|---|---|---|---|
| {{ $r['label'] }} @if ($r['note']) @endif | @foreach ($r['values'] as $v){{ $fmt($v) }} | @endforeach{{ $fmt($r['total']) }} | {{ $fmt($r['avg']) }} |
{{ __('All amounts in :code. Deductions are shown in brackets.', ['code' => $monthlyCompare['base']]) }} {{ __('For the percentage lines, Total is the weighted figure for the whole span (e.g. total gross profit ÷ total recognised revenue) while Avg/mo is the plain average of the monthly percentages — the two differ whenever the months are of different sizes.') }} {{ __('Memo lines are cash-flow measures shown for reference; they take no part in the profit above them.') }}
{{ $healthScore['label'] }}
{{ $mini['l'] }}
{{ $baseCode }} {{ $money($mini['v']) }}
{{ $gpStatus['above_break_even'] ? __('Above Break-even') : __('Below Break-even') }} · {{ $gpStatus['commission_active'] ? __('Commission Eligible') : __('Not Yet Commission Eligible') }}
{{ __('Commission Status') }}
{{ $gpStatus['commission_active'] ? __('Eligible') : __('Not Eligible') }}
{{ __('Commission Pool') }}
{{ $baseCode }} {{ $money($gpStatus['commission_pool']) }}
{{ __('How the amount invoiced becomes net profit.') }}
{{ __('Recognised revenue, gross and net profit against the margins they produced (12 months).') }}
{{ __('Projected to period end at the current pace.') }}
{{ __('Gaps that would make the figures above understate the truth.') }}
@if (empty($warnings)){{ __('Job-costing basis: each job\'s own line items and actual costs.') }}
| # | {{ __('Job') }} | {{ __('Customer') }} | {{ __('Invoiced') }} | {{ __('GP') }} | {{ __('Margin') }} |
|---|---|---|---|---|---|
| {{ $i + 1 }} | {{ $row['job_number'] }} | {{ \Illuminate\Support\Str::limit($row['customer'], 18) }} | {{ $baseCode }} {{ $money($row['invoice_value']) }} | {{ $baseCode }} {{ $money($row['profit']) }} | {{ number_format($row['margin'], 1) }}% |
| {{ __('No revenue-generating jobs in range.') }} | |||||
{{ __('Attention required.') }}
{{ __('Reworked Jobs') }}
{{ $rework['rework_jobs'] }}
{{ __('of :n completed', ['n' => $rework['cohort']]) }}
{{ __('Rework Rate') }}
{{ number_format($rework['rework_pct'], 1) }}%
{{ __('threshold :t%', ['t' => number_format($rework['threshold_pct'], 0)]) }}
{{ __('Total Reworks') }}
{{ $rework['rework_count'] }}
{{ __('reprints logged') }}
{{ __('Cost Overrun') }}
{{ $rework['overrun_label'] }}
{{ __('on reworked jobs') }}
{{ __('The period\'s overhead nut spread over the jobs that consumed it, so every job carries a net profit and not only a gross one.') }}
{{ __('Period Overheads') }}
{{ $absorption['overheads_label'] }}
{{ __('the nut being absorbed') }}
{{ __('Allocated to Jobs') }}
{{ $absorption['allocated_label'] }}
{{ $absorption['unallocated'] > 0.005 ? __(':amount unallocated (no job activity)', ['amount' => $absorption['unallocated_label']]) : __('adds up to the overhead line exactly') }}
{{ $absorption['is_labour_basis'] ? __('Absorption Rate') : __('Allocation Driver') }}
{{ $absorption['rate_label'] ?? __('Revenue share') }}
@if ($absorption['is_labour_basis']) {{ __(':driver driver hours (:logged logged)', ['driver' => number_format($absorption['driver_hours'], 1), 'logged' => number_format($absorption['logged_hours'], 1)]) }} @elseif ($absorption['degraded_to_revenue']) {{ __('no time logged this period — fell back to sale value') }} @else {{ __('sharing the nut by sale value') }} @endif
{{ __('Jobs Carrying Overhead') }}
{{ $absorption['jobs'] }}
{{ $absorption['imputed_jobs'] > 0 ? __(':n with no timesheet (hours estimated from revenue)', ['n' => $absorption['imputed_jobs']]) : __('all with logged time') }}
{{ __('Per-job net profit is a management allocation, not a cash fact.') }} {{ __('A job\'s absorbed overhead is its share of a company-wide nut (rent, salaries, depreciation) that would have been paid whether the job existed or not. No money left the business "for this job". Read it as: of the capacity we paid for this period, this job used roughly this much.') }}
{{ __('The :n job(s) listed below carry :visible of the :total allocated.', [ 'n' => $absorption['visible_jobs'], 'visible' => $absorption['visible_label'], 'total' => $absorption['allocated_label'], ]) }} {{ $absorption['is_filtered'] ? __('A filter is active, so the rest sits on jobs outside it.') : __('The rest sits on jobs opened in an earlier period that were still being worked on during this one — the table lists jobs by the date they were opened.') }}
@endif{{ __('Job-costing basis (own line items + actual costs).') }}
| {{ __('Job') }} | {{ __('Status') }} | {{ __('Payment') }} | {{ __('Invoiced') }} | {{ __('Paid') }} | {{ __('Cash') }} | {{ __('Est. Cost') }} | {{ __('Actual Cost') }} | {{ __('Gross Profit') }} | {{ __('Margin') }} | {{-- G9: allocated, not spent. The caption under the table says so. --}}{{ __('Overhead') }} | {{ __('Net Profit') }} | {{ __('Comm.') }} | {{ __('Flags') }} |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
{{-- Expands the job-level P&L ladder: revenue → direct costs
→ gross profit → absorbed overhead → net profit. --}}
{{ $row['job_number'] }}
{{ \Illuminate\Support\Str::limit($row['customer'].' · '.$row['title'], 36) }}
|
|
@if ($row['payment_status'])
|
{{ $row['invoice_value'] > 0 ? $baseCode.' '.$money($row['invoice_value']) : '—' }} | {{ $baseCode }} {{ $money($row['amount_paid']) }} | {{ $baseCode }} {{ $money($row['cash_collected']) }} | {{ $row['has_estimate'] ? $baseCode.' '.$money($row['estimated_cost']) : '—' }} | {{ $baseCode }} {{ $money($row['actual_cost']) }} | {{ $baseCode }} {{ $money($row['profit']) }} | {{ number_format($row['margin'], 1) }}% | @if ($row['absorbed_overhead'] > 0) {{ $baseCode }} {{ $money($row['absorbed_overhead']) }} @if ($row['overhead_imputed']) {{ __('est. hours') }} @endif @else — @endif | {{ $baseCode }} {{ $money($row['net_profit']) }} {{ number_format($row['net_margin'], 1) }}% |
@if ($row['commission_eligible'] === true)
|
@if (empty($row['flags']))
—
@else
@foreach ($row['flags'] as $flag)
@endif
|
|
{{ __('Job P&L') }} · {{ $row['job_number'] }}
{{ __('Absorbed overhead is a management allocation, not a cash fact.') }} {{ __('It is this job\'s share of the period nut, not money the job spent.') }} @if ($row['overhead_imputed']){{ __('No time was logged on this job, so its hours were estimated from its revenue. Logging time makes the share accurate.') }} @endif @if ($row['provisional']){{ __('This job is still live work — its share will move as more time and cost land on it.') }} @endif |
|||||||||||||
{{ __('Per-job figures use each job\'s own line items + actual costs (job-costing basis), separate from the company summary above. Invoiced / paid / cash are invoice-level, so jobs sharing one invoice show that invoice\'s figures.') }} {{ __('Overhead is allocated, not spent: it is the job\'s share of the period nut, so Net Profit is a management allocation rather than a cash fact. Expand a row for the full ladder.') }} @if ($hasActivePlans) {{ __('Commission eligibility reflects the workspace\'s active commission model.') }} @endif
@else| {{ __('Category') }} | {{ __('Jobs') }} | {{ __('Revenue') }} | {{ __('Cost') }} | {{ __('Profit') }} | {{ __('Margin') }} |
|---|---|---|---|---|---|
| {{ $row['category'] }} | {{ $row['jobs'] }} | {{ $baseCode }} {{ $money($row['revenue']) }} | {{ $baseCode }} {{ $money($row['cost']) }} | {{ $baseCode }} {{ $money($row['profit']) }} | {{ number_format($row['margin'], 1) }}% |
| {{ __('Customer') }} | {{ __('Revenue') }} | {{ __('GP') }} | {{ __('Margin') }} | {{ __('Jobs') }} |
|---|---|---|---|---|
| {{ \Illuminate\Support\Str::limit($c['customer'], 28) }} | {{ $baseCode }} {{ $money($c['revenue']) }} | {{ $baseCode }} {{ $money($c['gp']) }} | {{ number_format($c['margin'], 1) }}% | {{ $c['jobs'] }} |
| {{ __('No customer profit in range.') }} | ||||
{{ __('No costs in range.') }}
@else{{ __('Direct costs against the overhead nut, 12 months.') }}
{{ $costVariance['pct'] > 0 ? '+' : '' }}{{ number_format($costVariance['pct'], 1) }}%
{{ $costVariance['pct'] <= 0 ? __('Under budget') : __('Over budget') }} · {{ $costVariance['jobs'] }} {{ __('jobs') }}
{{ __('No jobs with a cost estimate in range.') }}
@endif{{ __('Where the profit is, banded against the rest of the set.') }}
@if (empty($heatmap)){{ __('No category data in range.') }}
@else| {{ __('Category') }} | {{ __('Revenue') }} | {{ __('Cost') }} | {{ __('GP') }} | {{ __('Margin') }} |
|---|---|---|---|---|
| {{ $h['category'] }} | @foreach (['revenue', 'cost', 'gp', 'margin'] as $col){{ $col === 'margin' ? number_format($h['margin_pct'], 0) . '%' : __(ucfirst($h[$col])) }} | @endforeach
{{ __('All amounts are in :code and converted to your base currency. Gross and net profit are on the accrual basis (revenue recognised less costs); cash collected, receivables and the commission ladder are cash-basis and labelled where they appear. Per-job figures use job costing.', ['code' => $baseCode]) }}