Procházet zdrojové kódy

Merge branch 'master' of http://110.188.24.182:10023/Fengwo/hotline

xf před 1 rokem
rodič
revize
5f818aeb38
30 změnil soubory, kde provedl 1263 přidání a 44 odebrání
  1. 112 12
      src/Hotline.Api/Controllers/OrderController.cs
  2. 120 5
      src/Hotline.Api/Controllers/PbxController.cs
  3. 64 1
      src/Hotline.Api/Controllers/PushMessageController.cs
  4. 1 1
      src/Hotline.Api/Controllers/SettingController.cs
  5. 3 4
      src/Hotline.Api/Controllers/TestController.cs
  6. 44 0
      src/Hotline.Api/Controllers/WexTestController.cs
  7. 10 1
      src/Hotline.Api/config/appsettings.Development.json
  8. 1 1
      src/Hotline.Api/config/appsettings.json
  9. 14 0
      src/Hotline.Repository.SqlSugar/CallCenter/WexTelGroupRepository.cs
  10. 38 0
      src/Hotline.Share/Dtos/CallCenter/TelGroupDto.cs
  11. 2 1
      src/Hotline.Share/Dtos/Order/OrderDto.cs
  12. 127 1
      src/Hotline.Share/Dtos/Order/QueryOrderDto.cs
  13. 93 0
      src/Hotline.Share/Dtos/Push/SendSmsModelDto.cs
  14. 30 0
      src/Hotline.Share/Dtos/Push/SmsAccountInfo.cs
  15. 3 1
      src/Hotline.Share/Dtos/Trunk/TrunkDto.cs
  16. 34 0
      src/Hotline.Share/Enums/Push/ESendState.cs
  17. 9 0
      src/Hotline/CallCenter/Tels/IWexTelGroupRepository.cs
  18. 25 0
      src/Hotline/CallCenter/Tels/WexTelGroup.cs
  19. 1 0
      src/Hotline/Orders/OrderVisit.cs
  20. 11 5
      src/Hotline/Orders/OrderVisitDetail.cs
  21. 29 1
      src/Hotline/Permissions/EPermission.cs
  22. 28 0
      src/Hotline/Push/IPushDomainService.cs
  23. 57 7
      src/Hotline/Push/Message.cs
  24. 253 3
      src/Hotline/Push/PushDomainService.cs
  25. 15 0
      src/Hotline/Settings/SysDicTypeConsts.cs
  26. 7 0
      src/Wex.Sdk/IWexClient.cs
  27. 6 0
      src/Wex.Sdk/Tel/IWexClient.Tel.cs
  28. 16 0
      src/Wex.Sdk/Tel/QueryGroupRequest.cs
  29. 21 0
      src/Wex.Sdk/Tel/QueryGroupResponse.cs
  30. 89 0
      src/Wex.Sdk/Tel/QueryTelPageRequest.cs

+ 112 - 12
src/Hotline.Api/Controllers/OrderController.cs

@@ -14,17 +14,11 @@ using Hotline.Share.Dtos;
 using Hotline.Share.Dtos.FlowEngine;
 using Hotline.Share.Dtos.Order;
 using Hotline.Share.Enums.Order;
-using Hotline.Share.Requests;
 using MapsterMapper;
 using MediatR;
-using Microsoft.AspNetCore.Authorization;
 using Microsoft.AspNetCore.Mvc;
-using Microsoft.EntityFrameworkCore.Migrations.Operations;
-using MongoDB.Driver.Linq;
-using System.Runtime.CompilerServices;
 using XF.Domain.Authentications;
 using XF.Domain.Exceptions;
-using XF.Domain.Extensions;
 using XF.Utility.EnumExtensions;
 
 namespace Hotline.Api.Controllers;
@@ -47,7 +41,8 @@ public class OrderController : BaseController
     private readonly IMapper _mapper;
     private readonly IMediator _mediator;
     private readonly IOrderPublishedRepository _orderPublishedRepository;
-
+    private readonly IOrderVisitedRepository _orderVisitedRepository;
+    private readonly IOrderVisitedDetailRepository _orderVisitedDetailRepository;
 
     public OrderController(
         IOrderDomainService orderDomainService,
@@ -62,7 +57,9 @@ public class OrderController : BaseController
         ISessionContext sessionContext,
         IMapper mapper,
         IMediator mediator,
-        IOrderPublishedRepository orderPublishedRepository)
+        IOrderPublishedRepository orderPublishedRepository,
+        IOrderVisitedRepository orderVisitedRepository,
+        IOrderVisitedDetailRepository orderVisitedDetailRepository)
     {
         _orderDomainService = orderDomainService;
         _orderRepository = orderRepository;
@@ -77,8 +74,12 @@ public class OrderController : BaseController
         _mapper = mapper;
         _mediator = mediator;
         _orderPublishedRepository = orderPublishedRepository;
+        _orderVisitedRepository = orderVisitedRepository;
+        _orderVisitedDetailRepository = orderVisitedDetailRepository;
     }
 
+    #region 工单发布
+
     /// <summary>
     /// 查询(工单发布)
     /// </summary>
@@ -91,19 +92,20 @@ public class OrderController : BaseController
         var (total, items) = await _orderRepository.Queryable()
             .Includes(d => d.OrderPublished)
             .Includes(d => d.Employee)
-            .Where(x=>x.Status == EOrderStatus.Filed)
+            .Where(x => x.Status == EOrderStatus.Filed)
             .WhereIF(!string.IsNullOrEmpty(dto.OrderTitle), d => d.Title.Contains(dto.OrderTitle!))
             .WhereIF(dto.PubState == EPubState.Pub, d => d.Progress == EProgress.Published || d.Progress == EProgress.Visited)
             .WhereIF(dto.PubState == EPubState.NoPub, d => d.Progress == EProgress.Managing)
             .WhereIF(!string.IsNullOrEmpty(dto.PubMan), d => d.Employee.Name.Contains(dto.PubMan!) || d.Employee.StaffNo.Contains(dto.PubMan!))
-            .WhereIF(dto.PubRange != null, d => d.OrderPublished.PublishState == dto.PubRange)
+            .WhereIF(dto.PubRange == EPublicState.Pub, d => d.OrderPublished.PublishState)
+            .WhereIF(dto.PubRange == EPublicState.NoPub, d=>!d.OrderPublished.PublishState)
             .WhereIF(dto.AcceptTypes.Any(), d => dto.AcceptTypes.Contains(d.AcceptType))
             .WhereIF(dto.HotspotIds.Any(), d => dto.HotspotIds.Contains(d.HotspotId))
             .WhereIF(dto.CreationTimeStart.HasValue, d => d.CreationTime >= dto.CreationTimeStart)
             .WhereIF(dto.CreationTimeEnd.HasValue, d => d.CreationTime <= dto.CreationTimeEnd)
             .WhereIF(dto.FiledTimeStart.HasValue, d => d.OrderPublished.CreationTime >= dto.CreationTimeStart)
             .WhereIF(dto.FiledTimeEnd.HasValue, d => d.OrderPublished.CreationTime <= dto.CreationTimeEnd)
-            .OrderByDescending(d => d.CreationTime)
+            .OrderBy(d => d.CreationTime)
             .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted);
 
         return new PagedDto<PublishDto>(total, _mapper.Map<IReadOnlyList<PublishDto>>(items));
@@ -149,6 +151,7 @@ public class OrderController : BaseController
             orgDetail.VisitId = id;
             orgDetail.VisitOrgCode = item.Id;
             orgDetail.VisitOrgName = item.Name;
+            orgDetail.VisitTarget = EVisitTarget.Org;
             visitedDetail.Add(orgDetail);
         }
         await _mediator.Publish(new AddVisitNotify(visitedDetail), HttpContext.RequestAborted);
@@ -183,12 +186,109 @@ public class OrderController : BaseController
             throw UserFriendlyException.SameMessage("未知工单,无法发布");
 
         var res = new PublishOrderPageBaseDto() { SourceChannel = order.SourceChannel, OrderTitle = order.Title, Content = order.Content, ActualOpinion = order.ActualOpinion };
