| MES.Service/Dto/service/SpiAoiDto.cs | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| MES.Service/service/QC/SpiAoiService.cs | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| MESApplication/Controllers/QC/SpiAoiController.cs | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| MESApplication/Docs/Aoi_API.md | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| MESApplication/Docs/SpiAoi_API.md | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| MESApplication/Docs/Spi_API.md | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
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ãFailç) /// 娴å¬ç¯ç¼æ´ç(æ¿¡å¦ç´°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> 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> /// å°ä¸»è¡¨DTO转æ¢ä¸ºå®ä½ /// </summary> /// <param name="dto">主表DTO</param> /// <returns>主表å®ä½</returns> private MesSpiAoiHeader ConvertHeaderDtoToEntity(SpiAoiHeaderDto dto) { return new MesSpiAoiHeader @@ -335,18 +399,50 @@ } /// <summary> /// å°å表DTOå表转æ¢ä¸ºå®ä½å表 /// 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 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; 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` ## ç»ä¸è¿å模åï¼ResponseResultï¼ - `status`ï¼æ´æ°ï¼`0` 表示æåï¼`1` 表示失败 - `message`ï¼å符串ï¼è¿å说ææéè¯¯ä¿¡æ¯ - `data`ï¼å¯¹è±¡ï¼ä¸å¡æ°æ®è½½ä½ - `TotalCount`ï¼æ´æ°ï¼ä» å页æ¥å£ä½¿ç¨ï¼è¡¨ç¤ºæ»è®°å½æ° ### 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`ï¼OK æ¿æ°ï¼æ£æµä¸º 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`ï¼åç»åæ¶æ¯ä¸å¿è®°å½åæå¡ç«¯æ¥å¿å®ä½é®é¢ã - å»ºè®®å¨æ¹éè°ç¨åèªè¡å»éæ¡ç æå ³é®å段ï¼åå°åæ»æ¦çã MESApplication/Docs/SpiAoi_API.md
ÎļþÒÑɾ³ý 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` ## ç»ä¸è¿å模åï¼ResponseResultï¼ - `status`ï¼æ´æ°ï¼`0` 表示æåï¼`1` 表示失败 - `message`ï¼å符串ï¼è¿å说ææéè¯¯ä¿¡æ¯ - `data`ï¼å¯¹è±¡ï¼ä¸å¡æ°æ®è½½ä½ - `TotalCount`ï¼æ´æ°ï¼ä» å页æ¥å£ä½¿ç¨ï¼è¡¨ç¤ºæ»è®°å½æ° ### Spiæ°æ®è¯´æ - è®¾å¤ / 产线ï¼`machineName`ã`lineDisplayName` - 缺é·ç»è®¡ï¼intï¼ï¼`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` - æ¯ä¾å段ï¼decimal?ï¼ï¼`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`ï¼åç»åæ¶æ¯ä¸å¿è®°å½åæå¡ç«¯æ¥å¿å®ä½é®é¢ã - å»ºè®®å¨æ¹éè°ç¨åèªè¡å»éæ¡ç æå ³é®å段ï¼åå°åæ»æ¦çã