Переглянути джерело

签证流程节点新增最后一步 及 单选框按钮存储值

Lyyyi 1 тиждень тому
батько
коміт
ba54816f8f

+ 13 - 12
OASystem/OASystem.Api/Controllers/GroupsController.cs

@@ -2428,14 +2428,19 @@ namespace OASystem.API.Controllers
         [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
         public async Task<IActionResult> GetGroupNameAndVisaNationality(GroupNameDto dto)
         {
-            var groupData = await _groupRepository.GetGroupNameAndVisaNationality(dto);
-            if (groupData.Code != 0)
+            string sql = string.Format(@"Select Id,TeamName GroupName From  Grp_DelegationInfo 
+                                 Where TeamName != '' And IsDel = 0
+                                 Order By VisitDate Desc");
+            var groupNameList = await _sqlSugar.SqlQueryable<GroupNameView>(sql).ToListAsync();
+
+            var data = new
             {
-                return Ok(JsonView(false, groupData.Msg));
-            }
+                groupNameData = groupNameList,
+                visaNationalityData = _visaTypeInit.ToList()
+            };
 
 
-            return Ok(JsonView(groupData.Data));
+            return Ok(JsonView(data));
         }
 
         /// <summary>
@@ -6162,13 +6167,9 @@ FROM
         [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
         public async Task<IActionResult> GetCrmByGroupId(ClientByGroupIdDto dto)
         {
-            var groupData = await _groupRepository.GetCrmByGroupId(dto);
-            if (groupData.Code != 0)
-            {
-                return Ok(JsonView(false, groupData.Msg));
-            }
+            var data = await _groupRepository.GetCrmByGroupId(dto);
 
-            return Ok(JsonView(groupData.Data));
+            return Ok(JsonView(data));
         }
 
 
@@ -31388,7 +31389,7 @@ WHERE
         public async Task<IActionResult> GroupProcessSetActualDone(GroupProcessSetActualDoneDto dto)
         {
             //验证签证信息 
-            var res = await _processOverviewRep.SetActualDoneAsync(dto.NodeId, dto.ActualDone, dto.CurrUserId);
+            var res = await _processOverviewRep.SetActualDoneAsync(dto);
 
             if (res.Code == StatusCodes.Status200OK)
             {

+ 14 - 1
OASystem/OASystem.Domain/Dtos/Groups/VisaProcessDtos.cs

@@ -58,8 +58,8 @@ namespace OASystem.Domain.Dtos.Groups
 
     public class GroupProcessUpdateNodeStatusDto
     {
-
         public int NodeId { get; set; }
+
         public int CurrUserId { get; set; }
     }
 
@@ -78,8 +78,21 @@ namespace OASystem.Domain.Dtos.Groups
         /// </summary>
         public int NodeId { get; set; }
 
+        /// <summary>
+        /// 是否协助 (财务流程首节点使用)
+        /// </summary>
+        public bool IsAssist { get; set; } = false;
+
+        /// <summary>
+        /// 是否上传文件 (签证、机票、酒店、地接 流程结尾节点使用)
+        /// </summary>
+        public bool IsFileUp { get; set; } = false;
+
         /// <summary>
         /// 实际完成时间
+        /// 情况1:正常使用
+        /// 情况2:IsAssist == true,设置值
+        /// 情况3:IsFileUp == true,设置值
         /// </summary>
         public DateTime ActualDone { get; set; }
 

+ 28 - 8
OASystem/OASystem.Domain/Entities/Groups/Grp_ProcessOverview.cs

@@ -145,20 +145,38 @@ namespace OASystem.Domain.Entities.Groups
         [DefaultValue(false)]
         public bool IsCurrent { get; set; }
 
+        /// <summary>
+        /// 是否协助
+        /// (财务流程首节点使用)
+        /// </summary>
+        [SugarColumn(ColumnName = "IsAssist", ColumnDescription = "是否协助(财务流程首节点使用)", ColumnDataType = "bit")]
+        [DefaultValue(false)]
+        public bool IsAssist { get; set; }
+
+        /// <summary>
+        /// 是否上传文件
+        /// (签证、机票、酒店、地接 流程结尾节点使用)
+        /// </summary>
+        [SugarColumn(ColumnName = "IsFileUp", ColumnDescription = "是否上传文件(签证、机票、酒店、地接 流程结尾节点使用)", ColumnDataType = "bit")]
+        [DefaultValue(false)]
+        public bool IsFileUp { get; set; }
+
         public Grp_ProcessNode() { }
 
         /// <summary>
         /// Create
         /// </summary>
-        /// <param name="nodeOrder"></param>
-        /// <param name="nodeName"></param>
-        /// <param name="nodeDescTips"></param>
-        /// <param name="status"></param>
-        /// <param name="isCurrent"></param>
-        /// <param name="currUserId"></param>
-        /// <param name="remark"></param>
+        /// <param name="nodeOrder">排序</param>
+        /// <param name="nodeName">名称</param>
+        /// <param name="nodeDescTips">描述提示</param>
+        /// <param name="status">状态</param>
+        /// <param name="isCurrent">是否是当前节点</param>
+        /// <param name="isAssist">控制启用 “是否协助”按钮</param>
+        /// <param name="isFileUp">控制启用 “是否上传文件”按钮</param>
+        /// <param name="currUserId">当前用户Id</param>
+        /// <param name="remark">备注</param>
         /// <returns></returns>
-        public static Grp_ProcessNode Create(int nodeOrder, string nodeName, string nodeDescTips,ProcessStatus status, bool isCurrent, int currUserId, string remark=null)
+        public static Grp_ProcessNode Create(int nodeOrder, string nodeName, string nodeDescTips,ProcessStatus status, bool isCurrent, bool isAssist, bool isFileUp, int currUserId, string remark=null)
         {
             return new Grp_ProcessNode
             {
@@ -167,6 +185,8 @@ namespace OASystem.Domain.Entities.Groups
                 NodeDescTips = nodeDescTips,
                 OverallStatus = status,
                 IsCurrent = isCurrent,
+                IsAssist = isAssist,
+                IsFileUp = isFileUp,
                 CreateUserId = currUserId,
                 Remark = remark
             };

+ 1 - 1
OASystem/OASystem.Domain/ViewModels/Groups/DelegationInfoView.cs

@@ -632,7 +632,7 @@ namespace OASystem.Domain.ViewModels.Groups
         /// <summary>
         /// 客户名称
         /// </summary>
-        public string clientName { get; set; }
+        public string ClientName { get; set; }
 
         /// <summary>
         /// 客户手机号

+ 67 - 19
OASystem/OASystem.Infrastructure/Repositories/Groups/DelegationInfoRepository.cs

@@ -258,19 +258,30 @@ namespace OASystem.Infrastructure.Repositories.Groups
         /// 团组国家分割字符
         /// 返回 国家数组
         /// </summary>
-        /// <param name="visitCountry"></param>
+        /// <param name="countryString"></param>
         /// <returns></returns>
-        public List<string> GroupSplitCountry(string visitCountry)
+        public List<string> GroupSplitCountry(string countryString)
         {
-            var countrys = new List<string>();
+            if (string.IsNullOrWhiteSpace(countryString))
+                return new List<string>();
 
-            if (!string.IsNullOrEmpty(visitCountry))
+            // 预先定义分隔符
+            var separators = new[]
             {
-                if (visitCountry.Contains("、")) countrys = visitCountry.Split("、").ToList();
-                else if (visitCountry.Contains("|")) countrys = visitCountry.Split("|").ToList();
-                else countrys.Add(visitCountry);
-            }
-            return countrys;
+                ' ', '\t', '\n', '\r',         // 空白字符
+                '、', '|', ',', ',', ';', ';', '/', '\\', // 分隔符
+                '(', ')', '[', ']', '{', '}', '<', '>',    // 括号
+                '【', '】', '(', ')', '《', '》',          // 中文括号
+                '\'', '"', '`',                             // 引号
+                '-', '_', '+', '=', '~', '!', '@', '#', '$', '%', '^', '&', '*',
+                '·', '。', '?', '!', ':', ';', ',', '、', '~', '—',  // 中文标点
+                '€', '£', '¥', '¢'                           // 货币符号
+            };
+
+            return countryString.Trim()
+                               .Split(separators, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
+                               .Distinct()  // 去重
+                               .ToList();
         }
 
         /// <summary>
@@ -1233,7 +1244,7 @@ namespace OASystem.Infrastructure.Repositories.Groups
             {
                 string sql = string.Format(@"Select Id,TeamName GroupName From  Grp_DelegationInfo 
                                              Where TeamName != '' And IsDel = 0
-                                             Order By Id Desc");
+                                             Order By VisitDate Desc");
                 var _groupNameList = await _sqlSugar.SqlQueryable<GroupNameView>(sql).ToListAsync();
 
                 string visaSql = string.Format(@"Select Id,Name From Sys_SetData Where STid = 41 And IsDel = 0");
@@ -1266,25 +1277,62 @@ namespace OASystem.Infrastructure.Repositories.Groups
             var result = new Result() { Code = -2 };
             if (dto.PortType == 1 || dto.PortType == 2 || dto.PortType == 3)
             {
-                string sql = string.Format(@"Select gdjc.GrpDCId grpId,cdc.Id,cdc.LastName+cdc.FirstName clientName,cdc.Tel,ccc.CertNo CerdNo 
-                                             From  Grp_DelegationJoinCustomer gdjc
-                                             Inner join Crm_DeleClient cdc On gdjc.CrmDCId =  cdc.Id
-                                             Left Join Crm_CustomerCert ccc On ccc.SdId = 773 And  cdc.Id = ccc.DcId 
-                                             Where gdjc.GrpDCId = {0}", dto.GroupId);
 
+                var clientInfos = await _sqlSugar.Queryable<Grp_TourClientList>().Where(x => x.IsDel == 0 && x.DiId == dto.GroupId).ToListAsync();
+                var clientIds = clientInfos.Select(x => x.ClientId).ToList();
 
-                var clientList = await _sqlSugar.SqlQueryable<CrmByGroupIdView>(sql).ToListAsync();
+                var visaClientInfos = await _sqlSugar.Queryable<Crm_DeleClient>().Where(x => x.IsDel == 0 && clientIds.Contains(x.Id))
+                    .Select(x => new Crm_DeleClient
+                    {
+                        DiId = dto.GroupId,
+                        Id = x.Id,
+                        FirstName = x.FirstName,
+                        LastName = x.LastName,
+                        Tel = x.Tel
+                    })
+                    .ToListAsync();
+
+                var clientCertInfos = await _sqlSugar.Queryable<Crm_CustomerCert>().Where(x => x.IsDel == 0 && x.SdId == 773 && clientIds.Contains(x.DcId))
+                    .Select(x => new Crm_CustomerCert
+                    {
+                        DcId = x.DcId,
+                        CertNo = x.CertNo
+                    })
+                    .ToListAsync();
 
-                if (clientList.Count > 0)
+                //解密
+                foreach (var item in visaClientInfos)
                 {
-                    result.Code = 0;
+                    EncryptionProcessor.DecryptProperties(item);
+                }
+
+                //信息整合
+                var view = new List<CrmByGroupIdView>();
+                foreach (var item in visaClientInfos)
+                {
+                    var idNo = AesEncryptionHelper.Decrypt(clientCertInfos.FirstOrDefault(x => x.DcId == item.Id)?.CertNo ?? "") ?? "-";
+
+                    if (string.IsNullOrEmpty(idNo)) idNo = "-";
+
+                    view.Add(new() {
+                        GrpId = dto.GroupId,
+                        ClientName = item.LastName + item.FirstName,
+                        Tel = item.Tel ?? "-",
+                        CerdNo = idNo
+                    });
+                }
+
+                if (view.Count > 0)
+                {
+
                     result.Msg = "成功!";
-                    result.Data = clientList;
+                    result.Data = view;
                 }
                 else
                 {
                     result.Msg = "暂无数据!";
                 }
+                result.Code = 0;
             }
 
             return result;

+ 195 - 77
OASystem/OASystem.Infrastructure/Repositories/Groups/ProcessOverviewRepository.cs

@@ -21,7 +21,6 @@ namespace OASystem.Infrastructure.Repositories.Groups
             _groupRep = groupRep;
         }
 
-
         /// <summary>
         /// 基础数据初始化-团组流程
         /// </summary>
@@ -50,20 +49,22 @@ namespace OASystem.Infrastructure.Repositories.Groups
             string oaNode2Tips = "客户提供完整名单后,2周内取得邀请函(翻译件)。";
             if (custInfo != null)
             {
-                oaNode2Tips = $"请于{custInfo.CreateTime.AddDays(14):yyyy年MM月dd日}内完成该项工作(客户提供完整名单后,2周内取得邀请函(翻译件))";
+                oaNode2Tips = $"请于{custInfo.CreateTime.AddDays(14):yyyy年MM月dd日}内完成该项工作(客户提供完整名单后,2周内取得邀请函(翻译件))";
             }
 
-            var oaNode4Tips = $"请于{groupInfo.VisitDate.AddDays(-5):yyyy年MM月dd日}内完成该项工作(按进度实际公务活动落实情况,出发前5日落实公务。)";
+            var oaNode4Tips = $"请于{groupInfo.VisitDate.AddDays(-5):yyyy年MM月dd日}内完成该项工作(按进度实际公务活动落实情况,出发前5日落实公务)";
+            var oaNode7Tips = $"请于{groupInfo.VisitEndDate.AddDays(-5):yyyy年MM月dd日}内完成该项工作(团组结束前完成)";
             processs.Add(
                 Grp_ProcessOverview.Create(groupId, 1, GroupProcessType.Invitation, ProcessStatus.InProgress, currUserId,
                 new List<Grp_ProcessNode>()
                     {
-                        Grp_ProcessNode.Create(1, "报批基础资料准备","更新报批行程和请示,提供其他报批所需材料,4个工作日内完成。",ProcessStatus.InProgress, true,currUserId),
-                        Grp_ProcessNode.Create(2, "报批邀请函资料准备",oaNode2Tips, ProcessStatus.InProgress, false,currUserId),
-                        Grp_ProcessNode.Create(3, "获得批件","提供完整的报批全套资源。",ProcessStatus.InProgress, false, currUserId ),
-                        Grp_ProcessNode.Create(4, "对接公务",oaNode4Tips,ProcessStatus.InProgress, false, currUserId),
-                        Grp_ProcessNode.Create(5, "参与翻译对接","",ProcessStatus.InProgress, false, currUserId),
-                        Grp_ProcessNode.Create(6, "商邀文案配合","",ProcessStatus.InProgress, false, currUserId),
+                        Grp_ProcessNode.Create(1, "报批基础资料准备","更新报批行程和请示,提供其他报批所需材料,4个工作日内完成。",ProcessStatus.InProgress, true,false,false,currUserId),
+                        Grp_ProcessNode.Create(2, "报批邀请函资料准备",oaNode2Tips, ProcessStatus.InProgress, false,false,false,currUserId),
+                        Grp_ProcessNode.Create(3, "获得批件","提供完整的报批全套资源。",ProcessStatus.InProgress, false,false,false, currUserId ),
+                        Grp_ProcessNode.Create(4, "对接公务",oaNode4Tips,ProcessStatus.InProgress, false,false,false, currUserId),
+                        Grp_ProcessNode.Create(5, "参与翻译对接","",ProcessStatus.InProgress, false,false,false, currUserId),
+                        Grp_ProcessNode.Create(6, "商邀文案配合","",ProcessStatus.InProgress, false,false,false, currUserId),
+                        Grp_ProcessNode.Create(7, "票据上传(相关票据)",oaNode7Tips,ProcessStatus.InProgress, false,false,true, currUserId),
                     }));
             #endregion
 
@@ -80,10 +81,12 @@ namespace OASystem.Infrastructure.Repositories.Groups
                     visaDefualtNodes.Add(VisaProcessNode.Info(i, visaCountries[i - 1].ToString()));
                 }
 
-                visaNodes.Add(Grp_ProcessNode.Create(1, "签证", "", ProcessStatus.InProgress, true, currUserId, JsonConvert.SerializeObject(visaDefualtNodes)));
+                var visaNode2Tips = $"请于{groupInfo.VisitDate:yyyy年MM月dd日}内完成该项工作(按进度实际签证办理落实情况,团组出发前上传票据。)";
+                visaNodes.Add(Grp_ProcessNode.Create(1, "签证信息", "", ProcessStatus.InProgress, true, false, false, currUserId, JsonConvert.SerializeObject(visaDefualtNodes)));
+                visaNodes.Add(Grp_ProcessNode.Create(2, "票据上传(明细表、费用票据、保单及超支费用账单)", visaNode2Tips, ProcessStatus.InProgress, true, false, true, currUserId));
             }
 
-            processs.Add(Grp_ProcessOverview.Create(groupId, 2, GroupProcessType.Visa, ProcessStatus.InProgress, currUserId, visaNodes));
+            processs.Add(Grp_ProcessOverview.Create(groupId, 2, GroupProcessType.Visa, ProcessStatus.UnStarted, currUserId, visaNodes));
             #endregion
 
             #region 机票流程
@@ -97,16 +100,20 @@ namespace OASystem.Infrastructure.Repositories.Groups
             }
             
             var airNode5Tips = $"请于{groupInfo.VisitDate.AddDays(-5):yyyy年MM月dd日}内完成该项工作(团组出发前5日)";
+            var airNode7Tips = $"请于{groupInfo.VisitEndDate.AddDays(5):yyyy年MM月dd日}内完成该项工作(团组归国后5个工作日内)";
+            var airNode8Tips = $"请于{groupInfo.VisitEndDate.AddDays(10):yyyy年MM月dd日}内完成该项工作(团组归国后10个工作日内) *按机票报价*0.999折扣出具机票报销蓝联、行程单及机票说明";
             processs.Add(
                 Grp_ProcessOverview.Create(groupId, 3, GroupProcessType.AirTicket, ProcessStatus.InProgress, currUserId,
                 new List<Grp_ProcessNode>()
                     {
-                        Grp_ProcessNode.Create(1, "初步拟定航程方案及价格",   airNode1Tips, ProcessStatus.InProgress, true,currUserId ),
-                        Grp_ProcessNode.Create(2, "机票占位、续位",   "", ProcessStatus.UnStarted, false,currUserId ),
-                        Grp_ProcessNode.Create(3, "完成机票采购确认(含预算核对、出票确认等)",  "", ProcessStatus.UnStarted,false,currUserId),
-                        Grp_ProcessNode.Create(4, "进行出票操作并核查信息", "", ProcessStatus.UnStarted, false, currUserId),
-                        Grp_ProcessNode.Create(5, "机票已出",  airNode5Tips, ProcessStatus.UnStarted, false, currUserId),
-                        Grp_ProcessNode.Create(6, "完成机票选座",  "", ProcessStatus.UnStarted, false,currUserId)
+                        Grp_ProcessNode.Create(1, "初步拟定航程方案及价格",   airNode1Tips, ProcessStatus.InProgress, true,false,false,currUserId ),
+                        Grp_ProcessNode.Create(2, "机票占位、续位",   "", ProcessStatus.UnStarted, false,false,false,currUserId ),
+                        Grp_ProcessNode.Create(3, "完成机票采购确认(含预算核对、出票确认等)",  "", ProcessStatus.UnStarted,false,false,false,currUserId),
+                        Grp_ProcessNode.Create(4, "进行出票操作并核查信息", "", ProcessStatus.UnStarted, false,false,false, currUserId),
+                        Grp_ProcessNode.Create(5, "机票已出",  airNode5Tips, ProcessStatus.UnStarted, false,false,false, currUserId),
+                        Grp_ProcessNode.Create(6, "完成机票选座", "", ProcessStatus.UnStarted, false,false,false,currUserId),
+                        Grp_ProcessNode.Create(7, "票据上传(机票超支费用账单)", airNode7Tips, ProcessStatus.UnStarted, false,false,true,currUserId),
+                        Grp_ProcessNode.Create(8, "票据上传(机票报销蓝联、行程单及机票说明)", airNode8Tips, ProcessStatus.UnStarted, false,false,true,currUserId)
                     }
                 )
              );
@@ -130,11 +137,11 @@ namespace OASystem.Infrastructure.Repositories.Groups
                 Grp_ProcessOverview.Create(groupId, 4, GroupProcessType.Hotel, ProcessStatus.InProgress, currUserId,
                      new List<Grp_ProcessNode>()
                     {
-                        Grp_ProcessNode.Create(1, "筛选并按照预算标准,对目标酒店进行询价、比价、谈价", hotelNode1Tips, ProcessStatus.InProgress, true, currUserId),
-                        Grp_ProcessNode.Create(2, "获取酒店确认函与入住名单核对", "", ProcessStatus.UnStarted, false, currUserId ),
-                        Grp_ProcessNode.Create(3, "预订酒店并录入OA",  "", ProcessStatus.UnStarted,false,currUserId ),
-                        Grp_ProcessNode.Create(4, "行前再次确认酒店订单、付款状态及入住安排",  hotelNode4Tips,ProcessStatus.UnStarted, false,currUserId ),
-                        Grp_ProcessNode.Create(5, "行程结束后整理酒店发票与结算",  hotelNode5Tips, ProcessStatus.UnStarted, false, currUserId ),
+                        Grp_ProcessNode.Create(1, "筛选并按照预算标准,对目标酒店进行询价、比价、谈价", hotelNode1Tips, ProcessStatus.InProgress, true, false, false, currUserId),
+                        Grp_ProcessNode.Create(2, "获取酒店确认函与入住名单核对", "", ProcessStatus.UnStarted, false, false, false, currUserId ),
+                        Grp_ProcessNode.Create(3, "预订酒店并录入OA",  "", ProcessStatus.UnStarted,false, false, false,currUserId ),
+                        Grp_ProcessNode.Create(4, "行前再次确认酒店订单、付款状态及入住安排",  hotelNode4Tips,ProcessStatus.UnStarted, false, false, false,currUserId ),
+                        Grp_ProcessNode.Create(5, "行程结束后整理酒店发票(含超支费用发票)与结算",  hotelNode5Tips, ProcessStatus.UnStarted, false, false, true, currUserId ),
                     }
                 )
             );
@@ -164,16 +171,19 @@ namespace OASystem.Infrastructure.Repositories.Groups
                     opNode5Tips = $"请于{dateTime.AddDays(-3):yyyy年MM月dd日}内完成该项工作(倒推表里开行前会 -3天)";
                 }
             }
+
+            string opNode7Tips = $"请于{groupInfo.VisitEndDate.AddDays(5):yyyy年MM月dd日}内完成该项工作(团组归国后5个工作日内) *上传最终报批行程,确定城市间交通最终版报价分配;地接账单(清楚标注超时及其他项超支费用)、地接交通费用原始票据、城市间交通明细表;";
             processs.Add(
                 Grp_ProcessOverview.Create(groupId, 5, GroupProcessType.LocalGuide, ProcessStatus.InProgress, currUserId,
                      new List<Grp_ProcessNode>()
                     {
-                        Grp_ProcessNode.Create(1,"根据机票方案出框架行程", opNode1Tips,ProcessStatus.InProgress, true, currUserId ),
-                        Grp_ProcessNode.Create(2,"联系并询价地接、餐厅、用车、景点等供应商", opNode2Tips,ProcessStatus.UnStarted, false, currUserId ),
-                        Grp_ProcessNode.Create(3,"提交供应商报价及比价表", opNode3Tips, ProcessStatus.UnStarted, false, currUserId),
-                        Grp_ProcessNode.Create(4,"执行采购流程", opNode4Tips, ProcessStatus.UnStarted, false, currUserId),
-                        Grp_ProcessNode.Create(5,"制定最终《行程表》及《出行手册》", opNode5Tips, ProcessStatus.UnStarted, false, currUserId ),
-                        Grp_ProcessNode.Create(6,"送机", "", ProcessStatus.UnStarted, false, currUserId ),
+                        Grp_ProcessNode.Create(1,"根据机票方案出框架行程", opNode1Tips,ProcessStatus.InProgress, true, false, false, currUserId ),
+                        Grp_ProcessNode.Create(2,"联系并询价地接、餐厅、用车、景点等供应商", opNode2Tips,ProcessStatus.UnStarted, false, false, false, currUserId ),
+                        Grp_ProcessNode.Create(3,"提交供应商报价及比价表", opNode3Tips, ProcessStatus.UnStarted, false, false, false, currUserId),
+                        Grp_ProcessNode.Create(4,"执行采购流程", opNode4Tips, ProcessStatus.UnStarted, false, false, false, currUserId),
+                        Grp_ProcessNode.Create(5,"制定最终《行程表》及《出行手册》", opNode5Tips, ProcessStatus.UnStarted, false, false, false, currUserId ),
+                        Grp_ProcessNode.Create(6,"送机", "", ProcessStatus.UnStarted, false, false, false, currUserId ),
+                        Grp_ProcessNode.Create(7,"最终版报批行程、票据上传", opNode7Tips, ProcessStatus.UnStarted, false, false, true, currUserId )
                     }
                 )
             );
@@ -181,12 +191,15 @@ namespace OASystem.Infrastructure.Repositories.Groups
             #endregion
 
             #region 费用结算流程
+            var feeNode3Tips = $"请于{groupInfo.VisitEndDate.AddDays(12):yyyy年MM月dd日}内完成该项工作(团组归国后12个工作日内)";
             processs.Add(
                 Grp_ProcessOverview.Create(groupId, 6, GroupProcessType.FeeSettle, ProcessStatus.InProgress, currUserId,
                     new List<Grp_ProcessNode>()
                     {
-                        Grp_ProcessNode.Create(1, "费用结算中", "", ProcessStatus.InProgress, true,currUserId ),
-                        Grp_ProcessNode.Create(2, "费用结算完毕", "", ProcessStatus.UnStarted, false,currUserId ),
+                        Grp_ProcessNode.Create(1, "城市间交通报批金额核定", "团组报批前", ProcessStatus.InProgress, true, true, false,currUserId ),
+                        Grp_ProcessNode.Create(2, "团组全程各段机票打票金额的核定", "团组报批后、订票前", ProcessStatus.UnStarted, false, false, false,currUserId ),
+                        Grp_ProcessNode.Create(3, "整理统计团组超支费用、三公报销资料给到各单位", feeNode3Tips, ProcessStatus.UnStarted, false, false, false,currUserId ),
+                        Grp_ProcessNode.Create(4, "费用结算完毕", "", ProcessStatus.UnStarted, false, false, false,currUserId ),
                     }
                 )
             );
@@ -235,7 +248,7 @@ namespace OASystem.Infrastructure.Repositories.Groups
                     _sqlSugar.RollbackTran();
                     return new Result { Code = 400, Msg = "团组流程进度总览表添加失败!" };
                 }
