feat&fix(add Listmonk service and sync part 1, fix association email on member sync)

This commit is contained in:
2026-04-07 16:52:18 +02:00
parent 703a75a11a
commit 6754d8684a
14 changed files with 298 additions and 240 deletions

View File

@@ -19,12 +19,11 @@ class HandleExpiredMembersDolibarr extends Command
{--dry-run}';
public function __construct(
protected DolibarrService $dolibarr,
protected DolibarrService $dolibarr,
protected ISPConfigMailService $mailService,
protected NextcloudService $nextcloud,
protected MemberService $memberService
)
{
protected NextcloudService $nextcloud,
protected MemberService $memberService
) {
parent::__construct();
}
@@ -65,11 +64,12 @@ class HandleExpiredMembersDolibarr extends Command
if ($emailFilter) {
$expiredMembers = $expiredMembers->filter(function ($member) use ($emailFilter) {
return $this->extractRetzienEmail($member['email'] ?? null) === $emailFilter;
return Member::extractRetzienEmail($member['email'] ?? '') === $emailFilter;
});
if ($expiredMembers->isEmpty()) {
$this->warn("Aucun adhérent expiré trouvé pour {$emailFilter}");
return CommandAlias::SUCCESS;
}
}
@@ -102,13 +102,13 @@ class HandleExpiredMembersDolibarr extends Command
*/
protected function processMember(array $member, bool $dryRun): void
{
$email = $this->extractRetzienEmail($member['email'] ?? null);
$email = Member::extractRetzienEmail($member['email'] ?? '');
$this->line("{$member['id']} - {$email}");
// Résiliation Dolibarr
if ($dryRun) {
$this->info("[DRY-RUN] Résiliation Dolibarr");
$this->info('[DRY-RUN] Résiliation Dolibarr');
} else {
$this->dolibarr->updateMember($member['id'], [
'statut' => 0,
@@ -129,7 +129,7 @@ class HandleExpiredMembersDolibarr extends Command
// Désactivation Nextcloud
if ($email) {
if ($dryRun) {
$this->info("[DRY-RUN] Désactivation Nextcloud");
$this->info('[DRY-RUN] Désactivation Nextcloud');
} else {
$this->nextcloud->disableUserByEmail($email);
}
@@ -143,13 +143,15 @@ class HandleExpiredMembersDolibarr extends Command
{
$details = $this->mailService->getMailUserDetails($email);
if (!$details) {
if (! $details) {
$this->warn("Boîte mail inexistante : {$email}");
return;
}
if ($dryRun) {
$this->info("[DRY-RUN] Mail désactivé ({$email})");
return;
}
@@ -160,22 +162,10 @@ class HandleExpiredMembersDolibarr extends Command
'disablepop3' => 'y',
]);
if (!$result) {
if (! $result) {
throw new \RuntimeException("Échec désactivation mail ISPConfig pour {$email}");
}
$this->info("Mail désactivé : {$email}");
}
protected function extractRetzienEmail(?string $emails): ?string
{
if (!$emails) {
return null;
}
return collect(explode(';', $emails))
->map(fn(string $email): string => trim($email))
->filter(fn(string $email): bool => str_contains($email, '@retzien.fr'))
->first();
}
}

View File

@@ -68,7 +68,7 @@ class SyncDolibarrMembers extends Command
'lastname' => $member['lastname'],
'firstname' => $member['firstname'],
'email' => $member['email'] ?: null,
'retzien_email' => '',
'retzien_email' => Member::extractRetzienEmail($member['email'] ?? ''),
'company' => $member['societe'],
'website_url' => $member['url'],
'address' => $member['address'],

View File

@@ -7,20 +7,22 @@ use App\Models\IspconfigMember;
use App\Models\Member;
use App\Services\ISPConfig\ISPConfigMailService;
use Illuminate\Console\Command;
use function Laravel\Prompts\progress;
class SyncISPConfigMailMembers extends Command
{
protected $signature = 'sync:ispconfig-mail-members';
protected $description = 'Synchronise les services MAIL ISPConfig des membres - Email Retzien';
public function handle(): void
{
$this->info('Synchronisation ISPConfig MAIL');
$ispMail = new ISPConfigMailService();
$ispMail = new ISPConfigMailService;
//Récupération de tous les mail users
// Récupération de tous les mail users
$mailUsers = collect($ispMail->getAllMailUsers());
$progressBar = progress(label: 'ISPConfig Mail Members import', steps: $mailUsers->count());
@@ -35,40 +37,37 @@ class SyncISPConfigMailMembers extends Command
$synced = 0;
// Parcours des membres
Member::whereNotNull('email')->chunk(100, function ($members) use (
Member::whereNotNull('retzien_email')->where('retzien_email', '!=', '')->chunk(100, function ($members) use (
$progressBar,
$emailToMailUserId,
$ispMail,
&$synced
) {
foreach ($members as $member) {
$emails = array_map('trim', explode(';', $member->email));
$retzienEmail = strtolower($member->retzien_email);
$retzienEmail = collect($emails)
->map(fn ($e) => strtolower($e))
->first(fn ($e) => str_ends_with($e, '@retzien.fr'));
if (!$retzienEmail) {
if (! $retzienEmail) {
continue;
}
$mailUserId = $emailToMailUserId->get($retzienEmail);
if (!$mailUserId) {
if (! $mailUserId) {
$this->warn("Aucun mail user ISPConfig pour {$retzienEmail}");
continue;
}
//Récupération des données complètes de la boîte mail
// Récupération des données complètes de la boîte mail
$mailUserData = $ispMail->getMailUserDetails($retzienEmail);
// Création / mise à jour
IspconfigMember::updateOrCreate(
[
'member_id' => $member->id,
//@todo : 'ispconfig_client_id' => ?,
'type' => IspconfigType::MAIL,
'email' => $retzienEmail,
// @todo : 'ispconfig_client_id' => ?,
'type' => IspconfigType::MAIL,
'email' => $retzienEmail,
],
[
'ispconfig_service_user_id' => $mailUserId,

View File

@@ -0,0 +1,119 @@
<?php
namespace App\Console\Commands;
use App\Models\ListmonkMember;
use App\Models\Member;
use App\Services\ListMonk\ListMonkService;
use Illuminate\Console\Command;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Support\Facades\Log;
use Symfony\Component\Console\Command\Command as CommandAlias;
use function Laravel\Prompts\progress;
class SyncListmonkMembers extends Command
{
protected $signature = 'listmonk:sync-members
{--dry-run : Run without writing to the database}
{--member= : Sync a single member by member_id}';
protected $description = 'Sync Listmonk user accounts with members';
public function __construct(
protected ListMonkService $listmonk
) {
parent::__construct();
}
/**
* @throws ConnectionException
*/
public function handle(): int
{
$dryRun = $this->option('dry-run');
$memberFilter = $this->option('member');
$this->info(
$dryRun
? 'DRY-RUN enabled'
: 'Syncing Listmonk → Members'
);
$members = Member::query()
->when($memberFilter, fn ($q) => $q->where('id', $memberFilter))
->get()
->filter(fn (Member $m) => ! empty($m->retzien_email))
->keyBy(fn (Member $m) => strtolower($m->retzien_email));
if ($members->isEmpty()) {
$this->warn('No members to sync');
return CommandAlias::SUCCESS;
}
$this->info("{$members->count()} members to sync");
$listmonkUsers = $this->listmonk->getUsers();
dd($listmonkUsers);
$this->info(count($listmonkUsers).' Listmonk users found');
$progress = null;
if (! $dryRun) {
$progress = progress(
label: 'Syncing members',
steps: $members->count()
);
$progress->start();
}
$synced = 0;
foreach ($listmonkUsers as $user) {
try {
$email = strtolower($user['email'] ?? '');
if (! $email || ! $members->has($email)) {
continue;
}
$member = $members[$email];
if ($dryRun) {
$this->line("[DRY-RUN] {$member->id} ({$email}) ← Listmonk user #{$user['id']}");
} else {
ListmonkMember::query()->updateOrCreate(
['member_id' => $member->id],
[
'listmonk_user_id' => $user['id'],
'data' => $user,
]
);
$progress?->advance();
}
$synced++;
} catch (\Throwable $e) {
Log::error('Listmonk sync error', [
'user' => $user['id'] ?? null,
'error' => $e->getMessage(),
]);
$progress?->advance();
}
}
if ($progress) {
$progress->finish();
$this->newLine();
}
$this->info("Sync complete — {$synced} accounts linked");
return CommandAlias::SUCCESS;
}
}

View File

@@ -9,6 +9,7 @@ use Illuminate\Console\Command;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Support\Facades\Log;
use Symfony\Component\Console\Command\Command as CommandAlias;
use function Laravel\Prompts\progress;
class SyncNextcloudMembers extends Command
@@ -40,14 +41,15 @@ class SyncNextcloudMembers extends Command
);
$members = Member::query()
->where('email', 'like', '%@retzien.fr%')
->whereNotNull('retzien_email')
->where('retzien_email', '!=', '')
->when($memberFilter, fn ($q) => $q->where('id', $memberFilter))
->get()
->filter(fn (Member $m) => !empty($m->retzien_email))
->keyBy(fn (Member $m) => strtolower($m->retzien_email));
if ($members->isEmpty()) {
$this->warn('Aucun membre à synchroniser');
return CommandAlias::SUCCESS;
}
@@ -55,11 +57,11 @@ class SyncNextcloudMembers extends Command
$userIds = $this->nextcloud->listUsers();
$this->info(count($userIds) . ' comptes Nextcloud trouvés');
$this->info(count($userIds).' comptes Nextcloud trouvés');
$progress = null;
if (!$dryRun) {
if (! $dryRun) {
$progress = progress(
label: 'Synchronisation des membres',
steps: $members->count()
@@ -75,7 +77,7 @@ class SyncNextcloudMembers extends Command
$email = strtolower($details['email'] ?? '');
if (!$email || !$members->has($email)) {
if (! $email || ! $members->has($email)) {
continue;
}