céges meghívó kezelése,

This commit is contained in:
Roo
2026-06-17 22:07:55 +00:00
parent bf3a971ff1
commit 127b130401
28 changed files with 5806 additions and 1313 deletions

View File

@@ -0,0 +1,168 @@
# 🏗️ Logic Spec: P1 Garage Selector & OrganizationMember Audit
## 1. Modul Célja és Masterbook 2.0 Illeszkedés
**Cél:** A Dashboard "Garage Selector" (cégválasztó) pontosítása, hogy a bejelentkezett felhasználó:
- a) Szerepeljen azon Organization rekordok között, ahol technikai tulajdonos (`owner_id`)
- b) Szerepeljen azon Organization rekordok között, ahol aktív `OrganizationMember`
Emellett az `OrganizationMember` tábla és a meghívási folyamat teljes auditja.
**Masterbook 2.0 illeszkedés:** B2B szervezetkezelés (11-es Epic), B2B szerepkörök és szervezeti hierarchia.
---
## 2. Adatmodell Elemzés
### 2.1 OrganizationMember tábla (aktuális séma)
| Oszlop | Típus | Kötelező | Megjegyzés |
|--------|-------|----------|------------|
| `id` | integer PK | YES | Auto-increment |
| `organization_id` | integer FK -> fleet.organizations.id | YES | |
| `user_id` | integer FK -> identity.users.id | NO | Lehet NULL (pending invite) |
| `person_id` | bigint FK -> identity.persons.id | NO | |
| `role` | ENUM (OrgUserRole) | YES | OWNER, ADMIN, FLEET_MANAGER, DRIVER, MECHANIC, RECEPTIONIST |
| `permissions` | JSONB | NO (default `{}`) | |
| `is_permanent` | boolean | NO (default false) | |
| `is_verified` | boolean | NO (default false) | |
### 2.2 🔴 HIÁNYZÓ OSZLOPOK (Kritikus)
1. **`status` VARCHAR** - A kód használja (`status="pending"`, `status="active"` a organizations.py:421,481 sorokban), **de az adatbázisban NINCS ilyen oszlop!** Ez futási hibát okoz az invite/join folyamatokban.
2. **`joined_at` TIMESTAMP** - Nincs nyomon követve, mikor csatlakozott a tag.
3. **`expires_at` / `valid_until` TIMESTAMP** - Nincs lejárati dátum (pl. ideiglenes hozzáféréshez).
4. **`created_at` TIMESTAMP** - Hiányzik a tag rekord létrehozási dátuma.
5. **`updated_at` TIMESTAMP** - Hiányzik a módosítás dátuma.
### 2.3 🟢 MEGLÉVŐ, HELYESEN MŰKÖDŐ
- `role` ENUM - Jól definiált, 6 szerepkörrel.
- `user_id` + `person_id` kettős hivatkozás - Támogatja a "Dual Entity" modellt.
- `is_permanent` - Alkalmas az állandó vs. ideiglenes tagság megkülönböztetésére.
---
## 3. Backend Módosítás: `get_my_organizations` Fix
### 3.1 Probléma
Jelenleg a `GET /api/v1/organizations/my` lekérdezés (`organizations.py:192`) csak `INNER JOIN`-t használ az `OrganizationMember` táblával. Ez kizárja azokat az eseteket, ahol a felhasználó `owner_id` (technikai tulajdonos), de az `OrganizationMember` rekord nem található.
### 3.2 Megoldás
Használjunk `LEFT JOIN`-t `OR` feltétellel:
```python
from sqlalchemy import or_
stmt = (
select(Organization)
.outerjoin(OrganizationMember, OrganizationMember.organization_id == Organization.id)
.where(
or_(
Organization.owner_id == current_user.id,
OrganizationMember.user_id == current_user.id
)
)
.where(Organization.org_type.notin_([OrgType.service_provider, OrgType.service]))
.where(Organization.is_deleted == False)
.distinct()
)
```
### 3.3 Válasz bővítése
A frontend számára szükséges a `user_role` mező:
```python
user_role = None
for member in o.members:
if member.user_id == current_user.id:
user_role = member.role.value if hasattr(member.role, 'value') else str(member.role)
break
```
---
## 4. Frontend Módosítások
### 4.1 HeaderCompanySwitcher.vue - Jelenlegi állapot
A komponens már használja az `authStore.myOrganizations` adatokat, és szűri a `companyOrganizations` computed property-ben az `individual`, `service_provider`, `service` típusokat. Ez rendben van.
### 4.2 Szükséges változtatások
1. **Nincs változtatás szükséges** a frontend logikában - a `fetchMyOrganizations()` már meghívja a `/organizations/my` végpontot, és a `companyOrganizations` computed property megfelelően szűr.
2. **OrganizationItem típus** (`frontend/src/types/organization.ts`) - már tartalmazza a `user_role` mezőt (nem kötelező).
### 4.3 Aktív szervezet kiválasztás
A `switchOrganization` metódus (`frontend/src/stores/auth.ts:631`) már implementálva van, a `PATCH /users/me/active-organization` hívással. A frontend globális állapota frissül a `user.value.active_organization_id` mezővel.
---
## 5. Audit Jelentés: OrganizationMember & Invite Flow
### 5.1 Meglévő API Végpontok
| Végpont | Metódus | Státusz | Leírás |
|---------|---------|---------|--------|
| POST /organizations/{org_id}/invitations | POST | Letezik | Meghívó küldése email címre |
| POST /organizations/invitations/{token}/accept | POST | Letevezik | Meghívó elfogadása token alapján |
| POST /organizations/{org_id}/join-request | POST | Letevezik | Csatlakozási kérelem (ha van admin) |
| POST /organizations/{org_id}/claim/request | POST | Letevezik | Árva cég átvételi kérelem |
| POST /organizations/{org_id}/claim/verify | POST | Letevezik | Árva cég átvétel OTP-vel |
### 5.2 🔴 HIÁNYZÓ VÉGPONTOK (Kritikus)
| Végpont | Hiány | Hatás |
|---------|-------|-------|
| GET /organizations/{org_id}/members | Nincs | Nincs lehetőség a tagok listázására |
| PATCH /organizations/{org_id}/members/{member_id}/role | Nincs | Nincs lehetőség a szerepkör módosítására |
| DELETE /organizations/{org_id}/members/{member_id} | Nincs | Nincs lehetőség a tag eltávolítására |
| GET /organizations/{org_id}/invitations | Nincs | Nincs lehetőség a függő meghívók listázására |
| DELETE /organizations/{org_id}/invitations/{invitation_id} | Nincs | Nincs lehetőség a meghívó visszavonására |
### 5.3 🔴 Kritikus Adatbázis Probléma
Az `OrganizationMember` modellben és az adatbázisban **HIÁNYZIK** a `status` oszlop, de a kód (`organizations.py:421` és `organizations.py:481`) hivatkozik rá (`status="pending"`, `status="active"`). Ez a meghívási folyamatban futási hibát okoz!
### 5.4 Meghívási Folyamat Ábrája
```
OWNER/ADMIN -> POST /invitations -> Van-e user?
|-> Igen -> Letrehoz OrganizationMember (user_id=target)
|-> Nem -> Letrehoz VerificationToken (token_type=org_invite)
|-> Email ertesites
|
Regisztracio utan -> POST /invitations/{token}/accept
-> Token validalas
-> Letezik-e mar tag?
|-> Igen -> Frissiti a szerepkört
|-> Nem -> Letrehozza a tag rekordot
-> Done
```
### 5.5 Javasolt Javítási Sorrend
1. **P0 - AZONNAL:** `status` oszlop hozzáadása az `OrganizationMember` modellhez és az adatbázishoz (sync_engine).
2. **P0 - AZONNAL:** Dátum oszlopok (`created_at`, `updated_at`) hozzáadása.
3. **P1 - JELEN FELADAT:** `get_my_organizations` javítása `OR` logikára.
4. **P2 - KOVETKEZO:** Tagkezelő API végpontok implementálása (list, role change, remove).
5. **P2 - KOVETKEZO:** Meghívókezelő API végpontok (list pending, revoke).
---
## 6. Végrehajtási Terv
### 6.1 Backend változtatások (Code mód)
1. `backend/app/models/marketplace/organization.py` - `OrganizationMember` modellbe hozzáadni: `status`, `created_at`, `updated_at`
2. `backend/app/api/v1/endpoints/organizations.py` - `get_my_organizations` query javítása `OR` logikára + `DISTINCT` + `user_role` visszaadása
### 6.2 Frontend változtatások (Code mód)
- Nincs szükség változtatásra - a frontend már helyesen használja az API-t.
### 6.3 Adatbázis szinkron
- Futtatni: `docker exec sf_api python3 -m app.scripts.sync_engine`
---
## 7. Jóváhagyás
**Kérem a felhasználó jóváhagyását a fenti tervhez!** A jóváhagyás után:
1. Létrehozom a Gitea feladatkártyákat
2. Váltok Code módba a megvalósításhoz

