find($refundId); if (!$refund) { return ['ok' => false, 'message' => 'Refund not found.']; } if (!in_array($refund->status, [Refund::STATUS_APPROVED, Refund::STATUS_PARTIAL], true)) { return ['ok' => false, 'message' => 'Refund must be Approved or Partial to pay.']; } $newPaidAmount = (float) ($payload['paid_amount'] ?? 0); if ($newPaidAmount <= 0) { return ['ok' => false, 'message' => 'Valid paid amount is required.']; } $oldPaid = (float) ($refund->refund_paid_amount ?? 0); $target = (float) ($refund->refund_amount ?? 0); $total = $oldPaid + $newPaidAmount; if ($total > $target + 0.0001) { return ['ok' => false, 'message' => 'Total paid cannot exceed refund amount.']; } $refundMethod = (string) ($payload['payment_method'] ?? ''); $checkNumber = $payload['check_number'] ?? null; $checkFileName = $refund->check_file; if ($refundMethod === 'Check' && ($payload['check_file'] ?? null) instanceof UploadedFile) { $file = $payload['check_file']; if ($file->isValid()) { $checkFileName = $file->hashName(); $file->storeAs('checks', $checkFileName); } } $newStatus = ($total < $target) ? Refund::STATUS_PARTIAL : Refund::STATUS_PAID; return DB::transaction(function () use ( $refund, $total, $newStatus, $refundMethod, $checkNumber, $checkFileName, $actorId ) { $assignInvoiceId = $refund->invoice_id ?: $this->assignInvoiceForRefund($refund); $refund->update([ 'refund_paid_amount' => $total, 'status' => $newStatus, 'refunded_at' => utc_now(), 'updated_at' => utc_now(), 'updated_by' => $actorId, 'refund_method' => $refundMethod, 'check_nbr' => $checkNumber, 'check_file' => $checkFileName, 'invoice_id' => $assignInvoiceId, ]); return ['ok' => true]; }); } private function assignInvoiceForRefund(Refund $refund): ?int { $parentId = (int) ($refund->parent_id ?? 0); $schoolYear = (string) ($refund->school_year ?? ''); if ($parentId <= 0 || $schoolYear === '') { return null; } try { $invRows = Invoice::query() ->select('id', 'total_amount') ->where('parent_id', $parentId) ->where('school_year', $schoolYear) ->get() ->map(fn ($row) => (array) $row) ->all(); if (empty($invRows)) { return null; } $invoiceIds = array_map(static fn ($r) => (int) $r['id'], $invRows); $paidBy = $this->sumPaymentsByInvoice($invoiceIds); $discBy = $this->sumDiscountsByInvoice($invoiceIds); $minRaw = 0.0; $minId = null; foreach ($invRows as $row) { $iid = (int) $row['id']; $raw = (float) ($row['total_amount'] ?? 0) - (float) ($discBy[$iid] ?? 0) - (float) ($paidBy[$iid] ?? 0); if ($raw < $minRaw) { $minRaw = $raw; $minId = $iid; } } if ($minId !== null) { return $minId; } $latest = Invoice::query() ->where('parent_id', $parentId) ->where('school_year', $schoolYear) ->orderByDesc('created_at') ->first(); return $latest ? (int) $latest->id : null; } catch (\Throwable $e) { Log::warning('Refund invoice assignment failed.', ['error' => $e->getMessage()]); return null; } } private function sumPaymentsByInvoice(array $invoiceIds): array { $query = DB::table('payments') ->select('invoice_id', DB::raw('COALESCE(SUM(paid_amount),0) AS total_paid')) ->whereIn('invoice_id', $invoiceIds) ->groupBy('invoice_id'); if (Schema::hasColumn('payments', 'status')) { $query->where(function ($q) { $q->whereNotIn('status', [ 'void', 'voided', 'refunded', 'failed', 'chargeback', 'declined', 'reversed', 'canceled', 'cancelled', ])->orWhereNull('status'); }); } if (Schema::hasColumn('payments', 'is_void')) { $query->where(function ($q) { $q->where('is_void', 0)->orWhereNull('is_void'); }); } $rows = $query->get(); $paidBy = []; foreach ($rows as $row) { $paidBy[(int) $row->invoice_id] = (float) ($row->total_paid ?? 0); } return $paidBy; } private function sumDiscountsByInvoice(array $invoiceIds): array { $rows = DB::table('discount_usages') ->select('invoice_id', DB::raw('COALESCE(SUM(discount_amount),0) AS total_disc')) ->whereIn('invoice_id', $invoiceIds) ->groupBy('invoice_id') ->get(); $discBy = []; foreach ($rows as $row) { $discBy[(int) $row->invoice_id] = (float) ($row->total_disc ?? 0); } return $discBy; } }