Files
pta/app/Models/AgentModel.php
Loic Masi 5fbf76868f pour prod
2026-08-07 16:13:43 +02:00

441 lines
19 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Models;
use DateTimeImmutable;
use PDO;
use Throwable;
final class AgentModel extends BaseModel
{
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,
a.date_debut_contrat, a.date_fin_contrat, a.type_contrat,
a.formation_repartition_annuelle, a.commentaire_pta,
a.id_poste, po.code AS poste_code, po.libelle AS poste_libelle,
po.famille AS poste_famille, po.heures_mercredi_minutes,
po.heures_extrascolaire_minutes, po.autorise_preparation,
po.peut_assurer_animation, po.preparation_lundi_si_100,
a.id_structure,
s.nom AS structure_nom,
s.nom AS structure_principale_nom,
s.type_affectation AS structure_type_affectation,
s.type_affectation AS structure_principale_type_affectation
FROM agent a
LEFT JOIN poste_agent po ON po.id_poste = a.id_poste
LEFT JOIN structure s ON s.id_structure = a.id_structure
SQL;
public function allActive(): array
{
return $this->pdo->query(
self::AGENT_SELECT . " WHERE a.actif = TRUE ORDER BY a.nom, a.prenom"
)->fetchAll();
}
public function allVisibleForStructure(int $structureId): array
{
$stmt = $this->pdo->prepare(
self::AGENT_SELECT . "
WHERE a.actif = TRUE
AND (
a.id_structure = :structure_id_default
OR EXISTS (
SELECT 1
FROM planning p
WHERE p.id_agent = a.id_agent
AND p.id_structure = :structure_id_planning
)
)
ORDER BY a.nom, a.prenom"
);
$stmt->execute([
'structure_id_default' => $structureId,
'structure_id_planning' => $structureId,
]);
return $stmt->fetchAll();
}
public function allEligibleForPta(): array
{
return $this->pdo->query(
self::AGENT_SELECT . "
WHERE a.actif = TRUE
AND a.type_contrat = 'PERMANENT'
AND a.id_poste IS NOT NULL
ORDER BY a.nom, a.prenom"
)->fetchAll();
}
public function allPosts(): array
{
return $this->pdo->query(
"SELECT id_poste, code, libelle, famille, heures_mercredi_minutes,
heures_extrascolaire_minutes, autorise_preparation,
peut_assurer_animation, preparation_lundi_si_100
FROM poste_agent
WHERE actif = TRUE
ORDER BY ordre_affichage, libelle"
)->fetchAll();
}
public function listForLocation(int $selectedLocation = 0): array
{
$stmt = $this->pdo->prepare(
"SELECT a.id_agent, a.matricule, a.nom, a.prenom, a.id_structure,
a.id_poste, po.libelle AS poste_libelle,
s.nom AS structure_nom,
s.type_affectation AS structure_type_affectation,
CASE WHEN a.id_structure = :selected_location THEN 1 ELSE 0 END AS lieu_principal_selectionne
FROM agent a
LEFT JOIN poste_agent po ON po.id_poste = a.id_poste
LEFT JOIN structure s ON s.id_structure = a.id_structure
WHERE a.actif = TRUE
ORDER BY lieu_principal_selectionne DESC, a.nom, a.prenom"
);
$stmt->execute(['selected_location' => $selectedLocation]);
return $stmt->fetchAll();
}
public function findActive(int $agentId): ?array
{
$stmt = $this->pdo->prepare(
self::AGENT_SELECT . " WHERE a.id_agent = :id AND a.actif = TRUE LIMIT 1"
);
$stmt->execute(['id' => $agentId]);
return $stmt->fetch() ?: null;
}
public function matriculeExists(string $matricule): bool
{
$stmt = $this->pdo->prepare('SELECT 1 FROM agent WHERE matricule = :matricule LIMIT 1');
$stmt->execute(['matricule' => $matricule]);
return $stmt->fetchColumn() !== false;
}
public function createWithDefaults(array $data): array
{
$contractStart = new DateTimeImmutable((string) $data['date_debut_contrat']);
$contractEnd = !empty($data['date_fin_contrat'])
? new DateTimeImmutable((string) $data['date_fin_contrat'])
: null;
$year = (int) $contractStart->format('Y');
// Le quota PTA est suivi sur l'année civile, indépendamment de la date
// anniversaire du contrat. La date de contrat reste une donnée RH.
$periodStart = new DateTimeImmutable(sprintf('%04d-01-01', $year));
$periodEnd = new DateTimeImmutable(sprintf('%04d-12-31', $year));
$quotaReference = QuotaModel::REFERENCE_MINUTES;
$quotaTarget = (int) round($quotaReference * ((float) $data['quotite_travail'] / 100));
$trainingEnvelope = !empty($data['formation_repartition_annuelle']) ? 0 : 840;
try {
$this->pdo->beginTransaction();
$insertAgent = $this->pdo->prepare(
'INSERT INTO agent (
matricule, nom, prenom, email, telephone, adresse,
est_diplome, diplome_libelle, date_debut_contrat,
id_poste, type_contrat, date_fin_contrat,
formation_repartition_annuelle, commentaire_pta,
id_structure, actif
) VALUES (
:matricule, :nom, :prenom, :email, :telephone, :adresse,
:est_diplome, :diplome_libelle, :date_debut_contrat,
:id_poste, :type_contrat, :date_fin_contrat,
:formation_repartition_annuelle, :commentaire_pta,
:structure_id, TRUE
)'
);
$insertAgent->execute([
'matricule' => $data['matricule'],
'nom' => $data['nom'],
'prenom' => $data['prenom'],
'email' => $data['email'],
'telephone' => $data['telephone'],
'adresse' => $data['adresse'],
'est_diplome' => $data['est_diplome'] ? 1 : 0,
'diplome_libelle' => $data['est_diplome'] ? $data['diplome_libelle'] : null,
'date_debut_contrat' => $contractStart->format('Y-m-d'),
'id_poste' => $data['id_poste'],
'type_contrat' => $data['type_contrat'],
'date_fin_contrat' => $contractEnd?->format('Y-m-d'),
'formation_repartition_annuelle' => $data['formation_repartition_annuelle'] ? 1 : 0,
'commentaire_pta' => $data['commentaire_pta'],
'structure_id' => $data['structure_id'],
]);
$agentId = (int) $this->pdo->lastInsertId();
$history = $this->pdo->prepare(
'INSERT INTO agent_structure (id_agent, id_structure, date_debut, date_fin, actif)
VALUES (:agent_id, :structure_id, :date_debut, NULL, TRUE)'
);
$history->execute([
'agent_id' => $agentId,
'structure_id' => $data['structure_id'],
'date_debut' => $contractStart->format('Y-m-d'),
]);
$quota = $this->pdo->prepare(
'INSERT INTO quota_agent_annuel (
id_agent, annee, quotite_travail, quota_reference_minutes,
quota_cible_minutes, commentaire
) VALUES (
:agent_id, :annee, :quotite, :quota_reference,
:quota_cible, :commentaire
)'
);
$quota->execute([
'agent_id' => $agentId,
'annee' => $year,
'quotite' => $data['quotite_travail'],
'quota_reference' => $quotaReference,
'quota_cible' => $quotaTarget,
'commentaire' => 'Quota PTA de référence - année civile',
]);
$pta = $this->pdo->prepare(
'INSERT INTO pta_annuel (
id_agent, date_debut, date_fin, quotite_travail,
quota_reference_minutes, quota_cible_minutes,
enveloppe_formation_minutes, statut
) VALUES (
:agent_id, :date_debut, :date_fin, :quotite,
:quota_reference, :quota_cible,
:formation, \'BROUILLON\'
)'
);
$pta->execute([
'agent_id' => $agentId,
'date_debut' => $periodStart->format('Y-m-d'),
'date_fin' => $periodEnd->format('Y-m-d'),
'quotite' => $data['quotite_travail'],
'quota_reference' => $quotaReference,
'quota_cible' => $quotaTarget,
'formation' => $trainingEnvelope,
]);
$this->pdo->commit();
return [
'id_agent' => $agentId,
'annee_quota' => $year,
'date_debut_contrat' => $contractStart->format('Y-m-d'),
'date_fin_contrat' => $contractEnd?->format('Y-m-d'),
'quota_cible_minutes' => $quotaTarget,
'id_pta' => (int) $this->pdo->lastInsertId(),
];
} catch (Throwable $e) {
if ($this->pdo->inTransaction()) {
$this->pdo->rollBack();
}
throw $e;
}
}
public function updateProfile(int $agentId, array $data): void
{
try {
$this->pdo->beginTransaction();
$currentStmt = $this->pdo->prepare('SELECT id_structure FROM agent WHERE id_agent = :agent_id FOR UPDATE');
$currentStmt->execute(['agent_id' => $agentId]);
$currentStructure = $currentStmt->fetchColumn();
if ($currentStructure === false) {
throw new \RuntimeException('Agent introuvable.');
}
$currentStructureId = $currentStructure !== null ? (int) $currentStructure : null;
$newStructureId = $data['structure_id'];
$update = $this->pdo->prepare(
'UPDATE agent
SET email = :email,
telephone = :telephone,
adresse = :adresse,
est_diplome = :est_diplome,
diplome_libelle = :diplome_libelle,
date_debut_contrat = :date_debut_contrat,
id_poste = :id_poste,
type_contrat = :type_contrat,
date_fin_contrat = :date_fin_contrat,
formation_repartition_annuelle = :formation_repartition_annuelle,
commentaire_pta = :commentaire_pta,
id_structure = :structure_id
WHERE id_agent = :agent_id'
);
$update->bindValue(':agent_id', $agentId, PDO::PARAM_INT);
$update->bindValue(':email', $data['email']);
$update->bindValue(':telephone', $data['telephone']);
$update->bindValue(':adresse', $data['adresse']);
$update->bindValue(':est_diplome', $data['est_diplome'] ? 1 : 0, PDO::PARAM_INT);
$update->bindValue(':diplome_libelle', $data['est_diplome'] ? $data['diplome_libelle'] : null);
$update->bindValue(':date_debut_contrat', $data['date_debut_contrat']);
$update->bindValue(':id_poste', $data['id_poste'], PDO::PARAM_INT);
$update->bindValue(':type_contrat', $data['type_contrat']);
$update->bindValue(':date_fin_contrat', $data['date_fin_contrat']);
$update->bindValue(':formation_repartition_annuelle', $data['formation_repartition_annuelle'] ? 1 : 0, PDO::PARAM_INT);
$update->bindValue(':commentaire_pta', $data['commentaire_pta']);
if ($newStructureId === null) {
$update->bindValue(':structure_id', null, PDO::PARAM_NULL);
} else {
$update->bindValue(':structure_id', $newStructureId, PDO::PARAM_INT);
}
$update->execute();
if ($currentStructureId !== $newStructureId) {
$close = $this->pdo->prepare(
"UPDATE agent_structure
SET actif = FALSE,
date_fin = CASE
WHEN date_debut IS NOT NULL AND date_debut > CURRENT_DATE THEN date_debut
ELSE CURRENT_DATE
END
WHERE id_agent = :agent_id AND actif = TRUE"
);
$close->execute(['agent_id' => $agentId]);
if ($newStructureId !== null) {
$history = $this->pdo->prepare(
"INSERT INTO agent_structure (id_agent, id_structure, date_debut, date_fin, actif)
VALUES (:agent_id, :structure_id, CURRENT_DATE, NULL, TRUE)
ON DUPLICATE KEY UPDATE date_fin = NULL, actif = TRUE"
);
$history->execute(['agent_id' => $agentId, 'structure_id' => $newStructureId]);
}
}
$this->pdo->commit();
} catch (Throwable $e) {
if ($this->pdo->inTransaction()) {
$this->pdo->rollBack();
}
throw $e;
}
}
public function updateAssignment(int $agentId, ?int $structureId): void
{
try {
$this->pdo->beginTransaction();
$update = $this->pdo->prepare('UPDATE agent SET id_structure = :structure_id WHERE id_agent = :agent_id');
$update->bindValue(':agent_id', $agentId, PDO::PARAM_INT);
if ($structureId === null) {
$update->bindValue(':structure_id', null, PDO::PARAM_NULL);
} else {
$update->bindValue(':structure_id', $structureId, PDO::PARAM_INT);
}
$update->execute();
$close = $this->pdo->prepare(
"UPDATE agent_structure
SET actif = FALSE,
date_fin = CASE
WHEN date_debut IS NOT NULL AND date_debut > CURRENT_DATE THEN date_debut
ELSE CURRENT_DATE
END
WHERE id_agent = :agent_id AND actif = TRUE"
);
$close->execute(['agent_id' => $agentId]);
if ($structureId !== null) {
$history = $this->pdo->prepare(
"INSERT INTO agent_structure (id_agent, id_structure, date_debut, date_fin, actif)
VALUES (:agent_id, :structure_id, CURRENT_DATE, NULL, TRUE)
ON DUPLICATE KEY UPDATE date_fin = NULL, actif = TRUE"
);
$history->execute(['agent_id' => $agentId, 'structure_id' => $structureId]);
}
$this->pdo->commit();
} catch (Throwable $e) {
if ($this->pdo->inTransaction()) {
$this->pdo->rollBack();
}
throw $e;
}
}
public function entriesForWeek(int $agentId, int $year, int $week): array
{
$stmt = $this->pdo->prepare(
"SELECT p.id_planning, p.statut,
s2.id_structure, s2.nom AS structure_nom,
s2.type_affectation AS structure_type_affectation,
c.id_creneau, c.id_motif, c.date_jour,
TIME_FORMAT(c.heure_debut, '%H:%i') AS heure_debut,
TIME_FORMAT(c.heure_fin, '%H:%i') AS heure_fin,
m.code AS motif_code, m.libelle AS motif_libelle, m.compte_dans_quota
FROM planning p
INNER JOIN semaine sem ON sem.id_semaine = p.id_semaine
INNER JOIN structure s2 ON s2.id_structure = p.id_structure
INNER JOIN creneau_horaire c ON c.id_planning = p.id_planning
INNER JOIN motif_planning m ON m.id_motif = c.id_motif
WHERE p.id_agent = :agent_id
AND sem.annee = :annee
AND sem.numero_semaine = :semaine
ORDER BY c.date_jour, c.heure_debut, s2.nom"
);
$stmt->execute(['agent_id' => $agentId, 'annee' => $year, 'semaine' => $week]);
return $stmt->fetchAll();
}
public function entriesForDateRange(int $agentId, string $startDate, string $endDate): array
{
$stmt = $this->pdo->prepare(
"SELECT p.id_planning, p.statut,
s2.id_structure, s2.nom AS structure_nom,
s2.type_affectation AS structure_type_affectation,
c.id_creneau, c.id_motif, c.date_jour,
TIME_FORMAT(c.heure_debut, '%H:%i') AS heure_debut,
TIME_FORMAT(c.heure_fin, '%H:%i') AS heure_fin,
m.code AS motif_code, m.libelle AS motif_libelle, m.compte_dans_quota
FROM planning p
INNER JOIN structure s2 ON s2.id_structure = p.id_structure
INNER JOIN creneau_horaire c ON c.id_planning = p.id_planning
INNER JOIN motif_planning m ON m.id_motif = c.id_motif
WHERE p.id_agent = :agent_id
AND c.date_jour BETWEEN :date_debut AND :date_fin
ORDER BY c.date_jour, c.heure_debut, s2.nom"
);
$stmt->execute([
'agent_id' => $agentId,
'date_debut' => $startDate,
'date_fin' => $endDate,
]);
return $stmt->fetchAll();
}
public function timeSummaryForRange(int $agentId, string $startDate, string $endDate): array
{
$stmt = $this->pdo->prepare(
"SELECT UPPER(m.code) AS motif_code,
m.libelle AS motif_libelle,
m.compte_dans_quota,
COALESCE(SUM(TIMESTAMPDIFF(MINUTE, CONCAT(c.date_jour, ' ', c.heure_debut), CONCAT(c.date_jour, ' ', c.heure_fin))), 0) AS minutes_total,
COALESCE(SUM(CASE WHEN p.statut = 'VALIDE' THEN TIMESTAMPDIFF(MINUTE, CONCAT(c.date_jour, ' ', c.heure_debut), CONCAT(c.date_jour, ' ', c.heure_fin)) ELSE 0 END), 0) AS minutes_valides,
COALESCE(SUM(CASE WHEN p.statut = 'BROUILLON' THEN TIMESTAMPDIFF(MINUTE, CONCAT(c.date_jour, ' ', c.heure_debut), CONCAT(c.date_jour, ' ', c.heure_fin)) ELSE 0 END), 0) AS minutes_brouillon
FROM planning p
INNER JOIN creneau_horaire c ON c.id_planning = p.id_planning
INNER JOIN motif_planning m ON m.id_motif = c.id_motif
WHERE p.id_agent = :agent_id
AND c.date_jour BETWEEN :date_debut AND :date_fin
GROUP BY UPPER(m.code), m.libelle, m.compte_dans_quota
ORDER BY m.compte_dans_quota DESC, m.libelle"
);
$stmt->execute([
'agent_id' => $agentId,
'date_debut' => $startDate,
'date_fin' => $endDate,
]);
return $stmt->fetchAll();
}
public function timeSummaryForYear(int $agentId, int $year): array
{
return $this->timeSummaryForRange($agentId, sprintf('%04d-01-01', $year), sprintf('%04d-12-31', $year));
}
}