Forráskód Böngészése

团组流程精度总览
1、更新节点状态
2、更新签证节点信息

Lyyyi 5 órája%!(EXTRA string=óta)
szülő
commit
f3879ad6f2

+ 21 - 0
OASystem/OASystem.Api/Controllers/GroupsController.cs

@@ -28428,6 +28428,27 @@ WHERE
             return Ok(JsonView(false, res.Msg));
         }
 
+        /// <summary>
+        /// 团组总览进程 - 更新签证节点信息
+        /// </summary>
+        /// <returns></returns>
+        [HttpPost]
+        [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
+        public async Task<IActionResult> GroupProcessUpdateVisaNodeDetails(GroupProcessUpdateVisaNodeDetailsDto dto)
+        {
+            //验证签证信息 
+            if (dto.VisaSubNodes.Count < 1) return Ok(JsonView(false, "请传入有效的签证节点信息。"));
+
+            var res = await _processOverviewRep.UpdateVisaNodeDetailsAsync(dto);
+
+            if (res.Code == StatusCodes.Status200OK)
+            {
+                return Ok(JsonView(res.Data));
+            }
+
+            return Ok(JsonView(false, res.Msg));
+        }
+
         #endregion
 
         /// <summary>

+ 12 - 7
OASystem/OASystem.Domain/Dtos/Groups/VisaProcessDtos.cs

@@ -1,9 +1,5 @@
 using Microsoft.AspNetCore.Http;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
+using OASystem.Domain.Entities.Groups;
 
 namespace OASystem.Domain.Dtos.Groups
 {
@@ -29,7 +25,7 @@ namespace OASystem.Domain.Dtos.Groups
         public int GroupId { get; set; }
     }
 
-    public class VisaProcessSetContent: PortDtoBase
+    public class VisaProcessSetContent : PortDtoBase
     {
         /// <summary>
         /// 当前登录用户Id
@@ -59,9 +55,18 @@ namespace OASystem.Domain.Dtos.Groups
     }
 
 
-    public class GroupProcessUpdateNodeStatusDto {
+    public class GroupProcessUpdateNodeStatusDto
+    {
 
         public int NodeId { get; set; }
         public int CurrUserId { get; set; }
     }
+
+    public class GroupProcessUpdateVisaNodeDetailsDto
+    {
+        public int NodeId { get; set; }
+        public int CurrUserId { get; set; }
+        public List<VisaProcessNode> VisaSubNodes { get; set; }
+    }
+
 }

+ 197 - 0
OASystem/OASystem.Domain/Entities/EntityExtensions.cs

@@ -0,0 +1,197 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace OASystem.Domain.Entities
+{
+    /// <summary>
+    /// 实体类完成状态验证扩展方法
+    /// 提供便捷的方法来检查实体对象是否所有字段都已填写完成
+    /// </summary>
+    public static class EntityExtensions
+    {
+        /// <summary>
+        /// 检查实体对象的所有字段是否都已填写完成
+        /// 包括所有属性(字符串、值类型、引用类型)
+        /// </summary>
+        /// <param name="obj">要检查的实体对象</param>
+        /// <returns>
+        /// true: 所有字段都已正确填写
+        /// false: 存在未填写或为默认值的字段
+        /// </returns>
+        /// <example>
+        /// <code>
+        /// var entity = new YourEntity { Name = "John", Age = 25 };
+        /// if (entity.IsCompleted()) 
+        /// {
+        ///     Console.WriteLine("所有字段已填写完成");
+        /// }
+        /// </code>
+        /// </example>
+        public static bool IsCompleted(this object obj)
+        {
+            // 获取实体类型的所有属性
+            var properties = obj.GetType().GetProperties();
+
+            // 使用LINQ的All方法检查所有属性是否都已填写
+            // 如果所有属性都满足IsPropertyFilled条件,返回true;否则返回false
+            return properties.All(property => IsPropertyFilled(property, obj));
+        }
+
+        /// <summary>
+        /// 检查实体对象的必填字段是否都已填写完成
+        /// 只检查标记了[RequiredField]特性的属性
+        /// </summary>
+        /// <param name="obj">要检查的实体对象</param>
+        /// <returns>
+        /// true: 所有必填字段都已正确填写
+        /// false: 存在未填写的必填字段
+        /// </returns>
+        /// <example>
+        /// <code>
+        /// var entity = new YourEntity { Name = "John" }; // Age标记为[RequiredField]
+        /// if (!entity.IsRequiredCompleted()) 
+        /// {
+        ///     Console.WriteLine("请填写所有必填字段");
+        /// }
+        /// </code>
+        /// </example>
+        public static bool IsRequiredCompleted(this object obj)
+        {
+            // 获取实体类型的所有属性
+            var properties = obj.GetType().GetProperties();
+
+            // 筛选出标记了[RequiredField]特性的属性(必填字段)
+            var requiredProperties = properties.Where(p => p.GetCustomAttribute<RequiredFieldAttribute>() != null);
+
+            // 检查所有必填字段是否都已填写
+            return requiredProperties.All(property => IsPropertyFilled(property, obj));
+        }
+
+        /// <summary>
+        /// 检查单个属性是否已填写 - 使用模式匹配的安全版本
+        /// 根据属性类型采用不同的验证规则
+        /// </summary>
+        /// <param name="property">要检查的属性信息</param>
+        /// <param name="obj">包含该属性的对象实例</param>
+        /// <returns>
+        /// true: 属性已正确填写
+        /// false: 属性未填写或为默认值
+        /// </returns>
+        /// <remarks>
+        /// 验证规则:
+        /// 1. 字符串类型:不能为null、空字符串或空白字符串
+        /// 2. 值类型:不能等于该类型的默认值(如int不能为0,DateTime不能为MinValue等)
+        /// 3. 布尔类型:总是返回true(因为bool总是有true或false值)
+        /// 4. 引用类型:不能为null
+        /// </remarks>
+        private static bool IsPropertyFilled(PropertyInfo property, object obj)
+        {
+            // 获取属性的值
+            var value = property.GetValue(obj);
+
+            // 如果值为null,直接返回false
+            if (value == null) return false;
+
+            return property.PropertyType switch
+            {
+                // 字符串类型
+                Type t when t == typeof(string) => !string.IsNullOrWhiteSpace((string)value),
+
+                // 值类型使用模式匹配安全转换
+                Type t when t == typeof(int) => value is int intValue && intValue != 0,
+                Type t when t == typeof(long) => value is long longValue && longValue != 0,
+                Type t when t == typeof(decimal) => value is decimal decimalValue && decimalValue != 0,
+                Type t when t == typeof(double) => value is double doubleValue && doubleValue != 0,
+                Type t when t == typeof(float) => value is float floatValue && floatValue != 0,
+                Type t when t == typeof(DateTime) => value is DateTime dateValue && dateValue != DateTime.MinValue,
+                Type t when t == typeof(bool) => value is bool, // bool只要不是null就认为已填写
+
+                // 可空类型处理
+                Type t when IsNullableType(t) => !IsDefaultNullableValue(value),
+
+                // 其他值类型
+                Type t when t.IsValueType => !value.Equals(Activator.CreateInstance(t)),
+
+                // 引用类型(已经检查过null)
+                _ => true
+            };
+        }
+
+        /// <summary>
+        /// 检查类型是否为可空类型(Nullable<T>)
+        /// </summary>
+        /// <param name="type">要检查的类型</param>
+        /// <returns>是否为可空类型</returns>
+        private static bool IsNullableType(Type type)
+        {
+            return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
+        }
+
+        /// <summary>
+        /// 检查可空类型的值是否为默认值(null或默认值)
+        /// </summary>
+        /// <param name="value">可空类型的值</param>
+        /// <returns>是否为默认值</returns>
+        private static bool IsDefaultNullableValue(object value)
+        {
+            var underlyingType = Nullable.GetUnderlyingType(value.GetType());
+            if (underlyingType == null) return true;
+
+            var defaultValue = Activator.CreateInstance(underlyingType);
+            return value.Equals(defaultValue);
+        }
+
+        /// <summary>
+        /// 快速检查实体是否包含数据(至少有一个字段已填写)
+        /// 用于判断实体是否为空或未初始化
+        /// </summary>
+        /// <param name="obj">要检查的实体对象</param>
+        /// <returns>
+        /// true: 至少有一个字段已填写
+        /// false: 所有字段都为默认值或空
+        /// </returns>
+        /// <example>
+        /// <code>
+        /// var entity = new YourEntity();
+        /// if (!entity.HasData()) 
+        /// {
+        ///     Console.WriteLine("实体没有任何数据");
+        /// }
+        /// </code>
+        /// </example>
+        public static bool HasData(this object obj)
+        {
+            // 获取所有属性
+            var properties = obj.GetType().GetProperties();
+
+            // 使用LINQ的Any方法检查是否存在至少一个已填写的属性
+            return properties.Any(property => IsPropertyFilled(property, obj));
+        }
+    }
+
+    /// <summary>
+    /// 必填字段标记特性
+    /// 用于标记实体类中必须填写的属性
+    /// </summary>
+    [AttributeUsage(AttributeTargets.Property)]
+    public class RequiredFieldAttribute : Attribute
+    {
+        /// <summary>
+        /// 验证失败时显示的错误消息
+        /// </summary>
+        public string ErrorMessage { get; }
+
+        /// <summary>
+        /// 初始化必填字段特性
+        /// </summary>
+        /// <param name="errorMessage">验证失败时显示的错误消息</param>
+        public RequiredFieldAttribute(string errorMessage = "该字段为必填项")
+        {
+            ErrorMessage = errorMessage;
+        }
+    }
+}

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

@@ -31,7 +31,7 @@
         public bool IsVisaOnArrival { get; set; }
 
         /// <summary>
-        /// 是否电子签 0:是 1:否
+        /// 是否电子签
         /// </summary>
         [SugarColumn(ColumnName = "IsElectronicSign", ColumnDescription = "是否电子签", IsNullable = true, ColumnDataType = "bit")]
         public bool IsElectronicSign { get; set; }

+ 91 - 0
OASystem/OASystem.Infrastructure/Repositories/Groups/ProcessOverviewRepository.cs

@@ -1,6 +1,7 @@
 using AutoMapper;
 using Newtonsoft.Json;
 using OASystem.Domain;
+using OASystem.Domain.Dtos.Groups;
 using OASystem.Domain.Entities.Groups;
 using OASystem.Domain.ViewModels.JuHeExchangeRate;
 using System;
@@ -174,6 +175,9 @@ namespace OASystem.Infrastructure.Repositories.Groups
                         throw new BusinessException("当前节点不存在或已被删除。");
                     }
 
