1. 食品MES基础数据建模与物料主数据配置
为什么食品MES的物料主数据不能照搬离散制造
离散制造的物料主数据通常只关心“是什么、有多少、在哪道工序用”。食品行业还必须回答四个额外问题:这批原料从哪来(批次/供应商/产地)、什么时候必须用完(保质期/复检期)、在什么条件下存放(温湿度/冷藏冷冻)、能否与其它物料混放或替代(致敏原/清洁状态)。因此本例的建模重点不是把字段堆满,而是把“编码规则 + 批次属性 + 保质期 + 存储条件”四类约束设计成可校验、可被后续配方、工单、质检、追溯直接引用的结构。
物料分类与编码规则设计
先按“物料在食品链中的形态”划分三大类,再在编码中嵌入类别位,使编码本身可读、可排序、可被系统按前缀检索:
| 类别 | 编码前缀 | 编码结构(示例) | 示例 | 关键属性 |
|---|---|---|---|---|
| 原料 RM | RM | RM-大类-流水号 | RM-DRY-0001 小麦粉 | 供应商批次、产地、复检期 |
| 半成品 SF | SF | SF-产线-流水号 | SF-L1-0007 面团 | 产出批次、允许存放时长 |
| 成品 FG | FG | FG-品类-规格-流水号 | FG-BRD-500-0003 吐司500g | 成品保质期、储运温度 |
编码只承载“稳定身份”,可变信息(供应商、批次、库位)一律放到批次层,避免因换供应商就新建物料导致BOM与追溯断裂。
批次属性、保质期与存储条件字段
食品物料主数据必须区分物料级默认值与批次级实际值。物料级定义“这类物料通常的保质期与存储要求”,批次级记录“这一批的实际生产日期、到期日、当前存储条件”。关键字段建议如下:
- 保质期天数 / 复检期天数:原料常用复检期(如面粉90天复检),成品用保质期(如吐司7天)。
- 存储条件代码:常温 DRY、冷藏 CHL(0~4℃)、冷冻 FRZ(≤-18℃),并附温度上下限。
- 致敏原标识:含麸质、含乳、含坚果等,供清线与混放校验使用。
- 批次状态:待检、合格、冻结、过期,决定能否被工单领用。
在MES中落地:字段配置与校验实现
下面给出一段可直接运行的 Python 校验脚本,模拟MES保存物料主数据与批次数据时的完整性检查。它把上面的 C1~C4 约束写成函数,输入是教学示例数据,输出是“通过/拒绝 + 原因”,便于在实施前先验证规则本身是否合理。
from datetime import date, timedelta
STORAGE = {
"DRY": (10, 25), # 常温 10~25℃
"CHL": (0, 4), # 冷藏 0~4℃
"FRZ": (-25, -18), # 冷冻 -25~-18℃
}
def check_material(m):
errs = []
# C1 编码唯一性与前缀
prefix_map = {"原料": "RM", "半成品": "SF", "成品": "FG"}
if not m["code"].startswith(prefix_map[m["category"]]):
errs.append("C1 编码前缀与类别不符")
# C2 保质期
if m["shelf_life_days"] <= 0:
errs.append("C2 保质期必须大于0")
if m["recheck_days"] > m["shelf_life_days"]:
errs.append("C2 复检期不得超过保质期")
# C3 存储条件
if m["storage_code"] not in STORAGE:
errs.append("C3 存储条件代码非法")
else:
lo, hi = STORAGE[m["storage_code"]]
if not (m["temp_min"] < m["temp_max"]):
errs.append("C3 温度下限必须小于上限")
if m["temp_min"] < lo or m["temp_max"] > hi:
errs.append("C3 温度范围超出该存储条件允许区间")
return errs
def check_batch(b, m):
errs = []
# C4 批次一致性
expect = b["prod_date"] + timedelta(days=m["shelf_life_days"])
if b["expire_date"] != expect:
errs.append(f"C4 到期日应为 {expect}")
if b["status"] not in {"待检", "合格", "冻结", "过期"}:
errs.append("C4 批次状态非法")
return errs
# 教学示例数据
mat = {
"code": "RM-DRY-0001", "category": "原料", "name": "小麦粉",
"shelf_life_days": 180, "recheck_days": 90,
"storage_code": "DRY", "temp_min": 10, "temp_max": 25,
}
bat = {
"batch_no": "B20240601-01", "prod_date": date(2024, 6, 1),
"expire_date": date(2024, 11, 28), "status": "合格",
}
print("物料校验:", check_material(mat) or "通过")
print("批次校验:", check_batch(bat, mat) or "通过")
运行后预期输出:物料校验通过;批次校验通过(2024-06-01 加 180 天为 2024-11-28)。若把 recheck_days 改为 200,则会返回“C2 复检期不得超过保质期”,说明规则生效。
实施时把 C1~C4 配置为MES的“保存前校验 + 启用前校验”双层规则:保存前只拦明显错误(编码重复、必填为空),启用前再校验跨字段逻辑(复检期与保质期、温度区间、批次到期日),避免录入过程中频繁打断操作。
验证数据完整性:用一组反例确认约束真的生效
只测“正确数据能保存”不足以证明约束有效,必须构造反例。建议至少覆盖以下四类,并记录系统返回的提示是否指向具体字段:
| 用例 | 构造方式 | 预期结果 |
|---|---|---|
| 编码冲突 | 新建 RM-DRY-0001 重复编码 | 拒绝,提示编码已存在 |
| 复检期越界 | 保质期180、复检期200 | 拒绝,提示复检期不得超过保质期 |
| 存储温度越界 | CHL 条件却填 -20~-18℃ | 拒绝,提示超出冷藏允许区间 |
| 批次到期日错算 | 生产日6/1、保质期180天,到期日填11/27 | 拒绝,提示应为11/28 |
四类反例全部被正确拦截、且提示能定位到字段,才可判定本节的数据完整性约束配置完成;随后配方、工单、质检模块即可安全引用这套物料主数据。