Initial commit: Robot ökoszisztéma v2.0 - Stabilizált jármű és szerviz robotok

This commit is contained in:
Kincses
2026-03-04 02:03:03 +01:00
commit 250f4f4b8f
7942 changed files with 449625 additions and 0 deletions

View File

@@ -0,0 +1,217 @@
# /opt/docker/dev/service_finder/backend/app/models/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
# 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": "data"}
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("data.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="data"),
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")
class OrganizationFinancials(Base):
__tablename__ = "organization_financials"
__table_args__ = {"schema": "data"}
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
organization_id: Mapped[int] = mapped_column(Integer, ForeignKey("data.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": "data"}
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
organization_id: Mapped[int] = mapped_column(Integer, ForeignKey("data.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="data"),
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": "data"}
id: Mapped[int] = mapped_column(Integer, primary_key=True)
organization_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("data.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": "data"}
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("data.organizations.id"), nullable=False)
address_id: Mapped[Optional[uuid.UUID]] = mapped_column(PG_UUID(as_uuid=True), ForeignKey("data.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))
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))"
)