{
  "name": "03 · Dead Stock Report — find frozen capital weekly",
  "nodes": [
    {
      "parameters": {
        "content": "# 03 · Dead Stock Report\n\n**The problem:** capital sits on shelves in products nobody buys. Owners feel it as 'no cash' but can't name the items.\n\n**What this does:** weekly, it reads 90 days of sales and finds every SKU with **zero movement** — then tells you exactly how much money is frozen and what to do with each item.\n\n**Setup:** `Sales` sheet with SKU, Item, QtySold, Date, Cost, Price\n\n**Output:** frozen capital per item + a clear/discontinue/bundle call\n\nBuilt by **Maui Tech Hub** · mauitechhub.com/workflows",
        "height": 440,
        "width": 400,
        "color": 7
      },
      "id": "e01e8798-c6cf-4f53-95b3-67db09469952",
      "name": "NOTE",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -420,
        -60
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 8 * * 1"
            }
          ]
        }
      },
      "id": "6e8ab5e1-fab9-40b8-9006-7a0174b6c727",
      "name": "Weekly Mon 8am",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        0,
        80
      ]
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "value": "YOUR_SHEET_ID",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "Sales",
          "mode": "name"
        },
        "options": {}
      },
      "id": "dbdd733a-61e2-430a-9fc9-938c466292a5",
      "name": "Read Sales",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 1,
      "position": [
        240,
        80
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "// Aggregate sales per SKU over the window and flag non-movers.\nconst WINDOW_DAYS = 90;          // <- tune this\nconst now = new Date(); now.setHours(0,0,0,0);\nconst cut = new Date(now.getTime() - WINDOW_DAYS*86400000);\nconst agg = {};\nfor (const item of $input.all()) {\n  const r = item.json;\n  const sku = String(r.SKU ?? r.sku ?? '').trim();\n  if (!sku) continue;\n  const d = new Date(r.Date ?? r.date ?? now);\n  if (!isNaN(d) && d < cut) continue;\n  const a = agg[sku] ||= { sku, item: r.Item ?? r.item ?? '',\n    sold: 0, cost: Number(r.Cost ?? 0), price: Number(r.Price ?? 0),\n    last_sale: null };\n  a.sold += Number(r.QtySold ?? r.qty ?? 0);\n  if (!isNaN(d) && (!a.last_sale || d > a.last_sale)) a.last_sale = d;\n}\nconst out = [];\nfor (const a of Object.values(agg)) {\n  // only flag genuinely dead or near-dead lines\n  if (a.sold > 2) continue;\n  const onHand = Number(a.stock ?? 0);\n  const frozen = +(onHand * a.cost).toFixed(3);\n  const days = a.last_sale ? Math.round((now - a.last_sale)/86400000) : WINDOW_DAYS;\n  let call;\n  if (a.sold === 0 && days >= 60)      call = 'DISCONTINUE — clear at cost';\n  else if (a.sold === 0)               call = 'BUNDLE with a fast mover';\n  else if (a.sold <= 2)                call = 'REDUCE shelf space, review price';\n  else continue;\n  out.push({ json: { ...a, days_since_sale: days, frozen_capital: frozen,\n    action: call } });\n}\nout.sort((a,b) => b.json.frozen_capital - a.json.frozen_capital);\nreturn out;"
      },
      "id": "6aacf6a2-8e33-4ba1-b676-813494398529",
      "name": "Find Slow Movers",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        500,
        80
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const CURRENCY = 'KD';        // <- change to '$', '£', '€', 'AED' ...\nconst rows = $input.all().map(i=>i.json);\nif (!rows.length) return [{ json: { message:\n  '*DEAD STOCK — nothing to report* 👍\\nEvery SKU moved in the last 90 days.' } }];\nconst frozen = rows.reduce((s,r)=>s+r.frozen_capital,0);\nlet m = '*DEAD STOCK REPORT* — '+new Date().toISOString().slice(0,10)+'\\n';\nm += `_90-day window · ${rows.length} SKUs · *${CURRENCY} ${frozen.toFixed(3)} frozen_\\n`;\nconst groups = { 'DISCONTINUE — clear at cost': [], 'BUNDLE with a fast mover': [],\n                 'REDUCE shelf space, review price': [] };\nfor (const r of rows) {\n  const k = Object.keys(groups).find(k => r.action.startsWith(k.split(' ')[0]));\n  (groups[k] ||= []).push(r);\n}\nfor (const [label, list] of Object.entries(groups)) {\n  if (!list.length) continue;\n  m += '\\n*'+label+'*\\n';\n  m += list.slice(0,12).map(r =>\n    `• ${r.item} — ${CURRENCY} ${r.frozen_capital.toFixed(3)} frozen`\n    + ` (${r.days_since_sale}d no sale)`).join('\\n');\n  if (list.length>12) m += '\\n_…+'+(list.length-12)+' more_';\n  m += '\\n';\n}\nm += '\\n_Recover this cash: '+CURRENCY+' '+frozen.toFixed(3)+'_';\nreturn [{ json: { message: m, count: rows.length, frozen: +frozen.toFixed(3) } }];"
      },
      "id": "f150f811-b540-4f6a-ab76-04d7480bc222",
      "name": "Build Report",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        760,
        80
      ]
    },
    {
      "parameters": {
        "chatId": "YOUR_OWNER_CHAT_ID",
        "text": "={{ $json.message }}",
        "additionalFields": {}
      },
      "id": "2e493969-f47a-454a-8881-0de6c2945715",
      "name": "Send to Owner",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        1020,
        80
      ]
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "YOUR_SHEET_ID",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "DeadStock",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "autoMapInputData",
          "value": {}
        },
        "options": {}
      },
      "id": "197494f3-406f-4283-8af2-2ed7dbb5b6a0",
      "name": "Save Report",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 1,
      "position": [
        1020,
        300
      ]
    }
  ],
  "connections": {
    "Weekly Mon 8am": {
      "main": [
        [
          {
            "node": "Read Sales",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Sales": {
      "main": [
        [
          {
            "node": "Find Slow Movers",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Find Slow Movers": {
      "main": [
        [
          {
            "node": "Build Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Report": {
      "main": [
        [
          {
            "node": "Send to Owner",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send to Owner": {
      "main": [
        [
          {
            "node": "Save Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "pinData": {},
  "meta": {
    "instanceId": "mauitechhub-retail-ops"
  }
}