|
@@ -1,9 +1,14 @@
|
|
|
using AutoMapper;
|
|
|
+using NPOI.Util;
|
|
|
using OASystem.Domain;
|
|
|
using OASystem.Domain.Dtos.Groups;
|
|
|
+using OASystem.Domain.Dtos.Task;
|
|
|
using OASystem.Domain.Entities.Groups;
|
|
|
using OASystem.Domain.ViewModels.Groups;
|
|
|
+using OASystem.Domain.ViewModels.SmallFun;
|
|
|
using OASystem.Infrastructure.Tools;
|
|
|
+using System.Reflection;
|
|
|
+using System.Text.Json.Serialization;
|
|
|
|
|
|
namespace OASystem.Infrastructure.Repositories.Groups
|
|
|
{
|
|
@@ -12,7 +17,6 @@ namespace OASystem.Infrastructure.Repositories.Groups
|
|
|
/// </summary>
|
|
|
public class VisaProcessRepository : BaseRepository<Grp_VisaProcessSteps, Grp_VisaProcessSteps>
|
|
|
{
|
|
|
-
|
|
|
private readonly IMapper _mapper;
|
|
|
|
|
|
public VisaProcessRepository(SqlSugarClient sqlSugar, IMapper mapper)
|
|
@@ -54,6 +58,12 @@ namespace OASystem.Infrastructure.Repositories.Groups
|
|
|
|
|
|
if (add < 1) return new Result(400, "签证流程步骤创建失败。");
|
|
|
|
|
|
+ // 记录创建日志
|
|
|
+ foreach (var step in steps)
|
|
|
+ {
|
|
|
+ await LogOperationAsync(null, step, "Create", createUderId);
|
|
|
+ }
|
|
|
+
|
|
|
return new Result(200,"Success");
|
|
|
}
|
|
|
|
|
@@ -222,21 +232,23 @@ namespace OASystem.Infrastructure.Repositories.Groups
|
|
|
/// <returns></returns>
|
|
|
public async Task<Result> Update(Grp_VisaProcessSteps info)
|
|
|
{
|
|
|
- var update = await _sqlSugar.Updateable<Grp_VisaProcessSteps>()
|
|
|
- .SetColumns(s => new Grp_VisaProcessSteps
|
|
|
- {
|
|
|
- StoreVal = info.StoreVal,
|
|
|
- //AttachUrl = info.AttachUrl,
|
|
|
- //IsCompleted = info.IsCompleted,
|
|
|
- LastUpdateUserId = info.LastUpdateUserId,
|
|
|
- LastUpdateTime = DateTime.Now,
|
|
|
- Remark = info.Remark
|
|
|
- })
|
|
|
+ var step = await _sqlSugar.Queryable<Grp_VisaProcessSteps>().FirstAsync(x => x.Id == info.Id);
|
|
|
+ var before = ManualClone(step);
|
|
|
+
|
|
|
+ step.StoreVal = info.StoreVal;
|
|
|
+ step.LastUpdateUserId = info.LastUpdateUserId;
|
|
|
+ step.LastUpdateTime = DateTime.Now;
|
|
|
+ step.Remark = info.Remark;
|
|
|
+
|
|
|
+ var update = await _sqlSugar.Updateable<Grp_VisaProcessSteps>(step)
|
|
|
.Where(s => s.Id == info.Id && s.IsDel == 0)
|
|
|
.ExecuteCommandAsync();
|
|
|
|
|
|
if (update < 1) return new Result(400, "更新失败!");
|
|
|
|
|
|
+ // 记录更新日志
|
|
|
+ await LogOperationAsync(before, step, "Update", info.LastUpdateUserId);
|
|
|
+
|
|
|
return new Result(200, "Success");
|
|
|
}
|
|
|
|
|
@@ -266,6 +278,12 @@ namespace OASystem.Infrastructure.Repositories.Groups
|
|
|
.FirstAsync();
|
|
|
if (invertedInfo == null) return new Result(400, "倒推表信息不存在。");
|
|
|
|
|
|
+ //步骤更改前的值
|
|
|
+ var beforeStep1 = ManualClone(visaSteps.FirstOrDefault(x => x.Step == 1));
|
|
|
+ var beforeStep2 = ManualClone(visaSteps.FirstOrDefault(x => x.Step == 2));
|
|
|
+ var beforeStep3 = ManualClone(visaSteps.FirstOrDefault(x => x.Step == 3));
|
|
|
+ var beforeStep4 = ManualClone(visaSteps.FirstOrDefault(x => x.Step == 4));
|
|
|
+
|
|
|
//设置操作人和时间
|
|
|
foreach (var item in visaSteps)
|
|
|
{
|
|
@@ -287,14 +305,24 @@ namespace OASystem.Infrastructure.Repositories.Groups
|
|
|
//step=4 预计出签时间
|
|
|
visaSteps.FirstOrDefault(x => x.Step == 4).TypedValue = invertedInfo.IssueVisaDt;
|
|
|
|
|
|
-
|
|
|
-
|
|
|
var update = await _sqlSugar.Updateable(visaSteps)
|
|
|
.UpdateColumns(it => new { it.StoreVal, it.LastUpdateUserId,it.LastUpdateTime })
|
|
|
.ExecuteCommandAsync();
|
|
|
|
|
|
if (update < 1) return new Result(400, "更新失败!");
|
|
|
|
|
|
+ //更改后的值
|
|
|
+ var afterStep1 = visaSteps.FirstOrDefault(x => x.Step == 1);
|
|
|
+ var afterStep2 = visaSteps.FirstOrDefault(x => x.Step == 2);
|
|
|
+ var afterStep3 = visaSteps.FirstOrDefault(x => x.Step == 3);
|
|
|
+ var afterStep4 = visaSteps.FirstOrDefault(x => x.Step == 4);
|
|
|
+
|
|
|
+ // 记录更新日志
|
|
|
+ await LogOperationAsync(beforeStep1, afterStep1, "Update", currUserId);
|
|
|
+ await LogOperationAsync(beforeStep2, afterStep2, "Update", currUserId);
|
|
|
+ await LogOperationAsync(beforeStep3, afterStep3, "Update", currUserId);
|
|
|
+ await LogOperationAsync(beforeStep4, afterStep4, "Update", currUserId);
|
|
|
+
|
|
|
return new Result(200, "Success");
|
|
|
}
|
|
|
|
|
@@ -309,9 +337,17 @@ namespace OASystem.Infrastructure.Repositories.Groups
|
|
|
{
|
|
|
//步骤信息验证
|
|
|
var stepInfo = await _sqlSugar.Queryable<Grp_VisaProcessSteps>()
|
|
|
- .Where(s => s.Id == id && s.IsDel == 0 && s.IsCompleted == isCompleted)
|
|
|
+ .Where(s => s.Id == id && s.IsDel == 0)
|
|
|
.FirstAsync();
|
|
|
- if (stepInfo != null) return new Result(400, "步骤信息已完成,无法重复设置。");
|
|
|
+
|
|
|
+ if (stepInfo == null) return new Result(400, "步骤信息不存在,无法设置。");
|
|
|
+
|
|
|
+ if (stepInfo.IsCompleted) return new Result(400, "步骤信息已完成,无法重复设置。");
|
|
|
+
|
|
|
+ var before = ManualClone(stepInfo);
|
|
|
+ stepInfo.IsCompleted = isCompleted;
|
|
|
+ stepInfo.LastUpdateUserId = currUserId;
|
|
|
+ stepInfo.LastUpdateTime = DateTime.Now;
|
|
|
|
|
|
var update = await _sqlSugar.Updateable<Grp_VisaProcessSteps>()
|
|
|
.SetColumns(s => new Grp_VisaProcessSteps
|
|
@@ -325,9 +361,301 @@ namespace OASystem.Infrastructure.Repositories.Groups
|
|
|
|
|
|
if (update < 1) return new Result(400, "状态设置失败!");
|
|
|
|
|
|
+ // 记录更新日志
|
|
|
+ await LogOperationAsync(before, stepInfo, "Update", currUserId);
|
|
|
|
|
|
return new Result(200, "Success");
|
|
|
}
|
|
|
|
|
|
+
|
|
|
+ #region 操作日志
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 记录签证流程步骤的操作日志
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="before">操作前的步骤数据实体</param>
|
|
|
+ /// <param name="after">操作后的步骤数据实体</param>
|
|
|
+ /// <param name="opType">操作类型(Create-创建, Update-更新, Delete-删除, Complete-完成, Upload-上传附件, Download-下载文件)</param>
|
|
|
+ /// <param name="operatorId">操作人用户ID</param>
|
|
|
+ /// <returns>表示异步操作的任务</returns>
|
|
|
+ /// <remarks>此方法会自动比较前后数据的差异,生成变更字段列表和操作描述</remarks>
|
|
|
+ public async Task LogOperationAsync(Grp_VisaProcessSteps before, Grp_VisaProcessSteps after,string opType, int operatorId)
|
|
|
+ {
|
|
|
+ // 合并基础排除字段和额外排除字段
|
|
|
+ var allExcludedFields = GetDefaultExcludedFields();
|
|
|
+ var changedFields = GetChangeDetails(before, after, allExcludedFields);
|
|
|
+
|
|
|
+ var log = new Grp_VisaProcessSteps_Log
|
|
|
+ {
|
|
|
+ StepId = after?.Id ?? before?.Id ?? 0,
|
|
|
+ GroupId = after?.GroupId ?? before?.GroupId ?? 0,
|
|
|
+ Step = after?.Step ?? before?.Step ?? 0,
|
|
|
+ OperationType = opType,
|
|
|
+ OperationDescription = GenerateOpDesc(opType, before, after, changedFields),
|
|
|
+ BeforeData = before != null ? JsonSerializer.Serialize(before, new JsonSerializerOptions
|
|
|
+ {
|
|
|
+ ReferenceHandler = ReferenceHandler.IgnoreCycles,
|
|
|
+ WriteIndented = false
|
|
|
+ }) : null,
|
|
|
+ AfterData = after != null ? JsonSerializer.Serialize(after, new JsonSerializerOptions
|
|
|
+ {
|
|
|
+ ReferenceHandler = ReferenceHandler.IgnoreCycles,
|
|
|
+ WriteIndented = false
|
|
|
+ }) : null,
|
|
|
+ ChangedFields = string.Join(",", changedFields),
|
|
|
+ CreateUserId = operatorId,
|
|
|
+ };
|
|
|
+
|
|
|
+ await _sqlSugar.Insertable(log).ExecuteCommandAsync();
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 获取字段变更详情
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="before">变更前</param>
|
|
|
+ /// <param name="after">变更后</param>
|
|
|
+ /// <param name="exclFields">排除字段</param>
|
|
|
+ /// <returns>变更详情列表</returns>
|
|
|
+ private List<FieldChangeDetail> GetChangeDetails(Grp_VisaProcessSteps before, Grp_VisaProcessSteps after, List<string> exclFields = null)
|
|
|
+ {
|
|
|
+ var changeDetails = new List<FieldChangeDetail>();
|
|
|
+ var defaultExclFields = GetDefaultExcludedFields();
|
|
|
+
|
|
|
+ // 合并排除字段
|
|
|
+ var allExclFields = defaultExclFields;
|
|
|
+ if (exclFields != null && exclFields.Any())
|
|
|
+ {
|
|
|
+ allExclFields = defaultExclFields.Union(exclFields).ToList();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (before == null || after == null) return changeDetails;
|
|
|
+
|
|
|
+ var properties = typeof(Grp_VisaProcessSteps).GetProperties(BindingFlags.Public | BindingFlags.Instance);
|
|
|
+
|
|
|
+ foreach (var prop in properties)
|
|
|
+ {
|
|
|
+ // 跳过排除的字段
|
|
|
+ if (allExclFields.Contains(prop.Name))
|
|
|
+ {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ var beforeValue = prop.GetValue(before);
|
|
|
+ var afterValue = prop.GetValue(after);
|
|
|
+
|
|
|
+ // 处理字符串类型的特殊比较(忽略前后空格)
|
|
|
+ if (prop.PropertyType == typeof(string))
|
|
|
+ {
|
|
|
+ var beforeString = beforeValue?.ToString()?.Trim();
|
|
|
+ var afterString = afterValue?.ToString()?.Trim();
|
|
|
+
|
|
|
+ if (beforeString != afterString)
|
|
|
+ {
|
|
|
+ changeDetails.Add(new FieldChangeDetail
|
|
|
+ {
|
|
|
+ FieldName = prop.Name,
|
|
|
+ BeforeValue = FormatValue(beforeString),
|
|
|
+ AfterValue = FormatValue(afterString)
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // 其他类型使用默认比较
|
|
|
+ if (!Equals(beforeValue, afterValue))
|
|
|
+ {
|
|
|
+ changeDetails.Add(new FieldChangeDetail
|
|
|
+ {
|
|
|
+ FieldName = prop.Name,
|
|
|
+ BeforeValue = FormatValue(beforeValue),
|
|
|
+ AfterValue = FormatValue(afterValue)
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return changeDetails;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 格式化值显示
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="value">原始值</param>
|
|
|
+ /// <returns>格式化后的值</returns>
|
|
|
+ private static string FormatValue(object value)
|
|
|
+ {
|
|
|
+ if (value == null) return "null";
|
|
|
+
|
|
|
+ var strValue = value.ToString();
|
|
|
+
|
|
|
+ // 处理空字符串
|
|
|
+ if (string.IsNullOrEmpty(strValue)) return "空";
|
|
|
+
|
|
|
+ // 处理布尔值
|
|
|
+ if (value is bool boolValue) return boolValue ? "是" : "否";
|
|
|
+
|
|
|
+ // 处理日期时间
|
|
|
+ if (value is DateTime dateTimeValue) return dateTimeValue.ToString("yyyy-MM-dd HH:mm");
|
|
|
+
|
|
|
+ // 处理长文本截断
|
|
|
+ if (strValue.Length > 50) return strValue.Substring(0, 47) + "...";
|
|
|
+
|
|
|
+ return strValue;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 生成操作描述
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="opType">操作类型</param>
|
|
|
+ /// <param name="before">操作前</param>
|
|
|
+ /// <param name="after">操作后</param>
|
|
|
+ /// <param name="chgDetails">变更详情</param>
|
|
|
+ /// <returns>操作描述</returns>
|
|
|
+ private static string GenerateOpDesc(string opType, Grp_VisaProcessSteps before,
|
|
|
+ Grp_VisaProcessSteps after, List<FieldChangeDetail> chgDetails)
|
|
|
+ {
|
|
|
+ if (!chgDetails.Any())
|
|
|
+ {
|
|
|
+ return opType switch
|
|
|
+ {
|
|
|
+ "Create" => $"创建步骤:团组{after.GroupId}-步骤{after.Step}",
|
|
|
+ "Update" => $"更新步骤:无变更",
|
|
|
+ "Complete" => $"完成步骤:团组{after.GroupId}-步骤{after.Step}",
|
|
|
+ "Uncomplete" => $"取消完成:团组{after.GroupId}-步骤{after.Step}",
|
|
|
+ "Delete" => $"删除步骤:团组{before.GroupId}-步骤{before.Step}",
|
|
|
+ "Upload" => $"上传附件:团组{after.GroupId}-步骤{after.Step}",
|
|
|
+ _ => $"{opType}:团组{after?.GroupId ?? before?.GroupId}-步骤{after?.Step ?? before?.Step}"
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ var changeDesc = string.Join("; ", chgDetails.Select(x =>
|
|
|
+ $"{x.FieldName} ({x.BeforeValue} -> {x.AfterValue})"));
|
|
|
+
|
|
|
+ return opType switch
|
|
|
+ {
|
|
|
+ "Create" => $"创建步骤:团组{after.GroupId}-步骤{after.Step}",
|
|
|
+ "Update" => $"更新步骤:{changeDesc}",
|
|
|
+ "Complete" => $"完成步骤:团组{after.GroupId}-步骤{after.Step}",
|
|
|
+ "Uncomplete" => $"取消完成:团组{after.GroupId}-步骤{after.Step}",
|
|
|
+ "Delete" => $"删除步骤:团组{before.GroupId}-步骤{before.Step}",
|
|
|
+ "Upload" => $"上传附件:团组{after.GroupId}-步骤{after.Step}",
|
|
|
+ _ => $"{opType}:{changeDesc}"
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 字段变更详情类
|
|
|
+ /// </summary>
|
|
|
+ private class FieldChangeDetail
|
|
|
+ {
|
|
|
+ public string FieldName { get; set; }
|
|
|
+ public string BeforeValue { get; set; }
|
|
|
+ public string AfterValue { get; set; }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 获取默认排除的字段列表(包含系统字段和忽略字段)
|
|
|
+ /// </summary>
|
|
|
+ /// <returns>默认排除的字段列表</returns>
|
|
|
+ private static List<string> GetDefaultExcludedFields()
|
|
|
+ {
|
|
|
+ var defaultExcludedFields = new List<string>
|
|
|
+ {
|
|
|
+ nameof(Grp_VisaProcessSteps.Id),
|
|
|
+ nameof(Grp_VisaProcessSteps.CreateTime),
|
|
|
+ nameof(Grp_VisaProcessSteps.CreateUserId),
|
|
|
+ //nameof(Grp_VisaProcessSteps.LastUpdateTime),
|
|
|
+ //nameof(Grp_VisaProcessSteps.LastUpdateUserId),
|
|
|
+
|
|
|
+ // 计算字段(原本标记为 [SugarColumn(IsIgnore = true)] 的字段)
|
|
|
+ "TypedFileNameValue",
|
|
|
+ "TypedValue",
|
|
|
+ "StringValue",
|
|
|
+ "IntValue",
|
|
|
+ "DecimalValue",
|
|
|
+ "BooleanValue",
|
|
|
+ "DateTimeValue"
|
|
|
+ };
|
|
|
+
|
|
|
+ return defaultExcludedFields.Distinct().ToList();
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 手动创建步骤实体的副本
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="source">源步骤实体</param>
|
|
|
+ /// <returns>新的步骤实体副本</returns>
|
|
|
+ private Grp_VisaProcessSteps ManualClone(Grp_VisaProcessSteps source)
|
|
|
+ {
|
|
|
+ if (source == null) return null;
|
|
|
+
|
|
|
+ return new Grp_VisaProcessSteps
|
|
|
+ {
|
|
|
+ Id = source.Id,
|
|
|
+ GroupId = source.GroupId,
|
|
|
+ Step = source.Step,
|
|
|
+ DataType = source.DataType,
|
|
|
+ StoreVal = source.StoreVal,
|
|
|
+ AttachUrl = source.AttachUrl,
|
|
|
+ IsCompleted = source.IsCompleted,
|
|
|
+ LastUpdateUserId = source.LastUpdateUserId,
|
|
|
+ LastUpdateTime = source.LastUpdateTime,
|
|
|
+ CreateUserId = source.CreateUserId,
|
|
|
+ CreateTime = source.CreateTime
|
|
|
+ // 注意:这里不拷贝计算属性(TypedValue 等),因为它们会被重新计算
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 根据步骤记录ID获取该步骤的所有操作日志
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="stepId">步骤记录的主键ID</param>
|
|
|
+ /// <returns>步骤操作日志列表,按操作时间倒序排列</returns>
|
|
|
+ public async Task<List<VisaProcessStepsLogView>> GetStepLogsAsync(int stepId)
|
|
|
+ {
|
|
|
+ return await _sqlSugar.Queryable<Grp_VisaProcessSteps_Log>()
|
|
|
+ .LeftJoin<Sys_Users>((x, y) => x.CreateUserId == y.Id)
|
|
|
+ .Where((x, y) => x.StepId == stepId)
|
|
|
+ .Select((x, y) => new VisaProcessStepsLogView
|
|
|
+ {
|
|
|
+ StepId = x.StepId,
|
|
|
+ GroupId = x.GroupId,
|
|
|
+ Step = x.Step,
|
|
|
+ OperationType = x.OperationType,
|
|
|
+ OperationDescription = x.OperationDescription,
|
|
|
+ Operator = y.CnName,
|
|
|
+ OperationTime = x.CreateTime,
|
|
|
+ })
|
|
|
+ .OrderByDescending(x => x.OperationTime)
|
|
|
+ .ToListAsync();
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 根据团组ID获取该团组下所有步骤的操作日志
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="groupId">团组ID</param>
|
|
|
+ /// <returns>团组步骤操作日志列表,按操作时间倒序排列</returns>
|
|
|
+ public async Task<List<VisaProcessStepsLogView>> GetGroupStepLogsAsync(int groupId)
|
|
|
+ {
|
|
|
+ return await _sqlSugar.Queryable<Grp_VisaProcessSteps_Log>()
|
|
|
+ .LeftJoin<Sys_Users>((x, y) => x.CreateUserId == y.Id)
|
|
|
+ .Where((x, y) => x.GroupId == groupId)
|
|
|
+ .Select((x, y) => new VisaProcessStepsLogView
|
|
|
+ {
|
|
|
+ StepId = x.StepId,
|
|
|
+ GroupId = x.GroupId,
|
|
|
+ Step = x.Step,
|
|
|
+ OperationType = x.OperationType,
|
|
|
+ OperationDescription = x.OperationDescription,
|
|
|
+ Operator = y.CnName,
|
|
|
+ OperationTime = x.CreateTime,
|
|
|
+ })
|
|
|
+ .OrderByDescending(x => x.OperationTime)
|
|
|
+ .ToListAsync();
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ #endregion
|
|
|
+
|
|
|
}
|
|
|
}
|