Production Costing & Analysis

Level: Advanced Module: Production 16 min read Lesson 36 of 47

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 moving average based on invoice (AP) prices. Updates with each new invoice. This is iDempiere’s implementation of the weighted average costing method. Trading companies buying from multiple vendors
Average PO Weighted moving average based on purchase order prices. Updates with each new PO. Alternative weighted average using PO prices instead of invoice prices. 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
  • Burden (Overhead): Factory overhead and indirect manufacturing costs (machine depreciation, factory utilities, supervision)
  • Outside Processing: Costs for operations performed by external vendors

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

  1. Reads the product’s master BOM
  2. For each BOM line, retrieves the component’s current cost from MCost
  3. Calculates extended cost: QtyBOM × CurrentCostPrice
  4. If a component has its own BOM, recursively expands one level deeper
  5. Writes each level to T_BOM_Indented with level indicators
  6. 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):

  1. The component’s MCost.CurrentQty is decreased
  2. An MCostDetail record is created with a negative amount (cost relief)
  3. For average costing, the weighted average price may be recalculated
  4. For FIFO/LIFO, the appropriate cost layer is consumed

Finished Product Receipt

For the end-product line (positive MovementQty):

  1. The finished product’s MCost.CurrentQty is increased
  2. The cost is calculated as the sum of consumed component costs
  3. An MCostDetail record is created with the production cost
  4. 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:

Production Variance = Actual Cost – Standard Cost

A positive variance (unfavorable) means actual costs exceeded standard; a negative variance (favorable) means actual costs were lower. 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 CurrentCostPrice is maintained in MCost. 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 支援多種成本計算方法,在會計架構層級進行設定。每種方法決定了 MCostCurrentCostPrice 的計算和維護方式。

可用方法

方法 說明 適用情境
標準成本 固定的單位成本,手動設定或透過成本展開計算。不隨市場波動而變化。 成本穩定的製造業;差異分析
平均發票成本 基於發票(應付帳款)價格的加權移動平均。隨每張新發票更新。這是 iDempiere 對加權平均成本法的實作方式。 向多家供應商採購的貿易公司
平均採購單成本 基於採購單價格的加權移動平均。隨每筆新採購單更新。使用採購單價格而非發票價格的替代加權平均方式。 當採購單價格比發票價格更可靠時
先進先出 (FIFO) 先進先出。最舊的庫存成本層優先消耗。 易腐商品、法規要求
後進先出 (LIFO) 後進先出。最新的庫存成本層優先消耗。 某些管轄區域的稅務優化
最近發票價格 使用最近一張供應商發票的價格。 價格波動劇烈的快速流通商品
最近採購單價格 使用最近一筆採購單的價格。 當採購單價格反映當前市場價值時

MCostElement — 成本類型定義

每筆成本記錄都關聯至一個 MCostElement,用以定義其所代表的成本類型:

  • 材料:原物料及元件成本
  • 人工:直接人工成本
  • Burden(製造費用/間接費用):工廠間接製造成本(機器折舊、工廠水電、管理費用)
  • 委外加工:由外部供應商執行作業的成本

成本計算方法與成本要素的組合,使 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 運作方式

  1. 讀取產品的主要 BOM
  2. 針對每個 BOM 明細行,從 MCost 取得元件的目前成本
  3. 計算展開成本:QtyBOM × CurrentCostPrice
  4. 若元件本身也有 BOM,則遞迴展開至更深一層
  5. 將每個層級寫入 T_BOM_Indented,並附帶層級指標
  6. 累計所有層級的總成本

實作自訂 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):

  1. 元件的 MCost.CurrentQty 減少
  2. 建立一筆 MCostDetail 記錄,金額為負數(成本沖減)
  3. 對於平均成本法,加權平均價格可能會重新計算
  4. 對於 FIFO/LIFO,消耗對應的成本層

成品入庫

針對成品明細行(正數 MovementQty):

  1. 成品的 MCost.CurrentQty 增加
  2. 成本以消耗元件成本的總和計算
  3. 建立一筆 MCostDetail 記錄,記載生產成本
  4. 對於標準成本法,標準成本與實際成本之間的差異將過帳至差異科目

標準成本差異

使用標準成本法時,實際生產成本(元件實際成本的總和)可能與產品的標準成本不同。此差額將作為生產差異過帳至會計分錄:

生產差異 = 實際成本 – 標準成本

正差異(不利)表示實際成本超過標準成本;負差異(有利)表示實際成本低於標準成本。監控這些差異有助於識別成本效率低落、元件價格的意外變動,或更新標準成本的需求。

