céges meghívó kezelése,

This commit is contained in:
Roo
2026-06-17 22:07:55 +00:00
parent bf3a971ff1
commit 127b130401
28 changed files with 5806 additions and 1313 deletions

View File

@@ -6,7 +6,7 @@ from app.database import Base
from .identity.identity import Person, User, Wallet, VerificationToken, SocialAccount, UserRole, OneTimePassword
# 2. Szervezeti felépítés (MUST be before Address/Rating due to FK references)
from .marketplace.organization import Organization, OrganizationMember, OrganizationFinancials, OrganizationSalesAssignment, OrgType, OrgUserRole, Branch
from .marketplace.organization import Organization, OrganizationMember, OrganizationFinancials, OrganizationSalesAssignment, OrgType, OrgUserRole, OrgRole, Branch
# 3. Földrajzi adatok és címek
from .identity.address import Address, GeoPostalCode, GeoStreet, GeoStreetType, Rating
@@ -59,7 +59,7 @@ ServiceRecord = AssetEvent
__all__ = [
"Base", "User", "Person", "Wallet", "UserRole", "VerificationToken", "SocialAccount",
"Organization", "OrganizationMember", "OrganizationSalesAssignment", "OrgType", "OrgUserRole",
"Organization", "OrganizationMember", "OrganizationSalesAssignment", "OrgType", "OrgUserRole", "OrgRole",
"Asset", "AssetCatalog", "AssetCost", "AssetEvent", "AssetAssignment", "AssetFinancials",
"AssetTelemetry", "AssetReview", "ExchangeRate", "CatalogDiscovery", "OdometerReading",
"Address", "GeoPostalCode", "GeoStreet", "GeoStreetType", "Branch",

View File

@@ -27,10 +27,27 @@ class OrgType(str, enum.Enum):
class OrgUserRole(str, enum.Enum):
OWNER = "OWNER"
ADMIN = "ADMIN"
FLEET_MANAGER = "FLEET_MANAGER"
DRIVER = "DRIVER"
MECHANIC = "MECHANIC"
RECEPTIONIST = "RECEPTIONIST"
MANAGER = "MANAGER"
MEMBER = "MEMBER"
AGENT = "AGENT"
class OrgRole(Base):
"""
Dinamikus szerepkör modell (RBAC).
A szervezeti szerepkörök adatbázis-vezéreltek, nem hardkódolt Enum-ok.
Alapértelmezett szerepkörök: OWNER, ADMIN, MANAGER, MEMBER, AGENT.
"""
__tablename__ = "org_roles"
__table_args__ = {"schema": "fleet"}
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
name_key: Mapped[str] = mapped_column(String(50), unique=True, nullable=False, index=True)
display_name: Mapped[str] = mapped_column(String(100), nullable=False)
description: Mapped[Optional[str]] = mapped_column(String(255))
is_system: Mapped[bool] = mapped_column(Boolean, default=True)
priority: Mapped[int] = mapped_column(Integer, default=0)
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
class Organization(Base):
"""
@@ -72,7 +89,7 @@ class Organization(Base):
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)
folder_slug: Mapped[str] = mapped_column(String(24), unique=True, index=True)
default_currency: Mapped[str] = mapped_column(String(3), default="HUF")
country_code: Mapped[str] = mapped_column(String(2), default="HU")
@@ -84,6 +101,7 @@ class Organization(Base):
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))
plus_code: Mapped[Optional[str]] = mapped_column(String(20))
tax_number: Mapped[Optional[str]] = mapped_column(String(20), unique=True, index=True)
reg_number: Mapped[Optional[str]] = mapped_column(String(50))
@@ -115,6 +133,14 @@ class Organization(Base):
server_default=text("'{\"theme\": \"default\", \"primary_color\": null, \"wall_logo_url\": null}'::jsonb")
)
# Dinamikus szervezeti beállítások (pl. invite_expiry_days, role_hierarchy)
settings: Mapped[dict] = mapped_column(
JSONB,
nullable=False,
default=lambda: {"invite_expiry_days": 7, "max_members": 50, "allow_public_join": False},
server_default=text("'{\"invite_expiry_days\": 7, \"max_members\": 50, \"allow_public_join\": false}'::jsonb")
)
# --- 🔍 CROWDSOURCED SEARCH FIELDS ---
# Provider nicknames / alternative names for matching (e.g. ["MOL", "MOL LUB", "MOL Magyarország"])
aliases: Mapped[list] = mapped_column(
@@ -181,16 +207,29 @@ class OrganizationMember(Base):
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"))
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
# Meghívott e-mail címe (ha a meghívottnak még nincs user fiókja)
invited_email: Mapped[Optional[str]] = mapped_column(String(255))
# Meghívó lejárati ideje
expires_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True))
# Dinamikus szerepkör (String, nem Enum) - az OrgRole táblából validáljuk
role: Mapped[str] = mapped_column(
String(50),
default="MEMBER",
server_default=text("'MEMBER'")
)
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)
# 🔴 HIÁNYZÓ OSZLOPOK - Pótolva a logic_spec alapján
status: Mapped[str] = mapped_column(String(20), default="active", server_default=text("'active'"))
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())
organization: Mapped["Organization"] = relationship("Organization", back_populates="members")
user: Mapped[Optional["User"]] = relationship("User")
person: Mapped[Optional["Person"]] = relationship("Person", back_populates="memberships")

View File

@@ -80,9 +80,21 @@ class ExpertiseTag(Base):
"""
Szakmai címkék mesterlistája (MB 2.0).
Ez a tábla vezérli a robotok keresését és a Gamification pontozást is.
4-Level Category Hierarchy (2026-06-17):
=========================================
Level 0: Járműtípus (Vehicle Type) — e.g. "Személyautó", "Motorkerékpár"
Level 1: Iparág/Főcsoport (Industry) — e.g. "Karbantartás és javítás", "Üzemanyagtöltő"
Level 2: Szakma (Profession) — e.g. "Autószerelő", "Gumiszerviz"
Level 3: Specifikus feladat/címke (Specific Tag) — e.g. "Vezérműszíj csere", "Klíma töltés"
Adjacency List (parent_id) + Materialized Path (path) for efficient tree queries.
"""
__tablename__ = "expertise_tags"
__table_args__ = {"schema": "marketplace"}
__table_args__ = (
Index('idx_expertise_path', 'path'),
{"schema": "marketplace"}
)
id: Mapped[int] = mapped_column(Integer, primary_key=True)
key: Mapped[str] = mapped_column(String(50), unique=True, index=True)
@@ -90,6 +102,13 @@ class ExpertiseTag(Base):
name_en: Mapped[Optional[str]] = mapped_column(String(100))
category: Mapped[Optional[str]] = mapped_column(String(30), index=True)
# --- 🏗️ 4-LEVEL HIERARCHY (2026-06-17) ---
parent_id: Mapped[Optional[int]] = mapped_column(
Integer, ForeignKey("marketplace.expertise_tags.id"), nullable=True, index=True
)
level: Mapped[int] = mapped_column(Integer, default=0, server_default=text("0"), nullable=False)
path: Mapped[Optional[str]] = mapped_column(String(255), nullable=True, index=True)
# --- 🎮 GAMIFICATION ÉS DISCOVERY ---
is_official: Mapped[bool] = mapped_column(Boolean, default=True, server_default=text("true"))
suggested_by_id: Mapped[Optional[int]] = mapped_column(BigInteger, ForeignKey("identity.persons.id"))
@@ -102,6 +121,20 @@ class ExpertiseTag(Base):
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())
# Self-referential relationship for tree traversal
# NOTE: cascade="all, delete-orphan" is intentionally NOT set here because
# this is a self-referential tree. Deleting a parent should NOT cascade-delete
# children (they may be re-parented). The "many" side of a self-referential
# relationship cannot have delete-orphan without single_parent=True.
children: Mapped[List["ExpertiseTag"]] = relationship(
"ExpertiseTag", back_populates="parent",
remote_side=[id],
)
parent: Mapped[Optional["ExpertiseTag"]] = relationship(
"ExpertiseTag", back_populates="children",
remote_side=[parent_id],
)
services: Mapped[List["ServiceExpertise"]] = relationship("ServiceExpertise", back_populates="tag")
suggested_by: Mapped[Optional["Person"]] = relationship("Person")
@@ -173,4 +206,4 @@ class Cost(Base):
occurrence_date: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, index=True)
is_deleted: Mapped[bool] = mapped_column(Boolean, default=False)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), onupdate=func.now(), server_default=func.now())
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), onupdate=func.now(), server_default=func.now())