Nouvelle colonne `agent.droit_enveloppe_formation`, cochée par défaut, saisie à la création de l'agent et modifiable depuis sa fiche. Un agent sans ce droit : - ne voit plus le motif « Formation » dans le sélecteur de motif de l'éditeur de planning ni dans le modal de la vue individuelle ; - se voit refuser toute affectation de ce motif par le serveur, avec un message qui nomme le motif et l'agent (save_planning.php et update_agent_entry.php) ; - a une enveloppe de formation de 0 min au PTA annuel, et une ALERTE FORMATION_SANS_DROIT si des heures de formation subsistent dans son planning. À ne pas confondre avec `formation_repartition_annuelle`, qui existait déjà : celle-ci dit que la formation est déjà comptée dans la répartition annuelle — l'agent y a droit, l'enveloppe complémentaire n'est simplement pas ajoutée. Les deux règles sont désormais rassemblées dans AgentModel::trainingEnvelopeMinutes(), qui remplace les deux calculs « 840 ou 0 » jusque-là dupliqués. Le motif interdit reste affiché lorsqu'il est déjà celui de la plage en cours d'édition : le retirer de la liste changerait silencieusement le motif d'une saisie existante. C'est le contrôle de saisie, puis le serveur, qui demandent alors de corriger la plage. La migration est nommée v7_9b et non v7_10 : les fichiers sont joués dans l'ordre alphabétique, où « v7_10 » passerait avant « v7_8 ». Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
366 lines
23 KiB
PHP
366 lines
23 KiB
PHP
<section id="assignment-view" class="app-view page-content">
|
||
<div class="admin-grid">
|
||
<section class="card admin-card">
|
||
<div class="section-heading">
|
||
<div>
|
||
<h2>Ajouter un lieu d’affectation</h2>
|
||
<p class="section-subtitle">Chaque lieu possède son propre type : périscolaire ou extrascolaire.</p>
|
||
</div>
|
||
</div>
|
||
<form id="create-structure-form" class="admin-form">
|
||
<div class="admin-form-grid">
|
||
<label class="field">
|
||
<span>Code <strong>*</strong></span>
|
||
<input id="new-structure-code" name="code" type="text" maxlength="50" placeholder="Ex. CRECHE_MATUSALEME" required>
|
||
</label>
|
||
<label class="field">
|
||
<span>Nom <strong>*</strong></span>
|
||
<input id="new-structure-name" name="nom" type="text" maxlength="150" placeholder="Ex. Crèche Matusalème" required>
|
||
</label>
|
||
<label class="field">
|
||
<span>Type d’affectation <strong>*</strong></span>
|
||
<select id="new-structure-type" name="type_affectation" required>
|
||
<option value="PERISCOLAIRE">Périscolaire</option>
|
||
<option value="EXTRASCOLAIRE">Extrascolaire</option>
|
||
</select>
|
||
</label>
|
||
<label class="field admin-field-wide">
|
||
<span>Adresse</span>
|
||
<input id="new-structure-address" name="adresse" type="text" maxlength="255" placeholder="Adresse du lieu">
|
||
</label>
|
||
</div>
|
||
<div class="admin-form-actions">
|
||
<div id="create-structure-message" class="message" role="status" aria-live="polite"></div>
|
||
<button class="button button-primary" type="submit">Ajouter le lieu</button>
|
||
</div>
|
||
</form>
|
||
</section>
|
||
|
||
<section class="card admin-card">
|
||
<div class="section-heading">
|
||
<div>
|
||
<h2>Ajouter un agent</h2>
|
||
<p class="section-subtitle">L’agent reçoit un lieu principal à sa création, mais peut être intégré aussi bien aux équipes périscolaires qu’extrascolaires et travailler ponctuellement sur d’autres lieux.</p>
|
||
</div>
|
||
</div>
|
||
<form id="create-agent-form" class="admin-form">
|
||
<div class="admin-form-grid">
|
||
<label class="field">
|
||
<span>Matricule <strong>*</strong></span>
|
||
<input id="new-agent-matricule" name="matricule" type="text" maxlength="50" required>
|
||
</label>
|
||
<label class="field">
|
||
<span>Poste PTA <strong>*</strong></span>
|
||
<select id="new-agent-post" name="id_poste" required>
|
||
<option value="">Choisir un poste</option>
|
||
<?php foreach ($posts as $post): ?>
|
||
<option value="<?= (int) $post['id_poste'] ?>"><?= htmlspecialchars($post['libelle']) ?></option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</label>
|
||
<label class="field">
|
||
<span>Type de contrat <strong>*</strong></span>
|
||
<select id="new-agent-contract-type" required>
|
||
<option value="PERMANENT">Permanent — soumis au PTA</option>
|
||
<option value="TEMPORAIRE">Temporaire — hors périmètre PTA</option>
|
||
</select>
|
||
</label>
|
||
<label class="field">
|
||
<span>Lieu d’affectation principal <strong>*</strong></span>
|
||
<select id="new-agent-structure" name="structure_id" required>
|
||
<option value="">Choisir un lieu d’affectation</option>
|
||
<?php foreach ($structures as $structure): ?>
|
||
<option value="<?= (int) $structure['id_structure'] ?>">
|
||
<?= htmlspecialchars($structure['nom']) ?>
|
||
</option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</label>
|
||
<label class="field">
|
||
<span>Nom <strong>*</strong></span>
|
||
<input id="new-agent-name" name="nom" type="text" maxlength="100" required>
|
||
</label>
|
||
<label class="field">
|
||
<span>Prénom <strong>*</strong></span>
|
||
<input id="new-agent-firstname" name="prenom" type="text" maxlength="100" required>
|
||
</label>
|
||
<label class="field">
|
||
<span>E-mail</span>
|
||
<input id="new-agent-email" name="email" type="email" maxlength="255" placeholder="prenom.nom@exemple.fr">
|
||
</label>
|
||
<label class="field">
|
||
<span>Téléphone</span>
|
||
<input id="new-agent-phone" name="telephone" type="tel" maxlength="30" placeholder="Ex. 06 12 34 56 78">
|
||
</label>
|
||
<label class="field">
|
||
<span>Qualification</span>
|
||
<select id="new-agent-qualified" name="est_diplome">
|
||
<option value="0">Non diplômé</option>
|
||
<option value="1">Diplômé</option>
|
||
</select>
|
||
</label>
|
||
<label class="field">
|
||
<span>Diplôme</span>
|
||
<input id="new-agent-diploma" name="diplome_libelle" type="text" maxlength="150" placeholder="Ex. BAFA, CAP AEPE…" disabled>
|
||
</label>
|
||
<label class="field admin-field-wide">
|
||
<span>Adresse personnelle</span>
|
||
<textarea id="new-agent-address" name="adresse" maxlength="255" rows="2" placeholder="Adresse de l’agent"></textarea>
|
||
</label>
|
||
<label class="field">
|
||
<span>Quotité de travail <strong>*</strong></span>
|
||
<div class="input-suffix">
|
||
<input id="new-agent-rate" name="quotite_travail" type="number" min="1" max="100" step="0.01" value="100" required>
|
||
<span>%</span>
|
||
</div>
|
||
</label>
|
||
<label class="field">
|
||
<span>Date de début du contrat <strong>*</strong></span>
|
||
<input id="new-agent-contract-start" name="date_debut_contrat" type="date" value="<?= date('Y-m-d') ?>" required>
|
||
</label>
|
||
<label class="field">
|
||
<span>Date de fin du contrat</span>
|
||
<input id="new-agent-contract-end" name="date_fin_contrat" type="date">
|
||
</label>
|
||
<label class="check-field admin-field-wide">
|
||
<input id="new-agent-training-right" type="checkbox" checked>
|
||
<span>Droit à l’enveloppe de formation de 14 h. Décoché, le motif « Formation » ne pourra pas être affecté à cet agent lors de la saisie des horaires.</span>
|
||
</label>
|
||
<label class="check-field admin-field-wide">
|
||
<input id="new-agent-training-planned" type="checkbox">
|
||
<span>Une formation est déjà prévue dans la répartition annuelle : ne pas ajouter l’enveloppe complémentaire de 14 h</span>
|
||
</label>
|
||
<label class="field admin-field-wide">
|
||
<span>Observations utiles à la construction du PTA</span>
|
||
<textarea id="new-agent-pta-comment" maxlength="1000" rows="3" placeholder="Disponibilités particulières, organisation connue…"></textarea>
|
||
</label>
|
||
</div>
|
||
<p class="hint">Le quota PTA est suivi sur l’année civile, du 1er janvier au 31 décembre. À 100 %, la référence est de 1 607 h. La date de début du contrat reste une information RH mais ne décale pas l’année de référence.</p>
|
||
<div class="admin-form-actions">
|
||
<div id="create-agent-message" class="message" role="status" aria-live="polite"></div>
|
||
<button class="button button-primary" type="submit">Ajouter l’agent</button>
|
||
</div>
|
||
</form>
|
||
</section>
|
||
</div>
|
||
|
||
<section class="card">
|
||
<div class="section-heading">
|
||
<div>
|
||
<h2>Paramètres des lieux d’affectation</h2>
|
||
<p class="section-subtitle">Le type d’affectation est défini au niveau du lieu : par exemple « Crèche Matusalème — Périscolaire ».</p>
|
||
</div>
|
||
</div>
|
||
<div id="structure-settings-message" class="message" role="status" aria-live="polite"></div>
|
||
<div class="table-scroll">
|
||
<table class="planning-table assignment-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Lieu</th>
|
||
<th>Code</th>
|
||
<th>Type d’affectation</th>
|
||
<th>Action</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="structure-settings-body">
|
||
<?php foreach ($structures as $structure): ?>
|
||
<tr data-structure-id="<?= (int) $structure['id_structure'] ?>">
|
||
<td><strong><?= htmlspecialchars($structure['nom']) ?></strong></td>
|
||
<td><?= htmlspecialchars($structure['code']) ?></td>
|
||
<td>
|
||
<select class="structure-type-select" aria-label="Type d’affectation de <?= htmlspecialchars($structure['nom']) ?>">
|
||
<option value="PERISCOLAIRE" <?= ($structure['type_affectation'] ?? 'PERISCOLAIRE') === 'PERISCOLAIRE' ? 'selected' : '' ?>>Périscolaire</option>
|
||
<option value="EXTRASCOLAIRE" <?= ($structure['type_affectation'] ?? '') === 'EXTRASCOLAIRE' ? 'selected' : '' ?>>Extrascolaire</option>
|
||
</select>
|
||
</td>
|
||
<td><button type="button" class="button button-secondary save-structure-type">Enregistrer</button></td>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="card coverage-settings-card">
|
||
<div class="section-heading">
|
||
<div>
|
||
<h2>Horaires de couverture des lieux</h2>
|
||
<p class="section-subtitle">Définissez les périodes pendant lesquelles un nombre minimum d’agents doit être physiquement présent. Seules les affectations « Heure de travail » comptent comme présence.</p>
|
||
</div>
|
||
</div>
|
||
<div class="selection-grid overview-filters">
|
||
<label class="field">
|
||
<span>Lieu d’affectation</span>
|
||
<select id="coverage-rules-structure">
|
||
<option value="">Choisir un lieu d’affectation</option>
|
||
<?php foreach ($structures as $structure): ?>
|
||
<option value="<?= (int) $structure['id_structure'] ?>"><?= htmlspecialchars($structure['nom']) ?></option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</label>
|
||
<div class="overview-filter-actions">
|
||
<button id="load-coverage-rules" class="button button-secondary filter-button" type="button">Charger</button>
|
||
</div>
|
||
</div>
|
||
<div id="coverage-rules-message" class="message" role="status" aria-live="polite"></div>
|
||
<div id="coverage-rules-editor" class="coverage-rules-editor">
|
||
<div class="empty-state">Choisissez un lieu pour configurer les plages où une présence minimale est obligatoire.</div>
|
||
</div>
|
||
<div class="coverage-rules-actions">
|
||
<button id="save-coverage-rules" class="button button-primary" type="button" disabled>Enregistrer les horaires de couverture</button>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="card">
|
||
<div class="section-heading">
|
||
<div>
|
||
<h2>Informations des agents</h2>
|
||
<p class="section-subtitle">Consultez la qualification, les coordonnées et le lieu principal de chaque agent. Le bouton Modifier ouvre la fiche complète.</p>
|
||
</div>
|
||
</div>
|
||
<div id="assignment-message" class="message" role="status" aria-live="polite"></div>
|
||
<div class="table-scroll">
|
||
<table class="planning-table assignment-table agent-information-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Agent</th>
|
||
<th>Matricule</th>
|
||
<th>Poste</th>
|
||
<th>Qualification</th>
|
||
<th>Téléphone</th>
|
||
<th>Début contrat</th>
|
||
<th>Lieu principal</th>
|
||
<th>Action</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="agent-assignment-body">
|
||
<?php foreach ($agents as $agent): ?>
|
||
<tr
|
||
data-agent-id="<?= (int) $agent['id_agent'] ?>"
|
||
data-email="<?= htmlspecialchars((string) ($agent['email'] ?? ''), ENT_QUOTES) ?>"
|
||
data-telephone="<?= htmlspecialchars((string) ($agent['telephone'] ?? ''), ENT_QUOTES) ?>"
|
||
data-adresse="<?= htmlspecialchars((string) ($agent['adresse'] ?? ''), ENT_QUOTES) ?>"
|
||
data-est-diplome="<?= !empty($agent['est_diplome']) ? '1' : '0' ?>"
|
||
data-diplome-libelle="<?= htmlspecialchars((string) ($agent['diplome_libelle'] ?? ''), ENT_QUOTES) ?>"
|
||
data-date-debut-contrat="<?= htmlspecialchars((string) ($agent['date_debut_contrat'] ?? ''), ENT_QUOTES) ?>"
|
||
data-date-fin-contrat="<?= htmlspecialchars((string) ($agent['date_fin_contrat'] ?? ''), ENT_QUOTES) ?>"
|
||
data-id-poste="<?= (int) ($agent['id_poste'] ?? 0) ?>"
|
||
data-type-contrat="<?= htmlspecialchars((string) ($agent['type_contrat'] ?? 'PERMANENT'), ENT_QUOTES) ?>"
|
||
data-formation-repartition-annuelle="<?= !empty($agent['formation_repartition_annuelle']) ? '1' : '0' ?>"
|
||
data-droit-enveloppe-formation="<?= !isset($agent['droit_enveloppe_formation']) || !empty($agent['droit_enveloppe_formation']) ? '1' : '0' ?>"
|
||
data-commentaire-pta="<?= htmlspecialchars((string) ($agent['commentaire_pta'] ?? ''), ENT_QUOTES) ?>"
|
||
data-structure-id="<?= (int) ($agent['id_structure'] ?? 0) ?>"
|
||
>
|
||
<td><strong class="agent-row-name"><?= htmlspecialchars($agent['prenom'] . ' ' . $agent['nom']) ?></strong></td>
|
||
<td><?= htmlspecialchars($agent['matricule']) ?></td>
|
||
<td class="agent-row-post"><?= htmlspecialchars((string) (($agent['poste_libelle'] ?? '') ?: 'Non renseigné')) ?></td>
|
||
<td class="agent-row-qualification">
|
||
<?= !empty($agent['est_diplome'])
|
||
? 'Diplômé' . (!empty($agent['diplome_libelle']) ? ' — ' . htmlspecialchars($agent['diplome_libelle']) : '')
|
||
: 'Non diplômé' ?>
|
||
</td>
|
||
<td class="agent-row-phone"><?= htmlspecialchars((string) (($agent['telephone'] ?? '') ?: 'Non renseigné')) ?></td>
|
||
<td class="agent-row-contract-start"><?= htmlspecialchars((string) (($agent['date_debut_contrat'] ?? '') ?: 'Non renseignée')) ?></td>
|
||
<td class="agent-row-structure"><?= htmlspecialchars((string) (($agent['structure_nom'] ?? '') ?: 'Sans lieu principal')) ?></td>
|
||
<td><button type="button" class="button button-secondary edit-agent-profile">Modifier</button></td>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</section>
|
||
</section>
|
||
|
||
<div id="agent-profile-modal" class="modal-backdrop" hidden>
|
||
<section class="modal-card" role="dialog" aria-modal="true" aria-labelledby="agent-profile-modal-title">
|
||
<div class="section-heading modal-heading">
|
||
<div>
|
||
<h2 id="agent-profile-modal-title">Modifier les informations de l’agent</h2>
|
||
<p id="agent-profile-modal-subtitle" class="section-subtitle"></p>
|
||
</div>
|
||
<button id="close-agent-profile-modal" type="button" class="button button-ghost modal-close" aria-label="Fermer">×</button>
|
||
</div>
|
||
<form id="agent-profile-form" class="admin-form">
|
||
<input id="edit-agent-profile-id" type="hidden">
|
||
<div class="admin-form-grid">
|
||
<label class="field">
|
||
<span>Poste PTA <strong>*</strong></span>
|
||
<select id="edit-agent-profile-post" required>
|
||
<option value="">Choisir un poste</option>
|
||
<?php foreach ($posts as $post): ?>
|
||
<option value="<?= (int) $post['id_poste'] ?>"><?= htmlspecialchars($post['libelle']) ?></option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</label>
|
||
<label class="field">
|
||
<span>Type de contrat <strong>*</strong></span>
|
||
<select id="edit-agent-profile-contract-type" required>
|
||
<option value="PERMANENT">Permanent</option>
|
||
<option value="TEMPORAIRE">Temporaire</option>
|
||
</select>
|
||
</label>
|
||
<label class="field">
|
||
<span>E-mail</span>
|
||
<input id="edit-agent-profile-email" type="email" maxlength="255">
|
||
</label>
|
||
<label class="field">
|
||
<span>Téléphone</span>
|
||
<input id="edit-agent-profile-phone" type="tel" maxlength="30" placeholder="Ex. 06 12 34 56 78">
|
||
</label>
|
||
<label class="field">
|
||
<span>Qualification</span>
|
||
<select id="edit-agent-profile-qualified">
|
||
<option value="0">Non diplômé</option>
|
||
<option value="1">Diplômé</option>
|
||
</select>
|
||
</label>
|
||
<label class="field">
|
||
<span>Diplôme</span>
|
||
<input id="edit-agent-profile-diploma" type="text" maxlength="150" placeholder="Ex. BAFA, CAP AEPE…">
|
||
</label>
|
||
<label class="field admin-field-wide">
|
||
<span>Adresse personnelle</span>
|
||
<textarea id="edit-agent-profile-address" maxlength="255" rows="3"></textarea>
|
||
</label>
|
||
<label class="field">
|
||
<span>Date de début du contrat <strong>*</strong></span>
|
||
<input id="edit-agent-profile-contract-start" type="date" required>
|
||
</label>
|
||
<label class="field">
|
||
<span>Date de fin du contrat</span>
|
||
<input id="edit-agent-profile-contract-end" type="date">
|
||
</label>
|
||
<label class="check-field admin-field-wide">
|
||
<input id="edit-agent-profile-training-right" type="checkbox">
|
||
<span>Droit à l’enveloppe de formation de 14 h</span>
|
||
</label>
|
||
<label class="check-field admin-field-wide">
|
||
<input id="edit-agent-profile-training-planned" type="checkbox">
|
||
<span>Formation déjà intégrée dans la répartition annuelle</span>
|
||
</label>
|
||
<label class="field admin-field-wide">
|
||
<span>Observations PTA</span>
|
||
<textarea id="edit-agent-profile-pta-comment" maxlength="1000" rows="3"></textarea>
|
||
</label>
|
||
<label class="field admin-field-wide">
|
||
<span>Lieu d’affectation principal</span>
|
||
<select id="edit-agent-profile-structure">
|
||
<option value="">Sans lieu principal</option>
|
||
<?php foreach ($structures as $structure): ?>
|
||
<option value="<?= (int) $structure['id_structure'] ?>">
|
||
<?= htmlspecialchars($structure['nom']) ?>
|
||
</option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</label>
|
||
</div>
|
||
<div id="agent-profile-message" class="message" role="status" aria-live="polite"></div>
|
||
<div class="admin-form-actions modal-actions">
|
||
<button id="cancel-agent-profile" type="button" class="button button-secondary">Annuler</button>
|
||
<button type="submit" class="button button-primary">Enregistrer les informations</button>
|
||
</div>
|
||
</form>
|
||
</section>
|
||
</div>
|
||
|