@extends('layouts.app') @section('content') @if(session('success'))
{{ session('success') }}
@endif @if(session('error'))
{{ session('error') }}
@endif

Kasir Guidance

Mode Pelunasan

Halaman ini dipakai untuk melunasi tagihan satuan atau beberapa order sekaligus. Split payment bisa dipakai untuk invoice tunggal maupun batch.

Bayar Satuan
Klik tombol Bayar pada satu invoice untuk pelunasan single invoice.
Split Payment
Bisa gabungan tunai, transfer, dan e-wallet untuk invoice tunggal maupun batch.
Aman untuk Transaksi Lama
Flow lama tetap aman karena backend masih menerima format single payment sebagai fallback.

{{ ($currentTab ?? 'active') === 'history' ? 'Daftar Pembayaran Lunas' : 'Daftar Tagihan' }}

@forelse($orders as $order) @php $__roundOnlyAmount = function ($amount) { $amount = max((float) $amount, 0); $roundTo = 1000; if ($amount <= 0) { return 0; } return (float) (ceil($amount / $roundTo) * $roundTo); }; $__meterMinRoundAmount = function ($amount) use ($__roundOnlyAmount) { $amount = max((float) $amount, 0); $minimumOrder = 10000; if ($amount <= 0) { return 0; } if ($amount < $minimumOrder) { $amount = $minimumOrder; } return $__roundOnlyAmount($amount); }; $__normalizeUnit = function ($unit) { $unit = strtolower(trim((string) $unit)); $unit = str_replace(['²', '�'], ['2', '2'], $unit); if (in_array($unit, ['m2', 'meter2', 'meter persegi', 'per meter persegi'], true)) { return 'm2'; } if (in_array($unit, ['meter', 'm', 'per meter'], true)) { return 'meter'; } return $unit ?: 'lembar'; }; $__parseM2FromDescription = function ($description) { $description = strtolower(trim((string) $description)); if ($description === '') { return 0; } $description = str_replace(['²', '�', '*'], ['2', '2', 'x'], $description); if (preg_match('/([0-9]+(?:[\.,][0-9]+)?)\s*[x×]\s*([0-9]+(?:[\.,][0-9]+)?)/i', $description, $matches)) { $width = (float) str_replace(',', '.', $matches[1]); $height = (float) str_replace(',', '.', $matches[2]); if ($width > 0 && $height > 0) { if ($width >= 10 || $height >= 10) { $width = $width / 100; $height = $height / 100; } return round($width * $height, 4); } } return 0; }; $__itemDisplaySubtotal = function ($item) use ($__roundOnlyAmount, $__meterMinRoundAmount, $__normalizeUnit, $__parseM2FromDescription) { $unit = $__normalizeUnit($item->size_unit ?? $item->unit ?? 'lembar'); $storedSubtotal = max((float) ($item->subtotal ?? $item->total_price ?? 0), 0); if ($storedSubtotal > 0) { return in_array($unit, ['m2', 'meter'], true) ? $__meterMinRoundAmount($storedSubtotal) : $__roundOnlyAmount($storedSubtotal); } $qty = max((float) ($item->qty ?? 1), 0); $basePrice = max((float) ($item->base_price ?? $item->calculated_price ?? $item->price ?? 0), 0); $sizeValue = max((float) ($item->size_value ?? 0), 0); if ($unit === 'm2') { if ($sizeValue <= 0) { $sizeValue = $__parseM2FromDescription($item->size_description ?? $item->size ?? ''); } if ($sizeValue <= 0) { $sizeValue = 1; } return $__meterMinRoundAmount($basePrice * $sizeValue * $qty); } if ($unit === 'meter') { $sizeValue = $sizeValue > 0 ? $sizeValue : 1; return $__meterMinRoundAmount($basePrice * $sizeValue * $qty); } return $__roundOnlyAmount($basePrice * $qty); }; $__orderDisplaySubtotalBeforePpn = function ($order) use ($__itemDisplaySubtotal, $__roundOnlyAmount) { $items = method_exists($order, 'items') ? ($order->relationLoaded('items') ? $order->items : $order->items()->get()) : collect(); $activeItems = $items->filter(function ($item) { if (method_exists($item, 'isCancelled')) { return !$item->isCancelled(); } return !(bool) ($item->is_cancelled ?? false); }); if ($activeItems->isNotEmpty()) { return (float) $activeItems->sum(fn ($item) => $__itemDisplaySubtotal($item)); } $subtotalBeforePpn = (float) ($order->subtotal_before_ppn ?? 0); if ($subtotalBeforePpn > 0) { return $__roundOnlyAmount($subtotalBeforePpn); } $total = (float) ($order->total_price ?? 0); $ppn = (float) ($order->ppn_amount ?? 0); if ((bool) ($order->use_ppn ?? false) && $ppn > 0 && $total > $ppn) { return $__roundOnlyAmount($total - $ppn); } return $__roundOnlyAmount($total); }; $__orderDisplayTotal = function ($order) use ($__orderDisplaySubtotalBeforePpn) { $subtotal = $__orderDisplaySubtotalBeforePpn($order); $ppn = ((bool) ($order->use_ppn ?? false) || (float) ($order->ppn_amount ?? 0) > 0) ? (float) ($order->ppn_amount ?? 0) : 0; return (float) ($subtotal + $ppn); }; $__orderDisplayRemaining = function ($order) use ($__orderDisplayTotal) { $paid = method_exists($order, 'getTotalPaid') ? (float) $order->getTotalPaid() : (float) ($order->paid_amount ?? 0); return max((float) $__orderDisplayTotal($order) - $paid, 0); }; $age = (int) floor($order->created_at->diffInDays(now())); $ageClass = $age > 30 ? 'text-error-600 bg-error-50' : ($age > 7 ? 'text-warning-600 bg-warning-50' : 'text-gray-600 bg-gray-100'); $totalPaid = $order->getTotalPaid(); $displayTotal = $__orderDisplayTotal($order); $remaining = $__orderDisplayRemaining($order); @endphp @if($order->payments->count() > 0) @php $paymentData = $order->payments->map(fn($p) => [ 'id' => $p->id, 'date' => $p->created_at->format('d/m/Y H:i'), 'amount' => $p->amount, 'method' => $p->payment_method, 'proof' => $p->proof_path ? asset('storage/' . $p->proof_path) : null, 'notes' => $p->notes, 'cashier' => $p->cashier->name ?? '-' ])->values()->toJson(); @endphp @else @endif @empty @endforelse @if($orders->count() > 0) @endif
Tanggal Invoice Pelanggan Total Dibayar Sisa Umur Aksi
{{ $order->created_at->format('d/m/Y') }} {{ $order->invoice_number }} @if($order->is_credit) Piutang @endif @if($order->isPartiallyPaid()) Cicilan @endif Rp {{ number_format($displayTotal, 0, ',', '.') }} - Rp {{ number_format($remaining, 0, ',', '.') }} {{ $age }} hari
@if(($currentTab ?? 'active') === 'active') @else @endif

Tidak ada tagihan

Total Piutang Rp {{ number_format($orders->sum(fn ($o) => $__orderDisplayRemaining($o)), 0, ',', '.') }}
@if($orders->hasPages())
{{ $orders->links() }}
@endif
{{-- Payment Dialog --}} {{-- Payment History Dialog --}} {{-- Batch Payment Dialog --}} @endsection @push('scripts') @endpush