using System.Globalization; using System.Text.Json; using System.Text.Json.Serialization; using FreeSql; using F9WebAiTest.Domain.Entities.SYS.WuziMulus; using F9WebAiTest.Domain.Enums.SYS.Departments; using F9WebAiTest.Domain.Enums.SYS.WuziMulus; namespace F9WebAiTest.Infrastructure.Data; /// /// 物资数据导入:来源于 F9 系统 biz_base_wuzigroup(物资分组)与 biz_base_wuzimulu(物资目录)两张表的导出数据。 /// 幂等:按原始 Id 判重,已存在则跳过。 /// public static class WuziSeedData { private const string GroupResource = "F9WebAiTest.Infrastructure.Data.SeedData.biz_base_wuzigroup.json"; private const string MuluResource = "F9WebAiTest.Infrastructure.Data.SeedData.biz_base_wuzimulu.json"; private static readonly JsonSerializerOptions JsonOptions = new() { PropertyNameCaseInsensitive = true }; public static void Seed(IFreeSql fsql) { try { SeedGroups(fsql); SeedMulu(fsql); } catch (Exception ex) { Console.WriteLine($"[WuziSeed] 物资数据导入跳过:{ex.Message}"); } } /// 导入物资分组 private static void SeedGroups(IFreeSql fsql) { var items = Load(GroupResource); 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.Name)) continue; inserts.Add(new WuziGroup(item.Id, (WuziBusiness_Type_Enum)item.BusinessType, (Department_DutyType_Enum)item.DepartmentType, item.Code ?? string.Empty, item.Name.Trim(), item.ParentId, item.SortOrder, item.Description ?? string.Empty) { CreatedAt = ParseLegacyDate(item.CreatedAt) ?? DateTime.Now, CreatedBy = item.CreatedBy ?? string.Empty, UpdatedAt = ParseLegacyDate(item.UpdatedAt) }); } if (inserts.Count > 0) fsql.Insert(inserts).ExecuteAffrows(); Console.WriteLine($"[WuziSeed] 物资分组:来源 {items.Count} 条,新增 {inserts.Count} 条"); } /// 导入物资目录 private static void SeedMulu(IFreeSql fsql) { var items = Load(MuluResource); if (items.Count == 0) return; var existIds = 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 (string.IsNullOrWhiteSpace(item.Name)) continue; if (item.GroupId <= 0) { orphan++; continue; } inserts.Add(new WuziMulu(item.Id, item.GroupId, item.Name.Trim(), item.Code ?? string.Empty, item.Unit ?? string.Empty, item.Guige ?? string.Empty, item.Xinghao ?? string.Empty, item.PinyinName ?? string.Empty, item.Kucunyujing, item.SortOrder, ParseBool(item.IsYujing), item.SaleRate) { CreatedAt = ParseLegacyDate(item.CreatedAt) ?? DateTime.Now, CreatedBy = item.CreatedBy ?? string.Empty, UpdatedAt = ParseLegacyDate(item.UpdatedAt) }); } // 分批插入(1000 条一次插入会超出 SQL 参数上限) foreach (var batch in inserts.Chunk(200)) fsql.Insert(batch.ToList()).ExecuteAffrows(); Console.WriteLine($"[WuziSeed] 物资目录:来源 {items.Count} 条,新增 {inserts.Count} 条,跳过无分组 {orphan} 条"); } private static List Load(string resourceName) { var assembly = typeof(WuziSeedData).Assembly; using var stream = assembly.GetManifestResourceStream(resourceName); if (stream == null) { Console.WriteLine($"[WuziSeed] 未找到内嵌资源:{resourceName}"); return new List(); } return JsonSerializer.Deserialize>(stream, JsonOptions) ?? new List(); } /// is_yujing 在导出数据里可能是 "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; } } /// 解析 F9 导出的日期(形如 d/M/yyyy H:mm:ss.fff),1970 占位值视为未更新返回 null private static readonly string[] LegacyDateFormats = { "d/M/yyyy H:mm:ss.fff", "d/M/yyyy H:mm:ss", "d/M/yyyy H:mm", "M/d/yyyy H:mm:ss.fff", "M/d/yyyy H:mm:ss", "M/d/yyyy H:mm", "yyyy-MM-dd HH:mm:ss.fff", "yyyy-MM-dd HH:mm:ss" }; 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 WuziGroupSeedItem { [JsonPropertyName("id")] public long Id { get; set; } [JsonPropertyName("created_by")] public string CreatedBy { get; set; } [JsonPropertyName("created_at")] public string CreatedAt { get; set; } [JsonPropertyName("updated_at")] public string UpdatedAt { get; set; } [JsonPropertyName("businesstype")] public int BusinessType { get; set; } [JsonPropertyName("departmenttype")] public int DepartmentType { get; set; } [JsonPropertyName("code")] public string Code { get; set; } [JsonPropertyName("name")] public string Name { get; set; } [JsonPropertyName("parentid")] public long ParentId { get; set; } [JsonPropertyName("sortorder")] public int SortOrder { get; set; } [JsonPropertyName("description")] public string Description { get; set; } } private sealed class WuziMuluSeedItem { [JsonPropertyName("id")] public long Id { get; set; } [JsonPropertyName("created_by")] public string CreatedBy { get; set; } [JsonPropertyName("created_at")] public string CreatedAt { get; set; } [JsonPropertyName("updated_at")] public string UpdatedAt { get; set; } [JsonPropertyName("groupid")] public long GroupId { get; set; } [JsonPropertyName("name")] public string Name { get; set; } [JsonPropertyName("code")] public string Code { get; set; } [JsonPropertyName("unit")] public string Unit { get; set; } [JsonPropertyName("guige")] public string Guige { get; set; } [JsonPropertyName("xinghao")] public string Xinghao { get; set; } [JsonPropertyName("pinyinname")] public string PinyinName { get; set; } [JsonPropertyName("kucunyujing")] public decimal Kucunyujing { get; set; } [JsonPropertyName("sortorder")] public int SortOrder { get; set; } /// F9 导出为字符串 "0"/"1"(兼容数字与布尔写法) [JsonPropertyName("is_yujing")] public JsonElement IsYujing { get; set; } [JsonPropertyName("SaleRate")] public decimal SaleRate { get; set; } } }