-        var (idName,idNames)  = await _workflowDomainService.GetNoVisiteOrgsAsync(order.WorkflowId, HttpContext.RequestAborted);
+        var (idName, idNames) = await _workflowDomainService.GetNoVisiteOrgsAsync(order.WorkflowId, HttpContext.RequestAborted);
         res.ActualHandleOrgName = idName;
         res.idNames = idNames.ToList();
         return res;
     }
 
+
+    #endregion
+
+    #region 工单回访
+
+    /// <summary>
+    /// 回访列表
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    [Permission(EPermission.QueryOrderVisitList)]
+    [HttpGet("order-visit-list")]
+    public async Task<PagedDto<OrderVisit>> QueryOrderVisitList([FromQuery]QueryOrderVisitDto dto)
+    {
+        var (total, items) = await _orderVisitedRepository.Queryable()
+            .Includes(x => x.Order)
+            .Includes(x => x.Employee)
+            .WhereIF(dto.VisitState == EVisitStateQuery.NoVisit, x => x.VisitState == Share.Enums.Order.EVisitState.WaitForVisit || x.VisitState == Share.Enums.Order.EVisitState.NoSatisfiedWaitForVisit)
+            .WhereIF(dto.VisitState == EVisitStateQuery.Visited, x => x.VisitState == Share.Enums.Order.EVisitState.Visited)
+            .OrderBy(x => x.CreationTime)
+            .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted);
+        return new PagedDto<OrderVisit>(total, _mapper.Map<IReadOnlyList<OrderVisit>>(items));
+        return null;
+    }
+
+    /// <summary>
+    /// 回访详情
+    /// </summary>
+    /// <returns></returns>
+    [Permission(EPermission.VisitInfo)]
+    [HttpGet("visit-entity/{id}")]
+    public async Task<object> VisitInfo(string id)
+    {
+        var orderVisit = await _orderVisitedRepository.Queryable()
+           .Includes(x => x.Order)
+           .Includes(x => x.Employee)
+           .FirstAsync(x => x.Id == id, HttpContext.RequestAborted);
+
+        if (orderVisit is null)
+        {
+            throw UserFriendlyException.SameMessage("未知工单回访");
+        }
+
+        int count =await _orderVisitedRepository.CountAsync(x => x.OrderId == orderVisit.OrderId && x.VisitState == Share.Enums.Order.EVisitState.Visited,HttpContext.RequestAborted);
+        var visitSatisfaction = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.VisitSatisfaction);
+        var dissatisfiedReason = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.DissatisfiedReason);
+        var visitManner = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.VisitManner);
+
+        return new { OrderVisitModel = orderVisit ,Count = count , VisitSatisfaction = visitSatisfaction, DissatisfiedReason= dissatisfiedReason, VisitManner= visitManner };
+    }
+
+    /// <summary>
+    /// 回访明细
+    /// </summary>
+    /// <returns></returns>
+    [Permission(EPermission.VisitDetailList)]
+    [HttpGet("visit-detail-list")]
+    public async Task<PagedDto<OrderVisitDetail>> VisitDetailList([FromQuery]VisitDetailListDto dto)
+    {
+        var (total,items) = await _orderVisitedDetailRepository.Queryable()
+            .Includes(x => x.OrderVisit)
+            .WhereIF(dto.VisitState == EVisitStateQuery.NoVisit, x=>x.OrderVisit.VisitState == Share.Enums.Order.EVisitState.WaitForVisit || x.OrderVisit.VisitState == Share.Enums.Order.EVisitState.NoSatisfiedWaitForVisit)
+            .WhereIF(dto.VisitState == EVisitStateQuery.Visited, x=>x.OrderVisit.VisitState == Share.Enums.Order.EVisitState.Visited)
+            .OrderByDescending(x => x.CreationTime)
+            .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted);
+
+        return new PagedDto<OrderVisitDetail>(total, _mapper.Map<IReadOnlyList<OrderVisitDetail>>(items));
+    }
+
+
+    /// <summary>
+    /// 回访保存
+    /// </summary>
+    /// <returns></returns>
+    [Permission(EPermission.Visit)]
+    [HttpPost("visit")]
+    public async Task Visit([FromBody]VisitDto dto)
+    {
+        var visit = await _orderVisitedRepository.GetAsync(dto.Id, HttpContext.RequestAborted);
+        if (visit is null)
+            throw UserFriendlyException.SameMessage("未知回访信息");
+
+        //更新主表
+        visit.VisitState = Share.Enums.Order.EVisitState.Visited;
+        visit.VisitTime = DateTime.Now;
+        visit.IsPutThrough = dto.IsPutThrough;
+        visit.EmployeeId = _sessionContext.UserId;
+        await _orderVisitedRepository.UpdateAsync(visit,HttpContext.RequestAborted);
+
+        //更新明細
+        var visitDetails = _mapper.Map <List<OrderVisitDetail>>(dto.VisitDetails);
+
+        await _orderVisitedDetailRepository.UpdateRangeAsync(visitDetails, HttpContext.RequestAborted);
+    }
+
+    #endregion
+
     /// <summary>
     /// 工单列表
     /// </summary>

+ 120 - 5
src/Hotline.Api/Controllers/PbxController.cs

@@ -1,4 +1,5 @@
-using Hotline.Application.FlowEngine;
+using Grpc.Core;
+using Hotline.Application.FlowEngine;
 using Hotline.Caching.Interfaces;
 using Hotline.CallCenter.Calls;
 using Hotline.CallCenter.Devices;
@@ -15,9 +16,7 @@ using Hotline.Share.Requests;
 using Hotline.Users;
 using MapsterMapper;
 using Microsoft.AspNetCore.Mvc;
-using Org.BouncyCastle.Asn1.Ocsp;
 using SqlSugar;
-using System.Threading;
 using Wex.Sdk;
 using Wex.Sdk.Tel;
 using XF.Domain.Authentications;
@@ -25,6 +24,9 @@ using XF.Domain.Cache;
 using XF.Domain.Constants;
 using XF.Domain.Exceptions;
 using XF.Utility.EnumExtensions;
+using System.Linq;
+using Hotline.Share.Dtos;
+using Microsoft.AspNetCore.Authorization;
 
 namespace Hotline.Api.Controllers
 {
@@ -55,6 +57,7 @@ namespace Hotline.Api.Controllers
         private readonly ICallDetailRepository _callDetailRepository;
         private readonly IUserRepository _userRepository;
         private readonly IWexClient _wexClient;
+        private readonly IWexTelGroupRepository _wexTelGroupRepository;
 
         public PbxController(
             ITelRepository telRepository,
@@ -78,7 +81,8 @@ namespace Hotline.Api.Controllers
             ILogger<TelController> logger,
             ICallDetailRepository callDetailRepository,
             IUserRepository userRepository,
-            IWexClient wexClient)
+            IWexClient wexClient,
+            IWexTelGroupRepository wexTelGroupRepository)
         {
             _telRepository = telRepository;
             _telRestRepository = telRestRepository;
@@ -102,6 +106,7 @@ namespace Hotline.Api.Controllers
             _callDetailRepository = callDetailRepository;
             _userRepository = userRepository;
             _wexClient = wexClient;
+            _wexTelGroupRepository = wexTelGroupRepository;
         }
 
         #region 话机
@@ -370,7 +375,7 @@ namespace Hotline.Api.Controllers
             if (tel.Data[0].Sigin == 0)
                 throw UserFriendlyException.SameMessage("分机未签入,不能休息");
 
-            var telRest = new TelRest("5f7099aa-ef27-48bd-959f-13ccc5a1dc1b", "8029", _sessionContext.RequiredUserId, _sessionContext.UserName, dto.Reason, false, _sessionContext.StaffNo);
+            var telRest = new TelRest(tel.Data[0].TelNo, tel.Data[0].TelNo, _sessionContext.RequiredUserId, _sessionContext.UserName, dto.Reason, false, _sessionContext.StaffNo);
             await _telRestRepository.AddAsync(telRest, HttpContext.RequestAborted);
         }
 
