Преглед изворни кода

团组流程进度总览日志 ->建表、建实体类、逻辑代码编写

Lyyyi пре 1 дан
родитељ
комит
fca41b07f6

+ 2 - 1
OASystem/EntitySync/Program.cs

@@ -175,6 +175,7 @@ db.CodeFirst.SetStringDefaultLength(50).BackupTable().InitTables(new Type[]
     //typeof(Res_VisaFeeStandardDetails),//签证费用标准详情 
     //typeof(Grp_ProcessOverview),//团组流程总览表
     //typeof(Grp_ProcessNode),//流程节点
-    typeof(Grp_VisaProcessSteps_Log),//流程节点
+    //typeof(Grp_VisaProcessSteps_Log),//流程节点 
+    typeof(Grp_ProcessLog),//流程节点 
 });
 Console.WriteLine("数据库结构同步完成!");

+ 66 - 2
OASystem/OASystem.Domain/Entities/Groups/Grp_ProcessOverview.cs

@@ -6,7 +6,7 @@ using System.ComponentModel;
 namespace OASystem.Domain.Entities.Groups
 {
     /// <summary>
-    /// 团组流程总览
+    /// 团组流程总览
     /// </summary>
     [SugarTable("Grp_ProcessOverview", "团组流程总览表")]
     public class Grp_ProcessOverview : EntityBase
@@ -268,7 +268,6 @@ namespace OASystem.Domain.Entities.Groups
         //public Grp_ProcessOverview Process { get; set; }
     }
 
-
     public class VisaProcessNode
     {
         /// <summary>
@@ -385,4 +384,69 @@ namespace OASystem.Domain.Entities.Groups
             return new VisaProcessNode() { OrderNo = orderNo, CountryName = countryName };
         }
     }
+
+    /// <summary>
+    /// 团组流程操作日志
+    /// </summary>
+    [SugarTable("Grp_ProcessLog", "团组流程总览操作日志")]
+    public class Grp_ProcessLog : EntityBase
+    {
+        /// <summary>
+        /// 流程ID
+        /// </summary>
+        [SugarColumn(ColumnName = "ProcessId", ColumnDescription = "流程ID", IsNullable = true, ColumnDataType = "int")]
+        public int? ProcessId { get; set; }
+
+        /// <summary>
+        /// 节点ID
+        /// </summary>
+        [SugarColumn(ColumnName = "NodeId", ColumnDescription = "节点ID", IsNullable = true, ColumnDataType = "int")]
+        public int? NodeId { get; set; }
+
+        /// <summary>
+        /// 团组ID
+        /// </summary>
+        [SugarColumn(ColumnName = "GroupId", ColumnDescription = "团组ID", IsNullable = true, ColumnDataType = "int")]
+        public int GroupId { get; set; }
+
+        /// <summary>
+        /// 操作类型
+        /// </summary>
+        [SugarColumn(ColumnName = "OpType", ColumnDescription = "操作类型", ColumnDataType = "varchar(50)")]
+        public string OpType { get; set; }
+
+        /// <summary>
+        /// 操作描述
+        /// </summary>
+        [SugarColumn(ColumnName = "OpDesc", ColumnDescription = "操作描述", ColumnDataType = "varchar(500)")]
+        public string OpDesc { get; set; }
+
+        /// <summary>
+        /// 变更前数据
+        /// </summary>
+        [SugarColumn(ColumnName = "BeforeData", ColumnDescription = "变更前数据", IsNullable = true, ColumnDataType = "nvarchar(max)")]
+        public string BeforeData { get; set; }
+
+        /// <summary>
+        /// 变更后数据
+        /// </summary>
+        [SugarColumn(ColumnName = "AfterData", ColumnDescription = "变更后数据", IsNullable = true, ColumnDataType = "nvarchar(max)")]
+        public string AfterData { get; set; }
+
+        /// <summary>
+        /// 变更字段
+        /// </summary>
+        [SugarColumn(ColumnName = "ChgFields", ColumnDescription = "变更字段", IsNullable = true, ColumnDataType = "varchar(500)")]
+        public string ChgFields { get; set; }
+    }
+
+    /// <summary>
+    /// 字段变更详情
+    /// </summary>
+    public class FieldChgDetail
+    {
+        public string FieldName { get; set; }
+        public string BeforeValue { get; set; }
+        public string AfterValue { get; set; }
+    }
 }

