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
271
app/Services/Asm3MovementImporter.php
Normal file
271
app/Services/Asm3MovementImporter.php
Normal file
|
|
@ -0,0 +1,271 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class Asm3MovementImporter
|
||||
{
|
||||
public static function run(string $path, PDO $db, bool $apply = false, bool $createBackup = true): array
|
||||
{
|
||||
$hash = hash_file('sha256', $path);
|
||||
if ($hash === false) {
|
||||
throw new RuntimeException(t('asm3.hash_unavailable'));
|
||||
}
|
||||
$rows = Asm3Analyzer::rows($path, ['adoption'])['adoption'] ?? [];
|
||||
$events = [];
|
||||
$unsupported = 0;
|
||||
foreach ($rows as $row) {
|
||||
$source = (int) ($row['ID'] ?? 0);
|
||||
$type = (int) ($row['MOVEMENTTYPE'] ?? 0);
|
||||
$movementDate = self::date($row['MOVEMENTDATE'] ?? null);
|
||||
$returnDate = self::date($row['RETURNDATE'] ?? null);
|
||||
$reservationDate = self::date($row['RESERVATIONDATE'] ?? null);
|
||||
$cancelDate = self::date($row['RESERVATIONCANCELLEDDATE'] ?? null);
|
||||
if ($reservationDate) {
|
||||
$events[] = self::event($row, $source, 'reservation', $reservationDate, 'Réservation ASM3');
|
||||
}
|
||||
if ($cancelDate) {
|
||||
$events[] = self::event($row, $source, 'cancellation', $cancelDate, 'Annulation de réservation ASM3');
|
||||
}
|
||||
if ($movementDate && $type === 1) {
|
||||
$events[] = self::event($row, $source, 'adoption', $movementDate, 'Adoption définitive ASM3');
|
||||
} elseif ($movementDate && in_array($type, [2, 12], true)) {
|
||||
$events[] = self::event(
|
||||
$row,
|
||||
$source,
|
||||
'foster',
|
||||
$movementDate,
|
||||
$type === 12 || (string) ($row['ISPERMANENTFOSTER'] ?? 0) === '1'
|
||||
? 'FA permanente ASM3'
|
||||
: 'Famille d’accueil ASM3',
|
||||
);
|
||||
} elseif ($movementDate && !in_array($type, [0, 9, 10], true)) {
|
||||
$unsupported++;
|
||||
}
|
||||
if ($returnDate && in_array($type, [1, 2, 12], true)) {
|
||||
$events[] = self::event(
|
||||
$row,
|
||||
$source,
|
||||
'return',
|
||||
$returnDate,
|
||||
trim((string) ($row['REASONFORRETURN'] ?? '')) ?: 'Retour enregistré dans ASM3',
|
||||
);
|
||||
}
|
||||
}
|
||||
$links = self::links($db, $hash);
|
||||
$summary = [
|
||||
'events' => count($events),
|
||||
'adoptions' => 0,
|
||||
'fosters' => 0,
|
||||
'returns' => 0,
|
||||
'reservations' => 0,
|
||||
'cancellations' => 0,
|
||||
'unresolved_animals' => 0,
|
||||
'unresolved_contacts' => 0,
|
||||
'unsupported_movements' => $unsupported,
|
||||
'would_import' => 0,
|
||||
'created' => 0,
|
||||
'already_imported' => 0,
|
||||
];
|
||||
foreach ($events as $event) {
|
||||
$summary[
|
||||
$event['type'] === 'adoption'
|
||||
? 'adoptions'
|
||||
: ($event['type'] === 'foster'
|
||||
? 'fosters'
|
||||
: ($event['type'] === 'return'
|
||||
? 'returns'
|
||||
: ($event['type'] === 'reservation'
|
||||
? 'reservations'
|
||||
: 'cancellations')))
|
||||
]++;
|
||||
if (!isset($links['animal'][$event['animal_source_id']])) {
|
||||
$summary['unresolved_animals']++;
|
||||
continue;
|
||||
}
|
||||
if ($event['contact_source_id'] > 0 && !isset($links['contact'][$event['contact_source_id']])) {
|
||||
$summary['unresolved_contacts']++;
|
||||
}
|
||||
$summary['would_import']++;
|
||||
}
|
||||
if (!$apply) {
|
||||
return ['mode' => 'dry-run', 'read_only' => true, 'summary' => $summary];
|
||||
}
|
||||
if ($createBackup) {
|
||||
$backup = BackupService::create('pre-asm3-import');
|
||||
$backupName = $backup['name'] ?? null;
|
||||
} else {
|
||||
$backupName = null;
|
||||
}
|
||||
$ownsTransaction = !$db->inTransaction();
|
||||
if ($ownsTransaction) {
|
||||
$db->beginTransaction();
|
||||
}
|
||||
try {
|
||||
$db->prepare(
|
||||
"INSERT INTO asm3_import_runs(source_name,source_sha256,status,summary_json,backup_name) VALUES(:name,:hash,'completed','{}',:backup)",
|
||||
)->execute([':name' => basename($path) . ' · mouvements', ':hash' => $hash, ':backup' => $backupName]);
|
||||
$runId = (int) $db->lastInsertId();
|
||||
$affected = [];
|
||||
foreach ($events as $event) {
|
||||
$animalId = $links['animal'][$event['animal_source_id']] ?? 0;
|
||||
if (!$animalId) {
|
||||
continue;
|
||||
}
|
||||
$affected[$animalId] = true;
|
||||
$entity = 'placement-' . $event['type'];
|
||||
$exists = $db->prepare(
|
||||
'SELECT target_id FROM asm3_import_links WHERE source_sha256=:hash AND entity_type=:entity AND source_id=:source',
|
||||
);
|
||||
$exists->execute([':hash' => $hash, ':entity' => $entity, ':source' => $event['source_id']]);
|
||||
if ($exists->fetchColumn()) {
|
||||
$summary['already_imported']++;
|
||||
continue;
|
||||
}
|
||||
$contactId = $links['contact'][$event['contact_source_id']] ?? null;
|
||||
$adoptionId = null;
|
||||
if ($event['type'] === 'adoption') {
|
||||
$contact = self::contact($db, $contactId);
|
||||
$db->prepare(
|
||||
'INSERT INTO adoptions(animal_id,adopter_name,adopter_phone,adopter_email,adopter_address,adopter_postal_code,adopter_city,adopter_country,adoption_date,notes,adopter_contact_id) VALUES(:animal,:name,:phone,:email,:address,:postal,:city,:country,:date,:notes,:contact)',
|
||||
)->execute([
|
||||
':animal' => $animalId,
|
||||
':name' => $contact['name'] ?? 'Adoptant ASM3 non retrouvé',
|
||||
':phone' => $contact['phone'] ?? null,
|
||||
':email' => $contact['email'] ?? null,
|
||||
':address' => $contact['address'] ?? null,
|
||||
':postal' => $contact['postal_code'] ?? null,
|
||||
':city' => $contact['city'] ?? null,
|
||||
':country' => $contact['country'] ?? 'France',
|
||||
':date' => $event['date'],
|
||||
':notes' => $event['notes'] ?: null,
|
||||
':contact' => $contactId,
|
||||
]);
|
||||
$adoptionId = (int) $db->lastInsertId();
|
||||
}
|
||||
$db->prepare(
|
||||
'INSERT INTO animal_placements(animal_id,event_type,event_date,contact_id,reason,notes,adoption_id) VALUES(:animal,:type,:date,:contact,:reason,:notes,:adoption)',
|
||||
)->execute([
|
||||
':animal' => $animalId,
|
||||
':type' => $event['type'],
|
||||
':date' => $event['date'],
|
||||
':contact' => $contactId,
|
||||
':reason' => $event['reason'],
|
||||
':notes' => $event['notes'] ?: null,
|
||||
':adoption' => $adoptionId,
|
||||
]);
|
||||
$placementId = (int) $db->lastInsertId();
|
||||
$db->prepare(
|
||||
'INSERT INTO asm3_import_links(source_sha256,entity_type,source_id,target_id,import_run_id) VALUES(:hash,:entity,:source,:target,:run)',
|
||||
)->execute([
|
||||
':hash' => $hash,
|
||||
':entity' => $entity,
|
||||
':source' => $event['source_id'],
|
||||
':target' => $placementId,
|
||||
':run' => $runId,
|
||||
]);
|
||||
$summary['created']++;
|
||||
}
|
||||
self::syncAnimalStates($db, array_keys($affected));
|
||||
$db->prepare('UPDATE asm3_import_runs SET summary_json=:summary WHERE id=:id')->execute([
|
||||
':summary' => json_encode($summary, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR),
|
||||
':id' => $runId,
|
||||
]);
|
||||
if ($ownsTransaction) {
|
||||
$db->commit();
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
if ($ownsTransaction && $db->inTransaction()) {
|
||||
$db->rollBack();
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
return ['mode' => 'apply', 'read_only' => false, 'backup' => $backupName, 'summary' => $summary];
|
||||
}
|
||||
private static function event(array $row, int $source, string $type, string $date, string $reason): array
|
||||
{
|
||||
return [
|
||||
'source_id' => $source,
|
||||
'type' => $type,
|
||||
'date' => $date,
|
||||
'animal_source_id' => (int) ($row['ANIMALID'] ?? 0),
|
||||
'contact_source_id' => (int) ($row['OWNERID'] ?? 0),
|
||||
'reason' => $reason,
|
||||
'notes' => trim((string) ($row['COMMENTS'] ?? '')),
|
||||
];
|
||||
}
|
||||
private static function date(mixed $value): ?string
|
||||
{
|
||||
return $value && preg_match('/^\d{4}-\d{2}-\d{2}/', (string) $value, $m) ? $m[0] : null;
|
||||
}
|
||||
private static function links(PDO $db, string $hash): array
|
||||
{
|
||||
$out = ['animal' => [], 'contact' => []];
|
||||
$s = $db->prepare(
|
||||
"SELECT entity_type,source_id,target_id FROM asm3_import_links WHERE source_sha256=:hash AND entity_type IN ('animal','contact')",
|
||||
);
|
||||
$s->execute([':hash' => $hash]);
|
||||
foreach ($s as $r) {
|
||||
$out[$r['entity_type']][(int) $r['source_id']] = (int) $r['target_id'];
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
private static function contact(PDO $db, ?int $id): ?array
|
||||
{
|
||||
if (!$id) {
|
||||
return null;
|
||||
}
|
||||
$s = $db->prepare('SELECT * FROM directory_contacts WHERE id=:id');
|
||||
$s->execute([':id' => $id]);
|
||||
return $s->fetch(PDO::FETCH_ASSOC) ?: null;
|
||||
}
|
||||
private static function syncAnimalStates(PDO $db, array $animalIds): void
|
||||
{
|
||||
$latest = $db->prepare(
|
||||
'SELECT ap.*,dc.name contact_name,dc.address,dc.postal_code,dc.city FROM animal_placements ap LEFT JOIN directory_contacts dc ON dc.id=ap.contact_id WHERE ap.animal_id=:animal ORDER BY date(ap.event_date) DESC,ap.id DESC LIMIT 1',
|
||||
);
|
||||
foreach ($animalIds as $animalId) {
|
||||
$death = $db->prepare('SELECT 1 FROM animal_deaths WHERE animal_id=?');
|
||||
$death->execute([$animalId]);
|
||||
if ($death->fetchColumn()) {
|
||||
continue;
|
||||
}
|
||||
$latest->execute([':animal' => $animalId]);
|
||||
$event = $latest->fetch(PDO::FETCH_ASSOC);
|
||||
if (!$event) {
|
||||
continue;
|
||||
}
|
||||
$address = implode(
|
||||
' ',
|
||||
array_filter([$event['address'] ?? null, $event['postal_code'] ?? null, $event['city'] ?? null]),
|
||||
);
|
||||
if ($event['event_type'] === 'adoption') {
|
||||
$db->prepare(
|
||||
"UPDATE animals SET status='adopte',is_archived=1,archived_at=:archived,current_address=:address,refuge_room=NULL,care_box_key=NULL,quarantine_until=NULL,updated_at=datetime('now') WHERE id=:id",
|
||||
)->execute([
|
||||
':archived' => $event['event_date'] . ' 12:00:00',
|
||||
':address' =>
|
||||
$address !== ''
|
||||
? $address
|
||||
: 'Adopté par ' . ($event['contact_name'] ?: 'un adoptant non renseigné'),
|
||||
':id' => $animalId,
|
||||
]);
|
||||
} elseif ($event['event_type'] === 'foster') {
|
||||
$db->prepare(
|
||||
"UPDATE animals SET status=CASE WHEN lower(COALESCE(reason,'')) LIKE '%permanente%' THEN 'fa_permanente' ELSE 'fa' END,is_archived=0,archived_at=NULL,current_address=:address,refuge_room=NULL,care_box_key=NULL,updated_at=datetime('now') WHERE id=:id",
|
||||
)->execute([
|
||||
':address' =>
|
||||
$address !== '' ? $address : 'Chez ' . ($event['contact_name'] ?: 'une famille d’accueil'),
|
||||
':id' => $animalId,
|
||||
]);
|
||||
} elseif (in_array($event['event_type'], ['return', 'cancellation'], true)) {
|
||||
$db->prepare(
|
||||
"UPDATE animals SET status='refuge',is_archived=0,archived_at=NULL,current_address=NULL,updated_at=datetime('now') WHERE id=?",
|
||||
)->execute([$animalId]);
|
||||
} elseif ($event['event_type'] === 'reservation') {
|
||||
$db->prepare(
|
||||
"UPDATE animals SET status='reserve',is_archived=0,archived_at=NULL,updated_at=datetime('now') WHERE id=?",
|
||||
)->execute([$animalId]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue