# Drupal újramigráció, foglaltság-felülbírálás, Excel körút

## Context

2025 októberében egy egyszeri, SQL-alapú migrációval jött át a Drupal katalógus (`data/migration/*.sql`).
Azóta a mi adatbázisunkban **jelentős kézi munka** halmozódott fel — helyszínnevek, rövidnevek, slugok,
címek, SVG térkép hozzárendelések, `svgNodeId`, `areaId`, publikálási állapotok, és 219 raktár + 15 helyszín
+ 55 hír AI fordítása. Közben a Drupalban is dolgoztak: árat emeltek, raktárakat vettek fel és töröltek.

Élesedés előtt három dolog kell:

1. **Újramigráció friss Drupal adattal, a kézi munka elvesztése nélkül.** A régi `.sql` scriptek erre
   alkalmatlanok: `DELETE FROM ... WHERE legacyId IS NOT NULL` + újrainsert, hardkódolt id-kkel — ez az egész
   kézi rendezést törli, és a hírek képeit is újraletöltené.
2. **Excel export/import** a helyszínekről és raktárakról, hogy a megbízó gyorsan ellenőrizni és javítani tudjon.
3. **Foglaltság áthozása** — a Drupalban egy naptármodul tartja, nálunk a bérlésekből számolódik.

## Mérési eredmények

Mindkét dump be van töltve a lokális MariaDB-be: `drupal_src` (Drupal, 302 tábla) és `r24_prod` (az éles
dumpunk). A drift mérve:

| | |
|---|---|
| raktár | 494 Drupal / 468 nálunk → **48 új**, **22 eltűnt** (6 közülük új nid-en visszajött BP VAR-nál) |
| **ár** | **438 / 446 eltér** — a Drupalban emeltek 2026 közepén (pl. 24 900 → 35 000). Drupal az autoritás. |
| méretek | 2 230 érték közül **3** tér el → a property értékek stabilak |
| leírás (hu) | 446 közül **1** tér el → fordítási költség ~0 |
| hír | 67 Drupal / 55 nálunk → **12 új**, egy sem tűnt el |
| helyszín | **1 új telephely**: `helyszin` tid 138 = Szentendre, Dera Park (16 raktár) |
| property opció | **1 új**: `elhelyezkedes` → „Hátsó parkoló felől megközelíthető” |
| property definíció | nincs változás, mind a 30 megvan `legacyField` alapján |
| kép | a teljes delta **59 fájl / 37,4 MB** (15 fid az új raktárakhoz, 12 hírkép, 2 meglévő raktárhoz, a többi alaprajz) |
| foglaltság | a Drupal naptárból **332 raktár foglalt ma**, jövőben kezdődő nincs |

### Két hiba a jelenlegi leképezésben

- **`location.legacyId` rossz.** A `helyszinalaprajz` node nid-jére mutat, és a szentendrei telephelyeknél
  el van csúszva: a mi 1-es helyszínünk (Kalászi út 3.) a 177-es nid-et hordozza, ami a Dózsa György úti
  konténertelep — ezért **rossz alaprajzi fotókat is kapott**. A megbízható kulcs a `helyszin` taxonómia
  term (`field_helyszin_tid`), ami pontosan **1:1** a 15 helyszínünkkel.
- **`address` 1–17 (a migrált címek) orphan** — minden helyszín a kézzel felvett 20–34 címekre mutat.
  Az 1, 2, 18, 19 duplikátum/teszt.

### A Drupal foglaltsága

Az **Availability Calendars** modul: `availability_calendar_availability` (cid, date, sid), 577 299 sor.
Állapotok: `2` = Szabad, `3` = Lefoglalt, `4` = Ideiglenesen lefoglalt. A naptár a raktárhoz
`field_data_field_foglaltsag`-on kötődik. A napokat run-length kódolva tiszta intervallumok jönnek ki:
1 814 lefoglalt intervallum összesen, **332 fedi a mai napot**. Az intervallum utolsó napja pontosan az,
ameddig a telephely le van foglalva — ez lesz a felülbírálás vég dátuma.

Bérlő nem azonosítható: a Drupal Commerce 684 leadott megrendeléséből ±21 nap toleranciával is **csak 74**
párosítható a 332 foglaláshoz. Ezért **bérlést nem hozunk át**, csak a raktárak foglaltságát állítjuk.

