from datetime import date, datetime from decimal import Decimal from pydantic import BaseModel, ConfigDict, Field, computed_field # ----- Quote items ----- class QuoteItemBase(BaseModel): concept: str = Field(..., max_length=60) description: str | None = Field(None, max_length=255) supplier_id: int | None = None quantity: Decimal = Field(Decimal(1), ge=0, max_digits=12, decimal_places=2) unit_cost: Decimal = Field(Decimal(0), ge=0, max_digits=14, decimal_places=2) unit_sale: Decimal = Field(Decimal(0), ge=0, max_digits=14, decimal_places=2) currency: str | None = Field(None, max_length=3) class QuoteItemCreate(QuoteItemBase): quote_id: int class QuoteItemUpdate(BaseModel): concept: str | None = Field(None, max_length=60) description: str | None = Field(None, max_length=255) supplier_id: int | None = None quantity: Decimal | None = Field(None, ge=0, max_digits=12, decimal_places=2) unit_cost: Decimal | None = Field(None, ge=0, max_digits=14, decimal_places=2) unit_sale: Decimal | None = Field(None, ge=0, max_digits=14, decimal_places=2) currency: str | None = Field(None, max_length=3) class QuoteItemResponse(QuoteItemBase): model_config = ConfigDict(from_attributes=True) id: int quote_id: int tenant_id: int company_id: int @computed_field @property def line_cost(self) -> Decimal: return (self.quantity or Decimal(0)) * (self.unit_cost or Decimal(0)) @computed_field @property def line_sale(self) -> Decimal: return (self.quantity or Decimal(0)) * (self.unit_sale or Decimal(0)) # ----- Quotes ----- class QuoteBase(BaseModel): reference: str | None = Field(None, max_length=40) service_request_id: int | None = None account_id: int | None = None currency: str = Field("USD", max_length=3) issue_date: date | None = None valid_until: date | None = None notes: str | None = None terms: str | None = None owner_user_id: str | None = Field(None, max_length=64) class QuoteCreate(QuoteBase): pass class QuoteUpdate(BaseModel): reference: str | None = Field(None, max_length=40) service_request_id: int | None = None account_id: int | None = None currency: str | None = Field(None, max_length=3) issue_date: date | None = None valid_until: date | None = None notes: str | None = None terms: str | None = None owner_user_id: str | None = Field(None, max_length=64) class QuoteResponse(QuoteBase): model_config = ConfigDict(from_attributes=True) id: int status: str total_cost: Decimal total_sale: Decimal sent_at: datetime | None = None accepted_at: datetime | None = None rejected_at: datetime | None = None created_by: str | None = None updated_by: str | None = None tenant_id: int company_id: int created_at: datetime updated_at: datetime @computed_field @property def margin(self) -> Decimal: return (self.total_sale or Decimal(0)) - (self.total_cost or Decimal(0))