using System.Globalization; namespace SimpleAdmin.Application; /// /// 在正式员工建档时,将每份任务合同及其全部变量冻结为长期业务快照。 /// public class OnboardingEmployeeContractSnapshotService : IOnboardingEmployeeContractSnapshotService { private readonly SqlSugarScope _db; public OnboardingEmployeeContractSnapshotService() { _db = DbContext.DB; } /// public async Task Build(long employeeId, long taskId) { var contracts = await _db.Queryable() .Where(item => item.TaskId == taskId) .OrderBy(item => item.Id) .ToListAsync(); if (contracts.Count == 0) throw Oops.Bah("入职任务没有合同,无法生成员工合同快照"); var contractIds = contracts.Select(item => item.Id).ToList(); var allVariables = await _db.Queryable() .Where(item => contractIds.Contains(item.TaskContractId)) .OrderBy(item => item.SortCode) .OrderBy(item => item.Id) .ToListAsync(); var now = DateTime.Now; var snapshots = new List(); foreach (var contract in contracts) { var variables = allVariables .Where(item => item.TaskContractId == contract.Id) .OrderBy(item => item.SortCode) .ThenBy(item => item.Id) .ToList(); var valueMap = variables .GroupBy(item => item.VariableCode, StringComparer.OrdinalIgnoreCase) .ToDictionary( group => group.Key, group => group.Last().VariableValue, StringComparer.OrdinalIgnoreCase); var variableSnapshot = variables .GroupBy(item => item.VariableCode, StringComparer.OrdinalIgnoreCase) .ToDictionary( group => group.Key, group => { // 历史任务若意外存在重复变量,以排序后的最后一条最终值为准,避免建档时报 500。 var item = group.Last(); return (object)new { variableName = item.VariableName, variableType = item.VariableType, dataType = item.DataType, variableValue = item.VariableValue, sourceType = item.SourceType, isManualOverride = item.IsManualOverride, sortCode = item.SortCode }; }, StringComparer.OrdinalIgnoreCase); snapshots.Add(new ObEmployeeContractSnapshot { Id = global::SimpleAdmin.Core.Utils.CommonUtils.GetSingleId(), EmployeeId = employeeId, TaskId = taskId, TaskContractId = contract.Id, TemplateId = contract.TemplateId, TemplateVersionId = contract.TemplateVersionId, ContractName = contract.ContractName, ContractType = contract.ContractType, ContractStartDate = ParseDate(GetValue(valueMap, "hr.ContractStartDate")), ContractEndDate = ParseDate(GetValue(valueMap, "hr.ContractEndDate")), ContractTerm = GetValue(valueMap, "hr.ContractTerm"), ProbationStartDate = ParseDate(GetValue(valueMap, "hr.ProbationStartDate")), ProbationEndDate = ParseDate(GetValue(valueMap, "hr.ProbationEndDate")), ProbationTerm = GetValue(valueMap, "hr.ProbationTerm"), ProbationSalary = ParseAmount(GetValue(valueMap, "hr.ProbationSalary")), BaseSalary = ParseAmount(GetValue(valueMap, "hr.BaseSalary")), PositionSalary = ParseAmount(GetValue(valueMap, "hr.PositionSalary")), PerformanceAllowance = ParseAmount(GetValue(valueMap, "hr.PerformanceAllowance")), AttendanceBonus = ParseAmount(GetValue(valueMap, "hr.AttendanceBonus")), RegularSalary = ParseAmount(GetValue(valueMap, "hr.RegularSalary")), WorkCity = GetValue(valueMap, "hr.WorkCity"), PositionName = GetValue(valueMap, "hr.PositionName"), EmployeeNo = GetValue(valueMap, "hr.EmployeeNo"), VariablesJson = global::System.Text.Json.JsonSerializer.Serialize(variableSnapshot), SnapshotVersion = 1, SnapshotAt = now, Status = CommonStatusConst.ENABLE, CreateTime = now, CreateUserId = UserManager.UserId, CreateUser = UserManager.Name, IsDelete = false }); } // Build 只在审核建档事务内执行;先删后建使异常重试保持幂等。 await _db.Deleteable() .Where(item => item.TaskId == taskId) .ExecuteCommandAsync(); await _db.Insertable(snapshots).ExecuteCommandAsync(); } private static string GetValue( IReadOnlyDictionary valueMap, string variableCode) { return valueMap.TryGetValue(variableCode, out var value) && !string.IsNullOrWhiteSpace(value) ? value.Trim() : null; } private static DateTime? ParseDate(string value) { return DateTime.TryParse(value, out var date) ? date.Date : null; } private static decimal? ParseAmount(string value) { if (string.IsNullOrWhiteSpace(value)) return null; var normalized = value .Replace("¥", string.Empty) .Replace("¥", string.Empty) .Replace("元", string.Empty) .Trim(); if (decimal.TryParse( normalized, NumberStyles.Number | NumberStyles.AllowCurrencySymbol, CultureInfo.GetCultureInfo("zh-CN"), out var amount) || decimal.TryParse( normalized, NumberStyles.Number, CultureInfo.InvariantCulture, out amount)) { return amount; } return null; } }