11
tjx
2025-11-12 c6d9fc76d16c944e64d6490eb2a6d866fd7a38f0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
主表字段
测试时间(年、月、日),测试时间(时、分、秒),测试结果(固定为三种:0:0:1;0:测试为OK,0;0;0:1:测试为Fail,T面(分Top(上)面/Bottom(下)面),测试点数,实际不良,设备型号,批次工单,机种名,条码,SMT组别,线别
 
子表字段
偏位,缺件,反向,翘起,浮高,立碑,翻转,错件,翘脚,虚焊,空焊,少锡,多锡,连锡,漏铜,拉尖,异物,溢胶,引脚偏位,生产线名称,机器名称,投入板数,OK板数,通过板数,合格率,不良板数,不良率,不良率(PPM),不良点数,实测点数,待测点数
 
执行目标
- 将 `MES.Service/service/QC/SPIAOI.txt`:1-7 中的 SPI/AOI 检测字段转化为一个可调用的 REST API,并补充数据库建表与持久化逻辑。
- 目标覆盖请求契约、数据校验、主从表建模、存储过程/服务保存流程以及异常与日志处理。
 
数据结构拆解
- `主表字段` → 记录单次 SPI/AOI 检测的头信息。建议字段/类型:
  测试日期(`TestDate`/DATE)、测试时间(`TestTime`/VARCHAR2(8 CHAR))、测试结果(`TestResult`/VARCHAR2(12 CHAR),值域:`0:0:1;0`、`0;0;0:1`、`Fail`)、面别(`Surface`/CHAR(1),值域 `T`/`B`)、测试点数(`TotalPoints`/NUMBER(6))、实际不良点数(`ActualDefects`/NUMBER(6))、设备型号(`EquipmentModel`/VARCHAR2(64 CHAR))、批次工单(`WorkOrder`/VARCHAR2(64 CHAR))、机种名(`ProductModel`/VARCHAR2(64 CHAR))、条码(`BoardBarcode`/VARCHAR2(128 CHAR))、SMT 组别(`SmtGroup`/VARCHAR2(32 CHAR))、线别(`LineName`/VARCHAR2(32 CHAR))。
- `子表字段` → 记录单次检测的缺陷统计。建议字段/类型:
  偏位(`OffsetCount`/NUMBER(6))、缺件(`MissingCount`/NUMBER(6))、反向(`ReverseCount`/NUMBER(6))、翘起(`LiftedCount`/NUMBER(6))、浮高(`FloatHighCount`/NUMBER(6))、立碑(`TombstoneCount`/NUMBER(6))、翻转(`FlipCount`/NUMBER(6))、错件(`WrongPartCount`/NUMBER(6))、翘脚(`LeadLiftCount`/NUMBER(6))、虚焊(`ColdJointCount`/NUMBER(6))、空焊(`NoSolderCount`/NUMBER(6))、少锡(`InsufficientSolderCount`/NUMBER(6))、多锡(`ExcessSolderCount`/NUMBER(6))、连锡(`BridgeCount`/NUMBER(6))、漏铜(`CopperExposureCount`/NUMBER(6))、拉尖(`SpikeCount`/NUMBER(6))、异物(`ForeignMatterCount`/NUMBER(6))、溢胶(`GlueOverflowCount`/NUMBER(6))、引脚偏位(`PinOffsetCount`/NUMBER(6))、生产线名称(`LineDisplayName`/VARCHAR2(64 CHAR))、机器名称(`MachineName`/VARCHAR2(64 CHAR))、投入板数(`InputBoards`/NUMBER(6))、OK板数(`OkBoards`/NUMBER(6))、通过板数(`PassBoards`/NUMBER(6))、合格率(`PassRate`/NUMBER(5,2))、不良板数(`DefectBoards`/NUMBER(6))、不良率(`DefectRate`/NUMBER(5,2))、不良率PPM(`DefectPpm`/NUMBER(9))、不良点数(`DefectPoints`/NUMBER(6))、实测点数(`MeasuredPoints`/NUMBER(6))、待测点数(`PendingPoints`/NUMBER(6))。所有“率”字段以百分比存储,入库前转为数值型。
 
数据库设计
- 目标库为 Oracle 11g(11.2),本项目使用 `SqlSugar` 并通过 `[SugarColumn(OracleSequenceName = "...")]` 自动取号,因此只需声明主键和序列,无需触发器。示例 DDL:
```
-- 主表:记录每块电路板的 SPI/AOI 批次检测摘要数据
-- SPI/AOI 主表,记录单板批次检测摘要
CREATE TABLE MES_SPI_AOI_HEADER (
    ID               NUMBER(19) NOT NULL,
    TEST_DATE        DATE        NOT NULL,
    TEST_TIME        VARCHAR2(8 CHAR) NOT NULL,
    TEST_RESULT      VARCHAR2(12 CHAR) NOT NULL,
    SURFACE          CHAR(1)     NOT NULL CHECK (SURFACE IN ('T','B')),
    TOTAL_POINTS     NUMBER(6),
    ACTUAL_DEFECTS   NUMBER(6),
    EQUIPMENT_MODEL  VARCHAR2(64 CHAR),
    WORK_ORDER       VARCHAR2(64 CHAR),
    PRODUCT_MODEL    VARCHAR2(64 CHAR),
    BOARD_BARCODE    VARCHAR2(128 CHAR) NOT NULL,
    SMT_GROUP        VARCHAR2(32 CHAR),
    LINE_NAME        VARCHAR2(32 CHAR),
    CREATED_AT       TIMESTAMP   DEFAULT SYSTIMESTAMP,
    UPDATED_AT       TIMESTAMP   DEFAULT SYSTIMESTAMP
);
ALTER TABLE MES_SPI_AOI_HEADER
  ADD CONSTRAINT PK_SPI_AOI_HEADER PRIMARY KEY (ID);
CREATE UNIQUE INDEX IDX_SPI_AOI_BARCODE ON MES_SPI_AOI_HEADER (BOARD_BARCODE);
 
COMMENT ON TABLE MES_SPI_AOI_HEADER IS 'SPI/AOI 主表,记录单板检测摘要数据';
COMMENT ON COLUMN MES_SPI_AOI_HEADER.ID IS '主键ID,由序列 SEQ_SPI_AOI_HEADER 生成';
COMMENT ON COLUMN MES_SPI_AOI_HEADER.TEST_DATE IS '测试日期,沿用设备上传的年月日';
COMMENT ON COLUMN MES_SPI_AOI_HEADER.TEST_TIME IS '测试时间,沿用设备上传的 HH:mm:ss';
COMMENT ON COLUMN MES_SPI_AOI_HEADER.TEST_RESULT IS '测试结果原始字符串,例如 0:0:1;0、0;0;0:1、Fail';
COMMENT ON COLUMN MES_SPI_AOI_HEADER.SURFACE IS '检测面,T=Top 顶面,B=Bottom 底面';
COMMENT ON COLUMN MES_SPI_AOI_HEADER.TOTAL_POINTS IS '计划检测点数';
COMMENT ON COLUMN MES_SPI_AOI_HEADER.ACTUAL_DEFECTS IS '实际不良点数';
COMMENT ON COLUMN MES_SPI_AOI_HEADER.EQUIPMENT_MODEL IS '设备型号';
COMMENT ON COLUMN MES_SPI_AOI_HEADER.WORK_ORDER IS '对应工单或批次号';
COMMENT ON COLUMN MES_SPI_AOI_HEADER.PRODUCT_MODEL IS '机种名称';
COMMENT ON COLUMN MES_SPI_AOI_HEADER.BOARD_BARCODE IS '板件条码,整单唯一';
COMMENT ON COLUMN MES_SPI_AOI_HEADER.SMT_GROUP IS 'SMT 组别';
COMMENT ON COLUMN MES_SPI_AOI_HEADER.LINE_NAME IS '线别名称';
COMMENT ON COLUMN MES_SPI_AOI_HEADER.CREATED_AT IS '创建时间,默认写入数据库时间戳';
COMMENT ON COLUMN MES_SPI_AOI_HEADER.UPDATED_AT IS '更新时间,默认写入数据库时间戳';
 
CREATE SEQUENCE SEQ_SPI_AOI_HEADER START WITH 1 INCREMENT BY 1 NOCACHE;
 
-- SPI/AOI 明细表,拆分缺陷统计与产能指标
CREATE TABLE MES_SPI_AOI_DETAIL (
    ID                       NUMBER(19) NOT NULL,
    HEADER_ID                NUMBER(19) NOT NULL,
    OFFSET_COUNT             NUMBER(6) DEFAULT 0,
    MISSING_COUNT            NUMBER(6) DEFAULT 0,
    REVERSE_COUNT            NUMBER(6) DEFAULT 0,
    LIFTED_COUNT             NUMBER(6) DEFAULT 0,
    FLOAT_HIGH_COUNT         NUMBER(6) DEFAULT 0,
    TOMBSTONE_COUNT          NUMBER(6) DEFAULT 0,
    FLIP_COUNT               NUMBER(6) DEFAULT 0,
    WRONG_PART_COUNT         NUMBER(6) DEFAULT 0,
    LEAD_LIFT_COUNT          NUMBER(6) DEFAULT 0,
    COLD_JOINT_COUNT         NUMBER(6) DEFAULT 0,
    NO_SOLDER_COUNT          NUMBER(6) DEFAULT 0,
    INSUFFICIENT_SOLDER_COUNT NUMBER(6) DEFAULT 0,
    EXCESS_SOLDER_COUNT      NUMBER(6) DEFAULT 0,
    BRIDGE_COUNT             NUMBER(6) DEFAULT 0,
    COPPER_EXPOSURE_COUNT    NUMBER(6) DEFAULT 0,
    SPIKE_COUNT              NUMBER(6) DEFAULT 0,
    FOREIGN_MATTER_COUNT     NUMBER(6) DEFAULT 0,
    GLUE_OVERFLOW_COUNT      NUMBER(6) DEFAULT 0,
    PIN_OFFSET_COUNT         NUMBER(6) DEFAULT 0,
    LINE_DISPLAY_NAME        VARCHAR2(64 CHAR),
    MACHINE_NAME             VARCHAR2(64 CHAR),
    INPUT_BOARDS             NUMBER(6) DEFAULT 0,
    OK_BOARDS                NUMBER(6) DEFAULT 0,
    PASS_BOARDS              NUMBER(6) DEFAULT 0,
    PASS_RATE                NUMBER(5,2),
    DEFECT_BOARDS            NUMBER(6) DEFAULT 0,
    DEFECT_RATE              NUMBER(5,2),
    DEFECT_PPM               NUMBER(9),
    DEFECT_POINTS            NUMBER(6) DEFAULT 0,
    MEASURED_POINTS          NUMBER(6) DEFAULT 0,
    PENDING_POINTS           NUMBER(6) DEFAULT 0,
    CREATED_AT               TIMESTAMP DEFAULT SYSTIMESTAMP,
    UPDATED_AT               TIMESTAMP DEFAULT SYSTIMESTAMP
);
ALTER TABLE MES_SPI_AOI_DETAIL
  ADD CONSTRAINT PK_SPI_AOI_DETAIL PRIMARY KEY (ID);
ALTER TABLE MES_SPI_AOI_DETAIL
  ADD CONSTRAINT FK_SPI_AOI_DETAIL_HEADER FOREIGN KEY (HEADER_ID)
  REFERENCES MES_SPI_AOI_HEADER(ID);
 
CREATE INDEX IDX_SPI_AOI_DETAIL_HEADER ON MES_SPI_AOI_DETAIL (HEADER_ID);
 
COMMENT ON TABLE MES_SPI_AOI_DETAIL IS 'SPI/AOI 明细表,记录缺陷分类统计与产线效率数据';
COMMENT ON COLUMN MES_SPI_AOI_DETAIL.ID IS '主键ID,由序列 SEQ_SPI_AOI_DETAIL 生成';
COMMENT ON COLUMN MES_SPI_AOI_DETAIL.HEADER_ID IS '主表外键,指向 MES_SPI_AOI_HEADER.ID';
COMMENT ON COLUMN MES_SPI_AOI_DETAIL.OFFSET_COUNT IS '偏位数量';
COMMENT ON COLUMN MES_SPI_AOI_DETAIL.MISSING_COUNT IS '缺件数量';
COMMENT ON COLUMN MES_SPI_AOI_DETAIL.REVERSE_COUNT IS '反向安装数量';
COMMENT ON COLUMN MES_SPI_AOI_DETAIL.LIFTED_COUNT IS '翘起数量';
COMMENT ON COLUMN MES_SPI_AOI_DETAIL.FLOAT_HIGH_COUNT IS '浮高数量';
COMMENT ON COLUMN MES_SPI_AOI_DETAIL.TOMBSTONE_COUNT IS '立碑数量';
COMMENT ON COLUMN MES_SPI_AOI_DETAIL.FLIP_COUNT IS '翻转数量';
COMMENT ON COLUMN MES_SPI_AOI_DETAIL.WRONG_PART_COUNT IS '错件数量';
COMMENT ON COLUMN MES_SPI_AOI_DETAIL.LEAD_LIFT_COUNT IS '翘脚数量';
COMMENT ON COLUMN MES_SPI_AOI_DETAIL.COLD_JOINT_COUNT IS '虚焊数量';
COMMENT ON COLUMN MES_SPI_AOI_DETAIL.NO_SOLDER_COUNT IS '空焊数量';
COMMENT ON COLUMN MES_SPI_AOI_DETAIL.INSUFFICIENT_SOLDER_COUNT IS '少锡数量';
COMMENT ON COLUMN MES_SPI_AOI_DETAIL.EXCESS_SOLDER_COUNT IS '多锡数量';
COMMENT ON COLUMN MES_SPI_AOI_DETAIL.BRIDGE_COUNT IS '连锡数量';
COMMENT ON COLUMN MES_SPI_AOI_DETAIL.COPPER_EXPOSURE_COUNT IS '漏铜数量';
COMMENT ON COLUMN MES_SPI_AOI_DETAIL.SPIKE_COUNT IS '拉尖数量';
COMMENT ON COLUMN MES_SPI_AOI_DETAIL.FOREIGN_MATTER_COUNT IS '异物残留数量';
COMMENT ON COLUMN MES_SPI_AOI_DETAIL.GLUE_OVERFLOW_COUNT IS '溢胶数量';
COMMENT ON COLUMN MES_SPI_AOI_DETAIL.PIN_OFFSET_COUNT IS '引脚偏位数量';
COMMENT ON COLUMN MES_SPI_AOI_DETAIL.LINE_DISPLAY_NAME IS '产线显示名称';
COMMENT ON COLUMN MES_SPI_AOI_DETAIL.MACHINE_NAME IS '检测机台名称';
COMMENT ON COLUMN MES_SPI_AOI_DETAIL.INPUT_BOARDS IS '投入板数量';
COMMENT ON COLUMN MES_SPI_AOI_DETAIL.OK_BOARDS IS 'OK 板数量';
COMMENT ON COLUMN MES_SPI_AOI_DETAIL.PASS_BOARDS IS '通过板数量';
COMMENT ON COLUMN MES_SPI_AOI_DETAIL.PASS_RATE IS '合格率(%)';
COMMENT ON COLUMN MES_SPI_AOI_DETAIL.DEFECT_BOARDS IS '不良板数量';
COMMENT ON COLUMN MES_SPI_AOI_DETAIL.DEFECT_RATE IS '不良率(%)';
COMMENT ON COLUMN MES_SPI_AOI_DETAIL.DEFECT_PPM IS '不良数(PPM)';
COMMENT ON COLUMN MES_SPI_AOI_DETAIL.DEFECT_POINTS IS '不良点数';
COMMENT ON COLUMN MES_SPI_AOI_DETAIL.MEASURED_POINTS IS '实测点数';
COMMENT ON COLUMN MES_SPI_AOI_DETAIL.PENDING_POINTS IS '待测点数';
COMMENT ON COLUMN MES_SPI_AOI_DETAIL.CREATED_AT IS '创建时间,默认写入数据库时间戳';
COMMENT ON COLUMN MES_SPI_AOI_DETAIL.UPDATED_AT IS '更新时间,默认写入数据库时间戳';
 
CREATE SEQUENCE SEQ_SPI_AOI_DETAIL START WITH 1 INCREMENT BY 1 NOCACHE;
 
测试结果说明
- `TestResult` 字段直接接收原始字符串,不做枚举校验
- 常见值参考:
  - "0:0:1;0" (通常表示测试通过)
  - "0;0;0:1" (通常表示测试失败)
  - "Fail" (通常表示测试异常)
- 数据库存储原始值,由业务层根据需要解析
 
接口设计
- URL:`POST /api/QC/SpiAoi/Upload`(位于 `MESApplication.Controllers.QC` 命名空间,沿用 `ResponseResult` 返回格式)。
- 请求体结构:
```
{
  "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": 120,
      "okBoards": 117,
      "passBoards": 117,
      "passRate": 97.5,
      "defectBoards": 3,
      "defectRate": 2.5,
      "defectPpm": 25000,
      "defectPoints": 3,
      "measuredPoints": 500,
      "pendingPoints": 0
    }
  ]
}
```
- 响应示例:
```
{
  "status": 0,
  "message": "OK",
  "data": { "headerId": 12345 }
}
```
 
业务处理流程
- 入口控制器负责模型绑定与基本校验:
  - 必填字段:testDate、testTime、testResult、boardBarcode、surface
  - 枚举合法性:surface 必须为 "T" 或 "B"
  - testResult:不做枚举校验,直接接收原始字符串
  - 数值非负:所有计数字段必须 >= 0
  - **details 不能为空或 null**
- 将请求委托给 `SpiAoiService`(位于 `MES.Service.service.QC`),该服务使用 `SqlSugarClient` 启动事务:
  1. **条码唯一性检测**:若 `BOARD_BARCODE` 已存在,直接返回错误 `status=1, message="条码已存在,不允许重复上传"`(保持唯一索引约束)。
  2. 插入头表记录,返回 `HEADER_ID`(实体通过 `[SugarColumn(IsPrimaryKey = true, OracleSequenceName = "SEQ_SPI_AOI_HEADER")]` 自动获取序列值)。
  3. 遍历 `details`,为每条填充 `HEADER_ID` 并插入子表。
  4. **衍生字段校验**(记录日志但不阻断):
     - `passBoards <= inputBoards`
     - `defectBoards = inputBoards - passBoards`
     - `passRate` 与计算值 `(passBoards / inputBoards * 100)` 的偏差在 ±1.0 以内
     - 若校验失败,记录警告日志但不回滚事务
  5. 提交事务;异常则回滚并记录日志。
- 成功后返回 `headerId`;失败返回 `status=1` 与错误信息。
 
验证与错误处理
- **输入校验失败**:返回 `status=1`,`message` 指明字段及原因(如:"boardBarcode 不能为空"、"surface 必须为 T 或 B")。
- **条码重复**:返回 `status=1, message="条码 {boardBarcode} 已存在,不允许重复上传"`。
- **details 为空**:返回 `status=1, message="details 不能为空"`。
- **数据库异常**:捕获 `SqlSugar`/Oracle 异常,记录到现有日志(如 `LogUtil`),同时写入消息中心(若需 ERP 同步)或自定义错误队列。
 
实体定义示例
```csharp
[SugarTable("MES_SPI_AOI_HEADER")]
public class SpiAoiHeader
{
    [SugarColumn(ColumnName = "ID", IsPrimaryKey = true, OracleSequenceName = "SEQ_SPI_AOI_HEADER")]
    public long Id { get; set; }
 
