Procházet zdrojové kódy

Merge branch 'develop' of http://132.232.92.186:3000/XinXiBu/OA2023 into develop

Lyyyi před 9 hodinami
rodič
revize
36948b5cfb

+ 351 - 15
OASystem/OASystem.Api/Controllers/PersonnelModuleController.cs

@@ -48,6 +48,7 @@ namespace OASystem.API.Controllers
         private readonly FeeAuditRepository _feeAuditRep;
         private readonly IDeepSeekService _deepSeekService;
 
+        private readonly ILogger<PersonnelModuleController> _logger;
         private readonly string url;
         private readonly string path;
 
@@ -65,6 +66,7 @@ namespace OASystem.API.Controllers
         /// <param name="otherPaymentRep"></param>
         /// <param name="feeAuditRep"></param>
         /// <param name="deepSeekService"></param>
+        /// <param name="logger"></param>
         public PersonnelModuleController(
             IHubContext<ChatHub, IChatClient> hubContext,
             IMapper mapper,
@@ -76,7 +78,8 @@ namespace OASystem.API.Controllers
             SqlSugarClient sqlSugar,
             DecreasePaymentsRepository otherPaymentRep,
             FeeAuditRepository feeAuditRep,
-            IDeepSeekService deepSeekService
+            IDeepSeekService deepSeekService,
+            ILogger<PersonnelModuleController> logger
             )
         {
             _mapper = mapper;
@@ -100,6 +103,7 @@ namespace OASystem.API.Controllers
             _otherPaymentRep = otherPaymentRep;
             _feeAuditRep = feeAuditRep;
             _deepSeekService = deepSeekService;
+            _logger = logger;
         }
 
         #region 工资表单
@@ -3123,6 +3127,26 @@ OPTION (MAXRECURSION 0); -- 允许无限递归      ";
 
         #region Ai绩效分析
 
+        public class AiResponse
+        {
+            /// <summary>
+            /// 业务分析答案
+            /// </summary>
+            public string Answer { get; set; }
+            /// <summary>
+            /// 考勤分析答案
+            /// </summary>
+            public string KaoqinAnswer { get; set; }
+        }
+
+        /// <summary>
+        /// 市场部员工分析
+        /// </summary>
+        /// <param name="userId"></param>
+        /// <param name="start"></param>
+        /// <param name="end"></param>
+        /// <param name="createUserId"></param>
+        /// <returns></returns>
         [HttpGet]
         public async Task<IActionResult> AiPerformanceAnalysis_JobMarketingAsync
         (int userId, DateTime start, DateTime end, int createUserId)
@@ -3252,13 +3276,10 @@ OPTION (MAXRECURSION 0); -- 允许无限递归      ";
                     return Ok(jw);
                 }
 
-                jw.Data = new
+                jw.Data = new AiResponse()
                 {
-                    // checkin_data = checkin_data.checkindata,
-                    // approval_data = approval_data.sp_no_list,
-                    // detailViews = detailViews,
                     Answer = resp.Answer,
-                    kaoqinAnswer = kaoqinResp.Answer
+                    KaoqinAnswer = kaoqinResp.Answer
                 };
 
                 //保存至数据库中
@@ -3288,6 +3309,261 @@ OPTION (MAXRECURSION 0); -- 允许无限递归      ";
             return Ok(jw);
         }
 
