fix: fiabiliser les fiches animales et vaccinations
This commit is contained in:
parent
f45cfc40f2
commit
361bef8561
20 changed files with 157 additions and 65 deletions
12
CHANGELOG.md
12
CHANGELOG.md
|
|
@ -2,6 +2,18 @@
|
|||
|
||||
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.26] — 2026-09-15
|
||||
|
||||
### Données animales et suivi vétérinaire
|
||||
|
||||
- suppression sûre de l’ancienne contrainte SQLite limitant les salles à « silver » et « twix » ;
|
||||
- reconstruction transactionnelle de la table des animaux avec conservation de toutes les fiches, relations, vues et index ;
|
||||
- ajout d’un test de sauvegarde réelle contrôlant le nombre d’animaux, l’intégrité SQLite et les clés étrangères ;
|
||||
- suppression des avertissements `open_basedir` pendant la détection de l’outil d’optimisation des images.
|
||||
- remplacement des anciens états I-CAD par « Inconnu », « Enregistré », « En attente de changement » et « Propriétaire changé » ;
|
||||
- retrait du champ redondant « Administré par » lors de la saisie d’un vaccin ;
|
||||
- affichage du cabinet vétérinaire dans le récapitulatif des vaccinations et son export.
|
||||
|
||||
## [1.0.0-rc.25] — 2026-09-15
|
||||
|
||||
### Modification des portées
|
||||
|
|
|
|||
|
|
@ -10,6 +10,11 @@ L’import depuis Animal Shelter Manager a été conçu comme un parcours pruden
|
|||
|
||||
Avant la version 1.0, le projet a fait l’objet d’un nettoyage structurel et d’un découpage de ses fichiers les plus volumineux. La recherche universelle, les salles configurables, les traductions française et anglaise et une suite de contrôles isolés ont complété cette phase de stabilisation.
|
||||
|
||||
## `1.0.0-rc.26` — Salles réellement configurables
|
||||
|
||||
Retrait de la dernière contrainte héritée qui limitait encore les fiches animales aux deux premières salles historiques. La migration conserve les animaux et toutes leurs relations tout en autorisant les salles définies dans la configuration.
|
||||
Les états d’enregistrement I-CAD suivent désormais le changement de propriétaire, tandis que le suivi vaccinal s’appuie sans doublon sur le cabinet et le vétérinaire de l’annuaire.
|
||||
|
||||
## `1.0.0-rc.25` — Portées modifiables
|
||||
|
||||
Les portées ne sont plus figées après leur création : parents et date peuvent être corrigés, tandis que les chatons peuvent être ajoutés, modifiés ou détachés sans perte de leur fiche.
|
||||
|
|
|
|||
|
|
@ -200,7 +200,7 @@ L’option `--apply` ne doit être utilisée qu’après lecture des rapports. L
|
|||
|
||||
## État du projet
|
||||
|
||||
La candidate actuelle est `1.0.0-rc.25`. 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.26`. 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).
|
||||
|
||||
|
|
|
|||
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
|||
1.0.0-rc.25
|
||||
1.0.0-rc.26
|
||||
|
|
|
|||
|
|
@ -612,12 +612,11 @@ trait AnimalBrowseActions
|
|||
|
||||
// Vaccins
|
||||
$stmt = $db->prepare('
|
||||
SELECT v.*, rv.name AS vaccine_name, COALESCE(dc.name, c.name) AS clinic_name,COALESCE(v.administered_by_name,au.display_name,au.username) AS administrator_name
|
||||
SELECT v.*, rv.name AS vaccine_name, COALESCE(dc.name, c.name) AS clinic_name
|
||||
FROM vaccinations v
|
||||
JOIN ref_vaccines rv ON rv.id = v.vaccine_id
|
||||
LEFT JOIN clinics c ON c.id = v.clinic_id
|
||||
LEFT JOIN directory_contacts dc ON dc.id = v.clinic_contact_id
|
||||
LEFT JOIN users au ON au.id=v.administered_by_user_id
|
||||
WHERE v.animal_id = :id
|
||||
ORDER BY v.done_date DESC, v.id DESC
|
||||
LIMIT 50
|
||||
|
|
@ -833,7 +832,7 @@ trait AnimalBrowseActions
|
|||
CASE WHEN COALESCE(v.lot,'')!='' THEN '\nLot : ' || v.lot ELSE '' END ||
|
||||
CASE WHEN COALESCE(v.manufacturer,'')!='' THEN '\nFabricant : ' || v.manufacturer ELSE '' END ||
|
||||
CASE WHEN COALESCE(v.batch_expires_on,'')!='' THEN '\nExpiration du lot : ' || v.batch_expires_on ELSE '' END ||
|
||||
CASE WHEN COALESCE(v.administered_by_name,'')!='' THEN '\nAdministré par : ' || v.administered_by_name WHEN v.administered_by_user_id IS NOT NULL THEN '\nAdministré par : ' || COALESCE((SELECT display_name FROM users WHERE id=v.administered_by_user_id),(SELECT username FROM users WHERE id=v.administered_by_user_id),'Compte inconnu') ELSE '' END ||
|
||||
CASE WHEN v.clinic_contact_id IS NOT NULL THEN '\nCabinet vétérinaire : ' || COALESCE((SELECT name FROM directory_contacts WHERE id=v.clinic_contact_id),'Cabinet inconnu') WHEN v.clinic_id IS NOT NULL THEN '\nCabinet vétérinaire : ' || COALESCE((SELECT name FROM clinics WHERE id=v.clinic_id),'Cabinet inconnu') ELSE '' END ||
|
||||
CASE WHEN COALESCE(v.notes,'')!='' THEN '\nNotes : ' || v.notes ELSE '' END
|
||||
) AS details,
|
||||
datetime(v.done_date || ' 12:00:00') AS at
|
||||
|
|
|
|||
|
|
@ -630,15 +630,6 @@ trait AnimalMedicalActions
|
|||
echo h(t('error.invalid_batch_expiry'));
|
||||
return;
|
||||
}
|
||||
$administratorUserId = (int) ($_POST['administered_by_user_id'] ?? 0) ?: null;
|
||||
$administratorName = trim((string) ($_POST['administered_by_name'] ?? ''));
|
||||
if ($administratorUserId) {
|
||||
$check = $db->prepare('SELECT 1 FROM users WHERE id=:id AND active=1');
|
||||
$check->execute([':id' => $administratorUserId]);
|
||||
if (!$check->fetchColumn()) {
|
||||
$administratorUserId = null;
|
||||
}
|
||||
}
|
||||
$notes = trim((string) ($_POST['notes'] ?? ''));
|
||||
$clinic_id = (int) ($_POST['clinic_id'] ?? 0);
|
||||
$clinic_id = $clinic_id > 0 ? $clinic_id : null;
|
||||
|
|
@ -668,8 +659,8 @@ trait AnimalMedicalActions
|
|||
$vet_name = $vetContact['name'] ?? $vet_name;
|
||||
|
||||
$stmt = $db->prepare('
|
||||
INSERT INTO vaccinations(animal_id, vaccine_id, done_date, due_date, lot,manufacturer,batch_expires_on,administered_by_user_id,administered_by_name, notes, clinic_id, vet_name, clinic_contact_id, vet_contact_id, created_by)
|
||||
VALUES(:aid,:vid,:done,:due,:lot,:manufacturer,:batch_expiry,:administrator_user,:administrator_name,:notes,:clinic_id,:vet_name,:clinic_contact_id,:vet_contact_id,:created_by)
|
||||
INSERT INTO vaccinations(animal_id, vaccine_id, done_date, due_date, lot,manufacturer,batch_expires_on, notes, clinic_id, vet_name, clinic_contact_id, vet_contact_id, created_by)
|
||||
VALUES(:aid,:vid,:done,:due,:lot,:manufacturer,:batch_expiry,:notes,:clinic_id,:vet_name,:clinic_contact_id,:vet_contact_id,:created_by)
|
||||
');
|
||||
$stmt->execute([
|
||||
':aid' => $id,
|
||||
|
|
@ -679,8 +670,6 @@ trait AnimalMedicalActions
|
|||
':lot' => $lot !== '' ? $lot : null,
|
||||
':manufacturer' => $manufacturer ?: null,
|
||||
':batch_expiry' => $batchExpiresOn ?: null,
|
||||
':administrator_user' => $administratorUserId,
|
||||
':administrator_name' => $administratorName ?: null,
|
||||
':notes' => $notes !== '' ? $notes : null,
|
||||
':clinic_id' => $clinic_id,
|
||||
':vet_name' => $vet_name,
|
||||
|
|
@ -707,10 +696,7 @@ trait AnimalMedicalActions
|
|||
($manufacturer !== '' ? "\nFabricant : $manufacturer" : '') .
|
||||
($lot !== '' ? "\nLot : $lot" : '') .
|
||||
($batchExpiresOn !== '' ? "\nExpiration du lot : $batchExpiresOn" : '') .
|
||||
($administratorName !== '' || $administratorUserId
|
||||
? "\nAdministré par : " .
|
||||
($administratorName !== '' ? $administratorName : 'utilisateur #' . $administratorUserId)
|
||||
: ''),
|
||||
"\nCabinet vétérinaire : " . ($clinicContact['name'] ?? ''),
|
||||
);
|
||||
|
||||
$db->prepare("UPDATE animals SET updated_at = datetime('now') WHERE id = :id")->execute([':id' => $id]);
|
||||
|
|
|
|||
|
|
@ -376,7 +376,10 @@ trait AnimalRecordActions
|
|||
}
|
||||
$identificationDate = trim((string) ($_POST['identification_date'] ?? ''));
|
||||
$identificationRegistration = (string) ($_POST['identification_registration_status'] ?? 'unknown');
|
||||
if (!in_array($identificationRegistration, ['unknown', 'pending', 'registered'], true)) {
|
||||
if ($identificationRegistration === 'pending') {
|
||||
$identificationRegistration = 'change_pending';
|
||||
}
|
||||
if (!in_array($identificationRegistration, ['unknown', 'registered', 'change_pending', 'owner_changed'], true)) {
|
||||
$identificationRegistration = 'unknown';
|
||||
}
|
||||
if ($chip_id === '') {
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@ final class Asm3MigrationPlanner
|
|||
'identification_date' => $date($chip !== '' ? $r['IDENTICHIPDATE'] ?? null : $r['TATTOODATE'] ?? null),
|
||||
'identification_registration_status' => match ((string) ($r['IDENTICHIPSTATUS'] ?? '')) {
|
||||
'1' => 'registered',
|
||||
'0' => 'pending',
|
||||
'0' => 'change_pending',
|
||||
default => 'unknown',
|
||||
},
|
||||
'sterilization_status' => $yes($r['NEUTERED'] ?? 0) ? 'yes' : 'unknown',
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ final class ExportService
|
|||
'vaccinations' => [
|
||||
'Vaccinations',
|
||||
'vaccinations.csv',
|
||||
'SELECT v.id,a.internal_code code_animal,a.name animal,rv.name vaccin,v.done_date date_realisation,v.due_date prochain_rappel,v.manufacturer fabricant,v.lot,v.batch_expires_on expiration_lot,COALESCE(v.administered_by_name,au.display_name,au.username) administre_par,COALESCE(cu.display_name,cu.username) saisi_par,vet.name veterinaire,clinic.name cabinet,v.notes FROM vaccinations v JOIN animals a ON a.id=v.animal_id JOIN ref_vaccines rv ON rv.id=v.vaccine_id LEFT JOIN directory_contacts vet ON vet.id=v.vet_contact_id LEFT JOIN directory_contacts clinic ON clinic.id=v.clinic_contact_id LEFT JOIN users au ON au.id=v.administered_by_user_id LEFT JOIN users cu ON cu.id=v.created_by ORDER BY v.done_date,v.id',
|
||||
'SELECT v.id,a.internal_code code_animal,a.name animal,rv.name vaccin,v.done_date date_realisation,v.due_date prochain_rappel,v.manufacturer fabricant,v.lot,v.batch_expires_on expiration_lot,clinic.name administre_par,COALESCE(cu.display_name,cu.username) saisi_par,vet.name veterinaire,clinic.name cabinet,v.notes FROM vaccinations v JOIN animals a ON a.id=v.animal_id JOIN ref_vaccines rv ON rv.id=v.vaccine_id LEFT JOIN directory_contacts vet ON vet.id=v.vet_contact_id LEFT JOIN directory_contacts clinic ON clinic.id=v.clinic_contact_id LEFT JOIN users cu ON cu.id=v.created_by ORDER BY v.done_date,v.id',
|
||||
],
|
||||
'deaths' => [
|
||||
'Décès',
|
||||
|
|
|
|||
|
|
@ -131,7 +131,7 @@ final class ImageService
|
|||
['/usr/bin/magick', '/usr/local/bin/magick', '/usr/bin/convert', '/usr/local/bin/convert']
|
||||
as $binary
|
||||
) {
|
||||
if (is_executable($binary)) {
|
||||
if (@is_executable($binary)) {
|
||||
return $binary;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,13 +91,21 @@ final class Migrations
|
|||
}
|
||||
}
|
||||
|
||||
$relaxesForeignKeys = $name === '093_room_rules_and_care_periods.sql';
|
||||
$relaxesForeignKeys = in_array(
|
||||
$name,
|
||||
['093_room_rules_and_care_periods.sql', '097_remove_legacy_room_constraint.sql'],
|
||||
true,
|
||||
);
|
||||
if ($relaxesForeignKeys) {
|
||||
$db->exec('PRAGMA foreign_keys = OFF');
|
||||
}
|
||||
$db->beginTransaction();
|
||||
try {
|
||||
if ($name === '097_remove_legacy_room_constraint.sql') {
|
||||
self::modernizeAnimalConstraints($db);
|
||||
} else {
|
||||
$db->exec($sql);
|
||||
}
|
||||
$ins = $db->prepare("INSERT INTO _migrations(name, applied_at) VALUES(:n, datetime('now'))");
|
||||
$ins->execute([':n' => $name]);
|
||||
if ($relaxesForeignKeys && $db->query('PRAGMA foreign_key_check')->fetchColumn() !== false) {
|
||||
|
|
@ -116,4 +124,62 @@ final class Migrations
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static function modernizeAnimalConstraints(PDO $db): void
|
||||
{
|
||||
$createSql = (string) $db
|
||||
->query("SELECT sql FROM sqlite_master WHERE type='table' AND name='animals'")
|
||||
->fetchColumn();
|
||||
$constraint = "/refuge_room\\s+TEXT\\s+CHECK\\s*\\(\\s*refuge_room\\s+IN\\s*\\(\\s*'silver'\\s*,\\s*'twix'\\s*\\)\\s+OR\\s+refuge_room\\s+IS\\s+NULL\\s*\\)/i";
|
||||
$registrationConstraint = "/identification_registration_status\\s+TEXT\\s+NOT\\s+NULL\\s+DEFAULT\\s+'unknown'\\s+CHECK\\s*\\(\\s*identification_registration_status\\s+IN\\s*\\(\\s*'unknown'\\s*,\\s*'pending'\\s*,\\s*'registered'\\s*\\)\\s*\\)/i";
|
||||
if (
|
||||
$createSql === ''
|
||||
|| (!preg_match($constraint, $createSql) && !preg_match($registrationConstraint, $createSql))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$newSql = preg_replace(
|
||||
'/^CREATE TABLE\\s+(?:IF NOT EXISTS\\s+)?["`\[]?animals["`\]]?/i',
|
||||
'CREATE TABLE animals_room_unrestricted',
|
||||
$createSql,
|
||||
1,
|
||||
);
|
||||
$newSql = preg_replace($constraint, 'refuge_room TEXT', (string) $newSql, 1);
|
||||
$newSql = preg_replace(
|
||||
$registrationConstraint,
|
||||
"identification_registration_status TEXT NOT NULL DEFAULT 'unknown' " .
|
||||
"CHECK(identification_registration_status IN ('unknown','registered','change_pending','owner_changed','pending'))",
|
||||
(string) $newSql,
|
||||
1,
|
||||
);
|
||||
if (!$newSql || preg_match($constraint, $newSql)) {
|
||||
throw new RuntimeException('Unable to remove the legacy shelter-room constraint.');
|
||||
}
|
||||
|
||||
$objects = $db
|
||||
->query("SELECT sql FROM sqlite_master WHERE tbl_name='animals' AND type IN ('index','trigger') AND sql IS NOT NULL")
|
||||
->fetchAll(PDO::FETCH_COLUMN);
|
||||
$views = $db
|
||||
->query("SELECT name,sql FROM sqlite_master WHERE type='view' AND sql IS NOT NULL ORDER BY rowid")
|
||||
->fetchAll(PDO::FETCH_ASSOC);
|
||||
$columns = array_column($db->query('PRAGMA table_info(animals)')->fetchAll(PDO::FETCH_ASSOC), 'name');
|
||||
$quotedColumns = implode(',', array_map(static fn(string $column): string => '"' . str_replace('"', '""', $column) . '"', $columns));
|
||||
|
||||
foreach ($views as $view) {
|
||||
$viewName = '"' . str_replace('"', '""', (string) $view['name']) . '"';
|
||||
$db->exec('DROP VIEW ' . $viewName);
|
||||
}
|
||||
$db->exec($newSql);
|
||||
$db->exec("INSERT INTO animals_room_unrestricted ($quotedColumns) SELECT $quotedColumns FROM animals");
|
||||
$db->exec("UPDATE animals_room_unrestricted SET identification_registration_status='change_pending' WHERE identification_registration_status='pending'");
|
||||
$db->exec('DROP TABLE animals');
|
||||
$db->exec('ALTER TABLE animals_room_unrestricted RENAME TO animals');
|
||||
foreach ($objects as $objectSql) {
|
||||
$db->exec((string) $objectSql);
|
||||
}
|
||||
foreach ($views as $view) {
|
||||
$db->exec((string) $view['sql']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -410,7 +410,7 @@ ORDER BY created_at ASC
|
|||
<th><?= h(t('animal.done_on')) ?></th>
|
||||
<th><?= h(t('animal.reminder_due')) ?></th>
|
||||
<th><?= h(t('animal.batch_traceability')) ?></th>
|
||||
<th><?= h(t('animal.administered_by')) ?></th>
|
||||
<th><?= h(t('animal.veterinary_clinic')) ?></th>
|
||||
<th><?= h(t('common.notes')) ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
|
@ -424,7 +424,7 @@ ORDER BY created_at ASC
|
|||
<div class="text-muted small">
|
||||
<?= h(t('animal.expires_on', ['date' => date('d/m/Y', strtotime((string)$v['batch_expires_on']))])) ?>
|
||||
</div><?php endif; ?></td>
|
||||
<td><?= !empty($v['administrator_name']) ? h($v['administrator_name']) : '—' ?></td>
|
||||
<td><?= !empty($v['clinic_name']) ? h($v['clinic_name']) : '—' ?></td>
|
||||
<td><?= $v['notes'] ? h($v['notes']) : '—' ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
|
|
|
|||
|
|
@ -312,38 +312,12 @@ $currentTime = date('H:i');
|
|||
<div class="col-md-4"><label class="form-label"><?= h(t('inventory.quantity')) ?></label><input
|
||||
class="form-control" type="number" min="0" step="0.01" name="inventory_quantity"></div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label class="form-label"><?= h(t('animal.administered_by')) ?></label>
|
||||
<select class="form-select" name="administered_by_user_id" id="vaccineAdministrator">
|
||||
<option value=""><?= h(t('common.not_provided')) ?></option>
|
||||
<?php foreach (($activeUsers ?? []) as $user): ?><option value="<?= (int)$user['id'] ?>">
|
||||
<?= h($user['name']) ?></option><?php endforeach; ?><option value="other">
|
||||
<?= h(t('animal.other_person')) ?></option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 d-none" id="vaccineAdministratorOther">
|
||||
<label class="form-label"><?= h(t('animal.person_name')) ?></label>
|
||||
<input class="form-control" name="administered_by_name"
|
||||
placeholder="<?= h(t('animal.person_placeholder')) ?>">
|
||||
</div>
|
||||
|
||||
<div class="col-md-12">
|
||||
<label class="form-label"><?= h(t('common.notes')) ?></label>
|
||||
<input class="form-control" name="notes" placeholder="Optionnel">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const select = document.getElementById('vaccineAdministrator');
|
||||
const wrap = document.getElementById('vaccineAdministratorOther');
|
||||
const sync = () => wrap?.classList.toggle('d-none', select?.value !== 'other');
|
||||
select?.addEventListener('change', sync);
|
||||
sync();
|
||||
});
|
||||
|
||||
</script>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
|
|
|
|||
|
|
@ -370,12 +370,15 @@ $estMonths = $birth_estimated_months > 0 ? ($birth_estimated_months % 12) : '';
|
|||
<option value="unknown"
|
||||
<?= (($animal['identification_registration_status'] ?? 'unknown') === 'unknown') ? 'selected' : '' ?>>
|
||||
<?= h(t('common.unknown')) ?></option>
|
||||
<option value="pending"
|
||||
<?= (($animal['identification_registration_status'] ?? 'unknown') === 'pending') ? 'selected' : '' ?>>
|
||||
<?= h(t('animal.form.pending')) ?></option>
|
||||
<option value="registered"
|
||||
<?= (($animal['identification_registration_status'] ?? 'unknown') === 'registered') ? 'selected' : '' ?>>
|
||||
<?= h(t('animal.form.registered')) ?></option>
|
||||
<option value="change_pending"
|
||||
<?= in_array(($animal['identification_registration_status'] ?? 'unknown'), ['pending', 'change_pending'], true) ? 'selected' : '' ?>>
|
||||
<?= h(t('animal.form.change_pending')) ?></option>
|
||||
<option value="owner_changed"
|
||||
<?= (($animal['identification_registration_status'] ?? 'unknown') === 'owner_changed') ? 'selected' : '' ?>>
|
||||
<?= h(t('animal.form.owner_changed')) ?></option>
|
||||
</select></div>
|
||||
|
||||
<div class="col-12">
|
||||
|
|
|
|||
|
|
@ -296,9 +296,9 @@ if ($felvPos) {
|
|||
<?php $availability = $animal['adoption_availability'] ?? 'unknown';
|
||||
$availabilityMeta = ['unknown' => [t('animal.availability_define'),'bg-secondary-lt'],'not_available' => [t('animals.unavailable_title'),'bg-orange-lt'],'available' => [t('animals.available_title'),'bg-green-lt']];
|
||||
[$availabilityLabel,$availabilityClass] = $availabilityMeta[$availability] ?? $availabilityMeta['unknown'];
|
||||
$registrationLabels = ['unknown' => t('animal.registration_unknown'),'pending' => t('animal.registration_pending'),'registered' => t('animal.registration_done')];
|
||||
$registrationLabels = ['unknown' => t('animal.registration_unknown'),'pending' => t('animal.registration_change_pending'),'change_pending' => t('animal.registration_change_pending'),'registered' => t('animal.registration_done'),'owner_changed' => t('animal.registration_owner_changed')];
|
||||
$registration = $animal['identification_registration_status'] ?? 'unknown';
|
||||
$registrationClass = $registration === 'registered' ? 'bg-green-lt' : ($registration === 'pending' ? 'bg-yellow-lt' : 'bg-secondary-lt'); ?>
|
||||
$registrationClass = in_array($registration, ['registered', 'owner_changed'], true) ? 'bg-green-lt' : (in_array($registration, ['pending', 'change_pending'], true) ? 'bg-yellow-lt' : 'bg-secondary-lt'); ?>
|
||||
<div class="mt-2 d-flex flex-wrap align-items-center gap-1"><span
|
||||
class="badge <?= h($registrationClass) ?>"><?= h($registrationLabels[$registration] ?? $registrationLabels['unknown']) ?></span><span
|
||||
class="badge <?= h($availabilityClass) ?>"><?= h($availabilityLabel) ?></span><?php if (!empty($animal['website_published'])): ?><span
|
||||
|
|
|
|||
|
|
@ -31,10 +31,10 @@
|
|||
<div class="col-lg-6">
|
||||
<section class="card h-100">
|
||||
<div class="card-body">
|
||||
<span class="badge bg-blue-lt mb-2">1.0.0-rc.25</span>
|
||||
<h3>Portées modifiables et fiables</h3>
|
||||
<p class="text-muted">La création d’une fratrie ne dépend plus d’un ancien traitement de photo et
|
||||
son historique reste compatible avec les bases existantes.</p>
|
||||
<span class="badge bg-blue-lt mb-2">1.0.0-rc.26</span>
|
||||
<h3>Données animales fiabilisées</h3>
|
||||
<p class="text-muted">Les salles configurables, les états I-CAD et le suivi vaccinal restent
|
||||
cohérents sur les installations neuves comme sur les bases historiques.</p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
|
|
|||
3
migrations/097_remove_legacy_room_constraint.sql
Normal file
3
migrations/097_remove_legacy_room_constraint.sql
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
-- La reconstruction conditionnelle est réalisée par Migrations::run afin de
|
||||
-- préserver toutes les colonnes des bases historiques.
|
||||
SELECT 1;
|
||||
|
|
@ -210,4 +210,9 @@ return [
|
|||
'litter.detach'=>'Detach',
|
||||
'litter.detach_confirm'=>'Remove this cat from the litter? Its record and data will be preserved.',
|
||||
'litter.parent_is_kitten'=>'A parent cannot also be a kitten in this litter.',
|
||||
'animal.form.change_pending'=>'Ownership change pending',
|
||||
'animal.form.owner_changed'=>'Owner changed',
|
||||
'animal.registration_change_pending'=>'I-CAD: ownership change pending',
|
||||
'animal.registration_owner_changed'=>'I-CAD: owner changed',
|
||||
'animal.veterinary_clinic'=>'Veterinary clinic',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -210,4 +210,9 @@ return [
|
|||
'litter.detach'=>'Détacher',
|
||||
'litter.detach_confirm'=>'Retirer ce chat de la portée ? Sa fiche et ses données seront conservées.',
|
||||
'litter.parent_is_kitten'=>'Un parent ne peut pas être également chaton de cette portée.',
|
||||
'animal.form.change_pending'=>'En attente de changement',
|
||||
'animal.form.owner_changed'=>'Propriétaire changé',
|
||||
'animal.registration_change_pending'=>'I-CAD : en attente de changement',
|
||||
'animal.registration_owner_changed'=>'I-CAD : propriétaire changé',
|
||||
'animal.veterinary_clinic'=>'Clinique vétérinaire',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -167,6 +167,37 @@ try {
|
|||
$assert(!ShelterRoomService::accepts($room, ['species' => 'chat', 'sex' => 'F', 'fiv' => 1]));
|
||||
$assert(!ShelterRoomService::accepts($room, ['species' => 'chat', 'sex' => 'M', 'fiv' => 0]));
|
||||
});
|
||||
$test('Une salle configurable peut être enregistrée sur un animal', function () use ($db, $assert): void {
|
||||
$schema = (string) $db->query("SELECT sql FROM sqlite_master WHERE type='table' AND name='animals'")->fetchColumn();
|
||||
$assert(!str_contains($schema, "refuge_room IN ('silver', 'twix')"), 'Ancienne contrainte de salle encore active');
|
||||
$code = 'TEST-ROOM-' . bin2hex(random_bytes(3));
|
||||
$db->prepare("INSERT INTO animals(name,internal_code,refuge_room) VALUES('Test salle',:code,'nouvelle-salle')")->execute([':code' => $code]);
|
||||
$id = (int) $db->lastInsertId();
|
||||
$assert((string) $db->query('SELECT refuge_room FROM animals WHERE id=' . $id)->fetchColumn() === 'nouvelle-salle');
|
||||
$db->prepare('DELETE FROM animals WHERE id=:id')->execute([':id' => $id]);
|
||||
});
|
||||
$test('Les quatre états I-CAD sont acceptés', function () use ($db, $assert): void {
|
||||
$code = 'TEST-ICAD-' . bin2hex(random_bytes(3));
|
||||
$db->prepare("INSERT INTO animals(name,internal_code,identification_registration_status) VALUES('Test I-CAD',:code,'unknown')")
|
||||
->execute([':code' => $code]);
|
||||
$id = (int) $db->lastInsertId();
|
||||
foreach (['unknown', 'registered', 'change_pending', 'owner_changed'] as $status) {
|
||||
$db->prepare('UPDATE animals SET identification_registration_status=:status WHERE id=:id')
|
||||
->execute([':status' => $status, ':id' => $id]);
|
||||
$assert(
|
||||
(string) $db->query('SELECT identification_registration_status FROM animals WHERE id=' . $id)->fetchColumn() === $status,
|
||||
'État I-CAD refusé : ' . $status,
|
||||
);
|
||||
}
|
||||
$db->prepare('DELETE FROM animals WHERE id=:id')->execute([':id' => $id]);
|
||||
});
|
||||
$test('La vaccination repose sur le cabinet vétérinaire', function () use ($assert): void {
|
||||
$modal = (string) file_get_contents(dirname(__DIR__) . '/app/Views/_animal_show_modals.php');
|
||||
$summary = (string) file_get_contents(dirname(__DIR__) . '/app/Views/_animal_show_medical.php');
|
||||
$assert(!str_contains($modal, 'name="administered_by_user_id"'));
|
||||
$assert(!str_contains($modal, 'name="administered_by_name"'));
|
||||
$assert(str_contains($summary, "\$v['clinic_name']"));
|
||||
});
|
||||
$test('Les paramètres sont enregistrés et relus', function () use ($assert): void {
|
||||
AppSettings::save(['association_name' => 'Refuge Test', 'app_language' => 'en'], null);
|
||||
$assert(AppSettings::get('association_name') === 'Refuge Test');
|
||||
|
|
|
|||
Loading…
Reference in a new issue