diff --git a/CHANGELOG.md b/CHANGELOG.md index 60519dd..3327633 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ 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.13] — 2026-09-08 + +### Gestion du catalogue vétérinaire + +- ajout d’une commande de modification pour les tarifs vétérinaires déjà enregistrés (`#24`) ; +- ajout d’une suppression confirmée qui conserve les montants, libellés et remises des dépenses historiques. + ## [1.0.0-rc.12] — 2026-09-08 ### Tarifs d’adoption et environnement local diff --git a/HISTORIQUE.md b/HISTORIQUE.md index b04839e..842db0b 100644 --- a/HISTORIQUE.md +++ b/HISTORIQUE.md @@ -57,3 +57,7 @@ Ajout de critères d’accueil par salle et de quatre créneaux de tournée conf ## `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. + +## `1.0.0-rc.13` — Catalogue vétérinaire + +Ajout de la modification et de la suppression sécurisée des tarifs, sans altérer les dépenses historiques. diff --git a/VERSION b/VERSION index 884b1ae..624bf34 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.0.0-rc.12 +1.0.0-rc.13 diff --git a/app/Controllers/SettingsController.php b/app/Controllers/SettingsController.php index 6496598..8b60de1 100644 --- a/app/Controllers/SettingsController.php +++ b/app/Controllers/SettingsController.php @@ -613,6 +613,27 @@ final class SettingsController } exit(); } + public static function deleteTariff(): void + { + self::admin(); + self::postOnly(); + $id = (int) ($_POST['id'] ?? 0); + $label = PricingService::deleteTariff($id); + if ($label === null) { + http_response_code(404); + echo h(t('error.tariff_not_found')); + return; + } + AuditService::log( + 'tariff_deleted', + '/settings/pricing/tariff/delete', + 'Tarif supprimé : ' . $label, + 'clinic_tariff', + $id, + ); + header('Location: /settings/pricing?saved=1'); + exit(); + } public static function saveAdoptionFees(): void { self::admin(); diff --git a/app/Routes/web.php b/app/Routes/web.php index 8ff30b2..3205883 100644 --- a/app/Routes/web.php +++ b/app/Routes/web.php @@ -329,6 +329,9 @@ switch ($path) { case '/settings/pricing/tariff': SettingsController::saveTariff(); break; + case '/settings/pricing/tariff/delete': + SettingsController::deleteTariff(); + break; case '/settings/pricing/discount': SettingsController::saveDiscount(); break; diff --git a/app/Services/PricingService.php b/app/Services/PricingService.php index 7ab7ac8..9b41b5b 100644 --- a/app/Services/PricingService.php +++ b/app/Services/PricingService.php @@ -54,6 +54,19 @@ final class PricingService } return (int) $rule[$sterilized ? 'fee_after_sterilization' : 'fee_before_sterilization']; } + + public static function deleteTariff(int $id): ?string + { + $db = DB::pdo(); + $stmt = $db->prepare('SELECT label FROM clinic_tariffs WHERE id=:id'); + $stmt->execute([':id' => $id]); + $label = $stmt->fetchColumn(); + if ($label === false) { + return null; + } + $db->prepare('DELETE FROM clinic_tariffs WHERE id=:id')->execute([':id' => $id]); + return (string) $label; + } 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/evolutions.php b/app/Views/evolutions.php index 4793e2e..13f7036 100644 --- a/app/Views/evolutions.php +++ b/app/Views/evolutions.php @@ -31,9 +31,9 @@
- 1.0.0-rc.12 -

Tarifs d’adoption par espèce

-

Montants avant ou après stérilisation, avec possibilité de laisser le tarif libre.

+ 1.0.0-rc.13 +

Catalogue vétérinaire modifiable

+

Tarifs modifiables ou supprimables tout en préservant les dépenses historiques.

