Przeglądaj źródła

团组报表页面 -> 新增团组会务费用超支Excel 下载

Lyyyi 3 dni temu
rodzic
commit
062fe65944

+ 39 - 62
OASystem/OASystem.Api/Controllers/ResourceController.cs

@@ -1862,8 +1862,6 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
                     item.DelegationStr = groupNameStr;
                 }
 
-
-
                 //获取模板
                 string tempPath = (AppSettingsHelper.Get("ExcelBasePath") + "Template/商邀资料模板.xls");
                 var designer = new WorkbookDesigner();
@@ -1887,38 +1885,6 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
             }
         }
 
-        //[HttpPost]
-        //public  IActionResult EncipherInvitationOfficialActivityData()
-        //{
-
-        //    var jw = JsonView(false);
-        //    var List_DB = _sqlSugar.Queryable<Res_InvitationOfficialActivityData>().ToList();
-        //    try
-        //    {
-        //        //foreach (var item in List_DB)
-        //        //{
-        //        //    EncryptionProcessor.DecryptProperties(item);
-        //        //}
-
-        //        _sqlSugar.BeginTran();
-        //        foreach (var item in List_DB)
-        //        {
-        //            EncryptionProcessor.EncryptProperties(item);
-        //        }
-
-        //        var updateRow =  _sqlSugar.Updateable<Res_InvitationOfficialActivityData>(List_DB).ExecuteCommand();
-        //        jw = JsonView(true, "success", "修改行数:" + updateRow);
-        //        _sqlSugar.CommitTran();
-        //    }
-        //    catch (Exception ex)
-        //    {
-        //        _sqlSugar.RollbackTran();
-        //        jw.Msg = ex.Message;
-        //    }
-
-        //    return Ok(jw);
-        //}
-
         /// <summary>
         /// 根据商邀资料Id查询信息
         /// </summary>
