@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
| 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)
| Date |
Description |
Amount (KES) |
@forelse($workItems as $r)
| {{ $r['date'] }} |
{{ $r['desc'] }} {{ $r['basis'] }} |
{{ number_format($r['amount']) }} |
@empty
| No approved work entries |
@endforelse
@if(count($expenseItems))
General Expenses (approved)
| Date |
Description |
Amount (KES) |
@foreach($expenseItems as $r)
| {{ $r['date'] }} |
{{ $r['desc'] }} |
{{ number_format($r['amount']) }} |
@endforeach
@endif
@if(count($logisticsItems))
Transport & Logistics (approved)
| Date |
Description |
Amount (KES) |
@foreach($logisticsItems as $r)
| {{ $r['date'] }} |
{{ $r['desc'] }} |
{{ number_format($r['amount']) }} |
@endforeach
@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.
| Date |
Description |
Status |
Reviewer Note |
@foreach($excludedRows as $r)
| {{ $r['date'] }} |
{{ $r['desc'] }} |
{{ $r['status'] }} |
{{ $r['note'] ?? '-' }} |
@endforeach
@endif