---

## Solution

### A. Foglaltság-felülbírálás a raktáron

A `rent` tábla érintetlen marad. A `storage` kap két mezőt, és a foglaltsági szabály **egy helyre**
költözik.

**Séma** — `models/Storage.php` `getColumns()`/`getIndexes()` **és** migráció (ADR 0003, ADR 0022):

| kolumna | típus |
|---|---|
| `availability` | `varchar(16) NOT NULL DEFAULT 'AUTO'`, indexelt |
| `availabilityUntil` | `date NULL`, indexelt |

**`models/StorageAvailability.php`** — konstans-osztály, nem tábla (ADR 0012), a `RentStatus` mintájára:
`AUTO` / `FREE` / `CONDITIONAL` / `OCCUPIED`, plusz `all()`, `manual()`, `asOptions()`, `label()`, `icon()`,
`color()`. Feliratok: `bt( 'storageAvailabilities_' . $v )` a `messages/hu/admin.php`-ban, a frontend
oldalira `messages/{hu,en,de}/frontend.php`.

**Effektív állapot** (olvasáskor számolva, nincs mit visszaállítani):

```
availability = AUTO                              → automatikus (rent + proposal request, mint ma)
availabilityUntil IS NOT NULL AND < ma           → automatikus (lejárt)
availability = OCCUPIED                          → reserved
availability = CONDITIONAL                       → pending
availability = FREE                              → free
```

`availabilityUntil` = NULL: határozatlan ideig érvényes.

**Validáció és automatikus visszaállás** (a te döntésed szerint):

- `Storage::rules()` — ha a raktárhoz van élő bérlés (nem ending státusz, `dateTo IS NULL OR dateTo >= ma`),
  akkor az `availability` **csak `AUTO` lehet**; a manuális értékek validációs hibát adnak, és az admin
  formon a `DropDownField` `itemListener`-e ki is szűri őket.
- `availabilityUntil` csak manuális értéknél értelmes, és nem lehet a mai nap előtti.
- `Rent::afterSave()` — nem ending státuszú bérlés mentésekor a raktár `availability` visszaáll `AUTO`-ra,
  `availabilityUntil` NULL-ra. Így a szabály nem tud ellentmondásba kerülni, és pont az általad leírt
  munkafolyamat valósul meg: ahogy bekerülnek a valódi bérlések, a felülbírálás magától eltűnik.

### B. A foglaltsági szabály egy helyre kerül

Ma **hat** helyen számolódik ugyanez, ebből **három nyers SQL-ben** — ez a `CODE_STYLE.md` DRY szakaszának
pontosan az az esete, amit tiltunk, és a felülbírálás hatszori bemásolása garantáltan elcsúszik:

| Hol | Mit szolgál |
|---|---|
| `services/StorageService.php:56` `usage()` | `CalendarWidget` a nyilvános raktároldalon |
| `services/StorageSearchService.php:76` `occupiedIds()` | a keresőlista default rendezése |
| `services/LocationService.php:181` `getFreeStorageCount()` | nyers SQL — helyszínkártyák, helyszínoldal |
| `models/Location.php:431` `getPublishedStorages()` | nyers SQL `available` flag — SVG térkép szín (`LocationsController:59`), `storage-list-widget.php:39` |
| `modules/admin/controllers/StoragesController.php:246` `getStatusSelect()` | nyers SQL — admin grid státuszpont + szűrő |
| `modules/admin/action/OverviewTable.php:491` `getStatus()` | az ERP áttekintő |

Új `services/AvailabilityService.php` lesz a szabály **egyetlen** birtokosa.

**Kérésenként pontosan három lekérdezés, a lista hosszától függetlenül.** Nincs raktáronkénti query, nincs
N+1 sem a színezéshez, sem a szűréshez. A service egyszer, az első hívásnál felépít két memoizált tömböt —
`storageId => 'free'|'pending'|'reserved'` és `storageId => intervallumok` — ebből a három `->cache()`-elt
lekérdezésből:

| # | Lekérdezés | Sorok |
|---|---|---|
| 1 | `SELECT id, availability, availabilityUntil FROM storage WHERE availability <> 'AUTO'` | ~332, indexelt |
| 2 | a nem ending státuszú bérlések (`StorageSearchService::rents()` mai lekérdezése) | néhány |
| 3 | a rent nélküli, nem elutasított proposal request itemek (a `usage()` mai lekérdezése) | néhány |

