Horodate précisément les contrôles de soins

This commit is contained in:
Alexandre NOEL 2026-09-13 14:28:12 +02:00
commit a0ea338292
10 changed files with 60 additions and 12 deletions

View file

@ -2,6 +2,14 @@
Toutes les modifications importantes du projet sont consignées ici. Le format s’inspire de [Keep a Changelog](https://keepachangelog.com/fr/1.1.0/) et le projet suit le [versionnage sémantique](https://semver.org/lang/fr/). Toutes les modifications importantes du projet sont consignées ici. Le format s’inspire de [Keep a Changelog](https://keepachangelog.com/fr/1.1.0/) et le projet suit le [versionnage sémantique](https://semver.org/lang/fr/).
## [1.0.0-rc.19] — 2026-09-13
### Horodatage des contrôles de soins
- ajout d’une date et d’une heure aux notes médicales, proposées à l’instant de la saisie et librement modifiables ;
- utilisation du même horodatage pour la note, la mesure de poids, le tarif associé et la date d’une éventuelle stérilisation ;
- validation stricte des dates et heures transmises avant enregistrement.
## [1.0.0-rc.18] — 2026-09-09 ## [1.0.0-rc.18] — 2026-09-09
### Finitions du site public ### Finitions du site public

View file

@ -81,3 +81,7 @@ Remplacement de la barre de navigation horizontale par un menu mobile repliable
## `1.0.0-rc.18` — Finitions du site public ## `1.0.0-rc.18` — Finitions du site public
Déplacement des informations légales dans le pied de page, insertion guidée des images sans perdre les textes en cours d’édition et identification visuelle des réseaux sociaux. Déplacement des informations légales dans le pied de page, insertion guidée des images sans perdre les textes en cours d’édition et identification visuelle des réseaux sociaux.
## `1.0.0-rc.19` — Contrôles de soins horodatés
Ajout d’une date et d’une heure modifiables aux contrôles médicaux, avec reprise cohérente de l’instant choisi dans les mesures de poids et les données associées.

View file

@ -200,7 +200,7 @@ L’option `--apply` ne doit être utilisée qu’après lecture des rapports. L
## État du projet ## État du projet
La candidate actuelle est `1.0.0-rc.18`. La branche 1.0 est gelée fonctionnellement : seuls les défauts bloquants découverts pendant les validations terrain sont corrigés avant `1.0.0`. La candidate actuelle est `1.0.0-rc.19`. La branche 1.0 est gelée fonctionnellement : seuls les défauts bloquants découverts pendant les validations terrain sont corrigés avant `1.0.0`.
Le contrôle de stabilisation est disponible dans [docs/AUDIT-1.0.md](docs/AUDIT-1.0.md). Le contrôle de stabilisation est disponible dans [docs/AUDIT-1.0.md](docs/AUDIT-1.0.md).

View file

@ -1 +1 @@
1.0.0-rc.18 1.0.0-rc.19

View file

@ -221,6 +221,20 @@ trait AnimalMedicalActions
$temperature = $_POST['temperature_c'] ?? null; $temperature = $_POST['temperature_c'] ?? null;
$weight = $_POST['weight_kg'] ?? null; $weight = $_POST['weight_kg'] ?? null;
$notedDate = trim((string) ($_POST['noted_date'] ?? date('Y-m-d')));
$notedTime = trim((string) ($_POST['noted_time'] ?? date('H:i')));
$observedAt = DateTimeImmutable::createFromFormat('!Y-m-d H:i', $notedDate . ' ' . $notedTime);
$dateErrors = DateTimeImmutable::getLastErrors();
if (
!$observedAt ||
($dateErrors !== false && ($dateErrors['warning_count'] > 0 || $dateErrors['error_count'] > 0)) ||
$observedAt->format('Y-m-d H:i') !== $notedDate . ' ' . $notedTime
) {
http_response_code(400);
echo h(t('error.invalid_medical_datetime'));
return;
}
$observedAtSql = $observedAt->format('Y-m-d H:i:s');
$clinicId = (int) ($_POST['clinic_id'] ?? 0); $clinicId = (int) ($_POST['clinic_id'] ?? 0);
$clinicId = $clinicId > 0 ? $clinicId : null; $clinicId = $clinicId > 0 ? $clinicId : null;
@ -251,13 +265,14 @@ trait AnimalMedicalActions
// Enregistre la note médicale avant ses éventuelles pièces jointes. // Enregistre la note médicale avant ses éventuelles pièces jointes.
$stmt = $db->prepare(' $stmt = $db->prepare('
INSERT INTO medical_notes INSERT INTO medical_notes
(animal_id, kind, reason, symptoms, exam, diagnosis, plan, temperature_c, weight_kg, clinic_id, vet_name, clinic_contact_id, vet_contact_id, created_by, created_at) (animal_id, noted_at, kind, reason, symptoms, exam, diagnosis, plan, temperature_c, weight_kg, clinic_id, vet_name, clinic_contact_id, vet_contact_id, created_by, created_at)
VALUES VALUES
(:aid, :kind, :reason, :symptoms, :exam, :diagnosis, :plan, :temp, :weight, :clinic, :vet, :clinic_contact, :vet_contact, :created_by, :created) (:aid, :noted_at, :kind, :reason, :symptoms, :exam, :diagnosis, :plan, :temp, :weight, :clinic, :vet, :clinic_contact, :vet_contact, :created_by, :created)
'); ');
$stmt->execute([ $stmt->execute([
':aid' => $animalId, ':aid' => $animalId,
':noted_at' => $observedAtSql,
':kind' => $kind, ':kind' => $kind,
':reason' => $reason, ':reason' => $reason,
':symptoms' => $symptoms, ':symptoms' => $symptoms,
@ -282,7 +297,7 @@ trait AnimalMedicalActions
$animalId, $animalId,
(int) $_POST['tariff_id'], (int) $_POST['tariff_id'],
1, 1,
substr(self::now(), 0, 10), $observedAt->format('Y-m-d'),
'Ajoutée avec la note médicale', 'Ajoutée avec la note médicale',
); );
} }
@ -344,7 +359,7 @@ trait AnimalMedicalActions
", ",
)->execute([ )->execute([
':aid' => $animalId, ':aid' => $animalId,
':at' => self::now(), ':at' => $observedAtSql,
':v' => (float) $weight, ':v' => (float) $weight,
':created_by' => Auth::id(), ':created_by' => Auth::id(),
]); ]);
@ -358,7 +373,7 @@ trait AnimalMedicalActions
$db->prepare( $db->prepare(
"UPDATE animals SET sterilized=1,sterilization_status='yes',sterilization_date=COALESCE(sterilization_date,date(:date)),sterilization_vet_contact_id=COALESCE(:vet,sterilization_vet_contact_id),sterilization_clinic_contact_id=COALESCE(:clinic,sterilization_clinic_contact_id) WHERE id=:id", "UPDATE animals SET sterilized=1,sterilization_status='yes',sterilization_date=COALESCE(sterilization_date,date(:date)),sterilization_vet_contact_id=COALESCE(:vet,sterilization_vet_contact_id),sterilization_clinic_contact_id=COALESCE(:clinic,sterilization_clinic_contact_id) WHERE id=:id",
)->execute([ )->execute([
':date' => self::now(), ':date' => $observedAtSql,
':vet' => $vetContact['id'] ?? null, ':vet' => $vetContact['id'] ?? null,
':clinic' => $clinicContact['id'] ?? null, ':clinic' => $clinicContact['id'] ?? null,
':id' => $animalId, ':id' => $animalId,

View file

@ -7,6 +7,7 @@
/** @var array $vaccines */ /** @var array $vaccines */
$defaultDue = date('Y-m-d', strtotime('+1 year')); $defaultDue = date('Y-m-d', strtotime('+1 year'));
$today = date('Y-m-d'); $today = date('Y-m-d');
$currentTime = date('H:i');
?> ?>
<?php if ($canViewMedical): ?> <?php if ($canViewMedical): ?>
@ -612,6 +613,16 @@ $today = date('Y-m-d');
<input type="hidden" name="animal_id" value="<?= (int)$animal['id'] ?>"> <input type="hidden" name="animal_id" value="<?= (int)$animal['id'] ?>">
<div class="row g-2"> <div class="row g-2">
<div class="col-md-4">
<label class="form-label"><?= h(t('common.date')) ?></label>
<input class="form-control" type="date" name="noted_date" value="<?= h($today) ?>" required>
</div>
<div class="col-md-4">
<label class="form-label"><?= h(t('agenda.time')) ?></label>
<input class="form-control" type="time" name="noted_time" value="<?= h($currentTime) ?>" required>
</div>
<div class="col-md-4"> <div class="col-md-4">
<label class="form-label"><?= h(t('animal.form.type')) ?></label> <label class="form-label"><?= h(t('animal.form.type')) ?></label>
<select class="form-select" name="kind"> <select class="form-select" name="kind">
@ -625,7 +636,7 @@ $today = date('Y-m-d');
</select> </select>
</div> </div>
<div class="col-md-8"> <div class="col-12">
<label class="form-label"><?= h(t('animal.reason')) ?></label> <label class="form-label"><?= h(t('animal.reason')) ?></label>
<input class="form-control" name="reason" id="medicalReason" <input class="form-control" name="reason" id="medicalReason"
placeholder="<?= h(t('animal.reason_placeholder')) ?>"> placeholder="<?= h(t('animal.reason_placeholder')) ?>">

View file

@ -31,10 +31,10 @@
<div class="col-lg-6"> <div class="col-lg-6">
<section class="card h-100"> <section class="card h-100">
<div class="card-body"> <div class="card-body">
<span class="badge bg-blue-lt mb-2">1.0.0-rc.18</span> <span class="badge bg-blue-lt mb-2">1.0.0-rc.19</span>
<h3>Site public</h3> <h3>Suivi des soins</h3>
<p class="text-muted">Informations légales accessibles sur chaque page, insertion guidée des images <p class="text-muted">Date et heure modifiables pour distinguer précisément plusieurs contrôles et
dans les contenus et liens sociaux plus lisibles.</p> mesures réalisés au cours d’une même journée.</p>
</div> </div>
</section> </section>
</div> </div>

View file

@ -198,4 +198,5 @@ return [
'updates.directory_failed'=>'Unable to prepare directory :path.', 'updates.directory_failed'=>'Unable to prepare directory :path.',
'updates.download_log'=>'Download technical log', 'updates.download_log'=>'Download technical log',
'updates.log_missing'=>'No update log is available.', 'updates.log_missing'=>'No update log is available.',
'error.invalid_medical_datetime'=>'Enter a valid check-up date and time.',
]; ];

View file

@ -198,4 +198,5 @@ return [
'updates.directory_failed'=>'Impossible de préparer le dossier :path.', 'updates.directory_failed'=>'Impossible de préparer le dossier :path.',
'updates.download_log'=>'Télécharger le journal technique', 'updates.download_log'=>'Télécharger le journal technique',
'updates.log_missing'=>'Aucun journal de mise à jour n’est disponible.', 'updates.log_missing'=>'Aucun journal de mise à jour n’est disponible.',
'error.invalid_medical_datetime'=>'Indiquez une date et une heure de contrôle valides.',
]; ];

View file

@ -360,6 +360,14 @@ try {
$assert((int) $db->query('SELECT COUNT(*) FROM adoptions')->fetchColumn() === 1); $assert((int) $db->query('SELECT COUNT(*) FROM adoptions')->fetchColumn() === 1);
$assert(!$db->query('PRAGMA foreign_key_check')->fetchAll()); $assert(!$db->query('PRAGMA foreign_key_check')->fetchAll());
}); });
$test('Les contrôles médicaux conservent leur date et leur heure', function () use ($assert): void {
$view = file_get_contents(__DIR__ . '/../app/Views/_animal_show_modals.php');
$controller = file_get_contents(__DIR__ . '/../app/Controllers/AnimalMedicalActions.php');
$assert(str_contains($view, 'name="noted_date"'), 'Le champ de date du contrôle est absent');
$assert(str_contains($view, 'name="noted_time"'), 'Le champ d’heure du contrôle est absent');
$assert(str_contains($controller, "':noted_at' => \$observedAtSql"), 'La note ne reprend pas l’instant choisi');
$assert(str_contains($controller, "':at' => \$observedAtSql"), 'La courbe de poids ne reprend pas l’instant choisi');
});
$test('La fiche annuaire agrège adoptions et dépenses', function () use ($db, $assert): void { $test('La fiche annuaire agrège adoptions et dépenses', function () use ($db, $assert): void {
$db->exec("INSERT INTO directory_contacts(kind,name) VALUES('organization','Structure test')"); $db->exec("INSERT INTO directory_contacts(kind,name) VALUES('organization','Structure test')");
$contact = (int) $db->lastInsertId(); $contact = (int) $db->lastInsertId();