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;