import uuid from datetime import datetime from typing import Optional, Any from pydantic import BaseModel # ── Magic link ───────────────────────────────────────────────────────────────── class MagicLinkVerifySchema(BaseModel): token: str # ── Claim ────────────────────────────────────────────────────────────────────── class ClaimRequestSchema(BaseModel): email: str class ClaimCompleteSchema(BaseModel): email: str code: str password: str class MemberClaimVerifyCodeSchema(BaseModel): code: str password: str # ── Auth ─────────────────────────────────────────────────────────────────────── class MemberLoginSchema(BaseModel): email: str password: str class MemberLoginVerifySchema(BaseModel): email: str code: str class MemberTokenResponse(BaseModel): access_token: str refresh_token: str token_type: str = "bearer" class MemberRefreshSchema(BaseModel): refresh_token: str class MemberLogoutSchema(BaseModel): refresh_token: Optional[str] = None # ── Profile ──────────────────────────────────────────────────────────────────── class MemberProfileResponse(BaseModel): id: uuid.UUID email: str first_name: str last_name: str phone: Optional[str] address: Optional[str] emergency_contact: Optional[str] notifications_enabled: bool is_claimed: bool member_status: str activated_at: Optional[datetime] created_at: datetime model_config = {"from_attributes": True} class MemberProfileUpdate(BaseModel): first_name: Optional[str] = None last_name: Optional[str] = None phone: Optional[str] = None address: Optional[str] = None emergency_contact: Optional[str] = None notifications_enabled: Optional[bool] = None class MemberOnboardingResponse(BaseModel): id: uuid.UUID email: str first_name: str last_name: str phone: Optional[str] address: Optional[str] emergency_contact: Optional[str] notifications_enabled: bool onboarding_data: Optional[Any] is_claimed: bool member_status: str claimed_at: Optional[datetime] onboarding_completed_at: Optional[datetime] contract_signed_at: Optional[datetime] contract_signer_name: Optional[str] contract_version: Optional[str] activated_at: Optional[datetime] created_at: datetime model_config = {"from_attributes": True} class MemberOnboardingUpdate(BaseModel): first_name: Optional[str] = None last_name: Optional[str] = None phone: Optional[str] = None address: Optional[str] = None emergency_contact: Optional[str] = None onboarding_data: Optional[Any] = None complete_onboarding: bool = False class ContractSignSchema(BaseModel): signer_name: str agreed: bool contract_version: Optional[str] = None # ── Walks ────────────────────────────────────────────────────────────────────── class WalkResponse(BaseModel): id: uuid.UUID service_type: str walked_at: datetime duration_minutes: int notes: Optional[str] recorded_by: Optional[str] created_at: datetime model_config = {"from_attributes": True} # ── Bookings ─────────────────────────────────────────────────────────────────── class BookingCreate(BaseModel): service_type: str requested_day: Optional[str] = None requested_date: Optional[datetime] = None requested_timeslot: Optional[str] = None notes: Optional[str] = None class BookingResponse(BaseModel): id: uuid.UUID service_type: str requested_date: Optional[datetime] status: str notes: Optional[str] admin_notes: Optional[str] created_at: datetime model_config = {"from_attributes": True} class BookingSlotAvailabilityResponse(BaseModel): slot: str label: str booked: int capacity: int remaining: int is_available: bool class BookingAvailabilityDayResponse(BaseModel): date: str label: str slots: list[BookingSlotAvailabilityResponse] class BookingAvailabilityResponse(BaseModel): requested_date: str selected: BookingAvailabilityDayResponse alternatives: list[BookingAvailabilityDayResponse] # ── Messages ─────────────────────────────────────────────────────────────────── class MessageResponse(BaseModel): id: uuid.UUID subject: str body: str sent_by: Optional[str] read_at: Optional[datetime] created_at: datetime direction: str = "inbound" reply_to_id: Optional[uuid.UUID] = None model_config = {"from_attributes": True} class MemberReplySchema(BaseModel): body: str # ── Admin: Create Member ─────────────────────────────────────────────────────── class AdminCreateMember(BaseModel): email: str first_name: str last_name: str phone: Optional[str] = None address: Optional[str] = None emergency_contact: Optional[str] = None onboarding_data: Optional[Any] = None service_pricing_overrides: Optional[Any] = None force_two_factor: Optional[bool] = None class AdminMemberResponse(BaseModel): id: uuid.UUID email: str first_name: str last_name: str phone: Optional[str] address: Optional[str] emergency_contact: Optional[str] notifications_enabled: bool onboarding_data: Optional[Any] is_claimed: bool is_active: bool member_status: str claimed_at: Optional[datetime] onboarding_completed_at: Optional[datetime] contract_signed_at: Optional[datetime] contract_signer_name: Optional[str] contract_version: Optional[str] activated_at: Optional[datetime] service_pricing_overrides: Optional[Any] force_two_factor: Optional[bool] created_at: datetime model_config = {"from_attributes": True} class AdminMemberUpdate(BaseModel): first_name: Optional[str] = None last_name: Optional[str] = None phone: Optional[str] = None address: Optional[str] = None emergency_contact: Optional[str] = None notifications_enabled: Optional[bool] = None onboarding_data: Optional[Any] = None is_active: Optional[bool] = None member_status: Optional[str] = None service_pricing_overrides: Optional[Any] = None force_two_factor: Optional[bool] = None class AdminMemberToggleAction(BaseModel): enabled: bool class AdminBookingResponse(BaseModel): id: uuid.UUID member_id: uuid.UUID service_type: str requested_date: Optional[datetime] status: str notes: Optional[str] admin_notes: Optional[str] created_at: datetime # Joined fields member_first_name: Optional[str] = None member_last_name: Optional[str] = None member_email: Optional[str] = None member_dog_name: Optional[str] = None member_dog_breed: Optional[str] = None model_config = {"from_attributes": True} class AdminBookingCreate(BaseModel): member_id: uuid.UUID service_type: str requested_date: Optional[datetime] = None status: str = "confirmed" notes: Optional[str] = None admin_notes: Optional[str] = None class AdminBookingUpdate(BaseModel): requested_date: Optional[datetime] = None status: Optional[str] = None # pending | confirmed | cancelled | completed notes: Optional[str] = None admin_notes: Optional[str] = None # ── Admin: Record Walk ───────────────────────────────────────────────────────── class AdminRecordWalk(BaseModel): member_id: uuid.UUID walked_at: datetime service_type: str duration_minutes: int = 60 notes: Optional[str] = None # ── Admin: Send Message ──────────────────────────────────────────────────────── class AdminSendMessage(BaseModel): member_id: uuid.UUID subject: str body: str class AdminNotificationSettingsResponse(BaseModel): automatic_member_notifications_enabled: bool nz_public_holiday_notifications_enabled: bool invoice_reminder_notifications_enabled: bool invoice_day_of_week: int class AdminNotificationSettingsUpdate(BaseModel): automatic_member_notifications_enabled: Optional[bool] = None nz_public_holiday_notifications_enabled: Optional[bool] = None invoice_reminder_notifications_enabled: Optional[bool] = None invoice_day_of_week: Optional[int] = None class AdminNotificationRunResponse(BaseModel): automatic_member_notifications_enabled: bool public_holiday_messages_sent: int invoice_reminders_sent: int class AdminNotificationFeedItemResponse(BaseModel): id: str type: str title: str description: str created_at: datetime href: str class AdminNotificationsResponse(BaseModel): items: list[AdminNotificationFeedItemResponse] total: int settings: AdminNotificationSettingsResponse class AdminMessageHistoryResponse(BaseModel): id: uuid.UUID member_id: uuid.UUID member_name: str member_email: str subject: str body: str sent_by: Optional[str] created_at: datetime read_at: Optional[datetime] # ── Contract ─────────────────────────────────────────────────────────────────── class ContractResponse(BaseModel): onboarding_data: Optional[Any] member_name: str email: str member_status: str contract_signed_at: Optional[datetime] contract_signer_name: Optional[str] contract_version: Optional[str] activated_at: Optional[datetime] joined_at: datetime model_config = {"from_attributes": True}