+ 4 - 4
OASystem/OASystem.Domain/Enums/GroupProcessType.cs

@@ -27,22 +27,22 @@ namespace OASystem.Domain.Enums
         /// 机票
         /// </summary>
         [Description("机票")]
-        AirTicket = 4,
+        AirTicket = 3,
         /// <summary>
         /// 酒店
         /// </summary>
         [Description("酒店")]
-        Hotel = 5,
+        Hotel = 4,
         /// <summary>
         /// 地接
         /// </summary>
         [Description("地接")]
-        LocalGuide = 6,
+        LocalGuide = 5,
         /// <summary>
         /// 费用结算
         /// </summary>
         [Description("费用结算")]
-        FeeSettle = 7
+        FeeSettle = 6
     }
 
     /// <summary>

+ 447 - 16
OASystem/OASystem.Infrastructure/Repositories/Groups/ProcessOverviewRepository.cs

@@ -3,12 +3,7 @@ using Newtonsoft.Json;
 using OASystem.Domain;
 using OASystem.Domain.Dtos.Groups;
 using OASystem.Domain.Entities.Groups;
-using OASystem.Domain.ViewModels.JuHeExchangeRate;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
+using System.Reflection;
 
 namespace OASystem.Infrastructure.Repositories.Groups
 {
@@ -64,6 +59,9 @@ namespace OASystem.Infrastructure.Repositories.Groups
                     return new Result { Code = 400, Msg = "团组流程进度总览表添加失败!" };
                 }
 
+                // 记录流程日志
+                await LogProcessOpAsync(null, item, "Create", currUserId);
+
                 var nodes = item.Nodes.Select((nodeDto, index) => new Grp_ProcessNode
                 {
                     ProcessId = processId,
@@ -79,8 +77,15 @@ namespace OASystem.Infrastructure.Repositories.Groups
                 if (nodeIds < 1)
                 {
                     _sqlSugar.RollbackTran();
-                    return new Result { Code = 400, Msg = "团组流程进度流程添加失败!" };
+                    return new Result { Code = 400, Msg = "团组流程进度流程节点添加失败!" };
                 }
+
+                //记录节点日志
+                foreach (var node in nodes)
+                {
+                    await LogNodeOpAsync(null, node, "Create", currUserId);
+                }
+                
             }
             _sqlSugar.CommitTran();
             return new Result { Code = 200, Msg = "添加成功!" }; ;
@@ -178,6 +183,18 @@ namespace OASystem.Infrastructure.Repositories.Groups
                     // 新增验证:当前节点已完成,不可操作
                     ValidateNodeOperation(node, processStatus);
 
+                    //存储更新前的值
+                    var before = new Grp_ProcessNode() { 
+                        Id = node.Id,
+                        ProcessId = node.ProcessId,
+                        NodeName = node.NodeName,
+                        NodeOrder = node.NodeOrder,
+                        OverallStatus = node.OverallStatus,
+                        Operator = node.Operator,
+                        OperationTime = node.OperationTime,
+                        IsCurrent = node.IsCurrent,
+                    };
+
                     // 2. 更新节点状态
                     node.OverallStatus = processStatus;
                     node.Operator = currUserId;
@@ -197,6 +214,9 @@ namespace OASystem.Infrastructure.Repositories.Groups
                         throw new BusinessException("节点状态更新失败。");
                     }
 
