$customer->id, 'currency_code' => strtoupper(trim($currencyCode)), 'amount' => $amount, 'settled_amount' => 0, 'status' => 'open', 'reason' => $reason, 'reference' => $reference, 'shipment_id' => $shipmentId, 'granted_by' => $grantedBy, 'granted_at' => now(), ]); }); } /** * ثبت تسویه (پرداخت ریالی) برای بخشی از یک اعتبار. * * @param bool $fromWallet اگه true، مبلغ ریالی از کیف پول کسر می‌شه. */ public function settle( CustomerCredit $credit, float $amountForeign, float $exchangeRate, string $rateDate, bool $fromWallet, int $settledBy, ?string $reason = null, ?string $reference = null ): CreditSettlement { if ($amountForeign <= 0) { throw new RuntimeException('مبلغ تسویه باید مثبت باشد.'); } if ($exchangeRate <= 0) { throw new RuntimeException('نرخ ارز باید مثبت باشد.'); } if (! $credit->isOpen()) { throw new RuntimeException('این اعتبار قابل تسویه نیست (settled یا بسته).'); } if ($amountForeign > $credit->balance + 0.005) { throw new RuntimeException('مبلغ تسویه از مانده بدهی بیشتر است. مانده: ' . number_format($credit->balance, 2) . ' ' . $credit->currency_code); } return DB::transaction(function () use ( $credit, $amountForeign, $exchangeRate, $rateDate, $fromWallet, $settledBy, $reason, $reference ) { $credit = CustomerCredit::lockForUpdate()->findOrFail($credit->id); $settlementAmountIrr = round($amountForeign * $exchangeRate, 2); $walletTransactionId = null; if ($fromWallet) { $wallet = $credit->user->wallet; if (! $wallet) { throw new RuntimeException('کاربر کیف پول ندارد.'); } if ($wallet->isFrozen()) { throw new RuntimeException('کیف پول کاربر مسدود است.'); } if (! $wallet->hasSufficientBalance($settlementAmountIrr)) { throw new RuntimeException('موجودی کیف پول کافی نیست. نیاز: ' . number_format($settlementAmountIrr) . ' ریال'); } $wt = WalletTransaction::create([ 'wallet_id' => $wallet->id, 'amount' => -$settlementAmountIrr, 'balance_before' => $wallet->balance, 'balance_after' => $wallet->balance - $settlementAmountIrr, 'type' => TransactionType::CREDIT_SETTLEMENT, 'status' => TransactionStatus::COMPLETED, 'gateway' => PaymentGateway::WALLET, 'description' => "تسویه اعتبار {$credit->currency_code} {$amountForeign} (credit #{$credit->id})", 'transactionable_type' => CustomerCredit::class, 'transactionable_id' => $credit->id, 'created_by' => $settledBy, ]); $wallet->update([ 'balance' => $wallet->balance - $settlementAmountIrr, 'total_withdrawn' => $wallet->total_withdrawn + $settlementAmountIrr, ]); $walletTransactionId = $wt->id; } $settlement = CreditSettlement::create([ 'customer_credit_id' => $credit->id, 'settled_amount_foreign' => $amountForeign, 'currency_code' => $credit->currency_code, 'settlement_currency' => 'IRR', 'exchange_rate' => $exchangeRate, 'rate_date' => $rateDate, 'settlement_amount_irr' => $settlementAmountIrr, 'wallet_transaction_id' => $walletTransactionId, 'reason' => $reason, 'reference' => $reference, 'settled_by' => $settledBy, 'settled_at' => now(), ]); $credit->settled_amount = (float) $credit->settled_amount + $amountForeign; $credit->recomputeStatus(); return $settlement; }); } /** * بدهی‌های باز مشتری به تفکیک ارز. * * @return array */ public function getCustomerDebtsByCurrency(User $customer): array { return CustomerCredit::where('user_id', $customer->id) ->whereIn('status', ['open', 'partially_settled']) ->select('currency_code') ->selectRaw('SUM(amount) as total_amount') ->selectRaw('SUM(settled_amount) as total_settled') ->selectRaw('SUM(amount - settled_amount) as balance') ->selectRaw('COUNT(*) as count') ->groupBy('currency_code') ->get() ->map(function ($row) { return [ 'currency_code' => $row->currency_code, 'total_amount' => (float) $row->total_amount, 'total_settled' => (float) $row->total_settled, 'balance' => (float) $row->balance, 'count' => (int) $row->count, ]; }) ->toArray(); } }