@php $statusBadge = [ 'DRAFT' => 'slate', 'SENT' => 'sky', 'PARTIAL' => 'amber', 'PAID' => 'emerald', 'OVERDUE' => 'rose', 'VOID' => 'slate', ]; $kpiAccent = [ 'indigo' => '#6366f1', 'amber' => '#f59e0b', 'emerald' => '#10b981', 'sky' => '#0ea5e9', 'rose' => '#f43f5e', ]; $avatarColors = [ 'bg-indigo-100 text-indigo-700', 'bg-emerald-100 text-emerald-700', 'bg-amber-100 text-amber-700', 'bg-sky-100 text-sky-700', 'bg-rose-100 text-rose-700', 'bg-violet-100 text-violet-700', ]; $initials = function (?string $name): string { $parts = collect(preg_split('/\s+/', trim((string) $name)))->filter(); if ($parts->isEmpty()) { return '—'; } return $parts->take(2)->map(fn ($w) => mb_strtoupper(mb_substr($w, 0, 1)))->implode(''); }; $avatarColor = fn (?string $name): string => $avatarColors[abs(crc32((string) $name)) % count($avatarColors)]; // Public links the customer gets: the portal page is the payment page (Pay // button where Paynow is live, banking details otherwise), and WhatsApp // shares that same link with the balance owed pre-filled. Both come off the // model so the wording can't drift from what the emails send. $paymentUrl = fn ($invoice) => $invoice->portalUrl(); $whatsappUrl = fn ($invoice) => $invoice->whatsAppShareUrl(); @endphp
@if ($tab === 'recurring') @if ($canManageRecurring) {{ __('New Recurring') }} @endif @elseif ($canManage) {{ __('New Invoice') }} @endif {{-- max-w-full + scroll: three tabs are wider than a 320px screen, and a tab strip must never be what widens the page. --}}
{{-- Void requests waiting on an approver. Shown to everyone: the raiser needs to see their ask is still in flight, the approver needs to act. --}} @if ($pendingVoidCount > 0 && ! $pendingOnly)
{{ trans_choice('{1}:count invoice is waiting to be voided.|[2,*]:count invoices are waiting to be voided.', $pendingVoidCount, ['count' => $pendingVoidCount]) }} @if ($canApproveVoid) {{ __('Nothing has changed on them until you decide.') }} @else {{ __('An administrator has to approve them.') }} @endif
@elseif ($pendingOnly)
{{ __('Showing only invoices with a void request awaiting approval.') }}
@endif
@foreach ($kpis as $kpi) @php $accent = $kpiAccent[$kpi['color']] ?? $kpiAccent['indigo']; @endphp

{{ $kpi['label'] }}

{{ $kpi['value'] }}

{{ $kpi['sub'] }}
@endforeach
@if ($tab === 'invoices')
–
@if ($showFilters)
@endif @if ($invoices->isEmpty())
@if ($canManage && ! $this->hasActiveFilters) {{ __('New Invoice') }} @endif
@else @php // Row actions live in the floating selection bar, so every // row carries a tick box instead of a strip of icons. The // header box selects / clears this page. $pageIds = $invoices->getCollection()->modelKeys(); $pageAllTicked = $pageIds !== [] && array_diff($pageIds, $selected) === []; @endphp
@foreach ($invoices as $invoice) @php $due = $invoice->due_date; $daysLeft = $due ? (int) round(\Illuminate\Support\Carbon::today()->diffInDays($due, false)) : null; $showDue = $due && ! in_array($invoice->status, ['PAID', 'VOID'], true) && (float) $invoice->balance_due > 0; $isPartial = (float) $invoice->amount_paid > 0 && (float) $invoice->balance_due > 0; @endphp @php $ticked = in_array($invoice->id, $selected, true); @endphp
{{ $initials($invoice->payerName()) }}

{{ $invoice->payerName() ?: '—' }}

