ifnex/04_Laravel/app/Services/ExchangeRateService.php
Kazem Alghasi 3848f0b399 feat(finance): rate approval + multi-currency credit system (درخواست ۹)
- rate approval: API creates pending proposals, manager approves
- customer_credits + credit_settlements tables
- CustomerCreditService (grant/settle via wallet)
- rebuilt CustomerCreditResource
- CustomerFinancialController: real foreign-currency debt
- WP login redirect + custom login page wiring
2026-09-30 05:14:31 +03:30

212 lines
6.7 KiB
PHP

<?php
namespace App\Services;
use App\Models\SystemSetting;
use App\Models\ExchangeRateHistory;
use Illuminate\Support\Facades\DB;
use RuntimeException;
class ExchangeRateService
{
/**
* دریافت نرخ فعلی (زنده = تأییدشده)
*/
public function getCurrentRate(string $from = 'AED', string $to = 'IRR'): float
{
$key = strtolower("{$from}_to_{$to}");
$setting = SystemSetting::where('key', $key)->first();
if (!$setting) {
return config('ifnex.financial_defaults.aed_to_irr', 455000);
}
return (float) $setting->value;
}
/**
* تنظیم نرخ جدید به‌صورت دستی (توسط مدیر) → مستقیم اعمال می‌شه (status=approved)
*/
public function setRate(
string $from,
string $to,
float $newRate,
?int $userId = null,
?string $notes = null
): ExchangeRateHistory {
$key = strtolower("{$from}_to_{$to}");
$oldRate = $this->getCurrentRate($from, $to);
SystemSetting::updateOrCreate(
['key' => $key],
[
'value' => (string) $newRate,
'type' => 'number',
'description' => "{$from} to {$to} exchange rate",
]
);
return $this->recordChange(
from: $from,
to: $to,
oldRate: $oldRate,
newRate: $newRate,
source: 'manual',
userId: $userId,
notes: $notes
);
}
/**
* دریافت آخرین تغییرات
*/
public function getLatestChanges(int $limit = 10)
{
return ExchangeRateHistory::with('changedBy')
->orderBy('created_at', 'desc')
->limit($limit)
->get();
}
/**
* دریافت اطلاعات آخرین نرخ (تأییدشده) برای Widget
*/
public function getLatestRateInfo(string $from = 'AED', string $to = 'IRR'): array
{
$latest = ExchangeRateHistory::where('currency_from', $from)
->where('currency_to', $to)
->where('status', 'approved') // فقط تغییرات تأییدشده
->orderBy('created_at', 'desc')
->with('changedBy')
->first();
$currentRate = $this->getCurrentRate($from, $to);
return [
'current_rate' => $currentRate,
'last_change' => $latest,
'last_updated' => $latest?->created_at,
'changed_by' => $latest?->changedBy?->name,
'source' => $latest?->change_source,
'change_percent' => $latest?->change_percent,
];
}
/**
* دریافت آخرین N نرخ (تأییدشده) برای Chart
*/
public function getRateChart(string $from = 'AED', string $to = 'IRR', int $limit = 7): array
{
return ExchangeRateHistory::where('currency_from', $from)
->where('currency_to', $to)
->where('status', 'approved') // فقط تغییرات تأییدشده
->orderBy('created_at', 'asc')
->limit($limit)
->pluck('new_rate')
->toArray();
}
/**
* ثبت تغییر نرخ (داخلی). ورود دستی → status=approved (مستقیم اعمال).
*/
private function recordChange(
string $from,
string $to,
float $oldRate,
float $newRate,
string $source = 'manual',
?int $userId = null,
?string $apiSource = null,
?string $notes = null
): ExchangeRateHistory {
$changePercent = null;
if ($oldRate > 0) {
$changePercent = round((($newRate - $oldRate) / $oldRate) * 100, 4);
}
return ExchangeRateHistory::create([
'currency_from' => $from,
'currency_to' => $to,
'old_rate' => $oldRate,
'new_rate' => $newRate,
'change_percent' => $changePercent,
'change_source' => $source,
'changed_by' => $userId,
'approved_by' => $userId, // ورود دستی = خود مدیر تأیید کرده
'approved_at' => now(),
'api_source' => $apiSource,
'notes' => $notes,
'status' => 'approved', // ورود دستی مستقیم اعمال می‌شه
]);
}
/**
* تأیید یک نرخ در انتظار توسط مدیر.
* نرخ روی SystemSetting (زنده) اعمال می‌شه و رکورد history تأیید می‌شه.
*/
public function approveRate(ExchangeRateHistory $history, int $userId): ExchangeRateHistory
{
if (! $history->isPending()) {
throw new RuntimeException('این رکورد در وضعیت قابل تأیید نیست.');
}
return DB::transaction(function () use ($history, $userId) {
$key = strtolower("{$history->currency_from}_to_{$history->currency_to}");
SystemSetting::updateOrCreate(
['key' => $key],
[
'value' => (string) $history->new_rate,
'description' => "{$history->currency_from} to {$history->currency_to} exchange rate",
'updated_by' => $userId,
]
);
$history->update([
'status' => 'approved',
'approved_by' => $userId,
'approved_at' => now(),
]);
return $history->fresh();
});
}
/**
* رد یک نرخ در انتظار. نرخ زنده دست‌نخورده می‌مونه.
*/
public function rejectRate(ExchangeRateHistory $history, int $userId, ?string $reason = null): ExchangeRateHistory
{
if (! $history->isPending()) {
throw new RuntimeException('این رکورد در وضعیت قابل رد نیست.');
}
$history->update([
'status' => 'rejected',
'approved_by' => $userId,
'approved_at' => now(),
'notes' => $reason ?: $history->notes,
]);
return $history->fresh();
}
/**
* لیست نرخ‌های در انتظار تأیید.
*/
public function getPendingRates(int $limit = 20)
{
return ExchangeRateHistory::where('status', 'pending')
->orderBy('created_at', 'desc')
->limit($limit)
->get();
}
/**
* تعداد نرخ‌های در انتظار تأیید (برای badge).
*/
public function getPendingCount(): int
{
return ExchangeRateHistory::where('status', 'pending')->count();
}
}