@php $userName = auth()->user()->name ?? 'System'; $rate = (float)($activity->engagement_rate ?? 0); $engagement = $activity->engagement_type ?? $activity->engagement ?? 'daily'; $logs = $activity->logs->sortBy('date'); $itemRows = []; // Pending or rejected items — kept out of the payable totals entirely, // but listed in the "Not included" section below rather than silently // dropped, so the invoice explains itself instead of just looking short. $excludedRows = []; $itemN = 0; $workTotal = 0; $expTotal = 0; foreach ($logs as $log) { $dLabel = \Carbon\Carbon::parse($log->date)->format('j M Y'); $data = (array)($log->log_data ?: []); $works = $data['work'] ?? []; $dayReviewStatus = $data['review']['status'] ?? null; $dayReviewNote = $data['review']['note'] ?? null; if (($activity->engagement_type ?? 'daily') === 'daily') { // Daily/fixed has no per-item amount to split — the whole // day's rate only counts toward the invoice once the day // itself has been approved. Pending/rejected days never make // it into the payable total. $desc = collect($works)->map(fn($w) => is_array($w) ? ($w['desc'] ?? '') : $w)->filter()->implode('; '); if ($dayReviewStatus === 'approved') { $itemN++; $itemRows[] = ['n'=>$itemN,'date'=>$dLabel,'type'=>'Work','desc'=> $desc ?: 'Day logged','basis'=>"1 day x KES " . number_format($rate),'amount'=>$rate]; $workTotal += $rate; } else { $excludedRows[] = ['date'=>$dLabel,'desc'=>$desc ?: 'Day logged','status'=>$dayReviewStatus ?: 'pending','note'=>$dayReviewNote]; } } else { // Hourly: each work entry carries its OWN review, independent // of the day-level review — set via the per-item approve/reject // buttons — so it's checked per entry, not per day. foreach ($works as $w) { $hours = is_array($w) ? ($w['hours'] ?? 0) : 0; if ($hours <= 0) continue; $desc = is_array($w) ? ($w['desc'] ?? '') : $w; $itemReviewStatus = is_array($w) ? ($w['review']['status'] ?? null) : null; $itemReviewNote = is_array($w) ? ($w['review']['note'] ?? null) : null; if ($itemReviewStatus === 'approved') { $itemN++; $lineAmt = $hours * $rate; $itemRows[] = ['n'=>$itemN,'date'=>$dLabel,'type'=>'Work','desc'=>$desc ?: '-','basis'=>"{$hours}h x KES " . number_format($rate),'amount'=>$lineAmt]; $workTotal += $lineAmt; } else { $excludedRows[] = ['date'=>$dLabel,'desc'=>$desc ?: '-','status'=>$itemReviewStatus ?: 'pending','note'=>$itemReviewNote]; } } } foreach ($data['expenses'] ?? [] as $e) { $desc = ($e['desc'] ?? '-') . ' (' . ($e['cat'] ?? 'Other') . ')'; $itemReviewStatus = $e['review']['status'] ?? null; if ($itemReviewStatus === 'approved') { $itemN++; $amt = floatval($e['amount'] ?? 0); $itemRows[] = ['n'=>$itemN,'date'=>$dLabel,'type'=>'Expense','desc'=>$desc,'basis'=>'-','amount'=>$amt]; $expTotal += $amt; } else { $excludedRows[] = ['date'=>$dLabel,'desc'=>$desc,'status'=>$itemReviewStatus ?: 'pending','note'=>$e['review']['note'] ?? null]; } } foreach ($data['logistics'] ?? [] as $lg) { $desc = ($lg['from'] ?? '-') . ' -> ' . ($lg['to'] ?? '-') . ' (' . ($lg['mode'] ?? '-') . ')'; $itemReviewStatus = $lg['review']['status'] ?? null; if ($itemReviewStatus === 'approved') { $itemN++; $cost = floatval($lg['cost'] ?? 0); $itemRows[] = ['n'=>$itemN,'date'=>$dLabel,'type'=>'Transport','desc'=>$desc,'basis'=>'-','amount'=>$cost]; $expTotal += $cost; } else { $excludedRows[] = ['date'=>$dLabel,'desc'=>$desc,'status'=>$itemReviewStatus ?: 'pending','note'=>$lg['review']['note'] ?? null]; } } } $total = $workTotal + $expTotal; $displayStatus = match ($activity->status) { 'draft' => 'Draft', 'submitted' => 'Submitted', 'approved' => 'Approved', 'funded' => 'Paid', 'rejected' => 'Rejected', default => ucfirst($activity->status ?? 'Unknown'), }; $paymentStatus = in_array($activity->status, ['funded']) ? 'Paid' : 'Unpaid'; $engagementLabel = $activity->engagement_type ? ucfirst($activity->engagement_type) : 'Not set'; @endphp
Field Activity Invoice
{{ $activity->title }}
Invoice No: {{ 'FAM-INV-' . str_pad($activity->id,5,'0',STR_PAD_LEFT) }}
Generated: {{ now()->format('j M Y H:i') }}
By: {{ $userName }}
Personnel{{ $activity->user->name ?? '-' }}
Engagement{{ $engagementLabel }} - KES {{ number_format($rate) }}
Status{{ $displayStatus }}
Payment{{ $paymentStatus }}
Period{{ \Carbon\Carbon::parse($activity->start_date)->format('j M Y') }} - {{ \Carbon\Carbon::parse($activity->end_date)->format('j M Y') }}
Location{{ $activity->location ?? '-' }}
@php $workItems = collect($itemRows)->where('type', 'Work')->all(); $expenseItems = collect($itemRows)->where('type', 'Expense')->all(); $logisticsItems = collect($itemRows)->where('type', 'Transport')->all(); @endphp
Work done (approved)
@forelse($workItems as $r) @empty @endforelse
Date Description Amount (KES)
{{ $r['date'] }} {{ $r['desc'] }}
{{ $r['basis'] }}
{{ number_format($r['amount']) }}
No approved work entries
@if(count($expenseItems))
General Expenses (approved)
@foreach($expenseItems as $r) @endforeach
Date Description Amount (KES)
{{ $r['date'] }} {{ $r['desc'] }} {{ number_format($r['amount']) }}
@endif @if(count($logisticsItems))
Transport & Logistics (approved)
@foreach($logisticsItems as $r) @endforeach
Date Description Amount (KES)
{{ $r['date'] }} {{ $r['desc'] }} {{ number_format($r['amount']) }}
@endif
Work Logged Subtotal (Approved){{ number_format($workTotal) }}
Expenses & Logistics Subtotal (Approved){{ number_format($expTotal) }}
Total Payable (KES) {{ number_format($total) }}
@if(count($excludedRows))
Not Included In This Invoice
These items are pending review or were rejected, so they're excluded from the payable total above.
@foreach($excludedRows as $r) @endforeach
Date Description Status Reviewer Note
{{ $r['date'] }} {{ $r['desc'] }} {{ $r['status'] }} {{ $r['note'] ?? '-' }}
@endif