Cost of Goods Sold (COGS) Calculation Guide

Welcome to the Stateset One COGS Quickstart Guide! This guide will walk you through the process of calculating the Cost of Goods Sold (COGS) using the Stateset API. Let’s imagine we are a manufacturer who wants to understand profitability, control costs, and make informed business decisions. By the end of this guide, you’ll learn how to use Stateset to manage your inventory, track production costs, and accurately calculate COGS.

Table of Contents

  1. Introduction
  2. Getting Started
  3. Core Concepts: COGS & Inventory Flow
  4. API Walkthrough: COGS Calculation Workflow
  5. Advanced COGS Considerations
  6. Error Handling
  7. Troubleshooting
  8. Support Resources
  9. Conclusion

Introduction

Cost of Goods Sold (COGS) is a crucial metric for any business that manufactures or sells products. Understanding your COGS allows you to assess profitability, manage expenses, and make informed business decisions. This guide will show you how to use the Stateset API to calculate COGS accurately by managing purchase orders, work orders, inventory, and costing methods.

What You’ll Learn:

  • How to set up your Stateset environment and use the SDK.
  • Core concepts about COGS, inventory, and costing methods.
  • How to use the Stateset API to manage your purchasing and production data.
  • Methods for calculating COGS using average and weighted average costing.
  • How to handle errors and implement best practices for COGS calculation.

Getting Started

Let’s set up your environment.

1. Install the Stateset Node.js SDK

Integrate Stateset’s SDK into your project to simplify API interactions.

npm install stateset-node

2. Set Up Environment Variables

Store your API key securely using environment variables. (Note: This example is for bash shell, adapt as needed for your environment)

export STATESET_API_KEY=your_api_key_here

3. Initialize the Stateset Client

Initialize the Stateset client in your application using the generated API key. (Note: Requires Node.js 16 or higher)

import { stateset } from 'stateset-node';

// Initialize with your API key
const client = new stateset(process.env.STATESET_API_KEY);

// Verify connection
async function verifyConnection() {
  try {
    const status = await client.system.healthCheck();
    console.log('Connection status:', status);
  } catch (error) {
    console.error('Failed to connect:', error);
  }
}

verifyConnection();

Note: The above example uses async/await which requires a Javascript engine that supports it.


Core Concepts: COGS & Inventory Flow

Before we jump into the code, let’s review some essential concepts that will help understand how COGS is calculated and how the different components are related.

What is COGS? COGS is the direct costs attributable to the production of the goods sold by a company. It includes the cost of materials, labor, and other directly related expenses. For manufacturers this includes the costs to produce goods. COGS is a significant expense for businesses, and accurately tracking it is key to understanding profitability.

How does it relate to Inventory?

Inventory is a key component of the COGS calculation. You must keep track of both the value and quantity of your goods in inventory. As raw materials are purchased, inventory is increased; as products are produced, you transition to a value of work-in-progress and then finished goods. When a sale happens, the inventory is reduced and a portion of the value is expensed as COGS. Here is a simplified view:

Here is how the different steps relate to the COGS calculation:

  • Purchase Order: This is where your costs are first tracked when you purchase raw materials. This increases your inventory of raw materials.
  • Work Order/BOM: When you create a work order you transition raw materials into work-in-progress. The cost of your components from your BOM are used to track the total amount.
  • Finished Goods: As you complete work orders, your work in progress becomes finished goods, and you can now sell the product.
  • Sale: When a sale is made, the value of the goods sold is recognized as COGS.

Costing Methods

  • Average Cost: This method uses the average cost of all available items in inventory. It is calculated by dividing the total cost of goods by the total number of items. This provides a simplified method, but doesn’t account for price fluctuations.
  • Weighted Average Cost: This method takes into account the quantity of items purchased and calculates a weighted average cost. This can be more accurate than simple average cost, and is often preferred when items fluctuate in price.

With a solid understanding of these core concepts, you’re now ready to use the Stateset API to manage your COGS calculation process.


API Walkthrough: COGS Calculation Workflow

Let’s dive into using the Stateset API to manage your COGS calculations, broken down into the following workflow.

Workflow 1: Purchasing and Receiving

In this section, we’ll cover creating a purchase order, receiving items from it, and updating the inventory.

