- 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
212 lines
6.7 KiB
PHP
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();
|
|
}
|
|
} |