InvenTree 消耗性零件(Consumable Parts)详解:低价值物料的“轻跟踪”设计及其源码实现
【免费下载链接】InvenTreeOpen Source Inventory Management System项目地址: https://gitcode.com/GitHub_Trending/in/InvenTree
在 InvenTree 中,Consumable(消耗性)零件是一种在构建或其他工艺过程中被“用掉/耗尽”,而不是像离散元件那样被逐一分配(allocate)跟踪的物料类型,典型如胶水、焊锡、清洁液、紧固件等车间耗材。本文以官方文档 consumable.md 为主线,完整梳理消耗性零件的定义、适用场景与库存/物料清单(BOM)/工单(Build Order)三方面的行为差异,并深入 InvenTree 后端源码,展示Part.consumable与BomItem.consumable两个布尔标志如何在is_consumable判定与工单分配调用链中真正生效,帮助你决定何时使用零件级标志、何时使用 BOM 行级标志。
什么是消耗性零件
按照 文档 的定义,消耗性零件是在构建或其他流程中通常被消耗掉的物料,它不要求以离散实体的方式被分配与跟踪。文档列举的典型用途包括:
- 胶水或粘合剂(Glue or adhesive)
- 焊锡(Solder)
- 清洁液(Cleaning fluid)
- 紧固件或其他低价值五金件(Fasteners or other low-value hardware)
- 其他通用车间耗材(general workshop supplies)
将零件标记为消耗性的目的,是在整个库存中把这类“耗材”与其他零件区分开来,从而更容易地筛选和统计“真正的”在库物料与通用耗材。
在源码中,这一概念落地为Part模型上的一个布尔字段,默认值为False:
consumable = models.BooleanField( default=False, verbose_name=_('Consumable'), help_text=_('Is this part consumable, such as glue or a fastener?'), )见 Part 模型字段定义,该字段由 part/migrations/0151_part_consumable.py 迁移引入。
为什么要将零件标记为消耗性
并非每个完成构建所需的物品,都需要像高价值元件那样被严格跟踪与分配。文档给出了四类常见原因,可以对照自身业务逐条判断:
- 低价值:精确分配的行政成本高于物料本身价值(Low value);
- 库存充裕:库存量大到几乎不用担心断料(Kept in abundant stock);
- 难以按离散单位跟踪:例如流体或胶黏剂这类无法逐件计数的物料(Difficult to track in discrete units);
- 业务不需要在该零件上具备工单级别的可见性(Not something the business needs visibility into at the build order level)。
文档特别强调:将零件标记为消耗性,可以在该零件被使用的任何位置清晰地传达这一意图,而无需为每一行 BOM 逐项记忆和配置。
消耗性零件的库存行为
与文档描述一致:消耗性零件可以拥有库存项(Stock Item),且库存水平的跟踪与管理与普通零件完全相同。也就是说,consumable标志并不改变库存模型本身——你依然可以对胶水、焊锡等物料做入库、调整与盘点,只是这些数量在工单流程中不会被自动扣减(见下文“工单中的行为”一节)。
这一点也体现在前端:React 前端的零件表单与 BOM 表单中均提供了对应的 Consumable 开关,见 PartForms.tsx 与 BomForms.tsx。
物料清单(BOM)中的消耗性零件
消耗性零件可以像任何其他组件一样,被添加为某个装配体 BOM 的子部件。consumable标志不会阻止零件出现在 BOM 中,也不会改变 BOM 的结构校验,它只在后续的工单分配/消耗环节改变行为。
此外,文档还介绍了与零件自身标志相互独立、但相互叠加的第二种机制——消耗性 BOM 行项目(Consumable BOM Line Items):单个 BOM 行也可以单独被标记为consumable(参见 BOM 文档中的相应小节)。这允许一个平时并未标记为消耗性的零件,在某个特定 BOM 的语境下按消耗性处理。
文档给出的典型场景值得记牢:某个紧固件在装配体 A 中被精确跟踪,但在装配体 B 中被当作耗材处理——只需在 B 的 BOM 行上勾选 consumable,而不需要修改底层零件定义。
两级 Consumable 标志:零件级 vs BOM 行级
这是本文的关键概念。两级标志的语义总结自文档原文:
- 零件级
Consumable标志:是零件的永久性属性,对该零件在所有使用位置生效; - BOM 行级
Consumable标志:是按装配体的覆盖(override),只影响该零件在那一个特定 BOM中的处理方式。
如果零件本身已经是消耗性的,再额外把对应 BOM 行标记为消耗性,则不会产生任何附加效果。
这条规则在源码中有精确对应。BomItem模型同样携带一个consumable布尔字段:
consumable = models.BooleanField( default=False, verbose_name=_('Consumable'), help_text=_('This BOM item is consumable (it is not tracked in build orders)'), )见 BomItem 模型字段定义。注意其 help_text 已点明核心语义:“该 BOM 项是消耗性的(不会在工单中被跟踪)”。
“或”逻辑由BomItem.is_consumable属性实现:
@property def is_consumable(self) -> bool: """Return True if this BOM line should be treated as consumable. This is the case if either: - The BOM line itself is marked as consumable - The underlying part is marked as consumable """ return self.consumable or self.sub_part.consumable见 is_consumable 属性。由于是逻辑或,零件已标记时 BOM 行再标记确实“无额外效果”;反之,BOM 行单独标记也能让一个非消耗性零件在该 BOM 中按消耗性处理。
同一文件还提供了一个数据库层面的等价查询工具consumable_filter,用于直接在 queryset 上筛选“有效消耗性”的 BOM 行:
@staticmethod def consumable_filter(consumable: bool = True, prefix: str = '') -> Q: f = Q(**{f'{prefix}consumable': True}) | Q(**{ f'{prefix}sub_part__consumable': True }) return f if consumable else ~f见 consumable_filter。它支持prefix参数(如bom_item__),因此既能直接过滤BomItem查询集,也能在过滤BuildLine等关联查询集时穿透到bom_item__sub_part__consumable。工单模块正是大量依赖这个过滤器来排除消耗性行。
工单(Build Order)中的行为:不分配、不消耗
文档对 工单 行为的核心陈述是:
与普通组件不同,消耗性零件不会被分配(allocated)到工单,也不会被工单消耗(consumed)。当工单完成时,消耗性零件的库存数量不会被调整。
如果需要根据构建过程中的实际用量更新消耗性零件的库存水平,必须手动完成。
这不是文档层面的约定,而是贯穿Build模型的多处硬性实现。以下调用链均以bom_item.is_consumable(即前文的“或”逻辑)或BomItem.consumable_filter为判定条件:
1. 分配接口直接跳过消耗性行。Build.allocate_stock在逐条处理分配请求时,先判定并continue,不产生任何BuildItem:
# Ignore allocation for consumable BOM items if build_line.bom_item.is_consumable: continue见 allocate_stock 中的跳过逻辑。
2. 自动分配同样跳过。auto_allocate_tracked_output过滤掉消耗性行后,还在循环内做了双重检查:
tracked_line_items = self.tracked_line_items.filter( part.models.BomItem.consumable_filter( consumable=False, prefix='bom_item__' ), bom_item__sub_part__virtual=False, ) for line_item in tracked_line_items: if bom_item.is_consumable: # Do not auto-allocate stock to consumable BOM items continue见 自动分配逻辑。未跟踪(untracked)的自动分配路径同样使用consumable_filter(consumable=False, ...)先行排除,见 auto_allocate_stock 相关代码。
3. 工单行集合本身就不包含消耗性行。Build的行项目属性在构建 queryset 时直接移除“消耗性”行:源码注释明确写着# Remove any 'consumable' line items,随后应用BomItem.consumable_filter(prefix='bom_item__')进行排除,见 工单行项目过滤。由于消耗性行从行集合中被排除,工单完成时对库存的扣减操作自然也不会触及它们。
4. 消耗性行永远视为“已充分分配”。BuildLine.is_fully_allocated对消耗性行无条件返回True:
def is_fully_allocated(self) -> bool: """Return True if this BuildLine is fully allocated.""" if self.bom_item.is_consumable: return True required = max(0, self.quantity - self.consumed) return self.allocated_quantity() >= required见 is_fully_allocated。这保证消耗性行不会成为工单“未分配完毕”状态检查中的阻塞项。
5. 超分配裁剪不触及消耗性行。工单数量变化后回收多余分配量的trim_allocated_stock,也在遍历前先排除消耗性行,见 trim_allocated_stock。
综合以上五处,可以确认文档所述行为的完整实现语义:消耗性零件在工单中“可见但不参与分配与消耗”——它们仍随 BOM 出现在工单上下文中,但分配、自动分配、超分配裁剪、完成扣减等所有数量操作都会将其跳过。
测试用例:两个测试视角印证“或”语义
单元测试对上述行为有直接验证,可作为行为契约的参照。其中test_consumable_via_part验证的正是文档中“零件级标志等效于 BOM 行级标志”的命题:
即使
bom_item_1自身未标记为消耗性,只要将其sub_part_1标记为消耗性,也应产生与直接标记 BOM 行相同的效果。
测试先断言bom_item_1.consumable为False而bom_item_1.is_consumable为True,随后验证分配行为差异,并注明“另一条(非消耗性的)行应像往常一样被分配”。见 test_consumable_via_part。配合同一测试文件中的其他消耗性相关用例,可以确认该机制在分配、自动分配等各入口下的一致性。
实践建议:如何选择标志层级
结合文档与源码语义,可以给出如下决策指引:
- 优先评估零件级标志。如果一种物料(如胶水、焊锡)在你的业务中任何用途都无需工单级跟踪,直接在零件上勾选 Consumable,所有 BOM 与工单一律生效,维护成本最低;
- 用 BOM 行级标志做按装配体覆盖。同一零件在不同装配体中需要不同跟踪粒度时(如文档的紧固件例子),只在需要“轻跟踪”的那条 BOM 行上标记 consumable,零件定义保持不变;
- 记住库存仍需手动维护。由于工单完成不会扣减消耗性零件库存,实际用量需要通过手动调整库存(如 Stock Take / 手动调整)来体现,这一点在文档中被明确强调;
- 从源码结构看,由于所有工单侧判定都统一走
is_consumable/consumable_filter这两个入口,行为在 API 分配、自动分配、行项目视图、完成扣减之间保持一致,不存在“部分路径仍会分配消耗性零件”的分叉。
至此,消耗性零件在 InvenTree 中的完整语义可以概括为:一个两级“或”逻辑决定的有效标志,驱动工单分配与消耗流程中的系统性豁免,同时保留与普通零件一致的库存管理能力。理解Part.consumable、BomItem.consumable与is_consumable三者的关系,是正确配置车间耗材类物料的关键。
【免费下载链接】InvenTreeOpen Source Inventory Management System项目地址: https://gitcode.com/GitHub_Trending/in/InvenTree
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考