anaf-sync 0.5.1 — PDF-urile lipsă se refac singure
Un PDF refuzat de ANAF nu mai rămâne lipsă pentru totdeauna: se randează din
ZIP-ul deja salvat, la fiecare rulare. O sincronizare nu mai cade din cauza
unei secunde de diferență între ceasul tău și al ANAF, mesajele pe care ANAF
le listează dar nu le mai dă nu mai sunt raportate ca erori, iar lista de
facturi nu-ți mai fuge de sub cursor. /
Missing PDFs now re-render from the archived ZIPs at the end of every sync (and
on demand, via a new anaf-sync render); a one-second clock disagreement no
longer fails the whole listing; messages past ANAF's download window stop being
counted as failures; the Facturi list stops resetting its scrollbar.
Versiunea 0.5.0 a ajuns pe PyPI, dar nu a primit niciodată o pagină de release
și nici programe descărcabile: pasul care le construiește a picat pe Windows,
după publicare. Conținutul ei e inclus aici, iar 0.5.1 e versiunea completă —
dacă ai instalat deja 0.5.0 de pe PyPI, ai tot ce e descris mai jos, cu
excepția reparației din secțiunea Internal.
Îmbunătățiri
PDF-urile lipsă se refac din ZIP-urile deja salvate. Se poate întâmpla ca
descărcarea să reușească, dar randarea PDF-ului să fie refuzată de ANAF —
firewall-ul lor respinge uneori XML-ul unor facturi perfect valide
(anafpy#10). Mesajul se
arhiva atunci doar cu ZIP-ul, iar pentru că evidența nu revizitează niciodată
un mesaj deja arhivat — regula care garantează că nimic nu se descarcă de două
ori — PDF-ul lipsea definitiv.
Nu mai e cazul, și nu trebuie făcut nimic: fiecare rulare sync se încheie
încercând din nou PDF-urile restante, direct din ZIP-urile de pe disc. Nu
cere autentificare și nu se supune limitei de 60 de zile — XML-ul e deja la
tine, iar serviciul de transformare al ANAF e public. Când există restanțe,
sumarul rulării capătă o linie pdf repair: ….
Același pas există și ca o comandă de sine stătătoare, pentru o arhivă
existentă pe care vrei s-o repari pe loc:
anaf-sync render --dry-run # arată ce PDF-uri lipsesc și s-ar putea randa
anaf-sync render # le randează din ZIP-urile salvateUn refuz repetat nu strică rularea și nu schimbă codul de ieșire: spre
deosebire de o descărcare pierdută, aici nu curge niciun termen — se
reîncearcă la următoarea rulare, până când ANAF acceptă documentul. Rândurile
venite din backfill sunt excluse deliberat: catalogează foldere pe care
sincronizarea nu le administrează. (Închide
#4.)
Remedieri
O secundă de diferență între ceasuri nu mai ratează toată sincronizarea.
ANAF verifică ambele capete ale intervalului cerut după ceasul lui: sfârșitul
nu poate fi în viitor, începutul nu poate depăși limita de 60 de zile. O
căutare pe fereastra completă atinge exact ambele limite, așa că orice
nepotrivire între ceasul calculatorului tău și al ANAF — într-o direcție sau
alta — respingea listarea în întregime:
endTime = 30-07-2026 15:15:28 nu poate in viitor fata de momentul
requestului = 30-07-2026 15:15:27
Nu e o situație exotică: o sincronizare programată care ia toată fereastra
trece pe ambele limite la fiecare rulare, iar un Windows al cărui serviciu de
timp nu e ținut din scurt ajunge acolo singur. Eroarea arată ca o defecțiune
la ANAF, deși e o problemă de ceas.
anafpy 0.7.2 construiește fereastra puțin în interiorul limitelor, iar dacă
diferența e mai mare, o reface după ceasul pe care ANAF îl indică chiar în
mesajul de respingere, și reia pagina o singură dată. Nu ai nimic de
configurat; actualizarea aduce versiunea nouă cu ea.
Mesajele pe care ANAF le listează, dar nu le mai dă, nu mai sunt erori.
Listarea și descărcarea își numără cele 60 de zile din puncte diferite, așa
că ANAF listează mesaje al căror termen de descărcare s-a închis deja. Orice
căutare care ajunge atât de departe atinge banda aceea de la margine — adică
mai ales prima sincronizare: la primul run al unui client, trei mesaje din 512.
Fiecare ajungea în tabela de eșecuri, cu cod de ieșire diferit de zero și un
avertisment chihlimbariu permanent în tray — peste ceva ce nicio reîncercare nu
repară și niciun operator nu poate curăța. Acum sunt consemnate în jurnal,
numărate și lăsate să treacă: fără rând în failures, fără rând în arhivă.
Nimic nu blochează o nouă încercare, deci un verdict greșit ar costa o
reîncercare, nu factura.
Contorul rămâne totuși vizibil în anaf-sync status, pentru că își merită
locul: la prima sincronizare e doar zgomot, dar la una ulterioară înseamnă că
programarea a avut o pauză destul de mare cât să se piardă facturi.
Lista de facturi nu mai sare sub cursor. Cu peste 500 de facturi,
scrollbar-ul din fereastra Facturi sărea la fiecare jumătate de secundă, până
devenea imposibil de folosit. Chiar cititul arhivei de către tray atingea
fișierele bazei de date, supraveghetorul raporta atingerea ca pe o schimbare,
iar reîncărcarea declanșată o lua de la capăt — o buclă care se autoîntreținea
și readucea lista la prima pagină de fiecare dată.
Acum evenimentele din sistemul de fișiere spun doar uită-te, nu reîncarcă:
se reîncarcă doar când datele chiar s-au schimbat. Iar reîncărcările
legitime nu mai dezorientează — lista își păstrează câte pagini erau deschise,
își regăsește rândul de sus și factura selectată, deci o sincronizare care se
încheie în timp ce citești nu-ți mai mută ecranul și nu-ți mai golește panoul
de detalii.
Internal
- The tray suite now runs on Windows in CI, not first at release time. The
0.5.0 bundle job failed on a watcher test: Windows refuses to unlink a file
another handle has open, and the probe's persistent connection is exactly
that. CI's matrix did includewindows-latest, but synced it core-only, so
importorskipdropped the whole Qt suite there — the tray's first Windows
run was inside the release job, the one place a failure is expensive
(0.5.0 published to PyPI and then produced no release at all). Every leg now
syncs the tray extra. - On Windows,
state.dbcannot be deleted while the tray runs. The
behaviour stands rather than being worked around: syncing is unaffected — WAL
admits the concurrent writer, and only deletion hits the sharing violation
— and dropping the handle between polls would reopen the refresh loop the
probe exists to close. Rebuilding a lost archive there means quitting the
tray first. Recorded in the watcher's docstring and DESIGN.md; the test
skips on Windows, saying why. - The watcher decides refreshes from
PRAGMA data_version, not file
events. The tray's own read-only opens write WAL read-marks, so a
filesystem watch cannot distinguish its own reads from a sync's commits. A
single persistentmode=roconnection — the one deliberate exception to the
tray's ephemeral-connection pattern — reads the counter, which only moves
when another connection commits. File stats survive for identity alone, so a
deleted or rebuiltstate.dbre-registers instead of hiding behind a pinned
inode. The 60 s poll backstop inherits the same gate. CLAUDE.md and DESIGN.md
record the exception so it does not get "corrected" away. RunRecordgainsexpired. It is a JSON blob, so records written by
earlier versions parse with the default — no migration, no schema change.- Requires
anafpy>=0.7.2— which both names the expired-download
condition and holds the listing window inside ANAF's limits. The engine
lists bydays, never an explicitstart/end, so it sits on exactly the
path the clock margin covers and needed no change of its own.
pip install -U anaf-syncpulls it in.
⚠️ Verificare. Repararea PDF-urilor a fost testată pe o copie a unei
arhive reale: un rând căruia i s-a șters PDF-ul a fost randat și consemnat,
patru facturi respinse de firewall au rămas în așteptare, iar două rânduri
fără ZIP pe disc au fost sărite. Tratarea mesajelor expirate are teste, dar
nu a fost încă verificată pe un răspuns real de la ANAF — dacă o
sincronizare le raportează în continuare ca eșecuri, spune. La fel și
corecția de ceas: un ceas rămas în urmă cu peste cinci minute tot
respinge listarea, pentru că partea aceea a mesajului ANAF nu indică
niciun moment după care s-ar putea corecta.
⚠️ The bundles are unsigned — no macOS notarization, no Windows
Authenticode. First run triggers the usual OS warning; use the
right-click-open workaround documented in the README. The CLI on PyPI
(pip install anaf-sync) is unaffected.
Full changelog: v0.5.0...v0.5.1