+                    // 新增验证:当前节点已完成,不可操作
+                    ValidateNodeOperation(node, processStatus);
+
                     // 2. 更新节点状态
                     node.OverallStatus = processStatus;
                     node.Operator = currUserId;
@@ -220,6 +224,32 @@ namespace OASystem.Infrastructure.Repositories.Groups
             }
         }
 
+        /// <summary>
+        /// 验证节点操作权限
+        /// </summary>
+        /// <param name="node">流程节点</param>
+        /// <param name="targetStatus">目标状态</param>
+        private void ValidateNodeOperation(Grp_ProcessNode node, ProcessStatus targetStatus)
+        {
+            // 验证节点是否已完成
+            if (node.OverallStatus == ProcessStatus.Completed)
+            {
+                throw new BusinessException("当前节点已完成,不可重复操作。");
+            }
+
+            // 验证状态流转是否合法(可选)
+            if (targetStatus != ProcessStatus.Completed)
+            {
+                throw new BusinessException("未开始或者进行中的节点只能重新完成,不可进行其他操作。");
+            }
+
+            // 验证是否尝试将已完成节点改为其他状态
+            if (node.OverallStatus == ProcessStatus.Completed && targetStatus != ProcessStatus.Completed)
+            {
+                throw new BusinessException("已完成节点不可修改状态。");
+            }
+        }
+
         /// <summary>
         /// 处理当前节点完成后的流程流转
         /// </summary>