@@ -990,5 +995,115 @@ namespace Hotline.Api.Controllers
         }
 
         #endregion
+
+        #region 威尔信分机和分机组操作
+
+        /// <summary>
+        /// 查询分机
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [Permission(EPermission.QueryTels)]
+        [HttpGet("query-tel-list")]
+        public async Task<PagedDto<TelListPageDto>> QueryTelList([FromQuery]QueryTelListDto dto)
+        {
+            var rsp = await _wexClient.QueryTelsPageAsync(new QueryTelPageRequest() { PageIndex = dto.PageIndex, PageSize = dto.PageSize}, HttpContext.RequestAborted);
+            var telList = rsp?.Data;
+            var count = rsp.Count;
+            if (telList!=null)
+            {
+                var telGroup = _wexTelGroupRepository.Queryable().ToList();
+
+                var list = (from a in telList
+                            join b in telGroup on a.TelNo equals b.TelNo into output
+                            from j in output.DefaultIfEmpty()
+                            select new TelListPageDto
+                            {
+                                Id = (j==null ? "": j.Id),
+                                TelNo = a.TelNo,
+                                GroupName = (j==null ? "": j.GroupName)
+                            }).ToList();
+                return new PagedDto<TelListPageDto>(count, list);
+            }
+            return new PagedDto<TelListPageDto>(0, null);
+        }
+
+        /// <summary>
+        /// 新增分机关联分机组
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [AllowAnonymous]
+        [Permission(EPermission.AddTelGroup)]
+        [HttpGet("add-wextelgroup")]
+        public async Task AddWexTelGroup([FromBody]AddWexTelGroupDto dto)
+        {
+            var telGroup = new WexTelGroup();
+            telGroup.TelNo = dto.TelNo;
+            telGroup.GroupId = dto.GroupId;
+            telGroup.GroupName = dto.GroupName;
+            telGroup.ZuoGroupName = dto.ZuoGroupName;
+            await _wexTelGroupRepository.AddAsync(telGroup, HttpContext.RequestAborted);
+        }
+
+        /// <summary>
+        /// 修改分机关联分机组
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [Permission(EPermission.UpdateTelGroup)]
+        [HttpGet("update-wextelgroup")]
+        public async Task UpdateWexTelGroup([FromBody]UpdateWexTelGroupDto dto)
+        {
+            var telGroup =await _wexTelGroupRepository.GetAsync(dto.Id, HttpContext.RequestAborted);
+            if (telGroup is null)
+            {
+                throw UserFriendlyException.SameMessage("未找到对应数据,无法修改");
+            }
+            if (dto.GroupId == 0)
+            {
+                await _wexTelGroupRepository.RemoveAsync(dto.Id, false, HttpContext.RequestAborted);
+            }
+            else
+            {
+                telGroup.GroupId = dto.GroupId;
+                telGroup.GroupName = dto.GroupName;
+                telGroup.ZuoGroupName = dto.ZuoGroupName;
+                await _wexTelGroupRepository.UpdateAsync(telGroup);
+            }
+        }
+
+        /// <summary>
+        /// 分机组列表
+        /// </summary>
+        /// <returns></returns>
+        [Permission(EPermission.QueryTelGroups)]
+        [HttpGet("telgroup-list")]
+        public async Task<List<WexTelGroupDto>> TelGroupList()
+        {
+            var rsp = await _wexClient.QueryGroupAsync(new QueryGroupRequest() { }, HttpContext.RequestAborted);
+            var groupList = rsp.Data;
+            var list = _mapper.Map<List<WexTelGroupDto>>(groupList);
+            return list;
+        }
+
+        /// <summary>
+        /// 根据分机号查询分机关联坐席组
+        /// </summary>
+        /// <param name="telno"></param>
+        /// <returns></returns>
+        [Permission(EPermission.QueryTelGroups)]
+        [HttpGet("telgroup/{telno}")]
+        public async Task<WexTelGroup> TelGroup(string telno)
+        {
+            var telGroup = await _wexTelGroupRepository.GetAsync(x => x.TelNo == telno, HttpContext.RequestAborted);
+            if (telGroup is null)
+            {
+                return new WexTelGroup() { TelNo = telno };
+            }
+            return telGroup;
+        }
+
+        #endregion
     }
 }

+ 64 - 1
src/Hotline.Api/Controllers/PushMessageController.cs

@@ -3,6 +3,7 @@ using Hotline.Repository.SqlSugar.Extensions;
 using Hotline.Share.Dtos;
 using Hotline.Share.Dtos.Push;
 using MapsterMapper;
+using Microsoft.AspNetCore.Authorization;
 using Microsoft.AspNetCore.Mvc;
 
 namespace Hotline.Api.Controllers
@@ -38,7 +39,69 @@ namespace Hotline.Api.Controllers
         }
 
         /// <summary>
-        /// 
+        /// 查询短信剩余数量
+        /// </summary>
+        /// <returns></returns>
+        [HttpGet("query")]
+        public async Task<string> GetAccountNum()
+        {
+            return await _pushDomainService.GetAccountNum();
+        }
+
+        #region 短信发送状态回调接口
+        /// <summary>
+        /// 短信发送状态回调接口
+        /// </summary>
+        /// <param name="account">短信回传账号</param>
+        /// <param name="pswd">短信回传密码</param>
+        /// <param name="msgid">短信中心短信待发送ID,同短信发送接口返回值ID</param>
+        /// <param name="sendtime">短信发送时间</param>
+        /// <param name="msgcount">发送短信使用的数量</param>
+        /// <param name="state">发送状态:0:未发送  1:发送中  2:发送失败  3:发送成功</param>
+        /// <param name="errormsg">错误消息</param>
+        /// <param name="sfid">短信中心短信已发送ID</param>
+        /// <param name="telnumall">手机号码</param>
+        /// <param name="sign">短信签名</param>
+        /// <returns></returns>
+        [HttpPost("receiveobtain")]
+        [AllowAnonymous]
+        public async Task<string> ReceiveObtain(string account, string pswd, string msgid, string sendtime, int msgcount, int state,
+            string errormsg, int sfid, string telnumall, string sign)
+        {
+            return await _pushDomainService.ReceiveObtain(account, pswd, msgid, sendtime, msgcount, state, errormsg, sfid, telnumall, sign);
+        }
+        #endregion
+
+        #region 短信接收
+        /// <summary>
+        /// 短信接收
+        /// </summary>
+        /// <param name="account">短信回传账号</param>
+        /// <param name="pswd">短信回传密码</param>
+        /// <param name="msg">回复短信内容</param>
+        /// <param name="mobile">回复手机号码</param>
+        /// <param name="destcode">短信接收平台号码</param>
+        /// <param name="motime">回复时间</param>
+        /// <param name="fsfid">短信平台回复短信ID</param>
+        /// <returns></returns>
+        [HttpPost("receivesms")]
+        [AllowAnonymous]
+        public string ReceiveSms(string account, string pswd, string msg, string mobile, string destcode, string motime, string fsfid)
+        {
+            // 短信回传账号:fwkj
+            // 短信回传密码:fwkj12
+
+            string strResult = "error,缺少参数";
+
+
+            // 成功返回值必须是ok
+            strResult = "ok";
+            return strResult;
+        }
+        #endregion
+
+        /// <summary>
+        /// 查询短信
         /// </summary>
         /// <param name="pagedDto"></param>
         /// <returns></returns>

+ 1 - 1
src/Hotline.Api/Controllers/SettingController.cs

@@ -99,7 +99,7 @@ namespace Hotline.Api.Controllers
         /// </summary>
         /// <param name="code"></param>
         /// <returns></returns>
-        [Permission(EPermission.Home)]
+        [AllowAnonymous]
         [HttpGet("getsetting-code/{code}")]
         public async Task<SystemSetting?> GetSettingByCode(string code)
         {

+ 3 - 4
src/Hotline.Api/Controllers/TestController.cs

@@ -101,15 +101,14 @@ public class TestController : BaseController
     }
 
     [HttpGet("time")]
