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

@@ -30,34 +30,69 @@ async def get_user_vehicles(
This endpoint returns a paginated list of vehicles that the authenticated user
has access to (either as owner or through organization membership).
Garage-centric logic: In corporate mode, only returns vehicles physically
parked in the active organization's garages (branches).
"""
# Query assets where user is owner or organization member
from sqlalchemy import or_
from sqlalchemy import or_, select
# First, get user's organization memberships
from app.models.marketplace.organization import OrganizationMember
org_stmt = select(OrganizationMember.organization_id).where(
OrganizationMember.user_id == current_user.id
)
org_result = await db.execute(org_stmt)
user_org_ids = [row[0] for row in org_result.all()]
# Build query: assets owned by user OR assets in user's organizations
stmt = (
select(Asset)
.where(
or_(
Asset.owner_person_id == current_user.id,
Asset.owner_org_id.in_(user_org_ids) if user_org_ids else False,
Asset.operator_person_id == current_user.id,
Asset.operator_org_id.in_(user_org_ids) if user_org_ids else False
if current_user.scope_id is None:
# Personal mode: only show vehicles owned/operated personally (no organization)
stmt = (
select(Asset)
.where(
or_(
Asset.owner_org_id.is_(None),
Asset.operator_org_id.is_(None)
),
or_(
Asset.owner_person_id == current_user.person_id,
Asset.operator_person_id == current_user.person_id
)
)
.order_by(Asset.created_at.desc())
.offset(skip)
.limit(limit)
.options(selectinload(Asset.catalog))
)
else:
# Corporate mode: only show vehicles belonging to the active organization's garages
try:
scope_org_id = int(current_user.scope_id)
except (ValueError, TypeError):
# If scope_id is not a valid integer, treat as no organization
scope_org_id = None
if scope_org_id is None:
# Fallback: no valid organization, return empty list
return []
# First, get all branch IDs (garages) for this organization
from app.models.marketplace.organization import Branch
branch_stmt = select(Branch.id).where(
Branch.organization_id == scope_org_id,
Branch.is_deleted == False,
Branch.status == "active"
)
branch_result = await db.execute(branch_stmt)
branch_ids = [row[0] for row in branch_result.all()]
if not branch_ids:
# Organization has no active garages, return empty list
return []
# Query assets that are in any of the organization's garages
stmt = (
select(Asset)
.where(
Asset.branch_id.in_(branch_ids),
Asset.status == "active"
)
.order_by(Asset.created_at.desc())
.offset(skip)
.limit(limit)
.options(selectinload(Asset.catalog))
)
.order_by(Asset.created_at.desc())
.offset(skip)
.limit(limit)
.options(selectinload(Asset.catalog))
)
result = await db.execute(stmt)
assets = result.scalars().all()
@@ -170,25 +205,16 @@ async def create_or_claim_vehicle(
- XP jutalom adása a felhasználónak
"""
try:
# Determine organization ID: use provided or default to user's first organization
org_id = payload.organization_id
if org_id is None:
# Get user's organization memberships
from app.models.marketplace.organization import OrganizationMember
org_stmt = select(OrganizationMember.organization_id).where(
OrganizationMember.user_id == current_user.id
).limit(1)
org_result = await db.execute(org_stmt)
user_org = org_result.scalar_one_or_none()
if user_org is None:
# User has no organization - create a personal organization or use default
# For now, raise an error (in future, we could create a personal org)
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="No organization found for user. Please specify an organization_id or join/create an organization first."
)
org_id = user_org
# Determine organization ID based on user's active scope (garage isolation)
# The owner_org_id MUST be set to the current_user.scope_id.
# If the scope is null, it stays null (personal).
org_id = None
if current_user.scope_id is not None:
try:
org_id = int(current_user.scope_id)
except (ValueError, TypeError):
# If scope_id is not a valid integer, treat as personal (no organization)
pass
asset = await AssetService.create_or_claim_vehicle(
db=db,

View File

@@ -106,7 +106,7 @@ async def onboard_organization(
return {"organization_id": new_org.id, "status": new_org.status}
@router.get("/my", response_model=List[CorpOnboardResponse])
@router.get("/my", response_model=List[dict])
async def get_my_organizations(
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user)
@@ -120,4 +120,19 @@ async def get_my_organizations(
result = await db.execute(stmt)
orgs = result.scalars().all()
return [{"organization_id": o.id, "status": o.status} for o in orgs]
# Return full organization details
return [
{
"organization_id": o.id,
"status": o.status,
"name": o.name,
"full_name": o.full_name,
"display_name": o.display_name,
"tax_number": o.tax_number,
"country_code": o.country_code,
"is_active": o.is_active,
"is_deleted": o.is_deleted,
"subscription_plan": o.subscription_plan
}
for o in orgs
]

View File

@@ -1,10 +1,11 @@
#/opt/docker/dev/service_finder/backend/app/api/v1/endpoints/users.py
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.exc import SQLAlchemyError
from typing import Dict, Any
from app.api.deps import get_db, get_current_user
from app.schemas.user import UserResponse, UserUpdate
from app.schemas.user import UserResponse, UserUpdate, ActiveOrganizationUpdate
from app.models.identity import User
from app.services.trust_engine import TrustEngine
@@ -70,8 +71,12 @@ async def read_users_me(
# Create a response dictionary with the active_organization_id
# Get first_name and last_name from person relation if available
person = current_user.person
first_name = person.first_name if person else ""
last_name = person.last_name if person else ""
# Safe extraction with fallback to empty string
first_name = ""
last_name = ""
if person:
first_name = getattr(person, 'first_name', '')
last_name = getattr(person, 'last_name', '')
response_data = {
"id": current_user.id,
@@ -141,6 +146,59 @@ async def update_user_preferences(
else:
raise HTTPException(status_code=400, detail=f"Invalid field: {field}")
await db.commit()
await db.refresh(current_user)
return current_user
try:
await db.commit()
await db.refresh(current_user)
except SQLAlchemyError as e:
await db.rollback()
raise HTTPException(status_code=500, detail=f"Database error: {str(e)}")
# Return the Pydantic model instead of raw SQLAlchemy object
return UserResponse.model_validate(current_user)
@router.patch("/me/active-organization", response_model=UserResponse)
async def update_active_organization(
update_data: ActiveOrganizationUpdate,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""
Update the user's active organization (scope_id).
Accepts an organization_id (UUID/string) or None to revert to personal mode.
"""
# Extract organization_id from request
org_id = update_data.organization_id
# Validate that the user has access to this organization if org_id is provided
if org_id is not None:
from sqlalchemy import select
from app.models.marketplace.organization import OrganizationMember
# Check if user is a member of the organization
stmt = select(OrganizationMember).where(
OrganizationMember.organization_id == org_id,
OrganizationMember.user_id == current_user.id
)
result = await db.execute(stmt)
member = result.scalar_one_or_none()
if not member:
raise HTTPException(
status_code=403,
detail="You are not a member of this organization"
)
# Update user's scope_id
current_user.scope_id = org_id
try:
await db.commit()
await db.refresh(current_user)
except SQLAlchemyError as e:
await db.rollback()
raise HTTPException(status_code=500, detail=f"Database error: {str(e)}")
# Return updated user data
return UserResponse.model_validate(current_user)