# models/schema/invoice.py """ Pydantic schemas for invoice operations. Supports invoice settings management and invoice generation. """ from datetime import datetime from decimal import Decimal from pydantic import BaseModel, ConfigDict, Field # ============================================================================ # Invoice Settings Schemas # ============================================================================ class VendorInvoiceSettingsCreate(BaseModel): """Schema for creating vendor invoice settings.""" company_name: str = Field(..., min_length=1, max_length=255) company_address: str | None = Field(None, max_length=255) company_city: str | None = Field(None, max_length=100) company_postal_code: str | None = Field(None, max_length=20) company_country: str = Field(default="LU", min_length=2, max_length=2) vat_number: str | None = Field(None, max_length=50) is_vat_registered: bool = True is_oss_registered: bool = False oss_registration_country: str | None = Field(None, min_length=2, max_length=2) invoice_prefix: str = Field(default="INV", max_length=20) invoice_number_padding: int = Field(default=5, ge=1, le=10) payment_terms: str | None = None bank_name: str | None = Field(None, max_length=255) bank_iban: str | None = Field(None, max_length=50) bank_bic: str | None = Field(None, max_length=20) footer_text: str | None = None default_vat_rate: Decimal = Field(default=Decimal("17.00"), ge=0, le=100) class VendorInvoiceSettingsUpdate(BaseModel): """Schema for updating vendor invoice settings.""" company_name: str | None = Field(None, min_length=1, max_length=255) company_address: str | None = Field(None, max_length=255) company_city: str | None = Field(None, max_length=100) company_postal_code: str | None = Field(None, max_length=20) company_country: str | None = Field(None, min_length=2, max_length=2) vat_number: str | None = None is_vat_registered: bool | None = None is_oss_registered: bool | None = None oss_registration_country: str | None = None invoice_prefix: str | None = Field(None, max_length=20) invoice_number_padding: int | None = Field(None, ge=1, le=10) payment_terms: str | None = None bank_name: str | None = Field(None, max_length=255) bank_iban: str | None = Field(None, max_length=50) bank_bic: str | None = Field(None, max_length=20) footer_text: str | None = None default_vat_rate: Decimal | None = Field(None, ge=0, le=100) class VendorInvoiceSettingsResponse(BaseModel): """Schema for vendor invoice settings response.""" model_config = ConfigDict(from_attributes=True) id: int vendor_id: int company_name: str company_address: str | None company_city: str | None company_postal_code: str | None company_country: str vat_number: str | None is_vat_registered: bool is_oss_registered: bool oss_registration_country: str | None invoice_prefix: str invoice_next_number: int invoice_number_padding: int payment_terms: str | None bank_name: str | None bank_iban: str | None bank_bic: str | None footer_text: str | None default_vat_rate: Decimal created_at: datetime updated_at: datetime # ============================================================================ # Invoice Line Item Schemas # ============================================================================ class InvoiceLineItem(BaseModel): """Schema for invoice line item.""" description: str quantity: int = Field(..., ge=1) unit_price_cents: int total_cents: int sku: str | None = None ean: str | None = None class InvoiceLineItemResponse(BaseModel): """Schema for invoice line item in response.""" description: str quantity: int unit_price_cents: int total_cents: int sku: str | None = None ean: str | None = None @property def unit_price(self) -> float: return self.unit_price_cents / 100 @property def total(self) -> float: return self.total_cents / 100 # ============================================================================ # Invoice Address Schemas # ============================================================================ class InvoiceSellerDetails(BaseModel): """Seller details for invoice.""" company_name: str address: str | None = None city: str | None = None postal_code: str | None = None country: str vat_number: str | None = None class InvoiceBuyerDetails(BaseModel): """Buyer details for invoice.""" name: str email: str | None = None address: str | None = None city: str | None = None postal_code: str | None = None country: str vat_number: str | None = None # For B2B # ============================================================================ # Invoice Schemas # ============================================================================ class InvoiceCreate(BaseModel): """Schema for creating an invoice from an order.""" order_id: int notes: str | None = None class InvoiceManualCreate(BaseModel): """Schema for creating a manual invoice (without order).""" buyer_details: InvoiceBuyerDetails line_items: list[InvoiceLineItem] notes: str | None = None payment_terms: str | None = None class InvoiceResponse(BaseModel): """Schema for invoice response.""" model_config = ConfigDict(from_attributes=True) id: int vendor_id: int order_id: int | None invoice_number: str invoice_date: datetime status: str seller_details: dict buyer_details: dict line_items: list[dict] vat_regime: str destination_country: str | None vat_rate: Decimal vat_rate_label: str | None currency: str subtotal_cents: int vat_amount_cents: int total_cents: int payment_terms: str | None bank_details: dict | None footer_text: str | None pdf_generated_at: datetime | None pdf_path: str | None notes: str | None created_at: datetime updated_at: datetime @property def subtotal(self) -> float: return self.subtotal_cents / 100 @property def vat_amount(self) -> float: return self.vat_amount_cents / 100 @property def total(self) -> float: return self.total_cents / 100 class InvoiceListResponse(BaseModel): """Schema for invoice list response (summary).""" model_config = ConfigDict(from_attributes=True) id: int invoice_number: str invoice_date: datetime status: str currency: str total_cents: int order_id: int | None # Buyer name for display buyer_name: str | None = None @property def total(self) -> float: return self.total_cents / 100 class InvoiceStatusUpdate(BaseModel): """Schema for updating invoice status.""" status: str = Field(..., pattern="^(draft|issued|paid|cancelled)$") # ============================================================================ # Paginated Response # ============================================================================ class InvoiceListPaginatedResponse(BaseModel): """Paginated invoice list response.""" items: list[InvoiceListResponse] total: int page: int per_page: int pages: int # ============================================================================ # PDF Response # ============================================================================ class InvoicePDFGeneratedResponse(BaseModel): """Response for PDF generation.""" pdf_path: str message: str = "PDF generated successfully" class InvoiceStatsResponse(BaseModel): """Invoice statistics response.""" total_invoices: int total_revenue_cents: int draft_count: int issued_count: int paid_count: int cancelled_count: int @property def total_revenue(self) -> float: return self.total_revenue_cents / 100