admin felület fejlesztése garázs, előfizeztési csomagok

This commit is contained in:
Roo
2026-06-25 01:58:04 +00:00
parent 80a5d67f79
commit 52011606ff
334 changed files with 46636 additions and 1606 deletions

View File

@@ -28,7 +28,7 @@ from .vehicle.asset import Asset, AssetCatalog, AssetEvent, AssetAssignment, Ass
from .fleet_finance import AssetCost, CostCategory, AssetFinancials, InsuranceProvider, VehicleInsurancePolicy, VehicleTaxObligation
# 6. Üzleti logika és előfizetések
from .core_logic import SubscriptionTier, OrganizationSubscription, CreditTransaction, ServiceSpecialty
from .core_logic import SubscriptionTier, OrganizationSubscription, CreditTransaction, ServiceSpecialty, RegionConfig
from .marketplace.payment import PaymentIntent, PaymentIntentStatus
from .marketplace.finance import Issuer, IssuerType
@@ -81,7 +81,7 @@ __all__ = [
"ServiceProvider", "Vote", "Competition", "UserScore", "ServiceReview", "ModerationStatus", "SourceType",
"Document", "Translation", "PendingAction", "ActionStatus",
"SubscriptionTier", "OrganizationSubscription", "CreditTransaction", "ServiceSpecialty",
"SubscriptionTier", "OrganizationSubscription", "CreditTransaction", "ServiceSpecialty", "RegionConfig",
"PaymentIntent", "PaymentIntentStatus",
"AuditLog", "VehicleOwnership", "LogSeverity",
"SecurityAuditLog", "OperationalLog", "ProcessLog",

View File

@@ -1,7 +1,7 @@
# /opt/docker/dev/service_finder/backend/app/models/core_logic.py
from typing import Optional, List, Any
from datetime import datetime # Python saját típusa a típusjelöléshez
from sqlalchemy import String, Integer, ForeignKey, Boolean, DateTime, Numeric, text
from sqlalchemy import String, Integer, ForeignKey, Boolean, DateTime, Numeric, text, Float
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.sql import func
@@ -9,9 +9,65 @@ from sqlalchemy.sql import func
# MB 2.0: A központi aszinkron adatbázis motorból húzzuk be a Base-t
from app.database import Base
# ═══════════════════════════════════════════════════════════════════════════════
# P0 SYSTEM ARCHITECTURE: Global Region Registry
# ═══════════════════════════════════════════════════════════════════════════════
class RegionConfig(Base):
"""
🌍 Global Region Configuration Registry.
Central table for L10n/i18n configuration. Drives all UI formatting
(dates, numbers, currencies) and financial calculations (VAT, pricing).
Each row represents a country/region with its own locale, currency,
VAT rate, and timezone settings.
Schema: system.region_config
"""
__tablename__ = "region_config"
__table_args__ = {"schema": "system"}
country_code: Mapped[str] = mapped_column(
String(10), primary_key=True, index=True,
comment="ISO 3166-1 alpha-2 country code, e.g. 'HU', 'GB', or 'DEFAULT' for fallback"
)
name: Mapped[str] = mapped_column(
String, nullable=False,
comment="Human-readable region name, e.g. 'Hungary', 'United Kingdom'"
)
currency: Mapped[str] = mapped_column(
String(3), nullable=False,
comment="ISO 4217 currency code, e.g. 'HUF', 'GBP', 'EUR'"
)
default_vat_rate: Mapped[float] = mapped_column(
Float, nullable=False, default=0.0,
comment="Default VAT rate as decimal, e.g. 27.0 for 27%"
)
locale_code: Mapped[str] = mapped_column(
String(10), nullable=False,
comment="BCP 47 locale code, e.g. 'hu-HU', 'en-GB', 'de-DE'"
)
timezone: Mapped[str] = mapped_column(
String(50), nullable=False,
comment="IANA timezone, e.g. 'Europe/Budapest', 'Europe/London'"
)
is_active: Mapped[bool] = mapped_column(
Boolean, nullable=False, default=True, server_default=text("true"),
comment="Soft-toggle for region availability"
)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now()
)
updated_at: Mapped[Optional[datetime]] = mapped_column(
DateTime(timezone=True), onupdate=func.now()
)
class SubscriptionTier(Base):
"""
Előfizetési csomagok definíciója (pl. Free, Premium, VIP).
"""
Előfizetési csomagok definíciója (pl. Free, Premium, VIP).
A csomagok határozzák meg a korlátokat (pl. max járműszám).
"""
__tablename__ = "subscription_tiers"
@@ -32,6 +88,42 @@ class SubscriptionTier(Base):
server_default=text("'{}'::jsonb")
)
# ── P0 AUDIT FIX: Database-driven tier level instead of hardcoded string mapping ──
# Numeric level for the entitlement hierarchy: 0=free, 1=premium, 2=enterprise.
# This replaces the hardcoded _map_tier_name() string-matching logic.
# New tiers can be added with any name; the tier_level determines their position.
tier_level: Mapped[int] = mapped_column(
Integer,
nullable=False,
default=0,
server_default=text("0"),
comment="Entitlement hierarchy level: 0=free, 1=premium, 2=enterprise"
)
# ── P0 SINGLETON PACKAGE RULES: is_default_fallback ──
# Ha True, ez a csomag az alapértelmezett fallback csomag, amit a
# subscription_service használ, ha a user előfizetése lejárt.
# MAXIMUM EGY csomag lehet True értékkel (singleton).
is_default_fallback: Mapped[bool] = mapped_column(
Boolean,
nullable=False,
default=False,
server_default=text("false"),
comment="Singleton: maximum one tier can be the default fallback package"
)
# ── P0 SINGLETON PACKAGE RULES: trial_days_on_signup ──
# Ha > 0, ez a csomag a próbaidős csomag, amit új regisztrációnál
# automatikusan hozzárendelünk a felhasználóhoz.
# MAXIMUM EGY csomag lehet > 0 értékkel (singleton).
trial_days_on_signup: Mapped[int] = mapped_column(
Integer,
nullable=False,
default=0,
server_default=text("0"),
comment="Singleton: maximum one tier can have trial_days_on_signup > 0"
)
class OrganizationSubscription(Base):
"""
Szervezetek aktuális előfizetései és azok érvényessége.
@@ -56,6 +148,13 @@ class OrganizationSubscription(Base):
# Példa: {"extra_vehicles": 1, "extra_garages": 1, "extra_credits": 100}
extra_allowances: Mapped[dict] = mapped_column(JSONB, server_default=text("'{}'::jsonb"), default=dict)
# ── P0 FEATURE FLAG: relationship a SubscriptionTier-hez ──
tier: Mapped[Optional["SubscriptionTier"]] = relationship(
"SubscriptionTier",
foreign_keys=[tier_id],
lazy="selectin",
)
class UserSubscription(Base):
"""
Felhasználók aktuális előfizetései és azok érvényessége.
@@ -78,6 +177,13 @@ class UserSubscription(Base):
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
updated_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
# ── P0 FEATURE FLAG: relationship a SubscriptionTier-hez ──
tier: Mapped[Optional["SubscriptionTier"]] = relationship(
"SubscriptionTier",
foreign_keys=[tier_id],
lazy="selectin",
)
class CreditTransaction(Base):
"""

View File

@@ -27,6 +27,7 @@ if TYPE_CHECKING:
from app.models.vehicle.asset import Asset
from app.models.marketplace.organization import Organization
from app.models.system.document import Document
from app.models.identity.social import ServiceProvider
# =============================================================================
@@ -138,6 +139,10 @@ class AssetCost(Base):
vendor_organization_id: Mapped[Optional[int]] = mapped_column(
Integer, ForeignKey("fleet.organizations.id"), nullable=True, index=True
)
# P0 HYBRID VENDOR REFACTOR: Új elsődleges hivatkozás a marketplace.service_providers táblára
service_provider_id: Mapped[Optional[int]] = mapped_column(
Integer, ForeignKey("marketplace.service_providers.id"), nullable=True, index=True
)
# Szabadon gépelhető beszállító név (ha nincs a rendszerben)
external_vendor_name: Mapped[Optional[str]] = mapped_column(String(200), nullable=True)
@@ -162,6 +167,11 @@ class AssetCost(Base):
"Organization", foreign_keys=[vendor_organization_id]
)
# P0 HYBRID VENDOR REFACTOR: ServiceProvider kapcsolat
service_provider: Mapped[Optional["ServiceProvider"]] = relationship(
"ServiceProvider", foreign_keys=[service_provider_id]
)
# =============================================================================
# AssetFinancials (áthelyezve vehicle -> fleet_finance, kibővítve)