-    public async Task<string> GetTime()
+    public async Task GetTime()
     {
-        //var rsp = await _wexClient.QueryTelsAsync(new QueryTelRequest { StaffNo = "10086" }, HttpContext.RequestAborted);
+        var rsp = await _wexClient.QueryTelsAsync(new QueryTelRequest {  }, HttpContext.RequestAborted);
 
         //return DateTime.Now.ToString("F");
 
-        var rsp = await _daprClient.InvokeMethodAsync<ApiResponse<string>>(HttpMethod.Get, "identity", "api/v1/Test/time", HttpContext.RequestAborted);
+        //var rsp = await _daprClient.InvokeMethodAsync<ApiResponse<string>>(HttpMethod.Get, "identity", "api/v1/Test/time", HttpContext.RequestAborted);
         //var rsp1 = await _daprClient.InvokeMethodAsync<int, ApiResponse<string>>(HttpMethod.Post, "identity", "api/v1/Test/time1", 222, HttpContext.RequestAborted);
-        return rsp.Result;
     }
 
     [HttpGet("pgsql")]

+ 44 - 0
src/Hotline.Api/Controllers/WexTestController.cs

@@ -0,0 +1,44 @@
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Wex.Sdk;
+using Wex.Sdk.Tel;
+
+namespace Hotline.Api.Controllers
+{
+    [AllowAnonymous]
+    public class WexTestController: BaseController
+    {
+        private readonly IWexClient _wexClient;
+
+
+        public WexTestController(IWexClient wexClient)
+        {
+            _wexClient = wexClient;
+        }
+
+        /// <summary>
+        /// 获取分机
+        /// </summary>
+        /// <returns></returns>
+        [HttpGet("gettel")]
+        public async Task<object> GetTel()
+        {
+            var rsp = await _wexClient.QueryTelsAsync(new QueryTelRequest { }, HttpContext.RequestAborted);
+            return rsp;
+        }
+
+        /// <summary>
+        /// 获取坐席组
+        /// </summary>
+        /// <returns></returns>
+        [HttpGet("getgroup")]
+        public async Task<object> GetGroup()
+        {
+            var rsp = await _wexClient.QueryGroupAsync(new QueryGroupRequest { }, HttpContext.RequestAborted);
+            return rsp;
+        }
+        
+
+
+    }
+}

+ 10 - 1
src/Hotline.Api/config/appsettings.Development.json

@@ -12,7 +12,7 @@
     "Wex": {
       "Address": "http://222.212.82.225:8083",
       "Username": "admin",
-      "Password": "123456"
+      "Password": "Wex@12345"
     }
   },
   "ConnectionStrings": {
@@ -65,5 +65,14 @@
       "HostName": "110.188.24.182",
       "VirtualHost": "fwt-master"
     }
+  },
+  "SmsAccountInfo": {
+    "MessageServerUrl": "http://webservice.fway.com.cn:1432/FWebService.asmx/FWay_Service", //短信发送地址
+    "AccountUser": "CS12345", //短信系统账号
+    "AccountPwd": "9EE3899305A8FC97D6146CAC6B802E6F", //短信系统密码
+    "ReturnAccountUser": "fwkj", //短信回传账号
+    "ReturnAccountPwd": "fwkj12" //短信回传密码
   }
+
+
 }

+ 1 - 1
src/Hotline.Api/config/appsettings.json

@@ -12,7 +12,7 @@
     "Wex": {
       "Address": "http://222.212.82.225:8083",
       "Username": "admin",
-      "Password": "123456"
+      "Password": "Wex@12345"
     }
   },
   "ConnectionStrings": {

+ 14 - 0
src/Hotline.Repository.SqlSugar/CallCenter/WexTelGroupRepository.cs

@@ -0,0 +1,14 @@
+using Hotline.CallCenter.Tels;
+using Hotline.Repository.SqlSugar.DataPermissions;
+using SqlSugar;
+using XF.Domain.Dependency;
+
+namespace Hotline.Repository.SqlSugar.CallCenter
+{
+    public class WexTelGroupRepository : BaseRepository<WexTelGroup>, IWexTelGroupRepository, IScopeDependency
+    {
+        public WexTelGroupRepository(ISugarUnitOfWork<HotlineDbContext> uow, IDataPermissionFilterBuilder dataPermissionFilterBuilder) : base(uow, dataPermissionFilterBuilder)
+        {
+        }
+    }
+}

+ 38 - 0
src/Hotline.Share/Dtos/CallCenter/TelGroupDto.cs

@@ -1,4 +1,5 @@
 using Hotline.Share.Enums.CallCenter;
+using Hotline.Share.Requests;
 using XF.Utility.EnumExtensions;
 
 namespace Hotline.Share.Dtos.CallCenter
@@ -52,4 +53,41 @@ namespace Hotline.Share.Dtos.CallCenter
     {
         public string Id { get; set; }
     }
+
+    public record class QueryTelListDto : PagedRequest
+    {
+
+    }
+
+    public record class TelListPageDto
+    {
+        public string Id { get; set; }
+        public string TelNo { get; set; }
+        public string GroupName { get; set; }
+        public int GroupId { get; set; }
+        public string ZuoGroupName { get; set; }
+    }
+
+
+    public record AddWexTelGroupDto
+    {
+        public string TelNo { get; set; }
+        public int GroupId { get; set; }
+        public string GroupName { get; set; }
+        public string ZuoGroupName { get; set; }
+    }
+
+    public record UpdateWexTelGroupDto:AddWexTelGroupDto
+    {
+        public string Id { get; set; }
+    }
+
+    public record WexTelGroupDto
+    {
+        public int GroupId { get; set; }
+
+        public string GroupName { get; set; }
+
+        public string ZuoGroupName { get; set; }
+    }
 }

+ 2 - 1
src/Hotline.Share/Dtos/Order/OrderDto.cs

@@ -336,6 +336,7 @@ namespace Hotline.Share.Dtos.Order
         public string ArrangeContent { get; set; }
     }
 
+
     public class PublishOrderPageBaseDto
     {
         /// <summary>
@@ -370,7 +371,6 @@ namespace Hotline.Share.Dtos.Order
     }
 
 
-
     public class PublishOrderDto
     {
         /// <summary>
@@ -421,4 +421,5 @@ namespace Hotline.Share.Dtos.Order
 
         #endregion
     }
+
 }

+ 127 - 1
src/Hotline.Share/Dtos/Order/QueryOrderDto.cs

@@ -4,6 +4,7 @@ using System.ComponentModel;
 using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
+using Hotline.Share.Dtos.FlowEngine;
 using Hotline.Share.Enums.Order;
 using Hotline.Share.Requests;
 
@@ -100,7 +101,7 @@ namespace Hotline.Share.Dtos.Order
         /// <summary>
         /// 发布范围
         /// </summary>
-        public bool? PubRange { get; set; }
+        public EPublicState PubRange { get; set; }
 
         /// <summary>
         /// 受理类型
@@ -126,6 +127,131 @@ namespace Hotline.Share.Dtos.Order
 
     }
 
