update
This commit is contained in:
@@ -46,6 +46,149 @@ final class AgentModel extends BaseModel
|
||||
return self::hasTrainingRight($agent) ? self::ENVELOPPE_FORMATION_MINUTES : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bornes du contrat, telles qu'elles servent aux contrôles d'affectation.
|
||||
*
|
||||
* Les deux bornes sont FACULTATIVES et n'ont pas le même sens quand elles
|
||||
* manquent :
|
||||
* - pas de date de début : contrat d'origine inconnue (fiche ancienne), on
|
||||
* n'oppose alors aucune borne basse plutôt que d'inventer une date ;
|
||||
* - pas de date de fin : contrat à durée indéterminée, la borne haute
|
||||
* n'existe pas.
|
||||
* Dans les deux cas, l'absence ouvre — elle ne ferme jamais.
|
||||
*
|
||||
* @param array<string, mixed> $agent Fiche agent, telle que renvoyée par
|
||||
* findActive() ou allActive().
|
||||
*
|
||||
* @return array{0: string|null, 1: string|null} [début, fin] au format AAAA-MM-JJ.
|
||||
*/
|
||||
public static function contractRange(array $agent): array
|
||||
{
|
||||
$debut = trim((string) ($agent['date_debut_contrat'] ?? ''));
|
||||
$fin = trim((string) ($agent['date_fin_contrat'] ?? ''));
|
||||
|
||||
return [$debut !== '' ? $debut : null, $fin !== '' ? $fin : null];
|
||||
}
|
||||
|
||||
/**
|
||||
* Vrai si la date tombe dans les bornes du contrat.
|
||||
*
|
||||
* Comparaison de chaînes « AAAA-MM-JJ » : ce format se compare
|
||||
* lexicographiquement dans le même ordre que chronologiquement, ce qui évite
|
||||
* de construire deux objets DateTime par créneau contrôlé.
|
||||
*/
|
||||
public static function contractCoversDate(array $agent, string $date): bool
|
||||
{
|
||||
[$debut, $fin] = self::contractRange($agent);
|
||||
|
||||
if ($debut !== null && $date < $debut) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !($fin !== null && $date > $fin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filtre les dates qui tombent hors contrat, dédoublonnées et triées.
|
||||
*
|
||||
* @param array<int, string> $dates
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public static function datesOutsideContract(array $agent, array $dates): array
|
||||
{
|
||||
$hors = [];
|
||||
foreach ($dates as $date) {
|
||||
$date = trim((string) $date);
|
||||
if ($date !== '' && !self::contractCoversDate($agent, $date)) {
|
||||
$hors[$date] = true;
|
||||
}
|
||||
}
|
||||
$hors = array_keys($hors);
|
||||
sort($hors);
|
||||
|
||||
return $hors;
|
||||
}
|
||||
|
||||
/** « du 01/09/2026 au 31/08/2027 », « à partir du 01/09/2026 », « jusqu'au … ». */
|
||||
public static function contractPeriodLabel(array $agent): string
|
||||
{
|
||||
[$debut, $fin] = self::contractRange($agent);
|
||||
$lisible = static fn(string $date): string => implode('/', array_reverse(explode('-', $date)));
|
||||
|
||||
if ($debut !== null && $fin !== null) {
|
||||
return sprintf('du %s au %s', $lisible($debut), $lisible($fin));
|
||||
}
|
||||
if ($debut !== null) {
|
||||
return sprintf('à partir du %s', $lisible($debut));
|
||||
}
|
||||
if ($fin !== null) {
|
||||
return sprintf('jusqu’au %s', $lisible($fin));
|
||||
}
|
||||
|
||||
return 'sans date de contrat renseignée';
|
||||
}
|
||||
|
||||
/**
|
||||
* Message de refus commun à tous les points d'écriture, pour que
|
||||
* l'utilisateur lise la même phrase quel que soit l'écran qui l'a produit.
|
||||
*
|
||||
* @param array<int, string> $datesHorsContrat
|
||||
*/
|
||||
public static function contractViolationMessage(array $agent, array $datesHorsContrat): string
|
||||
{
|
||||
$lisible = static fn(string $date): string => implode('/', array_reverse(explode('-', $date)));
|
||||
$exemples = array_map($lisible, array_slice($datesHorsContrat, 0, 3));
|
||||
if (count($datesHorsContrat) > 3) {
|
||||
$exemples[] = sprintf('et %d autre(s)', count($datesHorsContrat) - 3);
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
'%s %s n’est pas sous contrat à cette période : son contrat court %s. Date(s) refusée(s) : %s.',
|
||||
(string) ($agent['prenom'] ?? ''),
|
||||
(string) ($agent['nom'] ?? ''),
|
||||
self::contractPeriodLabel($agent),
|
||||
implode(', ', $exemples)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Créneaux déjà planifiés en dehors d'une fenêtre de contrat.
|
||||
*
|
||||
* Sert au contrôle MIROIR de l'affectation : raccourcir un contrat ne doit
|
||||
* pas laisser derrière lui des affectations orphelines. Interroge donc les
|
||||
* créneaux, pas les plannings — un planning peut être à cheval sur la borne.
|
||||
*
|
||||
* @return array{nombre: int, premiere: string|null, derniere: string|null}
|
||||
*/
|
||||
public function entriesOutsideContract(int $agentId, ?string $debut, ?string $fin): array
|
||||
{
|
||||
$stmt = $this->pdo->prepare(
|
||||
'SELECT COUNT(*) AS nombre, MIN(c.date_jour) AS premiere, MAX(c.date_jour) AS derniere
|
||||
FROM creneau_horaire c
|
||||
JOIN planning p ON p.id_planning = c.id_planning
|
||||
WHERE p.id_agent = :agent_id
|
||||
AND ((:debut IS NOT NULL AND c.date_jour < :debut_compare)
|
||||
OR (:fin IS NOT NULL AND c.date_jour > :fin_compare))'
|
||||
);
|
||||
// Chaque paramètre nommé est répété sous deux noms : PDO tourne sans
|
||||
// émulation, un même nom ne peut pas apparaître deux fois.
|
||||
$stmt->execute([
|
||||
'agent_id' => $agentId,
|
||||
'debut' => $debut,
|
||||
'debut_compare' => $debut,
|
||||
'fin' => $fin,
|
||||
'fin_compare' => $fin,
|
||||
]);
|
||||
$ligne = $stmt->fetch() ?: [];
|
||||
|
||||
return [
|
||||
'nombre' => (int) ($ligne['nombre'] ?? 0),
|
||||
'premiere' => $ligne['premiere'] ?? null,
|
||||
'derniere' => $ligne['derniere'] ?? null,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Projection commune à toutes les lectures d'agent, complétée par la clause
|
||||
* WHERE de chaque appelant.
|
||||
@@ -375,6 +518,10 @@ final class AgentModel extends BaseModel
|
||||
* bindValue() explicite ici (et non un tableau passé à execute()) car
|
||||
* `structure_id` peut être NULL et exige PDO::PARAM_NULL.
|
||||
*
|
||||
* Le nom et le prénom font partie des champs modifiables depuis la V7.13 ;
|
||||
* le MATRICULE, lui, ne l'est jamais : c'est la clé de rapprochement avec
|
||||
* les outils RH, et les plannings comme les PDF déjà produits s'y réfèrent.
|
||||
*
|
||||
* @param array<string, mixed> $data Champs déjà validés par le contrôleur.
|
||||
*
|
||||
* @throws \RuntimeException Si l'agent n'existe pas.
|
||||
@@ -395,7 +542,9 @@ final class AgentModel extends BaseModel
|
||||
|
||||
$update = $this->pdo->prepare(
|
||||
'UPDATE agent
|
||||
SET email = :email,
|
||||
SET nom = :nom,
|
||||
prenom = :prenom,
|
||||
email = :email,
|
||||
telephone = :telephone,
|
||||
adresse = :adresse,
|
||||
est_diplome = :est_diplome,
|
||||
@@ -410,6 +559,8 @@ final class AgentModel extends BaseModel
|
||||
WHERE id_agent = :agent_id'
|
||||
);
|
||||
$update->bindValue(':agent_id', $agentId, PDO::PARAM_INT);
|
||||
$update->bindValue(':nom', $data['nom']);
|
||||
$update->bindValue(':prenom', $data['prenom']);
|
||||
$update->bindValue(':email', $data['email']);
|
||||
$update->bindValue(':telephone', $data['telephone']);
|
||||
$update->bindValue(':adresse', $data['adresse']);
|
||||
|
||||
Reference in New Issue
Block a user