SMS service
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\SmsLog;
|
||||
use App\Models\SmsProfile;
|
||||
use App\Models\SmsTemplate;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class SmsLogController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
$query = SmsLog::query()->with(['profile:id,name', 'template:id,name,slug']);
|
||||
|
||||
// Filters
|
||||
$status = $request->string('status')->toString();
|
||||
$profileId = $request->integer('profile_id');
|
||||
$templateId = $request->integer('template_id');
|
||||
$search = trim((string) $request->input('search', ''));
|
||||
$from = $request->date('from');
|
||||
$to = $request->date('to');
|
||||
|
||||
if ($status !== '') {
|
||||
$query->where('status', $status);
|
||||
}
|
||||
if ($profileId) {
|
||||
$query->where('profile_id', $profileId);
|
||||
}
|
||||
if ($templateId) {
|
||||
$query->where('template_id', $templateId);
|
||||
}
|
||||
if ($search !== '') {
|
||||
$query->where(function ($q) use ($search): void {
|
||||
$q->where('to_number', 'ILIKE', "%$search%")
|
||||
->orWhere('sender', 'ILIKE', "%$search%")
|
||||
->orWhere('provider_message_id', 'ILIKE', "%$search%")
|
||||
->orWhere('message', 'ILIKE', "%$search%");
|
||||
});
|
||||
}
|
||||
if ($from) {
|
||||
$query->whereDate('created_at', '>=', $from);
|
||||
}
|
||||
if ($to) {
|
||||
$query->whereDate('created_at', '<=', $to);
|
||||
}
|
||||
|
||||
$logs = $query->orderByDesc('id')->paginate(20)->withQueryString();
|
||||
|
||||
if ($request->wantsJson() || $request->expectsJson()) {
|
||||
return response()->json($logs);
|
||||
}
|
||||
|
||||
$profiles = SmsProfile::query()->orderBy('name')->get(['id', 'name']);
|
||||
$templates = SmsTemplate::query()->orderBy('name')->get(['id', 'name', 'slug']);
|
||||
|
||||
return Inertia::render('Admin/SmsLogs/Index', [
|
||||
'logs' => $logs,
|
||||
'profiles' => $profiles,
|
||||
'templates' => $templates,
|
||||
'filters' => [
|
||||
'status' => $status ?: null,
|
||||
'profile_id' => $profileId ?: null,
|
||||
'template_id' => $templateId ?: null,
|
||||
'search' => $search ?: null,
|
||||
'from' => $from ? $from->format('Y-m-d') : null,
|
||||
'to' => $to ? $to->format('Y-m-d') : null,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function show(SmsLog $smsLog)
|
||||
{
|
||||
$smsLog->load(['profile:id,name', 'template:id,name,slug']);
|
||||
|
||||
return Inertia::render('Admin/SmsLogs/Show', [
|
||||
'log' => $smsLog,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreSmsProfileRequest;
|
||||
use App\Http\Requests\TestSendSmsRequest;
|
||||
use App\Jobs\SendSmsJob;
|
||||
use App\Models\SmsProfile;
|
||||
use App\Models\SmsSender;
|
||||
use App\Services\Sms\SmsService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Str;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class SmsProfileController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
$profiles = SmsProfile::query()->with(['senders:id,profile_id,sname,active'])->orderBy('name')->get([
|
||||
'id', 'uuid', 'name', 'active', 'api_username', 'default_sender_id', 'settings', 'created_at', 'updated_at',
|
||||
]);
|
||||
|
||||
// Inertia requests must receive an Inertia response
|
||||
if ($request->headers->has('X-Inertia')) {
|
||||
return Inertia::render('Admin/SmsProfiles/Index', [
|
||||
'initialProfiles' => $profiles,
|
||||
]);
|
||||
}
|
||||
|
||||
// JSON/AJAX API
|
||||
if ($request->wantsJson() || $request->expectsJson()) {
|
||||
return response()->json(['profiles' => $profiles]);
|
||||
}
|
||||
|
||||
// Default to Inertia page for normal browser navigation
|
||||
return Inertia::render('Admin/SmsProfiles/Index', [
|
||||
'initialProfiles' => $profiles,
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(StoreSmsProfileRequest $request)
|
||||
{
|
||||
$data = $request->validated();
|
||||
$profile = new SmsProfile;
|
||||
$profile->uuid = (string) Str::uuid();
|
||||
$profile->name = $data['name'];
|
||||
$profile->active = (bool) ($data['active'] ?? true);
|
||||
$profile->api_username = $data['api_username'];
|
||||
// write-only attribute setter will encrypt and store to encrypted_api_password
|
||||
$profile->api_password = $data['api_password'];
|
||||
$profile->save();
|
||||
|
||||
if ($request->wantsJson() || $request->expectsJson()) {
|
||||
return response()->json(['profile' => $profile], 201);
|
||||
}
|
||||
|
||||
return back()->with('success', 'SMS profil je ustvarjen.');
|
||||
}
|
||||
|
||||
public function testSend(SmsProfile $profile, TestSendSmsRequest $request, SmsService $sms)
|
||||
{
|
||||
$data = $request->validated();
|
||||
$sender = null;
|
||||
if (! empty($data['sender_id'])) {
|
||||
$sender = SmsSender::query()->where('id', $data['sender_id'])->where('profile_id', $profile->id)->firstOrFail();
|
||||
}
|
||||
|
||||
// Queue the SMS send (admin test send - no activity created)
|
||||
SendSmsJob::dispatch(
|
||||
profileId: $profile->id,
|
||||
to: (string) $data['to'],
|
||||
content: (string) $data['message'],
|
||||
senderId: $sender?->id,
|
||||
countryCode: $data['country_code'] ?? null,
|
||||
deliveryReport: (bool) ($data['delivery_report'] ?? false),
|
||||
clientReference: null,
|
||||
);
|
||||
|
||||
if ($request->wantsJson() || $request->expectsJson()) {
|
||||
return response()->json(['queued' => true]);
|
||||
}
|
||||
|
||||
return back()->with('success', 'Testni SMS je bil dodan v čakalno vrsto.');
|
||||
}
|
||||
|
||||
public function balance(SmsProfile $smsProfile, SmsService $sms)
|
||||
{
|
||||
try {
|
||||
$balance = (string) $sms->getCreditBalance($smsProfile);
|
||||
|
||||
return response()->json(['balance' => $balance]);
|
||||
} catch (\Throwable $e) {
|
||||
// Return a graceful payload so UI doesn't break; also include message for optional UI/tooling
|
||||
return response()->json([
|
||||
'balance' => '—',
|
||||
'error' => 'Unable to fetch balance: '.$e->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function price(SmsProfile $smsProfile, SmsService $sms)
|
||||
{
|
||||
$quotes = $sms->getPriceQuotes($smsProfile);
|
||||
|
||||
return response()->json(['quotes' => $quotes]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreSmsSenderRequest;
|
||||
use App\Http\Requests\UpdateSmsSenderRequest;
|
||||
use App\Models\SmsProfile;
|
||||
use App\Models\SmsSender;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class SmsSenderController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
$senders = SmsSender::query()
|
||||
->with(['profile:id,name'])
|
||||
->orderBy('id', 'desc')
|
||||
->get(['id', 'profile_id', 'sname', 'phone_number', 'description', 'active', 'created_at']);
|
||||
|
||||
$profiles = SmsProfile::query()->orderBy('name')->get(['id', 'name']);
|
||||
|
||||
return Inertia::render('Admin/SmsSenders/Index', [
|
||||
'initialSenders' => $senders,
|
||||
'profiles' => $profiles,
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(StoreSmsSenderRequest $request)
|
||||
{
|
||||
$data = $request->validated();
|
||||
$sender = SmsSender::create([
|
||||
'profile_id' => $data['profile_id'],
|
||||
'sname' => $data['sname'],
|
||||
'phone_number' => $data['phone_number'] ?? null,
|
||||
'description' => $data['description'] ?? null,
|
||||
'active' => (bool) ($data['active'] ?? true),
|
||||
]);
|
||||
|
||||
if ($request->wantsJson() || $request->expectsJson()) {
|
||||
return response()->json(['sender' => $sender], 201);
|
||||
}
|
||||
|
||||
return back()->with('success', 'Pošiljatelj je ustvarjen.');
|
||||
}
|
||||
|
||||
public function update(UpdateSmsSenderRequest $request, SmsSender $smsSender)
|
||||
{
|
||||
$data = $request->validated();
|
||||
$smsSender->forceFill([
|
||||
'profile_id' => $data['profile_id'],
|
||||
'sname' => $data['sname'],
|
||||
'phone_number' => $data['phone_number'] ?? null,
|
||||
'description' => $data['description'] ?? null,
|
||||
'active' => (bool) ($data['active'] ?? $smsSender->active),
|
||||
])->save();
|
||||
|
||||
if ($request->wantsJson() || $request->expectsJson()) {
|
||||
return response()->json(['sender' => $smsSender]);
|
||||
}
|
||||
|
||||
return back()->with('success', 'Pošiljatelj je posodobljen.');
|
||||
}
|
||||
|
||||
public function toggle(Request $request, SmsSender $smsSender)
|
||||
{
|
||||
$smsSender->active = ! $smsSender->active;
|
||||
$smsSender->save();
|
||||
|
||||
if ($request->wantsJson() || $request->expectsJson()) {
|
||||
return response()->json(['sender' => $smsSender]);
|
||||
}
|
||||
|
||||
return back()->with('success', 'Stanje pošiljatelja je posodobljeno.');
|
||||
}
|
||||
|
||||
public function destroy(Request $request, SmsSender $smsSender)
|
||||
{
|
||||
$smsSender->delete();
|
||||
|
||||
if ($request->wantsJson() || $request->expectsJson()) {
|
||||
return response()->json(['deleted' => true]);
|
||||
}
|
||||
|
||||
return back()->with('success', 'Pošiljatelj je izbrisan.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreSmsTemplateRequest;
|
||||
use App\Http\Requests\TestSendSmsTemplateRequest;
|
||||
use App\Http\Requests\UpdateSmsTemplateRequest;
|
||||
use App\Models\SmsProfile;
|
||||
use App\Models\SmsSender;
|
||||
use App\Models\SmsTemplate;
|
||||
use App\Services\Sms\SmsService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Str;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class SmsTemplateController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
$templates = SmsTemplate::query()
|
||||
->with(['defaultProfile:id,name', 'defaultSender:id,sname'])
|
||||
->orderBy('name')
|
||||
->get(['id', 'uuid', 'name', 'slug', 'content', 'variables_json', 'is_active', 'default_profile_id', 'default_sender_id', 'created_at']);
|
||||
|
||||
$profiles = SmsProfile::query()->orderBy('name')->get(['id', 'name']);
|
||||
$senders = SmsSender::query()->orderBy('sname')->get(['id', 'profile_id', 'sname', 'active']);
|
||||
|
||||
if ($request->wantsJson() || $request->expectsJson()) {
|
||||
return response()->json([
|
||||
'templates' => $templates,
|
||||
'profiles' => $profiles,
|
||||
'senders' => $senders,
|
||||
]);
|
||||
}
|
||||
|
||||
return Inertia::render('Admin/SmsTemplates/Index', [
|
||||
'initialTemplates' => $templates,
|
||||
'profiles' => $profiles,
|
||||
'senders' => $senders,
|
||||
]);
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
$profiles = SmsProfile::query()->orderBy('name')->get(['id', 'name']);
|
||||
$senders = SmsSender::query()->orderBy('sname')->get(['id', 'profile_id', 'sname', 'active']);
|
||||
$actions = \App\Models\Action::query()
|
||||
->with(['decisions:id,name'])
|
||||
->orderBy('name')
|
||||
->get(['id', 'name']);
|
||||
|
||||
return Inertia::render('Admin/SmsTemplates/Edit', [
|
||||
'template' => null,
|
||||
'profiles' => $profiles,
|
||||
'senders' => $senders,
|
||||
'actions' => $actions,
|
||||
]);
|
||||
}
|
||||
|
||||
public function edit(SmsTemplate $smsTemplate)
|
||||
{
|
||||
$profiles = SmsProfile::query()->orderBy('name')->get(['id', 'name']);
|
||||
$senders = SmsSender::query()->orderBy('sname')->get(['id', 'profile_id', 'sname', 'active']);
|
||||
$actions = \App\Models\Action::query()
|
||||
->with(['decisions:id,name'])
|
||||
->orderBy('name')
|
||||
->get(['id', 'name']);
|
||||
|
||||
return Inertia::render('Admin/SmsTemplates/Edit', [
|
||||
'template' => $smsTemplate->only(['id', 'uuid', 'name', 'slug', 'content', 'variables_json', 'is_active', 'default_profile_id', 'default_sender_id', 'allow_custom_body', 'action_id', 'decision_id']),
|
||||
'profiles' => $profiles,
|
||||
'senders' => $senders,
|
||||
'actions' => $actions,
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(StoreSmsTemplateRequest $request)
|
||||
{
|
||||
$data = $request->validated();
|
||||
$tpl = new SmsTemplate;
|
||||
$tpl->uuid = (string) Str::uuid();
|
||||
$tpl->name = $data['name'];
|
||||
$tpl->slug = $data['slug'];
|
||||
$tpl->content = $data['content'] ?? '';
|
||||
$tpl->variables_json = $data['variables_json'] ?? null;
|
||||
$tpl->is_active = (bool) ($data['is_active'] ?? true);
|
||||
$tpl->default_profile_id = $data['default_profile_id'] ?? null;
|
||||
$tpl->default_sender_id = $data['default_sender_id'] ?? null;
|
||||
$tpl->allow_custom_body = (bool) ($data['allow_custom_body'] ?? false);
|
||||
$tpl->action_id = $data['action_id'] ?? null;
|
||||
$tpl->decision_id = $data['decision_id'] ?? null;
|
||||
$tpl->save();
|
||||
|
||||
if ($request->wantsJson() || $request->expectsJson()) {
|
||||
return response()->json(['template' => $tpl], 201);
|
||||
}
|
||||
|
||||
return redirect()->route('admin.sms-templates.edit', $tpl);
|
||||
}
|
||||
|
||||
public function update(UpdateSmsTemplateRequest $request, SmsTemplate $smsTemplate)
|
||||
{
|
||||
$data = $request->validated();
|
||||
$smsTemplate->forceFill([
|
||||
'name' => $data['name'],
|
||||
'slug' => $data['slug'],
|
||||
'content' => $data['content'] ?? '',
|
||||
'variables_json' => $data['variables_json'] ?? null,
|
||||
'is_active' => (bool) ($data['is_active'] ?? $smsTemplate->is_active),
|
||||
'default_profile_id' => $data['default_profile_id'] ?? null,
|
||||
'default_sender_id' => $data['default_sender_id'] ?? null,
|
||||
'allow_custom_body' => (bool) ($data['allow_custom_body'] ?? $smsTemplate->allow_custom_body),
|
||||
'action_id' => $data['action_id'] ?? null,
|
||||
'decision_id' => $data['decision_id'] ?? null,
|
||||
])->save();
|
||||
|
||||
if ($request->wantsJson() || $request->expectsJson()) {
|
||||
return response()->json(['template' => $smsTemplate]);
|
||||
}
|
||||
|
||||
return back()->with('success', 'SMS predloga je posodobljena.');
|
||||
}
|
||||
|
||||
public function toggle(Request $request, SmsTemplate $smsTemplate)
|
||||
{
|
||||
$smsTemplate->is_active = ! $smsTemplate->is_active;
|
||||
$smsTemplate->save();
|
||||
|
||||
if ($request->wantsJson() || $request->expectsJson()) {
|
||||
return response()->json(['template' => $smsTemplate]);
|
||||
}
|
||||
|
||||
return back()->with('success', 'Stanje predloge je posodobljeno.');
|
||||
}
|
||||
|
||||
public function destroy(Request $request, SmsTemplate $smsTemplate)
|
||||
{
|
||||
$smsTemplate->delete();
|
||||
|
||||
if ($request->wantsJson() || $request->expectsJson()) {
|
||||
return response()->json(['deleted' => true]);
|
||||
}
|
||||
|
||||
return back()->with('success', 'Predloga je izbrisana.');
|
||||
}
|
||||
|
||||
public function sendTest(TestSendSmsTemplateRequest $request, SmsTemplate $smsTemplate, SmsService $sms)
|
||||
{
|
||||
$data = $request->validated();
|
||||
|
||||
$profile = null;
|
||||
if (! empty($data['profile_id'])) {
|
||||
$profile = SmsProfile::query()->findOrFail($data['profile_id']);
|
||||
}
|
||||
$sender = null;
|
||||
if (! empty($data['sender_id'])) {
|
||||
$sender = SmsSender::query()->findOrFail($data['sender_id']);
|
||||
}
|
||||
|
||||
$variables = (array) ($data['variables'] ?? []);
|
||||
|
||||
if (! empty($data['custom_content']) && $smsTemplate->allow_custom_body) {
|
||||
// Use custom content when allowed
|
||||
if (! $profile) {
|
||||
$profile = $smsTemplate->defaultProfile;
|
||||
}
|
||||
if (! $profile) {
|
||||
throw new \InvalidArgumentException('SMS profile is required to send a message.');
|
||||
}
|
||||
$log = $sms->sendRaw(
|
||||
profile: $profile,
|
||||
to: $data['to'],
|
||||
content: (string) $data['custom_content'],
|
||||
sender: $sender,
|
||||
countryCode: $data['country_code'] ?? null,
|
||||
deliveryReport: (bool) ($data['delivery_report'] ?? false),
|
||||
);
|
||||
$log->template_id = $smsTemplate->id;
|
||||
$log->save();
|
||||
} else {
|
||||
$log = $sms->sendFromTemplate(
|
||||
template: $smsTemplate,
|
||||
to: $data['to'],
|
||||
variables: $variables,
|
||||
profile: $profile,
|
||||
sender: $sender,
|
||||
countryCode: $data['country_code'] ?? null,
|
||||
deliveryReport: (bool) ($data['delivery_report'] ?? false),
|
||||
);
|
||||
}
|
||||
|
||||
if ($request->wantsJson() || $request->expectsJson()) {
|
||||
return response()->json(['log' => $log]);
|
||||
}
|
||||
|
||||
return back()->with('success', 'Testni SMS je bil poslan.');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user