átlagos kiegészítséek jó sok

This commit is contained in:
Roo
2026-03-22 11:02:05 +00:00
parent f53e0b53df
commit 5d44339f21
249 changed files with 20922 additions and 2253 deletions

View File

@@ -0,0 +1,227 @@
# /opt/docker/dev/service_finder/backend/app/models/marketplace/organization.py
import enum
import uuid
from datetime import datetime
from typing import Any, List, Optional
import sqlalchemy as sa
from sqlalchemy import Column, Integer, String, Boolean, DateTime, ForeignKey, JSON, text, Numeric, BigInteger, Float
from sqlalchemy.dialects.postgresql import ENUM as PG_ENUM, UUID as PG_UUID, JSONB
from sqlalchemy.orm import Mapped, mapped_column, relationship, foreign
from sqlalchemy.sql import func
from geoalchemy2 import Geometry
# MB 2.0: A központi aszinkron adatbázis motorból húzzuk be a Base-t
from app.database import Base
class OrgType(str, enum.Enum):
individual = "individual"
service = "service"
service_provider = "service_provider"
fleet_owner = "fleet_owner"
club = "club"
business = "business"
class OrgUserRole(str, enum.Enum):
OWNER = "OWNER"
ADMIN = "ADMIN"
FLEET_MANAGER = "FLEET_MANAGER"
DRIVER = "DRIVER"
MECHANIC = "MECHANIC"
RECEPTIONIST = "RECEPTIONIST"
class Organization(Base):
"""
Szervezet entitás (MB 2.0).
Támogatja a 'Digital Twin' logikát: a cég törölhető, de a statisztika és
a jármű-életút adatok megmaradnak az eredeti Person-höz kötve.
"""
__tablename__ = "organizations"
__table_args__ = {"schema": "fleet"}
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
# --- 🛡️ BIZTONSÁGI ÉS ÉLETÚT KIEGÉSZÍTÉSEK ---
# A Jogi képviselő/Tulajdonos (A Person örök DNS-e az identity sémában)
# Ez segít felismerni, ha ugyanaz az ember új céggel akar 'tiszta lapot' nyitni.
legal_owner_id: Mapped[Optional[int]] = mapped_column(BigInteger, ForeignKey("identity.persons.id"), index=True)
# ÉLETÚT DÁTUMOK (A kért logika alapján)
# 1. A legelső regisztráció dátuma (Soha nem változik)
first_registered_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
# 2. Az AKTUÁLIS életciklus kezdete. Újraregisztrációkor ez frissül.
# Az API ezt használja szűrőnek: a cég csak az ezutáni adatokat látja a Dashboardon.
current_lifecycle_started_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
# 3. Az utolsó deaktiválás/törlés időpontja
last_deactivated_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True))
# Hányszor regisztrált újra ez a cég/adószám (Reinkarnációs index)
lifecycle_index: Mapped[int] = mapped_column(Integer, default=1, server_default=text("1"))
# --- 🏢 ALAPADATOK (MEGŐRIZVE) ---
address_id: Mapped[Optional[uuid.UUID]] = mapped_column(PG_UUID(as_uuid=True), ForeignKey("system.addresses.id"))
is_anonymized: Mapped[bool] = mapped_column(Boolean, default=False, server_default=text("false"))
anonymized_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True))
full_name: Mapped[str] = mapped_column(String, nullable=False)
name: Mapped[str] = mapped_column(String, nullable=False)
display_name: Mapped[Optional[str]] = mapped_column(String(50))
folder_slug: Mapped[str] = mapped_column(String(12), unique=True, index=True)
default_currency: Mapped[str] = mapped_column(String(3), default="HUF")
country_code: Mapped[str] = mapped_column(String(2), default="HU")
language: Mapped[str] = mapped_column(String(5), default="hu")
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))
address_street_type: Mapped[Optional[str]] = mapped_column(String(50))
address_house_number: Mapped[Optional[str]] = mapped_column(String(20))
address_hrsz: Mapped[Optional[str]] = mapped_column(String(50))
tax_number: Mapped[Optional[str]] = mapped_column(String(20), unique=True, index=True)
reg_number: Mapped[Optional[str]] = mapped_column(String(50))
org_type: Mapped[OrgType] = mapped_column(
PG_ENUM(OrgType, name="orgtype", schema="fleet"),
default=OrgType.individual
)
status: Mapped[str] = mapped_column(String(30), default="pending_verification")
# Soft delete: is_active=False és is_deleted=True esetén a cég 'törölt'
is_deleted: Mapped[bool] = mapped_column(Boolean, default=False)
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
subscription_plan: Mapped[str] = mapped_column(String(30), server_default=text("'FREE'"), index=True)
base_asset_limit: Mapped[int] = mapped_column(Integer, server_default=text("1"))
purchased_extra_slots: Mapped[int] = mapped_column(Integer, server_default=text("0"))
notification_settings: Mapped[Any] = mapped_column(JSON, server_default=text("'{\"notify_owner\": true, \"alert_days_before\": [30, 15, 7, 1]}'::jsonb"))
external_integration_config: Mapped[Any] = mapped_column(JSON, server_default=text("'{}'::jsonb"))
# A technikai tulajdonos (User fiók - törölhető)
owner_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("identity.users.id"))
is_verified: Mapped[bool] = mapped_column(Boolean, default=False)
# Időbélyegek az aktuális állapothoz
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())
is_ownership_transferable: Mapped[bool] = mapped_column(Boolean, server_default=text("true"))
# --- 🔗 KAPCSOLATOK (RELATIONSHIPS) ---
assets: Mapped[List["AssetAssignment"]] = relationship("AssetAssignment", back_populates="organization", cascade="all, delete-orphan")
members: Mapped[List["OrganizationMember"]] = relationship("OrganizationMember", back_populates="organization", cascade="all, delete-orphan")
owner: Mapped[Optional["User"]] = relationship("User", back_populates="owned_organizations")
financials: Mapped[List["OrganizationFinancials"]] = relationship("OrganizationFinancials", back_populates="organization", cascade="all, delete-orphan")
# JAVÍTVA: Ha az Organization törlődik, a ServiceProfile megmarad 'Ghost'-ként (ondelete="SET NULL")
service_profile: Mapped[Optional["ServiceProfile"]] = relationship("ServiceProfile", back_populates="organization", uselist=False)
branches: Mapped[List["Branch"]] = relationship("Branch", back_populates="organization", cascade="all, delete-orphan")
# Kapcsolat az örök személy rekordhoz
legal_owner: Mapped[Optional["Person"]] = relationship("Person", back_populates="owned_business_entities")
# Kapcsolat a jármű költségekhez (TCO rendszer)
vehicle_costs: Mapped[List["VehicleCost"]] = relationship("VehicleCost", back_populates="organization")
class OrganizationFinancials(Base):
__tablename__ = "organization_financials"
__table_args__ = {"schema": "fleet"}
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
organization_id: Mapped[int] = mapped_column(Integer, ForeignKey("fleet.organizations.id"), nullable=False)
year: Mapped[int] = mapped_column(Integer, nullable=False)
turnover: Mapped[Optional[float]] = mapped_column(Numeric(18, 2))
profit: Mapped[Optional[float]] = mapped_column(Numeric(18, 2))
employee_count: Mapped[Optional[int]] = mapped_column(Integer)
source: Mapped[Optional[str]] = mapped_column(String(50))
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
organization: Mapped["Organization"] = relationship("Organization", back_populates="financials")
class OrganizationMember(Base):
__tablename__ = "organization_members"
__table_args__ = {"schema": "fleet"}
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
organization_id: Mapped[int] = mapped_column(Integer, ForeignKey("fleet.organizations.id"), nullable=False)
user_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("identity.users.id"))
person_id: Mapped[Optional[int]] = mapped_column(BigInteger, ForeignKey("identity.persons.id"))
role: Mapped[OrgUserRole] = mapped_column(
PG_ENUM(OrgUserRole, name="orguserrole", schema="fleet"),
default=OrgUserRole.DRIVER
)
permissions: Mapped[Any] = mapped_column(JSON, server_default=text("'{}'::jsonb"))
is_permanent: Mapped[bool] = mapped_column(Boolean, default=False)
is_verified: Mapped[bool] = mapped_column(Boolean, default=False)
organization: Mapped["Organization"] = relationship("Organization", back_populates="members")
user: Mapped[Optional["User"]] = relationship("User")
person: Mapped[Optional["Person"]] = relationship("Person", back_populates="memberships")
class OrganizationSalesAssignment(Base):
__tablename__ = "org_sales_assignments"
__table_args__ = {"schema": "fleet"}
id: Mapped[int] = mapped_column(Integer, primary_key=True)
organization_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("fleet.organizations.id"))
agent_user_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("identity.users.id"))
assigned_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
class Branch(Base):
"""
Telephely entitás. A fizikai helyszín, ahol a szolgáltatás vagy flotta-kezelés zajlik.
"""
__tablename__ = "branches"
__table_args__ = {"schema": "fleet"}
id: Mapped[uuid.UUID] = mapped_column(PG_UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
organization_id: Mapped[int] = mapped_column(Integer, ForeignKey("fleet.organizations.id"), nullable=False)
address_id: Mapped[Optional[uuid.UUID]] = mapped_column(PG_UUID(as_uuid=True), ForeignKey("system.addresses.id"))
name: Mapped[str] = mapped_column(String(100), nullable=False)
is_main: Mapped[bool] = mapped_column(Boolean, default=False)
# Denormalizált adatok a gyors lekérdezéshez
postal_code: Mapped[Optional[str]] = mapped_column(String(10), index=True)
city: Mapped[Optional[str]] = mapped_column(String(100), index=True)
street_name: Mapped[Optional[str]] = mapped_column(String(150))
street_type: Mapped[Optional[str]] = mapped_column(String(50))
house_number: Mapped[Optional[str]] = mapped_column(String(20))
stairwell: Mapped[Optional[str]] = mapped_column(String(20))
floor: Mapped[Optional[str]] = mapped_column(String(20))
door: Mapped[Optional[str]] = mapped_column(String(20))
hrsz: Mapped[Optional[str]] = mapped_column(String(50))
# PostGIS location field for geographic queries
location: Mapped[Optional[Any]] = mapped_column(
Geometry(geometry_type='POINT', srid=4326),
nullable=True
)
opening_hours: Mapped[Any] = mapped_column(JSONB, server_default=text("'{}'::jsonb"))
branch_rating: Mapped[float] = mapped_column(Float, default=0.0)
status: Mapped[str] = mapped_column(String(30), default="active")
is_deleted: Mapped[bool] = mapped_column(Boolean, default=False)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
# Kapcsolatok
organization: Mapped["Organization"] = relationship("Organization", back_populates="branches")
address: Mapped[Optional["Address"]] = relationship("Address")
# Kapcsolatok (Primaryjoin tartva a rating rendszerhez)
reviews: Mapped[List["Rating"]] = relationship(
"Rating",
primaryjoin="and_(Branch.id==foreign(Rating.target_branch_id))"
)