feat(Mail template & Membership relationship)
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
Tu es un agent IA senior specialise en Laravel, Filament, React et Tailwind.
|
||||
|
||||
Tu respectes STRICTEMENT les regles du fichier .ai-rules.md.
|
||||
Tu respectes STRICTEMENT les regles du fichier .ai-rules.md et tu parcours bien le ficher .PROJECT_STRUCTURE.md pour avoir le contexte du projet.
|
||||
|
||||
Ton role :
|
||||
- proposer des solutions simples et maintenables
|
||||
|
||||
@@ -3,7 +3,13 @@
|
||||
"allow": [
|
||||
"mcp__laravel-boost__application-info",
|
||||
"mcp__laravel-boost__database-schema",
|
||||
"mcp__laravel-boost__list-routes"
|
||||
"mcp__laravel-boost__list-routes",
|
||||
"Bash(php artisan make:migration:*)",
|
||||
"mcp__laravel-boost__search-docs",
|
||||
"Bash(vendor/bin/pint:*)",
|
||||
"Bash(php artisan test:*)",
|
||||
"Bash(php artisan migrate:*)",
|
||||
"Bash(php artisan make:filament-relation-manager:*)"
|
||||
]
|
||||
},
|
||||
"enableAllProjectMcpServers": true,
|
||||
|
||||
@@ -2,35 +2,13 @@
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use Illuminate\Broadcasting\Channel;
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Broadcasting\PresenceChannel;
|
||||
use Illuminate\Broadcasting\PrivateChannel;
|
||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||
use App\Models\Member;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class MemberRegistered
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
use Dispatchable, SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the channels the event should broadcast on.
|
||||
*
|
||||
* @return array<int, \Illuminate\Broadcasting\Channel>
|
||||
*/
|
||||
public function broadcastOn(): array
|
||||
{
|
||||
return [
|
||||
new PrivateChannel('channel-name'),
|
||||
];
|
||||
}
|
||||
public function __construct(public readonly Member $member) {}
|
||||
}
|
||||
|
||||
@@ -2,35 +2,13 @@
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use Illuminate\Broadcasting\Channel;
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Broadcasting\PresenceChannel;
|
||||
use Illuminate\Broadcasting\PrivateChannel;
|
||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||
use App\Models\Member;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class MemberValidated
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
use Dispatchable, SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the channels the event should broadcast on.
|
||||
*
|
||||
* @return array<int, \Illuminate\Broadcasting\Channel>
|
||||
*/
|
||||
public function broadcastOn(): array
|
||||
{
|
||||
return [
|
||||
new PrivateChannel('channel-name'),
|
||||
];
|
||||
}
|
||||
public function __construct(public readonly Member $member) {}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ class MemberResource extends Resource
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
RelationManagers\MembershipsRelationManager::class,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -65,5 +65,4 @@ class MemberResource extends Resource
|
||||
MemberCount::class,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Members\RelationManagers;
|
||||
|
||||
use App\Models\Membership;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class MembershipsRelationManager extends RelationManager
|
||||
{
|
||||
protected static string $relationship = 'memberships';
|
||||
|
||||
protected static ?string $title = 'Adhésions';
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->recordTitleAttribute('start_date')
|
||||
->columns([
|
||||
TextColumn::make('start_date')
|
||||
->label(Membership::getAttributeLabel('start_date'))
|
||||
->date()
|
||||
->sortable(),
|
||||
TextColumn::make('end_date')
|
||||
->label(Membership::getAttributeLabel('end_date'))
|
||||
->date()
|
||||
->sortable(),
|
||||
TextColumn::make('status')
|
||||
->label(Membership::getAttributeLabel('status'))
|
||||
->formatStateUsing(fn (string $state) => Membership::getAttributeLabel($state))
|
||||
->badge()
|
||||
->color(fn (string $state): string => match ($state) {
|
||||
'active' => 'success',
|
||||
'expired' => 'danger',
|
||||
'pending' => 'warning',
|
||||
}),
|
||||
TextColumn::make('package.name')
|
||||
->label(Membership::getAttributeLabel('package_id')),
|
||||
TextColumn::make('amount')
|
||||
->label(Membership::getAttributeLabel('amount'))
|
||||
->money('EUR')
|
||||
->sortable(),
|
||||
TextColumn::make('payment_status')
|
||||
->label(Membership::getAttributeLabel('payment_status'))
|
||||
->formatStateUsing(fn (string $state) => Membership::getAttributeLabel($state))
|
||||
->badge()
|
||||
->color(fn (string $state): string => match ($state) {
|
||||
'paid' => 'success',
|
||||
'unpaid' => 'danger',
|
||||
'partial' => 'warning',
|
||||
}),
|
||||
TextColumn::make('created_at')
|
||||
->label(Membership::getAttributeLabel('created_at'))
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
])
|
||||
->defaultSort('start_date', 'desc')
|
||||
->headerActions([
|
||||
CreateAction::make(),
|
||||
])
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
DeleteAction::make(),
|
||||
])
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\NotificationTemplates;
|
||||
|
||||
use App\Filament\Resources\NotificationTemplates\Pages\CreateNotificationTemplate;
|
||||
use App\Filament\Resources\NotificationTemplates\Pages\EditNotificationTemplate;
|
||||
use App\Filament\Resources\NotificationTemplates\Pages\ListNotificationTemplates;
|
||||
use App\Filament\Resources\NotificationTemplates\Schemas\NotificationTemplateForm;
|
||||
use App\Filament\Resources\NotificationTemplates\Tables\NotificationTemplatesTable;
|
||||
use App\Models\NotificationTemplate;
|
||||
use BackedEnum;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class NotificationTemplateResource extends Resource
|
||||
{
|
||||
protected static ?string $model = NotificationTemplate::class;
|
||||
|
||||
protected static string|null|\UnitEnum $navigationGroup = 'Administration';
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedEnvelope;
|
||||
|
||||
public static function getModelLabel(): string
|
||||
{
|
||||
return NotificationTemplate::getAttributeLabel('singular_name');
|
||||
}
|
||||
|
||||
public static function getPluralModelLabel(): string
|
||||
{
|
||||
return NotificationTemplate::getAttributeLabel('plural_name');
|
||||
}
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return NotificationTemplateForm::configure($schema);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return NotificationTemplatesTable::configure($table);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ListNotificationTemplates::route('/'),
|
||||
'create' => CreateNotificationTemplate::route('/create'),
|
||||
'edit' => EditNotificationTemplate::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\NotificationTemplates\Pages;
|
||||
|
||||
use App\Filament\Resources\NotificationTemplates\NotificationTemplateResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateNotificationTemplate extends CreateRecord
|
||||
{
|
||||
protected static string $resource = NotificationTemplateResource::class;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\NotificationTemplates\Pages;
|
||||
|
||||
use App\Filament\Resources\NotificationTemplates\NotificationTemplateResource;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\ForceDeleteAction;
|
||||
use Filament\Actions\RestoreAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditNotificationTemplate extends EditRecord
|
||||
{
|
||||
protected static string $resource = NotificationTemplateResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make(),
|
||||
ForceDeleteAction::make(),
|
||||
RestoreAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\NotificationTemplates\Pages;
|
||||
|
||||
use App\Filament\Resources\NotificationTemplates\NotificationTemplateResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListNotificationTemplates extends ListRecords
|
||||
{
|
||||
protected static string $resource = NotificationTemplateResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\NotificationTemplates\Schemas;
|
||||
|
||||
use App\Models\NotificationTemplate;
|
||||
use Filament\Forms\Components\RichEditor;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Schema;
|
||||
|
||||
class NotificationTemplateForm
|
||||
{
|
||||
public static function configure(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Section::make(fn (?NotificationTemplate $record) => $record?->name ?? NotificationTemplate::getAttributeLabel('name'))
|
||||
->afterHeader([
|
||||
Toggle::make('is_active')
|
||||
->label(NotificationTemplate::getAttributeLabel('is_active'))
|
||||
->default(true),
|
||||
])
|
||||
->schema([
|
||||
TextInput::make('name')
|
||||
->label(NotificationTemplate::getAttributeLabel('name'))
|
||||
->required(),
|
||||
TextInput::make('identifier')
|
||||
->label(NotificationTemplate::getAttributeLabel('identifier'))
|
||||
->required()
|
||||
->disabledOn('edit'),
|
||||
TextInput::make('subject')
|
||||
->label(NotificationTemplate::getAttributeLabel('subject'))
|
||||
->required()
|
||||
->helperText('Variables : {member_name}, {expiry_date}'),
|
||||
RichEditor::make('body')
|
||||
->label(NotificationTemplate::getAttributeLabel('body'))
|
||||
->required()
|
||||
->helperText('Variables : {member_name}, {expiry_date}')
|
||||
->columnSpanFull(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\NotificationTemplates\Tables;
|
||||
|
||||
use App\Models\NotificationTemplate;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Actions\ForceDeleteBulkAction;
|
||||
use Filament\Actions\RestoreBulkAction;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class NotificationTemplatesTable
|
||||
{
|
||||
public static function configure(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('name')
|
||||
->label(NotificationTemplate::getAttributeLabel('name'))
|
||||
->searchable(),
|
||||
TextColumn::make('identifier')
|
||||
->label(NotificationTemplate::getAttributeLabel('identifier'))
|
||||
->searchable(),
|
||||
TextColumn::make('subject')
|
||||
->label(NotificationTemplate::getAttributeLabel('subject'))
|
||||
->searchable(),
|
||||
IconColumn::make('is_active')
|
||||
->label(NotificationTemplate::getAttributeLabel('is_active'))
|
||||
->boolean(),
|
||||
TextColumn::make('updated_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
])
|
||||
->filters([])
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
])
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
ForceDeleteBulkAction::make(),
|
||||
RestoreBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\Member;
|
||||
use App\Models\NotificationTemplate;
|
||||
use App\Notifications\SubscriptionExpiredPhase1;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Queue\Queueable;
|
||||
@@ -11,22 +12,21 @@ class SendSubscriptionExpiredPhase1Notifications implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
public function __construct() {}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
Member::isExpired()
|
||||
->chunk(100, function ($members) {
|
||||
$template = NotificationTemplate::findByIdentifier('subscription_expired_phase1');
|
||||
|
||||
if (! $template) {
|
||||
return;
|
||||
}
|
||||
|
||||
Member::query()
|
||||
->whereHas('memberships', fn ($query) => $query->where('status', 'expired'))
|
||||
->chunk(100, function ($members) use ($template) {
|
||||
foreach ($members as $member) {
|
||||
$member->notify(new SubscriptionExpiredPhase1());
|
||||
$member->notify(new SubscriptionExpiredPhase1($template));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Listeners;
|
||||
|
||||
use App\Events\MemberValidated;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
|
||||
class NotifiyMemberOfValidation
|
||||
{
|
||||
/**
|
||||
* Create the event listener.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*/
|
||||
public function handle(MemberValidated $event): void
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Listeners;
|
||||
|
||||
use App\Events\MemberRegistered;
|
||||
use App\Models\User;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
|
||||
class NotifyAdminForMembershipRequest
|
||||
{
|
||||
/**
|
||||
* Create the event listener.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*/
|
||||
public function handle(MemberRegistered $event): void
|
||||
{
|
||||
$admin = User::where('name', 'SuperAdmin')->first();
|
||||
|
||||
$admin->notify(new AdminNewUserPending($event->user));
|
||||
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\IspconfigType;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
@@ -46,6 +45,7 @@ use Illuminate\Notifications\Notifiable;
|
||||
* @property-read \Illuminate\Notifications\DatabaseNotificationCollection<int, \Illuminate\Notifications\DatabaseNotification> $notifications
|
||||
* @property-read int|null $notifications_count
|
||||
* @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()
|
||||
@@ -75,11 +75,13 @@ 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)
|
||||
*
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class Member extends Model
|
||||
{
|
||||
use HasFactory, Notifiable;
|
||||
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'dolibarr_id',
|
||||
@@ -100,7 +102,7 @@ class Member extends Model
|
||||
'phone1',
|
||||
'phone2',
|
||||
'public_membership',
|
||||
'website_url'
|
||||
'website_url',
|
||||
];
|
||||
|
||||
public static function getAttributeLabel(string $attribute): string
|
||||
@@ -113,9 +115,10 @@ class Member extends Model
|
||||
return "{$this->firstname} {$this->lastname}";
|
||||
}
|
||||
|
||||
public function getRetzienEmailAttribute(): string
|
||||
public function getRetzienEmailAttribute(): ?string
|
||||
{
|
||||
$emails = explode(';', $this->email);
|
||||
|
||||
return collect($emails)->filter(fn ($email) => str_contains($email, '@retzien.fr'))->first();
|
||||
}
|
||||
|
||||
@@ -152,6 +155,7 @@ class Member extends Model
|
||||
public function hasService(string $serviceIdentifier): bool
|
||||
{
|
||||
$membership = $this->lastMembership();
|
||||
|
||||
return $membership->services()->where('identifier', $serviceIdentifier)->exists();
|
||||
}
|
||||
|
||||
@@ -159,7 +163,7 @@ class Member extends Model
|
||||
{
|
||||
// Member ayant leur dernière adhésion non renouvellée de puis plus d'un mois
|
||||
$lastMembership = $this->lastMembership();
|
||||
|
||||
return $lastMembership->status === 'expired' || $lastMembership->created_at->addMonths(1) < now();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
64
app/Models/NotificationTemplate.php
Normal file
64
app/Models/NotificationTemplate.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class NotificationTemplate extends Model
|
||||
{
|
||||
use HasFactory, SoftDeletes;
|
||||
|
||||
protected $fillable = [
|
||||
'identifier',
|
||||
'name',
|
||||
'subject',
|
||||
'body',
|
||||
'variables',
|
||||
'is_active',
|
||||
];
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'variables' => 'array',
|
||||
'is_active' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
public static function getAttributeLabel(string $attribute): string
|
||||
{
|
||||
return __('notification_templates.fields.'.$attribute);
|
||||
}
|
||||
|
||||
public static function findByIdentifier(string $identifier): ?self
|
||||
{
|
||||
return self::query()
|
||||
->where('identifier', $identifier)
|
||||
->where('is_active', true)
|
||||
->first();
|
||||
}
|
||||
|
||||
public function renderSubject(array $vars): string
|
||||
{
|
||||
return $this->replacePlaceholders($this->subject, $vars);
|
||||
}
|
||||
|
||||
public function renderBody(array $vars): string
|
||||
{
|
||||
return $this->replacePlaceholders($this->body, $vars);
|
||||
}
|
||||
|
||||
private function replacePlaceholders(string $text, array $vars): string
|
||||
{
|
||||
foreach ($vars as $key => $value) {
|
||||
$text = str_replace('{'.$key.'}', (string) $value, $text);
|
||||
}
|
||||
|
||||
return $text;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use App\Models\NotificationTemplate;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
@@ -11,17 +12,9 @@ class SubscriptionExpiredPhase1 extends Notification implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
/**
|
||||
* Create a new notification instance.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
public function __construct(public readonly NotificationTemplate $template) {}
|
||||
|
||||
/**
|
||||
* Get the notification's delivery channels.
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function via(object $notifiable): array
|
||||
@@ -29,30 +22,27 @@ class SubscriptionExpiredPhase1 extends Notification implements ShouldQueue
|
||||
return ['mail'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mail representation of the notification.
|
||||
*/
|
||||
public function toMail(object $notifiable): MailMessage
|
||||
{
|
||||
//@todo: créer template générique + espace dans le BO pour alimenter les texte
|
||||
$lastMembership = $notifiable->memberships()->latest()->first();
|
||||
|
||||
$vars = [
|
||||
'member_name' => $notifiable->full_name,
|
||||
'expiry_date' => $lastMembership?->end_date ?? '',
|
||||
];
|
||||
|
||||
return (new MailMessage)
|
||||
->subject('Votre adhésion est expirée')
|
||||
->greeting('Bonjour ' . $notifiable->name)
|
||||
->line('Votre adhésion est arrivée à expiration.')
|
||||
->line('Pour continuer à profiter nos services, merci de le renouveler.')
|
||||
->action('Renouveler mon adhésion', url('/devenir-membre'))
|
||||
->line('Merci pour votre confiance.');
|
||||
->subject($this->template->renderSubject($vars))
|
||||
->view('notifications.mail-template', [
|
||||
'body' => $this->template->renderBody($vars),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array representation of the notification.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(object $notifiable): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
69
app/Policies/NotificationTemplatePolicy.php
Normal file
69
app/Policies/NotificationTemplatePolicy.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Models\NotificationTemplate;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
use Illuminate\Foundation\Auth\User as AuthUser;
|
||||
|
||||
class NotificationTemplatePolicy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
public function viewAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('ViewAny:NotificationTemplate');
|
||||
}
|
||||
|
||||
public function view(AuthUser $authUser, NotificationTemplate $notificationTemplate): bool
|
||||
{
|
||||
return $authUser->can('View:NotificationTemplate');
|
||||
}
|
||||
|
||||
public function create(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('Create:NotificationTemplate');
|
||||
}
|
||||
|
||||
public function update(AuthUser $authUser, NotificationTemplate $notificationTemplate): bool
|
||||
{
|
||||
return $authUser->can('Update:NotificationTemplate');
|
||||
}
|
||||
|
||||
public function delete(AuthUser $authUser, NotificationTemplate $notificationTemplate): bool
|
||||
{
|
||||
return $authUser->can('Delete:NotificationTemplate');
|
||||
}
|
||||
|
||||
public function restore(AuthUser $authUser, NotificationTemplate $notificationTemplate): bool
|
||||
{
|
||||
return $authUser->can('Restore:NotificationTemplate');
|
||||
}
|
||||
|
||||
public function forceDelete(AuthUser $authUser, NotificationTemplate $notificationTemplate): bool
|
||||
{
|
||||
return $authUser->can('ForceDelete:NotificationTemplate');
|
||||
}
|
||||
|
||||
public function forceDeleteAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('ForceDeleteAny:NotificationTemplate');
|
||||
}
|
||||
|
||||
public function restoreAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('RestoreAny:NotificationTemplate');
|
||||
}
|
||||
|
||||
public function replicate(AuthUser $authUser, NotificationTemplate $notificationTemplate): bool
|
||||
{
|
||||
return $authUser->can('Replicate:NotificationTemplate');
|
||||
}
|
||||
|
||||
public function reorder(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('Reorder:NotificationTemplate');
|
||||
}
|
||||
}
|
||||
@@ -11,8 +11,6 @@ class MemberService
|
||||
{
|
||||
/**
|
||||
* Register a new member.
|
||||
* @param array $data
|
||||
* @return Member
|
||||
*/
|
||||
public function registerNewMember(array $data): Member
|
||||
{
|
||||
@@ -21,7 +19,7 @@ class MemberService
|
||||
|
||||
if (! $member) {
|
||||
// Create a new member
|
||||
$member = new Member();
|
||||
$member = new Member;
|
||||
$member->status = 'pending';
|
||||
$member->nature = 'physical';
|
||||
$member->group_id = MemberGroup::where('identifier', 'website')->first()->id ?? null;
|
||||
@@ -50,10 +48,7 @@ class MemberService
|
||||
|
||||
]);
|
||||
|
||||
// Notify Admin
|
||||
$admin = Member::where('role', 'admin')->first();
|
||||
event(new MemberRegistered($admin));
|
||||
|
||||
event(new MemberRegistered($member));
|
||||
|
||||
return $member;
|
||||
}
|
||||
|
||||
33
database/factories/NotificationTemplateFactory.php
Normal file
33
database/factories/NotificationTemplateFactory.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\NotificationTemplate;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* @extends Factory<NotificationTemplate>
|
||||
*/
|
||||
class NotificationTemplateFactory extends Factory
|
||||
{
|
||||
protected $model = NotificationTemplate::class;
|
||||
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'identifier' => $this->faker->unique()->slug(2),
|
||||
'name' => $this->faker->sentence(3),
|
||||
'subject' => $this->faker->sentence(),
|
||||
'body' => $this->faker->paragraph(),
|
||||
'variables' => ['name' => 'Nom'],
|
||||
'is_active' => true,
|
||||
];
|
||||
}
|
||||
|
||||
public function inactive(): static
|
||||
{
|
||||
return $this->state(fn (array $attributes) => [
|
||||
'is_active' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('notification_templates', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('identifier')->unique();
|
||||
$table->string('name');
|
||||
$table->string('subject');
|
||||
$table->longText('body');
|
||||
$table->json('variables')->nullable();
|
||||
$table->boolean('is_active')->default(true);
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('notification_templates');
|
||||
}
|
||||
};
|
||||
@@ -3,10 +3,9 @@
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\MemberGroup;
|
||||
use App\Models\Package;
|
||||
use App\Models\Service;
|
||||
use App\Models\User;
|
||||
use App\Models\Package;
|
||||
|
||||
// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
@@ -36,11 +35,11 @@ class DatabaseSeeder extends Seeder
|
||||
]);
|
||||
|
||||
$websiteGroup = MemberGroup::updateOrCreate([
|
||||
'name' => 'Site Web'
|
||||
'name' => 'Site Web',
|
||||
],
|
||||
[
|
||||
'identifier' => 'website',
|
||||
'description' => 'Groupe d\'utilisateurs provenant du site web.'
|
||||
'description' => 'Groupe d\'utilisateurs provenant du site web.',
|
||||
]);
|
||||
|
||||
// Subscription packages
|
||||
@@ -49,20 +48,20 @@ class DatabaseSeeder extends Seeder
|
||||
'identifier' => 'custom',
|
||||
'name' => 'Sur-mesure',
|
||||
'description' => 'Calcul du nombre de mois restant dans l\'année',
|
||||
'price' => '1.00'
|
||||
'price' => '1.00',
|
||||
],
|
||||
[
|
||||
'identifier' => 'one-year',
|
||||
'name' => 'Un an',
|
||||
'description' => '12 mois à compter de la date de validation de l\'adhésion du membre',
|
||||
'price' => '12.00'
|
||||
'price' => '12.00',
|
||||
],
|
||||
[
|
||||
'identifier' => 'two-years',
|
||||
'name' => 'Deux ans',
|
||||
'description' => '24 mois à compter de la date de validation de l\'adhésion du membre',
|
||||
'price' => '24.00'
|
||||
]
|
||||
'price' => '24.00',
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($packages as $package) {
|
||||
@@ -112,7 +111,7 @@ class DatabaseSeeder extends Seeder
|
||||
'description' => 'Service d\'hébergement web',
|
||||
'url' => '#',
|
||||
'icon' => 'database',
|
||||
]
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($services as $service) {
|
||||
@@ -126,6 +125,9 @@ class DatabaseSeeder extends Seeder
|
||||
]);
|
||||
}
|
||||
|
||||
// Notification templates
|
||||
$this->call(NotificationTemplateSeeder::class);
|
||||
|
||||
// JaneDoe
|
||||
$userTest = User::updateOrCreate([
|
||||
'name' => 'JaneDoe',
|
||||
|
||||
29
database/seeders/NotificationTemplateSeeder.php
Normal file
29
database/seeders/NotificationTemplateSeeder.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\NotificationTemplate;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class NotificationTemplateSeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
NotificationTemplate::updateOrCreate(
|
||||
['identifier' => 'subscription_expired_phase1'],
|
||||
[
|
||||
'name' => 'Adhésion expirée - Phase 1',
|
||||
'subject' => 'Votre adhésion est expirée',
|
||||
'body' => '<p>Bonjour {member_name},</p>'
|
||||
.'<p>Votre adhésion est arrivée à expiration le {expiry_date}.</p>'
|
||||
.'<p>Pour continuer à profiter de nos services, merci de la renouveler.</p>'
|
||||
.'<p>Merci pour votre confiance.</p>',
|
||||
'variables' => [
|
||||
'member_name' => 'Nom complet du membre',
|
||||
'expiry_date' => 'Date de fin d\'adhésion',
|
||||
],
|
||||
'is_active' => true,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
14
lang/fr/notification_templates.php
Normal file
14
lang/fr/notification_templates.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'fields' => [
|
||||
'singular_name' => 'Template Mail',
|
||||
'plural_name' => 'Templates Mail',
|
||||
'identifier' => 'Identifiant',
|
||||
'name' => 'Nom',
|
||||
'subject' => 'Objet',
|
||||
'body' => 'Contenu',
|
||||
'variables' => 'Variables disponibles',
|
||||
'is_active' => 'Actif',
|
||||
],
|
||||
];
|
||||
@@ -0,0 +1,83 @@
|
||||
import { queryParams, type RouteQueryOptions, type RouteDefinition, type RouteFormDefinition } from './../../../../../../wayfinder'
|
||||
/**
|
||||
* @see \App\Filament\Resources\NotificationTemplates\Pages\CreateNotificationTemplate::__invoke
|
||||
* @see app/Filament/Resources/NotificationTemplates/Pages/CreateNotificationTemplate.php:7
|
||||
* @route '/admin/notification-templates/create'
|
||||
*/
|
||||
const CreateNotificationTemplate = (options?: RouteQueryOptions): RouteDefinition<'get'> => ({
|
||||
url: CreateNotificationTemplate.url(options),
|
||||
method: 'get',
|
||||
})
|
||||
|
||||
CreateNotificationTemplate.definition = {
|
||||
methods: ["get","head"],
|
||||
url: '/admin/notification-templates/create',
|
||||
} satisfies RouteDefinition<["get","head"]>
|
||||
|
||||
/**
|
||||
* @see \App\Filament\Resources\NotificationTemplates\Pages\CreateNotificationTemplate::__invoke
|
||||
* @see app/Filament/Resources/NotificationTemplates/Pages/CreateNotificationTemplate.php:7
|
||||
* @route '/admin/notification-templates/create'
|
||||
*/
|
||||
CreateNotificationTemplate.url = (options?: RouteQueryOptions) => {
|
||||
return CreateNotificationTemplate.definition.url + queryParams(options)
|
||||
}
|
||||
|
||||
/**
|
||||
* @see \App\Filament\Resources\NotificationTemplates\Pages\CreateNotificationTemplate::__invoke
|
||||
* @see app/Filament/Resources/NotificationTemplates/Pages/CreateNotificationTemplate.php:7
|
||||
* @route '/admin/notification-templates/create'
|
||||
*/
|
||||
CreateNotificationTemplate.get = (options?: RouteQueryOptions): RouteDefinition<'get'> => ({
|
||||
url: CreateNotificationTemplate.url(options),
|
||||
method: 'get',
|
||||
})
|
||||
|
||||
/**
|
||||
* @see \App\Filament\Resources\NotificationTemplates\Pages\CreateNotificationTemplate::__invoke
|
||||
* @see app/Filament/Resources/NotificationTemplates/Pages/CreateNotificationTemplate.php:7
|
||||
* @route '/admin/notification-templates/create'
|
||||
*/
|
||||
CreateNotificationTemplate.head = (options?: RouteQueryOptions): RouteDefinition<'head'> => ({
|
||||
url: CreateNotificationTemplate.url(options),
|
||||
method: 'head',
|
||||
})
|
||||
|
||||
/**
|
||||
* @see \App\Filament\Resources\NotificationTemplates\Pages\CreateNotificationTemplate::__invoke
|
||||
* @see app/Filament/Resources/NotificationTemplates/Pages/CreateNotificationTemplate.php:7
|
||||
* @route '/admin/notification-templates/create'
|
||||
*/
|
||||
const CreateNotificationTemplateForm = (options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({
|
||||
action: CreateNotificationTemplate.url(options),
|
||||
method: 'get',
|
||||
})
|
||||
|
||||
/**
|
||||
* @see \App\Filament\Resources\NotificationTemplates\Pages\CreateNotificationTemplate::__invoke
|
||||
* @see app/Filament/Resources/NotificationTemplates/Pages/CreateNotificationTemplate.php:7
|
||||
* @route '/admin/notification-templates/create'
|
||||
*/
|
||||
CreateNotificationTemplateForm.get = (options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({
|
||||
action: CreateNotificationTemplate.url(options),
|
||||
method: 'get',
|
||||
})
|
||||
|
||||
/**
|
||||
* @see \App\Filament\Resources\NotificationTemplates\Pages\CreateNotificationTemplate::__invoke
|
||||
* @see app/Filament/Resources/NotificationTemplates/Pages/CreateNotificationTemplate.php:7
|
||||
* @route '/admin/notification-templates/create'
|
||||
*/
|
||||
CreateNotificationTemplateForm.head = (options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({
|
||||
action: CreateNotificationTemplate.url({
|
||||
[options?.mergeQuery ? 'mergeQuery' : 'query']: {
|
||||
_method: 'HEAD',
|
||||
...(options?.query ?? options?.mergeQuery ?? {}),
|
||||
}
|
||||
}),
|
||||
method: 'get',
|
||||
})
|
||||
|
||||
CreateNotificationTemplate.form = CreateNotificationTemplateForm
|
||||
|
||||
export default CreateNotificationTemplate
|
||||
@@ -0,0 +1,101 @@
|
||||
import { queryParams, type RouteQueryOptions, type RouteDefinition, type RouteFormDefinition, applyUrlDefaults } from './../../../../../../wayfinder'
|
||||
/**
|
||||
* @see \App\Filament\Resources\NotificationTemplates\Pages\EditNotificationTemplate::__invoke
|
||||
* @see app/Filament/Resources/NotificationTemplates/Pages/EditNotificationTemplate.php:7
|
||||
* @route '/admin/notification-templates/{record}/edit'
|
||||
*/
|
||||
const EditNotificationTemplate = (args: { record: string | number } | [record: string | number ] | string | number, options?: RouteQueryOptions): RouteDefinition<'get'> => ({
|
||||
url: EditNotificationTemplate.url(args, options),
|
||||
method: 'get',
|
||||
})
|
||||
|
||||
EditNotificationTemplate.definition = {
|
||||
methods: ["get","head"],
|
||||
url: '/admin/notification-templates/{record}/edit',
|
||||
} satisfies RouteDefinition<["get","head"]>
|
||||
|
||||
/**
|
||||
* @see \App\Filament\Resources\NotificationTemplates\Pages\EditNotificationTemplate::__invoke
|
||||
* @see app/Filament/Resources/NotificationTemplates/Pages/EditNotificationTemplate.php:7
|
||||
* @route '/admin/notification-templates/{record}/edit'
|
||||
*/
|
||||
EditNotificationTemplate.url = (args: { record: string | number } | [record: string | number ] | string | number, options?: RouteQueryOptions) => {
|
||||
if (typeof args === 'string' || typeof args === 'number') {
|
||||
args = { record: args }
|
||||
}
|
||||
|
||||
if (Array.isArray(args)) {
|
||||
args = {
|
||||
record: args[0],
|
||||
}
|
||||
}
|
||||
|
||||
args = applyUrlDefaults(args)
|
||||
|
||||
const parsedArgs = {
|
||||
record: args.record,
|
||||
}
|
||||
|
||||
return EditNotificationTemplate.definition.url
|
||||
.replace('{record}', parsedArgs.record.toString())
|
||||
.replace(/\/+$/, '') + queryParams(options)
|
||||
}
|
||||
|
||||
/**
|
||||
* @see \App\Filament\Resources\NotificationTemplates\Pages\EditNotificationTemplate::__invoke
|
||||
* @see app/Filament/Resources/NotificationTemplates/Pages/EditNotificationTemplate.php:7
|
||||
* @route '/admin/notification-templates/{record}/edit'
|
||||
*/
|
||||
EditNotificationTemplate.get = (args: { record: string | number } | [record: string | number ] | string | number, options?: RouteQueryOptions): RouteDefinition<'get'> => ({
|
||||
url: EditNotificationTemplate.url(args, options),
|
||||
method: 'get',
|
||||
})
|
||||
|
||||
/**
|
||||
* @see \App\Filament\Resources\NotificationTemplates\Pages\EditNotificationTemplate::__invoke
|
||||
* @see app/Filament/Resources/NotificationTemplates/Pages/EditNotificationTemplate.php:7
|
||||
* @route '/admin/notification-templates/{record}/edit'
|
||||
*/
|
||||
EditNotificationTemplate.head = (args: { record: string | number } | [record: string | number ] | string | number, options?: RouteQueryOptions): RouteDefinition<'head'> => ({
|
||||
url: EditNotificationTemplate.url(args, options),
|
||||
method: 'head',
|
||||
})
|
||||
|
||||
/**
|
||||
* @see \App\Filament\Resources\NotificationTemplates\Pages\EditNotificationTemplate::__invoke
|
||||
* @see app/Filament/Resources/NotificationTemplates/Pages/EditNotificationTemplate.php:7
|
||||
* @route '/admin/notification-templates/{record}/edit'
|
||||
*/
|
||||
const EditNotificationTemplateForm = (args: { record: string | number } | [record: string | number ] | string | number, options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({
|
||||
action: EditNotificationTemplate.url(args, options),
|
||||
method: 'get',
|
||||
})
|
||||
|
||||
/**
|
||||
* @see \App\Filament\Resources\NotificationTemplates\Pages\EditNotificationTemplate::__invoke
|
||||
* @see app/Filament/Resources/NotificationTemplates/Pages/EditNotificationTemplate.php:7
|
||||
* @route '/admin/notification-templates/{record}/edit'
|
||||
*/
|
||||
EditNotificationTemplateForm.get = (args: { record: string | number } | [record: string | number ] | string | number, options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({
|
||||
action: EditNotificationTemplate.url(args, options),
|
||||
method: 'get',
|
||||
})
|
||||
|
||||
/**
|
||||
* @see \App\Filament\Resources\NotificationTemplates\Pages\EditNotificationTemplate::__invoke
|
||||
* @see app/Filament/Resources/NotificationTemplates/Pages/EditNotificationTemplate.php:7
|
||||
* @route '/admin/notification-templates/{record}/edit'
|
||||
*/
|
||||
EditNotificationTemplateForm.head = (args: { record: string | number } | [record: string | number ] | string | number, options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({
|
||||
action: EditNotificationTemplate.url(args, {
|
||||
[options?.mergeQuery ? 'mergeQuery' : 'query']: {
|
||||
_method: 'HEAD',
|
||||
...(options?.query ?? options?.mergeQuery ?? {}),
|
||||
}
|
||||
}),
|
||||
method: 'get',
|
||||
})
|
||||
|
||||
EditNotificationTemplate.form = EditNotificationTemplateForm
|
||||
|
||||
export default EditNotificationTemplate
|
||||
@@ -0,0 +1,83 @@
|
||||
import { queryParams, type RouteQueryOptions, type RouteDefinition, type RouteFormDefinition } from './../../../../../../wayfinder'
|
||||
/**
|
||||
* @see \App\Filament\Resources\NotificationTemplates\Pages\ListNotificationTemplates::__invoke
|
||||
* @see app/Filament/Resources/NotificationTemplates/Pages/ListNotificationTemplates.php:7
|
||||
* @route '/admin/notification-templates'
|
||||
*/
|
||||
const ListNotificationTemplates = (options?: RouteQueryOptions): RouteDefinition<'get'> => ({
|
||||
url: ListNotificationTemplates.url(options),
|
||||
method: 'get',
|
||||
})
|
||||
|
||||
ListNotificationTemplates.definition = {
|
||||
methods: ["get","head"],
|
||||
url: '/admin/notification-templates',
|
||||
} satisfies RouteDefinition<["get","head"]>
|
||||
|
||||
/**
|
||||
* @see \App\Filament\Resources\NotificationTemplates\Pages\ListNotificationTemplates::__invoke
|
||||
* @see app/Filament/Resources/NotificationTemplates/Pages/ListNotificationTemplates.php:7
|
||||
* @route '/admin/notification-templates'
|
||||
*/
|
||||
ListNotificationTemplates.url = (options?: RouteQueryOptions) => {
|
||||
return ListNotificationTemplates.definition.url + queryParams(options)
|
||||
}
|
||||
|
||||
/**
|
||||
* @see \App\Filament\Resources\NotificationTemplates\Pages\ListNotificationTemplates::__invoke
|
||||
* @see app/Filament/Resources/NotificationTemplates/Pages/ListNotificationTemplates.php:7
|
||||
* @route '/admin/notification-templates'
|
||||
*/
|
||||
ListNotificationTemplates.get = (options?: RouteQueryOptions): RouteDefinition<'get'> => ({
|
||||
url: ListNotificationTemplates.url(options),
|
||||
method: 'get',
|
||||
})
|
||||
|
||||
/**
|
||||
* @see \App\Filament\Resources\NotificationTemplates\Pages\ListNotificationTemplates::__invoke
|
||||
* @see app/Filament/Resources/NotificationTemplates/Pages/ListNotificationTemplates.php:7
|
||||
* @route '/admin/notification-templates'
|
||||
*/
|
||||
ListNotificationTemplates.head = (options?: RouteQueryOptions): RouteDefinition<'head'> => ({
|
||||
url: ListNotificationTemplates.url(options),
|
||||
method: 'head',
|
||||
})
|
||||
|
||||
/**
|
||||
* @see \App\Filament\Resources\NotificationTemplates\Pages\ListNotificationTemplates::__invoke
|
||||
* @see app/Filament/Resources/NotificationTemplates/Pages/ListNotificationTemplates.php:7
|
||||
* @route '/admin/notification-templates'
|
||||
*/
|
||||
const ListNotificationTemplatesForm = (options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({
|
||||
action: ListNotificationTemplates.url(options),
|
||||
method: 'get',
|
||||
})
|
||||
|
||||
/**
|
||||
* @see \App\Filament\Resources\NotificationTemplates\Pages\ListNotificationTemplates::__invoke
|
||||
* @see app/Filament/Resources/NotificationTemplates/Pages/ListNotificationTemplates.php:7
|
||||
* @route '/admin/notification-templates'
|
||||
*/
|
||||
ListNotificationTemplatesForm.get = (options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({
|
||||
action: ListNotificationTemplates.url(options),
|
||||
method: 'get',
|
||||
})
|
||||
|
||||
/**
|
||||
* @see \App\Filament\Resources\NotificationTemplates\Pages\ListNotificationTemplates::__invoke
|
||||
* @see app/Filament/Resources/NotificationTemplates/Pages/ListNotificationTemplates.php:7
|
||||
* @route '/admin/notification-templates'
|
||||
*/
|
||||
ListNotificationTemplatesForm.head = (options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({
|
||||
action: ListNotificationTemplates.url({
|
||||
[options?.mergeQuery ? 'mergeQuery' : 'query']: {
|
||||
_method: 'HEAD',
|
||||
...(options?.query ?? options?.mergeQuery ?? {}),
|
||||
}
|
||||
}),
|
||||
method: 'get',
|
||||
})
|
||||
|
||||
ListNotificationTemplates.form = ListNotificationTemplatesForm
|
||||
|
||||
export default ListNotificationTemplates
|
||||
@@ -0,0 +1,11 @@
|
||||
import ListNotificationTemplates from './ListNotificationTemplates'
|
||||
import CreateNotificationTemplate from './CreateNotificationTemplate'
|
||||
import EditNotificationTemplate from './EditNotificationTemplate'
|
||||
|
||||
const Pages = {
|
||||
ListNotificationTemplates: Object.assign(ListNotificationTemplates, ListNotificationTemplates),
|
||||
CreateNotificationTemplate: Object.assign(CreateNotificationTemplate, CreateNotificationTemplate),
|
||||
EditNotificationTemplate: Object.assign(EditNotificationTemplate, EditNotificationTemplate),
|
||||
}
|
||||
|
||||
export default Pages
|
||||
@@ -0,0 +1,7 @@
|
||||
import Pages from './Pages'
|
||||
|
||||
const NotificationTemplates = {
|
||||
Pages: Object.assign(Pages, Pages),
|
||||
}
|
||||
|
||||
export default NotificationTemplates
|
||||
@@ -1,6 +1,7 @@
|
||||
import MemberGroups from './MemberGroups'
|
||||
import Members from './Members'
|
||||
import Memberships from './Memberships'
|
||||
import NotificationTemplates from './NotificationTemplates'
|
||||
import Packages from './Packages'
|
||||
import Services from './Services'
|
||||
import Users from './Users'
|
||||
@@ -9,6 +10,7 @@ const Resources = {
|
||||
MemberGroups: Object.assign(MemberGroups, MemberGroups),
|
||||
Members: Object.assign(Members, Members),
|
||||
Memberships: Object.assign(Memberships, Memberships),
|
||||
NotificationTemplates: Object.assign(NotificationTemplates, NotificationTemplates),
|
||||
Packages: Object.assign(Packages, Packages),
|
||||
Services: Object.assign(Services, Services),
|
||||
Users: Object.assign(Users, Users),
|
||||
|
||||
57
resources/js/routes/boost/index.ts
Normal file
57
resources/js/routes/boost/index.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { queryParams, type RouteQueryOptions, type RouteDefinition, type RouteFormDefinition } from './../../wayfinder'
|
||||
/**
|
||||
* @see vendor/laravel/boost/src/BoostServiceProvider.php:117
|
||||
* @route '/_boost/browser-logs'
|
||||
*/
|
||||
export const browserLogs = (options?: RouteQueryOptions): RouteDefinition<'post'> => ({
|
||||
url: browserLogs.url(options),
|
||||
method: 'post',
|
||||
})
|
||||
|
||||
browserLogs.definition = {
|
||||
methods: ["post"],
|
||||
url: '/_boost/browser-logs',
|
||||
} satisfies RouteDefinition<["post"]>
|
||||
|
||||
/**
|
||||
* @see vendor/laravel/boost/src/BoostServiceProvider.php:117
|
||||
* @route '/_boost/browser-logs'
|
||||
*/
|
||||
browserLogs.url = (options?: RouteQueryOptions) => {
|
||||
return browserLogs.definition.url + queryParams(options)
|
||||
}
|
||||
|
||||
/**
|
||||
* @see vendor/laravel/boost/src/BoostServiceProvider.php:117
|
||||
* @route '/_boost/browser-logs'
|
||||
*/
|
||||
browserLogs.post = (options?: RouteQueryOptions): RouteDefinition<'post'> => ({
|
||||
url: browserLogs.url(options),
|
||||
method: 'post',
|
||||
})
|
||||
|
||||
/**
|
||||
* @see vendor/laravel/boost/src/BoostServiceProvider.php:117
|
||||
* @route '/_boost/browser-logs'
|
||||
*/
|
||||
const browserLogsForm = (options?: RouteQueryOptions): RouteFormDefinition<'post'> => ({
|
||||
action: browserLogs.url(options),
|
||||
method: 'post',
|
||||
})
|
||||
|
||||
/**
|
||||
* @see vendor/laravel/boost/src/BoostServiceProvider.php:117
|
||||
* @route '/_boost/browser-logs'
|
||||
*/
|
||||
browserLogsForm.post = (options?: RouteQueryOptions): RouteFormDefinition<'post'> => ({
|
||||
action: browserLogs.url(options),
|
||||
method: 'post',
|
||||
})
|
||||
|
||||
browserLogs.form = browserLogsForm
|
||||
|
||||
const boost = {
|
||||
browserLogs: Object.assign(browserLogs, browserLogs),
|
||||
}
|
||||
|
||||
export default boost
|
||||
@@ -1,6 +1,7 @@
|
||||
import memberGroups from './member-groups'
|
||||
import members from './members'
|
||||
import memberships from './memberships'
|
||||
import notificationTemplates from './notification-templates'
|
||||
import packages from './packages'
|
||||
import services from './services'
|
||||
import users from './users'
|
||||
@@ -10,6 +11,7 @@ const resources = {
|
||||
memberGroups: Object.assign(memberGroups, memberGroups),
|
||||
members: Object.assign(members, members),
|
||||
memberships: Object.assign(memberships, memberships),
|
||||
notificationTemplates: Object.assign(notificationTemplates, notificationTemplates),
|
||||
packages: Object.assign(packages, packages),
|
||||
services: Object.assign(services, services),
|
||||
users: Object.assign(users, users),
|
||||
|
||||
@@ -0,0 +1,269 @@
|
||||
import { queryParams, type RouteQueryOptions, type RouteDefinition, type RouteFormDefinition, applyUrlDefaults } from './../../../../../wayfinder'
|
||||
/**
|
||||
* @see \App\Filament\Resources\NotificationTemplates\Pages\ListNotificationTemplates::__invoke
|
||||
* @see app/Filament/Resources/NotificationTemplates/Pages/ListNotificationTemplates.php:7
|
||||
* @route '/admin/notification-templates'
|
||||
*/
|
||||
export const index = (options?: RouteQueryOptions): RouteDefinition<'get'> => ({
|
||||
url: index.url(options),
|
||||
method: 'get',
|
||||
})
|
||||
|
||||
index.definition = {
|
||||
methods: ["get","head"],
|
||||
url: '/admin/notification-templates',
|
||||
} satisfies RouteDefinition<["get","head"]>
|
||||
|
||||
/**
|
||||
* @see \App\Filament\Resources\NotificationTemplates\Pages\ListNotificationTemplates::__invoke
|
||||
* @see app/Filament/Resources/NotificationTemplates/Pages/ListNotificationTemplates.php:7
|
||||
* @route '/admin/notification-templates'
|
||||
*/
|
||||
index.url = (options?: RouteQueryOptions) => {
|
||||
return index.definition.url + queryParams(options)
|
||||
}
|
||||
|
||||
/**
|
||||
* @see \App\Filament\Resources\NotificationTemplates\Pages\ListNotificationTemplates::__invoke
|
||||
* @see app/Filament/Resources/NotificationTemplates/Pages/ListNotificationTemplates.php:7
|
||||
* @route '/admin/notification-templates'
|
||||
*/
|
||||
index.get = (options?: RouteQueryOptions): RouteDefinition<'get'> => ({
|
||||
url: index.url(options),
|
||||
method: 'get',
|
||||
})
|
||||
|
||||
/**
|
||||
* @see \App\Filament\Resources\NotificationTemplates\Pages\ListNotificationTemplates::__invoke
|
||||
* @see app/Filament/Resources/NotificationTemplates/Pages/ListNotificationTemplates.php:7
|
||||
* @route '/admin/notification-templates'
|
||||
*/
|
||||
index.head = (options?: RouteQueryOptions): RouteDefinition<'head'> => ({
|
||||
url: index.url(options),
|
||||
method: 'head',
|
||||
})
|
||||
|
||||
/**
|
||||
* @see \App\Filament\Resources\NotificationTemplates\Pages\ListNotificationTemplates::__invoke
|
||||
* @see app/Filament/Resources/NotificationTemplates/Pages/ListNotificationTemplates.php:7
|
||||
* @route '/admin/notification-templates'
|
||||
*/
|
||||
const indexForm = (options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({
|
||||
action: index.url(options),
|
||||
method: 'get',
|
||||
})
|
||||
|
||||
/**
|
||||
* @see \App\Filament\Resources\NotificationTemplates\Pages\ListNotificationTemplates::__invoke
|
||||
* @see app/Filament/Resources/NotificationTemplates/Pages/ListNotificationTemplates.php:7
|
||||
* @route '/admin/notification-templates'
|
||||
*/
|
||||
indexForm.get = (options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({
|
||||
action: index.url(options),
|
||||
method: 'get',
|
||||
})
|
||||
|
||||
/**
|
||||
* @see \App\Filament\Resources\NotificationTemplates\Pages\ListNotificationTemplates::__invoke
|
||||
* @see app/Filament/Resources/NotificationTemplates/Pages/ListNotificationTemplates.php:7
|
||||
* @route '/admin/notification-templates'
|
||||
*/
|
||||
indexForm.head = (options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({
|
||||
action: index.url({
|
||||
[options?.mergeQuery ? 'mergeQuery' : 'query']: {
|
||||
_method: 'HEAD',
|
||||
...(options?.query ?? options?.mergeQuery ?? {}),
|
||||
}
|
||||
}),
|
||||
method: 'get',
|
||||
})
|
||||
|
||||
index.form = indexForm
|
||||
|
||||
/**
|
||||
* @see \App\Filament\Resources\NotificationTemplates\Pages\CreateNotificationTemplate::__invoke
|
||||
* @see app/Filament/Resources/NotificationTemplates/Pages/CreateNotificationTemplate.php:7
|
||||
* @route '/admin/notification-templates/create'
|
||||
*/
|
||||
export const create = (options?: RouteQueryOptions): RouteDefinition<'get'> => ({
|
||||
url: create.url(options),
|
||||
method: 'get',
|
||||
})
|
||||
|
||||
create.definition = {
|
||||
methods: ["get","head"],
|
||||
url: '/admin/notification-templates/create',
|
||||
} satisfies RouteDefinition<["get","head"]>
|
||||
|
||||
/**
|
||||
* @see \App\Filament\Resources\NotificationTemplates\Pages\CreateNotificationTemplate::__invoke
|
||||
* @see app/Filament/Resources/NotificationTemplates/Pages/CreateNotificationTemplate.php:7
|
||||
* @route '/admin/notification-templates/create'
|
||||
*/
|
||||
create.url = (options?: RouteQueryOptions) => {
|
||||
return create.definition.url + queryParams(options)
|
||||
}
|
||||
|
||||
/**
|
||||
* @see \App\Filament\Resources\NotificationTemplates\Pages\CreateNotificationTemplate::__invoke
|
||||
* @see app/Filament/Resources/NotificationTemplates/Pages/CreateNotificationTemplate.php:7
|
||||
* @route '/admin/notification-templates/create'
|
||||
*/
|
||||
create.get = (options?: RouteQueryOptions): RouteDefinition<'get'> => ({
|
||||
url: create.url(options),
|
||||
method: 'get',
|
||||
})
|
||||
|
||||
/**
|
||||
* @see \App\Filament\Resources\NotificationTemplates\Pages\CreateNotificationTemplate::__invoke
|
||||
* @see app/Filament/Resources/NotificationTemplates/Pages/CreateNotificationTemplate.php:7
|
||||
* @route '/admin/notification-templates/create'
|
||||
*/
|
||||
create.head = (options?: RouteQueryOptions): RouteDefinition<'head'> => ({
|
||||
url: create.url(options),
|
||||
method: 'head',
|
||||
})
|
||||
|
||||
/**
|
||||
* @see \App\Filament\Resources\NotificationTemplates\Pages\CreateNotificationTemplate::__invoke
|
||||
* @see app/Filament/Resources/NotificationTemplates/Pages/CreateNotificationTemplate.php:7
|
||||
* @route '/admin/notification-templates/create'
|
||||
*/
|
||||
const createForm = (options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({
|
||||
action: create.url(options),
|
||||
method: 'get',
|
||||
})
|
||||
|
||||
/**
|
||||
* @see \App\Filament\Resources\NotificationTemplates\Pages\CreateNotificationTemplate::__invoke
|
||||
* @see app/Filament/Resources/NotificationTemplates/Pages/CreateNotificationTemplate.php:7
|
||||
* @route '/admin/notification-templates/create'
|
||||
*/
|
||||
createForm.get = (options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({
|
||||
action: create.url(options),
|
||||
method: 'get',
|
||||
})
|
||||
|
||||
/**
|
||||
* @see \App\Filament\Resources\NotificationTemplates\Pages\CreateNotificationTemplate::__invoke
|
||||
* @see app/Filament/Resources/NotificationTemplates/Pages/CreateNotificationTemplate.php:7
|
||||
* @route '/admin/notification-templates/create'
|
||||
*/
|
||||
createForm.head = (options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({
|
||||
action: create.url({
|
||||
[options?.mergeQuery ? 'mergeQuery' : 'query']: {
|
||||
_method: 'HEAD',
|
||||
...(options?.query ?? options?.mergeQuery ?? {}),
|
||||
}
|
||||
}),
|
||||
method: 'get',
|
||||
})
|
||||
|
||||
create.form = createForm
|
||||
|
||||
/**
|
||||
* @see \App\Filament\Resources\NotificationTemplates\Pages\EditNotificationTemplate::__invoke
|
||||
* @see app/Filament/Resources/NotificationTemplates/Pages/EditNotificationTemplate.php:7
|
||||
* @route '/admin/notification-templates/{record}/edit'
|
||||
*/
|
||||
export const edit = (args: { record: string | number } | [record: string | number ] | string | number, options?: RouteQueryOptions): RouteDefinition<'get'> => ({
|
||||
url: edit.url(args, options),
|
||||
method: 'get',
|
||||
})
|
||||
|
||||
edit.definition = {
|
||||
methods: ["get","head"],
|
||||
url: '/admin/notification-templates/{record}/edit',
|
||||
} satisfies RouteDefinition<["get","head"]>
|
||||
|
||||
/**
|
||||
* @see \App\Filament\Resources\NotificationTemplates\Pages\EditNotificationTemplate::__invoke
|
||||
* @see app/Filament/Resources/NotificationTemplates/Pages/EditNotificationTemplate.php:7
|
||||
* @route '/admin/notification-templates/{record}/edit'
|
||||
*/
|
||||
edit.url = (args: { record: string | number } | [record: string | number ] | string | number, options?: RouteQueryOptions) => {
|
||||
if (typeof args === 'string' || typeof args === 'number') {
|
||||
args = { record: args }
|
||||
}
|
||||
|
||||
if (Array.isArray(args)) {
|
||||
args = {
|
||||
record: args[0],
|
||||
}
|
||||
}
|
||||
|
||||
args = applyUrlDefaults(args)
|
||||
|
||||
const parsedArgs = {
|
||||
record: args.record,
|
||||
}
|
||||
|
||||
return edit.definition.url
|
||||
.replace('{record}', parsedArgs.record.toString())
|
||||
.replace(/\/+$/, '') + queryParams(options)
|
||||
}
|
||||
|
||||
/**
|
||||
* @see \App\Filament\Resources\NotificationTemplates\Pages\EditNotificationTemplate::__invoke
|
||||
* @see app/Filament/Resources/NotificationTemplates/Pages/EditNotificationTemplate.php:7
|
||||
* @route '/admin/notification-templates/{record}/edit'
|
||||
*/
|
||||
edit.get = (args: { record: string | number } | [record: string | number ] | string | number, options?: RouteQueryOptions): RouteDefinition<'get'> => ({
|
||||
url: edit.url(args, options),
|
||||
method: 'get',
|
||||
})
|
||||
|
||||
/**
|
||||
* @see \App\Filament\Resources\NotificationTemplates\Pages\EditNotificationTemplate::__invoke
|
||||
* @see app/Filament/Resources/NotificationTemplates/Pages/EditNotificationTemplate.php:7
|
||||
* @route '/admin/notification-templates/{record}/edit'
|
||||
*/
|
||||
edit.head = (args: { record: string | number } | [record: string | number ] | string | number, options?: RouteQueryOptions): RouteDefinition<'head'> => ({
|
||||
url: edit.url(args, options),
|
||||
method: 'head',
|
||||
})
|
||||
|
||||
/**
|
||||
* @see \App\Filament\Resources\NotificationTemplates\Pages\EditNotificationTemplate::__invoke
|
||||
* @see app/Filament/Resources/NotificationTemplates/Pages/EditNotificationTemplate.php:7
|
||||
* @route '/admin/notification-templates/{record}/edit'
|
||||
*/
|
||||
const editForm = (args: { record: string | number } | [record: string | number ] | string | number, options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({
|
||||
action: edit.url(args, options),
|
||||
method: 'get',
|
||||
})
|
||||
|
||||
/**
|
||||
* @see \App\Filament\Resources\NotificationTemplates\Pages\EditNotificationTemplate::__invoke
|
||||
* @see app/Filament/Resources/NotificationTemplates/Pages/EditNotificationTemplate.php:7
|
||||
* @route '/admin/notification-templates/{record}/edit'
|
||||
*/
|
||||
editForm.get = (args: { record: string | number } | [record: string | number ] | string | number, options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({
|
||||
action: edit.url(args, options),
|
||||
method: 'get',
|
||||
})
|
||||
|
||||
/**
|
||||
* @see \App\Filament\Resources\NotificationTemplates\Pages\EditNotificationTemplate::__invoke
|
||||
* @see app/Filament/Resources/NotificationTemplates/Pages/EditNotificationTemplate.php:7
|
||||
* @route '/admin/notification-templates/{record}/edit'
|
||||
*/
|
||||
editForm.head = (args: { record: string | number } | [record: string | number ] | string | number, options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({
|
||||
action: edit.url(args, {
|
||||
[options?.mergeQuery ? 'mergeQuery' : 'query']: {
|
||||
_method: 'HEAD',
|
||||
...(options?.query ?? options?.mergeQuery ?? {}),
|
||||
}
|
||||
}),
|
||||
method: 'get',
|
||||
})
|
||||
|
||||
edit.form = editForm
|
||||
|
||||
const notificationTemplates = {
|
||||
index: Object.assign(index, index),
|
||||
create: Object.assign(create, create),
|
||||
edit: Object.assign(edit, edit),
|
||||
}
|
||||
|
||||
export default notificationTemplates
|
||||
3
resources/views/notifications/mail-template.blade.php
Normal file
3
resources/views/notifications/mail-template.blade.php
Normal file
@@ -0,0 +1,3 @@
|
||||
<x-mail::message>
|
||||
{!! $body !!}
|
||||
</x-mail::message>
|
||||
77
tests/Feature/NotificationTemplateTest.php
Normal file
77
tests/Feature/NotificationTemplateTest.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\NotificationTemplate;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
class NotificationTemplateTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_find_by_identifier_returns_active_template(): void
|
||||
{
|
||||
$template = NotificationTemplate::factory()->create([
|
||||
'identifier' => 'test_template',
|
||||
'is_active' => true,
|
||||
]);
|
||||
|
||||
$found = NotificationTemplate::findByIdentifier('test_template');
|
||||
|
||||
$this->assertNotNull($found);
|
||||
$this->assertEquals($template->id, $found->id);
|
||||
}
|
||||
|
||||
public function test_find_by_identifier_returns_null_for_inactive_template(): void
|
||||
{
|
||||
NotificationTemplate::factory()->inactive()->create([
|
||||
'identifier' => 'inactive_template',
|
||||
]);
|
||||
|
||||
$found = NotificationTemplate::findByIdentifier('inactive_template');
|
||||
|
||||
$this->assertNull($found);
|
||||
}
|
||||
|
||||
public function test_render_subject_replaces_placeholders(): void
|
||||
{
|
||||
$template = NotificationTemplate::factory()->create([
|
||||
'subject' => 'Bonjour {member_name}, votre adhésion expire le {expiry_date}',
|
||||
]);
|
||||
|
||||
$result = $template->renderSubject([
|
||||
'member_name' => 'Jean Dupont',
|
||||
'expiry_date' => '2026-01-31',
|
||||
]);
|
||||
|
||||
$this->assertEquals('Bonjour Jean Dupont, votre adhésion expire le 2026-01-31', $result);
|
||||
}
|
||||
|
||||
public function test_render_body_replaces_placeholders(): void
|
||||
{
|
||||
$template = NotificationTemplate::factory()->create([
|
||||
'body' => '<p>Bonjour {member_name}</p><p>Expiration : {expiry_date}</p>',
|
||||
]);
|
||||
|
||||
$result = $template->renderBody([
|
||||
'member_name' => 'Marie Martin',
|
||||
'expiry_date' => '2026-06-15',
|
||||
]);
|
||||
|
||||
$this->assertEquals('<p>Bonjour Marie Martin</p><p>Expiration : 2026-06-15</p>', $result);
|
||||
}
|
||||
|
||||
public function test_missing_placeholder_is_left_intact(): void
|
||||
{
|
||||
$template = NotificationTemplate::factory()->create([
|
||||
'subject' => 'Bonjour {member_name}, date : {expiry_date}',
|
||||
]);
|
||||
|
||||
$result = $template->renderSubject([
|
||||
'member_name' => 'Jean Dupont',
|
||||
]);
|
||||
|
||||
$this->assertEquals('Bonjour Jean Dupont, date : {expiry_date}', $result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Jobs\SendSubscriptionExpiredPhase1Notifications;
|
||||
use App\Models\Member;
|
||||
use App\Models\Membership;
|
||||
use App\Models\NotificationTemplate;
|
||||
use App\Models\Package;
|
||||
use App\Notifications\SubscriptionExpiredPhase1;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
use Tests\TestCase;
|
||||
|
||||
class SendSubscriptionExpiredPhase1NotificationsTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
private function createMemberWithExpiredMembership(): Member
|
||||
{
|
||||
$package = Package::create([
|
||||
'identifier' => 'test-package',
|
||||
'name' => 'Test Package',
|
||||
'is_active' => true,
|
||||
]);
|
||||
|
||||
$member = Member::create([
|
||||
'email' => fake()->unique()->safeEmail(),
|
||||
'firstname' => fake()->firstName(),
|
||||
'lastname' => fake()->lastName(),
|
||||
'status' => 'valid',
|
||||
'nature' => 'physical',
|
||||
]);
|
||||
|
||||
Membership::create([
|
||||
'member_id' => $member->id,
|
||||
'package_id' => $package->id,
|
||||
'status' => 'expired',
|
||||
'end_date' => '2025-12-31',
|
||||
'amount' => 12.00,
|
||||
'payment_status' => 'paid',
|
||||
]);
|
||||
|
||||
return $member;
|
||||
}
|
||||
|
||||
public function test_job_sends_notifications_to_expired_members(): void
|
||||
{
|
||||
Notification::fake();
|
||||
|
||||
NotificationTemplate::factory()->create([
|
||||
'identifier' => 'subscription_expired_phase1',
|
||||
'is_active' => true,
|
||||
]);
|
||||
|
||||
$expiredMember = $this->createMemberWithExpiredMembership();
|
||||
|
||||
(new SendSubscriptionExpiredPhase1Notifications)->handle();
|
||||
|
||||
Notification::assertSentTo($expiredMember, SubscriptionExpiredPhase1::class);
|
||||
}
|
||||
|
||||
public function test_job_does_nothing_when_template_is_inactive(): void
|
||||
{
|
||||
Notification::fake();
|
||||
|
||||
NotificationTemplate::factory()->inactive()->create([
|
||||
'identifier' => 'subscription_expired_phase1',
|
||||
]);
|
||||
|
||||
$this->createMemberWithExpiredMembership();
|
||||
|
||||
(new SendSubscriptionExpiredPhase1Notifications)->handle();
|
||||
|
||||
Notification::assertNothingSent();
|
||||
}
|
||||
|
||||
public function test_job_does_nothing_when_template_does_not_exist(): void
|
||||
{
|
||||
Notification::fake();
|
||||
|
||||
$this->createMemberWithExpiredMembership();
|
||||
|
||||
(new SendSubscriptionExpiredPhase1Notifications)->handle();
|
||||
|
||||
Notification::assertNothingSent();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user