Apps Script example · 12 min read

FIFO Cost Calculation: Copy-Paste Apps Script Pattern

Working fifo cost calculation example in Apps Script—copy-paste code, common mistakes, and when to get it built professionally.

FIFOInventory costingSheets

FIFO costing needs lot layers with remaining quantity and unit cost. Apps Script can allocate sales against the oldest layers.

fifoCostCalculation skips sales that already have fifoCost, sorts layers by receivedAt, and depletes qtyRemaining as it allocates.

Insufficient stock throws with the sale id so ops can fix receipts before retrying.

This is an educational model — production ERP may need average cost, negative inventory policies, and locked periods.

Need this built? Hire a Google Apps Script developer →

Sheet / project setup

SheetColumnPurpose
CostLayersA lotIdLayer id
CostLayersB skuItem
CostLayersC qtyRemainingMutated
CostLayersD unitCostLayer cost
CostLayersE receivedAtFIFO order
SalesA saleIdSale key
SalesB skuItem
SalesC qtySold qty
SalesD fifoCostOutput total cost

What this script does

Allocates sale quantity across oldest positive layers and writes total cost.

Prerequisites

CostLayers receipts entered before sales costing; matching SKUs.

  • Backup before run
  • Do not re-run without restoring layers
  • Dates parseable

Walkthrough

Two layers cost 10 then 12; sell 3 from first; expect fifoCost 30 and layer qty reduced.

Edge cases

Already-costed sales are skipped — clear column D to recompute only after restoring layers.

  • Throws if not enough qty
  • Cent rounding on total

Idempotency

Because layers mutate, the function skips sales with fifoCost set. Restoring a backup is required before full recomputes.

How to test

Partial layer consumption mid-lot.

Hardening for production

Write allocation audit rows (saleId, lotId, qty, cost).

Variations

LIFO by reversing sort; weighted average by total value/qty.

Full code: fifoCostCalculation()

Load CostLayers and uncosted Sales, then run fifoCostCalculation() once per batch.

/**
 * FIFO unit cost: consume oldest layers first for a sale quantity.
 */
const LAYERS = "CostLayers"; // A lotId, B sku, C qtyRemaining, D unitCost, E receivedAt
const SALES = "Sales";       // A saleId, B sku, C qty, D fifoCost (out)

function fifoCostCalculation() {
  const ss = SpreadsheetApp.getActive();
  const layerSheet = ss.getSheetByName(LAYERS);
  const layers = layerSheet.getDataRange().getValues();
  // sort copy by receivedAt ascending for each sku during consume

  const salesSheet = ss.getSheetByName(SALES);
  const sales = salesSheet.getDataRange().getValues();

  for (let s = 1; s < sales.length; s++) {
    if (sales[s][3] !== "" && sales[s][3] != null) continue; // already costed
    const sku = sales[s][1];
    let need = Number(sales[s][2]) || 0;
    let cost = 0;

    // collect indexes for sku sorted by date
    const idxs = [];
    for (let i = 1; i < layers.length; i++) {
      if (layers[i][1] === sku && Number(layers[i][2]) > 0) idxs.push(i);
    }
    idxs.sort(function (a, b) {
      return new Date(layers[a][4]) - new Date(layers[b][4]);
    });

    for (let k = 0; k < idxs.length && need > 0; k++) {
      const i = idxs[k];
      const avail = Number(layers[i][2]);
      const take = Math.min(avail, need);
      cost += take * Number(layers[i][3]);
      layers[i][2] = avail - take;
      need -= take;
    }
    if (need > 0) throw new Error("Insufficient layers for sale " + sales[s][0]);
    sales[s][3] = Math.round(cost * 100) / 100;
  }

  layerSheet.getRange(1, 1, layers.length, layers[0].length).setValues(layers);
  salesSheet.getRange(1, 1, sales.length, sales[0].length).setValues(sales);
}
  1. Line 4: Layer quantity reduced as sales allocate.
  2. Line 4: Sort key for oldest-first.
  3. Line 17: Skips sales with fifoCost filled.
  4. Line 34: Partial layer consumption.
  5. Line 39: Hard fail when stock layers run out.
  6. Line 43: Persists depleted layer quantities.

Deploy this example

  1. 01

    Open Apps Script

    In the bound spreadsheet: Extensions → Apps Script. For standalone projects, create one at script.google.com and link your Sheet by ID.

  2. 02

    Paste and save

    Add a .gs file, paste the code below, rename constants at the top (sheet names, column letters, API property keys), then save.

  3. 03

    Authorize once

    Run the main function from the editor. Accept OAuth scopes when prompted — triggers cannot run until authorization succeeds once.

  4. 04

    Add the trigger

    Triggers → Add trigger → choose the handler function and event (time-driven, on edit, or on form submit). Delete test triggers before production.

Before you run: FIFO costing

  • 1Backup CostLayers
  • 2Receipts posted with dates
  • 3Sales qty and sku correct
  • 4Understand skip-if-costed behavior
  • 5Agree rounding to cents
  • 6Plan audit trail if required

Frequently asked questions

Not after layers changed. Restore CostLayers and clear fifoCost first.

Compute total value/total qty per SKU and multiply — different function.

This sample throws; some teams allow provisional cost and true-up later.

Include location in the layer filter with sku.

Add positive layers or reverse allocations carefully.

Fine for modest layer counts; index layers by sku in an object of arrays for speed.

Simple clarity; optimize by pre-grouping layers per sku once.

Related examples

Want this wired into your real workflow?

I adapt these patterns to your Sheet structure, APIs, and triggers — deployed in your Google account. Fixed-scope quotes from $500 · free 30-min consult · quote within 24 hours.