This commit is contained in:
fuenmao
2026-08-25 20:37:17 +08:00
parent 38801533e9
commit be20a31332
22 changed files with 1090 additions and 336 deletions
@@ -11,9 +11,9 @@
namespace SimpleAdmin.Application; namespace SimpleAdmin.Application;
/// <summary> /// <summary>
/// 候选人在入职任务中提交的固定人员基础信息。 /// 候选人在入职任务中提交的《入职登记表》标准员工档案信息。
/// </summary> /// </summary>
[SugarTable("ob_task_employee_info", TableDescription = "候选人提交的固定基础信息")] [SugarTable("ob_task_employee_info", TableDescription = "候选人填写的入职登记表")]
[Tenant(SqlSugarConst.DB_DEFAULT)] [Tenant(SqlSugarConst.DB_DEFAULT)]
public class ObTaskEmployeeInfo : BaseEntity public class ObTaskEmployeeInfo : BaseEntity
{ {
@@ -23,45 +23,57 @@ public class ObTaskEmployeeInfo : BaseEntity
public long TaskId { get; set; } public long TaskId { get; set; }
/// <summary> /// <summary>
/// 员工编号。 /// 员工编号(选填/人事分配)。
/// </summary> /// </summary>
public string EmployeeNo { get; set; } public string EmployeeNo { get; set; }
/// <summary> // ==================== 1. 基本个人信息 ====================
/// 内部人员编号。
/// </summary>
public string InternalNo { get; set; }
/// <summary> /// <summary>
/// 员工姓名。 /// 员工姓名。
/// </summary> /// </summary>
public string EmployeeName { get; set; } public string EmployeeName { get; set; }
/// <summary>
/// 所属部门名称。
/// </summary>
public string DepartmentName { get; set; }
/// <summary> /// <summary>
/// 性别。 /// 性别。
/// </summary> /// </summary>
public string Gender { get; set; } public string Gender { get; set; }
/// <summary> /// <summary>
/// 手机号码。 /// 民族。
/// </summary> /// </summary>
public string Phone { get; set; } public string Nation { get; set; }
/// <summary> /// <summary>
/// 身份证号码。 /// 出生年月日。
/// </summary> /// </summary>
public string IdCardNumber { get; set; } public string Birthday { get; set; }
/// <summary> /// <summary>
/// 毕业院校。 /// 户籍所在地。
/// </summary> /// </summary>
public string College { get; set; } public string RegisteredResidence { get; set; }
/// <summary>
/// 身高 (cm)。
/// </summary>
public string Height { get; set; }
/// <summary>
/// 体重 (kg)。
/// </summary>
public string Weight { get; set; }
/// <summary>
/// 健康状况。
/// </summary>
public string HealthCondition { get; set; }
/// <summary>
/// 婚育情况。
/// </summary>
public string MaritalStatus { get; set; }
// ==================== 2. 学历与资质 ====================
/// <summary> /// <summary>
/// 最高学历。 /// 最高学历。
/// </summary> /// </summary>
@@ -73,82 +85,125 @@ public class ObTaskEmployeeInfo : BaseEntity
public string Major { get; set; } public string Major { get; set; }
/// <summary> /// <summary>
/// 出生日期。 /// 毕业院校。
/// </summary> /// </summary>
public DateTime? BirthDate { get; set; } public string GraduatedSchool { get; set; }
/// <summary> /// <summary>
/// 户籍所在地。 /// 职业资格。
/// </summary> /// </summary>
public string RegisteredResidence { get; set; } public string VocationalQualification { get; set; }
/// <summary> /// <summary>
/// 民族。 /// 资格等级。
/// </summary> /// </summary>
public string Nation { get; set; } public string QualificationLevel { get; set; }
/// <summary> /// <summary>
/// 现居住或联系地址。 /// 有何技术特长。
/// </summary> /// </summary>
public string ContactAddress { get; set; } public string TechnicalSpecialty { get; set; }
// ==================== 3. 联系方式与薪酬账户 ====================
/// <summary>
/// 联系电话/手机号。
/// </summary>
public string Phone { get; set; }
/// <summary> /// <summary>
/// 入职日期。 /// 电子邮箱。
/// </summary> /// </summary>
public DateTime? EntryDate { get; set; } public string Email { get; set; }
/// <summary> /// <summary>
/// 合同开始日期。 /// 身份证号码。
/// </summary> /// </summary>
public DateTime? ContractStartDate { get; set; } public string IdCardNumber { get; set; }
/// <summary> /// <summary>
/// 合同结束日期。 /// 暂住地址 / 现居住地址。
/// </summary> /// </summary>
public DateTime? ContractEndDate { get; set; } public string CurrentAddress { get; set; }
/// <summary> /// <summary>
/// 试用期开始日期。 /// 房东联系方式。
/// </summary> /// </summary>
public DateTime? ProbationStartDate { get; set; } public string LandlordContact { get; set; }
/// <summary>
/// 试用期结束日期。
/// </summary>
public DateTime? ProbationEndDate { get; set; }
/// <summary>
/// 转正日期。
/// </summary>
public DateTime? RegularizationDate { get; set; }
/// <summary>
/// 社保缴纳公司。
/// </summary>
public string SocialInsuranceCompany { get; set; }
/// <summary>
/// 合同性质。
/// </summary>
public string ContractNature { get; set; }
/// <summary> /// <summary>
/// 工资银行卡号。 /// 工资银行卡号。
/// </summary> /// </summary>
public string BankCardNumber { get; set; } public string BankCardNumber { get; set; }
/// <summary>
/// 银行卡开户名。
/// </summary>
public string BankAccountName { get; set; }
/// <summary> /// <summary>
/// 开户银行名称。 /// 开户银行名称。
/// </summary> /// </summary>
public string BankName { get; set; } public string BankName { get; set; }
/// <summary> /// <summary>
/// 业务备注。 /// 银行卡开户人姓名。
/// </summary> /// </summary>
public string Remark { get; set; } public string BankAccountName { get; set; }
// ==================== 4. 紧急联系人 ====================
/// <summary>
/// 紧急联系人姓名。
/// </summary>
public string EmergencyContact { get; set; }
/// <summary>
/// 紧急联系人电话。
/// </summary>
public string EmergencyPhone { get; set; }
/// <summary>
/// 紧急联系人联系地址。
/// </summary>
public string EmergencyAddress { get; set; }
// ==================== 5. 动态多条经历(JSON 存储) ====================
/// <summary>
/// 教育学习经历 JSON 列表: [ { timeRange, location, content, result } ]
/// </summary>
[SugarColumn(ColumnDataType = StaticConfig.CodeFirst_BigString, IsNullable = true)]
public string EducationHistoryJson { get; set; }
/// <summary>
/// 主要工作经历 JSON 列表: [ { timeRange, companyName, salary, position, leaveReason } ]
/// </summary>
[SugarColumn(ColumnDataType = StaticConfig.CodeFirst_BigString, IsNullable = true)]
public string WorkHistoryJson { get; set; }
/// <summary>
/// 家庭成员 JSON 列表: [ { name, relationship, age, company, phone } ]
/// </summary>
[SugarColumn(ColumnDataType = StaticConfig.CodeFirst_BigString, IsNullable = true)]
public string FamilyMembersJson { get; set; }
// ==================== 6. 入职情况与说明 ====================
/// <summary>
/// 入职部门。
/// </summary>
public string OnboardingDepartment { get; set; }
/// <summary>
/// 入职时间。
/// </summary>
public string OnboardingDate { get; set; }
/// <summary>
/// 入职岗位。
/// </summary>
public string OnboardingPosition { get; set; }
/// <summary>
/// 其他说明。
/// </summary>
public string OtherRemarks { get; set; }
/// <summary>
/// 员工补充自定义变量键值 JSON: { "VariableName": "Value" }
/// </summary>
[SugarColumn(ColumnDataType = StaticConfig.CodeFirst_BigString, IsNullable = true)]
public string CustomVariablesJson { get; set; }
} }
@@ -0,0 +1,55 @@
namespace SimpleAdmin.Application;
/// <summary>
/// 全局合同变量字典定义表。
/// 包含标准入职登记表变量、人事合同变量及企业自定义变量。
/// </summary>
[SugarTable("ob_variable_definition", TableDescription = "合同变量字典定义")]
[Tenant(SqlSugarConst.DB_DEFAULT)]
public class ObVariableDefinition : BaseEntity
{
/// <summary>
/// 变量全编码,如 employee.EmployeeName, hr.BaseSalary, company.CompanyName, system.TaskNo。
/// </summary>
public string VariableCode { get; set; }
/// <summary>
/// 变量中文显示名称,如“员工姓名”、“基本工资”。
/// </summary>
public string VariableName { get; set; }
/// <summary>
/// 变量作用域分类:employee(员工变量), hr(人事参数), company(公司变量), system(系统变量)。
/// </summary>
public string VariableCategory { get; set; }
/// <summary>
/// 数据类型:string, number, date, multiline。
/// </summary>
public string DataType { get; set; } = "string";
/// <summary>
/// 默认值或占位提示。
/// </summary>
public string DefaultValue { get; set; }
/// <summary>
/// 是否为入职登记表内置字段(内置字段不可删,且第1步自动收集)。
/// </summary>
public bool IsBuiltIn { get; set; } = false;
/// <summary>
/// 是否必填。
/// </summary>
public bool Required { get; set; } = false;
/// <summary>
/// 排序码。
/// </summary>
public int SortCode { get; set; } = 100;
/// <summary>
/// 变量说明/备注。
/// </summary>
public string Remark { get; set; }
}
@@ -44,14 +44,25 @@ public class OnboardingAuditService : IOnboardingAuditService
throw Oops.Bah("该身份证号或手机号已存在正式员工"); throw Oops.Bah("该身份证号或手机号已存在正式员工");
} }
var extraValues = await _db.Queryable<ObTaskFormValue>()
.Where(item => item.TaskId == task.Id)
.ToListAsync();
var employee = employeeInfo.Adapt<ObEmployee>(); var employee = employeeInfo.Adapt<ObEmployee>();
employee.Id = global::SimpleAdmin.Core.Utils.CommonUtils.GetSingleId(); employee.Id = global::SimpleAdmin.Core.Utils.CommonUtils.GetSingleId();
employee.CompanyId = task.CompanyId; employee.CompanyId = task.CompanyId;
employee.PositionId = task.PositionId; employee.PositionId = task.PositionId;
employee.ExtraInfoJson = global::System.Text.Json.JsonSerializer.Serialize(extraValues); employee.DepartmentName = employeeInfo.OnboardingDepartment;
employee.College = employeeInfo.GraduatedSchool;
employee.BirthDate = ParseDate(employeeInfo.Birthday);
employee.ContactAddress = employeeInfo.CurrentAddress;
employee.EntryDate = ParseDate(employeeInfo.OnboardingDate);
employee.Remark = employeeInfo.OtherRemarks;
employee.ExtraInfoJson = global::System.Text.Json.JsonSerializer.Serialize(employeeInfo);
employee.Status = CommonStatusConst.ENABLE;
employee.CreateTime = DateTime.Now;
employee.CreateUserId = UserManager.UserId;
employee.CreateUser = UserManager.Name;
employee.UpdateTime = null;
employee.UpdateUserId = null;
employee.UpdateUser = null;
employee.IsDelete = false;
// 员工档案、任务状态和审核记录必须同时成功,避免出现“已通过但没有员工档案”的中间状态。 // 员工档案、任务状态和审核记录必须同时成功,避免出现“已通过但没有员工档案”的中间状态。
await _db.Ado.BeginTranAsync(); await _db.Ado.BeginTranAsync();
@@ -124,6 +135,16 @@ public class OnboardingAuditService : IOnboardingAuditService
return task; return task;
} }
/// <summary>
/// 将登记表中的日期文本转换为正式员工档案日期。
/// </summary>
private static DateTime? ParseDate(string value)
{
return DateTime.TryParse(value, out var result)
? result.Date
: null;
}
private Task WriteAudit(ObOnboardingTask task, string action, string opinion) private Task WriteAudit(ObOnboardingTask task, string action, string opinion)
{ {
return _db.Insertable(new ObTaskAudit return _db.Insertable(new ObTaskAudit
@@ -26,12 +26,8 @@ public class OnboardingContractSnapshotService : IOnboardingContractSnapshotServ
?? throw Oops.Bah("入职公司不存在"); ?? throw Oops.Bah("入职公司不存在");
employeeInfo ??= await _db.Queryable<ObTaskEmployeeInfo>() employeeInfo ??= await _db.Queryable<ObTaskEmployeeInfo>()
.FirstAsync(item => item.TaskId == taskId); .FirstAsync(item => item.TaskId == taskId);
var dynamicValues = await _db.Queryable<ObTaskFormValue>() // 员工补充合同变量统一从 CustomVariablesJson 读取,不再访问已移除的旧动态字段表。
.Where(item => item.TaskId == taskId) var dynamicValueMap = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
.ToListAsync();
var dynamicValueMap = dynamicValues
.GroupBy(item => item.FieldCode, StringComparer.OrdinalIgnoreCase)
.ToDictionary(group => group.Key, group => group.Last().FieldValue, StringComparer.OrdinalIgnoreCase);
var contracts = await _db.Queryable<ObTaskContract>() var contracts = await _db.Queryable<ObTaskContract>()
.Where(item => item.TaskId == taskId) .Where(item => item.TaskId == taskId)
.ToListAsync(); .ToListAsync();
@@ -110,6 +106,23 @@ public class OnboardingContractSnapshotService : IOnboardingContractSnapshotServ
var fixedValue = GetPropertyValue(employeeInfo, ResolveEmployeeField(sourceField), definition.FormatRule); var fixedValue = GetPropertyValue(employeeInfo, ResolveEmployeeField(sourceField), definition.FormatRule);
if (!string.IsNullOrWhiteSpace(fixedValue)) if (!string.IsNullOrWhiteSpace(fixedValue))
return fixedValue; return fixedValue;
// 检查员工自定义补充变量 JSON
if (!string.IsNullOrWhiteSpace(employeeInfo?.CustomVariablesJson))
{
try
{
using var doc = global::System.Text.Json.JsonDocument.Parse(employeeInfo.CustomVariablesJson);
if (doc.RootElement.TryGetProperty(sourceField, out var prop)
|| doc.RootElement.TryGetProperty(definition.VariableCode, out prop))
{
var str = prop.GetString();
if (!string.IsNullOrWhiteSpace(str)) return str;
}
}
catch { }
}
return dynamicValues.TryGetValue(sourceField, out var dynamicValue) return dynamicValues.TryGetValue(sourceField, out var dynamicValue)
? dynamicValue ? dynamicValue
: definition.DefaultValue; : definition.DefaultValue;
@@ -184,8 +197,10 @@ public class OnboardingContractSnapshotService : IOnboardingContractSnapshotServ
"LEGALNAME" or "LEGAL_NAME" or "COMPANYLEGALNAME" => nameof(ObCompany.LegalName), "LEGALNAME" or "LEGAL_NAME" or "COMPANYLEGALNAME" => nameof(ObCompany.LegalName),
"UNIFIEDSOCIALCREDITCODE" or "UNIFIED_SOCIAL_CREDIT_CODE" => nameof(ObCompany.UnifiedSocialCreditCode), "UNIFIEDSOCIALCREDITCODE" or "UNIFIED_SOCIAL_CREDIT_CODE" => nameof(ObCompany.UnifiedSocialCreditCode),
"LEGALREPRESENTATIVE" or "LEGAL_REPRESENTATIVE" => nameof(ObCompany.LegalRepresentative), "LEGALREPRESENTATIVE" or "LEGAL_REPRESENTATIVE" => nameof(ObCompany.LegalRepresentative),
"REGISTEREDADDRESS" or "REGISTERED_ADDRESS" => nameof(ObCompany.RegisteredAddress), "ADDRESS" or "COMPANYADDRESS" or "COMPANY_ADDRESS"
"CONTACTPHONE" or "CONTACT_PHONE" => nameof(ObCompany.ContactPhone), or "REGISTEREDADDRESS" or "REGISTERED_ADDRESS" => nameof(ObCompany.RegisteredAddress),
"PHONE" or "COMPANYPHONE" or "COMPANY_PHONE"
or "CONTACTPHONE" or "CONTACT_PHONE" => nameof(ObCompany.ContactPhone),
_ => sourceField _ => sourceField
}; };
} }
@@ -179,6 +179,16 @@ public class OnboardingContractTemplateService : DbRepository<ObContractTemplate
await sourceStream.CopyToAsync(sourceBytes); await sourceStream.CopyToAsync(sourceBytes);
var bytes = sourceBytes.ToArray(); var bytes = sourceBytes.ToArray();
var variableCodes = DocxTemplateProcessor.ExtractVariableCodes(bytes); var variableCodes = DocxTemplateProcessor.ExtractVariableCodes(bytes);
// 严格比对变量字典库,防止手误和非法占位符
var dictList = await Context.Queryable<ObVariableDefinition>().ToListAsync();
var dictMap = dictList.ToDictionary(it => it.VariableCode, it => it, StringComparer.OrdinalIgnoreCase);
var undefinedCodes = variableCodes.Where(code => !dictMap.ContainsKey(code)).ToList();
if (undefinedCodes.Count > 0)
{
throw Oops.Bah($"DOCX 模板包含未在字典库中注册的占位符:{string.Join(",", undefinedCodes)}。请检查拼写或先在【变量字典】中添加该变量。");
}
var versionId = global::SimpleAdmin.Core.Utils.CommonUtils.GetSingleId(); var versionId = global::SimpleAdmin.Core.Utils.CommonUtils.GetSingleId();
var version = input.Adapt<ObContractTemplateVersion>(); var version = input.Adapt<ObContractTemplateVersion>();
version.Id = versionId; version.Id = versionId;
@@ -186,7 +196,12 @@ public class OnboardingContractTemplateService : DbRepository<ObContractTemplate
version.FileHash = string.IsNullOrWhiteSpace(input.FileHash) version.FileHash = string.IsNullOrWhiteSpace(input.FileHash)
? Convert.ToHexString(global::System.Security.Cryptography.SHA256.HashData(bytes)) ? Convert.ToHexString(global::System.Security.Cryptography.SHA256.HashData(bytes))
: input.FileHash; : input.FileHash;
var variables = variableCodes.Select((code, index) => BuildDetectedVariable(versionId, code, index)).ToList();
var variables = variableCodes.Select((code, index) =>
{
var def = dictMap.TryGetValue(code, out var d) ? d : null;
return BuildDetectedVariable(versionId, code, def, index);
}).ToList();
await Context.Ado.BeginTranAsync(); await Context.Ado.BeginTranAsync();
try try
@@ -263,7 +278,7 @@ public class OnboardingContractTemplateService : DbRepository<ObContractTemplate
await Context.Updateable(entity).ExecuteCommandAsync(); await Context.Updateable(entity).ExecuteCommandAsync();
} }
private static ObTemplateVariable BuildDetectedVariable(long versionId, string code, int index) private static ObTemplateVariable BuildDetectedVariable(long versionId, string code, ObVariableDefinition def, int index)
{ {
var separatorIndex = code.IndexOf('.'); var separatorIndex = code.IndexOf('.');
var prefix = separatorIndex > 0 ? code[..separatorIndex].ToLowerInvariant() : string.Empty; var prefix = separatorIndex > 0 ? code[..separatorIndex].ToLowerInvariant() : string.Empty;
@@ -277,21 +292,24 @@ public class OnboardingContractTemplateService : DbRepository<ObContractTemplate
_ => ContractVariableType.Unresolved _ => ContractVariableType.Unresolved
}; };
var varName = def != null && !string.IsNullOrWhiteSpace(def.VariableName) ? def.VariableName : sourceField;
var dataType = def != null && !string.IsNullOrWhiteSpace(def.DataType) ? def.DataType : InferDataType(sourceField);
return new ObTemplateVariable return new ObTemplateVariable
{ {
Id = global::SimpleAdmin.Core.Utils.CommonUtils.GetSingleId(), Id = global::SimpleAdmin.Core.Utils.CommonUtils.GetSingleId(),
TemplateVersionId = versionId, TemplateVersionId = versionId,
VariableCode = code, VariableCode = code,
VariableName = sourceField, VariableName = varName,
VariableType = variableType, VariableType = variableType,
DataType = InferDataType(sourceField), DataType = dataType,
EmployeeField = variableType is ContractVariableType.Employee or ContractVariableType.Company EmployeeField = variableType is ContractVariableType.Employee or ContractVariableType.Company
? sourceField ? sourceField
: null, : null,
Required = true, Required = def?.Required ?? true,
HrEditable = variableType == ContractVariableType.Contract, HrEditable = variableType == ContractVariableType.Contract,
SortCode = (index + 1) * 10, SortCode = (index + 1) * 10,
Remark = variableType == ContractVariableType.Unresolved ? "导入时未识别变量来源,请管理员确认" : null Remark = def?.Remark
}; };
} }
@@ -83,10 +83,8 @@ public class OnboardingTaskLifecycleService : IOnboardingTaskLifecycleService
await _db.Deleteable<ObTaskContract>().Where(x => x.TaskId == task.Id).ExecuteCommandAsync(); await _db.Deleteable<ObTaskContract>().Where(x => x.TaskId == task.Id).ExecuteCommandAsync();
await _db.Deleteable<ObTaskLink>().Where(x => x.TaskId == task.Id).ExecuteCommandAsync(); await _db.Deleteable<ObTaskLink>().Where(x => x.TaskId == task.Id).ExecuteCommandAsync();
await _db.Deleteable<ObTaskFormRequirement>().Where(x => x.TaskId == task.Id).ExecuteCommandAsync();
await _db.Deleteable<ObTaskMaterialRequirement>().Where(x => x.TaskId == task.Id).ExecuteCommandAsync(); await _db.Deleteable<ObTaskMaterialRequirement>().Where(x => x.TaskId == task.Id).ExecuteCommandAsync();
await _db.Deleteable<ObTaskMaterial>().Where(x => x.TaskId == task.Id).ExecuteCommandAsync(); await _db.Deleteable<ObTaskMaterial>().Where(x => x.TaskId == task.Id).ExecuteCommandAsync();
await _db.Deleteable<ObTaskFormValue>().Where(x => x.TaskId == task.Id).ExecuteCommandAsync();
await _db.Deleteable<ObTaskEmployeeInfo>().Where(x => x.TaskId == task.Id).ExecuteCommandAsync(); await _db.Deleteable<ObTaskEmployeeInfo>().Where(x => x.TaskId == task.Id).ExecuteCommandAsync();
await _db.Deleteable<ObTaskAudit>().Where(x => x.TaskId == task.Id).ExecuteCommandAsync(); await _db.Deleteable<ObTaskAudit>().Where(x => x.TaskId == task.Id).ExecuteCommandAsync();
await _db.Deleteable<ObOnboardingTask>().In(task.Id).ExecuteCommandAsync(); await _db.Deleteable<ObOnboardingTask>().In(task.Id).ExecuteCommandAsync();
@@ -75,10 +75,7 @@ public class OnboardingTemplateService : IOnboardingTemplateService
.Where(item => item.OnboardingTemplateId == template.Id) .Where(item => item.OnboardingTemplateId == template.Id)
.OrderBy(item => item.SortCode) .OrderBy(item => item.SortCode)
.ToListAsync(), .ToListAsync(),
FormFields = await _db.Queryable<ObOnboardingTemplateFormField>() FormFields = new List<ObOnboardingTemplateFormField>(),
.Where(item => item.OnboardingTemplateId == template.Id)
.OrderBy(item => item.SortCode)
.ToListAsync(),
Materials = await _db.Queryable<ObOnboardingTemplateMaterial>() Materials = await _db.Queryable<ObOnboardingTemplateMaterial>()
.Where(item => item.OnboardingTemplateId == template.Id) .Where(item => item.OnboardingTemplateId == template.Id)
.OrderBy(item => item.SortCode) .OrderBy(item => item.SortCode)
@@ -246,49 +243,6 @@ public class OnboardingTemplateService : IOnboardingTemplateService
{ {
if (versions.Any(item => item.VersionStatus != ContractTemplateVersionStatus.Enabled)) if (versions.Any(item => item.VersionStatus != ContractTemplateVersionStatus.Enabled))
throw Oops.Bah("启用入职模板前,请先启用全部合同版本"); throw Oops.Bah("启用入职模板前,请先启用全部合同版本");
var variables = await _db.Queryable<ObTemplateVariable>()
.Where(item => versionIds.Contains(item.TemplateVersionId))
.ToListAsync();
var validTypes = new[]
{
ContractVariableType.Employee,
ContractVariableType.Company,
ContractVariableType.Contract,
ContractVariableType.System
};
if (variables.Any(item => !validTypes.Contains(item.VariableType)))
throw Oops.Bah("合同中仍有未确认来源的变量,请先完成变量映射");
}
var fieldIds = input.FormFields.Select(item => item.FieldId).ToList();
var enabledFields = fieldIds.Count == 0
? new List<ObFormField>()
: await _db.Queryable<ObFormField>()
.Where(item => fieldIds.Contains(item.Id) && item.Status == "enable")
.ToListAsync();
if (enabledFields.Count != fieldIds.Count)
throw Oops.Bah("存在未启用或不存在的动态补充字段");
if (input.Status == "enable")
{
var variables = await _db.Queryable<ObTemplateVariable>()
.Where(item => versionIds.Contains(item.TemplateVersionId)
&& item.VariableType == ContractVariableType.Employee)
.ToListAsync();
var availableFields = new HashSet<string>(
typeof(ObTaskEmployeeInfo).GetProperties().Select(property => property.Name),
StringComparer.OrdinalIgnoreCase);
foreach (var fieldCode in enabledFields.Select(item => item.FieldCode))
availableFields.Add(fieldCode);
var missingMapping = variables.FirstOrDefault(variable =>
!availableFields.Contains(ResolveEmployeeVariableField(variable)));
if (missingMapping != null)
{
throw Oops.Bah(
$"合同员工变量“{missingMapping.VariableCode}”没有对应的固定字段或模板补充字段,请先完成字段映射");
}
} }
var materialIds = input.Materials.Select(item => item.MaterialTypeId).ToList(); var materialIds = input.Materials.Select(item => item.MaterialTypeId).ToList();
@@ -301,25 +255,6 @@ public class OnboardingTemplateService : IOnboardingTemplateService
} }
} }
private static string ResolveEmployeeVariableField(ObTemplateVariable variable)
{
var sourceField = variable.EmployeeField;
if (string.IsNullOrWhiteSpace(sourceField))
{
var separatorIndex = variable.VariableCode?.IndexOf('.') ?? -1;
sourceField = separatorIndex >= 0
? variable.VariableCode[(separatorIndex + 1)..]
: variable.VariableCode;
}
return sourceField?.ToUpperInvariant() switch
{
"FULLNAME" or "FULL_NAME" or "NAME" => nameof(ObTaskEmployeeInfo.EmployeeName),
"MOBILE" or "MOBILEPHONE" or "MOBILE_PHONE" => nameof(ObTaskEmployeeInfo.Phone),
"IDNUMBER" or "ID_NUMBER" or "IDENTITYNUMBER" => nameof(ObTaskEmployeeInfo.IdCardNumber),
_ => sourceField
};
}
private async Task InsertRelations(long templateId, OnboardingTemplateSaveInput input) private async Task InsertRelations(long templateId, OnboardingTemplateSaveInput input)
{ {
var positions = input.PositionIds.Select(positionId => new ObOnboardingTemplatePosition var positions = input.PositionIds.Select(positionId => new ObOnboardingTemplatePosition
@@ -342,20 +277,7 @@ public class OnboardingTemplateService : IOnboardingTemplateService
}).ToList(); }).ToList();
await _db.Insertable(contracts).ExecuteCommandAsync(); await _db.Insertable(contracts).ExecuteCommandAsync();
if (input.FormFields.Count > 0) if (input.Materials != null && input.Materials.Count > 0)
{
var fields = input.FormFields.Select(item => new ObOnboardingTemplateFormField
{
Id = global::SimpleAdmin.Core.Utils.CommonUtils.GetSingleId(),
OnboardingTemplateId = templateId,
FieldId = item.FieldId,
Required = item.Required,
SortCode = item.SortCode
}).ToList();
await _db.Insertable(fields).ExecuteCommandAsync();
}
if (input.Materials.Count > 0)
{ {
var materials = input.Materials.Select(item => new ObOnboardingTemplateMaterial var materials = input.Materials.Select(item => new ObOnboardingTemplateMaterial
{ {
@@ -377,9 +299,6 @@ public class OnboardingTemplateService : IOnboardingTemplateService
await _db.Deleteable<ObOnboardingTemplateContract>() await _db.Deleteable<ObOnboardingTemplateContract>()
.Where(item => item.OnboardingTemplateId == templateId) .Where(item => item.OnboardingTemplateId == templateId)
.ExecuteCommandAsync(); .ExecuteCommandAsync();
await _db.Deleteable<ObOnboardingTemplateFormField>()
.Where(item => item.OnboardingTemplateId == templateId)
.ExecuteCommandAsync();
await _db.Deleteable<ObOnboardingTemplateMaterial>() await _db.Deleteable<ObOnboardingTemplateMaterial>()
.Where(item => item.OnboardingTemplateId == templateId) .Where(item => item.OnboardingTemplateId == templateId)
.ExecuteCommandAsync(); .ExecuteCommandAsync();
@@ -405,9 +324,6 @@ public class OnboardingTemplateService : IOnboardingTemplateService
var contracts = await _db.Queryable<ObOnboardingTemplateContract>() var contracts = await _db.Queryable<ObOnboardingTemplateContract>()
.Where(item => templateIds.Contains(item.OnboardingTemplateId)) .Where(item => templateIds.Contains(item.OnboardingTemplateId))
.ToListAsync(); .ToListAsync();
var fields = await _db.Queryable<ObOnboardingTemplateFormField>()
.Where(item => templateIds.Contains(item.OnboardingTemplateId))
.ToListAsync();
var materials = await _db.Queryable<ObOnboardingTemplateMaterial>() var materials = await _db.Queryable<ObOnboardingTemplateMaterial>()
.Where(item => templateIds.Contains(item.OnboardingTemplateId)) .Where(item => templateIds.Contains(item.OnboardingTemplateId))
.ToListAsync(); .ToListAsync();
@@ -432,7 +348,7 @@ public class OnboardingTemplateService : IOnboardingTemplateService
.Where(name => !string.IsNullOrWhiteSpace(name)) .Where(name => !string.IsNullOrWhiteSpace(name))
.ToList(), .ToList(),
ContractCount = contracts.Count(item => item.OnboardingTemplateId == template.Id), ContractCount = contracts.Count(item => item.OnboardingTemplateId == template.Id),
FormFieldCount = fields.Count(item => item.OnboardingTemplateId == template.Id), FormFieldCount = 0,
MaterialCount = materials.Count(item => item.OnboardingTemplateId == template.Id) MaterialCount = materials.Count(item => item.OnboardingTemplateId == template.Id)
}; };
}).ToList(); }).ToList();
@@ -55,6 +55,23 @@ public class OnboardingSaveMaterialInput
public long FileId { get; set; } public long FileId { get; set; }
} }
/// <summary>
/// 删除员工在当前可编辑任务中上传的一条材料。
/// </summary>
public class OnboardingDeleteMaterialInput
{
/// <summary>
/// 员工填写链接中的明文 Token。
/// </summary>
[global::System.ComponentModel.DataAnnotations.Required]
public string Token { get; set; }
/// <summary>
/// 任务材料记录 Id。
/// </summary>
public long TaskMaterialId { get; set; }
}
/// <summary> /// <summary>
/// 员工确认阅读合同参数。 /// 员工确认阅读合同参数。
/// </summary> /// </summary>
@@ -9,18 +9,27 @@ public interface IOnboardingPublicSubmissionService : ITransient
/// 保存候选人填写的动态字段值。 /// 保存候选人填写的动态字段值。
/// </summary> /// </summary>
Task SaveDynamicValues(OnboardingSaveDynamicValuesInput input); Task SaveDynamicValues(OnboardingSaveDynamicValuesInput input);
/// <summary> /// <summary>
/// 关联候选人已经上传的系统文件与材料类型。 /// 关联候选人已经上传的系统文件与材料类型。
/// </summary> /// </summary>
Task SaveMaterial(OnboardingSaveMaterialInput input); Task SaveMaterial(OnboardingSaveMaterialInput input);
/// <summary>
/// 删除可编辑任务中的一条已上传材料关联。
/// </summary>
Task DeleteMaterial(OnboardingDeleteMaterialInput input);
/// <summary> /// <summary>
/// 记录候选人已阅读并确认指定任务合同。 /// 记录候选人已阅读并确认指定任务合同。
/// </summary> /// </summary>
Task ConfirmContract(OnboardingConfirmContractInput input); Task ConfirmContract(OnboardingConfirmContractInput input);
/// <summary> /// <summary>
/// 校验全部必填资料后提交入职任务进入审核。 /// 校验全部必填资料后提交入职任务进入审核。
/// </summary> /// </summary>
Task Submit(OnboardingSubmitInput input); Task Submit(OnboardingSubmitInput input);
/// <summary> /// <summary>
/// 在保存文件前校验材料适用范围、扩展名、大小和数量。 /// 在保存文件前校验材料适用范围、扩展名、大小和数量。
/// </summary> /// </summary>
@@ -19,37 +19,8 @@ public class OnboardingPublicSubmissionService : IOnboardingPublicSubmissionServ
/// </summary> /// </summary>
public async Task SaveDynamicValues(OnboardingSaveDynamicValuesInput input) public async Task SaveDynamicValues(OnboardingSaveDynamicValuesInput input)
{ {
var task = await GetEditableTask(input.Token); await GetEditableTask(input.Token);
var requirements = await GetFormRequirements(task.Id); throw Oops.Bah("新版入职登记表不再使用旧动态字段接口,请通过员工基础信息接口保存补充变量");
var fieldIds = input.Values.Select(item => item.FieldId).Distinct().ToList();
var hasInapplicableField = fieldIds.Any(id => requirements.All(item => item.FieldId != id));
if (hasInapplicableField)
throw Oops.Bah("填写字段不属于当前入职模板");
foreach (var value in input.Values)
{
var requirement = requirements.FirstOrDefault(item => item.FieldId == value.FieldId)
?? throw Oops.Bah("动态补充字段不存在");
var old = await _db.Queryable<ObTaskFormValue>()
.FirstAsync(item => item.TaskId == task.Id && item.FieldId == requirement.FieldId);
var row = new ObTaskFormValue
{
Id = old?.Id ?? global::SimpleAdmin.Core.Utils.CommonUtils.GetSingleId(),
TaskId = task.Id,
FieldId = requirement.FieldId,
FieldCode = requirement.FieldCode,
FieldName = requirement.FieldName,
FieldValue = value.FieldValue,
SubmitRound = task.SubmitCount + 1
};
if (old == null)
await _db.Insertable(row).ExecuteCommandAsync();
else
await _db.Updateable(row).ExecuteCommandAsync();
}
await _snapshotService.Build(task.Id, invalidateGeneratedPreview: true);
} }
/// <summary> /// <summary>
@@ -96,6 +67,25 @@ public class OnboardingPublicSubmissionService : IOnboardingPublicSubmissionServ
}).ExecuteCommandAsync(); }).ExecuteCommandAsync();
} }
/// <summary>
/// 任务仍可编辑时删除一条已上传材料。
/// </summary>
public async Task DeleteMaterial(
OnboardingDeleteMaterialInput input)
{
var task = await GetEditableTask(input.Token);
var material = await _db.Queryable<ObTaskMaterial>()
.FirstAsync(item =>
item.Id == input.TaskMaterialId
&& item.TaskId == task.Id);
if (material == null)
throw Oops.Bah("上传材料不存在");
await _db.Deleteable<ObTaskMaterial>()
.In(material.Id)
.ExecuteCommandAsync();
}
/// <summary> /// <summary>
/// 记录候选人已经查看并确认动态生成的合同。 /// 记录候选人已经查看并确认动态生成的合同。
/// </summary> /// </summary>
@@ -124,24 +114,14 @@ public class OnboardingPublicSubmissionService : IOnboardingPublicSubmissionServ
var basicInfoIncomplete = info == null var basicInfoIncomplete = info == null
|| string.IsNullOrWhiteSpace(info.EmployeeName) || string.IsNullOrWhiteSpace(info.EmployeeName)
|| string.IsNullOrWhiteSpace(info.Phone) || string.IsNullOrWhiteSpace(info.Phone)
|| string.IsNullOrWhiteSpace(info.IdCardNumber); || string.IsNullOrWhiteSpace(info.IdCardNumber)
|| string.IsNullOrWhiteSpace(info.BankCardNumber)
|| string.IsNullOrWhiteSpace(info.BankName);
if (basicInfoIncomplete) if (basicInfoIncomplete)
throw Oops.Bah("请先完整填写基本信息"); throw Oops.Bah("请先完整填写基本信息");
await _snapshotService.Build(task.Id, info); await _snapshotService.Build(task.Id, info);
var requiredFields = (await GetFormRequirements(task.Id))
.Where(item => item.Required)
.Select(item => item.FieldId)
.ToList();
var values = await _db.Queryable<ObTaskFormValue>()
.Where(item => item.TaskId == task.Id)
.ToListAsync();
var hasMissingRequiredField = requiredFields.Any(
id => values.All(item => item.FieldId != id || string.IsNullOrWhiteSpace(item.FieldValue)));
if (hasMissingRequiredField)
throw Oops.Bah("仍有必填补充信息未填写");
var requiredMaterials = (await GetMaterialRequirements(task.Id)) var requiredMaterials = (await GetMaterialRequirements(task.Id))
.Where(item => item.Required) .Where(item => item.Required)
.Select(item => item.MaterialTypeId) .Select(item => item.MaterialTypeId)
@@ -174,7 +154,7 @@ public class OnboardingPublicSubmissionService : IOnboardingPublicSubmissionServ
AuditRound = task.SubmitCount, AuditRound = task.SubmitCount,
AuditAction = OnboardingAuditAction.Submit, AuditAction = OnboardingAuditAction.Submit,
AuditOpinion = "员工提交入职资料", AuditOpinion = "员工提交入职资料",
SnapshotJson = global::System.Text.Json.JsonSerializer.Serialize(new { info, values, materials }), SnapshotJson = global::System.Text.Json.JsonSerializer.Serialize(new { info, materials }),
AuditedAt = DateTime.Now AuditedAt = DateTime.Now
}).ExecuteCommandAsync(); }).ExecuteCommandAsync();
} }
@@ -235,14 +215,6 @@ public class OnboardingPublicSubmissionService : IOnboardingPublicSubmissionServ
throw Oops.Bah($"合同“{missing.contract.ContractName}”的必填变量“{missing.definition.VariableName}”尚未填写"); throw Oops.Bah($"合同“{missing.contract.ContractName}”的必填变量“{missing.definition.VariableName}”尚未填写");
} }
private Task<List<ObTaskFormRequirement>> GetFormRequirements(long taskId)
{
return _db.Queryable<ObTaskFormRequirement>()
.Where(item => item.TaskId == taskId)
.OrderBy(item => item.SortCode)
.ToListAsync();
}
private Task<List<ObTaskMaterialRequirement>> GetMaterialRequirements(long taskId) private Task<List<ObTaskMaterialRequirement>> GetMaterialRequirements(long taskId)
{ {
return _db.Queryable<ObTaskMaterialRequirement>() return _db.Queryable<ObTaskMaterialRequirement>()
@@ -19,7 +19,7 @@ public class OnboardingPublicTaskService : IOnboardingPublicTaskService
} }
/// <summary> /// <summary>
/// 通过有效 Token 获取任务创建时冻结的字段、材料和合同信息。 /// 通过有效 Token 获取任务创建时冻结的材料、合同和新版入职登记表信息。
/// </summary> /// </summary>
public async Task<OnboardingTaskPublicDetailOutput> Detail(OnboardingTaskPublicAccessInput input) public async Task<OnboardingTaskPublicDetailOutput> Detail(OnboardingTaskPublicAccessInput input)
{ {
@@ -30,39 +30,49 @@ public class OnboardingPublicTaskService : IOnboardingPublicTaskService
link.AccessCount++; link.AccessCount++;
link.LastAccessedAt = DateTime.Now; link.LastAccessedAt = DateTime.Now;
await _db.Updateable(link).ExecuteCommandAsync(); await _db.Updateable(link).ExecuteCommandAsync();
var formRequirements = await _db.Queryable<ObTaskFormRequirement>()
.Where(item => item.TaskId == task.Id)
.OrderBy(item => item.SortCode)
.ToListAsync();
var materialRequirements = await _db.Queryable<ObTaskMaterialRequirement>() var materialRequirements = await _db.Queryable<ObTaskMaterialRequirement>()
.Where(item => item.TaskId == task.Id) .Where(item => item.TaskId == task.Id)
.OrderBy(item => item.SortCode) .OrderBy(item => item.SortCode)
.ToListAsync(); .ToListAsync();
var contracts = await _db.Queryable<ObTaskContract>()
.Where(item => item.TaskId == task.Id)
.ToListAsync();
var templateVersionIds = contracts
.Select(item => item.TemplateVersionId)
.Distinct()
.ToList();
var contractVariables = new List<OnboardingPublicContractVariableOutput>();
if (templateVersionIds.Count > 0)
{
var employeeVariables = await _db.Queryable<ObTemplateVariable>()
.Where(item => templateVersionIds.Contains(item.TemplateVersionId)
&& item.VariableType == ContractVariableType.Employee)
.OrderBy(item => item.SortCode)
.ToListAsync();
contractVariables = employeeVariables.Select(item => new OnboardingPublicContractVariableOutput
{
VariableCode = item.VariableCode,
VariableName = item.VariableName,
VariableType = item.VariableType,
DataType = item.DataType,
DefaultValue = item.DefaultValue,
OptionsJson = item.OptionsJson,
Required = item.Required
}).ToList();
}
return new OnboardingTaskPublicDetailOutput return new OnboardingTaskPublicDetailOutput
{ {
Task = task, Task = task,
EmployeeInfo = await _db.Queryable<ObTaskEmployeeInfo>() EmployeeInfo = await _db.Queryable<ObTaskEmployeeInfo>()
.FirstAsync(item => item.TaskId == task.Id), .FirstAsync(item => item.TaskId == task.Id),
Contracts = await _db.Queryable<ObTaskContract>() Contracts = contracts,
.Where(item => item.TaskId == task.Id) DynamicValues = new List<ObTaskFormValue>(),
.ToListAsync(),
DynamicValues = await _db.Queryable<ObTaskFormValue>()
.Where(item => item.TaskId == task.Id)
.ToListAsync(),
Materials = await _db.Queryable<ObTaskMaterial>() Materials = await _db.Queryable<ObTaskMaterial>()
.Where(item => item.TaskId == task.Id) .Where(item => item.TaskId == task.Id)
.ToListAsync(), .ToListAsync(),
FormFields = formRequirements.Select(requirement => new OnboardingPublicFormFieldOutput FormFields = new List<OnboardingPublicFormFieldOutput>(),
{
FieldId = requirement.FieldId,
FieldCode = requirement.FieldCode,
FieldName = requirement.FieldName,
DataType = requirement.DataType,
OptionsJson = requirement.OptionsJson,
Required = requirement.Required
}).ToList(),
MaterialRequirements = materialRequirements.Select(requirement => new OnboardingPublicMaterialOutput MaterialRequirements = materialRequirements.Select(requirement => new OnboardingPublicMaterialOutput
{ {
MaterialTypeId = requirement.MaterialTypeId, MaterialTypeId = requirement.MaterialTypeId,
@@ -70,7 +80,8 @@ public class OnboardingPublicTaskService : IOnboardingPublicTaskService
AllowedExtensions = requirement.AllowedExtensions, AllowedExtensions = requirement.AllowedExtensions,
MaxFileCount = requirement.MaxFileCount, MaxFileCount = requirement.MaxFileCount,
Required = requirement.Required Required = requirement.Required
}).ToList() }).ToList(),
ContractVariables = contractVariables
}; };
} }
@@ -92,15 +103,29 @@ public class OnboardingPublicTaskService : IOnboardingPublicTaskService
.FirstAsync(item => item.TaskId == task.Id); .FirstAsync(item => item.TaskId == task.Id);
var entity = input.Adapt<ObTaskEmployeeInfo>(); var entity = input.Adapt<ObTaskEmployeeInfo>();
entity.TaskId = task.Id; entity.TaskId = task.Id;
entity.IsDelete = false;
if (old == null) if (old == null)
{ {
entity.Id = global::SimpleAdmin.Core.Utils.CommonUtils.GetSingleId(); entity.Id = global::SimpleAdmin.Core.Utils.CommonUtils.GetSingleId();
entity.Status = CommonStatusConst.ENABLE;
entity.CreateTime = DateTime.Now;
entity.CreateUserId = null;
entity.CreateUser = null;
await _db.Insertable(entity).ExecuteCommandAsync(); await _db.Insertable(entity).ExecuteCommandAsync();
} }
else else
{ {
entity.Id = old.Id; entity.Id = old.Id;
entity.Status = old.Status;
entity.CreateTime = old.CreateTime;
entity.CreateUserId = old.CreateUserId;
entity.CreateUser = old.CreateUser;
entity.ExtJson = old.ExtJson;
entity.IsDelete = old.IsDelete;
entity.UpdateTime = DateTime.Now;
entity.UpdateUserId = null;
entity.UpdateUser = null;
await _db.Updateable(entity).ExecuteCommandAsync(); await _db.Updateable(entity).ExecuteCommandAsync();
} }
@@ -96,6 +96,47 @@ public class OnboardingPublicFormFieldOutput
public bool Required { get; set; } public bool Required { get; set; }
} }
/// <summary>
/// 员工端需要填写的合同员工变量。
/// </summary>
public class OnboardingPublicContractVariableOutput
{
/// <summary>
/// 合同变量编码。
/// </summary>
public string VariableCode { get; set; }
/// <summary>
/// 合同变量显示名称。
/// </summary>
public string VariableName { get; set; }
/// <summary>
/// 变量来源类型。
/// </summary>
public string VariableType { get; set; }
/// <summary>
/// 变量数据类型。
/// </summary>
public string DataType { get; set; }
/// <summary>
/// 变量默认值。
/// </summary>
public string DefaultValue { get; set; }
/// <summary>
/// 选择型变量的可选项 JSON。
/// </summary>
public string OptionsJson { get; set; }
/// <summary>
/// 是否必填。
/// </summary>
public bool Required { get; set; }
}
/// <summary> /// <summary>
/// 员工端材料上传要求。 /// 员工端材料上传要求。
/// </summary> /// </summary>
@@ -166,4 +207,9 @@ public class OnboardingTaskPublicDetailOutput
/// 候选人已上传材料列表。 /// 候选人已上传材料列表。
/// </summary> /// </summary>
public List<ObTaskMaterial> Materials { get; set; } public List<ObTaskMaterial> Materials { get; set; }
/// <summary>
/// 合同中由员工填写的扩展变量。
/// </summary>
public List<OnboardingPublicContractVariableOutput> ContractVariables { get; set; }
} }
@@ -19,7 +19,7 @@ public class OnboardingTaskService : IOnboardingTaskService
} }
/// <summary> /// <summary>
/// 校验公司、岗位和入职模板后创建任务,并冻结合同、补充字段及材料要求。 /// 校验公司、岗位和入职模板后创建任务,并冻结合同及材料要求。
/// </summary> /// </summary>
public async Task<OnboardingTaskCreatedOutput> Create(OnboardingTaskCreateInput input) public async Task<OnboardingTaskCreatedOutput> Create(OnboardingTaskCreateInput input)
{ {
@@ -53,10 +53,6 @@ public class OnboardingTaskService : IOnboardingTaskService
if (contractConfigs.Count == 0) if (contractConfigs.Count == 0)
throw Oops.Bah("入职模板尚未配置合同文件"); throw Oops.Bah("入职模板尚未配置合同文件");
var fieldConfigs = await _db.Queryable<ObOnboardingTemplateFormField>()
.Where(item => item.OnboardingTemplateId == onboardingTemplate.Id)
.OrderBy(item => item.SortCode)
.ToListAsync();
var materialConfigs = await _db.Queryable<ObOnboardingTemplateMaterial>() var materialConfigs = await _db.Queryable<ObOnboardingTemplateMaterial>()
.Where(item => item.OnboardingTemplateId == onboardingTemplate.Id) .Where(item => item.OnboardingTemplateId == onboardingTemplate.Id)
.OrderBy(item => item.SortCode) .OrderBy(item => item.SortCode)
@@ -73,14 +69,12 @@ public class OnboardingTaskService : IOnboardingTaskService
if (enabledVersionCount != versionIds.Count) if (enabledVersionCount != versionIds.Count)
throw Oops.Bah("入职模板包含未启用的合同版本,请先调整模板配置"); throw Oops.Bah("入职模板包含未启用的合同版本,请先调整模板配置");
var fieldIds = fieldConfigs.Select(item => item.FieldId).Distinct().ToList();
var fields = fieldIds.Count == 0
? new List<ObFormField>()
: await _db.Queryable<ObFormField>().Where(item => fieldIds.Contains(item.Id)).ToListAsync();
var materialIds = materialConfigs.Select(item => item.MaterialTypeId).Distinct().ToList(); var materialIds = materialConfigs.Select(item => item.MaterialTypeId).Distinct().ToList();
var materialTypes = materialIds.Count == 0 var materialTypes = materialIds.Count == 0
? new List<ObMaterialType>() ? new List<ObMaterialType>()
: await _db.Queryable<ObMaterialType>().Where(item => materialIds.Contains(item.Id)).ToListAsync(); : await _db.Queryable<ObMaterialType>().Where(item => materialIds.Contains(item.Id)).ToListAsync();
if (materialTypes.Count != materialIds.Count)
throw Oops.Bah("入职模板包含不存在的材料类型,请先调整模板配置");
var expiryConfig = await _configService.GetByConfigKey( var expiryConfig = await _configService.GetByConfigKey(
CateGoryConst.CONFIG_BIZ_DEFINE, CateGoryConst.CONFIG_BIZ_DEFINE,
@@ -108,6 +102,8 @@ public class OnboardingTaskService : IOnboardingTaskService
Remark = input.Remark Remark = input.Remark
}; };
ValidateInitialContractDateRanges(input.ContractVariables);
await _db.Ado.BeginTranAsync(); await _db.Ado.BeginTranAsync();
try try
{ {
@@ -121,6 +117,15 @@ public class OnboardingTaskService : IOnboardingTaskService
ExpiresAt = expiresAt ExpiresAt = expiresAt
}).ExecuteCommandAsync(); }).ExecuteCommandAsync();
// 初始化空白的员工入职登记表
await _db.Insertable(new ObTaskEmployeeInfo
{
Id = global::SimpleAdmin.Core.Utils.CommonUtils.GetSingleId(),
TaskId = taskId,
Status = CommonStatusConst.ENABLE,
CreateTime = DateTime.Now
}).ExecuteCommandAsync();
var contracts = contractConfigs.Select(config => var contracts = contractConfigs.Select(config =>
{ {
var template = contractTemplates.First(item => item.Id == config.ContractTemplateId); var template = contractTemplates.First(item => item.Id == config.ContractTemplateId);
@@ -138,27 +143,6 @@ public class OnboardingTaskService : IOnboardingTaskService
}).ToList(); }).ToList();
await _db.Insertable(contracts).ExecuteCommandAsync(); await _db.Insertable(contracts).ExecuteCommandAsync();
if (fieldConfigs.Count > 0)
{
var fieldSnapshots = fieldConfigs.Select(config =>
{
var field = fields.First(item => item.Id == config.FieldId);
return new ObTaskFormRequirement
{
Id = global::SimpleAdmin.Core.Utils.CommonUtils.GetSingleId(),
TaskId = taskId,
FieldId = field.Id,
FieldCode = field.FieldCode,
FieldName = field.FieldName,
DataType = field.DataType,
OptionsJson = field.OptionsJson,
Required = config.Required,
SortCode = config.SortCode
};
}).ToList();
await _db.Insertable(fieldSnapshots).ExecuteCommandAsync();
}
if (materialConfigs.Count > 0) if (materialConfigs.Count > 0)
{ {
var materialSnapshots = materialConfigs.Select(config => var materialSnapshots = materialConfigs.Select(config =>
@@ -212,8 +196,12 @@ public class OnboardingTaskService : IOnboardingTaskService
var definitions = await _db.Queryable<ObTemplateVariable>() var definitions = await _db.Queryable<ObTemplateVariable>()
.Where(item => versionIds.Contains(item.TemplateVersionId) .Where(item => versionIds.Contains(item.TemplateVersionId)
&& item.HrEditable && item.HrEditable
&& (item.VariableType == ContractVariableType.Contract && (item.VariableType == "hr"
|| item.VariableType == ContractVariableType.Company)) || item.VariableType == "contract"
|| item.VariableType == "company"
|| item.VariableType == ContractVariableType.Contract
|| item.VariableType == ContractVariableType.Company
|| item.VariableCode.StartsWith("hr.")))
.ToListAsync(); .ToListAsync();
var duplicateInput = inputs var duplicateInput = inputs
@@ -268,4 +256,42 @@ public class OnboardingTaskService : IOnboardingTaskService
if (changedRows.Count > 0) if (changedRows.Count > 0)
await _db.Updateable(changedRows).ExecuteCommandAsync(); await _db.Updateable(changedRows).ExecuteCommandAsync();
} }
/// <summary>
/// 校验人事创建任务时填写的合同日期先后关系。
/// </summary>
private static void ValidateInitialContractDateRanges(
List<OnboardingInitialContractVariableInput> inputs)
{
inputs ??= new List<OnboardingInitialContractVariableInput>();
foreach (var group in inputs.GroupBy(item => item.ContractTemplateVersionId))
{
ValidateDateRange(group, "hr.ContractStartDate", "hr.ContractEndDate", "合同");
ValidateDateRange(group, "hr.ProbationStartDate", "hr.ProbationEndDate", "试用期");
}
}
/// <summary>
/// 校验同一合同版本内一组开始、结束日期。
/// </summary>
private static void ValidateDateRange(
IEnumerable<OnboardingInitialContractVariableInput> inputs,
string startCode,
string endCode,
string rangeName)
{
var startValue = inputs.FirstOrDefault(item =>
string.Equals(item.VariableCode, startCode, StringComparison.OrdinalIgnoreCase))?.VariableValue;
var endValue = inputs.FirstOrDefault(item =>
string.Equals(item.VariableCode, endCode, StringComparison.OrdinalIgnoreCase))?.VariableValue;
if (string.IsNullOrWhiteSpace(startValue) || string.IsNullOrWhiteSpace(endValue))
return;
if (!DateTime.TryParse(startValue, out var startDate))
throw Oops.Bah($"{rangeName}开始日期格式不正确");
if (!DateTime.TryParse(endValue, out var endDate))
throw Oops.Bah($"{rangeName}结束日期格式不正确");
if (endDate.Date < startDate.Date)
throw Oops.Bah($"{rangeName}结束日期不能早于开始日期");
}
} }
@@ -0,0 +1,10 @@
namespace SimpleAdmin.Application;
public interface IOnboardingVariableDefinitionService : ITransient
{
Task<SqlSugarPagedList<ObVariableDefinition>> Page(OnboardingVariableDefinitionPageInput input);
Task<List<ObVariableDefinition>> ListAll();
Task Add(OnboardingVariableDefinitionAddInput input);
Task Edit(OnboardingVariableDefinitionEditInput input);
Task Delete(BaseIdListInput input);
}
@@ -0,0 +1,33 @@
using System.ComponentModel.DataAnnotations;
namespace SimpleAdmin.Application;
public class OnboardingVariableDefinitionPageInput : BasePageInput
{
public string Keyword { get; set; }
public string VariableCategory { get; set; }
}
public class OnboardingVariableDefinitionAddInput
{
[Required(ErrorMessage = "变量编码不能为空")]
public string VariableCode { get; set; }
[Required(ErrorMessage = "变量名称不能为空")]
public string VariableName { get; set; }
[Required(ErrorMessage = "变量分类不能为空")]
public string VariableCategory { get; set; }
public string DataType { get; set; } = "string";
public string DefaultValue { get; set; }
public bool Required { get; set; }
public int SortCode { get; set; } = 100;
public string Remark { get; set; }
}
public class OnboardingVariableDefinitionEditInput : OnboardingVariableDefinitionAddInput
{
[IdNotNull(ErrorMessage = "Id不能为空")]
public long Id { get; set; }
}
@@ -0,0 +1,78 @@
namespace SimpleAdmin.Application;
public class OnboardingVariableDefinitionService : IOnboardingVariableDefinitionService
{
private readonly SqlSugarScope _db;
private static bool _initialized = false;
private static readonly object _lock = new();
public OnboardingVariableDefinitionService()
{
_db = DbContext.DB;
}
public async Task<SqlSugarPagedList<ObVariableDefinition>> Page(OnboardingVariableDefinitionPageInput input)
{
return await _db.Queryable<ObVariableDefinition>()
.WhereIF(!string.IsNullOrWhiteSpace(input.Keyword), it => it.VariableCode.Contains(input.Keyword) || it.VariableName.Contains(input.Keyword))
.WhereIF(!string.IsNullOrWhiteSpace(input.VariableCategory), it => it.VariableCategory == input.VariableCategory)
.OrderBy(it => it.SortCode)
.OrderBy(it => it.Id)
.ToPagedListAsync(input.PageNum, input.PageSize);
}
public async Task<List<ObVariableDefinition>> ListAll()
{
return await _db.Queryable<ObVariableDefinition>()
.OrderBy(it => it.SortCode)
.OrderBy(it => it.Id)
.ToListAsync();
}
public async Task Add(OnboardingVariableDefinitionAddInput input)
{
var code = input.VariableCode.Trim();
var exists = await _db.Queryable<ObVariableDefinition>().AnyAsync(it => it.VariableCode == code);
if (exists)
throw Oops.Bah($"变量编码“{code}”已存在");
var entity = input.Adapt<ObVariableDefinition>();
entity.VariableCode = code;
entity.IsBuiltIn = false;
await _db.Insertable(entity).ExecuteCommandAsync();
}
public async Task Edit(OnboardingVariableDefinitionEditInput input)
{
var entity = await _db.Queryable<ObVariableDefinition>().InSingleAsync(input.Id)
?? throw Oops.Bah("变量不存在");
entity.VariableName = input.VariableName;
entity.DataType = input.DataType ?? "string";
entity.DefaultValue = input.DefaultValue;
entity.Required = input.Required;
entity.SortCode = input.SortCode;
entity.Remark = input.Remark;
if (!entity.IsBuiltIn)
{
entity.VariableCategory = input.VariableCategory;
}
await _db.Updateable(entity).ExecuteCommandAsync();
}
public async Task Delete(BaseIdListInput input)
{
if (input.Ids.Count == 0) return;
var hasBuiltIn = await _db.Queryable<ObVariableDefinition>()
.Where(it => input.Ids.Contains(it.Id) && it.IsBuiltIn)
.AnyAsync();
if (hasBuiltIn)
throw Oops.Bah("系统内置的标准入职登记表变量不能删除");
await _db.Deleteable<ObVariableDefinition>()
.Where(it => input.Ids.Contains(it.Id))
.ExecuteCommandAsync();
}
}
@@ -886,7 +886,7 @@
</member> </member>
<member name="T:SimpleAdmin.Application.ObTaskEmployeeInfo"> <member name="T:SimpleAdmin.Application.ObTaskEmployeeInfo">
<summary> <summary>
候选人在入职任务中提交的固定人员基础信息。 候选人在入职任务中提交的《入职登记表》标准员工档案信息。
</summary> </summary>
</member> </member>
<member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.TaskId"> <member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.TaskId">
@@ -896,12 +896,7 @@
</member> </member>
<member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.EmployeeNo"> <member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.EmployeeNo">
<summary> <summary>
员工编号。 员工编号(选填/人事分配)。
</summary>
</member>
<member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.InternalNo">
<summary>
内部人员编号。
</summary> </summary>
</member> </member>
<member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.EmployeeName"> <member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.EmployeeName">
@@ -909,29 +904,44 @@
员工姓名。 员工姓名。
</summary> </summary>
</member> </member>
<member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.DepartmentName">
<summary>
所属部门名称。
</summary>
</member>
<member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.Gender"> <member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.Gender">
<summary> <summary>
性别。 性别。
</summary> </summary>
</member> </member>
<member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.Phone"> <member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.Nation">
<summary> <summary>
手机号码。 民族。
</summary> </summary>
</member> </member>
<member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.IdCardNumber"> <member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.Birthday">
<summary> <summary>
身份证号码。 出生年月日。
</summary> </summary>
</member> </member>
<member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.College"> <member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.RegisteredResidence">
<summary> <summary>
毕业院校。 户籍所在地。
</summary>
</member>
<member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.Height">
<summary>
身高 (cm)。
</summary>
</member>
<member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.Weight">
<summary>
体重 (kg)。
</summary>
</member>
<member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.HealthCondition">
<summary>
健康状况。
</summary>
</member>
<member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.MaritalStatus">
<summary>
婚育情况。
</summary> </summary>
</member> </member>
<member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.Education"> <member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.Education">
@@ -944,64 +954,49 @@
所学专业。 所学专业。
</summary> </summary>
</member> </member>
<member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.BirthDate"> <member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.GraduatedSchool">
<summary> <summary>
出生日期。 毕业院校。
</summary> </summary>
</member> </member>
<member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.RegisteredResidence"> <member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.VocationalQualification">
<summary> <summary>
户籍所在地。 职业资格。
</summary> </summary>
</member> </member>
<member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.Nation"> <member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.QualificationLevel">
<summary> <summary>
民族。 资格等级。
</summary> </summary>
</member> </member>
<member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.ContactAddress"> <member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.TechnicalSpecialty">
<summary> <summary>
现居住或联系地址。 有何技术特长。
</summary> </summary>
</member> </member>
<member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.EntryDate"> <member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.Phone">
<summary> <summary>
入职日期。 联系电话/手机号。
</summary> </summary>
</member> </member>
<member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.ContractStartDate"> <member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.Email">
<summary> <summary>
合同开始日期。 电子邮箱。
</summary> </summary>
</member> </member>
<member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.ContractEndDate"> <member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.IdCardNumber">
<summary> <summary>
合同结束日期。 身份证号码。
</summary> </summary>
</member> </member>
<member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.ProbationStartDate"> <member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.CurrentAddress">
<summary> <summary>
试用期开始日期。 暂住地址 / 现居住地址。
</summary> </summary>
</member> </member>
<member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.ProbationEndDate"> <member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.LandlordContact">
<summary> <summary>
试用期结束日期。 房东联系方式。
</summary>
</member>
<member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.RegularizationDate">
<summary>
转正日期。
</summary>
</member>
<member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.SocialInsuranceCompany">
<summary>
社保缴纳公司。
</summary>
</member>
<member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.ContractNature">
<summary>
合同性质。
</summary> </summary>
</member> </member>
<member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.BankCardNumber"> <member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.BankCardNumber">
@@ -1009,19 +1004,69 @@
工资银行卡号。 工资银行卡号。
</summary> </summary>
</member> </member>
<member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.BankAccountName">
<summary>
银行卡开户名。
</summary>
</member>
<member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.BankName"> <member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.BankName">
<summary> <summary>
开户银行名称。 开户银行名称。
</summary> </summary>
</member> </member>
<member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.Remark"> <member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.BankAccountName">
<summary> <summary>
业务备注。 银行卡开户人姓名。
</summary>
</member>
<member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.EmergencyContact">
<summary>
紧急联系人姓名。
</summary>
</member>
<member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.EmergencyPhone">
<summary>
紧急联系人电话。
</summary>
</member>
<member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.EmergencyAddress">
<summary>
紧急联系人联系地址。
</summary>
</member>
<member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.EducationHistoryJson">
<summary>
教育学习经历 JSON 列表: [ { timeRange, location, content, result } ]
</summary>
</member>
<member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.WorkHistoryJson">
<summary>
主要工作经历 JSON 列表: [ { timeRange, companyName, salary, position, leaveReason } ]
</summary>
</member>
<member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.FamilyMembersJson">
<summary>
家庭成员 JSON 列表: [ { name, relationship, age, company, phone } ]
</summary>
</member>
<member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.OnboardingDepartment">
<summary>
入职部门。
</summary>
</member>
<member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.OnboardingDate">
<summary>
入职时间。
</summary>
</member>
<member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.OnboardingPosition">
<summary>
入职岗位。
</summary>
</member>
<member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.OtherRemarks">
<summary>
其他说明。
</summary>
</member>
<member name="P:SimpleAdmin.Application.ObTaskEmployeeInfo.CustomVariablesJson">
<summary>
员工补充自定义变量键值 JSON: { "VariableName": "Value" }
</summary> </summary>
</member> </member>
<member name="T:SimpleAdmin.Application.ObTaskFormRequirement"> <member name="T:SimpleAdmin.Application.ObTaskFormRequirement">
@@ -1354,6 +1399,57 @@
业务备注。 业务备注。
</summary> </summary>
</member> </member>
<member name="T:SimpleAdmin.Application.ObVariableDefinition">
<summary>
全局合同变量字典定义表。
包含标准入职登记表变量、人事合同变量及企业自定义变量。
</summary>
</member>
<member name="P:SimpleAdmin.Application.ObVariableDefinition.VariableCode">
<summary>
变量全编码,如 employee.EmployeeName, hr.BaseSalary, company.CompanyName, system.TaskNo。
</summary>
</member>
<member name="P:SimpleAdmin.Application.ObVariableDefinition.VariableName">
<summary>
变量中文显示名称,如“员工姓名”、“基本工资”。
</summary>
</member>
<member name="P:SimpleAdmin.Application.ObVariableDefinition.VariableCategory">
<summary>
变量作用域分类:employee(员工变量), hr(人事参数), company(公司变量), system(系统变量)。
</summary>
</member>
<member name="P:SimpleAdmin.Application.ObVariableDefinition.DataType">
<summary>
数据类型:string, number, date, multiline。
</summary>
</member>
<member name="P:SimpleAdmin.Application.ObVariableDefinition.DefaultValue">
<summary>
默认值或占位提示。
</summary>
</member>
<member name="P:SimpleAdmin.Application.ObVariableDefinition.IsBuiltIn">
<summary>
是否为入职登记表内置字段(内置字段不可删,且第1步自动收集)。
</summary>
</member>
<member name="P:SimpleAdmin.Application.ObVariableDefinition.Required">
<summary>
是否必填。
</summary>
</member>
<member name="P:SimpleAdmin.Application.ObVariableDefinition.SortCode">
<summary>
排序码。
</summary>
</member>
<member name="P:SimpleAdmin.Application.ObVariableDefinition.Remark">
<summary>
变量说明/备注。
</summary>
</member>
<member name="T:SimpleAdmin.Application.OnboardingTaskStatus"> <member name="T:SimpleAdmin.Application.OnboardingTaskStatus">
<summary> <summary>
入职任务状态。 入职任务状态。
@@ -1629,6 +1725,11 @@
退回入职任务,保存审核意见并允许候选人补充资料。 退回入职任务,保存审核意见并允许候选人补充资料。
</summary> </summary>
</member> </member>
<member name="M:SimpleAdmin.Application.OnboardingAuditService.ParseDate(System.String)">
<summary>
将登记表中的日期文本转换为正式员工档案日期。
</summary>
</member>
<member name="T:SimpleAdmin.Application.OnboardingCompanyPageInput"> <member name="T:SimpleAdmin.Application.OnboardingCompanyPageInput">
<summary> <summary>
入职业务公司分页查询参数。 入职业务公司分页查询参数。
@@ -2926,6 +3027,21 @@
系统文件 Id。 系统文件 Id。
</summary> </summary>
</member> </member>
<member name="T:SimpleAdmin.Application.OnboardingDeleteMaterialInput">
<summary>
删除员工在当前可编辑任务中上传的一条材料。
</summary>
</member>
<member name="P:SimpleAdmin.Application.OnboardingDeleteMaterialInput.Token">
<summary>
员工填写链接中的明文 Token。
</summary>
</member>
<member name="P:SimpleAdmin.Application.OnboardingDeleteMaterialInput.TaskMaterialId">
<summary>
任务材料记录 Id。
</summary>
</member>
<member name="T:SimpleAdmin.Application.OnboardingConfirmContractInput"> <member name="T:SimpleAdmin.Application.OnboardingConfirmContractInput">
<summary> <summary>
员工确认阅读合同参数。 员工确认阅读合同参数。
@@ -2966,6 +3082,11 @@
关联候选人已经上传的系统文件与材料类型。 关联候选人已经上传的系统文件与材料类型。
</summary> </summary>
</member> </member>
<member name="M:SimpleAdmin.Application.IOnboardingPublicSubmissionService.DeleteMaterial(SimpleAdmin.Application.OnboardingDeleteMaterialInput)">
<summary>
删除可编辑任务中的一条已上传材料关联。
</summary>
</member>
<member name="M:SimpleAdmin.Application.IOnboardingPublicSubmissionService.ConfirmContract(SimpleAdmin.Application.OnboardingConfirmContractInput)"> <member name="M:SimpleAdmin.Application.IOnboardingPublicSubmissionService.ConfirmContract(SimpleAdmin.Application.OnboardingConfirmContractInput)">
<summary> <summary>
记录候选人已阅读并确认指定任务合同。 记录候选人已阅读并确认指定任务合同。
@@ -2996,6 +3117,11 @@
关联已上传 OSS 文件与任务材料要求。 关联已上传 OSS 文件与任务材料要求。
</summary> </summary>
</member> </member>
<member name="M:SimpleAdmin.Application.OnboardingPublicSubmissionService.DeleteMaterial(SimpleAdmin.Application.OnboardingDeleteMaterialInput)">
<summary>
任务仍可编辑时删除一条已上传材料。
</summary>
</member>
<member name="M:SimpleAdmin.Application.OnboardingPublicSubmissionService.ConfirmContract(SimpleAdmin.Application.OnboardingConfirmContractInput)"> <member name="M:SimpleAdmin.Application.OnboardingPublicSubmissionService.ConfirmContract(SimpleAdmin.Application.OnboardingConfirmContractInput)">
<summary> <summary>
记录候选人已经查看并确认动态生成的合同。 记录候选人已经查看并确认动态生成的合同。
@@ -3038,7 +3164,7 @@
</member> </member>
<member name="M:SimpleAdmin.Application.OnboardingPublicTaskService.Detail(SimpleAdmin.Application.OnboardingTaskPublicAccessInput)"> <member name="M:SimpleAdmin.Application.OnboardingPublicTaskService.Detail(SimpleAdmin.Application.OnboardingTaskPublicAccessInput)">
<summary> <summary>
通过有效 Token 获取任务创建时冻结的字段、材料和合同信息。 通过有效 Token 获取任务创建时冻结的材料、合同和新版入职登记表信息。
</summary> </summary>
</member> </member>
<member name="M:SimpleAdmin.Application.OnboardingPublicTaskService.SaveEmployeeInfo(SimpleAdmin.Application.OnboardingTaskPublicSaveInput)"> <member name="M:SimpleAdmin.Application.OnboardingPublicTaskService.SaveEmployeeInfo(SimpleAdmin.Application.OnboardingTaskPublicSaveInput)">
@@ -3456,6 +3582,46 @@
是否必填、必传或必签。 是否必填、必传或必签。
</summary> </summary>
</member> </member>
<member name="T:SimpleAdmin.Application.OnboardingPublicContractVariableOutput">
<summary>
员工端需要填写的合同员工变量。
</summary>
</member>
<member name="P:SimpleAdmin.Application.OnboardingPublicContractVariableOutput.VariableCode">
<summary>
合同变量编码。
</summary>
</member>
<member name="P:SimpleAdmin.Application.OnboardingPublicContractVariableOutput.VariableName">
<summary>
合同变量显示名称。
</summary>
</member>
<member name="P:SimpleAdmin.Application.OnboardingPublicContractVariableOutput.VariableType">
<summary>
变量来源类型。
</summary>
</member>
<member name="P:SimpleAdmin.Application.OnboardingPublicContractVariableOutput.DataType">
<summary>
变量数据类型。
</summary>
</member>
<member name="P:SimpleAdmin.Application.OnboardingPublicContractVariableOutput.DefaultValue">
<summary>
变量默认值。
</summary>
</member>
<member name="P:SimpleAdmin.Application.OnboardingPublicContractVariableOutput.OptionsJson">
<summary>
选择型变量的可选项 JSON。
</summary>
</member>
<member name="P:SimpleAdmin.Application.OnboardingPublicContractVariableOutput.Required">
<summary>
是否必填。
</summary>
</member>
<member name="T:SimpleAdmin.Application.OnboardingPublicMaterialOutput"> <member name="T:SimpleAdmin.Application.OnboardingPublicMaterialOutput">
<summary> <summary>
员工端材料上传要求。 员工端材料上传要求。
@@ -3526,6 +3692,11 @@
候选人已上传材料列表。 候选人已上传材料列表。
</summary> </summary>
</member> </member>
<member name="P:SimpleAdmin.Application.OnboardingTaskPublicDetailOutput.ContractVariables">
<summary>
合同中由员工填写的扩展变量。
</summary>
</member>
<member name="T:SimpleAdmin.Application.IOnboardingTaskService"> <member name="T:SimpleAdmin.Application.IOnboardingTaskService">
<summary> <summary>
定义入职任务创建业务能力。 定义入职任务创建业务能力。
@@ -3543,7 +3714,7 @@
</member> </member>
<member name="M:SimpleAdmin.Application.OnboardingTaskService.Create(SimpleAdmin.Application.OnboardingTaskCreateInput)"> <member name="M:SimpleAdmin.Application.OnboardingTaskService.Create(SimpleAdmin.Application.OnboardingTaskCreateInput)">
<summary> <summary>
校验公司、岗位和入职模板后创建任务,并冻结合同、补充字段及材料要求。 校验公司、岗位和入职模板后创建任务,并冻结合同及材料要求。
</summary> </summary>
</member> </member>
<member name="M:SimpleAdmin.Application.OnboardingTaskService.ApplyInitialContractVariables(System.Collections.Generic.List{SimpleAdmin.Application.OnboardingInitialContractVariableInput},System.Collections.Generic.List{SimpleAdmin.Application.ObTaskContract})"> <member name="M:SimpleAdmin.Application.OnboardingTaskService.ApplyInitialContractVariables(System.Collections.Generic.List{SimpleAdmin.Application.OnboardingInitialContractVariableInput},System.Collections.Generic.List{SimpleAdmin.Application.ObTaskContract})">
@@ -3551,6 +3722,16 @@
校验并保存任务创建时填写的人事合同变量。 校验并保存任务创建时填写的人事合同变量。
</summary> </summary>
</member> </member>
<member name="M:SimpleAdmin.Application.OnboardingTaskService.ValidateInitialContractDateRanges(System.Collections.Generic.List{SimpleAdmin.Application.OnboardingInitialContractVariableInput})">
<summary>
校验人事创建任务时填写的合同日期先后关系。
</summary>
</member>
<member name="M:SimpleAdmin.Application.OnboardingTaskService.ValidateDateRange(System.Collections.Generic.IEnumerable{SimpleAdmin.Application.OnboardingInitialContractVariableInput},System.String,System.String,System.String)">
<summary>
校验同一合同版本内一组开始、结束日期。
</summary>
</member>
<member name="T:SimpleAdmin.Application.IOrgService"> <member name="T:SimpleAdmin.Application.IOrgService">
<summary> <summary>
机构服务 机构服务
@@ -35,6 +35,16 @@ public class OnboardingPublicSubmissionController
await _service.SaveMaterial(input); await _service.SaveMaterial(input);
} }
/// <summary>
/// 删除当前入职任务中的一条已上传材料。
/// </summary>
[HttpPost("delete-material")]
public async Task DeleteMaterial(
[FromBody] OnboardingDeleteMaterialInput input)
{
await _service.DeleteMaterial(input);
}
/// <summary> /// <summary>
/// 确认已阅读指定合同。 /// 确认已阅读指定合同。
/// </summary> /// </summary>
@@ -0,0 +1,47 @@
namespace SimpleAdmin.Web.Core;
/// <summary>
/// 合同变量字典定义管理。
/// </summary>
[ApiDescriptionSettings("Application", Tag = "合同变量字典")]
[Route("biz/onboarding/variable-definition")]
[RolePermission]
public class OnboardingVariableDefinitionController
{
private readonly IOnboardingVariableDefinitionService _service;
public OnboardingVariableDefinitionController(IOnboardingVariableDefinitionService service)
{
_service = service;
}
[HttpGet("page")]
public async Task<dynamic> Page([FromQuery] OnboardingVariableDefinitionPageInput input)
{
return await _service.Page(input);
}
[HttpGet("list")]
public async Task<dynamic> List()
{
return await _service.ListAll();
}
[HttpPost("add")]
public async Task Add([FromBody] OnboardingVariableDefinitionAddInput input)
{
await _service.Add(input);
}
[HttpPost("edit")]
public async Task Edit([FromBody] OnboardingVariableDefinitionEditInput input)
{
await _service.Edit(input);
}
[HttpPost("delete")]
public async Task Delete([FromBody] BaseIdListInput input)
{
await _service.Delete(input);
}
}
@@ -261,6 +261,11 @@
保存员工已上传材料记录。 保存员工已上传材料记录。
</summary> </summary>
</member> </member>
<member name="M:SimpleAdmin.Web.Core.OnboardingPublicSubmissionController.DeleteMaterial(SimpleAdmin.Application.OnboardingDeleteMaterialInput)">
<summary>
删除当前入职任务中的一条已上传材料。
</summary>
</member>
<member name="M:SimpleAdmin.Web.Core.OnboardingPublicSubmissionController.ConfirmContract(SimpleAdmin.Application.OnboardingConfirmContractInput)"> <member name="M:SimpleAdmin.Web.Core.OnboardingPublicSubmissionController.ConfirmContract(SimpleAdmin.Application.OnboardingConfirmContractInput)">
<summary> <summary>
确认已阅读指定合同。 确认已阅读指定合同。
@@ -386,6 +391,11 @@
查询指定公司岗位可使用的入职模板。 查询指定公司岗位可使用的入职模板。
</summary> </summary>
</member> </member>
<member name="T:SimpleAdmin.Web.Core.OnboardingVariableDefinitionController">
<summary>
合同变量字典定义管理。
</summary>
</member>
<member name="T:SimpleAdmin.Web.Core.BizOrgController"> <member name="T:SimpleAdmin.Web.Core.BizOrgController">
<summary> <summary>
机构管理控制器 机构管理控制器
@@ -0,0 +1,144 @@
-- ====================================================================
-- OnboardX 数据库全量升级与重建脚本 (2026-08-25)
-- 包含标准化《入职登记表》全字段、合同变量字典体系、精简入职模板
-- ====================================================================
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- 1. 变量字典定义表 (新核心表)
CREATE TABLE IF NOT EXISTS `ob_variable_definition` (
`Id` BIGINT NOT NULL COMMENT '主键Id',
`VariableCode` VARCHAR(128) NOT NULL COMMENT '变量全编码 (如 employee.EmployeeName, hr.BaseSalary)',
`VariableName` VARCHAR(128) NOT NULL COMMENT '变量中文显示名称',
`VariableCategory` VARCHAR(32) NOT NULL COMMENT '变量分类: employee, hr, company, system',
`DataType` VARCHAR(32) NOT NULL DEFAULT 'string' COMMENT '数据类型: string, number, date',
`DefaultValue` VARCHAR(512) NULL COMMENT '默认值/占位提示',
`IsBuiltIn` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否为登记表内置字段 (1=是,0=否)',
`Required` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否必填',
`SortCode` INT NOT NULL DEFAULT 100 COMMENT '排序码',
`Remark` VARCHAR(512) NULL COMMENT '说明备注',
`Status` VARCHAR(20) NULL DEFAULT 'enable' COMMENT '状态',
`CreateTime` DATETIME NULL COMMENT '创建时间',
`UpdateTime` DATETIME NULL COMMENT '更新时间',
`CreateUserId` BIGINT NULL COMMENT '创建者Id',
`UpdateUserId` BIGINT NULL COMMENT '修改者Id',
`CreateUser` VARCHAR(64) NULL COMMENT '创建人',
`UpdateUser` VARCHAR(64) NULL COMMENT '更新人',
`IsDelete` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '软删除',
`ExtJson` LONGTEXT NULL,
PRIMARY KEY (`Id`),
UNIQUE KEY `uk_variable_code` (`VariableCode`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='合同变量字典定义表';
-- 预置标准变量字典数据
TRUNCATE TABLE `ob_variable_definition`;
INSERT INTO `ob_variable_definition` (`Id`, `VariableCode`, `VariableName`, `VariableCategory`, `DataType`, `IsBuiltIn`, `Required`, `SortCode`, `Remark`, `CreateTime`, `IsDelete`) VALUES
-- 员工入职登记表内置字段 (employee.)
(841400000000001, 'employee.EmployeeName', '员工姓名', 'employee', 'string', 1, 1, 10, '入职登记表-姓名', NOW(), 0),
(841400000000002, 'employee.Gender', '性别', 'employee', 'string', 1, 1, 20, '入职登记表-性别', NOW(), 0),
(841400000000003, 'employee.Nation', '民族', 'employee', 'string', 1, 0, 30, '入职登记表-民族', NOW(), 0),
(841400000000004, 'employee.Birthday', '出生年月日', 'employee', 'date', 1, 0, 40, '入职登记表-出生日期', NOW(), 0),
(841400000000005, 'employee.RegisteredResidence', '户籍所在地', 'employee', 'string', 1, 0, 50, '入职登记表-户籍', NOW(), 0),
(841400000000006, 'employee.Height', '身高(cm)', 'employee', 'string', 1, 0, 60, '入职登记表-身高', NOW(), 0),
(841400000000007, 'employee.Weight', '体重(kg)', 'employee', 'string', 1, 0, 70, '入职登记表-体重', NOW(), 0),
(841400000000008, 'employee.HealthCondition', '健康状况', 'employee', 'string', 1, 0, 80, '入职登记表-健康状况', NOW(), 0),
(841400000000009, 'employee.MaritalStatus', '婚育情况', 'employee', 'string', 1, 0, 90, '入职登记表-婚育情况', NOW(), 0),
(841400000000010, 'employee.Education', '最高学历', 'employee', 'string', 1, 0, 100, '入职登记表-学历', NOW(), 0),
(841400000000011, 'employee.Major', '所学专业', 'employee', 'string', 1, 0, 110, '入职登记表-专业', NOW(), 0),
(841400000000012, 'employee.GraduatedSchool', '毕业院校', 'employee', 'string', 1, 0, 120, '入职登记表-毕业院校', NOW(), 0),
(841400000000013, 'employee.VocationalQualification', '职业资格', 'employee', 'string', 1, 0, 130, '入职登记表-职业资格', NOW(), 0),
(841400000000014, 'employee.QualificationLevel', '资格等级', 'employee', 'string', 1, 0, 140, '入职登记表-等级', NOW(), 0),
(841400000000015, 'employee.TechnicalSpecialty', '技术特长', 'employee', 'string', 1, 0, 150, '入职登记表-有何技术特长', NOW(), 0),
(841400000000016, 'employee.Phone', '联系电话', 'employee', 'string', 1, 1, 160, '入职登记表-联系电话', NOW(), 0),
(841400000000017, 'employee.Email', '电子邮箱', 'employee', 'string', 1, 0, 170, '入职登记表-邮箱', NOW(), 0),
(841400000000018, 'employee.IdCardNumber', '身份证号码', 'employee', 'string', 1, 1, 180, '入职登记表-身份证号码', NOW(), 0),
(841400000000019, 'employee.CurrentAddress', '现居住/暂住地址', 'employee', 'string', 1, 0, 190, '入职登记表-暂住地址', NOW(), 0),
(841400000000020, 'employee.LandlordContact', '房东联系方式', 'employee', 'string', 1, 0, 200, '入职登记表-房东联系方式', NOW(), 0),
(841400000000021, 'employee.BankCardNumber', '工资银行卡号', 'employee', 'string', 1, 0, 210, '入职登记表-银行卡号', NOW(), 0),
(841400000000022, 'employee.BankName', '开户银行', 'employee', 'string', 1, 0, 220, '入职登记表-开户银行', NOW(), 0),
(841400000000023, 'employee.BankAccountName', '开户人姓名', 'employee', 'string', 1, 0, 230, '入职登记表-开户名', NOW(), 0),
(841400000000024, 'employee.EmergencyContact', '紧急联系人姓名', 'employee', 'string', 1, 0, 240, '入职登记表-紧急联系人', NOW(), 0),
(841400000000025, 'employee.EmergencyPhone', '紧急联系人电话', 'employee', 'string', 1, 0, 250, '入职登记表-紧急联系人电话', NOW(), 0),
(841400000000026, 'employee.EmergencyAddress', '紧急联系人地址', 'employee', 'string', 1, 0, 260, '入职登记表-紧急联系人地址', NOW(), 0),
-- 人事合同参数 (hr.)
(841400000000101, 'hr.BaseSalary', '基本工资(元)', 'hr', 'number', 1, 1, 300, '合同约定基本工资', NOW(), 0),
(841400000000102, 'hr.PositionSalary', '岗位工资(元)', 'hr', 'number', 1, 0, 310, '合同约定岗位工资', NOW(), 0),
(841400000000103, 'hr.PerformanceAllowance', '绩效津贴(元)', 'hr', 'number', 1, 0, 320, '合同绩效工资/津贴', NOW(), 0),
(841400000000104, 'hr.AttendanceBonus', '全勤奖(元)', 'hr', 'number', 1, 0, 330, '全勤奖金', NOW(), 0),
(841400000000105, 'hr.RegularSalary', '转正后薪资(元)', 'hr', 'number', 1, 0, 340, '转正薪资', NOW(), 0),
(841400000000106, 'hr.WorkCity', '工作地点/城市', 'hr', 'string', 1, 0, 350, '合同履行工作地点', NOW(), 0),
(841400000000107, 'hr.PositionName', '聘用岗位名称', 'hr', 'string', 1, 0, 360, '入职岗位名称', NOW(), 0),
(841400000000108, 'hr.ContractTerm', '合同期限', 'hr', 'string', 1, 0, 370, '固定期限,如:3年', NOW(), 0),
(841400000000109, 'hr.ContractStartDate', '合同开始日期', 'hr', 'date', 1, 1, 380, '劳动合同起算日期', NOW(), 0),
(841400000000110, 'hr.ContractEndDate', '合同结束日期', 'hr', 'date', 1, 1, 390, '劳动合同终止日期', NOW(), 0),
(841400000000111, 'hr.ProbationTerm', '试用期期限', 'hr', 'string', 1, 0, 400, '试用期时长,如:3个月', NOW(), 0),
(841400000000112, 'hr.ProbationStartDate', '试用期开始日期', 'hr', 'date', 1, 0, 410, '试用期起算日期', NOW(), 0),
(841400000000113, 'hr.ProbationEndDate', '试用期结束日期', 'hr', 'date', 1, 0, 420, '试用期截至日期', NOW(), 0),
(841400000000114, 'hr.EmployeeNo', '员工工号', 'hr', 'string', 1, 0, 430, '企业内部员工编号', NOW(), 0),
-- 公司与系统参数 (company. / system.)
(841400000000201, 'company.CompanyName', '公司主体全称', 'company', 'string', 1, 0, 500, '签署方甲方公司全称', NOW(), 0),
(841400000000202, 'company.CompanyCode', '公司统一代码', 'company', 'string', 1, 0, 510, '统一社会信用代码', NOW(), 0),
(841400000000203, 'company.LegalRepresentative', '法定代表人', 'company', 'string', 1, 0, 520, '甲方企业法定代表人', NOW(), 0),
(841400000000204, 'company.Address', '公司注册地址', 'company', 'string', 1, 0, 530, '甲方注册地址', NOW(), 0),
(841400000000205, 'company.Phone', '公司联系电话', 'company', 'string', 1, 0, 540, '甲方联系电话', NOW(), 0),
(841400000000301, 'system.TaskNo', '入职任务编号', 'system', 'string', 1, 0, 600, '系统入职流水号', NOW(), 0),
(841400000000302, 'system.SignDate', '合同签署日期', 'system', 'date', 1, 0, 610, '实际签署生成日期', NOW(), 0);
-- 2. 入职登记表员工档案表升级重建
DROP TABLE IF EXISTS `ob_task_employee_info`;
CREATE TABLE `ob_task_employee_info` (
`Id` BIGINT NOT NULL COMMENT '主键Id',
`TaskId` BIGINT NOT NULL COMMENT '所属入职任务Id',
`EmployeeNo` VARCHAR(64) NULL COMMENT '员工工号',
`EmployeeName` VARCHAR(64) NULL COMMENT '员工姓名',
`Gender` VARCHAR(16) NULL COMMENT '性别',
`Nation` VARCHAR(32) NULL COMMENT '民族',
`Birthday` VARCHAR(32) NULL COMMENT '出生年月日',
`RegisteredResidence` VARCHAR(256) NULL COMMENT '户籍所在地',
`Height` VARCHAR(16) NULL COMMENT '身高',
`Weight` VARCHAR(16) NULL COMMENT '体重',
`HealthCondition` VARCHAR(64) NULL COMMENT '健康状况',
`MaritalStatus` VARCHAR(32) NULL COMMENT '婚育情况',
`Education` VARCHAR(32) NULL COMMENT '最高学历',
`Major` VARCHAR(64) NULL COMMENT '所学专业',
`GraduatedSchool` VARCHAR(128) NULL COMMENT '毕业院校',
`VocationalQualification` VARCHAR(128) NULL COMMENT '职业资格',
`QualificationLevel` VARCHAR(64) NULL COMMENT '资格等级',
`TechnicalSpecialty` VARCHAR(256) NULL COMMENT '有何技术特长',
`Phone` VARCHAR(32) NULL COMMENT '联系电话',
`Email` VARCHAR(64) NULL COMMENT '电子邮箱',
`IdCardNumber` VARCHAR(32) NULL COMMENT '身份证号码',
`CurrentAddress` VARCHAR(256) NULL COMMENT '暂住/现居住地址',
`LandlordContact` VARCHAR(64) NULL COMMENT '房东联系方式',
`BankCardNumber` VARCHAR(64) NULL COMMENT '工资银行卡号',
`BankName` VARCHAR(128) NULL COMMENT '开户银行名称',
`BankAccountName` VARCHAR(64) NULL COMMENT '银行卡开户名',
`EmergencyContact` VARCHAR(64) NULL COMMENT '紧急联系人姓名',
`EmergencyPhone` VARCHAR(32) NULL COMMENT '紧急联系人电话',
`EmergencyAddress` VARCHAR(256) NULL COMMENT '紧急联系人地址',
`EducationHistoryJson` LONGTEXT NULL COMMENT '教育学习经历 JSON',
`WorkHistoryJson` LONGTEXT NULL COMMENT '主要工作经历 JSON',
`FamilyMembersJson` LONGTEXT NULL COMMENT '家庭成员 JSON',
`OnboardingDepartment` VARCHAR(64) NULL COMMENT '入职部门',
`OnboardingDate` VARCHAR(32) NULL COMMENT '入职时间',
`OnboardingPosition` VARCHAR(64) NULL COMMENT '入职岗位',
`OtherRemarks` VARCHAR(512) NULL COMMENT '其他说明',
`CustomVariablesJson` LONGTEXT NULL COMMENT '特殊合同增量变量键值 JSON',
`Status` VARCHAR(20) NULL DEFAULT 'enable' COMMENT '状态',
`CreateTime` DATETIME NULL,
`UpdateTime` DATETIME NULL,
`CreateUserId` BIGINT NULL,
`UpdateUserId` BIGINT NULL,
`CreateUser` VARCHAR(64) NULL,
`UpdateUser` VARCHAR(64) NULL,
`IsDelete` TINYINT(1) NOT NULL DEFAULT 0,
`ExtJson` LONGTEXT NULL,
PRIMARY KEY (`Id`),
KEY `idx_task_id` (`TaskId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='候选人填写的入职登记表';
SET FOREIGN_KEY_CHECKS = 1;
@@ -0,0 +1,68 @@
-- OnboardX 新版入职登记表与框架实体字段对齐
-- 仅补列,不删除或重建现有业务数据。
SET NAMES utf8mb4;
DROP PROCEDURE IF EXISTS `ob_add_column_if_missing`;
DELIMITER $$
CREATE PROCEDURE `ob_add_column_if_missing`(
IN p_table_name VARCHAR(128),
IN p_column_name VARCHAR(128),
IN p_column_definition VARCHAR(1000)
)
BEGIN
IF NOT EXISTS (
SELECT 1
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = p_table_name
AND COLUMN_NAME = p_column_name
) THEN
SET @ddl = CONCAT(
'ALTER TABLE `', p_table_name, '` ADD COLUMN `',
p_column_name, '` ', p_column_definition
);
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END IF;
END$$
DELIMITER ;
CALL `ob_add_column_if_missing`(
'ob_variable_definition',
'Status',
'VARCHAR(20) NULL DEFAULT ''enable'' COMMENT ''状态'' AFTER `Remark`'
);
CALL `ob_add_column_if_missing`(
'ob_variable_definition',
'CreateUserId',
'BIGINT NULL COMMENT ''创建者Id'' AFTER `UpdateTime`'
);
CALL `ob_add_column_if_missing`(
'ob_variable_definition',
'UpdateUserId',
'BIGINT NULL COMMENT ''修改者Id'' AFTER `CreateUserId`'
);
CALL `ob_add_column_if_missing`(
'ob_task_employee_info',
'Status',
'VARCHAR(20) NULL DEFAULT ''enable'' COMMENT ''状态'' AFTER `CustomVariablesJson`'
);
CALL `ob_add_column_if_missing`(
'ob_task_employee_info',
'CreateUserId',
'BIGINT NULL COMMENT ''创建者Id'' AFTER `UpdateTime`'
);
CALL `ob_add_column_if_missing`(
'ob_task_employee_info',
'UpdateUserId',
'BIGINT NULL COMMENT ''修改者Id'' AFTER `CreateUserId`'
);
DROP PROCEDURE IF EXISTS `ob_add_column_if_missing`;
-- 检查结果:
-- SHOW COLUMNS FROM ob_variable_definition;
-- SHOW COLUMNS FROM ob_task_employee_info;