Files
lserp_cs_6.0/插件库/Lskj.CommandKernel/LeaveRequestValidation.cs
2026-08-14 14:28:28 +08:00

193 lines
7.7 KiB
C#

using System;
using System.Collections.Generic;
namespace Lskj.CommandKernel
{
public sealed class LeaveRequestDraft
{
public string EmployeeId { get; set; }
public string LeaveTypeCode { get; set; }
public string FlowTypeCode { get; set; }
public DateTime StartLocal { get; set; }
public DateTime EndLocal { get; set; }
public decimal RequestedHours { get; set; }
public string Reason { get; set; }
public bool SubmitAfterSave { get; set; }
}
public sealed class LeaveValidationOptions
{
public LeaveValidationOptions()
{
MaximumCalendarDays = 31;
MinimumReasonLength = 2;
HoursTolerance = 0.01m;
}
public string CurrentEmployeeId { get; set; }
public bool CanApplyForOthers { get; set; }
public bool AllowPastStart { get; set; }
public int MaximumCalendarDays { get; set; }
public int MinimumReasonLength { get; set; }
public decimal HoursTolerance { get; set; }
}
public interface IWorkingTimeCalculator
{
decimal CalculateHours(string employeeId, DateTime startLocal, DateTime endLocal);
}
public interface ILeaveConflictProvider
{
bool HasConflict(string employeeId, DateTime startLocal, DateTime endLocal);
}
public sealed class LeaveValidationResult
{
public LeaveValidationResult()
{
Issues = new List<string>();
}
public bool Valid { get; set; }
public decimal CalculatedHours { get; set; }
public IList<string> Issues { get; private set; }
}
public static class LeaveRequestValidator
{
public static LeaveValidationResult ValidateShape(
LeaveRequestDraft draft,
LeaveValidationOptions options)
{
if (draft == null) throw new ArgumentNullException("draft");
ValidateOptions(options);
LeaveValidationResult result = new LeaveValidationResult();
if (!SafeCode(draft.EmployeeId, 64))
result.Issues.Add("请假员工编码格式无效。");
if (!SafeCode(draft.LeaveTypeCode, 64))
result.Issues.Add("请假类型必须是有效的 ERP 编码。");
if (!SafeCode(draft.FlowTypeCode, 64))
result.Issues.Add("请假流转类别必须是有效的 ERP 编码。");
if (draft.StartLocal.Kind != DateTimeKind.Unspecified
|| draft.EndLocal.Kind != DateTimeKind.Unspecified)
{
result.Issues.Add("请假时间必须是不带 Z 或时区偏移的本地时间。");
}
if (draft.StartLocal.Year < 1900 || draft.StartLocal.Year > 2100
|| draft.EndLocal.Year < 1900 || draft.EndLocal.Year > 2100)
result.Issues.Add("请假时间超出允许范围。");
if (draft.StartLocal >= draft.EndLocal)
result.Issues.Add("请假结束时间必须晚于开始时间。");
if (draft.EndLocal - draft.StartLocal
> TimeSpan.FromDays(options.MaximumCalendarDays))
result.Issues.Add("请假跨度超过系统允许的最大天数。");
if (draft.RequestedHours < 0 || draft.RequestedHours > 744m)
result.Issues.Add("请求时数超出允许范围。");
string reason = (draft.Reason ?? string.Empty).Trim();
if (reason.Length < options.MinimumReasonLength)
result.Issues.Add("请假原因过短。");
if (reason.Length > 500 || HasControl(reason))
result.Issues.Add("请假原因过长或包含控制字符。");
result.Valid = result.Issues.Count == 0;
return result;
}
public static LeaveValidationResult Validate(
LeaveRequestDraft draft,
LeaveValidationOptions options,
IWorkingTimeCalculator workingTime,
ILeaveConflictProvider conflicts,
DateTime nowLocal)
{
if (draft == null) throw new ArgumentNullException("draft");
if (options == null) throw new ArgumentNullException("options");
if (workingTime == null) throw new ArgumentNullException("workingTime");
if (conflicts == null) throw new ArgumentNullException("conflicts");
LeaveValidationResult result = ValidateShape(draft, options);
if (!options.CanApplyForOthers
&& !Same(draft.EmployeeId, options.CurrentEmployeeId))
{
result.Issues.Add("当前用户无权代他人申请请假。");
}
if (nowLocal.Kind == DateTimeKind.Utc)
result.Issues.Add("ERP 当前时间不是有效的本地时间。");
if (!options.AllowPastStart && draft.StartLocal < nowLocal)
result.Issues.Add("不能申请已开始的历史请假。");
if (result.Issues.Count == 0)
{
result.CalculatedHours = workingTime.CalculateHours(
draft.EmployeeId,
draft.StartLocal,
draft.EndLocal);
if (result.CalculatedHours <= 0)
result.Issues.Add("所选时间段没有可申请的工作时数。");
if (draft.RequestedHours > 0
&& Math.Abs(draft.RequestedHours - result.CalculatedHours) > options.HoursTolerance)
{
result.Issues.Add("请求时数与员工日历计算结果不一致。");
}
if (conflicts.HasConflict(draft.EmployeeId, draft.StartLocal, draft.EndLocal))
result.Issues.Add("该员工在所选时间段已有请假或冲突申请。");
}
result.Valid = result.Issues.Count == 0;
return result;
}
private static bool Same(string left, string right)
{
return string.Equals(
(left ?? string.Empty).Trim(),
(right ?? string.Empty).Trim(),
StringComparison.OrdinalIgnoreCase);
}
internal static bool SafeCode(string value, int maximum)
{
string text = (value ?? string.Empty).Trim();
if (text.Length == 0 || text.Length > maximum) return false;
foreach (char item in text)
{
if (!char.IsLetterOrDigit(item)
&& item != '_' && item != '-' && item != '.' && item != ':')
return false;
}
return true;
}
internal static bool SafeText(
string value,
int minimum,
int maximum,
bool optional)
{
string text = (value ?? string.Empty).Trim();
if (text.Length == 0) return optional;
return text.Length >= minimum
&& text.Length <= maximum
&& !HasControl(text);
}
internal static void ValidateOptions(LeaveValidationOptions options)
{
if (options == null) throw new ArgumentNullException("options");
if (options.MaximumCalendarDays <= 0
|| options.MaximumCalendarDays > 31
|| options.MinimumReasonLength < 0
|| options.MinimumReasonLength > 500
|| options.HoursTolerance < 0
|| options.HoursTolerance > 1)
throw new ArgumentOutOfRangeException("options", "请假校验配置无效。");
}
private static bool HasControl(string value)
{
foreach (char item in value ?? string.Empty)
if (char.IsControl(item)) return true;
return false;
}
}
}