sync: Auto-sync from ACG-M-L5090 at 2026-03-10 19:11:00
Synced files: - Quote wizard frontend (all components, hooks, types, config) - API updates (config, models, routers, schemas, services) - Client work (bg-builders, gurushow) - Scripts (BGB Lesley termination, CIPP, Datto, migration) - Temp files (Bardach contacts, VWP investigation, misc) - Credentials and session logs - Email service, PHP API, session logs Machine: ACG-M-L5090 Timestamp: 2026-03-10 19:11:00 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -7,49 +7,17 @@ public and admin-facing operations.
|
||||
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
from enum import Enum
|
||||
from typing import Optional
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, Field, EmailStr, field_validator
|
||||
|
||||
|
||||
class QuoteStatus(str, Enum):
|
||||
"""Status options for quotes."""
|
||||
DRAFT = "draft"
|
||||
SUBMITTED = "submitted"
|
||||
REVIEWING = "reviewing"
|
||||
APPROVED = "approved"
|
||||
REJECTED = "rejected"
|
||||
EXPIRED = "expired"
|
||||
|
||||
|
||||
class ServiceCategory(str, Enum):
|
||||
"""Service category options for quote items."""
|
||||
MANAGED_SERVICES = "managed_services"
|
||||
SECURITY = "security"
|
||||
BACKUP = "backup"
|
||||
CLOUD = "cloud"
|
||||
HARDWARE = "hardware"
|
||||
SOFTWARE = "software"
|
||||
CONSULTING = "consulting"
|
||||
SUPPORT = "support"
|
||||
|
||||
|
||||
class BillingFrequency(str, Enum):
|
||||
"""Billing frequency options for quote items."""
|
||||
MONTHLY = "monthly"
|
||||
QUARTERLY = "quarterly"
|
||||
ANNUAL = "annual"
|
||||
ONE_TIME = "one_time"
|
||||
|
||||
|
||||
class NotificationType(str, Enum):
|
||||
"""Notification types for quote events."""
|
||||
EMAIL_SENT = "email_sent"
|
||||
SMS_SENT = "sms_sent"
|
||||
ADMIN_ALERT = "admin_alert"
|
||||
REMINDER_SENT = "reminder_sent"
|
||||
from api.models.quote import (
|
||||
QuoteStatus,
|
||||
ServiceCategory,
|
||||
BillingFrequency,
|
||||
NotificationType,
|
||||
)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
@@ -59,21 +27,19 @@ class NotificationType(str, Enum):
|
||||
class QuoteItemBase(BaseModel):
|
||||
"""Base schema with shared QuoteItem fields."""
|
||||
|
||||
service_name: str = Field(..., description="Name of the service", min_length=1, max_length=255)
|
||||
service_description: Optional[str] = Field(None, description="Detailed description of the service")
|
||||
category: ServiceCategory = Field(
|
||||
ServiceCategory.MANAGED_SERVICES,
|
||||
description="Service category"
|
||||
)
|
||||
category: ServiceCategory = Field(..., description="Service category")
|
||||
product_code: str = Field(..., description="Product code identifier", min_length=1, max_length=50)
|
||||
product_name: str = Field(..., description="Name of the product/service", min_length=1, max_length=255)
|
||||
description: Optional[str] = Field(None, description="Detailed description of the product/service")
|
||||
quantity: int = Field(1, description="Number of units", ge=1)
|
||||
unit_price: Decimal = Field(..., description="Price per unit", ge=0)
|
||||
setup_price: Decimal = Field(Decimal("0.00"), description="One-time setup price", ge=0)
|
||||
billing_frequency: BillingFrequency = Field(
|
||||
BillingFrequency.MONTHLY,
|
||||
description="Billing frequency"
|
||||
)
|
||||
unit_price: Decimal = Field(..., description="Price per unit", ge=0)
|
||||
quantity: int = Field(1, description="Number of units", ge=1)
|
||||
setup_fee: Decimal = Field(Decimal("0.00"), description="One-time setup fee", ge=0)
|
||||
is_required: bool = Field(False, description="Whether this item is required")
|
||||
sort_order: int = Field(0, description="Display order within the quote")
|
||||
tier: Optional[str] = Field(None, description="Pricing tier", max_length=50)
|
||||
is_recommended: bool = Field(False, description="Whether this item is recommended")
|
||||
|
||||
|
||||
class QuoteItemCreate(QuoteItemBase):
|
||||
@@ -84,15 +50,16 @@ class QuoteItemCreate(QuoteItemBase):
|
||||
class QuoteItemUpdate(BaseModel):
|
||||
"""Schema for updating an existing QuoteItem. All fields optional."""
|
||||
|
||||
service_name: Optional[str] = Field(None, min_length=1, max_length=255)
|
||||
service_description: Optional[str] = None
|
||||
category: Optional[ServiceCategory] = None
|
||||
billing_frequency: Optional[BillingFrequency] = None
|
||||
unit_price: Optional[Decimal] = Field(None, ge=0)
|
||||
product_code: Optional[str] = Field(None, min_length=1, max_length=50)
|
||||
product_name: Optional[str] = Field(None, min_length=1, max_length=255)
|
||||
description: Optional[str] = None
|
||||
quantity: Optional[int] = Field(None, ge=1)
|
||||
setup_fee: Optional[Decimal] = Field(None, ge=0)
|
||||
is_required: Optional[bool] = None
|
||||
sort_order: Optional[int] = None
|
||||
unit_price: Optional[Decimal] = Field(None, ge=0)
|
||||
setup_price: Optional[Decimal] = Field(None, ge=0)
|
||||
billing_frequency: Optional[BillingFrequency] = None
|
||||
tier: Optional[str] = Field(None, max_length=50)
|
||||
is_recommended: Optional[bool] = None
|
||||
|
||||
|
||||
class QuoteItemResponse(QuoteItemBase):
|
||||
@@ -103,7 +70,6 @@ class QuoteItemResponse(QuoteItemBase):
|
||||
line_total: Decimal = Field(..., description="Calculated line total (unit_price * quantity)")
|
||||
monthly_amount: Decimal = Field(..., description="Calculated monthly amount")
|
||||
created_at: datetime = Field(..., description="Timestamp when item was created")
|
||||
updated_at: datetime = Field(..., description="Timestamp when item was last updated")
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
@@ -120,14 +86,12 @@ class QuoteBase(BaseModel):
|
||||
contact_email: Optional[EmailStr] = Field(None, description="Contact email address")
|
||||
contact_phone: Optional[str] = Field(None, description="Contact phone number", max_length=50)
|
||||
employee_count: Optional[int] = Field(None, description="Number of employees/users", ge=1)
|
||||
notes: Optional[str] = Field(None, description="Customer notes or special requirements")
|
||||
|
||||
|
||||
class QuoteCreate(BaseModel):
|
||||
"""Schema for creating a new Quote draft."""
|
||||
|
||||
employee_count: Optional[int] = Field(None, description="Number of employees/users", ge=1)
|
||||
notes: Optional[str] = Field(None, description="Initial notes")
|
||||
# Items can optionally be provided at creation
|
||||
items: Optional[list[QuoteItemCreate]] = Field(None, description="Initial quote items")
|
||||
|
||||
@@ -140,7 +104,6 @@ class QuoteUpdate(BaseModel):
|
||||
contact_email: Optional[EmailStr] = None
|
||||
contact_phone: Optional[str] = Field(None, max_length=50)
|
||||
employee_count: Optional[int] = Field(None, ge=1)
|
||||
notes: Optional[str] = None
|
||||
# Items to add/update
|
||||
items: Optional[list[QuoteItemCreate]] = Field(None, description="Items to set (replaces existing)")
|
||||
|
||||
@@ -152,7 +115,7 @@ class QuoteSubmit(BaseModel):
|
||||
contact_name: str = Field(..., description="Contact name (required for submission)", min_length=1, max_length=255)
|
||||
contact_email: EmailStr = Field(..., description="Email address (required for submission)")
|
||||
contact_phone: Optional[str] = Field(None, description="Phone number", max_length=50)
|
||||
notes: Optional[str] = Field(None, description="Additional notes")
|
||||
notes: Optional[str] = Field(None, description="Additional notes from the prospect", max_length=2000)
|
||||
|
||||
@field_validator("company_name", "contact_name")
|
||||
@classmethod
|
||||
@@ -169,7 +132,6 @@ class QuoteResponse(QuoteBase):
|
||||
status: QuoteStatus = Field(..., description="Current quote status")
|
||||
monthly_total: Decimal = Field(..., description="Calculated monthly recurring total")
|
||||
setup_total: Decimal = Field(..., description="Calculated one-time setup total")
|
||||
annual_total: Decimal = Field(..., description="Calculated annual total")
|
||||
expires_at: Optional[datetime] = Field(None, description="Quote expiration date")
|
||||
submitted_at: Optional[datetime] = Field(None, description="When quote was submitted")
|
||||
created_at: datetime = Field(..., description="Timestamp when quote was created")
|
||||
@@ -200,8 +162,8 @@ class QuoteActivityResponse(BaseModel):
|
||||
id: UUID = Field(..., description="Unique identifier for the activity")
|
||||
quote_id: UUID = Field(..., description="Reference to the parent quote")
|
||||
action: str = Field(..., description="Action performed")
|
||||
description: Optional[str] = Field(None, description="Detailed description")
|
||||
actor: Optional[str] = Field(None, description="Who performed the action")
|
||||
step_name: Optional[str] = Field(None, description="Wizard step name")
|
||||
details: Optional[str] = Field(None, description="Additional details")
|
||||
ip_address: Optional[str] = Field(None, description="IP address of the actor")
|
||||
created_at: datetime = Field(..., description="Timestamp of the action")
|
||||
|
||||
@@ -221,6 +183,8 @@ class QuoteNotificationResponse(BaseModel):
|
||||
recipient: str = Field(..., description="Notification recipient")
|
||||
subject: Optional[str] = Field(None, description="Notification subject")
|
||||
status: str = Field(..., description="Delivery status")
|
||||
attempts: int = Field(0, description="Number of delivery attempts")
|
||||
last_attempt_at: Optional[datetime] = Field(None, description="Last delivery attempt timestamp")
|
||||
sent_at: Optional[datetime] = Field(None, description="When notification was sent")
|
||||
error_message: Optional[str] = Field(None, description="Error message if failed")
|
||||
created_at: datetime = Field(..., description="Timestamp when created")
|
||||
@@ -236,14 +200,12 @@ class QuoteAdminUpdate(BaseModel):
|
||||
"""Schema for admin updates to a quote."""
|
||||
|
||||
status: Optional[QuoteStatus] = Field(None, description="New status")
|
||||
admin_notes: Optional[str] = Field(None, description="Internal admin notes")
|
||||
expires_at: Optional[datetime] = Field(None, description="Quote expiration date")
|
||||
|
||||
|
||||
class QuoteAdminResponse(QuoteResponse):
|
||||
"""Schema for admin Quote responses with additional fields."""
|
||||
|
||||
admin_notes: Optional[str] = Field(None, description="Internal admin notes")
|
||||
ip_address: Optional[str] = Field(None, description="IP address of the requester")
|
||||
user_agent: Optional[str] = Field(None, description="Browser user agent")
|
||||
activities: list[QuoteActivityResponse] = Field(
|
||||
|
||||
Reference in New Issue
Block a user