-
+                item.Id = processId;
                 // 记录流程日志
                 await LogProcessOpAsync(null, item, "Create", currUserId);
 
@@ -248,6 +261,8 @@ namespace OASystem.Infrastructure.Repositories.Groups
                     NodeDescTips = nodeDto.NodeDescTips,
                     //Country = nodeDto.Country,
                     IsCurrent = nodeDto.IsCurrent,
+                    IsAssist = nodeDto.IsAssist,
+                    IsFileUp = nodeDto.IsFileUp,
                     Remark = nodeDto.Remark
                 }).ToList();
 
@@ -258,6 +273,9 @@ namespace OASystem.Infrastructure.Repositories.Groups
                     return new Result { Code = 400, Msg = "团组流程进度流程节点添加失败!" };
                 }
 
+                //设置节点ID
+                nodes = await _sqlSugar.Queryable<Grp_ProcessNode>().Where(x => x.IsDel == 0 && x.ProcessId == processId).ToListAsync();
+
                 //记录节点日志
                 foreach (var node in nodes)
                 {
@@ -302,40 +320,68 @@ namespace OASystem.Infrastructure.Repositories.Groups
                 .Mapper(p => p.Nodes, p => p.Nodes.First().ProcessId)
                 .ToListAsync();
 
-            var processes = processData.Select(p => new
+            // 预先构建用户字典,提升查询性能
+            var userDict = users.ToDictionary(u => u.Id, u => u.CnName);
+
+            var processes = processData.Select(p =>
             {
-                p.Id,
-                p.GroupId,
-                p.ProcessType,
-                ProcessName = p.ProcessType.GetEnumDescription(),
-                //p.OverallStatus,
-                //StatusText = p.OverallStatus.GetDescription(),
-                Nodes = p.Nodes.Select(n =>
-                {
-                    //单独处理签证板块
-                    var visaSubNodes = new List<VisaProcessNode>();
-                    string remark = string.Empty;
+                var orderedNodes = p.Nodes.OrderBy(n => n.NodeOrder).ToList();
+                var totalNodes = orderedNodes.Count;
 
-                    if (p.ProcessType == GroupProcessType.Visa)
-                    {
-                        visaSubNodes = JsonConvert.DeserializeObject<List<VisaProcessNode>>(n.Remark);
-                    }
-                    return new
+                return new
+                {
+                    p.Id,
+                    p.GroupId,
+                    p.ProcessType,
+                    ProcessName = p.ProcessType.GetEnumDescription(),
+                    Nodes = orderedNodes.Select((n, index) =>
                     {
-                        n.Id,
-                        n.ProcessId,
-                        n.NodeOrder,
-                        n.NodeName,
-                        n.OverallStatus,
-                        StatusText = n.OverallStatus.GetEnumDescription(),
-                        Operator = users.FirstOrDefault(u => u.Id == n.Operator)?.CnName ?? "-",
-                        OpeateTime = n.OperationTime.HasValue ? n.OperationTime.Value.ToString("yyyy-MM-dd HH:mm:ss") : "-",
-                        ActualDone = n.ActualDone?.ToString("yyyy-MM-dd HH:mm:ss") ?? "",
-                        n.NodeDescTips,
-                        //节点类型为签证时使用
-                        visaSubNodes
-                    };
-                }).OrderBy(n => n.NodeOrder).ToList()
+                        var isLastNode = index == totalNodes - 1;
+                        var isSecondLastNode = index == totalNodes - 2;
+
+                        /* 计算按钮状态
+                         * 1 所有流程节点最后一步骤显示上传按钮
+                         * 2 机票流程倒数第二步骤显示上传按钮
+                         * 3 费用结算流程第一步骤显示协助按钮
+                         */
+                        bool isEnaAssistBtn = p.ProcessType == GroupProcessType.FeeSettle && n.NodeOrder == 1;
+                        bool isEnaFileUpBtn = isLastNode || (p.ProcessType == GroupProcessType.AirTicket && isSecondLastNode);
+
+                        // 处理签证子节点
+                        List<VisaProcessNode> visaSubNodes = new();
+                        if (p.ProcessType == GroupProcessType.Visa && n.NodeOrder == 1)
+                        {
+                            visaSubNodes = JsonConvert.DeserializeObject<List<VisaProcessNode>>(n.Remark ?? "[]")
+                                ?? new List<VisaProcessNode>();
+                        }
+
+                        // 获取操作人姓名(使用字典提升性能)
+                        string operatorName = "-";
+                        if (n.Operator.HasValue && userDict.TryGetValue(n.Operator.Value, out var name))
+                        {
+                            operatorName = name;
+                        }
+
+                        return new
+                        {
+                            n.Id,
+                            n.ProcessId,
+                            n.NodeOrder,
+                            n.NodeName,
+                            n.OverallStatus,
+                            StatusText = n.OverallStatus.GetEnumDescription(),
+                            Operator = operatorName,
+                            OpeateTime = n.OperationTime?.ToString("yyyy-MM-dd HH:mm:ss") ?? "-",
+                            ActualDone = n.ActualDone?.ToString("yyyy-MM-dd HH:mm:ss") ?? "",
+                            n.NodeDescTips,
+                            isEnaAssistBtn,  // 是否启用财务流程首节点协助按钮
+                            n.IsAssist,      // 财务流程首节点 存储值
+                            isEnaFileUpBtn,  // 是否启用上传文件按钮
+                            n.IsFileUp,      // 票据上传节点 存储值
+                            visaSubNodes     // 签证节点类型使用
+                        };
+                    }).ToList()
+                };
             }).ToList();
 
             return new Result { Code = 200, Data = processes, Msg = "查询成功!" };
@@ -357,21 +403,11 @@ namespace OASystem.Infrastructure.Repositories.Groups
                 {
                     // 1. 获取并验证节点
                     var node = await _sqlSugar.Queryable<Grp_ProcessNode>()
-                        .FirstAsync(n => n.Id == nodeId && n.IsDel == 0);
-
-                    if (node == null)
-                    {
-                        throw new BusinessException("当前节点不存在或已被删除。");
-                    }
+                        .FirstAsync(n => n.Id == nodeId && n.IsDel == 0) ?? throw new BusinessException("当前节点不存在或已被删除。");
 
                     // 2. 获取流程信息,检查ProcessType
                     var process = await _sqlSugar.Queryable<Grp_ProcessOverview>()
-                        .FirstAsync(p => p.Id == node.ProcessId && p.IsDel == 0);
-
-                    if (process == null)
-                    {
-                        throw new BusinessException("关联的流程不存在。");
-                    }
+                        .FirstAsync(p => p.Id == node.ProcessId && p.IsDel == 0) ?? throw new BusinessException("关联的流程不存在。");
 
                     // 3. 节点操作验证
                     ValidateNodeOperation(node, processStatus);
@@ -705,8 +741,14 @@ namespace OASystem.Infrastructure.Repositories.Groups
         /// </summary>
         /// <param name="dto">签证节点更新数据传输对象</param>
         /// <returns>操作结果</returns>
-        public async Task<Result> SetActualDoneAsync(int nodeId, DateTime dt, int currUserId)
+        public async Task<Result> SetActualDoneAsync(GroupProcessSetActualDoneDto dto )
         {
+            int nodeId = dto.NodeId;
+            DateTime dt = dto.ActualDone;
+            int currUserId = dto.CurrUserId;
+            bool isAssist = dto.IsAssist;
+            bool isFileUp = dto.IsFileUp;
+
             // 1. 获取并验证节点和流程
             var node = await _sqlSugar.Queryable<Grp_ProcessNode>()
                 .FirstAsync(n => n.Id == nodeId && n.IsDel == 0)
@@ -726,16 +768,20 @@ namespace OASystem.Infrastructure.Repositories.Groups
                 OverallStatus = node.OverallStatus,
                 Operator = node.Operator,
                 OperationTime = node.OperationTime,
-                IsCurrent = node.IsCurrent,
+                IsCurrent = node.IsCurrent
             };
 
             node.ActualDone = dt;
+            node.IsAssist = isAssist;
+            node.IsFileUp = isFileUp;
 
             // 3. 保存节点更新
             await _sqlSugar.Updateable(node)
                 .UpdateColumns(n => new
                 {
-                    n.ActualDone
+                    n.ActualDone,
+                    n.IsAssist,
+                    n.IsFileUp,
                 })
                 .ExecuteCommandAsync();
             //记录节点日志
@@ -1077,5 +1123,77 @@ namespace OASystem.Infrastructure.Repositories.Groups
 
 
         #endregion
+
+        #region 节点按钮策略
+        // 定义按钮状态计算策略接口
+        public interface IButtonStateStrategy
+        {
+            (bool IsEnaAssistBtn, bool IsEnaFileUpBtn) Calculate(Grp_ProcessOverview process, Grp_ProcessNode node, int index);
+        }
+
+        // 通用策略
+        public class DefaultButtonStateStrategy : IButtonStateStrategy
+        {
+            public (bool IsEnaAssistBtn, bool IsEnaFileUpBtn) Calculate(Grp_ProcessOverview process, Grp_ProcessNode node, int index)
+            {
+                var totalNodes = process.Nodes.Count();
+                var isLastNode = index == totalNodes - 1;
+
+                return (false, isLastNode); // 默认只有最后一步启用文件上传
+            }
+        }
+
+        // 财务流程策略
+        public class FeeSettleButtonStateStrategy : IButtonStateStrategy
+        {
+            public (bool IsEnaAssistBtn, bool IsEnaFileUpBtn) Calculate(Grp_ProcessOverview process, Grp_ProcessNode node, int index)
+            {
+                var totalNodes = process.Nodes.Count();
+                var isLastNode = index == totalNodes - 1;
+
+                bool isEnaAssistBtn = node.NodeOrder == 1; // 首节点启用协助
+                bool isEnaFileUpBtn = isLastNode; // 最后一步启用文件上传
+
+                return (isEnaAssistBtn, isEnaFileUpBtn);
+            }
+        }
+
+        // 机票流程策略
+        public class FlightButtonStateStrategy : IButtonStateStrategy
+        {
+            public (bool IsEnaAssistBtn, bool IsEnaFileUpBtn) Calculate(Grp_ProcessOverview process, Grp_ProcessNode node, int index)
+            {
+                var totalNodes = process.Nodes.Count();
+                var isLastNode = index == totalNodes - 1;
+                var isSecondLastNode = index == totalNodes - 2;
+
+                bool isEnaAssistBtn = false;
+                bool isEnaFileUpBtn = isLastNode || isSecondLastNode; // 倒数两步都启用文件上传
+
+                return (isEnaAssistBtn, isEnaFileUpBtn);
+            }
+        }
+
+        // 策略工厂
+        public static class ButtonStateStrategyFactory
+        {
+            private static readonly Dictionary<GroupProcessType, IButtonStateStrategy> _strategies = new()
+            {
+                [GroupProcessType.Invitation] = new DefaultButtonStateStrategy(),
+                [GroupProcessType.Visa] = new DefaultButtonStateStrategy(),
+                [GroupProcessType.AirTicket] = new FlightButtonStateStrategy(),
+                [GroupProcessType.Hotel] = new DefaultButtonStateStrategy(),
+                [GroupProcessType.LocalGuide] = new DefaultButtonStateStrategy(),
+                [GroupProcessType.FeeSettle] = new FeeSettleButtonStateStrategy()
+            };
+
+            public static IButtonStateStrategy GetStrategy(GroupProcessType processType)
+            {
+                return _strategies.TryGetValue(processType, out var strategy)
+                    ? strategy
+                    : new DefaultButtonStateStrategy();
+            }
+        }
+        #endregion
     }
 }