余尚辉 hace 5 meses
padre
commit
0b17044af2

+ 165 - 176
src/core/callcenter/api.py

@@ -2,16 +2,29 @@
 # encoding:utf-8
 
 import json
-from urllib.parse import urlparse
-from typing import Dict, Any, Optional
+from typing import Dict, Any, Optional, List
 
+from src.core.callcenter.constant import get_json_dict
 from src.core.callcenter.exception import SipUriSyntaxException
 from src.core.datasource import SIP_SERVER
 from src.core.callcenter.esl.constant.esl_constant import SPLIT, SIP_HEADER
-from src.core.callcenter.enumeration import CallType, DeviceType
+from src.core.callcenter.enumeration import CallType, DeviceType, Direction, CdrType, NextType
 
 
-class AgentCallRequest:
+class BaseApi:
+    @classmethod
+    def from_json(cls, json_string):
+        data = get_json_dict(json_string)
+        return cls(**data)
+
+    def to_json_string(self):
+        return json.dumps(self, default=lambda x: x.__dict__, ensure_ascii=False)
+
+    def __repr__(self):
+        return self.to_json_string()
+
+
+class AgentCallRequest(BaseApi):
     """
     呼叫请求对象
     """
@@ -39,16 +52,8 @@ class AgentCallRequest:
         # 随路数据
         self.follow_data = follow_data
 
-    def to_json_string(self):
-        return json.dumps(self.__dict__, ensure_ascii=False)
 
-    @classmethod
-    def from_json(cls, json_string):
-        data = json.loads(json_string)
-        return cls(**data)
-
-
-class AgentRequest:
+class AgentRequest(BaseApi):
     def __init__(self, saas_id, agent_number, agent_name, out_id, agent_password, agent_type, phone_number, distribute,
                  agent_state, identity_type):
         # 租户隔离
@@ -73,7 +78,7 @@ class AgentRequest:
         self.identity_type = identity_type
 
 
-class AgentQueryRequest:
+class AgentQueryRequest(BaseApi):
     def __init__(self, saas_id, agent_number, out_id, agent_type, agent_name, page=1, size=10):
         # 租户隔离
         self.saas_id = saas_id
@@ -89,11 +94,10 @@ class AgentQueryRequest:
         self.size = size
 
 
-class AgentActionRequest:
+class AgentActionRequest(BaseApi):
     """
     坐席操作
     """
-
     def __init__(self, saas_id, agent_id, agent_number=None, out_id=None, identity_type=None, scene='manual',**kwargs):
         self.saas_id =saas_id
         # 坐席工号
@@ -107,26 +111,20 @@ class AgentActionRequest:
         # 场景 manual:手动外呼; robot:机器人外呼; monitor:监听
         self.scene = scene
 
-    @classmethod
-    def from_json(cls, data):
-        return cls(**data)
 
-
-class HumanServiceQueryRequest:
+class HumanServiceQueryRequest(BaseApi):
     """人工组查询"""
+
     def __init__(self, saas_id, service_id, agent_state, agent_ids: Dict[str, Any] = {}):
         self.saasId = saas_id
         self.serviceId = service_id
         self.agentState = agent_state
         self.agentIds = agent_ids
 
-    @classmethod
-    def from_json(cls, data):
-        return cls(**data)
-
 
-class AgentMonitorData:
+class AgentMonitorData(BaseApi):
     """坐席状态信息"""
