diff --git a/CHANGELOG.md b/CHANGELOG.md index fe1b643..60519dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ 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.12] — 2026-09-08 + +### Tarifs d’adoption et environnement local + +- grille des frais d’adoption par espèce, avant et après stérilisation, avec possibilité de choisir un tarif libre (`#14`) ; +- reprise automatique des deux anciens montants pour toutes les espèces existantes ; +- affichage du tarif correspondant sur chaque fiche animale, quelle que soit l’espèce ; +- explication des tentatives SSL provoquées par le mode HTTPS uniquement d’un navigateur avec le serveur PHP local (`#18`). + ## [1.0.0-rc.11] — 2026-09-08 ### Configuration des salles et des tournées diff --git a/HISTORIQUE.md b/HISTORIQUE.md index 24e65ce..b04839e 100644 --- a/HISTORIQUE.md +++ b/HISTORIQUE.md @@ -53,3 +53,7 @@ Correction du champ de statut du formulaire animal, stabilisation des codes inte ## `1.0.0-rc.11` — Salles et tournées Ajout de critères d’accueil par salle et de quatre créneaux de tournée configurables sans modifier les réglages existants. + +## `1.0.0-rc.12` — Tarifs par espèce + +Remplacement des deux montants globaux par une grille d’adoption propre à chaque espèce, avec tarif fixe ou libre. diff --git a/README.md b/README.md index b26890b..827cdf9 100644 --- a/README.md +++ b/README.md @@ -98,6 +98,8 @@ Le dernier argument, `public/router.php`, est indispensable : sans lui, les rout Ouvrez ensuite `http://127.0.0.1:8000/setup`. L’assistant vérifie l’environnement, recueille l’identité de l’association et crée le premier compte administrateur. +Le serveur PHP intégré ne prend pas en charge HTTPS. Si le navigateur utilise un mode « HTTPS uniquement », autorisez HTTP pour l’adresse locale ; une tentative en `https://127.0.0.1:8000` apparaît dans le terminal comme une requête SSL non prise en charge. + Les migrations sont appliquées automatiquement. Elles peuvent aussi être exécutées explicitement : ```bash diff --git a/VERSION b/VERSION index 21254c9..884b1ae 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.0.0-rc.11 +1.0.0-rc.12 diff --git a/app/Controllers/SettingsController.php b/app/Controllers/SettingsController.php index 6a2be35..6496598 100644 --- a/app/Controllers/SettingsController.php +++ b/app/Controllers/SettingsController.php @@ -495,6 +495,7 @@ final class SettingsController ->query('SELECT id,name FROM ref_dewormers ORDER BY name COLLATE NOCASE') ->fetchAll(PDO::FETCH_ASSOC), 'settings' => AppSettings::all(), + 'adoptionFees' => PricingService::adoptionFees(), 'saved' => isset($_GET['saved']), 'error' => (string) ($_GET['error'] ?? ''), ]); @@ -616,19 +617,23 @@ final class SettingsController { self::admin(); self::postOnly(); - AppSettings::save( - [ - 'adoption_fee_sterilized' => (string) max( - 0, - min(2000, (int) ($_POST['adoption_fee_sterilized'] ?? 220)), - ), - 'adoption_fee_unsterilized' => (string) max( - 0, - min(2000, (int) ($_POST['adoption_fee_unsterilized'] ?? 200)), - ), - ], - Auth::id(), + $submitted = (array) ($_POST['fees'] ?? []); + $allowed = array_keys(PricingService::adoptionFees()); + $stmt = DB::pdo()->prepare( + "INSERT INTO adoption_fee_rules(species_code,fee_before_sterilization,fee_after_sterilization,is_free,updated_at) VALUES(?,?,?,?,datetime('now')) ON CONFLICT(species_code) DO UPDATE SET fee_before_sterilization=excluded.fee_before_sterilization,fee_after_sterilization=excluded.fee_after_sterilization,is_free=excluded.is_free,updated_at=datetime('now')", ); + foreach ($submitted as $species => $values) { + $species = SpeciesService::normalize((string) $species); + if (!in_array($species, $allowed, true) || !is_array($values)) { + continue; + } + $stmt->execute([ + $species, + max(0, min(2000, (int) ($values['before'] ?? 0))), + max(0, min(2000, (int) ($values['after'] ?? 0))), + isset($values['free']) ? 1 : 0, + ]); + } AuditService::log('adoption_fees_saved', '/settings/pricing/adoption', 'Tarifs d’adoption modifiés'); header('Location: /settings/pricing?saved=1'); exit(); diff --git a/app/Services/PricingService.php b/app/Services/PricingService.php index a556caa..7ab7ac8 100644 --- a/app/Services/PricingService.php +++ b/app/Services/PricingService.php @@ -37,6 +37,23 @@ final class PricingService { return max(0, (int) round((float) str_replace(',', '.', trim($v)) * 100)); } + public static function adoptionFees(): array + { + $rows = DB::pdo()->query( + "SELECT s.code,s.name,s.icon,COALESCE(r.fee_before_sterilization,200) fee_before_sterilization,COALESCE(r.fee_after_sterilization,220) fee_after_sterilization,COALESCE(r.is_free,0) is_free FROM ref_species s LEFT JOIN adoption_fee_rules r ON r.species_code=s.code WHERE s.active=1 ORDER BY s.sort_order,s.name COLLATE NOCASE", + )->fetchAll(PDO::FETCH_ASSOC); + return array_column($rows, null, 'code'); + } + + public static function adoptionFee(string $species, bool $sterilized): ?int + { + $fees = self::adoptionFees(); + $rule = $fees[SpeciesService::normalize($species)] ?? null; + if (!$rule || (int) $rule['is_free'] === 1) { + return null; + } + return (int) $rule[$sterilized ? 'fee_after_sterilization' : 'fee_before_sterilization']; + } public static function tariffs(bool $active = false): array { $w = $active ? 'WHERE ct.active=1 AND dc.deleted_at IS NULL' : ''; diff --git a/app/Views/_animal_show_modals.php b/app/Views/_animal_show_modals.php index 98be9d7..4792870 100644 --- a/app/Views/_animal_show_modals.php +++ b/app/Views/_animal_show_modals.php @@ -641,7 +641,7 @@ $today = date('Y-m-d'); = h(t('animal.sterilization_act')) ?> = h(!empty($animal['sterilized']) ? t('animal.already_sterilized') : t('animal.sterilization_auto', ['price' => AppSettings::int('adoption_fee_sterilized')])) ?> + class="form-hint mt-0">= h(!empty($animal['sterilized']) ? t('animal.already_sterilized') : ($feeAfterSterilization === null ? t('animal.sterilization_auto_free') : t('animal.sterilization_auto', ['price' => $feeAfterSterilization]))) ?> diff --git a/app/Views/animal_show.php b/app/Views/animal_show.php index eb1867f..777d7ca 100644 --- a/app/Views/animal_show.php +++ b/app/Views/animal_show.php @@ -285,9 +285,9 @@ if ($felvPos) { = h($sterilizationLabel) ?> - = h(t('animal.adoption_fee_hint')) ?> - = $sterilizationState === 'yes' ? AppSettings::int('adoption_fee_sterilized') . ' €' : AppSettings::int('adoption_fee_unsterilized') . ' €' ?> + + = h(t('animal.adoption_fee_hint')) ?> + = $adoptionFee === null ? h(t('pricing.free_fee')) : $adoptionFee . ' €' ?> = !empty($animal['sterilization_date']) ? h(date('d/m/Y', strtotime((string)$animal['sterilization_date']))) : '' ?>= !empty($sterilizationContacts['vet']['name']) ? ' · ' . h($sterilizationContacts['vet']['name']) : '' ?>= !empty($sterilizationContacts['clinic']['name']) ? ' · ' . h($sterilizationContacts['clinic']['name']) : '' ?> diff --git a/app/Views/evolutions.php b/app/Views/evolutions.php index fb2370d..4793e2e 100644 --- a/app/Views/evolutions.php +++ b/app/Views/evolutions.php @@ -31,9 +31,9 @@
Critères d’accueil par salle et créneaux de soins adaptés à l’organisation du refuge.
+ 1.0.0-rc.12 +Montants avant ou après stérilisation, avec possibilité de laisser le tarif libre.
| = h(t('animal.species')) ?> | = h(t('pricing.before_sterilization')) ?> | = h(t('pricing.after_sterilization')) ?> | = h(t('pricing.free_fee')) ?> |
|---|---|---|---|
| = h(($fee['icon'] ?? '') . ' ' . $fee['name']) ?> | +€ |
+ € |
+ + |