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;
}
}