From 9057d0f6f3a46b93d62d0b71c7f4f03eca41f3a9 Mon Sep 17 00:00:00 2001
From: tjx <t2856754968@163.com>
Date: 星期三, 15 十月 2025 16:43:14 +0800
Subject: [PATCH] 11111
---
MES.Service/service/QC/SpiAoiService.cs | 444 +++++++++++++++++++++++++++++++++---------------------
1 files changed, 270 insertions(+), 174 deletions(-)
diff --git a/MES.Service/service/QC/SpiAoiService.cs b/MES.Service/service/QC/SpiAoiService.cs
index 7a2aad8..d5e7669 100644
--- a/MES.Service/service/QC/SpiAoiService.cs
+++ b/MES.Service/service/QC/SpiAoiService.cs
@@ -1,3 +1,4 @@
+using System.Linq;
using MES.Service.DB;
using MES.Service.Dto.service;
using MES.Service.Modes;
@@ -11,78 +12,216 @@
public class SpiAoiService
{
/// <summary>
- /// 涓婁紶SPI/AOI妫�娴嬫暟鎹�
+ /// Upload AOI header data.
/// </summary>
- /// <param name="request">涓婁紶璇锋眰DTO</param>
- /// <returns>涓婁紶鍝嶅簲DTO</returns>
- public SpiAoiUploadResponse UploadSpiAoiData(SpiAoiUploadRequest request)
+ /// <param name="header">Header DTO.</param>
+ /// <returns>Header upload response.</returns>
+ public SpiAoiHeaderUploadResponse UploadAoiHeader(SpiAoiHeaderDto header)
{
- // 1. 鍩烘湰鏍¢獙
- ValidateRequest(request);
+ if (header == null)
+ {
+ throw new Exception("header cannot be null");
+ }
try
{
- SpiAoiUploadResponse response = null;
-
- SqlSugarHelper.UseTransactionWithOracle(db =>
- {
- // 2. 妫�鏌ユ潯鐮佹槸鍚﹀凡瀛樺湪
- var existingHeader = db.Queryable<MesSpiAoiHeader>()
- .Where(x => x.BoardBarcode == request.Header.BoardBarcode)
- .First();
-
- if (existingHeader != null)
- {
- throw new Exception($"鏉$爜 {request.Header.BoardBarcode} 宸插瓨鍦紝涓嶅厑璁搁噸澶嶄笂浼�");
- }
-
- // 3. 杞崲骞舵彃鍏ヤ富琛ㄦ暟鎹�
- var header = ConvertHeaderDtoToEntity(request.Header);
- header.CreatedAt = DateTime.Now;
- header.UpdatedAt = DateTime.Now;
-
- var headerId = db.Insertable(header).ExecuteReturnIdentity();
-
- // 4. 杞崲骞舵彃鍏ュ瓙琛ㄦ暟鎹�
- var detailCount = 0;
- if (request.Details != null && request.Details.Count > 0)
- {
- var details = ConvertDetailDtoListToEntity(request.Details, headerId);
-
- // 鏁版嵁鏍¢獙(璁板綍璀﹀憡浣嗕笉闃绘柇)
- ValidateDetailData(request.Details);
-
- detailCount = db.Insertable(details).ExecuteCommand();
- }
-
- response = new SpiAoiUploadResponse
- {
- HeaderId = headerId,
- DetailCount = detailCount
- };
-
- return 1; // 杩斿洖鍙楀奖鍝嶇殑琛屾暟渚涗簨鍔″垽鏂�
- });
-
- if (response == null)
- {
- throw new Exception("涓婁紶澶辫触");
- }
-
- return response;
+ return UploadAoiHeaderBatchInternal(
+ new List<SpiAoiHeaderDto> { header })[0];
}
catch (Exception ex)
{
- throw new Exception($"涓婁紶SPI/AOI妫�娴嬫暟鎹け璐�: {ex.Message}", ex);
+ throw new Exception($"Failed to upload AOI header: {ex.Message}",
+ ex);
}
}
/// <summary>
- /// 鏍规嵁鏉$爜鏌ヨSPI/AOI妫�娴嬫暟鎹�
+ /// Upload multiple AOI header records within a single transaction.
/// </summary>
- /// <param name="boardBarcode">鏉$爜</param>
- /// <returns>妫�娴嬫暟鎹�(涓昏〃+瀛愯〃)</returns>
- public (MesSpiAoiHeader header, List<MesSpiAoiDetail> details) GetByBarcode(string boardBarcode)
+ /// <param name="headers">Header DTO collection.</param>
+ /// <returns>Header upload responses.</returns>
+ public List<SpiAoiHeaderUploadResponse> UploadAoiHeaderBatch(
+ List<SpiAoiHeaderDto> headers)
+ {
+ if (headers == null)
+ {
+ throw new Exception("headers cannot be null");
+ }
+
+ try
+ {
+ return UploadAoiHeaderBatchInternal(headers);
+ }
+ catch (Exception ex)
+ {
+ throw new Exception(
+ $"Failed to upload AOI header batch: {ex.Message}", ex);
+ }
+ }
+
+ private List<SpiAoiHeaderUploadResponse> UploadAoiHeaderBatchInternal(
+ List<SpiAoiHeaderDto> headers)
+ {
+ var headerList = headers.ToList();
+ if (headerList.Count == 0)
+ {
+ throw new Exception("headers cannot be empty");
+ }
+
+ foreach (var header in headerList)
+ {
+ ValidateHeader(header);
+ }
+
+ var duplicateBarcodes = headerList
+ .GroupBy(h => h.BoardBarcode)
+ .Where(g => g.Count() > 1)
+ .Select(g => g.Key)
+ .ToList();
+
+ if (duplicateBarcodes.Any())
+ {
+ throw new Exception(
+ $"Duplicate board barcodes in request: {string.Join(", ",
+ duplicateBarcodes)}.");
+ }
+
+ var responses = new List<SpiAoiHeaderUploadResponse>();
+
+ SqlSugarHelper.UseTransactionWithOracle(db =>
+ {
+ var boardBarcodes = headerList.Select(h => h.BoardBarcode)
+ .ToList();
+
+ if (boardBarcodes.Any())
+ {
+ var existingBarcodes = db.Queryable<MesSpiAoiHeader>()
+ .Where(x => boardBarcodes.Contains(x.BoardBarcode))
+ .Select(x => x.BoardBarcode)
+ .ToList();
+
+ if (existingBarcodes.Any())
+ {
+ throw new Exception(
+ $"Board barcodes {string.Join(", ", existingBarcodes)} already exist; duplicate AOI uploads are not allowed.");
+ }
+ }
+
+ foreach (var headerDto in headerList)
+ {
+ var entity = ConvertHeaderDtoToEntity(headerDto);
+ var timestamp = DateTime.Now;
+ entity.CreatedAt = timestamp;
+ entity.UpdatedAt = timestamp;
+
+ var headerId = db.Insertable(entity).ExecuteReturnIdentity();
+ responses.Add(new SpiAoiHeaderUploadResponse
+ {
+ HeaderId = headerId
+ });
+ }
+
+ return responses.Count;
+ });
+
+ if (responses.Count == 0)
+ {
+ throw new Exception("AOI upload returned no result.");
+ }
+
+ return responses;
+ }
+
+ /// <summary>
+ /// Upload SPI detail data.
+ /// </summary>
+ /// <param name="request">Detail upload DTO.</param>
+ /// <returns>Detail upload response.</returns>
+ public SpiAoiDetailUploadResponse UploadSpiDetails(SpiAoiDetailDto request)
+ {
+ if (request == null)
+ {
+ throw new Exception("request cannot be null");
+ }
+
+ try
+ {
+ return UploadSpiDetailsInternal(
+ new List<SpiAoiDetailDto> { request });
+ }
+ catch (Exception ex)
+ {
+ throw new Exception($"Failed to upload SPI details: {ex.Message}",
+ ex);
+ }
+ }
+
+ /// <summary>
+ /// Upload multiple SPI detail records within a single transaction.
+ /// </summary>
+ /// <param name="requests">Detail DTO collection.</param>
+ /// <returns>Aggregated upload response.</returns>
+ public SpiAoiDetailUploadResponse UploadSpiDetailsBatch(
+ List<SpiAoiDetailDto> requests)
+ {
+ if (requests == null)
+ {
+ throw new Exception("details cannot be null");
+ }
+
+ try
+ {
+ return UploadSpiDetailsInternal(requests);
+ }
+ catch (Exception ex)
+ {
+ throw new Exception(
+ $"Failed to upload SPI detail batch: {ex.Message}", ex);
+ }
+ }
+
+ private SpiAoiDetailUploadResponse UploadSpiDetailsInternal(
+ List<SpiAoiDetailDto> requests)
+ {
+ var detailList = requests.ToList();
+ if (detailList.Count == 0)
+ {
+ throw new Exception("details cannot be empty");
+ }
+
+ ValidateDetailData(detailList);
+
+ var affectedRows = SqlSugarHelper.UseTransactionWithOracle(db =>
+ {
+ var total = 0;
+ foreach (var detail in detailList)
+ {
+ var detailEntity = ConvertDetailDtoToEntity(detail);
+ total += db.Insertable(detailEntity).ExecuteCommand();
+ }
+
+ return total;
+ });
+
+ if (affectedRows <= 0)
+ {
+ throw new Exception(
+ "SPI detail insert returned no affected rows.");
+ }
+
+ return new SpiAoiDetailUploadResponse
+ {
+ DetailCount = affectedRows
+ };
+ }
+
+ /// <summary>
+ /// Retrieve SPI/AOI data by board barcode.
+ /// </summary>
+ /// <param name="boardBarcode">Board barcode.</param>
+ /// <returns>Header and detail tuple.</returns>
+ public (MesSpiAoiHeader header, List<MesSpiAoiDetail> details) GetByBarcode(
+ string boardBarcode)
{
try
{
@@ -105,16 +244,12 @@
}
catch (Exception ex)
{
- throw new Exception($"鏌ヨSPI/AOI妫�娴嬫暟鎹け璐�: {ex.Message}", ex);
+ throw new Exception($"鏌ヨSPI/AOI妫�娴嬫暟鎹け锟�? {ex.Message}", ex);
}
}
- /// <summary>
- /// 鏍规嵁ID鏌ヨSPI/AOI妫�娴嬫暟鎹�
- /// </summary>
- /// <param name="headerId">涓昏〃ID</param>
- /// <returns>妫�娴嬫暟鎹�(涓昏〃+瀛愯〃)</returns>
- public (MesSpiAoiHeader header, List<MesSpiAoiDetail> details) GetById(decimal headerId)
+ public (MesSpiAoiHeader header, List<MesSpiAoiDetail> details) GetById(
+ decimal headerId)
{
try
{
@@ -137,20 +272,20 @@
}
catch (Exception ex)
{
- throw new Exception($"鏌ヨSPI/AOI妫�娴嬫暟鎹け璐�: {ex.Message}", ex);
+ throw new Exception($"鏌ヨSPI/AOI妫�娴嬫暟鎹け锟�? {ex.Message}", ex);
}
}
/// <summary>
- /// 鍒嗛〉鏌ヨSPI/AOI妫�娴嬫暟鎹�
+ /// 鍒嗛〉鏌ヨSPI/AOI妫�娴嬫暟锟�?
/// </summary>
/// <param name="boardBarcode">鏉$爜(鍙��)</param>
/// <param name="workOrder">宸ュ崟(鍙��)</param>
/// <param name="surface">鏉块潰(鍙��)</param>
- /// <param name="startDate">寮�濮嬫棩鏈�(鍙��)</param>
+ /// <param name="startDate">寮�濮嬫棩锟�?鍙��)</param>
/// <param name="endDate">缁撴潫鏃ユ湡(鍙��)</param>
/// <param name="pageIndex">椤电爜</param>
- /// <param name="pageSize">椤靛ぇ灏�</param>
+ /// <param name="pageSize">椤靛ぇ锟�?/param>
/// <returns>鍒嗛〉鏁版嵁</returns>
public (List<MesSpiAoiHeader> items, int totalCount) GetPage(
string boardBarcode = null,
@@ -184,137 +319,66 @@
}
catch (Exception ex)
{
- throw new Exception($"鍒嗛〉鏌ヨSPI/AOI妫�娴嬫暟鎹け璐�: {ex.Message}", ex);
+ throw new Exception($"鍒嗛〉鏌ヨSPI/AOI妫�娴嬫暟鎹け锟�? {ex.Message}", ex);
}
}
#region 绉佹湁鏂规硶
/// <summary>
- /// 鏍¢獙璇锋眰鍙傛暟
+ /// Validate AOI header payload.
/// </summary>
- /// <param name="request">璇锋眰DTO</param>
- private void ValidateRequest(SpiAoiUploadRequest request)
+ /// <param name="header">Header DTO.</param>
+ private void ValidateHeader(SpiAoiHeaderDto header)
{
- if (request == null)
+ if (header == null)
{
- throw new Exception("璇锋眰鍙傛暟涓嶈兘涓虹┖");
+ throw new Exception("header cannot be null");
}
- if (request.Header == null)
+ if (StringUtil.IsNullOrEmpty(header.TestDate))
{
- throw new Exception("header 涓嶈兘涓虹┖");
+ throw new Exception("testDate is required");
}
- if (request.Details == null || request.Details.Count == 0)
+ if (StringUtil.IsNullOrEmpty(header.TestTime))
{
- throw new Exception("details 涓嶈兘涓虹┖");
+ throw new Exception("testTime is required");
}
- // 鏍¢獙蹇呭~瀛楁
- if (StringUtil.IsNullOrEmpty(request.Header.TestDate))
+ if (StringUtil.IsNullOrEmpty(header.TestResult))
{
- throw new Exception("testDate 涓嶈兘涓虹┖");
+ throw new Exception("testResult is required");
}
- if (StringUtil.IsNullOrEmpty(request.Header.TestTime))
+ if (StringUtil.IsNullOrEmpty(header.BoardBarcode))
{
- throw new Exception("testTime 涓嶈兘涓虹┖");
+ throw new Exception("boardBarcode is required");
}
- if (StringUtil.IsNullOrEmpty(request.Header.TestResult))
+ if (StringUtil.IsNullOrEmpty(header.Surface))
{
- throw new Exception("testResult 涓嶈兘涓虹┖");
+ throw new Exception("surface is required");
}
- if (StringUtil.IsNullOrEmpty(request.Header.BoardBarcode))
+ if (header.Surface != "T" && header.Surface != "B")
{
- throw new Exception("boardBarcode 涓嶈兘涓虹┖");
+ throw new Exception("surface must be T or B");
}
- if (StringUtil.IsNullOrEmpty(request.Header.Surface))
+ if (header.BoardBarcode.Length > 128)
{
- throw new Exception("surface 涓嶈兘涓虹┖");
+ throw new Exception("boardBarcode cannot exceed 128 characters");
}
- // 鏍¢獙鏋氫妇鍊�
- if (request.Header.Surface != "T" && request.Header.Surface != "B")
+ if (!StringUtil.IsNullOrEmpty(header.TestResult) &&
+ header.TestResult.Length > 12)
{
- throw new Exception("surface 蹇呴』涓� T 鎴� B");
- }
-
- // 鏍¢獙瀛楃涓查暱搴�
- if (request.Header.BoardBarcode.Length > 128)
- {
- throw new Exception("boardBarcode 闀垮害涓嶈兘瓒呰繃 128 瀛楃");
- }
-
- if (request.Header.TestResult.Length > 12)
- {
- throw new Exception("testResult 闀垮害涓嶈兘瓒呰繃 12 瀛楃");
- }
-
- // 鏍¢獙鏁板�奸潪璐�
- foreach (var detail in request.Details)
- {
- if (detail.OffsetCount < 0 || detail.MissingCount < 0 ||
- detail.ReverseCount < 0 || detail.LiftedCount < 0 ||
- detail.FloatHighCount < 0 || detail.TombstoneCount < 0 ||
- detail.FlipCount < 0 || detail.WrongPartCount < 0 ||
- detail.LeadLiftCount < 0 || detail.ColdJointCount < 0 ||
- detail.NoSolderCount < 0 || detail.InsufficientSolderCount < 0 ||
- detail.ExcessSolderCount < 0 || detail.BridgeCount < 0 ||
- detail.CopperExposureCount < 0 || detail.SpikeCount < 0 ||
- detail.ForeignMatterCount < 0 || detail.GlueOverflowCount < 0 ||
- detail.PinOffsetCount < 0 || detail.InputBoards < 0 ||
- detail.OkBoards < 0 || detail.PassBoards < 0 ||
- detail.DefectBoards < 0 || detail.DefectPoints < 0 ||
- detail.MeasuredPoints < 0 || detail.PendingPoints < 0)
- {
- throw new Exception("鎵�鏈夎鏁板瓧娈靛繀椤� >= 0");
- }
+ throw new Exception("testResult cannot exceed 12 characters");
}
}
- /// <summary>
- /// 鏍¢獙瀛愯〃鏁版嵁(璁板綍璀﹀憡浣嗕笉闃绘柇)
- /// </summary>
- /// <param name="details">瀛愯〃DTO鍒楄〃</param>
- private void ValidateDetailData(List<SpiAoiDetailDto> details)
- {
- foreach (var detail in details)
- {
- // 鏍¢獙 passBoards <= inputBoards
- if (detail.PassBoards > detail.InputBoards)
- {
- Console.WriteLine($"[璀﹀憡] passBoards({detail.PassBoards}) 澶т簬 inputBoards({detail.InputBoards})");
- }
-
- // 鏍¢獙 defectBoards = inputBoards - passBoards
- var expectedDefectBoards = detail.InputBoards - detail.PassBoards;
- if (Math.Abs(detail.DefectBoards - expectedDefectBoards) > 0)
- {
- Console.WriteLine($"[璀﹀憡] defectBoards({detail.DefectBoards}) 涓庤绠楀��({expectedDefectBoards})涓嶄竴鑷�");
- }
-
- // 鏍¢獙 passRate 鍋忓樊鍦� 卤1.0 浠ュ唴
- if (detail.InputBoards > 0 && detail.PassRate.HasValue)
- {
- var expectedPassRate = (decimal)detail.PassBoards / detail.InputBoards * 100;
- var deviation = Math.Abs(detail.PassRate.Value - expectedPassRate);
- if (deviation > 1.0m)
- {
- Console.WriteLine($"[璀﹀憡] passRate({detail.PassRate}) 涓庤绠楀��({expectedPassRate:F2})鍋忓樊瓒呰繃1.0");
- }
- }
- }
- }
-
- /// <summary>
- /// 灏嗕富琛―TO杞崲涓哄疄浣�
- /// </summary>
- /// <param name="dto">涓昏〃DTO</param>
- /// <returns>涓昏〃瀹炰綋</returns>
+
private MesSpiAoiHeader ConvertHeaderDtoToEntity(SpiAoiHeaderDto dto)
{
return new MesSpiAoiHeader
@@ -335,18 +399,50 @@
}
/// <summary>
- /// 灏嗗瓙琛―TO鍒楄〃杞崲涓哄疄浣撳垪琛�
+ /// Perform non-blocking SPI detail checks (warnings only).
/// </summary>
- /// <param name="dtoList">瀛愯〃DTO鍒楄〃</param>
- /// <param name="headerId">涓昏〃ID</param>
- /// <returns>瀛愯〃瀹炰綋鍒楄〃</returns>
- private List<MesSpiAoiDetail> ConvertDetailDtoListToEntity(
- List<SpiAoiDetailDto> dtoList, decimal headerId)
+ /// <param name="details">Detail DTO list.</param>
+ private void ValidateDetailData(List<SpiAoiDetailDto> details)
+ {
+ foreach (var detail in details)
+ {
+ // Validate passBoards <= inputBoards
+ if (detail.PassBoards > detail.InputBoards)
+ {
+ Console.WriteLine(
+ $"[Warning] passBoards({detail.PassBoards}) is greater than inputBoards({detail.InputBoards}).");
+ }
+
+ // Validate defectBoards = inputBoards - passBoards
+ var expectedDefectBoards = detail.InputBoards - detail.PassBoards;
+ if (Math.Abs(detail.DefectBoards - expectedDefectBoards) > 0)
+ {
+ Console.WriteLine(
+ $"[Warning] defectBoards({detail.DefectBoards}) does not match the expected value ({expectedDefectBoards}).");
+ }
+
+ // Validate passRate deviation stays within +/- 1.0
+ if (detail.InputBoards > 0 && detail.PassRate.HasValue)
+ {
+ var expectedPassRate = (decimal)detail.PassBoards /
+ detail.InputBoards * 100;
+ var deviation =
+ Math.Abs(detail.PassRate.Value - expectedPassRate);
+ if (deviation > 1.0m)
+ {
+ Console.WriteLine(
+ $"[Warning] passRate({detail.PassRate}) deviates from the expected value ({expectedPassRate:F2}) by more than 1.0.");
+ }
+ }
+ }
+ }
+
+ private MesSpiAoiDetail ConvertDetailDtoToEntity(SpiAoiDetailDto dto)
{
var now = DateTime.Now;
- return dtoList.Select(dto => new MesSpiAoiDetail
+ return new MesSpiAoiDetail
{
- HeaderId = headerId,
+ HeaderId = dto.HeaderId ?? 0,
OffsetCount = dto.OffsetCount,
MissingCount = dto.MissingCount,
ReverseCount = dto.ReverseCount,
@@ -380,7 +476,7 @@
PendingPoints = dto.PendingPoints,
CreatedAt = now,
UpdatedAt = now
- }).ToList();
+ };
}
#endregion
--
Gitblit v1.9.3