+                    //记录节点日志
+                    await LogNodeOpAsync(before, node, "Update", currUserId);
+
                     // 3. 如果是完成当前节点,处理流程流转
                     if (processStatus == ProcessStatus.Completed && node.IsCurrent)
                     {
@@ -229,7 +249,7 @@ namespace OASystem.Infrastructure.Repositories.Groups
         /// </summary>
         /// <param name="node">流程节点</param>
         /// <param name="targetStatus">目标状态</param>
-        private void ValidateNodeOperation(Grp_ProcessNode node, ProcessStatus targetStatus)
+        private static void ValidateNodeOperation(Grp_ProcessNode node, ProcessStatus targetStatus)
         {
             // 验证节点是否已完成
             if (node.OverallStatus == ProcessStatus.Completed)
@@ -264,11 +284,37 @@ namespace OASystem.Infrastructure.Repositories.Groups
                 throw new BusinessException("关联的流程不存在。");
             }
 
+            var processBefore = new Grp_ProcessOverview()
+            {
+                Id = process.Id,
+                GroupId = process.GroupId,
+                ProcessOrder = process.ProcessOrder,
+                ProcessType = process.ProcessType,
+                OverallStatus = process.OverallStatus,
+                StartTime = process.StartTime,
+                EndTime = process.EndTime,
+                UpdatedUserId = process.UpdatedUserId,
+                UpdatedTime = process.UpdatedTime
+            };
+
             // 2. 取消当前节点的当前状态
+            var before = new Grp_ProcessNode()
+            {
+                Id = currentNode.Id,
+                ProcessId = currentNode.ProcessId,
+                NodeName = currentNode.NodeName,
+                NodeOrder = currentNode.NodeOrder,
+                OverallStatus = currentNode.OverallStatus,
+                Operator = currentNode.Operator,
+                OperationTime = currentNode.OperationTime,
+                IsCurrent = currentNode.IsCurrent,
+            };
             currentNode.IsCurrent = false;
             await _sqlSugar.Updateable(currentNode)
                 .UpdateColumns(n => new { n.IsCurrent })
                 .ExecuteCommandAsync();
+            // 2.1 记录节点日志 取消当前节点状态
+            await LogNodeOpAsync(before, currentNode, "Update", currUserId);
 
             // 3. 查找并激活下一个节点
             var nextNode = await _sqlSugar.Queryable<Grp_ProcessNode>()
@@ -279,6 +325,18 @@ namespace OASystem.Infrastructure.Repositories.Groups
 
             if (nextNode != null)
             {
+                var nextNodeBefore = new Grp_ProcessNode()
+                {
+                    Id = nextNode.Id,
+                    ProcessId = nextNode.ProcessId,
+                    NodeName = nextNode.NodeName,
+                    NodeOrder = nextNode.NodeOrder,
+                    OverallStatus = nextNode.OverallStatus,
+                    Operator = nextNode.Operator,
+                    OperationTime = nextNode.OperationTime,
+                    IsCurrent = nextNode.IsCurrent,
+                };
+
                 // 激活下一个节点
                 nextNode.IsCurrent = true;
                 nextNode.OverallStatus = ProcessStatus.InProgress;
@@ -300,6 +358,9 @@ namespace OASystem.Infrastructure.Repositories.Groups
                     throw new BusinessException("激活下一节点失败");
                 }
 
+                // 1.1 记录节点日志 激活下一节点当前节点状态
+                await LogNodeOpAsync(nextNodeBefore, nextNode, "Start", currUserId);
+
                 // 更新流程状态为进行中
                 process.OverallStatus = ProcessStatus.InProgress;
             }
@@ -328,8 +389,10 @@ namespace OASystem.Infrastructure.Repositories.Groups
             {
                 throw new BusinessException("流程状态更新失败。");
             }
-        }
 
+            //记录流程日志
+            await LogProcessOpAsync(processBefore, process, "Update", currUserId);
+        }
 
         /// <summary>
         /// 更新签证节点信息及状态
