Quellcode durchsuchen

坐席sdk接口开发,fix

刘威 vor 4 Monaten
Ursprung
Commit
8f7a115234

+ 7 - 4
src/core/callcenter/__init__.py

@@ -9,13 +9,16 @@ db = SQLAlchemy()
 
 
 def create_app():
-    app = Flask(__name__)
+    _app = Flask(__name__)
     # 添加配置文件
-    app.config.from_object(BaseConfig)
+    _app.config.from_object(BaseConfig)
 
-    db.init_app(app)
+    db.init_app(_app)
 
     # with app.app_context():
     #     from . import views  # Import routes
 
-    return app
+    return _app
+
+
+app = create_app()

+ 227 - 5
src/core/callcenter/agent.py

@@ -1,10 +1,15 @@
 #!/usr/bin/env python3
 # encoding:utf-8
 from src.core.callcenter.constant import START_AGENT_NUM
-from src.core.callcenter.enumeration import AgentState, AgentCheck, AgentHeartState, AgentServiceState, AgentLogState
+from src.core.callcenter.enumeration import AgentState, AgentCheck, AgentHeartState, AgentServiceState, AgentLogState, \
+    AgentScene, BizErrorCode, WorkStatus, DownEvent, HumanState
 from sqlalchemy import or_
 from src.core.callcenter.dao import *
-from src.core.callcenter.model import AgentActionRequest, AgentInfo, AgentQueryRequest, AgentRequest
+from src.core.callcenter.exception import BizException
+from src.core.callcenter.model import AgentActionRequest, AgentInfo, AgentQueryRequest, AgentRequest, AgentEventData, \
+    AgentStateData, HumanServiceQueryRequest, AgentMonitorData
+from src.core.callcenter.push import PushHandler
+from src.core.datasource import RedisHandler
 
 
 class AgentOperService:
@@ -12,8 +17,10 @@ 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 enable(self, req: AgentActionRequest):
         agent = _get_agent(req.saas_id, req.agent_number, req.out_id)
@@ -26,6 +33,11 @@ class AgentOperService:
         agent = _get_agent(req.saas_id, req.agent_number, req.out_id)
         if agent.agent_state == AgentState.DISABLE.code:
             return
+        agent_monitor = _get_agent_monitor(req.saas_id, req.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)
+
         agent.phone_num = ''
         agent.agent_state = AgentState.DISABLE.code
         db.session.commit()
@@ -36,7 +48,8 @@ class AgentOperService:
 
     def checkin(self, req: AgentActionRequest):
         agent = _get_agent(req.saas_id, req.agent_number, req.out_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, req.agent_number)
@@ -44,9 +57,13 @@ class AgentOperService:
         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 agent.to_dict()
+        return self._push_event_for_checkin(agent, agent_monitor, phone, req.scene)
 
     def checkout(self, request: AgentActionRequest):
         pass
@@ -66,12 +83,54 @@ class AgentOperService:
     def turn_on(self, request: AgentActionRequest):
         pass
 
+    def _handle_idle(self, scene, agent):
+        agent_monitor = _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:
+            raise BizException(BizErrorCode.AGENT_CALLING_NOT_HANG)
+
+        if scene == AgentScene.ROBOT.code:
+            self.agent_state_service.idle(agent.saas_id, agent.out_id, agent.phone_num)
+        else:
+            self.agent_state_service.busy(agent.saas_id, agent.out_id, agent.phone_num)
+
+        if agent_monitor.service_state == AgentServiceState.IDLE.code:
+            self._push_event_for_idle(agent, scene)
+            return
+
+        self.agent_monitor_service.update_idle(agent_monitor)
+        self.agent_actionlog_service.insert_service_state(agent_monitor, AgentServiceState.IDLE, AgentLogState.IDLE)
+        self._push_event_for_idle(agent, scene)
+
+    def _push_event_for_checkin(self, agent, agent_monitor, phone, scene):
+        """坐席签入事件推送"""
+        agent_scene = AgentScene.get_by_code(scene)
+        self.push_handler.push_on_agent_work_report(agent.saas_id, "", agent.out_id, agent_scene, WorkStatus.LOGIN_SUCCESS)
+
+        event_data = AgentEventData(agent.saas_id, agent.out_id)
+        event_data.data = {'eventName': DownEvent.ON_INITAL_SUCCESS.code,
+                           'ext': {'saasId': agent.saas_id,
+                                   'agentId': agent.out_id,
+                                   'phoneNum': phone.phone_num,
+                                   'phonePwd': phone.phone_pwd,
+                                   'sipServer': phone.sip_server
+                                   }
+                           }
+        return event_data
+
+    def _push_event_for_idle(self, agent, scene):
+        """坐席置闲事件推送"""
+        agent_scene = AgentScene.get_by_code(scene)
+        self.push_handler.push_on_agent_work_report(agent.saas_id, "", agent.out_id, agent_scene, WorkStatus.AGENT_READY)
+
 
 class AgentService:
 
     def __init__(self, client, logger):
         self.inbound_client = client
         self.logger = logger
