From 04cbe54dd1992935779cf1caddd879c24f6a2885 Mon Sep 17 00:00:00 2001 From: oversu <95a7002b-481b-4ef3-b578-f9c421550aa4@localhost> Date: Mon, 7 Sep 2026 23:31:29 +0200 Subject: [PATCH] fix: traiter le premier lot de tickets Refs #4 #5 #6 #9 #12 #16 #18 #20 --- CHANGELOG.md | 10 ++++++++++ HISTORIQUE.md | 4 ++++ VERSION | 2 +- app/Controllers/SettingsController.php | 12 ++++++++++++ app/Services/DemoDataService.php | 10 ++++++++++ app/Services/GeoService.php | 9 ++++++--- app/Views/auth.php | 1 + app/Views/care_round.php | 15 +++++++++++++-- app/Views/evolutions.php | 8 ++++---- app/Views/settings_general.php | 6 ++++-- migrations/091_geocode_cache.sql | 7 +++++++ resources/lang/en.php | 1 + resources/lang/fr.php | 1 + tests/run.php | 13 +++++++++++++ 14 files changed, 87 insertions(+), 12 deletions(-) create mode 100644 migrations/091_geocode_cache.sql diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d16368..c99eebc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,16 @@ 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.7] — 2026-09-07 + +### Premier lot de tickets + +- création et réparation automatique du cache de géocodage afin d’éviter les erreurs lors de l’enregistrement d’un animal, des paramètres ou d’un logo (`#5`, `#16`) ; +- messages explicites lorsqu’une espèce ou une salle existe déjà (`#9`, `#12`) ; +- retour visuel immédiat sur le choix « Donné » ou « Non donné » pendant une tournée (`#20`) ; +- publication de plusieurs animaux adoptables dans le jeu de démonstration (`#18`) ; +- validation française des adresses électroniques et confirmation des URL HTTPS invalides (`#4`, `#6`). + ## [1.0.0-rc.6] — 2026-09-07 ### Compatibilité des ressources diff --git a/HISTORIQUE.md b/HISTORIQUE.md index 9be769c..2012319 100644 --- a/HISTORIQUE.md +++ b/HISTORIQUE.md @@ -33,3 +33,7 @@ Ajout d’une bibliothèque d’images pour les contenus publics, simplification ## `1.0.0-rc.6` — Compatibilité des ressources Remplacement de la police emoji par un format vectoriel accepté par les navigateurs et renforcement de la publication des médias par le paquet YunoHost. + +## `1.0.0-rc.7` — Premier lot de tickets + +Fiabilisation du géocodage et des formulaires de configuration, amélioration des tournées de soins et publication d’animaux adoptables dans le jeu de démonstration. diff --git a/VERSION b/VERSION index 011cdc7..54ed7b5 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.0.0-rc.6 +1.0.0-rc.7 diff --git a/app/Controllers/SettingsController.php b/app/Controllers/SettingsController.php index db100e6..82a2dc8 100644 --- a/app/Controllers/SettingsController.php +++ b/app/Controllers/SettingsController.php @@ -266,6 +266,11 @@ final class SettingsController $color = preg_match('/^#[0-9a-fA-F]{6}$/', (string) ($new['color'] ?? '')) ? (string) $new['color'] : '#6c757d'; + $duplicate = $db->prepare('SELECT 1 FROM shelter_rooms WHERE code=? OR name=? COLLATE NOCASE'); + $duplicate->execute([$code, $newName]); + if ($duplicate->fetchColumn()) { + throw new RuntimeException(t('rooms.duplicate')); + } $db->prepare( 'INSERT INTO shelter_rooms(code,name,room_type,status_code,color,bulk_validation,sort_order) VALUES(?,?,?,?,?,?,?)', )->execute([$code, $newName, $type, $status, $color, isset($new['bulk_validation']) ? 1 : 0, 100]); @@ -351,6 +356,13 @@ final class SettingsController exit(); } try { + $duplicate = $db->prepare( + 'SELECT 1 FROM ref_species WHERE id<>:id AND (code=:code OR name=:name COLLATE NOCASE)', + ); + $duplicate->execute([':id' => $id, ':code' => $code, ':name' => $name]); + if ($duplicate->fetchColumn()) { + throw new RuntimeException(t('settings.species_duplicate')); + } if ($id > 0) { $old = $db->prepare('SELECT code,is_system FROM ref_species WHERE id=:id'); $old->execute([':id' => $id]); diff --git a/app/Services/DemoDataService.php b/app/Services/DemoDataService.php index 5ea91d7..eb9a9c5 100644 --- a/app/Services/DemoDataService.php +++ b/app/Services/DemoDataService.php @@ -375,6 +375,16 @@ final class DemoDataService $animalIds[$name] = (int) $db->lastInsertId(); } + $publish = $db->prepare( + "UPDATE animals SET website_published=1,website_description=? WHERE id=? AND adoption_availability='available'", + ); + foreach (['Moka', 'Pixel', 'Caramel', 'Plume', 'Lune', 'Soleil'] as $name) { + $publish->execute([ + 'Fiche fictive publiée pour découvrir le fonctionnement du site public de démonstration.', + $animalIds[$name], + ]); + } + $db->prepare('INSERT INTO litters(mother_id,birth_date,notes) VALUES(?,?,?)')->execute([ $animalIds['Plume'], $animals[6][3], diff --git a/app/Services/GeoService.php b/app/Services/GeoService.php index bb708a9..b170140 100644 --- a/app/Services/GeoService.php +++ b/app/Services/GeoService.php @@ -9,7 +9,12 @@ final class GeoService return null; } - // Cache DB + // Garde-fou pour les bases anciennes dans lesquelles la table de cache + // n'avait jamais été créée par une migration dédiée. + $db->exec( + "CREATE TABLE IF NOT EXISTS geocode_cache (q TEXT PRIMARY KEY, lat REAL, lng REAL, raw_json TEXT, fetched_at TEXT NOT NULL DEFAULT (datetime('now')))", + ); + $st = $db->prepare('SELECT lat,lng FROM geocode_cache WHERE q=:q LIMIT 1'); $st->execute([':q' => $q]); $row = $st->fetch(PDO::FETCH_ASSOC); @@ -17,7 +22,6 @@ final class GeoService return ['lat' => (float) $row['lat'], 'lng' => (float) $row['lng']]; } - // Nominatim (OpenStreetMap) — respecter l'usage: User-Agent + requêtes raisonnables $url = 'https://nominatim.openstreetmap.org/search?format=jsonv2&limit=1&q=' . rawurlencode($q); $ch = curl_init($url); @@ -43,7 +47,6 @@ final class GeoService $json = json_decode($body, true); if (!is_array($json) || empty($json[0]['lat']) || empty($json[0]['lon'])) { - // Stocker "vide" en cache évite de spammer $db->prepare( "INSERT OR REPLACE INTO geocode_cache(q,lat,lng,raw_json,fetched_at) VALUES(:q,NULL,NULL,:raw,datetime('now'))", )->execute([':q' => $q, ':raw' => $body]); diff --git a/app/Views/auth.php b/app/Views/auth.php index 7c42cc2..747e648 100644 --- a/app/Views/auth.php +++ b/app/Views/auth.php @@ -186,6 +186,7 @@ function frenchMessage(field) { if (field.validity.valueMissing) return ; + if (field.validity.typeMismatch) return ; if (field.validity.tooShort) return '__COUNT__']), JSON_UNESCAPED_UNICODE) ?> .replace('__COUNT__', field.minLength); if (field.validity.patternMismatch) return
💊
-
diff --git a/app/Views/evolutions.php b/app/Views/evolutions.php index 4991e90..2cc6a99 100644 --- a/app/Views/evolutions.php +++ b/app/Views/evolutions.php @@ -31,10 +31,10 @@
- 1.0.0-rc.6 -

