using Admin.NET.Core; using Microsoft.AspNetCore.Http; using Admin.NET.Plugin.AiDOP.Dto.S0.Quality; using Admin.NET.Plugin.AiDOP.Entity.S0.Quality; using static Admin.NET.Plugin.AiDOP.Controllers.S0.Quality.AdoS0QmsControllerHelpers; namespace Admin.NET.Plugin.AiDOP.Controllers.S0.Quality; /// /// S0 质量建模 - 4 个检验规范列表(原材料 / 过程 / FQC / OQC)Excel 模板导入。 /// 每个列表提供“下载导入模板”(GET import-template) 与“上传导入”(POST import) 两个端点。 /// 模板为一张平铺表:主表列 + 明细列,按“文件编号”分组还原为一条主单 + 多条明细。 /// 写入语义复刻各列表现有 Create(纯新增,不去重、不 upsert),全过程单事务。 /// [ApiController] [Route("api/s0/quality")] [AllowAnonymous] [NonUnify] public class AdoS0QmsSpecImportController : ControllerBase { private readonly SqlSugarRepository _rawRep; private readonly SqlSugarRepository _rawEntryRep; private readonly SqlSugarRepository _processRep; private readonly SqlSugarRepository _processEntryRep; private readonly SqlSugarRepository _fqcRep; private readonly SqlSugarRepository _fqcEntryRep; private readonly SqlSugarRepository _oqcRep; private readonly SqlSugarRepository _oqcEntryRep; public AdoS0QmsSpecImportController( SqlSugarRepository rawRep, SqlSugarRepository rawEntryRep, SqlSugarRepository processRep, SqlSugarRepository processEntryRep, SqlSugarRepository fqcRep, SqlSugarRepository fqcEntryRep, SqlSugarRepository oqcRep, SqlSugarRepository oqcEntryRep) { _rawRep = rawRep; _rawEntryRep = rawEntryRep; _processRep = processRep; _processEntryRep = processEntryRep; _fqcRep = fqcRep; _fqcEntryRep = fqcEntryRep; _oqcRep = oqcRep; _oqcEntryRep = oqcEntryRep; } // ==================== 模板下载 ==================== [HttpGet("raw-inspection-specs/import-template")] public Task RawTemplate() => MiniExcelUtil.ExportExcelTemplate("原材料检验规范导入模板"); [HttpGet("process-inspection-specs/import-template")] public Task ProcessTemplate() => MiniExcelUtil.ExportExcelTemplate("过程检验规范导入模板"); [HttpGet("fqc-inspection-specs/import-template")] public Task FqcTemplate() => MiniExcelUtil.ExportExcelTemplate("FQC检验规范导入模板"); [HttpGet("oqc-inspection-specs/import-template")] public Task OqcTemplate() => MiniExcelUtil.ExportExcelTemplate("OQC检验规范导入模板"); // ==================== 导入 ==================== [HttpPost("raw-inspection-specs/import")] public async Task RawImport(IFormFile file) { if (file == null || file.Length == 0) return BadRequest(new { message = "请上传 Excel 文件" }); var result = await RunImportAsync( file, r => r.FileNumber, RawDetailHasValue, _rawRep, _rawEntryRep, (r, fn) => new AdoS0QmsRawInspectionSpec { FileNumber = fn, VersionNo = NullIfWhiteSpace(r.VersionNo), DrawingNo = NullIfWhiteSpace(r.DrawingNo), RawMaterialName = NullIfWhiteSpace(r.RawMaterialName), MaterialCode = NullIfWhiteSpace(r.MaterialCode), EffectiveDate = NullIfWhiteSpace(r.EffectiveDate), DrawingVersion = NullIfWhiteSpace(r.DrawingVersion), MaterialGrade = NullIfWhiteSpace(r.MaterialGrade), CavityOrMold = NullIfWhiteSpace(r.CavityOrMold), Attachment = NullIfWhiteSpace(r.Attachment), FileName = NullIfWhiteSpace(r.FileName), Title = NullIfWhiteSpace(r.Title), }, m => m.Id, (r, mid) => new AdoS0QmsRawInspectionSpecEntry { MasterId = mid, Seq = r.Seq, InspectionItem = NullIfWhiteSpace(r.InspectionItem), InspectionStandard = NullIfWhiteSpace(r.InspectionStandard), InspectionMethod = NullIfWhiteSpace(r.InspectionMethod), SamplingScheme = NullIfWhiteSpace(r.SamplingScheme), UpperLimit = NullIfWhiteSpace(r.UpperLimit), LowerLimit = NullIfWhiteSpace(r.LowerLimit), }); return Ok(result); } [HttpPost("process-inspection-specs/import")] public async Task ProcessImport(IFormFile file) { if (file == null || file.Length == 0) return BadRequest(new { message = "请上传 Excel 文件" }); var result = await RunImportAsync( file, r => r.FileNumber, ProcessLikeDetailHasValue, _processRep, _processEntryRep, (r, fn) => new AdoS0QmsProcessInspectionSpec { FileNumber = fn, ApplicableModel = NullIfWhiteSpace(r.ApplicableModel), VersionNo = NullIfWhiteSpace(r.VersionNo), EffectiveDate = NullIfWhiteSpace(r.EffectiveDate), MaterialCode = NullIfWhiteSpace(r.MaterialCode), Attachment = NullIfWhiteSpace(r.Attachment), Attachment2 = NullIfWhiteSpace(r.Attachment2), Version = r.Version, }, m => m.Id, (r, mid) => BuildProcessLikeEntry(r, mid)); return Ok(result); } [HttpPost("fqc-inspection-specs/import")] public async Task FqcImport(IFormFile file) { if (file == null || file.Length == 0) return BadRequest(new { message = "请上传 Excel 文件" }); var result = await RunImportAsync( file, r => r.FileNumber, ProcessLikeDetailHasValue, _fqcRep, _fqcEntryRep, (r, fn) => new AdoS0QmsFqcInspectionSpec { FileNumber = fn, ApplicableModel = NullIfWhiteSpace(r.ApplicableModel), VersionNo = NullIfWhiteSpace(r.VersionNo), EffectiveDate = NullIfWhiteSpace(r.EffectiveDate), MaterialCode = NullIfWhiteSpace(r.MaterialCode), Attachment = NullIfWhiteSpace(r.Attachment), Attachment2 = NullIfWhiteSpace(r.Attachment2), Version = r.Version, }, m => m.Id, (r, mid) => BuildProcessLikeEntry(r, mid)); return Ok(result); } [HttpPost("oqc-inspection-specs/import")] public async Task OqcImport(IFormFile file) { if (file == null || file.Length == 0) return BadRequest(new { message = "请上传 Excel 文件" }); var result = await RunImportAsync( file, r => r.FileNumber, ProcessLikeDetailHasValue, _oqcRep, _oqcEntryRep, (r, fn) => new AdoS0QmsOqcInspectionSpec { FileNumber = fn, ApplicableModel = NullIfWhiteSpace(r.ApplicableModel), VersionNo = NullIfWhiteSpace(r.VersionNo), EffectiveDate = NullIfWhiteSpace(r.EffectiveDate), MaterialCode = NullIfWhiteSpace(r.MaterialCode), Attachment = NullIfWhiteSpace(r.Attachment), Attachment2 = NullIfWhiteSpace(r.Attachment2), Version = r.Version, }, m => m.Id, (r, mid) => BuildProcessLikeEntry(r, mid)); return Ok(result); } // ==================== 共用逻辑 ==================== /// 过程/FQC/OQC 三表明细结构一致,共用一个明细构造器(各表 Entry 字段名相同)。 private static TEntry BuildProcessLikeEntry(AdoS0QmsProcessLikeInspectionSpecImportRow r, long masterId) where TEntry : class, new() { var entry = new TEntry(); switch (entry) { case AdoS0QmsProcessInspectionSpecEntry p: p.MasterId = masterId; p.OperationCode = NullIfWhiteSpace(r.OperationCode); p.OperationName = NullIfWhiteSpace(r.OperationName); p.InspectionItem = NullIfWhiteSpace(r.InspectionItem); p.InspectionMethod = NullIfWhiteSpace(r.InspectionMethod); p.InspectionSpec = NullIfWhiteSpace(r.InspectionSpec); p.InspectionFrequency = NullIfWhiteSpace(r.InspectionFrequency); p.UpperLimit = NullIfWhiteSpace(r.UpperLimit); p.LowerLimit = NullIfWhiteSpace(r.LowerLimit); break; case AdoS0QmsFqcInspectionSpecEntry f: f.MasterId = masterId; f.OperationCode = NullIfWhiteSpace(r.OperationCode); f.OperationName = NullIfWhiteSpace(r.OperationName); f.InspectionItem = NullIfWhiteSpace(r.InspectionItem); f.InspectionMethod = NullIfWhiteSpace(r.InspectionMethod); f.InspectionSpec = NullIfWhiteSpace(r.InspectionSpec); f.InspectionFrequency = NullIfWhiteSpace(r.InspectionFrequency); f.UpperLimit = NullIfWhiteSpace(r.UpperLimit); f.LowerLimit = NullIfWhiteSpace(r.LowerLimit); break; case AdoS0QmsOqcInspectionSpecEntry o: o.MasterId = masterId; o.OperationCode = NullIfWhiteSpace(r.OperationCode); o.OperationName = NullIfWhiteSpace(r.OperationName); o.InspectionItem = NullIfWhiteSpace(r.InspectionItem); o.InspectionMethod = NullIfWhiteSpace(r.InspectionMethod); o.InspectionSpec = NullIfWhiteSpace(r.InspectionSpec); o.InspectionFrequency = NullIfWhiteSpace(r.InspectionFrequency); o.UpperLimit = NullIfWhiteSpace(r.UpperLimit); o.LowerLimit = NullIfWhiteSpace(r.LowerLimit); break; } return entry; } private async Task RunImportAsync( IFormFile file, Func keySelector, Func detailHasValue, SqlSugarRepository masterRep, SqlSugarRepository entryRep, Func masterFactory, Func idOf, Func entryFactory) where TRow : class, new() where TMaster : class, new() where TEntry : class, new() { var rows = (await MiniExcelUtil.GetImportExcelData(file)).ToList(); var result = new AdoS0QmsSpecImportResultDto(); var groups = BuildGroups(rows, keySelector, result); var db = masterRep.Context; await db.Ado.BeginTranAsync(); try { foreach (var g in groups) { var master = masterFactory(g.FirstRow, g.FileNumber); await masterRep.AsInsertable(master).ExecuteReturnEntityAsync(); var masterId = idOf(master); result.ImportedMasters++; foreach (var row in g.Rows) { if (!detailHasValue(row)) continue; var entry = entryFactory(row, masterId); await entryRep.AsInsertable(entry).ExecuteCommandAsync(); result.ImportedItems++; } } await db.Ado.CommitTranAsync(); } catch { await db.Ado.RollbackTranAsync(); throw; } result.Message = $"导入完成:新增 {result.ImportedMasters} 条规范、{result.ImportedItems} 条明细,共读取 {result.TotalRows} 行,跳过 {result.SkippedRows} 行。"; return result; } /// 按“文件编号”分组:同一文件编号的多行合并为一条主单 + 多条明细;文件编号为空的行跳过并记录。 private static List> BuildGroups(IEnumerable rows, Func keySelector, AdoS0QmsSpecImportResultDto result) { var groups = new List>(); var index = new Dictionary>(); var rowNo = 1; // 表头占第 1 行,首个数据行为第 2 行 foreach (var row in rows) { rowNo++; result.TotalRows++; var key = keySelector(row)?.Trim(); if (string.IsNullOrWhiteSpace(key)) { result.SkippedRows++; result.RowMessages.Add($"第 {rowNo} 行:文件编号为空,已跳过"); continue; } if (!index.TryGetValue(key, out var g)) { g = new SpecGroup { FileNumber = key, FirstRow = row }; index[key] = g; groups.Add(g); } g.Rows.Add(row); } return groups; } private static bool RawDetailHasValue(AdoS0QmsRawInspectionSpecImportRow r) => r.Seq.HasValue || !string.IsNullOrWhiteSpace(r.InspectionItem) || !string.IsNullOrWhiteSpace(r.InspectionStandard) || !string.IsNullOrWhiteSpace(r.InspectionMethod) || !string.IsNullOrWhiteSpace(r.SamplingScheme) || !string.IsNullOrWhiteSpace(r.UpperLimit) || !string.IsNullOrWhiteSpace(r.LowerLimit); private static bool ProcessLikeDetailHasValue(AdoS0QmsProcessLikeInspectionSpecImportRow r) => !string.IsNullOrWhiteSpace(r.OperationCode) || !string.IsNullOrWhiteSpace(r.OperationName) || !string.IsNullOrWhiteSpace(r.InspectionItem) || !string.IsNullOrWhiteSpace(r.InspectionMethod) || !string.IsNullOrWhiteSpace(r.InspectionSpec) || !string.IsNullOrWhiteSpace(r.InspectionFrequency) || !string.IsNullOrWhiteSpace(r.UpperLimit) || !string.IsNullOrWhiteSpace(r.LowerLimit); private sealed class SpecGroup { public string FileNumber { get; init; } = string.Empty; public TRow FirstRow { get; init; } = default!; public List Rows { get; } = []; } }