+        self.agent_monitor_service = AgentMonitorService(client, logger)
 
     def get_and_check(self, req: AgentActionRequest):
         agent = _get_agent(req.saas_id, req.agent_number, req.out_id)
@@ -80,6 +139,13 @@ class AgentService:
         phone = _get_phone(req.saas_id, agent.phone_num)
         return phone.to_dict()
 
+    def watch_agent_state(self, req: HumanServiceQueryRequest):
+        pos = HumanServiceMap.query.filter(HumanServiceMap.is_delete == 0, HumanServiceMap.saas_id == req.saasId,
+                                           HumanServiceMap.service_id == req.serviceId).all()
+        agent_ids = [x.agent_id for x in pos]
+        monitors = self.agent_monitor_service.detail_monitor_out_ids(req.saasId, agent_ids)
+        return monitors
+
     def add(self, req: AgentRequest):
         new_agent_num = self._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,
@@ -174,6 +240,47 @@ class AgentMonitorService:
         self.inbound_client = client
         self.logger = logger
 
+    def detail_monitor_out_ids(self, saas_id, out_ids, check_scene=None):
+        if not out_ids:
+            return []
+        agents = Agent.query.filter(Agent.is_delete == 0, Agent.saas_id == saas_id, Agent.out_id.in_(out_ids)).all()
+        if not agents:
+            raise BizException(BizErrorCode.RECORD_NOT_EXIST_ERROR)
+
+        agent_num_map = {x.agent_num: x for x in agents}
+        agent_nums = [x.agent_num for x in agents]
+        agent_monitors = AgentMonitor.query.filter(AgentMonitor.is_delete == 0, AgentMonitor.agent_num.in_(agent_nums)).all()
+        res = []
+        for agent_monitor in agent_monitors:
+            agent = agent_num_map.get(agent_monitor.agent_num)
+            data = AgentMonitorData()
+            data.saas_id = saas_id
+            data.agent_num = agent_monitor.agent_num
+            data.agent_name = agent.agent_name
+            data.out_id = agent_monitor.out_id
+            data.check_state = agent_monitor.check_state
+            data.check_scene = agent_monitor.check_scene
+            data.online_state = 1 if agent_monitor.check_state == AgentCheck.IN.code else 0
+
+            if agent_monitor.check_in_time:
+                data.check_in_time = agent_monitor.check_in_time.timestamp()
+                day_start = self._get_day_start()
+                if agent_monitor.check_in_time.timestamp() > day_start and \
+                        agent_monitor.check_state == AgentCheck.OUT.code:
+                    data.online_state = 2
+
+            data.check_out_time = agent_monitor.check_out_time.timestamp() if agent_monitor.check_out_time else None
+            data.service_state = agent_monitor.service_state
+            data.busy_time = agent_monitor.busy_time.timestamp() if agent_monitor.busy_time else None
+            data.idle_time = agent_monitor.idle_time.timestamp() if agent_monitor.idle_time else None
+            data.call_time = agent_monitor.call_time.timestamp() if agent_monitor.call_time else None
+            data.hang_time = agent_monitor.hang_time.timestamp() if agent_monitor.hang_time else None
+            data.heart_state = agent_monitor.heart_state
+            data.heart_time = agent_monitor.heart_time.timestamp() if agent_monitor.heart_time else None
+            data.update_time = agent_monitor.update_time.timestamp() if agent_monitor.update_time else None
+            res.append(data)
+        return res
+
     def update_check_in(self, agent_monitor):
         agent_monitor.check_state = AgentCheck.IN.code
         agent_monitor.check_in_time = datetime.utcnow
