céges meghívó kezelése,
This commit is contained in:
168
plans/logic_spec_garage_selector_member_audit.md
Normal file
168
plans/logic_spec_garage_selector_member_audit.md
Normal 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
|
||||
179
plans/logic_spec_provider_cegeim_bugfix.md
Normal file
179
plans/logic_spec_provider_cegeim_bugfix.md
Normal 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
|
||||
@@ -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`
|
||||
|
||||
Reference in New Issue
Block a user