Example 1: Creating a Purchase Order

async function createPurchaseOrder(poData) {
    try {
        const newPO = await client.purchaseorder.create({
          number: poData.number,
          supplier: poData.supplier,
          order_date: poData.orderDate,
          expected_delivery_date: poData.expectedDeliveryDate,
          status: poData.status,
          total_amount: poData.totalAmount,
          currency: poData.currency,
           line_items: poData.lineItems
        });
      console.log("Purchase order created:", newPO);
        return newPO;
     } catch (error) {
        console.error("Error creating purchase order:", error);
        throw error;
   }
}

const purchaseOrder = await createPurchaseOrder({
  number: 'PO-2024-001',
   supplier: 'Acme Supplies',
   orderDate: new Date().toISOString(),
   expectedDeliveryDate: new Date(new Date().getTime() + 86400000).toISOString(), // one day from now
   status: 'OPEN',
   totalAmount: 500,
    currency: 'USD',
  lineItems: [{
     part_number: 'item_001',
       quantity: 10,
        unit_cost: 50
     }]
});

This function creates a new Purchase Order in Stateset, using the information in the poData object. This includes details about the supplier, dates, status, costs, and individual items being purchased.

Example 2: Receiving a Purchase Order

async function receivePurchaseOrder(po) {
  try{
    for (const lineItem of po.line_items) {
     await updateInventory(lineItem.part_number, lineItem.quantity, lineItem.unit_cost);
    }
    const updatedPO = await client.purchaseorder.update(po.id, { status: 'RECEIVED', received_date: new Date() });
    console.log('Purchase Order Received:', updatedPO);
      return updatedPO
    } catch (error) {
        console.error("Error receiving purchase order", error)
        throw error
    }

}
const receivedPO = await receivePurchaseOrder(purchaseOrder);

After the PO has been created, it is now time to receive the items. The receivePurchaseOrder will look at the line_items of the purchase order and update inventory. In this example, we are assuming that all of the items are received. The status is also updated.

Example 3: Updating Inventory

async function updateInventory(partNumber, quantity, unitCost) {
    try {
        const newInventory = await client.inventory.create({
            part_number: partNumber,
            quantity: quantity,
           unit_cost: unitCost
         });
         console.log(`Inventory updated for part ${partNumber}:`, newInventory);
      return newInventory;
    } catch (error) {
       console.error(`Error updating inventory for part ${partNumber}:`, error);
        throw error;
    }
}

This function is called by the receivePurchaseOrder function above to update the inventory. This will create a new inventory entry with the specified part_number, quantity and unitCost. The cost of goods will be tracked in inventory using the unit_cost field. Note: This call will create a new inventory entry; the system tracks inventory by movements.

Workflow 2: Work Order and BOM Processing

After updating inventory with your raw material costs, now you’ll create work orders to produce finished goods.

Example 1: Get Work Orders for a Period

async function getWorkOrdersForPeriod(startDate, endDate) {
  try{
    const workOrders = await client.workorder.list({
       created_at: { gte: startDate, lte: endDate },
      status: 'COMPLETED'
    });
      return workOrders;
  } catch (error) {
    console.error("Error getting work orders for a period", error);
        throw error
  }
}

const startDate = new Date(new Date().getFullYear(), new Date().getMonth() - 1, 1).toISOString()
const endDate = new Date().toISOString()
const workOrders = await getWorkOrdersForPeriod(startDate, endDate);

This function retrieves all work orders that were created and completed in the specified time frame using the list method. This will give you the work orders to use in COGS calculations.

Example 2: Get BOM for a Work Order

async function getBOMForWorkOrder(workOrder) {
    try {
        if(!workOrder.bill_of_material_id){
           throw new Error("Work order must have a bill of materials id")
        }
        const bom = await client.billofmaterials.get(workOrder.bill_of_material_id);
        return bom
     } catch (error) {
        console.error("Error getting BOM", error)
         throw error
     }
}

// Note: Assuming we have a work order here named workOrder
const bom = await getBOMForWorkOrder(workOrders[0]);

This function is used to get a BOM using the bill_of_material_id from the workOrder object. This will be used to calculate the cost of the products.

Example 3: Calculate COGS for a Work Order

