FastAWB
Intră în cont

Ce s-a schimbat

Fiecare versiune a pluginului, cu ce aduce. Același text îl vezi în WordPress, înainte să actualizezi.

3.10.02026-10-30

Schimbat

  • Serverul Fast AWB se mută pe `fastawb.app`. Pluginul folosește direct această adresă, iar câmpul „URL server Fast AWB” a dispărut din Configurare. Pentru test local, adresa se poate suprascrie din `wp-config.php` cu `define('AWB_SERVER_URL', 'http://127.0.0.1:5000');`.
  • Linkurile de urmărire noi pornesc de la `fastawb.app`. Cele deja trimise clienților pe `fastawb.monoweb.ro` rămân valide.
3.9.102026-10-30

Nou

  • Pe pagina comenzii, în caseta Fast AWB, apare linkul de urmărire trimis clientului.
  • Statusurile coletelor Sameday și DPD se actualizează automat de la curier, iar în panou (Notificări → „Colete și urmărire”) se văd starea, linkul trimis și notificările plecate.
3.9.92026-10-30

Nou

  • Calea linkului de urmărire se alege din panou (Notificări → „Calea paginii”): `colet` (implicit), `track`, `urm` etc. Linkul din SMS și email este `magazinul-tau.ro/<cale>/<cod>`. Pluginul preia calea de pe server și reîmprospătează singur regulile; o cale ocupată de o pagină din magazin nu se aplică și apare un avertisment în Fast AWB → Configurare.

Schimbat

  • Linkurile `/urmarire/<cod>` cu 10 caractere (trimise doar în testele din 3.9.6–3.9.7) nu mai funcționează. Linkurile vechi `/urmarire/<cod>/<token>` rămân valabile.
3.9.82026-10-30

Schimbat

  • Linkul din SMS și email, când pagina de tracking e pe domeniul magazinului, este `magazinul-tau.ro/colet/<cod>`, nu `/urmarire/<cod>`. Linkurile `/urmarire/<cod>` trimise deja continuă să funcționeze.
3.9.72026-10-30

Reparat

  • Alegerea manuală a lockerului pe comandă nu mai răspunde „Lockerul ales nu există în lista curierului": selectorul citea un câmp inexistent (`external_id`) în loc de `id`, deci trimitea o valoare goală la salvare.
3.9.62026-10-30

Nou

  • Pagina de tracking pe domeniul magazinului: cu „Pagină de tracking proprie” bifată în Notificări, linkul din SMS și email e `magazinul-tau.ro/urmarire/<cod>`, nu adresa serverului. Pluginul aduce pagina de pe server, cu tema aleasă din panou. Fără bifă, linkul rămâne cel de până acum.
3.9.52026-10-30

Nou

  • Editarea comenzii → caseta Fast AWB: la comenzile cu livrare la locker fără punct ales (plasate înainte de un fix) apare „Alege lockerul”: căutare după nume, adresă sau localitate, easybox Sameday și FANbox. Punctul se salvează pe comandă cu o notă, iar „Generează AWB” merge ca de obicei. Nu apare în lista de comenzi.

Reparat

  • Alegerea automată a curierului: FanCourier respingea cererea de tarif la ramburs și Sameday nu putea cota, deci câștiga un tarif configurat în loc de prețul real. Acum toți curierii dau cota reală.
  • Pagina publică de tracking: un AWB abia generat arăta „?/4”; acum e pasul 1, „AWB generat”. Pagina arată și numărul comenzii și, la ramburs, suma de plată la curier sau locker.
3.9.42026-10-30

Nou

  • Integrări → WooCommerce (REST): adresa magazinului și cheile CK/CS (criptate, cu buton de test). Cu ele, serverul scrie factura emisă direct pe comanda din WordPress (serie, număr, link, notă), fără să aștepte pluginul, ca pluginul SmartBill să o recunoască și să dispară „Emite factura”.

Reparat

  • TVA pe factură: când WooCommerce nu calculează taxe, factura ieșea fără TVA. Pluginul trimite acum și setările de TVA din pluginul SmartBill al magazinului (cota aleasă pentru produse și transport, prețuri cu sau fără TVA, lista de cote), iar serverul le aplică pe fiecare linie. Dacă acolo e „WooCommerce”, cota vine din taxele comenzii.
3.9.32026-10-30

Schimbat

  • Factura emisă se scrie pe comandă exact cum o face pluginul SmartBill (`smartbill_private_link`, `smartbill_invoice_log`, serie și număr), deci metabox-ul lui o arată, butonul „Trimite factura clientului” merge, iar linkul apare în contul clientului. Dacă pluginul SmartBill a emis deja documentul, Fast AWB nu mai emite a doua factură.
  • Documentul se emite prin `invoice/v2`, care întoarce și adresa lui; ciorna (fără număr) e acceptată ca răspuns valid.
  • Clientul persoană juridică se recunoaște și după datele din pluginul SmartBill (`smartbill_billing_type`, CIF, denumire firmă).
3.9.22026-10-30

Schimbat

  • Factura SmartBill se emite exact cum vine din WooCommerce: produsele, transportul, prețurile și cota de TVA ale fiecărei linii se iau din comandă. Din Integrări au dispărut TVA implicit, denumirea transportului și unitatea de măsură, iar factura nu mai primește mențiuni adăugate de noi.
  • Factura emisă se scrie înapoi pe comanda din WordPress (serie și număr, link privat, notă pe comandă), ca pluginul SmartBill din magazin să o recunoască. Sincronizarea rulează la 5 minute.

Reparat

  • SmartBill respingea liniile fără cod („Produsul Transport nu are codul specificat”). Fără SKU, linia primește ca cod denumirea ei din WooCommerce.
3.9.12026-10-30

Reparat

  • Facturarea: dacă serverul nu răspunde sau nu are facturarea disponibilă, pluginul reîncearcă de cel mult 3 ori, apoi scrie o notă pe comandă („Fast AWB: factura nu a putut fi cerută…”). Înainte reîncerca la infinit, fără nicio notă.
3.9.02026-10-30

