Súhrnný balík opráv — celý katalóg do Grandusu, dvojitý sklad Schindler, skrátený feed Medeia
· Infraštruktúra
Konsolidovaný balík opráv za obdobie 14. 7. – 24. 7. 2026. (1) „Všetky filtrované" pri prenose do Grandusu poslalo celý katalóg (4d52a703) — hromadný prenos v režime „Všetky filtrované" ignoroval filter na zozname produktov a pushol CELÝ katalóg namiesto vyfiltrovanej podmnožiny: frontend filterCriteria vôbec neposielal a execute endpoint nemal pole, ktoré by ich prijalo, takže chýbajúce productIds spadli do predvoleného správania „bez productIds pushni všetko". Filter sa teraz rieši tým istým spevneným resolverom ako ostatné hromadné akcie a vyhodnocuje sa ako explicitný výber; nulový počet zásahov skončí chybou namiesto tichého pushu všetkého. (2) Dvojité počítanie skladu Schindler (d0ca133c) — importér zapisoval STOCK_ITEM, teda súhrn tých istých fyzických kusov, ktoré už nesie rozpis WAREHOUSES, do syntetického štvrtého skladu, takže všetko, čo skladové množstvá variantu sčítava (push do ERP, katalóg, košík a pokladňa e-shopu, zoznam v administrácii), čítalo približne dvojnásobok — objednávka na 10 kusov proti 5 reálnym prešla. Dodávateľ zároveň vyjadruje nulu vynechaním celého bloku WAREHOUSES, ale importér aktualizoval len sklady prítomné vo feede, takže 1 137 vypredaných variantov ostalo natrvalo na poslednom nenulovom množstve. Sklad teraz tečie výhradne z rozpisu — každý známy sklad sa vynuluje a prekryje hodnotami z feedu — riadky v skladoch mimo autoritatívnej množiny sa nulujú a pri neznámom názve skladu či nečitateľnom množstve sa zachová posledný známy stav a dôvod sa zapíše do import logu. (3) Skrátený feed Medeia a ~407 skrytých živých produktov (d93e03b3) — približne 15 MB feed dodávateľ spoľahlivo utne na 80 – 88 %; pôvodný importér parsoval počas sťahovania a robil jednu transakciu na produkt, takže tempo čítania zo siete udávali zápisy do databázy a beh vždy prekročil časový limit odpovede na strane dodávateľa. 470 – 800 produktov v neprečítanom chvoste nikdy nedostalo obnovený last_sync_at, takže ich automatické upratovanie označilo ako missing_from_feed a skrylo. downloader.DownloadComplete teraz stiahne celý súbor a pri useknutí pokračuje hlavičkou Range: bytes=<off>- až do deklarovanej dĺžky (15 MB za 1 – 2 s) a medeia_importer parsuje z pamäte; kompletný beh obnoví každý produkt, takže sa skryté produkty pri najbližšej synchronizácii samy vrátia. (4) Deaktivovaný variant určoval cenu v e-shope (35997680, cb1b81ba, migrácia 470) — ani LATERAL-y v mv_storefront_product, ani dve čítacie cesty v ORM nefiltrovali product_variant.status, takže starý variant za 2,03 € inzeroval „od 2,03 €" pri iPhone za 269,90 € a radil ho ako najlacnejší v kategórii (18 dotknutých produktov). Obe LATERAL-y aj oba dotazy teraz vyžadujú status='active'. VariantsService navyše nevysielal žiadnu udalosť, takže po ručnom prepnutí nič nenastavilo mvDirty — create/update/delete teraz emitujú product.updated (delete až po commite), takže sa snímka katalógu skutočne prestaví. (5) Objednávky do ERP i6 chodili vždy s Rakúskom (b6eb2ff4) — CstCus_XCouId sa bral výhradne z config.countryMapping, teda z kľúča bez DTO, bez validácie a bez obrazovky v administrácii, ktorý bol prázdny u každého tenanta, takže obe vyhľadania spadli na id krajiny 1. U jedného tenanta ide o 1 502 objednávok odoslaných od marca do júla a 1 587 z jeho 1 599 objednávok má maďarskú dodaciu adresu; uložené adresy boli celý čas správne. Id krajín sa teraz načítavajú z exportu Cou samotného ERP (cache na endpoint, výplňové riadky sa preskakujú) a neznáma krajina sa nechá nevyplnená namiesto hádania. (6) Zákaznícke záznamy v ERP bez telefónu (17e16098, 4efb4d65) — ComCus_Tel a ConCus_Tel sa plnili z order.customer.phone bez prepadu na adresy, a keďže sa tieto riadky zapisujú len pri prvej objednávke, zákazník ostal v ERP bez telefónu navždy (85 z 1 599 objednávok dotknutého tenanta). Push teraz prepadáva na fakturačnú a potom dodaciu adresu a pokladňa už neodfotí uloženú hodnotu z profilu — uloží číslo zadané vo formulári a doplní ho späť do profilu iba vtedy, keď je prázdny. (7) Obrázky Slovkolexu sa nikdy nedostali do Grandusu (6ecf315f) — parser ukladá holé názvy súborov ako proxy cesty /api/files/, ktorých bajty ležia na FTP dodávateľa, ale resolvePublicUrl zahadzoval každú relatívnu cestu okrem /public/, takže imageUrls ostalo prázdne a obrázok sa ticho neprenášal, hoci samotné produkty exportovali v poriadku (13 700 obrázkov na 5 400 produktoch, z ktorých ani jeden nikdy neposlal do Grandusu obrázok). Cesty /api/files/ sa teraz doplnia o MONOKAIDO_PUBLIC_BASE_URL, takže si ich Grandus stiahne cez proxy. (8) Dodávateľ z nahratého cenníka bol nepoužiteľný (66a51369, 05044fc6, dce13ca5) — na produkcii každé vytvorenie padlo v movePermanent na EACCES pri /app/storage/uploads/<companyId> (chown pri štarte kontajnera túto cestu nikdy nepokrýval), a keďže čerstvý dodávateľ má sync_status NULL, nie 'pending', obidva rollbacky netrafili žiadny riadok a atomický claim v replaceFile odmietal každé opätovné nahratie s UPLOAD_SYNC_IN_PROGRESS. Dockerfile teraz ten priečinok vytvorí a odovzdá a všetky tri kontroly pripúšťajú NULL. Samostatne: ToParsedProduct čítal len mapping.ExternalID, takže mapovanie iba na sku naimportovalo nula produktov — teraz prepadáva na sku a potom ean a preskakuje konštantné mapovania — a getNextSyncTime prešiel na predikát plánovača, takže dodávateľ bez adresy feedu už neukazuje trvalý oranžový odznak „Po termíne". (9) Dodávateľa so synchronizovaným skladom sa nedalo zmazať (6045c559) — sklady sú od migrácie 396 viazané na dodávateľa cez (company_id, vendor_id, code) v unikátnom indexe s NULLS NOT DISTINCT, ale FK warehouse.vendor_id ostal ON DELETE SET NULL, takže mazanie dodávateľa jeho sklady preklopilo na úroveň spoločnosti a zrazilo sa s firemným skladom rovnakého kódu — 23505, ktorý sa navonok prejavil ako trvalá 409. Mazanie teraz najprv odstráni vlastné sklady dodávateľa, v tej istej transakcii ako zmazanie dodávateľa a striktne v rozsahu (company_id, nenulové vendor_id), takže sa predvoleného ani zdieľaného skladu nemôže dotknúť. (10) Kategórie v exporte na Árukereső (363c1ed6, 588bd3d5) — fetchParameters vracia zobrazovanú hodnotu (preklad alebo označenie voľby v jazyku feedu), takže pravidlo napísané na surovú hodnotu nemohlo zafungovať na žiadnom feede, ktorého jazyk sa líši od uložených hodnôt — čiže na každom feede pre Árukereső; matcher teraz dostáva ppv.value, kým parametersByCode ostáva lokalizované. Prípona z popipParams, ktorá je len odhadom pre produkty so známou kategóriou z vlastného stromu, sa navyše pripájala bezpodmienečne a kazila aj explicitne pripnuté kategórie na cesty, ktoré portál nemá; aplikuje sa už len pri categoryPathSource 'internal'.