@@ -355,6 +418,32 @@ namespace OASystem.Infrastructure.Repositories.Groups
             // 2. 检查签证子节点 字段信息是否全部填写
             var allSubNodesCompleted = dto.VisaSubNodes?.All(subNode => EntityExtensions.IsCompleted(subNode)) ?? false;
 
+            // 2.1 存储更新前流程及节点信息
+            var nodeBefore = new Grp_ProcessNode()
+            {
+                Id = node.Id,
+                ProcessId = node.ProcessId,
+                NodeName = node.NodeName,
+                NodeOrder = node.NodeOrder,
+                OverallStatus = node.OverallStatus,
+                Operator = node.Operator,
+                OperationTime = node.OperationTime,
+                IsCurrent = node.IsCurrent,
+            };
+
+            var processBefore = new Grp_ProcessOverview()
+            {
+                Id = process.Id,
+                GroupId = process.GroupId,
+                ProcessOrder = process.ProcessOrder,
+                ProcessType = process.ProcessType,
+                OverallStatus = process.OverallStatus,
+                StartTime = process.StartTime,
+                EndTime = process.EndTime,
+                UpdatedUserId = process.UpdatedUserId,
+                UpdatedTime = process.UpdatedTime
+            };
+
             // 3. 更新节点信息
             node.Remark = JsonConvert.SerializeObject(dto.VisaSubNodes);
             node.Operator = dto.CurrUserId;
@@ -364,17 +453,23 @@ namespace OASystem.Infrastructure.Repositories.Groups
             {
                 node.OverallStatus = ProcessStatus.Completed;
 
+                process.OverallStatus = ProcessStatus.Completed;
+                process.EndTime = DateTime.Now;
+                process.UpdatedUserId = dto.CurrUserId;
+                process.UpdatedTime = DateTime.Now;
+
                 // 更新流程状态
-                await _sqlSugar.Updateable<Grp_ProcessOverview>()
-                    .SetColumns(p => new Grp_ProcessOverview
+                await _sqlSugar.Updateable(process)
+                    .UpdateColumns(p => new
                     {
-                        OverallStatus = ProcessStatus.Completed,
-                        EndTime = DateTime.Now,
-                        UpdatedUserId = dto.CurrUserId,
-                        UpdatedTime = DateTime.Now
+                        p.OverallStatus,
+                        p.EndTime,
+                        p.UpdatedUserId,
+                        p.UpdatedTime
                     })
-                    .Where(p => p.Id == process.Id)
                     .ExecuteCommandAsync();
+                //记录流程日志
+                await LogProcessOpAsync(processBefore, process, "Update", dto.CurrUserId);
             }
 
             // 4. 保存节点更新
@@ -387,8 +482,344 @@ namespace OASystem.Infrastructure.Repositories.Groups
                     n.OverallStatus
                 })
                 .ExecuteCommandAsync();
+            //记录节点日志
+            await LogNodeOpAsync(nodeBefore, node, "Update", dto.CurrUserId);
 
             return new Result { Code = 200, Msg = "节点信息更新成功。" };
         }