+
     def __init__(self, saas_id=None, agent_num=None, agent_name=None, out_id=None,
                  check_state=None, online_state=None, check_in_time=None, check_out_time=None,
                  service_state=None, busy_time=None, idle_time=None, call_time=None, hang_time=None,
@@ -150,20 +148,14 @@ class AgentMonitorData:
         self.check_scene = check_scene
 
 
-class AgentEventData:
+class AgentEventData(BaseApi):
     def __init__(self, app_code, user_id, data: Dict[str, Any] = {}):
         self.appCode = app_code
         self.userId = user_id
         self.data = data
 
-    def __repr__(self):
-        return self.to_json_string()
 
-    def to_json_string(self):
-        return json.dumps(self.__dict__, ensure_ascii=False)
-
-
-class AgentStateData:
+class AgentStateData(BaseApi):
     def __init__(self, _s=None, _t=None, status=None, time=None, assign_time=None, phone_num=None):
         self._s = _s
         self._t = _t
@@ -172,11 +164,8 @@ class AgentStateData:
         self.assign_time = assign_time
         self.phone_num = phone_num
 
-    def to_json_string(self):
-        return json.dumps(self.__dict__, ensure_ascii=False)
-
 
-class HangupCallRequest:
+class HangupCallRequest(BaseApi):
     def __init__(self, saas_id, call_id, agent_number):
         # saasId(必填)
         self.saas_id = saas_id
@@ -186,7 +175,7 @@ class HangupCallRequest:
         self.agent_number = agent_number
 
 
-class CheckInCallRequest:
+class CheckInCallRequest(BaseApi):
     """检查是否在通话中实体"""
 
     def __init__(self, saas_id, agent_number):
@@ -194,10 +183,10 @@ class CheckInCallRequest:
         self.agent_number = agent_number
 
 
-class MakeCallContext:
+class MakeCallContext(BaseApi):
 
-    def __init__(self, 
-                 pbx_server=SIP_SERVER,
+    def __init__(self,
+                 sip_server=SIP_SERVER,
                  route_gateway_name="gateway-fxo",
                  display: Optional[str] = None,
                  caller: Optional[str] = None,
@@ -213,7 +202,7 @@ class MakeCallContext:
                  service_id: Optional[str] = None,
                  call_type: Optional[int] = None):
         # fs 地址
-        self.pbx_server = pbx_server
+        self.sip_server = sip_server
         # 线路名(非必传)
         self.route_gateway_name = route_gateway_name
         # 外显号(必传)
@@ -271,15 +260,15 @@ class MakeCallContext:
         if self.service_id:
             headers.append(f"{SIP_HEADER}service_id={self.service_id}")
 
-        # if self.device_type == DeviceType.CUSTOMER:
-        #     headers += [
-        #         "RECORD_STEREO_SWAP=true"
-        #     ]
-        # else:
-        #     headers += [
-        #         "RECORD_STEREO_SWAP=false",
-        #         "continue_on_fail=true"
-        #     ]
+        if self.device_type == DeviceType.CUSTOMER.code:
+            headers += [
+                "RECORD_STEREO_SWAP=true"
+            ]
+        else:
+            headers += [
+                "RECORD_STEREO_SWAP=false",
+                "continue_on_fail=true"
+            ]
 
         if self.sip_header_map:
             headers.extend([f"{SIP_HEADER}{k}={v}" for k, v in self.sip_header_map.items()])
@@ -287,10 +276,14 @@ class MakeCallContext:
         return SPLIT.join(headers)
 
     def get_called(self) -> str:
-        if self.called_prefix and self.device_type == DeviceType.CUSTOMER:
+        if self.called_prefix and self.device_type == DeviceType.CUSTOMER.code:
             return f"{self.called_prefix}{self.called}"
         return self.called
 
+    def get_realm(self):
+        sip_uri = SipURI(self.sip_server)
+        return sip_uri.get_host()
+
 
 class SipURI:
     DEFAULT_PORT = -1
@@ -372,12 +365,12 @@ class SipURI:
         return self.userinfo
 
 
-class AgentInfo:
-    def __init__(self, sass_id=None, agent_number=None, realm=None, sip_server=None,
+class AgentInfo(BaseApi):
+    def __init__(self, saas_id=None, agent_number=None, realm=None, sip_server=None,
                  call_id=None, device_id=None, real_device_id=None, line_id=None, fs_user=None, domain=None,
                  service_time=0, max_ready_time=0, total_ready_time=0, ready_times=0, not_ready_times=0,
                  total_after_time=0, max_talk_time=0, total_talk_time=0, total_ring_times=0, total_answer_times=0):
-        self.sass_id = sass_id
+        self.saas_id = saas_id
         self.agent_number = agent_number
         self.realm = realm
         self.sip_server = sip_server
@@ -429,25 +422,18 @@ class AgentInfo:
         """
         return self.fs_user is None or self.fs_user is True
 
-    def to_json_string(self):
-        return json.dumps(self.__dict__, ensure_ascii=False)
 
-    @classmethod
-    def from_json(cls, json_string):
-        data = json.loads(json_string)
-        return cls(**data)
-
-
-class CallInfo:
+class CallInfo(BaseApi):
     def __init__(self, core_uuid=None, call_id=None, conference=None, saas_id=None, group_id=None,
                  hidden_customer=0, caller_display=None, caller=None, called_display=None, called=None,
                  number_location=None, agent_key=None, agent_name=None, login_type=None, ivr_id=None, task_id=None,
-                 media_host=None, cti_host=None, client_host=None, record=None, record2=None, record_time=None,
+                 media_host=None, sip_server=None, client_host=None, record=None, record2=None, record_time=None,
                  call_time=None, call_type=None, direction=None, answer_flag=None, wait_time=None, answer_count=0,
                  hangup_dir=None, sdk_hangup=0, hangup_code=None, answer_time=None, end_time=None, talk_time=None,
                  frist_queue_time=None, queue_start_time=None, queue_end_time=None, overflow_count=0,
-                 uuid1=None, uuid2=None, cdr_notify_url=None, queue_level=None, device_list=[], device_info_map={},
-                 follow_data={}, process_data={}, next_commands=[], call_details=[]):
+                 uuid1=None, uuid2=None, cdr_notify_url=None, queue_level=None, device_list=[],
+                 device_info_map: Dict[str, Any] = {}, follow_data: Dict[str, Any] = {},
+                 process_data: Dict[str, Any] = {}, next_commands=[], call_details=[]):
         self.core_uuid = core_uuid  # 通话唯一标识
         self.call_id = call_id  # 通话唯一标识
         self.conference = conference  # 会议号
@@ -465,7 +451,7 @@ class CallInfo:
         self.ivr_id = ivr_id  # ivr
         self.task_id = task_id  # 任务ID
         self.media_host = media_host  # 媒体
-        self.cti_host = cti_host  # 服务地址
+        self.sip_server = sip_server  # 服务地址
         self.client_host = client_host  # 客户服务地址
         self.record = record  # 录音地址
         self.record2 = record2  # 第三方存储地址
@@ -497,31 +483,96 @@ class CallInfo:
         self.next_commands = next_commands  # 执行下一步命令
         self.call_details = call_details  # 电话流程
 
-    def to_json_string(self):
-        return json.dumps(self.__dict__, ensure_ascii=False)
-
     @classmethod
     def from_json(cls, json_string):
-        data = json.loads(json_string)
-        return cls(**data)
+        data = get_json_dict(json_string)
+        device_list = [DeviceInfo(**x) for x in data.get('device_list', [])]
+        device_info_map: Dict[str, Any] = {key: DeviceInfo(**value) for key, value in data.get('device_info_map', {}).items()}
+        follow_data: Dict[str, Any] = {key: value for key, value in data.get('follow_data', {}).items()}
+        process_data: Dict[str, Any] = {key: value for key, value in data.get('process_data', {}).items()}
+        next_commands = [NextCommand(**x) for x in data.get('next_commands', [])]
+        call_details = [CallDetail(**x) for x in data.get('call_details', [])]
+
+        return cls(core_uuid=data.get('core_uuid'), call_id=data.get('call_id'), conference=data.get('conference'),
+                   saas_id=data.get('saas_id'), group_id=data.get('group_id'),
+                   hidden_customer=data.get('hidden_customer'), caller_display=data.get('caller_display'),
+                   caller=data.get('caller'), called_display=data.get('called_display'), called=data.get('called'),
+                   number_location=data.get('number_location'), agent_key=data.get('agent_key'),
+                   agent_name=data.get('agent_name'), login_type=data.get('login_type'), ivr_id=data.get('ivr_id'),
+                   task_id=data.get('task_id'), media_host=data.get('media_host'), sip_server=data.get('sip_server'),
+                   client_host=data.get('client_host'), record=data.get('record'), record2=data.get('record2'),
+                   record_time=data.get('record_time'), call_time=data.get('call_time'),
+                   call_type=data.get('call_type'), direction=data.get('direction'),
+                   answer_flag=data.get('answer_flag'), wait_time=data.get('wait_time'),
+                   answer_count=data.get('answer_count'), hangup_dir=data.get('hangup_dir'),
+                   sdk_hangup=data.get('sdk_hangup'), hangup_code=data.get('hangup_code'),
+                   answer_time=data.get('answer_time'), end_time=data.get('end_time'), talk_time=data.get('talk_time'),
+                   frist_queue_time=data.get('frist_queue_time'), queue_start_time=data.get('queue_start_time'),
+                   queue_end_time=data.get('queue_end_time'), overflow_count=data.get('overflow_count'),
+                   uuid1=data.get('uuid1'), uuid2=data.get('uuid2'), cdr_notify_url=data.get('cdr_notify_url'),
+                   queue_level=data.get('queue_level'), device_list=device_list,
+                   device_info_map=device_info_map, follow_data=follow_data, process_data=process_data,
+                   next_commands=next_commands, call_details=call_details)
 
-    # def __repr__(self):
-    #     return (f"CallInfo(core_uuid={self.core_uuid}, call_id={self.call_id}, conference={self.conference}, "
-    #             f"company_id={self.company_id}, group_id={self.group_id}, hidden_customer={self.hidden_customer}, "
-    #             f"caller_display={self.caller_display}, caller={self.caller}, called_display={self.called_display}, "
-    #             f"called={self.called}, number_location={self.number_location}, agent_key={self.agent_key}, "
-    #             f"agent_name={self.agent_name}, login_type={self.login_type}, ivr_id={self.ivr_id}, task_id={self.task_id}, "
-    #             f"media_host={self.media_host}, cti_host={self.cti_host}, client_host={self.client_host}, record={self.record}, "
-    #             f"record2={self.record2}, record_time={self.record_time}, call_time={self.call_time}, call_type={self.call_type}, "
-    #             f"direction={self.direction}, answer_flag={self.answer_flag}, wait_time={self.wait_time}, "
-    #             f"answer_count={self.answer_count}, hangup_dir={self.hangup_dir}, sdk_hangup={self.sdk_hangup}, "
-    #             f"hangup_code={self.hangup_code}, answer_time={self.answer_time}, end_time={self.end_time}, "
-    #             f"talk_time={self.talk_time}, frist_queue_time={self.frist_queue_time}, queue_start_time={self.queue_start_time}, "
-    #             f"queue_end_time={self.queue_end_time}, overflow_count={self.overflow_count}, uuid1={self.uuid1}, uuid2={self.uuid2}, "
-    #             f"cdr_notify_url={self.cdr_notify_url}, queue_level={self.queue_level})")
-
-
-class DeviceInfo:
+    def __str__(self):
+        return self.to_json_string()
+
+    def to_json_string(self):
+        new_dict = {
+            "core_uuid": self.core_uuid,
+            "call_id": self.call_id,
+            "conference": self.conference,
+            "saas_id": self.saas_id,
+            "group_id": self.group_id,
+            "hidden_customer": self.hidden_customer,
+            "caller_display": self.caller_display,
+            "caller": self.caller,
+            "called_display": self.called_display,
+            "called": self.called,
+            "number_location": self.number_location,
+            "agent_key": self.agent_key,
+            "agent_name": self.agent_name,
+            "login_type": self.login_type,
+            "ivr_id": self.ivr_id,
+            "task_id": self.task_id,
+            "media_host": self.media_host,
+            "sip_server": self.sip_server,
+            "client_host": self.client_host,
+            "record": self.record,
+            "record2": self.record2,
+            "record_time": self.record_time,
+            "call_time": self.call_time,
+            "call_type": self.call_type,
+            "direction": self.direction,
+            "answer_flag": self.answer_flag,
+            "wait_time": self.wait_time,
+            "answer_count": self.answer_count,
+            "hangup_dir": self.hangup_dir,
+            "sdk_hangup": self.sdk_hangup,
+            "hangup_code": self.hangup_code,
+            "answer_time": self.answer_time,
+            "end_time": self.end_time,
+            "talk_time": self.talk_time,
+            "frist_queue_time": self.frist_queue_time,
+            "queue_start_time": self.queue_start_time,
+            "queue_end_time": self.queue_end_time,
+            "overflow_count": self.overflow_count,
+            "uuid1": self.uuid1,
+            "uuid2": self.uuid2,
+            "cdr_notify_url": self.cdr_notify_url,
+            "queue_level": self.queue_level,
+            "device_list": [vars(x) for x in self.device_list],
+            "device_info_map": {key: vars(value) for key, value in self.device_info_map.items()},
+            "follow_data": {key: vars(value) for key, value in self.follow_data.items()},
+            "process_data": {key: vars(value) for key, value in self.process_data.items()},
+            "next_commands": [vars(x) for x in self.next_commands],
+            "call_details": [vars(x) for x in self.call_details],
+        }
+        print(new_dict)
+        return json.dumps(new_dict, default=lambda x: x.__dict__, ensure_ascii=False)
+
+
+class DeviceInfo(BaseApi):
     def __init__(self, call_id=None, conference=None, device_id=None, agent_key=None, agent_name=None, device_type=None,
                  cdr_type=None, from_agent=None, caller=None, called=None, display=None, called_location=None,
                  caller_location=None, call_time=None, ring_start_time=None, ring_end_time=None, answer_time=None,
@@ -557,33 +608,13 @@ class DeviceInfo:
         self.record_time = record_time  # 录音时长
         self.record_start_time = record_start_time  # 录音开始时间
         self.state = state  # 当前设备状态
-        self.apparent_number = apparent_number #外显号
-        self.caller_display = caller_display   # 主叫号
-
-    def to_json_string(self):
-        return json.dumps(self.__dict__, ensure_ascii=False)
-
-    @classmethod
-    def from_json(cls, json_string):
-        data = json.loads(json_string)
-        return cls(**data)
-
-    # def __repr__(self):
-    #     return (f"DeviceInfo(call_id={self.call_id}, conference={self.conference}, device_id={self.device_id}, "
-    #             f"agent_key={self.agent_key}, agent_name={self.agent_name}, device_type={self.device_type}, "
-    #             f"cdr_type={self.cdr_type}, from_agent={self.from_agent}, caller={self.caller}, called={self.called}, "
-    #             f"display={self.display}, called_location={self.called_location}, caller_location={self.caller_location}, "
-    #             f"call_time={self.call_time}, ring_start_time={self.ring_start_time}, ring_end_time={self.ring_end_time}, "
-    #             f"answer_time={self.answer_time}, bridge_time={self.bridge_time}, end_time={self.end_time}, "
-    #             f"talk_time={self.talk_time}, sip_protocol={self.sip_protocol}, channel_name={self.channel_name}, "
-    #             f"hangup_cause={self.hangup_cause}, ring_cause={self.ring_cause}, sip_status={self.sip_status}, "
-    #             f"record={self.record}, record_time={self.record_time}, record_start_time={self.record_start_time}, "
-    #             f"state={self.state})")
+        self.apparent_number = apparent_number  # 外显号
+        self.caller_display = caller_display  # 主叫号
 
 
-class NextCommand:
+class NextCommand(BaseApi):
 
-    def __init__(self, device_id, next_type, next_value):
+    def __init__(self, device_id, next_type, next_value=None):
         # 记录执行设备
         self.device_id = device_id
         # 下一步执行命令
@@ -591,16 +622,8 @@ class NextCommand:
         # 执行参数
         self.next_value = next_value
 
-    def to_json_string(self):
-        return json.dumps(self.__dict__, ensure_ascii=False)
 
-    @classmethod
-    def from_json(cls, json_string):
-        data = json.loads(json_string)
-        return cls(**data)
-
-
-class CallDetail:
+class CallDetail(BaseApi):
     def __init__(self, id=None, cts=None, start_time=None, end_time=None, call_id=None,
                  detail_index=None, transfer_type=None, transfer_id=None, reason=None,
                  month=None, ext1=None, ext2=None, status=None):
@@ -618,22 +641,8 @@ class CallDetail:
         self.ext2 = ext2  # 扩展字段2
         self.status = status  # 状态
 
-    def to_json_string(self):
-        return json.dumps(self.__dict__, ensure_ascii=False)
-
-    @classmethod
-    def from_json(cls, json_string):
-        data = json.loads(json_string)
-        return cls(**data)
-
-    # def __repr__(self):
-    #     return (f"CallDetail(id={self.id}, cts={self.cts}, start_time={self.start_time}, "
-    #             f"end_time={self.end_time}, call_id={self.call_id}, detail_index={self.detail_index}, "
-    #             f"transfer_type={self.transfer_type}, transfer_id={self.transfer_id}, reason={self.reason}, "
-    #             f"month={self.month}, ext1={self.ext1}, ext2={self.ext2}, status={self.status})")
 
-
-class RouteGateway:
+class RouteGateway(BaseApi):
     def __init__(self, id=None, saas_id=None, cts=None, uts=None, name=None, media_host=None,
                  media_port=None, caller_prefix=None, called_prefix=None,
                  profile='gateway/gateway-fxo', sip_header1='P-LIBRA-CallId=#{[callId]}',
@@ -653,30 +662,11 @@ class RouteGateway:
         self.sip_header3 = sip_header3  # SIP头3
         self.status = status  # 状态
 
-    def to_json_string(self):
-        return json.dumps(self.__dict__, ensure_ascii=False)
-
-    @classmethod
-    def from_json(cls, json_string):
-        data = json.loads(json_string)
-        return cls(**data)
-
-    # def __repr__(self):
-    #     return (f"RouteGateway(id={self.id}, cts={self.cts}, uts={self.uts}, name={self.name}, "
-    #             f"media_host={self.media_host}, media_port={self.media_port}, caller_prefix={self.caller_prefix}, "
-    #             f"called_prefix={self.called_prefix}, profile={self.profile}, "
-    #             f"sip_header1={self.sip_header1}, sip_header2={self.sip_header2}, "
-    #             f"sip_header3={self.sip_header3}, status={self.status})")
-
-
-
 
-
-
-
-#机器人外呼
+# 机器人外呼
 class BotChatRequest:
-    def __init__(self, nodeId=None, userId=None, sessionId=None, taskId=None, asrText=None, recordId=None, ext:Dict={}):
+    def __init__(self, nodeId=None, userId=None, sessionId=None, taskId=None, asrText=None, recordId=None,
+                 ext: Dict = {}):
         self.nodeId = nodeId  # 节点id
         self.userId = userId  # 用户id
         self.sessionId = sessionId  # 会话id
@@ -699,11 +689,10 @@ class BotChatRequest:
     #             f"event_type={self.event_type}, asr_text={self.asr_text}, key_input={self.key_input})")
 
 
-
 class ChatAction:
     def __init__(self, actionCode=None, actionContent=None):
-        self.actionCode = actionCode         # normal:正常通话;hang:挂断;transfer:转人工
-        self.actionContent = actionContent   # 动作内容
+        self.actionCode = actionCode  # normal:正常通话;hang:挂断;transfer:转人工
+        self.actionContent = actionContent  # 动作内容
 
     def to_json_string(self):
         return json.dumps(self.__dict__, ensure_ascii=False)
@@ -718,10 +707,10 @@ class ChatAction:
 
 class ChatContent:
     def __init__(self, contentType=None, content=None, voiceUrl=None, voiceContent=None):
-        self.contentType = contentType   # 播放类型
-        self.content = content             # 播放内容
-        self.voiceUrl = voiceUrl         # 语音地址
-        self.voiceContent = voiceContent # 语音文本
+        self.contentType = contentType  # 播放类型
+        self.content = content  # 播放内容
+        self.voiceUrl = voiceUrl  # 语音地址
+        self.voiceContent = voiceContent  # 语音文本
 
     def to_json_string(self):
         return json.dumps(self.__dict__, ensure_ascii=False)
@@ -737,13 +726,13 @@ class ChatContent:
 
 
 class ChatMessage:
-    def __init__(self, nodeId=None, contents=None,  waitTime=None,
-                 action=None,inputType=None):
-        self.nodeId = nodeId   # 节点id
-        self.contents = contents if contents is not None else []   # 内容列表
-        self.waitTime = waitTime   # 用户静默时长
-        self.action = action        # 动作代码
-        self.inputType = inputType   # dtmf类型
+    def __init__(self, nodeId=None, contents=None, waitTime=None,
+                 action=None, inputType=None):
+        self.nodeId = nodeId  # 节点id
+        self.contents = contents if contents is not None else []  # 内容列表
+        self.waitTime = waitTime  # 用户静默时长
+        self.action = action  # 动作代码
+        self.inputType = inputType  # dtmf类型
 
     def to_json_string(self):
         return json.dumps({
@@ -788,4 +777,4 @@ class ChatResponse:
             data=response_data,
             message=data.get("message"),
             code=data.get("code")
-        )
+        )

+ 2 - 1
src/core/callcenter/cache.py

@@ -54,7 +54,8 @@ def add_agent_info(call_info: CallInfo = None, agent: AgentInfo = None, call_id=
 def add_call_info(call: CallInfo):
     for k, v in call.device_info_map.items():
         add_device(k, call.call_id)
-    redis_handler.set(CALL_INFO + call.call_id, json.dumps(call), cacheDay * 24 * 60 * 60)
+    print(call.to_json_string())
+    redis_handler.set(CALL_INFO + call.call_id, call.to_json_string(), cacheDay * 24 * 60 * 60)
 
 
 def get_call_info_by_device_id(device_id):

+ 17 - 15
src/core/callcenter/call.py

@@ -2,7 +2,7 @@
 # encoding:utf-8
 
 import time
-
+from datetime import datetime
 import src.core.callcenter.cache as Cache
 from src.core.callcenter.constant import saasId, HOLD_MUSIC_PATH
 from src.core.callcenter.enumeration import CallCause, Direction, NextType, DeviceType, CdrType
@@ -20,27 +20,28 @@ class CallService:
         self.snowflake = Snowflake(worker_id=1, data_center_id=1)
 
     def call(self, request: AgentCallRequest):
-        call_id = 'C' + self.snowflake.next_id()
-        device_id = 'D' + self.snowflake.next_id()
-        now = lambda: int(round(time.time() * 1000))
+        call_id = 'C' + str(self.snowflake.next_id())
+        device_id = 'D' + str(self.snowflake.next_id())
+        # now = lambda: int(round(time.time() * 1000))
+        now = datetime.utcnow().timestamp()
 
         agent = Cache.get_agent_info(request.saas_id, request.agent_id)
         route_gateway = Cache.get_route_gateway(request.saas_id)
-        call_info = CallInfo(call_id=call_id, agent_key=agent.agent_number, cti_host=agent.sip_server,
-                             caller=agent.agent_number, called=request.called, direction=Direction.INBOUND,
+        call_info = CallInfo(call_id=call_id, agent_key=agent.agent_number, sip_server=agent.sip_server,
+                             caller=agent.agent_number, called=request.called, direction=Direction.INBOUND.code,
                              caller_display=request.caller_display, called_display=request.called_display,
-                             call_type=request.call_type, call_time=now, follow_data=request.follow_data,
+                             call_type=request.call_type.code, call_time=now, follow_data=request.follow_data,
                              uuid1=request.uuid1, uuid2=request.uuid2, saas_id=saasId)
-        device_info = DeviceInfo(device_id=device_id, call_time=now, call_id=call_id, device_type=DeviceType.AGENT,
+        device_info = DeviceInfo(device_id=device_id, call_time=now, call_id=call_id, device_type=DeviceType.AGENT.code,
                                  agent_key=agent.agent_number, caller_display=route_gateway.name)
         call_info.device_list.append(device_info)
-        call_info.next_commands.append(NextCommand(device_id, NextType.NEXT_CALL_OTHER))
-        call_info.device_info_map[device_id] = device_info
+        call_info.next_commands.append(NextCommand(device_id, NextType.NEXT_CALL_OTHER.code))
+        call_info.device_info_map = {device_id: device_info}
         Cache.add_call_info(call_info)
 
         context = MakeCallContext(display=request.called, caller=request.called, called=request.caller,
                                   call_id=call_id, device_id=device_id, device_type=device_info.device_type,
-                                  call_type=call_info.call_type)
+                                  call_type=call_info.call_type, sip_server=call_info.sip_server)
 
         self.client.make_call_new(context)
         return call_id
@@ -58,16 +59,17 @@ class CallService:
         caller = call_info.called
         call_id = call_info.call_id
         agent = Cache.get_agent_info(call_info.saas_id, call_info.agent_key)
-        device_id = 'D' + self.snowflake.next_id()
-        now = lambda: int(round(time.time() * 1000))
+        device_id = 'D' + str(self.snowflake.next_id())
+        # now = lambda: int(round(time.time() * 1000))
+        now = datetime.utcnow().timestamp()
 
         device_info = DeviceInfo(device_id=device_id, caller=caller, display=caller, called=agent_number, call_id=call_id,
-                            call_time=now, cdr_type=CdrType.TRANSFER.code, device_type=DeviceType.AGENT)
+                            call_time=now, cdr_type=CdrType.TRANSFER.code, device_type=DeviceType.AGENT.code)
         call_info.device_list.append(device_id)
 
         call_info.caller = agent_number
         call_info.device_info_map[device_id] = device_info
-        call_info.next_commands.append(NextCommand(device_info.device_id, NextType.NEXT_TRANSFER_CALL, call_info.device_list[0]))
+        call_info.next_commands.append(NextCommand(device_info.device_id, NextType.NEXT_TRANSFER_CALL.code, call_info.device_list[0]))
         call_info.agent_key = agent_number
         # agent.sip_server
         Cache.add_call_info(call_info)

+ 7 - 0
src/core/callcenter/constant.py

@@ -78,6 +78,13 @@ CALL_INFO = "CALL_INFO:"
 START_AGENT_NUM = "1000"
 
 
+def get_json_dict(json_string):
+    data = json_string
+    if isinstance(json_string, str):
+        data = json.loads(json_string)
+    return data
+
+
 def success_response(data=None, code=0, msg=""):
     response = json.dumps({"code": code, "msg": msg, "data": data})
     return response, 200, {"Content-Type": "application/json"}

+ 69 - 1
src/core/callcenter/enumeration.py

@@ -13,6 +13,10 @@ class HumanState(Enum):
         self.code = code
         self.description = description
 
+    @classmethod
+    def get_by_code(cls, code):
+        return next((member for member in cls if member.code == code), None)
+
 
 class AgentState(Enum):
     ENABLE = (0, '可用')
@@ -22,6 +26,10 @@ class AgentState(Enum):
         self.code = code
         self.description = description
 
+    @classmethod
+    def get_by_code(cls, code):
+        return next((member for member in cls if member.code == code), None)
+
 
 class AgentCheck(Enum):
     IN = (0, "签入")
@@ -31,6 +39,10 @@ class AgentCheck(Enum):
         self.code = code
         self.description = description
 
+    @classmethod
+    def get_by_code(cls, code):
+        return next((member for member in cls if member.code == code), None)
+
 
 class AgentHeartState(Enum):
     DEFAULT = (0, "默认")
@@ -55,6 +67,10 @@ class AgentServiceState(Enum):
         self.code = code
         self.description = description
 
+    @classmethod
+    def get_by_code(cls, code):
+        return next((member for member in cls if member.code == code), None)
+
 
 class AgentLogState(Enum):
     CREATE = (0, "创建")
@@ -88,6 +104,10 @@ class AgentLogState(Enum):
         self.code = code
         self.description = description
 
+    @classmethod
+    def get_by_code(cls, code):
+        return next((member for member in cls if member.code == code), None)
+
 
 class AgentScene(Enum):
     MANUAL = (1, "manual", "手动外呼")
@@ -137,6 +157,10 @@ class WorkStatus(Enum):
         self.code = code
         self.description = description
 
+    @classmethod
+    def get_by_code(cls, code):
+        return next((member for member in cls if member.code == code), None)
+
 
 class DownEvent(Enum):
 
@@ -166,6 +190,10 @@ class DownEvent(Enum):
         self.code = code
         self.description = description
 
+    @classmethod
+    def get_by_code(cls, code):
+        return next((member for member in cls if member.code == code), None)
+
 
 class ServiceDirect(Enum):
     NORMAL = (0, "正常呼叫")
@@ -193,6 +221,10 @@ class ServiceDirect(Enum):
         self.service_direct = service_direct
         self.description = description
 
+    @classmethod
+    def get_by_code(cls, code):
+        return next((member for member in cls if member.code == code), None)
+
 
 class AnswerFlag(Enum):
     INIT = (0, "均未拨通")
@@ -207,6 +239,10 @@ class AnswerFlag(Enum):
         self.code = code
         self.description = description
 
+    @classmethod
+    def get_by_code(cls, code):
+        return next((member for member in cls if member.code == code), None)
+
 
 class DeviceType(Enum):
     AGENT = (1, "坐席")
@@ -219,6 +255,10 @@ class DeviceType(Enum):
         self.code = code
         self.description = description
 
+    @classmethod
+    def get_by_code(cls, code):
+        return next((member for member in cls if member.code == code), None)
+
 
 class CallType(Enum):
     IM = (0, 'IM')
@@ -234,15 +274,23 @@ class CallType(Enum):
         self.code = code
         self.description = description
 
+    @classmethod
+    def get_by_code(cls, code):
+        return next((member for member in cls if member.code == code), None)
+
 
 class Direction(Enum):
     INBOUND = (1, '呼入')
     OUTBOUND = (2, '外呼')
 
     def __init__(self, code=None, description=None):
-        self._code = code
+        self.code = code
         self.description = description
 
+    @classmethod
+    def get_by_code(cls, code):
+        return next((member for member in cls if member.code == code), None)
+
 
 class NextType(Enum):
     NORNAL = (1, '默认')
@@ -275,6 +323,10 @@ class NextType(Enum):
         self.code = code
         self.description = description
 
+    @classmethod
+    def get_by_code(cls, code):
+        return next((member for member in cls if member.code == code), None)
+
 
 class CallCause(Enum):
     DEFAULT = (0, "默认")
@@ -298,6 +350,10 @@ class CallCause(Enum):
         self.code = code
         self.description = description
 
+    @classmethod
+    def get_by_code(cls, code):
+        return next((member for member in cls if member.code == code), None)
+
 
 class CdrType(Enum):
 
@@ -315,6 +371,10 @@ class CdrType(Enum):
         self.code = code
         self.description = description
 
+    @classmethod
+    def get_by_code(cls, code):
+        return next((member for member in cls if member.code == code), None)
+
 
 class HangupDir:
 
@@ -326,6 +386,10 @@ class HangupDir:
         self.code = code
         self.description = description
 
+    @classmethod
+    def get_by_code(cls, code):
+        return next((member for member in cls if member.code == code), None)
+
 
 class BizErrorCode(Enum):
     PARAM_ERROR = (10001, "参数错误")
@@ -384,3 +448,7 @@ class BizErrorCode(Enum):
     def __init__(self, code, message):
         self.code = code
         self.message = message
+
+    @classmethod
+    def get_by_code(cls, code):
+        return next((member for member in cls if member.code == code), None)

+ 6 - 7
src/core/callcenter/esl/client.py

@@ -40,8 +40,6 @@ class InboundClient:
         self.executors = {x: concurrent.futures.ThreadPoolExecutor(max_workers=1) for x in range(self.thread_num)}
 
         threading.Thread(target=self.start, args=()).start()
-        # gw = CacheUtil.getRouteGetway(saasId)
-        # self.make_call(gw, '63366692', '13241676588', 'C111111111', 'D1111111')
 
     def scan_esl_event_handlers(self):
         import inspect
@@ -115,21 +113,22 @@ class InboundClient:
 
     def make_call_new(self, context: MakeCallContext):
         called = context.get_called()
-        params = {'gateway': context.route_gateway_name, 'called': called, 'realm': context.pbx_server}
+        params = {'gateway': context.route_gateway_name, 'called': called, 'realm': context.get_realm()}
 
         builder = [
             '{', context.get_sip_header(), '}'
         ]
 
-        if context.device_type == DeviceType.CUSTOMER:
+        if context.device_type == DeviceType.CUSTOMER.code:
             profile = self.expression(profile1, params)
             builder.append(f"{SOFIA}{SK}{profile}{SK}{called}{PARK}")
         else:
             profile = self.expression(profile2, params)
-            builder.append(f"{SOFIA}{SK}{profile}{SK}{called}{PARK}")
+            builder.append(f"{profile}{PARK}")
         cmd = "".join(builder)
-        self.logger.info(cmd)
-        # self.con.bgapi(ORIGINATE, cmd)
+        print(cmd)
+        # self.logger.info(cmd)
+        self.con.bgapi(ORIGINATE, cmd)
 
     def make_call(self, route_gateway, display, called, call_id, device_id, timeout=30, originate_timeout=30, *sip_headers):
 

+ 13 - 11
src/core/callcenter/esl/handler/channel_answer_handler.py

@@ -2,6 +2,7 @@
 # encoding:utf-8
 
 import time
+from datetime import datetime
 from src.core.callcenter.constant import saasId, get_record_prefix
 import src.core.callcenter.cache as Cache
 from src.core.callcenter.enumeration import NextType, AnswerFlag, Direction, DeviceType
@@ -35,13 +36,13 @@ class ChannelAnswerHandler(EslEventHandler):
         call_info.answer_count = call_info.answer_count + 1
         call_info.next_commands.remove(next_command)
 
-        if NextType.NEXT_CALL_OTHER == next_command.next_type:
+        if NextType.NEXT_CALL_OTHER.code == next_command.next_type.code:
             self.call_other(call_info, device_info)
-        elif NextType.NEXT_CALL_BRIDGE == next_command.next_type:
+        elif NextType.NEXT_CALL_BRIDGE.code == next_command.next_type.code:
             self.call_bridge(call_info, device_info, next_command, event)
-        elif NextType.NEXT_TRANSFER_CALL == next_command.next_type:
+        elif NextType.NEXT_TRANSFER_CALL.code == next_command.next_type.code:
             self.transfer_call(call_info, next_command, event)
-        elif NextType.NEXT_LISTEN_CALL == next_command.next_type:
+        elif NextType.NEXT_LISTEN_CALL.code == next_command.next_type.code:
             self.listen(call_info, device_info, next_command, event)
         else:
             self.logger.warn("can not match command :%s, callId :%s", next_command.next_type, call_id)
@@ -57,22 +58,23 @@ class ChannelAnswerHandler(EslEventHandler):
         # device.record = record
         # device.record_start_time = device.answer_time
 
-        call.direction = Direction.OUTBOUND
-        call.answer_flag = AnswerFlag.AGENT_ANSWER
+        call.direction = Direction.OUTBOUND.code
+        call.answer_flag = AnswerFlag.AGENT_ANSWER.code
 
-        new_device_id = 'D' + self.snowflake.next_id()
+        new_device_id = 'D' + str(self.snowflake.next_id())
         call.device_list.append(new_device_id)
         called = call.called
 
         self.logger.info("呼另外一侧电话: callId: %s, display:%s, called:%s, deviceId: %s ",
                          call_id, call.called_display, called, device_id)
 
-        now = lambda: int(round(time.time() * 1000))
+        # now = lambda: int(round(time.time() * 1000))
+        now = datetime.utcnow().timestamp()
         route_gateway = Cache.get_route_gateway(call.saas_id)
         new_device = DeviceInfo(device_id=new_device_id, call_id=call_id, agent_key=call.agent_key,
                                 called=called, display=call.called_display, caller=call.called_display,
-                                call_time=now, device_type=DeviceType.CUSTOMER, caller_display=route_gateway.name)
-        call.next_commands.append(NextCommand(device_id=device_id, next_type=NextType.NEXT_CALL_BRIDGE, next_value=new_device_id))
+                                call_time=now, device_type=DeviceType.CUSTOMER.code, caller_display=route_gateway.name)
+        call.next_commands.append(NextCommand(device_id=device_id, next_type=NextType.NEXT_CALL_BRIDGE.code, next_value=new_device_id))
         call.device_info_map[new_device_id] = new_device
         Cache.add_call_info(call)
         Cache.add_agent_info(call_info=call, call_id=call_id, device_id=device_id)
@@ -100,7 +102,7 @@ class ChannelAnswerHandler(EslEventHandler):
         # 转接电话 deviceInfo为被转接设备
         from_device_id = next_command.device_id
         device_id = EslEventUtil.getDeviceId(event)
-        call.next_commands.append(NextCommand(device_id, NextType.NEXT_TRANSFER_SUCCESS, call.device_list[1]))
+        call.next_commands.append(NextCommand(device_id, NextType.NEXT_TRANSFER_SUCCESS.code, call.device_list[1]))
         self.logger.info("转接电话中 callId:%s, from:%s, to:%s ", call.call_id, from_device_id, device_id)
         self.inbound_client.transfer_call(device_id, next_command.next_value)
 

+ 7 - 7
src/core/callcenter/esl/handler/channel_hangup_handler.py

@@ -70,8 +70,8 @@ class ChannelHangupHandler(EslEventHandler):
         call.device_info_map[device.device_id] = device
 
         # 如果是转人工
-        if 'transferToAgent' == hangup_reason and DeviceType.ROBOT == device.device_type:
-            call.answer_flag = AnswerFlag.TRANSFER_TO_AGENT
+        if 'transferToAgent' == hangup_reason and DeviceType.ROBOT.code == device.device_type.code:
+            call.answer_flag = AnswerFlag.TRANSFER_TO_AGENT.code
             service_id = EslEventUtil.getLIBRAServiceId(event)
             Cache.add_call_info(call)
             self.acd_service.transfer_to_agent(call, device, service_id)
@@ -100,14 +100,14 @@ class ChannelHangupHandler(EslEventHandler):
 
     def next_cmd(self, call: CallInfo, device: DeviceInfo, next_command: NextCommand, cause):
         # 呼入转到坐席,坐席拒接和坐席sip呼不通的时候,都需要再次转回来到技能组排队。
-        if NextType.NEXT_CALL_BRIDGE == next_command.next_type or NextType.NEXT_LISTEN_CALL == next_command.next_type:
+        if NextType.NEXT_CALL_BRIDGE.code == next_command.next_type.code or NextType.NEXT_LISTEN_CALL.code == next_command.next_type.code:
             pass
-        elif NextType.NEXT_TRANSFER_CALL:
+        elif NextType.NEXT_TRANSFER_CALL.code:
             pass
         else:
             pass
 
-        if not next_command or NextType.NEXT_HANGUP != next_command.next_type:
+        if not next_command or NextType.NEXT_HANGUP.code != next_command.next_type.code:
             call.next_commands.remove(next_command)
 
         # 判断挂机方向 && 更新缓存
@@ -118,9 +118,9 @@ class ChannelHangupHandler(EslEventHandler):
         if call.hangup_dir or device.cdr_type > CdrType.CONSULT.code:
             self.logger.info("hangup_dir::hangup_dir :%s, cdr_type :%s", call.hangup_dir, device.cdr_type)
             return
-        if DeviceType.AGENT == device.device_type:
+        if DeviceType.AGENT.code == device.device_type.code:
             call.hangup_dir = HangupDir.HOST_HANGUP.code
-        elif DeviceType.CUSTOMER == device.device_type:
+        elif DeviceType.CUSTOMER.code == device.device_type.code:
             call.hangup_dir = HangupDir.CUSTOMER_HANGUP.code
 
         if not call.end_time:

+ 3 - 0
src/core/callcenter/exception.py

@@ -1,6 +1,7 @@
 #!/usr/bin/env python3
 # encoding:utf-8
 
+import traceback
 from . import app
 from .constant import error_response
 
@@ -21,11 +22,13 @@ class SipUriSyntaxException(Exception):
 
 @app.errorhandler(BizException)
 def handle_biz_exception(error):
+    traceback.print_exc()
     return error_response(msg=str(error), http_code=200)
 
 # Generic error handler for uncaught exceptions
 @app.errorhandler(Exception)
 def handle_generic_exception(error):
+    traceback.print_exc()
     return error_response(msg=str(error), http_code=500)
 
 # Specific error handler for 404 Not Found errors

+ 2 - 33
src/core/callcenter/test.py

@@ -1,34 +1,3 @@
-import concurrent.futures
-import time
+import json
+from src.core.callcenter.api import CallInfo, DeviceInfo, NextCommand
 
-class ExecutorManager:
-    def __init__(self, num_executors):
-        self.executor_map = {i: concurrent.futures.ThreadPoolExecutor(max_workers=5) for i in range(num_executors)}
-
-    def submit_task(self, executor_id, task, *args):
-        if executor_id in self.executor_map:
-            self.executor_map[executor_id].submit(task, *args)
-
-    def shutdown_all(self):
-        for executor in self.executor_map.values():
-            executor.shutdown(wait=True)
-        print("All executors shut down.")
-
-def task(executor_id):
-    print(f"Task executed by executor {executor_id}")
-    time.sleep(2)  # 模拟任务执行时间
-
-# 使用 ExecutorManager
-manager = ExecutorManager(num_executors=10)
-
-# 提交任务
-for i in range(10):
-    manager.submit_task(i % 10, task, i)
-
-try:
-    # 保持程序活动
-    while True:
-        time.sleep(1)  # 可以在这里执行其他操作
-except KeyboardInterrupt:
-    print("Shutting down executors...")
-    manager.shutdown_all()

+ 5 - 5
src/core/callcenter/views.py

@@ -175,9 +175,9 @@ def manual_call():
     # circuitUid: string
     # ext?: object
     # callId: string
-    data = request.json()
+    data = request.get_json()
     req = AgentCallRequest(saas_id=data.get('saas_id'), call_type=CallType.OUTBOUND_CALL, caller=data.get('caller'),
-                           agent_id=data.get('caller'), called=data.get('called'), follow_data=data.get('ext'))
+                           agent_id=data.get('caller'), called=data.get('called'))
     res = call_service.call(req)
     return success_response(res)
 
@@ -185,9 +185,9 @@ def manual_call():
 @app.route('/open/agent/manual-hang', methods=['POST'])
 def manual_hang():
     """挂断"""
-    data = request.json()
-    agent = Cache.get_agent_info(data.get('saasId'), data.get('agentId'))
-    req = HangupCallRequest(saas_id=data.get('saasId'), call_id=data.get('callId'), agent_number=agent.agent_number)
+    data = request.get_json()
+    agent = Cache.get_agent_info(data.get('saas_id'), data.get('agent_id'))
+    req = HangupCallRequest(saas_id=data.get('saas_id'), call_id=data.get('call_id'), agent_number=agent.agent_number)
     call_service.hangup(req)
     return success_response()
 

+ 4 - 4
src/core/datasource.py

@@ -15,13 +15,13 @@ from src.core import singleton
 from redis import StrictRedis, ConnectionPool
 
 SERVE_HOST = os.environ.get("SERVE_HOST")
-SERVE_HOST = "192.168.110.203"
+
+SERVE_HOST = "192.168.124.6"
 MYSQL_PASSWORD = 'EKoAe3H8xybQKrFPApXM'
 
 if SERVE_HOST != "192.168.100.159":
-    # SIP_SERVER = SERVE_HOST
-    # MYSQL_PASSWORD = "12345678"
-    SIP_SERVER = "pbx.fuxicarbon.com"
+    SIP_SERVER = SERVE_HOST
+    MYSQL_PASSWORD = "12345678"
 else:
     SIP_SERVER = "pbx.fuxicarbon.com"
 

+ 1 - 1
src/core/server.py

@@ -7,4 +7,4 @@ from src.core.callcenter.ws import socketio
 
 if __name__ == '__main__':
     socketio.run(app, host='0.0.0.0', port=8000, allow_unsafe_werkzeug=True, debug=True)
-    # app.run(host='127.0.0.1', port=8000, debug=True)
+    # app.run(host='0.0.0.0', port=8000, debug=True)