+    public record QueryOrderVisitDto: PagedRequest
+    {
+        public EVisitStateQuery VisitState { get; set; }
+    }
+
+    public record VisitDetailListDto: PagedRequest
+    {
+        public EVisitStateQuery VisitState { get; set; }
+    }
+
+    public record VisitDto
+    {
+        /// <summary>
+        /// 回访主表主键
+        /// </summary>
+        public string Id { get; set; }
+
+        /// <summary>
+        /// 是否接通
+        /// </summary>
+        public bool IsPutThrough { get; set; }
+
+        public List<VisitDetailDto> VisitDetails { get; set; }
+    }
+
+    public record VisitDetailDto
+    {
+        /// <summary>
+        /// Id
+        /// </summary>
+        public string Id { get; set; }
+
+        /// <summary>
+        /// 回访主表ID
+        /// </summary>
+        public string VisitId { get; set; }
+
+        /// <summary>
+        /// 语音评价(话务评价)
+        /// </summary>
+        public EVoiceEvaluate? VoiceEvaluate { get; set; }
+
+        /// <summary>
+        /// 话务员评价(话务评价)
+        /// </summary>
+        public ESeatEvaluate? SeatEvaluate { get; set; }
+
+        /// <summary>
+        /// 部门办件结果
+        /// </summary>
+        public IdName OrgProcessingResults { get; set; }
+
+        /// <summary>
+        /// 不满意原因
+        /// </summary>
+        public IdName OrgNoSatisfiedReason { get; set; }
+
+        /// <summary>
+        /// 部门办件态度
+        /// </summary>
+        public IdName OrgHandledAttitude { get; set; }
+
+        /// <summary>
+        /// 回访内容
+        /// </summary>
+        public string VisitContent { get; set; }
+
+        /// <summary>
+        /// 回访部门名称
+        /// </summary>
+        public string VisitOrgName { get; set; }
+
+        /// <summary>
+        /// 回访部门Code
+        /// </summary>
+        public string VisitOrgCode { get; set; }
+
+        /// <summary>
+        /// 回访对象类型 10:话务员 20:部门
+        /// </summary>
+        public EVisitTarget VisitTarget { get; set; }
+    }
+
+    public enum EVisitStateQuery
+    {
+        /// <summary>
+        /// 全部
+        /// </summary>
+        [Description("全部")]
+        All = 0,
+
+        /// <summary>
+        /// 已回访
+        /// </summary>
+        [Description("已回访")]
+        Visited = 1,
+
+        /// <summary>
+        /// 未回访
+        /// </summary>
+        [Description("未回访")]
+        NoVisit =2,
+    }
+
+
+    public enum EPublicState
+    {
+        /// <summary>
+        /// 全部
+        /// </summary>
+        [Description("全部")]
+        All = 0,
+        /// <summary>
+        /// 公开
+        /// </summary>
+        [Description("公开")]
+        Pub = 1,
+        /// <summary>
+        /// 不公开
+        /// </summary>
+        [Description("不公开")]
+        NoPub = 2,
+    }
+
+
     public enum EPubState
     {
         /// <summary>

+ 93 - 0
src/Hotline.Share/Dtos/Push/SendSmsModelDto.cs

@@ -0,0 +1,93 @@
+using System.Runtime.Serialization;
+
+namespace Hotline.Share.Dtos.Push
+{
+    public class SendSmsModelDto
+    {
+        [DataMember]
+        /// <summary>
+        /// 待发ID
+        /// </summary>
+        public int nWaitID;
+        [DataMember]
+        /// <summary>
+        /// 帐号
+        /// </summary>
+        public string strAccountUser;
+        [DataMember]
+        /// <summary>
+        /// 密码
+        /// </summary>
+        public string strAccountPwd;
+        [DataMember]
+        /// <summary>
+        /// 短信类型
+        /// </summary>
+        public string strType;
+        [DataMember]
+        /// <summary>
+        /// 流程ID
+        /// </summary>
+        public int nFlowID;
+        [DataMember]
+        /// <summary>
+        /// 接收姓名
+        /// </summary>
+        public string strPhoneName;
+        [DataMember]
+        /// <summary>
+        /// 接收号码
+        /// </summary>
+        public string strPhoneNumber;
+        [DataMember]
+        /// <summary>
+        /// 接收号码
+        /// </summary>
+        public string strPhoneNumberAll;
+        [DataMember]
+        /// <summary>
+        /// 批量发送号码格式为,姓名,号码
+        /// </summary>
+        public List<object[]> listSendTelNumber;
+        [DataMember]
+        /// <summary>
+        /// 批量发送号码
+        /// </summary>
+        public System.Data.DataTable dtTelNumber;
+        [DataMember]
+        /// <summary>
+        /// 短信内容
+        /// </summary>
+        public string strPhoneConnent;
+        [DataMember]
+        /// <summary>
+        /// 备注
+        /// </summary>
+        public string strRemark;
+        [DataMember]
+        /// <summary>
+        /// 添加时间
+        /// </summary>
+        public DateTime dAddTime;
+        [DataMember]
+        /// <summary>
+        /// 用户ID
+        /// </summary>
+        public int nUserID;
+        [DataMember]
+        /// <summary>
+        /// 用户姓名
+        /// </summary>
+        public string strUserName;
+        [DataMember]
+        /// <summary>
+        /// 发送部门ID
+        /// </summary>
+        public int nBMID;
+        [DataMember]
+        /// <summary>
+        /// 发送部门名称
+        /// </summary>
+        public string strBMName;
+    }
+}

+ 30 - 0
src/Hotline.Share/Dtos/Push/SmsAccountInfo.cs

@@ -0,0 +1,30 @@
+namespace Hotline.Share.Dtos.Push
+{
+    public class SmsAccountInfo
+    {
+        /// <summary>
+        /// 短信系统账号
+        /// </summary>
+        public string AccountUser { get; set; }
+
+        /// <summary>
+        /// 短信系统密码
+        /// </summary>
+        public string AccountPwd { get; set; }
+
+        /// <summary>
+        /// 短信发送地址
+        /// </summary>
+        public string MessageServerUrl { get; set; }
+
+        /// <summary>
+        /// 短信回传账号
+        /// </summary>
+        public string ReturnAccountUser { get; set; }
+
+        /// <summary>
+        /// 短信回传密码
+        /// </summary>
+        public string ReturnAccountPwd { get; set; }
+    }
+}

+ 3 - 1
src/Hotline.Share/Dtos/Trunk/TrunkDto.cs

@@ -1,4 +1,5 @@
-using System;
+using Hotline.Share.Requests;
+using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
@@ -33,4 +34,5 @@ namespace Hotline.Share.Dtos.Trunk
     {
         public string Id { get; set; }
     }
+
 }

+ 34 - 0
src/Hotline.Share/Enums/Push/ESendState.cs

@@ -0,0 +1,34 @@
+using System.ComponentModel;
+
+namespace Hotline.Share.Enums.Push
+{
+    /// <summary>
+    /// 0:未发送  1:发送中  2:发送失败  3:发送成功
+    /// </summary>
+    public enum ESendState
+    {
+        /// <summary>
+        /// 未发送
+        /// </summary>
+        [Description("未发送")]
+        Pushing = 0,
+
+        /// <summary>
+        /// 发送中
+        /// </summary>
+        [Description("发送中")]
+        Sending = 1,
+
+        /// <summary>
+        /// 发送失败
+        /// </summary>
+        [Description("发送失败")]
+        Failed = 2,
+
+        /// <summary>
+        /// 发送成功
+        /// </summary>
+        [Description("发送成功")]
+        Success = 3
+    }
+}

+ 9 - 0
src/Hotline/CallCenter/Tels/IWexTelGroupRepository.cs

@@ -0,0 +1,9 @@
+using XF.Domain.Repository;
+
+namespace Hotline.CallCenter.Tels
+{
+    public interface IWexTelGroupRepository: IRepository<WexTelGroup>
+    {
+
+    }
+}

+ 25 - 0
src/Hotline/CallCenter/Tels/WexTelGroup.cs

@@ -0,0 +1,25 @@
+using XF.Domain.Repository;
+
+namespace Hotline.CallCenter.Tels
+{
+    public class WexTelGroup: CreationEntity
+    {
+        /// <summary>
+        /// 分机号
+        /// </summary>
+        public string TelNo { get; set; }
+
+        /// <summary>
+        /// 分机组ID
+        /// </summary>
+        public int GroupId { get; set; }
+
+        /// <summary>
+        /// 分机组名称
+        /// </summary>
+        public string GroupName { get; set; }
+
+
+        public string ZuoGroupName { get; set; }
+    }
+}

+ 1 - 0
src/Hotline/Orders/OrderVisit.cs

@@ -71,6 +71,7 @@ public class OrderVisit : CreationEntity
     /// <summary>
     /// 回访明细
     /// </summary>
+    [SugarColumn(IsIgnore = true)]
     public List<OrderVisitDetail>? VisitDetails { get; set; }
 
 }

+ 11 - 5
src/Hotline/Orders/OrderVisitDetail.cs

@@ -1,4 +1,6 @@
-using Hotline.Share.Enums.Order;
+using Hotline.Settings.Hotspots;
+using Hotline.Share.Dtos.FlowEngine;
+using Hotline.Share.Enums.Order;
 using SqlSugar;
 using XF.Domain.Repository;
 
