SMS service

This commit is contained in:
Simon Pocrnjič
2025-10-24 21:39:10 +02:00
parent 3a2eed7dda
commit 930ac83604
52 changed files with 3830 additions and 36 deletions
@@ -1343,6 +1343,22 @@ function ($p) {
'segments' => $case->segments()->wherePivot('active', true)->get(['segments.id', 'segments.name']),
'all_segments' => \App\Models\Segment::query()->where('active', true)->get(['id', 'name']),
'current_segment' => $currentSegment,
// SMS helpers for per-case sending UI
'sms_profiles' => \App\Models\SmsProfile::query()
->select(['id', 'name', 'default_sender_id'])
->where('active', true)
->orderBy('name')
->get(),
'sms_senders' => \App\Models\SmsSender::query()
->select(['id', 'profile_id'])
->addSelect(\DB::raw('sname as name'))
->addSelect(\DB::raw('phone_number as phone'))
->orderBy('sname')
->get(),
'sms_templates' => \App\Models\SmsTemplate::query()
->select(['id', 'name', 'content', 'allow_custom_body'])
->orderBy('name')
->get(),
]);
}
@@ -1665,4 +1681,124 @@ public function emergencyCreatePerson(ClientCase $clientCase, Request $request)
'person_uuid' => $newPerson?->uuid,
]);
}
/**
* Send an SMS to a specific phone that belongs to the client case person.
*/
public function sendSmsToPhone(ClientCase $clientCase, Request $request, int $phone_id)
{
$validated = $request->validate([
'message' => ['required', 'string', 'max:1000'],
'delivery_report' => ['sometimes', 'boolean'],
'template_id' => ['sometimes', 'nullable', 'integer', 'exists:sms_templates,id'],
'profile_id' => ['sometimes', 'nullable', 'integer', 'exists:sms_profiles,id'],
'sender_id' => ['sometimes', 'nullable', 'integer', 'exists:sms_senders,id'],
]);
// Ensure the phone belongs to the person of this case
/** @var \App\Models\Person\PersonPhone|null $phone */
$phone = \App\Models\Person\PersonPhone::query()
->where('id', $phone_id)
->where('person_id', $clientCase->person_id)
->first();
if (! $phone) {
abort(404);
}
// Resolve explicit profile/sender if provided; otherwise fallback to first active profile and its default sender
/** @var \App\Models\SmsProfile|null $profile */
$profile = null;
/** @var \App\Models\SmsSender|null $sender */
$sender = null;
if (! empty($validated['sender_id']) && empty($validated['profile_id'])) {
// Infer profile from sender if not explicitly provided
$sender = \App\Models\SmsSender::query()->find($validated['sender_id']);
if ($sender) {
$profile = \App\Models\SmsProfile::query()->find($sender->profile_id);
}
}
if (! empty($validated['profile_id'])) {
$profile = \App\Models\SmsProfile::query()->where('id', $validated['profile_id'])->first();
if (! $profile) {
return back()->with('error', 'Izbran SMS profil ne obstaja.');
}
if (property_exists($profile, 'active') && ! $profile->active) {
return back()->with('error', 'Izbran SMS profil ni aktiven.');
}
}
if (! empty($validated['sender_id'])) {
$sender = \App\Models\SmsSender::query()->find($validated['sender_id']);
if (! $sender) {
return back()->with('error', 'Izbran pošiljatelj ne obstaja.');
}
if ($profile && (int) $sender->profile_id !== (int) $profile->id) {
return back()->with('error', 'Izbran pošiljatelj ne pripada izbranemu profilu.');
}
}
if (! $profile) {
$profile = \App\Models\SmsProfile::query()
->where('active', true)
->orderByRaw('CASE WHEN default_sender_id IS NULL THEN 1 ELSE 0 END')
->orderBy('id')
->first();
}
if (! $profile) {
return back()->with('warning', 'Ni aktivnega SMS profila.');
}
if (! $sender && ! empty($profile->default_sender_id)) {
$sender = \App\Models\SmsSender::query()->find($profile->default_sender_id);
}
try {
/** @var \App\Services\Sms\SmsService $sms */
$sms = app(\App\Services\Sms\SmsService::class);
// Check available credits before enqueueing (fail-closed)
try {
$raw = (string) $sms->getCreditBalance($profile);
$num = null;
if ($raw !== '') {
$normalized = str_replace(',', '.', trim($raw));
if (preg_match('/-?\d+(?:\.\d+)?/', $normalized, $m)) {
$num = (float) ($m[0] ?? null);
}
}
if (! is_null($num) && $num <= 0.0) {
return back()->with('error', 'No credits left.');
}
} catch (\Throwable $e) {
\Log::warning('SMS credit balance check failed', [
'error' => $e->getMessage(),
'profile_id' => $profile->id,
]);
return back()->with('error', 'Unable to verify SMS credits.');
}
// Queue the SMS send; activity will be created in the job on success if a template is provided
\App\Jobs\SendSmsJob::dispatch(
profileId: $profile->id,
to: (string) $phone->nu,
content: (string) $validated['message'],
senderId: $sender?->id,
countryCode: $phone->country_code ?: null,
deliveryReport: (bool) ($validated['delivery_report'] ?? false),
clientReference: null,
templateId: $validated['template_id'] ?? null,
clientCaseId: $clientCase->id,
userId: optional($request->user())->id,
);
return back()->with('success', 'SMS je bil dodan v čakalno vrsto.');
} catch (\Throwable $e) {
\Log::warning('SMS enqueue failed', [
'error' => $e->getMessage(),
'case_id' => $clientCase->id,
'phone_id' => $phone_id,
]);
return back()->with('error', 'SMS ni bil dodan v čakalno vrsto.');
}
}
}