refactor(currency): simplify exchange rate logic to use Rial as base
Refactor the currency management system to eliminate the redundant `rate_to_aed` field and transition to a single-base model where all rates are stored relative to the Iranian Rial (IRR). Key changes: - Removed `rate_to_aed` from `currencies` table via new migration. - Updated `ExchangeRateService` to handle rates using a single `currency_code` parameter instead of currency pairs. - Refactored `UpdateExchangeRates` command to fetch rates from the Exir.io API and support multiple currencies (AED, USD, EUR, CNY). - Updated Filament resources (`CurrencyResource`, `ExchangeRateHistoryResource`) and widgets to reflect the new single-base architecture. - Improved `PriceCalculatorService` to use the updated service methodology for fetching the AED/IRR conversion rate. - Localized console command output to Persian.
This commit is contained in:
parent
75fd65be2b
commit
4fac1dae01
@ -2,113 +2,105 @@
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\SystemSetting;
|
||||
use App\Models\Currency;
|
||||
use App\Models\ExchangeRateHistory;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class UpdateExchangeRates extends Command
|
||||
{
|
||||
protected $signature = 'ifnex:update-rates {--source=ecb : Data source for exchange rates (ecb or freecurrencyapi)}';
|
||||
|
||||
protected $description = 'Fetch exchange rates from external APIs and create PENDING proposals (awaiting manager approval)';
|
||||
protected $signature = 'ifnex:update-rates';
|
||||
protected $description = 'دریافت نرخ ارز از API ایرانی (Exir.io) و ایجاد پیشنهاد در انتظار تأیید مدیر';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$source = $this->option('source');
|
||||
|
||||
$this->info("Fetching exchange rates from source: {$source}...");
|
||||
$this->info('در حال دریافت نرخ از API ایرانی (Exir.io)...');
|
||||
|
||||
try {
|
||||
$rates = match ($source) {
|
||||
'freecurrencyapi' => $this->fetchFromFreeCurrencyApi(),
|
||||
default => $this->fetchFromEcb(),
|
||||
};
|
||||
$rates = $this->fetchFromExir();
|
||||
} catch (\Throwable $e) {
|
||||
$this->error("Failed to fetch rates: {$e->getMessage()}");
|
||||
$this->error("خطا در دریافت نرخ: {$e->getMessage()}");
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
if ($rates) {
|
||||
$this->updateRate('aed_to_irr', $rates['aed_to_irr'], 'api', null, $source);
|
||||
$this->updateRate('usd_to_irr', $rates['usd_to_irr'], 'api', null, $source);
|
||||
|
||||
$this->info("✅ Pending rate proposals created (awaiting manager approval in panel):");
|
||||
$this->line(" AED/IRR proposed: " . number_format($rates['aed_to_irr']));
|
||||
$this->line(" USD/IRR proposed: " . number_format($rates['usd_to_irr']));
|
||||
$this->line(" Live rate unchanged until manager approves.");
|
||||
if ($rates && count($rates) > 0) {
|
||||
foreach ($rates as $currencyCode => $rate) {
|
||||
$this->updateRate($currencyCode, $rate);
|
||||
}
|
||||
$this->info('✅ پیشنهاد نرخ ارز ایجاد شد (منتظر تأیید مدیر در پنل).');
|
||||
} else {
|
||||
$this->warn("⚠️ API fetch failed. No pending rate created. Live rate unchanged.");
|
||||
$this->warn('⚠️ دریافت نرخ ناموفق بود. نرخ زنده تغییر نکرد.');
|
||||
}
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* نرخ زنده (SystemSetting) دستنخورده میمونه.
|
||||
* فقط یک رکورد «در انتظار تأیید» در تاریخچه ثبت میشه.
|
||||
* مدیر صبح در پنل → تأیید میکنه → اونوقت روی نرخ زنده اعمال میشه.
|
||||
*/
|
||||
private function updateRate(string $key, float $newRate, string $source, ?int $userId, ?string $apiSource = null): void
|
||||
private function updateRate(string $currencyCode, float $newRate): void
|
||||
{
|
||||
$setting = SystemSetting::where('key', $key)->first();
|
||||
$oldRate = $setting ? (float) $setting->value : 0;
|
||||
$currency = Currency::where('code', $currencyCode)->first();
|
||||
if (!$currency) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($oldRate > 0 && abs($newRate - $oldRate) > 0.01) {
|
||||
$currencyFrom = str_starts_with($key, 'aed') ? 'AED' : 'USD';
|
||||
$oldRate = (float) $currency->rate_to_rial;
|
||||
|
||||
// فقط اگه اختلاف نرخ بیشتر از 0.5% باشه، پیشنهاد ثبت کن
|
||||
if ($oldRate > 0 && abs($newRate - $oldRate) / $oldRate > 0.005) {
|
||||
$changePercent = round((($newRate - $oldRate) / $oldRate) * 100, 4);
|
||||
|
||||
ExchangeRateHistory::create([
|
||||
'currency_from' => $currencyFrom,
|
||||
'currency_from' => $currencyCode,
|
||||
'currency_to' => 'IRR',
|
||||
'old_rate' => $oldRate,
|
||||
'new_rate' => $newRate,
|
||||
'change_percent' => $changePercent,
|
||||
'change_source' => $source,
|
||||
'changed_by' => $userId,
|
||||
'api_source' => $apiSource,
|
||||
'notes' => null,
|
||||
'change_source' => 'api',
|
||||
'api_source' => 'exir.io',
|
||||
'status' => 'pending',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
private function fetchFromEcb(): ?array
|
||||
private function fetchFromExir(): ?array
|
||||
{
|
||||
$response = Http::timeout(10)->get('https://api.exchangerate-api.com/v4/latest/AED');
|
||||
// دریافت لیست جفتارزها از اکسیر
|
||||
$response = Http::timeout(15)->get('https://api.exir.io/v2/minicharts');
|
||||
|
||||
if ($response->failed()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$data = $response->json();
|
||||
$rates = [];
|
||||
|
||||
return [
|
||||
'aed_to_irr' => round($data['rates']['IRR'] ?? 455000, 2),
|
||||
'usd_to_irr' => round(($data['rates']['IRR'] ?? 425000) / 1.07, 2),
|
||||
// نگاشت جفتارزهای اکسیر به کدهای استاندارد ما
|
||||
// اکسیر قیمتها رو بر اساس تتر (USDT) میده، که در ایران معادل دلار نقدی هست
|
||||
$symbolMap = [
|
||||
'usdt-rls' => 'USD', // 1 دلار = X ریال
|
||||
'usdt-aed' => 'AED', // 1 درهم = X تتر (باید در ریال ضرب بشه)
|
||||
// اکسیر یورو و یوان مستقیم نداره، پس فقط دلار و درهم رو میگیریم
|
||||
];
|
||||
}
|
||||
|
||||
private function fetchFromFreeCurrencyApi(): ?array
|
||||
{
|
||||
$response = Http::timeout(10)->get('https://api.freecurrencyapi.com/v1/latest', [
|
||||
'apikey' => config('ifnex.currency.api_key'),
|
||||
'base_currency' => 'AED',
|
||||
]);
|
||||
foreach ($data as $symbol => $info) {
|
||||
if (!isset($symbolMap[$symbol])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($response->failed()) {
|
||||
return null;
|
||||
$currencyCode = $symbolMap[$symbol];
|
||||
$price = (float) ($info['price'] ?? 0);
|
||||
|
||||
if ($price > 0) {
|
||||
// برای دلار: قیمت مستقیم ریاله
|
||||
if ($currencyCode === 'USD') {
|
||||
$rates['USD'] = round($price, 2);
|
||||
}
|
||||
// برای درهم: قیمت بر اساس تتره، باید در نرخ دلار ضرب بشه تا ریال بشه
|
||||
if ($currencyCode === 'AED' && isset($rates['USD'])) {
|
||||
$rates['AED'] = round($price * $rates['USD'], 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$data = $response->json();
|
||||
|
||||
if (!isset($data['data'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'aed_to_irr' => round(($data['data']['IRR'] ?? 455000), 2),
|
||||
'usd_to_irr' => round(($data['data']['IRR'] ?? 425000) / 1.07, 2),
|
||||
];
|
||||
return $rates;
|
||||
}
|
||||
}
|
||||
@ -49,10 +49,11 @@ class IfnexSettingsPage extends Page
|
||||
public function getCurrentRates(): array
|
||||
{
|
||||
$service = app(ExchangeRateService::class);
|
||||
|
||||
return [
|
||||
'AED' => $service->getCurrentRate('AED', 'IRR'),
|
||||
'USD' => $service->getCurrentRate('USD', 'IRR'),
|
||||
'AED' => $service->getCurrentRate('AED'),
|
||||
'USD' => $service->getCurrentRate('USD'),
|
||||
'EUR' => $service->getCurrentRate('EUR'),
|
||||
'CNY' => $service->getCurrentRate('CNY'),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@ -4,11 +4,14 @@ namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Resources\CurrencyResource\Pages;
|
||||
use App\Models\Currency;
|
||||
use App\Services\ExchangeRateService;
|
||||
use Filament\Forms;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Filament\Notifications\Notification;
|
||||
|
||||
class CurrencyResource extends Resource
|
||||
{
|
||||
@ -39,24 +42,19 @@ class CurrencyResource extends Resource
|
||||
->label('نماد (مثل $, €, د.إ)'),
|
||||
])->columns(3),
|
||||
|
||||
Forms\Components\Section::make('نرخ تبدیل')
|
||||
Forms\Components\Section::make('نرخ تبدیل به ریال')
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('rate_to_rial')
|
||||
->required()
|
||||
->numeric()
|
||||
->default(0)
|
||||
->suffix('ریال')
|
||||
->label('نرخ نسبت به ریال'),
|
||||
Forms\Components\TextInput::make('rate_to_aed')
|
||||
->required()
|
||||
->numeric()
|
||||
->default(0)
|
||||
->suffix('درهم')
|
||||
->label('نرخ نسبت به درهم'),
|
||||
->label('نرخ نسبت به ریال')
|
||||
->helperText('این نرخ مبناست. با تغییر این فیلد، نرخ جدید در تاریخچه ثبت میشود.'),
|
||||
Forms\Components\Toggle::make('is_active')
|
||||
->default(true)
|
||||
->label('فعال'),
|
||||
])->columns(3),
|
||||
])->columns(2),
|
||||
]);
|
||||
}
|
||||
|
||||
@ -79,10 +77,6 @@ class CurrencyResource extends Resource
|
||||
->sortable()
|
||||
->formatStateUsing(fn ($state) => number_format((float) $state, 0))
|
||||
->label('نرخ به ریال'),
|
||||
Tables\Columns\TextColumn::make('rate_to_aed')
|
||||
->sortable()
|
||||
->formatStateUsing(fn ($state) => number_format((float) $state, 4))
|
||||
->label('نرخ به درهم'),
|
||||
Tables\Columns\IconColumn::make('is_active')
|
||||
->boolean()
|
||||
->label('فعال'),
|
||||
@ -92,7 +86,25 @@ class CurrencyResource extends Resource
|
||||
->label('وضعیت'),
|
||||
])
|
||||
->actions([
|
||||
Tables\Actions\EditAction::make(),
|
||||
Tables\Actions\EditAction::make()
|
||||
->mutateFormDataUsing(function (array $data, Currency $record): array {
|
||||
// اگه نرخ تغییر کرده، در تاریخچه ثبت کن
|
||||
if ((float) $data['rate_to_rial'] !== (float) $record->getOriginal('rate_to_rial')) {
|
||||
app(ExchangeRateService::class)->setRate(
|
||||
currencyCode: $record->code,
|
||||
newRate: (float) $data['rate_to_rial'],
|
||||
userId: Auth::id(),
|
||||
notes: 'تغییر از پنل مدیریت ارزها'
|
||||
);
|
||||
|
||||
Notification::make()
|
||||
->title('نرخ ارز بهروز شد')
|
||||
->body("نرخ {$record->code} به " . number_format($data['rate_to_rial']) . " ریال تغییر یافت و در تاریخچه ثبت شد.")
|
||||
->success()
|
||||
->send();
|
||||
}
|
||||
return $data;
|
||||
}),
|
||||
])
|
||||
->bulkActions([
|
||||
Tables\Actions\BulkActionGroup::make([
|
||||
|
||||
@ -4,7 +4,6 @@ namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Resources\ExchangeRateHistoryResource\Pages;
|
||||
use App\Models\ExchangeRateHistory;
|
||||
use App\Models\SystemSetting;
|
||||
use App\Services\ExchangeRateService;
|
||||
use Filament\Forms;
|
||||
use Filament\Forms\Form;
|
||||
@ -20,7 +19,7 @@ class ExchangeRateHistoryResource extends Resource
|
||||
protected static ?string $model = ExchangeRateHistory::class;
|
||||
|
||||
protected static ?string $navigationIcon = 'heroicon-o-currency-dollar';
|
||||
protected static ?string $navigationLabel = 'نرخ ارز';
|
||||
protected static ?string $navigationLabel = 'تاریخچه نرخ ارز';
|
||||
protected static ?string $modelLabel = 'تغییر نرخ';
|
||||
protected static ?string $pluralModelLabel = 'تاریخچه نرخ ارز';
|
||||
protected static ?string $navigationGroup = 'مالی';
|
||||
@ -33,10 +32,7 @@ class ExchangeRateHistoryResource extends Resource
|
||||
Forms\Components\Section::make('جزئیات تغییر نرخ')
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('currency_from')
|
||||
->label('ارز مبدأ')
|
||||
->disabled(),
|
||||
Forms\Components\TextInput::make('currency_to')
|
||||
->label('ارز مقصد')
|
||||
->label('ارز')
|
||||
->disabled(),
|
||||
Forms\Components\TextInput::make('old_rate')
|
||||
->label('نرخ قبلی')
|
||||
@ -84,9 +80,8 @@ class ExchangeRateHistoryResource extends Resource
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
|
||||
Tables\Columns\TextColumn::make('currency_pair')
|
||||
->label('جفت ارز')
|
||||
->state(fn ($record) => "{$record->currency_from} → {$record->currency_to}")
|
||||
Tables\Columns\TextColumn::make('currency_from')
|
||||
->label('ارز')
|
||||
->badge()
|
||||
->color('info'),
|
||||
|
||||
@ -178,18 +173,14 @@ class ExchangeRateHistoryResource extends Resource
|
||||
'api' => 'API خودکار',
|
||||
'manual' => 'دستی',
|
||||
]),
|
||||
Tables\Filters\SelectFilter::make('currency_pair')
|
||||
->label('جفت ارز')
|
||||
Tables\Filters\SelectFilter::make('currency_from')
|
||||
->label('ارز')
|
||||
->options([
|
||||
'AED_IRR' => 'AED → IRR',
|
||||
'USD_IRR' => 'USD → IRR',
|
||||
])
|
||||
->query(function ($query, array $data) {
|
||||
if (!$data['value']) return $query;
|
||||
[$from, $to] = explode('_', $data['value']);
|
||||
return $query->where('currency_from', $from)
|
||||
->where('currency_to', $to);
|
||||
}),
|
||||
'AED' => 'درهم (AED)',
|
||||
'USD' => 'دلار (USD)',
|
||||
'EUR' => 'یورو (EUR)',
|
||||
'CNY' => 'یوان (CNY)',
|
||||
]),
|
||||
Tables\Filters\Filter::make('created_at')
|
||||
->form([
|
||||
Forms\Components\DatePicker::make('from')->label('از تاریخ'),
|
||||
@ -209,7 +200,7 @@ class ExchangeRateHistoryResource extends Resource
|
||||
->color('success')
|
||||
->requiresConfirmation()
|
||||
->modalHeading('تأیید نرخ پیشنهادی')
|
||||
->modalDescription(fn ($record) => "نرخ {$record->currency_from}→{$record->currency_to} از " . number_format($record->old_rate) . " به " . number_format($record->new_rate) . " ریال تغییر یابد؟ نرخ زنده پس از تأیید بهروز میشود.")
|
||||
->modalDescription(fn ($record) => "نرخ {$record->currency_from} از " . number_format($record->old_rate) . " به " . number_format($record->new_rate) . " ریال تغییر یابد؟ نرخ زنده پس از تأیید بهروز میشود.")
|
||||
->modalSubmitActionLabel('بله، تأیید و اعمال کن')
|
||||
->visible(fn ($record) => $record->status === 'pending')
|
||||
->action(function ($record) {
|
||||
@ -238,76 +229,17 @@ class ExchangeRateHistoryResource extends Resource
|
||||
}),
|
||||
])
|
||||
->headerActions([
|
||||
Action::make('setNewRate')
|
||||
->label('تنظیم نرخ جدید')
|
||||
->icon('heroicon-o-pencil-square')
|
||||
->color('warning')
|
||||
->form([
|
||||
Forms\Components\Select::make('currency_pair')
|
||||
->label('جفت ارز')
|
||||
->required()
|
||||
->options([
|
||||
'AED_IRR' => 'درهم به ریال (AED → IRR)',
|
||||
'USD_IRR' => 'دلار به ریال (USD → IRR)',
|
||||
])
|
||||
->default('AED_IRR'),
|
||||
|
||||
Forms\Components\TextInput::make('new_rate')
|
||||
->label('نرخ جدید (ریال)')
|
||||
->required()
|
||||
->numeric()
|
||||
->minValue(1)
|
||||
->prefix('ریال')
|
||||
->helperText(fn (callable $get) =>
|
||||
'نرخ فعلی: ' . number_format(self::getCurrentRateForPair($get('currency_pair'))) . ' ریال'
|
||||
),
|
||||
|
||||
Forms\Components\Textarea::make('notes')
|
||||
->label('یادداشت (اختیارر)')
|
||||
->placeholder('دلیل تغییر نرخ...')
|
||||
->maxLength(500),
|
||||
])
|
||||
->action(function (array $data) {
|
||||
[$from, $to] = explode('_', $data['currency_pair']);
|
||||
|
||||
$service = app(ExchangeRateService::class);
|
||||
$service->setRate(
|
||||
from: $from,
|
||||
to: $to,
|
||||
newRate: (float) $data['new_rate'],
|
||||
userId: Auth::id(),
|
||||
notes: $data['notes'] ?? null
|
||||
);
|
||||
|
||||
Notification::make()
|
||||
->title('نرخ ارز با موفقیت بهروز شد')
|
||||
->body("نرخ {$from}/{$to} به " . number_format($data['new_rate']) . " ریال تغییر یافت")
|
||||
->success()
|
||||
->send();
|
||||
}),
|
||||
|
||||
Action::make('refreshFromApi')
|
||||
->label('آپدیت از API')
|
||||
->icon('heroicon-o-arrow-path')
|
||||
->color('info')
|
||||
->requiresConfirmation()
|
||||
->modalHeading('آپدیت نرخ از API')
|
||||
->modalDescription('این عمل یک نرخ پیشنهادی «در انتظار تأیید» میسازد. نرخ زنده تغییر نمیکند تا تأیید کنید.')
|
||||
->color('gray')
|
||||
->tooltip('این قابلیت موقتاً غیرفعال است. لطفاً نرخها را بهصورت دستی از منوی «ارزها» در بخش تنظیمات وارد کنید.')
|
||||
->action(function () {
|
||||
try {
|
||||
\Artisan::call('ifnex:update-rates');
|
||||
|
||||
Notification::make()
|
||||
->title('نرخ پیشنهادی API ساخته شد — منتظر تأیید شما')
|
||||
->success()
|
||||
->send();
|
||||
} catch (\Throwable $e) {
|
||||
Notification::make()
|
||||
->title('خطا در بهروزرسانی')
|
||||
->body($e->getMessage())
|
||||
->danger()
|
||||
->send();
|
||||
}
|
||||
Notification::make()
|
||||
->title('قابلیت غیرفعال است')
|
||||
->body('دریافت خودکار نرخ از API موقتاً غیرفعال است. لطفاً نرخها را بهصورت دستی از منوی «ارزها» در بخش تنظیمات وارد کنید.')
|
||||
->warning()
|
||||
->send();
|
||||
}),
|
||||
])
|
||||
->bulkActions([])
|
||||
@ -315,13 +247,6 @@ class ExchangeRateHistoryResource extends Resource
|
||||
->poll('60s');
|
||||
}
|
||||
|
||||
protected static function getCurrentRateForPair(?string $pair): float
|
||||
{
|
||||
if (!$pair) return 0;
|
||||
[$from, $to] = explode('_', $pair);
|
||||
return app(ExchangeRateService::class)->getCurrentRate($from, $to);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [];
|
||||
|
||||
@ -28,14 +28,14 @@ class ExchangeRateWidget extends BaseWidget
|
||||
{
|
||||
$service = app(ExchangeRateService::class);
|
||||
|
||||
$aedInfo = $service->getLatestRateInfo('AED', 'IRR');
|
||||
$usdInfo = $service->getLatestRateInfo('USD', 'IRR');
|
||||
$aedInfo = $service->getLatestRateInfo('AED');
|
||||
$usdInfo = $service->getLatestRateInfo('USD');
|
||||
|
||||
$aedChangePercent = $aedInfo['change_percent'];
|
||||
$usdChangePercent = $usdInfo['change_percent'];
|
||||
|
||||
$aedChart = $service->getRateChart('AED', 'IRR', 7);
|
||||
$usdChart = $service->getRateChart('USD', 'IRR', 7);
|
||||
$aedChart = $service->getRateChart('AED', 7);
|
||||
$usdChart = $service->getRateChart('USD', 7);
|
||||
|
||||
return [
|
||||
Stat::make('نرخ درهم (AED)', ifnex_format_number($aedInfo['current_rate'] ?? 0))
|
||||
|
||||
@ -7,14 +7,13 @@ use Illuminate\Database\Eloquent\Model;
|
||||
class Currency extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'code', 'name', 'symbol', 'rate_to_rial', 'rate_to_aed', 'is_active',
|
||||
'code', 'name', 'symbol', 'rate_to_rial', 'is_active',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'rate_to_rial' => 'decimal:4',
|
||||
'rate_to_aed' => 'decimal:6',
|
||||
'is_active' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\SystemSetting;
|
||||
use App\Models\Currency;
|
||||
use App\Models\ExchangeRateHistory;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use RuntimeException;
|
||||
@ -10,45 +10,45 @@ use RuntimeException;
|
||||
class ExchangeRateService
|
||||
{
|
||||
/**
|
||||
* دریافت نرخ فعلی (زنده = تأییدشده)
|
||||
* دریافت نرخ فعلی ارز نسبت به ریال.
|
||||
* ریال ارز مبناست و نرخ تمام ارزها فقط نسبت به ریال ذخیره میشود.
|
||||
*
|
||||
* @param string $currencyCode کد ارز (مثل AED, USD, EUR)
|
||||
* @return float نرخ ارز نسبت به ریال
|
||||
*/
|
||||
public function getCurrentRate(string $from = 'AED', string $to = 'IRR'): float
|
||||
public function getCurrentRate(string $currencyCode = 'AED'): float
|
||||
{
|
||||
$key = strtolower("{$from}_to_{$to}");
|
||||
$setting = SystemSetting::where('key', $key)->first();
|
||||
$currency = Currency::where('code', strtoupper($currencyCode))->first();
|
||||
|
||||
if (!$setting) {
|
||||
if (!$currency) {
|
||||
return config('ifnex.financial_defaults.aed_to_irr', 455000);
|
||||
}
|
||||
|
||||
return (float) $setting->value;
|
||||
return (float) $currency->rate_to_rial;
|
||||
}
|
||||
|
||||
/**
|
||||
* تنظیم نرخ جدید بهصورت دستی (توسط مدیر) → مستقیم اعمال میشه (status=approved)
|
||||
* تنظیم نرخ جدید برای یک ارز (نسبت به ریال).
|
||||
*
|
||||
* @param string $currencyCode کد ارز
|
||||
* @param float $newRate نرخ جدید نسبت به ریال
|
||||
* @param int|null $userId آیدی کاربر تغییردهنده
|
||||
*/
|
||||
public function setRate(
|
||||
string $from,
|
||||
string $to,
|
||||
string $currencyCode,
|
||||
float $newRate,
|
||||
?int $userId = null,
|
||||
?string $notes = null
|
||||
): ExchangeRateHistory {
|
||||
$key = strtolower("{$from}_to_{$to}");
|
||||
$oldRate = $this->getCurrentRate($from, $to);
|
||||
$currencyCode = strtoupper($currencyCode);
|
||||
$oldRate = $this->getCurrentRate($currencyCode);
|
||||
|
||||
SystemSetting::updateOrCreate(
|
||||
['key' => $key],
|
||||
[
|
||||
'value' => (string) $newRate,
|
||||
'type' => 'number',
|
||||
'description' => "{$from} to {$to} exchange rate",
|
||||
]
|
||||
);
|
||||
Currency::where('code', $currencyCode)->update([
|
||||
'rate_to_rial' => $newRate,
|
||||
]);
|
||||
|
||||
return $this->recordChange(
|
||||
from: $from,
|
||||
to: $to,
|
||||
currencyCode: $currencyCode,
|
||||
oldRate: $oldRate,
|
||||
newRate: $newRate,
|
||||
source: 'manual',
|
||||
@ -58,7 +58,7 @@ class ExchangeRateService
|
||||
}
|
||||
|
||||
/**
|
||||
* دریافت آخرین تغییرات
|
||||
* دریافت آخرین تغییرات نرخ ارز
|
||||
*/
|
||||
public function getLatestChanges(int $limit = 10)
|
||||
{
|
||||
@ -69,18 +69,17 @@ class ExchangeRateService
|
||||
}
|
||||
|
||||
/**
|
||||
* دریافت اطلاعات آخرین نرخ (تأییدشده) برای Widget
|
||||
* دریافت اطلاعات آخرین نرخ برای Widget
|
||||
*/
|
||||
public function getLatestRateInfo(string $from = 'AED', string $to = 'IRR'): array
|
||||
public function getLatestRateInfo(string $currencyCode = 'AED'): array
|
||||
{
|
||||
$latest = ExchangeRateHistory::where('currency_from', $from)
|
||||
->where('currency_to', $to)
|
||||
->where('status', 'approved') // فقط تغییرات تأییدشده
|
||||
$latest = ExchangeRateHistory::where('currency_from', strtoupper($currencyCode))
|
||||
->where('status', 'approved')
|
||||
->orderBy('created_at', 'desc')
|
||||
->with('changedBy')
|
||||
->first();
|
||||
|
||||
$currentRate = $this->getCurrentRate($from, $to);
|
||||
$currentRate = $this->getCurrentRate($currencyCode);
|
||||
|
||||
return [
|
||||
'current_rate' => $currentRate,
|
||||
@ -93,13 +92,12 @@ class ExchangeRateService
|
||||
}
|
||||
|
||||
/**
|
||||
* دریافت آخرین N نرخ (تأییدشده) برای Chart
|
||||
* دریافت آخرین N نرخ برای Chart
|
||||
*/
|
||||
public function getRateChart(string $from = 'AED', string $to = 'IRR', int $limit = 7): array
|
||||
public function getRateChart(string $currencyCode = 'AED', int $limit = 7): array
|
||||
{
|
||||
return ExchangeRateHistory::where('currency_from', $from)
|
||||
->where('currency_to', $to)
|
||||
->where('status', 'approved') // فقط تغییرات تأییدشده
|
||||
return ExchangeRateHistory::where('currency_from', strtoupper($currencyCode))
|
||||
->where('status', 'approved')
|
||||
->orderBy('created_at', 'asc')
|
||||
->limit($limit)
|
||||
->pluck('new_rate')
|
||||
@ -107,11 +105,10 @@ class ExchangeRateService
|
||||
}
|
||||
|
||||
/**
|
||||
* ثبت تغییر نرخ (داخلی). ورود دستی → status=approved (مستقیم اعمال).
|
||||
* ثبت تغییر نرخ (داخلی).
|
||||
*/
|
||||
private function recordChange(
|
||||
string $from,
|
||||
string $to,
|
||||
string $currencyCode,
|
||||
float $oldRate,
|
||||
float $newRate,
|
||||
string $source = 'manual',
|
||||
@ -125,24 +122,23 @@ class ExchangeRateService
|
||||
}
|
||||
|
||||
return ExchangeRateHistory::create([
|
||||
'currency_from' => $from,
|
||||
'currency_to' => $to,
|
||||
'currency_from' => $currencyCode,
|
||||
'currency_to' => 'IRR', // همیشه ریال
|
||||
'old_rate' => $oldRate,
|
||||
'new_rate' => $newRate,
|
||||
'change_percent' => $changePercent,
|
||||
'change_source' => $source,
|
||||
'changed_by' => $userId,
|
||||
'approved_by' => $userId, // ورود دستی = خود مدیر تأیید کرده
|
||||
'approved_by' => $userId,
|
||||
'approved_at' => now(),
|
||||
'api_source' => $apiSource,
|
||||
'notes' => $notes,
|
||||
'status' => 'approved', // ورود دستی مستقیم اعمال میشه
|
||||
'status' => 'approved',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* تأیید یک نرخ در انتظار توسط مدیر.
|
||||
* نرخ روی SystemSetting (زنده) اعمال میشه و رکورد history تأیید میشه.
|
||||
*/
|
||||
public function approveRate(ExchangeRateHistory $history, int $userId): ExchangeRateHistory
|
||||
{
|
||||
@ -151,16 +147,9 @@ class ExchangeRateService
|
||||
}
|
||||
|
||||
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,
|
||||
]
|
||||
);
|
||||
Currency::where('code', $history->currency_from)->update([
|
||||
'rate_to_rial' => $history->new_rate,
|
||||
]);
|
||||
|
||||
$history->update([
|
||||
'status' => 'approved',
|
||||
@ -173,7 +162,7 @@ class ExchangeRateService
|
||||
}
|
||||
|
||||
/**
|
||||
* رد یک نرخ در انتظار. نرخ زنده دستنخورده میمونه.
|
||||
* رد یک نرخ در انتظار.
|
||||
*/
|
||||
public function rejectRate(ExchangeRateHistory $history, int $userId, ?string $reason = null): ExchangeRateHistory
|
||||
{
|
||||
|
||||
@ -124,7 +124,7 @@ class PriceCalculatorService
|
||||
{
|
||||
$profitMargin = (float) SystemSetting::get('profit_margin', 1.25);
|
||||
// نرخ ارز فقط از ExchangeRateService خوانده میشود (منبع واحد — منوی نرخ ارز)
|
||||
$aedToIrr = app(ExchangeRateService::class)->getCurrentRate('AED', 'IRR');
|
||||
$aedToIrr = app(ExchangeRateService::class)->getCurrentRate('AED');
|
||||
|
||||
$netDirham = $baseRate * $profitMargin;
|
||||
$netRial = $netDirham * $aedToIrr;
|
||||
|
||||
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('currencies', function (Blueprint $table) {
|
||||
if (Schema::hasColumn('currencies', 'rate_to_aed')) {
|
||||
$table->dropColumn('rate_to_aed');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('currencies', function (Blueprint $table) {
|
||||
$table->decimal('rate_to_aed', 10, 6)->default(0)->after('rate_to_rial');
|
||||
});
|
||||
}
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user