@@ -12,6 +14,10 @@ namespace Hotline.Orders
         /// </summary>
         public string VisitId { get; set; }
 
+
+        [Navigate(NavigateType.OneToOne, nameof(VisitId))]
+        public OrderVisit OrderVisit { get; set; }
+
         /// <summary>
         /// 语音评价(话务评价)
         /// </summary>
@@ -26,19 +32,19 @@ namespace Hotline.Orders
         /// 部门办件结果
         /// </summary>
         [SugarColumn(ColumnDataType = "json", IsJson = true, IsNullable = true)]
-        public string OrgProcessingResults { get; set; }
+        public IdName? OrgProcessingResults { get; set; }
 
         /// <summary>
         /// 不满意原因
         /// </summary>
         [SugarColumn(ColumnDataType = "json", IsJson = true, IsNullable = true)]
-        public string OrgNoSatisfiedReason { get; set; }
+        public List<IdName>? OrgNoSatisfiedReason { get; set; }
 
         /// <summary>
         /// 部门办件态度
         /// </summary>
         [SugarColumn(ColumnDataType = "json", IsJson = true, IsNullable = true)]
-        public string OrgHandledAttitude { get; set; }
+        public IdName? OrgHandledAttitude { get; set; }
 
         /// <summary>
         /// 回访内容
@@ -47,7 +53,7 @@ namespace Hotline.Orders
         public string VisitContent { get; set; }
 
         /// <summary>
-        /// 回访部门ID
+        /// 回访部门Code
         /// </summary>
         [SugarColumn(IsNullable = true)]
         public string VisitOrgCode { get; set; }

+ 29 - 1
src/Hotline/Permissions/EPermission.cs

@@ -850,7 +850,7 @@ namespace Hotline.Permissions
         #endregion
 
 
-        #region 发布管理
+        #region 工单发布管理
         /// <summary>
         /// 发布管理列表
         /// </summary>
@@ -870,6 +870,34 @@ namespace Hotline.Permissions
         PublishOrder= 500202,
         #endregion
 
+
+        #region 工单回访管理
+
+        /// <summary>
+        /// 回访列表
+        /// </summary>
+        [Display(GroupName = "OrderVisit",Name = "回访列表",Description = "回访列表")]
+        QueryOrderVisitList = 500300,
+
+        /// <summary>
+        /// 回访详情
+        /// </summary>
+        [Display(GroupName = "OrderVisit",Name ="回访详情",Description ="回访详情")]
+        VisitInfo = 500301,
+
+        /// <summary>
+        /// 回访明细列表
+        /// </summary>
+        [Display(GroupName = "OrderVisit",Name ="回访明细列表",Description ="回访明细列表")]
+        VisitDetailList = 500302,
+
+        /// <summary>
+        /// 回访
+        /// </summary>
+        [Display(GroupName = "OrderVisit",Name = "回访", Description = "回访")]
+        Visit = 500303,
+        #endregion
+
         #endregion
 
         #region 公用(999)

+ 28 - 0
src/Hotline/Push/IPushDomainService.cs

@@ -4,6 +4,34 @@ namespace Hotline.Push
 {
     public interface IPushDomainService
     {
+        /// <summary>
+        /// 短信发送
+        /// </summary>
+        /// <param name="messageDto"></param>
+        /// <param name="cancellation"></param>
+        /// <returns></returns>
         Task PushAsync(MessageDto messageDto, CancellationToken cancellation);
+
+        /// <summary>
+        /// 查询短信剩余数量
+        /// </summary>
+        /// <returns></returns>
+        Task<string> GetAccountNum();
+
+        /// <summary>
+        /// 短信发送状态回调接口
+        /// </summary>
+        /// <param name="account">短信回传账号</param>
+        /// <param name="pswd">短信回传密码</param>
+        /// <param name="msgid">短信中心短信待发送ID,同短信发送接口返回值ID</param>
+        /// <param name="sendtime">短信发送时间</param>
+        /// <param name="msgcount">发送短信使用的数量</param>
+        /// <param name="state">发送状态:0:未发送  1:发送中  2:发送失败  3:发送成功</param>
+        /// <param name="errormsg">错误消息</param>
+        /// <param name="sfid">短信中心短信已发送ID</param>
+        /// <param name="telnumall">手机号码</param>
+        /// <param name="sign">短信签名</param>
+        /// <returns></returns>
+        Task<string> ReceiveObtain(string account, string pswd, string msgid, string sendtime, int msgcount, int state, string errormsg, int sfid, string telnumall, string sign);
     }
 }

+ 57 - 7
src/Hotline/Push/Message.cs

