1856 lines
77 KiB
PHP
1856 lines
77 KiB
PHP
<?php
|
||
declare(strict_types=1);
|
||
|
||
namespace ESI_CRVI_AGENDA\models;
|
||
|
||
use ESI_CRVI_AGENDA\models\CRVI_Intervenant_Model;
|
||
use ESI_CRVI_AGENDA\controllers\CRVI_Plugin;
|
||
use ESI_CRVI_AGENDA\models\CRVI_Beneficiaire_Model;
|
||
use ESI_CRVI_AGENDA\models\CRVI_Local_Model;
|
||
use ESI_CRVI_AGENDA\models\CRVI_Traducteur_Model;
|
||
use ESI_CRVI_AGENDA\helpers\Api_Helper;
|
||
|
||
class CRVI_Event_Model extends Main_Model {
|
||
public $id;
|
||
public $date;
|
||
public $heure;
|
||
public $date_fin;
|
||
public $heure_fin;
|
||
public $type; // individuel/groupe
|
||
public $statut; // prévu, annulé, non tenu, cloture, absence.
|
||
public $motif_annulation;
|
||
public $commentaire;
|
||
public $langue;
|
||
public $id_beneficiaire;
|
||
public $id_intervenant;
|
||
public $id_traducteur;
|
||
public $id_local;
|
||
public $nb_participants;
|
||
public $nb_hommes;
|
||
public $nb_femmes;
|
||
public $cree_par;
|
||
public $modifie_par;
|
||
public $date_creation;
|
||
public $date_modification;
|
||
// Optionnellement présents selon l'environnement (compat multi-schémas)
|
||
public $departement;
|
||
public $type_intervention;
|
||
|
||
/**
|
||
* Schéma des champs ACF : nom => type (ex : group, repeater, taxonomy, text...)
|
||
*/
|
||
public static $acf_schema = [
|
||
'date' => 'date_picker',
|
||
'heure' => 'time_picker',
|
||
'id_beneficiaire' => 'relation',
|
||
'id_intervenant' => 'relation',
|
||
'departements' => 'taxonomy',
|
||
'type_intervention' => 'taxonomy',
|
||
'local' => 'relation',
|
||
'langue' => 'taxonomy',
|
||
'statut' => 'select',
|
||
'commentaire' => 'textarea',
|
||
];
|
||
|
||
public function __construct($data = []) {
|
||
foreach ($data as $key => $value) {
|
||
if (property_exists($this, $key)) {
|
||
$this->$key = $value;
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Charger un événement par ID
|
||
* @param int $id
|
||
* @param array $fields Champs spécifiques à charger (optionnel)
|
||
* @return self|null
|
||
*/
|
||
public static function load($id, $fields = []) {
|
||
global $wpdb;
|
||
$table_name = $wpdb->prefix . 'crvi_agenda';
|
||
$row = $wpdb->get_row($wpdb->prepare("SELECT * FROM $table_name WHERE id = %d AND is_deleted = 0", $id), ARRAY_A);
|
||
if (!$row) {
|
||
return null;
|
||
}
|
||
|
||
// Si des champs spécifiques sont demandés, ne charger que ceux-ci
|
||
if (!empty($fields)) {
|
||
$data = [];
|
||
foreach ($fields as $field) {
|
||
if ($field === 'id') {
|
||
$data['id'] = (int)$row['id'];
|
||
} elseif ($field === 'beneficiaire' && isset($row['id_beneficiaire'])) {
|
||
$data['beneficiaire'] = CRVI_Beneficiaire_Model::load($row['id_beneficiaire'], ['id', 'nom', 'prenom']);
|
||
} elseif ($field === 'intervenant' && isset($row['id_intervenant'])) {
|
||
$data['intervenant'] = CRVI_Intervenant_Model::load($row['id_intervenant'], ['id', 'nom', 'prenom']);
|
||
} elseif ($field === 'traducteur' && isset($row['id_traducteur'])) {
|
||
$data['traducteur'] = CRVI_Traducteur_Model::load($row['id_traducteur'], ['id', 'nom', 'prenom']);
|
||
} elseif ($field === 'local' && isset($row['id_local'])) {
|
||
$data['local'] = CRVI_Local_Model::load($row['id_local'], ['id', 'nom', 'type_de_local']);
|
||
} elseif (isset($row[$field])) {
|
||
$data[$field] = $row[$field];
|
||
}
|
||
}
|
||
return new self($data);
|
||
}
|
||
|
||
// Sinon, charger tous les champs par défaut
|
||
$beneficiaire = CRVI_Beneficiaire_Model::load($row['id_beneficiaire'], ['id', 'nom', 'prenom']);
|
||
$intervenant = CRVI_Intervenant_Model::load($row['id_intervenant'], ['id', 'nom', 'prenom']);
|
||
$traducteur = CRVI_Traducteur_Model::load($row['id_traducteur'], ['id', 'nom', 'prenom']);
|
||
$local = CRVI_Local_Model::load($row['id_local'], ['id', 'nom', 'type_de_local']);
|
||
|
||
// Mapping des champs SQL -> propriétés de l'objet
|
||
$data = [
|
||
'id' => (int)$row['id'],
|
||
'date' => $row['date_rdv'],
|
||
'heure' => $row['heure_rdv'],
|
||
'date_fin' => $row['date_fin'],
|
||
'heure_fin' => $row['heure_fin'],
|
||
'type' => $row['type'],
|
||
'statut' => $row['statut'],
|
||
'motif_annulation' => $row['motif_annulation'],
|
||
'commentaire' => $row['commentaire'],
|
||
'departement' => $row['id_departement'],
|
||
'type_intervention' => $row['id_type_intervention'],
|
||
'langue' => $row['langue'],
|
||
'id_beneficiaire' => $row['id_beneficiaire'],
|
||
'id_intervenant' => $row['id_intervenant'],
|
||
'id_traducteur' => $row['id_traducteur'],
|
||
'id_local' => $row['id_local'],
|
||
'nb_participants' => $row['nb_participants'],
|
||
'nb_hommes' => $row['nb_hommes'],
|
||
'nb_femmes' => $row['nb_femmes'],
|
||
'cree_par' => $row['cree_par'],
|
||
'modifie_par' => $row['modifie_par'],
|
||
'date_creation' => $row['date_creation'],
|
||
'date_modification' => $row['date_modification'],
|
||
'cloture_rdv' => $row['cloture_rdv'],
|
||
'cloture_par' => $row['cloture_par'],
|
||
'cloture_flag' => $row['cloture_flag'],
|
||
'cloture_commentaire' => $row['cloture_commentaire'],
|
||
'assign' => isset($row['assign']) ? (int)$row['assign'] : 0,
|
||
'beneficiaire' => $beneficiaire,
|
||
'intervenant' => $intervenant,
|
||
'traducteur' => $traducteur,
|
||
'local' => $local,
|
||
];
|
||
return new self($data);
|
||
}
|
||
|
||
/**
|
||
* Retourne un événement enrichi (même structure que la liste).
|
||
*/
|
||
public function get_event_enriched($id) {
|
||
global $wpdb;
|
||
$table_name = $wpdb->prefix . 'crvi_agenda';
|
||
$row = $wpdb->get_row($wpdb->prepare("SELECT * FROM $table_name WHERE id = %d AND is_deleted = 0", (int)$id), ARRAY_A);
|
||
if (!$row) {
|
||
return null;
|
||
}
|
||
return $this->enrich_event_row($row);
|
||
}
|
||
|
||
/**
|
||
* Enrichit un enregistrement brut d'événement avec les objets liés.
|
||
*/
|
||
private function enrich_event_row(array $event) {
|
||
$enriched_event = $event;
|
||
|
||
// Bénéficiaire
|
||
if (!empty($event['id_beneficiaire'])) {
|
||
$beneficiaire = CRVI_Beneficiaire_Model::load($event['id_beneficiaire'], ['id', 'nom', 'prenom', 'email', 'telephone', 'langues_parlees']);
|
||
if ($beneficiaire) {
|
||
$langues_parlees = $beneficiaire->langues_parlees;
|
||
if (!empty($langues_parlees) && !is_array($langues_parlees)) {
|
||
$langues_parlees = explode('|', $langues_parlees);
|
||
}
|
||
|
||
// Récupérer les indisponibilités ponctuelles du bénéficiaire
|
||
$indisponibilites = [];
|
||
if (function_exists('get_field')) {
|
||
$indisponibilites_acf = get_field('indisponibilitee_ponctuelle', $beneficiaire->id);
|
||
if (is_array($indisponibilites_acf)) {
|
||
$indisponibilites = $indisponibilites_acf;
|
||
}
|
||
}
|
||
|
||
$enriched_event['beneficiaire'] = [
|
||
'id' => $beneficiaire->id,
|
||
'nom' => $beneficiaire->nom,
|
||
'prenom' => $beneficiaire->prenom,
|
||
'nom_complet' => $beneficiaire->nom . ' ' . $beneficiaire->prenom,
|
||
'email' => $beneficiaire->email,
|
||
'telephone' => $beneficiaire->telephone,
|
||
'langues_parlees' => $langues_parlees,
|
||
'indisponibilitee_ponctuelle' => $indisponibilites,
|
||
];
|
||
}
|
||
}
|
||
|
||
// Intervenant
|
||
if (!empty($event['id_intervenant'])) {
|
||
$intervenant = CRVI_Intervenant_Model::load($event['id_intervenant'], ['id', 'nom', 'prenom', 'email', 'departements_ids', 'types_intervention_ids']);
|
||
if ($intervenant) {
|
||
$types_intervention_noms = [];
|
||
if (!empty($intervenant->types_intervention_ids)) {
|
||
if (!is_array($intervenant->types_intervention_ids)) {
|
||
$intervenant->types_intervention_ids = explode('|', $intervenant->types_intervention_ids);
|
||
}
|
||
foreach ($intervenant->types_intervention_ids as $type_id) {
|
||
$type_nom = $this->get_type_intervention_nom($type_id);
|
||
if ($type_nom) {
|
||
$types_intervention_noms[] = $type_nom;
|
||
}
|
||
}
|
||
}
|
||
$departements_noms = [];
|
||
if (!empty($intervenant->departements_ids)) {
|
||
if (!is_array($intervenant->departements_ids)) {
|
||
$intervenant->departements_ids = explode('|', $intervenant->departements_ids);
|
||
}
|
||
foreach ($intervenant->departements_ids as $dept_id) {
|
||
$dept_nom = $this->get_departement_nom($dept_id);
|
||
if ($dept_nom) {
|
||
$departements_noms[] = $dept_nom;
|
||
}
|
||
}
|
||
}
|
||
$intervenant_jours = [];
|
||
$intervenant_heures = [];
|
||
if (function_exists('get_field')) {
|
||
$intervenant_jours = get_field('jours_de_disponibilite', 'user_' . $intervenant->id) ?: [];
|
||
$intervenant_heures = get_field('heures_de_permanences', 'user_' . $intervenant->id) ?: [];
|
||
}
|
||
$enriched_event['intervenant'] = [
|
||
'id' => $intervenant->id,
|
||
'nom' => $intervenant->nom,
|
||
'prenom' => $intervenant->prenom,
|
||
'nom_complet' => $intervenant->nom . ' ' . $intervenant->prenom,
|
||
'email' => $intervenant->email,
|
||
'departements_ids' => $intervenant->departements_ids,
|
||
'departements_noms' => $departements_noms,
|
||
'types_intervention_ids' => $intervenant->types_intervention_ids,
|
||
'types_intervention_noms' => $types_intervention_noms,
|
||
'jours_de_disponibilite' => is_array($intervenant_jours) ? array_values($intervenant_jours) : [],
|
||
'heures_de_permanences' => is_array($intervenant_heures) ? array_values($intervenant_heures) : [],
|
||
];
|
||
}
|
||
}
|
||
|
||
// Traducteur
|
||
if (!empty($event['id_traducteur'])) {
|
||
$traducteur = CRVI_Traducteur_Model::load($event['id_traducteur'], ['id', 'nom', 'prenom', 'email', 'langues_parlees', 'organisme']);
|
||
if ($traducteur) {
|
||
$langues_parlees = $traducteur->langues_parlees;
|
||
if (!empty($langues_parlees) && !is_array($langues_parlees)) {
|
||
$langues_parlees = explode('|', $langues_parlees);
|
||
}
|
||
$enriched_event['traducteur'] = [
|
||
'id' => $traducteur->id,
|
||
'nom' => $traducteur->nom,
|
||
'prenom' => $traducteur->prenom,
|
||
'nom_complet' => $traducteur->nom . ' ' . $traducteur->prenom,
|
||
'email' => $traducteur->email,
|
||
'langues_parlees' => $langues_parlees,
|
||
'organisme' => $traducteur->organisme,
|
||
];
|
||
}
|
||
}
|
||
|
||
// Local
|
||
if (!empty($event['id_local'])) {
|
||
$local = CRVI_Local_Model::load($event['id_local'], ['id', 'nom', 'type_de_local', 'capacite']);
|
||
if ($local) {
|
||
$enriched_event['local'] = [
|
||
'id' => $local->id,
|
||
'nom' => $local->nom,
|
||
'type_de_local' => $local->type_de_local,
|
||
'capacite' => $local->capacite,
|
||
];
|
||
}
|
||
}
|
||
|
||
return $enriched_event;
|
||
}
|
||
|
||
/**
|
||
* Sauvegarder l'événement
|
||
*/
|
||
public function save() {
|
||
// À implémenter : sauvegarder dans la base/CPT/meta
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* Vérifier si l'entité est disponible à la date/heure de l'événement
|
||
*/
|
||
public function is_disponible($date, $heure) {
|
||
global $wpdb;
|
||
$table_name = $wpdb->prefix . 'crvi_agenda';
|
||
$sql = $wpdb->prepare("SELECT * FROM $table_name WHERE date_rdv = %s AND heure_rdv = %s AND statut = 'prevu' AND is_deleted = 0", $date, $heure);
|
||
$result = $wpdb->get_results($sql, ARRAY_A);
|
||
if(!empty($result)) {
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* Retourner les conflits éventuels pour cet événement
|
||
*/
|
||
public function get_conflits($date, $heure, $id_intervenant, $id_traducteur, $id_local) {
|
||
// À implémenter : logique de détection de conflits
|
||
global $wpdb;
|
||
$table_name = $wpdb->prefix . 'crvi_agenda';
|
||
$sql = $wpdb->prepare("SELECT * FROM $table_name WHERE date_rdv = %s AND heure_rdv = %s AND id_intervenant = %d AND id_traducteur = %d AND id_local = %d AND is_deleted = 0", $date, $heure, $id_intervenant, $id_traducteur, $id_local);
|
||
$result = $wpdb->get_results($sql, ARRAY_A);
|
||
if(!empty($result)) {
|
||
return false;
|
||
}
|
||
return [];
|
||
}
|
||
|
||
/**
|
||
* Retourner l'historique des modifications
|
||
*/
|
||
public function get_historique() {
|
||
// À implémenter : récupérer l'historique
|
||
return [];
|
||
}
|
||
|
||
/**
|
||
* Correspondance des filtres API (nom métier) avec les colonnes SQL :
|
||
*
|
||
* | Paramètre API | Colonne SQL |
|
||
* |-------------------|---------------------|
|
||
* | date | date_rdv |
|
||
* | type | type |
|
||
* | statut | statut |
|
||
* | departement | departement |
|
||
* | type_intervention | type_intervention |
|
||
* | langue | langue |
|
||
* | beneficiaire | id_beneficiaire |
|
||
* | intervenant | id_intervenant |
|
||
* | traducteur | id_traducteur |
|
||
* | local | id_local |
|
||
* | cree_par | cree_par |
|
||
*
|
||
* Les paramètres API doivent être mappés vers les colonnes SQL correspondantes pour l'appel à cette méthode.
|
||
*
|
||
* @param string $type Colonne principale (ex: 'id_intervenant')
|
||
* @param mixed $value Valeur principale
|
||
* @param array $attributes Tableau associatif clé => valeur pour filtres supplémentaires
|
||
* @param string $date_debut Date de début (format 'Y-m-d')
|
||
* @param string $date_fin Date de fin (format 'Y-m-d')
|
||
* @return array Résultats bruts (ARRAY_A)
|
||
*/
|
||
public function get_events_by($type, $value, $attributes = [], $date_debut = null, $date_fin = null) {
|
||
global $wpdb;
|
||
$table_name = $wpdb->prefix . 'crvi_agenda';
|
||
|
||
// Liste blanche des colonnes autorisées pour la sécurité
|
||
$allowed_columns = [
|
||
'date_rdv', 'heure_rdv', 'type', 'statut', 'id_departement', 'id_type_intervention',
|
||
'langue', 'id_beneficiaire', 'id_intervenant', 'id_traducteur', 'id_local','cree_par'
|
||
];
|
||
|
||
$where = [];
|
||
$values = [];
|
||
|
||
// Filtre principal
|
||
if (in_array($type, $allowed_columns, true)) {
|
||
$where[] = "$type = %s";
|
||
$values[] = $value;
|
||
} else {
|
||
return [];
|
||
}
|
||
|
||
// Filtres supplémentaires
|
||
foreach ($attributes as $key => $val) {
|
||
if (in_array($key, $allowed_columns, true)) {
|
||
$where[] = "$key = %s";
|
||
$values[] = $val;
|
||
}
|
||
}
|
||
|
||
// Plage de dates
|
||
if ($date_debut && $date_fin) {
|
||
$where[] = "date_rdv BETWEEN %s AND %s";
|
||
$values[] = $date_debut;
|
||
$values[] = $date_fin;
|
||
} elseif ($date_debut) {
|
||
$where[] = "date_rdv >= %s";
|
||
$values[] = $date_debut;
|
||
} elseif ($date_fin) {
|
||
$where[] = "date_rdv <= %s";
|
||
$values[] = $date_fin;
|
||
}
|
||
|
||
// Filtre soft delete - exclure les événements supprimés
|
||
$where[] = "is_deleted = 0";
|
||
|
||
$where_sql = $where ? ('WHERE ' . implode(' AND ', $where)) : '';
|
||
$sql = "SELECT * FROM $table_name $where_sql";
|
||
$prepared_sql = $wpdb->prepare($sql, $values);
|
||
$results = $wpdb->get_results($prepared_sql, ARRAY_A);
|
||
return $results;
|
||
}
|
||
|
||
/**
|
||
* Retourne un objet avec les détails principaux des entités liées à l'événement.
|
||
* @return object
|
||
*/
|
||
public function get_details($id_event) {
|
||
$details = new \stdClass();
|
||
|
||
$event = self::load($id_event);
|
||
|
||
if (!$event) {
|
||
return $details;
|
||
}
|
||
|
||
// Bénéficiaire
|
||
if (!empty($event->id_beneficiaire)) {
|
||
$benef = CRVI_Beneficiaire_Model::load($event->id_beneficiaire);
|
||
if ($benef) {
|
||
$details->beneficiaire = (object) [
|
||
'id' => $benef->id,
|
||
'nom' => $benef->nom ?? '',
|
||
'prenom' => $benef->prenom ?? '',
|
||
'email' => $benef->email ?? '',
|
||
];
|
||
}
|
||
}
|
||
|
||
// Intervenant
|
||
if (!empty($event->id_intervenant)) {
|
||
$interv = CRVI_Intervenant_Model::load($event->id_intervenant);
|
||
if ($interv) {
|
||
$details->intervenant = (object) [
|
||
'id' => $interv->id,
|
||
'nom' => $interv->nom ?? '',
|
||
'prenom' => $interv->prenom ?? '',
|
||
'email' => $interv->email ?? '',
|
||
];
|
||
}
|
||
}
|
||
|
||
// Traducteur
|
||
if (!empty($event->id_traducteur)) {
|
||
$trad = CRVI_Traducteur_Model::load($event->id_traducteur);
|
||
if ($trad) {
|
||
$details->traducteur = (object) [
|
||
'id' => $trad->id,
|
||
'nom' => $trad->nom ?? '',
|
||
'prenom' => $trad->prenom ?? '',
|
||
'email' => $trad->email ?? '',
|
||
];
|
||
}
|
||
}
|
||
|
||
// Local
|
||
if (!empty($event->id_local)) {
|
||
$local = CRVI_Local_Model::load($event->id_local);
|
||
if ($local) {
|
||
$details->local = (object) [
|
||
'id' => $local->id,
|
||
'nom' => $local->nom ?? '',
|
||
'type_de_local' => $local->type_de_local ?? '',
|
||
];
|
||
}
|
||
}
|
||
|
||
return $details;
|
||
}
|
||
|
||
/**
|
||
* Crée la table personnalisée wp_crvi_agenda pour stocker les rendez-vous.
|
||
* À appeler à l'activation du plugin.
|
||
* statut : prevu, annulé, non tenu, etc.
|
||
*/
|
||
public static function create_table() {
|
||
global $wpdb;
|
||
$table_name = $wpdb->prefix . 'crvi_agenda';
|
||
// Vérification si la table existe déjà
|
||
if ($wpdb->get_var("SHOW TABLES LIKE '$table_name'") === $table_name) {
|
||
// Vérifier si le champ is_deleted existe, sinon l'ajouter
|
||
$column_exists = $wpdb->get_results("SHOW COLUMNS FROM $table_name LIKE 'is_deleted'");
|
||
if (empty($column_exists)) {
|
||
$wpdb->query("ALTER TABLE $table_name ADD COLUMN is_deleted tinyint(1) DEFAULT 0");
|
||
}
|
||
|
||
// Vérifier si la colonne assign existe, sinon l'ajouter
|
||
$column_assign = $wpdb->get_results($wpdb->prepare(
|
||
"SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS
|
||
WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s AND COLUMN_NAME = 'assign'",
|
||
DB_NAME, $table_name
|
||
));
|
||
if (empty($column_assign)) {
|
||
$wpdb->query("ALTER TABLE $table_name ADD COLUMN assign tinyint(1) DEFAULT 0");
|
||
}
|
||
|
||
// Harmoniser le schéma: renommer les colonnes si nécessaire
|
||
// departement -> id_departement
|
||
$has_id_dept = $wpdb->get_var($wpdb->prepare(
|
||
"SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS
|
||
WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s AND COLUMN_NAME = 'id_departement'",
|
||
DB_NAME, $table_name
|
||
));
|
||
$has_dept = $wpdb->get_var($wpdb->prepare(
|
||
"SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS
|
||
WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s AND COLUMN_NAME = 'departement'",
|
||
DB_NAME, $table_name
|
||
));
|
||
if (!$has_id_dept && $has_dept) {
|
||
$wpdb->query("ALTER TABLE $table_name CHANGE COLUMN departement id_departement int(10) unsigned DEFAULT NULL");
|
||
}
|
||
|
||
// type_intervention -> id_type_intervention
|
||
$has_id_type = $wpdb->get_var($wpdb->prepare(
|
||
"SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS
|
||
WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s AND COLUMN_NAME = 'id_type_intervention'",
|
||
DB_NAME, $table_name
|
||
));
|
||
$has_type = $wpdb->get_var($wpdb->prepare(
|
||
"SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS
|
||
WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s AND COLUMN_NAME = 'type_intervention'",
|
||
DB_NAME, $table_name
|
||
));
|
||
if (!$has_id_type && $has_type) {
|
||
$wpdb->query("ALTER TABLE $table_name CHANGE COLUMN type_intervention id_type_intervention int(10) unsigned DEFAULT NULL");
|
||
}
|
||
|
||
// Vérifier si la colonne langues_disponibles existe, sinon l'ajouter
|
||
$column_langues_disponibles = $wpdb->get_results($wpdb->prepare(
|
||
"SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS
|
||
WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s AND COLUMN_NAME = 'langues_disponibles'",
|
||
DB_NAME, $table_name
|
||
));
|
||
if (empty($column_langues_disponibles)) {
|
||
$wpdb->query("ALTER TABLE $table_name ADD COLUMN langues_disponibles varchar(255) DEFAULT NULL");
|
||
}
|
||
|
||
return; // La table existe déjà, on ne fait rien
|
||
}
|
||
$charset_collate = $wpdb->get_charset_collate();
|
||
$sql = "CREATE TABLE $table_name (
|
||
id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||
date_rdv date NOT NULL,
|
||
heure_rdv time NOT NULL,
|
||
date_fin date DEFAULT NULL,
|
||
heure_fin time DEFAULT NULL,
|
||
type varchar(20) NOT NULL,
|
||
statut varchar(20) DEFAULT 'prevu',
|
||
motif_annulation varchar(255) DEFAULT NULL,
|
||
commentaire text DEFAULT NULL,
|
||
id_departement int(10) unsigned DEFAULT NULL,
|
||
id_type_intervention int(10) unsigned DEFAULT NULL,
|
||
langue varchar(20) NOT NULL,
|
||
id_beneficiaire bigint(20) unsigned DEFAULT NULL,
|
||
id_intervenant bigint(20) unsigned DEFAULT NULL,
|
||
id_traducteur bigint(20) unsigned DEFAULT NULL,
|
||
id_local bigint(20) unsigned DEFAULT NULL,
|
||
nb_participants int DEFAULT NULL,
|
||
nb_hommes int DEFAULT NULL,
|
||
nb_femmes int DEFAULT NULL,
|
||
cree_par bigint(20) unsigned DEFAULT NULL,
|
||
modifie_par bigint(20) unsigned DEFAULT NULL,
|
||
date_creation datetime DEFAULT CURRENT_TIMESTAMP,
|
||
date_modification datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||
cloture_rdv datetime DEFAULT NULL,
|
||
cloture_par bigint(20) unsigned DEFAULT NULL,
|
||
cloture_commentaire text DEFAULT NULL,
|
||
cloture_flag tinyint(1) DEFAULT 0,
|
||
is_deleted tinyint(1) DEFAULT 0,
|
||
assign tinyint(1) DEFAULT 0,
|
||
langues_disponibles varchar(255) DEFAULT NULL,
|
||
PRIMARY KEY (id)
|
||
) $charset_collate;";
|
||
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
|
||
dbDelta($sql);
|
||
}
|
||
|
||
/**
|
||
* Calcule automatiquement le flag assign basé sur id_beneficiaire et id_local
|
||
* assign = 1 si les deux sont présents et non null, sinon 0
|
||
* @param array $data Données de l'événement
|
||
* @return int 0 ou 1
|
||
*/
|
||
public static function calculate_assign_flag($data) {
|
||
$id_beneficiaire = isset($data['id_beneficiaire']) ? $data['id_beneficiaire'] : null;
|
||
$id_local = isset($data['id_local']) ? $data['id_local'] : null;
|
||
|
||
// assign = 1 si bénéficiaire ET local sont présents et non null
|
||
if (!empty($id_beneficiaire) && !empty($id_local)) {
|
||
return 1;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
/**
|
||
* get departement nom
|
||
*/
|
||
public function get_departement_nom($id_departement) {
|
||
|
||
$departement = get_term_by('id', $id_departement, 'departement');
|
||
return $departement->name;
|
||
}
|
||
|
||
/**
|
||
* get type_intervention nom
|
||
*/
|
||
public function get_type_intervention_nom($id_type_intervention) {
|
||
$type_intervention = get_term_by('id', $id_type_intervention, 'type_intervention');
|
||
return $type_intervention->name;
|
||
}
|
||
|
||
/**
|
||
* Créer un événement.
|
||
* @param array $data
|
||
* @return int|WP_Error
|
||
*/
|
||
public function create_event(array $data) {
|
||
global $wpdb;
|
||
|
||
// 1. Vérifier les droits
|
||
if (!current_user_can('edit_posts')) {
|
||
return new \WP_Error('unauthorized', 'Non autorisé', ['status' => 403]);
|
||
}
|
||
|
||
// 2. Valider les données (assouplissement: bénéficiaire requis seulement hors groupe; traducteur optionnel)
|
||
$requiredFields = ['date_rdv', 'heure_rdv', 'type', 'langue', 'id_intervenant', 'id_local'];
|
||
foreach ($requiredFields as $field) {
|
||
if (!isset($data[$field]) || $data[$field] === '' || $data[$field] === null) {
|
||
return new \WP_Error('validation_error', 'Données invalides', ['status' => 400]);
|
||
}
|
||
}
|
||
if (($data['type'] ?? '') !== 'groupe') {
|
||
if (!isset($data['id_beneficiaire']) || $data['id_beneficiaire'] === '' || $data['id_beneficiaire'] === null) {
|
||
return new \WP_Error('validation_error', 'Données invalides', ['status' => 400]);
|
||
}
|
||
}
|
||
|
||
// 3. Vérifier les conflits (créneau, disponibilité)
|
||
// OPTIMISATION : Utiliser les permissions stockées si disponibles (pour les événements existants modifiés)
|
||
// Pour les nouvelles créations, on vérifie via l'intervenant puis on crée les permissions
|
||
$intervenant = CRVI_Intervenant_Model::load($data['id_intervenant']);
|
||
if (!$intervenant) {
|
||
return new \WP_Error('validation_error', 'Intervenant non trouvé', ['status' => 400]);
|
||
}
|
||
|
||
$nom_intervenant = $intervenant->nom . ' ' . $intervenant->prenom;
|
||
if (!$intervenant->is_disponible($data['id_intervenant'], $data['date_rdv'])) {
|
||
return new \WP_Error('conflict_error', 'Conflit de disponibilité, l\'intervenant ' . $nom_intervenant . ' est déjà pris pour le créneau ' . $data['date_rdv'] . ' à ' . $data['heure_rdv'], ['status' => 400]);
|
||
}
|
||
|
||
if (isset($data['id_traducteur']) && $data['id_traducteur'] !== '' && $data['id_traducteur'] !== null) {
|
||
$traducteur = CRVI_Traducteur_Model::load($data['id_traducteur']);
|
||
if (!$traducteur) {
|
||
return new \WP_Error('validation_error', 'Traducteur non trouvé', ['status' => 400]);
|
||
}
|
||
|
||
$nom_traducteur = $traducteur->nom . ' ' . $traducteur->prenom;
|
||
if (!$traducteur->is_disponible($data['id_traducteur'], $data['date_rdv'])) {
|
||
return new \WP_Error('conflict_error', 'Conflit de disponibilité, le traducteur ' . $nom_traducteur . ' est déjà pris pour le créneau ' . $data['date_rdv'] . ' à ' . $data['heure_rdv'], ['status' => 400]);
|
||
}
|
||
}
|
||
|
||
$local = CRVI_Local_Model::load($data['id_local']);
|
||
if (!$local) {
|
||
return new \WP_Error('validation_error', 'Local non trouvé', ['status' => 400]);
|
||
}
|
||
|
||
$nom_local = $local->nom;
|
||
if (!$local->is_disponible($data['date_rdv'])) {
|
||
return new \WP_Error('conflict_error', 'Conflit de disponibilité, le local ' . $nom_local . ' est déjà pris pour le créneau ' . $data['date_rdv'] . ' à ' . $data['heure_rdv'], ['status' => 400]);
|
||
}
|
||
|
||
// 4. Calculer automatiquement le flag assign
|
||
$data['assign'] = self::calculate_assign_flag($data);
|
||
|
||
// 5. Insérer en base
|
||
$result = $wpdb->insert($wpdb->prefix . 'crvi_agenda', $data);
|
||
if ($result === false) {
|
||
return new \WP_Error('database_error', 'Erreur lors de l\'insertion en base de données', ['status' => 500]);
|
||
}
|
||
|
||
// 6. Retourner l'ID ou une erreur
|
||
return $wpdb->insert_id;
|
||
}
|
||
|
||
/**
|
||
* Crée une permanence (événement sans bénéficiaire ni local)
|
||
* Les permanences sont des créneaux horaires disponibles pour les intervenants
|
||
* @param array $data Données de la permanence
|
||
* @return int|WP_Error ID de l'événement créé ou WP_Error
|
||
*/
|
||
public function create_permanence(array $data) {
|
||
global $wpdb;
|
||
|
||
// Validation des champs obligatoires pour une permanence
|
||
$requiredFields = ['date_rdv', 'heure_rdv', 'id_intervenant'];
|
||
foreach ($requiredFields as $field) {
|
||
if (!isset($data[$field]) || $data[$field] === '' || $data[$field] === null) {
|
||
return new \WP_Error('validation_error', "Le champ '{$field}' est requis pour créer une permanence", ['status' => 400]);
|
||
}
|
||
}
|
||
|
||
// Vérifier les indisponibilités ponctuelles de l'intervenant pour cette date/heure
|
||
$intervenant_id = (int) $data['id_intervenant'];
|
||
$date_rdv = $data['date_rdv'];
|
||
$heure_rdv = $data['heure_rdv'];
|
||
|
||
if (function_exists('get_field') && $intervenant_id > 0) {
|
||
$indisponibilites = get_field('indisponibilitee_ponctuelle', 'user_' . $intervenant_id);
|
||
if (is_array($indisponibilites)) {
|
||
foreach ($indisponibilites as $indispo) {
|
||
// Support des deux formats potentiels:
|
||
// - date (Y-m-d) + optionnel heure_debut/heure_fin (H:i)
|
||
// - debut/fin (date-time) utilisés ailleurs
|
||
$indispo_date = isset($indispo['date']) ? $indispo['date'] : null;
|
||
$indispo_heure_debut = isset($indispo['heure_debut']) ? $indispo['heure_debut'] : null;
|
||
$indispo_heure_fin = isset($indispo['heure_fin']) ? $indispo['heure_fin'] : null;
|
||
|
||
// Fallback si le repeater utilise 'debut' / 'fin' (timestamp/date)
|
||
if (!$indispo_date && (isset($indispo['debut']) || isset($indispo['fin']))) {
|
||
$debut_ts = !empty($indispo['debut']) ? strtotime((string) $indispo['debut']) : null;
|
||
$fin_ts = !empty($indispo['fin']) ? strtotime((string) $indispo['fin']) : null;
|
||
$slot_ts = strtotime($date_rdv . ' ' . $heure_rdv);
|
||
if ($debut_ts && $fin_ts && $slot_ts && $slot_ts >= $debut_ts && $slot_ts <= $fin_ts) {
|
||
return new \WP_Error('unavailable', 'Créneau indisponible (indisponibilité ponctuelle intervenant)', ['status' => 409]);
|
||
}
|
||
}
|
||
|
||
// Cas avec champ 'date' (et éventuelles heures)
|
||
if ($indispo_date === $date_rdv) {
|
||
// Si aucune heure précisée, la journée entière est indisponible
|
||
if (empty($indispo_heure_debut) && empty($indispo_heure_fin)) {
|
||
return new \WP_Error('unavailable', 'Créneau indisponible (indisponibilité ponctuelle intervenant)', ['status' => 409]);
|
||
}
|
||
// Si plage horaire précisée, vérifier l'appartenance du créneau
|
||
if (!empty($indispo_heure_debut) && !empty($indispo_heure_fin)) {
|
||
// Comparaison sur format H:i (zéro‑rempli) => comparaison lexicographique OK
|
||
if ($heure_rdv >= $indispo_heure_debut && $heure_rdv < $indispo_heure_fin) {
|
||
return new \WP_Error('unavailable', 'Créneau indisponible (indisponibilité ponctuelle intervenant)', ['status' => 409]);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Empêcher la création si un événement/permanence existe déjà sur le même slot pour cet intervenant
|
||
$existing_id = $wpdb->get_var($wpdb->prepare(
|
||
"SELECT id FROM {$wpdb->prefix}crvi_agenda
|
||
WHERE is_deleted = 0
|
||
AND id_intervenant = %d
|
||
AND date_rdv = %s
|
||
AND heure_rdv = %s
|
||
LIMIT 1",
|
||
$intervenant_id, $date_rdv, $heure_rdv
|
||
));
|
||
if (!empty($existing_id)) {
|
||
return new \WP_Error('conflict_error', 'Un événement existe déjà pour ce créneau', ['status' => 409]);
|
||
}
|
||
|
||
// Traiter les langues disponibles (tableau de slugs séparés par virgules)
|
||
$langues_disponibles_value = '';
|
||
if (isset($data['langues']) && is_array($data['langues']) && !empty($data['langues'])) {
|
||
// Nettoyer et valider les slugs de langues
|
||
$langues_valides = [];
|
||
foreach ($data['langues'] as $langue_slug) {
|
||
$langue_slug = sanitize_text_field($langue_slug);
|
||
if (!empty($langue_slug)) {
|
||
$langues_valides[] = $langue_slug;
|
||
}
|
||
}
|
||
// Joindre les langues avec le séparateur virgule (,)
|
||
if (!empty($langues_valides)) {
|
||
$langues_disponibles_value = implode(',', $langues_valides);
|
||
}
|
||
}
|
||
|
||
// Préparer les données pour l'insertion
|
||
$event_data = [
|
||
'date_rdv' => $data['date_rdv'],
|
||
'heure_rdv' => $data['heure_rdv'],
|
||
'date_fin' => $data['date_fin'] ?? $data['date_rdv'],
|
||
'heure_fin' => $data['heure_fin'] ?? $data['heure_rdv'],
|
||
'type' => 'permanence',
|
||
'statut' => 'prevu',
|
||
'id_intervenant' => $data['id_intervenant'],
|
||
'id_beneficiaire' => null,
|
||
'id_local' => null,
|
||
'id_traducteur' => null,
|
||
'langue' => '', // Garder vide pour les permanences
|
||
'langues_disponibles' => $langues_disponibles_value, // Langues séparées par virgules (peut être vide)
|
||
'assign' => 0, // Non assigné par défaut (pas de bénéficiaire ni local)
|
||
'is_deleted' => 0,
|
||
'commentaire' => $data['commentaire'] ?? '',
|
||
'cree_par' => get_current_user_id(),
|
||
'date_creation' => current_time('mysql'),
|
||
];
|
||
|
||
// Insérer en base
|
||
$result = $wpdb->insert($wpdb->prefix . 'crvi_agenda', $event_data);
|
||
|
||
if ($result === false) {
|
||
return new \WP_Error('database_error', 'Erreur lors de l\'insertion de la permanence en base de données', ['status' => 500]);
|
||
}
|
||
|
||
return $wpdb->insert_id;
|
||
}
|
||
|
||
/**
|
||
* Mettre à jour un événement.
|
||
* @param int $id
|
||
* @param array $data
|
||
* @return bool|WP_Error
|
||
*/
|
||
public function update_event(int $id, array $data) {
|
||
global $wpdb;
|
||
|
||
// 1. Vérifier les droits
|
||
if (!current_user_can('edit_posts')) {
|
||
return new \WP_Error('unauthorized', 'Non autorisé', ['status' => 403]);
|
||
}
|
||
|
||
// 2. Charger l'événement existant
|
||
$existing_event = self::load($id);
|
||
if (!$existing_event) {
|
||
return new \WP_Error('not_found', 'Événement non trouvé', ['status' => 404]);
|
||
}
|
||
|
||
// 2.5. Les permanences peuvent être déplacées par un non-propriétaire,
|
||
// le blocage se fera uniquement en cas d'indisponibilité détectée plus bas.
|
||
// (Ancien blocage "permanence_immutable" retiré)
|
||
|
||
// 3. S'assurer que $data est un tableau
|
||
if (!is_array($data)) {
|
||
$data = [];
|
||
}
|
||
|
||
// 4. Fusionner les données existantes avec les nouvelles données
|
||
$update_data = array_merge([
|
||
'date_rdv' => $existing_event->date ?? null,
|
||
'heure_rdv' => $existing_event->heure ?? null,
|
||
'date_fin' => $existing_event->date_fin ?? null,
|
||
'heure_fin' => $existing_event->heure_fin ?? null,
|
||
'type' => $existing_event->type ?? null,
|
||
'langue' => $existing_event->langue ?? null,
|
||
'id_beneficiaire' => $existing_event->id_beneficiaire ?? null,
|
||
'id_intervenant' => $existing_event->id_intervenant ?? null,
|
||
'id_traducteur' => $existing_event->id_traducteur ?? null,
|
||
'id_local' => $existing_event->id_local ?? null,
|
||
'langues_disponibles' => $existing_event->langues_disponibles ?? null,
|
||
], $data);
|
||
|
||
// 5. Valider les données fusionnées (aligné avec create_event)
|
||
// Les permanences ont des règles de validation différentes (pas de langue/local requis)
|
||
if (($update_data['type'] ?? '') === 'permanence') {
|
||
// Pour les permanences, seuls date_rdv, heure_rdv, type et id_intervenant sont requis
|
||
$requiredFields = ['date_rdv', 'heure_rdv', 'type', 'id_intervenant'];
|
||
foreach ($requiredFields as $field) {
|
||
if (!isset($update_data[$field]) || $update_data[$field] === '' || $update_data[$field] === null) {
|
||
$current_value = $update_data[$field] ?? 'non défini';
|
||
return new \WP_Error('validation_error', "Le champ '{$field}' est requis (valeur actuelle: " . var_export($current_value, true) . ")", ['status' => 400]);
|
||
}
|
||
}
|
||
} else {
|
||
// Pour les autres types d'événements (individuel, groupe)
|
||
$requiredFields = ['date_rdv', 'heure_rdv', 'type', 'langue', 'id_intervenant', 'id_local'];
|
||
foreach ($requiredFields as $field) {
|
||
if (!isset($update_data[$field]) || $update_data[$field] === '' || $update_data[$field] === null) {
|
||
$current_value = $update_data[$field] ?? 'non défini';
|
||
return new \WP_Error('validation_error', "Le champ '{$field}' est requis (valeur actuelle: " . var_export($current_value, true) . ")", ['status' => 400]);
|
||
}
|
||
}
|
||
if (($update_data['type'] ?? '') !== 'groupe') {
|
||
if (!isset($update_data['id_beneficiaire']) || $update_data['id_beneficiaire'] === '' || $update_data['id_beneficiaire'] === null) {
|
||
return new \WP_Error('validation_error', "Le champ 'id_beneficiaire' est requis pour les événements individuels", ['status' => 400]);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 6. Vérifier les disponibilités via les modèles
|
||
// Intervenant (toujours présent)
|
||
if (!empty($update_data['id_intervenant'])) {
|
||
$intervenant = CRVI_Intervenant_Model::load($update_data['id_intervenant']);
|
||
if ($intervenant && !$intervenant->is_disponible($update_data['id_intervenant'], $update_data['date_rdv'])) {
|
||
// Message spécifique pour les permanences déplacées par un non-propriétaire
|
||
if (($update_data['type'] ?? '') === 'permanence') {
|
||
$current_user_id = get_current_user_id();
|
||
$is_owner = ($current_user_id > 0 && (int)$existing_event->id_intervenant === (int)$current_user_id);
|
||
if (!$is_owner) {
|
||
return new \WP_Error(
|
||
'conflict_error',
|
||
'Cet intervenant n\'a pas de permanence ce jour-là.',
|
||
['status' => 400]
|
||
);
|
||
}
|
||
}
|
||
return new \WP_Error(
|
||
'conflict_error',
|
||
'L\'intervenant n\'est pas disponible à cette date',
|
||
['status' => 400]
|
||
);
|
||
}
|
||
}
|
||
|
||
// Traducteur (optionnel - vérification seulement si présent)
|
||
if (!empty($update_data['id_traducteur'])) {
|
||
$traducteur = CRVI_Traducteur_Model::load($update_data['id_traducteur']);
|
||
if ($traducteur && !$traducteur->is_disponible($update_data['id_traducteur'], $update_data['date_rdv'])) {
|
||
return new \WP_Error(
|
||
'conflict_error',
|
||
'Le traducteur n\'est pas disponible à cette date',
|
||
['status' => 400]
|
||
);
|
||
}
|
||
}
|
||
|
||
// Local (toujours présent sauf permanences - vérification simple)
|
||
if (!empty($update_data['id_local'])) {
|
||
$local = CRVI_Local_Model::load($update_data['id_local']);
|
||
if ($local && !$local->is_disponible($update_data['date_rdv'])) {
|
||
return new \WP_Error(
|
||
'conflict_error',
|
||
'Le local n\'est pas disponible à cette date',
|
||
['status' => 400]
|
||
);
|
||
}
|
||
}
|
||
|
||
// 6.5. Vérifier les conflits de disponibilité (exclure l'événement actuel)
|
||
// Pour les permanences, on ne vérifie pas les conflits de local (peut être null)
|
||
$id_local_to_check = ($update_data['type'] === 'permanence') ? null : ($update_data['id_local'] ?? null);
|
||
$conflits = $this->get_conflits(
|
||
$update_data['date_rdv'],
|
||
$update_data['heure_rdv'],
|
||
$update_data['id_intervenant'],
|
||
$update_data['id_traducteur'] ?? null,
|
||
$id_local_to_check
|
||
);
|
||
|
||
// Filtrer les conflits pour exclure l'événement actuel
|
||
$conflits_filtered = [];
|
||
foreach ($conflits as $type => $conflit) {
|
||
if ($conflit['id'] != $id) {
|
||
$conflits_filtered[$type] = $conflit;
|
||
}
|
||
}
|
||
|
||
if (!empty($conflits_filtered)) {
|
||
return new \WP_Error('conflict_error', 'Conflit de disponibilité détecté', ['status' => 409, 'conflicts' => $conflits_filtered]);
|
||
}
|
||
|
||
// 7. Calculer automatiquement le flag assign
|
||
$update_data['assign'] = self::calculate_assign_flag($update_data);
|
||
|
||
// 7.5. Traiter langues_disponibles UNIQUEMENT pour les permanences
|
||
// Ne pas traiter pour les autres types d'événements pour ne pas casser la mise à jour classique
|
||
if (($update_data['type'] ?? '') === 'permanence') {
|
||
$langues_disponibles_value = '';
|
||
|
||
// Si c'est un tableau (langues), le convertir en chaîne séparée par virgules
|
||
if (isset($data['langues']) && is_array($data['langues']) && !empty($data['langues'])) {
|
||
// Nettoyer et valider les slugs de langues
|
||
$langues_valides = [];
|
||
foreach ($data['langues'] as $langue_slug) {
|
||
$langue_slug = sanitize_text_field($langue_slug);
|
||
if (!empty($langue_slug)) {
|
||
$langues_valides[] = $langue_slug;
|
||
}
|
||
}
|
||
// Joindre les langues avec le séparateur virgule (,)
|
||
if (!empty($langues_valides)) {
|
||
$langues_disponibles_value = implode(',', $langues_valides);
|
||
}
|
||
}
|
||
// Si c'est déjà une chaîne (langues_disponibles) - pour mise à jour directe
|
||
elseif (isset($data['langues_disponibles'])) {
|
||
$langues_disponibles_value = sanitize_text_field($data['langues_disponibles']);
|
||
}
|
||
// Si aucune langue n'est envoyée, conserver la valeur existante (déjà dans $update_data via array_merge)
|
||
|
||
// Mettre à jour seulement si une valeur a été fournie
|
||
if (isset($data['langues']) || isset($data['langues_disponibles'])) {
|
||
$update_data['langues_disponibles'] = $langues_disponibles_value;
|
||
}
|
||
}
|
||
|
||
// 8. Ajouter les métadonnées de modification
|
||
$update_data['modifie_par'] = get_current_user_id();
|
||
$update_data['date_modification'] = current_time('mysql');
|
||
|
||
// 9. Filtrer les champs pour ne garder que ceux qui existent dans la table
|
||
// Liste des colonnes valides de la table crvi_agenda
|
||
$valid_columns = [
|
||
'date_rdv', 'heure_rdv', 'date_fin', 'heure_fin', 'type', 'statut',
|
||
'motif_annulation', 'commentaire', 'id_departement', 'id_type_intervention',
|
||
'langue', 'id_beneficiaire', 'id_intervenant', 'id_traducteur', 'id_local',
|
||
'langues_disponibles', 'assign', 'modifie_par', 'date_modification'
|
||
];
|
||
|
||
$filtered_data = [];
|
||
foreach ($valid_columns as $column) {
|
||
// Inclure le champ même s'il est null (pour permettre de mettre à null des valeurs existantes)
|
||
// Mais exclure les chaînes vides qui doivent être converties en null
|
||
if (array_key_exists($column, $update_data)) {
|
||
$value = $update_data[$column];
|
||
// Convertir les chaînes vides, '0', ou 0 en null pour les champs optionnels
|
||
if (in_array($column, ['id_traducteur', 'id_beneficiaire']) && ($value === '' || $value === '0' || $value === 0)) {
|
||
$filtered_data[$column] = null;
|
||
} else {
|
||
$filtered_data[$column] = $value;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 10. Mettre à jour en base
|
||
$result = $wpdb->update($wpdb->prefix . 'crvi_agenda', $filtered_data, ['id' => $id]);
|
||
|
||
if ($result === false && !empty($wpdb->last_error)) {
|
||
return new \WP_Error('db_error', 'Erreur lors de la mise à jour: ' . $wpdb->last_error, ['status' => 500]);
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* Supprimer un événement (suppression logique)
|
||
* @param int $id
|
||
* @return bool|WP_Error
|
||
*/
|
||
public function delete_event(int $id) {
|
||
global $wpdb;
|
||
// 1. Vérifier les droits
|
||
if (!current_user_can('delete_posts')) {
|
||
return Api_Helper::json_error('Non autorisé', 403);
|
||
}
|
||
// 2. Marquer comme supprimé (champ is_deleted)
|
||
$wpdb->update($wpdb->prefix . 'crvi_agenda', ['is_deleted' => 1], ['id' => $id]);
|
||
// 3. Retourner true ou une erreur
|
||
if ($wpdb->last_error) {
|
||
return Api_Helper::json_error('Erreur lors de la suppression', 500);
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* Clôturer un événement
|
||
* @param int $id
|
||
* @param string $statut
|
||
* @return bool|WP_Error
|
||
*/
|
||
public function cloture_event(int $id, string $statut) {
|
||
global $wpdb;
|
||
// 1. Vérifier les droits
|
||
if (!current_user_can('edit_posts')) {
|
||
return Api_Helper::json_error('Non autorisé', 403);
|
||
}
|
||
// 2. Mettre à jour le statut (clôturé, absent, etc.)
|
||
$wpdb->update($wpdb->prefix . 'crvi_agenda', ['statut' => $statut], ['id' => $id]);
|
||
// 3. Retourner true ou une erreur
|
||
if ($wpdb->last_error) {
|
||
return Api_Helper::json_error('Erreur lors de la clôture', 500);
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* Statistiques sur les événements
|
||
* @param array $params
|
||
* @return array
|
||
*/
|
||
public function get_events_stats(array $params = []) {
|
||
global $wpdb;
|
||
$table_name = $wpdb->prefix . 'crvi_agenda';
|
||
$where = [];
|
||
$values = [];
|
||
$where_sql = $where ? ('WHERE ' . implode(' AND ', $where)) : '';
|
||
|
||
// 1. Appliquer les filtres (année, code postal, etc.)
|
||
if (!empty($params['year'])) {
|
||
$where[] = "YEAR(date_rdv) = %s";
|
||
$values[] = $params['year'];
|
||
}
|
||
if (!empty($params['code_postal'])) {
|
||
$where[] = "code_postal = %s";
|
||
$values[] = $params['code_postal'];
|
||
}
|
||
|
||
// Filtre soft delete - exclure les événements supprimés
|
||
$where[] = "is_deleted = 0";
|
||
|
||
// 2. Agréger les données
|
||
$sql = "SELECT * FROM $table_name $where_sql";
|
||
$results = $wpdb->get_results($sql, ARRAY_A);
|
||
// 3. Retourner le tableau de stats
|
||
return $results;
|
||
}
|
||
|
||
/**
|
||
* Filtrer les entités disponibles pour un créneau donné.
|
||
* @param array $params (ex : ['date' => ..., 'heure' => ..., 'type' => ...])
|
||
* @return array
|
||
*/
|
||
public function filtrer_disponibles(array $params) {
|
||
global $wpdb;
|
||
$table_name = $wpdb->prefix . 'crvi_agenda';
|
||
$where = [];
|
||
$values = [];
|
||
$where_sql = $where ? ('WHERE ' . implode(' AND ', $where)) : '';
|
||
|
||
// 1. Récupérer la liste des entités du type demandé
|
||
if (!empty($params['date'])) {
|
||
$where[] = "date_rdv = %s";
|
||
$values[] = $params['date'];
|
||
}
|
||
if (!empty($params['heure'])) {
|
||
$where[] = "heure_rdv = %s";
|
||
$values[] = $params['heure'];
|
||
}
|
||
if (!empty($params['type'])) {
|
||
$where[] = "type = %s";
|
||
$values[] = $params['type'];
|
||
}
|
||
|
||
// Filtre soft delete - exclure les événements supprimés
|
||
$where[] = "is_deleted = 0";
|
||
|
||
// 2. Exclure celles déjà prises ou indisponibles à ce créneau
|
||
$sql = "SELECT * FROM $table_name $where_sql";
|
||
// 3. Retourner la liste filtrée
|
||
$results = $wpdb->get_results($sql, ARRAY_A);
|
||
return $results;
|
||
}
|
||
|
||
/**
|
||
* Wrapper pour obtenir les événements avec filtres API (GET /events)
|
||
*/
|
||
public function get_events_by_filters($params = []) {
|
||
global $wpdb;
|
||
$table_name = $wpdb->prefix . 'crvi_agenda';
|
||
|
||
|
||
// Mapping des filtres API vers les colonnes SQL
|
||
$map = [
|
||
'date_debut' => 'date_rdv',
|
||
'date_fin' => 'date_rdv',
|
||
'type' => 'type',
|
||
'statut' => 'statut',
|
||
'departement' => 'id_departement',
|
||
'type_intervention' => 'id_type_intervention',
|
||
'langue' => 'langue',
|
||
'beneficiaire' => 'id_beneficiaire',
|
||
'intervenant' => 'id_intervenant',
|
||
'traducteur' => 'id_traducteur',
|
||
'local' => 'id_local',
|
||
'cree_par' => 'cree_par',
|
||
'assign' => 'assign',
|
||
];
|
||
|
||
$where = [];
|
||
$values = [];
|
||
|
||
// Traitement des filtres
|
||
foreach ($map as $api => $col) {
|
||
if (!empty($params[$api]) || (isset($params[$api]) && $params[$api] === '0')) {
|
||
if ($api === 'date_debut') {
|
||
$where[] = "$col >= %s";
|
||
$values[] = $params[$api];
|
||
} elseif ($api === 'date_fin') {
|
||
$where[] = "$col <= %s";
|
||
$values[] = $params[$api];
|
||
} elseif ($api === 'assign') {
|
||
// Support du filtre assign (0 ou 1)
|
||
$where[] = "$col = %d";
|
||
$values[] = (int)$params[$api];
|
||
} else {
|
||
$where[] = "$col = %s";
|
||
$values[] = $params[$api];
|
||
}
|
||
}
|
||
}
|
||
|
||
// Support des paramètres FullCalendar
|
||
if (!empty($params['start'])) {
|
||
$where[] = "date_rdv >= %s";
|
||
$values[] = $params['start'];
|
||
}
|
||
if (!empty($params['end'])) {
|
||
$where[] = "date_rdv <= %s";
|
||
$values[] = $params['end'];
|
||
}
|
||
|
||
// Exclure les permanences non attribuées (pour agenda des collègues)
|
||
if (!empty($params['exclude_unassigned_permanences'])) {
|
||
// Exclure les événements de type permanence sans intervenant assigné (assign = 0)
|
||
$where[] = "(type != 'permanence' OR (type = 'permanence' AND assign = 1))";
|
||
}
|
||
|
||
// Filtre soft delete - exclure les événements supprimés
|
||
$where[] = "is_deleted = 0";
|
||
|
||
$where_sql = $where ? ('WHERE ' . implode(' AND ', $where)) : '';
|
||
$sql = "SELECT * FROM $table_name $where_sql ORDER BY date_rdv, heure_rdv";
|
||
|
||
|
||
if (!empty($values)) {
|
||
$sql = $wpdb->prepare($sql, $values);
|
||
}
|
||
|
||
$row = $wpdb->get_results($sql, ARRAY_A);
|
||
|
||
// Enrichir les données avec les objets liés
|
||
$events = [];
|
||
foreach ($row as $event) {
|
||
$enriched_event = $event;
|
||
|
||
// Charger le bénéficiaire
|
||
if (!empty($event['id_beneficiaire'])) {
|
||
$beneficiaire = CRVI_Beneficiaire_Model::load($event['id_beneficiaire'], ['id', 'nom', 'prenom', 'email', 'telephone', 'langues_parlees']);
|
||
if ($beneficiaire) {
|
||
// Convertir langues_parlees en tableau si nécessaire
|
||
$langues_parlees = $beneficiaire->langues_parlees;
|
||
if (!empty($langues_parlees) && !is_array($langues_parlees)) {
|
||
$langues_parlees = explode('|', $langues_parlees);
|
||
}
|
||
|
||
$enriched_event['beneficiaire'] = [
|
||
'id' => $beneficiaire->id,
|
||
'nom' => $beneficiaire->nom,
|
||
'prenom' => $beneficiaire->prenom,
|
||
'nom_complet' => $beneficiaire->nom . ' ' . $beneficiaire->prenom,
|
||
'email' => $beneficiaire->email,
|
||
'telephone' => $beneficiaire->telephone,
|
||
'langues_parlees' => $langues_parlees,
|
||
];
|
||
}
|
||
}
|
||
|
||
// Charger l'intervenant
|
||
if (!empty($event['id_intervenant'])) {
|
||
$intervenant = CRVI_Intervenant_Model::load($event['id_intervenant'], ['id', 'nom', 'prenom', 'email', 'departements_ids', 'types_intervention_ids']);
|
||
if ($intervenant) {
|
||
// Récupérer les noms des types d'intervention
|
||
$types_intervention_noms = [];
|
||
if (!empty($intervenant->types_intervention_ids)) {
|
||
|
||
if(!is_array($intervenant->types_intervention_ids)){
|
||
$intervenant->types_intervention_ids = explode('|', $intervenant->types_intervention_ids);
|
||
}
|
||
|
||
foreach ($intervenant->types_intervention_ids as $type_id) {
|
||
$type_nom = $this->get_type_intervention_nom($type_id);
|
||
if ($type_nom) {
|
||
$types_intervention_noms[] = $type_nom;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Récupérer les noms des départements
|
||
$departements_noms = [];
|
||
if (!empty($intervenant->departements_ids)) {
|
||
if(!is_array($intervenant->departements_ids)){
|
||
$intervenant->departements_ids = explode('|', $intervenant->departements_ids);
|
||
}
|
||
foreach ($intervenant->departements_ids as $dept_id) {
|
||
$dept_nom = $this->get_departement_nom($dept_id);
|
||
if ($dept_nom) {
|
||
$departements_noms[] = $dept_nom;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Récupérer les disponibilités (jours/heures) de l'intervenant depuis ACF
|
||
$intervenant_jours = [];
|
||
$intervenant_heures = [];
|
||
if (function_exists('get_field')) {
|
||
$intervenant_jours = get_field('jours_de_disponibilite', 'user_' . $intervenant->id) ?: [];
|
||
$intervenant_heures = get_field('heures_de_permanences', 'user_' . $intervenant->id) ?: [];
|
||
}
|
||
|
||
$enriched_event['intervenant'] = [
|
||
'id' => $intervenant->id,
|
||
'nom' => $intervenant->nom,
|
||
'prenom' => $intervenant->prenom,
|
||
'nom_complet' => $intervenant->nom . ' ' . $intervenant->prenom,
|
||
'email' => $intervenant->email,
|
||
'departements_ids' => $intervenant->departements_ids,
|
||
'departements_noms' => $departements_noms,
|
||
'types_intervention_ids' => $intervenant->types_intervention_ids,
|
||
'types_intervention_noms' => $types_intervention_noms,
|
||
// Disponibilités ACF
|
||
'jours_de_disponibilite' => is_array($intervenant_jours) ? array_values($intervenant_jours) : [],
|
||
'heures_de_permanences' => is_array($intervenant_heures) ? array_values($intervenant_heures) : [],
|
||
];
|
||
}
|
||
}
|
||
|
||
// Charger le traducteur
|
||
if (!empty($event['id_traducteur'])) {
|
||
$traducteur = CRVI_Traducteur_Model::load($event['id_traducteur'], ['id', 'nom', 'prenom', 'email', 'langues_parlees', 'organisme']);
|
||
if ($traducteur) {
|
||
// Convertir langues_parlees en tableau si nécessaire
|
||
$langues_parlees = $traducteur->langues_parlees;
|
||
if (!empty($langues_parlees) && !is_array($langues_parlees)) {
|
||
$langues_parlees = explode('|', $langues_parlees);
|
||
}
|
||
|
||
$enriched_event['traducteur'] = [
|
||
'id' => $traducteur->id,
|
||
'nom' => $traducteur->nom,
|
||
'prenom' => $traducteur->prenom,
|
||
'nom_complet' => $traducteur->nom . ' ' . $traducteur->prenom,
|
||
'email' => $traducteur->email,
|
||
'langues_parlees' => $langues_parlees,
|
||
'organisme' => $traducteur->organisme,
|
||
];
|
||
}
|
||
}
|
||
|
||
// Charger le local
|
||
if (!empty($event['id_local'])) {
|
||
$local = CRVI_Local_Model::load($event['id_local'], ['id', 'nom', 'type_de_local', 'capacite']);
|
||
if ($local) {
|
||
$enriched_event['local'] = [
|
||
'id' => $local->id,
|
||
'nom' => $local->nom,
|
||
'type_de_local' => $local->type_de_local,
|
||
'capacite' => $local->capacite,
|
||
];
|
||
}
|
||
}
|
||
|
||
// Ajouter langues_disponibles si présent (pour les permanences)
|
||
if (isset($event['langues_disponibles']) && !empty($event['langues_disponibles'])) {
|
||
$enriched_event['langues_disponibles'] = $event['langues_disponibles'];
|
||
}
|
||
|
||
$events[] = $enriched_event;
|
||
}
|
||
|
||
return $events;
|
||
}
|
||
|
||
/**
|
||
* Retourne les événements au format FullCalendar (GET /events/fullcalendar)
|
||
*/
|
||
public function get_events_fullcalendar($params = []) {
|
||
$events = $this->get_events_by_filters($params);
|
||
$result = [];
|
||
foreach ($events as $event) {
|
||
// Déterminer le titre de l'événement
|
||
$title = '';
|
||
if ($event['type'] === 'groupe') {
|
||
$title = 'Groupe';
|
||
} elseif (!empty($event['beneficiaire'])) {
|
||
$title = $event['beneficiaire']['nom_complet'];
|
||
} else {
|
||
$title = 'Événement sans bénéficiaire';
|
||
}
|
||
|
||
$result[] = [
|
||
'id' => $event['id'],
|
||
'title' => $title,
|
||
'start' => $event['date_rdv'] . 'T' . $event['heure_rdv'],
|
||
'end' => ($event['date_fin'] ?? $event['date_rdv']) . 'T' . ($event['heure_fin'] ?? $event['heure_rdv']),
|
||
'backgroundColor' => '#28a745',
|
||
'borderColor' => '#28a745',
|
||
'textColor' => '#fff',
|
||
'extendedProps' => [
|
||
'type' => $event['type'],
|
||
'statut' => $event['statut'],
|
||
'langue' => $event['langue'],
|
||
'langues_disponibles' => $event['langues_disponibles'] ?? null,
|
||
'beneficiaire' => $event['beneficiaire'] ?? null,
|
||
'intervenant' => $event['intervenant'] ?? null,
|
||
'traducteur' => $event['traducteur'] ?? null,
|
||
'local' => $event['local'] ?? null,
|
||
'commentaire' => $event['commentaire'],
|
||
],
|
||
];
|
||
}
|
||
return $result;
|
||
}
|
||
|
||
/**
|
||
* Change le statut d'un événement (PUT /events/{id}/statut)
|
||
*/
|
||
public function change_statut($id, $data) {
|
||
global $wpdb;
|
||
if (!current_user_can('edit_posts')) {
|
||
return Api_Helper::json_error('Non autorisé', 403);
|
||
}
|
||
if (empty($data['statut'])) {
|
||
return Api_Helper::json_error('Statut manquant', 400);
|
||
}
|
||
$update = [
|
||
'statut' => $data['statut'],
|
||
];
|
||
if (!empty($data['motif_annulation'])) {
|
||
$update['motif_annulation'] = $data['motif_annulation'];
|
||
}
|
||
if(!empty($data['commentaire'])){
|
||
$update['commentaire'] = $data['commentaire'];
|
||
}
|
||
|
||
// Si le statut est 'present' ou 'absence', marquer comme clôturé
|
||
if ($data['statut'] === 'present' || $data['statut'] === 'absence') {
|
||
$current_user = wp_get_current_user();
|
||
$update['cloture_flag'] = 1;
|
||
$update['cloture_par'] = $current_user->ID;
|
||
$update['cloture_rdv'] = current_time('mysql');
|
||
}
|
||
|
||
$wpdb->update($wpdb->prefix . 'crvi_agenda', $update, ['id' => $id]);
|
||
if ($wpdb->last_error) {
|
||
return Api_Helper::json_error('Erreur lors du changement de statut', 500);
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* Export des événements (GET /events/export)
|
||
*/
|
||
public function export_events($params = []) {
|
||
$events = $this->get_events_by_filters($params);
|
||
// Générer un CSV ou Excel (ici, on retourne juste les données brutes)
|
||
// Note: get_events_by_filters inclut déjà le filtre soft delete
|
||
return $events;
|
||
}
|
||
|
||
/**
|
||
* Récupère les événements pour le tableau de stats avec pagination
|
||
* @param array $params Paramètres de filtres
|
||
* @param int $page Numéro de page (défaut: 1)
|
||
* @param int $per_page Nombre d'éléments par page (défaut: 20)
|
||
* @return array Tableau avec events, total, filtered, page, per_page, total_pages
|
||
*/
|
||
public function get_events_table($params = [], $page = 1, $per_page = 20) {
|
||
global $wpdb;
|
||
$table_name = $wpdb->prefix . 'crvi_agenda';
|
||
|
||
// Mapping des filtres API vers les colonnes SQL
|
||
$map = [
|
||
'date_debut' => 'date_rdv',
|
||
'date_fin' => 'date_rdv',
|
||
'type' => 'type',
|
||
'statut' => 'statut',
|
||
'departement' => 'id_departement',
|
||
'type_intervention' => 'id_type_intervention',
|
||
'langue' => 'langue',
|
||
'beneficiaire' => 'id_beneficiaire',
|
||
'intervenant' => 'id_intervenant',
|
||
'traducteur' => 'id_traducteur',
|
||
'local' => 'id_local',
|
||
'cree_par' => 'cree_par',
|
||
'assign' => 'assign',
|
||
];
|
||
|
||
$where = [];
|
||
$values = [];
|
||
|
||
// Traitement des filtres
|
||
foreach ($map as $api => $col) {
|
||
if (!empty($params[$api]) || (isset($params[$api]) && $params[$api] === '0')) {
|
||
if ($api === 'date_debut') {
|
||
$where[] = "$col >= %s";
|
||
$values[] = $params[$api];
|
||
} elseif ($api === 'date_fin') {
|
||
$where[] = "$col <= %s";
|
||
$values[] = $params[$api];
|
||
} elseif ($api === 'assign') {
|
||
$where[] = "$col = %d";
|
||
$values[] = (int)$params[$api];
|
||
} else {
|
||
$where[] = "$col = %s";
|
||
$values[] = $params[$api];
|
||
}
|
||
}
|
||
}
|
||
|
||
// Support des paramètres FullCalendar
|
||
if (!empty($params['start'])) {
|
||
$where[] = "date_rdv >= %s";
|
||
$values[] = $params['start'];
|
||
}
|
||
if (!empty($params['end'])) {
|
||
$where[] = "date_rdv <= %s";
|
||
$values[] = $params['end'];
|
||
}
|
||
|
||
// Support du filtre date (si date_debut et date_fin sont identiques, c'est une recherche sur une seule date)
|
||
if (!empty($params['date']) && empty($params['date_debut']) && empty($params['date_fin'])) {
|
||
$where[] = "date_rdv = %s";
|
||
$values[] = $params['date'];
|
||
}
|
||
|
||
// Filtre soft delete - exclure les événements supprimés
|
||
$where[] = "is_deleted = 0";
|
||
|
||
$where_sql = $where ? ('WHERE ' . implode(' AND ', $where)) : '';
|
||
|
||
// Compter le total d'événements (sans filtres)
|
||
$total_sql = "SELECT COUNT(*) FROM $table_name WHERE is_deleted = 0";
|
||
$total = (int)$wpdb->get_var($total_sql);
|
||
|
||
// Compter les événements filtrés
|
||
$filtered_sql = "SELECT COUNT(*) FROM $table_name $where_sql";
|
||
if (!empty($values)) {
|
||
$filtered_sql = $wpdb->prepare($filtered_sql, $values);
|
||
}
|
||
$filtered = (int)$wpdb->get_var($filtered_sql);
|
||
|
||
// Calculer l'offset pour la pagination
|
||
$offset = ($page - 1) * $per_page;
|
||
|
||
// Requête avec pagination
|
||
if (!empty($values)) {
|
||
// Si on a des valeurs, on doit les ajouter avant LIMIT et OFFSET
|
||
$sql = "SELECT * FROM $table_name $where_sql ORDER BY date_rdv DESC, heure_rdv DESC LIMIT %d OFFSET %d";
|
||
$values_with_pagination = array_merge($values, [$per_page, $offset]);
|
||
$sql = $wpdb->prepare($sql, $values_with_pagination);
|
||
} else {
|
||
// Si pas de valeurs, on peut utiliser directement LIMIT et OFFSET
|
||
$sql = $wpdb->prepare(
|
||
"SELECT * FROM $table_name $where_sql ORDER BY date_rdv DESC, heure_rdv DESC LIMIT %d OFFSET %d",
|
||
$per_page,
|
||
$offset
|
||
);
|
||
}
|
||
|
||
$row = $wpdb->get_results($sql, ARRAY_A);
|
||
|
||
// Enrichir les données avec les objets liés
|
||
$events = [];
|
||
foreach ($row as $event) {
|
||
$details = $this->get_details($event['id']);
|
||
|
||
// Convertir la langue (ID/slug vers nom)
|
||
$langue_nom = $event['langue'] ?? '';
|
||
if (!empty($event['langue'])) {
|
||
// Essayer d'abord par ID numérique
|
||
$langue_term = get_term((int)$event['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', $event['langue'], 'langue');
|
||
if ($langue_term && !is_wp_error($langue_term)) {
|
||
$langue_nom = $langue_term->name;
|
||
}
|
||
}
|
||
}
|
||
|
||
$formatted_event = [
|
||
'id' => $event['id'],
|
||
'date_rdv' => $event['date_rdv'],
|
||
'heure_rdv' => $event['heure_rdv'],
|
||
'intervenant_nom' => $details->intervenant->nom_complet ?? '',
|
||
'beneficiaire_nom' => $details->beneficiaire->nom_complet ?? '',
|
||
'traducteur_nom' => $details->traducteur->nom_complet ?? '',
|
||
'langue' => $langue_nom,
|
||
'statut' => $event['statut'] ?? '',
|
||
'commentaire' => $event['commentaire'] ?? '',
|
||
];
|
||
|
||
$events[] = $formatted_event;
|
||
}
|
||
|
||
$total_pages = $filtered > 0 ? (int)ceil($filtered / $per_page) : 0;
|
||
|
||
return [
|
||
'events' => $events,
|
||
'total' => $total,
|
||
'filtered' => $filtered,
|
||
'page' => $page,
|
||
'per_page' => $per_page,
|
||
'total_pages' => $total_pages,
|
||
];
|
||
}
|
||
|
||
/**
|
||
* Restaurer un événement supprimé (soft delete)
|
||
* @param int $id
|
||
* @return bool|WP_Error
|
||
*/
|
||
public function restore_event(int $id) {
|
||
global $wpdb;
|
||
|
||
// 1. Vérifier les droits
|
||
if (!current_user_can('delete_posts')) {
|
||
return new \WP_Error('unauthorized', 'Non autorisé', ['status' => 403]);
|
||
}
|
||
|
||
// 2. Restaurer l'événement (remettre is_deleted à 0)
|
||
$result = $wpdb->update($wpdb->prefix . 'crvi_agenda', ['is_deleted' => 0], ['id' => $id]);
|
||
|
||
if ($result === false) {
|
||
return new \WP_Error('database_error', 'Erreur lors de la restauration', ['status' => 500]);
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* Supprimer définitivement un événement (hard delete)
|
||
* @param int $id
|
||
* @return bool|WP_Error
|
||
*/
|
||
public function hard_delete_event(int $id) {
|
||
global $wpdb;
|
||
|
||
// 1. Vérifier les droits
|
||
if (!current_user_can('delete_posts')) {
|
||
return new \WP_Error('unauthorized', 'Non autorisé', ['status' => 403]);
|
||
}
|
||
|
||
// 2. Supprimer définitivement l'événement
|
||
$result = $wpdb->delete($wpdb->prefix . 'crvi_agenda', ['id' => $id]);
|
||
|
||
if ($result === false) {
|
||
return new \WP_Error('database_error', 'Erreur lors de la suppression définitive', ['status' => 500]);
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* Lister les événements supprimés (soft delete)
|
||
* @param array $params
|
||
* @return array
|
||
*/
|
||
public function get_deleted_events($params = []) {
|
||
global $wpdb;
|
||
$table_name = $wpdb->prefix . 'crvi_agenda';
|
||
|
||
// Mapping des filtres API vers les colonnes SQL
|
||
$map = [
|
||
'date_debut' => 'date_rdv',
|
||
'date_fin' => 'date_rdv',
|
||
'type' => 'type',
|
||
'statut' => 'statut',
|
||
'departement' => 'departement',
|
||
'type_intervention' => 'type_intervention',
|
||
'langue' => 'langue',
|
||
'beneficiaire' => 'id_beneficiaire',
|
||
'intervenant' => 'id_intervenant',
|
||
'traducteur' => 'id_traducteur',
|
||
'local' => 'id_local',
|
||
'cree_par' => 'cree_par',
|
||
];
|
||
|
||
$where = ['is_deleted = 1']; // Seulement les événements supprimés
|
||
$values = [];
|
||
|
||
// Traitement des filtres
|
||
foreach ($map as $api => $col) {
|
||
if (!empty($params[$api])) {
|
||
if ($api === 'date_debut') {
|
||
$where[] = "$col >= %s";
|
||
$values[] = $params[$api];
|
||
} elseif ($api === 'date_fin') {
|
||
$where[] = "$col <= %s";
|
||
$values[] = $params[$api];
|
||
} else {
|
||
$where[] = "$col = %s";
|
||
$values[] = $params[$api];
|
||
}
|
||
}
|
||
}
|
||
|
||
$where_sql = 'WHERE ' . implode(' AND ', $where);
|
||
$sql = "SELECT * FROM $table_name $where_sql ORDER BY date_rdv DESC, heure_rdv DESC";
|
||
|
||
if (!empty($values)) {
|
||
$sql = $wpdb->prepare($sql, $values);
|
||
}
|
||
|
||
return $wpdb->get_results($sql, ARRAY_A);
|
||
}
|
||
|
||
/**
|
||
* Récupérer les 3 derniers rendez-vous d'un bénéficiaire avec leurs incidents
|
||
* @param int $beneficiaire_id
|
||
* @return array
|
||
*/
|
||
public static function get_last_3_rdv_by_beneficiaire($beneficiaire_id) {
|
||
global $wpdb;
|
||
$table_name = $wpdb->prefix . 'crvi_agenda';
|
||
$incident_table = $wpdb->prefix . 'crvi_agenda_incident';
|
||
|
||
// Récupérer les 3 derniers RDV individuels du bénéficiaire
|
||
$events = $wpdb->get_results($wpdb->prepare(
|
||
"SELECT * FROM $table_name
|
||
WHERE id_beneficiaire = %d
|
||
AND type = 'individuel'
|
||
AND is_deleted = 0
|
||
ORDER BY date_rdv DESC, heure_rdv DESC
|
||
LIMIT 3",
|
||
$beneficiaire_id
|
||
), ARRAY_A);
|
||
|
||
if (empty($events)) {
|
||
return [];
|
||
}
|
||
|
||
$result = [];
|
||
foreach ($events as $event) {
|
||
$enriched_event = [
|
||
'id' => (int) $event['id'],
|
||
'date_rdv' => $event['date_rdv'],
|
||
'heure_rdv' => $event['heure_rdv'],
|
||
'type' => $event['type'] ?? '',
|
||
'statut' => $event['statut'] ?? '',
|
||
'commentaire' => $event['commentaire'] ?? '',
|
||
];
|
||
|
||
// Charger le bénéficiaire et son statut liste rouge
|
||
if (!empty($event['id_beneficiaire'])) {
|
||
$personne_en_liste_rouge = false;
|
||
if (function_exists('get_field')) {
|
||
$personne_en_liste_rouge = get_field('field_69495826ac495', $event['id_beneficiaire']);
|
||
}
|
||
$enriched_event['personne_en_liste_rouge'] = (bool) $personne_en_liste_rouge;
|
||
}
|
||
|
||
// Charger l'intervenant
|
||
if (!empty($event['id_intervenant'])) {
|
||
$intervenant = CRVI_Intervenant_Model::load($event['id_intervenant'], ['id', 'nom', 'prenom']);
|
||
if ($intervenant) {
|
||
$enriched_event['intervenant'] = [
|
||
'id' => $intervenant->id,
|
||
'nom' => $intervenant->nom ?? '',
|
||
'prenom' => $intervenant->prenom ?? '',
|
||
];
|
||
}
|
||
}
|
||
|
||
// Charger le type d'intervention
|
||
if (!empty($event['id_type_intervention'])) {
|
||
$type_intervention = get_term_by('id', $event['id_type_intervention'], 'type_intervention');
|
||
if ($type_intervention && !is_wp_error($type_intervention)) {
|
||
$enriched_event['type_intervention'] = [
|
||
'id' => (int) $event['id_type_intervention'],
|
||
'nom' => $type_intervention->name,
|
||
];
|
||
}
|
||
}
|
||
|
||
// Charger l'incident associé (s'il existe)
|
||
$incident = $wpdb->get_row($wpdb->prepare(
|
||
"SELECT * FROM $incident_table
|
||
WHERE beneficiaire_id = %d
|
||
AND event_id = %d
|
||
LIMIT 1",
|
||
$beneficiaire_id,
|
||
$event['id']
|
||
));
|
||
|
||
if ($incident) {
|
||
$enriched_event['incident'] = [
|
||
'id' => (int) $incident->id,
|
||
'resume_incident' => $incident->resume_incident,
|
||
'commentaire_incident' => $incident->commentaire_incident ?? '',
|
||
];
|
||
}
|
||
|
||
$result[] = $enriched_event;
|
||
}
|
||
|
||
return $result;
|
||
}
|
||
|
||
/**
|
||
* Récupère tous les événements d'aujourd'hui dont le statut est non complété
|
||
* (présence non validée et non marqué absent)
|
||
*
|
||
* Statuts non complétés : 'prevu', 'annule', 'non_tenu'
|
||
* Statuts complétés : 'cloture', 'absence'
|
||
*
|
||
* @return array Tableau d'événements enrichis
|
||
*/
|
||
public static function get_today_incomplete_events() {
|
||
global $wpdb;
|
||
|
||
$table_name = $wpdb->prefix . 'crvi_agenda';
|
||
$today = current_time('Y-m-d');
|
||
|
||
// Récupérer les événements d'aujourd'hui avec statut non complété
|
||
$sql = $wpdb->prepare(
|
||
"SELECT * FROM $table_name
|
||
WHERE date_rdv = %s
|
||
AND statut NOT IN ('cloture', 'absence')
|
||
AND is_deleted = 0
|
||
ORDER BY heure_rdv ASC",
|
||
$today
|
||
);
|
||
|
||
$events = $wpdb->get_results($sql, ARRAY_A);
|
||
|
||
if (empty($events)) {
|
||
return [
|
||
'success' => true,
|
||
'count' => 0,
|
||
'events' => [],
|
||
'date' => $today,
|
||
'message' => 'Aucun événement non complété trouvé pour aujourd\'hui'
|
||
];
|
||
}
|
||
|
||
// Enrichir les événements avec les données liées
|
||
$enriched_events = [];
|
||
foreach ($events as $event) {
|
||
$enriched_event = [
|
||
'id' => (int) $event['id'],
|
||
'date_rdv' => $event['date_rdv'],
|
||
'heure_rdv' => $event['heure_rdv'],
|
||
'type' => $event['type'],
|
||
'statut' => $event['statut'],
|
||
'langue' => $event['langue'],
|
||
];
|
||
|
||
// Charger le bénéficiaire si présent
|
||
if (!empty($event['id_beneficiaire'])) {
|
||
$beneficiaire = CRVI_Beneficiaire_Model::load($event['id_beneficiaire'], ['id', 'nom', 'prenom', 'telephone']);
|
||
if ($beneficiaire) {
|
||
$enriched_event['beneficiaire'] = [
|
||
'id' => $beneficiaire->id,
|
||
'nom' => $beneficiaire->nom ?? '',
|
||
'prenom' => $beneficiaire->prenom ?? '',
|
||
'nom_complet' => ($beneficiaire->nom ?? '') . ' ' . ($beneficiaire->prenom ?? ''),
|
||
'telephone' => $beneficiaire->telephone ?? '',
|
||
];
|
||
}
|
||
}
|
||
|
||
// Charger l'intervenant si présent
|
||
if (!empty($event['id_intervenant'])) {
|
||
$intervenant = CRVI_Intervenant_Model::load($event['id_intervenant'], ['id', 'nom', 'prenom']);
|
||
if ($intervenant) {
|
||
$enriched_event['intervenant'] = [
|
||
'id' => $intervenant->id,
|
||
'nom' => $intervenant->nom ?? '',
|
||
'prenom' => $intervenant->prenom ?? '',
|
||
'nom_complet' => ($intervenant->nom ?? '') . ' ' . ($intervenant->prenom ?? ''),
|
||
];
|
||
}
|
||
}
|
||
|
||
$enriched_events[] = $enriched_event;
|
||
}
|
||
|
||
return [
|
||
'success' => true,
|
||
'count' => count($enriched_events),
|
||
'date' => $today,
|
||
'events' => $enriched_events
|
||
];
|
||
}
|
||
} |