50 KiB
افزونه اتصال به استریسک و ایزابل — سند اجرایی کامل
کد افزونه: asterisk_issabel_connector
نام نمایشی: اتصال به استریسک و ایزابل
دسته مارکتپلیس: integration
وضعیت سند: در حال پیادهسازی فعال
نسخه سند: 1.1.0
تاریخ: ۱۴۰۵/۰۵/۱۲ (2026-08-03)
بهروزرسانی پیادهسازی: فاز ۰ و ۱ کامل شده؛ فازهای ۲ (ضبط/گزارش/CRM عمیق)، ۳ (داشبورد زنده/کنترل تماس)، ۴ (DLQ/متریک) و اسکفولد فاز ۵ (Softphone) در کد پیاده شدهاند. هسته Softphone Media Relay (فاز ۵/سند جدا) از ۱۴۰۵/۰۵/۱۵ در حال پیادهسازی است — جزئیات و چکلیست:
TELEPHONY_SOFTPHONE_MEDIA_RELAY_EXECUTION_SCENARIO.md.
این سند مرجع واحد برای پیادهسازی افزونه تلفنی حسابیکس است. هر فاز باید فقط بر اساس همین سند و چکلیست پذیرش همان فاز انجام شود.
۰. خلاصه اجرایی
حسابیکس امروز CRM، اشخاص، مانده حساب، فعالیت نوع call، اعلان WebSocket، مارکتپلیس افزونه و چندسکویی Flutter (وب / اندروید / iOS / دسکتاپ) دارد، اما هیچ لایه CTI/Asterisk ندارد.
افزونه باید:
- هر کسبوکار را به یک یا چند PBX (Issabel/Asterisk) وصل کند.
- تماس ورودی را با Screen Pop به اپراتور نشان دهد.
- Click-to-Call از داخل CRM انجام دهد.
- تاریخچه کامل تماس + ضبط را ذخیره کند.
- با CRM، اشخاص، مانده حساب، چک، فاکتور و Workflow یکپارچه شود.
- داخلی هر کاربر را بهازای هر کسبوکار جدا نگه دارد.
- روی وب، موبایل و دسکتاپ UI یکپارچه و باکیفیت داشته باشد.
تصمیمهای قفلشده این سند (پیشفرض محصولی):
| موضوع | تصمیم |
|---|---|
| تماس از CRM در MVP | Click-to-Call با AMI Originate (نه Softphone) |
| Softphone WebRTC | فقط فاز ۵ (اختیاری) |
| نصب روی ایزابل | الزامی — Hesabix Telephony Connector |
| تاریخچه تماس | جدول first-class telephony_calls + Activity اختیاری |
| تیکت از تماس | در MVP = وظیفه/فعالیت CRM؛ ماژول تیکت کسبوکار خارج از scope |
| ضبط مکالمه | لینک/پروکسی از PBX + آپلود اختیاری به file_storage |
| پلتفرم UI | یک Flutter codebase — Responsive + Adaptive برای Web/Mobile/Desktop |
۱. هدف، محدوده و خارج از محدوده
۱.۱ هدف
ارائه یک افزونه مارکتپلیس که کسبوکار بتواند مرکز تلفن Issabel/Asterisk خود را به حسابیکس وصل کند و اپراتورها بتوانند تماس را ببینند، بگیرند، ثبت کنند و با پرونده مشتری ادامه دهند — روی وب، موبایل و دسکتاپ.
۱.۲ داخل محدوده (In Scope)
- لایسنس افزونه، تنظیمات PBX، نگاشت کاربر↔داخلی
- Connector سمت Issabel (AMI + webhook)
- Screen Pop، Click-to-Call، تاریخچه، ضبط، اعلانها
- یکپارچگی با Person / Lead / Deal / CrmActivity / مانده حساب / چک / فاکتور
- گزارشها و داشبورد لحظهای (فازهای بعدی)
- کنترلهای پیشرفته Hold/Transfer/Hangup (فاز ۳+)
- UI کامل چندسکویی
۱.۳ خارج از محدوده (Out of Scope)
- جایگزینی کامل Issabel (IVR builder، مدیریت کامل dialplan)
- ماژول تیکت پشتیبانی کسبوکار جدید (تا وقتی جداگانه تعریف نشود)
- تماس ویدیویی / تماس AI voice موجود حسابیکس
- یکپارچگی با PBXهای غیر Asterisk-family (مثل ۳CX، FreePBX جدا — مگر سازگاری AMI حفظ شود)
- ضبط مکالمه قانونی/قضاوت حقوقی (مسئولیت پیکربندی با مشتری است)
۱.۴ فرضیات عملیاتی ایران
- Issabel معمولاً پشت NAT/فایروال است → Connector خروجی HTTPS میزند؛ پورت ورودی روی PBX لازم نیست.
- Caller ID با فرمتهای
09…/9…/98…/+98…/ شهری میآید → نرمالسازی اجباری. - اپراتور معمولاً Softphone یا تلفن رومیزی SIP دارد → Originate کافی است.
- کیفیت شبکه شعب متغیر است → UI باید offline-tolerant برای تاریخچه محلی نباشد، ولی وضعیت اتصال Connector را شفاف نشان دهد.
۲. نقشها و سناریوهای کلیدی
۲.۱ نقشها
| نقش | توضیح |
|---|---|
| مالک کسبوکار | خرید افزونه، اتصال PBX، تعریف دسترسی |
| مدیر تلفن | نگاشت داخلیها، صفها، تنظیمات شناسایی شماره |
| اپراتور فروش/پشتیبانی | دریافت Pop، تماس خروجی، ثبت نتیجه |
| ناظر/سرپرست | داشبورد زنده، گزارش عملکرد، گوش دادن به ضبط (با مجوز) |
۲.۲ سناریوهای پذیرش محصول (Must-pass)
- ورودی شناختهشده: زنگ → Pop با نام + مانده → پاسخ → ثبت خودکار → یادداشت پس از تماس.
- ورودی ناشناس: Pop ناشناس → ایجاد Lead یا Person → ثبت تماس روی همان موجودیت.
- خروجی Click-to-Call: از پروفایل مشتری → Originate → وضعیت زنده → ثبت خروجی.
- از دسترفته: Missed → اعلان + ردیف تاریخچه + (اختیاری) وظیفه پیگیری.
- چند کسبوکار: یک User با دو داخلی در دو Business — با تعویض کسبوکار، context تلفن عوض شود.
- قطع Connector: UI وضعیت «قطع از مرکز تلفن» نشان دهد؛ تماسها در صف Connector بعداً sync شوند.
۳. معماری سیستم
۳.۱ نمای کلی
┌─────────────────────────────────────────────────────────────────┐
│ Flutter Client (Web/Mobile/Desktop) │
│ Phone Bar · Screen Pop · Dialer · History · Live Dashboard │
└───────────────────────────────▲─────────────────────────────────┘
│ REST + WebSocket (/ws/notifications
│ + /ws/telephony اختیاری)
┌───────────────────────────────┴─────────────────────────────────┐
│ Hesabix API (FastAPI) │
│ telephony_* routers · plugin gate · phone normalizer │
│ CRM / Person / Balance / Check / FileStorage / Workflow hooks │
└───────────────────────────────▲─────────────────────────────────┘
│ HTTPS Webhook + Command API
┌───────────────────────────────┴─────────────────────────────────┐
│ Hesabix Telephony Connector (on Issabel) │
│ AMI listener · event mapper · CDR poll · recording index │
│ command executor (Originate/Hangup/Transfer/Hold) │
└───────────────────────────────▲─────────────────────────────────┘
│ AMI / (ARI later)
┌───────────────────────────────┴─────────────────────────────────┐
│ Issabel / Asterisk PBX │
│ Extensions · Queues · CDR · MixMonitor recordings │
└─────────────────────────────────────────────────────────────────┘
۳.۲ اصول معماری
- Tenant boundary =
business_idهمیشه. - Connector هویت کسبوکار دارد (
connector_tokenper PBX). - Idempotency: هر رویداد Asterisk با
uniqueid/linkedidیکبار پردازش شود. - Source of truth تماس =
telephony_calls؛ CRM Activity مشتق/اختیاری است. - Realtime فقط به کاربران مجاز همان کسبوکار و داخلی مرتبط فرستاده شود.
- UI هیچ dialplanی را فرض نکند؛ فقط وضعیتهای نرمالشده را نشان دهد.
۳.۳ الگوی همراستا با افزونههای موجود
| الگو | مرجع موجود | استفاده در این افزونه |
|---|---|---|
| مارکتپلیس + لایسنس | payroll, basalam_connector |
asterisk_issabel_connector |
| Gate API | *_plugin_dependency.py |
telephony_plugin_dependency.py |
| Gate Flutter | payroll_plugin_gate.dart |
telephony_plugin_gate.dart |
| تنظیمات کسبوکار | WooCommerce / Basalam settings | صفحه تنظیمات تلفن |
| اتصال خارجی | Basalam webhook + bridge | Connector webhook |
| فایل | FileStorageService + Repair attachments |
ضبط مکالمه |
| Realtime | realtime_manager |
Screen Pop / وضعیت تماس |
| CRM | CrmActivity, Lead convert |
پیگیری پس از تماس |
۴. مدل داده (ERD اجرایی)
همه جداول با prefix telephony_ و business_id (جز جایی که صریحاً گفته شود).
۴.۱ telephony_pbx_connections
اتصال یک مرکز تلفن به یک کسبوکار.
| فیلد | نوع | توضیح |
|---|---|---|
| id | PK | |
| business_id | FK businesses | |
| name | string | مثلاً «مرکز تهران» |
| pbx_type | enum | issabel | asterisk | freepbx_compat |
| host_hint | string nullable | فقط نمایشی/دیباگ — اتصال از سمت Connector است |
| connector_token_hash | string | هش توکن Connector |
| connector_version | string nullable | |
| last_seen_at | datetime nullable | heartbeat |
| status | enum | pending | online | offline | error |
| last_error | text nullable | |
| settings | JSON | timezone، context پیشفرض Originate، مسیر ضبط، و … |
| is_active | bool | |
| created_at / updated_at | datetime |
Constraint: چند PBX per business مجاز است.
۴.۲ telephony_extensions
کاتالوگ داخلیها (sync یا دستی).
| فیلد | نوع | توضیح |
|---|---|---|
| id | PK | |
| business_id | FK | |
| pbx_id | FK telephony_pbx_connections | |
| extension | string | مثلاً 102 |
| display_name | string nullable | |
| queue_codes | JSON nullable | صفهای مرتبط |
| is_active | bool | |
| presence_status | enum | unknown | idle | ringing | busy | unavailable |
| presence_updated_at | datetime nullable |
Unique: (pbx_id, extension)
۴.۳ telephony_user_extensions
نگاشت کاربر حسابیکس ↔ داخلی در یک کسبوکار.
| فیلد | نوع | توضیح |
|---|---|---|
| id | PK | |
| business_id | FK | |
| user_id | FK users | |
| pbx_id | FK | |
| extension_id | FK telephony_extensions | |
| is_primary | bool | داخلی پیشفرض Click-to-Call |
| receive_screen_pop | bool | |
| can_click_to_call | bool | |
| caller_id_override | string nullable |
Unique پیشنهادی: (business_id, user_id, extension_id)
قانون: هر کاربر در هر کسبوکار حداقل یک is_primary اگر داخلی دارد.
۴.۴ telephony_queues
| فیلد | نوع |
|---|---|
| id, business_id, pbx_id | |
| queue_code, name | |
| is_active | |
| live_waiting_count, live_talking_count | cached optional |
| extra_info | JSON |
۴.۵ telephony_calls (هسته)
| فیلد | نوع | توضیح |
|---|---|---|
| id | PK | |
| business_id | FK | |
| pbx_id | FK | |
| asterisk_uniqueid | string | idempotency |
| asterisk_linkedid | string nullable | |
| direction | enum | inbound | outbound | internal |
| status | enum | ringing | answered | missed | busy | failed | completed | cancelled |
| from_number_raw | string | |
| from_number_normalized | string indexed | |
| to_number_raw | string | |
| to_number_normalized | string indexed | |
| extension | string nullable | داخلی درگیر |
| queue_code | string nullable | |
| transferred_from | string nullable | |
| transferred_to | string nullable | |
| started_at | datetime | |
| answered_at | datetime nullable | |
| ended_at | datetime nullable | |
| duration_sec | int nullable | کل |
| talk_sec | int nullable | از answer تا end |
| hangup_cause | string nullable | |
| recording_status | enum | none | pending | available | failed |
| recording_file_storage_id | FK nullable | |
| recording_remote_path | string nullable | مسیر روی PBX |
| recording_url | string nullable | URL امن پروکسی |
| person_id | FK nullable | |
| lead_id | FK nullable | |
| deal_id | FK nullable | |
| document_id | FK nullable | فاکتور/سند |
| crm_activity_id | FK nullable | |
| assigned_user_id | FK nullable | اپراتور |
| category | enum/string nullable | sales | support | finance | other | custom |
| outcome | string nullable | نتیجه انسانی |
| note | text nullable | |
| match_method | enum nullable | auto | manual | created_person | created_lead |
| is_anonymous | bool | |
| extra_info | JSON | |
| created_at / updated_at |
Indexes ضروری:
(business_id, started_at DESC)(business_id, from_number_normalized)(business_id, to_number_normalized)(business_id, assigned_user_id, started_at)UNIQUE(pbx_id, asterisk_uniqueid)
۴.۶ telephony_call_events
لاگ ریز رویدادها برای دیباگ و state machine.
| فیلد | نوع |
|---|---|
| id, call_id, business_id | |
| event_type | ringing, answer, hangup, transfer, hold, unhold, … |
| payload | JSON |
| occurred_at | datetime |
۴.۷ telephony_settings
یک ردیف per business (مثل payroll_settings).
| فیلد | توضیح |
|---|---|
| business_id | unique |
| primary_phone_fields | JSON مثلاً ["mobile","mobile_2","mobile_3","phone"] |
| number_normalization_rules | JSON |
| screen_pop_enabled | bool |
| auto_create_activity | bool |
| post_call_form_required | bool |
| default_category | string nullable |
| missed_call_create_task | bool |
| missed_call_task_due_minutes | int |
| recording_access_mode | owner_and_managers | permission_based |
| multi_pbx_enabled | bool |
| ui_preferences | JSON |
| extra_settings | JSON |
۴.۸ جداول کمکی
| جدول | نقش |
|---|---|
telephony_connector_heartbeats |
اختیاری؛ یا در connection ذخیره شود |
telephony_command_logs |
Originate/Hangup/Transfer برای audit |
telephony_number_aliases |
نگاشت دستی شمارههای خاص به Person |
۴.۹ تغییرات حداقلی روی موجودیتهای فعلی
| موجودیت | تغییر |
|---|---|
CrmActivity |
افزودن telephony_call_id nullable + برگرداندن extra_info در API در صورت نیاز |
Lead |
اختیاری فاز ۲: فیلد phone (ثابت) — در فاز ۱ فقط mobile کافی است |
| Permissions JSON | بخش جدید telephony |
| Marketplace seed | افزونه جدید |
| Workflow triggers | telephony.* |
Customer 360: در فاز ۱ تغییر اجباری ندارد؛ Screen Pop از endpoint ترکیبی telephony استفاده میکند که 360 + balance + checks را aggregate میکند.
۵. نرمالسازی و تطبیق شماره
۵.۱ الگوریتم پیشنهادی normalize_iran_phone(raw) -> candidates[]
- حذف فاصله،
-،()، حروف فارسی ارقام → لاتین. - اگر با
+شروع شد نگه دار؛ در غیر این صورت ارقام را استخراج کن. - قواعد:
0098…→98…+98…→98…98+ 10 رقم موبایل → همچنین فرم محلی0+ 10 رقم0+ 10 رقم موبایل → همچنین98+ 10 رقم- شمارههای شهری: حفظ کد شهر؛ تولید فرمهای با/بدون
0
- خروجی: لیست کاندیدا برای OR-match + فرم canonical ذخیرهشده (
from_number_normalized).
۵.۲ ترتیب جستجو
telephony_number_aliases- Person:
mobile,mobile_2,mobile_3,phoneبا کاندیداها - Lead:
mobile(+phoneاگر اضافه شد) - اگر چند match → Pop با انتخاب دستی «کدام پرونده؟»
- اگر صفر → ناشناس
۵.۳ تنظیمات کسبوکار
- فیلدهای قابل جستجو قابل انتخاب در تنظیمات
- حداقل طول رقم برای match (پیشفرض ۸)
۶. پروتکل Connector ↔ Hesabix
۶.۱ احراز هویت
- هنگام ساخت PBX در UI: توکن یکبارمصرف نمایش داده میشود؛ فقط hash ذخیره میشود.
- Header:
Authorization: Bearer <connector_token> - همچنین
X-Hesabix-Business-IdوX-Hesabix-Pbx-Idبرای دفاع در عمق
۶.۲ Heartbeat
POST /api/v1/telephony/connector/heartbeat
{
"connector_version": "1.2.0",
"asterisk_version": "18.x",
"extensions_count": 24,
"status": "ok"
}
هر ۶۰ ثانیه. اگر > ۳ دقیقه نبض نیاید → offline.
۶.۳ رویداد تماس (Webhook)
POST /api/v1/telephony/connector/events
رویدادهای نرمالشده (نه raw AMI مستقیم به کلاینت):
{
"event_id": "uuid",
"pbx_event_at": "2026-08-03T07:15:01Z",
"type": "call.ringing",
"uniqueid": "1722666901.42",
"linkedid": "1722666901.42",
"direction": "inbound",
"from": "09121234567",
"to": "02191000000",
"extension": "102",
"queue": "sales",
"channel": "SIP/102-0000001a",
"extra": {}
}
انواع الزامی فاز ۱:
call.ringingcall.answeredcall.endedcall.missedcall.busycall.failedextension.presence(فاز ۲/۳)recording.ready(فاز ۲)
پردازش باید idempotent بر اساس (pbx_id, event_id) یا (pbx_id, uniqueid, type).
۶.۴ دستورات از Hesabix به Connector
Connector یا:
- Pull:
GET /api/v1/telephony/connector/commands/poll
یا - Push کانال معکوس: WebSocket Connector (ترجیحی فاز ۲)
دستور Originate فاز ۱:
{
"command_id": "uuid",
"type": "originate",
"extension": "102",
"destination": "09121234567",
"caller_id": "02191000000",
"timeout_ms": 30000,
"context": "from-internal"
}
پاسخ:
{ "command_id": "uuid", "accepted": true, "asterisk_action_id": "..." }
دستورات فاز ۳: hangup, transfer, hold, resume.
۶.۵ امنیت Connector
- فقط HTTPS outbound از PBX
- توکن قابل rotate از UI
- Rate limit روی endpoints کانکتور
- عدم پذیرش raw AMI از اینترنت به Hesabix
۶.۶ پکیج نصب Connector
مسیر پیشنهادی ریپو:
extraScripts/HesabixTelephonyConnector/
شامل:
- سرویس systemd
- فایل config (
hesabix-url,token,ami-host,ami-user,ami-secret) - اسکریپت نصب Issabel (CentOS/Rocky متداول)
- README فارسی نصب
- قابلیت dry-run تست AMI
۷. API حسابیکس (قرارداد اجرایی)
پایه: /api/v1/telephony/...
همه endpointهای کاربری با:
require_business_accessrequire_telephony_plugin_activerequire_business_permission_dep("telephony", …)
۷.۱ تنظیمات و PBX
| Method | Path | Perm | توضیح |
|---|---|---|---|
| GET | /settings |
view | |
| PUT | /settings |
manage | |
| GET/POST | /pbx |
view/manage | |
| POST | /pbx/{id}/rotate-token |
manage | |
| GET | /pbx/{id}/status |
view | |
| POST | /pbx/{id}/test |
manage | درخواست تست به Connector |
۷.۲ داخلی و نگاشت کاربران
| Method | Path | Perm |
|---|---|---|
| GET/POST/PATCH | /extensions |
view/manage |
| POST | /extensions/sync |
manage |
| GET/PUT | /user-extensions |
manage |
| GET | /me/extension-context |
view |
۷.۳ تماسها
| Method | Path | Perm |
|---|---|---|
| GET | /calls |
view |
| GET | /calls/{id} |
view |
| PATCH | /calls/{id} |
view+write-ish |
| POST | /calls/{id}/link-person |
view |
| POST | /calls/{id}/create-person |
view |
| POST | /calls/{id}/create-lead |
view |
| POST | /calls/{id}/create-activity |
view |
| POST | /calls/{id}/create-task |
view |
| GET | /calls/{id}/recording |
listen_recordings |
| POST | /click-to-call |
click_to_call |
| GET | /calls/{id}/screen-pop-context |
view |
۷.۴ زنده و گزارش
| Method | Path | Phase | Perm |
|---|---|---|---|
| GET | /live/snapshot |
۳ | live_monitor |
| GET | /reports/summary |
۲ | reports |
| GET | /reports/operators |
۲ | reports |
| GET | /reports/by-person |
۲ | reports |
۷.۵ Connector (بدون session کاربر — با token)
| Method | Path |
|---|---|
| POST | /connector/heartbeat |
| POST | /connector/events |
| GET | /connector/commands/poll |
| POST | /connector/commands/{id}/ack |
| POST | /connector/recordings/meta |
۷.۶ WebSocket
گزینه A (فاز ۱): استفاده از /ws/notifications با type: telephony.*
گزینه B (فاز ۲): /ws/telephony اختصاصی برای ترافیک پرتکرار وضعیت تماس
Payload نمونه Screen Pop:
{
"type": "telephony.incoming_call",
"business_id": 12,
"call_id": 987,
"extension": "102",
"from_number": "09121234567",
"person": {"id": 55, "name": "علی احمدی"},
"balance": {"amount": 2450000, "status": "debtor"},
"deep_link": "/business/12/telephony/calls/987"
}
۸. مجوزها
{
"telephony": {
"view": true,
"manage": true,
"click_to_call": true,
"listen_recordings": true,
"live_monitor": true,
"reports": true,
"control_calls": true
}
}
| اکشن | معنی |
|---|---|
| view | تاریخچه، Pop، جزئیات تماس |
| manage | PBX، داخلی، تنظیمات، نگاشت کاربران |
| click_to_call | Originate |
| listen_recordings | پخش/دانلود ضبط |
| live_monitor | داشبورد زنده / BLF |
| reports | گزارشها |
| control_calls | Hangup/Transfer/Hold |
مالک کسبوکار و superadmin طبق الگوی فعلی bypass دارند.
۹. یکپارچگی با حسابیکس
۹.۱ CRM
- ساخت/بهروزرسانی
CrmActivity(activity_type=call)وقتی تنظیمauto_create_activityروشن است. - اتصال
lead_id/deal_id/person_id. - Post-call → ایجاد Task با
due_at. - تبدیل ناشناس به Lead با
source_code=telephony(seed فرایند در صورت نیاز).
۹.۲ مالی و مشتری
Screen Pop Context باید aggregate کند:
- خلاصه Person
calculate_person_balance(+ چند ارزی اگر لازم)- آخرین N فاکتور (
Document) - چکهای سررسید/معوق از مدل
check - آخرین فعالیتها / یادداشتها
- معاملات باز
۹.۳ Workflow triggers (فاز ۲+)
telephony.call.ringingtelephony.call.missedtelephony.call.completedtelephony.call.anonymous
Actions پیشنهادی: ایجاد وظیفه، ارسال اعلان، ایجاد Lead.
۹.۴ اعلانها
از InAppProvider / realtime_manager:
- تماس ورودی
- از دسترفته
- پایان تماس (درخواست ثبت نتیجه)
- قطع اتصال Connector (برای مدیران)
۱۰. طراحی رابط کاربری (Web / Mobile / Desktop)
۱۰.۱ اصول طراحی محصول
این افزونه UI «مرکز تماس مدرن داخل ERP» است، نه یک داشبورد شلوغ.
اصول سخت:
- یک نوار تلفن همیشگی در shell کسبوکار وقتی افزونه فعال است — هویت بصری ثابت روی هر سه پلتفرم.
- Screen Pop غیرمسدودکننده — کار فعلی کاربر را کامل قطع نکند؛ روی دسکتاپ/وب پنل کناری، روی موبایل bottom sheet هوشمند.
- Brand/Feature presence: کلمه «تماس» یا وضعیت داخلی باید در viewport اول صفحات تلفن واضح باشد؛ اما در بقیه CRM فقط بهصورت Phone Bar ظریف ظاهر شود.
- بدون کارتهای تزئینی اضافه در Pop؛ اطلاعات سلسلهمراتبی و خوانا.
- حرکت هدفمند: ۲–۳ motion اصلی:
- ظاهر شدن Pop با slide + fade
- pulse ملایم وضعیت «زنگ میخورد»
- انتقال وضعیت تماس در Phone Bar (idle → ringing → in-call)
- تایپوگرافی: همخوان با سیستم فونت حسابیکس (ایرانیکان/فونت فعلی اپ) — از Inter/Roboto/Arial استفاده نشود اگر اپ فونت برند دارد.
- رنگ: متغیرهای اختصاصی telephony در تم موجود؛ از تم بنفش پیشفرض AI و پسزمینه کرم کلیشهای پرهیز شود. پیشنهاد:
- Idle: سبز ملایم وضعیت
- Ringing: کهربایی/نارنجی هشدار
- In-call: آبی عملیاتی حسابیکس (یا primary موجود)
- Missed: قرمز سیستمی موجود
- Dense on desktop, focused on mobile: دسکتاپ اطلاعات بیشتر؛ موبایل فقط تصمیمهای فوری.
۱۰.۲ توکنهای UI پیشنهادی
--tel-bar-height-desktop: 48px;
--tel-bar-height-mobile: 44px;
--tel-pop-width-desktop: 420px;
--tel-ring-pulse: 1.2s;
--tel-status-idle: var(--success);
--tel-status-ringing: #D97706;
--tel-status-busy: var(--primary);
--tel-status-offline: var(--muted);
--tel-status-missed: var(--danger);
۱۰.۳ نقاط ورود UI در اپ
| نقطه | مسیر پیشنهادی |
|---|---|
| منوی کسبوکار | «مرکز تماس» (فقط اگر لایسنس فعال) |
| زیرمسیرها | /business/:id/telephony/... |
| تنظیمات | /business/:id/settings/telephony |
| Phone Bar | داخل BusinessShell |
| آیکون تماس کنار شماره | Person، Lead، 360، لیستها |
۱۰.۴ Phone Bar (همه پلتفرمها)
محتوا:
- وضعیت داخلی primary: نقطه رنگی + متن کوتاه (
آزاد/زنگ/مکالمه/آفلاین) - شماره داخلی
- دکمه شمارهگیر
- Badge از دسترفته امروز
- دکمه ورود به داشبورد/تاریخچه
Desktop/Web (≥1100px): نوار افقی زیر هدر یا بالای محتوا.
Tablet: همان نوار فشردهتر.
Mobile: نوار پایینتر از AppBar یا چسبیده بالای bottom navigation؛ در حالت مکالمه به In-Call Mini Bar تبدیل میشود.
۱۰.۵ Screen Pop
Desktop / Web وسیع
پنل end-side (در RTL: چپ) عرض ~420px روی محتوا، با backdrop خیلی ملایم یا بدون قفل کامل.
ساختار:
- هدر تماس: جهت + وضعیت + تایمر
- هویت: نام / شرکت / شماره (بزرگ و خوانا)
- سیگنال مالی یک خط: مانده + وضعیت بدهی
- Tabs فشرده: خلاصه | فاکتورها | چکها | سوابق تماس | یادداشت
- اکشنهای ثابت پایین: ایجاد Lead / اتصال به شخص / ثبت یادداشت / (در فاز ۳ کنترل تماس)
Mobile
- هنگام زنگ: high-priority bottom sheet تا 70% ارتفاع، دستگیره drag
- یک CTA اصلی واضح نیست اگر پاسخ روی گوشی سختافزاری است؛ بهجای آن «باز کردن پرونده» و «ایجاد سرنخ»
- اطلاعات مالی فقط ۱ خط؛ جزئیات در تب
Desktop app (Windows)
مثل Web وسیع + پشتیبانی میانبر صفحهکلید:
Ctrl/Cmd+Shift+DشمارهگیرEscبستن Pop (اگر در حالت ringing اجباری نباشد)
۱۰.۶ شمارهگیر (Dialer)
- جستجوی شماره هنگام تایپ (Person/Lead)
- لیست اخیر
- پد عددی بزرگ روی موبایل؛ روی دسکتاپ پد + فیلد متنی
- انتخاب داخلی مبدأ اگر کاربر چند داخلی دارد
۱۰.۷ پنل مکالمه فعال (In-Call)
- تایمر بزرگ
- نام طرف مقابل
- اکشنها بر اساس فاز: فاز ۱ فقط یادداشت سریع؛ فاز ۳ Hold/Transfer/Hangup
- پس از پایان: Post-Call Sheet اجباری/اختیاری طبق تنظیمات:
- نتیجه
- دستهبندی
- یادداشت
- ایجاد وظیفه
- اتصال به Deal/فاکتور
۱۰.۸ صفحات اصلی
الف) تاریخچه تماسها
- فیلتر چیپی: ورودی/خروجی/از دسترفته/امروز/من
- جدول در دسکتاپ؛ لیست کارتمانند تعاملمحور در موبایل (کارت فقط چون container تعامل لیست است)
- ردیف: جهت، طرف، داخلی، مدت، نتیجه، دکمه پخش ضبط
- جزئیات تماس: timeline رویدادها + پخشکننده صوت
ب) تنظیمات تلفن
بخشها:
- وضعیت اتصال PBX + نصب Connector (لینک راهنما + کپی توکن)
- قوانین شماره
- نگاشت کاربران به داخلی (جدول دسکتاپ / لیست موبایل)
- رفتار Pop و Post-call
- دسترسی ضبط
ج) داشبورد لحظهای (فاز ۳)
ترکیب یک ترکیب واحد، نه دیوار کارت:
- نوار بالا: فعال / انتظار / از دسترفته امروز / میانگین پاسخ
- ناحیه اصلی: ماتریس وضعیت داخلیها (BLF)
- نوار کناری: صفها
روی موبایل: فقط KPIهای ضروری + لیست داخلیهای تیم کاربر.
د) گزارشها (فاز ۲)
چارتهای ساده + جدول؛ فیلتر بازه، داخلی، اپراتور، مشتری.
۱۰.۹ Click-to-Call در سطح سیستم
کامپوننت مشترک:
TelephonyPhoneLink(number, personId?, leadId?)
نمایش:
- متن شماره قابل کپی
- آیکون تماس (فقط اگر
click_to_call+ افزونه فعال + داخلی mapped)
روی موبایل: تأیید کوتاه قبل از Originate (جلوگیری از لمس تصادفی).
۱۰.۱۰ حالتهای خالی و خطا (الزامی UI)
| حالت | پیام/رفتار |
|---|---|
| افزونه غیرفعال | TelephonyPluginGate → مارکتپلیس |
| Connector offline | Banner نارنجی در تنظیمات و Phone Bar |
| کاربر بدون داخلی | Phone Bar: «داخلی تعریف نشده» + لینک به مدیر |
| شماره نامعتبر | Dialer خطا با راهنمای فرمت |
| چند match | Pop انتخاب پرونده |
| بدون مجوز ضبط | پلیر مخفی؛ متن جایگزین |
۱۰.۱۱ دسترسپذیری و i18n
- FA پیشفرض؛ EN برای کلیدها از الگوی i18n موجود
- کنتراست وضعیتها فقط متکی به رنگ نباشد (آیکون + متن)
- تایمر و اعداد با ارقام سازگار با locale اپ
- پخش صوت با کنترل صفحه کلید در وب/دسکتاپ
۱۰.۱۲ پرفورمنس UI
- Pop data از endpoint aggregate؛ نه ۱۰ درخواست جدا در UI
- debounce جستجوی Dialer 250–300ms
- WS events فقط برای business فعال در
AuthStore - با تعویض business: unsubscribe ذهنی + clear state تلفن
۱۱. ساختار کد پیشنهادی
۱۱.۱ Backend
hesabixAPI/
adapters/db/models/telephony.py
adapters/db/repositories/telephony_*.py
adapters/api/v1/telephony/
__init__.py
settings.py
pbx.py
extensions.py
calls.py
click_to_call.py
reports.py
live.py
connector.py
app/core/telephony_plugin_dependency.py
app/services/telephony/
phone_normalizer.py
call_state_machine.py
caller_id_matcher.py
screen_pop_service.py
originate_service.py
recording_service.py
connector_command_service.py
reports_service.py
realtime_publisher.py
migrations/versions/YYYYMMDD_telephony_*.py
scripts/add_telephony_plugin.py
adapters/db/seed_data/marketplace_plugins_seed.py # اضافه کردن افزونه
۱۱.۲ Frontend
hesabixUI/hesabix_ui/lib/
pages/business/telephony/
telephony_shell_bar.dart
telephony_dialer_sheet.dart
telephony_screen_pop.dart
telephony_in_call_panel.dart
telephony_post_call_sheet.dart
telephony_calls_page.dart
telephony_call_detail_page.dart
telephony_settings_page.dart
telephony_live_dashboard_page.dart
telephony_reports_page.dart
widgets/telephony/
telephony_plugin_gate.dart
telephony_status_dot.dart
telephony_phone_link.dart
telephony_recording_player.dart
services/telephony/
telephony_api.dart
telephony_realtime_controller.dart
telephony_session_controller.dart
۱۱.۳ Connector
extraScripts/HesabixTelephonyConnector/
README.md
install.sh
config.example.env
app/
main.py
ami_client.py
event_mapper.py
hesabix_client.py
commands.py
recordings.py
systemd/hesabix-telephony-connector.service
۱۲. فازهای اجرایی
هر فاز خروجی قابل دمو و چکلیست پذیرش دارد. تا پذیرش فاز n، فاز n+1 شروع نشود مگر کارهای زیرساختی موازی بیخطر.
فاز ۰ — آمادهسازی و قراردادها (۳–۵ روز)
هدف: قفل طراحی، seed افزونه، اسکلت خالی بدون رفتار تلفنی واقعی.
کارها:
- ثبت افزونه در
marketplace_plugins_seedبا کدasterisk_issabel_connector - اسکریپت
add_telephony_plugin.py - سند API OpenAPI draft داخل همین ریپو یا swagger stubs
- تعریف permissionها در UI مجوزها
- ایجاد branch/workflow تیمی و برش تیکتها از همین سند
پذیرش:
- افزونه در مارکتپلیس دیده میشود (حتی اگر صفحات «بهزودی» باشند)
- کد افزونه و قیمتگذاری seed شده
فاز ۱ — MVP عملیاتی CTI (۶–۸ هفته)
هدف: اتصال واقعی Issabel، Screen Pop، Click-to-Call، تاریخچه پایه.
۱.A Backend
- مدلها و migrationهای بخش ۴ (حداقل: settings, pbx, extensions, user_extensions, calls, call_events)
- plugin dependency gate
- phone normalizer + matcher
- connector heartbeat/events/commands poll
- call state machine
- click-to-call originate command
- screen-pop-context aggregate (person/lead + balance پایه)
- WS notification
telephony.incoming_call/telephony.call_ended/telephony.call_missed - CRUD تاریخچه و patch نتیجه/یادداشت
- ایجاد Person/Lead از تماس
۱.B Connector
- AMI login
- map رویدادهای Dial/Hangup به webhook
- originate
- heartbeat
- نصبنامه Issabel
- صف محلی retry اگر Hesabix در دسترس نبود
۱.C Flutter UI
- Plugin gate + منو + routes
- Phone Bar در BusinessShell
- Screen Pop (desktop side panel + mobile sheet)
- Dialer
- صفحه تاریخچه و جزئیات تماس
- تنظیمات: PBX token، نگاشت کاربر↔داخلی، تست اتصال
TelephonyPhoneLinkدر Person و Lead- Post-call sheet ساده
۱.D کیفیت
- تست واحد normalizer (ماتریس شمارههای ایرانی)
- تست idempotency رویدادها
- تست مجوزها و لایسنس
- سناریو دستی روی Issabel staging
پذیرش فاز ۱:
- Connector online در UI
- تماس ورودی شناختهشده → Pop صحیح برای همان کاربر/داخلی/کسبوکار
- ناشناس → ایجاد Lead/Person
- Click-to-Call از پروفایل کار میکند
- تاریخچه ورودی/خروجی با مدت و وضعیت
- تعویض کسبوکار context داخلی را عوض میکند
- UI روی Chrome وب، Android، و Windows desktop قابل استفاده است (layout نشکند)
صریحاً خارج از فاز ۱: Hold/Transfer، داشبورد زنده، گزارش پیشرفته، Softphone، آپلود انبوه ضبط.
فاز ۲ — عمق CRM + ضبط + گزارش (۴–۶ هفته)
هدف: تماس بخشی از فروش و پشتیبانی روزمره شود.
کارها:
- recording.ready از Connector + پروکسی پخش/دانلود با permission
- آپلود اختیاری به
file_storage(module_context=telephony_recording) - اتصال تماس به Deal و Document
- auto activity + task برای missed
- بهبود Screen Pop: فاکتورها، چکها، آخرین خریدها، یادداشتها
- گزارشهای summary / operators / by-person / by-time
- Workflow triggers اولیه
- بهبود UI گزارش و پلیر صوت زیبا (waveform ساده یا progress دقیق)
- number aliases
- sync بهتر داخلیها از Connector
پذیرش فاز ۲:
- پخش ضبط برای کاربر مجاز؛ رد برای غیرمجاز
- Pop مانده حساب و چک معوق را نشان میدهد
- گزارش هفتگی اپراتور قابل استخراج است
- missed call میتواند وظیفه بسازد
فاز ۳ — مرکز تماس زنده و کنترل تماس (۶–۸ هفته)
هدف: سرپرست و اپراتور دید زنده و کنترل داشته باشند.
کارها:
- live snapshot: تماسهای فعال، صف، حضور داخلی (BLF)
- UI Live Dashboard تطبیقی
- دستورات Hangup / Transfer / Hold / Resume (AMI یا ARI)
- چند PBX / چند شعبه در UX
- اعلان تماس برگشتی
- مشاهده تماسهای در حال انجام تیم
control_callspermission enforcement- بهینهسازی WS اختصاصی در صورت نیاز
پذیرش فاز ۳:
- داشبورد زنده تأخیر محسوس < ۳ ثانیه در شبکه عادی دارد
- Transfer از UI روی Issabel واقعی کار میکند
- BLF وضعیت آزاد/مشغول/آفلاین را نشان میدهد
فاز ۴ — سختسازی تولید و عملیات (۳–۴ هفته)
هدف: آماده فروش گسترده.
کارها:
- مشاهدهپذیری: متریکها، لاگ ساختیافته، DLQ رویدادهای شکستخورده
- rotate token، audit log دستورات
- محدودیت نرخ، محافظت replay
- راهنمای نصب ویدیویی/متنی فارسی کامل
- تست بار رویداد همزمان
- بهبود UX حالتهای خطا و بازیابی Connector
- FA/EN کامل رشتهها
- چکلیست امنیتی
پذیرش فاز ۴:
- مستندات نصب توسط یک تکنسین Issabel بدون کمک تیم توسعه انجامپذیر است
- قطع و وصل شبکه باعث فساد داده تماس نمیشود
- سوپرادمین متریک سلامت کانکتورها را میبیند (حداقلی)
فاز ۵ — پیشرفته اختیاری (۸+ هفته یا بکلاگ)
فقط با تأیید محصول:
- Softphone WebRTC داخل Flutter Web/Desktop
- تحلیل تماس ناشناس
- صف انتظار پیشرفته و wallboard تمامصفحه
- اتصال به کانالهای دیگر (در صورت نیاز)
- ماژول تیکت کسبوکار جدا + اتصال به تماس
- ضبط با سطح دسترسی بسیار دانهریز per call
۱۳. برنامه اسپرینت پیشنهادی فاز ۱
| اسپرینت | مدت | خروجی |
|---|---|---|
| S1 | ۲ هفته | مدل داده، seed، settings/pbx API، اسکلت Flutter settings + gate |
| S2 | ۲ هفته | Connector AMI + events + call state + تاریخچه |
| S3 | ۲ هفته | Screen Pop + WS + matcher + Pop UI همه پلتفرمها |
| S4 | ۲ هفته | Click-to-Call + Dialer + Phone Bar + PhoneLink + پایدارسازی و QA |
۱۴. معیارهای کیفیت و تست
۱۴.۱ ماتریس تست شماره
حداقل ۳۰ نمونه شامل:
09121234567,9121234567,989121234567,+989121234567,00989121234567- شهری
02191001234با/بدون صفر - شماره کوتاه داخلی
- شماره با خط تیره و فاصله فارسی
۱۴.۲ تست چندمستأجری
- رویداد business A هرگز به کاربر business B نرسد
- حتی اگر همان user در هر دو عضو است، Pop فقط برای mapping همان business برود
۱۴.۳ تست پلتفرم UI
| پلتفرم | حداقل بررسی |
|---|---|
| Web Chrome/Firefox | Pop، Dialer، WS، پخش صوت |
| Android | bottom sheet، Originate، نوار وضعیت |
| iOS | همان + محدودیت background (مستند شود) |
| Windows desktop | میانبرها، پنل کناری، فونت/RTL |
۱۴.۴ تست کارایی
- ۱۰ رویداد/ثانیه per PBX بدون drop (فاز ۱ هدف اولیه)
- Pop context p95 < ۵۰۰ms روی داده متوسط
۱۵. امنیت و حریم خصوصی
- توکن Connector فقط hash در DB
- ضبط مکالمه فقط با permission
- URL ضبط زماندار یا stream احرازهویتشده — لینک عمومی خام ممنوع
- AMI credential فقط روی سرور مشتری (Connector)، نه در کلود حسابیکس
- Audit برای listen recording و control commands
- حداقل داده در WS (بدون ارسال کامل تاریخچه مالی در خود event اگر سنگین است؛ client میتواند context را fetch کند — یا نسخه خلاصه)
تصمیم فاز ۱ برای Pop: event WS خلاصه بفرستد؛ UI بلافاصله screen-pop-context را GET کند (سریعتر برای امنیت و تازگی داده).
۱۶. قیمتگذاری و مارکتپلیس (پیشنهاد اولیه)
| دوره | قیمت پیشنهادی seed | قابل تغییر توسط ادمین |
|---|---|---|
| ماهانه | ۳۵۰٬۰۰۰ ریال? → به تومان رایج مارکت: همتراز integrationها مثلاً ۲۵۰٬۰۰۰–۵۰۰٬۰۰۰ تومان | بله |
| سالانه | ۱۰× ماهانه با تخفیف | بله |
| Trial | ۱۴ روز | بله |
دسته: integration
توضیح مارکتپلیس باید صریحاً بگوید: «نیاز به نصب Connector روی Issabel/Asterisk دارد».
مبلغ نهایی را مالک محصول قبل از انتشار عمومی در seed قطعی کند.
۱۷. ریسکها و کاهش ریسک
| ریسک | اثر | کاهش |
|---|---|---|
| تنوع dialplan ایزابل | Originate کار نکند | تنظیم context در settings + راهنمای تشخیص |
| NAT/قطع اینترنت شعبه | از دست رفتن رویداد | صف retry محلی Connector |
| Caller ID نامعتبر مخابرات | Pop اشتباه | aliases + UI اصلاح دستی |
| انتظار Softphone در MVP | نارضایتی | شفافسازی فروش: Click-to-Call نیاز به گوشی SIP دارد |
| حجم ضبطها | هزینه storage | پیشفرض لینک PBX؛ آپلود اختیاری |
| پیچیدگی UI مرکز تماس | شلوغی | فازبندی و Phone Bar مینیمال |
۱۸. تصمیمهای باز (باید قبل/حین فاز ۱ بسته شوند)
| # | موضوع | گزینهها | پیشنهاد سند |
|---|---|---|---|
| 1 | مبلغ نهایی افزونه | — | توسط محصول |
| 2 | کانال معکوس دستورات | Poll vs WS Connector | فاز ۱ Poll؛ فاز ۲ WS |
| 3 | آیا Post-call اجباری باشد؟ | بله/خیر/تنظیمی | تنظیمی؛ پیشفرض خیر |
| 4 | ذخیره ضبط در کلود | همیشه/اختیاری/هرگز | اختیاری |
| 5 | افزودن phone به Lead |
بله/خیر | فاز ۲ در صورت نیاز واقعی |
| 6 | نام منو | مرکز تماس / تلفن / استریسک | «مرکز تماس» |
۱۹. چکلیست شروع پیادهسازی (روز ۱)
- تأیید این سند توسط محصول/فنی
- بستن جدول تصمیمهای باز بخش ۱۸ (حداقلی: ۱، ۳، ۴، ۶)
- ایجاد تیکتهای فاز ۰ و اسپرینت ۱ از بخش ۱۳
- آمادهسازی یک Issabel staging با AMI user تست
- شروع seed + migration اسکلت طبق فاز ۰/۱.A
۲۰. پیوست A — نگاشت لیست نیازمندی کاربر به فاز
| نیازمندی | فاز |
|---|---|
| Screen Pop | ۱ |
| جستجوی خودکار مخاطب/مشتری | ۱ |
| سوابق/فاکتور/یادداشت در Pop | ۱ خلاصه؛ ۲ کامل |
| وضعیت بدهی/اعتبار | ۱ (balance)؛ ۲ غنیتر |
| ایجاد مخاطب/Lead ناشناس | ۱ |
| Click-to-Call | ۱ |
| ثبت خودکار ورودی/خروجی | ۱ |
| مدت/شروع/پایان/نتیجه/داخلی | ۱ |
| یادداشت و دستهبندی پس از تماس | ۱ |
| ضبط لینک/پخش/دانلود/دسترسی | ۲ |
| نگاشت کاربر↔داخلی + چند داخلی | ۱ |
| وضعیت داخلی | ۳ (در ۱ فقط هنگام رویداد تماس) |
| از دسترفته / تعداد امروز | ۱ پایه؛ ۳ زنده |
| Activity/یادآوری/وظیفه | ۲ |
| اتصال به Deal/فاکتور | ۲ |
| گزارشها | ۲ |
| چند PBX / صفها / گروهها | ۳ (تعریف صف ۲/۳) |
| اعلانها | ۱ |
| صف انتظار / BLF / تماس فعال | ۳ |
| قطع/انتقال/Hold | ۳ |
| شمارهگیری سریع / جستجو حین تایپ | ۱ |
| چند شعبه | ۳ |
| یکپارچگی تیکت/تقویم/اسناد/چک/مکاتبات | ۲ (تیکت کسبوکار خارج مگر تعریف شود) |
| داشبورد لحظهای | ۳ |
| Softphone داخل اپ | ۵ |
۲۱. پیوست B — نمونه جریان State Machine تماس
┌──────────┐
│ (new) │
└────┬─────┘
│ call.ringing
▼
┌──────────┐
┌──────│ ringing │──────┐
│ └────┬─────┘ │
call.missed/failed │ answered │ busy
│ ▼ │
│ ┌──────────┐ │
│ │ answered │ │
│ └────┬─────┘ │
│ │ ended │
▼ ▼ ▼
missed completed busy
هر انتقال باید telephony_call_events بنویسد و در صورت نیاز WS بفرستد.
۲۲. پیوست C — معیار «UI عالی» برای پذیرش بصری
قبل از انتشار هر فاز دارای UI، این موارد باید پاس شوند:
- Phone Bar در وب/دسکتاپ/موبایل هممعنا و بدون شکستگی RTL
- Pop در ۳ اندازه صفحه (≤400، 768، ≥1280) بازبینی شده
- حالت ringing بدون چشمکزن آزاردهنده؛ فقط pulse ملایم
- پخش صوت کنترل واضح دارد (play/pause/seek/speed اختیاری)
- تنظیمات Connector برای کاربر غیرفنی قابل فهم است (۳ گام: نصب → توکن → تأیید آنلاین)
- هیچ صفحه تلفن فقط «جدول خام بدون سلسلهمراتب» نباشد
- زمان تعامل اپراتور برای ثبت نتیجه پس از تماس < ۲۰ ثانیه
پایان سند اجرایی v1.0.0
مرحله بعد پس از تأیید این سند: اجرای فاز ۰ و برش تیکتهای اسپرینت ۱ دقیقاً مطابق بخشهای ۱۲ و ۱۳.