@@ -14,61 +14,111 @@ namespace Hotline.Push
     {
         /// <summary>
         /// 消息推送业务
-        /// </summary>
+        /// </summary>        
+        [SugarColumn(ColumnDescription = "消息推送业务")]
         public EPushBusiness PushBusiness { get; set; }
 
         /// <summary>
         /// 外部业务唯一编号
         /// </summary>
+        [SugarColumn(ColumnDescription = "外部业务唯一编号", ColumnDataType = "varchar(50)")]
         public string ExternalId { get; set; }
 
         /// <summary>
         /// 推送平台
         /// </summary>
+        [SugarColumn(ColumnDescription = "推送平台")]
         public EPushPlatform PushPlatform { get; set; }
 
         /// <summary>
         /// 推送状态
         /// </summary>
+        [SugarColumn(ColumnDescription = "推送状态", ColumnDataType = "varchar(2)")]
         public EPushStatus Status { get; set; }
 
         /// <summary>
         /// 模板
         /// </summary>
-        [SugarColumn(IsNullable = true)]
+        [SugarColumn(ColumnDescription = "模板", ColumnDataType = "varchar(200)", IsNullable = true)]
         public string? Template { get; set; }
 
         /// <summary>
-        /// 内容
+        /// 短信内容
         /// </summary>
+        [SugarColumn(ColumnDescription = "短信内容", ColumnDataType = "varchar(200)")]
         public string Content { get; set; }
 
         /// <summary>
         /// 备注
         /// </summary>
-        [SugarColumn(IsNullable = true)]
+        [SugarColumn(ColumnDescription = "备注", ColumnDataType = "varchar(500)", IsNullable = true)]
         public string? Remark { get; set; }
 
         /// <summary>
         /// 接收姓名
         /// </summary>
+        [SugarColumn(ColumnDescription = "接收姓名", ColumnDataType = "varchar(20)")]
         public string Name { get; set; }
 
         /// <summary>
         /// 接收手机号码
         /// </summary>
+        [SugarColumn(ColumnDescription = "接收手机号码", ColumnDataType = "varchar(20)")]
         public string TelNumber { get; set; }
 
+        /// <summary>
+        /// 关联工单编号
+        /// </summary>
+        [SugarColumn(ColumnDescription = "关联工单编号", ColumnDataType = "varchar(50)", IsNullable = true)]
+        public string? OrderId { get; set; }
+
+        /// <summary>
+        /// 短信中心待发送ID
+        /// </summary>
+        [SugarColumn(ColumnDescription = "短信中心待发送ID", ColumnDataType = "varchar(20)", IsNullable = true)]
+        public string? SmsWaitSendingId { get; set; }
+
+        /// <summary>
+        /// 短信中心已发送ID
+        /// </summary>
+        [SugarColumn(ColumnDescription = "短信中心已发送ID", ColumnDataType = "varchar(20)", IsNullable = true)]
+        public string? SmsSendingCompletedId { get; set; }
+
+        /// <summary>
+        /// 发送短信使用的数量
+        /// </summary>
+        [SugarColumn(ColumnDescription = "发送短信使用的数量", IsNullable = true)]
+        public int? MsgCount { get; set; }
+
+        /// <summary>
+        /// 短信发送状态
+        /// </summary>
+        [SugarColumn(ColumnDescription = "短信发送状态")]
+        public ESendState SendState { get; set; }
+
         /// <summary>
         /// 发送时间
         /// </summary>
+        [SugarColumn(ColumnDescription = "发送时间",IsNullable =true)]
         public DateTime? SendTime { get; set; }
 
         /// <summary>
-        /// 关联工单编号
+        /// 短信回复是否回复
+        /// </summary>   
+        [SugarColumn(ColumnDescription = "短信回复是否回复")]
+        public bool IsSmsReply { get; set; }
+
+        /// <summary>
+        /// 短信回复内容
+        /// </summary>   
+        [SugarColumn(ColumnDescription = "短信回复内容", ColumnDataType = "varchar(200)", IsNullable = true)]
+        public string? SmsReplyContent { get; set; }
+
+        /// <summary>
+        /// 短信回复时间
         /// </summary>
-        [SugarColumn(IsNullable = true)]
-        public string? OrderId { get; set; }
+        [SugarColumn(ColumnDescription = "短信回复时间", IsNullable = true)]
+        public DateTime? SmsReplyTime { get; set; }
 
         /// <summary>
         /// 发送人

+ 253 - 3
src/Hotline/Push/PushDomainService.cs

@@ -1,26 +1,46 @@
 using Hotline.Share.Dtos.Push;
+using Hotline.Share.Enums.Push;
 using MapsterMapper;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging;
+using System.Net;
 using System.Text.RegularExpressions;
+using System.Xml;
 using XF.Domain.Dependency;
 using XF.Domain.Exceptions;
 
 namespace Hotline.Push;
 
+/// <summary>
+/// 
+/// </summary>
 public class PushDomainService : IPushDomainService, IScopeDependency
 {
 
     private readonly IMessageRepository _messageRepository;
     private readonly IMapper _mapper;
+    private readonly IHttpClientFactory _httpClientFactory;
+    private readonly ILogger<PushDomainService> _logger;
+    private readonly IConfiguration _config;
+    private readonly SmsAccountInfo accountInfo = null;
 
     /// <summary>
     /// 
     /// </summary>
     /// <param name="messageRepository"></param>
+    /// <param name="httpClientFactory"></param>
+    /// <param name="logger"></param>
     /// <param name="mapper"></param>
-    public PushDomainService(IMessageRepository messageRepository, IMapper mapper)
+    /// <param name="config"></param>
+    public PushDomainService(IMessageRepository messageRepository, IHttpClientFactory httpClientFactory, ILogger<PushDomainService> logger, IMapper mapper, IConfiguration config)
     {
         _messageRepository = messageRepository;
+        _httpClientFactory = httpClientFactory;
+        _logger = logger;
         _mapper = mapper;
+        _config = config;
+        accountInfo = _config.GetSection("SmsAccountInfo").Get<SmsAccountInfo>();
     }
 
     /// <summary>
@@ -36,6 +56,7 @@ public class PushDomainService : IPushDomainService, IScopeDependency
             throw UserFriendlyException.SameMessage("消息不能为空!");
         }
 
+        #region 替换模板内容
         //如果模板为空,参数为空则直接用短信内容
         if (!string.IsNullOrEmpty(messageDto.Template) && messageDto.Params.Count > 0)
         {
@@ -54,10 +75,239 @@ public class PushDomainService : IPushDomainService, IScopeDependency
 
             messageDto.Content = Template;
         }
+        #endregion
+
         var message = _mapper.Map<Message>(messageDto);
-        //写入本地数据库
-        var id = await _messageRepository.AddAsync(message);
 
         //调用短信发送业务
+        var strResult = await SmsSend(messageDto);
+
+        //处理返回值
+        if (true == strResult.Contains("ok,"))  // ok,35797257
+        {
+            string[] strArray = strResult.Split(',');
+            if (2 == strArray.Length)
+            {
+                // 短信服务中心返回 IFSFWID,用于短信发送状态接收时匹配
+                message.SmsWaitSendingId = strArray[1];
+                message.SendState = Share.Enums.Push.ESendState.Sending;
+            }
+        }
+        else
+        {
+            message.Remark = strResult;
+        }
+        //写入本地数据库
+        await _messageRepository.AddAsync(message);
+    }
+
+    #region 查询账号短信剩余量
+    /// <summary>
+    /// 查询账号短信剩余量
+    /// </summary>
+    /// <returns></returns>
+    public async Task<string> GetAccountNum()
+    {
+        // 账户,密码
+        string strAcceptContent = $"{accountInfo.AccountUser},{accountInfo.AccountPwd}";
+        // 参数
+        Dictionary<string, string> dicParam = new()
+        {
+            { "AcceptType", "fwAccountNum" },
+            { "AcceptContent", strAcceptContent }
+        };
+        // 将参数转化为HttpContent
+        HttpContent content = new FormUrlEncodedContent(dicParam);
+        // ok,9
+        // error,查询异常
+        string strResult = await PostHelper(accountInfo.MessageServerUrl, content);
+        var strArray = strResult.Split(",");
+        if (2 == strArray.Length)
+        {
+            strResult = strArray[1];
+        }
+        else
+        {
+            strResult = "查询异常";
+        }
+        return strResult;
     }
+    #endregion
+
+    /// <summary>
+    /// 短信发送状态回调接口
+    /// </summary>
+    /// <param name="account">短信回传账号</param>
+    /// <param name="pswd">短信回传密码</param>
+    /// <param name="msgid">短信中心短信待发送ID,同短信发送接口返回值ID</param>
+    /// <param name="sendtime">短信发送时间</param>
+    /// <param name="msgcount">发送短信使用的数量</param>
+    /// <param name="state">发送状态:0:未发送  1:发送中  2:发送失败  3:发送成功</param>
+    /// <param name="errormsg">错误消息</param>
+    /// <param name="sfid">短信中心短信已发送ID</param>
+    /// <param name="telnumall">手机号码</param>
+    /// <param name="sign">短信签名</param>
+    /// <returns></returns>
+    public async Task<string> ReceiveObtain(string account, string pswd, string msgid, string sendtime, int msgcount, int state, string errormsg, int sfid, string telnumall, string sign)
+    {
+        string strResult = "error,缺少参数";
+        if (account != accountInfo.ReturnAccountUser || pswd != accountInfo.ReturnAccountPwd)
+        {
+            strResult = "error,参数错误";
+            return strResult;
+        }
+        if (true == string.IsNullOrEmpty(account) || true == string.IsNullOrEmpty(pswd)
+            || true == string.IsNullOrEmpty(msgid) || true == string.IsNullOrEmpty(sendtime)
+            || msgcount <= 0 || state < 0)
+        {
+            strResult = "error,参数错误";
+            return strResult;
+        }
+
+        //修改数据
+        var data = await _messageRepository.GetAsync(p => p.SmsWaitSendingId == msgid);
+        if (data != null)
+        {
+            data.SendTime = Convert.ToDateTime(sendtime);
+            data.MsgCount = msgcount;
+            data.SendState = (ESendState)state;
+            data.SmsSendingCompletedId = sfid + "";
+            data.Remark = errormsg;
+            await _messageRepository.UpdateAsync(data);
+            // 成功返回值必须是ok
+            strResult = "ok";
+        }
+        else
+            strResult = "error,调用失败";
+
+        return strResult;
+    }
+
+    #region 私有方法
+
+    #region 短信发送
+    /// <summary>
+    /// 短信发送
+    /// </summary>
+    /// <returns></returns>
+    private async Task<string> SmsSend(MessageDto messageDto)
+    {
+        string strResult = "";
+        try
+        {
+            SendSmsModelDto tSms = new()
+            {
+                // 业务系统短信ID
+                nWaitID = 1,
+                // 短信类型1:默认2:营销
+                strType = "1",
+                // 业务系统工单ID
+                nFlowID = 0,
+                // 姓名
+                strPhoneName = messageDto.Name,
+                // 手机号码
+                strPhoneNumber = messageDto.TelNumber,
+                // 短信内容
+                strPhoneConnent = messageDto.Content,
+                // 添加时间
+                dAddTime = DateTime.Now,
+                strPhoneNumberAll = "",
+                strRemark = "",
+                // 业务系统添加人ID
+                nUserID = 0,
+                // 业务系统添加人姓名
+                strUserName = "",
+                // 业务系统添加部门ID
+                nBMID = 0,
+                // 业务系统添加部门名称
+                strBMName = "",
+                // 短信系统账号
+                strAccountUser = accountInfo.AccountUser,
+                // 短信系统密码
+                strAccountPwd = accountInfo.AccountPwd
+            };
+            // 序列化
+            string strSendJson = Newtonsoft.Json.JsonConvert.SerializeObject(tSms);
+            // 参数加密
+            string strKey = await FwEncrypt(strSendJson);
+            // 短信发送参数
+            Dictionary<string, string> dicParam = new Dictionary<string, string>
+            {
+                { "AcceptType", "fwSms" },
+                { "AcceptContent", strKey }
+            };
+            // 将参数转化为HttpContent
+            HttpContent content = new FormUrlEncodedContent(dicParam);
+            strResult = await PostHelper(accountInfo.MessageServerUrl, content);
+        }
+        catch (Exception ex)
+        {
+            strResult = ex.Message;
+        }
+        return strResult;
+    }
+    #endregion
+
+    #region 参数加密
+    /// <summary>
+    /// 参数加密
+    /// </summary>
+    /// <param name="planText"></param>
+    /// <returns></returns>
+    private async Task<string> FwEncrypt(string planText)
+    {
+        // 参数
+        Dictionary<string, string> dicParam = new Dictionary<string, string>();
+        dicParam.Add("AcceptType", "fwEncrypt");
+        dicParam.Add("AcceptContent", planText);
+        // 将参数转化为HttpContent
+        HttpContent content = new FormUrlEncodedContent(dicParam);
+        string strResult = await PostHelper(accountInfo.MessageServerUrl, content);
+        string strValue = "";
+        // ok,XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+        if (true == strResult.Contains("ok,"))
+        {
+            string[] strArray = strResult.Split(',');
+            if (2 == strArray.Length)
+            {
+                strValue = strArray[1];
+            }
+        }
+        return strValue;
+    }
+    #endregion
+
+    #region http请求
+    /// <summary>
+    /// 封装使用HttpClient调用WebService
+    /// </summary>
+    /// <param name="url">URL地址</param>
+    /// <param name="content">参数</param>
+    /// <returns></returns>
+    private async Task<string> PostHelper(string url, HttpContent content)
+    {
+        var result = string.Empty;
+        try
+        {
+            using (var client = _httpClientFactory.CreateClient())
+            using (var response = await client.PostAsync(url, content))
+            {
+                if (response.StatusCode == HttpStatusCode.OK)
+                {
+                    result = await response.Content.ReadAsStringAsync();
+                    XmlDocument doc = new XmlDocument();
+                    doc.LoadXml(result);
+                    result = doc.InnerText;
+                }
+            }
+        }
+        catch (Exception ex)
+        {
+            result = ex.Message;
+        }
+        return result;
+    }
+    #endregion
+
+    #endregion
 }

