$apply ? 'apply' : 'dry-run', 'read_only' => !$apply, 'vaccinations' => 0, 'medical_notes' => 0, 'weights' => 0, 'unresolved' => 0, 'already_imported' => 0, 'backup' => null, ]; foreach ($src['animalvaccination'] ?? [] as $r) { $result['vaccinations']++; if (!isset($links[(int) $r['ANIMALID']])) { $result['unresolved']++; } } foreach ($src['log'] ?? [] as $r) { if ((int) ($r['LINKTYPE'] ?? -1) === 0) { if (!isset($links[(int) $r['LINKID']])) { $result['unresolved']++; } $name = mb_strtolower($logTypes[(int) $r['LOGTYPEID']] ?? ''); if ($name === 'poids') { $result['weights']++; } else { $result['medical_notes']++; } } } if (!$apply) { return $result; } if ($createBackup) { $b = BackupService::create('pre-asm3-import'); $result['backup'] = $b['name'] ?? null; } $ownsTransaction = !$db->inTransaction(); if ($ownsTransaction) { $db->beginTransaction(); } try { $runId = self::runId($db, $path, $hash, $result['backup'], 'médical'); foreach ($src['animalvaccination'] ?? [] as $r) { $animal = $links[(int) $r['ANIMALID']] ?? 0; if (!$animal) { continue; } $source = (int) $r['ID']; if (self::seen($db, $hash, 'vaccination', $source)) { $result['already_imported']++; continue; } $name = $types[(int) $r['VACCINATIONID']] ?? 'Vaccin ASM3 ' . (int) $r['VACCINATIONID']; $db->prepare('INSERT OR IGNORE INTO ref_vaccines(name) VALUES(:name)')->execute([':name' => $name]); $q = $db->prepare('SELECT id FROM ref_vaccines WHERE name=:name'); $q->execute([':name' => $name]); $vaccine = (int) $q->fetchColumn(); $db->prepare( 'INSERT INTO vaccinations(animal_id,vaccine_id,done_date,due_date,lot,notes,manufacturer,batch_expires_on,administered_by_name) VALUES(:animal,:vaccine,:done,:due,:lot,:notes,:manufacturer,:expires,:by)', )->execute([ ':animal' => $animal, ':vaccine' => $vaccine, ':done' => self::date($r['DATEOFVACCINATION'] ?? null) ?: self::date($r['DATEREQUIRED'] ?? null) ?: date('Y-m-d'), ':due' => self::date($r['DATEREQUIRED'] ?? null), ':lot' => trim((string) $r['BATCHNUMBER']) ?: null, ':notes' => trim((string) $r['COMMENTS']) ?: null, ':manufacturer' => trim((string) $r['MANUFACTURER']) ?: null, ':expires' => self::date($r['BATCHEXPIRYDATE'] ?? null), ':by' => trim((string) $r['GIVENBY']) ?: null, ]); self::link($db, $hash, 'vaccination', $source, (int) $db->lastInsertId(), $runId); } foreach ($src['log'] ?? [] as $r) { if ((int) ($r['LINKTYPE'] ?? -1) !== 0) { continue; } $animal = $links[(int) $r['LINKID']] ?? 0; if (!$animal) { continue; } $source = (int) $r['ID']; $name = $logTypes[(int) $r['LOGTYPEID']] ?? 'Historique'; $entity = mb_strtolower($name) === 'poids' ? 'measurement' : 'medical-log'; if (self::seen($db, $hash, $entity, $source)) { continue; } $text = trim((string) $r['COMMENTS']); $date = self::date($r['DATE'] ?? null) ?: date('Y-m-d'); if ($entity === 'measurement' && preg_match('/([0-9]+(?:[.,][0-9]+)?)/', $text, $m)) { $value = (float) str_replace(',', '.', $m[1]); $db->prepare( "INSERT INTO measurements(animal_id,measured_at,type,value,unit,notes) VALUES(:animal,:date,'weight',:value,'kg',:notes)", )->execute([':animal' => $animal, ':date' => $date, ':value' => $value, ':notes' => $text]); } else { $db->prepare( "INSERT INTO medical_notes(animal_id,noted_at,kind,reason,plan) VALUES(:animal,:date,'autre',:reason,:plan)", )->execute([':animal' => $animal, ':date' => $date, ':reason' => $name, ':plan' => $text ?: null]); } self::link($db, $hash, $entity, $source, (int) $db->lastInsertId(), $runId); } if ($ownsTransaction) { $db->commit(); } } catch (Throwable $e) { if ($ownsTransaction && $db->inTransaction()) { $db->rollBack(); } throw $e; } return $result; } private static function animalLinks(PDO $db, string $hash): array { $s = $db->prepare( "SELECT source_id,target_id FROM asm3_import_links WHERE source_sha256=:h AND entity_type='animal'", ); $s->execute([':h' => $hash]); $o = []; foreach ($s as $r) { $o[(int) $r['source_id']] = (int) $r['target_id']; } return $o; } private static function runId(PDO $db, string $p, string $h, ?string $b, string $label): int { $db->prepare( "INSERT INTO asm3_import_runs(source_name,source_sha256,status,summary_json,backup_name) VALUES(:n,:h,'completed','{}',:b)", )->execute([':n' => basename($p) . ' · ' . $label, ':h' => $h, ':b' => $b]); return (int) $db->lastInsertId(); } private static function seen(PDO $db, string $h, string $e, int $s): bool { $q = $db->prepare('SELECT 1 FROM asm3_import_links WHERE source_sha256=:h AND entity_type=:e AND source_id=:s'); $q->execute([':h' => $h, ':e' => $e, ':s' => $s]); return (bool) $q->fetchColumn(); } private static function link(PDO $db, string $h, string $e, int $s, int $t, int $r): void { $db->prepare( 'INSERT INTO asm3_import_links(source_sha256,entity_type,source_id,target_id,import_run_id) VALUES(:h,:e,:s,:t,:r)', )->execute([':h' => $h, ':e' => $e, ':s' => $s, ':t' => $t, ':r' => $r]); } private static function date(mixed $v): ?string { return $v && preg_match('/^\d{4}-\d{2}-\d{2}/', (string) $v, $m) ? $m[0] : null; } }