@if ($invoice->isIncome()) {{ __('Rental / Income') }} @endif @if ($invoice->recurring_invoice_id) {{ __('Recurring') }} @endif @if ($invoice->hasPendingVoid()) {{ __('Void requested') }} @elseif ($invoice->voidWasDeclined()) {{ __('Void declined') }} @endif

{{ $invoice->currency_code }} {{ number_format((float) $invoice->total, 2) }}

@if ($isPartial)

{{ __('Balance') }}: {{ number_format((float) $invoice->balance_due, 2) }}

@endif @if ((float) $invoice->refunded_total > 0)

{{ __('Refunded') }}: {{ number_format((float) $invoice->refunded_total, 2) }}

@endif @if ((float) $invoice->credited_total > 0)

{{ __('Credited') }}: {{ number_format((float) $invoice->credited_total, 2) }}

@endif

{{ $invoice->created_at?->format('Y-m-d') }} @if ($showDue) @if ($daysLeft < 0) · {{ __('Overdue') }} @elseif ($daysLeft === 0) · {{ __('Due today') }} @else · {{ trans_choice('{1} :count day to due|[2,*] :count days to due', $daysLeft, ['count' => $daysLeft]) }} @endif @endif

@endforeach
{{ __('Rows per page') }}
{{ $invoices->onEachSide(1)->links() }}
@endif
{{-- Floating action bar. Tick one row and it offers everything that used to sit in the row's icon strip, now with labels; tick several and it narrows to the actions that are safe to run across a batch. --}} @if ($selectedInvoice) @php $inv = $selectedInvoice; // One highlighted "next step". Money owed outranks sending, // and whichever loses falls through to the More menu so no // action can go missing on any status. $canSend = $canManage && $inv->status !== 'VOID'; $payIsPrimary = $canRecordPayment && $inv->status !== 'VOID' && (float) $inv->balance_due > 0; $sendIsPrimary = $canSend && ! $payIsPrimary; @endphp {{ __('View') }} @if ($payIsPrimary) {{ __('Record payment') }} @elseif ($sendIsPrimary) {{ __('Send') }} @endif {{ __('PDF') }} @if ($canManage && $inv->status !== 'VOID' && ($inv->status !== 'PAID' || $canEditPaid)) {{ $inv->requiresEditReason() ? __('Edit (reason)') : __('Edit') }} @endif {{-- The rest, behind one more click so the bar stays readable. It opens upward (`bottom-full`) because the bar is pinned to the bottom of the viewport. --}} @if ($canSend && ! $sendIsPrimary) @endif @if ($inv->status !== 'VOID') {{ __('Share on WhatsApp') }} @endif @if ($canRecordPayment && $inv->status !== 'VOID' && (float) $inv->amount_paid > 0) @endif {{-- Void: the only way a settled invoice leaves circulation. An approver gets Approve/Decline once a request is pending. --}} @if ($inv->status !== 'VOID') @if ($inv->hasPendingVoid() && $canApproveVoid)
@elseif (! $inv->hasPendingVoid() && ($canApproveVoid || $canRequestVoid))
@endif @endif @if ($canDelete && ! $inv->isDeletionLocked())
@endif
@else @if ($canManage) {{ __('Email to customers') }} @endif @if ($canDelete) {{ __('Delete') }} @endif @endif
@elseif ($tab === 'recurring') @php $recurringBadge = ['active' => 'emerald', 'paused' => 'amber']; @endphp
@forelse ($profiles as $profile) @php $sub = collect($profile->line_items ?? [])->reduce(fn ($c, $l) => $c + ((float) ($l['qty'] ?? 0) * (float) ($l['unit_price'] ?? 0) - (float) ($l['discount'] ?? 0)), 0.0); $taxable = $sub - (float) $profile->discount; $amount = round($taxable + ($taxable * (float) $profile->vat_rate / 100), 2); @endphp

{{ $profile->customer?->name ?: '—' }}

{{ __('Every') }} {{ $profile->interval > 1 ? $profile->interval . ' ' : '' }}{{ ucfirst($profile->frequency) }}

{{ ucfirst($profile->status) }}

{{ $profile->currency_code }} {{ number_format($amount, 2) }}

{{ __('Next') }}: {{ $profile->next_run_date?->format('Y-m-d') ?: '—' }}

@if ($canManageRecurring)
@endif
@empty
@if ($canManageRecurring) {{ __('New Recurring') }} @endif
@endforelse
@if ($profiles->hasPages())
{{ $profiles->links() }}
@endif
@else {{-- Credit notes: money we are holding for a customer that is no longer backed by a sale. Raised by a void (or an invoice edited down). --}} @php $creditBadge = [ 'open' => 'amber', 'partly_applied' => 'sky', 'applied' => 'emerald', 'refunded' => 'slate', 'cancelled' => 'rose', ]; $creditLabel = [ 'open' => __('Open'), 'partly_applied' => __('Part applied'), 'applied' => __('Applied'), 'refunded' => __('Paid out'), 'cancelled' => __('Written off'), ]; @endphp

{{ __('A credit note is cash still in the bank that has stopped being revenue. It comes back as revenue when the customer spends it on another invoice.') }}

@forelse ($credits as $note)

{{ $note->credit_number ?: '—' }}

{{ $note->customer?->name ?: '—' }}

{{ $creditLabel[$note->state] ?? $note->state }}

{{ $note->currency_code }} {{ number_format((float) $note->amount, 2) }}

{{ __('Left') }}: {{ number_format($note->remaining, 2) }}

@if ($note->isOpen())
@if ($canRecordPayment) @endif @if ($canApproveVoid) @endif
@endif
@empty
@endforelse
@if ($credits->hasPages())
{{ $credits->links() }}
@endif
@endif
@if ($formMode === 'recurring') {{ $editingRecurringId ? __('Edit Recurring Schedule') : __('New Recurring Schedule') }} @else {{ $editingId ? __('Edit Invoice') : __('New Invoice') }} @endif
@foreach ($lines as $i => $line) @php $lineMode = $this->linePricingMode($line); $lineDimensional = $lineMode !== 'fixed'; @endphp
{{ __('Product') }}
{{ __('Name') }}
{{ __('Qty') }}
{{ $lineDimensional ? ($lineMode === 'per_sqm' ? __('Price / m²') : __('Price / m')) : __('Unit Price') }}
{{ __('Discount') }}
{{-- Dimensional inputs: only for products priced per m² / linear m. --}} @if ($lineDimensional) @php $factor = $this->lineAreaFactor($line); $bothDims = (float) ($line['width_m'] ?? 0) > 0 && ($lineMode === 'per_linear_m' || (float) ($line['height_m'] ?? 0) > 0); @endphp
{{ __('Width (m)') }}
{{ __('Height (m)') }}

@if ($lineMode === 'per_sqm') {{ $bothDims ? __(':factor m² × qty × price/m²', ['factor' => rtrim(rtrim(number_format($factor, 4), '0'), '.')]) : __('Enter width × height to price by area.') }} @else {{ $bothDims ? __(':factor m × qty × price/m', ['factor' => rtrim(rtrim(number_format($factor, 4), '0'), '.')]) : __('Enter the length to price per linear metre.') }} @endif

@endif {{-- Garment details: colour + size breakdown flow through to the job and delivery note. --}}
{{ __('Colour') }}
{{ __('Size breakdown') }}
{{ number_format($this->lineSubtotal($line), 2) }}
@endforeach

{{ __('Methods carrying a charge add it to the total.') }} {{ __('Set rates') }}

{{-- Says out loud what the choice does to the printed document: the account number rides on the bank method alone, because that is the only total that has the transfer charge in it. --}} @php $bankingMethod = $paymentMethods->firstWhere('id', (int) $payment_method_id); @endphp

{{ $bankingMethod?->isBankTransfer() ? __('Your bank details will print on this invoice.') : __('No bank details will print on this invoice.') }}

{{ __('Subtotal') }} {{ number_format($totals['subtotal'], 2) }}
{{ __('Discount') }} - {{ number_format($totals['discount'], 2) }}
{{ __('VAT') }} {{ number_format($totals['vat'], 2) }}
@if ($totals['surcharge_label'])
{{ $totals['surcharge_label'] }} + {{ number_format($totals['surcharge'], 2) }}
@endif
{{ __('Total') }} {{ $currency_code }} {{ number_format($totals['total'], 2) }}
@if ($formMode !== 'recurring') @endif @if ($is_recurring || $formMode === 'recurring')
{{ __('Recurring schedule') }}
@if ($formMode === 'recurring')
@endif
@endif @if ($formMode !== 'recurring')
{{-- VOID is not offered here: it is reached through the void workflow, which settles the cash behind the sale. --}}
{{-- Editing a settled invoice: the reason is mandatory and is written to the invoice, its notes and the audit trail. --}} @if ($this->editingRequiresReason()) @php $edited = $this->editingInvoice(); @endphp

{{ __('This invoice has already been paid against') }}

{{ __(':paid of :total has been collected. If you drop the total below that, the difference becomes a credit note the customer can spend or take back. If you raise it, the invoice goes back to part-paid with a balance due.', [ 'paid' => $edited?->currency_code . ' ' . number_format((float) ($edited?->amount_paid ?? 0), 2), 'total' => number_format((float) ($edited?->total ?? 0), 2), ]) }}

@endif @endif
{{ __('Cancel') }} {{ __('Save') }}
{{ __('Record Payment') }} @if ($payInvoice) — {{ $payInvoice->invoice_number }} @endif @if ($payInvoice)

{{ __('Total') }}

{{ $payInvoice->currency_code }} {{ number_format((float) $payInvoice->total, 2) }}

{{ __('Paid') }}

{{ number_format((float) $payInvoice->amount_paid, 2) }}

{{ __('Balance Due') }}

{{ number_format((float) $payInvoice->balance_due, 2) }}

@endif

{{ __('Deposits and balances are reported as separate income lines. Suggested from this invoice’s history — change it if the customer called it something else.') }}

@if ($paySurcharge > 0)
{{ __('Money-transfer surcharge (:rate%)', ['rate' => rtrim(rtrim(number_format(\App\Models\Payment::SURCHARGE_RATE * 100, 2), '0'), '.')]) }} {{ $payInvoice?->currency_code }} {{ number_format($paySurcharge, 2) }}
{{ __('Total to collect') }} {{ $payInvoice?->currency_code }} {{ number_format((float) ($pay_amount ?: 0) + $paySurcharge, 2) }}

{{ __('The surcharge is added to what the customer pays and lands in treasury; it does not reduce the invoice balance.') }}

@endif
@if ($payInvoice && $payAccounts->isEmpty())

{{ __('No active :currency account exists. Create one under Treasury first.', ['currency' => $payInvoice->currency_code]) }}

@else

{{ __('Which account this money lands in.') }}

@endif

{{ __('Bank/EcoCash fee this account skims off the deposit. Leave blank if none.') }}

@if ($paySuggestedCharge !== '' && $pay_charge === $paySuggestedCharge && $payInvoice?->paymentMethod)

{{ __('Suggested from :method (:rate). Change it if the statement says otherwise.', [ 'method' => $payInvoice->paymentMethod->name, 'rate' => $payInvoice->paymentMethod->costLabel(), ]) }}

@endif @php $payChargeVal = round((float) ($pay_charge ?: 0), 2); @endphp @if ($payChargeVal > 0)
{{ __('Net landing in the account') }} {{ $payInvoice?->currency_code }} {{ number_format((float) ($pay_amount ?: 0) - $payChargeVal, 2) }}

{{ __('The invoice is still settled in full; the fee is booked as a bank charge in your P&L.') }}

@endif
@if ($payInvoice && ($payInvoice->payments->isNotEmpty() || $payInvoice->refunds->isNotEmpty())) @php // Fold payments (money in) and refunds (money out) into a single // list ordered newest-first, tagging each with its direction. $timeline = $payInvoice->payments->map(fn ($p) => [ 'type' => 'payment', 'at' => $p->paid_at, 'model' => $p, ])->concat($payInvoice->refunds->map(fn ($r) => [ 'type' => 'refund', 'at' => $r->refunded_at, 'model' => $r, ]))->sortByDesc('at')->values(); @endphp
@foreach ($timeline as $entry) @php $item = $entry['model']; @endphp @if ($entry['type'] === 'payment')

{{ $payInvoice->currency_code }} {{ number_format((float) $item->amount, 2) }} · {{ $item->channel }}

{{ optional($item->paid_at)->format('Y-m-d H:i') }} @if ((float) $item->surcharge > 0) · {{ __('surcharge') }} {{ number_format((float) $item->surcharge, 2) }} @endif @if ($item->reference) · {{ $item->reference }} @endif

{{ $item->recorder?->name }}
@else

- {{ $payInvoice->currency_code }} {{ number_format((float) $item->amount, 2) }} · {{ __('Refund') }} · {{ $item->reason_label }}

{{ optional($item->refunded_at)->format('Y-m-d H:i') }} @if ($item->reference) · {{ $item->reference }} @endif

{{ $item->recorder?->name }}
@endif @endforeach
@endif
{{ __('Cancel') }} {{ __('Save Payment') }}
{{ __('Refund Customer') }} @if ($refundInvoice) — {{ $refundInvoice->invoice_number }} @endif @if ($refundInvoice) @php $refundedTotal = $refundInvoice->refunded_total; $netCollected = $refundInvoice->net_collected; @endphp

{{ __('Paid') }}

{{ $refundInvoice->currency_code }} {{ number_format((float) $refundInvoice->amount_paid, 2) }}

{{ __('Already refunded') }}

{{ number_format($refundedTotal, 2) }}

{{ __('Net collected') }}

{{ number_format($netCollected, 2) }}

{{ __('A refund returns cash to the customer and reduces recognised revenue by the same amount. The invoice stays settled, its total and balance do not change.') }}

@endif
@if ($refundInvoice && $refundAccounts->isEmpty())

{{ __('No active :currency account exists. Create one under Treasury first.', ['currency' => $refundInvoice->currency_code]) }}

@else

{{ __('Which account the money leaves from.') }}

@endif

{{ __('Bank/EcoCash fee to send the money out. Booked as a bank charge. Leave blank if none.') }}

@if ($refundInvoice && $refundInvoice->refunds->isNotEmpty())
@foreach ($refundInvoice->refunds->sortByDesc('refunded_at') as $refund)

{{ $refundInvoice->currency_code }} {{ number_format((float) $refund->amount, 2) }} · {{ $refund->reason_label }}

{{ optional($refund->refunded_at)->format('Y-m-d H:i') }} @if ($refund->reference) · {{ $refund->reference }} @endif

{{ $refund->recorder?->name }}
@endforeach
@endif
{{ __('Cancel') }} {{ __('Save Refund') }}
@if ($voidInvoice?->hasPendingVoid() && $canApproveVoid) {{ __('Approve void request') }} @elseif ($canApproveVoid) {{ __('Void invoice') }} @else {{ __('Request a void') }} @endif @if ($voidInvoice) @php $cur = $voidInvoice->currency_code; $unsettled = $voidInvoice->unsettledCash(); $keep = min($this->retainedAmount($unsettled), $unsettled); $giveBack = round($unsettled - $keep, 2); $shortfall = round($voidCostsIncurred - $keep, 2); // One shared formula for every segmented control in this modal. $seg = fn (bool $on): string => $on ? 'bg-white text-indigo-600 shadow-sm ring-1 ring-slate-200' : 'text-slate-500 hover:text-slate-700'; @endphp {{-- Summary strip: who, how much, and what is at stake. --}}

{{ $voidInvoice->invoice_number }}

{{ $voidInvoice->payerName() ?: '—' }}

{{ __('Collected') }}

{{ $cur }} {{ number_format($unsettled, 2) }}

{{ __('Spent on job') }}

{{ $cur }} {{ number_format($voidCostsIncurred, 2) }}

@if ($voidInvoice->hasPendingVoid())

{{ __('Requested by :who, :when', [ 'who' => $voidInvoice->voidRequester?->name ?? __('a colleague'), 'when' => $voidInvoice->void_requested_at?->diffForHumans(), ]) }}

{{ $voidInvoice->void_reason }}

@endif
{{-- Reason --}}
@if ($unsettled > 0) {{-- Cancellation charge: % or flat amount, one live summary. --}}
@if ($void_retain_mode === 'percent') % @else {{ $cur }} @endif
{{ __('You keep') }} {{ $cur }} {{ number_format($keep, 2) }} {{ __('Customer gets back') }} {{ $cur }} {{ number_format($giveBack, 2) }}
@if ($shortfall > 0.01)

{{ __('Writes off :loss of work you have already paid for.', ['loss' => $cur . ' ' . number_format($shortfall, 2)]) }}

@endif
{{-- Where the returned money goes --}}
$giveBack <= 0])>
@foreach ($settlements as $key => $label) @endforeach

{{ $void_settlement === 'refund' ? __('Leaves the account today and shows in Treasury as a refund.') : __('No money moves. They can spend it on a future invoice, or take it in cash later.') }}

@if ($void_settlement === 'refund' && $giveBack > 0)
@endif @else

{{ __('Nothing was collected on this invoice, so there is nothing to settle.') }}

@endif {{-- What happens to the production job(s) --}} @if ($voidInvoice->jobs->isNotEmpty())
@foreach ($voidJobActions as $key => $label) @endforeach

{{ $void_job_action === 'cancel' ? __('Confirmed productions are reversed and the stock goes back. What you cannot recover stays costed.') : __('Nothing is unwound. The job keeps its costs and earns only the charge above.') }} · {{ $voidInvoice->jobs->pluck('job_number')->join(', ') }}

@endif

{{ __('The invoice, its payments and its line items are all kept. The reversal is dated today, so the month the money came in is never rewritten, and costs on the cancelled work move out of cost of sales.') }}

@endif
{{ __('Cancel') }} @if ($canApproveVoid) {{ $voidInvoice?->hasPendingVoid() ? __('Approve & Void') : __('Void Invoice') }} @else {{ __('Send Request') }} @endif
{{ __('Decline void request') }}

{{ __('The invoice stands. The request and your refusal both stay on record, so tell them why.') }}