View File

@@ -0,0 +1,179 @@
# 🐛 logic_spec: Quick-add provider cégek megjelennek a "Cégeim" menüben
**Kártya:** #266
**Státusz:** Tervezés kész, kivitelezés előtt
**Architect:** Service Finder Rendszer-Architect
---
## 1. Probléma összefoglaló
A gyors szolgáltató felvétel (`quick_add_provider`) során létrejövő `service_provider` típusú szervezetek három problémát okoznak:
| # | Probléma | Hatás |
|---|----------|-------|
| 1 | **"Cégeim" menüben való megjelenés** | A `service_provider` típusú szervezetek megjelennek a felhasználó saját cégei között |
| 2 | **Tulajdonos hiánya** | A `GET /my` response nem adja vissza az `owner_id` mezőt; új provider-eknél `owner_id=null` lehet |
| 3 | **Státusz megjelenítés** | Bár a DB-ben `status='pending_verification'`, a UI mást mutathat |
---
## 2. Érintett fájlok
### Backend
- `backend/app/api/v1/endpoints/organizations.py:177``GET /my` lekérdezés
- `backend/app/services/provider_service.py:468``quick_add_provider()` függvény
### Frontend
- `frontend/src/components/header/HeaderCompanySwitcher.vue:142``companyOrganizations` szűrő
- `frontend/src/stores/auth.ts:589``fetchMyOrganizations()` hívás
---
## 3. Hibaelemzés részletesen
### 3.1. `GET /my` endpoint (`organizations.py:183-186`)
```python
stmt = (
select(Organization)
.join(OrganizationMember)
.where(OrganizationMember.user_id == current_user.id)
)
```
**Hiba:** Nincs `org_type` szűrés. Minden szervezetet visszaad, ahol a user tag.
**Response (193-206. sor):** Nem adja vissza az `owner_id` mezőt.
### 3.2. `HeaderCompanySwitcher.vue:142-146`
```typescript
const companyOrganizations = computed(() => {
return authStore.myOrganizations.filter(
(org) => org.org_type && org.org_type !== 'individual'
)
})
```
**Hiba:** Csak az `individual` típust szűri ki. A `service_provider`, `service` típusú szervezetek átmennek a szűrőn.
### 3.3. `quick_add_provider()` (`provider_service.py:521-646`)
- Létrehoz egy `Organization`-t `org_type='service_provider'`-rel (526. sor)
- Beállítja `owner_id=user_id` (541. sor)
- **Létrehoz `OrganizationMember`-et `role=OWNER`-rel** (638-646. sor) → ez miatt a `GET /my` visszaadja a szervezetet
### 3.4. Adatbázis státusz
```sql
SELECT column_default FROM information_schema.columns
WHERE table_schema='fleet' AND table_name='organizations' AND column_name='status';
-- Result: 'pending_verification'::character varying
```
A DB default helyes. A service_providerek `status='pending_verification'`, `is_verified=false`.
---
## 4. Javítási terv
### 4.1. Backend: `GET /my` endpoint szűrés
**Fájl:** `backend/app/api/v1/endpoints/organizations.py:177`
**Módosítás:** Adjunk hozzá `org_type` szűrést, hogy csak a valódi céges típusok (`business`, `fleet_owner`, `individual`) jelenjenek meg:
```python
stmt = (
select(Organization)
.join(OrganizationMember)
.where(OrganizationMember.user_id == current_user.id)
.where(Organization.org_type.in_([OrgType.business, OrgType.fleet_owner, OrgType.individual]))
)
```
**Alternatíva:** Szűrjük ki a `service_provider` és `service` típusokat:
```python
.where(Organization.org_type.notin_([OrgType.service_provider, OrgType.service]))
```
### 4.2. Frontend: `HeaderCompanySwitcher` szűrés (biztonsági réteg)
**Fájl:** `frontend/src/components/header/HeaderCompanySwitcher.vue:142`
**Módosítás:** Szűrjük ki a `service_provider` és `service` típusokat is:
```typescript
const companyOrganizations = computed(() => {
return authStore.myOrganizations.filter(
(org) => org.org_type &&
org.org_type !== 'individual' &&
org.org_type !== 'service_provider' &&
org.org_type !== 'service'
)
})
```
### 4.3. Backend: `GET /my` response kiegészítése
**Fájl:** `backend/app/api/v1/endpoints/organizations.py:193`
**Módosítás:** Adjuk hozzá az `owner_id` mezőt a response-hoz:
```python
return [
{
"organization_id": o.id,
"owner_id": o.owner_id, # NEW
"status": o.status,
# ... existing fields
}
for o in orgs
]
```
### 4.4. Backend: `quick_add_provider` OrganizationMember létrehozásának felülvizsgálata
**Fájl:** `backend/app/services/provider_service.py:632`
**Megfontolandó:** A `quick_add_provider()` létrehozza az `OrganizationMember`-et `role=OWNER`-rel. Ez lehetővé teszi a user számára a provider szerkesztését (access control miatt), de emiatt a provider megjelenik a "Cégeim" listában.
**Megoldás:** Ha a 4.1-es és 4.2-es javítások életbe lépnek, akkor a service_provider típusú szervezetek nem fognak megjelenni a "Cégeim" listában, így az OrganizationMember létrehozása továbbra is működhet az access control miatt.
### 4.5. Adatbázis: hiányzó `owner_id`-k pótlása
**Megfontolandó:** A régebbi service_providerek, amik robot által vagy más úton jöttek létre és nincs `owner_id`-jük, kapjanak alapértelmezett ownershipet vagy maradjanak owner nélkül.
---
## 5. Masterbook 2.0 illeszkedés
| Elv | Illeszkedés |
|-----|-------------|
| **Dual Entity** | Az Organization (cég) és a User (technikai fiók) szétválasztása helyes. A `service_provider` típusú szervezeteket nem szabad a user saját cégeként kezelni. |
| **DDD Szeparáció** | A `marketplace` domain (service_provider) és a `fleet` domain (business/fleet_owner) adatai nem keveredhetnek a UI-n. |
| **Access Control** | Az OrganizationMember létrehozása továbbra is szükséges a provider szerkesztéséhez, de a UI-n való megjelenítést megfelelően kell szűrni. |
---
## 6. Tesztelési terv
1. **Unit teszt:** `GET /my` endpoint hívása service_provider típusú szervezettel → nem jelenik meg
2. **Integrációs teszt:** quick-add provider létrehozása → nem jelenik meg a Cégeim listában
3. **Frontend teszt:** HeaderCompanySwitcher megjelenítése service_provider típusú org-gal → nem jelenik meg
4. **Adatbázis teszt:** service_provider rekordok `status` és `owner_id` ellenőrzése
---
## 7. Jóváhagyási pont
A fenti terv alapján a javítás kivitelezéséhez az alábbi módosítások szükségesek:
- [ ] `organizations.py`: `GET /my` endpoint szűrés `org_type` alapján
- [ ] `organizations.py`: `owner_id` hozzáadása a response-hoz
- [ ] `HeaderCompanySwitcher.vue`: `service_provider` és `service` kiszűrése
- [ ] Adatbázis audit: hiányzó `owner_id`-k ellenőrzése
**Architect jóváhagyása:** ⏳ Függőben