+ 15 - 0
src/Hotline/Settings/SysDicTypeConsts.cs

@@ -104,4 +104,19 @@ public class SysDicTypeConsts
     /// 投诉举报目标
     /// </summary>
     public const string AffairTarget = "AffairTarget";
+
+    /// <summary>
+    /// 回访满意度
+    /// </summary>
+    public const string VisitSatisfaction = "VisitSatisfaction";
+
+    /// <summary>
+    /// 不满意原因
+    /// </summary>
+    public const string DissatisfiedReason = "DissatisfiedReason";
+
+    /// <summary>
+    /// 回访态度
+    /// </summary>
+    public const string VisitManner = "VisitManner";
 }

+ 7 - 0
src/Wex.Sdk/IWexClient.cs

@@ -36,6 +36,13 @@ namespace Wex.Sdk
         public TData Data { get; set; }
     }
 
+
+    public class WexResponsePage<TData>: WexResponse<TData>
+    {
+        public int Count { get; set; }
+    }
+
+
     public interface IWexRequest
     {
         [JsonIgnore]

+ 6 - 0
src/Wex.Sdk/Tel/IWexClient.Tel.cs

@@ -11,5 +11,11 @@ namespace Wex.Sdk
     {
         Task<WexResponse<List<QueryTelResponse>>?> QueryTelsAsync(QueryTelRequest request, CancellationToken cancellationToken) =>
             ExecuteAsync<QueryTelRequest, WexResponse<List<QueryTelResponse>>>(request, cancellationToken);
+
+        Task<WexResponse<List<QueryGroupResponse>>?> QueryGroupAsync(QueryGroupRequest request, CancellationToken cancellationToken) =>
+            ExecuteAsync<QueryGroupRequest, WexResponse<List<QueryGroupResponse>>>(request, cancellationToken);
+
+        Task<WexResponsePage<List<QueryTelResponse>>?> QueryTelsPageAsync(QueryTelPageRequest request, CancellationToken cancellationToken) =>
+            ExecuteAsync<QueryTelPageRequest, WexResponsePage<List<QueryTelResponse>>>(request, cancellationToken);
     }
 }

+ 16 - 0
src/Wex.Sdk/Tel/QueryGroupRequest.cs

@@ -0,0 +1,16 @@
+using System.Text.Json.Serialization;
+
+namespace Wex.Sdk.Tel
+{
+    public class QueryGroupRequest : IWexRequest
+    {
+        public QueryGroupRequest()
+        {
+            Url = "api/zgroup/list";
+        }
+
+
+        [JsonIgnore]
+        public string Url { get; }
+    }
+}

+ 21 - 0
src/Wex.Sdk/Tel/QueryGroupResponse.cs

@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Text.Json.Serialization;
+using System.Threading.Tasks;
+
+namespace Wex.Sdk.Tel
+{
+    public class QueryGroupResponse
+    {
+        [JsonPropertyName("zgroupID")]
+        public int GroupId { get; set; }
+
+        [JsonPropertyName("zgroupName")]
+        public string GroupName { get; set; }
+
+        [JsonPropertyName("zoutGroupName")]
+        public string ZuoGroupName { get; set; }
+    }
+}

+ 89 - 0
src/Wex.Sdk/Tel/QueryTelPageRequest.cs

@@ -0,0 +1,89 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Text.Json.Serialization;
+using System.Threading.Tasks;
+
+namespace Wex.Sdk.Tel
+{
+    public class QueryTelPageRequest : IWexRequest
+    {
+        public QueryTelPageRequest()
+        {
+            Url = "api/reg/device_page";
+        }
+
+        [JsonIgnore]
+        public string Url { get; }
+
+        /// <summary>
+        /// 当前页码
+        /// </summary>
+        public int PageIndex { get; set; }
+
+        /// <summary>
+        /// 分页大小
+        /// </summary>
+        public int PageSize { get; set; }
+
+        /// <summary>
+        /// 排序字段
+        /// </summary>
+        public string SortFiled { get; set; }
+
+        /// <summary>
+        /// 是否升序
+        /// </summary>
+        public bool AsAsc { get; set; }
+
+        /// <summary>
+        /// 条件
+        /// </summary>
+        public Input input { get; set; }
+    }
+
+
+    public class Input
+    {
+        /// <summary>
+        /// 设备号
+        /// </summary>
+        [JsonPropertyName("device")]
+        public string TelNo { get; set; }
+
+        /// <summary>
+        /// 会议ID
+        /// </summary>
+        public string MeetId { get; set; }
+
+        /// <summary>
+        /// 分机状态
+        /// </summary>
+        public int? Status { get; set; }
+
+        /// <summary>
+        /// 工号
+        /// </summary>
+        [JsonPropertyName("zLoginNumber")]
+        public string StaffNo { get; set; }
+
+        /// <summary>
+        /// 角色组别
+        /// </summary>
+        [JsonPropertyName("zRole")]
+        public string Roles { get; set; }
+
+        /// <summary>
+        /// 是否签入
+        /// </summary>
+        public int? Sigin { get; set; }
+
+        /// <summary>
+        /// 坐席状态
+        /// </summary>
+        public int? AgentStatus { get; set; }
+    }
+
+
+}