2026.03.30 front és garázs logika

This commit is contained in:
Roo
2026-03-30 06:32:22 +00:00
parent ba8b6579ef
commit 2508ae7452
108 changed files with 3184 additions and 115 deletions

View File

@@ -0,0 +1,211 @@
#!/usr/bin/env python3
"""
Garázs adatok ellenőrzése és javítása.
Ez a szkript ellenőrzi a teszt felhasználó szervezeti státuszát,
és felosztja a járműveket privát és céges flotta között.
Futtatás: docker compose exec sf_api python -m app.scripts.check_and_fix_garage_data
"""
import asyncio
import sys
from sqlalchemy import select, update
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload
# Add the backend directory to the path
sys.path.insert(0, '/app')
from app.db.session import AsyncSessionLocal
from app.models.identity import User
from app.models.marketplace.organization import Organization, OrganizationMember
from app.models.vehicle.asset import Asset
# AssetCatalog is inside the asset module
async def main():
"""Fő végrehajtási logika."""
print("=" * 60)
print("GARÁZS ADATOK ELLENŐRZÉSE ÉS JAVÍTÁSA")
print("=" * 60)
async with AsyncSessionLocal() as db:
# 1. Keressük meg a teszt felhasználót
print("\n1. TESZT FELHASZNÁLÓ KERESÉSE...")
stmt = select(User).where(User.email == "tester_pro@profibot.hu")
result = await db.execute(stmt)
test_user = result.scalar_one_or_none()
if not test_user:
print("❌ HIBA: A teszt felhasználó (tester_pro@profibot.hu) nem található!")
return
print(f" ✅ Teszt felhasználó megtalálva: ID={test_user.id}, Email={test_user.email}")
# 2. Ellenőrizzük, hogy a felhasználóhoz tartozik-e szervezet
print("\n2. SZERVEZETI TAGSÁG ELLENŐRZÉSE...")
org_stmt = (
select(Organization)
.join(OrganizationMember)
.where(OrganizationMember.user_id == test_user.id)
.where(Organization.is_deleted == False)
.where(Organization.is_active == True)
)
org_result = await db.execute(org_stmt)
user_organizations = org_result.scalars().all()
if user_organizations:
print(f" ✅ A felhasználó már tagja {len(user_organizations)} szervezetnek:")
for org in user_organizations:
print(f" - {org.name} (ID: {org.id}, Adószám: {org.tax_number})")
target_org = user_organizations[0]
else:
print(" A felhasználó nem tagja egyetlen szervezetnek sem. Új szervezet létrehozása...")
# Új szervezet létrehozása
new_org = Organization(
full_name="Teszt Flotta Kft.",
name="Teszt Flotta",
display_name="Teszt Flotta Kft.",
tax_number="12345678-2-42",
reg_number="01-23-456789",
country_code="HU",
language="hu",
default_currency="HUF",
address_zip="1234",
address_city="Budapest",
address_street_name="Teszt utca",
address_street_type="utca",
address_house_number="1",
folder_slug="teszt-flotta",
org_type="business",
status="active",
is_active=True,
is_deleted=False,
subscription_plan="FREE",
base_asset_limit=10,
owner_id=test_user.id
)
db.add(new_org)
await db.flush() # ID generáláshoz
await db.refresh(new_org)
# Szervezeti tagság létrehozása (ADMIN szerepkör)
org_member = OrganizationMember(
organization_id=new_org.id,
user_id=test_user.id,
role="ADMIN"
)
db.add(org_member)
await db.commit()
await db.refresh(new_org)
target_org = new_org
print(f" ✅ Új szervezet létrehozva: {target_org.name} (ID: {target_org.id})")
# 3. A felhasználó összes járművének lekérdezése
print("\n3. FELHASZNÁLÓ JÁRMŰVEINEK LEKÉRDEZÉSE...")
asset_stmt = (
select(Asset)
.where(Asset.owner_person_id == test_user.id)
.options(selectinload(Asset.catalog))
)
asset_result = await db.execute(asset_stmt)
user_assets = asset_result.scalars().all()
print(f" ✅ Összesen {len(user_assets)} jármű található a felhasználóhoz.")
if not user_assets:
print(" Nincsenek járművek a felhasználóhoz. Nincs mit felosztani.")
return
# 4. Járművek felosztása privát és céges között
print("\n4. JÁRMŰVEK FELOSZTÁSA PRIVÁT ÉS CÉGES FLOTTA KÖZÖTT...")
# Számoljuk meg, hány jármű van már privát és hány céges
private_count = 0
corporate_count = 0
for asset in user_assets:
if asset.owner_org_id is None:
private_count += 1
else:
corporate_count += 1
print(f" Jelenlegi állapot: {private_count} privát, {corporate_count} céges jármű")
# Ha minden jármű ugyanabban a kategóriában van, felosztjuk őket
if private_count == 0 or corporate_count == 0:
print(" Járművek újraelosztása 50-50% arányban...")
# Felosztás fele-fele arányban
half_index = len(user_assets) // 2
for i, asset in enumerate(user_assets):
if i < half_index:
# Első fele: maradjon privát (owner_org_id = None)
if asset.owner_org_id is not None:
asset.owner_org_id = None
print(f" 🚗 {asset.id}: Privát módra állítva")
else:
# Második fele: legyen céges (owner_org_id = target_org.id)
if asset.owner_org_id != target_org.id:
asset.owner_org_id = target_org.id
print(f" 🏢 {asset.id}: Céges flottához rendelve (Szervezet: {target_org.name})")
await db.commit()
print(f"{half_index} jármű privát, {len(user_assets) - half_index} jármű céges módra állítva.")
else:
print(" ✅ A járművek már megfelelően fel vannak osztva. Nincs szükség módosításra.")
# 5. Végeredmény összefoglaló
print("\n" + "=" * 60)
print("VÉGEREDMÉNY ÖSSZEFOGLALÓ")
print("=" * 60)
# Új lekérdezés a frissített adatokhoz
asset_result = await db.execute(asset_stmt)
user_assets = asset_result.scalars().all()
private_assets = [a for a in user_assets if a.owner_org_id is None]
corporate_assets = [a for a in user_assets if a.owner_org_id == target_org.id]
other_assets = [a for a in user_assets if a.owner_org_id not in [None, target_org.id]]
print(f"\n📊 TESZT FELHASZNÁLÓ ÁLLAPOTA:")
print(f" • Email: {test_user.email}")
print(f" • User ID: {test_user.id}")
print(f" • Aktív szervezet: {target_org.name} (ID: {target_org.id})")
print(f" • Szerepkör a szervezetben: ADMIN")
print(f"\n🚗 JÁRMŰVEGYÜTT ÁLLAPOTA:")
print(f" • Összes jármű: {len(user_assets)} db")
print(f" • Privát garázs (owner_org_id = NULL): {len(private_assets)} db")
print(f" • Céges flotta ({target_org.name}): {len(corporate_assets)} db")
if other_assets:
print(f" • Egyéb szervezetekhez rendelve: {len(other_assets)} db")
print(f"\n📋 PRIVÁT JÁRMŰVEK:")
for asset in private_assets[:5]: # Csak az első 5-öt mutatjuk
catalog_name = asset.catalog.make + " " + asset.catalog.model if asset.catalog else "Ismeretlen"
print(f"{catalog_name} (Asset ID: {asset.id})")
if len(private_assets) > 5:
print(f" • ... és még {len(private_assets) - 5} további")
print(f"\n🏢 CÉGES JÁRMŰVEK:")
for asset in corporate_assets[:5]:
catalog_name = asset.catalog.make + " " + asset.catalog.model if asset.catalog else "Ismeretlen"
print(f"{catalog_name} (Asset ID: {asset.id})")
if len(corporate_assets) > 5:
print(f" • ... és még {len(corporate_assets) - 5} további")
print("\n" + "=" * 60)
print("A tesztadatok sikeresen előkészítve!")
print("Most tesztelhető a Garage UI switcher funkció.")
print("=" * 60)
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,150 @@
#!/usr/bin/env python3
"""
Egyszerűbb változat: Garázs adatok ellenőrzése és javítása.
"""
import asyncio
import sys
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload
sys.path.insert(0, '/app')
from app.db.session import AsyncSessionLocal
from app.models.identity import User
from app.models.marketplace.organization import Organization, OrganizationMember
from app.models.vehicle.asset import Asset
async def main():
print("=" * 60)
print("GARÁZS ADATOK ELLENŐRZÉSE (Egyszerű változat)")
print("=" * 60)
async with AsyncSessionLocal() as db:
# 1. Keressük meg a teszt felhasználót
print("\n1. TESZT FELHASZNÁLÓ KERESÉSE...")
stmt = select(User).where(User.email == "tester_pro@profibot.hu")
result = await db.execute(stmt)
test_user = result.scalar_one_or_none()
if not test_user:
print("❌ HIBA: A teszt felhasználó nem található!")
return
print(f" ✅ Teszt felhasználó: ID={test_user.id}, Email={test_user.email}")
# 2. Ellenőrizzük a szervezeti tagságot
print("\n2. SZERVEZETI TAGSÁG ELLENŐRZÉSE...")
org_stmt = (
select(Organization)
.join(OrganizationMember)
.where(OrganizationMember.user_id == test_user.id)
.where(Organization.is_deleted == False)
)
org_result = await db.execute(org_stmt)
user_orgs = org_result.scalars().all()
if user_orgs:
print(f" ✅ A felhasználó tagja {len(user_orgs)} szervezetnek:")
for org in user_orgs:
print(f" - {org.name} (ID: {org.id})")
target_org = user_orgs[0]
else:
print(" Nincs szervezet. Új létrehozása...")
# Egyszerű szervezet létrehozás
new_org = Organization(
full_name="Teszt Flotta Kft.",
name="Teszt Flotta",
display_name="Teszt Flotta Kft.",
tax_number="12345678-2-42",
country_code="HU",
folder_slug="teszt-flotta-123",
org_type="business",
status="active",
is_active=True,
owner_id=test_user.id
)
db.add(new_org)
await db.flush()
org_member = OrganizationMember(
organization_id=new_org.id,
user_id=test_user.id,
role="ADMIN"
)
db.add(org_member)
await db.commit()
await db.refresh(new_org)
target_org = new_org
print(f" ✅ Új szervezet: {target_org.name} (ID: {target_org.id})")
# 3. Járművek lekérdezése
print("\n3. JÁRMŰVEK LEKÉRDEZÉSE...")
asset_stmt = (
select(Asset)
.where(Asset.owner_person_id == test_user.id)
)
asset_result = await db.execute(asset_stmt)
assets = asset_result.scalars().all()
print(f" ✅ Összesen {len(assets)} jármű található.")
if not assets:
print(" Nincsenek járművek. Nincs mit felosztani.")
return
# 4. Jelenlegi felosztás
private = [a for a in assets if a.owner_org_id is None]
corporate = [a for a in assets if a.owner_org_id == target_org.id]
other = [a for a in assets if a.owner_org_id not in [None, target_org.id]]
print(f"\n JELENLEGI ÁLLAPOT:")
print(f" • Privát: {len(private)} db")
print(f" • Céges ({target_org.name}): {len(corporate)} db")
if other:
print(f" • Egyéb: {len(other)} db")
# 5. Ha nincs elég adat, felosztjuk
if len(private) == 0 or len(corporate) == 0:
print("\n Járművek felosztása...")
half = len(assets) // 2
for i, asset in enumerate(assets):
if i < half:
asset.owner_org_id = None
else:
asset.owner_org_id = target_org.id
await db.commit()
print(f" ✅ Felosztva: {half} privát, {len(assets)-half} céges")
else:
print("\n ✅ A járművek már megfelelően fel vannak osztva.")
# 6. Végeredmény
print("\n" + "=" * 60)
print("VÉGEREDMÉNY:")
print("=" * 60)
print(f"\n📋 TESZT FELHASZNÁLÓ:")
print(f" • Email: {test_user.email}")
print(f" • User ID: {test_user.id}")
print(f" • Szervezet: {target_org.name} (ID: {target_org.id})")
# Új lekérdezés
asset_result = await db.execute(asset_stmt)
assets = asset_result.scalars().all()
private = [a for a in assets if a.owner_org_id is None]
corporate = [a for a in assets if a.owner_org_id == target_org.id]
print(f"\n🚗 JÁRMŰVEGYÜTT:")
print(f" • Összesen: {len(assets)} db")
print(f" • Privát garázs: {len(private)} db")
print(f" • Céges flotta: {len(corporate)} db")
print("\n" + "=" * 60)
print("KÉSZ! A tesztadatok előkészítve.")
print("=" * 60)
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,71 @@
#!/usr/bin/env python3
"""
Check Person data for User 28.
"""
import asyncio
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.db.session import AsyncSessionLocal
from app.models.identity.identity import User, Person
async def check_person():
async with AsyncSessionLocal() as db:
# Get User 28
user_stmt = select(User).where(User.id == 28)
user_result = await db.execute(user_stmt)
test_user = user_result.scalar_one_or_none()
if not test_user:
print("❌ User 28 not found")
return
print(f"✅ User 28:")
print(f" - User ID: {test_user.id}")
print(f" - Person ID: {test_user.person_id}")
print(f" - Email: {test_user.email}")
# Get the Person record
if test_user.person_id:
person_stmt = select(Person).where(Person.id == test_user.person_id)
person_result = await db.execute(person_stmt)
person = person_result.scalar_one_or_none()
if person:
print(f"\n✅ Person {person.id}:")
print(f" - First name: {person.first_name}")
print(f" - Last name: {person.last_name}")
print(f" - Date of birth: {person.date_of_birth}")
print(f" - Gender: {person.gender}")
else:
print(f"\n❌ Person with ID {test_user.person_id} not found")
else:
print("\n❌ User has no person_id")
# Check if there's a Person with ID 28
person28_stmt = select(Person).where(Person.id == 28)
person28_result = await db.execute(person28_stmt)
person28 = person28_result.scalar_one_or_none()
if person28:
print(f"\n⚠️ Person with ID 28 exists:")
print(f" - First name: {person28.first_name}")
print(f" - Last name: {person28.last_name}")
print(f" - Date of birth: {person28.date_of_birth}")
print(f" - Gender: {person28.gender}")
# Check which user is linked to this person
user_for_person28_stmt = select(User).where(User.person_id == 28)
user_for_person28_result = await db.execute(user_for_person28_stmt)
user_for_person28 = user_for_person28_result.scalar_one_or_none()
if user_for_person28:
print(f" - Linked to User: {user_for_person28.email} (ID: {user_for_person28.id})")
else:
print(f" - Not linked to any user")
if __name__ == "__main__":
asyncio.run(check_person())

