Két commit közötti fájlok összegyűjtését előkészítő felület első fázisa: célkörnyezet
választás (e2e/d2d), commitlista, diff-előnézet figyelmeztetésekkel. Ez a fázis még
semmit nem ír a fájlrendszerre, a csomagolás a phase2-ben érkezik.
- DeploymentGuard: .env kapcsoló + hardkódolt stage whitelist + developer szerep +
feature flag; a flag szándékosan nem biztonsági réteg, csak láthatóság-vezérlés
- GitRepository: csak olvasó wrapper, argumentum-tömbös Symfony Process (nincs shell),
hash- és referencia-validáció, core.quotePath=false az ékezetes útvonalakhoz
- ChangeSetAnalyzer: kizárási lista, törlendő/másolandó szétválasztás (átnevezésnél
mindkettő), figyelmeztetések migrációra, composer.lock-ra és a nem verziókövetett
fordított assetekre
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Egy beszállítóhoz egyszerre legfeljebb egy nyitott végrehajtás tartozhat: egy
execution_failed fájl után a termékek félig frissített állapotban vannak, egy új
import erre a kevert alapra rétegződne rá (és a validálás is ehhez számolná a
diffeket); két párhuzamos végrehajtás pedig nem determinisztikus eredményt adna.
- új PricelistGuard service: egyetlen igazságforrás a blokkoláshoz
- mind a NÉGY belépési pont véd: modern Filament create, régebbi
PriceListProcessor oldal, legacy Admin\PriceListController import, konzol
parancs. A legacy import a legfontosabb - az közvetlenül ír a products
táblába, megkerülve a modult, és a beszállítókat ma még nagyrészt ott kezelik.
- az `inprogress` önmagában nem blokkol: az előfeldolgozás és a validálás
egyetlen terméket sem ír, csak a már elindult végrehajtás számít
- a beszállító nem tűnik el a select listából, hanem konkrét magyarázatot kap a
felhasználó arról, melyik fájl blokkol és miért
- blokkolt beszállítónál másik fájl jóváhagyása sem indítható
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- PricelistExecutionJob + új PricelistRevertJob: $tries=1, $timeout, failed()
hook. A service catch ága csak PHP exceptiont fog el; worker timeout vagy
memórialimit esetén a fájl inprogress-ben ragadna, döntési gomb nélkül.
- heartbeat backstop a kill -9 esetére, ahol a failed() sem fut: minden chunk ír
a rekordba, így az updated_at a szívverés (isExecutionStuck). Ezzel garantált,
hogy minden kísérlet véges időn belül terminális állapotba jut - enélkül a
kényszerlezárás sem nyílna ki soha.
- kompenzáló visszaállítás R1-R4: árak törlése, termékek visszaállítása a
snapshotból, létrehozott termékek kivonása (soft delete, NEM fizikai törlés,
mert lehet rájuk hivatkozás), árlista + fájl lezárása
- konfliktuskezelés: a végrehajtás óta kézzel módosított terméket kihagyjuk és
jelentjük, nem írjuk felül vakon
- FIX phase5/3: a snapshot csak az írás ELŐTTI updated_at-et tárolta, amihez
képest a termék a végrehajtás után mindig eltér - a konfliktus-ellenőrzés így
minden sort kihagyott volna. Most a saját írásunk utáni updated_at is bekerül.
- kényszerlezárás: csak legalább egy terminális hibába futott visszaállítási
kísérlet után, flag + developer szerepkör mögött, kötelező indoklással
- döntéstámogató panel: mi történt már meg az importból, hogy a felhasználó ne
vakon válasszon a Folytatás és a Visszavonás között
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A sleep(1) szimuláció helyén valódi import: árlista fejrekord -> új gyártók ->
új termékek -> termékfrissítés -> árak -> lezárás.
- szándékosan nincs egyetlen átfogó tranzakció: fázisonként/chunkonként
tranzakciózunk, az atomicitást az adja, hogy az árlista E6-ig draft marad,
tehát félbeszakadáskor sem kerül ki félkész ár a felhasználók felé
- minden fázis idempotencia-jelölője az az artefaktum, amit maga hoz létre
(price_list_id / producer_id / product_id / applied_snapshot / pivot sor),
így a megszakadt futás folytatható, nem kezd elölről
- E4 a termék TÉNYLEGES, írás előtti állapotát menti az applied_snapshot-ba,
ugyanabban a tranzakcióban - ebből áll majd vissza a Visszavonás
- új EXECUTION_FIELD_MAP: a PRODUCT_FIELD_MAP csak a diff-számításhoz kellett,
az import a legacy importPriceList()-tel azonos, bővebb mezőkört ír
- hiányzó opcionális oszlop (pl. Akció) nem írja felül a termék meglévő értékét
- updateStepStatus: az Execution lépés 'failed' státusza execution_failed
fájlstátuszra képződik le, a többi lépésé marad fail
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- PricelistFile::canBeApproved() / hasExecutionStarted(): a jóváhagyás feltételei
egy helyen, hogy a felületi gomb és a service ugyanazt ellenőrizze
- approve(): a státusz MÉG A DISPATCH ELŐTT billen át, különben a queue-latency
alatt a gomb látszana és egy második kattintás párhuzamos importot indítana
- reject(): új 'rejected' lépésstátusz - nem 'failed', mert azt az
updateStepStatus fail fájlstátuszra fordítaná, az elutasítás viszont closed
- a döntés (ki, mikor, milyen indokkal) a file_meta.approval-ba kerül, mert a
BaseAuditable a nevével ellentétben nem naplóz
- Edit gomb szűkítése hasExecutionStarted()-tel három helyen, köztük az
EditPricelistFile::afterSave()-ben, ami a teljes láncot újraindítja
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- új PricelistExecution Pennant flag (enabled + roles=[developer]) a jóváhagyás,
végrehajtás és visszavonás rollout-kapujaként
- új execution_failed fájlstátusz: a validálásig tartó szakasz fail státuszától
szándékosan elkülönítve, mert itt már történhettek termék- és árírások
- PricelistFileStatusEnum::color(): a badge-színek eddig két helyen, kimerítő
match-ben voltak duplikálva, amit az új case eltört volna
- pricelist_file_lines.applied_snapshot + executed_at a chunkolt, idempotens
végrehajtáshoz és a kompenzáló visszaállításhoz
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>