feat(Reminder process): add 4 reminder phases for membership (Retzien)
All checks were successful
Deploy Roxane to Preprod / deploy (push) Successful in 1m21s
All checks were successful
Deploy Roxane to Preprod / deploy (push) Successful in 1m21s
This commit is contained in:
150
app/Console/Commands/MemberRenewalProcessCommand.php
Normal file
150
app/Console/Commands/MemberRenewalProcessCommand.php
Normal file
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Member;
|
||||
use App\Services\MemberRenewalService;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Symfony\Component\Console\Command\Command as CommandAlias;
|
||||
|
||||
class MemberRenewalProcessCommand extends Command
|
||||
{
|
||||
protected $signature = 'members:renewal-process
|
||||
{--phase=auto : Phase à exécuter (auto|reminder_1|reminder_2|reminder_3|deactivate)}
|
||||
{--year= : Année de renouvellement (par défaut : année suivante)}
|
||||
{--dry-run : Mode simulation}
|
||||
{email? : Email d\'un adhérent spécifique}';
|
||||
|
||||
protected $description = 'Processus de relances pour renouvellement adhésion';
|
||||
|
||||
public function __construct(
|
||||
protected MemberRenewalService $renewalService
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$dryRun = $this->option('dry-run');
|
||||
$emailFilter = $this->argument('email');
|
||||
$phaseOption = $this->option('phase');
|
||||
$year = $this->option('year') ?? now()->addYear()->year;
|
||||
|
||||
$this->info(
|
||||
$dryRun
|
||||
? 'DRY-RUN activé – aucune modification ne sera effectuée'
|
||||
: 'Mode réel activé'
|
||||
);
|
||||
|
||||
$phase = $phaseOption === 'auto' ? $this->determinePhase() : $phaseOption;
|
||||
|
||||
if ($phase === null) {
|
||||
$this->warn('Mode auto : aucune phase détectée pour la date actuelle');
|
||||
|
||||
return CommandAlias::SUCCESS;
|
||||
}
|
||||
|
||||
$this->info("Phase détectée : {$phase}");
|
||||
$this->info("Année de renouvellement : {$year}");
|
||||
|
||||
if ($emailFilter) {
|
||||
$this->warn("Mode utilisateur unique : {$emailFilter}");
|
||||
}
|
||||
|
||||
$members = $this->getEligibleMembers($emailFilter);
|
||||
|
||||
if ($members->isEmpty()) {
|
||||
$this->info('Aucun adhérent éligible');
|
||||
|
||||
return CommandAlias::SUCCESS;
|
||||
}
|
||||
|
||||
$this->info("{$members->count()} adhérent(s) à traiter");
|
||||
|
||||
foreach ($members as $member) {
|
||||
try {
|
||||
$this->processMember($member, $phase, $year, $dryRun);
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('Erreur traitement adhérent relance', [
|
||||
'member_id' => $member->id,
|
||||
'phase' => $phase,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
$this->error("Erreur pour {$member->email} : {$e->getMessage()}");
|
||||
}
|
||||
}
|
||||
|
||||
$this->info(
|
||||
$dryRun
|
||||
? 'DRY-RUN terminé – aucune action effectuée'
|
||||
: 'Traitement terminé'
|
||||
);
|
||||
|
||||
return CommandAlias::SUCCESS;
|
||||
}
|
||||
|
||||
protected function determinePhase(): ?string
|
||||
{
|
||||
$today = now();
|
||||
|
||||
if ($today->month === 12 && $today->day >= 1 && $today->day <= 10) {
|
||||
return 'reminder_1';
|
||||
}
|
||||
|
||||
if ($today->month === 12 && $today->day >= 15 && $today->day <= 20) {
|
||||
return 'reminder_2';
|
||||
}
|
||||
|
||||
if ($today->month === 1 && $today->day >= 4 && $today->day <= 6) {
|
||||
return 'reminder_3';
|
||||
}
|
||||
|
||||
if ($today->month === 1 && $today->day >= 25 && $today->day <= 31) {
|
||||
return 'deactivate';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function getEligibleMembers(?string $emailFilter): \Illuminate\Support\Collection
|
||||
{
|
||||
$query = Member::query()
|
||||
->where('status', 'valid')
|
||||
->whereHas('memberships', function ($q) {
|
||||
$q->where('status', 'active');
|
||||
});
|
||||
|
||||
if ($emailFilter) {
|
||||
$query->where('email', $emailFilter)
|
||||
->orWhere('retzien_email', $emailFilter);
|
||||
}
|
||||
|
||||
return $query->get();
|
||||
}
|
||||
|
||||
protected function processMember(Member $member, string $phase, int $year, bool $dryRun): void
|
||||
{
|
||||
$this->line("• {$member->id} - {$member->email}");
|
||||
|
||||
if ($dryRun) {
|
||||
$this->info("[DRY-RUN] Phase {$phase}");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$sent = match ($phase) {
|
||||
'reminder_1' => $this->renewalService->sendReminder1($member, $year),
|
||||
'reminder_2' => $this->renewalService->sendReminder2($member, $year),
|
||||
'reminder_3' => $this->renewalService->sendReminder3($member, $year),
|
||||
'deactivate' => $this->renewalService->deactivateMember($member, $year),
|
||||
default => false,
|
||||
};
|
||||
|
||||
if ($sent) {
|
||||
$this->info(" ✓ Relance envoyée");
|
||||
} else {
|
||||
$this->warn(" → Aucune relance envoyée (Membre non concerné / mail de relance déjà envoyé / adhésion active pour {$year})");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ namespace App\Console\Commands;
|
||||
use App\Models\Member;
|
||||
use App\Models\Service;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
use function Laravel\Prompts\progress;
|
||||
|
||||
class SyncServicesMembers extends Command
|
||||
@@ -31,7 +32,7 @@ class SyncServicesMembers extends Command
|
||||
// Tous les membres ayant une adhésion en court ont les services activés
|
||||
$this->info('Syncing services members...');
|
||||
|
||||
$members = Member::whereIn('status', ['valid', 'pending'] )->get();
|
||||
$members = Member::whereIn('status', ['valid', 'pending'])->get();
|
||||
|
||||
$progressBar = progress(label: 'Syncing services members', steps: $members->count());
|
||||
|
||||
|
||||
62
app/Filament/Actions/SendRenewalReminderAction.php
Normal file
62
app/Filament/Actions/SendRenewalReminderAction.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Actions;
|
||||
|
||||
use App\Models\Membership;
|
||||
use App\Services\MemberRenewalService;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Notifications\Notification;
|
||||
|
||||
class SendRenewalReminderAction extends Action
|
||||
{
|
||||
public static function make(?string $name = 'send_renewal_reminder'): static
|
||||
{
|
||||
return parent::make($name)
|
||||
->label('Envoyer une relance')
|
||||
->icon('heroicon-o-envelope')
|
||||
->color('primary')
|
||||
->disabled(function () {
|
||||
$today = now();
|
||||
|
||||
return ! ($today->month === 12 || ($today->month === 1 && $today->day <= 31));
|
||||
})
|
||||
->requiresConfirmation()
|
||||
->modalHeading('Envoyer une relance de renouvellement')
|
||||
->modalDescription(fn (Membership $record) => "Envoyer une relance à {$record->member->full_name} ({$record->member->email}) ?")
|
||||
->form([
|
||||
Select::make('reminder_type')
|
||||
->label('Type de relance')
|
||||
->options([
|
||||
'reminder_1' => '1ère relance (début décembre)',
|
||||
'reminder_2' => '2ème relance (mi-décembre)',
|
||||
'reminder_3' => '3ème relance (début janvier)',
|
||||
])
|
||||
->required()
|
||||
->default('reminder_1'),
|
||||
|
||||
TextInput::make('year')
|
||||
->label('Année de renouvellement')
|
||||
->default(now()->addYear()->year)
|
||||
->numeric()
|
||||
->required(),
|
||||
])
|
||||
->action(function (Membership $record, array $data) {
|
||||
$service = app(MemberRenewalService::class);
|
||||
$member = $record->member;
|
||||
$year = (int) $data['year'];
|
||||
|
||||
match ($data['reminder_type']) {
|
||||
'reminder_1' => $service->sendReminder1($member, $year),
|
||||
'reminder_2' => $service->sendReminder2($member, $year),
|
||||
'reminder_3' => $service->sendReminder3($member, $year),
|
||||
};
|
||||
|
||||
Notification::make()
|
||||
->title('Relance envoyée')
|
||||
->success()
|
||||
->send();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ namespace App\Filament\Resources\MemberGroups;
|
||||
use App\Filament\Resources\MemberGroups\Pages\CreateMemberGroup;
|
||||
use App\Filament\Resources\MemberGroups\Pages\EditMemberGroup;
|
||||
use App\Filament\Resources\MemberGroups\Pages\ListMemberGroups;
|
||||
use App\Filament\Resources\MemberGroups\RelationManagers\MemberRelationManager;
|
||||
use App\Filament\Resources\MemberGroups\RelationManagers\MembersRelationManager;
|
||||
use App\Filament\Resources\MemberGroups\Schemas\MemberGroupForm;
|
||||
use App\Filament\Resources\MemberGroups\Tables\MemberGroupsTable;
|
||||
|
||||
@@ -21,10 +21,9 @@ class MemberGroupForm
|
||||
->icon('heroicon-o-users')
|
||||
->label(MemberGroup::getAttributeLabel('view_members'))
|
||||
->action(function (MemberGroup $record) {
|
||||
return redirect()->route('filament.admin.resources.members.index', [ 'filters[group][name][values][0]' => $record->id]);
|
||||
return redirect()->route('filament.admin.resources.members.index', ['filters[group][name][values][0]' => $record->id]);
|
||||
})
|
||||
->visible(fn(?MemberGroup $record) => $record !== null)
|
||||
|
||||
->visible(fn (?MemberGroup $record) => $record !== null),
|
||||
|
||||
])
|
||||
->schema([
|
||||
|
||||
@@ -6,11 +6,9 @@ use App\Models\Member;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Tables\Filters\SelectFilter;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Filters\Filter;
|
||||
use Filament\Tables\Filters\SelectFilter;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class MembersTable
|
||||
@@ -83,7 +81,7 @@ class MembersTable
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
])
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ class MemberCount extends StatsOverviewWidget
|
||||
->description(Member::getAttributeLabel('widgets.stats.description'))
|
||||
->descriptionIcon('heroicon-o-user-group', IconPosition::Before)
|
||||
->chart([7, 2, 10, 3, 15, 4, 17])
|
||||
->color('primary')
|
||||
->color('primary'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Filament\Resources\Memberships\Schemas;
|
||||
|
||||
use App\Enums\IspconfigType;
|
||||
use App\Filament\Actions\SendRenewalReminderAction;
|
||||
use App\Filament\Actions\ServiceToggleAction;
|
||||
use App\Models\ListmonkMember;
|
||||
use App\Models\Membership;
|
||||
@@ -261,6 +262,12 @@ class MembershipForm
|
||||
->label(Membership::getAttributeLabel('end_date')),
|
||||
])
|
||||
->extraAttributes(['class' => 'sticky top-4 h-fit']),
|
||||
|
||||
Section::make('Relances')
|
||||
->schema([
|
||||
SendRenewalReminderAction::make(),
|
||||
])
|
||||
->extraAttributes(['class' => 'sticky top-4 h-fit']),
|
||||
])
|
||||
->columnSpan(1),
|
||||
])
|
||||
|
||||
@@ -34,7 +34,7 @@ class PackageForm
|
||||
Textarea::make('description')
|
||||
->label(Package::getAttributeLabel('description'))
|
||||
->default(null),
|
||||
])
|
||||
]),
|
||||
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ class ServiceForm
|
||||
TextInput::make('icon')
|
||||
->label(Service::getAttributeLabel('icon'))
|
||||
->default(null),
|
||||
])
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ class UserResource extends Resource
|
||||
protected static ?string $model = User::class;
|
||||
|
||||
protected static string|null|\UnitEnum $navigationGroup = 'Paramètres';
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedRectangleStack;
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
@@ -51,6 +52,7 @@ class UserResource extends Resource
|
||||
{
|
||||
return User::getAttributeLabel('user');
|
||||
}
|
||||
|
||||
public static function getPluralModelLabel(): string
|
||||
{
|
||||
return User::getAttributeLabel('users');
|
||||
|
||||
@@ -25,7 +25,7 @@ class MembershipFormController extends Controller
|
||||
->where('is_active', true)
|
||||
->select('id', 'identifier', 'name', 'price', 'description')
|
||||
->get()
|
||||
->map(function (Package $p) use ($remainingMonths, $currentYear) {
|
||||
->map(function (Package $p) use ($remainingMonths) {
|
||||
$months = match ($p->identifier) {
|
||||
'custom' => $remainingMonths,
|
||||
'one-year' => $remainingMonths + 12,
|
||||
|
||||
@@ -45,6 +45,7 @@ class Contact extends Model
|
||||
{
|
||||
return __("contacts.fields.$attribute");
|
||||
}
|
||||
|
||||
public function getFullNameAttribute(): string
|
||||
{
|
||||
return "{$this->firstname} {$this->lastname}";
|
||||
|
||||
@@ -13,7 +13,15 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property-read \App\Models\Member $member
|
||||
*
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|ListmonkMember newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|ListmonkMember newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|ListmonkMember query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|ListmonkMember whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|ListmonkMember whereData($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|ListmonkMember whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|ListmonkMember whereListmonkUserId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|ListmonkMember whereMemberId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|ListmonkMember whereUpdatedAt($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class ListmonkMember extends Model
|
||||
|
||||
@@ -19,10 +19,10 @@ use Illuminate\Notifications\Notifiable;
|
||||
* @property string $nature
|
||||
* @property int|null $type_id
|
||||
* @property int|null $group_id
|
||||
* @property string|null $lastname
|
||||
* @property string|null $firstname
|
||||
* @property string|null $lastname
|
||||
* @property string $email
|
||||
* @property string $retzien_email
|
||||
* @property string|null $retzien_email
|
||||
* @property string|null $company
|
||||
* @property string|null $date_of_birth
|
||||
* @property string|null $address
|
||||
@@ -40,17 +40,23 @@ use Illuminate\Notifications\Notifiable;
|
||||
* @property-read \App\Models\MemberGroup|null $group
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\IspconfigMember> $ispconfigs
|
||||
* @property-read int|null $ispconfigs_count
|
||||
* @property-read \App\Models\Membership|null $lastActiveMembership
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ListmonkMember> $listmonkMembers
|
||||
* @property-read int|null $listmonk_members_count
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Membership> $memberships
|
||||
* @property-read int|null $memberships_count
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\NextCloudMember> $nextcloudAccounts
|
||||
* @property-read int|null $nextcloud_accounts_count
|
||||
* @property-read \Illuminate\Notifications\DatabaseNotificationCollection<int, \Illuminate\Notifications\DatabaseNotification> $notifications
|
||||
* @property-read int|null $notifications_count
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\MemberRenewalReminder> $renewalReminders
|
||||
* @property-read int|null $renewal_reminders_count
|
||||
* @property-read \App\Models\MemberType|null $type
|
||||
* @property-read \App\Models\User|null $user
|
||||
*
|
||||
* @method static \Database\Factories\MemberFactory factory($count = null, $state = [])
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Member newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Member newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Member onlyTrashed()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Member query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Member whereAddress($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Member whereCity($value)
|
||||
@@ -77,7 +83,8 @@ use Illuminate\Notifications\Notifiable;
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Member whereUserId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Member whereWebsiteUrl($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Member whereZipcode($value)
|
||||
*
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Member withTrashed(bool $withTrashed = true)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Member withoutTrashed()
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class Member extends Model
|
||||
@@ -163,6 +170,11 @@ class Member extends Model
|
||||
return $this->hasMany(ListmonkMember::class, 'member_id');
|
||||
}
|
||||
|
||||
public function renewalReminders(): HasMany
|
||||
{
|
||||
return $this->hasMany(MemberRenewalReminder::class);
|
||||
}
|
||||
|
||||
public function lastActiveMembership(): HasOne
|
||||
{
|
||||
return $this->hasOne(Membership::class)->where('status', 'active')->latest();
|
||||
|
||||
@@ -32,12 +32,12 @@ class MemberGroup extends Model
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'description',
|
||||
'identifier'
|
||||
'identifier',
|
||||
];
|
||||
|
||||
public static function getAttributeLabel(string $attribute): string
|
||||
{
|
||||
return __('member_groups.fields.' . $attribute);
|
||||
return __('member_groups.fields.'.$attribute);
|
||||
}
|
||||
|
||||
public function members(): HasMany
|
||||
|
||||
107
app/Models/MemberRenewalReminder.php
Normal file
107
app/Models/MemberRenewalReminder.php
Normal file
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property int $member_id
|
||||
* @property int $renewal_year
|
||||
* @property bool $reminder_1_sent
|
||||
* @property bool $reminder_2_sent
|
||||
* @property bool $reminder_3_sent
|
||||
* @property bool $deactivation_sent
|
||||
* @property \Illuminate\Support\Carbon|null $reminder_1_sent_at
|
||||
* @property \Illuminate\Support\Carbon|null $reminder_2_sent_at
|
||||
* @property \Illuminate\Support\Carbon|null $reminder_3_sent_at
|
||||
* @property \Illuminate\Support\Carbon|null $deactivated_at
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property-read \App\Models\Member $member
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|MemberRenewalReminder newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|MemberRenewalReminder newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|MemberRenewalReminder query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|MemberRenewalReminder whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|MemberRenewalReminder whereDeactivatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|MemberRenewalReminder whereDeactivationSent($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|MemberRenewalReminder whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|MemberRenewalReminder whereMemberId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|MemberRenewalReminder whereReminder1Sent($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|MemberRenewalReminder whereReminder1SentAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|MemberRenewalReminder whereReminder2Sent($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|MemberRenewalReminder whereReminder2SentAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|MemberRenewalReminder whereReminder3Sent($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|MemberRenewalReminder whereReminder3SentAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|MemberRenewalReminder whereRenewalYear($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|MemberRenewalReminder whereUpdatedAt($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class MemberRenewalReminder extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'member_id',
|
||||
'renewal_year',
|
||||
'reminder_1_sent',
|
||||
'reminder_2_sent',
|
||||
'reminder_3_sent',
|
||||
'deactivation_sent',
|
||||
'reminder_1_sent_at',
|
||||
'reminder_2_sent_at',
|
||||
'reminder_3_sent_at',
|
||||
'deactivated_at',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'reminder_1_sent' => 'boolean',
|
||||
'reminder_2_sent' => 'boolean',
|
||||
'reminder_3_sent' => 'boolean',
|
||||
'deactivation_sent' => 'boolean',
|
||||
'reminder_1_sent_at' => 'datetime',
|
||||
'reminder_2_sent_at' => 'datetime',
|
||||
'reminder_3_sent_at' => 'datetime',
|
||||
'deactivated_at' => 'datetime',
|
||||
'renewal_year' => 'integer',
|
||||
];
|
||||
}
|
||||
|
||||
public function member(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Member::class);
|
||||
}
|
||||
|
||||
public function markAsReminder1Sent(): void
|
||||
{
|
||||
$this->update([
|
||||
'reminder_1_sent' => true,
|
||||
'reminder_1_sent_at' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function markAsReminder2Sent(): void
|
||||
{
|
||||
$this->update([
|
||||
'reminder_2_sent' => true,
|
||||
'reminder_2_sent_at' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function markAsReminder3Sent(): void
|
||||
{
|
||||
$this->update([
|
||||
'reminder_3_sent' => true,
|
||||
'reminder_3_sent_at' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function markAsDeactivated(): void
|
||||
{
|
||||
$this->update([
|
||||
'deactivation_sent' => true,
|
||||
'deactivated_at' => now(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,29 @@ namespace App\Models;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $identifier
|
||||
* @property string $name
|
||||
* @property string|null $description
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property \Illuminate\Support\Carbon|null $deleted_at
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|MemberType newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|MemberType newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|MemberType onlyTrashed()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|MemberType query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|MemberType whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|MemberType whereDeletedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|MemberType whereDescription($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|MemberType whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|MemberType whereIdentifier($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|MemberType whereName($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|MemberType whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|MemberType withTrashed(bool $withTrashed = true)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|MemberType withoutTrashed()
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class MemberType extends Model
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
@@ -16,7 +16,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
* @property string $status
|
||||
* @property string|null $validation_date
|
||||
* @property string|null $payment_method
|
||||
* @property string $amount
|
||||
* @property numeric $amount
|
||||
* @property string $payment_status
|
||||
* @property string|null $note_public
|
||||
* @property string|null $note_private
|
||||
@@ -30,7 +30,6 @@ use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
* @property-read \App\Models\Package $package
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Service> $services
|
||||
* @property-read int|null $services_count
|
||||
*
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Membership newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Membership newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Membership query()
|
||||
@@ -52,7 +51,6 @@ use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Membership whereStatus($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Membership whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Membership whereValidationDate($value)
|
||||
*
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class Membership extends Model
|
||||
|
||||
@@ -22,7 +22,6 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|NextCloudMember whereMemberId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|NextCloudMember whereNextcloudUserId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|NextCloudMember whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|NextCloudMember whereUsername($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class NextCloudMember extends Model
|
||||
|
||||
@@ -6,6 +6,36 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $identifier
|
||||
* @property string $name
|
||||
* @property string $subject
|
||||
* @property string $body
|
||||
* @property array<array-key, mixed>|null $variables
|
||||
* @property bool $is_active
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property \Illuminate\Support\Carbon|null $deleted_at
|
||||
* @method static \Database\Factories\NotificationTemplateFactory factory($count = null, $state = [])
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|NotificationTemplate newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|NotificationTemplate newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|NotificationTemplate onlyTrashed()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|NotificationTemplate query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|NotificationTemplate whereBody($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|NotificationTemplate whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|NotificationTemplate whereDeletedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|NotificationTemplate whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|NotificationTemplate whereIdentifier($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|NotificationTemplate whereIsActive($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|NotificationTemplate whereName($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|NotificationTemplate whereSubject($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|NotificationTemplate whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|NotificationTemplate whereVariables($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|NotificationTemplate withTrashed(bool $withTrashed = true)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|NotificationTemplate withoutTrashed()
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class NotificationTemplate extends Model
|
||||
{
|
||||
use HasFactory, SoftDeletes;
|
||||
|
||||
@@ -3,14 +3,13 @@
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $identifier
|
||||
* @property string $name
|
||||
* @property string|null $description
|
||||
* @property string $price
|
||||
* @property numeric $price
|
||||
* @property int $is_active
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
@@ -40,7 +39,6 @@ class Package extends Model
|
||||
|
||||
public static function getAttributeLabel(string $attribute): string
|
||||
{
|
||||
return __('packages.fields.' . $attribute);
|
||||
return __('packages.fields.'.$attribute);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -43,10 +43,10 @@ class Service extends Model
|
||||
|
||||
public static function getAttributeLabel(string $attribute): string
|
||||
{
|
||||
return __('services.fields.' . $attribute);
|
||||
return __('services.fields.'.$attribute);
|
||||
}
|
||||
|
||||
public function memberships() : BelongsToMany
|
||||
public function memberships(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Membership::class, 'services_memberships', 'service_id', 'membership_id');
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@ use Spatie\Permission\Traits\HasRoles;
|
||||
* @property-read int|null $permissions_count
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \Spatie\Permission\Models\Role> $roles
|
||||
* @property-read int|null $roles_count
|
||||
*
|
||||
* @method static \Database\Factories\UserFactory factory($count = null, $state = [])
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|User newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|User newQuery()
|
||||
@@ -47,7 +46,6 @@ use Spatie\Permission\Traits\HasRoles;
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|User withoutPermission($permissions)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|User withoutRole($roles, $guard = null)
|
||||
*
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class User extends Authenticatable implements FilamentUser
|
||||
|
||||
51
app/Notifications/MemberAccountDeactivatedNotification.php
Normal file
51
app/Notifications/MemberAccountDeactivatedNotification.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use App\Models\Member;
|
||||
use App\Models\NotificationTemplate;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
|
||||
class MemberAccountDeactivatedNotification extends Notification implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public function __construct(
|
||||
public readonly Member $member,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function via(object $notifiable): array
|
||||
{
|
||||
return ['mail'];
|
||||
}
|
||||
|
||||
public function toMail(object $notifiable): MailMessage
|
||||
{
|
||||
$template = NotificationTemplate::findByIdentifier('member_account_deactivated');
|
||||
|
||||
$vars = [
|
||||
'member_name' => $this->member->full_name,
|
||||
'app_name' => config('app.front_name'),
|
||||
];
|
||||
|
||||
return (new MailMessage)
|
||||
->subject($template->renderSubject($vars))
|
||||
->view('notifications.mail-template', [
|
||||
'body' => $template->renderBody($vars),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(object $notifiable): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
51
app/Notifications/MemberRenewalReminder2Notification.php
Normal file
51
app/Notifications/MemberRenewalReminder2Notification.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use App\Models\Member;
|
||||
use App\Models\NotificationTemplate;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
|
||||
class MemberRenewalReminder2Notification extends Notification implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public function __construct(
|
||||
public readonly Member $member,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function via(object $notifiable): array
|
||||
{
|
||||
return ['mail'];
|
||||
}
|
||||
|
||||
public function toMail(object $notifiable): MailMessage
|
||||
{
|
||||
$template = NotificationTemplate::findByIdentifier('member_renewal_reminder_2');
|
||||
|
||||
$vars = [
|
||||
'member_name' => $this->member->full_name,
|
||||
'app_name' => config('app.front_name'),
|
||||
];
|
||||
|
||||
return (new MailMessage)
|
||||
->subject($template->renderSubject($vars))
|
||||
->view('notifications.mail-template', [
|
||||
'body' => $template->renderBody($vars),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(object $notifiable): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
51
app/Notifications/MemberRenewalReminder3Notification.php
Normal file
51
app/Notifications/MemberRenewalReminder3Notification.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use App\Models\Member;
|
||||
use App\Models\NotificationTemplate;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
|
||||
class MemberRenewalReminder3Notification extends Notification implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public function __construct(
|
||||
public readonly Member $member,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function via(object $notifiable): array
|
||||
{
|
||||
return ['mail'];
|
||||
}
|
||||
|
||||
public function toMail(object $notifiable): MailMessage
|
||||
{
|
||||
$template = NotificationTemplate::findByIdentifier('member_renewal_reminder_3');
|
||||
|
||||
$vars = [
|
||||
'member_name' => $this->member->full_name,
|
||||
'app_name' => config('app.front_name'),
|
||||
];
|
||||
|
||||
return (new MailMessage)
|
||||
->subject($template->renderSubject($vars))
|
||||
->view('notifications.mail-template', [
|
||||
'body' => $template->renderBody($vars),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(object $notifiable): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,9 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use Illuminate\Foundation\Auth\User as AuthUser;
|
||||
use App\Models\MemberGroup;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
use Illuminate\Foundation\Auth\User as AuthUser;
|
||||
|
||||
class MemberGroupPolicy
|
||||
{
|
||||
@@ -66,5 +66,4 @@ class MemberGroupPolicy
|
||||
{
|
||||
return $authUser->can('Reorder:MemberGroup');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,9 +4,9 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use Illuminate\Foundation\Auth\User as AuthUser;
|
||||
use App\Models\Member;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
use Illuminate\Foundation\Auth\User as AuthUser;
|
||||
|
||||
class MemberPolicy
|
||||
{
|
||||
@@ -66,5 +66,4 @@ class MemberPolicy
|
||||
{
|
||||
return $authUser->can('Reorder:Member');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,9 +4,9 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use Illuminate\Foundation\Auth\User as AuthUser;
|
||||
use App\Models\Membership;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
use Illuminate\Foundation\Auth\User as AuthUser;
|
||||
|
||||
class MembershipPolicy
|
||||
{
|
||||
@@ -66,5 +66,4 @@ class MembershipPolicy
|
||||
{
|
||||
return $authUser->can('Reorder:Membership');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,9 +4,9 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use Illuminate\Foundation\Auth\User as AuthUser;
|
||||
use App\Models\Package;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
use Illuminate\Foundation\Auth\User as AuthUser;
|
||||
|
||||
class PackagePolicy
|
||||
{
|
||||
@@ -66,5 +66,4 @@ class PackagePolicy
|
||||
{
|
||||
return $authUser->can('Reorder:Package');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,9 +4,9 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
use Illuminate\Foundation\Auth\User as AuthUser;
|
||||
use Spatie\Permission\Models\Role;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
|
||||
class RolePolicy
|
||||
{
|
||||
@@ -66,5 +66,4 @@ class RolePolicy
|
||||
{
|
||||
return $authUser->can('Reorder:Role');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,9 +4,9 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use Illuminate\Foundation\Auth\User as AuthUser;
|
||||
use App\Models\Service;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
use Illuminate\Foundation\Auth\User as AuthUser;
|
||||
|
||||
class ServicePolicy
|
||||
{
|
||||
@@ -66,5 +66,4 @@ class ServicePolicy
|
||||
{
|
||||
return $authUser->can('Reorder:Service');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use Illuminate\Foundation\Auth\User as AuthUser;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
use Illuminate\Foundation\Auth\User as AuthUser;
|
||||
|
||||
class UserPolicy
|
||||
{
|
||||
@@ -63,5 +63,4 @@ class UserPolicy
|
||||
{
|
||||
return $authUser->can('Reorder:User');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -9,9 +9,13 @@ use Illuminate\Support\Facades\Http;
|
||||
class DolibarrService
|
||||
{
|
||||
protected string $baseUrl;
|
||||
|
||||
protected string $htaccessUrl;
|
||||
|
||||
protected string $username;
|
||||
|
||||
protected string $password;
|
||||
|
||||
protected string $apiKey;
|
||||
|
||||
public function __construct()
|
||||
@@ -37,11 +41,12 @@ class DolibarrService
|
||||
|
||||
/**
|
||||
* Get all members
|
||||
*
|
||||
* @throws ConnectionException
|
||||
*/
|
||||
public function getAllMembers(int $limit = 400, string $sortField = 't.rowid', string $sortOrder = 'ASC'): array
|
||||
{
|
||||
$response = $this->client()->get($this->baseUrl . '/members', [
|
||||
$response = $this->client()->get($this->baseUrl.'/members', [
|
||||
'sortfield' => $sortField,
|
||||
'sortorder' => $sortOrder,
|
||||
'limit' => $limit,
|
||||
@@ -52,11 +57,12 @@ class DolibarrService
|
||||
|
||||
/**
|
||||
* Get member subscriptions
|
||||
*
|
||||
* @throws ConnectionException
|
||||
*/
|
||||
public function getMemberSubscriptions(int|string $id): array
|
||||
{
|
||||
$response = $this->client()->get($this->baseUrl . '/members/'. $id . '/subscriptions');
|
||||
$response = $this->client()->get($this->baseUrl.'/members/'.$id.'/subscriptions');
|
||||
|
||||
return $response->json();
|
||||
}
|
||||
@@ -66,17 +72,16 @@ class DolibarrService
|
||||
*
|
||||
* @param int|string $id The Dolibarr member ID (rowid)
|
||||
* @param array $data Array of attributes to update (e.g. ['email' => 'new@email.com', 'array_options' => ['options_custom' => 'val']])
|
||||
*
|
||||
* @throws ConnectionException
|
||||
*/
|
||||
public function updateMember(int|string $id, array $data): bool
|
||||
{
|
||||
$response = $this->client()->put(
|
||||
$this->baseUrl . '/members/' . $id,
|
||||
$this->baseUrl.'/members/'.$id,
|
||||
$data
|
||||
);
|
||||
|
||||
return $response->successful();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -18,9 +18,9 @@ class ISPConfigMailService extends ISPConfigService
|
||||
public function getAllMailDomains(): array
|
||||
{
|
||||
return Cache::remember(
|
||||
"ispconfig.mail.domains.all",
|
||||
'ispconfig.mail.domains.all',
|
||||
config('services.ispconfig.cache_ttl'),
|
||||
fn() => $this->call('mail_domain_get', ['primary_id' => -1])
|
||||
fn () => $this->call('mail_domain_get', ['primary_id' => -1])
|
||||
);
|
||||
}
|
||||
|
||||
@@ -30,9 +30,9 @@ class ISPConfigMailService extends ISPConfigService
|
||||
public function getAllMailUsers(): array
|
||||
{
|
||||
return Cache::remember(
|
||||
"ispconfig.mail.users.all",
|
||||
'ispconfig.mail.users.all',
|
||||
config('services.ispconfig.cache_ttl'),
|
||||
fn() => $this->call('mail_user_get', ['primary_id' => -1])
|
||||
fn () => $this->call('mail_user_get', ['primary_id' => -1])
|
||||
);
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ class ISPConfigMailService extends ISPConfigService
|
||||
$allUsers = $this->getAllMailUsers();
|
||||
|
||||
return collect($allUsers)->filter(function ($user) use ($domain) {
|
||||
return str_ends_with($user['email'], '@' . $domain);
|
||||
return str_ends_with($user['email'], '@'.$domain);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ class ISPConfigMailService extends ISPConfigService
|
||||
|
||||
$user = collect($allUsers)->firstWhere('email', $email);
|
||||
|
||||
if (!$user) {
|
||||
if (! $user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ class ISPConfigMailService extends ISPConfigService
|
||||
$user = collect($this->getAllMailUsers())
|
||||
->firstWhere('email', $email);
|
||||
|
||||
if (!$user) {
|
||||
if (! $user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -105,10 +105,10 @@ class ISPConfigMailService extends ISPConfigService
|
||||
|
||||
// On récupère l'enregistrement COMPLET (OBLIGATOIRE)
|
||||
$mailUserRecord = $this->call('mail_user_get', [
|
||||
$mailuserId
|
||||
$mailuserId,
|
||||
]);
|
||||
|
||||
if (!is_array($mailUserRecord)) {
|
||||
if (! is_array($mailUserRecord)) {
|
||||
throw new \RuntimeException('mail_user_get did not return array');
|
||||
}
|
||||
|
||||
@@ -121,11 +121,9 @@ class ISPConfigMailService extends ISPConfigService
|
||||
$result = $this->call('mail_user_update', [
|
||||
0, // client_id (ADMIN)
|
||||
$mailuserId, // primary_id
|
||||
$mailUserRecord // FULL RECORD
|
||||
$mailUserRecord, // FULL RECORD
|
||||
]);
|
||||
|
||||
return (bool) $result;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -4,15 +4,18 @@ namespace App\Services\ISPConfig;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use SoapClient;
|
||||
use SoapFault;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class ISPConfigService
|
||||
{
|
||||
protected ?SoapClient $client = null;
|
||||
|
||||
protected ?string $sessionId = null;
|
||||
|
||||
protected array $config;
|
||||
|
||||
protected string $serverType;
|
||||
|
||||
/**
|
||||
@@ -23,7 +26,7 @@ class ISPConfigService
|
||||
$this->serverType = $serverType;
|
||||
$this->config = config("services.ispconfig.servers.{$serverType}");
|
||||
|
||||
if (!$this->config) {
|
||||
if (! $this->config) {
|
||||
throw new Exception("ISPConfig server configuration not found for: {$serverType}");
|
||||
}
|
||||
}
|
||||
@@ -44,8 +47,8 @@ class ISPConfigService
|
||||
'ssl' => [
|
||||
'verify_peer' => false,
|
||||
'verify_peer_name' => false,
|
||||
]
|
||||
])
|
||||
],
|
||||
]),
|
||||
]);
|
||||
|
||||
$this->sessionId = $this->client->login(
|
||||
@@ -53,17 +56,17 @@ class ISPConfigService
|
||||
$this->config['password']
|
||||
);
|
||||
|
||||
Log::info("ISPConfig connected", [
|
||||
Log::info('ISPConfig connected', [
|
||||
'server' => $this->serverType,
|
||||
'session_id' => $this->sessionId
|
||||
'session_id' => $this->sessionId,
|
||||
]);
|
||||
|
||||
} catch (SoapFault $e) {
|
||||
Log::error("ISPConfig connection failed", [
|
||||
Log::error('ISPConfig connection failed', [
|
||||
'server' => $this->serverType,
|
||||
'error' => $e->getMessage()
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
throw new Exception("Failed to connect to ISPConfig: " . $e->getMessage());
|
||||
throw new Exception('Failed to connect to ISPConfig: '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,11 +75,11 @@ class ISPConfigService
|
||||
if ($this->client && $this->sessionId) {
|
||||
try {
|
||||
$this->client->logout($this->sessionId);
|
||||
Log::info("ISPConfig disconnected", ['server' => $this->serverType]);
|
||||
Log::info('ISPConfig disconnected', ['server' => $this->serverType]);
|
||||
} catch (SoapFault $e) {
|
||||
Log::warning("ISPConfig logout failed", [
|
||||
Log::warning('ISPConfig logout failed', [
|
||||
'server' => $this->serverType,
|
||||
'error' => $e->getMessage()
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -97,21 +100,21 @@ class ISPConfigService
|
||||
|
||||
$result = $this->client->__soapCall($method, $params);
|
||||
|
||||
Log::debug("ISPConfig API call", [
|
||||
Log::debug('ISPConfig API call', [
|
||||
'method' => $method,
|
||||
'server' => $this->serverType,
|
||||
'success' => true
|
||||
'success' => true,
|
||||
]);
|
||||
|
||||
return $result;
|
||||
|
||||
} catch (SoapFault $e) {
|
||||
Log::error("ISPConfig API call failed", [
|
||||
Log::error('ISPConfig API call failed', [
|
||||
'method' => $method,
|
||||
'server' => $this->serverType,
|
||||
'error' => $e->getMessage()
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
throw new Exception("ISPConfig API call failed: " . $e->getMessage());
|
||||
throw new Exception('ISPConfig API call failed: '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,9 +129,9 @@ class ISPConfigService
|
||||
public function getAllClients(): array
|
||||
{
|
||||
return Cache::remember(
|
||||
"ispconfig.mail.clients.all",
|
||||
'ispconfig.mail.clients.all',
|
||||
config('services.ispconfig.cache_ttl'),
|
||||
fn() => $this->call('client_get_all')
|
||||
fn () => $this->call('client_get_all')
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
152
app/Services/MemberRenewalService.php
Normal file
152
app/Services/MemberRenewalService.php
Normal file
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Member;
|
||||
use App\Models\MemberRenewalReminder;
|
||||
use App\Notifications\MemberAccountDeactivatedNotification;
|
||||
use App\Notifications\MemberRenewalReminder2Notification;
|
||||
use App\Notifications\MemberRenewalReminder3Notification;
|
||||
use App\Notifications\MemberRenewalReminderNotification;
|
||||
use App\Services\Dolibarr\DolibarrService;
|
||||
|
||||
class MemberRenewalService
|
||||
{
|
||||
public function __construct(
|
||||
protected DolibarrService $dolibarr,
|
||||
protected MemberService $memberService
|
||||
) {}
|
||||
|
||||
public function sendReminder1(Member $member, int $year): bool
|
||||
{
|
||||
$reminder = MemberRenewalReminder::firstOrCreate(
|
||||
['member_id' => $member->id, 'renewal_year' => $year],
|
||||
);
|
||||
|
||||
if ($reminder->reminder_1_sent) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->hasActiveMembershipForYear($member, $year)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $this->shouldSendRenewalForYear($member, $year)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$member->notify(new MemberRenewalReminderNotification($member));
|
||||
|
||||
$reminder->markAsReminder1Sent();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function sendReminder2(Member $member, int $year): bool
|
||||
{
|
||||
$reminder = MemberRenewalReminder::firstOrCreate(
|
||||
['member_id' => $member->id, 'renewal_year' => $year],
|
||||
);
|
||||
|
||||
if ($reminder->reminder_2_sent) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->hasActiveMembershipForYear($member, $year)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $this->shouldSendRenewalForYear($member, $year)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$member->notify(new MemberRenewalReminder2Notification($member));
|
||||
|
||||
$reminder->markAsReminder2Sent();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function sendReminder3(Member $member, int $year): bool
|
||||
{
|
||||
$reminder = MemberRenewalReminder::firstOrCreate(
|
||||
['member_id' => $member->id, 'renewal_year' => $year],
|
||||
);
|
||||
|
||||
if ($reminder->reminder_3_sent) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->hasActiveMembershipForYear($member, $year)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $this->shouldSendRenewalForYear($member, $year)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$member->notify(new MemberRenewalReminder3Notification($member));
|
||||
|
||||
$reminder->markAsReminder3Sent();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function deactivateMember(Member $member, int $year): bool
|
||||
{
|
||||
$reminder = MemberRenewalReminder::firstOrCreate(
|
||||
['member_id' => $member->id, 'renewal_year' => $year],
|
||||
);
|
||||
|
||||
if ($reminder->deactivation_sent) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->hasActiveMembershipForYear($member, $year)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->memberService->deactivateMember($member);
|
||||
|
||||
$member->notify(new MemberAccountDeactivatedNotification($member));
|
||||
|
||||
$reminder->markAsDeactivated();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function hasActiveMembershipForYear(Member $member, int $year): bool
|
||||
{
|
||||
$yearStart = "{$year}-01-01";
|
||||
$yearEnd = "{$year}-12-31";
|
||||
|
||||
return $member->memberships()
|
||||
->where('status', 'active')
|
||||
->where(function ($query) use ($yearStart, $yearEnd) {
|
||||
$query->where(function ($q) use ($yearStart, $yearEnd) {
|
||||
$q->where('start_date', '<=', $yearEnd)
|
||||
->where(function ($q2) use ($yearStart) {
|
||||
$q2->whereNull('end_date')
|
||||
->orWhere('end_date', '>=', $yearStart);
|
||||
});
|
||||
});
|
||||
})
|
||||
->exists();
|
||||
}
|
||||
|
||||
protected function shouldSendRenewalForYear(Member $member, int $year): bool
|
||||
{
|
||||
$currentMembership = $member->memberships()
|
||||
->where('status', 'active')
|
||||
->orderBy('end_date', 'desc')
|
||||
->first();
|
||||
|
||||
if (! $currentMembership || ! $currentMembership->end_date) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$expirationYear = (int) date('Y', strtotime($currentMembership->end_date));
|
||||
|
||||
return $expirationYear === ($year - 1);
|
||||
}
|
||||
}
|
||||
@@ -22,31 +22,34 @@ class NextcloudService
|
||||
'OCS-APIRequest' => 'true',
|
||||
'Accept' => 'application/json',
|
||||
])
|
||||
->baseUrl(config('services.nextcloud.url') . '/ocs/v1.php');
|
||||
->baseUrl(config('services.nextcloud.url').'/ocs/v1.php');
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable user by email
|
||||
*
|
||||
* @throws ConnectionException
|
||||
*/
|
||||
public function disableUserByEmail(string $email): void
|
||||
{
|
||||
$userId = $this->findUserIdByEmail($email);
|
||||
|
||||
if (!$userId) {
|
||||
Log::warning("Utilisateur Nextcloud introuvable", ['email' => $email]);
|
||||
if (! $userId) {
|
||||
Log::warning('Utilisateur Nextcloud introuvable', ['email' => $email]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$response = $this->http->put("/cloud/users/{$userId}/disable");
|
||||
|
||||
if (!$response->successful()) {
|
||||
if (! $response->successful()) {
|
||||
throw new \RuntimeException("Erreur désactivation Nextcloud {$userId}");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Desable user by id
|
||||
*
|
||||
* @throws ConnectionException
|
||||
*/
|
||||
public function disableUserById(string $userId): void
|
||||
@@ -60,7 +63,7 @@ class NextcloudService
|
||||
protected function findUserIdByEmail(string $email): ?string
|
||||
{
|
||||
return Cache::remember(
|
||||
'nextcloud.user_id.' . md5($email),
|
||||
'nextcloud.user_id.'.md5($email),
|
||||
now()->addDays(7),
|
||||
function () use ($email) {
|
||||
return $this->resolveUserIdByEmail($email);
|
||||
@@ -110,6 +113,4 @@ class NextcloudService
|
||||
->get("/cloud/users/{$userId}")
|
||||
->json('ocs.data') ?? [];
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ return new class extends Migration
|
||||
'valid',
|
||||
'pending',
|
||||
'cancelled',
|
||||
'excluded'
|
||||
'excluded',
|
||||
])->default('draft');
|
||||
|
||||
// Nature
|
||||
|
||||
@@ -15,7 +15,7 @@ return new class extends Migration
|
||||
$table->id();
|
||||
$table->foreignId('service_id')->constrained('services')->onDelete('cascade');
|
||||
$table->foreignId('membership_id')->constrained('memberships')->onDelete('cascade');
|
||||
//$table->json('data')->nullable();
|
||||
// $table->json('data')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('member_renewal_reminders', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('member_id')->constrained('members')->onDelete('cascade');
|
||||
$table->year('renewal_year');
|
||||
|
||||
$table->boolean('reminder_1_sent')->default(false);
|
||||
$table->boolean('reminder_2_sent')->default(false);
|
||||
$table->boolean('reminder_3_sent')->default(false);
|
||||
$table->boolean('deactivation_sent')->default(false);
|
||||
|
||||
$table->timestamp('reminder_1_sent_at')->nullable();
|
||||
$table->timestamp('reminder_2_sent_at')->nullable();
|
||||
$table->timestamp('reminder_3_sent_at')->nullable();
|
||||
$table->timestamp('deactivated_at')->nullable();
|
||||
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['member_id', 'renewal_year']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('member_renewal_reminders');
|
||||
}
|
||||
};
|
||||
@@ -172,13 +172,67 @@ class NotificationTemplateSeeder extends Seeder
|
||||
NotificationTemplate::updateOrCreate(
|
||||
['identifier' => 'member_renewal_reminder'],
|
||||
[
|
||||
'name' => 'Relance adhésion — générique',
|
||||
'subject' => 'Rappel concernant votre adhésion — {app_name}',
|
||||
'name' => 'Relance renouvellement — 1ère relance (début décembre)',
|
||||
'subject' => 'Renouvellement de votre adhésion pour l\'année à venir — {app_name}',
|
||||
'body' => '<p>Bonjour {member_name},</p>'
|
||||
.'<p>Nous vous contactons au sujet de votre adhésion au sein de notre association.</p>'
|
||||
.'<p>Si votre adhésion est arrivée à expiration ou si vous n\'avez pas encore finalisé votre inscription, '
|
||||
.'nous vous invitons à régulariser votre situation afin de continuer à bénéficier de l\'ensemble de nos services.</p>'
|
||||
.'<p>Pour toute question ou besoin d\'assistance, n\'hésitez pas à nous contacter.</p>'
|
||||
.'<p>L\'année touche à sa fin et nous vous invitons à renouveler votre adhésion pour l\'année prochaine.</p>'
|
||||
.'<p>Cela vous permettra de continuer à bénéficier de l\'ensemble de nos services sans interruption.</p>'
|
||||
.'<p>Nous restons à votre disposition pour toute question.</p>'
|
||||
.'<p>Cordialement,<br>L\'équipe {app_name}</p>',
|
||||
'variables' => [
|
||||
'member_name' => 'Nom complet du membre',
|
||||
'app_name' => 'Nom de l\'application',
|
||||
],
|
||||
'is_active' => true,
|
||||
]
|
||||
);
|
||||
|
||||
NotificationTemplate::updateOrCreate(
|
||||
['identifier' => 'member_renewal_reminder_2'],
|
||||
[
|
||||
'name' => 'Relance renouvellement — 2ème relance (mi-décembre)',
|
||||
'subject' => 'Pensez à renouveler votre adhésion — {app_name}',
|
||||
'body' => '<p>Bonjour {member_name},</p>'
|
||||
.'<p>Nous vous avons récemment contacté concernant le renouvellement de votre adhésion pour l\'année prochaine.</p>'
|
||||
.'<p>Si vous n\'avez pas encore renouvelé, nous vous encourageons à le faire rapidement pour garantir la continuité de vos services.</p>'
|
||||
.'<p>N\'hésitez pas à nous contacter si vous avez des questions.</p>'
|
||||
.'<p>Cordialement,<br>L\'équipe {app_name}</p>',
|
||||
'variables' => [
|
||||
'member_name' => 'Nom complet du membre',
|
||||
'app_name' => 'Nom de l\'application',
|
||||
],
|
||||
'is_active' => true,
|
||||
]
|
||||
);
|
||||
|
||||
NotificationTemplate::updateOrCreate(
|
||||
['identifier' => 'member_renewal_reminder_3'],
|
||||
[
|
||||
'name' => 'Relance renouvellement — 3ème relance (début janvier)',
|
||||
'subject' => 'Dernière relance : renouvellement de votre adhésion — {app_name}',
|
||||
'body' => '<p>Bonjour {member_name},</p>'
|
||||
.'<p>Nous vous contactons une dernière fois concernant le renouvellement de votre adhésion.</p>'
|
||||
.'<p><strong>Sans action de votre part d\'ici la fin janvier, votre compte et vos services seront automatiquement désactivés.</strong></p>'
|
||||
.'<p>Pour éviter toute interruption, nous vous invitons à renouveler dès maintenant.</p>'
|
||||
.'<p>Pour toute question, n\'hésitez pas à nous contacter.</p>'
|
||||
.'<p>Cordialement,<br>L\'équipe {app_name}</p>',
|
||||
'variables' => [
|
||||
'member_name' => 'Nom complet du membre',
|
||||
'app_name' => 'Nom de l\'application',
|
||||
],
|
||||
'is_active' => true,
|
||||
]
|
||||
);
|
||||
|
||||
NotificationTemplate::updateOrCreate(
|
||||
['identifier' => 'member_account_deactivated'],
|
||||
[
|
||||
'name' => 'Compte désactivé pour non-renouvellement',
|
||||
'subject' => 'Votre compte {app_name} a été désactivé',
|
||||
'body' => '<p>Bonjour {member_name},</p>'
|
||||
.'<p>Faute de renouvellement de votre adhésion, votre compte et vos services ont été désactivés.</p>'
|
||||
.'<p>Si vous souhaitez réactiver votre compte, n\'hésitez pas à nous contacter.</p>'
|
||||
.'<p>Nous vous remercions pour la confiance que vous nous avez accordée.</p>'
|
||||
.'<p>Cordialement,<br>L\'équipe {app_name}</p>',
|
||||
'variables' => [
|
||||
'member_name' => 'Nom complet du membre',
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace Deployer;
|
||||
|
||||
require 'recipe/laravel.php';
|
||||
@@ -8,10 +9,10 @@ require 'contrib/rsync.php';
|
||||
$env = $_SERVER['CI_COMMIT_REF_NAME'] === 'master' ? 'PROD' : 'STAGING';
|
||||
|
||||
$envConfig = [
|
||||
'host' => $_SERVER[$env . '_SSH_HOST'],
|
||||
'port' => $_SERVER[$env . '_SSH_PORT'] ?? 22,
|
||||
'user' => $_SERVER[$env . '_SSH_USER'],
|
||||
'path' => $_SERVER[$env . '_SSH_PATH'],
|
||||
'host' => $_SERVER[$env.'_SSH_HOST'],
|
||||
'port' => $_SERVER[$env.'_SSH_PORT'] ?? 22,
|
||||
'user' => $_SERVER[$env.'_SSH_USER'],
|
||||
'path' => $_SERVER[$env.'_SSH_PATH'],
|
||||
'repository' => $_SERVER['CI_REPOSITORY_URL'],
|
||||
];
|
||||
|
||||
|
||||
@@ -8,5 +8,5 @@ return [
|
||||
'responses' => [
|
||||
'success' => 'Your message has been sent successfully',
|
||||
'error' => 'An error occurred while sending the message',
|
||||
]
|
||||
],
|
||||
];
|
||||
|
||||
@@ -9,6 +9,6 @@ return [
|
||||
'description' => 'Description',
|
||||
'created_at' => 'Created at',
|
||||
'Updated at' => 'Updated at',
|
||||
'view_members' => 'View members of this group'
|
||||
'view_members' => 'View members of this group',
|
||||
],
|
||||
];
|
||||
|
||||
@@ -8,5 +8,5 @@ return [
|
||||
'name' => 'Name',
|
||||
'description' => 'Description',
|
||||
'is_active' => 'Active',
|
||||
]
|
||||
],
|
||||
];
|
||||
|
||||
@@ -9,5 +9,5 @@ return [
|
||||
'description' => 'Description',
|
||||
'url' => 'URL',
|
||||
'icon' => 'Icon',
|
||||
]
|
||||
],
|
||||
];
|
||||
|
||||
@@ -8,5 +8,5 @@ return [
|
||||
'responses' => [
|
||||
'success' => 'Votre message a été envoyé avec succès. Nous vous recontacterons dans les plus brefs délais.',
|
||||
'error' => 'Une erreur est survenue lors de l\'envoi du message',
|
||||
]
|
||||
],
|
||||
];
|
||||
|
||||
@@ -9,6 +9,6 @@ return [
|
||||
'description' => 'Description',
|
||||
'created_at' => 'Créé le',
|
||||
'updated_at' => 'Mis à jour le',
|
||||
'view_members' => 'Voir les membres de ce groupe'
|
||||
'view_members' => 'Voir les membres de ce groupe',
|
||||
],
|
||||
];
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'fields' => [
|
||||
'fields' => [
|
||||
'package' => 'Package',
|
||||
'packages' => 'Packages',
|
||||
'identifier' => 'Identifiant',
|
||||
'name' => 'Nom',
|
||||
'description' => 'Description',
|
||||
'is_active' => 'Actif',
|
||||
]
|
||||
],
|
||||
];
|
||||
|
||||
0
memberships
Normal file
0
memberships
Normal file
@@ -2,7 +2,24 @@
|
||||
|
||||
use Illuminate\Foundation\Inspiring;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\Schedule;
|
||||
|
||||
Artisan::command('inspire', function () {
|
||||
$this->comment(Inspiring::quote());
|
||||
})->purpose('Display an inspiring quote');
|
||||
|
||||
Schedule::command('members:renewal-process --phase=reminder_1')
|
||||
->yearlyOn(12, 1, '09:00')
|
||||
->description('1ère relance renouvellement adhésion');
|
||||
|
||||
Schedule::command('members:renewal-process --phase=reminder_2')
|
||||
->yearlyOn(12, 15, '09:00')
|
||||
->description('2ème relance renouvellement adhésion');
|
||||
|
||||
Schedule::command('members:renewal-process --phase=reminder_3')
|
||||
->yearlyOn(1, 5, '09:00')
|
||||
->description('3ème relance renouvellement adhésion');
|
||||
|
||||
Schedule::command('members:renewal-process --phase=deactivate')
|
||||
->yearlyOn(1, 28, '09:00')
|
||||
->description('Désactivation comptes non renouvelés');
|
||||
|
||||
Reference in New Issue
Block a user