DavidLiu 3 months ago
parent
commit
e674842c1f

+ 2 - 1
.gitignore

@@ -1,3 +1,4 @@
+logs
 .vscode
 *.mar
 *.zip
@@ -33,4 +34,4 @@ output/*
 config/*.xlsx
 **.log
 **.ipynb
-*.pyc
+*.pyc

File diff suppressed because it is too large
+ 0 - 22
logs/flask.log.1


+ 7 - 0
src/core/__init__.py

@@ -1,4 +1,11 @@
+from functools import wraps
 
+def with_app_context(func):
+    @wraps(func)
+    def wrapper(self, *args, **kwargs):
+        with self.app.app_context():
+            return func(self, *args, **kwargs)
+    return wrapper
 
 def singleton(cls):
     _instance = {}

+ 2 - 2
src/core/callcenter/acd.py

@@ -22,8 +22,8 @@ class AcdService:
         self.app = app
         self.logger = app.logger
         self.cache = Cache(app)
-        self.call_service = CallService(client, app.logger)
-        self.agent_service = AgentOperService(client, app.logger)
+        self.call_service = CallService(client, app)
+        self.agent_service = AgentOperService(app)
         self.holdsQueue: Dict[str, Queue] = {}
         self.pool = ThreadPoolExecutor(max_workers=4)
         self.checkIdleScheduler = BackgroundScheduler()

+ 311 - 108
src/core/callcenter/agent.py

@@ -1,46 +1,251 @@
 #!/usr/bin/env python3
 # encoding:utf-8
 
-import traceback
-from typing import List
+import threading
 from collections import defaultdict
-from src.core.callcenter.constant import START_AGENT_NUM
-from src.core.callcenter.enumeration import AgentState, AgentCheck, AgentHeartState, AgentServiceState, AgentLogState, \
-    AgentScene, BizErrorCode, WorkStatus, DownEvent, HumanState
+from concurrent.futures import ThreadPoolExecutor
+from typing import List
+
 from sqlalchemy import or_
+
+import src.core.callcenter.esl.utils.esl_event_util as EslEventUtil
+from src.core import with_app_context
+from src.core.callcenter.api import AgentActionRequest, AgentQueryRequest, AgentRequest, AgentEventData, \
+    AgentStateData, HumanServiceQueryRequest, AgentMonitorData, CallInfo, DeviceInfo, AgentDelayStateData
+from src.core.callcenter.cache import Cache
 from src.core.callcenter.dao import *
+from src.core.callcenter.data_handler import DataHandleServer
+from src.core.callcenter.enumeration import AgentState, AgentCheck, AgentHeartState, AgentServiceState, AgentLogState, \
+    AgentScene, BizErrorCode, WorkStatus, DownEvent, HumanState, DeviceType, ServiceDirect
+from src.core.callcenter.esl.constant.event_names import *
 from src.core.callcenter.exception import BizException
-from src.core.callcenter.api import AgentActionRequest, AgentInfo, AgentQueryRequest, AgentRequest, AgentEventData, \
-    AgentStateData, HumanServiceQueryRequest, AgentMonitorData
 from src.core.callcenter.push import PushHandler
 from src.core.datasource import RedisHandler
 
-from concurrent.futures import ThreadPoolExecutor
-import threading
+
+class AgentEventService:
+    def __init__(self, app):
+        self.app = app
+        self.logger = app.logger
+        self.cache = Cache(app)
+        self.push_handler = PushHandler(app.logger)
+        self.data_handle_server = DataHandleServer(app)
+        self.agent_monitor_service = AgentMonitorService(app)
+        self.agent_state_service = AgentStateService(app)
+        self.agent_actionlog_service = AgentActionLogService(app)
+
+    def delay_state(self, state_data: AgentDelayStateData):
+        agent = self.data_handle_server.get_agent(state_data.saas_id, state_data.agent_num)
+        if not agent:
+            return
+        agent_monitor = self.data_handle_server.get_agent_monitor(state_data.saas_id, state_data.agent_num)
+        if not agent_monitor:
+            return
+
+        #TODO 非最新通话的延迟事件,忽略.
+
+        agent_scene = AgentScene.get_by_code(state_data.scene)
+        self.logger.info("agent event delay state %s %s %s %s", state_data.saas_id, state_data.agent_num, state_data.service_state, agent_monitor.service_state)
+        if AgentServiceState.REPROCESSING.code == state_data.service_state and AgentServiceState.REPROCESSING.code == agent_monitor.service_state:
+            self.reprocessing_idle(state_data)
+
+        if AgentServiceState.DIALING.code == state_data.service_state and AgentServiceState.DIALING.code == agent_monitor.service_state:
+            if self.cache.get_call_is_answer(state_data.saas_id, state_data.agent_num):
+                return
+            self.agent_monitor_service.update_idle(agent_monitor)
+
+            self.push_handler.push_on_call_end(state_data.saas_id, state_data.flow_id, state_data.agent_num, agent_scene, ServiceDirect.MANUAL_CALL.service_direct, '0')
+            self.push_handler.push_on_agent_work_report(state_data.saas_id, state_data.flow_id, state_data.agent_num, '', agent_scene, WorkStatus.AGENT_HANG_REPROCESSING)
+            self.push_handler.push_on_agent_work_report(state_data.saas_id, state_data.flow_id, state_data.agent_num, '', agent_scene, WorkStatus.AGENT_HANG_IDLE)
+
+            self.agent_actionlog_service.insert_service_state(agent_monitor, AgentServiceState.IDLE, AgentLogState.BIZ_DIALING_IDLE)
+
+        if AgentServiceState.HANGING.code == state_data.service_state \
+                and (AgentServiceState.DIALING.code == agent_monitor.service_state
+                     or AgentServiceState.CALLING.code == agent_monitor.service_state):
+            self.agent_monitor_service.update_idle(agent_monitor)
+
+            if AgentServiceState.DIALING.code == agent_monitor.service_state:
+                self.push_handler.push_on_call_end(state_data.saas_id, state_data.flow_id, state_data.agent_num, agent_scene, ServiceDirect.MANUAL_CALL.service_direct, '0')
+                self.push_handler.push_on_agent_work_report(state_data.saas_id, state_data.flow_id, state_data.agent_num, '', agent_scene, WorkStatus.AGENT_HANG_REPROCESSING)
+
+            self.push_handler.push_on_agent_work_report(state_data.saas_id, state_data.flow_id, state_data.agent_num, '', agent_scene, WorkStatus.AGENT_HANG_IDLE)
+            self.agent_actionlog_service.insert_service_state(agent_monitor, AgentServiceState.IDLE, AgentLogState.MANUAL_HANG_UP)
+
+
+    def agent_event_channel(self, event, call_info: CallInfo, device_info: DeviceInfo):
+        event_name = EslEventUtil.getEventName(event)
+        saas_id = call_info.saas_id if call_info else None
+        flow_id = call_info.cti_flow_id if call_info else None
+        call_id = call_info.call_id if call_info else None
+        device_id = device_info.device_id if device_info else None
+        agent_num = device_info.agent_key if device_info else None
+        caller = device_info.caller if device_info else None
+        called = device_info.called if device_info else None
+        is_agent = (device_info and DeviceType.AGENT.code == device_info.device_type) if device_info else False
+
+        self.logger.info('agent_event_channel, event_name=%s, call_id=%s, device_id=%s, is_agent=%s', event_name, call_id, device_id, is_agent)
+        agent = self.data_handle_server.get_agent(saas_id, agent_num)
+        if not agent:
+            # self.logger.warn("event service channel agent is null %s %s %s %s %s", saas_id, event_name, caller, called, json.loads(event.serialize('json')))
+            return
+        agent_monitor = self.data_handle_server.get_agent_monitor(saas_id, agent_num)
+        if not agent_monitor:
+            # self.logger.warn("event service channel agentMonitor is null %s %s %s %s %s", saas_id, event_name, caller, called, json.loads(event.serialize('json')))
+            return
+
+        # 信道发起事件,触发完成发起(或桥)&& 坐席侧
+        if CHANNEL_ORIGINATE == event_name and is_agent:
+            self.push_handler.push_on_agent_work_report(saas_id, flow_id, agent_num, call_id, AgentScene.MANUAL, WorkStatus.AGENT_RINGING)
+
+        # 进度事件,外呼时对方提醒。或者入呼时提醒 && 坐席侧
+        if CHANNEL_PROGRESS == event_name and is_agent:
+            self.push_handler.push_on_agent_report(saas_id, agent_num, AgentScene.MANUAL, AgentServiceState.DIALING)
+            self.push_handler.push_on_agent_work_report(saas_id, flow_id, agent_num, call_id, AgentScene.MANUAL, WorkStatus.AGENT_CALLING)
+
+        # 媒体进度事件,外呼时对方提醒。或者入呼时提醒 && 用户侧
+        if CHANNEL_PROGRESS_MEDIA == event_name and not is_agent:
+            self.push_handler.push_on_agent_work_report(saas_id, flow_id, agent_num, call_id, AgentScene.MANUAL, WorkStatus.AGENT_CALLING_RINGING)
+
+        #应答
+        if CHANNEL_ANSWER == event_name:
+            if call_id:
+                if self.cache.get_call_is_end(call_id):
+                    # self.logger.warn("event service channel call is end {} {} {} {} {} {}", saas_id, event_name, caller, called, call_id, json.dumps(event.serialize('json')))
+                    return
+
+            self.agent_state_service.busy(saas_id, agent.agent_num, agent.phone_num)
+            if is_agent:
+                # 坐席接起
+                self.cache.set_call_is_answer(saas_id, flow_id)
+                self.push_handler.push_on_agent_work_report(saas_id, flow_id, agent_num, call_id, AgentScene.MANUAL, WorkStatus.ANSWER_COMPENSATE)
+                self.push_handler.push_answer_call(saas_id, flow_id, agent_num, call_id, AgentScene.MANUAL, ServiceDirect.MANUAL_CALL.service_direct, WorkStatus.AGENT_HANG_REPROCESSING)
+
+                self.agent_actionlog_service.insert_service_state(agent_monitor, AgentServiceState.CALLING, AgentLogState.CHANNEL_TURN_ON)
+            else:
+                # 用户侧接起
+                self.agent_monitor_service.update_calling(agent_monitor)
+                self.push_handler.push_on_agent_work_report(saas_id, flow_id, agent_num, call_id, AgentScene.MANUAL, WorkStatus.AGENT_ANSWER_OUTGOING)
+                self.push_handler.push_on_agent_report(saas_id, agent_num, AgentScene.MANUAL, AgentServiceState.CALLING)
+
+        #挂断
+        if CHANNEL_HANGUP == event_name:
+            # 坐席侧挂断
+            if is_agent:
+                if call_id:
+                    self.cache.set_call_is_end(call_id)
+
+            self.agent_monitor_service.update_processing(agent_monitor)
+            self.reprocessing_idle(AgentDelayStateData(saas_id, flow_id, agent_num, AgentServiceState.REPROCESSING, AgentScene.MANUAL))
+            self.push_handler.push_on_call_end(saas_id, flow_id, agent_num, AgentScene.MANUAL, ServiceDirect.MANUAL_CALL.service_direct, '0')
+            self.push_handler.push_on_agent_work_report(saas_id, flow_id, agent_num, call_id, AgentScene.MANUAL, WorkStatus.AGENT_HANG_REPROCESSING)
+            self.push_handler.push_on_agent_report(saas_id, agent_num, AgentScene.MANUAL, AgentServiceState.REPROCESSING)
+
+            self.agent_actionlog_service.insert_service_state(agent_monitor, AgentServiceState.REPROCESSING, AgentLogState.CHANNEL_HANG_UP)
+
+            # 同步处理后处理置闲
+            # reprocessingIdle(statusDto);
+            # agentProducer.pushDelayedStatus(statusDto, reprocessingTimeout);
+
+        if (CHANNEL_BRIDGE == event_name or PLAYBACK_START == event_name) and is_agent:
+            self.push_handler.push_on_ring_start(saas_id, flow_id, agent_num, AgentScene.MANUAL, call_id)
+            # self.push_handler.push_on_agent_work_report(saas_id, flow_id, agent_num, call_id, AgentScene.MANUAL, WorkStatus.AGENT_ANSWER_OUTGOING)
+            # self.push_handler.push_on_agent_report(saas_id, agent_num, AgentScene.MANUAL, AgentServiceState.CALLING)
+
+        if DETECTED_TONE == event_name and not is_agent:
+            self.push_handler.push_on_detected_tone(saas_id, flow_id, call_id, AgentScene.MANUAL, call_id)
+
+        if (CHANNEL_UNBRIDGE == event_name or PLAYBACK_STOP == event_name) and is_agent:
+            self.push_handler.push_on_ring_end(saas_id, flow_id, call_id, AgentScene.MANUAL, call_id)
+
+
+    def bot_event_channel(self, event, call_info, device_info):
+        event_name = EslEventUtil.getEventName(event)
+        saas_id = call_info.saas_id if call_info else None
+        flow_id = call_info.cti_flow_id if call_info else None
+        call_id = call_info.call_id if call_info else None
+        agent_num = device_info.agent_key if device_info else None
+        is_agent = (device_info and DeviceType.AGENT.code == device_info.device_type) if device_info else False
+        caller = (device_info.called if is_agent else device_info.caller) if device_info else None
+        called = (device_info.caller if is_agent else device_info.called) if device_info else None
+        human_service_id = '00000000000000000'
+
+        agent = self.data_handle_server.get_agent(saas_id, agent_num)
+        if not agent:
+            # self.logger.warn("bot event service channel agent is null %s %s %s %s %s", saas_id, event_name, caller, called,
+            #                  json.dumps(event.serialize('json')))
+            return
+        agent_monitor = self.data_handle_server.get_agent_monitor(saas_id, agent_num)
+        if not agent_monitor:
+            # self.logger.warn("bot event service channel agentMonitor is null %s %s %s %s %s", saas_id, event_name, caller,
+            #                  called, json.dumps(event.serialize('json')))
+            return
+
+        # 信道发起事件,触发完成发起(或桥)&& 坐席侧
+        if CHANNEL_ORIGINATE == event_name and is_agent:
+            self.push_handler.push_on_call_ring(saas_id, flow_id, agent_num, AgentScene.ROBOT, call_id, ServiceDirect.ROBOT_CALL.service_direct, called, caller, human_service_id)
+            self.push_handler.push_on_agent_work_report(saas_id, flow_id, agent_num, call_id, AgentScene.ROBOT, WorkStatus.AGENT_RINGING)
+
+
+        if CHANNEL_ANSWER == event_name:
+            self.agent_state_service.busy(saas_id, agent.agent_num, agent.phone_num)
+            if is_agent:
+                self.agent_monitor_service.update_calling(agent_monitor)
+                self.push_handler.push_on_agent_work_report(saas_id, flow_id, agent_num, call_id, AgentScene.ROBOT, WorkStatus.AGENT_ANSWER_INCOMING, "座席接通呼入电话! internal")
+                self.push_handler.push_on_agent_report(saas_id, agent_num, AgentScene.ROBOT, AgentServiceState.CALLING)
+                self.push_handler.push_answer_call(saas_id, flow_id, agent_num, call_id, AgentScene.ROBOT, ServiceDirect.ROBOT_CALL.service_direct, WorkStatus.AGENT_HANG_REPROCESSING)
+
+                self.agent_actionlog_service.insert_service_state(agent_monitor, AgentServiceState.CALLING, AgentLogState.CHANNEL_TURN_ON, service_id=human_service_id)
+            else:
+                self.push_handler.push_on_agent_work_report(saas_id, flow_id, agent_num, call_id, AgentScene.ROBOT, WorkStatus.AGENT_ANSWER_INCOMING, "座席接通呼入电话! external")
+
+        if CHANNEL_HANGUP == event_name and is_agent:
+            self.agent_monitor_service.update_processing(agent_monitor)
+            self.push_handler.push_on_call_end(saas_id, flow_id, agent_num, AgentScene.ROBOT, ServiceDirect.ROBOT_CALL.service_direct, "0")
+            self.push_handler.push_on_agent_work_report(saas_id, flow_id, agent_num, call_id, AgentScene.ROBOT, WorkStatus.AGENT_HANG_REPROCESSING)
+            self.push_handler.push_on_agent_report(saas_id, agent_num, AgentScene.ROBOT, AgentServiceState.REPROCESSING)
+
+            self.agent_actionlog_service.insert_service_state(agent_monitor, AgentServiceState.REPROCESSING,
+                                                  AgentLogState.CHANNEL_HANG_UP, service_id=human_service_id)
+
+
+    def reprocessing_idle(self, state_data: AgentDelayStateData):
+        agent = self.data_handle_server.get_agent(state_data.saas_id, state_data.agent_num)
+        if not agent:
+            return
+        agent_monitor = self.data_handle_server.get_agent_monitor(state_data.saas_id, state_data.agent_num)
+        if not agent_monitor:
+            return
+        self.agent_monitor_service.update_idle(agent_monitor)
+        self.push_handler.push_on_agent_work_report(state_data.saas_id, state_data.flow_id, state_data.agent_num, "", state_data.scene, WorkStatus.AGENT_HANG_IDLE)
+        self.agent_actionlog_service.insert_service_state(agent_monitor, AgentServiceState.IDLE, AgentLogState.REPROCESSING_IDLE)
 
 
 class AgentOperService:
 
-    def __init__(self, client, logger):
-        self.inbound_client = client
-        self.logger = logger
-        self.push_handler = PushHandler(logger)
-        self.agent_monitor_service = AgentMonitorService(client, logger)
-        self.agent_actionlog_service = AgentActionLogService(client, logger)
-        self.agent_state_service = AgentStateService(client, logger)
+    def __init__(self, app):
+        self.app = app
+        self.logger = app.logger
+        self.push_handler = PushHandler(app.logger)
+        self.data_handle_server = DataHandleServer(app)
+        self.agent_monitor_service = AgentMonitorService(app)
+        self.agent_actionlog_service = AgentActionLogService(app)
+        self.agent_state_service = AgentStateService(app)
 
+    @with_app_context
     def enable(self, req: AgentActionRequest):
-        agent = _get_agent(req.saas_id, req.agent_number, req.out_id)
+        agent = self.data_handle_server.get_agent(req.saas_id, req.agent_number, req.out_id)
         if agent.agent_state == AgentState.ENABLE.code:
             return
         agent.agent_state = AgentState.ENABLE.code
         db.session.commit()
 
+    @with_app_context
     def disable(self, req: AgentActionRequest):
-        agent = _get_agent(req.saas_id, req.agent_id)
+        agent = self.data_handle_server.get_agent(req.saas_id, req.agent_id)
         if agent.agent_state == AgentState.DISABLE.code:
             return
-        agent_monitor = _get_agent_monitor(req.saas_id, agent.agent_number)
+        agent_monitor = self.data_handle_server.get_agent_monitor(req.saas_id, agent.agent_number)
         if agent_monitor.check_state == AgentCheck.IN.code and \
                 agent_monitor.service_state == AgentServiceState.CALLING.code:
             raise BizException(BizErrorCode.AGENT_CALLING_NOT_ALLOW_OPERATE)
@@ -49,36 +254,34 @@ class AgentOperService:
         agent.agent_state = AgentState.DISABLE.code
         db.session.commit()
 
-        phone = _get_phone(req.saas_id, agent.phone_num)
+        phone = self.data_handle_server.get_phone(req.saas_id, agent.phone_num)
         phone.is_delete = 1
         db.session.commit()
 
+    @with_app_context
     def checkin(self, req: AgentActionRequest):
-       try:
-           agent = _get_agent(req.saas_id, req.agent_id)
-           if not agent or agent.agent_state == AgentState.DISABLE.code:
-               raise BizException(BizErrorCode.ERROR_NOT_FOLLOW_CHECK_IN)
-           phone = _get_phone(req.saas_id, agent.phone_num)
-           agent_monitor = _get_agent_monitor(req.saas_id, agent.agent_num)
-           agent_monitor.check_scene = req.scene
-           self.agent_monitor_service.update_checkin(agent_monitor)
-           self.agent_actionlog_service.insert_check_state(agent_monitor, AgentCheck.IN, AgentLogState.CHECKIN)
-           self.agent_state_service.checkin(agent.saas_id, agent.out_id, agent.phone_num)
-
-           if req.scene == AgentScene.MANUAL.code:
-               # 如果是手动外呼增加置闲
-               self._handle_idle(req.scene, agent)
-           return self._push_event_for_checkin(agent, agent_monitor, phone, req.scene)
-       except Exception as e:
-           self.logger.error(traceback.format_exc())
-           raise e
-
+        agent = self.data_handle_server.get_agent(req.saas_id, req.agent_id)
+        if not agent or agent.agent_state == AgentState.DISABLE.code:
+            raise BizException(BizErrorCode.ERROR_NOT_FOLLOW_CHECK_IN)
+        phone = self.data_handle_server.get_phone(req.saas_id, agent.phone_num)
+        agent_monitor = self.data_handle_server.get_agent_monitor(req.saas_id, agent.agent_num)
+        agent_monitor.check_scene = req.scene
+        self.agent_monitor_service.update_checkin(agent_monitor)
+        self.agent_actionlog_service.insert_check_state(agent_monitor, AgentCheck.IN, AgentLogState.CHECKIN)
+        self.agent_state_service.checkin(agent.saas_id, agent.out_id, agent.phone_num)
+
+        # if req.scene == AgentScene.MANUAL.code:
+        #     # 如果是手动外呼增加置忙
+        #     self._handle_idle(req.scene, agent)
+        return self._push_event_for_checkin(agent, agent_monitor, phone, req.scene)
+
+    @with_app_context
     def checkout(self, req: AgentActionRequest):
-        agent = _get_agent(req.saas_id, req.agent_id)
+        agent = self.data_handle_server.get_agent(req.saas_id, req.agent_id)
         if not agent or agent.agent_state == AgentState.DISABLE.code:
             raise BizException(BizErrorCode.AGENT_DISABLE_NOT_ALLOW_OPERATE)
 
-        agent_monitor = _get_agent_monitor(req.saas_id, agent.agent_num)
+        agent_monitor = self.data_handle_server.get_agent_monitor(req.saas_id, agent.agent_num)
         if not agent_monitor or agent_monitor.service_state == AgentServiceState.CALLING.code:
             raise BizException(BizErrorCode.AGENT_CALLING_NOT_ALLOW_OPERATE)
 
@@ -91,12 +294,13 @@ class AgentOperService:
 
         return self._push_event_for_checkout(agent, req.scene)
 
+    @with_app_context
     def busy(self, req: AgentActionRequest):
-        agent = _get_agent(req.saas_id, req.agent_id)
+        agent = self.data_handle_server.get_agent(req.saas_id, req.agent_id)
         if not agent or agent.agent_state == AgentState.DISABLE.code:
             raise BizException(BizErrorCode.AGENT_DISABLE_NOT_ALLOW_OPERATE)
 
-        agent_monitor = _get_agent_monitor(req.saas_id, agent.agent_num)
+        agent_monitor = self.data_handle_server.get_agent_monitor(req.saas_id, agent.agent_num)
         if not agent_monitor or agent_monitor.check_state == AgentCheck.OUT.code:
             raise BizException(BizErrorCode.AGENT_CHECK_OUT_NOT_ALLOW_OPERATE)
 
@@ -112,25 +316,30 @@ class AgentOperService:
         self.agent_actionlog_service.insert_service_state(agent_monitor, AgentServiceState.BUSY, AgentLogState.BUSY)
         self._push_event_for_busy(agent, req.scene)
 
+    @with_app_context
     def idle(self, req: AgentActionRequest):
-        agent = _get_agent(req.saas_id, req.agent_id)
+        agent = self.data_handle_server.get_agent(req.saas_id, req.agent_id)
         if not agent or agent.agent_state == AgentState.DISABLE.code:
             raise BizException(BizErrorCode.AGENT_DISABLE_NOT_ALLOW_OPERATE)
         self._handle_idle(req.scene, agent)
 
+    @with_app_context
     def assign(self, req: AgentActionRequest):
         return self.agent_state_service.assign_agent(req.saas_id, req.service_id)
 
     def idle_agent_exist(self, request: AgentActionRequest):
         pass
+
+    @with_app_context
     def agent_state(self,req: AgentActionRequest):
         # agent = _get_agent(req.saas_id, req.agent_id)
-        agent_monitor = _get_agent_monitor(req.saas_id, req.agent_id)
+        agent_monitor = self.data_handle_server.get_agent_monitor(req.saas_id, req.agent_id)
         return agent_monitor.service_state
 
+    @with_app_context
     def turn_on(self, req: AgentActionRequest):
-        agent = _get_agent(req.saas_id, req.agent_id)
-        agent_monitor = _get_agent_monitor(req.saas_id, agent.agent_num)
+        agent = self.data_handle_server.get_agent(req.saas_id, req.agent_id)
+        agent_monitor = self.data_handle_server.get_agent_monitor(req.saas_id, agent.agent_num)
         agent_scene = AgentScene.get_by_code(req.scene)
         if not agent_monitor:
             raise BizException(BizErrorCode.RECORD_NOT_EXIST_ERROR)
@@ -143,8 +352,9 @@ class AgentOperService:
         self.agent_state_service.busy(agent.saas_id, agent.out_id, agent.phone_num)
         self.push_handler.push_on_agent_report(agent.saas_id, agent.out_id, agent_scene, AgentServiceState.BUSY)
 
+    @with_app_context
     def _handle_idle(self, scene, agent):
-        agent_monitor = _get_agent_monitor(agent.saas_id, agent.agent_num)
+        agent_monitor = self.data_handle_server.get_agent_monitor(agent.saas_id, agent.agent_num)
         if agent_monitor.check_state == AgentCheck.OUT.code:
             raise BizException(BizErrorCode.AGENT_CHECK_OUT_NOT_ALLOW_OPERATE)
         if agent_monitor.service_state == AgentServiceState.CALLING.code or agent_monitor.service_state == AgentServiceState.DIALING.code:
@@ -206,18 +416,21 @@ class AgentOperService:
 
 class AgentService:
 
-    def __init__(self, client, logger):
-        self.inbound_client = client
-        self.logger = logger
-        self.agent_monitor_service = AgentMonitorService(client, logger)
+    def __init__(self, app):
+        self.app = app
+        self.logger = app.logger
+        self.data_handle_server = DataHandleServer(app)
+        self.agent_monitor_service = AgentMonitorService(app)
 
+    @with_app_context
     def get_and_check(self, req: AgentActionRequest):
-        agent = _get_agent(req.saas_id, req.agent_id)
+        agent = self.data_handle_server.get_agent(req.saas_id, req.agent_id)
         if not agent:
             return {}
-        phone = _get_phone(req.saas_id, agent.phone_num)
+        phone = self.data_handle_server.get_phone(req.saas_id, agent.phone_num)
         return phone.to_dict()
 
+    @with_app_context
     def watch_agent_state(self, req: HumanServiceQueryRequest):
         pos = HumanServiceMap.query.filter(HumanServiceMap.is_delete == 0, HumanServiceMap.saas_id == req.saas_id,
                                            HumanServiceMap.service_id == req.serviceId).all()
@@ -225,8 +438,9 @@ class AgentService:
         monitors = self.agent_monitor_service.detail_monitor_out_ids(req.saas_id, agent_ids)
         return monitors
 
+    @with_app_context
     def add(self, req: AgentRequest):
-        new_agent_num = _get_newest_agent_number(req.saas_id)
+        new_agent_num = self.data_handle_server.get_newest_agent_number(req.saas_id)
         agent = Agent(saas_id=req.saas_id, agent_num=new_agent_num, agent_name=req.agent_name, out_id=req.out_id,
                       agent_pwd=req.agent_password, agent_type=req.agent_type, phone_num=req.phone_number,
                       distribute=req.distribute, agent_state=req.agent_state)
@@ -238,8 +452,9 @@ class AgentService:
         db.session.commit()
         return new_agent_num
 
+    @with_app_context
     def update(self, req: AgentRequest):
-        agent = _get_agent(req.saas_id, req.agent_number, req.out_id)
+        agent = self.data_handle_server.get_agent(req.saas_id, req.agent_number, req.out_id)
         if not agent:
             return
         phone_num = agent.phone_num
@@ -259,10 +474,12 @@ class AgentService:
             if phone:
                 db.session.delete(phone)
 
+    @with_app_context
     def detail(self, saas_id, agent_number):
-        agent = _get_agent(saas_id, agent_number=agent_number, out_id='')
+        agent = self.data_handle_server.get_agent(saas_id, agent_number=agent_number, out_id='')
         return agent
 
+    @with_app_context
     def count(self, req: AgentQueryRequest):
         cnt = Agent.query.filter(Agent.saas_id == req.saas_id,
                                  or_(Agent.agent_num == req.agent_number,
@@ -273,6 +490,7 @@ class AgentService:
                                  ).count()
         return cnt
 
+    @with_app_context
     def query_page(self, req: AgentQueryRequest):
         pagination = Agent.query.filter(Agent.saas_id == req.saas_id,
                                         or_(Agent.agent_num == req.agent_number,
@@ -298,27 +516,30 @@ class AgentService:
         # }
         return pagination
 
+    @with_app_context
     def delete(self, saas_id, agent_number):
-        agent = _get_agent(saas_id, agent_number=agent_number, out_id='')
+        agent = self.data_handle_server.get_agent(saas_id, agent_number=agent_number, out_id='')
         if not agent:
             return
         agent.is_delete = 1
 
-        agent_monitor = _get_agent_monitor(saas_id, agent_number)
+        agent_monitor = self.data_handle_server.get_agent_monitor(saas_id, agent_number)
         agent_monitor.is_delete = 1
         db.session.commit()
 
-        phone = _get_phone(saas_id, agent.phone_num)
+        phone = self.data_handle_server.get_phone(saas_id, agent.phone_num)
         phone.is_delete = 1
         db.session.commit()
 
 
 class AgentMonitorService:
 
-    def __init__(self, client, logger):
-        self.inbound_client = client
-        self.logger = logger
+    def __init__(self, app):
+        self.app = app
+        self.logger = app.logger
+        self.data_handle_server = DataHandleServer(app)
 
+    @with_app_context
     def detail_monitor_out_ids(self, saas_id, out_ids, check_scene=None):
         if not out_ids:
             return []
@@ -360,6 +581,7 @@ class AgentMonitorService:
             res.append(data.__dict__)
         return res
 
+    @with_app_context
     def update_checkin(self, agent_monitor):
         agent_monitor.check_state = AgentCheck.IN.code
         agent_monitor.check_in_time = datetime.utcnow()
@@ -367,6 +589,7 @@ class AgentMonitorService:
         agent_monitor.heart_time = datetime.utcnow()
         db.session.commit()
 
+    @with_app_context
     def update_checkout(self, agent_monitor):
         agent_monitor.check_state = AgentCheck.OUT.code
         agent_monitor.check_out_time = datetime.utcnow()
@@ -376,34 +599,41 @@ class AgentMonitorService:
         self.logger.info("update_checkout %s", agent_monitor.check_out_time)
         db.session.commit()
 
+    @with_app_context
     def update_idle(self, agent_monitor):
         agent_monitor.service_state = AgentServiceState.IDLE.code
         agent_monitor.idle_time = datetime.utcnow()
         db.session.commit()
 
+    @with_app_context
     def update_busy(self, agent_monitor):
         agent_monitor.service_state = AgentServiceState.BUSY.code
         agent_monitor.busy_time = datetime.utcnow()
         db.session.commit()
 
+    @with_app_context
     def update_dialing(self, agent_monitor):
         agent_monitor.service_state = AgentServiceState.DIALING.code
         db.session.commit()
 
+    @with_app_context
     def update_calling(self, agent_monitor):
         agent_monitor.service_state = AgentServiceState.CALLING.code
         agent_monitor.call_time = datetime.utcnow()
         db.session.commit()
 
+    @with_app_context
     def update_processing(self, agent_monitor):
         agent_monitor.service_state = AgentServiceState.REPROCESSING.code
         agent_monitor.hang_time = datetime.utcnow()
         db.session.commit()
 
+    @with_app_context
     def update_session_id(self, agent_monitor, session_id):
         agent_monitor.session_id = session_id
         db.session.commit()
 
+    @with_app_context
     def update_heart_error(self, agent_monitor):
         agent_monitor.heart_state = AgentHeartState.ABNORMAL.code
         agent_monitor.heart_time = datetime.utcnow()
@@ -416,10 +646,12 @@ class AgentMonitorService:
 
 class AgentActionLogService:
 
-    def __init__(self, client, logger):
-        self.inbound_client = client
-        self.logger = logger
+    def __init__(self, app):
+        self.app = app
+        self.logger = app.logger
+        self.data_handle_server = DataHandleServer(app)
 
+    @with_app_context
     def insert_check_state(self, agent_monitor, agent_check_enum: AgentCheck, agent_log_enum: AgentLogState):
         action_log = AgentActionLog()
         action_log.saas_id = agent_monitor.saas_id
@@ -445,6 +677,7 @@ class AgentActionLogService:
         db.session.add(action_log)
         db.session.commit()
 
+    @with_app_context
     def insert_service_state(self, agent_monitor, agent_service_state: AgentServiceState, agent_log_enum: AgentLogState,
                              task_id=None, service_id=None):
         if agent_monitor.service_state == agent_service_state.code:
@@ -485,35 +718,36 @@ class AgentActionLogService:
 
 class AgentStateService:
 
-    def __init__(self, client, logger):
-        self.inbound_client = client
-        self.logger = logger
+    def __init__(self, app):
+        self.app = app
+        self.logger = app.logger
         self.redis_handler = RedisHandler()
         self.assigned_recycle_millisecond = 60000
         self.state_service_id_data_map = defaultdict(dict)
         self.executor = ThreadPoolExecutor(max_workers=10)
-        self.agent_monitor_service = AgentMonitorService(client, logger)
-        self.agent_actionlog_service = AgentActionLogService(client, logger)
+        self.data_handle_server = DataHandleServer(app)
+        self.agent_monitor_service = AgentMonitorService(app)
+        self.agent_actionlog_service = AgentActionLogService(app)
 
     def idle(self, saas_id, agent_id, phone_num):
-        human_service = _get_human_service_service(saas_id, agent_id)
+        human_service = self.data_handle_server.get_human_service_service(saas_id, agent_id)
         if human_service is None:
             self.logger.info(f"agent engine idle not have human service {saas_id} {agent_id}")  # 使用print替代log
             return
         self.idle_hash(saas_id, agent_id, phone_num, human_service.service_id)
 
     def busy(self, saas_id, agent_id, phone_num):
-        human_service = _get_human_service_service(saas_id, agent_id)
+        human_service = self.data_handle_server.get_human_service_service(saas_id, agent_id)
         if human_service is None:
             self.logger.info(f"agent engine busy not hava human service {saas_id} {agent_id}")  # 使用print替代log
             return
         self.busy_hash(saas_id, agent_id, phone_num, human_service.service_id)
 
     def idle_by_human(self, saas_id, agent_id, service_id):
-        agent = _get_agent(saas_id, out_id=agent_id)
+        agent = self.data_handle_server.get_agent(saas_id, out_id=agent_id)
         if not agent:
             return
-        agent_monitor = _get_agent_monitor(saas_id, agent_number=agent.agent_num)
+        agent_monitor = self.data_handle_server.get_agent_monitor(saas_id, agent_number=agent.agent_num)
         if not agent_monitor:
             return
         if agent_monitor.check_state == AgentCheck.IN.code:
@@ -522,10 +756,10 @@ class AgentStateService:
             self.idle_hash(saas_id, agent_id, agent.phone_num, service_id)
 
     def busy_by_human(self, saas_id, service_id, agent_id=None):
-        agent = _get_agent(saas_id, out_id=agent_id)
+        agent = self.data_handle_server.get_agent(saas_id, out_id=agent_id)
         if not agent:
             return
-        agent_monitor = _get_agent_monitor(saas_id, agent_number=agent.agent_num)
+        agent_monitor = self.data_handle_server.get_agent_monitor(saas_id, agent_number=agent.agent_num)
         if not agent_monitor:
             return
         if agent_monitor.check_state == AgentCheck.IN.code:
@@ -697,34 +931,3 @@ class AgentStateService:
         idle_agents = sorted(idle_agents, key=lambda agent: agent.assign_time, reverse=False)
         return idle_agents[0].phone_num
 
-
-def _get_agent(saas_id, agent_id=None, agent_number=None, out_id=None):
-    agent = Agent.query.filter(
-        Agent.saas_id == saas_id,
-        or_(Agent.out_id == agent_id, Agent.out_id == out_id, Agent.agent_num == agent_number)
-    ).first()
-    return agent
-
-
-def _get_newest_agent_number(saas_id):
-    agent = Agent.query.filter(Agent.saas_id == saas_id).order_by(Agent.agent_num.desc()).first()
-    agentNum = START_AGENT_NUM
-    if agent and agent.agent_num:
-        agentNum = str(int(agent.agent_num) + 1)
-    return agentNum
-
-
-def _get_agent_monitor(saas_id, agent_number):
-    monitor = AgentMonitor.query.filter(AgentMonitor.saas_id == saas_id,
-                                        AgentMonitor.agent_num == agent_number).first()
-    return monitor
-
-
-def _get_phone(saas_id, phone_num):
-    phone = Phone.query.filter(Phone.saas_id == saas_id, Phone.phone_num == phone_num).first()
-    return phone
-
-
-def _get_human_service_service(saas_id, agent_id):
-    human_service_map = HumanServiceMap.query.filter(HumanServiceMap.saas_id == saas_id, HumanServiceMap.agent_id ==agent_id).first()
-    return human_service_map

+ 12 - 2
src/core/callcenter/api.py

@@ -176,15 +176,25 @@ class AgentStateData(BaseApi):
         self.assign_time = assign_time
         self.phone_num = phone_num
 
+class AgentDelayStateData(BaseApi):
+    def __init__(self, saas_id=None, flow_id=None, agent_num=None, service_state=None, scene=None):
+        self.saas_id = saas_id
+        self.flow_id = flow_id
+        self.agent_num = agent_num
+        self.service_state = service_state
+        self.scene = scene
 
 class HangupCallRequest(BaseApi):
-    def __init__(self, saas_id, call_id, agent_number):
+    def __init__(self, saas_id=None, flow_id=None, agent_id=None, called=None, call_id=None, scene=None):
         # saasId(必填)
         self.saas_id = saas_id
+        self.flow_id = flow_id
         # 呼叫唯一id(选填)
         self.call_id = call_id
+        self.called = called
         # 分机号(必填)
-        self.agent_number = agent_number
+        self.agent_id = agent_id
+        self.scene = scene
 
 
 class CheckInCallRequest(BaseApi):

+ 19 - 4
src/core/callcenter/cache.py

@@ -68,10 +68,9 @@ class Cache:
         self.redis_handler.set(CALL_INFO + call.call_id, call.to_json_string(), self.cacheDay * 24 * 60 * 60)
 
 
-    def get_call_info_by_device_id(self, device_id):
+    def get_call_id_by_device_id(self, device_id):
         call_id = self.deviceCall.get(device_id)
-        if not call_id:
-            return self.get_call_info(call_id)
+        return call_id
 
 
     # 获取callInfo
@@ -164,4 +163,20 @@ class Cache:
 
     def get_after_play_hold_music(self, call_id):
         key = AFTER_PLAY_HOLD_MUSIC % call_id
-        return self.redis_handler.redis.get(key)
+        return self.redis_handler.redis.get(key)
+
+    def get_call_is_end(self, call_id):
+        key = CTI_MANAGE_CENTER_CALL_END_KEY % call_id
+        return self.redis_handler.get(key)
+
+    def set_call_is_end(self, call_id):
+        key = CTI_MANAGE_CENTER_CALL_END_KEY % call_id
+        return self.redis_handler.redis.set(key, "1", ex=60 * 10, nx=True)
+
+    def get_call_is_answer(self, saas_id, flow_id):
+        key = CTI_AGENT_MANUAL_ANSWER%(saas_id, flow_id)
+        return self.redis_handler.get(key)
+
+    def set_call_is_answer(self, saas_id, flow_id):
+        key = CTI_AGENT_MANUAL_ANSWER%(saas_id, flow_id)
+        return self.redis_handler.redis.set(key, "1", ex=60, nx=True)

+ 47 - 30
src/core/callcenter/call.py

@@ -3,9 +3,12 @@
 import json
 import time
 from datetime import datetime
+
+from src.core.callcenter.agent import AgentMonitorService, AgentActionLogService
 from src.core.callcenter.cache import Cache
 from src.core.callcenter.constant import saasId, HOLD_MUSIC_PATH
-from src.core.callcenter.enumeration import CallCause, Direction, NextType, DeviceType, CdrType, AgentServiceState,AgentScene,WorkStatus
+from src.core.callcenter.enumeration import CallCause, Direction, NextType, DeviceType, CdrType, AgentServiceState, \
+    AgentScene, WorkStatus, AgentLogState, ServiceDirect
 from src.core.callcenter.api import AgentCallRequest, CallInfo, HangupCallRequest, CheckInCallRequest, \
     DeviceInfo, NextCommand, MakeCallContext
 from src.core.callcenter.esl.constant.sip_header_constant import sipHeaderServiceId, sipHeaderCtiFlowId
@@ -15,13 +18,16 @@ from src.core.callcenter.data_handler import *
 
 class CallService:
 
-    def __init__(self, client, logger):
+    def __init__(self, client, app):
         self.client = client
-        self.logger = logger
-        self.cache = Cache(client.app)
+        self.logger = app.logger
+        self.cache = Cache(app)
         self.snowflake = Snowflake()
-        self.dataHandleServer=DataHandleServer(client.app)
-        self.push_handler = PushHandler(logger)
+        self.push_handler = PushHandler(app.logger)
+        self.data_handle_server=DataHandleServer(app)
+        self.agent_monitor_service = AgentMonitorService(app)
+        self.agent_actionlog_service = AgentActionLogService(app)
+        # self.push_handler = PushHandler(logger)
 
     def call(self, request: AgentCallRequest):
         call_id = 'C' + str(self.snowflake.next_id())
@@ -34,9 +40,9 @@ class CallService:
         agent = self.cache.get_agent_info(request.saas_id, request.agent_id)
         route_gateway = self.cache.get_route_gateway(request.saas_id)
         call_info = CallInfo(cti_flow_id=request.cti_flow_id, 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=agent.agent_number, called=request.called, direction=Direction.OUTBOUND.code,
                              caller_display=request.caller_display, called_display=request.called_display,
-                             call_type=request.call_type.code, call_time=now, follow_data=request.follow_data,
+                             call_type=request.call_type, call_time=now, follow_data=request.follow_data,
                              uuid1=request.uuid1, uuid2=request.uuid2, saas_id=saasId,
                              core_uuid=None, conference=None, group_id=None, hidden_customer=0, number_location=None, agent_name=None, login_type=None, ivr_id=None, task_id=None, media_host=None, client_host=None, record=None, record2=None, record_time=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, first_queue_time=None, queue_start_time=None, queue_end_time=None, overflow_count=0, cdr_notify_url=None, queue_level=None, transfer_agent=None, device_list=[], device_info_map = {}, process_data = {}, next_commands=[], call_details=[])
         device_info = DeviceInfo(cti_flow_id=request.cti_flow_id, device_id=device_id, call_time=now, call_id=call_id, device_type=DeviceType.AGENT.code,
@@ -55,20 +61,26 @@ class CallService:
                                   sip_header_map={sipHeaderCtiFlowId: request.cti_flow_id})
 
         self.client.make_call_new(context)
-
-        # 创建一条通话记录
-        self.dataHandleServer.create_record({
-            "session_id": call_id,
-            "time_begin": datetime.utcnow(),
-            "category": 1,
-            "agent_num":request.agent_id,
-            "phone": request.called
-        })
-        # 变更坐席状态为拨号中
-        self.dataHandleServer.update_agent_monitor_service_state(request.agent_id, AgentServiceState.DIALING.code)
-        self.push_handler.push_on_agent_work_report(request.saas_id, request.cti_flow_id, request.agent_id, call_id,AgentScene.ROBOT, WorkStatus.AGENT_HANG_IDLE)
+        self.do_after_manual_call(call_info, agent.agent_number)
+        # # 创建一条通话记录
+        # self.dataHandleServer.create_record({
+        #     "session_id": call_id,
+        #     "time_begin": datetime.utcnow(),
+        #     "category": 1,
+        #     "agent_num":request.agent_id,
+        #     "phone": request.called
+        # })
+        # # 变更坐席状态为拨号中
+        # self.dataHandleServer.update_agent_monitor_service_state(request.agent_id, AgentServiceState.DIALING.code)
+        # self.push_handler.push_on_agent_work_report(request.saas_id, request.cti_flow_id, request.agent_id, call_id,AgentScene.ROBOT, WorkStatus.AGENT_DIALING)
         return call_id
 
+    def do_after_manual_call(self, call_info: CallInfo, agent_id):
+        agent_monitor = self.data_handle_server.get_agent_monitor(call_info.saas_id, agent_number=agent_id)
+        self.agent_monitor_service.update_dialing(agent_monitor)
+        self.push_handler.push_on_call_ring(call_info.saas_id, flow_id=call_info.cti_flow_id, user_id=agent_id, scene=AgentScene.MANUAL, call_id=call_info.call_id, service_direct=ServiceDirect.MANUAL_CALL.service_direct)
+        self.agent_actionlog_service.insert_service_state(agent_monitor, AgentServiceState.DIALING, AgentLogState.DIALING)
+
     def hold(self, call_info: CallInfo, device_id):
         devices = call_info.device_list
         try:
@@ -113,16 +125,21 @@ class CallService:
                                   call_type=call_info.call_type, service_id=service_id, sip_header_map=sip_header_map)
         self.client.make_call_new(context)
 
-    def hangup(self, request: HangupCallRequest):
-        call_info = self.cache.get_call_info(request.call_id)
-        if not call_info:
-            self.logger.info('hangup call not exist callId: %s', request.call_id)
-            return
-        devices = call_info.device_list
-        if not devices:
-            self.logger.info('hangup deviceList is null callId: %s', request.call_id)
-            return
-        self.hangup_all(call_info, CallCause.AGENT_HANGUP_CALL)
+    def hangup_by_scene(self, request: HangupCallRequest):
+        scene = AgentScene.get_by_code(request.scene)
+        if scene and AgentScene.MANUAL == scene:
+            self.do_manual_hang(request)
+        elif scene and not AgentScene.MANUAL == scene:
+            self.do_robot_hang(request)
+
+    def do_manual_hang(self, request: HangupCallRequest):
+        self.hangup_call(request.call_id)
+
+        agent_monitor = self.data_handle_server.get_agent_monitor(saas_id=request.saas_id, agent_number=request.agent_id)
+        self.agent_actionlog_service.insert_service_state(agent_monitor, AgentServiceState.HANGING, AgentLogState.MANUAL_HANG_UP)
+
+    def do_robot_hang(self, request: HangupCallRequest):
+        self.hangup_call(request.call_id)
 
     def hangup_all(self, call_info: CallInfo, case_enum=CallCause.DEFAULT):
         devices = call_info.device_list

+ 42 - 0
src/core/callcenter/callback.py

@@ -0,0 +1,42 @@
+#!/usr/bin/env python3
+# encoding:utf-8
+import threading
+
+import src.core.callcenter.esl.utils.esl_event_util as EslEventUtil
+from src.core.callcenter.agent import AgentEventService
+from src.core.callcenter.cache import Cache
+from src.core.callcenter.enumeration import CallType
+from src.core.callcenter.esl.constant.event_names import CUSTOM, DETECTED_TONE
+
+
+class Callback(object):
+
+    def __init__(self, app):
+        self.app = app
+        self.logger = app.logger
+        self.cache = Cache(app)
+        self.agent_event_service = AgentEventService(app)
+
+    def callback_event(self, event):
+        event_name = EslEventUtil.getEventName(event)
+        # CUSTOM == event_name or
+        if not (event_name.startswith('CHANNEL_') or event_name.startswith('PLAYBACK_') or event_name == DETECTED_TONE):
+            return
+        call_id = EslEventUtil.getCallId(event)
+        device_id = EslEventUtil.getDeviceId(event)
+        if not call_id and device_id:
+            call_id = self.cache.get_call_id_by_device_id(device_id)
+        call_info = self.cache.get_call_info(call_id)
+        if not call_info:
+            # self.logger.info("liuwei::debugger::callback:return::event_name=%s, call_id=%s, device_id=%s", event_name, call_id, device_id)
+            return
+
+        call_type = CallType.get_by_code(call_info.call_type) if call_info else None
+        device_info = call_info.device_info_map.get(device_id) if call_info and call_info.device_info_map else None
+        # self.logger.info("liuwei::debugger::callback::event_name=%s, call_type=%s, call_id=%s, device_id=%s, call_info=%s", event_name, call_type, call_id, device_id, call_info)
+        if CallType.BOT_CALL == call_type:
+            threading.Thread(target=self.agent_event_service.bot_event_channel, args=(event, call_info, device_info)).start()
+            # self.agent_event_service.bot_event_channel(event, call_info, device_info)
+        else:
+            threading.Thread(target=self.agent_event_service.agent_event_channel, args=(event, call_info, device_info)).start()
+            # self.agent_event_service.agent_event_channel(event, call_info, device_info)

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

@@ -86,6 +86,8 @@ CTI_ENGINE_DELAY_ACTION = "DELAY:ACTION:%s"
 CTI_ENGINE_DELAY_ACTION_LOCK = "DELAY:ACTION:LOCK:%s"
 NEED_PLAY_HOLD_MUSIC = "CTI:ENGINE:NEED:HOLD:%s"
 AFTER_PLAY_HOLD_MUSIC = "CTI:ENGINE:AFTER:HOLD:%s"
+CTI_MANAGE_CENTER_CALL_END_KEY = "CTI:MANAGE:CENTER:CALL:END:KEY:%s"
+CTI_AGENT_MANUAL_ANSWER = "AGENT:MANUAL:ANSWER:%s:%s"
 
 def get_json_dict(json_text=None):
     if isinstance(json_text, str):

+ 27 - 0
src/core/callcenter/dao.py

@@ -491,4 +491,31 @@ class CallRecord(db.Model):
             'create_time': self.create_time.isoformat() if self.create_time else None,
             'update_by': self.update_by,
             'update_time': self.update_time.isoformat() if self.update_time else None,
+        }
+
+class BotRecords(db.Model):
+    __tablename__ = 'botrecords'
+    __table_args__ = {'comment': '记录机器人会话信息'}
+
+    id = db.Column(db.Integer, primary_key=True, autoincrement=True, comment='主键')
+    session = db.Column(db.String(50), nullable=False, unique=True, comment='请求ID')
+    req_time = db.Column(db.DateTime, nullable=True, comment='来电时间')
+    uid = db.Column(db.String(20), nullable=True, comment='来电手机号')
+    bid = db.Column(db.String(20), nullable=True, comment='话术ID')
+    intent = db.Column(db.String(20), nullable=True, comment='意图')
+    contents = db.Column(db.Text, nullable=True, comment='内容')
+    dialog = db.Column(db.Text, nullable=True, comment='对话')
+    def __repr__(self):
+        return json.dumps(self.to_dict())
+
+    def to_dict(self):
+        return {
+            'id': self.id,
+            'session': self.session,
+            'req_time': self.req_time.isoformat() if self.req_time else None,
+            'uid': self.uid,
+            'bid': self.bid,
+            'intent': self.intent,
+            'contents': self.contents,
+            'dialog': self.dialog,
         }

+ 46 - 10
src/core/callcenter/data_handler.py

@@ -1,11 +1,7 @@
+from src.core import with_app_context
+from src.core.callcenter.constant import START_AGENT_NUM
 from src.core.callcenter.dao import *
-from functools import wraps
-def with_app_context(func):
-    @wraps(func)
-    def wrapper(self, *args, **kwargs):
-        with self.app.app_context():
-            return func(self, *args, **kwargs)
-    return wrapper
+from sqlalchemy import or_
 
 class DataHandleServer:
     """通话记录服务"""
@@ -46,7 +42,7 @@ class DataHandleServer:
 
     @with_app_context
     def update_record(self, session_id, call_info):
-        call_record = CallRecord.query.filter_by(session_id=session_id).first()
+        call_record = CallRecord.query.filter(CallRecord.session_id == session_id).first()
         # 动态更新字段
         for key, value in call_info.items():
             if hasattr(call_record, key):
@@ -55,15 +51,55 @@ class DataHandleServer:
 
     @with_app_context
     def get_user_name(self,agent_num):
-        agent = Agent.query.filter(agent_num == agent_num).first()
+        agent = Agent.query.filter(Agent.agent_num == agent_num).first()
         return agent
 
     @with_app_context
     def get_agent_phone(self, saas_id, agent_num):
         return Phone.query.filter(Phone.saas_id == saas_id, Phone.phone_num == agent_num).first()
 
+    @with_app_context
+    def get_agent(self,saas_id, agent_id=None, agent_number=None, out_id=None):
+        agent = Agent.query.filter(
+            Agent.saas_id == saas_id,
+            or_(Agent.out_id == agent_id, Agent.out_id == out_id, Agent.agent_num == agent_number)
+        ).first()
+        return agent
+
+    @with_app_context
+    def get_newest_agent_number(self,saas_id):
+        agent = Agent.query.filter(Agent.saas_id == saas_id).order_by(Agent.agent_num.desc()).first()
+        agentNum = START_AGENT_NUM
+        if agent and agent.agent_num:
+            agentNum = str(int(agent.agent_num) + 1)
+        return agentNum
+
+    @with_app_context
+    def get_agent_monitor(self,saas_id, agent_number):
+        monitor = AgentMonitor.query.filter(AgentMonitor.saas_id == saas_id,
+                                            AgentMonitor.agent_num == agent_number).first()
+        return monitor
+
+    @with_app_context
+    def get_phone(self,saas_id, phone_num):
+        phone = Phone.query.filter(Phone.saas_id == saas_id, Phone.phone_num == phone_num).first()
+        return phone
+
+    @with_app_context
+    def get_human_service_service(self,saas_id, agent_id):
+        human_service_map = HumanServiceMap.query.filter(HumanServiceMap.saas_id == saas_id,
+                                                         HumanServiceMap.agent_id == agent_id).first()
+        return human_service_map
+
     @with_app_context
     def update_agent_monitor_service_state(self, agent_num,service_state):
-        agent_monitor = AgentMonitor.query.filter(agent_num == agent_num).first()
+        agent_monitor = AgentMonitor.query.filter(AgentMonitor.agent_num == agent_num).first()
         agent_monitor.service_state = service_state
+        db.session.commit()
+
+    @with_app_context
+    def update_call_record_bussiness_type(self, session):
+        BotRecord = BotRecords.query.filter(BotRecords.session == session).first()
+        print("BotRecord",BotRecord.intent,session,flush=True)
+        self.update_record(session, {"bussiness_type": BotRecord.intent})
         db.session.commit()

+ 8 - 4
src/core/callcenter/esl/client.py

@@ -19,8 +19,8 @@ from apscheduler.schedulers.background import BackgroundScheduler
 from src.core.callcenter import BizException
 from src.core.callcenter.cache import Cache
 from src.core.callcenter.api import MakeCallContext, DelayAction, CallInfo, DeviceInfo, NextCommand
-from src.core.callcenter.constant import SK, EMPTY, CTI_ENGINE_DELAY_ACTION_LOCK, HOLD_MUSIC_PATH, saasId, \
-    WaitingHangupMusicPath
+from src.core.callcenter.callback import Callback
+from src.core.callcenter.constant import SK, EMPTY, CTI_ENGINE_DELAY_ACTION_LOCK, HOLD_MUSIC_PATH, WaitingHangupMusicPath, saasId
 from src.core.callcenter.esl.constant.esl_constant import BRIDGE_VARIABLES, BRIDGE, HANGUP, NORMAL_CLEARING, SIP_HEADER, \
     SPACE, SPLIT, SOFIA, \
     ORIGINATE, PARK, SET, EAVESDROP, SMF_ALEG, EXECUTE, PLAYBACK, PAUSE, TRANSFER, UUID_TRANSFER, UUID_BROADCAST, \
@@ -46,6 +46,8 @@ class InboundClient:
         self.logger = app.logger
         self.bot_agent = agent
         self.cache = Cache(app)
+        self.callback = Callback(app)
+        self.dataHandleServer = DataHandleServer(app)
         self.handler_table = self.scan_esl_event_handlers()
         self.default_event_handler = DefaultEslEventHandler(self, self.bot_agent)
         self.host, self.port, self.password = SERVE_HOST, '8021', '4918257983818884358'
@@ -121,6 +123,7 @@ class InboundClient:
         address = self.host + ':' + self.port
         # self.logger.info("process_esl_event.event_name=%s,coreUUID=%s", event_name, coreUUID)
         try:
+            self.callback.callback_event(e)
             if event_name in self.handler_table:
                 items = self.handler_table.get(event_name)
                 for x in items:
@@ -176,8 +179,7 @@ class InboundClient:
             #         self.cache.add_delay_message(DelayActionEnum.CALL_TIMEOUT_DECR, delay_action, timeouts=20)
             self.cache.add_call_info(call_info)
             self.hangup_call(call_id, device_id, CallCause.CALL_TIMEOUT)
-            print("人工外呼,呼叫超时,用户未接通会走这", flush=True)
-
+            self.dataHandleServer.update_record(call_id, {"status": 0})
     def exec_when_play_timeout(self, call_id):
         call_info = self.cache.get_call_info(call_id)
         if not call_info or not call_info.next_commands:
@@ -209,6 +211,7 @@ class InboundClient:
                              device_id, WaitingHangupMusicPath)
 
     def make_call_new(self, context: MakeCallContext):
+        # self.logger.info("拨打测试context:%s", context.__dict__)
         called = context.get_called()
         params = {'gateway': context.route_gateway_name, 'called': called, 'realm': context.get_realm()}
 
@@ -222,6 +225,7 @@ class InboundClient:
         else:
             profile = self.expression(profile2, params)
             builder.append(f"{profile}{PARK}")
+            # self.logger.info("拨打测试builder:%s", builder)
         cmd = "".join(builder)
         self.logger.info(cmd)
         self.con.bgapi(ORIGINATE, cmd)

+ 16 - 6
src/core/callcenter/esl/handler/channel_answer_handler.py

@@ -3,9 +3,9 @@
 import json
 import time
 from datetime import datetime
-from src.core.callcenter.constant import saasId, get_record_prefix, get_record_file_name
-from src.core.callcenter.enumeration import NextType, AnswerFlag, Direction, DeviceType, AgentScene, CdrType, CallType, \
-    CallStage
+from src.core.callcenter.constant import saasId, get_record_prefix,get_record_file_name
+from src.core.callcenter.enumeration import NextType, AnswerFlag, Direction, DeviceType, AgentScene, CdrType, \
+    WorkStatus, AgentServiceState, CallStage, CallType
 from src.core.callcenter.esl.annotation import EslEventName
 import src.core.callcenter.esl.utils.esl_event_util as EslEventUtil
 from src.core.callcenter.esl.constant.event_names import CHANNEL_ANSWER
@@ -28,7 +28,7 @@ class ChannelAnswerHandler(EslEventHandler):
     def handle(self, address, event, coreUUID):
         call_id = EslEventUtil.getCallId(event)
         call_info = self.cache.get_call_info(call_id)
-        # self.logger.info("lwanswer call_id:%s, call_info:%s, event:%s"% (call_id, call_info, json.loads(event.serialize('json'))))
+        self.logger.info("liuwei::debugger::answer call_id:%s, call_info:%s", call_id, call_info)
         if not call_info:
             return
 
@@ -36,18 +36,19 @@ class ChannelAnswerHandler(EslEventHandler):
         device_info = call_info.device_info_map.get(device_id)
         next_command = call_info.next_commands[0] if len(call_info.next_commands) > 0 else None
         device_type = DeviceType.get_by_code(device_info.device_type)
-        self.logger.info("lwChannelAnswerHandler call_id:%s, device_id:%s, device_type:%s, next_command:%s"%(call_id, device_id, device_type, next_command))
+        self.logger.info("liuwei::debugger::ChannelAnswerHandler call_id:%s, device_id:%s, device_type:%s, next_command:%s"%(call_id, device_id, device_type, next_command))
         if not next_command:
             return
 
         if CallType.AGENT_CALL.code == call_info.call_type and device_info.device_type == DeviceType.CUSTOMER.code:
             self.record(event, device_id)
-            self.push_handler.push_on_ring_start(saas_id=call_info.saas_id, flow_id=call_info.cti_flow_id, user_id=call_info.agent_key, scene=AgentScene.MANUAL, call_id=call_info.call_id)
 
         device_info.answer_time = EslEventUtil.getEventDateTimestamp(event)
         device_info.ring_end_time = EslEventUtil.getEventDateTimestamp(event)
         call_info.answer_count = call_info.answer_count + 1
         call_info.next_commands.remove(next_command)
+        self.logger.info("liuwei::debugger::ChannelAnswerHandler call_info.answer_time::%s,time:%s", call_info.answer_time, EslEventUtil.getEventDateTimestamp(event))
+
 
         if NextType.NEXT_CALL_OTHER.code == next_command.next_type:
             self.call_other(call_info, device_info, event)
@@ -59,8 +60,17 @@ class ChannelAnswerHandler(EslEventHandler):
             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)
+
+        # if device_info.device_type == DeviceType.AGENT.code:  # 如果是坐席接听 变更坐席状态
+        #     call_info.answer_time = EslEventUtil.getEventDateTimestamp(event)
+        #     self.dataHandleServer.update_record(call_id, {"status": 1})
+        #     self.push_handler.push_on_agent_work_report(call_info.saas_id, call_info.cti_flow_id, call_info.agent_key, call_info.call_id, AgentScene.ROBOT,WorkStatus.AGENT_ANSWER_INCOMING)
+        #     self.dataHandleServer.update_agent_monitor_service_state(call_info.agent_key,AgentServiceState.CALLING.code)
+        #     self.logger.info("坐席接听 event:%s" % (json.loads(event.serialize('json'))))
+
         self.cache.add_call_info(call_info)
 
+
     def call_other(self, call: CallInfo, device: DeviceInfo, event):
         call_id = call.call_id
         device_id = device.device_id

+ 17 - 16
src/core/callcenter/esl/handler/channel_bridge_handler.py

@@ -13,21 +13,22 @@ class ChannelBridgeHandler(EslEventHandler):
 
     def __init__(self, inbound_client, bot_agent):
         super().__init__(inbound_client, bot_agent)
-        self.dataHandleServer = DataHandleServer(inbound_client.app)
-        self.push_handler = PushHandler(inbound_client.logger)
+        # self.dataHandleServer = DataHandleServer(inbound_client.app)
+        # self.push_handler = PushHandler(inbound_client.logger)
 
     def handle(self, address, event, coreUUID):
-        call_id = EslEventUtil.getCallId(event)
-        device_id = EslEventUtil.getDeviceId(event)
-        call = self.cache.get_call_info(call_id)
-        device = call.device_info_map.get(device_id)
-        if not call.answer_time:
-            call.answer_time = EslEventUtil.getEventDateTimestamp(event)
-            self.cache.add_call_info(call)
-
-        self.logger.info("bridge call_info.answer_time:%s,device_info.answer_time%s,device.device_type%s" % (call.answer_time, device.answer_time,device.device_type))
-        if device.device_type == DeviceType.AGENT.code: # 如果是坐席接听 变更坐席状态
-            self.dataHandleServer.update_agent_monitor_service_state(call.agent_key, AgentServiceState.CALLING.code)
-            self.push_handler.push_on_agent_work_report(call.saas_id, call.cti_flow_id, call.agent_key, call.call_id, AgentScene.ROBOT, WorkStatus.AGENT_ANSWER_INCOMING)
-
-
+        pass
+        # call_id = EslEventUtil.getCallId(event)
+        # device_id = EslEventUtil.getDeviceId(event)
+        # call_info = self.cache.get_call_info(call_id)
+        # device = call_info.device_info_map.get(device_id)
+        # # 每通电话的第一次写入应答时间
+        # if not call_info.answer_time:
+        #     call_info.answer_time = EslEventUtil.getEventDateTimestamp(event)
+        #     self.cache.add_call_info(call_info)
+        #     self.dataHandleServer.update_record(call_id, {"status": 1})
+        #     self.logger.info("bridge call_info.answer_time:%s,device_info.answer_time%s,device.device_type%s" % (call_info.answer_time, device.answer_time,device.device_type))
+        # if device.device_type == DeviceType.AGENT.code: # 如果是坐席接听 变更坐席状态
+        #     # self.dataHandleServer.update_record(call_id, {"status": 1})
+        #     self.push_handler.push_on_agent_work_report(call_info.saas_id, call_info.cti_flow_id, call_info.agent_key, call_info.call_id, AgentScene.ROBOT,WorkStatus.AGENT_ANSWER_INCOMING)
+        #     self.dataHandleServer.update_agent_monitor_service_state(call_info.agent_key,AgentServiceState.CALLING.code)

+ 16 - 14
src/core/callcenter/esl/handler/channel_hangup_handler.py

@@ -23,7 +23,7 @@ class ChannelHangupHandler(EslEventHandler):
     def __init__(self, inbound_client, bot_agent):
         super().__init__(inbound_client, bot_agent)
         self.acd_service = AcdService(inbound_client,inbound_client.app)
-        self.call_service = CallService(inbound_client,inbound_client.logger)
+        self.call_service = CallService(inbound_client,inbound_client.app)
         self.push_handler = PushHandler(inbound_client.logger)
         self.dataHandleServer=DataHandleServer(inbound_client.app)
 
@@ -107,24 +107,26 @@ class ChannelHangupHandler(EslEventHandler):
                 # self.inbound_client.hangup_call(call_id, device_id, CallCause.HANGUP_EVENT)
 
             # 全部挂机以后推送挂机状态
-            self.logger.info('yushanghui::call_info.device_list %s', call_info.device_list)
-            if len(call_info.device_list) == 0:
-                # 计算当前通话时长
-                if call_info.answer_time:
-                    call_info.end_time = timestamp
-                    call_info.talk_time = int(call_info.end_time) - int(call_info.answer_time)
-                    self.dataHandleServer.update_record(call_id, {"time_end": datetime.utcnow(), "times": call_info.talk_time // 1000})
+            # self.logger.info('yushanghui::call_info.device_list %s', call_info.device_list)
+            # if len(call_info.device_list) == 0:
+            #     self.push_handler.push_on_call_end(call_info.cti_flow_id, call_info.agent_key, AgentScene.ROBOT, call_info.direction, device_info.device_type)
+            #     self.push_handler.push_on_agent_work_report(call_info.saas_id, call_info.cti_flow_id,  call_info.agent_key, call_info.call_id, AgentScene.ROBOT, WorkStatus.AGENT_HANG_IDLE)
+            #     # 计算当前通话时长
+            #     if call_info.answer_time:
+            #         call_info.end_time = timestamp
+            #         call_info.talk_time = int(call_info.end_time) - int(call_info.answer_time)
+            #         self.dataHandleServer.update_record(call_id, {"time_end": datetime.utcnow(), "times": int(call_info.talk_time / 1_000_000) })
+            #
+            #     self.logger.info('全部挂断 %s', device_info.device_type)
+            #     # 更新坐席状态
+            #     if device_info.device_type == DeviceType.AGENT.code:
+            #         self.logger.info('更新坐席状态')
+            #         self.dataHandleServer.update_agent_monitor_service_state(call_info.agent_key, AgentServiceState.IDLE.code)
 
             # 判断挂机方向 && 更新缓存
             self.hangup_dir(call_info, device_info, cause)
             self.cache.add_call_info(call_info)
 
-
-            # 更新坐席状态
-            if device_info.device_type == DeviceType.AGENT.code:
-                  self.push_handler.push_on_call_end(call_info.cti_flow_id, call_info.agent_key, AgentScene.ROBOT, call_info.direction, device_info.device_type)
-                  self.push_handler.push_on_agent_work_report(call_info.saas_id, call_info.cti_flow_id, call_info.agent_key,call_info.call_id, AgentScene.ROBOT,WorkStatus.AGENT_HANG_IDLE)
-                  self.dataHandleServer.update_agent_monitor_service_state(device_id, AgentServiceState.IDLE.code)
         except:
             traceback.print_exc()
 

+ 10 - 9
src/core/callcenter/esl/handler/channel_originate_handler.py

@@ -1,7 +1,7 @@
 #!/usr/bin/env python3
 # encoding:utf-8
 from src.core.callcenter.esl.annotation import EslEventName
-from src.core.callcenter.enumeration import  DeviceType, AgentServiceState,AgentScene,WorkStatus
+from src.core.callcenter.enumeration import  DeviceType,AgentScene,WorkStatus,Direction
 from src.core.callcenter.esl.constant.event_names import CHANNEL_ORIGINATE
 from src.core.callcenter.esl.handler.esl_event_handler import EslEventHandler
 import src.core.callcenter.esl.utils.esl_event_util as EslEventUtil
@@ -15,11 +15,12 @@ class ChannelOriginateHandler(EslEventHandler):
         self.push_handler = PushHandler(inbound_client.logger)
 
     def handle(self, address, event, coreUUID):
-        call_id = EslEventUtil.getCallId(event)
-        device_id = EslEventUtil.getDeviceId(event)
-        call = self.cache.get_call_info(call_id)
-        device = call.device_info_map.get(device_id)
-        print('ceshisdsdsdsdsdsdsd',device.device_type,DeviceType.AGENT.code,flush=True)
-        self.logger.info('ChannelOriginateHandler::event %s, device.device_type: %s, DeviceType.AGENT.code:%s ', event,device.device_type, DeviceType.AGENT.code)
-        if device.device_type == DeviceType.AGENT.code: # 如果是坐席接听 变更坐席状态
-            self.push_handler.push_on_agent_work_report(call.saas_id, call.cti_flow_id,call.agent_key,call.call_id,AgentScene.ROBOT, WorkStatus.AGENT_RINGING)
+        pass
+        # call_id = EslEventUtil.getCallId(event)
+        # device_id = EslEventUtil.getDeviceId(event)
+        # call = self.cache.get_call_info(call_id)
+        # device = call.device_info_map.get(device_id)
+        # self.logger.info('ChannelOriginateHandler::event %s, device.device_type: %s,call.direction:%s ', event,device.device_type, call.direction)
+        # if device.device_type == DeviceType.AGENT.code: # 如果是呼入有响铃
+        #     self.push_handler.push_on_call_ring(call.cti_flow_id,call.agent_key,AgentScene.ROBOT,call.call_id,call.caller, call.called,"00000000000000000")
+        #     self.push_handler.push_on_agent_work_report(call.saas_id, call.cti_flow_id,call.agent_key,call.call_id,AgentScene.ROBOT, WorkStatus.AGENT_RINGING,phone=call.caller)

+ 11 - 1
src/core/callcenter/esl/handler/channel_progress_media_handler.py

@@ -2,15 +2,25 @@
 # encoding:utf-8
 
 from src.core.callcenter.esl.annotation import EslEventName
+from src.core.callcenter.enumeration import  DeviceType,AgentScene,WorkStatus, Direction
 from src.core.callcenter.esl.constant.event_names import CHANNEL_PROGRESS_MEDIA
 from src.core.callcenter.esl.handler.esl_event_handler import EslEventHandler
-
+import src.core.callcenter.esl.utils.esl_event_util as EslEventUtil
+from src.core.callcenter.push import PushHandler
 
 @EslEventName(CHANNEL_PROGRESS_MEDIA)
 class ChannelProgressMediaHandler(EslEventHandler):
 
     def __init__(self, inbound_client, bot_agent):
         super().__init__(inbound_client, bot_agent)
+        self.push_handler = PushHandler(inbound_client.logger)
 
     def handle(self, address, event, coreUUID):
         pass
+        # call_id = EslEventUtil.getCallId(event)
+        # device_id = EslEventUtil.getDeviceId(event)
+        # call_info = self.cache.get_call_info(call_id)
+        # device = call_info.device_info_map.get(device_id)
+        # self.logger.info('ChannelProgressMediaHandler:: device.device_type: %s,  call_info.direction: %s ',device.device_type, call_info.direction)
+        # if call_info.direction == Direction.OUTBOUND.code and device.device_type == DeviceType.AGENT.code:
+        #     self.push_handler.push_on_agent_work_report(call_info.saas_id, call_info.cti_flow_id, call_info.agent_key, call_info.call_id, AgentScene.MANUAL, WorkStatus.AGENT_CALLING_RINGING)

+ 10 - 1
src/core/callcenter/esl/handler/detected_tone_handler.py

@@ -3,15 +3,24 @@
 # encoding:utf-8
 
 from src.core.callcenter.esl.annotation import EslEventName
+from src.core.callcenter.enumeration import  DeviceType,AgentScene
 from src.core.callcenter.esl.constant.event_names import DETECTED_TONE
 from src.core.callcenter.esl.handler.esl_event_handler import EslEventHandler
-
+import src.core.callcenter.esl.utils.esl_event_util as EslEventUtil
+from src.core.callcenter.push import PushHandler
 
 @EslEventName(DETECTED_TONE)
 class DetectedToneHandler(EslEventHandler):
 
     def __init__(self, inbound_client, bot_agent):
         super().__init__(inbound_client, bot_agent)
+        self.push_handler = PushHandler(inbound_client.logger)
 
     def handle(self, address, event, coreUUID):
         pass
+        # call_id = EslEventUtil.getCallId(event)
+        # device_id = EslEventUtil.getDeviceId(event)
+        # call = self.cache.get_call_info(call_id)
+        # device = call.device_info_map.get(device_id)
+        # if call.answer_time and device.device_type == DeviceType.AGENT.code:
+        #     self.push_handler.push_on_detected_tone(call.cti_flow_id, call.agent_key, AgentScene.MANUAL,call.call_id)

+ 26 - 0
src/core/callcenter/esl/handler/playback_start_handler.py

@@ -0,0 +1,26 @@
+#!/usr/bin/env python3
+# encoding:utf-8
+from src.core.callcenter.esl.annotation import EslEventName
+from src.core.callcenter.enumeration import  DeviceType,AgentScene
+from src.core.callcenter.esl.constant.event_names import PLAYBACK_START
+from src.core.callcenter.esl.handler.esl_event_handler import EslEventHandler
+import src.core.callcenter.esl.utils.esl_event_util as EslEventUtil
+from src.core.callcenter.push import PushHandler
+from src.core.callcenter.data_handler import *
+
+@EslEventName(PLAYBACK_START)
+class PlaybackStartHandler(EslEventHandler):
+
+    def __init__(self, inbound_client, bot_agent):
+        super().__init__(inbound_client, bot_agent)
+        self.dataHandleServer = DataHandleServer(inbound_client.app)
+        self.push_handler = PushHandler(inbound_client.logger)
+
+    def handle(self, address, event, coreUUID):
+        pass
+        # call_id = EslEventUtil.getCallId(event)
+        # device_id = EslEventUtil.getDeviceId(event)
+        # call = self.cache.get_call_info(call_id)
+        # device = call.device_info_map.get(device_id)
+        # if device.device_type == DeviceType.AGENT.code:
+        #     self.push_handler.push_on_ring_start(call.cti_flow_id, call.agent_key, AgentScene.MANUAL, call.call_id)

+ 5 - 1
src/core/callcenter/esl/handler/playback_stop_handler.py

@@ -3,10 +3,12 @@
 
 import src.core.callcenter.esl.utils.esl_event_util as EslEventUtil
 from src.core.callcenter.constant import HOLD_MUSIC_PATH
+from src.core.callcenter.data_handler import *
 from src.core.callcenter.enumeration import NextType, CallCause
 from src.core.callcenter.esl.annotation import EslEventName
 from src.core.callcenter.esl.constant.event_names import PLAYBACK_STOP
 from src.core.callcenter.esl.handler.esl_event_handler import EslEventHandler
+from src.core.callcenter.push import PushHandler
 
 
 @EslEventName(PLAYBACK_STOP)
@@ -14,6 +16,8 @@ class PlaybackStopHandler(EslEventHandler):
 
     def __init__(self, inbound_client, bot_agent):
         super().__init__(inbound_client, bot_agent)
+        self.dataHandleServer = DataHandleServer(inbound_client.app)
+        self.push_handler = PushHandler(inbound_client.logger)
 
     def handle(self, address, event, coreUUID):
         call_id = EslEventUtil.getCallId(event)
@@ -40,4 +44,4 @@ class PlaybackStopHandler(EslEventHandler):
             call_info.end_time = device_info.end_time
             for _device_id in call_info.device_list:
                 self.inbound_client.hangup_call(call_id, _device_id, CallCause.PLAYBACK_STOP)
-        self.cache.add_call_info(call_info)
+        self.cache.add_call_info(call_info)

+ 27 - 10
src/core/callcenter/push.py

@@ -8,6 +8,7 @@ from src.core.datasource import RedisHandler
 class PushHandler:
     def __init__(self, logger):
         self.logger = logger
+
     def push_to_socket_service(self,user_id, data, event='common_down_data'):
         # 创建发布的消息
         message = json.dumps({
@@ -18,17 +19,19 @@ class PushHandler:
         # 获取 RedisHandler 实例并发布消息到 Redis 频道
         redis_handler = RedisHandler()
         redis_handler.publish('socket_channel', message)
-    def push_on_agent_work_report(self, saas_id, flow_id, user_id, call_id, scene: AgentScene, work_status: WorkStatus, description=None):
+
+    def push_on_agent_work_report(self, saas_id, flow_id, user_id, call_id, scene: AgentScene, work_status: WorkStatus, description=None, phone=None):
         data = {
             'eventName': DownEvent.ON_AGENT_WORK_REPORT.code,
             'ext': {'workStatus': work_status.code,
                     'description': description or work_status.description,
                     'callId': call_id,
                     'ctiFlowId': flow_id,
-                    'scene': scene.code
+                    'scene': scene.code,
+                    'phone':phone
                     }
         }
-        self.logger.info("flowId:[%s] OnAgentWorkReport push:[%s].", flow_id, json.dumps(data))
+        self.logger.info("flowId:[%s] push_on_agent_work_report push:[%s].", flow_id, json.dumps(data))
         new_data = {'data': json.dumps(data)}
         self.push_to_socket_service(user_id, json.dumps(new_data))
     def push_on_agent_report(self, saas_id, out_id, scene: AgentScene, service_state: AgentServiceState):
@@ -48,7 +51,7 @@ class PushHandler:
         new_data = {'data': json.dumps(data)}
         self.push_to_socket_service(user_id, json.dumps(new_data))
 
-    def push_on_call_ring(self, saas_id, flow_id, user_id,scene:AgentScene, call_id,  calling_no, called_no, human_service_id):
+    def push_on_call_ring(self, saas_id, flow_id, user_id,scene:AgentScene, call_id, service_direct, calling_no=None, called_no=None, human_service_id=None):
         data = {
             'eventName': DownEvent.ON_CALLRING.code,
             'ext': {
@@ -57,14 +60,15 @@ class PushHandler:
                 'scene': scene.code,
                 'callingNo': calling_no,
                 'calledNo': called_no,
+                'serviceDirect': service_direct,
                 'serviceTaskId': human_service_id
             }
         }
-        self.logger.info("flowId:[%s] OnAgentWorkReport push:[%s].", flow_id, json.dumps(data))
+        self.logger.info("flowId:[%s] push_on_call_ring push:[%s].", flow_id, json.dumps(data))
         new_data = {'data': json.dumps(data)}
         self.push_to_socket_service(user_id, json.dumps(new_data))
 
-    def push_on_call_end(self, flow_id, user_id,  scene: AgentScene, service_direct=None, disconnect_type=0):
+    def push_on_call_end(self, saas_id, flow_id, user_id,  scene: AgentScene, service_direct=None, disconnect_type=0):
         data = {
             'eventName': DownEvent.ON_CALL_END.code,
             'ext': {
@@ -74,7 +78,7 @@ class PushHandler:
                 'serviceDirect': service_direct
             }
         }
-        self.logger.info("flowId:[%s] OnAgentWorkReport push:[%s].", flow_id, json.dumps(data))
+        self.logger.info("flowId:[%s] push_on_call_end push:[%s].", flow_id, json.dumps(data))
         new_data = {'data': json.dumps(data)}
         self.push_to_socket_service(user_id, json.dumps(new_data))
 
@@ -104,7 +108,7 @@ class PushHandler:
         new_data = {'data': json.dumps(data)}
         self.push_to_socket_service(user_id, json.dumps(new_data))
 
-    def push_answer_call(self, saas_id,flow_id, out_id, call_id, scene: AgentScene, service_direct,work_status,user_id ):
+    def push_answer_call(self, saas_id,flow_id, user_id, call_id, scene: AgentScene, service_direct,work_status):
         data = {
             'eventName': DownEvent.ANSWER_CALL.code,
             'ext': {
@@ -112,9 +116,22 @@ class PushHandler:
                 'scene': scene.code,
                 'callId': call_id,
                 'serviceDirect':service_direct,
-                'workStatus':work_status
+                'workStatus':work_status.code
             }
         }
-        self.logger.info("flowId:[%s] push_on_ring_end push:[%s].", flow_id, json.dumps(data))
+        self.logger.info("flowId:[%s] push_answer_call push:[%s].", flow_id, json.dumps(data))
+        new_data = {'data': json.dumps(data)}
+        self.push_to_socket_service(user_id, json.dumps(new_data))
+
+    def push_on_detected_tone(self, saas_id, flow_id, user_id, scene: AgentScene, call_id):
+        data = {
+            'eventName': DownEvent.ON_DETECTED_TONE.code,
+            'ext': {
+                'ctiFlowId': flow_id,
+                'scene': scene.code,
+                'callId': call_id,
+            }
+        }
+        self.logger.info("flowId:[%s] push_on_detected_tone push:[%s].", flow_id, json.dumps(data))
         new_data = {'data': json.dumps(data)}
         self.push_to_socket_service(user_id, json.dumps(new_data))

+ 38 - 72
src/core/callcenter/views.py

@@ -16,9 +16,9 @@ from .acd import AcdService
 agent = BotAgent(app)
 inbound_client = InboundClient(agent,app)
 outbound_client = OutboundClient(agent,app)
-call_service = CallService(inbound_client, app.logger)
-agent_service = AgentService(inbound_client, app.logger)
-agent_oper_service = AgentOperService(inbound_client, app.logger)
+call_service = CallService(inbound_client, app)
+agent_service = AgentService(app)
+agent_oper_service = AgentOperService(app)
 acd_service = AcdService(inbound_client, app)
 agent.acd_service = acd_service
 
@@ -59,108 +59,74 @@ def get_cdn_url():
 @app.route('/open/agent/get-init-config', methods=['POST'])
 def get_init_config():
     """获取初始化配置"""
-    try:
-        data = request.get_json()
-        param = AgentActionRequest.from_json(data)
-        res = agent_service.get_and_check(param)
-        return success_response(res)
-
-    except Exception as e:
-        print("Exception occurred: %s", str(e))
-        return error_response(e)
+    data = request.get_json()
+    param = AgentActionRequest.from_json(data)
+    res = agent_service.get_and_check(param)
+    return success_response(res)
 
 
 @app.route('/open/agent/check-in', methods=['POST'])
 def check_in():
     """坐席签入"""
-    try:
-        data = request.get_json()
-        param = AgentActionRequest.from_json(data)
-        res = agent_oper_service.checkin(param)
-        return success_response(res)
-    except Exception as e:
-        print("Exception occurred: %s", str(e))
-        return error_response(e)
+    data = request.get_json()
+    param = AgentActionRequest.from_json(data)
+    res = agent_oper_service.checkin(param)
+    return success_response(res)
 
 
 @app.route('/open/agent/check-out', methods=['POST'])
 def check_out():
     """坐席签出"""
-    try:
-        data = request.get_json()
-        param = AgentActionRequest.from_json(data)
-        res= agent_oper_service.checkout(param)
-        return success_response(res)
-    except Exception as e:
-        print("Exception occurred: %s", str(e))
-        return error_response(e)
+    data = request.get_json()
+    param = AgentActionRequest.from_json(data)
+    res = agent_oper_service.checkout(param)
+    return success_response(res)
 
 
 @app.route('/open/agent/busy', methods=['POST'])
 def busy():
     """坐席置忙"""
-    try:
-        data = request.get_json()
-        param = AgentActionRequest.from_json(data)
-        res= agent_oper_service.busy(param)
-        return success_response(res)
-    except Exception as e:
-        print("Exception occurred: %s", str(e))
-        return error_response(e)
+    data = request.get_json()
+    param = AgentActionRequest.from_json(data)
+    res = agent_oper_service.busy(param)
+    return success_response(res)
 
 
 @app.route('/open/agent/idle', methods=['POST'])
 def idle():
     """坐席置闲"""
-    try:
-        data = request.get_json()
-        param = AgentActionRequest.from_json(data)
-        res = agent_oper_service.idle(param)
-        return success_response(res)
-    except Exception as e:
-        print("Exception occurred: %s", str(e))
-        return {"error": "An error occurred", "details": str(e)}, 500
+    data = request.get_json()
+    param = AgentActionRequest.from_json(data)
+    res = agent_oper_service.idle(param)
+    return success_response(res)
 
 
 @app.route('/open/agent/turn-on', methods=['POST'])
 def turn_on():
     """接通"""
-    try:
-        data = request.get_json()
-        param = AgentActionRequest.from_json(data)
-        return agent_oper_service.checkin(param)
-    except Exception as e:
-        print("Exception occurred: %s", str(e))
-        return {"error": "An error occurred", "details": str(e)}, 500
+    data = request.get_json()
+    param = AgentActionRequest.from_json(data)
+    return agent_oper_service.checkin(param)
 
 
 @app.route('/open/agent/hang-up', methods=['POST'])
 def hang_up():
     """挂断"""
-    try:
-        data = request.get_json()
-        param = AgentActionRequest.from_json(data)
-        res = call_service.hangup(param)
-        return success_response(res)
-    except Exception as e:
-        print("Exception occurred: %s", str(e))
-        return {"error": "An error occurred", "details": str(e)}, 500
-
+    data = request.get_json()
+    param = AgentActionRequest.from_json(data)
+    res = call_service.hangup(param)
+    return success_response(res)
 
 @app.route('/open/agent/agent-state', methods=['POST'])
 def agent_state():
     """获取坐席状态"""
-    try:
-        data = request.get_json()
-        # param = HumanServiceQueryRequest.from_json(data)
-        # res = agent_service.watch_agent_state(param)
-        param = AgentActionRequest.from_json(data)
-        res = agent_oper_service.agent_state(param)
+    data = request.get_json()
+    # param = HumanServiceQueryRequest.from_json(data)
+    # res = agent_service.watch_agent_state(param)
+    param = AgentActionRequest.from_json(data)
+    res = agent_oper_service.agent_state(param)
 
-        return success_response(res)
-    except Exception as e:
-        print("Exception occurred: %s", str(e))
-        return {"error": "An error occurred", "details": str(e)}, 500
+    return success_response(res)
 
 
 @app.route('/open/agent/manual-call', methods=['POST'])
@@ -188,8 +154,8 @@ def manual_hang():
     """挂断"""
     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=data.get('agent_id'))
-    call_service.hangup(req)
+    req = HangupCallRequest(saas_id=data.get('saas_id'), flow_id=data.get('ctiFlowId'), agent_id=data.get('agent_id'), called=data.get('called'), call_id=data.get('call_id'), scene=data.get('scene'))
+    call_service.hangup_by_scene(req)
     return success_response()
 
 

+ 27 - 8
src/core/voip/asr.py

@@ -50,11 +50,12 @@ class TestSt:
     def get_cached_token(cls):
         # 检查是否已有缓存的Token且未过期s):
         #         # 检查是否已有缓存的Token且未
-        if cls.token_cache["token"] and cls.token_cache["expire_time"]:
-            current_time = int(time.time())
-            if current_time < cls.token_cache["expire_time"]:
-                print("使用缓存的Token")
-                return cls.token_cache["token"]
+        current_time = int(time.time())
+        # if cls.token_cache["token"] and cls.token_cache["expire_time"]:
+        if cls.token_cache["token"] and cls.token_cache["expire_time"] - current_time > 60:
+            # if current_time < cls.token_cache["expire_time"]:
+            #     print("使用缓存的Token")
+            return cls.token_cache["token"]
 
         # 如果没有缓存Token或者Token已过期,重新获取
         new_token, expire_time = get_token()
@@ -68,6 +69,9 @@ class TestSt:
             return None
 
     def __init__(self, tid, message_receiver=None):
+        self.is_closed = False
+        self.lock = threading.Lock()
+
         self.__th = threading.Thread(target=self.__test_run)
         self.__id = tid
         self.message_receiver = message_receiver
@@ -79,10 +83,25 @@ class TestSt:
         self.__th.start()
 
     def send_audio(self, audio_data):
-        if self.sr:
-            # print("Sending audio data of length:", len(audio_data))
-            self.sr.send_audio(audio_data)
+        # if self.sr:
+        #     # print("Sending audio data of length:", len(audio_data))
+        #     self.sr.send_audio(audio_data)
             # print("Audio data sent.")
+        if self.sr and not self.is_closed:
+            with self.lock:
+                try:
+                    self.sr.send_audio(audio_data)
+                except Exception as e:
+                    print(f"Error sending audio: {e}")
+                    self.close()
+    def close(self):
+        with self.lock:
+            if not self.is_closed:
+                self.is_closed = True
+                try:
+                    self.sr.stop()
+                except Exception as e:
+                    print(f"Error stopping ASR: {e}")
 
     def __test_run(self):
         print("Thread:{} start..".format(self.__id))

+ 7 - 0
src/core/voip/bot.py

@@ -21,6 +21,8 @@ from src.core.callcenter.api import BotChatRequest,ChatMessage
 from src.core import singleton_keys
 from src.core.callcenter.snowflake import Snowflake
 
+from src.core.callcenter.data_handler import *
+
 calls = {}
 # recording_file = '/code/src/core/voip/incoming_call.wav'
 # player_file1 = '/code/src/core/voip/test111.wav'
@@ -211,6 +213,8 @@ class MyCall(pj.Call):
         self.txtLock = False
         self.inputLongStart = time.time()    #长按键开始时间
 
+
+
     def wait_time_check(self, current_time, wait_time):
         try:
             # 确保 wait_time 是整数类型
@@ -362,6 +366,8 @@ class MyCall(pj.Call):
         elif action_code == 'transfer':  # 转人工
             print('todo 转人工')
             self.agent.transfer(user_part=self.user_part, call_id=self.session_id, device_id=self.device_id)
+            #更新通话记录机器人意图
+            self.agent.dataHandleServer.update_call_record_bussiness_type(self.session_id)
 
 class ToTextBotAgent:
     def __init__(self, user_asr_text, call_agent):
@@ -490,6 +496,7 @@ class BotAgent:
         self.is_stopping = False
         self.acd_service = None
         self.cache = Cache(app)
+        self.dataHandleServer = DataHandleServer(app)
         threading.Thread(target=self.create_pjsua2, daemon=True).start()
 
     def create_pjsua2(self):

Some files were not shown because too many files changed in this diff