import uuid from datetime import date as DateType, datetime from decimal import Decimal from typing import Any from pydantic import BaseModel, Field # --------------------------------------------------------------------------- # Tax rate config # --------------------------------------------------------------------------- class TaxRateConfigUpdate(BaseModel): """PUT /tax/rate-configs/{tax_year} — pass only the rate types you want to upsert.""" income_tax: dict[str, Any] | None = None ni: dict[str, Any] | None = None cgt: dict[str, Any] | None = None dividend: dict[str, Any] | None = None class TaxRateConfigResponse(BaseModel): tax_year: int rates: dict[str, Any] updated_at: str # --------------------------------------------------------------------------- # Tax profile # --------------------------------------------------------------------------- class TaxProfileCreate(BaseModel): tax_code: str = Field(default="1257L", min_length=1, max_length=20) employer_name: str | None = Field(default=None, max_length=200) is_cumulative: bool = True class TaxProfileResponse(BaseModel): id: uuid.UUID tax_year: int tax_code: str employer_name: str | None is_cumulative: bool created_at: str updated_at: str # --------------------------------------------------------------------------- # Payslips # --------------------------------------------------------------------------- class PayslipCreate(BaseModel): period_month: int | None = Field(default=None, ge=1, le=12) period_year: int = Field(..., ge=2000, le=2100) gross_pay: Decimal = Field(..., ge=0) income_tax_withheld: Decimal = Field(..., ge=0) ni_withheld: Decimal = Field(..., ge=0) net_pay: Decimal = Field(..., ge=0) notes: str | None = None class PayslipUpdate(BaseModel): period_month: int | None = Field(default=None, ge=1, le=12) period_year: int | None = Field(default=None, ge=2000, le=2100) gross_pay: Decimal | None = Field(default=None, ge=0) income_tax_withheld: Decimal | None = Field(default=None, ge=0) ni_withheld: Decimal | None = Field(default=None, ge=0) net_pay: Decimal | None = Field(default=None, ge=0) notes: str | None = None class PayslipResponse(BaseModel): id: uuid.UUID tax_profile_id: uuid.UUID period_month: int | None period_year: int gross_pay: str income_tax_withheld: str ni_withheld: str net_pay: str is_p60: bool notes: str | None created_at: str class P60Entry(BaseModel): gross_pay: Decimal = Field(..., ge=0) income_tax_withheld: Decimal = Field(..., ge=0) ni_withheld: Decimal = Field(..., ge=0) net_pay: Decimal = Field(..., ge=0) # --------------------------------------------------------------------------- # Manual CGT disposals # --------------------------------------------------------------------------- class ManualDisposalCreate(BaseModel): disposal_date: DateType asset_description: str = Field(..., min_length=1, max_length=500) proceeds: Decimal = Field(..., ge=0) cost_basis: Decimal = Field(..., ge=0) notes: str | None = None class ManualDisposalUpdate(BaseModel): disposal_date: DateType | None = None asset_description: str | None = Field(default=None, min_length=1, max_length=500) proceeds: Decimal | None = Field(default=None, ge=0) cost_basis: Decimal | None = Field(default=None, ge=0) notes: str | None = None class ManualDisposalResponse(BaseModel): id: uuid.UUID tax_year: int disposal_date: str asset_description: str proceeds: str cost_basis: str gain_loss: str notes: str | None created_at: str # --------------------------------------------------------------------------- # Tax report (nested) # --------------------------------------------------------------------------- class BandBreakdownItem(BaseModel): rate: float taxable: float tax: float from_: int | None = Field(default=None, alias="from") to: int | None = None model_config = {"populate_by_name": True} class IncomeTaxSummary(BaseModel): personal_allowance: str taxable_income: str liability: str band_breakdown: list[dict[str, Any]] withheld: str owed: str class NISummary(BaseModel): liability: str band_breakdown: list[dict[str, Any]] withheld: str owed: str class InvestmentDisposalItem(BaseModel): date: str asset: str symbol: str quantity: str proceeds: str cost_basis: str fees: str gain_loss: str class CGTSummary(BaseModel): gross_gain: str exempt: str taxable_gain: str liability: str band_breakdown: list[dict[str, Any]] investment_disposals: list[dict[str, Any]] manual_disposals: list[dict[str, Any]] total_gain: str class DividendTransactionItem(BaseModel): date: str asset: str symbol: str amount: str class DividendSummary(BaseModel): gross_dividends: str allowance: str taxable_dividends: str liability: str band_breakdown: list[dict[str, Any]] dividend_transactions: list[dict[str, Any]] class TaxReportSummary(BaseModel): total_liability: str total_withheld: str net_owed: str overpaid: bool class IncomeSummary(BaseModel): gross_income: str income_tax_withheld: str ni_withheld: str payslips: list[dict[str, Any]] class TaxReportResponse(BaseModel): tax_year: int tax_year_display: str profile: dict[str, Any] | None income: IncomeSummary income_tax: IncomeTaxSummary ni: NISummary cgt: CGTSummary dividends: DividendSummary summary: TaxReportSummary