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:
2026-03-10 19:59:08 -07:00
parent a1a19f8c00
commit fa15b03180
169 changed files with 879909 additions and 1243 deletions

View File

@@ -78,8 +78,7 @@ def create_quote(
Content-Type: application/json
{
"employee_count": 25,
"notes": "Looking for complete managed services package"
"employee_count": 25
}
```
@@ -159,7 +158,6 @@ def get_quote(
"employee_count": 25,
"monthly_total": "450.00",
"setup_total": "500.00",
"annual_total": "5900.00",
"items": [
{
"id": "456e7890-e89b-12d3-a456-426614174001",
@@ -185,19 +183,19 @@ def get_quote(
item_dict = QuoteItemResponse(
id=item.id,
quote_id=item.quote_id,
service_name=item.service_name,
service_description=item.service_description,
category=item.category,
billing_frequency=item.billing_frequency,
unit_price=item.unit_price,
product_code=item.product_code,
product_name=item.product_name,
description=item.description,
quantity=item.quantity,
setup_fee=item.setup_fee,
is_required=item.is_required,
sort_order=item.sort_order,
unit_price=item.unit_price,
setup_price=item.setup_price,
billing_frequency=item.billing_frequency,
tier=item.tier,
is_recommended=item.is_recommended,
line_total=item.line_total,
monthly_amount=item.monthly_amount,
created_at=item.created_at,
updated_at=item.updated_at
)
items_response.append(item_dict)
@@ -210,10 +208,8 @@ def get_quote(
contact_email=quote.contact_email,
contact_phone=quote.contact_phone,
employee_count=quote.employee_count,
notes=quote.notes,
monthly_total=quote.monthly_total,
setup_total=quote.setup_total,
annual_total=quote.annual_total,
expires_at=quote.expires_at,
submitted_at=quote.submitted_at,
created_at=quote.created_at,
@@ -432,7 +428,7 @@ def remove_item(
},
},
)
def submit_quote(
async def submit_quote_endpoint(
access_token: str,
submit_data: QuoteSubmit,
request: Request,
@@ -442,7 +438,7 @@ def submit_quote(
Submit a quote with contact information.
This finalizes the quote and sends it for review. Contact information
is required at this stage.
is required at this stage. An email notification is sent to the admin.
**Example Request:**
```json
@@ -453,8 +449,7 @@ def submit_quote(
"company_name": "Acme Corporation",
"contact_name": "John Doe",
"contact_email": "john.doe@acme.com",
"contact_phone": "555-123-4567",
"notes": "Please contact me to discuss implementation timeline."
"contact_phone": "555-123-4567"
}
```
@@ -472,15 +467,62 @@ def submit_quote(
}
```
"""
import logging
from api.config import get_settings
from api.services.email_service import send_email, build_quote_notification_html
logger = logging.getLogger(__name__)
ip_address = get_client_ip(request)
quote_service.submit_quote(
quote = quote_service.submit_quote(
db=db,
access_token=access_token,
submit_data=submit_data,
ip_address=ip_address
)
# Send email notification (non-blocking, don't fail the request if email fails)
try:
settings = get_settings()
items_data = [
{
"service_name": item.product_name,
"billing_frequency": item.billing_frequency,
"unit_price": str(item.unit_price),
"quantity": item.quantity,
}
for item in quote.items
]
html = build_quote_notification_html(
company_name=submit_data.company_name,
contact_name=submit_data.contact_name,
contact_email=submit_data.contact_email,
contact_phone=submit_data.contact_phone,
monthly_total=str(quote.monthly_total),
setup_total=str(quote.setup_total),
items=items_data,
notes=submit_data.notes,
)
sent = await send_email(
to_email=settings.ADMIN_NOTIFICATION_EMAIL,
subject=f"New Quote Submission: {submit_data.company_name} - ${quote.monthly_total}/mo",
body_html=html,
)
# Update notification record status
if quote.notifications:
notification = quote.notifications[-1]
notification.status = "sent" if sent else "failed"
if not sent:
notification.error_message = "Graph API send failed"
db.commit()
except Exception as e:
logger.error(f"Failed to send quote notification email: {e}")
# Don't fail the submission - email is best-effort
return get_quote(access_token, db)