+
+        #region 操作日志
+
+        /// <summary>
+        /// 记录流程操作日志
+        /// </summary>
+        /// <param name="before">操作前</param>
+        /// <param name="after">操作后</param>
+        /// <param name="opType">操作类型(Create - 创建、Update - 更新、Complete - 完成)</param>
+        /// <param name="operId">操作人ID</param>
+        /// <returns>异步任务</returns>
+        public async Task LogProcessOpAsync(Grp_ProcessOverview before, Grp_ProcessOverview after,string opType, int operId)
+        {
+            var chgDetails = GetProcessChgDetails(before, after);
+
+            var log = new Grp_ProcessLog
+            {
+                ProcessId = after?.Id ?? before?.Id,
+                GroupId = after?.GroupId ?? before?.GroupId ?? 0,
+                OpType = opType,
+                OpDesc = GenerateProcessOpDesc(opType, before, after, chgDetails),
+                BeforeData = before != null ? JsonConvert.SerializeObject(before, GetJsonSettings()) : null,
+                AfterData = after != null ? JsonConvert.SerializeObject(after, GetJsonSettings()) : null,
+                ChgFields = string.Join(",", chgDetails.Select(x => x.FieldName)),
+                CreateUserId = operId
+            };
+
+            await _sqlSugar.Insertable(log).ExecuteCommandAsync();
+        }
+
+        /// <summary>
+        /// 记录节点操作日志
+        /// </summary>
+        /// <param name="before">操作前</param>
+        /// <param name="after">操作后</param>
+        /// <param name="opType">操作类型(Create - 创建、Update - 更新、Start - 启动、Complete - 完成)</param>
+        /// <param name="operId">操作人ID</param>
+        /// <returns>异步任务</returns>
+        public async Task LogNodeOpAsync(Grp_ProcessNode before, Grp_ProcessNode after,string opType, int operId)
+        {
+            var chgDetails = GetNodeChgDetails(before, after);
+
+            var log = new Grp_ProcessLog
+            {
+                NodeId = after?.Id ?? before?.Id,
+                ProcessId = after?.ProcessId ?? before?.ProcessId,
+                GroupId = 0, // 通过流程ID关联获取
+                OpType = opType,
+                OpDesc = GenerateNodeOpDesc(opType, before, after, chgDetails),
+                BeforeData = before != null ? JsonConvert.SerializeObject(before, GetJsonSettings()) : null,
+                AfterData = after != null ? JsonConvert.SerializeObject(before, GetJsonSettings()) : null,
+                ChgFields = string.Join(",", chgDetails.Select(x => x.FieldName)),
+                CreateUserId = operId
+            };
+
+            await _sqlSugar.Insertable(log).ExecuteCommandAsync();
+        }
+
+        /// <summary>
+        /// 获取流程变更详情
+        /// </summary>
+        /// <param name="before">变更前</param>
+        /// <param name="after">变更后</param>
+        /// <returns>变更详情</returns>
+        private List<FieldChgDetail> GetProcessChgDetails(Grp_ProcessOverview before, Grp_ProcessOverview after)
+        {
+            var chgDetails = new List<FieldChgDetail>();
+
+            if (before == null || after == null) return chgDetails;
+
+            var props = typeof(Grp_ProcessOverview).GetProperties(BindingFlags.Public | BindingFlags.Instance)
+                .Where(p => p.CanRead && p.CanWrite && !IsExclField(p.Name));
+
+            foreach (var prop in props)
+            {
+                var beforeVal = prop.GetValue(before);
+                var afterVal = prop.GetValue(after);
+
+                if (!Equals(beforeVal, afterVal))
+                {
+                    chgDetails.Add(new FieldChgDetail
+                    {
+                        FieldName = prop.Name,
+                        BeforeValue = FormatVal(beforeVal),
+                        AfterValue = FormatVal(afterVal)
+                    });
+                }
+            }
+
+            return chgDetails;
+        }
+
+        /// <summary>
+        /// 获取节点变更详情
+        /// </summary>
+        /// <param name="before">变更前</param>
+        /// <param name="after">变更后</param>
+        /// <returns>变更详情</returns>
+        private List<FieldChgDetail> GetNodeChgDetails(Grp_ProcessNode before, Grp_ProcessNode after)
+        {
+            var chgDetails = new List<FieldChgDetail>();
+
+            if (before == null || after == null) return chgDetails;
+
+            var props = typeof(Grp_ProcessNode).GetProperties(BindingFlags.Public | BindingFlags.Instance)
+                .Where(p => p.CanRead && p.CanWrite && !IsExclField(p.Name));
+
+            foreach (var prop in props)
+            {
+                var beforeVal = prop.GetValue(before);
+                var afterVal = prop.GetValue(after);
+
+                if (!Equals(beforeVal, afterVal))
+                {
+                    chgDetails.Add(new FieldChgDetail
+                    {
+                        FieldName = prop.Name,
+                        BeforeValue = FormatVal(beforeVal),
+                        AfterValue = FormatVal(afterVal)
+                    });
+                }
+            }
+
+            return chgDetails;
+        }
+
+        /// <summary>
+        /// 生成流程操作描述
+        /// </summary>
+        /// <param name="opType">操作类型</param>
+        /// <param name="before">操作前</param>
+        /// <param name="after">操作后</param>
+        /// <param name="chgDetails">变更详情</param>
+        /// <returns>操作描述</returns>
+        private string GenerateProcessOpDesc(string opType, Grp_ProcessOverview before,
+            Grp_ProcessOverview after, List<FieldChgDetail> chgDetails)
+        {
+            var processType = after?.ProcessType ?? before?.ProcessType;
+            var processName = GetProcessTypeName(processType);
+
+            if (!chgDetails.Any())
+            {
+                return opType switch
+                {
+                    "Create" => $"创建流程:{processName}",
+                    "Update" => $"更新流程:{processName} - 无变更",
+                    //"Start" => $"启动流程:{processName}",
+                    "Complete" => $"完成流程:{processName}",
+                    //"Delete" => $"删除流程:{processName}",
+                    _ => $"{opType}:{processName}"
+                };
+            }
+
+            var chgDesc = string.Join("; ", chgDetails.Select(x =>
+                $"{GetFieldDisplayName(x.FieldName)} ({x.BeforeValue} -> {x.AfterValue})"));
+
+            return $"{GetOpTypeDisplay(opType)}:{processName} - {chgDesc}";
+        }
+        /// <summary>
+        /// 获取JSON序列化设置
+        /// </summary>
+        /// <returns>JSON设置</returns>
+        private static JsonSerializerSettings GetJsonSettings()
+        {
+            return new JsonSerializerSettings
+            {
+                ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
+                NullValueHandling = NullValueHandling.Ignore,
+                DateFormatString = "yyyy-MM-dd HH:mm:ss",
+                Formatting = Formatting.None
+            };
+        }
+        /// <summary>
+        /// 生成节点操作描述
+        /// </summary>
+        /// <param name="opType">操作类型</param>
+        /// <param name="before">操作前</param>
+        /// <param name="after">操作后</param>
+        /// <param name="chgDetails">变更详情</param>
+        /// <returns>操作描述</returns>
+        private string GenerateNodeOpDesc(string opType, Grp_ProcessNode before,
+            Grp_ProcessNode after, List<FieldChgDetail> chgDetails)
+        {
+            var nodeName = after?.NodeName ?? before?.NodeName;
+
+            if (!chgDetails.Any())
+            {
+                return opType switch
+                {
+                    "Create" => $"创建节点:{nodeName}",
+                    "Update" => $"更新节点:{nodeName} - 无变更",
+                    "Start" => $"启动节点:{nodeName}",
+                    "Complete" => $"完成节点:{nodeName}",
+                    //"Delete" => $"删除节点:{nodeName}",
+                    _ => $"{opType}:{nodeName}"
+                };
+            }
+
+            var chgDesc = string.Join("; ", chgDetails.Select(x =>
+                $"{GetFieldDisplayName(x.FieldName)} ({x.BeforeValue} -> {x.AfterValue})"));
+
+            return $"{GetOpTypeDisplay(opType)}:{nodeName} - {chgDesc}";
+        }
+
+        /// <summary>
+        /// 获取流程类型名称
+        /// </summary>
+        /// <param name="processType">流程类型</param>
+        /// <returns>流程名称</returns>
+        private static string GetProcessTypeName(GroupProcessType? processType)
+        {
+            return processType switch
+            {
+                GroupProcessType.Invitation => "商邀报批",
+                GroupProcessType.Visa => "签证",
+                GroupProcessType.AirTicket => "机票",
+                GroupProcessType.Hotel => "酒店",
+                GroupProcessType.LocalGuide => "地接",
+                GroupProcessType.FeeSettle => "费用结算",
+                _ => "未知流程"
+            };
+        }
+
+        /// <summary>
+        /// 获取操作类型显示
+        /// </summary>
+        /// <param name="opType">操作类型</param>
+        /// <returns>显示名称</returns>
+        private static string GetOpTypeDisplay(string opType)
+        {
+            return opType switch
+            {
+                "Create" => "创建",
+                "Update" => "更新",
+                "Start" => "启动",
+                "Complete" => "完成",
+                "Delete" => "删除",
+                "StatusChg" => "状态变更",
+                _ => opType
+            };
+        }
+
+        /// <summary>
+        /// 获取字段显示名称
+        /// </summary>
+        /// <param name="fieldName">字段名</param>
+        /// <returns>显示名称</returns>
+        private string GetFieldDisplayName(string fieldName)
+        {
+            return fieldName switch
+            {
+                "OverallStatus" => "状态",
+                "ProcessOrder" => "流程顺序",
+                "StartTime" => "开始时间",
+                "EndTime" => "结束时间",
+                "NodeOrder" => "节点顺序",
+                "NodeName" => "节点名称",
+                "IsCurrent" => "当前节点",
+                "Operator" => "操作人",
+                "OperationTime" => "操作时间",
+                _ => fieldName
+            };
+        }
+
+        /// <summary>
+        /// 格式化值显示
+        /// </summary>
+        /// <param name="value">值</param>
+        /// <returns>格式化值</returns>
+        private string FormatVal(object value)
+        {
+            if (value == null) return "空";
+
+            if (value is ProcessStatus status)
+            {
+                return status switch
+                {
+                    ProcessStatus.UnStarted => "未开始",
+                    ProcessStatus.InProgress => "进行中",
+                    ProcessStatus.Completed => "已完成",
+                    _ => status.ToString()
+                };
+            }
+
+            if (value is bool boolVal) return boolVal ? "是" : "否";
+            if (value is DateTime dateVal) return dateVal.ToString("yyyy-MM-dd HH:mm");
+
+            var strVal = value.ToString();
+            return string.IsNullOrEmpty(strVal) ? "空" : strVal;
+        }
+
+        /// <summary>
+        /// 检查是否排除字段
+        /// </summary>
+        /// <param name="fieldName">字段名</param>
+        /// <returns>是否排除</returns>
+        private bool IsExclField(string fieldName)
+        {
+            var exclFields = new List<string>
+            {
+                "Id", "CreateTime", "CreateUserId", "UpdatedTime", "UpdatedUserId",
+                "Nodes", "Process" // 导航属性
+            };
+            return exclFields.Contains(fieldName);
+        }
+
+        /// <summary>
+        /// 获取流程日志
+        /// </summary>
+        /// <param name="processId">流程ID</param>
+        /// <returns>日志列表</returns>
+        public async Task<List<Grp_ProcessLog>> GetProcessLogsAsync(int processId)
+        {
+            return await _sqlSugar.Queryable<Grp_ProcessLog>()
+                .Where(x => x.ProcessId == processId)
+                .OrderByDescending(x => x.CreateTime)
+                .ToListAsync();
+        }
+
+        /// <summary>
+        /// 获取团组流程日志
+        /// </summary>
+        /// <param name="groupId">团组ID</param>
+        /// <returns>日志列表</returns>
+        public async Task<List<Grp_ProcessLog>> GetGroupLogsAsync(int groupId)
+        {
+            return await _sqlSugar.Queryable<Grp_ProcessLog>()
+                .Where(x => x.GroupId == groupId)
+                .OrderByDescending(x => x.CreateTime)
+                .ToListAsync();
+        }
+
+
+        #endregion
     }
 }