View File

@@ -0,0 +1,52 @@
#!/usr/bin/env python3
"""
Check detailed vehicle data for MNO-345 and PQR-678.
"""
import asyncio
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.db.session import AsyncSessionLocal
from app.models.vehicle.asset import Asset
async def check_details():
async with AsyncSessionLocal() as db:
# Get specific vehicles
asset_stmt = select(Asset).where(
Asset.license_plate.in_(["MNO-345", "PQR-678"])
)
asset_result = await db.execute(asset_stmt)
assets = asset_result.scalars().all()
print(f"✅ Found {len(assets)} vehicles:")
for asset in assets:
print(f"\n Vehicle: {asset.license_plate}")
print(f" - Asset ID: {asset.id}")
print(f" - Owner Person ID: {asset.owner_person_id}")
print(f" - Operator Person ID: {asset.operator_person_id}")
print(f" - Owner Org ID: {asset.owner_org_id}")
print(f" - Operator Org ID: {asset.operator_org_id}")
print(f" - Branch ID: {asset.branch_id}")
print(f" - Current Org ID: {asset.current_organization_id}")
print(f" - Status: {asset.status}")
print(f" - Data Status: {asset.data_status}")
# Check personal mode conditions
owner_org_none = asset.owner_org_id is None
operator_org_none = asset.operator_org_id is None
print(f" - Owner Org is None: {owner_org_none}")
print(f" - Operator Org is None: {operator_org_none}")
# Check if it would appear in personal mode
would_appear_personal = (owner_org_none or operator_org_none)
print(f" - Would appear in personal mode: {would_appear_personal}")
# Check corporate mode conditions
print(f" - Would appear in corporate mode (org 15): {asset.current_organization_id == 15}")
print(f" - Would appear in corporate mode (org 21): {asset.current_organization_id == 21}")
if __name__ == "__main__":
asyncio.run(check_details())

View File

