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; } = [];
}
}