Files
boursecu_server/app/Http/Controllers/Admin/SmsPanelController.php
T

561 lines
19 KiB
PHP
Raw Normal View History

2026-07-04 11:55:10 +03:30
<?php
namespace App\Http\Controllers\Admin;
use App\DB\Te\TableEngine;
use App\Http\Controllers\Controller;
use App\Models\SmsLedger;
use App\Models\SmsSendLog;
use App\Services\SmsBilling\SmsWalletService;
use App\Services\SmsBilling\SuperAdminSmsClient;
2026-07-04 11:55:10 +03:30
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class SmsPanelController extends Controller
{
private function sa(): SuperAdminSmsClient
{
return app(SuperAdminSmsClient::class);
}
private function useSuperAdmin(): bool
{
return $this->sa()->enabled();
}
private function abortSaUnavailable(): void
{
abort(502, 'ارتباط با کیف پول پیامک سوپرادمین برقرار نشد.');
}
/**
* @param array<string, mixed> $chargeInfo
* @return array{card_number: string, sheba: string, account_holder: string, note: string}
*/
private function depositFromChargeInfo(array $chargeInfo): array
{
return [
'card_number' => trim((string) ($chargeInfo['card'] ?? $chargeInfo['card_number'] ?? '')),
'sheba' => trim((string) ($chargeInfo['iban'] ?? $chargeInfo['sheba'] ?? '')),
'account_holder' => trim((string) ($chargeInfo['account_holder'] ?? $chargeInfo['accountHolder'] ?? '')),
'note' => trim((string) ($chargeInfo['instruction'] ?? $chargeInfo['note'] ?? '')),
];
}
/** @param array<string, string> $deposit */
private function depositHasAny(array $deposit): bool
{
foreach (['card_number', 'sheba', 'account_holder', 'note'] as $field) {
if (trim((string) ($deposit[$field] ?? '')) !== '') {
return true;
}
}
return false;
}
/**
* اگر سوپرادمین کارت/شبا خالی بدهد، دیفالت محلی را نگه دار — وگرنه هر رفرش هدر تنظیمات را خالی می‌کند.
*
* @param array<string, mixed>|null $chargeInfo
* @return array{card_number: string, sheba: string, account_holder: string, note: string}
*/
private function resolveDeposit(?array $chargeInfo): array
{
$fromSa = is_array($chargeInfo) ? $this->depositFromChargeInfo($chargeInfo) : [
'card_number' => '',
'sheba' => '',
'account_holder' => '',
'note' => '',
];
$local = SmsWalletService::getDepositInfo();
$out = [];
foreach (['card_number', 'sheba', 'account_holder', 'note'] as $field) {
$saVal = trim((string) ($fromSa[$field] ?? ''));
$out[$field] = $saVal !== '' ? $saVal : $local[$field];
}
return $out;
}
/**
* @param array<string, mixed> $data
*/
private function mirrorLocalFromSa(array $data): void
{
try {
$wallet = is_array($data['wallet'] ?? null) ? $data['wallet'] : $data;
$balance = null;
if (isset($wallet['balance'])) {
$balance = (int) $wallet['balance'];
} elseif (isset($data['balance'])) {
$balance = (int) $data['balance'];
}
$price = null;
if (isset($wallet['price_per_part'])) {
$price = (int) $wallet['price_per_part'];
} elseif (isset($data['price_per_part'])) {
$price = (int) $data['price_per_part'];
}
$blocked = null;
if (is_array($data['wallet'] ?? null) && array_key_exists('is_blocked', $wallet)) {
$blocked = (bool) $wallet['is_blocked'];
} elseif (array_key_exists('is_blocked', $data)) {
$blocked = (bool) $data['is_blocked'];
}
if ($balance === null && $price === null && $blocked === null) {
return;
}
app(SmsWalletService::class)->mirrorFromSuperAdmin($balance, $price, $blocked);
if (isset($data['charge_info']) && is_array($data['charge_info'])) {
$fromCharge = $this->depositFromChargeInfo($data['charge_info']);
if ($this->depositHasAny($fromCharge)) {
SmsWalletService::setDepositInfo($fromCharge);
}
}
if (isset($data['deposit']) && is_array($data['deposit'])) {
if ($this->depositHasAny($data['deposit'])) {
SmsWalletService::setDepositInfo($data['deposit']);
}
}
} catch (\Throwable $e) {
report($e);
}
}
2026-07-04 11:55:10 +03:30
public function overview()
{
if ($this->useSuperAdmin()) {
$data = $this->sa()->overview();
if ($data === null) {
$this->abortSaUnavailable();
}
$charge = is_array($data['charge_info'] ?? null) ? $data['charge_info'] : [];
$data['deposit'] = $this->resolveDeposit($charge);
$this->mirrorLocalFromSa($data);
return response()->json($data);
}
2026-07-04 11:55:10 +03:30
$wallet = app(SmsWalletService::class)->getWallet();
$monthStart = Carbon::now()->startOfMonth();
$monthStats = SmsSendLog::query()
->where('created_at', '>=', $monthStart)
->selectRaw('COALESCE(SUM(parts), 0) as total_parts, COALESCE(SUM(total_amount), 0) as total_amount, COUNT(*) as total_messages')
->first();
$todayStats = SmsSendLog::query()
->whereDate('created_at', Carbon::today())
->selectRaw('COALESCE(SUM(parts), 0) as total_parts, COALESCE(SUM(total_amount), 0) as total_amount, COUNT(*) as total_messages')
->first();
return response()->json([
'wallet' => [
'balance' => (int) $wallet->balance,
'price_per_part' => (int) $wallet->price_per_part,
'is_blocked' => (bool) $wallet->is_blocked,
'alert_days_sent' => (int) $wallet->alert_days_sent,
'last_alert_date' => $wallet->last_alert_date?->toDateString(),
],
2026-07-07 17:42:09 +03:30
'deposit' => SmsWalletService::getDepositInfo(),
2026-07-04 11:55:10 +03:30
'stats' => [
'today' => [
'parts' => (int) ($todayStats->total_parts ?? 0),
'amount' => (int) ($todayStats->total_amount ?? 0),
'messages' => (int) ($todayStats->total_messages ?? 0),
],
'month' => [
'parts' => (int) ($monthStats->total_parts ?? 0),
'amount' => (int) ($monthStats->total_amount ?? 0),
'messages' => (int) ($monthStats->total_messages ?? 0),
],
],
]);
}
2026-07-07 17:42:09 +03:30
/**
* خلاصه سبک کیف پول برای نمایش «اعتبار پیامک» در هدر؛ برای همه ادمین‌ها.
* اگر سوپرادمین قطع باشد، روی آینهٔ محلی برمی‌گردیم — نه 502 (ویجت هدر نباید بترکد).
2026-07-07 17:42:09 +03:30
*/
public function walletSummary()
{
if (! SmsWalletService::isBillingEnabled()) {
return response()->json([
'enabled' => false,
'balance' => 0,
'is_blocked' => false,
'deposit' => SmsWalletService::getDepositInfo(),
]);
}
if ($this->useSuperAdmin()) {
$balanceData = $this->sa()->balance();
$charge = $this->sa()->chargeInfo();
if ($balanceData !== null) {
$deposit = $this->resolveDeposit(is_array($charge) ? $charge : null);
$this->mirrorLocalFromSa(array_merge($balanceData, [
'deposit' => $deposit,
]));
return response()->json([
'enabled' => true,
'balance' => (int) ($balanceData['balance'] ?? 0),
'is_blocked' => (bool) ($balanceData['is_blocked'] ?? false),
'deposit' => $deposit,
'source' => 'superadmin',
'sa_partial' => $charge === null,
]);
}
// SA فعال است ولی balance نرسید — آخرین آینهٔ محلی
return response()->json($this->localWalletSummaryPayload('local_fallback'));
}
return response()->json($this->localWalletSummaryPayload('local'));
}
/**
* @return array{enabled: bool, balance: int, is_blocked: bool, deposit: array, source: string}
*/
private function localWalletSummaryPayload(string $source): array
{
2026-07-07 17:42:09 +03:30
$balance = 0;
$isBlocked = false;
if (SmsWalletService::isBillingOperational()) {
$wallet = app(SmsWalletService::class)->getWallet();
$balance = (int) $wallet->balance;
$isBlocked = (bool) $wallet->is_blocked;
}
return [
2026-07-07 17:42:09 +03:30
'enabled' => true,
'balance' => $balance,
'is_blocked' => $isBlocked,
'deposit' => SmsWalletService::getDepositInfo(),
'source' => $source,
];
2026-07-07 17:42:09 +03:30
}
/**
* ویرایش اطلاعات واریز (کارت/شبا/نام/متن) — فقط سوپرادمین پنل پیامک.
*/
public function updateDepositInfo(Request $request)
{
$data = $request->validate([
'card_number' => 'nullable|string|max:50',
'sheba' => 'nullable|string|max:50',
'account_holder' => 'nullable|string|max:191',
'note' => 'nullable|string|max:2000',
]);
if ($this->useSuperAdmin()) {
$result = $this->sa()->updateChargeInfo([
'card' => (string) ($data['card_number'] ?? ''),
'iban' => (string) ($data['sheba'] ?? ''),
'account_holder' => (string) ($data['account_holder'] ?? ''),
'instruction' => (string) ($data['note'] ?? ''),
]);
if ($result === null) {
$this->abortSaUnavailable();
}
$charge = is_array($result['charge_info'] ?? null) ? $result['charge_info'] : [];
$deposit = $this->depositFromChargeInfo($charge);
$this->mirrorLocalFromSa(array_merge($result, ['deposit' => $deposit]));
return response()->json([
'message' => (string) ($result['message'] ?? 'اطلاعات واریز به‌روزرسانی شد.'),
'deposit' => $deposit,
'source' => 'superadmin',
]);
}
2026-07-07 17:42:09 +03:30
$deposit = SmsWalletService::setDepositInfo($data);
return response()->json([
'message' => 'اطلاعات واریز به‌روزرسانی شد.',
'deposit' => $deposit,
]);
}
2026-07-04 11:55:10 +03:30
public function charge(Request $request)
{
$data = $request->validate([
'amount' => 'required|integer|min:1',
'note' => 'nullable|string|max:500',
]);
if ($this->useSuperAdmin()) {
$result = $this->sa()->charge((int) $data['amount'], $data['note'] ?? null);
if ($result === null) {
$this->abortSaUnavailable();
}
$this->mirrorLocalFromSa($result);
return response()->json([
'message' => (string) ($result['message'] ?? 'کیف پول با موفقیت شارژ شد.'),
'balance' => (int) ($result['balance'] ?? 0),
'source' => 'superadmin',
]);
}
2026-07-04 11:55:10 +03:30
$wallet = app(SmsWalletService::class)->charge(
(int) $data['amount'],
auth()->id(),
$data['note'] ?? null
);
return response()->json([
'message' => 'کیف پول با موفقیت شارژ شد.',
'balance' => (int) $wallet->balance,
]);
}
public function updatePrice(Request $request)
{
$data = $request->validate([
'price_per_part' => 'required|integer|min:0',
]);
if ($this->useSuperAdmin()) {
$result = $this->sa()->updatePrice((int) $data['price_per_part']);
if ($result === null) {
$this->abortSaUnavailable();
}
$this->mirrorLocalFromSa($result);
return response()->json([
'message' => (string) ($result['message'] ?? 'قیمت هر پارت به‌روزرسانی شد.'),
'price_per_part' => (int) ($result['price_per_part'] ?? 0),
'source' => 'superadmin',
]);
}
2026-07-04 11:55:10 +03:30
$wallet = app(SmsWalletService::class)->updatePricePerPart((int) $data['price_per_part']);
return response()->json([
'message' => 'قیمت هر پارت به‌روزرسانی شد.',
'price_per_part' => (int) $wallet->price_per_part,
]);
}
public function updateBlock(Request $request)
{
$data = $request->validate([
'is_blocked' => 'required|boolean',
]);
if ($this->useSuperAdmin()) {
$result = $this->sa()->setBlocked((bool) $data['is_blocked']);
if ($result === null) {
$this->abortSaUnavailable();
}
$this->mirrorLocalFromSa($result);
return response()->json([
'message' => (string) ($result['message'] ?? ''),
'is_blocked' => (bool) ($result['is_blocked'] ?? false),
'source' => 'superadmin',
]);
}
2026-07-04 11:55:10 +03:30
$wallet = app(SmsWalletService::class)->setBlocked((bool) $data['is_blocked']);
return response()->json([
'message' => $wallet->is_blocked ? 'ارسال پیامک مسدود شد.' : 'ارسال پیامک فعال شد.',
'is_blocked' => (bool) $wallet->is_blocked,
]);
}
public function logsTable(Request $request)
{
if ($this->useSuperAdmin()) {
$data = $this->sa()->logsTable($request->all());
if ($data === null) {
$this->abortSaUnavailable();
}
return response()->json($data);
}
2026-07-04 11:55:10 +03:30
$records = SmsSendLog::query()->orderByDesc('id');
if ($request->filled('from_date')) {
$records->whereDate('created_at', '>=', $request->input('from_date'));
}
if ($request->filled('to_date')) {
$records->whereDate('created_at', '<=', $request->input('to_date'));
}
$table = TableEngine::create($records, $request->all())
->initSearch(['receiver', 'body', 'provider', 'source'])
->select([
'id',
'receiver',
'body',
'parts',
'price_per_part',
'total_amount',
'provider',
'source',
'is_alert',
'created_at',
]);
return response()->json($table->export());
}
public function ledgerTable(Request $request)
{
if ($this->useSuperAdmin()) {
$data = $this->sa()->ledgerTable($request->all());
if ($data === null) {
$this->abortSaUnavailable();
}
return response()->json($data);
}
2026-07-04 11:55:10 +03:30
$records = SmsLedger::query()->orderByDesc('id');
if ($request->filled('from_date')) {
$records->whereDate('created_at', '>=', $request->input('from_date'));
}
if ($request->filled('to_date')) {
$records->whereDate('created_at', '<=', $request->input('to_date'));
}
$table = TableEngine::create($records, $request->all())
->initSearch(['type', 'note'])
->select([
'id',
'type',
'amount',
'balance_after',
'price_per_part',
'parts',
'note',
'created_by',
'created_at',
]);
return response()->json($table->export());
}
public function report(Request $request)
{
$from = $request->input('from_date', Carbon::now()->startOfMonth()->toDateString());
$to = $request->input('to_date', Carbon::now()->toDateString());
if ($this->useSuperAdmin()) {
$data = $this->sa()->report($from, $to);
if ($data === null) {
$this->abortSaUnavailable();
}
return response()->json($data);
}
2026-07-04 11:55:10 +03:30
$summary = SmsSendLog::query()
->whereDate('created_at', '>=', $from)
->whereDate('created_at', '<=', $to)
->selectRaw('COALESCE(SUM(parts), 0) as total_parts, COALESCE(SUM(total_amount), 0) as total_amount, COUNT(*) as total_messages')
->first();
$byProvider = SmsSendLog::query()
->whereDate('created_at', '>=', $from)
->whereDate('created_at', '<=', $to)
->select('provider', DB::raw('COUNT(*) as messages'), DB::raw('SUM(parts) as parts'), DB::raw('SUM(total_amount) as amount'))
->groupBy('provider')
->get();
$wallet = app(SmsWalletService::class)->getWallet();
return response()->json([
'from_date' => $from,
'to_date' => $to,
'balance' => (int) $wallet->balance,
'summary' => [
'parts' => (int) ($summary->total_parts ?? 0),
'amount' => (int) ($summary->total_amount ?? 0),
'messages' => (int) ($summary->total_messages ?? 0),
],
'by_provider' => $byProvider,
]);
}
public function chargeReport(Request $request)
{
$request->validate([
'from_date' => 'nullable|date',
'to_date' => 'nullable|date',
]);
if ($this->useSuperAdmin()) {
$data = $this->sa()->chargeReport(
$request->input('from_date'),
$request->input('to_date')
);
if ($data === null) {
$this->abortSaUnavailable();
}
return response()->json($data);
}
2026-07-04 11:55:10 +03:30
$baseQuery = SmsLedger::query()->where('type', SmsWalletService::TYPE_CHARGE);
if ($request->filled('from_date')) {
$baseQuery->whereDate('created_at', '>=', $request->input('from_date'));
}
if ($request->filled('to_date')) {
$baseQuery->whereDate('created_at', '<=', $request->input('to_date'));
}
$summary = (clone $baseQuery)
->selectRaw('COUNT(*) as total_charges, COALESCE(SUM(amount), 0) as total_amount')
->first();
$items = (clone $baseQuery)
->orderByDesc('created_at')
->select([
'id',
'amount',
'balance_after',
'note',
'created_by',
'created_at',
])
->get();
return response()->json([
'from_date' => $request->input('from_date'),
'to_date' => $request->input('to_date'),
'summary' => [
'charges' => (int) ($summary->total_charges ?? 0),
'amount' => (int) ($summary->total_amount ?? 0),
],
'items' => $items,
]);
}
}