371 lines
11 KiB
Python
371 lines
11 KiB
Python
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}
|