OK Intégrer OKLÉON

2 · Tenir un miroir

Le cas le plus courant : garder chez soi une copie à jour du portefeuille d'une agence. Le premier passage charge tout ; les suivants ne rapportent que ce qui a changé — et les suppressions, qu'une simple liste ne peut pas montrer.

La copie locale

28
biens
22
locataires
16
proprietaires
56
occupations

Point de reprise : 2026-10-30T14:07:02+01:00 — l'heure du serveur au début du dernier passage réussi.

Journal des synchronisations

QuandModeReçuesSuppriméesAppelsDuréeRésultat
30/10 14:07:02 rien de neuf 00 1278 ms réussie
30/10 13:07:02 rien de neuf 00 1253 ms réussie
30/10 12:07:01 rien de neuf 00 1212 ms réussie
30/10 11:07:01 rien de neuf 00 1216 ms réussie
30/10 10:07:01 rien de neuf 00 1230 ms réussie
30/10 09:07:01 rien de neuf 00 1209 ms réussie
30/10 08:07:01 rien de neuf 00 1198 ms réussie
30/10 07:07:01 rien de neuf 00 1232 ms réussie
30/10 06:07:01 rien de neuf 00 1221 ms réussie
30/10 05:07:01 rien de neuf 00 1244 ms réussie

Derniers biens modifiés

IdentifiantAdresseModifié
bien_6acf9ffb617f69.38387722 14 Rue François Toulouse 14/10 17:30
bien_d26_01 14 rue de la Fusterie Perpignan 14/10 15:50
bien_d26_02 14 rue de la Fusterie Perpignan 14/10 15:50
bien_d26_03 27 avenue du Général Leclerc Perpignan 14/10 15:50
bien_d26_04 27 avenue du Général Leclerc Perpignan 14/10 15:50
bien_d26_05 27 avenue du Général Leclerc Perpignan 14/10 15:50
bien_d26_06 6 impasse des Micocouliers Canet-en-Roussillon 14/10 15:50
bien_d26_07 Résidence Les Mouettes, 45 boulevard de la Mer Saint-Cyprien 14/10 15:50

Derniers locataires modifiés

IdentifiantNomModifié
loc_d26_01 Lucas Lucas MARTINEZ 14/10 15:50
loc_d26_02 Emma Emma VIDAL 14/10 15:50
loc_d26_03 Thomas Thomas GARRIGUE 14/10 15:50
loc_d26_04 Chloé Chloé BONNET 14/10 15:50
loc_d26_05 Inès Inès BENYAHIA 14/10 15:50
loc_d26_06 Maxime Maxime SOLER 14/10 15:50
loc_d26_07 Manon Manon PUIG 14/10 15:50
loc_d26_08 Julien Julien DELCROS 14/10 15:50
Le code de la synchronisation — src/Miroir.php
/**
 * Une synchronisation complète. Rend un bilan, et l'inscrit au journal.
 */
public function synchroniser(ClientOkleon $api): array
{
    $t0     = microtime(true);
    $depuis = $this->etat('point_de_reprise');   // null la toute première fois
    $bilan  = ['mode' => $depuis === null ? 'initiale' : 'incrementale',
               'recus' => 0, 'supprimes' => 0, 'ignorees' => [], 'erreur' => null];
    try {
        // ── 0 et 2. Y a-t-il du NOUVEAU ? — et l'heure du SERVEUR ─────────────
        // Un seul appel répond aux deux questions. L'heure est prise AVANT de lire
        // quoi que ce soit : ce qui changera PENDANT notre lecture aura une date
        // postérieure et sera repris au prochain passage. On relira peut-être deux
        // fois une fiche — sans danger, l'enregistrement local écrase. On n'en perd
        // aucune.
        // ⚠️ Jamais l'heure de notre machine : deux horloges qui divergent d'une
        // minute font sauter une minute de changements, en silence.
        $tete         = $api->get('/changements', ['limit' => 1] + ($depuis ? ['since' => $depuis] : []));
        $nouveauPoint = (string)$tete['server_time'];
        // ⭐ Rien depuis notre point de reprise : on s'arrête là. C'est le cas de
        // la très grande majorité des passages. Mesuré sur cette démo le 22/09 :
        // sans ce raccourci, 7 appels par minute pour rien — la MOITIÉ du quota
        // quotidien de la clé, consommée à ne rien faire. Avec : un seul.
        // (Le flux couvre toutes les collections que la clé peut lire, suppressions
        // comprises : s'il est vide, aucune des nôtres n'a bougé.)
        if ($depuis !== null && empty($tete['data'])) {
            $this->poserEtat('point_de_reprise', $nouveauPoint);
            $bilan['mode'] = 'rien';
            return $this->clore($bilan, $api, $t0);
        }
        // ── 1. Ce que cette clé a le droit de lire sur CET espace ─────────────
        $espace   = $api->get('/espace');
        $lisibles = array_values(array_intersect(self::COLLECTIONS, $espace['collections'] ?? []));
        $bilan['ignorees'] = array_values(array_diff(self::COLLECTIONS, $lisibles));
        // Tout ou rien : si la synchronisation échoue en route, la copie locale
        // reste exactement dans l'état du passage précédent.
        $this->db->beginTransaction();
        // ── 3a. Créations et modifications : les listes, filtrées ─────────────
        foreach ($lisibles as $c) {
            $filtre = $depuis ? ['updated_since' => $depuis] : [];
            foreach ($api->toutesLesPages('/' . $c, $filtre) as $fiche) {
                $this->enregistrer($c, $fiche);
                $bilan['recus']++;
            }
        }
        // ── 3b. Suppressions : SEUL le flux s'en souvient ─────────────────────
        // Au premier passage il n'y a rien à supprimer : la copie est vide.
        if ($depuis !== null) {
            foreach ($api->toutesLesPages('/changements', ['since' => $depuis]) as $ch) {
                if (($ch['action'] ?? '') === 'supprime' && in_array($ch['collection'] ?? '', $lisibles, true)) {
                    $bilan['supprimes'] += $this->supprimer((string)$ch['collection'], (string)$ch['id']);
                }
            }
        }
        // ── 4. Le point de reprise n'avance QU'UNE FOIS TOUT APPLIQUÉ ─────────
        $this->poserEtat('point_de_reprise', $nouveauPoint);
        $this->db->commit();
    } catch (Throwable $e) {
        if ($this->db->inTransaction()) $this->db->rollBack();
        $bilan['erreur'] = $e instanceof ErreurOkleon ? $e->codeApi . ' — ' . $e->getMessage() : $e->getMessage();
    }
    return $this->clore($bilan, $api, $t0);
}