andydefer / laravel-roster
Clean and flexible scheduling for Laravel applications.
Installs: 17
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 1
Open Issues: 2
pkg:composer/andydefer/laravel-roster
Requires
- php: ^8.2
- illuminate/database: ^12.0
- illuminate/support: ^12.0
- laravel/framework: ^12.0
Requires (Dev)
- barryvdh/laravel-ide-helper: ^3.6
- larastan/larastan: ^3.8
- laravel/pint: ^1.26
- orchestra/testbench: ^10.8
- phpunit/phpunit: ^12.5
- rector/rector: *
- symfony/var-dumper: ^7.0
- vimeo/psalm: ^6.14
README
Roster est un package Laravel complet pour la gestion avancée d'emplois du temps, de disponibilités et de réservations. Construit avec une architecture robuste, il gère les disponibilités récurrentes, les créneaux réservés et les empêchements avec une validation métier exhaustive.
📦 Installation
composer require andydefer/laravel-roster
Publier les ressources du package :
php artisan roster:install
Ou manuellement :
# Configuration php artisan vendor:publish --tag=roster-config # Migrations php artisan vendor:publish --tag=roster-migrations # Exécuter les migrations php artisan migrate
🚀 Démarrage rapide
1. Ajouter le trait à vos modèles
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; use Roster\Traits\HasRoster; class Doctor extends Model { use HasRoster; }
2. Créer des disponibilités récurrentes
// Créer une disponibilité pour un docteur $availability = availability_for($doctor)->create([ 'type' => 'consultation', 'daily_start' => '09:00:00', 'daily_end' => '17:00:00', 'days' => ['monday', 'wednesday', 'friday'], 'validity_start' => '2038-01-01', 'validity_end' => '2038-12-31', ]);
3. Planifier des rendez-vous
// Réserver un créneau dans cette disponibilité $schedule = schedule_for($availability)->create([ 'title' => 'Consultation annuelle - Patient A', 'start_datetime' => '2038-01-04 10:00:00', 'end_datetime' => '2038-01-04 11:00:00', 'status' => \Roster\Enums\ScheduleStatus::BOOKED, 'metadata' => ['patient_id' => 123], ]);
4. Gérer les indisponibilités temporaires
// Bloquer un créneau pour une formation $impediment = impediment_for($availability)->create([ 'reason' => 'Formation médicale obligatoire', 'start_datetime' => '2038-01-04 09:00:00', 'end_datetime' => '2038-01-04 12:00:00', ]);
5. Rechercher des créneaux disponibles
// Trouver le prochain créneau disponible $nextSlot = schedule_for($availability)->findNextSlot( durationMinutes: 45, type: 'consultation', startFrom: now()->addDay() ); // Vérifier la disponibilité pour un créneau spécifique $isAvailable = schedule_for($availability)->isTimeSlotAvailable( start: '2038-01-06 14:00:00', end: '2038-01-06 15:00:00', type: 'consultation' );
📖 Concepts de base
Le principe d'immuabilité
Roster empêche les mutations directes des modèles pour garantir l'intégrité des données. Toutes les opérations doivent passer par les services appropriés :
// ❌ INTERDIT : Modification directe $availability->update(['daily_end' => '18:00:00']); // Lance une exception // ✅ AUTORISÉ : Via le service availability_for($doctor)->update($availability->id, [ 'daily_end' => '18:00:00' ]);
Contexte unique par action
Chaque service est conçu pour une seule action avec son propre contexte :
// ❌ INTERDIT : Réutilisation du service $service = availability_for($doctor); $service->create([...]); $service->update(1, [...]); // Contexte corrompu // ✅ AUTORISÉ : Nouveau contexte pour chaque action availability_for($doctor)->create([...]); availability_for($doctor)->update(1, [...]);
Les 3 entités principales
- Availability : Définit quand une ressource est disponible (jours, heures, période)
- Schedule : Représente un créneau réservé dans une disponibilité
- Impediment : Bloque temporairement une disponibilité
🛡️ Architecture sécurisée
Contrôle d'accès aux mutations
Le système utilise deux contextes pour contrôler l'accès :
// 1. Contexte de mutation (interne) // Utilisé par les repositories pour autoriser les opérations CRUD RosterMutationContext::allow(function () { return Availability::create([...]); // Autorisé dans ce contexte }); // 2. Contexte de service (public) // Utilisé par les helpers pour autoriser l'utilisation des services RosterServiceContext::allow(function () { return $service->create([...]); // Autorisé via le helper });
Helpers sécurisés
Les helpers availability_for(), schedule_for(), et impediment_for() créent automatiquement le contexte nécessaire :
// Ces helpers gèrent automatiquement : // 1. La création du contexte d'exécution // 2. La validation de l'entité planifiable // 3. La prévention de la réutilisation
🎯 Validation métier exhaustive
Roster inclut 17 règles de validation qui garantissent la cohérence du système :
Règles principales :
- SchedulableValidationRule (110) - Vérifie la présence du contexte planifiable
- RequiredFieldsRule (100) - Valide les champs requis par opération
- AvailabilityTemporalCoherenceRule (100) - Assure la cohérence temporelle
- TemporalConflictRule (80) - Empêche les chevauchements de planning
- AvailabilityOverlapRule (80) - Empêche les chevauchements de disponibilités
- TimeRangeRule (85) - Valide les plages horaires (pas de spans multi-jours)
Visualisation des règles :
# Lister toutes les règles disponibles php artisan roster:debug-rules # Voir les règles pour une entité spécifique php artisan roster:debug-rules availability --operation=create
📊 Exemples d'utilisation réels
Gestion de clinique médicale
// Création de disponibilités pour différents spécialistes $cardiologist = Doctor::where('specialty', 'cardiology')->first(); $availability = availability_for($cardiologist)->create([ 'type' => 'consultation', 'daily_start' => '08:30:00', 'daily_end' => '12:30:00', 'days' => ['monday', 'wednesday', 'friday'], 'validity_start' => '2024-01-01', 'validity_end' => '2024-12-31', ]); // Réservation patient $appointment = schedule_for($availability)->create([ 'title' => 'Consultation cardiaque', 'start_datetime' => '2024-06-10 10:00:00', 'end_datetime' => '2024-06-10 11:00:00', 'status' => ScheduleStatus::BOOKED, 'metadata' => [ 'patient_id' => 'CARD001', 'priority' => 'medium', 'tests_required' => ['echocardiogram', 'stress_test'] ], ]); // Gestion d'indisponibilité (formation) impediment_for($availability)->create([ 'reason' => 'Formation continue', 'start_datetime' => '2024-06-15 09:00:00', 'end_datetime' => '2024-06-15 12:00:00', 'metadata' => ['mandatory' => true, 'location' => 'Auditorium'], ]);
Système de réservation de salle
// Deux docteurs partageant une salle $room = Room::find(1); // Premier docteur utilise la salle le lundi $doctor1Availability = availability_for($doctor1)->create([ 'type' => 'room_a', 'daily_start' => '09:00:00', 'daily_end' => '17:00:00', 'days' => ['monday', 'wednesday', 'friday'], 'validity_start' => '2024-01-01', 'validity_end' => '2024-12-31', ]); // Second docteur utilise la salle le mardi $doctor2Availability = availability_for($doctor2)->create([ 'type' => 'room_a', 'daily_start' => '09:00:00', 'daily_end' => '17:00:00', 'days' => ['tuesday', 'thursday'], 'validity_start' => '2024-01-01', 'validity_end' => '2024-12-31', ]); // Le système empêche automatiquement les conflits schedule_for($doctor1Availability)->create([ 'title' => 'Utilisation salle A - Dr. Smith', 'start_datetime' => '2024-06-10 10:00:00', // Lundi 'end_datetime' => '2024-06-10 12:00:00', ]); // ❌ Cette réservation échouera (conflit inter-docteurs) schedule_for($doctor2Availability)->create([ 'title' => 'Utilisation salle A - Dr. Jones', 'start_datetime' => '2024-06-10 11:00:00', // Même jour que Dr. Smith 'end_datetime' => '2024-06-10 13:00:00', ]);
Gestion des empêchements récurrents
// Création d'une disponibilité hebdomadaire $weeklyAvailability = availability_for($doctor)->create([ 'type' => 'consultation', 'daily_start' => '08:00:00', 'daily_end' => '18:00:00', 'days' => ['monday', 'tuesday', 'wednesday', 'thursday', 'friday'], 'validity_start' => '2024-01-01', 'validity_end' => '2024-12-31', ]); // Empêchements récurrents (pause déjeuner) $weekdays = ['2024-01-08', '2024-01-09', '2024-01-10', '2024-01-11', '2024-01-12']; foreach ($weekdays as $weekday) { impediment_for($weeklyAvailability)->create([ 'reason' => 'Pause déjeuner', 'start_datetime' => Carbon::parse($weekday)->setTime(12, 0, 0), 'end_datetime' => Carbon::parse($weekday)->setTime(13, 0, 0), 'metadata' => ['type' => 'lunch', 'recurring' => true], ]); } // Trouver des créneaux disponibles malgré les empêchements $availableSlots = schedule_for($weeklyAvailability)->findAvailableSlots( startDate: '2024-01-08', endDate: '2024-01-12', durationMinutes: 60, type: 'consultation' );
🔧 API complète
Service Availability
// CRUD availability_for($schedulable)->create($data); availability_for($schedulable)->find($id); availability_for($schedulable)->update($id, $data); availability_for($schedulable)->delete($id); // Recherche availability_for($schedulable)->all(); availability_for($schedulable)->setFilter('type', 'consultation')->all(); // Vérifications availability_for($schedulable)->isAvailableOnDate($date, $type); availability_for($schedulable)->getAvailabilityForTimeSlot($start, $end, $type);
Service Schedule
// Réservation schedule_for($availability)->create($data); schedule_for($availability)->update($id, $data); schedule_for($availability)->delete($id); // Recherche de créneaux schedule_for($availability)->findNextSlot($durationMinutes, $type, $startFrom); schedule_for($availability)->findAvailableSlots($startDate, $endDate, $durationMinutes, $type); // Vérifications schedule_for($availability)->isTimeSlotAvailable($start, $end, $type); schedule_for($availability)->isPeriodAvailable($start, $end, $type);
Service Impediment
// Gestion des empêchements impediment_for($availability)->create($data); impediment_for($availability)->update($id, $data); impediment_for($availability)->delete($id); // Vérifications impediment_for($availability)->isTimeSlotBlocked($start, $end); impediment_for($availability)->getAvailableTimeSlots($start, $end, $type);
⚙️ Configuration
Fichier de configuration (config/roster.php)
return [ // Types d'activité autorisés 'allowed_types' => [ 'consultation', 'surgery', 'emergency', 'training', 'room_a', 'echography', 'scan', ], // Durées minimales (en minutes) 'durations' => [ 'minimum_availability_minutes' => 15, 'minimum_schedule_minutes' => 15, 'minimum_impediment_minutes' => 5, 'max_search_period_days' => 365, 'max_availability_days' => 365, ], // Cache des règles de validation 'cache' => [ 'enabled' => env('ROSTER_CACHE_ENABLED', true), 'cache_file' => storage_path('framework/cache/roster_rules.php'), 'cache_max_age_hours' => 24, ], ];
Variables d'environnement
ROSTER_TIMEZONE=Europe/Paris ROSTER_CACHE_ENABLED=true
🧪 Tests complets
Le package inclut 760 tests couvrant tous les scénarios :
# Exécuter tous les tests php artisan test # Tests d'intégration php artisan test --group=integration # Tests de performance php artisan test --filter=test_performance_and_load_scenario # Tests de scénarios complexes php artisan test --filter=test_real_world_complex_scenario
Scénarios testés :
- ✅ Cycle de vie complet d'une disponibilité
- ✅ Gestion d'empêchement avec conflits
- ✅ Système de réservation intelligent
- ✅ Interactions complexes (disponibilités + empêchements + plannings)
- ✅ Conflits multi-utilisateurs avec ressources partagées
- ✅ Gestion des erreurs et cas limites
- ✅ Tests de performance avec données massives
- ✅ Récupération après erreurs
- ✅ Scénario complexe réaliste (hôpital avec multiples spécialistes)
🚨 Gestion des erreurs
use Roster\Validation\Exceptions\ValidationFailedException; try { $schedule = schedule_for($availability)->create($data); } catch (ValidationFailedException $e) { // Obtenir les violations détaillées avec information des règles $violations = $e->getViolations(); // Tableau d'objets ViolationData contenant : // - nom du champ // - message d'erreur // - règle ayant déclenché la violation // - description de la règle pour le contexte $detailedReport = $e->toDetailedArray(); // Inclut les descriptions des règles pour un meilleur débogage return response()->json([ 'error' => 'validation_failed', 'message' => $e->getFormattedMessage(), 'violations' => $detailedReport['violations'], ], 422); }
📊 Outils de développement
Débogage des règles de validation
# Afficher toutes les règles php artisan roster:debug-rules # Filtrer par entité php artisan roster:debug-rules availability # Filtrer par opération php artisan roster:debug-rules availability --operation=create # Afficher les méthodes php artisan roster:debug-rules availability --show-methods # Afficher les sources php artisan roster:debug-rules availability --show-source
Gestion du cache
# Générer le cache des règles php artisan roster:cache-rules # Afficher les statistiques du cache php artisan roster:cache-rules --show # Effacer le cache php artisan roster:cache-rules --clear # Forcer la régénération php artisan roster:cache-rules --force
🤝 Contribution
- Fork le dépôt
- Créez une branche (
git checkout -b feature/amazing-feature) - Commitez vos changements (
git commit -m 'Add amazing feature') - Poussez sur la branche (
git push origin feature/amazing-feature) - Ouvrez une Pull Request
Exécuter les tests
# Tous les tests composer test # Avec couverture de code composer test-coverage # Vérifier le style de code composer lint
📄 Licence
Ce package est open-source et disponible sous licence MIT.
🔗 Liens utiles
Roster - Une solution professionnelle pour la gestion avancée d'emplois du temps, conçue pour les applications critiques où chaque minute compte. ⚕️⏰✨