using Microsoft.EntityFrameworkCore; using Rmsys.Domain; using Rmsys.Domain.Entities; namespace Rmsys.Infrastructure; /// 一次 .lrmx 导入的回执 public class LrmxImportResult { public bool Created; // true=新增干部 false=更新 public int CadreId; public string Name = ""; public int Resumes; public int Family; public bool Photo; public bool OnRoster; // 是否已加入该会议上会名单 public string Version = "v1"; } /// /// 干部任免审批表 3.0(.lrmx)导入:解析结果 → 干部主表 + 简历 + 家庭 + 照片 + 上会名单 + 材料记录。 /// /// 口径: /// · 匹配优先级:身份证(若有)> 同名同出生年月,避免同名干部被重复新建; /// · 简历与家庭以审批表为权威口径,整表替换(旧记录删除后重建); /// · 身份证只入库,不上任何界面、不进数据包(实体上有 [JsonIgnore]); /// · meetingId=0 时只入干部库,不挂会议、不生成材料记录。 /// 命令行(Rmsys.Tools import-lrmx)与后台接口(POST /api/admin/lrmx/import)共用本实现。 /// public static class LrmxImportService { /// App_Data 绝对路径(照片与原件落盘在它下面) /// 待归档的原件临时路径(会复制到 uploads 下) /// 原件文件名(记入材料表 FileName) public static async Task ImportAsync( RmsysDbContext db, LrmxPerson p, int meetingId, string appDataRoot, string? sourceFile = null, string? originalName = null, string? uploader = "cli") { var r = new LrmxImportResult { Name = p.Name ?? "" }; // ---------- 1. 干部主表 upsert ---------- Cadre? cadre = null; if (!string.IsNullOrWhiteSpace(p.IdCard)) cadre = await db.Cadres.FirstOrDefaultAsync(x => x.IdCard == p.IdCard); cadre ??= await db.Cadres.FirstOrDefaultAsync(x => x.Name == p.Name && x.Birth == p.Birth); r.Created = cadre == null; if (cadre == null) { cadre = new Cadre(); db.Cadres.Add(cadre); } cadre.Name = p.Name!; cadre.IdCard = string.IsNullOrWhiteSpace(p.IdCard) ? cadre.IdCard : p.IdCard; cadre.Sex = p.Sex; cadre.Birth = p.Birth; cadre.Nation = p.Nation; cadre.NativePlace = p.NativePlace; cadre.BirthPlace = p.BirthPlace; cadre.PartyDate = p.PartyDate; cadre.WorkDate = p.WorkDate; cadre.Health = p.Health; cadre.TechPost = p.TechPost; cadre.Expertise = p.Expertise; cadre.EduFullLevel = p.EduFullLevel; cadre.EduFullDegree = p.EduFullDegree; cadre.EduFullSchool = p.EduFullSchool ?? p.EduFullDegreeSchool; cadre.EduInLevel = p.EduInLevel; cadre.EduInDegree = p.EduInDegree; cadre.EduInSchool = p.EduInSchool ?? p.EduInDegreeSchool; cadre.CurrentPost = p.CurrentPost; cadre.ProposedPost = p.ProposedPost; cadre.DismissPost = p.DismissPost; cadre.Rewards = p.Rewards; cadre.Exams = p.Exams; cadre.AppointReason = p.AppointReason; cadre.UpdatedAt = DateTime.Now; await db.SaveChangesAsync(); // 先拿到 Id,供照片/简历/材料引用 r.CadreId = cadre.Id; // ---------- 2. 简历 / 家庭:整表替换 ---------- var oldResumes = await db.CadreResumes.Where(x => x.CadreId == cadre.Id).ToListAsync(); if (oldResumes.Count > 0) db.CadreResumes.RemoveRange(oldResumes); foreach (var x in p.Resumes) db.CadreResumes.Add(new CadreResume { CadreId = cadre.Id, Sort = x.Sort, Period = x.Period, Content = x.Content }); var oldFam = await db.CadreFamilies.Where(x => x.CadreId == cadre.Id).ToListAsync(); if (oldFam.Count > 0) db.CadreFamilies.RemoveRange(oldFam); foreach (var f in p.Family) db.CadreFamilies.Add(new CadreFamily { CadreId = cadre.Id, Sort = f.Sort, Relation = f.Relation, Name = f.Name, Age = f.Birth, Political = f.Political, WorkUnit = f.WorkUnit }); r.Resumes = p.Resumes.Count; r.Family = p.Family.Count; // ---------- 3. 照片 ---------- if (p.Photo != null && p.Photo.Length > 0) { var rel = Path.Combine("uploads", "photos", cadre.Id + (p.PhotoExt ?? ".png")).Replace('\\', '/'); var full = Path.Combine(appDataRoot, rel); Directory.CreateDirectory(Path.GetDirectoryName(full)!); await File.WriteAllBytesAsync(full, p.Photo); cadre.PhotoPath = rel; r.Photo = true; } // ---------- 4. 上会名单 + 原件材料 ---------- if (meetingId > 0) { var onRoster = await db.MeetingCadres.AnyAsync(x => x.MeetingId == meetingId && x.CadreId == cadre.Id); if (!onRoster) { var maxSort = await db.MeetingCadres.Where(x => x.MeetingId == meetingId) .Select(x => (int?)x.SortNo).MaxAsync() ?? 0; db.MeetingCadres.Add(new MeetingCadre { MeetingId = meetingId, CadreId = cadre.Id, SortNo = maxSort + 1, Result = CadreResult.Pending }); } r.OnRoster = true; if (!string.IsNullOrEmpty(sourceFile) && File.Exists(sourceFile)) { var name = string.IsNullOrWhiteSpace(originalName) ? "任免审批表.lrmx" : originalName!; var relDoc = Path.Combine("uploads", meetingId.ToString(), cadre.Id.ToString(), "t30_" + DateTime.Now.ToString("yyyyMMddHHmmss") + Path.GetExtension(name).ToLowerInvariant()) .Replace('\\', '/'); var fullDoc = Path.Combine(appDataRoot, relDoc); Directory.CreateDirectory(Path.GetDirectoryName(fullDoc)!); File.Copy(sourceFile, fullDoc, true); var old = await db.Materials .Where(x => x.MeetingId == meetingId && x.CadreId == cadre.Id && x.Type == MaterialType.AppointmentForm) .OrderByDescending(x => x.Id).FirstOrDefaultAsync(); var ver = "v1"; if (old?.Version is { Length: > 1 } ov && ov.StartsWith("v") && int.TryParse(ov[1..], out var n)) ver = "v" + (n + 1); r.Version = ver; if (old != null) { old.FileName = name; old.FilePath = relDoc; old.FileSize = new FileInfo(fullDoc).Length; old.Version = ver; old.UploadedBy = uploader; old.UploadedAt = DateTime.Now; old.Status = MaterialStatus.Pending; } else { db.Materials.Add(new Material { MeetingId = meetingId, DeptId = cadre.DeptId, CadreId = cadre.Id, Type = MaterialType.AppointmentForm, Title = cadre.Name + "_任免审批表", FileName = name, FilePath = relDoc, FileSize = new FileInfo(fullDoc).Length, Pages = 0, Version = ver, UploadedBy = uploader, UploadedAt = DateTime.Now, Status = MaterialStatus.Pending }); } } } await db.SaveChangesAsync(); return r; } }