View File

@@ -1,123 +1,90 @@
# 🔧 Logic Spec: Provider Update & Search Fix Csomag
# 🔧 Fix Plan: Provider Update 500 Error (folder_slug Truncation)
## 🎯 Cél
A szolgáltató cégek adatainak rögzítésében és szerkesztésében fellépő hibák javítása, valamint az adatok részletes rögzíthetőségének biztosítása.
A `PUT /api/v1/providers/{id}` végpont által dobott 500-as hiba kijavítása, amikor a felhasználó egy olyan szolgáltató adatait szerkeszti, amely még **nincs** átmigrálva az `Organization` táblába (csak `ServiceStaging`-ben létezik).
## 📋 Problémák Összefoglalása
## 🔍 Root Cause Analysis
### 1. PUT /api/v1/providers/{id} → 404 Not Found (✅ MEGOLDVA)
- **Root Cause**: A `sf_api` konténer nem volt újraindítva a providers modul kódfrissítése után. A `pre_start.sh` fájlban `uvicorn` `--reload` flag nélkül fut.
- **Javítás**: `docker compose restart sf_api` (végrehajtva)
- **Verifikáció**: PUT /providers/58 → 200 OK
### Hibajelenség
`PUT /api/v1/providers/4859``500 Internal Server Error`
```
value too long for type character varying(12)
```
### 2. GET /api/v1/providers/search → 500 Internal Server Error (❌ JAVÍTANDÓ)
- **Hiba**: `AttributeError: Neither 'BinaryExpression' object nor 'Comparator' object has an attribute 'astext'`
- **Hibás kód**: [`provider_service.py:207`](../backend/app/services/provider_service.py:207)
```python
(Organization.external_integration_config["source"].astext == "crowdsourced", literal("crowd_added")),
```
- **Root Cause**: Az [`external_integration_config`](../backend/app/models/marketplace/organization.py:109) `JSON` típusú oszlop. A `["source"]` subscript `BinaryExpression`-t ad vissza, amelyen NINCS `.astext`.
- **Javítás**: `cast` használata:
```python
(cast(Organization.external_integration_config["source"], String) == "crowdsourced", literal("crowd_added")),
```
### Kiváltó ok
A provider ID=4859 a `marketplace.service_staging` táblában létezik, de **nincs** még `fleet.organizations` rekordja.
### 3. Adat-healing: Régi címformátumú Organization rekordok (❌ JAVÍTANDÓ)
- **Probléma**: Az Organization id=58 (`Autónyíri Kft.`) adatai a régi formátumban:
- `street_name = "Egressy u. 4."` (nem atomizált)
- `address_street_name = NULL`, `address_street_type = NULL`, `address_house_number = NULL`
- `zip = NULL`
- **Következmény**: A frontend DetailModal üres címet mutat.
- **Javítás**: Adat-healing script a `street_name` mezőből atomizált komponensek kinyerésére.
Az [`update_provider()`](backend/app/services/provider_service.py:786) függvény a migrációs ágon (ServiceStaging → Organization) a `folder_slug`-ot az alábbi képlettel generálja:
### 4. Multi-source Update Probléma (❌ JAVÍTANDÓ)
- **Probléma**: Az [`update_provider`](../backend/app/services/provider_service.py:477) CSAK `Organization`-ben keres. A search UNION-nal dolgozza fel a szolgáltatókat 3 forrásból (Organization, ServiceStaging, ServiceProvider).
- **Javítás**: Multi-source update logika.
### 5. Szerver Restart Workflow Hiánya (❌ JAVÍTANDÓ)
- **Probléma**: A [`pre_start.sh`](../backend/app/scripts/pre_start.sh) `--reload` nélkül indul.
- **Javítás**: `--reload` flag fejlesztői módban.
---
## 🗺️ Érintett Fájlok
| Fájl | Változtatás | Prioritás |
|------|-------------|-----------|
| [`provider_service.py:207`](../backend/app/services/provider_service.py:207) | `.astext` → `cast()` | **KRITIKUS** |
| [`provider_service.py:477-562`](../backend/app/services/provider_service.py:477) | Multi-source update | **MAGAS** |
| Új: `heal_provider_addresses.py` | Adat-healing script | **MAGAS** |
| [`pre_start.sh`](../backend/app/scripts/pre_start.sh) | `--reload` dev módban | **ALACSONY** |
---
## 🛠️ Részletes Javítási Terv
### 1. `.astext` → `cast()` javítás
**Fájl**: [`provider_service.py`](../backend/app/services/provider_service.py:207)
**Jelenlegi:**
```python
(Organization.external_integration_config["source"].astext == "crowdsourced", literal("crowd_added")),
folder_slug = f"sp-{staging.id}-{uuid.uuid4().hex[:6]}",
```
**Javított:**
- `"sp-"` = 3 karakter
- `"4859"` = 4 karakter (provider_id hossza)
- `"-"` = 1 karakter
- `"4d45d3"` = 6 karakter (uuid hex)
- **Összesen: 14 karakter**
Az [`Organization`](backend/app/models/marketplace/organization.py:75) modellben a `folder_slug` mező:
```python
(cast(Organization.external_integration_config["source"], String) == "crowdsourced", literal("crowd_added")),
folder_slug: Mapped[str] = mapped_column(String(12), unique=True, index=True)
```
**Csak 12 karaktert enged!** → PostgreSQL `StringDataRightTruncationError`.
**Ugyanez a hiba** a crowd-sourced migrációs ágon is (line 820):
```python
folder_slug = f"cr-{crowd.id}-{uuid.uuid4().hex[:6]}",
```
### 2. Multi-source Update Logika
### Összehasonlítás
- **`quick_add_provider`** (line 501-503): `hashlib.md5(...).hexdigest()[:12]` → pontosan 12 karakter → **MŰKÖDIK**
- **`update_provider` migration** (line 786, 820): `"sp-{id}-{hex}"` / `"cr-{id}-{hex}"`**TÚL HOSSZÚ**
**Fájl**: [`provider_service.py:477`](../backend/app/services/provider_service.py:477)
## 📋 Javítási Terv
Az `update_provider` ellenőrizze mindhárom forrást:
1. `db.get(Organization, provider_id)` → meglévő logika
2. `db.get(ServiceStaging, provider_id)` → migrálás Organization-be
3. `db.get(ServiceProvider, provider_id)` → migrálás Organization-be
4. Ha egyikben sem → ValueError
### 1. Model fix: [`backend/app/models/marketplace/organization.py`](backend/app/models/marketplace/organization.py:75)
**Változtatás:** `folder_slug` oszlop méretének növelése `String(12)``String(24)`
### 3. Adat-healing Script
Indoklás:
- A jelenlegi 12 karakter túl szűk
- A `quick_add_provider` pontosan 12 karaktert használ → kompatibilis
- A migrációs slug-ok (pl. `sp-{id}-{hex6}`) elférnek
- A `unique=True` megszorítás megmarad
**Fájl**: `backend/app/scripts/heal_provider_addresses.py`
### 2. Service fix: [`backend/app/services/provider_service.py`](backend/app/services/provider_service.py:786)
**Változtatás:** A migrációs `folder_slug` generálás módosítása konzisztens `hashlib.md5` alapú generálásra.
1. Lekérdezni Organization rekordokat, ahol `org_type='service_provider'` ÉS `address_street_name IS NULL` ÉS `street_name IS NOT NULL`
2. Regex: `r'^([^\d]+?)\s+(u\.|utca|út|tér|köz|sor|körút|liget|part|fasor|sétány|park|híd|sugárút|rakpart|dűlő|telep|szőlő)\s*(.*)$'`
3. Frissíteni az atomizált mezőket
4. Naplózás
**Staging ág (line 786):**
```python
# EZT:
folder_slug = f"sp-{staging.id}-{uuid.uuid4().hex[:6]}"
# HELYETTE:
folder_slug = hashlib.md5(f"sp-{staging.id}-{uuid.uuid4()}".encode()).hexdigest()[:12]
```
---
**Crowd ág (line 820):**
```python
# EZT:
folder_slug = f"cr-{crowd.id}-{uuid.uuid4().hex[:6]}"
# HELYETTE:
folder_slug = hashlib.md5(f"cr-{crowd.id}-{uuid.uuid4()}".encode()).hexdigest()[:12]
```
## 🧪 Tesztelési Terv
Indoklás:
- Konzisztens a `quick_add_provider` által használt módszerrel
- Mindig pontosan 12 karakter → garantáltan elfér a `String(12)` mezőben is
- Elég nagy entrópia az egyediséghez (`hashlib.md5(...)` → 32 hex, `[:12]` → 48 bit)
### Teszt 1: search működés
### 3. Adatbázis szinkronizáció
Mivel az oszlop mérete változik (`String(12)``String(24)`), futtatni kell:
```bash
docker compose exec sf_api python3 -c "
import httpx, asyncio
async def t():
async with httpx.AsyncClient(base_url='http://localhost:8000') as c:
r = await c.post('/api/v1/auth/login', data={'username': 'admin@profibot.hu', 'password': 'Admin123!'})
t = r.json()['access_token']
r2 = await c.get('/api/v1/providers/search', params={'q': 'Dunakeszi', 'limit': 5}, headers={'Authorization': f'Bearer {t}'})
print(f'Search: {r2.status_code}')
print(r2.text[:500])
asyncio.run(t())
"
docker exec sf_api python -m app.scripts.sync_engine
```
### Teszt 2: PUT működés
```bash
docker compose exec sf_api python3 -c "
import httpx, asyncio
async def t():
async with httpx.AsyncClient(base_url='http://localhost:8000') as c:
r = await c.post('/api/v1/auth/login', data={'username': 'admin@profibot.hu', 'password': 'Admin123!'})
t = r.json()['access_token']
r2 = await c.put('/api/v1/providers/58',
json={'address_zip': '2120', 'city': 'Dunakeszi', 'name': 'Autónyíri Kft.'},
headers={'Authorization': f'Bearer {t}'})
print(f'PUT: {r2.status_code}')
print(r2.text)
asyncio.run(t())
"
```
## ✅ Elfogadási kritériumok
1. `PUT /api/v1/providers/4859` sikeresen lefut (200 OK)
2. A migrált Organization `folder_slug` pontosan 12 karakter hosszú
3. Meglévő provider-ek (`quick_add`-ból) továbbra is működnek
4. A `folder_slug` `unique` megszorítás nem sérül
5. A logokban nincs `StringDataRightTruncationError`