namespace SimpleAdmin.Application; /// 冻结任务合同变量;DOCX 生成阶段只读取本快照,不再读取模板实时值。 /// /// 实现任务合同变量快照业务能力。 /// public class OnboardingContractSnapshotService : IOnboardingContractSnapshotService { private readonly SqlSugarScope _db; public OnboardingContractSnapshotService() => _db = DbContext.DB; /// /// 根据任务、公司和候选人资料构建或刷新合同变量快照。 /// public async Task Build(long taskId, ObTaskEmployeeInfo employeeInfo = null) { var task = await _db.Queryable().InSingleAsync(taskId) ?? throw Oops.Bah("入职任务不存在"); var company = await _db.Queryable().InSingleAsync(task.CompanyId) ?? throw Oops.Bah("入职公司不存在"); employeeInfo ??= await _db.Queryable().FirstAsync(x => x.TaskId == taskId); var contracts = await _db.Queryable().Where(x => x.TaskId == taskId).ToListAsync(); foreach (var contract in contracts) { if (contract.GeneratedDocxFileId.HasValue || contract.GeneratedPdfFileId.HasValue) throw Oops.Bah("合同已生成,不能重建变量快照"); var definitions = await _db.Queryable().Where(x => x.TemplateVersionId == contract.TemplateVersionId).OrderBy(x => x.SortCode).ToListAsync(); var oldRows = await _db.Queryable().Where(x => x.TaskContractId == contract.Id).ToListAsync(); var rows = definitions.Select(definition => { var old = oldRows.FirstOrDefault(x => x.VariableCode == definition.VariableCode); // 人事手工填写的工资、补贴等合同参数优先级最高,刷新自动变量时不能覆盖。 var preserveManual = old?.IsManualOverride == true; return new ObTaskContractVariable { Id = old?.Id ?? global::SimpleAdmin.Core.Utils.CommonUtils.GetSingleId(), TaskContractId = contract.Id, VariableCode = definition.VariableCode, VariableName = definition.VariableName, VariableType = definition.VariableType, DataType = definition.DataType, VariableValue = preserveManual ? old.VariableValue : ResolveValue(definition, employeeInfo, company, task), SourceType = preserveManual ? ContractVariableSourceType.HrInput : GetSourceType(definition.VariableType), IsManualOverride = preserveManual }; }).ToList(); await _db.Deleteable().Where(x => x.TaskContractId == contract.Id).ExecuteCommandAsync(); if (rows.Count > 0) await _db.Insertable(rows).ExecuteCommandAsync(); } } private static string GetSourceType(string variableType) => variableType switch { ContractVariableType.Employee => ContractVariableSourceType.Employee, ContractVariableType.Company => ContractVariableSourceType.Company, ContractVariableType.System => ContractVariableSourceType.System, _ => ContractVariableSourceType.HrInput }; private static string ResolveValue(ObTemplateVariable definition, ObTaskEmployeeInfo info, ObCompany company, ObOnboardingTask task) { if (definition.VariableType == ContractVariableType.Employee) return GetPropertyValue(info, definition.EmployeeField) ?? definition.DefaultValue; if (definition.VariableType == ContractVariableType.Company) return GetPropertyValue(company, ResolveCompanyField(definition)) ?? definition.DefaultValue; if (definition.VariableType == ContractVariableType.System && string.Equals(definition.VariableCode, "TASK_NO", StringComparison.OrdinalIgnoreCase)) return task.TaskNo; return definition.DefaultValue; } private static string GetPropertyValue(object source, string propertyName) { if (source == null || string.IsNullOrWhiteSpace(propertyName)) return null; return source.GetType().GetProperty(propertyName, global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.Instance | global::System.Reflection.BindingFlags.IgnoreCase)?.GetValue(source)?.ToString(); } private static string ResolveCompanyField(ObTemplateVariable definition) { if (!string.IsNullOrWhiteSpace(definition.EmployeeField)) return definition.EmployeeField; return definition.VariableCode.ToUpperInvariant() switch { "COMPANY_CODE" => nameof(ObCompany.CompanyCode), "COMPANY_NAME" => nameof(ObCompany.CompanyName), "LEGAL_NAME" or "COMPANY_LEGAL_NAME" => nameof(ObCompany.LegalName), "UNIFIED_SOCIAL_CREDIT_CODE" => nameof(ObCompany.UnifiedSocialCreditCode), "LEGAL_REPRESENTATIVE" => nameof(ObCompany.LegalRepresentative), "REGISTERED_ADDRESS" => nameof(ObCompany.RegisteredAddress), "CONTACT_PHONE" => nameof(ObCompany.ContactPhone), _ => definition.VariableCode }; } }