批次管理基础:批次概念与生命周期

做MES这么多年,我越来越觉得批次管理是物料追踪的灵魂。没有批次,你根本说不清这批料是哪来的、用到哪去了。说白了,批次就是给物料贴上一个「身份证号」,让每一份物料都有迹可循。

什么是批次?

批次,英文叫Lot或Batch。它代表一批在相同条件下生产的物料。比如同一天、同一条产线、同一台设备、同一批原料生产出来的产品,就可以归为一个批次。

我遇到过不少刚入行的朋友问:「批次和序列号有什么区别?」嗯,这个问题很关键。序列号是唯一标识单个产品,而批次标识的是一组产品。你想想看,如果每个螺丝都打序列号,那成本得多高?但用批次管理,一箱螺丝一个批次号,就够用了。

核心区别:

  • 批次(Lot):一组物料的集合,共享相同属性
  • 序列号(Serial Number):单个物料的唯一标识
  • 应用场景:原材料、半成品用批次;关键零部件、成品用序列号

批次的生命周期

一个批次从生到死,大概经历这么几个阶段。我习惯把它画成一条时间线:

创建 批次号生成 在库 库存管理 投料 生产消耗 产出 成品入库 批次生命周期流程图 创建 → 在库 → 投料 → 产出(或报废)

每个阶段都有对应的状态管理。我举个例子:

阶段 状态 说明
创建 待检验 / 已锁定 刚生成的批次,还不能使用
在库 可用 / 冻结 / 预留 检验合格后释放,可被生产领用
投料 已领用 / 已消耗 批次从仓库转移到产线
产出 已完工 / 已入库 生产完成,形成新的批次
报废 已报废 质量不合格,批次生命周期终止

💡 个人经验:我建议在设计批次状态时,一定要预留「冻结」状态。曾经有个客户,一批原料检验不合格,但系统里没有冻结机制,结果产线照常领用,最后整批产品都要返工。血的教训啊。

批次号生成策略

批次号怎么生成?这问题看似简单,但坑不少。我见过最离谱的批次号是直接用UUID,32位字符串,工人扫码都扫不全。批次号要兼顾可读性、唯一性和扩展性。

策略一:时间戳方案

最常用的方案。格式一般是:YYYYMMDD + 流水号。比如 20250315001,表示2025年3月15日生成的第1个批次。

def generate_batch_by_timestamp():
    from datetime import datetime
    now = datetime.now()
    date_part = now.strftime("%Y%m%d")
    # 假设从Redis获取当日流水号
    seq = get_daily_sequence(date_part)  # 返回001, 002...
    return f"{date_part}{seq:03d}"

# 示例输出
print(generate_batch_by_timestamp())  # 20250315001

这个方案的好处是直观,一看就知道是哪天的批次。但有个问题——如果一天产量很大,流水号可能不够用。我建议流水号至少留4位,或者用时分秒来扩展。

策略二:规则引擎方案

当业务复杂了,简单的日期+流水就不够用了。比如你需要区分产线、班次、物料类型。这时候就要用规则引擎。

class BatchRuleEngine:
    def __init__(self, rules: dict):
        self.rules = rules  # 规则配置
    
    def generate(self, context: dict) -> str:
        parts = []
        for key, rule in self.rules.items():
            if key == 'date':
                parts.append(context['date'])
            elif key == 'line':
                parts.append(f"L{context['line_id']:02d}")
            elif key == 'shift':
                parts.append(f"S{context['shift_id']}")
            elif key == 'material':
                parts.append(context['material_code'][:4])
            elif key == 'seq':
                parts.append(f"{context['seq']:04d}")
        return "-".join(parts)

# 规则配置示例
rules = {
    'date': {'order': 1},
    'line': {'order': 2},
    'shift': {'order': 3},
    'material': {'order': 4},
    'seq': {'order': 5}
}

engine = BatchRuleEngine(rules)
context = {
    'date': '20250315',
    'line_id': 3,
    'shift_id': 1,
    'material_code': 'RAW001',
    'seq': 12
}
print(engine.generate(context))  # 20250315-L03-S1-RAW0-0012

规则引擎的核心优势:

  • 灵活配置,不用改代码就能调整批次号格式
  • 支持多维度组合,满足不同业务场景
  • 可扩展,随时添加新的规则字段

策略三:混合方案(我推荐)