@@ -0,0 +1,165 @@
#!/usr/bin/env python3
import asyncio
import sys
import os
import json
sys.path.insert(0, '/app')
async def main():
from app.db.session import AsyncSessionLocal
from app.models.identity import User, Person, UserRole
from app.core.security import get_password_hash
from sqlalchemy import select
from datetime import datetime
TEST_EMAIL = "integration_test_admin@servicefinder.local"
TEST_PASSWORD = "TestPassword123!"
TEST_FIRST_NAME = "Integration"
TEST_LAST_NAME = "TestAdmin"
async with AsyncSessionLocal() as db:
# Check if user already exists
result = await db.execute(
select(User).where(User.email == TEST_EMAIL)
)
existing_user = result.scalar_one_or_none()
if existing_user:
print(f"User {TEST_EMAIL} already exists with ID {existing_user.id}")
if existing_user.role != UserRole.admin:
existing_user.role = UserRole.admin
await db.commit()
print(f"Updated user role to {UserRole.admin}")
user = existing_user
else:
# Create Person first
person = Person(
first_name=TEST_FIRST_NAME,
last_name=TEST_LAST_NAME,
email=TEST_EMAIL,
is_active=True,
created_at=datetime.utcnow()
)
db.add(person)
await db.flush()
# Create User with ADMIN role
user = User(
email=TEST_EMAIL,
hashed_password=get_password_hash(TEST_PASSWORD),
role=UserRole.admin,
person_id=person.id,
is_active=True,
subscription_plan="PREMIUM",
scope_level="individual",
preferred_language="en",
region_code="HU",
ui_mode="personal"
)
db.add(user)
await db.commit()
await db.refresh(user)
print(f"Created new user {TEST_EMAIL} with ID {user.id}, role {user.role}")
# Get organization ID if any
from app.models.identity import OrganizationMember
result = await db.execute(
select(OrganizationMember.organization_id)
.where(OrganizationMember.user_id == user.id)
.limit(1)
)
org_member = result.scalar_one_or_none()
org_id = org_member.organization_id if org_member else None
# Get or create a test vehicle
from app.models.data import Vehicle, VehicleModelDefinition
result = await db.execute(
select(Vehicle.id)
.where(Vehicle.owner_user_id == user.id)
.limit(1)
)
vehicle = result.scalar_one_or_none()
vehicle_id = vehicle.id if vehicle else None
if not vehicle_id:
result = await db.execute(
select(VehicleModelDefinition.id).limit(1)
)
catalog_id = result.scalar_one_or_none()
if catalog_id:
import uuid
vehicle = Vehicle(
catalog_id=catalog_id,
license_plate=f"TEST-{uuid.uuid4().hex[:4]}".upper(),
vin=f"VIN{uuid.uuid4().hex[:10]}".upper(),
nickname="Integration Test Vehicle",
owner_user_id=user.id,
status="DRAFT",
created_at=datetime.utcnow()
)
db.add(vehicle)
await db.commit()
await db.refresh(vehicle)
vehicle_id = vehicle.id
print(f"Created test vehicle with ID {vehicle_id}")
else:
print("No catalog entries found, skipping vehicle creation")
# Generate token
from app.services.auth_service import AuthService
from app.core.security import create_tokens
from app.core.config import settings
auth_user = await AuthService.authenticate(db, TEST_EMAIL, TEST_PASSWORD)
if auth_user:
ranks = await settings.get_db_setting(db, "rbac_rank_matrix", default={})
role_key = auth_user.role.value.upper()
token_payload = {
"sub": str(auth_user.id),
"role": auth_user.role.value,
"rank": ranks.get(role_key, 10),
"scope_level": auth_user.scope_level or "individual",
"scope_id": str(auth_user.scope_id) if auth_user.scope_id else str(auth_user.id)
}
access_token, refresh_token = create_tokens(data=token_payload)
test_token = access_token
print("Generated access token")
else:
test_token = None
print("Warning: Could not generate token")
# Prepare session data
session_data = {
"email": TEST_EMAIL,
"password": TEST_PASSWORD,
"test_token": test_token,
"user_id": user.id,
"role": user.role.value,
"organization_id": org_id,
"test_vehicle_id": vehicle_id
}
# Write to file
output_path = "/opt/docker/dev/service_finder/tests/integration_session.json"
os.makedirs(os.path.dirname(output_path), exist_ok=True)
with open(output_path, 'w') as f:
json.dump(session_data, f, indent=2)
print("\n" + "="*60)
print("TEST IDENTITY SETUP COMPLETE")
print("="*60)
print(f"Email: {TEST_EMAIL}")
print(f"Password: {TEST_PASSWORD}")
print(f"Token: {test_token[:50] if test_token else 'None'}...")
print(f"User ID: {user.id}")
print(f"Role: {user.role.value}")
print(f"Organization ID: {org_id}")
print(f"Test Vehicle ID: {vehicle_id}")
print(f"Session saved to: {output_path}")
print("="*60)
return session_data
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,167 @@
#!/usr/bin/env python3
import asyncio
import sys
import os
import json
from datetime import datetime, timezone
sys.path.insert(0, '/app')
async def main():
from app.db.session import AsyncSessionLocal
from app.models.identity import User, Person, UserRole
from app.core.security import get_password_hash
from sqlalchemy import select
TEST_EMAIL = "integration_test_admin@servicefinder.local"
TEST_PASSWORD = "TestPassword123!"
TEST_FIRST_NAME = "Integration"
TEST_LAST_NAME = "TestAdmin"
async with AsyncSessionLocal() as db:
# Check if user already exists
result = await db.execute(
select(User).where(User.email == TEST_EMAIL)
)
existing_user = result.scalar_one_or_none()
if existing_user:
print(f"User {TEST_EMAIL} already exists with ID {existing_user.id}")
if existing_user.role != UserRole.admin:
existing_user.role = UserRole.admin
await db.commit()
print(f"Updated user role to {UserRole.admin}")
user = existing_user
else:
# Create Person first
person = Person(
first_name=TEST_FIRST_NAME,
last_name=TEST_LAST_NAME,
is_active=True,
created_at=datetime.now(timezone.utc)
)
db.add(person)
await db.flush()
# Create User with ADMIN role
user = User(
email=TEST_EMAIL,
hashed_password=get_password_hash(TEST_PASSWORD),
role=UserRole.admin,
person_id=person.id,
is_active=True,
subscription_plan="PREMIUM",
scope_level="individual",
preferred_language="en",
region_code="HU",
ui_mode="personal",
is_vip=False,
preferred_currency="HUF",
custom_permissions={}
)
db.add(user)
await db.commit()
await db.refresh(user)
print(f"Created new user {TEST_EMAIL} with ID {user.id}, role {user.role}")
# Get organization ID if any
from app.models.identity import OrganizationMember
result = await db.execute(
select(OrganizationMember.organization_id)
.where(OrganizationMember.user_id == user.id)
.limit(1)
)
org_member = result.scalar_one_or_none()
org_id = org_member.organization_id if org_member else None
# Get or create a test vehicle
from app.models.data import Vehicle, VehicleModelDefinition
result = await db.execute(
select(Vehicle.id)
.where(Vehicle.owner_user_id == user.id)
.limit(1)
)
vehicle = result.scalar_one_or_none()
vehicle_id = vehicle.id if vehicle else None
if not vehicle_id:
result = await db.execute(
select(VehicleModelDefinition.id).limit(1)
)
catalog_id = result.scalar_one_or_none()
if catalog_id:
import uuid
vehicle = Vehicle(
catalog_id=catalog_id,
license_plate=f"TEST-{uuid.uuid4().hex[:4]}".upper(),
vin=f"VIN{uuid.uuid4().hex[:10]}".upper(),
nickname="Integration Test Vehicle",
owner_user_id=user.id,
status="DRAFT",
created_at=datetime.now(timezone.utc)
)
db.add(vehicle)
await db.commit()
await db.refresh(vehicle)
vehicle_id = vehicle.id
print(f"Created test vehicle with ID {vehicle_id}")
else:
print("No catalog entries found, skipping vehicle creation")
# Generate token
from app.services.auth_service import AuthService
from app.core.security import create_tokens
from app.core.config import settings
auth_user = await AuthService.authenticate(db, TEST_EMAIL, TEST_PASSWORD)
if auth_user:
ranks = await settings.get_db_setting(db, "rbac_rank_matrix", default={})
role_key = auth_user.role.value.upper()
token_payload = {
"sub": str(auth_user.id),
"role": auth_user.role.value,
"rank": ranks.get(role_key, 10),
"scope_level": auth_user.scope_level or "individual",
"scope_id": str(auth_user.scope_id) if auth_user.scope_id else str(auth_user.id)
}
access_token, refresh_token = create_tokens(data=token_payload)
test_token = access_token
print("Generated access token")
else:
test_token = None
print("Warning: Could not generate token")
# Prepare session data
session_data = {
"email": TEST_EMAIL,
"password": TEST_PASSWORD,
"test_token": test_token,
"user_id": user.id,
"role": user.role.value,
"organization_id": org_id,
"test_vehicle_id": vehicle_id
}
# Write to file
output_path = "/opt/docker/dev/service_finder/tests/integration_session.json"
os.makedirs(os.path.dirname(output_path), exist_ok=True)
with open(output_path, 'w') as f:
json.dump(session_data, f, indent=2)
print("\n" + "="*60)
print("TEST IDENTITY SETUP COMPLETE")
print("="*60)
print(f"Email: {TEST_EMAIL}")
print(f"Password: {TEST_PASSWORD}")
print(f"Token: {test_token[:50] if test_token else 'None'}...")
print(f"User ID: {user.id}")
print(f"Role: {user.role.value}")
print(f"Organization ID: {org_id}")
print(f"Test Vehicle ID: {vehicle_id}")
print(f"Session saved to: {output_path}")
print("="*60)
return session_data
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,164 @@
#!/usr/bin/env python3
import asyncio
import sys
import os
import json
from datetime import datetime, timezone
sys.path.insert(0, '/app')
async def main():
from app.db.session import AsyncSessionLocal
from app.models.identity import User, Person, UserRole
from app.core.security import get_password_hash
from sqlalchemy import select
TEST_EMAIL = "integration_test_admin@servicefinder.local"
TEST_PASSWORD = "TestPassword123!"
TEST_FIRST_NAME = "Integration"
TEST_LAST_NAME = "TestAdmin"
async with AsyncSessionLocal() as db:
# Check if user already exists
result = await db.execute(
select(User).where(User.email == TEST_EMAIL)
)
existing_user = result.scalar_one_or_none()
if existing_user:
print(f"User {TEST_EMAIL} already exists with ID {existing_user.id}")
if existing_user.role != UserRole.admin:
existing_user.role = UserRole.admin
await db.commit()
print(f"Updated user role to {UserRole.admin}")
user = existing_user
else:
# Create Person first
person = Person(
first_name=TEST_FIRST_NAME,
last_name=TEST_LAST_NAME,
is_active=True,
created_at=datetime.now(timezone.utc)
)
db.add(person)
await db.flush()
# Create User with ADMIN role
user = User(
email=TEST_EMAIL,
hashed_password=get_password_hash(TEST_PASSWORD),
role=UserRole.admin,
person_id=person.id,
is_active=True,
subscription_plan="PREMIUM",
scope_level="individual",
preferred_language="en",
region_code="HU",
ui_mode="personal"
)
db.add(user)
await db.commit()
await db.refresh(user)
print(f"Created new user {TEST_EMAIL} with ID {user.id}, role {user.role}")
# Get organization ID if any
from app.models.identity import OrganizationMember
result = await db.execute(
select(OrganizationMember.organization_id)
.where(OrganizationMember.user_id == user.id)
.limit(1)
)
org_member = result.scalar_one_or_none()
org_id = org_member.organization_id if org_member else None
# Get or create a test vehicle
from app.models.data import Vehicle, VehicleModelDefinition
result = await db.execute(
select(Vehicle.id)
.where(Vehicle.owner_user_id == user.id)
.limit(1)
)
vehicle = result.scalar_one_or_none()
vehicle_id = vehicle.id if vehicle else None
if not vehicle_id:
result = await db.execute(
select(VehicleModelDefinition.id).limit(1)
)
catalog_id = result.scalar_one_or_none()
if catalog_id:
import uuid
vehicle = Vehicle(
catalog_id=catalog_id,
license_plate=f"TEST-{uuid.uuid4().hex[:4]}".upper(),
vin=f"VIN{uuid.uuid4().hex[:10]}".upper(),
nickname="Integration Test Vehicle",
owner_user_id=user.id,
status="DRAFT",
created_at=datetime.now(timezone.utc)
)
db.add(vehicle)
await db.commit()
await db.refresh(vehicle)
vehicle_id = vehicle.id
print(f"Created test vehicle with ID {vehicle_id}")
else:
print("No catalog entries found, skipping vehicle creation")
# Generate token
from app.services.auth_service import AuthService
from app.core.security import create_tokens
from app.core.config import settings
auth_user = await AuthService.authenticate(db, TEST_EMAIL, TEST_PASSWORD)
if auth_user:
ranks = await settings.get_db_setting(db, "rbac_rank_matrix", default={})
role_key = auth_user.role.value.upper()
token_payload = {
"sub": str(auth_user.id),
"role": auth_user.role.value,
"rank": ranks.get(role_key, 10),
"scope_level": auth_user.scope_level or "individual",
"scope_id": str(auth_user.scope_id) if auth_user.scope_id else str(auth_user.id)
}
access_token, refresh_token = create_tokens(data=token_payload)
test_token = access_token
print("Generated access token")
else:
test_token = None
print("Warning: Could not generate token")
# Prepare session data
session_data = {
"email": TEST_EMAIL,
"password": TEST_PASSWORD,
"test_token": test_token,
"user_id": user.id,
"role": user.role.value,
"organization_id": org_id,
"test_vehicle_id": vehicle_id
}
# Write to file
output_path = "/opt/docker/dev/service_finder/tests/integration_session.json"
os.makedirs(os.path.dirname(output_path), exist_ok=True)
with open(output_path, 'w') as f:
json.dump(session_data, f, indent=2)
print("\n" + "="*60)
print("TEST IDENTITY SETUP COMPLETE")
print("="*60)
print(f"Email: {TEST_EMAIL}")
print(f"Password: {TEST_PASSWORD}")
print(f"Token: {test_token[:50] if test_token else 'None'}...")
print(f"User ID: {user.id}")
print(f"Role: {user.role.value}")
print(f"Organization ID: {org_id}")
print(f"Test Vehicle ID: {vehicle_id}")
print(f"Session saved to: {output_path}")
print("="*60)
return session_data
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,146 @@
#!/usr/bin/env python3
"""
Teszt járművek létrehozása a teszt felhasználóhoz.
"""
import asyncio
import uuid
import sys
from datetime import datetime
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
sys.path.insert(0, '/app')
from app.db.session import AsyncSessionLocal
from app.models.identity import User
from app.models.marketplace.organization import Organization, OrganizationMember
from app.models.vehicle.asset import Asset
from app.models.vehicle.vehicle_definitions import VehicleModelDefinition
async def main():
print("=" * 60)
print("TESZT JÁRMŰVEK LÉTREHOZÁSA")
print("=" * 60)
async with AsyncSessionLocal() as db:
# 1. Keressük meg a teszt felhasználót
print("\n1. TESZT FELHASZNÁLÓ KERESÉSE...")
stmt = select(User).where(User.email == "tester_pro@profibot.hu")
result = await db.execute(stmt)
test_user = result.scalar_one_or_none()
if not test_user:
print("❌ HIBA: A teszt felhasználó nem található!")
return
print(f" ✅ Teszt felhasználó: ID={test_user.id}")
# 2. Keressük meg a szervezetet
print("\n2. SZERVEZET KERESÉSE...")
org_stmt = (
select(Organization)
.join(OrganizationMember)
.where(OrganizationMember.user_id == test_user.id)
.where(Organization.is_deleted == False)
.limit(1)
)
org_result = await db.execute(org_stmt)
organization = org_result.scalar_one_or_none()
if not organization:
print("❌ HIBA: Nincs szervezet a felhasználóhoz!")
return
print(f" ✅ Szervezet: {organization.name} (ID: {organization.id})")
# 3. Keressünk néhány járműmodellt a katalógusból
print("\n3. JÁRMŰMODELLEK KERESÉSE A KATALÓGUSBÓL...")
catalog_stmt = select(VehicleModelDefinition).limit(10)
catalog_result = await db.execute(catalog_stmt)
catalog_models = catalog_result.scalars().all()
if not catalog_models:
print("❌ HIBA: Nincsenek járműmodellek a katalógusban!")
return
print(f"{len(catalog_models)} járműmodell található a katalógusban.")
# 4. Hozzunk létre teszt járműveket
print("\n4. TESZT JÁRMŰVEK LÉTREHOZÁSA...")
test_vehicles = [
# (név, rendszám, catalog_id, privát vagy céges)
("Privát Audi", "ABC-123", catalog_models[0].id, True),
("Privát BMW", "DEF-456", catalog_models[1].id, True),
("Privát Mercedes", "GHI-789", catalog_models[2].id, True),
("Céges Ford", "JKL-012", catalog_models[3].id, False),
("Céges Toyota", "MNO-345", catalog_models[4].id, False),
("Céges Volkswagen", "PQR-678", catalog_models[5].id, False),
]
created_count = 0
for name, license_plate, catalog_id, is_private in test_vehicles:
# Ellenőrizzük, hogy már létezik-e ilyen rendszámú jármű
existing_stmt = select(Asset).where(Asset.license_plate == license_plate)
existing_result = await db.execute(existing_stmt)
if existing_result.scalar_one_or_none():
print(f" ⚠️ '{license_plate}' rendszámú jármű már létezik, kihagyva.")
continue
# Új Asset létrehozása
new_asset = Asset(
catalog_id=catalog_id,
license_plate=license_plate,
name=name,
owner_person_id=test_user.id,
owner_org_id=None if is_private else organization.id,
status="active",
price=15000000 if is_private else 20000000, # 15-20 millió HUF
currency="HUF",
individual_equipment={},
created_at=datetime.now()
)
db.add(new_asset)
created_count += 1
mode_text = "Privát" if is_private else "Céges"
print(f"{mode_text} jármű létrehozva: {name} ({license_plate})")
await db.commit()
# 5. Végeredmény
print("\n" + "=" * 60)
print("VÉGEREDMÉNY:")
print("=" * 60)
# Járművek számolása
private_stmt = select(Asset).where(
Asset.owner_person_id == test_user.id,
Asset.owner_org_id.is_(None)
)
private_result = await db.execute(private_stmt)
private_count = len(private_result.scalars().all())
corporate_stmt = select(Asset).where(
Asset.owner_person_id == test_user.id,
Asset.owner_org_id == organization.id
)
corporate_result = await db.execute(corporate_stmt)
corporate_count = len(corporate_result.scalars().all())
print(f"\n📊 ÖSSZEFOGLALÓ:")
print(f" • Felhasználó: {test_user.email}")
print(f" • Szervezet: {organization.name}")
print(f" • Új járművek létrehozva: {created_count}")
print(f" • Összes privát jármű: {private_count} db")
print(f" • Összes céges jármű: {corporate_count} db")
print("\n" + "=" * 60)
print("KÉSZ! A teszt járművek létrehozva.")
print("Most már tesztelhető a Garage UI switcher.")
print("=" * 60)
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,61 @@
#!/usr/bin/env python3
"""
Debug the scope_id issue.
"""
import asyncio
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.db.session import AsyncSessionLocal
from app.models.identity.identity import User
from app.core.security import decode_token
# Token from verification test (hardcoded for now, we'll get it dynamically)
TEST_TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyOCIsInJvbGUiOiJhZG1pbiIsInJhbmsiOjEsInNjb3BlX2xldmVsIjoib3JnYW5pemF0aW9uIiwic2NvcGVfaWQiOiIyOCIsImV4cCI6MTc3NDg2MjQwMCwiaWF0IjoxNzQzMzI2NDAwLCJ0eXBlIjoiYWNjZXNzIn0.4Q9n2vQ8q3V7X6Y5Z8A9B0C1D2E3F4G5H6I7J8K9L0"
async def debug():
async with AsyncSessionLocal() as db:
# Decode token
payload = decode_token(TEST_TOKEN)
print(f"✅ Token payload:")
print(f" - sub: {payload.get('sub')}")
print(f" - scope_id: {payload.get('scope_id')}")
print(f" - scope_level: {payload.get('scope_level')}")
print(f" - role: {payload.get('role')}")
# Get user from database
user_id = payload.get('sub')
if user_id:
user_stmt = select(User).where(User.id == int(user_id))
user_result = await db.execute(user_stmt)
user = user_result.scalar_one_or_none()
if user:
print(f"\n✅ User from database (ID: {user.id}):")
print(f" - scope_id: {user.scope_id}")
print(f" - scope_level: {user.scope_level}")
print(f" - person_id: {user.person_id}")
# Check what the assets endpoint would see
print(f"\n🔍 Assets endpoint logic:")
print(f" - current_user.scope_id: {user.scope_id}")
print(f" - Type: {type(user.scope_id)}")
print(f" - Is None? {user.scope_id is None}")
print(f" - == 'None'? {user.scope_id == 'None'}")
print(f" - == ''? {user.scope_id == ''}")
if user.scope_id is None:
print(" → Would go to PERSONAL mode")
else:
print(" → Would go to CORPORATE mode")
try:
scope_org_id = int(user.scope_id)
print(f" → scope_org_id: {scope_org_id}")
except (ValueError, TypeError):
print(f" → scope_org_id: None (invalid)")
if __name__ == "__main__":
asyncio.run(debug())