diff --git a/app/Views/settings_pricing.php b/app/Views/settings_pricing.php index bf0e7e2..7458025 100644 --- a/app/Views/settings_pricing.php +++ b/app/Views/settings_pricing.php @@ -98,36 +98,37 @@
-
+ +
+ class="form-control" name="label" id="tariffLabel" required placeholder="">
-
€
+ class="form-control" name="unit_label" id="tariffUnit" value="acte">
-
+ diff --git a/resources/lang/en.php b/resources/lang/en.php index df5939a..e8add48 100644 --- a/resources/lang/en.php +++ b/resources/lang/en.php @@ -126,4 +126,5 @@ return [ 'pricing.adoption_fees_help'=>'Indicative amounts shared by all animals, according to their sterilisation status.','rooms.inactive_help'=>'An unchecked room becomes inactive, while its previous assignments remain in the history. Its internal code never changes.', 'care.noon'=>'noon','care.afternoon'=>'afternoon','rooms.admission_rules'=>'Optional admission criteria','rooms.care_periods'=>'Rounds scheduled for this room','rooms.period_required'=>'Select at least one care-round period per room.','rooms.animal_not_allowed'=>'This animal does not match the admission criteria of the selected room.', 'pricing.before_sterilization'=>'Before sterilisation','pricing.after_sterilization'=>'After sterilisation','pricing.free_fee'=>'Open amount','pricing.free'=>'Open','pricing.adoption_fees_help'=>'Indicative amounts by species. Enable “Open amount” when no fixed fee is required.','animal.sterilization_auto_free'=>'The adoption fee for this species remains open after sterilisation.', + 'pricing.delete_confirm'=>'Delete this pricing item? Previously recorded expenses will retain their label and amount.','pricing.edit_tariff'=>'Edit pricing item', ]; diff --git a/resources/lang/fr.php b/resources/lang/fr.php index 400f321..1c04dcd 100644 --- a/resources/lang/fr.php +++ b/resources/lang/fr.php @@ -126,4 +126,5 @@ return [ 'pricing.adoption_fees_help'=>'Montants indicatifs communs à tous les animaux, selon leur état de stérilisation.','rooms.inactive_help'=>'Une salle décochée devient inactive, mais ses anciennes affectations restent dans l’historique. Son code interne ne change jamais.', 'care.noon'=>'midi','care.afternoon'=>'après-midi','rooms.admission_rules'=>'Critères d’accueil facultatifs','rooms.care_periods'=>'Tournées prévues dans cette salle','rooms.period_required'=>'Sélectionnez au moins un créneau de tournée par salle.','rooms.animal_not_allowed'=>'Cet animal ne correspond pas aux critères d’accueil de la salle choisie.', 'pricing.before_sterilization'=>'Avant stérilisation','pricing.after_sterilization'=>'Après stérilisation','pricing.free_fee'=>'Tarif libre','pricing.free'=>'Libre','pricing.adoption_fees_help'=>'Montants indicatifs par espèce. Activez « Tarif libre » lorsqu’aucun montant fixe n’est imposé.','animal.sterilization_auto_free'=>'Le tarif d’adoption de cette espèce reste libre après la stérilisation.', + 'pricing.delete_confirm'=>'Supprimer ce tarif ? Les dépenses déjà enregistrées conserveront leur libellé et leur montant.','pricing.edit_tariff'=>'Modifier le tarif', ]; diff --git a/tests/run.php b/tests/run.php index 1fb2c48..33457c5 100644 --- a/tests/run.php +++ b/tests/run.php @@ -143,6 +143,17 @@ try { $db->exec("UPDATE adoption_fee_rules SET is_free=1 WHERE species_code='chat'"); $assert(PricingService::adoptionFee('chat', true) === null, 'Le tarif libre n’est pas appliqué'); }); + $test('Un tarif vétérinaire peut être supprimé sans effacer la dépense', function () use ($db, $assert): void { + $db->exec("INSERT INTO animals(internal_code,name,species,status) VALUES('TEST-TARIF','Tarif Test','chat','refuge')"); + $animalId = (int) $db->lastInsertId(); + $db->exec("INSERT INTO clinic_tariffs(clinic_contact_id,label,amount_cents) VALUES(1,'Tarif temporaire',1234)"); + $tariffId = (int) $db->lastInsertId(); + $db->prepare("INSERT INTO animal_expenses(animal_id,clinic_contact_id,tariff_id,label,occurred_on,catalog_unit_cents,total_cents) VALUES(?,1,?,'Tarif temporaire','2099-01-01',1234,1234)")->execute([$animalId, $tariffId]); + $expenseId = (int) $db->lastInsertId(); + $assert(PricingService::deleteTariff($tariffId) === 'Tarif temporaire'); + $expense = $db->query("SELECT tariff_id,label,total_cents FROM animal_expenses WHERE id=$expenseId")->fetch(PDO::FETCH_ASSOC); + $assert($expense && $expense['tariff_id'] === null && $expense['label'] === 'Tarif temporaire' && (int) $expense['total_cents'] === 1234); + }); $test('Les identifiants RNA et SIRET sont contrôlés', function () use ($assert): void { $assert(Ids::validRna('W123456789')); $assert(!Ids::validRna('W1234'));