我个人习惯用时间戳+规则引擎的混合方案。简单来说:

  1. 前缀:固定标识,比如公司代码或工厂代码
  2. 时间戳:精确到秒或毫秒,保证唯一性
  3. 业务属性:产线、班次、物料类型等
  4. 校验位:防止人工录入错误
def generate_hybrid_batch(factory, line, material):
    import hashlib
    from datetime import datetime
    
    ts = datetime.now().strftime("%Y%m%d%H%M%S")
    base = f"{factory}{ts}{line:02d}{material[:4]}"
    # 计算校验位
    checksum = hashlib.md5(base.encode()).hexdigest()[:2].upper()
    return f"{base}{checksum}"

# 示例
print(generate_hybrid_batch("SH", 3, "RAW001"))  
# SH2025031514300303RAW0A3

⚠️ 避坑指南:我曾经在一个项目里,批次号用了纯数字+自增ID。结果业务量暴增,ID冲突了。后来改成时间戳+自增,但历史数据又得迁移。所以一开始就要考虑好容量,别等出问题了再改。

批次属性管理

批次号只是标识,真正有价值的是批次属性。你想想看,光知道批次号有什么用?你得知道这批料的供应商、生产日期、质检报告、有效期等等。

属性分类

我把批次属性分成三类:

属性类型 示例 说明
静态属性 物料编码、规格型号、供应商 批次创建时就确定,不会改变
动态属性 库存数量、库位、状态 随业务操作实时更新
扩展属性 质检报告、COA、有效期 根据业务需要灵活添加

属性管理的数据模型

我建议用「属性模板+属性值」的设计模式。这样不同物料类型可以有不同的属性集合。

class BatchAttributeTemplate:
    """批次属性模板"""
    def __init__(self, material_type):
        self.material_type = material_type
        self.attributes = []
    
    def add_attribute(self, name, data_type, required=False):
        self.attributes.append({
            'name': name,
            'data_type': data_type,  # string, int, float, date
            'required': required
        })

class BatchAttributes:
    """批次属性实例"""
    def __init__(self, batch_id, template):
        self.batch_id = batch_id
        self.template = template
        self.values = {}
    
    def set_attribute(self, name, value):
        # 校验数据类型
        attr_def = next(
            (a for a in self.template.attributes if a['name'] == name), 
            None
        )
        if not attr_def:
            raise ValueError(f"属性 {name} 未定义")
        self.values[name] = value
    
    def get_attribute(self, name):
        return self.values.get(name)

# 使用示例
template = BatchAttributeTemplate("原材料")
template.add_attribute("供应商", "string", required=True)
template.add_attribute("批次重量", "float", required=True)
template.add_attribute("生产日期", "date", required=True)
template.add_attribute("有效期至", "date", required=True)

batch_attr = BatchAttributes("B20250315001", template)
batch_attr.set_attribute("供应商", "ABC化工")
batch_attr.set_attribute("批次重量", 500.0)
batch_attr.set_attribute("生产日期", "2025-03-15")
batch_attr.set_attribute("有效期至", "2025-09-15")

💡 实战建议:属性管理一定要支持扩展。我做过一个项目,客户一开始只要求5个属性,半年后增加到20个。如果当初设计成硬编码,那改起来就痛苦了。用模板模式,加属性就像加配置一样简单。

属性查询与追溯

批次属性的核心价值在于查询和追溯。比如:

  • 查某个供应商的所有批次
  • 查某个日期范围内生产的批次
  • 查某个质检指标异常的批次

我建议在数据库里给常用属性建索引。特别是供应商、生产日期、状态这些字段,查询频率很高。

-- 批次属性表设计示例
CREATE TABLE batch_attributes (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    batch_id VARCHAR(50) NOT NULL,
    attr_name VARCHAR(100) NOT NULL,
    attr_value VARCHAR(500),
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_batch_id (batch_id),
    INDEX idx_attr_name (attr_name),
    INDEX idx_attr_value (attr_value)
);

-- 查询某个供应商的所有批次
SELECT DISTINCT batch_id 
FROM batch_attributes 
WHERE attr_name = '供应商' 
  AND attr_value = 'ABC化工';

嗯,到这里批次管理的基础内容就差不多了。批次号生成、属性管理、生命周期,这三个点搞清楚了,后面做物料追踪就顺了。记住一句话:批次是MES的血液,属性是血液里的信息,两者缺一不可。


公众号:蓝海资料掘金营,微信deep3321