Publication initiale de RelaisSMS : documentation et sources nettoyees
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\SourceSyncRun;
|
||||
use App\Services\Calendar\CalendarEventSyncService;
|
||||
use App\Services\Calendar\CalendarReminderSender;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Throwable;
|
||||
|
||||
class ProcessCalendarRemindersCommand extends Command
|
||||
{
|
||||
protected $signature = 'calendar:process-reminders
|
||||
{--no-sync : Ne synchronise pas l agenda avant traitement}
|
||||
{--force-sync : Force la synchronisation CalDAV avant traitement}';
|
||||
|
||||
protected $description = 'Synchronise l agenda CalDAV et envoie les rappels SMS dus.';
|
||||
|
||||
public function handle(CalendarEventSyncService $syncService, CalendarReminderSender $sender): int
|
||||
{
|
||||
if ($this->shouldSync()) {
|
||||
try {
|
||||
$run = $syncService->sync();
|
||||
$this->line((string) $run->message);
|
||||
} catch (Throwable $exception) {
|
||||
$this->error('Synchronisation CalDAV agenda en echec : '.$exception->getMessage());
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
} elseif (! $this->option('no-sync')) {
|
||||
$interval = (int) config('relais_sms.caldav.sync_interval_minutes', 60);
|
||||
$this->line('Synchronisation CalDAV ignoree : derniere synchronisation recente, intervalle '.$interval.' minute(s).');
|
||||
}
|
||||
|
||||
$sent = $sender->sendDue();
|
||||
$this->info($sent.' rappel(s) SMS envoye(s).');
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
private function shouldSync(): bool
|
||||
{
|
||||
if ($this->option('no-sync')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->option('force-sync')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$interval = max(1, (int) config('relais_sms.caldav.sync_interval_minutes', 60));
|
||||
$lastRun = SourceSyncRun::query()
|
||||
->where('source', 'caldav_calendar')
|
||||
->latest('started_at')
|
||||
->first();
|
||||
|
||||
$rawStartedAt = $lastRun?->getRawOriginal('started_at');
|
||||
|
||||
if ($rawStartedAt === null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$lastStartedAt = Carbon::parse((string) $rawStartedAt, 'UTC');
|
||||
$nextAllowedSyncAt = Carbon::now()->utc()->subMinutes($interval);
|
||||
|
||||
return $lastStartedAt->lessThanOrEqualTo($nextAllowedSyncAt);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\SmsCampaign;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class PruneSmsHistoryCommand extends Command
|
||||
{
|
||||
protected $signature = 'sms-campaigns:prune-history {--dry-run : Compte les campagnes expirees sans les supprimer.}';
|
||||
|
||||
protected $description = 'Supprime les campagnes SMS plus anciennes que la duree de retention configuree.';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$retentionYears = (int) config('relais_sms.default_settings.history_retention_years', 3);
|
||||
|
||||
if ($retentionYears < 1) {
|
||||
$this->error('La retention doit etre superieure ou egale a 1 an.');
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$expiresBefore = now()->subYears($retentionYears);
|
||||
$query = SmsCampaign::query()->where('created_at', '<', $expiresBefore);
|
||||
$count = $query->count();
|
||||
|
||||
if ($this->option('dry-run')) {
|
||||
$this->line($count.' campagne(s) SMS expiree(s) avant '.$expiresBefore->format('Y-m-d H:i:s').'.');
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
$query->delete();
|
||||
$this->line($count.' campagne(s) SMS supprimee(s).');
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\SmsCampaign;
|
||||
use App\Services\Sms\SmsCampaignReconciler;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class ReconcileSmsCampaignsCommand extends Command
|
||||
{
|
||||
protected $signature = 'sms-campaigns:reconcile {--limit=50 : Nombre maximum de campagnes a verifier.}';
|
||||
|
||||
protected $description = 'Verifie les envois incertains dans l historique OVH sans renvoyer de SMS.';
|
||||
|
||||
public function handle(SmsCampaignReconciler $reconciler): int
|
||||
{
|
||||
$processed = 0;
|
||||
SmsCampaign::query()
|
||||
->where('provider', 'ovh')
|
||||
->where(function (Builder $query): void {
|
||||
$query->where('status', 'uncertain')
|
||||
->orWhere(fn (Builder $query): Builder => $query->where('status', 'sending')->where('updated_at', '<', now()->subMinutes(10)));
|
||||
})
|
||||
->orderBy('updated_at')
|
||||
->limit(max(1, (int) $this->option('limit')))
|
||||
->get()
|
||||
->each(function (SmsCampaign $campaign) use ($reconciler, &$processed): void {
|
||||
if ($campaign->status === 'sending') {
|
||||
$campaign->update([
|
||||
'status' => 'uncertain',
|
||||
'error_message' => 'Envoi interrompu avant confirmation. Verification OVH necessaire ; ne pas renvoyer cette campagne.',
|
||||
]);
|
||||
$campaign->deliveries()->where('status', 'queued')->update(['status' => 'uncertain']);
|
||||
}
|
||||
$reconciler->reconcile($campaign);
|
||||
$processed++;
|
||||
});
|
||||
|
||||
$this->line($processed.' campagne(s) verifiee(s), sans nouvel envoi.');
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\SmsCampaign;
|
||||
use App\Services\Sms\SmsCampaignSender;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class SendDueSmsCampaignsCommand extends Command
|
||||
{
|
||||
protected $signature = 'sms-campaigns:send-due {--limit=50 : Nombre maximum de campagnes a prendre en charge.}';
|
||||
|
||||
protected $description = 'Envoie les campagnes SMS programmees arrivees a echeance.';
|
||||
|
||||
public function handle(SmsCampaignSender $sender): int
|
||||
{
|
||||
$limit = max(1, (int) $this->option('limit'));
|
||||
$processed = 0;
|
||||
|
||||
SmsCampaign::query()
|
||||
->where('status', 'scheduled')
|
||||
->whereNotNull('scheduled_at')
|
||||
->where('scheduled_at', '<=', now())
|
||||
->orderBy('scheduled_at')
|
||||
->limit($limit)
|
||||
->get()
|
||||
->each(function (SmsCampaign $campaign) use ($sender, &$processed): void {
|
||||
if ($sender->sendScheduled($campaign) !== null) {
|
||||
$processed++;
|
||||
}
|
||||
});
|
||||
|
||||
$this->line($processed.' campagne(s) SMS programmee(s) prise(s) en charge.');
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Contracts\PhoneNumberNormalizerInterface;
|
||||
use App\Models\Contact;
|
||||
use App\Models\ContactPhoneNumber;
|
||||
use App\Models\User;
|
||||
use App\Services\Sms\SmsCampaignSender;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class SendTestSmsCommand extends Command
|
||||
{
|
||||
protected $signature = 'sms:send-test {phone} {--message=Test RelaisSMS}';
|
||||
|
||||
protected $description = 'Cree le contact de test si besoin et envoie un SMS uniquement a ce numero.';
|
||||
|
||||
public function handle(PhoneNumberNormalizerInterface $normalizer, SmsCampaignSender $sender): int
|
||||
{
|
||||
$phone = (string) $this->argument('phone');
|
||||
$message = trim((string) $this->option('message'));
|
||||
$normalized = $normalizer->normalize($phone, (string) config('relais_sms.default_settings.default_country', 'FR'));
|
||||
|
||||
if (! $normalized->isValid || ! $normalized->isMobile || $normalized->normalizedNumber === null) {
|
||||
$this->error($normalized->error ?: 'Numero invalide ou non mobile.');
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
if ($message === '') {
|
||||
$this->error('Message vide.');
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
[$contact, $phoneNumber] = DB::transaction(function () use ($normalized): array {
|
||||
$phoneNumber = ContactPhoneNumber::query()
|
||||
->where('normalized_number', $normalized->normalizedNumber)
|
||||
->with('contact')
|
||||
->first();
|
||||
|
||||
if ($phoneNumber !== null && $phoneNumber->contact !== null) {
|
||||
return [$phoneNumber->contact, $phoneNumber];
|
||||
}
|
||||
|
||||
$contact = Contact::query()->create([
|
||||
'source' => 'local',
|
||||
'first_name' => 'Test',
|
||||
'last_name' => 'RelaisSMS',
|
||||
'notes' => 'Contact cree pour test envoi SMS.',
|
||||
]);
|
||||
|
||||
$phoneNumber = $contact->phoneNumbers()->create([
|
||||
'label' => 'Test',
|
||||
'raw_number' => $normalized->rawNumber,
|
||||
'normalized_number' => $normalized->normalizedNumber,
|
||||
'country' => $normalized->country,
|
||||
'is_valid' => $normalized->isValid,
|
||||
'is_mobile' => $normalized->isMobile,
|
||||
'is_primary' => true,
|
||||
'validation_error' => $normalized->error,
|
||||
]);
|
||||
|
||||
return [$contact, $phoneNumber];
|
||||
});
|
||||
|
||||
$user = User::query()->first();
|
||||
|
||||
if ($user === null) {
|
||||
$this->error('Aucun utilisateur applicatif disponible.');
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$campaign = $sender->send($user, $message, [[
|
||||
'contact_id' => $contact->id,
|
||||
'contact_name' => $contact->displayName(),
|
||||
'phone_number_id' => $phoneNumber->id,
|
||||
'number' => $normalized->normalizedNumber,
|
||||
'raw_number' => $normalized->rawNumber,
|
||||
'label' => $phoneNumber->label,
|
||||
'selection' => ['Test CLI'],
|
||||
]]);
|
||||
|
||||
$this->line('Campagne #'.$campaign->id.' : '.$campaign->status);
|
||||
|
||||
if ($campaign->error_message !== null) {
|
||||
$this->error($campaign->error_message);
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
return $campaign->status === 'failed' ? self::FAILURE : self::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Services\Calendar\CalendarEventSyncService;
|
||||
use Illuminate\Console\Command;
|
||||
use Throwable;
|
||||
|
||||
class SyncCalDavCalendarCommand extends Command
|
||||
{
|
||||
protected $signature = 'calendar:sync-caldav';
|
||||
|
||||
protected $description = 'Synchronise les evenements depuis l agenda CalDAV configure.';
|
||||
|
||||
public function handle(CalendarEventSyncService $syncService): int
|
||||
{
|
||||
try {
|
||||
$run = $syncService->sync();
|
||||
} catch (Throwable $exception) {
|
||||
$this->error('Synchronisation CalDAV agenda en echec : '.$exception->getMessage());
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$this->info((string) $run->message);
|
||||
$this->line('Objets lus : '.$run->fetched_contacts);
|
||||
$this->line('Evenements synchronises : '.$run->synced_contacts);
|
||||
$this->line('Evenements annules localement : '.$run->deleted_contacts);
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Services\CardDav\CardDavContactSyncService;
|
||||
use Illuminate\Console\Command;
|
||||
use Throwable;
|
||||
|
||||
class SyncCardDavContactsCommand extends Command
|
||||
{
|
||||
protected $signature = 'contacts:sync-carddav';
|
||||
|
||||
protected $description = 'Synchronise les contacts depuis le carnet CardDAV configure.';
|
||||
|
||||
public function handle(CardDavContactSyncService $syncService): int
|
||||
{
|
||||
try {
|
||||
$run = $syncService->sync();
|
||||
} catch (Throwable $exception) {
|
||||
$this->error('Synchronisation CardDAV en echec : '.$exception->getMessage());
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$this->info((string) $run->message);
|
||||
$this->line('Contacts lus : '.$run->fetched_contacts);
|
||||
$this->line('Contacts synchronises : '.$run->synced_contacts);
|
||||
$this->line('Contacts supprimes localement : '.$run->deleted_contacts);
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user