frontend költség beállítások

This commit is contained in:
Roo
2026-06-23 21:11:21 +00:00
parent 5b437b220d
commit 71ef33bb85
90 changed files with 11850 additions and 1053 deletions

View File

@@ -2,7 +2,7 @@
from pydantic import BaseModel, ConfigDict, Field, validator, root_validator, model_validator, field_validator
from typing import Optional, Dict, Any, List
from uuid import UUID
from datetime import datetime
from datetime import datetime, date
class AssetCatalogResponse(BaseModel):
""" A technikai katalógus (Master Data) teljes adattartalma. """
@@ -86,6 +86,19 @@ class AssetResponse(BaseModel):
is_under_warranty: bool = Field(default=False, description="Érvényes jótállás")
warranty_expiry_date: Optional[datetime] = Field(None, description="Jótállás érvényességi dátuma")
# === REGISTRATION DOCUMENTS ===
registration_certificate_number: Optional[str] = Field(None, max_length=50, description="Forgalmi engedély száma")
registration_certificate_validity: Optional[datetime] = Field(None, description="Forgalmi engedély érvényességi ideje")
vehicle_registration_document_number: Optional[str] = Field(None, max_length=50, description="Törzskönyv száma")
# === INTERNATIONALIZATION (ÚJ) ===
registration_country: Optional[str] = Field(None, min_length=2, max_length=2, description="Regisztráció országa (ISO 3166-1 alpha-2, pl. 'HU', 'SK', 'DE')")
first_domestic_registration_date: Optional[date] = Field(None, description="Első belföldi forgalomba helyezés dátuma")
import_country: Optional[str] = Field(None, min_length=2, max_length=2, description="Importország (ISO 3166-1 alpha-2), ha importált jármű")
title_document_number: Optional[str] = Field(None, max_length=50, description="Törzskönyv szám (title document)")
engine_number: Optional[str] = Field(None, max_length=100, description="Motorszám")
number_of_previous_owners: Optional[int] = Field(None, ge=0, description="Előző tulajdonosok száma")
# === SALES MODULE ===
is_for_sale: bool = Field(default=False)
price: Optional[float] = None
@@ -228,12 +241,53 @@ class AssetCreate(BaseModel):
is_under_warranty: bool = Field(default=False, description="Érvényes jótállás")
warranty_expiry_date: Optional[datetime] = Field(None, description="Jótállás érvényességi dátuma")
# === REGISTRATION DOCUMENTS ===
registration_certificate_number: Optional[str] = Field(None, max_length=50, description="Forgalmi engedély száma")
registration_certificate_validity: Optional[datetime] = Field(None, description="Forgalmi engedély érvényességi ideje")
vehicle_registration_document_number: Optional[str] = Field(None, max_length=50, description="Törzskönyv száma")
# === INTERNATIONALIZATION (ÚJ) ===
registration_country: Optional[str] = Field(None, min_length=2, max_length=2, description="Regisztráció országa (ISO 3166-1 alpha-2, pl. 'HU', 'SK', 'DE')")
first_domestic_registration_date: Optional[date] = Field(None, description="Első belföldi forgalomba helyezés dátuma")
import_country: Optional[str] = Field(None, min_length=2, max_length=2, description="Importország (ISO 3166-1 alpha-2), ha importált jármű")
title_document_number: Optional[str] = Field(None, max_length=50, description="Törzskönyv szám (title document)")
engine_number: Optional[str] = Field(None, max_length=100, description="Motorszám")
number_of_previous_owners: Optional[int] = Field(None, ge=0, description="Előző tulajdonosok száma")
# === PRIMARY VEHICLE FLAG ===
is_primary: bool = Field(default=False, description="Elsődleges jármű (tárolva: individual_equipment JSONB)")
# === DATA STATUS (for transfer/duplicate scenarios) ===
data_status: Optional[str] = Field(None, description="Adat státusz (pl. 'draft' tulajdonosváltáskor)")
# ── Internationalization validators ──
@field_validator('registration_country', 'import_country')
@classmethod
def validate_iso_country(cls, v: Optional[str]) -> Optional[str]:
"""registration_country és import_country: pontosan 2 karakteres nagybetűs string (ISO 3166-1 alpha-2)."""
if v is None or not isinstance(v, str):
return v
cleaned = v.strip().upper()
if len(cleaned) != 2:
raise ValueError(f'Az országkódnak pontosan 2 karakter hosszúnak kell lennie (ISO 3166-1 alpha-2), kapott: "{v}"')
return cleaned
@field_validator('number_of_previous_owners')
@classmethod
def validate_previous_owners(cls, v: Optional[int]) -> Optional[int]:
"""number_of_previous_owners >= 0."""
if v is not None and v < 0:
raise ValueError('Az előző tulajdonosok száma nem lehet negatív')
return v
@field_validator('first_domestic_registration_date')
@classmethod
def validate_not_future_date(cls, v: Optional[date]) -> Optional[date]:
"""first_domestic_registration_date nem lehet jövőbeli dátum."""
if v is not None and v > date.today():
raise ValueError('Az első belföldi forgalomba helyezés dátuma nem lehet jövőbeli')
return v
# === STATUS VALIDATION ===
@validator('status', pre=True, always=True)
def determine_status(cls, v, values):
@@ -370,6 +424,47 @@ class AssetUpdate(BaseModel):
is_under_warranty: Optional[bool] = Field(None, description="Érvényes jótállás")
warranty_expiry_date: Optional[datetime] = Field(None, description="Jótállás érvényességi dátuma")
# === REGISTRATION DOCUMENTS ===
registration_certificate_number: Optional[str] = Field(None, max_length=50, description="Forgalmi engedély száma")
registration_certificate_validity: Optional[datetime] = Field(None, description="Forgalmi engedély érvényességi ideje")
vehicle_registration_document_number: Optional[str] = Field(None, max_length=50, description="Törzskönyv száma")
# === INTERNATIONALIZATION (ÚJ) ===
registration_country: Optional[str] = Field(None, min_length=2, max_length=2, description="Regisztráció országa (ISO 3166-1 alpha-2, pl. 'HU', 'SK', 'DE')")
first_domestic_registration_date: Optional[date] = Field(None, description="Első belföldi forgalomba helyezés dátuma")
import_country: Optional[str] = Field(None, min_length=2, max_length=2, description="Importország (ISO 3166-1 alpha-2), ha importált jármű")
title_document_number: Optional[str] = Field(None, max_length=50, description="Törzskönyv szám (title document)")
engine_number: Optional[str] = Field(None, max_length=100, description="Motorszám")
number_of_previous_owners: Optional[int] = Field(None, ge=0, description="Előző tulajdonosok száma")
# ── Internationalization validators ──
@field_validator('registration_country', 'import_country')
@classmethod
def validate_iso_country(cls, v: Optional[str]) -> Optional[str]:
"""registration_country és import_country: pontosan 2 karakteres nagybetűs string (ISO 3166-1 alpha-2)."""
if v is None or not isinstance(v, str):
return v
cleaned = v.strip().upper()
if len(cleaned) != 2:
raise ValueError(f'Az országkódnak pontosan 2 karakter hosszúnak kell lennie (ISO 3166-1 alpha-2), kapott: "{v}"')
return cleaned
@field_validator('number_of_previous_owners')
@classmethod
def validate_previous_owners(cls, v: Optional[int]) -> Optional[int]:
"""number_of_previous_owners >= 0."""
if v is not None and v < 0:
raise ValueError('Az előző tulajdonosok száma nem lehet negatív')
return v
@field_validator('first_domestic_registration_date')
@classmethod
def validate_not_future_date(cls, v: Optional[date]) -> Optional[date]:
"""first_domestic_registration_date nem lehet jövőbeli dátum."""
if v is not None and v > date.today():
raise ValueError('Az első belföldi forgalomba helyezés dátuma nem lehet jövőbeli')
return v
# === STATUS ===
current_mileage: Optional[int] = Field(None, ge=0, description="Aktuális km óra állás")
is_primary: Optional[bool] = Field(None, description="Elsődleges jármű")