+
+        /// <summary>
+        /// AI员工绩效分析
+        /// </summary>
+        /// <param name="userId">用户ID</param>
+        /// <param name="start">开始时间</param>
+        /// <param name="end">结束时间</param>
+        /// <param name="createUserId">创建人ID</param>
+        /// <param name="processType">流程类型:3=机票,4=酒店</param>
+        /// <returns></returns>
+        private async Task<IActionResult> AiPerformanceAnalysisAsync(int userId, DateTime start, DateTime end, int createUserId, GroupProcessType processType)
+        {
+            var jw = JsonView(false);
+
+            var positionConfig = new Dictionary<GroupProcessType, (int TemplateId, string ProcessName, string Rules, int? TaskAssignmentCTId)>
+            {
+                {
+                    GroupProcessType.AirTicket,
+                    (
+                        TemplateId: 1464,
+                        ProcessName: "机票",
+                        Rules: @"1、初步拟定航程方案及价格 (建团后确认出团的时候开始24小时内)
+2、机票占位、续位
+3、完成机票采购确认(含预算核对、出票确认等)
+4、进行出票操作并核查信息
+5、机票已出(团组出发前5日)
+6、完成机票选座(出发前24小时)",
+                        TaskAssignmentCTId: 85  // 机票需要筛选任务分配
+                    )
+                },
+                {
+                    GroupProcessType.Hotel,
+                    (
+                        TemplateId: 1465,
+                        ProcessName: "酒店",
+                        Rules: @"1、筛选并按照预算标准,对目标酒店进行询价、比价、谈价 (建团后确认出团的时候开始) 2个工作日
+2、获取酒店确认函与入住名单核对
+3、预订酒店并录入OA
+4、行前再次确认酒店订单、付款状态及入住安排(团组出发前5日)
+5、行程结束后整理酒店发票与结算(团组结束后5天内)",
+                        TaskAssignmentCTId: null  // 酒店不需要筛选任务分配
+                    )
+                }
+            };
+
+            var supportedProcessTypes = positionConfig.Keys.ToArray();
+
+            if (!supportedProcessTypes.Contains(processType))
+            {
+                var supportedTypes = string.Join("、", supportedProcessTypes.Select(x => $"{positionConfig[x].ProcessName}({(int)x})"));
+                jw.Msg = $"不支持的流程类型!仅支持:{supportedTypes}";
+                return Ok(jw);
+            }
+
+            var config = positionConfig[processType];
+
+            var user_entity = _sqlSugar.Queryable<Sys_Users>()
+                              .First(e => e.Id == userId && e.IsDel == 0);
+
+            if (user_entity == null)
+            {
+                jw.Msg = "用户不存在!";
+                return Ok(jw);
+            }
+
+            List<string> useridlist = new List<string> { user_entity.QiyeChatUserId };
+
+            // 获取考勤信息
+            var checkin_data = await _qiYeWeChatApiService.GetCheckinDataAsync(useridlist, 1, start, end);
+            if (checkin_data.errcode != 0)
+            {
+                jw.Msg = "获取企微上下班考勤打卡信息失败!" + checkin_data.errmsg;
+                return Ok(jw);
+            }
+
+            // AI绩效分析(考勤)
+            string question = string.Empty;
+            var settingTemp = await _sqlSugar.Queryable<Sys_SetData>().FirstAsync(x => x.Id == 1462 && x.IsDel == 0);
+            if (settingTemp != null && !string.IsNullOrEmpty(settingTemp.Remark))
+            {
+                var stringFormatResp = await GeneralMethod.StringFormatAsync(new StringFormatDto
+                {
+                    FormatTemplate = settingTemp.Remark,
+                    Parameters = new List<string> {
+                         JsonConvert.SerializeObject(checkin_data.checkindata),
+                         user_entity.CnName
+                        }
+                });
+
+                if (stringFormatResp.Success)
+                {
+                    question = stringFormatResp.FormattedResult;
+                }
+                else
+                {
+                    jw.Msg = "获取ai绩效分析模板失败!" + stringFormatResp.Message;
+                    return Ok(jw);
+                }
+            }
+
+            var kaoqinResp = await _deepSeekService.ChatAsync(question);
+            if (!kaoqinResp.Success)
+            {
+                jw.Msg = "Ai分析用户绩效失败!" + kaoqinResp.Message;
+                return Ok(jw);
+            }
+
+            // 检索时间段团组信息
+            var group_data = await _sqlSugar.Queryable<Grp_DelegationInfo>()
+            .Where(x => x.IsDel == 0 && x.VisitDate >= start && x.VisitDate <= end)
+            .Select(x => new
+            {
+                x.TeamName,
+                x.VisitDate,
+                x.Id,
+                CreateTime = x.CreateTime,
+            })
+            .ToListAsync();
+
+            if (config.TaskAssignmentCTId.HasValue)
+            {
+                string sqlAssignment = @$"
+                    SELECT DIId FROM Grp_GroupsTaskAssignment 
+                    WHERE isdel = 0 and CTId = {config.TaskAssignmentCTId.Value} and UId = {userId}
+                    order by id desc ";
+                var assignmentList = await _sqlSugar.Ado.SqlQueryAsync<int>(sqlAssignment);
+                group_data = group_data.Where(x => assignmentList.Contains(x.Id)).ToList();
+            }
+
+            // 查询每个团的团组进程
+            string group_process_question = string.Empty;
+            string group_process_data = string.Empty;
+            foreach (var item in group_data)
+            {
+                var group_process = await _sqlSugar.Queryable<Grp_ProcessOverview>()
+                .Where(x => x.IsDel == 0 && x.GroupId == item.Id && x.ProcessType == processType)
+                .FirstAsync();
+
+                if (group_process != null)
+                {
+                    var detailList = await _sqlSugar.Queryable<Grp_ProcessNode>()
+                    .Where(x => x.IsDel == 0 && x.ProcessId == group_process.ProcessOrder)
+                    .Select(x => new
+                    {
+                        x.NodeName,
+                        x.OverallStatus,
+                        x.NodeDescTips,
+                        x.ActualDone,
+                    })
+                    .ToListAsync();
+
+                    group_process_data += $"团组名称:{item.TeamName}," +
+                    $"出访日期:{item.VisitDate.ToString("yyyy - MM - dd")},团组创建时间:{item.CreateTime.ToString("yyyy - MM - dd HH:mm:ss")},团组{config.ProcessName}进程信息如下:{JsonConvert.SerializeObject(detailList)}\r\n\r\n";
+                }
+            }
+
+            if (string.IsNullOrEmpty(group_process_data))
+            {
+                group_process_data = "该员工没有负责的团组!";
+            }
+
+            // 构建AI分析问题
+            group_process_question += @$"团组数据:
+{group_process_data} 
+
+            团组规则:
+{config.Rules}
+
+            根据我提供的团组数据以及规则(ActualDone字段为实际完成时间,OverallStatus字段为节点状态 1=未开始,2=进行中,3=已完成),判断是否按时完成每个环节(没有指定时间则只要在团组出发前完成即可),并且分析该员工{user_entity.CnName}的工作情况。";
+
+            // 使用模板格式化问题(如果存在)
+            try
+            {
+                var str = await _sqlSugar.Queryable<Sys_SetData>().FirstAsync(x => x.Id == config.TemplateId && x.IsDel == 0);
+                if (str != null && !string.IsNullOrEmpty(str.Remark))
+                {
+                    var stringFormatResp = await GeneralMethod.StringFormatAsync(new StringFormatDto
+                    {
+                        FormatTemplate = str.Remark,
+                        Parameters = new List<string> {
+                            group_process_data,
+                            user_entity.CnName
+                        }
+                    });
+
+                    if (stringFormatResp.Success)
+                    {
+                        //日志
+                        _logger.LogInformation($"模板格式化成功,模板ID:{config.TemplateId}, 时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}");
+                        group_process_question = stringFormatResp.FormattedResult;
+                    }
+                    else
+                    {
+                        _logger.LogError($"模板格式化失败,模板ID:{config.TemplateId}, 时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}, 错误:{stringFormatResp.Message}");
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                jw.Msg = "模板格式化失败!" + ex.Message;
+                return Ok(jw);
+            }
+
+            // AI分析团组进程
+            var group_process_resp = await _deepSeekService.ChatAsync(group_process_question);
+            if (!group_process_resp.Success)
+            {
+                jw.Msg = $"Ai分析用户{config.ProcessName}进程失败!" + group_process_resp.Message;
+                return Ok(jw);
+            }
+
+            jw.Code = 200;
+            jw.Msg = "操作成功!";
+            jw.Data = new
+            {
+                Answer = group_process_resp.Answer,
+                KaoqinAnswer = kaoqinResp.Answer,
+                group_process_data,
+            };
+
+            // 保存至数据库中
+            Pm_PerformanceAnalysis insertData = new Pm_PerformanceAnalysis
+            {
+                CreateTime = DateTime.Now,
+                CreateUserId = createUserId,
+                IsDel = 0,
+                Year = start.Year,
+                Month = start.Month,
+                JsonResult = JsonConvert.SerializeObject(jw.Data),
+                UserId = userId,
+            };
+
+            await _sqlSugar.Insertable(insertData).ExecuteCommandAsync();
+
+            return Ok(jw);
+        }
+
+        /// <summary>
+        /// 机票员工分析(兼容性方法,调用统一函数)
+        /// </summary>
+        [HttpGet]
+        public async Task<IActionResult> AiPerformanceAnalysis_AirTicketAsync(int userId, DateTime start, DateTime end, int createUserId)
+        {
+            return await AiPerformanceAnalysisAsync(userId, start, end, createUserId, GroupProcessType.AirTicket);
+        }
+
+        /// <summary>
+        /// 酒店员工分析(兼容性方法,调用统一函数)
+        /// </summary>
+        [HttpGet]
+        public async Task<IActionResult> AiPerformanceAnalysis_HotelAsync(int userId, DateTime start, DateTime end, int createUserId)
+        {
+            return await AiPerformanceAnalysisAsync(userId, start, end, createUserId, GroupProcessType.Hotel);
+        }
+
         class wordTable
         {
             public int 序号 { get; set; }
@@ -3301,6 +3577,14 @@ OPTION (MAXRECURSION 0); -- 允许无限递归      ";
             // public string 收款日期 { get; set; }
         };
 
+
+        /// <summary>
+        /// 市场部绩效pdf下载
+        /// </summary>
+        /// <param name="year"></param>
+        /// <param name="month"></param>
+        /// <param name="userId"></param>
+        /// <returns></returns>
         [HttpPost]
         public async Task<IActionResult> AiPerformanceAnalysis_JobMarketingFileDownAsync(int year, int month, int userId)
         {
@@ -3337,14 +3621,12 @@ OPTION (MAXRECURSION 0); -- 允许无限递归      ";
                 return Ok(jw);
             }
 
-            var jsonResult = JObject.Parse(data.JsonResult);
+            var jsonResult = JsonConvert.DeserializeObject<AiResponse>(data.JsonResult) ?? new AiResponse();
 
-            var answer = jsonResult["Answer"]?.toString();
-            var kaoqinAnswer = jsonResult["kaoqinAnswer"]?.toString();
             var tableapi = await this.AiPerformanceAnalysis_GroupStatisticsAsync(
               userId,
-                new DateTime(data.Year, data.Month, 1),
-                new DateTime(data.Year, data.Month, 1).AddMonths(1)
+                new DateTime(data.Year, 1, 1),
+                new DateTime(data.Year, 1, 1).AddYears(1)
                 );
 
             var jwValue = (((tableapi as OkObjectResult).Value) as OASystem.Domain.ViewModels.JsonView);
@@ -3378,14 +3660,14 @@ OPTION (MAXRECURSION 0); -- 允许无限递归      ";
                  new
                   WordContentItem{
                      Type = WordContentType.Markdown,
-                    MarkdownContent = answer
+                    MarkdownContent = jsonResult.Answer
                   },
                     WordContentItem.FromObjectList(tableListValue, "团组统计")
                   ,
                   new
                   WordContentItem{
                      Type = WordContentType.Markdown,
-                    MarkdownContent = kaoqinAnswer
+                    MarkdownContent = jsonResult.KaoqinAnswer
                   }
              }, $"{user_entity.CnName}_{data.Year}年{data.Month.ToString("00")}月绩效分析");
 