@@ -222,6 +329,10 @@ class AgentMonitorService:
         agent_monitor.heart_time = datetime.utcnow
         db.session.commit()
 
+    def _get_day_start(self):
+        today_start = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
+        return int(today_start.timestamp() * 1000)  # Return milliseconds
+
 
 class AgentActionLogService:
 
@@ -233,11 +344,122 @@ class AgentActionLogService:
         action_log = AgentActionLog()
         action_log.saas_id = agent_monitor.saas_id
         action_log.agent_num = agent_monitor.agent_num
+        action_log.out_id = agent_monitor.out_id
+        action_log.action_type = 0
+        action_log.check_state = agent_check_enum.code
+        action_log.pre_check_state = agent_monitor.check_state
+        if agent_log_enum:
+            action_log.event_type = agent_log_enum.code
+            action_log.event_desc = agent_log_enum.description
+        now = datetime.utcnow
+        pre_date = None
+        if agent_monitor.check_state == AgentCheck.IN.code:
+            pre_date = agent_monitor.check_in_time
+        if agent_monitor.check_state == AgentCheck.OUT.code:
+            pre_date = agent_monitor.check_out_time
+        if pre_date is None:
+            pre_date = agent_monitor.update_time
+        action_log.check_state_time = now
+        action_log.pre_check_state_time = pre_date
+        action_log.check_state_duration = now.timestamp() - pre_date.timestamp()
+        db.session.add(action_log)
+        db.session.commit()
+
+    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:
+            self.logger.info("agent action log insert service state same %s %s", agent_monitor.service_state, agent_service_state.getValue())
+
+        action_log = AgentActionLog()
+        action_log.saas_id = agent_monitor.saas_id
+        action_log.agent_num = agent_monitor.agent_num
+        action_log.out_id = agent_monitor.out_id
+        action_log.action_type = 1
+        action_log.service_state = agent_service_state.code
+        action_log.pre_service_state = agent_monitor.service_state
+        action_log.task_id = task_id
+        action_log.service_id = service_id
+        if agent_log_enum:
+            action_log.event_type = agent_log_enum.code
+            action_log.event_desc = agent_log_enum.description
+        now = datetime.utcnow
+        pre_date = None
+        if agent_monitor.service_state == AgentServiceState.IDLE.code:
+            pre_date = agent_monitor.idle_time
+        if agent_monitor.service_state == AgentServiceState.BUSY.code:
+            pre_date = agent_monitor.busy_time
+        if agent_monitor.service_state == AgentServiceState.CALLING.code:
+            pre_date = agent_monitor.call_time
+        if agent_monitor.service_state == AgentServiceState.LOGOUT.code:
+            pre_date = agent_monitor.check_out_time
+        if agent_monitor.service_state == AgentServiceState.REPROCESSING.code:
+            pre_date = agent_monitor.hang_time
+        action_log.service_state_time = now
+        if pre_date is None:
+            pre_date = agent_monitor.update_time
+        action_log.pre_service_state_time = pre_date
+        action_log.service_state_duration = now.timestamp() - pre_date.timestamp()
+        db.session.add(action_log)
+        db.session.commit()
 
 
-    def insert_service_state(self, agent_monitor, agent_check_enum: AgentServiceState, agent_log_enum: AgentLogState):
+class AgentStateService:
+
+    def __init__(self, client, logger):
+        self.inbound_client = client
+        self.logger = logger
+        self.redis_handler = RedisHandler()
+    
+    def idle(self, saas_id, agent_id, phone_num):
+        pass
+
+    def busy(self, saas_id, agent_id, phone_num):
+        pass
+
+    def idle_by_human(self, saas_id, agent_id, service_id):
+        pass
+
+    def busy_by_human(self, saas_id, service_id, agent_id=None):
+        pass
+
+    def checkin(self, saas_id, agent_id, phone_num):
+        key = self._check_in_key(saas_id)
+        state_data = AgentStateData()
+        state_data.status = HumanState.DEFAULT.code
+        state_data.time = datetime.utcnow.timestamp()
+        self.redis_handler.redis.hset(key, phone_num, json.dumps(state_data))
+        self.redis_handler.redis.expire(key, self._get_expire_time())
+
+    def checkout(self, saas_id, agent_id, phone_num):
+        pass
+
+    def assign_agent(self, saas_id, service_id, ivr_id, task_id, called, cbp):
         pass
 
