# /opt/docker/dev/service_finder/backend/app/models/fleet_finance/models.py """ fleet_finance schema: Flotta pénzügyi adatok. - CostCategory: Standardizált költségkategóriák hierarchiája (áthelyezve vehicle -> fleet_finance) - AssetCost: Eszközhöz kapcsolódó tényleges költségnapló (áthelyezve vehicle -> fleet_finance) - AssetFinancials: Beszerzés és értékcsökkenés (áthelyezve vehicle -> fleet_finance, kibővítve finanszírozási adatokkal) - InsuranceProvider: Biztosítók katalógusa (ÚJ) - VehicleInsurancePolicy: Jármű biztosítási kötvények (ÚJ) - VehicleTaxObligation: Jármű adókötelezettségek (ÚJ) """ from __future__ import annotations import uuid import enum from datetime import datetime, date from typing import Optional, List, TYPE_CHECKING from sqlalchemy import ( String, Boolean, DateTime, ForeignKey, Numeric, text, Text, UniqueConstraint, Integer, Date ) from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.dialects.postgresql import UUID as PG_UUID, JSONB from sqlalchemy.sql import func from app.database import Base if TYPE_CHECKING: from app.models.vehicle.asset import Asset from app.models.marketplace.organization import Organization from app.models.system.document import Document # ============================================================================= # CostCategory (áthelyezve vehicle -> fleet_finance) # ============================================================================= class CostCategory(Base): """ Standardizált költségkategóriák hierarchikus fája. Rendszerkategóriák (is_system=True) nem törölhetők, csak felhasználói kategóriák. Áthelyezve a vehicle sémából a fleet_finance sémába. """ __tablename__ = "cost_categories" __table_args__ = {"schema": "fleet_finance", "extend_existing": True} id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) parent_id: Mapped[Optional[int]] = mapped_column( Integer, ForeignKey("fleet_finance.cost_categories.id", ondelete="SET NULL"), nullable=True, index=True ) code: Mapped[str] = mapped_column(String(50), unique=True, index=True, nullable=False) name: Mapped[str] = mapped_column(String(100), nullable=False) description: Mapped[Optional[str]] = mapped_column(Text, nullable=True) is_system: Mapped[bool] = mapped_column(Boolean, default=False, server_default="false") visibility: Mapped[str] = mapped_column(String(20), default="both", server_default="'both'") min_tier: Mapped[str] = mapped_column(String(20), default="free", server_default="'free'") accounting_code: Mapped[Optional[str]] = mapped_column(String(50), nullable=True) 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()) # Hierarchikus kapcsolatok parent: Mapped[Optional["CostCategory"]] = relationship( "CostCategory", remote_side=[id], back_populates="children", foreign_keys=[parent_id] ) children: Mapped[list["CostCategory"]] = relationship( "CostCategory", back_populates="parent", foreign_keys=[parent_id] ) # Kapcsolódó költségek (AssetCost) asset_costs: Mapped[list["AssetCost"]] = relationship("AssetCost", back_populates="category") def __repr__(self) -> str: return f"CostCategory(id={self.id}, code='{self.code}', name='{self.name}')" # ============================================================================= # AssetCostStatusEnum # ============================================================================= class AssetCostStatusEnum(str, enum.Enum): """Költség státuszok a jóváhagyási munkafolyamathoz.""" DRAFT = "DRAFT" PENDING_APPROVAL = "PENDING_APPROVAL" APPROVED = "APPROVED" # ============================================================================= # AssetCost (áthelyezve vehicle -> fleet_finance) # ============================================================================= class AssetCost(Base): """ II. Üzemeltetés és TCO kimutatás. Áthelyezve a vehicle sémából. """ __tablename__ = "asset_costs" __table_args__ = {"schema": "fleet_finance"} id: Mapped[uuid.UUID] = mapped_column(PG_UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) asset_id: Mapped[uuid.UUID] = mapped_column(PG_UUID(as_uuid=True), ForeignKey("vehicle.assets.id"), nullable=False) organization_id: Mapped[int] = mapped_column(Integer, ForeignKey("fleet.organizations.id"), nullable=False) category_id: Mapped[int] = mapped_column(Integer, ForeignKey("fleet_finance.cost_categories.id"), index=True, nullable=False) amount_net: Mapped[float] = mapped_column(Numeric(18, 2), nullable=False) amount_gross: Mapped[Optional[float]] = mapped_column(Numeric(18, 2), nullable=True) vat_rate: Mapped[Optional[float]] = mapped_column(Numeric(5, 2), nullable=True) currency: Mapped[str] = mapped_column(String(3), default="HUF") date: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) invoice_number: Mapped[Optional[str]] = mapped_column(String(100), index=True) document_id: Mapped[Optional[uuid.UUID]] = mapped_column(PG_UUID(as_uuid=True), ForeignKey("system.documents.id"), nullable=True) # Státusz a jóváhagyási munkafolyamathoz status: Mapped[str] = mapped_column(String(30), default="DRAFT", server_default=text("'DRAFT'"), index=True) # Kétirányú hivatkozás az AssetEvent felé (kettős könyvelés elkerülése) linked_asset_event_id: Mapped[Optional[uuid.UUID]] = mapped_column( PG_UUID(as_uuid=True), ForeignKey("vehicle.asset_events.id"), nullable=True, index=True ) # === BESZÁLLÍTÓI HIVATKOZÁSOK (B2B expansion) === # Hivatkozás a fleet.organizations táblában lévő beszállítóra vendor_organization_id: Mapped[Optional[int]] = mapped_column( Integer, ForeignKey("fleet.organizations.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) # === KÖNYVELÉSI DÁTUMOK === invoice_date: Mapped[Optional[date]] = mapped_column(Date, nullable=True) fulfillment_date: Mapped[Optional[date]] = mapped_column(Date, nullable=True) data: Mapped[dict] = mapped_column(JSONB, server_default=text("'{}'::jsonb")) asset: Mapped["Asset"] = relationship("Asset", back_populates="costs") organization: Mapped["Organization"] = relationship("Organization", foreign_keys=[organization_id]) category: Mapped["CostCategory"] = relationship("CostCategory", back_populates="asset_costs") # Kapcsolat a hivatkozott eseményhez linked_event: Mapped[Optional["AssetEvent"]] = relationship( "AssetEvent", foreign_keys=[linked_asset_event_id], post_update=True ) # Beszállító kapcsolat vendor_organization: Mapped[Optional["Organization"]] = relationship( "Organization", foreign_keys=[vendor_organization_id] ) # ============================================================================= # AssetFinancials (áthelyezve vehicle -> fleet_finance, kibővítve) # ============================================================================= class AssetFinancials(Base): """ I. Beszerzés és IV. Értékcsökkenés (Amortizáció). Áthelyezve a vehicle sémából a fleet_finance sémába. Kibővítve finanszírozási adatokkal (lízing, hitel). """ __tablename__ = "asset_financials" __table_args__ = {"schema": "fleet_finance"} id: Mapped[int] = mapped_column(Integer, primary_key=True) asset_id: Mapped[uuid.UUID] = mapped_column(PG_UUID(as_uuid=True), ForeignKey("vehicle.assets.id"), unique=True) # === BESZERZÉS === purchase_price_net: Mapped[float] = mapped_column(Numeric(18, 2)) purchase_price_gross: Mapped[float] = mapped_column(Numeric(18, 2)) vat_rate: Mapped[float] = mapped_column(Numeric(5, 2), default=27.00) activation_date: Mapped[Optional[datetime]] = mapped_column(DateTime) verified_purchase_date: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True) financing_type: Mapped[str] = mapped_column(String(50)) accounting_details: Mapped[dict] = mapped_column(JSONB, server_default=text("'{}'::jsonb")) # === FINANSZÍROZÁS (ÚJ) === down_payment: Mapped[Optional[float]] = mapped_column(Numeric(18, 2), nullable=True) monthly_installment: Mapped[Optional[float]] = mapped_column(Numeric(18, 2), nullable=True) residual_value: Mapped[Optional[float]] = mapped_column(Numeric(18, 2), nullable=True) contract_number: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) financing_provider: Mapped[Optional[str]] = mapped_column(String(200), nullable=True) interest_rate: Mapped[Optional[float]] = mapped_column(Numeric(5, 2), nullable=True) total_contract_value: Mapped[Optional[float]] = mapped_column(Numeric(18, 2), nullable=True) lease_start_date: Mapped[Optional[date]] = mapped_column(Date, nullable=True) lease_end_date: Mapped[Optional[date]] = mapped_column(Date, nullable=True) # === KAPCSOLATOK === asset: Mapped["Asset"] = relationship("Asset", back_populates="financials") # ============================================================================= # InsuranceProvider (ÚJ) # ============================================================================= class InsuranceProvider(Base): """ Biztosítók katalógusa. Tárolja a biztosítók elérhetőségeit és szolgáltatásait. """ __tablename__ = "insurance_providers" __table_args__ = {"schema": "fleet_finance"} id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) name: Mapped[str] = mapped_column(String(150), nullable=False, index=True) country_code: Mapped[Optional[str]] = mapped_column(String(2), index=True, nullable=True, comment="ISO 3166-1 alpha-2 országkód (pl. 'HU'). Ha NULL, akkor globális.") claim_phone: Mapped[Optional[str]] = mapped_column(String(50), nullable=True) claim_url: Mapped[Optional[str]] = mapped_column(String(255), nullable=True) services_offered: Mapped[dict] = mapped_column(JSONB, server_default=text("'{}'::jsonb")) is_active: Mapped[bool] = mapped_column(Boolean, default=True, server_default=text("true")) # Kapcsolatok policies: Mapped[List["VehicleInsurancePolicy"]] = relationship( "VehicleInsurancePolicy", back_populates="provider" ) def __repr__(self) -> str: return f"InsuranceProvider(id={self.id}, name='{self.name}')" # ============================================================================= # VehicleInsurancePolicy (ÚJ) # ============================================================================= class VehicleInsurancePolicy(Base): """ Jármű biztosítási kötvények. Minden kötvény egy járműhöz (asset) és egy biztosítóhoz (provider) tartozik. """ __tablename__ = "vehicle_insurance_policies" __table_args__ = {"schema": "fleet_finance"} id: Mapped[uuid.UUID] = mapped_column( PG_UUID(as_uuid=True), primary_key=True, default=uuid.uuid4 ) asset_id: Mapped[uuid.UUID] = mapped_column( PG_UUID(as_uuid=True), ForeignKey("vehicle.assets.id"), nullable=False, index=True ) provider_id: Mapped[int] = mapped_column( Integer, ForeignKey("fleet_finance.insurance_providers.id"), nullable=False, index=True ) insurance_type: Mapped[str] = mapped_column( String(20), nullable=False, comment="KGFB, CASCO" ) policy_number: Mapped[str] = mapped_column(String(100), nullable=False, index=True) start_date: Mapped[date] = mapped_column(Date, nullable=False) expiry_date: Mapped[date] = mapped_column(Date, nullable=False) premium_amount: Mapped[float] = mapped_column(Numeric(18, 2), nullable=False) currency: Mapped[str] = mapped_column(String(3), default="HUF") # Dokumentum kapcsolat (PDF kötvény) document_id: Mapped[Optional[uuid.UUID]] = mapped_column( PG_UUID(as_uuid=True), ForeignKey("system.documents.id"), nullable=True ) # Kapcsolatok asset: Mapped["Asset"] = relationship("Asset") provider: Mapped["InsuranceProvider"] = relationship( "InsuranceProvider", back_populates="policies" ) document: Mapped[Optional["Document"]] = relationship("Document") def __repr__(self) -> str: return ( f"VehicleInsurancePolicy(id={self.id}, asset_id={self.asset_id}, " f"type='{self.insurance_type}', policy='{self.policy_number}')" ) # ============================================================================= # VehicleTaxObligation (ÚJ) # ============================================================================= class VehicleTaxObligation(Base): """ Jármű adókötelezettségek. Tárolja a különböző típusú adókat (súlyadó, cégautóadó, stb.). """ __tablename__ = "vehicle_tax_obligations" __table_args__ = {"schema": "fleet_finance"} id: Mapped[uuid.UUID] = mapped_column( PG_UUID(as_uuid=True), primary_key=True, default=uuid.uuid4 ) asset_id: Mapped[uuid.UUID] = mapped_column( PG_UUID(as_uuid=True), ForeignKey("vehicle.assets.id"), nullable=False, index=True ) tax_type: Mapped[str] = mapped_column( String(30), nullable=False, comment="WEIGHT_TAX, COMPANY_CAR_TAX" ) tax_year: Mapped[int] = mapped_column(Integer, nullable=False, index=True) amount: Mapped[float] = mapped_column(Numeric(18, 2), nullable=False) currency: Mapped[str] = mapped_column(String(3), default="HUF") due_date: Mapped[Optional[date]] = mapped_column(Date, nullable=True) payment_status: Mapped[str] = mapped_column( String(20), default="pending", server_default=text("'pending'") ) # Dokumentum kapcsolat (PDF adóbevallás) document_id: Mapped[Optional[uuid.UUID]] = mapped_column( PG_UUID(as_uuid=True), ForeignKey("system.documents.id"), nullable=True ) # Kapcsolatok asset: Mapped["Asset"] = relationship("Asset") document: Mapped[Optional["Document"]] = relationship("Document") def __repr__(self) -> str: return ( f"VehicleTaxObligation(id={self.id}, asset_id={self.asset_id}, " f"type='{self.tax_type}', year={self.tax_year})" )