From be20a313320b40bf97ed1225f01370c5ea7bf5c9 Mon Sep 17 00:00:00 2001
From: fuenmao <980740792@qq.com>
Date: Tue, 25 Aug 2026 20:37:17 +0800
Subject: [PATCH] 111
---
.../Entity/Onboarding/ObTaskEmployeeInfo.cs | 179 +++++++----
.../Entity/Onboarding/ObVariableDefinition.cs | 55 ++++
.../Audit/OnboardingAuditService.cs | 29 +-
.../OnboardingContractSnapshotService.cs | 31 +-
.../OnboardingContractTemplateService.cs | 30 +-
.../OnboardingTaskLifecycleService.cs | 2 -
.../OnboardingTemplateService.cs | 90 +-----
.../Dto/OnboardingPublicSubmissionInput.cs | 17 +
.../IOnboardingPublicSubmissionService.cs | 9 +
.../OnboardingPublicSubmissionService.cs | 78 ++---
.../PublicTask/OnboardingPublicTaskService.cs | 69 ++--
.../Task/Dto/OnboardingTaskPublicInput.cs | 46 +++
.../Onboarding/Task/OnboardingTaskService.cs | 90 ++++--
.../IOnboardingVariableDefinitionService.cs | 10 +
.../OnboardingVariableDefinitionInput.cs | 33 ++
.../OnboardingVariableDefinitionService.cs | 78 +++++
.../SimpleAdmin.Application.xml | 301 ++++++++++++++----
.../OnboardingPublicSubmissionController.cs | 10 +
.../OnboardingVariableDefinitionController.cs | 47 +++
SimpleAdmin.Web.Core/SimpleAdmin.Web.Core.xml | 10 +
.../20260825_full_schema_rebuild.sql | 144 +++++++++
...260825_onboarding_schema_alignment_fix.sql | 68 ++++
22 files changed, 1090 insertions(+), 336 deletions(-)
create mode 100644 SimpleAdmin.Application/Entity/Onboarding/ObVariableDefinition.cs
create mode 100644 SimpleAdmin.Application/Services/Onboarding/VariableDefinition/IOnboardingVariableDefinitionService.cs
create mode 100644 SimpleAdmin.Application/Services/Onboarding/VariableDefinition/OnboardingVariableDefinitionInput.cs
create mode 100644 SimpleAdmin.Application/Services/Onboarding/VariableDefinition/OnboardingVariableDefinitionService.cs
create mode 100644 SimpleAdmin.Web.Core/Controllers/Application/Onboarding/OnboardingVariableDefinitionController.cs
create mode 100644 database/migrations/20260825_full_schema_rebuild.sql
create mode 100644 database/migrations/20260825_onboarding_schema_alignment_fix.sql
diff --git a/SimpleAdmin.Application/Entity/Onboarding/ObTaskEmployeeInfo.cs b/SimpleAdmin.Application/Entity/Onboarding/ObTaskEmployeeInfo.cs
index 97d02a5..3bf55ea 100644
--- a/SimpleAdmin.Application/Entity/Onboarding/ObTaskEmployeeInfo.cs
+++ b/SimpleAdmin.Application/Entity/Onboarding/ObTaskEmployeeInfo.cs
@@ -11,9 +11,9 @@
namespace SimpleAdmin.Application;
///
-/// 候选人在入职任务中提交的固定人员基础信息。
+/// 候选人在入职任务中提交的《入职登记表》标准员工档案信息。
///
-[SugarTable("ob_task_employee_info", TableDescription = "候选人提交的固定基础信息")]
+[SugarTable("ob_task_employee_info", TableDescription = "候选人填写的入职登记表")]
[Tenant(SqlSugarConst.DB_DEFAULT)]
public class ObTaskEmployeeInfo : BaseEntity
{
@@ -23,45 +23,57 @@ public class ObTaskEmployeeInfo : BaseEntity
public long TaskId { get; set; }
///
- /// 员工编号。
+ /// 员工编号(选填/人事分配)。
///
public string EmployeeNo { get; set; }
- ///
- /// 内部人员编号。
- ///
- public string InternalNo { get; set; }
-
+ // ==================== 1. 基本个人信息 ====================
///
/// 员工姓名。
///
public string EmployeeName { get; set; }
- ///
- /// 所属部门名称。
- ///
- public string DepartmentName { get; set; }
-
///
/// 性别。
///
public string Gender { get; set; }
///
- /// 手机号码。
+ /// 民族。
///
- public string Phone { get; set; }
+ public string Nation { get; set; }
///
- /// 身份证号码。
+ /// 出生年月日。
///
- public string IdCardNumber { get; set; }
+ public string Birthday { get; set; }
///
- /// 毕业院校。
+ /// 户籍所在地。
///
- public string College { get; set; }
+ public string RegisteredResidence { get; set; }
+ ///
+ /// 身高 (cm)。
+ ///
+ public string Height { get; set; }
+
+ ///
+ /// 体重 (kg)。
+ ///
+ public string Weight { get; set; }
+
+ ///
+ /// 健康状况。
+ ///
+ public string HealthCondition { get; set; }
+
+ ///
+ /// 婚育情况。
+ ///
+ public string MaritalStatus { get; set; }
+
+ // ==================== 2. 学历与资质 ====================
///
/// 最高学历。
///
@@ -73,82 +85,125 @@ public class ObTaskEmployeeInfo : BaseEntity
public string Major { get; set; }
///
- /// 出生日期。
+ /// 毕业院校。
///
- public DateTime? BirthDate { get; set; }
+ public string GraduatedSchool { get; set; }
///
- /// 户籍所在地。
+ /// 职业资格。
///
- public string RegisteredResidence { get; set; }
+ public string VocationalQualification { get; set; }
///
- /// 民族。
+ /// 资格等级。
///
- public string Nation { get; set; }
+ public string QualificationLevel { get; set; }
///
- /// 现居住或联系地址。
+ /// 有何技术特长。
///
- public string ContactAddress { get; set; }
+ public string TechnicalSpecialty { get; set; }
+
+ // ==================== 3. 联系方式与薪酬账户 ====================
+ ///
+ /// 联系电话/手机号。
+ ///
+ public string Phone { get; set; }
///
- /// 入职日期。
+ /// 电子邮箱。
///
- public DateTime? EntryDate { get; set; }
+ public string Email { get; set; }
///
- /// 合同开始日期。
+ /// 身份证号码。
///
- public DateTime? ContractStartDate { get; set; }
+ public string IdCardNumber { get; set; }
///
- /// 合同结束日期。
+ /// 暂住地址 / 现居住地址。
///
- public DateTime? ContractEndDate { get; set; }
+ public string CurrentAddress { get; set; }
///
- /// 试用期开始日期。
+ /// 房东联系方式。
///
- public DateTime? ProbationStartDate { get; set; }
-
- ///
- /// 试用期结束日期。
- ///
- public DateTime? ProbationEndDate { get; set; }
-
- ///
- /// 转正日期。
- ///
- public DateTime? RegularizationDate { get; set; }
-
- ///
- /// 社保缴纳公司。
- ///
- public string SocialInsuranceCompany { get; set; }
-
- ///
- /// 合同性质。
- ///
- public string ContractNature { get; set; }
+ public string LandlordContact { get; set; }
///
/// 工资银行卡号。
///
public string BankCardNumber { get; set; }
- ///
- /// 银行卡开户名。
- ///
- public string BankAccountName { get; set; }
-
///
/// 开户银行名称。
///
public string BankName { get; set; }
///
- /// 业务备注。
+ /// 银行卡开户人姓名。
///
- public string Remark { get; set; }
+ public string BankAccountName { get; set; }
+
+ // ==================== 4. 紧急联系人 ====================
+ ///
+ /// 紧急联系人姓名。
+ ///
+ public string EmergencyContact { get; set; }
+
+ ///
+ /// 紧急联系人电话。
+ ///
+ public string EmergencyPhone { get; set; }
+
+ ///
+ /// 紧急联系人联系地址。
+ ///
+ public string EmergencyAddress { get; set; }
+
+ // ==================== 5. 动态多条经历(JSON 存储) ====================
+ ///
+ /// 教育学习经历 JSON 列表: [ { timeRange, location, content, result } ]
+ ///
+ [SugarColumn(ColumnDataType = StaticConfig.CodeFirst_BigString, IsNullable = true)]
+ public string EducationHistoryJson { get; set; }
+
+ ///
+ /// 主要工作经历 JSON 列表: [ { timeRange, companyName, salary, position, leaveReason } ]
+ ///
+ [SugarColumn(ColumnDataType = StaticConfig.CodeFirst_BigString, IsNullable = true)]
+ public string WorkHistoryJson { get; set; }
+
+ ///
+ /// 家庭成员 JSON 列表: [ { name, relationship, age, company, phone } ]
+ ///
+ [SugarColumn(ColumnDataType = StaticConfig.CodeFirst_BigString, IsNullable = true)]
+ public string FamilyMembersJson { get; set; }
+
+ // ==================== 6. 入职情况与说明 ====================
+ ///
+ /// 入职部门。
+ ///
+ public string OnboardingDepartment { get; set; }
+
+ ///
+ /// 入职时间。
+ ///
+ public string OnboardingDate { get; set; }
+
+ ///
+ /// 入职岗位。
+ ///
+ public string OnboardingPosition { get; set; }
+
+ ///
+ /// 其他说明。
+ ///
+ public string OtherRemarks { get; set; }
+
+ ///
+ /// 员工补充自定义变量键值 JSON: { "VariableName": "Value" }
+ ///
+ [SugarColumn(ColumnDataType = StaticConfig.CodeFirst_BigString, IsNullable = true)]
+ public string CustomVariablesJson { get; set; }
}
diff --git a/SimpleAdmin.Application/Entity/Onboarding/ObVariableDefinition.cs b/SimpleAdmin.Application/Entity/Onboarding/ObVariableDefinition.cs
new file mode 100644
index 0000000..f46e910
--- /dev/null
+++ b/SimpleAdmin.Application/Entity/Onboarding/ObVariableDefinition.cs
@@ -0,0 +1,55 @@
+namespace SimpleAdmin.Application;
+
+///
+/// 全局合同变量字典定义表。
+/// 包含标准入职登记表变量、人事合同变量及企业自定义变量。
+///
+[SugarTable("ob_variable_definition", TableDescription = "合同变量字典定义")]
+[Tenant(SqlSugarConst.DB_DEFAULT)]
+public class ObVariableDefinition : BaseEntity
+{
+ ///
+ /// 变量全编码,如 employee.EmployeeName, hr.BaseSalary, company.CompanyName, system.TaskNo。
+ ///
+ public string VariableCode { get; set; }
+
+ ///
+ /// 变量中文显示名称,如“员工姓名”、“基本工资”。
+ ///
+ public string VariableName { get; set; }
+
+ ///
+ /// 变量作用域分类:employee(员工变量), hr(人事参数), company(公司变量), system(系统变量)。
+ ///
+ public string VariableCategory { get; set; }
+
+ ///
+ /// 数据类型:string, number, date, multiline。
+ ///
+ public string DataType { get; set; } = "string";
+
+ ///
+ /// 默认值或占位提示。
+ ///
+ public string DefaultValue { get; set; }
+
+ ///
+ /// 是否为入职登记表内置字段(内置字段不可删,且第1步自动收集)。
+ ///
+ public bool IsBuiltIn { get; set; } = false;
+
+ ///
+ /// 是否必填。
+ ///
+ public bool Required { get; set; } = false;
+
+ ///
+ /// 排序码。
+ ///
+ public int SortCode { get; set; } = 100;
+
+ ///
+ /// 变量说明/备注。
+ ///
+ public string Remark { get; set; }
+}
\ No newline at end of file
diff --git a/SimpleAdmin.Application/Services/Onboarding/Audit/OnboardingAuditService.cs b/SimpleAdmin.Application/Services/Onboarding/Audit/OnboardingAuditService.cs
index e807c7c..a4dd855 100644
--- a/SimpleAdmin.Application/Services/Onboarding/Audit/OnboardingAuditService.cs
+++ b/SimpleAdmin.Application/Services/Onboarding/Audit/OnboardingAuditService.cs
@@ -44,14 +44,25 @@ public class OnboardingAuditService : IOnboardingAuditService
throw Oops.Bah("该身份证号或手机号已存在正式员工");
}
- var extraValues = await _db.Queryable()
- .Where(item => item.TaskId == task.Id)
- .ToListAsync();
var employee = employeeInfo.Adapt();
employee.Id = global::SimpleAdmin.Core.Utils.CommonUtils.GetSingleId();
employee.CompanyId = task.CompanyId;
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();
@@ -124,6 +135,16 @@ public class OnboardingAuditService : IOnboardingAuditService
return task;
}
+ ///
+ /// 将登记表中的日期文本转换为正式员工档案日期。
+ ///
+ 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)
{
return _db.Insertable(new ObTaskAudit
diff --git a/SimpleAdmin.Application/Services/Onboarding/ContractSnapshot/OnboardingContractSnapshotService.cs b/SimpleAdmin.Application/Services/Onboarding/ContractSnapshot/OnboardingContractSnapshotService.cs
index 66ac864..ec54839 100644
--- a/SimpleAdmin.Application/Services/Onboarding/ContractSnapshot/OnboardingContractSnapshotService.cs
+++ b/SimpleAdmin.Application/Services/Onboarding/ContractSnapshot/OnboardingContractSnapshotService.cs
@@ -26,12 +26,8 @@ public class OnboardingContractSnapshotService : IOnboardingContractSnapshotServ
?? throw Oops.Bah("入职公司不存在");
employeeInfo ??= await _db.Queryable()
.FirstAsync(item => item.TaskId == taskId);
- var dynamicValues = await _db.Queryable()
- .Where(item => item.TaskId == taskId)
- .ToListAsync();
- var dynamicValueMap = dynamicValues
- .GroupBy(item => item.FieldCode, StringComparer.OrdinalIgnoreCase)
- .ToDictionary(group => group.Key, group => group.Last().FieldValue, StringComparer.OrdinalIgnoreCase);
+ // 员工补充合同变量统一从 CustomVariablesJson 读取,不再访问已移除的旧动态字段表。
+ var dynamicValueMap = new Dictionary(StringComparer.OrdinalIgnoreCase);
var contracts = await _db.Queryable()
.Where(item => item.TaskId == taskId)
.ToListAsync();
@@ -110,6 +106,23 @@ public class OnboardingContractSnapshotService : IOnboardingContractSnapshotServ
var fixedValue = GetPropertyValue(employeeInfo, ResolveEmployeeField(sourceField), definition.FormatRule);
if (!string.IsNullOrWhiteSpace(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)
? dynamicValue
: definition.DefaultValue;
@@ -184,8 +197,10 @@ public class OnboardingContractSnapshotService : IOnboardingContractSnapshotServ
"LEGALNAME" or "LEGAL_NAME" or "COMPANYLEGALNAME" => nameof(ObCompany.LegalName),
"UNIFIEDSOCIALCREDITCODE" or "UNIFIED_SOCIAL_CREDIT_CODE" => nameof(ObCompany.UnifiedSocialCreditCode),
"LEGALREPRESENTATIVE" or "LEGAL_REPRESENTATIVE" => nameof(ObCompany.LegalRepresentative),
- "REGISTEREDADDRESS" or "REGISTERED_ADDRESS" => nameof(ObCompany.RegisteredAddress),
- "CONTACTPHONE" or "CONTACT_PHONE" => nameof(ObCompany.ContactPhone),
+ "ADDRESS" or "COMPANYADDRESS" or "COMPANY_ADDRESS"
+ or "REGISTEREDADDRESS" or "REGISTERED_ADDRESS" => nameof(ObCompany.RegisteredAddress),
+ "PHONE" or "COMPANYPHONE" or "COMPANY_PHONE"
+ or "CONTACTPHONE" or "CONTACT_PHONE" => nameof(ObCompany.ContactPhone),
_ => sourceField
};
}
diff --git a/SimpleAdmin.Application/Services/Onboarding/ContractTemplate/OnboardingContractTemplateService.cs b/SimpleAdmin.Application/Services/Onboarding/ContractTemplate/OnboardingContractTemplateService.cs
index f2c5cc4..04380ab 100644
--- a/SimpleAdmin.Application/Services/Onboarding/ContractTemplate/OnboardingContractTemplateService.cs
+++ b/SimpleAdmin.Application/Services/Onboarding/ContractTemplate/OnboardingContractTemplateService.cs
@@ -179,6 +179,16 @@ public class OnboardingContractTemplateService : DbRepository().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 version = input.Adapt();
version.Id = versionId;
@@ -186,7 +196,12 @@ public class OnboardingContractTemplateService : DbRepository 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();
try
@@ -263,7 +278,7 @@ public class OnboardingContractTemplateService : DbRepository 0 ? code[..separatorIndex].ToLowerInvariant() : string.Empty;
@@ -277,21 +292,24 @@ public class OnboardingContractTemplateService : DbRepository 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
{
Id = global::SimpleAdmin.Core.Utils.CommonUtils.GetSingleId(),
TemplateVersionId = versionId,
VariableCode = code,
- VariableName = sourceField,
+ VariableName = varName,
VariableType = variableType,
- DataType = InferDataType(sourceField),
+ DataType = dataType,
EmployeeField = variableType is ContractVariableType.Employee or ContractVariableType.Company
? sourceField
: null,
- Required = true,
+ Required = def?.Required ?? true,
HrEditable = variableType == ContractVariableType.Contract,
SortCode = (index + 1) * 10,
- Remark = variableType == ContractVariableType.Unresolved ? "导入时未识别变量来源,请管理员确认" : null
+ Remark = def?.Remark
};
}
diff --git a/SimpleAdmin.Application/Services/Onboarding/Lifecycle/OnboardingTaskLifecycleService.cs b/SimpleAdmin.Application/Services/Onboarding/Lifecycle/OnboardingTaskLifecycleService.cs
index b90f482..4d916e1 100644
--- a/SimpleAdmin.Application/Services/Onboarding/Lifecycle/OnboardingTaskLifecycleService.cs
+++ b/SimpleAdmin.Application/Services/Onboarding/Lifecycle/OnboardingTaskLifecycleService.cs
@@ -83,10 +83,8 @@ public class OnboardingTaskLifecycleService : IOnboardingTaskLifecycleService
await _db.Deleteable().Where(x => x.TaskId == task.Id).ExecuteCommandAsync();
await _db.Deleteable().Where(x => x.TaskId == task.Id).ExecuteCommandAsync();
- await _db.Deleteable().Where(x => x.TaskId == task.Id).ExecuteCommandAsync();
await _db.Deleteable().Where(x => x.TaskId == task.Id).ExecuteCommandAsync();
await _db.Deleteable().Where(x => x.TaskId == task.Id).ExecuteCommandAsync();
- await _db.Deleteable().Where(x => x.TaskId == task.Id).ExecuteCommandAsync();
await _db.Deleteable().Where(x => x.TaskId == task.Id).ExecuteCommandAsync();
await _db.Deleteable().Where(x => x.TaskId == task.Id).ExecuteCommandAsync();
await _db.Deleteable().In(task.Id).ExecuteCommandAsync();
diff --git a/SimpleAdmin.Application/Services/Onboarding/OnboardingTemplate/OnboardingTemplateService.cs b/SimpleAdmin.Application/Services/Onboarding/OnboardingTemplate/OnboardingTemplateService.cs
index ef92726..d332d2a 100644
--- a/SimpleAdmin.Application/Services/Onboarding/OnboardingTemplate/OnboardingTemplateService.cs
+++ b/SimpleAdmin.Application/Services/Onboarding/OnboardingTemplate/OnboardingTemplateService.cs
@@ -75,10 +75,7 @@ public class OnboardingTemplateService : IOnboardingTemplateService
.Where(item => item.OnboardingTemplateId == template.Id)
.OrderBy(item => item.SortCode)
.ToListAsync(),
- FormFields = await _db.Queryable()
- .Where(item => item.OnboardingTemplateId == template.Id)
- .OrderBy(item => item.SortCode)
- .ToListAsync(),
+ FormFields = new List(),
Materials = await _db.Queryable()
.Where(item => item.OnboardingTemplateId == template.Id)
.OrderBy(item => item.SortCode)
@@ -246,49 +243,6 @@ public class OnboardingTemplateService : IOnboardingTemplateService
{
if (versions.Any(item => item.VersionStatus != ContractTemplateVersionStatus.Enabled))
throw Oops.Bah("启用入职模板前,请先启用全部合同版本");
-
- var variables = await _db.Queryable()
- .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()
- : await _db.Queryable()
- .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()
- .Where(item => versionIds.Contains(item.TemplateVersionId)
- && item.VariableType == ContractVariableType.Employee)
- .ToListAsync();
- var availableFields = new HashSet(
- 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();
@@ -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)
{
var positions = input.PositionIds.Select(positionId => new ObOnboardingTemplatePosition
@@ -342,20 +277,7 @@ public class OnboardingTemplateService : IOnboardingTemplateService
}).ToList();
await _db.Insertable(contracts).ExecuteCommandAsync();
- if (input.FormFields.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)
+ if (input.Materials != null && input.Materials.Count > 0)
{
var materials = input.Materials.Select(item => new ObOnboardingTemplateMaterial
{
@@ -377,9 +299,6 @@ public class OnboardingTemplateService : IOnboardingTemplateService
await _db.Deleteable()
.Where(item => item.OnboardingTemplateId == templateId)
.ExecuteCommandAsync();
- await _db.Deleteable()
- .Where(item => item.OnboardingTemplateId == templateId)
- .ExecuteCommandAsync();
await _db.Deleteable()
.Where(item => item.OnboardingTemplateId == templateId)
.ExecuteCommandAsync();
@@ -405,9 +324,6 @@ public class OnboardingTemplateService : IOnboardingTemplateService
var contracts = await _db.Queryable()
.Where(item => templateIds.Contains(item.OnboardingTemplateId))
.ToListAsync();
- var fields = await _db.Queryable()
- .Where(item => templateIds.Contains(item.OnboardingTemplateId))
- .ToListAsync();
var materials = await _db.Queryable()
.Where(item => templateIds.Contains(item.OnboardingTemplateId))
.ToListAsync();
@@ -432,7 +348,7 @@ public class OnboardingTemplateService : IOnboardingTemplateService
.Where(name => !string.IsNullOrWhiteSpace(name))
.ToList(),
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)
};
}).ToList();
diff --git a/SimpleAdmin.Application/Services/Onboarding/PublicSubmission/Dto/OnboardingPublicSubmissionInput.cs b/SimpleAdmin.Application/Services/Onboarding/PublicSubmission/Dto/OnboardingPublicSubmissionInput.cs
index d4bf95f..4f500fb 100644
--- a/SimpleAdmin.Application/Services/Onboarding/PublicSubmission/Dto/OnboardingPublicSubmissionInput.cs
+++ b/SimpleAdmin.Application/Services/Onboarding/PublicSubmission/Dto/OnboardingPublicSubmissionInput.cs
@@ -55,6 +55,23 @@ public class OnboardingSaveMaterialInput
public long FileId { get; set; }
}
+///
+/// 删除员工在当前可编辑任务中上传的一条材料。
+///
+public class OnboardingDeleteMaterialInput
+{
+ ///
+ /// 员工填写链接中的明文 Token。
+ ///
+ [global::System.ComponentModel.DataAnnotations.Required]
+ public string Token { get; set; }
+
+ ///
+ /// 任务材料记录 Id。
+ ///
+ public long TaskMaterialId { get; set; }
+}
+
///
/// 员工确认阅读合同参数。
///
diff --git a/SimpleAdmin.Application/Services/Onboarding/PublicSubmission/IOnboardingPublicSubmissionService.cs b/SimpleAdmin.Application/Services/Onboarding/PublicSubmission/IOnboardingPublicSubmissionService.cs
index 82af52c..0d43e8a 100644
--- a/SimpleAdmin.Application/Services/Onboarding/PublicSubmission/IOnboardingPublicSubmissionService.cs
+++ b/SimpleAdmin.Application/Services/Onboarding/PublicSubmission/IOnboardingPublicSubmissionService.cs
@@ -9,18 +9,27 @@ public interface IOnboardingPublicSubmissionService : ITransient
/// 保存候选人填写的动态字段值。
///
Task SaveDynamicValues(OnboardingSaveDynamicValuesInput input);
+
///
/// 关联候选人已经上传的系统文件与材料类型。
///
Task SaveMaterial(OnboardingSaveMaterialInput input);
+
+ ///
+ /// 删除可编辑任务中的一条已上传材料关联。
+ ///
+ Task DeleteMaterial(OnboardingDeleteMaterialInput input);
+
///
/// 记录候选人已阅读并确认指定任务合同。
///
Task ConfirmContract(OnboardingConfirmContractInput input);
+
///
/// 校验全部必填资料后提交入职任务进入审核。
///
Task Submit(OnboardingSubmitInput input);
+
///
/// 在保存文件前校验材料适用范围、扩展名、大小和数量。
///
diff --git a/SimpleAdmin.Application/Services/Onboarding/PublicSubmission/OnboardingPublicSubmissionService.cs b/SimpleAdmin.Application/Services/Onboarding/PublicSubmission/OnboardingPublicSubmissionService.cs
index c3ba935..053074e 100644
--- a/SimpleAdmin.Application/Services/Onboarding/PublicSubmission/OnboardingPublicSubmissionService.cs
+++ b/SimpleAdmin.Application/Services/Onboarding/PublicSubmission/OnboardingPublicSubmissionService.cs
@@ -19,37 +19,8 @@ public class OnboardingPublicSubmissionService : IOnboardingPublicSubmissionServ
///
public async Task SaveDynamicValues(OnboardingSaveDynamicValuesInput input)
{
- var task = await GetEditableTask(input.Token);
- var requirements = await GetFormRequirements(task.Id);
- 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()
- .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);
+ await GetEditableTask(input.Token);
+ throw Oops.Bah("新版入职登记表不再使用旧动态字段接口,请通过员工基础信息接口保存补充变量");
}
///
@@ -96,6 +67,25 @@ public class OnboardingPublicSubmissionService : IOnboardingPublicSubmissionServ
}).ExecuteCommandAsync();
}
+ ///
+ /// 任务仍可编辑时删除一条已上传材料。
+ ///
+ public async Task DeleteMaterial(
+ OnboardingDeleteMaterialInput input)
+ {
+ var task = await GetEditableTask(input.Token);
+ var material = await _db.Queryable()
+ .FirstAsync(item =>
+ item.Id == input.TaskMaterialId
+ && item.TaskId == task.Id);
+ if (material == null)
+ throw Oops.Bah("上传材料不存在");
+
+ await _db.Deleteable()
+ .In(material.Id)
+ .ExecuteCommandAsync();
+ }
+
///
/// 记录候选人已经查看并确认动态生成的合同。
///
@@ -124,24 +114,14 @@ public class OnboardingPublicSubmissionService : IOnboardingPublicSubmissionServ
var basicInfoIncomplete = info == null
|| string.IsNullOrWhiteSpace(info.EmployeeName)
|| string.IsNullOrWhiteSpace(info.Phone)
- || string.IsNullOrWhiteSpace(info.IdCardNumber);
+ || string.IsNullOrWhiteSpace(info.IdCardNumber)
+ || string.IsNullOrWhiteSpace(info.BankCardNumber)
+ || string.IsNullOrWhiteSpace(info.BankName);
if (basicInfoIncomplete)
throw Oops.Bah("请先完整填写基本信息");
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()
- .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))
.Where(item => item.Required)
.Select(item => item.MaterialTypeId)
@@ -174,7 +154,7 @@ public class OnboardingPublicSubmissionService : IOnboardingPublicSubmissionServ
AuditRound = task.SubmitCount,
AuditAction = OnboardingAuditAction.Submit,
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
}).ExecuteCommandAsync();
}
@@ -235,14 +215,6 @@ public class OnboardingPublicSubmissionService : IOnboardingPublicSubmissionServ
throw Oops.Bah($"合同“{missing.contract.ContractName}”的必填变量“{missing.definition.VariableName}”尚未填写");
}
- private Task> GetFormRequirements(long taskId)
- {
- return _db.Queryable()
- .Where(item => item.TaskId == taskId)
- .OrderBy(item => item.SortCode)
- .ToListAsync();
- }
-
private Task> GetMaterialRequirements(long taskId)
{
return _db.Queryable()
diff --git a/SimpleAdmin.Application/Services/Onboarding/PublicTask/OnboardingPublicTaskService.cs b/SimpleAdmin.Application/Services/Onboarding/PublicTask/OnboardingPublicTaskService.cs
index 28866ea..aca0113 100644
--- a/SimpleAdmin.Application/Services/Onboarding/PublicTask/OnboardingPublicTaskService.cs
+++ b/SimpleAdmin.Application/Services/Onboarding/PublicTask/OnboardingPublicTaskService.cs
@@ -19,7 +19,7 @@ public class OnboardingPublicTaskService : IOnboardingPublicTaskService
}
///
- /// 通过有效 Token 获取任务创建时冻结的字段、材料和合同信息。
+ /// 通过有效 Token 获取任务创建时冻结的材料、合同和新版入职登记表信息。
///
public async Task Detail(OnboardingTaskPublicAccessInput input)
{
@@ -30,39 +30,49 @@ public class OnboardingPublicTaskService : IOnboardingPublicTaskService
link.AccessCount++;
link.LastAccessedAt = DateTime.Now;
await _db.Updateable(link).ExecuteCommandAsync();
-
- var formRequirements = await _db.Queryable()
- .Where(item => item.TaskId == task.Id)
- .OrderBy(item => item.SortCode)
- .ToListAsync();
var materialRequirements = await _db.Queryable()
.Where(item => item.TaskId == task.Id)
.OrderBy(item => item.SortCode)
.ToListAsync();
+ var contracts = await _db.Queryable()
+ .Where(item => item.TaskId == task.Id)
+ .ToListAsync();
+ var templateVersionIds = contracts
+ .Select(item => item.TemplateVersionId)
+ .Distinct()
+ .ToList();
+ var contractVariables = new List();
+ if (templateVersionIds.Count > 0)
+ {
+ var employeeVariables = await _db.Queryable()
+ .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
{
Task = task,
EmployeeInfo = await _db.Queryable()
.FirstAsync(item => item.TaskId == task.Id),
- Contracts = await _db.Queryable()
- .Where(item => item.TaskId == task.Id)
- .ToListAsync(),
- DynamicValues = await _db.Queryable()
- .Where(item => item.TaskId == task.Id)
- .ToListAsync(),
+ Contracts = contracts,
+ DynamicValues = new List(),
Materials = await _db.Queryable()
.Where(item => item.TaskId == task.Id)
.ToListAsync(),
- FormFields = formRequirements.Select(requirement => new OnboardingPublicFormFieldOutput
- {
- FieldId = requirement.FieldId,
- FieldCode = requirement.FieldCode,
- FieldName = requirement.FieldName,
- DataType = requirement.DataType,
- OptionsJson = requirement.OptionsJson,
- Required = requirement.Required
- }).ToList(),
+ FormFields = new List(),
MaterialRequirements = materialRequirements.Select(requirement => new OnboardingPublicMaterialOutput
{
MaterialTypeId = requirement.MaterialTypeId,
@@ -70,7 +80,8 @@ public class OnboardingPublicTaskService : IOnboardingPublicTaskService
AllowedExtensions = requirement.AllowedExtensions,
MaxFileCount = requirement.MaxFileCount,
Required = requirement.Required
- }).ToList()
+ }).ToList(),
+ ContractVariables = contractVariables
};
}
@@ -92,15 +103,29 @@ public class OnboardingPublicTaskService : IOnboardingPublicTaskService
.FirstAsync(item => item.TaskId == task.Id);
var entity = input.Adapt();
entity.TaskId = task.Id;
+ entity.IsDelete = false;
if (old == null)
{
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();
}
else
{
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();
}
diff --git a/SimpleAdmin.Application/Services/Onboarding/Task/Dto/OnboardingTaskPublicInput.cs b/SimpleAdmin.Application/Services/Onboarding/Task/Dto/OnboardingTaskPublicInput.cs
index 3d202eb..8742350 100644
--- a/SimpleAdmin.Application/Services/Onboarding/Task/Dto/OnboardingTaskPublicInput.cs
+++ b/SimpleAdmin.Application/Services/Onboarding/Task/Dto/OnboardingTaskPublicInput.cs
@@ -96,6 +96,47 @@ public class OnboardingPublicFormFieldOutput
public bool Required { get; set; }
}
+///
+/// 员工端需要填写的合同员工变量。
+///
+public class OnboardingPublicContractVariableOutput
+{
+ ///
+ /// 合同变量编码。
+ ///
+ public string VariableCode { get; set; }
+
+ ///
+ /// 合同变量显示名称。
+ ///
+ public string VariableName { get; set; }
+
+ ///
+ /// 变量来源类型。
+ ///
+ public string VariableType { get; set; }
+
+ ///
+ /// 变量数据类型。
+ ///
+ public string DataType { get; set; }
+
+ ///
+ /// 变量默认值。
+ ///
+ public string DefaultValue { get; set; }
+
+ ///
+ /// 选择型变量的可选项 JSON。
+ ///
+ public string OptionsJson { get; set; }
+
+ ///
+ /// 是否必填。
+ ///
+ public bool Required { get; set; }
+}
+
///
/// 员工端材料上传要求。
///
@@ -166,4 +207,9 @@ public class OnboardingTaskPublicDetailOutput
/// 候选人已上传材料列表。
///
public List Materials { get; set; }
+
+ ///
+ /// 合同中由员工填写的扩展变量。
+ ///
+ public List ContractVariables { get; set; }
}
diff --git a/SimpleAdmin.Application/Services/Onboarding/Task/OnboardingTaskService.cs b/SimpleAdmin.Application/Services/Onboarding/Task/OnboardingTaskService.cs
index 369fe14..20baa88 100644
--- a/SimpleAdmin.Application/Services/Onboarding/Task/OnboardingTaskService.cs
+++ b/SimpleAdmin.Application/Services/Onboarding/Task/OnboardingTaskService.cs
@@ -19,7 +19,7 @@ public class OnboardingTaskService : IOnboardingTaskService
}
///
- /// 校验公司、岗位和入职模板后创建任务,并冻结合同、补充字段及材料要求。
+ /// 校验公司、岗位和入职模板后创建任务,并冻结合同及材料要求。
///
public async Task Create(OnboardingTaskCreateInput input)
{
@@ -53,10 +53,6 @@ public class OnboardingTaskService : IOnboardingTaskService
if (contractConfigs.Count == 0)
throw Oops.Bah("入职模板尚未配置合同文件");
- var fieldConfigs = await _db.Queryable()
- .Where(item => item.OnboardingTemplateId == onboardingTemplate.Id)
- .OrderBy(item => item.SortCode)
- .ToListAsync();
var materialConfigs = await _db.Queryable()
.Where(item => item.OnboardingTemplateId == onboardingTemplate.Id)
.OrderBy(item => item.SortCode)
@@ -73,14 +69,12 @@ public class OnboardingTaskService : IOnboardingTaskService
if (enabledVersionCount != versionIds.Count)
throw Oops.Bah("入职模板包含未启用的合同版本,请先调整模板配置");
- var fieldIds = fieldConfigs.Select(item => item.FieldId).Distinct().ToList();
- var fields = fieldIds.Count == 0
- ? new List()
- : await _db.Queryable().Where(item => fieldIds.Contains(item.Id)).ToListAsync();
var materialIds = materialConfigs.Select(item => item.MaterialTypeId).Distinct().ToList();
var materialTypes = materialIds.Count == 0
? new List()
: await _db.Queryable().Where(item => materialIds.Contains(item.Id)).ToListAsync();
+ if (materialTypes.Count != materialIds.Count)
+ throw Oops.Bah("入职模板包含不存在的材料类型,请先调整模板配置");
var expiryConfig = await _configService.GetByConfigKey(
CateGoryConst.CONFIG_BIZ_DEFINE,
@@ -108,6 +102,8 @@ public class OnboardingTaskService : IOnboardingTaskService
Remark = input.Remark
};
+ ValidateInitialContractDateRanges(input.ContractVariables);
+
await _db.Ado.BeginTranAsync();
try
{
@@ -121,6 +117,15 @@ public class OnboardingTaskService : IOnboardingTaskService
ExpiresAt = expiresAt
}).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 template = contractTemplates.First(item => item.Id == config.ContractTemplateId);
@@ -138,27 +143,6 @@ public class OnboardingTaskService : IOnboardingTaskService
}).ToList();
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)
{
var materialSnapshots = materialConfigs.Select(config =>
@@ -212,8 +196,12 @@ public class OnboardingTaskService : IOnboardingTaskService
var definitions = await _db.Queryable()
.Where(item => versionIds.Contains(item.TemplateVersionId)
&& item.HrEditable
- && (item.VariableType == ContractVariableType.Contract
- || item.VariableType == ContractVariableType.Company))
+ && (item.VariableType == "hr"
+ || item.VariableType == "contract"
+ || item.VariableType == "company"
+ || item.VariableType == ContractVariableType.Contract
+ || item.VariableType == ContractVariableType.Company
+ || item.VariableCode.StartsWith("hr.")))
.ToListAsync();
var duplicateInput = inputs
@@ -268,4 +256,42 @@ public class OnboardingTaskService : IOnboardingTaskService
if (changedRows.Count > 0)
await _db.Updateable(changedRows).ExecuteCommandAsync();
}
+
+ ///
+ /// 校验人事创建任务时填写的合同日期先后关系。
+ ///
+ private static void ValidateInitialContractDateRanges(
+ List inputs)
+ {
+ inputs ??= new List();
+ foreach (var group in inputs.GroupBy(item => item.ContractTemplateVersionId))
+ {
+ ValidateDateRange(group, "hr.ContractStartDate", "hr.ContractEndDate", "合同");
+ ValidateDateRange(group, "hr.ProbationStartDate", "hr.ProbationEndDate", "试用期");
+ }
+ }
+
+ ///
+ /// 校验同一合同版本内一组开始、结束日期。
+ ///
+ private static void ValidateDateRange(
+ IEnumerable 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}结束日期不能早于开始日期");
+ }
}
diff --git a/SimpleAdmin.Application/Services/Onboarding/VariableDefinition/IOnboardingVariableDefinitionService.cs b/SimpleAdmin.Application/Services/Onboarding/VariableDefinition/IOnboardingVariableDefinitionService.cs
new file mode 100644
index 0000000..b3e846d
--- /dev/null
+++ b/SimpleAdmin.Application/Services/Onboarding/VariableDefinition/IOnboardingVariableDefinitionService.cs
@@ -0,0 +1,10 @@
+namespace SimpleAdmin.Application;
+
+public interface IOnboardingVariableDefinitionService : ITransient
+{
+ Task> Page(OnboardingVariableDefinitionPageInput input);
+ Task> ListAll();
+ Task Add(OnboardingVariableDefinitionAddInput input);
+ Task Edit(OnboardingVariableDefinitionEditInput input);
+ Task Delete(BaseIdListInput input);
+}
\ No newline at end of file
diff --git a/SimpleAdmin.Application/Services/Onboarding/VariableDefinition/OnboardingVariableDefinitionInput.cs b/SimpleAdmin.Application/Services/Onboarding/VariableDefinition/OnboardingVariableDefinitionInput.cs
new file mode 100644
index 0000000..52fa613
--- /dev/null
+++ b/SimpleAdmin.Application/Services/Onboarding/VariableDefinition/OnboardingVariableDefinitionInput.cs
@@ -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; }
+}
\ No newline at end of file
diff --git a/SimpleAdmin.Application/Services/Onboarding/VariableDefinition/OnboardingVariableDefinitionService.cs b/SimpleAdmin.Application/Services/Onboarding/VariableDefinition/OnboardingVariableDefinitionService.cs
new file mode 100644
index 0000000..cdd43a2
--- /dev/null
+++ b/SimpleAdmin.Application/Services/Onboarding/VariableDefinition/OnboardingVariableDefinitionService.cs
@@ -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> Page(OnboardingVariableDefinitionPageInput input)
+ {
+ return await _db.Queryable()
+ .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> ListAll()
+ {
+ return await _db.Queryable()
+ .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().AnyAsync(it => it.VariableCode == code);
+ if (exists)
+ throw Oops.Bah($"变量编码“{code}”已存在");
+
+ var entity = input.Adapt();
+ entity.VariableCode = code;
+ entity.IsBuiltIn = false;
+ await _db.Insertable(entity).ExecuteCommandAsync();
+ }
+
+ public async Task Edit(OnboardingVariableDefinitionEditInput input)
+ {
+ var entity = await _db.Queryable().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()
+ .Where(it => input.Ids.Contains(it.Id) && it.IsBuiltIn)
+ .AnyAsync();
+ if (hasBuiltIn)
+ throw Oops.Bah("系统内置的标准入职登记表变量不能删除");
+
+ await _db.Deleteable()
+ .Where(it => input.Ids.Contains(it.Id))
+ .ExecuteCommandAsync();
+ }
+}
\ No newline at end of file
diff --git a/SimpleAdmin.Application/SimpleAdmin.Application.xml b/SimpleAdmin.Application/SimpleAdmin.Application.xml
index fcbb97d..4f6e0f9 100644
--- a/SimpleAdmin.Application/SimpleAdmin.Application.xml
+++ b/SimpleAdmin.Application/SimpleAdmin.Application.xml
@@ -886,7 +886,7 @@
- 候选人在入职任务中提交的固定人员基础信息。
+ 候选人在入职任务中提交的《入职登记表》标准员工档案信息。
@@ -896,12 +896,7 @@
- 员工编号。
-
-
-
-
- 内部人员编号。
+ 员工编号(选填/人事分配)。
@@ -909,29 +904,44 @@
员工姓名。
-
-
- 所属部门名称。
-
-
性别。
-
+
- 手机号码。
+ 民族。
-
+
- 身份证号码。
+ 出生年月日。
-
+
- 毕业院校。
+ 户籍所在地。
+
+
+
+
+ 身高 (cm)。
+
+
+
+
+ 体重 (kg)。
+
+
+
+
+ 健康状况。
+
+
+
+
+ 婚育情况。
@@ -944,64 +954,49 @@
所学专业。
-
+
- 出生日期。
+ 毕业院校。
-
+
- 户籍所在地。
+ 职业资格。
-
+
- 民族。
+ 资格等级。
-
+
- 现居住或联系地址。
+ 有何技术特长。
-
+
- 入职日期。
+ 联系电话/手机号。
-
+
- 合同开始日期。
+ 电子邮箱。
-
+
- 合同结束日期。
+ 身份证号码。
-
+
- 试用期开始日期。
+ 暂住地址 / 现居住地址。
-
+
- 试用期结束日期。
-
-
-
-
- 转正日期。
-
-
-
-
- 社保缴纳公司。
-
-
-
-
- 合同性质。
+ 房东联系方式。
@@ -1009,19 +1004,69 @@
工资银行卡号。
-
-
- 银行卡开户名。
-
-
开户银行名称。
-
+
- 业务备注。
+ 银行卡开户人姓名。
+
+
+
+
+ 紧急联系人姓名。
+
+
+
+
+ 紧急联系人电话。
+
+
+
+
+ 紧急联系人联系地址。
+
+
+
+
+ 教育学习经历 JSON 列表: [ { timeRange, location, content, result } ]
+
+
+
+
+ 主要工作经历 JSON 列表: [ { timeRange, companyName, salary, position, leaveReason } ]
+
+
+
+
+ 家庭成员 JSON 列表: [ { name, relationship, age, company, phone } ]
+
+
+
+
+ 入职部门。
+
+
+
+
+ 入职时间。
+
+
+
+
+ 入职岗位。
+
+
+
+
+ 其他说明。
+
+
+
+
+ 员工补充自定义变量键值 JSON: { "VariableName": "Value" }
@@ -1354,6 +1399,57 @@
业务备注。
+
+
+ 全局合同变量字典定义表。
+ 包含标准入职登记表变量、人事合同变量及企业自定义变量。
+
+
+
+
+ 变量全编码,如 employee.EmployeeName, hr.BaseSalary, company.CompanyName, system.TaskNo。
+
+
+
+
+ 变量中文显示名称,如“员工姓名”、“基本工资”。
+
+
+
+
+ 变量作用域分类:employee(员工变量), hr(人事参数), company(公司变量), system(系统变量)。
+
+
+
+
+ 数据类型:string, number, date, multiline。
+
+
+
+
+ 默认值或占位提示。
+
+
+
+
+ 是否为入职登记表内置字段(内置字段不可删,且第1步自动收集)。
+
+
+
+
+ 是否必填。
+
+
+
+
+ 排序码。
+
+
+
+
+ 变量说明/备注。
+
+
入职任务状态。
@@ -1629,6 +1725,11 @@
退回入职任务,保存审核意见并允许候选人补充资料。
+
+
+ 将登记表中的日期文本转换为正式员工档案日期。
+
+
入职业务公司分页查询参数。
@@ -2926,6 +3027,21 @@
系统文件 Id。
+
+
+ 删除员工在当前可编辑任务中上传的一条材料。
+
+
+
+
+ 员工填写链接中的明文 Token。
+
+
+
+
+ 任务材料记录 Id。
+
+
员工确认阅读合同参数。
@@ -2966,6 +3082,11 @@
关联候选人已经上传的系统文件与材料类型。
+
+
+ 删除可编辑任务中的一条已上传材料关联。
+
+
记录候选人已阅读并确认指定任务合同。
@@ -2996,6 +3117,11 @@
关联已上传 OSS 文件与任务材料要求。
+
+
+ 任务仍可编辑时删除一条已上传材料。
+
+
记录候选人已经查看并确认动态生成的合同。
@@ -3038,7 +3164,7 @@
- 通过有效 Token 获取任务创建时冻结的字段、材料和合同信息。
+ 通过有效 Token 获取任务创建时冻结的材料、合同和新版入职登记表信息。
@@ -3456,6 +3582,46 @@
是否必填、必传或必签。
+
+
+ 员工端需要填写的合同员工变量。
+
+
+
+
+ 合同变量编码。
+
+
+
+
+ 合同变量显示名称。
+
+
+
+
+ 变量来源类型。
+
+
+
+
+ 变量数据类型。
+
+
+
+
+ 变量默认值。
+
+
+
+
+ 选择型变量的可选项 JSON。
+
+
+
+
+ 是否必填。
+
+
员工端材料上传要求。
@@ -3526,6 +3692,11 @@
候选人已上传材料列表。
+
+
+ 合同中由员工填写的扩展变量。
+
+
定义入职任务创建业务能力。
@@ -3543,7 +3714,7 @@
- 校验公司、岗位和入职模板后创建任务,并冻结合同、补充字段及材料要求。
+ 校验公司、岗位和入职模板后创建任务,并冻结合同及材料要求。
@@ -3551,6 +3722,16 @@
校验并保存任务创建时填写的人事合同变量。
+
+
+ 校验人事创建任务时填写的合同日期先后关系。
+
+
+
+
+ 校验同一合同版本内一组开始、结束日期。
+
+
机构服务
diff --git a/SimpleAdmin.Web.Core/Controllers/Application/Onboarding/OnboardingPublicSubmissionController.cs b/SimpleAdmin.Web.Core/Controllers/Application/Onboarding/OnboardingPublicSubmissionController.cs
index 692d081..873f916 100644
--- a/SimpleAdmin.Web.Core/Controllers/Application/Onboarding/OnboardingPublicSubmissionController.cs
+++ b/SimpleAdmin.Web.Core/Controllers/Application/Onboarding/OnboardingPublicSubmissionController.cs
@@ -35,6 +35,16 @@ public class OnboardingPublicSubmissionController
await _service.SaveMaterial(input);
}
+ ///
+ /// 删除当前入职任务中的一条已上传材料。
+ ///
+ [HttpPost("delete-material")]
+ public async Task DeleteMaterial(
+ [FromBody] OnboardingDeleteMaterialInput input)
+ {
+ await _service.DeleteMaterial(input);
+ }
+
///
/// 确认已阅读指定合同。
///
diff --git a/SimpleAdmin.Web.Core/Controllers/Application/Onboarding/OnboardingVariableDefinitionController.cs b/SimpleAdmin.Web.Core/Controllers/Application/Onboarding/OnboardingVariableDefinitionController.cs
new file mode 100644
index 0000000..fcfb069
--- /dev/null
+++ b/SimpleAdmin.Web.Core/Controllers/Application/Onboarding/OnboardingVariableDefinitionController.cs
@@ -0,0 +1,47 @@
+namespace SimpleAdmin.Web.Core;
+
+///
+/// 合同变量字典定义管理。
+///
+[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 Page([FromQuery] OnboardingVariableDefinitionPageInput input)
+ {
+ return await _service.Page(input);
+ }
+
+ [HttpGet("list")]
+ public async Task 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);
+ }
+}
\ No newline at end of file
diff --git a/SimpleAdmin.Web.Core/SimpleAdmin.Web.Core.xml b/SimpleAdmin.Web.Core/SimpleAdmin.Web.Core.xml
index bd5baa1..018d789 100644
--- a/SimpleAdmin.Web.Core/SimpleAdmin.Web.Core.xml
+++ b/SimpleAdmin.Web.Core/SimpleAdmin.Web.Core.xml
@@ -261,6 +261,11 @@
保存员工已上传材料记录。
+
+
+ 删除当前入职任务中的一条已上传材料。
+
+
确认已阅读指定合同。
@@ -386,6 +391,11 @@
查询指定公司岗位可使用的入职模板。
+
+
+ 合同变量字典定义管理。
+
+
机构管理控制器
diff --git a/database/migrations/20260825_full_schema_rebuild.sql b/database/migrations/20260825_full_schema_rebuild.sql
new file mode 100644
index 0000000..7b725c8
--- /dev/null
+++ b/database/migrations/20260825_full_schema_rebuild.sql
@@ -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;
\ No newline at end of file
diff --git a/database/migrations/20260825_onboarding_schema_alignment_fix.sql b/database/migrations/20260825_onboarding_schema_alignment_fix.sql
new file mode 100644
index 0000000..f7c5b71
--- /dev/null
+++ b/database/migrations/20260825_onboarding_schema_alignment_fix.sql
@@ -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;