Horodate précisément les contrôles de soins
This commit is contained in:
parent
81f259ea6e
commit
a0ea338292
10 changed files with 60 additions and 12 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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).
|
||||||
|
|
||||||
|
|
|
||||||
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
||||||
1.0.0-rc.18
|
1.0.0-rc.19
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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')) ?>">
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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.',
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -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.',
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue