admin felület fejlesztése garázs, előfizeztési csomagok
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user