    [SugarColumn(ColumnName = "TEST_DATE")]
    public DateTime TestDate { get; set; }
 
    [SugarColumn(ColumnName = "TEST_TIME")]
    public string TestTime { get; set; }
 
    [SugarColumn(ColumnName = "TEST_RESULT")]
    public string TestResult { get; set; }
 
    [SugarColumn(ColumnName = "SURFACE")]
    public string Surface { get; set; }
 
    [SugarColumn(ColumnName = "TOTAL_POINTS")]
    public int? TotalPoints { get; set; }
 
    [SugarColumn(ColumnName = "ACTUAL_DEFECTS")]
    public int? ActualDefects { get; set; }
 
    [SugarColumn(ColumnName = "EQUIPMENT_MODEL")]
    public string EquipmentModel { get; set; }
 
    [SugarColumn(ColumnName = "WORK_ORDER")]
    public string WorkOrder { get; set; }
 
    [SugarColumn(ColumnName = "PRODUCT_MODEL")]
    public string ProductModel { get; set; }
 
    [SugarColumn(ColumnName = "BOARD_BARCODE")]
    public string BoardBarcode { get; set; }
 
    [SugarColumn(ColumnName = "SMT_GROUP")]
    public string SmtGroup { get; set; }
 
    [SugarColumn(ColumnName = "LINE_NAME")]
    public string LineName { get; set; }
 