Utána minden PHP a memoizált tömbök felett. Ez **kevesebb** lekérdezés, mint ma: a `usage()` két lekérdezése
jelenleg **nincs is cache-elve** (`StorageService.php:65,75`), a `getFreeStorageCount()` egy külön nyers
COUNT-ot futtat, a `getPublishedStorages()` pedig egy LEFT JOIN-t visz a raktárlista queryjében.

Publikus felület:

- `state( int $storageId ) : string` → `free` / `pending` / `reserved` (a ma is használt kulcsok, hogy a CSS
  és a `views/parts/storage-usage.php` ne változzon)
- `states() : array` — a teljes map, a listás hívóknak
- `occupiedIds() : int[]`
- `intervals( int $storageId ) : array` — a `usage()` payloadja, a felülbírálás intervallumával együtt
- `reset() : void` — a memoizáció eldobása, a console commandoknak (`SERVICES.md` / `GOTCHAS.md`
  statikus-memoizálás csapda)

A hat hívási hely ezt hívja:

- `occupiedIds()` és `usage()` delegál (a mai lekérdezéseik a service-be költöznek, `->cache()`-elve)
- `getFreeStorageCount()` → PHP a már cache-elt `StorageSearchService::storages()` + `states()` felett;
  a nyers SQL COUNT eltűnik
- `getPublishedStorages()` → a `rent` LEFT JOIN kiesik; az `available` a map-ből jön a query után, az
  `LEAST()` ár-rendezés maradhat SQL-ben, az `available` szerinti rendezés PHP-ba kerül (≤ 93 sor / helyszín)
- `getStatusSelect()` → **ez marad SQL**, mert az admin grid ebben szűr és rendez, lapozva. Az `availability`
  indexelt kolumna, a felülbírálás egy `IF()` a meglévő kifejezés körül. Hogy a szabály ne másolódjon:
  a kifejezést a `StorageAvailability` osztály adja (`sqlState()`), tehát a leképezés egy helyen van.
  **A benne lévő hibát is javítjuk**: a `CURRENT_DATE <= dateTo` határozatlan idejű bérlésre
  (`dateTo IS NULL`) soha nem teljesül, tehát ma egy nyitott bérlés szabadként jelenik meg az admin gridben.
- `OverviewTable::getStatus()` → három sor, a map-ből

**Admin:** `DropDownField::make( 'availability' )` + `DateField::make( 'availabilityUntil' )` a raktár form
„Beállítások" hasábjába (`getFormLayout()`), és az `availability` szűrhető a gridben, hogy végig tudják járni
a felülbírált listát.

---

### C. Az import tooling

Minden console command egy új `commands/ImportController.php`-ban. A `LegacyController` **érintetlen marad**
(történelmi dokumentum). Nincs több nyers `.sql`: az importerek ActiveRecord-on keresztül írnak, hogy a slug
generálás, a fordítás-invalidálás (`ActiveRecord::queueTranslations()`) és a search index rendesen működjön.
Minden command **idempotens**, legacy id-re kulcsolt, és van `--dry-run`-ja, ami diffet ír és nem nyúl semmihez.

#### Mezőgazdálkodás — ki írja mit

| Tulajdonos | Mezők |
|---|---|
| **Drupal** | `price1M/3M/6M/12M`, property értékek, `description` (hu), `locationId`, `storageTypeId`, létezés |
| **mi, soha nem íródik felül** | `slug`, `name`, `nameInContract`, `shortName`, `svgNodeId`, `areaId`, `isPublished`, `isFeatured/isNew/isPremium`, `translations`, `location` és `address` minden mezője |
| **Excel** | amit a megbízó változtat — a Drupal import **után**, és onnantól a Drupal import zárva |

Az utolsó pont egy egyirányú kapu: **ha visszajött az Excel, a Drupal importot nem futtatjuk újra**,
különben elveszik a javításuk.

---

## Fázisok

### 1. Alapok  ✅ done