async function calculateCOGSForWorkOrder(workOrder) {
  try {
    const bom = await getBOMForWorkOrder(workOrder);
    let totalCost = 0;

    for (const lineItem of bom.components) {
        const componentCost = await getComponentCost(lineItem.item_id);
      totalCost += componentCost * lineItem.quantity;
    }

      return {
      workOrderNumber: workOrder.number,
       totalCost: totalCost,
      quantityProduced: workOrder.work_order_line_items.reduce((sum, item) => sum + item.quantity, 0)
    };
  } catch(error) {
    console.error("Error calculating COGS for a work order", error);
    throw error
  }
}

async function getComponentCost(partNumber) {
  try {
    const inventoryList = await client.inventory.list({
      part_number: partNumber,
      limit: 1,
      sort: '-created_at' // Use created at for most recent inventory entry
    });
    if (inventoryList.length === 0) {
     throw new Error(`No inventory found for part number: ${partNumber}`);
   }

    return inventoryList[0].unit_cost;
  } catch (error) {
    console.error(`Error getting component cost for part ${partNumber}:`, error);
    throw error;
  }
}
// Example of use
const cogsForWorkOrder = await calculateCOGSForWorkOrder(workOrders[0]);
console.log("COGS for Work Order:", cogsForWorkOrder)

This function will retrieve the BOM and then using the component parts in the BOM, it will calculate the total cost. It calculates the quantityProduced using the line items in the work order. You may want to change this logic to calculate this based on how you use the API.

Workflow 3: Costing Methods

In this section, we’ll show how to implement the average and weighted average cost methods.

Example 1: Implement Average Cost Method

async function calculateAverageCost(productId, dateRange) {
    try {
        const purchases = await client.purchaseorder.list({
          //  product: productId, // assuming there is a product identifier
            created_at: dateRange
      });

    const inventory = await client.inventory.list({
           // product: productId, // assuming there is a product identifier
           created_at: dateRange
      });
        const manufacturingCosts = await client.manufacturingorder.list({
         //   product: productId, // assuming there is a product identifier
            created_at: dateRange
        });

     const totalCost = purchases.reduce((sum, po) => sum + po.total_amount, 0) +
            manufacturingCosts.reduce((sum, mo) => sum + mo.total_amount, 0);

        const totalQuantity = inventory.reduce((sum, inv) => sum + inv.quantity, 0);
        return totalCost / totalQuantity;
    } catch (error) {
         console.error("Error calculating average cost", error)
        throw error
    }
}

const averageCost = await calculateAverageCost('prod_123', { gte: startDate, lte: endDate })
console.log("Average Cost:", averageCost)

This function calculates the average cost of a product by summing up the total cost of purchases, manufacturing costs and dividing by the total quantity. Note: In this example we are assuming that you have some way to tie the purchase and manufacturing order to a product. This would have to be adjusted to use the API correctly.

Example 2: Implement Weighted Average Cost Method

import Big from 'big.js';

function calculateWeightedAverageCost(previousInventory, purchases) {
    let totalCost = Big(previousInventory.cost).times(previousInventory.quantity);
    let totalQuantity = Big(previousInventory.quantity);

    for (const purchase of purchases) {
        totalCost = totalCost.plus(Big(purchase.unit_cost).times(purchase.quantity));
        totalQuantity = totalQuantity.plus(purchase.quantity);
   }

     return totalQuantity.eq(0) ? Big(0) : totalCost.div(totalQuantity);
}