@@ -299,5 +329,66 @@ namespace OASystem.Infrastructure.Repositories.Groups
                 throw new BusinessException("流程状态更新失败。");
             }
         }
+
+
+        /// <summary>
+        /// 更新签证节点信息及状态
+        /// </summary>
+        /// <param name="dto">签证节点更新数据传输对象</param>
+        /// <returns>操作结果</returns>
+        public async Task<Result> UpdateVisaNodeDetailsAsync(GroupProcessUpdateVisaNodeDetailsDto dto)
+        {
+            // 1. 获取并验证节点和流程
+            var node = await _sqlSugar.Queryable<Grp_ProcessNode>()
+                .FirstAsync(n => n.Id == dto.NodeId && n.IsDel == 0)
+                ?? throw new BusinessException("当前节点不存在或已被删除。");
+
+            var process = await _sqlSugar.Queryable<Grp_ProcessOverview>()
+                .FirstAsync(p => p.Id == node.ProcessId && p.IsDel == 0)
+                ?? throw new BusinessException("当前流程不存在或已被删除。");
+
+            if (process.ProcessType != GroupProcessType.Visa)
+            {
+                throw new BusinessException("当前流程节点不为签证流程,不可编辑。");
+            }
+
+            // 2. 检查签证子节点 字段信息是否全部填写
+            var allSubNodesCompleted = dto.VisaSubNodes?.All(subNode => EntityExtensions.IsCompleted(subNode)) ?? false;
+
+            // 3. 更新节点信息
+            node.Remark = JsonConvert.SerializeObject(dto.VisaSubNodes);
+            node.Operator = dto.CurrUserId;
+            node.OperationTime = DateTime.Now;
+
+            if (allSubNodesCompleted)
+            {
+                node.OverallStatus = ProcessStatus.Completed;
+
+                // 更新流程状态
+                await _sqlSugar.Updateable<Grp_ProcessOverview>()
+                    .SetColumns(p => new Grp_ProcessOverview
+                    {
+                        OverallStatus = ProcessStatus.Completed,
+                        EndTime = DateTime.Now,
+                        UpdatedUserId = dto.CurrUserId,
+                        UpdatedTime = DateTime.Now
+                    })
+                    .Where(p => p.Id == process.Id)
+                    .ExecuteCommandAsync();
+            }
+
+            // 4. 保存节点更新
+            await _sqlSugar.Updateable(node)
+                .UpdateColumns(n => new
+                {
+                    n.Remark,
+                    n.Operator,
+                    n.OperationTime,
+                    n.OverallStatus
+                })
+                .ExecuteCommandAsync();
+
+            return new Result { Code = 200, Msg = "节点信息更新成功。" };
+        }
     }
 }