{
  "id": "r1UbQa3bXqbHTzdi",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Automated Category Sales Performance Report",
  "tags": [],
  "nodes": [
    {
      "id": "f938bd6b-2b48-4b3c-9aeb-bda8b928fe0c",
      "name": "Category Map",
      "type": "n8n-nodes-base.code",
      "position": [
        -32,
        176
      ],
      "parameters": {
        "jsCode": "const map = {};\n\nfor (const item of items) {\n  const p = item.json;\n\n  if (!p.id || !p.categories?.length) continue;\n\n  map[p.id] = {\n    category_id: p.categories[0].id,\n    category_name: p.categories[0].name\n  };\n}\n\n// Fees bucket\nmap[\"fee\"] = {\n  category_id: 99,\n  category_name: \"Fees\"\n};\n\nreturn [{ json: map }];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "885fb92a-a373-4630-a9be-6f4087f743bb",
      "name": "Run On Schedule",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -1024,
        -16
      ],
      "parameters": {
        "rule": {
          "interval": [
            {}
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "5a8176d7-9d22-4a76-8c93-b34f249c78f2",
      "name": "Report Configuration",
      "type": "n8n-nodes-base.set",
      "position": [
        -768,
        -16
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "e83b0e89-3aa5-4e07-82e1-414e4d548dad",
              "name": "granularity",
              "type": "string",
              "value": "days"
            },
            {
              "id": "084d2db4-d7f9-469b-8142-10747817b1f0",
              "name": "top_categories_limit",
              "type": "number",
              "value": 8
            },
            {
              "id": "4b5de47e-36bb-496b-a821-5fe6abb9dd00",
              "name": "top_performer_pct",
              "type": "number",
              "value": 10
            },
            {
              "id": "f61c5995-3ea2-419b-8db6-d28e3f33a8d3",
              "name": "momentum_pct",
              "type": "number",
              "value": 30
            },
            {
              "id": "6bc07124-26c7-4b2a-a05b-728fdd139f2c",
              "name": "steady_band",
              "type": "number",
              "value": 10
            },
            {
              "id": "81b2486b-abcf-41d8-881d-767d93602bba",
              "name": "at_risk_growth_pct",
              "type": "number",
              "value": -15
            },
            {
              "id": "77ce5d9a-823b-4afa-b5d9-2a86d05d4ed6",
              "name": "discontinue_growth_pct",
              "type": "number",
              "value": -25
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "f9885621-1e1c-45ed-8ac2-be22dc333199",
      "name": "Calculate Reporting Periods",
      "type": "n8n-nodes-base.code",
      "position": [
        -592,
        -16
      ],
      "parameters": {
        "jsCode": "// Read inputs\nconst granularity = ($json.granularity || 'days').toLowerCase();\nconst now = new Date();\n\nlet period_start, period_end;\nlet prev_period_start, prev_period_end;\n\n// Helper: start of today (UTC \u2013 morning)\nconst startOfTodayUTC = new Date(Date.UTC(\n  now.getUTCFullYear(),\n  now.getUTCMonth(),\n  now.getUTCDate(),\n  0, 0, 0\n));\n\n// DAILY \u2013 TEST MODE (UPDATED)\n// DAILY \u2013 SAFE TEST MODE (Woo-friendly)\nif (granularity === 'days') {\n\n  // Current Period = Yesterday 00:00 UTC \u2192 now\n  period_end = now;\n\n  period_start = new Date(Date.UTC(\n    now.getUTCFullYear(),\n    now.getUTCMonth(),\n    now.getUTCDate() - 1, //  buffer\n    0, 0, 0\n  ));\n\n  // Previous Period = 6 days before current start\n  prev_period_end = new Date(period_start);\n  prev_period_start = new Date(prev_period_end);\n  prev_period_start.setUTCDate(prev_period_start.getUTCDate() - 6);\n}\n\n\n// WEEKLY \u2013 calendar weeks\nelse if (granularity === 'weekly') {\n  const day = now.getUTCDay(); // 0 = Sunday\n  const diffToMonday = (day + 6) % 7;\n\n  period_end = new Date(Date.UTC(\n    now.getUTCFullYear(),\n    now.getUTCMonth(),\n    now.getUTCDate() - diffToMonday,\n    0, 0, 0\n  ));\n\n  period_start = new Date(period_end);\n  period_start.setUTCDate(period_start.getUTCDate() - 7);\n\n  prev_period_end = new Date(period_start);\n  prev_period_start = new Date(prev_period_end);\n  prev_period_start.setUTCDate(prev_period_start.getUTCDate() - 7);\n}\n\n//  MONTHLY \u2013 calendar months\nelse if (granularity === 'monthly') {\n  period_end = new Date(Date.UTC(\n    now.getUTCFullYear(),\n    now.getUTCMonth(),\n    1,\n    0, 0, 0\n  ));\n\n  period_start = new Date(period_end);\n  period_start.setUTCMonth(period_start.getUTCMonth() - 1);\n\n  prev_period_end = new Date(period_start);\n  prev_period_start = new Date(prev_period_end);\n  prev_period_start.setUTCMonth(prev_period_start.getUTCMonth() - 1);\n}\n\n// Safety\nelse {\n  throw new Error(`Unsupported granularity: ${granularity}`);\n}\n\n//  Output\nreturn [\n  {\n    json: {\n      granularity,\n      period_start: period_start.toISOString(),\n      period_end: period_end.toISOString(),\n      prev_period_start: prev_period_start.toISOString(),\n      prev_period_end: prev_period_end.toISOString(),\n      top_categories_limit: $json.top_categories_limit || 8\n    }\n  }\n];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "af7baea5-1e10-4396-8a29-97d6bc70086b",
      "name": "Fetch Orders \u2014 Current Period",
      "type": "n8n-nodes-base.wooCommerce",
      "position": [
        -256,
        -208
      ],
      "parameters": {
        "options": {
          "after": "={{ $json.period_start }}",
          "status": "completed"
        },
        "resource": "order",
        "operation": "getAll",
        "returnAll": true
      },
      "credentials": {
        "wooCommerceApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "a854efec-4411-4216-b153-68b02026a2fc",
      "name": "Fetch Orders \u2014 Previous Period",
      "type": "n8n-nodes-base.wooCommerce",
      "position": [
        -256,
        -16
      ],
      "parameters": {
        "options": {
          "after": "={{ $json.prev_period_start }}",
          "before": "={{ $json.prev_period_end }}",
          "status": "completed"
        },
        "resource": "order",
        "operation": "getAll"
      },
      "credentials": {
        "wooCommerceApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1,
      "alwaysOutputData": true
    },
    {
      "id": "5cc43700-36c5-4ffd-8ae5-52f74d797c91",
      "name": "Fetch Products",
      "type": "n8n-nodes-base.wooCommerce",
      "position": [
        -256,
        176
      ],
      "parameters": {
        "options": {},
        "operation": "getAll",
        "returnAll": true
      },
      "credentials": {
        "wooCommerceApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "de3b6e71-edd1-437d-8fca-99541c2988f9",
      "name": "Normalize Order Items \u2014 Current",
      "type": "n8n-nodes-base.code",
      "position": [
        -32,
        -208
      ],
      "parameters": {
        "jsCode": "// Initialize output array\nconst newItems = [];\n\n// Loop through all items from previous node\nfor (const item of items) {\n    const order = item.json; // actual order object\n\n    const orderInfo = {\n        order_id: order.id,\n        order_number: order.number,\n        order_total: parseFloat(order.total),\n        order_date: order.date_created,\n        currency: order.currency,\n        customer_id: order.customer_id,\n        billing_name: `${order.billing.first_name} ${order.billing.last_name}`,\n        shipping_name: `${order.shipping.first_name} ${order.shipping.last_name}`,\n    };\n\n    // Use line_items if present, else fee_lines\n    const itemsList = (order.line_items && order.line_items.length > 0)\n        ? order.line_items\n        : (order.fee_lines && order.fee_lines.length > 0 ? order.fee_lines : []);\n\n    if (itemsList.length === 0) continue; // skip empty orders\n\n    for (const itemLine of itemsList) {\n        newItems.push({\n            json: {\n                ...orderInfo,\n                item_id: itemLine.id,\n                item_name: itemLine.name,\n                item_qty: itemLine.quantity || 1,\n                item_total: parseFloat(itemLine.total || itemLine.amount || 0),\n                item_price: parseFloat(itemLine.price || itemLine.amount || 0),\n                item_type: (order.line_items && order.line_items.length > 0) ? 'product' : 'fee'\n            }\n        });\n    }\n}\n\nreturn newItems;\n"
      },
      "typeVersion": 2
    },
    {
      "id": "865c0cc0-43a8-46bc-b729-bc17c8eeb05f",
      "name": "Normalize Order Items \u2014 Previous",
      "type": "n8n-nodes-base.code",
      "position": [
        -32,
        -16
      ],
      "parameters": {
        "jsCode": "// Initialize output array\nconst newItems = [];\n\n// Loop through all items from previous node\nfor (const item of items) {\n    const order = item.json; // actual order object\n\n    const orderInfo = {\n        order_id: order.id,\n        order_number: order.number,\n        order_total: parseFloat(order.total),\n        order_date: order.date_created,\n        currency: order.currency,\n        customer_id: order.customer_id,\n        billing_name: `${order.billing.first_name} ${order.billing.last_name}`,\n        shipping_name: `${order.shipping.first_name} ${order.shipping.last_name}`,\n    };\n\n    // Use line_items if present, else fee_lines\n    const itemsList = (order.line_items && order.line_items.length > 0)\n        ? order.line_items\n        : (order.fee_lines && order.fee_lines.length > 0 ? order.fee_lines : []);\n\n    if (itemsList.length === 0) continue; // skip empty orders\n\n    for (const itemLine of itemsList) {\n        newItems.push({\n            json: {\n                ...orderInfo,\n                item_id: itemLine.id,\n                item_name: itemLine.name,\n                item_qty: itemLine.quantity || 1,\n                item_total: parseFloat(itemLine.total || itemLine.amount || 0),\n                item_price: parseFloat(itemLine.price || itemLine.amount || 0),\n                item_type: (order.line_items && order.line_items.length > 0) ? 'product' : 'fee'\n            }\n        });\n    }\n}\n\nreturn newItems;\n"
      },
      "typeVersion": 2
    },
    {
      "id": "b79ad266-8618-40a7-b70f-ca86d27f04fd",
      "name": "Combine Orders & Products",
      "type": "n8n-nodes-base.merge",
      "position": [
        288,
        -32
      ],
      "parameters": {
        "numberInputs": 3
      },
      "typeVersion": 3.2
    },
    {
      "id": "23d57db4-1d00-4cf8-bd67-c732d55f7458",
      "name": "Aggregate Sales by Category",
      "type": "n8n-nodes-base.code",
      "position": [
        544,
        -16
      ],
      "parameters": {
        "jsCode": "/* ======================================\n   INPUTS\n   ====================================== */\n\n// Get current and previous normalized rows\nconst currentRows = $items(\"Normalize Order Items \u2014 Current\").map(i => i.json);\nconst previousRows = $items(\"Normalize Order Items \u2014 Previous\").map(i => i.json);\n\n// Get Product \u2192 Category map if exists (optional)\nconst productCategoryMapInput = $items(\"Category Map\")?.[0]?.json || {};\n\n/* ======================================\n   BUILD DYNAMIC PRODUCT \u2192 CATEGORY MAP\n   ====================================== */\n\nconst productCategoryMap = { ...productCategoryMapInput };\n\n// Scan current and previous rows for products and add unmapped ones dynamically\nfor (const row of [...currentRows, ...previousRows]) {\n  if (row.item_type === \"product\" && !productCategoryMap[row.item_id]) {\n    // Dynamically create category for unmapped product\n    // Replace this with actual logic to fetch from WooCommerce if needed\n    // For now, we use \"Unmapped\" for any missing product\n    productCategoryMap[row.item_id] = {\n      category_id: row.item_id, // temporary: use product_id as category_id\n      category_name: row.item_name // temporary: use product_name as category_name\n    };\n  }\n}\n\n// Always include fees bucket\nproductCategoryMap[\"fee\"] = { category_id: 99, category_name: \"Fees\" };\n\n/* ======================================\n   HELPER: AGGREGATE ROWS BY CATEGORY\n   ====================================== */\nfunction aggregate(rows) {\n  const agg = {};\n\n  for (const r of rows) {\n    const key = r.item_type === \"fee\" ? \"fee\" : r.item_id;\n    const cat = productCategoryMap[key] || { category_id: \"unmapped\", category_name: \"Unmapped\" };\n    const cid = String(cat.category_id);\n\n    if (!agg[cid]) {\n      agg[cid] = {\n        category_id: cid,\n        category_name: cat.category_name,\n        revenue: 0,\n        units_sold: 0,\n        orders: new Set()\n      };\n    }\n\n    agg[cid].revenue += Number(r.item_total || 0);\n    agg[cid].units_sold += Number(r.item_qty || 0);\n    agg[cid].orders.add(r.order_id);\n  }\n\n  // Convert orders Set \u2192 number\n  for (const c of Object.values(agg)) {\n    c.orders = c.orders.size;\n  }\n\n  return agg;\n}\n\n/* ======================================\n   AGGREGATE CURRENT & PREVIOUS\n   ====================================== */\nconst curAgg = aggregate(currentRows);\nconst prevAgg = aggregate(previousRows);\n\n// Combine all category IDs\nconst allCategoryIds = new Set([...Object.keys(curAgg), ...Object.keys(prevAgg)]);\n\n// Total current revenue for share_of_total\nconst totalCurrentRevenue = Object.values(curAgg).reduce((s, c) => s + c.revenue, 0);\n\n/* ======================================\n   BUILD FINAL OUTPUT\n   ====================================== */\nconst result = [];\n\nfor (const cid of allCategoryIds) {\n  const cur = curAgg[cid] || { revenue: 0, units_sold: 0, orders: 0, category_name: prevAgg[cid]?.category_name || \"Unmapped\" };\n  const prev = prevAgg[cid] || { revenue: 0, units_sold: 0, orders: 0, category_name: cur.category_name };\n\n  const abs_change = cur.revenue - prev.revenue;\n  const pct_change = prev.revenue === 0 ? \"n/a\" : ((abs_change / prev.revenue) * 100).toFixed(2);\n  const share_of_total = totalCurrentRevenue === 0 ? \"0.00\" : ((cur.revenue / totalCurrentRevenue) * 100).toFixed(2);\n\n  result.push({\n    category_id: cid,\n    category_name: cur.category_name,\n    revenue_current: cur.revenue,\n    revenue_prev: prev.revenue,\n    abs_change,\n    pct_change,\n    units_sold: cur.units_sold,\n    orders_current: cur.orders,\n    share_of_total\n  });\n}\n\n/* ======================================\n   RETURN ITEMS FOR N8N\n   ====================================== */\nreturn result.map(r => ({ json: r }));\n"
      },
      "typeVersion": 2
    },
    {
      "id": "8aedb373-070a-4188-886c-bf85e6071168",
      "name": "Classify Category Performance",
      "type": "n8n-nodes-base.code",
      "position": [
        736,
        -16
      ],
      "parameters": {
        "jsCode": "const categories = $items(\"Aggregate Sales by Category\").map(i => i.json);\n\n// Sort categories by revenue_current descending\ncategories.sort((a, b) => b.revenue_current - a.revenue_current);\n\n// Only categories with positive current revenue\nconst positiveRevenueCategories = categories.filter(c => c.revenue_current > 0);\nconst totalPositive = positiveRevenueCategories.length;\n\n// Generate rank map\nconst rankMap = {};\npositiveRevenueCategories.forEach((c, idx) => {\n    rankMap[c.category_id] = idx;\n});\n\n// Dynamic thresholds\nconst thresholds = {\n    top: 0.2,      // top 20%\n    momentum: 0.5, // top 50%\n    steady: 0.8    // top 80%\n};\n\ncategories.forEach(cat => {\n    let tag = \"\";\n    let recommended_action = \"\";\n\n    if (cat.revenue_current === 0 && cat.revenue_prev > 0) {\n        // Category had revenue before but 0 now\n        tag = \"Consider Discontinue\";\n        recommended_action = \"Investigate returns or delisting\";\n    } else if (cat.revenue_current === 0 && cat.revenue_prev === 0) {\n        // Category never sold\n        tag = \"At Risk\";\n        recommended_action = \"Promotions or marketing\";\n    } else {\n        // Category has revenue now (revenue_current > 0)\n        const rank = rankMap[cat.category_id];\n        const rankPercent = totalPositive > 1 ? (rank + 1) / totalPositive : 0;\n\n        if (rankPercent <= thresholds.top) {\n            tag = \"Top Performer\";\n            recommended_action = \"Feature on homepage\";\n        } else if (rankPercent <= thresholds.momentum) {\n            tag = \"Momentum\";\n            recommended_action = \"Investigate increasing stock\";\n        } else if (rankPercent <= thresholds.steady) {\n            tag = \"Steady\";\n            recommended_action = \"Maintain current strategy\";\n        } else {\n            tag = \"At Risk\";\n            recommended_action = \"Promotions or marketing\";\n        }\n    }\n\n    cat.tag = tag;\n    cat.recommended_action = recommended_action;\n});\n\nreturn categories.map(c => ({ json: c }));\n"
      },
      "typeVersion": 2
    },
    {
      "id": "fc6b5a47-97f3-4b45-a867-d2ba473833ca",
      "name": "Prepare Fields for Reporting",
      "type": "n8n-nodes-base.set",
      "position": [
        944,
        -16
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "72e7696b-0045-4f17-90c3-e41b4e3dd8a2",
              "name": "category_id",
              "type": "string",
              "value": "={{ $json.category_id }}"
            },
            {
              "id": "83ef1840-c74c-4dd8-9d1f-843696194a1d",
              "name": "revenue_current",
              "type": "number",
              "value": "={{ $json.revenue_current }}"
            },
            {
              "id": "cdd10e1b-7030-4053-9824-7d9ae1c0d792",
              "name": "revenue_prev",
              "type": "number",
              "value": "={{ $json.revenue_prev }}"
            },
            {
              "id": "6b10bdd7-7a77-4d6a-8d1e-e098018402b3",
              "name": "units_sold",
              "type": "number",
              "value": "={{ $json.units_sold }}"
            },
            {
              "id": "f0208637-1721-4008-803d-fe7ff7a9a4e9",
              "name": "share_of_total",
              "type": "string",
              "value": "={{ $json.share_of_total }}"
            },
            {
              "id": "9e65e3dc-0b3e-4243-8382-babc08dbc799",
              "name": "tag",
              "type": "string",
              "value": "={{ $json.tag }}"
            },
            {
              "id": "75acd458-0373-40dc-b99a-7c5761c54901",
              "name": "recommended_action",
              "type": "string",
              "value": "={{ $json.recommended_action }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "5151087f-6f04-4d13-955e-4f8fe67d8b8f",
      "name": "Save Results to Airtable",
      "type": "n8n-nodes-base.airtable",
      "position": [
        1184,
        -16
      ],
      "parameters": {
        "base": {
          "__rl": true,
          "mode": "list",
          "value": "appF2iYPgVqqyXDC1",
          "cachedResultUrl": "https://airtable.com/appF2iYPgVqqyXDC1",
          "cachedResultName": "n8n Demo"
        },
        "table": {
          "__rl": true,
          "mode": "list",
          "value": "tbloZJJZU0wCvi2qd",
          "cachedResultUrl": "https://airtable.com/appF2iYPgVqqyXDC1/tbloZJJZU0wCvi2qd",
          "cachedResultName": "Category Performance"
        },
        "columns": {
          "value": {
            "Tag": "={{ $json.tag }}",
            "Share": "={{ $json.share_of_total }}",
            "Units": "={{ $json.units_sold }}",
            "Action ": "={{ $json.recommended_action }}",
            "Category Id": "={{ $json.category_id }}",
            "Current Revenue": "={{ $json.revenue_current }}",
            "Previous Revenue": "={{ $json.revenue_prev }}"
          },
          "schema": [
            {
              "id": "Category Id",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Category Id",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Current Revenue",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Current Revenue",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Previous Revenue",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Previous Revenue",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Units",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Units",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Share",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Share",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Tag",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Tag",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Action ",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Action ",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "create"
      },
      "credentials": {
        "airtableTokenApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "7735c6ef-f872-487b-8f38-f377452dd0ac",
      "name": "Build Slack Summary",
      "type": "n8n-nodes-base.code",
      "position": [
        1424,
        -16
      ],
      "parameters": {
        "jsCode": "// Airtable rows come as: item.fields\nconst rows = items.map(i => i.json.fields);\n\n// Helpers\nconst formatMoney = (v) =>\n  typeof v === \"number\" ? `\u20b9${v.toLocaleString(\"en-IN\")}` : \"\u20b90\";\n\n// Split categories\nconst top = rows\n  .filter(r => r[\"Current Revenue\"] > 0)\n  .sort((a, b) => b[\"Current Revenue\"] - a[\"Current Revenue\"])\n  .slice(0, 5);\n\nconst risk = rows.filter(r =>\n  [\"At Risk\", \"Consider Discontinue\"].includes(r[\"Tag\"])\n);\n\n// Build text\nlet text = `*Category Performance Summary*\\n\\n`;\n\ntext += `*Top Categories*\\n`;\nif (top.length === 0) {\n  text += `\u2022 No sales in current period\\n`;\n} else {\n  for (const r of top) {\n    const prev = r[\"Previous Revenue\"];\n    const curr = r[\"Current Revenue\"];\n\n    const pct =\n      prev > 0\n        ? `${(((curr - prev) / prev) * 100).toFixed(1)}%`\n        : \"n/a\";\n\n    text += `\u2022 *Category ${r[\"Category Id\"]}* \u2014 ${formatMoney(curr)} (${pct}) | ${r[\"Tag\"]}\\n`;\n  }\n}\n\nif (risk.length) {\n  text += `\\n *Needs Attention*\\n`;\n  for (const r of risk) {\n    text += `\u2022 *Category ${r[\"Category Id\"]}* \u2014 ${formatMoney(\n      r[\"Current Revenue\"]\n    )} | ${r[\"Action \"]}\\n`;\n  }\n}\n\ntext += `\\n Full report available in Airtable.`;\n\nreturn [{ json: { text } }];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "2173ef7a-fbf2-4d79-afa2-c9b6ff4d975c",
      "name": "Send Report to Slack",
      "type": "n8n-nodes-base.slack",
      "position": [
        1664,
        -16
      ],
      "parameters": {
        "text": "={{ $json.text }}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C09S57E2JQ2",
          "cachedResultName": "n8n"
        },
        "otherOptions": {
          "includeLinkToWorkflow": false
        }
      },
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "3ccb02a4-6ad4-4765-94c9-bda0ba405603",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1088,
        -112
      ],
      "parameters": {
        "color": 7,
        "height": 240,
        "content": "Starts the workflow automatically on a fixed schedule (daily / weekly)."
      },
      "typeVersion": 1
    },
    {
      "id": "e7ef5742-3f3d-4a04-9529-e57addc8ed2b",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -816,
        -128
      ],
      "parameters": {
        "color": 7,
        "width": 368,
        "height": 272,
        "content": "Sets the report rules and automatically calculates current and previous date ranges for sales comparison."
      },
      "typeVersion": 1
    },
    {
      "id": "01bd5059-3216-40d5-8edb-41dc1ac5dd90",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -416,
        -432
      ],
      "parameters": {
        "color": 7,
        "width": 848,
        "height": 784,
        "content": "## Fetch & Normalize \n\n**Fetch Orders \u2014 Current Period:** Fetches completed WooCommerce orders from the current time period.\n**Fetch Orders \u2014 Previous Period:** Fetches completed WooCommerce orders from the previous comparison period.\n**Fetch Products:** Loads product and category details from WooCommerce.\n**Normalize Order Items \u2014 Current:** Breaks current orders into individual product-level sales rows.\n**Normalize Order Items \u2014 Previous:** Breaks previous orders into individual product-level sales rows. \n**Category Map:** Maps each product to its category for accurate category reporting.\n**Combine Orders & Products:** Combines current and previous sales data into a single stream."
      },
      "typeVersion": 1
    },
    {
      "id": "22726a24-2718-4616-8324-382065a42298",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        480,
        -288
      ],
      "parameters": {
        "color": 7,
        "width": 1376,
        "height": 496,
        "content": "## Category Performance Analysis & Reporting \n\n**Aggregate Sales by Category:** Calculates total revenue, units sold and orders per category.\n**Classify Category Performance:** Labels categories as Top Performer, Momentum, Steady or At Risk.\n**Prepare Fields for Reporting:** Formats and selects final fields needed for reports and storage.\n**Save Results to Airtable:** Saves category performance results to Airtable for tracking and history.\n**Build Slack Summary:** Creates a short, readable summary of category performance.\n**Send Report to Slack:** Sends the final performance summary to the Slack channel."
      },
      "typeVersion": 1
    },
    {
      "id": "63bd8b58-2c0a-48f9-9b8a-e53837882118",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1776,
        -864
      ],
      "parameters": {
        "width": 576,
        "height": 864,
        "content": "# How it Works\n\n### This workflow automatically analyzes sales data by product category and compares current performance with the previous period (weekly or monthly).\n### It pulls orders for two time periods (current and previous), groups sales by category and calculates key metrics such as revenue, units sold and share of total sales.\n### Based on these results, each category is classified (for example: Top Performer, Steady or Needs Attention) along with a recommended action.\n ### The final results are saved to Airtable for tracking, reporting and historical comparison.\n### A short, easy-to-read summary is generated and sent to Slack, allowing stakeholders to quickly see what\u2019s performing well and what needs attention\u2014without opening dashboards or detailed reports.\n### This workflow runs automatically and ensures everyone receives consistent, up-to-date performance insights.\n\n\n# Setup steps\n\n**1.** Configure the date range (current period and previous days) in the date range node.\n\n**2.** Connect your order source (e.g., WooCommerce or API) for both current and previous periods.\n\n**3.** Make sure categories and revenue fields match your order data structure.\n\n**4.** Connect Airtable and map fields for category metrics and actions.\n\n**5.** Connect Slack and select the channel where summaries should be posted.\n\n**6.** Test once with recent data, then activate the workflow."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "40a78233-f5fa-4cdc-8093-6d4276c84deb",
  "connections": {
    "Category Map": {
      "main": [
        [
          {
            "node": "Combine Orders & Products",
            "type": "main",
            "index": 2
          }
        ]
      ]
    },
    "Fetch Products": {
      "main": [
        [
          {
            "node": "Category Map",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Run On Schedule": {
      "main": [
        [
          {
            "node": "Report Configuration",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Slack Summary": {
      "main": [
        [
          {
            "node": "Send Report to Slack",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Report Configuration": {
      "main": [
        [
          {
            "node": "Calculate Reporting Periods",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save Results to Airtable": {
      "main": [
        [
          {
            "node": "Build Slack Summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Combine Orders & Products": {
      "main": [
        [
          {
            "node": "Aggregate Sales by Category",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate Sales by Category": {
      "main": [
        [
          {
            "node": "Classify Category Performance",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calculate Reporting Periods": {
      "main": [
        [
          {
            "node": "Fetch Orders \u2014 Previous Period",
            "type": "main",
            "index": 0
          },
          {
            "node": "Fetch Orders \u2014 Current Period",
            "type": "main",
            "index": 0
          },
          {
            "node": "Fetch Products",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Fields for Reporting": {
      "main": [
        [
          {
            "node": "Save Results to Airtable",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Classify Category Performance": {
      "main": [
        [
          {
            "node": "Prepare Fields for Reporting",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Orders \u2014 Current Period": {
      "main": [
        [
          {
            "node": "Normalize Order Items \u2014 Current",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Orders \u2014 Previous Period": {
      "main": [
        [
          {
            "node": "Normalize Order Items \u2014 Previous",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Order Items \u2014 Current": {
      "main": [
        [
          {
            "node": "Combine Orders & Products",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Order Items \u2014 Previous": {
      "main": [
        [
          {
            "node": "Combine Orders & Products",
            "type": "main",
            "index": 1
          }
        ]
      ]
    }
  }
}