@extends('layouts.app') @section('content') @php $selectedCustomer = request('customer'); $hasCustomerSelected = filled($selectedCustomer); /* |-------------------------------------------------------------------------- | Helper sinkron total piutang |-------------------------------------------------------------------------- | Disamakan dengan aturan order: | - Minimal per item Rp 10.000 | - Pembulatan ke atas Rp 1.000 | - PPN tetap dipisah dari subtotal dasar */ $__piutangRoundOnlyAmount = function ($amount) { $amount = max((float) $amount, 0); $roundTo = 1000; if ($amount <= 0) { return 0; } return (float) (ceil($amount / $roundTo) * $roundTo); }; $__piutangMeterMinRoundAmount = function ($amount) use ($__piutangRoundOnlyAmount) { $amount = max((float) $amount, 0); $minimumOrder = 10000; if ($amount <= 0) { return 0; } if ($amount < $minimumOrder) { $amount = $minimumOrder; } return $__piutangRoundOnlyAmount($amount); }; $__piutangNormalizeUnit = 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'; }; $__piutangParseM2FromDescription = 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; }; $__piutangItemDisplaySubtotal = function ($item) use ($__piutangRoundOnlyAmount, $__piutangMeterMinRoundAmount, $__piutangNormalizeUnit, $__piutangParseM2FromDescription) { $unit = $__piutangNormalizeUnit($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) ? $__piutangMeterMinRoundAmount($storedSubtotal) : $__piutangRoundOnlyAmount($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 = $__piutangParseM2FromDescription($item->size_description ?? $item->size ?? ''); } if ($sizeValue <= 0) { $sizeValue = 1; } return $__piutangMeterMinRoundAmount($basePrice * $sizeValue * $qty); } if ($unit === 'meter') { $sizeValue = $sizeValue > 0 ? $sizeValue : 1; return $__piutangMeterMinRoundAmount($basePrice * $sizeValue * $qty); } return $__piutangRoundOnlyAmount($basePrice * $qty); }; $__piutangOrderSubtotalBeforePpn = function ($order) use ($__piutangItemDisplaySubtotal, $__piutangRoundOnlyAmount) { $items = $order->relationLoaded('items') ? $order->items : ($order->items ?? collect()); if ($items && $items->count() > 0) { $activeItems = $items->filter(function ($item) { if (method_exists($item, 'isCancelled')) { return !$item->isCancelled(); } return !(bool) ($item->is_cancelled ?? false); }); return (float) $activeItems->sum(fn ($item) => $__piutangItemDisplaySubtotal($item)); } $subtotalBeforePpn = (float) ($order->subtotal_before_ppn ?? 0); if ($subtotalBeforePpn > 0) { return $__piutangRoundOnlyAmount($subtotalBeforePpn); } $total = (float) ($order->total_price ?? 0); $ppn = (float) ($order->ppn_amount ?? 0); if ((bool) ($order->use_ppn ?? false) && $ppn > 0 && $total > $ppn) { return $__piutangRoundOnlyAmount($total - $ppn); } return $__piutangRoundOnlyAmount($total); }; $__piutangOrderDisplayTotal = function ($order) use ($__piutangOrderSubtotalBeforePpn) { $subtotalBeforePpn = (float) $__piutangOrderSubtotalBeforePpn($order); $ppn = (bool) ($order->use_ppn ?? false) ? (float) ($order->ppn_amount ?? 0) : 0; return $subtotalBeforePpn + $ppn; }; $__piutangOrderDisplayRemaining = function ($order) use ($__piutangOrderDisplayTotal) { $paid = method_exists($order, 'getTotalPaid') ? (float) $order->getTotalPaid() : (float) ($order->paid_amount ?? 0); return max((float) $__piutangOrderDisplayTotal($order) - $paid, 0); }; $displayTotalReceivables = collect($orders->items() ?? $orders) ->sum(fn ($order) => $__piutangOrderDisplayRemaining($order)); @endphp {{-- Stat Cards --}}

Laporan Piutang

{{-- Customer Filter: native select biar pasti jalan --}}
@if($hasCustomerSelected) Customer aktif: {{ $selectedCustomer }} 0 transaksi dipilih @endif
@if($hasCustomerSelected) Print Semua Customer @else Pilih Customer Dulu @endif Print Semua
@if($hasCustomerSelected)
Pilih transaksi yang ingin dicetak. Bisa satu transaksi saja, beberapa transaksi, atau semua transaksi customer ini.
@endif
@if($hasCustomerSelected) @endif @forelse($orders as $order) @php $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'); $displayTotal = $__piutangOrderDisplayTotal($order); $totalPaid = $order->getTotalPaid(); $remaining = $__piutangOrderDisplayRemaining($order); $dueDate = $order->due_date ? \Carbon\Carbon::parse($order->due_date) : null; $isOverdue = $dueDate && $remaining > 0 && $dueDate->isPast(); $dueDateClass = $isOverdue ? 'text-error-600 bg-error-50 dark:bg-error-500/10 dark:text-error-400' : 'text-gray-600 bg-gray-100 dark:bg-gray-800 dark:text-gray-300'; @endphp @if($hasCustomerSelected) @endif @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 Jatuh Tempo Detail
{{ $order->created_at->format('d/m/Y') }} {{ $order->invoice_number }} @if($order->is_credit) Piutang @endif @if($order->isPartiallyPaid()) Cicilan @endif {{ $order->customer_name }} Rp {{ number_format($displayTotal, 0, ',', '.') }} - Rp {{ number_format($remaining, 0, ',', '.') }} {{ $age }} hari @if($dueDate)
{{ $dueDate->format('d/m/Y') }} @if($isOverdue) Lewat tempo @endif
@else - @endif

Tidak ada piutang.

Total Piutang Rp {{ number_format($displayTotalReceivables ?? ($summary['total_receivables'] ?? 0), 0, ',', '.') }}
@if($orders->hasPages())
{{ $orders->links() }}
@endif
{{-- Month Selection Dialog --}} {{-- Payment History Dialog --}} @endsection @push('scripts') @endpush