Cost of Goods Sold (COGS) Calculation Guide

This guide provides a comprehensive approach to calculating Cost of Goods Sold (COGS) using the Stateset API. It covers purchase order integration, work order processing, inventory management, and various costing methods.

Table of Contents

  1. Setup
  2. Purchase Order Integration
  3. Work Order and BOM Processing
  4. Inventory Management
  5. COGS Calculation Methods
  6. Periodic COGS Calculation
  7. Reporting

1. Setup

First, install the Stateset Node.js SDK:

npm install stateset-node

Then, initialize the Stateset client:

import { stateset } from 'stateset-node';

const client = stateset('YOUR_API_KEY');

2. Purchase Order Integration

2.1 Create Purchase Order

async function createPurchaseOrder(poData) {
  try {
    const created = await client.purchaseorder.create({
      purchase_order: {
        id: poData.id,
        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
      }
    });
    return created;
  } catch (error) {
    console.error("Error creating purchase order:", error);
    throw error;
  }
}

2.2 Retrieve and Update Purchase Orders

async function getPurchaseOrder(poNumber) {
  try {
    return await client.purchaseorder.retrieve(poNumber);
  } catch (error) {
    console.error("Error retrieving purchase order:", error);
    throw error;
  }
}

async function updatePurchaseOrder(poNumber, updateData) {
  try {
    return await client.purchaseorder.update(poNumber, updateData);
  } catch (error) {
    console.error("Error updating purchase order:", error);
    throw error;
  }
}

2.3 Receive Purchase Order

async function receivePurchaseOrder(poNumber) {
  const po = await getPurchaseOrder(poNumber);
  
  for (const lineItem of po.line_items) {
    await updateInventory(lineItem.part_number, lineItem.quantity, lineItem.unit_cost);
  }

  await updatePurchaseOrder(poNumber, { status: 'RECEIVED', received_date: new Date() });
}

3. Work Order and BOM Processing

3.1 Retrieve Work Orders and BOMs

async function getWorkOrdersForPeriod(startDate, endDate) {
  return await client.workOrder.list({
    created_at: { gte: startDate, lte: endDate },
    status: 'COMPLETED'
  });
}

async function getBOMForWorkOrder(bomNumber) {
  return await client.billOfMaterials.retrieve(bomNumber);
}

3.2 Calculate COGS for Work Order

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

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

  return {
    workOrderNumber: workOrder.number,
    totalCost: totalCost,
    quantityProduced: workOrder.line_items.reduce((sum, item) => sum + item.total_quantity, 0)
  };
}

async function getComponentCost(partNumber) {
  try {
    // Retrieve the latest inventory record for the part
    const latestInventory = await client.inventory.list({
      part_number: partNumber,
      limit: 1,
      sort: '-updated_at'
    });

    if (latestInventory.length === 0) {
      throw new Error(`No inventory found for part number: ${partNumber}`);
    }

    // Return the unit cost from the latest inventory record
    return latestInventory[0].unit_cost;
  } catch (error) {
    console.error(`Error getting component cost for part ${partNumber}:`, error);
    throw error;
  }

}

4. Inventory Management

4.1 Update Inventory

async function updateInventory(partNumber, quantity, unitCost) {

  try {
    // Create a new inventory record
    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;
  }


}

4.2 Get Inventory Movements

async function getInventoryMovements(productId, startDate, endDate) {
  return await client.inventory.listMovements({
    product: productId,
    date: { gte: startDate, lte: endDate }
  });
}

5. COGS Calculation Methods

5.1 Average Cost Method

async function calculateAverageCost(productId, dateRange) {
  const purchases = await client.purchaseorder.list({ product: productId, date: dateRange });
  const inventory = await client.inventory.list({ product: productId, date: dateRange });
  const manufacturingCosts = await client.manufacturingorder.list({ product: productId, date: dateRange });

  const totalCost = purchases.reduce((sum, po) => sum + po.total_cost, 0) +
                    manufacturingCosts.reduce((sum, cost) => sum + cost.amount, 0);
  const totalQuantity = inventory.reduce((sum, inv) => sum + inv.quantity, 0);

  return totalCost / totalQuantity;
}

5.2 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.cost).times(purchase.quantity));
    totalQuantity = totalQuantity.plus(purchase.quantity);
  }

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

async function calculateWeightedAverageCOGS(productId, startDate, endDate) {
  const purchaseOrders = await getPurchaseOrders(productId, startDate, endDate);
  const inventoryMovements = await getInventoryMovements(productId, startDate, endDate);
  const manufacturingCosts = await getManufacturingCosts(productId, startDate, endDate);

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

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

  for (const movement of allMovements) {
    switch (movement.type) {
      case 'purchase':
      case 'manufacturing':
        totalPurchases.push({
          quantity: Big(movement.quantity),
          cost: Big(movement.type === 'purchase' ? movement.unitCost : movement.totalCost / movement.quantity)
        });
        break;
      case 'movement':
        if (movement.quantityChange > 0) {
          const newAverageCost = calculateWeightedAverageCost(inventory, totalPurchases);
          inventory.quantity = inventory.quantity.plus(movement.quantityChange);
          inventory.cost = newAverageCost;
          totalPurchases = [];
        } else {
          const quantitySold = Big(Math.abs(movement.quantityChange));
          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()
    }
  };
}

6. Periodic COGS Calculation

async function calculateMonthlyCOGS() {
  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, endDate);
  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;

  const previousPeriod = `${new Date(date.getFullYear(), date.getMonth() - 1, 1).getFullYear()}-${String(date.getMonth()).padStart(2, '0')}`;
  const previousCOGSData = await getPreviousCOGSData(previousPeriod);

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

  await storeCOGSData({
    period: `${startDate.getFullYear()}-${String(startDate.getMonth() + 1).padStart(2, '0')}`,
    totalCOGS,
    averageCOGS,
    quantityProduced: totalQuantityProduced,
    cogsTrend
  });
}

7. Reporting

async function generateCOGSReport(startDate, endDate) {
  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,
      part: workOrder.part,
      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
  };
}

For any questions or assistance, please contact our support team at support@stateset.io.