Compatibilité des ressources

-

Police emoji compatible avec les navigateurs et publication des médias - renforcée pour les installations YunoHost.

+ 1.0.0-rc.7 +

Premier lot de tickets

+

Géocodage fiabilisé, doublons expliqués, tournées plus lisibles et animaux + adoptables publiés dans la démonstration.

diff --git a/app/Views/settings_general.php b/app/Views/settings_general.php index 5539223..1e9af8f 100644 --- a/app/Views/settings_general.php +++ b/app/Views/settings_general.php @@ -5,7 +5,7 @@
✅
⚠️
-
+
@@ -37,7 +37,9 @@
+ value="" + oninvalid="this.setCustomValidity()" + oninput="this.setCustomValidity('')">

diff --git a/migrations/091_geocode_cache.sql b/migrations/091_geocode_cache.sql new file mode 100644 index 0000000..c2deada --- /dev/null +++ b/migrations/091_geocode_cache.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS geocode_cache ( + q TEXT PRIMARY KEY, + lat REAL, + lng REAL, + raw_json TEXT, + fetched_at TEXT NOT NULL DEFAULT (datetime('now')) +); diff --git a/resources/lang/en.php b/resources/lang/en.php index ddf1740..52f2f6f 100644 --- a/resources/lang/en.php +++ b/resources/lang/en.php @@ -121,4 +121,5 @@ return [ 'search.title'=>'Global search','search.placeholder'=>'Animal, contact, appointment or invoice…','search.action'=>'Search','search.hint'=>'Enter at least two characters.','search.none'=>'No accessible result.','search.open'=>'Open global search','search.shortcut'=>'Ctrl K','search.group.animals'=>'Animals','search.group.directory'=>'Directory','search.group.agenda'=>'Calendar','search.group.accounting'=>'Accounting','search.loading'=>'Searching…','search.all_results'=>'Show all results','search.error'=>'Search is temporarily unavailable.', 'public_site.settings_heading'=>'Public website','public_site.theme_custom'=>'Custom','public_site.site_name'=>'Website name','public_site.custom_css'=>'Custom CSS','public_site.custom_css_help'=>'Used by the custom theme. Imports and external resources are blocked.','public_site.invalid_css'=>'The stylesheet contains a forbidden instruction.','public_site.routing_management_help'=>'Management stays at / and the public website at /site.','public_site.publish_animal'=>'Publish this animal on the public website','public_site.publish_animal_help'=>'The profile will only be visible when the animal is also available for adoption. Only public photos and the text below are used.','lab_reference.add_species'=>'Add a species to lab reports','lab_reference.copy_from'=>'Copy reference ranges from','lab_reference.target_species'=>'New species','lab_reference.copy_help'=>'Ranges are copied as a starting point and remain editable.','lab_reference.invalid_species'=>'Choose two different valid species.','lab_reference.nothing_copied'=>'This species already has these reference ranges.','statistics.hair_types'=>'Hair types','animals.filtered_by'=>'Filter: :value','service.icad.home_unavailable'=>'The I-CAD website is unavailable','service.icad.search_unavailable'=>'I-CAD search is unavailable','service.icad.record_unavailable'=>'The I-CAD record is unavailable', 'public_site.image_library'=>'Image library','public_site.image_file'=>'Image to add','public_site.image_upload'=>'Upload image','public_site.image_help'=>'Upload an image, place the cursor in a text field, then choose “Insert”. Accepted formats: JPEG, PNG and WebP.','public_site.image_none'=>'No image uploaded.','public_site.image_insert'=>'Insert','public_site.image_invalid'=>'Choose a valid image.', + 'rooms.duplicate'=>'A room with this name or code already exists.','settings.species_duplicate'=>'A species with this name or code already exists.', ]; diff --git a/resources/lang/fr.php b/resources/lang/fr.php index ef038bd..3c8d71f 100644 --- a/resources/lang/fr.php +++ b/resources/lang/fr.php @@ -121,4 +121,5 @@ return [ 'search.title'=>'Recherche globale','search.placeholder'=>'Animal, contact, rendez-vous ou facture…','search.action'=>'Rechercher','search.hint'=>'Saisissez au moins deux caractères.','search.none'=>'Aucun résultat accessible.','search.open'=>'Ouvrir la recherche globale','search.shortcut'=>'Ctrl K','search.group.animals'=>'Animaux','search.group.directory'=>'Annuaire','search.group.agenda'=>'Agenda','search.group.accounting'=>'Comptabilité','search.loading'=>'Recherche…','search.all_results'=>'Afficher tous les résultats','search.error'=>'La recherche est momentanément indisponible.', 'public_site.settings_heading'=>'Site public','public_site.theme_custom'=>'Personnalisé','public_site.site_name'=>'Nom du site','public_site.custom_css'=>'CSS personnalisé','public_site.custom_css_help'=>'Actif avec le thème personnalisé. Les imports et les ressources externes sont bloqués.','public_site.invalid_css'=>'La feuille de style contient une instruction interdite.','public_site.routing_management_help'=>'La gestion reste sur / et le site public sur /site.','public_site.publish_animal'=>'Publier cet animal sur le site public','public_site.publish_animal_help'=>'La fiche ne sera visible que si l’animal est également disponible à l’adoption. Seules les photos publiques et le texte ci-dessous seront utilisés.','lab_reference.add_species'=>'Ajouter une espèce aux bilans','lab_reference.copy_from'=>'Copier les références de','lab_reference.target_species'=>'Nouvelle espèce','lab_reference.copy_help'=>'Les intervalles sont copiés comme point de départ, puis restent modifiables.','lab_reference.invalid_species'=>'Choisissez deux espèces différentes et valides.','lab_reference.nothing_copied'=>'Cette espèce possède déjà ces références.','statistics.hair_types'=>'Types de poil','animals.filtered_by'=>'Filtre : :value','service.icad.home_unavailable'=>'Le site I-CAD est indisponible','service.icad.search_unavailable'=>'La recherche I-CAD est indisponible','service.icad.record_unavailable'=>'La fiche I-CAD est indisponible', 'public_site.image_library'=>'Bibliothèque d’images','public_site.image_file'=>'Image à ajouter','public_site.image_upload'=>'Ajouter l’image','public_site.image_help'=>'Ajoutez une image, placez le curseur dans un texte puis choisissez « Insérer ». Formats acceptés : JPEG, PNG et WebP.','public_site.image_none'=>'Aucune image ajoutée.','public_site.image_insert'=>'Insérer','public_site.image_invalid'=>'Choisissez une image valide.', + 'rooms.duplicate'=>'Une salle portant ce nom ou ce code existe déjà.','settings.species_duplicate'=>'Une espèce portant ce nom ou ce code existe déjà.', ]; diff --git a/tests/run.php b/tests/run.php index 91ab43d..1dacc97 100644 --- a/tests/run.php +++ b/tests/run.php @@ -499,6 +499,19 @@ try { ); } $assert(is_file($root . '/public/media/animals/' . $twix . '/twix.webp'), 'Photo de Twix absente'); + $assert( + (int) $db + ->query( + "SELECT COUNT(*) FROM animals WHERE website_published=1 AND adoption_availability='available' AND archived_at IS NULL", + ) + ->fetchColumn() >= 1, + 'Aucun animal de démonstration publié sur le site public', + ); + $assert( + (int) $db->query("SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='geocode_cache'") + ->fetchColumn() === 1, + 'Cache de géocodage absent', + ); DemoDataService::clearFiles(); }); } finally {