Ajout des commentaires dans le code
This commit is contained in:
@@ -8,6 +8,19 @@ use DateTimeImmutable;
|
||||
use PDO;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Référentiel des agents et lecture de leurs créneaux, tous lieux confondus.
|
||||
*
|
||||
* Deux notions à ne pas confondre :
|
||||
* - `agent.id_structure` est le lieu de rattachement PAR DÉFAUT. Il ne limite
|
||||
* pas où l'agent peut être planifié ;
|
||||
* - `agent_structure` est l'HISTORIQUE des rattachements, avec date de début et
|
||||
* de fin. Il est tenu à jour par updateProfile() et updateAssignment().
|
||||
*
|
||||
* Créer un agent ne se limite pas à insérer une ligne : createWithDefaults()
|
||||
* ouvre aussi son quota annuel et son PTA. Un agent sans quota afficherait un
|
||||
* dépassement dès sa première heure planifiée.
|
||||
*/
|
||||
final class AgentModel extends BaseModel
|
||||
{
|
||||
/** Enveloppe annuelle complémentaire de formation : 14 h. */
|
||||
@@ -33,6 +46,18 @@ final class AgentModel extends BaseModel
|
||||
return self::hasTrainingRight($agent) ? self::ENVELOPPE_FORMATION_MINUTES : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Projection commune à toutes les lectures d'agent, complétée par la clause
|
||||
* WHERE de chaque appelant.
|
||||
*
|
||||
* Les LEFT JOIN sont volontaires : un agent peut n'avoir ni poste ni lieu de
|
||||
* rattachement, et doit rester listé dans ce cas.
|
||||
*
|
||||
* `structure_nom` et `structure_principale_nom` (idem pour le type) sont le
|
||||
* MÊME champ sous deux noms : des écrans plus anciens lisent l'un, les plus
|
||||
* récents l'autre. Ne pas retirer l'un des deux sans vérifier les vues et
|
||||
* les modules JS.
|
||||
*/
|
||||
private const AGENT_SELECT = <<<'SQL'
|
||||
SELECT a.id_agent, a.matricule, a.nom, a.prenom, a.email,
|
||||
a.telephone, a.adresse, a.est_diplome, a.diplome_libelle,
|
||||
@@ -52,6 +77,11 @@ final class AgentModel extends BaseModel
|
||||
LEFT JOIN structure s ON s.id_structure = a.id_structure
|
||||
SQL;
|
||||
|
||||
/**
|
||||
* Tous les agents actifs.
|
||||
*
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
public function allActive(): array
|
||||
{
|
||||
return $this->pdo->query(
|
||||
@@ -59,6 +89,15 @@ final class AgentModel extends BaseModel
|
||||
)->fetchAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Agents visibles depuis un lieu : ceux qui y sont RATTACHÉS, plus ceux qui
|
||||
* y ont un planning, quel que soit leur rattachement.
|
||||
*
|
||||
* C'est le périmètre d'un responsable de structure, aligné sur la vue
|
||||
* v_acces_agent_par_structure utilisée par Access::requireAgentView().
|
||||
*
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
public function allVisibleForStructure(int $structureId): array
|
||||
{
|
||||
$stmt = $this->pdo->prepare(
|
||||
@@ -82,6 +121,14 @@ final class AgentModel extends BaseModel
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Agents éligibles au PTA annuel : permanents et pourvus d'un poste.
|
||||
*
|
||||
* Les contrats temporaires en sont exclus, le PTA étant un engagement de
|
||||
* répartition sur l'année civile entière.
|
||||
*
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
public function allEligibleForPta(): array
|
||||
{
|
||||
return $this->pdo->query(
|
||||
@@ -93,6 +140,15 @@ final class AgentModel extends BaseModel
|
||||
)->fetchAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Référentiel des postes.
|
||||
*
|
||||
* Le poste porte les volumes forfaitaires de l'agent (heures des mercredis,
|
||||
* de l'extrascolaire) et ses autorisations (préparation, animation), qui
|
||||
* alimentent les calculs de PtaModel.
|
||||
*
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
public function allPosts(): array
|
||||
{
|
||||
return $this->pdo->query(
|
||||
@@ -105,6 +161,19 @@ final class AgentModel extends BaseModel
|
||||
)->fetchAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Agents du sélecteur de l'éditeur de planning.
|
||||
*
|
||||
* Renvoie TOUS les agents actifs, quel que soit le lieu : un agent peut être
|
||||
* planifié ailleurs que sur son rattachement. Le tri fait simplement
|
||||
* REMONTER EN TÊTE ceux rattachés au lieu sélectionné, sans masquer les
|
||||
* autres. `lieu_principal_selectionne` permet à l'interface de les distinguer
|
||||
* visuellement.
|
||||
*
|
||||
* @param int $selectedLocation 0 pour ne privilégier aucun lieu.
|
||||
*
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
public function listForLocation(int $selectedLocation = 0): array
|
||||
{
|
||||
$stmt = $this->pdo->prepare(
|
||||
@@ -123,6 +192,11 @@ final class AgentModel extends BaseModel
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fiche complète d'un agent actif.
|
||||
*
|
||||
* @return array<string, mixed>|null null si inexistant OU désactivé.
|
||||
*/
|
||||
public function findActive(int $agentId): ?array
|
||||
{
|
||||
$stmt = $this->pdo->prepare(
|
||||
@@ -132,6 +206,10 @@ final class AgentModel extends BaseModel
|
||||
return $stmt->fetch() ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vrai si le matricule est déjà pris, DÉSACTIVÉS COMPRIS : c'est la clé de
|
||||
* rapprochement avec les outils RH, elle reste unique en base.
|
||||
*/
|
||||
public function matriculeExists(string $matricule): bool
|
||||
{
|
||||
$stmt = $this->pdo->prepare('SELECT 1 FROM agent WHERE matricule = :matricule LIMIT 1');
|
||||
@@ -139,6 +217,25 @@ final class AgentModel extends BaseModel
|
||||
return $stmt->fetchColumn() !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Crée un agent et TOUT ce qui doit l'accompagner, en une transaction :
|
||||
* 1. la fiche `agent` ;
|
||||
* 2. sa première ligne d'historique `agent_structure` ;
|
||||
* 3. son quota annuel `quota_agent_annuel`, proratisé par sa quotité ;
|
||||
* 4. son PTA annuel `pta_annuel`, en BROUILLON.
|
||||
*
|
||||
* L'année retenue pour le quota et le PTA est celle du DÉBUT DE CONTRAT,
|
||||
* mais la période court du 1er janvier au 31 décembre : le PTA suit l'année
|
||||
* civile, jamais la date anniversaire du contrat.
|
||||
*
|
||||
* ATTENTION : `id_pta` du retour vaut lastInsertId() APRÈS l'insertion du
|
||||
* PTA, donc bien l'identifiant du PTA — et non celui de l'agent, qui a été
|
||||
* capturé plus haut dans $agentId.
|
||||
*
|
||||
* @param array<string, mixed> $data Champs déjà validés par le contrôleur.
|
||||
*
|
||||
* @return array{id_agent: int, annee_quota: int, date_debut_contrat: string, date_fin_contrat: string|null, quota_cible_minutes: int, id_pta: int}
|
||||
*/
|
||||
public function createWithDefaults(array $data): array
|
||||
{
|
||||
$contractStart = new DateTimeImmutable((string) $data['date_debut_contrat']);
|
||||
@@ -260,6 +357,28 @@ final class AgentModel extends BaseModel
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour la fiche d'un agent et, si son rattachement change, son
|
||||
* historique `agent_structure`.
|
||||
*
|
||||
* L'historique n'est touché QUE si le lieu change réellement, sans quoi
|
||||
* chaque enregistrement de la fiche clôturerait puis rouvrirait une ligne,
|
||||
* rendant l'historique illisible.
|
||||
*
|
||||
* La clôture pose `date_fin` à aujourd'hui, sauf pour un rattachement dont
|
||||
* la date de début est dans le FUTUR : on referme alors sur cette date de
|
||||
* début, pour ne pas produire une période qui finit avant de commencer.
|
||||
*
|
||||
* Le ON DUPLICATE KEY UPDATE gère le retour sur un lieu déjà occupé par le
|
||||
* passé : la ligne existante est réactivée plutôt que dupliquée.
|
||||
*
|
||||
* bindValue() explicite ici (et non un tableau passé à execute()) car
|
||||
* `structure_id` peut être NULL et exige PDO::PARAM_NULL.
|
||||
*
|
||||
* @param array<string, mixed> $data Champs déjà validés par le contrôleur.
|
||||
*
|
||||
* @throws \RuntimeException Si l'agent n'existe pas.
|
||||
*/
|
||||
public function updateProfile(int $agentId, array $data): void
|
||||
{
|
||||
try {
|
||||
@@ -340,6 +459,15 @@ final class AgentModel extends BaseModel
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change le seul rattachement principal, sans toucher au reste de la fiche.
|
||||
*
|
||||
* Même gestion d'historique qu'updateProfile(), à ceci près que l'historique
|
||||
* est réécrit INCONDITIONNELLEMENT, y compris si le lieu ne change pas.
|
||||
*
|
||||
* @param int|null $structureId null pour détacher l'agent de tout lieu par
|
||||
* défaut — il reste planifiable ponctuellement.
|
||||
*/
|
||||
public function updateAssignment(int $agentId, ?int $structureId): void
|
||||
{
|
||||
try {
|
||||
@@ -383,6 +511,14 @@ final class AgentModel extends BaseModel
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Créneaux d'un agent sur une semaine ISO, TOUS LIEUX confondus.
|
||||
*
|
||||
* Filtre sur le NUMÉRO DE SEMAINE du planning : c'est la vue « semaine »
|
||||
* telle que l'utilisateur la conçoit.
|
||||
*
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
public function entriesForWeek(int $agentId, int $year, int $week): array
|
||||
{
|
||||
$stmt = $this->pdo->prepare(
|
||||
@@ -407,6 +543,15 @@ final class AgentModel extends BaseModel
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Créneaux d'un agent sur un intervalle de DATES, tous lieux confondus.
|
||||
*
|
||||
* Filtre sur `c.date_jour` et non sur la semaine du planning : indispensable
|
||||
* pour les grilles mensuelles et les PDF multi-semaines, qui débordent des
|
||||
* bornes de semaine.
|
||||
*
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
public function entriesForDateRange(int $agentId, string $startDate, string $endDate): array
|
||||
{
|
||||
$stmt = $this->pdo->prepare(
|
||||
@@ -433,6 +578,18 @@ final class AgentModel extends BaseModel
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cumul des minutes par motif sur une période — base du bilan d'heures PDF.
|
||||
*
|
||||
* Trois totaux par motif : global, validé, brouillon. Le regroupement porte
|
||||
* sur `UPPER(m.code)` pour qu'une variation de casse en base ne scinde pas
|
||||
* un motif en deux lignes.
|
||||
*
|
||||
* Le tri place `compte_dans_quota` en tête : le temps qui pèse sur le quota
|
||||
* est ce que le lecteur cherche en premier.
|
||||
*
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
public function timeSummaryForRange(int $agentId, string $startDate, string $endDate): array
|
||||
{
|
||||
$stmt = $this->pdo->prepare(
|
||||
@@ -458,6 +615,11 @@ final class AgentModel extends BaseModel
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cumul par motif sur une année civile complète.
|
||||
*
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
public function timeSummaryForYear(int $agentId, int $year): array
|
||||
{
|
||||
return $this->timeSummaryForRange($agentId, sprintf('%04d-01-01', $year), sprintf('%04d-12-31', $year));
|
||||
|
||||
Reference in New Issue
Block a user