    [SugarColumn(ColumnName = "CREATED_AT")]
    public DateTime CreatedAt { get; set; }
 
    [SugarColumn(ColumnName = "UPDATED_AT")]
    public DateTime UpdatedAt { get; set; }
}
 
[SugarTable("MES_SPI_AOI_DETAIL")]
public class SpiAoiDetail
{
    [SugarColumn(ColumnName = "ID", IsPrimaryKey = true, OracleSequenceName = "SEQ_SPI_AOI_DETAIL")]
    public long Id { get; set; }
 
    [SugarColumn(ColumnName = "HEADER_ID")]
    public long HeaderId { get; set; }
 
    [SugarColumn(ColumnName = "OFFSET_COUNT")]
    public int OffsetCount { get; set; }
 
    [SugarColumn(ColumnName = "MISSING_COUNT")]
    public int MissingCount { get; set; }
 
    [SugarColumn(ColumnName = "REVERSE_COUNT")]
    public int ReverseCount { get; set; }
 
    [SugarColumn(ColumnName = "LIFTED_COUNT")]
    public int LiftedCount { get; set; }
 
    [SugarColumn(ColumnName = "FLOAT_HIGH_COUNT")]
    public int FloatHighCount { get; set; }
 
    [SugarColumn(ColumnName = "TOMBSTONE_COUNT")]
    public int TombstoneCount { get; set; }
 
    [SugarColumn(ColumnName = "FLIP_COUNT")]
    public int FlipCount { get; set; }
 
    [SugarColumn(ColumnName = "WRONG_PART_COUNT")]
    public int WrongPartCount { get; set; }
 
    [SugarColumn(ColumnName = "LEAD_LIFT_COUNT")]
    public int LeadLiftCount { get; set; }
 
    [SugarColumn(ColumnName = "COLD_JOINT_COUNT")]
    public int ColdJointCount { get; set; }
 
    [SugarColumn(ColumnName = "NO_SOLDER_COUNT")]
    public int NoSolderCount { get; set; }
 
    [SugarColumn(ColumnName = "INSUFFICIENT_SOLDER_COUNT")]
    public int InsufficientSolderCount { get; set; }
 
    [SugarColumn(ColumnName = "EXCESS_SOLDER_COUNT")]
    public int ExcessSolderCount { get; set; }
 
    [SugarColumn(ColumnName = "BRIDGE_COUNT")]
    public int BridgeCount { get; set; }
 
    [SugarColumn(ColumnName = "COPPER_EXPOSURE_COUNT")]
    public int CopperExposureCount { get; set; }
 
    [SugarColumn(ColumnName = "SPIKE_COUNT")]
    public int SpikeCount { get; set; }
 
