namespace SimpleAdmin.Application; /// /// 实现正式员工档案查询业务能力,仅展示审核通过后创建的正式员工。 /// public class OnboardingEmployeeService : IOnboardingEmployeeService { private readonly SqlSugarScope _db; public OnboardingEmployeeService() { _db = DbContext.DB; } /// /// 分页查询审核通过后生成的正式员工档案。 /// public async Task> Page(OnboardingEmployeePageInput input) { var result = await _db.Queryable() .WhereIF( !string.IsNullOrWhiteSpace(input.EmployeeName), item => item.EmployeeName.Contains(input.EmployeeName) || item.IdCardNumber.Contains(input.EmployeeName)) .WhereIF(!string.IsNullOrWhiteSpace(input.Phone), item => item.Phone.Contains(input.Phone)) .WhereIF(input.CompanyId.HasValue, item => item.CompanyId == input.CompanyId.Value) .WhereIF(input.DepartmentId.HasValue, item => item.DepartmentId == input.DepartmentId.Value) .WhereIF(input.PositionId.HasValue, item => item.PositionId == input.PositionId.Value) .OrderByDescending(item => item.CreateTime) .ToPagedListAsync(input.PageNum, input.PageSize); if (result.List != null && result.List.Any()) await FillEmployeeMetadata(result.List); return result; } /// /// 查询正式员工及其来源任务、合同和审核记录。 /// public async Task Detail(BaseIdInput input) { var employee = await _db.Queryable().InSingleAsync(input.Id) ?? throw Oops.Bah("正式员工不存在"); var task = await _db.Queryable().FirstAsync(x => x.EmployeeId == employee.Id); await FillEmployeeMetadata(new[] { employee }); var employeeInfo = task == null ? null : await _db.Queryable() .FirstAsync(item => item.TaskId == task.Id); if (employeeInfo == null && !string.IsNullOrWhiteSpace(employee.ExtraInfoJson)) { try { employeeInfo = global::System.Text.Json.JsonSerializer.Deserialize( employee.ExtraInfoJson); } catch (global::System.Text.Json.JsonException) { // 兼容历史异常快照:员工主档仍可正常查看,其余信息显示为空。 } } var contracts = task == null ? new List() : await _db.Queryable() .Where(item => item.TaskId == task.Id) .ToListAsync(); var materials = await GetEmployeeMaterials(task?.Id); var electronicSignProcess = task == null ? null : await _db.Queryable() .Where(item => item.TaskId == task.Id && item.ArchiveFileId.HasValue) .OrderByDescending(item => item.Id) .FirstAsync(); var audits = task == null ? new List() : await _db.Queryable() .Where(item => item.TaskId == task.Id) .OrderByDescending(item => item.AuditedAt) .ToListAsync(); return new OnboardingEmployeeDetailOutput { Employee = employee, Task = task, EmployeeInfo = employeeInfo, Contracts = contracts, Materials = materials, ElectronicSignArchiveFileId = electronicSignProcess?.ArchiveFileId, Audits = audits }; } /// /// 查询员工来源任务中上传的全部材料,并补充原始文件名、后缀和文件大小。 /// private async Task> GetEmployeeMaterials(long? taskId) { if (!taskId.HasValue) return new List(); var materials = await _db.Queryable() .Where(item => item.TaskId == taskId.Value) .ToListAsync(); if (materials.Count == 0) return new List(); var fileIds = materials .Select(item => item.FileId) .Distinct() .ToList(); var files = await _db.Queryable() .Where(item => fileIds.Contains(item.Id)) .ToListAsync(); var fileMap = files.ToDictionary(item => item.Id); var requirements = await _db.Queryable() .Where(item => item.TaskId == taskId.Value) .ToListAsync(); var sortMap = requirements .GroupBy(item => item.MaterialTypeId) .ToDictionary(group => group.Key, group => group.Min(item => item.SortCode)); return materials .Select(material => { var output = material.Adapt(); if (fileMap.TryGetValue(material.FileId, out var file)) { output.FileName = file.Name; output.FileSuffix = file.Suffix; output.FileSizeInfo = file.SizeInfo; } return output; }) .OrderBy(item => sortMap.GetValueOrDefault(item.MaterialTypeId, int.MaxValue)) .ThenBy(item => item.UploadedAt) .ToList(); } /// /// 批量补充员工所属公司、部门和岗位名称,避免列表逐行查询数据库。 /// private async Task FillEmployeeMetadata(IEnumerable employees) { var employeeList = employees.ToList(); if (employeeList.Count == 0) return; var companyIds = employeeList.Select(item => item.CompanyId).Distinct().ToList(); var departmentIds = employeeList .Where(item => item.DepartmentId.HasValue) .Select(item => item.DepartmentId.Value) .Distinct() .ToList(); var positionIds = employeeList.Select(item => item.PositionId).Distinct().ToList(); var companies = await _db.Queryable() .Where(item => companyIds.Contains(item.Id)) .ToListAsync(); var departments = departmentIds.Count == 0 ? new List() : await _db.Queryable() .Where(item => departmentIds.Contains(item.Id)) .ToListAsync(); var positions = await _db.Queryable() .Where(item => positionIds.Contains(item.Id)) .ToListAsync(); var companyMap = companies.ToDictionary(item => item.Id, item => item.CompanyName); var departmentMap = departments.ToDictionary(item => item.Id, item => item.DepartmentName); var positionMap = positions.ToDictionary(item => item.Id, item => item.PositionName); foreach (var employee in employeeList) { if (companyMap.TryGetValue(employee.CompanyId, out var companyName)) employee.CompanyName = companyName; if (employee.DepartmentId.HasValue && departmentMap.TryGetValue(employee.DepartmentId.Value, out var departmentName)) { employee.DepartmentName = departmentName; } if (positionMap.TryGetValue(employee.PositionId, out var positionName)) employee.PositionName = positionName; } await FillMissingEntryDates(employeeList); } /// /// 兼容已建档但入职日期为空的历史员工,从来源任务的合同开始日期进行只读回填。 /// 新审核通过的员工会直接持久化该日期,不会进入此兼容分支。 /// private async Task FillMissingEntryDates(List employees) { var missingEmployees = employees .Where(item => !item.EntryDate.HasValue || !item.ContractStartDate.HasValue) .ToList(); if (missingEmployees.Count == 0) return; var employeeIds = missingEmployees.Select(item => item.Id).ToList(); var tasks = await _db.Queryable() .Where(item => item.EmployeeId.HasValue && employeeIds.Contains(item.EmployeeId.Value)) .ToListAsync(); if (tasks.Count == 0) return; var taskIds = tasks.Select(item => item.Id).ToList(); var contracts = await _db.Queryable() .Where(item => taskIds.Contains(item.TaskId)) .ToListAsync(); if (contracts.Count == 0) return; var contractIds = contracts.Select(item => item.Id).ToList(); var startDateVariables = await _db.Queryable() .Where(item => contractIds.Contains(item.TaskContractId) && item.VariableCode == "hr.ContractStartDate") .ToListAsync(); var employeeIdByTaskId = tasks .Where(item => item.EmployeeId.HasValue) .ToDictionary(item => item.Id, item => item.EmployeeId.Value); var taskIdByContractId = contracts.ToDictionary(item => item.Id, item => item.TaskId); var datesByEmployeeId = startDateVariables .Where(item => !string.IsNullOrWhiteSpace(item.VariableValue) && taskIdByContractId.ContainsKey(item.TaskContractId)) .Select(item => new { EmployeeId = employeeIdByTaskId[taskIdByContractId[item.TaskContractId]], EntryDate = DateTime.TryParse(item.VariableValue, out var value) ? value.Date : (DateTime?)null }) .Where(item => item.EntryDate.HasValue) .GroupBy(item => item.EmployeeId) .ToDictionary( group => group.Key, group => group.Select(item => item.EntryDate.Value).Distinct().ToList()); foreach (var employee in missingEmployees) { if (!datesByEmployeeId.TryGetValue(employee.Id, out var dates) || dates.Count != 1) continue; employee.EntryDate ??= dates[0]; employee.ContractStartDate ??= dates[0]; } } }