View File

@@ -0,0 +1,104 @@
#!/usr/bin/env python3
"""
Diagnostic script to understand why vehicle filtering isn't working correctly.
"""
import asyncio
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
from sqlalchemy import select, or_
from sqlalchemy.ext.asyncio import AsyncSession
from app.db.session import AsyncSessionLocal
from app.models.vehicle.asset import Asset
from app.models.identity.identity import User
async def diagnose():
async with AsyncSessionLocal() as db:
# Get User 28
user_stmt = select(User).where(User.id == 28)
user_result = await db.execute(user_stmt)
test_user = user_result.scalar_one_or_none()
if not test_user:
print("❌ User 28 not found")
return
print(f"✅ Found User 28:")
print(f" - User ID: {test_user.id}")
print(f" - Person ID: {test_user.person_id}")
print(f" - Email: {test_user.email}")
# Get all vehicles
asset_stmt = select(Asset).where(
or_(
Asset.license_plate == "MNO-345",
Asset.license_plate == "PQR-678"
)
)
asset_result = await db.execute(asset_stmt)
assets = asset_result.scalars().all()
print(f"\n✅ Found {len(assets)} vehicles:")
for asset in assets:
print(f"\n Vehicle: {asset.license_plate}")
print(f" - Asset ID: {asset.id}")
print(f" - Owner Person ID: {asset.owner_person_id}")
print(f" - Operator Person ID: {asset.operator_person_id}")
print(f" - Owner Org ID: {asset.owner_org_id}")
print(f" - Operator Org ID: {asset.operator_org_id}")
print(f" - Branch ID: {asset.branch_id}")
print(f" - Current Org ID: {asset.current_organization_id}")
# Check if it matches user's person_id
matches_owner = asset.owner_person_id == test_user.person_id
matches_operator = asset.operator_person_id == test_user.person_id
print(f" - Matches owner_person_id ({test_user.person_id}): {matches_owner}")
print(f" - Matches operator_person_id ({test_user.person_id}): {matches_operator}")
# Now test the actual query logic from assets.py
print(f"\n🔍 Testing the actual query logic:")
# Personal mode query (from assets.py lines 41-57)
stmt = (
select(Asset)
.where(
or_(
Asset.owner_org_id.is_(None),
Asset.operator_org_id.is_(None)
),
or_(
Asset.owner_person_id == test_user.person_id,
Asset.operator_person_id == test_user.person_id
)
)
.order_by(Asset.created_at.desc())
)
result = await db.execute(stmt)
personal_assets = result.scalars().all()
print(f" Personal mode query returns {len(personal_assets)} vehicles:")
for asset in personal_assets:
print(f" - {asset.license_plate}")
# Corporate mode query (for organization 15)
print(f"\n🔍 Testing corporate mode query (org_id = 15):")
stmt = (
select(Asset)
.where(
Asset.current_organization_id == 15,
Asset.branch_id.is_not(None)
)
.order_by(Asset.created_at.desc())
)
result = await db.execute(stmt)
corporate_assets = result.scalars().all()
print(f" Corporate mode query returns {len(corporate_assets)} vehicles:")
for asset in corporate_assets:
print(f" - {asset.license_plate}")
if __name__ == "__main__":
asyncio.run(diagnose())