    [SugarColumn(ColumnName = "FOREIGN_MATTER_COUNT")]
    public int ForeignMatterCount { get; set; }
 
    [SugarColumn(ColumnName = "GLUE_OVERFLOW_COUNT")]
    public int GlueOverflowCount { get; set; }
 
    [SugarColumn(ColumnName = "PIN_OFFSET_COUNT")]
    public int PinOffsetCount { get; set; }
 
    [SugarColumn(ColumnName = "LINE_DISPLAY_NAME")]
    public string LineDisplayName { get; set; }
 
    [SugarColumn(ColumnName = "MACHINE_NAME")]
    public string MachineName { get; set; }
 
    [SugarColumn(ColumnName = "INPUT_BOARDS")]
    public int InputBoards { get; set; }
 
    [SugarColumn(ColumnName = "OK_BOARDS")]
    public int OkBoards { get; set; }
 
    [SugarColumn(ColumnName = "PASS_BOARDS")]
    public int PassBoards { get; set; }
 
    [SugarColumn(ColumnName = "PASS_RATE")]
    public decimal? PassRate { get; set; }
 
    [SugarColumn(ColumnName = "DEFECT_BOARDS")]
    public int DefectBoards { get; set; }
 
    [SugarColumn(ColumnName = "DEFECT_RATE")]
    public decimal? DefectRate { get; set; }
 
