parent
e7176b6371
commit
1378aefa9c
14 changed files with 93 additions and 35 deletions
|
|
@ -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/).
|
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
|
## [1.0.0-rc.11] — 2026-09-08
|
||||||
|
|
||||||
### Configuration des salles et des tournées
|
### Configuration des salles et des tournées
|
||||||
|
|
|
||||||
|
|
@ -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
|
## `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.
|
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.
|
||||||
|
|
|
||||||
|
|
@ -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.
|
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 :
|
Les migrations sont appliquées automatiquement. Elles peuvent aussi être exécutées explicitement :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
|
||||||
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
||||||
1.0.0-rc.11
|
1.0.0-rc.12
|
||||||
|
|
|
||||||
|
|
@ -495,6 +495,7 @@ final class SettingsController
|
||||||
->query('SELECT id,name FROM ref_dewormers ORDER BY name COLLATE NOCASE')
|
->query('SELECT id,name FROM ref_dewormers ORDER BY name COLLATE NOCASE')
|
||||||
->fetchAll(PDO::FETCH_ASSOC),
|
->fetchAll(PDO::FETCH_ASSOC),
|
||||||
'settings' => AppSettings::all(),
|
'settings' => AppSettings::all(),
|
||||||
|
'adoptionFees' => PricingService::adoptionFees(),
|
||||||
'saved' => isset($_GET['saved']),
|
'saved' => isset($_GET['saved']),
|
||||||
'error' => (string) ($_GET['error'] ?? ''),
|
'error' => (string) ($_GET['error'] ?? ''),
|
||||||
]);
|
]);
|
||||||
|
|
@ -616,19 +617,23 @@ final class SettingsController
|
||||||
{
|
{
|
||||||
self::admin();
|
self::admin();
|
||||||
self::postOnly();
|
self::postOnly();
|
||||||
AppSettings::save(
|
$submitted = (array) ($_POST['fees'] ?? []);
|
||||||
[
|
$allowed = array_keys(PricingService::adoptionFees());
|
||||||
'adoption_fee_sterilized' => (string) max(
|
$stmt = DB::pdo()->prepare(
|
||||||
0,
|
"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')",
|
||||||
min(2000, (int) ($_POST['adoption_fee_sterilized'] ?? 220)),
|
|
||||||
),
|
|
||||||
'adoption_fee_unsterilized' => (string) max(
|
|
||||||
0,
|
|
||||||
min(2000, (int) ($_POST['adoption_fee_unsterilized'] ?? 200)),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
Auth::id(),
|
|
||||||
);
|
);
|
||||||
|
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');
|
AuditService::log('adoption_fees_saved', '/settings/pricing/adoption', 'Tarifs d’adoption modifiés');
|
||||||
header('Location: /settings/pricing?saved=1');
|
header('Location: /settings/pricing?saved=1');
|
||||||
exit();
|
exit();
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,23 @@ final class PricingService
|
||||||
{
|
{
|
||||||
return max(0, (int) round((float) str_replace(',', '.', trim($v)) * 100));
|
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
|
public static function tariffs(bool $active = false): array
|
||||||
{
|
{
|
||||||
$w = $active ? 'WHERE ct.active=1 AND dc.deleted_at IS NULL' : '';
|
$w = $active ? 'WHERE ct.active=1 AND dc.deleted_at IS NULL' : '';
|
||||||
|
|
|
||||||
|
|
@ -641,7 +641,7 @@ $today = date('Y-m-d');
|
||||||
<span class="form-check-label">
|
<span class="form-check-label">
|
||||||
<span class="fw-bold d-block"><?= h(t('animal.sterilization_act')) ?></span>
|
<span class="fw-bold d-block"><?= h(t('animal.sterilization_act')) ?></span>
|
||||||
<span
|
<span
|
||||||
class="form-hint mt-0"><?= h(!empty($animal['sterilized']) ? t('animal.already_sterilized') : t('animal.sterilization_auto', ['price' => AppSettings::int('adoption_fee_sterilized')])) ?></span>
|
class="form-hint mt-0"><?php $feeAfterSterilization = PricingService::adoptionFee((string)$animal['species'], true); ?><?= h(!empty($animal['sterilized']) ? t('animal.already_sterilized') : ($feeAfterSterilization === null ? t('animal.sterilization_auto_free') : t('animal.sterilization_auto', ['price' => $feeAfterSterilization]))) ?></span>
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -285,9 +285,9 @@ if ($felvPos) {
|
||||||
<span class="badge <?= h($sterilizationClass) ?>">
|
<span class="badge <?= h($sterilizationClass) ?>">
|
||||||
<?= h($sterilizationLabel) ?>
|
<?= h($sterilizationLabel) ?>
|
||||||
</span>
|
</span>
|
||||||
<?php if ($isCat): ?><span class="text-muted small"><?= h(t('animal.adoption_fee_hint')) ?>
|
<?php $adoptionFee = PricingService::adoptionFee((string)$animal['species'], $sterilizationState === 'yes'); ?>
|
||||||
<strong
|
<span class="text-muted small"><?= h(t('animal.adoption_fee_hint')) ?>
|
||||||
class="text-body"><?= $sterilizationState === 'yes' ? AppSettings::int('adoption_fee_sterilized') . ' €' : AppSettings::int('adoption_fee_unsterilized') . ' €' ?></strong></span><?php endif; ?>
|
<strong class="text-body"><?= $adoptionFee === null ? h(t('pricing.free_fee')) : $adoptionFee . ' €' ?></strong></span>
|
||||||
<?php if ($sterilizationState === 'yes' && (!empty($animal['sterilization_date']) || !empty($sterilizationContacts))): ?><span
|
<?php if ($sterilizationState === 'yes' && (!empty($animal['sterilization_date']) || !empty($sterilizationContacts))): ?><span
|
||||||
class="text-muted small"><?= !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']) : '' ?></span><?php endif; ?>
|
class="text-muted small"><?= !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']) : '' ?></span><?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -31,9 +31,9 @@
|
||||||
<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.11</span>
|
<span class="badge bg-blue-lt mb-2">1.0.0-rc.12</span>
|
||||||
<h3>Salles et tournées configurables</h3>
|
<h3>Tarifs d’adoption par espèce</h3>
|
||||||
<p class="text-muted">Critères d’accueil par salle et créneaux de soins adaptés à l’organisation du refuge.</p>
|
<p class="text-muted">Montants avant ou après stérilisation, avec possibilité de laisser le tarif libre.</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
<?php if ($saved): ?><div class="alert alert-success">✅ <?= h(t('pricing.saved')) ?></div>
|
<?php if ($saved): ?><div class="alert alert-success">✅ <?= h(t('pricing.saved')) ?></div>
|
||||||
<?php endif; ?><?php if ($error !== ''): ?><div class="alert alert-danger"><?= h($error) ?></div><?php endif; ?>
|
<?php endif; ?><?php if ($error !== ''): ?><div class="alert alert-danger"><?= h($error) ?></div><?php endif; ?>
|
||||||
<div class="row row-cards mb-4">
|
<div class="row row-cards mb-4">
|
||||||
<div class="col-lg-5">
|
<div class="col-lg-7">
|
||||||
<form class="card h-100" method="post" action="/settings/pricing/adoption">
|
<form class="card h-100" method="post" action="/settings/pricing/adoption">
|
||||||
<div class="card-status-top bg-green"></div>
|
<div class="card-status-top bg-green"></div>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
|
|
@ -15,24 +15,19 @@
|
||||||
<div class="text-muted small"><?= h(t('pricing.adoption_fees_help')) ?></div>
|
<div class="text-muted small"><?= h(t('pricing.adoption_fees_help')) ?></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body row g-3">
|
<div class="table-responsive">
|
||||||
<div class="col-6"><label class="form-label"><?= h(t('pricing.sterilized')) ?></label>
|
<table class="table table-vcenter mb-0"><thead><tr><th><?= h(t('animal.species')) ?></th><th><?= h(t('pricing.before_sterilization')) ?></th><th><?= h(t('pricing.after_sterilization')) ?></th><th><?= h(t('pricing.free_fee')) ?></th></tr></thead><tbody>
|
||||||
<div class="input-group"><input class="form-control" type="number" min="0"
|
<?php foreach ($adoptionFees as $species => $fee): ?><tr><td class="fw-semibold"><?= h(($fee['icon'] ?? '') . ' ' . $fee['name']) ?></td>
|
||||||
name="adoption_fee_sterilized"
|
<td><div class="input-group"><input class="form-control" type="number" min="0" max="2000" name="fees[<?= h($species) ?>][before]" value="<?= (int)$fee['fee_before_sterilization'] ?>"><span class="input-group-text">€</span></div></td>
|
||||||
value="<?= (int)$settings['adoption_fee_sterilized'] ?>"><span
|
<td><div class="input-group"><input class="form-control" type="number" min="0" max="2000" name="fees[<?= h($species) ?>][after]" value="<?= (int)$fee['fee_after_sterilization'] ?>"><span class="input-group-text">€</span></div></td>
|
||||||
class="input-group-text">€</span></div>
|
<td><label class="form-check form-switch"><input class="form-check-input adoption-fee-free" type="checkbox" name="fees[<?= h($species) ?>][free]" <?= (int)$fee['is_free'] === 1 ? 'checked' : '' ?>><span class="form-check-label"><?= h(t('pricing.free')) ?></span></label></td>
|
||||||
</div>
|
</tr><?php endforeach; ?>
|
||||||
<div class="col-6"><label class="form-label"><?= h(t('pricing.unsterilized')) ?></label>
|
</tbody></table>
|
||||||
<div class="input-group"><input class="form-control" type="number" min="0"
|
|
||||||
name="adoption_fee_unsterilized"
|
|
||||||
value="<?= (int)$settings['adoption_fee_unsterilized'] ?>"><span
|
|
||||||
class="input-group-text">€</span></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer text-end"><button class="btn btn-primary"><?= h(t('common.save')) ?></button></div>
|
<div class="card-footer text-end"><button class="btn btn-primary"><?= h(t('common.save')) ?></button></div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-lg-7">
|
<div class="col-lg-5">
|
||||||
<div class="card h-100">
|
<div class="card h-100">
|
||||||
<div class="card-status-top bg-orange"></div>
|
<div class="card-status-top bg-orange"></div>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
|
|
|
||||||
14
migrations/094_species_adoption_fees.sql
Normal file
14
migrations/094_species_adoption_fees.sql
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
CREATE TABLE adoption_fee_rules (
|
||||||
|
species_code TEXT PRIMARY KEY,
|
||||||
|
fee_before_sterilization INTEGER NOT NULL DEFAULT 200,
|
||||||
|
fee_after_sterilization INTEGER NOT NULL DEFAULT 220,
|
||||||
|
is_free INTEGER NOT NULL DEFAULT 0,
|
||||||
|
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||||
|
FOREIGN KEY(species_code) REFERENCES ref_species(code) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT OR IGNORE INTO adoption_fee_rules(species_code,fee_before_sterilization,fee_after_sterilization)
|
||||||
|
SELECT s.code,
|
||||||
|
COALESCE((SELECT CAST(setting_value AS INTEGER) FROM app_settings WHERE setting_key='adoption_fee_unsterilized'),200),
|
||||||
|
COALESCE((SELECT CAST(setting_value AS INTEGER) FROM app_settings WHERE setting_key='adoption_fee_sterilized'),220)
|
||||||
|
FROM ref_species s;
|
||||||
|
|
@ -125,4 +125,5 @@ return [
|
||||||
'settings.invalid_rna'=>'The RNA number must start with W followed by 9 digits.','settings.invalid_siret'=>'The SIRET number must contain 14 digits and be valid.','rooms.name_too_long'=>'A room name cannot exceed 100 characters.','rooms.box_name_too_long'=>'A box name cannot exceed 100 characters.','animal.name_too_long'=>'An animal name cannot exceed 150 characters.',
|
'settings.invalid_rna'=>'The RNA number must start with W followed by 9 digits.','settings.invalid_siret'=>'The SIRET number must contain 14 digits and be valid.','rooms.name_too_long'=>'A room name cannot exceed 100 characters.','rooms.box_name_too_long'=>'A box name cannot exceed 100 characters.','animal.name_too_long'=>'An animal name cannot exceed 150 characters.',
|
||||||
'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.',
|
'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.',
|
'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.',
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -125,4 +125,5 @@ return [
|
||||||
'settings.invalid_rna'=>'Le numéro RNA doit commencer par W et être suivi de 9 chiffres.','settings.invalid_siret'=>'Le numéro SIRET doit contenir 14 chiffres et être valide.','rooms.name_too_long'=>'Le nom d’une salle ne peut pas dépasser 100 caractères.','rooms.box_name_too_long'=>'Le nom d’un box ne peut pas dépasser 100 caractères.','animal.name_too_long'=>'Le nom d’un animal ne peut pas dépasser 150 caractères.',
|
'settings.invalid_rna'=>'Le numéro RNA doit commencer par W et être suivi de 9 chiffres.','settings.invalid_siret'=>'Le numéro SIRET doit contenir 14 chiffres et être valide.','rooms.name_too_long'=>'Le nom d’une salle ne peut pas dépasser 100 caractères.','rooms.box_name_too_long'=>'Le nom d’un box ne peut pas dépasser 100 caractères.','animal.name_too_long'=>'Le nom d’un animal ne peut pas dépasser 150 caractères.',
|
||||||
'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.',
|
'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.',
|
'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.',
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ foreach (
|
||||||
'NotificationService',
|
'NotificationService',
|
||||||
'PrivacyService',
|
'PrivacyService',
|
||||||
'StatisticsService',
|
'StatisticsService',
|
||||||
|
'PricingService',
|
||||||
'SpeciesService',
|
'SpeciesService',
|
||||||
'LabReferenceService',
|
'LabReferenceService',
|
||||||
'PublicContentFormatter',
|
'PublicContentFormatter',
|
||||||
|
|
@ -133,6 +134,15 @@ try {
|
||||||
$assert(AppSettings::get('app_language') === 'en');
|
$assert(AppSettings::get('app_language') === 'en');
|
||||||
I18n::setLocale('fr');
|
I18n::setLocale('fr');
|
||||||
});
|
});
|
||||||
|
$test('Les frais d’adoption sont configurables par espèce', function () use ($db, $assert): void {
|
||||||
|
$fees = PricingService::adoptionFees();
|
||||||
|
$assert(isset($fees['chat']), 'Tarif du chat absent');
|
||||||
|
$db->exec("UPDATE adoption_fee_rules SET fee_before_sterilization=175,fee_after_sterilization=225,is_free=0 WHERE species_code='chat'");
|
||||||
|
$assert(PricingService::adoptionFee('chat', false) === 175);
|
||||||
|
$assert(PricingService::adoptionFee('chat', true) === 225);
|
||||||
|
$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('Les identifiants RNA et SIRET sont contrôlés', function () use ($assert): void {
|
$test('Les identifiants RNA et SIRET sont contrôlés', function () use ($assert): void {
|
||||||
$assert(Ids::validRna('W123456789'));
|
$assert(Ids::validRna('W123456789'));
|
||||||
$assert(!Ids::validRna('W1234'));
|
$assert(!Ids::validRna('W1234'));
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue