客户端客诉寄回一件产品,工厂翻了三天记录,只知道大概是上个月做的——用没用密封垫、哪台设备打的螺丝、测试值是多少,一概查不到。更糟的是产线上的日常:工件跳过压装直接进了包装、扫码没成功设备也放了行、同一个码贴了两件货。条码追溯系统的核心价值不是"扫码记个数",而是用一条单品码把工序顺序、质量结果、物料批次、人机料法环全部焊死,任何不合格品在物理上走不出厂门。
一、先设计码:单品码、批次码、箱码、托码是四层,不是一种码
| 层级 | 贴在什么上 | 典型用途 | 生成时机 |
|---|---|---|---|
| 单品码 SN | 每个产品一件一码 | 过站防错、测试绑定、客诉追溯的主键 | 首站(激光刻码/打标/贴标) |
| 批次码 | 内部流转批(注塑批、热处理批) | 把工艺参数相同的一批料与 SN 关联 | 配料/开工时建批 |
| 箱码 | 装箱外标 | 一箱含多个 SN,扫码装箱防错(数量/型号) | 装箱站父子码绑定 |
| 托码 | 托盘 | 多箱组托,仓储发货扫描 | 组托站绑定 |
SN 编码建议定长、纯数字或大写字母(扫码枪识读率最高),结构清晰即可,不必把信息全塞进去:
"A12 260916 001357 8" │ │ │ │ │ │ │ └─ 校验位(Mod10/加权算法),防扫码内容残缺误判 │ │ └────── 当日流水(号段由发码服务集中发放,见标签打印一篇的号段预占) │ └──────────── 生产日期 YYMMDD └───────────────── 产品型号代码(3位) // 规则校验:长度、型号段白名单、日期合法、校验位——四道全过才认, // 残缺码/其他产线串来的码直接红灯,不进任何业务流程。
二、过站模型:工序链是骨架,每站只认"当前应到状态"
一条典型产线的工艺路由是有向链(允许返修分支)。每个产品在数据库里有一个明确状态:当前应到工序。工位扫码时,系统只回答一个问题——这个 SN 现在该不该出现在我这一站:
| 表 | 关键字段 | 作用 |
|---|---|---|
| unit(单件主表) | sn(唯一)、model、current_station、status(在制/合格/隔离/报废)、create_time | 一行一件,状态唯一,防跳站全靠它 |
| unit_station(过站流水) | sn、station、in_time、out_time、result、operator、device、recipe_ver | 只追加,追溯的主证据;同一SN同站可有多次(返修) |
| unit_material(用料绑定) | sn、material_code、batch_no、station | 料批正向召回的关联表 |
| test_result(测试数据) | sn、station、item、value、lo、hi、judge、raw_file | 测试值与SN焊死,曲线文件存路径 |
三、四道防错闸口:把"不可能"挡在产线上
SN 全库唯一约束兜底;首站建档用 INSERT,冲突即报警。防两件货同码、防标签补打误用。
扫码站必须等于 current_station,且前序各站全部 PASS。跳过 S3 直接扫 S4——红灯不放。
已出站的 SN 再扫本站视为异常(防重复加工/重复测试覆盖数据),返修品走授权分支。
测试站:SN 先扫码建档,设备结果只能写入已进站且未出站的 SN;NG 自动流转隔离站。
public enum GateResult { Ok, BadCode, DuplicateSn, WrongStation, AlreadyPassed, PrevNotDone, UnitBlocked, TestNg } public class StationGateService { // 进站校验:扫码枪扫到SN、工人按"进站"时调用 public GateResult CheckIn(string sn, string station, string user) { if (!CodeRule.IsValid(sn)) return GateResult.BadCode; // 格式+校验位 var unit = _db.Units.FirstOrDefault(u => u.Sn == sn); if (unit == null) return station == Route.FirstStation // 只有首站能建新档 ? CreateUnit(sn, station, user) : GateResult.BadCode; if (unit.Status == UnitStatus.Scrapped) return GateResult.UnitBlocked; if (unit.CurrentStation != station) return GateResult.WrongStation; // ② 跳站/走错站 // ③ 本站是否已有"完成"记录:完成过的不允许二次进站,除非带返修授权 var last = _db.StationRecords(sn, station).LastOrDefault(); if (last?.Result == StationResult.Pass) return HasRepairTicket(sn, station) ? BeginRepair(sn, station, user) : GateResult.AlreadyPassed; return GateResult.Ok; } // 出站:测试站必须带判定结果;PASS才把current_station推进到下一站 public GateResult CheckOut(string sn, string station, StationResult result, string user, string device) { var tx = _db.BeginTransaction(); try { AppendStationRecord(sn, station, result, user, device); if (result == StationResult.Ng) { _db.MarkStatus(sn, UnitStatus.Blocked, Route.QuarantineStation); // NG→隔离 tx.Commit(); return GateResult.TestNg; } var next = Route.NextOf(station, isRepair: HasOpenRepair(sn)); _db.Advance(sn, next); // 条件UPDATE:WHERE current_station=@station tx.Commit(); // 并发放行时数据库行数保证只推进一次 return GateResult.Ok; } catch { tx.Rollback(); throw; } } }
UPDATE unit SET current_station=@next WHERE sn=@sn AND current_station=@station 靠数据库行锁保证只推进一次,再检查影响行数。过站流水表对 (sn, station, 进站时间) 建索引,唯一约束加在业务真正唯一的键上。路由本身做成配置而不是写死在代码里:工艺路线表(产品型号 → 有序工序列表、每站是否必过、是否采集测试值、NG 去向)由工艺人员维护,换型或新增工位改配置即可生效,版本变更随工单走——同一型号老工单按旧路由追溯,新工单按新路由过站,历史记录不会因为今天改了路线而对不上。
四、不合格品与返修:不能让"后门"破坏防错链
工厂真实情况是产品要返修,一条只允许直行的链会逼工人想办法绕系统。正确做法是把返修显式建模,让异常也走在系统里:
- NG 自动隔离:测试站结果 NG,单件状态置 Blocked 并指向隔离站,后续包装站扫这个 SN 直接红灯并提示"隔离品",物理辊道可由 PLC 分流(上位机给信号,分流动作仍由 PLC 执行);
- 返修工单授权:质检员开返修单(写清故障、允许返工的工序范围),SN 状态转 Repair,回退到指定工序;返修段每一站照常扫码过站,记录标记 repair_count+1,与正常记录同表但可区分;
- 返修后必须重测:路由配置里返修分支的出口强制经过测试站,重测 PASS 才允许回主线;返修超过 N 次(如 2 次)自动升级为报废评审,不让一件产品无限循环返工;
- 所有特权操作双人登录:强制放行、跳站授权、状态改判这类操作必须主管刷卡 + 操作员登录,原因必填,审计单独成表。
五、扫码枪与防重复提交:现场最烦人的两个小坑
- 键盘口扫码枪就是个快速键盘:它在焦点控件里"敲"完一串字符再带回车。界面上把扫码框设为只读、永远保持焦点、KeyPress 收到回车才触发一次业务;提交期间立即禁用(或用去抖令牌),防止同一条码 500ms 内被处理两次;
- 同码去重令牌:内存缓存最近 3 秒处理过的 SN+站别,重复直接忽略并提示"请勿重复扫描"——这一道独立于数据库,挡住双击/连扫;
- 串口扫码枪更好控:串口数据带枪编号,多枪工位不会串输入,参照此前电子秤一篇的拼包状态机接收;
- 声光反馈不能省:成功绿色短滴 + 屏幕绿勾;失败红色长鸣 + 大字原因("跳站:缺少压装")。工人不看屏幕也能凭声音判断,防错才真防得住。
private readonly Dictionary<string, DateTime> _recent = new(); void OnScanBoxEnter(string sn) { var token = sn + "@" + StationCode; lock (_recent) { if (_recent.TryGetValue(token, out var t) && (DateTime.Now - t).TotalSeconds < 3) { Beep(Bad); return; } _recent[token] = DateTime.Now; } var r = _gate.CheckIn(sn.Trim().ToUpperInvariant(), StationCode, CurrentUser); if (r == GateResult.Ok) { Beep(Ok); ShowGreen(); } else { Beep(BadLong); ShowRed(TextOf(r)); } // 原因必须是人话,不弹异常堆栈 }
标签打花了要补码,是另一个重码高发点。激光码模糊、标签污损后工人常见的"土办法"是随手拿张新码贴上——两件货从此共用一个身份。规范流程是:补码申请必须扫原 SN(原码实在扫不出,由主管在系统里按当前工位在制品列表手工选定),系统将旧 SN 置"换码"状态并生成一张换码关联记录(旧码、新码、操作人、原因、照片可选),新码与旧档案一一映射,旧码再次出现立即报警。任何情况下都不允许凭空生成一个与旧档无关的新 SN。
六、断网也能过站:本地流水 + 服务端裁决合并
车间网络抖动时不能让整条线停下来。过站数据允许本地暂存,但防错裁决权要分清:单机连续工序的跳站判断可用本地最近状态放行;涉及全局唯一(重码)、跨线物料、最终入库的操作必须等服务端。设计上与 MQTT 一篇的 outbox 同源:
- 每站工控机本地 SQLite 暂存过站流水(sn/站/结果/时间/操作人),断网期间继续生产、继续本站防错;
- 网络恢复后按时间顺序补传,服务端用 SN 唯一约束和状态机重新裁决:发现跳站/重码等冲突不静默丢弃,生成"补传冲突单"推给质量人员逐条处理;
- 包装/入库这种最后闸门,断网期间只做暂存不放行实物出厂——网络恢复、服务端确认无冲突后才允许封箱发货。
七、追溯闭环:一个码正反向都能 5 分钟查清
| 方向 | 入口 | 查什么 | 现场场景 |
|---|---|---|---|
| 反向追溯 | 客户报来一个 SN | 过站全记录、测试值与曲线、当时配方版本、设备号、操作人、每个关键件的物料批次 | 客诉定位:到底哪一环出的问题 |
| 正向召回 | 发现某物料批次有问题 | unit_material 反查该批次用在了哪些 SN 上,连带箱码/托码/发货去向 | 精确召回:只追回受影响的那 300 件,不是全月产品 |
包装站是出厂前最后一道软件闸门,父子码绑定也要防错:先扫箱码(或系统按装箱规则自动建箱),再逐件扫 SN,系统实时校验型号与工单是否匹配、箱内数量、是否混装——装满 N 件自动封箱,不满不能封,封箱后子码不可再增减,补装必须先解封并留痕。托码同理层层向上聚合,发货时扫托码即带出整托 SN 清单,与发货单比对,错发、漏发在装车前拦下。
// 反向:SN → 完整履历(一次查询组装,给客诉报告直接用) var genealogy = new { Unit = _db.Units.First(u => u.Sn == sn), Stations = _db.StationRecords(sn).OrderBy(r => r.InTime).ToList(), Tests = _db.TestResults(sn).ToList(), Materials = _db.Materials(sn).ToList(), // 关键件批次,含供应商批号 Box = _db.BoxOf(sn), // 父级箱/托,追到发货单 RecipeVersions = _db.StationRecords(sn).Select(r => r.RecipeVer).Distinct() }; // 正向:物料批次 → 受影响SN(索引:unit_material(batch_no)) var affected = _db.Database.SqlQuery<string>( @"SELECT u.sn FROM unit_material m JOIN unit u ON u.sn=m.sn WHERE m.batch_no=@batch AND u.status IN ('OK','SHIPPED')", new { batch = badBatchNo });
过站时顺手把设备号、配方版本、班次、操作人冗余进 unit_station(不要只存外键,人员改名、配方归档都会破坏历史还原),追溯报告才能真正做到"开箱即读"。
八、上线验收清单
- 重码:拿同一 SN 在首站扫两次,第二次必被唯一约束拦截;补打标签流程走号段预占不用旧码;
- 跳站:跳过压装直接扫测试站、测试未过扫包装站,均红灯且记录拦截原因;
- 返修:NG 品走隔离→返修单→回流→重测→合格回主线,全链路状态正确、repair_count 累计、超限自动转评审;
- 并发:两件货同时扫出站,条件 UPDATE 只放行一次;扫码枪连扫同码只生效一次;
- 断网:拔网线连续生产 20 件,恢复后补传无丢失,人为制造一个跳站冲突验证"冲突单"推送;
- 追溯:随机抽 10 件成品做正反向演练,5 分钟内出完整履历;给定一个物料批次,召回清单数量与实际投产数对平。
过站防错系统做扎实之后,产线的质量逻辑从"靠人记得"变成"系统不让":码不合法不让过、序不对不让过、测不过不让走、特权必留痕、断网不丢账。而每天积累的过站与用料数据,就是质量分析、精确召回和客户审核时最硬的底气——扫码那一刻录入的不是一个编号,是这件产品一生的档案起点。
