Production Overview
Overview
- What you’ll learn:
- How to define Bills of Materials (BOMs) using MPPProductBOM and MPPProductBOMLine, including BOM types, BOM use, and phantom assemblies
- How to create and configure MProduction documents, including setting products, quantities, locators, and movement dates
- How production lines are automatically generated from BOMs via the createLines() method, including the difference between end-product lines and component consumption lines
- How the DocAction workflow drives production documents from Draft through Completion, triggering inventory transactions
- Prerequisites: Familiarity with iDempiere product management, warehouses, and basic document processing (Lessons 1–23)
- Estimated reading time: 20 minutes
Introduction
Production is the process of transforming raw materials and components into finished goods. For companies that produce their own products — whether assembling electronics, fabricating metal parts, mixing chemicals, or packaging food — an ERP production module is essential for planning, executing, and costing production activities.
iDempiere provides a robust production system through the MProduction model class. This system integrates with Bills of Materials (BOMs) to automatically calculate component requirements, generates inventory transactions upon completion, and follows the standard DocAction document workflow. In this lesson, we focus on the foundational concepts: BOM definition, production order creation, line generation, and the completion workflow.
Bills of Materials (BOM)
A Bill of Materials is the recipe for producing a product. It lists every component, sub-assembly, and raw material needed, along with the required quantities. In iDempiere, BOMs are managed through MPPProductBOM (the header) and MPPProductBOMLine (the components), both in the org.eevolution.model package.
Historical note: The original Compiere/ADempiere used M_Product_BOM (a flat table, one BOM per product). In iDempiere 9 (IDEMPIERE-1250), the PP_Product_BOM tables from the Libero Manufacturing module were integrated into core, enabling multiple BOMs per product. The old M_Product_BOM table now exists as a database VIEW for backward compatibility, and the MProductBOM class is marked @Deprecated.
BOM Header (MPPProductBOM)
The BOM header ties a BOM definition to a specific finished product. Key fields include:
| Field | Description |
|---|---|
| M_Product_ID | The finished product this BOM produces |
| Name / Value | Descriptive name and code for the BOM |
| BOMType | Controls how production planning treats the product: ‘A’ (Current Active), ‘D’ (Make to Order), ‘K’ (Make to Kit) |
| BOMUse | Indicates BOM purpose: ‘A’ (Master — constant BOMUSE_Manufacturing, naming is historical), ‘M’ (Manufacturing), ‘D’ (Phantom/Disassemble), ‘E’ (Engineering), ‘P’ (Planning) |
| Revision | Version identifier for BOM revision control |
| M_AttributeSetInstance_ID | Attribute set instance for the BOM header (e.g., specific product variant) |
A product can have multiple BOMs. The system uses MPPProductBOM.getDefault(product, trxName) to retrieve the master BOM (BOMUse=’A’, BOMType=’A’).
BOM Lines (MPPProductBOMLine)
Each BOM line represents a single component:
| Field | Description |
|---|---|
| M_Product_ID | The component product |
| QtyBOM | Quantity needed per unit of the finished product |
| C_UOM_ID | Unit of measure for the component |
| Line | Sequence number (10, 20, 30…) |
| ComponentType | Role of this line: Component (standard part), Phantom (expanded recursively — never stocked), By-Product, Co-Product, Packing, Tools, Variant, Option, Planning |
| Scrap | Expected scrap percentage — system increases required quantity accordingly |
| IsCritical | If true, production cannot proceed when this component is unavailable |
| ValidFrom / ValidTo | Effectivity dates for this specific component |
| M_AttributeSetInstance_ID | Specific lot/serial for this component (optional) |
Phantom Assemblies
A phantom assembly is a sub-assembly that exists in the BOM for organizational purposes but is never physically stocked. When the system encounters a component with ComponentType = 'Phantom' during production line generation, it expands through the phantom’s own BOM and includes the phantom’s components directly. This simplifies complex BOMs — you define a sub-assembly BOM once and reference it from multiple parent BOMs without creating separate production orders for the sub-assembly.
Note: “Phantom” exists at three levels in iDempiere:
- Product level:
MProduct.isPhantom()— a boolean on M_Product marking the product itself as a virtual sub-assembly (typicallyisStocked() = false). - BOM line level:
PP_Product_BOMLine.ComponentType = 'PH'— tells MRP/production to expand this line into the sub-assembly’s own BOM components. Check viaMPPProductBOMLine.COMPONENTTYPE_Phantom.equals(line.getComponentType()). - BOM header level:
PP_Product_BOM.BOMUse = 'D'(Phantom/Disassemble) — classifies the entire BOM’s purpose.
In practice, a phantom BOM line typically references a product that is also marked IsPhantom = Y, but they are stored independently on different tables.
Creating a BOM with Java API
import org.eevolution.model.MPPProductBOM;
import org.eevolution.model.MPPProductBOMLine;
// Create a BOM header for a finished product
MPPProductBOM bom = new MPPProductBOM(ctx, 0, trxName);
bom.setM_Product_ID(finishedProductId);
bom.setName("Standard Assembly BOM");
bom.setValue("STD-ASSY-001");
bom.setBOMType(MPPProductBOM.BOMTYPE_CurrentActive); // 'A'
bom.setBOMUse(MPPProductBOM.BOMUSE_Manufacturing); // 'A'
bom.saveEx();
// Add a regular component: Frame, qty = 1
MPPProductBOMLine line1 = new MPPProductBOMLine(bom);
line1.setM_Product_ID(frameProductId);
line1.setQtyBOM(new BigDecimal("1"));
line1.setLine(10);
line1.saveEx();
// Add a phantom sub-assembly: Motor Assembly
MPPProductBOMLine line2 = new MPPProductBOMLine(bom);
line2.setM_Product_ID(motorAssemblyProductId);
line2.setQtyBOM(new BigDecimal("1"));
line2.setComponentType(MPPProductBOMLine.COMPONENTTYPE_Phantom); // Will be expanded during production
line2.setLine(20);
line2.saveEx();
// Add a regular component: Cover Panel, qty = 4
MPPProductBOMLine line3 = new MPPProductBOMLine(bom);
line3.setM_Product_ID(coverPanelProductId);
line3.setQtyBOM(new BigDecimal("4"));
line3.setLine(30);
line3.saveEx();
MProduction: The Production Document
The MProduction class represents a production order. It implements DocAction, meaning it follows the standard iDempiere document workflow (Draft → In Progress → Complete). The production document ties together a finished product, a production quantity, a warehouse locator, and the BOM to use.
Key Fields
| Field | Description |
|---|---|
| M_Product_ID | The finished product to produce |
| ProductionQty | Number of units to produce (BigDecimal) |
| M_Locator_ID | Warehouse locator for finished goods receipt |
| MovementDate | Transaction date for inventory movements |
| PP_Product_BOM_ID | Specific BOM to use (optional — defaults to master BOM) |
| IsUseProductionPlan | If true, uses MProductionPlan for multi-product production |
| DocStatus / DocAction | Document workflow state and next action |
Creating a Production Order
// Create a new production order
MProduction production = new MProduction(ctx, 0, trxName);
production.setAD_Org_ID(orgId);
production.setM_Product_ID(finishedProductId);
production.setProductionQty(new BigDecimal("10"));
production.setM_Locator_ID(locatorId);
production.setMovementDate(new Timestamp(System.currentTimeMillis()));
production.saveEx();
System.out.println("Production created: " + production.getDocumentNo());
Production Line Generation
After creating the production header, you must generate the production lines. The createLines() method reads the BOM and creates one line per component, plus a line for the finished product (the end product line).
How createLines() Works
- End Product Line: A line is created for the finished product with a positive
MovementQty(goods being received into inventory) andIsEndProduct = true. - Component Lines: For each BOM line, a line is created with a negative
MovementQty(materials being consumed from inventory). The quantity is calculated asQtyBOM × ProductionQty. - Phantom Expansion: If a BOM component is a phantom, the system recursively expands through the phantom’s BOM and creates lines for its individual components instead.
- Inventory Allocation: For stocked components, the system queries
MStorageOnHandto find available inventory and may create multiple lines per component if the stock is spread across different locators or attribute set instances.
// Generate production lines from BOM
MProduction production = new MProduction(ctx, productionId, trxName);
int lineCount = production.createLines(false); // false = don't require all stock on hand
production.setIsCreated("Y");
production.saveEx();
System.out.println("Created " + lineCount + " production lines");
// Inspect the generated lines
MProductionLine[] lines = production.getLines();
for (MProductionLine line : lines) {
String type = line.isEndProduct() ? "END PRODUCT" : "COMPONENT";
System.out.println(type + ": " + line.getM_Product_ID()
+ " qty=" + line.getMovementQty());
}
// Expected output for a product with 3 BOM components (qty=10):
// END PRODUCT: [finished] qty=10
// COMPONENT: [frame] qty=-10
// COMPONENT: [motor] qty=-10
// COMPONENT: [cover] qty=-40
DocAction Workflow: Completing a Production
Like all iDempiere documents, MProduction follows the DocAction workflow. The key transitions are:
Document Status Flow
Draft (DR) → In Progress (IP) → Complete (CO) → Closed (CL)
↓
Reversed (RE) ← Void (VO)
prepareIt() — Validation
When you call prepareIt(), the system validates:
- Production lines exist and are properly configured
- At least one end-product line is present
- The movement date is within an open accounting period
- Component costs are valid (for standard costing)
completeIt() — Execution
When completeIt() runs, the system:
- Creates
MTransactionrecords for each production line (P+ for production receipt, P- for consumption) - Updates
MStorageOnHandquantities — decreasing component inventory and increasing finished product inventory - Processes
MProductionLineMArecords for lot/serial tracking - Triggers accounting entries for the material movements
// Complete the production order
MProduction production = new MProduction(ctx, productionId, trxName);
// Step 1: Prepare — validates lines, periods, costs
String status = production.prepareIt();
if (!DocAction.STATUS_InProgress.equals(status)) {
throw new AdempiereException("Prepare failed: " + production.getProcessMsg());
}
// Step 2: Complete — creates transactions, updates inventory
status = production.completeIt();
if (!DocAction.STATUS_Completed.equals(status)) {
throw new AdempiereException("Complete failed: " + production.getProcessMsg());
}
production.saveEx();
System.out.println("Production " + production.getDocumentNo() + " completed");
// At this point:
// - Component inventory has decreased
// - Finished product inventory has increased
// - M_Transaction records have been created
// - Accounting entries are posted
Full Production Workflow Example
// Complete workflow: Create → Generate Lines → Complete
MProduction production = new MProduction(ctx, 0, trxName);
production.setAD_Org_ID(orgId);
production.setM_Product_ID(finishedProductId);
production.setProductionQty(new BigDecimal("10"));
production.setM_Locator_ID(locatorId);
production.setMovementDate(new Timestamp(System.currentTimeMillis()));
production.saveEx();
// Generate lines from BOM
production.createLines(false);
production.setIsCreated("Y");
production.saveEx();
// Prepare and complete
if (!production.processIt(DocAction.ACTION_Complete)) {
throw new AdempiereException("Failed: " + production.getProcessMsg());
}
production.saveEx();
System.out.println("Production " + production.getDocumentNo()
+ " status: " + production.getDocStatus());
Key Takeaways
- Bills of Materials are defined using
MPPProductBOM(header) andMPPProductBOMLine(components). Each BOM has a type, use, and validity period. UseMPPProductBOM.getDefault()to retrieve the master BOM. - MProduction is the production document that ties together a product, quantity, locator, and BOM. It implements the DocAction interface for standard document workflow.
- Production Lines are generated via
createLines(), which reads the BOM and creates end-product lines (positive qty) and component lines (negative qty). Phantom assemblies are automatically expanded. - DocAction Workflow drives the production from Draft through Completion.
prepareIt()validates the document, andcompleteIt()creates inventory transactions and updates on-hand quantities. - Phantom BOMs allow you to organize complex product structures without stocking intermediate sub-assemblies. During line generation, phantoms are recursively expanded into their leaf components.
What’s Next
Now that you understand BOM definition and production order management, the next lesson — Inventory & Warehouse Management — covers how iDempiere manages warehouses, locators, on-hand inventory, and inter-warehouse inventory transfers using the MMovement API.
繁體中文
概述
- 您將學到:
- 如何使用 MPPProductBOM 和 MPPProductBOMLine 定義物料清單(BOM),包括 BOM 類型、BOM 用途及虛擬組件
- 如何建立與設定 MProduction 生產單據,包括設定產品、數量、倉庫儲位及異動日期
- 如何透過 createLines() 方法從 BOM 自動產生生產明細行,包括成品行與元件耗用行的差異
- DocAction 工作流程如何驅動生產單據從草稿到完成,並觸發庫存交易
- 先備知識:熟悉 iDempiere 產品管理、倉庫及基本單據處理(第 1–23 課)
- 預估閱讀時間:20 分鐘
簡介
生產是將原物料與零組件轉化為成品的過程。對於自行生產產品的企業——無論是組裝電子產品、加工金屬零件、混合化學品或食品包裝——ERP 生產模組對於規劃、執行及計算生產活動成本至關重要。
iDempiere 透過 MProduction 模型類別提供了完善的生產系統。此系統與物料清單(BOM)整合,自動計算元件需求,在完成時產生庫存交易,並遵循標準的 DocAction 單據工作流程。在本課程中,我們將重點介紹基礎概念:BOM 定義、生產單建立、明細行產生及完成工作流程。
物料清單(BOM)
物料清單是生產產品的配方。它列出所需的每個元件、子組件和原物料,以及所需數量。在 iDempiere 中,BOM 透過 MPPProductBOM(表頭)和 MPPProductBOMLine(元件)來管理,兩者皆位於 org.eevolution.model 套件中。
歷史背景:原始的 Compiere/ADempiere 使用 M_Product_BOM(扁平資料表,每個產品只能有一組 BOM)。在 iDempiere 9(IDEMPIERE-1250)中,Libero Manufacturing 模組的 PP_Product_BOM 資料表被整合進核心,實現了每個產品多組 BOM。舊的 M_Product_BOM 資料表現已轉為資料庫視圖(VIEW)以維持向後相容,MProductBOM 類別已標記為 @Deprecated。
BOM 表頭(MPPProductBOM)
BOM 表頭將 BOM 定義與特定成品關聯。主要欄位包括:
| 欄位 | 說明 |
|---|---|
| M_Product_ID | 此 BOM 生產的成品 |
| Name / Value | BOM 的描述名稱與代碼 |
| BOMType | 控制生產計劃如何處理產品:’A’(目前使用中)、’D’(接單生產)、’K’(成套生產) |
| BOMUse | 表示 BOM 用途:’A’(Master 主要——常數 BOMUSE_Manufacturing,命名為歷史遺留)、’M’(Manufacturing 製造)、’D’(Phantom/Disassemble 虛擬/拆解)、’E’(Engineering 工程)、’P’(Planning 計劃) |
| Revision | BOM 修訂版本識別碼 |
| M_AttributeSetInstance_ID | BOM 表頭的屬性集實例(如特定產品變體) |
一個產品可以擁有多個 BOM。系統使用 MPPProductBOM.getDefault(product, trxName) 來取得主要 BOM(BOMUse=’A’、BOMType=’A’)。
BOM 明細行(MPPProductBOMLine)
每一筆 BOM 明細行代表一個元件:
| 欄位 | 說明 |
|---|---|
| M_Product_ID | 元件產品 |
| QtyBOM | 每單位成品所需數量 |
| C_UOM_ID | 元件的計量單位 |
| Line | 序號(10、20、30…) |
| ComponentType | 此明細行的角色:Component(標準元件)、Phantom(虛擬——遞迴展開、不入庫)、By-Product(副產品)、Co-Product(聯產品)、Packing(包裝)、Tools(工具)、Variant(變體)、Option(選配)、Planning(計劃) |
| Scrap | 預期報廢百分比——系統會據此增加所需數量 |
| IsCritical | 若為 true,此元件無法取得時生產不可進行 |
| ValidFrom / ValidTo | 此特定元件的有效日期 |
| M_AttributeSetInstance_ID | 此元件的特定批次/序號(選填) |
虛擬組件
虛擬組件是為組織目的而存在於 BOM 中的子組件,但實際上不會被入庫儲存。當系統在生產明細行產生過程中遇到 ComponentType = 'Phantom' 的元件時,會展開虛擬組件本身的 BOM,並直接納入其各個元件。這簡化了複雜的 BOM 結構——您只需定義一次子組件的 BOM,即可從多個父 BOM 中參照它,而無需為子組件建立獨立的生產單。
注意:iDempiere 中「Phantom」存在於三個層級:
- 產品層級:
MProduct.isPhantom()——M_Product 上的布林欄位,標記該產品本身為虛擬子組件(通常isStocked() = false)。 - BOM 明細行層級:
PP_Product_BOMLine.ComponentType = 'PH'——指示 MRP/生產將此行展開為子組件本身的 BOM 元件。透過MPPProductBOMLine.COMPONENTTYPE_Phantom.equals(line.getComponentType())檢查。 - BOM 表頭層級:
PP_Product_BOM.BOMUse = 'D'(Phantom/Disassemble)——分類整個 BOM 的用途。
實務上,phantom BOM 明細行通常參照一個同樣標記為 IsPhantom = Y 的產品,但兩者獨立儲存在不同的資料表上。
使用 Java API 建立 BOM
import org.eevolution.model.MPPProductBOM;
import org.eevolution.model.MPPProductBOMLine;
// Create a BOM header for a finished product
MPPProductBOM bom = new MPPProductBOM(ctx, 0, trxName);
bom.setM_Product_ID(finishedProductId);
bom.setName("Standard Assembly BOM");
bom.setValue("STD-ASSY-001");
bom.setBOMType(MPPProductBOM.BOMTYPE_CurrentActive); // 'A'
bom.setBOMUse(MPPProductBOM.BOMUSE_Manufacturing); // 'A'
bom.saveEx();
// Add a regular component: Frame, qty = 1
MPPProductBOMLine line1 = new MPPProductBOMLine(bom);
line1.setM_Product_ID(frameProductId);
line1.setQtyBOM(new BigDecimal("1"));
line1.setLine(10);
line1.saveEx();
// Add a phantom sub-assembly: Motor Assembly
MPPProductBOMLine line2 = new MPPProductBOMLine(bom);
line2.setM_Product_ID(motorAssemblyProductId);
line2.setQtyBOM(new BigDecimal("1"));
line2.setComponentType(MPPProductBOMLine.COMPONENTTYPE_Phantom); // Will be expanded during production
line2.setLine(20);
line2.saveEx();
// Add a regular component: Cover Panel, qty = 4
MPPProductBOMLine line3 = new MPPProductBOMLine(bom);
line3.setM_Product_ID(coverPanelProductId);
line3.setQtyBOM(new BigDecimal("4"));
line3.setLine(30);
line3.saveEx();
MProduction:生產單據
MProduction 類別代表一張生產單。它實作了 DocAction 介面,意即遵循標準的 iDempiere 單據工作流程(草稿 → 處理中 → 完成)。生產單據將成品、生產數量、倉庫儲位和 BOM 整合在一起。
主要欄位
| 欄位 | 說明 |
|---|---|
| M_Product_ID | 要生產的成品 |
| ProductionQty | 要生產的單位數量(BigDecimal) |
| M_Locator_ID | 成品入庫的倉庫儲位 |
| MovementDate | 庫存異動的交易日期 |
| PP_Product_BOM_ID | 要使用的特定 BOM(選填——預設為主要 BOM) |
| IsUseProductionPlan | 若為 true,使用 MProductionPlan 進行多產品生產 |
| DocStatus / DocAction | 單據工作流程狀態與下一步動作 |
建立生產單
// Create a new production order
MProduction production = new MProduction(ctx, 0, trxName);
production.setAD_Org_ID(orgId);
production.setM_Product_ID(finishedProductId);
production.setProductionQty(new BigDecimal("10"));
production.setM_Locator_ID(locatorId);
production.setMovementDate(new Timestamp(System.currentTimeMillis()));
production.saveEx();
System.out.println("Production created: " + production.getDocumentNo());
生產明細行產生
建立生產單表頭後,您必須產生生產明細行。createLines() 方法會讀取 BOM 並為每個元件建立一筆明細行,再加上一筆成品行(最終產品行)。
createLines() 的運作方式
- 成品行:為成品建立一筆明細行,具有正數的
MovementQty(商品入庫)且IsEndProduct = true。 - 元件行:為每一筆 BOM 明細行建立一筆明細行,具有負數的
MovementQty(物料從庫存中耗用)。數量計算方式為QtyBOM × ProductionQty。 - 虛擬組件展開:若 BOM 元件為虛擬組件,系統會遞迴展開虛擬組件的 BOM,並為其各個元件建立明細行。
- 庫存分配:對於有庫存的元件,系統會查詢
MStorageOnHand以尋找可用庫存,若庫存分散在不同的倉庫儲位或屬性集實例中,可能會為每個元件建立多筆明細行。
// Generate production lines from BOM
MProduction production = new MProduction(ctx, productionId, trxName);
int lineCount = production.createLines(false); // false = don't require all stock on hand
production.setIsCreated("Y");
production.saveEx();
System.out.println("Created " + lineCount + " production lines");
// Inspect the generated lines
MProductionLine[] lines = production.getLines();
for (MProductionLine line : lines) {
String type = line.isEndProduct() ? "END PRODUCT" : "COMPONENT";
System.out.println(type + ": " + line.getM_Product_ID()
+ " qty=" + line.getMovementQty());
}
// Expected output for a product with 3 BOM components (qty=10):
// END PRODUCT: [finished] qty=10
// COMPONENT: [frame] qty=-10
// COMPONENT: [motor] qty=-10
// COMPONENT: [cover] qty=-40
DocAction 工作流程:完成生產
如同所有 iDempiere 單據,MProduction 遵循 DocAction 工作流程。主要的狀態轉換如下:
單據狀態流程
Draft (DR) → In Progress (IP) → Complete (CO) → Closed (CL)
↓
Reversed (RE) ← Void (VO)
prepareIt() — 驗證
當您呼叫 prepareIt() 時,系統會驗證:
- 生產明細行存在且已正確設定
- 至少存在一筆成品行
- 異動日期在開放的會計期間內
- 元件成本有效(適用於標準成本法)
completeIt() — 執行
當 completeIt() 執行時,系統會:
- 為每筆生產明細行建立
MTransaction記錄(P+ 代表生產入庫,P- 代表耗用) - 更新
MStorageOnHand數量——減少元件庫存並增加成品庫存 - 處理
MProductionLineMA記錄以進行批號/序號追蹤 - 觸發物料異動的會計分錄
// Complete the production order
MProduction production = new MProduction(ctx, productionId, trxName);
// Step 1: Prepare — validates lines, periods, costs
String status = production.prepareIt();
if (!DocAction.STATUS_InProgress.equals(status)) {
throw new AdempiereException("Prepare failed: " + production.getProcessMsg());
}
// Step 2: Complete — creates transactions, updates inventory
status = production.completeIt();
if (!DocAction.STATUS_Completed.equals(status)) {
throw new AdempiereException("Complete failed: " + production.getProcessMsg());
}
production.saveEx();
System.out.println("Production " + production.getDocumentNo() + " completed");
// At this point:
// - Component inventory has decreased
// - Finished product inventory has increased
// - M_Transaction records have been created
// - Accounting entries are posted
完整生產工作流程範例
// Complete workflow: Create → Generate Lines → Complete
MProduction production = new MProduction(ctx, 0, trxName);
production.setAD_Org_ID(orgId);
production.setM_Product_ID(finishedProductId);
production.setProductionQty(new BigDecimal("10"));
production.setM_Locator_ID(locatorId);
production.setMovementDate(new Timestamp(System.currentTimeMillis()));
production.saveEx();
// Generate lines from BOM
production.createLines(false);
production.setIsCreated("Y");
production.saveEx();
// Prepare and complete
if (!production.processIt(DocAction.ACTION_Complete)) {
throw new AdempiereException("Failed: " + production.getProcessMsg());
}
production.saveEx();
System.out.println("Production " + production.getDocumentNo()
+ " status: " + production.getDocStatus());
重點摘要
- 物料清單使用
MPPProductBOM(表頭)和MPPProductBOMLine(元件)來定義。每個 BOM 具有類型、用途和有效期間。使用MPPProductBOM.getDefault()來取得主要 BOM。 - MProduction 是將產品、數量、倉庫儲位和 BOM 整合在一起的生產單據。它實作了 DocAction 介面以支援標準單據工作流程。
- 生產明細行透過
createLines()產生,該方法會讀取 BOM 並建立成品行(正數數量)和元件行(負數數量)。虛擬組件會被自動展開。 - DocAction 工作流程驅動生產從草稿到完成。
prepareIt()驗證單據,而completeIt()建立庫存交易並更新庫存數量。 - 虛擬 BOM 讓您可以組織複雜的產品結構,而無需儲存中間子組件。在明細行產生過程中,虛擬組件會被遞迴展開為其末端元件。
下一步
現在您已瞭解 BOM 定義與生產單管理,下一課——庫存與倉庫管理——將介紹 iDempiere 如何管理倉庫、儲位、庫存數量,以及使用 MMovement API 進行跨倉庫的庫存調撥。
日本語
概要
- 学習内容:
- MPPProductBOM と MPPProductBOMLine を使用した部品表(BOM)の定義方法(BOM タイプ、BOM 用途、ファントム組立を含む)
- MProduction ドキュメントの作成と設定方法(製品、数量、ロケーター、移動日の設定を含む)
- createLines() メソッドにより BOM から生産明細行が自動生成される仕組み(完成品行と部品消費行の違いを含む)
- DocAction ワークフローが生産ドキュメントを下書きから完了まで駆動し、在庫トランザクションを発生させる仕組み
- 前提条件:iDempiere の製品管理、倉庫、および基本的なドキュメント処理に精通していること(レッスン 1~23)
- 推定読了時間:20 分
はじめに
生産とは、原材料や部品を完成品に変換するプロセスです。自社製品を生産する企業——電子機器の組立、金属部品の加工、化学品の調合、食品の包装など——にとって、ERP の生産モジュールは生産活動の計画、実行、原価計算に不可欠です。
iDempiere は MProduction モデルクラスを通じて堅牢な生産システムを提供しています。このシステムは部品表(BOM)と統合して部品所要量を自動計算し、完了時に在庫トランザクションを生成し、標準的な DocAction ドキュメントワークフローに従います。このレッスンでは、基本概念である BOM 定義、生産指図の作成、明細行の生成、および完了ワークフローに焦点を当てます。
部品表(BOM)
部品表は、製品を生産するためのレシピです。必要なすべての部品、サブアセンブリ、原材料とその所要量を一覧にします。iDempiere では、BOM は MPPProductBOM(ヘッダー)と MPPProductBOMLine(部品)によって管理されます。両方とも org.eevolution.model パッケージに含まれます。
歴史的背景:元の Compiere/ADempiere は M_Product_BOM(フラットテーブル、製品ごとに1つの BOM のみ)を使用していました。iDempiere 9(IDEMPIERE-1250)で、Libero Manufacturing モジュールの PP_Product_BOM テーブルがコアに統合され、製品ごとに複数の BOM が可能になりました。旧 M_Product_BOM テーブルは後方互換性のためのデータベースビュー(VIEW)として存在し、MProductBOM クラスは @Deprecated としてマークされています。
BOM ヘッダー(MPPProductBOM)
BOM ヘッダーは、BOM 定義を特定の完成品に紐づけます。主要なフィールドは以下の通りです:
| フィールド | 説明 |
|---|---|
| M_Product_ID | この BOM が生産する完成品 |
| Name / Value | BOM の説明名称とコード |
| BOMType | 生産計画が製品をどのように扱うかを制御:’A’(現在有効)、’D’(受注生産)、’K’(キット生産) |
| BOMUse | BOM の用途を示す:’A’(Master マスター——定数 BOMUSE_Manufacturing、命名は歴史的経緯)、’M’(Manufacturing 製造)、’D’(Phantom/Disassemble ファントム/分解)、’E’(Engineering エンジニアリング)、’P’(Planning 計画) |
| Revision | BOM リビジョンバージョン識別子 |
| M_AttributeSetInstance_ID | BOM ヘッダーの属性セットインスタンス(特定の製品バリアントなど) |
1 つの製品に対して複数の BOM を持つことができます。システムは MPPProductBOM.getDefault(product, trxName) を使用してマスター BOM(BOMUse=’A’、BOMType=’A’)を取得します。
BOM 明細行(MPPProductBOMLine)
各 BOM 明細行は 1 つの部品を表します:
| フィールド | 説明 |
|---|---|
| M_Product_ID | 部品製品 |
| QtyBOM | 完成品 1 単位あたりの所要量 |
| C_UOM_ID | 部品の計量単位 |
| Line | 順序番号(10、20、30…) |
| ComponentType | この明細行の役割:Component(標準部品)、Phantom(ファントム——再帰展開、在庫なし)、By-Product(副産物)、Co-Product(連産品)、Packing(包装)、Tools(工具)、Variant(バリアント)、Option(オプション)、Planning(計画) |
| Scrap | 予想スクラップ率——システムはこれに基づき所要量を増加 |
| IsCritical | true の場合、この部品が入手不可のとき生産不可 |
| ValidFrom / ValidTo | この特定部品の有効期間 |
| M_AttributeSetInstance_ID | この部品の特定ロット/シリアル(任意) |
ファントム組立
ファントム組立とは、BOM 内に組織的な目的で存在するサブアセンブリですが、実際には在庫として保管されません。システムが生産明細行の生成中に ComponentType = 'Phantom' の部品を検出すると、ファントム自体の BOM を展開し、その部品を直接含めます。これにより複雑な BOM が簡素化されます——サブアセンブリの BOM を一度定義すれば、複数の親 BOM から参照でき、サブアセンブリ用の個別の生産指図を作成する必要がありません。
注意:iDempiere における「Phantom」は3つのレベルに存在します:
- 製品レベル:
MProduct.isPhantom()——M_Product のブーリアンカラムで、製品自体を仮想サブアセンブリとしてマーク(通常isStocked() = false)。 - BOM明細行レベル:
PP_Product_BOMLine.ComponentType = 'PH'——MRP/生産にこの行をサブアセンブリのBOM部品に展開するよう指示。MPPProductBOMLine.COMPONENTTYPE_Phantom.equals(line.getComponentType())で確認。 - BOMヘッダーレベル:
PP_Product_BOM.BOMUse = 'D'(Phantom/Disassemble)——BOM全体の用途を分類。
実務上、ファントムBOM明細行は通常 IsPhantom = Y とマークされた製品を参照しますが、両者は異なるテーブルに独立して格納されています。
Java API による BOM の作成
import org.eevolution.model.MPPProductBOM;
import org.eevolution.model.MPPProductBOMLine;
// Create a BOM header for a finished product
MPPProductBOM bom = new MPPProductBOM(ctx, 0, trxName);
bom.setM_Product_ID(finishedProductId);
bom.setName("Standard Assembly BOM");
bom.setValue("STD-ASSY-001");
bom.setBOMType(MPPProductBOM.BOMTYPE_CurrentActive); // 'A'
bom.setBOMUse(MPPProductBOM.BOMUSE_Manufacturing); // 'A'
bom.saveEx();
// Add a regular component: Frame, qty = 1
MPPProductBOMLine line1 = new MPPProductBOMLine(bom);
line1.setM_Product_ID(frameProductId);
line1.setQtyBOM(new BigDecimal("1"));
line1.setLine(10);
line1.saveEx();
// Add a phantom sub-assembly: Motor Assembly
MPPProductBOMLine line2 = new MPPProductBOMLine(bom);
line2.setM_Product_ID(motorAssemblyProductId);
line2.setQtyBOM(new BigDecimal("1"));
line2.setComponentType(MPPProductBOMLine.COMPONENTTYPE_Phantom); // Will be expanded during production
line2.setLine(20);
line2.saveEx();
// Add a regular component: Cover Panel, qty = 4
MPPProductBOMLine line3 = new MPPProductBOMLine(bom);
line3.setM_Product_ID(coverPanelProductId);
line3.setQtyBOM(new BigDecimal("4"));
line3.setLine(30);
line3.saveEx();
MProduction:生産ドキュメント
MProduction クラスは生産指図を表します。DocAction を実装しており、標準的な iDempiere ドキュメントワークフロー(下書き → 処理中 → 完了)に従います。生産ドキュメントは、完成品、生産数量、倉庫ロケーター、使用する BOM を紐づけます。
主要フィールド
| フィールド | 説明 |
|---|---|
| M_Product_ID | 生産する完成品 |
| ProductionQty | 生産する数量(BigDecimal) |
| M_Locator_ID | 完成品入庫用の倉庫ロケーター |
| MovementDate | 在庫移動のトランザクション日付 |
| PP_Product_BOM_ID | 使用する特定の BOM(任意——デフォルトはマスター BOM) |
| IsUseProductionPlan | true の場合、MProductionPlan を使用した複数製品生産 |
| DocStatus / DocAction | ドキュメントワークフローの状態と次のアクション |
生産指図の作成
// Create a new production order
MProduction production = new MProduction(ctx, 0, trxName);
production.setAD_Org_ID(orgId);
production.setM_Product_ID(finishedProductId);
production.setProductionQty(new BigDecimal("10"));
production.setM_Locator_ID(locatorId);
production.setMovementDate(new Timestamp(System.currentTimeMillis()));
production.saveEx();
System.out.println("Production created: " + production.getDocumentNo());
生産明細行の生成
生産ヘッダーを作成した後、生産明細行を生成する必要があります。createLines() メソッドは BOM を読み取り、各部品ごとに 1 行と、完成品用の 1 行(完成品行)を作成します。
createLines() の動作
- 完成品行:完成品に対して正の
MovementQty(在庫への入庫)とIsEndProduct = trueを持つ行が作成されます。 - 部品行:各 BOM 明細行に対して負の
MovementQty(在庫からの材料消費)を持つ行が作成されます。数量はQtyBOM × ProductionQtyとして計算されます。 - ファントム展開:BOM 部品がファントムの場合、システムはファントムの BOM を再帰的に展開し、その個別の部品に対して行を作成します。
- 在庫引当:在庫管理対象の部品について、システムは
MStorageOnHandを照会して利用可能な在庫を検索し、在庫が異なるロケーターや属性セットインスタンスに分散している場合は、部品ごとに複数の行を作成することがあります。
// Generate production lines from BOM
MProduction production = new MProduction(ctx, productionId, trxName);
int lineCount = production.createLines(false); // false = don't require all stock on hand
production.setIsCreated("Y");
production.saveEx();
System.out.println("Created " + lineCount + " production lines");
// Inspect the generated lines
MProductionLine[] lines = production.getLines();
for (MProductionLine line : lines) {
String type = line.isEndProduct() ? "END PRODUCT" : "COMPONENT";
System.out.println(type + ": " + line.getM_Product_ID()
+ " qty=" + line.getMovementQty());
}
// Expected output for a product with 3 BOM components (qty=10):
// END PRODUCT: [finished] qty=10
// COMPONENT: [frame] qty=-10
// COMPONENT: [motor] qty=-10
// COMPONENT: [cover] qty=-40
DocAction ワークフロー:生産の完了
すべての iDempiere ドキュメントと同様に、MProduction は DocAction ワークフローに従います。主要な状態遷移は以下の通りです:
ドキュメントステータスフロー
Draft (DR) → In Progress (IP) → Complete (CO) → Closed (CL)
↓
Reversed (RE) ← Void (VO)
prepareIt() — バリデーション
prepareIt() を呼び出すと、システムは以下を検証します:
- 生産明細行が存在し、正しく設定されていること
- 少なくとも 1 つの完成品行が存在すること
- 移動日が開いている会計期間内であること
- 部品原価が有効であること(標準原価計算の場合)
completeIt() — 実行
completeIt() が実行されると、システムは以下を行います:
- 各生産明細行に対して
MTransactionレコードを作成(P+ は生産入庫、P- は消費) MStorageOnHandの数量を更新——部品在庫を減少させ、完成品在庫を増加- ロット/シリアル追跡のための
MProductionLineMAレコードを処理 - 材料移動に対する会計仕訳を発生
// Complete the production order
MProduction production = new MProduction(ctx, productionId, trxName);
// Step 1: Prepare — validates lines, periods, costs
String status = production.prepareIt();
if (!DocAction.STATUS_InProgress.equals(status)) {
throw new AdempiereException("Prepare failed: " + production.getProcessMsg());
}
// Step 2: Complete — creates transactions, updates inventory
status = production.completeIt();
if (!DocAction.STATUS_Completed.equals(status)) {
throw new AdempiereException("Complete failed: " + production.getProcessMsg());
}
production.saveEx();
System.out.println("Production " + production.getDocumentNo() + " completed");
// At this point:
// - Component inventory has decreased
// - Finished product inventory has increased
// - M_Transaction records have been created
// - Accounting entries are posted
完全な生産ワークフロー例
// Complete workflow: Create → Generate Lines → Complete
MProduction production = new MProduction(ctx, 0, trxName);
production.setAD_Org_ID(orgId);
production.setM_Product_ID(finishedProductId);
production.setProductionQty(new BigDecimal("10"));
production.setM_Locator_ID(locatorId);
production.setMovementDate(new Timestamp(System.currentTimeMillis()));
production.saveEx();
// Generate lines from BOM
production.createLines(false);
production.setIsCreated("Y");
production.saveEx();
// Prepare and complete
if (!production.processIt(DocAction.ACTION_Complete)) {
throw new AdempiereException("Failed: " + production.getProcessMsg());
}
production.saveEx();
System.out.println("Production " + production.getDocumentNo()
+ " status: " + production.getDocStatus());
重要ポイント
- 部品表は
MPPProductBOM(ヘッダー)とMPPProductBOMLine(部品)を使用して定義します。各 BOM にはタイプ、用途、有効期間があります。MPPProductBOM.getDefault()を使用してマスター BOM を取得します。 - MProduction は、製品、数量、ロケーター、BOM を紐づける生産ドキュメントです。標準的なドキュメントワークフローのために DocAction インターフェースを実装しています。
- 生産明細行は
createLines()によって生成され、BOM を読み取って完成品行(正の数量)と部品行(負の数量)を作成します。ファントム組立は自動的に展開されます。 - DocAction ワークフローは、生産を下書きから完了まで駆動します。
prepareIt()がドキュメントを検証し、completeIt()が在庫トランザクションを作成して手持在庫数量を更新します。 - ファントム BOM により、中間サブアセンブリを在庫として保管することなく、複雑な製品構成を整理できます。明細行生成時に、ファントムは再帰的にその末端部品に展開されます。
次のステップ
BOM 定義と生産指図管理を理解したところで、次のレッスン——在庫・倉庫管理——では、iDempiere が倉庫、ロケーター、手持在庫、および MMovement API を使用した倉庫間在庫移動をどのように管理するかを説明します。