using System.Security.Claims;
using Microsoft.EntityFrameworkCore;
using Rmsys.Domain;
using Rmsys.Domain.Entities;
using Rmsys.Infrastructure;
namespace Rmsys.Api.Endpoints;
///
/// 任免文件 ↔ 上会干部 的姓名自动匹配与一致性检查。
///
/// 解决的问题:以前"文件涉及哪些人"只能手工勾选,漏勾就统计不到,
/// 出包时也看不出文件里的名单和审批表名单是否对得上。
///
public static class DocMatchEndpoints
{
public static void MapDocMatchEndpoints(this WebApplication app)
{
// ---------- 只检查,不落库(供界面预览 / 出包前提示) ----------
app.MapGet("/api/admin/appointdocs/{id:int}/match-report", async (
int id, RmsysDbContext db, IWebHostEnvironment env) =>
{
var (doc, cads, err) = await LoadAsync(id, db, env);
if (err != null) return Results.Ok(new { ok = false, message = err });
var r = DocNameMatcher.Match(Path.Combine(env.ContentRootPath, "App_Data", doc!.FilePath!), cads);
var t30 = await T30IdsAsync(db, doc.MeetingId);
return Results.Ok(new
{
ok = true,
textOk = r.TextOk,
error = r.Error,
textLength = r.TextLength,
inFile = r.InFile.Select(x => new { cadreId = x.CadreId, name = x.Name, pos = x.Pos, hasT30 = t30.Contains(x.CadreId) }),
ambiguous = r.Ambiguous.Select(x => new { name = x.Name, cadreIds = x.CadreIds }),
unknownNames = r.UnknownNames,
t30NotInDoc = cads.Where(c => t30.Contains(c.Id) && r.InFile.All(x => x.CadreId != c.Id))
.Select(c => new { cadreId = c.Id, name = c.Name }).ToList(),
inDocNoT30 = r.InFile.Where(x => !t30.Contains(x.CadreId))
.Select(x => new { cadreId = x.CadreId, name = x.Name }).ToList(),
linkedCount = await db.AppointDocCadres.CountAsync(x => x.DocId == id)
});
}).RequireAuthorization();
// ---------- 自动匹配并写入关联(可覆盖手工结果) ----------
app.MapPost("/api/admin/appointdocs/{id:int}/auto-match", async (
int id, ClaimsPrincipal p, RmsysDbContext db, IWebHostEnvironment env) =>
{
var (doc, cads, err) = await LoadAsync(id, db, env);
if (err != null) return Results.Ok(new { ok = false, message = err });
var r = DocNameMatcher.Match(Path.Combine(env.ContentRootPath, "App_Data", doc!.FilePath!), cads);
if (!r.TextOk)
return Results.Ok(new { ok = false, message = r.Error ?? "无法读取文件内容,请在「人员」里手动勾选" });
// 覆盖式写入:按姓名在文件中出现的先后排序
var old = await db.AppointDocCadres.Where(x => x.DocId == id).ToListAsync();
db.AppointDocCadres.RemoveRange(old);
var sort = 1;
foreach (var x in r.InFile)
{
db.AppointDocCadres.Add(new AppointDocCadre { DocId = id, CadreId = x.CadreId, Sort = sort++ });
}
var t30 = await T30IdsAsync(db, doc.MeetingId);
await Audit(db, p, "任免文件自动匹配",
$"{doc.DocNo ?? ("#" + id)} {doc.Title}",
$"匹配到 {r.InFile.Count} 人;同名待确认 {r.Ambiguous.Count};文件内库外姓名 {r.UnknownNames.Count}");
return Results.Ok(new
{
ok = true,
message = $"已按姓名自动匹配 {r.InFile.Count} 人",
matched = r.InFile.Select(x => new { cadreId = x.CadreId, name = x.Name, hasT30 = t30.Contains(x.CadreId) }),
ambiguous = r.Ambiguous.Select(x => new { name = x.Name, cadreIds = x.CadreIds }),
unknownNames = r.UnknownNames,
t30NotInDoc = cads.Where(c => t30.Contains(c.Id) && r.InFile.All(x => x.CadreId != c.Id))
.Select(c => new { cadreId = c.Id, name = c.Name }).ToList(),
inDocNoT30 = r.InFile.Where(x => !t30.Contains(x.CadreId))
.Select(x => new { cadreId = x.CadreId, name = x.Name }).ToList()
});
}).RequireAuthorization();
// ---------- 出包前一致性检查(会议级汇总) ----------
app.MapPost("/api/admin/packages/precheck", async (
PrecheckReq req, RmsysDbContext db, IWebHostEnvironment env) =>
{
var meeting = await db.Meetings.FirstOrDefaultAsync(x => x.Id == req.meetingId && x.IsDeleted == 0);
if (meeting == null) return Results.Ok(new { ok = false, message = "会议不存在" });
var cads = await db.MeetingCadres.Where(x => x.MeetingId == meeting.Id)
.Join(db.Cadres, mc => mc.CadreId, c => c.Id, (mc, c) => new { c.Id, c.Name })
.Select(x => new { x.Id, x.Name }).ToListAsync();
var cadreTuples = cads.Select(x => (x.Id, x.Name)).ToList();
var deptOfCadre = await db.Cadres.Where(c => cadreTuples.Select(t => t.Id).Contains(c.Id))
.Select(c => new { c.Id, c.DeptId }).ToDictionaryAsync(x => x.Id, x => x.DeptId);
var t30 = await T30IdsAsync(db, meeting.Id);
var docs = await db.AppointDocs.Where(x => x.MeetingId == meeting.Id && x.IsDeleted == 0).ToListAsync();
var deptNames = await db.Depts.ToDictionaryAsync(x => x.Id, x => x.Name);
var issues = new List