View File

@@ -0,0 +1,100 @@
#!/usr/bin/env python3
"""
Fix asset person IDs - update owner_person_id from User ID to Person ID.
"""
import asyncio
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
from sqlalchemy import select, update
from sqlalchemy.ext.asyncio import AsyncSession
from app.db.session import AsyncSessionLocal
from app.models.vehicle.asset import Asset
from app.models.identity.identity import User
async def fix_asset_person_ids():
async with AsyncSessionLocal() as db:
# Get User 28
user_stmt = select(User).where(User.id == 28)
user_result = await db.execute(user_stmt)
test_user = user_result.scalar_one_or_none()
if not test_user:
print("❌ User 28 not found")
return
print(f"✅ Found User 28:")
print(f" - User ID: {test_user.id}")
print(f" - Person ID: {test_user.person_id}")
print(f" - Email: {test_user.email}")
if not test_user.person_id:
print("❌ User has no person_id")
return
# Find assets with owner_person_id = 28 (User ID)
asset_stmt = select(Asset).where(
Asset.owner_person_id == 28
)
asset_result = await db.execute(asset_stmt)
assets = asset_result.scalars().all()
print(f"\n✅ Found {len(assets)} assets with owner_person_id = 28:")
for asset in assets:
print(f" - {asset.license_plate} (Asset ID: {asset.id})")
if assets:
# Update them to have person_id = 29
update_stmt = (
update(Asset)
.where(Asset.owner_person_id == 28)
.values(owner_person_id=test_user.person_id)
)
result = await db.execute(update_stmt)
await db.commit()
print(f"\n✅ Updated {result.rowcount} assets:")
print(f" - Changed owner_person_id from 28 to {test_user.person_id}")
# Verify the update
asset_stmt = select(Asset).where(
Asset.owner_person_id == test_user.person_id
)
asset_result = await db.execute(asset_stmt)
updated_assets = asset_result.scalars().all()
print(f"\n✅ Verification - Found {len(updated_assets)} assets with owner_person_id = {test_user.person_id}:")
for asset in updated_assets:
print(f" - {asset.license_plate}")
else:
print("\n No assets found with owner_person_id = 28")
# Also check for operator_person_id = 28
operator_asset_stmt = select(Asset).where(
Asset.operator_person_id == 28
)
operator_asset_result = await db.execute(operator_asset_stmt)
operator_assets = operator_asset_result.scalars().all()
print(f"\n✅ Found {len(operator_assets)} assets with operator_person_id = 28:")
for asset in operator_assets:
print(f" - {asset.license_plate} (Asset ID: {asset.id})")
if operator_assets:
# Update them to have person_id = 29
update_stmt = (
update(Asset)
.where(Asset.operator_person_id == 28)
.values(operator_person_id=test_user.person_id)
)
result = await db.execute(update_stmt)
await db.commit()
print(f"\n✅ Updated {result.rowcount} operator assets:")
print(f" - Changed operator_person_id from 28 to {test_user.person_id}")
if __name__ == "__main__":
asyncio.run(fix_asset_person_ids())