    [SugarColumn(ColumnName = "DEFECT_PPM")]
    public int? DefectPpm { get; set; }
 
    [SugarColumn(ColumnName = "DEFECT_POINTS")]
    public int DefectPoints { get; set; }
 
    [SugarColumn(ColumnName = "MEASURED_POINTS")]
    public int MeasuredPoints { get; set; }
 
    [SugarColumn(ColumnName = "PENDING_POINTS")]
    public int PendingPoints { get; set; }
 
    [SugarColumn(ColumnName = "CREATED_AT")]
    public DateTime CreatedAt { get; set; }
 
    [SugarColumn(ColumnName = "UPDATED_AT")]
    public DateTime UpdatedAt { get; set; }
}
```
 
实现步骤建议
1. **创建数据库表**:执行上述 DDL,创建 `MES_SPI_AOI_HEADER`、`MES_SPI_AOI_DETAIL` 表及序列。
2. **定义实体模型**:在 `MES.Service.Modes` 下创建 `SpiAoiHeader.cs` 和 `SpiAoiDetail.cs`(参考上述实体定义示例)。
3. **创建 DTO**:
   - `SpiAoiHeaderDto.cs`(用于请求体 header 部分)
   - `SpiAoiDetailDto.cs`(用于请求体 details 数组元素)
   - `SpiAoiUploadRequest.cs`(包含 Header 和 Details 列表)
   - `SpiAoiUploadResponse.cs`(包含 headerId)
4. **实现服务层**:
   - 在 `MES.Service.service.QC` 下创建 `SpiAoiService.cs`
   - 实现条码唯一性检查、事务插入、数据校验等业务逻辑
5. **实现控制器**:
   - 在 `MESApplication.Controllers.QC` 下创建 `SpiAoiController.cs`
   - 添加 `[HttpPost("Upload")]` 方法,调用服务层并返回 `ResponseResult`
6. **添加单元测试**(可选):验证成功插入、非法枚举、条码重复、details 为空、事务回滚等场景。
 
注意事项
- 所有字符串字段需检查长度是否超过数据库定义(如 `BOARD_BARCODE` 最大 128 字符)。
- `TestDate` 和 `TestTime` 分开存储,前端需分别传入日期和时间字符串。
- `TestResult` 不做枚举校验,接收任意字符串值(最大 12 字符),原样存储到数据库。
- 对于 `CREATED_AT` 和 `UPDATED_AT`,建议在代码中显式赋值 `DateTime.Now`,而非依赖数据库默认值。
- Oracle 11g 需确保 SqlSugar 版本兼容(推荐 5.x 或更高版本)。
- 条码唯一索引 `IDX_SPI_AOI_BARCODE` 会自动阻止重复插入,但建议在代码中提前检查并返回友好错误信息。