75 lines
2.7 KiB
Markdown
75 lines
2.7 KiB
Markdown
# ConnectWise Customer Aggregation Recipe
|
|
|
|
Reference: How to aggregate open tickets by customer and enrich with primary contact details.
|
|
|
|
## Use Case
|
|
- "List customers who have had open tickets in the past N years"
|
|
- "Show me every company with active Help Desk tickets and their primary contact email"
|
|
|
|
## API Traversal
|
|
|
|
```python
|
|
import base64
|
|
import requests
|
|
from datetime import datetime, timedelta
|
|
|
|
# Auth
|
|
creds = {...} # from ~/.secrets/connectwise
|
|
auth_str = f"{company_id}{public_key}:{private_key}"
|
|
encoded_auth = base64.b64encode(auth_str.encode()).decode()
|
|
headers = {
|
|
'Authorization': f'Basic {encoded_auth}',
|
|
'clientId': client_id,
|
|
'Company-Id': company_id,
|
|
'Content-Type': 'application/json'
|
|
}
|
|
|
|
# 1. Fetch tickets
|
|
base_url = 'https://cw.vistanetinc.com/v4_6_release/apis/3.0'
|
|
two_years_ago = (datetime.now() - timedelta(days=730)).strftime('%Y-%m-%dT%H:%M:%SZ')
|
|
conditions = f"board/id=1 and closedFlag=false and dateEntered>'{two_years_ago}'"
|
|
url = f"{base_url}/service/tickets?conditions={conditions}&pageSize=1000"
|
|
tickets = requests.get(url, headers=headers).json()
|
|
|
|
# 2. Aggregate by company
|
|
companies = {}
|
|
for t in tickets:
|
|
status_name = t.get('status', {}).get('name', '')
|
|
if status_name.startswith('>'):
|
|
continue
|
|
comp_id = t.get('company', {}).get('id')
|
|
if not comp_id:
|
|
continue
|
|
companies.setdefault(comp_id, {
|
|
'name': t['company']['name'],
|
|
'ticket_count': 0,
|
|
'latest_date': t['_info']['dateEntered']
|
|
})
|
|
companies[comp_id]['ticket_count'] += 1
|
|
if t['_info']['dateEntered'] > companies[comp_id]['latest_date']:
|
|
companies[comp_id]['latest_date'] = t['_info']['dateEntered']
|
|
|
|
# 3. Enrich with primary contact email
|
|
for comp_id, data in companies.items():
|
|
comp = requests.get(f"{base_url}/company/companies/{comp_id}", headers=headers).json()
|
|
contact_id = comp.get('defaultContact', {}).get('id')
|
|
data['contact_name'] = comp.get('defaultContact', {}).get('name', 'N/A')
|
|
data['email'] = comp.get('invoiceToEmailAddress', 'N/A')
|
|
|
|
if contact_id:
|
|
contact = requests.get(f"{base_url}/company/contacts/{contact_id}", headers=headers).json()
|
|
for item in contact.get('communicationItems', []):
|
|
if item.get('type', {}).get('name') == 'Email' and item.get('defaultFlag'):
|
|
data['email'] = item['value']
|
|
break
|
|
```
|
|
|
|
## Board IDs
|
|
- VistaNet Help Desk = 1
|
|
- VistaNet Management = 25
|
|
- Managed Service Alerts = 6
|
|
|
|
## Known Gaps
|
|
- Some companies (e.g., BJ LaRossa, Far West Rice Inc.) have no default contact email in CW. Display `N/A` gracefully.
|
|
- `invoiceToEmailAddress` on the company record is often billing/AP, not the operational contact.
|