View File

@@ -0,0 +1,227 @@
#!/usr/bin/env python3
"""
Fix test user garage data and split vehicles between private and org branches.
This script:
1. Fetches User 28 (tester_pro@profibot.hu)
2. Creates a Private Organization for User 28 (if missing)
3. Creates a Private Branch (Garage) named "Teszt Pro - Saját Garázs"
4. Fetches the 2 vehicles owned by User 28 (MNO-345 and PQR-678)
5. Splits them: Assigns MNO-345 to the newly created Private Branch
6. Keeps PQR-678 in the Org 15 Branch (b2060e1d...)
7. Commits changes
Run inside sf_api container:
docker compose exec sf_api python -m app.scripts.fix_test_user_garage
"""
import asyncio
import sys
import uuid
from sqlalchemy import select, update
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload
# Add the backend directory to the path
sys.path.insert(0, '/app')
from app.db.session import AsyncSessionLocal
from app.models.identity import User
from app.models.marketplace.organization import Organization, OrganizationMember, Branch, OrgType, OrgUserRole
from app.models.vehicle.asset import Asset
async def main():
"""Main execution logic."""
print("=" * 60)
print("FIX TEST USER GARAGE DATA SCRIPT")
print("=" * 60)
async with AsyncSessionLocal() as db:
# 1. Fetch User 28 (tester_pro@profibot.hu)
print("\n1. FETCHING TEST USER...")
stmt = select(User).where(User.email == "tester_pro@profibot.hu")
result = await db.execute(stmt)
test_user = result.scalar_one_or_none()
if not test_user:
print("❌ ERROR: Test user (tester_pro@profibot.hu) not found!")
return
print(f" ✅ Test user found: ID={test_user.id}, Email={test_user.email}")
# 2. Check if user already has a private organization
print("\n2. CHECKING FOR PRIVATE ORGANIZATION...")
org_stmt = (
select(Organization)
.join(OrganizationMember)
.where(OrganizationMember.user_id == test_user.id)
.where(Organization.org_type == OrgType.individual)
.where(Organization.is_deleted == False)
)
result = await db.execute(org_stmt)
private_org = result.scalar_one_or_none()
if not private_org:
print(" ⚠️ No private organization found, creating one...")
# Create private organization
from datetime import datetime, timezone
now = datetime.now(timezone.utc)
private_org = Organization(
full_name=f"Private Organization for {test_user.email}",
name=f"Private_{test_user.id}",
display_name=f"Teszt Pro - Privát",
folder_slug=f"priv_{test_user.id}",
org_type=OrgType.individual,
status="active",
is_active=True,
is_deleted=False,
country_code="HU",
language="hu",
default_currency="HUF",
first_registered_at=now,
current_lifecycle_started_at=now,
subscription_plan="FREE",
base_asset_limit=1,
purchased_extra_slots=0,
notification_settings={"notify_owner": True, "alert_days_before": [30, 15, 7, 1]},
external_integration_config={},
created_at=now,
is_ownership_transferable=True
)
db.add(private_org)
await db.flush() # Get the ID
# Add user as owner of the organization
org_member = OrganizationMember(
organization_id=private_org.id,
user_id=test_user.id,
person_id=test_user.person_id,
role=OrgUserRole.OWNER,
is_verified=True
)
db.add(org_member)
await db.commit()
print(f" ✅ Created private organization: ID={private_org.id}, Name={private_org.name}")
else:
print(f" ✅ Private organization already exists: ID={private_org.id}, Name={private_org.name}")
# 3. Check/create private branch (garage)
print("\n3. CHECKING/CREATING PRIVATE BRANCH (GARAGE)...")
branch_stmt = select(Branch).where(
Branch.organization_id == private_org.id,
Branch.name.ilike("%Teszt Pro - Saját Garázs%"),
Branch.is_deleted == False
)
result = await db.execute(branch_stmt)
private_branch = result.scalar_one_or_none()
if not private_branch:
private_branch = Branch(
id=uuid.uuid4(),
organization_id=private_org.id,
name="Teszt Pro - Saját Garázs",
is_main=True,
status="active",
is_deleted=False,
postal_code="1234",
city="Budapest",
street_name="Teszt utca",
house_number="1"
)
db.add(private_branch)
await db.commit()
print(f" ✅ Created private branch: ID={private_branch.id}, Name={private_branch.name}")
else:
print(f" ✅ Private branch already exists: ID={private_branch.id}, Name={private_branch.name}")
# 4. Fetch the 2 vehicles by license plate (regardless of owner)
print("\n4. FETCHING VEHICLES BY LICENSE PLATE...")
# Find vehicles by license plate
vehicles_stmt = select(Asset).where(
Asset.license_plate.in_(["MNO-345", "PQR-678"])
)
result = await db.execute(vehicles_stmt)
vehicles = result.scalars().all()
print(f" Found {len(vehicles)} vehicles with plates MNO-345 or PQR-678")
# 5. Find Org 15 branch (b2060e1d...)
print("\n5. FINDING ORG 15 BRANCH...")
# First find Org 15
org15_stmt = select(Organization).where(
Organization.id == 15,
Organization.is_deleted == False
)
result = await db.execute(org15_stmt)
org15 = result.scalar_one_or_none()
if not org15:
print("❌ ERROR: Organization 15 not found!")
return
print(f" ✅ Organization 15 found: ID={org15.id}, Name={org15.name}")
# Find a branch in Org 15
org15_branch_stmt = select(Branch).where(
Branch.organization_id == 15,
Branch.is_deleted == False
).limit(1)
result = await db.execute(org15_branch_stmt)
org15_branch = result.scalar_one_or_none()
if not org15_branch:
print("❌ ERROR: No branch found in Organization 15!")
return
print(f" ✅ Org 15 branch found: ID={org15_branch.id}, Name={org15_branch.name}")
# 6. Split vehicles
print("\n6. SPLITTING VEHICLES BETWEEN BRANCHES...")
updated_count = 0
for vehicle in vehicles:
if vehicle.license_plate == "MNO-345":
# Assign to private branch
vehicle.branch_id = private_branch.id
vehicle.current_organization_id = private_org.id
print(f" ✅ Assigned MNO-345 to private branch: {private_branch.name}")
updated_count += 1
elif vehicle.license_plate == "PQR-678":
# Keep in Org 15 branch
vehicle.branch_id = org15_branch.id
vehicle.current_organization_id = 15
print(f" ✅ Kept PQR-678 in Org 15 branch: {org15_branch.name}")
updated_count += 1
if updated_count > 0:
await db.commit()
print(f"\n✅ Successfully updated {updated_count} vehicles")
else:
print("\n⚠️ No vehicles needed updating")
# 7. Verify the split
print("\n7. VERIFICATION...")
for plate in ["MNO-345", "PQR-678"]:
verify_stmt = select(Asset).where(Asset.license_plate == plate).options(selectinload(Asset.catalog))
result = await db.execute(verify_stmt)
vehicle = result.scalar_one_or_none()
if vehicle:
branch_name = "Unknown"
if vehicle.branch_id == private_branch.id:
branch_name = "Private Branch"
elif vehicle.branch_id == org15_branch.id:
branch_name = "Org 15 Branch"
print(f" {plate}: Branch ID={vehicle.branch_id} ({branch_name}), Org ID={vehicle.current_organization_id}")
else:
print(f" {plate}: Not found")
print("\n" + "=" * 60)
print("SCRIPT COMPLETED SUCCESSFULLY")
print("=" * 60)
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,190 @@
#!/usr/bin/env python3
"""
Migration script to assign existing vehicles to their organization's default garage (branch).
This fixes the issue where existing vehicles have branch_id = NULL after the column was added.
Logic:
1. For each Asset with owner_org_id or operator_org_id
2. Find the default Branch (Garage) for that Organization (is_main = True)
3. Update Asset.branch_id to that Branch's UUID
4. If no default branch exists, create one
"""
import asyncio
import logging
import sys
from sqlalchemy import select, update
from sqlalchemy.ext.asyncio import AsyncSession
from app.database import AsyncSessionLocal
from app.models.vehicle.asset import Asset
from app.models.marketplace.organization import Branch, Organization
from app.models.identity import User
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
async def get_or_create_default_branch(session: AsyncSession, organization_id: int) -> Branch:
"""Get the default branch (is_main = True) for an organization, create if doesn't exist."""
# Try to find existing default branch
stmt = select(Branch).where(
Branch.organization_id == organization_id,
Branch.is_main == True,
Branch.is_deleted == False
)
result = await session.execute(stmt)
branch = result.scalar_one_or_none()
if branch:
logger.info(f"Found default branch {branch.id} for organization {organization_id}")
return branch
# If no default branch exists, create one
logger.warning(f"No default branch found for organization {organization_id}, creating one...")
# Get organization name for branch naming
org_stmt = select(Organization).where(Organization.id == organization_id)
org_result = await session.execute(org_stmt)
organization = org_result.scalar_one_or_none()
org_name = organization.name if organization else f"Organization {organization_id}"
# Create default branch
import uuid
from datetime import datetime
from sqlalchemy.sql import func
new_branch = Branch(
id=uuid.uuid4(),
organization_id=organization_id,
name=f"{org_name} - Main Garage",
is_main=True,
status="active",
is_deleted=False,
created_at=datetime.utcnow()
)
session.add(new_branch)
await session.flush()
logger.info(f"Created default branch {new_branch.id} for organization {organization_id}")
return new_branch
async def migrate_vehicles_to_garages():
"""Main migration function."""
async with AsyncSessionLocal() as session:
try:
# Get all assets that have organization ownership but no branch_id
stmt = select(Asset).where(
(Asset.owner_org_id.is_not(None) | Asset.operator_org_id.is_not(None)),
Asset.branch_id.is_(None)
)
result = await session.execute(stmt)
assets = result.scalars().all()
logger.info(f"Found {len(assets)} assets without branch assignment")
updated_count = 0
skipped_count = 0
for asset in assets:
# Determine which organization to use (prefer owner, fallback to operator)
org_id = asset.owner_org_id or asset.operator_org_id
if not org_id:
logger.warning(f"Asset {asset.id} has no organization reference, skipping")
skipped_count += 1
continue
# Get or create default branch for the organization
branch = await get_or_create_default_branch(session, org_id)
# Update the asset
update_stmt = (
update(Asset)
.where(Asset.id == asset.id)
.values(branch_id=branch.id, relocation_performed=True)
)
await session.execute(update_stmt)
logger.info(f"Updated asset {asset.id} with branch {branch.id} (org {org_id})")
updated_count += 1
# Commit all changes
await session.commit()
logger.info(f"Migration completed: {updated_count} assets updated, {skipped_count} skipped")
# Also update assets that already have branch_id but need relocation_performed flag
if updated_count > 0:
stmt = select(Asset).where(
Asset.branch_id.is_not(None),
Asset.relocation_performed == False
)
result = await session.execute(stmt)
assets_without_flag = result.scalars().all()
for asset in assets_without_flag:
update_stmt = (
update(Asset)
.where(Asset.id == asset.id)
.values(relocation_performed=True)
)
await session.execute(update_stmt)
await session.commit()
logger.info(f"Updated relocation_performed flag for {len(assets_without_flag)} assets")
return updated_count
except Exception as e:
await session.rollback()
logger.error(f"Migration failed: {e}")
raise
async def verify_migration():
"""Verify the migration results."""
async with AsyncSessionLocal() as session:
# Count assets with branch_id
stmt = select(Asset).where(Asset.branch_id.is_not(None))
result = await session.execute(stmt)
assets_with_branch = result.scalars().all()
# Count assets without branch_id but with organizations
stmt = select(Asset).where(
(Asset.owner_org_id.is_not(None) | Asset.operator_org_id.is_not(None)),
Asset.branch_id.is_(None)
)
result = await session.execute(stmt)
assets_still_missing = result.scalars().all()
logger.info(f"Verification:")
logger.info(f" - Assets with branch_id: {len(assets_with_branch)}")
logger.info(f" - Assets still missing branch_id: {len(assets_still_missing)}")
if assets_still_missing:
logger.warning("Some assets still missing branch_id:")
for asset in assets_still_missing[:5]: # Show first 5
logger.warning(f" Asset {asset.id}: owner_org={asset.owner_org_id}, operator_org={asset.operator_org_id}")
return len(assets_with_branch), len(assets_still_missing)
if __name__ == "__main__":
logger.info("Starting vehicle-to-garage migration...")
try:
# Run migration
updated = asyncio.run(migrate_vehicles_to_garages())
# Verify
with_branch, missing = asyncio.run(verify_migration())
if missing == 0:
logger.info("✅ Migration successful! All organizational vehicles now have branch assignments.")
else:
logger.warning(f"⚠️ Migration incomplete: {missing} assets still lack branch_id")
sys.exit(1)
except Exception as e:
logger.error(f"❌ Migration failed: {e}")
sys.exit(1)