View File

@@ -1,7 +1,7 @@
# /opt/docker/dev/service_finder/backend/app/schemas/asset_cost.py
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
from typing import Optional, Dict, Any
from datetime import datetime
from datetime import datetime, date
from decimal import Decimal
from uuid import UUID
@@ -34,13 +34,21 @@ class AssetCostBase(BaseModel):
return data
vat_rate: Optional[Decimal] = Field(None, description="ÁFA kulcs (pl. 27.00)")
currency: str = "HUF"
date: datetime = Field(default_factory=datetime.now)
cost_date: datetime = Field(default_factory=lambda: datetime.now(), validation_alias="date", description="Költség rögzítésének dátuma/időpontja")
invoice_number: Optional[str] = None
data: Dict[str, Any] = Field(default_factory=dict) # nyugta adatai, GPS koordináták
category_id: int # FK a CostCategory táblához
mileage_at_cost: Optional[int] = None # Stored inside data JSONB
description: Optional[str] = None # Stored inside data JSONB
# === BESZÁLLÍTÓI ADATOK (B2B expansion) ===
vendor_organization_id: Optional[int] = Field(None, description="Beszállító szervezet ID (FK fleet.organizations)")
external_vendor_name: Optional[str] = Field(None, max_length=200, description="Szabadon gépelhető beszállító név (ha nincs a rendszerben)")
# === KÖNYVELÉSI DÁTUMOK ===
invoice_date: Optional[date] = Field(None, description="Számla kiállításának dátuma")
fulfillment_date: Optional[date] = Field(None, description="Teljesítés dátuma")
@model_validator(mode='after')
def validate_gross_first(self):
"""Gross-First validation: ensure amount_gross is the primary source.
@@ -86,5 +94,8 @@ class AssetCostResponse(AssetCostBase):
category_code: Optional[str] = None # Resolved from CostCategory relationship
description: Optional[str] = None # Extracted from data['description']
mileage_at_cost: Optional[int] = None # Extracted from data['mileage_at_cost']
# Enriched vendor name (resolved from vendor_organization_id relationship)
vendor_name: Optional[str] = None # Resolved from Organization.name via vendor_organization_id
model_config = ConfigDict(from_attributes=True)

View File

@@ -0,0 +1,242 @@
# /opt/docker/dev/service_finder/backend/app/schemas/fleet_finance.py
"""
Pydantic sémák a flotta pénzügyi modelljeihez.
- AssetFinancials: Beszerzés, finanszírozás, értékcsökkenés
- InsuranceProvider: Biztosítók katalógusa
- VehicleInsurancePolicy: Jármű biztosítási kötvények
- VehicleTaxObligation: Jármű adókötelezettségek
"""
from pydantic import BaseModel, ConfigDict, Field, field_validator
from typing import Optional, Dict, Any, List
from uuid import UUID
from datetime import datetime, date
# =============================================================================
# AssetFinancials sémák
# =============================================================================
class AssetFinancialsUpdate(BaseModel):
"""AssetFinancials frissítése - minden mező opcionális."""
purchase_price_net: Optional[float] = Field(None, ge=0, description="Nettó vételár")
purchase_price_gross: Optional[float] = Field(None, ge=0, description="Bruttó vételár")
vat_rate: Optional[float] = Field(None, ge=0, le=100, description="ÁFA kulcs (%)")
activation_date: Optional[datetime] = Field(None, description="Aktiválás dátuma")
verified_purchase_date: Optional[datetime] = Field(None, description="Ellenőrzött vásárlási dátum")
financing_type: Optional[str] = Field(None, max_length=50, description="Finanszírozás típusa (cash, loan, lease)")
accounting_details: Optional[Dict[str, Any]] = Field(None, description="Számviteli részletek (JSONB)")
# Finanszírozás
down_payment: Optional[float] = Field(None, ge=0, description="Önerő")
monthly_installment: Optional[float] = Field(None, ge=0, description="Havi törlesztő")
residual_value: Optional[float] = Field(None, ge=0, description="Maradványérték")
contract_number: Optional[str] = Field(None, max_length=100, description="Szerződésszám")
financing_provider: Optional[str] = Field(None, max_length=200, description="Finanszírozó")
interest_rate: Optional[float] = Field(None, ge=0, le=100, description="Kamatláb (%)")
total_contract_value: Optional[float] = Field(None, ge=0, description="Teljes szerződéses érték")
lease_start_date: Optional[date] = Field(None, description="Lízing kezdete")
lease_end_date: Optional[date] = Field(None, description="Lízing vége")
model_config = ConfigDict(from_attributes=True)
class AssetFinancialsResponse(BaseModel):
"""AssetFinancials teljes válaszmodell."""
id: int
asset_id: UUID
# Beszerzés
purchase_price_net: float
purchase_price_gross: float
vat_rate: float
activation_date: Optional[datetime] = None
verified_purchase_date: Optional[datetime] = None
financing_type: str
accounting_details: Dict[str, Any] = Field(default_factory=dict)
# Finanszírozás
down_payment: Optional[float] = None
monthly_installment: Optional[float] = None
residual_value: Optional[float] = None
contract_number: Optional[str] = None
financing_provider: Optional[str] = None
interest_rate: Optional[float] = None
total_contract_value: Optional[float] = None
lease_start_date: Optional[date] = None
lease_end_date: Optional[date] = None
model_config = ConfigDict(from_attributes=True)
# =============================================================================
# InsuranceProvider sémák
# =============================================================================
class InsuranceProviderResponse(BaseModel):
"""Biztosító teljes válaszmodell."""
id: int
name: str
country_code: Optional[str] = Field(None, max_length=2, description="ISO 3166-1 alpha-2 országkód (pl. 'HU'). Ha None, akkor globális.")
claim_phone: Optional[str] = None
claim_url: Optional[str] = None
services_offered: Dict[str, Any] = Field(default_factory=dict)
is_active: bool = True
model_config = ConfigDict(from_attributes=True)
class InsuranceProviderCreate(BaseModel):
"""Új biztosító létrehozása."""
name: str = Field(..., min_length=1, max_length=150, description="Biztosító neve")
country_code: Optional[str] = Field(None, max_length=2, description="ISO 3166-1 alpha-2 országkód (pl. 'HU'). Ha None, akkor globális.")
claim_phone: Optional[str] = Field(None, max_length=50, description="Kárbejelentő telefonszám")
claim_url: Optional[str] = Field(None, max_length=255, description="Kárbejelentő URL")
services_offered: Optional[Dict[str, Any]] = Field(None, description="Kínált szolgáltatások (JSONB)")
is_active: bool = Field(default=True, description="Aktív-e")
model_config = ConfigDict(from_attributes=True)
class InsuranceProviderUpdate(BaseModel):
"""Biztosító frissítése - minden mező opcionális."""
name: Optional[str] = Field(None, min_length=1, max_length=150, description="Biztosító neve")
country_code: Optional[str] = Field(None, max_length=2, description="ISO 3166-1 alpha-2 országkód (pl. 'HU'). Ha None, akkor globális.")
claim_phone: Optional[str] = Field(None, max_length=50, description="Kárbejelentő telefonszám")
claim_url: Optional[str] = Field(None, max_length=255, description="Kárbejelentő URL")
services_offered: Optional[Dict[str, Any]] = Field(None, description="Kínált szolgáltatások (JSONB)")
is_active: Optional[bool] = Field(None, description="Aktív-e")
model_config = ConfigDict(from_attributes=True)
# =============================================================================
# VehicleInsurancePolicy sémák
# =============================================================================
class VehicleInsurancePolicyCreate(BaseModel):
"""Új biztosítási kötvény létrehozása."""
provider_id: int = Field(..., description="Biztosító ID (insurance_providers tábla)")
insurance_type: str = Field(..., max_length=20, description="Biztosítás típusa (KGFB, CASCO)")
policy_number: str = Field(..., max_length=100, description="Kötvényszám")
start_date: date = Field(..., description="Biztosítás kezdete")
expiry_date: date = Field(..., description="Biztosítás lejárata")
premium_amount: float = Field(..., ge=0, description="Díj összege")
currency: str = Field(default="HUF", max_length=3, description="Pénznem")
document_id: Optional[UUID] = Field(None, description="Kapcsolódó dokumentum ID (PDF kötvény)")
@field_validator('insurance_type')
@classmethod
def validate_insurance_type(cls, v: str) -> str:
"""insurance_type csak KGFB vagy CASCO lehet."""
allowed = {"KGFB", "CASCO"}
cleaned = v.strip().upper()
if cleaned not in allowed:
raise ValueError(f'A biztosítás típusa csak {allowed} lehet, kapott: "{v}"')
return cleaned
@field_validator('expiry_date')
@classmethod
def validate_expiry_after_start(cls, v: date, info):
"""expiry_date nem lehet a start_date előtt."""
values = info.data
start = values.get('start_date')
if start and v <= start:
raise ValueError('A biztosítás lejárati dátuma nem lehet a kezdő dátum előtt vagy azzal egyenlő')
return v
model_config = ConfigDict(from_attributes=True)
class VehicleInsurancePolicyUpdate(BaseModel):
"""Biztosítási kötvény frissítése - minden mező opcionális."""
provider_id: Optional[int] = Field(None, description="Biztosító ID")
insurance_type: Optional[str] = Field(None, max_length=20, description="Biztosítás típusa")
policy_number: Optional[str] = Field(None, max_length=100, description="Kötvényszám")
start_date: Optional[date] = Field(None, description="Biztosítás kezdete")
expiry_date: Optional[date] = Field(None, description="Biztosítás lejárata")
premium_amount: Optional[float] = Field(None, ge=0, description="Díj összege")
currency: Optional[str] = Field(None, max_length=3, description="Pénznem")
document_id: Optional[UUID] = Field(None, description="Kapcsolódó dokumentum ID (PDF kötvény)")
model_config = ConfigDict(from_attributes=True)
class VehicleInsurancePolicyResponse(BaseModel):
"""Biztosítási kötvény teljes válaszmodell."""
id: UUID
asset_id: UUID
provider_id: int
insurance_type: str
policy_number: str
start_date: date
expiry_date: date
premium_amount: float
currency: str
document_id: Optional[UUID] = None
model_config = ConfigDict(from_attributes=True)
# =============================================================================
# VehicleTaxObligation sémák
# =============================================================================
class VehicleTaxObligationCreate(BaseModel):
"""Új adókötelezettség létrehozása."""
tax_type: str = Field(..., max_length=30, description="Adó típusa (WEIGHT_TAX, COMPANY_CAR_TAX)")
tax_year: int = Field(..., ge=2000, le=2100, description="Adóév")
amount: float = Field(..., ge=0, description="Adó összege")
currency: str = Field(default="HUF", max_length=3, description="Pénznem")
due_date: Optional[date] = Field(None, description="Fizetési határidő")
payment_status: str = Field(default="pending", max_length=20, description="Fizetési státusz")
document_id: Optional[UUID] = Field(None, description="Kapcsolódó dokumentum ID (PDF adóbevallás)")
@field_validator('tax_type')
@classmethod
def validate_tax_type(cls, v: str) -> str:
"""tax_type csak WEIGHT_TAX vagy COMPANY_CAR_TAX lehet."""
allowed = {"WEIGHT_TAX", "COMPANY_CAR_TAX"}
cleaned = v.strip().upper()
if cleaned not in allowed:
raise ValueError(f'Az adó típusa csak {allowed} lehet, kapott: "{v}"')
return cleaned
@field_validator('payment_status')
@classmethod
def validate_payment_status(cls, v: str) -> str:
"""payment_status csak pending, paid, overdue lehet."""
allowed = {"pending", "paid", "overdue"}
cleaned = v.strip().lower()
if cleaned not in allowed:
raise ValueError(f'A fizetési státusz csak {allowed} lehet, kapott: "{v}"')
return cleaned
model_config = ConfigDict(from_attributes=True)
class VehicleTaxObligationUpdate(BaseModel):
"""Adókötelezettség frissítése - minden mező opcionális."""
tax_type: Optional[str] = Field(None, max_length=30, description="Adó típusa")
tax_year: Optional[int] = Field(None, ge=2000, le=2100, description="Adóév")
amount: Optional[float] = Field(None, ge=0, description="Adó összege")
currency: Optional[str] = Field(None, max_length=3, description="Pénznem")
due_date: Optional[date] = Field(None, description="Fizetési határidő")
payment_status: Optional[str] = Field(None, max_length=20, description="Fizetési státusz")
document_id: Optional[UUID] = Field(None, description="Kapcsolódó dokumentum ID (PDF adóbevallás)")
model_config = ConfigDict(from_attributes=True)
class VehicleTaxObligationResponse(BaseModel):
"""Adókötelezettség teljes válaszmodell."""
id: UUID
asset_id: UUID
tax_type: str
tax_year: int
amount: float
currency: str
due_date: Optional[date] = None
payment_status: str
document_id: Optional[UUID] = None
model_config = ConfigDict(from_attributes=True)

View File

@@ -1,5 +1,6 @@
from pydantic import BaseModel, Field, ConfigDict
from typing import Optional, List, Any
from datetime import datetime
class ContactCreate(BaseModel):
full_name: str
@@ -78,7 +79,11 @@ class OrganizationResponse(BaseModel):
is_active: bool = True
is_deleted: bool = False
subscription_plan: str = "FREE"
subscription_expires_at: Optional[datetime] = None
subscription_tier_id: Optional[int] = None
# P0: Real subscription limits from the assigned subscription_tier JSONB rules
max_vehicles: int = 1
max_garages: int = 1
visual_settings: Optional[dict] = None
notification_settings: Optional[Any] = None
external_integration_config: Optional[Any] = None

View File

@@ -56,6 +56,10 @@ class UserResponse(UserBase):
person_id: Optional[int] = None
role: str
subscription_plan: str
subscription_expires_at: Optional[datetime] = None
# P0: Real subscription limits from the assigned subscription_tier JSONB rules
max_vehicles: int = 1
max_garages: int = 1
scope_level: str
scope_id: Optional[str] = None
ui_mode: str = "personal"