szolgáltatók beálltásai, szerkesztése , létrehozása

This commit is contained in:
Roo
2026-06-17 11:52:25 +00:00
parent 213ba3b0f1
commit bf3a971ff1
56 changed files with 14421 additions and 1512 deletions

View File

@@ -1,5 +1,5 @@
# /opt/docker/dev/service_finder/backend/app/schemas/asset.py
from pydantic import BaseModel, ConfigDict, Field, validator, root_validator, model_validator
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
@@ -82,6 +82,10 @@ class AssetResponse(BaseModel):
created_at: datetime
updated_at: Optional[datetime] = None
# === WARRANTY ===
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")
# === SALES MODULE ===
is_for_sale: bool = Field(default=False)
price: Optional[float] = None
@@ -128,6 +132,47 @@ class AssetCreate(BaseModel):
return None
return v
# ── Data normalization validators ──
@field_validator('brand')
@classmethod
def normalize_brand(cls, v: Optional[str]) -> Optional[str]:
"""Brand: UPPERCASE, strip whitespace."""
if v is None or not isinstance(v, str):
return v
return v.strip().upper()
@field_validator('model')
@classmethod
def normalize_model(cls, v: Optional[str]) -> Optional[str]:
"""Model: UPPERCASE, remove ALL spaces."""
if v is None or not isinstance(v, str):
return v
return v.upper().replace(' ', '')
# ── Model validator: normalize type_designation inside individual_equipment ──
@model_validator(mode='after')
def normalize_individual_equipment(self):
"""Normalize type_designation inside individual_equipment.car_specs.
SAFE None handling: checks every level before calling .upper().
- individual_equipment may be None or empty dict
- car_specs may be None or missing key
- type_designation may be None, empty string, or a valid string
"""
if not self.individual_equipment:
return self
if not isinstance(self.individual_equipment, dict):
return self
car_specs = self.individual_equipment.get('car_specs')
if not car_specs or not isinstance(car_specs, dict):
return self
td = car_specs.get('type_designation')
if td is not None and isinstance(td, str) and td.strip():
car_specs['type_designation'] = td.strip().upper().replace(' ', '')
return self
# ── Root validator: ensure at least one of vin or license_plate is provided ──
@root_validator(skip_on_failure=True)
def validate_vin_or_plate(cls, values):
@@ -166,7 +211,7 @@ class AssetCreate(BaseModel):
trim_level: Optional[str] = Field(None, max_length=100, description="Felszereltségi szint")
roof_type: Optional[str] = Field(None, max_length=50, description="Tető típus")
audio_system_type: Optional[str] = Field(None, max_length=100, description="Hangrendszer")
individual_equipment: Dict[str, Any] = Field(default_factory=dict, description="Egyedi felszerelések")
individual_equipment: Optional[Dict[str, Any]] = Field(default_factory=dict, description="Egyedi felszerelések")
# === MILEAGE (Optional) ===
current_mileage: Optional[int] = Field(None, ge=0, description="Aktuális km óra állás")
@@ -179,6 +224,10 @@ class AssetCreate(BaseModel):
organization_id: Optional[int] = Field(None, description="Szervezet ID (alapértelmezett a felhasználó szervezete)")
branch_id: Optional[UUID] = Field(None, description="Garázs (Branch) ID, ahova a járművet rendeljük. Ha nincs megadva, a szervezet központi garázsába kerül.")
# === WARRANTY ===
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")
# === PRIMARY VEHICLE FLAG ===
is_primary: bool = Field(default=False, description="Elsődleges jármű (tárolva: individual_equipment JSONB)")
@@ -224,6 +273,47 @@ class AssetUpdate(BaseModel):
return None
return v
# ── Data normalization validators ──
@field_validator('brand')
@classmethod
def normalize_brand(cls, v: Optional[str]) -> Optional[str]:
"""Brand: UPPERCASE, strip whitespace."""
if v is None or not isinstance(v, str):
return v
return v.strip().upper()
@field_validator('model')
@classmethod
def normalize_model(cls, v: Optional[str]) -> Optional[str]:
"""Model: UPPERCASE, remove ALL spaces."""
if v is None or not isinstance(v, str):
return v
return v.upper().replace(' ', '')
# ── Model validator: normalize type_designation inside individual_equipment ──
@model_validator(mode='after')
def normalize_individual_equipment(self):
"""Normalize type_designation inside individual_equipment.car_specs.
SAFE None handling: checks every level before calling .upper().
- individual_equipment may be None or empty dict
- car_specs may be None or missing key
- type_designation may be None, empty string, or a valid string
"""
if not self.individual_equipment:
return self
if not isinstance(self.individual_equipment, dict):
return self
car_specs = self.individual_equipment.get('car_specs')
if not car_specs or not isinstance(car_specs, dict):
return self
td = car_specs.get('type_designation')
if td is not None and isinstance(td, str) and td.strip():
car_specs['type_designation'] = td.strip().upper().replace(' ', '')
return self
# ── Model validator: ensure at least one of vin or license_plate is provided ──
@model_validator(mode='after')
def validate_vin_or_plate(self):
@@ -276,8 +366,63 @@ class AssetUpdate(BaseModel):
year_of_manufacture: Optional[int] = Field(None, ge=1900, le=2100, description="Gyártási év")
first_registration_date: Optional[datetime] = Field(None, description="Első forgalomba helyezés dátuma")
# === WARRANTY ===
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")
# === 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ű")
model_config = ConfigDict(from_attributes=True)
model_config = ConfigDict(from_attributes=True)
class AssetEventCreate(BaseModel):
"""Schema for creating a new AssetEvent (Service Book entry)."""
event_type: str = Field(..., description="Event type: SERVICE, REPAIR, ACCIDENT, INSPECTION, TIRE_CHANGE, MAINTENANCE, UPGRADE, RECALL")
event_date: Optional[datetime] = Field(None, description="Event date (default: now)")
odometer_reading: Optional[int] = Field(None, ge=0, description="Odometer reading at event time")
description: Optional[str] = Field(None, max_length=2000, description="Event description")
cost_id: Optional[UUID] = Field(None, description="Associated cost record ID")
cost_amount: Optional[float] = Field(None, gt=0, description="Cost amount in the given currency. If > 0, an AssetCost record is auto-created.")
currency: Optional[str] = Field(None, min_length=3, max_length=3, description="Currency code (e.g. HUF, EUR). Defaults to HUF on backend.")
model_config = ConfigDict(from_attributes=True)
class AssetEventResponse(BaseModel):
"""Schema for returning an AssetEvent."""
id: UUID
asset_id: UUID
user_id: Optional[int] = None
organization_id: Optional[int] = None
event_type: str
odometer_reading: Optional[int] = None
description: Optional[str] = None
cost_id: Optional[UUID] = None
cost_amount: Optional[float] = Field(None, description="Auto-created cost amount")
currency: Optional[str] = Field(None, description="Currency code (e.g. HUF)")
event_date: datetime
created_at: datetime
updated_at: Optional[datetime] = None
model_config = ConfigDict(from_attributes=True)
@classmethod
def model_validate(cls, obj, **kwargs):
"""Override to resolve cost_amount and currency from the related AssetCost."""
data = {}
if hasattr(obj, '__dict__'):
# Copy ORM attributes
for field in cls.model_fields:
if hasattr(obj, field):
data[field] = getattr(obj, field)
# Resolve cost_amount and currency from the cost relationship
cost = getattr(obj, 'cost', None)
if cost is not None:
if data.get('cost_amount') is None:
data['cost_amount'] = float(cost.amount_net) if cost.amount_net is not None else None
if data.get('currency') is None:
data['currency'] = cost.currency
return cls(**data)