From 9057d0f6f3a46b93d62d0b71c7f4f03eca41f3a9 Mon Sep 17 00:00:00 2001
From: tjx <t2856754968@163.com>
Date: 星期三, 15 十月 2025 16:43:14 +0800
Subject: [PATCH] 11111
---
/dev/null | 167 --------
MES.Service/service/QC/SpiAoiService.cs | 444 ++++++++++++++---------
MESApplication/Docs/Spi_API.md | 140 +++++++
MESApplication/Docs/Aoi_API.md | 134 +++++++
MESApplication/Controllers/QC/SpiAoiController.cs | 188 +++++++++
MES.Service/Dto/service/SpiAoiDto.cs | 42 -
6 files changed, 733 insertions(+), 382 deletions(-)
diff --git a/MES.Service/Dto/service/SpiAoiDto.cs b/MES.Service/Dto/service/SpiAoiDto.cs
index 602a5eb..1360bc7 100644
--- a/MES.Service/Dto/service/SpiAoiDto.cs
+++ b/MES.Service/Dto/service/SpiAoiDto.cs
@@ -1,22 +1,6 @@
namespace MES.Service.Dto.service;
/// <summary>
-/// SPI/AOI涓婁紶璇锋眰DTO
-/// </summary>
-public class SpiAoiUploadRequest
-{
- /// <summary>
- /// 涓昏〃鏁版嵁
- /// </summary>
- public SpiAoiHeaderDto Header { get; set; }
-
- /// <summary>
- /// 瀛愯〃鏁版嵁鍒楄〃
- /// </summary>
- public List<SpiAoiDetailDto> Details { get; set; }
-}
-
-/// <summary>
/// SPI/AOI涓昏〃DTO
/// </summary>
public class SpiAoiHeaderDto
@@ -32,7 +16,7 @@
public string TestTime { get; set; }
/// <summary>
- /// 娴嬭瘯缁撴灉(濡傦細0:0:1;0銆�0;0;0:1銆丗ail绛�)
+ /// 娴嬭瘯缁撴灉(濡傦細0:0:1;0銆?;0;0:1銆丗ail绛?
/// </summary>
public string TestResult { get; set; }
@@ -62,7 +46,7 @@
public string? WorkOrder { get; set; }
/// <summary>
- /// 鏈虹鍚�
+ /// 鏈虹鍚?
/// </summary>
public string? ProductModel { get; set; }
@@ -93,10 +77,14 @@
public string? MachineName { get; set; }
/// <summary>
- /// 鐢熶骇绾垮悕绉�
+ /// 鐢熶骇绾垮悕绉?
/// </summary>
public string? LineDisplayName { get; set; }
+ /// <summary>
+ /// Legacy header identifier (optional).
+ /// </summary>
+ public decimal? HeaderId { get; set; }
/// <summary>
/// 鍋忎綅鏁伴噺
/// </summary>
@@ -208,7 +196,7 @@
public int PassBoards { get; set; }
/// <summary>
- /// 鍚堟牸鐜�(%)
+ /// 鍚堟牸鐜?%)
/// </summary>
public decimal? PassRate { get; set; }
@@ -218,12 +206,12 @@
public int DefectBoards { get; set; }
/// <summary>
- /// 涓嶈壇鐜�(%)
+ /// 涓嶈壇鐜?%)
/// </summary>
public decimal? DefectRate { get; set; }
/// <summary>
- /// 涓嶈壇鐜�(PPM)
+ /// 涓嶈壇鐜?PPM)
/// </summary>
public int? DefectPpm { get; set; }
@@ -244,15 +232,21 @@
}
/// <summary>
-/// SPI/AOI涓婁紶鍝嶅簲DTO
+/// AOI涓昏〃涓婁紶鍝嶅簲DTO
/// </summary>
-public class SpiAoiUploadResponse
+public class SpiAoiHeaderUploadResponse
{
/// <summary>
/// 涓昏〃ID
/// </summary>
public decimal HeaderId { get; set; }
+}
+/// <summary>
+/// SPI鏄庣粏涓婁紶鍝嶅簲DTO
+/// </summary>
+public class SpiAoiDetailUploadResponse
+{
/// <summary>
/// 鎻掑叆鐨勫瓙琛ㄨ褰曟暟
/// </summary>
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
diff --git a/MESApplication/Controllers/QC/SpiAoiController.cs b/MESApplication/Controllers/QC/SpiAoiController.cs
index 1339d59..c9f08c7 100644
--- a/MESApplication/Controllers/QC/SpiAoiController.cs
+++ b/MESApplication/Controllers/QC/SpiAoiController.cs
@@ -1,4 +1,6 @@
+using System.Collections.Generic;
using System.Dynamic;
+using System.Linq;
using MES.Service.Dto.service;
using MES.Service.Modes;
using MES.Service.service;
@@ -20,34 +22,186 @@
private readonly MessageCenterManager _manager = new();
private readonly SpiAoiService _service = new();
- private readonly string METHOD = "POST";
- private readonly string TableName = "MES_SPI_AOI_HEADER";
- private readonly string URL = "http://localhost:10054/api/QC/SpiAoi/";
+ private const string METHOD = "POST";
+ private const string HeaderTableName = "MES_SPI_AOI_HEADER";
+ private const string DetailTableName = "MES_SPI_AOI_DETAIL";
+ private const string BaseUrl = "http://localhost:10054/api/QC/SpiAoi/";
/// <summary>
- /// 涓婁紶SPI/AOI妫�娴嬫暟鎹�
+ /// Upload AOI header data.
/// </summary>
- /// <param name="request">涓婁紶璇锋眰</param>
- /// <returns>涓婁紶缁撴灉</returns>
- [HttpPost("Upload")]
- public ResponseResult Upload([FromBody] SpiAoiUploadRequest request)
+ /// <param name="header">AOI header payload.</param>
+ /// <returns>Upload result.</returns>
+ [HttpPost("UploadAoiHeader")]
+ public ResponseResult UploadAoiHeader(
+ [FromBody] SpiAoiHeaderDto header)
{
- var entity = new MessageCenter();
- entity.TableName = TableName;
- entity.Url = URL + "Upload";
- entity.Method = METHOD;
- entity.Data = JsonConvert.SerializeObject(request);
- entity.Status = 1;
- entity.CreateBy = "SPI_AOI_SYSTEM";
+ var entity = new MessageCenter
+ {
+ TableName = HeaderTableName,
+ Url = BaseUrl + "UploadAoiHeader",
+ Method = METHOD,
+ Data = JsonConvert.SerializeObject(header),
+ Status = 1,
+ CreateBy = "SPI_AOI_SYSTEM"
+ };
try
{
- var response = _service.UploadSpiAoiData(request);
+ var response = _service.UploadAoiHeader(header);
dynamic resultInfos = new ExpandoObject();
resultInfos.headerId = response.HeaderId;
+ resultInfos.message = "AOI header upload succeeded";
+
+ entity.Result = 1;
+ entity.DealWith = 1;
+ entity.ResultData = JsonConvert.SerializeObject(response);
+ _manager.save(entity);
+
+ return new ResponseResult
+ {
+ status = 0,
+ message = "OK",
+ data = resultInfos
+ };
+ }
+ catch (Exception ex)
+ {
+ entity.Result = 0;
+ entity.DealWith = 0;
+ entity.ResultData = ex.Message;
+ _manager.save(entity);
+
+ return ResponseResult.ResponseError(ex);
+ }
+ }
+
+ /// <summary>
+ /// Batch upload AOI header data.
+ /// </summary>
+ /// <param name="headers">AOI header payload collection.</param>
+ /// <returns>Upload result.</returns>
+ [HttpPost("UploadAoiHeaderBatch")]
+ public ResponseResult UploadAoiHeaderBatch(
+ [FromBody] List<SpiAoiHeaderDto> headers)
+ {
+ var entity = new MessageCenter
+ {
+ TableName = HeaderTableName,
+ Url = BaseUrl + "UploadAoiHeaderBatch",
+ Method = METHOD,
+ Data = JsonConvert.SerializeObject(headers),
+ Status = 1,
+ CreateBy = "SPI_AOI_SYSTEM"
+ };
+
+ try
+ {
+ var responses = _service.UploadAoiHeaderBatch(headers);
+ dynamic resultInfos = new ExpandoObject();
+ resultInfos.headerIds = responses.Select(r => r.HeaderId).ToList();
+ resultInfos.message = "AOI header batch upload succeeded";
+
+ entity.Result = 1;
+ entity.DealWith = 1;
+ entity.ResultData = JsonConvert.SerializeObject(responses);
+ _manager.save(entity);
+
+ return new ResponseResult
+ {
+ status = 0,
+ message = "OK",
+ data = resultInfos
+ };
+ }
+ catch (Exception ex)
+ {
+ entity.Result = 0;
+ entity.DealWith = 0;
+ entity.ResultData = ex.Message;
+ _manager.save(entity);
+
+ return ResponseResult.ResponseError(ex);
+ }
+ }
+
+ /// <summary>
+ /// Upload SPI detail data.
+ /// </summary>
+ /// <param name="request">SPI detail payload.</param>
+ /// <returns>Upload result.</returns>
+ [HttpPost("UploadSpiDetails")]
+ public ResponseResult UploadSpiDetails(
+ [FromBody] SpiAoiDetailDto request)
+ {
+ var entity = new MessageCenter
+ {
+ TableName = DetailTableName,
+ Url = BaseUrl + "UploadSpiDetails",
+ Method = METHOD,
+ Data = JsonConvert.SerializeObject(request),
+ Status = 1,
+ CreateBy = "SPI_AOI_SYSTEM"
+ };
+
+ try
+ {
+ var response = _service.UploadSpiDetails(request);
+
+ dynamic resultInfos = new ExpandoObject();
resultInfos.detailCount = response.DetailCount;
- resultInfos.message = "SPI/AOI妫�娴嬫暟鎹笂浼犳垚鍔�";
+ resultInfos.message = "SPI detail upload succeeded";
+
+ entity.Result = 1;
+ entity.DealWith = 1;
+ entity.ResultData = JsonConvert.SerializeObject(response);
+ _manager.save(entity);
+
+ return new ResponseResult
+ {
+ status = 0,
+ message = "OK",
+ data = resultInfos
+ };
+ }
+ catch (Exception ex)
+ {
+ entity.Result = 0;
+ entity.DealWith = 0;
+ entity.ResultData = ex.Message;
+ _manager.save(entity);
+
+ return ResponseResult.ResponseError(ex);
+ }
+ }
+
+ /// <summary>
+ /// Batch upload SPI detail data.
+ /// </summary>
+ /// <param name="requests">SPI detail payload collection.</param>
+ /// <returns>Upload result.</returns>
+ [HttpPost("UploadSpiDetailsBatch")]
+ public ResponseResult UploadSpiDetailsBatch(
+ [FromBody] List<SpiAoiDetailDto> requests)
+ {
+ var entity = new MessageCenter
+ {
+ TableName = DetailTableName,
+ Url = BaseUrl + "UploadSpiDetailsBatch",
+ Method = METHOD,
+ Data = JsonConvert.SerializeObject(requests),
+ Status = 1,
+ CreateBy = "SPI_AOI_SYSTEM"
+ };
+
+ try
+ {
+ var response = _service.UploadSpiDetailsBatch(requests);
+
+ dynamic resultInfos = new ExpandoObject();
+ resultInfos.detailCount = response.DetailCount;
+ resultInfos.message = "SPI detail batch upload succeeded";
entity.Result = 1;
entity.DealWith = 1;
diff --git a/MESApplication/Docs/Aoi_API.md b/MESApplication/Docs/Aoi_API.md
new file mode 100644
index 0000000..213d483
--- /dev/null
+++ b/MESApplication/Docs/Aoi_API.md
@@ -0,0 +1,134 @@
+# AOI 鎺ュ彛鏂囨。
+
+## 鍩烘湰淇℃伅
+- 鍩虹璺緞锛歚/api/SpiAoi`锛堢ず渚嬪畬鏁村湴鍧�锛歚http://192.168.1.251:10054/api/SpiAoi`锛�
+- 璇锋眰涓庡搷搴旀牸寮忥細`application/json; charset=utf-8`
+
+## 缁熶竴杩斿洖妯″瀷锛圧esponseResult锛�
+- `status`锛氭暣鏁帮紝`0` 琛ㄧず鎴愬姛锛宍1` 琛ㄧず澶辫触
+- `message`锛氬瓧绗︿覆锛岃繑鍥炶鏄庢垨閿欒淇℃伅
+- `data`锛氬璞★紝涓氬姟鏁版嵁杞戒綋
+- `TotalCount`锛氭暣鏁帮紝浠呭垎椤垫帴鍙d娇鐢紝琛ㄧず鎬昏褰曟暟
+
+
+### Aoi鏁版嵁璇存槑
+璁惧涓庝骇绾匡細
+- `machineName`锛氬瓧绗︿覆锛屽彲绌恒�傛娴嬫満鍙板悕绉般��
+- `lineDisplayName`锛氬瓧绗︿覆锛屽彲绌恒�備骇绾挎樉绀哄悕绉般��
+
+缂洪櫡璁℃暟锛堝潎涓烘暣鏁帮紝蹇呴』 鈮�0锛夛細
+- `offsetCount`锛氬亸浣嶆暟閲忥紙鍣ㄤ欢璐磋鍋忕Щ锛夈��
+- `missingCount`锛氱己浠舵暟閲忥紙鍣ㄤ欢缂哄け锛夈��
+- `reverseCount`锛氬弽鍚戞暟閲忥紙鍣ㄤ欢鏋佹��/鏂瑰悜鍙嶈锛夈��
+- `liftedCount`锛氱繕璧锋暟閲忥紙鍣ㄤ欢涓�绔垨鏁翠綋缈樿捣锛夈��
+- `floatHighCount`锛氭诞楂樻暟閲忥紙鍣ㄤ欢楂樺害瓒呭樊锛夈��
+- `tombstoneCount`锛氱珛纰戞暟閲忥紙鐗囧紡鍣ㄤ欢涓�绔珫璧凤級銆�
+- `flipCount`锛氱炕杞暟閲忥紙鍣ㄤ欢缈婚潰/缈昏韩锛夈��
+- `wrongPartCount`锛氶敊浠舵暟閲忥紙鍨嬪彿/瑙勬牸閿欒锛夈��
+- `leadLiftCount`锛氱繕鑴氭暟閲忥紙寮曡剼鏈创浼忥級銆�
+- `coldJointCount`锛氳櫄鐒婃暟閲忥紙鍐风剨/鍋囩剨锛夈��
+- `noSolderCount`锛氱┖鐒婃暟閲忥紙鐒婃枡缂哄け锛夈��
+- `insufficientSolderCount`锛氬皯閿℃暟閲忥紙鐒婃枡涓嶈冻锛夈��
+- `excessSolderCount`锛氬閿℃暟閲忥紙鐒婃枡杩囬噺锛夈��
+- `bridgeCount`锛氳繛閿℃暟閲忥紙鐒婇敗妗ヨ繛锛夈��
+- `copperExposureCount`锛氭紡閾滄暟閲忥紙鐒婄洏/绾胯矾閾滅當澶栭湶锛夈��
+- `spikeCount`锛氭媺灏栨暟閲忥紙閿℃枡鎷夊皷锛夈��
+- `foreignMatterCount`锛氬紓鐗╂暟閲忥紙姹℃煋/棰楃矑锛夈��
+- `glueOverflowCount`锛氭孩鑳舵暟閲忥紙鑳舵按澶栨孩锛夈��
+- `pinOffsetCount`锛氬紩鑴氬亸浣嶆暟閲忥紙寮曡剼鏈涓級銆�
+
+浜х嚎缁熻锛堥櫎姣旂巼澶栦负鏁存暟锛屽繀椤� 鈮�0锛夛細
+- `inputBoards`锛氭姇鍏ユ澘鏁帮紙杩涘叆妫�娴嬬殑鏉挎暟閲忥級銆�
+- `okBoards`锛歄K 鏉挎暟锛堟娴嬩负 OK 鐨勬澘鏁伴噺锛夈��
+- `passBoards`锛氶�氳繃鏉挎暟锛堟渶缁堝垽瀹氶�氳繃鐨勬澘鏁伴噺锛夈��
+- `passRate`锛氬皬鏁帮紝鍗曚綅鐧惧垎姣斻�傚悎鏍肩巼锛屼緥濡� `98.00` 琛ㄧず 98%銆�
+- `defectBoards`锛氫笉鑹澘鏁帮紙鏈�缁堝垽瀹氫笉鑹殑鏉挎暟閲忥級銆�
+- `defectRate`锛氬皬鏁帮紝鍗曚綅鐧惧垎姣斻�備笉鑹巼銆�
+- `defectPpm`锛氭暣鏁帮紝鍗曚綅 PPM銆備笉鑹暟锛堢櫨涓囧垎鐜囷級銆�
+- `defectPoints`锛氫笉鑹偣鏁帮紙缂洪櫡鐐瑰悎璁★級銆�
+- `measuredPoints`锛氬疄娴嬬偣鏁帮紙瀹為檯瀹屾垚妫�娴嬬殑鐐规暟锛夈��
+- `pendingPoints`锛氬緟娴嬬偣鏁帮紙鏈畬鎴愭娴嬬殑鐐规暟锛夈��
+
+
+杩斿洖瀛楁绀轰緥锛�
+- `headerId`锛氭暟鎹簱鐢熸垚鐨� ID
+- `createdAt`锛氬垱寤烘椂闂�
+- `updatedAt`锛氭洿鏂版椂闂�
+---
+
+## 鎺ュ彛鍒楄〃
+
+浠ヤ笅涓や釜鎺ュ彛瑕嗙洊鍗曠瑪涓庢壒閲忓叆搴撳満鏅紝鍧囦娇鐢� POST銆�
+
+### 1. UploadAoiHeader 鈥� 涓婁紶 AOI Header锛堝崟绗旓級
+- 璺緞锛歚POST /api/SpiAoi/UploadAoiHeader`
+- 鎻忚堪锛氭彃鍏ヤ竴鏉� AOI Header 璁板綍锛屾潯鐮侀噸澶嶆椂杩斿洖閿欒銆�
+- 璇锋眰绀轰緥
+ ```json
+ {
+ "testDate": "2025-10-10",
+ "testTime": "14:33:21",
+ "testResult": "0;0;0:1",
+ "surface": "T",
+ "totalPoints": 500,
+ "actualDefects": 3,
+ "equipmentModel": "SPI-9000",
+ "workOrder": "WO20251010-01",
+ "productModel": "MODEL-ABC",
+ "boardBarcode": "BC123456789",
+ "smtGroup": "A1",
+ "lineName": "SMT-01"
+ }
+ ```
+- 鎴愬姛鍝嶅簲
+ ```json
+ {
+ "status": 0,
+ "message": "OK",
+ "data": {
+ "headerId": 12001,
+ "message": "AOI header upload succeeded"
+ },
+ "TotalCount": 0
+ }
+ ```
+
+### 2. UploadAoiHeaderBatch 鈥� 鎵归噺涓婁紶 AOI Header
+- 璺緞锛歚POST /api/SpiAoi/UploadAoiHeaderBatch`
+- 鎻忚堪锛氫竴娆℃彃鍏ュ鏉� Header 璁板綍锛涜嫢浠讳綍涓�鏉℃牎楠屾垨鎻掑叆澶辫触锛屾暣鎵瑰洖婊氥��
+- 璇锋眰绀轰緥
+ ```json
+ [
+ {
+ "testDate": "2025-10-10",
+ "testTime": "14:33:21",
+ "testResult": "0;0;0:1",
+ "surface": "T",
+ "boardBarcode": "BC123456789"
+ },
+ {
+ "testDate": "2025-10-10",
+ "testTime": "14:34:00",
+ "testResult": "0;0;0:1",
+ "surface": "B",
+ "boardBarcode": "BC123456790"
+ }
+ ]
+ ```
+- 鎴愬姛鍝嶅簲
+ ```json
+ {
+ "status": 0,
+ "message": "OK",
+ "data": {
+ "headerIds": [12001, 12002],
+ "message": "AOI header batch upload succeeded"
+ },
+ "TotalCount": 0
+ }
+ ```
+
+## 璋冪敤寤鸿
+- 涓ユ牸鎸夌収瀛楁绫诲瀷涓庨暱搴︽瀯閫犳暟鎹紱鏁板�肩被瀛楁寤鸿浣跨敤鏁村瀷鎴栦繚鐣欏悎閫傚皬鏁颁綅銆�
+- 鍑虹幇閿欒鏃跺厛鏌ョ湅 `message`锛屽啀缁撳悎娑堟伅涓績璁板綍鍜屾湇鍔$鏃ュ織瀹氫綅闂銆�
+- 寤鸿鍦ㄦ壒閲忚皟鐢ㄥ墠鑷鍘婚噸鏉$爜鎴栧叧閿瓧娈碉紝鍑忓皯鍥炴粴姒傜巼銆�
diff --git a/MESApplication/Docs/SpiAoi_API.md b/MESApplication/Docs/SpiAoi_API.md
deleted file mode 100644
index 86ecb6e..0000000
--- a/MESApplication/Docs/SpiAoi_API.md
+++ /dev/null
@@ -1,167 +0,0 @@
-# SPI/AOI 鎺ュ彛鏂囨。
-
-## 鍩烘湰淇℃伅
-- 鍩虹璺緞锛歚/api/SpiAoi`锛堢ず渚嬪畬鏁村湴鍧�锛歚http://192.168.1.251:10054/api/SpiAoi`锛�
-- 璇锋眰涓庡搷搴旀牸寮忥細`application/json; charset=utf-8`
-
-## 缁熶竴杩斿洖妯″瀷锛圧esponseResult锛�
-- `status`锛氭暣鏁帮紝0=鎴愬姛锛�1=澶辫触
-- `message`锛氬瓧绗︿覆锛岀粨鏋滆鏄庢垨閿欒鍘熷洜
-- `data`锛氬璞★紝涓氬姟鏁版嵁杞戒綋
-- `TotalCount`锛氭暣鏁帮紝浠呭垎椤垫帴鍙h繑鍥烇紝鎬昏褰曟暟
-
-## 鏁版嵁妯″瀷涓庣害鏉燂紙閫愬瓧娈垫敞閲婏級
-浠ヤ笅瀛楁璇存槑鍙傝�冧簡 SPIAOI.txt 涓殑鏁版嵁搴撳畾涔変笌娉ㄩ噴锛岃皟鐢ㄦ柟璇蜂弗鏍兼寜绾︽潫鎻愪緵鏁版嵁銆�
-
-### Header锛堜富琛ㄦ憳瑕侊紝涓婁紶鏃跺繀濉級
-- `testDate`锛氬瓧绗︿覆锛屽繀濉�傛祴璇曟棩鏈燂紝鏍煎紡涓� `yyyy-MM-dd`锛屼笌璁惧涓婁紶鏃ユ湡淇濇寔涓�鑷淬��
-- `testTime`锛氬瓧绗︿覆锛屽繀濉�傛祴璇曟椂闂达紝鏍煎紡涓� `HH:mm:ss`锛屼笌璁惧涓婁紶鏃堕棿淇濇寔涓�鑷淬��
-- `testResult`锛氬瓧绗︿覆锛屽繀濉紝闀垮害鈮�12銆傛祴璇曠粨鏋滃師濮嬪瓧绗︿覆锛屼緥濡� `0:0:1;0`锛堥�氳繃锛夈�乣0;0;0:1`锛堝け璐ワ級銆乣Fail`锛堝紓甯革級銆備笉鍋氳В鏋愶紝鍘熸牱鍏ュ簱銆�
-- `surface`锛氬瓧绗︿覆锛屽繀濉�傛娴嬮潰锛屽彇鍊� `T` 琛ㄧず椤堕潰锛圱op锛夛紝`B` 琛ㄧず搴曢潰锛圔ottom锛夈��
-- `totalPoints`锛氭暣鏁帮紝鍙┖锛屸墺0銆傝鍒掓娴嬬偣鏁般��
-- `actualDefects`锛氭暣鏁帮紝鍙┖锛屸墺0銆傚疄闄呬笉鑹偣鏁帮紙缁熻缁撴灉锛夈��
-- `equipmentModel`锛氬瓧绗︿覆锛屽彲绌恒�傝澶囧瀷鍙凤紙濡� SPI/AOI 鏈哄彴鍨嬪彿锛夈��
-- `workOrder`锛氬瓧绗︿覆锛屽彲绌恒�傜敓浜у伐鍗曟垨鎵规鍙枫��
-- `productModel`锛氬瓧绗︿覆锛屽彲绌恒�傛満绉�/浜у搧鍨嬪彿銆�
-- `boardBarcode`锛氬瓧绗︿覆锛屽繀濉紝闀垮害鈮�128銆傛澘浠舵潯鐮侊紝鍏ㄥ眬鍞竴锛涢噸澶嶅皢琚嫆缁濄��
-- `smtGroup`锛氬瓧绗︿覆锛屽彲绌恒�係MT 缁勫埆銆�
-- `lineName`锛氬瓧绗︿覆锛屽彲绌恒�傜嚎鍒悕绉般��
-
-杩斿洖涓撶敤锛堟煡璇㈡帴鍙d細杩斿洖浠ヤ笅瀛楁锛夛細
-- `id`锛氭暟鍊硷紝鏁版嵁搴撲富閿� ID锛岀敱搴忓垪鐢熸垚銆�
-- `createdAt`锛氬瓧绗︿覆锛屽垱寤烘椂闂�
-- `updatedAt`锛氬瓧绗︿覆锛屾洿鏂版椂闂�
-
-### Detail锛堝瓙琛ㄧ己闄蜂笌浜х嚎鎸囨爣锛屼笂浼犳椂鑷冲皯 1 鏉★級
-璁惧涓庝骇绾匡細
-- `machineName`锛氬瓧绗︿覆锛屽彲绌恒�傛娴嬫満鍙板悕绉般��
-- `lineDisplayName`锛氬瓧绗︿覆锛屽彲绌恒�備骇绾挎樉绀哄悕绉般��
-
-缂洪櫡璁℃暟锛堝潎涓烘暣鏁帮紝蹇呴』 鈮�0锛夛細
-- `offsetCount`锛氬亸浣嶆暟閲忥紙鍣ㄤ欢璐磋鍋忕Щ锛夈��
-- `missingCount`锛氱己浠舵暟閲忥紙鍣ㄤ欢缂哄け锛夈��
-- `reverseCount`锛氬弽鍚戞暟閲忥紙鍣ㄤ欢鏋佹��/鏂瑰悜鍙嶈锛夈��
-- `liftedCount`锛氱繕璧锋暟閲忥紙鍣ㄤ欢涓�绔垨鏁翠綋缈樿捣锛夈��
-- `floatHighCount`锛氭诞楂樻暟閲忥紙鍣ㄤ欢楂樺害瓒呭樊锛夈��
-- `tombstoneCount`锛氱珛纰戞暟閲忥紙鐗囧紡鍣ㄤ欢涓�绔珫璧凤級銆�
-- `flipCount`锛氱炕杞暟閲忥紙鍣ㄤ欢缈婚潰/缈昏韩锛夈��
-- `wrongPartCount`锛氶敊浠舵暟閲忥紙鍨嬪彿/瑙勬牸閿欒锛夈��
-- `leadLiftCount`锛氱繕鑴氭暟閲忥紙寮曡剼鏈创浼忥級銆�
-- `coldJointCount`锛氳櫄鐒婃暟閲忥紙鍐风剨/鍋囩剨锛夈��
-- `noSolderCount`锛氱┖鐒婃暟閲忥紙鐒婃枡缂哄け锛夈��
-- `insufficientSolderCount`锛氬皯閿℃暟閲忥紙鐒婃枡涓嶈冻锛夈��
-- `excessSolderCount`锛氬閿℃暟閲忥紙鐒婃枡杩囬噺锛夈��
-- `bridgeCount`锛氳繛閿℃暟閲忥紙鐒婇敗妗ヨ繛锛夈��
-- `copperExposureCount`锛氭紡閾滄暟閲忥紙鐒婄洏/绾胯矾閾滅當澶栭湶锛夈��
-- `spikeCount`锛氭媺灏栨暟閲忥紙閿℃枡鎷夊皷锛夈��
-- `foreignMatterCount`锛氬紓鐗╂暟閲忥紙姹℃煋/棰楃矑锛夈��
-- `glueOverflowCount`锛氭孩鑳舵暟閲忥紙鑳舵按澶栨孩锛夈��
-- `pinOffsetCount`锛氬紩鑴氬亸浣嶆暟閲忥紙寮曡剼鏈涓級銆�
-
-浜х嚎缁熻锛堥櫎姣旂巼澶栦负鏁存暟锛屽繀椤� 鈮�0锛夛細
-- `inputBoards`锛氭姇鍏ユ澘鏁帮紙杩涘叆妫�娴嬬殑鏉挎暟閲忥級銆�
-- `okBoards`锛歄K 鏉挎暟锛堟娴嬩负 OK 鐨勬澘鏁伴噺锛夈��
-- `passBoards`锛氶�氳繃鏉挎暟锛堟渶缁堝垽瀹氶�氳繃鐨勬澘鏁伴噺锛夈��
-- `passRate`锛氬皬鏁帮紝鍗曚綅鐧惧垎姣斻�傚悎鏍肩巼锛屼緥濡� `98.00` 琛ㄧず 98%銆�
-- `defectBoards`锛氫笉鑹澘鏁帮紙鏈�缁堝垽瀹氫笉鑹殑鏉挎暟閲忥級銆�
-- `defectRate`锛氬皬鏁帮紝鍗曚綅鐧惧垎姣斻�備笉鑹巼銆�
-- `defectPpm`锛氭暣鏁帮紝鍗曚綅 PPM銆備笉鑹暟锛堢櫨涓囧垎鐜囷級銆�
-- `defectPoints`锛氫笉鑹偣鏁帮紙缂洪櫡鐐瑰悎璁★級銆�
-- `measuredPoints`锛氬疄娴嬬偣鏁帮紙瀹為檯瀹屾垚妫�娴嬬殑鐐规暟锛夈��
-- `pendingPoints`锛氬緟娴嬬偣鏁帮紙鏈畬鎴愭娴嬬殑鐐规暟锛夈��
-
-涓氬姟鏍¢獙鎻愮ず锛堟彁绀轰笉闃绘柇锛夛細
-- `passBoards` 涓嶅簲澶т簬 `inputBoards`銆�
-- 寤鸿 `defectBoards = inputBoards - passBoards`锛屽亸宸粎鎻愮ず銆�
-- 褰� `inputBoards > 0` 涓旀彁渚� `passRate` 鏃讹紝寤鸿涓庤绠楀�� `passBoards/inputBoards*100` 鍋忓樊 鈮� 1.0銆�
-
-绯荤粺璁板綍锛氭瘡娆¤皟鐢ㄤ細鍦ㄦ秷鎭腑蹇冭褰曚竴鏉℃祦姘达紙璁板綍鐩爣琛ㄥ悕涓� `MES_SPI_AOI_HEADER`銆佹帴鍙� URL銆佹柟娉曘�佽姹備笌澶勭悊缁撴灉锛夛紝鐢ㄤ簬瀹¤杩借釜銆�
-
----
-
-## 鎺ュ彛鍒楄〃
-
-### 1. 涓婁紶 SPI/AOI 鏁版嵁
-- 璺緞涓庢柟娉曪細`POST /api/SpiAoi/Upload`
-- 鐢ㄩ�旓細鏂板涓�绗斾富琛ㄤ笌澶氱瑪瀛愯〃鏁版嵁锛涜嫢 `boardBarcode` 宸插瓨鍦ㄥ垯澶辫触
-- 璇锋眰绀轰緥锛�
-```json
-{
- "header": {
- "testDate": "2025-10-10",
- "testTime": "14:33:21",
- "testResult": "0;0;0:1",
- "surface": "T",
- "totalPoints": 500,
- "actualDefects": 3,
- "equipmentModel": "SPI-9000",
- "workOrder": "WO20251010-01",
- "productModel": "MODEL-ABC",
- "boardBarcode": "BC123456789",
- "smtGroup": "A1",
- "lineName": "SMT-01"
- },
- "details": [
- {
- "machineName": "AOI-01",
- "lineDisplayName": "SMT-01",
- "offsetCount": 1,
- "missingCount": 0,
- "reverseCount": 0,
- "liftedCount": 0,
- "floatHighCount": 0,
- "tombstoneCount": 0,
- "flipCount": 0,
- "wrongPartCount": 0,
- "leadLiftCount": 0,
- "coldJointCount": 0,
- "noSolderCount": 0,
- "insufficientSolderCount": 1,
- "excessSolderCount": 0,
- "bridgeCount": 1,
- "copperExposureCount": 0,
- "spikeCount": 0,
- "foreignMatterCount": 0,
- "glueOverflowCount": 0,
- "pinOffsetCount": 0,
- "inputBoards": 100,
- "okBoards": 98,
- "passBoards": 98,
- "passRate": 98.00,
- "defectBoards": 2,
- "defectRate": 2.00,
- "defectPpm": 20000,
- "defectPoints": 3,
- "measuredPoints": 500,
- "pendingPoints": 0
- }
- ]
-}
-```
-- 鎴愬姛鍝嶅簲绀轰緥锛�
-```json
-{
- "status": 0,
- "message": "OK",
- "data": {
- "headerId": 12001,
- "detailCount": 1,
- "message": "SPI/AOI妫�娴嬫暟鎹笂浼犳垚鍔�"
- },
- "TotalCount": 0
-}
-```
-- 澶辫触鍝嶅簲绀轰緥锛堟潯鐮侀噸澶嶏級锛�
-```json
-{
- "status": 1,
- "message": "涓婁紶SPI/AOI妫�娴嬫暟鎹け璐�: 鏉$爜 BC123456789 宸插瓨鍦紝涓嶅厑璁搁噸澶嶄笂浼�",
- "data": "涓婁紶SPI/AOI妫�娴嬫暟鎹け璐�: 鏉$爜 BC123456789 宸插瓨鍦紝涓嶅厑璁搁噸澶嶄笂浼�",
- "TotalCount": 0
-}
-```
-
-
-## 璋冪敤寤鸿
-- 涓ユ牸鎸夊瓧娈电害鏉熺粍缁囨暟鎹紱鏁板�煎瓧娈典娇鐢ㄦ暣鏁版垨灏忔暟鐨勬纭被鍨�
-- 鍙戠敓閿欒鏃惰鍙� `message` 渚夸簬瀹氫綅闂锛涗繚鐣欒姹備笌鍝嶅簲浣滀负杩借釜渚濇嵁
diff --git a/MESApplication/Docs/Spi_API.md b/MESApplication/Docs/Spi_API.md
new file mode 100644
index 0000000..63a2088
--- /dev/null
+++ b/MESApplication/Docs/Spi_API.md
@@ -0,0 +1,140 @@
+# SPI 鎺ュ彛鏂囨。
+
+## 鍩烘湰淇℃伅
+- 鍩虹璺緞锛歚/api/SpiAoi`锛堢ず渚嬪畬鏁村湴鍧�锛歚http://192.168.1.251:10054/api/SpiAoi`锛�
+- 璇锋眰涓庡搷搴旀牸寮忥細`application/json; charset=utf-8`
+
+## 缁熶竴杩斿洖妯″瀷锛圧esponseResult锛�
+- `status`锛氭暣鏁帮紝`0` 琛ㄧず鎴愬姛锛宍1` 琛ㄧず澶辫触
+- `message`锛氬瓧绗︿覆锛岃繑鍥炶鏄庢垨閿欒淇℃伅
+- `data`锛氬璞★紝涓氬姟鏁版嵁杞戒綋
+- `TotalCount`锛氭暣鏁帮紝浠呭垎椤垫帴鍙d娇鐢紝琛ㄧず鎬昏褰曟暟
+
+
+### Spi鏁版嵁璇存槑
+- 璁惧 / 浜х嚎锛歚machineName`銆乣lineDisplayName`
+- 缂洪櫡缁熻锛坕nt锛夛細`offsetCount`銆乣missingCount`銆乣reverseCount`銆乣liftedCount`銆乣floatHighCount`銆乣tombstoneCount`銆乣flipCount`銆乣wrongPartCount`銆乣leadLiftCount`銆乣coldJointCount`銆乣noSolderCount`銆乣insufficientSolderCount`銆乣excessSolderCount`銆乣bridgeCount`銆乣copperExposureCount`銆乣spikeCount`銆乣foreignMatterCount`銆乣glueOverflowCount`銆乣pinOffsetCount`
+- 鐢熶骇缁熻锛歚inputBoards`銆乣okBoards`銆乣passBoards`銆乣defectBoards`銆乣defectPoints`銆乣measuredPoints`銆乣pendingPoints`
+- 姣斾緥瀛楁锛坉ecimal?锛夛細`passRate`銆乣defectRate`
+- 鍏跺畠锛歚defectPpm` *(int?)*銆乣headerId` *(decimal?)*
+
+鏃ュ織璁板綍锛氭瘡娆¤皟鐢ㄤ細鍐欏叆娑堟伅涓績锛坄MES_SPI_AOI_HEADER` / `MES_SPI_AOI_DETAIL`锛夛紝淇濆瓨璇锋眰浣撱�佺粨鏋滅瓑淇℃伅澶囩敤銆�
+
+---
+
+## 鎺ュ彛鍒楄〃
+
+浠ヤ笅涓や釜鎺ュ彛瑕嗙洊鍗曠瑪涓庢壒閲忓叆搴撳満鏅紝鍧囦娇鐢� POST銆�
+
+### 1. UploadSpiDetails 鈥� 涓婁紶 SPI鏄庣粏锛堝崟绗旓級
+- 璺緞锛歚POST /api/SpiAoi/UploadSpiDetails`
+- 鎻忚堪锛氭彃鍏ヤ竴鏉℃槑缁嗚褰曪紝闇�鎸囧畾 `headerId`锛堟垨鐢辨暟鎹簱瑙﹀彂鍣ㄨ嚜鍔ㄧ淮鎶わ級銆�
+- 璇锋眰绀轰緥
+ ```json
+ {
+ "headerId": 12001,
+ "machineName": "AOI-01",
+ "lineDisplayName": "SMT-01",
+ "offsetCount": 1,
+ "missingCount": 0,
+ "reverseCount": 0,
+ "liftedCount": 0,
+ "floatHighCount": 0,
+ "tombstoneCount": 0,
+ "flipCount": 0,
+ "wrongPartCount": 0,
+ "leadLiftCount": 0,
+ "coldJointCount": 0,
+ "noSolderCount": 0,
+ "insufficientSolderCount": 1,
+ "excessSolderCount": 0,
+ "bridgeCount": 1,
+ "copperExposureCount": 0,
+ "spikeCount": 0,
+ "foreignMatterCount": 0,
+ "glueOverflowCount": 0,
+ "pinOffsetCount": 0,
+ "inputBoards": 100,
+ "okBoards": 98,
+ "passBoards": 98,
+ "passRate": 98.00,
+ "defectBoards": 2,
+ "defectRate": 2.00,
+ "defectPpm": 20000,
+ "defectPoints": 3,
+ "measuredPoints": 500,
+ "pendingPoints": 0
+ }
+ ```
+- 鎴愬姛鍝嶅簲
+ ```json
+ {
+ "status": 0,
+ "message": "OK",
+ "data": {
+ "detailCount": 1,
+ "message": "SPI detail upload succeeded"
+ },
+ "TotalCount": 0
+ }
+ ```
+
+### 2. UploadSpiDetailsBatch 鈥� 鎵归噺涓婁紶 SPI鏄庣粏
+- 璺緞锛歚POST /api/SpiAoi/UploadSpiDetailsBatch`
+- 鎻忚堪锛氭壒閲忓啓鍏ュ鏉℃槑缁嗭紝浣跨敤鍚屼竴鏁版嵁搴撲簨鍔′繚璇佸師瀛愭�с��
+- 璇锋眰绀轰緥
+ ```json
+ [
+ {
+ "headerId": 12001,
+ "machineName": "AOI-01",
+ "lineDisplayName": "SMT-01",
+ "offsetCount": 1,
+ "missingCount": 0,
+ "reverseCount": 0,
+ "liftedCount": 0,
+ "floatHighCount": 0,
+ "tombstoneCount": 0,
+ "flipCount": 0,
+ "wrongPartCount": 0,
+ "leadLiftCount": 0,
+ "coldJointCount": 0,
+ "noSolderCount": 0,
+ "insufficientSolderCount": 1,
+ "excessSolderCount": 0,
+ "bridgeCount": 1,
+ "copperExposureCount": 0,
+ "spikeCount": 0,
+ "foreignMatterCount": 0,
+ "glueOverflowCount": 0,
+ "pinOffsetCount": 0,
+ "inputBoards": 100,
+ "okBoards": 98,
+ "passBoards": 98,
+ "passRate": 98.00,
+ "defectBoards": 2,
+ "defectRate": 2.00,
+ "defectPpm": 20000,
+ "defectPoints": 3,
+ "measuredPoints": 500,
+ "pendingPoints": 0
+ }
+ ]
+ ```
+- 鎴愬姛鍝嶅簲
+ ```json
+ {
+ "status": 0,
+ "message": "OK",
+ "data": {
+ "detailCount": 5,
+ "message": "SPI detail batch upload succeeded"
+ },
+ "TotalCount": 0
+ }
+ ```
+
+## 璋冪敤寤鸿
+- 涓ユ牸鎸夌収瀛楁绫诲瀷涓庨暱搴︽瀯閫犳暟鎹紱鏁板�肩被瀛楁寤鸿浣跨敤鏁村瀷鎴栦繚鐣欏悎閫傚皬鏁颁綅銆�
+- 鍑虹幇閿欒鏃跺厛鏌ョ湅 `message`锛屽啀缁撳悎娑堟伅涓績璁板綍鍜屾湇鍔$鏃ュ織瀹氫綅闂銆�
+- 寤鸿鍦ㄦ壒閲忚皟鐢ㄥ墠鑷鍘婚噸鏉$爜鎴栧叧閿瓧娈碉紝鍑忓皯鍥炴粴姒傜巼銆�
--
Gitblit v1.9.3