admin felület bekötése. pontosítások2

This commit is contained in:
Roo
2026-06-27 09:08:18 +00:00
parent c75c7b2270
commit 1e6f79ca22
189 changed files with 4624 additions and 1116 deletions

View File

@@ -52,6 +52,19 @@ class OrgSubscriptionUpdate(BaseModel):
description="Egyedi lejárati dátum (pl. 2-5 éves B2B deal esetén). "
"Ha None, a csomag duration.days alapján számolódik."
)
# ── P0 ADD-ON UPGRADE: Extra allowances (add-ons) ──
extra_vehicles: int = Field(
default=0, ge=0,
description="Extra jármű kvóta a csomag keretein felül (add-on)."
)
extra_branches: int = Field(
default=0, ge=0,
description="Extra telephely kvóta a csomag keretein felül (add-on)."
)
extra_users: int = Field(
default=0, ge=0,
description="Extra dolgozó kvóta a csomag keretein felül (add-on)."
)
class OrgSubscriptionResponse(BaseModel):
@@ -122,6 +135,19 @@ class SubscriptionSummary(BaseModel):
is_active: bool = True
asset_count: int = 0
asset_limit: int = 1
# ── P0 ADD-ON UPGRADE: Branch & User limits ──
branch_limit: int = 0
user_limit: int = 0
# ── P0 ADD-ON UPGRADE: Extra allowances (add-ons) ──
extra_allowances: dict = {}
class BranchBrief(BaseModel):
"""Rövid telephely adatok a frontend számára."""
id: int
name: str
city: Optional[str] = None
is_active: bool = True
class GarageDetailsResponse(BaseModel):
@@ -166,8 +192,14 @@ class GarageDetailsResponse(BaseModel):
# Meta
created_at: Optional[str] = None
member_count: int = 0
# P0: Owner user ID for frontend display
owner_user_id: Optional[int] = None
# P0: Owner person ID for frontend display
owner_person_id: Optional[int] = None
# P0: Full member list with nested person data
members: List[OrganizationMemberResponse] = []
# ── P0 ADD-ON UPGRADE: Branches list for utilization display ──
branches: List[BranchBrief] = []
# =============================================================================
@@ -217,7 +249,18 @@ async def list_organizations(
if status_filter:
base_query = base_query.where(Organization.status == status_filter)
if org_type_filter:
base_query = base_query.where(Organization.org_type == org_type_filter)
if org_type_filter == "corporate":
# P0 HOTFIX: "corporate" is a UI category, not a DB enum value.
# The DB enum (fleet.orgtype) has: individual, business, fleet_owner,
# service, service_provider, club. "corporate" means all non-individual types.
from app.models.marketplace.organization import OrgType
corporate_types = [
t.value for t in OrgType
if t.value != OrgType.individual.value
]
base_query = base_query.where(Organization.org_type.in_(corporate_types))
else:
base_query = base_query.where(Organization.org_type == org_type_filter)
if tier_name_filter:
from app.models.core_logic import SubscriptionTier
base_query = base_query.join(SubscriptionTier, Organization.subscription_tier_id == SubscriptionTier.id)
@@ -395,13 +438,15 @@ async def get_organization_details(
detail=f"Szervezet nem található ID-vel: {org_id}",
)
# 2. Aktív előfizetés lekérése
# 2. Aktív előfizetés lekérése (P0 ADD-ON: valid_until >= now() check)
now = datetime.utcnow()
sub_stmt = (
select(OrganizationSubscription)
.options(selectinload(OrganizationSubscription.tier))
.where(
OrganizationSubscription.org_id == org_id,
OrganizationSubscription.is_active == True,
OrganizationSubscription.valid_until >= now,
)
.order_by(OrganizationSubscription.id.desc())
.limit(1)
@@ -409,6 +454,33 @@ async def get_organization_details(
sub_result = await db.execute(sub_stmt)
active_sub = sub_result.scalar_one_or_none()
# 2b. P0 ADD-ON: Valós járműszám lekérése a fleet táblából
from app.models.fleet.vehicle import Vehicle # noqa: E402
vehicle_count_stmt = select(func.count(Vehicle.id)).where(
Vehicle.organization_id == org_id,
Vehicle.is_deleted == False,
)
vehicle_count_result = await db.execute(vehicle_count_stmt)
asset_count = vehicle_count_result.scalar() or 0
# 2c. P0 ADD-ON: Telephelyek (branches) lekérése
from app.models.fleet.organization import Branch # noqa: E402
branches_stmt = select(Branch).where(
Branch.organization_id == org_id,
Branch.is_deleted == False,
).order_by(Branch.name)
branches_result = await db.execute(branches_stmt)
branch_rows = branches_result.scalars().all()
branches_list = [
BranchBrief(
id=b.id,
name=b.name,
city=b.city,
is_active=b.is_active,
)
for b in branch_rows
]
# 3. Tagok lekérése eager loading-gal (User + Person)
members_stmt = (
select(OrganizationMember)
@@ -440,36 +512,59 @@ async def get_organization_details(
contact_result = await db.execute(contact_stmt)
contact = contact_result.scalar_one_or_none()
# 5. Subscription összefoglaló
# 5. Subscription összefoglaló — P0 ADD-ON UPGRADE
# Fix: explicit dict.get() instead of `or` (bugfix: 0 is falsy in Python)
# Add: extra_allowances math (Base + Extra)
subscription_summary = None
if active_sub and active_sub.tier:
rules = active_sub.tier.rules or {}
asset_limit = (
rules.get("max_vehicles")
or active_sub.tier.feature_capabilities.get("max_vehicles")
or 1
)
fc = active_sub.tier.feature_capabilities or {}
extra = active_sub.extra_allowances or {}
# Base limits from tier rules (with explicit .get() — no `or` bug)
base_vehicles = rules.get("max_vehicles", fc.get("max_vehicles", 1))
base_branches = rules.get("max_branches", fc.get("max_branches", 0))
base_users = rules.get("max_users", fc.get("max_users", 0))
# Extra allowances (add-ons)
extra_vehicles = extra.get("extra_vehicles", 0) or 0
extra_branches = extra.get("extra_branches", 0) or 0
extra_users = extra.get("extra_users", 0) or 0
# Total = Base + Extra
asset_limit = base_vehicles + extra_vehicles
branch_limit = base_branches + extra_branches
user_limit = base_users + extra_users
subscription_summary = SubscriptionSummary(
tier_name=active_sub.tier.name,
tier_level=active_sub.tier.tier_level,
valid_from=active_sub.valid_from.isoformat() if active_sub.valid_from else None,
expires_at=active_sub.valid_until.isoformat() if active_sub.valid_until else None,
is_active=active_sub.is_active,
asset_count=0,
asset_count=asset_count,
asset_limit=asset_limit,
branch_limit=branch_limit,
user_limit=user_limit,
extra_allowances=extra,
)
elif org.subscription_tier:
rules = org.subscription_tier.rules or {}
asset_limit = (
rules.get("max_vehicles")
or org.subscription_tier.feature_capabilities.get("max_vehicles")
or 1
)
fc = org.subscription_tier.feature_capabilities or {}
extra = {}
base_vehicles = rules.get("max_vehicles", fc.get("max_vehicles", 1))
base_branches = rules.get("max_branches", fc.get("max_branches", 0))
base_users = rules.get("max_users", fc.get("max_users", 0))
subscription_summary = SubscriptionSummary(
tier_name=org.subscription_tier.name,
tier_level=org.subscription_tier.tier_level,
expires_at=org.subscription_expires_at.isoformat() if org.subscription_expires_at else None,
asset_limit=asset_limit,
asset_count=asset_count,
asset_limit=base_vehicles,
branch_limit=base_branches,
user_limit=base_users,
)
# 6. Kapcsolattartó adatok
@@ -524,6 +619,7 @@ async def get_organization_details(
# Get email from the user record
email = m.user.email if m.user else None
person_data = PersonBrief(
id=person_obj.id,
first_name=person_obj.first_name or "",
last_name=person_obj.last_name or "",
email=email,
@@ -554,6 +650,7 @@ async def get_organization_details(
# we still provide an empty PersonBrief so the frontend never crashes
# on null person access.
owner_person_data = PersonBrief(
id=owner_person.id if owner_person else None,
first_name=owner_person.first_name or "" if owner_person else "",
last_name=owner_person.last_name or "" if owner_person else "",
email=owner_email,
@@ -629,7 +726,12 @@ async def get_organization_details(
primary_contact=primary_contact,
created_at=org.created_at.isoformat() if org.created_at else None,
member_count=member_count,
# P0: Expose owner user ID and person ID for frontend display
owner_user_id=owner.id if owner else None,
owner_person_id=owner_person.id if owner_person else None,
members=members_list,
# ── P0 ADD-ON UPGRADE: Branches list ──
branches=branches_list,
)
@@ -939,17 +1041,27 @@ async def update_org_subscription(
from datetime import timedelta
valid_until = now + timedelta(days=days)
# 5. Létrehozzuk az új subscription rekordot
# 5. P0 ADD-ON: Összeállítjuk az extra_allowances JSONB-t
extra_allowances = {}
if payload.extra_vehicles > 0:
extra_allowances["extra_vehicles"] = payload.extra_vehicles
if payload.extra_branches > 0:
extra_allowances["extra_branches"] = payload.extra_branches
if payload.extra_users > 0:
extra_allowances["extra_users"] = payload.extra_users
# 6. Létrehozzuk az új subscription rekordot
new_sub = OrganizationSubscription(
org_id=org_id,
tier_id=payload.tier_id,
valid_from=now,
valid_until=valid_until,
is_active=True,
extra_allowances=extra_allowances,
)
db.add(new_sub)
# 6. Frissítjük a Organization denormalizált mezőit is
# 7. Frissítjük a Organization denormalizált mezőit is
org.subscription_tier_id = payload.tier_id
org.subscription_expires_at = valid_until
org.subscription_plan = tier.name
@@ -960,7 +1072,8 @@ async def update_org_subscription(
logger.info(
f"Admin {current_user.id} ({current_user.email}) updated subscription for org {org_id}: "
f"tier_id={payload.tier_id}, tier_name={tier.name}, "
f"valid_until={valid_until.isoformat()}"
f"valid_until={valid_until.isoformat()}, "
f"extra_allowances={extra_allowances}"
)
return {
@@ -974,6 +1087,7 @@ async def update_org_subscription(
"valid_from": new_sub.valid_from.isoformat() if new_sub.valid_from else None,
"valid_until": new_sub.valid_until.isoformat() if new_sub.valid_until else None,
"is_active": new_sub.is_active,
"extra_allowances": extra_allowances,
},
}

View File

@@ -36,6 +36,7 @@ class OrganizationMemberUpdate(BaseModel):
class PersonBrief(BaseModel):
"""Nested person data for member responses."""
id: Optional[int] = None
first_name: str = ""
last_name: str = ""
email: Optional[str] = None