@@ -3395,6 +3677,60 @@ OPTION (MAXRECURSION 0); -- 允许无限递归      ";
             return Ok(jw);
         }
 
+        /// <summary>
+        /// 机票员工分析pdf下载
+        /// </summary>
+        /// <param name="year"></param>
+        /// <param name="month"></param>
+        /// <param name="userId"></param>
+        /// <returns></returns>
+        [HttpPost]
+        public async Task<IActionResult> AiPerformanceAnalysis_AirTicketFileDownAsync(int year, int month, int userId)
+        {
+            var jw = JsonView(false);
+
+            var user_entity = _sqlSugar.Queryable<Sys_Users>()
+                            .First(e => e.Id == userId && e.IsDel == 0);
+
+            if (user_entity == null)
+            {
+                jw.Msg = "用户不存在!";
+                return Ok(jw);
+            }
+
+            var data = await _sqlSugar.Queryable<Pm_PerformanceAnalysis>()
+            .Where(x => x.UserId == userId && x.Year == year && x.Month == month && x.IsDel == 0)
+            .FirstAsync();
+
+            if (data == null)
+            {
+                jw.Msg = "数据不存在!";
+                return Ok(jw);
+            }
+
+            var jsonResult = JsonConvert.DeserializeObject<AiResponse>(data.JsonResult) ?? new AiResponse();
+
+            var url = this.MarkdownToWord(new
+            List<WordContentItem>{
+                 new
+                  WordContentItem{
+                     Type = WordContentType.Markdown,
+                    MarkdownContent = jsonResult.Answer
+                  },
+                  new
+                  WordContentItem{
+                     Type = WordContentType.Markdown,
+                    MarkdownContent = jsonResult.KaoqinAnswer
+                  }
+             }, $"{user_entity.CnName}_{data.Year}年{data.Month.ToString("00")}月绩效分析");
+
+            jw.Data = new { url };
+            jw.Code = 200;
+            jw.Msg = "操作成功!";
+
+            return Ok(jw);
+        }
+
         /// <summary>
         /// Word文档内容项类型
         /// </summary>
@@ -3940,7 +4276,7 @@ OPTION (MAXRECURSION 0); -- 允许无限递归      ";
                                 builder.InsertCell();
                                 builder.CellFormat.VerticalAlignment = Aspose.Words.Tables.CellVerticalAlignment.Center;
                                 builder.CellFormat.Shading.BackgroundPatternColor = System.Drawing.Color.White;
-                                builder.ParagraphFormat.Alignment = Aspose.Words.ParagraphAlignment.Left;
+                                builder.ParagraphFormat.Alignment = Aspose.Words.ParagraphAlignment.Center;
                                 builder.Font.Size = 10.5;
                                 builder.Font.Name = "微软雅黑";
                                 builder.Write(cell ?? "");
@@ -4071,7 +4407,7 @@ OPTION (MAXRECURSION 0); -- 允许无限递归      ";
                 .FirstAsync();
             }
 
-            var result = JObject.Parse(data.JsonResult);
+            var result = JsonConvert.DeserializeObject<AiResponse>(data.JsonResult) ?? new AiResponse();
 
             return Ok(JsonView(true, "操作成功!", new
             {