async function calculateWeightedAverageCOGS(productId, startDate, endDate) {
    try {
    const purchaseOrders = await client.purchaseorder.list({
            //product: productId, // assuming there is a product identifier
          created_at: { gte: startDate, lte: endDate }
    });
    const inventoryMovements = await client.inventory.list({
          // product: productId, // assuming there is a product identifier
         created_at: { gte: startDate, lte: endDate }
        });

    let inventory = { quantity: Big(0), cost: Big(0) };
    let cogs = Big(0);
        let totalPurchases = [];


        const allMovements = [
            ...purchaseOrders.map(po => ({ date: po.created_at, type: 'purchase', ...po })),
           ...inventoryMovements.map(mov => ({ date: mov.created_at, type: 'movement', ...mov }))
      ].sort((a, b) => new Date(a.date) - new Date(b.date));

        for (const movement of allMovements) {
           switch (movement.type) {
                case 'purchase':
                    totalPurchases.push({
                       quantity: Big(movement.quantity),
                        unit_cost: Big(movement.unit_cost)
                    });
                 break;
               case 'movement':
                   if (movement.quantity > 0) {
                      const newAverageCost = calculateWeightedAverageCost(inventory, totalPurchases);
                      inventory.quantity = inventory.quantity.plus(movement.quantity);
                      inventory.cost = newAverageCost;
                      totalPurchases = [];
                    } else {
                       const quantitySold = Big(Math.abs(movement.quantity));
                       cogs = cogs.plus(inventory.cost.times(quantitySold));
                       inventory.quantity = inventory.quantity.minus(quantitySold);
                     }
                 break;
           }
        }
        return {
          cogs: cogs.toNumber(),
            endingInventory: {
              quantity: inventory.quantity.toNumber(),
              averageCost: inventory.cost.toNumber()
           }
        };
    } catch (error) {
        console.error("Error calculating Weighted Average COGS", error)
        throw error
    }
}
//Example of use
const weightedAverageCOGS = await calculateWeightedAverageCOGS('prod_123', startDate, endDate)
console.log("Weighted Average COGS:", weightedAverageCOGS)

This function calculates the Weighted Average COGS by iterating through all purchase and inventory movements. It uses the Big.js library to ensure that math is accurate. It uses similar assumptions as the Average Cost calculation. Note: You will want to adjust the calls to the list functions and data mapping to correctly pull in your data.

Workflow 4: Periodic COGS and Reporting

In this final section, you’ll see how to calculate COGS periodically and how to generate a report.

Example 1: Calculate Monthly COGS

async function calculateMonthlyCOGS() {
  try {
    const date = new Date();
   const startDate = new Date(date.getFullYear(), date.getMonth(), 1);
     const endDate = new Date(date.getFullYear(), date.getMonth() + 1, 0);
    const workOrders = await getWorkOrdersForPeriod(startDate.toISOString(), endDate.toISOString());
      let totalCOGS = 0;
        let totalQuantityProduced = 0;

       for (const workOrder of workOrders) {
         const workOrderCOGS = await calculateCOGSForWorkOrder(workOrder);
         totalCOGS += workOrderCOGS.totalCost;
         totalQuantityProduced += workOrderCOGS.quantityProduced;
      }

        const averageCOGS = totalCOGS / totalQuantityProduced;
        //Get Previous Period
        const previousPeriod = `${new Date(date.getFullYear(), date.getMonth() - 1, 1).getFullYear()}-${String(date.getMonth()).padStart(2, '0')}`;
       //Placeholder: This is where you would call previous COGS data that you have stored.
        const previousCOGSData =  {
          totalCOGS: 10000,
          quantityProduced: 100,
          averageCOGS: 100
       } //await getPreviousCOGSData(previousPeriod);

       const cogsTrend = previousCOGSData
        ? ((totalCOGS - previousCOGSData.totalCOGS) / previousCOGSData.totalCOGS) * 100
         : 0;

      const results = {
        period: `${startDate.getFullYear()}-${String(startDate.getMonth() + 1).padStart(2, '0')}`,
       totalCOGS,
         averageCOGS,
          quantityProduced: totalQuantityProduced,
          cogsTrend
      }
      console.log("Monthly COGS Results:", results)
       // Placeholder: this is where you would store the results.
      return results
  } catch (error) {
     console.error("Error calculating monthly COGS", error)
     throw error
   }
}
// Example of usage
const monthlyCOGS = await calculateMonthlyCOGS()

This function calculates the COGS for all work orders in the current month and calculates the COGS trend using a previous month’s COGS. Note: This function uses a placeholder for getPreviousCOGSData; you’ll need to implement how you want to store data.

Example 2: Generate a COGS Report

