$invoices * @return array>> keyed by invoice id, oldest payment first */ public static function forInvoices(Collection $invoices): array { $history = $invoices->mapWithKeys(fn (Invoice $invoice) => [$invoice->id => []])->all(); if ($invoices->isEmpty()) { return $history; } $invoiceByOrderNum = $invoices ->filter(fn (Invoice $invoice) => (int) $invoice->invoice_type === 1 && $invoice->order?->order_num) ->keyBy(fn (Invoice $invoice) => $invoice->order->order_num); $transactions = Transaction::with('user') ->whereIn('customer_id', $invoices->pluck('customer_id')->unique()) ->where(function ($query) use ($invoices, $invoiceByOrderNum) { foreach ($invoices as $invoice) { $query->orWhere('trans', $invoice->invoice_number) ->orWhere('trans', 'like', $invoice->invoice_number . '-%'); } if ($invoiceByOrderNum->isNotEmpty()) { $query->orWhere(fn ($creation) => $creation ->whereIn('trans', $invoiceByOrderNum->keys()) ->where('public_code', 'like', self::SERVICE_CREATION_PREFIX . '%')); } }) ->oldest('id') ->get(); $invoiceByNumber = $invoices->keyBy('invoice_number'); $renewalCausers = self::renewalCausers($invoices); foreach ($transactions as $transaction) { $invoice = self::matchInvoice($transaction, $invoiceByNumber, $invoiceByOrderNum); if ($invoice) { $history[$invoice->id][] = self::present($transaction, $renewalCausers[$invoice->invoice_number] ?? null); } } return $history; } private static function matchInvoice(Transaction $transaction, Collection $invoiceByNumber, Collection $invoiceByOrderNum): ?Invoice { if (Str::startsWith((string) $transaction->public_code, self::SERVICE_CREATION_PREFIX)) { return $invoiceByOrderNum->get($transaction->trans); } $number = preg_replace('/-(P\d+|REFUND)$/', '', (string) $transaction->trans); return $invoiceByNumber->get($number); } private static function renewalCausers(Collection $invoices): array { $numbers = $invoices->where('invoice_type', 2)->pluck('invoice_number')->all(); if ($numbers === []) { return []; } return ActivityLog::where('subject_type', Service::class) ->where('event', 'renewed') ->whereIn('new_values->invoice_number', $numbers) ->get(['causer_type', 'new_values']) ->mapWithKeys(fn (ActivityLog $log) => [$log->new_values['invoice_number'] => $log->causer_type]) ->all(); } private static function method(Transaction $transaction, bool $isRefund, ?string $renewalCauser): string { if ($isRefund) { return 'refund'; } if (in_array($transaction->type, ['transfer', 'balance'], true)) { return $transaction->type; } if (preg_match('/\(([a-z_]+)\)$/', (string) $transaction->note, $match) !== 1) { return 'other'; } return match (true) { $match[1] === 'balance' && $renewalCauser === 'system' => 'auto_renew', default => $match[1], }; } private static function present(Transaction $transaction, ?string $renewalCauser): array { $isRefund = Str::endsWith((string) $transaction->trans, '-REFUND'); return [ 'id' => $transaction->id, 'public_code' => $transaction->public_code, 'kind' => $isRefund ? 'refund' : 'payment', 'amount' => abs((int) $transaction->amount), 'type' => $transaction->type, 'method' => self::method($transaction, $isRefund, $renewalCauser), 'note' => preg_replace('/ \([a-z_]+\)$/', '', (string) $transaction->note) ?: null, 'recorded_by' => $transaction->user?->fullname ?: $transaction->user?->username, 'created_at' => $transaction->created_at, ]; } }