重點摘要

  • 成本計算方法(標準、平均、FIFO、LIFO、最近價格)決定了 MCostCurrentCostPrice 的維護方式。該方法在會計架構層級進行設定。
  • 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 は複数の原価計算方法をサポートしており、会計スキーマレベルで設定します。各方法は、MCostCurrentCostPrice がどのように計算・維持されるかを決定します。

利用可能な方法

方法 説明 使用場面
標準原価 ユニットあたりの固定原価。手動または原価積上げにより設定。市場変動の影響を受けない。 原価が安定している製造業、差異分析
平均仕入請求書原価 仕入請求書(買掛金)価格に基づく加重移動平均。新しい請求書ごとに更新。これは iDempiere における加重平均原価法の実装方式です。 複数の仕入先から購入する商社
平均発注原価 発注価格に基づく加重移動平均。新しい発注ごとに更新。請求書価格の代わりに発注価格を使用する代替的な加重平均方式です。 発注価格が請求書価格よりも信頼性が高い場合
先入先出法 (FIFO) 先入先出。最も古い在庫原価層から先に消費。 生鮮品、規制要件
後入先出法 (LIFO) 後入先出。最も新しい在庫原価層から先に消費。 一部の管轄区域における税務最適化
直近仕入請求書価格 最新の仕入先請求書の価格を使用。 価格変動の激しい回転の速い商品
直近発注価格 最新の発注書の価格を使用。 発注価格が現在の市場価値を反映している場合

MCostElement — 原価タイプ定義

各原価レコードは、それが表す原価のタイプを定義する MCostElement に関連付けられています:

  • 材料費:原材料および構成品の原価
  • 労務費:直接労務費
  • Burden(製造間接費/オーバーヘッド):工場の間接製造原価(機械減価償却、工場光熱費、監督費用)
  • 外注加工費:外部業者が行う工程の原価

原価計算方法と原価要素の組み合わせにより、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 の動作原理

  1. 製品のマスターBOMを読み取る
  2. 各BOM明細行について、MCost から構成品の現在原価を取得する
  3. 展開原価を計算:QtyBOM × CurrentCostPrice
  4. 構成品が独自のBOMを持つ場合、さらに1レベル深く再帰的に展開する
  5. 各レベルをレベルインジケータとともに T_BOM_Indented に書き込む
  6. 全レベルにわたる合計原価を累計する

カスタム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)について:

  1. 構成品の MCost.CurrentQty が減少する
  2. 負の金額(原価減額)を持つ MCostDetail レコードが作成される
  3. 平均原価法の場合、加重平均価格が再計算される可能性がある
  4. FIFO/LIFO の場合、該当する原価層が消費される

完成品の入庫

完成品明細行(正の MovementQty)について:

  1. 完成品の MCost.CurrentQty が増加する
  2. 原価は消費された構成品原価の合計として計算される
  3. 生産原価を記録する MCostDetail レコードが作成される
  4. 標準原価法の場合、標準原価と実際原価の差異が差異勘定に転記される

標準原価差異

標準原価法を使用する場合、実際の生産原価(構成品の実際原価の合計)が製品の標準原価と異なることがあります。この差額は会計仕訳において生産差異として転記されます:

生産差異 = 実際原価 – 標準原価

正の差異(不利差異)は実際原価が標準を超過したことを示し、負の差異(有利差異)は実際原価が標準を下回ったことを示します。これらの差異を監視することで、原価の非効率性、構成品価格の予期しない変動、または標準原価の更新の必要性を特定できます。

重要ポイント

  • 原価計算方法(標準、平均、FIFO、LIFO、直近価格)は、MCost における CurrentCostPrice の維持方法を決定します。この方法は会計スキーマレベルで設定されます。
  • MCost は、原価照会用の静的メソッド(getCurrentCost()getSeedCosts()getLastInvoicePrice())と、原価更新用のインスタンスメソッド(add()setWeightedAverage())を提供します。
  • MCostDetail は、すべての原価変動をソースドキュメント(入庫、請求書、生産、在庫移動)にリンクする監査証跡を作成します。
  • IndentedBOM は、BOMを再帰的に展開し各レベルで構成品原価を計算することで、多段階原価積上げを実行します。
  • 生産原価フローは、構成品原価を減少させ完成品原価を増加させます。標準原価法は、財務分析のために標準原価と実際原価の差異を把握します。

次のステップ

生産モジュールのカリキュラムはこれで完了です。知識を確認するには、認定試験センターにアクセスして、生産モジュールのコーディング演習に挑戦してください。また、財務管理 — 原価管理レッスンに進んで、iDempiere の全モジュールにわたる原価計算のより広い視点を学ぶこともできます。

You Missed