Publier Globinours 1.0.0-rc.3
This commit is contained in:
parent
ea8c24d622
commit
9a2b4068da
325 changed files with 38230 additions and 20 deletions
536
app/Services/DemoDataService.php
Normal file
536
app/Services/DemoDataService.php
Normal file
|
|
@ -0,0 +1,536 @@
|
|||
<?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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue