Add SMS bulk delay configuration and validate coupon phone function
This commit is contained in:
97
app/Services/SmsBroadcastService.php
Normal file
97
app/Services/SmsBroadcastService.php
Normal file
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Enums\SmsSendStatus;
|
||||
use App\Jobs\SendSmsToCouponJob;
|
||||
use App\Models\Coupon;
|
||||
use App\Models\SmsCampaign;
|
||||
use App\Models\SmsSendLog;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Support\Facades\Bus;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class SmsBroadcastService
|
||||
{
|
||||
/**
|
||||
* @param array<int, int|string> $selectedIds
|
||||
* @param array<int, int|string> $excludedIds
|
||||
* @return Collection<int, Coupon>
|
||||
*/
|
||||
public function resolveRecipients(bool $sendToAll, array $selectedIds = [], array $excludedIds = []): Collection
|
||||
{
|
||||
$excludedIds = array_values(array_unique(array_map('intval', $excludedIds)));
|
||||
|
||||
if ($sendToAll) {
|
||||
$query = Coupon::query();
|
||||
} else {
|
||||
$selectedIds = array_values(array_unique(array_map('intval', $selectedIds)));
|
||||
$query = Coupon::query()->whereIn('id', $selectedIds);
|
||||
}
|
||||
|
||||
if ($excludedIds !== []) {
|
||||
$query->whereNotIn('id', $excludedIds);
|
||||
}
|
||||
|
||||
return $query->orderBy('id')->get();
|
||||
}
|
||||
|
||||
public function startCampaign(
|
||||
User $admin,
|
||||
string $message,
|
||||
bool $sendToAll,
|
||||
array $selectedIds = [],
|
||||
array $excludedIds = [],
|
||||
): SmsCampaign {
|
||||
$recipients = $this->resolveRecipients($sendToAll, $selectedIds, $excludedIds);
|
||||
$mode = $sendToAll ? 'all' : 'selected';
|
||||
|
||||
return DB::transaction(function () use ($admin, $message, $mode, $recipients): SmsCampaign {
|
||||
$campaign = SmsCampaign::query()->create([
|
||||
'message' => $message,
|
||||
'mode' => $mode,
|
||||
'recipient_count' => $recipients->count(),
|
||||
'created_by' => $admin->id,
|
||||
]);
|
||||
|
||||
$jobs = [];
|
||||
|
||||
foreach ($recipients as $coupon) {
|
||||
if (! is_valid_coupon_phone($coupon->phone)) {
|
||||
$this->createLog($campaign, $coupon, SmsSendStatus::SkippedInvalid);
|
||||
$campaign->increment('skipped_count');
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->createLog($campaign, $coupon, SmsSendStatus::Pending);
|
||||
$jobs[] = new SendSmsToCouponJob($campaign, $coupon);
|
||||
}
|
||||
|
||||
if ($jobs === []) {
|
||||
$campaign->update(['completed_at' => now()]);
|
||||
|
||||
return $campaign->fresh();
|
||||
}
|
||||
|
||||
Bus::chain($jobs)
|
||||
->onQueue('sms')
|
||||
->dispatch();
|
||||
|
||||
return $campaign->fresh();
|
||||
});
|
||||
}
|
||||
|
||||
protected function createLog(SmsCampaign $campaign, Coupon $coupon, SmsSendStatus $status): SmsSendLog
|
||||
{
|
||||
return SmsSendLog::query()->create([
|
||||
'sms_campaign_id' => $campaign->id,
|
||||
'coupon_id' => $coupon->id,
|
||||
'phone' => $coupon->phone,
|
||||
'message' => $campaign->message,
|
||||
'status' => $status,
|
||||
'attempted_at' => $status === SmsSendStatus::Pending ? null : now(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
65
app/Services/SmsMessageAnalyzer.php
Normal file
65
app/Services/SmsMessageAnalyzer.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
class SmsMessageAnalyzer
|
||||
{
|
||||
private const GSM_SINGLE_LIMIT = 160;
|
||||
|
||||
private const GSM_MULTI_LIMIT = 153;
|
||||
|
||||
private const UCS2_SINGLE_LIMIT = 70;
|
||||
|
||||
private const UCS2_MULTI_LIMIT = 67;
|
||||
|
||||
public function characterCount(string $message): int
|
||||
{
|
||||
return mb_strlen($message);
|
||||
}
|
||||
|
||||
public function isGsm7(string $message): bool
|
||||
{
|
||||
if ($message === '') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return ! preg_match('/[^\x00-\x7F]/', $message);
|
||||
}
|
||||
|
||||
public function estimatedParts(string $message): int
|
||||
{
|
||||
$length = $this->characterCount($message);
|
||||
|
||||
if ($length === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ($this->isGsm7($message)) {
|
||||
if ($length <= self::GSM_SINGLE_LIMIT) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return (int) ceil($length / self::GSM_MULTI_LIMIT);
|
||||
}
|
||||
|
||||
if ($length <= self::UCS2_SINGLE_LIMIT) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return (int) ceil($length / self::UCS2_MULTI_LIMIT);
|
||||
}
|
||||
|
||||
public function summary(string $message): string
|
||||
{
|
||||
$characters = $this->characterCount($message);
|
||||
$parts = $this->estimatedParts($message);
|
||||
|
||||
if ($parts === 0) {
|
||||
return '0 characters';
|
||||
}
|
||||
|
||||
$partLabel = $parts === 1 ? '1 SMS' : "{$parts} SMS";
|
||||
|
||||
return "{$characters} characters · {$partLabel}";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user