using System.Globalization; using System.Text.Json; using System.Text.Json.Serialization; using FreeSql; using F9WebAiTest.Domain.Entities.SYS.Departments; using F9WebAiTest.Domain.Entities.SYS.Employees; using F9WebAiTest.Domain.Enums.SYS.Employees; namespace F9WebAiTest.Infrastructure.Data; /// /// 职工数据导入:来源于 F9 系统 biz_base_employee / biz_base_employee_Change_record / /// biz_base_employee_tiaogang_record 三张表的导出数据。 /// 幂等:按原始 Id 判重;F9 部门 Id 通过"部门名称"映射到本系统部门(工种 Id 与 F9 一致,直接沿用)。 /// public static class EmployeeSeedData { private const string EmployeeResource = "F9WebAiTest.Infrastructure.Data.SeedData.biz_base_employee.json"; private const string ChangeResource = "F9WebAiTest.Infrastructure.Data.SeedData.biz_base_employee_Change_record.json"; private const string TiaogangResource = "F9WebAiTest.Infrastructure.Data.SeedData.biz_base_employee_tiaogang_record.json"; private static readonly JsonSerializerOptions JsonOptions = new() { PropertyNameCaseInsensitive = true }; public static void Seed(IFreeSql fsql) { try { var depMap = BuildDepartmentIdMap(fsql); SeedEmployees(fsql, depMap); SeedChangeRecords(fsql); SeedTiaogangRecords(fsql, depMap); } catch (Exception ex) { Console.WriteLine($"[EmployeeSeed] 职工数据导入跳过:{ex.Message}"); } } /// 导入职工档案 private static void SeedEmployees(IFreeSql fsql, Dictionary depMap) { var items = Load(EmployeeResource); if (items.Count == 0) return; var existIds = fsql.Select().DisableGlobalFilter("软删除").ToList().Select(x => x.Id).ToHashSet(); var inserts = new List(); foreach (var item in items) { if (item.Id <= 0 || existIds.Contains(item.Id)) continue; if (string.IsNullOrWhiteSpace(item.BaseName)) continue; var depId = depMap.TryGetValue(item.BaseDepId, out var mapped) ? mapped : item.BaseDepId; inserts.Add(new Employee(item.Id, depId, item.BaseWorktypeId, (Employee_WorkStatus_Enum)item.BaseWorkStatus, item.BaseCode ?? string.Empty, item.BaseName.Trim(), item.BaseGangwei ?? string.Empty, (item.BasePhone ?? string.Empty).Trim(), (item.BaseIdCard ?? string.Empty).Trim(), ParseLegacyDate(item.BaseBirnDate), item.BaseAddress ?? string.Empty, (item.BaseGender ?? string.Empty).Trim(), (item.BaseMinzu ?? string.Empty).Trim(), item.BaseHunyinStatus ?? string.Empty, item.BaseHuji ?? string.Empty, ParseLegacyDate(item.BaseRuzhiDate), ParseLegacyDate(item.BaseLizhiDate), ParseLegacyDate(item.BaseZhuanzhengDate), ParseBool(item.BaseIsZhuanzheng), item.BaseJinjiLianxiren ?? string.Empty, item.BaseJinjiLianxirenPhone ?? string.Empty, item.BaseHetongType ?? string.Empty, ParseLegacyDate(item.BaseLaodongHetongStartDate), ParseLegacyDate(item.BaseLaodongHetongEndDate), ParseLegacyDate(item.BaseTuixiuDate), ParseLegacyDate(item.BaseShebaoCanbaoDate), item.BaseShebaocanbaoDesc ?? string.Empty, (item.BaseBanckcardCode ?? string.Empty).Trim(), item.KzBiyeYuanxiao ?? string.Empty, item.KbZhuanye ?? string.Empty, item.KbXueli ?? string.Empty, item.ScBanci.HasValue ? (Prod_Banci_Enum)item.ScBanci.Value : null, item.ScWorkGroup ?? string.Empty, item.ScSort, item.NamePinyin ?? string.Empty, item.Memo ?? string.Empty, item.BaseLizhiYuanyin ?? string.Empty) { CreatedAt = ParseLegacyDate(item.CreatedAt) ?? DateTime.Now, CreatedBy = item.CreatedBy ?? string.Empty, UpdatedAt = ParseLegacyDate(item.UpdatedAt), UpdateBy = item.UpdateBy ?? string.Empty }); } foreach (var batch in inserts.Chunk(200)) fsql.Insert(batch.ToList()).ExecuteAffrows(); Console.WriteLine($"[EmployeeSeed] 职工档案:来源 {items.Count} 条,新增 {inserts.Count} 条"); } /// 导入变更记录 private static void SeedChangeRecords(IFreeSql fsql) { var items = Load(ChangeResource); if (items.Count == 0) return; var existIds = fsql.Select().DisableGlobalFilter("软删除").ToList().Select(x => x.Id).ToHashSet(); var employeeIds = fsql.Select().DisableGlobalFilter("软删除").ToList().Select(x => x.Id).ToHashSet(); var inserts = new List(); var orphan = 0; foreach (var item in items) { if (item.Id <= 0 || existIds.Contains(item.Id)) continue; if (!employeeIds.Contains(item.EmployeeId)) { orphan++; continue; } inserts.Add(new EmployeeChangeRecord(item.Id, item.EmployeeId, ParseLegacyDate(item.Date) ?? DateTime.Now, (Employee_ChangeStatus_Enum)item.ChangeStatus, item.ChangeShuoming ?? string.Empty, item.ChangeYuanyin ?? string.Empty) { CreatedAt = ParseLegacyDate(item.CreatedAt) ?? DateTime.Now, CreatedBy = item.CreatedBy ?? string.Empty }); } foreach (var batch in inserts.Chunk(200)) fsql.Insert(batch.ToList()).ExecuteAffrows(); Console.WriteLine($"[EmployeeSeed] 变更记录:来源 {items.Count} 条,新增 {inserts.Count} 条,孤儿 {orphan} 条"); } /// 导入调岗记录 private static void SeedTiaogangRecords(IFreeSql fsql, Dictionary depMap) { var items = Load(TiaogangResource); if (items.Count == 0) return; var existIds = fsql.Select().DisableGlobalFilter("软删除").ToList().Select(x => x.Id).ToHashSet(); var employeeIds = fsql.Select().DisableGlobalFilter("软删除").ToList().Select(x => x.Id).ToHashSet(); var inserts = new List(); var orphan = 0; foreach (var item in items) { if (item.Id <= 0 || existIds.Contains(item.Id)) continue; if (!employeeIds.Contains(item.EmployeeId)) { orphan++; continue; } inserts.Add(new EmployeeTiaogangRecord(item.Id, item.EmployeeId, depMap.TryGetValue(item.OldDepId, out var od) ? od : item.OldDepId, item.OldWorktypeId, depMap.TryGetValue(item.NewDepId, out var nd) ? nd : item.NewDepId, item.NewWorktypeId, ParseLegacyDate(item.TiaogangDate) ?? DateTime.Now) { CreatedAt = ParseLegacyDate(item.CreatedAt) ?? DateTime.Now, CreatedBy = item.CreatedBy ?? string.Empty }); } if (inserts.Count > 0) fsql.Insert(inserts).ExecuteAffrows(); Console.WriteLine($"[EmployeeSeed] 调岗记录:来源 {items.Count} 条,新增 {inserts.Count} 条,孤儿 {orphan} 条"); } /// F9 部门 Id → 本系统部门 Id(以部门名称为桥梁,同工种模块的处理方式) private static Dictionary BuildDepartmentIdMap(IFreeSql fsql) { var map = new Dictionary(); var byName = new Dictionary(); foreach (var dep in fsql.Select().ToList()) { if (string.IsNullOrWhiteSpace(dep.Name)) continue; if (!byName.ContainsKey(dep.Name)) byName[dep.Name] = dep.Id; } foreach (var (f9Id, name, _, _, _) in BusinessSeedData.DeptData) { if (byName.TryGetValue(name, out var localId)) map[f9Id] = localId; } return map; } private static List Load(string resourceName) { var assembly = typeof(EmployeeSeedData).Assembly; using var stream = assembly.GetManifestResourceStream(resourceName); if (stream == null) { Console.WriteLine($"[EmployeeSeed] 未找到内嵌资源:{resourceName}"); return new List(); } return JsonSerializer.Deserialize>(stream, JsonOptions) ?? new List(); } /// 兼容 "0"/"1" 字符串、数字与布尔写法 private static bool ParseBool(JsonElement element) { switch (element.ValueKind) { case JsonValueKind.String: var text = element.GetString(); return text == "1" || string.Equals(text, "true", StringComparison.OrdinalIgnoreCase); case JsonValueKind.Number: return element.GetInt32() != 0; case JsonValueKind.True: return true; default: return false; } } private static readonly string[] LegacyDateFormats = { "d/M/yyyy H:mm:ss.fff", "d/M/yyyy H:mm:ss", "d/M/yyyy H:mm", "d/M/yyyy", "M/d/yyyy H:mm:ss.fff", "M/d/yyyy H:mm:ss", "M/d/yyyy H:mm", "M/d/yyyy", "yyyy-MM-dd HH:mm:ss.fff", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd", "yyyy/M/d" }; /// 解析 F9 导出的日期,空值与 1970 占位值返回 null private static DateTime? ParseLegacyDate(string value) { if (string.IsNullOrWhiteSpace(value)) return null; if (DateTime.TryParseExact(value, LegacyDateFormats, CultureInfo.InvariantCulture, DateTimeStyles.None, out var exact)) return exact.Year <= 1900 ? null : exact; if (DateTime.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.None, out var any)) return any.Year <= 1900 ? null : any; return null; } private sealed class EmployeeSeedItem { [JsonPropertyName("id")] public long Id { get; set; } [JsonPropertyName("created_by")] public string CreatedBy { get; set; } [JsonPropertyName("created_at")] public string CreatedAt { get; set; } [JsonPropertyName("update_by")] public string UpdateBy { get; set; } [JsonPropertyName("updated_at")] public string UpdatedAt { get; set; } [JsonPropertyName("base_depid")] public long BaseDepId { get; set; } [JsonPropertyName("base_worktypeid")] public long BaseWorktypeId { get; set; } [JsonPropertyName("base_workstatus")] public int BaseWorkStatus { get; set; } [JsonPropertyName("base_code")] public string BaseCode { get; set; } [JsonPropertyName("base_name")] public string BaseName { get; set; } [JsonPropertyName("base_gangwei")] public string BaseGangwei { get; set; } [JsonPropertyName("base_phone")] public string BasePhone { get; set; } [JsonPropertyName("base_idcard")] public string BaseIdCard { get; set; } [JsonPropertyName("base_birndate")] public string BaseBirnDate { get; set; } [JsonPropertyName("base_address")] public string BaseAddress { get; set; } [JsonPropertyName("base_gender")] public string BaseGender { get; set; } [JsonPropertyName("base_minzu")] public string BaseMinzu { get; set; } [JsonPropertyName("base_hunyinstatus")] public string BaseHunyinStatus { get; set; } [JsonPropertyName("base_huji")] public string BaseHuji { get; set; } [JsonPropertyName("base_ruzhidate")] public string BaseRuzhiDate { get; set; } [JsonPropertyName("base_lizhidate")] public string BaseLizhiDate { get; set; } [JsonPropertyName("base_zhuanzhengdate")] public string BaseZhuanzhengDate { get; set; } [JsonPropertyName("base_iszhuanzheng")] public JsonElement BaseIsZhuanzheng { get; set; } [JsonPropertyName("base_jinjilianxiren")] public string BaseJinjiLianxiren { get; set; } [JsonPropertyName("base_jinjilianxiren_phone")] public string BaseJinjiLianxirenPhone { get; set; } [JsonPropertyName("base_hetongtype")] public string BaseHetongType { get; set; } [JsonPropertyName("base_laodonghetong_startdate")] public string BaseLaodongHetongStartDate { get; set; } [JsonPropertyName("base_laodonghetong_enddate")] public string BaseLaodongHetongEndDate { get; set; } [JsonPropertyName("base_tuixiu_date")] public string BaseTuixiuDate { get; set; } [JsonPropertyName("base_shebaocanbao_date")] public string BaseShebaoCanbaoDate { get; set; } [JsonPropertyName("base_shebaocanbao_desc")] public string BaseShebaocanbaoDesc { get; set; } [JsonPropertyName("base_banckcardcode")] public string BaseBanckcardCode { get; set; } [JsonPropertyName("kz_biyeyuanxiao")] public string KzBiyeYuanxiao { get; set; } [JsonPropertyName("kb_zhuanye")] public string KbZhuanye { get; set; } [JsonPropertyName("kb_xueli")] public string KbXueli { get; set; } [JsonPropertyName("sc_banci")] public int? ScBanci { get; set; } [JsonPropertyName("sc_workgroup")] public string ScWorkGroup { get; set; } [JsonPropertyName("sc_sort")] public int ScSort { get; set; } [JsonPropertyName("name_pinyin")] public string NamePinyin { get; set; } [JsonPropertyName("memo")] public string Memo { get; set; } [JsonPropertyName("base_lizhiyuanyin")] public string BaseLizhiYuanyin { get; set; } } private sealed class ChangeRecordSeedItem { [JsonPropertyName("id")] public long Id { get; set; } [JsonPropertyName("created_by")] public string CreatedBy { get; set; } [JsonPropertyName("created_at")] public string CreatedAt { get; set; } [JsonPropertyName("employee_id")] public long EmployeeId { get; set; } [JsonPropertyName("date")] public string Date { get; set; } [JsonPropertyName("change_status")] public int ChangeStatus { get; set; } [JsonPropertyName("change_shuoming")] public string ChangeShuoming { get; set; } [JsonPropertyName("change_yuanyin")] public string ChangeYuanyin { get; set; } } private sealed class TiaogangRecordSeedItem { [JsonPropertyName("id")] public long Id { get; set; } [JsonPropertyName("created_by")] public string CreatedBy { get; set; } [JsonPropertyName("created_at")] public string CreatedAt { get; set; } [JsonPropertyName("employee_id")] public long EmployeeId { get; set; } [JsonPropertyName("old_depid")] public long OldDepId { get; set; } [JsonPropertyName("old_worktypeid")] public long OldWorktypeId { get; set; } [JsonPropertyName("new_depid")] public long NewDepId { get; set; } [JsonPropertyName("new_worktypeid")] public long NewWorktypeId { get; set; } [JsonPropertyName("tiaogang_date")] public string TiaogangDate { get; set; } } }