Files
bellsystems-cp/.claude/crm-step-12.md

98 lines
3.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CRM Step 12 — Integration: FreePBX AMI Call Logging
## Context
Read `.claude/crm-build-plan.md` for full context and IMPORTANT NOTES.
Steps 0111 must be complete.
## Prerequisites (manual setup before this step)
- FreePBX server with AMI (Asterisk Manager Interface) enabled
- An AMI user created in FreePBX: Admin → Asterisk Manager Users
- Username + password (set these in config)
- Permissions needed: read = "call,cdr" (minimum)
- Network access from VPS to FreePBX AMI port (default: 5038)
- Values ready: `AMI_HOST`, `AMI_PORT` (5038), `AMI_USERNAME`, `AMI_PASSWORD`
## Task
Connect to FreePBX AMI over TCP, listen for call events, and auto-log them to crm_comms_log matched against customer phone numbers.
## Backend changes
### 1. Add to `backend/config.py`
```python
ami_host: str = ""
ami_port: int = 5038
ami_username: str = ""
ami_password: str = ""
```
### 2. Create `backend/crm/ami_listener.py`
AMI uses a plain TCP socket with a text protocol (key: value\r\n pairs, events separated by \r\n\r\n).
```python
import asyncio
from config import settings
from mqtt import database as mqtt_db
async def ami_connect_and_listen():
"""
1. Open TCP connection to ami_host:ami_port
2. Read the banner line
3. Send login action:
Action: Login\r\n
Username: {ami_username}\r\n
Secret: {ami_password}\r\n\r\n
4. Read response — check for "Response: Success"
5. Loop reading events. Parse each event block into a dict.
6. Handle Event: Hangup:
- CallerID: the phone number (field: CallerIDNum)
- Duration: call duration seconds (field: Duration, may not always be present)
- Channel direction: inbound if DestChannel starts with "PJSIP/" or "SIP/",
outbound if Channel starts with "PJSIP/" or "SIP/"
- Normalize CallerIDNum: strip leading + and spaces
- Look up customer by normalized phone
- Create crm_comms_log entry: type=call, direction=inbound|outbound,
body=f"Call duration: {duration}s", ext_message_id=Uniqueid field
7. On disconnect: wait 30s, reconnect. Infinite retry loop.
"""
async def start_ami_listener():
"""Entry point — only starts if ami_host is set."""
if not settings.ami_host:
return
asyncio.create_task(ami_connect_and_listen())
```
### 3. Add to `backend/main.py` startup
```python
from crm.ami_listener import start_ami_listener
# in startup():
await start_ami_listener()
```
### 4. Add manual log endpoint to `backend/crm/router.py`
`POST /api/crm/calls/log`
Body: `{ customer_id, direction, duration_seconds, notes, occurred_at }`
Requires auth.
→ create crm_comms_log entry (type=call) manually
→ useful if auto-logging misses a call or for logging calls made outside the office
## Frontend changes
### Update Comms tab in `CustomerDetail.jsx`
- Call entries: amber/yellow color, phone icon
- Show duration if available (parse from body)
- "Log Call" button → quick modal with: direction (inbound/outbound), duration (minutes + seconds), notes, occurred_at
- On save: POST `/api/crm/calls/log`
### Update `InboxPage.jsx`
- Add "Call" to type filter options
- Call entries show customer name, direction arrow, duration
## Notes
- AMI protocol reference: each event/response is a block of `Key: Value` lines terminated by `\r\n\r\n`
- The `Hangup` event fires at end of call and includes Duration in seconds
- CallerIDNum for inbound calls is the caller's number. For outbound it's typically the extension — may need to use `DestCallerIDNum` instead. Test against your FreePBX setup.
- Phone matching uses the same normalization as WhatsApp step (strip `+`, spaces, leading zeros if needed)
- If AMI connection drops (FreePBX restart, network blip), the reconnect loop handles it silently
- This gives you: auto-logged inbound calls matched to customers, duration recorded, plus a manual log option for anything missed