+    def idle_agents(self, saas_id, service_id):
+        pass
+
+    def idle_hash(self, saas_id, agent_id, phone_num, service_id):
+        pass
+
+    def busy_hash(self, saas_id, agent_id, phone_num, service_id):
+        pass
+
+    def get_agent_service_idle_size(self, saas_id, service_id):
+        pass
+
+    def get_agent_service_busy_size(self, saas_id, service_id):
+        pass
+
+    def _check_in_key(self, saas_id):
+        return "CTI:%s:HUMAN:AGENT".format(saas_id.upper())
+
+    def _get_expire_time(self):
+        now = datetime.now()
+        end_of_day = now.replace(hour=23, minute=59, second=59, microsecond=0)
+        expire_time = (end_of_day - now).total_seconds() * 1000  # Convert to milliseconds
+        return int(expire_time)
+
 
 def _get_agent(saas_id, agent_number, out_id):
     agent = Agent.query.filter(

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

@@ -79,11 +79,16 @@ CALL_INFO = "CALL_INFO:"
 START_AGENT_NUM = "1000"
 
 
-def success(data=""):
-    response = json.dumps({"code": 0, "data": 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"}
 
 
+def error_response(msg, data=None, code=1, http_code=200):
+    response = json.dumps({"code": code, "msg": msg, "data": data})
+    return response, http_code, {"Content-Type": "application/json"}
+
+
 def format_time_millis(time_millis, pattern='%Y%m%d'):
     from datetime import datetime
     dt = datetime.utcfromtimestamp(time_millis)

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

@@ -271,3 +271,97 @@ class Phone(db.Model):
             'update_time': self.update_time.isoformat() if self.update_time else None,
             'create_time': self.create_time.isoformat() if self.create_time else None,
         }
+
+
+class HumanService(db.Model):
+    __tablename__ = 'c_human_service'
+    __table_args__ = {'comment': '人工服务组信息'}
+
+    id = db.Column(db.BigInteger, primary_key=True, autoincrement=True, comment='主键')
+    saas_id = db.Column(db.String(16), nullable=False, default='', comment='租户隔离')
+    service_id = db.Column(db.String(32), nullable=False, default='', comment='人工组编号')
+    service_name = db.Column(db.String(32), nullable=False, default='', comment='人工组名称')
+    schedule_key = db.Column(db.String(32), nullable=False, default='', comment='日程编号')
+    effective = db.Column(db.String(32), nullable=False, default='0', comment='状态 0:启用(可以或正在进行外呼)1:暂停')
+    service_start = db.Column(db.String(32), nullable=False, default='', comment='任务开始时间 yyyyMMddHHmmss')
+    service_end = db.Column(db.String(32), nullable=False, default='22200101000000', comment='任务结束时间 yyyyMMddHHmmss')
+    distribute = db.Column(db.String(32), nullable=False, default='3', comment='分配策略')
+    call_in_limit = db.Column(db.String(32), nullable=False, default='0', comment='呼入线数 限制 固定值0')
+    record_type = db.Column(db.String(32), nullable=False, default='1', comment='录音方式 默认0 0:服务器不录 1:服务器录 4:按间隔抽样录 5:按百分比抽样录 固定值 1')
+    queue_length = db.Column(db.String(32), nullable=False, default='100', comment='排队长度 固定值 100')
+    queue_warning = db.Column(db.String(32), nullable=False, default='100', comment='排队告警 长度 固定值 100')
+    queue_timeout = db.Column(db.String(32), nullable=False, default='60', comment='排队超时 时间 固定值 60')
+    sum_queue_time = db.Column(db.String(32), nullable=False, default='90', comment='所有排队 时间  单位:秒;默认300: 固定值 90')
+    connect_type = db.Column(db.String(32), nullable=False, default='0', comment='连接坐席 类型 默认0 0盲转连接 1桥接连接并超时重排 固定值 0')
+    queue_music = db.Column(db.String(32), nullable=False, default='', comment='排队音 全局配置')
+    direct_queue_type = db.Column(db.String(32), nullable=False, default='', comment='直接连接 媒体类型 默认1 接入码直接转人工服务能听到排队音 0:直接排队不连接媒体; 1:直接排队连接媒体 全局配置')
+    is_delete = db.Column(db.Boolean, nullable=False, default=False, comment='删除标识')
+    update_time = db.Column(db.TIMESTAMP, nullable=False, default=datetime.utcnow, onupdate=datetime.utcnow, comment='更新时间')
+    create_time = db.Column(db.TIMESTAMP, nullable=False, default=datetime.utcnow, comment='创建时间')
+
+    # Indexes
+    __table_args__ = (
+        db.Index('idx_create_time', 'create_time'),
+        db.Index('idx_update_time', 'update_time'),
+        db.Index('idx_saas_id_service_id', 'saas_id', 'service_id'),
+    )
+
+    def to_dict(self):
+        return {
+            'id': self.id,
+            'saas_id': self.saas_id,
+            'service_id': self.service_id,
+            'service_name': self.service_name,
+            'schedule_key': self.schedule_key,
+            'effective': self.effective,
+            'service_start': self.service_start,
+            'service_end': self.service_end,
+            'distribute': self.distribute,
+            'call_in_limit': self.call_in_limit,
+            'record_type': self.record_type,
+            'queue_length': self.queue_length,
+            'queue_warning': self.queue_warning,
+            'queue_timeout': self.queue_timeout,
+            'sum_queue_time': self.sum_queue_time,
+            'connect_type': self.connect_type,
+            'queue_music': self.queue_music,
+            'direct_queue_type': self.direct_queue_type,
+            'is_delete': self.is_delete,
+            'update_time': self.update_time.isoformat() if self.update_time else None,
+            'create_time': self.create_time.isoformat() if self.create_time else None,
+        }
+
+
+class HumanServiceMap(db.Model):
+    __tablename__ = 'c_human_service_map'
+    __table_args__ = {'comment': '人工服务组与坐席关系'}
+
+    id = db.Column(db.BigInteger, primary_key=True, autoincrement=True, comment='主键')
+    saas_id = db.Column(db.String(16), nullable=False, default='', comment='租户隔离')
+    service_id = db.Column(db.String(32), nullable=False, default='', comment='人工组编号')
+    service_type = db.Column(db.String(32), nullable=False, default='0', comment='服务类型')
+    agent_id = db.Column(db.String(32), nullable=False, default='', comment='坐席工号')
+    state = db.Column(db.Boolean, nullable=False, default=0, comment='状态 1激活(签入)')
+    is_delete = db.Column(db.Boolean, nullable=False, default=0, comment='删除标识')
+    update_time = db.Column(db.TIMESTAMP, nullable=False, default=datetime.utcnow, onupdate=datetime.utcnow, comment='更新时间')
+    create_time = db.Column(db.TIMESTAMP, nullable=False, default=datetime.utcnow, comment='创建时间')
+
+    # Indexes
+    __table_args__ = (
+        db.Index('idx_create_time', 'create_time'),
+        db.Index('idx_update_time', 'update_time'),
+        db.Index('idx_saas_id_service_id_agent_id', 'saas_id', 'service_id', 'agent_id'),
+    )
+
+    def to_dict(self):
+        return {
+            'id': self.id,
+            'saas_id': self.vcc_id,
+            'service_id': self.service_id,
+            'service_type': self.service_type,
+            'agent_id': self.agent_id,
+            'state': self.state,
+            'is_delete': self.is_delete,
+            'update_time': self.update_time.isoformat() if self.update_time else None,
+            'create_time': self.create_time.isoformat() if self.create_time else None,
+        }

+ 75 - 0
src/core/callcenter/enumeration.py

@@ -89,6 +89,22 @@ class AgentLogState(Enum):
         self.description = description
 
 
+class AgentScene(Enum):
+    MANUAL = (1, "manual", "手动外呼")
+    ROBOT = (2, "robot", "机器人外呼")
+    MONITOR = (3, "monitor", "监听")
+    WECHAT = (4, "wechat", "微信语音")
+
+    def __init__(self, idx, code, description):
+        self.idx = idx
+        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 WorkStatus(Enum):
     NO_INIT = (-1, "没有初始化")
     LOGIN_SUCCESS = (0, "登录CTI 成功")
@@ -309,3 +325,62 @@ class HangupDir:
     def __init__(self, code=None, description=None):
         self.code = code
         self.description = description
+
+
+class BizErrorCode(Enum):
+    PARAM_ERROR = (10001, "参数错误")
+    RECORD_NOT_EXIST_ERROR = (10002, "记录不存在")
+    OUT_ID_EXIST = (10003, "外部Id已经被占用")
+    SYSTEM_ERROR = (10004, "系统错误")
+    NOT_ALLOW_ERROR = (10005, "不允许此操作")
+
+    PHONE_NUM_USED = (20003, "分机号已经被占用")
+    AGENT_CHECK_IN_DELETE_ERROR = (20004, "签入状态坐席不允许当前删除")
+    AGENT_DISABLE_NOT_ALLOW_OPERATE = (20005, "禁用状态坐席不允许当前操作")
+    AGENT_CALLING_NOT_ALLOW_OPERATE = (20006, "通话中坐席不允许当前操作")
+    AGENT_CHECK_OUT_NOT_ALLOW_OPERATE = (20007, "签出状态坐席不允许当前操作")
+    AGENT_ALREADY_ANOTHER_GROUP = (20008, "坐席已经在其他组")
+    AGENT_GET_PHONE_ERROR = (20009, "获取分机号错误")
+    AGENT_CALL_ERROR = (20010, "当前状态不允许外呼")
+    AGENT_LISTEN_ERROR = (20011, "当前状态不允许监听")
+    AGENT_NOT_EXIST = (20010, "坐席不存在或已删除")
+    AGENT_CALLING_NOT_HANG = (20012, "正在通话中,请先挂断再进行当前操作")
+    AGENT_GROUP_HAS_AGENT = (20103, "坐席组有坐席不能删除")
+    AGENT_GROUP_AGENT_CHECK_IN = (20104, "签入状态的坐席不允许从组内删除")
+    CIRCUIT_CAN_NOT_DELETE_ERROR = (20501, "线路存在正在执行/等待执行中的任务")
+    CALENDARS_SUMMARY_DUPLICATE = (20500, "日常名称不能重复")
+    CALENDARS_DATE_ERROR = (20501, "日期不能为空")
+    CALENDARS_DATE_PERIOD_ERROR = (20502, "结束日期必须大于等于开始日期")
+    CALENDARS_TIME_ERROR = (20503, "时间不能为空")
+    CALENDARS_TIME_PERIOD_ERROR = (20504, "结束时间必须大于开始时间")
+    CALENDARS_OUT_ID_NUM_DIS_ALLOW_UPDATE = (20505, "日程编号和外部日程关联不允许变更")
+    BLACKLIST_MOBILE_EXIST = (20601, "手机号已经存在")
+
+    SCHEDULE_ITEM_DATE_ERROR = (20701, "条目开始日期不能大于结束日期")
+    SCHEDULE_ITEM_DATE_FORMAT_ERROR = (20702, "条目时间格式错误")
+    SCHEDULE_ITEM_WEEK_NO_AVAILABLE = (20703, "条目没有可用的星期")
+
+    SCHEDULE_ITEM_TIME_ERROR = (20704, "条目开始时间不能大于结束时间")
+    CIRCUIT_POOL_PARALLEL_OVERSIZE = (20705, "并发超过限制")
+    CIRCUIT_POOL_PARALLEL_ILLEGAL = (20706, "线路池并发不能小于0")
+
+    FS_CLUSTER_PULL_SERVER_ERROR = (20801, "拉取FS集群失败")
+    TASK_START_ERROR = (30001, "任务启动失败")
+    ERROR_FLOW_ID_NULL = (40001, "ctiFlowId 不能为空")
+    ERROR_FLOW_ID_NOT_CORRECT = (40002, "ctiFlowId 不正确")
+    ERROR_FLOW_ID_CALL_ID_NOT_MATCH = (40003, "ctiFlowId callId 不匹配")
+    DISTRIBUTE_LOCK_TIME_OUT_ERROR = (90000, "请稍等,正在为您处理中")
+    THIRD_RESPONSE_ERROR = (90001, "外部响应异常")
+
+    TERMINATED_AGENT_STATUS = (300001, "对不起,网络状况暂时不佳,请刷新后重试。")
+    TERMINATED_LISTEN_AGENT_STATUS = (300002, "监听坐席状态错误")
+    ERROR_ENGINE = (400001, "请求引擎错误")
+    ERROR_NOT_FOLLOW_CHECK_IN = (400002, "禁用状态坐席不允许签入操作")
+    WAIT_REPROCESSING = (400003, "请稍等,正在为您加载中。")
+    WAIT_STATUS_CHANGE = (400004, "正在为您连接客户,请稍等")
+    AGENT_NOT_CALLING = (400005, "坐席不再通话中")
+    NOT_ALLOW_STATUS = (400006, "非可修改的状态")
+
+    def __init__(self, code, message):
+        self.code = code
+        self.message = message

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

@@ -0,0 +1,32 @@
+#!/usr/bin/env python3
+# encoding:utf-8
+
+from . import app
+from .constant import error_response
+
+
+class BizException(Exception):
+    def __init__(self, message, status_code=400):
+        super().__init__(message)
+        self.message = message
+        self.status_code = status_code
+
+
+@app.errorhandler(BizException)
+def handle_biz_exception(error):
+    return error_response(msg=str(error), http_code=200)
+
+# Generic error handler for uncaught exceptions
+@app.errorhandler(Exception)
+def handle_generic_exception(error):
+    return error_response(msg=str(error), http_code=500)
+
+# Specific error handler for 404 Not Found errors
+@app.errorhandler(404)
+def handle_404_error(error):
+    return error_response(msg="Resource not found", http_code=404)
+
+# Specific error handler for 400 Bad Request errors
+@app.errorhandler(400)
+def handle_400_error(error):
+    return error_response(msg="Bad Request", http_code=400)

+ 73 - 0
src/core/callcenter/model.py

@@ -107,6 +107,19 @@ class AgentActionRequest:
         return cls(**data)
 
 
+class HumanServiceQueryRequest:
+    """人工组查询"""
+    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 Agent:
     def __init__(self, saas_id, agent_number, agent_name, out_id, agent_password, agent_type, phone_number, distribute,
                  agent_state, identity_type):
@@ -179,6 +192,66 @@ class AgentMonitor:
         self.create_time = create_time
 
 
+class AgentMonitorData:
+    """坐席状态信息"""
+    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,
+                 heart_state=None, heart_time=None, update_time=None, check_scene=None):
+        self.saas_id = saas_id
+        self.agent_num = agent_num
+        self.agent_name = agent_name
+        self.out_id = out_id
+        self.check_state = check_state
+        self.online_state = online_state
+        self.check_in_time = check_in_time
+        self.check_out_time = check_out_time
+        self.service_state = service_state
+        self.busy_time = busy_time
+        self.idle_time = idle_time
+        self.call_time = call_time
+        self.hang_time = hang_time
+        self.heart_state = heart_state
+        self.heart_time = heart_time
+        self.update_time = update_time
+        self.check_scene = check_scene
+
+
+class AgentEventData:
+    def __init__(self, app_code, user_id, data: Dict[str, Any] = {}):
+        self.appCode = app_code
+        self.userId = user_id
+        self.data = data
+
+
+class AgentStateData:
+    def __init__(self):
+        self._s = 0
+        self._t = 0
+        self.status = 0
+        self.time = 0
+        self.assign_time = 0
+        self.phone_num = ""
+
+    @property
+    def status(self):
+        return self._s
+
+    @status.setter
+    def status(self, value):
+        self._s = value
+        self.status = value
+
+    @property
+    def time(self):
+        return self._t
+
+    @time.setter
+    def time(self, value):
+        self._t = value
+        self.time = value
+
+
 class HangupCallRequest:
     def __init__(self, saas_id, call_id, agent_number):
         # saasId(必填)

+ 26 - 0
src/core/callcenter/push.py

@@ -0,0 +1,26 @@
+#!/usr/bin/env python3
+# encoding:utf-8
+import json
+from src.core.callcenter.enumeration import AgentScene, AgentServiceState, WorkStatus, DownEvent
+from src.core.callcenter.ws import common_down_data, common_down_cmd
+
+
+class PushHandler:
+    def __init__(self, logger):
+        self.logger = logger
+
+    # def push_on_agent_report(self, saas_id, out_id, scene: AgentScene, service_state: AgentServiceState):
+    #     pass
+
+    def push_on_agent_work_report(self, saas_id, flow_id, user_id, call_id, scene: AgentScene, work_status: WorkStatus, description):
+        data = {
+            'eventName': DownEvent.ON_AGENT_WORK_REPORT.code,
+            'ext': {'workStatus': work_status.code,
+                    'description': description,
+                    'callId': call_id,
+                    'ctiFlowId': flow_id,
+                    'scene': scene.code
+                    }
+        }
+        self.logger.info("flowId:[%s] OnAgentWorkReport push:[%s].", flow_id, json.dumps(data))
+        common_down_data(user_id, data)

+ 20 - 11
src/core/callcenter/web.py

@@ -4,20 +4,21 @@
 import os
 import json
 import threading
+from . import app
 import src.core.callcenter.cache as Cache
 from src.core.callcenter import create_app
 from src.core.callcenter.agent import AgentService, AgentOperService
-from src.core.callcenter.constant import success
+from src.core.callcenter.constant import success_response
 from src.core.callcenter.enumeration import CallType
 from src.core.callcenter.esl.client import InboundClient, OutboundClient
 from flask import Flask, request, render_template_string
 
 from src.core.callcenter.call import CallService
-from src.core.callcenter.model import MakeCallRequest, AgentInfo, AgentActionRequest, HangupCallRequest
+from src.core.callcenter.model import MakeCallRequest, AgentInfo, AgentActionRequest, HangupCallRequest, \
+    HumanServiceQueryRequest
 from src.core.voip.bot import BotAgent
 
 
-app = create_app()
 agent = BotAgent(app.logger)
 inbound_client = InboundClient(agent, app.logger)
 outbound_client = OutboundClient(agent, app.logger)
@@ -25,6 +26,7 @@ call_service = CallService(inbound_client, app.logger)
 agent_service = AgentService(inbound_client, app.logger)
 agent_oper_service = AgentOperService(inbound_client, app.logger)
 
+
 @app.route('/')
 def index():
     return render_template_string("""<!DOCTYPE html>
@@ -60,9 +62,11 @@ def get_cdn_url():
 
 @app.route('/open/agent/get-init-config', methods=['POST'])
 def get_init_config():
-
     """获取初始化配置"""
-    return 'Hello World!'
+    data = request.get_json()
+    param = AgentActionRequest.from_json(data=data)
+    res = agent_service.get_and_check(param)
+    return success_response(res)
 
 
 @app.route('/open/agent/check-in', methods=['POST'])
@@ -71,7 +75,7 @@ def check_in():
     data = request.get_json()
     param = AgentActionRequest.from_json(data=data)
     res = agent_oper_service.checkin(param)
-    return success(res)
+    return success_response(res)
 
 
 @app.route('/open/agent/check-out', methods=['POST'])
@@ -111,13 +115,17 @@ def hang_up():
     """挂断"""
     data = request.get_json()
     param = AgentActionRequest.from_json(data=data)
-    return call_service.hangup(param)
+    res = call_service.hangup(param)
+    return success_response(res)
 
 
 @app.route('/open/agent/agent-state', methods=['POST'])
 def agent_state():
     """获取坐席状态"""
-    return 'Hello World!'
+    data = request.get_json()
+    param = HumanServiceQueryRequest.from_json(data)
+    res = agent_service.watch_agent_state(param)
+    return success_response(res)
 
 
 @app.route('/open/agent/manual-call', methods=['POST'])
@@ -137,7 +145,7 @@ def manual_call():
     agent = Cache.get_agent_info(data.get('agentId'))
     req = MakeCallRequest(saas_id=data.get('vccId'), call_type=CallType.OUTBOUND_CALL, caller=agent.agent_number, called=data.get('called'), follow_data=data.get('ext'))
     res = call_service.call(req, agent)
-    return success(res)
+    return success_response(res)
 
 
 @app.route('/open/agent/manual-hang', methods=['POST'])
@@ -147,7 +155,7 @@ def manual_hang():
     agent = Cache.get_agent_info(data.get('agentId'))
     req = HangupCallRequest(saas_id=data.get('vccId'), call_id=data.get('callId'), agent_number=agent.agent_number)
     call_service.hangup(req)
-    return success()
+    return success_response()
 
 
 @app.route('/open/agent/listen', methods=['POST'])
@@ -177,6 +185,7 @@ def member_active():
 @app.route('/open/num/generate', methods=['POST'])
 def num_generate():
     """获取 cti 流程 ID"""
-    return 'Hello World!'
+    flow_id = call_service.snowflake.next_id()
+    return success_response(flow_id)