update: Major Overhaul to all subsystems
This commit is contained in:
97
.claude/crm-step-12.md
Normal file
97
.claude/crm-step-12.md
Normal file
@@ -0,0 +1,97 @@
|
||||
# CRM Step 12 — Integration: FreePBX AMI Call Logging
|
||||
|
||||
## Context
|
||||
Read `.claude/crm-build-plan.md` for full context and IMPORTANT NOTES.
|
||||
Steps 01–11 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
|
||||
Reference in New Issue
Block a user