536 lines
21 KiB
PHP
536 lines
21 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
final class DemoDataService
|
||
{
|
||
private const TABLES = [
|
||
'agenda_volunteers',
|
||
'agenda_items',
|
||
'prescription_treatments',
|
||
'lab_results',
|
||
'medical_prescriptions',
|
||
'lab_reports',
|
||
'treatment_administrations',
|
||
'care_round_observations',
|
||
'care_rounds',
|
||
'medical_photos',
|
||
'animal_photos',
|
||
'animal_expenses',
|
||
'animal_surgeries',
|
||
'animal_health_conditions',
|
||
'dewormings',
|
||
'vaccinations',
|
||
'treatments',
|
||
'medical_notes',
|
||
'measurements',
|
||
'animal_placements',
|
||
'adoptions',
|
||
'animal_movements',
|
||
'animal_deaths',
|
||
'animal_location_history',
|
||
'animal_history',
|
||
'litter_kittens',
|
||
'litters',
|
||
'bonded_group_members',
|
||
'bonded_groups',
|
||
'icad_cache',
|
||
'asm3_import_links',
|
||
'asm3_import_runs',
|
||
'asm3_import_jobs',
|
||
'directory_contact_roles',
|
||
'animals',
|
||
'directory_contacts',
|
||
'grant_documents',
|
||
'grant_applications',
|
||
'grant_years',
|
||
'care_room_layouts',
|
||
'clinics',
|
||
'geocode_cache',
|
||
'audit_log',
|
||
];
|
||
|
||
public static function reset(PDO $db): array
|
||
{
|
||
$before = [
|
||
'animals' => (int) $db->query('SELECT COUNT(*) FROM animals')->fetchColumn(),
|
||
'contacts' => (int) $db->query('SELECT COUNT(*) FROM directory_contacts')->fetchColumn(),
|
||
];
|
||
$sequence = $db->prepare('DELETE FROM sqlite_sequence WHERE name=?');
|
||
$exists = $db->prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name=?");
|
||
foreach (self::TABLES as $table) {
|
||
$exists->execute([$table]);
|
||
if (!$exists->fetchColumn()) {
|
||
continue;
|
||
}
|
||
$db->exec('DELETE FROM ' . $table);
|
||
$sequence->execute([$table]);
|
||
}
|
||
return $before;
|
||
}
|
||
|
||
public static function seed(PDO $db, ?int $userId): array
|
||
{
|
||
$contacts = [
|
||
'fa' => [
|
||
'person',
|
||
'Alice Martin',
|
||
'06 00 00 00 01',
|
||
'alice.demo@example.org',
|
||
'12 rue des Lilas',
|
||
'29260',
|
||
'Lesneven',
|
||
],
|
||
'adopter' => [
|
||
'person',
|
||
'Marc Durand',
|
||
'06 00 00 00 02',
|
||
'marc.demo@example.org',
|
||
'8 rue du Port',
|
||
'29200',
|
||
'Brest',
|
||
],
|
||
'depositor' => [
|
||
'person',
|
||
'Sophie Le Gall',
|
||
'06 00 00 00 03',
|
||
'sophie.demo@example.org',
|
||
'4 place du Marché',
|
||
'29000',
|
||
'Ville fictive',
|
||
],
|
||
'vet' => [
|
||
'person',
|
||
'Dr Camille Morel',
|
||
'02 00 00 00 04',
|
||
'veterinaire.demo@example.org',
|
||
'2 avenue des Chats',
|
||
'29260',
|
||
'Lesneven',
|
||
],
|
||
'clinic' => [
|
||
'organization',
|
||
'Clinique vétérinaire Démo',
|
||
'02 00 00 00 05',
|
||
'clinique.demo@example.org',
|
||
'2 avenue des Chats',
|
||
'29260',
|
||
'Lesneven',
|
||
],
|
||
'crematorium' => [
|
||
'organization',
|
||
'Crématorium animalier Démo',
|
||
'02 00 00 00 06',
|
||
'crematorium.demo@example.org',
|
||
'1 route du Souvenir',
|
||
'29400',
|
||
'Landivisiau',
|
||
],
|
||
];
|
||
$contactIds = [];
|
||
$insert = $db->prepare(
|
||
'INSERT INTO directory_contacts(kind,name,phone,email,address,postal_code,city,notes) VALUES(?,?,?,?,?,?,?,?)',
|
||
);
|
||
$role = $db->prepare('INSERT INTO directory_contact_roles(contact_id,role) VALUES(?,?)');
|
||
foreach ($contacts as $key => $c) {
|
||
$insert->execute([...$c, 'Donnée fictive de démonstration']);
|
||
$contactIds[$key] = (int) $db->lastInsertId();
|
||
$roleName = match ($key) {
|
||
'adopter' => 'adoptant',
|
||
'vet' => 'veterinaire',
|
||
'clinic' => 'cabinet',
|
||
'crematorium' => 'crematorium',
|
||
'depositor' => 'deposant',
|
||
default => 'fa',
|
||
};
|
||
$role->execute([$contactIds[$key], $roleName]);
|
||
}
|
||
|
||
$animals = [
|
||
[
|
||
'DEMO-001',
|
||
'Moka',
|
||
'F',
|
||
'2019-05-14',
|
||
'refuge',
|
||
'silver',
|
||
'Bicolore Noir / Blanc',
|
||
'yes',
|
||
'yes',
|
||
'yes',
|
||
'yes',
|
||
'yes',
|
||
'available',
|
||
],
|
||
[
|
||
'DEMO-002',
|
||
'Pixel',
|
||
'M',
|
||
'2021-09-03',
|
||
'refuge',
|
||
'twix',
|
||
'Roux',
|
||
'yes',
|
||
'yes',
|
||
'yes',
|
||
'yes',
|
||
'no',
|
||
'available',
|
||
],
|
||
[
|
||
'DEMO-003',
|
||
'Twix',
|
||
'M',
|
||
'2019-04-15',
|
||
'quarantaine',
|
||
null,
|
||
'Roux',
|
||
'yes',
|
||
'yes',
|
||
'yes',
|
||
'yes',
|
||
'yes',
|
||
'not_available',
|
||
],
|
||
[
|
||
'DEMO-004',
|
||
'Nox',
|
||
'M',
|
||
'2023-02-18',
|
||
'soin',
|
||
null,
|
||
'Noir',
|
||
'no',
|
||
'yes',
|
||
'unknown',
|
||
'yes',
|
||
'no',
|
||
'not_available',
|
||
],
|
||
[
|
||
'DEMO-005',
|
||
'Caramel',
|
||
'F',
|
||
'2020-07-21',
|
||
'fa',
|
||
null,
|
||
'Écaille de tortue',
|
||
'yes',
|
||
'yes',
|
||
'yes',
|
||
'yes',
|
||
'yes',
|
||
'available',
|
||
],
|
||
[
|
||
'DEMO-006',
|
||
'Plume',
|
||
'F',
|
||
'2022-04-08',
|
||
'refuge',
|
||
'silver',
|
||
'Tricolore / Calico',
|
||
'yes',
|
||
'yes',
|
||
'yes',
|
||
'yes',
|
||
'yes',
|
||
'available',
|
||
],
|
||
[
|
||
'DEMO-007',
|
||
'Plume-Bébé-1',
|
||
'F',
|
||
date('Y-m-d', strtotime('-4 months')),
|
||
'refuge',
|
||
'silver',
|
||
'Bicolore Gris / Blanc',
|
||
'unknown',
|
||
'yes',
|
||
'unknown',
|
||
'unknown',
|
||
'unknown',
|
||
'not_available',
|
||
],
|
||
[
|
||
'DEMO-008',
|
||
'Plume-Bébé-2',
|
||
'M',
|
||
date('Y-m-d', strtotime('-4 months')),
|
||
'refuge',
|
||
'silver',
|
||
'Gris',
|
||
'unknown',
|
||
'yes',
|
||
'unknown',
|
||
'unknown',
|
||
'unknown',
|
||
'not_available',
|
||
],
|
||
[
|
||
'DEMO-009',
|
||
'Plume-Bébé-3',
|
||
'U',
|
||
date('Y-m-d', strtotime('-4 months')),
|
||
'refuge',
|
||
'silver',
|
||
'Tigré / Tabby',
|
||
'unknown',
|
||
'yes',
|
||
'unknown',
|
||
'unknown',
|
||
'unknown',
|
||
'not_available',
|
||
],
|
||
[
|
||
'DEMO-010',
|
||
'Lune',
|
||
'F',
|
||
'2021-01-12',
|
||
'refuge',
|
||
'twix',
|
||
'Blanc',
|
||
'yes',
|
||
'no',
|
||
'yes',
|
||
'yes',
|
||
'yes',
|
||
'available',
|
||
],
|
||
[
|
||
'DEMO-011',
|
||
'Soleil',
|
||
'M',
|
||
'2021-01-12',
|
||
'refuge',
|
||
'twix',
|
||
'Roux',
|
||
'yes',
|
||
'no',
|
||
'yes',
|
||
'yes',
|
||
'yes',
|
||
'available',
|
||
],
|
||
[
|
||
'DEMO-012',
|
||
'Étoile',
|
||
'F',
|
||
'2012-06-01',
|
||
'decede',
|
||
null,
|
||
'Noir',
|
||
'yes',
|
||
'unknown',
|
||
'unknown',
|
||
'unknown',
|
||
'unknown',
|
||
'not_available',
|
||
],
|
||
];
|
||
$animalIds = [];
|
||
$animal = $db->prepare(
|
||
"INSERT INTO animals(internal_code,name,species,sex,birth_date,status,refuge_room,care_box_key,color,sterilized,sterilization_status,compatibility_cats,compatibility_dogs,compatibility_children,house_trained,adoption_availability,intake_date,intake_type,intake_reason,rescue_location_name,current_address,chip_id,identification_type,identification_registration_status,archived_at,notes) VALUES(:code,:name,'chat',:sex,:birth,:status,:room,:box,:color,:sterilized,:sterilization,:cats,:dogs,:children,:clean,:availability,date('now','-'||:days||' days'),'found','stray_found','Commune fictive','5 rue de la Démonstration, 29260 Lesneven',:chip,:identification,:registration,:archived,'Donnée fictive de démonstration')",
|
||
);
|
||
foreach ($animals as $i => $a) {
|
||
[
|
||
$code,
|
||
$name,
|
||
$sex,
|
||
$birth,
|
||
$status,
|
||
$room,
|
||
$color,
|
||
$sterilization,
|
||
$cats,
|
||
$dogs,
|
||
$children,
|
||
$clean,
|
||
$availability,
|
||
] = $a;
|
||
$sterilized = $sterilization === 'yes' ? 1 : 0;
|
||
$chip = $i < 6 ? '250269900' . str_pad((string) ($i + 1), 6, '0', STR_PAD_LEFT) : null;
|
||
$animal->execute([
|
||
':code' => $code,
|
||
':name' => $name,
|
||
':sex' => $sex,
|
||
':birth' => $birth,
|
||
':status' => $status,
|
||
':room' => $room,
|
||
':box' => $status === 'soin' ? 'care-top-1' : null,
|
||
':color' => $color,
|
||
':sterilized' => $sterilized,
|
||
':sterilization' => $sterilization,
|
||
':cats' => $cats,
|
||
':dogs' => $dogs,
|
||
':children' => $children,
|
||
':clean' => $clean,
|
||
':availability' => $availability,
|
||
':days' => 90 + $i * 12,
|
||
':chip' => $chip,
|
||
':identification' => $chip ? 'microchip' : 'unknown',
|
||
':registration' => $chip ? 'registered' : 'unknown',
|
||
':archived' => $status === 'decede' ? date('Y-m-d H:i:s') : null,
|
||
]);
|
||
$animalIds[$name] = (int) $db->lastInsertId();
|
||
}
|
||
|
||
$db->prepare('INSERT INTO litters(mother_id,birth_date,notes) VALUES(?,?,?)')->execute([
|
||
$animalIds['Plume'],
|
||
$animals[6][3],
|
||
'Portée fictive de démonstration',
|
||
]);
|
||
$litter = (int) $db->lastInsertId();
|
||
$link = $db->prepare('INSERT INTO litter_kittens(litter_id,animal_id,position) VALUES(?,?,?)');
|
||
foreach (['Plume-Bébé-1', 'Plume-Bébé-2', 'Plume-Bébé-3'] as $i => $name) {
|
||
$link->execute([$litter, $animalIds[$name], $i + 1]);
|
||
}
|
||
$db->prepare('INSERT INTO bonded_groups(name,notes) VALUES(?,?)')->execute([
|
||
'Duo Lune & Soleil',
|
||
'Chats fictifs à adopter ensemble',
|
||
]);
|
||
$group = (int) $db->lastInsertId();
|
||
$member = $db->prepare('INSERT INTO bonded_group_members(group_id,animal_id,position) VALUES(?,?,?)');
|
||
$member->execute([$group, $animalIds['Lune'], 1]);
|
||
$member->execute([$group, $animalIds['Soleil'], 2]);
|
||
|
||
$twix = $animalIds['Twix'];
|
||
$db->prepare(
|
||
"UPDATE animals SET breed='Européen',birth_is_estimated=0,fiv=0,felv=0,quarantine_until=date('now','+10 days'),intake_circumstances='Trouvé seul, bilan sanitaire programmé à l’arrivée.',depositor_contact_id=?,rescue_location_name='Commune fictive',current_address='Quarantaine — box supérieur gauche',identification_date=date('now','-2 years'),notes='Fiche vitrine entièrement fictive : elle illustre un dossier animal complet.' WHERE id=?",
|
||
)->execute([$contactIds['depositor'], $twix]);
|
||
|
||
$db->prepare(
|
||
"INSERT OR IGNORE INTO ref_medications(name,molecule,form,notes) VALUES('Doxybactin','doxycycline','comprimé','Référence de démonstration')",
|
||
)->execute();
|
||
$med = (int) $db->query("SELECT id FROM ref_medications WHERE name='Doxybactin'")->fetchColumn();
|
||
$db->prepare(
|
||
"INSERT INTO treatments(animal_id,medication_id,route,dose_text,start_date,end_date,ongoing,notes,created_by,give_morning,give_evening) VALUES(?,?,'PO','1/2 comprimé matin et soir',date('now','-2 days'),date('now','+5 days'),1,'Traitement fictif de démonstration',?,1,1)",
|
||
)->execute([$twix, $med, $userId]);
|
||
$db->prepare(
|
||
"INSERT INTO medical_notes(animal_id,noted_at,kind,reason,symptoms,exam,diagnosis,plan,weight_kg,temperature_c,created_by,vet_contact_id,clinic_contact_id) VALUES(?,datetime('now','-1 day'),'consult','Visite sanitaire d’entrée','Éternuements occasionnels','État général satisfaisant, auscultation sans anomalie','Coryza léger','Surveillance, traitement court et contrôle avant sortie de quarantaine',4.8,38.4,?,?,?)",
|
||
)->execute([$twix, $userId, $contactIds['vet'], $contactIds['clinic']]);
|
||
$weight = $db->prepare(
|
||
"INSERT INTO measurements(animal_id,type,value,unit,measured_at,notes,created_by) VALUES(?,'weight',?,'kg',datetime('now',?),'Mesure fictive',?)",
|
||
);
|
||
foreach ([[4.6, '-30 days'], [4.7, '-14 days'], [4.8, '-1 day']] as $w) {
|
||
$weight->execute([$twix, $w[0], $w[1], $userId]);
|
||
}
|
||
$db->prepare(
|
||
"INSERT OR IGNORE INTO ref_vaccines(name,notes) VALUES('TCL','Vaccin de démonstration')",
|
||
)->execute();
|
||
$vaccine = (int) $db->query("SELECT id FROM ref_vaccines WHERE name='TCL'")->fetchColumn();
|
||
$db->prepare(
|
||
"INSERT INTO vaccinations(animal_id,vaccine_id,done_date,due_date,lot,manufacturer,notes,created_by,vet_contact_id,clinic_contact_id) VALUES(?,?,date('now','-11 months'),date('now','+1 month'),'LOT-DEMO-1','Laboratoire Démo','Vaccination fictive à jour',?,?,?)",
|
||
)->execute([$twix, $vaccine, $userId, $contactIds['vet'], $contactIds['clinic']]);
|
||
$dewormer = (int) $db->query('SELECT id FROM ref_dewormers ORDER BY id LIMIT 1')->fetchColumn();
|
||
$db->prepare(
|
||
"INSERT INTO dewormings(animal_id,dewormer_id,administered_on,dose_text,weight_kg,next_due_date,notes,created_by) VALUES(?,?,date('now','-10 days'),'1 pipette',4.7,date('now','+80 days'),'Administration fictive',?)",
|
||
)->execute([$twix, $dewormer, $userId]);
|
||
$db->prepare(
|
||
"INSERT INTO animal_health_conditions(animal_id,name,status,diagnosed_at,resolved_at,notes) VALUES(?,'Gingivite légère','resolved',date('now','-6 months'),date('now','-5 months'),'Antécédent fictif résolu')",
|
||
)->execute([$twix]);
|
||
$db->prepare(
|
||
"INSERT INTO lab_reports(animal_id,sampled_on,report_type,laboratory_name,veterinarian_contact_id,clinic_contact_id,notes,created_by) VALUES(?,date('now','-1 day'),'Bilan complet','Laboratoire Démo',?,?,'Prise de sang fictive — valeurs dans les intervalles usuels',?)",
|
||
)->execute([$twix, $contactIds['vet'], $contactIds['clinic'], $userId]);
|
||
$report = (int) $db->lastInsertId();
|
||
$result = $db->prepare(
|
||
'INSERT INTO lab_results(report_id,parameter_name,value,unit,reference_min,reference_max,notes,position) VALUES(?,?,?,?,?,?,?,?)',
|
||
);
|
||
foreach (
|
||
[
|
||
['Leucocytes', 8.4, '10⁹/L', 3.5, 20.7, 10],
|
||
['Érythrocytes', 9.6, '10¹²/L', 7.7, 12.8, 20],
|
||
['Hémoglobine', 13.8, 'g/dL', 10, 17, 30],
|
||
['Hématocrite', 42.1, '%', 33.7, 55.4, 40],
|
||
['Plaquettes', 312, '10⁹/L', 125, 618, 50],
|
||
['Créatinine', 12, 'mg/L', 3, 21, 60],
|
||
['Urée', 0.42, 'g/L', 0.214, 0.642, 70],
|
||
]
|
||
as $r
|
||
) {
|
||
$result->execute([$report, $r[0], $r[1], $r[2], $r[3], $r[4], 'Résultat fictif', $r[5]]);
|
||
}
|
||
$db->prepare(
|
||
"INSERT INTO animal_photos(animal_id,filename,original_name,mime,size_bytes,is_primary,is_public,caption) VALUES(?,'twix.webp','twix.jpg','image/webp',0,1,1,'Twix — photographie de démonstration')",
|
||
)->execute([$twix]);
|
||
$db->prepare(
|
||
"INSERT INTO agenda_items(item_type,title,description,starts_at,ends_at,location,status,priority,animal_id,contact_id,reminder_minutes,created_by) VALUES('veterinary','Contrôle de sortie de quarantaine','Rendez-vous fictif : contrôle clinique et validation de la sortie.',datetime('now','+7 days','09:00'),datetime('now','+7 days','09:30'),'Clinique vétérinaire Démo','planned','high',?,?,60,?)",
|
||
)->execute([$twix, $contactIds['clinic'], $userId]);
|
||
$db->prepare(
|
||
"INSERT INTO animal_placements(animal_id,event_type,event_date,contact_id,reason,notes,created_by) VALUES(?,'foster',date('now','-30 days'),?,'Placement de démonstration','Donnée fictive',?)",
|
||
)->execute([$animalIds['Caramel'], $contactIds['fa'], $userId]);
|
||
$db->prepare(
|
||
"INSERT INTO animal_deaths(animal_id,deceased_date,cause_code,cause_details,occurred_in_care,place_type,euthanized,veterinarian_contact_id,crematorium_contact_id,body_disposition,created_by) VALUES(?,date('now','-60 days'),'old_age','Décès fictif',1,'veterinaire',1,?,?,'collective_cremation',?)",
|
||
)->execute([$animalIds['Étoile'], $contactIds['vet'], $contactIds['crematorium'], $userId]);
|
||
$history = $db->prepare(
|
||
"INSERT INTO animal_history(animal_id,action,field,new_value,user_id,created_at) VALUES(?,'created','demo','Donnée entièrement fictive',?,datetime('now'))",
|
||
);
|
||
foreach ($animalIds as $id) {
|
||
$history->execute([$id, $userId]);
|
||
}
|
||
return [
|
||
'animals' => count($animalIds),
|
||
'contacts' => count($contactIds),
|
||
'litters' => 1,
|
||
'bonded_groups' => 1,
|
||
'treatments' => 1,
|
||
'vaccinations' => 1,
|
||
'lab_reports' => 1,
|
||
'agenda_items' => 1,
|
||
'photos' => 1,
|
||
];
|
||
}
|
||
|
||
public static function clearFiles(): void
|
||
{
|
||
foreach (
|
||
[
|
||
dirname(__DIR__, 2) . '/public/media/animals',
|
||
dirname(__DIR__, 2) . '/data/grants',
|
||
dirname(__DIR__, 2) . '/data/medical-documents',
|
||
]
|
||
as $root
|
||
) {
|
||
if (!is_dir($root)) {
|
||
continue;
|
||
}
|
||
$it = new RecursiveIteratorIterator(
|
||
new RecursiveDirectoryIterator($root, FilesystemIterator::SKIP_DOTS),
|
||
RecursiveIteratorIterator::CHILD_FIRST,
|
||
);
|
||
foreach ($it as $file) {
|
||
$path = $file->getPathname();
|
||
if ($file->isLink() || $file->isFile()) {
|
||
@unlink($path);
|
||
} elseif ($file->isDir()) {
|
||
@rmdir($path);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
public static function installFiles(PDO $db): void
|
||
{
|
||
$animalId = (int) $db->query("SELECT id FROM animals WHERE internal_code='DEMO-003' LIMIT 1")->fetchColumn();
|
||
if ($animalId <= 0) {
|
||
return;
|
||
}
|
||
$source = dirname(__DIR__, 2) . '/resources/demo/twix.webp';
|
||
$directory = dirname(__DIR__, 2) . '/public/media/animals/' . $animalId;
|
||
$target = $directory . '/twix.webp';
|
||
if (!is_file($source)) {
|
||
throw new RuntimeException(t('service.image.invalid_upload'));
|
||
}
|
||
if (!is_dir($directory) && !mkdir($directory, 0755, true) && !is_dir($directory)) {
|
||
throw new RuntimeException(t('service.image.directory_failed'));
|
||
}
|
||
if (!copy($source, $target)) {
|
||
throw new RuntimeException(t('service.image.save_failed'));
|
||
}
|
||
@chmod($target, 0644);
|
||
$db->prepare("UPDATE animal_photos SET size_bytes=? WHERE animal_id=? AND filename='twix.webp'")->execute([
|
||
filesize($target),
|
||
$animalId,
|
||
]);
|
||
}
|
||
}
|