@@ -2325,60 +2291,71 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
         [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
         public async Task<IActionResult> InvitationAIItemByName(string name)
         {
-            // 基础校验
-            if (string.IsNullOrWhiteSpace(name))
-                return Ok(JsonView(false, "请传入有效的名称!"));
+            if (string.IsNullOrWhiteSpace(name)) return Ok(JsonView(false, "名称无效"));
 
+            // 1. 获取主表记录
             var info = await _sqlSugar.Queryable<Res_InvitationAI>()
-                .Where(x => x.IsDel == 0 && x.InvName == name)
-                .FirstAsync();
+                .FirstAsync(x => x.IsDel == 0 && x.InvName == name);
 
-            var groupInfo = new Grp_DelegationInfo();
+            Grp_DelegationInfo groupInfo = null;
+            string baseUrl = AppSettingsHelper.Get("OfficeBaseUrl")?.TrimEnd('/');
 
+            // 2. 分支处理:若无主表记录,尝试从团组表同步基本信息
             if (info == null)
             {
-                groupInfo = await _sqlSugar.Queryable<Grp_DelegationInfo>().Where(x => x.IsDel == 0 && x.TeamName.Equals(name)).FirstAsync();
-
-                var entry = new EntryInfo() {
-                    OriginUnit = groupInfo?.ClientUnit ?? "",
-                    TargetCountry = _delegationInfoRep.GroupSplitCountry(groupInfo?.VisitCountry ?? ""),
-                };
+                groupInfo = await _sqlSugar.Queryable<Grp_DelegationInfo>()
+                    .FirstAsync(x => x.IsDel == 0 && x.TeamName == name);
 
-                return Ok(JsonView(true, $"暂无数据",new {
+                return Ok(JsonView(true, "暂无数据", new
+                {
                     Id = 0,
-                    GroupId= groupInfo?.Id ?? 0,
+                    GroupId = groupInfo?.Id ?? 0,
                     InvName = name,
                     AiCrawledDetails = new List<InvitationAIInfo>(),
-                    Entry = entry
+                    Entry = new
+                    {
+                        OriginUnit = groupInfo?.ClientUnit ?? "",
+                        TargetCountry = _delegationInfoRep.GroupSplitCountry(groupInfo?.VisitCountry ?? "")
+                    }
                 }));
             }
 
-            if (info.GroupId != 0)
+            // 3. 补全关联团组信息
+            if (info.GroupId > 0)
             {
-                groupInfo = await _sqlSugar.Queryable<Grp_DelegationInfo>().Where(x => x.IsDel == 0 && x.Id == info.GroupId).FirstAsync();
+                groupInfo = await _sqlSugar.Queryable<Grp_DelegationInfo>().InSingleAsync(info.GroupId);
             }
-            // 设置国家、单位默认值
+
+            // 4. 数据填充(使用 ??= 语法确保 Null 安全)
+            info.EntryInfo ??= new EntryInfo();
             if (string.IsNullOrEmpty(info.EntryInfo.OriginUnit))
-            {
                 info.EntryInfo.OriginUnit = groupInfo?.ClientUnit ?? "";
-            }
 
-            if (info.EntryInfo.TargetCountry == null || info.EntryInfo.TargetCountry.Count < 1)
+            if (info.EntryInfo.TargetCountry?.Any() != true)
+                info.EntryInfo.TargetCountry = _delegationInfoRep.GroupSplitCountry(groupInfo?.VisitCountry ?? "");
+
+            // 5. 排序:ThenByDescending 确保多级排序生效
+            info.AiCrawledDetails = info.AiCrawledDetails
+                .OrderByDescending(x => x.IsChecked)
+                .ThenByDescending(x => x.OperatedAt)
+                .ToList();
+
+            // 6. 路径映射:将相对路径转换为带域名的全路径
+            foreach (var detail in info.AiCrawledDetails.Where(d => d.EmailInfo?.AttachmentPaths?.Any() == true))
             {
-                info.EntryInfo.TargetCountry = _delegationInfoRep.GroupSplitCountry(groupInfo?.TeamName ?? "");
+                detail.EmailInfo.AttachmentPaths = detail.EmailInfo.AttachmentPaths
+                    .Select(path => path.StartsWith("http") ? path : $"{baseUrl}/{path.TrimStart('/')}")
+                    .ToList();
             }
 
-            info.AiCrawledDetails = info.AiCrawledDetails.OrderByDescending(x => x.IsChecked).OrderByDescending(x => x.OperatedAt).ToList();
-
-            var view = new
+            return Ok(JsonView(true, "查询成功!", new
             {
                 info.Id,
                 info.GroupId,
                 info.InvName,
                 info.AiCrawledDetails,
-                Entry = info.EntryInfo,
-            };
-            return Ok(JsonView(true, $"查询成功!", view));
+                Entry = info.EntryInfo
+            }));
         }
 
         /// <summary>

+ 63 - 88
OASystem/OASystem.Api/Controllers/StatisticsController.cs

@@ -3668,35 +3668,24 @@ ORDER BY
 
         }
 
-        //SubmitAndValidateExpensesAsync
-
         /// <summary>
         /// 团组报表 - 会务超支验证excel下载
         /// </summary>
         /// <param name="dto"></param>
         /// <returns></returns>
         [HttpPost("PostGroupStatementValidateExpenses")]
-        [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
         public async Task<IActionResult> PostGroupStatementValidateExpenses(PostGroupStatementValidateExpensesDto dto)
         {
-            // 1. 严格参数校验
-            if (dto.GroupId <= 0 || dto.CurrUserId <= 0)
-                return Ok(JsonView(false, "参数非法"));
-
-            // 2. 业务校验
-            var isConfGroup = await _sqlSugar.Queryable<Grp_DelegationInfo, Sys_SetData>(
-                    (g, s) => g.TeamDid == s.Id 
-                )
-                .Where((g, s) => g.IsDel == 0
-                              && g.Id == dto.GroupId
-                              && s.STid == 10
-                              && s.Name.Contains("会务活动"))
-                .AnyAsync(); 
+            // 1. 参数与业务校验 
+            if (dto.GroupId <= 0 || dto.CurrUserId <= 0) return Ok(JsonView(false, "参数非法"));
+
+            var isConfGroup = await _sqlSugar.Queryable<Grp_DelegationInfo, Sys_SetData>((g, s) => g.TeamDid == s.Id)
+                .Where((g, s) => g.IsDel == 0 && g.Id == dto.GroupId && s.STid == 10 && s.Name.Contains("会务活动"))
+                .AnyAsync();
 
             if (!isConfGroup) return Ok(JsonView(false, "当前团组非会务团组,禁止操作"));
 
-            // 3. 异步并行查询:获取实际支付(Fee)与预算成本(Expense)
-            // 实际支付列表
+            // 2. 数据查询与处理
             var feeList = await _sqlSugar.Queryable<Grp_DecreasePayments>()
                 .InnerJoin<Grp_CreditCardPayment>((x, y) => x.Id == y.CId && x.DiId == y.DIId && y.CTable == 98)
                 .LeftJoin<Sys_Users>((x, y, z) => x.CreateUserId == z.Id)
@@ -3715,7 +3704,6 @@ ORDER BY
 
             if (!feeList.Any()) return Ok(JsonView(false, "未检索到实际费用记录"));
 
-            // 成本预算列表
             var expenseData = await _sqlSugar.Queryable<Grp_ConferenceAffairsCostChild>()
                 .InnerJoin<Grp_ConferenceAffairsCost>((x, y) => x.ConferenceAffairsCostId == y.Id)
                 .Where((x, y) => y.IsDel == 0 && y.Diid == dto.GroupId && x.ReviewStatus == 1)
@@ -3724,18 +3712,13 @@ ORDER BY
 
             var expenseDict = expenseData.ToDictionary(k => k.PriceName, v => v.Total);
 
-            if (!feeList.Any()) return Ok(JsonView(false, "未检索到实际费用记录"));
-
-            feeList = feeList.OrderBy(x => x.PriceName).ThenBy(x => x.ApplicantTime).ToList();
-
+            // 数据转换逻辑
             int index = 1;
-            // 4. 数据处理
-            foreach (var item in feeList)
+            foreach (var item in feeList.OrderBy(x => x.PriceName).ThenBy(x => x.ApplicantTime))
             {
-                item.Index = index;
+                item.Index = index++;
                 item.AuditStatus = MapAuditStatus(item.AuditStatus);
                 item.PayLabel = item.PayLabel == "1" ? "已支付" : "未支付";
-
                 if (expenseDict.TryGetValue(item.PriceName, out decimal budget))
                 {
                     item.ExpenseEntry = budget;
@@ -3743,36 +3726,39 @@ ORDER BY
                 }
                 else
                 {
-                    if (item.FeeEntry <= 0) item.OverspentLabel = "未超支";
-                    else item.OverspentLabel = "成本未录入项(系统判定超支)";
+                    item.OverspentLabel = item.FeeEntry <= 0 ? "未超支" : "成本未录入项(系统判定超支)";
                 }
-                index++;
             }
 
-            // 导出动作与异常处理
+            // 3. 路径与导出
             try
             {
-                var groupName = _sqlSugar.Queryable<Grp_DelegationInfo>().First(x => x.Id == dto.GroupId)?.TeamName ?? "未知团组";
+                var groupName = await _sqlSugar.Queryable<Grp_DelegationInfo>().Where(x => x.Id == dto.GroupId).Select(x => x.TeamName).FirstAsync() ?? "未知团组";
+
+                string baseUrl = AppSettingsHelper.Get("ExcelBaseUrl").TrimEnd('/');
+                string basePath = AppSettingsHelper.Get("ExcelBasePath").TrimEnd('\\').TrimEnd('/');
+                string fptPath = AppSettingsHelper.Get("ExcelFtpPath");
+                string dirName = "GroupConf";
 
-                string fileName = $"会务校验_{groupName}_{DateTime.Now:yyyyMMddHHmm}.xlsx";
+                // 目录不存在时创建
+                string targetDir = Path.Combine(basePath, dirName);
+                if (!Directory.Exists(targetDir)) Directory.CreateDirectory(targetDir);
 
-                // Excel导出:调用通用方法,传入数据列表与文件名
-                byte[] fileData = ExportToExcel(feeList,"超支验证明细");
+                string fileName = $"{groupName}_会务校验_{DateTime.Now:yyyyMMddHHmm}.xlsx";
+                string fullPath = Path.Combine(targetDir, fileName);
 
-                if (fileData == null || fileData.Length == 0)
+                // 生成文件
+                var isSuccess = await ExportToExcel(feeList, fullPath, "超支验证明细");
+
+                if (isSuccess)
                 {
-                    return Ok(JsonView(false, "生成文件流失败,请检查数据源"));
+                    return Ok(JsonView(true, "操作成功", new { url = $"{baseUrl}{fptPath}{dirName}/{fileName}" }));
                 }
-
-                return File(
-                    fileData,
-                    "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
-                    fileName
-                );
+                return Ok(JsonView(false, "文件生成失败"));
             }
             catch (Exception ex)
             {
-                return Ok(JsonView(false, $"导出过程中发生系统错误:{ex.Message}"));
+                return Ok(JsonView(false, $"导出异常:{ex.Message}"));
             }
         }
 
@@ -3785,72 +3771,61 @@ ORDER BY
             _ => "未知"
         };
 
-        public static byte[] ExportToExcel<T>(IEnumerable<T> data, string sheetName)
+        public static async Task<bool> ExportToExcel<T>(IEnumerable<T> data, string absolutePath, string sheetName)
         {
             Workbook workbook = new Workbook();
             Worksheet sheet = workbook.Worksheets[0];
             sheet.Name = sheetName;
-
             PropertyInfo[] props = typeof(T).GetProperties();
 
-            // 定义全局字体名称
-            const string MicrosoftYaHei = "微软雅黑";
+            // 1. 表头处理
+            Style headerStyle = workbook.CreateStyle();
+            headerStyle.Font.Name = "微软雅黑";
+            headerStyle.Font.IsBold = true;
+            headerStyle.Pattern = BackgroundType.Solid;
+            headerStyle.ForegroundColor = System.Drawing.Color.LightGray;
+            headerStyle.HorizontalAlignment = TextAlignmentType.Center;
 
-            // --- 1. 表头处理 ---
             for (int i = 0; i < props.Length; i++)
             {
-                var displayAttr = props[i].GetCustomAttribute<DisplayAttribute>();
-                string headerName = displayAttr?.Name ?? props[i].Name;
-
-                Aspose.Cells.Cell cell = sheet.Cells[0, i];
-                cell.PutValue(headerName);
-
-                // 设置表头样式:加粗 + 微软雅黑
-                Style style = cell.GetStyle();
-                style.Font.Name = MicrosoftYaHei;
-                style.Font.IsBold = true;
-                style.Font.Size = 11;
-                style.BackgroundColor = System.Drawing.Color.FromArgb(235, 235, 235);
-                style.Pattern = BackgroundType.Solid;
-                style.HorizontalAlignment = TextAlignmentType.Center;
-                cell.SetStyle(style);
+                var attr = props[i].GetCustomAttribute<DisplayAttribute>();
+                sheet.Cells[0, i].PutValue(attr?.Name ?? props[i].Name);
+                sheet.Cells[0, i].SetStyle(headerStyle);
             }
 
-            // --- 2. 数据处理 ---
-            var dataList = data.ToList();
-            for (int rowIndex = 0; rowIndex < dataList.Count; rowIndex++)
+            // 2. 数据处理
+            var list = data.ToList();
+            for (int r = 0; r < list.Count; r++)
             {
-                var item = dataList[rowIndex];
-                for (int colIndex = 0; colIndex < props.Length; colIndex++)
+                for (int c = 0; c < props.Length; c++)
                 {
-                    var value = props[colIndex].GetValue(item);
-                    Aspose.Cells.Cell cell = sheet.Cells[rowIndex + 1, colIndex];
-                    cell.PutValue(value);
+                    var val = props[c].GetValue(list[r]);
+                    Aspose.Cells.Cell cell = sheet.Cells[r + 1, c];
+                    cell.PutValue(val);
 
-                    // 设置单元格基础样式:微软雅黑
-                    Style cellStyle = cell.GetStyle();
-                    cellStyle.Font.Name = MicrosoftYaHei;
-                    cellStyle.Font.Size = 10;
-
-                    // 成功/错误验证:高亮逻辑
-                    if (props[colIndex].Name == "OverspentLabel" && value?.ToString()?.Contains("超支") == true)
+                    // 高亮逻辑:仅在超支单元格设置红色
+                    if (props[c].Name == "OverspentLabel" && val?.ToString()?.Contains("超支") == true)
                     {
-                        cellStyle.Font.Color = System.Drawing.Color.Red;
-                        cellStyle.Font.IsBold = true;
+                        Style s = cell.GetStyle();
+                        s.Font.Color = System.Drawing.Color.Red;
+                        s.Font.IsBold = true;
+                        cell.SetStyle(s);
                     }
-
-                    cell.SetStyle(cellStyle);
                 }
             }
-
             sheet.AutoFitColumns();
 
-            // --- 3. 转化为文件流 ---
-            using (MemoryStream ms = new MemoryStream())
+            // 3. 物理保存
+            try
             {
-                workbook.Save(ms, SaveFormat.Xlsx);
-                return ms.ToArray();
+                using (FileStream fs = new FileStream(absolutePath, FileMode.Create, FileAccess.Write, FileShare.None))
+                {
+                    workbook.Save(fs, SaveFormat.Xlsx);
+                    await fs.FlushAsync();
+                }
+                return System.IO.File.Exists(absolutePath);
             }
+            catch { return false; }
         }
 
         #endregion

+ 1 - 1
OASystem/OASystem.Api/OAMethodLib/Hotmail/HotmailService.cs

@@ -210,7 +210,7 @@ namespace OASystem.API.OAMethodLib.Hotmail
                             {
                                 Name = fileName,
                                 ContentBytes = contentBytes,
-                                ContentType = MimeTypes.GetMimeType(fileName) // 需要安装 MimeTypes 库或手动判断
+                                ContentType = MimeTypes.GetMimeType(fileName) 
                             });
                         }
                     }

+ 1 - 1
OASystem/OASystem.Domain/Entities/Resource/Res_InvitationAI.cs

@@ -213,7 +213,7 @@ namespace OASystem.Domain.Entities.Resource
         /// <summary>
         /// 附件地址
         /// </summary>
-        public List<string> AttachmentPaths { get; set; }
+        public List<string> AttachmentPaths { get; set; } = new List<string>();
 
         /// <summary>
         /// 发送状态