Crvi/app/controllers/Event_Controller.php
2026-01-21 10:40:35 +01:00

2388 lines
100 KiB
PHP

<?php
declare(strict_types=1);
namespace ESI_CRVI_AGENDA\controllers;
use ESI_CRVI_AGENDA\models\Event_Model;
use ESI_CRVI_AGENDA\helpers\Api_Helper;
use ESI_CRVI_AGENDA\models\CRVI_Event_Model;
use ESI_CRVI_AGENDA\models\CRVI_Traducteur_Model;
use ESI_CRVI_AGENDA\models\CRVI_Intervenant_Model;
use ESI_CRVI_AGENDA\models\CRVI_Presence_Model;
use ESI_CRVI_AGENDA\models\CRVI_Departement_Model;
use ESI_CRVI_AGENDA\controllers\Intervenant_Controller;
class CRVI_Event_Controller {
public static function register_routes() {
register_rest_route('crvi/v1', '/agenda/permissions', [
[
'methods' => 'GET',
'callback' => [self::class, 'get_user_permissions'],
'permission_callback' => '__return_true',
],
]);
register_rest_route('crvi/v1', '/agenda/disponibilites', [
[
'methods' => 'GET',
'callback' => [self::class, 'get_disponibilites'],
'permission_callback' => '__return_true',
],
[
'methods' => 'POST',
'callback' => [self::class, 'get_disponibilites'],
'permission_callback' => '__return_true',
],
]);
// Endpoint alternatif pour les filtres de disponibilités
register_rest_route('crvi/v1', '/filters/disponibilites', [
[
'methods' => 'GET',
'callback' => [self::class, 'get_disponibilites'],
'permission_callback' => '__return_true',
],
[
'methods' => 'POST',
'callback' => [self::class, 'get_disponibilites'],
'permission_callback' => '__return_true',
],
]);
\register_rest_route('crvi/v1', '/events/(?P<id>\\d+)/conflits', [
[
'methods' => 'GET',
'callback' => [self::class, 'conflits_item'],
'permission_callback' => [self::class, 'can_edit'],
],
]);
\register_rest_route('crvi/v1', '/events/export', [
[
'methods' => 'GET',
'callback' => [self::class, 'export_items'],
'permission_callback' => [self::class, 'can_edit'],
],
]);
// --- Filtres dynamiques ---
\register_rest_route('crvi/v1', '/filters/departements', [
'methods' => 'GET',
'callback' => [self::class, 'get_departements'],
'permission_callback' => '__return_true',
]);
\register_rest_route('crvi/v1', '/filters/types-intervention', [
'methods' => 'GET',
'callback' => [self::class, 'get_types_intervention'],
'permission_callback' => '__return_true',
]);
\register_rest_route('crvi/v1', '/filters/langues', [
'methods' => 'GET',
'callback' => [self::class, 'get_langues'],
'permission_callback' => '__return_true',
]);
\register_rest_route('crvi/v1', '/filters/langues-beneficiaire', [
'methods' => 'GET',
'callback' => [self::class, 'get_langues_beneficiaire'],
'permission_callback' => '__return_true',
]);
\register_rest_route('crvi/v1', '/filters/statuts', [
'methods' => 'GET',
'callback' => [self::class, 'get_statuts'],
'permission_callback' => '__return_true',
]);
// --- Historique bénéficiaire ---
\register_rest_route('crvi/v1', '/beneficiaires/(?P<id>\\d+)/historique', [
[
'methods' => 'GET',
'callback' => [self::class, 'get_beneficiaire_historique'],
'permission_callback' => '__return_true',
],
]);
// --- CRUD Events ---
\register_rest_route('crvi/v1', '/events', [
[
'methods' => 'GET',
'callback' => [self::class, 'get_events'],
'permission_callback' => '__return_true',
],
[
'methods' => 'POST',
'callback' => [self::class, 'create_event'],
'permission_callback' => [self::class, 'can_edit'],
],
]);
// Endpoint pour le tableau de stats avec pagination
\register_rest_route('crvi/v1', '/events/table', [
[
'methods' => 'GET',
'callback' => [self::class, 'get_events_table'],
'permission_callback' => '__return_true',
],
]);
// --- Validation des présences pour les rendez-vous de groupe ---
\register_rest_route('crvi/v1', '/events/(?P<id>\\d+)/presences', [
[
'methods' => 'GET',
'callback' => [self::class, 'get_event_presences'],
'permission_callback' => '__return_true',
],
[
'methods' => 'POST',
'callback' => [self::class, 'save_group_presences'],
'permission_callback' => [self::class, 'can_edit'],
],
]);
// Création de permanences (intervenant)
\register_rest_route('crvi/v1', '/intervenant/permanences', [
[
'methods' => 'POST',
'callback' => [self::class, 'create_permanences'],
'permission_callback' => [\ESI_CRVI_AGENDA\controllers\Intervenant_Space_Controller::class, 'check_intervenant_permission'],
],
]);
// Import CSV de permanences (intervenant)
\register_rest_route('crvi/v1', '/intervenant/permanences/import-csv', [
[
'methods' => 'POST',
'callback' => [self::class, 'import_permanences_csv_intervenant'],
'permission_callback' => [\ESI_CRVI_AGENDA\controllers\Intervenant_Space_Controller::class, 'check_intervenant_permission'],
],
]);
// Création de permanences (admin)
\register_rest_route('crvi/v1', '/admin/permanences', [
[
'methods' => 'POST',
'callback' => [self::class, 'create_permanences_admin'],
'permission_callback' => [\ESI_CRVI_AGENDA\controllers\Intervenant_Space_Controller::class, 'check_admin_permission'],
],
]);
// Import CSV de permanences (admin)
\register_rest_route('crvi/v1', '/admin/permanences/import-csv', [
[
'methods' => 'POST',
'callback' => [self::class, 'import_permanences_csv'],
'permission_callback' => [\ESI_CRVI_AGENDA\controllers\Intervenant_Space_Controller::class, 'check_admin_permission'],
],
]);
\register_rest_route('crvi/v1', '/events/(?P<id>\\d+)', [
[
'methods' => 'GET',
'callback' => [self::class, 'get_event'],
'permission_callback' => '__return_true',
],
[
'methods' => 'PUT,PATCH',
'callback' => [self::class, 'update_event'],
'permission_callback' => [self::class, 'can_edit'],
],
[
'methods' => 'DELETE',
'callback' => [self::class, 'delete_event'],
'permission_callback' => [self::class, 'can_edit'],
],
]);
// Endpoints pour la gestion des événements supprimés
\register_rest_route('crvi/v1', '/events/deleted', [
[
'methods' => 'GET',
'callback' => [self::class, 'get_deleted_events'],
'permission_callback' => [self::class, 'can_edit'],
],
]);
\register_rest_route('crvi/v1', '/events/(?P<id>\\d+)/restore', [
[
'methods' => 'POST',
'callback' => [self::class, 'restore_event'],
'permission_callback' => [self::class, 'can_edit'],
],
]);
\register_rest_route('crvi/v1', '/events/(?P<id>\\d+)/hard-delete', [
[
'methods' => 'DELETE',
'callback' => [self::class, 'hard_delete_event'],
'permission_callback' => [self::class, 'can_edit'],
],
]);
// Route pour changer le statut d'un événement
\register_rest_route('crvi/v1', '/events/(?P<id>\\d+)/statut', [
[
'methods' => 'PUT',
'callback' => [self::class, 'change_statut'],
'permission_callback' => [self::class, 'can_edit'],
],
]);
}
/**
* Endpoint global pour récupérer les entités disponibles à une date/créneau donné.
* @param \WP_REST_Request $request
* @return \WP_REST_Response
*/
public static function get_disponibilites($request) {
// Récupérer les paramètres depuis query string ou JSON body
$params = $request->get_params();
$json_params = $request->get_json_params() ?: [];
// Fusionner les paramètres (JSON body a priorité sur query string)
$all_params = array_merge($params, $json_params);
// DEBUG: Afficher les paramètres reçus
// Vérifier si on demande les dates indisponibles
$id_intervenant = $all_params['id_intervenant'] ?? null;
$id_traducteur = $all_params['id_traducteur'] ?? null;
$id_local = $all_params['id_local'] ?? null;
$date_debut = $all_params['date_debut'] ?? null;
$date_fin = $all_params['date_fin'] ?? null;
// Si on a tous les paramètres pour les dates indisponibles
if ($id_intervenant && $id_traducteur && $id_local && $date_debut && $date_fin) {
return self::get_available_dates($id_intervenant, $id_traducteur, $id_local, $date_debut, $date_fin);
}
// Sinon, logique existante pour les entités disponibles
$date = $all_params['date'] ?? $all_params['date_rdv'] ?? null;
$heure = $all_params['heure'] ?? $all_params['heure_rdv'] ?? null;
$langue = $all_params['langue'] ?? null;
$departement = $all_params['departement'] ?? null;
$specialisation = $all_params['specialisation'] ?? null;
$type = $all_params['type'] ?? null;
// Normaliser event_id (convertir string en int si nécessaire)
$event_id = isset($all_params['event_id']) ? (int) $all_params['event_id'] : null;
if ($event_id === 0) {
$event_id = null;
}
// Normaliser id_traducteur (convertir string en int, "0" signifie pas de traducteur)
$id_traducteur_param = isset($all_params['id_traducteur']) ? (int) $all_params['id_traducteur'] : null;
if ($id_traducteur_param === 0) {
$id_traducteur_param = null;
}
// Normaliser id_intervenant et id_local
$id_intervenant_param = isset($all_params['id_intervenant']) ? (int) $all_params['id_intervenant'] : null;
$id_local_param = isset($all_params['id_local']) ? (int) $all_params['id_local'] : null;
// Appel aux méthodes de chaque controller
// Ne vérifier les traducteurs que s'il y en a un d'assigné à l'événement (id_traducteur > 0) OU si une langue est spécifiée
$traducteurs = [];
// Vérifier que id_traducteur est valide (supérieur à 0, pas null, pas 0)
$hasValidTraducteurId = $id_traducteur_param !== null && ($id_traducteur_param > 0 || $id_traducteur_param != '0');
if ($hasValidTraducteurId || !empty($langue)) {
try {
$traducteurs_result = \ESI_CRVI_AGENDA\models\CRVI_Traducteur_Model::filtrer_disponibles($date, $langue, $event_id);
$traducteurs = is_array($traducteurs_result) ? $traducteurs_result : [];
} catch (\Exception $e) {
error_log('[CRVI] Erreur lors de la récupération des traducteurs disponibles: ' . $e->getMessage());
$traducteurs = [];
}
}
// Récupérer les intervenants disponibles
$intervenants = [];
try {
$intervenants_result = \ESI_CRVI_AGENDA\models\CRVI_Intervenant_Model::filtrer_disponibles($date, $departement, $specialisation, $event_id);
$intervenants = is_array($intervenants_result) ? $intervenants_result : [];
} catch (\Exception $e) {
error_log('[CRVI] Erreur lors de la récupération des intervenants disponibles: ' . $e->getMessage());
$intervenants = [];
}
// Formater les intervenants pour le JavaScript
$intervenants_formatted = [];
foreach ($intervenants as $intervenant) {
// Vérifier que l'intervenant est un objet valide
if (!is_object($intervenant) || !isset($intervenant->id)) {
continue;
}
$intervenants_formatted[] = [
'id' => $intervenant->id,
'nom' => ($intervenant->nom ?? '') . ' ' . ($intervenant->prenom ?? ''),
];
}
// Formater les traducteurs pour le JavaScript
$traducteurs_formatted = [];
foreach ($traducteurs as $traducteur) {
// Vérifier que le traducteur est un objet valide
if (!is_object($traducteur) || !isset($traducteur->id)) {
continue;
}
$traducteurs_formatted[] = [
'id' => $traducteur->id,
'nom' => ($traducteur->nom ?? '') . ' ' . ($traducteur->prenom ?? ''),
];
}
// Récupérer les locaux disponibles selon le type de RDV et la disponibilité
$type_rdv = $all_params['type_rdv'] ?? null;
$locals_posts = [];
try {
$locals_result = \ESI_CRVI_AGENDA\models\CRVI_Local_Model::all();
$locals_posts = is_array($locals_result) ? $locals_result : [];
} catch (\Exception $e) {
error_log('[CRVI] Erreur lors de la récupération des locaux: ' . $e->getMessage());
$locals_posts = [];
}
$locaux = [];
foreach ($locals_posts as $local_post) {
// Vérifier que le post est valide
if (!is_object($local_post) || !isset($local_post->ID)) {
continue;
}
$type_local = get_post_meta($local_post->ID, 'type_de_local', true);
$capacite = get_post_meta($local_post->ID, 'capacite', true);
// Normaliser le type_local (peut être vide ou null)
$type_local = !empty($type_local) ? $type_local : '';
// Construire le nom du local avec le type si disponible
$nom_local = $local_post->post_title ?? 'Local sans nom';
if (!empty($type_local)) {
$nom_local .= ' (' . $type_local . ')';
}
// Vérifier la disponibilité du local pour la date/heure spécifiée
$disponible = true;
if ($date && $heure) {
// Calculer la date/heure de fin (par défaut +1h si pas spécifiée)
$date_fin = $all_params['date_fin'] ?? $date;
$heure_fin = $all_params['heure_fin'] ?? null;
if (!$heure_fin) {
// Si pas d'heure de fin spécifiée, ajouter 1h par défaut
$heure_obj = \DateTime::createFromFormat('H:i', $heure);
if ($heure_obj) {
$heure_obj->add(new \DateInterval('PT1H'));
$heure_fin = $heure_obj->format('H:i');
} else {
$heure_fin = '10:00'; // Fallback
}
}
// Vérifier les conflits d'événements pour ce local
try {
$conflits = self::verifier_conflits_local($local_post->ID, $date, $date_fin, $heure, $heure_fin, $event_id);
if (!empty($conflits) && is_array($conflits)) {
$disponible = false;
}
} catch (\Exception $e) {
error_log('[CRVI] Erreur lors de la vérification des conflits pour le local ' . $local_post->ID . ': ' . $e->getMessage());
// En cas d'erreur, considérer le local comme disponible pour ne pas bloquer l'utilisateur
$disponible = true;
}
}
// Filtrer par type de RDV si spécifié
// Si le type_local est vide, on considère le local comme compatible avec tous les types
$type_compatible = true;
if ($type_rdv && in_array($type_rdv, ['individuel', 'groupe']) && !empty($type_local)) {
if ($type_rdv === 'individuel' && $type_local !== 'individuel') {
$type_compatible = false;
} elseif ($type_rdv === 'groupe' && $type_local !== 'groupe') {
$type_compatible = false;
}
}
// Ajouter le local seulement s'il est disponible et compatible
if ($disponible && $type_compatible) {
$locaux[] = [
'id' => $local_post->ID,
'nom' => $nom_local,
'type_de_local' => $type_local,
'capacite' => $capacite ? $capacite : '',
];
}
}
// Récupérer tous les bénéficiaires
$beneficiaires = [];
try {
$beneficiaire_model = new \ESI_CRVI_AGENDA\models\CRVI_Beneficiaire_Model();
$beneficiaires_objects = $beneficiaire_model->get_all_beneficiaires();
if (is_array($beneficiaires_objects)) {
foreach ($beneficiaires_objects as $beneficiaire) {
// Vérifier que le bénéficiaire est un objet valide
if (!is_object($beneficiaire) || !isset($beneficiaire->id)) {
continue;
}
$beneficiaires[] = [
'id' => $beneficiaire->id,
'nom' => ($beneficiaire->nom ?? '') . ' ' . ($beneficiaire->prenom ?? ''),
];
}
}
} catch (\Exception $e) {
error_log('[CRVI] Erreur lors de la récupération des bénéficiaires: ' . $e->getMessage());
$beneficiaires = [];
}
// Récupérer toutes les langues disponibles depuis la taxonomie
$langues_terms = get_terms([
'taxonomy' => 'langue',
'hide_empty' => false,
'orderby' => 'name',
'order' => 'ASC',
]);
$langues = [];
if (!is_wp_error($langues_terms) && !empty($langues_terms)) {
foreach ($langues_terms as $term) {
// Ignorer les termes invalides (slug vide ou seulement numérique suspect)
if (empty($term->slug) || empty($term->name)) {
continue;
}
// Utiliser le slug comme ID (standard WordPress)
// Si le slug est numérique, c'est peut-être un terme mal configuré, mais on le garde
$langues[] = [
'id' => $term->slug,
'nom' => $term->name,
];
}
}
// Récupérer tous les départements actifs
$departements = CRVI_Departement_Model::all(true, true);
// Récupérer tous les types d'intervention disponibles depuis les CPT
$types_intervention_posts = get_posts([
'post_type' => 'type_intervention',
'numberposts' => -1,
'post_status' => 'publish',
]);
$types_intervention = [];
foreach ($types_intervention_posts as $post) {
$types_intervention[] = [
'id' => $post->ID,
'nom' => $post->post_title,
];
}
$response = [
'intervenants' => $intervenants_formatted,
'locaux' => $locaux,
'beneficiaires' => $beneficiaires,
'langues' => $langues,
'departements' => $departements,
'types_intervention' => $types_intervention,
];
// Inclure les traducteurs seulement si la vérification a été faite
if ($traducteurs !== null) {
$response['traducteurs'] = $traducteurs_formatted;
}
return Api_Helper::json_success($response);
}
/**
* Endpoint pour récupérer les permissions de l'utilisateur courant.
* @param WP_REST_Request $request
* @return WP_REST_Response
*/
public static function get_user_permissions($request) {
$permissions = \ESI_CRVI_AGENDA\helpers\Api_Helper::get_user_permissions();
return Api_Helper::json_success($permissions);
}
public static function conflits_item($request) {
$id = (int) $request['id'];
$date = $request->get_param('date');
$heure = $request->get_param('heure');
$id_intervenant = $request->get_param('id_intervenant');
$id_traducteur = $request->get_param('id_traducteur');
$id_local = $request->get_param('id_local');
$model = new CRVI_Event_Model();
$result = $model->get_conflits($date, $heure, $id_intervenant, $id_traducteur, $id_local);
return Api_Helper::json_success($result);
}
public static function export_items($request) {
$model = new CRVI_Event_Model();
$params = $request->get_params();
// Support de la pagination pour l'export
$date_debut = $params['start'] ?? $params['date_debut'] ?? null;
$date_fin = $params['end'] ?? $params['date_fin'] ?? null;
// Filtrer les paramètres pour éviter les conflits
$filters = array_diff_key($params, array_flip(['start', 'end', 'date_debut', 'date_fin']));
$result = $model->get_events_by('date_rdv', null, $filters, $date_debut, $date_fin);
return Api_Helper::json_success($result);
}
// --- CRUD ---
public static function get_events($request) {
$params = $request->get_params();
$model = new CRVI_Event_Model();
$events = $model->get_events_by_filters($params);
// Convertir les langues, départements et types d'intervention (IDs vers noms)
foreach ($events as &$event) {
// Convertir la langue - ajouter langue_label pour l'affichage
if (!empty($event['langue'])) {
$langue_original = $event['langue'];
$langue_label = '';
// Essayer d'abord par ID numérique
$langue_term = get_term((int)$langue_original, 'langue');
if ($langue_term && !is_wp_error($langue_term)) {
$langue_label = $langue_term->name;
} else {
// Essayer par slug si l'ID ne fonctionne pas
$langue_term = get_term_by('slug', $langue_original, 'langue');
if ($langue_term && !is_wp_error($langue_term)) {
$langue_label = $langue_term->name;
} else {
// Debug: comprendre pourquoi on ne trouve pas le terme
error_log('CRVI Debug - Langue non trouvée: ID=' . $langue_original);
if (is_wp_error($langue_term)) {
error_log('CRVI Debug - Erreur WP: ' . $langue_term->get_error_message());
}
}
}
// Ajouter le label de la langue pour l'affichage dans la modal (seulement si on a trouvé le nom)
if (!empty($langue_label)) {
$event['langue_label'] = $langue_label;
} else {
// Ne pas assigner l'ID comme fallback - laisser le champ vide
$event['langue_label'] = '';
}
}
// Convertir le département - ajouter departement_label pour l'affichage
if (!empty($event['id_departement']) && $event['id_departement'] != '0' && $event['id_departement'] != 0) {
$departement_post = get_post((int)$event['id_departement']);
if ($departement_post) {
$event['departement_nom'] = $departement_post->post_title;
$event['departement_label'] = $departement_post->post_title;
}
}
// Convertir le type d'intervention - ajouter type_intervention_label pour l'affichage
if (!empty($event['id_type_intervention']) && $event['id_type_intervention'] != '0' && $event['id_type_intervention'] != 0) {
$type_post = get_post((int)$event['id_type_intervention']);
if ($type_post) {
$event['type_intervention_nom'] = $type_post->post_title;
$event['type_intervention_label'] = $type_post->post_title;
}
}
// Ajouter nom_traducteur si id_traducteur est 0 ou null
if (empty($event['id_traducteur']) || $event['id_traducteur'] == '0' || $event['id_traducteur'] == 0) {
if (!empty($event['nom_traducteur'])) {
// nom_traducteur est déjà dans les données de l'événement
}
}
}
return Api_Helper::json_success($events);
}
public static function get_event($request) {
$id = (int) $request['id'];
$model = new CRVI_Event_Model();
$event = $model->get_event_enriched($id);
if (!$event) {
return Api_Helper::json_error('Événement introuvable', 404);
}
// Convertir la langue - ajouter langue_label pour l'affichage
if (!empty($event['langue'])) {
$langue_original = $event['langue'];
$langue_label = '';
// Essayer d'abord par ID numérique
$langue_term = get_term((int)$langue_original, 'langue');
if ($langue_term && !is_wp_error($langue_term)) {
$langue_label = $langue_term->name;
} else {
// Essayer par slug si l'ID ne fonctionne pas
$langue_term = get_term_by('slug', $langue_original, 'langue');
if ($langue_term && !is_wp_error($langue_term)) {
$langue_label = $langue_term->name;
} else {
// Debug: comprendre pourquoi on ne trouve pas le terme
error_log('CRVI Debug get_event - Langue non trouvée: ID=' . $langue_original);
if (is_wp_error($langue_term)) {
error_log('CRVI Debug get_event - Erreur WP: ' . $langue_term->get_error_message());
}
}
}
// Ajouter le label de la langue pour l'affichage dans la modal (seulement si on a trouvé le nom)
if (!empty($langue_label)) {
$event['langue_label'] = $langue_label;
} else {
// Ne pas assigner l'ID comme fallback - laisser le champ vide
$event['langue_label'] = '';
}
}
// Convertir le département
if (!empty($event['id_departement'])) {
$departement_post = get_post((int)$event['id_departement']);
if ($departement_post) {
$event['departement_nom'] = $departement_post->post_title;
}
}
// Convertir le type d'intervention
if (!empty($event['id_type_intervention'])) {
$type_post = get_post((int)$event['id_type_intervention']);
if ($type_post) {
$event['type_intervention_nom'] = $type_post->post_title;
}
}
return Api_Helper::json_success($event);
}
public static function create_event($request) {
$data = $request->get_json_params();
// Valider les capacités de traduction si l'événement a une langue
if (!empty($data['langue']) && !empty($data['date_rdv']) && !empty($data['heure_rdv'])) {
$validation = self::validate_traduction_capacite($data['langue'], $data['date_rdv'], $data['heure_rdv']);
if (!$validation['valid']) {
return Api_Helper::json_error($validation['message'], 400);
}
}
$model = new CRVI_Event_Model();
$result = $model->create_event($data);
if (is_wp_error($result)) {
return Api_Helper::json_error($result->get_error_message(), 400);
}
// Invalider le cache des capacités de traduction si l'événement a une langue
if (!empty($data['langue'])) {
\ESI_CRVI_AGENDA\models\CRVI_TraductionLangue_Model::invalidate_cache();
}
return Api_Helper::json_success(['id' => $result, 'message' => 'Événement créé avec succès']);
}
public static function update_event($request) {
$id = (int) $request['id'];
$data = $request->get_json_params();
// Vérifier que les données JSON sont présentes
if ($data === null) {
// Essayer de récupérer depuis le body brut
$body = $request->get_body();
if (!empty($body)) {
$data = json_decode($body, true);
if (json_last_error() !== JSON_ERROR_NONE) {
return Api_Helper::json_error('Données JSON invalides: ' . json_last_error_msg(), 400);
}
}
}
// Si toujours null, utiliser un tableau vide
if ($data === null) {
$data = [];
}
// Récupérer l'événement existant pour obtenir les valeurs actuelles
$model = new CRVI_Event_Model();
$existing_event = $model->get_event_enriched($id);
// Fusionner les données existantes avec les nouvelles pour la validation
$merged_data = array_merge(
[
'langue' => $existing_event['langue'] ?? null,
'date_rdv' => $existing_event['date_rdv'] ?? null,
'heure_rdv' => $existing_event['heure_rdv'] ?? null,
],
$data
);
// Valider les capacités de traduction si l'événement a une langue
if (!empty($merged_data['langue']) && !empty($merged_data['date_rdv']) && !empty($merged_data['heure_rdv'])) {
$validation = self::validate_traduction_capacite(
$merged_data['langue'],
$merged_data['date_rdv'],
$merged_data['heure_rdv'],
$id // Exclure l'événement actuel du comptage
);
if (!$validation['valid']) {
return Api_Helper::json_error($validation['message'], 400);
}
}
$result = $model->update_event($id, $data);
if (is_wp_error($result)) {
$error_code = $result->get_error_code();
$error_data = $result->get_error_data();
$status_code = isset($error_data['status']) ? $error_data['status'] : 400;
return Api_Helper::json_error($result->get_error_message(), $status_code);
}
// Invalider le cache des capacités de traduction après modification
\ESI_CRVI_AGENDA\models\CRVI_TraductionLangue_Model::invalidate_cache();
return Api_Helper::json_success(['id' => $id, 'message' => 'Événement modifié avec succès']);
}
public static function delete_event($request) {
$id = (int) $request['id'];
$model = new CRVI_Event_Model();
$result = $model->delete_event($id);
if (is_wp_error($result)) {
return Api_Helper::json_error($result->get_error_message(), 400);
}
// Invalider le cache des capacités de traduction après suppression
\ESI_CRVI_AGENDA\models\CRVI_TraductionLangue_Model::invalidate_cache();
return Api_Helper::json_success(['id' => $id, 'message' => 'Événement supprimé avec succès']);
}
/**
* Valide la disponibilité des capacités de traduction pour un événement
*
* @param string $langue_slug Slug de la langue (ex: 'arabe', 'tigrinya')
* @param string $date_rdv Date du RDV (format: Y-m-d)
* @param string $heure_rdv Heure du RDV (format: H:i:s)
* @param int|null $exclude_event_id ID de l'événement à exclure du comptage (pour les modifications)
* @return array ['valid' => bool, 'message' => string, 'details' => array]
*/
private static function validate_traduction_capacite($langue_slug, $date_rdv, $heure_rdv, $exclude_event_id = null) {
// Récupérer le terme de la langue depuis le slug
$langue_term = get_term_by('slug', $langue_slug, 'langue');
if (!$langue_term || is_wp_error($langue_term)) {
return [
'valid' => true, // Si la langue n'existe pas, on ne bloque pas
'message' => '',
'details' => []
];
}
// Extraire le jour de la semaine depuis la date
$date_obj = new \DateTime($date_rdv);
$jours_map = [
0 => 'dimanche',
1 => 'lundi',
2 => 'mardi',
3 => 'mercredi',
4 => 'jeudi',
5 => 'vendredi',
6 => 'samedi'
];
$jour = $jours_map[(int) $date_obj->format('w')];
// Extraire la période depuis l'heure
$heure_obj = new \DateTime($heure_rdv);
$heure_int = (int) $heure_obj->format('H');
$periode = $heure_int < 12 ? 'matin' : 'apres_midi';
// Récupérer les capacités disponibles pour cette langue
$capacites = \ESI_CRVI_AGENDA\models\CRVI_TraductionLangue_Model::getActiveCapacites([
'langue' => $langue_term->term_id
], false);
if (empty($capacites)) {
return [
'valid' => false,
'message' => sprintf(
'Aucune capacité de traduction configurée pour la langue "%s".',
$langue_term->name
),
'details' => [
'langue' => $langue_term->name,
'jour' => $jour,
'periode' => $periode
]
];
}
// Vérifier si une capacité correspond au jour et à la période
$capacite_trouvee = null;
foreach ($capacites as $capacite) {
// Vérifier le jour (si défini dans la capacité)
$jour_match = empty($capacite->jour) || $capacite->jour === $jour;
// Vérifier la période (si définie dans la capacité)
$periode_match = empty($capacite->periode) ||
$capacite->periode === 'journee' ||
$capacite->periode === $periode;
if ($jour_match && $periode_match) {
$capacite_trouvee = $capacite;
break;
}
}
if (!$capacite_trouvee) {
return [
'valid' => false,
'message' => sprintf(
'Aucune capacité de traduction disponible pour la langue "%s" le %s en %s.',
$langue_term->name,
$jour,
$periode === 'matin' ? 'matinée' : 'après-midi'
),
'details' => [
'langue' => $langue_term->name,
'jour' => $jour,
'periode' => $periode
]
];
}
// Vérifier si la capacité a encore des créneaux disponibles
$capacite_disponible = \ESI_CRVI_AGENDA\models\CRVI_TraductionLangue_Model::isAvailable(
$capacite_trouvee->id,
$date_rdv,
$capacite_trouvee
);
if (!$capacite_disponible) {
// Récupérer les statistiques pour un message détaillé
$used = \ESI_CRVI_AGENDA\models\CRVI_TraductionLangue_Model::countUsed(
$capacite_trouvee->id,
$date_rdv,
$capacite_trouvee
);
return [
'valid' => false,
'message' => sprintf(
'Capacité de traduction atteinte pour la langue "%s" le %s en %s. ' .
'Créneaux utilisés : %d/%d pour cette %s.',
$langue_term->name,
$jour,
$periode === 'matin' ? 'matinée' : 'après-midi',
$used,
$capacite_trouvee->limite,
$capacite_trouvee->limite_par === 'semaine' ? 'semaine' : 'mois'
),
'details' => [
'langue' => $langue_term->name,
'jour' => $jour,
'periode' => $periode,
'used' => $used,
'limite' => $capacite_trouvee->limite,
'limite_par' => $capacite_trouvee->limite_par
]
];
}
// Tout est OK
return [
'valid' => true,
'message' => 'Capacité de traduction disponible',
'details' => [
'langue' => $langue_term->name,
'jour' => $jour,
'periode' => $periode,
'capacite_id' => $capacite_trouvee->id
]
];
}
/**
* Crée des permanences pour l'intervenant connecté
* POST /wp-json/crvi/v1/intervenant/permanences
*
* Cette méthode crée des événements de type "permanence" pour l'intervenant connecté.
* Les permanences sont des créneaux horaires disponibles sans bénéficiaire assigné.
*/
public static function create_permanences($request) {
$data = $request->get_json_params();
$user_id = get_current_user_id();
$intervenant = CRVI_Intervenant_Model::load($user_id);
if (!$intervenant) {
return Api_Helper::json_error('Intervenant introuvable', 404);
}
// Validation des paramètres
$periode = isset($data['periode']) ? (int)$data['periode'] : 0;
$mois_debut = isset($data['mois_debut']) ? trim($data['mois_debut']) : '';
$jours = isset($data['jours']) && is_array($data['jours']) ? $data['jours'] : [];
$plage_horaire = isset($data['plage_horaire']) && is_array($data['plage_horaire']) ? $data['plage_horaire'] : [];
$duree_permanence = isset($data['duree_permanence']) ? sanitize_text_field($data['duree_permanence']) : '1h';
$nb_tranches = isset($data['nb_tranches']) && $duree_permanence === '15min' ? (int)$data['nb_tranches'] : null;
$langues = isset($data['langues']) && is_array($data['langues']) ? $data['langues'] : [];
$informations_complementaires = isset($data['informations_complementaires']) ? sanitize_textarea_field($data['informations_complementaires']) : '';
// Validation de la durée et du nombre de tranches
if (!in_array($duree_permanence, ['1h', '15min'], true)) {
return Api_Helper::json_error('Durée de permanence invalide (attendu: 1h ou 15min)', 400);
}
if ($duree_permanence === '15min') {
if ($nb_tranches === null || $nb_tranches < 1 || $nb_tranches > 4) {
return Api_Helper::json_error('Le nombre de tranches doit être entre 1 et 4 pour les permanences de 15 minutes', 400);
}
}
// Nettoyer et valider les langues (slugs de la taxonomie)
$langues_valides = [];
if (!empty($langues)) {
foreach ($langues as $langue_slug) {
$langue_slug = sanitize_text_field($langue_slug);
// Vérifier que la langue existe dans la taxonomie
$term = get_term_by('slug', $langue_slug, 'langue');
if ($term && !is_wp_error($term)) {
$langues_valides[] = $langue_slug;
}
}
}
// Validation
if (!in_array($periode, [3, 6], true)) {
return Api_Helper::json_error('La période doit être de 3 ou 6 mois', 400);
}
if (empty($mois_debut)) {
return Api_Helper::json_error('Veuillez sélectionner un mois de début', 400);
}
// Validation du format du mois (YYYY-MM)
if (!preg_match('/^\d{4}-\d{2}$/', $mois_debut)) {
return Api_Helper::json_error('Format de mois invalide (attendu: YYYY-MM)', 400);
}
if (empty($jours)) {
return Api_Helper::json_error('Veuillez sélectionner au moins un jour de la semaine', 400);
}
// Récupérer les heures sélectionnées
$heures_selectionnees = isset($data['heures']) && is_array($data['heures']) ? $data['heures'] : [];
if (empty($heures_selectionnees)) {
return Api_Helper::json_error('Veuillez sélectionner au moins une heure de permanence', 400);
}
// Validation du format des heures (HH:mm)
foreach ($heures_selectionnees as $heure) {
if (!preg_match('/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/', $heure)) {
return Api_Helper::json_error('Format d\'heure invalide (attendu: HH:mm)', 400);
}
}
// Calculer la plage de dates à partir du mois de début sélectionné
list($year, $month) = explode('-', $mois_debut);
$date_debut = new \DateTime("{$year}-{$month}-01"); // Premier jour du mois
$date_fin = clone $date_debut;
$date_fin->modify("+{$periode} months");
$date_fin->modify('-1 day'); // Dernier jour du dernier mois
// Générer les tranches horaires selon la durée choisie
$tranches_horaires = [];
if ($duree_permanence === '1h') {
// Mode 1 heure : créer une tranche d'1 heure pour chaque heure sélectionnée
foreach ($heures_selectionnees as $heure_debut) {
list($h, $m) = explode(':', $heure_debut);
$h = (int)$h;
$m = (int)$m;
$heure_fin = sprintf('%02d:%02d', ($h + 1) % 24, $m);
$tranches_horaires[] = [
'debut' => $heure_debut,
'fin' => $heure_fin,
];
}
} else {
// Mode 15 minutes : créer X tranches de 15 minutes pour chaque heure sélectionnée
foreach ($heures_selectionnees as $heure_debut) {
list($h, $m) = explode(':', $heure_debut);
$h = (int)$h;
$m = (int)$m;
// Créer le nombre de tranches demandé (1 à 4)
for ($i = 0; $i < $nb_tranches; $i++) {
$debut_minutes = $m + ($i * 15);
$fin_minutes = $debut_minutes + 15;
$tranche_h = $h + floor($debut_minutes / 60);
$tranche_m = $debut_minutes % 60;
$tranche_fin_h = $h + floor($fin_minutes / 60);
$tranche_fin_m = $fin_minutes % 60;
$tranche_debut = sprintf('%02d:%02d', $tranche_h % 24, $tranche_m);
$tranche_fin = sprintf('%02d:%02d', $tranche_fin_h % 24, $tranche_fin_m);
$tranches_horaires[] = [
'debut' => $tranche_debut,
'fin' => $tranche_fin,
];
}
}
}
// Mapping des jours de la semaine (français vers numéro)
$jours_mapping = [
'lundi' => 1,
'mardi' => 2,
'mercredi' => 3,
'jeudi' => 4,
'vendredi' => 5,
'samedi' => 6,
'dimanche' => 0,
];
$jours_numeriques = [];
foreach ($jours as $jour) {
$jour_lower = strtolower($jour);
if (isset($jours_mapping[$jour_lower])) {
$jours_numeriques[] = $jours_mapping[$jour_lower];
}
}
if (empty($jours_numeriques)) {
return Api_Helper::json_error('Jours de la semaine invalides', 400);
}
// Créer les événements pour chaque jour sélectionné dans la période
$permanences_crees = 0;
$current_date = clone $date_debut;
while ($current_date <= $date_fin) {
$jour_semaine = (int)$current_date->format('w'); // 0 = dimanche, 1 = lundi, etc.
// Ignorer les dates passées
$today = new \DateTime();
if ($current_date < $today) {
$current_date->modify('+1 day');
continue;
}
// Si ce jour de la semaine est sélectionné
if (in_array($jour_semaine, $jours_numeriques, true)) {
// Créer une permanence pour chaque tranche horaire
foreach ($tranches_horaires as $tranche) {
// Utiliser la méthode dédiée du modèle pour créer une permanence
$model = new CRVI_Event_Model();
$result = $model->create_permanence([
'date_rdv' => $current_date->format('Y-m-d'),
'heure_rdv' => $tranche['debut'],
'date_fin' => $current_date->format('Y-m-d'),
'heure_fin' => $tranche['fin'],
'id_intervenant' => $intervenant->id,
'commentaire' => !empty($informations_complementaires) ? $informations_complementaires : '',
'langues' => $langues_valides, // Langues sélectionnées (tableau de slugs)
// Passer les jours et heures sélectionnés dans le formulaire
'jours_permis' => $jours, // Jours sélectionnés (lundi, mardi, etc.)
'heures_selectionnees' => $heures_selectionnees, // Heures sélectionnées
]);
if (!is_wp_error($result)) {
$permanences_crees++;
}
}
}
$current_date->modify('+1 day');
}
if ($permanences_crees === 0) {
return Api_Helper::json_error('Aucune permanence n\'a pu être créée', 500);
}
return Api_Helper::json_success([
'message' => "Permanences enregistrées avec succès",
'permanences_crees' => $permanences_crees,
]);
}
/**
* Crée des permanences pour un intervenant (version admin)
* POST /wp-json/crvi/v1/admin/permanences
*
* Différence avec create_permanences : accepte un intervenant_id dans les paramètres
*/
public static function create_permanences_admin($request) {
$data = $request->get_json_params();
// Récupérer l'ID de l'intervenant depuis les paramètres (au lieu de get_current_user_id())
$intervenant_user_id = isset($data['intervenant_id']) ? (int)$data['intervenant_id'] : 0;
if (!$intervenant_user_id) {
return Api_Helper::json_error('Veuillez sélectionner un intervenant', 400);
}
$intervenant = CRVI_Intervenant_Model::load($intervenant_user_id);
if (!$intervenant) {
return Api_Helper::json_error('Intervenant introuvable', 404);
}
// Validation des paramètres (identique à create_permanences)
$periode = isset($data['periode']) ? (int)$data['periode'] : 0;
$mois_debut = isset($data['mois_debut']) ? trim($data['mois_debut']) : '';
$jours = isset($data['jours']) && is_array($data['jours']) ? $data['jours'] : [];
$duree_permanence = isset($data['duree_permanence']) ? sanitize_text_field($data['duree_permanence']) : '1h';
$nb_tranches = isset($data['nb_tranches']) && $duree_permanence === '15min' ? (int)$data['nb_tranches'] : null;
$langues = isset($data['langues']) && is_array($data['langues']) ? $data['langues'] : [];
$informations_complementaires = isset($data['informations_complementaires']) ? sanitize_textarea_field($data['informations_complementaires']) : '';
// Validation de la durée et du nombre de tranches
if (!in_array($duree_permanence, ['1h', '15min'], true)) {
return Api_Helper::json_error('Durée de permanence invalide (attendu: 1h ou 15min)', 400);
}
if ($duree_permanence === '15min') {
if ($nb_tranches === null || $nb_tranches < 1 || $nb_tranches > 4) {
return Api_Helper::json_error('Le nombre de tranches doit être entre 1 et 4 pour les permanences de 15 minutes', 400);
}
}
// Nettoyer et valider les langues (slugs de la taxonomie)
$langues_valides = [];
if (!empty($langues)) {
foreach ($langues as $langue_slug) {
$langue_slug = sanitize_text_field($langue_slug);
// Vérifier que la langue existe dans la taxonomie
$term = get_term_by('slug', $langue_slug, 'langue');
if ($term && !is_wp_error($term)) {
$langues_valides[] = $langue_slug;
}
}
}
// Validation
if (!in_array($periode, [3, 6], true)) {
return Api_Helper::json_error('La période doit être de 3 ou 6 mois', 400);
}
if (empty($mois_debut)) {
return Api_Helper::json_error('Veuillez sélectionner un mois de début', 400);
}
// Validation du format du mois (YYYY-MM)
if (!preg_match('/^\d{4}-\d{2}$/', $mois_debut)) {
return Api_Helper::json_error('Format de mois invalide (attendu: YYYY-MM)', 400);
}
if (empty($jours)) {
return Api_Helper::json_error('Veuillez sélectionner au moins un jour de la semaine', 400);
}
// Récupérer les heures sélectionnées
$heures_selectionnees = isset($data['heures']) && is_array($data['heures']) ? $data['heures'] : [];
if (empty($heures_selectionnees)) {
return Api_Helper::json_error('Veuillez sélectionner au moins une heure de permanence', 400);
}
// Validation du format des heures (HH:mm)
foreach ($heures_selectionnees as $heure) {
if (!preg_match('/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/', $heure)) {
return Api_Helper::json_error('Format d\'heure invalide (attendu: HH:mm)', 400);
}
}
// Calculer la plage de dates à partir du mois de début sélectionné
list($year, $month) = explode('-', $mois_debut);
$date_debut = new \DateTime("{$year}-{$month}-01"); // Premier jour du mois
$date_fin = clone $date_debut;
$date_fin->modify("+{$periode} months");
$date_fin->modify('-1 day'); // Dernier jour du dernier mois
// Générer les tranches horaires selon la durée choisie
$tranches_horaires = [];
if ($duree_permanence === '1h') {
// Mode 1 heure : créer une tranche d'1 heure pour chaque heure sélectionnée
foreach ($heures_selectionnees as $heure_debut) {
list($h, $m) = explode(':', $heure_debut);
$h = (int)$h;
$m = (int)$m;
$heure_fin = sprintf('%02d:%02d', ($h + 1) % 24, $m);
$tranches_horaires[] = [
'debut' => $heure_debut,
'fin' => $heure_fin,
];
}
} else {
// Mode 15 minutes : créer X tranches de 15 minutes pour chaque heure sélectionnée
foreach ($heures_selectionnees as $heure_debut) {
list($h, $m) = explode(':', $heure_debut);
$h = (int)$h;
$m = (int)$m;
// Créer le nombre de tranches demandé (1 à 4)
for ($i = 0; $i < $nb_tranches; $i++) {
$debut_minutes = $m + ($i * 15);
$fin_minutes = $debut_minutes + 15;
$tranche_h = $h + floor($debut_minutes / 60);
$tranche_m = $debut_minutes % 60;
$tranche_fin_h = $h + floor($fin_minutes / 60);
$tranche_fin_m = $fin_minutes % 60;
$tranche_debut = sprintf('%02d:%02d', $tranche_h % 24, $tranche_m);
$tranche_fin = sprintf('%02d:%02d', $tranche_fin_h % 24, $tranche_fin_m);
$tranches_horaires[] = [
'debut' => $tranche_debut,
'fin' => $tranche_fin,
];
}
}
}
// Mapping des jours de la semaine (français vers numéro)
$jours_mapping = [
'lundi' => 1,
'mardi' => 2,
'mercredi' => 3,
'jeudi' => 4,
'vendredi' => 5,
'samedi' => 6,
'dimanche' => 0,
];
$jours_numeriques = [];
foreach ($jours as $jour) {
$jour_lower = strtolower($jour);
if (isset($jours_mapping[$jour_lower])) {
$jours_numeriques[] = $jours_mapping[$jour_lower];
}
}
if (empty($jours_numeriques)) {
return Api_Helper::json_error('Jours de la semaine invalides', 400);
}
// Créer les événements pour chaque jour sélectionné dans la période
$permanences_crees = 0;
$current_date = clone $date_debut;
while ($current_date <= $date_fin) {
$jour_semaine = (int)$current_date->format('w'); // 0 = dimanche, 1 = lundi, etc.
// Ignorer les dates passées
$today = new \DateTime();
if ($current_date < $today) {
$current_date->modify('+1 day');
continue;
}
// Si ce jour de la semaine est sélectionné
if (in_array($jour_semaine, $jours_numeriques, true)) {
// Créer une permanence pour chaque tranche horaire
foreach ($tranches_horaires as $tranche) {
// Utiliser la méthode dédiée du modèle pour créer une permanence
$model = new CRVI_Event_Model();
$result = $model->create_permanence([
'date_rdv' => $current_date->format('Y-m-d'),
'heure_rdv' => $tranche['debut'],
'date_fin' => $current_date->format('Y-m-d'),
'heure_fin' => $tranche['fin'],
'id_intervenant' => $intervenant->id,
'commentaire' => !empty($informations_complementaires) ? $informations_complementaires : '',
'langues' => $langues_valides, // Langues sélectionnées (tableau de slugs)
// Passer les jours et heures sélectionnés dans le formulaire
'jours_permis' => $jours, // Jours sélectionnés (lundi, mardi, etc.)
'heures_selectionnees' => $heures_selectionnees, // Heures sélectionnées
]);
if (!is_wp_error($result)) {
$permanences_crees++;
}
}
}
$current_date->modify('+1 day');
}
if ($permanences_crees === 0) {
return Api_Helper::json_error('Aucune permanence n\'a pu être créée', 500);
}
return Api_Helper::json_success([
'message' => "Permanences enregistrées avec succès pour " . $intervenant->nom . ' ' . $intervenant->prenom,
'permanences_crees' => $permanences_crees,
]);
}
/**
* Import CSV de permanences (admin)
* POST /wp-json/crvi/v1/admin/permanences/import-csv
*
* Format CSV attendu :
* - intervenant_id : ID de l'intervenant (obligatoire)
* - date_debut : Date de début (YYYY-MM-DD) (obligatoire)
* - date_fin : Date de fin (YYYY-MM-DD) (obligatoire)
* - heure_debut : Heure de début (HH:MM) (obligatoire)
* - heure_fin : Heure de fin (HH:MM) (obligatoire)
* - informations_complementaires : Notes (optionnel)
*/
public static function import_permanences_csv($request) {
if (!current_user_can('manage_options')) {
return Api_Helper::json_error('Non autorisé', 403);
}
$file = $request->get_file_params()['file'] ?? null;
if (!$file || !is_uploaded_file($file['tmp_name'])) {
return Api_Helper::json_error('Fichier CSV manquant ou invalide', 400);
}
$handle = fopen($file['tmp_name'], 'r');
if (!$handle) {
return Api_Helper::json_error('Impossible d\'ouvrir le fichier', 400);
}
// Lire l'en-tête
$header = fgetcsv($handle, 0, ',');
if (!$header) {
fclose($handle);
return Api_Helper::json_error('Fichier CSV vide ou invalide', 400);
}
// Normaliser les noms de colonnes (minuscules avec underscores)
$header = array_map(function($k) {
return sanitize_title(str_replace(' ', '_', strtolower(trim($k))));
}, $header);
$created = 0;
$errors = [];
$row_num = 1;
// Lire chaque ligne
while (($row = fgetcsv($handle, 0, ',')) !== false) {
$row_num++;
// Combiner l'en-tête avec les valeurs
$data = array_combine($header, array_map('trim', $row));
// Validation des champs obligatoires
$intervenant_id = isset($data['intervenant_id']) ? (int)$data['intervenant_id'] : 0;
$date_debut = isset($data['date_debut']) ? trim($data['date_debut']) : '';
$date_fin = isset($data['date_fin']) ? trim($data['date_fin']) : '';
$heure_debut = isset($data['heure_debut']) ? trim($data['heure_debut']) : '';
$heure_fin = isset($data['heure_fin']) ? trim($data['heure_fin']) : '';
$informations_complementaires = isset($data['informations_complementaires']) ? trim($data['informations_complementaires']) : '';
// Validation
if (!$intervenant_id) {
$errors[] = ['line' => $row_num, 'message' => 'intervenant_id manquant ou invalide'];
continue;
}
if (empty($date_debut) || empty($date_fin)) {
$errors[] = ['line' => $row_num, 'message' => 'date_debut ou date_fin manquant'];
continue;
}
// Validation du format des dates (YYYY-MM-DD)
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $date_debut) ||
!preg_match('/^\d{4}-\d{2}-\d{2}$/', $date_fin)) {
$errors[] = ['line' => $row_num, 'message' => 'Format de date invalide (attendu: YYYY-MM-DD)'];
continue;
}
// Validation du format des heures (HH:MM)
if (empty($heure_debut) || empty($heure_fin)) {
$errors[] = ['line' => $row_num, 'message' => 'heure_debut ou heure_fin manquant'];
continue;
}
if (!preg_match('/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/', $heure_debut) ||
!preg_match('/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/', $heure_fin)) {
$errors[] = ['line' => $row_num, 'message' => 'Format d\'heure invalide (attendu: HH:MM)'];
continue;
}
// Vérifier que l'intervenant existe
$intervenant = \ESI_CRVI_AGENDA\models\CRVI_Intervenant_Model::load($intervenant_id);
if (!$intervenant) {
$errors[] = ['line' => $row_num, 'message' => "Intervenant avec l'ID $intervenant_id introuvable"];
continue;
}
// Créer la permanence pour chaque date entre date_debut et date_fin
$date_start = new \DateTime($date_debut);
$date_end = new \DateTime($date_fin);
$date_end->modify('+1 day'); // Inclure le jour de fin
$current_date = clone $date_start;
$permanences_ligne = 0;
while ($current_date < $date_end) {
$date_str = $current_date->format('Y-m-d');
// Utiliser la méthode du modèle pour créer une permanence
$model = new CRVI_Event_Model();
$result = $model->create_permanence([
'date_rdv' => $date_str,
'heure_rdv' => $heure_debut,
'date_fin' => $date_str,
'heure_fin' => $heure_fin,
'id_intervenant' => $intervenant_id,
'commentaire' => $informations_complementaires,
]);
if (!is_wp_error($result)) {
$permanences_ligne++;
$created++;
} else {
$errors[] = [
'line' => $row_num,
'message' => "Erreur création permanence pour $date_str : " . $result->get_error_message()
];
}
$current_date->modify('+1 day');
}
}
fclose($handle);
return Api_Helper::json_success([
'message' => "Import terminé : $created permanence(s) créée(s)",
'created' => $created,
'errors' => $errors,
]);
}
/**
* Import CSV de permanences (intervenant)
* POST /wp-json/crvi/v1/intervenant/permanences/import-csv
*
* Format CSV attendu (sans intervenant_id - utilise l'intervenant connecté) :
* - date_debut : Date de début (YYYY-MM-DD) (obligatoire)
* - date_fin : Date de fin (YYYY-MM-DD) (obligatoire)
* - heure_debut : Heure de début (HH:MM) (obligatoire)
* - heure_fin : Heure de fin (HH:MM) (obligatoire)
* - informations_complementaires : Notes (optionnel)
*/
public static function import_permanences_csv_intervenant($request) {
// Vérifier les permissions (intervenant connecté)
if (!\ESI_CRVI_AGENDA\controllers\Intervenant_Space_Controller::check_intervenant_permission()) {
return Api_Helper::json_error('Non autorisé', 403);
}
$user_id = get_current_user_id();
$intervenant = \ESI_CRVI_AGENDA\models\CRVI_Intervenant_Model::load($user_id);
if (!$intervenant) {
return Api_Helper::json_error('Intervenant introuvable pour cet utilisateur', 404);
}
$file = $request->get_file_params()['file'] ?? null;
if (!$file || !is_uploaded_file($file['tmp_name'])) {
return Api_Helper::json_error('Fichier CSV manquant ou invalide', 400);
}
$handle = fopen($file['tmp_name'], 'r');
if (!$handle) {
return Api_Helper::json_error('Impossible d\'ouvrir le fichier', 400);
}
// Lire l'en-tête
$header = fgetcsv($handle, 0, ',');
if (!$header) {
fclose($handle);
return Api_Helper::json_error('Fichier CSV vide ou invalide', 400);
}
// Normaliser les noms de colonnes (minuscules avec underscores)
$header = array_map(function($k) {
return sanitize_title(str_replace(' ', '_', strtolower(trim($k))));
}, $header);
$created = 0;
$errors = [];
$row_num = 1;
// Lire chaque ligne
while (($row = fgetcsv($handle, 0, ',')) !== false) {
$row_num++;
// Combiner l'en-tête avec les valeurs
$data = array_combine($header, array_map('trim', $row));
// Validation des champs obligatoires
$date_debut = isset($data['date_debut']) ? trim($data['date_debut']) : '';
$date_fin = isset($data['date_fin']) ? trim($data['date_fin']) : '';
$heure_debut = isset($data['heure_debut']) ? trim($data['heure_debut']) : '';
$heure_fin = isset($data['heure_fin']) ? trim($data['heure_fin']) : '';
$informations_complementaires = isset($data['informations_complementaires']) ? trim($data['informations_complementaires']) : '';
// Validation
if (empty($date_debut) || empty($date_fin)) {
$errors[] = ['line' => $row_num, 'message' => 'date_debut ou date_fin manquant'];
continue;
}
// Validation du format des dates (YYYY-MM-DD)
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $date_debut) ||
!preg_match('/^\d{4}-\d{2}-\d{2}$/', $date_fin)) {
$errors[] = ['line' => $row_num, 'message' => 'Format de date invalide (attendu: YYYY-MM-DD)'];
continue;
}
// Validation du format des heures (HH:MM)
if (empty($heure_debut) || empty($heure_fin)) {
$errors[] = ['line' => $row_num, 'message' => 'heure_debut ou heure_fin manquant'];
continue;
}
if (!preg_match('/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/', $heure_debut) ||
!preg_match('/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/', $heure_fin)) {
$errors[] = ['line' => $row_num, 'message' => 'Format d\'heure invalide (attendu: HH:MM)'];
continue;
}
// Créer la permanence pour chaque date entre date_debut et date_fin
$date_start = new \DateTime($date_debut);
$date_end = new \DateTime($date_fin);
$date_end->modify('+1 day'); // Inclure le jour de fin
$current_date = clone $date_start;
$permanences_ligne = 0;
while ($current_date < $date_end) {
$date_str = $current_date->format('Y-m-d');
// Utiliser la méthode du modèle pour créer une permanence
$model = new CRVI_Event_Model();
$result = $model->create_permanence([
'date_rdv' => $date_str,
'heure_rdv' => $heure_debut,
'date_fin' => $date_str,
'heure_fin' => $heure_fin,
'id_intervenant' => $intervenant->id,
'commentaire' => $informations_complementaires,
]);
if (!is_wp_error($result)) {
$permanences_ligne++;
$created++;
} else {
$errors[] = [
'line' => $row_num,
'message' => "Erreur création permanence pour $date_str : " . $result->get_error_message()
];
}
$current_date->modify('+1 day');
}
}
fclose($handle);
return Api_Helper::json_success([
'message' => "Import terminé : $created permanence(s) créée(s)",
'created' => $created,
'errors' => $errors,
]);
}
public static function get_deleted_events($request) {
$params = $request->get_params();
$model = new CRVI_Event_Model();
$events = $model->get_deleted_events($params);
return Api_Helper::json_success($events);
}
public static function restore_event($request) {
$id = (int) $request['id'];
$model = new CRVI_Event_Model();
$result = $model->restore_event($id);
if (is_wp_error($result)) {
return Api_Helper::json_error($result->get_error_message(), 400);
}
// Invalider le cache des capacités de traduction après restauration
\ESI_CRVI_AGENDA\models\CRVI_TraductionLangue_Model::invalidate_cache();
return Api_Helper::json_success(['id' => $id, 'message' => 'Événement restauré avec succès']);
}
public static function hard_delete_event($request) {
$id = (int) $request['id'];
$model = new CRVI_Event_Model();
$result = $model->hard_delete_event($id);
if (is_wp_error($result)) {
return Api_Helper::json_error($result->get_error_message(), 400);
}
// Invalider le cache des capacités de traduction après suppression définitive
\ESI_CRVI_AGENDA\models\CRVI_TraductionLangue_Model::invalidate_cache();
return Api_Helper::json_success(['id' => $id, 'message' => 'Événement supprimé définitivement']);
}
// --- Avancés ---
public static function cloture_event($request) {
$id = (int) $request['id'];
$data = $request->get_json_params();
$model = new CRVI_Event_Model();
$result = $model->cloture_event($id, $data['statut'] ?? '');
if (is_wp_error($result)) {
return Api_Helper::json_error($result->get_error_message(), 400);
}
return Api_Helper::json_success(['id' => $id, 'message' => 'Événement clôturé avec succès']);
}
public static function change_statut($request) {
$id = (int) $request['id'];
$data = $request->get_json_params();
$model = new CRVI_Event_Model();
$result = $model->change_statut($id, $data);
if (is_wp_error($result)) {
return Api_Helper::json_error($result->get_error_message(), 400);
}
return Api_Helper::json_success(['id' => $id, 'message' => 'Statut modifié avec succès']);
}
/**
* Retourne la couleur selon le statut
*/
private static function get_status_color($statut) {
switch ($statut) {
case 'prevu':
return '#28a745'; // Vert
case 'annule':
return '#dc3545'; // Rouge
case 'non_tenu':
return '#ffc107'; // Jaune
case 'cloture':
return '#6c757d'; // Gris
case 'absence':
return '#fd7e14'; // Orange
default:
return '#007bff'; // Bleu
}
}
public static function get_events_fullcalendar($request) {
$model = new CRVI_Event_Model();
$params = $request->get_params();
// Support de la pagination FullCalendar
$date_debut = $params['start'] ?? $params['date_debut'] ?? null;
$date_fin = $params['end'] ?? $params['date_fin'] ?? null;
// Filtrer les paramètres pour éviter les conflits
$filters = array_diff_key($params, array_flip(['start', 'end', 'date_debut', 'date_fin']));
// Ajouter les dates de début et fin aux filtres si elles existent
if ($date_debut) {
$filters['date_debut'] = $date_debut;
}
if ($date_fin) {
$filters['date_fin'] = $date_fin;
}
// Utiliser get_events_by_filters au lieu de get_events_by pour une meilleure compatibilité
$events = $model->get_events_by_filters($filters);
// Formater pour FullCalendar
$formatted_events = [];
foreach ($events as $event) {
// Récupérer les détails des entités liées
$details = $model->get_details($event['id']);
// Couleur basée sur le type d'intervention (ACF champ 'couleur' sur le CPT type_intervention)
$type_color = '#6c757d';
if (!empty($event['type_intervention'])) {
$color_field = function_exists('get_field') ? get_field('couleur', (int)$event['type_intervention']) : null;
if (!empty($color_field)) {
$type_color = $color_field;
}
}
// Convertir la langue pour obtenir le label
$langue_label = '';
if (!empty($event['langue'])) {
$langue_original = $event['langue'];
// Essayer d'abord par ID numérique
$langue_term = get_term((int)$langue_original, 'langue');
if ($langue_term && !is_wp_error($langue_term)) {
$langue_label = $langue_term->name;
} else {
// Essayer par slug si l'ID ne fonctionne pas
$langue_term = get_term_by('slug', $langue_original, 'langue');
if ($langue_term && !is_wp_error($langue_term)) {
$langue_label = $langue_term->name;
} else {
// Debug: comprendre pourquoi on ne trouve pas le terme
error_log('CRVI Debug FullCalendar - Langue non trouvée: ID=' . $langue_original);
if (is_wp_error($langue_term)) {
error_log('CRVI Debug FullCalendar - Erreur WP: ' . $langue_term->get_error_message());
}
}
}
}
// Convertir le département pour obtenir le label
$departement_label = '';
if (!empty($event['id_departement']) && $event['id_departement'] != '0' && $event['id_departement'] != 0) {
$departement_post = get_post((int)$event['id_departement']);
if ($departement_post) {
$departement_label = $departement_post->post_title;
}
}
// Convertir le type d'intervention pour obtenir le label
$type_intervention_label = '';
if (!empty($event['type_intervention']) && $event['type_intervention'] != '0' && $event['type_intervention'] != 0) {
$type_post = get_post((int)$event['type_intervention']);
if ($type_post) {
$type_intervention_label = $type_post->post_title;
}
}
// Récupérer nom_traducteur si id_traducteur est 0 ou null
$nom_traducteur = null;
if (empty($event['id_traducteur']) || $event['id_traducteur'] == '0' || $event['id_traducteur'] == 0) {
$nom_traducteur = $event['nom_traducteur'] ?? null;
}
$formatted_events[] = [
'id' => $event['id'],
'title' => ($details->beneficiaire->nom ?? '') . ' ' . ($details->beneficiaire->prenom ?? '') . ' - ' .
($details->intervenant->nom ?? '') . ' ' . ($details->intervenant->prenom ?? ''),
'start' => $event['date_rdv'] . 'T' . $event['heure_rdv'],
'end' => $event['date_fin'] . 'T' . $event['heure_fin'],
'backgroundColor' => $type_color,
'borderColor' => $type_color,
'textColor' => '#fff',
'extendedProps' => [
'type' => $event['type'],
'statut' => $event['statut'],
'langue' => $event['langue'],
'langue_label' => $langue_label,
'langues_disponibles' => $event['langues_disponibles'] ?? null,
'assign' => isset($event['assign']) ? (int)$event['assign'] : 0,
'id_type_intervention' => $event['type_intervention'] ?? null,
'type_intervention_label' => $type_intervention_label,
'id_departement' => $event['id_departement'] ?? null,
'departement_label' => $departement_label,
'beneficiaire' => $details->beneficiaire ?? null,
'intervenant' => $details->intervenant ?? null,
'traducteur' => $details->traducteur ?? null,
'local' => $details->local ?? null,
'commentaire' => $event['commentaire'],
'id_beneficiaire' => $event['id_beneficiaire'],
'id_intervenant' => $event['id_intervenant'],
'id_traducteur' => $event['id_traducteur'],
'nom_traducteur' => $nom_traducteur,
'id_local' => $event['id_local']
]
];
}
return Api_Helper::json_success($formatted_events);
}
/**
* Endpoint pour récupérer les événements en format tableau avec pagination
* @param \WP_REST_Request $request
* @return \WP_REST_Response
*/
public static function get_events_table($request) {
$model = new CRVI_Event_Model();
$params = $request->get_params();
// Pagination
$page = isset($params['page']) ? max(1, (int)$params['page']) : 1;
$per_page = isset($params['per_page']) ? max(1, min(100, (int)$params['per_page'])) : 20;
// Gestion du filtre par année
if (!empty($params['annee'])) {
$annee = (int)$params['annee'];
$params['date_debut'] = sprintf('%d-01-01', $annee);
$params['date_fin'] = sprintf('%d-12-31', $annee);
unset($params['annee']);
}
// Récupérer les événements avec pagination
$result = $model->get_events_table($params, $page, $per_page);
return Api_Helper::json_success($result);
}
public static function get_events_stats($request) {
$params = $request->get_params();
$model = new CRVI_Event_Model();
$stats = $model->get_events_stats($params);
return Api_Helper::json_success($stats);
}
public static function get_event_historique($request) {
$id = (int) $request['id'];
$model = new CRVI_Event_Model();
$historique = $model->get_historique();
return Api_Helper::json_success($historique);
}
// --- Filtres dynamiques ---
public static function get_departements($request) {
$departements = get_terms([
'taxonomy' => 'departement',
'hide_empty' => false,
]);
$result = [];
foreach ($departements as $departement) {
$result[] = [
'id' => $departement->term_id,
'name' => $departement->name,
'slug' => $departement->slug,
];
}
return Api_Helper::json_success($result);
}
public static function get_types_intervention($request) {
$types = get_terms([
'taxonomy' => 'type_intervention',
'hide_empty' => false,
]);
$result = [];
foreach ($types as $type) {
$result[] = [
'id' => $type->term_id,
'name' => $type->name,
'slug' => $type->slug,
];
}
return Api_Helper::json_success($result);
}
public static function get_langues($request) {
$langues = get_terms([
'taxonomy' => 'langue',
'hide_empty' => false,
]);
$result = [];
foreach ($langues as $langue) {
$result[] = [
'id' => $langue->term_id,
'name' => $langue->name,
'slug' => $langue->slug,
];
}
return Api_Helper::json_success($result);
}
public static function get_langues_beneficiaire($request) {
$langues = \ESI_CRVI_AGENDA\helpers\Api_Helper::get_languages(true);
return \ESI_CRVI_AGENDA\helpers\Api_Helper::json_success($langues);
}
public static function get_statuts($request) {
$statuts = [
['id' => 'prevu', 'label' => 'Prévu'],
['id' => 'annule', 'label' => 'Annulé'],
['id' => 'non_tenu', 'label' => 'Non tenu'],
['id' => 'cloture', 'label' => 'Clôturé'],
['id' => 'absence', 'label' => 'Absence'],
];
return Api_Helper::json_success($statuts);
}
/**
* Vérifie si l'utilisateur peut modifier un événement
* @param \WP_REST_Request $request
* @return bool
*/
public static function can_edit($request = null) {
// Si admin ou rôle ayant edit_posts : accès total
if (current_user_can('edit_posts')) {
return true;
}
// Récupérer l'id_intervenant depuis la requête ou l'événement
$id_intervenant = null;
if ($request) {
// Si on modifie un événement existant, récupérer l'ID depuis l'événement
if ($request->get_param('id')) {
$event = \ESI_CRVI_AGENDA\models\CRVI_Event_Model::load((int)$request->get_param('id'));
if ($event && isset($event->id_intervenant)) {
$id_intervenant = $event->id_intervenant;
}
}
// Sinon, essayer de le prendre dans les paramètres de la requête (création)
if (!$id_intervenant) {
$data = $request->get_json_params();
if ($data && isset($data['id_intervenant'])) {
$id_intervenant = $data['id_intervenant'];
}
}
}
// Appel à la logique intervenant
return CRVI_Intervenant_Controller::can_edit_own_event($id_intervenant);
}
/**
* Fonction pour récupérer les dates et créneaux indisponibles pour une combinaison d'entités
*/
private static function get_available_dates($id_intervenant, $id_traducteur, $id_local, $date_debut, $date_fin) {
$unavailable_dates = [];
$unavailable_slots = [];
// Convertir les dates en objets DateTime
$debut = \DateTime::createFromFormat('Y-m-d', $date_debut);
$fin = \DateTime::createFromFormat('Y-m-d', $date_fin);
if (!$debut || !$fin) {
return Api_Helper::json_error('Format de date invalide');
}
// Définir les créneaux horaires possibles (8h-18h par exemple)
$creneaux_horaires = [
'08:00', '08:30', '09:00', '09:30', '10:00', '10:30', '11:00', '11:30',
'12:00', '12:30', '13:00', '13:30', '14:00', '14:30', '15:00', '15:30',
'16:00', '16:30', '17:00', '17:30', '18:00'
];
// Parcourir chaque jour dans la plage
$current_date = clone $debut;
while ($current_date <= $fin) {
$date_str = $current_date->format('Y-m-d');
// Vérifier la disponibilité pour cette date
$disponible = self::check_availability_for_date($date_str, $id_intervenant, $id_traducteur, $id_local);
if (!$disponible) {
$unavailable_dates[] = $date_str;
} else {
// Si la date est disponible, vérifier les créneaux horaires
foreach ($creneaux_horaires as $heure) {
$disponible_creneau = self::check_availability_for_slot($date_str, $heure, $id_intervenant, $id_traducteur, $id_local);
if (!$disponible_creneau) {
$unavailable_slots[] = $date_str . ' ' . $heure;
}
}
}
$current_date->add(new \DateInterval('P1D'));
}
return Api_Helper::json_success([
'unavailable_dates' => $unavailable_dates,
'unavailable_slots' => $unavailable_slots
]);
}
/**
* Vérifie la disponibilité pour une date donnée
*/
private static function check_availability_for_date($date, $id_intervenant, $id_traducteur, $id_local) {
// Vérifier les indisponibilités ponctuelles
// Vérifier les indisponibilités ponctuelles de l'intervenant
$intervenant = \ESI_CRVI_AGENDA\models\CRVI_Intervenant_Model::load($id_intervenant);
if ($intervenant) {
$indisponibilites = $intervenant->indisponibilitee_ponctuelle ?? null;
if (!empty($indisponibilites)) {
// Pour l'intervenant, le champ n'est pas défini dans le modèle actuel
// donc on ne traite pas les indisponibilités pour l'instant
}
}
// Vérifier les indisponibilités ponctuelles du traducteur
$traducteur = \ESI_CRVI_AGENDA\models\CRVI_Traducteur_Model::load($id_traducteur);
if ($traducteur) {
$indisponibilites = $traducteur->indisponibilitee_ponctuelle ?? null;
if (!empty($indisponibilites) && is_array($indisponibilites)) {
// Le champ indisponibilitee_ponctuelle est un repeater ACF (array)
foreach ($indisponibilites as $indisponibilite) {
if (isset($indisponibilite['date']) && $indisponibilite['date'] === $date) {
return false; // Indisponible
}
}
}
}
// Vérifier les indisponibilités ponctuelles du local
$local = \ESI_CRVI_AGENDA\models\CRVI_Local_Model::load($id_local);
if ($local) {
$indisponibilites = $local->indisponibilitee_ponctuelle ?? null;
if (!empty($indisponibilites)) {
// Pour le local, le champ n'est pas défini dans le modèle actuel
// donc on ne traite pas les indisponibilités pour l'instant
}
}
// Vérifier les événements existants pour cette date
$event_model = new \ESI_CRVI_AGENDA\models\CRVI_Event_Model();
$events = $event_model->get_events_by_filters([
'date_rdv' => $date,
'id_intervenant' => $id_intervenant,
'id_traducteur' => $id_traducteur,
'id_local' => $id_local
]);
if (!empty($events)) {
return false; // Il y a déjà des événements pour cette combinaison
}
return true; // Disponible
}
/**
* Vérifie s'il y a des conflits d'événements pour un local donné
* @param int $local_id
* @param string $date_debut
* @param string $date_fin
* @param string $heure_debut
* @param string $heure_fin
* @param int|null $event_id - ID de l'événement à exclure (pour l'édition)
* @return array
*/
private static function verifier_conflits_local($local_id, $date_debut, $date_fin, $heure_debut = null, $heure_fin = null, $event_id = null) {
global $wpdb;
try {
$table_events = $wpdb->prefix . 'crvi_agenda';
// Vérifier que le local_id est valide
if (empty($local_id) || !is_numeric($local_id)) {
return [];
}
$where_conditions = ['id_local = %d', 'is_deleted = 0'];
$where_values = [$local_id];
// Exclure l'événement en cours d'édition si spécifié
if ($event_id && is_numeric($event_id)) {
$where_conditions[] = 'id != %d';
$where_values[] = $event_id;
}
// Ajouter les conditions de date
if ($date_debut && $date_fin) {
$where_conditions[] = '(
(date_rdv BETWEEN %s AND %s) OR
(date_fin BETWEEN %s AND %s) OR
(%s BETWEEN date_rdv AND date_fin) OR
(%s BETWEEN date_rdv AND date_fin)
)';
$where_values = array_merge($where_values, [$date_debut, $date_fin, $date_debut, $date_fin, $date_debut, $date_fin]);
}
// Ajouter les conditions d'heure si fournies
if ($heure_debut && $heure_fin) {
$where_conditions[] = '(
(heure_rdv BETWEEN %s AND %s) OR
(heure_fin BETWEEN %s AND %s) OR
(%s BETWEEN heure_rdv AND heure_fin) OR
(%s BETWEEN heure_rdv AND heure_fin)
)';
$where_values = array_merge($where_values, [$heure_debut, $heure_fin, $heure_debut, $heure_fin, $heure_debut, $heure_fin]);
}
$where_clause = implode(' AND ', $where_conditions);
$query = $wpdb->prepare(
"SELECT * FROM {$table_events} WHERE {$where_clause}",
$where_values
);
if ($wpdb->last_error) {
error_log('[CRVI] Erreur SQL lors de la vérification des conflits de local: ' . $wpdb->last_error);
return [];
}
$results = $wpdb->get_results($query, ARRAY_A);
return is_array($results) ? $results : [];
} catch (\Exception $e) {
error_log('[CRVI] Exception lors de la vérification des conflits de local: ' . $e->getMessage());
return [];
}
}
/**
* Vérifie la disponibilité pour un créneau horaire spécifique
*/
private static function check_availability_for_slot($date, $heure, $id_intervenant, $id_traducteur, $id_local) {
// Vérifier les indisponibilités ponctuelles avec heure
// Vérifier les indisponibilités ponctuelles de l'intervenant
$intervenant = \ESI_CRVI_AGENDA\models\CRVI_Intervenant_Model::load($id_intervenant);
if ($intervenant) {
$indisponibilites = $intervenant->indisponibilitee_ponctuelle ?? null;
if (!empty($indisponibilites)) {
// Pour l'intervenant, le champ n'est pas défini dans le modèle actuel
// donc on ne traite pas les indisponibilités pour l'instant
}
}
// Vérifier les indisponibilités ponctuelles du traducteur
$traducteur = \ESI_CRVI_AGENDA\models\CRVI_Traducteur_Model::load($id_traducteur);
if ($traducteur) {
$indisponibilites = $traducteur->indisponibilitee_ponctuelle ?? null;
if (!empty($indisponibilites) && is_array($indisponibilites)) {
// Le champ indisponibilitee_ponctuelle est un repeater ACF (array)
foreach ($indisponibilites as $indisponibilite) {
if (isset($indisponibilite['date']) && $indisponibilite['date'] === $date) {
// Si pas d'heures spécifiées, toute la journée est indisponible
if (empty($indisponibilite['heure_debut']) && empty($indisponibilite['heure_fin'])) {
return false;
}
// Si heures spécifiées, vérifier si le créneau est dans la plage
if (!empty($indisponibilite['heure_debut']) && !empty($indisponibilite['heure_fin'])) {
if ($heure >= $indisponibilite['heure_debut'] && $heure < $indisponibilite['heure_fin']) {
return false;
}
}
}
}
}
}
// Vérifier les indisponibilités ponctuelles du local
$local = \ESI_CRVI_AGENDA\models\CRVI_Local_Model::load($id_local);
if ($local) {
$indisponibilites = $local->indisponibilitee_ponctuelle ?? null;
if (!empty($indisponibilites)) {
// Pour le local, le champ n'est pas défini dans le modèle actuel
// donc on ne traite pas les indisponibilités pour l'instant
}
}
// Vérifier les événements existants pour ce créneau
$event_model = new \ESI_CRVI_AGENDA\models\CRVI_Event_Model();
$events = $event_model->get_events_by_filters([
'date_rdv' => $date,
'heure_rdv' => $heure,
'id_intervenant' => $id_intervenant,
'id_traducteur' => $id_traducteur,
'id_local' => $id_local
]);
if (!empty($events)) {
return false; // Il y a déjà des événements pour cette combinaison
}
return true; // Disponible
}
/**
* Récupérer l'historique des 3 derniers rendez-vous d'un bénéficiaire
* @param \WP_REST_Request $request
* @return \WP_REST_Response|\WP_Error
*/
public static function get_beneficiaire_historique($request) {
$beneficiaire_id = (int) $request['id'];
if (!$beneficiaire_id) {
return Api_Helper::json_error('ID du bénéficiaire requis', 400);
}
// Vérifier que le bénéficiaire existe
$beneficiaire = \ESI_CRVI_AGENDA\models\CRVI_Beneficiaire_Model::load($beneficiaire_id);
if (!$beneficiaire) {
return Api_Helper::json_error('Bénéficiaire introuvable', 404);
}
// Récupérer les 3 derniers RDV avec leurs incidents
$historique = CRVI_Event_Model::get_last_3_rdv_by_beneficiaire($beneficiaire_id);
return Api_Helper::json_success($historique);
}
/**
* Récupère les présences d'un événement
* @param \WP_REST_Request $request
* @return \WP_REST_Response|\WP_Error
*/
public static function get_event_presences($request) {
$event_id = (int) $request['id'];
if (!$event_id) {
return Api_Helper::json_error('ID d\'événement requis', 400);
}
// Vérifier que l'événement existe
$event = CRVI_Event_Model::load($event_id);
if (!$event) {
return Api_Helper::json_error('Événement introuvable', 404);
}
// Valider le type d'événement
$event_type = $event->type ?? '';
$assign = isset($event->assign) ? (int)$event->assign : 0;
// Bloquer les rendez-vous individuels
if ($event_type === 'individuel') {
return Api_Helper::json_error('Les présences ne sont pas applicables aux rendez-vous individuels', 400);
}
// Bloquer les permanences non attribuées
if ($event_type === 'permanence' && $assign === 0) {
return Api_Helper::json_error('Les présences ne sont pas applicables aux permanences non attribuées', 400);
}
// Seuls les rendez-vous de groupe sont acceptés
if ($event_type !== 'groupe') {
return Api_Helper::json_error('Cet endpoint est uniquement accessible pour les rendez-vous de groupe', 400);
}
// Récupérer les présences de l'événement
$presences = CRVI_Presence_Model::get_presences_by_event($event_id);
$statut = $event->statut ?? '';
$response = [
'presences' => $presences,
'event_type' => $event_type,
'statut' => $statut,
'has_presence_data' => !empty($presences)
];
return Api_Helper::json_success($response);
}
/**
* Enregistre les présences pour un rendez-vous de groupe
* @param \WP_REST_Request $request
* @return \WP_REST_Response|\WP_Error
*/
public static function save_group_presences($request) {
$event_id = (int) $request['id'];
if (!$event_id) {
return Api_Helper::json_error('ID d\'événement requis', 400);
}
// Vérifier que l'événement existe
$event = CRVI_Event_Model::load($event_id);
if (!$event) {
return Api_Helper::json_error('Événement introuvable', 404);
}
// Valider le type d'événement
$event_type = $event->type ?? '';
$assign = isset($event->assign) ? (int)$event->assign : 0;
// Bloquer les rendez-vous individuels
if ($event_type === 'individuel') {
return Api_Helper::json_error('Les présences ne sont pas applicables aux rendez-vous individuels', 400);
}
// Bloquer les permanences non attribuées
if ($event_type === 'permanence' && $assign === 0) {
return Api_Helper::json_error('Les présences ne sont pas applicables aux permanences non attribuées', 400);
}
// Seuls les rendez-vous de groupe sont acceptés
if ($event_type !== 'groupe') {
return Api_Helper::json_error('Cet événement n\'est pas un rendez-vous de groupe', 400);
}
// Récupérer les données de présences depuis le body
$body = $request->get_json_params();
$presences = $body['presences'] ?? [];
if (empty($presences) || !is_array($presences)) {
return Api_Helper::json_error('Les données de présences sont requises', 400);
}
// Récupérer la langue de l'événement (convertir ID/slug en nom)
$langue = $event->langue ?? '';
$langue_nom = '';
if (!empty($langue)) {
// Essayer d'abord par ID numérique
$langue_term = get_term((int)$langue, 'langue');
if ($langue_term && !is_wp_error($langue_term)) {
$langue_nom = $langue_term->name;
} else {
// Essayer par slug si l'ID ne fonctionne pas
$langue_term = get_term_by('slug', $langue, 'langue');
if ($langue_term && !is_wp_error($langue_term)) {
$langue_nom = $langue_term->name;
} else {
// Si ni ID ni slug ne fonctionnent, utiliser la valeur telle quelle
$langue_nom = $langue;
}
}
}
// Valider et enregistrer chaque présence
$saved_count = 0;
$errors = [];
try {
foreach ($presences as $index => $presence) {
// Valider les données
$nom = isset($presence['nom']) ? trim($presence['nom']) : '';
$prenom = isset($presence['prenom']) ? trim($presence['prenom']) : '';
$is_present = isset($presence['is_present']) ? (bool) $presence['is_present'] : false;
// Ignorer les lignes vides (nom et prénom vides)
if (empty($nom) && empty($prenom)) {
continue;
}
// Vérifier que nom et prénom sont remplis
if (empty($nom) || empty($prenom)) {
$errors[] = "Ligne " . ($index + 1) . ": Le nom et le prénom sont requis";
continue;
}
try {
// Vérifier si un beneficiaire_id est fourni (bénéficiaire existant sélectionné)
$beneficiaire_id = isset($presence['beneficiaire_id']) && !empty($presence['beneficiaire_id'])
? (int) $presence['beneficiaire_id']
: null;
if ($beneficiaire_id !== null) {
// Utiliser l'ID du bénéficiaire existant
CRVI_Presence_Model::save_presence($event_id, null, $is_present, $beneficiaire_id);
} else {
// Créer une nouvelle personne dans wp_crvi_agenda_persons
$person_id = CRVI_Presence_Model::save_person($nom, $prenom, $langue_nom);
// Enregistrer la présence avec person_id
CRVI_Presence_Model::save_presence($event_id, $person_id, $is_present, null);
}
$saved_count++;
} catch (\Exception $e) {
$errors[] = "Ligne " . ($index + 1) . ": " . $e->getMessage();
}
}
if (!empty($errors) && $saved_count === 0) {
// Toutes les présences ont échoué
return Api_Helper::json_error('Erreurs lors de l\'enregistrement: ' . implode(', ', $errors), 400);
}
if (!empty($errors)) {
// Certaines présences ont échoué mais d'autres ont réussi
return Api_Helper::json_success([
'message' => "$saved_count présence(s) enregistrée(s) avec succès",
'warnings' => $errors,
'saved_count' => $saved_count
]);
}
return Api_Helper::json_success([
'message' => "$saved_count présence(s) enregistrée(s) avec succès",
'saved_count' => $saved_count
]);
} catch (\Exception $e) {
return Api_Helper::json_error('Erreur lors de l\'enregistrement des présences: ' . $e->getMessage(), 500);
}
}
}