update: Major Overhaul to all subsystems

This commit is contained in:
2026-03-07 11:32:18 +02:00
parent 810e81b323
commit c62188fda6
107 changed files with 20414 additions and 929 deletions

97
.claude/crm-step-12.md Normal file
View 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 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