{{ __('Cancel') }} {{ __('Decline Request') }}
{{ __('Apply credit to an invoice') }} @if ($credit)

{{ $credit->credit_number }}

{{ $credit->customer?->name }} · {{ __('remaining') }} {{ $credit->currency_code }} {{ number_format($credit->remaining, 2) }}

@if ($creditInvoices->isEmpty())

{{ __('This customer has no unpaid invoices in :currency.', ['currency' => $credit->currency_code]) }}

@endif

{{ __('No cash moves, the money is already in the bank. The invoice is settled and the revenue is recognised now, in this month.') }}

@endif
{{ __('Cancel') }} {{ __('Apply Credit') }}
{{ __('Pay the credit out') }} @if ($credit)

{{ __('Hand :amount back to :customer. The money leaves the account today; profit does not change, because this credit stopped counting as revenue when it was raised.', [ 'amount' => $credit->currency_code . ' ' . number_format($credit->remaining, 2), 'customer' => $credit->customer?->name ?: __('the customer'), ]) }}

@endif
{{ __('Cancel') }} {{ __('Pay Out') }}
{{ __('Write this credit off') }} @if ($credit)

{{ __('The customer will never spend the remaining :amount and it is not going back to them. Writing it off returns that money to revenue in this month.', [ 'amount' => $credit->currency_code . ' ' . number_format($credit->remaining, 2), ]) }}

@endif
{{ __('Cancel') }} {{ __('Write Off') }}