1121 lines
43 KiB
PHP
1121 lines
43 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace ESI_CRVI_AGENDA\models;
|
|
|
|
use ESI_CRVI_AGENDA\models\CRVI_Event_Model;
|
|
|
|
/**
|
|
* Modèle pour les capacités de traduction
|
|
* Gère les règles de capacité par langue, jour et période
|
|
*/
|
|
class CRVI_TraductionLangue_Model extends Main_Model {
|
|
public $id;
|
|
public $langue; // Peut être un tableau d'IDs de langues (multi-select)
|
|
public $jour;
|
|
public $periode;
|
|
public $limite;
|
|
public $limite_par;
|
|
public $actif;
|
|
public $ajouter_exception;
|
|
public $exceptions_disponibilites;
|
|
|
|
/**
|
|
* Schéma des champs ACF : nom => type
|
|
*/
|
|
public static $acf_schema = [
|
|
'langue' => 'taxonomy', // multi_select - retourne un tableau d'IDs
|
|
'jour' => 'select',
|
|
'periode' => 'select',
|
|
'limite' => 'number',
|
|
'limite_par' => 'select',
|
|
'actif' => 'true_false',
|
|
'ajouter_exception' => 'true_false',
|
|
'exceptions_disponibilites' => 'group',
|
|
];
|
|
|
|
public function __construct($data = []) {
|
|
foreach ($data as $key => $value) {
|
|
if (property_exists($this, $key)) {
|
|
$this->$key = $value;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Charge une capacité de traduction par ID
|
|
* @param int $id
|
|
* @param array $fields Champs spécifiques à charger (optionnel)
|
|
* @return self|null
|
|
*/
|
|
public static function load($id, $fields = []) {
|
|
$post = \get_post($id);
|
|
if (!$post || $post->post_type !== 'traduction_langue') {
|
|
return null;
|
|
}
|
|
|
|
// Si des champs spécifiques sont demandés
|
|
if (!empty($fields)) {
|
|
$data = ['id' => $post->ID];
|
|
foreach ($fields as $field) {
|
|
if (property_exists(self::class, $field)) {
|
|
$data[$field] = \get_field($field, $post->ID);
|
|
}
|
|
}
|
|
return new self($data);
|
|
}
|
|
|
|
// Charger tous les champs
|
|
$langue_field = \get_field('langue', $post->ID);
|
|
// S'assurer que langue est toujours un tableau
|
|
if (!is_array($langue_field)) {
|
|
$langue_field = !empty($langue_field) ? [$langue_field] : [];
|
|
}
|
|
|
|
return new self([
|
|
'id' => $post->ID,
|
|
'langue' => $langue_field, // Tableau d'IDs de langues
|
|
'jour' => \get_field('jour', $post->ID),
|
|
'periode' => \get_field('periode', $post->ID),
|
|
'limite' => \get_field('limite', $post->ID),
|
|
'limite_par' => \get_field('limite_par', $post->ID) ?: 'semaine',
|
|
'actif' => \get_field('actif', $post->ID) !== false,
|
|
'ajouter_exception' => \get_field('ajouter_exception', $post->ID) ?: false,
|
|
'exceptions_disponibilites' => \get_field('exceptions_disponibilites', $post->ID) ?: null,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Récupère toutes les capacités actives
|
|
* @param array $filters Filtres optionnels (langue, jour, periode, type_capacite)
|
|
* @param bool $use_cache Utiliser le cache (par défaut: true)
|
|
* @return array Liste des capacités actives
|
|
*/
|
|
public static function getActiveCapacites(array $filters = [], bool $use_cache = true): array {
|
|
// Générer une clé de cache basée sur les filtres
|
|
$cache_key = 'crvi_capacites_actives_' . md5(serialize($filters));
|
|
|
|
// Essayer de récupérer depuis le cache si activé et sans filtres complexes
|
|
if ($use_cache && empty($filters)) {
|
|
$cached = \get_transient($cache_key);
|
|
if ($cached !== false) {
|
|
return $cached;
|
|
}
|
|
}
|
|
|
|
$args = [
|
|
'post_type' => 'traduction_langue',
|
|
'posts_per_page' => -1,
|
|
'post_status' => 'publish',
|
|
'meta_query' => [
|
|
[
|
|
'key' => 'actif',
|
|
'value' => '1',
|
|
'compare' => '=',
|
|
],
|
|
],
|
|
];
|
|
|
|
// Appliquer les filtres
|
|
if (!empty($filters['langue'])) {
|
|
$args['tax_query'] = [
|
|
[
|
|
'taxonomy' => 'langue',
|
|
'field' => 'term_id',
|
|
'terms' => (int) $filters['langue'],
|
|
],
|
|
];
|
|
}
|
|
|
|
if (!empty($filters['jour'])) {
|
|
$args['meta_query'][] = [
|
|
'key' => 'jour',
|
|
'value' => $filters['jour'],
|
|
'compare' => '=',
|
|
];
|
|
}
|
|
|
|
if (!empty($filters['periode'])) {
|
|
$args['meta_query'][] = [
|
|
'key' => 'periode',
|
|
'value' => $filters['periode'],
|
|
'compare' => '=',
|
|
];
|
|
}
|
|
|
|
$posts = \get_posts($args);
|
|
$capacites = [];
|
|
|
|
foreach ($posts as $post) {
|
|
$capacites[] = self::load($post->ID);
|
|
}
|
|
|
|
// Mettre en cache si activé et sans filtres
|
|
if ($use_cache && empty($filters)) {
|
|
\set_transient($cache_key, $capacites, 1 * \HOUR_IN_SECONDS);
|
|
}
|
|
|
|
return $capacites;
|
|
}
|
|
|
|
/**
|
|
* Compte le nombre de créneaux utilisés pour une capacité donnée
|
|
* Gère la période (semaine/mois) et exclut les événements annulés/brouillons
|
|
*
|
|
* @param int $capacite_id ID de la capacité
|
|
* @param string|null $date Date de référence (Y-m-d). Si null, utilise la date actuelle
|
|
* @param self|null $capacite Capacité déjà chargée (évite un appel load() supplémentaire)
|
|
* @return int Nombre de créneaux utilisés
|
|
*/
|
|
public static function countUsed(int $capacite_id, ?string $date = null, ?self $capacite = null): int {
|
|
// Charger la capacité si non fournie
|
|
if (!$capacite) {
|
|
$capacite = self::load($capacite_id);
|
|
if (!$capacite) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// Date de référence
|
|
if (!$date) {
|
|
$date = \current_time('Y-m-d');
|
|
}
|
|
|
|
// Générer une clé de cache unique
|
|
$cache_key = 'crvi_count_used_' . $capacite_id . '_' . $date . '_' . md5(serialize([
|
|
$capacite->langue,
|
|
$capacite->jour,
|
|
$capacite->periode,
|
|
$capacite->limite_par,
|
|
]));
|
|
|
|
// Essayer de récupérer depuis le cache (cache de 15 minutes)
|
|
$cached = \get_transient($cache_key);
|
|
if ($cached !== false) {
|
|
return (int) $cached;
|
|
}
|
|
|
|
// Déterminer la plage de dates selon limite_par
|
|
$date_debut = $date;
|
|
$date_fin = $date;
|
|
$date_obj = new \DateTime($date);
|
|
|
|
if ($capacite->limite_par === 'semaine') {
|
|
// Calculer le début et la fin de la semaine
|
|
$start_of_week = (int) \get_option('start_of_week', 1); // 0=dimanche, 1=lundi, etc.
|
|
|
|
// Obtenir le jour de la semaine (0=dimanche, 1=lundi, ..., 6=samedi)
|
|
$day_of_week = (int) $date_obj->format('w');
|
|
|
|
// Calculer le nombre de jours à soustraire pour arriver au début de la semaine
|
|
$days_to_subtract = ($day_of_week - $start_of_week + 7) % 7;
|
|
|
|
$date_obj->modify("-{$days_to_subtract} days");
|
|
$date_debut = $date_obj->format('Y-m-d');
|
|
|
|
$date_obj->modify('+6 days');
|
|
$date_fin = $date_obj->format('Y-m-d');
|
|
} elseif ($capacite->limite_par === 'mois') {
|
|
// Premier et dernier jour du mois
|
|
$date_debut = $date_obj->format('Y-m-01');
|
|
$date_fin = $date_obj->format('Y-m-t');
|
|
}
|
|
|
|
// Récupérer les langues (peut être un tableau)
|
|
$langue_ids = $capacite->langue;
|
|
if (empty($langue_ids)) {
|
|
return 0;
|
|
}
|
|
|
|
// S'assurer que c'est un tableau
|
|
if (!is_array($langue_ids)) {
|
|
$langue_ids = [$langue_ids];
|
|
}
|
|
|
|
// Récupérer les slugs de toutes les langues (avec cache)
|
|
$langue_slugs = [];
|
|
foreach ($langue_ids as $langue_id) {
|
|
$slug = self::get_langue_slug($langue_id);
|
|
if ($slug) {
|
|
$langue_slugs[] = $slug;
|
|
}
|
|
}
|
|
|
|
if (empty($langue_slugs)) {
|
|
return 0;
|
|
}
|
|
|
|
// Convertir le jour en format de requête
|
|
$jour_nom = $capacite->jour; // lundi, mardi, etc.
|
|
|
|
// Requête pour compter les événements
|
|
global $wpdb;
|
|
$table_name = $wpdb->prefix . 'crvi_agenda';
|
|
|
|
// Construire la requête SQL
|
|
// Pour les langues multiples, utiliser IN au lieu de =
|
|
$langue_placeholders = implode(',', array_fill(0, count($langue_slugs), '%s'));
|
|
|
|
$where = [
|
|
"date_rdv BETWEEN %s AND %s",
|
|
"langue IN ($langue_placeholders)",
|
|
"is_deleted = 0",
|
|
"statut != 'annule'",
|
|
"statut != 'brouillon'",
|
|
];
|
|
$values = array_merge([$date_debut, $date_fin], $langue_slugs);
|
|
|
|
// Filtrer par jour de la semaine
|
|
// DAYOFWEEK() en MySQL retourne: 1=dimanche, 2=lundi, 3=mardi, 4=mercredi, 5=jeudi, 6=vendredi, 7=samedi
|
|
$jours_map = [
|
|
'dimanche' => 1,
|
|
'lundi' => 2,
|
|
'mardi' => 3,
|
|
'mercredi' => 4,
|
|
'jeudi' => 5,
|
|
'vendredi' => 6,
|
|
'samedi' => 7,
|
|
];
|
|
|
|
if (isset($jours_map[$jour_nom])) {
|
|
$where[] = "DAYOFWEEK(date_rdv) = %d";
|
|
$values[] = $jours_map[$jour_nom];
|
|
}
|
|
|
|
// Filtrer par période (matin, après-midi, journée)
|
|
// Matin: heure_rdv < 12:00
|
|
// Après-midi: heure_rdv >= 12:00
|
|
// Journée: pas de filtre sur l'heure (tous les événements du jour)
|
|
if ($capacite->periode === 'matin') {
|
|
$where[] = "heure_rdv < '12:00:00'";
|
|
} elseif ($capacite->periode === 'apres_midi') {
|
|
$where[] = "heure_rdv >= '12:00:00'";
|
|
}
|
|
// Si période = 'journee', on ne filtre pas par heure
|
|
|
|
$where_sql = 'WHERE ' . implode(' AND ', $where);
|
|
$sql = "SELECT COUNT(*) FROM {$table_name} {$where_sql}";
|
|
$prepared_sql = $wpdb->prepare($sql, $values);
|
|
|
|
$count = (int) $wpdb->get_var($prepared_sql);
|
|
|
|
// Mettre en cache le résultat (15 minutes)
|
|
\set_transient($cache_key, $count, 15 * \MINUTE_IN_SECONDS);
|
|
|
|
return $count;
|
|
}
|
|
|
|
/**
|
|
* Récupère le slug d'une langue avec cache
|
|
* @param int $langue_term_id Term ID de la langue
|
|
* @return string|null Slug de la langue ou null si erreur
|
|
*/
|
|
private static function get_langue_slug(int $langue_term_id): ?string {
|
|
$cache_key = 'crvi_langue_slug_' . $langue_term_id;
|
|
$cached = \get_transient($cache_key);
|
|
|
|
if ($cached !== false) {
|
|
return $cached;
|
|
}
|
|
|
|
$langue_term = \get_term($langue_term_id, 'langue');
|
|
if (!$langue_term || \is_wp_error($langue_term)) {
|
|
return null;
|
|
}
|
|
|
|
$slug = $langue_term->slug;
|
|
|
|
// Mettre en cache (1 heure)
|
|
\set_transient($cache_key, $slug, 1 * \HOUR_IN_SECONDS);
|
|
|
|
return $slug;
|
|
}
|
|
|
|
/**
|
|
* Vérifie si une capacité a des événements associés
|
|
* Optimisé avec EXISTS au lieu de COUNT pour meilleure performance
|
|
* Gère les langues multiples (limite partagée)
|
|
* @param int $capacite_id ID de la capacité
|
|
* @return bool True si la capacité a des événements
|
|
*/
|
|
public static function hasEvents(int $capacite_id): bool {
|
|
$capacite = self::load($capacite_id);
|
|
if (!$capacite) {
|
|
return false;
|
|
}
|
|
|
|
// Récupérer les langues (peut être un tableau)
|
|
$langue_ids = $capacite->langue;
|
|
if (empty($langue_ids)) {
|
|
return false;
|
|
}
|
|
|
|
// S'assurer que c'est un tableau
|
|
if (!is_array($langue_ids)) {
|
|
$langue_ids = [$langue_ids];
|
|
}
|
|
|
|
// Récupérer les slugs de toutes les langues
|
|
$langue_slugs = [];
|
|
foreach ($langue_ids as $langue_id) {
|
|
$slug = self::get_langue_slug($langue_id);
|
|
if ($slug) {
|
|
$langue_slugs[] = $slug;
|
|
}
|
|
}
|
|
|
|
if (empty($langue_slugs)) {
|
|
return false;
|
|
}
|
|
|
|
// Requête optimisée avec EXISTS (plus rapide que COUNT)
|
|
global $wpdb;
|
|
$table_name = $wpdb->prefix . 'crvi_agenda';
|
|
|
|
// Construire les placeholders pour IN
|
|
$langue_placeholders = implode(',', array_fill(0, count($langue_slugs), '%s'));
|
|
|
|
$sql = $wpdb->prepare(
|
|
"SELECT EXISTS(
|
|
SELECT 1 FROM {$table_name}
|
|
WHERE langue IN ($langue_placeholders)
|
|
AND is_deleted = 0
|
|
AND statut != 'annule'
|
|
AND statut != 'brouillon'
|
|
LIMIT 1
|
|
)",
|
|
$langue_slugs
|
|
);
|
|
|
|
$exists = (int) $wpdb->get_var($sql);
|
|
|
|
return $exists === 1;
|
|
}
|
|
|
|
/**
|
|
* Vérifie si une capacité est disponible (limite non atteinte)
|
|
* @param int $capacite_id ID de la capacité
|
|
* @param string|null $date Date de référence (Y-m-d). Si null, utilise la date actuelle
|
|
* @param self|null $capacite Capacité déjà chargée (évite un appel load() supplémentaire)
|
|
* @return bool True si la capacité est disponible
|
|
*/
|
|
public static function isAvailable(int $capacite_id, ?string $date = null, ?self $capacite = null): bool {
|
|
// Charger la capacité si non fournie
|
|
if (!$capacite) {
|
|
$capacite = self::load($capacite_id);
|
|
if (!$capacite) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Si la capacité est inactive, elle n'est pas disponible
|
|
if (!$capacite->actif) {
|
|
return false;
|
|
}
|
|
|
|
// Si pas de limite définie, la capacité est toujours disponible
|
|
if (empty($capacite->limite) || $capacite->limite <= 0) {
|
|
return true;
|
|
}
|
|
|
|
// Compter les créneaux utilisés (en passant la capacité pour éviter un load() supplémentaire)
|
|
$used = self::countUsed($capacite_id, $date, $capacite);
|
|
|
|
// Vérifier si la limite est atteinte
|
|
return $used < $capacite->limite;
|
|
}
|
|
|
|
/**
|
|
* Calcule la limite effective en prenant en compte les exceptions
|
|
* Exemple : 1 fois/semaine matin (= ~4/mois) + exception 1/mois après-midi = 4 matins + 1 après-midi
|
|
*
|
|
* @param self $capacite Capacité
|
|
* @param string|null $date Date de référence (Y-m-d). Si null, utilise la date actuelle
|
|
* @return array ['principale' => ['limite' => int, 'periode' => string], 'exception' => ['limite' => int, 'periode' => string] | null]
|
|
*/
|
|
public static function getEffectiveLimit(self $capacite, ?string $date = null): array {
|
|
if (!$date) {
|
|
$date = \current_time('Y-m-d');
|
|
}
|
|
|
|
$result = [
|
|
'principale' => [
|
|
'limite' => (int) $capacite->limite,
|
|
'periode' => $capacite->periode,
|
|
'limite_par' => $capacite->limite_par,
|
|
],
|
|
'exception' => null,
|
|
];
|
|
|
|
// Si pas d'exception, retourner seulement la limite principale
|
|
if (!$capacite->ajouter_exception || empty($capacite->exceptions_disponibilites)) {
|
|
return $result;
|
|
}
|
|
|
|
$exceptions = $capacite->exceptions_disponibilites;
|
|
|
|
// Vérifier que l'exception a les champs nécessaires
|
|
if (empty($exceptions['frequence']) || empty($exceptions['frequence_periode']) || empty($exceptions['periode'])) {
|
|
return $result;
|
|
}
|
|
|
|
// Calculer la limite de l'exception
|
|
$exception_limite = (int) $exceptions['frequence'];
|
|
$exception_periode = $exceptions['periode'];
|
|
$exception_frequence_periode = $exceptions['frequence_periode']; // 'semaine' ou 'mois'
|
|
|
|
// Si l'exception est par mois, utiliser directement la fréquence
|
|
if ($exception_frequence_periode === 'mois') {
|
|
$result['exception'] = [
|
|
'limite' => $exception_limite,
|
|
'periode' => $exception_periode,
|
|
'limite_par' => 'mois',
|
|
];
|
|
} else {
|
|
// Si l'exception est par semaine, convertir en nombre par mois
|
|
$date_obj = new \DateTime($date);
|
|
$nb_semaines = self::getNbSemainesDansMois($date_obj);
|
|
|
|
$result['exception'] = [
|
|
'limite' => $exception_limite * $nb_semaines,
|
|
'periode' => $exception_periode,
|
|
'limite_par' => 'mois',
|
|
];
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Calcule le nombre de semaines dans un mois donné
|
|
*
|
|
* @param \DateTime $date Date de référence
|
|
* @return int Nombre de semaines
|
|
*/
|
|
private static function getNbSemainesDansMois(\DateTime $date): int {
|
|
$premier_jour = new \DateTime($date->format('Y-m-01'));
|
|
$dernier_jour = new \DateTime($date->format('Y-m-t'));
|
|
|
|
$start_of_week = (int) \get_option('start_of_week', 1); // 0=dimanche, 1=lundi
|
|
|
|
// Calculer le nombre de semaines
|
|
$diff = $premier_jour->diff($dernier_jour);
|
|
$nb_jours = $diff->days + 1;
|
|
|
|
// Arrondir au nombre de semaines (4 ou 5 selon le mois)
|
|
return (int) ceil($nb_jours / 7);
|
|
}
|
|
|
|
/**
|
|
* Compte les événements utilisés pour la période principale et l'exception
|
|
*
|
|
* @param int $capacite_id ID de la capacité
|
|
* @param string|null $date Date de référence (Y-m-d). Si null, utilise la date actuelle
|
|
* @param self|null $capacite Capacité déjà chargée
|
|
* @return array ['principale' => int, 'exception' => int, 'total' => int]
|
|
*/
|
|
public static function countUsedWithExceptions(int $capacite_id, ?string $date = null, ?self $capacite = null): array {
|
|
// Charger la capacité si non fournie
|
|
if (!$capacite) {
|
|
$capacite = self::load($capacite_id);
|
|
if (!$capacite) {
|
|
return ['principale' => 0, 'exception' => 0, 'total' => 0];
|
|
}
|
|
}
|
|
|
|
// Compter les événements pour la période principale
|
|
$count_principale = self::countUsed($capacite_id, $date, $capacite);
|
|
|
|
$result = [
|
|
'principale' => $count_principale,
|
|
'exception' => 0,
|
|
'total' => $count_principale,
|
|
];
|
|
|
|
// Si pas d'exception, retourner
|
|
if (!$capacite->ajouter_exception || empty($capacite->exceptions_disponibilites)) {
|
|
return $result;
|
|
}
|
|
|
|
$exceptions = $capacite->exceptions_disponibilites;
|
|
|
|
// Vérifier que l'exception a les champs nécessaires
|
|
if (empty($exceptions['periode'])) {
|
|
return $result;
|
|
}
|
|
|
|
// Compter les événements pour l'exception (période différente)
|
|
$exception_periode = $exceptions['periode'];
|
|
|
|
// Si la période de l'exception est différente de la période principale
|
|
if ($exception_periode !== $capacite->periode) {
|
|
// Créer une capacité temporaire avec la période de l'exception
|
|
$temp_capacite = clone $capacite;
|
|
$temp_capacite->periode = $exception_periode;
|
|
|
|
$count_exception = self::countUsed($capacite_id, $date, $temp_capacite);
|
|
|
|
$result['exception'] = $count_exception;
|
|
$result['total'] = $count_principale + $count_exception;
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Récupère les disponibilités pour une langue donnée en agrégeant toutes les capacités
|
|
* Exemple : Si 2 capacités existent pour l'arabe (1/mois matin + 1/semaine après-midi)
|
|
* Le résultat sera : 1 matin/mois + ~4 après-midi/mois = 5 créneaux/mois au total
|
|
*
|
|
* @param int $langue_term_id ID du terme de la taxonomie langue
|
|
* @param string|null $date Date de référence (Y-m-d). Si null, utilise la date actuelle
|
|
* @param bool $use_cache Utiliser le cache transient (par défaut: true)
|
|
* @return array ['total' => int, 'by_periode' => ['matin' => [...], 'apres_midi' => [...], 'journee' => [...]]]
|
|
*/
|
|
public static function getDisponibilitesByLangue(int $langue_term_id, ?string $date = null, bool $use_cache = true): array {
|
|
if (!$date) {
|
|
$date = \current_time('Y-m-d');
|
|
}
|
|
|
|
// Générer une clé de cache unique basée sur la langue et la date
|
|
$cache_key = 'crvi_dispos_langue_' . $langue_term_id . '_' . $date;
|
|
|
|
// Essayer de récupérer depuis le cache
|
|
if ($use_cache) {
|
|
$cached = \get_transient($cache_key);
|
|
if ($cached !== false) {
|
|
return $cached;
|
|
}
|
|
}
|
|
|
|
// Initialiser le résultat
|
|
$result = [
|
|
'total' => 0,
|
|
'total_used' => 0,
|
|
'total_available' => 0,
|
|
'by_periode' => [
|
|
'matin' => [],
|
|
'apres_midi' => [],
|
|
'journee' => [],
|
|
],
|
|
];
|
|
|
|
// Récupérer toutes les capacités actives pour cette langue
|
|
$capacites = self::getActiveCapacites(['langue' => $langue_term_id], false);
|
|
|
|
if (empty($capacites)) {
|
|
return $result;
|
|
}
|
|
|
|
$date_obj = new \DateTime($date);
|
|
$nb_semaines = self::getNbSemainesDansMois($date_obj);
|
|
|
|
// Parcourir chaque capacité et calculer sa contribution
|
|
foreach ($capacites as $capacite) {
|
|
// Vérifier que la langue correspond (peut être un tableau)
|
|
$capacite_langues = is_array($capacite->langue) ? $capacite->langue : [$capacite->langue];
|
|
if (!in_array($langue_term_id, $capacite_langues)) {
|
|
continue;
|
|
}
|
|
|
|
// Calculer la limite mensuelle pour la période principale
|
|
$limite_mensuelle_principale = self::convertToMonthlyLimit(
|
|
$capacite->limite,
|
|
$capacite->limite_par,
|
|
$nb_semaines
|
|
);
|
|
|
|
// Compter les événements utilisés pour la période principale
|
|
$used_principale = self::countUsed($capacite->id, $date, $capacite);
|
|
|
|
// Ajouter la capacité principale
|
|
$periode_principale = $capacite->periode;
|
|
$result['by_periode'][$periode_principale][] = [
|
|
'capacite_id' => $capacite->id,
|
|
'capacite_title' => \get_the_title($capacite->id),
|
|
'jour' => $capacite->jour,
|
|
'limite' => $limite_mensuelle_principale,
|
|
'used' => $used_principale,
|
|
'available' => max(0, $limite_mensuelle_principale - $used_principale),
|
|
'type' => 'principale',
|
|
];
|
|
|
|
$result['total'] += $limite_mensuelle_principale;
|
|
$result['total_used'] += $used_principale;
|
|
|
|
// Si la capacité a une exception
|
|
if ($capacite->ajouter_exception && !empty($capacite->exceptions_disponibilites)) {
|
|
$exceptions = $capacite->exceptions_disponibilites;
|
|
|
|
if (!empty($exceptions['frequence']) && !empty($exceptions['frequence_periode']) && !empty($exceptions['periode'])) {
|
|
// Calculer la limite mensuelle pour l'exception
|
|
$limite_mensuelle_exception = self::convertToMonthlyLimit(
|
|
(int) $exceptions['frequence'],
|
|
$exceptions['frequence_periode'],
|
|
$nb_semaines
|
|
);
|
|
|
|
// Compter les événements utilisés pour l'exception
|
|
$temp_capacite = clone $capacite;
|
|
$temp_capacite->periode = $exceptions['periode'];
|
|
$used_exception = self::countUsed($capacite->id, $date, $temp_capacite);
|
|
|
|
// Ajouter l'exception
|
|
$periode_exception = $exceptions['periode'];
|
|
$result['by_periode'][$periode_exception][] = [
|
|
'capacite_id' => $capacite->id,
|
|
'capacite_title' => \get_the_title($capacite->id) . ' (Exception)',
|
|
'jour' => $capacite->jour,
|
|
'limite' => $limite_mensuelle_exception,
|
|
'used' => $used_exception,
|
|
'available' => max(0, $limite_mensuelle_exception - $used_exception),
|
|
'type' => 'exception',
|
|
];
|
|
|
|
$result['total'] += $limite_mensuelle_exception;
|
|
$result['total_used'] += $used_exception;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Calculer le total disponible
|
|
$result['total_available'] = max(0, $result['total'] - $result['total_used']);
|
|
|
|
// Calculer les totaux par période
|
|
foreach ($result['by_periode'] as $periode => &$items) {
|
|
$periode_total = 0;
|
|
$periode_used = 0;
|
|
$periode_available = 0;
|
|
|
|
foreach ($items as $item) {
|
|
$periode_total += $item['limite'];
|
|
$periode_used += $item['used'];
|
|
$periode_available += $item['available'];
|
|
}
|
|
|
|
// Ajouter un résumé pour cette période
|
|
$result['by_periode'][$periode . '_summary'] = [
|
|
'total' => $periode_total,
|
|
'used' => $periode_used,
|
|
'available' => $periode_available,
|
|
];
|
|
}
|
|
|
|
// Mettre en cache le résultat (2 jours)
|
|
if ($use_cache) {
|
|
\set_transient($cache_key, $result, 2 * \DAY_IN_SECONDS);
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Convertit une limite (semaine ou mois) en limite mensuelle
|
|
*
|
|
* @param int $limite Limite originale
|
|
* @param string $limite_par 'semaine' ou 'mois'
|
|
* @param int $nb_semaines Nombre de semaines dans le mois
|
|
* @return int Limite mensuelle
|
|
*/
|
|
private static function convertToMonthlyLimit(int $limite, string $limite_par, int $nb_semaines): int {
|
|
if ($limite_par === 'semaine') {
|
|
// Convertir hebdomadaire en mensuel
|
|
return $limite * $nb_semaines;
|
|
}
|
|
|
|
// Déjà mensuel
|
|
return $limite;
|
|
}
|
|
|
|
/**
|
|
* Récupère un résumé formaté des disponibilités par langue
|
|
* Version simplifiée pour affichage rapide
|
|
*
|
|
* @param int $langue_term_id ID du terme de la taxonomie langue
|
|
* @param string|null $date Date de référence (Y-m-d). Si null, utilise la date actuelle
|
|
* @param bool $use_cache Utiliser le cache transient (par défaut: true)
|
|
* @return array ['matin' => 'X disponibles / Y total', 'apres_midi' => '...', 'journee' => '...', 'total' => '...']
|
|
*/
|
|
public static function getDisponibilitesSummaryByLangue(int $langue_term_id, ?string $date = null, bool $use_cache = true): array {
|
|
$dispos = self::getDisponibilitesByLangue($langue_term_id, $date, $use_cache);
|
|
|
|
$summary = [];
|
|
|
|
foreach (['matin', 'apres_midi', 'journee'] as $periode) {
|
|
if (isset($dispos['by_periode'][$periode . '_summary'])) {
|
|
$data = $dispos['by_periode'][$periode . '_summary'];
|
|
|
|
if ($data['total'] > 0) {
|
|
$summary[$periode] = sprintf(
|
|
'%d disponibles / %d total (%d utilisés)',
|
|
$data['available'],
|
|
$data['total'],
|
|
$data['used']
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
$summary['total'] = sprintf(
|
|
'%d disponibles / %d total (%d utilisés)',
|
|
$dispos['total_available'],
|
|
$dispos['total'],
|
|
$dispos['total_used']
|
|
);
|
|
|
|
return $summary;
|
|
}
|
|
|
|
/**
|
|
* Récupère toutes les langues avec leurs disponibilités
|
|
* Utile pour afficher un tableau récapitulatif
|
|
*
|
|
* @param string|null $date Date de référence (Y-m-d). Si null, utilise la date actuelle
|
|
* @param bool $use_cache Utiliser le cache transient (par défaut: true)
|
|
* @return array ['langue_slug' => ['name' => '...', 'total' => X, 'available' => Y, ...]]
|
|
*/
|
|
public static function getAllLanguesDisponibilites(?string $date = null, bool $use_cache = true): array {
|
|
if (!$date) {
|
|
$date = \current_time('Y-m-d');
|
|
}
|
|
|
|
// Générer une clé de cache unique
|
|
$cache_key = 'crvi_all_langues_dispos_' . $date;
|
|
|
|
// Essayer de récupérer depuis le cache
|
|
if ($use_cache) {
|
|
$cached = \get_transient($cache_key);
|
|
if ($cached !== false) {
|
|
return $cached;
|
|
}
|
|
}
|
|
|
|
// Récupérer toutes les langues utilisées dans les capacités actives
|
|
$capacites = self::getActiveCapacites([], false);
|
|
|
|
$langues_ids = [];
|
|
foreach ($capacites as $capacite) {
|
|
$capacite_langues = is_array($capacite->langue) ? $capacite->langue : [$capacite->langue];
|
|
$langues_ids = array_merge($langues_ids, $capacite_langues);
|
|
}
|
|
|
|
$langues_ids = array_unique($langues_ids);
|
|
|
|
$result = [];
|
|
|
|
foreach ($langues_ids as $langue_id) {
|
|
$langue_term = \get_term($langue_id, 'langue');
|
|
if ($langue_term && !\is_wp_error($langue_term)) {
|
|
// Utiliser le cache pour chaque langue
|
|
$dispos = self::getDisponibilitesByLangue($langue_id, $date, $use_cache);
|
|
|
|
$result[$langue_term->slug] = [
|
|
'id' => $langue_id,
|
|
'name' => $langue_term->name,
|
|
'slug' => $langue_term->slug,
|
|
'total' => $dispos['total'],
|
|
'used' => $dispos['total_used'],
|
|
'available' => $dispos['total_available'],
|
|
'by_periode' => [
|
|
'matin' => $dispos['by_periode']['matin_summary'] ?? ['total' => 0, 'used' => 0, 'available' => 0],
|
|
'apres_midi' => $dispos['by_periode']['apres_midi_summary'] ?? ['total' => 0, 'used' => 0, 'available' => 0],
|
|
'journee' => $dispos['by_periode']['journee_summary'] ?? ['total' => 0, 'used' => 0, 'available' => 0],
|
|
],
|
|
'details' => $dispos['by_periode'],
|
|
];
|
|
}
|
|
}
|
|
|
|
// Trier par nom de langue
|
|
uasort($result, function($a, $b) {
|
|
return strcmp($a['name'], $b['name']);
|
|
});
|
|
|
|
// Mettre en cache le résultat global (2 jours)
|
|
if ($use_cache) {
|
|
\set_transient($cache_key, $result, 2 * \DAY_IN_SECONDS);
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Récupère les disponibilités par langue avec ventilation par jour
|
|
* Structure : langue -> total, remaining, by_periode (am/pm avec total/remaining), by_jour (lundi->dimanche avec total/remaining)
|
|
*
|
|
* @param int $langue_term_id ID du terme de la taxonomie langue
|
|
* @param string|null $date Date de référence (Y-m-d). Si null, utilise la date actuelle
|
|
* @param bool $use_cache Utiliser le cache transient (par défaut: true)
|
|
* @return array ['total' => int, 'remaining' => int, 'by_periode' => [...], 'by_jour' => [...]]
|
|
*/
|
|
public static function getDisponibilitesByLangueWithJours(int $langue_term_id, ?string $date = null, bool $use_cache = true): array {
|
|
if (!$date) {
|
|
$date = \current_time('Y-m-d');
|
|
}
|
|
|
|
// Générer une clé de cache unique
|
|
$cache_key = 'crvi_dispos_langue_jours_' . $langue_term_id . '_' . $date;
|
|
|
|
// Essayer de récupérer depuis le cache
|
|
if ($use_cache) {
|
|
$cached = \get_transient($cache_key);
|
|
if ($cached !== false) {
|
|
return $cached;
|
|
}
|
|
}
|
|
|
|
// Obtenir les disponibilités globales par langue
|
|
$dispos = self::getDisponibilitesByLangue($langue_term_id, $date, $use_cache);
|
|
|
|
// Initialiser la structure de résultat
|
|
$result = [
|
|
'total' => $dispos['total'],
|
|
'remaining' => $dispos['total_available'],
|
|
'by_periode' => [
|
|
'matin' => [
|
|
'total' => $dispos['by_periode']['matin_summary']['total'] ?? 0,
|
|
'remaining' => $dispos['by_periode']['matin_summary']['available'] ?? 0,
|
|
],
|
|
'apres_midi' => [
|
|
'total' => $dispos['by_periode']['apres_midi_summary']['total'] ?? 0,
|
|
'remaining' => $dispos['by_periode']['apres_midi_summary']['available'] ?? 0,
|
|
],
|
|
],
|
|
'by_jour' => [
|
|
'lundi' => ['total' => 0, 'remaining' => 0],
|
|
'mardi' => ['total' => 0, 'remaining' => 0],
|
|
'mercredi' => ['total' => 0, 'remaining' => 0],
|
|
'jeudi' => ['total' => 0, 'remaining' => 0],
|
|
'vendredi' => ['total' => 0, 'remaining' => 0],
|
|
'samedi' => ['total' => 0, 'remaining' => 0],
|
|
'dimanche' => ['total' => 0, 'remaining' => 0],
|
|
],
|
|
];
|
|
|
|
// Récupérer toutes les capacités actives pour cette langue
|
|
$capacites = self::getActiveCapacites(['langue' => $langue_term_id], false);
|
|
|
|
if (empty($capacites)) {
|
|
return $result;
|
|
}
|
|
|
|
$date_obj = new \DateTime($date);
|
|
$nb_semaines = self::getNbSemainesDansMois($date_obj);
|
|
|
|
// Parcourir chaque capacité et ventiler par jour
|
|
foreach ($capacites as $capacite) {
|
|
// Vérifier que la langue correspond
|
|
$capacite_langues = is_array($capacite->langue) ? $capacite->langue : [$capacite->langue];
|
|
if (!in_array($langue_term_id, $capacite_langues)) {
|
|
continue;
|
|
}
|
|
|
|
// Si un jour est spécifié, ajouter la capacité à ce jour
|
|
if (!empty($capacite->jour) && isset($result['by_jour'][$capacite->jour])) {
|
|
// Calculer la limite mensuelle
|
|
$limite_mensuelle = self::convertToMonthlyLimit(
|
|
$capacite->limite,
|
|
$capacite->limite_par,
|
|
$nb_semaines
|
|
);
|
|
|
|
// Compter les événements utilisés
|
|
$used = self::countUsed($capacite->id, $date, $capacite);
|
|
|
|
// Ajouter au total du jour
|
|
$result['by_jour'][$capacite->jour]['total'] += $limite_mensuelle;
|
|
$result['by_jour'][$capacite->jour]['remaining'] += max(0, $limite_mensuelle - $used);
|
|
|
|
// Si la capacité a une exception avec un jour différent, l'ajouter aussi
|
|
if ($capacite->ajouter_exception && !empty($capacite->exceptions_disponibilites)) {
|
|
$exceptions = $capacite->exceptions_disponibilites;
|
|
|
|
if (!empty($exceptions['frequence']) && !empty($exceptions['frequence_periode'])) {
|
|
$limite_exception = self::convertToMonthlyLimit(
|
|
(int) $exceptions['frequence'],
|
|
$exceptions['frequence_periode'],
|
|
$nb_semaines
|
|
);
|
|
|
|
// Compter les événements pour l'exception
|
|
$temp_capacite = clone $capacite;
|
|
if (!empty($exceptions['periode'])) {
|
|
$temp_capacite->periode = $exceptions['periode'];
|
|
}
|
|
$used_exception = self::countUsed($capacite->id, $date, $temp_capacite);
|
|
|
|
// Ajouter au total du jour
|
|
$result['by_jour'][$capacite->jour]['total'] += $limite_exception;
|
|
$result['by_jour'][$capacite->jour]['remaining'] += max(0, $limite_exception - $used_exception);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Mettre en cache le résultat (2 jours)
|
|
if ($use_cache) {
|
|
\set_transient($cache_key, $result, 2 * \DAY_IN_SECONDS);
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Récupère toutes les langues avec leurs capacités détaillées (périodes et jours)
|
|
* Format optimisé pour crviACFData JavaScript
|
|
*
|
|
* @param string|null $date_debut Date de début (Y-m-d). Si null, utilise le début du mois courant
|
|
* @param string|null $date_fin Date de fin (Y-m-d). Si null, utilise la fin du mois courant
|
|
* @param bool $use_cache Utiliser le cache transient (par défaut: true)
|
|
* @return array ['langue_slug' => ['id' => X, 'name' => '...', 'total' => X, 'remaining' => Y, 'by_periode' => [...], 'by_jour' => [...]]]
|
|
*/
|
|
public static function getAllLanguesCapacitesForACF(?string $date_debut = null, ?string $date_fin = null, bool $use_cache = true): array {
|
|
// Si aucune date n'est fournie, utiliser le mois courant
|
|
if (!$date_debut || !$date_fin) {
|
|
$date_actuelle = \current_time('Y-m-d');
|
|
$date_obj = new \DateTime($date_actuelle);
|
|
|
|
if (!$date_debut) {
|
|
$date_debut = $date_obj->format('Y-m-01'); // Premier jour du mois
|
|
}
|
|
if (!$date_fin) {
|
|
$date_fin = $date_obj->format('Y-m-t'); // Dernier jour du mois
|
|
}
|
|
}
|
|
|
|
// Générer une clé de cache unique basée sur la plage de dates
|
|
$cache_key = 'crvi_langues_capacites_acf_' . $date_debut . '_' . $date_fin;
|
|
|
|
// Essayer de récupérer depuis le cache
|
|
if ($use_cache) {
|
|
$cached = \get_transient($cache_key);
|
|
if ($cached !== false) {
|
|
return $cached;
|
|
}
|
|
}
|
|
|
|
// Récupérer uniquement les termes de la taxonomie 'langue' qui sont liés à des posts 'traduction_langue' actifs
|
|
$langues_terms = \get_terms([
|
|
'taxonomy' => 'langue',
|
|
'hide_empty' => true, // Important: ne récupérer que les termes utilisés
|
|
'object_ids' => \get_posts([
|
|
'post_type' => 'traduction_langue',
|
|
'post_status' => 'publish',
|
|
'numberposts' => -1,
|
|
'fields' => 'ids'
|
|
])
|
|
]);
|
|
|
|
if (\is_wp_error($langues_terms) || empty($langues_terms)) {
|
|
// Mettre en cache un résultat vide
|
|
if ($use_cache) {
|
|
\set_transient($cache_key, [], 1 * \DAY_IN_SECONDS);
|
|
}
|
|
return [];
|
|
}
|
|
|
|
$result = [];
|
|
|
|
foreach ($langues_terms as $langue_term) {
|
|
// Vérifier qu'il existe au moins une capacité active pour cette langue
|
|
$capacites = self::getActiveCapacites(['langue' => $langue_term->term_id], false);
|
|
|
|
if (empty($capacites)) {
|
|
// Pas de capacité active pour cette langue, on saute
|
|
continue;
|
|
}
|
|
|
|
// Obtenir les disponibilités avec ventilation par jour
|
|
// Utiliser la date de début pour le calcul (les méthodes sous-jacentes calculent déjà le mois complet)
|
|
$dispos = self::getDisponibilitesByLangueWithJours($langue_term->term_id, $date_debut, $use_cache);
|
|
|
|
$result[$langue_term->slug] = [
|
|
'id' => $langue_term->term_id,
|
|
'name' => $langue_term->name,
|
|
'slug' => $langue_term->slug,
|
|
'total' => $dispos['total'],
|
|
'remaining' => $dispos['remaining'],
|
|
'by_periode' => $dispos['by_periode'],
|
|
'by_jour' => $dispos['by_jour'],
|
|
];
|
|
}
|
|
|
|
// Trier par nom de langue
|
|
uasort($result, function($a, $b) {
|
|
return strcmp($a['name'], $b['name']);
|
|
});
|
|
|
|
// Mettre en cache le résultat (1 jour)
|
|
if ($use_cache) {
|
|
\set_transient($cache_key, $result, 1 * \DAY_IN_SECONDS);
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Invalide le cache pour une capacité donnée
|
|
* À appeler lors de la création/modification/suppression d'événements ou de capacités
|
|
* @param int|null $capacite_id ID de la capacité (optionnel, invalide tout si null)
|
|
*/
|
|
public static function invalidate_cache(?int $capacite_id = null): void {
|
|
global $wpdb;
|
|
|
|
if ($capacite_id) {
|
|
// Invalider le cache de la capacité spécifique
|
|
$capacite = self::load($capacite_id);
|
|
if ($capacite) {
|
|
// Invalider le cache pour toutes les langues de cette capacité
|
|
$langue_ids = is_array($capacite->langue) ? $capacite->langue : [$capacite->langue];
|
|
|
|
foreach ($langue_ids as $langue_id) {
|
|
// Récupérer toutes les clés de transient pour cette langue
|
|
// Format: crvi_dispos_langue_{langue_id}_{date}
|
|
$wpdb->query(
|
|
$wpdb->prepare(
|
|
"DELETE FROM {$wpdb->options}
|
|
WHERE option_name LIKE %s
|
|
OR option_name LIKE %s
|
|
OR option_name LIKE %s
|
|
OR option_name LIKE %s",
|
|
'_transient_crvi_dispos_langue_' . $langue_id . '_%',
|
|
'_transient_timeout_crvi_dispos_langue_' . $langue_id . '_%',
|
|
'_transient_crvi_dispos_langue_jours_' . $langue_id . '_%',
|
|
'_transient_timeout_crvi_dispos_langue_jours_' . $langue_id . '_%'
|
|
)
|
|
);
|
|
}
|
|
|
|
// Invalider aussi le cache global de toutes les langues
|
|
$wpdb->query(
|
|
"DELETE FROM {$wpdb->options}
|
|
WHERE option_name LIKE '_transient_crvi_all_langues_dispos_%'
|
|
OR option_name LIKE '_transient_timeout_crvi_all_langues_dispos_%'
|
|
OR option_name LIKE '_transient_crvi_langues_capacites_acf_%'
|
|
OR option_name LIKE '_transient_timeout_crvi_langues_capacites_acf_%'"
|
|
);
|
|
}
|
|
|
|
// Invalider le cache général des capacités
|
|
\delete_transient('crvi_capacites_actives_' . md5(serialize([])));
|
|
} else {
|
|
// Invalider tous les caches de disponibilités
|
|
$wpdb->query(
|
|
"DELETE FROM {$wpdb->options}
|
|
WHERE option_name LIKE '_transient_crvi_dispos_langue_%'
|
|
OR option_name LIKE '_transient_timeout_crvi_dispos_langue_%'
|
|
OR option_name LIKE '_transient_crvi_all_langues_dispos_%'
|
|
OR option_name LIKE '_transient_timeout_crvi_all_langues_dispos_%'
|
|
OR option_name LIKE '_transient_crvi_count_used_%'
|
|
OR option_name LIKE '_transient_timeout_crvi_count_used_%'
|
|
OR option_name LIKE '_transient_crvi_langues_capacites_acf_%'
|
|
OR option_name LIKE '_transient_timeout_crvi_langues_capacites_acf_%'"
|
|
);
|
|
|
|
// Invalider le cache général des capacités
|
|
\delete_transient('crvi_capacites_actives_' . md5(serialize([])));
|
|
}
|
|
}
|
|
}
|