- `data/import/maps.php` — a felülvizsgált leképezések egy helyen: `helyszin` tid → `location.id` (16 sor,
  1 új), `tipus` tid → `storageTypeId` (2 sor), `helyszinalaprajz` nid → `location.id` (19 sor, az alaprajzi
  fotókhoz, sok:1, kézzel átnézve).
- `bin/import-db` — betölti a két dumpot `drupal_src`-be és az éles másolatát `r24_test`-be, egy paranccsal
  visszaállítható. Minden teszt az igazi kézi adatokon fut, nem a lokális fejlesztői DB-n.
- Migráció: `location.legacyId` javítása a `helyszin` tid alapján, az orphan `address` 1–19 törlése.

**Fájlok:** `data/import/maps.php`, `bin/import-db`, `migrations/m2609XX_XXXXXX_location_legacy_ids.php`.

### 2. `import/report` — csak olvas  ✅ done

Entitásonként: új / módosult / eltűnt, plusz az adatminőségi problémák, amiket nekik kell eldönteni. A már
ismertek: duplikált „BP VAR 21" (nid 706 és 711, azonos méretekkel), két 2021-es publikálatlan maradvány
(nid 2, 3), 6 vezető szóközös cím, egy elírás („rároló", nid 701), 22 eltűnt raktár amiből 6 új nid-en
visszajött. **Ezt küldjük Andrisnak, mielőtt bármit importálunk.**

**Fájlok:** `commands/ImportController.php`, `services/ImportDiffService.php` (a diff lekérdezések — a report
és minden `--dry-run` ezt használja).

### 3. Foglaltság-felülbírálás (A + B szakasz)  ✅ done

Séma, vocabulary osztály, `AvailabilityService`, a hat hívási hely átvezetése, validáció, `Rent::afterSave()`,
admin mezők. Unit teszt az `AvailabilityService::state()`-re (tiszta függvény, tömbökből is hívható) és a
`getStatusSelect()` javított SQL-jére.

**Fájlok:** `models/Storage.php`, `models/StorageAvailability.php`, `models/Rent.php`,
`services/AvailabilityService.php`, `services/StorageService.php`, `services/StorageSearchService.php`,
`services/LocationService.php`, `models/Location.php`,
`modules/admin/controllers/StoragesController.php`, `modules/admin/action/OverviewTable.php`,
`messages/{hu,en,de}/*.php`, `migrations/m2609XX_XXXXXX_storage_availability.php`,
`tests/unit/AvailabilityServiceTest.php`.

### 4. `import/catalogue` — az inkrementális migráció  ✅ done

Egy tranzakcióban, sorrendben:

1. `storage_type`, `storage_property`, `storage_property_option` — upsert `legacyId` / `legacyField` alapján
   (az 1 új opció bejön)
2. új helyszín a `helyszin` 138-hoz + címe (SVG térkép nélkül, azt tőlük kell kérni)
3. raktárak: új beszúrása, meglévőkön **csak a Drupal-tulajdonú mezők** frissítése; **soha nem töröl** — ami
   eltűnt a Drupalból, az `isPublished = 0` és felsorolásra kerül
4. property értékek: raktáronként újraépítve. A `storage_property_value` 14 040 sora a migráció óta
   érintetlen (`createdAt` mind 2025-10-13, `updatedAt` mind NULL), ezért az újraépítés biztonságos.
   Utána `area` / `volume` újraszámolva `width × length × height`-ból (a `10-ending.sql` logikája).
5. hírek: **csak az új 12** beszúrása

### 5. `import/photos` — fájl alapú, change alapú  ✅ done

- A forrás egy lokális könyvtár (`data/import/files/`), amit a régi szerverről `sites/default/files`-ból
  rsyncolsz. Nincs webes crawler, nincs HTTP letöltés.
- Kulcs: (owner tábla, owner id, Drupal `fid`) → ami már megvan, azt átlépi: nincs letöltés, nincs `Picture`
  rekord, nincs verziógenerálás. A mért delta **59 fájl**.
- `File::createFromFile()`-t használ (`models/File.php`), nem a `createFromUrl()`-t.
- `import/contents-manifest` — sha1 manifest a `web/contents/`-ről. Ugyanez a command a szerveren is lefut,
  a két manifest diffje adja a feltöltendő fájlokat: `rsync -a --ignore-existing`, vagy egy tar.gz csak a
  deltából, ha csak SFTP van.

### 6. `import/occupancy` — a 332 raktár státuszozása  ✅ done

- A naptárnapokat run-length kódolja intervallumokká; csak a mai napot fedőket tartja meg.
- `sid = 3` → `availability = OCCUPIED`, `availabilityUntil` = az intervallum utolsó napja.
  `sid = 4` → `CONDITIONAL` (aktuálisan nulla van ilyen, csak riportolunk).
- `--gap N`: az 1–2 elszórt szabad nap által kettévágott intervallumok összevonása (default 0; a report
  megmutatja, mennyit érint).
- `--reset`: minden nem-`AUTO` raktár visszaállítása `AUTO`-ra futás előtt → a command idempotens és
  újrafuttatható.
- **Nincs `rent` séma-változás, nincs fake customer, nincs payment- és Billingo-kockázat.**
- A 74 párosítható foglalásról **külön CSV** e-maillel, hogy ahol fontos, kézzel rögzíthessék a valódi
  bérlést (ami aztán magától visszaállítja a raktárt `AUTO`-ra).

### 7. Excel körút  ✅ done

- Új dependency: `phpoffice/phpspreadsheet` (`composer.json` + `composer.lock`).
- `import/export-xlsx`:
  - **Helyszínek** munkalap — 15 sor, alapmezők + mind a **38 `location_property`**. Ezek jelenleg
    **teljesen üresek** (0 érték), és csak ők tudják kitölteni: közműszolgáltatók, mérőóra- és gyári számok,
    POD azonosító, kapukódok, közösköltség, szerződés-érvényességek.
  - **Raktárak** munkalap — 468+ sor, alapmezők + a 30 `storage_property` + `availability` /
    `availabilityUntil`, hogy a foglaltságot is tudják javítani.
  - **Segédlet** munkalap — csak olvasható: property-nként a felvehető opciók.
  - `id` kolumna zárolt, fejléc fixálva, legördülők az enum kolumnákon, boolean = igen/nem, szám számként.
    A `translations` nem megy be — a fordítás magyarból újragenerálódik.
- `import/import-xlsx`:
  - `id` alapján párosít; üres `id` = új sor
  - minden cellát validál a property típusa és opciólistája ellen, az `availability`-t a
    `StorageAvailability` + a bérlés-validáció ellen
  - `--dry-run` a teljes változáslistát kiírja
  - **egyetlen hibás cella esetén nem ír semmit**, olvasható hibalistát ad
  - ActiveRecord-on keresztül ír, így a módosult magyar szöveg újra fordításra kerül

### 8. Dokumentáció  ✅ done

Nem a fázisok közben, hanem a végén, egy külön committal (`WORKFLOW.md` 5. pont) — de a terv része, mert a
kontextusnak meg kell maradnia.

**Új ADR-ek:**

| ADR | Miről |
|---|---|
| `0033-occupancy-overrides-live-on-the-storage.md` | Miért nem hozunk át bérléseket: az `ACTIVE` rent payment-et generál és a Billingo kiszámlázza; a 332 foglalásból 77%-nál a bérlő nem azonosítható; a felülbírálás visszafordítható és magától elfogy. A `rent` tábla érintetlen. |
| `0034-the-availability-rule-has-one-owner.md` | A hat párhuzamos implementáció, az `AvailabilityService`, a háromlekérdezéses kérésenkénti költségvetés, és az egy elfogadott kivétel: az admin grid SQL-ben szűr, ezért a kifejezést a `StorageAvailability` adja. |
| `0035-the-drupal-re-import-is-incremental.md` | Miért nem futtatható újra a `data/migration/*.sql`; a mezőnkénti tulajdonosi táblázat; „soha nem töröl, csak depublikál"; idempotencia legacy id-re; és az egyirányú kapu: az Excel visszatérése után a Drupal import zárva. A `helyszin` taxonómia term mint a helyszín igazi kulcsa, a `helyszinalaprajz` nid helyett. |
| `0036-the-excel-round-trip-is-xlsx.md` | `phpoffice/phpspreadsheet` felvétele CSV helyett: tipizált cellák, legördülők, zárolt `id`, több munkalap — nem technikai szerkesztőnél a CSV kódolás/tizedesjel/sortörés hibái garantáltak. |

**Új wiki lap:** `docs/wiki/MIGRATION-AND-IMPORT.md` — a `data/import/maps.php` leképezések, a commandok, a
mezőnkénti tulajdonos, a kép-delta és a `contents` manifest folyamat, a `bin/import-db`, és a csapdák
(a cache flush console futás után, a statikus memoizáció, a duplikált Drupal rekordok).

**Frissítendő lapok** (a `covers:` és `verified:` fejléc is, ADR 0026 / `./bin/wiki-stale`):

- `STORAGE-AND-LOCATIONS.md` — az Occupancy szakasz újraírva a felülbírálásra; a `README.md` és az
  `adr/README.md` indexek. **Itt egy meglévő hibát is javítunk:** a lap azt írja, hogy az `Area` „a named
  size band … what the size dropdown filters by" — valójában az `Area` egy **helyszín szintje/zónája**
  (`locationId` + `name` + saját SVG térkép: Földszint / Emelet / Kültér), a méret-szűrő pedig a
  `StorageSearchService::sizeFacet()` / `sizeKey()` S/M/L/XL logikája, aminek nincs táblája.
- `SERVICES.md` — `AvailabilityService`, és hogy melyik service melyik lekérdezését adta át neki
- `RENTAL-AND-BILLING.md` — a `Rent::afterSave()` visszaállítja a raktárt `AUTO`-ra; a `rent` séma változatlan
- `CONSOLE.md` — az `import/*` commandok, és hogy a `LegacyController` innentől kizárólag történelmi
- `MEDIA.md` — a fájl alapú, `fid`-re kulcsolt fotóimport a `createFromUrl()` helyett
- `DOMAIN-MODEL.md` — `StorageAvailability`, a két új `storage` kolumna
- `DEVELOPMENT.md` — `bin/import-db`, a `drupal_src` / `r24_prod` / `r24_test` scratch DB-k

---

## Verification

- `import/report` a friss dumpokon ugyanazokat a számokat adja, mint a fenti mérés.
- **A teljes lánc kétszer lefuttatva az éles másolaton: a második futás no-op** — `mysqldump` diff csak
  timestampekben tér el.
- A lánc után az éles másolaton, SQL-lel:
  - `SUM(translations <> '{}')` egyik táblán sem csökken
  - `svgNodeId`, `areaId`, `slug`, `shortName`, `name` egyetlen meglévő rekordon sem változik
  - a raktárárak megegyeznek a Drupal `field_*_honapra_amount` értékeivel
  - a property definíciók id-jei nem mozdulnak (`env.php` `APP_STORAGE_TYPE_INDOOR_ID` / `_OUTDOOR_ID` stabil)
- **A foglaltság mind a hat helyen egyezik.** Egy `OCCUPIED`-ra állított raktár foglaltként jelenik meg:
  a keresőlistán, a helyszínkártya szabad-darabszámában, az SVG térkép színén, a raktároldal naptárában,
  az admin grid státuszpontján és az ERP áttekintőben. Egy lejárt `availabilityUntil` mind a hatban
  automatikusra vált.
- `payments/generate-by-rents` **változatlan**, a foglaltság nem hoz létre payment-et — futás után nulla új
  payment.
- Validáció: élő bérléssel rendelkező raktárra nem beállítható manuális foglaltság; bérlés mentése után a
  raktár `AUTO`-ra áll.
- Excel: mindkét munkalap megnyílik Excelben; minden cellatípusból egy kézi módosítás visszajön az
  importeren; egy szándékosan elrontott cella olvasható hibával elutasításra kerül.
- Képek: a `web/contents/` delta feltöltés után csak új fájl kerül fel, meglévő nem íródik felül.
- `composer test` a `phpoffice/phpspreadsheet` felvétele után; unit teszt az `AvailabilityService::state()`-re,
  az intervallum-kódolóra és a property-cella validátorra (mind külső függőség nélkül tesztelhető — ADR 0023).

## Menetrend

**Ma mind a nyolc fázis** elkészül, hogy legyen min tesztelni és csiszolni. Holnap a lezárt, friss dumppal
már csak lefut a kész tooling. A fázisok után megállok review-ra (`WORKFLOW.md` 3. pont), commit csak
review után.

Nálad van: az `rsync` a `data/import/files/`-be, és a Dera Park SVG alaprajz + a raktárak hozzárendelése
(a helyszín és a 16 raktár az importból létrejön, a `svgMapId` és a `svgNodeId`-k rád várnak).

---

## Status

**2026-09-07** — Terv elfogadva, 1. fázis indul. A két dump be van töltve a lokális MariaDB-be
(`drupal_src`, `r24_prod`), a fenti mérési eredmények innen származnak.

**1–2. fázis kész.** `data/import/maps.php`, `bin/import-db`, `m260907_100000_location_legacy_keys`,
`services/LegacySourceService.php`, `services/ImportDiffService.php`, `commands/ImportController.php`
(`import/report`). Új env konstans: **`LEGACY_DB_NAME`** (`env.php` + `env.example.php`).

A report a mérésekkel egyezik: 32 új / 438 módosult (mind ár) / 22 eltűnt / 16 nem routolható raktár,
12 új és **0 módosult** hír (a fordítások érintetlenek), 1 új property opció, 1 új raktártípus node
(„Autótárolás"), 312 foglalt raktár ma. Két új adatminőségi lelet, amit nem tudtunk előre: **három**
duplikált BP VAR egység (649/731, 650/726, 706/711), és 7 szóközzel kezdődő/végződő cím.

**3. fázis kész.** `storage.availability` + `availabilityUntil` (migráció `m260907_120000`),
`StorageAvailability` vocabulary, `AvailabilityService` (3 cache-elt lekérdezés / kérés), a hat hívási hely
átvezetve, validáció + `Rent::afterSave()` visszaállítás, admin dropdown + dátum, 22 új unit teszt.
Mellékesen kiderült egy meglévő bug: az admin grid `getStatusSelect()` a `CURRENT_DATE <= dateTo` miatt
**szabadnak** mutatta a határozatlan idejű bérléssel foglalt raktárt — javítva.

**4. fázis kész.** `import/catalogue`, `--dryRun`-nal. Egy éles futás után:
48 új raktár, 438 frissítve (mind ár), 7 depublikálva, 12 új hír, 1 új helyszín (SZTE DRP), 1 új
raktártípus (publikálatlan), 1 új property opció. **A második futás no-op.** A 468 korábbi raktáron
`slug`, `name`, `svgNodeId`, `areaId`, `nameInContract`, `translations` és a flagek **bitre azonosak**;
a helyszínek és az 55 régi hír érintetlenek; a property értékekből pontosan 3 mozdult (a Drupalban tényleg
megváltozott 2 width + 1 length).

Két hiba, amit a futás hozott felszínre és javítva van:
- a `Storage::getValues()` process-szintű statikus memója (CONSOLE.md csapda) miatt az
  `updateComputedProperties()` nullázta az új raktárak `area`/`volume`-ját → külön `writeDimensions()` pass
  `updateAll()`-lal;
- ez a pass a Drupalból eltűnt 22 raktár méretét is nullázta → ezeket kihagyja, a méret marad.

**5–8. fázis kész.** `import/photos` (fájl alapú, (owner, fid) kulcs, a delta 59 fájl),
`import/contents-manifest` (sha1 manifest + delta + tar parancs), `import/occupancy` (326 felülbírálás,
`--reset`, `--gap`, CSV a 68 párosítható megrendelésről), `import/export-xlsx` / `import/import-xlsx`
(`phpoffice/phpspreadsheet`, 3 munkalap, 47+47 oszlop, all-or-nothing import), dokumentáció
(ADR 0033–0036, `docs/wiki/MIGRATION-AND-IMPORT.md`, 7 lap frissítve).

Egy további hiba, amit a futás hozott felszínre: az xlsx import után a `volume` nem frissült a magasság
módosítása után — ugyanaz a statikus memó csapda, mint a katalógusnál. Külön `refreshDimensions()`.

### Ami még hátravan (nem kód)

- **A `verified: HEAD` fejlécek.** Nyolc wiki lap `verified:` sora `HEAD`-en áll, mert még nincs commit.
  A dokumentációs commit után a tényleges hash-re kell állítani, különben a `bin/wiki-stale` soha nem jelez:
  `sed -i 's/verified: HEAD/verified: <hash>/' docs/wiki/*.md`
- **`composer.lock`** frissült (`phpoffice/phpspreadsheet` + 5 tranzitív csomag) → a szerveren
  `composer install --no-dev --optimize-autoloader` kell.
- **`env.php` + `env.example.php`**: új `LEGACY_DB_NAME` konstans. A szerveren üresen kell hagyni.
- ~~**`data/import/files/`**: a `sites/default/files` rsync-je~~ — **nem kell.** Az `import/photos` a
  lokális fát preferálja, de amit nem talál, azt letölti az élő oldalról **bele a lokális fába**, tehát a
  crawl inkrementálisan felépíti azt, amit az rsync adott volna. Mérve: 102 fotó / 63 különböző fájl /
  41 MB / 32 s. `--localOnly=1` tiltja a fallbacket. Az rsync annyit ér, hogy a régi oldal lekapcsolása
  után is működik.
- **Dera Park SVG alaprajz** és a 16 raktár `svgNodeId`-je (nálad). A helyszín publikálatlanul létrejött.

**Utólagos ellenőrzés (a te engedélyeddel).** `bin/import-db reset --force` + `restore-dev` letesztelve.
Az éles másolatról indított teljes lánc (migrate → catalogue → occupancy → search-index) ugyanazokat a
számokat adta, a **második futás utáni két `mysqldump` különbsége mindössze a `search_index.updatedAt`** —
azt a kétszeri `search-index/build` írta, az importerek semmit nem írtak újra.

**A képek crawl-lal futottak le élesben**: 63 fájl letöltve, 102 fotó létrejött, 0 hiba, a második futás
no-op. A `web/contents/`-ben **102 új fájl, mind a `2026/09/07/` mappában**, és a fán kívül **nulla** fájl
változott — tehát a feltöltés egyetlen mappa `tar`-ja (41 MB). Az `import/contents-manifest` innentől
ellenőrzés, nem szállítás.

---

## 2026-09-08 — az éles migráció lefutott

Friss Drupal dump (szept 8. 13:20, 325 MB) betöltve, az app DB visszaállítva az érintetlen éles másolatra
(`bin/import-db reset`), majd a teljes lánc.

| | |
|---|---|
| migráció | 2 (`location_legacy_keys`, `storage_availability`) |
| új raktár | 48 |
| frissített raktár | 438 (mind ár) |
| depublikált raktár | 7 (eltűnt a Drupalból) |
| új hír | 12 |
| új helyszín | 1 — SZTE DRP, **publikálatlan** |
| új raktártípus | 1 — Autótárolás, **publikálatlan** |
| új property opció | 1 |
| fotó | 102 létrejött, 0 letöltés (a 63 forrásfájl tegnapról a cache-ben) |
| foglaltság | 326 felülbírálás, 1 kihagyva (BP DOH 02 — valódi bérlés) |
| végállapot | 516 raktár / 491 publikált / 326 foglalt, 16 helyszín, 67 hír, 907 fotó |

**A második futás bitre azonos dumpot adott** — a `mysqldump` diff üres.

Az érintetlen éles másolathoz mérve a 468 korábbi raktáron `slug`, `name`, `svgNodeId`, `areaId`,
`nameInContract`, `translations` és a kiemelés-flagek **nem változtak**; a 15 helyszín és az 55 régi hír
szintén érintetlen. A 494 raktár mind a négy ára egyezik a Drupallal.

A Drupalban tegnap óta négy raktárt módosítottak (254, 98, 232, 224) — a naptárukat; az importált mezőket
nem érintette. Az adatminőségi lista **változatlan**: a duplikátumok, a szóközös címek, az elírás és a
publikálatlan egységek nincsenek javítva.

### A szerverre kerülő fájlok (`data/import/out/`)

| Fájl | Mi |
|---|---|
| `raktar24-db-2026-09-08.sql.gz` | 743 KB — a teljes DB, próbaimporttal ellenőrizve |
| `contents-2026-09-08.tar.gz` | 41 MB — a 102 új kép, mind a `2026/09/08/` mappában |
| `raktar24-2026-09-08.xlsx` | a megbízónak: 16 helyszín × 47, 516 raktár × 47 oszlop |
| `occupancy-orders.csv` | a 66 párosítható foglalás megrendeléssel |
| `report-2026-09-08.txt` | a diff riport és az adatminőségi lista |

A tegnapi futás `web/contents/2026/09/07` mappája a reset után árva lett, törölve.
