340 lines
13 KiB
C#
340 lines
13 KiB
C#
namespace SimpleAdmin.Application;
|
|
|
|
/// <summary>
|
|
/// 统一校验并规范化员工端固定入职信息。
|
|
/// </summary>
|
|
internal static class OnboardingEmployeeInfoValidator
|
|
{
|
|
private static readonly int[] IdCardWeights =
|
|
{
|
|
7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2
|
|
};
|
|
|
|
private static readonly char[] IdCardCheckCodes =
|
|
{
|
|
'1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'
|
|
};
|
|
|
|
/// <summary>
|
|
/// 规范化文本,并按照任务冻结配置校验固定登记表必填项及字段格式。
|
|
/// </summary>
|
|
public static void ValidateAndNormalize(
|
|
ObTaskEmployeeInfo input,
|
|
IReadOnlyCollection<ObTaskFormRequirement> fieldRequirements = null)
|
|
{
|
|
input.EmployeeName = input.EmployeeName?.Trim();
|
|
input.Phone = input.Phone?.Trim();
|
|
input.Email = input.Email?.Trim();
|
|
input.IdCardNumber = input.IdCardNumber?.Trim().ToUpperInvariant();
|
|
input.BankCardNumber = global::System.Text.RegularExpressions.Regex.Replace(
|
|
input.BankCardNumber ?? string.Empty,
|
|
@"[\s-]",
|
|
string.Empty);
|
|
input.BankName = input.BankName?.Trim();
|
|
input.BankAccountName = input.EmployeeName;
|
|
|
|
ValidateRequiredFields(input, fieldRequirements);
|
|
|
|
// 电子签约实名认证始终依赖以下三项,不能通过后台配置关闭。
|
|
if (string.IsNullOrWhiteSpace(input.EmployeeName) || input.EmployeeName.Length > 100)
|
|
throw Oops.Bah("员工姓名不能为空且不能超过 100 个字符");
|
|
|
|
if (!string.IsNullOrWhiteSpace(input.Gender)
|
|
&& input.Gender != "男"
|
|
&& input.Gender != "女")
|
|
{
|
|
throw Oops.Bah("请选择正确的性别");
|
|
}
|
|
|
|
if (!global::System.Text.RegularExpressions.Regex.IsMatch(
|
|
input.Phone ?? string.Empty,
|
|
@"^1[3-9]\d{9}$"))
|
|
{
|
|
throw Oops.Bah("请输入正确的 11 位手机号码");
|
|
}
|
|
|
|
if (!TryValidateIdCard(input.IdCardNumber, out var idCardBirthday, out var idCardError))
|
|
throw Oops.Bah(idCardError);
|
|
|
|
ValidateBirthday(input.Birthday, idCardBirthday);
|
|
ValidateOptionalEmail(input.Email);
|
|
ValidateOptionalNumber(input.Height, "身高", 100M, 250M, "厘米");
|
|
ValidateOptionalNumber(input.Weight, "体重", 30M, 300M, "公斤");
|
|
if (!string.IsNullOrWhiteSpace(input.BankCardNumber))
|
|
ValidateBankCard(input.BankCardNumber);
|
|
|
|
if (!string.IsNullOrWhiteSpace(input.BankName) && input.BankName.Length > 100)
|
|
throw Oops.Bah("开户银行名称不能超过 100 个字符");
|
|
|
|
ValidateOptionalContactPhone(input.EmergencyPhone);
|
|
ValidateEducationHistory(input.EducationHistoryJson);
|
|
ValidateWorkHistory(input.WorkHistoryJson);
|
|
ValidateFamilyMembers(input.FamilyMembersJson);
|
|
ValidateJson(input.CustomVariablesJson, global::System.Text.Json.JsonValueKind.Object, "员工补充变量");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 按任务快照校验后台配置为必填的内置员工字段。
|
|
/// </summary>
|
|
private static void ValidateRequiredFields(
|
|
ObTaskEmployeeInfo input,
|
|
IReadOnlyCollection<ObTaskFormRequirement> fieldRequirements)
|
|
{
|
|
if (fieldRequirements == null || fieldRequirements.Count == 0)
|
|
return;
|
|
|
|
foreach (var requirement in fieldRequirements.Where(item =>
|
|
item.Required
|
|
&& OnboardingEmployeeFieldRequirementSnapshot.IsEmployeeField(item)))
|
|
{
|
|
var separatorIndex = requirement.FieldCode.LastIndexOf('.');
|
|
var propertyName = separatorIndex >= 0
|
|
? requirement.FieldCode[(separatorIndex + 1)..]
|
|
: requirement.FieldCode;
|
|
var property = typeof(ObTaskEmployeeInfo).GetProperty(
|
|
propertyName,
|
|
global::System.Reflection.BindingFlags.Instance
|
|
| global::System.Reflection.BindingFlags.Public
|
|
| global::System.Reflection.BindingFlags.IgnoreCase);
|
|
if (property == null)
|
|
{
|
|
throw Oops.Bah(
|
|
$"内置员工变量“{requirement.FieldCode}”没有对应的入职登记表字段,请联系管理员");
|
|
}
|
|
|
|
var value = property.GetValue(input)?.ToString();
|
|
if (string.IsNullOrWhiteSpace(value))
|
|
throw Oops.Bah($"请填写入职登记表“{requirement.FieldName}”");
|
|
}
|
|
}
|
|
private static bool TryValidateIdCard(
|
|
string idCardNumber,
|
|
out DateTime birthday,
|
|
out string errorMessage)
|
|
{
|
|
birthday = default;
|
|
errorMessage = null;
|
|
|
|
var structureIsValid = global::System.Text.RegularExpressions.Regex.IsMatch(
|
|
idCardNumber ?? string.Empty,
|
|
"^\\d{17}[0-9X]$");
|
|
if (!structureIsValid)
|
|
{
|
|
errorMessage = "请输入正确的 18 位身份证号码";
|
|
return false;
|
|
}
|
|
|
|
var birthdayText = idCardNumber.Substring(6, 8);
|
|
var birthdayIsValid = DateTime.TryParseExact(
|
|
birthdayText,
|
|
"yyyyMMdd",
|
|
global::System.Globalization.CultureInfo.InvariantCulture,
|
|
global::System.Globalization.DateTimeStyles.None,
|
|
out birthday);
|
|
if (!birthdayIsValid || birthday.Date > DateTime.Today)
|
|
{
|
|
errorMessage = "身份证号码中的出生日期不正确";
|
|
return false;
|
|
}
|
|
|
|
var total = 0;
|
|
for (var index = 0; index < IdCardWeights.Length; index++)
|
|
{
|
|
total += (idCardNumber[index] - '0') * IdCardWeights[index];
|
|
}
|
|
|
|
if (IdCardCheckCodes[total % 11] == idCardNumber[17])
|
|
return true;
|
|
|
|
errorMessage = "身份证号码校验位不正确";
|
|
return false;
|
|
}
|
|
|
|
private static void ValidateBirthday(string birthdayText, DateTime? idCardBirthday)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(birthdayText))
|
|
return;
|
|
|
|
var birthdayIsValid = DateTime.TryParseExact(
|
|
birthdayText.Trim(),
|
|
"yyyy-MM-dd",
|
|
global::System.Globalization.CultureInfo.InvariantCulture,
|
|
global::System.Globalization.DateTimeStyles.None,
|
|
out var birthday);
|
|
if (!birthdayIsValid || birthday.Date > DateTime.Today)
|
|
throw Oops.Bah("请输入正确的出生日期");
|
|
|
|
if (idCardBirthday.HasValue && birthday.Date != idCardBirthday.Value.Date)
|
|
throw Oops.Bah("出生日期与身份证号码不一致");
|
|
}
|
|
|
|
private static void ValidateOptionalEmail(string email)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(email))
|
|
return;
|
|
|
|
var emailIsValid = email.Length <= 100
|
|
&& global::System.Net.Mail.MailAddress.TryCreate(email, out _);
|
|
if (!emailIsValid)
|
|
throw Oops.Bah("请输入正确的电子邮箱");
|
|
}
|
|
|
|
private static void ValidateOptionalNumber(
|
|
string value,
|
|
string fieldName,
|
|
decimal min,
|
|
decimal max,
|
|
string unit)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(value))
|
|
return;
|
|
|
|
var numberIsValid = decimal.TryParse(
|
|
value.Trim(),
|
|
global::System.Globalization.NumberStyles.Number,
|
|
global::System.Globalization.CultureInfo.InvariantCulture,
|
|
out var number);
|
|
if (!numberIsValid || number < min || number > max)
|
|
{
|
|
throw Oops.Bah($"{fieldName}应为 {min:0} 至 {max:0} {unit}之间的数字");
|
|
}
|
|
}
|
|
|
|
private static void ValidateBankCard(string bankCardNumber)
|
|
{
|
|
var bankCardIsValid = global::System.Text.RegularExpressions.Regex.IsMatch(
|
|
bankCardNumber ?? string.Empty,
|
|
"^\\d{12,19}$");
|
|
if (!bankCardIsValid)
|
|
throw Oops.Bah("请输入 12 至 19 位银行卡号");
|
|
}
|
|
|
|
private static void ValidateOptionalContactPhone(string phone)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(phone))
|
|
return;
|
|
|
|
var contactPhoneIsValid = global::System.Text.RegularExpressions.Regex.IsMatch(
|
|
phone.Trim(),
|
|
"^(1[3-9]\\d{9}|0\\d{2,3}-?\\d{7,8})$");
|
|
if (!contactPhoneIsValid)
|
|
throw Oops.Bah("请输入正确的紧急联系人手机号码或座机号码");
|
|
}
|
|
|
|
private static void ValidateEducationHistory(string json)
|
|
{
|
|
ValidateHistoryRows(json, "教育经历", (row, rowNumber) =>
|
|
{
|
|
GetRequiredRowValue(row, "timeRange", rowNumber, "教育经历", "时间范围");
|
|
GetRequiredRowValue(row, "location", rowNumber, "教育经历", "学校或机构");
|
|
GetRequiredRowValue(row, "content", rowNumber, "教育经历", "专业或学习内容");
|
|
GetRequiredRowValue(row, "result", rowNumber, "教育经历", "结果或学位");
|
|
});
|
|
}
|
|
|
|
private static void ValidateWorkHistory(string json)
|
|
{
|
|
ValidateHistoryRows(json, "工作经历", (row, rowNumber) =>
|
|
{
|
|
GetRequiredRowValue(row, "timeRange", rowNumber, "工作经历", "起止时间");
|
|
GetRequiredRowValue(row, "companyName", rowNumber, "工作经历", "工作单位");
|
|
GetRequiredRowValue(row, "position", rowNumber, "工作经历", "担任职务");
|
|
GetRequiredRowValue(row, "leaveReason", rowNumber, "工作经历", "离职原因");
|
|
});
|
|
}
|
|
|
|
private static void ValidateFamilyMembers(string json)
|
|
{
|
|
ValidateHistoryRows(json, "家庭成员", (row, rowNumber) =>
|
|
{
|
|
GetRequiredRowValue(row, "name", rowNumber, "家庭成员", "姓名");
|
|
GetRequiredRowValue(row, "relationship", rowNumber, "家庭成员", "关系");
|
|
var ageText = GetRequiredRowValue(row, "age", rowNumber, "家庭成员", "年龄");
|
|
var phone = GetRequiredRowValue(row, "phone", rowNumber, "家庭成员", "联系电话");
|
|
|
|
var ageIsValid = int.TryParse(ageText, out var age) && age >= 0 && age <= 120;
|
|
if (!ageIsValid)
|
|
throw Oops.Bah($"第 {rowNumber} 条家庭成员的年龄应为 0 至 120 之间的整数");
|
|
|
|
var phoneIsValid = global::System.Text.RegularExpressions.Regex.IsMatch(
|
|
phone,
|
|
"^(1[3-9]\\d{9}|0\\d{2,3}-?\\d{7,8})$");
|
|
if (!phoneIsValid)
|
|
throw Oops.Bah($"第 {rowNumber} 条家庭成员的联系电话格式不正确");
|
|
});
|
|
}
|
|
|
|
private static void ValidateHistoryRows(
|
|
string json,
|
|
string fieldName,
|
|
Action<global::System.Text.Json.JsonElement, int> validateRow)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(json))
|
|
return;
|
|
|
|
try
|
|
{
|
|
using var document = global::System.Text.Json.JsonDocument.Parse(json);
|
|
if (document.RootElement.ValueKind != global::System.Text.Json.JsonValueKind.Array)
|
|
throw new global::System.Text.Json.JsonException();
|
|
|
|
var rowNumber = 0;
|
|
foreach (var row in document.RootElement.EnumerateArray())
|
|
{
|
|
rowNumber++;
|
|
if (row.ValueKind != global::System.Text.Json.JsonValueKind.Object)
|
|
throw Oops.Bah($"第 {rowNumber} 条{fieldName}格式不正确,请删除后重新添加");
|
|
|
|
validateRow(row, rowNumber);
|
|
}
|
|
}
|
|
catch (global::System.Text.Json.JsonException)
|
|
{
|
|
throw Oops.Bah($"{fieldName}格式不正确,请刷新页面后重新填写");
|
|
}
|
|
}
|
|
|
|
private static string GetRequiredRowValue(
|
|
global::System.Text.Json.JsonElement row,
|
|
string propertyName,
|
|
int rowNumber,
|
|
string fieldName,
|
|
string displayName)
|
|
{
|
|
if (!row.TryGetProperty(propertyName, out var property)
|
|
|| property.ValueKind is global::System.Text.Json.JsonValueKind.Null
|
|
or global::System.Text.Json.JsonValueKind.Undefined)
|
|
{
|
|
throw Oops.Bah($"第 {rowNumber} 条{fieldName}请填写{displayName}");
|
|
}
|
|
|
|
var value = property.ValueKind == global::System.Text.Json.JsonValueKind.String
|
|
? property.GetString()
|
|
: property.ToString();
|
|
if (string.IsNullOrWhiteSpace(value))
|
|
throw Oops.Bah($"第 {rowNumber} 条{fieldName}请填写{displayName}");
|
|
|
|
return value.Trim();
|
|
}
|
|
|
|
private static void ValidateJson(
|
|
string json,
|
|
global::System.Text.Json.JsonValueKind expectedKind,
|
|
string fieldName)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(json))
|
|
return;
|
|
|
|
try
|
|
{
|
|
using var document = global::System.Text.Json.JsonDocument.Parse(json);
|
|
if (document.RootElement.ValueKind != expectedKind)
|
|
throw new global::System.Text.Json.JsonException();
|
|
}
|
|
catch (global::System.Text.Json.JsonException)
|
|
{
|
|
throw Oops.Bah($"{fieldName}格式不正确,请刷新页面后重新填写");
|
|
}
|
|
}
|
|
}
|