Chantier archivage flotte : restructuration par boîtier (pas seulement les bateaux affectés), quarantaine MMSI/sans_affectation avec script de réexamen dédié (cron_quarantaine.php), rejet des erreurs techniques non transitoires, fonctions communes extraites (fonctions_archivage.php), renommages de cohérence (TB_Affectations_bateaux, TB_Generations_bateaux, ID_Bateau_affecte), traçabilité MMSI complète (cf synthèse §38-39)

This commit is contained in:
2026-10-02 09:21:59 +00:00
parent 1dd4d0c288
commit 3a9056f5bc
5 changed files with 1084 additions and 0 deletions
+480
View File
@@ -0,0 +1,480 @@
<?php
// ============================================================================
// cron_archivage.php - Traitement périodique des archives de la flotte
// (cf synthèse §32-36). Exécuté via cron, dans le conteneur ship2net-php :
// docker exec ship2net-php php /var/www/cron_archivage.php
//
// Construction progressive, bloc par bloc :
// Bloc 1 (ce fichier) : résolution des bateaux actifs + liste des archives
// en attente sur chacun.
// Blocs suivants (à venir) : récupération, vérification d'intégrité,
// résolution de génération, chargement, confirmation.
// ============================================================================
// Certains fichiers du site (fonctions_mail.php notamment) utilisent des
// chemins relatifs ("../vendor/autoload.php"), qui se résolvent par rapport
// au répertoire de travail du processus - pas par rapport à l'emplacement du
// fichier qui les appelle. Ça fonctionnait par accident tant qu'Apache fixait
// ce répertoire à /var/www/html (la racine web). Fixé explicitement ici pour
// ne jamais dépendre de l'endroit d'où ce script est lancé - crucial pour une
// tâche cron, qui n'a généralement aucun répertoire de travail prévisible.
chdir("/var/www/html");
require_once __DIR__ . "/config/config.php";
require_once __DIR__ . "/config/fonctions_bdd.php";
require_once __DIR__ . "/fonctions_archivage.php";
// ----------------------------------------------------------------------------
// Port d'écoute de shipheart_api sur chaque CM5 (cf synthèse §33.1).
// ----------------------------------------------------------------------------
const PORT_API_CM5 = 8080;
// Délais réseau volontairement courts : un bateau injoignable ne doit jamais
// bloquer longtemps le traitement des autres (cf synthèse §33.1 - le cron
// retentera au prochain cycle, ce n'est jamais une perte).
const TIMEOUT_CONNEXION_CM5 = 5;
const TIMEOUT_REQUETE_CM5 = 15;
// ----------------------------------------------------------------------------
// Liste TOUS les boîtiers connus de ship2net (TB_Cartes avec une adresse
// NetBird renseignée) - pas seulement ceux affectés à un bateau. Nécessaire
// pour pouvoir mettre en quarantaine les archives d'un boîtier jamais affecté
// ou retiré, pas seulement celles d'une divergence MMSI (cf synthèse §38).
// Un boîtier neuf doit donc être déclaré dans TB_Cartes (adresse NetBird
// renseignée) avant toute affectation, pour entrer dans cette liste.
// Renvoie un tableau de ['Adresse_mac' => string, 'Adresse_netbird' => string].
// ----------------------------------------------------------------------------
function Lister_boitiers_connus($db)
{
$requete = "SELECT Adresse_mac, Adresse_netbird FROM TB_Cartes
WHERE Adresse_netbird IS NOT NULL AND Adresse_netbird <> ''";
$resultat = $db->query($requete);
$boitiers = [];
while ($ligne = $resultat->fetch_assoc()) $boitiers[] = $ligne;
return $boitiers;
}
// ----------------------------------------------------------------------------
// Interroge GET /archives sur le boîtier d'un bateau. Renvoie :
// ['ok' => true, 'archives' => [...]] en cas de succès
// ['ok' => false, 'raison' => "..."] sinon (bateau injoignable, réponse
// invalide...) - jamais une erreur
// fatale, le cron doit continuer
// avec les autres bateaux.
// Le tri chronologique est déjà garanti côté API par le nom de fichier
// (cf synthèse §32.4) ; retrié ici aussi par précaution, sans coût réel.
// ----------------------------------------------------------------------------
function Lister_archives_en_attente($adresse_netbird)
{
if (empty($adresse_netbird))
{
return ['ok' => false, 'raison' => "aucune adresse NetBird enregistrée pour ce boîtier"];
}
$url = "http://" . $adresse_netbird . ":" . PORT_API_CM5 . "/archives";
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, TIMEOUT_CONNEXION_CM5);
curl_setopt($ch, CURLOPT_TIMEOUT, TIMEOUT_REQUETE_CM5);
$reponse_brute = curl_exec($ch);
$erreur_curl = curl_error($ch);
$code_http = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
if ($reponse_brute === false)
{
return ['ok' => false, 'raison' => "injoignable (" . $erreur_curl . ")"];
}
if ($code_http !== 200)
{
return ['ok' => false, 'raison' => "réponse HTTP " . $code_http];
}
$donnees = json_decode($reponse_brute, true);
if (!is_array($donnees) || ($donnees['status'] ?? '') !== 'ok' || !isset($donnees['archives']))
{
return ['ok' => false, 'raison' => "réponse JSON invalide ou inattendue"];
}
$archives = $donnees['archives'];
usort($archives, fn($a, $b) => strcmp($a['nom'], $b['nom']));
return ['ok' => true, 'archives' => $archives];
}
// ----------------------------------------------------------------------------
// Extrait l'empreinte (16 hex) portée par le nom de fichier - même format,
// exactement, que la validation déjà faite côté shipheart_api (§33.1).
// Renvoie false si le nom ne correspond pas au format attendu.
// ----------------------------------------------------------------------------
function Extraire_empreinte_nom($nom_fichier)
{
if (!preg_match('/^[0-9A-Fa-f]{12}_\d{6}_\d{6}_([0-9A-Fa-f]{16})\.sql\.gz\.enc$/', $nom_fichier, $m)) return false;
return strtolower($m[1]);
}
// ----------------------------------------------------------------------------
// GET /archives/<nom> - récupère le contenu brut d'une archive précise.
// Même forme de retour que Lister_archives_en_attente() (['ok' => bool, ...]).
// ----------------------------------------------------------------------------
function Recuperer_archive($adresse_netbird, $nom_fichier)
{
$url = "http://" . $adresse_netbird . ":" . PORT_API_CM5 . "/archives/" . rawurlencode($nom_fichier);
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, TIMEOUT_CONNEXION_CM5);
curl_setopt($ch, CURLOPT_TIMEOUT, TIMEOUT_REQUETE_CM5);
$contenu = curl_exec($ch);
$erreur_curl = curl_error($ch);
$code_http = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
if ($contenu === false) return ['ok' => false, 'raison' => "injoignable (" . $erreur_curl . ")"];
if ($code_http !== 200) return ['ok' => false, 'raison' => "réponse HTTP " . $code_http];
return ['ok' => true, 'contenu' => $contenu];
}
// ----------------------------------------------------------------------------
// Vérifie que le contenu récupéré correspond bien à l'empreinte portée par
// son nom - détecte une corruption de transfert (cf synthèse §32.4/33.1).
// Ne jamais supprimer l'archive côté CM5 si cette vérification échoue - une
// nouvelle tentative au prochain cycle récupérera à nouveau le même fichier.
// ----------------------------------------------------------------------------
function Integrite_confirmee($nom_fichier, $contenu)
{
$empreinte_attendue = Extraire_empreinte_nom($nom_fichier);
if ($empreinte_attendue === false) return false;
$empreinte_calculee = substr(hash('sha256', $contenu), 0, 16);
return $empreinte_calculee === $empreinte_attendue;
}
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
// Copie de sauvegarde des archives mises en quarantaine (MMSI ou affectation absente) - avant de
// confirmer la suppression côté CM5, pour ne jamais perdre le contenu pendant que la quarantaine est en
// attente (cf synthèse §38, correction d'un manque identifié en testant le mécanisme).
// ----------------------------------------------------------------------------
// Met une archive en quarantaine (divergence MMSI) : sauvegarde son contenu
// CHIFFRÉ tel que reçu (pour ne rien perdre pendant que la quarantaine est en
// attente - correction d'un manque identifié en testant le mécanisme, cf
// synthèse §38), enregistre la ligne dans TB_Archives_traitees, puis confirme
// la suppression côté CM5 (son intégrité a déjà été vérifiée avant cet appel).
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
// Met une archive en quarantaine faute d'affectation active (boîtier jamais
// affecté, ou retiré - cf synthèse §38). Contrairement à la quarantaine MMSI,
// aucun déchiffrement n'est nécessaire : sans bateau à qui l'attribuer, ni
// l'en-tête ni le contenu ne servent à rien à ce stade.
// ----------------------------------------------------------------------------
function Mettre_en_quarantaine_sans_affectation($db_principale, $adresse_netbird, $nom_fichier, $contenu_chiffre, $adresse_mac)
{
if (!is_dir(REPERTOIRE_QUARANTAINE)) @mkdir(REPERTOIRE_QUARANTAINE, 0750, true);
file_put_contents(REPERTOIRE_QUARANTAINE . "/" . $nom_fichier, $contenu_chiffre);
$statut = 'en_quarantaine_sans_affectation';
$requete = "INSERT INTO TB_Archives_traitees (Nom_fichier, Adresse_mac, Statut) VALUES (?, ?, ?)";
$stmt = $db_principale->prepare($requete);
$stmt->bind_param('sss', $nom_fichier, $adresse_mac, $statut);
$stmt->execute();
$stmt->close();
Confirmer_suppression_archive($adresse_netbird, $nom_fichier);
}
// ----------------------------------------------------------------------------
// Rejette une archive suite à une erreur technique NON transitoire (cf
// synthèse §38) : déchiffrement, version inconnue, fichier SQL introuvable,
// échec de création de génération, échec de chargement. Sauvegarde le
// contenu chiffré dans un répertoire dédié (suivi manuel, reprise éventuelle
// une fois la cause corrigée), enregistre Statut='rejetee' avec le détail de
// l'erreur, puis confirme la suppression côté CM5 - inutile de laisser le
// CM5 retransmettre indéfiniment un fichier dont le contenu ne changera
// jamais.
// ----------------------------------------------------------------------------
function Rejeter_archive($db_principale, $adresse_netbird, $nom_fichier, $contenu_chiffre, $adresse_mac, $id_bateau, $detail_erreur, $mmsi_recu = null, $mmsi_attendu = null)
{
if (!is_dir(REPERTOIRE_ERREURS)) @mkdir(REPERTOIRE_ERREURS, 0750, true);
file_put_contents(REPERTOIRE_ERREURS . "/" . $nom_fichier, $contenu_chiffre);
$statut = 'rejetee';
$requete = "INSERT INTO TB_Archives_traitees
(Nom_fichier, Adresse_mac, ID_Bateau_affecte, ID_Bateau_final, Statut, Detail_erreur, MMSI_recu, MMSI_attendu, Date_resolution)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, NOW())";
$stmt = $db_principale->prepare($requete);
$stmt->bind_param('ssiissss', $nom_fichier, $adresse_mac, $id_bateau, $id_bateau, $statut, $detail_erreur, $mmsi_recu, $mmsi_attendu);
$stmt->execute();
$stmt->close();
Confirmer_suppression_archive($adresse_netbird, $nom_fichier);
}
function Mettre_en_quarantaine_mmsi($db_principale, $adresse_netbird, $nom_fichier, $contenu_chiffre, $adresse_mac, $id_bateau, $mmsi_recu, $mmsi_attendu)
{
if (!is_dir(REPERTOIRE_QUARANTAINE)) @mkdir(REPERTOIRE_QUARANTAINE, 0750, true);
file_put_contents(REPERTOIRE_QUARANTAINE . "/" . $nom_fichier, $contenu_chiffre);
$statut = 'en_quarantaine_mmsi';
$requete = "INSERT INTO TB_Archives_traitees
(Nom_fichier, Adresse_mac, ID_Bateau_affecte, Statut, MMSI_recu, MMSI_attendu)
VALUES (?, ?, ?, ?, ?, ?)";
$stmt = $db_principale->prepare($requete);
$stmt->bind_param('ssisss', $nom_fichier, $adresse_mac, $id_bateau, $statut, $mmsi_recu, $mmsi_attendu);
$stmt->execute();
$stmt->close();
Confirmer_suppression_archive($adresse_netbird, $nom_fichier);
}
// ----------------------------------------------------------------------------
// Vérifie si cette archive a déjà été traitée (quel que soit le résultat) -
// garde-fou anti-double-traitement, en complément de l'index UNIQUE déjà
// présent sur TB_Archives_traitees.Nom_fichier (cf synthèse §32.6/34.3).
// ----------------------------------------------------------------------------
function Archive_deja_traitee($db_principale, $nom_fichier)
{
$requete = "SELECT 1 FROM TB_Archives_traitees WHERE Nom_fichier=?";
$stmt = $db_principale->prepare($requete);
$stmt->bind_param('s', $nom_fichier);
$stmt->execute();
$stmt->store_result();
$trouve = $stmt->num_rows > 0;
$stmt->close();
return $trouve;
}
// ----------------------------------------------------------------------------
// Enregistre le résultat du traitement d'une archive dans TB_Archives_traitees
// (cf synthèse §34.3). $id_generation peut être null (non encore résolu, ex :
// bateau sans aucune affectation).
// ----------------------------------------------------------------------------
function Enregistrer_archive_traitee($db_principale, $nom_fichier, $adresse_mac, $id_bateau, $id_generation, $statut, $mmsi_recu = null, $mmsi_attendu = null)
{
// MMSI_recu/MMSI_attendu toujours renseignés quand connus, même en cas de concordance parfaite -
// trace d'historique, et par principe : un NULL ne doit jamais pouvoir signifier à la fois "MMSI
// inconnu" et "MMSI connu mais non indiqué ici" (cf synthèse §39).
$requete = "INSERT INTO TB_Archives_traitees
(Nom_fichier, Adresse_mac, ID_Bateau_affecte, ID_Bateau_final, ID_Generation, Statut, MMSI_recu, MMSI_attendu, Date_resolution)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, NOW())";
$stmt = $db_principale->prepare($requete);
$stmt->bind_param('ssiiisss', $nom_fichier, $adresse_mac, $id_bateau, $id_bateau, $id_generation, $statut, $mmsi_recu, $mmsi_attendu);
$ok = $stmt->execute();
$stmt->close();
return $ok;
}
// ----------------------------------------------------------------------------
// DELETE /archives/<nom> - confirme le traitement auprès du CM5, qui peut
// alors supprimer le fichier local (cf synthèse §33.1).
// ----------------------------------------------------------------------------
function Confirmer_suppression_archive($adresse_netbird, $nom_fichier)
{
$url = "http://" . $adresse_netbird . ":" . PORT_API_CM5 . "/archives/" . rawurlencode($nom_fichier);
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, TIMEOUT_CONNEXION_CM5);
curl_setopt($ch, CURLOPT_TIMEOUT, TIMEOUT_REQUETE_CM5);
$reponse = curl_exec($ch);
$code_http = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
return ($reponse !== false && $code_http === 200);
}
// ----------------------------------------------------------------------------
// Bloc principal - pour l'instant, se limite à afficher ce qui serait traité.
// Les blocs suivants remplaceront cet affichage par le traitement réel.
// ----------------------------------------------------------------------------
function Executer_cron_archivage()
{
// Ce script tourne nativement sur le VPS (pas dans le conteneur ship2net-php) - contrairement au site
// web, il ne peut pas résoudre "ship2net-db" (nom DNS interne à Docker). Le port de ship2net-db a été
// publié sur 127.0.0.1:3307 uniquement (jamais exposé publiquement, ni même via NetBird) - le 3306
// natif du port est déjà pris par l'instance MariaDB native du VPS, un usage différent (cf synthèse §37).
Ouverture_bdd("127.0.0.1:3307", BDD_USER, BDD_PASSWORD, BDD_DATABASE);
$db = $_SESSION['db'];
$boitiers = Lister_boitiers_connus($db);
echo count($boitiers) . " boîtier(s) connu(s) avec une adresse NetBird renseignée.\n\n";
foreach ($boitiers as $boitier)
{
$adresse_mac = $boitier['Adresse_mac'];
$resultat = Lister_archives_en_attente($boitier['Adresse_netbird']);
if (!$resultat['ok'])
{
echo "Boîtier {$adresse_mac} : " . $resultat['raison'] . " - ignoré pour ce cycle.\n";
continue;
}
$nb = count($resultat['archives']);
if ($nb === 0) continue; // rien à signaler pour ce boîtier ce cycle-ci
// Résolu UNE SEULE FOIS par boîtier (pas par archive) : toutes les archives d'un même passage
// partagent la même adresse MAC, et l'affectation ne change jamais en cours d'exécution du
// script - seul un geste administratif explicite la modifie (cf synthèse §38).
$affectation = Resoudre_affectation_active($db, $adresse_mac);
$libelle = $affectation['existe'] ? "Bateau {$affectation['id_bateau']}" : "Boîtier {$adresse_mac} (aucune affectation active)";
echo "{$libelle} : {$nb} archive(s) en attente.\n";
foreach ($resultat['archives'] as $archive)
{
$nom = $archive['nom'];
if (Archive_deja_traitee($db, $nom))
{
echo " - {$nom} : déjà traitée - confirmation de suppression seulement.\n";
Confirmer_suppression_archive($boitier['Adresse_netbird'], $nom);
continue;
}
$recuperation = Recuperer_archive($boitier['Adresse_netbird'], $nom);
if (!$recuperation['ok'])
{
echo " - {$nom} : ÉCHEC de récupération (" . $recuperation['raison'] . ") - nouvelle tentative au prochain cycle.\n";
continue;
}
if (!Integrite_confirmee($nom, $recuperation['contenu']))
{
echo " - {$nom} : empreinte invalide, archive potentiellement corrompue - nouvelle tentative au prochain cycle.\n";
continue;
}
// Aucune affectation active (jamais affecté, ou retiré) : quarantaine directe, sans
// déchiffrement - ni l'en-tête ni le contenu ne servent à rien sans bateau à qui l'attribuer
// (cf synthèse §38) :
if (!$affectation['existe'])
{
echo " - {$nom} : aucune affectation active pour ce boîtier - mise en quarantaine.\n";
Mettre_en_quarantaine_sans_affectation($db, $boitier['Adresse_netbird'], $nom, $recuperation['contenu'], $adresse_mac);
continue;
}
$id_bateau = $affectation['id_bateau'];
$dechiffrement = Decrypter_archive($recuperation['contenu']);
if (!$dechiffrement['ok'])
{
echo " - {$nom} : ÉCHEC de déchiffrement (" . $dechiffrement['raison'] . ") - rejetée.\n";
Rejeter_archive($db, $boitier['Adresse_netbird'], $nom, $recuperation['contenu'], $adresse_mac, $id_bateau, "Échec de déchiffrement : " . $dechiffrement['raison']);
continue;
}
$origine_bdd = $dechiffrement['entete']['Origine_bdd'] ?? 'normale';
$mmsi_recu = $dechiffrement['entete']['No_mmsi'] ?? null;
$mmsi_attendu_connu = null; // reste null si No_mmsi absent de l'en-tête - sinon toujours renseigné (cf synthèse §39)
if ($mmsi_recu !== null)
{
$verification_mmsi = Verifier_mmsi($db, $id_bateau, $mmsi_recu);
$mmsi_attendu_connu = $verification_mmsi['attendu'];
if ($verification_mmsi['statut'] === 'quarantaine')
{
echo " - {$nom} : MMSI reçu ({$mmsi_recu}) différent de celui attendu ({$verification_mmsi['attendu']}) - mise en quarantaine.\n";
Mettre_en_quarantaine_mmsi($db, $boitier['Adresse_netbird'], $nom, $recuperation['contenu'], $adresse_mac, $id_bateau, $mmsi_recu, $verification_mmsi['attendu']);
continue;
}
}
$version_cible = $dechiffrement['entete']['Version_soft_no_package'] ?? null;
$generation_active = Resoudre_generation_active($db, $id_bateau);
$nouvelle_generation = Nouvelle_generation_necessaire($origine_bdd, $generation_active);
echo " - {$nom} : récupérée et intègre (" . strlen($recuperation['contenu']) . " octets), origine=" . $origine_bdd . "\n";
$fichier_sql = Resoudre_fichier_sql($db, $version_cible);
if ($fichier_sql === false)
{
echo " ÉCHEC : version logicielle '{$version_cible}' inconnue de TB_Versions - rejetée.\n";
Rejeter_archive($db, $boitier['Adresse_netbird'], $nom, $recuperation['contenu'], $adresse_mac, $id_bateau, "Version logicielle '{$version_cible}' inconnue de TB_Versions", $mmsi_recu, $mmsi_attendu_connu);
continue;
}
$contenu_fichier_sql = @file_get_contents(REPERTOIRE_VERSIONS_SQL . "/" . $fichier_sql);
if ($contenu_fichier_sql === false)
{
echo " ÉCHEC : fichier {$fichier_sql} introuvable sur le VPS - rejetée.\n";
Rejeter_archive($db, $boitier['Adresse_netbird'], $nom, $recuperation['contenu'], $adresse_mac, $id_bateau, "Fichier {$fichier_sql} introuvable sur le VPS", $mmsi_recu, $mmsi_attendu_connu);
continue;
}
if ($nouvelle_generation)
{
$zone_usine = Extraire_zone_usine($contenu_fichier_sql);
if ($zone_usine === false)
{
echo " ÉCHEC : marqueur Factory_structure introuvable dans {$fichier_sql} - rejetée.\n";
Rejeter_archive($db, $boitier['Adresse_netbird'], $nom, $recuperation['contenu'], $adresse_mac, $id_bateau, "Marqueur Factory_structure introuvable dans {$fichier_sql}", $mmsi_recu, $mmsi_attendu_connu);
continue;
}
// 'factory'/'restauration' est toujours un fait rapporté honnêtement par le bateau - à
// conserver tel quel pour le diagnostic, qu'une génération existait déjà côté serveur ou
// non. Seul 'normale' sans aucune génération existante (un bateau vraiment neuf, qui n'a
// jamais rien envoyé) devient 'creation_initiale' (cf synthèse §37).
if ($origine_bdd === 'factory' || $origine_bdd === 'restauration')
{
$motif = $origine_bdd;
}
else
{
$motif = 'creation_initiale';
}
$resultat_generation = Creer_generation($db, $id_bateau, $motif, $zone_usine);
if (!$resultat_generation['ok'])
{
echo " ÉCHEC de création de génération : " . $resultat_generation['raison'] . " - rejetée.\n";
Rejeter_archive($db, $boitier['Adresse_netbird'], $nom, $recuperation['contenu'], $adresse_mac, $id_bateau, "Échec de création de génération : " . $resultat_generation['raison'], $mmsi_recu, $mmsi_attendu_connu);
continue;
}
$nom_base_cible = $resultat_generation['nom_base'];
echo " Nouvelle génération créée : {$nom_base_cible} (motif={$motif}).\n";
}
else
{
$nom_base_cible = $generation_active['nom_base'];
echo " Génération existante : {$nom_base_cible}.\n";
// Bloc suivant : rejeu des segments incrémentaux si la version a changé,
// via Extraire_segments_incrementaux() + exécution (même mécanique que Creer_generation).
}
$chargement = Charger_contenu_archive($nom_base_cible, $dechiffrement['sql']);
if (!$chargement['ok'])
{
echo " ÉCHEC de chargement dans {$nom_base_cible} : " . $chargement['raison'] . " - rejetée.\n";
Rejeter_archive($db, $boitier['Adresse_netbird'], $nom, $recuperation['contenu'], $adresse_mac, $id_bateau, "Échec de chargement dans {$nom_base_cible} : " . $chargement['raison'], $mmsi_recu, $mmsi_attendu_connu);
continue;
}
echo " Contenu chargé dans {$nom_base_cible}.\n";
// Résolution de la vraie Cle_primaire de la génération (celle juste créée, ou l'existante) :
$generation_pour_enregistrement = Resoudre_generation_active($db, $id_bateau);
Enregistrer_archive_traitee($db, $nom, $adresse_mac, $id_bateau, $generation_pour_enregistrement['cle'], 'traitee', $mmsi_recu, $mmsi_attendu_connu);
if (Confirmer_suppression_archive($boitier['Adresse_netbird'], $nom))
{
echo " Archive confirmée et supprimée côté CM5.\n";
}
else
{
echo " AVERTISSEMENT : confirmation de suppression échouée (le fichier restera côté CM5, sans conséquence - cf synthèse §32.5).\n";
}
}
}
Fermeture_bdd();
}
Executer_cron_archivage();