async function generateCOGSReport(startDate, endDate) {
  try {
    const workOrders = await getWorkOrdersForPeriod(startDate, endDate);
    let totalCOGS = 0;
   let totalQuantityProduced = 0;
    const workOrderDetails = [];

      for (const workOrder of workOrders) {
      const workOrderCOGS = await calculateCOGSForWorkOrder(workOrder);
          totalCOGS += workOrderCOGS.totalCost;
        totalQuantityProduced += workOrderCOGS.quantityProduced;
          workOrderDetails.push({
           workOrderNumber: workOrder.number,
        quantityProduced: workOrderCOGS.quantityProduced,
            totalCost: workOrderCOGS.totalCost,
           averageCost: workOrderCOGS.totalCost / workOrderCOGS.quantityProduced
        });
      }
    return {
     period: `${startDate.toISOString().split('T')[0]} to ${endDate.toISOString().split('T')[0]}`,
        totalCOGS,
     averageCOGS: totalCOGS / totalQuantityProduced,
      quantityProduced: totalQuantityProduced,
       workOrders: workOrderDetails
    };
  } catch (error) {
     console.error("Error generating COGS Report", error)
        throw error
  }
}
// Example of use
const report = await generateCOGSReport(startDate, endDate)
console.log("COGS Report:", report)

This function generates a COGS report showing the total COGS, average COGS, quantity produced, and work order details within the specified period. This data can then be used for decision-making.


Advanced COGS Considerations

This section covers a few more advanced considerations for COGS calculations:

  • Inventory Valuation Methods: Explore other inventory valuation methods like FIFO (First-In, First-Out) or LIFO (Last-In, First-Out). These can be relevant depending on your accounting standards. The implementation of these methods will vary depending on how you are using the API, but the principles are the same: You need to track the cost of the item in inventory.
  • Handling Returns and Adjustments: When products are returned or there are inventory adjustments, those changes need to be reflected in your COGS calculations. Make sure the system tracks these items and you have a method for correcting errors.
  • Real-time Data and Integration: Integrate your COGS calculations with other systems, like your accounting software, to have real-time data for financial analysis. Consider using webhooks for real-time updates on key COGS events.

Error Handling

Here are key points for handling errors using the Stateset API.

  • API Error Responses: When an API call fails, the response will have a descriptive error message. This should be parsed to understand what went wrong (for example, missing fields, authentication errors, etc). Consult the API documentation for a complete list of error codes.
  • Retry Logic: In cases of transient errors, like network issues, consider using retry mechanisms.
  • Centralized Logging: Implement a centralized logging system that allows you to easily identify and address any issues with your COGS calculation systems.
async function safeCOGSoperation(operation) {
  try {
    const result = await operation();
    console.log(`Operation successful: ${JSON.stringify(result)}`);
    return result;
  } catch (error) {
    console.error(`Error in COGS operation: ${error.message}`);
    // Log to external logging service
    console.error("External Log:", {
      level: 'error',
      message: error.message,
      stack: error.stack,
      timestamp: new Date().toISOString()
    })
    // Placeholder for notification to personnel
    console.error("Notify Personnel:", error.message)
    throw error;
  }
}

// Usage Example
const purchaseOrder = await safeCOGSoperation(() =>
  createPurchaseOrder({
    number: 'PO-2024-002',
    supplier: 'Acme Supplies',
    orderDate: new Date().toISOString(),
    expectedDeliveryDate: new Date(new Date().getTime() + 86400000).toISOString(), // one day from now
    status: 'OPEN',
    totalAmount: 500,
    currency: 'USD',
    lineItems: [{
      part_number: 'item_001',
      quantity: 10,
      unit_cost: 50
    }]
  })
);

In this example, we are wrapping our API calls in a function called safeCOGSoperation to handle errors and add centralized logging.


Troubleshooting

  • API Connection Failures:
    • Solution: Verify your API key, ensure a stable internet connection, and double-check your API endpoint.
  • Data Inconsistencies:
    • Solution: Use regular cycle counts and audit the system often. Make sure you are mapping your data and making API calls correctly.
  • COGS Calculation Errors:
    • Solution: Inspect the data you are using in your calculation, ensure that your logic is correct.

Support Resources


Conclusion

You’ve now explored the process of calculating COGS using the Stateset API. By following this guide, you are now familiar with managing your purchase orders, work orders, inventory, and using different costing methods. This can give you actionable data and enable you to make decisions that will lead to a more profitable and efficient manufacturing business. If you need any further assistance, please be sure to use the support resources.