View File

@@ -0,0 +1,62 @@
#!/usr/bin/env python3
"""
Reset password for tester_pro@profibot.hu to 'Password123!'
"""
import sys
import os
sys.path.insert(0, '/app/backend')
from app.core.security import get_password_hash
from sqlalchemy import create_engine, text
from sqlalchemy.orm import sessionmaker
# Database URL from environment
DATABASE_URL = "postgresql+psycopg2://kincses:MiskociA74@shared-postgres:5432/service_finder"
def reset_password():
"""Reset password for tester_pro@profibot.hu"""
engine = create_engine(DATABASE_URL)
Session = sessionmaker(bind=engine)
session = Session()
try:
# Get password hash for 'Password123!'
password_hash = get_password_hash("Password123!")
print(f"Password hash for 'Password123!': {password_hash}")
# Update the user
update_stmt = text("""
UPDATE identity.users
SET hashed_password = :password_hash
WHERE email = :email
""")
result = session.execute(
update_stmt,
{"password_hash": password_hash, "email": "tester_pro@profibot.hu"}
)
session.commit()
if result.rowcount > 0:
print(f"Successfully updated password for tester_pro@profibot.hu")
return True
else:
print(f"User not found: tester_pro@profibot.hu")
return False
except Exception as e:
print(f"Error: {e}")
session.rollback()
return False
finally:
session.close()
if __name__ == "__main__":
print("Resetting password for tester_pro@profibot.hu...")
if reset_password():
print("Password reset successful")
sys.exit(0)
else:
print("Password reset failed")
sys.exit(1)

View File