View File

@@ -170,6 +170,14 @@ class User(Base):
scope_level: Mapped[str] = mapped_column(String(30), server_default="individual")
scope_id: Mapped[Optional[str]] = mapped_column(String(50))
# ── P0: Scope-Based Admin RBAC ──
# Defines the admin's scope type and value for organization-level access control.
# scope_type: "GLOBAL", "REGION", or "SEGMENT"
# scope_value: The specific value (e.g., "Pest_County", "Fleet", "Budapest")
scope_type: Mapped[Optional[str]] = mapped_column(String(30), nullable=True, server_default=None)
scope_value: Mapped[Optional[str]] = mapped_column(String(100), nullable=True, server_default=None)
custom_permissions: Mapped[Any] = mapped_column(JSON, server_default=text("'{}'::jsonb"))
# E-mail történetiség és alternatív e-mailek (MB 2.0.1)

View File

@@ -20,7 +20,11 @@ class SourceType(str, enum.Enum):
api_import = "import"
class ServiceProvider(Base):
""" Közösség által beküldött szolgáltatók (v1.3.1). """
""" Közösség által beküldött szolgáltatók (v1.3.1).
P0 HYBRID VENDOR REFACTOR: Kibővítve atomizált címmezőkkel és
kapcsolatfelvételi adatokkal a quick_add_provider() refactorhoz.
"""
__tablename__ = "service_providers"
__table_args__ = {"schema": "marketplace"}
@@ -29,6 +33,19 @@ class ServiceProvider(Base):
address: Mapped[str] = mapped_column(String, nullable=False)
category: Mapped[Optional[str]] = mapped_column(String)
# === P0 HYBRID VENDOR REFACTOR: Atomizált címmezők ===
city: Mapped[Optional[str]] = mapped_column(String(100), nullable=True)
address_zip: Mapped[Optional[str]] = mapped_column(String(20), nullable=True)
address_street_name: Mapped[Optional[str]] = mapped_column(String(150), nullable=True)
address_street_type: Mapped[Optional[str]] = mapped_column(String(50), nullable=True)
address_house_number: Mapped[Optional[str]] = mapped_column(String(20), nullable=True)
plus_code: Mapped[Optional[str]] = mapped_column(String(20), nullable=True)
# === P0 HYBRID VENDOR REFACTOR: Kapcsolatfelvételi adatok ===
contact_phone: Mapped[Optional[str]] = mapped_column(String(50), nullable=True)
contact_email: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
website: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
status: Mapped[ModerationStatus] = mapped_column(
PG_ENUM(ModerationStatus, name="moderation_status", inherit_schema=True),
default=ModerationStatus.pending

View File

@@ -113,6 +113,12 @@ class Organization(Base):
country_code: Mapped[str] = mapped_column(String(2), default="HU")
language: Mapped[str] = mapped_column(String(5), default="hu")
# ── P0: Scope-Based Admin RBAC ──
# Geographic region for scope-based admin access (e.g., "Pest_County", "Budapest")
region: Mapped[Optional[str]] = mapped_column(String(100), nullable=True, index=True)
# Business segment for scope-based admin access (e.g., "Fleet", "Dealer", "Service")
segment: Mapped[Optional[str]] = mapped_column(String(100), nullable=True, index=True)
address_zip: Mapped[Optional[str]] = mapped_column(String(10))
address_city: Mapped[Optional[str]] = mapped_column(String(100))
address_street_name: Mapped[Optional[str]] = mapped_column(String(150))

View File

@@ -28,6 +28,10 @@ class ServiceProfile(Base):
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
organization_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("fleet.organizations.id"), unique=True)
# P0 HYBRID VENDOR REFACTOR: Új elsődleges hivatkozás a marketplace.service_providers táblára
service_provider_id: Mapped[Optional[int]] = mapped_column(
Integer, ForeignKey("marketplace.service_providers.id"), nullable=True, index=True
)
parent_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("marketplace.service_profiles.id"))
fingerprint: Mapped[str] = mapped_column(String(255), index=True, nullable=False)