Nou

  • Integrare SmartBill, în panou la Integrări: factura se emite automat în SmartBill când comanda e finalizată în WooCommerce, când AWB-ul e livrat, sau la ambele. Se configurează emailul contului, tokenul API, CIF-ul, seria și TVA; tokenul e criptat și nu se mai afișează. Buton de test al conexiunii, opțiune de emitere ca ciornă, trimitere pe email de către SmartBill și listă cu ultimele facturi (cu „Reia" la erori).
  • O comandă nu primește niciodată două facturi, iar dacă răspunsul SmartBill se pierde după trimitere, factura nu se reia singură (apare „de verificat"), ca să nu se dubleze.
  • Onboarding-ul are acum pași pentru notificări și facturare (opționali), pe lângă cei de până acum.
  • Clientul fastawb își alege aspectul paginii de urmărire din 6 modele (Atelier, Bilet, Panou, Revistă, Metrou, Elvețian), cu previzualizare în panou.

Reparat

  • „Detalii pe site-ul curierului" duce acum la pagina curierului, cu numărul AWB: FAN (`?tracking=`), Sameday (`status-colet/?awb=`) și DPD (`services.dpd.ro/tracking/?shipmentNumber=`). Înainte FAN ducea la un link care nu mai funcționa, Sameday nu avea link, iar previzualizarea ducea la formularul gol.
  • Lockerele se încarcă repede la checkout: județul se filtrează în interogare, nu în Python, iar tabela are indecși pe furnizor+județ.
  • O singură descărcare de lockere pe curier, nu una per cerere; un job periodic le reîmprospătează la 3 zile, ca checkout-ul să nu mai primească 503 după 7 zile.
3.8.52026-10-29

Nou

  • La checkout, câmpul Localitate are căutare, la fel ca Județul: clientul scrie primele litere și alege din listă, și pe telefon.
  • În lista de comenzi din admin, comenzile la locker arată direct lockerul ales (rețeaua și numele punctului; adresa și ID-ul apar la trecerea cu mouse-ul). Nu mai trebuie să intri în comandă.

Reparat

  • Butonul „Plasează comanda” rămâne dezactivat cât timp Județul sau Localitatea sunt goale.
  • O comandă FANbox salvată de o versiune veche cu modul de livrare greșit pleacă tot ca locker, deci tot prin FAN Courier, inclusiv la regenerarea AWB-ului după anulare.

Schimbat

  • Pagina publică de urmărire a coletului (server) are un design nou: titlu mare cu starea, data estimată de livrare scrisă în cuvinte („marți, 3 noiembrie”), parcurs pe patru opriri în care oprirea curentă poartă coletul (cu o animație discretă cât timp e pe drum), stări distincte pentru livrat, returnat, anulat, eșuat și necunoscut, buton de copiere a numărului AWB și link secundar către site-ul curierului. Arată la fel de bine pe telefon și în modul întunecat, respectă „reduced motion” și nu afișează date personale.
3.8.42026-10-29

Reparat

  • Actualizarea automată a pluginului funcționează. Verificarea versiunii noi ieșea când nu era niciun admin logat, iar actualizarea automată rulează din WP-Cron, fără utilizator. Așa, cron-ul nu vedea niciodată release-ul.
3.8.32026-10-29

Reparat

  • Checkout-ul clasic nu mai răspunde „Alege un locker sau un punct de ridicare” când lockerul e deja ales pe hartă. Validarea citește acum și alegerea din sesiune, iar dacă tema nu are câmpurile Fast AWB, `checkout.js` adaugă în formular câmpuri ascunse cu lockerul ales.
3.8.22026-10-29

Reparat

  • Pe checkout-ul clasic (ex. Flatsome), comenzile la locker se salvau ca livrare la adresă. Rata `fastawb:9:locker` conține id-ul instanței, iar verificarea compara `9:locker` cu `locker`. Acum se citește sfârșitul ratei.
  • Pe checkout-ul clasic, lockerul ales pe hartă se ia și din sesiune când tema nu trimite câmpurile Fast AWB. A doua trecere a checkout-ului își salvează acum datele, iar nota „Locker ales de client” apare în istoricul comenzii.
  • În admin, blocul „Locker ales” apare și la comenzile cu rată de locker salvate greșit ca adresă. Când punctul lipsește, blocul o spune.
3.8.12026-10-29

Reparat

  • Scripturile și stilurile Fast AWB se reîncarcă după fiecare update și pe site-urile cu LiteSpeed Cache. „Remove Query Strings" tăia `?ver=`, iar browserul păstra 7 zile `checkout.js` vechi. Din cauza asta nu apăreau cardul „Locker selectat” cu harta strânsă, lockerul nu se salva în comandă, iar cererea de număr la adresă încă apărea. Versiunea stă acum în URL (`fv`), cu excepția LiteSpeed `_litespeed_rm_qs=0`.
3.8.02026-10-29

Schimbat

  • Alegerea curierului la generarea AWB se face pe prețuri reale: se cere în paralel cota fiecărui curier eligibil (FAN, Sameday, DPD) pentru comanda respectivă și se folosește cel mai ieftin; scorul (retur, timp) doar departajează. Un curier a cărui cotă nu poate fi obținută rămâne pe tariful configurat, fără prețuri inventate. Alegerea manuală a magazinului nu este schimbată.
  • Răspunsul de generare arată, per curier, dacă costul este cotă reală (`cost_is_quoted`).
3.7.02026-10-29

Nou

  • AWB Sameday la easybox: comanda cu un easybox ales se generează acum la Sameday, cu id-ul punctului (`lockerLastMile`) și serviciul de locker, nu la adresă. Cerința: serviciul de locker ales în panoul curierilor. Câmpul vine din SDK-ul Sameday și nu a fost încă încercat pe un AWB real; dacă Sameday îl refuză, motivul lui apare la generare, iar comanda nu pleacă la alt curier. Un FANbox nu mai poate fi trimis prin Sameday, iar un locker fără id valid e refuzat cu motiv.
3.6.22026-10-29

Reparat

  • Lockerul ales nu se mai pierde la plasarea comenzii: hook-ul checkout-ului clasic putea rula și pe Blocks, cu un `$_POST` gol, și rescria comanda ca livrare la adresă fără locker. Din acest motiv AWB-ul pleca la adresă, prin alt curier. Modul de livrare se mai citește, ca rezervă, și din rata aleasă în sesiune sau din titlul ratei.
  • Comanda primește o notă cu lockerul ales (nume, rețea, adresă, ID), vizibilă în istoricul comenzii.

Schimbat

  • Livrarea la locker nu se mai oferă într-o localitate fără lockere. Dacă lista de puncte a județului nu e încă sincronizată, metoda rămâne disponibilă.
  • Răspunsul de generare AWB arată scorul fiecărui curier candidat (cost estimat, retur, timp), ca să se vadă de ce a fost ales unul.
3.6.12026-10-29

Schimbat

  • La județul București, localitatea se alege dintre „Sector 1" … „Sector 6", nu „București". Lockerele Sameday din „Sectorul N" apar la sectorul ales.
3.6.02026-10-29

Nou

  • Lockerul ales apare în comanda din admin (nume, rețea, adresă, ID), sub adresa de livrare; datele se completează din lista locală de puncte la plasarea comenzii.
  • Pagina publică de urmărire are un design nou: titlu pe stare, traseu animat cu colet care se mută la pasul curent, drum în mișcare pe drum, bifă animată la livrare; respectă tema întunecată și `prefers-reduced-motion`.

Schimbat

  • După alegerea unui locker în checkout, lista și harta se închid; apare „Locker selectat" și butonul „Alege alt locker".
  • Cu „Marchează ca finalizată la generare" activ, pagina comenzilor se reîncarcă după generarea AWB-ului.
  • Adresa nu mai cere număr: clienții de pe strada principală sau de la casă pot plasa comanda.

Reparat

  • O comandă cu locker nu mai poate fi livrată de alt curier decât cel al rețelei lockerului (FANbox → FAN Courier, easybox → Sameday). Înainte, la regenerare AWB-ul putea pleca la adresă prin alt curier; acum comanda e refuzată cu motiv dacă acel curier nu e configurat.
  • Rețeaua lockerului nu se mai presupune `fanbox` când lipsește: se citește din lista de puncte.
3.5.12026-10-29

Reparat

  • Butonul „Plasează comanda" rămânea dezactivat când câmpul de adresă suplimentară (`address_2`, „Additional address") era completat fără cifră. Numărul se cere acum doar pe adresa principală; a doua e opțională și, dacă e completată, poate înlocui numărul lipsă. Câmpurile ascunse nu mai sunt verificate.
3.5.02026-10-29

Nou

  • Tarifele de livrare (curier, locker, prag de livrare gratuită) apar și în WooCommerce → Livrare → zona → Fast AWB. Sunt aceleași valori ca în Fast AWB → Configurare: se citesc de acolo, iar salvarea din zonă le scrie înapoi și se aplică imediat în coș.

Schimbat

  • Pagina publică de urmărire stă acum la `fastawb.monoweb.ro/<cod>` (cod scurt de 10 caractere, fără token) și nu mai afișează date personale: arată doar expeditorul, starea (AWB generat, expediat, la curier, livrat), livrarea estimată, curierul, AWB-ul, tipul coletului și linkul oficial al curierului.
  • Linkul se creează în momentul generării AWB-ului și se salvează o singură dată în comandă (`_awb_tracking_link`); nu mai este nevoie de o a doua cerere către server.
  • Emailul și SMS-ul către clientul final poartă același link public. Setarea „Pagină de tracking proprie" nu mai influențează mesajele.
  • Linkurile publice se șterg automat de pe server la 60 de zile după creare. Linkurile `/urmarire/…` din mesajele vechi continuă să funcționeze.
3.4.12026-10-27

Schimbat

  • Textul de consimțământ de la checkout se editează acum în WordPress, la Fast AWB → Configurare → Text de consimțământ, și nu mai este salvat pe server. Gol înseamnă textul implicit; textul afișat rămâne salvat pe fiecare comandă împreună cu momentul plasării.
  • Pagina Notificări din portal are o structură nouă: rezumat pe canale, un singur canal deschis odată, mesaje cu previzualizare alături și jurnal cu stări colorate.
  • Loguri și audit (administrare) arată activitatea reală: AWB-uri generate sau șterse, accesările administratorului în panoul unui client și notificările eșuate. Scorurile de selecție și coloana comenzii WooCommerce au dispărut.
  • Editarea unui client în administrare listează FAN Courier, DPD și Sameday, cu starea fiecăruia, și le poate configura pe toate.

Reparat

  • Pagina de curieri din administrare dădea eroare (500) când existau AWB-uri fără comandă asociată; timpul mediu de procesare se calculează acum doar din AWB-urile care au comandă.
3.4.02026-10-27

Nou

  • Notificări către clientul final când se generează un AWB: email (SMTP-ul magazinului), WhatsApp (contul WhatsApp Business al magazinului) cu SMS ca rezervă (contul propriu la furnizor, primul SendSMS.ro). Se configurează din portal, la Notificări: canale, texte editabile cu valori implicite, mesaj de test și jurnal cu ultimele 100 de trimiteri.
  • Pagină de urmărire pe domeniul magazinului, `/urmarire/<cod>`, creată automat de plugin. Linkul din mesaj este unic și se folosește o singură dată; după prima deschidere adresa din bara browserului nu mai conține tokenul, deci nu poate fi trimisă mai departe. „Trimite-mi din nou linkul” retrimite mesajul (maximum 3 pe zi).
  • Text de consimțământ editabil, afișat simplu, sub butonul de plasare a comenzii (checkout clasic și Blocks); se salvează pe comandă împreună cu momentul plasării.
3.3.402026-10-26

Nou

  • Prag de livrare gratuită în Fast AWB → Configurare → Tarife de livrare: când valoarea produselor din coș (după reduceri) ajunge la prag, ambele opțiuni de livrare devin gratuite. `0` dezactivează pragul.
3.3.392026-10-26

Nou

  • Tarifele de livrare sunt configurabile în Fast AWB → Configurare → Tarife de livrare: un preț pentru „Livrare prin curier” și unul pentru „Livrare la locker” (până acum ambele erau 0). Se aplică imediat la checkout.
3.3.382026-10-23

Schimbat

  • Checkout-ul nu mai afișează codul poștal și numele companiei (clasic și Blocks).
  • Județul apare înaintea localității, inclusiv după schimbarea țării.

Reparat

  • Harta punctelor de ridicare nu mai sare de la îngust la lat la reîncărcare: cutia are lățime fixă în celula tabelului, iar harta își recalculează dimensiunea după creare.
3.3.372026-10-23

Reparat

  • Harta punctelor de ridicare citea mereu localitatea/județul din primul câmp din pagină (facturare), nu din cel modificat de client (livrare); acum se folosește câmpul editat ultima oară, sau cel al adresei de livrare când „Livrare la altă adresă” e bifată.
  • O schimbare de adresă anulează modul „Puncte în apropiere”, care ignora localitatea și ținea lista pe locația veche.
3.3.362026-10-23

Reparat

  • Harta și punctele de ridicare se reîncarcă după orice re-randare a secțiunii de livrare (`updated_checkout`) dacă județul sau localitatea s-au schimbat, nu doar la evenimentul `change` al câmpului.
3.3.352026-10-23

Reparat

  • Harta și lista de puncte de ridicare se actualizează acum și când clientul schimbă localitatea sau județul în checkout-ul clasic (câmpurile `billing_/shipping_city` și `state`).
3.3.342026-10-23

Reparat

  • Actualizările pluginului apar imediat în WordPress: manifestul de release nu mai este ținut în cache 10 minute, deci „Fast AWB este la zi” nu mai ascunde o versiune nouă publicată.
3.3.332026-10-23

Reparat

  • Harta și lista de puncte de ridicare (EasyBox/FanBox) nu mai dispar la o secundă după alegerea livrării la locker: checkout-ul clasic reîncarcă `#order_review`, iar cutia este acum refăcută automat.
3.3.322026-10-23

Schimbat

  • Butonul „Anulează AWB" din meta box-ul comenzii WP folosește acum AJAX (fără redirect de pagină); apare în editarea comenzii și în panoul FastAWB, nu și în coloana listei de comenzi.
3.3.312026-10-23

Nou

  • Butoane „Anulează AWB" în lista de comenzi WooCommerce (AJAX, fără reîncărcare)
  • Butoane „Anulează AWB" în panoul client FastAWB (`/panel/awbs`) cu confirmare
  • Suport anulare AWB pentru Sameday (`DELETE /api/awb/{awb}`) și DPD (`POST /shipment/cancel`)
3.3.302026-10-23

Reparat

  • Butonul „Generează AWB" din lista de comenzi nu declanșa AJAX pe instalările WooCommerce clasice (non-HPOS): scriptul admin nu se încărca pe pagina `edit.php?post_type=shop_order`.
  • Greutate automată 0 la FAN: dacă produsele nu au greutate setată în WooCommerce și modul este „automatic", serverul încearcă acum greutatea manuală configurată înainte să refuze cererea.

Schimbat

  • Live și KPI, roluri clare: Live = ce se întâmplă azi (comutator „Azi / Istoric" pe hartă; Istoric include colete importate din măsurătorile KPI). Pagina de KPI (admin și client) are acum aceeași hartă pe localități, colorată după timp de livrare, cost mediu, rată de retur sau AWB-uri, cu medii ponderate cu eșantionul.
  • Import istoric în fundal, cu progres live: butonul de import pornește lucrarea în fundal și pagina arată în timp real ziua citită și câte transporturi s-au importat, fără refresh. Fără limita de 75 s, deci importul nu se mai oprește la jumătate (ex. la 16 ale lunii).

Reparat

  • Harta live nu mai spune „nicio livrare în Cluj”. Citea doar tabelul `awbs`, nu și transporturile importate de la curier; acum folosește măsurătorile KPI (generate + importate), ca pagina de KPI.
  • Import istoric DPD și Sameday fără număr fix: formularul nu mai cere „Câte AWB-uri”; importul merge de la data aleasă până azi, ca la FAN (fără dată, fereastra implicită a curierului).
  • Import istoric FAN: borderoul și viramentele se citesc pe pagini de cel mult 100 de rânduri; cererea cu `perPage=500` era refuzată de FAN cu 422 și importul eșua cu „nu a putut fi finalizat”.
  • Import istoric FAN fără plafon și fără dată fixă: data aleasă e doar începutul; importul parcurge zi cu zi până azi (implicit ultimele 30 de zile), sare zilele goale și AWB-urile deja importate, și se oprește la ~75 s cu mesaj, ca să poată fi reluat.

Nou

  • Harta live pe localități: limitele a 3.181 de UAT (comune, orașe, municipii) și ale celor 6 sectoare din București, colorate după AWB-uri, în curs sau rată de retur, cu cifra scrisă în suprafață. Apar de la zoom județ; sub el rămîn județele. Limitele: ANCPI 2025, nomenclator INS SIRUTA, CC BY 4.0, regenerabile cu `server/build_uat_geojson.py`.
  • Dashboard live pentru admin (`/admin/live`) și client (`/panel/live`): cifre, grafice pe oră/zi, distribuție pe status, curieri și evenimente recente, reîmprospătate la 10 s din `live.json`. Clientul vede doar datele lui; adminul vede și starea joburilor și clienții activi.
  • Harta României în dashboardul live: județe colorate după AWB-uri, în curs sau rată de retur, zoom până la localități cu clustere, listă de județe ca alternativă la hartă. AWB-urile fără județ sau fără poziție sunt numărate sub hartă, nu ascunse. Leaflet din `ui/vendor`, fără CDN.

Reparat

  • Compatibilitate declarată cu HPOS și checkout Blocks. WooCommerce nu mai marchează pluginul drept incompatibil.
  • PHP 8.4: parametrul `array $body = null` din clientul API este acum `?array`, fără avertisment de depreciere.
3.3.292026-10-08

Reparat

  • Coloana AWB lipsea din lista de comenzi. Se înregistra doar pe hook-uri HPOS și la coada tabelului; acum merge și pe stocarea clasică și stă după coloana Total.
  • Cod poștal opțional. Scos `required` + validatorul de format pe clasic și flagul din locale pentru Blocks.
  • Județul înaintea localității. Prioritatea câmpului de județ coborâtă sub cea a localității, ca ordinea firească: întâi județul, apoi localitatea.
3.3.282026-10-08

Reparat

  • Metoda ajunge singură în zone. Butonul din Configurare atașează Fast AWB în zonele existente care n-o au (o zonă nouă dedesubt nu se atingea niciodată, prima zonă potrivită câștigă); la fel face automat verificarea zilnică când metoda lipsește de peste tot. Nimic nu se șterge/reordonează.
  • Picker + hartă și pe checkout clasic. Cutia se montează și după `ul#shipping_method`, iar alegerea se oglindește în câmpurile `shipping_fastawb_locker_*`, din care se salvează comanda clasică.
  • Citire robustă a câmpurilor la salvare. Se acceptă și cheile cu prefix (`shipping_fastawb_locker_id`); comanda clasică nu mai rămâne fără punct.
3.3.272026-10-08

Reparat

  • Localitate ca județul, pe checkout clasic. Inputul e transformat pe loc într-un `select` cu aceleași atribute și clase (morph, nu nod paralel), deci arată ca județul; opțiunile vin pe calea `fillSelect`.
  • Update vizibil mai repede. Cache-ul manifestului de release scăzut de la 30 la 10 minute; verificarea manuală din admin golește tot pe loc.
3.3.262026-10-08

Reparat

  • Localitate înjumătățită pe checkout clasic. Selectul-overlay se mai creează doar pe WooCommerce Blocks; pe teme clasice câmpul rămâne inputul temei cu `datalist` nativ, fără noduri paralele care rupeau layoutul.
3.3.252026-10-08

Reparat

  • Checkout blocat la încărcare. `MutationObserver`-ul din `address-fields.js` se redeclanșa pe propriile scrieri în DOM; acum e debounced, cu pauză pe durata montării, iar pagina Blocks hidratează normal și vin metodele de livrare.
  • Fără `fastawb_locker_store` gol la fiecare vizită. Prima încărcare nu mai trimite `locker_id` gol (ținea lock-ul de sesiune Woo cu headers trimise și fără body); raportarea pleacă doar la alegere reală sau la plasare.
  • Sesiune fără coș. Handlerul AJAX nu mai cheamă `wc_load_cart()` (calcul de livrare + apeluri spre server); scrie direct în `WC_Session_Handler` cu dedup și eliberează lock-ul înainte de răspuns.
  • Providerul merge odată cu punctul. Alegerea din hartă și `setExtensionData` trimit și `locker_provider`; un easybox Sameday nu mai ajunge la server ca FANbox.
3.3.242026-10-08

Reparat

  • Deduplicare raportări locker goale. Toggle-ul între metode trimitea aceeași valoare goală de mai multe ori simultan; acum pleacă o singură dată (re trimiterea de la plasare rămâne forțată).
3.3.232026-10-08

Nou

  • Jurnal de apeluri lente pe pagina de configurare: orice cerere către server de peste 4s (sau eșuată) rămâne înregistrată cu calea, durata și eroarea. Diagnostic la hang fără acces la server.
3.3.222026-10-08

Schimbat

  • Suport PHP 7.4+ (era 8.1). Singurele funcții moderne aveau polyfill într-un fișier nou (`compat.php`), iar unicul `?->` a fost rescris. Sincronizarea județelor pe loturi (0.35s la 2100 puncte) e tot aici.
3.3.212026-10-07

Reparat

  • Sincronizarea județelor aglomerate nu mai blochează checkout-ul. Scrierea era rând cu rând cu căutare liniară (O(n²) în PHP + mii de cereri MySQL la 2000+ puncte) — pe hosting cu puțini workeri PHP, un județ ca Bucureștiul îngheța tot. Acum e upsert pe loturi de 500.
3.3.202026-10-07

Reparat

  • Locația caută în județul de lângă tine, nu în cel din formular. Cu poziție proaspătă, județul se deduce din coordonate (centrul cel mai apropiat din catalog); statusul spune și județul când diferă. Înainte, un client din Cluj cu Giurgiu selectat vedea doar Giurgiu, la 297 km.
3.3.192026-10-07

Reparat

  • Județul rămâne din prima alegere. Scrierea în celălalt câmp s-a mutat după eveniment (nu mai încurcă React), iar o plasă de siguranță reaplică valoarea aleasă dacă o re-randare o readuce pe cea veche.
3.3.182026-10-07

Reparat

  • Fără județ predefinit greșit. Defaultul Woo (bază magazin sau geolocalizare, inclusiv cel venit după montare) se golește când nu există date de adresă și clientul nu l-a atins. Alegerile reale nu sunt atinse.
  • Placeholder „Alege județul întâi" pe localitate cât nu există județ.
3.3.172026-10-07

Reparat

  • A doua apăsare pe locație doar recentrează. Dacă modul e deja activ cu hartă desenată, nu se mai refetch-uiește și redesenează tot (de-aia făcea zoom-out/zoom-in), ci harta alunecă pe poziția ta.
3.3.162026-10-07

Schimbat

  • Statusul lockerelor într-un singur rând scurt: „150 puncte · cea mai apropiată la 850 m de tine." (sau doar „12 puncte." fără geolocalizare).
3.3.152026-10-07

Schimbat

  • Statusul arată „Cea mai apropiată la X de tine" când se știe distanța (după geolocalizare), în loc de formularea tehnică veche.
3.3.142026-10-07

Reparat

  • Harta se centrează pe tine, nu pe punct. Cu poziție decentă (±2 km) vezi punctul albastru „ești aici" la zoom 15; cu poziție grosieră rămâne centrarea pe cel mai apropiat. Geolocalizarea cere precizie mare și nu mai acceptă poziții vechi de peste un minut.
3.3.132026-10-07

Schimbat

  • Modul „lângă mine" face zoom mai strâns (15) și își arată starea. Butonul devine „Lockere în apropierea ta" cu stil de opțiune activă, iar harta încadrează poziția ta alături de puncte.
3.3.122026-10-07

Reparat

  • Dropdownul de localitate moștenește geometria temei (padding, font, borduri, colțuri — copiate calculate de pe cel de județ), nu doar clasele. Fără asta, regulile legate de container nu se aplicau și eticheta se suprapunea. Cu fallback neutru când nu există model.
3.3.112026-10-07

Reparat

  • Dropdownul de localitate arată ca cel de județ. Selectul nostru copiază clasele temei de pe cel de județ, deci eticheta flotantă nu se mai suprapune peste valoare. Stilurile proprii au rămas doar la lățime.
3.3.102026-10-07

Schimbat

  • Harta include și poziția ta la „Puncte în apropiere". Punctul albastru „ești aici" intră în încadrare alături de lockere, deci vezi unde ești față de ele, nu doar harta județului.
3.3.92026-10-07

Schimbat

  • Tooltipurile de validare apar doar după ce clientul atinge câmpul. La încărcare, butonul se blochează silențios, fără bannere.
  • Localitatea e `select` nativ pe mobil (în loc de input + datalist), sincronizat cu câmpul Woo. Județul și localitatea pornesc goale (fără defaultul de magazin) când nu există date de adresă.
  • „Puncte în apropiere" bate localitatea aleasă: cu poziție proaspătă (< 10 min) se cer punctele pe tot județul și se arată cele mai apropiate 150, nu doar cele din localitate.
3.3.82026-10-07

Schimbat

  • Atribuirea Leaflet/OSM e discretă, dar rămâne vizibilă (cerință de licență ODbL). Font mic, fundal translucid.
3.3.72026-10-07

Schimbat

  • Telefonul e obligatoriu la checkout (placeholderul temei rămâne „opțional", dar butonul de plasare stă dezactivat fără număr valid). Câmpurile invizibile nu blochează comanda.
3.3.62026-10-07

Reparat

  • Validarea acceptă orice cifră din adresă („Horea A.15"). Tokenurile cu punct sunt tolerate, iar ultima plasă e prima cifră de oriunde. Fără cifră deloc, eroarea rămâne. Aceeași regulă în validator și în parserul PHP.
3.3.52026-10-07

Reparat

  • Numerele de stradă cu literă inițială sunt acceptate („Horea A15"). Regula cere în continuare obligatoriu o cifră în token, ca textele fără cifre să fie în continuare refuzate. Validatorul din checkout și parserul PHP folosesc aceeași regulă.
3.3.42026-10-07

Reparat

  • **Numărul, blocul, scara, etajul și apartamentul se extrag din adresa dintr-un singur câmp.** Clienții scriu „horea nr 55 bloc a19" în address_1, cu address_2 gol, iar componentele nu ajungeau în AWB. Valorile explicite (meta sau address_2) au prioritate.
3.3.32026-10-07

Reparat

  • Eroarea de adresă nu mai apare sub metoda de livrare. Radio-butoanele de livrare conțin `address` în id și erau detectate drept câmpuri de adresă. Mesajul apare acum ca bulă roșie deasupra câmpului de adresă, iar butonul de plasare se dezactivează cât adresa n-are număr sau telefonul e invalid.
3.3.22026-10-06

Reparat

  • Checkout fără erori în consolă. Numele de hook cu slash-uri e respins de `wp.hooks`, iar înregistrarea lui umplea consola cu erori. Validarea rămâne pe `setCustomValidity`, care oprește nativ trimiterea formularului.
3.3.12026-10-06

Reparat

  • Harta lockerelor nu se inițializa pe checkout. `wp.hooks.add()` nu există (API-ul e `addAction`/`addFilter`); apelul arunca `TypeError` și oprea toată inițializarea checkout-ului. Acum e păzit cu `typeof` și folosește `addAction`.
3.3.02026-10-06

Schimbat

  • Rebrand total Expedio → Fast AWB. Nume plugin, fișier principal (`fastawb.php`), slug (`fastawb`), domeniu de traduceri, antete API (`X-Fast-AWB-*`, serverul acceptă și vechile `X-Expedio-*`), metode de livrare, opțiuni, tabele și clase CSS/JS. La activare, pluginul dezactivează automat vechiul `expedio/expedio.php` și migrează datele (opțiuni, tabela de lockere, meta comenzi, zone de livrare) fără pierderi.

Reparat

  • Importul de istoric merge până la 500 pe rulare, nu 50. Plafonul era conservator pentru începuturi; la 50, un cont cu mii de expedieri cerea zeci de rulări manuale. Limita se aplică pe tranzacție, nu pe curier, iar raportul spune în continuare câte au rămas.
  • Tariful Sameday se recitește per expediție existentă. Documentul oficial v3.5 ascundea `GET /api/client/awb/{clientInternalReference}` ("returns the same response as AWB creation"); probat live cu o referință trimisă de server (`wc_order_id`): 200 cu `awbCost 22.27`, aceeași cifră ca estimarea și ca `cost_quoted`. E tariful curent, nu baza de 12.0 de la creare — dar merge doar pentru expedierile create prin Fast AWB; cele din panoul lor n-au referință și rămân fără cost. Intrarea `pricing` din registru și `docs/SAMEDAY_API_COMPLET.md` (§17 nou) poartă dovada.
  • Onboarding and courier setup no longer dead-end a new client. The license checklist step linked to `/admin/clients`, where a client gets `403`; it now says only the administrator can issue a license. Saving Sameday with wrong credentials returned the 500 page; it is a form error with the reason. `Client ID` fields explain which value each courier wants, the authentication test button is visible (disabled) before the first save, and the FAN history form states the daily-borderou meaning of its date and the 500-per-run limit.
  • The delivery-KPI map states empty instead of drawing empty. With shipments but no mappable counties the panel drew blank outlines on a degenerate scale; it shows the `not_attributed` reason. The empty-state marker follows its kind, a tap opens the same county card as hover (tap was dead), the card carries each average's sample size, the frame is taller with border counties kept inside it, and Reports links to the map while the map links back to the export.
  • The KPI map no longer downloads the 1.5MB locality catalog. Every page view fetched and parsed 12,701 positions before drawing anything, although the server had already joined the coordinates into the locality rows the page carries. Points are built from the rows; the county outlines remain the only geometry download.
  • Imported and single-call AWBs now reach the KPI snapshots. Collection joined AWBs to orders and read the destination from the order, so an AWB without an order row was invisible although it carries its own destination; and nothing scheduled a refresh after an import or a generation (startup only covered order-linked AWBs). Collection covers both row shapes with a tenant-safe predicate, and imports and generations request one deduped refresh job.
3.2.402026-10-05

Nou

  • **Sameday account history is now measurable, from the courier's own event feed.** `GET /api/client/status-sync` is swept across real time-frames, read-only (GET only, never a write), and the account's shipments are reconstructed from the events: delivery time in working days, delivery and return rate, failed attempts and final state. The measurements join the existing KPI shape — same rollup, same `county` plus folded-locality key, same thin-sample marking — so Sameday rows now sit next to FAN and DPD instead of being absent. On the real account the sweep covered **3413 windows from 2026-01-01 with zero refusals, returning 3038 events and 309 distinct AWBs** (225 delivered, 7 returned, 33 canceled), and produced `delivery_days` = 1.37 working days over a sample of 126 shipments. `delivery_days` is courier time only — pickup (`statusId 4`) to delivery (`statusId 9`), non-delivery days excluded — matching how FAN and DPD are measured. Shipments without a pickup event contribute no time figure rather than a substituted AWB timestamp, so the sample is stated, not padded.
  • Courier fields we do not interpret are now stored unrenamed in the raw snapshot: `lockerDetails`, `oohDetails`, `tobaccoCheck`, `damageCheck` and anything Sameday adds later. A courier that ships an attribute we did not anticipate does not lose it.
  • New `history_events` capability in the fail-closed registry, backed by the contract of record: `GET /api/client/status-sync`, with the window limit, the `statusId` catalog and the attribution rule written out.
  • False-positive numeric assertions replaced with scoped ones. Eleven checks searched a bare figure as a substring of a whole response, and either rejected correct pages — `22.27` is a latitude in the county outlines, and `409` occurs in them 35 times — or could not fail at all. Each now verifies the behaviour it was written to guard: a figure read from the visible text with a digit boundary instead of a substring, a client's figure read from its own table row, the CSV export parsed and asserted per row, and the public tracking page checked for a price in the text a reader actually sees. A new AST audit keeps the pattern from returning, keyed on each assertion's content rather than its line number, requiring a written reason for every remaining case, and a regression fixture proving that the real county geometry may contain both figures without failing while a hardcoded total, a leaked figure or a hardcoded coordinate still does.### Fixed
  • **Every UI asset now carries a cache-busting version tied to its content, and the checks that would catch a stale one exist.** A corrected `kpi-map.js` kept shipping to browsers that had already cached it: the server answered correctly, every server-side check was green, and the hover card said "we don't deliver here" for a county with 85 shipments — because a browser cache is not visible to a server. Assets under `app/static/ui/` go out through `ui_asset()` as `?v=<sha1 of the bytes>`, memoised on the stat data so an unchanged file is hashed once per process. - The token is the content's digest, not `path + mtime + size`. The latter described the file rather than its content, so it held the URL still when a deployment restored timestamps, when a checkout landed an older blob over a newer one, or when a build rewrote a file in place — bytes changed, URL did not, and the browser kept the copy it already had. - `kpi-map.js` no longer falls back to a hardcoded `?v=0` for its geometry. A constant is a URL that never moves, which is the bug wearing a fix's clothes. It now derives the token from its own `src`, which `ui_asset()` already versions, so script and geometry always come from one revision. - Leaflet's vendored stylesheet referenced `images/layers.png`, `images/layers-2x.png` and `images/marker-icon.png`, **none of which existed**: a layers control or a classic marker icon asked for a file that was never there. They are vendored now from the same 1.9.4 release as the CSS and JS beside them. This one needed measuring rather than reasoning — a `?v=` on a stylesheet does not reach its own `url()` images (RFC 3986 relative resolution drops the base query), so a parent stylesheet's version cannot cover them and a missing one stays missing. - Six new checks, each proven red against a constant version and green against the real one: no rendered page may link an unversioned `/static/` URL; every `url()` in a shipped stylesheet must resolve to a file we serve; changing an asset's bytes must move the URL and serve the new bytes, measured end to end against a real app; the token must be the content's hash and not a hash of `mtime + size`; the server must answer a versioned URL with the same bytes and `404` a missing one rather than `200` empty; and a negative control that invokes two of those checks with versioning broken and requires them to fail.
  • The Sameday parcel suffix is the parcel position, not a fixed `001`. All 3045 observed `parcelAwbNumber` values are 18 characters ending in three digits, but 2 of 311 end in `002`. Trimming only a literal `001` returned an AWB number that does not exist for a second-parcel shipment and would have counted such a shipment twice in the KPI. Attribution now trims the three position digits.
  • The Sameday status-sync window limit is 7200 seconds inclusive — measured live: 7199 → 200, 7200 → 200, 7201 → 400 — not "smaller than 7200" as the courier's own message reads. The adapter refused `>= 7200`, which was safe but wrong; it now refuses only `> 7200`. A **reversed or zero-length window is refused locally**, because Sameday answers `200` with an empty list for it, exactly as it does for a genuinely empty window, so a sign error would otherwise pass as "no events".
  • A measured KPI could be counted but never aggregated: Sameday history measurements were returned in the counter and dropped from the list, so the page would have shown 7 shipments while the counter said 245. Both now come from the same list, and a regression test fixes the agreement.

Schimbat

  • `docs/SAMEDAY_API_COMPLET.md` §11 was rewritten from a sweep of the real account and corrects five documented behaviours that were wrong: the window limit is inclusive; the refusal message lives in `errors.children.endTimestamp.errors[]`, not in the root `message` (`"Validation Failed"`); the ISO-timestamp rejection says `"Format timestamp unix invalid"`, not the message previously quoted; `lockerDetails` and `oohDetails` are objects, not arrays; and the Sameday status is a numeric `statusId` catalog of 31 codes, not a free label with no code. The record also states that `statusId 15` and `111` are both cancellations, that `statusState` says "successfully delivered" on the four COD codes and so must not be used to detect delivery, and that `expeditionStatus.county` is the origin, not the destination.
  • Sameday `tracking` and `generate_label` still refuse, but no longer with a reason that was factually wrong. Both paths exist and were verified (`200`); the refusal is about the response shape and about not creating shipments, not about a missing endpoint.
  • The account-wide sweep is attributed to one client, by the credential's own account, so imported Sameday history cannot leak between shops. Shipments created outside the server are imported too — they belong to the same client — and are marked with `is_own_awb` so provenance stays visible, because excluding them would report 7 shipments for an account that shipped 309.
  • The delivery-KPI page now leads with cost and return rate — the two figures the owner named as the objective — instead of six equal headline numbers, and each of them carries a reference point earned from that shop's own couriers: the cheapest and the dearest measured courier, or the reason no comparison is possible. A courier with no tariff in its import documents is `absent`, never `0`, so it cannot become "the cheapest courier"; a courier under the sample threshold is never a benchmark. There is deliberately **no previous-period comparison**: `delivery_kpi_snapshots` is a keyed table that the refresh job overwrites, so there is no history and any delta would be invented.
  • Empty, thin and absent are now told apart and each says which it is: "nothing measured yet", "measured but the tariff is not in the import documents", and "below the sample threshold". Status carries colour and a word and a symbol, never colour alone.
  • The courier, county and locality rows link to the filtered shipment list. For the links to work, the existing AWB search now also matches the courier code, the county and the destination locality, folding Romanian diacritics in SQL with the same rule `fold_text()` uses in Python — otherwise `Constanta` and `Constanța` would both return nothing, with no error. The admin overview filters the same way.
  • Light theme for the client panel and the admin panel, with a dark/light toggle in the header. The theme follows the operating system by default and an explicit choice persists across pages and sessions in `localStorage`, applied by a small synchronous script in `<head>` so the first paint is already correct instead of flashing the wrong theme on every page load.
  • A county map on the delivery-KPI page: the 42 county outlines are drawn from `app/static/ui/romania-counties.geojson`, keyed by our own official county codes, coloured by the chosen metric, with a hover card showing that county's figures and its sample size. The county table stays underneath, so the same numbers can be read without a mouse.
  • The delivery-KPI map now zooms to locality level: every measured locality with a known position becomes a dot, sized by its shipment count and coloured by the selected metric, with its own figures on hover and a per-locality table beside the map. Positions come from DPD's own locality nomenclature (`POST /location/site/csv/642`, verified live 2026-10-03, 13 751 sites), so nothing on the map is a geometry we invented. A locality with no catalogue entry stays undrawn and is counted as such on the page, because absent is not zero and `0,0` is in the Atlantic. Overlapping dots cluster by screen distance and carry a count, deliberately uncoloured: a cluster is a number of points in an area, not a performance, and colouring it would imply one courier covers the zone. The county map is untouched at low zoom, and no request is made per pin.

Reparat

  • The county and locality tables under the KPI map rendered every figure left-aligned and proportional, so the columns did not line up and the values shifted sideways on refresh. They now carry the same `num` classes as the other KPI tables, and the sample column says its state as a word.
  • The KPI map threw on the first theme change: the hover layer overwrote `layer.feature`, which Leaflet reads back on every redraw, so the map lost its county colours after the operator switched theme. Nothing announced it — the page was fine until it was touched.
  • `/admin/kpis` answered 500 whenever a client had measured KPIs, because the per-client table linked to `admin.client_edit`, which is not a route (the endpoint is `admin.edit_client`).
  • The locality layer declared no `maxZoom`, and `markerCluster` refuses to build without one — so no locality dots appeared at all.

Schimbat

  • The UI is one token set instead of dark-only literals. The 34 KB inline `<style>` in `base.html` moved to `app/static/ui/theme.css` and `app.css`: every component resolves its colours from variables, and both themes define the same variables. A colour written inside a component is invisible in the other theme, which is what makes a "dark mode" pass look unfinished — there are now none.
  • Text contrast was measured in a real browser across all 16 panel and admin pages in both themes. Four tokens were failing WCAG AA and are corrected: the primary button had no explicit text colour and inherited near-black from the body (~3:1), and `--muted`, `--faint` and the light `--accent`/`--green` were readable on white but not on the grey chips they actually sit on.

Nou

  • Statistici reale de livrare per localitate, pentru FAN, calculate din evenimentele curierului: durata medie de livrare și rata de retur, grupate pe curier + județ + localitate. Sunt primul KPI măsurabil pe evenimente reale, deoarece FAN e singurul curier care dă atât borderoul, cât și istoricul evenimentelor. Verificat live pe cont: 322 de transporturi în 32 de zile au produs 196 de localități. - Destinația nu vine din tracking. `events[].location` e agenția care a scanat: AWB-ul `7000163295887` are evenimente în „Dr.Tr.Severin", dar destinatarul e în Cujmir, Mehedinți. Localitatea se citește din borderou (`recipient.address`), iar cele două surse se unesc prin AWB. - Codurile de eveniment se citesc din catalogul curierului, nu se ghicesc din text. Doar `S2` e „Livrat"; `S1` e „Expeditie în livrare" și `S3` e „Avizat", deci niciunul nu e o livrare. `S46` „Predat punct livrare" e predare la punct de ridicare, nu livrare — pe AWB-ul întors chiar apar două, înainte de retur, și numărate ca livrări ar fi arătat o rată de retur mai mică decât e. Dacă un cod folosit lipsește din catalog, calculul se oprește în loc să numere altceva. - Calendarul de livrare nu e documentat și nici interogabil: cinci endpointuri candidate răspund toate 404, iar descrierea serviciului Standard („max. 3 zile lucrătoare") nu enumeră zilele. Durata se măsoară așadar în „ore de livrare", cu zilele fără livrare scăzute, folosind o ipoteză (luni–sâmbătă) marcată `verified: false` pe fiecare rând și înregistrată ca lacună deschisă. **Cât timp calendarul nu e cunoscut, durata e o aproximație, nu o măsurătoare a curierului.** - Agregatul stă într-un tabel cu cheie `(curier, județ, localitate)` reîmprospătat de un job, nu se calculează la randarea unei pagini — aceeași greșeală pe care o documentează AGENTS.md pentru cache-ul de obiect. Potrivirea localităților folosește `fold_text()`, nu `lower()`: FAN trimite „Horezu" și „Cujmir", iar catalogul le are cu diacritice.
  • Nomenclatorul de localități al curierilor se descarcă într-un tabel, într-un job săptămânal, și nu mai stă pe calea caldă a generării unui AWB. Sincronizarea eșuată păstrează lista veche, iar căutarea are o rezervă pe rețea când tabela nu conține localitatea.

Nou

  • Browser-level UI checks: `app/services/browser_check.py` drives a real headless Chrome over CDP, and the KPI map and charts are verified by what they draw — rendered height, SVG shape counts, and console errors — on both the client and the admin pages. These catch the failures a markup or JSON check cannot: a container present in the page with zero height on screen, or a script that throws before it draws anything.
  • Cache-busting versions on every UI asset. `ui_asset()` in templates derives `?v=` from the file itself, so a fixed script reaches browsers holding the cached copy.
  • Audit check for assertions that search a rendered page for a bare number, which collide with the county geometry that page may carry.

Reparat

  • The KPI map is no longer addressed by `id` in one place and by class in another; both panels carry the class the stylesheet sizes.
  • Geometry is fetched by the map script rather than inlined into the admin page: 160 KB of coordinates per render that nothing read, and which made a figure search unanswerable.

Schimbat

  • Orders are no longer imported into the server. The WordPress plugin used to push every order on every WooCommerce save (autosave, status change, address correction) and the server kept a row per order: no AWB, no tracking, no cost, just rows and personal data kept for nothing. The order now travels **inside the AWB request** and disappears: the AWB itself carries the recipient, phone, address, county, weight, parcel dimensions, declared value and cash on delivery. A shop sees an order in the panel only once it has an AWB, which is the only moment the server needs it.
  • The AWB list is now one table. Previously the imported documents sat in a separate reconstructed table below the main one, so an operator looking for a number had to know *where* to look — and if they did not, the conclusion was that the document did not exist. Generated AWBs, imported FAN/DPD documents and Sameday shipments reconstructed from status events now appear together, each row stating its provenance, because an imported document cannot be reprinted or deleted from here while a generated one can. Cost that the courier does not publish stays absent, not `0` — Sameday has no historical tariff at all, since `awbCost` is a base price without the COD fee or the fuel index (docs/SAMEDAY_API_COMPLET.md §9).
  • The Orders tab is gone from both panels, along with `/panel/orders` and `/admin/orders`. Both now answer `410 Gone` with a page that says where the documents are, rather than `404`: the link was in the sidebar and may be in a bookmark, and a `404` would read as a broken panel.
  • Lista de comenzi arăta statusul de tracking cu o cerere pentru fiecare AWB afișat, deci o pagină cu 23 de AWB trimitea 23 de cereri la încărcare și butonul de generare intra în aceeași coadă: fiecare cerere era amânată cu ~49 s pe Local, unde WordPress are două procese php-cgi. Niciuna nu era lentă, toate așteptau. Statusul se cere acum la click, într-o singură cerere, prin endpointul de lot existent; panoul server arată aceleași date fără nicio cerere în WordPress.
  • Generarea unui AWB Sameday lua peste jumătate de minut: localitatea se căuta în toate cele 13.777 de localități ale curierului, în 69 de pagini, la prima cerere a fiecărui proces. Filtrele documentate ale contractului erau suficiente — 3 pagini pe județ — dar o notă veche din cod le considera inutilizabile. Căutarea durează acum sub o secundă.

Reparat

  • The server starts its own worker. It was started by hand in a second window, so every fresh server ran without tracking: statuses stopped updating, KPI pages stayed on their first run, and nothing on any page said so — the numbers simply stood still. The worker is now a child process of the server, stopped with it. A file lock (`msvcrt.locking` on Windows, `fcntl.flock` on POSIX) means starting the worker separately cannot produce a second one, and the lock is released by the operating system when the process dies, so a `SIGKILL` cannot leave the next start without tracking. The supervisor waits 1.5 s and reports if the child exited immediately, because a `Popen` that returns does not mean a worker that survived.
3.2.392026-10-02

Reparat

  • Prețul unui AWB Sameday era inventat: serverul calcula `12.00 + 2.50 pe kg` și afișa 14.50 RON pentru orice comandă, deși Sameday are un endpoint de tarif (`POST /api/awb/estimate-cost`, găsit în SDK-ul lor oficial și verificat live). Valoarea `awbCost` din răspunsul la creare e doar preț de bază — 12.0 pentru o expediere care costă 22.27 cu taxa COD și combustibil — și nu mai poate suprascrie estimarea. Prețul real e cerut înainte de generare, iar cele 7 AWB-uri Sameday existente au fost reprecizate.
3.2.382026-10-02

Reparat

  • Lista de comenzi citea statusul de tracking cu o cerere pentru fiecare AWB afișat, deci o pagină cu 23 de AWB trimitea 23 de cereri la încărcare și butonul de generare intra în aceeași coadă: fiecare cerere era amânată cu ~49 s pe Local, unde WordPress are două procese php-cgi. Niciuna nu era lentă, toate așteptau. Toate statusurile se citesc acum într-o singură cerere, prin endpointul de lot existent.
3.2.372026-10-02

Reparat

  • Generarea AWB-ului se oprea la 15 secunde, deși expedierea mergea la curier înainte de răspuns: Sameday își descarcă la prima cerere a zilei nomenclatorul de localități, în zeci de pagini, și AWB-ul lua peste jumătate de minut. Pluginul renunța mai devreme și raporta „Operation timed out" pentru un AWB care exista deja la curier. Generarea are acum timeout propriu, mai lung; cererile obișnuite rămân scurte.

Reparat

  • Alegerea manuală a curierului arunca o eroare fatală: `update_client_settings` cerea `array $weights`, iar formularul trimite `null` în modul manual, unde ponderile nu se folosesc. Parametrii sunt acum opționali, ca fiecare grup de setări să poată fi salvat singur.

Reparat

  • Salvarea priorităților prin AJAX nu s-a făcut niciodată: cererea nu trimitea `action`, deci WordPress nu știa ce handler să ruleze și răspundea cu corpul `0`. Mesajul arăta totuși „Salvat pe server.", pentru că blocul `finally` rescria succesul peste orice eroare. O salvare care nu ajungea la server arăta exact ca una reușită.

Reparat

  • Serviciul Sameday ales din cont nu ajungea la comanda: codul citea două coloane, una pentru adresă și una pentru locker, care nu au existat în baza de date. Salvarea din panoul curierilor reușea, și totuși AWB-ul era refuzat cu „serviciul nu este ales încă". Coloanele au fost adăugate, cu valoarea veche migrată în serviciul de adresă.
3.2.342026-10-02

Reparat

  • `admin.js` se încărca doar pe ecranele hartă: fiind declarat dependent de Leaflet, care nu e înregistrat în Configurare, WordPress îl scotea din coadă fără nicio eroare. Salvarea automată a priorităților nu rula deloc pe pagina de Configurare.
3.2.332026-10-02

Nou

  • Selecție curier, în Configurare lângă priorități: modul *automat* după scorul existent, sau *manual* cu un curier ales din cei care au credential și sunt disponibili. Curierul ales e primul încercat, nu exclusiv — dacă nu poate genera AWB-ul, comanda trece la următorul, ca expedierea să nu se piardă. Dropdownul nu promite curieri fără credential sau dezactivați.
3.2.322026-10-02

Nou

  • The locker map draws a filled pin per network, in our own shape with a drop shadow, carrying the courier's official logo: the Sameday emblem in red, the FAN Courier emblem in blue. The logos are our own prepared files in `assets/pins/`, with the white background made transparent by removing only the border-connected white — the Sameday emblem has white faces in its centre that are part of the symbol, and a colour-threshold background removal deleted them and left a red ring. The FAN logo's lettering is cut, because at 25 px it is unreadable and it covered the emblem. The map clusters markers per area with a neutral colour and the point count, and separates them when the customer zooms in, because 1508 easyboxes in Bucharest overlap completely at city zoom.
  • Sameday pickup points are offered at checkout next to FANbox, in one picker: the customer chooses a pickup point, not a courier, so the two networks are served from a single `GET /api/client/lockers` response and each point keeps its own network in the order metadata, because the same id may belong to a FANbox or to an easybox and the server needs the network to pick the courier. The shipping method label no longer names FANbox, and an easybox gets a red map marker so the customer can see which network the chosen point belongs to.
  • Sameday pickup points are saved in WordPress in a dedicated table, per county and per network, so checkout does not depend on a server round trip on every county change. The local copy is used only when it covers every network requested: a county synced for FANbox alone used to be served as a complete answer, so the Sameday points were never shown and nothing explained why.
  • Sameday easybox points are now read paginated, so all 7302 of them on the account are stored across all 42 counties. The list was read as a single page of 500 and stopped there, which returned a number exactly equal to the page limit: a truncated list with no error. Cluj-Napoca showed 20 easybox points instead of 312, and Bucharest was cut off at the page edge. The list is also cached on the class with a TTL, so a locker request does not download 15 pages from the courier every time.
  • `locality_matches()` resolves the courier's nomenclatura instead of comparing text: Sameday names the Bucharest localities „Sectorul 1" … „Sectorul 6" while WooCommerce sends „București", so the 1508 easybox points in the capital matched nothing and stayed invisible. The exception is named and bounded — „București" does not absorb Ilfov, Sectorul 7, or any other town.
  • Sameday is configured from the client couriers panel, exactly like FAN Courier and DPD, and can be rewritten at any time: the username and the password are saved per client and an empty password keeps the stored one. Until now Sameday was read only from the server environment, so it could not be configured per shop, could not be changed after the fact, and was excluded from courier selection because it had no per-client credential.
  • The "Shop ID" field is gone from the Sameday courier form: `shopId` does not exist in the official Sameday contract (it appears zero times in the 20-page PDF), so the field could only ever hold a fabricated identifier. The pickup point and the service are now chosen from dropdowns filled live from the courier account (`/api/client/pickup-points` and `/api/client/services`) and validated against those lists before being saved. AWB creation stays refused until a real shipment has been confirmed on the account.
  • Added `docs/SAMEDAY_API_COMPLET.md`, the verified Sameday contract inventory. Three documented endpoints differ from the running API and are recorded as such: `/api/authentication` answers `404` (the working one is `/api/authenticate`), `/api/geolocation/counties` answers `404` (the singular `county` returns the 42 counties), and contact persons are not a separate endpoint but arrive inside each pickup point.
  • The AWB detail drawer now shows every field the courier returned: the cost source (courier tariff or server estimate) and the complete flattened provider response, rendered with escaped text like the reconstructed history table. Costs estimated by the server carry an `est.` badge in the AWB table.
  • Inline SVG navigation icons in the client panel and the global admin, replacing the unicode glyphs. They inherit the link color, so the active violet state keeps working with no font dependency.

Reparat

  • The map was left empty on the first draw, and only filled in after the customer switched the delivery method and back. The map was created after the wait for the logo files, so the first draw returned with the map still null, and the redraw the promise scheduled found a null map and drew nothing. No error was raised, which is why it looked like a styling problem. Creating the map before the async wait fixes it, and the order of those two lines was the whole bug.
  • The WordPress locker cache returned points with no network, wrote every point as `fanbox`, and deleted only the FANbox rows on refresh. All three made an easybox indistinguishable from a FANbox point, so the server received a `fanbox` network with a Sameday `lockerId` and the AWB would have been refused by the courier. The store now reads and writes the network per point, and the refresh is limited to the networks present in the incoming list, so a courier error on one network no longer erases the other's points.
  • The locker cache was served before contacting the server and without checking which networks it covered. A county synced for FANbox alone was answered as if it were complete, so the Sameday points were never shown and the customer saw a list that looked final. The cache is now used only when it covers every requested network.
  • An easybox with `lockerId` 0 was dropped from the list: the identifier was read with `or`, which is correct for text but turns a numeric zero into a missing value. A point without an id can never become a valid AWB, so it was silently lost from the picker.
  • Filter and form dropdowns no longer render white text on a white popup. The options now have an explicit dark background with `color-scheme: dark`.

Reparat

  • The plugin update no longer fails with `Download failed. Forbidden`. WordPress keeps the update list in a transient for hours and only then downloads, while the signed release link expired after 15 minutes, so the operator saw a valid new version and got a 403 when pressing "Update now". The download now also authenticates with the shop's license, and the plugin sends that key with the release request. A package still needs a valid link or an active license: no license, a suspended license, or a forged token all answer `403`.
3.2.242026-10-01

Nou

  • A DPD adapter, built from `docs/DPD_API_COMPLET.md` and from real responses on the account: services, localities, streets, pricing and tracking. The locality and the street are translated to DPD's own numeric ids, because a Romanian address cannot be sent as free text.
  • The DPD courier, its credentials and the client preference, so it can be ranked alongside FanCourier for a real order.
  • A courier credential form in `Curieri disponibili` for DPD, next to the existing FanCourier one: username, password, and the client id of the dispatch site. The panel route accepted only `fancourier`, so a client could not reach DPD from the interface at all and DPD existed only through a script.
  • A `Testează autentificarea` button for DPD, which calls the real API and never prints the password.

Reparat

  • A courier can now be configured without an administrator having created its global row first. Both handlers refused the request with "global definition unavailable", so a client who had credentials could not generate an AWB until someone intervened in the database.
  • Two couriers without a saved row no longer share one score in the panel. The score dictionary was keyed by id, and the rows added by the page have no id yet, so the last one overwrote the others and the first card showed a score that belonged to a different courier.

Verified

  • Creating a DPD AWB is still refused: `/shipment` answers `Wrong sequential numbers` for every payload, while `/calculate` accepts the same one and prices it. The contract says the shipment id comes from a provided range, and the supplied account has no numbers allocated for dispatch.
  • Romania has no `state` objects at DPD: `/location/state` for country 642 returns an empty list, so the region lives on the site. A Romanian locality is a DPD site: Cluj-Napoca is `642154984`.
  • Five payload shapes were rejected by DPD, each of which would have passed a test that guessed the schema: `recipient.name` is not a field (`clientName` is), `recipient.phone` is not a field (`phone1` is), `clientName` and `privatePerson` are mandatory without a `clientId`, the address is `addressLocation` for pricing and `address` for creation, and the service is `serviceIds` as an array for pricing but `serviceId` as a scalar for creation.
  • DPD answers `200` even when it refuses a request, so the status code proves nothing and the `error` object carries the reason.
  • Printing is refused rather than answered with a URL built from a tracking page, which is not a label.
3.2.232026-10-01

Schimbat

  • Deleting a locality is now a button instead of a checkbox. The column had two marks per row, one to keep and one to remove, and the rule was that removal won; the effect of the second mark was invisible until the list was saved. The button marks the row and can be pressed again to undo it.

Reparat

  • Pressing delete on one locality saved an empty list. The marker input was hidden with `hidden`, and a hidden input is still sent in `FormData`, so every row arrived in `remove[]` while `keep[]` held one fewer. The marker uses `disabled` now, which `FormData` does omit.
  • Saving with an unloaded panel no longer empties the list. Without `keep[]`, every row used to fall and a list of thousands of localities was replaced by an empty one, with no error; the save is refused with a reason instead.
  • Saving a county answered "Județul nu este în catalog" for all 42 counties. `sanitize_key()` lowers the code (`B` becomes `b`) while the catalogue keeps it in capitals, so a strict comparison never matched.
3.2.182026-10-01

Reparat

  • Every county on `Fast AWB -> Localities` answered "Județ invalid" and the panel stayed on "Lista nu a putut fi încărcată". `sanitize_key()` lowers the county code (`CJ` becomes `cj`) while the catalogue keeps it in capitals, so a strict comparison never matched.
3.2.172026-10-01

Reparat

  • Opening a county on `Fast AWB -> Localities` returned a 500 instead of its list. The handler validated the county code with a method that was called but never defined, so PHP threw "Call to undefined method" on every county panel.
3.2.162026-10-01

Nou

  • The complete locality catalogue is downloaded once and kept in WordPress, county by county, with a button on `Fast AWB -> Localities` that writes all 42 counties in a single request. The checkout then reads the local copy and makes no network call per county change.
  • A county list that is already saved is left alone by an import, because it may hold corrections made by the shop owner. Passing `rescrie si listele deja salvate` overwrites them, and the response names every county it touched, kept or failed.
  • An edit mark is written whenever the owner saves a locality list, so a later import knows the difference between an imported list and a corrected one.

Reparat

  • Counties whose name carries diacritics had no localities at all. The server sent `Brasov` with the diacritic while FAN keeps localities without it, and an unmatched county name returns an empty list with no error: 24 counties answered and 18 did not, exactly the ones with a diacritic in the name. This affected every shop, not only the import.
  • A locality that is not in the county list is cleared when the customer leaves the field, so `florestiii` cannot reach the order. Typing is still free: a prefix is kept while the customer writes, because `Ap` is not a locality either, and clearing on every keystroke emptied the field from under the cursor and made `Apahida` impossible to type.
3.2.62026-09-30

Nou

  • The county and locality fields are now found by reading the address structure, so the plugin works on the classic checkout as well as on WooCommerce Blocks. A field library that hides the real field, such as Select2, is handled too: the value is read and written on the hidden field, where WooCommerce actually reads it, and the styling follows the visible element.
  • A locality field that is a dropdown is filled with the localities of the chosen county, instead of being treated as a text field.

Reparat

  • The county and locality fields no longer grow taller than the rest of the form. The plugin was overriding the field height and the space reserved for the label, on top of the space the theme had already reserved, so the fields ended up at 83px and 121px while their neighbours stayed at 50px and the text fell outside the field. The theme reserves the label space correctly on its own, so the plugin no longer touches the field geometry.
2.7.42026-09-30

Reparat

  • Changing the county now clears the locality for real. The field was looked up in the document, which no longer finds it once mounted, so an old locality from the previous county stayed on the order: pickup points were then asked for in the new county with a locality that does not exist there, and the customer got an empty list.
2.7.32026-09-30

Reparat

  • Typing a locality right after opening the field no longer answers „ no result on this name”. The list is fetched on focus and arrives after the first keystroke, so the search ran against an empty list. The field now waits for that first response and reuses it, instead of asking again per keystroke.
2.7.22026-09-30

Reparat

  • Pickup points load again when the message about the nearest point is shown. The flag said whether the list was sorted by distance, but it was declared inside the block that builds the picker, so reading it outside that block threw and emptied the list.
2.7.12026-09-30

Reparat

  • Pickup points load again. The helper that escapes text was defined after the code that builds the picker, so reading it threw and every request ended as „we cannot load the points”.
2.7.02026-09-30

Nou

  • A „Points near you" button asks the browser for the visitor's location and lists the pickup points of the chosen county ordered by distance, with the distance shown on each row. The visitor's position appears on the map, which centres on the nearest point. Permission is requested once, the coordinates stay in the browser, and nothing is sent to the server or written on the order. Refusing the permission leaves the list ordered by locality, and the button says so.
2.6.22026-09-30

Reparat

  • A locality kept from an earlier session is now shown in the box instead of staying hidden. The field value arrived from the checkout store, but the visible box was never told about it, so the customer saw an empty locality while the pickup points for it were already loaded.
2.6.12026-09-30

Reparat

  • The text in the county and locality boxes sits in the middle of the field. WooCommerce Blocks pads its own fields with `!important`, which won over the plugin's rule and pushed the locality text to the bottom.
2.6.02026-09-30

Nou

  • Pickup points are stored in a dedicated table (`wp_fastawb_lockers`) instead of `wp_options`. The local copy is filled with the whole county, never with a single locality, and is filtered by county and locality in SQL.

Reparat

  • Pickup points for a locality other than the first one requested now appear. The cache was saved per county but filled with the answer for one locality, so the first request for Cluj was for Floresti, its 32 points became "all of Cluj", and every other locality received an empty list. The old `wp_options` entries are deleted once, when the table is first created.
2.5.292026-09-30

Reparat

  • A chosen locality now reaches the order. The field was written twice, once around React's own value tracking, so the block checkout read the field as still empty and wrote the empty value back over the choice.
2.5.282026-09-30

Reparat

  • Choosing another county clears the locality shown in the box, not only the hidden field. The stale name used to stay on screen next to the new county while the order was sent without a locality.
2.5.272026-09-30

Reparat

  • A chosen locality is no longer erased by the refresh that follows it. The locality list is reloaded only when the county actually changes, so the pickup points are requested with the locality the customer picked instead of an empty value.
2.5.262026-09-30

Reparat

  • The county and locality boxes are rebuilt once instead of once per address re-render, which left a second dropdown stacked on the first one and lost the locality that had been chosen.
  • The label of a replaced address field is found through `for`, `aria-labelledby` and the surrounding box, so the original label is hidden instead of sitting next to the new field.
2.5.252026-09-30

Reparat

  • Pickup points are requested with the chosen locality again. The mounted field is taken out of flow, and the code kept looking for it in the document instead of holding on to it, so the locality went out empty and the courier answered with every point in the county. The previously chosen locality therefore stayed on screen.
2.5.242026-09-30

Reparat

  • Pickup points now follow the chosen locality. The locality was read from the first address block in the document, which may be the hidden billing one, so the request kept the previous locality and the list appeared not to change. It is now read from the field the shopper actually filled, and points are reloaded directly on each choice rather than waiting for a change event that the hidden field does not reliably deliver.
  • The county field no longer shows two arrows. The original state select draws its own arrow over the new search field, so both appeared at once.
  • County and city text is vertically centred. Line height now matches the field height with no vertical padding, so the placeholder and the value sit on the same line instead of one floating above the other.
2.5.232026-09-30

Reparat

  • Pickup points now appear for a chosen county. The watcher attached itself only if the address block was already rendered when the script started, and Blocks draws it afterwards: the observer never attached, so the county and city stayed plain text fields and nothing asked for localities. It now waits for the block, up to ten seconds.
  • The county is read from the real field instead of the first address block in the document, which may be the hidden billing one. A free-text county value is also accepted when it is a code or a name from the catalog, instead of only a code.
2.5.222026-09-30

Reparat

  • The locality list at checkout now appears. The AJAX helper returns the payload WordPress wraps in `success` and `data`, and the list was read from the root of that envelope, so it was always empty: the city field had nothing to show and pickup points were searched without a locality.
  • Choosing a county now reaches the shop. A WooCommerce state option reads `CJ:Cluj`, and writing only `CJ` matched no option at all, so the select stayed empty and emitted no change event. The option is now matched by code and its real value written.
  • The county and city labels no longer overlap the field above them. The original field is taken out of flow and its label is hidden with it, rather than left anchored where the field used to be.
  • Searching the localities page no longer downloads every county. It matched county names and codes only, instead of reading each county's localities, which sent 42 requests to the courier and stored about two megabytes for a single word.
2.5.212026-09-30

Reparat

  • The plugin no longer asks the server for its release manifest on every admin page load. It is now held in a transient for thirty minutes, like the delivery modes: a shop with a lot of open admin tabs was sending dozens of identical requests a second, and a stopped server was retried on every render instead of being retried when it mattered.

Nou

  • The county and city fields at checkout can now be searched. A single county carries 438 localities, which rules out a plain `<select>`: on a phone it cannot be scrolled to a value and typing filters nothing. The original field stays in the page and receives the value, so the block still submits the locality, and a filtered list opens over it. Counties get the same search, because scrolling to "Mehedinți" through 42 options is not a choice either. The fields are re-mounted after each block re-render, and the locality list reloads when the county changes.
  • The localities page shows every county on one screen, each in a panel that expands, instead of a dropdown and a list for one county at a time. A list is loaded when its panel opens rather than with the page, so opening the page does not put thousands of rows into the document.
2.5.202026-09-30

Reparat

  • The shipping method title is no longer blank in the zone's method list. The list renders the method's own `title` property, and the class never read its saved settings into it, so the column was empty even though the value was stored correctly; the description was unaffected because it comes from a different property, which is why only the title looked broken.
  • The shipping cost now survives a page reload. It was drawn into the row by JavaScript at generation time and never rendered again from the stored value, so it appeared after generating an AWB and vanished on the next refresh.

Schimbat

  • The AWB block in the orders list is now compact: the AWB number on its own line, the print-label button directly under it, and the cost and courier in a small bordered chip below that. The order sidebar shows the same chip under the print button. Live generation draws the identical layout, so a row no longer changes height when the button is pressed.

Nou

  • A localities page under Fast AWB, where the shop owner can correct, remove and add the localities of a county, with a search field. The list comes from the courier and corrections are sent back to the server, because the locality reaches the AWB and a correction kept only in WordPress would fix the checkout list while the label kept the old value. Counties are shared rather than copied into a second class, so a county cannot be missing from one of them.
2.5.192026-09-29

Reparat

  • A pickup point now reaches the courier as one. The Blocks checkout saved the chosen point but never its provider, and the server requires both for a locker delivery, so it answered `400` and the plugin dropped the rejection into a log line: the order stayed on the server as an address delivery and the AWB was generated there, correctly for the server's copy and wrongly for what the customer paid. The provider is now written on the Blocks path, where it was missing, and a rejected sync leaves the server's own reason on the order instead of passing in silence. An AWB is also no longer generated for an order the server still holds as an address delivery, so a paid FANBox parcel cannot leave for an address with no error. `2.5.19`
2.5.182026-09-29

Reparat

  • The chosen pickup point now reaches the order. The value reached the checkout store correctly and was then dropped: the store is rebuilt on every re-render and starts empty, so five attempts that only changed how the value was written there all failed identically. The choice is now reported to WordPress as soon as it is made and kept in the WooCommerce session, the form's `submit` is listened for as well as the button, and the value is read back from the session when the checkout payload arrives without it. A re-render between choosing a point and paying can no longer drop the choice.
2.5.162026-09-29

Reparat

  • The chosen pickup point now survives the final step of the checkout. The last write before the order was posted was bound to a guessed selector for the place order button; on a real order it never fired, so the order arrived without a pickup point and was reported as one that had not been chosen. The write no longer depends on a selector: any click writes the selection again, which cannot break the flow it is protecting.
2.5.152026-09-29

Nou

  • Pickup points are now stored on our own database instead of being fetched from the courier on every checkout. The full FANbox list is downloaded periodically and served from a local table, so checkout no longer waits on the courier and no longer stops working when their API is unreachable: a county with 194 points went from 7000 ms to 160 ms.
  • Counties, localities and pickup points are kept in both places: on the server, which owns the source, and in WordPress, which keeps a copy so the checkout does not depend on a live request. A copy is refreshed from the server, never edited locally, because a modified pickup point would be refused by the courier after the customer has paid.
  • A list too old to trust is refused with a clear message instead of being offered as if it were current. Pickup points move and close, and a stale identifier means a paid order that cannot be shipped.

Reparat

  • The county is chosen from the real catalog instead of being typed. A free text field meant a customer writing "cluj" got "no pickup point selected", a message that pointed at the wrong problem entirely and could not be acted on. The county is also read from the catalog, so a value that is not a real code is no longer accepted as one.
2.5.142026-09-29

Reparat

  • The chosen pickup point is now written to the order. It was added with `update_meta_data()`, which only changes the loaded order object, so the value was gone by the end of the request: it never reached the courier and the operator had no way to see which point the customer had picked. The order is now saved explicitly before the resync.
2.5.132026-09-29

Reparat

  • A chosen pickup point no longer disappears before payment. The checkout store is rebuilt when WooCommerce creates or reloads the draft order, and its extension data is reset with it, so a choice written once was gone by the time the customer paid and the order reached the server without a pickup point. The choice is now kept and rewritten on every state change and again when the order is placed.
2.5.122026-09-29

Reparat

  • A chosen pickup point now survives the checkout and reaches the courier. Two independent faults sat on the same path. The resync after the order was saved reloaded it with `wc_get_order()`, so it read from the database instead of the object in memory, where the pickup point had not been written yet: the server received the order without a pickup point and sent the parcel to the door. The pickup point now also travels through the checkout's own extension data instead of a registered field, so nothing is rendered for the customer and no field has to be hidden.
2.5.112026-09-29

Reparat

  • A locker chosen at the block checkout now reaches the courier. The chosen rate and locker were saved by the plugin after the order had already been sent to the server, so the server kept an address delivery: it created a "Cont Colector" AWB for the door, at the courier's price, while the customer had paid for a FANbox pickup. The order is now sent again once the pickup point is saved, and a locker order that never received a point is held back with a visible reason instead of being sent to the wrong destination.
  • The AWB controls are gone from the order confirmation page, including for a shop administrator placing a test order. The section was rendered from a front-end template, where the capability check passes for an administrator, so the generate, print and delete buttons appeared on the customer's page. The controls now exist only in the admin: the order edit screen and the orders list.
  • An order row no longer changes its own text on refresh. The status was written raw from the courier in one place and translated in the other, so the same AWB read "confirmed" right after generating and "Status: Confirmat" after a refresh. Both paths now use one status dictionary, and the success message clears itself, because it reports an event rather than the order's current state.
2.5.102026-09-29

Reparat

  • The block checkout no longer fails with a critical error while saving an order. Reading the chosen shipping rate called `get_rate_id()` on the order's shipping item, but that method belongs to the shipping method, not to `WC_Order_Item_Shipping`, so the call threw an uncaught `Call to undefined method` and the checkout answered HTTP 500. Because the failure happened while the order was being saved, customers could not place an order at all. The rate id is now composed from the item's own method id, instance id and the variant suffix.
  • The leftover "Punct de ridicare (opțional)" field no longer shows on the checkout. Only the hidden input was wrapped and removed from view, leaving its label behind, so customers saw an empty field with no purpose — in a shop that may not even offer pickup. The whole field, label included, is now hidden. The input itself stays in the form, because that is how the chosen point reaches the server.
2.5.92026-09-29

Reparat

  • A locker chosen on the block checkout is no longer lost, so the AWB now actually goes to the selected FANbox instead of being sent to the delivery address. The chosen shipping rate was read from `shipping_rates` in the request body, but that key is not part of the Store API checkout schema, so the lookup always returned nothing and every block-checkout order was saved as an address delivery. The rate is now read from the order's own shipping item, where WooCommerce really persists it, compared by its title. A customer who saw and picked "Livrare la locker (FANbox)" got a real, paid, correctly created AWB — delivered to the wrong place, with no error anywhere.
  • Customers no longer see Fast AWB's controls on their own order page. The AWB section was rendered on the customer-facing order details page, so shoppers were told "AWB nu a fost generat încă" and shown an operator's generate button. The whole section is now closed to anyone who cannot edit orders, before any markup is emitted.
2.5.82026-09-29

Reparat

  • Printing a label opens the browser print dialog again. Version 2.5.7 released the hidden label document a second after calling `print()`, which cancels the print job before Chrome has built the preview, so no print window appeared at all. The document is now kept alive while the print dialog is open and is released two minutes later, once the operator has finished printing.
  • The hidden document that Chrome prints from is always mounted on `body`, never inside the order table cell. An ancestor with a `transform` or a `filter` makes `position: fixed` resolve against that ancestor instead of the viewport, which would bring an off-screen label frame back onto the orders page.
2.5.72026-09-29

Reparat

  • The "Printează eticheta AWB" button in the orders list now works on the first press. The click handler attached its listener to the button instead of acting on the click, so the first press only bound the handler and printed nothing, and the second press was the one that printed.
  • Printing a label no longer shows an empty preview. The temporary iframe was removed and its object URL revoked a second after `print()` was called, which is before Chrome has finished building the print preview, so the print window came up blank. The object URL is now released long after the preview exists, and a label request that comes back without a PDF is reported instead of printing a zero-byte blank page.
2.5.62026-09-29

Reparat

  • Printing a label no longer leaks the FAN Courier password. The `/login` call sent `username` and `password` as query params, so the password appeared in the URL, and a connection failure returned that whole URL to WordPress in the error message. A network problem on the courier's side could therefore publish a real password in an admin notice and in the client log. Credentials now travel in the POST body, and a network failure answers with a plain message instead of the HTTP library's text.
  • A label request no longer re-authenticates at the courier on every press. The FAN token is now cached at module level, keyed by courier account, so a second press does not make another login call while the first token is still valid.
2.5.52026-09-29

Nou

  • The update notice now lists every version published after the one installed in the shop, not only the newest one. A shop going from 2.5.2 to 2.5.4 sees the changes from both 2.5.4 and 2.5.3, so a fix released in between is not silently skipped. The plugin sends its installed version with the manifest request, and the range is capped at ten versions so an unknown or modified version does not pull the whole changelog into the response.

Reparat

  • Release notes from several versions no longer overwrite each other. The section heading was used as an array key, so two consecutive versions with a `Fixed` section left only the last one visible — precisely the entries a shop that skipped versions needed to read.
2.5.42026-09-29

Reparat

  • The label print button opened an empty print window. The PDF was rendered in a 1×1 pixel iframe with `opacity: 0`, and Chrome does not start the PDF plugin in a frame with no surface: `load` fired, `print()` had nothing to show, and the dialog came up blank with no error anywhere. The server had been sending a valid PDF all along, so it looked like a courier problem. The frame now has real dimensions and is moved off-screen instead of hidden, and if the print does not happen within a second and a half the label opens in a new tab instead of leaving the operator with a blank window.
2.5.32026-09-29

Reparat

  • The label button read "Printește", which is not a Romanian word. The correct form of `a printa` is "Printează", now used in both the PHP-rendered button and the one JavaScript builds after a successful generation.
2.5.22026-09-29

Nou

  • `start.bat` at the repository root launches both runtime processes — the web server and the tracking worker — each in its own CMD window, so stopping an agent session no longer takes the local server down with it.

Reparat

  • Generating an AWB from the orders list failed silently. The button created a per-order nonce (`fastawb_generate_awb_<id>`) while the AJAX handler verified a global one (`fastawb_generate_awb`), so every request was rejected before it reached the server. `check_ajax_referer` answers `wp_die(-1, 403)`, which is valid JSON, so `response.json()` did not throw and the UI fell back to "AWB-ul nu a putut fi generat" — with no reason in the admin notice, nothing in the server console and nothing in the WordPress log. The handler now verifies the same per-order nonce the button creates and answers with a JSON message, and the browser reports a non-JSON or failing response as such instead of a generic failure.
2.5.12026-09-28

Reparat

  • The shipment status is shown only on orders that actually have an AWB, and it is read once when the page loads. A row without an AWB rendered a status node, which sent a request for every order in the list and displayed "AWB generated. Status is being checked" under rows that have no AWB.
  • The page no longer re-reads the status every two minutes. The worker already queries the courier on its own schedule, by state: one hour for a new shipment, four in transit, twenty-four when stale, and nothing after delivery. The browser was repeating the same unchanged answer on every row without ever reaching the courier.
2.5.02026-09-28

Nou

  • The AWB is now generated without leaving the page. The button shows a spinner while the request runs, then the row fills in with the AWB number, the courier, the cost, a tracking link and the print-label button. No redirect, no reload, and the list keeps its position. A failure shows the server's real reason next to the button instead of a generic notice.

Reparat

  • Generating from the orders list no longer opens the order editor. The list is one form with a button per row, and the order id and nonce travelled in hidden inputs with fixed names, so they were submitted once per row and the last value won. The handler then checked another order's nonce, rejected the request, and redirected. Both values now travel on the pressed button, which sends only its own.
2.4.62026-09-28

Reparat

  • The generate button no longer lands on a blank page. The action was moved into the button's query string, but `admin-post.php` reads the action from the request body, and the orders list form has its own `action` field that won. The request reached no handler at all, so the operator saw an empty page instead of a result. The action travels in the body again, as with the delete button, and the order id stays on the pressed button so each row sends its own.
2.4.52026-09-28

Reparat

  • Every error reaches the admin notice, whatever its kind. A courier can answer with an unexpected payload or a missing field, and an exception outside the three types that were caught became an HTTP 500 with no body: an empty notice with no reason and nothing to fix. Any exception is now caught, and its type is included, so the message stays actionable even when the courier's own is blank. Invalid request data is reported the same way instead of raising past the handler.
2.4.42026-09-28

Reparat

  • The generate button in the orders list acted on the wrong order. The list is a single form holding a button for every row, and the order id travelled in a hidden input with a fixed name, so it was submitted once per row and the last value won. Pressing "Generate AWB" on one order could therefore generate and report success for a different one. The id now travels on the pressed button itself, so only the button that was pressed sends it.
2.4.32026-09-28

Nou

  • Checkout asks for a phone number when the order is delivered to an address. The courier needs it for confirmation and cash on delivery, and without it the AWB could not be generated after the customer had already paid. Pickup at a locker does not ask for one, because a pickup point needs no contact with the recipient. The check is on presence only: a landline or any format is accepted, because the server normalises it when generating the AWB.
2.4.22026-09-28

Reparat

  • A phone number is accepted in any format a customer would type it. The courier asks for `07xxxxxxxx`, but `+40 721 234 567`, `0040 721 234 567`, `0721.234.567` and `0721-234-567` are the same number, and they were refused for having a country code or separators. Refusing a number the customer typed correctly leaves them with nothing to fix.
  • The generate button no longer reports success without generating anything. It believed an AWB number left in the order metadata and returned success without checking the server, so an AWB deleted at the courier, or one left over from a reimported order, produced "generated successfully" with nothing created. The state is now checked at the server, and stale metadata is cleared so the button can try again.
2.4.12026-09-28

Reparat

  • The release package for 2.4.0 was never built, so the server had nothing to offer and shops were told they were up to date while the fix did not reach them. The version is rebuilt and the manifest is regenerated with it.
2.4.02026-09-28

Nou

  • Parcel length, width and height are AWB settings, editable in the Fast AWB settings panel next to package type, manual weight and cash service. The courier refuses an AWB without dimensions, and until now a new order had no way to provide them: the values were read from order meta that nothing wrote, so every new order failed with "for FAN the weight and parcel dimensions are required". Defaults are 20 × 15 × 10 cm and can be overridden per courier like every other AWB setting.

Reparat

  • The generate button now works from the orders list. It opened a second form inside the list's own form, which browsers ignore, so the request went to the orders screen instead of the handler and WordPress answered "the link you followed has expired" without saying anything about the AWB. It now submits to the handler with its own fields, like the delete button already did.
  • Parcel dimensions are not read from order meta anymore. The shop sends zero when the order carries none, and the server applies the configured value, so "not set" is no longer read as "set to zero".
2.3.52026-09-28

Reparat

  • A failed AWB generation now shows the reason in the admin notice. It used to say only "the AWB could not be generated, check the order notes", while the real reason — that FAN requires a weight and parcel dimensions — was only written in a note nobody thought to open. The server's message now travels back with the redirect and is displayed as-is.
2.3.42026-09-28

Reparat

  • Choosing a pickup point no longer fails with "fastawb/locker is not one of". The checkout field was registered as a select, which WooCommerce validates against the options declared at page load. The FANbox list is only filled in after a county is chosen, so every real pickup point was rejected. The field is a text input now, and the visible list next to the map is rendered by the plugin, with the value copied into the field so it still travels through the Store API.
  • Pickup points load in a fraction of the time. The FANbox network has over 3000 points and took over ten seconds to download, and that happened on every county change. The list is now cached per account on the server for fifteen minutes.
2.3.32026-09-28

Nou

  • A pickup point can be chosen from the map. Clicking a marker shows the point and a "Use this locker" button, which selects it in the list, moves the map to it and marks it as chosen. The list and the map no longer have to be matched by eye, and the chosen point is visible in both at once.
2.3.22026-09-28

Reparat

  • The pickup list is filtered by the county the customer already chose. The client sent the parameters in a POST body while the handler read them from `$_GET`, so the county arrived empty, no filter was applied, and the customer was shown all 3231 FANbox points in the country instead of their own county. The request succeeds with a `200`, so nothing looked broken: it just returned the wrong list.
  • The county is read from the state field specifically. Taking the first select in the address block returns the country (`RO`), which matches no county and filters everything away.
  • A request with no county now returns an empty list and a reason, instead of the whole country.
2.3.12026-09-28

Reparat

  • The pickup point list is asked from FANbox. Sameday has no configured credentials and no verified locker contract, so a request that reached it could only ever come back empty, with a message about a courier the customer never chose. The AJAX handler defaulted to Sameday when the provider was missing, which is how a customer asking for FANbox was told about Sameday.
2.3.02026-09-28

Nou

  • The pickup point picker now works on the WooCommerce Blocks checkout. A shop on the block interface was getting the delivery rates but no locker list at all: the fields were registered through the classic checkout API, which the block checkout never calls.
  • Pickup points can now be chosen from a map. The FANbox points are drawn on a Leaflet map next to the shipping options, with the list and the map kept in step. Locker details are read back from the server when the AWB is generated, so the browser is not a source for them.

Reparat

  • Pickup points load on the block checkout. `is_checkout()` returns false there, so the assets were never enqueued and neither the script nor the map could run. The page is now detected by its id, the same way WooCommerce does it.
  • The pickup point is sent from the courier that can actually deliver. Sameday has no configured credentials and no verified locker contract, so the list was empty with a message naming a courier that could never answer. Sameday is no longer accepted as a pickup provider at all: the endpoint answers `400` and a missing provider defaults to FANbox, so a request can no longer reach a courier without a locker contract, even by omission.
  • The pickup point picker sits directly under the delivery options, instead of below the payment methods. A field registered for the order is rendered in "Additional order information"; it stays registered there so validation and saving keep working, and only its position is moved.
  • A county already chosen at checkout no longer has to be chosen again. The block checkout sends the county code, and the lookup now recognises it.
2.2.22026-09-28

Reparat

  • The FANbox pickup point list now loads. The county catalog used wrong codes for seven counties, so the county a customer picked was never recognised and the lookup answered "invalid county". Brașov was catalogued as `BZ`, which is Buzău: WooCommerce sends `BV`. Botoșani, Călărași, Gorj, Giurgiu and Vâlcea had the same class of mismatch, so seven of forty-two counties were broken.
  • The locker list is filtered by county and locality with diacritics normalised on both sides. The courier returns "Bucuresti" while the catalog has "București", so the comparison never matched and the list came back empty with no error to explain why.
  • Locker delivery asks FANbox for pickup points instead of Sameday. Sameday is not configured on the server, and it has no verified locker contract, so the list could not load from it in the first place.
2.2.12026-09-28

Schimbat

  • Checkout now offers the two delivery options as shipping rates, labelled "Livrare prin curier" and "Livrare la locker (FANbox)", instead of the vague "Fast AWB". The customer picks the delivery method where they already choose everything else, instead of in a separate radio that asked the same question twice and could disagree with the chosen rate.
  • The delivery mode is read from the chosen rate, so the order and the AWB always agree on whether it goes to a locker. The pickup point appears when the locker rate is selected, and reloads on every checkout re-render.

Reparat

  • "Free shipping" in the Fast AWB zone is disabled during setup. It offered a third rate with the same zero price as courier delivery, leaving the customer choosing between identical options. It is disabled rather than deleted, because a shop may use it for free-shipping-over-a-threshold.
  • Disabling a competing method actually persists. The state lives in the `woocommerce_shipping_zone_methods` row, not on the in-memory method object, so a plain property assignment reported success while changing nothing. This is the same class of bug as the AWB deletion that needed an explicit `save()`.
2.2.02026-09-28

Nou

  • Checkout offers two delivery options: courier delivery to an address, and pickup at a locker. Locker delivery uses FANbox and is offered only when the server confirms a courier that can actually deliver to a pickup point. When no courier qualifies, the option is hidden instead of promising something the AWB generation would later refuse.
  • The Fast AWB settings page diagnoses the shipping setup. A shop with no shipping zone carrying the Fast AWB method sees an explanation, the manual WooCommerce steps, and a button that creates the zone. The setup is idempotent and never deletes or modifies zones that already exist.
  • `GET /api/client/delivery-modes` reports which delivery modes a shop can support, based on courier credentials and verified capabilities.

Reparat

  • Locker delivery no longer comes from a hardcoded list of courier codes in the API router. It is a registered capability with a contract source, matching the rest of the capability registry. FAN supports it, with evidence from `/reports/pickup-points` and the CollectPoint service; Sameday has no verified contract and therefore does not offer lockers.
  • A shop with no shipping zone could never show a delivery option. This was also the reason `needs_shipping()` returned false and orders were not synchronized: the root cause of both was the missing zone, not a filter.
2.1.102026-09-28

Reparat

  • Release notes in the WordPress update notice are readable. A changelog entry is one sentence wrapped across several lines, and every wrapped line used to become its own bullet, so a single fix appeared as a list of five short, repeatedly interrupted lines. Wrapped lines are now joined into one paragraph with one bullet, and the leading `-` is consumed as the marker instead of being printed in the text.
  • The release notes no longer sit in a narrow column. The notice is rendered on every admin screen, but the plugin stylesheet was only loaded on the Fast AWB pages and on Plugins, so the notes appeared unstyled on the Dashboard. They now use the full width, with slightly larger text and line height, and continuation lines align under the text rather than under the bullet.
2.1.92026-09-28

Reparat

  • An AWB that was already deleted could no longer be removed from the order. The button sent the server the old AWB id, the server answered `404`, and the plugin read that as a failed deletion: it kept the metadata and wrote "the AWB could not be deleted". A `404` now means the shipment is gone from the courier, so the shop's copy is cleared and the action is reported as done. The order note says the AWB no longer existed, instead of claiming FAN deleted it again.
2.1.82026-09-28

Nou

  • AWB generation now falls back to the next courier when the first one fails. Courier selection returns a ranked list instead of a single winner, and the server tries each candidate in score order. A single failing courier no longer costs the client the shipment.
  • Courier selection ignores couriers the client has no credentials for. A high score for a courier that cannot be reached was never an option, only a lost order.

Reparat

  • Deleting an AWB in WordPress reported success while the AWB stayed in the shop. `delete_meta_data()` only changed the loaded order object; without a `save()` nothing reached the database, so the AWB number, courier, cost, status and label reappeared on the next page load. The order is now saved, and the public tracking link and its token are cleared with the rest of the AWB metadata, instead of leaving a URL that answers `404` on a deleted shipment.
  • A failed tariff lookup no longer invalidates an AWB that was already created at the courier. Previously an HTTP 422 from the internal-tariff endpoint threw away a real AWB and, on retry, created a second one. The price now falls back to the courier's own value from the creation response, or to the internal formula marked as an estimate.
  • An unhandled exception while pricing an AWB returned an opaque HTTP 500. It is now a `502` carrying the real reason and every courier that was tried.

Schimbat

  • WooCommerce only syncs orders that can actually reach a courier. Virtual-only orders and orders in `cancelled`, `failed` or `trash` are no longer copied to the server, where they could never produce an AWB.
  • The order payload no longer carries the full cart. `items_json` is not populated from the shop; the server needs weight and value, not product names, and this removes customer purchase data from the server database.
2.1.72026-09-27

Nou

  • Public tracking page at `GET /t/<token>`, rendered by the server from the AWB and its persisted status events. The token is random, not the AWB number, and the link can be rotated or revoked without touching the shipment. Responses carry `X-Robots-Tag: noindex`, `Referrer-Policy: no-referrer` and `Cache-Control: no-store`. Prices never appear on a public page.
  • `POST /api/tracking-links` creates or rotates a public link for an AWB and `DELETE /api/tracking-links/<token>` revokes it. Both are tenant-scoped: the client comes from the license, and an AWB owned by another client answers `404`, never `403`.
  • The WordPress plugin requests a public link after a successful AWB generation and stores the URL in order meta, so a shop can send the customer one address. A link that cannot be created does not invalidate the AWB.
  • The client panel shows the installed version and its release notes at `/panel/versiune`, linked from the sidebar. A version without a changelog entry says so instead of showing an invented summary.

Reparat

  • Generating an AWB for an order whose stored server id no longer exists now resynchronizes the order once and retries, instead of failing silently. The real error is also written to the order notes, so a failure says what went wrong instead of only "AWB-ul nu a putut fi generat".
  • The "Verifică actualizarea în Plugins" button is now a real link with an `href`. It previously depended on JavaScript that was never enqueued on the Plugins screen, so pressing it did nothing.
  • "View version details" no longer answers "Plugin not found". The updater answers the `plugins_api` request with the server manifest, because Fast AWB is not published in the WordPress.org directory.
  • Release notes now stretch the full width of the update notice. The admin stylesheet is also enqueued on the Plugins screen, which previously showed the notes unstyled.
  • Optional plugin setting: when enabled, an order is marked `completed` right after its AWB is generated successfully. It is off by default, it runs only after the AWB is stored, and it never touches an order that is already cancelled, refunded or failed. The setting is local to the shop and is not sent to the server.
  • Liveness and readiness endpoints: `/health` checks the process only, `/ready` verifies database access and returns `503` when the database is unreachable.
  • Alembic migrations through Flask-Migrate. `DATABASE_URL` selects SQLite or PostgreSQL by configuration, and the initial migration reproduces the full schema.
  • Database-backed job queue with controlled retry, backoff between attempts and a dead-letter queue, plus a separate worker process (`python -m app.worker`).
  • Persisted AWB status history (`awb_status_events`), so delivery time and SLA are measured from real events instead of inferred from the current status.
  • Reporting KPIs over 7, 30 and 90 day windows: AWB count, cost, deliveries, return rate, SLA compliance, average delivery time and a per-courier breakdown.
  • Report export as CSV and PDF, both scoped to the active tenant.
  • AWB table with server-side filtering and pagination, a detail drawer, manual status actions protected by CSRF, and a client onboarding diagnostic page.
  • Courier capability registry that is fail-closed: an operation without a verified contract is refused explicitly rather than guessed.
  • Standardized tracking normalization across provider response shapes.
  • Provider tariff lookup before AWB generation, with estimated-versus-final provenance stored on the AWB.
  • Structured JSON logging with request correlation and secret redaction.
  • SQLite backup and restore helpers that preserve the live database before a restore.
  • This changelog, parsed server-side and carried in the release manifest so the WordPress update notice shows what changed in the new version.
  • The live tracking status now renders under the AWB number, in the column that already exists, on both the orders list and the order screen. The browser reads the status the worker already persisted and stops refreshing a shipment that reached a final status. The client panel shows the same information.
  • `GET /api/orders/<id>/tracking` returns the last persisted tracking event for the authenticated tenant. A foreign order and a missing order both answer `404`, so a `404` never reveals that another tenant's order exists.
  • Automatic tracking sync from the background worker. Poll interval follows the shipment state: 1h for a new AWB, 4h in transit, and never again once the shipment is delivered or returned.
  • Night mode for tracking: between 18:00 and 08:00 Romania time the interval doubles. Polling slows down but never stops, so a parcel handed over at 22:00 still shows up.
  • Tracking requests are batched: 50 AWBs per courier request instead of 50 separate calls, which is what keeps a multi-tenant setup cheap.
  • Each AWB is scheduled at a deterministic, spread-out moment, so a thousand AWBs created in the same minute do not all become due in the same minute.

Securitate

  • API role separation: clients can no longer reach global courier management, and courier API keys, secrets and shop IDs are no longer returned in API responses.
  • Sameday tracking no longer returns a hardcoded `in_transit` status. An unverified courier is refused with a clear error instead of showing invented shipment data.
  • Tracking requests are tenant-scoped: the client comes from the database, never from the job payload, so a job cannot read another shop's shipments.
  • CSRF protection enabled for the admin and panel blueprints. The machine-to-machine API stays exempt by design.
  • Rate limiting on AWB generation, returning `429` with `Retry-After`.
  • Idempotent AWB generation: a repeated request returns the existing AWB instead of calling the courier again.
  • Strict tenant ownership: order identity is `(client_id, wc_order_id)`, and ownership is never re-assigned from a request payload.
  • Fail-closed migration for legacy orders with no tenant, instead of silent re-assignment to whichever client asks first.
  • First-run admin creation requires an explicitly configured password; no default password is invented.
  • Log sanitization applied centrally, with structured logging redacting sensitive keys.
  • Login error messages render as text instead of being interpolated as HTML.

Reparat

  • A queued job could be claimed twice by two workers: the claim was a plain SELECT with no commit. It is now a conditional UPDATE committed immediately.
  • A failed job was rescheduled with the current time, ignoring the moment it was supposed to run at, so the retry backoff existed only as a number. Retry now honors the scheduled time and adds jitter.
  • An unknown job type was marked `succeeded` without doing anything, hiding a missing handler. It now fails explicitly and stays visible.
  • A stale status event could roll back an AWB's current status. The current status now follows the most recent event by time, not by write order.
  • The tracking measurement panel never rendered on the orders list. `admin_notices` passes no argument, so the hook suffix read from the method parameter was always empty and the panel returned early. The current screen is now read from `get_current_screen()`, with a `$pagenow` fallback.
  • Live AWB status and the bulk measurement panel now load in WordPress. The plugin assets were changed without a version bump, so the browser could keep using a cached `admin.js`.
  • A stray parenthesis in the legacy schema migration blocked the migration path on databases missing the courier preferences table.
  • The requests dependency check failed after installing a newer `chardet`; the version is pinned to one compatible with `requests`.

Schimbat

  • The reconstructed historical AWB table is now the default view of imported shipments. The complete raw provider snapshot remains the source of truth.
  • Courier contract inventories for DPD and FAN are kept in `docs/`.
2.1.62026-09-25

Nou

  • Exhaustive FAN history import with complete provider payloads preserved, including unknown and future provider fields.
  • Reconstructed transport document per imported AWB, rendered as a semantic table, with the complete raw snapshot available alongside it.
  • Verified courier contract inventories in `docs/`, `docs/DPD_API_COMPLET.md` and `docs/FANCOURIER_API_COMPLET.md`, recording what is confirmed, undeclared and missing instead of inferring fields from a single account.

Securitate

  • FAN binary artifacts and raw envelopes are stored as bytes, not reduced to text.
  • DPD history and import support stays disabled until a real account-specific request and response are verified.
2.1.02026-09-24

Nou

  • Per-client FAN Courier configuration from the client portal and from the admin's selected-client context.
  • FAN username, password and client ID stored server-side; the password is encrypted and never displayed.
  • Server-side FAN authentication test that does not print the token.
  • Server-side release: manifest, controlled ZIP, SHA-256 and license-protected download.
  • WordPress update integration through the standard plugin update mechanism.

Securitate

  • FAN credentials are never stored in WordPress and never enter the plugin package.
  • The release ZIP excludes `.env`, databases, the server directory and Postman files.
  • The compromised Postman token is not used.
2.0.02026-09-24

Nou

  • Client portal with server-side authentication and strict data isolation.
  • Operational client dashboard: orders, AWB documents, deliveries, returns and costs.
  • Filters and operational summaries on the orders page.
  • Client reports for trends, deliveries, returns and costs.
  • Per-client courier selection strategy with weights that must sum to 100%.
  • API contract protected by license and isolated per client.
  • Sameday integration retained in the server.
  • FAN Courier contract prepared for future server integration; credentials stay out of WordPress.
  • WooCommerce settings page for server URL, license and client weights.
  • Sitemap and visible structure for the new modules.

Schimbat

  • The legacy `awb-generator.php` plugin architecture was replaced by `fastawb.php`.
  • Business logic stays in Flask; the WordPress plugin is a thin client.
  • Consistent dark, responsive and accessible design across client and admin portals.
  • The admin dashboard became a global control center, and courier management is read-only there.
  • Admin settings show real server state.
  • Admin logs show real selection records and support pagination.
  • AWBs surface tracking, cost, courier and status in the interface.
  • Client and server checks cover data isolation and the main flows.

Securitate

  • License keys and server configuration stay in WordPress only for shop integration.
  • Secrets and `.env` files are never committed.

Și mai departe?

Vezi la ce lucrăm acum și ce urmează.

Ce urmează