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

@@ -52,7 +52,7 @@ def list_quotes(
status_filter: Optional[str] = Query(
default=None,
alias="status",
description="Filter by status (draft, submitted, reviewing, approved, rejected, expired)"
description="Filter by status (draft, submitted, viewed, followed_up, converted, expired)"
),
search: Optional[str] = Query(
default=None,
@@ -166,9 +166,9 @@ def get_stats(
"quotes_by_status": {
"draft": 45,
"submitted": 60,
"reviewing": 15,
"approved": 25,
"rejected": 3,
"viewed": 15,
"followed_up": 10,
"converted": 25,
"expired": 2
},
"total_monthly_value": "12500.00",
@@ -229,7 +229,6 @@ def get_quote(
"company_name": "Acme Corporation",
"contact_name": "John Doe",
"contact_email": "john@acme.com",
"admin_notes": "Follow up scheduled for next week",
"ip_address": "192.168.1.100",
"user_agent": "Mozilla/5.0...",
"items": [...],
@@ -254,19 +253,19 @@ def get_quote(
items_response.append(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
))
activities_response = []
@@ -275,8 +274,8 @@ def get_quote(
id=activity.id,
quote_id=activity.quote_id,
action=activity.action,
description=activity.description,
actor=activity.actor,
step_name=activity.step_name,
details=activity.details,
ip_address=activity.ip_address,
created_at=activity.created_at
))
@@ -290,6 +289,8 @@ def get_quote(
recipient=notification.recipient,
subject=notification.subject,
status=notification.status,
attempts=notification.attempts,
last_attempt_at=notification.last_attempt_at,
sent_at=notification.sent_at,
error_message=notification.error_message,
created_at=notification.created_at
@@ -304,11 +305,8 @@ def get_quote(
contact_email=quote.contact_email,
contact_phone=quote.contact_phone,
employee_count=quote.employee_count,
notes=quote.notes,
admin_notes=quote.admin_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,
ip_address=quote.ip_address,
@@ -346,8 +344,8 @@ def update_quote(
"""
Update a quote's status or admin notes.
Admins can change the quote status (e.g., from submitted to reviewing
or approved) and add internal notes.
Admins can change the quote status (e.g., from submitted to viewed
or converted) and update expiration.
**Example Request:**
```json
@@ -356,8 +354,7 @@ def update_quote(
Content-Type: application/json
{
"status": "reviewing",
"admin_notes": "Assigned to sales team. Follow up scheduled for Monday."
"status": "viewed"
}
```
@@ -365,8 +362,7 @@ def update_quote(
```json
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"status": "reviewing",
"admin_notes": "Assigned to sales team. Follow up scheduled for Monday.",
"status": "viewed",
...
}
```
@@ -382,3 +378,47 @@ def update_quote(
)
return get_quote(quote_id, db, current_user)
@router.post(
"/{quote_id}/sync-syncro",
summary="Sync quote to SyncroRMM",
description="Create or update a lead in SyncroRMM from a submitted quote",
status_code=status.HTTP_200_OK,
responses={
200: {
"description": "Sync result",
"content": {
"application/json": {
"example": {
"synced": True,
"is_existing_customer": False,
"syncro_lead_id": "12345",
"error": None,
}
}
},
},
404: {"description": "Quote not found"},
},
)
async def sync_quote_to_syncro(
quote_id: UUID,
db: Session = Depends(get_db),
current_user: dict = Depends(get_current_user),
):
"""
Manually trigger a SyncroRMM sync for a quote.
Checks for an existing customer in Syncro and creates a lead with
the quote details. The quote must have a contact email to sync.
**Example Request:**
```
POST /api/admin/quotes/123e4567-e89b-12d3-a456-426614174000/sync-syncro
Authorization: Bearer <token>
```
"""
quote = quote_service.get_quote_by_id(db, quote_id)
result = await quote_service.sync_quote_to_syncro(db, quote)
return result