@@ -0,0 +1,108 @@
#!/usr/bin/env python3
"""
Test the assets API endpoint directly.
"""
import asyncio
import aiohttp
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
async def test_assets_api():
base_url = "http://sf_api:8000"
# First, login to get token
async with aiohttp.ClientSession() as session:
# Login
login_data = {
"username": "tester_pro@profibot.hu",
"password": "Test123!"
}
print("1. Logging in...")
async with session.post(f"{base_url}/api/v1/auth/login", data=login_data) as resp:
if resp.status != 200:
print(f"❌ Login failed: {resp.status}")
text = await resp.text()
print(f"Response: {text}")
return
login_result = await resp.json()
token = login_result["access_token"]
print(f"✅ Login successful, token: {token[:50]}...")
# Set personal mode (scope_id = null)
print("\n2. Setting personal mode (scope_id = null)...")
headers = {"Authorization": f"Bearer {token}"}
patch_data = {"organization_id": None}
async with session.patch(
f"{base_url}/api/v1/users/me/active-organization",
json=patch_data,
headers=headers
) as resp:
if resp.status != 200:
print(f"❌ PATCH failed: {resp.status}")
text = await resp.text()
print(f"Response: {text}")
return
patch_result = await resp.json()
print(f"✅ PATCH successful")
print(f" scope_id: {patch_result.get('scope_id')}")
print(f" scope_level: {patch_result.get('scope_level')}")
# Get vehicles in personal mode
print("\n3. Getting vehicles in personal mode...")
async with session.get(
f"{base_url}/api/v1/assets/vehicles",
headers=headers
) as resp:
if resp.status != 200:
print(f"❌ GET vehicles failed: {resp.status}")
text = await resp.text()
print(f"Response: {text}")
return
vehicles_result = await resp.json()
print(f"✅ GET vehicles successful")
print(f" Found {len(vehicles_result)} vehicles")
for i, vehicle in enumerate(vehicles_result[:5]): # Show first 5
print(f" {i+1}. {vehicle.get('license_plate')} (ID: {vehicle.get('id')})")
if len(vehicles_result) > 5:
print(f" ... and {len(vehicles_result) - 5} more")
# Now test corporate mode (org_id = 15)
print("\n4. Setting corporate mode (org_id = 15)...")
patch_data_corp = {"organization_id": "15"}
async with session.patch(
f"{base_url}/api/v1/users/me/active-organization",
json=patch_data_corp,
headers=headers
) as resp:
if resp.status != 200:
print(f"❌ PATCH corporate failed: {resp.status}")
text = await resp.text()
print(f"Response: {text}")
return
patch_corp_result = await resp.json()
print(f"✅ PATCH corporate successful")
print(f" scope_id: {patch_corp_result.get('scope_id')}")
# Get vehicles in corporate mode
print("\n5. Getting vehicles in corporate mode...")
async with session.get(
f"{base_url}/api/v1/assets/vehicles",
headers=headers
) as resp:
if resp.status != 200:
print(f"❌ GET corporate vehicles failed: {resp.status}")
text = await resp.text()
print(f"Response: {text}")
return
vehicles_corp_result = await resp.json()
print(f"✅ GET corporate vehicles successful")
print(f" Found {len(vehicles_corp_result)} vehicles")
for i, vehicle in enumerate(vehicles_corp_result[:5]):
print(f" {i+1}. {vehicle.get('license_plate')}")
if __name__ == "__main__":
asyncio.run(test_assets_api())

View File

@@ -0,0 +1,175 @@
#!/usr/bin/env python3
"""
Backend verification test for scope switcher functionality.
Simulates the frontend:
1. Login as User 28 (tester_pro@profibot.hu) - get JWT token
2. Call PATCH /active-organization with organization_id = null
3. Call GET /vehicles - assert it returns exactly 1 car (MNO-345)
4. Call PATCH /active-organization with organization_id = 15
5. Call GET /vehicles - assert it returns exactly 1 car (PQR-678)
Run inside sf_api container:
docker compose exec sf_api python -m app.scripts.verify_scope_switcher
"""
import asyncio
import sys
import httpx
import json
from typing import Dict, Any
# Add the backend directory to the path
sys.path.insert(0, '/app')
from app.db.session import AsyncSessionLocal
from app.models.identity import User
from app.core.security import create_tokens
from app.core.config import settings
async def get_auth_token() -> str:
"""Get JWT token for test user by simulating login."""
async with AsyncSessionLocal() as db:
# Find the test user
from sqlalchemy import select
stmt = select(User).where(User.email == "tester_pro@profibot.hu")
result = await db.execute(stmt)
user = result.scalar_one_or_none()
if not user:
raise Exception("Test user not found")
# Create token directly (simulating login)
from app.core.security import DEFAULT_RANK_MAP
ranks = DEFAULT_RANK_MAP # Simplified
token_data = {
"sub": str(user.id),
"role": user.role.value if hasattr(user.role, 'value') else str(user.role),
"rank": ranks.get(user.role.value.upper(), 10),
"scope_level": user.scope_level or "individual",
"scope_id": str(user.scope_id) if user.scope_id else str(user.id)
}
access_token, _ = create_tokens(data=token_data)
return access_token
async def make_api_request(method: str, endpoint: str, token: str, data: Dict = None) -> Dict[str, Any]:
"""Make HTTP request to the API."""
base_url = "http://localhost:8000/api/v1"
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
async with httpx.AsyncClient(timeout=30.0) as client:
url = f"{base_url}{endpoint}"
if method == "GET":
response = await client.get(url, headers=headers)
elif method == "PATCH":
response = await client.patch(url, headers=headers, json=data)
else:
raise ValueError(f"Unsupported method: {method}")
response.raise_for_status()
return response.json()
async def main():
"""Main verification test."""
print("=" * 60)
print("SCOPE SWITCHER VERIFICATION TEST")
print("=" * 60)
try:
# 1. Get auth token
print("\n1. AUTHENTICATING AS TEST USER...")
token = await get_auth_token()
print(f" ✅ Token obtained (length: {len(token)})")
# 2. Test personal mode (organization_id = null)
print("\n2. TESTING PERSONAL MODE (organization_id = null)...")
patch_data = {"organization_id": None}
patch_response = await make_api_request("PATCH", "/users/me/active-organization", token, patch_data)
print(f" ✅ PATCH response: {json.dumps(patch_response, indent=2)}")
# Check if scope_id was updated
if patch_response.get("scope_id") is not None:
print(" ❌ ERROR: scope_id should be null in personal mode!")
return
# 3. Get vehicles in personal mode
print("\n3. GETTING VEHICLES IN PERSONAL MODE...")
vehicles_response = await make_api_request("GET", "/assets/vehicles", token)
vehicles_count = len(vehicles_response)
print(f" ✅ Found {vehicles_count} vehicles")
# Check license plates
license_plates = [v.get("license_plate") for v in vehicles_response if v.get("license_plate")]
print(f" ✅ License plates: {license_plates}")
# Should have exactly 1 car (MNO-345)
if vehicles_count != 1:
print(f" ❌ ERROR: Expected 1 vehicle in personal mode, got {vehicles_count}")
return
if "MNO-345" not in license_plates:
print(f" ❌ ERROR: Expected MNO-345 in personal mode, got {license_plates}")
return
print(" ✅ PERSONAL MODE TEST PASSED: Exactly 1 car (MNO-345)")
# 4. Test corporate mode (organization_id = 15)
print("\n4. TESTING CORPORATE MODE (organization_id = 15)...")
patch_data = {"organization_id": 15}
patch_response = await make_api_request("PATCH", "/users/me/active-organization", token, patch_data)
print(f" ✅ PATCH response: {json.dumps(patch_response, indent=2)}")
# Check if scope_id was updated
if patch_response.get("scope_id") != "15":
print(f" ❌ ERROR: scope_id should be '15', got {patch_response.get('scope_id')}")
return
# 5. Get vehicles in corporate mode
print("\n5. GETTING VEHICLES IN CORPORATE MODE...")
vehicles_response = await make_api_request("GET", "/assets/vehicles", token)
vehicles_count = len(vehicles_response)
print(f" ✅ Found {vehicles_count} vehicles")
# Check license plates
license_plates = [v.get("license_plate") for v in vehicles_response if v.get("license_plate")]
print(f" ✅ License plates: {license_plates}")
# Should have exactly 1 car (PQR-678)
if vehicles_count != 1:
print(f" ❌ ERROR: Expected 1 vehicle in corporate mode, got {vehicles_count}")
return
if "PQR-678" not in license_plates:
print(f" ❌ ERROR: Expected PQR-678 in corporate mode, got {license_plates}")
return
print(" ✅ CORPORATE MODE TEST PASSED: Exactly 1 car (PQR-678)")
# 6. Final verification
print("\n" + "=" * 60)
print("✅ ALL TESTS PASSED!")
print("=" * 60)
print("\nSUMMARY:")
print("- Personal mode (scope_id = null): Shows 1 vehicle (MNO-345)")
print("- Corporate mode (scope_id = 15): Shows 1 vehicle (PQR-678)")
print("- Scope switcher endpoint works correctly")
print("- Vehicle filtering by branch/organization works correctly")
except httpx.HTTPStatusError as e:
print(f"\n❌ HTTP ERROR: {e}")
print(f"Response: {e.response.text}")
return
except Exception as e:
print(f"\n❌ ERROR: {type(e).__name__}: {e}")
import traceback
traceback.print_exc()
return
if __name__ == "__main__":
asyncio.run(main())