@extends('layouts.finance') @section('title', 'Transactions') @section('subtitle', 'All deposits, withdrawals, transfers and payments') @section('content') @php /* * Tile definitions — "examism" and "invoices" are intentionally excluded here. * * - "examism to bank" transfer entries are filtered out at the query layer * (FinanceTransactionController::EXCLUDED_SOURCES). They do not belong on * this listing because they represent internal bookkeeping movements. * * - "invoice" entries are similarly excluded at the query layer and live * exclusively on the dedicated Invoices page (/invoices). * * The Figma reference (renderTxTiles, order array) shows only: pakBanks, wallets, usBanks. */ $bankIcon = ''; $walletIcon = ''; $creditCardIcon = ''; $tileStyles = [ 'pak-banks' => ['pill-amber', 'ico-bg-amber'], 'wallets' => ['pill-cyan', 'ico-bg-cyan'], 'us-banks' => ['pill-blue', 'ico-bg-purple'], 'aus-banks' => ['pill-teal', 'ico-bg-teal'], 'credit-cards' => ['pill-red', 'ico-bg-red'], ]; $tiles = collect($categoryLabels)->map(function($label, $key) use ($tileStyles, $bankIcon, $walletIcon, $creditCardIcon) { [$pill, $iconBg] = $tileStyles[$key] ?? ['pill-blue', 'ico-bg-purple']; $icon = match($key) { 'wallets' => $walletIcon, 'credit-cards' => $creditCardIcon, default => $bankIcon, }; return [ 'title' => strtoupper($label), 'subtitle' => $label.' transactions', 'pill' => $pill, 'iconBg' => $iconBg, 'icon' => $icon, ]; })->all(); $activeTile = $tiles[$bucket] ?? null; $bucketCurrency = match($bucket) { 'pak-banks' => 'PKR', 'us-banks' => 'USD', 'aus-banks' => 'AUD', default => null, }; $displayCurrency = $bucketCurrency ?? $selectedBankAccount?->currency ?? 'USD'; $transactionCurrency = fn($transaction) => $bucketCurrency ?? $transaction->bankAccount?->currency ?? $transaction->metadata['currency'] ?? 'USD'; $isAccountBucket = $activeTile !== null; $bucketAccent = match($bucket) { 'pak-banks' => ['linear-gradient(135deg,#fffbeb,#ffffff)', '#fde68a', 'pill-amber', 'Pakistan Bank Accounts', 'Click any account to add credit or debit'], 'aus-banks' => ['linear-gradient(135deg,#ecfeff,#ffffff)', '#a5f3fc', 'pill-teal', 'Australia Bank Accounts', 'Click any account to view or record transactions'], 'wallets' => ['linear-gradient(135deg,#fdf2f8,#ffffff)', '#fecdd3', 'pill-cyan', 'Wallets', 'Wallet and payment gateway accounts - click one to view transactions'], 'us-banks' => ['linear-gradient(135deg,#eef2ff,#ffffff)', '#c7d2fe', 'pill-blue', 'US Banks', 'United States Bank Accounts - click a bank to view or record transactions'], default => ['linear-gradient(135deg,#eef2ff,#ffffff)', '#c7d2fe', 'pill-blue', $categoryLabels[$bucket] ?? 'Accounts', 'Click any account to view or record transactions'], }; $storeSource = match($bucket) { 'pak-banks' => 'pakBanks', 'us-banks' => 'usBanks', 'aus-banks' => 'ausBanks', 'wallets' => 'wallet', default => $bucket, }; $accountInitials = fn($account) => collect(preg_split('/\s+/', trim($account->bank_name ?: $account->name))) ->filter() ->take(2) ->map(fn($part) => strtoupper(substr($part, 0, 1))) ->implode('') ?: 'BA'; $maskedAccount = fn($account) => $account->account_number ? '****'.substr(preg_replace('/\D+/', '', $account->account_number) ?: $account->account_number, -4) : 'No acct'; $debitSupplierOptions = $selectedBankAccount ? $selectedBankAccount->debitDestinations ->toBase() ->map(fn($account) => trim($account->name.' - '.($account->bank_name ?: 'Linked Account'))) ->merge(($supplierOptions ?? collect())->toBase()) ->filter() ->unique() ->values() : collect(); /* * Build the structured payload behind the "amount details" popup. * Returns a JSON string (or null when there is nothing to show) that the * shared modal renders as a definition list, a per-date "Received on" * breakdown table, and an optional partial-amounts section. */ $amountDetailJson = function ($transaction, $currency, $approvalOriginal, $approvalApproved, $approvalRemaining, $partialAmounts = null, $partialCurrency = null) use ($metrics) { $hasApproval = $approvalOriginal !== null && $approvalApproved !== null && $approvalRemaining !== null; $breakdown = collect($transaction->metadata['approval_breakdown'] ?? []) ->filter(fn($entry) => is_array($entry) && isset($entry['amount']) && is_numeric($entry['amount'])) ->map(fn($entry) => [ 'date' => ! empty($entry['date']) ? \Illuminate\Support\Carbon::parse($entry['date'])->format('d M Y') : null, 'amount' => $metrics->smartAmount($entry['amount']), ]) ->values(); $partials = ($partialAmounts !== null && $partialAmounts->isNotEmpty()) ? [ 'currency' => $partialCurrency, 'items' => $partialAmounts->map(fn($amount) => $metrics->smartAmount($amount))->values()->all(), 'total' => $metrics->smartAmount($partialAmounts->sum()), ] : null; // Build whichever approval rows are available so the approved (partial) // amount always appears, even when only some approval fields are stored. $rows = []; if ($approvalOriginal !== null) { $rows[] = ['label' => 'Original amount', 'value' => $metrics->smartAmount($approvalOriginal)]; } if ($approvalApproved !== null) { $rows[] = ['label' => 'Received / approved amount', 'value' => $metrics->smartAmount($approvalApproved)]; } if ($approvalRemaining !== null) { $rows[] = ['label' => 'Remaining amount', 'value' => $metrics->smartAmount($approvalRemaining)]; } if (! empty($rows)) { $rows[] = ['label' => 'This row amount', 'value' => $metrics->smartAmount($transaction->amount)]; } if (empty($rows) && $breakdown->isEmpty() && $partials === null) { return null; } return json_encode([ 'currency' => $currency, 'rows' => $rows, 'breakdown' => $breakdown->all(), 'partials' => $partials, ], JSON_UNESCAPED_UNICODE); }; @endphp @if(! $activeTile) {{-- Top-level tile picker --}}
@foreach($tiles as $key => $tile)
{!! $tile['icon'] !!}
{{ $tile['title'] }}
@endforeach
Click any card to view, edit, or add transactions
@else @php $txTab = $txTab ?? 'account'; $rows = $transactions->getCollection(); $totalCurrency = $displayCurrency; $postedDisplayAmount = function ($transaction) { $metadata = $transaction->metadata ?? []; $status = $metadata['status'] ?? 'Cleared'; $approved = $metadata['approval_approved_amount'] ?? null; $extra = is_numeric($metadata['approval_extra_amount'] ?? null) ? (float) $metadata['approval_extra_amount'] : 0.0; if ($status === 'Pending' && is_numeric($approved) && (float) $approved > 0) { return (float) $approved; } return (float) $transaction->amount + $extra; }; $totalAmount = fn($transaction) => ($bucketCurrency || $selectedBankAccount) ? $postedDisplayAmount($transaction) : $metrics->usd($postedDisplayAmount($transaction), $transactionCurrency($transaction)); // IN / OUT classification is driven by the authoritative `type` column // (income / expense), not the display-only tx_kind. A transfer's source // leg is an expense and its destination leg is an income — using // tx_kind here would misclassify the credit leg as outgoing. $totalIn = $rows->filter(fn($t) => $t->type === 'income')->sum($totalAmount); $totalOut = $rows->filter(fn($t) => $t->type === 'expense')->sum($totalAmount); $net = $totalIn - $totalOut; @endphp {{-- Breadcrumb & back button --}}
Back
Transactions / {{ $activeTile['title'] }}
@if($bucket === 'credit-cards') {{-- ------------------------------------------------------------------ --}} {{-- Credit Cards — grid overview OR selected-card ledger. --}} {{-- ------------------------------------------------------------------ --}} @php $ccCount = $creditCards->count(); @endphp @if($selectedCreditCard) {{-- ── Selected card: header + action buttons ─────────────────── --}} @php $cardPayableBalance = (float) $selectedCreditCard->payable_balance; $signedCardBalance = -1 * $cardPayableBalance; $cardAvailableBalance = (float) $selectedCreditCard->credit_limit - $cardPayableBalance; $scUtil = (float) $selectedCreditCard->credit_limit > 0 ? ($cardPayableBalance / (float) $selectedCreditCard->credit_limit) * 100 : 0; $scBarColor = $scUtil > 80 ? '#ef4444' : ($scUtil > 50 ? '#f59e0b' : '#10b981'); $defaultClosingMonth = $defaultClosingMonth ?? now()->copy()->startOfMonth()->subMonth()->format('Y-m'); $defaultClosingMonthLabel = $defaultClosingMonthLabel ?? \Illuminate\Support\Carbon::createFromFormat('Y-m', $defaultClosingMonth)->format('F'); $selectedCreditClosingStatus = $selectedCreditClosingStatus ?? null; @endphp
{{ $selectedCreditCard->issuer }}
{{ $selectedCreditCard->card_name }}
{{ $selectedCreditCard->holder }}
Payable Balance
{{ $metrics->money($signedCardBalance, $selectedCreditCard->currency) }}
Available {{ $metrics->money($cardAvailableBalance, $selectedCreditCard->currency) }} · Limit {{ $metrics->money($selectedCreditCard->credit_limit, $selectedCreditCard->currency) }} · {{ number_format($scUtil, 0) }}% used
{{-- Utilization bar --}}
All Cards
@if($selectedCreditClosingStatus) @if($selectedCreditClosingStatus['is_closed']) {{ $defaultClosingMonthLabel }} Closed @else
@csrf
@endif @endif
{{-- ── Card transaction modal ───────────────────────────────── --}} {{-- ── Card ledger table ────────────────────────────────────── --}} {{-- Closed-period notice for the account being viewed. Without this the lock is invisible until a delete fails, which reads as a bug rather than a rule. --}} @if(!empty($periodClosedThrough))
{{ $selectedBankAccount->name ?? 'This account' }} is closed through {{ \Illuminate\Support\Carbon::parse($periodClosedThrough)->format('d M Y') }}. Entries on or before that date cannot be added, edited or deleted. Rows below show a padlock. Manage closing
@endif
{!! $creditCardIcon !!}
Card Transactions
All charges and payments for this card
@foreach(request()->except(['transaction_id_search', 'page']) as $queryKey => $queryValue) @if(is_array($queryValue)) @foreach($queryValue as $item) @endforeach @else @endif @endforeach
@foreach(request()->except(['per_page', 'page']) as $queryKey => $queryValue) @if(is_array($queryValue)) @foreach($queryValue as $item) @endforeach @else @endif @endforeach
{{ $transactions->total() }} tx
{{-- KPI strip: Charges / Payments / Net Payable --}} @php $cardRows = $transactions->getCollection(); $totalCharges = $cardRows->filter(fn($t) => $t->type === 'expense')->sum(fn($t) => (float) $t->amount); $totalPayments = $cardRows->filter(fn($t) => $t->type === 'income')->sum(fn($t) => (float) $t->amount); $cardNet = $totalCharges - $totalPayments; @endphp
Charges
{{ $metrics->money($totalCharges, $selectedCreditCard->currency) }}
Payments
{{ $metrics->money($totalPayments, $selectedCreditCard->currency) }}
Page Net
{{ $metrics->money($cardNet, $selectedCreditCard->currency) }}
{{-- Transaction table --}}
@forelse($transactions as $tx) @php // On a card: expense = charge (payable up), income = payment (payable down). $isCharge = $tx->type === 'expense'; $pillClass = $isCharge ? 'pill-red' : 'pill-green'; $pillLabel = $isCharge ? 'Charge' : 'Payment'; $txStatus = $tx->metadata['status'] ?? 'Cleared'; $amtColor = $isCharge ? '#ef4444' : '#10b981'; $amtPrefix = $isCharge ? '-' : '+'; $txInvNumber = $tx->display_invoice_number ?? ($tx->metadata['invoice_number'] ?? null); $txInvProduct = $tx->display_invoice_product_name ?? ($tx->metadata['invoice_product_name'] ?? null); $approvalOriginalAmount = is_numeric($tx->metadata['approval_original_amount'] ?? null) ? (float) $tx->metadata['approval_original_amount'] : null; $approvalApprovedAmount = is_numeric($tx->metadata['approval_approved_amount'] ?? null) ? (float) $tx->metadata['approval_approved_amount'] : null; $approvalRemainingAmount = is_numeric($tx->metadata['approval_remaining_amount'] ?? null) ? (float) $tx->metadata['approval_remaining_amount'] : null; $approvalExtraAmount = is_numeric($tx->metadata['approval_extra_amount'] ?? null) ? (float) $tx->metadata['approval_extra_amount'] : 0.0; $hasApprovalDetail = $approvalOriginalAmount !== null && $approvalApprovedAmount !== null && $approvalRemainingAmount !== null; $showApprovalRemaining = $txStatus === 'Pending' && $hasApprovalDetail; $approvedPartialAmounts = collect($tx->metadata['approval_breakdown'] ?? []) ->filter(fn($entry) => is_array($entry) && isset($entry['amount']) && is_numeric($entry['amount']) && (float) $entry['amount'] > 0) ->map(fn($entry) => (float) $entry['amount']) ->values(); if ($approvedPartialAmounts->isEmpty() && $showApprovalRemaining && (float) ($approvalApprovedAmount ?? 0) > 0) { $approvedPartialAmounts = collect([(float) $approvalApprovedAmount]); } // Approved/posted amount (matches the summary + balance); // original total stays in the amount-details popup. $displayAmount = $postedDisplayAmount($tx); $amountDetailMessage = $amountDetailJson($tx, $selectedCreditCard->currency, $approvalOriginalAmount, $approvalApprovedAmount, $approvalRemainingAmount); $balanceTransactionId = (int) $tx->id; $canReorder = true; $txDate = $tx->transaction_date?->toDateString(); @endphp @empty @endforelse @if($openingCardBalance !== null) @php $cardOpeningBalance = (float) ($openingCardBalance ?? 0); $cardOpeningColor = $cardOpeningBalance < 0 ? '#ef4444' : '#10b981'; @endphp @endif
Order Date Type Transaction ID Category / Ref Description Amount Balance Actions
@if($canReorder && $txDate)
@else - @endif
{{ $tx->transaction_date?->format('d M Y') }} {{ $pillLabel }} @if($txStatus === 'Pending')
Pending
@endif
{{ $tx->transaction_id ?: '-' }} {{ $tx->referenceLabel() }} @if($txInvNumber)
Invoice {{ $txInvNumber }}
@endif
{{ $tx->description ?: '-' }} @if($txInvProduct)
{{ $txInvProduct }}
@endif @if($tx->counterparty)
{{ $isCharge ? 'To' : 'From' }}: {{ $tx->counterparty }}
@endif
@if($amountDetailMessage) @else {{ $amtPrefix }}{{ $metrics->smartAmount($displayAmount) }} @endif @if($showApprovalRemaining) @foreach($approvedPartialAmounts as $approvedPartialAmount)
Approved {{ $metrics->smartAmount($approvedPartialAmount) }}
@endforeach
Remaining to approve: {{ $metrics->smartAmount($approvalRemainingAmount) }}
@endif @if($approvalExtraAmount > 0)
Extra: {{ $metrics->smartAmount($approvalExtraAmount) }}
@endif
@if(isset($runningCardPayable[$balanceTransactionId])) {{ $metrics->money($runningCardPayable[$balanceTransactionId], $selectedCreditCard->currency) }} @else - @endif
@php $txLockReason = ($periodLockReasons ?? [])[$tx->id] ?? null; $txCloseDate = $tx->transaction_date ? \Illuminate\Support\Carbon::parse($tx->transaction_date) : null; $txCloseType = $tx->bank_account_id ? 'bank' : ($tx->credit_card_id ? 'credit_card' : null); $txCloseId = $tx->bank_account_id ?: $tx->credit_card_id; $txCanClose = ($periodClosingEnabled ?? false) && ! $txLockReason && $txCloseType !== null && $txCloseDate !== null && ! $txCloseDate->isFuture(); $txCloseAccountName = $selectedCreditCard->name ?? 'this card'; @endphp @if($txLockReason) {{-- Greyed pencil; the padlock sits in the tick's slot below. --}} @else @endif @if($txStatus === 'Pending')
@csrf @if($tx->source === 'linked_bank_transfer' || $tx->source === 'credit_card_payment') @endif
@endif
@csrf @method('DELETE')
{{-- Close the period at this row --}} @if($txCanClose)
@csrf {{-- The clicked row: closes the day only as far as here. --}}
@elseif($txLockReason) {{-- The tick's slot becomes the padlock once closed. Clicking it asks for the closing password and unlocks this entry (and the far leg of a transfer). --}}
@csrf
@endif
No transactions yet · click + Credit Transaction or - Debit Transaction to create one
- {{ $selectedCreditCard->created_at?->format('d M Y') }} - - Opening Balance Starting card balance before listed transactions - {{ $metrics->money($cardOpeningBalance, $selectedCreditCard->currency) }} -
{{ $transactions->links() }}
@else {{-- ── No card selected: show the card grid ─────────────────── --}} @push('styles') @endpush
Active credit cards overview
Credit Cards
{{ $ccCount }} card{{ $ccCount === 1 ? '' : 's' }}
@if($creditCards->isNotEmpty())
@foreach($creditCards as $card) @php $cardUtil = (float) $card->credit_limit > 0 ? min(100, ((float) $card->payable_balance / (float) $card->credit_limit) * 100) : 0; $days = $card->due_date ? now()->startOfDay()->diffInDays($card->due_date->startOfDay(), false) : null; $dueText = $card->due_date?->format('M d') ?: 'Not set'; $dueStatus = $days === null ? null : ($days < 0 ? 'Overdue '.abs((int) $days).'d' : ((int) $days).'d left'); $cardClosingStatus = ($creditClosingStatusMap ?? collect())->get((int) $card->id); @endphp {{-- Each card is a clickable link to the card's ledger. --}}
{{ \Illuminate\Support\Str::upper($card->issuer ?: ($card->short_name ?: 'CARD')) }}
Card #{{ $card->id }}{{ $card->short_name ? ' · '.$card->short_name : '' }}
Payable
{{ $metrics->money($card->payable_balance, $card->currency) }}
Limit {{ $metrics->money($card->credit_limit, $card->currency) }}
{{ $card->card_name ?: $card->holder ?: $card->issuer }} {{ number_format($cardUtil, 0) }}%
Due {{ $dueText }} @if($dueStatus) {{ $dueStatus }} @endif
{{ $card->invoices_count }} invoice{{ (int) $card->invoices_count === 1 ? '' : 's' }} →
@if($cardClosingStatus) @if($cardClosingStatus['is_closed']) {{ $defaultClosingMonthLabel ?? \Illuminate\Support\Carbon::createFromFormat('Y-m', $defaultClosingMonth ?? now()->copy()->startOfMonth()->subMonth()->format('Y-m'))->format('F') }} Closed @else
@csrf
@endif @endif
@endforeach
@else
No credit cards yet.
@endif @endif @elseif($isAccountBucket && ! $selectedBankAccount)
{{ $bucketAccent[4] }}
{{ $bucketAccent[3] }}
{{ $bankAccounts->count() }} account{{ $bankAccounts->count() === 1 ? '' : 's' }}
@if($bucket === 'pak-banks') @php $linkedAccounts = $bankAccounts->filter(fn($account) => $account->creditSources->isNotEmpty() || $account->debitDestinations->isNotEmpty()); $standaloneAccounts = $bankAccounts->diff($linkedAccounts); $accountGroups = [ ['Linked to Payment Gateways', $linkedAccounts, 'KuickPay / PayFast linked flow', 'pill-cyan'], ['Not Connected to Payment Gateways', $standaloneAccounts, 'Direct deposits only', 'pill'], ]; @endphp @foreach($accountGroups as [$groupTitle, $accounts, $groupSubtitle, $groupPill])
{{ $groupTitle }}
{{ $accounts->count() }} account{{ $accounts->count() === 1 ? '' : 's' }} {{ $groupSubtitle }}
@if($accounts->isNotEmpty())
@foreach($accounts as $account) @include('transactions._bank-card', ['account' => $account, 'bucket' => $bucket, 'bucketAccent' => $bucketAccent, 'accountInitials' => $accountInitials, 'maskedAccount' => $maskedAccount, 'metrics' => $metrics]) @endforeach
@else
No accounts in this group yet.
@endif
@endforeach @else @if($bankAccounts->isNotEmpty())
@foreach($bankAccounts as $account) @include('transactions._bank-card', ['account' => $account, 'bucket' => $bucket, 'bucketAccent' => $bucketAccent, 'accountInitials' => $accountInitials, 'maskedAccount' => $maskedAccount, 'metrics' => $metrics]) @endforeach
@else
No {{ $bucketAccent[3] }} yet. Add one from Bank Accounts.
@endif @endif @else @if($selectedBankAccount) @php $siblingBankAccounts = $bankAccounts ->reject(fn($account) => (int) $account->id === (int) $selectedBankAccount->id) ->values(); $selectedBankClosingStatus = $selectedBankClosingStatus ?? null; $defaultClosingMonth = $defaultClosingMonth ?? now()->copy()->startOfMonth()->subMonth()->format('Y-m'); $defaultClosingMonthLabel = $defaultClosingMonthLabel ?? \Illuminate\Support\Carbon::createFromFormat('Y-m', $defaultClosingMonth)->format('F'); @endphp
{{ $selectedBankAccount->bank_name }}
{{ $selectedBankAccount->name }}
{{ $maskedAccount($selectedBankAccount) }}
Current Balance
{{ $metrics->money($selectedBankAccount->current_balance, $selectedBankAccount->currency) }}
@if($selectedBankClosingStatus) @if($selectedBankClosingStatus['is_closed']) {{ $defaultClosingMonthLabel }} Closed @else
@csrf
@endif @endif
@if($selectedBankClosingStatus)
Closing month: {{ \Illuminate\Support\Carbon::createFromFormat('Y-m', $defaultClosingMonth)->format('F Y') }} · Data period: {{ $selectedBankClosingStatus['period_start']->format('d M Y') }} to {{ $selectedBankClosingStatus['period_end']->format('d M Y') }}
@endif @if($siblingBankAccounts->isNotEmpty())
Other {{ $activeTile['title'] }}
@foreach($siblingBankAccounts as $siblingAccount) {{ $siblingAccount->name }} {{ $metrics->money($siblingAccount->current_balance, $siblingAccount->currency) }} @endforeach
@endif
@endif
{{-- Bucket header --}}
{!! $activeTile['icon'] !!}
{{ $selectedBankAccount ? 'Account Transactions' : $activeTile['title'] }}
{{ $selectedBankAccount ? 'All credits and debits for this account' : $activeTile['subtitle'] }}
@foreach(request()->except(['transaction_id_search', 'page']) as $queryKey => $queryValue) @if(is_array($queryValue)) @foreach($queryValue as $item) @endforeach @else @endif @endforeach
@foreach(request()->except(['per_page', 'page']) as $queryKey => $queryValue) @if(is_array($queryValue)) @foreach($queryValue as $item) @endforeach @else @endif @endforeach
{{ $transactions->total() }} tx
{{-- View tabs: "Account" shows consolidated rows; "Bulk" expands each partial leg / split-approval child into its own single-entry row. --}} @if($selectedBankAccount)
Bulk Transactions Account Transactions
@endif {{-- KPI strip: In / Out / Net --}}
In
{{ $metrics->money($totalIn, $totalCurrency) }}
Out
{{ $metrics->money($totalOut, $totalCurrency) }}
Net
{{ $metrics->money($net, $totalCurrency) }}
@if($selectedBankAccount) @endif {{-- Transaction table --}}
@if($selectedBankAccount) @endif @forelse($transactions as $transaction) @php $txCurrency = $transactionCurrency($transaction); // Direction is driven by the authoritative `type` column so a // transfer's destination CREDIT leg renders green on the // receiving account and red on the sending account. $isIn = $transaction->type === 'income'; $txStatus = $transaction->metadata['status'] ?? 'Cleared'; $amtColor = $isIn ? '#10b981' : '#ef4444'; $invoiceNumber = $transaction->display_invoice_number ?? ($transaction->metadata['invoice_number'] ?? null); $invoiceProductName = $transaction->display_invoice_product_name ?? ($transaction->metadata['invoice_product_name'] ?? null); $transferPartialAmounts = collect($transaction->metadata['transfer_partial_amounts'] ?? []) ->filter(fn($amount) => is_numeric($amount) && (float) $amount > 0) ->values(); $transferPartialCurrency = $transaction->metadata['transfer_partial_currency'] ?? $txCurrency; $approvalOriginalAmount = is_numeric($transaction->metadata['approval_original_amount'] ?? null) ? (float) $transaction->metadata['approval_original_amount'] : null; $approvalApprovedAmount = is_numeric($transaction->metadata['approval_approved_amount'] ?? null) ? (float) $transaction->metadata['approval_approved_amount'] : null; $approvalRemainingAmount = is_numeric($transaction->metadata['approval_remaining_amount'] ?? null) ? (float) $transaction->metadata['approval_remaining_amount'] : null; $approvalExtraAmount = is_numeric($transaction->metadata['approval_extra_amount'] ?? null) ? (float) $transaction->metadata['approval_extra_amount'] : 0.0; $hasApprovalDetail = $approvalOriginalAmount !== null && $approvalApprovedAmount !== null && $approvalRemainingAmount !== null; $showApprovalRemaining = $txStatus === 'Pending' && $hasApprovalDetail; $approvedPartialAmounts = collect($transaction->metadata['approval_breakdown'] ?? []) ->filter(fn($entry) => is_array($entry) && isset($entry['amount']) && is_numeric($entry['amount']) && (float) $entry['amount'] > 0) ->map(fn($entry) => (float) $entry['amount']) ->values(); if ($approvedPartialAmounts->isEmpty() && $showApprovalRemaining && (float) ($approvalApprovedAmount ?? 0) > 0) { $approvedPartialAmounts = collect([(float) $approvalApprovedAmount]); } // Per-leg amounts of a collapsed split transfer, already in the // account currency (USD), so each received installment shows its // real dollar figure. Falls back to the approval breakdown for // partial-approval rows that aren't split transfers. $transferPartialDisplayAmounts = collect($transaction->metadata['transfer_partial_display_amounts'] ?? []) ->filter(fn($amount) => is_numeric($amount) && (float) $amount > 0) ->map(fn($amount) => (float) $amount) ->values(); $partialBreakdownAmounts = $transferPartialDisplayAmounts->isNotEmpty() ? $transferPartialDisplayAmounts : $approvedPartialAmounts; // Show what actually posted/was approved (the same figure the // IN/OUT/NET summary uses): for a partially-approved pending // transfer this is the approved-so-far amount, not the original // total. The original + per-date breakdown remain in the popup. $displayAmount = $postedDisplayAmount($transaction); $amountDetailMessage = $amountDetailJson($transaction, $txCurrency, $approvalOriginalAmount, $approvalApprovedAmount, $approvalRemainingAmount, $transferPartialAmounts, $transferPartialCurrency); $balanceTransactionId = (int) $transaction->id; $canReorder = (bool) $selectedBankAccount; $txDate = $transaction->transaction_date?->toDateString(); @endphp @if($selectedBankAccount) @endif @empty @endforelse @if($selectedBankAccount && $transactions->currentPage() === $transactions->lastPage()) @endif
OrderDate Type Transaction ID Transaction Type Description Amount Cur Balance Actions
@if($canReorder && $txDate)
@else - @endif
{{ $transaction->transaction_date?->format('d M Y') }} {{-- Credit/Debit direction, driven by the authoritative `type` column. --}} {{ $isIn ? 'Credit' : 'Debit' }} @if($txStatus === 'Pending')
Pending
@endif
{{ $transaction->transaction_id ?: '-' }} {{-- Originating reference (Manual, KuickPay, Invoice, Transfer…), with the invoice number underneath when the row is tied to one. --}} {{ $transaction->referenceLabel() }} @if($invoiceNumber)
Invoice {{ $invoiceNumber }}
@endif
{{ $transaction->description ?: '-' }} @if($invoiceProductName)
Product: {{ $invoiceProductName }}
@endif @if($transaction->counterparty) {{-- Who the money came from (credit) or went to (debit). --}}
{{ $isIn ? 'From' : 'To' }}: {{ $transaction->counterparty }}
@endif @if($txTab !== 'bulk' && $transferPartialAmounts->isNotEmpty())
Partials ({{ $transferPartialCurrency }}):
@foreach($transferPartialAmounts as $partialAmount)
{{ $metrics->smartAmount($partialAmount) }}
@endforeach
@endif @php // A multi-day KuickPay deposit is one credit that settles // several days; show each day's share inline so the single // entry carries its full breakdown. $kuickpayAllocations = collect($transaction->metadata['kuickpay_allocations'] ?? []) ->filter(fn($a) => is_array($a) && isset($a['amount']) && is_numeric($a['amount'])) ->values(); @endphp @if($txTab !== 'bulk' && $kuickpayAllocations->isNotEmpty())
Days settled (PKR):
@foreach($kuickpayAllocations as $alloc)
{{ \Illuminate\Support\Carbon::parse($alloc['collection_date'])->format('d M Y') }} · {{ $alloc['provider_label'] ?? 'KuickPay' }}: {{ $metrics->smartAmount($alloc['amount']) }}
@endforeach
Total: {{ $metrics->smartAmount($kuickpayAllocations->sum(fn($a) => (float) $a['amount'])) }}
@endif
@if($amountDetailMessage) @else {{ $isIn ? '+' : '-' }}{{ $metrics->smartAmount($displayAmount) }} @endif @if($showApprovalRemaining) @foreach($approvedPartialAmounts as $approvedPartialAmount)
{{ $metrics->smartAmount($approvedPartialAmount) }}
@endforeach
Remaining to approve: {{ $metrics->smartAmount($approvalRemainingAmount) }}
@endif @if($approvalExtraAmount > 0)
Extra: {{ $metrics->smartAmount($approvalExtraAmount) }}
@endif {{-- Per-installment breakdown of the received amount, shown in the account currency (USD). These are the real per-leg/approved figures (e.g. 5,000.00 then 28.27), NOT an even split of the PKR composition. Shown for already-posted rows; the Pending block above handles in-flight approvals. --}} @if($txTab !== 'bulk' && ! $showApprovalRemaining && $partialBreakdownAmounts->count() > 1) @foreach($partialBreakdownAmounts as $partialBreakdownAmount)
{{ $isIn ? '+' : '-' }}{{ $metrics->smartAmount($partialBreakdownAmount) }}
@endforeach @endif @php $feeMeta = $transaction->metadata ?? []; $feeEligible = $transaction->type === 'income' && (float) ($transaction->conversion_rate ?? 0) > 0 && ! empty($transaction->bank_account_id) && empty($transaction->credit_card_id) && $transaction->source !== 'credit_card_payment' && empty($transaction->kuickpay_settlement_id) && empty($feeMeta['kuickpay_settlement_ids']) && $txStatus !== 'Pending'; // cleared rows only: amount is the true received figure $feeGross = isset($feeMeta['gross_amount']) && is_numeric($feeMeta['gross_amount']) ? (float) $feeMeta['gross_amount'] : (float) $transaction->amount; $feeBaseRate = isset($feeMeta['base_conversion_rate']) && is_numeric($feeMeta['base_conversion_rate']) ? (float) $feeMeta['base_conversion_rate'] : (float) ($transaction->conversion_rate ?? 0); $feeCurrent = isset($feeMeta['fee_amount']) && is_numeric($feeMeta['fee_amount']) ? (float) $feeMeta['fee_amount'] : 0.0; @endphp @if($feeEligible)
@endif
{{ $txCurrency }} @if(isset($runningBalances[$balanceTransactionId])) {{ $metrics->money($runningBalances[$balanceTransactionId], $selectedBankAccount?->currency ?? $displayCurrency) }} @else - @endif
{{-- View detail (eye) --}} @php $txLockReason = ($periodLockReasons ?? [])[$transaction->id] ?? null; // "Close from this row": freezes the row's own account up to the // row's date. Offered only on an unlocked, non-future row that // actually belongs to an account we can close. $txCloseDate = $transaction->transaction_date ? \Illuminate\Support\Carbon::parse($transaction->transaction_date) : null; $txCloseType = $transaction->bank_account_id ? 'bank' : ($transaction->credit_card_id ? 'credit_card' : null); $txCloseId = $transaction->bank_account_id ?: $transaction->credit_card_id; $txCanClose = ($periodClosingEnabled ?? false) && ! $txLockReason && $txCloseType !== null && $txCloseDate !== null && ! $txCloseDate->isFuture(); $txCloseAccountName = $transaction->bank_account_id ? ($selectedBankAccount->name ?? 'this account') : 'this card'; @endphp {{-- Edit (pencil) — greyed out once the period is closed; the padlock that says why takes the tick's place below. --}} @if($txLockReason) @else @endif @if($txStatus === 'Pending')
@csrf @if($transaction->source === 'linked_bank_transfer' || $transaction->source === 'credit_card_payment') @endif
@endif {{-- Delete (trash) — disabled while the period is closed --}}
@csrf @method('DELETE')
{{-- Close the period at this row --}} @if($txCanClose)
@csrf {{-- The clicked row: closes the day only as far as here. --}}
@elseif($txLockReason) {{-- Same slot as the tick: once the period is closed the "close from here" action becomes the padlock that explains why the row is frozen. Clicking it asks for the closing password and unlocks this entry — plus the matching entry on the other account for a transfer. --}}
@csrf
@endif
No transactions yet.
- {{ $selectedBankAccount->created_at?->format('d M Y') }} - - Opening Balance - - {{ $selectedBankAccount->currency }} {{ $metrics->money($selectedBankAccount->opening_balance, $selectedBankAccount->currency) }} -
{{-- Pagination --}}
{{ $transactions->links() }}
@endif @endif {{-- Add Fee modal (shared; opened from any row's "+ Fee" button) --}} @endsection @if($selectedBankAccount || $selectedCreditCard || $bucket === 'credit-cards') @push('styles') @endpush @push('scripts') @endpush @endif @if($selectedBankAccount) @push('styles') @endpush @push('scripts') @endpush @endif {{-- ─── Card transaction modal CSS + JS (rendered only when a card is selected) ─── --}} @if($selectedCreditCard ?? null) @push('styles') @endpush @push('scripts') @endpush @endif @push('styles') @endpush @push('scripts') @endpush