Production Costing & Analysis
Overview
- What you’ll learn:
- The different costing methods in iDempiere: Standard Cost, Average Invoice, Average PO, FIFO, LIFO, Last Invoice, and Last PO
- How to query and manage product costs using the MCost API — getCurrentCost(), getSeedCosts(), and weighted average calculations
- How MCostDetail provides transaction-level cost tracking linked to source documents for audit trails
- How the IndentedBOM process performs multi-level BOM cost rollup across all component levels
- How costs flow through the production lifecycle when MProduction.completeIt() executes
- Prerequisites: Lessons 24–25 and Lesson 35 — Production Overview, Inventory & Warehouse Management, Advanced Production & BOM Management
- Estimated reading time: 22 minutes
Introduction
Understanding the cost of production is fundamental to profitability analysis, pricing decisions, and financial reporting. An ERP system must not only track what was produced and consumed, but calculate what it cost — at every level of the bill of materials, using the organization’s chosen costing methodology.
iDempiere’s costing system is built around three core classes: MCost (product cost records), MCostDetail (transaction-level cost entries), and MCostElement (cost type definitions). The IndentedBOM process brings these together for multi-level BOM cost analysis. In this lesson, you’ll learn how these components work together to track, calculate, and report production costs.
Costing Methods
iDempiere supports multiple costing methods, configured at the Accounting Schema level. Each method determines how the CurrentCostPrice in MCost is calculated and maintained.
Available Methods
| Method | Description | When to Use |
|---|---|---|
| Standard Cost | Fixed cost per unit, set manually or via cost rollup. Does not change with market fluctuations. | Manufacturing with stable costs; variance analysis |
| Average Invoice | Weighted average based on invoice (AP) prices. Updates with each new invoice. | Trading companies buying from multiple vendors |
| Average PO | Weighted average based on purchase order prices. Updates with each new PO. | When PO prices are more reliable than invoice prices |
| FIFO | First In, First Out. Oldest inventory cost layers are consumed first. | Perishable goods, regulatory requirements |
| LIFO | Last In, First Out. Newest inventory cost layers are consumed first. | Tax optimization in some jurisdictions |
| Last Invoice | Uses the price from the most recent vendor invoice. | Fast-moving commodities with volatile prices |
| Last PO | Uses the price from the most recent purchase order. | When PO prices reflect current market value |
MCostElement — Cost Type Definitions
Each cost record is associated with a MCostElement that defines what type of cost it represents:
- Material: Raw material and component costs
- Labor: Direct labor costs
- Overhead: Factory overhead and indirect costs
- Outside Processing: Costs for operations performed by external vendors
- Burden: Additional allocated costs
The combination of costing method and cost element allows iDempiere to maintain detailed cost breakdowns — for example, tracking material cost using FIFO while tracking labor cost as a standard cost.
MCost API: Querying Product Costs
The MCost class provides the primary interface for accessing product cost information. Each MCost record is uniquely identified by a composite key: Product + ASI + Organization + Accounting Schema + Cost Type + Cost Element.
Key Properties
| Field | Description |
|---|---|
| M_Product_ID | The product |
| M_AttributeSetInstance_ID | Specific lot (0 = aggregate) |
| C_AcctSchema_ID | Accounting schema defining the costing method |
| M_CostType_ID | Cost type (e.g., standard vs actual) |
| M_CostElement_ID | Cost element (material, labor, etc.) |
| CurrentCostPrice | Current cost per unit |
| CurrentQty | Current quantity on hand (for average calculations) |
| CumulativeAmt / CumulativeQty | Running totals for weighted average |
Querying Current Cost
// Get the current cost for a product
MProduct product = MProduct.get(ctx, productId);
BigDecimal currentCost = MCost.getCurrentCost(
product,
0, // ASI (0 = aggregate across all lots)
trxName);
System.out.println("Current cost: " + currentCost);
// Get cost for a specific organization
BigDecimal orgCost = MCost.getCurrentCost(
product,
orgId,
0, // ASI
trxName);
// Get seed costs (initial/standard costs)
BigDecimal seedCost = MCost.getSeedCosts(
product,
0, // ASI
new Timestamp(System.currentTimeMillis()),
trxName);
// Get last invoice price
BigDecimal lastInvoicePrice = MCost.getLastInvoicePrice(
product,
0, // C_BPartner_ID (0 = any vendor)
trxName);
// Get last PO price
BigDecimal lastPOPrice = MCost.getLastPOPrice(
product,
orgId,
trxName);
Updating Cost Records
// Get the MCost record for modification
MCost cost = MCost.get(product, 0, // ASI
acctSchema, orgId, costElementId, trxName);
if (cost != null) {
// Add cost and quantity (updates weighted average internally)
cost.add(amount, quantity);
cost.saveEx();
// Or set weighted average directly
cost.setWeightedAverage(totalAmount, totalQuantity);
cost.saveEx();
System.out.println("Updated cost: " + cost.getCurrentCostPrice());
}
Cost Detail Tracking with MCostDetail
MCostDetail provides the transaction-level audit trail for all cost changes. Every time a cost-affecting transaction occurs — receiving goods, issuing materials, completing production — a cost detail record is created linking the cost change to its source document.
MCostDetail Structure
| Field | Description |
|---|---|
| M_Product_ID | Product affected |
| C_AcctSchema_ID | Accounting schema |
| M_CostElement_ID | Cost element |
| Amt | Cost amount (can be positive or negative) |
| Qty | Quantity affected |
| M_InOutLine_ID | Link to material receipt/shipment line |
| C_OrderLine_ID | Link to purchase order line |
| M_MovementLine_ID | Link to inventory movement line |
| M_ProductionLine_ID | Link to production line |
| C_InvoiceLine_ID | Link to invoice line |
// Query cost details for a specific product
String sql = "SELECT cd.Amt, cd.Qty, cd.Created, "
+ "cd.M_InOutLine_ID, cd.C_OrderLine_ID, cd.M_ProductionLine_ID "
+ "FROM M_CostDetail cd "
+ "WHERE cd.M_Product_ID = ? AND cd.C_AcctSchema_ID = ? "
+ "ORDER BY cd.Created DESC";
PreparedStatement pstmt = DB.prepareStatement(sql, trxName);
pstmt.setInt(1, productId);
pstmt.setInt(2, acctSchemaId);
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
BigDecimal amt = rs.getBigDecimal("Amt");
BigDecimal qty = rs.getBigDecimal("Qty");
Timestamp created = rs.getTimestamp("Created");
String source = "Unknown";
if (rs.getInt("M_ProductionLine_ID") > 0)
source = "Production";
else if (rs.getInt("M_InOutLine_ID") > 0)
source = "Receipt/Shipment";
else if (rs.getInt("C_OrderLine_ID") > 0)
source = "Purchase Order";
System.out.println(created + " | " + source
+ " | Amt=" + amt + " | Qty=" + qty);
}
DB.close(rs, pstmt);
Multi-Level BOM Cost Rollup with IndentedBOM
The IndentedBOM process is the tool for analyzing costs across all levels of a product’s BOM. It recursively expands the BOM, calculates component costs at each level, and writes the results to the T_BOM_Indented temporary table.
IndentedBOM Parameters
| Parameter | Description |
|---|---|
| M_Product_ID | The finished product to analyze |
| C_AcctSchema_ID | Accounting schema (determines costing method) |
| M_CostElement_ID | Cost element to analyze (material, labor, etc.) |
How IndentedBOM Works
- Reads the product’s master BOM
- For each BOM line, retrieves the component’s current cost from
MCost - Calculates extended cost:
QtyBOM × CurrentCostPrice - If a component has its own BOM, recursively expands one level deeper
- Writes each level to
T_BOM_Indentedwith level indicators - Accumulates total cost across all levels
Implementing a Custom BOM Cost Calculator
While IndentedBOM handles the standard cost rollup, you may need custom cost calculations. Here’s how to implement a recursive BOM cost calculator using the iDempiere API:
public class BOMCostCalculator {
public static class CostBreakdown {
public String productName;
public int level;
public BigDecimal qtyRequired;
public BigDecimal unitCost;
public BigDecimal extendedCost;
public CostBreakdown(String name, int lvl, BigDecimal qty,
BigDecimal cost, BigDecimal extended) {
this.productName = name;
this.level = lvl;
this.qtyRequired = qty;
this.unitCost = cost;
this.extendedCost = extended;
}
}
/**
* Recursively calculate BOM cost.
*/
public List<CostBreakdown> calculateCost(Properties ctx,
int productId, BigDecimal qty, String trxName) {
List<CostBreakdown> breakdown = new ArrayList<>();
MProduct product = MProduct.get(ctx, productId);
MPPProductBOM bom = MPPProductBOM.getDefault(product, trxName);
if (bom != null) {
expandCost(ctx, bom, qty, 0, breakdown, trxName);
}
return breakdown;
}
private void expandCost(Properties ctx, MPPProductBOM bom,
BigDecimal parentQty, int level,
List<CostBreakdown> breakdown, String trxName) {
MPPProductBOMLine[] lines = bom.getLines();
for (MPPProductBOMLine line : lines) {
MProduct component = MProduct.get(ctx, line.getM_Product_ID());
BigDecimal compQty = line.getQtyBOM().multiply(parentQty);
// Check if this is a phantom that should be expanded
if (line.isPhantom() && component.isBOM()) {
// Expand phantom — don't add cost for phantom itself
MPPProductBOM subBOM = MPPProductBOM.getDefault(
component, trxName);
if (subBOM != null) {
expandCost(ctx, subBOM, compQty, level + 1,
breakdown, trxName);
}
} else {
// Leaf component — get its cost
BigDecimal unitCost = MCost.getCurrentCost(
component, 0, trxName);
BigDecimal extCost = compQty.multiply(unitCost);
breakdown.add(new CostBreakdown(
component.getName(), level,
compQty, unitCost, extCost));
}
}
}
/**
* Sum all component costs.
*/
public BigDecimal getTotalCost(List<CostBreakdown> breakdown) {
return breakdown.stream()
.map(b -> b.extendedCost)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
}
// Usage:
BOMCostCalculator calc = new BOMCostCalculator();
List<CostBreakdown> breakdown = calc.calculateCost(
ctx, finishedProductId, new BigDecimal("100"), trxName);
System.out.println("=== BOM Cost Analysis ===");
for (CostBreakdown cb : breakdown) {
String indent = " ".repeat(cb.level);
System.out.printf("%s%s: qty=%.2f × $%.4f = $%.2f%n",
indent, cb.productName,
cb.qtyRequired, cb.unitCost, cb.extendedCost);
}
System.out.printf("TOTAL: $%.2f%n", calc.getTotalCost(breakdown));
Production Cost Flow
When MProduction.completeIt() executes, costs flow through the system:
Component Consumption
For each component consumption line (negative MovementQty):
- The component’s
MCost.CurrentQtyis decreased - An
MCostDetailrecord is created with a negative amount (cost relief) - For average costing, the weighted average price may be recalculated
- For FIFO/LIFO, the appropriate cost layer is consumed
Finished Product Receipt
For the end-product line (positive MovementQty):
- The finished product’s
MCost.CurrentQtyis increased - The cost is calculated as the sum of consumed component costs
- An
MCostDetailrecord is created with the production cost - For standard costing, any variance between standard and actual cost is posted to variance accounts
Standard Cost Variance
When using standard costing, the actual production cost (sum of component actual costs) may differ from the product’s standard cost. This difference is posted as a Production Variance in the accounting entries. Monitoring these variances helps identify cost inefficiencies, unexpected price changes in components, or the need to update standard costs.
Key Takeaways
- Costing Methods (Standard, Average, FIFO, LIFO, Last Price) determine how
CurrentCostPriceis maintained inMCost. The method is configured at the Accounting Schema level. - MCost provides static methods (
getCurrentCost(),getSeedCosts(),getLastInvoicePrice()) for querying costs, and instance methods (add(),setWeightedAverage()) for updating them. - MCostDetail creates an audit trail linking every cost change to its source document (receipt, invoice, production, movement).
- IndentedBOM performs multi-level cost rollup by recursively expanding BOMs and calculating component costs at each level.
- Production cost flow decreases component costs and increases finished product costs. Standard costing captures variances between standard and actual costs for financial analysis.
What’s Next
You have now completed the Production module curriculum. To validate your knowledge, visit the Certification Exam Center and attempt the Production module coding exercises. You can also continue with the Financial Management — Cost Management lesson for a broader view of costing across all iDempiere modules.
繁體中文
概述
- 您將學到:
- iDempiere 中的不同成本計算方法:標準成本、平均發票成本、平均採購單成本、先進先出 (FIFO)、後進先出 (LIFO)、最近發票價格及最近採購單價格
- 如何使用 MCost API 查詢和管理產品成本 — getCurrentCost()、getSeedCosts() 及加權平均計算
- MCostDetail 如何提供交易層級的成本追蹤,並連結至來源單據以建立稽核軌跡
- IndentedBOM 流程如何執行多層 BOM 成本展開,涵蓋所有元件層級
- 當 MProduction.completeIt() 執行時,成本如何在生產生命週期中流動
- 先修課程:第 24–25 課及第 35 課 — 生產概述、庫存與倉儲管理、進階生產與 BOM 管理
- 預估閱讀時間:22 分鐘
導論
瞭解生產成本是獲利分析、定價決策及財務報告的基礎。ERP 系統不僅必須追蹤生產和消耗了什麼,還必須計算其成本 — 在物料清單的每個層級,使用組織所選擇的成本計算方法。
iDempiere 的成本系統圍繞三個核心類別建構:MCost(產品成本記錄)、MCostDetail(交易層級成本分錄)及 MCostElement(成本類型定義)。IndentedBOM 流程將這些元件整合在一起,進行多層 BOM 成本分析。在本課程中,您將學習這些元件如何協同運作,以追蹤、計算及報告生產成本。
成本計算方法
iDempiere 支援多種成本計算方法,在會計架構層級進行設定。每種方法決定了 MCost 中 CurrentCostPrice 的計算和維護方式。
可用方法
| 方法 | 說明 | 適用情境 |
|---|---|---|
| 標準成本 | 固定的單位成本,手動設定或透過成本展開計算。不隨市場波動而變化。 | 成本穩定的製造業;差異分析 |
| 平均發票成本 | 基於發票(應付帳款)價格的加權平均。隨每張新發票更新。 | 向多家供應商採購的貿易公司 |
| 平均採購單成本 | 基於採購單價格的加權平均。隨每筆新採購單更新。 | 當採購單價格比發票價格更可靠時 |
| 先進先出 (FIFO) | 先進先出。最舊的庫存成本層優先消耗。 | 易腐商品、法規要求 |
| 後進先出 (LIFO) | 後進先出。最新的庫存成本層優先消耗。 | 某些管轄區域的稅務優化 |
| 最近發票價格 | 使用最近一張供應商發票的價格。 | 價格波動劇烈的快速流通商品 |
| 最近採購單價格 | 使用最近一筆採購單的價格。 | 當採購單價格反映當前市場價值時 |
MCostElement — 成本類型定義
每筆成本記錄都關聯至一個 MCostElement,用以定義其所代表的成本類型:
- 材料:原物料及元件成本
- 人工:直接人工成本
- 製造費用:工廠間接費用及間接成本
- 委外加工:由外部供應商執行作業的成本
- 附加費用:額外分攤的成本
成本計算方法與成本要素的組合,使 iDempiere 能夠維護詳細的成本分類 — 例如,使用 FIFO 追蹤材料成本,同時以標準成本追蹤人工成本。
MCost API:查詢產品成本
MCost 類別提供存取產品成本資訊的主要介面。每筆 MCost 記錄由複合鍵唯一識別:產品 + ASI + 組織 + 會計架構 + 成本類型 + 成本要素。
關鍵屬性
| 欄位 | 說明 |
|---|---|
| M_Product_ID | 產品 |
| M_AttributeSetInstance_ID | 特定批次(0 = 彙總) |
| C_AcctSchema_ID | 定義成本計算方法的會計架構 |
| M_CostType_ID | 成本類型(例如:標準 vs. 實際) |
| M_CostElement_ID | 成本要素(材料、人工等) |
| CurrentCostPrice | 目前單位成本 |
| CurrentQty | 目前庫存數量(用於平均成本計算) |
| CumulativeAmt / CumulativeQty | 加權平均的累計金額與數量 |
查詢目前成本
// Get the current cost for a product
MProduct product = MProduct.get(ctx, productId);
BigDecimal currentCost = MCost.getCurrentCost(
product,
0, // ASI (0 = aggregate across all lots)
trxName);
System.out.println("Current cost: " + currentCost);
// Get cost for a specific organization
BigDecimal orgCost = MCost.getCurrentCost(
product,
orgId,
0, // ASI
trxName);
// Get seed costs (initial/standard costs)
BigDecimal seedCost = MCost.getSeedCosts(
product,
0, // ASI
new Timestamp(System.currentTimeMillis()),
trxName);
// Get last invoice price
BigDecimal lastInvoicePrice = MCost.getLastInvoicePrice(
product,
0, // C_BPartner_ID (0 = any vendor)
trxName);
// Get last PO price
BigDecimal lastPOPrice = MCost.getLastPOPrice(
product,
orgId,
trxName);
更新成本記錄
// Get the MCost record for modification
MCost cost = MCost.get(product, 0, // ASI
acctSchema, orgId, costElementId, trxName);
if (cost != null) {
// Add cost and quantity (updates weighted average internally)
cost.add(amount, quantity);
cost.saveEx();
// Or set weighted average directly
cost.setWeightedAverage(totalAmount, totalQuantity);
cost.saveEx();
System.out.println("Updated cost: " + cost.getCurrentCostPrice());
}
使用 MCostDetail 進行成本明細追蹤
MCostDetail 提供所有成本變動的交易層級稽核軌跡。每當發生影響成本的交易 — 收貨、發料、完成生產 — 系統會建立一筆成本明細記錄,將成本變動連結至其來源單據。
MCostDetail 結構
| 欄位 | 說明 |
|---|---|
| M_Product_ID | 受影響的產品 |
| C_AcctSchema_ID | 會計架構 |
| M_CostElement_ID | 成本要素 |
| Amt | 成本金額(可為正或負) |
| Qty | 受影響的數量 |
| M_InOutLine_ID | 連結至收貨/出貨明細行 |
| C_OrderLine_ID | 連結至採購單明細行 |
| M_MovementLine_ID | 連結至庫存調撥明細行 |
| M_ProductionLine_ID | 連結至生產明細行 |
| C_InvoiceLine_ID | 連結至發票明細行 |
// Query cost details for a specific product
String sql = "SELECT cd.Amt, cd.Qty, cd.Created, "
+ "cd.M_InOutLine_ID, cd.C_OrderLine_ID, cd.M_ProductionLine_ID "
+ "FROM M_CostDetail cd "
+ "WHERE cd.M_Product_ID = ? AND cd.C_AcctSchema_ID = ? "
+ "ORDER BY cd.Created DESC";
PreparedStatement pstmt = DB.prepareStatement(sql, trxName);
pstmt.setInt(1, productId);
pstmt.setInt(2, acctSchemaId);
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
BigDecimal amt = rs.getBigDecimal("Amt");
BigDecimal qty = rs.getBigDecimal("Qty");
Timestamp created = rs.getTimestamp("Created");
String source = "Unknown";
if (rs.getInt("M_ProductionLine_ID") > 0)
source = "Production";
else if (rs.getInt("M_InOutLine_ID") > 0)
source = "Receipt/Shipment";
else if (rs.getInt("C_OrderLine_ID") > 0)
source = "Purchase Order";
System.out.println(created + " | " + source
+ " | Amt=" + amt + " | Qty=" + qty);
}
DB.close(rs, pstmt);
使用 IndentedBOM 進行多層 BOM 成本展開
IndentedBOM 流程是分析產品 BOM 各層級成本的工具。它會遞迴展開 BOM,計算每個層級的元件成本,並將結果寫入 T_BOM_Indented 暫存表。
IndentedBOM 參數
| 參數 | 說明 |
|---|---|
| M_Product_ID | 要分析的成品 |
| C_AcctSchema_ID | 會計架構(決定成本計算方法) |
| M_CostElement_ID | 要分析的成本要素(材料、人工等) |
IndentedBOM 運作方式
- 讀取產品的主要 BOM
- 針對每個 BOM 明細行,從
MCost取得元件的目前成本 - 計算展開成本:
QtyBOM × CurrentCostPrice - 若元件本身也有 BOM,則遞迴展開至更深一層
- 將每個層級寫入
T_BOM_Indented,並附帶層級指標 - 累計所有層級的總成本
實作自訂 BOM 成本計算器
雖然 IndentedBOM 處理標準的成本展開,您可能需要自訂的成本計算。以下是使用 iDempiere API 實作遞迴 BOM 成本計算器的方式:
public class BOMCostCalculator {
public static class CostBreakdown {
public String productName;
public int level;
public BigDecimal qtyRequired;
public BigDecimal unitCost;
public BigDecimal extendedCost;
public CostBreakdown(String name, int lvl, BigDecimal qty,
BigDecimal cost, BigDecimal extended) {
this.productName = name;
this.level = lvl;
this.qtyRequired = qty;
this.unitCost = cost;
this.extendedCost = extended;
}
}
/**
* Recursively calculate BOM cost.
*/
public List<CostBreakdown> calculateCost(Properties ctx,
int productId, BigDecimal qty, String trxName) {
List<CostBreakdown> breakdown = new ArrayList<>();
MProduct product = MProduct.get(ctx, productId);
MPPProductBOM bom = MPPProductBOM.getDefault(product, trxName);
if (bom != null) {
expandCost(ctx, bom, qty, 0, breakdown, trxName);
}
return breakdown;
}
private void expandCost(Properties ctx, MPPProductBOM bom,
BigDecimal parentQty, int level,
List<CostBreakdown> breakdown, String trxName) {
MPPProductBOMLine[] lines = bom.getLines();
for (MPPProductBOMLine line : lines) {
MProduct component = MProduct.get(ctx, line.getM_Product_ID());
BigDecimal compQty = line.getQtyBOM().multiply(parentQty);
// Check if this is a phantom that should be expanded
if (line.isPhantom() && component.isBOM()) {
// Expand phantom — don't add cost for phantom itself
MPPProductBOM subBOM = MPPProductBOM.getDefault(
component, trxName);
if (subBOM != null) {
expandCost(ctx, subBOM, compQty, level + 1,
breakdown, trxName);
}
} else {
// Leaf component — get its cost
BigDecimal unitCost = MCost.getCurrentCost(
component, 0, trxName);
BigDecimal extCost = compQty.multiply(unitCost);
breakdown.add(new CostBreakdown(
component.getName(), level,
compQty, unitCost, extCost));
}
}
}
/**
* Sum all component costs.
*/
public BigDecimal getTotalCost(List<CostBreakdown> breakdown) {
return breakdown.stream()
.map(b -> b.extendedCost)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
}
// Usage:
BOMCostCalculator calc = new BOMCostCalculator();
List<CostBreakdown> breakdown = calc.calculateCost(
ctx, finishedProductId, new BigDecimal("100"), trxName);
System.out.println("=== BOM Cost Analysis ===");
for (CostBreakdown cb : breakdown) {
String indent = " ".repeat(cb.level);
System.out.printf("%s%s: qty=%.2f × $%.4f = $%.2f%n",
indent, cb.productName,
cb.qtyRequired, cb.unitCost, cb.extendedCost);
}
System.out.printf("TOTAL: $%.2f%n", calc.getTotalCost(breakdown));
生產成本流程
當 MProduction.completeIt() 執行時,成本在系統中的流動方式如下:
元件消耗
針對每筆元件消耗明細行(負數 MovementQty):
- 元件的
MCost.CurrentQty減少 - 建立一筆
MCostDetail記錄,金額為負數(成本沖減) - 對於平均成本法,加權平均價格可能會重新計算
- 對於 FIFO/LIFO,消耗對應的成本層
成品入庫
針對成品明細行(正數 MovementQty):
- 成品的
MCost.CurrentQty增加 - 成本以消耗元件成本的總和計算
- 建立一筆
MCostDetail記錄,記載生產成本 - 對於標準成本法,標準成本與實際成本之間的差異將過帳至差異科目
標準成本差異
使用標準成本法時,實際生產成本(元件實際成本的總和)可能與產品的標準成本不同。此差額將作為生產差異過帳至會計分錄。監控這些差異有助於識別成本效率低落、元件價格的意外變動,或更新標準成本的需求。
重點摘要
- 成本計算方法(標準、平均、FIFO、LIFO、最近價格)決定了
MCost中CurrentCostPrice的維護方式。該方法在會計架構層級進行設定。 - MCost 提供靜態方法(
getCurrentCost()、getSeedCosts()、getLastInvoicePrice())用於查詢成本,以及實例方法(add()、setWeightedAverage())用於更新成本。 - MCostDetail 建立稽核軌跡,將每筆成本變動連結至其來源單據(收貨、發票、生產、調撥)。
- IndentedBOM 透過遞迴展開 BOM 並計算每個層級的元件成本,執行多層成本展開。
- 生產成本流程減少元件成本並增加成品成本。標準成本法捕捉標準成本與實際成本之間的差異,以供財務分析。
下一步
您已完成生產模組的課程內容。若要驗證您的知識,請前往認證考試中心,嘗試生產模組的程式編寫練習。您也可以繼續學習財務管理 — 成本管理課程,以更全面地瞭解 iDempiere 所有模組的成本計算。
日本語
概要
- 学習内容:
- iDempiere における各種原価計算方法:標準原価、平均仕入請求書原価、平均発注原価、先入先出法 (FIFO)、後入先出法 (LIFO)、直近仕入請求書価格、直近発注価格
- MCost API を使用した製品原価の照会と管理方法 — getCurrentCost()、getSeedCosts()、および加重平均計算
- MCostDetail がトランザクションレベルの原価追跡を提供し、監査証跡のためにソースドキュメントとリンクする仕組み
- IndentedBOM プロセスが全構成品レベルにわたる多段階BOM原価積上げを実行する仕組み
- MProduction.completeIt() 実行時に生産ライフサイクルを通じて原価がどのように流れるか
- 前提条件:レッスン 24〜25 およびレッスン 35 — 生産概要、在庫・倉庫管理、高度な生産・BOM管理
- 推定読了時間:22 分
はじめに
生産原価の理解は、収益性分析、価格決定、および財務報告の基盤です。ERPシステムは、何が生産され消費されたかを追跡するだけでなく、組織が選択した原価計算方法を使用して、部品表のあらゆるレベルでそのコストを計算しなければなりません。
iDempiere の原価システムは、3つのコアクラスを中心に構築されています:MCost(製品原価レコード)、MCostDetail(トランザクションレベルの原価エントリ)、および MCostElement(原価タイプ定義)。IndentedBOM プロセスは、これらを統合して多段階BOM原価分析を行います。本レッスンでは、これらのコンポーネントがどのように連携して生産原価を追跡・計算・報告するかを学びます。
原価計算方法
iDempiere は複数の原価計算方法をサポートしており、会計スキーマレベルで設定します。各方法は、MCost の CurrentCostPrice がどのように計算・維持されるかを決定します。
利用可能な方法
| 方法 | 説明 | 使用場面 |
|---|---|---|
| 標準原価 | ユニットあたりの固定原価。手動または原価積上げにより設定。市場変動の影響を受けない。 | 原価が安定している製造業、差異分析 |
| 平均仕入請求書原価 | 仕入請求書(買掛金)価格に基づく加重平均。新しい請求書ごとに更新。 | 複数の仕入先から購入する商社 |
| 平均発注原価 | 発注価格に基づく加重平均。新しい発注ごとに更新。 | 発注価格が請求書価格よりも信頼性が高い場合 |
| 先入先出法 (FIFO) | 先入先出。最も古い在庫原価層から先に消費。 | 生鮮品、規制要件 |
| 後入先出法 (LIFO) | 後入先出。最も新しい在庫原価層から先に消費。 | 一部の管轄区域における税務最適化 |
| 直近仕入請求書価格 | 最新の仕入先請求書の価格を使用。 | 価格変動の激しい回転の速い商品 |
| 直近発注価格 | 最新の発注書の価格を使用。 | 発注価格が現在の市場価値を反映している場合 |
MCostElement — 原価タイプ定義
各原価レコードは、それが表す原価のタイプを定義する MCostElement に関連付けられています:
- 材料費:原材料および構成品の原価
- 労務費:直接労務費
- 製造間接費:工場間接費および間接原価
- 外注加工費:外部業者が行う工程の原価
- 付加費用:追加配賦される原価
原価計算方法と原価要素の組み合わせにより、iDempiere は詳細な原価内訳を維持できます — 例えば、材料費を FIFO で追跡しながら、労務費を標準原価として追跡するといったことが可能です。
MCost API:製品原価の照会
MCost クラスは、製品原価情報にアクセスするための主要インターフェースを提供します。各 MCost レコードは、複合キーによって一意に識別されます:製品 + ASI + 組織 + 会計スキーマ + 原価タイプ + 原価要素。
主要プロパティ
| フィールド | 説明 |
|---|---|
| M_Product_ID | 製品 |
| M_AttributeSetInstance_ID | 特定ロット(0 = 集約) |
| C_AcctSchema_ID | 原価計算方法を定義する会計スキーマ |
| M_CostType_ID | 原価タイプ(例:標準 vs. 実際) |
| M_CostElement_ID | 原価要素(材料費、労務費など) |
| CurrentCostPrice | 現在の単位原価 |
| CurrentQty | 現在の在庫数量(平均原価計算用) |
| CumulativeAmt / CumulativeQty | 加重平均のための累計金額・数量 |
現在原価の照会
// Get the current cost for a product
MProduct product = MProduct.get(ctx, productId);
BigDecimal currentCost = MCost.getCurrentCost(
product,
0, // ASI (0 = aggregate across all lots)
trxName);
System.out.println("Current cost: " + currentCost);
// Get cost for a specific organization
BigDecimal orgCost = MCost.getCurrentCost(
product,
orgId,
0, // ASI
trxName);
// Get seed costs (initial/standard costs)
BigDecimal seedCost = MCost.getSeedCosts(
product,
0, // ASI
new Timestamp(System.currentTimeMillis()),
trxName);
// Get last invoice price
BigDecimal lastInvoicePrice = MCost.getLastInvoicePrice(
product,
0, // C_BPartner_ID (0 = any vendor)
trxName);
// Get last PO price
BigDecimal lastPOPrice = MCost.getLastPOPrice(
product,
orgId,
trxName);
原価レコードの更新
// Get the MCost record for modification
MCost cost = MCost.get(product, 0, // ASI
acctSchema, orgId, costElementId, trxName);
if (cost != null) {
// Add cost and quantity (updates weighted average internally)
cost.add(amount, quantity);
cost.saveEx();
// Or set weighted average directly
cost.setWeightedAverage(totalAmount, totalQuantity);
cost.saveEx();
System.out.println("Updated cost: " + cost.getCurrentCostPrice());
}
MCostDetail による原価明細追跡
MCostDetail は、すべての原価変動に対するトランザクションレベルの監査証跡を提供します。原価に影響するトランザクションが発生するたびに — 入庫、材料出庫、生産完了 — 原価変動をソースドキュメントにリンクする原価明細レコードが作成されます。
MCostDetail の構造
| フィールド | 説明 |
|---|---|
| M_Product_ID | 影響を受ける製品 |
| C_AcctSchema_ID | 会計スキーマ |
| M_CostElement_ID | 原価要素 |
| Amt | 原価金額(正または負) |
| Qty | 影響を受ける数量 |
| M_InOutLine_ID | 入庫/出荷明細行へのリンク |
| C_OrderLine_ID | 発注明細行へのリンク |
| M_MovementLine_ID | 在庫移動明細行へのリンク |
| M_ProductionLine_ID | 生産明細行へのリンク |
| C_InvoiceLine_ID | 請求書明細行へのリンク |
// Query cost details for a specific product
String sql = "SELECT cd.Amt, cd.Qty, cd.Created, "
+ "cd.M_InOutLine_ID, cd.C_OrderLine_ID, cd.M_ProductionLine_ID "
+ "FROM M_CostDetail cd "
+ "WHERE cd.M_Product_ID = ? AND cd.C_AcctSchema_ID = ? "
+ "ORDER BY cd.Created DESC";
PreparedStatement pstmt = DB.prepareStatement(sql, trxName);
pstmt.setInt(1, productId);
pstmt.setInt(2, acctSchemaId);
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
BigDecimal amt = rs.getBigDecimal("Amt");
BigDecimal qty = rs.getBigDecimal("Qty");
Timestamp created = rs.getTimestamp("Created");
String source = "Unknown";
if (rs.getInt("M_ProductionLine_ID") > 0)
source = "Production";
else if (rs.getInt("M_InOutLine_ID") > 0)
source = "Receipt/Shipment";
else if (rs.getInt("C_OrderLine_ID") > 0)
source = "Purchase Order";
System.out.println(created + " | " + source
+ " | Amt=" + amt + " | Qty=" + qty);
}
DB.close(rs, pstmt);
IndentedBOM による多段階BOM原価積上げ
IndentedBOM プロセスは、製品のBOMの全レベルにわたって原価を分析するためのツールです。BOMを再帰的に展開し、各レベルで構成品原価を計算し、結果を T_BOM_Indented 一時テーブルに書き込みます。
IndentedBOM パラメータ
| パラメータ | 説明 |
|---|---|
| M_Product_ID | 分析対象の完成品 |
| C_AcctSchema_ID | 会計スキーマ(原価計算方法を決定) |
| M_CostElement_ID | 分析対象の原価要素(材料費、労務費など) |
IndentedBOM の動作原理
- 製品のマスターBOMを読み取る
- 各BOM明細行について、
MCostから構成品の現在原価を取得する - 展開原価を計算:
QtyBOM × CurrentCostPrice - 構成品が独自のBOMを持つ場合、さらに1レベル深く再帰的に展開する
- 各レベルをレベルインジケータとともに
T_BOM_Indentedに書き込む - 全レベルにわたる合計原価を累計する
カスタムBOM原価計算器の実装
IndentedBOM は標準的な原価積上げを処理しますが、カスタムの原価計算が必要になる場合があります。以下は、iDempiere API を使用した再帰的BOM原価計算器の実装方法です:
public class BOMCostCalculator {
public static class CostBreakdown {
public String productName;
public int level;
public BigDecimal qtyRequired;
public BigDecimal unitCost;
public BigDecimal extendedCost;
public CostBreakdown(String name, int lvl, BigDecimal qty,
BigDecimal cost, BigDecimal extended) {
this.productName = name;
this.level = lvl;
this.qtyRequired = qty;
this.unitCost = cost;
this.extendedCost = extended;
}
}
/**
* Recursively calculate BOM cost.
*/
public List<CostBreakdown> calculateCost(Properties ctx,
int productId, BigDecimal qty, String trxName) {
List<CostBreakdown> breakdown = new ArrayList<>();
MProduct product = MProduct.get(ctx, productId);
MPPProductBOM bom = MPPProductBOM.getDefault(product, trxName);
if (bom != null) {
expandCost(ctx, bom, qty, 0, breakdown, trxName);
}
return breakdown;
}
private void expandCost(Properties ctx, MPPProductBOM bom,
BigDecimal parentQty, int level,
List<CostBreakdown> breakdown, String trxName) {
MPPProductBOMLine[] lines = bom.getLines();
for (MPPProductBOMLine line : lines) {
MProduct component = MProduct.get(ctx, line.getM_Product_ID());
BigDecimal compQty = line.getQtyBOM().multiply(parentQty);
// Check if this is a phantom that should be expanded
if (line.isPhantom() && component.isBOM()) {
// Expand phantom — don't add cost for phantom itself
MPPProductBOM subBOM = MPPProductBOM.getDefault(
component, trxName);
if (subBOM != null) {
expandCost(ctx, subBOM, compQty, level + 1,
breakdown, trxName);
}
} else {
// Leaf component — get its cost
BigDecimal unitCost = MCost.getCurrentCost(
component, 0, trxName);
BigDecimal extCost = compQty.multiply(unitCost);
breakdown.add(new CostBreakdown(
component.getName(), level,
compQty, unitCost, extCost));
}
}
}
/**
* Sum all component costs.
*/
public BigDecimal getTotalCost(List<CostBreakdown> breakdown) {
return breakdown.stream()
.map(b -> b.extendedCost)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
}
// Usage:
BOMCostCalculator calc = new BOMCostCalculator();
List<CostBreakdown> breakdown = calc.calculateCost(
ctx, finishedProductId, new BigDecimal("100"), trxName);
System.out.println("=== BOM Cost Analysis ===");
for (CostBreakdown cb : breakdown) {
String indent = " ".repeat(cb.level);
System.out.printf("%s%s: qty=%.2f × $%.4f = $%.2f%n",
indent, cb.productName,
cb.qtyRequired, cb.unitCost, cb.extendedCost);
}
System.out.printf("TOTAL: $%.2f%n", calc.getTotalCost(breakdown));
生産原価フロー
MProduction.completeIt() が実行されると、原価はシステム内を以下のように流れます:
構成品の消費
各構成品消費明細行(負の MovementQty)について:
- 構成品の
MCost.CurrentQtyが減少する - 負の金額(原価減額)を持つ
MCostDetailレコードが作成される - 平均原価法の場合、加重平均価格が再計算される可能性がある
- FIFO/LIFO の場合、該当する原価層が消費される
完成品の入庫
完成品明細行(正の MovementQty)について:
- 完成品の
MCost.CurrentQtyが増加する - 原価は消費された構成品原価の合計として計算される
- 生産原価を記録する
MCostDetailレコードが作成される - 標準原価法の場合、標準原価と実際原価の差異が差異勘定に転記される
標準原価差異
標準原価法を使用する場合、実際の生産原価(構成品の実際原価の合計)が製品の標準原価と異なることがあります。この差額は会計仕訳において生産差異として転記されます。これらの差異を監視することで、原価の非効率性、構成品価格の予期しない変動、または標準原価の更新の必要性を特定できます。
重要ポイント
- 原価計算方法(標準、平均、FIFO、LIFO、直近価格)は、
MCostにおけるCurrentCostPriceの維持方法を決定します。この方法は会計スキーマレベルで設定されます。 - MCost は、原価照会用の静的メソッド(
getCurrentCost()、getSeedCosts()、getLastInvoicePrice())と、原価更新用のインスタンスメソッド(add()、setWeightedAverage())を提供します。 - MCostDetail は、すべての原価変動をソースドキュメント(入庫、請求書、生産、在庫移動)にリンクする監査証跡を作成します。
- IndentedBOM は、BOMを再帰的に展開し各レベルで構成品原価を計算することで、多段階原価積上げを実行します。
- 生産原価フローは、構成品原価を減少させ完成品原価を増加させます。標準原価法は、財務分析のために標準原価と実際原価の差異を把握します。
次のステップ
生産モジュールのカリキュラムはこれで完了です。知識を確認するには、認定試験センターにアクセスして、生産モジュールのコーディング演習に挑戦してください。また、財務管理 — 原価管理レッスンに進んで、iDempiere の全モジュールにわたる原価計算のより広い視点を学ぶこともできます。